一  CNN网络

1.1 CNN定义

       卷积神经网络(Convolutional Neural Network,CNN)是一种能够自动从数据中学习特征的深度学习模型。它最早被设计来处理图像任务,比如识别手写数字、动物、交通标志等,如今也广泛应用在语音识别、视频分析、文本理解等领域。

        和传统神经网络不同,CNN 有两个关键的“聪明设计”:
        ● 局部感受:
        每个神经元只看输入数据的一小块区域,就像人类只关注图片的某个局部。这样既能聚焦细节,又能减少计算量。
        ● 权值共享:
        同一个卷积核(Filter)会在整张图像上反复使用,相当于用同一副“特征眼镜”去扫描不同区域,既节省参数,又保证模型能发现重复的模式(比如边缘或形状)。

1.2 CNN组成结构

        CNN主要由输入层、卷积层、激活层、池化层、全连接层、输出层6部分组成,下面对每部分进行详细解释。其动画运行流程可以访问https://poloclub.github.io/cnn-explainer/进行观看。

1.2.1 输入层(Input Layer)

        输入层(Input Layer)是卷积神经网络接收原始数据的入口。可以把它想象成 CNN 的“眼睛”——所有图像、语音或文本信息,都是从这里进入网络的。

        在图像任务中,输入层的作用就是把一张图片转化为可以计算的数字矩阵。其矩阵的形式通常表示为 [c, w, h],其中:
        ● c 表示图像的通道数(channels),即图像包含的颜色通道数量;
        ● w 表示图像的宽度(width);
        ● h 表示图像的高度(height)。

        例如,对于灰度图像,输入矩阵为 [1, w, h],只有一个灰度通道;对于彩色 RGB 图像,输入矩阵为 [3, w, h],对应红、绿、蓝三个颜色通道。

1.2.2 卷积层(Convolutional Layer)

        卷积层(Convolutional Layer)是整个 CNN 的核心部分。它的主要任务是:从输入图像中自动提取特征。如果说输入层是“眼睛”,那卷积层就是“大脑皮层”——它学会识别图像中的边缘、角点、纹理,甚至更高层的语义结构(比如眼睛、车轮、船体等)。

        卷积层的基本思想是:用一个小窗口(卷积核,kernel)在整张图像上滑动,对每个位置计算加权和,生成新的“特征图(feature map)”。这个过程叫做卷积运算(convolution operation)。它的作用相当于在扫描整张图像,看看哪些局部区域“符合”某种特征模式。

        假设输入图像是大小为 [3, w, h] 的 RGB 图片,我们用一个大小为 3×3 的卷积核(kernel)进行卷积操作。卷积核在图像上从左到右、从上到下滑动,每次取一个 3×3×3 的小区域,与卷积核做点乘并求和,输出一个数。当卷积核滑完整张图后,就会生成一张新的特征图。如果我们使用 32 个卷积核,那这一层就会输出 32 张特征图,操作过程如下图所示:

1.2.3 激活层(Activation Layer)

        卷积层提取到的特征本质上只是线性组合,如果不加任何非线性处理,无论堆多少层卷积,
整个网络都只能学到“线性关系”——相当于再复杂的模型也只是一个加权平均器。

        于是我们需要在每一层卷积之后,加上一个“激活层”,让网络具备非线性表达能力,能学到复杂的模式和边界。这层就像神经网络的“活化剂”,让它真正有了“智慧”。

        常见的激活函数有:ReLU、Sigmoid、Tanh等。其计算公式如下:

1.2.4 池化层(Pooling Layer)

        在前面的卷积层与激活层中,网络已经提取出了大量局部特征。但这些特征图往往体积很大,计算量也随之暴涨。此时,我们需要一个“信息筛选器”,在保留关键信息的同时,缩小特征图尺寸、降低计算复杂度。这就是池化层(Pooling Layer)的使命。

        常见的池化操作有:平均池化(Average Pooling)、最大池化(Max Pooling)。

1.2.5 全连接层(Fully Connected Layer)

        当卷积和池化层已经将图像的空间信息逐渐压缩、提炼为抽象特征时,我们需要一个模块把这些特征“汇总”起来,并据此做出最终判断(例如图片属于猫、狗还是船)。这一步就是由 全连接层(Fully Connected Layer) 完成的。

1.2.6 输出层(Output Layer)

        输出层(Output Layer)是整个卷积神经网络的最后一层,它的作用是:将全连接层输出的数值,转化为具体的任务结果——例如分类概率、回归数值或检测框参数。如果说卷积层负责“看懂世界”,全连接层负责“综合判断”,那输出层就是那个说出最终答案的人

        输出层的形式取决于任务类型,不同任务对应不同的设计:

二   Pytorch实现——mnist手写数字识别

1 导入库及设置GPU

# 导入库
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
import torch.nn.functional as F
import matplotlib.pyplot as plt
from datetime import datetime
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

# 设置硬件设备,如果有GPU则使用,没有则使用cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

2 导入数据

2.1 数据下载

使用dataset下载MNIST数据集,并划分好训练集与测试集

函数原型:

torchvision.datasets.MNIST(
    root, 
    train=True, 
    transform=None, 
    target_transform=None, 
    download=False
)

参数说明:

● root (string) :数据地址
● train (string) :True-训练集,False-测试集
● download (bool,optional) : 如果为True,从互联网上下载数据集,并把数据集放在root目录下
● transform (callable, optional ):这里的参数选择一个你想要的数据转化函数,直接完成数据转化
● target_transform (callable,optional) :接受目标并对其进行转换的函数/转换

2.2 数据加载

使用dataloader加载数据,并设置好基本的batch_size

函数原型:

torch.utils.data.DataLoader(
    dataset, 
    batch_size=1, 
    shuffle=None, 
    sampler=None, 
    batch_sampler=None, 
    num_workers=0, 
    collate_fn=None, 
    pin_memory=False, 
    drop_last=False, 
    timeout=0, 
    worker_init_fn=None, 
    multiprocessing_context=None, 
    generator=None, 
    *, 
    prefetch_factor=2, 
    persistent_workers=False, 
    pin_memory_device=''
)

参数说明:

● dataset (string) :加载的数据集
● batch_size (int,optional) :每批加载的样本大小(默认值:1)
● shuffle (bool,optional) : 如果为True,每个epoch重新排列数据
● sampler (Sampler or iterable, optional)  : 定义从数据集中抽取样本的策略。 可以是任何实现了 __len__ 的 Iterable。 如果指定,则不得指定 shuffle 
● batch_sampler (Sampler or iterable, optional) : 类似于sampler,但一次返回一批索引。与 batch_size、shuffle、sampler 和 drop_last 互斥
● num_workers (int,optional) : 用于数据加载的子进程数。 0 表示数据将在主进程中加载(默认值:0)

2.3 代码

# 下载数据
train_ds = torchvision.datasets.MNIST('data', 
                                      train=True, 
                                      transform=torchvision.transforms.ToTensor(), 
                                      download=True)

test_ds  = torchvision.datasets.MNIST('data', 
                                      train=False, 
                                      transform=torchvision.transforms.ToTensor(), 
                                      download=True)

# 加载数据
batch_size = 32

train_dl = torch.utils.data.DataLoader(train_ds, 
                                       batch_size=batch_size, 
                                       shuffle=True)

test_dl  = torch.utils.data.DataLoader(test_ds, 
                                       batch_size=batch_size)

3 构建CNN网络

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        # 卷积 + 池化
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=0)
        self.pool1 = nn.MaxPool2d(2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=0)
        self.pool2 = nn.MaxPool2d(2, stride=2, padding=0)

        # 展平
        self.fc1 = nn.Linear(1600, 64)
        self.fc2 = nn.Linear(64, 10)

    # 前向传播
    def forward(self, x):
        # 卷积池化1
        x = self.conv1(x)
        x = F.relu(x)
        x = self.pool1(x)
        # 卷积池化2
        x = self.conv2(x)
        x = F.relu(x)
        x = self.pool2(x)
        # 展平
        x = torch.flatten(x, start_dim=1)
        # 全连接层1
        x = self.fc1(x)
        x = F.relu(x)
        # 输出层
        x = self.fc2(x)

        return x

4 模型训练

4.1 设置超参数

# 模型
model = Model().to(device)  
# 损失函数(交叉熵)
loss_fn = nn.CrossEntropyLoss() 
# 优化器
opt = torch.optim.SGD(model.parameters(),lr=1e-2)

4.2 编写训练函数

def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)   # 训练集的大小
    num_batches = len(dataloader)    # 批次数目

    # 初始化训练损失和正确率
    train_loss = 0
    train_acc = 0

    # 获取图片和标签
    for X, y in dataloader:
        X = X.to(device)
        y = y.to(device)

        # 计算预测误差
        pred = model(X)
        loss = loss_fn(pred, y)

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

        # 记录acc和loss
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()

    train_acc /= size
    train_loss /= num_batches

    return train_acc, train_loss

4.3 编写测试函数

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)

    test_loss = 0
    test_acc = 0

    # 当不进行训练时,停止梯度训练,节省计算资源内耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)

            # 计算loss
            target_pred = model(imgs)
            loss        = loss_fn(target_pred, target)

            test_loss += loss.item()
            test_acc  += (target_pred.argmax(1) == target).type(torch.float).sum().item()

        test_acc /= size
        test_loss /= num_batches

        return test_acc, test_loss

4.4 正式训练

epochs     = 5
train_loss = []
train_acc  = []
test_loss  = []
test_acc   = []

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)

    model.eval()
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)

    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)

    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}')
    print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))

5 结果可视化

current_time = datetime.now()   # 获取当前时间

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))   # 新建画布,宽 12 英寸、高 3 英寸

# 左图:Accuracy
plt.subplot(1, 2, 1)   # 把画布分成 1 行 2 列的子图,激活第 1 个子图(左图)
# 在左图上画两条曲线:训练准确率、测试准确率。默认连续折线
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')   # 显示图例,放在左图的右下角
plt.title('Training and Test Accuracy')   # 左图标题

# 右图:Loss
plt.subplot(1, 2, 2)   # 激活第 2 个子图(右图)
# 右图画训练/测试损失曲线
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')   # 右图图例放在右上角
plt.title('Training and Test Loss')   # 右图标题

plt.show()

最终运行结果如下:

● 完整代码链接https://gitcode.com/m0_75274681/DL/blob/main/Pytorch_CNN_mnist%E6%89%8B%E5%86%99%E6%95%B0%E5%AD%97%E8%AF%86%E5%88%AB.py

三 相关练习

1 MNIST手写数字识别https://gitcode.com/m0_75274681/DL/blob/main/1_CNN_MNIST%E6%89%8B%E5%86%99%E6%95%B0%E5%AD%97%E8%AF%86%E5%88%AB.py

2 CIFAR10 彩色图片识别https://gitcode.com/m0_75274681/DL/blob/main/2_CNN_CIFAR10%E5%BD%A9%E8%89%B2%E5%9B%BE%E7%89%87%E8%AF%86%E5%88%AB.py

3 天气识别https://gitcode.com/m0_75274681/DL/blob/main/3_CNN_%E5%A4%A9%E6%B0%94%E8%AF%86%E5%88%AB.py

4 猴痘检测https://gitcode.com/m0_75274681/DL/blob/main/4_CNN_%E7%8C%B4%E7%97%98%E6%A3%80%E6%B5%8B.py

5 运动鞋识别https://gitcode.com/m0_75274681/DL/blob/main/5_CNN_%E8%BF%90%E5%8A%A8%E9%9E%8B%E8%AF%86%E5%88%AB.py

6 人脸识别https://gitcode.com/m0_75274681/DL/blob/main/6_CNN_%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB.py

7 马铃薯病害识别https://gitcode.com/m0_75274681/DL/blob/main/7_CNN_%E9%A9%AC%E9%93%83%E8%96%AF%E7%97%85%E5%AE%B3%E8%AF%86.py

    Logo

    码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

    更多推荐