第一个神经元 — 感知机如何在三十行代码里学会 AND 和 OR¶
这一章讲三件事: 一个 1957 年的算法(感知机)怎么用三十行 Rust 代码「学会」 AND 和 OR 这两个函数;它内部的三个动作(前向、算差值、更新)各自在干什么、 为什么这套动作保证越错越少;以及它的能力边界画在哪里——哪些数据它永远学不会。 读完你会拿到全书的第一条机制推导链,本组 03 章直接接在它的边界上。 不需要任何基础,遇到的生词都在当场解释。
1. 先看现象:同一个算法,学会两个不同的函数¶
第 1 章结尾的测试里发生了这样一件事:
作者把 OR 函数的四个真值行喂给感知机,训练——拿数据把模型里的数调对——, 测试通过——它学会了 OR。 然后他一行代码不改,把 AND 函数的四个真值行喂给同一个算法, 测试又通过了——它学会了 AND41。
这两次训练之间,变的只有数据。同一套代码自己从数据里摸出了两个完全不同的函数, 靠的是同一套代码摸出两组不同的内部数值。
这个现象的份量,原作者引用 Rosenblatt 自己的话来标定: 他称感知机为「第一台能有原创想法的机器」——意思是它能学习和推广到新情况, 而不只是执行预先写死的规则41。
本章的主线例子就是这条训练轨迹。 下面每一节都会回到它: 第 4–6 节搭出代码和数据,第 7 节把训练全程走一遍(每步都有具体数字), 第 8 节解释为什么这套更新一定有用。
顺便交代本章的组织:原书第 1 章分四节(直觉建立 → 从数据学 → 实现 → 为什么有效), 我们的拆解按同样的顺序走,只在 Rust 教学密集处放慢脚步。 如果你只想抓主线,读第 2、7、8 节即可;第 4–6 节是给动手读者的慢速通道。
2. 顶层全景:感知机的全部身家¶
先别管 Rust,先看清这个算法长什么样。读之前先回答一个直觉问题: 既然 AND/OR 用 if-else 两行就写完了,为什么要造一台「学」的机器? 因为 if-else 方案里,行为是你给的;而我们要的行为(比如「下一个该接什么词」) 没人给得出。感知机是通用的:它不在乎学的是 AND 还是别的什么, 只要数据能画进坐标系、答案能分成两类,同一台机器就能学—— 用一行 if-else 换一个可以装任何函数的容器,这就是下面全部零件的意义。
它身上的零件一只手数得完:
输入 [x₁, x₂] 两个数,比如 A、B 两个开关的取值
│
├─ × w₁, × w₂ 各自乘以一个「权重」(weight)——学到的数
├─ 求和 + b 加起来,再加一个「偏置」(bias)——学到的数
└─ 过一道开关 σ 大于 0 输出 1(是),否则输出 -1(否)
图说:从左到右跑一遍叫「前向」(forward)。学习 = 把 w₁、w₂、b 调对。
写成一行公式(原书的写法,照抄):
y = σ(wᵀx + b)
wᵀx 的意思是:w 和 x 是对应位置相乘再全部加起来(两个数乘积的和)。
比如 w = [2.1, 2.7]、x = [1, -1],wᵀx = 2.1×1 + 2.7×(−1) = −0.6。
σ 是那道开关——书里的正式名叫**阶跃函数**(step function):输入大于 0 出 1,否则出 −1。
每个零件解决一个问题:
| 零件 | 解决什么问题 | 没它会怎样 |
|---|---|---|
| 权重 w | 两个输入的重要性不同(比如「两个都真才算真」的题里,B 的影响和 A 不一样) | 所有输入只能一视同仁 |
| 偏置 b | 阶跃函数的触发门槛可以整体平移(没它,过原点的线不够用) | 有些分界线画不出来 |
| 阶跃函数 σ | 要的是是非判断,不是连续数值 | 输出没法对应「真/假」两类 |
学习在这台机器里就是一件事:找到一组 w 和 b,让所有训练例子都判对。 训练开始前它们是多少?不知道——这正是「学习」存在的意义42。
记号就地展开:wᵀx 到底是什么¶
wᵀx 这个记号后面会成千上万次出现,第一次见就拆开(原书在前言约定过记号,
这里提前兑现):w 和 x 都是向量(一串数),wᵀx 的意思是——
对应位置相乘,再全部加起来。拿一组具体的数(为演示编的):
上标 ᵀ(转置)在这里不用深究:它只是一种约定,提醒你「w 和 x 是两个向量, 这一串运算叫加权求和」。第 6 节的感知机代码里那个 for 循环, 干的就是这件事——乘、加,循环到底。
一段历史:1957 年的起点,和一句给本章定调的引语¶
本章题头引了 Helen Keller 的一句话:
我渴望成就伟大而崇高的事业;但我的首要职责,是把小事当作伟大而崇高的事业来完成1。
这句引语选得用心:本章的「小事」就是 AND 和 OR——简单到每个程序员都懂, 但原书会认真对待它们,就像对待伟大任务一样。 因为从这两个小函数里长出来的机制,会一直活到最终的神经网络里。
主角感知机,1957 年由 Frank Rosenblatt 做出,1958 年发在论文 《The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain》—— 标题直译:「感知机:大脑中信息存储与组织的概率模型」2。 两个信息都在标题里:它的灵感来自神经科学和心理学(模拟生物神经元如何产生学习和认知), 以及它从一开始就自视为「学习机制」的模型,不只是电路玩具。
原书从 Rosenblatt 的遗产里挑出两个到今天仍是机器学习地基的思想3:
- 线性分类器:用一条直线(高维时是平面、超平面)把两类分开;
- 通过参数更新来学习:不直接写答案,而是反复小步调整内部数值。
本章的路线也预先交代了,分四步4:
① 用 AND/OR 建立直觉(第 3–4 节)
② 把视角从「写代码」换成「从数据学」(第 4 节)
③ 实现感知机,看它在数据上训练(第 5–6 节)
④ 退后一步,搞懂它为什么有效,顺带立起一批基础概念(第 7–8 节)
4. 热身一:AND/OR 长什么样,以及第一段 Rust¶
原书不直接从算法讲起,而是先让你用最笨的办法把 AND/OR 写死, 然后问:这种「写死」能不能换成「学出来」? 这个对照是本章全部内容的地基。
AND 和 OR 的真值表¶
两个布尔值(只能是真或假)进去,一个布尔值出来。这张表还有个值得点破的性质: 真值表是「数据即函数」的特例——逻辑课里,AND/OR 干脆就是靠真值表定义的, 表里的每一行都是完整的知识,没有隐藏的未知5。后面会反复用到这个对照: 真值表数据没有任何未知,真实数据全是未知(本组 03 章的核心话题)。
| A | B | A AND B | A OR B |
|---|---|---|---|
| 真 | 真 | 真 | 真 |
| 真 | 假 | 假 | 真 |
| 假 | 真 | 假 | 真 |
| 假 | 假 | 假 | 假 |
AND 要两个都真才真;OR 只要有一个真就真43。
写程序时这俩就是语言自带的运算符:Rust 里 AND 是 &&,OR 是 ||44。
最小的 Rust 程序长什么样¶
原书的第一段代码就是上面两个函数加一个 main。
逐行说清这里第一次出现的语言零件(后面每一章都要用):
fn定义函数。 名字后面括号里写参数,参数要有名字和类型(a: bool)。 Rust 的命名习惯是全小写,多个词用下划线连(叫 snake case),比如predict_one45。- 返回值用
-> 类型声明。 Rust 不用写return:函数里最后一个不带分号的表达式 就是返回值46。 let声明变量,默认不可改。 想改必须加mut(mutable 的缩写)—— 「这个变量会被重新赋值」在 Rust 里是显式声明的,不是默认值47。- 同名再声明一次可以「覆盖」旧值,叫 shadowing。 不加
mut也能「改」, 实际是造了个新变量把旧的盖住了48。 println!末尾带感叹号,是宏调用——一种长得和普通调用很像的特殊代码形态, 现阶段只要知道它负责往屏幕印东西49。
把程序存成 main.rs,rustc main.rs 编译,./main 运行,屏幕上印出
true AND false = false 这类结果——行为被代码写得明明白白50。
补一层背景:Rust 程序的两辈子¶
对完全没碰过编译型语言的读者,补一层必要的背景。
你写的 main.rs 是给人看的源代码;rustc 把它整个翻译成机器语言,
产出一个可执行文件——从此这个程序可以离开 rustc、在任何同类机器上直接跑。
这和 Python 那种「边读边执行」的活法不同:
编译期(翻译那一刻)就是 Rust 的检查全部生效的地方——类型不对、所有权违规、
函数不存在,全部在这一刻被拦下,而不是等程序跑到那一行才炸6。
记住这个分工,后面所有「编译器替你把关」的说法都有着落: Rust 的安全性不是运行时巡逻出来的,是翻译时一次性验完的。
作者的提问就是本章的发动机: 这种明明白白写出来的行为, 能不能不写出来、改成从数据里学出来?51
5. 热身二:把真值表变成程序读得懂的数据集¶
要「从数据学」,先得有数据。真值表天然就是一张微型数据集:
- 特征(feature,也叫属性、输入变量):每一行前面的列——这里的 A、B;
- 标签(label,也叫输出变量):每一行的答案列;
- 每一行是一个完整的例子(example);每个标签值定义一个类别(class), 本题两类:真、假52。
但程序不认「真/假」这两个字。原书做了一个选择:用 1.0 代表真,用 −1.0 代表假53。 (为什么不用 0 和 1?先卖个关子——第 6 节会看到,−1/1 让「更新方向」的数学干净得多。)
于是 OR 函数的数据集变成四行文本:
前两列是特征,最后一列是标签:前三种情况 OR 为真,只有「假 OR 假」为假。
cargo init 与 cargo test:两条命令走天下¶
Rust 项目的日常由两条命令撑起7:
cargo init:把一个普通文件夹变成 Rust 项目——它生成Cargo.toml(项目的清单:名字、版本、依赖列表)和目录结构;cargo test:编译并运行所有测试,红绿一目了然。
后面还会陆续遇到 cargo run(编译并运行程序)、cargo build(只编译)。
这四条命令覆盖了本书的全部日常;遇到新的再就地解释。
测试先行:先写「验收标准」,再写实现¶
作者接下来的做法有个名字,叫测试驱动开发(Test-Driven Development): 先写一个会自动检查的测试,描述「数据加载正确应该是什么样」,再去实现加载54。 好处有两个:动手前先逼你想清输入输出长什么样;改代码后随时确认没改坏。
第一个测试做的事:把上面那段文本喂给一个叫 load_data_from_csv 的函数,
断言(用 assert_eq! 宏)返回的矩阵每一格都是对的55。
第一次跑必然失败——函数还不存在,编译器直接报错 cannot find function56。
补一个只有一行 todo!() 的空函数,编译过了,测试运行,红(FAILED,「not yet implemented」)57。
最后写出真实现,测试转绿(ok)58。
数据加载器的六行,逐个零件点名¶
加载器的完整实现只有六行,把它和零件表对起来看(代码照原书):
fn load_data_from_csv(content: String) -> Vec<Vec<f32>> {
content
.lines()
.map(|line| {
line.split(',')
.map(|field| field.trim().parse().unwrap())
.collect()
})
.collect()
}
content
.lines() 按行切:得到 4 行的序列
.map(|line| ...) 对每一行再切一次:
split(',') 按逗号切成字段
.map(...) 每个字段:去空白(trim) → 解析成 f32(parse) → 担保成功(unwrap)
.collect() 收成一行 Vec<f32>
.collect() 收成 Vec<Vec<f32>>:4 行 × 3 列
数据的流向是「从外到内再收回来」:lines 先切行,内层 map 处理每行内部,
两个 collect 各收一层8。类型写明 -> Vec<Vec<f32>> 让编译器知道
最终要收成什么——collect 自己猜不出目标形状。
回看这两类方法:.map 属于「返回一列结果、继续链式」的方法,
两个 .collect 属于「吃掉整列、给出结果」的方法,分类严丝合缝。
属性、mod tests 与 #[cfg(test)]:测试为什么住在代码文件里¶
第一个测试的完整模样里还有两个没解释的东西:
mod tests { ... }声明了一个叫 tests 的模块(module)—— 给一坨代码围起来的命名区域。Rust 的惯例是把单元测试放进被测文件里的这个模块, 和被测代码住同一屋檐下(模块的细节第 4 章再展开)9;#[cfg(test)]是一种属性(attribute):贴在代码块上的标签信息, 不改变逻辑,但影响编译。cfg(test)的意思是「只有在跑测试时才编译这一块」—— 正常构建程序时,测试代码根本不进可执行文件10;use super::*;把外层文件的东西引进来:测试要调用load_data_from_csv, 得先「看见」它。
把这套机制和第 4 节测试驱动开发的流程合起来看:Rust 把「测试是代码的一等居民」
做成了语言级设施——测试就在被测文件里、有编译条件、有独立运行入口(cargo test)。
写测试的摩擦小到几乎为零,这本身就是对「测试驱动」这种工作法的语言级支持。
Result 的一手实验:失败被写进了类型¶
parse() 的完整实验原书给过,照抄思路走一遍:
let number_string = "1.0";
let result : Result<f32, ParseFloatError> = number_string.parse();
let number: f32 = result.unwrap();
三行的意思:第一行是个文本;第二行试着做解析——把文本转成数——目标是 f32——
返回的不是数,而是一个 Result,它要么包着「成功值」、要么包着「错误值」,
尖括号里的 f32, ParseFloatError 就是这两样东西各自的类型85;
第三行 unwrap() 是说「我担保成功,把数给我;万一错了就当场 panic」。
把 "1.0" 换成 "abc" 再跑,panic 的消息会告诉你解析失败。
注意全程没有 try/catch 那种「绕着失败走」的语法——Rust 的哲学是:
失败是返回值的一种常态,必须被看见、被处理、或者被显式担保掉(unwrap 就是那纸担保书)。
作者也交了底:教学代码里这么写,是因为「我们控制自己解析的文本」;
生产用法应该改进61。这句交代很值得学——每个 unwrap 都是一处 TODO,
意思是「这里可能 panic,我暂且担保它不会」。
测试数据串里的一个小机关¶
测试里那串数据的写法有个值得认的小机关:
行尾的反斜杠 \ 是续行:它告诉编译器「这串字符还没完,下一行接着来」,
于是四行文本在源码里排得整齐,运行时仍是同一个带换行符的文本块11。
\n 是换行符本身。原书还交代了一个测试惯例:测试数据尽量摆在明处——
写测试时,让数据完整躺在读者眼前,比藏在文件里更清楚12。
cargo test 的输出里有什么¶
第一次成功的 cargo test,输出值得逐段认一遍13:
Compiling play v0.1.0 (...) ← 编译阶段:这一步过了,类型与所有权检查全过
Running unittests main.rs (...) ← 运行阶段
running 1 test
test tests::it_loads_or_data ... ok ← 绿
test result: ok. 1 passed; 0 failed; 0 ignored; ...
三个分段对应三件事:编译(语言级检查)、运行(逻辑执行)、结果(通过数)。
前面那个红阶段(FAILED + not yet implemented)同样三段都在,只是结果段报红。
原书说:之后的章节不再贴完整测试输出,但测试会一直陪着——
经常写在代码之前,有时紧跟其后,用来验证刚造出来的东西14。
「测试先行」从这一章的演示,变成全书的默认工作方式。
元组、for 循环与 split_at:分离输入和标签¶
数据加载完,下一步是把每行最后一列(标签)和前两列(输入)分开。 原书用一段新代码同时教了三样东西:
fn split_columns(matrix: Vec<Vec<f32>>) -> (Vec<Vec<f32>>, Vec<f32>) {
let mut labels = Vec::new();
let mut inputs = Vec::new();
for row in matrix {
let (rest, last) = row.split_at(row.len() - 1);
labels.push(last[0]);
inputs.push(rest.to_vec());
}
(inputs, labels)
}
for row in matrix:对这一行行数据循环,每次拿到一行;let (rest, last) = ...:这叫元组(tuple)——把几个值打包成一个, 左边同样形状的括号把它拆开,一份给rest、一份给last。 函数签名末尾的-> (Vec<Vec<f32>>, Vec<f32>)说的是: 这个函数返回两个值,一对;split_at(n):把向量在位置 n 切成两段——这里取「除最后一格以外」和「最后一格」, 正好就是输入和标签的分离86。
push 是往向量尾部追加。循环走完,inputs 和 labels 两个向量一一对应:
第 i 个输入的标签就是 labels[i]。从这张表到这两列,数据才算真的准备好了。
数据加载里的 Rust,以及第一个「语言即机制」时刻¶
真正实现只有六行,但它把 Rust 的几样主力全用上了:
-
字符串——程序里的文本形态——按行切开,再按逗号切开,再把每格从文字转成数字, 一列操作串起来做完;
-
这种「逐个处理一列元素」的做法叫迭代,工具本身叫迭代器(iterator), 可以链式地
.map(...).collect()起来59。 - 转数字可能失败(格子里万一不是数字呢?),Rust 不让失败悄悄溜过:
parse()返回一个叫Result的类型,它要么是「成功值」、要么是「错误值」60。 - 代码写了
.unwrap():「我保证这里是成功值,直接给我结果;万一错了就让程序当场停掉。」 这种当场停掉叫 panic。教学代码可以这么干,生产代码不该61。
「语言即机制」时刻在这里: Result 的存在意味着「解析可能失败」这件事
被写进了类型里,编译器会盯着你处理它。Python 里你很可能忘掉这茬,直到线上炸了才想起来。
Rust 的类型系统在替你把关——这就是本章前文说过的「把别处隐含的细节摆上台面」,
这里是它第一次具体兑现。
Rust 的类型全家福:一张先混个脸熟的表¶
原书在第一个测试旁插了一段类型导览,后面会一个个用熟,这里先整张脸熟15:
| 类别 | 成员 | 一句话 |
|---|---|---|
| 标量(单个值) | 布尔 bool、整数 i32/u32 等、浮点 f32/f64、字符 char | 数字、真假、字母这些原子 |
| 复合(打包) | 元组 tuple、数组 array | 元组各格类型可不同;数组各格同类型、定长 |
| 切片 slice | &[f32] |
引用一个集合的一段,不拥有数据 |
| 标准库常用 | String、Vec、HashMap | 文本、可变长数列、键值表 |
两个马上用得上的细节:
-
String 在 Rust 里其实是一串字节(数据存储的最小单位,8 个二进制位)加一批方法,
trim()(去空白)和parse()(转数字)就是那些方法里的两个16; -
f64 是默认浮点(f64 的精度——小数点后的可靠位数——更高), 本书用 f32 是为了省内存、贴近机器学习的真实约束63。
记不住全表没关系——每一样第一次真正用到时,我们都会就地再解释一次。
String 在 Rust 里有两个常用形态,原书只选了其中一个:String 类型
(字节向量加一串方法);另一个是字符串切片(&str,只是借用一段文本、
不拥有它)17。眼下不用区分两者的使用场景,只要知道存在两种、本书用 String。
Vec 可变、可追加、长度随用随长;Vec::new() 造一个空的,
push 往里添。HashMap 是键值对照表,后面章节用到时再认识17。
trim() 和 parse() 是两个马上要用的方法:trim 去掉文本首尾空白
(数据里「1, 1」的空格就靠它),parse 把文本转成数字18。
顺手认识两个类型¶
Vec<f32>:可变长度的数列,f32是 32 位浮点数。Vec<Vec<f32>>就是「数列的数列」——按原书的约定,内层每个向量是一行, 同下标的位置构成列62。这就是后面所有矩阵运算的毛坯。f32还是f64? Rust 默认f64(64 位,更精确),但全书多数例子用f32: 省内存,也更贴近机器学习实现里「内存吃紧」的真实约束63。
编译器是对手还是教练:这一章的报错文化¶
读这一章时,你会注意到原书对编译错误的态度很特别:不躲、不恼,一路展示。
- 第一次
cargo test,红字cannot find function——作者平静地解释: 我们还没写这个函数呢19; todo!()让测试从「编译失败」进级为「运行失败」(not yet implementedpanic)—— 这是有意的中间状态,证明测试框架已经能跑20;- 第 3 章的 E0382 报错,作者干脆整段印出来,一行行解读编译器给出的建议。
这套态度值得单独点名:Rust 的报错密度高,但每一条都是编译器在替你做检查清单。 把报错当教练而不是对手,是这门语言唯一正确的打开方式—— 也和这本书「让隐藏的东西显式」的主旨完全同构。
6. 感知机的代码形状:trait、struct 与两个方法¶
数据能读了,现在造学习器。原书在这里教了 Rust 里最重要的两个组织工具。
用 trait 定义「一个分类器该有的行为」¶
作者先不急着写感知机,而是先写所有分类器都该遵守的契约:
pub trait Classifier {
fn train(&mut self, input: Vec<Vec<f32>>, labels: Vec<f32>);
fn classify(&self, input: Vec<f32>) -> f32;
}
trait 是 Rust 里定义「一组行为」的方式。 它的意思是:
谁声称自己是个分类器,谁就必须提供这两个方法——train(拿数据练自己,会改内部状态)
和 classify(拿一个新样本,吐出一个类别)64。
两个方法的「伙食」也值得记住:
train吃两样:全体输入(Vec<Vec<f32>>,例子们的矩阵)和全体标签(Vec<f32>)—— 整批进,消化完才算练完一遍;classify只吃一样:单个输入(Vec<f32>),吐一个数——一次一问,即问即答。
训练是「批处理」,推断是「交互式」——这个不对称不是 Rust 的设计, 是机器学习本身的形状:学习必须看够例子才发生,推断对每个新样本独立成立。 trait 的签名把这个形状原样固定了下来21。
这个设计的眼光要放大看:本书终点是神经网络,但神经网络的「行为」同样是 train 和 classify 这两个动作。现在写下的 trait,到第 8 章(神经网络)还在用—— 契约先于实现,模型可以换,行为约定不变65。
train 的第一个参数是 &mut self(classify 是 &self):
training 要改自己,classify 只读自己。「这个方法会不会修改对象」
被写在了方法签名上,这也是 mut 哲学的延续。
泛型:Vec<T> 里的 T 是什么¶
Vec<f32> 中间尖括号里的东西值得单独说破:它叫泛型(generics),
意思是「这个容器对任意类型都可用,用的时候把类型填进去」。
Vec<f32> 是「装 f32 的向量」,Vec<Vec<f32>> 就是「装『装 f32 的向量』的向量」22。
泛型是 Rust 类型系统的核心能力之一,也是本书第 4 章要自己造线性代数库的前提——
到时候你会看到 Matrix<T> 这类写法。现在只需记住:尖括号里是类型占位,
填进去什么,整个结构就只为那种类型工作,填错了,编译器当场拦住。
用 struct 装下「学到的知识」¶
感知机这个类型本身朴素得可爱:
struct 是把几个相关的值打包成一个类型的容器。 三个成员分工明确:
| 成员 | 是什么 | 类别 |
|---|---|---|
weights: Vec<f32> |
每个特征一个权重 | 学到的(训练填数) |
bias: f32 |
门槛 | 学到的 |
iterations: usize |
训练循环几遍 | 人定的(配置) |
注意那个 usize——Rust 里专管「个数、下标」的整数类型66。
最后一行把「参数」这个重要概念引出来:模型内部的数分两类,
一类是训练学出来的(权重、偏置),一类是人提前定的(迭代次数)。
人定的这类后文叫超参数(hyperparameter)——带个「参」字,
听着像更高级的参数,其实是两码事:前者从数据里学,后者学不到,只能人调。
初始值设成全零。感知机对此不敏感,零起步没问题67; 但作者特意记了一笔:权重初始化是个大题目,第 4 章起要专门对待67—— 很多算法对初始值极其敏感,感知机只是运气好。
impl 块、self 与 ::new:方法从哪来¶
Rust 的方法不写在 struct 里,而是写在独立的 impl 块里:
impl PerceptronClassifier {
pub fn new(iterations: usize) -> PerceptronClassifier {
PerceptronClassifier {
weights: vec![],
bias: 0.0,
iterations,
}
}
}
三个要点:
self是方法身处的那个对象。train(&mut self, ...)里的 self 就是「调用这个方法的具体分类器」;&mut表示这个方法会改它,&表示只读它。方法签名就是「这个方法动我不动我」的声明书;::new用双冒号,不用点号。 点号调的是「作用在某个对象上」的方法, 双冒号调的是「挂在类型上」的方法——不需要 self,等价于别的语言里的静态方法87;vec![]是宏:一个创建向量的简写,方括号里直接填初始内容。
测试里的调用串起来就是全貌:
let mut classifier = PerceptronClassifier::new(100); 造一个分类器,
classifier.train(input, labels); 训练它,
classifier.classify(vec![-1.0, -1.0]) 问它新样本88。
测试本身写的也是「验收标准」:四个输入各自的预期输出,
和真值表逐格对应89。
train 的三步骨架¶
trait 的两个方法落进 struct,train 的骨架就这几行:
for _ in 0..self.iterations { // 外层:超参数定好的遍数
for (input, label) in zip(&x_train, &y_train) { // 内层:每个例子
let prediction = self.forward(input); // ① 前向
let loss = self.loss(*label, prediction); // ② 算误差
if loss.abs() > 0.0 {
self.update(input, loss); // ③ 更新(只在错了时)
}
}
}
zip 把输入列和标签列配成一对一对,挨个走68。
三个函数的逐行走查¶
骨架里调用的三个函数,逐个拆开看(代码照原书,注释是我们的):
fn forward(&self, input: &Vec<f32>) -> f32 {
let mut weighted_sum = 0.0;
for feat in 0..input.len() {
weighted_sum += input[feat] * self.weights[feat];
}
weighted_sum += self.bias;
if weighted_sum > 0.0 { 1.0 } else { -1.0 }
}
前向(「forward」是机器学习的通行叫法:数据从输入端流向输出端,从左到右)23。
两个细节:输入是 &Vec<f32>(借用,看一眼就还);返回值不是加权和,是过了阶跃函数的 ±1——
模型对外只说「是/否」。
fn loss(&mut self, label: f32, prediction: f32) -> f32 {
label - prediction
}
fn update(&mut self, input: &Vec<f32>, error: f32) {
for feat in 0..input.len() {
self.weights[feat] += error * input[feat];
}
self.bias += error;
}
loss 只做一个减法:e = 标签 − 预测。
e = 0 判对了;e = 2 表示正类被判成负;e = −2 表示负类被判成正24。
e 的符号天然带着「往哪边修」的信息——这是 −1/1 编码的回报:
若当初用 0/1,「错」只有 0 和 1 两种,分不出方向。
update 把这个方向信息花掉:每个权重按「误差——答错的量——乘以对应输入」挪动,
偏置按误差挪动25。
配合骨架里那行 if loss.abs() > 0.0 { self.update(...) }——
只在犯错时更新,更新量正比于错多少。 三个函数加起来不到三十行,
就是感知机算法的全部实现。
三步循环,就是感知机的全部训练。下一节把三步逐个拆开,带着真数字走。
7. 主线例子:AND 数据集,五步收敛全程¶
现在把第 1 节的悬念走完。用 AND 数据集(注意行的顺序:先三个非全假的,再全假那行),
训练前作者故意把初始值设成差的:w = [0.1, 0.7], b = 10.0——
教材式的不良初值,纯粹为了让你「看见」边界被一点点修好69。
OR 的五遍:相似但不相同¶
原书展示的是 AND 的五遍轨迹;同一台机器喂 OR 的表,走势相似—— 同样的先平移、后转角度、最后干净一遍——但具体的权重路径不同, 收敛遍数也不同26。这不奇怪:OR 的解空间和 AND 的解空间本来就不是一个形状 (OR 的负类只有 (−1,−1) 一个点,边界的「压力分布」完全不同)。
「同一算法、不同轨迹」这个观察,本身就是下一个认知台阶的入口: 既然收敛到的是「某一条能分开的线」,而不是「那条最好的线」, 就难怪原书第 2 章(本组 03 章)要追问「该要哪一条」了。
开局:一片「全是 −1」的世界¶
先体会一下起点。权重全零、偏置为零时:加权和永远是 0, 阶跃函数永远输出 −1——不管输入什么,模型的答案都是「否」27。 前几遍训练,就是在「全错或几乎全错」的地基上开始的: 对 AND 来说,四个例子里只有一个 (−1,−1) 的答案本来就是 −1,其余三个全错。
这就是「从数据学」的朴素起点:它不是带着一点先见起步,而是带着满盘错误起步。 学习算法的全部工作,就是把这一片 −1 的世界改成对的形状。
第 0 遍:错 3 次,权重被推着走¶
下面每个数字都是原书运行时打印记录里的真实输出70, 每步旁边的中间结果是我们按更新规则当场算的。
初始 w=[0.1, 0.7], b=10.0。阶跃函数的规则:加权和 > 0 判 1,否则判 −1。
| 例子 (x₁, x₂) | 标签 | 加权和 wᵀx+b | 预测 | 对错 | 更新后 w / b |
|---|---|---|---|---|---|
| (1, 1) | 1 | 0.1+0.7+10 = 10.8 | 1 | ✓ | 不变 / 10.0 |
| (1, −1) | −1 | 0.1−0.7+10 = 9.4 | 1 | ✗ | w += (−2)·(1,−1) → [−1.9, 2.7] / 8.0 |
| (−1, 1) | −1 | 1.9+2.7+8 = 12.6 | 1 | ✗ | w += (−2)·(−1,1) → [0.1, 0.7] / 6.0 |
| (−1, −1) | −1 | −0.1−0.7+6 = 5.2 | 1 | ✗ | w += (−2)·(−1,−1) → [2.1, 2.7] / 4.0 |
三遍错误一模一样:该判 −1 的全被判成了 1——因为偏置 10 太大,输出整体偏向「是」。
每错一次,误差 e = 标签 − 预测 = −1 − 1 = −2,更新规则 w ← w + e·x, b ← b + e
把权重往「压低加权和」的方向推一格。
日志:Iteration: 0, Error: 3, Weights: [2.1, 2.7], Bias: 470。对上了。
第 1 遍:错误搬家,权重来回翻¶
从 w=[2.1,2.7], b=4 出发,四个例子挨个过:
| 例子 (x₁, x₂) | 标签 | 加权和 | 预测 | 对错 | 更新后 w / b |
|---|---|---|---|---|---|
| (1, 1) | 1 | 2.1+2.7+4 = 8.8 | 1 | ✓ | 不变 / 4.0 |
| (1, −1) | −1 | 2.1−2.7+4 = 3.4 | 1 | ✗ | w += (−2)·(1,−1) → [0.1, 4.7] / 2.0 |
| (−1, 1) | −1 | −0.1+4.7+2 = 6.6 | 1 | ✗ | w += (−2)·(−1,1) → [2.1, 2.7] / 0.0 |
| (−1, −1) | −1 | −2.1−2.7+0 = −4.8 | −1 | ✓ | 不变 / 0.0 |
错 2 次,最后停在 w=[2.1, 2.7], b=0——日志 Error: 2, Weights: [2.1, 2.7], Bias: 070。
这一遍值得盯的是中间状态:更新把权重推出去、下一个错误又把它拉回来,
w 在这一遍里来回翻了一次——拔河的雏形,单个点的改善会打乱别的点。
第 2 遍:斜率动了¶
b=0 之后,两个「恰好相反」的例子都判对了,只剩 (−1, 1):
| 例子 (x₁, x₂) | 标签 | 加权和 | 预测 | 对错 | 更新后 w / b |
|---|---|---|---|---|---|
| (1, 1) | 1 | 2.1+2.7+0 = 4.8 | 1 | ✓ | 不变 / 0.0 |
| (1, −1) | −1 | 2.1−2.7 = −0.6 | −1 | ✓ | 不变 / 0.0 |
| (−1, 1) | −1 | −2.1+2.7 = 0.6 | 1 | ✗ | w += (−2)·(−1,1) → [4.1, 0.7] / −2.0 |
| (−1, −1) | −1 | −4.1−0.7−2 = −6.8 | −1 | ✓ | 不变 / −2.0 |
错 1 次,w 从 [2.1, 2.7] 变成 [4.1, 0.7]、b=−2——日志 Error: 170。
一个权重往下掉(2.7→0.7)、一个往上走(2.1→4.1)——线的斜率动了。
这就是原书说的:权重的变化对应边界斜率的变化72。
第 3 遍:最后一错;第 4 遍:干净通过¶
第 3 遍从 w=[4.1,0.7], b=−2 出发:
| 例子 (x₁, x₂) | 标签 | 加权和 | 预测 | 对错 | 更新后 w / b |
|---|---|---|---|---|---|
| (1, 1) | 1 | 4.1+0.7−2 = 2.8 | 1 | ✓ | 不变 / −2.0 |
| (1, −1) | −1 | 4.1−0.7−2 = 1.4 | 1 | ✗ | w += (−2)·(1,−1) → [2.1, 2.7] / −4.0 |
| (−1, 1) | −1 | −2.1+2.7−4 = −3.4 | −1 | ✓ | 不变 / −4.0 |
| (−1, −1) | −1 | −2.1−2.7−4 = −8.8 | −1 | ✓ | 不变 / −4.0 |
错 1 次((1,−1) 加权和 1.4 > 0 被判成 1),更新把 w 翻回 [2.1, 2.7]、b=−470。
第 4 遍:四个例子全对,一遍下来一次更新都没发生——Error: 0。
日志打出的却是 Converged after 5 iterations(收敛于第 5 次),不是 470。
为什么?因为它没法提前知道已经对了——必须完整走过一整遍、
确认一遍里一个错都没犯,才敢宣布收敛71。这个「干净的一遍」是收敛的判定标准。
更新规则的符号直觉:三种错法三种修法¶
w ← w + e·x, b ← b + e 这两行,对四种「错的情形」各意味着什么(数为演示编的):
例:某点 x = (2, 1),标签 y = 1(该判正),现在判负,e = 2。
w₁ ← w₁ + 2×2 = +4 与 x₁ 同号且放大 → 抬高这项对加权和的贡献
w₂ ← w₂ + 2×1 = +2 同理
b ← b + 2 整体门槛向「更容易判正」移
效果:这个点(和同类点)的加权和整体抬高,朝「判正」那边推。
若同一点标签是 −1(该判负),e = −2:三个更新全部反向,
把这个方向对加权和的贡献往下压。
一句话:更新方向 = 「让这类点下次更容易被判对」的方向, 步长正比于输入本身的大小(所以第 8 章会说:离原点远的点被推得狠)28。 「特征和误差同号权重就增大、异号就减小」——原书这句正文, 说的就是这个表的压缩版29。
把五步摆在一起看¶
遍数 犯错数 权重 偏置 这条线在干什么
0 3 [2.1, 2.7] 4 全偏向「是」,被三个「否」往回推
1 2 [2.1, 2.7] 0 平移(偏置从 4 到 0,斜率没变)
2 1 [4.1, 0.7] -2 斜率动了,转了个角度
3 1 [2.1, 2.7] -4 再修一次,位置基本到位
4 0 [2.1, 2.7] -4 干净的一遍 → 宣布收敛
三句话收拢这一节:
- 更新永远只在犯错时发生,更新量 = 误差 × 输入;
- 偏置管线的平移,权重管线的角度——日志和图里两者交替变化不是巧合, 原书专门点破:权重的变化对应边界斜率的变化72;
- 「收敛」不是「对了」,而是「完整走过一遍都没错」。
同一个算法,换个数据集¶
第 1 节说的现象现在可以解释一半了:OR 的测试把数据换成 OR 的表,
同样的循环摸出另一组 w 和 b,那组数恰好编码了 OR 的行为73。
算法不变,变的是数据;函数不写在代码里,写在学出来的数里。
第二个测试的诞生:OR 学会了,AND 跟上¶
测试驱动开发的循环在感知机身上又跑了一圈。 先写「验收标准」——创建一个分类器、训练、断言四个输入各自的输出30:
let mut classifier = PerceptronClassifier::new(100);
classifier.train(input, labels);
assert_eq!(classifier.classify(vec![-1.0, -1.0]), -1.0);
assert_eq!(classifier.classify(vec![ 1.0, -1.0]), 1.0);
assert_eq!(classifier.classify(vec![-1.0, 1.0]), 1.0);
assert_eq!(classifier.classify(vec![ 1.0, 1.0]), 1.0);
new(100) 里的 100 是迭代遍数——超参数,人定的。
OR 的测试转绿之后,作者顺手加了一道练习题:给 AND 也写一个测试31。
AND 的表和 OR 只差中间两行(AND 在「一真一假」时输出假,OR 输出真),
测试代码结构一模一样,数据一换就行。
结果:AND 测试一次通过,实现一行没改。73
这一节反复出现的奇迹,至此可以拆开解释了:
「学出来的行为住在权重和偏置里」——这句话从口号变成了你能指认的事实:
OR 训练完 w=[2.1, 2.7], b=−4 这样的数;AND 训练完是另一组数。
代码是机器,参数是灵魂。
8. 为什么这套更新一定有用:决策边界与收敛定理¶
现象走完了,该回答「凭什么」。这一节是本章的承重墙,原书用了三层论证,我们照它的顺序接住。
第一层:把数据画出来,答案就在眼前¶
把每个例子画在平面坐标系上:横轴 A,纵轴 B,真值的点画圈,假值的点画三角 (这是原书图 1-2 的画法)34。AND 的图:三个圈挤在右上角,一个三角在左下角。 画一条线把圈和三角分开——分类完成。 这条线在机器学习里叫决策边界(decision boundary):模型从「预测这个」换成「预测那个」的分界面74。 分类问题的本质,就是找到那条线的位置和角度。
第二层:权重和偏置,就是那条线的参数¶
原书给了推导,照抄过来。二维时,决策边界就是加权和等于 0 的地方:
这是一条直线的标准样子:−w₁/w₂ 是斜率,−b/w₂ 是截距(线与纵轴的交点)75。
这一层把第 6 节的日志翻译成了几何:
- 第 0→1 遍,权重
[2.1, 2.7]没变、偏置从 4 变 0 → 斜率不变、线整体平移; - 第 2 遍,权重变成
[4.1, 0.7]→ 斜率 −4.1/0.7 变成 −2.1/2.7,线转了个角度72。
「学习」就是这条线在平面里一边转一边挪,直到站对位置。 原书给这个过程起了个名字: 这已经是优化的雏形——搜索能消灭分类错误的参数76(第 7 章会展开成真正的优化理论)。
把日志翻译成线:斜率和截距的数值对账¶
第 6 节的五行日志,用上面的公式 x₂ = −(w₁/w₂)·x₁ − b/w₂ 逐遍换算,
就得到那条线每一遍的「证件照」(斜率、截距由书中日志的 w、b 算出):
| 遍数 | w | b | 斜率 −w₁/w₂ | 截距 −b/w₂ | 线的动作 |
|---|---|---|---|---|---|
| 0 | [2.1, 2.7] | 4 | −0.78 | −1.48 | 斜率到位,位置偏高 |
| 1 | [2.1, 2.7] | 0 | −0.78 | 0 | 斜率不动,平移穿过原点 |
| 2 | [4.1, 0.7] | −2 | −5.86 | 2.86 | 陡转一个角度 |
| 3 | [2.1, 2.7] | −4 | −0.78 | 1.48 | 转回,平移到最终位置 |
| 4 | [2.1, 2.7] | −4 | −0.78 | 1.48 | 不动(干净一遍) |
对照书里的图(图 1-4),每一遍的线确实如此:第 0→1 遍平移、第 2 遍陡转、第 3 遍归位32。 原书还专门点出一个细节:第 1 到第 2 遍之间、第 2 到第 3 遍之间,权重变了—— 图里斜率发生变化的,恰好就是这两步;其余步骤只有截距动33。 「权重决定边界的朝向」从一句陈述变成了可以逐遍对账的事实。
第三层:每次更新,都把这个点往对的一边推¶
为什么「挪一格」是朝对的方向挪?原书的证明这样走(记号照原书):
设某个点 x 的标签是 y(只能是 1 或 −1),现在被判错了—— 也就是说 y 和加权和 wᵀx + b 符号相反,它们的乘积 y(wᵀx + b) ≤ 077。
代码里的更新是 w ← w + e·x, b ← b + e,其中误差 e = y − ŷ。
对判错的点:ŷ 和 y 相反,e = y − Ŷ = 2y(两个相反数的差是 ±2)。
所以代码实际做的是 w ← w + 2y·x, b ← b + 2y——
比教材里的标准形式 w ← w + y·x, b ← b + y 大 2 倍78。
乘个常数无所谓:每次更新都乘同一个 2,找到的分界线不变78。
看更新之后,同一个点的新加权和变成多少(把新 w、新 b 代回去展开):
新增的项 y(xᵀx + 1):xᵀx + 1 恒为正(平方和加 1),
所以新项的符号和 y 一致——加权和朝 y 那一边移动了。
离原点越远的点 xᵀx 越大,推得越狠79。
更精确的说法:因为 y² = 1,更新后 y(wᵀx + b) 恰好增加了 xᵀx + 1—— 一个恒正的数80。而这个量 y(wᵀx+b) 衡量的是「这个点被判对的把握」: 它越正,点离自己的一边越远。所以:
每次犯错,算法都把这个点的把握抬高了一个确定的正数。
几何上,你可以想象成把分界线朝那个分错的点推近一格; 点对 y 同号就把它的地盘扩一点,异号就压回去81。
零点归哪边:一个任意的选择¶
阶跃函数有一个边界情形书里专门交代了:加权和恰好等于 0 怎么办? 规则必须二选一,书里选了 −1(判负)——理由是这纯粹是任意选择: 加权为零的点正好骑在决策边界上,而训练好的模型几乎不会恰好停在那里35。
这个「任意但要说清」的细节,是本书教法的又一例: 规范里每一处模糊的角落,都值得花一句话钉死,不然读者会自己脑补出各种规则。
决策边界在高维长什么样¶
本章的所有图都是二维:两个特征,一条直线。但如果特征是三个呢?一百个呢? 原书一句话带过、后面天天要用的推广是:边界这个词在更高维依然成立—— 三维时它是一张平面,d 维时它是一个 d−1 维的超平面36。 「超」没有玄学,就是「比所在空间少一维」的统称:直线之于平面是超平面, 平面之于三维空间也是。
「特征空间」(feature space)这个词也顺手认识:把所有特征当坐标轴 搭出来的空间,每个例子是其中的一个点37。本章的平面上猫狗各据一方, 就是「两个特征、二维特征空间」的样子;第 10 章的图像分类, 每个像素是一个特征,特征空间有几万维——那里没有图可画, 但「找分界」这件事的结构一模一样。
第三层论证还藏着一个对下文的预告。把「点离边界多近」这个概念单独拎出来, 它有个名字——间隔(margin):点就是站在边界两边、按 y(wᵀx+b) 量出来的距离。 感知机的更新只关心「站错边没有」,完全不关心「站得多开」; 把「站得开」变成优化目标本身,正是下一章支持向量分类器的出发点38。 可以说,下一章的全部内容,都是从「拔河只分对错、不分远近」这个缺口长出来的。
收敛定理:这场拔河一定结束¶
最后一块拼图:更新会改善触发它的那个点,但可能恶化别的点—— 所以这不是「每次全体变好」的单调故事,而是一场点与点之间的拔河81。
感知机收敛定理说的就是这个拔河的结局: 只要数据能被一条直线分开(线性可分),这场拔河保证在有限次更新内结束, 算法一定能找到一条分界线81。
这句话的边界和本组 03 章直接相关:数据不能被直线分开时,定理不适用, 拔河永远打不完——感知机会在几个错误之间震荡,永远停不下来。 XOR 就是最小的反例(那里详讲)。
9. 感知机的天花板:章末自己交代的两个伏笔¶
第 1 章在结尾自己划了边界,留给下一章:
-
它学不会非线性可分的数据。 章末明说:数据不能被直线分开时,感知机失败; 「非线性」——数据和答案之间的关系画不成直线——说的就是这个82。
-
它的解「能用」但未必「好用」。 这一条章末没展开,但日志里已经埋了线索: 第 4 遍之后
w=[2.1, 2.7], b=−4,AND 的训练数据其实还能被无数条线分开, 感知机找到哪条算哪条——对新数据的泛化——学来的本领用在没见过的新数据上——好坏, 它一概不管。本组 03 章的全部内容,就是从这条线索长出来的。
Rosenblatt 当年的豪情(「第一台能有原创想法的机器」)和他身后的历史, 原书用一句话收束:正是这些局限,推着后世的科学家走向更强的技术, 最终通向现代机器学习和今天的 AI82。
下一章的两个预告(第 1 章自己埋的)¶
本章结尾,原书预告了第 2 章的两件事39:
- 把感知机用到「没见过的数据」上。 AND/OR 是数据即函数的特例; 下一章换真实样本,看感知机的分类能不能推广到训练集之外;
- 亲眼看它的两个局限发作。 不可分数据上的失败、以及「勉强可分」时的挣扎, 都会实跑出来。
这两个预告都兑现了:本组 03 章的前两节,就是它们的完整展开。
10. 作者的判断与证据¶
书里有明确证据的:
- 五步训练日志与更新规则逐点吻合(我们上面每步验算过)—— 「偏置平移、权重转角度」「干净一遍才算收敛」这些说法,书里都有原文对应707172;
- 收敛定理的增量论证
y(wᵀx+b)增加xᵀx+1,书里有完整推导80; - 「严格来说我们算的不是正式损失函数,只是误差项」——作者自己声明83, 正式的、光滑的损失函数第 2 章才登场(那就是 hinge loss)。
- 全零初始化的安全性有前提:只对感知机成立,作者明确把「权重初始化是大题目、 第 4 章专门对待」写进了正文67——没有把特例当成普适规律,这种分寸感值得学。
作者没展开、我们替读者标出来的:
- 为什么用 −1/1 而不是 0/1 编码标签?书里没有明说。我们的补充(不在书里,来自通用知识): 0/1 会让「更新方向」的符号分析变别扭(误差 e 只有 0/1 两种,无法区分「错在多判了」还是「错在少判了」), −1/1 下 e = ±2 天然带方向。这个选择是后续一切推导的前提,读者最好心里有数。
- 感知机「在哪个例子顺序下先收敛到哪个解」受数据顺序影响——书没提; 第 2 章的「打乱数据」会回来补这一课。
判断(我们的,不是书里的): 第 1 章的教学设计里,最值钱的是那个 「故意不良初值」的安排——从零出发两趟就收敛,什么都看不见; 从
[0.1, 0.7], b=10出发,五趟里平移和旋转交替出现,决策边界的每个动作 都被迫显形。讲机制时,「让错误慢慢发生」比「演示正确答案」有效得多, 这个手法值得任何写教程的人抄走。 如果错,会错在: 如果学习者是「只想跑通」型,这种刻意减速的安排会显得拖沓—— 但对以「读懂机制」为目标的读者,它恰是最短路径。这本书的目标读者是后者。判断(我们的,不是书里的): 第 1 章的教学顺序(直觉 → 数据 → 实现 → 理论) 是「机制类课程」的优等生排法:每一步都踩在前一步的看得见成果上, 理论最后登场时,读者手里已经有了一批亲手跑出来的现象可以对账。 反面教材是「先三个月数学再写代码」的传统排法——理论先行对自学者是劝退率最高的安排。 如果错,会错在: 这个顺序对「目标是从零实现」的读者最优; 如果读者的目标是「先建立理论框架再动手」,理论殿后可能显得拖沓—— 但这本书的读者画像(写代码的人)恰好站在前一阵营。
教材用词之辨:它叫 loss,其实还不是 loss¶
一个容易被滑过去的较真:代码里那个函数名叫 loss,但原书主动承认——
它算的严格说是误差项(error),不是正式的损失函数:
真正的损失函数是「预测和目标之间光滑的差异度量」,感知机这一步只用
「错了没有、错在哪边」40。
为什么这个较真值得写进拆解?因为「损失函数」是后面每一章的中心物件, 第 2 章 hinge loss 一出场就是真损失——它是连续的、有梯度的、能衡量「差多少」的。 回头看感知机的「误差项」,它既不连续(对了是 0,错了跳到 ±2), 也没有「差多少」的细腻度。两步之差,恰好是「拍脑袋更新」和「梯度下降更新」的分界线。
记这个演化的轨迹:
11. 边界与局限¶
- 只对线性可分数据有效。 收敛定理的前提写死在定理里; XOR 这类数据上它永远震荡,这不是 bug,是模型类的表达力上限82。
- 找到哪个解有随机性。 解不唯一,感知机对「要哪个」没有偏好—— 泛化能力参差不齐,这是第 2 章「最大间隔」要修的第一个缺陷。
- 阶跃函数太硬。 它(大于 0 出 1,否则出 −1)不可微分、不光滑, 没法用「往下降最快的方向挪」这种高级优化——这逼着第 2 章换上更光滑的 loss, 也是后面神经网络要换激活函数——加权和之后那道把结果压弯的变换——的伏笔。
- 教学代码不是生产代码。 书里的
unwrap()(解析失败就 panic)对受控的教学数据可以接受, 作者自己注明生产环境要改进61。照抄到真实项目前记得换掉。 - Early release 痕迹。 章首照例申明「这是未编辑稿、配书仓库稍后开放」84; 引用时锚定的是 2026-07-23 这一版。
- 迭代遍数 100 的来历没有交代。
new(100)里的 100 是「够用」的经验值: AND/OR 五步就收敛,100 只是保险上限。书里没讨论「遍数设多少」的方法论, 这个空白要等未放出的第 3 章(验证与测试)来填。 - 例子的规模极小(每类 4 个例子)。 这是教学上的清醒选择——小到能手算对账, 但也意味着本章所有「它学会了」的结论都不涉及统计意义上的泛化: 四行数据的 AND 与四万张图的猫狗,之间隔着整本书还没走的路程。
- 顺序敏感没有给出衡量。 本章的例子顺序固定且有利(错误均匀分布在前几行); 换个顺序,收敛遍数会变——书里没演示,只把它留给第 2 章「打乱」那一课。
12. 可带走的(合上这章,你应该带走了)¶
- 感知机 = 一行公式
y = σ(wᵀx + b)+ 训练三步:前向(算加权和过阶跃函数)、 误差(e = y − ŷ)、更新(w ← w + e·x,b ← b + e,只在犯错时)。 - −1/1 编码标签;权重学重要性、偏置定门槛;阶跃函数 σ 负责把连续值切成两类。
- 决策边界 = wᵀx + b = 0;偏置管平移、权重管角度——看训练日志就能读出线的动作。
- 收敛 = 完整走过一遍数据零更新;收敛定理保证线性可分时有限步内结束。
- 更新的方向是对的:每次犯错,该点的 y(wᵀx+b) 增加 xᵀx + 1,把握抬高一个正数; 代价是可能拉低别的点——所以是拔河,不是单调变好。
- 代码里学到的数(权重、偏置)和人定的数(迭代次数)是两类:后者叫超参数。
- Rust 侧:
trait定义行为契约(train/classify),struct装学到的数,mut/&/&mut把「改不改、谁负责」写进类型;Result让失败无法被忽略。 - 测试驱动开发的红→绿循环:先写验收标准,再写实现。
- 感知机的两个天花板:非线性数据学不会;会学的解不挑好坏。下一章全是为了修它们。
- 报错是教练不是对手:Rust 的每个编译错误都是一次免费检查, 把红字读完再动手改,是这门语言唯一正确的节奏。
13. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 感知机的历史(1957/1958,Rosenblatt) | Chapter 1. The First Neuron | text/05-ch01-chapter-1-the-first-neuron.txt:15(搜「1957」) |
| Helen Keller 引语 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:12(搜「great and noble task」) |
| 测试数据串的续行写法 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:185(搜「x[3], [-1.0, -1.0, -1.0]」) |
| cargo test 输出的三段 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:310(搜「test result: ok」) |
| 更新规则的符号说明 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:545(搜「same sign」) |
| OR 测试的相似模式 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:612(搜「similar pattern for the OR test」) |
| 本章聚焦的两个思想 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:17(搜「linear classifiers and learning via parameter updates」) |
| AND/OR 定义 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:25(搜「returns True if both」) |
| Rust 命名约定 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:53(搜「snake case」) |
| 最后一个表达式即返回值 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:57(搜「last expression」) |
| 变量默认不可变 / mut | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:63(搜「immutable」) |
| shadowing | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:73(搜「shadowing」) |
| 宏调用 println! | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:82(搜「macro calls」) |
| 「能不能从数据学」之问 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:88(搜「without having to specify that behavior ourselves」) |
| 特征 / 标签 / 例子 / 类别 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:154(搜「complete example or instance」) |
| 测试驱动开发 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:158(搜「Test-Driven Development」) |
| −1.0 / 1.0 编码真与假 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:202(搜「represent the values False and True」) |
| 行 = 例子的约定 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:208(搜「represents a row in the matrix」) |
| f32 的选择 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:206(搜「32 bits」) |
| 编译错误:函数不存在 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:225(搜「cannot find function」) |
| todo!() 与红→绿 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:269(搜「not yet implemented」) |
| 迭代器与链式变换 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:286(搜「two main types of methods」) |
| Result 与 unwrap / panic | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:290(搜「Result<T,E>」);:292(搜「called a panic」) |
| 感知机是线性二分类器 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:319(搜「linear binary classifier」) |
| 权重与偏置的角色 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:323(搜「These are the weights」);:327(搜「step function」) |
| 「学习 = 找到对的权重和偏置」 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:333(搜「getting to the values of weights and biases」) |
| Classifier trait 的设计意图 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:337(搜「the same two behaviors」) |
| 超参数概念(iterations) | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:430(搜「play a crucial role」) |
| 零初始化对感知机无害 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:434(搜「set everything to zero」) |
| 训练三步 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:513(搜「composed of three steps」) |
| 前向的定义 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:519(搜「if the weighted sum is bigger than zero」) |
| 零点判负的选择 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:529(搜「At exactly zero」) |
| 误差项 e = y − ŷ | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:533(搜「always produces −1.0」) |
| 不是正式损失函数 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:539(搜「formal loss function」) |
| 更新规则的方向性 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:545(搜「same sign」) |
| 同一个算法学会 AND | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:578(搜「The SAME algorithm」) |
| 决策边界的定义 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:590(搜「separates different classes」) |
| 故意的不良初值 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:598(搜「0.1, 0.7」) |
| 训练日志(五个迭代) | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:622(搜「Iteration: 0, Error: 3」) |
| 属性、#[cfg(test)] 与 mod tests | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:190(搜「mod tests」);:192(搜「metadata annotations」) |
| 类型全家福 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:200(搜「four primary scalar types」) |
| 迭代器的两类方法 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:286(搜「two main types of methods」) |
| forward 的逐行实现 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:467(搜「fn forward」) |
| 更新函数的实现 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:488(搜「fn update」) |
| Classifier trait 的签名 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:341(搜「pub trait Classifier」) |
| PerceptronClassifier 结构体 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:410(搜「pub struct PerceptronClassifier」) |
| 本章的四个小节路线 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:19(搜「step by step approach」) |
| usize 类型 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:413(搜「iterations: usize」) |
| 训练数据非空的防御 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:443(搜「Training data cannot be empty」) |
| zip 对齐输入与标签 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:449(搜「zip(&x_train, &y_train)」) |
| 干净一遍才算收敛 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:631(搜「completes one full pass」) |
| Result 的显式实验 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:296(搜「let number_string」) |
| split_columns 与元组 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:389(搜「split_columns」) |
| 斜率与截距的公式 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:641(搜「defines the slope」) |
| 权重变化对应斜率变化 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:629(搜「determine the orientation」) |
| 已经是优化的雏形 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:645(搜「already a form of optimization」) |
| 判错点的符号分析 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:647(搜「sign disagrees」) |
| 代码更新是标准形式的 2 倍 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:655(搜「scaled by a factor of two」) |
| 更新后加权和的展开式 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:653(搜「moves the weight vector」);:661(搜「increases by exactly」) |
| 拔河与收敛定理 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:663(搜「perceptron convergence theorem」) |
| 「原创想法的机器」与局限伏笔 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:671(搜「original idea」);:673(搜「important limitations」) |
| early release 声明 | 同上 | text/05-ch01-chapter-1-the-first-neuron.txt:8(搜「GitHub repo will be made active later on」) |
-
出处:「Chapter 1. The First Neuron」第 12 段(
text/05-ch01-chapter-1-the-first-neuron.txt:12,搜「great and noble task」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 15 段(
text/05-ch01-chapter-1-the-first-neuron.txt:15,搜「1957」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 17 段(
text/05-ch01-chapter-1-the-first-neuron.txt:17,搜「linear classifiers and learning via parameter updates」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 19 段(
text/05-ch01-chapter-1-the-first-neuron.txt:19,搜「step by step approach」)。 ↩ -
出处:「Chapter 2. The Search for Structure」第 17 段(
text/06-ch02-chapter-2-the-search-for-structure.txt:17,搜「data and function coincide」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 51 段(
text/05-ch01-chapter-1-the-first-neuron.txt:51,搜「compiled to a machine language」)。原文:「These files are then compiled to a machine language and can be executed by the machine.」 ↩ -
出处:「Chapter 1. The First Neuron」第 214 段(
text/05-ch01-chapter-1-the-first-neuron.txt:214,搜「cargo init」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 275 段(
text/05-ch01-chapter-1-the-first-neuron.txt:275,搜「fn load_data_from_csv」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 190 段(
text/05-ch01-chapter-1-the-first-neuron.txt:190,搜「mod tests」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 192 段(
text/05-ch01-chapter-1-the-first-neuron.txt:192,搜「metadata annotations」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 185 段(
text/05-ch01-chapter-1-the-first-neuron.txt:185,搜「x[3], [-1.0, -1.0, -1.0]」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 379 段(
text/05-ch01-chapter-1-the-first-neuron.txt:379,搜「explicitly in front of us」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 310 段(
text/05-ch01-chapter-1-the-first-neuron.txt:310,搜「test result: ok」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 315 段(
text/05-ch01-chapter-1-the-first-neuron.txt:315,搜「won’t be pasting full test outputs」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 200 段(
text/05-ch01-chapter-1-the-first-neuron.txt:200,搜「four primary scalar types」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 202 段(
text/05-ch01-chapter-1-the-first-neuron.txt:202,搜「trim() and parse()」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 200 段(
text/05-ch01-chapter-1-the-first-neuron.txt:200,搜「HashMaps」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 202 段(
text/05-ch01-chapter-1-the-first-neuron.txt:202,搜「trim() and parse()」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 225 段(
text/05-ch01-chapter-1-the-first-neuron.txt:225,搜「cannot find function」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 269 段(
text/05-ch01-chapter-1-the-first-neuron.txt:269,搜「not yet implemented」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 341 段(
text/05-ch01-chapter-1-the-first-neuron.txt:341,搜「pub trait Classifier」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 204 段(
text/05-ch01-chapter-1-the-first-neuron.txt:204,搜「generics notation」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 517 段(
text/05-ch01-chapter-1-the-first-neuron.txt:517,搜「going from left to right」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 533 段(
text/05-ch01-chapter-1-the-first-neuron.txt:533,搜「regardless of the input」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 492 段(
text/05-ch01-chapter-1-the-first-neuron.txt:492,搜「self.weights[feat] += error * input[feat]」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 612 段(
text/05-ch01-chapter-1-the-first-neuron.txt:612,搜「similar pattern for the OR test」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 533 段(
text/05-ch01-chapter-1-the-first-neuron.txt:533,搜「always produces −1.0」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 661 段(
text/05-ch01-chapter-1-the-first-neuron.txt:661,搜「moves further for points far from the origin」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 545 段(
text/05-ch01-chapter-1-the-first-neuron.txt:545,搜「same sign」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 359 段(
text/05-ch01-chapter-1-the-first-neuron.txt:359,搜「it_classifies_or_inputs」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 558 段(
text/05-ch01-chapter-1-the-first-neuron.txt:558,搜「it_classifies_and_inputs」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 612 段(
text/05-ch01-chapter-1-the-first-neuron.txt:612,搜「decision boundary evolves」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 616–618 段(
text/05-ch01-chapter-1-the-first-neuron.txt:616,搜「the slope of the lines remains unchanged」)与第 629 段(搜「correspondence is not accidental」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 588 段(
text/05-ch01-chapter-1-the-first-neuron.txt:588,搜「Circles correspond to True and triangles to False」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 529 段(
text/05-ch01-chapter-1-the-first-neuron.txt:529,搜「At exactly zero」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 590 段(
text/05-ch01-chapter-1-the-first-neuron.txt:590,搜「surface」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 590 段(
text/05-ch01-chapter-1-the-first-neuron.txt:590,搜「feature space」)。 ↩ -
出处:「Chapter 2. The Search for Structure」第 31 段(
text/06-ch02-chapter-2-the-search-for-structure.txt:31,搜「distance between a decision boundary and the closest data points」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 671 段(
text/05-ch01-chapter-1-the-first-neuron.txt:671,搜「never seen before」)与第 673 段(搜「slightly violated」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 539 段(
text/05-ch01-chapter-1-the-first-neuron.txt:539,搜「smooth measures of discrepancy」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 578 段(
text/05-ch01-chapter-1-the-first-neuron.txt:578,搜「The SAME algorithm」)与第 671 段(搜「original idea」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 333 段(
text/05-ch01-chapter-1-the-first-neuron.txt:333,搜「getting to the values of weights and biases」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 25 段(
text/05-ch01-chapter-1-the-first-neuron.txt:25,搜「returns True if both」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 59 段(
text/05-ch01-chapter-1-the-first-neuron.txt:59,搜「The boolean operator for AND is &&」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 53 段(
text/05-ch01-chapter-1-the-first-neuron.txt:53,搜「snake case」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 57 段(
text/05-ch01-chapter-1-the-first-neuron.txt:57,搜「last expression」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 63 段(
text/05-ch01-chapter-1-the-first-neuron.txt:63,搜「variables in Rust are immutable」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 73 段(
text/05-ch01-chapter-1-the-first-neuron.txt:73,搜「shadowing」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 82 段(
text/05-ch01-chapter-1-the-first-neuron.txt:82,搜「macro calls」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 44 段(
text/05-ch01-chapter-1-the-first-neuron.txt:44,搜「rustc main.rs」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 88 段(
text/05-ch01-chapter-1-the-first-neuron.txt:88,搜「without having to specify that behavior ourselves」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 154 段(
text/05-ch01-chapter-1-the-first-neuron.txt:154,搜「complete example or instance」)与第 156 段(搜「Each output value defines a class」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 202 段(
text/05-ch01-chapter-1-the-first-neuron.txt:202,搜「represent the values False and True」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 158 段(
text/05-ch01-chapter-1-the-first-neuron.txt:158,搜「Test-Driven Development」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 170 段(
text/05-ch01-chapter-1-the-first-neuron.txt:170,搜「fn it_loads_or_data」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 225 段(
text/05-ch01-chapter-1-the-first-neuron.txt:225,搜「cannot find function」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 257 段(
text/05-ch01-chapter-1-the-first-neuron.txt:257,搜「panicked at main.rs」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 301 段(
text/05-ch01-chapter-1-the-first-neuron.txt:301,搜「cargo test again」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 286 段(
text/05-ch01-chapter-1-the-first-neuron.txt:286,搜「two main types of methods」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 290 段(
text/05-ch01-chapter-1-the-first-neuron.txt:290,搜「Result<T,E>」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 292 段(
text/05-ch01-chapter-1-the-first-neuron.txt:292,搜「called a panic in Rust」)。 ↩↩↩ -
出处:「Chapter 1. The First Neuron」第 208 段(
text/05-ch01-chapter-1-the-first-neuron.txt:208,搜「represents a row in the matrix」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 206 段(
text/05-ch01-chapter-1-the-first-neuron.txt:206,搜「32 bits」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 339 段(
text/05-ch01-chapter-1-the-first-neuron.txt:339,搜「using Traits」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 337 段(
text/05-ch01-chapter-1-the-first-neuron.txt:337,搜「the same two behaviors」)——「后来训练神经网络分类图像时,同样是这两个行为」。 ↩ -
出处:「Chapter 1. The First Neuron」第 413 段(
text/05-ch01-chapter-1-the-first-neuron.txt:413,搜「iterations: usize」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 434 段(
text/05-ch01-chapter-1-the-first-neuron.txt:434,搜「set everything to zero」)。 ↩↩↩ -
出处:「Chapter 1. The First Neuron」第 509 段(
text/05-ch01-chapter-1-the-first-neuron.txt:509,搜「vector of tuples」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 598 段(
text/05-ch01-chapter-1-the-first-neuron.txt:598,搜「0.1, 0.7」)与第 612 段(搜「deliberately bad」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 622 段(
text/05-ch01-chapter-1-the-first-neuron.txt:622,搜「Iteration: 0, Error: 3」)。五行日志在第 622–626 段。 ↩↩↩↩↩↩↩ -
出处:「Chapter 1. The First Neuron」第 631 段(
text/05-ch01-chapter-1-the-first-neuron.txt:631,搜「completes one full pass」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 629 段(
text/05-ch01-chapter-1-the-first-neuron.txt:629,搜「determine the orientation of the boundary」)。 ↩↩↩↩ -
出处:「Chapter 1. The First Neuron」第 578 段(
text/05-ch01-chapter-1-the-first-neuron.txt:578,搜「encapsulating the behavior of AND」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 588 段(
text/05-ch01-chapter-1-the-first-neuron.txt:588,搜「drawing a line」)与第 590 段(搜「decision boundary」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 633–641 段(
text/05-ch01-chapter-1-the-first-neuron.txt:633,搜「decision boundary is given by」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 645 段(
text/05-ch01-chapter-1-the-first-neuron.txt:645,搜「already a form of optimization」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 647 段(
text/05-ch01-chapter-1-the-first-neuron.txt:647,搜「sign disagrees with y」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 655 段(
text/05-ch01-chapter-1-the-first-neuron.txt:655,搜「scaled by a factor of two」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 653 段(
text/05-ch01-chapter-1-the-first-neuron.txt:653,搜「moves the weight vector」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 661 段(
text/05-ch01-chapter-1-the-first-neuron.txt:661,搜「increases by exactly」)。 ↩↩ -
出处:「Chapter 1. The First Neuron」第 663 段(
text/05-ch01-chapter-1-the-first-neuron.txt:663,搜「tug-of-war」)。 ↩↩↩ -
出处:「Chapter 1. The First Neuron」第 673 段(
text/05-ch01-chapter-1-the-first-neuron.txt:673,搜「important limitations」)。 ↩↩↩ -
出处:「Chapter 1. The First Neuron」第 539 段(
text/05-ch01-chapter-1-the-first-neuron.txt:539,搜「formal loss function」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 8 段(
text/05-ch01-chapter-1-the-first-neuron.txt:8,搜「GitHub repo will be made active later on」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 296 段(
text/05-ch01-chapter-1-the-first-neuron.txt:296,搜「let number_string」)。原书用三行显式代码展示 parse → Result → unwrap 的完整链路。 ↩ -
出处:「Chapter 1. The First Neuron」第 389 段(
text/05-ch01-chapter-1-the-first-neuron.txt:389,搜「fn split_columns」);元组的说明见第 404 段(搜「collection of values that can have different types」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 385 段(
text/05-ch01-chapter-1-the-first-neuron.txt:385,搜「static method」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 370 段(
text/05-ch01-chapter-1-the-first-neuron.txt:370,搜「PerceptronClassifier::new(100)」)。 ↩ -
出处:「Chapter 1. The First Neuron」第 359 段(
text/05-ch01-chapter-1-the-first-neuron.txt:359,搜「it_classifies_or_inputs」)。 ↩