教程 | 用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方向)正在招募中,後臺回覆:讀者群,聯絡營長,新增營長請備註姓名,研究方向。
☟☟☟點選 | 閱讀原文 | 檢視大會詳情相關文章
- 養貓人士必備App——CatbookAPP
- 用CSS來擼一隻神經貓CSS
- 我的貓圖靈完備嗎? - belaycpp圖靈
- 疫情下,宅居廣州,程式設計與擼貓程式設計
- 玩家為什麼喜歡在遊戲中擼貓?遊戲
- 某AI圖片生成網AI
- 控制時間破解謎題,順手還可以擼貓的《Timelie》
- 路由器和貓怎麼連線?路由器和貓的正確連線方法教程路由器
- 一篇用AI生成圖片與內容的文章AI
- mongoose(貓鼬)Go
- 光纖貓下接路由器的設定教程路由器
- 手把手擼一個實用必備CrashHandler
- 如果程式設計師是貓,你是哪隻貓?程式設計師
- 如果程式設計師是貓 你是哪隻貓?程式設計師
- 貓樹詳解
- 用Node.js寫爬蟲,擼羞羞的圖片Node.js爬蟲
- 【教程】如何在區塊鏈上養貓賺錢!區塊鏈
- 高效能人士面試必備技能面試
- 光纖貓怎麼連線無線路由器?光纖貓連線無線路由器的方法教程路由器
- IT 專業人士的必備遠端工具——radmin
- 肥貓工作室
- 佳芯小姐與貓
- 用免費GPU部署自己的stable-diffusion專案(AI生成圖片)GPUAI
- 貓咪動態查詢框架“倉庫貓”升級到0.5版框架
- 家用電信光貓更換教程+設定路由模式路由模式
- 光貓和路由器的區別 裝了光貓還要路由器嗎路由器
- 貓讀《軟體估算》三
- 彩虹貓病毒[學習版]
- 我的原始碼讓貓給吃了原始碼
- 應用軟體暗藏貓膩,資訊保安咋保障
- 程式設計貓海龜編輯器2.0 附使用教程程式設計
- Mousy智慧逗貓器:還不快給喵大人奉上這個智慧逗貓器
- 英偉達新研究:“狗生貓,貓生萬物”的多模態無監督影像轉換
- 手把手教你生成一幅好看的AI圖片AI
- AI圖片AI
- 從 薛定諤的貓 聊到 Event loopOOP
- 機器學習是如何區分貓和狗的?機器學習
- 對彩虹貓樣本分析的復現