首页 > 编程笔记 > Python笔记 阅读:36

卷积神经网络详解(附带PyTorch实例)

随着深度神经网络技术的成熟和发展,往往采用层数很深的神经网络来识别图像。

输入层和隐层之间是通过权值连接起来的,如果把输入层和隐层的神经元全部连接起来,那么权值数量就太多了。例如,对于一幅 1 000 ×1 000 像素大小的图像,输入层的神经元的个数就是每个像素点,个数为 1 000×1 000 个。假设和这个输入层连接的隐层的神经元个数为 1 000 000个,那么 W 的数量就是 1 000×1 000×1 000 000。

对于给定的输入图片,用一个卷积核处理这张图片,也就是说一个卷积核处理整张图,所以权重是一样的,这称为权值共享。

卷积层提取出特征,再进行组合形成更抽象的特征,最后形成对图片对象的描述特征。

卷积神经网络(Convolutional Neural Network,CNN)具有独特的结构,旨在模仿真实动物大脑的运作方式,而不是让每层中的每个神经元连接到下一层中的所有神经元(多层感知器),神经元以三维结构排列,以便考虑不同神经元之间的空间关系。

我们将从探索什么是卷积神经网络及其工作方式开始。然后,将使用 torchvision(一个包含各种数据集和与计算机视觉相关的辅助函数的库)加载 CIFAR10 数据集。然后,从头开始构建和训练 CNN。最后,将测试我们的模型。

卷积神经网络(CNN)获取输入图像并将其分类为任何输出类。每个图像都经过一系列不同的层——主要是卷积层、池化层和完全连接层。

卷积层用于从输入图像中提取特征。它是输入图像和内核(滤波器)之间的数学运算。使用不同的滤波器来提取不同种类的特征。

池化层用于减小任何图像的大小,同时保持最重要的特征。使用的最常见类型的池化层是最大池化和平均池化,它们分别从给定大小的滤波器(即 2×2、3×3 等)中获得最大值和平均值。

从加载一些数据开始。我们将使用 CIFAR-10 数据集。该数据集有 60 000 个 32px ×32px 的彩色图像,属于 10 个不同的类别(6 000个图像/类别)。数据集分为 50 000 个训练图像和 10 000 个测试图像。

从导入所需的库并定义一些变量开始,代码如下:
# Load in relevant libraries, and alias where appropriate
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms

# Define relevant variables for the ML task
batch_size = 64
num_classes = 10
learning_rate = 0.001
num_epochs = 20

# Device will determine whether to run the training on GPU or CPU.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
device 变量将决定是在 GPU 上还是在 CPU 上运行训练。

为了加载数据集,将使用 torchvision 中的内置数据集。它提供了下载数据集的能力,还可以应用想要的任何转换。

先来看代码:
# Use transforms.compose method to reformat images for modeling,
# and save to variable all_transforms for later use
all_transforms = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2023, 0.1994, 0.2010])
])

# Create Training dataset
train_dataset = torchvision.datasets.CIFAR10(root='./data',
                                             train=True,
                                             transform=all_transforms,
                                             download=True)

# Create Testing dataset
test_dataset = torchvision.datasets.CIFAR10(root='./data',
                                            train=False,
                                            transform=all_transforms,
                                            download=True)

# Instantiate loader objects to facilitate processing
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=True)
剖析一下这段代码:
1) 从写一些转换开始。调整图像的大小,将其转换为张量,并通过使用输入图像中每个频带的平均值和标准差对其进行归一化。

2) 加载数据集,包括训练和测试。将 download 参数设置为 True,以便在尚未下载的情况下进行下载。

3) 一次将整个数据集加载到内存中不是一个好的做法,严重的话,可能会导致计算机停机。这就是使用数据加载器的原因,它允许通过批量加载数据来迭代数据集。

4) 创建两个数据加载程序(用于训练/测试),并设置批次大小,将 shuffle 设置为 True,以便每个类的图像都包含在一个批次中。

在深入研究代码之前,了解一下如何在 PyTorch 中定义神经网络。
1) 创建一个新类,该类扩展了 PyTorch 中的 nn.Module 类。当创建神经网络时,这是必要的,因为它提供了一系列有用的方法。

2) 必须定义神经网络中的层,这是在类的 __init__() 方法中完成的。只是简单地命名层,然后将它们分配给想要的适当层,如卷积层、池化层、全连接层等。

3) 最后要做的事情是在类中定义一个 forward() 方法。此方法的目的是定义输入数据通过各个层的顺序。

现在,深入了解代码:
# Creating a CNN class
class ConvNeuralNet(nn.Module):
    # Determine what layers and their order in CNN object
    def __init__(self, num_classes):
        super(ConvNeuralNet, self).__init__()
        self.conv_layer1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3)
        self.conv_layer2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3)
        self.max_pool1 = nn.MaxPool2d(kernel_size=2, stride=2)

        self.conv_layer3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)
        self.conv_layer4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3)
        self.max_pool2 = nn.MaxPool2d(kernel_size=2, stride=2)

        self.fc1 = nn.Linear(1600, 128)
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Linear(128, num_classes)

    # Progresses data across layers
    def forward(self, x):
        out = self.conv_layer1(x)
        out = self.conv_layer2(out)
        out = self.max_pool1(out)

        out = self.conv_layer3(out)
        out = self.conv_layer4(out)
        out = self.max_pool2(out)

        out = out.reshape(out.size(0), -1)

        out = self.fc1(out)
        out = self.relu1(out)
        out = self.fc2(out)

        return out
首先创建一个继承 nn.Module 类的类,然后分别在 __init__() 和 forward() 中定义层及其执行顺序。

这里需要注意如下事项:
现在为训练目的设置一些超参数,代码如下:
model = ConvNeuralNet(num_classes)

# Set Loss function with criterion
criterion = nn.CrossEntropyLoss()

# Set optimizer with optimizer
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=0.005, momentum=0.9)

total_step = len(train_loader)
首先,用类的数量初始化模型;然后,分别选择交叉熵和 SGD(随机梯度下降)作为损失函数和优化器。这些有不同的选择,但在实验时可以获得最大的准确性。这里还定义了变量 total_step,以使通过各种批次的迭代更容易。

下面开始训练模型,代码如下:
# We use the pre-defined number of epochs to determine how many iterations to train the network on
for epoch in range(num_epochs):
    # Load in the data in batches using the train_loader object
    for i, (images, labels) in enumerate(train_loader):
        # Move tensors to the configured device
        images = images.to(device)
        labels = labels.to(device)

        # Forward pass
        outputs = model(images)
        loss = criterion(outputs, labels)

        # Backward and optimize
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, loss.item()))
看看代码的作用:
输出如下:
Epoch [1/20], Loss: 1.5199
Epoch [2/20], Loss: 1.7072
Epoch [3/20], Loss: 1.7464
Epoch [4/20], Loss: 0.9564
Epoch [5/20], Loss: 1.6862
Epoch [6/20], Loss: 1.1571
Epoch [7/20], Loss: 0.9546
Epoch [8/20], Loss: 1.1339
Epoch [9/20], Loss: 1.4111
Epoch [10/20], Loss: 1.3795
Epoch [11/20], Loss: 0.3771
Epoch [12/20], Loss: 0.6321
Epoch [13/20], Loss: 1.0079
Epoch [14/20], Loss: 0.8751
Epoch [15/20], Loss: 0.5534
Epoch [16/20], Loss: 0.5643
Epoch [17/20], Loss: 0.2529
Epoch [18/20], Loss: 0.4387
Epoch [19/20], Loss: 0.3719
Epoch [20/20], Loss: 0.3089
随着回合的增多,损失略有减少,这是一个好迹象。在最后是波动的,这可能意味着模型过拟合或 batch_size 很小。这时,不得不进行测试以了解发生了什么。

下面测试模型。测试代码与训练没有太大区别,除了计算梯度,没有更新任何权重,代码如下:
with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in train_loader:
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

    print('Accuracy of the network on the {} train images: {} %'.format(500000, 100 * correct / total))
这里将代码封装在 torch.no_grad() 中,因为不需要计算任何梯度。然后,使用模型预测每个批次,并计算它正确预测的数量。得到的最终结果准确率约为 83%:

Accuracy of the network on the 50000 train images: 83.36 %

相关文章