
简介这份Matlab深度学习人脸识别源码包面向计算机、电子信息工程及数学专业的大学生适用于课程设计、期末大作业或毕业设计中的算法验证与功能参考。代码基于MatConvNet框架实现覆盖卷积、池化、批归一化、SoftMax分类等核心网络层模块并包含DagNN网络封装及数据预处理相关函数适合有一定深度学习基础、需要自行调试与二次开发的读者作为参考资料。资源共44个文件全部为m脚本压缩包仅37KB结构紧凑、命名规范便于按模块快速定位并理解网络构建逻辑可直接导入Matlab运行调试。目前已有1224人学习浏览适合作为人脸识别算法入门或课程项目实现的可行参考。需注意源码为通用实现而非定制方案使用时建议结合自身环境适配参数并具备独立解决运行报错的能力。1. 为什么在深度学习时代还要用Matlab做人脸识别先想清楚适用边界看到“基于Matlab实现深度学习人脸识别”这类标题先别急着否定Matlab。中小规模的人脸识别场景这套基于Deep Learning Toolbox的迁移学习方案比现搭一套Python环境更容易跑通也更容易被懂行的人审阅。深度学习人脸识别落到工程上是三个环节照片数据整理、预训练网络迁移、识别Demo验证。标题指向的这类源码包常见实现都围绕这条线展开。直接说适合谁需要快速出原型的工程师、做项目预研的学生、团队已经习惯了Matlab又不想为一个人脸识别引入第二套技术栈的人。数据量不大、人员名单固定、要在一周内拿出可演示结果的任务Matlab反而省时间。本篇记录按同样的顺序展开先讲技术路径和选型再复现数据准备与训练最后给避坑清单和验收方法。2. 人脸识别在Matlab里的技术骨架分类头、特征向量与验证流程做深度学习人脸识别Matlab不是拿来改配置的玩具它的数据流和训练过程是完整可审计的。动手前先想清楚一个模型在工程上是“认人”还是“验证人”。这个选择决定了最后一层的写法也决定了后续踩坑的类型。2.1 先决定你的模型要解决哪种问题封闭集还是开放集直接分类的路线把任务当成图像分类网络最后接一个全连接层输出维度等于已知身份数再接softmax用交叉熵损失逼近目标。识别时输入一张脸输出一个概率分布概率最高的就是预测身份。这套结构实现最直接收敛稳定适合人员基本固定的考勤类场景。开放集验证的路线把网络当成特征提取器取最后一层之前的特征向量对脸图做归一化用余弦距离与特征库里的样板做比较。距离小于阈值判为同一个人否则判为陌生人。它不需要在训练标签里引入“陌生人”这个类适合门禁、访客这类不断出现新面孔的场景。维度直接分类封闭集特征向量验证开放集训练标签身份类别可复用分类训练得到的特征层网络输出各类别概率特征向量 距离比较遇到陌生人被强行归到某个已知人可设阈值拒绝适用场景固定人员考勤门禁、新增人员频繁实现成本低低但需要做特征库维护这类源码包的常见实现是先走分类路线把训练跑通把识别率做到可用再在部署层加一道特征距离判断来拦住陌生人。两个阶段共用同一个网络主干代码成本不高。先弄清自己处在哪一端再看下面的实现才不会走偏。2.2 Matlab深度学习工作流一个数据流的完整骨架Deep Learning Toolbox把训练管线拆成几个明确角色imageDatastore负责把照片和标签组织起来augmentedImageDatastore负责在喂入网络前完成resize和数据增强layerGraph负责搭建和修改网络结构trainNetwork执行训练classify和activations负责推理。% 骨架Matlab深度学习人脸识别的完整数据流 imds imageDatastore(dataset, IncludeSubfolders, true, ... LabelSource, foldernames); % 子文件夹名即标签 [imdsTrain, imdsVal] splitEachLabel(imds, 0.8, randomized); inputSize [224 224 3]; augmenter imageDataAugmenter(RandRotation, [-10 10]); auimdsTrain augmentedImageDatastore(inputSize, imdsTrain, ... DataAugmentation, augmenter); % 训练集做增强 auimdsVal augmentedImageDatastore(inputSize, imdsVal); % 验证集只resize lgraph layerGraph(resnet18); % 这里必须替换末端全连接层和分类层见第4章骨架不能直接训练 options trainingOptions(sgdm, InitialLearnRate, 1e-4); net trainNetwork(auimdsTrain, lgraph, options);注意几点imageDatastore通过子文件夹名自动生成categorical类型的标签不需要手工维护一个标签文件augmentedImageDatastore的响应标签继承自原datastore不需要另外指定trainNetwork的第二个参数是层图或层数组不是已经训练好的网络。把这条链理顺后面所有调参和排查都围绕它转。习惯上我会先把这段骨架在本地跑一遍哪怕只放几个身份、每人十几张图用analyzeNetwork检查一次网络结构。这样做能在动手前拦下绝大部分“层名找不到”“维度对不上”“分类层数量不对”的问题。2.3 为什么迁移学习能压缩工程量预训练权重、冻结层与调参范围为什么用迁移学习而不是从零搭网络因为人脸识别工程场景通常只有几千到几万张图从零训练的卷积网络在这个量级很难收敛出稳定的通用特征。预训练权重在通用大图上已经学到边缘、纹理、形状这些基础特征能省掉大量样本和时间。迁移学习真正需要管理的是三件事哪些层要更新、哪些层别碰、更新得多快。选主干网络我倾向两个ResNet-18和ResNet-50。样本量小、机器普通就用18速度快、内存占用低精度对小数据集足够样本量过万且追求更高精度再上50。加载预训练网络在Matlab里是一行命令首次会联网把权重下载到本机缓存离线环境记得提前准备好权重文件。要清楚ResNet类网络的末端结构。以常见版本为例末端三层是全局池化、全连接层和分类层。全局池化是特征出口把空间信息压成一个固定维度的向量全连接层负责把特征映射到N类得分分类层负责计算交叉熵损失。工程里最常见的做法是把全连接层替换成自己的尺寸给新层一个较大的学习率backbone保持很小的学习率或直接冻结。这是最短路径也是后面第4章展开的内容。3. 把照片整理成能训练的数据集目录结构、拆分与增强数据永远是副作用最大的一环。人脸识别训练集不是把照片堆在一起就完事目录结构、划分方式和增强策略合在一起提前决定了模型能学到什么。这一章把数据从磁盘到内存的完整过程走一遍。3.1 目录结构让文件夹自动成为标签最省事的打标签方式是目录结构。在dataset/train下面为每个身份建一个文件夹文件夹名就是标签。imageDatastore读取时开启IncludeSubfolders和foldernames标签源标签自动变成categorical类型直接作为分类任务的y。dataset/ ├── train/ │ ├── person_01/ │ │ ├── person_01_day01_front.jpg │ │ ├── person_01_day02_side.jpg │ ├── person_02/ │ └── person_03/ └── val/ ├── person_01/ └── person_02/文件名本身不重要但建议带上采集日期或角度标识。这不是格式要求而是为了后面排查数据泄漏和脏数据时能直接溯源。对比之下不推荐把所有人照片全部堆在一个目录再手工维护一个标签表格。那样第一眼方便后面每次增删样本都要同步改表格很容易出现“图在、标签没”或反过来。imageDatastore支持jpg/png/bmp等常见格式。同一批训练里不要混入已经做过拼接、加过水印、带大面积遮挡的图。这类困难样本可以留到验证阶段当压力测试但别先混进训练集否则模型会花大量容量去拟合异常模式。3.2 拆分训练集与验证集比例好设批次难分直接用splitEachLabel做最常见的8比2拆分代码非常短imds imageDatastore(dataset/train, ... IncludeSubfolders, true, ... LabelSource, foldernames); countEachLabel(imds) % 先看每个身份的样本量 imds shuffle(imds); [imdsTrain, imdsVal] splitEachLabel(imds, 0.8, randomized);splitEachLabel按类别比例分配样本randomized表示先随机打乱再切。0.8代表每个身份80%进训练、20%进验证。注意验证集每个身份至少留一张身份太少时这个比例可能切出空集所以先把countEachLabel的统计表打出来看一眼。最常翻车的是连续帧场景。如果某段视频按帧抽图同一时间抽出的几十张几乎一模一样随机拆分时很可能同时出现在训练集和验证集。训练时模型已经见过几乎相同的脸验证分数当然虚高一换场景就现原形。提示验证集最可靠的做法是“时间隔离”而不是“随机隔离”。让训练照片和验证照片采集于不同日期或不同环境指标才有参考价值。如果不想重新组织目录退一步的做法是按文件编号排序取前80%做训练、后20%做验证至少避免同批次照片被随机打散。这是我处理临时数据时经常采用的妥协方案。3.3 增强与归一化增强只补多样性不补质量augmentedImageDatastore把resize和增强放在一起做训练集用增强验证集只做resizeinputSize [224 224 3]; augmenter imageDataAugmenter(... RandRotation, [-15 15], ... % 角度扰动 RandXScale, [0.9 1.1], ... % 水平方向缩放 RandYScale, [0.9 1.1], ... % 垂直方向缩放 RandXTranslation, [-10 10], ... % 水平平移 RandYTranslation, [-10 10], ... % 垂直平移 FillValue, 128); % 边界填充中性灰 auimdsTrain augmentedImageDatastore(inputSize, imdsTrain, ... DataAugmentation, augmenter); auimdsVal augmentedImageDatastore(inputSize, imdsVal); % 无增强增强不是越狠越好。人脸是有结构约束的物体旋转超过30度、缩放超过1.2倍会产生非人脸形变模型学到的东西偏离真实场景。人脸识别里常见的做法是采用温和的参数范围让模型看到更多的“偏移但仍然是同一个人”的样本。增强参数常用范围说明RandRotation±10° 到 ±20°再大就不再是正脸结构RandXScale / RandYScale0.9 到 1.1轻微缩放模拟距离变化RandXTranslation / RandYTranslation±10 到 ±30 像素模拟人脸对齐误差FillValue128 或 127平移旋转露出的边界用中性灰填充在放大规模训练之前我习惯先取2到3个身份、每个身份十几张图跑一遍完整流程把从数据到损失曲线的链路打通。这个小实验通常十几分钟能提前暴露层名、维度、文件读取等基础问题比拿全量数据跑一个通宵再报错要划算得多。4. 迁移学习训练人脸分类器换掉ResNet最后一层是最短路径数据就绪后接下来是把预训练网络改成自己的分类器。常见做法是换掉ResNet的全连接层让输出维度等于身份数量。这里的关键在替换哪些层、被替换的层叫什么名、新层学习率怎么设以及要不要冻结backbone。4.1 加载预训练网络用ResNet-18还是ResNet-50在Matlab里加载预训练ResNet-18很简单net resnet18; analyzeNetwork(net)analyzeNetwork会打开网络结构图界面虽然偏大但值得看一次。你不需要理解每个残差块只记住末端层的结构全局池化层是特征层后面的全连接层负责分类最后是分类层。后面要处理的就是这几个层。首次加载时Matlab会联网把权重下载到本机的预训练目录之后离线可用。内存紧张时优先用resnet18而不是resnet50前者的占用和训练速度优势明显在小数据集上的精度差距并不大。如果训练过程中频繁出现内存问题先查一下当前加载的是不是50。4.2 替换最后一层让输出维度等于身份数替换层是迁移学习里最核心的一步。先取出层图再根据身份数重建全连接层和分类层lgraph layerGraph(net); numClasses numel(categories(imdsTrain.Labels)); % 身份类别数 newFc fullyConnectedLayer(numClasses, ... Name, fc_person, ... WeightLearnRateFactor, 10, ... % 新层随机初始化学习率放大10倍 BiasLearnRateFactor, 10); newClf classificationLayer(Name, class_person); lgraph replaceLayer(lgraph, fc1000, newFc); lgraph replaceLayer(lgraph, ClassificationLayer_fc1000, newClf); analyzeNetwork(lgraph)全连接层的输出维度必须等于已知身份数。WeightLearnRateFactor设为10是因为新层是随机初值如果和backbone用同一个学习率几百个迭代内很难跑到和预训练权重匹配的尺度。这个因子把新层的学习率放大训练初期快速就位backbone保持默认学习率或冻结预训练特征不会被破坏。层名以最常见的预训练网络版本为准。不同Matlab版本或不同权重格式下最后分类层可能不叫ClassificationLayer_fc1000。写替换代码前先用lgraph.Layers查看末端实际层名这一步能避免一大半报错。4.3 冻结策略什么时候冻结什么时候整体微调冻结backbone有三种常见做法按数据量从小到大排列。第一种是全部解冻backbone用很小的学习率整体微调。适合数据量中等、希望特征做一点适应调整时。第二种是冻结backbone只更新新加的全连接层把backbone各层的LearnRateFactor统一设为0for k 1:numel(lgraph.Layers) layerName lgraph.Layers(k).Name; if isprop(lgraph.Layers(k), WeightLearnRateFactor) lgraph setLearnRateFactor(lgraph, layerName, 0); end end冻结后特征提取部分不更新训练时间缩短也不容易过拟合。人脸识别的预训练特征已经足够强样本少于几千张时只做新的分类头训练通常就够用。第三种是先冻结训练几个epoch再解冻微调第一段跑出可用的分类头第二段用更小学习率让backbone适应任务。效果最好代价是要分两段盯训练曲线。4.4 训练参数与早停让训练过程可控而不是玄学训练选项直接影响收敛质量我常用的迁移学习参数组合如下options trainingOptions(sgdm, ... InitialLearnRate, 1e-4, ... % 迁移学习保守值 MiniBatchSize, 16, ... % CPU建议8~16GPU可到32 MaxEpochs, 20, ... Shuffle, every-epoch, ... ValidationData, auimdsVal, ... ValidationFrequency, 20, ... % 每20个迭代评估一次 Plots, training-progress, ... OutputFcn, (info)earlyStop(info)); net trainNetwork(auimdsTrain, lgraph, options);InitialLearnRate取1e-4是迁移学习的保守起点。1e-3偏激进如果验证损失在头几个epoch就开始震荡说明学习率大了。MiniBatchSize在CPU上建议8到16GPU上16到32。ValidationFrequency按迭代数生效20表示每20个迭代在验证集上评估一次。配合早停函数可以避免训练进入平台期后继续空跑浪费时间function stop earlyStop(info) persistent bestAcc cnt; if isempty(cnt) || info.State start bestAcc 0; cnt 0; end stop false; if info.State iteration ... isfield(info, ValidationAccuracy) ... ~isempty(info.ValidationAccuracy) acc info.ValidationAccuracy(end); if acc bestAcc bestAcc acc; cnt 0; else cnt cnt 1; stop cnt 3; % 连续三次未刷新纪录即停止 end end if info.State done bestAcc []; cnt []; end endOutputFcn在每个迭代后被调用函数返回true时训练中止。注意一个细节早停返回的网络不一定是验证集最优的那一版。严谨的做法是在回调里把验证准确率最高的网络快照单独保存或者记录对应epoch后重新训练到那一轮。数据量小时差异不大正式交付前值得处理。训练必须可见。把Plots开成training-progress随时看损失曲线走势远程执行或长时间训练时再关掉避免图形窗口占用资源。许多网络问题只能靠曲线走向来判断黑匣子式训练很难排查。5. 人脸识别训练避坑过拟合、脏数据、类别失衡与资源耗尽训练跑通只是第一步真正的麻烦在评估阶段。数据、验证方式、资源限制三方面的问题会让模型在指标上很好看、用起来却处处翻车。下面几条按踩坑频率排序遇到问题时也建议按这个顺序排查。5.1 训练集准确率飘高但验证集掉队先查数据泄漏现象训练准确率很快到95%以上验证集稳定在70%上下。第一反应是调学习率或改网络结构但大多数情况问题出在数据划分。原因连续帧按比例随机切分同一个人的相似照片同时进入训练集和验证集验证指标虚高。另一个常见来源是增强位置放错训练用了增强datastore验证也误用了同一个增强datastore。解决验证集只做resize不做增强按采集日期整体切分而不是随机切。把两个集的文件路径并排对比如果同一个文件夹下的同一批编号在两边大量出现基本就是数据泄漏。任何基于同一段录像切出来的训练集和验证集指标都没有说服力。5.2 换个光线和镜头就翻车数据多样性比调参更重要现象训练的模型在顺光实验室环境里识别率很高一旦换到走廊、靠窗或夜景环境识别率明显下降。原因训练样本镜头单一、光照单一模型把亮度、白平衡、背景这些环境信息当成了身份特征。增强里只加了旋转和缩放没有覆盖光照差异。解决训练时加入不同时段的照片注册特征库时每个身份至少保留两个光照条件下的样本。算法层面对人脸做对齐后再输入网络也能减少亮度干扰。但根本做法是让训练数据覆盖实际部署环境增强只能补多样性补不了缺失的环境维度。5.3 类别不均衡样本最少的那个身份总是认错现象20个身份里有两个身份只有5张照片其他身份有100张训练后少数类全被分错。原因softmax分类目标受先验影响多数类的梯度占主导少数类的决策边界被挤压。解决简单可靠的做法是把少数类复制补齐到接近多数类水平imds imageDatastore(dataset/train, ... IncludeSubfolders, true, ... LabelSource, foldernames); tbl countEachLabel(imds); target max(tbl.Count); % 以最多类为基准 minority tbl.Label(tbl.Count target); for i 1:numel(minority) files imds.Files(imds.Labels minority(i)); n numel(files); for k 1:(target - n) src files{randi(n)}; [p, name, ext] fileparts(src); copyfile(src, fullfile(p, [name _dup num2str(k) ext])); end end补完之后必须重新构造imageDatastore。旧的imds在创建时已经缓存了文件列表原地复制文件继续训练新文件不会被读到这是最容易忽视的一步。复制出来的样本本质是同一张图的重复依赖增强产生差异所以复制倍数不要超过多数类的样本量太多否则少数类反而会过拟合。5.4 训练中断与内存耗尽从Out of Memory到意外退出现象训练中途报内存错误或者CPU训练跑了一个小时还在第一个epoch。原因MiniBatchSize太大、输入图像分辨率远高于网络要求的224、验证集也做了复杂增强。CPU训练时验证频率太高会拖慢整体速度。解决把MiniBatchSize从32调到16或8把augmentedImageDatastore的outputSize设为网络要求的224不要直接用原图尺寸ValidationFrequency从10提高到50远程训练时把Plots改成none用训练日志曲线判断效果。还不行就换成resnet18而不是resnet50。6. 封装人脸识别Demo用特征距离拒绝陌生人并做增量注册训练收敛之后模型已经能对已知身份分类但要交付成可用的识别人脸Demo还差最后一步面对训练名单之外的人要说出“不认识”。直接分类做不到这一点softmax会把任何输入强行归到概率最高的已知身份上。把模型从分类模式切到验证模式就能补上这个短板。6.1 用pool5特征向量替代分类得分加一层余弦距离人脸识别的常用做法是取网络特征层输出做身份比对featureLayer pool5; % ResNet-18的全局池化特征层 % 注册库为每个已知身份提取特征 galleryFeat activations(net, auimdsGallery, featureLayer, OutputAs, rows); galleryFeat normalize(galleryFeat, 2); % 待测图单张图提取特征 im imread(test.jpg); testFeat activations(net, im, featureLayer, OutputAs, rows); testFeat normalize(testFeat, 2); sim testFeat * galleryFeat; [bestSim, idx] max(sim); if bestSim threshold label unknown; else label galleryLabels(idx); endResNet-18的pool5把整张脸压缩成512维向量。注册库里的每人照片提取特征后得到galleryFeat矩阵待测图特征与它做内积归一化后等价于余弦相似度值越接近1越像同一个人。相似度低于阈值判为未知否则取最相似身份作为结果。阈值怎么定把验证集分成两类计算同类相似度分布和异类相似度分布取两个分布的交叠点附近。常用范围在0.5到0.8之间数据越规整越靠高值。注册库在提取特征时要同步保存galleryLabels否则相似度最高的身份无法对应人名。6.2 增量注册新人员不需要重训整个网络这套结构带来一个实用能力新增人员不需要重训网络。新成员拍几张正面照走一遍activations把特征追加进galleryFeat再把标签追加进galleryLabels即可。只有当误识明显增多、或新人与现有人员长相接近时才需要重新训练分类头。我建议把验证拆成三组数据注册集只用于建库校准集用于选阈值测试集必须是训练之外另采集的照片。三组数据分开后模型的真实水平才暴露出来。模拟项目X曾有段时间指标很漂亮交付前发现校准阶段漏了暗光样本现场光线一暗误识率立刻上升。从那以后我习惯把采集环境条件写进验证记录而不是只看“准确率98%”这个数字。数据覆盖程度一般比网络结构更能决定人脸识别能不能用希望帮到你。本文还有配套的精品资源点击获取