大词表语言模型在续写任务上的一个问题及对策
By 苏剑林 | 2023-09-13 | 29411位读者 |对于LLM来说,通过增大Tokenizer的词表来提高压缩率,从而缩短序列长度、降低解码成本,是大家都喜闻乐见的事情。毕竟增大词表只需要增大Embedding层和输出的Dense层,这部分增加的计算量几乎不可感知,但缩短序列长度之后带来的解码速度提升却是实打实的。当然,增加词表大小也可能会对模型效果带来一些负面影响,所以也不能无节制地增加词表大小。本文就来分析增大词表后语言模型在续写任务上会出现的一个问题,并提出参考的解决方案。
优劣分析 #
增加词表大小的好处是显而易见的。一方面,由于LLM是自回归的,它的解码会越来越慢,而“增大词表 → 提高压缩率 → 缩短序列长度”,换言之相同文本对应的tokens数变少了,也就是解码步数变少了,从而解码速度提升了;另一方面,语言模型的训练方式是Teacher Forcing,缩短序列长度能够缓解Teacher Forcing带来的Exposure Bias问题,从而可能提升模型效果。
不过增大词表的缺点也很明显,最直接的就是会割裂token与token之间在字符层面之间的联系,从而可能会影响泛化,甚至会损失做某些任务的能力。比如“太阳能”和“太阳”都是词表中的一个词的话,模型是不知道“太阳能”是由“太阳”和“能”组成,也不知道“太阳”是“太”和“阳”,这样如果要做一些子词相关的任务就会比较艰难,比如最经典的问“‘太阳能’反过来怎么读?”,期望回答时“能阳太”,但由于模型不知道它是“太”、“阳”、“能”三个字组成,从而很难回答正确。
续写问题 #
近日 @Armen Aghajanyan 分享了另一个问题。他们在训练代码模型时使用了超大词表,结果就是常见的命令如“import numpy as np”都变成了一个token,然后发现当用户输入“import numpy”时,模型无法续写出“ as np”。原因很简单,“import numpy as np”被当作了一个token,于是当“import numpy”单独出现时,模型会发现它后面永远不会接“ as np”(接“ as np”的都被合并成单独的“import numpy as np”了),自然也无法完成续写。
这个现象确实很经典,其实不单是代码模型,常见的自然语言模型也会出现。比如当“太阳能”和“太阳”都成为了一个独立的token时,用户输入“太阳”后,接下来续写的字就基本不会是“能”了,这可能不符合用户的分布期望;又比如“白云”、“白云山”、“白云机场”都是一个独立的token时,用户输入“广州的白云”后,接下来也几乎不会续写出“广州的白云机场”、“广州的白云山”,等等。
参考对策 #
然而,笔者认为 Armen Aghajanyan 所提的现象,并不能构成增大词表的缺点,反而稍微处理一下之后,它还有可能成为增大词表的优点。其实这个问题很简单,以前没有LLM的时候,基于“词表+前缀搜索”我们也能做一定的补全任务,现在有了LLM,难道我们就一定要囿于LLM,不能将基于LLM的续写和基于词表的续写结合起来吗?
还是刚才的例子,假设用户输入了“广州的白云”,Tokenizer将它分为“广州/的/白云”,现在如果将这三个词直接转为id输入到模型中,就会无法续写出“广州/的/白云机场”等结果。这本质上是因为Tokenizer无法提前预估未来的文本,从而导致分词结果出错(当然,也可以考虑在训练阶段就使用带有随机性的tokenize算法,这种情况下“白云机场”可能作为一个词出现,也可能作为“白云/机场”出现,此时分词结果不至于严重影响后续效果,甚至能增强泛化能力,参考《Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates》)。
那么,我们是否可以预估一下未来的文本呢?假设分词为“广州/的/白云”后,我们回退一步,拿“白云”去词表做前缀搜索,不妨再假设搜索结果为“白云”、“白云机场”、“白云山”、“白云路”四个词,这步搜索是纯粹基于词表做的,相比LLM的计算量可以忽略不计。有了搜索结果后,我们用LLM计算:
\begin{equation}\begin{aligned}
p(\text{白云}|\text{广州},\text{的}) \\p(\text{白云机场}|\text{广州},\text{的}) \\
p(\text{白云山}|\text{广州},\text{的}) \\
p(\text{白云路}|\text{广州},\text{的}) \\
\end{aligned}\end{equation}
由于输入都是相同的,所以计算这四个条件概率只需要运行一次LLM。有了这四个条件概率后,我们将它们重新归一化然后进行采样。假如采样结果是“白云”,那么我们就按照“广州/的/白云”来做续写;如果采样到“白云机场”,那么就可以输出“机场”,并按照“广州/的/白云机场”来做续写;依此类推。这就轻松解决了Armen Aghajanyan所提到的问题,并且将缺点转化为优点了(压缩率高时,即便回退了一步,但是前缀搜索出来的词可能很长,可以一次性生成更多的字)。特别地,回退操作只需要在采样第一步进行,它只是为了避免输入不完整导致的分词错误,从第二步开始就不需要回退操作了,因此新增的计算量是非常少的。
值得一提的是,微软有一个名为“guidance”的库,也提出了同样的技巧(参考这里)。此外,考虑更一般的场景,有时候回退一步也不够,比如“import numpy as np”的例子,单输入“import numpy”时,可能被分为“import/ numpy”了,这时候起码要回退两步才能完整合理的序列。但这没有本质的区别,只是细节上稍微复杂一些,这里就不展开了,读者部署推理模型的时候自行构造就好。
文章小结 #
本文介绍了超大词表的LLM在做文本续写任务时可能出现的一个问题,并分享了参考的解决方案。
转载到请包括本文地址:https://kexue.fm/archives/9762
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Sep. 13, 2023). 《大词表语言模型在续写任务上的一个问题及对策 》[Blog post]. Retrieved from https://kexue.fm/archives/9762
@online{kexuefm-9762,
title={大词表语言模型在续写任务上的一个问题及对策},
author={苏剑林},
year={2023},
month={Sep},
url={\url{https://kexue.fm/archives/9762}},
}
September 16th, 2023
[...]上一篇文章《大词表语言模型在续写任务上的一个问题及对策》发布后,很快就有读者指出可以在训练阶段引入带有随机性的分词结果来解决同样的问题,并且已经有论文和实现。经过进一步查阅学习,笔者发现这是一个名为Subword Regularization的技巧,最早应用在NMT(机器翻译)中,目前SentencePiece也有相应的实现。看起来这个技巧确实能缓解前述问题,甚至有助于增强语言模型的容错能力,所以[...]
September 16th, 2023
请问博主为什么需要将它们重新归一化然后进行采样,而不是直接输出概率最高的词?
因为LLM的解码通常是随机采样的形式呀,你要确定性解码,那就输出概率最高的词呗。
September 19th, 2023
感谢分享!
我在chatgpt上测试了下
prompt 为:
'''
我要书写的完整的内容是“number=map['number']”
我目前已经写的内容是"{}" 后边需要补全的内容是?
'''
{}中分别测试了n,nu,numb,numbe ...等等一大堆从token中间截断的情况,但chatgpt都能准确补全。bpe的分词应该是达不到这种效果的,请问博主知道这是如何实现的吗
这种已经变成了QA了,不是续写,所以只要模型够强,都可以做到。
September 27th, 2023
从n,nu,numb,numbe ..等任意prefix, 都能稳定补全到 “number=map['number']”,一样会有token分割的问题吧。例如它是怎么判断 number这个token可以由 n+umber、 nu + mber、numb + er、numbe +r 等任意方式组成的,训练的时候应该也不会把所有拆分方式都加入数据集吧。
chatgpt的效果是:我输入n,它回答 umber ;我输入nu,它回答mber;我输入numbe,它回答r。
特意挑各种从token中间切断的情况,都能回答正确。
我其实也比较好奇,这个问题和较早之前的单词反转一类的问题本质上是一样的,LLM基于sub-word粒度,按理说character模型是感知不到的,但现在ChatGPT的单词反转已经做得很好了,不知道是怎么泛化的,有没有可能预训练阶段模型自己学习到了单词的字母组成这种隐式的东西(毕竟更复杂的模式模型都能捕捉到)
只要训练的语料足够多,那么一切皆有可能(当然也可能加上了random tokenize:https://kexue.fm/archives/9811 )。
它怎么实现泛化是它的事,我想表达的是只要你用了chatgpt来做这件事,不管你怎么构建prompt,它都是一个QA而不是续写,所以它理论上能完成这件事(理论上,怎么实现的我不知道)。
而本文探究的是续写问题表面上的一个“bug”,如果不做相应的策略,那么它是理论上不能完成这件事,这就是它们的区别。
November 10th, 2023
和投机采样的思想是一致的,这里的前缀搜索就是把小模型Mq“小”到了极致
好有道理!感谢指点。这样一代入感觉就高大上了。
发现了一篇新文章:https://arxiv.org/abs/2311.08252 ,算是这个对策在Speculative Decoding框架了的一般推广了