资讯动态

10行代码搭建神经网络:Keras实战入门与核心原理解析

发布时间:2026/10/4 2:24:06 来源:尧图企业网站定制
1. 这个实验到底要做什么去年我在社区里分享了一段只有十几行的神经网络代码评论区问得最多的一句话是“就这点代码能叫神经网络”说实话我特别理解这种疑问。很多人对神经网络的印象停留在“高深、复杂、需要大量数学基础”觉得没有几百行代码、不手写反向传播就不算真正接触了深度学习。但今天的实际情况是像 TensorFlow、PyTorch 这样的框架已经把所有底层实现打包成了几层 API——你要做的就是用这些积木块把网络结构搭出来。10 行代码搭一个能跑的神经网络完全真实而且是个特别好的切入点它让你先看见全貌再回头看细节。这篇内容我准备了很久目标读者就是两类人一是刚接触机器学习、想用最短路径看看神经网络到底怎么工作的新手二是已经会调库但一直没搞懂每个参数在干什么的“半吊子”学习者。实验会用 Python 和 Keras 完成数据集选用最经典的鸢尾花Iris数据任务是一个三分类问题。跑完整个流程你会清楚感知到“神经网络从输入到输出到底经历什么”而不是停留在“它很神奇”的模糊印象里。为了让整个过程尽量贴近实际操作我不会只贴一段代码让你自己琢磨。我会把每一行代码拆开讲清楚把模型训练过程中的关键决策点以及常见的坑都聊一遍确保你照着敲一遍也能跑通。2. 为什么说用 10 行代码就能搭建神经网络2.1 框架把最难的数学藏在了底层先说个扎心的事实如果不用任何深度学习框架纯粹靠 NumPy 手写一个多层感知机从参数初始化、前向传播、反向传播到梯度更新至少要 150 到 200 行代码其中 80% 都在处理矩阵运算的维度和公式推导。而且这还只是最简单的网络随便加个 BatchNorm 或者正则化代码量立刻翻倍。十年前深度学习研究者确实都是这么过来的。但后来 Keras 这类高层 API 出现把网络定义、损失函数、优化器、训练循环全部封装成了声明式接口。你今天写神经网络本质上更像是在搭积木——每一层是一个组件训练是一个命令。底层那些张量运算、自动微分、梯度下降的实现细节框架全都替你处理完了。所以 10 行代码搭神经网络不是噱头而是现代深度学习工程化的真实状态。2.2 技术选型为什么用 Keras 而不是 PyTorch动手之前先聊聊框架选择。目前主流的中文社区里PyTorch 的声量确实越来越大学术论文的代码几乎都默认 PyTorch但它解决问题的思路是“define-by-run”代码结构更灵活也会更“啰嗦”一点。哪怕是最简单的线性层网络PyTorch 也要写一堆optimizer.zero_grad()、loss.backward()、optimizer.step()这些步骤虽然清晰但对于第一次接触神经网络的新手来说反而增加了认知负担。Keras 走的是“define-and-run”路线你用几行代码声明网络结构调用fit()就进入训练闭环。初学者可以把全部注意力放在“数据长什么样、网络有几层、训练效果如何”这几个关键问题上不会被样板代码干扰。说实话我自己现在做原型验证也经常用 Keras因为快。另外Keras 在 TensorFlow 里可以直接from tensorflow import keras调用不需要额外安装这一点对新手特别友好。我画过一张对比表供参考对比维度Keras本实验选用PyTorch学习曲线平缓几条命令即可上手较陡需理解动态图机制代码结构声明式简洁命令式灵活但啰嗦调试体验相对黑盒更透明逐行可断点生产部署有 TF Serving 等完整链路也有 TorchServe但整体偏学术适合人群新手入门、快速原型科研、需要深度定制的人2.3 一个反直觉的事实模型精度不是这个实验的重点很多人第一次跑通神经网络第一个动作就是盯着准确率看然后开始焦虑“准确率才 0.9是不是代码写错了”先把这个心态端正过来用 10 行代码搭的模型核心目的是验证“从数据输入到模型输出”这条链路是否走通而不是在整个公开数据集上刷精度。像鸢尾花数据集用逻辑回归都能做到 95% 以上神经网络在这里更多是“杀鸡用牛刀”地演示原理。所以这个实验你要观察的重点是三件事输入数据经过网络之后输出的 shape 是否符合预期训练过程中 loss 是否在逐步下降测试集上的评估结果是否明显优于随机猜测三分类的随机准确率是 33%只要这三点都满足说明你的神经网络搭建成功且已经学会了数据中的某种模式。3. 核心概念速览前馈、反向传播与激活函数3.1 神经网络的两次“传播”进入代码之前必须把两个概念讲清楚因为后续所有调试都会围绕它们发生前向传播前馈和反向传播。前向传播的逻辑可以用一个特别生活化的类比解释想象你是一个新来的销售需要给一批商品定价格。你手上只有四个特征尺寸、重量、材质、品牌你根据这四条线索进入一个“决策房间”房间里每个同事分别负责不同的判断依次传递意见最后汇总出一个估价。这个过程数据从第一层流向最后一层中间每一层都会对信息做一次变换这就是前向传播。反向传播的逻辑则像考试对答案你发现刚才估价 100 块而真实价格是 80 块于是你往回倒推看看是哪个环节、哪个“同事”的判断造成了这 20 块的偏差然后让这个人把权重调一调下次更准。神经网络训练的本质就是反复执行“前向算出误差反向逐层修正参数”这个循环。3.2 激活函数为什么必不可少如果你没有激活函数神经网络无论堆多少层本质上都等价于一次线性变换这是数学上可以直接证明的结论。换句话说它永远学不会非线性关系比如“颜色深且形状圆的商品反而便宜”这种复杂规律。我在代码里会用到两种激活函数分别是隐藏层的 ReLU 和输出层的 Softmax。ReLU 的公式是f(x) max(0, x)它的优点是计算简单、能缓解梯度消失。你把它理解成一个“过滤器”好消息正数原样通过坏消息负数直接截断为零。这个特性让网络训练快且稳定是目前隐藏层事实标准。Softmax 则是专门用于多分类输出的激活函数它会把网络最后一层的若干个实数输出转换成一堆和为 1 的概率值。比如输出的三个数字是 [2.0, 1.0, 0.5]经过 Softmax 后变成 [0.65, 0.24, 0.11]我们就说这个样本有 65% 概率属于第一类。提示新手最常见的错误之一就是在多分类任务里最后一个隐藏层忘加 Softmax导致输出不是概率分布。遇到这种情况模型的损失会很高而且很难收敛。3.3 损失函数与优化器到底在干什么损失函数给出“模型错得有多离谱”的量化指标反向传播就是沿着这个指标的梯度方向调整权重。代码里用的损失函数是sparse_categorical_crossentropy名字看着长拆开理解就不难categorical_crossentropy交叉熵损失用于分类任务。它在数学上衡量的是两个概率分布之间的差距。sparse_ 前缀表示我们喂给模型的标签是整数0、1、2而不是 one-hot 编码后的向量。如果你用了 one-hot需要去掉这个前缀直接用categorical_crossentropy。优化器是负责权重更新的策略代码里选的是 Adam。Adam 可以理解为一个自带“惯性”的梯度下降它不仅看当前坡度还参考前几步的下降方向来修正步伐结果就是它收敛快、对学习率不敏感。对新手来说Adam 是最不会出错的默认选择。4. 实操部分10 行核心代码搭建神经网络4.1 环境准备一杯咖啡的时间开始写代码之前先把环境整明白不然代码跑不起来你会在环境问题上浪费两小时。我自己常用的一套配置如下Python 3.9 或以上 tensorflow 2.10 或以上 scikit-learn 1.2 或以上 numpy 1.23 或以上安装命令很简单pip install tensorflow scikit-learn numpy如果你用的是 M 系列芯片的 Mac或者有 NVIDIA GPU可能需要额外处理。但这类内容网上已经有很多中文教程这里不展开默认我用的是 CPU 版 TensorFlow。鸢尾花数据集就三万多个数值跑几十轮迭代CPU 也就是几秒的事。注意TensorFlow 在 M 系列 Mac 上的安装推荐直接用pip install tensorflow-macos或者用tensorflow的官方轮子这几年的兼容性已经很好了。Windows 上则要注意 Python 版本3.11 以上某些轮子可能不完整保险起见建议用 3.9 或 3.10。4.2 完整代码一个可以照抄的版本先把全部代码贴出来。这段代码我精简到 12 行其中两行是导入和打印严格意义上的网络定义加训练流程就是 10 行左右import numpy as np from tensorflow import keras from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split X, y load_iris(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) model keras.Sequential([ keras.layers.Dense(8, activationrelu, input_shape(4,)), keras.layers.Dense(3, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) model.fit(X_train, y_train, epochs50, verbose0) print(model.evaluate(X_test, y_test))补充 end-to-end runnable 版本包含预测部分import numpy as np from tensorflow import keras from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split X, y load_iris(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) model keras.Sequential([ keras.layers.Dense(8, activationrelu, input_shape(4,)), keras.layers.Dense(3, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) model.fit(X_train, y_train, epochs50, verbose0) y_pred np.argmax(model.predict(X_test), axis1) print(预测结果, y_pred) print(真实标签, y_test) loss, acc model.evaluate(X_test, y_test, verbose0) print(f测试集损失{loss:.4f}准确率{acc:.4f})这段代码在标准环境下跑完大概需要 1 到 2 秒。4.3 逐行拆解每一行代码在干什么现在逐行讲。跳过导入和数据处理两行从核心代码开始。第一段是模型结构定义model keras.Sequential([ keras.layers.Dense(8, activationrelu, input_shape(4,)), keras.layers.Dense(3, activationsoftmax) ])Sequential的中文直译是“顺序模型”意思是网络层是按顺序前后拼接的。第一层Dense(8, activationrelu, input_shape(4,))是全连接层有 8 个神经元接收 4 个输入特征激活函数是 ReLU。这里有个常见疑问隐藏层神经元数量为什么选 8其实这个数字不是严格算出来的。对于这种只有 4 个输入、3 个类别的简单任务隐藏层神经元 4 到 16 个都行8 是个人偏好——太少学不到特征太多容易过拟合还会拖慢训练。这个参数到了真实项目里一般靠试或者靠经验法则输入维度加输出维度的两倍附近。第二层Dense(3, activationsoftmax)是输出层3 个神经元对应 3 个类别Softmax 把输出变成概率分布。注意它不需要再指定input_shapeKeras 会自动沿用上一层的输出维度。第二段是编译model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy])compile的意义是把你之前定义的网络和训练策略绑定。三个参数分别指定了如何更新权重、如何计算误差、评估模型时关注什么指标。这一步不涉及实际计算只是配置。第三段是训练model.fit(X_train, y_train, epochs50, verbose0)fit才是真正开始干活的地方。epochs50表示将整个训练集完整送入网络 50 遍。每一遍模型都会把 120 个训练样本鸢尾花总共 150 个切走 30 个测试集分批送进去计算梯度、更新参数。verbose0表示不打印每个 epoch 的进度条。如果想看训练过程中的 loss 变化把verbose改成 1 或 2。4.4 数据准备的细节为什么不直接塞原始数据这次用的鸢尾花数据集是机器学习领域最经典的“练习题库”每个样本有 4 个数值特征花萼长宽、花瓣长宽标签是三种鸢尾花之一山鸢尾、变色鸢尾、维吉尼亚鸢尾。你可能要问为什么加载数据之后要先做train_test_split因为机器学习的铁律是模型必须用“没见过”的数据来检验。你要是在全部数据上训练又在同一份数据上评估模型会“背答案”而不自知——准确率虚高换个新数据就现出原形。test_size0.2就是切 20% 出来留作考试剩下 80% 用来学习。random_state42是固定随机种子。很多第一次跑代码的人不理解为什么非要固定它如果你不用固定种子每次切分的数据都不一样每次跑出来的准确率也会轻微浮动这不利于复现实验结果。固定 42你能保证自己和别人跑出来的训练集、测试集分布完全一致方便对比。实操心得我强烈建议新手在做这类实验时第一步永远先打印一下X_train.shape和y_train.shape。我见过太多学员因为数据 shape 和网络输入对不上卡在莫名其妙的地方。先确认数据维度再定义模型能省掉 80% 的调试时间。4.5 模型评估如何判断好坏模型的最后一行print(model.evaluate(X_test, y_test))evaluate会同时返回测试集上的损失值和准确率通常跑出来的结果在 0.9 以上的准确率。这个结果远超三分类随机猜测的 33%说明模型确实学到了分类规律。但只看一个准确率数字还不能体现深入观察的价值。我一般会再加一段预测展示看看模型具体错在哪些样本上y_pred np.argmax(model.predict(X_test), axis1) for i in range(len(y_test)): status ✔ if y_pred[i] y_test[i] else ✘ print(f样本{i}: 预测{y_pred[i]}, 真实{y_test[i]} {status})其中np.argmax(..., axis1)的含义是把概率最高的类别的下标提取出来。模型输出的概率分布是三维向量比如[0.05, 0.90, 0.05]Argmax 就会返回 1。看到哪些样本被分错能帮你理解模型的能力边界也方便后续脏数据分析。5. 从零手写一个更精简的网络理解辅助5.1 不用框架能不能更简单地理解神经网络如果你觉得上面 Keras 的代码还是有点“黑盒”这里再提供一个更加底层的版本。我不用 TensorFlow而是只用 NumPy 搭建一个等价网络的核心逻辑其中隐藏层 8 个神经元、输出层 3 个神经元激活函数分别为 ReLU 和 Softmax损失函数是交叉熵。用梯度下降法手动更新参数。这个版本更直白适合理解神经网络的内部运转。import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder X, y load_iris(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) X_train (X_train - X_train.mean(axis0)) / X_train.std(axis0) X_test (X_test - X_test.mean(axis0)) / X_test.std(axis0) enc OneHotEncoder(sparseFalse) Y_train enc.fit_transform(y_train.reshape(-1, 1)) np.random.seed(42) W1 np.random.randn(4, 8) * 0.1 b1 np.zeros((1, 8)) W2 np.random.randn(8, 3) * 0.1 b2 np.zeros((1, 3)) lr 0.1 for epoch in range(200): # 前向传播 z1 X_train W1 b1 a1 np.maximum(0, z1) # ReLU z2 a1 W2 b2 exp_z np.exp(z2 - z2.max(axis1, keepdimsTrue)) probs exp_z / exp_z.sum(axis1, keepdimsTrue) # Softmax # 交叉熵损失 loss -np.mean(np.log(probs[np.arange(len(Y_train)), y_train])) # 反向传播 dz2 probs - Y_train dW2 a1.T dz2 / len(X_train) db2 dz2.mean(axis0, keepdimsTrue) dz1 (dz2 W2.T) dz1[a1 0] 0 # ReLU 导数 dW1 X_train.T dz1 / len(X_train) db1 dz1.mean(axis0, keepdimsTrue) # 参数更新 W1 - lr * dW1 b1 - lr * db1 W2 - lr * dW2 b2 - lr * db2 if epoch % 20 0: print(fepoch {epoch}, loss {loss:.4f}) # 测试 z1 X_test W1 b1 a1 np.maximum(0, z1) z2 a1 W2 b2 pred np.argmax(z2, axis1) acc (pred y_test).mean() print(测试准确率:, acc)这段纯 NumPy 代码有 50 多行但它的逻辑和前面 Keras 版本是完全等价的。当你亲手完成从“前向传播算输出”到“反向传播更新权重”的闭环再去回看 Keras 的 API就会有一个特别直观的感受model.fit()背后打包的就是这些你刚刚手写过的操作。5.2 从手写版本中学到的三条实战教训第一权重初始化不能乱来。上面代码里我用np.random.randn(...) * 0.1也就是把标准正态采样的值压缩到 0.1 倍。这是为了确保初始梯度不会太大或太小。如果你用np.random.randn(4, 8)而不乘任何系数参数初始值动辄 1、2网络前向传播后输出的 Softmax 概率会接近 one-hot梯度接近零训练基本废掉。第二Softmax 前要减去最大值。我在np.exp(z2 - z2.max(axis1, keepdimsTrue))这一行做了这个操作。虽然名字还是 Softmax但结果等价而且避免了np.exp在输入较大时产生极大数导致的计算溢出。这是数值稳定性上的一个经典细节框架内部早就实现了但你手写时很容易忽略。第三损失函数的“真实标签路径”写法。交叉熵在实现时要索引到正确类别的概率np.log(probs[np.arange(len(Y_train)), y_train])。这个写法一次算出所有样本的交叉熵贡献比写 for 循环快很多也是 NumPy 常用的向量化技巧。手写版本的学习率我用的是 0.1这算是个经验起始值。太大容易震荡不收敛太小则要等很久才能下降。你把它改成 1.0 试试会看到 loss 曲线剧烈抖动甚至越调越高这就是学习率过大的直观感受。5.3 手写版本和框架版本的性能对比很多读者关心手写版本有没有意义我的观点是纯手写神经网络对你理解“神经网络是什么”极有帮助但仅此而已。它并不是一个可用于生产的方案。对比维度Keras 版本手写 NumPy 版本代码量约 10 行核心代码50 行以上自动微分内置手动推导支持 GPU自动不支持数值稳定性内置保证需自己处理调试难度略黑盒完全透明学习价值工程落地思维算法原理思维建议路径是先用 Keras 帮你建立全局观跑通了再回头手写一遍两相对照你的理解会非常扎实。6. 常见问题速查表与避坑心得6.1 新手高频问题症状、原因与对策我把这些年带新人跑神经网络时遇到的常见问题整理成一个速查表按“症状-原因-对策”三列排开症状原因对策训练时 loss 是 NaN学习率过大或数据需要归一化降低学习率检查数据是否标准化准确率一直停留在 0.33 左右模型没学到东西可能网络太浅检查激活函数确认标签格式训练 loss 一直在下降但测试准确率低过拟合增加数据、增加 Dropout 层报错 shape 不匹配输入维度声明错了打印数据 shape对照网络第一层输入维度第一次能跑通第二次结果不一样没有固定随机种子代码开头np.random.seed(42)模型输出全是同一个类最后一层没加合适的激活分类任务加 Softmax6.2 关于 Keras 版本兼容性经验TensorFlow 2.x 的 API 迭代很快网上搜到的旧教程经常遇到 API 消失或者参数更名。比如早年间keras是一个独立安装的库现在官方推荐的做法是直接用from tensorflow import keras。如果你在环境里单独装了keras同时又装了tensorflow经常会出现TypeError: module object is not callable或者奇怪的导入错误。推荐的诊断路径是代码开头打印print(tf.__version__)和print(keras.__version__)确认它们来自同一个 TensorFlow 包。如果模块版本混乱建议注销冲突的包只留 TensorFlow 自带的那份。6.3 数据归一化多数新手会忽略的隐藏步骤鸢尾花数据集的四个特征取值范围都在 0 到 8 之间量级差异不大所以很多教程不归一化也跑得很好。但一旦你切换到真实数据比如“收入”是几万、“年龄”是几十、OneHot列是 0 或 1模型很可能训不动或者训练特别慢。原因是数值大的特征天然占据了更大的梯度权值相当于它“喊的声音”比其他特征大得多。归一化之后所有特征处于同一数量级模型就听得到所有特征的声音。常见的归一化手段是标准化让所有特征满足均值为 0、方差为 1。这种方式在极端值很多的数据上比简单的 MinMax 缩放更稳定。我专门加了这个内容是因为很多人调了半天参数准确率上不去最后发现只是少了这几行数据预处理代码。在真实项目里数据预处理的重要性远高于神经网络结构本身。6.4 我的个人调试习惯最后分享几个我自己的调试习惯不一定标准但实测下来很管用第一第一次训练永远把verbose开成 1 或 2盯着每个 epoch 的 loss 变化。loss 从大到小说明在学loss 忽大忽小说明学习率问题或数据问题loss 一直不动基本就是数据或梯度出问题。改参数不可怕可怕的是你闭着眼睛调。第二固定random_state。不只是切分数据模型的随机种子也值得固定。虽然 Keras 并不总是能完美复现GPU 上的确定性本身就是个难题但固定的种子至少能让你在不同代码版本间做有效对比。第三把可视化加进来。每轮训练后用 Matplotlib 绘制 loss 曲线用混淆矩阵观察错误类型。绝大多数网络问题都能从“训练曲线”和“错误分布”中一眼看出来。7. 后续扩展从 10 行到真实项目很多读者跑通这个实验后会问下一步学什么我按学习路径排一个优先级首先是换掉数据集。鸢尾花数据集只能做演示真实项目里你不会遇到这么“乖巧”的数据。下一步建议先换成人手写数字数据集MNIST它也是 10 分类但图像数据意味着你要从向量输入升级到矩阵输入。你会碰到卷积神经网络CNN这个方向代码里只需把Dense层换成Conv2D和MaxPooling2D但理解起来可能需要几天。然后是理解训练技巧。比如你可以在现在的模型上加入Dropout层看看过拟合怎么缓解或者把优化器换掉对比收敛速度。我写过一个实验在相同数据上分别用 SGD、Adam、RMSprop 跑误差曲线差异很大建议新手工余时间都试试。再往后就是序列数据处理自然语言或者时间序列都会涉及 RNN 和 LSTM。这些方向的模型结构跟咱们现在这个简单前馈网络已经有很大差别但是底层的“前向传播-反向传播-优化器”这一套三件套逻辑完全一致。我个人的体会是搞懂原理之后再去看那些看起来特别复杂的深度学习项目心里会踏实很多。因为任何复杂模型拆到底都是若干层、若干激活函数、一个损失函数、一个优化器在协同工作而这些你都亲手搭建过——虽然只有 10 行但那是你从 0 到 1 跨出的最重要一步。

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

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

免费获取报价 →
↑