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 定義して自由に使えるようになるってのですが,長い道のりになりそな気配....まあぼちぼち.