数据预处理(Data Preprocessing) #
自然语言预处理(Natural Language Preprocessing) #
大语言模型(Large Language Models, LLMs)是基于深度学习的人工智能技术,能够理解和生成自然语言文本。这些模型通常以Transformer架构为核心,经过大规模的文本数据预训练,从而掌握语法、语义和上下文推理能力。LLMs的关键特点在于其通用性,不仅可以执行文本生成、翻译、问答等自然语言处理(NLP)任务,还能够通过微调适应特定领域需求,如医疗诊断、金融分析和法律文书撰写。在现代应用中,LLMs常被用作聊天机器人(Chatbot)、智能助理(Agent)、检索增强生成(Retrieval-Augmented Generation, RAG)系统的核心组件,同时也为复杂的多模态交互和自动化决策提供支持。随着技术的进步,LLMs通过集成增强知识检索、多模态融合和高效部署方法,正逐步成为AI驱动型产品开发中的核心工具。
数据准备与预处理(Data Preparation & Preprocessing) #
数据准备与预处理流程从原始文本到模型输入的完整链路可归纳为:
- 数据预处理:通过清洗(移除噪声、特殊符号、冗余数字)、分词(按词/子词/字符粒度切分)、编码(One-Hot/TF-IDF/标签编码等)将文本转化为结构化数值;
- 嵌入表示:基于编码结果生成低维稠密向量,传统方法(Word2Vec/GloVe)学习静态词向量,深度模型(BERT/Transformer)生成上下文动态向量;
- 模型适配:将向量输入任务模型(分类器/生成器)前,需进行标准化(归一化/降维)、序列对齐(填充/截断)及数据划分(训练/验证/测试集),最终完成端到端训练。
文本清理(Text Cleaning) #
文本数据清洗是自然语言处理(NLP)中至关重要的第一步,其目标是将原始文本转化为干净、结构化的输入数据。
- 噪声去除(Noise Removal):删除或替换文本中无意义、干扰性的字符或片段。
- 结构化标签去除
- HTML/XML标签:网页文本中常包含
<div>
,<a href>
等标签,需完全删除。- 处理方法:正则表达式(如
re.sub(r'<.*?>', '', text)
)或专用库(如BeautifulSoup
)。
- 处理方法:正则表达式(如
- Markdown标记:删除
**粗体**
、![图片]()
等格式符号。
- HTML/XML标签:网页文本中常包含
- 特殊符号处理
- 无用符号:如版权符号(
©
)、商标符号(®
)、乱码字符(�
)。 - 保留符号:感叹号(
!
)、问号(?
)等可能携带情感或语义的符号需保留。
- 无用符号:如版权符号(
- 链接与用户提及
URL:http://
或www.
开头的链接需删除(如re.sub(r'http\S+', '', text)
)。- 社交媒体标签:删除
@用户名
或#话题
(如re.sub(r'[@#]\w+', '', text)
)。
- 冗余空白处理
- 合并多个空格为单个空格:
re.sub(r'\s+', ' ', text)
。 - 删除首尾空格:
text.strip()
。
- 合并多个空格为单个空格:
- 结构化标签去除
- 文本规范化(Text Normalization):将文本转化为一致的格式,消除非标准变体。
- 大小写统一
- 常规做法:全部转为小写(
text.lower()
)。 - 例外场景:
- 专有名词(如产品名“iPhone”需保留大写)。
- 情感分析中大写可能表示强调(如“LOVE” vs “love”)。
- 常规做法:全部转为小写(
- 数字处理策略
- 直接删除:当数字不携带语义时(如通用文本中的随机数字)。
- 替换为标记:统一为
<NUM>
(适用于分类任务)。 - 保留特殊格式:日期(2023-08-20)、金额($199)需按需处理。
- 缩写与拼写校正
- 缩写展开:
- 规则库映射(如 “don’t” → “do not”, “I’m” → “I am”)。
- 拼写纠错:
- 规则方法:
pyenchant
库检测并建议修正。 - 深度学习方法:BERT等模型预测上下文正确拼写。
- 规则方法:
- 缩写展开:
- 表情符号与颜文字
- 删除:当任务不需要情感信号时(如法律文本分析)。
- 转换文字描述:使用
emoji
库将😊转为“笑脸”(保留语义)。
- 大小写统一
- 语言与编码处理
- 多语言文本处理
- 语言检测:使用
langdetect
库过滤非目标语言文本。 - 混合语言处理:中英文混杂时需统一分词策略(如“Apple发布会”需切分为[“Apple”, “发布”, “会”])。
- 语言检测:使用
- 编码标准化
- Unicode规范化:统一为NFC格式(避免字形相同但编码不同的问题)。
- 处理乱码:检测并删除无法解码的字节(如
text.encode('utf-8', 'ignore').decode('utf-8')
)。
- 多语言文本处理
分词(Tokenization) #
分词(Tokenization)的目标是将连续的自然语言文本切分为有语义的离散单元(Token)。分词的粒度与质量直接影响模型对语义的理解能力。其核心目标包括:语义单元提取(将文本分割为模型可理解的原子单元(如词、子词、字符)),跨语言兼容性(适应不同语言的分词规则(如中文无空格、德语复合词)),未登录词(OOV)处理(解决词典未覆盖的新词或罕见词问题)。基础的分词方法有:
- 基于规则的分词:
- 空格分词:适用于英语等以空格分隔的语言,但对连字符(state-of-the-art)、缩写(Mr.)处理不佳。
- 正则表达式:自定义模式匹配,如切分带连字符的复合词(
r'\w+-\w+'
)。 - 最大匹配法(MaxMatch):从右向左或从左向右扫描,选择词典中最长的匹配词。
- 缺点:无法解决歧义(如“南京市长江大桥”可能误切为“南京市长/江/大桥”)。
- 子词分词(Subword Tokenization):将词分解为更小的可重用单元(子词),平衡词典大小与OOV问题。
- BPE(Byte-Pair Encoding) 是一种基于频率统计的子词分词算法
- 其训练过程分为两个阶段:首先将文本拆分为单个字符作为初始词汇表,随后迭代合并出现频率最高的相邻字符对,逐步扩展子词单元。
- 例如,高频组合“e”和“s”可能被合并为“es”,最终形成包含高频完整词和可读子词的词典。BPE的特点在于通过频率驱动合并,能够保留常见词的完整性(如“ing”作为整体),同时生成具有可解释性的子词(如“un”和“friend”组合成“unfriend”)。
- 这一方法在生成式模型中得到广泛应用,例如 GPT系列模型通过BPE处理文本,有效平衡词典规模与未登录词(OOV)问题。
- WordPiece 的核心理念与BPE相似,但合并策略更注重语义完整性。
- 其训练过程同样从基础单元(如字符)开始,但选择合并的标准并非单纯依赖频率,而是通过计算合并后对语言模型概率的提升幅度,优先保留能够增强语义连贯性的子词。
- 例如,若合并“##ing”比拆分更符合上下文概率,则将其作为独立单元。这种策略使得WordPiece生成的子词更贴近自然语言形态(如保留“##ly”作为后缀),从而在理解任务中表现更优。
- BERT模型即采用WordPiece分词,通过动态上下文编码实现高效的语义捕捉。
- SentencePiece 是一种更通用的分词框架,其核心创新在于直接处理原始文本(包括空格和特殊符号),无需依赖预分词步骤。
- 它支持两种底层算法:BPE或基于概率的Unigram Language Model。训练时,SentencePiece将空格视为普通字符,可 直接处理多语言混合文本(如中英文混杂),并自动学习跨语言的统一子词划分规则。例如,中文句子“我喜欢NLP”可能被切分为“我/喜/欢/N/L/P”,其中“”表示空格。
- 这一特性使其在需要多语言支持的场景(如T5模型)中表现突出,同时简化了数据处理流程,特别适合处理社交媒体文本等非规范化输入。
- BPE(Byte-Pair Encoding) 是一种基于频率统计的子词分词算法
停用词(Stopwords)、词干提取(Stemming)和句子处理(Sentence Processing) #
停用词(Stopwords):停用词是指在文本处理中经常出现、但对 NLP 任务贡献较小的词。这些词通常是介词、冠词、代词、连词、助动词等,例如:
- 英语:the, is, at, which, on, in, a, an, and, but, or
- 中文:的, 了, 在, 是, 和, 有, 也, 与, 都
去除停用词的作用主要有:
- 降维(Dimensionality Reduction):许多 NLP 任务(如文本分类)只关心关键信息,去除停用词可以减少词表大小,提高计算效率。
- 减少噪声(Noise Reduction):在 TF-IDF 计算或文本聚类等任务中,停用词可能会干扰语义分析,因为它们频繁出现但不提供额外信息。
- 提高模型效率(Efficiency Improvement):停用词可能会增加计算复杂度,而它们的去除可以使得 NLP 模型在更少的特征上训练,提高训练和推理速度。
词干提取(Stemming):词干提取是一种规则化处理方法,通过截取单词的词根,去掉变形部分(如时态、复数、动名词后缀),使得同一词根的不同变体归一化。常见 Stemming 算法有
- Porter Stemmer(最常用):
- running → run
- flies → fli
- happiness → happi(去掉 “-ness”)
- Porter Stemmer(最常用):
词形还原(Lemmatization):词形还原(Lemmatization)通过词典映射将单词还原为词典中的标准形式(Lemma),不同于 Stemming,它考虑单词的词性。
- 词干提取(Stemming):
caring → car
规则化处理,速度快,但有误差 - 词形还原(Lemmatization):
caring → care
语法正确,但需要词性标注,速度慢
- 词干提取(Stemming):
句子处理(Sentence Processing):句子处理包括分词、分句、词性标注、句法分析等
- 句子分割(Sentence Segmentation):直接基于标点 ‘.’, ‘?’, ‘!’ 进行拆分
- 词性标注(POS Tagging):识别每个词的词性(名词、动词、形容词等)。
NLP 数据集(Text Datasets) #
任务类型 | 数据集示例 | 数据特点 | 典型应用 |
---|---|---|---|
文本分类 | IMDb影评、AG News | 文本 + 类别标签 | 情感分析、主题分类 |
序列标注 | CoNLL-2003、OntoNotes | 字符/词级标签 | 命名实体识别、词性标注 |
问答系统 | SQuAD、HotpotQA | 问题 + 上下文 + 答案 | 阅读理解、开放域问答 |
文本生成 | CNN/DailyMail、Gigaword | 原文 + 摘要 | 摘要生成、对话系统 |
语义相似度 | STS-B、MRPC | 句子对 + 相似度分数 | 检索排序、复述检测 |
- 结构化数据:
- 格式:CSV/JSON中的字段化文本(如电商评论包含评分、用户ID)
- 处理重点:字段提取与关联分析
- 非结构化文本:
- 格式:纯文本文件、网页爬取内容 *处理重点:清洗与段落分割
- 对话数据:
- 格式:多轮对话记录(如Customer Support聊天记录)
- 处理重点:对话轮次划分与角色标注
编码(Encoding) #
在自然语言处理(NLP)中,Encoding(编码)是将文本数据转换为计算机可以处理的数值形式的过程。由于计算机无法直接理解文字,所以我们需要将文字映射到数值空间中,便于后续的处理和分析。编码技术的选择通常取决于具体任务和数据特性。
Encoding 主要做的事情是 把文本转换成结构化的、可索引的格式,这样后续的模型或者算法可以进行查询和计算,这个过程类似于构建一个 “字典”(vocabulary),把文本转换成可以查询的 ID 表示。除了构建可查询的字典,Encoding 还可以:
- 加入词频或语法信息(如 TF-IDF, Bag-of-Words)
- 考虑位置信息(如 Position Encoding in Transformers)
- 压缩文本信息(如 Huffman Encoding, Byte-Pair Encoding)
Note: 在 NLP 流程中,Encoding 是预处理步骤,Embedding 是特征学习步骤。
独热编码(One-Hot Encoding) #
One-Hot编码是一种最基础的编码方法。它将每个词表示为一个稀疏的向量,在这个向量中,词汇表中每个词都有一个唯一的索引。如果一个词在文本中出现,那么它对应的向量在该位置上取1,其他位置则取0。假设我们有一个简单的词汇表 {“I”, “love”, “AI”}
,那么词“love”在One-Hot编码中的表示就是 [0, 1, 0]
。
- 这种表示方式非常简单,但其最大的问题是它并 没有捕捉到词汇之间的语义关系,因为每个词都被表示为一个独立的离散向量。
- 维度灾难(Curse of Dimensionality):词汇表大小较大时内存开销极高。
Bag-of-Words (BoW) #
Bag-of-Words是一种常用的文本表示方法,它将文本视为一个词袋,忽略词序和语法,仅考虑每个词在文本中出现的频率。在BoW模型中,每篇文本被表示为一个向量,向量的维度等于词汇表的大小,每个位置表示词汇表中某个词出现的次数或频率。例如句子 “I love NLP and love coding”
→ {"I":1, "love":2, "NLP":1, "and":1, "coding":1}
。
- 虽然这种表示方法比One-Hot编码更灵活,能捕捉到词频信息,但它同样不能反映词汇间的关系,且当词汇表很大时,生成的向量非常稀疏,计算效率较低。
TF-IDF (Term Frequency-Inverse Document Frequency) #
TF-IDF是改进BoW的一种方法,它考虑了 词频(Term Frequency, TF) 和 逆文档频率(Inverse Document Frequency, IDF) 两个因素,旨在提高词语在文档中的重要性衡量。TF衡量某个词在一篇文档中出现的频率,而IDF则衡量该词在整个语料库中出现的稀有程度。TF-IDF 通过计算公式: \[ \text{TF-IDF}(t, d) = \text{TF}(t, d) \times \text{IDF}(t) \] 其中, \(t\) 是词, \(d\) 是文档, \(\text{TF}(t, d)\) 表示词 \(t\) 在文档 \(d\) 中出现的频率, \(\text{IDF}(t) = \log \frac{N}{df(t)}\) ,其中 \(N\) 是文档总数, \(df(t)\) 是包含词 \(t\) 的文档数。通过这种方法,TF-IDF能够给予在少数文档中出现的词更高的权重,从而使得模型能够识别出更具区分性的词。
BPE (Byte Pair Encoding) #
BPE是一种基于频率的子词分解方法,它通过 反复合并出现频率最高的字节对来生成词汇表。这意味着BPE会将词语分解为多个子词(subword)或字符单元。BPE的主要目的是能够将稀有词或未登录词分解成较常见的子词,从而避免直接处理未知的词汇。BPE的编码过程:
将所有词分解为字符级别的单元。
["l", "o", "w", " ", "l", "o", "w", "e", "r", " ", "n", "e", "w", "e", "s", "t", " ", "w", "i", "d", "e", "s", "t"]
通过统计训练语料中最常见的字符对,合并频率最高的字符对为一个新的子词单位。
("l", "o") -> 2次 ("o", "w") -> 2次 ("e", "s") -> 2次 ("s", "t") -> 2次 ...
重复这个过程直到得到预定大小的词汇表。
["low", "low", "er", "newest", "widest"]
vocab = {"low": 100, "lower": 101, "er": 102, "newest": 103, "widest": 104}
BPE的优点是它 能够有效地处理未登录词,并且在处理长尾词(rare words)时表现良好。比如,词”unhappiness”可以被分解为”un” + “happiness”,而不是完全看作一个新的词。
WordPiece #
WordPiece是由Google开发的分词技术,最初用于 BERT 中。它与BPE类似,也是通过子词分解处理词汇表,旨在解决词汇表过大导致的存储和计算问题,并提高模型处理稀有词的能力。WordPiece通过统计训练语料中的子词频率来构建词汇表。WordPiece的编码过程:
- 与BPE类似,首先将所有词分解为最小的单位(如字符)。
["l", "o", "w", " ", "l", "o", "w", "e", "r", " ", "n", "e", "w", "e", "s", "t", " ", "w", "i", "d", "e", "s", "t"]
- 基于子词的出现频率来选择最常见的子词,并合并它们。这里的
##
表示这个 token 只能作为后缀出现,不会单独存在。{"low", "##er", "##ing", "new", "##est", "wide", "##st"}
- 直到构建出一个具有固定大小的子词词汇表。
vocab = {"low": 100, "##er": 101, "##ing": 102, "new": 103, "##est": 104, "wide": 105, "##st": 106}
WordPiece通常通过在训练过程中反复构建最优的子词分解,使模型能够有效地处理复杂和未登录的词。
词嵌入(Embedding) #
在自然语言处理中,Embedding(词嵌入)是将 离散的文本数据(如单词、短语或句子)映射到一个连续的、低维度的向量空间(vector space)的过程。这种表示方式不仅减少了数据的维度,还能捕捉到文本中的语义信息,使得语义相近的词在嵌入空间中具有相似的向量表示。词嵌入(Embedding)也可区分为:
- 静态词向量(Static Word Embeddings) 是一种将每个词映射为固定不变的低维稠密向量的技术,其核心特点是 无论词语出现在何种上下文中,其向量表示均保持不变。这类方法通过大规模语料训练,捕捉词语间的语义和语法关系,例如通过词共现模式(如Word2Vec的局部窗口预测、GloVe的全局矩阵分解)或子词组合(如FastText的字符级n-gram)生成向量。静态词向量的优势在于训练高效、资源消耗低,且生成的向量可直观反映语义相似性(如“猫”和“狗”向量接近);但其 局限性是无法处理多义词(如“苹果”在“水果”和“手机”场景中的不同含义),因为每个词仅对应单一向量。
- 上下文动态词向量(Contextual Word Embeddings):静态嵌入虽然可以表示词语的语义,但它们无法根据上下文动态调整,例如 “bank” 在 “river bank” 和 “bank account” 里的含义不同。而 动态词嵌入 解决了这个问题,代表性模型包括 ELMo、BERT 和 GPT。
Word2Vec #
Word2Vec 是 Google 在 2013 年提出的词嵌入方法,它将每个词映射到一个固定长度的向量,这些向量能更好地表达不同词之间的相似性和类比关系。word2vec工具包含两个模型,即跳元模型(skip-gram) (Mikolov et al., 2013)和连续词袋(CBOW) (Mikolov et al., 2013)。对于在语义上有意义的表示,它们的训练依赖于条件概率,条件概率可以被看作使用语料库中一些词来预测另一些单词。由于是不带标签的数据,因此跳元模型和连续词袋都是 自监督模型。
跳元模型(Skip-Gram) #
跳元模型假设 一个词可以用来在文本序列中生成其周围的单词。以文本序列“the”“man”“loves”“his”“son”为例。假设中心词选择“loves”,并将上下文窗口设置为2,给定中心词“loves”,跳元模型考虑生成上下文词“the”“man”“him”“son”的条件概率: \[ P(\textrm{"the"},\textrm{"man"},\textrm{"his"},\textrm{"son"}\mid\textrm{"loves"}). \]
在跳元模型中,每个词都有两个 \(d\) 维向量表示,用于计算条件概率。更具体地说,对于词典中索引为 \(i\) 的任何词,分别用 \(\mathbf{v}_i\in\mathbb{R}^d\) 和 \(\mathbf{u}_i\in\mathbb{R}^d\) 表示其用作中心词和上下文词时的两个向量。给定中心词 \(w_c\) (词典中的索引 \(c\) ),生成任何上下文词 \(w_o\) (词典中的索引 \(o\) )的条件概率可以通过对向量点积的softmax操作来建模: \[ P(w_o \mid w_c) = \frac{\text{exp}(\mathbf{u}_o^\top \mathbf{v}_c)}{ \sum_{i \in \mathcal{V}} \text{exp}(\mathbf{u}_i^\top \mathbf{v}_c)}, \]
换个角度来说,Word2Vec 的核心是 一个浅层神经网络(Shallow Neural Network),由一个输入层、一个隐藏层(线性变换层)、一个输出层(Softmax 或其他采样方法)组成:
- 输入示例:
- 句子:“I love natural language processing.”
- 若窗口大小为1,中心词为“natural”,则上下文词为“love”和“language”。
- 输入通过 One-Hot 编码 表示为一个稀疏向量。例如,若词汇表为 [“cat”, “dog”, “fish”],则“dog”的输入编码为 [0, 1, 0]。
- 输入层到隐藏层:输入向量与 输入权重矩阵 \(W_{in}\) (维度为 \(V×d\) , \(V\) 是词汇表大小, \(d\) 是词向量维度)相乘,得到中心词的嵌入向量 \(v_i\) 。 \[ v_i=W_{in}⋅OneHot(w)。 \]
- 通过 输出权重矩阵 \(W_{out}\) (维度为 \(d×V\) )将隐层向量映射到输出概率: \[ u_i = W_{out}⋅v_i。 \]
- 使用 Softmax 归一化,通过梯度下降优化词向量,使得上下文词的概率最大化。
Note: 训练后,输入矩阵中的向量 \(v_i\) 即为词的低维表示。输入向量更聚焦中心词语义,输出向量辅助建模上下文关系,最终通常只使用输入向量。
在 PyTorch 的实现中,Word2Vec(Skip-gram)通常使用 nn.Embedding
来替代传统的神经网络全连接层:
- 用
nn.Embedding
代替输入层权重矩阵 \(W_{\text{in}}\) ,它会直接输出词向量(即中心词的 embedding)。负责学习词本身的表示。 - 用
nn.Embedding
代替输出层权重矩阵 \(W_{\text{out}}\) ,它会直接输出目标词的向量(即上下文词的 embedding)。负责学习上下文词的关系。 - 最终用两个 embedding 向量进行点积,然后计算 loss
import torch
import torch.nn as nn
import torch.optim as optim
class Word2Vec(nn.Module):
def __init__(self, vocab_size, embed_dim):
super(Word2Vec, self).__init__()
self.in_embedding = nn.Embedding(vocab_size, embed_dim)
self.out_embedding = nn.Embedding(vocab_size, embed_dim)
def forward(self, center_word, context_word):
center_embed = self.in_embedding(center_word) # (batch_size, embed_dim)
context_embed = self.out_embedding(context_word) # (batch_size, embed_dim)
# 计算两个 embedding 向量的点积
score = torch.sum(center_embed * context_embed, dim=1) # (batch_size,)
return score
连续词袋(CBOW)模型 #
连续词袋(continuous bag of words, CBOW)模型类似于跳元模型。与跳元模型的主要区别在于,连续词袋模型假设中心词是基于其在文本序列中的周围上下文词生成的。例如,在文本序列“the”“man”“loves”“his”“son”中,在“loves”为中心词且上下文窗口为2的情况下,连续词袋模型考虑基于上下文词“the”“man”“him”“son” 生成中心词“loves”的条件概率,即: \[ P(\textrm{"loves"}\mid\textrm{"the"},\textrm{"man"},\textrm{"his"},\textrm{"son"}). \]
连续词袋(CBOW)的训练细节与 跳元模型(Skip-Gram)大部分类似,但是在输入 One-Hot 编码表示时,跳元模型(Skip-Gram)将中心词进行 One-Hot 编码,而连续词袋(CBOW)将上下文词进行 One-Hot 编码并取平均值。 例如,中心词为“natural”,上下文词为“love”和“language”。输入为 [0, 1, 0, 0, 0]
(“love”)和 [0, 0, 0, 1, 0]
(“language”)的平均向量 [0, 0.5, 0, 0.5, 0]
。此外,输出概率通过 Softmax 计算公式也有不同:
\[
P(w_c \mid \mathcal{W}_o) = \frac{\exp\left(\mathbf{u}_c^\top \bar{\mathbf{v}}_o\right)}{\sum_{i \in \mathcal{V}} \exp\left(\mathbf{u}_i^\top \bar{\mathbf{v}}_o\right)}.
\]
import torch
import torch.nn as nn
class CBOWModel(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOWModel, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim) # 词嵌入层
self.linear = nn.Linear(embedding_dim, vocab_size) # 输出层(全连接层)
def forward(self, context_words):
# 1. 查找词向量
embedded = self.embeddings(context_words) # (batch_size, context_size, embedding_dim)
# 2. 计算上下文词向量的平均值
embedded = embedded.mean(dim=1) # (batch_size, embedding_dim)
# 3. 通过全连接层计算每个单词的概率
output = self.linear(embedded) # (batch_size, vocab_size)
return output
维度 | CBOW | Skip-Gram |
---|---|---|
输入-输出关系 | 多个上下文词 → 中心词 | 中心词 → 多个上下文词 |
训练速度 | 更快(上下文词平均后单次预测) | 更慢(每个上下文词单独预测) |
小数据集表现 | 更好(利用上下文词共现信息) | 较差(依赖中心词独立预测) |
生僻词处理 | 较差(上下文噪声平均可能稀释语义) | 更好(直接建模中心词与上下文关联) |
典型应用场景 | 高频词密集的语料(如新闻文本) | 生僻词多或上下文稀疏的语料 |
近似训练 #
由于softmax操作的性质,上下文词可以是词表 \(V\) 中的任意项,但是,在一个词典上(通常有几十万或数百万个单词)求和的梯度的计算成本是巨大的!为了降低计算复杂度,可以采用两种近似训练方法:负采样(Negative Sampling)和层序softmax(Hierarchical Softmax)。
负采样(Negative Sampling):
- 核心思想:将复杂的多分类问题(预测所有词的概率)简化为二分类问题,用少量负样本近似全词汇的Softmax计算。
- 正负样本构建:
- 对每个正样本(中心词与真实上下文词对),随机采样 \(K\) 个负样本(非上下文词)。
- 例如,中心词“apple”的真实上下文词为“fruit”,则负样本可能是随机选择的“car”“book”等无关词。
- 目标函数:最大化正样本对的相似度,同时最小化负样本对的相似度: \[ \log \sigma\left( \mathbf{u}_{\text{正}}^\top \mathbf{v}_c \right) + \sum_{k=1}^K \log \sigma\left( -\mathbf{u}_{\text{负}_k}^\top \mathbf{v}_c \right) \]
- 参数选择:负样本数 \(K\) 一般取5~20,越小则训练越快,但可能欠拟合;越大则逼近原始Softmax,但计算量增加。
- 缺点:采样质量依赖分布设计,可能引入偏差(如高频负样本主导训练)。
层序softmax(Hierarchical Softmax):
- 核心思想:通过二叉树(如霍夫曼树)编码词汇表,将全局Softmax分解为路径上的二分类概率乘积,减少计算量。
- 霍夫曼树构建:按词频从高到低排列词汇,高频词靠近根节点,形成最短路径。每个内部节点含一个可训练的向量参数 \(\theta_n\) 。
- 概率计算:
- 预测词 \(w\) 的概率转化为从根节点到叶节点 \(w\) 的路径概率乘积: \[ P(w \mid c) = \prod_{n \in \text{Path}(w)} \sigma\left( \mathbf{\theta}_n^\top \mathbf{v}_c \right)^{\text{dir}(n)} \] 其中 \(dir(n)\) 表示路径方向(左分支为1,右分支为-1)。
- 例如,词“dog”的路径为根→A→B,则概率为: \[ \sigma\left( \mathbf{\theta}_A^\top \mathbf{v}_c \right) \sigma\left( -\mathbf{\theta}_B^\top \mathbf{v}_c \right) \]
- 缺点:
- 树结构需预构建,无法动态调整(如新增词需重构树)。
- 高频词路径短,低频词路径长,可能放大频次差异的影响。
维度 | Negative Sampling | Hierarchical Softmax |
---|---|---|
计算效率 | ( \(O(K+1)\) ),( \(K\) ) 通常为 5~20 | ( \(O(\log V)\) ),( \(V\) ) 为词汇表大小 |
内存占用 | 需存储负样本分布 | 需存储树结构,但无需额外采样矩阵 |
低频词处理 | 依赖采样策略,可能欠拟合 | 路径长度随词频变化,低频词更新机会少 |
训练稳定性 | 简单,适合大规模数据 | 树结构影响收敛,需预计算 |
适用场景 | Skip-Gram、实时训练 | CBOW、内存敏感场景 |
GloVe #
GloVe(Global Vectors for Word Representation)是一种基于全局统计信息的词嵌入方法,由斯坦福大学的研究者在 2014 年提出。它的核心思想是利用整个语料库的共现信息(co-occurrence information),通过构建词与词之间的共现矩阵,并使用矩阵分解技术学习词的向量表示。GloVe 直接基于全局词频统计信息,从更宏观的角度捕捉词语之间的关系。
GloVe 的目标是通过学习一个能很好地拟合 词共现概率的向量表示。给定一个大型文本语料库,首先构建一个 共现矩阵(co-occurrence matrix) \(X\) ,其中 \(X_{ij}\) 表示词 \(i\) 和词 \(j\) 在一定窗口范围内共同出现的次数。然后计算共现概率: \[ P_{ij} = \frac{X_{ij}}{\sum_k X_{ik}} \]
即,给定词 \(i\) ,在所有可能的词 \(k\) 里,词 \(j\) 作为上下文词的概率。GloVe 的核心目标是学习一个词向量映射,使得向量之间的点积能够近似这个共现概率的对数: \[ \mathbf{u}_j^\top \mathbf{v}_i + b_i + c_j = \log(X_{ij}) \]
- \(\mathbf{v}_i, \mathbf{u}_j\) 是词 \(i\) 和词 \(j\) 的向量表示,每个词都由两个向量组成,一个是中心词向量 ,一个是上下文词向量
- \(b_i, c_j\) 是学习到的偏置项。
GloVe 试图通过优化使得词向量的点积能够反映它们在全局统计中的共现关系。
为了有效优化上面的目标,GloVe 设计了一个加权的平方误差损失函数: \[ J = \sum_{i,j} f(X_{ij}) (\mathbf{u}_j^\top \mathbf{v}_i + b_i + c_j - \log(X_{ij}))^2 \]
其中 \(f(X_{ij})\) 是一个权重函数,用于控制低频词的影响,避免数据稀疏问题。
Note: 严格来说,GloVe 并不依赖传统的神经网络,它的学习过程 更接近矩阵分解(Matrix Factorization)的优化方法,而非 Word2Vec 这样的前馈神经网络(Feedforward Neural Network)。GloVe 不依赖反向传播(Backpropagation),而是直接最小化共现概率对数的加权平方误差,来学习词向量。但GloVe 仍然需要通过优化方法(如 SGD 或 AdaGrad)来更新词向量,但它不使用神经网络的前向传播和激活函数。
在传统的矩阵分解方法(如 奇异值分解 SVD)中,我们通常希望找到一个低维的潜在表示(latent representation),使得某个矩阵的近似表示能够捕捉数据的主要结构。例如,我们可以对共现矩阵 \(X\) 进行奇异值分解:
\[ X \approx W \Sigma W^T \]这里的 \(W\) 就是我们想要学习的词向量矩阵。GloVe 的目标函数本质上也是在优化类似于矩阵分解的目标。
实际训练过程中:
- 首先需要构建一个 共现矩阵(Co-occurrence Matrix)
\(X\)
,其中:
- \(X_{ij}\) 代表词 \(x_i\) 和词 \(x_j\) 在一定窗口大小内共同出现的次数。
- 窗口可以是 滑动窗口(例如 5 个单词)或者基于 整个文档(如 PMI 方法)。
- 之后计算共现概率: \(P_{ij} = \frac{X_{ij}}{\sum_k X_{ik}}\)
- 试图学习词向量 \(\mathbf{v}_i 和 \mathbf{u}_j\) 使得它们的点积可以近似拟合共现概率的对数: \(\mathbf{u}_j^\top \mathbf{v}_i + b_i + c_j \approx \log(X_{ij})\)
- 初始化词向量:随机初始化每个词的主词向量 \(\mathbf{v}_i\) 和上下文词向量 \(\mathbf{u}_j\) ,以及它们对应的偏置项 \(b_i\) 和 \(c_j\) 。
- 计算误差:对于每一对词 \((x_i, x_j)\) ,计算 \(\mathbf{u}_j^\top \mathbf{v}_i + b_i + c_j\) 与 \(\log(X_{ij})\) 之间的误差。
- 计算梯度并更新参数:使用 梯度下降(Gradient Descent) 或者 AdaGrad 进行参数更新
FastText #
fastText 是由 Facebook AI 研究团队(FAIR)在 2016 年提出的一种高效的文本表示和分类方法,它在 Word2Vec 的基础上进行了改进,引入了子词(subword)信息,使得模型能够更好地捕捉词的内部结构,并且在处理低频词、未登录词(OOV, Out-Of-Vocabulary)和多语言文本时表现更优。
fastText 的基本思想:基于子词的嵌入(Subword Embeddings):在传统的 Word2Vec(CBOW 和 Skip-gram)模型中,每个单词都被视为一个独立的最小单位,训练后会得到一个固定的词向量。然而,这种方法有一些明显的不足:
- 无法处理未见过的新词(OOV):如果一个单词没有出现在训练语料中,它就无法被表示。
- 对形态丰富的语言表现较差:例如在英语中,“run”、“running”、“runner” 可能共享相似的语义,但 Word2Vec 仍然会将它们视为完全独立的单词。
为了缓解这些问题,fastText 引入了子词(subword)信息,即把一个单词拆分成多个 n-gram 片段,并分别计算这些子词的向量。例如,单词 “apple” 可能被分解为:
<ap, app, ppl, ple, le> (3-gram 表示)
fastText 的训练方式 仍然基于CBOW(Continuous Bag of Words)和 Skip-gram,但在计算过程中,它并不直接对完整的单词进行 embedding,而是 使用所有子词 n-gram 片段的向量之和来计算单词的最终表示。例如,在 Skip-gram 训练过程中,目标是最大化一个中心词的 embedding 与其上下文词的 embedding 之间的点积。
上下文敏感词表示模型(BERT) #
传统词嵌入模型(如Word2Vec、GloVe)的局限性在于上下文无关性,即无论词语出现在何种上下文中(如 “a crane is flying”(一只鹤在飞)和 “a crane driver came”(一名吊车司机来了)中含义不同),其向量表示均固定。这种静态表示无法捕捉多义词和复杂语义关系。上下文敏感词表示模型(如ELMo、GPT、BERT)的改进在于:
- 动态语义编码:词向量根据上下文动态生成。
- 模型架构演进:
- ELMo:基于双向LSTM,将各层隐藏状态加权融合,作为词表示。ELMo向量作为附加特征与任务模型(如GloVe)拼接,冻结预训练参数,在6类NLP任务(情感分析、问答等)中刷新SOTA。
- GPT:基于Transformer解码器,通过单向语言模型(左到右)生成词表示,全参数微调下游任务,在12类任务中提升9类性能。但其单向性导致无法捕捉右侧上下文(如"bank" 在 “i went to the bank to deposit cash”(我去银行存现金)和“i went to the bank to sit down”(我去河岸边坐下)场景中的歧义)。
- BERT融合了ELMo和GPT的优势:
- 双向上下文编码:通过Transformer编码器捕捉左右两侧上下文信息。
- 任务无关设计:仅需微调预训练模型并添加简单输出层,即可适配多种任务。
- 核心改进:
- 预训练目标:掩码语言模型(MLM)和下一句预测(NSP)联合优化。
- 参数微调:所有预训练参数在下游任务中可调(不同于ELMo的冻结),提升模型灵活性。