EnsekiTT Blog

EnsekiTTが書くブログです。

Chainerで転移学習できるように重みやバイアスのコピーを1層ずつやってみた話

つまりなにしたの?

Caffe model zooのモデルを読み込んで使えるようにはなった。
転移学習のようなことをしようと思ったけど、モデルを好き放題付け替えてみたいと思った時に
自動的にやるスクリプトはちょっと自動過ぎて楽しめないので丁寧に重みとバイアスをコピーする方法を確認した。
f:id:ensekitt:20171125222552j:plain

前回まではこちら

Caffe Model Zooのモデルをとりあえず実行するまではこっちでできる
ensekitt.hatenablog.com
ensekitt.hatenablog.com

モデルを読み込むところ

ここは実行したときと同じ。
読み込んだモデルはmodelという名前の変数にいれることにした。
せっかくなのでBVLCのGoogLeNetのを使ってみた(これはちょっと後悔した→何故か?→モデルが複雑だった)

MODEL = 'model/bvlc_googlenet.caffemodel'
PICKLE = 'model/ggnet.pkl'
if os.path.exists(PICKLE):
    print("Load pickle")
    with open(PICKLE, 'rb') as pkl:
        model = pickle.load(pkl)
else:
    print("Load caffemodel and make pickle")
    if os.path.exists(MODEL):
        model = CaffeFunction(MODEL)
        with open(PICKLE, 'wb') as pkl:
            pickle.dump(model, pkl)
    print(MODEL + " not found.")

自前のGoogLeNetのモデル

class GoogLeNet(Chain):
    insize = 224
    
    def __init__(self):
        super(GoogLeNet, self).__init__()
        with self.init_scope():
            self.conv1 =  L.Convolution2D(3, 64, 7, stride=2, pad=3)
            self.conv2_reduce = L.Convolution2D(64, 64, 1)
            self.conv2 = L.Convolution2D(64, 192, 3, stride=1, pad=1)
            self.inception_3a = L.Inception(192, 64, 96, 128, 16, 32, 32)
            self.inception_3b = L.Inception(256, 128, 128, 192, 32, 96, 64)
            self.inception_4a = L.Inception(480, 192, 96, 208, 16, 48, 64)
            self.inception_4b = L.Inception(512, 160, 112, 224, 24, 64, 64)
            self.inception_4c = L.Inception(512, 128, 128, 256, 24, 64, 64)
            self.inception_4d = L.Inception(512, 112, 144, 288, 32, 64, 64)
            self.inception_4e = L.Inception(528, 256, 160, 320, 32, 128, 128)
            self.inception_5a = L.Inception(832, 256, 160, 320, 32, 128, 128)
            self.inception_5b = L.Inception(832, 384, 192, 384, 48, 128, 128)
            loss3_fc = L.Linear(1024, 1000)
            
            loss1_conv = L.Convolution2D(512, 128, 1)
            loss1_fc1 = L.Linear(2048, 1024)
            loss1_fc2 = L.Linear(1024, 1000)
            
            loss2_conv = L.Convolution2D(528, 128, 1)
            loss2_fc1 = L.Linear(2048, 1024)
            loss2_fc2 = L.Linear(1024, 1000)
        
    def __call__(self, x, train=True):
        h = F.relu(self.conv1(x))
        h = F.max_pooling_2d(h, 3, stride=2)
        h = F.local_response_normalization(h, n=5, k=1, alpha=2e-05)
        h = F.relu(self.conv2_reduce(h))
        h = F.relu(self.conv2(h))
        h = F.local_response_normalization(h, n=5, k=1, alpha=2e-05)
        h = F.max_pooling_2d(h, 3, stride=2)
        
        h = self.inception_3a(h)
        h = self.inception_3b(h)
        h = F.max_pooling_2d(h, 3, stride=2)
        h = self.inception_4a(h)
        
        b = F.average_pooling_2d(h, 5, stride=3)
        b = F.relu(self.loss1_conv(b))
        b = F.relu(self.loss1_fc1(b))
        b = self.loss1_fc2(b)
        
        h = self.inception_4b(h)
        h = self.inception_4c(h)
        h = self.inception_4d(h)
        
        b = F.average_pooling_2d(h, 5, stride=3)
        b = F.relu(self.loss2_conv(b))
        b = F.relu(self.loss2_fc1(b))
        b = self.loss2_fc2(b)
        
        h = self.inception_4e(h)
        h = F.max_pooling_2d(h, 3, stride=2)
        h = self.inception_5a(h)
        h = self.inception_5b(h)
        
        h = F.average_pooling_2d(h, 7, stride=1)
        y = self.loss3_fc(F.dropout(h, 0.4, train=train))
        return y

ggn = GoogLeNet()

今回の目的だと最初のconv1だけあるモデルでいいけど後々どうせ使うので全部入れた。

各層の中身を見る

各層にアクセスするにはドットで層の名前を接続するか、[]に層の名前を指定することでできる。

# コピー先
ggn.conv1.W.shape
# 実行例: (64, 3, 7, 7)

# コピー元
model['conv1/7x7_s2'].W.shape
# 実行例: (64, 3, 7, 7)
# 一致していることを確認すればコピーできる

f:id:ensekitt:20171125223431p:plain

コピーする

普通に代入すればいい。

# コピーする
ggn.conv1.W = model['conv1/7x7_s2'].W
ggn.conv1.b = model['conv1/7x7_s2'].b

f:id:ensekitt:20171125223506p:plain

クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。