あかすくぱるふぇ

同人サークル「あかすくぱるふぇ」のブログです。

以下の記事で紹介している画風変換のソースコードからエッセンスを抽出してみました。
画風を変換するアルゴリズム

エッセンスを抽出したソースコードを以下に載せます。
元コードのchainer-gogh.pyに相当します。
#coding:utf-8
import os, sys
import chainer.links as L
import numpy as np
from chainer import optimizers
from PIL import Image
from models import *

# 定数
iter = 5000
width = 435
mean = 120
lr = 4.0

# 画像ファイルの入力と変換
def image_resize(img_file, width):
img = Image.open(img_file)
if img.size != (width, width):
print "Image must be square."
sys.exit()
img = np.asarray(img)[:,:,:3].transpose(2, 0, 1)[::-1].astype(np.float32)
return img.reshape(1, 3, width, width) - mean

# 画像の保存
def save_image(img, width, it):
def to_img(x):
im = np.zeros((width, width, 3))
im[:,:,0] = x[2,:,:]
im[:,:,1] = x[1,:,:]
im[:,:,2] = x[0,:,:]
def clip(a):
return 0 if a<0 else (255 if a>255 else a)
im = np.vectorize(clip)(im).astype(np.uint8)
Image.fromarray(im).save("im_%05d.png"%it)

to_img(img[0,:,:,:] + mean)

# スタイル行列の算出
def get_matrix(y):
ch = y.data.shape[1]
wd = y.data.shape[2]
gogh_y = F.reshape(y, (ch,wd**2))
gogh_matrix = F.matmul(gogh_y, gogh_y, transb=True)/np.float32(ch*wd**2)
return gogh_matrix

# モデルの読み込み
nn = NIN()

# 画像の入力
img_orig = image_resize("cat.png", width)
img_style = image_resize("style_0.png", width)

# 中間層とスタイル行列の算出
mid_orig = nn.forward(Variable(img_orig, volatile=True))
style_mats = [get_matrix(y) for y in nn.forward(Variable(img_style, volatile=True))]

# 初期合成画像を生成し、最適化対象として設定
img_gen = np.random.uniform(-20,20,(1,3,width,width)).astype(np.float32)
img_gen = L.Parameter(img_gen)
optimizer = optimizers.Adam(alpha = lr)
optimizer.setup(img_gen)

for i in range(iter):
img_gen.zerograds()
y = nn.forward(img_gen.W)
loss = Variable(np.zeros((), dtype=np.float32))
for l in range(len(y)):
# 損失関数の算出
loss1 = np.float32(0.005) * np.float32(nn.alpha[l])*F.mean_squared_error(y[l], Variable(mid_orig[l].data))
loss2 = np.float32(nn.beta[l])*F.mean_squared_error(get_matrix(y[l]), Variable(style_mats[l].data))/np.float32(len(y))
loss += loss1 + loss2

loss.backward()
optimizer.update()
tmp_shape = img_gen.W.data.shape
def clip(x):
return -120 if x<-120 else (136 if x>136 else x)
img_gen.W.data += np.vectorize(clip)(img_gen.W.data).reshape(tmp_shape) - img_gen.W.data

if i%50==0:
print i
save_image(img_gen.W.data, width, i)
たったの82行!
やはりChainerすごいですね。

ほとんど解説の必要はなさそうですが……、肝となるのは以下の部分でしょうか。
# 初期合成画像を生成し、最適化対象として設定
img_gen = np.random.uniform(-20,20,(1,3,width,width)).astype(np.float32)
img_gen = L.Parameter(img_gen)
optimizer = optimizers.Adam(alpha = lr)
optimizer.setup(img_gen)
ランダムな画像を生成して、それを最適化対象パラメータとしてoptimizerに設定しています。
ネットワークではなく、画像を更新するのですね。面白い。

Chainerでcaffemodelを読み込んで使えるらしいです。素晴らしい!
Chainerでcaffemodelを読み込んで画像を分類する

ただ、例のごとく、Python初心者には何をやっているのかよく分からなかったので解析してみました。
ソースコードは上記リンクのものを利用させていただき、Python初心者が躓きそうなところをピックアップして解説していきます。

def load(self, path):
self.func = caffe.CaffeFunction(path)

def _predict_class(self, x):
y, = self.func(inputs={'data': x}, outputs=['loss3/classifier'], disable=['loss1/ave_pool', 'loss2/ave_pool'], train=False)
return F.softmax(y)
まずは一番肝になる部分から。
CaffeFunction()は、caffemodelファイルを読みこんで、Chainerで呼び出せる関数を生成します。
func()の引数は、(入力データ, 出力層, 出力に関連しない無視する層, 学習フェーズかどうか)です。
層の名前は読み込むcaffemodelによって異なるので、調べる必要があります。
第三引数は指定しなくても結果は変わりません(無視することによって処理時間が短くなるとか?)。

def load_label(self, path):
self.categories = np.loadtxt(path, str, delimiter="\n")
カテゴリ名を記載したファイルの読み込みです。
この例では、categoriesは1000要素の配列で、各要素はカテゴリ名となります。

def predict(self, image_path, rank=10):
x = chainer.Variable(self.load_image(image_path), volatile=True)
y = self._predict_class(x)
result = zip(y.data.reshape((y.data.size,)), self.categories)
return sorted(result, reverse=True)
result = zip(a, b)は(a, b)というセットを要素として持つ配列resultを生成します。
そして、sorted(result)は、resultを(zip(a, b)のaについて)ソートする関数です。
この処理によって、カテゴリを確率の高いものから順に並べた結果を得ることができます。

def print_prediction(self, image_path, rank=10):
prediction = self.predict(image_path, rank)
for i, (score, label) in enumerate(prediction[:rank]):
print '{:>3d} {:>6.2f}% {}'.format(i + 1, score * 100, label)
enumerate()はインデックスと要素を同時に得るための関数です。
この例では、iにインデックス、(score, label)にprediction[i]が入ります。
また、{:>3d}や{:>6.2f}はオプション付きの書式であり、>は右詰めのオプションです。

以上です。

本記事では、Python初心者のためにCIFAR-10データのファイル入力について解説します。
CIFAR-10というのは、一般物体認識のベンチマーク用画像データセットです。
http://www.cs.toronto.edu/~kriz/cifar.html

本記事を書くきっかけとなったのは、以下の記事を参考にChainerでCNNを試したことです。
実行はうまくいったのですが、Python初心者なので、CIFAR-10のファイル入力の理解に手間取りました。
ChainerによるCIFAR-10の一般物体認識 (1)

まずは、ソースコードを載せます。
このソースコードは上記サイトと以下のサイトのコードを合体して、改変したものです。
http://aidiary.hatenablog.com/entry/20151014/1444827123
#coding: utf-8
import numpy as np
import matplotlib.pyplot as plt

def unpickle(f):
import cPickle
fo = open(f, 'rb')
d = cPickle.load(fo)
fo.close()
return d

def load_cifar10(datadir):
train_data = []
train_target = []

# 訓練データをロード
for i in range(1, 6):
d = unpickle("%s/data_batch_%d" % (datadir, i))
train_data.extend(d["data"])
train_target.extend(d["labels"])

# テストデータをロード
d = unpickle("%s/test_batch" % (datadir))
test_data = d["data"]
test_target = d["labels"]

# データはfloat32、ラベルはint32のndarrayに変換
train_data = np.array(train_data, dtype=np.float32)
train_target = np.array(train_target, dtype=np.int32)
test_data = np.array(test_data, dtype=np.float32)
test_target = np.array(test_target, dtype=np.int32)

# 画像のピクセル値を0-1に正規化
train_data /= 255.0
test_data /= 255.0

return train_data, test_data, train_target, test_target

# CIFAR-10データをロード
print "load CIFAR-10 dataset"
X_train, X_test, y_train, y_test = load_cifar10("data")

# 画像を (nsample, channel, height, width) の4次元テンソルに変換
X_train = X_train.reshape((len(X_train), 3, 32, 32))
X_test = X_test.reshape((len(X_test), 3, 32, 32))

# 画像を表示
plt.imshow(X_train[0].reshape(3, 32, 32).transpose(1, 2, 0))
plt.show()
では、解説していきます。
def unpickle(f):
import cPickle
fo = open(f, 'rb')
d = cPickle.load(fo)
fo.close()
return d
ここでは、PythonのcPickleを利用しています。
cPickleはPythonオブジェクトとバイト列との相互変換を行うライブラリです。
この例では、load()によってファイル入力を行い、Pythonのdict型オブジェクトであるdを生成しています。
dict型オブジェクトは、以下のサイトに記されているように、キーと値が紐づけられたオブジェクトです。
http://www.pythonweb.jp/tutorial/dictionary/index8.html

# 訓練データをロード
for i in range(1, 6):
d = unpickle("%s/data_batch_%d" % (datadir, i))
train_data.extend(d["data"])
train_target.extend(d["labels"])
6個のファイルに分かれた訓練データをロードします。
"%s/data_batch_%d" % (datadir, i)は、%sにdatadir、%dにiを代入して、ファイル名文字列となります。
このファイル名のファイルからデータをロードし、train_dataとtrain_targetに追加していきます。
d["key"]はdict型オブジェクトdから"key"という名前のキーに紐づけられた値を参照します。
"data"キーは画像データ、"labels"キーは正解ラベルの値と紐づけられています。

# 画像を (nsample, channel, height, width) の4次元テンソルに変換
X_train = X_train.reshape((len(X_train), 3, 32, 32))
X_test = X_test.reshape((len(X_test), 3, 32, 32))
データをChainerのデータ形式(画像数, チャンネル数, 高さ, 幅)に変換します。

以上です。

↑このページのトップヘ