深入理解卷积神经网络(CNN):从原理到实践

引言

卷积神经网络(Convolutional Neural Networks, CNN)是深度学习领域最具影响力的架构之一,尤其在计算机视觉任务中表现出色。自2012年AlexNet在ImageNet竞赛中一战成名以来,CNN不断演进,推动着图像识别、医疗影像分析、自动驾驶等领域的快速发展。本文将系统介绍CNN的核心原理、关键组件和实际应用,并通过PyTorch代码示例展示如何构建一个CNN模型。

一、CNN的基本原理

1.1 传统神经网络的问题

在处理图像数据时,传统全连接神经网络(FCN)存在明显缺陷:

  • 参数爆炸:一张28×28的灰度图像展开后就有784个输入节点,若第一隐藏层有1000个神经元, 仅这一层就需要784,000个参数!

  • 忽略局部相关性:图像中相邻像素间存在强相关性,但全连接网络无法有效利用这种空间信息

  • 缺乏平移不变性:物体在图像中的位置变化会导致网络需要重新学习

1.2 CNN的三大核心思想

CNN通过以下设计巧妙解决了上述问题:

  1. 局部感受野(Local Receptive Fields)

每个神经元只连接输入区域的局部区域,而非全部输入

  1. 权值共享(Shared Weights)

同一特征图使用相同的卷积核,大幅减少参数数量

  1. 空间下采样(Spatial Subsampling)

通过池化层逐步降低空间分辨率,增加高层特征的感受野

> 参数数量对比

> 对于28×28图像,全连接层(隐藏层1000神经元)需要784,000参数

> 而5×5卷积核(32个通道)仅需5×5×32=800参数

二、CNN的核心组件详解

2.1 卷积层(Convolutional Layer)
python 复制代码
import torch.nn as nn

# 定义一个卷积层
conv_layer = nn.Conv2d(
    in_channels=3,    # 输入通道数(RGB图像为3)
    out_channels=16,  # 输出通道数/卷积核数量
    kernel_size=3,    # 卷积核尺寸
    stride=1,         # 步长
    padding=1         # 边缘填充
)
2.2 池化层(Pooling Layer)
python 复制代码
# 最大池化示例
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)

# 平均池化示例
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)

作用:

  • 降低空间维度,减少计算量

  • 增强平移不变性

  • 扩大感受野

2.3 激活函数

python 复制代码
# ReLU激活函数
activation = nn.ReLU()

# LeakyReLU示例
leaky_relu = nn.LeakyReLU(negative_slope=0.01)

三、经典CNN架构演进

python 复制代码
class CNN(nn.Module):
    def __init__(self):   #python基础关于类,self类自已本身
        super(CNN,self).__init__()   #继承的父类初始化
        self.conv1=nn.Sequential(
            nn.Conv2d(
                in_channels=1,
                out_channels=16,
                kernel_size=5,
                stride=1,
                padding=2,
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
        )
        self.conv2=nn.Sequential(
            nn.Conv2d(16,32,5,1,2),
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2),
        )
        self.conv3=nn.Sequential(
            nn.Conv2d(32,64,5,1,2),
            nn.ReLU(),
        )
        self.out=nn.Linear(64*7*7,10)
    def forward(self,x):
        x=self.conv1(x)
        x=self.conv2(x)
        x=self.conv3(x)
        x=x.view(x.size(0),-1)
        output=self.out(x)
        return output

四、PyTorch实现CNN实战

4.1手写数字识别:

python 复制代码
import torch

print(torch.__version__)

'''
MNIST包含70,000张手写数字图像:60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
'''
import torch
from torch import nn   #导入神经网络模块,
from torch.utils.data import DataLoader   #数据包管理工具,打包数据,
from torchvision import datasets    #封装了很多与图像相关的模型,数据集
from torchvision.transforms import ToTensor    #数据转换,张量,将其他类型的数据转换为tensor张量

'''下载训练数据集(包含训练图片+标签)'''
training_data=datasets.MNIST(
    root='data',           #表示下载的手写数字 到哪个路径。60000
    train=True,            #读取下载后的数据 中的 训练集
    download=True,         #如果你之前已经下载过了,就不用再下载
    transform=ToTensor(),  #张量,图片是不能直接传入神经网络模型
)    #对于pytorch库能够识别的数据一般是tensor张量。

'''下载测试数据集(包含训练图片+标签)'''
test_data=datasets.MNIST(
    root='data',           #表示下载的手写数字 到哪个路径。60000
    train=False,           #读取下载后的数据 中的 训练集
    download=True,         #如果你之前已经下载过了,就不用再下载
    transform=ToTensor(),  #Tensor是在深度学习中提出并广泛应用的数据类型
)    #NumPy数组只能在CPU上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度
print(len(training_data))

'''展示手写字图片,把训练数据集中的前59000张图片展示一下'''
from matplotlib import pyplot as plt
figure = plt.figure()
for i in range(9):
    img,label=training_data[i+59000]#提取第59000张图片

    figure.add_subplot(3,3,i+1)#图像窗口中创建多个小窗口,小窗口用于显示图片
    plt.title(label)
    plt.axis("off") #  plt.show(I)#是示矢量,
    plt.imshow(img.squeeze(),cmap="gray") #plt.imshow()将Numpy数组data中的数据显示为图像,并在图形窗口中显贡
    a = img.squeeze()# img.squeeze()从张量img中去掉维度为1的。如果该维度的大小不为1则张量不会改变。#cmap="gray
plt.show()

# 创建数据DataLoader(数据加载器)
# batch_size:将数据集分成多份,每一份为batch_size个数据
# 优点:可以减少内存的使用,提高训练速度。

train_dataloader=DataLoader(training_data,batch_size=64)
test_dataloader=DataLoader(test_data,batch_size=64)

'''断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU。'''
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")  #字符串的格式化

'''定义神经网络 类的继承'''
class CNN(nn.Module):
    def __init__(self):   #python基础关于类,self类自已本身
        super(CNN,self).__init__()   #继承的父类初始化
        self.conv1=nn.Sequential(
            nn.Conv2d(
                in_channels=1,
                out_channels=16,
                kernel_size=5,
                stride=1,
                padding=2,
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
        )
        self.conv2=nn.Sequential(
            nn.Conv2d(16,32,5,1,2),
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2),
        )
        self.conv3=nn.Sequential(
            nn.Conv2d(32,64,5,1,2),
            nn.ReLU(),
        )
        self.out=nn.Linear(64*7*7,10)
    def forward(self,x):
        x=self.conv1(x)
        x=self.conv2(x)
        x=self.conv3(x)
        x=x.view(x.size(0),-1)
        output=self.out(x)
        return output

model = CNN().to(device)
print(model)

def train(dataloader,model,loss_fn,optimizer):
    model.train()   #告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w。在训练过程中,w会被修改的
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train()和 model.eval()。
#一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval()
    batch_size_num=1
    for X,y in dataloader:       #其中batch为每一个数据的编号
        X,y=X.to(device),y.to(device)    #把训练数据集和标签传入cpu或GPU
        pred=model.forward(X)    #.forward可以被省略,父类中已经对次功能进行了设置。自动初始化
        loss=loss_fn(pred,y)     #通过交叉熵损失函数计算损失值loss
        # Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络
        optimizer.zero_grad()    #梯度值清零
        loss.backward()          #反向传播计算得到每个参数的梯度值w
        optimizer.step()         #根据梯度更新网络w参数

        loss_value=loss.item()   #从tensor数据中提取数据出来,tensor获取损失值
        if batch_size_num %100 ==0:
            print(f'loss:{loss_value:>7f} [number:{batch_size_num}]')
        batch_size_num+=1

def test(dataloader,model,loss_fn):
    size=len(dataloader.dataset)
    num_batches=len(dataloader)  #打包的数量
    model.eval()  #测试,w就不能再更新。
    test_loss,correct=0,0
    with torch.no_grad():    #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。
        for X,y in dataloader:
            X,y=X.to(device),y.to(device)
            pred=model.forward(X)
            test_loss+=loss_fn(pred,y).item()   #test_loss是会自动累加每一个批次的损失值
            correct+=(pred.argmax(1)==y).type(torch.float).sum().item()
            a=(pred.argmax(1)==y)   #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值
            b=(pred.argmax(1)==y).type(torch.float)
    test_loss /=num_batches
    correct /=size
    print(f'Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}')

loss_fn=nn.CrossEntropyLoss()   #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
optimizer=torch.optim.Adam(model.parameters(),lr=0.01)   #创建一个优化器,SGD为随机梯度下降算法
# #params:要训练的参数,一般我们传入的都是model.parameters()#
# lr:learning_rate学习率,也就是步长

#loss表示模型训练后的输出结果与,样本标签的差距。如果差距越小,就表示模型训练越好,越逼近干真实的模型。

# train(train_dataloader,model,loss_fn,optimizer)
# test(test_dataloader,model,loss_fn)

epoch=9
for i in range(epoch):
    print(i+1)
    train(train_dataloader, model, loss_fn, optimizer)

test(test_dataloader,model,loss_fn)

运行结果:

结语

CNN作为深度学习的基石架构,不仅在计算机视觉领域大放异彩,其核心思想也被广泛应用于语音识别、自然语言处理等领域。通过本文的系统介绍,希望读者能够建立起对CNN的全面认识,并能够动手实现自己的CNN模型。记住,理解原理只是第一步,持续的实践和创新才是掌握CNN的关键!

相关推荐
Ronin-Lotus2 分钟前
深度学习篇---Yolov系列
人工智能·深度学习
静心问道30 分钟前
GoT:超越思维链:语言模型中的有效思维图推理
人工智能·计算机视觉·语言模型
aneasystone本尊41 分钟前
学习 Claude Code 的工具使用(三)
人工智能
szxinmai主板定制专家43 分钟前
【精密测量】基于ARM+FPGA的多路光栅信号采集方案
服务器·arm开发·人工智能·嵌入式硬件·fpga开发
T__TIII1 小时前
Dify 自定义插件
人工智能·github
快起来别睡了1 小时前
LangChain 介绍及使用指南:从“会聊天”到“能干活”的 AI 应用开发工具
人工智能
AI数据皮皮侠1 小时前
中国区域10m空间分辨率楼高数据集(全国/分省/分市/免费数据)
大数据·人工智能·机器学习·分类·业界资讯
静心问道2 小时前
大语言模型能够理解并可以通过情绪刺激进行增强
人工智能·语言模型·大模型
运器1232 小时前
【一起来学AI大模型】算法核心:数组/哈希表/树/排序/动态规划(LeetCode精练)
开发语言·人工智能·python·算法·ai·散列表·ai编程
aneasystone本尊2 小时前
管理 Claude Code 的工具权限
人工智能