词向量(Word Embedding)是自然语言处理(NLP)领域中用于表示单词的一种低维稠密向量形式。下面从概念、原理和作用三个方面详细解释:
一、词向量是什么?
在传统的自然语言处理中,单词通常用 ** 独热编码(One - Hot Encoding)** 表示。例如,假设词汇表有三个单词:“猫”“狗”“桌子”,那么 “猫” 的独热编码是[1, 0, 0],“狗” 是[0, 1, 0],“桌子” 是[0, 0, 1]。
但独热编码有明显缺陷:
-
维度灾难:词汇表越大,向量维度越高(若有 10 万个单词,向量维度就是 10 万),计算和存储成本极高。
-
语义无关性:独热编码的向量是正交的(向量间点积为 0),无法体现单词的语义关系(比如 “猫” 和 “狗” 都是动物,语义相近,但独热编码无法体现这一点)。
而词向量则是将单词映射到一个低维的连续向量空间(比如 100 维、300 维),向量的每个维度都承载了单词的语义或语法特征。例如:
-
“猫” 的词向量可能是[0.8, 0.1, -0.3, ..., 0.2](100 维)。
-
“狗” 的词向量可能是[0.7, 0.2, -0.2, ..., 0.3]。
-
由于 “猫” 和 “狗” 语义相近,它们的词向量在空间中的距离会很近(可通过余弦相似度等指标衡量)。
二、词向量的原理
词向量的核心思想基于 **“分布式假设”**:在相同上下文中出现的单词,语义往往相近。
例如,“猫在沙发上睡觉” 和 “狗在沙发上睡觉” 中,“猫” 和 “狗” 的上下文(“在沙发上睡觉”)相似,因此它们的语义也应相近,反映在词向量上就是向量距离近。
常见的词向量训练方法有:
-
Word2Vec:通过 “预测上下文”(CBOW 模型,用上下文预测中心词)或 “由中心词预测上下文”(Skip - Gram 模型),从大规模语料中学习词向量。
-
GloVe:结合全局词频统计(共现矩阵)和局部上下文信息,训练更稳定的词向量。
-
FastText:考虑单词的子词(Subword)信息(如 “apple” 可拆分为 “app”“ple” 等子词),能更好处理未登录词(OOV)。
三、词向量的作用
词向量是 NLP 任务的 “基石”,主要作用体现在以下几个方面:
1. 捕捉语义和语法关系
-
语义相似性:词向量能量化单词的语义相似度。例如,“苹果” 和 “香蕉” 的词向量距离,比 “苹果” 和 “电脑” 的距离更近。
-
语法类比:能通过向量运算实现语法推理,如 “国王 - 男人 + 女人 = 王后”(对应向量运算:vec(king) - vec(man) + vec(woman) ≈ vec(queen))。
2. 作为模型的初始化特征
在深度学习模型(如 RNN、LSTM、Transformer)中,词向量常作为 ** 嵌入层(Embedding Layer)** 的初始化权重。这样模型无需从零学习单词表示,而是基于预训练的词向量快速捕捉语义,提升训练效率和效果。
例如,在文本分类任务中,先将句子中的每个单词转换为词向量,再输入模型,模型能更快理解句子的语义倾向。
3. 缓解数据稀疏性
对于低频词或未登录词(训练数据中没出现的词),独热编码几乎无法表示,但词向量可通过 “子词信息” 或 “上下文相似性” 为其生成合理的向量表示,缓解数据稀疏带来的问题。
4. 跨任务迁移
预训练的词向量(如基于维基百科训练的 Word2Vec、GloVe)可迁移到不同 NLP 任务(如情感分析、机器翻译、命名实体识别),无需为每个任务单独训练词表示,降低了任务的门槛和成本。
总结
词向量是一种将单词映射到低维稠密向量的技术,它解决了独热编码的缺陷,能高效捕捉单词的语义和语法关系,并作为核心特征支撑各类 NLP 任务,是现代 NLP 技术的基础之一。
关键步骤
-
准备预训练词向量文件: 预训练词向量通常存储在文本文件中如(glove.6B.100d.txt)每行格式为 “单词 词向量各维度数值”(数值间用空格分隔)。
-
构建词汇表(vocab):从预训练词向量文件中提取所有单词,形成单词列表tokens。使用ds.text.Vocab.from_list(tokens)从单词列表创建词汇表vocab,还可额外添加特殊符号(如<pad>用于填充、<unk>用于未知词)。
-
获取单词索引:利用词汇表的tokens_to_ids方法,传入单词(如'glove'),得到该单词在词汇表中的索引idx,代码示例:idx = vocab.tokens_to_ids('glove')。
-
加载词向量并提取对应向量:将预训练词向量文件中的词向量加载为矩阵embeddings(行对应单词在词汇表中的索引,列对应词向量维度)。根据单词的索引idx,从embeddings中提取该单词的词向量,代码示例:embedding = embeddings[idx]。
下图为代码演示,我将基于代码做详细解释:
import zipfile
import numpy as np
def load_glove(glove_path):
glove_100d_path = os.path.join(cache_dir, 'glove.6B.100d.txt')
if not os.path.exists(glove_100d_path):
glove_zip = zipfile.ZipFile(glove_path)
glove_zip.extractall(cache_dir)
embeddings = []
tokens = []
with open(glove_100d_path, encoding='utf-8') as gf:
for glove in gf:
word, embedding = glove.split(maxsplit=1)
tokens.append(word)
embeddings.append(np.fromstring(embedding, dtype=np.float32, sep=' '))
# 添加 <unk>, <pad> 两个特殊占位符对应的embedding
embeddings.append(np.random.rand(100))
embeddings.append(np.zeros((100,), np.float32))
vocab = ds.text.Vocab.from_list(tokens, special_tokens=["<unk>", "<pad>"], special_first=False)
embeddings = np.array(embeddings).astype(np.float32)
return vocab, embeddings
这段代码是一个加载 GloVe 预训练词向量的工具函数,主要功能是从 GloVe 压缩包中提取词向量文件、构建词汇表,并返回词汇表与对应的词向量矩阵。
1. 导入依赖库
import zipfile # 用于解压GloVe词向量的压缩包(.zip文件)
import numpy as np # 用于处理词向量的数值计算(如数组转换、随机初始化等)
-
zipfile:Python 标准库,专门处理 ZIP 格式的压缩文件,这里用于提取压缩包中的词向量文本文件。
-
numpy:数值计算库,词向量本质是浮点数数组,用numpy存储和处理更高效。
2. 定义函数load_glove(glove_path)
函数接收一个参数glove_path,表示 GloVe 词向量压缩包(.zip)的路径,返回两个结果:
-
vocab:词汇表(单词到索引的映射)
-
embeddings:词向量矩阵(每个单词对应的预训练向量)
3. 确定词向量文件路径并解压(若未解压)
glove_100d_path = os.path.join(cache_dir, 'glove.6B.100d.txt')
-
os.path.join:拼接路径,cache_dir是缓存目录(代码中未显式定义,通常是提前设定的本地文件夹,用于存放解压后的文件),glove.6B.100d.txt是目标词向量文件(100 维,训练语料为 60 亿词)。
if not os.path.exists(glove_100d_path):
glove_zip = zipfile.ZipFile(glove_path)
glove_zip.extractall(cache_dir)
-
s.path.exists(glove_100d_path):检查目标词向量文件是否已存在于cache_dir中。
-
如果不存在,则通过zipfile.ZipFile(glove_path)打开压缩包,并用extractall(cache_dir)将压缩包中的所有文件解压到cache_dir目录(避免重复解压,节省时间)。
4. 读取词向量文件,提取单词和对应的向量
embeddings = [] # 存储所有词向量(初始为空列表)
tokens = [] # 存储所有单词(初始为空列表)
-
tokens:收集从词向量文件中读取的所有单词,用于后续构建词汇表。
-
embeddings:收集每个单词对应的向量,后续转换为矩阵。
with open(glove_100d_path, encoding='utf-8') as gf:
for glove in gf:
word, embedding = glove.split(maxsplit=1)
tokens.append(word)
embeddings.append(np.fromstring(embedding, dtype=np.float32, sep=' '))
-
with open(...) as gf:打开 100 维词向量文件,encoding='utf-8'确保中文等字符正常读取。
-
循环读取文件的每一行(每一行对应一个单词和它的向量):
-
glove.split(maxsplit=1):按空格分割,maxsplit=1表示只分割一次,左边是单词(word),右边是向量的所有数值(embedding字符串,如 "0.123 0.456 ... -0.789")。
-
tokens.append(word):将单词加入tokens列表(如添加 "apple"、"china" 等)。
-
np.fromstring(embedding, dtype=np.float32, sep=' '):将向量字符串转换为numpy数组(100 维,float32 类型),并加入embeddings列表。
5. 添加特殊符号的词向量
embeddings.append(np.random.rand(100)) # 为<unk>(未知词)添加100维随机向量
embeddings.append(np.zeros((100,), np.float32)) # 为<pad>(填充符)添加100维零向量
-
NLP 任务中通常需要两个特殊符号:
-
<unk>:表示词汇表中未出现的词(OOV),用随机向量初始化(np.random.rand(100)生成 100 个 0-1 之间的随机数)。
-
<pad>:表示填充符(用于将不同长度的句子补成相同长度),用零向量初始化(np.zeros确保填充不影响语义)。
-
注意:这里先添加<unk>和<pad>的向量,后续构建词汇表时会对应上这两个符号。
6. 构建词汇表并转换词向量为矩阵
vocab = ds.text.Vocab.from_list(tokens, special_tokens=["<unk>", "<pad>"], special_first=False)
-
ds.text.Vocab:通常是深度学习框架(如 MindSpore、TensorFlow 等)中的词汇表类,用于建立 “单词 - 索引” 映射。
-
from_list(tokens):基于tokens列表(从词向量文件中读取的单词)构建词汇表。
-
special_tokens=["<unk>", "<pad>"]:添加两个特殊符号到词汇表。
-
special_first=False:特殊符号的索引排在普通单词之后(即先有tokens中的单词,再是<unk>和<pad>)。
-
例如:假设tokens有 10000 个单词,那么<unk>的索引是 10000,<pad>是 10001。
embeddings = np.array(embeddings).astype(np.float32)
-
将embeddings列表(包含普通单词向量、<unk>向量、<pad>向量)转换为numpy矩阵,形状为(词汇表大小, 100)(每行对应一个单词的 100 维向量),并确保数据类型为float32(节省内存,适合模型计算)。
7. 函数返回值
return vocab, embeddings
-
返回构建好的词汇表(vocab)和词向量矩阵(embeddings),可直接用于模型的嵌入层初始化。
总结代码功能
-
解压 GloVe 压缩包,获取 100 维词向量文件。
-
读取文件,提取所有单词和对应的 100 维向量。
-
为特殊符号<unk>(未知词)和<pad>(填充符)添加向量。
-
构建包含普通单词和特殊符号的词汇表。
-
将所有向量转换为矩阵,返回词汇表和矩阵。
通过这个函数,能快速加载预训练词向量,为 NLP 任务(如文本分类、命名实体识别等)提供初始化的词嵌入特征。