这一节解决一个很实际的局面:目标任务的标注只有十几条,怎么训出能用的模型。 答案是从一个数据管够的相似任务里,把「哪些特征有用」这份判断搬过来当起点。 它省的不是训练时间,是标注数据。也正因为如此,它有一个绕不开的风险: 两个任务要是不够像,搬过来的东西会把模型推到错的一边——这叫负迁移。
这一节是 7.2 的直接延伸。7.2 里所有模型都默认「有一份够用的训练集」; 这里把这个前提抽掉,只留十几条,看还能不能学。 它跟 8.1 是一对: 集成学习解决「一个模型不够稳」,迁移学习解决「一个任务数据不够」。 两者都不是「换个更强的算法」,而是换「信息的来源」——一个从别的模型来,一个从别的任务来。
一、标注才是贵的那一项
先把问题摆对。「数据不够」有三种常见的应对,代价完全不同:
| 办法 | 代价 | 什么时候合适 |
|---|---|---|
| 多标一些 | 时间和钱,有些场景根本标不了 | 数据买得到 |
| 换更简单的模型 | 上限低,复杂规律学不动 | 特征少、规律简单 |
| 借一个相似任务 | 有负迁移的风险 | 存在「数据管够」的相似任务 |
| 第三条就是迁移学习。它的定义只有一句话:把一个任务上学到的东西,当作另一个任务的起点。 | ||
| 这里要记住的第一个区别:它省的不是训练时间,是标注。训练时间在今天的机器上很便宜, | ||
| 一条标好的医学影像、一句人工翻译的句子,那才是真的贵。 | ||
| 因此它有三个前提,缺一条就不该用: |
| 前提 | 意思 | 不满足会怎样 |
|---|---|---|
| 两个任务像 | 输入到输出的关系是同方向的 | 负迁移,比不搬还差 |
| 源任务数据够 | 它自己先得学出点东西 | 搬过来的是一份噪声 |
| 目标任务有几条标注 | 用来校准方向 | 不知道往哪边调 |
| 第二条最容易被忽略。如果源任务自己都只有二十条数据, | ||
| 它学到的权重里九成是噪声,搬过去等于把噪声当经验。 |
二、能搬的到底是什么
不是答案,是「哪些方向有用」。先看源任务学出了什么:
| 量 | 数值 | 怎么读 |
|---|---|---|
| 源任务测试准确率 | 0.9920 | 这份经验是可靠的 |
| 前 4 维的权重 | 2.86/3.01/2.81/2.96 | 四个方向都被看重 |
| 后 12 维的平均绝对值 | 0.0236 | 几乎为零,等于被忽略 |
| 没人告诉它「前 4 维有用」。是 4000 条数据把它逼出来的: | ||
| 把权重放在 12 个噪声维上,只会拉低准确率。 | ||
| 这份「把 16 维压成 4 个有用方向」的判断,就是所谓的表征。迁移学习搬的正是它—— | ||
| 不是搬「大于 0 算正例」这个答案,是搬「往哪几个方向看」这件事。 | ||
| 三种搬法,改动的地方不一样: |
| 搬法 | 具体做什么 | 什么时候用 |
|---|---|---|
| 当特征提取器 | 冻结前面的层,只训最后那一层 | 目标数据极少、任务很像 |
| 当初始化再微调 | 全部层都从源权重出发继续训 | 目标数据中等、任务很像 |
| 只搬前面几层 | 底层冻结、高层重训 | 底层像、高层不像 |
| 为什么会有「只搬前面几层」这种做法?因为网络的不同层学的东西不一样: | ||
| 底层学的是「边缘、颜色、纹理」这类通用东西,高层学的才是「这是猫还是狗」。 | ||
| 任务换了,通用部分还能用;判断部分必须重学。 | ||
| 这三种搬法在第 5 段会被量化成一条曲线:练多少步。冻结就是 0 步,微调就是几百步, | ||
| 而 0 步在负迁移的场景里是灾难。 |
三、收益曲线:数据越少越值钱
把目标任务的标注量从每类 5 条扫到 200 条,两种训法各跑 20 次取平均:
| 每类给几条 | 从零训练 | 拿源权重当初值 | 差多少 |
|---|---|---|---|
| 5 | 0.6759 | 0.8245 | +0.1486 |
| 10 | 0.7511 | 0.8442 | +0.0932 |
| 20 | 0.8379 | 0.8716 | +0.0337 |
| 50 | 0.9143 | 0.9220 | +0.0077 |
| 200 | 0.9587 | 0.9640 | +0.0053 |
| 每类只有 5 条(一共 10 条)时,从零训练 0.6759,借源权重 0.8245。 | |||
| 换个说法:错误率从 32.4% 降到 17.6%,少了 46%。 | |||
| 每类 200 条时两条线几乎并到一起(0.9587 对 0.9640)。 | |||
| 这就是迁移学习的收益曲线:数据越少,借来的东西越值钱;数据一多,自己学也够了。 | |||
| 「差多少」那一列从 +0.1486 单调掉到 +0.0053,这是这条曲线最干净的证据。 | |||
| 它同时在说两件事:借是有用的;借的用处会随数据变多而消失。 | |||
| 为什么数据一多就用不上了?因为源权重其实是一个很强的先验—— | |||
| 「前 4 维重要」这件事已经写进初值里了。 | |||
| 数据少的时候,这份先验是唯一的信息来源,所以它顶事;数据一多,数据自己就能把先验盖过去。 | |||
| 注意 200 条那一行借的还是略好(+0.0053)。这不是噪声里的巧合, | |||
| 而是「从一个好起点出发」本身带的一点正则效果——它让模型不容易在少量数据上跑偏。 |
四、负迁移:搬错了比不搬还差
上面那个目标任务是「门槛从 0 挪到 1.0」,方向没变。 现在换个狠的:规则完全反过来——前两维相加小于 0 才算正例。
| 每类给几条 | 从零训练 | 拿源权重当初值 | 差多少 |
|---|---|---|---|
| 10 | 0.7788 | 0.7095 | −0.0693 |
| 50 | 0.9225 | 0.9218 | −0.0006 |
| 200 | 0.9729 | 0.9728 | −0.0001 |
| 每类 10 条时,迁移反而更差:0.7788 对 0.7095。 | |||
| 原因很直接:源权重把「前两维取正」这个方向先写进了模型,而正确答案要求取负。 | |||
| 初值把模型推到了错的一边,10 条数据拉不回来。 | |||
| 数据一多(每类 200 条)就又追平了:0.9729 对 0.9728。 | |||
| 数据够多的时候,初值在哪儿都无所谓——反正会被数据自己拽到对的地方。 | |||
| 这叫负迁移。它是迁移学习唯一的真风险,也是「先看两个任务像不像」这条规矩的由来。 | |||
| 「像不像」看的不是「输入长什么样」,是「输入到输出的那个关系是不是同一个方向」。 | |||
| 两个任务都用同一批特征(前两维),输入完全一样,可关系的符号反了——这就是不像。 | |||
| 一句话记住:负迁移不是意外,是默认;正迁移才需要理由。 | |||
| 搬之前先问「凭什么它能帮上忙」,答不上来就别搬。 |
五、练多少步:冻结、微调、分层学习率
还是那个反向任务,每类只给 10 条。从源权重出发,跑不同的步数:
| 训练步数 | 测试准确率 | 权重前两维 |
|---|---|---|
| 0 | 0.2547 | 2.86/3.01 |
| 5 | 0.3090 | 1.84/1.81 |
| 20 | 0.4577 | 0.86/−0.22 |
| 100 | 0.5820 | −0.13/−1.55 |
| 300 | 0.6343 | −0.87/−2.26 |
| 1000 | 0.6817 | −1.66/−2.80 |
| 0 步那一行就是「完全冻结」:准确率 0.2547,比瞎猜(0.5)还差得多。 | ||
| 因为它把方向搞反了,还一步都不许改。 | ||
| 「冻结前面、只训最后一层」不是万能药。前面学的东西跟新任务方向相反时,冻结等于把错误焊死。 | ||
| 注意最右列那两串数是怎么翻过来的:从 +2.86 和 +3.01,一路走到 −1.66 和 −2.80。 | ||
| 它不是「在原来的方向上微调」,是「慢慢掉头」。这 1000 步干的就是掉头这件事。 | ||
| 可练到 1000 步,准确率也只有 0.6817。而同样这 20 条数据,从零训练是 0.7617。 | ||
| 借来的东西不但没帮上忙,还拖了后腿——这就是负迁移的完整样子。 | ||
| 这两行数字把迁移学习的两难摆得很清楚:练得少,怕方向错;练得多,等于没借。 | ||
| 工程上的折中叫分层学习率(也叫差分学习率):前面的层用小学习率慢慢动,最后一层用正常学习率。 | ||
| 它的想法是「底层大概率是对的、顶层大概率是错的」—— | ||
| 这正好对应第 2 段那句「底层通用、高层专用」。 | ||
| 最后落成三条可执行的规矩: |
| 规矩 | 为什么 |
|---|---|
| 先看两个任务的关系是不是同方向 | 方向反了必负迁移 |
| 目标数据越少,越要少练 | 数据少时先验是唯一的信息来源 |
| 一定留一份目标任务的验证集 | 只有它知道借来的东西帮没帮上忙 |
| 第三条是这三条里最不能省的。负迁移不会报错、不会崩, | |
| 它只会让准确率悄悄变低——没有验证集,你根本不会发现自己在往下走。 |
六、动手 Demo:把学过的搬过来用
五段:两个任务、源任务学到了什么、数据量扫描、负迁移、微调步数。 第 3 段和第 4 段是同一张表的两面:一个正迁移、一个负迁移。 这一节所有数字都来自同一个 16 维的小模型,唯一改动的是「目标任务怎么定义」。 这样才看得出:帮不帮上忙,取决于任务之间的关系,不取决于搬多少。
def make(n, kind, r=None): # 造数据:16 维,只有前 4 维有信息
X = r.normal(0.0, 1.0, (n, D))
if kind == u"源":
y = (X[:, :INF].sum(1) > 0).astype(int)
elif kind == u"目标":
y = (X[:, :INF].sum(1) > 1.0).astype(int) # 同一批特征,门槛挪了
else:
y = ((X[:, 0] + X[:, 1]) < 0).astype(int) # 反向:方向正好相反
return X, y
def train(X, y, w0=None, steps=300, lr=0.5, l2=1e-3):
w = np.zeros(D + 1) if w0 is None else w0.copy() # 给了 w0 就是「接着练」
for _ in range(steps):
p = 1.0 / (1.0 + np.exp(-Xb.dot(w)))
g = Xb.T.dot(p - y) / n + l2 * w
w -= lr * g
return w
WS = train(XTR, YTR) # 源任务 4000 条,学出一份权重
for nper in (5, 10, 20, 50, 200): # 目标数据从几条扫到 200 条
for s in range(20): # 每种情况换 20 次抽样,取平均
idx = few(YT, nper, r)
a.append(acc(train(XT[idx], YT[idx], None), XT, YT)) # 从零训练
b.append(acc(train(XT[idx], YT[idx], WS), XT, YT)) # 借源权重
实跑输出:
--------------------------------------------------------------------
把学过的搬过来用
--------------------------------------------------------------------
==============================================================
[1] 两个任务:一个数据多,一个数据少
16 维输入,**只有前 4 维有信息,后 12 维是纯噪声。**两个任务都用这 16 维:
任务 标签怎么定 能拿到多少条
--------------------------------------------------------------------------
源任务 前 4 维相加 > 0 4000 条
目标任务 前 4 维相加 > 1.0 每类十几条
**这就是迁移学习的真实局面:源任务数据管够,目标任务标注贵得要命。**
目标任务的规则跟源任务几乎一样,**只把门槛从 0 挪到了 1.0**——
**方向完全一致,用的是同一批特征。这是「值得迁移」的典型情形。**
[2] 先看源任务学到了什么
源任务测试准确率 0.9920
前 4 维的权重:2.86/3.01/2.81/2.96
后 12 维权重的平均绝对值:0.0236
**它自己学会了「前 4 维有用、后 12 维别理」。**没人告诉它这件事——
**是数据量把它逼出来的:4000 条数据下,把权重放在噪声维上只会拉低准确率。**
这份权重就是「学到的经验」。**迁移学习要搬的,正是这个东西。**
[3] 目标数据越来越少时,借来的权重值多少
目标任务测试集 3000 条。每条曲线跑 20 次不同的抽样,取平均:
每类给几条 从零训练 拿源权重当初值 差多少
--------------------------------------------------------------------------
5 0.6759 0.8245 +0.1486
10 0.7511 0.8442 +0.0932
20 0.8379 0.8716 +0.0337
50 0.9143 0.9220 +0.0077
200 0.9587 0.9640 +0.0053
**每类只有 5 条时:从零训练 0.6759,拿源权重当初值 0.8245——错误率少了 46%。**
**每类 200 条时两条线几乎并到一起(0.9587 对 0.9640)。**
**这就是迁移学习的收益曲线:数据越少,借来的东西越值钱;数据一多,自己学也够了。**
所以它省的不是「训练时间」,**是「标注数据」——而标注才是真正贵的那一项。**
[4] 什么时候搬过来会帮倒忙
换个目标任务:**标签完全反过来**(前两维相加小于 0 才算正例)。
它跟源任务的规则在同样的几个特征上「方向相反」。
每类给几条 从零训练 拿源权重当初值 差多少
--------------------------------------------------------------------------
10 0.7788 0.7095 -0.0693
50 0.9225 0.9218 -0.0006
200 0.9729 0.9728 -0.0001
**每类 10 条时,迁移反而更差:0.7788 对 0.7095。**
**因为源权重把「前两维取正」这个方向先写进了模型,而正确答案要求取负。**
**初值把模型推到了错的一边,数据太少拉不回来。**
**数据一多(每类 200 条)就又追平了:0.9729 对 0.9728。**
**这叫负迁移。它是迁移学习唯一的真风险,也是「先看两个任务像不像」这条规矩的由来。**
[5] 「搬过来」之后要练多少步
还是那个反向任务,每类只给 10 条。**从源权重出发,跑不同的步数看结果:**
训练步数 测试准确率 权重前两维
------------------------------------------------------
0 0.2547 2.86/3.01
5 0.3090 1.84/1.81
20 0.4577 0.86/-0.22
100 0.5820 -0.13/-1.55
300 0.6343 -0.87/-2.26
1000 0.6817 -1.66/-2.80
**0 步那一行就是「完全冻结」:准确率 0.2547,比瞎猜还差——因为它把方向搞反了。**
**「冻结前面、只训最后一层」不是万能的:前面学的东西跟新任务方向相反时,冻结等于把错误焊死。**
练到 1000 步,它确实慢慢翻过来了(0.6817)。**但同样这 20 条数据,从零训练是 0.7617。**
**借来的东西不但没帮上忙,还拖了后腿——这就是负迁移的完整样子。**
这几行数字把迁移学习的两难摆得很清楚:
**练得少,怕方向错;练得多,等于没借。**
**工程上的折中是「分层学习率」:前面几层用小学习率慢慢动,最后一层正常学。**
三段连起来:源任务学到「哪些特征有用」→ 这份权重当目标任务的起点
→ 练多少要试。**它省的是标注,赌的是「两个任务长得像」。赌错了就叫负迁移。**
第 3 段那张表的「差多少」一列,是这一节最值得盯的一列。 +0.1486 一路掉到 +0.0053,单调下降——它同时证明了两件事:借有用、 以及借的用处会随数据变多而消失。 第 4 段那个 0.7095 是负迁移最干净的证据。同样的代码、同样的超参、 只把标签的符号反过来,迁移就从帮忙变成了拖后腿。 所以「先看两个任务像不像」不是保守,是必须。 第 5 段最该看的是最右列那两串权重,不是准确率。从 2.86/3.01 走到 −1.66/−2.80, 它证明模型真的在「掉头」,而不是在错误的坡上原地打转。 第 5 段结尾那对比(0.6817 对 0.7617)是全节最重要的一对数。 它说明「借」不是免费的:借错了,练到 1000 步也追不回从零训练。 这也是为什么真实的迁移学习工程里,验证集比源模型更值钱。
30 秒实验
- 把第 3 段的
nper从 5 改成 2,重跑。 差距会更大——数据越少,初值的那份先验占的比重就越大。 - 把
train的steps从 300 改成 30,重跑第 3 段。 两条线都会掉下来,但「借」那条掉得少——因为它的起点本来就更靠近答案。 - 把源任务的标签改成「前 4 维相加 > 3.0」(更难、样本更偏),重跑第 3 段。 迁移的收益会变小——源任务自己学得越偏,搬过来的先验就越不可靠。
常见误区
- 以为迁移学习是「省训练时间」。它省的是标注。 训练时间在今天的机器上很便宜,一条标好的专业数据才是真贵的那一项。
- 以为「模型能复用」就等于「任务像」。像不像看的是输入到输出的关系方向, 不是输入长得像不像。Demo 第 4 段的两个任务用的是同一批特征,可关系反了,就是负迁移。
- 以为「冻结前面」总是安全的。Demo 第 5 段 0 步那一行是 0.2547,比瞎猜还差。 前面学的东西方向错了,冻结等于把错误焊死。
- 以为负迁移会报错。它不会。 它只让准确率悄悄往下走,所以目标任务的验证集是必需品,不是可选项。
自测三问(点开对答案)
1. 为什么说迁移学习省的是「标注」而不是「训练时间」? —— 因为**训练时间在今天很便宜,而标注很贵**:一张医学影像要医生看,一句翻译要人写。 **迁移学习做的事是「用别的任务的数据补上这个任务缺的数据」**——所以它直接减掉的是标注量, 不是算力。Demo 里每类 5 条就能到 0.8245,**而从零训练要 200 条才到 0.9587。** 2. 为什么「每类 5 条」时迁移收益最大,而「每类 200 条」时几乎没收益? —— 因为**源权重是一份很强的先验**(「前 4 维有用」已经写进初值里了)。 **数据少时,这份先验是唯一的信息来源,所以它顶事**; **数据一多,数据自己就能把先验盖过去**——初值在哪儿都无所谓了。 Demo 里 +0.1486 单调掉到 +0.0053,就是这个过程的量化。 3. 同样是「拿源权重当初值」,为什么一个是正迁移、一个是负迁移? —— **区别在目标任务跟源任务的关系方向。**正迁移那个任务只是把门槛从 0 挪到 1.0, **方向完全一致,源权重指的方向还是对的**; 负迁移那个任务要求「前两维相加小于 0」,**符号反了,源权重把模型推到错的一边**。 数据少的时候(每类 10 条)拉不回来, **所以 0.7788 掉到 0.7095;数据一多(每类 200 条)就追平了。**这一节管的是「任务之间借东西」。 下一节换一个完全不同的问题:任务不变,可一台机器已经装不下它了——模型放不进显存、数据读不完、 算一遍要几个月。 那时候要解决的不是「学什么」,是「怎么把算分到很多台机器上」。
- ☐ (本节完,下一节 8.4 分布式人工智能)