电子说
编者按:Google Brain机器学习开发者hardmu使用TensorFlow,基于CPPN网络生成了许多有趣的高分辨率抽象艺术图片。一起来看看他是怎么做的吧。
钻石恒久远
本文尝试使用TensorFlow探索复合模式生成网络(Compositional pattern-producing networks)。相关代码放在github上。乍看起来,用TensorFlow实现CPPN是高射炮打蚊子,因为用numpy就可以实现CPPN。不过,用TensorFlow方便我们以后进一步扩展。
介绍
近来,基于神经网络的图像生成技术通常使用尝试一次性绘制整个图像的生成网络。例如,如果输出图像的目标分辨率是256x256,那么神经网络的最后一层会有65536个值(黑白图像)。由于内存和这些算法的可扩展性方面的限制,将输出分辨率提高到现代图像的分辨率(2880x1800)可能是不可行的。
在这篇文章中,我将描述一个非常简单的生成高分辨率的图像的方法。神经网络并不一次性生成所有像素,而是根据像素的位置生成单个像素的亮度或颜色。然后,为目标输出图像中的每个像素查询一次网络,从而生成整个图像。这种方法可以用来生成高分辨率的图像,图像的分辨率大小将仅仅受限于内存,同时提供了一个缩放图像的优雅方式,并且图像会具有一些分形特性。
网上已经有一些demo试验了这一技术,包括karpathy基于convnetjs的绘图demo,以及生成式神经网络艺术和Neurogram的javascript实现。
在这篇文章中,我将介绍如何基于TensorFlow实现这个简单的技术,以生成随机的抽象艺术。我想使用TensorFlow来实现这个功能,是因为它可以作为基础,在未来利用TensorFlow的机器学习能力做些更有趣的事情。这种类型的生成网络还可以用来创建非随机图片,我将在以后的帖子中讨论这一点。
复合模式生成网络
我们定制的CPPN的语义
我在以前的一个帖子中介绍过CPPN的基础情况,其中使用CPPN结合NEAT算法来演化生成式神经网络。
本质上,CPPN只是一个函数,c=f(x, y),该函数定义了空间中每个点的图像亮度。这使得CPPN很适合生成高分辨率的图像,因为,给定像素的位置,你可以直接调用这个函数来获取每个像素的颜色或亮度。
f(x, y)这个函数可以通过很多数学运算构建,也可以通过神经网络来表示,该神经网络包含一个连接激活门的权重集合,其中的权重在绘制图像时保持不变。因此整个图像可以被定义为f(w, x, y)。
我们的TensorFlow实现将与之前基于CPPN-NEAT的工作略有不同。像之前的工作一样,f(x, y)将返回一个0到1之间的实数,以定义该点处的图像亮度(结果将是灰度图像)或一个三维向量,向量的每个值在(0, 1)之内,以表示彩色亮度(红、绿、蓝)。此外,与CPPN-NEAT类似,为了使图像更有趣,我们还将每个点到原点的距离作为输入(r = sqrt(x^2+y^2)),因此CPPN函数为f(w, x, y, r)。神经网络的权重w将被初始化为单位高斯分布的随机值。
与CPPN-NEAT不同,我们要在这个实现中使用的神经网络将仅仅是一个由用户定义的多层前馈网络。例如,用户可以修改TensorFlow代码,使f(w, x, y, r)成为由双曲线正切、relu、softplus、正弦曲线等定义的前馈神经网络。每一层还将有一个额外的偏置输入,出于简明的考虑,图中省略了这个偏置输入。
我们还将在CPPN函数中添加一个称为潜向量的额外输入z,这是一个包含n个实数的向量(n通常远小于网络中加权连接的总数),所以我们的生成网络定义为f(w, z, x, y, r)。修改z的值会导致我们的网络产生不同的图像,而通过改变z的值我们的网络可能生成的整个图像空间被称为潜空间。从某种意义上说,z可以被解释为基于n个实数对最终图像的压缩描述。由于网络是一个连续的函数,如果我们稍微调整z的值,那么输出图像也只会稍微变化,所以我们也可以通过使用一个从z1缓慢地移动到z2的潜向量z生成一系列图像,从而可视化潜空间中的一张图像如何缓慢地渐变为同一潜空间中的另一张图像。
注意,示意图中的空白层只是在进入第一个隐藏层之前,应用缩放因子至x、y、r和z的一个预处理层。
我们本可以逐渐调整权重来获得不同的输出图像,之所以需要一个额外的潜向量输入,是因为,一个复杂的生成式网络可能有成百上千的权重,并且在许多生成应用中,我们希望将潜向量的数量控制在一个很小的值。当我们最终在大型数据集上进行训练时,潜向量不仅可以控制正在绘制的对象,还可以控制图像的特定风格。使用概率论的一些工具,我们甚至可以强制z具有良好的性质,例如独立和单位高斯。例如,z的一个子集可以表示个人性别,另一个子集可以表示由生成算法绘制的人脸的情绪。在后续的帖子中,我们会试图训练这类CPPN来书写数字,也可能会绘制脸部、动物、汽车、厕所,等等。
探索神经网络的潜空间
生成.png和.gif图像的CPPN模型和代码可以在github上找到。我将使用代码作为参考,以通过IPython会话生成图像。
在代码仓库中,model.py包含使用TensorFlow库常规生成图像的CPPN类。如果你打算尝试修改CPPN的神经网络架构,可以查看一下generator方法。其中有一些替代架构被注释掉了,这些架构可供尝试。我们将使用sampler.py文件中的Sampler类,这允许我们互动请求IPython会话内的图像。
在IPython会话内,我们首先进入仓库目录并运行:
%run -i sampler.py
sampler = Sampler(z_dim = 8, scale = 10.0, net_size = 32)
Sampler将生成一个CPPN模型,该模型使用包含8个实数的潜向量,10倍放大,在每个神经网络层包含32个激活函数。注意这些是默认值,因此我们下次也可以直接使用sampler = Sampler()。
为了生成图像,我们需要生成一个包含8个实数的随机潜向量z:
z1 = sampler.generate_z()
之后我们可以将z传递给生成函数以查看输出图像。img_data是一个包含图像数据的numpy数列。
img_data = sampler.generate(z1)
sampler.show_image(img_data)
输出
调用reinit()方法可以重置和随机化神经网络的权重:
sampler.reinit()
sampler.show_image(sampler.generate(z1))
输出
注意,随着网络权重的改变,潜空间改变了,因此尽管我们传入了相同的z向量,我们会看到一个不同的输出。
我们可以通过sampler.save_png(img_data, 'output.png')保存文件。
穿越潜空间
在同一潜空间中生成另一张随机图像:
z2 = sampler.generate_z()
sampler.show_image(sampler.generate(z2))
输出
上面的两种图像均属于同一潜空间,同时能以矢量格式紧凑地表示。如果我们从z1一点点地移动到z2,然后在每步生成一个图像,我们可以看到由z1定义的图像是如何渐变为由z2定义的图像。我创建了一个如上所述生成.gif图像的方法。该方法有一些可供调整的设置,用于调节时间和每帧的大小。默认值将生成约5M大的.gif文件。
sampler.save_anim_gif(z1, z2, 'output.gif')
输出
着色
重置IPython会话,将c_dim设置为3后可以生成彩色图像。
%run -i sampler.py
sampler = Sampler(z_dim = 8, c_dim = 3, scale = 10.0, net_size = 32)
我们可以让CPPN在每个像素的位置输出3个值,为每个像素定义RGB值。然而,我个人觉得,对于使用随机权重的生成式网络而言,黑白版本更为优雅。
修改网络架构
我们也可以修改model.py内的generate方法来修改CPPN网络的架构。
默认架构是若干包含双曲正切函数的神经网络层。
H = tf.nn.tanh(U)
for i in range(3):
H = tf.nn.tanh(fully_connected(H, net_size, 'g_tanh_'+str(i)))
output = tf.sigmoid(fully_connected(H, self.c_dim, 'g_final'))
例子
我们也可以在tanh层中混入softplus层:
H = tf.nn.tanh(U)
H = tf.nn.softplus(fully_connected(H, net_size, 'g_softplus_1'))
H = tf.nn.tanh(fully_connected(H, net_size, 'g_tanh_2'))
H = tf.nn.softplus(fully_connected(H, net_size, 'g_softplus_2'))
output = tf.sigmoid(fully_connected(H, self.c_dim, 'g_final'))
例子
我们甚至可以加入正弦激活函数:
H = tf.nn.tanh(U)
H = tf.nn.softplus(fully_connected(H, net_size, 'g_softplus_1'))
H = tf.nn.tanh(fully_connected(H, net_size, 'g_tanh_2'))
H = tf.nn.softplus(fully_connected(H, net_size, 'g_softplus_2'))
output = 0.5 * tf.sin(fully_connected(H, self.c_dim, 'g_final')) + 0.5
例子
我也尝试了绝对值函数、平方根、自己实现的高斯激活函数,和残差结构。
CPPN相当泛化,并具备同时生成大批量图片的能力。未来我们想让CPPN为我们生成一些有趣的东西时,批量处理对基于GPU的神经网络训练非常有用。
结论
TensorFlow库提供了执行向量运算的高效方法,可用于不需要梯度运算的任务。但是这意味着未来可以利用深度学习功能扩展各种各样的工作。像本文中的例子一样,我们以后可以使用这些代码,并尝试训练我们的CPPN做一些有趣的事情,比如,绘制某类图像、字体或不同风格的数字,这些都在高分辨率的条件下完成。
全部0条评论
快来发表一下你的评论吧 !