ARTICLE DETAIL

资讯详情

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

前馈神经网络实战:Fashion-MNIST图像分类全流程解析

前馈神经网络实战:Fashion-MNIST图像分类全流程解析 简介基于Fashion-MNIST数据集的神经网络图像分类完整代码面向初学深度学习的开发者和学生用于解决时装图像自动分类问题并帮助读者理解从数据预处理到模型训练评估的全流程。压缩包共2个文件包含1个Python脚本和1个Word文档整体仅559KB十分轻量。Python脚本覆盖数据归一化、标签one-hot编码、卷积层与池化层特征提取、全连接层分类、交叉熵损失与Adam优化器配置、迭代训练与准确率评估等关键环节配套文档进一步说明实现思路与实验步骤便于逐段对照代码学习。该数据集包含T恤、连衣裙、运动鞋等10个衣物类别是MNIST手写数字识别之外更贴近实际场景的入门练习也可直接用作课程作业或上机实验参考模板。已有297人学习下载适合快速掌握图像分类及TensorFlow/PyTorch框架用法。1. 神经网络实现fashion数据分类为什么这是你该先跑通的第一个工程拿到“神经网络实现fashion数据分类代码.zip”这样的压缩包里面装的几乎都是同一件事用神经网络去对Fashion-MNIST这个服装图像数据集做10分类。Fashion-MNIST有10个类别、60000张训练图、10000张测试图每张图片是28×28的灰度像素常被拿来替代手写数字MNIST作为入门任务。对你来说最有价值的不是那份准确率数字而是代码把数据加载、网络搭建、训练、评估四个环节完整串了一遍——这正是你理解前馈神经网络里正向传播和反向传播的最短路径。本文按我实际整理这类代码包的习惯把每个环节的选型理由、参数含义和复现时最容易翻车的点展开讲新手照着能跑通熟手也可以核对一下自己的参数习惯。2. 把Fashion-MNIST喂给神经网络加载、归一化与批次设计2.1 从load_data开始先搞清原始数据的形态再动手绝大多数这类代码包数据加载用的是Keras自带的数据集接口核心就两行from tensorflow.keras.datasets import fashion_mnist (x_train, y_train), (x_test, y_test) fashion_mnist.load_data() print(x_train.shape, y_train.shape, x_test.shape, y_test.shape) # 输出: (60000, 28, 28) (60000,) (10000, 28, 28) (10000,)代码逻辑说明load_data()返回两个元组分别对应训练集和测试集。x_train是60000张28×28的灰度图像素值范围在0255之间y_train是60000个整数标签取值09对应10个服装类别。为什么这里不分验证集因为这类入门代码包通常会在训练阶段用validation_split参数从训练集里切一块出来后面第4章会细讲。参数说明这里要养成的第一个习惯是“先看形状再写下一步”。很多新手拿到代码包后直接跳去建模型把(60000,28,28)的三维数组塞进Dense层然后报错说维度对不上接着开始怀疑model写错了。其实model没错是数据没展平。先打印shape再决定下一步能省掉很多不必要的排查时间。我一般会顺手把10个整数标签映射成可读的类别名因为训练时打印出来全是数字你根本看不出模型错在哪个类上class_names [T恤/上衣, 裤子, 套头衫, 连衣裙, 外套, 凉鞋, 衬衫, 运动鞋, 包, 踝靴] print(class_names[y_train[0]]) # 看看第一张图到底是什么这步不参与训练但最后看混淆矩阵时你会回来感谢它。Fashion-MNIST里有个经典坑标签4是外套、标签6是衬衫两者像素轮廓非常接近标签5是凉鞋、标签7是运动鞋、标签9是踝靴脚部类之间也容易混。名字映射出来之后你才能直观理解为什么某些类别准确率总是偏低。2.2 归一化与维度变换float32、除以255和784的关系接着是预处理标准写法如下x_train x_train.astype(float32) / 255.0 x_test x_test.astype(float32) / 255.0 x_train_flat x_train.reshape(-1, 784) x_test_flat x_test.reshape(-1, 784)代码逻辑说明第一行把像素从0255的uint8整数变成0.01.0的float32浮点数。神经网络对输入尺度敏感大数值直接进入网络后会把激活函数推向饱和区导致梯度接近零、反向传播几乎学不动。第二行把每张28×28的二维矩阵展平成784维向量这是全连接网络的输入格式——每张图像变成一个特征向量Dense层接收的就是这个形态。参数说明astype(float32)不是可选项。如果漏掉TensorFlow在训练时会提示数据类型不匹配或者内部强制转换造成精度损失。除以255.0是灰度图归一化的固定做法换成别的缩放比如除127.5再减1不是不行但对于Fashion-MNIST这类简单任务归一化到[0,1]已经是绝大多数代码包的统一基线先按这个来再谈其他。有个问题经常在读者群里被反复问为什么不能保留二维结构直接喂Dense层原因是全连接层本质是矩阵乘法输入必须是一个二维矩阵[batch_size, feature_dim]每行是一个样本的特征向量。reshape(-1, 784)里的-1表示“这个维度由框架自动推导”相当于自动适配batch大小。如果你想保留二维空间结构那就不是前馈神经网络的范畴了得去用卷积神经网络那是第6章要聊的升级方向。2.3 验证集怎么切手动切还是validation_split代码包里切验证集有几种常见写法先看手动切分的那一种from sklearn.model_selection import train_test_split x_train_part, x_val, y_train_part, y_val train_test_split( x_train_flat, y_train, test_size0.1, random_state42 ) print(x_train_part.shape, x_val.shape) # 输出: (54000, 784) (6000, 784)代码逻辑说明train_test_split会把训练集按9:1切成新的训练集和验证集。验证集不出现在梯度更新里只用于每个epoch结束后评估模型在未见数据上的表现目的是及早发现过拟合。参数说明test_size0.1对应切出6000张作为验证集这个比例在数据量充足时是合理的太小则验证集方差大太大则训练数据不足。random_state42固定切分方式保证多次运行用的是同一批验证样本这样不同次的实验结果才可以互相比较。如果你嫌麻烦直接用上面说的validation_split0.1也行。两种做法的区别在于validation_split是从数据尾部按顺序切出10%而train_test_split默认会先shuffle再切。对于Fashion-MNIST数据集本身已经随机打乱过顺序切和随机切差别不大。但如果你想复现别人的准确率数字注意他代码里用的是哪种切法——这个细节会让最终准确率差零点几个百分点。3. 前馈神经网络搭建结构、激活函数与损失函数怎么选3.1 网络结构设计为什么128-64-10这个组合够用代码包里的模型通常长这样from tensorflow.keras import layers, models model models.Sequential([ layers.Input(shape(784,)), layers.Dense(128, activationrelu), layers.Dense(64, activationrelu), layers.Dense(10, activationsoftmax) ]) model.summary()代码逻辑说明Sequential是顺序模型数据从输入层依次流过三个全连接层。第一个Dense层有128个神经元接收784维输入做一次线性变换加非线性激活第二个Dense层64个神经元继续提取高维特征最后输出层10个神经元对应10个类别用softmax把输出变成总和为1的概率分布。中间层叫隐藏层这是标准的前馈神经网络结构也叫多层感知机。参数说明中间层神经元数128和64是性价比很高的平衡点。神经元太少拟合能力不足准确率上不去太多则参数量变大、训练变慢、更容易过拟合。我见过有人一上来堆512-256-128三层训练时间翻倍测试准确率只涨零点几个百分点还更容易过拟合。对Fashion-MNIST这种简单分类128-64已经足够跑出88%左右的准确率。这里要聊一下输入层的写法。layers.Input(shape(784,))显式声明了输入维度好处是模型summary能正确显示参数数量且在你误传错误shape的数据时能更快暴露问题。老代码里常见第一层直接写layers.Dense(128, activationrelu, input_shape(784,))效果一样只是表达方式不同。如果你在代码包里看到这两种写法不用纠结它们是等价的。3.2 激活函数与损失函数ReLU、Softmax和交叉熵为什么是成套的网络结构和损失函数是一起决定的对应代码里这行model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] )代码逻辑说明这是训练前的配置步骤。optimizer选adam是多分类任务里默认选择它对学习率不敏感收敛稳定loss选sparse_categorical_crossentropy它对应“标签是整数”的情况——你的y_train是09的整数而categorical_crossentropy要求标签是one-hot编码。这个sparse前缀是新手最容易忽略的细节。参数说明如果你的数据预处理阶段已经做了one-hot编码标签形状变成(60000,10)这里就要换成losscategorical_crossentropy。用错的话模型不会直接报错但准确率会异常低——这是最隐蔽的坑之一。我判断这个问题的标准做法是先看y_train.shape是(60000,)就用sparse是(60000,10)就用不带sparse的版本。至于激活函数为什么中间层用ReLU而不是sigmoid可以从反向传播的角度理解。sigmoid在输入绝对值较大时梯度趋近于零多层叠加后梯度连乘会迅速衰减前面层的权重几乎得不到有效更新。ReLU在正区间梯度恒为1能有效缓解梯度消失让前馈神经网络在反向传播时把残差顺利传回前面的层。输出层的softmax加交叉熵是个成套组合softmax把分值平滑地变成概率分布交叉熵衡量预测分布和真实分布的差异两者配合在数学上能推导出形式简洁的梯度项训练时数值更稳定。一个实用的判断标准第一次训练时如果loss起始值在2.30附近说明模型在随机猜测状态。因为ln(10)≈2.30这是10分类的随机熵值。看到loss从这个数值开始往下走说明正向传播和反向传播的实现是正确的如果起始值明显偏离2.30比如只有0.5那就要怀疑是不是数据泄漏或标签错乱了。4. 训练与调参把模型跑起来并让它收敛4.1 核心训练代码fit函数的真实含义训练阶段用一行fit就能启动全部流程history model.fit( x_train_flat, y_train, epochs10, batch_size32, validation_split0.1, verbose1 )代码逻辑说明fit启动一个完整的训练循环。每个epoch会把全部训练数据分成多个batch每个batch依次做正向传播预测、计算损失、反向传播求梯度、更新权重。validation_split0.1表示从训练集尾部切出6000张作验证集每个epoch结束时在这个子集上评估一次但不参与梯度更新。参数说明epochs10对128-64-10这个结构来说10个epoch足够让loss进入平台期。如果你看history曲线发现验证loss还在明显下降可以加到1520如果训练loss持续下降但验证loss开始回升那是过拟合信号应该早停在验证loss最低的epoch。batch_size32一次送入32张图计算平均梯度。这个值太小如8梯度噪声大、收敛慢太大如256收敛速度快但泛化能力可能略降。32是图像分类任务最常见的起点不要自己去发明新值。verbose1控制进度条打印。等于0时静默运行等于2时每个epoch打一行结果。训练结束后测试集评估是最后一步test_loss, test_acc model.evaluate(x_test_flat, y_test) print(f测试集准确率: {test_acc:.4f})代码逻辑说明evaluate在10000张未见过的测试图上做一次完整预测返回损失值和准确率。对128-64-10结构加Adam优化器合理预期是0.870.89。如果你跑出来是0.80以下基本可以断定前面某一步出错了直接跳到第5章排查清单。4.2 学习率与正则化参数怎么调才能稳定收敛Adam优化器把学习率的默认值定在0.001这个值对Fashion-MNIST恰好合适from tensorflow.keras.optimizers import Adam model.compile( optimizerAdam(learning_rate0.001), losssparse_categorical_crossentropy, metrics[accuracy] )参数说明learning_rate0.001是Adam的默认值也是这类任务的最佳起点。把它调大到0.01你会发现loss曲线剧烈震荡准确率大起大落调小到0.0001收敛明显变慢10个epoch可能到不了应有水平。我的建议是新手先别动学习率跑通后再试0.0005和0.002这两个相邻值对比loss曲线的下降速度。过拟合是训练阶段第二常见的现象加dropout是最直接的应对model models.Sequential([ layers.Input(shape(784,)), layers.Dense(128, activationrelu), layers.Dropout(0.3), layers.Dense(64, activationrelu), layers.Dropout(0.3), layers.Dense(10, activationsoftmax) ])代码逻辑说明Dropout在训练时随机丢弃30%的神经元输出迫使模型不依赖特定的几个神经元让剩余神经元各自学到更鲁棒的特征。加了dropout后训练集准确率会下降从98%降到95%左右但测试准确率反而可能上涨——这是典型的用训练精度换泛化精度。参数说明dropout比例0.20.3是经验区间。太高0.5以上会让模型欠拟合训练loss都降不下去太低0.1又几乎不起作用。另外注意dropout只在训练时生效模型推理时自动关闭不影响你最后用evaluate或部署时的表现。还有个实用的训练技巧把history里的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()代码逻辑说明这段代码画出训练loss和验证loss随epoch变化的曲线。两条线同步下降说明正常训练loss持续走低而验证loss在第6个epoch后掉头向上说明过拟合已经开始需要早停或加正则化。这个判断方法比盯着单个准确率数字可靠得多。4.3 早停与模型保存训练代码包里的“后悔药”当你多次调整epoch后最想要的是自动停止机制和保存最佳模型的方法from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), ModelCheckpoint(best_model.h5, monitorval_loss, save_best_onlyTrue) ] history model.fit( x_train_flat, y_train, epochs20, batch_size32, validation_split0.1, callbackscallbacks )代码逻辑说明EarlyStopping监视验证损失连续3个epoch没有改善就停止训练并把权重回滚到验证损失最低的轮次。ModelCheckpoint在每次验证损失刷新时自动保存模型文件这样即使训练中断你手里也有一份最优权重。参数说明patience3意味着允许模型连续3个epoch不进步才停。这个值太小容易在训练中段误停太大又失去了早停的意义。restore_best_weightsTrue很关键不设置的话EarlyStopping触发后留下的是最后一个epoch的权重而不是最优的那一份。ModelCheckpoint里的save_best_onlyTrue保证磁盘上只保留一份最优模型避免中间状态把空间撑爆。这两个回调配合使用基本就是入门代码包升级到工程化训练的第一步。保存的best_model.h5后续可以直接用load_model加载做预测不需要重新训练这个习惯在实际工作中能替你省下大量的重复训练时间。5. 复现代码包时的避坑清单从数据错位到过拟合5.1 测试准确率只有80%而代码包声称接近88%现象我照着代码原样跑test accuracy只有80%出头跟描述差了一个档次。模型结构、epoch、batch_size都核对过了没有发现明显差异。原因最常见的是两个。一是像素没做归一化直接把0255的整数喂给网络梯度计算不稳定收敛不到好的局部最优点二是标签形态和损失函数不匹配比如代码里标签是整数却用了categorical_crossentropy模型虽然能跑但学不到正确的映射关系。解决对照第2章的预处理代码确认x_train做了astype(float32)/255.0再看y_train.shape按它决定用sparse_categorical_crossentropy还是categorical_crossentropy。我把这两处检查列成固定习惯每次跑别人的代码包都会先核对这两个点能排除一半以上的“复现不出效果”问题。5.2 每次运行结果相差2个百分点代码不稳定现象同一个脚本连跑三次测试准确率在86.5%到88.5%之间浮动。我一度以为是程序有什么隐藏bug。原因神经网络有两个随机性来源权重初始化和训练数据的shuffle顺序。Keras默认不固定随机种子所以每次运行都会产生不同的初始权重和不同的batch组成最终结果自然浮动。解决在代码开头固定随机种子import numpy as np import tensorflow as tf np.random.seed(42) tf.random.set_seed(42)代码逻辑说明设置随机种子后权重初始化和数据shuffle顺序都变成确定性的多次运行会得到完全一致的结果。注意TensorFlow 2.x要用tf.random.set_seed()很多老代码里写的tf.set_random_seed()已经废弃直接调用会报错或失效。参数说明种子本身取多少不重要42只是习惯。关键是要在所有其他代码之前设置最好是在import之后第一行就写。如果你只给numpy设了种子而没给tensorflow设shuffle顺序仍然是随机的结果照样漂。设置固定种子后你应该能复现出代码包描述的那个准确率。如果还是对不上再用第5.1条的方法逐项检查。5.3 训练集准确率97%测试集只有86%现象训练过程中看到准确率一路冲到97%但evaluate测试集只有86%我以为模型被训练坏了。原因这是典型的过拟合。模型把训练集里的噪声和细节也背了下来遇到没见过的样本就水土不服。128-64这种结构在60000张训练图上本身就容易过拟合epoch越多越明显。解决按第3.2节的方案加dropout把epoch从15降到10左右同时观察validation loss。加了dropout(0.3)后训练准确率会降到94%95%但测试准确率大概率会提升到88%以上。如果你在loss曲线上看到val_loss在第6个epoch后开始回升train_loss还在降那说明过拟合已经开始设置EarlyStopping是更好的办法。还有一种值得留意的特殊情况如果训练loss和验证loss从一开始就都下不去那不是过拟合而是欠拟合。这时候加dropout只会更糟正确做法是增大网络容量或降低正则化强度。判断清楚是过拟合还是欠拟合再动手改参数。5.4 shape mismatch报错信息让人怀疑人生现象报错大概长这样——Input 0 of layer dense is incompatible with the layer: expected min_ndim2, found ndim3。我反复修改model定义问题依然存在。原因大多数时候不是model写错了而是直接把(60000,28,28)的三维数据喂给了Dense层。全连接层要求输入是(样本数, 特征数)的二维结构你需要先做展平。解决在fit之前加一个断言检查把数据形态问题扼杀在训练开始之前assert x_train_flat.shape[1] 784 assert x_test_flat.shape[1] 784代码逻辑说明assert在条件不成立时会直接抛异常。如果训练数据根本没有展平这两行会在fit之前就把问题暴露出来报错信息也比框架内部的shape mismatch容易理解得多。加上这个检查后你就能自信地区分是数据处理出问题而不是model定义出问题。5.5 validation_acc高但test_acc低代码包作者是不是作弊了现象代码里validation准确率显示89%我自己跑test准确率只有87%感觉作者把评估结果粉饰过。原因validation_split是从训练集尾部切出来的而test集是真正独立的数据。如果训练前没做shuffle尾部数据可能与前面的分布有细微偏差另外验证集会间接影响你的调参决策本质上也算一种信息泄漏所以验证集准确率通常略高于测试集是正常现象。解决这个问题不用过度纠结。Fashion-MNIST的两个子集来自同一个分布0.51.5个百分点的差异属于正常范围。如果你的验证集和测试集准确率差距超过2个百分点那要怀疑数据处理环节是否存在泄漏——比如对验证集做过了某种针对训练集的统计计算。通常做法是认可测试集准确率为最终结果验证集只当训练过程的参考指标。6. 不止于准确率混淆矩阵、错误样本分析与CNN升级路径6.1 用混淆矩阵看分类器在哪些类别上打架准确率是个平均值会把类别间的差异抹平。训练完成后我总会加一段混淆矩阵分析import numpy as np from sklearn.metrics import confusion_matrix y_pred model.predict(x_test_flat) y_pred_cls np.argmax(y_pred, axis1) cm confusion_matrix(y_test, y_pred_cls) print(cm)代码逻辑说明predict返回每张测试图的10个类别概率np.argmax取最大概率对应的类别索引然后由confusion_matrix生成一个10×10的矩阵行代表真实类别列代表预测类别。对角线上的值越大说明该类别识别得越好对角线之外的值越大说明这两个类别越容易互相混淆。观察Fashion-MNIST的混淆矩阵你会发现T恤/上衣、套头衫、衬衫这三个类别之间混得很厉害——它们在像素层面的轮廓确实太接近了。凉鞋、运动鞋、踝靴之间也有明显的交叉误判。这说明全连接网络学到的是全局的像素特征组合而不是像卷积网络那样关注局部纹理差异。类别间视觉差异小准确率就卡在88%上不去。我还习惯把每个类别的精确率、召回率单独打出来这样能发现某个类别是被漏报多还是误报多。对入门代码包来说这一步的意义是让你意识到准确率之外还有更细的评估维度后续做模型优化时有明确的分析依据。6.2 从全连接转向CNN改造方向与预期收益如果想把Fashion-MNIST准确率从88%推到92%以上标准路线是换卷积网络model models.Sequential([ layers.Input(shape(28, 28, 1)), layers.Conv2D(32, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.3), layers.Dense(10, activationsoftmax) ])代码逻辑说明Conv2D用卷积核在图像上滑动提取局部特征MaxPooling做下采样缩小特征图尺寸、保留主要信息。输入也改回了(28,28,1)的三维形态多出的最后一维是通道数灰度图就是1。卷积结构显式利用了像素的空间局部性这是它比全连接更适合图像分类的根本原因。参数说明第一个卷积层用32个3×3卷积核提取边缘、纹理等低层特征第二个卷积层用64个核组合成更高层的语义特征。kernel_size选(3,3)是惯例它兼顾感受野和参数量。Flatten之后接的Dense(128)和Dropout(0.3)与第4章的讨论一脉相承——卷积负责特征提取全连接负责分类决策正则化负责对抗过拟合。这个结构在Fashion-MNIST上能到92%左右且训练时间不长。将参数遗产传给新结构kernel_size可以试(5,5)准确率略涨但参数量增大加BatchNormalization层可以加速收敛换Adam的默认学习率到0.001效果依然稳定。6.3 把参数记录变成习惯最后一件事跟任何框架无关是我跑这类代码包压箱底的习惯每次实验都在一个文本文件里记录参数组合、验证准确率、测试准确率和当时的改动意图。比如“加dropout 0.3后val_loss从0.32降到0.28”“epoch从15降到10后测试准确率持平但训练时间减少三分之一”。这个习惯帮我在反复调试中少走了很多弯路也避免下次翻代码包时抓瞎——以为自己调过学习率其实从未动过。无论你是刚跑通第一个分类还是准备在此之上做更复杂的模型先从记录一行参数开始这比记住任何调参口诀都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表