TIM Labs

2017年5月アーカイブ

前回、コマンドラインからプーリングのサイズを指定できるようになったので、3x3, 4x4,....のプーリングを試してみよう。
サイズを大きくするということは、データが集約されて、肝心の情報が無視されてしまうような気もするのだが。

まず、3x3の場合。プーリングサイズ以外は、以前と同じ状態だ。


CIFAR-10$ python train_cifar5.py -g 0 -u 500 500 -p 3
GPU: 0
# number: 50000
# units: [500, 500]
# pooling: 3
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.36881     1.05861               0.50406        0.6211                    9.16741       
2           0.941233    0.887284              0.66934        0.6877                    18.1766       
3           0.765903    0.862215              0.73098        0.6993                    27.2039       
4           0.628062    0.834987              0.7782         0.715                     36.3343       
5           0.491332    0.892278              0.82718        0.7122                    45.5346       
6           0.369885    0.987892              0.86914        0.7141                    54.6321       
7           0.258368    1.06508               0.909741       0.7145                    63.7151       
8           0.185255    1.28405               0.935921       0.7027                    72.7915       
9           0.134805    1.41083               0.953181       0.7086                    81.8067       
10          0.102211    1.61051               0.964881       0.7063                    90.8323       
11          0.0954875   1.59982               0.966341       0.7073                    99.89         
12          0.0744035   1.87134               0.974921       0.6999                    108.908       
13          0.0799358   1.87029               0.972621       0.7059                    117.916       
14          0.064477    1.79121               0.977761       0.706                     126.942       
15          0.0539574   1.99034               0.98168        0.6957                    135.992       
16          0.0564298   2.12325               0.981061       0.7012                    145.031       
17          0.0636167   1.98566               0.978781       0.6953                    154.082       
18          0.0543167   2.06661               0.98182        0.6956                    163.078       
19          0.0525986   2.02431               0.982701       0.7004                    172.108       
20          0.0473705   2.20115               0.98364        0.6995                    181.123       
CIFAR-10$ 

accuracy-CNNp3x3.png
3x3にしたら、テスト精度が70%を超え、71.5%まで達した。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第7章、一般化線形混合モデル(GLMM)についてのまとめの五回目です。

この章では複数の分布を混ぜて使う、一般化線形混合モデルついて説明がされています。 なので、二項分布と正規分布を掛けた値をグラフにしてくれるコードを用意しました。 コードはRで書きました。

N <- 20

linkFunction <- function(b1, b2, x) {
  function(r) {
    1 / (1 + exp(-b1 - b2 * x - r))
  }
}

distribution <- function(y, linkFunction) {
  l <- match.fun(linkFunction)
  function(r) {
    dbinom(y, N, l(r)) * dnorm(r, mean = 0, sd = 2)
  }
}

xl <- "r"
yl <- "Probability"
ys <- seq(0, 20, by = 5)
rs <- seq(-5, 5, by = 0.5)
legends = paste0("y = ", ys)
for (b1 in seq(-0.5, 0.5, by = 0.2)) {
  for (b2 in seq(-0.5, 0.5, by = 0.2)) {
    for (x in 0:3) {
      l <- linkFunction(b1, b2, x)
      title <- paste0("logit(q) = ", b1, " + ", b2, " * ", x, " + r, r ~ N(0, 2)")
      plot(0, 0, type = "n", xlim = c(-5, 5), ylim = c(0.0, 0.1), main = title, xlab = xl, ylab = yl)
      for (i in 1:5) {
        d <- distribution(ys[i], l)
        lines(rs, d(rs), type = "l")
        points(rs, d(rs), pch = ys[i])
      }
      legend("topright", legend = legends, pch = ys)
    }
  }
}

リンク関数は logit(q) = b1 + b2 * x + r です。 r は平均 0、標準偏差 2 の正規分布から生成される変数です。 事象の試行回数は 20 にしました。 リンク関数を使って二項分布する事象の発生確率 q が求まれば、事象が y 回起こる確率を計算できます。 あとは、正規分布から r が生成される確率と、二項分布を掛け合わせれば、欲しい値になります。

様々な b1b2x を与えて、r に対して確率をプロットしてみました。 コードを実行すると144枚のグラフがプロットされます。 例えば次のようなグラフが得られます。

mixed_2.png

y によってグラフの山の大きさが変わってくるのが分かります。 混合分布はこれを r で積分したものです

プーリングのサイズを2や3で解説している例が多い。
たぶん、サイズを大きくしてしまうと、目が荒くなりすぎて、情報が漏れてしまう気がする。
でも、サイズが大きくなると、どのようにダメになっていくのもきちんと確認すべきではないだろうか。
ということで、コマンドラインからプーリングのサイズを指定できるようにした。

つまり、コマンド行で、 -p でプーリングサイズを指定できるようにしようという訳。
CIFAR-10$ python train_cifar5.py -g 0 -u 500 500 -p 4
GPU: 0
# number: 50000
# units: [500, 500]
# pooling: 4
# Minibatch-size: 100
# epoch: 20

プログラムは、最後にまとめて示す。
CNNオブジェクト(model)を生成するときに、プーリングサイズを渡すようにした。
渡されたプーリングのサイズはCNNオブジェクトの変数に記憶させておき、実際にプーリング処理をするときにサイズを参照するようにした。

プーリングサイズを大きくしていくな何が起きるのか、予想をしよう。
 サイズは、2か3あたりが良い。 それとも、もっと大きくして、7,8,9あたりの方が実は良いとか。
さすがに画像サイズと同程度になってしまったら、ダメだろうな。

実験の報告は、次回からになる。

以下が、プログラム全体である。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第7章、一般化線形混合モデル(GLMM)についてのまとめの四回目です。

この章では複数の分布を混ぜて使う、一般化線形混合モデルついて説明がされています。 まずは二項分布と正規分布を混ぜた混合分布について解説されています。 なので、混合分布をグラフにしてみたいところですが、やっぱり変数がたくさんあって複雑なので、まずは感覚をつかむために数値を羅列してくれるコードを用意しました。 コードはRで書きました。

linkFunction <- function(b1, b2, x) {
  function(r) {
    1 / (1 + exp(-b1 - b2 * x - r))
  }
}

l <- linkFunction(-0.5, 0.3, 2)

cat("r\ty=0\t\ty=1\t\ty=2\t\ty=3\n")
sum <- vector(length = 4)
for (r in seq(-5, 5, by = 0.5)) {
  q <- l(r)
  cat(r, "\t")
  for (y in 0:3) {
    b <- dbinom(y, 3, q)
    n <- dnorm(r, mean = 0, sd = 2)
    mixture <- b * n
    sum[y + 1] <- sum[y + 1] + mixture * 0.5
    cat(mixture, "\t")
  }
  cat("\n")
}
cat("sum\t", sum[1], "\t", sum[2], "\t", sum[3], "\t", sum[4], "\n")

リンク関数は xr を変数に含んでいます。 x2 に固定した場合について計算しました。 なので、あとは r を与えれば、二項分布している事象の発生確率 q が求まります。 r は正規分布する変数です。 今回はr は平均 0、標準偏差 2 の正規分布から得られるとしました。 事象の試行回数は 3 にして、0 回から 3 回事象が発生する場合の確率を計算して出力しています。 r-5 から 5 まで、0.5 刻みで計算しました。 コードを実行すると、次の出力が得られます。

r       y=0             y=1             y=2             y=3
-5      0.008571241     0.0001914794    1.425867e-06    3.539279e-09    
-4.5    0.01529937      0.0005635068    6.918364e-06    2.831304e-08    
-4      0.02542036      0.00154367      3.124683e-05    2.108318e-07    
-3.5    0.03909264      0.003913947     0.0001306212    1.453086e-06    
-3      0.05514584      0.009102904     0.0005008711    9.186514e-06    
-2.5    0.07038014      0.01915423      0.001737632     5.254481e-05    
-2      0.07963943      0.03573468      0.005344786     0.0002664708    
-1.5    0.07772425      0.05749969      0.01417925      0.00116552  
-1      0.06325714      0.0771553       0.031369        0.004251228     
-0.5    0.04148674      0.08342818      0.05592358      0.01249557  
0       0.02138051      0.07088734      0.07834263      0.02886066  
0.5     0.008601664     0.04701976      0.08567559      0.05203704  
1       0.002741934     0.02471168      0.07423798      0.07434107  
1.5     0.0007137071    0.01060504      0.05252712      0.08672284  
2       0.0001570974    0.003848653     0.03142876      0.08555086  
2.5     3.018185e-05    0.001219082     0.0164134       0.07366188  
3       5.187402e-06    0.0003454491    0.007668262     0.0567399   
3.5     8.116416e-07    8.911395e-05    0.003261413     0.03978732  
4       1.169644e-07    2.1173e-05      0.001277585     0.02569661  
4.5     1.564146e-08    4.66824e-06     0.0004644167    0.01540073  
5       1.950226e-09    9.596395e-07    0.0001574019    0.008605787     
sum      0.2548242       0.2235203       0.2303399      0.2828235 

r の増加に従って q も増加するので、r が小さいうちは事象の発生回数は少ない方が確率が高く、r が大きくなると多い方が確率が高くなるのが分かります。 混合分布では、r についての積分がとられるので、最後の行にざっくりとした積分の近似値を出力しておきました。 二項分布と正規分布の混合分布は、だいたいこういう数字になります。

前回は、プーリングの説明をした。
今回からは、プーリングの実装について書く。
といっても、僅かな変更を加えるだけである。

とりあえす、プーリングのサイズが2の場合を試す。
# Network definition
class CNN(chainer.Chain):

    def __init__(self, n_units, n_out):
        super(CNN, self).__init__(
            conv1=L.Convolution2D(None, 36, 3),     # 畳み込み
            conv2=L.Convolution2D(None, 36, 4),     # 畳み込み
            l1=L.Linear(None, n_units[0]),  #            -> n_units[0]
            l2=L.Linear(None, n_units[1]),  # n_units[0] -> n_units[1]
            l3=L.Linear(None, n_out),  	    # n_units[1] -> n_out
        )
        
    def __call__(self, x):
        c1 = F.relu(self.conv1(x))                  # 畳み込み
        c2 = F.relu(self.conv2(c1))                 # 畳み込み
        p1 = F.max_pooling_2d(c2,2)                 # プーリング
        h1 = F.relu(self.l1(p1))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

畳み込みでは、1層増やす毎に、__init__() と __call__() が1行ずつ増えたのだが、プーリングでは、__call__()の中に1行書き加えただけである。 畳み込みの2回目が終わって、全結合層に入る直前にプーリング層を入れている。 何も説明することがないくらい簡単で困る。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第7章、一般化線形混合モデル(GLMM)についてのまとめの三回目です。

この章では複数の分布を混ぜて使う、一般化線形混合モデルついて説明がされています。 なので、複数の分布を混ぜた分布をグラフにしてみたいところです。 ですが、変数の数が多くて複雑なので、まずは感覚をつかむために数値を羅列してくれるコードを用意しました。 コードはRで書きました。

linkFunction <- function(b1, b2) {
  function(x, r) {
    1 / (1 + exp(-b1 - b2 * x - r))
  }
}

l <- linkFunction(-0.5, 0.3)

cat("r\tq\t\tdbinom\t\tdnorm\t\tmixed\n")
for (r in seq(-1, 1, by = 0.1)) {
  q <- l(0.8, r)
  b <- dbinom(7, 20, q)
  n <- dnorm(r, mean = 0, sd = 1)
  mixed <- b * n
  cat(r, "\t", q, "\t", b, "\t", n, "\t", mixed, "\n")
}

このコードは二項分布と正規分布を掛けて得られる数値を出力します。 二項分布のパラメータである、事象の発生確率 q はリンク関数から得られます。 リンク関数には xr という変数があって、r の方が正規分布から生成されます。 計算したのは、dbinom(7, 20, q)dnorm(r, 0, 1) の積です。 コードを実行すると次の出力が得られます。

r       q           dbinom          dnorm       mixed
-1       0.2209739   0.07762054      0.2419707   0.0187819 
-0.9     0.2386673   0.0987262       0.2660852   0.02626959 
-0.8     0.2573095   0.1210895       0.2896916   0.03507862 
-0.7     0.2768782   0.1429499       0.3122539   0.04463668 
-0.6     0.2973393   0.1621339       0.3332246   0.05402699 
-0.5     0.3186463   0.1763681       0.3520653   0.06209308 
-0.4     0.3407396   0.1837016       0.3682701   0.06765181 
-0.3     0.3635475   0.1829363       0.3813878   0.06976967 
-0.2     0.3869858   0.1739386       0.3910427   0.06801741 
-0.1     0.4109596   0.1577226       0.3969525   0.06260837 
0        0.4353637   0.1362616       0.3989423   0.05436053 
0.1      0.4600851   0.1120753       0.3969525   0.04448858 
0.2      0.4850045   0.08771599      0.3910427   0.0343007 
0.3      0.5099987   0.06530726      0.3813878   0.02490739 
0.4      0.5349429   0.04625377      0.3682701   0.01703388 
0.5      0.5597136   0.03116972      0.3520653   0.01097378 
0.6      0.5841905   0.01999504      0.3332246   0.00666284 
0.7      0.608259    0.01221858      0.3122539   0.003815299 
0.8      0.6318124   0.007119175     0.2896916   0.002062365 
0.9      0.6547535   0.003959494     0.2660852   0.001053563 
1        0.6769959   0.00210485      0.2419707   0.000509312 

r に対して q は単調に増加しています。 二項分布の部分で計算しているのは、事象が 20 回中 7 回起きる確率なので、q が小さい時は小さい値を取ります。 q が増加すると、だんだん確率は上がりますが、増加し過ぎると 7 回より多い回数、事象が発生しやすくなるので、あるところで最大をとって、後は下がります。 正規分布は 0 を中心とした山なりの分布なので、r0 に近い値なら確率は高く、そこから離れるほど小さくなります。

この二つを掛け合わせた値は、だいたいどっちも大きな値を取るときに大きいですが、どちらの最大とも微妙にずれたところに最大がきているのが分かります。

Deep Learningで利用する技術はまだまだあるらしい。
その中で、畳み込みとセットで使われることが多いのがPoolingなのだと。

ということで、以下の画像はスタンフォード大からのものである。
CS231n: Convolutional Neural Networks for Visual Recognition
がこの世界で非常に有名な講義であり、githubにあったものだ。

その中に、Pooling Layerという項目がある。
今回は、これを攻略してみようと思う。

Poolingにもいくつかのタイプがあるのだが、今回はよく使われているというmax pooling についてだ。
まず、Stanford大にあった説明図を示す。

maxpool-stanford-1.jpegこれは、2x2のフィルタのときの例である。
フィルタは正方形に限るようで、この場合、単にサイズ2という。
フィルタのサイズで、元の画像(配列データ)を区分けし、各区分けの中で一番大きい値を採用するものだ。

これは最大値を採用しているが、平均値を使うものもあり、average pooling と呼ぶ。

サイズ2のフィルタだと、縦、横1/2になり、全体で1/4になる。つまり、入力に比べてかなり小さくなる。
サイズ3のフィルタだと、縦、横1/3になり、全体で1/9になる。
つまり、データがかなり小さくなる。

上の図は1チャネルだけを示しているが、実際のデータは多数のチャネルが重なっているので、次図のようになる。

maxpool-stanford-2.jpeg
この操作により、データのボリュームはぐんと小さくなる。
ということは、大切な情報、微妙な情報が抜け落ちてしまい、結果が悪くならないだろうか?

という懸念が当然起きると思うが、皆さん使っているようなので、多分大丈夫なのだろう。
次回、やってみよう。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第7章、一般化線形混合モデル(GLMM)についてのまとめの二回目です。

この章では複数の分布を混ぜて使う、一般化線形混合モデルついて説明がされています。 まずはロジスティック回帰にパラメータを追加したモデルについて説明されています。 このモデルでは、リンク関数に新しいパラメータが追加されて、そのパラメータは正規分布から生成されます。 なので、いろいろなパラメータについて、その値をプロットしてくれるコードを用意しました。 コードはRで書きました。

linkFunction <- function(b1, b2) {
  function(x, r) {
    1 / (1 + exp(-b1 - b2 * x - r))
  }
}

xl <- "x"
yl <- "q"
xs <- runif(100, 0, 10)
ys <- function(r) {
  linkFunction(-0.5, 0.3)(xs, r)
}
for (sd in seq(0.1, 2.0, by = 0.1)) {
  rs <- rnorm(100, mean = 0, sd = sd)
  ps <- data.frame(x = xs, y = ys(rs))
  title <- paste0("logit(q) = -0.5 + 0.3 * x + r, r ~ N(0, ", sd, ")")
  plot(0, 0, type = "n", xlim = c(0, 10), ylim = c(0.0, 1.0), main = title, xlab = xl, ylab = yl)
  points(ps, pch = 1)
}

コードを実行すると図が20枚プロットされます。 横軸はリンク関数に使われている説明変数 x で、縦軸は事象の発生確率 q です。 q には x の他にも、平均 0 の正規分布から生成された乱数の影響があります。 この乱数が説明変数として考慮されていない影響を表します。 例えば以下のような図がプロットされます。

logit_2.png

この図は分散が 0.2 の正規分布から生成された乱数がリンク関数を通して q に影響している場合です。 x の増加に対して q が緩やかに上昇する傾向が見て取れます。 これは説明変数以外の要因の影響が少ない場合です。 一方、以下のような図もプロットされます。

logit_10.png

この図は分散が 1.0 の場合です。 xq の関係が分かりにくくなりました。 これは説明変数以外の要因の影響が多い場合です。 このように、パラメータを生成する正規分布の分散によって、説明変数として考慮されていない要因の影響が大きい場合や小さい場合を表現できるのが分かります。

前回、畳み込み層を1層入れたら、10%精度が向上した。
ということは、もう1層追加したら、さらに精度が上がるかも知れない。

では、さっそく追加してみよう。

# Network definition
class CNN(chainer.Chain):

    def __init__(self, n_units, n_out):
        super(CNN, self).__init__(
            conv1=L.Convolution2D(None, 36, 3),     # 追加行
            conv2=L.Convolution2D(None, 36, 3),     # 追加行
            l1=L.Linear(None, n_units[0]),  # n_in -> n_units[0]
            l2=L.Linear(None, n_units[1]),  # n_units[0] -> n_units[1]
            l3=L.Linear(None, n_out),  	    # n_units[1] -> n_out
        )
        
    def __call__(self, x):
        c1 = F.relu(self.conv1(x))                  # 追加行
        c2 = F.relu(self.conv2(c1))                 # 追加行
        h1 = F.relu(self.l1(c2))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)
変更は僅かであり、説明する必要もないだろう。 1層のときの出力チャネル数は48だったが、今回は36チャンネルにした。 チャネル数をいくつにするのがベストなのかよくわからないので、適当に値を変えて、一番良さそうな値を選んだだけであり、根拠はない。
では、早速走らせてみよう。
CIFAR-10$ python train_cifar3.py -g 0 -u 200 200 -e 20
GPU: 0
# number: 50000
# units: [200, 200]
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.4159      1.13834               0.48884        0.599                     9.25972       
2           0.97892     0.988769              0.65318        0.654                     18.4154       
3           0.746028    0.980138              0.73654        0.6667                    27.5391       
4           0.542533    1.07012               0.810679       0.66                      36.6622       
5           0.343509    1.23141               0.88124        0.6609                    45.7941       
6           0.203577    1.43938               0.930721       0.6502                    54.9523       
7           0.130787    1.79407               0.955861       0.6455                    64.1065       
8           0.10182     2.02728               0.965981       0.641                     73.2419       
9           0.0723793   2.26742               0.976501       0.6303                    82.3618       
10          0.069895    2.23746               0.976921       0.6361                    91.4902       
11          0.0561796   2.46271               0.98124        0.6307                    100.622       
12          0.0611673   2.62696               0.979021       0.6274                    109.749       
13          0.0434337   2.80836               0.985561       0.6262                    118.893       
14          0.0537561   2.52369               0.9824         0.6351                    128.022       
15          0.054097    2.75292               0.98178        0.6274                    137.158       
16          0.045905    2.88034               0.9847         0.6354                    146.3         
17          0.038558    2.94397               0.98716        0.6351                    155.418       
18          0.0468413   2.75888               0.98436        0.627                     164.583       
19          0.0346255   3.07014               0.98884        0.6249                    173.721       
20          0.0300259   3.2695                0.99022        0.6193                    182.873       
CIFAR-10$ 

accuracy-CNN2.png
畳み込みを1層から2層にすることで、テスト精度の最大値が、61.89%から66.67%に向上した。
約5%精度が向上した。
まあまあだ。
でも、向上の割合は半減しているので、畳み込み層を単純に増加させるだけでは限界が近いようだ。
何か別のことも試さないといけない。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第7章、一般化線形混合モデル(GLMM)についてのまとめの一回目です。

この章では複数の分布を混ぜて使う、一般化線形混合モデルついて説明がされています。 まずはロジスティック回帰にパラメータを追加したモデルについて説明されています。 なので、いろいろなパラメータについてそのモデルをプロットしてくれるコードを用意しました。 コードはRで書きました。

N <- 20

linkFunction <- function(b1, b2) {
  function(x, r) {
    1 / (1 + exp(-b1 - b2 * x - r))
  }
}

distribution <- function(x, r, linkFunction) {
  l <- match.fun(linkFunction)
  function(y) {
    dbinom(y, N, l(x, r))
  }
}

rs <- -2:2
ys <- 0:N
pchs <- 1:5
xl <- "y"
yl <- "Probability"
legends <- paste0("r = ", rs)
for (b1 in c(-0.5, 0.5, by = 0.2)) {
  for (b2 in c(-0.5, 0.5, by = 0.2)) {
    l <- linkFunction(b1, b2)
    title <- paste0("logit(q) = ", b1, " + ",b2," * 2"," + r")
    plot(0, 0, type = "n", xlim = c(0, N), ylim = c(0.0, 1.0), main = title, xlab = xl, ylab = yl)
    for (i in 1:5) {
      d <- distribution(2, rs[i], l)
      lines(ys, d(ys), type = "l")
      points(ys, d(ys), pch = pchs[i])
    }
    legend("topright", legend = legends, pch = pchs)
  }
}

このモデルでは、二項分布のパラメータである事象の発生確率 q が以下のコードで与えられます。

linkFunction <- function(b1, b2) {
  function(x, r) {
    1 / (1 + exp(-b1 - b2 * x - r))
  }
}

GLMのモデルと比べると、新しい変数として r が追加されています。 この r が説明変数 x とは違った、何とは特定されていない、余分な事象の発生確率への影響を表します。

コードを実行すると様々な b1b2r の値に対してモデルをプロットします。 x2 で固定にしました。 事象の試行回数は N = 20 で、事象の発生回数は y です。 例えば下図のような図がプロットされます。

mixed.png

図を見ると、確かに r の増減に従って y の分布が影響を受けるのが分かります。

畳み込みを1層だけ最初に追加したCNNでCIFAR-10の学習をさせた結果が以下である。

CIFAR-10$ python train_cifar2.py -g 0 -u 200 200 -e 30
GPU: 0
# number: 50000
# units: [200, 200]
# Minibatch-size: 100
# epoch: 30

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.48308     1.24916               0.47942        0.5549                    7.63466       
2           1.09741     1.1589                0.61328        0.5884                    15.1307       
3           0.902901    1.12019               0.684281       0.607                     22.6394       
4           0.746917    1.12401               0.73846        0.6189                    30.102        
5           0.607654    1.19505               0.78932        0.6183                    37.5926       
6           0.473919    1.24625               0.834919       0.6182                    45.1233       
7           0.360158    1.45592               0.87666        0.6133                    52.5734       
8           0.272877    1.64055               0.905601       0.5979                    60.049        
9           0.200269    1.79699               0.932441       0.593                     67.5596       
10          0.147457    2.04664               0.951441       0.6033                    75.1765       
11          0.138903    2.10482               0.954261       0.6068                    82.7928       
12          0.119845    2.36163               0.959241       0.5933                    90.3151       
13          0.100088    2.4714                0.966941       0.5943                    97.825        
14          0.0921214   2.58492               0.969361       0.5995                    105.367       
15          0.0904041   2.68363               0.970821       0.5898                    113.004       
16          0.0665794   2.76532               0.97846        0.6024                    120.664       
17          0.0730196   3.07065               0.975461       0.5976                    128.205       
18          0.0666776   3.06092               0.977981       0.5966                    135.714       
19          0.06255     3.05346               0.98012        0.605                     143.241       
20          0.0676996   3.14168               0.978761       0.5951                    150.761       
21          0.0504124   3.24065               0.984          0.5864                    158.293       
22          0.0662862   3.33464               0.97872        0.5926                    165.852       
23          0.0556291   3.08113               0.982401       0.5892                    173.412       
24          0.0491994   3.51707               0.9846         0.5939                    180.943       
25          0.0509226   3.43427               0.983321       0.5909                    188.522       
26          0.0460675   3.51363               0.98522        0.6022                    195.956       
27          0.0530636   3.69479               0.98338        0.591                     203.52        
28          0.0500169   3.60148               0.98434        0.5908                    211.039       
29          0.0501503   3.55716               0.98432        0.5945                    218.465       
30          0.0384891   3.71265               0.9885         0.581                     225.952       
CIFAR-10$ 

accuracy-CNN1.png

第4epochで、最高精度 61.89% になり、その後少し下がって60%前後をうろちょろしている。

畳み込みを1層追加する前(Chainer:CIFAR-10のカラー画像の学習の最初の一歩)は、50%を少し超えたあたりが限界だったので、1層入れただけ、それも何も考えず、何も工夫していない入れ方で10%も向上した。

畳み込みは、どうやら強力なのかもしれない。

次回は、畳み込み層をもう少し増やして、どんどん精度が上がるかどうか調べてみよう。

前回、いきなり畳み込みをChainerで使ってみた。
適当に本やネットに書かれているのを真似れば、Chainerでは非常に簡単に畳み込みができてしまう。
でも、それではちょっと不味いかと思い、遅ればせながら解説を書いておこうと思う。

畳み込みニューラルネットワークは英語では Convolutional Neural Network と言い、頭文字をとってCNNと略すことが多い。

比較的小さい正方形の配列を用意して、対象となる入力データ(画像)に適応するもので、実際には積和演算を行う。まあ、内積のことだ。
このとき、この配列をフィルタと呼ぶ。
これは、ニューラルネット以前から使われていた方法で、画像処理では昔から特徴抽出のために行われていた古典的な技術だ。

フィルタの使い方がディープラーニングになって、かなり賢くなったというか、自動化された。
昔は、人間が抽出したい特徴に合わせて、フィルタを用意し、フィルタを通した結果から、画像のどのあたりに特徴的なパターンがあるかを判別していた。
ディープラーニングでは、フィルタ、つまり配列の要素の値自体を学習により設定し、どんどん良い結果がでるようにコントロールする。
まあ、それだけなんだが、これではわかりにくいので、もうちょっと書こう。

ネット上に大量の説明が存在するので、細かい説明はここでは書かない。
しかし、ネット上の説明の多くは、1チャンネルの画像(白黒画像)の場合の例がやたらに多く、カラー画像、さらにはネットワークが多層になっていくと、チャネル数も増やしていくことが多いのだが、そのときのフィルタのサイズと、入力データのサイズ、出力データのサイズの関係があまり書かれていない。

しっかり勉強したい場合は、CS231n: Convolutional Neural Networks for Visual Recognition
Spring 2017
が良いと思う。スタンフォード大学の授業で、当然全部英語だ。

もうちょっと簡易に勉強できるものとして、Deep Learning for Computer Vision - Introduction to Convolution Neural Networks が見つかった。
CNNのとき、入力のチャネル数、出力のチャネル数とフィルタの関係がしっかり図解されているのがとても良い。

最後に、もっと簡単で、日本語なのだが、そのあたりが書かれているページがあったので紹介する。
Convolutional Neural Networkとは何なのか
結局、これもスターンフォードの授業を参考に書いてあるらしいので、スタンフォード大で勉強してしまうのが一番の早道のようだ。

その中で紹介されている画像を2枚紹介する。

stanford_convol_1.png
入力が3チャネル(3枚)で、サイズ5のフィルタと言った場合、フィルタのサイズは5x5x3になる。
そして、フィルタが1つにつき出力データが1チャネル(1枚)できる。

stanford_convol_2.png
実際には、出力チャネルを複数枚にし、そのぶんだけフィルタが用意され、学習される。
上記の場合に、サイズ5のフィルタを指定したら、
   サイズ x サイズ x 入力チャネル数 x 出力チャネル数
だけのフィルタ群が作られる。

このあたりがイメージできると、計算量やメモリ使用量がわかってくる。

さて、次回はChainerに戻ろう。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第6章、GLMの応用範囲をひろげるについてのまとめの九回目です。

この章では様々なタイプのGLMについて説明がされています。 その中でガンマ回帰について説明がされています。 なので、サンプルデータを用意して、ガンマ回帰をやってみるコードを用意しました。 コードはRで書きました。

dataSize <- 1000
createData <- function(createY) {
  createY <- match.fun(createY)
  x <- runif(dataSize, 0.1, 0.9)
  y <- createY(x)
  data.frame(x, y)
}

dataGenerator <- function()
  createData(function(x) rgamma(dataSize, shape = 4, rate = 4 / exp(0.5 + log(x))))

data <- dataGenerator()
fit <- glm(y ~ log(x), data = data, family = Gamma(link = "log"))
b1 <- fit$coefficients[1]
b2 <- fit$coefficients[2]
phi <- summary(fit)$dispersion

cat("b1", b1, "\n")
cat("b2", b2, "\n")
cat("shape", 1 / phi, "\n")
cat(paste0("rate = ", round(1 / phi, 3), " / exp(", round(b1, 3), " + ", round(b2, 3), " * log(x))\n"))

rate <- function(x) {
  1 / (phi * exp(b1 + b2 * log(x)))
}

xs <- c(0.1, 1.0, 2.0, 3.0, 4.0)
ys <- 1:10
title <- "Gamma regression"
xl <- "y"
yl <- "Probability"
legends <- paste0("x = ", xs)
plot(0, 0, type = "n", xlim = c(1, 10), ylim = c(0.0, 1.0), main = title, xlab = xl, ylab = yl)
for (x in xs) {
  y <- dgamma(ys, shape = 1 / phi, rate = rate(x))
  lines(ys, y, type="l")
  points(ys, y, pch = x)
}
legend("topleft", legend = legends, pch = xs)

ガンマ回帰のリンク関数は平均を与えるだけで、ガンマ分布のパラメータそのものは得られません。 なので、推定された分布を計算するには、もうひと工夫必要になります。 どうすればいいかはサポートページに書いてありました。 以下のコードがガンマ回帰でリンク関数のパラメータと、ガンマ分布のパラメータを決定するために必要なパラメータを計算している部分です。

fit <- glm(y ~ log(x), data = data, family = Gamma(link = "log"))
b1 <- fit$coefficients[1]
b2 <- fit$coefficients[2]
phi <- summary(fit)$dispersion

phi はshapeパラメータと shape = 1 / phi という関係を持つパラメータです。 shapeパラメータが得られれば、rateパラメータは以下のように得られます。

rate <- function(x) {
  1 / (phi * exp(b1 + b2 * log(x)))
}

これで推定したガンマ分布をプロットすることができるようになりました。 コードを実行すると以下のような図がプロットされます。

gamma_regression.png

画像ファイルを学習させるには、中間層が全部、単純至極な全結合層ではだめらしい。
世の中では、畳み込み層というのを使い、通常3x3のフィルタを用いて畳み込みニューラルネットワークを作るとあちこちで見かけた。

ということで、早速やってみよう。
というか、ネットで探してみた。

すると、Chainerでは、Convolution2Dなるものが用意されていて、これを使って中間層を増やしていけば良いらしい。
Chainerのマニュアルによると、引数は以下のようになっていて、急にこんなにたくさん理解できない(笑)

class chainer.links.Convolution2D(in_channels, out_channels, ksize, stride=1, pad=0, wscale=1, bias=0, nobias=False, use_cudnn=True, initialW=None, initial_bias=None, deterministic=False)

肝心なのは、最初の3つのパラメータのようだ。

in_channels : 入力チャネル数
out_channels : 出力チャネル数
ksize : フィルタのサイズ

入力チャネル数は、画像データがRGBの3チャンネルだから3と入れても良いのだが、Noneと指定すれば、入力データからチャネル数を自動で決めてくれるらしい。無駄な指定(努力)はバグの元、横着をしよう。

出力チャネル数は、まあ適当な値を設定しよう。

フィルタのサイズは、もっとも一般的な 3x3 のフィルタを指定するので、3になる。

中間層が1つ増えたので、クラスの中の実行メソッド(?)  __call(self,x)を1つ増やした。

さらに、クラスの名前を、CNN (convolutional neural network) に変更したの、modelを作るときのクラス名がCNNに変わった。

CNNを1層増やすためにした変更は、全部でたったの3行である。クラス名はついでなので、実質2行の変更で畳み込みネットワークを使えるようになる。
なんだか、嘘のように簡単だ。

とりあえず、CNNクラスを示す。

# Network definition
class CNN(chainer.Chain):

    def __init__(self, n_units, n_out):
        super(CNN, self).__init__(
            conv1=L.Convolution2D(None, 48, 3),     # 追加行
            l1=L.Linear(None, n_units[0]),  # n_in -> n_units[0]
            l2=L.Linear(None, n_units[1]),  # n_units[0] -> n_units[1]
            l3=L.Linear(None, n_out),  	    # n_units[1] -> n_out
        )
        
    def __call__(self, x):
        c1 = F.relu(self.conv1(x))                  # 追加行
        h1 = F.relu(self.l1(c1))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

modelを作るところが、ちょっと変わった。

    model = L.Classifier(CNN(args.units, 10))
たったこれだけの変更で、畳み込みができるようになり、精度が上昇するであろうか? 結果は次回に示す。

このブログ記事は『データ解析のための統計モデリング入門』(久保拓弥 著、岩波書店)という、とても分かりやすい統計モデリングの入門書を、さらに分かりやすくするために読書メモをまとめたものです。 今回は第6章、GLMの応用範囲をひろげるについてのまとめの八回目です。

この章では様々なタイプのGLMについて説明がされています。 その中でガンマ回帰について説明がされています。 なので、いろいろなパラメータについてガンマ回帰のモデルをプロットしてくれるコードを用意しました。 コードはRで書きました。

linkFunction <- function(b1, b2) {
  function(x) {
    exp(b1 + b2 * log(x))
  }
}

distribution <- function(x, s, linkFunction) {
  l <- match.fun(linkFunction)
  function(y) {
    dgamma(y, shape = s, rate = s / l(x))
  }
}

xs <- c(0.1, 1.0, 2.0, 3.0, 4.0)
ys <- seq(0.5, 10.0, by = 0.5)
pchs <- 1:5
xl <- "y"
yl <- "Probability"
legends <- paste0("x = ", xs)
for (b1 in seq(0.5, 2.5, by = 0.5)) {
  for (b2 in seq(0.5, 2.5, by = 0.5)) {
    for (s in 1:5) {
      l <- linkFunction(b1, b2)
      title <- paste0("shape = ", s, ", rate = ", s, " / exp(", b1, " + ", b2, "log(x))")
      plot(0, 0, type = "n", xlim = c(0, 10), ylim = c(0.0, 1.0), main = title, xlab = xl, ylab = yl)
      for (i in 1:5) {
        d <- distribution(xs[i], s, l)
        lines(ys, d(ys), type = "l")
        points(ys, d(ys), pch = pchs[i])
      }
      legend("topright", legend = legends, pch = pchs)
    }
  }
}

リンク関数は x の単調増加関数です。 コードでは以下の部分です。

linkFunction <- function(b1, b2) {
  function(x) {
    exp(b1 + b2 * log(x))
  }
}

これがガンマ分布の平均を与えます。 ガンマ分布にはshapeパラメータとrateパラメータがありますが、平均は shape / rate なのでリンク関数のパラメータだけを与えても、どんなガンマ分布になるか分かりません。 なので、リンク関数のパラメータ b1b2 の他にshapeパラメータも与えました。

コードを実行すると様々な b1b2shape の値に対してモデルをプロットします。 例えば下図のような図がプロットされます。

gamma_model_4.png

これは b1 = 1b2 = 0.5shape = 4 の場合です。 ガンマ分布の平均は shape / rate = exp(1 + 0.5 * log(x)) なので、rate = 4 / exp(1 + 0.5 * log(x)) になります。 平均が x の単調増加関数なので、x が大きな値を取るようになるほど y の分布も大きな値を取るように変化するのが分かります。

前回の結果は、こんな感じになった。
エポック数は20から30に増やしている。

CIFAR-10$ python train_cifar1.py -g 0
GPU: 0
# number: 50000
# units: [1000, 1000]
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.86715     1.73653               0.32922        0.3812                    2.75118       
2           1.66337     1.59714               0.40486        0.4357                    5.40721       
3           1.57334     1.57614               0.44104        0.4358                    8.07569       
4           1.51226     1.49099               0.46104        0.4785                    10.7339       
5           1.46874     1.46533               0.47504        0.4749                    13.3348       
6           1.42551     1.45455               0.49212        0.4804                    15.9548       
7           1.39173     1.42694               0.50112        0.4923                    18.5731       
8           1.36098     1.43224               0.51336        0.4911                    21.168        
9           1.33312     1.4021                0.52336        0.5021                    23.7716       
10          1.31362     1.41015               0.52904        0.4992                    26.3923       
11          1.27622     1.38601               0.5428         0.5111                    29.0275       
12          1.25692     1.4047                0.55126        0.5059                    31.6632       
13          1.23182     1.40042               0.5605         0.5099                    34.2548       
14          1.20969     1.39606               0.56652        0.5132                    36.8616       
15          1.17579     1.40497               0.57774        0.5122                    39.4807       
16          1.15794     1.38415               0.58356        0.5161                    42.1522       
17          1.13568     1.45162               0.592          0.5038                    44.7706       
18          1.11563     1.40237               0.5983         0.5241                    47.4012       
19          1.08962     1.41428               0.60758        0.5191                    50.014        
20          1.06738     1.45898               0.617439       0.5032                    52.6235       
CIFAR-10$ 
accuracy.png 学習の精度はまだまだ上昇しそうだが、テストの精度が50%をやや超えたあたりでもう上昇する気配がない。

テストの精度が51%としたとき、これは学習していると言えるのだろうか。

まず、画像データについて考えよう。
The CIFAR-10 は、10種類の写真の識別である。これは、手書き数字の判別とまったく同じだ。

もし何も学習できなくてテストをしたら、正解率の期待値は10%のはずである。
デタラメに答えていたら10%なのが、学習により51%になったので、結構学習したと考えられる。

しかし、半分は分類ミスを犯しているので、良い成績とは言いがたい。

まあ、これも、手抜きのせいであるのはわかっている。

そろそろ、単純な全結合ニューラルネットワークだけでなく、畳み込みニューラルネットワークを使うことを模索してみよう。
CIFAR-10のデータが取得できたのは前回わかった。
そうなったら、やはりいち早くディープラーニングをやってみたい。
一番てっとり早いのは、プログラムを書かないことだ。
ということで、横着なことを考えた。

今までMNISTのプログラム train_mnist.py をいじってきた。
ならば、このプログラムをちょっと変更するだけで、CIFAR-10に対応することはできないだろうかと考えるのが自然であろう。

そして、頑張らない作業をやった。
そして、diff がこれだけだ。
CIFAR-10$ diff ../mnist/train_mnist1.py train_cifar1.py
53c53
<     parser.add_argument('--number', '-n', type=int, default=60000,
---
>     parser.add_argument('--number', '-n', type=int, default=50000,
77c77
<     train, test = chainer.datasets.get_mnist()
---
>     train, test = chainer.datasets.get_cifar10()
CIFAR-10$ 
変更は、たった2個所である。

学習データが60000から50000に変更になったので、デフォルトの値を変えた。
何も指定がないときは、実際のデータのサイズに自動的に合わせるようにすれば、この変更は不要になる。

そして、データの取得先の変更を、chainer.datasetsに用意されているメソッド get_mnist() を get_cifar10() に変更した。
さすがこの変更はしないと、無理である。

たったこれだけの変更で、プログラムが動くであろうか。

MNISTは、28x28のグレースケールの画像である。
CIFARは、32x32のカラーの画像なので、32x32のR,G,Bのデータが入っていて、3x32x32の画像である。
これが、上記の最高に手抜きした変更で動くのであろか。
相当疑問である。


CIFAR-10$ python train_cifar1.py -g 0
GPU: 0
# number: 50000
# units: [1000, 1000]
# Minibatch-size: 100
# epoch: 20

結果は次回のお楽しみ
このあと予想されることは、

  1. エラーが起きて、止まってしまう。
  2. とりあえず動くが、意味不明な結果がでる。
  3. ちゃんと動いて、それなりの学習が行われたことが分かる。

さて、どれでしょう。

ChainerでCIFAR-10を直接読み込むことが、MNISTと同じようにできるのがわかったの、早速試してみた。
>>> import numpy as np
>>> import chainer
>>> (train, test) = chainer.datasets.get_cifar10()
>>> len(train)
50000
>>> train[0]
(array([[[ 0.23137257,  0.16862746,  0.19607845, ...,  0.61960787,
           0.59607846,  0.58039218],
         [ 0.0627451 ,  0.        ,  0.07058824, ...,  0.48235297,

 .................  中略 ................

 [ 0.45490199, 0.36862746, 0.34117648, ..., 0.54901963, 0.32941177, 0.28235295]]], dtype=float32), 6) >>> train[0][0].shape (3, 32, 32) >>> type(train[0][0]) <class 'numpy.ndarray'> >>> type(train[0][0][0][0][0]) <class 'numpy.float32'> >>> type(train[0][1]) <class 'numpy.int32'>
>>> [train[i][1] for i in range(30)]
[6, 9, 9, 4, 1, 1, 2, 7, 8, 3, 4, 7, 7, 2, 9, 9, 9, 3, 2, 6, 4, 3, 6, 6, 2, 6, 3, 5, 4, 0]
>>>
これから、trainは50000個の要素が詰まっていて、各要素は、(イメージ,クラス)の形になっていて、画像データは、numpy.float32の3x32x32配列であることが分かる。

これだけ分かれば、画像を表示して確認できるので、やってみた。

CIFAR-10-9x12.png プログラムは、MNISTの時とほとんど同じである。

import chainer
import matplotlib.pyplot as plt
import numpy as np

(train, test) = chainer.datasets.get_cifar10()

fig,ax = plt.subplots(nrows=9,ncols=12,sharex=True,sharey=True)
ax = ax.flatten()
for i in range(108):
    img = train[i][0]
    newimg = np.dstack((img[0],img[1],img[2]))
    ax[i].imshow(newimg,interpolation='none')

ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
plt.savefig("CIFAR-10-9x12.png")
plt.show()
次回からは、CIFER-10の画像データセットを使って Deep Learning をやってみよう。

このアーカイブについて

このページには、2017年5月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2017年4月です。

次のアーカイブは2017年6月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。