作者 | Sourish Dey
来源 | Medium
编辑 | 代码医生团队
近几年来,经历了计算机视觉在生活中几乎每个角落的应用 - 得益于大量数据和超级动力GPU的可用性,这些GPU已经对卷积神经网络进行了训练和部署(CNN)超级容易。今天在机器学习中最有趣的讨论之一是它如何在未来几十年影响和塑造文化和艺术生产。神经风格迁移是卷积神经网络最具创造性的应用之一。
通过拍摄内容图像和风格图像,神经网络可以重新组合内容和风格图像,以有效地创建艺术(重构)图像。虽然像Prisma这样的应用程序可以为从手机拍摄的照片生成艺术风格,本文的目的是了解这个看似困难的概念背后的科学和艺术。这里共享实时可实现的代码。
背景 - 神经风格迁移
神经风格迁移概念首先由Gatys,Ecker和Bethge(艺术风格的神经算法)引入开创性论文 在2015年)展示了一种将一个图像的艺术风格与另一个图像的内容相结合的方法。基本思想是采用由预训练的深度卷积神经网络(例如VGG 16(通常训练用于图像分类或物体检测))学习的特征表示来获得图像的样式和内容的单独表示。一旦找到这些表示,就会尝试优化生成的图像,以重新组合不同目标图像的内容和样式。因此该概念使纹理,对比度和颜色随机化,同时保留内容图像的形状和语义特征(中心方面)。虽然它有点类似于颜色转换。
问题陈述 - 这不是优化问题吗?
因此这里的问题陈述给出了内容照片X和样式照片Y如何将Y的样式转移到内容X以生成新的照片Z。如何训练CNN来处理和优化差异(X之间的差异)和Y)达到最佳全局(Z)?
优化问题概述
Gatys在原始论文(2015年的艺术风格的神经算法 )中表示,“将一个图像转换为另一个内容图像的样式(纹理)作为优化问题,可以通过训练深度神经网络来解决”。这个难题的组成部分:
https://github.com/nitsourish/Neural-Style-Transfer-on-video-data
实时视频的神经风格迁移:
将解释图像的步骤,因为视频只是一组图像的集合。这些图像被称为帧,可以组合起来获得原始视频。因此可以遍历所有单独帧的步骤,重新组合并生成风格化视频。
第1步:加载预先训练的VGG-16 CNN模型
为NST应用从头开始构建(训练)CNN需要大量的时间和强大的计算基础设施,而这些基础设施并不是个人可用的。
因此将加载预先训练的CNN -VGG-16的权重(从着名的' ImageNet。'挑战图像训练)来实现神经样式迁移。将使用Keras应用程序加载具有预训练重量的VGG-16。对于NST,VGG-16可能不是最佳(期望的复杂性)CNN架构。对于此应用程序,有更复杂(更深入的高级架构)网络,如InceptionV4,VGG-19,Resnet-101等,这将花费更多时间来加载和运行。然而,作为实验选择了VGG-16(具有高分类精度和对特征的良好内在理解)。
from keras.applications.vgg16 import VGG16
shape = (224,224)
vgg = VGG16(input_shape=shape,weights='imagenet',include_top=False)
这里的形状很重要,因为VGG-16网络采用224 x 224 x 3形状的输入图像。
vgg.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_21 (InputLayer) (None, 224, 224, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
average_pooling2d_101 (Avera (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
average_pooling2d_102 (Avera (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
average_pooling2d_103 (Avera (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
average_pooling2d_104 (Avera (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
average_pooling2d_105 (Avera (None, 7, 7, 512) 0
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
VGG-16架构
第2步:定义内容模型和成本函数
对于高级内容功能,希望考虑整个图像中的功能。因此将使用平均池替换max-pool(可能会丢弃一些信息)。然后将从总共13个卷中选择任何更深层作为“输出”并将模型定义到该层。然后将在n / w中提供我们的预处理内容图像(X),以在输出层计算(预测)特征/激活图,该模型和模型输出与定义形状的任何随机(白噪声)矩阵相对应(224 x 224 x 3)。计算内容图像网络的MSE损失和梯度。这将有助于将输入图像(随机图像)更新为梯度的相反方向,并允许内容丢失值减小,以便生成的图像将与输入的图像匹配图片。详细的实施代码保存在GitHub存储库。
https://github.com/nitsourish/Neural-Style-Transfer-on-video-data
content_model = vgg_cutoff(shape, 13) #Can be experimented with other deep layers
# make the target
target = K.variable(content_model.predict(x))
# try to match the input image
# define loss in keras
loss = K.mean(K.square(target - content_model.output))
# gradients which are needed by the optimizer
grads = K.gradients(loss, content_model.input)
第3步:定义样式模型和样式损失函数
两个图像的特征图在给定层产生相同的Gram矩阵,希望两个图像具有相同的样式(但不一定是相同的内容)。因此网络中早期层中的激活图将捕获一些更精细的纹理(低级特征),而激活贴图更深的层将捕获更高级别的图像样式元素。为了获得最佳结果,将结合浅层和深层作为输出来比较图像的样式表示和相应地定义了多输出模型。
首先,计算每层的Gram矩阵,并计算样式网络的总样式损失。对不同的层采用不同的权重来计算加权损失。然后基于样式损失(样式分量的差异)和渐变,更新输入图像(随机图像)并减少样式损失值,使得生成的图像(Z)纹理看起来类似于样式图像(Y)。
#Define multi-output model
symb_conv_outputs = [layer.get_output_at(1) for layer in vgg.layers\
if layer.name.endswith('conv1')]
multi_output_model = Model(vgg.input, symb_conv_outputs)
#Style feature map(outputs) of style image
symb_layer_out = [K.variable(y) for y in multi_output_model.predect(x)]
#Defining Style loss
def gram_matrix(img):
X = K.batch_flatten(K.permute_dimensions(img,(2,0,1)))
gram_mat = K.dot(X,K.transpose(X))/img.get_shape().num_elements()
return gram_mat
def style_loss(y,t):
return K.mean(K.square(gram_matrix(y)-gram_matrix(t)))
#Style loss calculation through out the network
#Defining layer weights for layers
weights = [0.2,0.4,0.3,0.5,0.2]
loss=0
for symb,actual,w in zip(symb_conv_outputs,symb_layer_out,weights):
loss += w * style_loss(symb[0],actual[0])
grad = K.gradients(loss,multi_output_model.input)
get_loss_grad = K.Function(inputs=[multi_output_model.input], outputs=[loss] + grad)
第4步:定义总成本(总损失):
现在可以将内容和样式损失结合起来以获得网络的整体损失。需要使用合适的优化算法在迭代中最小化该数量。
#Content Loss
loss=K.mean(K.square(content_model.output-content_target)) * Wc #Wc is content loss weight(hyperparameter)
#Defining layer weights of layers for style loss
weights = [0.2,0.4,0.3,0.5,0.2]
#Total loss and gradient
for symb,actual,w in zip(symb_conv_outputs,symb_layer_out,weights):
loss += Ws * w * style_loss(symb[0],actual[0]) #Wc is content loss weight(hyperparameter)
grad = K.gradients(loss,vgg.input)
get_loss_grad = K.Function(inputs=[vgg.input], outputs=[loss] + grad)
第5步:解决优化问题和损失最小化函数
在定义整个符号计算图之后,优化算法是主要组件,其将能够迭代地最小化整体网络成本。这里不使用keras标准优化器函数(例如optimizers.Adam,optimizers.sgd等),这可能需要更多时间,将使用有限内存BFGS(Broyden-Fletcher-Goldfarb-Shanno),这是一个近似的数值使用有限数量的计算机内存的优化算法。由于其产生的线性存储器要求,该方法非常适用于涉及大量无变量(参数)的优化问题。与普通BFGS一样,它是一种标准的准牛顿方法,通过最大化正则化对数似然来优化平滑函数。
Scipy的最小化函数(fmin_l_bfgs_b)允许传回函数值f(x)及其渐变f'(x),在前面的步骤中计算过。但是需要将输入展开为1-D数组格式的最小化函数,并且丢失和渐变都必须是np.float64。
#Wrapper Function to feed loss and gradient with proper format to L-BFGS
def get_loss_grad_wrapper(x_vec):
l,g = get_loss_grad([x_vec.reshape(*batch_shape)])
return l.astype(np.float64), g.flatten().astype(np.float64)
#Function to minimize loss and iteratively generate the image
def min_loss(fn,epochs,batch_shape):
t0 = datetime.now()
losses = []
x = np.random.randn(np.prod(batch_shape))
for i in range(epochs):
x, l, _ = scipy.optimize.fmin_l_bfgs_b(func=fn,x0=x,maxfun=20)
# bounds=[[-127, 127]]*len(x.flatten())
#x = np.clip(x, -127, 127)
# print("min:", x.min(), "max:", x.max())
print("iter=%s, loss=%s" % (i, l))
losses.append(l)
print("duration:", datetime.now() - t0)
plt.plot(losses)
plt.show()
newimg = x.reshape(*batch_shape)
final_img = unpreprocess(newimg)
return final_img[0]
第6步:在输入内容和样式图像上运行优化器功能:
在输入内容框架和样式图像上运行优化器,并根据定义的符号计算图形,网络完成其最小化总体损失的预期工作,并生成看起来与内容和样式图像一样接近的图像。
输出图像仍然很嘈杂,因为只运行网络30次迭代。理想的NST网络应针对数千次迭代进行优化,以达到最小损耗阈值,从而生成清晰的混合输出。
第7步:对所有图像帧重复上述步骤:
在从短视频中提取帧之后对每个帧执行网络推断,为每个帧生成样式化图像并重新组合/缝合样式化图像帧。
#Vedio Reading and extracting frames
cap = cv2.VideoCapture(path)
while(True):
ret, frame = cap.read()
frame = cv2.resize(frame,(224,224))
X = preprocess_img(frame)
#Running the above optimization as per defined comutation graph and generate styled image frame#
final_img = min_loss(fn=get_loss_grad_wrapper,epochs=30,batch_shape=batch_shape)
plt.imshow(scale(final_img))
plt.show()
cv2.imwrite(filename, final_img)
#Recombine styled image frames to form the video
video = cv2.VideoWriter(video_name, 0, 1, (width,height))
for image in images:
video.write(cv2.imread(os.path.join(image_folder, image)))
cv2.destroyAllWindows()
video.release()
也可以使用设备相机尝试使用视频,并尝试在线模式(实时视频)中的样式传输,只需调整VideoCapture模式即可。
cap = cv2.VideoCapture(0)
cap.release()
商业应用
除了个人和艺术使用这种看似奇特的技术外,神经风格迁移还有可能改变人类创造力传统上占主导地位的任何行业,例如美术,时尚,建筑或新潮流的汽车纹理设计等。例如,时尚产业需要深刻理解时尚机制:趋势的起因和传播,循环重复的原则和演变模式,以发展明天的时尚。
然而,神经网络或NST可以通过为不同类型的服装自动分配形状,元素和创意纹理(样式)来帮助设计新设计,并进一步将它们结合起来,为明天创造时尚的时尚。通过自动化,NST的重要部分有可能大大减少服装设计过程。
进一步改进和实验:
以下是一些提高生成图像质量的策略:
1)更多迭代:更明显的是,运行网络进行更多迭代(大约1000次)将减少整体损失并创建更清晰的混合图像。
2)先进的CNN架构:对于NST应用,通常具有非常先进的连接的更深入的神经网络可以更准确地捕获高水平(空间)和详细的纹理特征。所以值得尝试其他优秀的预训练网络,如InceptionV4,GoogLeNet,Resnet-101等。然而,这些网络的运行时间非常高,NST应用程序需要数千次迭代,并且需要昂贵的计算基础设施,如强大的GPU堆栈。
3)调整内容和样式损失权重:作为一个实验,分别尝试使用4和0.03作为内容和样式损失权重,主要是尽可能专注于捕获内容(因为我只运行几次迭代网络)。但是,这可能不合适,找到最佳权重的理想方法是通过网格搜索。
4)调整样式损失的图层权重:为了最大化样式特征捕获,需要在相应的转换层中调整权重以控制样式损失计算,以优化纹理的提取(早期层的更精细纹理和更深层的更高级别特征)。这些都是超参数,网格搜索是理想的选择。
此外,可以使用图层(L)来提取内容特征。L也是网络的超参数。