教程 | 用AI生成猫的图片,撸猫人士必备
2018 区块链技术及应用峰会(BTA)·中国
倒计时 3 天
2018,想要follow最火的区块链技术?你还差一场严谨纯粹的技术交流会——2018区块链技术及应用峰会(BTA)·中国将于2018年3月30-31日登陆北京喜来登长城饭店。追求专业性?你要的这里全都有:当超强嘉宾阵容遇上业界同好的脑洞大联欢,1+1=无限可能,目前门票预热火热进行中。
活动详情: http://dwz.cn/7FI1Ch
编译 | 小梁
出品 | AI科技大本营(公众号ID:rgznai100)
【AI科技大本营导读】我们身边总是不乏各种各样的撸猫人士,面对朋友圈一波又一波晒猫的浪潮,作为学生狗和工作狗的我们只有羡慕的份,更流传有“吸猫穷三代,撸猫毁一生?”的名言,今天营长就为广大爱猫人士发放一份福利,看看如何用AI来生成猫的图片?
用DCGAN生成的猫图片示例
领军研究员 Yann Lecun 称生成式对抗网络( Generative Adverserial Networks, GAN )是“过去20年里机器学习中最棒的想法”。因为这种网络结构的出现,我们才能在今天搭建一个可以生成栩栩如生的猫图片的 AI 系统。这是不是很令人振奋?
DCGAN的训练过程
完整代码(Github):
https://gist.github.com/simoninithomas/c7d1e80810ef838330d7dab068d6b26f#file-training-py
如果你使用过 Python、Tensorflow,学习过深度学习、CNNs(卷积神经网络),将对理解代码大有裨益。
▌什么是 DCGAN?
深度卷积生成对抗网络(Deep Convolutional Generative Adverserial Networks,DCGAN)是一种深度学习架构,它会生成和训练集中数据相似的结果。
这一模型用卷积层代替了生成对抗网络(GAN)模型中的全连接层。
为了解释 DCGAN 是如何运行的,我们用艺术专家和冒牌专家来做比喻。
冒牌专家( 即“生成器” )企图模仿梵高的画作生成图片并把它当做真实的梵高作品。
而另一边,艺术专家( 即“分类器” )试图利用它们对梵高画作的了解来识别出赝品( 即生成图片 )。
随着时间推移,艺术专家鉴别赝品的技术不断长进,冒牌专家仿作的能力也不断提高。
如我们所见,DCGANs 由两个互相对抗的深度神经网络组成。
生成器是一个仿造者,生成和真实数据相似的结果。它本身不知道真实数据是什么样,但会从另一个模型的反馈信息中学习和调整。
分类器是一个检测者,通过与真实数据比较来确定伪造数据(即模型生成的图片),但尽力不对真实数据报错。这一部分会为生成器的反向传播服务。
DCGAN工作流程示例
生成器会加入随机噪声向量,生成图片;
这张图片被输入给分类器,和训练集进行比较;
最后分类器返回一个 0(伪造图像)和 1(真实图像)之间的数字。
▌让我们来创建 DCGAN 吧!
现在,我们可以准备创建AI了。
在这部分,我们将关注模型的主要元素。若你想看所有代码,请点这里的 notebook(https://github.com/simoninithomas/CatDCGAN/blob/master/Cat%20DCGAN.ipynb)。
输入部分
先创建输入占位符:分类器:inputs_real,生成器:inputs_z。
注意,我们用两个学习率,一个是生成器的学习率,一个是分类器的学习率。
DCGANs 对超参数特别敏感,所以精确调参尤其重要。
def model_inputs(real_dim, z_dim): """ Create the model inputs :param real_dim: tuple containing width, height and channels :param z_dim: The dimension of Z :return: Tuple of (tensor of real input images, tensor of z data, learning rate G, learning rate D) """ # inputs_real for Discriminator inputs_real = tf.placeholder(tf.float32, (None, *real_dim), name='inputs_real') # inputs_z for Generator inputs_z = tf.placeholder(tf.float32, (None, z_dim), name="input_z") # Two different learning rate : one for the generator, one for the discriminator learning_rate_G = tf.placeholder(tf.float32, name="learning_rate_G") learning_rate_D = tf.placeholder(tf.float32, name="learning_rate_D") return inputs_real, inputs_z, learning_rate_G, learning_rate_D
分类器和生成器
我们用函数 tf.variable_scope 的原因有两个:
第一,我们想要保证所有变量名称都以 generator 或 discriminator 开头,这将为我们之后训练两个网络提供帮助。
第二,我们要用不同的输入重复训练网络:对于生成器,既要训练它,也要在训练后从生成图像中采样;对于分类器,我们需要在生成图像和真实图像间共用变量。
我们先来创建分类器。记住,要用真实或生成图像作为输入,然后输出分数。
需要注意的技术点:
关键点是在每个卷积层加倍过滤器的尺寸;
不建议进行下采样,我们只用一定步长的卷积层;
每层都使用 batch 标准化(输入层除外),因为它会减小协方差转变。想了解更多信息的话请看这篇文章(https://medium.com/@ageitgey/machine-learning-is-fun-80ea3ec3c471)。
我们用 Leaky ReLU 作为激活函数,因为它能帮助避免梯度消失问题。
def discriminator(x, is_reuse=False, alpha = 0.2): ''' Build the discriminator network. Arguments --------- x : Input tensor for the discriminator n_units: Number of units in hidden layer reuse : Reuse the variables with tf.variable_scope alpha : leak parameter for leaky ReLU Returns ------- out, logits: ''' with tf.variable_scope("discriminator", reuse = is_reuse): # Input layer 128*128*3 --> 64x64x64 # Conv --> BatchNorm --> LeakyReLU conv1 = tf.layers.conv2d(inputs = x, filters = 64, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv1') batch_norm1 = tf.layers.batch_normalization(conv1, training = True, epsilon = 1e-5, name = 'batch_norm1') conv1_out = tf.nn.leaky_relu(batch_norm1, alpha=alpha, name="conv1_out") # 64x64x64--> 32x32x128 # Conv --> BatchNorm --> LeakyReLU conv2 = tf.layers.conv2d(inputs = conv1_out, filters = 128, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv2') batch_norm2 = tf.layers.batch_normalization(conv2, training = True, epsilon = 1e-5, name = 'batch_norm2') conv2_out = tf.nn.leaky_relu(batch_norm2, alpha=alpha, name="conv2_out") # 32x32x128 --> 16x16x256 # Conv --> BatchNorm --> LeakyReLU conv3 = tf.layers.conv2d(inputs = conv2_out, filters = 256, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv3') batch_norm3 = tf.layers.batch_normalization(conv3, training = True, epsilon = 1e-5, name = 'batch_norm3') conv3_out = tf.nn.leaky_relu(batch_norm3, alpha=alpha, name="conv3_out") # 16x16x256 --> 16x16x512 # Conv --> BatchNorm --> LeakyReLU conv4 = tf.layers.conv2d(inputs = conv3_out, filters = 512, kernel_size = [5, 5], strides = [1, 1], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv4') batch_norm4 = tf.layers.batch_normalization(conv4, training = True, epsilon = 1e-5, name = 'batch_norm4') conv4_out = tf.nn.leaky_relu(batch_norm4, alpha=alpha, name="conv4_out") # 16x16x512 --> 8x8x1024 # Conv --> BatchNorm --> LeakyReLU conv5 = tf.layers.conv2d(inputs = conv4_out, filters = 1024, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv5') batch_norm5 = tf.layers.batch_normalization(conv5, training = True, epsilon = 1e-5, name = 'batch_norm5') conv5_out = tf.nn.leaky_relu(batch_norm5, alpha=alpha, name="conv5_out") # Flatten it flatten = tf.reshape(conv5_out, (-1, 8*8*1024)) # Logits logits = tf.layers.dense(inputs = flatten, units = 1, activation = None) out = tf.sigmoid(logits) return out, logits
再来创建生成器。记住,用随机噪声向量(z)作为输入,根据转置的卷积层输出生成图像。
其主要思想是在每层将过滤器尺寸减半,而将图片尺寸加倍。研究已经发现,用 tanh 作为输出层的激活函数时,生成器的表现最好。
def generator(z, output_channel_dim, is_train=True): ''' Build the generator network. Arguments --------- z : Input tensor for the generator output_channel_dim : Shape of the generator output n_units : Number of units in hidden layer reuse : Reuse the variables with tf.variable_scope alpha : leak parameter for leaky ReLU Returns ------- out: ''' with tf.variable_scope("generator", reuse= not is_train): # First FC layer --> 8x8x1024 fc1 = tf.layers.dense(z, 8*8*1024) # Reshape it fc1 = tf.reshape(fc1, (-1, 8, 8, 1024)) # Leaky ReLU fc1 = tf.nn.leaky_relu(fc1, alpha=alpha) # Transposed conv 1 --> BatchNorm --> LeakyReLU # 8x8x1024 --> 16x16x512 trans_conv1 = tf.layers.conv2d_transpose(inputs = fc1, filters = 512, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv1") # Transposed conv 1 --> BatchNorm --> LeakyReLU # 8x8x1024 --> 16x16x512 trans_conv1 = tf.layers.conv2d_transpose(inputs = fc1, filters = 512, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv1") batch_trans_conv1 = tf.layers.batch_normalization(inputs = trans_conv1, training=is_train, epsilon=1e-5, name="batch_trans_conv1") trans_conv1_out = tf.nn.leaky_relu(batch_trans_conv1, alpha=alpha, name="trans_conv1_out") # Transposed conv 2 --> BatchNorm --> LeakyReLU # 16x16x512 --> 32x32x256 trans_conv2 = tf.layers.conv2d_transpose(inputs = trans_conv1_out, filters = 256, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv2") batch_trans_conv2 = tf.layers.batch_normalization(inputs = trans_conv2, training=is_train, epsilon=1e-5, name="batch_trans_conv2") trans_conv2_out = tf.nn.leaky_relu(batch_trans_conv2, alpha=alpha, name="trans_conv2_out") # Transposed conv 3 --> BatchNorm --> LeakyReLU # 32x32x256 --> 64x64x128 trans_conv3 = tf.layers.conv2d_transpose(inputs = trans_conv2_out, filters = 128, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv3") batch_trans_conv3 = tf.layers.batch_normalization(inputs = trans_conv3, training=is_train, epsilon=1e-5, name="batch_trans_conv3") trans_conv3_out = tf.nn.leaky_relu(batch_trans_conv3, alpha=alpha, name="trans_conv3_out") # Transposed conv 4 --> BatchNorm --> LeakyReLU # 64x64x128 --> 128x128x64 trans_conv4 = tf.layers.conv2d_transpose(inputs = trans_conv3_out, filters = 64, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv4") batch_trans_conv4 = tf.layers.batch_normalization(inputs = trans_conv4, training=is_train, epsilon=1e-5, name="batch_trans_conv4") trans_conv4_out = tf.nn.leaky_relu(batch_trans_conv4, alpha=alpha, name="trans_conv4_out") # Transposed conv 5 --> tanh # 128x128x64 --> 128x128x3 logits = tf.layers.conv2d_transpose(inputs = trans_conv4_out, filters = 3, kernel_size = [5,5], strides = [1,1], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="logits") out = tf.tanh(logits, name="out") return out
▌分类器和生成器的损失
因为我们是同时训练分类器和生成器,因此,两个网络的损失都需要计算。
我们的目标是使分类器认为图片为真实图片时输出“ 1 ”,认为图片是生成图片时输出“ 0 ”。因此,我们需要设计能够反映这一特点的损失函数。
分类器的损失是真实和生成图片的损失之和:
d_loss = d_loss_real + d_loss_fake
d_loss_real 是分类器将真实图片错误地预测为生成图片时的损失。它的计算如下:
用 d_logits_real ,所有标签均为1(因为所有数据都是真实的);
labels = tf.ones_like(tensor) * (1 - smooth) ,使用标签平滑:也就是略微减小标签,例如从 1.0 变为 0.9 ,从而使分类器泛化地更好。
d_loss_fake 是分类器预测一张图片为真实图片、但实际是生成图片时的损失。
用 d_logits_fake ,所有标签都为0.
生成器的损失仍使用分类器中的 d_logits_fake ,但标签均为1,因为生成器要迷惑分类器。
def model_loss(input_real, input_z, output_channel_dim, alpha): """ Get the loss for the discriminator and generator :param input_real: Images from the real dataset :param input_z: Z input :param out_channel_dim: The number of channels in the output image :return: A tuple of (discriminator loss, generator loss) """ # Generator network here g_model = generator(input_z, output_channel_dim) # g_model is the generator output # Discriminator network here d_model_real, d_logits_real = discriminator(input_real, alpha=alpha) d_model_fake, d_logits_fake = discriminator(g_model,is_reuse=True, alpha=alpha) # Calculate losses d_loss_real = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, labels=tf.ones_like(d_model_real))) d_loss_fake = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.zeros_like(d_model_fake))) d_loss = d_loss_real + d_loss_fake g_loss = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.ones_like(d_model_fake))) return d_loss, g_loss
▌优化器
计算损失后,我们需要分别更新生成器和分类器。
要更新生成器和分类器,我们需要在每部分用 tf.trainable_variables() 获取变量,这样便创建了一个包含已在图中定义好的所有变量的列表。
def model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1): """ Get optimization operations :param d_loss: Discriminator loss Tensor :param g_loss: Generator loss Tensor :param learning_rate: Learning Rate Placeholder :param beta1: The exponential decay rate for the 1st moment in the optimizer :return: A tuple of (discriminator training operation, generator training operation) """ # Get the trainable_variables, split into G and D parts t_vars = tf.trainable_variables() g_vars = [var for var in t_vars if var.name.startswith("generator")] d_vars = [var for var in t_vars if var.name.startswith("discriminator")] update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) # Generator update gen_updates = [op for op in update_ops if op.name.startswith('generator')] # Optimizers with tf.control_dependencies(gen_updates): d_train_opt = tf.train.AdamOptimizer(learning_rate=lr_D, beta1=beta1).minimize(d_loss, var_list=d_vars) g_train_opt = tf.train.AdamOptimizer(learning_rate=lr_G, beta1=beta1).minimize(g_loss, var_list=g_vars) return d_train_opt, g_train_opt
▌训练
现在,我们来执行训练函数。
想法很简单:
每迭代5次保存一次模型;
每训练10个 batch 的图片就保存一张;
每迭代15次将 g_loss , d_loss 和生成图片可视化一次。这样做的原因很简单:显示太多图片的话,Jupyter Notebook 可能会出错。
或者,我们也可以直接通过加载保存的模型来查看图片(这样会节省20h的训练时间)。
def train(epoch_count, batch_size, z_dim, learning_rate_D, learning_rate_G, beta1, get_batches, data_shape, data_image_mode, alpha): """ Train the GAN :param epoch_count: Number of epochs :param batch_size: Batch Size :param z_dim: Z dimension :param learning_rate: Learning Rate :param beta1: The exponential decay rate for the 1st moment in the optimizer :param get_batches: Function to get batches :param data_shape: Shape of the data :param data_image_mode: The image mode to use for images ("RGB" or "L") """ # Create our input placeholders input_images, input_z, lr_G, lr_D = model_inputs(data_shape[1:], z_dim) # Losses d_loss, g_loss = model_loss(input_images, input_z, data_shape[3], alpha) # Optimizers d_opt, g_opt = model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1) i = 0 version = "firstTrain" with tf.Session() as sess: sess.run(tf.global_variables_initializer()) # Saver saver = tf.train.Saver() num_epoch = 0 if from_checkpoint == True: saver.restore(sess, "./models/model.ckpt") show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) else: for epoch_i in range(epoch_count): num_epoch += 1 if num_epoch % 5 == 0: # Save model every 5 epochs #if not os.path.exists("models/" + version): # os.makedirs("models/" + version) save_path = saver.save(sess, "./models/model.ckpt") print("Model saved") for batch_images in get_batches(batch_size): # Random noise batch_z = np.random.uniform(-1, 1, size=(batch_size, z_dim)) i += 1 # Run optimizers _ = sess.run(d_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_D: learning_rate_D}) _ = sess.run(g_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_G: learning_rate_G}) if i % 10 == 0: train_loss_d = d_loss.eval({input_z: batch_z, input_images: batch_images}) train_loss_g = g_loss.eval({input_z: batch_z}) # Save it image_name = str(i) + ".jpg" image_path = "./images/" + image_name show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) return losses, samples
▌怎样运行模型
你不能在自己的笔记本上运行这个模型——除非你有自己的 GPU,或者准备好等个十来年。
因此,你最好用在线 GPU 服务,如 AWS 或者 FloydHub 。我个人训练这个 DCGAN 模型花了 20 个小时,用的是 Microsoft Azure 和他们的深度学习虚拟机。
Deep Learning Virtual Machine:
https://medium.com/@ageitgey/machine-learning-is-fun-80ea3ec3c471
作者 | Thomas Simonini
原文链接
https://medium.freecodecamp.org/how-ai-can-learn-to-generate-pictures-of-cats-ba692cb6eae4
2018年3月30-31日,第二届中国区块链技术暨应用大会将于北京喜来登长城饭店盛大开场,50+区块链技术领导人物,100+区块链投资商业大咖,100+技术&财经媒体,1000+区块链技术爱好者,强强联合,共同探讨最in区块链技术,豪华干货礼包享不停。八折门票火热抢购中!2018,未来已来,带你玩转区块链。
AI科技大本营用户群(计算机视觉、机器学习、深度学习、NLP、Python、AI硬件、AI+金融、AI+PM方向)正在招募中,后台回复:读者群,联系营长,添加营长请备注姓名,研究方向。
教程 | 用AI生成猫的图片,撸猫人士必备相关推荐
- ai生成图片是什么技术_人工智能如何学习生成猫的图片
ai生成图片是什么技术 by Thomas Simonini 通过托马斯·西蒙尼(Thomas Simonini) 人工智能如何学习生成猫的图片 (How AI can learn to genera ...
- stable-diffusion-webui教程(AI绘画真人教程)
stable-diffusion-webui教程(AI绘画真人教程) 首先给大家看看效果,这个就是新一代的AI绘画的造物,是不是很漂亮,AI自定义老婆从此不再是梦了. 下面就给大家演示一下,这个软件如 ...
- 【实操演练】平民玩家借力AI生成美少女图片
前言 AI的风已经吹了有一阵子了.看着各种头条的AI图片,各种群里的惊艳美女,是不是和我一样也想自己去实操一番呢?说起AI生图领域,目前较火的应该是Midjourney和Stable Diffusio ...
- GMIC 2018大会AI 生万物 嘉宾分享摘要
4月26-28日举办的GMIC全球移动互联网大会, 主旨是AI 生万物.大会有来自全球各大公司的重量级人工智能方向的嘉宾,比如李开复.Yann LeCun.Michael Jordan等等,针对AI的 ...
- jquery 图片裁剪 java_[Java教程]5 款最新的 jQuery 图片裁剪插件
[Java教程]5 款最新的 jQuery 图片裁剪插件 0 2015-05-18 16:00:20 这篇文章主要介绍最新的 5 款 jQuery 图片裁剪插件,可以帮助你轻松的实现你网站需要的图像裁 ...
- python爬取图片教程-推荐|Python 爬虫系列教程一爬取批量百度图片
Python 爬虫系列教程一爬取批量百度图片https://blog.csdn.net/qq_40774175/article/details/81273198# -*- coding: utf-8 ...
- 【Android 安装包优化】Android 中使用 SVG 图片 ( Android 5.0 以下的矢量图方案 | 矢量图生成为 PNG 图片 )
文章目录 一.Android 5.0 以下的矢量图方案 二.矢量图生成为 PNG 图片 三.完整的 build.gradle 构建脚本 四.编译效果 五.参考资料 一.Android 5.0 以下的矢 ...
- android 编辑9图片,Android基础入门教程——1.6 .9(九妹)图片怎么玩
Android基础入门教程--1.6 .9(九妹)图片怎么玩 Android基础入门教程 1.本节引言: 可能有的一些疑问: 1.什么是.9图片? 答:图片后缀名前有.9的图片,如pic1.9.png ...
- Python3.x爬虫教程:爬网页、爬图片、自己主动登录
林炳文Evankaka原创作品. 转载请注明出处http://blog.csdn.net/evankaka 摘要:本文将使用Python3.4爬网页.爬图片.自己主动登录.并对HTTP协议做了一个简单 ...
最新文章
- 不想被AI降维打击?美国“四院院士”写的DL科普书了解一下
- Linux nc命令
- # mergeSort 归并排序
- Android 调用12306接口,GitHub - AndroidyxChen/loading-12306: 仿PC端12306的刷新loading的自定义view...
- 【CodeChef - CLIQUED 】Bear and Clique Distances(建图,缩点技巧,思维)
- 信号signal()、alarm()、信号集函数、sigprocmask()
- Go 语言发布 2018 调查报告,最被诟病问题竟然是......
- (译)iOS Code Signing: 解惑
- 使用工具类DbUtils连接数据库,并简单操作数据库
- Android 高通 Wi-Fi 驱动
- 计算机组成原理完整学习笔记(八):控制器设计
- Qt之QTimeEdit时间控件
- 三维图看法亲身经验.
- 儿童拼图游戏软件测试,宝宝益智英语字母拼图大全(测试版)
- 偷盗者问题。甲乙丙丁四个嫌疑犯,只有一个是偷盗者。在审讯中,四人都有可能说真话或假话
- 乐高教育版45544零件---分类识别
- LeetCode刷题之1436. 旅行终点站
- python求二项式系数的几种方法及性能对比
- 程序设计天梯赛2021年选拔 L2-4红豆 (什么完全树给定后序遍历)
- 关于 Python PyQt5 界面运行时提示无法初始化Qt平台的解决方案