第十二站:卷积神经网络(CNN):附代码详解,一文看懂CNN

1. 卷积神经网络(CNN)概述

卷积神经网络(CNN)是一种前馈神经网络,主要用于处理具有网格结构的数据(如图像)。CNN 在深度学习的图像分类和物体识别等任务中取得了突破性的成果。

CNN 的基本结构:
  1. 卷积层(Convolutional Layer)

    • 卷积层是 CNN 中的核心部分,主要用于从输入数据中提取特征。
    • 卷积操作使用多个 卷积核(滤波器),通过滑动窗口的方式与输入图像进行卷积运算,生成特征图(feature map)。
    • 卷积层能够自动学习图像中的局部特征,如边缘、纹理、角点等。
  2. 激活函数(Activation Function)

    • 激活函数常用于卷积层之后,最常用的激活函数是 ReLU(Rectified Linear Unit)。
    • ReLU 函数可以将所有负数置为零,保持正数不变,从而增加网络的非线性特性。
  3. 池化层(Pooling Layer)

    • 池化层用于对特征图进行下采样,减小特征图的尺寸,同时保留最重要的信息。
    • 最常见的池化操作是 最大池化(Max Pooling),它选择池化窗口内的最大值。
  4. 全连接层(Fully Connected Layer)

    • 全连接层将卷积和池化层提取到的特征进行进一步的组合,最终输出分类结果。
    • 在图像分类任务中,最后一层通常是一个 softmax 层,用于输出每个类别的概率。
  5. Dropout 层

    • Dropout 层可以帮助防止过拟合,在训练过程中随机丢弃一些神经元。
CNN 的工作原理:
  1. 特征提取:卷积层负责从输入图像中提取低级特征(如边缘、纹理等),池化层则帮助降维和减少计算量。
  2. 特征组合:全连接层负责将这些特征组合成高层次的抽象,进行分类或回归任务。

2. CNN 的代码实现(PyTorch 示例)

我们通过一个简单的图像分类任务来实现 CNN。这里以 CIFAR-10 数据集(一个包含 10 类物体的图像分类数据集)为例。

代码示例:使用 PyTorch 构建 CNN
import torch  # 导入 PyTorch 库
import torch.nn as nn  # 导入神经网络模块
import torch.optim as optim  # 导入优化器模块
import torchvision  # 导入 torchvision,用于数据加载和预处理
import torchvision.transforms as transforms  # 导入 transforms 用于数据的变换
from torch.utils.data import DataLoader  # 从 torch.utils.data 导入 DataLoader,用于加载数据集


# 定义 CNN 模型
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第一层卷积层:输入通道 3(RGB 图像),输出通道 16,卷积核大小 3x3,padding=1 保持图像大小不变
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
        # 第二层卷积层:输入通道 16,输出通道 32,卷积核大小 3x3,padding=1 保持图像大小不变
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
        # 第三层卷积层:输入通道 32,输出通道 64,卷积核大小 3x3,padding=1 保持图像大小不变
        self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)

        # 最大池化层:每 2x2 区域取最大值,步长为 2
        self.pool = nn.MaxPool2d(2, 2)

        # 修改后的全连接层:将卷积层的输出展平并连接到 512 个神经元
        self.fc1 = nn.Linear(64 * 4 * 4, 512)  # 修改为 1024(64 * 4 * 4)
        # 输出层:输出 10 个类别(CIFAR-10 数据集有 10 类)
        self.fc2 = nn.Linear(512, 10)

    def forward(self, x):
        # 卷积层 + 激活函数 + 池化
        x = self.pool(torch.relu(self.conv1(x)))  # 第一层卷积 + ReLU 激活 + 池化
        x = self.pool(torch.relu(self.conv2(x)))  # 第二层卷积 + ReLU 激活 + 池化
        x = self.pool(torch.relu(self.conv3(x)))  # 第三层卷积 + ReLU 激活 + 池化

        # 展平数据(将三维的图像数据转换为一维的向量)
        x = x.view(-1, 64 * 4 * 4)  # 修正展平后的形状(batch_size, 64 * 4 * 4)

        # 全连接层
        x = torch.relu(self.fc1(x))  # ReLU 激活函数
        x = self.fc2(x)  # 输出 10 个类别的得分
        return x  # 返回模型的输出



# 数据加载和预处理
transform = transforms.Compose([  # 将多个变换操作组合在一起
    transforms.ToTensor(),  # 将图像转换为张量格式
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 标准化处理,均值和标准差都为 0.5
])

# 下载并加载 CIFAR-10 数据集(训练集)
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = DataLoader(trainset, batch_size=32, shuffle=True)  # 创建 DataLoader,用于加载训练数据,批量大小为 32,数据顺序打乱

# 下载并加载 CIFAR-10 数据集(测试集)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=32, shuffle=False)  # 创建 DataLoader,用于加载测试数据,批量大小为 32

# 初始化 CNN 模型
model = CNN()

# 使用交叉熵损失函数(用于分类任务)和 SGD 优化器(随机梯度下降)
criterion = nn.CrossEntropyLoss()  # 交叉熵损失函数
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)  # 随机梯度下降优化器,学习率 0.001,动量 0.9

# 训练模型
for epoch in range(2):  # 训练 2 个 epoch
    running_loss = 0.0  # 用于记录每个小批量的损失
    for i, data in enumerate(trainloader, 0):  # 遍历训练数据,每个数据 batch 的索引为 i
        inputs, labels = data  # 输入数据和对应的标签
        print(inputs.shape)
        optimizer.zero_grad()  # 清空之前的梯度

        outputs = model(inputs)  # 将输入传入模型进行前向传播
        print(outputs.shape)  # 调试输出
        print(labels.shape)  # 调试输出
        loss = criterion(outputs, labels)  # 计算损失(预测输出与实际标签之间的差距)
        loss.backward()  # 反向传播,计算梯度
        optimizer.step()  # 根据梯度更新模型参数

        running_loss += loss.item()  # 累加当前批次的损失
        if i % 200 == 199:  # 每 200 个小批量输出一次损失
            print(f"[{epoch + 1}, {i + 1}] loss: {running_loss / 200:.3f}")
            running_loss = 0.0  # 重置损失

print('Finished Training')  # 训练完成

# 测试模型
correct = 0  # 记录正确预测的数量
total = 0  # 记录总共测试的样本数量
with torch.no_grad():  # 不计算梯度,节省内存
    for data in testloader:  # 遍历测试数据
        inputs, labels = data  # 输入数据和对应的标签
        outputs = model(inputs)  # 将输入传入模型进行前向传播
        _, predicted = torch.max(outputs, 1)  # 取得最大概率的类别(输出类别索引)
        total += labels.size(0)  # 增加总样本数
        correct += (predicted == labels).sum().item()  # 统计正确预测的数量

# 输出模型在测试集上的准确率
print(f'Accuracy of the network on the 10000 test images: {100 * correct / total}%')

代码详解:


1. 导入必要的模块

import torch  # 导入 PyTorch 库,用于构建和训练深度学习模型
import torch.nn as nn  # 导入 nn 模块,它包含了构建神经网络所需的所有组件
import torch.optim as optim  # 导入优化器模块,用于训练模型时优化参数
import torchvision  # 导入 torchvision 库,它提供了常见的图像数据集和预训练模型
import torchvision.transforms as transforms  # 导入 transforms 用于图像数据预处理
from torch.utils.data import DataLoader  # 导入 DataLoader,用于加载数据集
  • torch:是 PyTorch 的核心库,包含了许多功能,如张量计算、神经网络构建等。
  • torch.nn:包含构建神经网络所需的所有模块和层,如卷积层(Conv2d)、全连接层(Linear)等。
  • torch.optim:包含了多种优化器,如随机梯度下降(SGD)、Adam 等。
  • torchvision:包含用于处理计算机视觉任务的功能,如加载标准数据集(如 CIFAR-10)和使用预训练模型等。
  • transforms:提供了图像数据预处理的方法,如归一化、随机裁剪等。
  • DataLoader:用于从数据集中加载批量数据,支持并行加载和数据增强。

2. 定义 CNN 模型

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()  # 初始化父类 nn.Module 的构造函数
  • class CNN(nn.Module)::定义了一个新的类 CNN,继承自 nn.Module。所有的 PyTorch 神经网络模型都需要继承自 nn.Module 类。
  • super(CNN, self).__init__():调用父类 nn.Module 的构造函数,确保可以访问 PyTorch 提供的所有功能。比如网络参数的管理、梯度计算等。

3. 定义卷积层

self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)  # 第一层卷积层
  • self.conv1:这是卷积层的定义,self 表示将该层定义为类的属性,这样在 forward() 方法中可以调用。
  • nn.Conv2d(3, 16, kernel_size=3, padding=1)
    • 3:输入通道数,表示输入是 RGB 图像,所以有 3 个通道。
    • 16:输出通道数,表示该卷积层将有 16 个不同的卷积核(过滤器),每个卷积核会提取不同的特征。
    • kernel_size=3:卷积核的大小是 3x3。
    • padding=1:表示在图像的四周加上 1 像素的零填充,这样卷积操作后,输出的图像尺寸保持不变。

4. 定义其他卷积层

self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)  # 第二层卷积层
self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)  # 第三层卷积层
  • self.conv2self.conv3:第二层和第三层卷积层的定义。
    • self.conv2:输入通道数为 16(上一层的输出),输出通道数为 32。
    • self.conv3:输入通道数为 32(上一层的输出),输出通道数为 64。
  • kernel_size=3:每一层的卷积核大小均为 3x3,适用于图像的特征提取。

5. 定义池化层

self.pool = nn.MaxPool2d(2, 2)  # 最大池化层:每 2x2 区域取最大值
  • self.pool:定义了一个最大池化层。池化层用于减小特征图的尺寸,从而减少计算量。
  • nn.MaxPool2d(2, 2)
    • 2:池化窗口的大小为 2x2。
    • 2:步幅为 2,表示池化窗口每次移动 2 个像素。

6. 定义全连接层

self.fc1 = nn.Linear(64 * 8 * 8, 512)  # 全连接层:输入 64*8*8,输出 512 个神经元
self.fc2 = nn.Linear(512, 10)  # 输出层:输出 10 个类别
  • self.fc1:全连接层,连接卷积层提取的特征到 512 个神经元。
    • 64 * 8 * 8:输入的特征数量。假设输入图像为 32x32,经过卷积和池化后,图像大小变为 8x8,且通道数为 64。
    • 512:全连接层输出 512 个神经元。
  • self.fc2:输出层,输出 10 个类别的得分,适用于 CIFAR-10 数据集(有 10 类)。
    • 512:上一层的输出。
    • 10:输出层的类别数。

7. 定义前向传播过程(forward

def forward(self, x):
    x = self.pool(torch.relu(self.conv1(x)))  # 第一层卷积 + ReLU 激活 + 池化
    x = self.pool(torch.relu(self.conv2(x)))  # 第二层卷积 + ReLU 激活 + 池化
    x = self.pool(torch.relu(self.conv3(x)))  # 第三层卷积 + ReLU 激活 + 池化
  • x = self.pool(torch.relu(self.conv1(x))):将输入 x 通过卷积层 conv1 处理,应用 ReLU 激活函数,然后进行池化。重复这一操作对 conv2conv3 进行处理。
    • torch.relu():应用 ReLU 激活函数,增加非线性,使得网络能够学习复杂的模式。
    • self.pool():进行池化操作,减小特征图尺寸。
x = x.view(-1, 64 * 8 * 8)  # 展平数据(将三维的图像数据转换为一维的向量)
  • x.view(-1, 64 * 8 * 8):将三维的图像数据(64个通道,8x8的特征图)展平成一个一维的向量,准备送入全连接层。-1 代表批量大小(自动计算)。
x = torch.relu(self.fc1(x))  # ReLU 激活函数
x = self.fc2(x)  # 输出 10 个类别的得分
  • x = torch.relu(self.fc1(x)):将展平后的数据传入全连接层 fc1,并应用 ReLU 激活函数。
  • x = self.fc2(x):将 fc1 的输出传入最后的输出层 fc2,生成最终的分类结果(10 个类别的得分)。
return x  # 返回模型的输出
  • return x:返回网络的最终输出,即每个类别的得分。

8. 数据加载和预处理

transform = transforms.Compose([  # 数据预处理步骤
    transforms.ToTensor(),  # 将图像转换为 Tensor 格式
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 标准化处理,均值和标准差都为 0.5
])
  • transforms.Compose:将多个图像预处理操作组合在一起。
    • transforms.ToTensor():将图像转换为 PyTorch 的 Tensor 格式,便于进行张量计算。
    • transforms.Normalize():对图像进行归一化处理,将像素值转换为标准正态分布。

9. 训练模型

for epoch in range(2):  # 训练 2 个 epoch
    running_loss = 0.0  # 用于记录每个小批量的损失
    for i, data in enumerate(trainloader, 0):  # 遍历训练数据
        inputs, labels = data  # 获取输入图像和标签
        optimizer.zero_grad()  # 清空之前的梯度

        outputs = model(inputs)  # 前向传播,获取模型输出
        loss = criterion(outputs, labels)  # 计算损失
        loss.backward()  # 反向传播,计算梯度
        optimizer.step()  # 更新模型参数

        running_loss += loss.item()  # 累加当前批次的损失
        if i % 200 == 199:  # 每 200 个小批量输出一次损失
            print(f"[{epoch + 1}, {i + 1}] loss: {running_loss / 200:.3f}")
            running_loss = 0.0  # 重置损失
  • 训练循环:每次训练时,我们都遍历所有小批量数据,计算损失并更新参数。
  • optimizer.zero_grad():清空上一步计算的梯度,避免累加。
  • loss.backward():反向传播,计算梯度。
  • optimizer.step():使用优化器更新网络的权重。

10. 测试模型

correct = 0
total = 0
with torch.no_grad():

  # 不计算梯度,节省内存
    for data in testloader:  # 遍历测试数据
        inputs, labels = data  # 获取输入图像和标签
        outputs = model(inputs)  # 前向传播,获取模型输出
        _, predicted = torch.max(outputs, 1)  # 获取最大概率的类别索引
        total += labels.size(0)  # 增加总样本数
        correct += (predicted == labels).sum().item()  # 统计正确预测的数量

print(f'Accuracy of the network on the 10000 test images: {100 * correct / total}%')
  • 测试循环:计算模型在测试集上的准确率。
  • torch.no_grad():禁用梯度计算,节省内存。
  • torch.max(outputs, 1):返回每个样本的最大输出概率对应的类别索引。
  • (predicted == labels).sum().item():计算模型预测正确的数量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值