跳到主要内容

让车跑起来(二):让网络自己吐出一个转角

这一章讲三件事: 第二条路怎么走 —— 一张图进去,一个 −1 到 1 的数出来,中间不用人拼; 一处全书最漂亮的推理 —— 「把图片翻一下」这个到处都在用的做法,在这个任务上会毁掉数据; 以及一条最值钱的经验 —— 训到 400 轮损失更低,实车跑起来反而更差。 它在全书链条里的位置是第 11 级台阶的第二段,也是全书的技术高潮。 这一章的主走查是一张 128×128×3 的车前道路图,逐层走到那个 tanh 输出。 遇到的生词都在当场解释。

1. 顶层全景

第 12 章那条路: 图 → 转灰度 → 蒙版 → HSV 阈值 → 二值图 → 算偏差 → 角速度
↑ 每一步都是人写死的规则,每个数都是人调的

这一章这条路: 图 ──────────▶ 一个卷积网 ──────────▶ 一个 −1 到 1 的数
↑ 中间那些步骤全没了,网络自己学出来一套

图说:**中间那一整段消失了,不是被简化了,是被换掉了。**
换来的代价在第 2 节:得有人真的开着车跑出几千张带转角的图。

这一章的全程:

① 数据从哪来 ── 人遥控车跑,同时记下手上的转角(第 2 节)
│ ⚠ 这个任务**根本不需要人工标注**

② 数据增强 ── 通用的两大类里,**有一类在这里会毁掉数据**(第 3、4 节)


③ 模型怎么改 ── 和分类只差最后一层:接到 **1 个**神经元上(第 5 节)

├── 为什么要自己设计一个小网络(第 6 节)—— 板子扛不动
├── 为什么非要填充不可(第 7 节)—— 边缘像素只被卷过 1 次
└── 四个设计选择各自的理由(第 9 节)


④ 逐层走一遍 ── 128×128×3 一路走到一个数(第 8 节)


⑤ 训练 ── 300 轮 vs 400 轮那个教训(第 10 节)


⑥ 部署 ── 两个文件、一根网线、一个配置项(第 11 节)

2. 这个任务不需要任何人工标注

这是这条路最反直觉的一点,也是它相对第三条路最大的优势。

现象先行:深度学习不是都得先有人标数据吗

通常是的。 第 09 章那个车辆分类,得有人一张张标「这是汽车、这是货车」; 第 14 章那条路更狠,得有人一个像素一个像素地描边

这个任务不用。理由一句话:标签是开车开出来的。

采集流水线,第 05 章已经完整讲过

这里只回指一句: 人拿手柄遥控车沿赛道走 → 车头顶端的摄像头按帧率拍前方道路图 → 同一时刻程序记下手上的转向幅度 → 归一化到 [−1, 1] → 存成「图片名 → 转角」1

人在开车的同时,标注就已经完成了。 没有第二道工序。

这一章真正的新东西:三条现场经验

书在这一节给了三个只有真做过才写得出来的细节。

① 手柄怎么接上去:控制手柄通过 USB 扩展器与 EdgeBoard 板卡相连1。 —— 也就是说,采数据用的就是跑模型那块板子,不是另接一台电脑。

② 采集速度压在 20 左右。 书给了理由: 车速可在 0—100 的范围内自由设置,为了兼顾控制手柄时人的反应速度以及摄像头采集帧率, 速度一般设置在 20 左右1

车开太快会怎样(两头都出问题):

人这一头 ── 反应跟不上,转角是慌乱中打的,和路况对不上
相机这一头 ── 帧率固定,车越快两帧之间挪得越远,
相邻两张图之间会有大段路没被拍到

⇒ **20 是这两个约束夹出来的数,不是随便定的。**
(「两头出问题」这个拆法是我们补的;书给的是结论和两个理由。)

③ 尽量不要出现跳跃性的大幅度转向。 书的理由是: 为了保证最后小车转向控制模型的光滑连续性,采集时要保证小车能够顺滑地连续移动1

为什么(我们补): 模型学的是「这张图 ↔ 这个转角」的对应关系。 你猛地打一把方向,那一帧的图和上一帧几乎一样,标签却差了很多 —— 等于给模型喂了一对自相矛盾的样本。这种样本多了,模型学出来的输出就会一顿一顿的。

④ 最后按 8:2 随机划分训练集与验证集2。 (注意和第 09 章那个项目的 9:1 不一样 —— 书没有解释为什么改了比例。)

中间还有一道格式转换,顺带解释一件事

采集出来的原始数据是键值对形式的:键是图片编号(比如 001.jpg),值是转向角度,存成 JSON1; 然后要转成「图片名 + 转角」的文本格式,理由是「与后续网络训练时所需要的数据格式匹配」3

第 03 章讲过 JSON 是什么(三维及以上的数据统一用键值对来写)。 那句理由本身比这道工序更值钱:格式转换不是为了好看,是为了迁就下游那个数据加载器认得的格式。 —— 这类「无聊但必须做」的一步,在真实项目里占的时间比想象中多得多。

3. 全书最漂亮的一处推理:翻转会毁掉数据

这是本章第一个承重点,也是这本书最值得单独带走的一条。

现象先行:数据不够,通用做法是什么

第 11 章讲 AlexNet 时提过一味药:数据增强 —— 拿现有的图片变着花样造出更多训练样本。

书在这里把它分成两大类4:

具体手段改的是什么
几何变换水平翻转、垂直翻转、平移、缩放像素的位置
颜色变换调滤镜、色调、对比度、亮度、加噪声像素的值

第 04 章讲过这条分界线 —— 几何变换只改位置不改值,滤波与增强只改值不改位置。 那一章埋的账在这里还,而且还得非常漂亮。

书的判断:几何变换在这里不适用

书的原话是:

自动巡航模型需要根据图像中车道的位置、角度等信息来匹配其与小车转向角度之间的关系, 因此,几何变换在此并不适用4

把这句话拆开,拿一个具体例子走一遍:

原样本: 一张右前方有弯道的图 标签 **+0.6**(往右打)

做一次水平翻转(几何变换):
图变了 —— 弯道跑到左前方去了
标签呢?**还是 +0.6**,因为翻转只动图片、不动标签

⇒ 现在这一对数据在说:「看到左边有弯道,请往右打。」
**这是一条错误的样本,而且是被你亲手造出来的。**

图说:注意这不是「增强效果不好」,是**造假数据**。
几何变换在分类任务上没问题(一只猫翻过来还是猫),
**在这个任务上不行,因为标签本身带方向。**

这条推理值得从这本书里单独带走,它的一般形式是:

任何「通用最佳实践」都要先问一句:它和我这个任务的标签冲不冲突。

但书漏了一个办法

判断(我们的,不是书里的):水平翻转其实是能用的 —— 只要把标签也取负号。

图翻过来的时候,把 +0.6 改成 −0.6,这一对数据就重新对得上了 —— 画面里弯道在左边,标签也说往左打。

而且这一手在这个任务上格外划算: 赛道是一个环,人绕着跑一圈, 左转的样本和右转的样本天然不一样多;翻转 + 取负正好把两边配平。

书没有提这个办法。 它的推理没错(翻转会毁数据),但结论下得比必要的更死。

如果错,会错在: 如果车模本身左右不对称(比如摄像头没装正、或者前轮左右响应不一致), 那么翻转出来的图对应的真实转角就不是简单的取负,这一手会引入新的误差。 判据是:摄像头装正之后,同一条弯道左右两个方向跑,记录下来的转角是不是大小相等符号相反。

4. 那就只做颜色变换,而且这一类正对症

书给了现场证据,不是凭空说好

书的理由是从调试里来的:

在实际调试过程中会发现,背景环境的光照条件、现场地图反光程度和印刷色差等因素 会对模型预测结果有非常大的影响4

注意这三样和第 12 章那条路的死穴是同一批东西。 第 12 章那条路对付它们的办法是「重新调阈值」;这条路的办法是 「训练时就把这些变化喂给模型,让它自己学会不在乎」。

这是两条路在同一个问题上的分野,值得记一笔。

五类手段,全部只改像素的值

书用的是五类:加不同滤镜、改变色调、调整对比度、调整亮度、改变饱和度4

工具也交代了: 滤镜用 OpenCV;色调、对比度、亮度、饱和度的变换用 PIL 里的 ImageEnhance 类; 而这些变换实际上是改变图像像素的 RGB 值,所以要先用 numpy 把图片的像素信息转成矩阵形式再算5

第 04 章那句「图像就是一个数组」在这里第三次兑现 —— 调亮度这种听起来很「图形」的操作,落到程序里就是对一堆数做算术。

还有一条容易漏的口径:必须随机地做

书特意点了: 要对采集到的所有图片随机性地进行增强处理, 「为了排除人为因素的干扰并防止出现特征集中的问题」, 以便获得特征随机性更强的数据集6

「特征集中」是什么(我们补的解释): 假设你按顺序处理 —— 前一千张全部调亮、后一千张全部调暗。 那么「亮」这个特征就和「前一千张拍的那段路」绑在一起了。 模型很可能学成「画面偏亮 ⇒ 往左打」,而这纯粹是你的处理顺序造出来的假规律。

5. 回归和分类,只差最后一层

这是本章第二个承重点。

现象先行:前十二章的模型都在做分类

第 09 章那个分三类、第 10 章那个分两类,输出都是一组加起来等于 1 的可能性而这里要的是一个 −1 到 1 的连续数。差别在哪?

书给的答案出奇地小

用卷积神经网络解决回归问题,其基本结构与分类任务是类似的。 不同之处在于:最后一层全连接层仍旧会输出 m 个参数, 但现在不是把这 m 个参数连接到 n 个神经元上,而是连接到一个神经元上; 经过这个神经元的计算,输出的是一个连续值7

分类(第 09、10 章):
… → 全连接 → **n 个神经元**(n = 类别数)→ Softmax
⇒ 输出 n 个加起来等于 1 的可能性

回归(本章):
… → 全连接 → **1 个神经元** → tanh
⇒ 输出 1 个 −1 到 1 的数

图说:整个网络前面那一大段(卷积、池化、拉平)一个字都不用改。
**改的只有最后一层接几个神经元,以及后面跟什么。**

书还给了这两类任务最本质的区分8:

分类回归
解决的是离散型问题连续型问题
输出对应分类数的概率值一个连续值

「离散」和「连续」就地解释: 离散是「答案只能从有限几个里挑一个」(汽车 / 摩托车 / 货车,中间没有第四种); 连续是「答案可以是某个范围里的任何一个数」(转角可以是 0.5、也可以是 0.5001)。

6. 为什么不直接用 AlexNet 或 ResNet

书给的理由,是第 01 章那条主线的第一次真正兑现

由于巡航模型需要部署在小车搭载的 EdgeBoard 板上运行, 这种边缘计算开发板算力有限,因此要求网络模型规模较小9

第 01 章说过:板子的算力约束反过来决定后面每一处模型设计。 这里是它第一次落到具体的数上。

于是设计成这样

书的选择9:

决定具体理由
借鉴 AlexNet 的思路5 个卷积层;第 1、2、5 层后跟池化,第 3、4 层不跟结构成熟
输入压到 128×128×3远小于 AlexNet 的输入尺寸板子扛不动更大的
卷积核数量和通道数都大幅减少32 / 32 / 64 / 64 / 128图像场景较为单一,特征较少

「图像场景较为单一」这个理由值得留意 —— 它说的是: AlexNet 要认一千个类别的世间万物,而这辆车这辈子只看同一块赛道布。 任务窄,模型就可以窄。

判断(我们的,不是书里的):书说「参数量仅约为其 1/4」,这句话指的是输入图的大小, 不是模型参数量。

理由分两半。先说「不是模型参数量」: 书自己在同一段说卷积核数量和通道数都 「大大减少」,而按它给的那五个通道数算,五个卷积层合计只有约 14 万个参数(见第 8 节)。 AlexNet 是 6000 万 —— 那是四百分之一,不是四分之一。所以这句话说的不可能是模型参数。

再说「是输入图的大小」,而且只有一种量法对得上:

拿什么和 128×128 比算出来是多少对不对得上「1/4」
AlexNet 送进网络的那一块 224×224128² ÷ 224² ≈ 1/3
AlexNet 的原始输入 256×256128² ÷ 256² = 正好 1/4

这两个数不是矛盾,是两道工序: AlexNet 先把图统一缩成 256×256, 再从里面随机裁一块 224×224 送进网络 —— 也就是第 11 章讲的「随机剪裁」那一手。 书那句 1/4,对得上的只有前一个数。(AlexNet 这两个尺寸书里都没给。 补充:不在书里,来自通用知识。)

如果错,会错在: 如果作者心里比的是别的东西(比如某本教材里写成 227×227 的那个版本, 128² ÷ 227² ≈ 0.32,同样对不上 1/4),那这条判断就落空了。 判据是:能让「1/4」恰好成立的量只有 256×256 这一个 —— 换任何一个都得写成「约」。

7. 为什么非要填充不可

现象先行:填充听起来像是凑数用的

第 10 章讲过填充是什么:先在图四周补几圈 0 再开始卷。 听起来它的用处就是「让输出尺寸好看一点」。

书在这里给了一个真正的理由,而且是可以自己数出来的。

书给的账:边缘 1 次,中心 9 次

在卷积核大小为 3×3 的卷积运算中,在图像的边缘,部分像素最少仅能经过 1 次卷积计算, 但是图像中心区域像素却能经过多达 9 次的卷积运算,由此就会带来边缘部分像素信息提取不够充分的问题10

为什么是 9 和 1(书没有拆开,我们补):

3×3 的核在图上滑(步长 1,不填充)。
问:一个像素总共会被多少个不同的窗口盖到?

中心的像素:
核的中心可以落在它的正上、正下、左、右、四个斜角、以及它自己 —— **9 个位置**
⇒ 它参与了 9 次运算

最角上那个像素:
核的中心只有落在它斜内侧那一个位置时才盖得到它 —— **1 个位置**
⇒ 它只参与了 1 次运算

┌───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 2 │ 1 │ 表格里的数 = 这个像素被卷到几次
├───┼───┼───┼───┼───┤
│ 2 │ 4 │ 6 │ 4 │ 2 │
├───┼───┼───┼───┼───┤
│ 3 │ 6 │ **9** │ 6 │ 3 │
├───┼───┼───┼───┼───┤
│ 2 │ 4 │ 6 │ 4 │ 2 │
├───┼───┼───┼───┼───┤
│ 1 │ 2 │ 3 │ 2 │ 1 │
└───┴───┴───┴───┴───┘

图说:**四个角只被数了 1 次,中心被数了 9 次 —— 相差九倍。**
补一圈 0 之后,原来的边缘像素就变成了「内部」,被卷到的次数追上来。
(1 和 9 是书给的;这张表是我们数出来的。)

书还给了填充的第二个理由: 经过卷积运算后特征图尺寸会越来越小,填充能有效缓解这一点10

这个模型用的是 padding = 2(第一层的完整设置:3×3 的核、32 个、步长 2、padding 2)10

8. 主走查:一张 128×128×3 的图,逐层走到一个数

这是本章的主走查。上面每个机制在它上面各占一步。

先交代一件书没写、我们算出来的事

判断(我们的,不是书里的):这个网络五个卷积层全部是「3×3 的核、步长 2、padding 2」, 三个池化层全部是「2×2、步长 1」。

书只写了第一层的设置(3×3、32 个核、步长 2、padding 2), 其余四层在表 10.1 里 —— 而那张表是图片,不在清洗文本里。 但书在正文里给出了逐层的输出尺寸11,于是可以反推。 我们拿第 10 章那条尺寸公式逐层核了一遍,八个尺寸一个不差:

算式得数书给的
conv1(128 + 2×2 − 3) ÷ 2 + 16532×65×65 ✓
pool1(65 − 2) ÷ 1 + 16432×64×64 ✓
conv2(64 + 4 − 3) ÷ 2 + 13332×33×33 ✓
pool2(33 − 2) ÷ 1 + 13232×32×32 ✓
conv3(32 + 4 − 3) ÷ 2 + 11764×17×17 ✓
conv4(17 + 4 − 3) ÷ 2 + 11064×10×10 ✓
conv5(10 + 4 − 3) ÷ 2 + 16128×6×6 ✓
pool5(6 − 2) ÷ 1 + 15128×5×5 ✓

(除不尽时向下取整。)

如果错,会错在: 如果某一层用的是别的组合、恰好也得到同一个尺寸 (比如 5×5 的核配 padding 3),那我们这条反推就只是「一个说得通的解释」。 判据是:表 10.1 那张图片里写的到底是什么 —— 我们看不到它。

走查

━━ 输入 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

一张车前道路图,前方偏右有一个弯
采集时人手上打的转角是 **+0.60** ← 这就是它的标签

resize 到 **128 × 128 × 3**(三个通道:蓝、绿、红)= 49 152 个数

━━ 训练前:随机做一次颜色增强 ━━━━━━━━━━━━━━━━━━━━━━━━

这一张恰好被抽中「调亮度」:每个像素的三个值各乘 1.15
某个像素 (40, 200, 230) → (46, 230, 255) ← 255 封顶
**标签仍然是 +0.60 —— 因为亮度不带方向,这一步没有造假。**
(对照:如果这一步是水平翻转,标签就必须跟着变 −0.60,否则这条样本是错的。)

━━ 五个卷积层 + 三个池化层(尺寸全部是书给的) ━━━━━━━━━━━

输入 3 × 128 × 128
conv1 → **32 × 65 × 65** (3×3 核 32 个,步长 2,padding 2)
pool1 → **32 × 64 × 64**
conv2 → **32 × 33 × 33**
pool2 → **32 × 32 × 32**
conv3 → **64 × 17 × 17** (这一层后面不跟池化)
conv4 → **64 × 10 × 10** (这一层后面也不跟)
conv5 → **128 × 6 × 6**
pool5 → **128 × 5 × 5**

每一层的激活函数都是 **leaky-ReLU**(第 9 节讲为什么不是 ReLU)

卷积那一段的参数量(我们按书给的通道数和 3×3 核算的):
conv1 3×32×9 + 32 = 896
conv2 32×32×9 + 32 = 9 248
conv3 32×64×9 + 64 = 18 496
conv4 64×64×9 + 64 = 36 928
conv5 64×128×9 + 128 = 73 856
────────
**139 424 个,约 14 万**

━━ 拉平 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

128 × 5 × 5 = **3200 个数**排成一条线

━━ 三层全连接(中间夹 dropout,丢弃率 0.1) ━━━━━━━━━━━━━

3200 → …… → …… → **1**

⚠ 中间两层各有多少个神经元,**书没有给**(在表 10.1 那张图片里)。
所以全网的参数总数算不出来。**但可以肯定绝大部分参数在这一段** ——
光是「3200 接到 512 个神经元」这一层就是 164 万个参数,
是前面五个卷积层加起来的十一倍。**第 11 章那条观察在这里又应验了一次。**
(512 这个数是我们为演示假设的。)

dropout 0.1:每一批随机让**一成**的神经元这次不参与

━━ 最后一层 + tanh ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

最后一个神经元算出一个原始值,假设是 **0.63**
过 tanh:tanh(0.63) = **0.558**

⇒ **模型的预测:+0.558**(往右打)
**真实标签: +0.600**

━━ 算损失(均方差) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

差 = 0.600 − 0.558 = 0.042
均方差 = 0.042² = **0.001764**

⇒ 反向传播 → 优化器改参数 → 下一张图(第 08、09 章那一套)

━━ 上车之后 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**转角 +0.558** 送给底盘 → **第 02 章那个差速转向**:往右打就右轮减速、左轮不变
⇒ 车拐了。下一帧重来。

(这里送进去的本来就是 −1 到 1 的转角,和第 02 章那一步吃的是同一种量,
不需要额外换算 —— 第 12 章那条路不一样,它算出来的是角速度,
多一层书没给的折算,见那一章走查第 ⑦′ 步。)

这条走查里书给的: 128×128×3、八个逐层尺寸、leaky-ReLU、dropout 0.1、tanh、均方差、 最后一层 1 个输出。 我们算的: 五个卷积层的参数量、那个 512 的假设、0.63 / 0.558 / 0.001764 这几个演示数字、 以及那次亮度增强的像素值。

9. 四个设计选择,每一个书都给了理由

这是本章第三个承重点。这四条是这本书最像「工程师笔记」的一段。

① 激活用 leaky-ReLU,不用 ReLU

书的理由12:

ReLU 相对 sigmoid 解决了梯度消失,而且因为未激活所有神经元,还有减小参数量的作用。 然而正因为 ReLU 在 x < 0 时函数值为 0,网络不断加深时它极有可能带来权值无法更新的情况。 所以巡航模型选 leaky-ReLU:在 x < 0 时函数值不等于 0,而是等于一个相对较小的值 (乘一个非零的固定斜率),有选择地保留部分负值

ReLU leaky-ReLU
│ ╱ │ ╱
│ ╱ │ ╱
──────┼──╱──── ─────┼──╱────
│ ╱ │
全平 │ 斜率很小的一小段
│ │

x < 0 时:ReLU 输出 0、斜率 0 leaky-ReLU 输出一个小负数、斜率是个小正数
⇒ 梯度为 0,这个神经元 ⇒ 梯度不为 0,还救得回来
再也更新不了,「死」了

图说:**第 07 章讲的那个「死区」问题,在这里变成了一个不得不做的选择。**
07 章讲的是这个机制长什么样,这里是同一个机制在具体工程约束下的一次决策。

② 加 dropout,但丢弃率只取 0.1

书给了 dropout 的两个好处,第二个第 11 章没提过13:

好处说明
因为是随机丢弃,可以避免提取出的特征只在某些特定组合下才有效,大大增加网络的普适性
当数据量相较于网络复杂程度过小时,网络容易提取出过多冗余特征;丢弃部分神经元可以减少冗余中间特征的数量,同时提高各个特征之间的正交性

「正交」就地解释: 两个特征互不重复、各说各的事,就叫正交。 反过来,五个神经元都在认同一条边,那是四份冗余。

丢弃率为什么只取 0.1(丢一成): 书的理由是 「鉴于巡航网络模型参数量相对较小」13

这一条要和第 11 章连起来读: AlexNet 有 6000 万个参数,丢掉一半也还剩很多; 这个网络本来就小,丢多了就真的不够用了。

③ 最后加一个 tanh

书的理由干净利落: 因为采集数据和控制小车时用的转向角度都是经过归一化后的小数, 所以最后要加一个 tanh 层把输出转换到 [−1, 1] 之间14

换句话说:模型输出的取值范围,是被标签的取值范围反推出来的。 第 05 章那个「归一化到 [−1,1]」的决定,在这里决定了最后一层用什么。

④ 损失用均方差,不用绝对误差

书给的理由是全章最精细的一处推理15:

相较于绝对误差,均方差误差的梯度是随着损失值的大小而不断变化的 —— 损失值较大时梯度也相对较大,而当损失值趋近于 0 时,均方差的梯度也会下降至接近于 0。 因此用均方差可以使模型在训练结束时更加精确,更容易收敛

拿主走查那组数算给你看(这几个数是我们编来演示的):

训练刚开始:预测 0.000,标签 0.600 差 = 0.600
均方差的梯度 ∝ 2 × 0.600 = **1.200** ← 步子大,快速逼近
绝对误差的梯度 = **1**(恒定)

训练快结束:预测 0.558,标签 0.600 差 = 0.042
均方差的梯度 ∝ 2 × 0.042 = **0.084** ← 步子自动缩小了十四倍
绝对误差的梯度 = **1**(还是那么大)

图说:**绝对误差的毛病就在最后那一行** ——
已经很接近答案了,它还在迈同样大的步子,于是在最优点附近来回跳、停不下来。
均方差自己会减速。**这就是书说的「训练结束时更加精确,更容易收敛」。**

10. 最值钱的一条经验:400 轮比 300 轮更差

这是这本书应该被记住的那句话。

书给的数

书自己的项目训了 300 轮,总耗时约 180 分钟16:

损失曲线:
起点 **0.219**
终点 **0.019** ← 降到原来的十一分之一
大约 **250 轮**开始下降变缓、逐渐趋于平稳
所以书判断:**训练轮次设在 300 左右较为合适**

(第 09 章的口径:一轮 = 整个训练集完整过一遍 —— 这里就是人开车采回来的那批图。
**书没有给这批图的张数**(见第 13 节边界),所以下面不拿它做任何换算。)

然后是那句话

经过训练验证,如果继续训练模型,例如训练 400 轮,则模型会出现过拟合的情况, 此时尽管模型的损失值较低,但是使用该模型进行实测时效果会变差16

把这句话摊开:

300 轮的模型: 损失 0.019 实车跑得好
400 轮的模型: 损失 更低 **实车跑得更差**

⇒ **数字更好看的那个模型,在真车上更烂。**

图说:这就是「指标」和「有没有用」之间那道缝。
第 09 章讲过「看损失曲线判断该不该停」——
**这一段告诉你曲线只能告诉你「还在不在学」,不能告诉你「学到的有没有用」。**

为什么会这样(第 07、11 章那个病在实车上的样子): 过拟合是「把训练那批数据背下来了」。训练集里那些采集时的抖动、 偶尔打歪的一把方向、某个角度的一片反光 —— 训久了模型会把这些也一起背进去。

而在验证集上看不出来,因为验证集是从同一批数据里切的,毛病一样。 只有真车跑起来、遇到从没出现过的光照和角度时,才露馅。 (这一段的机理是我们补的;书只报了现象。)

判断(我们的,不是书里的):这一条是这本书在整个书架上最不可替代的一句话, 因为写出它需要一辆真车。 绝大多数教材碰不到这条缝 —— 它们的评估终点就是验证集上那个数字。 如果错,会错在: 书只报了这一次观察,没有报重复实验,也没有说「实测效果变差」 是差在哪(圈速?出界次数?)。如果那一次只是采集数据本身有偏差,这条经验就没有它看起来那么普适。 判据是:换一批采集数据重跑,300 轮和 400 轮的这个拐点还在不在。

11. 部署与验证

部署:两个文件、一根网线、一个配置项

书给的流程是完整可照做的17:

① 训练完成 → 生成两个文件:
**model** 保存模型结构
**params** 保存模型参数
(第 05 章讲过这个分野:结构是「算式长什么样」,参数是「里面那些数」。)

② 把这两个文件下载到自己的电脑上

③ 打开 MobaXterm(一个远程连接工具)→ 新建 SSH 会话
输入板卡的 IP,**通常是 192.168.1.254**
(SSH 就地解释:一条加密的远程命令通道 ——
连上之后,你在自己电脑上敲的命令是在那块板子上执行的。)

④ 连上之后,把 model 和 params 传到指定目录

⑤ 改配置文件 `config.py`,要改的主要是两项:
**模型路径** 和 **所有调用摄像头的编码**

⑥ 执行巡航程序

图说:注意这里**没有「重新训练」这一步**。
模型在云上训完,板子只负责跑 —— 这就是第 01 章说的
「训练在云上,推理在车上」。

验证分两层

第一层:静态图片。 输入前置摄像头拍到的图,预测转角并把数字打印在图片上18

书的结论,以及一句非常诚实的话:

无论是在直道、弯道,还是路径更复杂的 S 弯道和十字路口,预测结果与人为判断相比都比较准确。 尽管对于极少数的图片预测结果会有一定偏差,但数量较少 —— 有可能是数据采集时出现的部分错误噪声图像对模型的训练结果造成了干扰。 事实上,无论何种模型,在应用时都难以获得 100% 的成功率18

这段话的三个层次值得分开看:

说的是属于哪一类
「预测结果与人为判断相比都比较准确」定性结论,没有量化
「有可能是数据采集时的错误噪声图像干扰了训练」作者的猜测,书自己用了「有可能」
「无论何种模型都难以获得 100% 的成功率」一句诚实的通则,值得留

第二层:实车路试。 书报的结果是:直道、小幅度弯道、十字路口、大幅度转弯, 小车对于路径的跟踪效果都较好19同样是定性的。

12. 作者的判断与证据

说法是哪一类
采集流水线、手柄接法、速度设在 20、避免跳跃性大转向有证据(现场经验),而且理由都给了
几何变换在这个任务上不适用有证据,而且推理完整 —— 这是全书最漂亮的一处
「翻转的同时把标签取负就能用」不在书里,是我们补的(第 3 节判断块)。书的推理没错,但结论下得比必要的更死
光照、反光、印刷色差影响很大有证据(实际调试中发现的)
回归与分类只差最后一层教科书式的共识,书的表述准确
边缘像素只被卷 1 次、中心 9 次有证据,而且我们数出来对得上(第 6 节那张表)
「参数量仅约为其 1/4」表述含糊。 我们判定它指的是输入数据量而非模型参数(第 6 节判断块)
五个卷积层的完整设置不在书里(在图片表格中)。 我们从逐层输出尺寸反推,八个尺寸全部对得上(第 8 节判断块)
四个设计选择的理由全部有证据,而且这是全书最像工程师笔记的一段
300 轮 vs 400 轮那条经验有证据(一次实验观察),但没有重复实验,也没有说「变差」差在哪个指标上
「极少数偏差有可能是采集噪声造成的」作者的猜测,书自己用了「有可能」。别当结论引
「预测比较准确」「跟踪效果都较好」定性结论,没有成功率、圈速或失败案例
「无论何种模型都难以获得 100% 的成功率」一句诚实的通则

13. 边界与局限

  • 这一章对应原书 §10.3。 采集流水线在第 05 章已经完整讲过,这里只回指并补这一章的新细节。
  • 书没有解释为什么这里用 8:2 而第 09 章那个项目用 9:1。
  • 书没有给这个数据集的张数,也没有给一次拿几张来算(批大小)。 它只说了怎么采、 按 8:2 分,以及训了 300 轮、用掉 180 分钟。所以「300 轮等于多少次参数更新」这笔账, 在这本书里算不出来 —— 谁要算,那两个数都得自己假设。
  • 书没有给这个模型的参数总数。 五个卷积层能按通道数算出来(约 14 万), 但三层全连接的中间尺寸在图片表格里,全网总数算不出来
  • 书没有给这个模型在板子上的推理速度。 每秒能处理几帧、够不够跟得上车速,一个字没提 —— 而这恰恰是「算力有限」这条约束最该被量化的地方。
  • 书没有说「实测效果会变差」到底差在哪。 出界?画龙?圈速慢?没有指标。
  • 书没有讲怎么处理「模型吐了个离谱的数」。 真车上如果某一帧预测出 +0.99, 车会猛打一把方向 —— 有没有做限幅、有没有做时序平滑,书没提。(补充:不在书里,来自通用知识。)
  • 书没有代码。 项目代码在一个在线平台上,书里只给了链接20
  • 这条路和第 12 章那条路没有被放在一起比较。 三条路的对比表在第 14 章,那是我们做的。

14. 可带走的

全章主走查一行写完:

一张车前道路图,人手上打的转角 +0.60 就是它的标签(不需要任何人工标注)→ resize 到 128×128×3 → 随机做一次颜色增强(调亮度,标签不动; 如果换成水平翻转,标签就必须跟着变 −0.60)→ conv1 32×65×65 → pool1 32×64×64 → conv2 32×33×33 → pool2 32×32×32 → conv3 64×17×17 → conv4 64×10×10 → conv5 128×6×6 → pool5 128×5×5 (激活全用 leaky-ReLU;卷积那一段约 14 万个参数)→ 拉平成 3200 个数 → 三层全连接(中间夹丢弃率 0.1 的 dropout)→ 最后一层只有 1 个输出,算出 0.63 → tanh → +0.558 → 均方差损失 (0.600 − 0.558)² = 0.001764 → 回传改参数。

上车之后:0.558 → 第 02 章那个差速转向 → 车往右拐。

  1. 这条路把中间那一整段规则换掉了 —— 不是简化,是让网络自己学一套;
  2. 这个任务不需要任何人工标注 —— 标签是开车开出来的,人在开车的同时标注就完成了;
  3. 采集速度压在 20 左右: 快了人的反应跟不上,快了两帧之间会漏掉大段路;
  4. 不要出现跳跃性的大幅度转向 —— 那会造出「图几乎一样、标签差很多」的自相矛盾样本;
  5. 全书最漂亮的一处推理:几何变换(翻转、平移、缩放)在这个任务上会毁掉数据 —— 图翻了、方向反了,标签却没跟着反;
  6. 它的一般形式值得单独带走:任何「通用最佳实践」都要先问一句, 它和我这个任务的标签冲不冲突;
  7. 颜色变换正对症 —— 因为光照、反光、印刷色差本来就是这个赛场最大的干扰源; 而且必须随机地做,否则会造出「亮 ⇒ 往左打」这种假规律;
  8. 回归和分类只差最后一层: 接到 1 个神经元上,而不是 n 个;
  9. 自己设计一个小网络的唯一理由是板子扛不动 —— 第 01 章那条「边缘算力反过来约束模型设计」在这里第一次真的兑现;
  10. 填充不是凑数用的: 3×3 的核不填充时,边缘像素最少只被卷过 1 次,中心被卷到 9 次;
  11. 四个设计选择各有理由: leaky-ReLU(躲开 ReLU 的死区)、 dropout 0.1(网络本来就小)、tanh(标签本来就在 [−1,1])、 均方差(损失大时步子大、快到终点时步子自动缩小);
  12. 最值钱的一条:训到 400 轮损失更低,实车跑起来反而更差。 损失曲线只告诉你「还在不在学」,不告诉你「学到的有没有用」;
  13. 部署没有「重新训练」这一步 —— 云上训完,板子只跑;要改的只有模型路径和摄像头编码;
  14. 书自己的一句通则值得留:无论何种模型,在应用时都难以获得 100% 的成功率。

15. 原文地图

主题原书章原文位置
这是一个回归问题;三步(采数据 → 设计训练 → 实车部署)第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:141(搜「这属于机器学习的回归问题」)
采集流水线;两大部分(图像 = 输入、转角 = 标签)第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:145(搜「形成图像与转角互相对应的数据对」)
手柄经 USB 扩展器接板卡;速度设在 20;避免跳跃性大转向第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:147(搜「速度一般设置在20左右」) · text/11-ch10.txt:147(搜「尽量避免出现跳跃性的大幅度转向」)
原始数据存成键值对;转成文本格式的理由第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:151(搜「与后续网络训练时所需要的数据格式匹配」)
8:2 划分训练集与验证集第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:153(搜「8:2的比例随机划分」)
泛化性与过拟合的定义第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:161(搜「能否将从某些数据中学习到的信息正确应用在其他数据上」) · text/11-ch10.txt:163(搜「在训练集上的效果好,在测试集上的效果差」)
两大类增强;几何变换在此并不适用;光照/反光/色差影响很大;五类颜色变换第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:167(搜「几何变换在此并不适用」) · text/11-ch10.txt:167(搜「现场地图反光程度和印刷色差」)
增强要用 numpy 先把像素转成矩阵第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:169(搜「先用numpy库将图片的像素信息转换为矩阵形式」)
必须随机地做,防止特征集中第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:175(搜「防止出现特征集中的问题」)
回归 vs 分类:最后一层接 1 个神经元第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:181(搜「而是将其连接到一个神经元上」) · text/11-ch10.txt:185(搜「分类任务解决的是一个离散型问题」)
板卡算力有限,所以模型要小;输入 128×128×3;5 层卷积的安排第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:189(搜「这种边缘计算开发板算力有限」)
填充的理由:边缘 1 次 vs 中心 9 次;第一层的完整设置第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:193(搜「部分像素最少仅能经过1次」)
逐层输出尺寸(八个数)第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:201(搜「经过池化后输出为p=128×5×5」)
leaky-ReLU 的理由(ReLU 的死区)第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:205(搜「极有可能会带来权值无法更新的情况」) · text/11-ch10.txt:207(搜「有选择地保留部分负值」)
dropout 的两个好处;丢弃率 0.1 的理由第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:209(搜「提高各个特征之间的正交性」) · text/11-ch10.txt:209(搜「丢弃率设置为0.1」)
三层全连接;拉平;tanh 的理由第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:211(搜「还需加入一个tanh层」)
均方差 vs 绝对误差的理由第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:213(搜「均方差误差的梯度是随着损失值的大小而不断变化的」)
300 轮 180 分钟、0.219 → 0.019、250 轮平稳、400 轮反而更差第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:225(搜「损失值由0.219下降至0.019」) · text/11-ch10.txt:225(搜「使用该模型进行实测时效果会变差」)
部署:model 与 params;SSH;IP;改 config.py第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:233(搜「分别保存模型结构和模型参数」) · text/11-ch10.txt:239(搜「192.168.1.254」) · text/11-ch10.txt:247(搜「模型路径和所有调用摄像头的编码」)
静态验证;「难以获得100%的成功率」第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:257(搜「难以获得100%的成功率」)
实车路试的结论第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:267(搜「小车对于路径的跟踪效果都较好」)
不附代码,只给在线项目链接第10章 智能车自动巡航算法设计及部署text/11-ch10.txt:217(搜「在本书中就不附代码和数据集介绍」)

Footnotes

  1. 出处:「第10章 智能车自动巡航算法设计及部署」第 145 段(text/11-ch10.txt:145,搜「形成图像与转角互相对应的数据对」)与第 147 段(text/11-ch10.txt:147,搜「速度一般设置在20左右」;同段搜「尽量避免出现跳跃性的大幅度转向」)。第 147 段还给了手柄经 USB 扩展器与 EdgeBoard 板卡相连,以及数据以「图像编号 → 转向角度」的键值对存进 JSON。「车开太快两头都出问题」和「跳跃转向为什么会造出自相矛盾样本」这两段拆解是我们补的。 2 3 4 5

  2. 出处:「第10章 智能车自动巡航算法设计及部署」第 153 段(text/11-ch10.txt:153,搜「8:2的比例随机划分」)。书没有解释为什么这里是 8:2 而第 8 章那个项目是 9:1。

  3. 出处:「第10章 智能车自动巡航算法设计及部署」第 151 段(text/11-ch10.txt:151,搜「与后续网络训练时所需要的数据格式匹配」)。

  4. 出处:「第10章 智能车自动巡航算法设计及部署」第 167 段(text/11-ch10.txt:167,搜「几何变换在此并不适用」;同段搜「现场地图反光程度和印刷色差」)。原文同段列了两大类增强手段与本项目实际用的五类颜色变换。第 3 节那个「+0.6 翻转之后仍是 +0.6」的例子是我们编来演示这条推理的。 2 3 4

  5. 出处:「第10章 智能车自动巡航算法设计及部署」第 169 段(text/11-ch10.txt:169,搜「先用numpy库将图片的像素信息转换为矩阵形式」)。原文:滤镜用 OpenCV,对比度/亮度/饱和度/色调的变换用 PIL 的 ImageEnhance 类。

  6. 出处:「第10章 智能车自动巡航算法设计及部署」第 175 段(text/11-ch10.txt:175,搜「防止出现特征集中的问题」)。「特征集中」具体会出什么问题,书没有展开,那个「亮 ⇒ 往左打」的例子是我们补的。

  7. 出处:「第10章 智能车自动巡航算法设计及部署」第 181 段(text/11-ch10.txt:181,搜「而是将其连接到一个神经元上」)。

  8. 出处:「第10章 智能车自动巡航算法设计及部署」第 185 段(text/11-ch10.txt:185,搜「分类任务解决的是一个离散型问题」)。「离散」与「连续」的解释是我们补的。

  9. 出处:「第10章 智能车自动巡航算法设计及部署」第 189 段(text/11-ch10.txt:189,搜「这种边缘计算开发板算力有限」)。同段说输入 128×128×3「远小于 AlexNet 输入图像尺寸,参数量仅约为其 1/4」,这句表述含糊,见第 6 节的判断块。 2

  10. 出处:「第10章 智能车自动巡航算法设计及部署」第 193 段(text/11-ch10.txt:193,搜「部分像素最少仅能经过1次」)。同段给了第一层的完整设置(3×3 核、32 个、步长 2、padding 2)与填充的第二个理由(特征图会越来越小)。第 6 节那张「被卷到几次」的计数表是我们数出来的,书只给了 1 和 9 这两个端点。 2 3

  11. 出处:「第10章 智能车自动巡航算法设计及部署」第 201 段(text/11-ch10.txt:201,搜「经过池化后输出为p=128×5×5」)。八个逐层尺寸全部在这一段;各层的核大小、步长、填充在表 10.1 里,而那是图片。第 8 节那张反推表是我们用第 10 章的尺寸公式算的。

  12. 出处:「第10章 智能车自动巡航算法设计及部署」第 205 段(text/11-ch10.txt:205,搜「极有可能会带来权值无法更新的情况」)与第 207 段(text/11-ch10.txt:207,搜「有选择地保留部分负值」)。

  13. 出处:「第10章 智能车自动巡航算法设计及部署」第 209 段(text/11-ch10.txt:209,搜「提高各个特征之间的正交性」;同段搜「丢弃率设置为0.1」)。「正交」这个词的解释是我们补的。 2

  14. 出处:「第10章 智能车自动巡航算法设计及部署」第 211 段(text/11-ch10.txt:211,搜「还需加入一个tanh层」)。原文同段还说三层全连接的激活函数也是 leaky-ReLU。

  15. 出处:「第10章 智能车自动巡航算法设计及部署」第 213 段(text/11-ch10.txt:213,搜「均方差误差的梯度是随着损失值的大小而不断变化的」)。第 9 节那两组梯度对比数字是我们算来演示的。

  16. 出处:「第10章 智能车自动巡航算法设计及部署」第 225 段(text/11-ch10.txt:225,搜「损失值由0.219下降至0.019」;同段搜「使用该模型进行实测时效果会变差」)。原文还说读者可以尝试修改超参数设置、观察结果的不同。「为什么会这样」那段机理是我们补的,书只报了现象。 2

  17. 出处:「第10章 智能车自动巡航算法设计及部署」第 233 段(text/11-ch10.txt:233,搜「分别保存模型结构和模型参数」)、第 239 段(text/11-ch10.txt:239,搜「192.168.1.254」)与第 247 段(text/11-ch10.txt:247,搜「模型路径和所有调用摄像头的编码」)。SSH 是什么是我们补的通用知识。

  18. 出处:「第10章 智能车自动巡航算法设计及部署」第 257 段(text/11-ch10.txt:257,搜「难以获得100%的成功率」)。同段还说少数偏差「有可能是数据采集时出现的部分错误噪声图像对模型的训练结果造成了干扰」——书自己用的是「有可能」,这是猜测不是结论。 2

  19. 出处:「第10章 智能车自动巡航算法设计及部署」第 267 段(text/11-ch10.txt:267,搜「小车对于路径的跟踪效果都较好」)。这是定性结论,书没有给成功率、圈速或失败案例。

  20. 出处:「第10章 智能车自动巡航算法设计及部署」第 217 段(text/11-ch10.txt:217,搜「在本书中就不附代码和数据集介绍」)。在线项目地址在第 221 段。