ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Matlab+CNN手写汉字识别:从跑通到重新训练与扩展类别

Matlab+CNN手写汉字识别:从跑通到重新训练与扩展类别 最近在后台收到不少读者问同一个问题学校里发的“基于 Matlab CNN 手写汉字识别”项目跑通了训练脚本、看到了准确率但下一步不知道该做什么。有人想换成自己的数据集有人想加几个汉字类别进去还有人干脆想知道这类项目到底能说明白什么问题。这个题目很典型。它来自一份开源课程设计项目标题里有两句话特别关键——“可以增加其它含义”和“代码可以重新训练”。但大多数人拿到手之后只会按部就班地点运行然后盯着准确率发一会儿呆就结束了。如果只是把它的训练脚本跑通那这篇文章可能帮不上你什么。但如果你想知道为什么这个项目值得重新训练、重新训练时大概率会在哪里翻车、以及怎么把一套“汉字识别”框架改造成你自己的分类工具那这篇内容应该能给你一个比较完整的答案。1. 为什么手写汉字识别是 CNN 入门最合适的试炼场很多入门教程喜欢拿 MNIST 手写数字识别当第一个例子。模型简单数据干净几行代码就能跑出 99% 的准确率。但说实话MNIST 的问题太规整了跑完之后你对“调参”这件事几乎没有什么感觉。手写汉字识别则完全不同。它的难度刚好在“想跑通很容易想做好很难”的区间里特别适合用来建立对卷积神经网络的实际手感。1.1 汉字识别的难点和 MNIST 完全不同MNIST 只有 10 个类别灰度图像尺寸统一字迹虽然稍有变化但整体结构非常稳定。汉字识别面对的则是另外一个局面。第一类别数量跨度极大。一个基础教学项目可能只包含几十个汉字但 GB2312 标准里的常用汉字就有 6763 个Unicode 中的汉字数量更多。几十类的分类任务和几千类的分类任务网络复杂度、训练难度、数据需求完全是数量级的差异。第二结构相似的字非常多。“己、已、巳”这三个字差别只在最后一笔的开口大小“土”和“士”、“日”和“曰”笔画长短稍有不同就是另一个字。传统图像处理方法对这类细微差异非常吃力而 CNN 能不能学到这种细粒度特征直接取决于训练数据的质量和网络的表达力。第三手写风格极不稳定。同一个人在不同状态下的书写可能差别很大更不用说不同人的运笔习惯。字的大小、倾斜角度、连笔程度、笔画粗细都会对识别造成干扰。而且项目标题里写的是“手写汉字”不是印刷体汉字。印刷体识别在光学字符识别时代就已经有比较成熟的方案了手写体才是深度学习真正发挥价值的地方。1.2 用 Matlab 做 CNN关键不在“写网络”而在“控流程”Matlab 的 Deep Learning Toolbox 已经封装好了卷积层、池化层、全连接层、分类层这些基础组件你不需要从零手写反向传播算法。这就意味着这个项目的重点不是“实现一个卷积神经网络”而是“完整跑通一个深度学习流程”。这句话值得反复强调一下。很多同学以为做完这个项目就学会了“手写 CNN”但实际你学到的是整个训练流程的编排能力数据怎么读取、图片怎么预处理、数据集怎么划分、网络结构怎么定义、训练参数怎么设置、训练完成之后怎么评估、怎么把模型保存下来再做推理。这套流程比“会写卷积层代码”重要得多。因为你在工作或者进一步研究时遇到的不可能是“请实现一个 CNN”这种题目而是“我这里有一批图片帮我做分类”或者“我现在这个模型效果不太好帮我看看是数据问题还是参数问题”。这时候你对流程的理解深度直接决定了你解决问题的速度。2. 从零跑通先让项目能转起来再谈其他拿到项目源码之后第一件事不是读代码而是先把流程跑通。这里的“跑通”不是指训练完成后看一眼准确率而是指你亲手确认了数据在流动、模型在学习、输出在产生。2.1 环境准备与项目解包Matlab 深度学习训练一般要求 R2021a 或更新版本因为新版本对神经网络层、训练选项和 GPU 加速的支持都更完善。如果使用的是老版本很可能遇到某些层不支持或者训练选项缺失的问题。需要确认的 Toolbox 主要有三个工具箱作用Deep Learning Toolbox构建和训练神经网络Computer Vision Toolbox图像读取、预处理、增强等Image Processing Toolbox图像尺寸调整、滤波等基础操作项目解压后一般会看到三个目录样本数据集、源代码、训练输出的模型保存目录。建议先读一下项目自带的说明文件确认数据组织方式。对于这类“源码包”数据目录里通常已经分好了类别文件夹每个文件夹名对应一个汉字类别里面放的是该汉字的样本图片。这里有一个很容易踩的坑中文路径。Matlab 对中文字符路径的支持在一些情况下会出现乱码或者读取失败尤其是早期版本。我的建议是把所有路径都改成英文项目目录也不要有中文。否则你可能会在数据加载阶段就浪费一晚上。2.2 数据准备与导入的核心步骤项目中通常会使用imageDatastore来管理图片数据。这个 API 是 Matlab 图像分类任务的核心它可以根据文件夹结构自动生成标签不需要你手动写一个 CSV 标签文件。下面是一个很典型的最小示例展示原始项目里读取数据的常见写法% 读取数据每个子文件夹对应一个类别 imds imageDatastore(dataset, ... IncludeSubfolders, true, ... LabelSource, foldernames); % 统计类别信息 tbl countEachLabel(imds); disp(tbl);比数据读取更关键的一步是尺寸统一。CNN 网络第一层的输入尺寸是固定的所有训练图片必须缩放到同一尺寸。常见的选择是 28×28、32×32 或者 64×64。教学项目为了降低训练压力常用比较小的尺寸。如果你发现训练出来的模型识别率很糟糕先怀疑图片预处理是否一致而不是急着换网络结构。% 将读取到的图片统一缩放为 64x64 imds.ReadFcn (filename) imresize(imread(filename), [64, 64]);接下来要把数据划分成训练集和验证集常见比例是 8:2 或者 7:3。用 70% 的数据训练30% 的数据验证避免模型只在训练集上表现好换一批数据就露馅。[imdsTrain, imdsValidation] splitEachLabel(imds, 0.7, randomized);这一步之后你可以用preview方法快速查看训练样本和对应标签。确认图片内容与标签一致再做训练。2.3 训练入口与初次验证项目的训练脚本通常会经历三个步骤定义网络结构、设置训练选项、调用trainNetwork训练模型。一个最常见的网络结构是这样的layers [ imageInputLayer([64 64 1]) convolution2dLayer(3, 8, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 16, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) fullyConnectedLayer(120) reluLayer fullyConnectedLayer(numClasses) softmaxLayer classificationLayer];训练选项通常如下options trainingOptions(sgdm, ... InitialLearnRate, 0.01, ... MaxEpochs, 20, ... Shuffle, every-epoch, ... ValidationData, imdsValidation, ... Plots, training-progress, ... Verbose, false);这里先说一个重要建议第一次跑通项目时不要改任何参数。先让默认配置把整个流程走完。这一步的目标不是调优而是确认数据读取没问题、网络定义了没问题、训练能够正常启动、损失曲线能够降下去、准确率能够打印出来。训练完成之后再拿测试图片集做一次推理验证。用训练好的网络对一张全新的图片做预测判断输出类别是否正确。只有走到这一步“跑通”才算完成。注意不要一上来就调整卷积核数量、层数或学习率。先用默认配置跑通再谈优化。改参数的前提是你能判断当前的瓶颈是在数据、网络结构还是训练配置。3. 关键参数与网络结构为什么不要急着调参项目跑通之后很多人会马上进入“调参”环节——把学习率改大一点把卷积层加厚一点然后重新训练。但几乎所有翻车现场都发生在这一步。原因很简单调参之前你没有先建立判断依据。3.1 学习率、BatchSize 和 Epoch 的真实含义这三个参数是训练过程中最常被调整的三个也是最容易被误用的三个。学习率决定的是每次参数更新的步长。学习率太大损失函数会在最小值附近震荡甚至发散表现为准确率剧烈波动学习率太小训练效率很低表现为损失下降得非常缓慢。教学项目里常见初始值在 0.001 到 0.01 之间。判断学习率是否合理的简单方式是看训练前几个 Epoch 的损失变化趋势。BatchSize 决定的是每次迭代计算梯度时使用的样本数量。它直接影响 GPU 或 CPU 的内存占用同时也影响梯度估计的稳定性。BatchSize 过大内存可能撑不住BatchSize 过小梯度方向波动大训练不稳定。教学项目的图片尺寸通常不大常见配置在 32 到 128 之间。Epoch 表示所有训练样本被完整训练过一遍的次数。不要盲目追求大 Epoch。正确做法是观察训练损失和验证损失的曲线如果验证损失开始上升而训练损失还在下降那就是典型的过拟合信号这时候继续训练已经没有意义。Matlab 里这些配置都集中在trainingOptions中。它的作用不再只是一个参数打包函数更像是一个训练策略的配置文件。options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 30, ... MiniBatchSize, 64, ... GradientThreshold, 1, ... ValidationFrequency, 30, ... Plots, training-progress);3.2 CNN 网络结构里最容易被忽略的三个点如果说训练参数决定“学不学得动”那网络结构就是“能不能学到”。对于教学项目来说网络结构不需要特别深但要搞清楚几个关键点。首先是输入层尺寸。imageInputLayer的第一个参数必须和你预处理后的图片尺寸完全一致。如果图片缩放成了 64×64 灰度图输入层就应该是[64 64 1]如果是 RGB 图就是[64 64 3]。维度不匹配时Matlab 会直接报错这反而是好事因为它能在训练前就发现错误。其次是全连接层的输出节点数。这一层必须和你的类别数一致。如果项目原本只识别 30 个汉字你想要新增 5 个汉字那么最后一个fullyConnectedLayer的输出节点数要改成 35。很多人改了数据却没有改这一层训练时直接报维度错误。最后是softmaxLayer和classificationLayer的搭配。这两层通常成对出现在分类任务中前者将输出转换为概率分布后者计算分类损失。如果你用trainNetwork训练分类层是必须的如果你只是构建网络做特征提取那分类层可以不加但一般的完整项目都需要保留。理解这三个点之后你再回头看项目里的网络结构代码就不会只看到一堆函数的堆叠而是能看出每一层存在的理由。4. 重新训练是最核心的功能也是最容易踩坑的地方回到标题里的另一个关键词——“可以重新训练”。这句话看起来平平无奇但恰恰是这类源码包真正的价值所在。一个训练好的模型如果只是用来做推理它就是一个固定的黑箱但如果你能重新训练它就变成了一套可以持续扩展的工具。4.1 增加新汉字类别的具体流程假设你的项目原本能识别 20 个汉字现在需要加上“猫”“狗”“鱼”三个字。很多人第一反应是把三个新文件夹加入数据目录然后直接运行训练脚本。结果大概率会得到一个维度不匹配的报错。正确的扩展流程应该是这样的第一步准备新类别的训练数据。每个新汉字收集几十到几百张手写图片放入对应的文件夹。图片格式要一致命名不要有空格推荐统一为cat_001.png这种风格。第二步更新imageDatastore。因为新文件夹会被自动识别为新类别这一步通常不需要改代码但一定要确认标签确实被正确读取了。第三步修改网络结构中的输出类别数。% 原本是 20 类 % fullyConnectedLayer(20) % 现在改为 23 类 fullyConnectedLayer(23)第四步重新训练。这里有两种策略可以选择从头训练全部数据一起参与训练网络参数随机初始化。适合新数据比较多、项目本身数据量不大的情况。微调训练加载已经训练好的网络替换最后一层然后用较小的学习率继续训练。适合新类别数量少、原模型已经学出了比较好的特征的情况。在常见实践里如果原始数据量不大从头训练往往更省心。如果原始模型已经在大量数据上训练过那么微调更合适。具体选择需要结合你的项目规模来判断不能一概而论。提醒重新训练之前先备份你已经训练好的网络。一旦新训练过程出现问题你至少能回到一个可用的版本。4.2 从“新增类别”到“改成其它含义”标题里那句“可以增加其它含义”其实是个更有意思的伏笔。它意味着这套项目的框架并不局限在汉字识别上。只要训练数据的形式还是“图片放在文件夹里文件夹名作为标签”你就可以把整个流程迁移到很多其他任务上印刷体数字识别英文字母识别简单交通标志分类猫狗图片分类零件外观分类关键改动只有两处数据换成新任务的数据网络输出节点数改成新任务的类别数。其他流程包括数据读取、预处理、训练、评估、推理全部可以复用。这就是“可以增加其它含义”的真正价值。它不只是一个汉字识别项目而是一套图像分类项目的最小可复用框架。理解到这一层你再看源码时就会有不同的视角你会去看数据是怎么组织的而不是盯着一行行网络层代码复制粘贴。5. 常见的坑与系统化排查顺序这类项目做久了你会发现训练阶段的报错其实很少大量时间都花在排查“与预期不符”上训练跑完了准确率不高损失下降很快验证准确率却一直不涨某些类别识别得不错某些类别几乎全错。这些问题的排查需要一套清晰的顺序。5.1 训练流程排查链路我的经验是按照下面的链路一层层排查通常可以快速定位问题先看现象。是训练报错还是准确率低是损失不降还是验证集和训练集表现差距很大现象不同排查方向完全不同。再看输入。图片尺寸是否统一灰度图和 RGB 图是否混用文件夹标签是否和图片内容一致用preview随机抽样看几批图片往往能发现数据标签错位的问题。看数据划分。训练集和验证集是否来自同一个分布如果某个类别数据量极少划分时又没有做分层抽样可能导致验证集缺失某些类别。看网络结构。输入层尺寸、全连接层输出节点数、是否有分类层这三个点前面已经提过是最高频的报错原因。看训练参数。学习率是否过大导致不收敛Epoch 是否过少导致欠拟合BatchSize 是否太大导致内存溢出看日志和可视化曲线。Matlab 的训练进度窗口会显示损失和准确率曲线这是判断训练是否健康的最重要依据不要忽略它。5.2 数据相关的高频问题训练流程本身没问题但效果不好的情况绝大多数原因在数据侧问题常见表现排查方向样本数量太少训练准确率高验证准确率低增加数据或使用数据增强类别不均衡某些类别准确率明显偏低收集更多低频类别的样本图片分辨率过低整体准确率不高增大输入图片尺寸标签错位某些类别准确率异常低可视化抽样检查训练集验证集分布不一致验证准确率波动大检查随机划分方式和数据来源还有一个非常容易被忽略的问题如果项目里保存了训练好的模型但你新增了训练数据想让原来的模型“认识”新数据一定要重新训练或者微调而不是直接拿旧模型做推理。旧模型的全连接层类别数没有变它没有能力输出新类别。6. 长期价值从小项目到可复用的分类框架最后想聊一下为什么这类基于教学源码的项目值得认真对待。很多人在学习深度学习时陷入了一个误区不断地看理论、刷论文、理解更多模型结构却很少把时间花在“怎样把一个小项目改造好”上。而手写汉字识别这类项目恰恰是理论到实践之间的那个中间地带。你能从中学到的最重要能力不是“用 Matlab 训练了一个 CNN”而是“当数据和任务发生变化时如何调整一个已有的训练流程”。这个能力在以后任何图像分类、目标检测、甚至文本分类项目里都适用。从工程经验角度来看我建议把这个项目当成一套分类框架来维护而不只是完成一次课程设计目录结构保持清晰数据、训练脚本、评估脚本、模型输出分开。训练脚本的参数集中管理方便反复实验。每次重训之前保存模型版本方便回退。记录训练后准确率和参数之间的关系形成自己的实验日志。如果你能花一个周末把项目里的汉字数据换成你自己感兴趣的一组图片分类任务哪怕只是做一个“猫狗分类器”你的收获可能会超过重新听一遍 CNN 理论课。这也是我对这个项目最核心的判断它真正的价值不是“用 CNN 识别汉字”而是“把你从跑通 Demo 这一步带到能够重新训练、扩展和迁移模型的位置”。下一次再拿到这类源码时不要只问“这个项目能跑吗”而是先问“如果我要改数据、改类别、改任务我需要动哪些代码”带着这个思路去读源码你会得到完全不同的信息量。
返回列表