使用PyTorch进行编程:构建深度学习应用程序

图片你好居住者!伊恩·波因特(Ian Poynter)将帮助您弄清楚如何在云中设置PyTorch,如何创建神经体系结构以使其更易于使用图像,声音和文本。本书涵盖了学习移植,调试模型和使用PyTorch库的基本概念。您将学习:-实施深度学习模型-在大型项目中使用PyTorch-应用学习转移-使用PyTorch火炬音频和卷积模型对音频数据进行分类-使用在Wikipedia上训​​练的模型应用最新的NLP技术-调试PyTorch模型与TensorBoard和Flamegraph合作-在容器中部署PyTorch应用程序“ PyTorch是发展最快的深度学习库之一,几乎可以与Google巨头TensorFlow媲美。





使用PyTorch进行图像分类



深度学习教科书充满了专业的,难以理解的术语。我尝试将其保持在最低水平,并始终给出一个示例,当您习惯使用PyTorch时,可以轻松扩展该示例。我们在整本书中都使用此示例来演示如何调试模型(第7章)或将其部署到生产环境(第8章)。



从现在起到第四章结束,我们将编译图像分类器。神经网络通常用作图像分类器。网络提供图片并提出一个简单的问题:“这是什么?”



让我们从在PyTorch中创建应用程序开始。



分类问题



在这里,我们将创建一个简单的分类器,以区分鱼和猫。我们将迭代模型的设计和开发过程,以使其更加准确。

在图。2.1和2.2描绘了鱼和猫的所有荣耀。我不确定鱼是否有名字,但是猫的名字叫Helvetica。



让我们从讨论标准分类问题开始。



图片 图片



标准难度



如何编写一个可以分辨猫中鱼的程序?也许您会编写一组规则来描述猫是尾巴还是鱼有鳞,然后将这些规则应用于图像,以便程序可以对图像进行分类。但这需要时间,精力和技巧。如果遇到Manx猫怎么办?尽管它显然是猫,但没有尾巴。



当您尝试借助它们来描述所有可能的情况时,这些规则变得越来越复杂。另外,我必须承认视觉编程对我来说很糟糕,因此必须为所有这些规则手动编写代码的想法令人恐惧。



您需要一个在输入图像时返回猫或鱼的功能。简单地完整列出所有标准很难构造这样的功能。但是,深度学习本质上迫使计算机完成创建所有这些规则的艰苦工作,前提是我们创建结构,为网络提供大量数据,并让其知道是否给出了正确答案。这就是我们要做的。此外,您还将学习一些使用PyTorch的基本技术。



但首先是数据



首先,我们需要数据。多少数据?取决于各种因素。正如您将在第4章中看到的那样,任何深度学习技术都需要大量数据来训练神经网络的想法不一定是正确的。但是,我们现在要从头开始,这通常需要访问大量数据。鱼和猫的许多图像都是必需的。



人们可能会花一些时间从Google上的图像搜索中下载一堆图像,但是有一种更简单的方法:用于训练神经网络的图像的标准集合是ImageNet。它包含超过1400万张图像和2万张图像类别。这是所有图像分类器进行比较的标准。因此,我从那里拍摄图像,尽管您可以根据需要选择其他选项。



除数据外,PyTorch还应具有定义猫是什么和鱼是什么的方法。对我们来说这很容易,但是对于计算机来说却很难(这就是我们创建程序的原因!)。我们使用附加到数据的标签,这称为监督学习。 (如果您无权访问任何标签,那么您会猜到,使用了无监督机器学习。)



如果我们使用ImageNet数据,则它们的标签将无用,因为它们包含的信息过多。在电脑上标记虎斑猫或鳟鱼与猫或鱼不同。



需要重新标记它们。由于ImageNet包含大量图像,因此,我对图像和鱼和猫的URL进行了编译(https://oreil.ly/NbtEU)。



您可以在该目录中运行download.py脚本,它将从URL下载图像并将其放置在适当的培训位置。重新标记很简单;该脚本将猫的图像存储在train / cat目录中,将鱼的图像存储在train / fish目录中。如果您不想使用脚本来下载,只需创建这些目录并将相应的图像放在正确的位置即可。现在我们有了数据,但是我们需要将其转换为PyTorch可以理解的格式。



PyTorch和数据加载器



将数据加载并转换为可训练的格式通常是数据科学的一个领域,需要花费很长时间。 PyTorch已制定了既定的数据交互要求,使您无论使用图像,文本还是音频,都非常简单。



使用数据的两个主要条件是数据集和数据加载器。数据集是Python类,它使我们能够获取发送到神经网络的数据。



数据加载器是将数据从数据集传输到网络的工具。 (这可能包括以下信息:多少个工作进程正在将数据上载到网络?我们同时上载了多少张图像?)



首先让我们看一下数据集。如果每个数据集都满足以下抽象Python类的要求,则无论它包含图像,音频,文本,3D风景,股票市场信息还是其他任何内容,都可以与PyTorch进行交互:



class Dataset(object):
     def __getitem__(self, index):
          raise NotImplementedError

     def __len__(self):
          raise NotImplementedError


这很简单:我们必须使用一种返回数据集大小(len)的方法,以及一种可以成对地从数据集中提取元素的方法(标签,张量)。当数据加载器将数据馈送到神经网络进行训练时,这被调用。因此,我们必须为getitem方法编写一个主体,该主体可以获取图像,将其转换为张量,然后放回并标记回去,以便PyTorch可以使用它。一切都清楚了,但是显然这种情况很常见,所以也许PyTorch使任务更容易了吗?



创建训练数据集



torchvision软件包包括一个ImageFolder类,该类几乎可以完成所有操作,假设我们的图像位于每个目录都是标签的结构中(例如,所有的cat都位于名为cat的目录中)。这是猫和鱼的示例所需的内容:



import torchvision
from torchvision import transforms

train_data_path = "./train/"
transforms = transforms.Compose([
      transforms.Resize(64),
      transforms.ToTensor(),
      transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                std=[0.229, 0.224, 0.225] )
      ])

train_data = torchvision.datasets.ImageFolder
(root=train_data_path,transform=transforms)


这里添加了其他内容,因为Torchvision还允许您指定在图像进入神经网络之前要应用于图像的转换列表。默认的转换是获取图像数据并将其转换为张量(上一代码中所示的trans form.ToTensor()方法),但它还会执行其他一些可能不太明显的事情。



首先,构建GPU来执行快速,标准大小的计算。但是我们可能有各种各样分辨率的图像。为了提高处理性能,我们使用Resize变换(64)将每个输入图像缩放到相同的64x64分辨率。然后,我们将图像转换为张量,最后围绕一组特定的均值和标准偏差点标准化张量。



归一化很重要,因为当输入通过神经网络的各个层时,预计会执行大量的乘法运算。将输入值保持在0到1之间可防止在学习阶段出现较大的值增加(称为爆炸梯度问题)。这种神奇的化身只是ImageNet数据集整体的均值和标准差。您可以专门针对鱼和猫的子集进行计算,但是这些值相当可靠。 (如果您使用的是完全不同的数据集,则需要计算此均值和方差,尽管许多人只是使用ImageNet常量并报告可接受的结果。)



可组合的转换还使执行数据旋转的图像旋转和图像移位等操作变得容易,我们将在第4章中进行介绍。



在此示例中,我们将图像调整为64x64。我做出了这个随机选择,以加快在第一个网络上的计算速度。您将在第3章中看到的大多数现有体系结构的输入图像都使用224x224或299x299。通常,输入文件的大小越大,网络可以从中学习的数据就越多。通常您可以将一小批图像放入GPU内存中。


关于数据集还有很多其他信息,而不仅仅是这些。但是,如果我们已经了解了训练数据集,为什么我们应该比需要知道的更多呢?



验证和参考数据集



设置了我们的训练数据集,但是现在我们需要对验证数据集重复相同的步骤。这有什么区别?深度学习(实际上是所有机器学习)的陷阱之一是过拟合:该模型确实擅长识别其训练的内容,但不适用于尚未看到的示例。该模型看到的是猫的图片,并且如果所有其他猫的图片与此不太相似,则模型会确定它不是猫,尽管相反的情况很明显。为了防止神经网络表现为这种行为,我们将控制样本加载到download.py中,即加载到不在训练数据集中的一系列猫和鱼的图像中。在每个训练周期的末尾(也称为纪元),我们比较此设置以确保网络没有错误。请勿惊慌,此检查的代码非常简单:这是相同的代码,但更改了多个变量名称:



val_data_path = "./val/"
val_data = torchvision.datasets.ImageFolder(root=val_data_path,
                                                                  transform=transforms)


我们只是使用了transforms链,而不是再次定义它。



除了验证数据集,我们还需要创建一个验证数据集。完成所有训练后,可用于测试模型:



test_data_path = "./test/"
test_data = torchvision.datasets.ImageFolder(root=test_data_path,
                                                                   transform=transforms)


乍一看,不同类型的集合可能很复杂且令人困惑,因此我整理了一张表格,指出训练的哪一部分使用了每个集合(表2.1)。



图片


现在,我们可以使用几行Python代码来创建数据加载器:



batch_size=64
train_data_loader = data.DataLoader(train_data, batch_size=batch_size)
val_data_loader = data.DataLoader(val_data, batch_size=batch_size)
test_data_loader = data.DataLoader(test_data, batch_size=batch_size)


此代码中的新功能和值得注意的是batch_size命令。她说,在我们训练和更新之前,将有多少图像通过网络。从理论上讲,我们可以将batch_size分配给测试和训练数据集中的一系列图像,以便网络在刷新之前可以看到每个图像。在实践中,通常不会这样做,因为较小的数据包(在文献中通常称为小型数据包)需要较少的内存,并且不需要在数据集中存储有关每个图像的所有信息,并且较小的数据包大小会导致随着网络的学习速度加快更新快得多。对于PyTorch数据加载器,默认将batch_size设置为1,您很可能希望对其进行更改。尽管我选择了64,但您可以尝试了解在不耗尽GPU内存的情况下可以使用多少个微型程序包。试用一些其他参数:例如,您可以指定如何获取数据集,是否在每次启动时重新整理整个数据集以及涉及多少工作流来从数据集中检索数据。所有这些都可以在PyTorch文档



这是关于将数据传递给PyTorch的,所以现在让我们想象一个简单的神经网络,它将开始对图像进行分类。



最后是神经网络!



我们将从最简单的深度学习网络开始-输入层将与输入张量(我们的图像)一起工作;一个输出层,其大小等于我们的输出类的数量(2);和介于两者之间的隐藏层。在第一个示例中,我们将使用完全链接的层。在图。2.3显示了三个节点的输入层,三个节点的隐藏

层和两个节点的输出。



在此示例中,一层中的每个节点都会影响下一层中的节点,并且每个连接的权重决定了从该节点到下一层的信号强度。(这些权重在训练网络时通常会从随机初始化中进行更新。)当输入通过网络时,我们(或PyTorch)可以简单地将输入的权重和偏差乘以矩阵。在将它们传递给下一个函数之前,此结果将进入激活函数,这只是将非线性引入我们系统的一种方式。



图片


激活功能



激活功能听起来很棘手,但是您现在可以找到的最常见的激活功能是ReLU或整流线性单位。再次聪明!但这只是一个实现max(0,x)的函数,因此如果输入为负,则结果为0,如果x为正,则结果仅为输入(x)。就这么简单!



您最有可能遇到的另一个激活函数是多元逻辑函数(softmax),从数学意义上讲它稍微复杂一些。基本上,它会生成一组从0到1的值,这些值的总和为1(概率!),并以增加差异的方式对这些值进行加权,也就是说,它会在一个向量中产生一个结果,该结果将大于所有其他结果。您经常会看到它在分类网络的末尾使用,以确保该网络会对它认为输入数据的类别做出某种预测。



现在我们有了所有这些构建块,我们就可以开始构建第一个神经网络了。



神经网络创建



在PyTorch中构建神经网络类似于在Python中进行编程。我们从一个名为torch.nn.Network的类继承,并填充__init__和forward方法:



class SimpleNet(nn.Module):

def __init__(self):
     super(Net, self).__init__()
     self.fc1 = nn.Linear(12288, 84)
     self.fc2 = nn.Linear(84, 50)
     self.fc3 = nn.Linear(50,2)

def forward(self):
     x = x.view(-1, 12288)
     x = F.relu(self.fc1(x))
     x = F.relu(self.fc2(x))
     x = F.softmax(self.fc3(x))
     return x
simplenet = SimpleNet()


同样,这并不困难。我们在init()中进行必要的设置,在这种情况下,我们称为超类构造函数和三个完全连接的层(在PyTorch中称为Linear,在Keras中称为Dense)。 forward()方法描述了在训练和预测(推理)中如何通过网络传输数据。首先,我们必须转换图像中的3D张量(x和y以及3通道颜色信息-红色,绿色,蓝色)-注意! -变成一维张量,以便可以将其传递到第一个线性层,我们使用view()进行此操作。因此,我们按顺序应用图层和激活函数,返回softmax输出以获得该图像的预测。



隐藏层中的数字是任意的,除了最后一层的输出为2之外,它与我们的两类猫或鱼匹配。要求图层中的数据在堆栈中收缩时收缩。例如,如果该层从50个输入扩展到100个输出,则网络可以通过简单地将50个连接传递到一百个输出中的五十个来学习,并考虑其工作已完成。通过减小输出相对于输入的大小,我们迫使网络的这一部分以较少的资源来学习原始输入的代表性,这大概意味着网络定义了图像的一些区别特征:例如,它学会了识别鳍或尾巴。



我们有一个预测,可以将其与原始图像的实际标签进行比较,以查看它是否正确。但是,它需要某种方法来允许PyTorch不仅量化预测的正确性或不正确性,而且还可以量化预测的正确性或不正确性。损失函数可以做到这一点。



关于作者



Ian Poynter(Ian Pointer)-数据科学工程师,专门为《财富》 100强的许多客户提供机器学习解决方案(包括深入的教学方法)。目前,Yang在Lucidworks工作,该公司从事高级应用程序和NLP的开发。



»有关这本书的更多详细信息,请参见出版社网站

»目录

»摘录:



为居住者提供25%的优惠券折扣-PyTorch



在为该书的纸质版本付款后,会通过电子邮件发送一本电子书。



All Articles