词嵌入
引入
只要是使用深度学习模型处理 NLP 问题:在模型中就需要添加,Embedding 层。比如在
Transformers 中的左下角和右下角就有两个词嵌入层。
Embedding层负责将离散的单词数据转换称连续且固定长度的向量,简单来说:
词向量:用一个d维空间的向量表示一个词
词嵌入:就是将一个词嵌入到一个特定的向量空间
如果我们想把一句话,作为神经网络的输入,比如Are you OK? 神经网络是没有办法直接处理这个
文本的,我们需要先将Are you OK?基于词表vocab转换称整数索引序列的形式,例如转换称【1,
2,3,4】,然后再基于Embedding层,将整数索引序列转换成单词向量的序列,这里假设每个单
词用4维的向量表示,那么4个词的句子Are you OK? 就会被转换为 4×4 的词向量矩阵。每行对应
一个单词。得到输入文本的向量矩阵后,才可以使用神经网络对文本进行特征提取和处理。
下面我们会从3个方面解释词嵌入 (Embedding) 技术
(1)词嵌入的作用 (2)嵌入矩阵的计算 (3)Embedding 层的使用
词嵌入的作用
词嵌入是一种将词汇表中的词或短语映射为固定长度向量的技术我们可以将高维且稀疏的单词
索引转为低维且连续的向量转换后的连续向量可以表示出单词与单词之间的语义关系。此时我们使
用词嵌入技术,就可以把4个高纬稀疏的的向量,转换成4个低纬连续的向量,转换后的向量,每个
维度都是一个浮点数,这里我们就把向量映射到一个7维的空间中。
为了进一步表示词与词之间的关系,我们可以把词向量,基于降维算法,降成2维,从而使单词向
量在平面上绘制出来。此时就会发现语义相近的词语在向量空间中距离相近。词嵌入可通过向量的
数学运算体现词语的语义关联,例如:向量(“king”) - 向量(“man”) ≈ 向量(“queen”) - 向量
(“woman”)。词嵌入技术的核心价值:将自然语言中的词语转换为数值向量,并通过向量体现词
语间的语义关系;为机器翻译、文本分类、语义检索等高级 NLP 任务提供基础支撑。
嵌入矩阵
为了实现词嵌入,我们会使用特定的词嵌入算法,例如通过 Word2Vec、FastText、GloVe 等词嵌
入算法,训练得到一个通用的嵌入矩阵,该矩阵即为模型中的 Embedding 层。具体来说嵌入矩阵
的行就是语料库中词语的个数,嵌入矩阵的列表示词语的维度。设词表中包括5000个单词,每个
单词用一个128维的向量表示。我们将嵌入矩阵用E来表示。
嵌入矩阵的使用方法有两种形式:
基于矩阵相乘:将单词的独热编码向量与嵌入矩阵相乘,得到对应的词向量(本质是矩阵乘法实现
的映射)。
基于索引查找:直接通过单词在词汇表中的索引,从嵌入矩阵中提取对应的行向量(是实际工程中
更高效的常用方式)。
比如我们对Are you OK? 这句话进行编码,每个词是一个5000维的向量,整个句子是一个4*5000
矩阵我们将该矩阵记作V。然后我们把独热编码矩阵和嵌入矩阵相乘,就得到句子的嵌入向量矩
阵。
另一种方式是索引查找,句子中的每个词都对应着,词表中的一个整数索引,然后我们直接获取索
引在嵌入句子对应的行就好了。
Embedding 层的使用
下面我们会使用torch.nn来创建一个Embedding 层,并使用这个Embedding 层计算单词的词向
量,并实现可视化。
from torchtext.vocab import import GloVe # 导入GloVe词向量 # 简单介绍 # GloVe词向量,它是斯坦福大学的研究者在2014年开发和发布的 # GloVe和word2vec与fasttext,是当前最常用的3个词向量版本 # 6B表示了模型是基于60 亿个单词的语料库训练的 # 300表示一个单词,使用300维的向量表示 glove = GloVe(name='6B', dim=300)
import torch from torch import nn # 使用nn.Embedding创建词嵌入层 # 将glove.vectors,通过from_pretrained接口,导入到Embedding层中 # 此时的embedding层,就载入了GloVe词向量数据 embedding = nn.Embedding.from_pretrained(glove.vectors) # 打印embedding层中的weight的尺寸 print(f"embedding.shape: {embedding.weight.shape}")
程序输出:embedding.shape: torch.Size([400000, 300])表示 Embedding 层的权重矩阵尺寸为
“400000 个单词 × 300 维向量”,对应 GloVe 预训练词向量的词汇量与维度。
# 将man、woman、king、queen等8个词语的词向量,绘制到二维平面上 words = ['man', 'woman', 'king', 'queen', 'cat', 'dog', 'mother', 'father'] indices = [] for word in words: # 将单词word,通过glove的词汇表,转换为单词的索引 index = glove.stoi[word] # 将这些索引保存到indices数组中 indices.append(index) # 打印单词word和索引index的对应关系 print(f"{word} -> {index}")
程序输出:man -> 300 woman -> 787 king -> 691 queen -> 2060 cat -> 5450 dog -> 2926 mother
-> 808 father -> 629
# 将索引列表,使用torch.tensor,转为张量的形式 indices = torch.tensor(indices) # 将索引列表转换为,词向量的矩阵 vectors = embedding(indices).detach().numpy() # 打印vector的尺寸 print(f"vectors.shape: {vectors.shape}")
程序输出:vectors.shape: (8, 300)这段代码的核心是从 Embedding 层中提取目标单词的词向量:
(8, 300)表示词向量矩阵的尺寸为 “8 个单词 × 300 维向量”,对应 8 个目标单词各自的 300 维
GloVe 词向量。
from sklearn.decomposition import PCA import matplotlib.pyplot as plt # 使用PCA降维算法,将向量vectors进行降维 pca = PCA(n_components=2) vectors_2d = pca.fit_transform(vectors) # 将单词和向量,绘制到二维平面上 plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1]) for i, word in enumerate(words): plt.annotate(word, xy=(vectors_2d[i, 0], vectors_2d[i, 1]), xytext=(-10, 10), textcoords='offset points') plt.show()
用 PCA 算法将 300 维的词向量降维至 2 维(适配二维平面展示);
通过 matplotlib 绘制降维后的向量散点图,并为每个点标注对应的单词;
完整代码
from torchtext.vocab import GloVe # 导入GloVe词向量 # 简单介绍 # GloVe词向量,它是斯坦福大学的研究者在2014年开发和发布的 # GloVe和word2vec与fasttext,是当前最常用的3个词向量版本 # 6B表示了模型是基于60 亿个单词的语料库训练的 # 300表示一个单词,使用300维的向量表示 glove = GloVe(name='6B', dim=300) import torch from torch import nn # 使用nn.Embedding创建词嵌入层 # 将glove.vectors,通过from_pretrained接口,导入到Embedding层中 # 此时的embedding层,就载入了GloVe词向量数据 embedding = nn.Embedding.from_pretrained(glove.vectors) # 打印embedding层中的weight的尺寸 print(f"embedding.shape: {embedding.weight.shape}") # 将man、woman、king、queen等8个词语的词向量,绘制到二维平面上 words = ['man', 'woman', 'king', 'queen', 'cat', 'dog', 'mother', 'father'] indices = [] for word in words: # 将单词word,通过glove的词汇表,转换为单词的索引 index = glove.stoi[word] # 将这些索引保存到indices数组中 indices.append(index) # 打印单词word和索引index的对应关系 print(f"{word} -> {index}") # 将索引列表,使用torch.tensor,转为张量的形式 indices = torch.tensor(indices) # 将索引列表转换为,词向量的矩阵 vectors = embedding(indices).detach().numpy() # 打印vector的尺寸 print(f"vectors.shape: {vectors.shape}") from sklearn.decomposition import PCA import matplotlib.pyplot as plt # 使用PCA降维算法,将向量vectors进行降维 pca = PCA(n_components=2) vectors_2d = pca.fit_transform(vectors) # 将单词和向量,绘制到二维平面上 plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1]) for i, word in enumerate(words): plt.annotate(word, xy=(vectors_2d[i, 0], vectors_2d[i, 1]), xytext=(-10, 10), textcoords='offset points') plt.show()
位置编码 (PositionalEncoding)
为什么给词向量的每个维度,加上位置编码,就能给这个词向量加上位置信息呐?为什么可以
直接将词向量和位置编码相加?为什么这样的操作就是有实际意义?将正弦和余弦的常量信息,直
接加到词向量上,不会破坏词向量本身的信息吗?
下面我们将从4个方面详细介绍位置编码,位置编码 (PositionalEncoding):(1)什么是位置编码
(2) 如何计算位置编码(3)位置编码的真正意义(4)位置编码的代码实现
什么是位置编码
下面我们用机器翻译为例说明位置编码,待翻译英文Are you OK?会从下方的左侧位置输入,
中文标注 你好吗?会从下方的右侧位置输入,当Are you OK?和你好吗?进入到Transformer中,
会被第一个组件,词向量层处理,词向量层把单词变成词向量,“位置编码的目的是,将位置信
息‘附加’到原始的信息上。
如何计算位置编码?
d表示位置编码向量的维度,这里我们举出的例子中维度是4。
pos表示序列中的位置,比如,Are--->0,you--->1,OK--->2,?--->3
i表示位置编码向量维度索引的一半,比如:
第0维维度,2*0=0,所以i=0,2i=0
第1维维度,2*0+1=1,所以i=1,2i=0
第2维维度,2*1=1,所以i=1,2i=2
第3维维度,2*1+1=2,所以i=1.2i=2
在 Transformer 模型中,词向量(Word Embeddings)的表示是通过将输入的词嵌入
(Embedding)词向量矩阵和位置编码(Positional Encoding)矩阵相加得到的。
位置编码的意义
位置编码 (PositionalEncoding): 增加额外的位置信息帮助模型更好地理解语言的顺序性
质,通过绝对足够充分的训练数据:使模型能够学会理解 “词向量”+“位置编码” 的复合特征最终完
成对文本的整体理解。
代码实现
(1)定义词向量矩阵
词向量矩阵(形状:[5, 4]),word_embeds
word_embeds = torch.tensor([ [0.4002, 0.9882, 0.6261, 0.1502], # Are [0.3413, 0.9284, 0.9349, 0.8958], # you [0.5347, 0.2969, 0.7019, 0.6293], # OK [0.8704, 0.4956, 0.3794, 0.5646] # ? ]) print("图片中的词向量矩阵:") print(word_embeds)
(2)初始化位置编码模块(embed_dim=4,max_pos=5)
[5, 4]的矩阵(5 个位置,每个位置 4 维),pos的大小是5*1的代表,位置索引,计算
embed_dim=4 max_pos=5 # 1. 生成位置索引(0到max_pos-1) pos = torch.arange(max_pos).unsqueeze(1) # 形状:[max_pos, 1] print(pos) # 2. 计算正余弦编码的频率项(公式中的10000^(2i/d_model)的倒数) div_term = torch.exp(torch.arange(0, embed_dim, 2) * (-math.log(10000.0) / embed_dim)) print(div_term) # 3. 初始化位置编码矩阵(全零) pe = torch.zeros(max_pos, embed_dim) # 形状:[max_pos, embed_dim] print(pe) # 4. 填充偶数维度(用正弦函数) pe[:, 0::2] = torch.sin(pos * div_term) # 0::2表示从0开始,步长为2的列(偶数维) print(pe[:, 0::2]) # 5. 填充奇数维度(用余弦函数) pe[:, 1::2] = torch.cos(pos * div_term) # 1::2表示从1开始,步长为2的列(奇数维) print(pe[:, 1::2]) print(pos_encoder.pe[:4]) # 取前4个位置的编码
(3)完整代码
import torch import torch.nn as nn import math # 1. 定义位置编码模块(复用之前的实现) class PositionalEncoding(nn.Module): def __init__(self, embed_dim: int, max_pos: int = 10): super().__init__() pos = torch.arange(max_pos).unsqueeze(1) div_term = torch.exp(torch.arange(0, embed_dim, 2) * (-math.log(10000.0) / embed_dim)) pe = torch.zeros(max_pos, embed_dim) pe[:, 0::2] = torch.sin(pos * div_term) pe[:, 1::2] = torch.cos(pos * div_term) self.register_buffer('pe', pe) def forward(self, x: torch.Tensor) -> torch.Tensor: seq_len = x.size(0) return x + self.pe[:seq_len] # 这里x是[序列长度, 词向量维度],无需批次维度 # 2. 构造图片中的词向量矩阵(形状:[5, 4]) word_embeds = torch.tensor([ [0.4002, 0.9882, 0.6261, 0.1502], # Are [0.3413, 0.9284, 0.9349, 0.8958], # you [0.5347, 0.2969, 0.7019, 0.6293], # OK [0.8704, 0.4956, 0.3794, 0.5646] # ? ]) print("图片中的词向量矩阵:") print(word_embeds) # 3. 初始化位置编码模块(embed_dim=4,max_pos=5) pos_encoder = PositionalEncoding(embed_dim=4, max_pos=5) print("\n图片对应的位置编码矩阵:") print(pos_encoder.pe[:4]) # 取前4个位置的编码 # 4. 计算“词向量 + 位置编码”的结果 output = pos_encoder(word_embeds) print("\n最终相加结果:") print(output)
【1】参考:小黑黑讲AI