RTX4090上训练卷积神经网络CNN

向量数据库大模型机器学习

卷积神经网络 CNN 是专为图像识别和处理设计的一类深度神经网络,它可以有效地捕捉图像中的空间结构信息 ,通过局部感受野、权重共享、下采样 等机制,大大减少了参数数量,同时保留了重要特征。我们用另外一个经典的教学数据集CIFAR-10,来看下CNN的训练过程

数据集

先来看下 CIFAR-10 数据集的基本信息:

  • • 每条数据是一个 32x32 的彩色图像,每个图像打标了一个类别。
  • • 共包含10个类别,60,000张图像(50,000训练 + 10,000测试)

picture.image

在 Jupyter notebook 中,可以用如下代码,抽样查看数据集图片:


 
 
 
 
   
import matplotlib.pyplot as plt  
import numpy as np  
  
。。。  
  
# 获取5个样本  
samples = []  
classes = train\_set.classes  # CIFAR10的类别名称  
  
for i in range(5):  
    # 直接从训练集中获取样本 (会自动应用数据增强)  
    image, label = train\_set[i]  
    samples.append((image, label))  
  
# 创建可视化  
fig, axes = plt.subplots(1, 5, figsize=(15, 3))  
  
for i, (image, label) in enumerate(samples):  
    # 显示图像  
    ax = axes[i]  
    ax.imshow(image)  
    ax.set\_title(classes[label])  
    ax.axis('off')  
  
plt.tight\_layout()  
plt.show()

其中的 train_set 是之前代码加载好的数据集对象。执行后可以看到如下输出:

picture.image

这里是这个数据集的主页 : http://www.cs.toronto.edu/~kriz/cifar.html 里面有更详细的描述

我们用上一篇《深度学习模型训练的一般过程》中介绍的多层感知机MLP模型对这个数据集进行训练,结果如下:


 
 
 
 
   
Epoch: 5 [0/50000 (0%)]    Loss: 1.601727  
Epoch: 5 [12800/50000 (26%)]    Loss: 1.473466  
Epoch: 5 [25600/50000 (51%)]    Loss: 1.570823  
Epoch: 5 [38400/50000 (77%)]    Loss: 1.668174  
  
测试集: 平均损失: 0.0182, 准确率: 4358/10000 (43.58%)

可见图像复杂之后,MLP模型最后的准确率就不高了。我们接下来看看CNN模型在这个数据集上的表现怎么样。

模型训练

下面代码就是一个简单的 CNN 模型训练过程:


 
 
 
 
   
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  
  
# 设置随机种子确保可复现性  
torch.manual\_seed(42)  
  
# 检查GPU可用性  
device = torch.device('cuda' if torch.cuda.is\_available() else 'cpu')  
print(f"使用设备: {device}")  
  
# 数据预处理和增强  
transform\_train = transforms.Compose([  
    transforms.RandomCrop(32, padding=4),  
    transforms.RandomHorizontalFlip(),  
    transforms.ToTensor(),  
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  
])  
  
transform\_test = transforms.Compose([  
    transforms.ToTensor(),  
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  
])  
  
# 下载并加载数据集  
train\_set = torchvision.datasets.CIFAR10(  
    root='./data',  
    train=True,  
    download=True,  
    transform=transform\_train  
)  
test\_set = torchvision.datasets.CIFAR10(  
    root='./data',  
    train=False,  
    download=True,  
    transform=transform\_test  
)  
  
train\_loader = DataLoader(train\_set, batch\_size=128, shuffle=True, num\_workers=2)  
test\_loader = DataLoader(test\_set, batch\_size=100, shuffle=False, num\_workers=2)  
  
# 定义CNN模型类,继承自nn.Module  
  
  
class CNN(nn.Module):  
    # 初始化函数,定义网络层  
    def \_\_init\_\_(self):  
        super(CNN, self).\_\_init\_\_()  # 调用父类nn.Module的初始化方法  
  
        # 卷积层部分 - 使用Sequential容器按顺序组织各层  
        self.conv\_layers = nn.Sequential(  
            # 第一个卷积块  
            nn.Conv2d(3, 32, 3, padding=1),  # 输入通道3(RGB), 输出通道32, 3x3卷积核, padding=1保持尺寸  
            nn.ReLU(),  # 激活函数引入非线性  
            nn.Conv2d(32, 32, 3, padding=1),  # 再次卷积,加深特征提取  
            nn.ReLU(),  
            nn.MaxPool2d(2, 2),  # 2x2最大池化,尺寸减半(32x32 -> 16x16)  
            nn.Dropout(0.25),  # 随机丢弃25%神经元,防止过拟合  
  
            # 第二个卷积块  
            nn.Conv2d(32, 64, 3, padding=1),  # 增加通道数到64  
            nn.ReLU(),  
            nn.Conv2d(64, 64, 3, padding=1),  
            nn.ReLU(),  
            nn.MaxPool2d(2, 2),  # 尺寸再减半(16x16 -> 8x8)  
            nn.Dropout(0.25),  
  
            # 第三个卷积块  
            nn.Conv2d(64, 128, 3, padding=1),  # 增加通道数到128  
            nn.ReLU(),  
            nn.Conv2d(128, 128, 3, padding=1),  
            nn.ReLU(),  
            nn.MaxPool2d(2, 2),  # 尺寸再减半(8x8 -> 4x4)  
            nn.Dropout(0.25)  
        )  
  
        # 全连接层部分  
        self.fc\_layers = nn.Sequential(  
            nn.Flatten(),  # 将多维特征图展平为一维向量 (128 * 4 * 4 = 2048)  
            nn.Linear(128 * 4 * 4, 1024),  # 全连接层,2048输入 -> 1024输出  
            nn.ReLU(),  
            nn.Dropout(0.5),  # 更高的丢弃率,防止过拟合  
            nn.Linear(1024, 10)  # 最终输出层,10个类别对应CIFAR-10  
        )  
  
    # 前向传播函数,定义数据如何通过网络  
    def forward(self, x):  
        x = self.conv\_layers(x)  # 数据通过卷积层  
        x = self.fc\_layers(x)    # 再通过全连接层  
        return x  
  
  
# 创建模型实例并移动到设备(GPU/CPU)  
model = CNN().to(device)  
  
# 定义损失函数 - 交叉熵损失,适用于多分类问题  
criterion = nn.CrossEntropyLoss()  
  
# 定义优化器 - Adam优化器,学习率0.001,添加L2正则化(weight\_decay)  
optimizer = optim.Adam(model.parameters(), lr=0.001, weight\_decay=1e-5)  
  
# 定义学习率调度器 - 当验证损失不再下降时降低学习率  
scheduler = optim.lr\_scheduler.ReduceLROnPlateau(  
    optimizer,  
    'min',       # 监控验证损失最小值  
    patience=3,  # 等待3个epoch无改善  
    factor=0.5   # 学习率减半  
)  
  
# 训练函数  
  
  
def train(epoch):  
    model.train()  # 设置模型为训练模式(启用dropout等)  
    running\_loss = 0.0  # 累计损失  
  
    # 遍历训练集所有批次  
    for i, (inputs, labels) in enumerate(train\_loader):  
        # 将数据移动到设备(GPU/CPU)  
        inputs, labels = inputs.to(device), labels.to(device)  
  
        # 梯度清零 - 防止梯度累积  
        optimizer.zero\_grad()  
  
        # 前向传播  
        outputs = model(inputs)  
        # 计算损失  
        loss = criterion(outputs, labels)  
        # 反向传播计算梯度  
        loss.backward()  
        # 更新权重  
        optimizer.step()  
  
        # 累计损失  
        running\_loss += loss.item()  
  
        # 每100个batch打印一次损失  
        if i % 100 == 99:  
            print(f'Epoch: {epoch+1}, Batch: {i+1}, Loss: {running\_loss/100:.3f}')  
            running\_loss = 0.0  # 重置累计损失  
  
    return loss.item()  # 返回最后一个batch的损失  
  
# 测试函数  
  
  
def test():  
    model.eval()  # 设置模型为评估模式(禁用dropout等)  
    correct = 0   # 正确预测数  
    total = 0     # 总样本数  
    test\_loss = 0.0  # 测试损失  
  
    # 禁用梯度计算,节省内存和计算资源  
    with torch.no\_grad():  
        # 遍历测试集所有批次  
        for inputs, labels in test\_loader:  
            inputs, labels = inputs.to(device), labels.to(device)  
            # 前向传播  
            outputs = model(inputs)  
            # 计算损失  
            loss = criterion(outputs, labels)  
            test\_loss += loss.item()  
            # 获取预测结果(最大概率的类别)  
            \_, predicted = outputs.max(1)  
            # 统计正确预测数  
            total += labels.size(0)  
            correct += predicted.eq(labels).sum().item()  
  
    # 计算准确率  
    accuracy = 100. * correct / total  
    # 打印测试结果  
    print(f'测试集平均损失: {test\_loss/len(test\_loader):.4f}, 准确率: {accuracy:.2f}%')  
    return test\_loss/len(test\_loader), accuracy  # 返回平均损失和准确率  
  
  
# 主训练循环  
best\_accuracy = 0.0  # 记录最佳准确率  
  
# 遍历所有epoch  
for epoch in range(30):  
    print(f"\n开始训练第 {epoch+1} 个epoch...")  
    # 训练一个epoch  
    train\_loss = train(epoch)  
    # 在测试集上评估  
    test\_loss, accuracy = test()  
    # 根据测试损失调整学习率  
    scheduler.step(test\_loss)  
  
    # 如果当前准确率优于历史最佳,保存模型  
    if accuracy > best\_accuracy:  
        best\_accuracy = accuracy  
        # 保存模型权重  
        torch.save(model.state\_dict(), 'best\_model.pth')  
        print(f"保存新的最佳模型,准确率: {best\_accuracy:.2f}%")  
  
# 训练结束  
print(f"\n训练完成!最佳测试准确率: {best\_accuracy:.2f}%")

将如上代码保存为 cnn.py ,在上一篇《深度学习模型训练的一般过程》中提到的容器环境中执行:


 
 
 
 
   
root@cb82d3b9af0a:/workspace# python cnn.py  
使用设备: cuda  
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz  
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 170M/170M [03:20<00:00, 852kB/s]  
Extracting ./data/cifar-10-python.tar.gz to ./data  
Files already downloaded and verified  
  
开始训练第 1 个epoch...  
Epoch: 1, Batch: 100, Loss: 2.112  
Epoch: 1, Batch: 200, Loss: 1.866  
Epoch: 1, Batch: 300, Loss: 1.676  
测试集平均损失: 1.4287, 准确率: 45.92%  
保存新的最佳模型,准确率: 45.92%  
  
...  
  
开始训练第 30 个epoch...  
Epoch: 30, Batch: 100, Loss: 0.603  
Epoch: 30, Batch: 200, Loss: 0.605  
Epoch: 30, Batch: 300, Loss: 0.611  
测试集平均损失: 0.5210, 准确率: 82.82%  
保存新的最佳模型,准确率: 82.82%  
  
训练完成!最佳测试准确率: 82.82%

可以看到,结果准确率比 MLP 模型的 43.58% 有了大幅提升。

期间的资源消耗:


 
 
 
 
   
+-----------------------------------------+----------------------+----------------------+  
|   4  NVIDIA GeForce RTX 4090        On  | 00000000:81:00.0 Off |                  Off |  
| 30%   35C    P2              70W / 450W |    696MiB / 24564MiB |     10%      Default |  
|                                         |                      |                  N/A |  
+-----------------------------------------+----------------------+----------------------+

因数据集不大,显存和算力用得都不多。我们来看看发生了什么,首先看下模型结构

模型结构

上面代码中定义了模型结构的片段如下:


 
 
 
 
   
class CNN(nn.Module):  
    # 初始化函数,定义网络层  
    def \_\_init\_\_(self):  
        super(CNN, self).\_\_init\_\_()  # 调用父类nn.Module的初始化方法  
  
        # 卷积层部分 - 使用Sequential容器按顺序组织各层  
        self.conv\_layers = nn.Sequential(  
            # 第一个卷积块  
            nn.Conv2d(3, 32, 3, padding=1),  # 输入通道3(RGB), 输出通道32, 3x3卷积核, padding=1保持尺寸  
            nn.ReLU(),  # 激活函数引入非线性  
            nn.Conv2d(32, 32, 3, padding=1),  # 再次卷积,加深特征提取  
            nn.ReLU(),  
            nn.MaxPool2d(2, 2),  # 2x2最大池化,尺寸减半(32x32 -> 16x16)  
            nn.Dropout(0.25),  # 随机丢弃25%神经元,防止过拟合  
  
            # 第二个卷积块  
            nn.Conv2d(32, 64, 3, padding=1),  # 增加通道数到64  
            nn.ReLU(),  
            nn.Conv2d(64, 64, 3, padding=1),  
            nn.ReLU(),  
            nn.MaxPool2d(2, 2),  # 尺寸再减半(16x16 -> 8x8)  
            nn.Dropout(0.25),  
  
            # 第三个卷积块  
            nn.Conv2d(64, 128, 3, padding=1),  # 增加通道数到128  
            nn.ReLU(),  
            nn.Conv2d(128, 128, 3, padding=1),  
            nn.ReLU(),  
            nn.MaxPool2d(2, 2),  # 尺寸再减半(8x8 -> 4x4)  
            nn.Dropout(0.25)  
        )  
  
        # 全连接层部分  
        self.fc\_layers = nn.Sequential(  
            nn.Flatten(),  # 将多维特征图展平为一维向量 (128 * 4 * 4 = 2048)  
            nn.Linear(128 * 4 * 4, 1024),  # 全连接层,2048输入 -> 1024输出  
            nn.ReLU(),  
            nn.Dropout(0.5),  # 更高的丢弃率,防止过拟合  
            nn.Linear(1024, 10)  # 最终输出层,10个类别对应CIFAR-10  
        )  
  
    # 前向传播函数,定义数据如何通过网络  
    def forward(self, x):  
        x = self.conv\_layers(x)  # 数据通过卷积层  
        x = self.fc\_layers(x)    # 再通过全连接层  
        return x

这段代码定义的 CNN 网络由两个主要部分构成:

卷积层部分(self.conv_layers)用于从图像中提取局部空间特征。

  • • 卷积层 nn.Conv2d(in_channels, out_channels, kernel_size, padding)
  • • 扫描图像的局部区域,提取边缘、纹理、形状等低/高层次特征。
  • • padding=1 表示边缘补零,使输出大小与输入相同(便于尺寸控制)。
  • • 激活函数 nn.ReLU(),引入非线性,帮助模型学习更复杂的函数映射。
  • • 池化层 nn.MaxPool2d(kernel_size, stride)
  • • 减少特征图尺寸(降低计算量),保留最显著特征。
  • • 2x2 最大池化意味着每 2x2 区域保留最大值 → 尺寸减半。
  • • Dropout 层 nn.Dropout(p) 用于训练时随机丢弃一部分神经元,防止过拟合。

结构总结如下:

| 卷积块 | 输入尺寸 | 输出通道 | 空间尺寸变化 | | Block 1 | 3x32x32 → Conv2d(3→32) → Conv2d(32→32) | 32 | 32x32 → 16x16 | | Block 2 | Conv2d(32→64) → Conv2d(64→64) | 64 | 16x16 → 8x8 | | Block 3 | Conv2d(64→128) → Conv2d(128→128) | 128 | 8x8 → 4x4 |

全连接层部分("self.fc_layers"):用于将提取的高维特征映射为分类结果。

  • • Flatten 层:将形状为 "[batch_size, 128, 4, 4]" 的张量拉平成 "[batch_size, 2048]",供全连接层使用。
  • • 全连接层 "nn.Linear(in, out)":将扁平向量映射到新的空间,模拟传统神经网络的分类过程。
  • • 最后一层 "nn.Linear(1024, 10)":输出一个大小为 10 的向量,对应 CIFAR-10 数据集的 10 个类别。

前向传播过程 "forward"


 
 
 
 
   
def forward(self, x):  
    x = self.conv\_layers(x)  # 特征提取  
    x = self.fc\_layers(x)    # 分类预测  
    return x
  • • 输入图像:"[batch_size, 3, 32, 32]"
  • • 输出:"[batch_size, 10]",每个元素是一个类别得分(通常接 softmax)
卷积计算

以上代码中 nn.Conv2d(3, 32, 3, padding=1) 是一个标准的PyTorch 2D卷积层定义,其作用是在神经网络中创建特征提取器:


 
 
 
 
   
nn.Conv2d(  
    in\_channels=3,   # 输入通道数 (RGB图像)  
    out\_channels=32, # 输出通道数/卷积核数量  
    kernel\_size=3,    # 卷积核尺寸 3×3  
    padding=1,        # 边界填充量  
)

这行代码对应的模型权重,是 32 个“3×3×3” 的卷积核张量,也即3个输入通道RGB,每个通道对应一个3×3的卷积核。每个输出通道对应一个这样的卷积核组。

  • • 输出通道 0 使用的权重是一个 [3×3×3] 张量(3 个通道,每个是 3×3 的卷积核)
  • • 输出通道 1 再使用另一个 [3×3×3] 卷积核组
  • • …
  • • 输出通道 31 用的是第 32 个 [3×3×3] 卷积核组

对于单个通道来说,假设你有:

  • • 输入图像 "X ∈ ℝ^{C×H×W}"
  • • 卷积核 "K ∈ ℝ^{C×k×k}",其中 C 是通道数,k 是核大小(3)

则第 m 个输出通道的某个像素点 (i, j) 的计算公式为:

其中:

是输出通道

对输入通道

的卷积核(在改例中是3*3的矩阵)

是偏置项

这个公式的含义就是,对每个输入通道C(如该例中的RGB), 用一个 3×3 的矩阵(

),对输入中通道C对应的数据 H×W(该例中是 32*32 图像)做扫描,和扫描到的图像子矩阵做点积。所有输入通道的点积结果之和,加上一个偏置,作为结果特征图的一个像素点。考虑到边缘像素的公平性,计算前在图像周围加上一圈 padding; 计算过程如下图示意:(下图简化了输入和输出维度大小)

picture.image

经过多层次这样的卷积扫描(以及ReLU,池化等),通过在训练中反复的前向传播,损失计算,反向传播,梯度下降的迭代,试图让模型学习到不同层次的图像特征。为图像分类判断提供依据。这个过程,就像模拟人眼在看到图像时,先扫描图像识别局部特性,再抽象整体概念的过程。

CNN 最神奇的地方是 :不同的卷积层会学习到图像不同角度的特征。比如第一个卷积层,往往会学习到图像的边缘,纹理等特征。例如这个例子中,第一张图片,前6个通道,学习到的权重,生成的数据,可视化后如下:

picture.image

只是设计好了网络结构,通过数据样本反复学习,权重在网络结构不同层次的分布就会呈现出这样有规律的特点。这大概就是复杂系统科学中所指的涌现吧

是哪个天才设计出了这样的神经网络结构呢?正是下面这位大佬:

picture.image

图灵奖得主,AI科研三巨头之一,前 Meta(前Facebook)AI 掌门人:杨立昆(Yann André Le Cun),也被称为CNN之父。

总结

相比MLP这样的全连接层网络,CNN 具备如下优势:

  • • 权重共享 : 同一个卷积核在整张图上滑动,大幅减少参数量。
  • • 局部连接 :卷积核只处理局部区域,有效提取局部特征。
  • • 层级特征抽取 : 越往后抽象层级越高(从边缘到复杂结构)。
  • • 可泛化性强 :适合各种图像分类、目标检测、语义分割任务。

CNN的应用场景

  • • 人脸识别

  • • 医疗影像诊断

  • • 安全监控中的目标检测

  • • 自动驾驶中的道路识别

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
大规模高性能计算集群优化实践
随着机器学习的发展,数据量和训练模型都有越来越大的趋势,这对基础设施有了更高的要求,包括硬件、网络架构等。本次分享主要介绍火山引擎支撑大规模高性能计算集群的架构和优化实践。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论