こんにちは。技術研究所の 910 です。
Caffeではモデルの構造や学習方法などをprototxtという設定ファイルで定義できるので、非常に再利用し易く重宝しています。
しかしその一方で、私個人としてはTensorFlowを主軸に使っていきたいなーと思っていたりするので、今回はCaffeの学習済みモデルをTensorFlow向けに変換してみることにしました。
OS | macOS High Sierra Version 10.13 Beta7 |
Docker | Docker for mac Version 17.06.2-ce-mac27 (19124) |
変換に利用したライブラリ | caffe-tensorflow |
当然ではありますが、caffe-tensorflowを使う為にはCaffeとTensorFlowが両方導入されている環境が必要となります。
環境を新たに作るのもなかなか面倒ですし、変換の為だけに環境構築済みのGPUインスタンスを起動するのも勿体無いので、両方共導入済みのDocker imageを利用させていただきました。
先述の通り、今回はDockerで環境を構築しました。
利用させていただいたDocker imageには今回の作業に必要な環境がほぼ整った状態になっていますので、少しの修正で環境構築が完了しました。
- Docker imageの取得とコンテナへの接続上記にて示したコンテナはDocker Hubにて公開されているモノなので、以下のように簡単に取得して起動することができます。
docker pullするだけでOSも含め必要なモノを全てダウンロードから導入までしてくれるので、非常に楽チンです。
docker pull docker pull floydhub/dl-docker:cpu |
docker run --name=dl -it floydhub/dl-docker:cpu |
- ライブラリの更新
利用したDocker imageにはバージョン0.8.0のTensorFlowが導入されているので、1.3.0にアップデートして利用しました。
pip install --upgrade pip |
pip install --upgrade tensorflow=1.3.0 |
- caffe-tensorflowの取得と配置
GitHubにて公開されているので、これも簡単に入手できます。
この後の作業で必要となるので、適当なディレクトリに配置しました。
git clone https://github.com/ethereon/caffe-tensorflow.git |
- Docker imageの保存ここ迄の手順で環境の構築は完了しましたので、以下を実行して現状の環境を保存しました。
ちなみに、これをやらずにdocker rmしてしまうと変更内容が消えてしまいます。
# コンテナを止める |
docker stop dl |
# イメージの保存 |
docker commit dl dl |
このように非常に簡単な手順で、環境構築が完了しました。
また、これ以降の作業はこちらのDockerコンテナにattachした状態で行います。
サクッと環境を作ったところで、続いてCaffeの学習済みモデルを作成しました。
今回の本題は学習済みモデルの変換を試すことなので、手順を簡単に再現し易いように、Caffeのソースコードに同梱されているMNISTのサンプルを使っています。
もしこちらの手順を試してみる際には、Caffeのmasterブランチをgit clone
して試してみてください。
- MNISTのデータセットの取得
同梱されているスクリプトを利用しました。
cd $CAFFE_ROOT/data/mnist/ |
sh get_mnist.sh |
- Caffe用のデータセットの作成
学習を行う為に、以下のコマンドを実行してCaffeで扱える形式のデータセットに変換しました。
データセットの取得と同様、スクリプトが同梱されているので、これまた楽チンです。
cd $CAFFE_ROOT/ |
sh examples/mnist/create_mnist.sh |
上記のコマンドを実行すると、lmdbという文字列が付いた名称のディレクトリが$CAFFE_ROOT/examples/mnist直下に2つ生成されているはずです。
これを使って学習を行います。
- mac側との共有ディレクトリに必要ファイルをコピー
データセットや学習済みモデルはコンテナを止めた後も残したいので、macと共有したディレクトリにコピーしました。
今回は/opt/volumeをmacとDockerコンテナ間で共有しています。
# データセット、モデル定義等のコピー |
cp -R $CAFFE_ROOT/examples/mnist/*_lmdb /opt/volume/ |
cp $CAFFE_ROOT/examples/mnist/lenet.prototxt /opt/volume/ |
cp $CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt /opt/volume/ |
cp $CAFFE_ROOT/examples/mnist/lenet_solver.prototxt /opt/volume/ |
余談ですが、私はディレクトリを共有せずにdocker rmしてしまい、せっかく作った学習済みモデルを消してしまうという失態を犯しました。。
- prototxtの書き換え
このままのprototxtでは少々都合が悪いので、ちょっとだけ書き換えて使いました。
変更点を大きく分けると、参照するファイルパス、学習時のログの内容、学習済みモデルの形式の3点になります。
- lenet_train_test.prototxt
- 学習/テストに使うデータセットのパスを、macと共有しているディレクトリに変更
- テスト時の精度をログに出力する為、以下の記述を削除
# accuracyレイヤー内の以下の箇所 |
include { |
phase: TEST |
} |
- lenet_solver.prototxt
- 参照するモデルの構造(prototxt)のパスを、macと共有しているディレクトリに変更
- 学習済みモデルを吐くディレクトリのパスを変更
- 学習済みモデルをBINARYPROTO(caffemodel)形式で吐くように変更
snapshot_format: BINARYPROTO |
- 学習の実行
必要なファイル群が揃いましたので、これらを使って学習を行いました。
学習が終わるとlenet_solver.prototxtで指定したディレクトリにcaffemodel形式のファイルが生成されます。
$CAFFE_ROOT/build/tools/caffe train -solver=lenet_solver.prototxt |
これでCaffeの学習済みモデル(モデルの構造 + 重み)を得られましたので、次はいよいよTensorFlow向けに変換していきます。
caffe-tensorflowを使って学習済みモデルを変換する際には、モデルの構造(lenet.prototxt)と重み(caffemodelファイル)の2つのファイルが必要となります。 変換はconvert.pyを利用して簡単に行えます。
cd caffe-tensorflow |
python convert.py lenet.prototxt --caffemodel lenet.caffemodel --code-output-path=lenet.py --data-output-path=lenet.npy |
これでモデルの構造を定義したPythonスクリプトと、重みのNumPyファイルが作成されました。
ここまでがDockerコンテナ上での作業となります。
次はmacに戻って、変換した学習済みモデルを使ってみます。
【余談】重みファイルの中身
上記のコマンドを実行すると、NumPyで扱える形式の重みファイル(.npy)のファイルが得られます。
どんな構造になっているのかなー、と気になったので中身を見てみたところ、こんな形でデータが格納されていました。
当たり前ではありますが、レイヤー名、重み、バイアスがまとめて格納されているようです。
# 読み込み |
target_data = np.load("lenet.npy", encoding="bytes") |
print(target_data) |
# print(target_data)の結果を一部抜粋 |
{'ip2': { |
b'weights': array([ |
[-0.07131343, -0.02117168, 0.03516806, ..., 0.09743751, |
-0.03588517, 0.12011919], |
[-0.05028905, 0.1051558 , -0.06300636, ..., -0.02502614, |
0.01422695, 0.03254231], |
[-0.04494637, 0.12323597, -0.03401793, ..., -0.07391242, |
-0.15635414, -0.05781281], |
..., |
[ 0.01362752, -0.04886114, 0.11193732, ..., 0.0421311 , |
0.0472105 , -0.05062826], |
[ 0.01840238, -0.0607168 , -0.03646277, ..., 0.08482058, |
0.04715875, -0.06952184], |
[-0.06525502, 0.11371989, -0.01813108, ..., 0.00925197, |
0.01741672, -0.07823739] |
], dtype=float32), |
b'biases': array([ |
-0.00405315, 0.03918738, -0.01531509, -0.0083061 , -0.01913415, |
0.02737579, -0.06240119, 0.01329612, 0.06531051, -0.03596485 |
], dtype=float32) |
}, |
変換して得られた学習済みモデルを使って、実際に画像のクラス分類を行ってみました。
ちなみに変換して得られるモデル構造の定義ファイル(.py)は、caffe-tensorflowに同梱されているkaffeライブラリに依存しています。
なので、パスの通っているディレクトリか、モデル構造の定義ファイルと同一ディレクトリにkaffeライブラリを配置しておく必要があります。
ちなみにkaffeライブラリの実装はPython 2系向けになっているようで、Python 3.6にてクラス分類を実行した際に以下のようなエラーが出ました。
# basestringはPython 2系までしか無い |
NameError: name 'basestring' is not defined |
この他にもいくつもエラーが出たので逐次対応していたのですが、Python 3系で動かすことは本来の目的から外れるので、今回は大人しくPython 2.7で動かすことにしました。
結果として、変換後の学習済みモデルにて画像のクラス分類を行えることが確認できました。
【余談】CaffeとTensorFlowで出る確信度が異なる問題
クラス分類が行えるようになりましたが、CaffeとTensorFlowで同じ画像を処理しても、それぞれ異なる確信度が出力される状態になっていました。
結論としては画像の読み込み処理が異なることが原因で、Caffeでの画像の読み込み処理をTensorFlow側に移植することでほぼ同値の確信度が得られることを確認しました。
# ログ |
## TensorFlow |
['0.00000', '0.00000', '0.00000', '0.03599', '0.00000', '0.96401', '0.00000', '0.00000', '0.00000', '0.00000'] |
## Caffe |
Classifying 1 inputs. |
Done in 0.06 s. |
['0.00000', '0.00000', '0.00000', '0.03599', '0.00000', '0.96401', '0.00000', '0.00000', '0.00000', '0.00000'] |
- 今回初めてDockerを使いましたが、ものすごく便利ですね。
Dockerで好みの環境を一度作ってしまえば本当にどこにでも持ち込んで使えてしまうので、今後構築する開発用の環境はDockerfileを作成して構築しようかな、と思い始めています。 - caffe-tensorflowがPython 2系向けだったのは個人的には残念でしたが、重みファイルを変換できるだけでもcaffe-tensorflowは非常に有用だと思いました。
重みは手作業で再現困難なものですが、モデルの構造は頑張って実装すれば再現可能なので、重みを変換できることは非常に大きいメリットだと思っています。