手写数字识别实战:从数据预处理到神经网络训练全流程
Wolfram U 的《AI初学者指南》我一口气追到了第五讲。说实话前四讲我是在沙发上抱着笔记本看的一边看一边点头觉得AI不过如此。第五讲讲完我默默把笔记本搬到书桌上开始敲代码因为这一期终于不再讲概念了——它丢过来一个非常具体的任务让计算机自己学会认出图片里的手写数字比如一张潦草的7模型要告诉我这是7。如果你也处在听过梯度下降、知道神经网络大概是什么但自己还没跑通过一个完整项目的阶段这一讲几乎是为你量身定的。它不追求一次把深度学习讲透而是让你以最快速度走完AI项目的最小闭环拿到数据、搭一个简单模型、训练、评估、再调一调。我做完了过程比想象中曲折但收获也远超预期。下面我把这一讲的思路、实操步骤和踩坑记录完整梳理一遍方便你照着来一遍。1. 动手前必须想清楚为什么第五讲突然开始实战了1.1 前四讲到底铺垫了什么这个系列课程的结构其实特别像一本教材的目录先让你知道AI能干什么再让你知道机器学习在干什么接着拆开神经网络看内脏最后讲模型是怎么学会的。前四讲分别解决的是四个递进问题第1讲铺垫的是世界观AI是什么从哪里来今天说的深度学习跟当年的专家系统到底有什么区别。第2讲铺垫的是方法论机器学习解决什么问题样本、特征、标签分别是什么监督学习跟无监督学习的边界在哪里。第3讲铺垫的是机器的主体神经元为什么叫神经元激活函数在做什么多层感知机怎么把一堆简单计算堆成复杂能力。第4讲铺垫的是学习机制损失函数怎么定义错误梯度下降怎么更新参数过拟合为什么是初学者绕不过去的坎。所以到第五讲知识储备刚好够支撑一个最小项目。这个时候不动手后面就全是空中楼阁——你听再多的梯度消失批量归一化也不如亲眼看着一条loss曲线掉下来来得踏实。1.2 第五讲的实战任务设计老师给的Demo是图像分类而且选了一个对于初学者而言近乎完美的任务手写数字识别。说它完美是因为它同时满足了三个条件第一数据获取几乎没有门槛。不需要爬虫、不需要标注原始照片课程环境里已经内置了一大批手写数字图片加载就能用。第二图片本身足够简单。灰度图、纯黑背景、类别明确一共就0到9十个数字不需要处理复杂的背景干扰。第三反馈极其直观。模型训练完之后随机抽几张测试图一眼就能看出它认对了还是认错了。整个Demo做下来你会完整经历一个AI项目的六个步骤载入数据、预处理、划分数据集、搭建模型、训练、评估。这六步听着平平无奇但它就是工业界所有AI项目的骨架。图像识别是它语音识别是它甚至现在的大语言模型产品也逃不出这个流程的变体。2. 动手前要把三个关键概念真正弄懂2.1 训练集、验证集、测试集的边界感第五讲在让你跑代码之前花了很大篇幅讲数据集划分。我当时觉得这有什么好讲的不就是把数据分成几份吗真正动手之后才知道边界感没建立起来后面全在自欺欺人。一个最简单的类比训练集是平时作业验证集是模拟考测试集是最终考试。你可以在作业上反复演算可以在模拟考上调整策略但最终考试卷子一旦提前看过了分数就没有任何参考意义了。实际操作中我用的划分比例是7:1.5:1.5也就是训练集70%验证集15%测试集15%。如果数据量很小可以用交叉验证但新手阶段先把三分法用熟比什么都重要。这里有一个很容易犯的错有人直接把原始数据切成三份却忘了在切之前打乱顺序。如果原始数据里前几千张全是数字0后几千张全是数字1切出来的三个集每个类别分布都不一样模型的训练状态就是歪的。2.2 损失函数和评估指标不是一回事这是第五讲里一个特别容易混的点。训练时计算机需要一个数值化的目标来指导参数更新这就是损失函数训练结束后我们需要关心它在真实任务上的表现那就是准确率、精确率、召回率这些评估指标。课程里用的是交叉熵损失。通俗理解就是模型预测错得越离谱扣分越狠。比如真实答案是7模型把70%的概率给了7那损失就很小如果模型把90%的概率给了1损失就非常大。准确率则是另一套逻辑每个样本都是一道判断题预测对了得1分错了得0分最后算平均分。这两者不是同一个东西交叉熵能在模型还半懂不懂的时候给出连续变化的反馈准确率只有在分类边界被跨过时才会跳变所以训练过程看loss汇报结果看acc。还有一个工具值得做出来看看混淆矩阵。准确率只能告诉你总分是多少混淆矩阵能告诉你是哪一类在拖后腿。比如我跑出来的模型整体准确率92%看混淆矩阵才发现4和9互相认错的样本特别多因为这两个数字在潦草写法下确实长得太像了。这直接决定了下一步改进方向是收集更多4和9的样本而不是盲目调模型。2.3 epoch、batch、学习率这三个参数是任何训练脚本里都绕不开的第五讲用了特别直观的方式讲清楚。一个epoch等于把整个训练集从头到尾过一遍。batch是一次送入模型的一组样本——训练集1000张图batch size设为32那一个epoch就要送31次多每次更新一次参数。学习率是参数更新的步子大小。用背单词书类比的话batch是每天复习30个单词epoch是整本单词书从头到尾背一遍学习率是每背一个单词后对记忆方法调整的幅度。步子大了容易跨过最优解步子小了又半天走不到头。课程给的起始配置是epoch10batch_size32learning_rate0.001。这三个数值不是随便拍的而是经验沉淀出来的常见起点32这个batch size在显存占用和梯度稳定性之间比较平衡0.001是Adam优化器最常用的初始学习率10个epoch足够让一个小模型在这个规模的图像数据上收敛。你完全可以改但第一次跑通时用这套默认值最省心。3. 跟着做搭一个能用的图像分类Demo3.1 准备数据没处理过的数据不能直接进模型开始之前先想清楚一个问题模型吃的是数字不是你眼里的像素。图片在计算机眼里就是一堆0到255的整数矩阵而且不同图片大小还不一样不可能直接塞进全连接网络。所以预处理的第一步是统一尺寸。不管原图是多大统统缩放到28×28像素。这个尺寸是早期手写数字数据集的经典标准信息量刚好够识别数字又小到训练速度快。第二步是转灰度图因为颜色对判断数字没有帮助反而会引入额外干扰。第三步是归一化把像素值从0到255缩放到0到1之间。这一步尤其关键我见过很多初学者在这里翻车。如果输入不归一化大数值的输入会让加权和变得很大激活函数的梯度很容易饱和训练速度会慢得离谱甚至直接不收敛。下面的代码是我在实际操作中整理出来的预处理逻辑import numpy as np from PIL import Image def load_and_preprocess(path): # 转成灰度图 img Image.open(path).convert(L) # 统一尺寸 img img.resize((28, 28)) # 转成numpy数组并归一化 arr np.array(img) / 255.0 # 展平成784维向量 return arr.reshape(-1)数据加载完别忘了划分。我用的是两次切分而不是一次切三份from sklearn.model_selection import train_test_split X_train, X_temp, y_train, y_temp train_test_split( X, y, test_size0.3, random_state42 ) X_val, X_test, y_val, y_test train_test_split( X_temp, y_temp, test_size0.5, random_state42 )先分出30%作为临时集再把临时集对半分。为什么不用一次切三份因为切分函数通常只支持一次切两份你要得到三个互不重叠的集合就得做两次切分第二次切分的对象是第一次剩下的部分。这样验证集和测试集保证没有任何交集。3.2 搭一个最简单的神经网络模型结构很简单只有三层from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense model Sequential([ Dense(128, activationrelu, input_shape(784,)), Dense(10, activationsoftmax) ]) model.compile( optimizeradam, losscategorical_crossentropy, metrics[accuracy] )输入层784个节点对应28×28的图片展开。隐藏层128个节点激活函数是ReLU。输出层10个节点对应0到9十个数字用Softmax把输出变成十个类别的概率分布。这段代码能跑通但要理解每个组件为什么存在。隐藏层节点数选128是因为任务足够简单用不着512甚至1024个节点的豪华配置。ReLU被选中的原因是计算成本低而且能缓解梯度消失。Adam是当前最省心的优化器自带学习率自适应调节对新手来说比手动调SGD温柔得多。我这里直接用了一个比较高的封装因为第五讲的目的不是让你从零手写矩阵运算而是让你理解整体流程。封装库帮你处理了反向传播、权重初始化、批量迭代这些工程细节你要做的只是把数据喂进去。3.3 训练、验证、看曲线训练代码本身很简单history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs10, batch_size32 )但真正的关键在训练结束后。你要把history里的loss和val_loss画成曲线import matplotlib.pyplot as plt plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.legend() plt.show()看到训练曲线的那一刻前四讲讲的所有概念一下子串起来了。如果train_loss不断下降、val_loss先降后升说明模型开始死记硬背训练集这就是过拟合的实时影像。如果两条线都在下降但数据波动很大大概率是学习率偏大或batch太小。这些判断不是靠直觉而是靠盯着曲线上的一条条拐点得出的。最后用测试集做一次终极评估test_loss, test_acc model.evaluate(X_test, y_test) print(test_acc)注意这一步是整个实验过程中测试集唯一一次被使用。如果你反复拿测试集验证自己的调参效果就等于把答案提前透露给了模型后面的任何结果都失去了可信度。按这套流程做下来准确率过90%是基本操作不同环境可能会有几个百分点的浮动都算正常。4. 新手最容易踩的五个坑与排查方法4.1 一个排查顺序原则数据到模型再到超参数第五讲最后列出了一些运行时的异常现象和对应解法但我觉得最值钱的不是哪一条具体方案而是排查问题的顺序。很多新手一上来就怀疑模型结构这个网络不够深、那个激活函数不够高级其实大概率是数据根本没喂对。我现在的排查顺序永远是先看数据再看模型最后才动超参数。看数据的范围有没有归一化、标签对不对、划分有没有打乱确认数据没问题后用最简单的模型跑一遍能跑通再谈加层最后才轮到调学习率、batch size这些参数。顺序一乱你会在错误的方向上浪费大量时间。4.2 常见问题速查表现象高频原因排查顺序loss完全不下降学习率过大/过小或数据没归一化打印x.min()和x.max()确认数据范围试lr0.01、0.001、0.0001训练集准确率很高验证集很低过拟合减少隐藏层节点数、加Dropout、增加数据量训练曲线震荡剧烈batch太小或学习率太大增大batch_size到64降低学习率到0.0001内存耗尽/训练崩溃batch过大或图片尺寸不一致降batch到16、8检查预处理是否统一了尺寸每次跑结果差很多没有固定随机种子在数据切分和模型初始化时设置random_state/seed4.3 我踩过的一个坑忘了归一化这个坑必须单独拎出来说因为它的教训太典型了。我第一次跑Demo准确率一直卡在45%左右怎么调模型都不动。试了加大隐藏层、换激活函数、调学习率全部无效。最后打印了一下输入数组发现像素值还是0到255的原始范围归一化根本没执行。把像素除以255之后同一个模型、同一份参数直接冲到90%以上。这件事让我彻底意识到数据预处理不是可有可无的流程而是模型能不能学的分水岭。很多看似是模型太弱的问题根子都在数据没洗干净。4.4 另一个坑在测试集上反复挑模型这个坑是我在完成课程练习时犯过的。第一次测试集准确率92%我不满足回去改了改网络结构又用测试集验了一下变成93.5%。第三次再改准确率没涨反而跌了。表面看是调参失败实际上问题更严重——我已经用测试集做了两次决策那93.5%已经不属于严格的最终评估了。正确的做法很朴素测试集只许碰一次。所有模型结构对比、超参数选择全都在验证集上完成等到模型定型之后再用测试集做唯一一次检验。如果你觉得自己需要多次验证那就说明你该拆分出更多的训练数据而不是反复偷看测试集。4.5 课程里的隐藏设置随机种子初学者很容易忽略随机性。数据切分是随机的权重初始化是随机的batch抽取的顺序也是随机的。这意味着同样的代码你每次跑出来的准确率都可能不一样。如果差一两分还在合理范围但如果你要对比两个模型的谁优谁劣不固定随机种子对比结果就是一团乱账。所以在数据切分代码里加random_state42在模型训练前设置numpy、tensorflow的随机种子。这样至少实验是可复现的别人拿着你的代码也能跑出相近的结果。这是职业习惯第五讲没重点强调但每个踩过这个坑的人都会补上。5. 学完这一讲之后可以继续做什么5.1 换一个真实的场景Demo里的手写数字干净、背景统一、类别平衡真实场景完全不是这样。用自己的手机拍几张照片试着做一个垃圾分类或植物叶子识别的小项目你会发现难点从模型结构转移到了数据本身——光照变化、角度偏移、背景杂乱每一个都会让准确率暴跌。这其实是第五讲给你留的思考题当数据不再友好时预处理要怎么做、数据增强要不要上、模型容量够不够。这些问题不做一次真实项目永远只是在试卷上见过。5.2 做一组超参数消融实验把隐藏层节点数改成64、128、256各跑一遍把学习率改成1e-2、1e-3、1e-4各跑一遍把batch size改成16、32、64各跑一遍。每跑完一组记录验证集准确率画出一张对比表。这件事的意义不在于找出最好的参数而是让你亲眼看到每个超参数对训练曲线的影响幅度。看完这组实验你对梯度下降和过拟合的理解会比读十篇文章都透彻。我做完之后才对为什么默认配置是128、0.001、32有了真正的体感。5.3 试试预训练模型当你能够顺利调通自定义的小网络之后可以试一下预训练模型。这些模型经过大规模数据训练对图像特征的提取能力远超你在小型数据集上从零训练的网络。你要做的只是把最后的全连接层换掉保留前面已经学好的特征提取部分在自己的任务数据上做微调。这也是当前实际项目中最常见的做法属于深度学习应用的真实常态。从自己搭全连接网络到用预训练模型这条路径走完AI项目的完整骨架就真正立起来了。我个人学完整个第五讲最大的体会是AI入门最大的障碍不是数学也不是代码而是那种我以为懂了其实没有的错觉。第五讲把这种错觉用一次完整的实操彻底戳破了。真正跟着做一遍损失函数、过拟合、验证集这些东西才从脑子里的概念变成了手里的工具。如果你正准备开始别只盯着视频看一定要亲手把那个手写数字分类的Demo跑起来跑通了再回来复盘每一步到底在解决什么问题。