自然语言处理中的单词含义表示

我们日常使用的语言,如中文、英文,称为 自然语言(natural language)。 自然语言处理(Natural Language Processing,NLP)则是一种让计算机理解人类语言的技术。现在很多知名的工具,比如:搜索引擎、机器翻译、ChatGPT 等,都是基于自然语言处理技术的应用。

文本分词

大多数情况下,NLP 处理的是由大量单词组成的文本。因此,这里面临的第一个问题是如何进行分词。

英文的分词比较简单,每个单词都是独立的,可以通过空、标点符号、正则表达式等进行分词。相比而言,中文的分词会显得比较复杂。对此,研究人员针对不同语言开发了各种不同的分词工具,比如:

  • 英文分词可以使用 NLTK、spaCy 等工具
  • 中文分词可以使用 jieba、THULAC 等工具

一些知名的预训练模型 BERT 和 GPT 各自拥有专属的分词器(Tokenizer),比如:WordPiece、Byte Pair Encoding(BPE)等。

单词含义

自然语言的灵活性非常强,同一个单词在不同的语境下,不同的上下文中具有不同的含义,这与编程语言截然不同。因此,理解单词的含义成为了 NLP 的核心问题之一,也是本文将重点讨论的话题。

在 NLP 技术的发展过程中,单词含义的表示方法主要经历了一下几种:

  • 基于同义词词典的方法
  • 基于计数的方法
  • 基于推理的方法

下面,我们分别来介绍这几种单词含义的表示方法。

同义词词典法

基于 同义词词典(Thesaurus)的方法的核心思路是:

  • 将含义相同或含义类似的单词归类到同一个组中
  • 同时定义单词之间细粒度的层级关系,比如:上位与下位的关系,整体与局部的关系。

如下所示的例子是一个含义组别中,各个单词之间的关系,使用图进行表示。其中,motor vehicle 和 car 是上位和下位的关系。

在 NLP 领域中,最著名的同义词词典是普林斯顿大学开发的 WordNet,其至今被应用于各种自然语言处理应用中。

缺陷

首先,同义词词典是一个人工标记的词典,制作词典会耗费巨大的人力成本。

其次,同义词必须要随着语言的发展而更新。一方面,语言会出现新的单词,比如:团购、众筹等;另一方面,语言的含义也会发生变化,比如:卧龙凤雏,其含义从往年的褒义词变成了如今的贬义词。

然而最致命的缺陷还是同义词词典 无法表示单词含义的微妙差异。各种语言中都存在单词含义相同,但是用法不同的情况,而这种细微的差别在同义词词典中是无法表示出来的。

计数法

计数法,也称为统计法,其核心思想是 基于语料库(Corpus)中的大量文本数据,自动且高效地提取单词的本质信息

语料库预处理

语料库本质上就是文本数据而已,比如:Wikipedia、Google News、文学作品集等。

在使用语料库之前,必须先进行预处理,预处理主要有以下几个步骤:

  • 对文本进行分词
  • 为每个单词创建唯一的 ID
  • 基于语料库创建(ID,单词)和(单词,ID)的映射表,便于后续进行查找

如下所示,我们对一个简单的语料库进行预处理,分别得到 id_to_wordword_to_id 两个映射表。

1
2
3
4
5
6
7
8
9
10
11
>>> text
'you say goodbye and i say hello .'

>>> words
['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']

>>> id_to_word
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6:'.'}

>>> word_to_id
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}

分布式表示

在 NLP 中,广泛使用 分布式表示(Distributed Representation)来表示单词含义。分布式表示将单词表示为 固定长度的向量,其采用密集向量表示,即向量的大多数元素是由非 0 实数表示的。比如,一个三维分布式表示 [0.21, -0.45, 0.83]

为什么使用向量来表示?因为对于向量,我们可以使用 余弦相似度(Cosine Similarity)来计算两个向量之间的夹角余弦值,余弦值越接近 1,表示向量夹角越小,说明相似度越高。使用向量表示单词,那么就可以计算不同单词含义之间的相似度。

假设,有两个向量 \(x = (x_1, x_2, x_3, ..., x_n)\)\(y = (y_1, y_2, y_3, ..., y_n)\),那么余弦相似度的定义如下。为了避免分母为 0,一般会给分母加上一个微小值 eps,默认其值为 0.00000001。

\[\begin{aligned} similarity(x, y) = \frac{x \cdot y}{ ||x|| \space ||y|| } = \frac{x_1 y_1 + ... + x_n y_n}{\sqrt{x_1^2 + ... + x_n^2} \sqrt{y_1^2 + ... + y_n^2}} \end{aligned}\]

分布式假设

在 NLP 中,很多研究都是基于 分布式假设(Distributional Hypothesis)完成的。分布式假设认为,一个单词的含义是由其周围的单词形成的。单词本身没有含义,其含义是由上下文(语境)形成的。相同的单词经常出现在相同的语境中,比如:"I drink beer." 和 "I drink wine.",drink 附近经常会出现饮料,beer 和 wine 则是含义相似的单词。

这里提到的“上下文”,指的是某个单词周围的单词。我们将上下文的大小称为 窗口大小(Window Size)。假如,窗口大小为 2,则表示上下文包含了目标单词前后各两个单词。如下所示,是一个窗口大小为 2 的示例。

词嵌入

词嵌入(Word Embedding)表示 将单词映射到向量中间的过程或方法,也可以认为是 基于分布式假设将单词转换成分布式表示的过程或方法

计数法的词嵌入,可以分为以下几个步骤:

  • 首先,进行语料库预处理,得到每个单词的 ID 以及两个映射表
  • 其次,定义每个单词的向量维度为语料库中不同单词的数量
  • 然后,定义上下文大小,即窗口大小
  • 最后,根据窗口大小,为每个单词统计其上下文的单词数量,并记录在向量中指定位置(各个单词 ID 指定的位置)

假设,我们以 “you say goodby and i say hello.” 作为语料库,定义窗口大小为 1。由于语料库中总共有 7 个不同的单词,包括句号,因此单词的向量维度等于 7。

首先,我们来看 "you" 的向量表示,其上下文只有一个单词 "say",那么我们可以将单词 "say" 的 ID 对应在向量中的位置进行计数,值为 1,如下所示。

然后,我们来看 “say” 的向量表示。语料库中 “say” 出现了两次,因此我们需要分别统计这两个 “say” 的上下文,并在向量对应的位置进行计数,如下所示。

当对语料库中所有单词进行了词嵌入的处理后,我们可以得到所有的单词的向量表示,它们共同构成了一个矩阵,我们称之为 共现矩阵(Co-ocurrence Matrix),如下所示。

优化

高频词汇问题

共现矩阵的元素表示每个单词与其上下文的单词共同出现的次数。这种表示方法对于高频词汇可能会出现问题。比如:the 和 car 的共现次数会非常大,甚至要远远大于 car 和 drive 的共现次数。对于这种情况,实际上我们不应该认为 the 和 car 具有很强的相关性,因为 the 仅仅是一个常用词。

为了解决这个问题,我们可以使用 点互信息(Pointwise Mutual Information,PMI)指标,其定义如下所示。

\[\begin{aligned} PMI(x, y) = log_2 \frac{P(x, y)}{P(x) P(y)} \end{aligned}\]

其中,\(P(x)\) 表示 x 发生的概率,\(P(y)\) 表示 y 发生的概率,\(P(x, y)\) 表示 x 和 y 同时发生的概率。PMI 的值越高,表明相关性越强。

假设语料库的单词数量为 10000,the 出现 100 次,car 出现 20 次,drive 出现 10 次,the 和 car 共出现 10 次,car 和 drive 共出现 5 次。那么,我们可以计算一下 PMI 值,如下所示。

\[\begin{aligned} PMI(the, car) = & log_2 \frac{10 \cdot 10000}{1000 \cdot 20} \approx 2.32 \\ PMI(car, drive) = & log_2 \frac{5 \cdot 10000}{20 \cdot 10} \approx 7.97 \end{aligned}\]

通过 PMI,我们可以解决高频词汇在共现矩阵中对于单词关联性的影响。不过,PMI也有一个问题,即当两个单词共现次数为 0 时,\(log_2 0 = -\infty\)。为了解决这问题,在实际中使用 正的点互信息(Positive PMI,PPMI)来处理,其本质就是使用最小值 0 来进行兜底,其定义如下所示。

\[\begin{aligned} PPMI(x, y) = max(0, PMI(x, y)) \end{aligned}\]

经过 PPMI 的处理,我们可以将共现矩阵转换成 PPMI 矩阵,如下所示。

向量维度问题

计数法还存在一个问题,那就是随着语料库词汇量的增加,各个单词向量的维数也会增加。如果语料库的词汇量达到 10 万,那么单词向量的维度也会达到 10 万。维度过高会导致处理计算量极大增加,而且向量中绝大多数元素都是 0,会造成存储空间浪费。对此,我们要对向量进行 降维(Dimensionality Reduction)。

降维的核心思想是 从稀疏矩阵中找到重要的轴,用更少的维度重新表示。比如下图所示,我们发现二维向量的分布具有某种特点,而这种特点可以使用一维向量来表示。

具体而言,我们可以使用 奇异值分解(Singular Value Decomposition,SVD)来进行降维。关于 SVD 的工作原理,要解释明白的话,涉及的篇幅会很大,这里不作具体阐述,有兴趣的朋友可以自行去了解。

推理法

基于推理的方法,其本质上是基于神经网络来学习单词的内在含义,最终使用权重来表示单词的含义。推理法使用部分学习数据逐步进行学习;计数法则使用整个语料库进行数据统计,一次性处理得到单词的分布式表示。两者之间的差异如下所示。

语料库预处理

类似于计数法,推理法也会对语料库进行预处理,主要有以下几个步骤:

  • 对文本进行分词
  • 为每个单词创建唯一的 ID
  • 为每个单词定义 one-hot 表示

什么是 one-hot 表示?one-hot 表示会定义一个固定长度的向量,该向量的长度等于语料库中词汇的数量。每个单词的 one-hot 表示中,只有其 ID 对应的元素为 1,其余元素均为 0。如下所示为 one-hot 表示的示意图。

分布式表示

推理法中单词的分布式表示是通过神经网络的权重来表示的。如下所示,输入层和中间层会构建一个全连接层,其中输入层的神经元数量等于 one-hot 表示的长度,也就是语料库的词汇数量,比如 7;中间层的神经元数量可以自定义,比如 3。

通过学习,我们最终可以得到一个 7 x 3 的权重矩阵。我们使用任意一个单词的 one-hot 表示与权重矩阵进行乘积,即可提取到对应单词的分布式表示,如下所示。

词嵌入

推理法的词嵌入方法很多,本质而言,这些方法都是基于不同的神经网络模型实现的,通常我们会直接用神经网络模型表示不同的方法,比如:word2vec、gloVe 等。这里我们主要介绍 word2vec 模型,其主要有两种架构,分别是:

  • CBOW(Continuous Bag of Words)模型:根据上下文的多个单词预测中间的单词
  • skip-gram 模型:根据中间的单词预测上下文的单词

下面,我们分别来介绍这两种结构。

CBOW 模型

CBOW 模型,也称为连续词袋模型,其工作原理是通过上下文的多个单词来预测中间的单词。

CBOW 模型包含了输入层、中间层、输出层。输入层接收单词的 one-hot 表示,中间层通过权重矩阵将输入转换成低维度的密集向量,输出层则使用 softmax 函数预测目标词的概率分布,如下所示。输出层的各个神经元经过 softmax 的计算可以得到各自的概率值,概率值最大的值转换成 1,其余转换成 0,由此得到的 one-hot 表示可以对应一个单词。

CBOW 模型基于 softmax 函数和交叉熵误差进行学习,两者可以构建一个 Softmax with Loss 层,学习阶段时完备的神经网络结构如下所示。

Skip-gram 模型

Skip-gram 模型,其工作原理是通过中间的单词预测上下文的单词。

Skip-gram 模型同样包含了输入层、中间层、输出。区别在于输出层只接收中间单词的 one-hot 编码,中间层同样通过权重矩阵转换成密集向量,输出层则为上下文中的每个单词分配概率,如下所示。

Skip-gram 模型的输出层数量与上下文单词的个数相同。因此需要通过 Softmax with Loss 层分别求出各个输出层的损失并求和。具体而言,skip-gram 模型的函数可以表示如下所示的函数。

\[\begin{aligned} L = - \frac{1}{T} \sum_{t=1}^{T} (logP(w_{t-1}|w_t) + logP(w_{t+1}|w_t)) \end{aligned}\]

其中,\(P(w_{t-1}|w_t)\) 表示给定 \(w_t\) 时,\(w_{t-1}\) 发生的概率;\(P(w_{t+1}|w_t)\) 表示给定 \(w_t\) 时,\(w_{t+1}\) 发生的概率。

CBOW vs skip-gram

关于 CBOW 和 skip-gram 的对比,从单词的分布式表示的准确度而言,skip-gram 的表现更好。从学习速度而言,CBOW 的表现更快。

在实际应用中,更多会选择 skip-gram。因为一旦生成了单词的分布式表示后,我们可以重复应用到各种场景中。因此可以无需考虑学习速度,更应该注重准确度。

优化

推理法存在一个问题,即 one-hot 表示的维度问题。由于 one-hot 表示的维度等于语料库的词汇量数量,如果语料库的词汇量为 100 万,那么神经网络中各个层之间的矩阵运算的数据量将非常大,极其影响性能。

我们假设语料库词汇量有 100 万个,中间层神经元有 100 个,CBOW 模型如下所示。下面,我们将以 word2vec 的 CBOW 模型为例,进行优化。

输入层维度问题

首先,我们考虑 one-hot 表示对于输入层维度的影响。我们使用 one-hot 表示作为输入,one-hot 表示和权重矩阵 \(W_{in}\) 的乘积显然会耗费巨大的计算资源,如下所示。

从图中可以看出,矩阵乘法的目的是为了取出单词的 ID 在权重矩阵 \(W_{in}\) 中对应行的向量,即单词对应的向量表示。对此,我们可以创建一个层,专门用于从权重参数中提取 单词 ID 对应行(向量),称之为 Embedding 层

Embedding 层以单词 ID 作为输入,输出单词的向量表示。在反向传播时,权重只会更新单词 ID 对应行的权重,如下图所示。

当引入 Embedding 层之后,我们可以得到 CBOW 模型的全貌图,如下所示。

输出层维度问题

其次,我们考虑 one-hot 表示对于输出层维度的影响。

首先,权重矩阵 \(W_{out}\) 的大小是 100 x 1000000,中间层的单词向量为 100。很显然,单词向量与权重矩阵 \(W_{out}\) 的乘积页会耗费巨大的计算资源。

其次,基于如下所示的 softmax 公式,我们可以看出 softmax 也存在计算量过大的问题。

\[\begin{aligned} y_k = \frac{exp(s_k)}{\sum_{i=1}^{1000000} \space exp(s_i)} \end{aligned}\]

对此,我们使用 负采样(Negative Sampling)来进行优化。负采样的核心思想是 使用二分类拟合多分类。举个例子,在未优化的实现中,我们使用 softmax 计算出所有单词的概率值,由此构建一个 one-hot 结果,来与正确值进行对比。而负采样的实现中,我们给定一个单词,神经网络计算这个单词的概率,判断它是否是正确值。这样就能将计算复杂度从 O(N) 降低至 O(1),其示意图如下所示。

另一方面,在多分类的情况下,输出层使用 softmax 函数将得分转化为概率,损失函数使用交叉熵误差。在二分类的情况下,输出层使用 sigmoid 函数,损失函数也使用交叉熵误差。因此,我们可以进一步将 Softmax-with-Loss 替换成 Sigmoid-with-Loss,得到如下所示的二分类神经网络。

至此,我们已经将多分类问题转换成了二分类问题,神经网络结构也进行了优化。下面,我们来看看二分类情况下如何进行学习。

负采样的样本学习

负采样的样本学习,核心思想是使用一个正例、采样选择若干个负例,对这些数据的损失求和,基于此来进行学习,如下所示。

关于负例的选择,负采样的做法是:基于语料库中单词使用频率,计算出各个单词的概率分布,然后基于概率分布对单词进行采样。这样可以尽可能保证单词的出现概率与真实的情况类似。此外,为了避免稀有单词出现的概率过低,这里还会对概率分布进行调整,如下所示。

\[\begin{aligned} P'(w_i) = \frac{P(w_i)^{0.75}}{\sum_j^n P(w_j)^{0.75}} \end{aligned}\]

计算法 vs 推理法

最后,我们来简单对比一下计数法和推理法。

首先,两种方法在学习机制上存在差异。计数法通过对整个语料库的统计数据进行一次学习来获得单词的分布式表示;推理法则反复基于语料库的部分数据进行学习。每当语料库新增词汇时,计数法需要重新计算,完成共现矩阵生成、SVD 等一系列操作。推理法则可以允许参数的增量学习,可以将之前学习到的权重作为下一次学习的初始值。

其次,两种方法在单词的分布式表示的性质上存在差异。计数法主要是编码单词的相似性。推理法除了单词的相似性之外,还能理解单词之间更复杂的模式,比如能够求解 "king - man + woman = queen" 这样的类推问题。

当然,如果只评判单词的相似性,两种方法则不相上下。

总结

本文我们主要介绍了自然语言处理中的核心问题之一——单词含义的表示。由此,我们介绍了三种方法,分别是词典法、计数法、推理法。

词典法的核心思想是 将含义相同或含义类似的单词归类到同一个组中,同时定义单词之间细粒度的层级关系,比如:上位与下位的关系,整体与局部的关系

计数法的核心思想是 基于语料库(Corpus),根据上下文的窗口大小,统计目标词周围的单词的频率,这种方法的依据是 分布式假设,即一个单词的含义是由其周围的单词形成。

推理法则是采用了神经网络的方式,学习样本数据,更新权重,以权重作为单词含义的表示。文中,我们介绍了 word2vec 模型的两种架构,分别是 CBOW 和 skip-gram。

参考

  1. 《深度学习:自然语言处理》
  2. 《图解GPT》
  3. 一文彻底搞懂Transformer - Word Embedding(词嵌入)
  4. Pennington, Jeffrey, Richard Socher, Christopher D. Manning.Glove: Global Vectors for Word Representation[J]. EMNLP. Vol.14. 2014.