资讯详情

从Adaline到LMS:零基础跑通第一个神经网络训练循环

📅 2026/10/27 18:11:09 | 华诺云谱 👁 阅读
从Adaline到LMS:零基础跑通第一个神经网络训练循环
简介这份神经网络导论课程实验一代码面向正在学习人工神经网络基础的高校学生与自学者围绕Adaline自适应线性神经元及LMS最小均方误差学习规则展开帮助读者通过Matlab实践理解权重更新、误差收敛与线性分类的基本流程。压缩包共5个文件约10KB主要由3个Matlab脚本和2个数据文件组成脚本承担算法实现、随机权重初始化与样本随机划分数据文件保存训练集和测试集便于直接加载运行并观察训练与评估结果。资源已有300人学习适合作为课程实验参考或自学练手材料。内容覆盖Adaline结构、LMS权重更新公式、学习率选择、随机初始化对收敛的影响以及训练与测试集划分等知识点读者可据此复现实验、对比不同参数下的误差变化并加深对早期神经网络模型训练逻辑与泛化评估的理解。1. 从实验 1 代码出发Adaline 的 LMS 是神经网络最朴素的一次“学习”打开神经网络导论课程的第一份实验代码大多数人的第一反应是懵满屏的循环、向量和权重跟课本上那些画着连线的示意图根本对不上号。这份实验 1 的代码要做的事其实很朴素——让你亲手实现一个 Adaline 神经元用 Adaline 的 LMS 学习规则让它在几十轮迭代里自己把权重调对。它解决的正是新手最大的卡点知道神经网络会“学习”却不知道“学习”这一步到底改了哪个数字。适合刚学完一点线性代数、会写几行 Python、想真正跑通第一个神经网络的零基础同学。学完你拿到的不只是一份能跑的代码而是一条之后可以反复复用的训练循环骨架。2. 先弄懂一个概念Adaline 的 LMS 到底在算什么零基础学神经网络最容易掉的坑是一上来就去啃“反向传播”“梯度消失”这些词结果连一个神经元里发生了什么都没搞清楚。这一章不碰多层网络只把 Adaline 这一个模型扒开看看完你会发现它简单到有点可爱——本质就是一次加权求和加一条用来修正权重的规则。2.1 一个神经元本质是一次加权投票想象你要判断一家餐厅好不好吃。你问了三个朋友甲说好吃乙说一般丙说难吃。但你对这三个人的信任程度不一样甲的口味跟你最像权重给 0.6乙只给 0.3丙给 0.1。最后你算了个综合分0.6×甲 0.3×乙 0.1×丙。这个综合分超过某个门槛你就去否则不去。Adaline 干的正是这件事。它有 n 个输入 x₁ 到 xₙn 个对应的权重 w₁ 到 wₙ把输入和权重一一相乘再全部加起来z w₁x₁ w₂x₂ ... wₙxₙ b这里的 b 叫偏置你可以理解成“这个神经元天生乐观还是悲观”——它不随输入变化是一个固定的加分项或减分项。在代码里这个式子就是一行点积np.dot(w, x)没有任何神秘的地方。提示神经网络导论课上画的那些圆圈和箭头箭头代表权重圆圈代表求和一个圆圈就是一个这样的加权求和单元。你以后看到再复杂的网络图单个节点做的还是这一件事。2.2 和感知机的差别一条线拉直了很多人第一次学到这里会问这不就是感知机吗差别就在输出那一步。感知机算完加权和后立刻套一个阶跃函数大于 0 输出 1小于 0 输出 −1。然后拿这个 ±1 去跟正确答案比错了才更新权重对了就完全不动。Adaline 不一样它算完加权和之后不套函数直接把这个连续值 z 拿去和真实目标 y 比较用两者的差去更新权重分类只是训练完之后顺手做的一件事看 z 的正负号。这个差别带来的后果很实际感知机只要分对了就收工哪怕这个样本只是勉强压线过关Adaline 会继续把 z 往目标值上拉让“分对”变成“分得又对又稳”。所以同样的数据、同样的学习率Adaline 的收敛过程通常更平滑遇到噪声也不至于因为一个样本就把权重拽飞。用一张表对比会更清楚对比项感知机Adaline输出阶跃后的 ±1未激活的连续值 z更新依据分类对错连续误差 y − z分对之后停止更新继续缩小误差学习规则感知机规则LMS最小均方规则记住这张表的第三行它是新手理解两种模型时最容易忽略的一点。2.3 LMS 在最小化什么误差平方和加上一句梯度下降LMS 的全称是 Least Mean Squares最小均方。名字里的“均方”指的就是它想压小的东西——预测值和真实值之差的平方。把所有样本的误差平方加起来再除以 2这个 1/2 纯粹是为了求导时把平方的 2 约掉不影响结果就得到J(w) 1/2 × Σ (yᵢ − zᵢ)²训练的目标就是找到一组 w让 J(w) 尽量小。怎么找沿着让 J 下降最快的方向挪一小步这就是梯度下降。对 J 求关于 w 的偏导会得到一个非常干净的式子∂J/∂w −Σ (yᵢ − zᵢ) xᵢ负号表示我们要往梯度的反方向走所以权重更新写成w ← w η × (y − z) × x四个符号挨个解释η 是学习率决定每次挪多大一步y 是真实目标z 是当前预测x 是这次的输入向量。整个式子的意思就是——预测低了就顺着输入的方向把权重调大一点预测高了就往反方向调一点调多少由误差大小和学习率共同决定。我第一次推这个式子的时候卡了很久后来发现卡点根本不在求导而在于没想明白“为什么更新量要乘 x”。你换个角度想如果某个输入 xᵢ 这次是 0那它对结果没贡献自然也不该背锅乘上 xᵢ 之后它的权重就纹丝不动输入越大责任越大改得越多。这么一看就顺了。2.4 入门第一课选它不是因为它简单有人会觉得都学神经网络了还搞这么个线性模型是不是太小儿科。恰恰相反Adaline 是唯一一个你能把梯度自己手推一遍、并且每一步都能对上代码的模型。它身上有三个特点让它成为最好的起点。第一没有隐藏层没有链式法则误差从输出到权重的路径只有一步你不需要把反向传播当成黑匣子先背下来第二权重可解释训练完的 w 里每个数字对应一个特征的重要程度正负号代表方向你能拿它跟业务常识对一对对不对一目了然第三它的训练循环骨架——算预测、算误差、算梯度、更新权重——和后面几十层的网络是同一个骨架只是梯度那一步换成了更复杂的算法。说句血泪经验直接去啃多层网络的反向传播绝大多数零基础的人会在第三天放弃因为你不知道那些偏导符号在算什么。先在 Adaline 上把“误差驱动权重”这件事跑通十遍再去学多层你会发现新东西只是量变。3. 跑通第一个例子30 行代码让 Adaline 学会与运算概念讲完就该动手了。这一章的目标很具体不装任何深度学习框架只用 Python 和 numpy写出一个能自己学会“与运算”的 Adaline并且能看懂它每一步在干什么。整个过程 30 行左右跑完你会有一种“原来就这么点东西”的踏实感。3.1 开跑之前环境、目录和一份最小数据需要的东西少得可怜Python 3.8 以上加一个 numpy。打开终端执行下面这行看到版本号打印出来就算齐活。# 只需要 numpy不需要 torch / tensorflow pip install numpy python -c import numpy; print(numpy.__version__)如果你不想折腾环境用任何一个自带 numpy 的在线笔记本环境也一样能跑代码完全一致。目录建议这样组织图个清爽exp1_adaline/ ├── adaline.py # 模型主体 ├── data.py # 数据构造 └── run.py # 跑起来看结果数据我们手工造四个样本两个特征。这里用±1 编码而不是常见的 0/1原因稍后 3.3 节会说这个细节是新手最容易忽略、又最容易导致“代码看着没错但结果不对”的地方。目标值是“与运算”只有两个输入都是 1 时输出 1其余三个样本都是 −1。3.2 第一版完整代码从初始化到收敛把下面这段存成run.py直接运行。import numpy as np # 双极编码的四个样本第 0 列固定为 1充当偏置项的乘数 X np.array([[1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1]], dtypefloat) # 与运算目标只有 (1, 1) 输出 1其余三个都是 -1 y np.array([1, -1, -1, -1], dtypefloat) eta 0.1 # 学习率 w np.zeros(3) # 权重从 0 开始 max_epochs 30 # 最多扫 30 轮防止死循环 for epoch in range(max_epochs): wrong 0 # 这一轮分错几个 sse 0.0 # 这一轮的误差平方和 for xi, yi in zip(X, y): z w xi # 加权求和注意这里没有套激活函数 err yi - z # 真实值减预测值负数说明预测高了 w w eta * err * xi # LMS 更新一行就是全部的核心 sse err ** 2 if (z 0) ! (yi 0): # 用符号判断分类是否出错 wrong 1 print(f第 {epoch:2d} 轮 误差平方和 {sse:7.4f} 分错 {wrong} 个) if wrong 0: # 全分对就提前收工 print(f第 {epoch} 轮起已经全部分对停止训练) break print(学到的权重:, np.round(w, 3)) for xi, yi in zip(X, y): z w xi print(f输入 {xi[1:]} 线性输出 {z:.3f} 分类 {1 if z 0 else -1} 目标 {int(yi)})跑起来你会看到类似这样的输出数值会随环境有些微差异趋势一致第 0 轮 误差平方和 1.7081 分错 2 个 第 1 轮 误差平方和 0.5205 分错 0 个 第 1 轮起已经全部分对停止训练 学到的权重: [-0.198 0.178 0.156]第 0 轮结束后还有两个样本分错第 1 轮就全对了。这不是巧合也不是代码写错了——四个样本在二维平面上是线性可分的Adaline 只需要把那条分界线推到正确位置就够。停下来的时候权重还没收敛到最终解只是“刚好够用”这个问题 5.4 节会专门讲。3.3 逐行拆开训练循环里其实只有四个动作把上面那段循环扒掉所有装饰剩下的就是四件事这四件事构成了所有神经网络训练的核心算预测z w xi一行点积。算误差err yi - z方向很重要反了就变成梯度上升了。改权重w w eta * err * xi误差乘以输入再乘以学习率。看情况停全分对就 break否则继续下一轮。第 3 步是整份实验代码里唯一真正属于“学习”的部分其余都是准备工作。你可以试着把这一行注释掉程序会原地打转 30 轮权重永远是全 0——这就是“不学习”的网络长什么样。现在说回编码的问题。为什么用 ±1 而不是 0/1敲下面几行试试就明白了import numpy as np # 假设某个样本是 (0, 0)用 0/1 编码偏置列固定为 1 xi np.array([1.0, 0.0, 0.0]) err 1.0 # 假设当前预测是 0目标 1 print(0.1 * err * xi) # 输出 [0.1 0. 0. ]结果是只有偏置那一维被推动了两个特征的权重一动不动。因为它们的输入是 0乘出来就是 0。在 0/1 编码下一个样本“没有某个特征”和“某个特征的权重不该改”是无法区分的训练会变得非常拖沓。换成 ±1 编码后−1 也能贡献更新量每个维度每一轮都在参与调整收敛快得多。这是入门实验里最常见的一个隐性坑很多人抄完代码跑不收敛问题就出在编码上。3.4 结果怎么看三个必须打印的东西跑通不算完能读懂输出才算真的懂了。每次跑完至少要看这三样。第一样是收敛轮数。上面第 1 轮就停了说明数据太简单。如果你的实验数据收敛得要几十轮也正常跟学习率、初始化和数据尺度都有关系。第二样是最终权重。你会看到第一个数偏置项是负的后两个是正的。这完全符合逻辑偏置为负意味着这个神经元默认倾向输出 −1要推翻它必须两个特征同时给出正贡献把 z 顶到 0 以上。这正好就是“与运算”的语义。这就是线性模型的可解释性——权重不是随机数它讲了一个故事。第三样是每个样本的线性输出 z。注意不只是看符号还要看绝对值。z 0.5 和 z 5.0 符号相同、分类相同但后者离分界线远得多遇到新样本时更不容易翻车。这种“离边界有多远”的量后面会变成支持向量机里的间隔概念现在先有个直觉就行。注意如果某个样本分对了但 z 只有 0.001别高兴太早这说明它正好踩在分界线上稍微换个输入就会错。真正的收敛应该让每个样本都有一定的余量。3.5 动手改一改三个小实验验证你真的懂了看懂和会用之间隔着一段距离最快的跨法是动手改参数然后预测结果再看实际是不是这样。实验一改学习率。把eta改成 0.001 跑一遍再改成 5.0 跑一遍。前者你会看到收敛变慢、需要更多轮后者很可能直接震荡误差平方和越跑越大。这条经验以后调任何模型都用得上。实验二改初始化。把w np.zeros(3)换成w np.array([0.5, -0.3, 0.8])重跑。你会发现收敛轮数变了但最终仍然能分对。这就是线性可分数据的好处从这个角度看初始化的影响没那么玄学。实验三改成或运算。把 y 改成[1, 1, 1, -1]只要有一个输入是 1 就输出 1重跑看看权重变成什么样。你会发现偏置变成了正数两个特征权重仍然是正的——语义又对上了。三个实验加起来不到十分钟但对“权重在表达什么”这件事的理解比看十页教材都实在。4. 从玩具数据到像样的任务加上标准化和批量更新四个样本的与运算跑通只说明你会写循环了。真实一点的数据马上会给你上课特征尺度不一样、目标值是连续的、样本几百上千个逐个更新的循环慢得让人想砸键盘。这一章解决这三个问题改完之后你手上这套代码就能处理正经的回归任务了。4.1 为什么输入尺度一变就翻车先看一个现象。假设你要预测房价两个特征房间数1 到 5和面积30 到 300 平方米。量级差了 60 倍。这时候学习率设成 0.1房间数那维的更新量小得可以忽略面积那维一更新就把权重甩飞训练过程要么慢如蜗牛要么直接发散成 NaN。这不是代码写错了是梯度的形状被尺度带偏了。你可以把损失函数想象成一口锅尺度一致的时候锅是圆的梯度直直地指向锅底尺度差太多的时候锅被拉成了细长的椭圆梯度方向斜得厉害走一步就冲到对面山壁上来回震荡。血泪经验在这里我见过太多人包括我自己在实验报告里写“模型不收敛”回头一查数据压根没做任何预处理。神经网络导论的实验里标准化这一步经常被一句话带过但它是性价比最高的一个改动。4.2 加一步标准化三行代码效果立竿见影标准化的做法很朴素对每个特征减去均值、除以标准差把它压成均值 0、方差 1 的分布。import numpy as np rng np.random.default_rng(42) # 固定随机种子保证每次跑结果一样 n 200 X_raw rng.uniform(0, 1, size(n, 2)) X_raw[:, 1] X_raw[:, 1] * 500 # 故意让第二个特征量级大 500 倍 # 真实关系y 主要受第一个特征影响第二个特征影响很小 true_w np.array([3.0, -0.02]) y X_raw true_w 1.0 rng.normal(0, 0.1, sizen) # 关键的三行按列求均值、标准差然后整体缩放 mu X_raw.mean(axis0) sigma X_raw.std(axis0) X_std (X_raw - mu) / sigma # 在最左边补一列 1作为偏置项的乘数这样后面不用单独处理 b X np.hstack([np.ones((n, 1)), X_std]) print(标准化后每列的均值:, np.round(X[:, 1:].mean(axis0), 6)) print(标准化后每列的标准差:, np.round(X[:, 1:].std(axis0), 6))axis0表示沿着样本方向聚合也就是“对每一列单独算”。这个参数新手经常写错成axis1结果算出来的是每个样本自己的均值标准化完全失效——这也是个高频翻车点。np.hstack那一行值得多说一句。它把一列全 1 拼到特征矩阵最左边这样偏置 b 就变成了权重向量的第 0 个分量更新公式里不用再单独写一项。这是工程上的小技巧几乎所有入门代码都这么干看到的时候不要懵。提示如果以后要划分训练集和测试集标准化必须用训练集算出来的 mu 和 sigma 去处理测试集不能各算各的。否则测试集的分布信息就提前泄露了评估结果会虚高。4.3 换成连续目标把 Adaline 当线性回归用刚才那段数据的目标 y 是连续值不再是 ±1。这正是 Adaline 擅长的场景——它本来输出的就是连续值做分类只是顺手。把训练循环改一改损失换成均方误差w np.zeros(X.shape[1]) # 权重个数等于特征数含偏置列 eta 0.1 for epoch in range(200): z X w # 一次算出全部 200 个样本的预测形状 (200,) err y - z # 残差向量 w w eta * (X.T err) / n # 批量 LMS把所有样本的更新量取平均 if epoch % 50 0: mse np.mean(err ** 2) print(f第 {epoch:3d} 轮 均方误差 {mse:.6f}) print(学到的权重:, np.round(w, 3))跑完你会看到均方误差快速下降然后趋于平缓。学到的权重里第一个是偏置后两个对应两个特征——因为做了标准化它们不再直接对应原始量纲但可以横向比较重要性第一个特征的权重绝对值明显大于第二个跟造数据时设定的 3.0 和 −0.02 是对得上的。这三行代码里有两处值得停下来想。第一处是X w把原来逐个样本的循环换成了矩阵乘法200 个样本一次算完速度快几十倍这叫向量化是 numpy 最值得早点养成的习惯。第二处是X.T err / n它其实就是把每个样本的err * xi加起来再取平均——和 3.2 节那个一行更新在数学上是同一件事只是从“逐个别”变成了“一起算”。看懂这个等价关系你就跨过了从教学代码到工程代码的那道坎。4.4 调参的先后顺序先学习率再初始化最后轮数新手调参最常见的做法是三个参数一起瞎试试到能跑就收工。这样即便跑通了你也不知道是谁的功劳。有个更靠谱的顺序第一步只动学习率。固定初始化和轮数让 eta 从 0.001、0.01、0.1、1.0 各跑一次记录每轮的损失。判断标准很简单损失稳步下降且没有剧烈抖动就是合适损失上下横跳甚至变大说明太大损失降得像蜗牛说明太小。标准化之后0.01 到 0.1 通常是个安全区间。第二步再动初始化。学习率定下来之后换几组初始权重看收敛轮数的波动。如果波动极大说明数据可能接近不可分或者学习率还是偏大。第三步才考虑轮数。轮数不是越多越好Adaline 是线性模型训练太久只会在最优解附近来回微调甚至因为学习率偏大而越过最优点。所以别一上来就设 10000 轮图安心。5. 新手避坑Adaline 实验里最容易翻车的 5 个地方这一章的五条基本都是我和身边人真真切切踩过的。每条按“现象、原因、解决”写对照着看你自己卡在哪一步。5.1 坑一代码跑通了但换个输入就全错现象四个训练样本分类 100% 正确你兴冲冲拿个新样本 (1, 1) 去测结果输出 −1和预期相反。原因训练停得太早权重还停在“勉强分对”的位置分界线正好贴着某个样本。3.2 节那个例子第 1 轮就停了权重只有 ±0.2 的量级余量非常小。解决把停止条件从“全分对”改成“连续若干轮全分对”或者“误差平方和小于某个阈值”比如要求连续 5 轮wrong 0才停。另一个办法是训练时多跑几轮不停早事后再看每个样本的 z 绝对值是否都大于 0.5。留余量这件事在真实项目里比在实验里重要得多。5.2 坑二损失不降反升几轮之后变成 nan现象打印出来的误差平方和一轮比一轮大跑到后面出现nan或者inf。原因九成是学习率太大配合没做标准化的数据一次更新就把权重推到很远的地方。剩下的一成是数据里有极端值或者标签写反了。解决先把 eta 除以 10 再跑一遍如果还是发散检查数据尺度用 4.2 节的标准化处理。另外把每轮的损失打印出来而不是只看最后一轮这样你能看到它从第几轮开始变坏的定位快得多。5.3 坑三权重一直是 0训练像没发生现象跑了 30 轮最后打印出来的权重还是[0. 0. 0.]或者每轮损失完全一样。原因几种可能——eta被写成了整数除法比如1/100在某些写法下直接是 0、更新写成了w eta * err * xi而不是w w eta * err * xi少了累加每次都被覆盖、或者xi传进去的是空数组。解决在循环里加一句print(w)打印第一轮前后的权重一眼就能看出是没更新还是被覆盖。更新语句记住老规矩带加号。5.4 坑四循环看着收敛了但每次跑结果都不一样现象代码没改连跑三次权重三个样有时候收敛有时候不收敛。原因初始化用了np.random.randn却没设种子或者数据生成部分每次都在随机。解决在程序开头固定rng np.random.default_rng(42)之后所有随机操作都走这个 rng别用全局的np.random。实验代码最忌讳“结果不可复现”写报告的时候你会感谢自己。5.5 坑五把回归任务当分类任务判断收敛现象做一个连续值预测的实验怎么跑都“不收敛”打印出来分错的样本数一直是 0但均方误差很大。原因用if (z 0) ! (yi 0)这种符号判断去衡量连续目标的训练效果。连续目标下几乎所有样本的符号都相同这个指标永远是 0 分错完全没有参考价值。解决分类看分错个数和间隔回归看均方误差或平均绝对误差两套指标别混用。判断标准跟着任务类型走这条规律在以后所有模型上都成立。6. 下一步学什么从 Adaline 走到多层网络的路线图这份实验 1 的代码跑通之后最怕的就是停在原地反复改学习率玩一星期然后不知道下一步干嘛。下面这条路线是我自己走过、也推荐给别人的顺序每一阶段都给了明确的验收标准你照着往下一格一格跳就行。阶段要学的东西大致投入验收标准一把本次代码封装成Adaline类半天能初始化、fit、predict 三个方法跑通二感知机学习规则与 Adaline 的对比半天能说清两者更新时机差在哪三异或问题为什么线性模型做不了一天能画出两类点、说明不存在一条直线分开四多层感知机与反向传播三到五天能手推两层网络的梯度五用框架复现同一个实验两天同一份数据框架和手写结果对得上第一阶段的具体技巧值得展开讲。把你现在这堆散在脚本里的代码收成一个类class Adaline: def __init__(self, eta0.01, epochs50, seed42): self.eta eta # 学习率 self.epochs epochs # 最大轮数 self.rng np.random.default_rng(seed) def fit(self, X, y): self.w self.rng.normal(0, 0.01, X.shape[1]) # 小随机数初始化 self.losses_ [] for _ in range(self.epochs): err y - X self.w self.w self.eta * (X.T err) / len(X) self.losses_.append(float(np.mean(err ** 2))) return self def predict(self, X): return X self.w # 回归直接返回连续值别小看这一步。fit里把损失记到losses_里、参数全部放进__init__、fit返回self以便链式调用这些习惯和主流框架的接口是一致的。等你后面换到框架时会发现调用方式几乎没有区别只是把self.w换成了框架里的参数对象。第三阶段千万别跳过。异或问题看着像课后习题其实是整个神经网络历史的转折点正是因为线性模型做不了它才有了隐藏层和反向传播。你自己动手把这个失败复现一遍再看多层网络就知道那些额外的东西是为了解决什么问题而存在的不是凭空冒出来的。第五阶段有个容易忽略的细节用框架复现时务必把随机种子、数据划分、标准化参数全部对齐否则两边结果对不上你会怀疑人生。对不上是常态对上一次之后你对整个流程的理解会上一个台阶。从那以后我每次跑新实验都强制自己先把随机种子、数据标准化、损失打印这三件事写死在脚本开头再开始调模型——省下来的时间比多试十个超参数加起来都多。这份实验 1 的代码就是你练习这个习惯最安全的沙盒跑坏了重来一遍只要十分钟希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑