在上個(gè)數(shù)字識(shí)別的例子中,我們使用了一個(gè)簡單的3層神經(jīng)網(wǎng)絡(luò)來識(shí)別給定圖片的中的數(shù)字。
這次我們在上次的例子中在提升一下,這次我們選用條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)來生成數(shù)字圖片。
下面就讓我們開始吧!
第一步:import 我們需要的數(shù)據(jù)庫
%matplotlib inline
from __future__ import print_function, division
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import matplotlib.cm as cm
import seaborn as sns
sns.set_style('white')
from keras.layers import Input, Dense, Reshape, Flatten, Dropout, multiply
from keras.layers import BatchNormalization, Activation, Embedding, ZeroPadding2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D
from keras.models import Sequential, Model
from keras.optimizers import Adam, SGD
第二步:數(shù)據(jù)預(yù)處理
在上個(gè)例子中,我們使用的是28*28的二值圖像,也就是說像素只有0和1,0表示黑色,1表示白色。
在上個(gè)例子中,我們使用28*28的灰度圖像,每個(gè)像素的值都是從0~255的數(shù)值,值越大,越接近白色。
2.1 數(shù)據(jù)加載函數(shù)
首先定義一個(gè)數(shù)據(jù)加載函數(shù) load_data 用來加載數(shù)據(jù)。
不同于上一個(gè)例子,我們的數(shù)據(jù)存放在 npz 文件中,numpy 提供了 load 接口可以直接讀取。
通過函數(shù)的輸出我們就可以看到,npz文件里的內(nèi)容是 x_traln , y_traln , x_test , y_ test 。
這幾個(gè)內(nèi)容標(biāo)簽分別對(duì)應(yīng)訓(xùn)練圖片數(shù)據(jù),訓(xùn)練圖片數(shù)據(jù)的 label,測試圖片數(shù)據(jù),測試圖片數(shù)據(jù)的 label。
def load_data():
data = np.load('mnist.npz')
print(data.files)
x_train = data['x_train']
y_train = data['y_train']
x_test = data['x_test']
y_test = data['y_test']
x_train = (x_train.astype(np.float32) - 127.5) / 127.5
x_train = np.expand_dims(x_train, axis=3)
y_train = y_train.reshape(-1, 1)
return (x_train, y_train), (x_test, y_test)
(x_train, y_train), (x_test, y_test)=load_data()
2.2 數(shù)據(jù)查看
在任何模型建立之前,常規(guī)的操作是先查看數(shù)據(jù)的情況,比如數(shù)據(jù)集的大小,訓(xùn)練集和測試集的數(shù)據(jù)數(shù)量,標(biāo)簽的數(shù)據(jù)數(shù)量分布等等。
2.2.1 查看原始數(shù)據(jù)的緯度
訓(xùn)練集有60000條數(shù)據(jù),測試集有100000條數(shù)據(jù),并且每一條數(shù)據(jù)有28*28的圖片像素?cái)?shù)據(jù)。
print(x_train.shape)
print(x_test.shape)
2.2.2 查看標(biāo)簽的數(shù)量
通過查看訓(xùn)練標(biāo)簽跟測試標(biāo)簽的數(shù)量,我們可以觀察到,訓(xùn)練和測試的數(shù)據(jù)集跟訓(xùn)練和測試的標(biāo)簽在數(shù)量上是一一對(duì)應(yīng)的。這也是我么想要的結(jié)果,表示我們的數(shù)據(jù)集是完整的。
print(y_train.shape)
print(y_test.shape)
2.2.3 查看所有的標(biāo)簽種類
可以看出標(biāo)簽表示了從0-9的數(shù)字,沒有其他的錯(cuò)誤數(shù)據(jù)。
np.unique(y_train)
np.unique(y_test)
2.3數(shù)據(jù)可視化
接下來我隨機(jī)的選取一些我們已經(jīng)轉(zhuǎn)換好的圖片數(shù)據(jù),用 matplot 來查看下,標(biāo)簽和圖片是否一致。
plt.figure(figsize=(15, 9))
for i in range(50):
random_selection = np.random.randint(0, 500)
plt.subplot(5, 10, 1+i)
plt.title(y_train[random_selection])
plt.imshow(x_train[random_selection][:,:,0], cmap=cm.gray)
2.4 查看數(shù)據(jù)是否平衡
分類的設(shè)計(jì)都是基于類分布大致平衡這一假設(shè),通常假定用于訓(xùn)練的數(shù)據(jù)集是平衡的,即各類所含的樣本數(shù)大致相當(dāng)。
均勻的數(shù)據(jù)分布,將會(huì)提高模型的精度。如果數(shù)據(jù)不均勻,我么就要考慮進(jìn)行平衡處理,常用的處理方式包括采樣、加權(quán)、數(shù)據(jù)合成等。
我們看下標(biāo)簽的分布情況,看下每個(gè)標(biāo)簽種類的數(shù)據(jù)量是否分布均勻。
在 MNIST 數(shù)據(jù)集中,我們的數(shù)據(jù)是比較均勻分布的。
sns.distplot(y_train, kde=False, bins=10)
第三步:構(gòu)建模型
3.1接下來讓我們定義模型:
我們選用的是條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)
首先先讓我們來認(rèn)識(shí)下基本的生成對(duì)抗模型(Generative Adversarial Networks)的架構(gòu)
我們的輸入數(shù)據(jù)分成兩個(gè),一個(gè)是真實(shí)的圖片,一個(gè)是噪聲圖片。
首先,噪聲圖片輸入到生成模型中,通過生成模型輸出一張假的圖片,然后我們同時(shí)將得到的假的圖片跟真實(shí)的圖片輸入到判別模型中,通過判別模型,我們輸出一個(gè)預(yù)測的標(biāo)簽。
這個(gè)是最基本的GAN的模型流程。
3.2 條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)
從基本的生成對(duì)抗模型(Generative Adversarial Networks)模型中我們看到,輸入的只是一張隨機(jī)的噪聲圖片,并沒有指定這個(gè)噪聲圖片對(duì)應(yīng)的標(biāo)簽的任何信息。
那么在我們的這個(gè)例子里,我們希望輸入的噪聲圖片,是指定的一個(gè)數(shù)字的標(biāo)簽,并且在通過GAN模型以后,能夠輸出對(duì)于我們輸入標(biāo)簽的數(shù)字圖片。
因此我們需要在他的基礎(chǔ)上做些修改,這個(gè)模型就是我們這次使用的模型,叫做條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)。
模型示意圖
可以看到我們做了如下修改:
我們在生成網(wǎng)絡(luò)的輸入數(shù)據(jù)中加入了我們的隨機(jī)噪聲圖片所對(duì)應(yīng)的標(biāo)簽,
我們在判別網(wǎng)絡(luò)中加入了,真實(shí)圖片所對(duì)應(yīng)的標(biāo)簽。
3.3 定義網(wǎng)絡(luò)
下面讓我們來定義我們需要的模型
3.3.1先定義一些常量
# 輸入圖片數(shù)據(jù)的維度
img_shape = (28, 28, 1)
# 圖片通道數(shù)
channels = 1
# 標(biāo)簽數(shù)目
num_classes = 10
# 噪聲圖片的輸入維度
latent_dim = 100
3.3.2 定義優(yōu)化器
這里我們使用的優(yōu)化器是Adam(Adaptive Moment Estimation)
Adam 是一種可以替代傳統(tǒng)隨機(jī)梯度下降(SGD)過程的一階優(yōu)化算法,它能基于訓(xùn)練數(shù)據(jù)迭代地更新神經(jīng)網(wǎng)絡(luò)權(quán)重。
Adam最開始是由OpenAI的Diederik Kingma和多倫多大學(xué)的Jimmy Ba在提交到2015年ICLR論文(Adam: A Method for Stochastic Optimization)中提出的。
Adam優(yōu)化器有以下特點(diǎn):
1.實(shí)現(xiàn)簡單,計(jì)算高效,對(duì)內(nèi)存需求少
2.參數(shù)的更新不受梯度的伸縮變換影響
3.超參數(shù)具有很好的解釋性,且通常無需調(diào)整或僅需很少的微調(diào)
4.更新的步長能夠被限制在大致的范圍內(nèi)(初始學(xué)習(xí)率)
5.能自然地實(shí)現(xiàn)步長退火過程(自動(dòng)調(diào)整學(xué)習(xí)率)
6.很適合應(yīng)用于大規(guī)模的數(shù)據(jù)及參數(shù)的場景
7.適用于不穩(wěn)定目標(biāo)函數(shù)
8.適用于梯度稀疏或梯度存在很大噪聲的問題
optimizer = Adam(0.0002, 0.5)
3.3.3 定義生成模型
def build_generator():
model = Sequential()
model.add(Dense(256, input_dim=latent_dim))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization(momentum=0.8))
model.add(Dense(512))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization(momentum=0.8))
model.add(Dense(1024))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization(momentum=0.8))
model.add(Dense(np.prod(img_shape), activation='tanh'))
model.add(Reshape(img_shape))
model.summary()
noise = Input(shape=(latent_dim,))
label = Input(shape=(1,), dtype='int32')
label_embedding = Flatten()(Embedding(num_classes, latent_dim)(label))
model_input = multiply([noise, label_embedding])
img = model(model_input)
return Model([noise, label], img)
3.3.4 定義判別模型
def build_discriminator():
model = Sequential()
model.add(Dense(512, input_dim=np.prod(img_shape)))
model.add(LeakyReLU(alpha=0.2))
model.add(Dense(512))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.4))
model.add(Dense(512))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.4))
model.add(Dense(1, activation='sigmoid'))
model.summary()
img = Input(shape=img_shape)
label = Input(shape=(1,), dtype='int32')
label_embedding = Flatten()(Embedding(num_classes, np.prod(img_shape))(label))
flat_img = Flatten()(img)
model_input = multiply([flat_img, label_embedding])
validity = model(model_input)
return Model([img, label], validity)
在上面的生成模型跟判別模型中,我們使用了幾個(gè)新的網(wǎng)絡(luò), LeakyReLU, Dropout, BatchNormalization。
下面我們對(duì)這些層次進(jìn)行一些簡單的說明跟介紹。
3.4 帶泄露修正線性單元(Leaky ReLU)
在上一個(gè)數(shù)字識(shí)別的例子中, 我們使用了線性整流函數(shù)(Rectified Linear Unit)就是我們常說的 ReLU 來作為激活函數(shù)。
我們也同時(shí)介紹了它的優(yōu)缺點(diǎn),其中一個(gè)重要的缺點(diǎn)就是前向傳播過程中,在x<0時(shí),神經(jīng)元保持非激活狀態(tài)。
這樣會(huì)導(dǎo)致權(quán)重?zé)o法得到更新,也就是網(wǎng)絡(luò)無法學(xué)習(xí),為了解決 Relu 函數(shù)這個(gè)缺點(diǎn),在 Relu 函數(shù)的負(fù)半?yún)^(qū)間引入一個(gè)泄露(Leaky)值, 使得ReLU在這個(gè)區(qū)間不為零。因此 Leaky ReLU 的圖像如下, 通過參數(shù)a來控制函數(shù)負(fù)半?yún)^(qū)的值。
3.5 Dropout
在機(jī)器學(xué)習(xí)的模型中,如果模型的參數(shù)太多,而訓(xùn)練樣本又太少,訓(xùn)練出來的模型很容易產(chǎn)生過擬合的現(xiàn)象, 具體表現(xiàn)在模型在訓(xùn)練數(shù)據(jù)上損失函數(shù)較小,預(yù)測準(zhǔn)確率較高。
但是在測試數(shù)據(jù)上損失函數(shù)比較大,預(yù)測準(zhǔn)確率較低。為了解決過擬合問題,Hinton在其論文《Improving neural networks by preventing co-adaptation of feature detectors》中提出了 Dropout 。
Dropout 的工作原理是我們在前向傳播的時(shí)候,讓某個(gè)神經(jīng)元的激活值以一定的概率停止工作,這樣可以使模型泛化性更強(qiáng),因?yàn)樗粫?huì)太依賴某些局部的特征。
它的工作的可視化表示如下圖所示:
Dropout 可以有效的防止模型過擬合。
3.6 Batch Normallzatlon
機(jī)器學(xué)習(xí)領(lǐng)域有個(gè)很重要的假設(shè):IID獨(dú)立同分布假設(shè),就是假設(shè)訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)是滿足相同分布的,這是通過訓(xùn)練數(shù)據(jù)獲得的模型能夠在測試集獲得好的效果的一個(gè)基本保障。
Batch Normalization就是在深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程中使得每一層神經(jīng)網(wǎng)絡(luò)的輸入保持相同分布。
基本思想其實(shí)相當(dāng)直觀:因?yàn)樯顚由窠?jīng)網(wǎng)絡(luò)在做非線性變換前的激活輸入值隨著網(wǎng)絡(luò)深度加深或者在訓(xùn)練過程中,其分布逐漸發(fā)生偏移或者變動(dòng),之所以訓(xùn)練收斂慢,一般是整體分布逐漸往非線性函數(shù)的取值區(qū)間的上下限兩端靠近(參考Sigmoid函數(shù)),所以這導(dǎo)致反向傳播時(shí)低層神經(jīng)網(wǎng)絡(luò)的梯度消失,這是訓(xùn)練深層神經(jīng)網(wǎng)絡(luò)收斂越來越慢的本質(zhì)原因。
而 Batch Normalization 就是通過一定的規(guī)范化手段,把每層神經(jīng)網(wǎng)絡(luò)任意神經(jīng)元這個(gè)輸入值的分布強(qiáng)行拉回到均值為0方差為1的標(biāo)準(zhǔn)正態(tài)分布,其實(shí)就是把越來越偏的分布強(qiáng)制拉回比較標(biāo)準(zhǔn)的分布。
這樣使得激活輸入值落在非線性函數(shù)對(duì)輸入比較敏感的區(qū)域,這樣輸入的小變化就會(huì)導(dǎo)致?lián)p失函數(shù)較大的變化,意思是這樣讓梯度變大,避免梯度消失問題產(chǎn)生,而且梯度變大意味著學(xué)習(xí)收斂速度快,能大大加快訓(xùn)練速度
Batch Normalization有如下幾個(gè)有特點(diǎn):
1.使得網(wǎng)絡(luò)中每層輸入數(shù)據(jù)的分布相對(duì)穩(wěn)定,加速模型學(xué)習(xí)速度
2.使得模型對(duì)網(wǎng)絡(luò)中的參數(shù)不那么敏感,簡化調(diào)參過程,使得網(wǎng)絡(luò)學(xué)習(xí)更加穩(wěn)定
3.允許網(wǎng)絡(luò)使用飽和性激活函數(shù)(例如sigmoid,tanh等),緩解梯度消失問題
4.具有一定的正則化效果
3.7 定義中間結(jié)果顯示函數(shù)
這個(gè)函數(shù)主要用于在訓(xùn)練的時(shí)候,顯示當(dāng)前模型的預(yù)測情況,其中epoch這個(gè)參數(shù)表示當(dāng)前第幾個(gè) epoch,generator 表示生成函數(shù)模型。
我們將查看當(dāng)前 epoch 時(shí),生成模型對(duì)于0-9這個(gè)幾個(gè)數(shù)字的生成情況。
函數(shù)中,我們使用 numpy 產(chǎn)生一個(gè)0-9的標(biāo)簽數(shù)組,并且對(duì)每個(gè)0-9的數(shù)字產(chǎn)生一個(gè)噪聲圖片的數(shù)組,然后我們將噪聲圖片,以及對(duì)應(yīng)的標(biāo)簽交給生成模型預(yù)測。將產(chǎn)生的結(jié)果,用 matplot 分兩行繪制在一張圖片內(nèi)。
其中上面是0,1,2,3,4,下面是5,6,7,8,9, 并且將這張圖片保存在images文件夾中, 文件名為當(dāng)前 epoch,然后我們用 matplot 將這個(gè)圖像顯示jupyter上,方便查看。
def sample_images(epoch, generator):
print("第%d個(gè)epoch的預(yù)測結(jié)果" % epoch)
# 2行,5列
r, c = 2, 5
# 噪聲圖片
noise = np.random.normal(0, 1, (r * c, 100))
# 噪聲圖片對(duì)應(yīng)的標(biāo)簽
sampled_labels = np.arange(0, 10).reshape(-1, 1)
# 用生成模型預(yù)測結(jié)果
gen_imgs = generator.predict([noise, sampled_labels])
gen_imgs = 0.5 * gen_imgs + 0.5
# 將結(jié)果繪制在一張圖片上并保存
fig, axs = plt.subplots(r, c)
cnt = 0
for i in range(r):
for j in range(c):
# 我們繪制的時(shí)灰度圖片
axs[i,j].imshow(gen_imgs[cnt,:,:,0], cmap='gray')
# 將結(jié)果圖片的標(biāo)簽頁繪制在結(jié)果圖片的上方
axs[i,j].set_title("%d" % sampled_labels[cnt])
# 關(guān)閉坐標(biāo)軸
axs[i,j].axis('off')
cnt += 1
#保存圖片
fig.savefig("images/epoch_%d.png" % epoch)
plt.close()
# 讀取剛才的圖片,并顯示在jupyter上
img = mpimg.imread('images/epoch_%d.png' %epoch)
plt.imshow(img)
plt.axis('off')
plt.show()
3.8 定義條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)
class ConditionalGAN():
def __init__(self):
# loss值記錄列表,用于最后顯示Loss值的趨勢,查看訓(xùn)練效果
self.g_loss = []
# epoch的記錄列表
self.epoch_range = []
# ---------------------
# 判別模型部分
# ---------------------
self.discriminator = build_discriminator()
self.discriminator.compile(loss=['binary_crossentropy'],
optimizer=optimizer,
metrics=['accuracy'])
# ---------------------
# 生成模型部分
# ---------------------
self.generator = build_generator()
# ---------------------
# 合并模型部分
# ---------------------
noise = Input(shape=(latent_dim,))
label = Input(shape=(1,))
img = self.generator([noise, label])
# 在合并模型中,我們只訓(xùn)練生成模型
self.discriminator.trainable = False
valid = self.discriminator([img, label])
# 結(jié)合判別模型跟生成模型
self.combined = Model([noise, label], valid)
self.combined.compile(loss=['binary_crossentropy'],
optimizer=optimizer)
# 訓(xùn)練函數(shù)
def train(self, epochs, batch_size=128, sample_interval=50):
valid = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))
for epoch in range(epochs+1):
# ---------------------
# 訓(xùn)練判別模型部分
# ---------------------
idx = np.random.randint(0, x_train.shape[0], batch_size)
imgs, labels = x_train[idx], y_train[idx]
# 生成噪聲圖片
noise = np.random.normal(0, 1, (batch_size, 100))
# 生成模型通過噪聲圖片跟標(biāo)簽,生成相應(yīng)的圖片
gen_imgs = self.generator.predict([noise, labels])
# 訓(xùn)練判別模型
d_loss_real = self.discriminator.train_on_batch([imgs, labels], valid)
d_loss_fake = self.discriminator.train_on_batch([gen_imgs, labels], fake)
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
# ---------------------
# 訓(xùn)練生成模型部分
# ---------------------
sampled_labels = np.random.randint(0, 10, batch_size).reshape(-1, 1)
# 訓(xùn)練生成模型模型
g_loss = self.combined.train_on_batch([noise, labels], valid)
# 記錄訓(xùn)練結(jié)果的值
self.g_loss.append(g_loss)
self.epoch_range.append(epoch)
# 每200個(gè)epoch輸出一次結(jié)果,查看效果
if epoch % sample_interval == 0:
sample_images(epoch, self.generator)
創(chuàng)建條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)對(duì)象
gan = ConditionalGAN()
訓(xùn)練
這次我們訓(xùn)練20000個(gè)epochs,設(shè)置Batch Size大小為32, 同時(shí)每200個(gè)epoch,我們輸出一次預(yù)測結(jié)果,看下0-9這幾個(gè)數(shù)字在當(dāng)前模型下的生成情況。
從中間的每200個(gè)epoch的結(jié)果來看,我們的模型從最開始的隨機(jī)圖像,先慢慢的產(chǎn)生出黑色的背景圖,然后在每個(gè)圖像的中間慢慢的產(chǎn)生出內(nèi)容,隨著epoch迭代的增加,中間輸出的圖像的內(nèi)容也慢慢的變得更加有意義,直到迭代結(jié)束。我們輸出的結(jié)果圖像,基本可以用肉眼看到這個(gè)是什么數(shù)字。
gan.train(epochs=20000, batch_size=32, sample_interval=200)
查看訓(xùn)練過程中條件生成對(duì)抗模型(Conditional Generative Adversarial Networks)的損失值(loss)情況:
圖表中顯示了生成模型跟對(duì)抗模型的損失值(loss)的趨勢,
查看圖表,我們可以看到損失值(loss)從一開始的非常大的數(shù)值,下降到了一個(gè)穩(wěn)定的值。
這個(gè)表明我們的模型在不斷的迭代的過程中,產(chǎn)生的結(jié)果的誤差是在逐步逐步的減小,最后趨于一個(gè)穩(wěn)定的值,說明我們的模型一直在收斂。
plt.plot(gan.epoch_range, gan.g_loss, '-r', label= "Generator Loss")
plt.legend()
plt.xlabel('epoch')
plt.ylabel('loss')
腳本地址:https://github.com/matpool/mn...
矩池云現(xiàn)已經(jīng)把腳本鏡像以上線,有感興趣的用戶可以在矩池云中體驗(yàn)。
審核編輯 黃昊宇
-
python
+關(guān)注
關(guān)注
56文章
4801瀏覽量
84865 -
強(qiáng)化學(xué)習(xí)
+關(guān)注
關(guān)注
4文章
268瀏覽量
11273
發(fā)布評(píng)論請先 登錄
相關(guān)推薦
評(píng)論