読者です 読者をやめる 読者になる 読者になる

kivantium活動日記

プログラムを使っていろいろやります

Chainerの練習

畳み込みニューラルネットワーク(CNN)において、第一層の学習結果はエッジ検出などに用いられるフィルタに近いものになりやすいことが知られています。このことから、層を重ねることによって高次の特徴を抽出することができるようになったことがCNNが成功した理由なのではないかという議論もあります。

エッジ検出を行うフィルタには何種類もありますが、ここではラプラシアンフィルタを取り上げます。OpenCVではLaplacian()という関数に実装されています。一番簡単なものだと
f:id:kivantium:20160204211305p:plain
という行列を、あるピクセルを中心とする3x3の領域に掛けて総和をそのピクセルの値とするという感じの処理をするようです。

今回はChainerの練習としてこのLaplacianが学習できるかどうかやってみます。最初のCNNの話は特に関係ありません。

コード

ChainerでLaplacianと同じconvolutionを行って作った目標画像に近づくようにフィルタの値を最適化していくという感じで学習させていきます。誤差関数は平均二乗誤差です。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
import cv2
import argparse

import chainer
from chainer import optimizers
import chainer.functions as F
import chainer.links as L

import numpy as np

# 引数の処理
parser = argparse.ArgumentParser(
    description='train convolution filters')
parser.add_argument('org', help='Path to original image')
args = parser.parse_args()

# クラスの定義
class Conv(chainer.Chain):
    def __init__(self):
        super(Conv, self).__init__(
            # 入力・出力1ch, ksize=3
            conv1=L.Convolution2D(1, 1, 3, stride=1, pad=1),
        )

    def clear(self):
        self.loss = None
        self.accuracy = None

    def forward(self, x):
        self.clear()
        h = self.conv1(x)
        return h

    def calc_loss(self, x, t):
        self.clear()
        h = self.conv1(x)
        loss = F.mean_squared_error(h, t)
        return loss 

# 画像の読み込み
train_image = chainer.Variable(np.asarray([[cv2.imread(args.org, 0)/255.0]], dtype=np.float32))

# 正解画像の作成
laplacian = Conv()
laplacian.conv1.W.data[0][0] = [[0, 1, 0],[1, -4, 1], [0, 1, 0]]
laplacian.conv1.b.data[0] = 0.0
target = laplacian.forward(train_image)
cv2.imwrite("target.jpg", target.data[0][0]*255)

# 学習対象のモデル作成
model = Conv()
initial = model.forward(train_image)
cv2.imwrite("initial.jpg", initial.data[0][0]*255)

# 最適化の設定
optimizer = optimizers.Adam()
optimizer.setup(model)

# 学習
for seq in range(20000):
    loss = model.calc_loss(train_image, target)
    if seq%100==0:
        print("{}: {}".format(seq, loss.data))
    model.zerograds()
    loss.backward()
    optimizer.update()

# 学習結果の表示
print(model.conv1.W.data[0][0])
trained = model.forward(train_image).data[0][0]*255
cv2.imwrite("trained.jpg", trained)

入力画像
f:id:kivantium:20160204212243p:plain
目標画像
f:id:kivantium:20160204212307j:plain
初期画像
f:id:kivantium:20160204212354j:plain
学習後の画像
f:id:kivantium:20160204212413j:plain

学習前のconv1の重み

[[ 0.40687242 -0.22795093  0.03506852]
 [-0.17084719  0.46458822 -0.13198636]
 [ 0.19725543  0.07359087  0.03956784]]

学習後のconv1の重み

[[  1.64782505e-05   9.99969304e-01   1.64098510e-05]
 [  9.99969244e-01  -3.99994040e+00   9.99969304e-01]
 [  1.64479734e-05   9.99969304e-01   1.64535559e-05]]

目標とするべき重みにかなり近いものを学習できました。

まとめ

重みを設定してそれに近づけるように学習したら学習できたよというだけの話でした。Chainerの修行が足りないのでこういう簡単なものから少しずつ練習していきたいです。