使用深度学习进行图像分类
解决任何真实问题的重要一步是获取数据。Kaggle提供了大量不同数据科学问题的竞赛。我们将挑选一个2014年提出的问题,然后使用这个问题测试本章的深度学习算法,并在第5章中进行改进,我们将基于卷积神经网络(CNN)和一些可以使用的高级技术来改善图像识别模型的性能。大家可以从https://www.kaggle.com/c/dogs-vs-cats/data下载数据。数据集包含25,000张猫和狗的图片。在实现算法前,预处理数据,并对训练、验证和测试数据集进行划分是需要执行的重要步骤。数据下载完成后,可以看到对应数据文件夹包含了如图3.6所示的图片。
图3.6
当以图3.7所示的格式提供数据时,大多数框架能够更容易地读取图片并为它们设置标签的附注。也就是说每个类别应该有其所包含图片的独立文件夹。这里,所有猫的图片都应位于cat文件夹,所有狗的图片都应位于dog文件夹。
图3.7
Python可以很容易地将数据调整成需要的格式。请先快速浏览一下代码,然后,我们将讲述重要的部分。
上述代码所做的处理,就是获取所有图片文件,并挑选出2,000张用于创建验证数据集。它把图片划分到了cats和dogs这两个类别目录中。创建独立的验证集是通用的重要实践,因为在相同的用于训练的数据集上测试算法并不合理。为了创建validation数据集,我们创建了一个图片数量长度范围内的数字列表,并把图像无序排列。在创建validation数据集时,我们可使用无序排列的数据来挑选一组图像。让我们详细解释一下每段代码。
下面的代码用于创建文件:
glob方法返回特定路径的所有文件。当图片数量巨大时,也可以使用iglob,它返回一个迭代器,而不是将文件名载入到内存中。在我们的例子中,只有25,000个文件名,可以很容易加载到内存里。
可以使用下面的代码混合排列文件:
上述代码返回25,000个0~25,000范围内的无序排列的数字,可以把其作为选择图片子集的索引,用于创建validation数据集。
可以创建验证代码,如下所示:
上述代码创建了validation文件夹,并在train和valid目录里创建了对应的类别文件夹(cats和dogs)。
可以用下面的代码对索引进行无序排列:
在上面的代码中,我们使用无序排列后的索引随机抽出2000张不同的图片作为验证集。同样地,我们把训练数据用到的图片划分到train目录。
现在已经得到了需要格式的数据,我们来快速看一下如何把图片加载成PyTorch张量。
1.把数据加载到PyTorch张量
PyTorch的torchvision.datasets包提供了一个名为ImageFolder的工具类,当数据以前面提到的格式呈现时,它可以用于加载图片以及相应的标签。通常需要进行下面的预处理步骤。
1.把所有图片转换成同等大小。大多数深度学习架构都期望图片具有相同的尺寸。
2.用数据集的均值和标准差把数据集归一化。
3.把图片数据集转换成PyTorch张量。
PyTorch在transforms模块中提供了很多工具函数,从而简化了这些预处理步骤。例如,进行如下3种变换:
[if !supportLists]· [endif]调整成256 ×256大小的图片;
[if !supportLists]· [endif]转换成PyTorch张量;
[if !supportLists]· [endif]归一化数据(第5章将探讨如何获得均值和标准差)。
下面的代码演示了如何使用ImageFolder类进行变换和加载图片:
train对象为数据集保留了所有的图片和相应的标签。它包含两个重要属性:一个给出了类别和相应数据集索引的映射;另一个给出了类别列表。
把加载到张量中的数据可视化往往是一个最佳实践。为了可视化张量,必须对张量再次变形并将值反归一化。下面的函数实现了这样的功能:
现在,可以把张量传入前面的imshow函数,将张量转换成图片:
上述代码生成的输出如图3.8所示。
图3.8
2.按批加载PyTorch张量
在深度学习或机器学习中把图片进行批取样是一个通用实践,因为当今的图形处理器(GPU)和CPU都为批量图片的操作进行了优化。批尺寸根据我们使用的GPU种类而不同。每个GPU都有自己的内存,可能从2GB到12GB不等,有时商业GPU内存会更大。PyTorch提供了DataLoader类,它输入数据集将返回批图片。它抽象出了批处理的很多复杂度,如应用变换时的多worker的使用。下面的代码把前面的train和valid数据集转换到数据加载器(data loader)中:
DataLoader类提供了很多选项,其中最常使用的选项如下。
[if !supportLists]· [endif]shuffle:为true时,每次调用数据加载器时都混合排列图片。
[if !supportLists]· [endif]num_workers:负责并发。使用少于机器内核数量的worker是一个通用的实践。
3.构建网络架构
对于大多的真实用例,特别是在计算机视觉中,我们很少构建自己的架构。可以使用已有的不同架构快速解决我们的真实问题。在我们的例子中,使用了流行的名为ResNet的深度学习算法,它在2015年赢得了不同竞赛的冠军,如与计算机视觉相关的ImageNet。为了更容易理解,我们假设算法是一些仔细连接在一起的不同的PyTorch层,并不关注算法的内部。在第5章学习卷积神经网络(CNN)时,我们将看到一些关键的ResNet算法的构造块。PyTorch通过torchvision.models模块提供的现成应用使得用户更容易使用这样的流行算法。因而,对于本例,我们快速看一下如何使用算法,然后再详解每行代码:
models.resnet18(pertrained = True)对象创建了算法的实例,实例是PyTorch层的集合。我们打印出model_ft,快速地看一看哪些东西构成了ResNet算法。算法的一小部分看起来如图3.9所示。这里没有包含整个算法,因为这很可能会占用几页内容。
图3.9
可以看出,ResNet架构是一个层的集合,包含的层为Conv2d、BatchNorm2d和MaxPool2d,这些层以一种特有的方式组合在一起。所有这些算法都将接受一个名为pretrained的参数。当pretrained为True时,算法的权重已为特定的ImageNet分类问题微调好。ImageNet预测的类别有1000种,包括汽车、船、鱼、猫和狗等。训练该算法,使其预测1000种ImageNet类别,权重调整到某一点,让算法得到最高的准确率。我们为用例使用这些保存好并与模型共享的权重。与以随机权重开始的情况相比,算法以微调好的权重开始时会趋向于工作得更好。因而,我们的用例将从预训练好的权重开始。
ResNet算法不能直接使用,因为它是用来预测1,000种类别,而对于我们的用例,仅需预测猫和狗这两种类别。为此,我们拿到ResNet模型的最后一层——linear层,并把输出特征改成2,如下面的代码所示:
files = glob(os.path.join(path,'*/*.jpg')) no_of_images = len(files) shuffle = np.random.permutation(no_of_images) train = files[shuffle[:int(no_of_images*0.8)]] valid = files[shuffle[int(no_of_images*0.8):]]class Architecturel(nn.Module): def init (self, input_size, hidden_size, num_classes): super(Architecturel, self). init () self.fcl = nn.Linear(input_size, hidden_size) self.relu nn.ReLU() self.fc2 = nn.Linear(hidden_size, num_classes) self.relu = nn.ReLU() self.fc3 = nn.Linear(hidden_size, num_classes) def forward(self, x): out = self.fcl(x) out = self.relu(out) out = self.fc2(out) out = self.relu(out) out = self.fc3(out) return outclass Architecture2(nn.Module): def init (self, input_size, hidden_size, num_classes): super(Architecture2, self). init () self.fcl = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.fc2 = nn.Linear(hidden_size, num_classes) def forward(self, x): out = self.fcl(x) out = self.relu(out) out = self.fc2(out) return outmodel =Architecturel(l〇,2〇,2) optimizer = torch.optim.Adam(model.parameters(), lr=le-4, weight_decay=le-5)nn.dropout(x, training=True) scheduler = StepLR(optimizer, step_size=30, gamma=0.1) for epoch in range(100): scheduler.step() train(...) validate(...) scheduler = MultiStepLR(optimizer, milestones=[30,80], gamma=0.1) for epoch in range(100): scheduler.step() train(...) validate(...) optimizer = torch.optim.SGD(model.parameters(), lr=〇.l, momentum=0.9) scheduler = ReduceLR〇nPlateau(optimizer, 'min') for epoch in range(lO): train(...) val_loss = validate(...) # Note that step should be called after validate() scheduler.step(val_loss)
获取更多专业知识请关注公众号:“人工智能技术与咨询”
文章内容转自《PyTorch深度学习》,针对转载原作者有任何疑问,请第一时间联系我们,我们会进行调整或删除。