如何将深度学习研究论文实现为代码
作者:Bipin Krishnan P
编译:ronghuaiyang
导读
如果深度学习是一种超能力,那么将理论从论文转化为可用的代码就是一种超超能力。
为什么要去复现机器学习研究论文?
正如我所说的,能够将一篇论文转换成代码绝对是一种超超能力,尤其是在像机器学习这样每天都在快速发展的领域。
大多数研究论文来自大型科技公司或大学里的人,他们可能是博士,也可能是研究前沿技术的人。
还有什么比复现这些顶尖专业人士的研究成果更酷的呢?另一件需要注意的事情是,行业中对这些能够将研究论文进行代码复现的人需求量很大。
一旦你掌握了撰写研究论文的窍门,你就会处于与这些研究人员同等的地位。
这些研究人员也是通过阅读和复现研究论文的练习获得了这些技能。
我是如何来阅读和对论文进行复现的?
你可能会说,“嗯,我对深度学习算法有一个大致的了解,像全连接网络,卷积神经网络,循环神经网络,但问题是,我想开发SOTA(最新的)语音克隆AI,但我对语音克隆一无所知:(”。
好吧,这里是你的答案,我的方法的一些部分取自“Andrew Ng关于阅读论文的建议”:https://www.youtube.com/watch?v=733m6qBH-jI&list=PLoROMvodv4rOABXSygHTsbvUz4G_YQhOb&index=8。
如果你想了解一个特定的主题:
- 收集与特定主题相关的5-6篇论文(你可以通过arxiv或类似的网站找到与某个主题相关的论文)。
- 不要把每一篇论文完整读完,而是浏览所有的论文,然后挑一篇你感兴趣的,或者如果你心里有一篇特别的论文,那就挑一这篇。
- 仔细阅读摘要,从高层次上理解其中的思想,看看你的兴趣是否还在继续,如果还在,那就继续浏览图片,看看你是否能对论文内容做出假设。
- 现在,逐行仔细阅读引言,因为论文所包含的大部分内容将在这里用最简单的方式和最小的数学来解释。
- 如果你愿意,你可以跳过第一轮的数学方公式,但是不要跳过那些有熟悉的希腊字母的数学公式。
- 在任何情况下,如果你陷入了困境或某些词让你迷惑不解,不要犹豫,去google 吧,没有人生来什么都懂。
- 在完成第一关之后,你将处于一种对论文试图证明或改进的地方有一个高层次的理解的状态。
- 在第二步中,尝试理解本文中的几乎所有内容,如果遇到任何伪代码,尝试将其转换为你选择的python库(PyTorch、TensorFlow……)
- 你可以阅读更多的论文,并通过阅读每篇论文的参考资料部分来更好地了解该领域。
这里是一些高效理解论文的小技巧:
- 如果你是研究论文的初学者,最好在阅读论文之前阅读一些与该主题/研究论文相关的博客文章和视频。这使你的工作更容易。
- 在复现论文时,一定要做笔记,并把重要的地方圈出来,以方便做参考。
- 如果你是研究论文实现的新手,并且在任何地方遇到了困难,那么尝试一下开源实现并看看其他人是如何做到这一点的,这不是一个坏主意。
注意: 不要把第三点当成常规操作,因为这会导致你的学习曲线会下降,你会过拟合。
你应该发展自己的阅读和实现论文的方法,这只有通过开始才有可能,所以上面的步骤将帮助你开始。
Andrew Ng表示,如果你在一个主题上能读5 - 10篇论文,比如语音克隆,你会在实现的时候有一个较好的状态, 如果你读了50 - 100篇论文,那在这个主题上,你可以进行研究或做前沿技术的研发。
我们来讨论一篇论文
- 高层次的概要
现在,你已经了解了如何阅读论文,让我们阅读并为自己实现一篇论文。
我们将研究Ian Goodfellow的论文 —— 生成对抗网络(GAN),并使用PyTorch实现同样的功能。
论文摘要中对论文内容进行了详细的概述。
摘要告诉我们,研究人员提出了一个包含两种神经网络的新框架,它们被称为“生成器”和“判别器”。
不要被名字弄懵,它们只是两个神经网络的名字。但在摘要部分要注意的主要一点的是,上面提到的生成器和判别器会相互竞争。
好了,让我来讲清楚一点。
让我们以用GANs生成不存在的新人脸为例。
该发生器生成与真实图像尺寸(H×W×C)相同的人脸,并将其喂给判别器,判别器判断该图像是由发生器生成的伪图像还是真实的人脸。
现在你可能会有一个问题,“嗯,这个判别器是如何辨别真假图像的?”,下面就是你的答案:
判别图像是否是真实的是一个分类问题,也就是说,判别器必须分辨图像是真实的还是假的(0或1)。所以我们可以像训练狗和猫分类卷积神经网络那样来训练判别器,而本文中用的是全连接的网络。
DCGAN是另一种类型的GAN,使用卷积神经网络代替全连接网络会有更好的结果。所以我们训练判别器的方式是将图像输入判别器中,输出0或1,也就是说,假的或真的。
当我们训练我们的判别器时,我们将把由生成器生成的图像传递给判别器,并分类它是真的还是假的。生成器调整它所有的权值,直到它能够欺骗分类器预测生成的图像是真实的。
我们将给生成器一个随机概率分布(一个随机张量),生成器的职责是改变这个概率分布,以匹配真实图像的概率分布。
这些是我们在执行代码时应该遵循的步骤:
→加载包含真实图像的数据集。
→创建一个二维随机张量(假数据的概率分布)。
→创建判别器和生成器模型。
→在真实图像和虚假图像上训练判别器。
→将假图像的概率分布送到生成器中,并用判别器测试是否可以区分出这个图像是由生成器生成的虚假图像。
→调整生成器的权值(通过随机梯度下降)直到判别器无法区分真假图像。
你可能有几个疑问,但现在没关系,一旦我们实现了理论代码,你会了解它是如何工作的。
- 损失函数
在我们实现代码之前,我们需要一个损失函数,以便我们可以优化我们的生成器网络和判别器网络。
该判别器模型是个二分类问题,因此我们使用二元交叉熵损失作为判别器,也可以使用本文讨论的自定义损失函数。
本文的损失函数:[log D(x)] + [log(1−D(G(z)))]
x →真实图像
z →假数据或噪声(随机张量)
D →判别模型
G →生成模型
G(z) →将假数据或噪声送到生成器中(输出是假图像)
D(x) →将真实图像送到判别器中(输出是0或1)
D(G(z)) →将假数据送到生成器中,将生成器输出的图像送到判别器中得到预测(输出是0或1)
如果你想用论文中的损失函数,让我来解释一下:
本文认为,对于判别器,我们需要将上述损失函数最大化。
让我们看方程的第一部分:
— D(x) 输出0或1,所以,当我们最大化 *log[D(x)]*时,这使得当把真实图像喂给判别器的时候,输出趋向于1,这正是我们所需要的。
我们再看方程的第二部分:
— G(z) 输出一张图像,和真实的图像具有相同的尺寸,现在将假图像送到判别器*D(G(z))*中,当最大化判别器的输出的时候,判别器的输出会趋向于1,此时,当我们最大化[1 − D(G(z))]的时候,*D(G(z))*会得到趋向于0的值,这也正是当我们将假图像送到判别器的时候所需要的。
注意: 你可以在方程上加上一个负号,然后将损失函数转化为判别器的最小化问题这比最大化更容易。
对于生成器,我们需要最小化上述方程,但本文只考虑方程的第二部分*[log(1−D(G(z)))]*进行最小化。
— 当我们最小化*D(G(z))*时,判别器输出一个接近于0的值,并且方程的总输出接近于1,这就是我们的生成器想要达到的,当从生成器得到假图像时,欺骗鉴别器预测1(真实)。
好了,我们开始写代码!
我已经在谷歌colab中完成了代码实现,你试试在谷歌colab或jupyter中写代码。
1、导入所需的库—
import torchfrom torch import nnfrom torch import optimfrom torchvision.transforms import transformsimport numpy as npfrom PIL import Imageimport matplotlib.pyplot as pltfrom tqdm.notebook import tqdmimport warningswarnings.filterwarnings('ignore')
2、我们将使用一个单一的图像作为真实的图像,以更快的训练和得到结果,所以图像生成的生成器将类似于这个图像,你也可以使用一组图像的数据,这一切由你。
我们将使用PIL library将图像加载为PIL image,然后使用torchvision transforms调整大小并将图像转换为张量,然后创建大小为(1×100)的伪噪声来生成图像。
transform = transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor()])flat_img = 3072 #32×32×3 --size of flattened imageimg = 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、创建一个判别器模型,它是一个全连接的神经网络,接收真实图像或伪图像,输出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 out
4、创建一个生成器模型,它也是一个全连接的网络,接受随机噪声并输出一个与真实图像大小相同的图像张量。
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 out
5、初始化模型,优化器和损失函数,然后将它们移动到所需的设备(cuda或cpu)。我们在判别器中使用二元交叉熵损失,并对生成器使用本文中讨论的损失函数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、现在我们对模型进行训练,整个GAN被训练500个大的epoch,判别器先训练4个epoch,然后生成器再训练3个epoch。
epochs = 500discr_e = 4 gen_e = 3#whole model training starts herefor 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、将生成的图像与真实图像进行比较。你可以调整学习率,动量,epochs以及生成器和判别器中的层以得到更好的结果。
最后的思考
生成的图像可能分辨率不是很高,因为本文只是整个生成模型的开始。如果你仍然在坚持,你可以继续阅读DCGANs或其他论文:https://github.com/nightrome/really-awesome-gan,并实现看看那些了不起的结果,但请记住本文是这些论文的基础。
英文原文:https://towardsdatascience.com/converting-deep-learning-research-papers-to-code-f-f38bbd87352f