まんぼう日記

takataka's diary

Caffe を使ってみる (2)

Caffe を使ってみる - まんぼう日記  のつづき.Python のプログラムで,学習済みの CNN のパラメータを読み込む → テストデータに対する CNN の出力を計算,という処理をやってみます.使ってる caffe は,Ubuntu マシンに Caffe をインストール - まんぼう日記 でインストールしたもの ( version 1.0.0-rc3 ) です.

 

 

 

caffe.Classifier を使って学習済みネットワークを読み込む

CaffeでDeep Learning つまずきやすいところを中心に - Qiita という記事を参考にさせてもらいました.

In [1]: import caffe
In [2]: help(caffe.Classifier)

してみると

class Classifier(caffe._caffe.Net)
   :
 |  __init__(self, model_file, pretrained_file, image_dims=None, mean=None, input_scale=None, raw_scale=None, channel_swap=None)

やそうな.ほな,model_file には前回使った lenet_train_test.prototxt を,pretrained_file には前回の実験で出力されたファイル lenet_iter_10000.caffemodel を指定したらええんやろ,と思ったら...

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-4-43ef924ffe42> in <module>()
----> 1 cnn = caffe.Classifier( 'lenet_train_test.prototxt', 'lenet_iter_10000.caffemodel')

.../caffe/python/caffe/classifier.pyc in __init__(self, model_file, pretrained_file, image_dims, mean, input_scale, raw_scale, channel_swap)
     27 
     28         # configure pre-processing
---> 29         in_ = self.inputs[0]
     30         self.transformer = caffe.io.Transformer(
     31             {in_: self.blobs[in_].data.shape})

IndexError: list index out of range

怒られちゃいました.python - Pycaffe: Index out of range when using original train.prototxt as the pretrained_file in Caffe.classifier - Stack Overflow によると,どうやら,lenet_train_test.prototxt がまずいみたい.Data Layer で lmdb ファイルを読み込むようになってて,入力の shape が示されてないのが問題のようです.

 

で,元々の lenet_train_test.prototxt が置いてあった examples/mnist/ を探すと,lenet.prototxt というのんがありました.こちらは

layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 64 dim: 1 dim: 28 dim: 28 } }
}

というふうに Input Layer を定義してて shape も明示されてます.こっちのファイルで定義してるネットワークは lenet_train_test.prototxt と同じやったんで,こっちを使えばええんでしょう...

In [5]: cnn = caffe.Classifier( 'lenet.prototxt', 'lenet_iter_10000.caffemodel')
I0307 17:23:24.398818 2118865664 net.cpp:49] Initializing net from parameters: 
   :
I0307 17:23:24.403738 2118865664 net.cpp:274] Network initialization done.
I0307 17:23:24.406942 2118865664 net.cpp:753] Ignoring source layer mnist
I0307 17:23:24.407178 2118865664 net.cpp:753] Ignoring source layer loss

うん,ちゃんと読み込めたようです.

In [7]: for layername, params in cnn.params.items():
   ...:     print layername, params[0].data.shape, params[1].data.shape
   ...: 
conv1 (20, 1, 5, 5) (20,)
conv2 (50, 20, 5, 5) (50,)
ip1 (500, 800) (500,)
ip2 (10, 500) (10,)

こんなふうに CNN の重みの情報を取り出すこともできました.params[0].data が重みの numpy.array で,params[1].data がバイアスの配列のようです.

 

caffe.Classifier を使ってネットワークの出力を計算する

読み込んだ CNN にMNISTのテストデータを与えて出力を計算します.再び

In [2]: help(caffe.Classifier)

すると,Classifier オブジェクトの predict というメソッドを使えばよい,ということがわかりました.で,さくっと作ってみた Python のプログラムがこちら: ex160307.py · GitHub

 

と,ここまでやってるうちに,caffe.Classifier クラスが caffe/python/caffe/classifier.py で定義されてるのを発見.眺めてみたら,このクラスは caffe.Net を親として,画像識別向けに前処理・後処理をあれこれ付加したものやってことがわかりました.なんや,MNIST やったら caffe.Net の方で十分やん.

 

というわけで...

 

caffe.Net を使ってほげほげ

まずはネットワークの読み込んで Net クラスのオブジェクトを生成します.help(caffe.Net) の __init__ の説明が役立たずなので,上記 classifier.py を参考に.こんな感じです.

In [5]: cnn = caffe.Net( 'lenet.prototxt', 'lenet_iter_10000.caffemodel', caffe.TEST )

Classifier クラスの方で cnn.params.items() ってのを呼んでみてましたが,これは Net クラスから継承してるだけでしたので,こっちの cnn でも当然 cnn.params.items() を呼べます.

 

で,次はネットワークの出力の計算.help(caffe.Net) によると,forward メソッドか forward_all メソッドを使えばよいようです.

 |  forward_all = _Net_forward_all(self, blobs=None, **kwargs)
 |      Run net forward in batches.
 |      
 |      Parameters
 |      ----------
 |      blobs : list of blobs to extract as in forward()
 |      kwargs : Keys are input blob names and values are blob ndarrays.
 |               Refer to forward().
 |      
 |      Returns
 |      -------
 |      all_outs : {blob name: list of blobs} dict.

うーむ,引数の指定の仕方がわからへん....classifier.py 見たら

out = self.forward_all(**{self.inputs[0]: caffe_in})

って書いてるけど,**{self.inputs[0]: caffe_in} ってなんじゃこりゃ.途方にくれて「python kwargs」でググってみたら,*args **kwargsの意味 という記事に行き当たりました.そか,keyword arguments の略か.んで,**hoge はディクショナリ hoge をばらして渡す働きをしてるのね.今の例では入力を表す blob の名前は 'data' やから,キーワード引数で直接指定するならこう:

rv = cnn.forward_all( data = X )

ただし,X は入力データの np.array で,shape は ( データ数, チャンネル数, 画像の高さ, 画像の幅 ) です.さらに,今の例では

In [5]: cnn.inputs
Out[5]: ['data']

ということになってる,つまり cnn.inputs[0] が CNN への入力を表す blob の名前になってるので,それを利用すれば

rv = cnn.forward_all( **{cnn.inputs[0]:X} )

というふうに,入力 blob につける名前によらないプログラムが書ける.なるほど.

 

とまあ,そんなこんなで caffe.Net を使うバージョンのプログラムもできました: ex160307b.py · GitHub

 

実験

 

試しに, MNIST のテストデータ1万個を入力として,識別率と実行時間を測ってみました.マシン環境は Caffe を使ってみる - まんぼう日記 と同じで,

  • CPU: Core i7 5820K 6core 3.3GHz 
  • GPU: GeForce GTX TITAN X

です.

 

まずは,caffe.Classifier を使った方.

%run ex160307.py
# fnModel   =  lenet.prototxt
# fnTrained =  lenet_iter_10000.caffemodel
# conv1 (20, 1, 5, 5) (20,)
# conv2 (50, 20, 5, 5) (50,)
# ip1 (500, 800) (500,)
# ip2 (10, 500) (10,)
# useGPU =  True
# X.shape =  (10000, 28, 28, 1)
# recognition rate =  0.9902
# time =  4.34732294083

%run ex160307.py
# useGPU =  False
# time =  7.81033301353

 

次は,caffe.Net を使った方.

%run ex160307b.py
# useGPU =  True
# time =  0.200743913651

%run ex160307b.py
# useGPU =  False
# time =  4.46612715721

caffe.Classifier の方は無駄な前処理のせいでだいぶ遅くなってるようです.この前処理 GPU 使うてないんちゃうかな.

 

ふぅ,ようやく Python で学習済みネットワークを読み込んで出力を計算できるようになりました.次は Python 上で学習ですな.その先の目標は,自分で Layer 定義して自由に使えるようになるってのですが,長い道のりになりそな気配....まあぼちぼち.