卷积神经网络详解(附带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 个测试图像。
从导入所需的库并定义一些变量开始,代码如下:
为了加载数据集,将使用 torchvision 中的内置数据集。它提供了下载数据集的能力,还可以应用想要的任何转换。
先来看代码:
1) 从写一些转换开始。调整图像的大小,将其转换为张量,并通过使用输入图像中每个频带的平均值和标准差对其进行归一化。
2) 加载数据集,包括训练和测试。将 download 参数设置为 True,以便在尚未下载的情况下进行下载。
3) 一次将整个数据集加载到内存中不是一个好的做法,严重的话,可能会导致计算机停机。这就是使用数据加载器的原因,它允许通过批量加载数据来迭代数据集。
4) 创建两个数据加载程序(用于训练/测试),并设置批次大小,将 shuffle 设置为 True,以便每个类的图像都包含在一个批次中。
在深入研究代码之前,了解一下如何在 PyTorch 中定义神经网络。
1) 创建一个新类,该类扩展了 PyTorch 中的 nn.Module 类。当创建神经网络时,这是必要的,因为它提供了一系列有用的方法。
2) 必须定义神经网络中的层,这是在类的 __init__() 方法中完成的。只是简单地命名层,然后将它们分配给想要的适当层,如卷积层、池化层、全连接层等。
3) 最后要做的事情是在类中定义一个 forward() 方法。此方法的目的是定义输入数据通过各个层的顺序。
现在,深入了解代码:
这里需要注意如下事项:
现在为训练目的设置一些超参数,代码如下:
下面开始训练模型,代码如下:
输出如下:
下面测试模型。测试代码与训练没有太大区别,除了计算梯度,没有更新任何权重,代码如下:
输入层和隐层之间是通过权值连接起来的,如果把输入层和隐层的神经元全部连接起来,那么权值数量就太多了。例如,对于一幅 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() 中定义层及其执行顺序。
这里需要注意如下事项:
- nn.Conv2d() 用于定义卷积层。这里定义了它们接收的通道,以及它们应该返回多少通道及内核大小。这里从 3 个通道开始,因为使用 RGB 图像;
- nn.MaxPool2d() 是一个最大池化层,只需要内核大小和步长;
- nn.Lineral() 是完全连接的层,nn.ReLU() 是使用的激活函数;
- 在 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()))看看代码的作用:
- 首先迭代回合的数量,然后迭代训练数据中的批次;
- 根据使用的设备(即 GPU 或 CPU)转换图像和标签;
- 在前向通行中,使用模型进行预测,并根据这些预测和实际标签计算损失;
- 进行反向传递,实际更新权重以改进模型;
- 在每次更新之前,使用 optimizer.zero_grad() 函数将梯度设置为零;
- 使用 loss.backward() 函数计算新的梯度;
- 使用 optimizer.step() 函数更新权重。
输出如下:
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 %