🦙 第5章 基于 Llama 2 定制行业大模型
📄 一句话概括

让模型懂一个行业,有两条路:继续预训练(灌大量行业文本, 学说法)和指令微调(喂几千条问答,学任务)。两条路补的东西不一样,混着用才对。 这一节用字符级 n-gram 真做一次配比实验:行业语料从 0% 加到 100%, 看行业困惑度和通用困惑度怎么此消彼长。

📝 本章和前面几章的关系

上一节把模型立起来了,这一节改它。 第 3 章 3.3 节讲过全参微调与 LoRA 的账,这一节讲的是行业模型特有的那个问题: 灌了行业数据之后,通用能力掉多少、值不值。

一、两条路,补的东西不一样

继续预训练(领域预训练):拿海量无标注行业文本接着训。 它补的是「说法」——模型开始用行业里的词、行业里的句式。 指令微调:拿几千条问答对训。它补的是「任务」——模型知道被问什么该怎么答。 两条路的成本差两个数量级:领域预训练要几亿 token、几十张卡、几天; 指令微调几千条数据、一张卡、几小时。

所以顺序是先指令微调。 大部分行业应用,指令微调加上检索就够了,领域预训练是「说法实在不像」时才做的事。

二、配比:留多少通用语料

只用行业语料训,行业困惑度最低,通用困惑度最高——模型把通用语言忘了。 这就是灾难性遗忘。演示里行业占 100% 时行业困惑度 51.7、通用 93.7;行业占 80% 时是 55.6 和 81.4。 换算一下:20% 的通用语料,换来行业困惑度涨 3.9,通用困惑度降 12.3。这笔买卖很划算。

常见经验是行业语料占七到九成。这一两成通用语料不是浪费,它买的是「模型还记得怎么说话」。 更细的做法是分层配比:通用语料里再挑一部分「跟行业相关但不属于行业」的,比如通用科普、通用问答, 比纯文学语料更有效。

三、灾难性遗忘长什么样

在小模型上它表现为困惑度暴涨; 在真模型上它更隐蔽——不是「不会说人话了」,而是某些通用能力悄悄掉了:常识问答变差、指令跟随变差、 格式稳定性变差。 最容易被发现的是格式:微调之后回答开始不带标点、或者每条都强行套行业模板。 这是「通用指令跟随能力」被行业格式覆盖了。 对策有三条:混通用语料、降学习率、少训几轮。三条都是从「别让它改太多」出发的。

还有一条最实用:微调时把通用能力也放进评测集。不测就发现不了,发现了也已经上线了。

四、微调策略:LoRA 挂哪、学率多少、训几轮

行业微调一般先上 LoRA,秩 8 到 16,挂 q、k、v、o 四路。 理由是全参微调的成本高十倍,而行业微调通常不需要改太多。 学习率比全参高一个量级,万分之一到万分之二。轮数一到三轮,行业数据量小,训多了就背下来了。 数据量够大(几千条以上)再考虑加前馈三块。挂得越多参数越多,效果上限也越高。

有一个反直觉的经验: 要「注入知识」,LoRA 的秩要往上涨;要「改格式」,低秩就够。因为知识是分散的,格式是集中的。

五、知识进不去,是微调最常见的天花板

微调改不了模型不知道的事实。它只会让模型把不知道的东西说得更像真的——这叫幻觉,不叫学习。 判断标准很简单:这条知识在训练数据里出现过吗?出现过,微调有用;没出现过,微调没用。 要补新知识,用检索:把文档切块、建索引、问的时候捞三段进上下文。 改库即生效,不用重训,还能溯源。

所以行业模型的典型架构是三层:基座(会说人话)+ 微调(会说行业话)+ 检索(知道具体事实)。

六、动手 Demo:三种语料配比,看行业困惑度和通用困惑度怎么换

用字符级三阶 n-gram 当替身:它小、能真训、结论方向和大模型一致。 两份语料各八十句——一份日常词、一份行业词,句子都由术语拼成。 三种配比各训一个,在行业测试集和通用测试集上各算一次困惑度。 再把行业占比从 0% 扫到 100%,看两条曲线怎么交叉。

VOCAB = sorted(set(u''.join(COM_TERMS + IND_TERMS)))
NV = len(VOCAB) + 2        # 三种配比共用同一个词表

def build(train, order=3):
    table = {}
    for s in train:
        pad = u'^' * (order - 1) + s + u'$'
        for i in range(len(pad) - order + 1):
            key, nxt = pad[i:i + order - 1], pad[i + order - 1]
            d = table.setdefault(key, {})
            d[nxt] = d.get(nxt, 0) + 1
    return table

# 加一平滑:没见过的一半算未知,一半靠 +1 兜
p = (d.get(nxt, 0) + 1.0) / (sum(d.values()) + NV)

实跑输出:

--------------------------------------------------------------------
三种语料配比,看行业困惑度和通用困惑度怎么换
--------------------------------------------------------------------


  ① 语料与测试集
  --------------------------------------------------------------------
| 分块 | 条数 | 规模 |
|---|---|---:|
| 通用训练语料 | 80 句 | 950 字 |
| 行业训练语料 | 80 句 | 956 字 |
| 行业测试集 | 20 句 | 242 字 |
| 通用测试集 | 20 句 | 228 字 |

  语料是造的:从字表里随机取字拼句。所以这里量的是「字与字的搭配」学得怎么样,不是真实语言能力。
  要看的是三种配比之间的相对差别。
  ! 真实领域预训练也用这套方法验配比:拿两份小语料训几个小模型,比困惑度,再放大。
  先在小规模上把配比试出来,比直接烧一次大的便宜两个数量级。

  ② 三种配比
  --------------------------------------------------------------------
| 配比 | 通用条数 / 行业条数 | 比例 |
|---|:---:|:---:|
| 只用通用 | 80 / 0 | 100% / 0% |
| 只用行业 | 0 / 80 | 0% / 100% |
| 八二混合 | 16 / 64 | 20% / 80% |

  三种配比参数量相同(都是三阶字符模型),差别只在看到过什么。

  ③ 四种困惑度
  --------------------------------------------------------------------
| 训练语料 | 行业困惑度 | 通用困惑度 |
|---|---:|---:|
| 只用通用 | 93.5 | 53.7 |
| 只用行业 | 51.7 | 93.7 |
| 八二混合 | 55.6 | 81.4 |

  只用行业语的模型,行业困惑度最低,通用困惑度最高——它把通用语言忘了。
  ! 这就是灾难性遗忘的最小演示。
  真模型上一个数量级的差别往往表现得更隐蔽:不是「不会说人话了」,而是「某些通用能力悄悄掉了」。

  ④ 混合比例怎么影响两头
  --------------------------------------------------------------------
| 配比 | 行业困惑度 | 通用困惑度 |
|---|---:|---:|
| 行业占 0% | 93.5 | 53.7 |
| 行业占 20% | 78.7 | 58.1 |
| 行业占 40% | 67.2 | 65.7 |
| 行业占 60% | 61.6 | 69.9 |
| 行业占 80% | 55.6 | 81.4 |
| 行业占 100% | 51.7 | 93.7 |

  行业占比越高,行业困惑度越低、通用困惑度越高。没有两头都最优的点,只有折中。
  ! 常见经验是行业语料占七到九成,留一两成通用语料「压着不让它忘」。
  这一两成不是浪费——它买的是「模型还记得怎么说话」。

  ⑤ 微调与领域预训练的分工
  --------------------------------------------------------------------
| 手段 | 吃什么 | 补什么 | 代价 |
|---|---|---|---|
| 领域预训练 | 海量无标注行业文本 | 补知识、学说法 | 贵,要几亿 token |
| 指令微调 | 几千条问答对 | 学格式、学任务 | 便宜,一天能跑完 |
| 检索增强 | 行业文档库 | 补事实、可更新 | 不训练,改库即生效 |

  三者不是替代关系。知识要新就用检索,说法要像就用领域预训练,格式要对就用指令微调。
  ! 最容易做错的是「什么都想用微调解决」。
  微调改不了模型不知道的事实——它只会让模型把不知道的东西说得更像真的。

  ⑥ 真机上的配比参数
  --------------------------------------------------------------------

  [配置原样 · 本沙箱不执行]
  | # 继续预训练,混一点通用语料防遗忘
  | train_files:
  |   - path: data/industry.jsonl
  |     weight: 0.8
  |   - path: data/general.jsonl
  |     weight: 0.2
  这是训练框架的数据配置片段,本沙箱没有训练框架,不执行。

①语料是造的:由术语拼成句子,每条五到七个词。 为什么不像上一版那样直接随机取字?因为均匀随机的字之间没有搭配,三阶模型什么都学不到, 配比实验就测不出差别。 真实领域预训练也是这么验配比的:拿两份小语料训几个小模型,比困惑度,再放大。 先在小规模上把配比试出来,比直接烧一次大的便宜两个数量级。 ②三种配比共用同一个词表。这一点很重要——真实训练时词表在训练前就定死了,不会因为语料换了而缩水。 词表不一样,困惑度就没法横向比。 ③结果很清楚:只用通用语料的模型,通用困惑度 53.7、行业 93.5; 只用行业语料的正好反过来,行业 51.7、通用 93.7。各自只认得自己那一半。 八二混合:行业 55.6、通用 81.4。行业那边只差 3.9,通用那边好了 12.3。 20% 的通用语料买回了三分之二的通用能力。 ④扫一遍配比看趋势:行业占比从 0 涨到 100,行业困惑度单调降(93.5 到 51.7), 通用困惑度单调升(53.7 到 93.7)。 没有两头都最优的点,只有折中。折中点的选择取决于业务:如果只做行业问答,行业占九成也行; 如果还要处理通用闲聊,八成是底线。 ⑤三种手段的分工要分清:知识要新用检索,说法要像用领域预训练,格式要对用指令微调。 最容易做错的是「什么都想用微调解决」。 微调改不了模型不知道的事实——它只会让模型把不知道的东西说得更像真的。

30 秒实验

  1. 把八二混合改成「通用 8 条 + 行业 72 条」(一成通用),重跑第③步。通用困惑度会涨到多少? 这一成通用还值不值?
  2. 把三阶改成二阶,重跑。两阶模型的困惑度整体会变差,但配比的趋势有没有变?这决定结论稳不稳。
  3. 把 NV 从「共用词表」改成「各自语料的字符集」,重跑第③步。数值会怎么变? 这说明为什么困惑度不能跨词表比较。

常见误区

  1. 以为微调能灌知识。微调改的是「怎么说」,不是「知道什么」。要补事实,用检索; 检索不生效了,才轮到重训。
  2. 只用行业语料训。行业困惑度确实最低,但通用能力掉得厉害,表现是回答开始不像人话、格式开始飘。
  3. 拿困惑度跨模型比较。 困惑度跟词表、分词粒度、测试集都绑定,只能在同一套设置下比相对高低, 不能拿「我的模型 PPL 8,他的 12」当结论。
自测三问(点开对答案) 1. 领域预训练和指令微调,各自补什么? —— 领域预训练补「说法」——模型开始用行业词汇和句式; 指令微调补「任务」——模型知道被问什么该怎么答。前者贵两个数量级,所以先做后者。 2. 为什么行业占比 80% 时要留 20% 通用语料? —— 因为那 20% 买的是「模型还记得怎么说人话」。 演示里它只让行业困惑度涨 3.9,却让通用困惑度降了 12.3——这笔交换很划算。 3. 灾难性遗忘在真模型上为什么不容易发现? —— 因为它不表现为「突然不会说话」,而是通用能力悄悄下滑:常识变差、指令跟随变差、 格式稳定性变差。不把通用能力放进评测集,就永远发现不了。

模型改完了,最后一节:上线前怎么验,验不过怎么改。

相关笔记