计算机视觉实验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) 计算错误率:错误样本数 / 总样本数。

七、整体流程串联

graph TD A[cnnsetup初始化网络] --> B[可选:cnnnumgradcheck校验梯度] B --> C[cnntrain训练网络:每batch执行cnnff→cnnbp→cnnapplygrads] C --> D[cnntest测试模型,计算错误率]
  1. 初始化:cnnsetup根据数据维度初始化参数和结构;
  2. 调试:cnnnumgradcheck验证梯度正确性(仅调试阶段用);
  3. 训练:cnntrain多轮epoch循环,每batch执行“前向→反向→更新”;
  4. 测试: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:

  1. 第一层卷积(5×5 valid):28-5+1=24 → 6个24×24特征图;
  2. 第一层池化(2×2):24/2=12 → 6个12×12特征图;
  3. 第二层卷积(5×5 valid):12-5+1=8 → 12个8×8特征图;
  4. 第二层池化(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)    收藏  举报

导航