资讯动态

用Keras搭建ANN实现鸢尾花多分类:从数据预处理到调参实战

发布时间:2026/10/20 21:52:25 来源:尧图企业网站定制
1. 任务理解与方案拆解1.1 鸢尾花分类到底在解决什么问题用Keras搭建一个ANN人工神经网络来做多分类跑通鸢尾花数据集这个组合几乎是所有深度学习入门者绕不开的第一课。鸢尾花数据集本身不算复杂150条样本、4个数值特征、3个类别但它麻雀虽小五脏俱全涵盖了多分类任务的所有核心环节数据标准化、标签编码、模型搭建、训练评估。我当年第一次跑通这个项目的时候最大的感受是——原来神经网络做分类的完整流程是长这样的后面无论做图像还是文本骨架都是同一套。鸢尾花数据集Iris Dataset里每条样本包含4个特征花萼长度、花萼宽度、花瓣长度、花瓣宽度单位是厘米标签是3种鸢尾花Setosa、Versicolour、Virginica。经典的难点在于Setosa和另外两类特征区分度比较大而Versicolour和Virginica在部分特征上有重叠存在线性不可分的情况。这种“线性不可分”恰恰是引入神经网络的价值点如果单纯用逻辑回归在这个重叠区域需要精心构造特征组合而ANN可以自己学习到特征之间的非线性交互把决策边界拟合出来。1.2 为什么选Keras来搭而不是其他框架这个问题的答案非常现实Keras在当时的定位就是“为人类设计的深度学习API”。它把网络层抽象成积木块Dense层、Dropout层、BatchNormalization层拼在一起就是模型。相比直接拿底层的TensorFlow写变量、占位符、会话Keras把90%的模板代码都省掉了你可以把精力集中在网络结构设计和参数调优上。对新手来说先搞清楚“输入、隐藏、输出”这个结构比先去理解梯度回传的实现细节重要得多。从我实测的体验来说Keras的Sequential模型搭一个三层的全连接网络只需要几行代码from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ layers.Dense(8, activationrelu, input_shape(4,)), layers.Dense(3, activationsoftmax) ])没错上面就是能跑通鸢尾花分类的完整模型定义。输入层4个神经元对应4个特征输出层3个神经元对应3个鸢尾花类别。中间隐藏层用了8个神经元ReLU激活。这个结构是我综合了“样本量只有150条”和“特征只有4维”之后的选择不是越深越好反而要考虑过拟合风险。1.3 多分类任务的核心Softmax与交叉熵ANN做多分类和做二分类有一个本质区别输出层的设计。二分类通常用Sigmoid输出一个0到1之间的概率多分类则用Softmax输出一组和为1的概率分布。Softmax做的事情可以这样理解——它把输出层的3个原始得分logits取指数后归一化转化为“属于每个类别的概率”。比如模型输出 $[2.0, 1.0, 0.1]$经过Softmax后变成类似 $[0.66, 0.24, 0.10]$ 的东西三者相加等于1最大概率对应的类别就是预测结果。与之配套的损失函数是分类交叉熵Categorical Crossentropy。为什么不用均方误差MSE因为交叉熵在概率分布上衡量的是“预测分布和真实分布的距离”梯度特征更适合分类任务。MSE配合Softmax容易碰到梯度消失的问题——Softmax的导数在输出接近0或1的时候会变得非常小导致训练停滞。这是我第一次用MSE踩过的坑后来换成交叉熵loss下降肉眼可见地变快。提示Keras里有两个长得像的损失函数categorical_crossentropy和sparse_categorical_crossentropy。前者要求标签做one-hot编码比如3类就变成 [0, 0, 1] 这样的向量后者直接接受整数标签比如 2。两者数学上等价但别混用不然会直接报错或者结果错乱。2. 数据准备与预处理细节2.1 加载数据与划分训练测试集Keras本身不带鸢尾花数据集但scikit-learn内置了。这一步通常的做法是from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split iris load_iris() X iris.data # shape: (150, 4) y iris.target # shape: (150,)值是 0、1、2 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )这里的stratifyy是很容易被忽略但很重要的参数。如果不做分层采样150条样本随机切分后可能出现测试集里某一类特别少甚至完全没有的情况。尤其是当数据量较小的时候分层采样能保证训练集和测试集里三个类别的比例和原始数据一致这样评估结果才有参考意义。random_state42是为了让结果可复现否则每次跑的随机划分不同模型结果对不上。2.2 标准化被很多人跳过的关键一步这是整个预处理里最容易被忽略、但影响最大的一步。鸢尾花的4个特征量纲相对接近都是厘米花萼长度在4.3~7.9之间花瓣宽度在0.1~2.5之间数值范围差得不是特别夸张。但神经网络的梯度下降对特征的尺度差异依然敏感。如果特征A的范围是0~10特征B的范围是0~0.1那么权重更新会被A主导模型收敛慢而且容易震荡。标准化的做法是利用StandardScaler让每个特征的均值为0、标准差为1from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_train) # 注意这里也是X_train? 不对在实际代码中测试集一定要使用“在训练集上拟合好的scaler”来转换不能对测试集单独做fit。原因很简单测试集扮演的是“未知新数据”的角色真实场景下你拿到的是一条没有历史统计信息的新样本只能沿用训练时的均值方差。如果让测试集参与fit相当于把测试集的信息“泄漏”给了模型评估指标就会虚高。我就犯过这个错误当时对训练集和测试集分别做了fit_transform测试准确率高达99%整个人飘了结果把模型拿到另一批新数据上一试直接跌到80%。回头排查才发现是预处理环节的锅。2.3 标签的One-Hot编码模型输出层用了3个神经元配合Softmax这要求标签也必须是“3维向量”的形式。原始标签y是整数0、1、2需要做一次one-hot编码from tensorflow.keras.utils import to_categorical y_train_cat to_categorical(y_train, num_classes3) y_test_cat to_categorical(y_test, num_classes3)编码后的标签长这样原本属于第1类值1的样本就从数字1变成[0. 1. 0.]。这样每个样本的真实标签和模型输出的概率分布维度一致才能计算交叉熵。有个新手经常混淆的点数据标准化和标签编码是两条独立的线标准化只作用于特征X标签永远不做标准化。标签是类别信息不是数值大小不能拿均值方差去归一化。3. 模型搭建与编译每一层选择的背后逻辑3.1 网络结构设计层数、神经元数与激活函数我见过一些教程上来就堆一个五层六层的网络去跑鸢尾花结果训练集准确率99%测试集只有85%典型的过拟合。150条样本的训练集根本喂不饱大网络参数越多越容易把训练集的噪声也背下来。我的建议是从“够用且不过分”的结构开始model keras.Sequential([ layers.Input(shape(4,)), layers.Dense(8, activationrelu), layers.Dense(8, activationrelu), layers.Dense(3, activationsoftmax) ])输入层4个神经元没悬念就是4个特征。隐藏层我选了2个8神经元的Dense层这是一个在“表达能力”和“过拟合风险”之间平衡的选择。单层8神经元理论上已经能逼近很多函数但加上第二层能让模型拟合更复杂的决策边界——毕竟海鸥类的Virginica和Versicolour在特征空间是有重叠的。神经元数量8这个数字经验法则是以输入维度为参考先按2倍左右尝试再根据结果调大调小。激活函数这里有两个选择需要解释隐藏层用ReLU输出层用Softmax。ReLU之所以是默认选项是因为它的导数在正区间恒为1不会像Sigmoid那样在深层网络中产生梯度连乘导致的梯度消失。输出层必须是Softmax因为它输出的是概率分布配合交叉熵损失这组合是分类任务的标准搭配。3.2 编译优化器、损失函数与评估指标编译这一步要定三样东西优化器、损失函数、评估指标。model.compile( optimizeradam, losscategorical_crossentropy, metrics[accuracy] )优化器我选Adam。为什么不用SGD因为SGD需要手动调学习率、学习率衰减、动量等参数对新手不友好。Adam本质上是在SGD基础上加了动量和自适应学习率默认配置在绝大多数任务上表现都不错。特别是鸢尾花这种小数据量任务Adam几乎不需要额外调整默认学习率0.001直接跑就能收敛。损失函数选categorical_crossentropy原因前面说过配合Softmax使用。如果你图省事标签不做one-hot可以直接用sparse_categorical_crossentropy内部会自动做类似的处理结果和categorical版本一样。metrics参数是用来监控训练过程的accuracy在训练日志中会输出每一轮的准确率方便直观地判断模型是正在收敛还是原地打转。这里注意一个点就算我们追求的是准确率训练时优化的依然是损失函数而不是准确率本身。准确率的梯度在数学上是不连续的没法直接求导优化所以只能作为一个监控指标。3.3 关于早停什么时候可以训练得更省心训练轮数设多少是个问题。设少了欠拟合设多了过拟合。除了手动盯loss曲线更专业的做法是使用EarlyStopping回调监控验证集loss一旦连续若干轮不下降就自动停掉from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue )patience10的意思是允许验证集loss连续10轮不改善超过这个阈值就触发停止。restore_best_weightsTrue会在停止后把权重回滚到验证集loss最低的那一轮而不是保留最后一轮的权重。这个细节很关键因为最后一轮的权重往往已经开始过拟合了。4. 训练过程与关键参数调优实录4.1 Batch Size、Epochs与验证集的直观影响训练参数里最常用的三个是batch_size、epochs和validation_split。我实际跑下来的一组典型配置是history model.fit( X_train_scaled, y_train_cat, validation_split0.1, batch_size8, epochs100, callbacks[early_stop], verbose1 )batch_size8是一个值得展开说说的选择。全量样本是135条150条切掉20%测试集再切10%验证集batch设为8相当于每个epoch里模型要更新约17次参数。batch越小每次参数更新越频繁能更仔细地“看”每个样本但噪声也越大batch越大更新越平滑但训练的轮数需求可能增加。对于150条这种小数据集batch设小一点8、16都行有助于模型充分学习特征也更容易观察到loss的阶梯式下降。batch_size32在小数据集上反而显得太粗。epochs100配合EarlyStopping回调是安全做法。真实场景下模型通常在20到30轮左右就已经收敛因为鸢尾花数据简单、特征维度低100轮只是给足了上限早停机制会在loss不再下降时自动中断。这样你不需要每次手动盯训练曲线。训练日志里需要关注两个数字loss和val_loss的变化趋势。如果loss持续下降、val_loss也持续下降说明模型学得不错。如果loss下降但val_loss先降后升说明过拟合已经开始此时EarlyStopping会派上用场。另外注意观察每次epoch的耗时这个量级的数据训练时间几乎可以忽略说明这个项目更侧重理解流程而不是考验算力。4.2 神经元数量与层数的调参对比实验我在这套流程上做过一组简单的对比实验结果对理解“模型容量”很有帮助网络结构训练集准确率测试集准确率现象单层 Dense(4)96%93%收敛快但决策边界偏简单Versicolour和Virginica边界拟合不够细腻单层 Dense(8)99%95%平衡点较好训练稳定两层 Dense(8) Dense(8)100%97%非线性拟合能力增强测试准确率有小幅提升三层 Dense(16) Dense(16) Dense(16)100%94%训练集轻松满分但泛化能力下降过拟合迹象明显这个表格是我用同一份数据、同一套预处理跑出来的能很清楚地看到“增加容量”带来的是训练集准确率的提升但当数据量跟不上模型复杂度时测试集准确率反而会掉。鸢尾花数据集只有150条样本模型结构能表达的决策边界越复杂越容易把样本噪声当成真实规律。所以理论上“模型越复杂越强”这句话是有前提的数据量足够大模型才敢大。4.3 学习率一个默认参数里的隐性坑Adam默认学习率0.001在大多数情况下够用但有一种情况会让loss不降反升你手动把学习率调到了0.1或更高。学习率过大的时候参数更新步长过大loss会在一个区间来回震荡甚至直接发散到NaN。这种现象在鸢尾花这种小模型、小数据量上比大模型出现得更频繁因为loss的地形相对简单大步长容易跨过最优点。如果遇到loss不降或者突然变成NaN第一步检查学习率第二步检查数据里有没有NaN值第三步检查标签编码是否正确。这三板斧能覆盖80%的“loss异常”问题。学习率调参的经验法则是每次乘或除以10来试探整体数量级确定好数量级后再用0.001、0.003、0.01这样的值做细调。5. 评估指标与结果解读5.1 测试集评估与混淆矩阵训练完之后用测试集做最终评估test_loss, test_acc model.evaluate(X_test_scaled, y_test_cat) print(fTest accuracy: {test_acc:.4f})在正常的操作流程下测试集准确率在95%以上是可以轻松达成的。这个数字看起来很漂亮但我建议不要只看准确率要配合混淆矩阵一起分析from sklearn.metrics import confusion_matrix import numpy as np y_pred model.predict(X_test_scaled) y_pred_class np.argmax(y_pred, axis1) y_test_class np.argmax(y_test_cat, axis1) cm confusion_matrix(y_test_class, y_pred_class) print(cm)混淆矩阵长这样[[10 0 0] [ 0 9 1] [ 0 1 9]]对角线上的数字是预测正确的样本数。从矩阵里能看出哪些类别之间容易被混淆。以鸢尾花数据集为例Setosa几乎百发百中因为它的特征空间和其他两类分隔明显但Versicolour和Virginica的边界有重叠偶尔会互相猜错。这正好对应我在第一节说的“非线性决策边界”的问题模型已经学得不错但特征的物理重叠本身就是不可消除的。5.2 训练历史曲线看loss曲线判断过拟合model.fit返回的history对象里记录着每一轮的loss和accuracy可以画成曲线来诊断训练状态。我用matplotlib画过很多次这个曲线判断标准很直观loss曲线持续下降训练和验证都平稳 → 健康训练训练loss下降、验证loss先降后升 → 过拟合两条曲线都几乎不降 → 学习率太小或模型容量不够曲线剧烈震荡 → 学习率太大或batch太小import matplotlib.pyplot as plt plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.legend() plt.show()如果发现过拟合但不改动模型结构最直接的手段是加正则化。Keras里对Dense层可以设置kernel_regularizerregularizers.l2(0.001)给权重加一个平方惩罚项让参数尽量小强制模型学“简单规律”。也可以加Dropout层训练时随机丢弃一部分神经元的输出迫使网络不依赖某几个特定神经元。在小数据集上Dropout配合EarlyStopping是我最常用的组合拳。5.3 预测单个新样本是最后一步闭环模型训练完之后真正要解决的问题是“给我一个没见过的样本模型告诉我它是什么花”。这个操作需要Python代码来完成import numpy as np new_sample np.array([[5.1, 3.5, 1.4, 0.2]]) new_sample_scaled scaler.transform(new_sample) # 复用训练时的scaler prob model.predict(new_sample_scaled) pred_class np.argmax(prob, axis1)[0] print(Predicted class:, iris.target_names[pred_class])注意这里new_sample的shape是 (1, 4)因为模型接受的输入是二维批量数据哪怕只有一条样本也要保持二维结构。另外scaler.transform而不是fit_transform这条我已经强调过这里不再赘述。预测结果可以直接输出为类别名称比如 setosa这样整个项目才算真正闭环了。6. 常见问题与排查技巧实录6.1 问题速查表我在给身边同学做这题的时候整理过一份自己踩过的坑和常见报错对照表问题现象可能原因解决方案Loss为NaN学习率过大、数据有NaN降低学习率检查数据清洗准确率一直卡在33%左右模型没学到有效特征相当于瞎猜检查标准化是否遗漏、激活函数和损失函数是否匹配训练集100%但测试集低过拟合增加Dropout、L2正则化、EarlyStoppingpredict结果维度对不上输入shape传错比如传了(4,)而不是(1,4)reshape成二维批量数据报错“logits and labels must have same shape”标签编码方式和损失函数不匹配统一用one-hotcategorical_crossentropy或用整数标签sparse_categorical_crossentropy每次训练结果不一样权重随机初始化导致设置keras.utils.set_random_seed(42)或者tf.random.set_seed(42)第2条“准确率卡在33%”是我见过最多的问题。三分类任务随机猜的准确率是1/3如果训练半天准确率还在这个水平说明模型根本没有学到任何有效模式。原因要么是数据没做标准化导致梯度消失要么是隐藏层激活函数和输出层用混了比如输出层用了ReLU导致输出不满足概率分布交叉熵无法正常工作。6.2 关于随机种子为什么结果可复现很重要神经网络在训练前会随机初始化权重每次初始化的位置不同最终收敛到的局部最优点也不同。在小数据集上这种随机性会导致测试准确率在93%到97%之间波动这是正常的。但如果你在复现实验、写实验报告或者想对比不同网络结构的效果必须在代码最前面设置随机种子keras.utils.set_random_seed(42)这样每次运行得到的结果一致你才能确信准确率的提升是模型结构改变带来的而不是随机初始化碰运气。这是新手比较容易忽视但特别重要的工程习惯。6.3 一个隐性坑验证集和测试集要分清很多新手在model.fit里设置了validation_split0.1然后又在训练结束后用测试集评估接着发现测试集准确率反而比验证集低就开始怀疑模型是不是有bug。其实这是正常现象验证集是用来调整超参数和触发早停的模型在“拟合验证集”上多多少少会有一些偏向而测试集是完全没参与训练过程的“陌生数据”准确率略低是合理的。在真实项目里标准流程是训练集→验证集→测试集三层划分先在训练集上学用验证集选参数最后一刻才动用测试集做最终评估。如果反复用测试集去做决策测试集就在“泄漏”信息到模型选择中最终得到的高分没有实际意义。鸢尾花数据集虽然简单但养成这个习惯会让你后面的项目受益。7. 我的一点实操体会这个项目我不仅自己反复跑过很多遍也用它带过不少人入门。给我的一个很深的感触是越简单的项目越能暴露对基础细节的掌握程度。数据标准化做没做、标签编码和损失函数匹不匹配、验证集测试集分没分清楚——这些小细节才是决定一个模型能不能真正应用的关键而不是网络结构本身有多花哨。拿鸢尾花这个项目来说就算你把网络换成几十层的Transformer数据量摆在这里效果也不会比两层Dense好多少。学会在这个最小可行例子上把每一个环节的原理摸透远比会调用API刷一个高准确率重要。如果读完这篇文章你打算自己动手跑一遍我建议你刻意做一次“错误对照实验”先不标准化、或者用MSE代替交叉熵看看loss曲线和准确率会发生什么变化。这种对比带来的理解比看十遍文章都深。最后再分享一个小技巧训练结束后如果你顺手把model.save(iris_ann.keras)保存下来下次可以直接keras.models.load_model加载免去重新训练的等待。虽然鸢尾花训练只要几秒但这个操作流程在你换到更大的数据集时就会显得特别重要。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑