卷积神经网络(CNN)基础用法学习:识别CIFAR-10数据集中的图像

目录

1. 前言

2. 卷积神经网络基本概念

2.1 CNN的核心组件

2.2 CNN的工作原理

3. CNN实例

3.1 导入必要的库

3.2 数据预处理

3.3 定义CNN模型

3.4 训练模型

3.5 测试模型

4. 完整代码(方便运行调试)

5. 总结


1. 前言

在人工智能和深度学习领域,卷积神经网络(CNN,Convolutional Neural Networks)已经成为图像识别和计算机视觉任务的核心技术。从人脸识别到自动驾驶,CNN的应用无处不在。此次博客将深入浅出地介绍CNN的基本原理,并通过一个完整的实例来学习CNN模型。

2. 卷积神经网络基本概念

卷积神经网络是一种专门用于处理具有网格结构数据(如图像)的深度学习模型。它的核心思想是通过卷积操作自动提取数据中的特征,从而实现对图像的分类、识别和分割等任务。

2.1 CNN的核心组件

  1. 卷积层(Convolutional Layer)
    卷积层是CNN的核心,它通过卷积核(filter)在输入数据上滑动,提取局部特征。卷积操作可以捕捉图像中的边缘、纹理等信息。

  2. 池化层(Pooling Layer)
    池化层用于降低数据的维度,减少计算量,同时保留重要特征。常见的池化方法包括最大池化(Max Pooling)和平均池化(Average Pooling)。

  3. 激活函数(Activation Function)
    激活函数为网络引入非线性,使得模型能够学习复杂的特征。常用的激活函数包括ReLU、Sigmoid和Tanh。

  4. 全连接层(Fully Connected Layer)
    全连接层将卷积层和池化层提取的特征映射到输出空间,用于最终的分类或回归任务。

2.2 CNN的工作原理

CNN通过多层卷积和池化操作逐步提取图像的特征。每一层的卷积核学习不同的特征,从低级特征(如边缘)到高级特征(如形状和纹理)。通过逐层特征提取,CNN能够高效地处理图像数据。

3. CNN实例

由于手写数字识别太经典且在之前文章讲过,这里换一个实例进行学习。

接下来,我们将通过一个完整的实例来实现一个CNN模型,用于识别CIFAR-10数据集中的图像。

3.1 导入必要的库

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

3.2 数据预处理

# 定义数据转换
transform = transforms.Compose([
    transforms.ToTensor(),  # 将图像转换为Tensor
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 归一化处理
])

# 加载CIFAR-10数据集
train_dataset = torchvision.datasets.CIFAR10(root='./data', 
                                            train=True, 
                                            transform=transform, 
                                            download=True)

test_dataset = torchvision.datasets.CIFAR10(root='./data', 
                                           train=False, 
                                           transform=transform)

# 创建数据加载器
train_loader = DataLoader(dataset=train_dataset, 
                          batch_size=64, 
                          shuffle=True)

test_loader = DataLoader(dataset=test_dataset, 
                         batch_size=64, 
                         shuffle=False)

下载速度慢建议去官网下载模型,CIFAR-10数据官网 ,下载好后去data文件夹解压缩。

3.3 定义CNN模型

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第一个卷积块
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1)
        self.relu1 = nn.ReLU()
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # 第二个卷积块
        self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1)
        self.relu2 = nn.ReLU()
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # 第三个卷积块
        self.conv3 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1)
        self.relu3 = nn.ReLU()
        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # 全连接层
        self.fc = nn.Linear(128 * 4 * 4, 10)  # 输入特征维度为128*4*4,输出为10个类别
    
    def forward(self, x):
        # 第一个卷积块
        x = self.conv1(x)
        x = self.relu1(x)
        x = self.pool1(x)
        
        # 第二个卷积块
        x = self.conv2(x)
        x = self.relu2(x)
        x = self.pool2(x)
        
        # 第三个卷积块
        x = self.conv3(x)
        x = self.relu3(x)
        x = self.pool3(x)
        
        # 展平后输入全连接层
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

当你定义一个继承自nn.Module的类时,你需要重写forward方法。这个方法定义了网络的前向传播逻辑,即输入数据如何通过网络的各个层来生成输出。 

x = x.view(x.size(0), -1)这行代码的作用是将张量 x 展平为一个二维张量,其中第一个维度保持不变,第二个维度自动计算。

nn.Conv2d函数解释为:

1. in_channels

  • 含义:输入数据的通道数。

  • 作用:指定输入特征图的深度(即通道数)。例如,对于 RGB 图像,通道数为3。

  • 示例in_channels=3 表示输入数据是3通道的(如 RGB 图像)。

2. out_channels

  • 含义:输出数据的通道数。

  • 作用:指定卷积层输出的特征图数量。每个特征图由一个卷积核生成。

  • 示例out_channels=32 表示卷积层输出32个特征图。

3. kernel_size

  • 含义:卷积核的大小。

  • 作用:指定卷积核的高度和宽度。通常是奇数(如3x3、5x5),以便有一个中心点。

  • 示例kernel_size=3 表示卷积核的大小为3x3。

4. stride

  • 含义:卷积核在输入数据上滑动的步长。

  • 作用:控制卷积核每次移动的像素数。较大的步长会减少输出特征图的尺寸。

  • 示例stride=1 表示卷积核每次移动1个像素。

5. padding

  • 含义:在输入数据周围添加的零填充(zero padding)的宽度。

  • 作用:防止输出特征图的尺寸过小,或保持输出特征图的尺寸与输入一致。

  • 示例padding=1 表示在输入数据的上下左右各添加1行/列的零填充。

3.4 训练模型

# 初始化模型
model = CNN()

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 10

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    
    for images, labels in train_loader:
        # 前向传播
        outputs = model(images)
        loss = criterion(outputs, labels)
        
        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}')

(1)nn.CrossEntropyLoss() 实际上是将 LogSoftmaxNegative Log Likelihood Loss 合并在一起:

  1. LogSoftmax:将模型的输出转换为概率分布。

  2. Negative Log Likelihood:计算预测分布和真实分布之间的损失。

(2)PyTorch在内部对nn.Module类的__call__方法进行了重写。当你调用model(images)时,实际上是在调用model.__call__(images)。这个__call__方法会自动调用forward方法,并处理一些额外的任务,比如自动求导的设置和梯度的追踪。 

(3)Adam 为每个参数维护一个独立的学习率,能够自动调整学习率的大小。 

(4)反向传播函数用法

  • optimizer.zero_grad():清零梯度。

  • loss.backward():计算梯度。

  • optimizer.step():更新参数。

(5)神经网络的参数更新是在整个批次的梯度计算完成后进行的。具体步骤如下:

  1. 前向传播:将整个批次的64个图像输入网络,计算每个图像的输出。

  2. 计算损失:根据每个图像的输出和真实标签,计算整个批次的平均损失。

  3. 反向传播:计算损失对网络参数的梯度。

  4. 参数更新:根据计算出的梯度更新网络参数。

3.5 测试模型

# 测试模型
model.eval()
correct = 0
total = 0

with torch.no_grad():
    for images, labels in test_loader:
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Test Accuracy: {100 * correct / total:.2f}%')

详细解析如下: 

1. model.eval()

这行代码将模型设置为评估模式。在评估模式下,模型的行为会有所不同:

  • Dropout层:在训练时,Dropout层会随机失活一些神经元以防止过拟合,但在评估时,所有神经元都会被激活。

  • BatchNorm层:在训练时,BatchNorm层会使用当前批次的均值和方差进行归一化,而在评估时,它会使用整个训练集的均值和方差。

2. 初始化变量

correct = 0
total = 0
  • correct:用于记录预测正确的样本数。

  • total:用于记录总样本数。

3. with torch.no_grad():

这行代码进入一个上下文管理器,禁用梯度计算。在测试阶段,不需要计算梯度,因此可以使用 torch.no_grad() 来节省内存和计算资源。

4. 遍历测试集

for images, labels in test_loader:

这行代码遍历测试集中的每个批次的数据。images 是输入图像,labels 是对应的真实标签。

5. 前向传播

outputs = model(images)

这行代码将输入图像传递给模型,进行前向传播,得到模型的输出。

6. 获取预测结果

_, predicted = torch.max(outputs.data, 1)
  • outputs.data 是模型输出的原始分数。

  • torch.max(outputs.data, 1) 返回每个样本的最大值及其索引。索引即为预测的类别。

7. 更新统计变量

total += labels.size(0)
correct += (predicted == labels).sum().item()
  • labels.size(0) 是当前批次的样本数。

  • predicted == labels 比较预测结果和真实标签,返回一个布尔张量。

  • .sum().item() 计算预测正确的样本数,并将其转换为Python整数。

8. 计算准确率

print(f'Test Accuracy: {100 * correct / total:.2f}%')

这行代码计算并打印测试集上的准确率。

4. 完整代码(方便运行调试)

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

# 定义数据转换
transform = transforms.Compose([
    transforms.ToTensor(),  # 将图像转换为Tensor
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 归一化处理
])

# 加载CIFAR-10数据集
train_dataset = torchvision.datasets.CIFAR10(root='./data',
                                             train=True,
                                             transform=transform,
                                             download=False)

test_dataset = torchvision.datasets.CIFAR10(root='./data',
                                            train=False,
                                            transform=transform)

# 创建数据加载器
train_loader = DataLoader(dataset=train_dataset,
                          batch_size=64,
                          shuffle=True)

test_loader = DataLoader(dataset=test_dataset,
                         batch_size=64,
                         shuffle=False)


class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 第一个卷积块
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1)
        self.relu1 = nn.ReLU()
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)

        # 第二个卷积块
        self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1)
        self.relu2 = nn.ReLU()
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)

        # 第三个卷积块
        self.conv3 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1)
        self.relu3 = nn.ReLU()
        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)

        # 全连接层
        self.fc = nn.Linear(128 * 4 * 4, 10)  # 输入特征维度为128*4*4,输出为10个类别

    def forward(self, x):
        # 第一个卷积块
        x = self.conv1(x)
        x = self.relu1(x)
        x = self.pool1(x)

        # 第二个卷积块
        x = self.conv2(x)
        x = self.relu2(x)
        x = self.pool2(x)

        # 第三个卷积块
        x = self.conv3(x)
        x = self.relu3(x)
        x = self.pool3(x)

        # 展平后输入全连接层
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x


# 初始化模型
model = CNN()

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 10

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0

    for images, labels in train_loader:
        # 前向传播
        outputs = model(images)
        loss = criterion(outputs, labels)

        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

    print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {running_loss / len(train_loader):.4f}')

# 测试模型
model.eval()
correct = 0
total = 0

with torch.no_grad():
    for images, labels in test_loader:
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Test Accuracy: {100 * correct / total:.2f}%')

5. 总结

卷积神经网络(CNN)是一种强大的深度学习模型,特别适合处理图像数据。通过卷积层和池化层的组合,CNN能够高效地提取图像中的特征。本文通过一个CIFAR-10图像分类的实例,详细介绍了CNN的基本原理和实现方法。

未来,随着硬件性能的提升和算法的改进,CNN将在更多领域发挥重要作用,如自动驾驶、医疗影像分析和自然语言处理等。希望本文能帮助读者对CNN有一个全面的理解,并激发进一步探索的兴趣。我是橙色小博,关注我,在人工智能领域学习进步。