自然语言处理 (NLP) 基础
文本预处理
原始的文本数据充满了各种“噪音”和“冗余”,直接将其用于机器学习模型通常效果不佳。文本预处理的目标是将原始文本转换为一种更干净、更结构化、更适合模型学习的形式。
1. 分词 (Tokenization)
- 定义: 将连续的文本字符串分割成一系列有意义的单元 (tokens) 的过程。这些单元通常是词语、数字、标点符号,或者在某些情况下是子词 (subwords) 或字符。
- 核心思想: 文本的基本意义单元是词语。为了让计算机能够处理文本,首先需要将文本分解成这些基本单元。
- 重要性: 分词是大多数 NLP 任务的第一步,后续的处理和分析都是基于分词后的结果进行的。
- 挑战:
- 语言差异:
- 英文等空格分隔的语言: 通常以空格和标点符号作为分隔符,相对简单。但仍需处理连字符(如 "state-of-the-art")、缩写(如 "don't" -> "do", "n't")、数字、特殊符号等。
- 中文、日文、泰文等无明显分隔符的语言: 词与词之间没有空格,分词更为复杂,需要依赖词典和统计模型(如隐马尔可夫模型 HMM、条件随机场 CRF)或深度学习模型来识别词的边界。
- 歧义性: 有些词串可能有多种分词方式,导致不同的含义。例如,中文的“研究生命科学”可以分成“研究生 / 命 / 科学”或“研究 / 生命 / 科学”。
- 未登录词 (Out-of-Vocabulary, OOV): 词典中没有的词,如新词、网络用语、专有名词等。
- 语言差异:
- 输出: 一个 token 列表(或序列)。
- 通俗例子:
- 英文句子: "The quick brown fox jumps over the lazy dog."
- 分词后:
["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog", "."]
- 中文句子: “我爱自然语言处理。”
- 分词后:
["我", "爱", "自然语言处理", "。"]
(也可能分成["我", "爱", "自然", "语言", "处理", "。"]
,取决于分词工具和策略)
- 常用工具:
- 英文: NLTK (
nltk.word_tokenize
), spaCy, Stanford CoreNLP, 正则表达式。 - 中文: Jieba (结巴分词), spaCy (需要中文模型), HanLP, SnowNLP, THULAC (清华大学), PKUSeg (北京大学)。
- 现代 LLM Tokenizers: BPE (Byte Pair Encoding), WordPiece, SentencePiece,它们进行子词级别的分词,能更好地处理未登录词和罕见词。
- 英文: NLTK (
2. 词干提取 (Stemming)
- 定义: 一种粗略地将词语的屈折形式(如复数、时态变化)还原为其词干 (stem) 或词根 (root) 形式的过程。词干本身可能不是一个真实存在的有效词语。
- 核心思想: 具有相同词干的词语通常具有相似的含义。通过将它们归一化到相同的词干,可以减少词汇表的大小,并将语义上相近的词视为同一个词,有助于后续的分析(如信息检索、文本分类)。
- 方法: 通常基于一套启发式的后缀剥离规则 (suffix-stripping rules)。它会机械地去除词语末尾的常见后缀(如 "-s", "-es", "-ing", "-ed", "-ly" 等)。
- 优点:
- 实现简单,计算速度快。
- 能够有效地减少词形变化带来的冗余。
- 缺点:
- 过度提取 (Over-stemming): 有时会错误地将不同含义的词归到同一个词干,或者将词干切得过短,丢失了原有的含义。例如,"universe" 和 "university" 可能都被提取为 "univers"。
- 提取不足 (Under-stemming): 有时无法将应该归到一起的词提取到同一个词干。例如,"data" 和 "datum" 可能不会被归一化。
- 词干可能不是有效词: 例如,"studies" 和 "studying" 可能被提取为 "studi",这并不是一个实际的英文单词。
- 输出: 词语的词干。
- 通俗例子:
- "running" -> "run"
- "jumps" -> "jump"
- "flies" -> "fli" (可能不是一个有效词)
- "connection", "connected", "connects" -> "connect"
- "argue", "argued", "argues", "arguing" -> "argu" (Porter Stemmer 的结果)
- 常用算法/工具:
- Porter Stemmer: 最早也是最广泛使用的英文词干提取算法之一。
- Snowball Stemmer (Porter2 Stemmer): Porter Stemmer 的改进版,支持更多语言。
- Lancaster Stemmer: 另一个英文词干提取算法,比 Porter 更激进。
- NLTK 和 spaCy 等库中都包含了这些词干提取器的实现。
3. 词形还原 (Lemmatization)
- 定义: 将词语的屈折形式转换为其词典标准形式 (lemma) 或词元 (lexeme) 的过程。词元是一个有效的、存在于词典中的词语。
- 核心思想: 与词干提取类似,也是为了将词语的不同形式归一化,但词形还原更加基于词汇学和形态学分析,力求得到一个有意义的、符合语言规则的词元。
- 方法:
- 通常需要一个包含词形及其对应词元的词典 (lexicon)。
- 需要考虑词语的词性 (Part-of-Speech, POS)。同一个词在不同词性下可能有不同的词元。例如,"meeting"作为名词,其词元是 "meeting";作为动词 "meet" 的现在分词,其词元是 "meet"。
- 优点:
- 结果更准确: 产生的词元是真实的、有意义的词语,更符合语言学规范。
- 语义保留更好: 相较于词干提取,能更好地保留词语的原始含义。
- 缺点:
- 计算复杂度更高: 因为需要查词典和进行词性分析,所以比词干提取慢。
- 依赖高质量的词典和词性标注器: 如果词典不全或词性标注不准,词形还原的效果会受影响。
- 输出: 词语的词元。
- 通俗例子:
- "running" (动词) -> "run"
- "jumps" (动词) -> "jump"
- "flies" (名词复数) -> "fly"
- "flies" (动词第三人称单数) -> "fly"
- "better" (形容词比较级) -> "good" (需要词性信息)
- "meeting" (名词) -> "meeting"
- "meeting" (动词进行时) -> "meet"
- "are", "is", "was", "were" -> "be"
- 常用工具:
- WordNet Lemmatizer (NLTK): 基于 WordNet 词典进行词形还原。
- spaCy: 内置了高效的词形还原功能,通常会自动进行词性标注。
- Stanford CoreNLP.
词干提取 vs. 词形还原:
特性 | 词干提取 (Stemming) | 词形还原 (Lemmatization) |
---|---|---|
目标 | 粗略地切掉后缀,得到词干 | 转换为词典标准形式 (词元) |
方法 | 基于规则的后缀剥离 | 基于词典和词性分析 |
结果准确性 | 较低,词干可能不是有效词 | 较高,词元是有效词 |
计算速度 | 快 | 慢 |
语义保留 | 可能丢失部分语义 | 更好地保留语义 |
依赖 | 较少依赖外部资源 | 依赖词典和词性标注器 |
例子 | "studies" -> "studi" | "studies" (动词) -> "study" |
如何选择词干提取和词形还原?
- 如果对速度要求很高,且能容忍一定的精度损失(例如,在一些信息检索的早期阶段): 可以选择词干提取。
- 如果需要更准确的、符合语言学规范的结果,且对语义的保留要求较高(例如,在机器翻译、问答系统、文本生成等任务中): 应该选择词形还原。
- 在许多现代 NLP 应用中,词形还原通常是更受青睐的选择,尽管它计算成本更高。
- 对于某些任务,可能两者都不需要,或者只需要简单的文本规范化(如转小写)。
4. 停用词去除 (Stop Word Removal)
- 定义: 从文本中移除那些非常常见但通常不携带太多实际语义信息的词语的过程。这些词语被称为停用词 (stop words)。
- 核心思想: 停用词在文本中出现频率很高,但对于理解文本的核心内容贡献不大。去除它们可以:
- 减少数据维度: 降低后续处理的计算复杂度。
- 突出重要词汇: 让模型更关注那些真正携带信息的关键词。
- 提高模型性能: 在某些任务(如文本分类、信息检索)中,去除停用词可能有助于提高模型的准确性和效率。
- 常见的停用词:
- 英文: "a", "an", "the", "is", "are", "was", "were", "of", "in", "on", "at", "to", "for", "and", "but", "or", "I", "you", "he", "she", "it", "we", "they" 等。
- 中文: “的”、“了”、“在”、“是”、“我”、“你”、“他”、“她”、“它”、“们”、“和”、“与”、“或”、“也”、“都”、“着”、“地”、“得” 等。
- 方法: 通常维护一个预定义的停用词列表 (stop word list),然后遍历分词后的 token 序列,将出现在停用词列表中的 token 移除。
- 注意事项:
- 没有通用的停用词列表: 停用词列表可能因语言和具体应用场景而异。例如,在分析法律文档时,“shall” 可能是一个重要的词,但在普通对话中可能是停用词。
- 可能丢失信息: 在某些情况下,停用词可能携带重要的语义信息或影响句子的流畅性。例如,在情感分析中,“not” 是一个非常重要的词,不应该被当作停用词去除。在需要理解完整句子结构的任务(如机器翻译、文本生成)中,通常不去除停用词。
- 需要谨慎使用: 是否去除停用词以及使用哪个停用词列表,需要根据具体的 NLP 任务和实验结果来决定。
- 输出: 一个移除了停用词的 token 列表。
- 通俗例子:
- 原始句子: "This is a sample sentence to demonstrate stop word removal."
- 停用词列表可能包含: "this", "is", "a", "to"
- 去除停用词后:
["sample", "sentence", "demonstrate", "stop", "word", "removal", "."]
- 常用工具:
- NLTK, spaCy, Gensim 等库都提供了常见的英文停用词列表,并支持自定义。
- 对于中文,可以自己维护列表,或者使用一些中文 NLP 库提供的列表(如哈工大停用词表、百度停用词表)。
文本预处理的整体流程(简化版):
- 文本获取 (例如,从文件、网页、数据库中读取)
- (可选)文本清洗 (例如,去除 HTML 标签、特殊字符、转小写等)
- 分词 (Tokenization) -> 得到 token 序列
- (可选)停用词去除 (Stop Word Removal) -> 得到去除停用词的 token 序列
- (可选)词干提取 (Stemming) 或 词形还原 (Lemmatization) -> 得到词干或词元的 token 序列
- (后续步骤)转换为数值表示 (例如,词袋模型、TF-IDF、词嵌入) -> 送入机器学习模型
Embedding基础
好的,我们来详细介绍文本特征提取中两种非常经典且基础的技术:词袋模型 (Bag-of-Words, BoW) 和 TF-IDF (Term Frequency-Inverse Document Frequency)。这两种方法都是将文本转换为机器学习模型可以理解的数值化表示。
1. 词袋模型 (Bag-of-Words, BoW)
-
核心思想:
- 忽略文本中的词序和语法结构,只关注每个词在文本中出现的频率。
- 将每篇文档(或句子)看作是一个装满了词语的“袋子”,袋子里的词语是无序的。
- 用一个向量来表示这篇文档,向量的每个维度对应词典中的一个词,该维度的值表示这个词在文档中出现的次数(或其他度量,如是否出现)。
-
构建步骤:
-
收集语料库 (Corpus): 获取所有需要处理的文本数据(文档集合)。
- 例子语料库:
- 文档1: "猫 喜欢 吃 鱼"
- 文档2: "狗 喜欢 吃 肉"
- 文档3: "猫 和 狗 都 喜欢 宠物 零食"
- 例子语料库:
-
分词 (Tokenization): 对语料库中的每篇文档进行分词,得到词语列表。
- 例子分词结果:
- 文档1:
["猫", "喜欢", "吃", "鱼"]
- 文档2:
["狗", "喜欢", "吃", "肉"]
- 文档3:
["猫", "和", "狗", "都", "喜欢", "宠物", "零食"]
- 文档1:
- 例子分词结果:
-
构建词典 (Vocabulary / Dictionary): 收集语料库中所有出现过的不重复的词语,形成一个词典。词典中的每个词都有一个唯一的索引。
- 例子词典 (假设按出现顺序):
{ "猫": 0, "喜欢": 1, "吃": 2, "鱼": 3, "狗": 4, "肉": 5, "和": 6, "都": 7, "宠物": 8, "零食": 9 }
词典大小为 10。
- 例子词典 (假设按出现顺序):
-
文本向量化 (Vectorization): 对于每篇文档,创建一个与词典大小相同的向量。向量的第
i
个元素表示词典中第i
个词在该文档中的某种度量。- 常用的度量方法:
- 词频 (Term Frequency, TF): 向量的每个元素是对应词在文档中出现的次数。
- 例子 (使用词频):
- 文档1:
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
(猫:1, 喜欢:1, 吃:1, 鱼:1, 其他:0) - 文档2:
[0, 1, 1, 0, 1, 1, 0, 0, 0, 0]
(狗:1, 喜欢:1, 吃:1, 肉:1, 其他:0) - 文档3:
[1, 1, 0, 0, 1, 0, 1, 1, 1, 1]
(猫:1, 喜欢:1, 狗:1, 和:1, 都:1, 宠物:1, 零食:1, 其他:0)
- 文档1:
- 例子 (使用词频):
- 布尔值 (Boolean / Binary): 向量的每个元素是1(如果词出现)或0(如果词未出现)。
- 例子 (使用布尔值):
- 文档1:
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
- 文档2:
[0, 1, 1, 0, 1, 1, 0, 0, 0, 0]
- 文档3:
[1, 1, 0, 0, 1, 0, 1, 1, 1, 1]
- 文档1:
- 例子 (使用布尔值):
- N-gram 模型 (N-gram Model): 除了单个词,还可以将连续的 N 个词(N-grams,如 "自然 语言", "喜欢 吃")作为词典中的条目,以捕捉一些局部顺序信息。例如,2-gram(bigram)词袋模型。
- 词频 (Term Frequency, TF): 向量的每个元素是对应词在文档中出现的次数。
- 常用的度量方法:
-
-
输出: 每篇文档被表示为一个数值向量。整个语料库可以表示为一个文档-词项矩阵 (Document-Term Matrix),其中行代表文档,列代表词典中的词,矩阵元素是词在文档中的度量值。
-
通俗解释: 想象你要根据一段话的内容来判断它大概是讲什么的。
- 你先把你所有可能遇到的词列一个清单(构建词典)。
- 然后,对于每一段话,你都不管这些词是怎么排列组合的,你就数一下清单上的每个词在这段话里出现了几次(或者有没有出现)。
- 最后,这段话就被你用一串数字(每个数字对应清单上一个词的出现情况)来表示了。
-
优点:
- 简单直观,易于实现。
- 在很多基础的文本分类、聚类任务中效果不错。
- 可以作为更复杂模型的输入。
-
缺点:
- 忽略词序和语法结构: "猫喜欢鱼" 和 "鱼喜欢猫" 在词袋模型中可能表示相同或非常相似,但它们的含义完全不同。
- 无法捕捉语义信息: 它只关心词的出现频率,不理解词语之间的语义关系(例如,“高兴”和“开心”是近义词,但在词袋模型中是两个独立的维度)。
- 维度灾难 (Curse of Dimensionality): 如果词典非常大,生成的向量维度会非常高,导致计算量大且数据稀疏。
- 未考虑词的重要性: 像 "的"、"是" 这样的常见词(停用词,如果未去除)会获得很高的频率,但它们通常不携带太多区分性信息。
-
常用工具:
- Scikit-learn:
sklearn.feature_extraction.text.CountVectorizer
(用于实现词频或布尔值的词袋模型)。
- Scikit-learn:
2. TF-IDF (Term Frequency-Inverse Document Frequency)
-
核心思想:
- 对词袋模型的一种改进,旨在评估一个词对于一篇文档(在一个语料库中)的重要性。
- 一个词的重要性会随着它在当前文档中出现的次数增加而增加,但会随着它在整个语料库中出现的文档数增加而减少。
- 也就是说,如果一个词在某篇文档中频繁出现,但在其他文档中很少出现,那么这个词被认为对这篇文档有很好的区分度,权重就高。反之,如果一个词在很多文档中都频繁出现(比如常见的停用词),那么它的区分度就低,权重就低。
-
组成部分:
-
词频 (Term Frequency, TF):
- 定义: 衡量一个词
t
在某篇文档d
中出现的频繁程度。 - 计算方法有多种:
- 原始计数:
TF(t, d) = 词 t 在文档 d 中出现的次数
。 - 归一化计数:
TF(t, d) = (词 t 在文档 d 中出现的次数) / (文档 d 中的总词数)
。这样做是为了消除文档长度差异带来的影响。 - 对数归一化:
TF(t, d) = log(1 + 词 t 在文档 d 中出现的次数)
。用于平滑词频的影响,避免高频词权重过大。
- 原始计数:
- 定义: 衡量一个词
-
逆文档频率 (Inverse Document Frequency, IDF):
- 定义: 衡量一个词
t
在整个语料库中的普遍程度(或稀有程度)。IDF 越大,说明包含该词的文档越少,该词的区分能力越强。 - 计算方法:
IDF(t, D) = log( (语料库 D 中的总文档数 N) / (包含词 t 的文档数 df(t)) )
N
: 语料库中的总文档数量。df(t)
: 包含词语t
的文档数量。- 平滑处理: 为了避免分母
df(t)
为零(如果一个词从未在语料库中出现,虽然不太可能在计算IDF时遇到,因为通常基于已有词典)或df(t) = N
(所有文档都包含该词,导致log(1)=0
),通常会对公式进行平滑处理,例如:IDF(t, D) = log( (N + 1) / (df(t) + 1) ) + 1
(一种常见的平滑方式) 或者 Scikit-learn 默认的IDF(t, D) = log( N / df(t) ) + 1
(如果smooth_idf=True
且use_idf=True
,则N
和df(t)
都会加1)。
- 特性:
- 如果一个词在很多文档中都出现,
df(t)
就会很大,N/df(t)
接近1,log
值接近0,IDF 就小。 - 如果一个词只在少数文档中出现,
df(t)
就会很小,N/df(t)
较大,IDF 就大。
- 如果一个词在很多文档中都出现,
- 定义: 衡量一个词
-
TF-IDF 权重计算:
- 定义: 将词频 TF 和逆文档频率 IDF 相乘,得到一个词在一个文档中的 TF-IDF 权重。
- 公式:
TF-IDF(t, d, D) = TF(t, d) * IDF(t, D)
-
-
输出: 与词袋模型类似,每篇文档被表示为一个数值向量,向量的每个维度对应词典中的一个词,该维度的值是这个词在该文档中的 TF-IDF 权重。整个语料库也可以表示为一个文档-词项矩阵 (Document-Term Matrix)。
-
通俗解释: 想象你在一堆文件中找一份关于“人工智能在医疗领域的应用”的报告。
- TF (词频): "人工智能" 这个词在这份报告里出现了 20 次,说明它在这份报告里挺重要的。 "的" 这个词可能出现了 100 次,但它不那么重要。
- IDF (逆文档频率):
- "人工智能" 这个词可能只在少数几份关于科技的报告中出现,所以它的 IDF 值比较高,说明它有区分性。
- "的" 这个词几乎在所有中文报告里都会出现,所以它的 IDF 值很低,说明它没什么区分性。
- TF-IDF: 把 TF 和 IDF 乘起来。
- 对于 "人工智能":高 TF * 高 IDF = 很高的 TF-IDF 值,说明它在这份报告中既常出现,又具有独特性,是这份报告的关键词。
- 对于 "的":很高 TF * 很低 IDF = 较低的 TF-IDF 值,说明它虽然常出现,但太普遍了,不是关键词。
- 如果某个词(比如“外科手术”)在这份报告里只出现了一两次 (低 TF),但在整个文件库里也很少见 (高 IDF),它的 TF-IDF 值可能也会比较适中,表明它对这份报告也有一定的指示性。
-
优点:
- 考虑了词的重要性: 相较于简单的词袋模型,TF-IDF 能够更好地衡量一个词对文档的区分能力,降低了常见词的权重,提升了稀有但重要的词的权重。
- 简单有效: 实现相对简单,并且在很多文本挖掘任务(如信息检索、文本分类、文档聚类)中表现良好。
-
缺点:
- 仍然忽略词序和语法结构。
- 仍然无法捕捉语义信息: "高兴" 和 "开心" 依然是两个独立的维度。
- 对于某些特定领域的专业术语,如果它们在领域内的文档中都频繁出现,IDF 值可能会偏低,导致其重要性被低估。
- 依赖于一个固定的语料库来计算 IDF 值。 如果语料库发生变化,IDF 值也需要重新计算。
-
常用工具:
- Scikit-learn:
sklearn.feature_extraction.text.TfidfVectorizer
(一步完成分词、构建词典、计算TF-IDF权重)。 - Gensim 库也提供了 TF-IDF 的实现。
- Scikit-learn:
词袋模型 vs. TF-IDF:
特性 | 词袋模型 (BoW) | TF-IDF |
---|---|---|
权重计算 | 通常是词频或布尔值 | 词频 (TF) * 逆文档频率 (IDF) |
词的重要性 | 未直接考虑,高频常见词权重可能过高 | 考虑了词在当前文档的频率和在整个语料库的稀有度,更能反映词的重要性 |
对常见词处理 | 容易受常见词(停用词)影响,除非预先去除 | 能够自动降低常见词的权重 |
区分能力 | 较弱 | 较强,更能突出文档的关键词 |
复杂度 | 更简单 | 略复杂,需要计算 IDF |
总结:
词袋模型和 TF-IDF 都是将文本转换为数值向量的基石方法。词袋模型非常简单,只关注词的出现情况。TF-IDF 则在此基础上,通过引入逆文档频率来更好地衡量词的重要性,从而得到更能反映文档主题和内容的特征表示。尽管它们都有忽略词序和语义的局限性,但在许多 NLP 任务中仍然是非常有用的特征提取技术,并且可以作为更高级方法(如词嵌入)的输入或对比基线。
词袋模型 (BoW) 和 TF-IDF 的用途
尽管现代 NLP 更多地转向基于深度学习的词嵌入 (Word Embeddings),但 BoW 和 TF-IDF 仍然有其用武之地,并且在理解文本表示的发展历程中非常重要:
-
作为简单有效的基线模型 (Baseline Models):
- 在进行复杂的 NLP 项目时,通常会先用 BoW 或 TF-IDF 配合传统的机器学习模型(如逻辑回归、朴素贝叶斯、SVM)快速搭建一个基线系统。
- 这有助于了解问题的难度,并为后续更复杂的模型(如使用深度学习和词嵌入的模型)提供一个比较的基准。如果复杂模型的表现还不如简单的基线,那可能说明模型设计或数据处理有问题。
-
信息检索 (Information Retrieval):
- TF-IDF 在搜索引擎中长期扮演着核心角色。通过计算查询语句中词语与文档中词语的 TF-IDF 相似度,可以对文档进行排序,返回最相关的结果。
- 虽然现代搜索引擎结合了更复杂的算法,但 TF-IDF 的思想仍然有其价值。
-
文本分类和聚类 (Text Classification and Clustering):
- 对于一些主题明确、文本特征相对简单的分类或聚类任务,BoW 或 TF-IDF 仍然可以取得不错的效果,而且计算成本较低。
-
文档相似度计算:
- 可以将文档表示为 BoW 或 TF-IDF 向量,然后通过计算向量之间的余弦相似度等来衡量文档间的相似程度。
-
关键词提取 (Keyword Extraction):
- TF-IDF 值高的词通常可以被认为是文档的关键词。
-
教学和理解:
- 它们是理解更高级文本表示方法(如词嵌入)的基础。通过学习 BoW 和 TF-IDF 的优缺点,可以更好地理解为什么需要以及如何设计更先进的方法。
好的,我们来详细解释一下词嵌入 (Word Embeddings) 的概念,以及几种经典的静态词嵌入方法:Word2Vec (Skip-gram, CBOW)、GloVe 和 FastText,并重点理解它们是如何将词语映射到低维向量空间的。
什么是词嵌入 (Word Embeddings)?
在自然语言处理中,我们不能直接把文字喂给机器学习模型。我们需要一种方法将文本转换为计算机可以理解的数值形式。词袋模型 (BoW) 和 TF-IDF 是早期的方法,但它们有局限性(忽略词序、无法捕捉语义)。
词嵌入的核心思想是:用一个低维、稠密的向量来表示每个词语,并且这个向量能够捕捉到词语的语义信息。
这意味着:
- 语义相似的词,其对应的词向量在向量空间中的位置也应该相近。 例如,“国王”和“女王”的向量会比较接近,“狗”和“猫”的向量也会比较接近。
- 词向量之间可能存在一些有趣的线性关系。 例如,著名的例子是
vector("国王") - vector("男人") + vector("女人") ≈ vector("女王")
。
如何将词语映射到低维向量空间?—— 核心思想:分布式假设 (Distributional Hypothesis)
大多数词嵌入方法的理论基础都源于语言学中的分布式假设,其核心观点是:“一个词的含义由它经常出现的上下文所决定 (You shall know a word by the company it keeps)”。
也就是说,如果两个词经常出现在相似的上下文中,那么这两个词的语义就可能是相似的。词嵌入算法就是利用这个思想,通过分析大量文本数据中词语的共现模式来学习每个词的向量表示。
1. Word2Vec (Google, 2013)
Word2Vec 是一组用于学习词嵌入的相关模型。它不是单一的算法,而是包含两种主要的模型架构:Skip-gram 和 CBOW (Continuous Bag-of-Words)。这两种模型都是浅层的神经网络。
核心思想:通过预测任务来学习词向量。
- 初始化: 为词典中的每个词随机初始化一个向量。
- 训练: 通过一个预测任务来调整这些向量。如果一个词的向量能够很好地完成这个预测任务,那么这个向量就被认为是该词的一个好的表示。
a. Skip-gram (SG)
- 任务:给定一个中心词 (Center Word),预测它周围的上下文词 (Context Words)。
- 通俗解释: 想象你在玩一个填空游戏。我给你一个词,比如“敏捷的”,然后让你猜这个词旁边通常会出现哪些词,比如“狐狸”、“猫”、“思维”等等。Skip-gram 模型就是学习做这个预测。
b. CBOW (Continuous Bag-of-Words)
- 任务:给定一个词的上下文词,预测这个中心词。
- 通俗解释: 还是填空游戏,这次我把一个词周围的词告诉你,比如“敏捷的 __ 棕色的”,让你猜中间的词是什么,可能是“狐狸”或“猫”。CBOW 模型就是学习做这个预测。
2. GloVe (Global Vectors for Word Representation, Stanford, 2014)
- 核心思想: Word2Vec 是基于局部上下文窗口进行学习的,而 GloVe 认为词向量的学习应该基于全局的词共现统计信息 (Global Co-occurrence Statistics)。它试图直接从语料库的词-词共现矩阵中学习词向量。
- 通俗解释:
想象你在分析一大堆书籍,你想知道哪些词经常一起出现。
- 你先统计任意两个词(比如“国王”和“王冠”,“猫”和“抓”)在一定的窗口内(比如同一句话或附近几个词)共同出现的次数。
- GloVe 的目标是找到每个词的向量表示,使得那些经常一起出现的词,它们的向量之间的某种关系(点积加上偏置)能够很好地反映它们共同出现的频繁程度(共现次数的对数)。
3. FastText (Facebook, 2016)
- 核心思想: 对 Word2Vec (特别是 Skip-gram) 的扩展,它将每个词看作是其字符 n-gram (character n-grams) 的集合,并为每个字符 n-gram 学习一个向量表示。一个词的最终向量是其所有字符 n-gram 向量的和 (或平均)。
- 通俗解释:
想象你在学习一个新词,比如 "unbelievable"。
- Word2Vec 可能直接把 "unbelievable" 作为一个整体来学习它的向量。
- FastText 则会把这个词拆成更小的部分,比如 "un-", "believ-", "-able", "unb", "nbe", "bel", "eli", ... 等等,以及整个词 "unbelievable"。
- FastText 会学习这些小片段的含义(向量),然后把这些小片段的含义加起来,就得到了 "unbelievable" 这个词的整体含义(向量)。
总结:如何将词语映射到低维向量空间?
这些词嵌入方法的核心都是利用大规模文本数据中词语的上下文共现信息,通过不同的预测任务或优化目标,来学习每个词的分布式表示 (Distributed Representation),即那个低维、稠密的向量。
- Word2Vec (CBOW & Skip-gram): 通过一个浅层神经网络,让模型学习预测一个词的上下文,或者根据上下文预测一个词。在这个过程中,网络中间层的权重(即词向量)被不断调整,使得那些经常出现在相似上下文中的词,其向量也变得相似。
- GloVe: 直接对全局的词-词共现统计数据进行建模,试图让词向量的点积能够很好地拟合词语共同出现的频率。它更直接地利用了全局信息。
- FastText: 扩展了 Word2Vec 的思想,将词分解为字符 n-gram,学习这些子词单元的向量,然后组合起来得到整个词的向量。这使得它能处理未登录词并更好地理解词的内部结构。
共同点:
- 无监督学习: 它们都可以在大量无标签的文本数据上进行训练。
- 分布式表示: 词的含义被“分布”在向量的多个维度上,而不是像独热编码那样每个词一个独立的维度。
- 学习语义: 最终学习到的向量能够捕捉到词语之间的语义和句法关系。
这些静态词嵌入方法为后续更复杂的上下文相关词嵌入(如 ELMo, BERT, GPT)奠定了重要的基础。虽然现在 Transformer 模型产生的上下文嵌入更强大,但理解这些经典方法的思想对于理解整个 NLP 领域的发展仍然非常重要。
BGE 等现代 Embedding 模型的核心技术:Transformer
目前主流的、效果最好的开源 embedding 模型(如 BGE、Sentence-BERT、SimCSE、E5、Instructor 等)几乎无一例外都是基于 Transformer 架构的。
它们与经典静态词嵌入的主要区别在于:
-
上下文感知 (Context-Aware):
- 经典静态词嵌入: 一个词(例如 "bank")无论出现在什么句子中,它的词嵌入向量都是固定不变的。
- Transformer 기반 Embedding 模型: 一个词的嵌入向量会根据它在句子中的具体上下文动态生成。例如,“river bank”(河岸)中的 "bank" 和 “savings bank”(储蓄银行)中的 "bank" 会有不同的嵌入向量。这是通过 Transformer 的自注意力机制 (Self-Attention) 实现的,它允许模型在编码每个词时考虑到句子中所有其他词的信息。
-
深度神经网络:
- 经典静态词嵌入: 通常是浅层神经网络(如 Word2Vec 只有一个隐藏层)。
- Transformer 기반 Embedding 模型: 是深度神经网络,通常包含多层 Transformer Encoder(编码器)堆叠而成。更深的网络结构使其能够学习到更复杂、更抽象的语义表示。
-
预训练与微调 (Pre-training and Fine-tuning):
- 经典静态词嵌入: 主要通过无监督学习(如预测上下文)在大量文本上预训练得到。
- Transformer 기반 Embedding 模型:
- 大规模预训练: 首先在海量的通用文本数据上进行无监督或自监督的预训练(例如 BERT 的掩码语言模型 MLM 和下一句预测 NSP,或者其他对比学习、自回归预测等任务)。这个阶段学习到通用的语言理解能力。
- 特定任务微调 (Fine-tuning for Embeddings): 仅仅进行通用预训练得到的表示,虽然强大,但可能不是最优的句子/段落级别嵌入。因此,像 BGE 这样的模型通常会在预训练的基础上,使用特定的目标函数和数据集进行针对句子相似度、文本检索等任务的微调。常用的微调方法包括:
- 对比学习 (Contrastive Learning): 如 SimCSE,目标是让语义相似的句子对在向量空间中更接近,不相似的句子对更疏远。
- 三元组损失 (Triplet Loss): 选择一个锚点句子、一个正例句子(相似)和一个负例句子(不相似),目标是让锚点与正例的距离小于锚点与负例的距离。
- 使用有监督的句子对数据: 例如,使用 NLI (Natural Language Inference) 数据集,其中包含前提、假设和标签(蕴含、矛盾、中立),或者使用问答对、释义对等。
-
输出表示的级别:
- 经典静态词嵌入: 主要输出单个词的向量。要得到句子或段落的表示,通常需要对词向量进行简单平均或加权平均。
- Transformer 기반 Embedding 模型: 它们通常被设计用来直接输出整个句子或段落的固定长度的稠密向量表示。这通常是通过对 Transformer 最后一层输出的特定 token(如
[CLS]
token 的隐藏状态)进行池化 (Pooling,如平均池化或最大池化) 得到的。
BGE 模型的内部实现(基于推测和通用实践):
根据其发布信息(BAAI - 北京智源人工智能研究院)和当前主流的 embedding 模型实践,我们可以推测其内部:
- 基础架构: 极大概率是基于 Transformer Encoder 架构,类似于 BERT、RoBERTa 等。
- 预训练: 可能在一个非常大规模的中英文语料库上进行了通用的语言模型预训练。
- 针对 Embedding 的微调: 这是关键!BGE 之所以能成为优秀的 embedding 模型,是因为它在通用预训练之后,针对生成高质量的句子/段落嵌入进行了专门的微调。它可能使用了:
- 大量的有监督或无监督的句子对数据(例如,语义相似的句子对、不相似的句子对)。
- 对比学习损失函数或其他针对表示学习的损失函数,目标是让语义相似的文本在嵌入空间中距离更近。
- 可能还利用了如 hard negatives mining 等技术来提高微调的效果。
- 输出: 最终模型能够接收一段文本作为输入,并输出一个固定维度的稠密向量,这个向量可以很好地代表该文本的语义。
关于 PyTorch 实现:
像 BGE 这样先进的开源 embedding 模型,其核心实现几乎肯定是基于 PyTorch (或者 TensorFlow/JAX,但 PyTorch 在研究和开源社区中更为流行)。
- 模型构建: 使用 PyTorch 的
nn.Module
来定义 Transformer Encoder 的各个组件(多头注意力、前馈网络、层归一化等)。 - 训练过程: 使用 PyTorch 的优化器 (AdamW 很常用)、损失函数 (自定义的对比学习损失等)、
DataLoader
等进行模型训练和微调。 - Hugging Face Transformers 库: 大多数开源的 LLM 和 embedding 模型都会集成到 Hugging Face Transformers 库中。这个库提供了基于 PyTorch (和 TensorFlow) 的标准化接口,使得用户可以方便地加载预训练模型、进行推理或进一步微调。BGE 模型在 Hugging Face Model Hub 上就有提供,这进一步印证了其与主流深度学习框架的兼容性。
总结:
现代高性能的开源 embedding 模型(如 BGE)不是简单地使用 Word2Vec、GloVe 或 FastText 这样的经典静态词嵌入方法。它们是基于强大的 Transformer 架构,经过大规模通用预训练,并针对生成高质量句子/段落嵌入进行了专门的微调。
No comments to display
No comments to display