恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
TensorFlow实战:用Keras构建ANN,手写数字识别全流程详解
首页
资讯中心
/
TensorFlow实战:用Keras构建ANN,手写数字识别全流程详解
TensorFlow实战:用Keras构建ANN,手写数字识别全流程详解
发布时间:2026/10/14 22:04:41
直接说吧这篇是TensorFlow学习之路的第二篇。上一篇我们把环境、基本张量操作、自动求导这些地基打完了这一篇就该上手第一个真正的模型了。我会用最直白的方式把神经网络ANN从概念讲到代码落地全程用TensorFlow的Keras接口实现不整那些花里胡哨的底层API适合刚摸到深度学习门槛、想快点看到模型跑起来的人。我尽量把“为什么这么做”讲清楚而不是丢一段能跑的代码让你复制就完事。毕竟你现在把模型跑通了下个月遇到一个新问题换个数据集如果不懂背后的逻辑照样寸步难行。1. 神经网络到底在解决什么问题1.1 机器学习是一场“猜函数”的游戏先抛开神经网络那些吓人的术语。深度学习的本质是让计算机自己找一个函数“映射关系”你给我一堆输入我告诉你输出。比如输入是房屋面积、楼层、朝向输出是房价输入是一张图片的像素点输出是“猫”还是“狗”。传统的编程方式是人来写这条规则如果面积大于100且楼层在10层以上价格就是多少。但真实世界里的规则太复杂根本没法用if-else穷举于是我们换了个思路——不写规则只准备大量的“输入-输出”例子让模型自己从例子里归纳规则。这个过程就叫训练。所以你可以把神经网络理解成一个极其灵活的“万能函数拟合器”它内部有成千上万个参数权重和偏置训练的过程就是不断调整这些参数让模型在已知数据上的预测结果尽量接近真实答案。1.2 ANN和“深度学习”到底是什么关系ANN全称Artificial Neural Network人工神经网络。它是深度学习最基础的形态我们常说的深度神经网络DNN本质上就是一种层数较多的ANN。ANN的基本结构就三样东西输入层、隐藏层、输出层。输入层负责接收原始数据比如一张28x28的图片可以拉平成784个像素值隐藏层负责对数据进行层层加工、抽象提取出越来越高级的特征输出层给出最终预测结果。层与层之间的每个神经元都通过“权重”连接权重的大小就代表前一层某个神经元对后一层某个神经元的影响程度。我一直觉得把ANN比作一条流水线特别贴切原材料原始数据从第一个工位输入层进去每个工位隐藏层神经元都做一点加工加权求和并激活最后在包装工位输出层得出成品预测结果。如果加工工序够多够合理就算是很复杂的原材料也能做出想要的东西。2. 手工拆解一个神经元感知机与激活函数2.1 单个神经元的工作过程神经网络的基本单元叫神经元学术上叫感知机。它的工作流程其实特别简单四步走接收来自上一层神经元的输入值 x1, x2, x3…每个输入分别乘以对应的权重 w1, w2, w3…再求和加上一个偏置值 b相当于给神经元一个初始的“偏向性”把求和结果送入一个激活函数得到最终输出数学表达式就是 y activation(w1x1 w2x2 w3*x3 b)。前面那部分线性加权求和模型能表达的空间是有限的它只能画出一条直线来区分数据。但真实世界的数据哪儿有那么规整基本都是弯弯曲曲的边界。激活函数的作用就是往这个线性结果里注入“非线性”让模型有能力拟合弯曲的边界。我举个具体场景假设你想判断一个人是否适合买某款保险输入是年龄、收入、健康分三个特征。如果没有激活函数模型只能学到一个线性的加减规则比如“年龄乘0.3加收入乘0.2再减健康分乘0.1大于某个数就买”。但现实里往往是“年龄太大太小都不行收入中等偏好最好”这种非单调的复杂规律这时候没有非线性的激活函数模型就算把参数调到天荒地老也学不出来。2.2 激活函数怎么选常见的激活函数就那几个但选哪个很有讲究我用一张表总结一下激活函数输出范围优点缺点常用位置Sigmoid0到1输出可解释为概率平滑容易梯度消失输出非零中心二分类输出层Tanh-1到1零中心比Sigmoid强仍有梯度消失问题全连接隐藏层早期ReLU0到正无穷计算快缓解梯度消失神经元可能“坏死”隐藏层首选Softmax0到1且和为1多分类概率分布依赖输入尺度多分类输出层隐藏层现在基本不用Sigmoid和Tanh了ReLU是默认选项。原因很简单Sigmoid在输入值绝对值比较大的时候导数趋近于0误差信号传不回去网络就练不动了。ReLU在正区间导数恒为1梯度能顺畅地往回传训练速度快很多。不过ReLU有个坑叫“神经元坏死”。如果某个神经元的加权求和结果一直是负数ReLU输出永远为0它的梯度也永远是0这个神经元就再也激活不了。解决办法可以换Leaky ReLU这类变体但实际项目里标准ReLU配合合理的学习率设置绝大多数情况都够用不用过度纠结。3. 用TensorFlow落地一个ANN3.1 准备一份练手数据集理论说完了必须上手写代码。我选一个图像分类任务手写数字识别。数据集自带60000张训练图片和10000张测试图片每张图是28x28的灰度图代表数字0-9。任务是训练一个模型输入图片像素输出它是哪个数字。这个任务在深度学习里的地位基本等同于编程界的“Hello World”足够简单又不失代表性。首先是加载数据TensorFlow里直接一行代码就搞定。如果你本地网络不稳定数据集下载可能会卡住建议提前准备好本地副本。import tensorflow as tf # 加载内置数据集 (x_train, y_train), (x_test, y_test) tf.keras.datasets.mnist.load_data() print(训练集形状:, x_train.shape) # (60000, 28, 28) print(测试集形状:, x_test.shape) # (10000, 28, 28) print(标签示例:, y_train[:10]) # [5 0 4 1 9 2 1 3 1 4]你会看到训练数据是一个三维数组6万张图每张图是28行28列的二维像素矩阵。像素值范围在0到255之间255代表白色0代表黑色。3.2 从零定义一个两隐藏层的ANN数据准备好了接下来构建模型。我用Keras的Sequential模型一层一层地堆叠最直观也最适合新手理解。# 构建模型 model tf.keras.Sequential([ # 输入层把28x28的图片拉平成784个特征 tf.keras.layers.Flatten(input_shape(28, 28)), # 第一个隐藏层128个神经元ReLU激活 tf.keras.layers.Dense(128, activationrelu), # 第二个隐藏层64个神经元ReLU激活 tf.keras.layers.Dense(64, activationrelu), # 输出层10个神经元对应0-9十个数字 tf.keras.layers.Dense(10, activationsoftmax) ]) # 查看模型结构 model.summary()先看Flatten层它只是把数据“拉直”28x28变成784不参与计算。然后是两个Dense全连接层Dense的参数第一个是神经元数量第二个是激活函数。我故意用了128和64这样常见的配置给大家一个直观感知——层数不用多神经元数量也不用贪大这个量级处理手写数字绰绰有余。输出层的10个神经元对应10个类别用softmax激活输出的是一个概率分布。比如一张图片经过模型后输出一个长度为10的向量每个位置的值代表这张图属于对应数字的概率10个值加起来等于1概率最大的索引就是模型的预测结果。3.3 编译、训练与评估模型定义完需要先“编译”这一步是配置学习过程。这里初学者最容易懵我拆开讲# 编译模型 model.compile( optimizeradam, # 优化器 losssparse_categorical_crossentropy, # 损失函数 metrics[accuracy] # 关注指标 ) # 训练模型 history model.fit( x_train, y_train, epochs10, # 遍历整个训练集10次 batch_size32, # 每次喂32张图 validation_split0.2, # 从训练集分出20%做验证集 verbose1 )optimizer优化器是“怎么调整参数”的策略。adam可以说是目前最省心的默认选择它自带自适应学习率对新手极其友好基本不用调参就能有个不错的效果。loss损失函数是“预测得有多差”的度量。因为我们的标签是0-9的整数属于多分类问题所以用sparse_categorical_crossentropy。注意那个sparse前缀意思是标签是整数直接用如果你的标签是one-hot编码的向量就要用不带sparse的categorical_crossentropy。这两个名字是新手最容易搞混的地方。训练过程中的verbose参数建议设成1能实时看到每一轮训练结束后的损失和准确率。epochs设为10batch_size设为32这两个参数是训练时的核心超参数后文我会详细讲它们的影响。训练完成后用测试集评估test_loss, test_acc model.evaluate(x_test, y_test) print(测试准确率:, test_acc)第一次跑这个流程测试准确率应该能到97%以上。你都没做任何图像预处理也没精心调参就这么简单粗暴几层全连接就能达到这个效果可见手写数字识别任务本身相对简单同时说明ANN确实具备了一定的特征提取能力。4. 训练过程中的几个关键细节4.1 数据归一化最容易忽略却最关键的一步我故意在上面的代码里跳过了归一化。如果你直接跑准确率大概也能到90%出头但收敛速度明显慢很多而且最终的准确率上限会被压住。如果我把像素值归一化到0到1之间效果立刻不一样。# 归一化把像素值从0-255压缩到0-1 x_train x_train / 255.0 x_test x_test / 255.0为什么这一步这么重要因为神经网络的参数更新依赖梯度的大小而梯度的大小跟输入数据的尺度直接相关。当输入值是几百这种大数时加权求和的结果会非常大经过激活函数后容易落在饱和区梯度变得极小参数几乎推不动。把数据缩放到0-1后神经元的工作区间落在激活函数的敏感区域梯度传递顺畅模型学得又快又好。打个比方你量身高用厘米、量体重用千克这两个数字的数值范围差很多。如果直接把原始数值喂给模型体重的数值天然比身高大模型会误以为体重这个特征更重要分配更多权重给它。归一化就是让所有特征站在同一条起跑线上。这个操作还有一个更高级的版本叫标准化Standardization公式是 (x - mean) / std让每个特征的均值是0、方差是1。归一化的取值范围控制在0-1标准化的分布中心在0各有适用场景。但绝大多数图像任务直接除以255就够了。4.2 损失函数和优化器你必须理解的两个核心我前面提到loss是衡量“预测有多差”这个说法比较粗。准确地说它定义了一个“距离”预测的概率分布和真实标签的距离。模型训练的目标就是不断调整参数让这个距离越来越小。损失函数的选择跟任务类型强相关。回归问题预测房价、温度这种连续值用均方误差MSE二分类用binary_crossentropy多分类就是前面用的sparse_categorical_crossentropy。选错了损失函数模型要么训练不动要么输出的结果让你看不懂。优化器则是“沿着哪个方向、走多大步”来减小损失。adam的内部机制比较复杂它综合了动量法和自适应学习率的思路能够根据每个参数的历史梯度动态调整步长。所以相比传统的梯度下降adam收敛更快、更稳对学习率的设置也更宽容。我还是建议你亲手试一下用不同的损失函数和优化器组合感受它们的区别。有对比才有体会比如你用均方误差跑分类任务Loss会降低特别慢准确率也上不去踩过一次坑就明白“正确的工具做正确的事”这有多重要了。4.3 从绘制训练曲线读模型状态fit函数会返回一个History对象里面有每一轮训练后在训练集和验证集上的loss和accuracy。把这些数据画出来是判断模型状态最直观的手段。import matplotlib.pyplot as plt # 训练曲线可视化 plt.plot(history.history[loss], label训练集损失) plt.plot(history.history[val_loss], label验证集损失) plt.legend() plt.show()训练曲线至少要会看三种情况第一种训练集loss和验证集loss同步下降最后都趋于平缓这是最理想的“过拟合”状态模型学得很好。第二种训练集loss持续下降但验证集loss下降到某一轮后开始反弹上升这是过拟合的信号——模型把训练集上的细节背得太死丧失了泛化能力。第三种两种loss都高居不下甚至震荡起伏说明模型根本没学好可能要回过去查学习率是否太大、数据是否需要预处理、网络结构是否不合理。我现在每跑一个模型都会顺手把训练曲线画出来这个习惯帮我排查了太多问题。光看最终测试准确率你根本不知道中间发生了什么。5. 常见问题与排查手册5.1 Loss不下降或者乱跳模型训练了好几轮Loss纹丝不动或者像过山车一样忽高忽低。这时候按照下面的顺序排查排查步骤具体操作原因1. 检查数据预处理确认归一化/标准化是否做了数据尺度不一致导致训练不稳定2. 降低学习率把Adam默认学习率从0.001降到0.0001学习率太大导致参数来回震荡3. 简化模型减少隐藏层神经元数量模型太复杂导致梯度异常4. 检查标签确认y_train的标签值在合法范围内标签错误会让loss直接异常我在实际调试时发现新手最常犯的错误是不做归一化就直接训练尤其是输入数据范围特别大的时候Loss输出经常是nanNot a Number。一旦出现nan第一反应该去查数据预处理而不是调网络结构。5.2 过拟合了怎么办验证集准确率上不去、测试集表现明显差于训练集这就是过拟合的典型症状。我提供一个自用的“三步疗法”第一步增加训练数据量。这是最朴素但往往最有效的方法数据不够的时候一切正则化手段都是亡羊补牢。第二步加Dropout层。原理极其简单粗暴训练时随机让一部分神经元“失活”强迫网络不依赖某几个特定的神经元从而提高泛化能力。model tf.keras.Sequential([ tf.keras.layers.Flatten(input_shape(28, 28)), tf.keras.layers.Dense(128, activationrelu), tf.keras.layers.Dropout(0.3), # 随机丢弃30%的神经元 tf.keras.layers.Dense(64, activationrelu), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(10, activationsoftmax) ])第三步加L2正则化即在损失函数上追加一个权重的平方惩罚项让模型不敢把权重学得太大。Keras里直接在Dense层加kernel_regularizer参数就行。5.3 验证集loss一直比训练集高这个现象很多新手会紧张以为模型出问题了。实际上轻微的验证集loss高于训练集是完全正常的因为模型本来就在训练集上“见过答案”自然会考得更好一点。但如果差距越来越大就要警惕过拟合参考5.2的解法。反过来还有一种离奇情况验证集loss比训练集还低。这并不代表模型更好往往是因为验证集分布恰好比训练集更“容易”或者是Dropout在训练时发挥了随机失活的“拖后腿”作用导致训练时表现被拉低了而验证时Dropout关闭了。这同样正常不用太纠结。5.4 新手最容易踩的TensorFlow环境坑代码逻辑都对了但一跑就报错十有八九是版本问题。TensorFlow的API改过太多次了网上很多老教程里的写法在最新的2.x版本里已经废弃。我现在用的方式是创建项目时先固定版本比如tensorflow2.10.0然后所有代码都在这个版本下调试避免线上教程和本地环境“打架”。另一个很常见的坑是CPU版本的TensorFlow训练太慢。新手没必要一开始就去配GPU环境先拿小数据集跑通流程感受一下深度学习的完整流程之后再逐步迁移到显卡上。当然如果你手头有支持CUDA的显卡那就直接按官方文档装好GPU版训练速度能快几十倍。6. 从ANN开始的学习路径建议ANN内容到这里基本讲完了。你现在应该能够独立完成加载数据、构建一个简单的全连接网络、训练评估模型的完整流程也了解了归一化、损失函数、优化器、过拟合这些核心概念。这套知识框架能支撑你后续学习CNN卷积神经网络和RNN循环神经网络因为底层逻辑都是相通的。我个人的建议是不要在ANN上恋战太久。全连接网络虽然结构简单但处理图像时参数数量太大且无法利用空间结构这是它天然的短板。学完这篇下一步直接奔着CNN去。CNN通过卷积核共享权重既能大幅减少参数又能很好地提取图像的局部特征是真正让你感受到深度学习“威力”的模型。再往后可以按这个路径走CNN处理图像、RNN/LSTM处理序列数据、自编码器做特征压缩、注意力机制和Transformer模型处理文本和长序列。每一步都像搭积木一样递进但地基就是今天这个简单的ANN。最后说点实在的。我见过很多初学者在理论阶段徘徊太久不断刷课、抄公式但就是不动手敲代码。深度学习这门学问眼睛会了不算会手会了才是真会。试着把上面这个手写数字识别的模型改一改比如把两层改成三层、把神经元改成256、把激活函数换成tanh观察每一处的效果差异。改坏了大不了重新跑一遍反正训练也就几分钟的事但脑子里留下的“手感”是任何教程都替代不了的。