深度学习1:卷积神经网络CNN
- 🍨 本文为🔗365天深度学习训练营中的学习记录博客
- 🍖 原作者:K同学啊
一 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()
最终运行结果如下:


三 相关练习
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
更多推荐


所有评论(0)