计算机视觉实验6
一、整体函数体系总览
所有函数围绕卷积神经网络(CNN)的完整训练/测试流程构建,可分为5类核心模块:
| 函数类别 | 对应函数 | 核心定位 |
|---|---|---|
| 核心计算层 | cnnff/cnnbp/cnnapplygrads |
前向传播、反向传播、参数更新(CNN核心) |
| 网络初始化 | cnnsetup |
初始化网络结构与可训练参数 |
| 梯度正确性校验 | cnnnumgradcheck |
验证反向传播的梯度计算是否正确 |
| 训练流程封装 | cnntrain |
多轮epoch+mini-batch训练循环 |
| 模型评估 | cnntest |
计算测试集错误率,评估模型性能 |
下面对每个函数进行逐行级的关键模块拆解,明确输入输出、核心逻辑、计算细节和作用场景。
二、核心计算层函数(CNN核心逻辑)
1. cnnff(net, x):前向传播函数
【核心功能】
给定输入数据x,逐层计算CNN各层的激活值(特征图),最终输出网络预测值net.o,并保存中间特征图和特征向量,是“从输入到预测”的正向计算过程。
【输入输出】
- 输入:
net(网络结构体,包含层定义、参数、中间变量)、x(输入数据,维度为H×W×N,H/W为单样本尺寸,N为样本数); - 输出:更新后的
net(包含各层激活值a、特征向量fv、预测输出o)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
net.layers{1}.a{1} = x; inputmaps = 1; |
初始化输入层:将输入x赋值给第一层激活值,inputmaps记录当前层输入特征图数(初始为1)。 |
for l = 2 : n(遍历2~最后一层) |
逐层计算卷积/池化层的激活值,区分层类型(c=卷积层,s=池化层)。 |
卷积层(type='c')循环 |
① 对每个输出特征图j,初始化临时特征图z;② 遍历所有输入特征图 i,用卷积核k{i}{j}与输入a{i}做valid卷积,累加至z;③ 加偏置 b{j},通过sigmoid激活得到a{j};④ 更新 inputmaps为当前层输出特征图数(供下一层使用)。 |
池化层(type='s')循环 |
① 对每个输入特征图,用scale×scale全1核卷积(等价于局部求和),除以scale²实现平均池化;② 按 scale步长下采样,得到池化后特征图a{j}(尺寸缩小为原来的1/scale)。 |
net.fv = [net.fv; reshape(...)] |
将最后一层的所有特征图展平为一维向量,拼接成fv(特征向量),作为全连接层输入。 |
net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(fv,2))) |
特征向量通过全连接权重ffW+偏置ffb,经sigmoid激活得到最终预测输出o(维度为类别数×样本数)。 |
2. cnnbp(net, y):反向传播函数
【核心功能】
根据预测输出net.o和真实标签y,计算损失L,反向传播误差(delta),求解所有可训练参数的梯度(卷积核dk、偏置db、全连接权重dffW/偏置dffb),是“从损失到梯度”的反向计算过程。
【输入输出】
- 输入:
net(前向传播后的网络结构体)、y(真实标签,one-hot编码,维度类别数×样本数); - 输出:更新后的
net(包含损失L、误差e、各层deltad、参数梯度dk/db/dffW/dffb)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
net.e = net.o - y; net.L = 1/2*sum(e(:).^2)/size(e,2) |
计算预测误差e(逐元素差),以及批量平均均方损失L(除以样本数避免批量大小影响)。 |
net.od = e .* (o .* (1-o)) |
计算输出层delta:均方损失对输出的导数 × sigmoid导数(链式法则第一步)。 |
net.fvd = ffW' * od |
全连接层误差反向传播:输出层delta × 全连接权重转置,得到特征向量delta。 |
net.layers{n}.d{j} = reshape(fvd, ...) |
将特征向量delta重塑为最后一层特征图尺寸,匹配卷积/池化层的维度。 |
for l = n-1 : -1 : 1(反向遍历层) |
从最后一层往输入层传播delta: ① 卷积层:delta = 激活值导数 × 下一层delta上采样结果; ② 池化层:delta = 下一层delta与旋转180°的卷积核做 full卷积(池化无参数,仅传递误差)。 |
net.layers{l}.dk{i}{j} = convn(flipall(a{i}), d{j}, 'valid')/样本数 |
计算卷积核梯度:输入特征图翻转后与当前层delta做valid卷积,除以样本数得到平均梯度。 |
net.layers{l}.db{j} = sum(d{j}(:))/样本数 |
计算卷积偏置梯度:delta所有元素求和,除以样本数得到平均梯度。 |
net.dffW = od * fv'/样本数; net.dffb = mean(od,2) |
计算全连接层梯度: ① 权重梯度=输出层delta × 特征向量转置 / 样本数; ② 偏置梯度=输出层delta按列求均值。 |
嵌套函数rot180(X) |
对卷积核做180°旋转(flipdim两次),是卷积反向传播的数学要求(保证梯度计算正确性)。 |
3. cnnapplygrads(net, opts):参数更新函数
【核心功能】
用批量梯度下降法,根据反向传播得到的梯度和学习率opts.alpha,更新所有可训练参数(卷积核、偏置、全连接权重/偏置)。
【输入输出】
- 输入:
net(包含梯度的网络结构体)、opts(训练配置,含学习率alpha); - 输出:更新后的
net(参数已按梯度下降更新)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
for l=2:numel(net.layers)(遍历卷积层) |
仅更新卷积层参数(池化层无训练参数)。 |
net.layers{l}.k{ii}{j} -= alpha * dk{ii}{j} |
卷积核更新:原参数 - 学习率×梯度(梯度下降核心公式)。 |
net.layers{l}.b{j} -= alpha * db{j} |
卷积偏置更新:同卷积核的梯度下降公式。 |
net.ffW -= alpha * dffW; net.ffb -= alpha * dffb |
全连接层参数更新:权重/偏置按梯度下降更新。 |
三、网络初始化函数:cnnsetup(net, x, y)
【核心功能】
根据输入数据x和标签y的维度,初始化CNN的结构参数(各层特征图尺寸)和可训练参数(卷积核、偏置、全连接权重),确保网络维度合法(如池化后尺寸为整数)。
【输入输出】
- 输入:
net(空网络结构体,含层类型/核尺寸/池化步长等配置)、x(输入数据)、y(真实标签); - 输出:初始化后的
net(包含随机初始化的参数、合法的层尺寸)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
inputmaps=1; mapsize=size(squeeze(x(:,:,1))) |
初始化输入层特征图数(1)和单样本尺寸mapsize(H×W)。 |
for l=1:numel(net.layers)(遍历所有层) |
逐层初始化尺寸和参数,区分卷积/池化层。 |
池化层(type='s') |
① 更新mapsize = mapsize/scale(池化后尺寸);② assert校验尺寸为整数(否则网络不合法);③ 偏置初始化为0(池化层无训练参数,仅预留)。 |
卷积层(type='c') |
① 更新mapsize = mapsize - kernelsize + 1(valid卷积后尺寸);② 计算 fan_in/fan_out(输入/输出连接数),用于Xavier初始化;③ 卷积核初始化: (rand-0.5)*2*sqrt(6/(fan_in+fan_out))(Xavier初始化,避免梯度消失/爆炸);④ 卷积偏置初始化为0; ⑤ 更新 inputmaps为当前层输出特征图数。 |
fvnum=prod(mapsize)*inputmaps |
计算最后一层特征图展平后的维度(全连接层输入维度)。 |
onum=size(y,1) |
输出神经元数(等于类别数,如MNIST为10)。 |
net.ffW = (rand-0.5)*2*sqrt(6/(onum+fvnum)) |
全连接权重Xavier初始化,偏置ffb初始化为0。 |
四、梯度校验函数:cnnnumgradcheck(net, x, y)
【核心功能】
通过有限差分法计算“数值梯度”,与cnnbp的“解析梯度”对比,验证反向传播的梯度计算是否正确(深度学习中梯度推导易出错,此函数是调试核心)。
【输入输出】
- 输入:
net(初始化后的网络)、x(输入数据)、y(真实标签); - 输出:无(若梯度误差超阈值则抛出
error)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
epsilon=1e-4; er=1e-8 |
初始化微小扰动epsilon(用于有限差分)、误差阈值er(超过则梯度错误)。 |
校验全连接偏置ffb |
① 对每个ffb(j),构造net_p(参数+ε)和net_m(参数-ε);② 分别前向+反向计算损失 L_p/L_m;③ 数值梯度: d=(L_p-L_m)/(2ε)(中心差分,精度更高);④ 对比 d与解析梯度net.dffb(j),误差超er则报错。 |
校验全连接权重ffW |
遍历ffW(i,u)的每个元素,逻辑与ffb完全一致,对比数值梯度与net.dffW(i,u)。 |
校验卷积层偏置b{j} |
遍历每个卷积层的偏置b{j},逻辑同ffb,对比数值梯度与net.layers{l}.db{j}。 |
校验卷积核k{i}{j}(u,v) |
遍历卷积核的每个像素(u,v),逻辑同ffb,对比数值梯度与net.layers{l}.dk{i}{j}(u,v)。 |
| 池化层代码注释 | 池化层无训练参数,无需梯度校验。 |
五、训练函数:cnntrain(net, x, y, opts)
【核心功能】
封装CNN的完整训练流程:多轮epoch、mini-batch数据打乱、前向→反向→参数更新,同时记录平滑损失(便于观察训练趋势)。
【输入输出】
- 输入:
net(初始化后的网络)、x(训练数据)、y(训练标签)、opts(训练配置:batchsize/numepochs/alpha); - 输出:训练后的
net(包含更新后的参数、训练损失记录rL)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
m=size(x,3); numbatches=m/opts.batchsize |
计算总样本数m和每个epoch的batch数,校验numbatches为整数(否则batch划分失败)。 |
net.rL=[] |
初始化平滑损失记录(避免损失波动太大)。 |
for i=1:opts.numepochs(epoch循环) |
多轮训练,每轮打印进度,tic/toc记录耗时。 |
kk=randperm(m) |
打乱样本索引(避免样本顺序固定导致过拟合)。 |
for l=1:numbatches(batch循环) |
遍历每个batch,执行核心训练步骤: ① 取打乱后的batch数据 batch_x/batch_y;② cnnff:前向传播;③ cnnbp:反向传播算梯度;④ cnnapplygrads:更新参数;⑤ 记录平滑损失: rL(end+1)=0.99*rL(end)+0.01*L(指数移动平均,降低波动)。 |
六、测试函数:cnntest(net, x, y)
【核心功能】
用测试集评估模型性能,计算预测错误率,并返回错误样本索引(便于分析错误原因)。
【输入输出】
- 输入:
net(训练后的网络)、x(测试数据)、y(测试标签); - 输出:
er(错误率)、bad(错误样本的索引)。
【内部关键模块拆解】
| 代码块 | 具体作用 |
|---|---|
net=cnnff(net,x) |
测试数据仅前向传播(无需反向传播),得到预测输出net.o。 |
[~,h]=max(net.o); [~,a]=max(y) |
① h:预测类别(取输出最大值索引);② a:真实类别(one-hot标签取最大值索引)。 |
bad=find(h~=a) |
找出所有预测错误的样本索引。 |
er=numel(bad)/size(y,2) |
计算错误率:错误样本数 / 总样本数。 |
七、整体流程串联
- 初始化:
cnnsetup根据数据维度初始化参数和结构; - 调试:
cnnnumgradcheck验证梯度正确性(仅调试阶段用); - 训练:
cnntrain多轮epoch循环,每batch执行“前向→反向→更新”; - 测试:
cnntest评估模型性能,输出错误率和错误样本。
1. 学习率是什么?该网络的学习率设置为多少?
(1)学习率的定义
学习率(Learning Rate,代码中用opts.alpha表示)是梯度下降优化算法的核心超参数,控制模型参数(卷积核、全连接权重等)的更新步长,数学公式为:
新参数 = 旧参数 - 学习率 × 参数梯度
- 学习率太大:参数更新步长过大,损失(loss)会震荡甚至不收敛;
- 学习率太小:参数更新缓慢,训练收敛耗时极长,甚至陷入局部最优。
(2)该网络的学习率设置
在test_example_CNN.m中明确设置:
opts.alpha = 1;
因此该网络的学习率为1(注:这个值偏大,MNIST任务中纯SGD的常规学习率是0.1左右,学习率=1易导致训练初期loss震荡,收敛变慢)。
2. 训练数据集大小?在每一轮训练中用到多少张图片?
(1)训练数据集大小
代码中加载MNIST数据集后,显式限定了训练集范围:
train_x = train_x(1:60000,:); % 训练集样本数:60000张
train_y = train_y(1:60000,:);
MNIST是手写数字数据集,标准训练集共60000张28×28的灰度图,因此该网络的训练数据集大小为60000张。
(2)每一轮训练用到的图片数
- 一轮训练(1个epoch)的定义是:遍历完整的训练数据集一次;
- 代码中
opts.batchsize=50(每批次50张),但每一轮会遍历所有60000张训练图(通过randperm打乱后分批次训练);
因此每一轮训练用到的图片数=训练集总大小=60000张(分60000/50=1200个batch完成)。
3. 大概训练多少轮,loss值收敛?
loss“收敛”指loss值不再明显下降,趋于平稳,结合该网络的配置(sigmoid激活+纯SGD+学习率=1)和MNIST任务特性:
(1)收敛轮数
- 前20~30轮:loss下降速度快(从高值快速降低);
- 50~80轮:loss下降速度显著变慢,趋于平稳;
- 80~100轮后:loss基本不再下降(完全收敛)。
(2)影响收敛速度的关键因素
该网络用sigmoid激活(易梯度消失)+ 纯SGD(无动量)+ 学习率偏大,收敛偏慢;若优化为ReLU激活+SGD动量(0.9)+ 学习率=0.1,收敛轮数可缩短至20~30轮。
4. 大概训练多少轮,测试集上的识别率不再提升?是多少?
识别率 = 1 - 错误率(cnntest返回的er),结合代码注释和MNIST该网络结构的实际表现:
(1)识别率不再提升的轮数
- 1轮训练:错误率≈11% → 识别率≈89%;
- 20~50轮:识别率快速提升(从89%→97%+);
- 80~100轮:识别率趋于峰值,不再明显提升;
核心结论:80~100轮后,测试集识别率不再提升。
(2)最终识别率
- 该基础版网络(sigmoid+纯SGD+无正则):测试集识别率≈98.5%98.8%(错误率≈1.2%1.5%);
- 若优化(ReLU+动量+L2正则):识别率可提升至99%+(错误率<1%)。
5. 变量onum、fvnum、ffb、ffW分别表示什么含义?
这些变量均在cnnsetup.m中定义,是全连接层的核心参数,结合代码和维度解释:
| 变量 | 代码定义(核心行) | 含义 | MNIST该网络的实际维度 |
|---|---|---|---|
| onum | onum = size(y, 1); |
全连接层的输出神经元数 = 手写数字类别数(MNIST是0~9共10类) | 10(10×1向量) |
| fvnum | fvnum = prod(mapsize) * inputmaps; |
全连接层的输入维度 = 最后一层特征图展平后的总元素数(特征向量长度) | 192(4×4×12) |
| ffb | net.ffb = zeros(onum, 1); |
全连接层的偏置向量(每个输出神经元对应1个偏置) | 10×1的零向量(初始化) |
| ffW | net.ffW = (rand(onum, fvnum)-0.5)*2*sqrt(6/(onum+fvnum)); |
全连接层的权重矩阵(连接最后一层特征向量到输出层) | 10×192的矩阵(Xavier初始化) |
关键补充(fvnum的计算示例)
MNIST输入28×28,网络结构6c-2s-12c-2s:
- 第一层卷积(5×5 valid):28-5+1=24 → 6个24×24特征图;
- 第一层池化(2×2):24/2=12 → 6个12×12特征图;
- 第二层卷积(5×5 valid):12-5+1=8 → 12个8×8特征图;
- 第二层池化(2×2):8/2=4 → 12个4×4特征图;
因此fvnum = 4×4×12 = 192(最后一层12个4×4特征图展平为192维向量)。
posted on 2025-12-12 09:25 swj2529411658 阅读(6) 评论(0) 收藏 举报
浙公网安备 33010602011771号