Chainer Advent Calendar 2017 の 11日目 の記事です*1。
Caffeモデルを利用したFineTuningを試したいと思います。ChainerでのFineTuningは、ChainerのLink関数であるVGG16Layers()
なども用いることもできます。VGG16Layers()
を用いる方法については以下にまとめています。
Caffeモデルの利用
Caffeモデルのダウンロードと変換
今回もVGGのモデルを利用します。Caffeモデルは、VGG_ILSVRC_19_layers_deploy.prototxtのcaffemodel_urlのからダウンロードできます。ちなみに、このモデルは、VGG16Layers()
で使われているものと同じものです。
変換には時間がかかるので、事前に実施しておき、npz形式で保存しておきます。
from chainer.links.caffe.caffe_function import CaffeFunction from chainer.serializers import npz caffemodel = CaffeFunction('VGG_ILSVRC_16_layers.caffemodel') npz.save_npz('VGG_ILSVRC_16_layers.npz', caffemodel, compression=False)
ちなみに、ダウンロードに20分、変換に10分かかりました。。
モデルの定義
いつものようにChain
を継承してモデルを定義します。
BaseVGG
がCaffeモデルを利用するモデルで、BaseVGG
を使ってオリジナルのモデルVGG
を定義しています。fc6/fc7/fc8はCaffeモデルを利用しないモデルになっています。
npz.load_npz(pretrained_model, self.base)
で、BaseVGG
にCaffeモデルの重みを(初期化に)設定しています。
import chainer import chainer.functions as F import chainer.links as L from chainer.serializers import npz class VGG(chainer.Chain): def __init__(self, class_labels=100, pretrained_model='VGG_ILSVRC_16_layers.npz'): super(VGG, self).__init__() with self.init_scope(): self.base = BaseVGG() self.fc6 = L.Linear(None, 512) self.fc7 = L.Linear(None, 512) self.fc8 = L.Linear(None, class_labels) npz.load_npz(pretrained_model, self.base) def __call__(self, x, t): h = self.predict(x) loss = F.softmax_cross_entropy(h, t) chainer.report({'loss': loss, 'accuracy': F.accuracy(h, t)}, self) return loss def predict(self, x): h = self.base(x) h = F.dropout(F.relu(self.fc6(h))) h = F.dropout(F.relu(self.fc7(h))) return self.fc8(h) class BaseVGG(chainer.Chain): def __init__(self): super(BaseVGG, self).__init__() with self.init_scope(): self.conv1_1 = L.Convolution2D(None, 64, 3, 1, 1) self.conv1_2 = L.Convolution2D(64, 64, 3, 1, 1) self.conv2_1 = L.Convolution2D(64, 128, 3, 1, 1) self.conv2_2 = L.Convolution2D(128, 128, 3, 1, 1) self.conv3_1 = L.Convolution2D(128, 256, 3, 1, 1) self.conv3_2 = L.Convolution2D(256, 256, 3, 1, 1) self.conv3_3 = L.Convolution2D(256, 256, 3, 1, 1) self.conv4_1 = L.Convolution2D(256, 512, 3, 1, 1) self.conv4_2 = L.Convolution2D(512, 512, 3, 1, 1) self.conv4_3 = L.Convolution2D(512, 512, 3, 1, 1) self.conv5_1 = L.Convolution2D(512, 512, 3, 1, 1) self.conv5_2 = L.Convolution2D(512, 512, 3, 1, 1) self.conv5_3 = L.Convolution2D(512, 512, 3, 1, 1) def __call__(self, x): h = F.relu(self.conv1_1(x)) h = F.relu(self.conv1_2(h)) h = F.max_pooling_2d(h, ksize=2, stride=2) h = F.relu(self.conv2_1(h)) h = F.relu(self.conv2_2(h)) h = F.max_pooling_2d(h, ksize=2, stride=2) h = F.relu(self.conv3_1(h)) h = F.relu(self.conv3_2(h)) h = F.relu(self.conv3_3(h)) h = F.max_pooling_2d(h, ksize=2, stride=2) h = F.relu(self.conv4_1(h)) h = F.relu(self.conv4_2(h)) h = F.relu(self.conv4_3(h)) h = F.max_pooling_2d(h, ksize=2, stride=2) h = F.relu(self.conv5_1(h)) h = F.relu(self.conv5_2(h)) h = F.relu(self.conv5_3(h)) h = F.max_pooling_2d(h, ksize=2, stride=2) return h
Caffeモデルを利用するネットワーク構造は同じにする必要があります。また、インスタンス変数の名前も元のモデルの層の名前と同じにする必要があります。
ネットワーク構造や各層の名前は、VGG_ILSVRC_19_layers_deploy.prototxtを参照してください。
層の名前は、モデルファイルからも分かります。
import numpy as np model = np.load('VGG_ILSVRC_16_layers.npz') print(model.keys())
['conv5_2/W', 'conv5_2/b', 'conv2_2/W', 'conv2_2/b', 'fc8/W', 'fc8/b', 'conv5_1/W', 'conv5_1/b', 'fc6/W', 'fc6/b', 'conv3_2/W', 'conv3_2/b', 'conv1_2/W', 'conv1_2/b', 'conv2_1/W', 'conv2_1/b', 'conv4_2/W', 'conv4_2/b', 'conv1_1/W', 'conv1_1/b', 'conv4_3/W', 'conv4_3/b', 'conv3_3/W', 'conv3_3/b', 'conv3_1/W', 'conv3_1/b', 'conv5_3/W', 'conv5_3/b', 'conv4_1/W', 'conv4_1/b', 'fc7/W', 'fc7/b']
重みの固定
モデル定義では、Caffeモデルの重みを初期化に設定しただけなので、固定します。
model = VGG() optimizer = chainer.optimizers.MomentumSGD() optimizer.setup(model) model.base.disable_update()
重みの固定の詳細については、前回の記事も参照ください。
まとめ
今回は、fc6/fc7/fc8の3層を一気に学習するFineTuningを行いましたが、出力層に近い順に重みの固定を解除していくとうまくいくと言われています。つまり、fc8以外の重みを固定して、fc8を学習する -> fc7/fc8以外の重みを固定して、fc7/fc8を学習するといったように実施します。
また、VGGを使ったFineTuningを行いましたが、実際にはVGGよりResNetやInceptionモデルを利用した方が、計算量が少なく、高い精度が期待できます。
出展: [1605.07678] An Analysis of Deep Neural Network Models for Practical Applications
今回は、Chainerの機能を使って、Caffeモデルの利用をしましたが、異なるフレームワーク間で、学習したモデルをやり取りする仕組みが提唱されつつあります。有力なのが、FacebookとMicrosoftが開発しているONNX(Open Neural Network Exchange) で、以前PyTorchとCaffe2で利用してみました。
まだ、ChainerはONNXをサポートしていませんが、Chainer Advent Calendar 2017でもONNXの記事が投稿されるみたいなので、今後に期待したいところです。
また、今回使ったコードはGithubにあげています。
*1:本当はCapsNetの実装をやろうと思ったけど、すでにChainer版があったので没に...