PyTorch是最受欢迎的深度学习Python库之一,它被人工智能研究社区广泛使用。许多开发者和研究人员使用PyTorch来加速深度学习研究实验和原型设计。
1.为什么使用PyTorch
如果你正在学习机器学习,进行深度学习研究,或构建人工智能系统,你可能需要使用深度学习框架。深度学习框架可以很容易地完成数据加载、预处理、模型设计、训练和部署等常见任务。PyTorch由于其简单、灵活和Python接口,已经在学术和研究团体中非常受欢迎。
以下是学习和使用PyTorch的一些原因:
- PyTorch很受欢迎
- PyTorch得到所有主流云平台的支持,如Amazon Web Services (AWS)、谷歌云平台(GCP)、微软Azure、阿里云等
- PyTorch得到Google Colaboratory和Kaggle Kernels支持
- PyTorch成熟稳定
- PyTorch支持CPU、GPU、TPU和并行处理
- PyTorch支持分布式训练:您可以在多台机器上的多个gpu上训练神经网络。
- PyTorch支持部署到生产环境:使用新的TorchScript和TorchServe特性,您可以轻松地将模型部署到包括云服务器在内的生产环境中。
- PyTorch开始支持移动端部署:虽然目前还处于试验阶段,但你现在可以将模型部署到iOS和Android设备上。
- PyTorch拥有一个庞大的生态系统和一组开源库:Torchvision、fastai和PyTorch Lightning等库扩展了功能,并支持自然语言处理(NLP)和计算机视觉等特定领域。
- PyTorch也有一个c++前端:虽然在本书中我将重点关注Python接口,PyTorch还支持前端c++接口。如果您需要构建高性能、低延迟应用程序,您可以使用与Python API相同的设计和架构,用c++编写它们。
- PyTorch本身支持ONNX格式:你可以很容易地将你的模型导出为ONNX格式,并在与ONNX兼容的平台、运行时或可视化工具中使用它们。
- PyTorch拥有一个庞大的开发者社区和用户论坛:https://pytorch.tips/discuss
2.小试牛刀
在实践中,您将在代码的开头导入所有必要的库。但是,在本例中,我们将在使用库时导入它们,这样您就可以看到每个任务需要哪些库。
首先,让我们选择一个我们想分类的图像。在这个例子中,我们会选择一杯新鲜、热的咖啡。使用以下代码下载咖啡映像到您的本地环境:
import urllib.request
url = 'https://upload.wikimedia.org/wikipedia/commons/4/45/A_small_cup_of_coffee.JPG'
fpath = 'coffee.jpg'
urllib.request.urlretrieve(url, fpath)
注意,代码使用了urllib
库的urlretrieve()
函数从web
获取图像。通过指定fpath
,将文件重命名为coffee.jpg
。
接下来,我们使用PIL读取我们的本地图像:
import matplotlib.pyplot as plt
from PIL import Image
img = Image.open('coffee.jpg')
plt.imshow(img)
注意,我们还没有使用PyTorch。接下来,我们将把图像传递给一个经过预处理的图像分类神经网络(NN),但在此之前,我们需要对图像进行预处理。预处理数据在机器学习中很常见,因为神经网络期望输入满足一定的要求。
在我们的示例中,图像数据是RGB 1600 × 1200像素JPEG格式
。我们需要应用一系列被称为transforms
的预处理步骤,将图像转换成适合NN
的格式。我们在以下代码中使用Torchvision
实现了这一点:
import torch
from torchvision import transforms
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])])
img_tensor = transform(img)
print(type(img_tensor), img_tensor.shape)
# <class 'torch.tensor'> torch.Size([3, 224, 224])
我们使用Compose()
变换来定义一系列对图像进行预处理的变换。首先,我们需要调整和裁剪图像以适应NN
。图像目前是PIL
格式,因为这是我们之前读取它的方式。但是我们的神经网络需要一个张量输入,所以我们把PIL
图像转换成一个张量。
张量是PyTorch中最基本的数据对象,我们将在下一章中详细介绍它们。你可以考虑像NumPy数组或数字数组这样的张量,它们有一些额外的特性。现在,我们只需要把图像转换成一个数字张量数组就可以了。将像素值的范围缩放到0到1之间。
我们再应用一个称为Normalize()
的变换,将[0,1]范围内的像素值进行标准化。平均值和标准差(std)的值是根据用来训练模型的数据预先计算的。对图像进行归一化可以提高分类器的精度。
最后,我们调用transform(img)
对图像应用所有的变换。正如你所看到的,img_tensor
是一个3 × 224 × 224
的torch.Tensor
,代表224 × 224像素的3通道图像的张量。
高效的机器学习是批量处理数据的,我们的模型期望得到一批数据。然而,我们只有一个图像,所以我们需要创建一个大小为1的批处理,如下所示的代码:
batch = img_tensor.unsqueeze(0)
print(batch.shape)
# out: torch.Size([1, 3, 224, 224])
我们使用PyTorch的unsqueeze()
函数向张量添加一个维度,并创建一个大小为1
的批。现在我们有一个大小为1 × 3 × 224 × 224
的张量。
现在我们的图像已经为分类器NN
准备好了!我们将使用一个名为AlexNet
的著名图像分类器。AlexNet
赢得了2012ImageNet
大型视觉识别挑战赛。使用Torchvision
加载这个模型很容易,如下所示的代码:
from torchvision import models
model = models.alexnet(pretrained=True)
我们将使用一个预先训练过的模型,所以我们不需要训练它。AlexNet模型已经用数百万张图像进行了预训练,在对图像进行分类方面做得很好。让我们传入我们的图像,看看它是如何做到的:
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)
# out(results will vary): cpu
model.eval()
model.to(device)
y = model(batch.to(device))
print(y.shape)
# out: torch.Size([1, 1000])
GPU加速是PyTorch的一个关键优势。在第一行中,我们使用PyTorch的cuda.is_available()
函数来查看我们的机器是否有GPU。我们只对一张图像进行分类,所以我们不需要GPU,但如果我们有大量的图像,那么GPU可能会帮助我们加快速度。
model.eval()
函数配置我们的AlexNet模型进行推断或预测(而不是训练)。模型的某些组成部分只在训练中使用,我们不想在这里使用它们。使用model.to(device)
和batch.to(device)
将我们的模型和输入数据发送给GPU
(如果可用的话),并执行model(batch.to(device))
运行我们的分类器。
输出y
由1,000
个输出组成。因为我们的批处理只包含一个图像,第一个维度是1,而类的数量是1000,每个类一个值。值越高,图像包含该类的可能性越大。下面的代码找到获胜的类:
y_max, index = torch.max(y,1)
print(index, y_max)
# out: tensor([967]) tensor([22.3059],
# grad_fn=<MaxBackward0>)
使用PyTorch
的max()
函数,我们看到index
为967
的值最大,为22.3059
,因此是优胜者。然而,我们不知道967
类代表什么。让我们用类名加载这个文件,并找出答案:
import urllib.request
url = "https://raw.githubusercontent.com/joe-papa/pytorch-book/main/files/imagenet_class_labels.txt"
fpath = 'imagenet_class_labels.txt'
urllib.request.urlretrieve(url, fpath)
with open('imagenet_class_labels.txt') as f:
classes = [line.strip() for line in f.readlines()]
print(classes[967])
# out: 967: 'espresso',
与前面一样,我们使用urlretrieve()
下载包含每个类描述的文本文件。然后,我们使用readlines()
读取文件,并创建一个包含类名的列表。当我们print(classes[967])
时,它告诉我们967类
是浓缩咖啡!
使用PyTorch
的softmax()
函数,我们可以将输出值转换为概率:
prob = torch.nn.functional.softmax(y, dim=1)[0] * 100
print(classes[index[0]], prob[index[0]].item())
#967: 'espresso', 87.85208892822266
要打印索引处的概率,我们使用PyTorch
的tensor.item()
方法。item()
方法经常被使用,它返回一个张量中包含的数值。实验结果表明,该模型对该图像的确定度为87.85%。
我们可以使用PyTorch
的sort()
函数对输出概率进行排序,看看前5个:
_, indices = torch.sort(y, descending=True)
for idx in indices[0][:5]:
print(classes[idx], prob[idx].item())
# out:
# 967: 'espresso', 87.85208892822266
# 968: 'cup', 7.28359317779541
# 504: 'coffee mug', 4.33521032333374
# 925: 'consomme', 0.36686763167381287
# 960: 'chocolate sauce, chocolate syrup',
# 0.09037172049283981
我们看到模型预测图像是87.85%的浓缩咖啡,7.28%的杯子和4.3%的咖啡杯的概率,但它似乎相当确信图像是浓缩咖啡。
3.完整代码
你可能觉得你现在就需要一杯浓缩咖啡。在那个例子中我们已经讨论了很多!完成所有事情的核心代码实际上要短得多。假设你已经下载了这些文件,你只需要运行以下代码来使用AlexNet对图像进行分类:
import torch
from torchvision import transforms, models
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])])
img_tensor = transform(img)
batch = img_tensor.unsqueeze(0)
model = models.alexnet(pretrained=True)
device = "cuda" if torch.cuda.is_available() else "cpu"
model.eval()
model.to(device)
y = model(batch.to(device))
prob = torch.nn.functional.softmax(y, dim=1)[0] * 100
_, indices = torch.sort(y, descending=True)
for idx in indices[0][:5]:
print(classes[idx], prob[idx].item())
这就是如何用PyTorch构建一个图像分类器。试着在模型中运行您自己的图像,看看它是如何对它们进行分类的。