人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ChainerによるCIFAR-10の一般物体認識 (1)

Chainerによる畳み込みニューラルネットワークの実装(2015/10/7)のつづき。今回はMNISTの数字画像認識ではなく、CIFAR-10(2015/10/14)という画像データを使った一般物体認識をやってみた。画像を10個のクラスに分類するタスク。実装にはChainerを使った。

f:id:aidiary:20151014211729p:plain

MNISTは1チャンネルの白黒画像だったけれどCIFAR-10は3チャンネル(RGB)のカラー画像なので少しだけ複雑になる。CIFAR-10(2015/10/14)でも書いたけれどCIFAR-10の提供データは、各画像サンプルがchannel(3チャンネル)、row(32ピクセル)、column(32ピクセル)のフラット形式3*32*32=3072次元ベクトルの形で格納されている。Chainerでは画像を (nsample, channel, height, width) の形式にする必要があるためreshape()して次元を変換している。

    # 画像を (nsample, channel, height, width) の4次元テンソルに変換
    X_train = X_train.reshape((len(X_train), 3, 32, 32))
    X_test = X_test.reshape((len(X_test), 3, 32, 32))

今回は初めてなので畳み込み層とプーリング層が1つずつの簡単な構成で実験した。

INPUT -> (CONV -> RELU) -> POOL -> FC

CONVは畳み込み層、RELUはReLU活性化関数、POOLはプーリング層(max-pooling)、FCは全結合層(隠れ層1つ)である。Chainerでは下のようなコードで簡潔に書ける。modelの方には学習によって変化するパラメータがある層だけまとめられている。プーリング層はパラメータがないのでmodelには含まれない。

    model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=0),
                                l1=F.Linear(7200, 512),
                                l2=F.Linear(512, 10))

    def forward(x_data, y_data, train=True):
        x, t = chainer.Variable(x_data), chainer.Variable(y_data)
        h = F.max_pooling_2d(F.relu(model.conv1(x)), 2)
        h = F.dropout(F.relu(model.l1(h)), train=train)
        y = model.l2(h)
        if train:
            return F.softmax_cross_entropy(y, t)
        else:
            return F.accuracy(y, t)

畳込み層のconv1=F.Convolution2D(3, 32, 3, pad=0)は、入力が3チャンネル(RGB)、出力の特徴マップが32チャンネル、フィルタ(カーネル)サイズが3x3、パディングサイズが0であることを意味する。

フル結合層は2層からなりl1=F.Linear(7200, 512)は、7200ユニットから512ユニットへ変換する層、l2=F.Linear(512, 10)は512ユニットから10ユニットへ変換する層を意味する。CIFAR-10は10クラスの画像分類なので出力ユニット数は10になる。

畳み込み層ではパディングサイズが0だと出力の特徴マップの画像サイズが入力画像より少し小さくなる。入力画像サイズがW \times WでフィルタがH \times Hだと出力画像サイズは W - 2 \lfloor H/2 \rfloor \times W - 2 \lfloor H/2 \rfloorになる。ここで\lfloor \cdot \rfloorは小数点以下切り下げて整数化する演算。

今回の例だと入力が3x32x32conv1によって32x30x30になる。さらにmax_pooling_2dのウィンドウサイズが2なので画像サイズは半分になって32x15x15になる。これをフラット化してからフル結合層に入力するためフル結合層のユニット数は32x15x15=7200となる。これがl1の入力ユニット数が7200となっている理由。

このユニット数が間違っているとChainerが実行時に正しい値を教えてくれるので、自分で計算するのが面倒なら適当に入れておいてもよいかもしれない(笑)たとえば、適当に1000にしてみると

Actual: 7200 != 1000

というエラーが出る。これは「フル結合層のユニット数は本当は7200なのに1000になっていて違うよ!」という意味。

この構成だとテスト精度で最大67.7%だった。次は畳み込みニューラルネットワークの構成をいろいろ変えたとき精度がどのように変化するか調べてみよう。

参考

CIFAR-10

MNISTの数字画像はそろそろ飽きてきた(笑)ので一般物体認識のベンチマークとしてよく使われているCIFAR-10という画像データセットについて調べていた。

f:id:aidiary:20151014211729p:plain

このデータは、約8000万枚の画像がある80 Million Tiny Imagesからサブセットとして約6万枚の画像を抽出してラベル付けしたデータセット。このデータセットを整備したのは、SuperVision(またはAlexNet)と呼ばれる畳み込みニューラルネットワークを使ってILSVRC2012で優勝したAlex Krizhevskyさんとのこと。こういう泥臭い仕事もしていたなんて尊敬する。

CIFAR-10の元となる80 Million Tiny Imagesは類似画像検索(2009/10/3)で少し言及したことがあった。初出はこの論文(PDF)だと思うけれど、最初に読んだときいろいろな画像があってとてもわくわくし、いつか使ってみたいと思った覚えがある。今回は類似画像検索ではなく、Deep Learningを使った一般物体認識が主目的。だけど隠れ層の圧縮データを使って類似画像検索もできるらしいので後で試してみたい。

CIFAR-10の特徴

特徴をまとめておこう。

  • 80 million tiny imagesのサブセット
  • 全部で60000枚
  • 画像サイズは32ピクセルx32ピクセル
  • RGBの3チャンネルカラー画像
  • クラスラベルはairplane, automobile, bird, cat, deer, dog, frog, horse, ship, truckで10クラス
  • 50000枚(各クラス5000枚)の訓練画像と10000枚(各クラス1000枚)のテスト画像に分割されている
  • クラスラベルは排他的
  • PythonのcPickle形式で提供されている

最後が重要。BMPやPNGといった画像ファイルが60000枚提供されているわけではなく、ピクセルデータ配列としてPythonから簡単に読み込める形式で提供されている。Python使い歓喜!

逆に言うとピクセルデータしか提供されていないので人の目に見える画像として表示するには一手間かかる。人工知能がものを「見る」ためにはピクセルデータの数列さえあればよいが、人間が見るためにはそういうわけにはいかない。というわけで今回はこの画像セットをロードして画像として描画するところまでやってみたい。

CIFAR-10のCIFAR-10 python versionをダウンロード。tarで解凍すると

  • batches.meta - ラベル名のリスト
  • data_batch_1 - 訓練画像(1万枚)
  • data_batch_2 - 訓練画像(1万枚)
  • data_batch_3 - 訓練画像(1万枚)
  • data_batch_4 - 訓練画像(1万枚)
  • data_batch_5 - 訓練画像(1万枚)
  • test_batch - テスト画像(1万枚)

というファイルが出てくる。訓練画像は1万枚ずつ別のファイルになっている。全部、PythonのcPickleのバイナリダンプ形式のためcPickleモジュールでロードできる。ロードするとPythonのlistdictnumpy.ndarrayのオブジェクトとしてすぐに使える。ファイルをロードするにはホームページに記載されている通り下の関数でできる。

多くはdict形式なのでkeys()でどんなデータが格納されているか簡単に調べられる。

CIFAR-10の描画

data_batch_1の1万枚の画像から各クラス10枚の画像をランダムに描画してみよう。実行するたびに違う画像が表示される。

Pythonで描画するときはmatplotlibのimshow()が使える。ただし、RGBの3チャンネルの画像を描画するには、ndarray(row, column, channel) の順番に並び変える必要がある。これをやっているのが

plt.imshow(img.reshape(3, 32, 32).transpose(1, 2, 0))

のコード。オリジナルの画像はchannel(3チャンネル)、row(32ピクセル)、column(32ピクセル)のフラット形式3*32*32=3072次元ベクトルの形で格納されている。そのため、まずreshape()を使って([0]channel, [1]row, [2]column)の3次元arrayに変換している。これをimshow()で読める([1]row, [2]column, [0] channel) の順番に変更するためにnumpyのtranspose()を使って次元を入れ替えている。ちなみにChainerで画像を読み込む場合は(channel, row, column)の順番でよいらしいので元のままでOK。

実行するとCIFAR-10の元サイトにあるような画像が描画できる。

f:id:aidiary:20151014200947p:plain

上から飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船、トラックである。解像度がピクロスレベルの32x32しかないためすごく識別しにくいが何となくわかる。どうしてこの10種類のクラスを選んだのかはっきりしないけれど、動物と乗り物が好きだったのかな?(笑)

次回は、与えた画像が10種類のどのクラスかを当てる一般物体認識と呼ばれるタスクを畳み込みニューラルネットワークで解いてみたい。

参考

Chainerによる畳み込みニューラルネットワークの実装

Chainerによる多層パーセプトロンの実装(2015/10/5)のつづき。今回はChainerで畳み込みニューラルネットワーク(CNN:Convolutional Neural Network)を実装した。Theanoによる畳み込みニューラルネットワークの実装 (1)(2015/6/26)で書いたのと同じ構造をChainerで試しただけ。タスクは前回と同じくMNIST。

f:id:aidiary:20150626203849p:plain

今回は、MNISTデータの取得や訓練/テストの分割にscikit-learnの関数を使ってみた。

Chainerで畳み込みをするためには、訓練データの画像セットを(ミニバッチサイズ、チャンネル数、高さ、幅)の4次元テンソルに変換する必要があるここに書いてある)。今回はチャンネル数が1なので単純にreshapeで変形できる。

3チャンネルのカラー画像だとnumpyのtranspose()で4次元テンソルに変換できるみたい。transpose()は転置行列作るときに使うけどこのnumpyサンプルの3例目によるとndarrayの次元を入れ替えるときにも使えるようだ。あとで物体認識をやるときに確認しよう。

訓練時の誤差とテスト精度を描いてみると下のようになった。エポックが進むにつれて誤差が減り、学習が進んでいることがわかる。テスト精度は多少がたがたするが徐々に向上し、最大で99.3%くらいになる。今回はEarly-Stoppingのような高度な収束判定は使わず、単純に20エポック回しただけなので手を抜いている。GTX760で20エポックの学習に984秒かかった。

f:id:aidiary:20151007213443p:plain f:id:aidiary:20151007213449p:plain

学習したモデルはcPickleでファイルにダンプできる。このフォーラムの記事によると学習したモデルをファイルにダンプするときはmodel.to_cpu()でGPUからCPUに戻した方がよいとのこと。こうしておけばGPUがないマシンでも学習済みモデルを読み込める。

畳み込みニューラルネットは、学習対象の重みがフィルタに当たるので画像として描画できる。試しに学習したモデルの重みを可視化してみよう。下のようなコードで描ける。

f:id:aidiary:20151007220137p:plain

ちょっと解釈できない。ガボールフィルタみたいなのができるはずなのだけれど、ランダムだった初期状態からあまり変わらない気もする。もう少しエポック回せばよかったのかな?でも精度は十分上がったしなぁ。もっと別の例でも確認してみよう。

参考