LLM 架构解析词嵌入第 2 部分

LLM 架构解析词嵌入第 2 部分

Barry Lv6

深入探讨架构及构建利用 NLP 模型的实际应用,从 RNN 到 Transformer。

本系列的文章

  1. NLP 基础
  2. 词嵌入 ( 本文 )
  3. RNN、LSTM 和 GRU
  4. 序列到序列模型
  5. 注意力机制
  6. 变压器
  7. BERT
  8. GPT
  9. LLama
  10. Mistral

1. 引言

词嵌入是自然语言处理(NLP)领域的一个基本概念。它们本质上是一种将单词转换为数值表示或向量的方式,位于一个连续的向量空间中。其目标是捕捉单词的语义含义,使得具有相似含义的单词具有相似的向量表示。

这篇博客文章涵盖了词嵌入的基本到高级的各个方面,确保读者对该主题及其在NLP和LLMs背景下的发展有透彻的理解。

1.1 词嵌入

定义:词嵌入是密集的、低维的、连续的词向量表示,能够捕捉语义和句法信息。

特征

  • 密集:与稀疏表示(如独热编码)不同,嵌入是密集的,这意味着它们的大多数元素都是非零的。
  • 向量空间:词语在向量空间中定位,允许进行数学运算和比较。
  • 维度降低:向量空间通常被降低到较低的维度,以提高计算效率和可视化效果。
  • 语义相似性:具有相似意义的词语具有相似的嵌入。

示例

如果“king”用向量 v_king 表示,而“queen”用 v_queen 表示,这些向量之间的关系可以捕捉性别差异,例如 v_king - v_man + v_woman ≈ v_queen

词嵌入的必要性

虽然这有一定道理,但我们为什么要有足够的动力去学习和构建这些词嵌入呢?

  • 关于语音或图像识别系统,所有信息已经以丰富的密集特征向量的形式存在于高维数据集中,如音频频谱图和图像像素强度。
  • 然而,当涉及到原始文本数据时,尤其是基于计数的模型(如词袋模型),我们处理的是单个词,这些词可能有自己的标识符,并且无法捕捉词语之间的语义关系。
  • 这导致文本数据产生巨大的稀疏词向量,因此如果我们没有足够的数据,可能会得到较差的模型,甚至由于维度灾难而导致过拟合数据。

词嵌入如何使用?

  • 它们作为机器学习模型的输入。
    将词语 — -> 转换为它们的数值表示 — -> 用于训练或推理。
  • 表示或可视化用于训练它们的语料库中任何潜在的使用模式。

让我们举个例子,理解如何通过提取在特定条件下最常用的情感来生成词向量,并将每个表情符号转换为向量,条件将作为我们的特征。

2. 词嵌入基础知识

2.1 理解向量和向量空间

在自然语言处理(NLP)和词嵌入的背景下,理解向量和向量空间是基础,因为它们构成了表示单词及其关系的数学基础。

2.1.1 什么是向量?

向量是一个具有大小(长度)和方向的数学对象。简单来说,向量可以被视为一个有序的数字列表,表示空间中的一个点。例如,在二维空间中,向量可以表示为:

其中 v1v2 是向量在两个维度(例如,x轴和y轴)上的分量。

在自然语言处理(NLP)中,单词在多维空间中被表示为向量,每个维度捕捉单词意义的不同方面或特征。

2.1.2 什么是向量空间?

向量空间是由一组向量构成的数学结构,这些向量可以相加并与标量(数字)相乘,以产生同一空间内的另一个向量。向量空间通过其维度(例如,2D、3D等)来定义,维度指的是指定该空间内任何点所需的坐标数量。

在词嵌入的上下文中,我们处理的是高维向量空间,通常具有数百甚至数千个维度。每个词都映射到该空间中的一个唯一向量。

2.1.3 向量如何表示词语

当我们在向量空间中将词语表示为向量时,目标是捕捉词语的语义含义。具有相似含义或出现在相似上下文中的词语应该在向量空间中彼此接近。训练词嵌入的过程根据词语在大型文本语料库中出现的上下文学习这些向量表示。

例如,词语“king”和“queen”可能会被表示为在向量空间中彼此接近的向量,因为它们共享相似的上下文(例如,皇室、领导)。

2.1.4 向量空间中的操作

向量空间允许我们执行在自然语言处理(NLP)中有用的各种操作:

  1. 加法和减法:
  • 通过对向量进行加法或减法,我们可以探索单词之间的关系。例如,著名的类比:

  • 这个操作展示了如何通过调整“国王”的向量,利用“男人”和“女人”之间的差异来推导出表示“女王”的向量。

2. 点积:

  • 两个向量的点积提供了它们之间相似性的度量。如果两个词向量的点积很高,这意味着它们相似并共享上下文。

3. 余弦相似度:

  • 余弦相似度是衡量两个向量之间相似性的常用指标,计算为它们之间角度的余弦值。这在比较词向量时特别有用,因为它有助于规范化向量的大小,专注于它们的方向。

2.1.5 向量空间的可视化

虽然在自然语言处理(NLP)中使用的向量空间通常是高维的(远超我们直接可视化的能力),但通常会使用像PCA(主成分分析)或t-SNE(t分布随机邻域嵌入)这样的技术将维度降低到2D或3D。这种降维使我们能够可视化单词之间的相对位置,揭示语义相关单词的聚类。

2.2 Word Embeddings 如何表示意义

Word embeddings 是自然语言处理 (NLP) 中一种强大的工具,因为它们允许以一种捕捉单词语义意义和与其他单词关系的方式来表示单词。与传统方法如独热编码不同,后者将单词视为独立且无关的实体,word embeddings 以紧凑、密集的向量形式编码了关于单词上下文和用法的丰富信息。

2.2.1 词嵌入中的意义概念

词嵌入的核心思想是,出现在相似上下文中的词往往具有相似的意义。这基于语言学中的分布假设,该假设指出,在相同上下文中出现的词往往具有相似的意义。

例如,考虑“猫”和“狗”这两个词。这些词经常出现在相似的上下文中(例如,“猫/狗正在玩球”)。因此,它们的嵌入在向量空间中应该彼此接近,反映出它们的相似意义。

2.2.2 通过上下文捕捉意义

Word embeddings 通常通过分析单词出现的上下文从大型文本语料库中学习。学习过程涉及将每个单词映射到高维空间中的一个向量,以便该空间的几何形状捕捉单词之间的语义关系。

  1. 共现统计:
  • Word embeddings 通常源自共现统计,其中每个单词的向量是基于在文本中频繁出现的邻近单词学习的。例如,在 Word2Vec 模型中,嵌入的训练方式使得具有相似共现模式的单词具有相似的向量表示。
  1. 上下文相似性:
  • 出现在相似上下文中的单词(即,被相同单词集合包围)被赋予相似的向量表示。例如,“king”和“queen”这两个单词可能经常出现在相似的上下文中(例如,“The _ ruled the kingdom”),导致它们的嵌入在向量空间中接近。

2.2.3 嵌入中的几何关系

词嵌入的真正力量在于嵌入空间中向量之间的几何关系。这些关系编码了不同类型的意义和语义信息。

  1. 同义词和相似性:
  • 意思相近的词在向量空间中的嵌入彼此接近。例如,词“happy”和“joyful”可能由接近的向量表示,表明它们的语义相似性。
  1. 类比和语义关系:
  • 词嵌入的一个迷人特性是它们通过向量运算捕捉类比关系的能力。一个著名的例子是类比:

  • 这意味着“king”和“man”之间的向量差与“queen”和“woman”之间的向量差相似。这种运算表明嵌入捕捉了复杂的语义关系,如性别、王室,甚至地理关系(例如,“Paris — France + Italy ≈ Rome”)。
  1. 层次关系:
  • 一些嵌入还捕捉层次关系。例如,在一个经过良好训练的嵌入空间中,“dog”可能接近“animal”和“cat”,反映出“dog”和“cat”都是“animals”的类型的层次结构。
  1. 多义性和上下文意义:
  • 虽然传统的词嵌入在处理多义性(具有多重意义的词)时存在困难,但像上下文化嵌入(例如,BERT,ELMo)这样的新模型已经推动了这一概念。在这些模型中,词的嵌入会根据其出现的上下文而变化,从而使模型能够捕捉像“bank”这样的词的不同含义(例如,河岸与金融银行)。

2.2.4 密集表示

词嵌入被称为密集表示,因为它们将一个词的含义浓缩到相对较少的维度(例如,100–300),其中每个维度捕捉词义或上下文的不同方面。这与稀疏表示(如独热编码)形成对比,后者每个词由一个长向量表示,且大多数元素为零。

例如,考虑以下在三维空间中“猫”和“狗”的词嵌入:

这些向量彼此接近,反映了“猫”和“狗”的相似含义。

2.2.5 词嵌入中的意义应用

词嵌入捕捉意义的能力在自然语言处理(NLP)中有广泛的应用:

  • 相似性和相关性: 嵌入用于测量两个词之间的相似程度,这在信息检索、聚类和推荐系统等任务中非常有用。
  • 语义搜索: 词嵌入使得搜索引擎更加智能,能够理解同义词和相关术语。
  • 机器翻译: 嵌入帮助对齐不同语言中的词汇,从而促进更准确的翻译。
  • 情感分析: 通过理解词语在上下文中的意义,嵌入提高了情感分类的准确性。

2.3 词嵌入中的上下文概念

词嵌入通过分析在大规模文本语料库中与目标词紧密相邻的词来利用上下文捕捉词的含义。其理念是,出现在相似上下文中的词往往具有相似的意义。

  1. 上下文窗口:
  • 在训练词嵌入时,通常使用上下文窗口来定义围绕目标词的词的范围,这些词被视为其上下文。例如,在句子“The cat sat on the mat”中,如果目标词是“cat”,而上下文窗口大小为2,则上下文词为“The”和“sat”。
  • 上下文窗口的大小会影响嵌入的质量。较小的窗口关注于更近的词,捕捉更具体的关系,而较大的窗口可能捕捉更一般的语义关系。
  1. 上下文相似性:
  • 训练过程调整词向量,使得具有相似上下文的词最终具有相似的向量。例如,词“cat”和“dog”可能具有相似的上下文,如“pets”、“animals”、“home”等。因此,它们的向量在嵌入空间中会彼此接近。
  1. 上下文化嵌入:
  • 传统的词嵌入如Word2Vec和GloVe为每个词生成一个单一的向量,而不考虑其上下文。然而,像ELMo和BERT这样的新模型生成上下文化嵌入,其中一个词的向量会根据其上下文而变化。
  • 例如,在BERT中,“bank”在“river bank”和“financial bank”中的向量会不同,反映出它们在这些上下文中的不同含义。这使得对词及其含义的理解更加细致。

2.3.1 为什么上下文很重要

上下文至关重要,因为一个词的意思在没有上下文的情况下往往是模糊的。同一个词根据周围的词可以有不同的含义,理解这一点是自然语言理解的关键。

  1. 消歧义:
  • 上下文有助于消除具有多重含义(多义性)词语的歧义。例如,“bark”这个词可以指狗发出的声音或树的外皮。上下文(例如,“The dog barked loudly”与“The tree’s bark was rough”)有助于确定正确的含义。
  1. 同义词和相关词:
  • 意思相似或在相似上下文中使用的词会有相似的嵌入。例如,“happy”和“joyful”可能出现在相似的上下文中,如“feeling”或“emotion”,从而导致相似的嵌入。
  1. 捕捉细微差别:
  • 通过利用上下文,嵌入可以捕捉到意义上的细微差别。例如,“big”和“large”可能有相似的嵌入,但上下文可能揭示它们使用上的细微差别,如“big opportunity”与“large amount。”

3. 词嵌入技术

在自然语言处理(NLP)中,词嵌入的生成是理解语言语义的核心。这些嵌入是词的密集数值表示,捕捉语义关系,使机器能够有效处理文本数据。已经开发了几种技术来生成词嵌入,每种技术都提供了对语言语义结构的独特见解。让我们探索一些主要的方法:

3.1 基于频率的方法(浅层嵌入)

3.1.1 计数向量器

在收集用于分布式词表示的词数据时,可以从一系列文档中简单地统计词的出现次数开始。每个文档中每个词出现次数的总和就是一个 计数向量。CountVectorizer 通过计算每个词出现的次数将文本转换为固定长度的向量。现在,标记被存储为 词袋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sklearn.feature_extraction.text import CountVectorizer

# Sample corpus
corpus = [
'This is the first document.',
'This document is the second document.',
'And this is the third one.',
'Is this the first document?',
]

# Initialize the CountVectorizer
vectorizer = CountVectorizer()

# Fit and transform the corpus
X = vectorizer.fit_transform(corpus)

# Convert the result to a dense matrix and print it
print("Count Vectorized Matrix:\n", X.toarray())

# Print the feature names
print("Feature Names:\n", vectorizer.get_feature_names_out())

输出:

1
2
3
4
5
6
7
Count Vectorized Matrix:
[[0 1 1 1 0 0 1 0 1]
[0 2 0 1 0 1 1 0 1]
[1 0 0 1 1 0 1 1 1]
[0 1 1 1 0 0 1 0 1]]
Feature Names:
['and' 'document' 'first' 'is' 'one' 'second' 'the' 'third' 'this']

局限性

  • 由于词汇量大,维度高。
  • 忽略词的语义意义和上下文。
  • 不考虑词的顺序。
  • 产生稀疏特征矩阵。
  • 在捕捉长距离词关系方面有限。
  • 无法处理同义词;将每个词视为不同。
  • 在新文档中对词汇表外(OOV)词汇处理困难。
  • 对文档长度敏感,可能引入偏差。
  • 稀有词可能引入噪声而没有有意义的贡献。
  • 频繁出现的词可能主导特征空间,除非进行管理。
  • 对所有术语赋予相等的重要性,缺乏区分能力。
  • 对于大型语料库资源消耗大,可能存在可扩展性问题。

3.1.2 词袋模型 (BoW)

词袋模型是一种文本表示技术,它将文档表示为一个无序的单词集合及其各自的频率。它忽略了单词的顺序,并捕捉文档中每个单词的频率,从而创建一个向量表示。

这是一种非常灵活、直观且最简单的特征提取方法。文本/句子被表示为独特单词计数的列表,因此这种方法也被称为计数向量化。为了对我们的文档进行向量化,我们所要做的就是计算每个单词出现的次数。

由于词袋模型根据出现频率对单词进行加权。在实践中,像“is”、“the”、“and”这样的常见单词没有任何价值。在计数向量化之前,会去除停用词(在我系列博客中介绍过)。

示例

词汇量是这些文档中独特单词的总数。

1
Vocabulary: [‘dog’, ‘a’, ‘live’, ‘in’, ‘home’, ‘hut’, ‘the’, ‘is’]

代码

局限性

  • 忽略单词顺序和上下文,失去语义意义。
  • 高维稀疏向量可能导致计算效率低下。
  • 无法捕捉单词之间的语义相似性。
  • 对多义词(多个含义)和同义词(相同含义)处理困难。
  • 对词汇量的大小和选择敏感。
  • 不捕捉多词短语或表达。
  • 常见单词(停用词)可能主导表示。
  • 需要广泛的预处理(分词、去除停用词等)。
  • 不适合需要细致语言理解的复杂任务。

3.1.3 词频-逆文档频率 (TF-IDF)

TF-IDF(词频-逆文档频率)是一种数值统计,用于评估一个词在文档中相对于一组文档(或语料库)的重要性。基本思想是,如果一个词在某个文档中频繁出现,但在其他文档中不多见,那么它应该被赋予更高的重要性。

在语料库 D 中,文档 d 中的术语 t 的 TF-IDF 分数是通过两个指标的乘积计算得出的:词频 (TF) 和 **逆文档频率 (IDF)**。

1. 词频 (TF)

词频 (TF) 衡量一个术语在文档中出现的频率。通常通过文档中的总词数进行归一化,以防止对较长文档的偏见。

2. 逆文档频率 (IDF)

逆文档频率 (IDF) 衡量一个术语在整个语料库中的重要性。它降低了在许多文档中出现的术语的权重,并增加了在较少文档中出现的术语的权重。

  • 为了防止在术语未出现在任何文档中时出现除以零的情况,分母中添加了“+1”。

3. 结合 TF 和 IDF:TF-IDF

TF-IDF 分数是通过将文档 d 中术语 tTF 值与 IDF 值相乘来计算的:

重要性:有助于识别文档中的重要词汇,广泛用于信息检索和文本挖掘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.feature_extraction.text import TfidfVectorizer

# Sample text data (documents)
documents = [
"The cat sat on the mat.",
"The cat sat on the bed.",
"The dog barked."
]
# Initialize the TfidfVectorizer
vectorizer = TfidfVectorizer()
# Fit the model and transform the documents into TF-IDF representation
tfidf_matrix = vectorizer.fit_transform(documents)
# Get the feature names (unique words in the corpus)
feature_names = vectorizer.get_feature_names_out()
# Convert the TF-IDF matrix into an array
tfidf_array = tfidf_matrix.toarray()
# Display the TF-IDF matrix
print("Feature Names (Words):", feature_names)
print("\nTF-IDF Matrix:")
print(tfidf_array)

Output:

1
2
3
4
5
6
7
8
9
Feature Names (Words): ['barked' 'bed' 'cat' 'dog' 'mat' 'on' 'sat' 'the']

TF-IDF Matrix:
[[0. 0. 0.37420726 0. 0.49203758 0.37420726
0.37420726 0.58121064]
[0. 0.49203758 0.37420726 0. 0. 0.37420726
0.37420726 0.58121064]
[0.65249088 0. 0. 0.65249088 0. 0.
0. 0.38537163]]

局限性

  • 无法捕捉词语的上下文或含义。
  • 对于大型词汇表,高维且稀疏的向量。
  • 无法有效处理同义词或多义词。
  • 可能对较长文档过度惩罚。
  • 限于线性关系,无法捕捉复杂模式。
  • 静态,无法适应新上下文或不断发展的语言。
  • 对于非常短或非常长的文档效果不佳。
  • 对词序不敏感。

3.1.4 N-Grams

N-grams 是可以作为文本分析单位一起使用的单词序列。像“Mary had a little lamb”、“Mary had”、“had a”、“a little”和“little lamb”这样的语句是二元组(N=2)。许多 N-grams 可能在数据中出现得不够频繁,因此不够有用,导致稀疏且缺乏意义的表示。

类型

  • Unigram:单个单词。
  • Bigram:一对单词。
  • Trigram:三个单词的序列。

重要性:捕捉文本中的上下文和单词依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import nltk
from nltk.util import ngrams
from collections import Counter

# Sample text data
text = "The quick brown fox jumps over the lazy dog"
# Tokenize the text into words
tokens = nltk.word_tokenize(text)
# Generate Unigrams (1-gram)
unigrams = list(ngrams(tokens, 1))
print("Unigrams:")
print(unigrams)
# Generate Bigrams (2-gram)
bigrams = list(ngrams(tokens, 2))
print("\nBigrams:")
print(bigrams)
# Generate Trigrams (3-gram)
trigrams = list(ngrams(tokens, 3))
print("\nTrigrams:")
print(trigrams)
# Count frequency of each n-gram (for demonstration)
unigram_freq = Counter(unigrams)
bigram_freq = Counter(bigrams)
trigram_freq = Counter(trigrams)
# Print frequencies (optional)
print("\nUnigram Frequencies:")
print(unigram_freq)
print("\nBigram Frequencies:")
print(bigram_freq)
print("\nTrigram Frequencies:")
print(trigram_freq)

Output:

1
2
3
4
5
6
7
8
9
10
11
12
Unigrams:
[('The',), ('quick',), ('brown',), ('fox',), ('jumps',), ('over',), ('the',), ('lazy',), ('dog',)]
Bigrams:
[('The', 'quick'), ('quick', 'brown'), ('brown', 'fox'), ('fox', 'jumps'), ('jumps', 'over'), ('over', 'the'), ('the', 'lazy'), ('lazy', 'dog')]
Trigrams:
[('The', 'quick', 'brown'), ('quick', 'brown', 'fox'), ('brown', 'fox', 'jumps'), ('fox', 'jumps', 'over'), ('jumps', 'over', 'the'), ('over', 'the', 'lazy'), ('the', 'lazy', 'dog')]
Unigram Frequencies:
Counter({('The',): 1, ('quick',): 1, ('brown',): 1, ('fox',): 1, ('jumps',): 1, ('over',): 1, ('the',): 1, ('lazy',): 1, ('dog',): 1})
Bigram Frequencies:
Counter({('The', 'quick'): 1, ('quick', 'brown'): 1, ('brown', 'fox'): 1, ('fox', 'jumps'): 1, ('jumps', 'over'): 1, ('over', 'the'): 1, ('the', 'lazy'): 1, ('lazy', 'dog'): 1})
Trigram Frequencies:
Counter({('The', 'quick', 'brown'): 1, ('quick', 'brown', 'fox'): 1, ('brown', 'fox', 'jumps'): 1, ('fox', 'jumps', 'over'): 1, ('jumps', 'over', 'the'): 1, ('over', 'the', 'lazy'): 1, ('the', 'lazy', 'dog'): 1})

局限性

  • 高维度和稀疏性
  • 缺乏语义理解
  • 忽视上下文
  • 可扩展性问题
  • 对噪声和稀有词的敏感性
  • 难以捕捉多义词和同义词
  • 无法跨语言进行概括
  • 难以捕捉长期依赖关系

3.1.5 共现矩阵

共现矩阵捕捉了单词在给定上下文窗口中一起出现的频率。这些矩阵量化了单词之间的统计关系,为生成词嵌入提供了基础。

让我们考虑一个简单的例子来说明共现矩阵的概念。假设我们有一个包含以下三篇文档的语料库:

  • 文档 1: “The quick brown fox jumps over the lazy dog.”
  • 文档 2: “The brown dog barks loudly.”
  • 文档 3: “The lazy cat sleeps peacefully.”

我们希望基于这些文档中的单词构建一个共现矩阵,窗口大小为 1。这意味着我们考虑每个单词与其直接相邻单词的出现情况。我们将忽略标点符号,并以不区分大小写的方式处理单词。

首先,让我们根据语料库中的唯一单词构建一个词汇表:

1
Vocabulary: [the, quick, brown, fox, jumps, over, lazy, dog, barks, loudly, cat, sleeps, peacefully]

接下来,我们创建一个共现矩阵,其中行和列表示词汇表中的单词。矩阵中每个单元格 (i, j) 的值表示单词 i 在指定窗口大小内与单词 j 共同出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
           the  quick  brown  fox  jumps  over  lazy  dog  barks  loudly  cat  sleeps peacefully
the 0 1 1 0 0 0 1 1 0 0 1 1 1
quick 1 0 0 1 0 0 0 0 0 0 0 0 0
brown 1 0 0 0 0 0 0 1 1 0 0 0 0
fox 0 1 0 0 1 0 0 0 0 0 0 0 0
jumps 0 0 0 1 0 1 0 0 0 0 0 0 0
over 0 0 0 0 1 0 1 0 0 0 0 0 0
lazy 1 0 0 0 0 1 0 1 0 0 1 0 0
dog 1 0 1 0 0 0 1 0 1 1 0 0 0
barks 0 0 1 0 0 0 0 1 0 1 0 0 0
loudly 0 0 0 0 0 0 0 1 1 0 0 0 0
cat 1 0 0 0 0 0 1 0 0 0 0 1 1
sleeps 1 0 0 0 0 0 0 0 0 0 1 0 1
peacefully 1 0 0 0 0 0 0 0 0 0 1 1 0

这个共现矩阵捕捉了每个单词与其他单词在窗口大小为 1 内的共现频率。例如,行“the”和列“lazy”交叉处的值为 1,表示单词“lazy”在语料库中与单词“the”在指定窗口大小内共现一次。

这个例子展示了如何构建和利用共现矩阵来捕捉文本语料库中单词之间的统计关系,为各种自然语言处理任务(如词嵌入、情感分析和命名实体识别)提供有价值的见解。

共现矩阵在自然语言处理中的主要应用之一是生成词嵌入。通过分析语料库中单词的共现模式,共现矩阵可以捕捉每个单词周围的上下文信息。像 word2vec GloVe 等技术利用这些矩阵创建密集的低维向量表示,其中向量之间的几何关系反映了单词之间的语义相似性。这使得任务能够测量单词相似性、检测类比和进行语义搜索。

向量之间的几何关系反映了单词之间的语义相似性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import numpy as np
import pandas as pd
from collections import Counter
from sklearn.preprocessing import normalize

# Example corpus
corpus = [
"I love machine learning",
"machine learning is great",
"I love deep learning",
"deep learning and machine learning are related"
]

# Tokenize the sentences
corpus = [sentence.lower().split() for sentence in corpus]

# Flatten the list of sentences into a single list of words
vocab = set([word for sentence in corpus for word in sentence])
vocab = sorted(vocab) # Sorting for consistent order
vocab_size = len(vocab)

# Initialize an empty co-occurrence matrix
co_occurrence_matrix = np.zeros((vocab_size, vocab_size))

# Define the window size
window_size = 2

# Create a mapping from word to index
word2idx = {word: i for i, word in enumerate(vocab)}

# Populate the co-occurrence matrix
for sentence in corpus:
for i, word in enumerate(sentence):
word_idx = word2idx[word]
start = max(0, i - window_size)
end = min(len(sentence), i + window_size + 1)

for j in range(start, end):
if i != j:
context_word = sentence[j]
context_idx = word2idx[context_word]
co_occurrence_matrix[word_idx, context_idx] += 1

# Convert the matrix to a DataFrame for better visualization
co_occurrence_df = pd.DataFrame(co_occurrence_matrix, index=vocab, columns=vocab)

# Normalize the co-occurrence matrix
co_occurrence_normalized = normalize(co_occurrence_matrix, norm='l1', axis=1)

# Convert the normalized matrix to a DataFrame for better visualization
co_occurrence_normalized_df = pd.DataFrame(co_occurrence_normalized, index=vocab, columns=vocab)

# Display the co-occurrence matrix
print("Co-occurrence Matrix:")
print(co_occurrence_df)

# Display the normalized co-occurrence matrix
print("\nNormalized Co-occurrence Matrix:")
print(co_occurrence_normalized_df)

局限性

  • 高维度导致效率低下和存储问题。
  • 由于大多数单词对很少共现,极度稀疏。
  • 捕捉深层语义关系的能力有限。
  • 上下文独立,无法区分同一单词的不同含义。
  • 随着语料库的增大,矩阵大小呈平方增长,面临可扩展性挑战。
  • 对频繁单词的偏向,对稀有单词或短语不可靠。
  • 表达能力有限,无法捕捉复杂的语言结构。
  • 由于存在停用词和不太有意义的单词对,数据噪声较大。

3.1.6 独热编码

独热编码是自然语言处理(NLP)中表示单词的一种基本方法。词汇表中的每个单词都被表示为一个唯一的向量,除了一个元素外,所有元素都设置为0,该元素对应于单词在词汇表中的索引。

示例:给定这个包含10,000个单词的词汇表,最简单的数值表示每个单词的方法是什么?

好吧,你可以为每个单词分配一个整数索引:

所以,一些示例:

  • 我们第一个词汇单词“aardvark”的向量表示将是 [1, 0, 0, 0, …, 0],在第一个位置是“1”,后面跟着9,999个零。
  • 我们第二个词汇单词“ant”的向量表示将是 [0, 1, 0, 0, …, 0],在第一个位置是“0”,在第二个位置是“1”,后面是9,998个零。
  • 依此类推……

这个过程被称为独热向量编码。你可能也听说过这种方法用于表示多类分类问题中的标签。

现在,假设我们的NLP项目正在构建一个翻译模型,我们想将英语输入句子“the cat is black”翻译成另一种语言。我们首先需要用独热编码表示每个单词。我们首先查找第一个单词“the”的索引,发现它在我们10,000个单词的词汇表中的索引是8676。

我们对输入句子中的每个单词进行这种索引查找,并创建一个向量来表示每个输入单词。整个过程看起来像这样(GIF):

请注意,这个过程为每个输入单词生成了一个非常稀疏(大多数为零)特征向量(在这里,“特征向量”、“嵌入”和“单词表示”这几个术语可以互换使用)。

这些独热向量是将单词表示为实值数字向量的一种快速简便的方法。

注意:

如果你想生成整个句子的表示,而不仅仅是每个单词呢?最简单的方法是将句子的组成单词嵌入连接或平均(或两者的某种混合)。更高级的方法,如编码器-解码器RNN模型,将顺序读取每个单词的嵌入,以逐步通过变换层构建句子意义的密集表示。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def one_hot_encode(text):
words = text.split()
vocabulary = set(words)
word_to_index = {word: i for i, word in enumerate(vocabulary)}
one_hot_encoded = []
for word in words:
one_hot_vector = [0] * len(vocabulary)
one_hot_vector[word_to_index[word]] = 1
one_hot_encoded.append(one_hot_vector)

return one_hot_encoded, word_to_index, vocabulary

# sample
example_text = "cat in the hat dog on the mat bird in the tree"

one_hot_encoded, word_to_index, vocabulary = one_hot_encode(example_text)

print("Vocabulary:", vocabulary)
print("Word to Index Mapping:", word_to_index)
print("One-Hot Encoded Matrix:")
for word, encoding in zip(example_text.split(), one_hot_encoded):
print(f"{word}: {encoding}")

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vocabulary: {'mat', 'the', 'bird', 'hat', 'on', 'in', 'cat', 'tree', 'dog'}
Word to Index Mapping: {'mat': 0, 'the': 1, 'bird': 2, 'hat': 3, 'on': 4, 'in': 5, 'cat': 6, 'tree': 7, 'dog': 8}
One-Hot Encoded Matrix:
cat: [0, 0, 0, 0, 0, 0, 1, 0, 0]
in: [0, 0, 0, 0, 0, 1, 0, 0, 0]
the: [0, 1, 0, 0, 0, 0, 0, 0, 0]
hat: [0, 0, 0, 1, 0, 0, 0, 0, 0]
dog: [0, 0, 0, 0, 0, 0, 0, 0, 1]
on: [0, 0, 0, 0, 1, 0, 0, 0, 0]
the: [0, 1, 0, 0, 0, 0, 0, 0, 0]
mat: [1, 0, 0, 0, 0, 0, 0, 0, 0]
bird: [0, 0, 1, 0, 0, 0, 0, 0, 0]
in: [0, 0, 0, 0, 0, 1, 0, 0, 0]
the: [0, 1, 0, 0, 0, 0, 0, 0, 0]
tree: [0, 0, 0, 0, 0, 0, 0, 1, 0]

稀疏独热编码的问题

我们已经完成了独热编码,并成功地将每个单词表示为数字向量。许多NLP项目都这样做,但最终结果可能平平,尤其是在训练数据集较小的情况下。这是因为独热向量并不是一种很好的输入表示方法。

为什么单词的独热编码不是最优的?

  1. 缺乏语义相似性:独热编码无法捕捉单词之间的语义关系。例如,“cat”和“tiger”被表示为完全不同的向量,无法指示它们的相似性。这对于类比向量操作等任务是有问题的,我们期望“cat — small + large”这样的操作能得到类似“tiger”或“lion”的结果。独热编码缺乏执行此类任务所需的丰富性。

  2. 高维度:独热向量的维度与词汇表的大小线性增长。随着词汇表的增长,特征向量变得越来越大,加剧了维度诅咒。这不仅增加了需要估计的参数数量,还需要指数级更多的数据来训练一个能够很好地泛化的模型。

  3. 计算效率低:独热编码向量是稀疏和高维的,大多数元素为零。许多机器学习模型,尤其是神经网络,难以处理这种稀疏数据。大的特征空间也可能导致内存和存储问题,特别是如果模型不能有效处理稀疏矩阵。

3.2 静态嵌入

密集向量或词嵌入通过提供更具信息性和紧凑的单词表示,解决了独热编码的局限性。

  • 降维:嵌入通常使用维度较小的向量(例如,50、100或300),而不是与词汇表大小相等的向量长度。
  • 语义接近性:密集向量将语义相似的单词在向量空间中彼此靠近。
  • 例如,“cat”和“dog”的向量之间的余弦相似度将高于“cat”和“fish”之间的相似度。

示例:

  • Word2Vec:通过从上下文预测一个单词或反之来学习嵌入。
  • GloVe:使用矩阵分解来推导嵌入。
  • 变换器(例如,BERT、GPT):生成基于周围上下文捕捉单词含义的上下文嵌入。

独热向量最重要的问题是什么,而密集嵌入又是如何解决的?

嵌入解决的核心问题是泛化。

  • 泛化问题。 如果我们假设“cat”和“tiger”等单词确实相似,我们希望有某种方式将该信息传递给模型。如果其中一个单词很少见(例如“liger”),这就变得尤为重要,因为它可以借助一个相似的、更常见的单词在模型中的计算路径。因为在训练过程中,模型学习以某种方式处理输入“cat”,通过由权重和偏置参数定义的变换层。当网络最终看到“liger”时,如果它的嵌入与“cat”相似,那么它将沿着与“cat”相似的路径,而不是网络必须从头开始学习如何处理它。对于从未见过的事物进行预测是非常困难的——如果与您曾经见过的事物相关,则要容易得多。

这意味着嵌入使我们能够构建更具泛化能力的模型——而不是网络需要拼命学习处理不相关输入的多种不同方式,我们让相似的单词“共享”参数和计算路径。

朝着密集的、语义丰富的表示

如果我们从词汇表中取5个示例单词(比如“aardvark”、“black”、“cat”、“duvet”和“zombie”),并检查它们通过上述独热编码方法创建的嵌入向量,结果将如下所示:

使用独热编码的单词向量。每个单词由一个主要为零的向量表示,只有在该单词在词汇表中的索引位置上有一个“1”。注意:并不是说“black”、“cat”和“duvet”具有相同的特征向量,只是在这里看起来是这样的。

但是,作为某种语言的使用者,我们知道单词是这些具有多层含义和内涵的丰富实体。让我们为这5个单词手动构建一些语义特征。具体来说,让我们将每个单词表示为在“动物”、“蓬松度”、“危险”和“神秘”这四个语义特征之间的某种值,范围在0到1之间:

为词汇表中的5个单词手动构建的语义特征。

因此,解释几个示例:

  • 对于单词“aardvark”,我为特征“动物”赋予了一个高值(因为它确实是动物),而对于“蓬松度”(aardvark的毛发较短)、“危险”(它们是小型夜行性挖洞猪)和“神秘”(它们很迷人)赋予了相对较低的值。
  • 对于单词“cat”,我为特征“动物”和“蓬松度”赋予了高值(不言而喻),为“危险”赋予了中等值(如果您曾养过宠物猫,这很明显),为“神秘”赋予了中等值(试着搜索“无毛猫”的图片)。

根据语义特征值绘制单词

我们已经走到了重点:

每个语义特征可以被视为更广泛的高维语义空间中的一个单独维度。

  • 在上述虚构数据集中,有四个语义特征,我们可以将其中两个同时绘制为2D散点图(见下文)。每个特征是不同的轴/维度。
  • 每个单词在这个空间中的坐标由其在感兴趣特征上的具体值给出。例如,单词“aardvark”在蓬松度与动物的2D图上的坐标是(x=0.97,y=0.03)。

在2或3个轴上绘制单词特征值。

  • 同样,我们可以考虑三个特征(“动物”、“蓬松度”和“危险”),并在这个3D语义空间中绘制单词的位置。例如,单词“duvet”的坐标是(x=0.01,y=0.84,z=0.12),这表明“duvet”与蓬松度的概念高度相关,可能稍微有点危险,并且不是动物。

这是一个手动构建的玩具示例,但实际的嵌入算法当然会自动为输入语料库中的所有单词生成嵌入向量。如果您愿意,可以将word embedding算法(如word2vec)视为单词的无监督特征提取器。

word embedding算法(如word2vec)是单词的无监督特征提取器。

词嵌入的维度是多少?

一般来说,词嵌入的维度是指单词的向量表示所定义的维度数量。 这通常是一个在创建词嵌入时确定的固定值。词嵌入的维度表示在向量表示中编码的特征总数。

生成词嵌入的不同方法可能导致不同的维度。最常见的是,词嵌入的维度范围从50到300,尽管也可能有更高或更低的维度。

例如,下面的图显示了“king”、“queen”、“man”和“women”在3维空间中的词嵌入:

3.2.1 Word2Vec

Word2Vec,由Mikolov et al. 提出,Google的Word2Vec 是一种流行的基于预测的方法,通过预测上下文窗口内的周围单词来学习词嵌入。这种方法产生的稠密向量表示捕捉了单词之间的语义关系。

Bengio提出的方法为NLP研究人员提供了新的机会,可以修改技术和架构本身,以创建一种计算成本更低的方法。为什么?

Bengio等人提出的方法将词汇中的单词输入到一个前馈神经网络中,该网络具有嵌入层、隐藏层和softmax函数。

这些嵌入具有可学习的向量,通过反向传播进行自我优化。本质上,架构的第一层产生词嵌入,因为它是一个浅层网络。

这种架构的问题在于隐藏层和投影层之间的计算成本很高。其原因是复杂的:

  1. 投影中产生的值是稠密的
  2. 隐藏层计算词汇中所有单词的概率分布

为了解决这个问题,研究人员(Mikolov et al.在2013年)提出了一种叫做‘Word2Vec’的模型。

Word2Vec模型本质上解决了Bengio的NLM的问题。

它完全去除了隐藏层,但投影层对所有单词是共享的,就像Bengio的模型一样。缺点是这个没有神经网络的简单模型在数据较少时无法像神经网络那样精确地表示数据。

另一方面,使用更大的数据集时,它可以在嵌入空间中精确表示数据。同时,它还降低了复杂性,模型可以在更大的数据集上进行训练。

Word2Vec有两种神经嵌入方法:连续词袋模型(CBOW)跳字模型(Skip-gram)

3.2.1.1 连续词袋模型 (CBOW)

CBOW 是 word2vec 模型的一种变体,它通过上下文词(词袋)来预测中心词。因此,给定上下文窗口中的所有词(不包括中间的那个),CBOW 会告诉我们中心词最可能是什么。

例如,假设我们在以下句子中有一个大小为 2 的窗口。给定词语(“Quick”,“Brown”,“and”),我们希望网络预测“Fox”。

Skip-gram 网络的输入需要更改为接受多个词。我们使用“词袋”向量作为输入,而不是“独热”向量。概念是相同的,只是我们在多个位置放置 1(对应于上下文词)。

在窗口大小为 2 的情况下,skip-gram 每个中心词最多会生成四个训练样本,而 CBOW 仅生成一个。通过 skip-gram,我们看到与独热向量相乘只是从隐藏层权重矩阵中选择一行。当你改为与词袋向量相乘时,会发生什么呢?

结果是它选择相应的行并将它们相加。

对于 CBOW 架构,我们还将这个总和除以上下文词的数量,以计算它们的平均词向量。因此,CBOW 架构中隐藏层的输出是所有上下文词向量的平均值。从那里,输出层与 skip-gram 中的输出层相同。

该模型通过仅计算 **log2(V)**(其中 V 是词汇表的大小),消除了计算词汇表中所有词的概率分布的复杂性。因此,该模型更快且更高效。

3.2.1.2 Skip-Gram

首先,您知道不能将单词仅作为文本字符串输入到神经网络中,因此我们需要一种方法将单词表示给网络。为此,我们首先从训练文档中构建一个单词词汇表——假设我们有一个包含10,000个唯一单词的词汇表。我们将把输入单词“ants”表示为一个独热向量。这个向量将有10,000个分量(每个词汇中的一个),我们将在与单词“ants”对应的位置放置一个“1”,在其他所有位置放置0。网络的输出是一个单一的向量(同样有10,000个分量),包含我们词汇表中每个单词的概率,即随机选择的附近单词是该词汇单词的概率。以下是我们神经网络的架构。

隐藏层神经元没有激活函数,但输出神经元使用softmax。请注意,在训练这个网络时,输入是一个表示输入单词的独热向量,而训练输出也是一个表示输出单词的独热向量。但是当您评估训练好的网络时输出向量实际上将是一个概率分布(即一组浮点值,而不是独热向量)。

隐藏层

在我们的例子中,我们将说我们正在学习具有300个特征的单词向量。因此,隐藏层将由一个具有10,000行(每个词汇中的一个单词)和300列(每个隐藏神经元一个)的权重矩阵表示。

300个特征是谷歌在其发布的模型中使用的,该模型是在谷歌新闻数据集上训练的(您可以从这里下载)。特征的数量是一个“超参数”(即每个单词的嵌入维度),您只需根据应用进行调整(即尝试不同的值,看看哪个能产生最佳结果)。

处理文本数据中的单词的一般方法是对文本进行独热编码。您的文本词汇中将有成千上万或数百万个唯一单词。对于这些单词,使用独热编码向量进行计算将非常低效,因为您独热向量中的大多数值将为0。因此,在独热向量和第一隐藏层之间进行的矩阵计算将导致输出大多数为0的值。

我们使用嵌入来解决这个问题,并大大提高网络的效率。嵌入就像一个全连接层。我们将这个层称为——嵌入层,将权重称为——嵌入权重。

现在,我们不再在输入和隐藏层之间进行矩阵乘法,而是直接从嵌入权重矩阵中获取值。我们可以这样做,因为独热向量与权重矩阵的乘法返回的是与“1”输入单元的索引对应的矩阵行。如果您查看这个权重矩阵的行,这些实际上就是我们的单词向量。

如果您将一个1 x 10,000的独热向量与一个10,000 x 300的矩阵相乘,它实际上只会选择与“1”对应的矩阵行。以下是一个小示例,以便您更直观地理解。

因此,我们将这个权重矩阵用作查找表。我们将单词编码为整数,例如“cool”编码为512,“hot”编码为764。然后,要获取“cool”的隐藏层输出值,我们只需查找权重矩阵中的第512行。这个过程称为嵌入查找。隐藏层输出的维度是嵌入维度。

请注意,在训练的最开始,嵌入矩阵中的所有权重都被初始化为随机值。

注意:—— 单词嵌入的质量随着维度的增加而提高。然而,在达到某个阈值后,边际收益将会减少。通常,向量的维度设置在100到1,000之间。

输出层

“ants”的1 x 300单词向量随后被输入到输出层。为了保证输出单词的概率表示,输出层使用softmax激活函数 ,并在训练期间采用以下误差函数E

同时,为了减少计算工作量,隐藏神经元使用线性激活函数,并且相同的权重用于嵌入所有输入(CBOW)或所有输出(Skip-gram)。

由于输出层是一个softmax回归分类器,因此每个输出神经元(每个词汇中的一个!)将产生一个介于0和1之间的输出,并且所有这些输出值的总和将加起来为1。具体来说,每个输出神经元都有一个权重向量,它与来自隐藏层的单词向量相乘,然后对结果应用函数exp(x)。最后,为了使输出总和为1,我们将这个结果除以所有10,000个输出节点的结果之和。上面是计算单词“car”的输出神经元输出的示意图。

请注意,在训练的最开始,输出矩阵中的所有权重都设置为0。

连续skip-gram或skip-gram与CBOW类似。它不是预测目标单词(wt),而是预测其上下文中的周围单词。训练目标是学习表示或嵌入,这些表示在预测附近单词时表现良好。

它还接受一个n数量的单词。例如,如果n=2,句子是“the dog is playing in the park”,那么输入到模型中的单词将是playing,目标单词将是(the,dog,is,in,the,park)。

3.2.2 GloVE(全局词向量表示)

GloVe来自斯坦福大学 结合了基于计数和基于预测的方法的优点,通过利用共现统计数据来训练词嵌入。通过优化全局词-词共现矩阵,GloVe生成的嵌入能够捕捉局部和全局的语义关系。

GloVe是一种无监督学习算法,通过分析文本语料库中词的共现统计数据来获得向量词表示。这些词向量捕捉了词的语义意义和词之间的关系。

GloVe的关键思想是通过检查整个语料库中词共现的概率来学习词嵌入。它构建了一个全局词-词共现矩阵,然后对其进行分解,以推导出表示词在连续向量空间中的词向量。

由于能够捕捉词之间的语义关系,这些词向量在自然语言处理(NLP)任务中得到了广泛应用。它们被用于机器翻译、情感分析、文本分类等各种应用中,在这些应用中,理解词的意义和上下文至关重要。

上下文理解使我们能够从周围的词中理解词的含义。

GloVe嵌入与其他嵌入技术(如Word2Vec FastText )一起被广泛使用,显著提高了NLP模型的性能。

GloVe词嵌入是如何创建的?

GloVe模型的基本方法是首先创建一个巨大的词-上下文共现矩阵,由(词,上下文)对组成,使得该矩阵中的每个元素表示一个词与上下文(可以是一个词序列)一起出现的频率。然后,应用矩阵分解来近似这个矩阵,如下图所示。

考虑词-上下文(WC)矩阵、词-特征(WF)矩阵和特征-上下文(FC)矩阵,我们尝试分解**WC = WF x FC**,旨在通过相乘重构WC。为此,我们通常用一些随机权重初始化WFFC,并尝试相乘以获得WC’(WC的近似值),并测量它与WC的接近程度。我们使用随机梯度下降(SGD)多次进行此操作,以最小化误差。最后,词-特征矩阵(WF)为每个词提供词嵌入,其中F可以预设为特定的维度。一个非常重要的点是,Word2Vec和GloVe模型在工作原理上非常相似。它们都旨在构建一个向量空间,其中每个词的位置受到其邻近词的上下文和语义的影响。Word2Vec从局部的单个词共现对开始,而GloVe则从语料库中所有词的全局聚合共现统计数据开始。

3.2.3 FastText

Word2Vec的局限性

虽然Word2Vec在自然语言处理领域是一个重大突破,但我们将看到仍然有改进的空间:

  • 词汇外(OOV)词
    在Word2Vec中,为每个单词创建了一个嵌入。因此,它无法处理在训练期间未遇到的任何单词。

例如,“tensor”和“flow”这样的单词在Word2Vec的词汇中是存在的。但如果你尝试获取复合词“tensorflow”的嵌入,你将会得到一个词汇外错误。

  • 形态学
    对于具有相同词根的单词,如“eat”和“eaten”,Word2Vec不进行任何参数共享。每个单词都是根据其出现的上下文独特学习的。因此,有利用单词内部结构来提高处理效率的空间。

为了解决上述挑战,Bojanowski等人 提出了一种新的嵌入方法,称为FastText。他们的关键见解是利用单词的内部结构来改善从skip-gram方法获得的向量表示。

对skip-gram方法的修改如下:

1. 子词生成

对于一个单词,我们生成长度为3到6的字符n-gram。

  • 我们取一个单词,并添加尖括号以表示单词的开始和结束。

  • 然后,我们生成长度为n的字符n-gram。例如,对于单词“eating”,可以通过从尖括号的开始滑动一个3个字符的窗口,直到到达结束尖括号来生成长度为3的字符n-gram。在这里,我们每次将窗口移动一步。

  • 因此,我们得到了一个单词的字符n-gram列表。

不同长度字符n-gram的示例如下:

  • 由于可能存在大量唯一的n-gram,我们应用哈希来限制内存需求。我们不为每个唯一的n-gram学习一个嵌入,而是学习总共B个嵌入,其中B表示桶的大小。论文使用了一个大小为200万的桶。

每个字符n-gram被哈希到1到B之间的一个整数。尽管这可能导致冲突,但它有助于控制词汇大小。论文使用Fowler-Noll-Vo哈希 函数的FNV-1a变体将字符序列哈希为整数值。

2. 带负采样的skip-gram

为了理解预训练,让我们以一个简单的玩具示例为例。我们有一个包含中心词“eating”的句子,需要预测上下文词“am”和“food”。

  1. 首先,通过对字符n-gram和整个单词本身的向量求和来计算中心词的嵌入。

  1. 对于实际的上下文词,我们直接从嵌入表中获取它们的词向量,而不添加字符n-gram。

  1. 现在,我们根据单词频率的平方根随机收集负样本。对于一个实际的上下文词,随机采样5个负词。

  1. 我们计算中心词与实际上下文词之间的点积,并应用sigmoid函数以获得0到1之间的匹配分数。

  2. 根据损失,我们使用SGD优化器更新嵌入向量,使实际上下文词更接近中心词,同时增加与负样本之间的距离。

3.3 上下文嵌入

上下文嵌入在学习单词之间的关系方面确实显示出了一些有希望的结果。

例如,

它们能够生成上下文感知的表示,这要归功于它们的 自注意力 机制。这使得嵌入模型能够根据单词使用的上下文动态生成嵌入。因此,如果一个单词出现在不同的上下文中,模型将获得不同的表示。

这在下面的图像中准确地描绘了“Bank”这个词的不同用法。

为了可视化的目的,嵌入已通过 t-SNE 投影到 2D 空间中。

静态嵌入模型——Glove 和 Word2Vec 对于单词的不同用法产生相同的嵌入。

然而,上下文化嵌入模型则不然。

实际上,上下文化嵌入理解“Bank”这个词的不同含义/意义:

  • 金融机构
  • 倾斜的土地
  • 长长的山脊,等等。

不同的意义来自普林斯顿的 Wordnet 数据库:WordNet

因此,它们解决了静态嵌入模型的主要局限性。

3.3.1 自注意力

将静态嵌入转换为动态上下文嵌入

该图展示了“Apple”一词在句子中的上下文如何改变其含义。这是通过上下文嵌入实现的,其中单词的表示根据周围单词进行调整。让我们分解这个例子。

左侧:“我吃了一个苹果”

  • 句子:“我吃了一个苹果。”
  • 上下文:在这里,“Apple”显然指的是水果。

这个方程展示了“Apple”的最终表示如何受到周围单词的影响:

  • 我:表示说话者的代词。
  • 吃了:表示吃的动作的动词。
  • 一个:支持单个项目上下文的冠词,通常是可数名词。
  • Apple:这个词本身,受到上下文的影响。

结果:“Apple”的向量在这里严重倾向于水果的含义,因为“吃了”和“一个”这两个词的影响。

右侧:“我买了一个苹果”

  • 句子:“我买了一个苹果。”
  • 上下文:在这个上下文中,“Apple”更可能指的是科技公司,暗示购买了像iPhone或MacBook这样的Apple产品。

这个方程展示了上下文如何改变含义:

  • 我:再次表示说话者的代词。
  • 买了:暗示购买的动词。
  • 一个:如前所述的冠词。
  • Apple:在这里,这个词转向产品或品牌的上下文。

结果: 在这个句子中,“Apple”的向量转向代表公司或其产品,因为“买了”提供的上下文。

上下文很重要: “Apple”这个词可以根据周围单词的不同而有不同的含义。在“我吃了一个苹果”中,它指的是水果。在“我买了一个苹果”中,它暗示来自Apple Inc.的产品。

上下文嵌入: 这些嵌入根据上下文调整“Apple”的向量,提供对每个句子中单词含义的更准确理解。这种方法有助于更自然地理解语言,捕捉静态嵌入所遗漏的细微差别。

现在我们需要为每个独特的单词创建这样的内容,就像我们为Apple所做的那样。

上下文嵌入通过提供根据上下文变化的单词表示,解决了静态嵌入的局限性。这些嵌入是由经过训练以理解单词出现上下文的深度学习模型生成的。

  • 上下文感知:单词的表示受到周围单词的影响,允许根据上下文有不同的含义。
  • 动态表示:单词根据在不同句子中的用法具有多种表示。
  • 增强的语义理解:上下文嵌入捕捉更复杂的关系和细微的含义。

该领域的一个关键进展是ELMo(来自语言模型的嵌入)的发展。ELMo考虑整个句子来建立单词的含义。它在各种NLP任务上的表现显著提高。

在ELMo之后,BERT(来自Transformers的双向编码器表示)进一步推动了这一概念。BERT分析单词与句子中所有其他单词的关系,而不是孤立地进行分析。这导致了更细致的语言模型。

3.3.2 BERT

Transformer模型,如BERT,使用注意力机制来权衡句子中所有单词的相关性。这对NLP中的特征提取来说是一个游戏规则的改变。它使得更准确的预测和更好地理解语言的细微差别成为可能。

BERT 是一个基于transformer的模型,学习单词的上下文化嵌入。它通过考虑单词的左侧和右侧上下文来考虑单词的整个上下文,从而生成捕捉丰富上下文信息的嵌入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from transformers import BertTokenizer, BertModel
import torch

# Load pre-trained BERT model and tokenizer
model_name = 'bert-base-uncased'
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertModel.from_pretrained(model_name)
word_pairs = [('learn', 'learning'), ('india', 'indian'), ('fame', 'famous')]

# Compute similarity for each pair of words
for pair in word_pairs:
tokens = tokenizer(pair, return_tensors='pt')
with torch.no_grad():
outputs = model(**tokens)

# Extract embeddings for the [CLS] token
cls_embedding = outputs.last_hidden_state[:, 0, :]
similarity = torch.nn.functional.cosine_similarity(cls_embedding[0], cls_embedding[1], dim=0)

print(f"Similarity between '{pair[0]}' and '{pair[1]}' using BERT: {similarity:.3f}")

3.3.3 ELMo

ELMo(来自语言模型的嵌入)是由艾伦人工智能研究所的研究人员开发的。ELMo 嵌入在问答和情感分析任务中显示了性能提升。官方论文 — https://arxiv.org/pdf/1802.05365.pdf

ELMo(来自语言模型的嵌入)在自然语言处理(NLP)的特征提取方面代表了一个重要的进步。这项技术利用深度的上下文化词嵌入来捕捉语法和语义,以及多义性——具有多重含义的词。

与传统的嵌入不同,ELMo 在周围文本的上下文中分析单词,从而实现更丰富的理解。以下是一个 Python 中的简化示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from allennlp.modules.elmo import Elmo, batch_to_ids

# Initialize ELMo
options_file = 'elmo_options.json'
weight_file = 'elmo_weights.hdf5'
elmo = Elmo(options_file, weight_file, num_output_representations=1)

# Example sentences
sentences = [['I', 'have', 'a', 'green', 'apple'], ['I', 'have', 'a', 'green', 'thumb']]

# Convert sentences to character ids
character_ids = batch_to_ids(sentences)

# Get ELMo embeddings
embeddings = elmo(character_ids)

ELMo 的动态词嵌入是一个游戏规则改变者,提供了基于上下文反映不同含义的细致词表示。这为更复杂的 NLP 应用铺平了道路,增强了特征提取及其他任务的性能。

在某些范围有限的用例中,训练自定义嵌入模型可能会带来好处。训练一个能够很好地泛化的嵌入模型可能是一项繁琐的工作。收集和预处理文本数据可能会很麻烦。训练过程也可能变得计算成本高昂。

对于任何构建 AI 系统的人来说,好消息是,一旦创建的嵌入也可以在任务和领域之间泛化。一些著名的预训练嵌入可供使用:

  1. OpenAI 的嵌入模型

OpenAI 是 ChatGPT 和 GPT 系列大型语言模型的背后公司,也提供三种嵌入模型:text-embedding-ada-002、text-embedding-3-small、text-embedding-3-large

可以通过 OpenAI API 访问 OpenAI 模型。

2. Google 的 Gemini 嵌入模型

text-embedding-004(最后更新于 2024 年 4 月)是 Google Gemini 提供的模型。可以通过 Gemini API 访问。

3. Voyage AI

Voyage AI 嵌入模型由 Anthropic 推荐,Anthropic 是 Claude 系列大型语言模型的提供者。Voyage 提供多个嵌入模型,如 voyage-large-2-instruct、voyage-law-2、voyage-code-2。

4. Mistral AI 嵌入

Mistral 是 Mistral 和 Mixtral 等大型语言模型的背后公司。他们提供一个名为 mistral-embed 的 1024 维嵌入模型。这是一个开源嵌入模型。

5. Cohere 嵌入

Cohere 是 Command、Command R 和 Command R+ LLM 的开发者,也提供多种嵌入模型,如 embed-english-v3.0、embed-english-light-v3.0、embed-multilingual-v3.0 等,并可以通过 cohere API 访问。

4. 训练词嵌入

词嵌入是由Bengio等人(2001, 2003)提出的,用于解决被称为维度诅咒的问题,这是统计语言建模中的一个常见问题。

事实证明,Bengio的方法可以训练一个神经网络,使得每个训练句子可以向模型提供多个语义上相邻的词,这被称为词的分布式表示。该神经网络不仅建立了不同词之间的关系,还在语义和句法特性方面保留了这些关系。

这引入了一种神经网络架构方法,为许多当前的方法奠定了基础。

该神经网络有三个组成部分:

  1. 嵌入层生成词嵌入,参数在词之间共享。
  2. 一个隐藏层,由一个或多个层组成,引入非线性到嵌入中。
  3. 一个softmax函数,对词汇表中的所有词生成概率分布。

让我们通过代码来理解神经网络语言模型是如何工作的。

(这里是Notebook 原始论文 的链接)

步骤1:对词进行索引。

我们首先对词进行索引。对于句子中的每个词,我们将为其分配一个数字。

1
2
3
4
5
6
7
8
9
10
import torch
import torch.nn as nn
import torch.optim as optim

raw_sentence = ["i like dog", "i love coffee", "i hate milk"]
word_list = " ".join(raw_sentence).split()
word_list = list(set(word_list))
word2id = {w: i for i, w in enumerate(word_list)}
id2word = {i: w for i, w in enumerate(word_list)}
n_class = len(word2id)

步骤2:构建模型。

我们将按照论文中描述的方式构建模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.embeddings = nn.Embedding(n_class, m) #嵌入层或查找表
self.hidden1 = nn.Linear(n_step * m, n_hidden, bias=False)
self.ones = nn.Parameter(torch.ones(n_hidden))
self.hidden2 = nn.Linear(n_hidden, n_class, bias=False)
self.hidden3 = nn.Linear(n_step * m, n_class, bias=False) #最终层
self.bias = nn.Parameter(torch.ones(n_class))

def forward(self, X):
X = self.embeddings(X) # 嵌入
X = X.view(-1, n_step * m) # 第一层
tanh = torch.tanh(self.d + self.hidden1(X)) # tanh层
output = self.b + self.hidden3(X) + self.hidden2(tanh) # 将所有层与偏差相加
return output

我们将首先初始化一个嵌入层。嵌入层是一个查找表。

一旦输入词的索引通过嵌入层嵌入,它将被传递到第一个隐藏层,并添加偏差。这两个的输出随后通过tanh函数。

如果你还记得原始论文中的图,嵌入层的输出也被传递到最终的隐藏层,在那里tanh的输出被相加。

1
output = self.b + self.hidden3(X) + self.hidden2(tanh)

现在,在最后一步,我们将计算整个词汇表的概率分布。

步骤3:损失和优化函数。

现在我们得到了模型的输出,我们需要确保将其通过softmax函数以获得概率分布。

我们使用交叉熵损失。

1
criterion = nn.CrossEntropyLoss()

交叉熵损失由两个方程组成:对数softmax函数和负对数似然损失或NLLLoss。前者计算softmax归一化,而后者计算负对数似然损失。

对于优化,我们使用Adam优化器。

步骤4:训练。

最后,我们训练模型。

简而言之,词嵌入可以定义为在低维空间中以向量形式表示词的密集表示。这些嵌入伴随着可学习的向量或参数化函数。它们在反向传播过程中使用损失函数进行自我更新,并试图找到词之间的良好关系,保留语义和句法特性。

Softmax函数

到目前为止,你已经看到softmax函数在预测给定上下文中的词方面发挥了重要作用。但它存在复杂性问题。

回想一下softmax函数的方程:

其中wt是目标词,c是上下文词,y是每个目标词的输出。

如果你查看上面的方程,softmax函数的复杂性在于预测变量的数量很高。如果i=3,那么softmax函数将返回三个类别的概率分布。

但是,在NLP中,我们通常处理成千上万,有时甚至数百万个词。对这么多词获取概率分布将使计算变得非常昂贵和缓慢。

请记住,softmax函数返回确切的概率分布,因此随着参数的增加,它们往往变得更慢。对于每个词(wt),它在分母中对整个词汇表进行求和。

有几种技术通常用于训练词嵌入。这些技术在学习词之间的语义关系以及计算效率和有效性方面各不相同。一些最流行的词嵌入训练技术包括:

  • CBOW(连续词袋模型): CBOW是一种用于根据其周围上下文预测目标词的技术 。在这种技术中,模型将周围词的窗口作为输入,并尝试预测窗口中心的目标词。这种技术高效且适用于较小的数据集。
  • Skip-gram: Skip-gram是一种与CBOW类似的技术,但它不是根据上下文预测目标词,而是根据目标词预测上下文词。在这种技术中,模型将目标词作为输入,并尝试预测周围的上下文词。Skip-gram比CBOW计算密集,但在较大数据集上表现更好。
  • 负采样: 负采样是一种用于解决不平衡训练数据问题的技术。在传统的CBOW和Skip-gram技术中,模型在一个大多数词对为负样本(即它们在语料库中不共现)的数据集上进行训练。负采样通过在训练期间为每个正样本抽取少量负样本来解决此问题。这种技术可以加速训练并提高词嵌入的质量。
  • 层次softmax: 层次softmax是一种用于加速词嵌入训练过程的技术。在传统训练方法中,模型必须为每个训练示例计算词汇表中每个词的概率。这在计算上可能非常昂贵,尤其是对于较大的词汇表。层次softmax通过使用二叉树 来表示词汇表上的概率分布,解决了这个问题。这种技术可以显著加快较大词汇表的训练时间。
  • 子词信息: 子词信息是一种用于改善稀有词和拼写错误词表示的技术。在这种技术中,模型不仅学习单个词的表示,还学习其子词成分(例如,前缀、后缀和词干)的表示。这可以提高模型处理词汇外词的能力,并减少拼写错误对词表示的影响。

这些技术可以结合使用,以提高词嵌入的质量和效率。选择适当的训练技术取决于数据集的大小和复杂性、所需的训练速度以及具体的NLP任务。

让我们详细讨论这些技术:

4.1 连续词袋(CBOW)模型

训练连续词袋(CBOW)模型对于获得能够有效捕捉给定语料库中语义关系的词嵌入至关重要。在本节中,我们将探讨CBOW模型的训练过程,包括数据预处理、构建上下文窗口、创建输入-输出对、定义神经网络架构以及优化模型参数。

  1. 数据预处理: 在训练CBOW模型之前,我们需要对文本数据进行预处理 。预处理步骤通常包括分词 、去除标点符号、将文本转换为小写字母以及处理特殊字符。此外,我们可能会去除停用词 (语义价值较低的常见词)并进行词干提取 词形还原 以减少词的变体到其基本形式。
  2. 构建上下文窗口: CBOW的核心思想是根据其周围的上下文词预测目标词。为此,我们定义一个上下文窗口大小,该大小决定了目标词两侧将被视为上下文词的词数。较大的上下文窗口允许模型捕捉更多的上下文,但可能会导致计算开销增加。
  3. 创建输入-输出对: 一旦定义了上下文窗口,我们就会在预处理的文本数据上滑动它。我们提取窗口内的上下文词,以便为每个目标词创建输入-输出对。例如,如果上下文窗口大小设置为2,句子为“The quick brown fox jumps”,则输入-输出对为:
  4. – 输入:[The, brown] 输出:quick
    – 输入:[quick, fox] 输出:brown
    – 输入:[brown, jumps] 输出:fox
  5. 定义神经网络架构: 我们可以使用准备好的输入-输出对定义CBOW神经网络架构。该架构通常由输入层、隐藏层和输出层组成。上下文窗口中的每个词将在输入层表示为独热编码向量 。隐藏层包含嵌入层,在此学习词的表示,输出层则预测目标词。
  6. 训练CBOW模型: 训练过程涉及将输入-输出对输入CBOW模型,并调整模型的参数以最小化预测误差。常见的优化算法,如随机梯度下降(SGD)或Adam ,在训练过程中更新模型的权重。优化过程旨在找到最佳捕捉语料库中词之间语义关系的词嵌入。
  7. Softmax激活函数: CBOW模型的输出层通常采用softmax激活函数 。Softmax将原始输出分数转换为概率分布,使模型能够预测给定上下文中最可能的词。目标词的独热编码向量与预测的概率分布进行比较,并通过网络反向传播 误差以更新模型的参数。
  8. 训练轮次和批量大小: 在训练过程中,我们多次遍历输入-输出对,这称为轮次。轮次的数量决定了训练数据集被处理的频率。此外,输入-输出对通常被分成批次,以加速训练并利用并行性。批量大小是一个超参数,控制每个训练步骤中处理的样本数量。
  9. 训练过程中的评估: 为了监控CBOW模型的训练进度并防止过拟合,在训练过程中评估模型在验证集上的表现至关重要。验证集包含与训练集不同的输入-输出对。通过评估模型在该集上的表现,我们可以判断其是否能够很好地泛化到未见数据,以及是否适合停止训练或进行调整。
  10. 超参数调优: 如前所述,CBOW有几个超参数,包括上下文窗口大小、嵌入维度、学习率和批量大小。超参数调优涉及系统地尝试不同的超参数组合,以找到在验证集上表现最佳的最优配置。

训练CBOW模型涉及对文本数据进行预处理、创建输入-输出对、定义神经网络架构以及使用优化算法优化模型的参数。通过在大规模文本数据上训练CBOW模型,我们获得了能够捕捉词之间上下文关系的词嵌入,使我们能够利用这些嵌入进行各种下游NLP任务,如词相似度 、文本分类和情感分析。CBOW模型的成功在于其高效生成有意义的词表示的能力,从而促进更好的语言理解并增强NLP应用的性能。

4.1.1 连续词袋模型 (CBOW) 与 Python 和 TensorFlow

使用 Python 实现连续词袋模型 (CBOW) 涉及设置环境、准备数据、创建 CBOW 神经网络架构、训练模型和评估其性能。以下是使用 Python 和 TensorFlow(一个流行的深度学习框架)实现 CBOW 的逐步指南。

1. 设置环境:确保安装 Python 和 TensorFlow。您可以使用 pip 安装 TensorFlow:

1
pip install tensorflow

2. 准备数据:加载您的文本语料库并进行预处理。对句子进行分词,去除标点符号,将文本转换为小写,并创建一个包含唯一单词的词汇表。为每个单词分配一个索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 示例语料库
corpus = [
"the quick brown fox jumps",
"over the lazy dog",
"hello world",
# 根据需要添加更多句子
]
# 分词并创建词汇表
tokenizer = Tokenizer()
tokenizer.fit_on_texts(corpus)
word_index = tokenizer.word_index
vocab_size = len(word_index) + 1

3. 创建输入-输出对:对于 CBOW,通过在句子上滑动上下文窗口来创建输入-输出对。上下文窗口的大小决定了目标单词两侧要考虑的上下文单词的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

context_window = 2
def generate_data(corpus, context_window, tokenizer):
sequences = tokenizer.texts_to_sequences(corpus)
X, y = [], []
for sequence in sequences:
for i in range(context_window, len(sequence) - context_window):
context = sequence[i - context_window : i] + sequence[i + 1 : i + context_window + 1]
target = sequence[i]
X.append(context)
y.append(target)
return np.array(X), np.array(y)
X_train, y_train = generate_data(corpus, context_window, tokenizer)

4. 创建 CBOW 模型架构:使用 TensorFlow 定义 CBOW 神经网络架构。该模型由一个嵌入层、一个全局平均池化层和一个密集输出层组成。

1
2
3
4
5
6
7
8
embedding_dim = 100

model = tf.keras.Sequential([
tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=context_window*2),
tf.keras.layers.GlobalAveragePooling1D(),
tf.keras.layers.Dense(vocab_size, activation='softmax')
])
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')

5. 训练 CBOW 模型:使用准备好的输入-输出对训练 CBOW 模型。

1
2
3
4
epochs = 50
batch_size = 16

model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size)

6. 评估 CBOW 模型:训练后,您可以在词义相似性任务、类比任务或任何其他特定的 NLP 评估任务上评估 CBOW 模型的性能。

1
2
3
# 如果有测试数据,则进行评估
test_loss, test_accuracy = model.evaluate(X_test, y_test, batch_size=batch_size)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")

使用 Python 和 TensorFlow 实现连续词袋模型 (CBOW) 涉及数据预处理、定义 CBOW 神经网络架构、训练模型和评估其性能。按照这些步骤,您可以使用 CBOW 创建词嵌入,并将其用于各种 NLP 任务,例如词义相似性、情感分析和文本分类。请记得根据您的特定 NLP 任务和数据集调整超参数、上下文窗口大小和其他设置,以获得最佳结果。

让我们通过代码了解 CBOW 模型的工作原理。

(这里是 Notebook 原始论文 的链接)

首先,我们不会将单词编码方法更改为数字。那将保持不变。

步骤 1:定义一个函数,从目标单词的左右各取 n 个单词创建上下文窗口。

1
2
3
4
5
6
7
def CBOW(raw_text, window_size=2):
data = []
for i in range(window_size, len(raw_text) - window_size):
context = [raw_text[i - window_size], raw_text[i - (window_size - 1)], raw_text[i + (window_size - 1)], raw_text[i + window_size]]
target = raw_text[i]
data.append((context, target))
return data

该函数应接受两个参数:数据和窗口大小。窗口大小将定义我们应该从左右各取多少个单词。

for 循环:for i in range(window_size, len(raw_text) — window_size): 从窗口大小开始迭代,即 2 意味着它将忽略句子中的索引 0 和 1,并在句子结束前 2 个单词结束。

在 for 循环内部,我们尝试将上下文和目标单词分开,并将它们存储在一个列表中。

例如,如果句子是“The dog is eating and the cat is lying on the floor”,则窗口为 2 的 CBOW 将考虑单词‘The’,‘dog’,‘eating’和‘and’。本质上使目标单词为‘is’。

让 i = 窗口大小 = 2,则:

1
2
context = [raw_text[2 - 2], raw_text[2 - (2 - 1)], raw_text[i + (2 - 1)], raw_text[i + 2]]
target = raw_text[2]

让我们调用该函数并查看输出。

1
2
data = CBOW(raw_text)
print(data[0])
1
2
输出:
(['The', 'dog', 'eating', 'and'], 'is')

步骤 2:构建模型。

构建 CBOW 模型与我们之前构建的 NNLM 类似,但实际上简单得多。

在 CBOW 模型中,我们将隐藏层减少到仅一个。因此,总体上我们有:一个嵌入层,一个通过 ReLU 层的隐藏层,以及一个输出层。

1
2
3
4
5
6
7
8
9
10
11
12
13
class CBOW_Model(torch.nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOW_Model, self).__init__()
self.linear1 = nn.Linear(embedding_dim, 128)
self.activation_function1 = nn.ReLU()
self.linear2 = nn.Linear(128, vocab_size)

def forward(self, inputs):
embeds = sum(self.embeddings(inputs)).view(1,-1)
out = self.linear1(embeds)
out = self.activation_function1(out)
out = self.linear2(out)
return out

这个模型非常简单。上下文单词索引被输入到嵌入层中,然后通过隐藏层,接着是非线性激活层,即 ReLU,最后我们得到输出。

步骤 3:损失和优化函数。

与 NNLM 类似,我们使用相同的技术计算词汇表中所有单词的概率分布,即 nn.CrossEntropyLoss()。

对于优化,我们使用随机梯度下降。您也可以使用 Adam 优化器。在 NLP 中,Adam 是首选优化器,因为它比 SGD 收敛更快。

1
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

步骤 4:训练

训练与 NNLM 模型相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
for epoch in range(50):
total_loss = 0

for context, target in data:
context_vector = make_context_vector(context, word_to_ix)
output = model(context_vector)
target = torch.tensor([word_to_ix[target]])
total_loss += loss_function(output, target)

# 在每个 epoch 结束时优化
optimizer.zero_grad()
total_loss.backward()
optimizer.step()

make_context_vector 将单词转换为数字。

值得注意的是,这篇论文的作者发现 NNLM 保留了单词之间的线性关系。例如,‘king’‘queen’‘men’‘women’ 是相同的,即 NNLM 保留了性别的线性关系。

同样,像 CBOW 这样的模型以及我们接下来要讨论的任何神经网络模型将保留线性关系,即使我们在神经网络中明确定义了非线性。

4.2 跳字模型

在这里,我们将提供一个关于如何使用跳字模型的逐步指南。

1. 数据准备:

  • 第一步是准备训练数据,通常是一个大型文本语料库。文本被分词 为单个单词,并可选择通过去除标点符号、转换为小写等进行预处理。

2. 上下文-目标对:

  • 跳字模型旨在预测训练数据中每个单词的周围上下文单词。上下文由窗口大小定义,窗口大小决定了在目标单词之前和之后被视为上下文单词的单词数量。
  • 考虑一个示例句子:“我喜欢吃披萨。”
  • 如果我们将窗口大小设置为2,那么“喜欢”这个词的上下文-目标对将是:
  • 上下文:[我, 向, 吃]
  • 目标:喜欢

同样,我们为训练数据中的所有单词创建上下文-目标对。

3. 神经网络架构:

  • 跳字模型由一个单一的隐藏神经网络和一个投影层组成。
  • 输入层表示目标单词,投影层表示词嵌入或向量表示。
  • 投影层具有与词汇表中每个单词对应的权重。每个权重向量表示该特定单词的词嵌入。
  • 投影层的大小(词嵌入的维度)是一个超参数,需要在训练之前指定。

4. 训练:

  • 训练跳字模型的目标是最大化在给定目标单词的情况下正确预测上下文单词的概率。
  • 这通常使用随机梯度下降 (SGD)或其他优化算法来完成。
  • 训练过程涉及更新投影层的权重,以最小化预测的上下文单词与实际上下文单词之间的损失。
  • 模型学习调整词嵌入,使得相似的单词在嵌入空间中具有相似的向量表示。

5. 词嵌入:

  • 一旦跳字模型训练完成,词嵌入将从投影层中提取。
  • 这些词嵌入捕捉了训练数据中单词之间的语义关系。
  • 词嵌入的维度由投影层的大小决定,可以根据计算效率和语义表达能力之间的期望权衡进行选择。
  • 词嵌入可以作为各种下游NLP任务的输入特征,或用于测量单词相似性、聚类单词和其他语言分析。

4.2.1 使用 Python 和 TensorFlow 的 Skip-Gram 模型

让我们通过代码了解 skip-gram 模型是如何工作的。

(这里是 Notebook 原始论文 的链接)

skip-gram 模型与 CBOW 模型相同,唯一的区别在于创建上下文和目标词。

步骤 1:设置目标和上下文变量。

由于 skip-gram 采用一个上下文词和 n 个目标变量,我们只需将之前模型中的 CBOW 反转即可。

1
2
3
4
5
6
7
8
def skipgram(sentences, window_size=1):
skip_grams = []
for i in range(window_size, len(word_sequence) - window_size):
target = word_sequence[i]
context = [word_sequence[i - window_size], word_sequence[i + window_size]]
for w in context:
skip_grams.append([target, w])
return skip_grams

如您所见,函数几乎是相同的。

在这里,您需要理解的是,当窗口大小为 1 时,我们取目标词前后各一个词。

当我们调用该函数时,输出看起来像这样:

1
print(skipgram(word_sequence)[0:2])
1
2
Output:
[['my', 'During'], ['my', 'second']]

如您所见,目标词是 ‘my’,而这两个词是 ‘During’‘second’

本质上,我们试图创建一对词,使得每对都包含一个目标词。根据上下文窗口,它将包含相邻的词。

步骤 2:构建模型。

模型非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
class skipgramModel(nn.Module):
def __init__(self):
super(skipgramModel, self).__init__()
self.embedding = nn.Embedding(voc_size, embedding_size)
self.W = nn.Linear(embedding_size, embedding_size, bias=False)
self.WT = nn.Linear(embedding_size, voc_size, bias=False)

def forward(self, X):
embeddings = self.embedding(X)
hidden_layer = nn.functional.relu(self.W(embeddings))
output_layer = self.WT(hidden_layer)
return output_layer

损失函数和优化保持不变。

1
2
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

一旦我们定义了所有内容,就可以训练模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
for epoch in range(5000):
input_batch, target_batch = random_batch()
input_batch = torch.Tensor(input_batch)
target_batch = torch.LongTensor(target_batch)

optimizer.zero_grad()
output = model(input_batch)
# output : [batch_size, voc_size], target_batch : [batch_size] (LongTensor, not one-hot)
loss = criterion(output, target_batch)
if (epoch + 1) % 1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
loss.backward()
optimizer.step()

skip-gram 模型增加了计算复杂性,因为它必须根据相邻词的数量来预测附近的词。距离较远的词与当前词的相关性往往稍微较低。

4.3 GloVE 模型

在自然语言处理的实际应用中实现 GloVe 嵌入涉及几个关键步骤,从访问预训练的嵌入到为特定任务进行微调:

  1. 访问预训练的 GloVe 嵌入

首先,获取预训练的 GloVe 嵌入是至关重要的。这些嵌入有多种维度(例如,50、100、300),并在大量文本语料库上进行训练。您可以从存储库或 GloVe 网站获取它们。

  1. 将 GloVe 嵌入加载到模型中

将下载的 GloVe 嵌入加载到您选择的平台或库中,例如 TensorFlow 或 PyTorch — 使用字典或嵌入矩阵将单词映射到其对应的向量。

  1. 在 NLP 模型中集成 GloVe 嵌入

将 GloVe 向量嵌入作为 NLP 模型中嵌入层的初始权重。例如,在 TensorFlow 中,这些嵌入是 Embedding 层的权重,使网络能够从这些预训练的表示中学习。

  1. 微调 GloVe 嵌入(可选)

根据任务的不同,微调 GloVe 嵌入可以优化模型性能。您可以将嵌入冻结(trainable=False)以保留其预训练特征,或在训练期间更新它们(trainable=True)以适应特定领域的细微差别。

  1. 针对特定 NLP 任务进行定制

为专业的 NLP 任务定制 GloVe 嵌入。例如,在情感分析或文本分类中,将这些嵌入输入到像 递归神经网络 (RNNs) 卷积神经网络 (CNNs) 的模型中,以分类情感或对文本进行分类。

  1. 评估和调整模型

使用验证集和与任务相关的指标(准确率、F1-score ,等)评估模型性能。调整超参数以提高模型的准确性和泛化能力,包括学习率、架构和嵌入维度。

  1. 迭代和优化

通过不同的方法进行迭代,尝试各种架构,并考虑集成技术以优化模型性能。为了优化结果,微调模型和 GloVe 嵌入。

在 NLP 模型中利用 GloVe 嵌入为其提供了丰富的语义表示,从而更好地理解文本数据。有效利用这些嵌入有助于在各种 NLP 应用中实现更优的性能,增强语言理解、情感分析和 信息检索系统

4.3.1 GloVe词嵌入在Python中的应用

在Python中使用GloVe嵌入涉及几个步骤。您可以训练自己的嵌入或使用预训练的嵌入。以下是使用预训练嵌入的基本概述:

  1. 下载预训练的GloVe嵌入

GloVe提供了在大型语料库上训练的预训练词向量。您可以从GloVe网站 或其他存储库下载它们。

  1. 将GloVe嵌入加载到Python中

下载后,您需要将这些嵌入加载到您的Python环境中。您可以直接使用嵌入,或将其转换为Python字典以便于访问。

1
2
3
4
5
6
7
8
9
10
# Load GloVe embeddings into a dictionary
def load_embeddings(file_path):
embeddings = {}
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
values = line.split()
word = values[0]
vector = np.asarray(values[1:], dtype='float32')
embeddings[word] = vector
return embeddings
1
2
glove_embeddings_path = 'path_to_glove_file/glove.6B.100d.txt'  # Adjust the path to your downloaded GloVe file
glove_embeddings = load_embeddings(glove_embeddings_path)
  1. 使用GloVe嵌入

加载后,您可以在各种NLP任务中使用这些嵌入。例如,查找特定词的嵌入或对词向量执行操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

# Accessing word embeddings
word = 'example'
if word in glove_embeddings:
embedding = glove_embeddings[word]
print(f"Embedding for '{word}': {embedding}")
else:
print(f"'{word}' not found in embeddings")

# Finding similarity between word embeddings
from scipy.spatial.distance import cosine
word1 = 'king'
word2 = 'queen'
similarity = 1 - cosine(glove_embeddings[word1], glove_embeddings[word2])
print(f"Similarity between '{word1}' and '{word2}': {similarity}")
  1. 在模型中使用GloVe嵌入

您可以将这些嵌入集成到您的NLP模型中,作为情感分析、文本分类或任何其他需要词表示的应用的输入特征。

请记得根据您的具体用例和下载的GloVe嵌入的维度(例如,glove.6B.100d.txt表示在60亿标记语料库上训练的100维向量)调整文件路径和方法。如果您的环境中尚未安装,请确保安装必要的依赖项,例如用于数组操作的NumPy和用于相似性计算的SciPy。

如何在Gensim中使用GloVe词嵌入

Gensim不直接支持训练GloVe嵌入,但它提供了一种方便的方法来加载预训练的GloVe嵌入并在Python中使用它们。以下是如何使用gensim加载预训练GloVe嵌入的简单指南:

首先,确保您已安装gensim。您可以通过pip安装:

1
pip install gensim

安装后,您可以使用gensim加载预训练的GloVe嵌入:

1
2
3
from gensim.test.utils import datapath, get_tmpfile
from gensim.models import KeyedVectors
from gensim.scripts.glove2word2vec import glove2word2vec
1
2
# Replace 'path_to_glove_file/glove.6B.100d.txt' with your GloVe file path
glove_file = 'glove.6B.100d.txt'
1
2
3
# Convert GloVe format to Word2Vec format
word2vec_temp_file = get_tmpfile("glove_word2vec.txt")
glove2word2vec(glove_file, word2vec_temp_file)
1
2
# Load GloVe embeddings using Gensim
glove_model = KeyedVectors.load_word2vec_format(word2vec_temp_file)

这段代码从指定的文件加载GloVe嵌入,并将其存储在glove_model中。

加载后,您可以使用加载的模型执行各种操作,例如查找特定词的向量或计算词之间的相似性:

1
2
3
4
5
6
7
# Example usage
word = 'example'
if word in glove_model:
embedding = glove_model[word]
print(f"Embedding for '{word}': {embedding}")
else:
print(f"'{word}' not found in embeddings")
1
2
3
4
word1 = 'king'
word2 = 'queen'
similarity = glove_model.similarity(word1, word2)
print(f"Similarity between '{word1}' and '{word2}': {similarity}")

这段代码示范了如何访问特定词的嵌入以及使用加载的GloVe模型查找两个词之间的相似性。

调整文件路径(glove_file)以指向您下载的GloVe文件,考虑到您使用的GloVe嵌入的特定维度(glove.6B.100d.txt表示在60亿标记语料库上训练的100维向量)。

到目前为止的总结:

  1. 神经网络语言模型(NNLM)或Bengio模型优于早期的统计模型,如n-gram模型。
  2. NNLM还解决了维度诅咒,并通过其分布式表示保留了上下文、语言规律和模式。
  3. NNLM计算成本高。
  4. Word2Vec模型通过去除隐藏层和共享权重来解决计算复杂性。
  5. Word2Vec的缺点是没有神经网络,这使得表示数据变得困难,但优点是如果可以在大量数据上进行训练,由于其效率远高于神经网络,因此可以计算出非常准确的高维词向量。
  6. Word2Vec有两种模型:CBOW和Skipgram。前者比后者更快。

6. 模型训练优化

那么,有哪些不同的方法可以使计算变得便宜且快速,同时确保近似值不被妥协呢?

在下一节中,我们将讨论可以减少计算时间的不同方法。我们将尝试在整个词汇表上进行近似,而不是获取精确概率。这将减少复杂性并提高处理速度。

我们将讨论两种方法:基于softmax的方法基于采样的方法。

6.1 改进预测功能

在本节中,我们探讨三种可能的方法来改进预测,通过修改 softmax 函数以获得更好的结果,并用新方法替换 softmax。

6.1.1 基于Softmax的方法

基于Softmax的方法更倾向于修改Softmax,以获得对预测词的更好近似,而不是完全消除它。我们将讨论两种方法:层次Softmax方法和CNN方法。

层次Softmax

层次Softmax是由Morin和Bengio在2005年提出的,作为完整Softmax函数的替代方案,它用一个层次结构来替代它。它借用了二进制哈夫曼树的技术,将计算整个词汇表V的概率的复杂度降低到*log2(V)*,即二进制。

编码哈夫曼树是非常复杂的。我会尽量不使用代码来解释,但你可以在这里找到笔记本 并尝试一下。

要理解H-softmax,我们需要理解哈夫曼树的工作原理。

哈夫曼树是一种二叉树,它从词汇表中获取单词;根据它们在文档中的频率,创建一棵树。

以这段文本为例:“the cat is eating and the dog is barking”。为了创建哈夫曼树,我们需要计算整个词汇表中单词的频率。

1
2
3
word_to_id = {w:i for i, w in enumerate(set(raw_text))}
id_to_word = {i:w for w, i in word_to_id.items()}
word_frequency = {w:raw_text.count(w) for w,i in word_to_id.items()}
1
print(word_frequency)
1
2
Output:
{'and': 1, 'barking': 1, 'cat': 1, 'dog': 1, 'eating': 1, 'is': 2, 'the': 2}

下一步是创建哈夫曼树。我们的方法是取出出现频率最低的单词。在我们的例子中,有很多单词只出现一次,因此我们可以随意选择两个。我们选择“dog”和“and”。然后,我们将这两个叶节点通过一个父节点连接起来,并添加频率。

在下一步中,我们将取另一个出现频率最低的单词(同样是只出现一次的单词),并将其放在具有两个单词频率之和的节点旁边。请记住,频率较低的单词放在左侧,频率较高的单词放在右侧。

同样,我们将继续构建单词,直到使用完词汇表中的所有单词。

请记住,所有频率最低的单词都在底部。

1
print(Tree.wordid_code)
1
2
3
4
5
6
7
8
Output:
{0: [0, 1, 1],
1: [0, 1, 0],
2: [1, 1, 1, 1],
3: [1, 1, 1, 0],
4: [0, 0],
5: [1, 1, 0],
6: [1, 0]}

一旦树创建完成,我们就可以开始训练。

在哈夫曼树中,我们不再计算输出嵌入w`。相反,我们尝试计算在每个叶节点向右或向左转的概率,使用sigmoid函数。

*p(right | n,c)=σ(h⊤w′n)*,其中n是节点,c是上下文。

正如你在下面的代码中看到的,使用sigmoid函数来决定是向右还是向左。还需要注意的是,所有单词的概率应该加起来等于1。这确保了H-softmax在词汇表中的所有单词上具有归一化的概率分布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class SkipGramModel(nn.Module):
def __init__(self, emb_size, emb_dimension):
super(SkipGramModel, self).__init__()
self.emb_size = emb_size
self.emb_dimension = emb_dimension
self.w_embeddings = nn.Embedding(2*emb_size-1, emb_dimension, sparse=True)
self.v_embeddings = nn.Embedding(2*emb_size-1, emb_dimension, sparse=True)
self._init_emb()

def _init_emb(self):
initrange = 0.5 / self.emb_dimension
self.w_embeddings.weight.data.uniform_(-initrange, initrange)
self.v_embeddings.weight.data.uniform_(-0, 0)

def forward(self, pos_w, pos_v,neg_w, neg_v):

emb_w = self.w_embeddings(torch.LongTensor(pos_w))
neg_emb_w = self.w_embeddings(torch.LongTensor(neg_w))
emb_v = self.v_embeddings(torch.LongTensor(pos_v))
neg_emb_v = self.v_embeddings(torch.LongTensor(neg_v))
score = torch.mul(emb_w, emb_v).squeeze()
score = torch.sum(score, dim=1)
score = F.logsigmoid(-1 * score)
neg_score = torch.mul(neg_emb_w, neg_emb_v).squeeze()
neg_score = torch.sum(neg_score, dim=1)
neg_score = F.logsigmoid(neg_score)
# L = log sigmoid (Xw.T * θv) + [log sigmoid (-Xw.T * θv)]
loss = -1 * (torch.sum(score) + torch.sum(neg_score))
return loss

6.1.2 基于采样的方法

基于采样的方法完全消除了 softmax 层。

我们将讨论两种方法:噪声对比估计和负采样。

噪声对比估计

噪声对比估计(NCE)是一种近似方法,它替代了 softmax 层并降低了计算成本。它通过将预测问题转换为分类问题来实现这一点

本节将包含大量的数学解释。

NCE 采用一个未归一化的多项式函数(即具有多个标签且其输出未经过 softmax 层的函数),并将其转换为二元逻辑回归。

为了学习从某个特定上下文 © 预测目标词 (wt) 的分布,我们需要创建两个类别:正类负类。正类包含来自训练数据分布的样本,而负类包含来自噪声分布 Q 的样本,我们分别将它们标记为 1 和 0。噪声分布是训练集的单语分布

对于每个给定上下文的目标词,我们从分布 Q 生成样本噪声 *Q(w)*,使其比来自数据分布 P(w | c) 的样本频率高 k 倍。

这两个概率分布可以表示为彼此的总和,因为我们实际上是从这两个分布中抽样单词。因此,

如前所述,NCE 是一个二元分类器,真实标签为 ‘1’,虚假标签为 ‘0’。直观上,

当 y=1 时,

当 y=0 时,

我们的目标是开发一个具有参数 θ 的模型,使得给定上下文 c 时,其预测概率 P(w,c) 近似于原始数据分布 *Pd(w,c)*。

通常,噪声分布是通过抽样来近似的。我们通过生成 k 个噪声样本 {wij}: 来实现这一点:

其中 Zθ© 是来自 softmax 的归一化项,您会记得这是我们试图消除的。我们可以通过将其设为可学习参数来消除 Zθ©。本质上,我们将 softmax 函数从绝对值,即对词汇表中的所有单词进行多次求和的值,转变为一个动态值,以便找到更好的自我——它是可学习的。

但是,事实证明,Mnih 等人(2013)指出 Zθ© 可以固定为 1。尽管它再次是静态的,但它的归一化效果相当好,Zoph 等人(2016)发现 Zθ©=1 产生了一个低方差的模型。

我们可以用 exp(sθ(w | c)) 替换 *Pθ(w | c)*,使得损失函数可以写成:

需要记住的一点是,随着噪声样本数量 k 的增加,NCE 的导数接近似然梯度,或归一化模型的 softmax 函数。

总之,NCE 是通过将数据分布与噪声分布进行比较来学习数据分布的一种方法,并修改学习参数,使得模型 几乎等于 Pd

负采样

理解 NCE 是重要的,因为负采样是其修改版本。它也是一种更简化的版本。

首先,我们了解到,随着噪声样本数量 k 的增加,NCE 的导数接近似然梯度,或归一化模型的softmax 函数

负采样的工作方式是,通过用 1 替代噪声来消除噪声。直观上,

y=1 时,

在负采样中,我们使用 sigmoid 函数,因此我们将上述方程转换为:

我们知道 Pθ(w | c) 被替换为 *exp(sθ(w | c))*。

因此,

这使得方程更短。它必须计算 1 而不是噪声,因此方程变得计算高效。但我们为什么要简化 NCE 呢?

一个原因是我们关注词向量的高表示性,因此只要模型生成的词嵌入保持其质量,就可以简化模型。

如果我们用上述方程替换最终的 NCE 方程,我们得到:

由于 log(1)=0

因此,

由于我们处理的是 sigmoid 函数,即

我们可以将上述方程修改为:

(这里是 Notebook 原始论文 的链接)

7. 部署词嵌入模型的注意事项

  • 在部署模型时,您需要使用与创建词嵌入训练数据时相同的管道。如果使用不同的分词器或处理空格、标点符号等的方法,可能会导致输入不兼容。
  • 输入中没有预训练向量的单词。这些单词被称为 Out of Vocabulary(OOV) Words. 您可以将这些单词替换为“UNK”,表示未知,然后单独处理它们。
  • 维度不匹配:向量可以有多种长度。如果您使用长度为 400 的向量训练模型,然后在推理时尝试应用长度为 1000 的向量,您将遇到错误。因此,请确保在整个过程中使用相同的维度。

8. 如何选择嵌入模型?

自从ChatGPT发布和被恰当地描述为LLM战争的到来以来,嵌入模型的开发也进入了疯狂的竞争。评估LLM和嵌入的标准不断演变。对于“使用哪个嵌入模型?”这个问题没有正确答案。然而,您可能会发现某些嵌入在特定用例(如摘要、文本生成、分类等)中表现更好。

OpenAI过去会根据不同的用例推荐不同的嵌入模型。然而,现在他们推荐text-embeddings-3用于所有任务。

Hugging Face的MTEB Leaderboard评估了几乎所有可用的嵌入模型,涵盖七个用例——分类、聚类、配对分类、重排序、检索、语义文本相似性(STS)和摘要。

另一个重要的考虑因素是成本。使用OpenAI模型时,如果处理大量文档,可能会产生显著的费用。开源模型的成本将取决于具体的实现。

9. 结论

词嵌入的旅程已经从简单的独热编码演变为先进的基于变换器的模型。从提供静态嵌入的Word2Vec和GloVe等方法开始,该领域已经转向了具有上下文嵌入的ELMo、BERT和GPT,使得对人类语言的理解和生成更加细致和复杂。这一演变反映了在捕捉人类语言复杂性方面的重大进展,最终形成了当代LLM和变换器的强大能力。

10. 测试你的知识!

  1. 你如何解释两个词向量之间的余弦相似度,它对词之间的关系有什么意义?
    — 预期答案:余弦相似度衡量的是向量空间中两个向量之间角度的余弦值。余弦相似度接近1表示向量彼此接近,可能代表具有相似含义的词。余弦相似度为0表示向量正交,意味着这些词无关。负余弦相似度则暗示相反的含义。

2. 讨论向量空间表示在捕捉多义性和同音异义词方面的局限性。现代嵌入技术如何解决这些局限性? — 预期答案:传统的向量空间模型如Word2Vec为每个词分配一个单一向量,这无法捕捉多义性(一个词的多重含义)和同音异义词(发音相同但含义不同的词)。现代嵌入技术如上下文嵌入(例如BERT)通过根据上下文生成相同词的不同向量来解决这个问题,有效捕捉不同的含义。

3. 在训练词嵌入时,使用大词汇量和减少词汇量之间的权衡是什么? — 预期答案:大词汇量允许模型捕捉更广泛的词汇和细微差别,但增加了计算复杂性和内存需求。减少词汇量可以加快训练速度并减少资源使用,但可能会遗漏重要的词或细微差别,从而导致对不常见词的嵌入效果较差。

4. 在什么情况下你会更倾向于使用基于采样的方法(如负采样)而不是全软最大化来训练词嵌入? — 预期答案:在处理大型数据集和词汇时,负采样更受欢迎,因为它通过仅更新一小部分权重显著降低了计算成本。特别是在关注捕捉词之间的相似性而不是对词汇的完整分布建模时(如Word2Vec模型),负采样尤其有用。

5. BERT嵌入与Word2Vec嵌入在捕捉词义和上下文方面有什么不同? — 预期答案:BERT嵌入是上下文相关的,这意味着它们根据周围的上下文为相同的词生成不同的嵌入。这使得BERT能够根据词在句子中的使用情况捕捉词的动态含义。相比之下,Word2Vec生成静态嵌入,每个词都有一个单一的向量表示,与上下文无关。

6. 解释FastText如何通过结合子词信息来改进Word2Vec。这对稀有词的嵌入质量有什么影响? — 预期答案:FastText通过将词表示为字符n-gram的集合来改进Word2Vec,这使得它能够通过组合子词的嵌入来生成词的嵌入。这种方法有助于为稀有或超出词汇表的词生成更好的嵌入,因为它可以利用子词信息,即使在完整词数据稀缺时也能创建有意义的表示。

7. 讨论分层软最大化的使用及其对大型数据集训练效率的影响。 — 预期答案:分层软最大化是一种在大型词汇设置中近似软最大化函数的技术。它将词汇结构化为二叉树,使模型能够以对数时间而非线性时间计算概率。这大大提高了训练效率,特别是在处理非常大的数据集和词汇时,通过减少所需的计算次数。

8. 解释优化特定领域应用的嵌入面临的挑战,以及你将如何应对这些挑战。 — 预期答案:特定领域的应用可能需要捕捉独特于该领域的细微差别的嵌入,而通用嵌入可能会遗漏这些细微差别。挑战包括有限的特定领域数据、词汇不匹配和微调的需求。为了解决这些问题,可以使用迁移学习,即在特定领域数据上微调通用模型,或使用特定领域语料库从头开始训练嵌入。

9. 你将如何评估词嵌入在特定任务(如情感分析)中的质量?你会使用哪些指标? — 预期答案:词嵌入在情感分析中的质量可以使用内在和外在指标进行评估。内在评估包括词相似性或类比任务等任务。外在评估涉及在下游任务(如情感分析)中使用嵌入,并使用准确率、F1分数或AUC等指标来衡量性能。嵌入在特定任务上下文中区分正面和负面情感词的能力至关重要。

10. 讨论在评估词嵌入质量时,仅依赖类比任务等内在评估指标的潜在陷阱。 — 预期答案:内在指标如类比任务通常测试嵌入的几何特性,但可能与下游任务性能相关性不佳。它们可能给人一种虚假的质量感,因为它们没有考虑特定应用的具体要求。例如,在类比任务中表现良好的嵌入可能仍然无法捕捉情感分析或命名实体识别所需的细微差别。因此,外在评估提供了对特定任务嵌入质量的更实际的衡量标准。

11. 在低延迟环境中部署上下文嵌入(如BERT)时可能面临哪些挑战,你将如何缓解这些挑战? — 预期答案:在低延迟环境中部署BERT面临挑战,因为其庞大的规模和计算复杂性可能导致推理时间缓慢。缓解策略包括模型蒸馏(在保持性能的同时减少模型大小)、量化(降低模型权重的精度),或使用更高效的变体如DistilBERT或ALBERT。此外,缓存常见短语的嵌入或使用两阶段方法,在将输入传递给BERT之前先由更简单的模型过滤输入,也可以帮助减少延迟。

12. 在生产中部署词嵌入模型时,你将如何处理超出词汇表(OOV)词的问题? — 预期答案:处理OOV词可以通过使用结合子词信息的模型(如FastText)来实现,该模型可以通过将OOV词拆分为已知子词来生成嵌入。另一种方法是使用上下文模型如BERT,它可以根据上下文推断OOV词的含义。在某些情况下,你还可以维护一个后备机制,将OOV词映射到表示未知词的通用向量,或通过字符级嵌入或哈希等技术进行处理。

10.1 自助服务

  1. 选择哪个嵌入模型以及原因。你将如何选择嵌入模型的大小?
  2. 什么是“维度诅咒”,它与自然语言处理有什么关系?
  • 标题: LLM 架构解析词嵌入第 2 部分
  • 作者: Barry
  • 创建于 : 2024-08-25 19:06:26
  • 更新于 : 2024-08-31 06:59:45
  • 链接: https://wx.role.fun/2024/08/25/3dfdd6edaba84592a743ab0f6f698335/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。