自然语言处理

先把所有符号定义清楚

我们先把所有要用到的符号列出来,避免混淆:

符号 含义
\(V\) 词典的大小,也就是我们有多少个不同的词
\(X_{ij}\) 共现次数:词 i 作为主词,词 j 作为上下文,一起出现的次数
\(X_i = \sum_{k=1}^V X_{ik}\) 词 i 作为主词,所有上下文的总出现次数
\(P\_\{ik\} = P\(k|i\) = \\frac\{X\_\{ik\}\}\{X\_i\}\) 共现概率:给定主词 i,上下文是 k 的概率
\(w_i \in \mathbb{R}^d\) 主词 i 的词向量,d 是向量的维度
\(\tilde{w}_k \in \mathbb{R}^d\) 上下文 k 的词向量
\(b_i, \tilde{b}_k\) 偏置项,用来调平常数项

第一部分:核心直觉,我们要解决什么问题?

我们的核心观察是:两个词的语义区别,能通过它们和其他词的共现概率的比值,体现出来

举个论文里的经典例子:
我们有两个词,i=ice(冰),j=steam(蒸汽),然后我们看它们和其他词 k 的共现概率:

  • 当 k=solid(固体)的时候,\(P(k|i)\)很高,\(P(k|j)\)很低,所以比值\(\frac{P_{ik}}{P_{jk}}\)很大

  • 当 k=gas(气体)的时候,\(P(k|i)\)很低,\(P(k|j)\)很高,所以比值很小

  • 当 k=water(水)的时候,两个概率都很高,比值接近 1

  • 当 k=fashion(时尚)的时候,两个概率都很低,比值也接近 1

你看,这个比值,正好能把 "冰和蒸汽的区别" 给体现出来:和它们俩都相关的词,比值接近 1;和其中一个相关的词,比值偏离 1。

所以我们的目标就是:用我们的词向量,来拟合这个比值。


公式 (1):通用模型的起点

我们先写一个通用的模型:
\(F\left(w_{i}, w_{j}, \tilde{w}_{k}\right)=\frac{P_{i k}}{P_{j k}}\)
左边的 F 是我们要找的函数,输入是三个词的向量,输出是我们要拟合的共现概率的比值。

但是这个函数太泛了,我们要给它加限制,让它满足我们的需求。


公式 (2):我们要的是两个词的区别

我们想要的,是 i 和 j 这两个词的区别,对吧?因为比值本身,就是在对比 i 和 j 的区别,所以,F 的输入,不应该是三个独立的向量,而应该是i 和 j 的差,这样才能体现 "区别":
\(F\left(w_{i}-w_{j}, \tilde{w}_{k}\right)=\frac{P_{i k}}{P_{j k}}\)
这样,F 的输入,就变成了 "两个主词的差",和 "上下文的向量",正好对应我们要的:用两个主词的区别,来拟合它们的共现概率的区别。


公式 (3):把向量变成标量,用点积

现在,F 的输入是两个向量:\((w_i - w_j)\)\(\tilde{w}_k\),但是我们的输出是一个标量(概率的比值),所以我们需要把两个向量,变成一个标量,最自然的方式就是点积
\(F\left(\left(w_{i}-w_{j}\right)^{T} \tilde{w}_{k}\right)=\frac{P_{i k}}{P_{j k}}\)
点积的作用,就是把两个向量的相似性,变成一个标量,正好符合我们的需求。


公式 (4):为了对称性,我们给 F 加了一个限制

现在我们遇到了一个问题:主词和上下文,本来就没有本质区别
共现矩阵是对称的,\(X_{ik}=X_{ki}\),也就是说,i 是主词 k 是上下文,和 k 是主词 i 是上下文,是完全一样的,所以我们的模型,必须满足:把\(w\)\(\tilde{w}\)互换,把 i 和 k 互换,模型应该完全不变。

但是我们现在的 F,还不满足这个性质,所以我们要给 F 加一个限制条件,让它满足这个对称性。

我们先做个变量替换:
\(a = w_i^T \tilde{w}_k\)\(b = w_j^T \tilde{w}_k\)
那你看,F 的输入,是不是就是\(a - b\)?因为:
\((w_i - w_j)^T \tilde{w}_k = w_i^T \tilde{w}_k - w_j^T \tilde{w}_k = a - b\)

我们想要 F 满足什么呢?我们想要:
\(F(a - b) = \frac{F(a)}{F(b)}\)
为什么?因为只有这样,我们的模型才能满足对称性:
左边的 F 对差的处理,等于 F 各自处理之后再做除法,正好和我们右边的概率比值的形式完全匹配,而且,这个性质,能保证主词和上下文互换之后,整个公式依然成立。

把 a 和 b 代回去,就得到了你之前问的这个公式:
\(F\left(\left(w_{i}-w_{j}\right)^{T} \tilde{w}_{k}\right)=\frac{F\left(w_{i}^{T} \tilde{w}_{k}\right)}{F\left(w_{j}^{T} \tilde{w}_{k}\right)}\)
这就是这个公式的由来,它不是推导出来的,是我们为了满足对称性,主动给 F 加的限制条件!


解函数方程:F 到底是什么?

现在我们有了 F 的限制条件:
\(F(a - b) = \frac{F(a)}{F(b)}\)
我们来解这个函数方程,看看 F 到底是什么函数。

首先,两边取自然对数:
\(\ln F(a - b) = \ln F(a) - \ln F(b)\)

我们令 \(g(x) = \ln F(x)\),那上面的式子就变成了:
\(g(a - b) = g(a) - g(b)\)

这就是经典的柯西函数方程,它的解是:
\(g(x) = c x\)
其中 c 是一个常数,也就是说,g 是一个线性函数!

那我们代回去,就能得到 F 的形式:
\(\ln F(x) = c x \implies F(x) = e^{c x}\)
哦!原来 F 就是指数函数!


公式 (5):核心公式的推导

现在我们把 F 代回到我们的公式 (3) 里:
\(e^{c \cdot (w_i^T \tilde{w}_k)} = P_{ik} = \frac{X_{ik}}{X_i}\)

我们可以把常数 c 吸收到词向量里(因为词向量是我们要训练的参数,我们可以直接把 c 乘到向量上,不影响结果),所以我们可以直接把 c 当成 1,简化公式:
\(e^{w_i^T \tilde{w}_k} = \frac{X_{ik}}{X_i}\)

两边取对数,把指数去掉:
\(w_i^T \tilde{w}_k = \ln X_{ik} - \ln X_i\)

现在我们发现,右边的\(\ln X_i\),是一个只和 i 有关的常数,和 k 没有关系,我们可以把它拆成两个偏置项:

  • 一个是主词 i 的偏置\(b_i\),用来吸收\(\ln X_i\)

  • 一个是上下文 k 的偏置\(\tilde{b}_k\),用来调平 k 自己的常数项

这样我们就得到了 GloVe 的核心公式:
\(w_{i}^{T} \tilde{w}_{k}+b_{i}+\tilde{b}_{k}=\log \left(X_{i k}\right)\)
这就是我们最终要拟合的目标:左边是我们的词向量和偏置,右边是我们统计出来的共现次数的对数。


第二部分:损失函数,我们怎么训练?

我们的目标,就是让左边尽量等于右边,那我们怎么衡量这个差距呢?我们用最小二乘损失,也就是最小化误差的平方。

但是我们发现,不同的词对,可信度是不一样的:

  • 出现次数很少的词对,噪声很大,比如两个生僻词偶然一起出现了一次,我们不能太相信它

  • 出现次数很多的词对,到了一定程度,信息就饱和了,再增加出现次数,也不会带来更多的信息了

所以我们给不同的词对,加了不同的权重,出现次数少的,权重小一点,出现次数多的,权重大一点,到了一定程度,权重就不变了。


公式 (8):最终的损失函数

所以我们的损失函数就是:
\(J=\sum_{i, j=1}^{V} f\left(X_{i j}\right)\left(w_{i}^{T} \tilde{w}_{j}+b_{i}+\tilde{b}_{j}-\log X_{i j}\right)^{2}\)
其中\(f(X_{ij})\)就是我们的权重函数,给不同的词对加不同的权重。


公式 (9):权重函数

权重函数的形式是:
\(f(x)=\left{\begin{array}{cc} \left(x / x_{max }\right)^{\alpha} & if x<x_{max } \\ 1 & otherwise . \end{array}\right.\)
论文里实验发现,当\(x_{max}=100\)\(\alpha=0.75\)的时候,效果最好。

这个函数的意思是:

  • 当 x 比较小的时候,权重随 x 增长,出现次数越多,权重越大

  • 当 x 超过 100 之后,权重就变成 1 了,不再增长,避免那些特别常见的词对,把整个训练带偏


第三部分:复杂度分析,GloVe 到底有多快?

最后,论文里分析了 GloVe 的计算复杂度,证明了它的计算量特别小,我们来推导一下。


公式 (21):词对的幂律分布

我们之前说过,自然语言的词对,符合幂律分布:
\(X_{i j}=\frac{k}{\left(r_{i j}\right)^{\alpha}}\)
这里的\(r_{ij}\)是词对的排名:最常见的词对,r=1,第二常见的,r=2,以此类推。

这个公式的意思是:

  • 排名越靠前的词对(越常见),出现次数越多

  • 排名越靠后的词对(越生僻),出现次数越少

而且,这里有个很容易搞混的点:

出现次数越少的词对(生僻的),这种词对的数量,越多
反过来,出现次数越多的词对(常见的),这种词对的数量,越少
就像工资一样:低收入的普通人最多,高收入的有钱人很少,一模一样的道理。


公式 (22):复杂度的结果

现在我们来算,我们要处理的非零词对的数量\(|X|\),和总词数\(|C|\)的关系。

总词数,就是所有词对的出现次数的和:
\(|C| = \sum_{r=1}^{|X|} X_r = \sum_{r=1}^{|X|} \frac{k}{r^\alpha}\)
这是一个幂级数的和,我们来分析它的渐近行为:

  1. 当 α &lt; 1 的时候
    这个幂级数是发散的,它的增长速度是\(|X|^{1-\alpha}\),也就是说,总词数和非零词对的数量,是线性增长的,所以:
    \(|X| = O(|C|)\)

  2. 当 α &gt; 1 的时候
    这个幂级数是收敛的,它的和会趋近于一个常数,也就是说,总词数的增长,比非零词对的增长快很多,所以:
    \(|X| = O(|C|^{1/\alpha})\)

所以我们就得到了最终的复杂度公式:
\(|X|=\left{\begin{array}{cl} O(|C|) &{if \alpha<1,} \\ O\left(|C|^{1 / \alpha}\right) &{if \alpha>1 .} \end{array}\right.\)

这个公式告诉我们:不管是哪种情况,GloVe 的计算量,都比你想象的小太多了
正常的自然语言,α 大概是 1.4,所以我们的计算量,大概是\(O(|C|^{0.7})\),总词数翻 10 倍,我们的工作量只翻 5 倍,特别的快,哪怕是处理几十亿词的超大语料,也能轻松搞定。


posted @ 2026-05-10 19:50  cyyyyyyyyyyyyy  阅读(16)  评论(0)    收藏  举报