关于NBCE方法的一些补充说明和分析
By 苏剑林 | 2023-05-31 | 29946位读者 |上周在《NBCE:使用朴素贝叶斯扩展LLM的Context处理长度》中,我们介绍了一种基于朴素贝叶斯来扩展LLM的Context长度的方案NBCE(Naive Bayes-based Context Extension)。由于它有着即插即用、模型无关、不用微调等优点,也获得了一些读者的认可,总的来说目前大家反馈的测试效果还算可以。
当然,部分读者在使用的时候也提出了一些问题。本文就结合读者的疑问和笔者的后续思考,对NBCE方法做一些补充说明和分析。
方法回顾 #
假设T为要生成的token序列,S1,S2,⋯,Sn是给定的若干个Context,我们需要根据S1,S2,⋯,Sn生成T,那么就需要估计p(T|S1,S2,⋯,Sn)。根据朴素贝叶斯思想,我们得到
logp(T|S1,S2,⋯,Sn)=(β+1)¯logp(T|S)−βlogp(T)+常数
其中β=n−1,¯logp(T|S)=1nn∑k=1logp(T|Sk),细节请参考上一篇文章。NBCE做了两点改动:1、将β作为超参数来调;2、将¯logp(T|S)换成一般的Pooling方法P。结果变为
logp(T|S1,S2,⋯,Sn)=(β+1)P[logp(T|S)]−βlogp(T)+常数
最后,NBCE选取的Pooling方案为“取最小熵者”:
P[logp(T|S)]=logp(T|Sk)k=argmin{H1,H2,⋯,Hn}Hi=−∑Tp(T|Si)logp(T|Si)
截断预测 #
式(1)是标准的朴素贝叶斯结果,但笔者按照它实现时,发现随着n的增大,式(1)的效果慢慢变差直至完全乱码,所以反复调整后,最终选取“取最小熵者”作为NBCE的Pooling方案。但后来仔细一想,式(1)的这个表现是不正常的,因为朴素贝叶斯的唯一假设是Context之间相互独立,而我所测试的Context是随机找的若干篇新闻,一定程度上满足这个假设的,所以式(1)再怎么差也不至于完全乱码才对。
百思不得其解之际,微信上@孔某人提醒:语言模型的训练标签都是One-Hot,所以预测结果除了头部(概率最高的部分)外其实都是不可信的。这个提示可谓是一针见血,让答案瞬间水落石出了:由于式(1)包含了−βlogp(T)这一项,它会放大尾部的预测结果,如果尾部的预测结果不可靠,那么这个放大作用甚至会完全打乱头部的准确结果。为什么它不会影响“取最小熵者”呢?因为最小熵的结果头部概率往往更大,尾部概率更小,所以即便−βlogp(T)这一项放大了尾部,也依然无法胜过头部;而对于式(1)来说,它是所有预测结果的平均,则会弱化头部,以至于加上−βlogp(T)后尾部胜过了头部。
有了这个线索,解决方法也就很明显了,给每个预测结果都加上Top-P或者Top-K截断就行,在Github的代码中,笔者选的是Top-P截断。
无穷处理 #
然而,事情还没完,经过截断后,logp(T|Sk)和logp(T)的尾部都变成了−∞,这时候式(1)或式(2)都有可能出现(−∞)−(−∞)的无意义运算。总的来说,有下面几种情况:
logp(T|Sk)logp(T)logp(T|Sk)−logp(T)情况一>−∞>−∞>−∞情况二>−∞=−∞=+∞情况三=−∞>−∞=−∞情况四=−∞=−∞NaN
其中,“情况一”和“情况三”能正常运算,“情况二”虽然也能正常运算,但其结果正无穷是不合理的,“情况四”则是非良定的无意义运算,也就是说,我们需要想办法修正“情况二”和“情况四”,这两种情况正好对应logp(T)=−∞,所以我们将式(2)改为:
logp(T|S1,S2,⋯,Sn)={P[logp(T|S)],如果logp(T)=−∞(β+1)P[logp(T|S)]−βlogp(T),其他}+常数
经过上述处理后,标准的朴素贝叶斯式(1)也能输出正常结果了(虽然最终效果还是不如取最小熵,但至少不会乱码了),并且修改后的代码对Pooling方式和β都更加鲁棒。
转移概率 #
当用于回答一些观点型问题或者偏向自由创作的问题时,NBCE可能会出现在Context中反复跳转的问题。具体来说,就是模型并没有自信地注意到某个Context中,所以H1,H2,⋯,Hn之间相差不大,于是式(3)中的argmin结果不稳定,每步生成都选取到了不同的Context上,导致生成结果语义上的不连续甚至完全和Context不相关,加剧LLM的“幻觉”现象。
为了缓解这个问题,我们可以模仿转移概率的概念,适当给上一步所选的Context加权,让模型“非必要不跳转”,具体做法是引入参数η>0,将式(3)改为
k=argmin{H1,⋯,Hk′−1,Hk′−η,Hk′+1,⋯,Hn}
其中k′是上一步生成时选取的Context编号。这样一来,只有Hk<Hk′−η时才会发生Context跳转,降低了跳转概率。
以上提到的修改都已经同步到Github中:
Github: https://github.com/bojone/NBCE
适用场景 #
由于朴素贝叶斯所做的独立假设,不少读者可能会怀疑:当Context之间有明显的语义重叠时,NBCE的效果会不会明显下降?或者说,NBCE的适用场景是什么?
事实上,受限于独立假设的是标准的朴素贝叶斯,也就是式(1),一般化后的式(2)和式(3)已经基本上不受限于独立假设了。事实上,“取最小熵者”版本的NBCE实质就是用LLM的熵作为相似度对Context进行检索,每步生成都会更新检索结果。所以,NBCE的适用场景是:假设要预测的答案可以分为若干个片段,每个片段只依赖于一个Context。
基于这个结论,当我们只有一段长文本作为Context(比如一篇小说)时,我们可以通过有重叠的滑窗自动将长Context划分为多个短Context,而不一定需要人工分割为相对独立的片段,因为刚才的结论告诉我们,NBCE的适用场景跟Context的重叠性无关。至于为什么重叠地滑窗,则只是为了能尽可能依赖单个Context就能输出完整的结果。
以下两种场景用NBCE效果大概率是不会好的:
1、有序的Context:这是指当生成结果强烈依赖于Context的输入顺序(或者更复杂的嵌套结构)时,NBCE通常效果不好,因为NBCE保留了朴素贝叶斯的无序性,这种场景的典型例子是给小说写摘要(小说被切割为多个Context),一个治标不治本的方案是人工给每段Context加上可以识别顺序的标记,如“第xx章”;
2、耦合的Context:这是指必须要结合两个或更多Context构建输出时,NBCE效果不好,因为NBCE每次只选了一个Context,@孔某人给出了一个典型例子“已知x>1,且x<0,求x解集”,假设两个条件被分为两个Context,那么必须结合两个Context才能输出正确答案“空集”,只看单个Context无法确定是空集。
如果NBCE还要继续做下去的话,大体上也是围绕以上两个场景进行改进。
文章小结 #
本文介绍了Context长度扩展方案NBCE的一些后续更新及分析,并进一步讨论了NBCE的适用场景。
转载到请包括本文地址:https://kexue.fm/archives/9632
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (May. 31, 2023). 《关于NBCE方法的一些补充说明和分析 》[Blog post]. Retrieved from https://kexue.fm/archives/9632
@online{kexuefm-9632,
title={关于NBCE方法的一些补充说明和分析},
author={苏剑林},
year={2023},
month={May},
url={\url{https://kexue.fm/archives/9632}},
}
May 31st, 2023
感谢苏神的分享,我有两个问题想请教。
1. 这篇文章提到了由于尾部不可信导致的在p(T)部分产生的干扰,那么这种不可信有没有可能类似地干扰求均值的部分,即¯logp(T|S)?个人认为这种尾部的不确定性或许会在这个均值当中更加突出,因为头部只是对部分的context是头部。
2. NBCE的思路是所有的Si都是独立的,但是实际中可能会有一个综合的promptˆS,比如说输入了十个文段,然后加一个prompt让模型选出最符合我要求的一段。那么苏神有没有考虑对这种情况拓展NBCE?个人直观的思考是这时候的ˆS会跟Si之间存在依赖关系,比如不可能给十段简历让模型给我生成一段菜谱。不过相较于苏神提到的耦合的情况,这个应该算是一种可以处理的特例。
1、这个可能性是有的,所以我对logp(T|Si)和logp(T)都做了截断,只不过相对来说,logp(T)的负面影响应该更大;
2、朴素贝叶斯是假设所有Si相互独立,但是最终的NBCE应该已经弱化了这个假设。你说的prompt,我是将它放在T里边的,即T实际上包含了prompt和要生成的结果。
这一点看代码 https://github.com/bojone/NBCE/blob/main/test.py 或者 上一篇博客的示意图都很清楚。
好的好的,感谢苏神
May 31st, 2023
感谢苏老师的分享。如果输入内容虽然是嵌套结构,但是我们能确定输出内容也是嵌套结构结构,并且与输入内容一一对应,比如代码翻译任务。S1 { S2 { S3 } } } => T1 { T2 { T3 } }, 能不能先将输入转换为 S1 { d1 } , S2 { d2 }, S3 { d3 } , 并且d1,d2,d3可以表示成能够容忍的偏差,在这些条件下,应用NBCE解决Content过长的问题。
看上去无法解决问题,看不出如何能保证模型生成对应的嵌套结构。
事实上序结构应该是除无序外最简单的结构了,序结构都解决不了的话,我对更复杂的嵌套结构不抱希望。
June 4th, 2023
[...]Read More [...]
June 13th, 2023
[...]苏剑林. (May. 31, 2023). 《关于NBCE方法的一些补充说明和分析 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/9632[...]
June 14th, 2023
[...]苏剑林. (May. 31, 2023). 《关于NBCE方法的一些补充说明和分析 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/9632[...]
March 25th, 2024
请教一下,"NBCE的适用场景是:假设要预测的答案可以分为若干个片段,每个片段只依赖于一个Context。"
那么NBCE相比"直接对每个短Context+问题(如果没有答案则返回无)分别输入llm" 这个方式来说,岂不是一样了?仅仅是帮我选择了一个Top1最佳答案?
假设同时问多个问题的情况下,如果你是指“‘单个context + 多个问题’这样逐一让模型回答,最后再让模型整合”的pipline做法,我只能说理论上也可以,但pipline的形式可能不大优雅,并且少了一个context之间相互比较,可能效果没那么好(比如有些问题模型可能根据不正确的context强行回答)。