🦙 第4章 解决 Llama 2 大模型多轮对话难题
📄 一句话概括

多轮评测比单轮多两个维度:指代能不能解出、前后会不会自相矛盾。 这两个维度在单轮评测里根本量不到。 这一节用十二道题、两种设置量一次,重点是看「给更多历史」到底治好了什么、又弄坏了什么。

📝 本章和前面几章的关系

上一节讲了六种提升手段,这一节判断它们有没有用。 单轮评测的口径,第 3 章 3.4 节讲过; 这一节补的是多轮特有的那两个维度,以及「错题怎么变成动作」。

一、三个指标,两种设置

事实正确:答案对不对。指代解出:「它」「那个」指的是不是那个东西。 前后一致:同一个字段两次问,答案一样吗。三项分开量,别合成一个总分。 还要分两种设置:不带历史、带全部历史。两者之差,就是「历史值多少」。 演示里的数:事实 2/6 涨到 5/6,指代 0/6 涨到 4/6——历史把这两类治好了。

但「两个数取最新」那一栏两遍都是零。历史里同时有二十万和二十五万时,模型取的是最早出现的那个。 给更多历史,不一定更好。

二、题库怎么建

三类题不要:能从规范直接答出来的(测不出模型)、答案本身有争议的(测出来也吵不清)、 只考记忆的(换个说法就废)。 题库要固定、要版本化、要分类。分类不是为了好看,是为了「错哪一类」能直接读出「改哪里」。 题库只增不改。改题库等于换尺子,历史分数全部作废,你就再也说不清「这一版比上一版好在哪」。

三、错题归类:从分数到动作

演示里十二道题:不带历史错十道,带全部历史错三道。但错的类别整个换了一批。 指代悬空:补多轮数据。上下文缺失:接槽位。前后矛盾:提示里写「以最新为准」。格式不符:加格式约束。 每一类对应一个具体动作。 没有动作的评测报告,等于没写。「准确率 78%」这种句子,读完不知道该干什么。

还有一层:错题要留档。同一道题连着三个版本都错,说明它不是数据问题,是设计问题。

四、持续改进的闭环

抽样、归错、补数据、重训、重跑同一套题、看哪一列动了。六步,一步不能省。 一轮下来只应该有一两列动。 全都在动,说明你改的东西太多,归因不了——这跟第 3 章「一次只改一处」是同一个道理。 演示里的提升是脚本补了三条规则换来的,不是模型学会了。 但流程一模一样,只是真机上「补数据」之后要重训一轮,慢得多。

闭环一周转一圈就够了。转得太快,每轮的数据量太少,噪声会盖过信号。

五、动手 Demo:十二道多轮题,三种指标,带不带历史差多少

十二道题:六道事实、四道指代、两道「两个数打架」。两种设置各跑一遍,三个指标分开算。 作答的是一个规则替身,不是真模型。 它把「有上下文就答得出、没上下文就答不出」写成代码,好让差别能被数出来。 要看的是评测方法,不是这个替身的智商。

def answer(qid, ctx):
    kw = KEY[qid]
    if qid in CONFLICT and ctx.count(u'二十万') + ctx.count(u'三千') >= 2:
        return u'结论:二十五万。(取了最早出现的那个数)'
    if qid in NEED_CTX and kw not in ctx:
        return u'结论:不确定您指哪一项。' if qid in PRON else u'结论:需要前文才能确认。'
    return u'结论:%s。理由:按前文。' % kw

def score(res):
    f = sum(1 for qid, a in res if qid in FACT and KEY[qid] in a and u'理由:' in a)
    p = sum(1 for qid, a in res if qid in PRON and KEY[qid] in a)
    c = sum(1 for qid, a in res if qid in CONFLICT and KEY[qid] in a)
    return f, p, c

实跑输出:

--------------------------------------------------------------------
十二道多轮题,三种指标,带不带历史差多少
--------------------------------------------------------------------


  ① 题目分四类
  --------------------------------------------------------------------
| 编号 | 第几轮 | 类型 | 题目 |
|:---:|---|:---:|---|
| q01 | 1 | 事实 | 外立面要改哪一面 |
| q02 | 2 | 事实 | 预算够不够 |
| q03 | 3 | 指代 | 那它还能挪多少 |
| q04 | 4 | 指代 | 这个色温合适吗 |
| q05 | 5 | 指代 | 那面墙要不要加洗墙灯 |
| q06 | 6 | 事实 | 工期压到十天行不行 |
| q07 | 7 | 矛盾 | 刚才说的预算能改吗 |
| q08 | 8 | 指代 | 防护等级按那个来 |
| q09 | 9 | 事实 | 验收要几天 |
| q10 | 10 | 事实 | 下雨顺延吗 |
| q11 | 11 | 矛盾 | 前面定的色温是多少 |
| q12 | 12 | 事实 | 最后确认一下预算 |

  十二道题:六道事实、四道指代、两道「两个数打架」。
  ! 带代词的共六道(含两道「两个数打架」的),没有历史就一定答不出——这不是模型的问题,是输入不全。
  把它们放进题库,就是为了单独量出「历史到底值多少」。

  ② 两种设置,三种指标
  --------------------------------------------------------------------
| 设置 | 事实正确 | 指代解出 | 两数取新 |
|---|:---:|:---:|:---:|
| 不带历史 | 2 / 6 | 0 / 6 | 0 / 2 |
| 带全部历史 | 5 / 6 | 4 / 6 | 0 / 2 |

  事实题 2/6 涨到 5/6,指代 0/6 涨到 4/6——历史把这两类治好了。
  ! 但「两数取新」那一栏两次都是 0。
  历史里同时有「二十万」和「二十五万」时,模型取的是最早出现的那个。给更多历史,不一定更好。

  ③ 错题归成四类
  --------------------------------------------------------------------
| 类别 | 不带历史 | 带历史 | 例子 |
|---|:---:|:---:|---|
| 指代悬空 | 6 | 0 | q03、q04 |
| 上下文缺失 | 3 | 0 | q01、q02 |
| 前后矛盾 | 0 | 2 | q07、q11 |
| 格式不符 | 1 | 1 | q10 |

  十二道题:不带历史错 10 道,带全部历史错 3 道。错的类别整个换了一批。
  ! 归类的意义是决定改哪里:指代悬空要补多轮数据,上下文缺失要接槽位,
  前后矛盾要在提示里写「以最新为准」。如果只记「错了 3 道」,你没有任何下一步。

  ④ 补三条,重跑同一套题
  --------------------------------------------------------------------
| 指标 | 改进前 | 改进后 |
|---|:---:|:---:|
| 事实正确 | 5 / 6 | 6 / 6 |
| 指代解出 | 4 / 6 | 6 / 6 |
| 两数取新 | 0 / 2 | 2 / 2 |

  ! 这里的提升是脚本里补了三条规则换来的,不是模型学会了。
  演示的是闭环:错题 → 归类 → 补数据(或补规则)→ 重跑同一套题 → 看哪一列动了。
  真机上这一步是重新训一轮,慢得多,但流程一模一样。

  ⑤ 持续改进要落成什么
  --------------------------------------------------------------------

  [流程约定 · 不是代码]
  | 1. 线上抽样:每天抽 N 条真实对话
  | 2. 按四类归错:指代 / 漏取 / 矛盾 / 格式
  | 3. 每类补 5~20 条,进训练集或进提示
  | 4. 重跑固定题库,看哪一列动了
  | 5. 题库只增不改:改题库等于改尺子
  这一段是流程,不是代码,所以不涉及能不能跑。

  最后一条最要紧:题库一改,历史分数全部作废,你就再也说不清「这版比上版好在哪」。
  ! 所以题库要版本化,跟代码一起提交。「上个月多少分」这个问题,只有题库没动过才答得出。

①十二道题分四类:事实、指代、矛盾(还是事实题,只是历史里有两个数)。 带代词的共六道,没有历史就一定答不出——这不是模型的问题,是输入不全。 ②两张设置的对比很有信息量:事实 2/6 到 5/6,指代 0/6 到 4/6。但「两数取新」两遍都是零。 这一栏全是零,恰恰是最有价值的发现:「历史给得不够」和「历史给得太多」是两个不同的问题, 前者靠补历史解决,后者补历史只会更糟。 ③错题归类是这一节的方法核心。 不带历史错十道(指代六、缺上下文三、格式一),带历史错三道(矛盾二、格式一)——数量从十降到三, 但错的类别换了。 如果只记「错三道」,你没有任何下一步。归了类之后,三类对应三个动作,每一类都能当天开工。 ④补三条规则后重跑:事实 6/6、指代 6/6、两数取新 2/2。 这个提升是脚本补规则换来的,不是模型学会了——要诚实地说这一点,否则读者会以为「补数据」是魔法。 真机上这一步是重新训一轮。慢,但流程一样:错的样本进训练集,重训,重跑同一套题,看哪一列动了。 ⑤最后那张流程表里,第五条最要紧:题库只增不改。改了题库, 你就没法回答「这个月比上个月好多少」——那是最常被问到的一个问题。

30 秒实验

  1. 加一道题:问「刚才说的防护等级是多少」,但历史里 IP65 出现两次且一致。 看它会不会被误判成「两数打架」。
  2. 把「两数取新」的口径改成「两个都答出来算对」,重跑。分数会变吗?这说明口径怎么影响结论。
  3. 把 FACT 的定义改成「关键词对且不超过六十字」,重跑。哪一版的标准更接近业务要求?

常见误区

  1. 只报一个总分。 多轮评测里总分最没用——它把「指代悬空」和「格式不符」平均成一个数,而这两类的修法完全不同。
  2. 用同一段历史当训练数据和测试题。 这类污染在多轮数据里特别容易发生,因为对话都是同事写的,主题高度重合。
  3. 把改口径当改进。放宽字数、放宽格式,分数立刻好看,模型一点没变。 口径改动要单独记账,否则三个月后没人说得清分数是怎么涨的。
自测三问(点开对答案) 1. 为什么「两数取新」这一栏带历史反而更差? —— 因为历史里同时存在两个数,模型取到了最早的那个。 历史越长,这种「有两个候选」的情况越多,所以补历史解决不了它——得在提示里明确「以最新为准」, 或者直接查槽位。 2. 错题归类的意义是什么? —— 把分数变成动作。 指代悬空要补多轮数据,上下文缺失要接槽位,矛盾要改提示——三类三个动作,都能当天开工。 只记「错了几道」,读完不知道该干什么。 3. 为什么题库不许改? —— 因为题库是尺子。尺子一换,历史分数全部作废,你就无法回答「这版比上版好在哪」。 新题只能加,不能改也不能删。

评测说完了,这一章还剩最后一节:把上面所有结论落到一个具体的字符串上——prompt 怎么拼。

相关笔记