前一段時間特別火的 Prisma 大家都玩了么,看了這篇文章后,你也可以自己寫一個 Prisma 迷你版了。
這個 idea 最開始起源于 Google Research Blog
Here's the initial Google DeepDream blog post:
他們用大量的圖片數據來訓練深度神經網絡,使這個網絡可以判斷出圖片中的事物,然后投入一個新的圖片,讓圖片識別,不僅僅是識別,還要把圖片修正為網絡學到的東西。
然后另一個團隊發表了一篇相似的論文
他們用名畫來訓練模型,然后投入一個生活中的圖片,通過強化一些 feature,將這個圖片修正為更像名畫風格的圖片。
原理就是用一個 Convolutional Neural Network 學習一張圖片的 style ,然后把另一張圖片轉換成這種 style。
用到的工具是 python 和 keras 包,文章后面有作者的源碼的地址。
引入需要的包
from scipy.misc import imread, imresize, imsave
from scipy.optimize import fmin_l_bfgs_b
from sklearn.preprocessing import normalize
import numpy as np
import time
import os
import argparse
import h5py
from keras.models import Sequential
from keras.layers.convolutional import Convolution2D, ZeroPadding2D, AveragePooling2D
from keras import backend as K
定義三個圖片變量
#Define base image, style image, and result image paths
args = parser.parse_args()
base_image_path = args.base_image_path
style_reference_image_path = args.style_reference_image_path
result_prefix = args.result_prefix
引用事先計算好的 weights vgg16
這是提前訓練好的,可以識別生活中的圖片,以它作為模型的起點。
#Get the weights file
weights_path = r"vgg16_weights.h5"
定義 booleans 決定是否 reshape 圖片
#Init bools to decide whether or not to resize
rescale_image = strToBool(args.rescale_image)
maintain_aspect_ratio = strToBool(args.maintain_aspect_ratio)
然后初始化 style-content weights
什么是style-content weights?
在神經網絡學習的過程中,不同的層學到的東西是不一樣的,例如識別一個小狗,一層學到的是 edge,下一層學到的是 shape,再下一層是更復雜的 shape,最后學到的是整個的 dog。
在學習藝術風格的網絡中發現,低層次學到的是 style,如紋理 顏色框架等,高層次學到的是 content,如太陽等具體的物體,CNN會把 content 和 style 分離開,所以要達到不同的效果,需要不同的權重分配。
# Init variables for style and content weights.
total_variation_weight = args.tv_weight
style_weight = args.style_weight * args.style_scale
content_weight = args.content_weight
然后設定圖片維度,定義tensor代表三個圖片 base image,style image,output image。
# Init dimensions of the generated picture.
img_width = img_height = args.img_size
assert img_height == img_width, 'Due to the use of the Gram matrix, width and height must match.'
img_WIDTH = img_HEIGHT = 0
aspect_ratio = 0
# get tensor representations of our images
base_image = K.variable(preprocess_image(base_image_path, True))
style_reference_image = K.variable(preprocess_image(style_reference_image_path))
# this will contain our generated image
combination_image = K.placeholder((1, 3, img_width, img_height))
再組合到一個 tensor 中
# combine the 3 images into a single Keras tensor
input_tensor = K.concatenate([base_image,
style_reference_image,
combination_image], axis=0)
放在一個 tensor 中,因為更容易被 神經網絡 解析,這樣一個高維的圖片也可以有可以計算的復雜度。
建立 31 層的神經網絡
# build the VGG16 network with our 3 images as input
first_layer = ZeroPadding2D((1, 1))
first_layer.set_input(input_tensor, shape=(3, 3, img_width, img_height))
model = Sequential()
model.add(first_layer)
model.add(Convolution2D(64, 3, 3, activation='relu', name='conv1_1'))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(64, 3, 3, activation='relu'))
model.add(AveragePooling2D((2, 2), strides=(2, 2)))
。。。
一共有3種:
convolution2D layer:擁有可學習的filters,這些filters有receptive field,用來將神經元連接到下一層的一個局部的區域,而不是連接到每一個神經元
ZeroPadding layer:用來控制 output 的大小
Pooling layer:只用圖片的子集來計算,減少參數數量,用來避免 overfitting。
激活函數用的是 ReLU,比sigmoid更快一些。
各個層的參數分別是:
定義完模型后,引入 vgg16 的權重
# load the weights of the VGG16 networks
load_weights(weights_path, model)
定義 Loss Function:計算預測和實際的差別
# get the symbolic outputs of each "key" layer (we gave them unique names).
outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])
# get the loss (we combine style, content, and total variation loss into a single scalar)
loss = get_total_loss(outputs_dict)
得到 gradients
# get the gradients of the generated image wrt the loss
grads = K.gradients(loss, combination_image)
最后用 back propagation 訓練模型,此處用到的算法是 limit-memory BFGS,可以最小化 loss function 而且空間效率較高。
#combine loss and gradient
f_outputs = combine_loss_and_gradient(loss, grads)
# Run scipy-based optimization (L-BFGS) over the pixels of the generated image to minimize the neural style loss
# 5 Step process
x, num_iter = prepare_image()
for i in range(num_iter):
#Step 1 - Record iterations
print('Start of iteration', (i+1))
start_time = time.time()
#Step 2 - Perform l_bfgs optimization function using loss and gradient
x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x.flatten(),
fprime=evaluator.grads, maxfun=20)
print('Current loss value:', min_val)
#Step 3 - Get the generated image
img = deprocess_image(x.reshape((3, img_width, img_height)))
#Step 4 - Maintain aspect ratio
if (maintain_aspect_ratio) & (not rescale_image):
img_ht = int(img_width * aspect_ratio)
print("Rescaling Image to (%d, %d)" % (img_width, img_ht))
img = imresize(img, (img_width, img_ht), interp=args.rescale_method)
if rescale_image:
print("Rescaling Image to (%d, %d)" % (img_WIDTH, img_HEIGHT))
img = imresize(img, (img_WIDTH, img_HEIGHT), interp=args.rescale_method)
最后,rescale 并且保存圖片
#Step 5 - Save the generated image
fname = result_prefix + '_at_iteration_%d.png' % (i+1)
imsave(fname, img)
end_time = time.time()
print('Image saved as', fname)
print('Iteration %d completed in %ds' % (i+1, end_time - start_time))
這個算法也可以用到視頻中。
另外還找到一篇《我是如何用TensorFlow 做出屬于自己的Prisma的?》
感興趣就動手寫一下吧。
The code for this video is here:
Here's the initial Google DeepDream blog post:
A Deepdream web app:
The Neural Style Paper:
我是 不會停的蝸牛 Alice
85后全職主婦
喜歡人工智能,行動派
創造力,思考力,學習力提升修煉進行中
歡迎您的喜歡,關注和評論!