原文:towardsdatascience.com/pytorch-introduction-training-a-computer-vision-algorithm-c17ed96e64d5

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c489308a8a2e715545900d3b5ced549f.png

自动驾驶汽车主要依靠计算机视觉算法——由微软设计师中的 AI 生成的图像

PyTorch目前是深度学习领域中最热门的库之一。自ChatGPT发布和深度学习进入主流新闻头条以来,该库获得了显著的关注。

由于其高效训练深度学习模型(具有 GPU 就绪功能)的能力,它已成为机器学习工程师和数据科学家在训练复杂神经网络算法时的最佳拍档。

到目前为止,在这个PyTorch系列中,我们已经涵盖了几个基础,为我们提供了从零开始使用这个库的基础。例如:

在这篇博客文章中,我们终于要拿出杀手锏,训练我们的第一个计算机视觉算法。同时,我们还将了解卷积神经网络(CNNs),这是一组专为训练计算机视觉模型而设计的著名架构。虽然我们将使用一个简单的数据集(使其任何人都可以在自己的计算机上运行此代码),但我们看到的原理可以应用于其他图像分类算法。

让我们开始吧!


加载数据和准备数据

为了让每个人都能在没有 GPU 的情况下运行这个流程,我们将保持简单,并使用无处不在的MNIST数据集。

此数据集包含手写数字,可以通过运行以下代码在torchvision中找到:

import torchvision
from torchvision.transforms import transforms

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

# Load training and test datasets
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)

注意:为了可视化的目的,我会在代码中添加所需的导入,但建议你始终将它们放在代码的顶部

torchvision.datasets还有一个很酷的功能:我们可以立即添加一个transform,它将直接应用于图像。在我们的情况下,我们的transform管道将:

  • 将我们的数据转换为张量;

  • 标准化数据;

尽管对于这个只有单色通道的灰度数据集(因为它只包含一个颜色通道)来说不是非常重要,但归一化将肯定有助于梯度下降过程,并在我们模型的训练过程中防止梯度爆炸或消失。

在我们的数据集在内存中后,是时候看看我们的张量包含什么了!让我们看看第一个:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6a450ce2b3c2bb507f9cdbeb8264cdc2.png

MNIST 数据集的第一张张量 – 图像由作者提供

哎呀… 好吧,难以可视化!让我们看看张量的形状:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b4749590e22c6e339227413998879e56.png

我们有一个28 x 28的单通道张量。也许将我们的张量以图像格式可视化会更好!让我们这么做:

import matplotlib.pyplot as plt
import numpy as np

image, label = train_dataset[0]
image = np.transpose(image, (1, 2, 0))
plt.figure(figsize=(4, 4))
plt.imshow(image)
plt.title(label);

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ac086d4a2ceb7c924857996988eb42ca.png

MNIST 数据集中的数字 5 – 图像由作者提供

第一行有数字 5!让我们看看数据集中的另一个张量:

image, label = train_dataset[120]
image = np.transpose(image, (1, 2, 0))
plt.figure(figsize=(4, 4))
plt.imshow(image)
plt.title(label);

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/23913f46ae8c47d1e87f8ab8e2077982.png

MNIST 数据集中的数字 2 – 图像由作者提供

数字 2!

现在,我们的目标是使用正确的数字对这些数字进行分类。基本上,我们的算法将必须根据这些张量检查如何对图像上的数字进行分类。

如同往常,在pytorch中,我们需要使用批次来将数据喂给我们的神经网络 – 如果需要,torch有它!

import torch

train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=30, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=30)

我们在每个批次中使用 30 个示例。这将帮助我们训练我们的神经网络。

在我们的数据就绪后,让我们创建我们的模型!


创建一个标准的神经网络模型

在训练我们的模型之前,让我们看看我们的模型需要分类的类别:

class_names = train_dataset.classes
class_names

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c415dfb5cec12072e499033dad9f802a.png

MNIST 数据集中的类别

因此,我们的第一个算法将非常简单 – 一个带有 ReLU 激活的单隐藏层。如果这句话听起来很奇怪,请考虑访问本系列的其他博客文章,这些文章在引言中有链接!

class MNIST_NN(nn.Module):
    def __init__(self, input_shape: int, 
                 hidden_units: int, 
                 output_shape: int):
        super().__init__()

        # Create a hidden layer with non-linearities
        self.layer_stack = nn.Sequential(
            nn.Flatten(),
            nn.Linear(in_features=input_shape, out_features=hidden_units),
            nn.ReLU(),
            nn.Linear(in_features=hidden_units, out_features=output_shape),
            nn.ReLU()
        )

    def forward(self, x: torch.Tensor):
        return self.layer_stack(x)

我们将在隐藏层中使用 150 个单元:

model_non_linear = MNIST_NN(input_shape=28*28,
 hidden_units=150,
 output_shape=len(class_names))

作为损失函数,我们将使用交叉熵(适用于分类问题),并且我们将使用随机梯度下降优化器(学习率为 0.01):

loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_1.parameters(),
 lr=0.01)

接下来是定义我们的traintest步骤:

def train_step(model: torch.nn.Module,
               data_loader: torch.utils.data.DataLoader,
               loss_fn: torch.nn.Module,
               optimizer: torch.optim.Optimizer,
               accuracy_fn):

    # Zero loss and acc
    train_loss, train_acc = 0, 0

    model.train()

    for batch, (X, y) in enumerate(data_loader):

        y_pred = model(X)
        loss = loss_fn(y_pred, y)
        train_loss += loss.item()
        train_acc += accuracy_fn(y_true=y,
                                 y_pred=y_pred.argmax(dim=1))

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    train_loss /= len(data_loader)
    train_acc /= len(data_loader)
    print(f"Train loss: {train_loss:.2f} | Train accuracy: {train_acc:.2f}%")

    return train_loss

def test_step(data_loader: torch.utils.data.DataLoader,
              model: torch.nn.Module,
              loss_fn: torch.nn.Module,
              accuracy_fn):
    test_loss, test_acc = 0, 0
    model.eval() 

    with torch.no_grad(): 
        for X, y in data_loader:
            test_pred = model(X)
            test_loss += loss_fn(test_pred, y).item() 
            test_acc += accuracy_fn(y_true=y,
                                    y_pred=test_pred.argmax(dim=1))

        test_loss /= len(data_loader)
        test_acc /= len(data_loader)
        print(f"Test loss: {test_loss:.5f} | Test accuracy: {test_acc:.2f}%n")

        return test_loss

这些步骤已经在我们的训练线性模型博客文章中介绍。如果您觉得难以理解,请随时重新访问它!

简而言之,我们正在创建训练和步骤过程,这将帮助我们的神经网络执行反向传播。

好的,一切就绪。现在是时候训练我们的前馈神经网络模型了:

loss_hist = {}

loss_hist['train'] = {}
loss_hist['test'] = {}

epochs = 10
for epoch in tqdm(range(epochs)):
    print(f"Epoch: {epoch}n---------")
    train_loss = train_step(data_loader=train_loader,
        model=model_non_linear,
        loss_fn=loss_function,
        optimizer=optimizer,
        accuracy_fn=accuracy_fn
    )

    loss_hist['train'][epoch] = train_loss

    test_loss = test_step(data_loader=test_loader,
        model=model_non_linear,
        loss_fn=loss_function,
        accuracy_fn=accuracy_fn
    )

    loss_hist['test'][epoch] = test_loss

我们将训练它 10 个周期。记住,一个训练周期是对数据的完整遍历,所以我们的神经网络将看到每个示例 10 次。过了一段时间(取决于您的系统资源),神经网络应该完成训练过程:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/aa64146053cd59bf27ccf54052a9d389.png

训练和测试损失演变 - 作者提供)

我们可能还可以继续训练我们的神经网络一段时间,因为它似乎在超过 10 个 epoch 之后,损失仍然有一些空间可以进一步改进。

在准确率方面,让我们看看最后一个 epoch 的准确率和损失状况:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/fe7c2d129740c8962f14587e90c32b0f.png

上一个 epoch 的训练和测试准确率 - 作者提供

太棒了,大约 87%的准确率,我们的模型没有显示出过拟合的迹象!

最后,让我们通过算法传递一些手写图像,看看它在数字分类中的表现如何。为此,我们将使用自定义的show_predict_digit函数:

def show_predict_digit(model, dataset, index):
    image_tensor = dataset.data[index].unsqueeze(0).unsqueeze(0).to(torch.float32)
    plt.figure(figsize=(4, 4))
    plt.imshow(image_tensor.squeeze(0).squeeze(0))

    with torch.no_grad():
        output = model(image_tensor)

    return torch.argmax(output)

从我们的测试集中索引为 0 的数字开始:

show_predict_digit(model_non_linear,
 test_dataset,
 0)

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/be3bc0defbe7cc0a570b6abade317d86.png

使用我们的模型预测数字“7”的图像 - 作者提供

在图像上方,你看到“tensor(7)”吗?这是我们的神经网络输出的结果。在理想的世界里,这应该每次都匹配我们屏幕上看到的数字。

在这种情况下,我们正确地识别了这个数字!我们的神经网络预测的是数字 7。让我们看看另一个例子:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/faab0c57f1beb37317cd6c1f21633258.png

使用我们的模型预测数字“0”的图像 - 作者提供

太糟糕了!🙁 如我们的图像所强调的,我们的神经网络错误地将数字“9”分类为“0”。当然,算法中还有改进的空间!

预期前馈神经网络会表现出这种行为,尤其是在那些维度很大的问题中(例如依赖于像素级特征的图像分类问题)。

在本博客文章的下一部分,我们将尝试通过拟合我们的第一个卷积神经网络来解决这个问题的,这是一种专门针对处理计算机视觉问题而设计的神经网络类型


训练卷积神经网络

现在我们将使用pytorch构建一个新的神经网络模型——在这种情况下,我们将使用卷积神经网络(CNN)。

这些类型的神经网络架构是为视觉算法和与高维数据一起使用而量身定制的。当涉及到图像数据时,它们关注图像中的特定特征,其架构在某种程度上受到人眼的启发。

CNN 最著名的操作之一是池化、采样和展平操作 – 它们帮助我们的算法更快地收敛,并减少错误。解释卷积神经网络超出了本文的范围,但你可以查看这篇精彩的TDS 博客以及这个优秀的可视化器,了解数据是如何通过 CNN 层的。

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, 
                               kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(64 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, 10)
        self.dropout = nn.Dropout(0.5) 
        self.batch_norm1 = nn.BatchNorm2d(32)
        self.batch_norm2 = nn.BatchNorm2d(64)

    def forward(self, x):
        x = self.pool(F.relu(self.batch_norm1(self.conv1(x))))
        x = self.pool(F.relu(self.batch_norm2(self.conv2(x))))
        x = torch.flatten(x, 1)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

在我们的卷积神经网络中,我们有以下架构:

  • 一个具有 3x3 内核和 32 个输出通道的归一化卷积层。

  • 一个带有 ReLU 激活的池化操作,处理在self.pool(F.relu(self.batch_norm1(self.conv1(x))))

  • 一个具有 3x3 内核的第二个归一化卷积层。

  • 一个带有 ReLU 激活的池化操作,处理在self.pool(F.relu(self.batch_norm2(self.conv2(x))))

  • 一个展平层 - torch.flatten(x,1)

  • 最后,两个带有中间 dropout 的全连接层,以防止过拟合。

pytorch中训练 CNN 与训练典型的前馈神经网络类似:

net = SimpleCNN()

optimizer = torch.optim.SGD(params=net.parameters(),
                            lr=0.05)

在这个例子中,我们将使用带有0.05学习率的随机梯度下降。我们可以依赖之前定义的代码来训练网络:

loss_hist_cnn = {}

loss_hist_cnn['train'] = {}
loss_hist_cnn['test'] = {}

epochs = 10
for epoch in tqdm(range(epochs)):
    print(f"Epoch: {epoch}n---------")
    loss_cnn = train_step(data_loader=train_loader,
        model=net,
        loss_fn=loss_function,
        optimizer=optimizer,
        accuracy_fn=accuracy_fn
    )

    loss_hist_cnn['train'][epoch] = loss_cnn

    loss_cnn_test = test_step(data_loader=test_loader,
        model=net,
        loss_fn=loss_function,
        accuracy_fn=accuracy_fn
    )

    loss_hist_cnn['test'][epoch] = loss_cnn_test

在我们的第 10 个 epoch 后,以下是我们的准确率和损失结果:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8f010bbde055cbccbe4dad158561f7db.png

CNN 最后一个 epoch 的训练和测试准确率 - 图片由作者提供

太酷了!看起来我们大幅提高了模型准确率,提高了 10 多个百分点。这明显优于简单的非线性架构。

让我们通过绘制训练和测试损失曲线来确认这一点:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e351c1ba61e2a23f2621dce4fbf7cd61.png

训练和测试损失演变 - 图片由作者提供

我们确认损失低于第一次实验。但是,我们还不庆祝!现在是时候进行石蕊测试了:让我们看看我们的模型是如何分类第一个分类器预测错误的*“9”*数字的:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d3f3ac13cc2d1420e17e7cb14f3f0460.png

使用我们的 CNN 模型预测数字"9" – 图片由作者提供

太酷了!我们现在能够正确使用 CNN 预测这个数字。为了总结并出于好奇,让我们看看另一个随机示例:

show_predict_digit_cnn(net,
                  test_dataset,
                  15)

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3424b0fbccee39683ab5c0ceb054cc55.png

使用我们的 CNN 模型预测数字"5" – 图片由作者提供

另一个例子正确!你能检查更多数字,看看是否能找到 CNN 出错罕见的情况吗?


这篇文章就到这里了!感谢您阅读到最后,希望这些例子帮助您理解如何使用 pytorch 训练卷积神经网络。这也是我们第一次在框架中处理这类层,看到 torch 像乐高积木一样构建起来真的很酷!一旦您理解了层的工作原理,混合和匹配它们将在构建深度学习模型时赋予您非凡的能力。

总结一下,以下是我们在本博客文章中完成的内容:

  • 训练了我们第一个计算机视觉模型

  • 在 MNIST 数据集上拟合了一个典型的前馈神经网络

  • 在 MNIST 数据集上拟合了一个 CNN

  • 简要介绍了 CNN 的概念,并了解了卷积、展平和池化层。

希望您喜欢这篇文章,并期待在下一篇 PyTorch 文章中与您相见!

您可以从本系列的第一个 PyTorch 博客文章开始了解 [[这里](https://towardsdatascience.com/pytorch-introduction-building-your-first-linear-model-d868a8681a41)](https://towardsdatascience.com/pytorch-introduction-tensors-and-tensor-calculations-412ff818bd5b?sk=2cf4d44549664fc647baa3455e9d78e8) 和这里。我还推荐您访问 PyTorch 从零到精通课程,这是一个令人惊叹的免费资源,启发了本文背后的方法论,或者通过我们的 DareData 的 pytorch 学习小组,这里可以找到。

欢迎访问我新创建的 YouTube 频道 – 数据之旅,在那里我将在接下来的几个月内添加有关数据科学和机器学习的内容,并期待在那里见到您!

本文中使用的数据集受 MIT 许可协议保护。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐