语言模型
定位
语言模型(Language Model)是自然语言处理中的一个方向,其基本的功能是根据上文内容推断下一个 word/token 的概率分布。这里简单列出经典方法,算是开坑。
NLP 除了语言模型还有很多其他内容,比如语法分析之类的。这里只讲一部分。
PS:现在还没有完善的代码和数学公式,只是大致概括主要思想。
n-word
这是一个集成了马尔可夫和贝叶斯两人思想的方法。把文字序列 \(\{w_n\}\) 视为马尔可夫状物,即下一个 token 只与前 \(k\) 个 token 相关,这样容易得到:
\[ P\{w_n \mid \bigcap_{i=1}^k w_{n-i}\} = \frac{P\{\bigcap_{i=0}^k w_{n-i}\}}{P\{\bigcap_{i=1}^k w_{n-i}\}} \]
根据你拥有的数据量和希望的精准程度来统计各个概率。根据拉普拉斯接续准则,一种计算方法如下:设单词共有 \(N\) 种,用 \(C\{x\}\) 表示某物的出现次数,可以估计其概率为 \(\frac{C\{\bigcap_{i=0}^k w_{n-i}\} +1}{C\{\bigcap_{i=1}^k w_{n-i}\} + N}\)。
Embedding
从这里开始,一个重要的思想被广泛应用:嵌入(Embedding)。在之前的模型里,我们只是给单词编号,但这个编号并没有什么意义,不适合具有适应能力的模型学习。嵌入的想法是,把单词对应到高维向量。维度足够时,不同维度可以反映不同的词义,另外,可以通过向量点乘的办法知道两个单词的相近程度。
显然,向量的分配至关重要。如果只是静态地处理,常见的方法是统计 TF-IDF,即在收集的文章中各个单词的出现频率矩阵,用矩阵中的向量作为嵌入向量。这种方法弊端也显而易见,对于一词多义的情况表现不佳,并且只对一些实词有用。
后继的语言模型大多把 Embedding 作为一个部分,跟随模型一起学习。
另外,将单词出现的位置同样作为 Embedding 的一部分也是很常见的。
RNN
循环神经网络是一个很酷的想法,来自 Elman。
“循环”一词精准地指出了它前馈工作的方式:将上一次的输出作为下一次的输入。但是这个词也带来了一定的迷惑性,在反向传播时,我们是不是要返回去计算上一次的导数?答案是否定的。需要注意 RNN 的循环边其实是单向的,反向传播在遇到循环边时就停止了。
在完整的 RNN 中,处理 NLP 任务时,常常把 Embedding 作为一个层放在开头,把 Re-embedding 作为一个层放在结尾,一起训练。
Attention
注意力机制是一个有趣的设计,(很难想象它发迹这么晚)。
这个机制基于对 Embedding 的深度思考:既然向量代表单词,那么向量的加减组合是不是代表了新的意思?比如用“孔子”减去“春秋”再加上“战国”,得到“孟子”是不是有一定道理?在这个例子中,Embedding 的最大优势被体现:它可以传递出上下文对一个单词意思的影响。我们可以通过用一个向量加减另一个向量的方式来表示上下文含义。
但是单词这么多,怎么知道要如何加减呢?这就是注意力机制的一个关键:训练两个矩阵,不妨叫做 \(Q\) 和 \(K\)。对于两个单词 \(u,v\),我们用 \((Qu)^\text T(Kv)\) 这个量代表 \(u,v\) 之间的关系。你可以想象 \(Q\) 是一个询问矩阵,它把一个单词转化为一个询问向量,比如“形容词在哪呢?”;\(K\) 是一个回答矩阵,转化出一个回答向量,比如“我是个形容词”。当这两个向量点乘较大时,说明 \(u,v\) 在上下文比较相关,也称为 \(u\) 注意到 \(v\)。
当然,看上去有魔法的 \(Q\),\(K\) 矩阵都是要训练出来的。
最后,当我们得到了单词之间的两两相关性时,就可以通过互相加减的方式更新词义。具体来说,对一个单词,首先把所有的相关系数做 Softmax,然后按照归一化结果加权更新。Attention 做了一个调整,更新时并不是直接加减,而是加一个线性变换后的结果。比如用 \(v\) 更新 \(u\),实际是 \(u \leftarrow u + Wv\)。
在实际训练中,为了防止 Softmax 梯度消失,可选择将相关系数除以嵌入空间维度的平方根,保证 Softmax 的输入不要太大。
另外值得一提的是掩码设计:为了确保前后文关系,在计算出相关性矩阵后,常把矩阵主对角线下方设为负无穷,再进行 Softmax。这样对角线下方为 0,保证了前面没法注意到后面。
此外,Attention 既可以是自己与自己的(比如文本续写场景),也可以是两个序列之间的(比如翻译),分别被称为 Self-Attention 和 Cross-Attention。视情况考虑是否需要掩码。例如翻译任务就无需掩码,因为不同语言语序可能不同。
Transformer
由 Google 在论文 Attention Is All You Need 上发表。此文提出,Attention 机制本身就可以构成很好的模型。具体来说,Google 设计了 Multihead Attention 机制,并行训练许多组不同的 \(Q,K,W\),最终将输出统一相加。
实现上有一个技巧,一般询问向量维度远小于单词的嵌入空间维度,所以 \(Q,K\) 都是高维到低维的映射,大小较小;但 \(W\) 是一个嵌入空间到自身的方阵,会很大很慢。文章提出,将 \(W\) 进行低秩分解为 \(W_{\downarrow}W_{\uparrow}^\text{T}\),大小与 \(Q,K\) 相同,然后将 \(W_{\downarrow}\) 放进单个 Attention 里,将几个 \(W_{\uparrow}\) 合并在一起,与所有 Attention 的输出结果的合并相乘,起到相同的加和效果。这样是一个完整的 Multihead Attention 层。
整个 Transformer 是由很多 Multihead Attention 和 FC 层叠加在一起。一层 Multihead Attention,一层全连接层。实际上,因为全连接层没法缩小,所以 Attention 只占参数量的 1/3,并不是 All You Need。
当然,开头有必不可少的 Embedding(包括位置)。
GPT
在 Google 发表著名的 Attention Is All You Need 之后,有很多关于 Transformer 的尝试。其中最成功的,是 OpenAI 开发的 GPT 框架,即 Generative Pre-Trained。它包含了两个思想,一个是预训练,一个是多功能。
预训练是指在 Transformer 屁股上接一个 Softmax 层,指定任务为预测下一个单词的概率分布,使用大段的语料,每次截取 token window 长度的语料输入进行预测,再根据语料的下一个词进行反向传播。 (这是一种无监督学习 unsupervised,我不太理解这个分类,因为语料中包含了答案)
这样预训练之后的 Transformer 性能较好,当然也需要很多的资源。网上能下载的模型指的就是这个 Transformer。
之后是 fine-tuning,是将一个或多个训练好的 Transformer 和一些简单的线性层组合起来,然后在实际的问题(如文本分类,内容相似度比较)上用完整的有标记的数据集进行训练,通常只需要训练几轮就可以。因为 Transformer 很好地提炼了文本内容,所以微调主要是训练线性层,就很简单了。
这个框架的大获成功就在于它支持各种任务,还只需要简单的微调。