前面我們已經講完了一般的深層網絡,適用于圖像的卷積神經網絡,適用于序列的循環神經網絡。但是要知道Lecun提出第一代卷積網絡Lenet的時間是1998年,而循環神經網絡提出的時間更早,是在1986年。這些網絡在當時并沒有火起來,如今隨著計算能力的加強,數據集的增多,深度學習逐漸火了起來,隨著越來越多的人的研究,各種各樣的神經網絡都在不斷進步,CNN里面出現了inception net,resnet等等,RNN演變了LSTM和GRU,雖然神經網絡不斷在發展,但是本質上仍然是在CNN和RNN的基礎上。
直到2014年,深度學習三巨頭之一 Ian Goodfellow 提出了生成對抗網絡(Generative Adversarial Networks, GANs),剛開始的時候并沒有引起轟動,直到16年,學界、業界對其的興趣出現了“井噴”,多篇重磅文章陸續發表,Lecun也形容GANs“adversarial training is the coolest thing since sliced bread.” 16年12月NIPS大會上,Goodfellow做了GANs的專題報告,使得GANs成為了當今最炙手可熱的研究領域,等你看完了這篇文章你就會知道為什么GANs能夠成為當今人工智能領域的主要課題之一。
GANs
GANs的全稱叫做生成對抗網絡,根據這個名字,你就可以猜測這個網絡是由兩部分組成的,第一部分是生成,第二部分是對抗。那么你已經基本猜對了,這個網絡第一部分是生成網絡,第二部分對抗模型嚴格來講是一個判別器,簡單來說呢,就是讓兩個網絡相互競爭,生成網絡來生成假的數據,對抗網絡通過判別器去判別真偽,最后希望生成器生成的數據能夠以假亂真。
可以用這個圖來簡單的看一看這兩個過程。
下面我們就來依次介紹。
Discriminator Network
首先我們來講一下對抗過程,因為這個過程更加簡單。
對抗過程簡單來說就是一個判斷真假的判別器,相當于一個二分類問題,我們輸入一張真的圖片希望判別器輸出的結果是1,輸入一張假的圖片希望判別器輸出的結果是0。這其實已經和原圖片的label沒有關系了,不管原圖片到底是一個多少類別的圖片,他們都統一稱為真的圖片,label是1表示真實的;而生成的假的圖片的label是0表示假的。
我們訓練的過程就是希望這個判別器能夠正確的判出真的圖片和假的圖片,這其實就是一個簡單的二分類問題,對于這個問題可以用我們前面講過的很多方法去處理,比如logistic回歸,深層網絡,卷積神經網絡,循環神經網絡都可以。
Generative Network
接著我們要看看如何生成一張假的圖片。首先給出一個簡單的高維的正態分布的噪聲向量,如上圖所示的D-dimensional noise vector,這個時候我們可以通過仿射變換,也就是xw+b將其映射到一個更高的維度,然后將他重新排列成一個矩形,這樣看著更像一張圖片,接著進行一些卷積、池化、激活函數處理,最后得到了一個與我們輸入圖片大小一模一樣的噪音矩陣,這就是我們所說的假的圖片,這個時候我們如何去訓練這個生成器呢?就是通過判別器來得到結果,然后希望增大判別器判別這個結果為真的概率,在這一步我們不會更新判別器的參數,只會更新生成器的參數。
如下圖所示
以上的過程已經簡單的闡述了生成對抗網絡的學習過程,如果仍然不太清楚這個過程,下面我們會通過代碼來更清晰地展示整個過程。
Code
我們會使用mnist手寫數字來做數據集,通過生成對抗網絡我們希望生成一些“以假亂真”的手寫字體。為了加快訓練過程,我們不使用卷積網絡來做判別器,我們使用簡單的多層網絡來進行判別。
Discriminator Network
class discriminator(nn.Module):
def __init__(self):
super(discriminator, self).__init__()
self.dis = nn.Sequential(
nn.Linear(784, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 1),
nn.Sigmoid()
)
def forward(self, x):
x = self.dis(x)
return x
以上這個網絡是一個簡單的多層神經網絡,將圖片28x28展開成784,然后通過多層感知器,中間經過斜率設置為0.2的LeakyReLU激活函數,最后接sigmoid激活函數得到一個0到1之間的概率進行二分類。之所以使用LeakyRelu而不是用ReLU激活函數是因為經過實驗LeakyReLU的表現更好。
Generative Network
class generator(nn.Module):
def __init__(self, input_size):
super(generator, self).__init__()
self.gen = nn.Sequential(
nn.Linear(input_size, 256),
nn.ReLU(True),
nn.Linear(256, 256),
nn.ReLU(True),
nn.Linear(256, 784),
nn.Tanh()
)
def forward(self, x):
x = self.gen(x)
return x
輸入一個100維的0~1之間的高斯分布,然后通過第一層線性變換將其映射到256維,然后通過LeakyReLU激活函數,接著進行一個線性變換,再經過一個LeakyReLU激活函數,然后經過線性變換將其變成784維,最后經過Tanh激活函數是希望生成的假的圖片數據分布能夠在-1~1之間。
Discriminator Train
判別器的訓練由兩部分組成,第一部分是真的圖像判別為真,第二部分是假的圖片判別為假,在這兩個過程中,生成器的參數不參與更新。
首先我們需要定義loss的度量方式和優化函數,loss度量使用二分類的交叉熵,油畫函數注意使用的學習率是0.0003
criterion = nn.BCELoss()
d_optimizer = torch.optim.Adam(D.parameters(), lr=0.0003)
g_optimizer = torch.optim.Adam(G.parameters(), lr=0.0003)
接著進入訓練
img = img.view(num_img, -1) # 將圖片展開乘28x28=784
real_img = Variable(img).cuda() # 將tensor變成Variable放入計算圖中
real_label = Variable(torch.ones(num_img)).cuda() # 定義真實label為1
fake_label = Variable(torch.zeros(num_img)).cuda() # 定義假的label為0
# compute loss of real_img
real_out = D(real_img) # 將真實的圖片放入判別器中
d_loss_real = criterion(real_out, real_label) # 得到真實圖片的loss
real_scores = real_out # 真實圖片放入判別器輸出越接近1越好
# compute loss of fake_img
z = Variable(torch.randn(num_img, z_dimension)).cuda() # 隨機生成一些噪聲
fake_img = G(z) # 放入生成網絡生成一張假的圖片
fake_out = D(fake_img) # 判別器判斷假的圖片
d_loss_fake = criterion(fake_out, fake_label) # 得到假的圖片的loss
fake_scores = fake_out # 假的圖片放入判別器越接近0越好
# bp and optimize
d_loss = d_loss_real + d_loss_fake # 將真假圖片的loss加起來
d_optimizer.zero_grad() # 歸0梯度
d_loss.backward() # 反向傳播
d_optimizer.step() # 更新參數
我已經把每一步都注釋在了代碼上,這樣更加便于大家閱讀,這是一個判別器的訓練過程,我們希望判別器能夠正確辨別出真假圖片。
Generative Train
在生成網絡的訓練中,我們希望生成一張假的圖片,然后經過判別器之后希望他能夠判斷為真的圖片,在這個過程中,我們將判別器固定,將假的圖片傳入判別器的結果與真實label對應,反向傳播更新的參數是生成網絡里面的參數,這樣我們就可以通過跟新生成網絡里面的參數來使得判別器判斷生成的假的圖片為真,這樣就達到了生成對抗的作用。
# compute loss of fake_img
z = Variable(torch.randn(num_img, z_dimension)).cuda() # 得到隨機噪聲
fake_img = G(z) # 生成假的圖片
output = D(fake_img) # 經過判別器得到結果
g_loss = criterion(output, real_label) # 得到假的圖片與真實圖片label的loss
# bp and optimize
g_optimizer.zero_grad() # 歸0梯度
g_loss.backward() # 反向傳播
g_optimizer.step() # 更新生成網絡的參數
這樣我們就寫好了一個簡單的生成網絡,通過不斷地訓練我們希望能夠生成很真的圖片。
Result
通過不斷訓練,我們可以得到下面的圖片
這是真實圖片
第1幅為第一次生成的噪聲圖片,之后分別是跑完15次生成的圖片,跑完30次,跑完50次,跑完70次,最后一個是跑完100次生成的圖片
怎么樣,是不是特別神奇,我們居然可以生成一副看著很真的圖片,這里我們只是用了簡單的多層感知器來生成和判別模型,我們可以用更復雜的卷積神經網絡來做同樣的事情,代碼將和本文的代碼放在一起,有興趣的同學可以自己去看看,然后放幾張卷積網絡生成的圖片
可以發現產生的噪聲更少了,訓練也更加穩定,主要是里面引入了Batchnormalization,另外gan的訓練過程是特別困難的,兩個對偶網絡相互學習,這個時候有一些訓練技巧可以使得訓練生成更加穩定,詳細見一下github
最后我們來說一下為何Gans能夠成為最近20年來機器學習以及深度學習界革命性的發現。這是因為不管是深度學習還是機器學習仍然很大一部分是監督學習,但是創建這么多有label的數據集所需要的人力物力是極大的,同時遇到的新的任務時我們很容易得到原始的沒有label的數據集,這是我們需要花大量的時間去給其標定label,所以很多人都認為無監督學習才是機器學習的未來,這個時候Gans的出現為無監督學習提供了有力的支持,這當然引起了學界的大量關注,同時基于Gans的應用也越來越多,業界對其也非常狂熱。
最后引用Yan Lecun的話:"它(Gans)為創建無監督學習模型提供了強有力的算法框架,有望幫助我們為 AI 加入常識(common sense)。我們認為,沿著這條路走下去,有不小的成功機會能開發出更智慧的 AI 。"
以上我們簡單的介紹了Gans,通過網絡實現了手寫字體的生成,當然還有更多的變形和應用,有興趣的同學可以自己閱讀相關論文深入了解。
下一章我們將進入pytorch教程的最后一個部分,也是和AI聯系最為緊密的一個部分,reinforcement learning,增強學習。
本文代碼已經上傳到了github上
歡迎查看我的知乎專欄,深度煉丹
歡迎訪問我的博客