In [178]:
%matplotlib inline
import importlib
import utils2
from utils2 import *
In [179]:
from scipy.optimize import fmin_l_bfgs_b
from scipy.misc import imsave
from keras import metrics
from vgg16_avg import VGG16_Avg
In [180]:
limit_mem()
In [181]:
path = './data/imagenet/sample/'
In [182]:
# ImageNetのサンプル画像のファイル名を取得
fnames = glob.glob(path + '**/*.JPEG', recursive=True)
n = len(fnames)
n
Out[182]:
In [183]:
fn = fnames[50]
fn
Out[183]:
In [184]:
img = Image.open(fnames[315])
img
Out[184]:
In [185]:
# ImageNetの学習済み重みを使うための画像前処理
rn_mean = np.array([123.68, 116.779, 103.939], dtype=np.float32)
preproc = lambda x: (x - rn_mean)[:, :, :, ::-1]
In [186]:
# ネットワークから生成される画像を表示するには上の逆の処理が必要
deproc = lambda x, s: np.clip(x.reshape(s)[:, :, :, ::-1] + rn_mean, 0, 255)
In [187]:
img_arr = preproc(np.expand_dims(np.array(img), 0))
shp = img_arr.shape
shp
Out[187]:
In [265]:
model = VGG16_Avg(include_top=False)
In [266]:
model.summary()
In [267]:
target = 'block5_conv1'
layer = model.get_layer(target).output
In [268]:
layer
Out[268]:
In [269]:
# 指定したレイヤの出力を出力するモデルを新しく構成する
layer_model = Model(model.input, layer)
layer_model.summary()
# 出力をもとにバックプロパゲーションするので処理できるようにVariable化しておく
# targは特定の画像(img_arrに入っている鳥の絵)を入れたときの出力 Variable になる
# 一方、layerは同じレイヤの出力であるが特定の入力を指定していない Tensor になる
targ = K.variable(layer_model.predict(img_arr))
targ
Out[269]:
In [270]:
# fmin_l_bfgs_b()を使うにはlossとgradに別々にアクセスできる関数が必要
class Evaluator(object):
def __init__(self, f, shp):
# f: lossとgradを返すK.function
# shp: 入力画像(4D tensor)のサイズ
self.f, self.shp = f, shp
def loss(self, x):
# lossとgradがまとめて計算されるのでgradは保存しておく
loss_, self.grad_values = self.f([x.reshape(self.shp)])
return loss_.astype(np.float64)
def grads(self, x):
# 保存しておいたgradを返す
return self.grad_values.flatten().astype(np.float64)
In [271]:
# layerはまだ特定の画像を入れていない出力(あとでmodel.inputとして灰色の画像を入れる)
# targは鳥の画像を入れたときの出力
# metrics.mse()はスカラーを返さなくなった?
# TODO: fast.aiではK.sum()がなくてもスカラーを返している? Keras2では仕様が変わった?
# TODO: 論文だとK.sum()を使っている。content_lossとstyle_lossを組み合わせたときどうするのがよい?
loss = K.sum(metrics.mse(layer, targ))
In [272]:
loss
Out[272]:
In [273]:
# パラメータではなく、モデルへの入力画像に対する勾配を求める
grads = K.gradients(loss, model.input)
In [274]:
print(type([loss]), type(grads))
In [275]:
[1, 2, 3] + [4, 5, 6]
Out[275]:
In [276]:
# 足し算しても要素がちゃんとわかれている
[loss] + grads
Out[276]:
In [277]:
# lossとgradsを結合したリストを返す関数
fn = K.function([model.input], [loss] + grads)
In [278]:
evaluator = Evaluator(fn, shp)
In [279]:
def solve_image(eval_obj, niter, x):
for i in range(niter):
x, min_val, info = fmin_l_bfgs_b(eval_obj.loss, x.flatten(),
fprime=eval_obj.grads, maxfun=20)
x = np.clip(x, -127, 127)
print('Current loss value:', min_val)
imsave('./results/res_at_iteration_%d.png' % i, deproc(x.copy(), shp)[0])
return x
In [280]:
rand_img = lambda shape: np.random.uniform(-2.5, 2.5, shape) / 100
x = rand_img(shp)
print(x.shape)
plt.imshow(x[0])
Out[280]:
In [281]:
iterations = 10
x = solve_image(evaluator, iterations, x)
In [282]:
Image.open('results/res_at_iteration_0.png')
Out[282]:
In [283]:
Image.open('results/res_at_iteration_9.png')
Out[283]:
In [284]:
%ls results/
In [285]:
from IPython.display import HTML
from matplotlib import animation, rc
In [286]:
fig, ax = plt.subplots()
def animate(i):
ax.imshow(Image.open('results/res_at_iteration_%d.png' % i))
In [287]:
anim = animation.FuncAnimation(fig, animate, frames=10, interval=200)
HTML(anim.to_html5_video())
Out[287]:
In [288]:
style = Image.open('data/starry_night.jpg')
style
Out[288]:
In [289]:
style = style.resize((400, 500))
style
Out[289]:
In [290]:
style_arr = preproc(np.expand_dims(style, 0)[:, :, :, :3])
In [291]:
np.expand_dims(style, 0).shape
Out[291]:
In [292]:
shp = style_arr.shape
In [293]:
model = VGG16_Avg(include_top=False, input_shape=shp[1:])
model
Out[293]:
In [294]:
# layerの名前 => layerの出力Tensorへの辞書
outputs = {l.name: l.output for l in model.layers}
outputs
Out[294]:
In [295]:
# style lossは複数の層の出力を使う
layers = [outputs['block{}_conv1'.format(o)] for o in range(1, 3)]
layers
Out[295]:
In [296]:
layers_model = Model(model.input, layers)
In [297]:
layers_model.summary()
In [298]:
# 実際のスタイル画像(style_arr)を入力したときの各層の出力をターゲットとする
targs = [K.variable(o) for o in layers_model.predict(style_arr)]
targs
Out[298]:
It's unclear why this helps us achieve our goal, but it works. One thought is that the gramian shows how our features at that convolutional layer correlate, and completely removes all location information. So matching the gram matrix of channels can only match some type of texture information, not location information.
In [299]:
# xはあるレイヤの出力(複数チャネルあり)
# バッチはなくなっている
# 論文の式 (3) に当たる
def gram_matrix(x):
# (height, width, channel) => (channel, height, width)
features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1)))
# チャネル間の内積を取る
return K.dot(features, K.transpose(features)) / x.get_shape().num_elements()
In [300]:
# 論文の式(4)にあたる
def style_loss(x, targ):
# TODO: sumでよい?
return K.sum(metrics.mse(gram_matrix(x), gram_matrix(targ)))
In [301]:
# 式(5)のスタイルlossに当たるが重み付けはしていない
loss = sum(style_loss(l1[0], l2[0]) for l1, l2 in zip(layers, targs))
grads = K.gradients(loss, model.input)
style_fn = K.function([model.input], [loss] + grads)
evaluator = Evaluator(style_fn, shp)
In [302]:
rand_img = lambda shape: np.random.uniform(-2.5, 2.5, shape) / 1
x = rand_img(shp)
plt.imshow(x[0])
Out[302]:
In [303]:
x = scipy.ndimage.filters.gaussian_filter(x, [0, 2, 2, 0])
plt.imshow(x[0])
Out[303]:
In [304]:
iterations = 10
x = rand_img(shp)
x = solve_image(evaluator, iterations, x)
In [305]:
Image.open('results/res_at_iteration_0.png')
Out[305]:
In [306]:
Image.open('results/res_at_iteration_8.png')
Out[306]:
In [307]:
def plot_arr(arr):
plt.imshow(deproc(arr,arr.shape)[0].astype('uint8'))
In [308]:
# 使うモデルは1つなのでスタイルのサイズに合わせる必要がある
w, h = style.size
src = img_arr
print(style.size)
print(src.shape)
plot_arr(src)
In [309]:
outputs
Out[309]:
In [310]:
# block1からblock5のすべての層の出力を使ってスタイルを求める
# スタイルは複数の層の出力を使う
style_layers = [outputs['block{}_conv2'.format(o)] for o in range(1, 6)]
# コンテンツは一つの層の出力を使う
content_layer = outputs['block4_conv2']
In [311]:
# ここの model はスタイル再構成のときのモデルなのでスタイル画像のサイズが入力となる
style_model = Model(model.input, style_layers)
style_targs = [K.variable(o) for o in style_model.predict(style_arr)]
style_targs
Out[311]:
In [312]:
content_model = Model(model.input, content_layer)
# 入力はスタイルと同じサイズであることがわかる
content_model.summary()
# 画像を入れたときの出力
content_targ = K.variable(content_model.predict(src))
content_targ
Out[312]:
In [313]:
style_weights = [0.05, 0.2, 0.2, 0.25, 0.3]
In [330]:
# 重み付けしたスタイルloss 式 (5)
loss = sum(style_loss(l1[0], l2[0]) * w for l1, l2, w in zip(style_layers, style_targs, style_weights))
# コンテンツlossを足し合わせる
# コンテンツ重みとスタイル重みの比を変えられる
# 論文とはlossの求め方が違うので試行錯誤が必要
content_weight = 0.01
# 1: コンテンツが強く出過ぎでスタイルがほとんど出ない
# 0.001: スタイルが出ている
# 0.01: そんなに変わらない?
loss += K.sum(K.square(content_layer - content_targ)) * content_weight
grads = K.gradients(loss, model.input)
transfer_fn = K.function([model.input], [loss] + grads)
In [331]:
evaluator = Evaluator(transfer_fn, shp)
In [332]:
iterations = 10
x = rand_img(shp)
In [333]:
x = solve_image(evaluator, iterations, x)
In [334]:
Image.open('results/res_at_iteration_0.png')
Out[334]:
In [335]:
Image.open('results/res_at_iteration_9.png')
Out[335]:
In [ ]: