如何將深度學(xué)習(xí)研究論文實現(xiàn)為代碼的幾個要點

極市導(dǎo)讀
?深度學(xué)習(xí)領(lǐng)域涌現(xiàn)了大量研究論文,使得對現(xiàn)有研究的復(fù)現(xiàn)和使用成為一項挑戰(zhàn)。原因在于缺乏作者提供的開源實現(xiàn)。本文作者就這一問題詳細介紹了將深度學(xué)習(xí)研究論文實現(xiàn)為代碼的步驟及技巧,并輔以舉例講解。?>>加入極市CV技術(shù)交流群,走在計算機視覺的最前沿
導(dǎo)讀
如果深度學(xué)習(xí)是一種超能力,那么將理論從論文轉(zhuǎn)化為可用的代碼就是一種超超能力。

為什么要去復(fù)現(xiàn)機器學(xué)習(xí)研究論文?
正如我所說的,能夠?qū)⒁黄撐霓D(zhuǎn)換成代碼絕對是一種超超能力,尤其是在像機器學(xué)習(xí)這樣每天都在快速發(fā)展的領(lǐng)域。大多數(shù)研究論文來自大型科技公司或大學(xué)里的人,他們可能是博士,也可能是研究前沿技術(shù)的人。還有什么比復(fù)現(xiàn)這些頂尖專業(yè)人士的研究成果更酷的呢?另一件需要注意的事情是,行業(yè)中對這些能夠?qū)⒀芯空撐倪M行代碼復(fù)現(xiàn)的人需求量很大。一旦你掌握了撰寫研究論文的竅門,你就會處于與這些研究人員同等的地位。這些研究人員也是通過閱讀和復(fù)現(xiàn)研究論文的練習(xí)獲得了這些技能。
我是如何來閱讀和對論文進行復(fù)現(xiàn)的?
你可能會說,“嗯,我對深度學(xué)習(xí)算法有一個大致的了解,像全連接網(wǎng)絡(luò),卷積神經(jīng)網(wǎng)絡(luò),循環(huán)神經(jīng)網(wǎng)絡(luò),但問題是,我想開發(fā)SOTA(最新的)語音克隆AI,但我對語音克隆一無所知:(”。
好吧,這里是你的答案,我的方法的一些部分取自“Andrew Ng關(guān)于閱讀論文的建議”:https://www.youtube.com/watch?v=733m6qBH-jI&list=PLoROMvodv4rOABXSygHTsbvUz4G_YQhOb&index=8。
如果你想了解一個特定的主題:
收集與特定主題相關(guān)的5-6篇論文(你可以通過arxiv或類似的網(wǎng)站找到與某個主題相關(guān)的論文)。 不要把每一篇論文完整讀完,而是瀏覽所有的論文,然后挑一篇你感興趣的,或者如果你心里有一篇特別的論文,那就挑一這篇。 仔細閱讀摘要,從高層次上理解其中的思想,看看你的興趣是否還在繼續(xù),如果還在,那就繼續(xù)瀏覽圖片,看看你是否能對論文內(nèi)容做出假設(shè)。 現(xiàn)在,逐行仔細閱讀引言,因為論文所包含的大部分內(nèi)容將在這里用最簡單的方式和最小的數(shù)學(xué)來解釋。 如果你愿意,你可以跳過第一輪的數(shù)學(xué)方公式,但是不要跳過那些有熟悉的希臘字母的數(shù)學(xué)公式。 在任何情況下,如果你陷入了困境或某些詞讓你迷惑不解,不要猶豫,去google 吧,沒有人生來什么都懂。 在完成第一關(guān)之后,你將處于一種對論文試圖證明或改進的地方有一個高層次的理解的狀態(tài)。 在第二步中,嘗試理解本文中的幾乎所有內(nèi)容,如果遇到任何偽代碼,嘗試將其轉(zhuǎn)換為你選擇的python庫(PyTorch、TensorFlow……) 你可以閱讀更多的論文,并通過閱讀每篇論文的參考資料部分來更好地了解該領(lǐng)域。
?這里是一些高效理解論文的小技巧:
如果你是研究論文的初學(xué)者,最好在閱讀論文之前閱讀一些與該主題/研究論文相關(guān)的博客文章和視頻。這使你的工作更容易。 在復(fù)現(xiàn)論文時,一定要做筆記,并把重要的地方圈出來,以方便做參考。 如果你是研究論文實現(xiàn)的新手,并且在任何地方遇到了困難,那么嘗試一下開源實現(xiàn)并看看其他人是如何做到這一點的,這不是一個壞主意。
注意: 不要把第三點當(dāng)成常規(guī)操作,因為這會導(dǎo)致你的學(xué)習(xí)曲線會下降,你會過擬合。你應(yīng)該發(fā)展自己的閱讀和實現(xiàn)論文的方法,這只有通過開始才有可能,所以上面的步驟將幫助你開始。Andrew Ng表示,如果你在一個主題上能讀5 - 10篇論文,比如語音克隆,你會在實現(xiàn)的時候有一個較好的狀態(tài), 如果你讀了50 - 100篇論文,那在這個主題上,你可以進行研究或做前沿技術(shù)的研發(fā)。
我們來討論一篇論文
高層次的概要
現(xiàn)在,你已經(jīng)了解了如何閱讀論文,讓我們閱讀并為自己實現(xiàn)一篇論文。我們將研究Ian Goodfellow的論文 ——?生成對抗網(wǎng)絡(luò)(GAN),并使用PyTorch實現(xiàn)同樣的功能。
論文摘要中對論文內(nèi)容進行了詳細的概述。摘要告訴我們,研究人員提出了一個包含兩種神經(jīng)網(wǎng)絡(luò)的新框架,它們被稱為“生成器”和“判別器”。不要被名字弄懵,它們只是兩個神經(jīng)網(wǎng)絡(luò)的名字。
但在摘要部分要注意的主要一點的是,上面提到的生成器和判別器會相互競爭。好了,讓我來講清楚一點。
讓我們以用GANs生成不存在的新人臉為例。
該發(fā)生器生成與真實圖像尺寸(H×W×C)相同的人臉,并將其喂給判別器,判別器判斷該圖像是由發(fā)生器生成的偽圖像還是真實的人臉。
現(xiàn)在你可能會有一個問題,“嗯,這個判別器是如何辨別真假圖像的?”,下面就是你的答案:
判別圖像是否是真實的是一個分類問題,也就是說,判別器必須分辨圖像是真實的還是假的(0或1)。所以我們可以像訓(xùn)練狗和貓分類卷積神經(jīng)網(wǎng)絡(luò)那樣來訓(xùn)練判別器,而本文中用的是全連接的網(wǎng)絡(luò)。
?DCGAN是另一種類型的GAN,使用卷積神經(jīng)網(wǎng)絡(luò)代替全連接網(wǎng)絡(luò)會有更好的結(jié)果。所以我們訓(xùn)練判別器的方式是將圖像輸入判別器中,輸出0或1,也就是說,假的或真的。
當(dāng)我們訓(xùn)練我們的判別器時,我們將把由生成器生成的圖像傳遞給判別器,并分類它是真的還是假的。生成器調(diào)整它所有的權(quán)值,直到它能夠欺騙分類器預(yù)測生成的圖像是真實的。
我們將給生成器一個隨機概率分布(一個隨機張量),生成器的職責(zé)是改變這個概率分布,以匹配真實圖像的概率分布。
這些是我們在執(zhí)行代碼時應(yīng)該遵循的步驟:
→加載包含真實圖像的數(shù)據(jù)集。
→創(chuàng)建一個二維隨機張量(假數(shù)據(jù)的概率分布)。
→創(chuàng)建判別器和生成器模型。
→在真實圖像和虛假圖像上訓(xùn)練判別器。
→將假圖像的概率分布送到生成器中,并用判別器測試是否可以區(qū)分出這個圖像是由生成器生成的虛假圖像。
→調(diào)整生成器的權(quán)值(通過隨機梯度下降)直到判別器無法區(qū)分真假圖像。
你可能有幾個疑問,但現(xiàn)在沒關(guān)系,一旦我們實現(xiàn)了理論代碼,你會了解它是如何工作的。
損失函數(shù)
在我們實現(xiàn)代碼之前,我們需要一個損失函數(shù),以便我們可以優(yōu)化我們的生成器網(wǎng)絡(luò)和判別器網(wǎng)絡(luò)。
該判別器模型是個二分類問題,因此我們使用二元交叉熵損失作為判別器,也可以使用本文討論的自定義損失函數(shù)。
本文的損失函數(shù):[log D(x)] + [log(1?D(G(z)))]
x?→真實圖像
z?→假數(shù)據(jù)或噪聲(隨機張量)
D?→判別模型
G?→生成模型
G(z) →將假數(shù)據(jù)或噪聲送到生成器中(輸出是假圖像)
D(x)?→將真實圖像送到判別器中(輸出是0或1)
D(G(z))?→將假數(shù)據(jù)送到生成器中,將生成器輸出的圖像送到判別器中得到預(yù)測(輸出是0或1)
如果你想用論文中的損失函數(shù),讓我來解釋一下:
本文認為,對于判別器,我們需要將上述損失函數(shù)最大化。
讓我們看方程的第一部分:
—?D(x)?輸出0或1,所以,當(dāng)我們最大化 *log[D(x)]*時,這使得當(dāng)把真實圖像喂給判別器的時候,輸出趨向于1,這正是我們所需要的。
我們再看方程的第二部分:
—?G(z)?輸出一張圖像,和真實的圖像具有相同的尺寸,現(xiàn)在將假圖像送到判別器*D(G(z))*中,當(dāng)最大化判別器的輸出的時候,判別器的輸出會趨向于1,此時,當(dāng)我們最大化[1 ? D(G(z))]的時候,*D(G(z))*會得到趨向于0的值,這也正是當(dāng)我們將假圖像送到判別器的時候所需要的。
注意: 你可以在方程上加上一個負號,然后將損失函數(shù)轉(zhuǎn)化為判別器的最小化問題這比最大化更容易。
對于生成器,我們需要最小化上述方程,但本文只考慮方程的第二部分*[log(1?D(G(z)))]*進行最小化。
— 當(dāng)我們最小化*D(G(z))*時,判別器輸出一個接近于0的值,并且方程的總輸出接近于1,這就是我們的生成器想要達到的,當(dāng)從生成器得到假圖像時,欺騙鑒別器預(yù)測1(真實)。
好了,我們開始寫代碼!
我已經(jīng)在谷歌colab中完成了代碼實現(xiàn),你試試在谷歌colab或jupyter中寫代碼。1、導(dǎo)入所需的庫—
import?torch
from?torch?import?nn
from?torch?import?optim
from?torchvision.transforms?import?transforms
import?numpy?as?np
from?PIL?import?Image
import?matplotlib.pyplot?as?plt
from?tqdm.notebook?import?tqdm
import?warnings
warnings.filterwarnings("ignore")2、我們將使用一個單一的圖像作為真實的圖像,以更快的訓(xùn)練和得到結(jié)果,所以圖像生成的生成器將類似于這個圖像,你也可以使用一組圖像的數(shù)據(jù),這一切由你。我們將使用_PIL library_將圖像加載為PIL image,然后使用_torchvision transforms_調(diào)整大小并將圖像轉(zhuǎn)換為張量,然后創(chuàng)建大小為(1×100)的偽噪聲來生成圖像。
transform?=?transforms.Compose([
????????????????????????????????transforms.Resize((32,?32)),
????????????????????????????????transforms.ToTensor()
])
flat_img?=?3072??#32×32×3?--size?of?flattened?image
img?=?Image.open('truck.jpeg')
real_img?=?transform(img)
torch.manual_seed(2)
fake_img?=?torch.rand(1,?100)
plt.imshow(np.transpose(real_img.numpy(),?(1,?2,?0)))
print(real_img.size())3、創(chuàng)建一個判別器模型,它是一個全連接的神經(jīng)網(wǎng)絡(luò),接收真實圖像或偽圖像,輸出0或1。
class?Discriminator(nn.Module):
??def?__init__(self):
????super().__init__()
????self.linear?=?nn.Sequential(
????????nn.Linear(flat_img,?10000),
????????nn.ReLU(),
????????nn.Linear(10000,?1),
????????nn.Sigmoid()
????)
??def?forward(self,?img):
????img?=?img.view(1,?-1)
????out?=?self.linear(img)
????return?out4、創(chuàng)建一個生成器模型,它也是一個全連接的網(wǎng)絡(luò),接受隨機噪聲并輸出一個與真實圖像大小相同的圖像張量。
class?Generator(nn.Module):
??def?__init__(self):
????super().__init__()
????self.linear?=?nn.Sequential(
????????nn.Linear(100,?10000),
????????nn.LeakyReLU(),
????????nn.Linear(10000,?4000),
????????nn.LeakyReLU(),
????????nn.Linear(4000,?flat_img)
????)
??def?forward(self,?latent_space):
????latent_space?=?latent_space.view(1,?-1)
????out?=?self.linear(latent_space)
????return?out5、初始化模型,優(yōu)化器和損失函數(shù),然后將它們移動到所需的設(shè)備(cuda或cpu)。我們在判別器中使用二元交叉熵損失,并對生成器使用本文中討論的損失函數(shù)_log(1 - D(G(z))_。
device?=?'cuda:0'?if?torch.cuda.is_available()?else?'cpu'
discr?=?Discriminator().to(device)
gen?=?Generator().to(device)
opt_d?=?optim.SGD(discr.parameters(),?lr=0.001,?momentum=0.9)
opt_g?=?optim.SGD(gen.parameters(),?lr=0.001,?momentum=0.9)
criterion?=?nn.BCELoss()6、現(xiàn)在我們對模型進行訓(xùn)練,整個GAN被訓(xùn)練500個大的epoch,判別器先 訓(xùn)練4個epoch,然后生成器再訓(xùn)練3個epoch。
epochs?=?500
discr_e?=?4?
gen_e?=?3
#whole?model?training?starts?here
for?epoch?in?tqdm(range(epochs),?total=epochs):
??#discriminator?training
??for?k?in?range(discr_e):
????opt_d.zero_grad()
????out_d1?=?discr(real_img.to(device))
????#loss?for?real?image
????loss_d1?=?criterion(out_d1,?torch.ones((1,?1)).to(device))
????loss_d1.backward()
????out_d2?=?gen(fake_img.to(device)).detach()
????#loss?for?fake?image
????loss_d2?=?criterion(discr(out_d2.to(device)),?torch.zeros((1,?1)).to(device))
????loss_d2.backward()
????opt_d.step()
??#generator?training
??for?i?in?range(gen_e):
????opt_g.zero_grad()
????
????out_g?=?gen(fake_img.to(device))
????
????#Binary?cross?entropy?loss
????#loss_g?=??criterion(discr(out_g.to(device)),?torch.ones(1,?1).to(device))
????#----Loss?function?in?the?GAN?paper
????#[log(1?-?D(G(z)))]
????loss_g?=?torch.log(1.0?-?(discr(out_g.to(device))))?
????loss_g.backward()
????opt_g.step()?7、將生成的圖像與真實圖像進行比較。你可以調(diào)整學(xué)習(xí)率,動量,epochs以及生成器和判別器中的層以得到更好的結(jié)果。
最后的思考
生成的圖像可能分辨率不是很高,因為本文只是整個生成模型的開始。如果你仍然在堅持,你可以繼續(xù)閱讀DCGANs或其他論文:https://github.com/nightrome/really-awesome-gan,并實現(xiàn)看看那些了不起的結(jié)果,但請記住本文是這些論文的基礎(chǔ)。
推薦閱讀


