chainerのVGG16 pretrainモデルを早速使ってみた

最近めっきり記事を書いてないので、今後はメモくらいのつもりでもいいから小出しに書いていこうと思う。

chainerがv1.22.0でVGG16LayersとResNet50Layersをchainer.linksでサポートした。
Standard Link implementations — Chainer 1.22.0 documentation

これによってcaffeの学習済みモデルがより気軽にchainerで試せるようになったようだ。
ちょうど学習済みモデルを手っ取り早く使いたいなと思ったところでタイミングが良かったので早速試してみた。

今回はVGG16Layersのみについて書いていこうと思う。

基本的な使い方

基本的な使い方はchainerのリファレンスをみるのが手っ取り早い。
Standard Link implementations — Chainer 1.22.0 documentation
これによると、以下のように使えると書かれている。

from chainer.links import VGG16Layers
from PIL import Image

model = VGG16Layers()
img = Image.open("path/to/image.jpg")
feature = model.extract([img], layers=["fc7"])["fc7"]

お手軽感がすごい。

特徴

特徴をまとめると以下のようになる。

extractpredictというメソッドがある
  • 入力はPIL.Imageとnumpy.ndarray (height,width)(height,width,channels)(channels,height,width)のどれでもサポートする
  • バッチで与えたいときは、入力を上記のオブジェクトのリストで与えればよい(1つのnumpy.arrayに統合してはいけないので注意)
  • extractはlayers=['fc6','fc7']のようにlayersで指定した層の出力をまとめて辞書形式で返す。
  • predictはfc8(最上層)の出力、つまり各クラスの予測値を返す

主にお世話になるのはextractだと思われる。

chainier.links.model.vision.vgg.prepareというメソッドがある

VGGモデルは、通常入力画像を(batchsize,channels,height,width)、channelsはRGBではなくBGRという形式のnumpyarrayに整形する必要があるが、これはそれらの操作を行ってくれる。また、256x256へリサイズ、224x224へクロップする作業、VGG16の平均画素[103.939, 116.779, 123.68]の引き算も同時に行う。
(extractとpredictにはこれが既に内部に組み込まれているのでわざわざ使う必要はない)

処理の高速化のため、画像のローディングを別スレッドorプロセスで並列に行いたい場合はこれを使えばよいということだと思われる。
その場合、既に1つのミニバッチ化されたデータを入力として、そのままcallすればメインのスレッドorプロセスでの処理はextractよりも少なくて済む。
ちなみに、このメソッド自体は複数入力に対応していないので、リストでなく1つずつ入力する必要がある。

パラメータ固定、fine-tuningでそれぞれ使ってみる

VGG16Layersの良いところはchainer.linksでサポートされてる点だと思っている。書き方を少し工夫すれば、自分で定義したエンコーダにVGG16を隠蔽することができるので仕様に振り回されなくて済む。例えば、以下のようにかける。

from chainer import links as L
class Encoder(Chain):
    def __init__(self,n_image):
        super(Encoder, self).__init__(
            model = L.VGG16Layers(),
            fc = L.Linear(4096,n_image))

    def __call__(self, x, train=True, finetune=False):
        x = Variable(self.xp.asarray(x,dtype=np.float32),volatile=not finetune)
        h = self.model(x,layers=['fc7'], test=not finetune)
        h = h['fc7']
        if finetune==False:
            h = Variable(h.data,volatile=not train)
        return F.tanh(self.fc(h))

    def make_optimizer(self):
        return optimizers.Adam(alpha=2e-4, beta1=0.5)

pretrainモデルをimage-captioningなどのEncoderとして用いる場合、最初からfine-tuningするとまだ学習が進んでいないDecoderのエラーが伝搬してpre-train部分が悪影響を受けることがある。従って、pre-train側とそうじゃない部分は分けて学習できるようになっているのが望ましい。今回の例ではEncoderの後段がpretrainモデルじゃない場合のEncoderという想定で書いてみた。訓練フェイズでpretrainモデル部分を固定にするかfine-tuningをするか切り替えるには、単純にVariableの中身を一度取り出して再度Variableでラッピングして計算グラフを切るか切らないかで切り替えることができる。