概要
しばらくの間に進化し続けているchainerさんで、少し見ない間にだいぶ変わっていたので、独自データを利用したクラス分類をMNISTのサンプルを参考にしてやってみました。
コードはgithub上においてます。
-
https://github.com/komorin0521/chainer_myexample/tree/master/image_classify/01_src
なお、古いデータもありますが、今回用いたのは以下のコードです
- util.py
- network.py
- train_chainer_310.py
- predict_chainer_310.py
環境
- Ubuntu 16.04(CPU環境:windows上のVM)
- python 3.6.2
- chainer 3.1.0
実行例
pipでchainer
とpillow
をインストールしていることを前提
トレーニング
$ python train_chainer_310.py -lf ../02_inputdata/labels.txt -f ../02_inputdata/filepath.txt -df ../02_inputdata/
標準出力にロス値や精度が出ます
予測結果の取得
$ python predict_chainer_310.py -lf ../02_inputdata/labels.txt -mf result/model_675.npz -if ../02_inputdata/01_a/01_colour/04_a.png
トレーニング時に${workdir}/result/model_*npz
にトレーニング済みのモデルが生成されるので、それを利用します。
-if
は画像分類を行いたい対象のファイルです。
なお、標準出力には、確率、インデックス、ラベル名が表示されます。
index: 0, prob: 0.999291, label: a
ちょっとした説明
基本的にはMNISTのトレーニングのサンプルを用いてやっているので、
以下の3点にまとを絞って説明を書いています。
- 独自データの定義部分
- モデルの保存
- ある1枚の画像の結果を取得する
独自データの定義
chainerに限らないが、だいたいのサンプルコードって、MNISTの画像をDLしたついでに
いい感じに前処理して、ひょいってトレーニングしている気がする(笑)
なので、どういう風に自前のデータを学習すればよいかわからないこと多い!
chainer
では、datasets
を用いて簡単にデータセットを定義できる。
画像分類であれば画像情報、ラベル、というTupleDataset
を用いてやればよい。
なお、chainerでは画像情報はnp.float32
で扱う必要があるので、
util.py
のload_data
内部で上記を指定してます。
トレーニングのバッチサイズとかは、データセットと別にchainer.iterators.SerialIterator
で定義してあげる。
train_chainer_310.py
内部の下記の部分
...
def load_my_dataset(filepath, datafolderpath, labelfilepath, test_ratio=0.9):
"""
loading and make train and test dataests
filepath: the list of imagefilenamelist
datafolderpath: the datafolder of inputting image
labelfilepath: the data of label
for this example the label is "a", "i", "u", "e", "o"
"""
labellist = load_label(labelfilepath)
with open(filepath, "r") as readfile:
filepathlist = [os.path.join(datafolderpath, line.strip())
for line in readfile.readlines()]
datasets = list()
for imagefilepath in filepathlist:
img = load_data(imagefilepath)
filename = imagefilepath.split(os.path.sep)[-1]
label = filename.split(".")[0].split("_")[1]
index = labellist.index(label)
datasets.append([img, index])
random.shuffle(datasets)
index = int(len(datasets) * test_ratio)
traindatasets = datasets[:index]
testdatasets = datasets[index:]
train_imgs = [train_data[0] for train_data in traindatasets]
train_labels = [train_data[1] for train_data in traindatasets]
test_imgs = [test_data[0] for test_data in testdatasets]
test_labels = [test_data[1] for test_data in testdatasets]
return chainer.datasets.tuple_dataset.TupleDataset(train_imgs, train_labels), \
chainer.datasets.tuple_dataset.TupleDataset(test_imgs, test_labels)
...
モデルのセーブについて
トレーニング中にトリガーを使って、ロス値や精度を出すのにtrainer.extend
にて定義してあげればよい
MNISTの例でもいろいろと利用されているが、モデルのセーブがない...!!
ちょっと見てみると、chainer.training.extensions.snapshot_object
というものを用いれば、対象のオブジェクトファイルをセーブしてくれるみたい。
中身を見るとどうやら*{.updater.iteration}.npz
のように定義してあげていると、
いい感じにフォーマットしてくれるとのこと。
なので、上記のように下記のように途中でモデルをセーブするのを追加してみた。
もしかしたらtrainer
がセーブされているので、そこから拾い出せるのかもしれないが、
最終予測結果のみを得るのであれば、model
のみエクスポートする方が、なにかと便利な気がする。
train_chainer_310.py
内部の下記の部分
# save the modelfile
trainer.extend(chainer.training.extensions.snapshot_object(
model, 'model_{.updater.iteration}.npz'), trigger=(10, 'epoch'))
予測を行う部分について
predict_chainer_310.py
内部に書いてます。
処理の流れは、おおざっぱには下記の通り。
1. 訓練済みモデルをロード
2. 予測したいイメージファイルを読み込み
3. 訓練済みモデルを用いて予測
モデルロードは、chainer.serializers.load_npz(対象ファイル,読み込み対象オブジェクト)
関数であっさりできる
2は、np.float32
の配列で読み込んであげることだけ注意する
3が少し注意が必要で、predict
関数の部分にあたる
基本的に複数のイメージを処理することを前提としているので、イメージファイル一個でもちゃんと画像ファイルリストにしてあげてから、Variable
型に変換してあげる
x = Variable(np.asarray([inputdata]))
そして、順伝播はmodel.predictor(x)
で行ってあげる。
model.predictor
の実態はL.Classifier(LinearNet(n_units_h1, n_units_h2, n_out))
の引数のLinearNet
にあたるので、それを呼び出すとLinearNet.__call__(x)
が呼び出されるようになっている。
ただ、ネットワークで定義した最終出力層は、ソフトマックス関数にかける前の値なので、
F.sotmax
を行うことで、確率にしてあげないといけない。
y
も結果の配列になっているので、画像が一個の場合はy.data[0]
が入力画像1つに対する各ラベルの確率となっている。
なので、一番おっきい確率になっているindex
を探しています。
(たぶんforを使わなくてもできるが、ひとまず)
以上、ちょっとした解説です。
所感
tensorflow
を使って独自データセットを用いて同じことやろうとしたけど、
けっこうデータセットの定義の部分とかいろいろとややこしいが、
chainerでは簡単にできたなあと
(chainerをもう少し古いバージョンのときに少し勉強したおかげもある)
ドロップアウトとかCNNとかその辺ももう少しやってみようと思いました。