NLP_词的向量表示Word2Vec 和 Embedding

文章目录


词向量

下面这张图就形象地呈现了词向量的内涵:把词转化为向量,从而捕捉词与词之间的语义和句法关系,使得具有相似含义或相关性的词语在向量空间中距离较近。

我们把语料库中的词和某些上下文信息,都"嵌入"了向量表示中。

将词映射到向量空间时,会将这个词和它周围的一些词语一起学习,这就使得具有相似语义的词在向量空间中靠得更近。这样,我们就可以通过向量之间的距离来度量词之间的相似性了。

Word2Vec:CBOW模型和Skip-Gram模型

稠密向量中的元素大部分为非零值。稠密向量通常具有较低的维度,同时能够捕捉到更丰富的信息。Word2Vec就是一种典型的稠密向量表示。稠密向量能够捕捉词与词之间的语义和语法关系,使得具有相似含义或相关性的词在向量空间中距离较近。

在自然语言处理中,稠密向量通常更受欢迎,因为它们能够捕捉到更多的信息,同时计算效率更高。下图直观地展示了二者的区别。

通过Word2Vec学习得到的向量可以捕捉到词与词之间的语义和语法关系。而且,这个算法比以前的方法更加高效,能够轻松地处理大规模的文本数据。因此,Word2Vec迅速流行起来。

具体来说,Word2Vec有两种主要实现方式:CBOW(Continuous Bag of Words,有时翻译为"连续词袋")模型和Skip-Gram(有时翻译为"跳字")模型,如下图所示。CBOW模型通过给定上下文词(也叫"周围词")来预测目标词(也叫"中心词");而Skip-Gram模型则相反,通过给定目标词来预测上下文词。这两个模型都是通过训练神经网络来学习词向量的。在训练过程中,我们通过最小化预测词和实际词之间的损失来学习词向量。当训练完成后,词向量可以从神经网络的权重中提取出来。

通过nn.Embedding来实现词嵌入

在PyTorch中,nn.Embedding是nn中的一个模块,它用于将离散的索引(通常是单词在词汇表中的索引)映射到固定大小的向量空间。在自然语言处理任务中,词嵌入是将单词表示为高维向量的一种常见方法。词嵌入可以捕捉单词之间的相似性、语义关系等。在训练过程中,嵌入层会自动更新权重以最小化损失函数,从而学习到有意义的词向量。

嵌入层的构造函数接收以下两个参数。

  • num_embeddings :词汇表的大小,即唯一单词的数量。
  • embedding_dim:词嵌入向量的维度。
    使用嵌入层有以下优点。
  • 更简洁的代码:与线性层相比,嵌入层提供了更简洁、更直观的表示词嵌入的方式。这使得代码更容易理解和维护。
  • 更高的效率:嵌入层比线性层更高效,因为它不需要进行矩阵乘法操作。它直接从权重矩阵中查找对应的行(嵌入向量),这在计算上更高效。
  • 更容易训练:嵌入层不需要将输入转换为One-Hot编码后的向量。我们可以直接将单词索引作为输入,从而减少训练的计算复杂性。
python 复制代码
# 定义一个句子列表,后面会用这些句子来训练 CBOW 和 Skip-Gram 模型
sentences = ["Kage is Teacher", "Mazong is Boss", "Niuzong is Boss",
             "Xiaobing is Student", "Xiaoxue is Student",]
# 将所有句子连接在一起,然后用空格分隔成多个单词
words = ' '.join(sentences).split()
# 构建词汇表,去除重复的词
word_list = list(set(words))
# 创建一个字典,将每个词映射到一个唯一的索引
word_to_idx = {word: idx for idx, word in enumerate(word_list)}
# 创建一个字典,将每个索引映射到对应的词
idx_to_word = {idx: word for idx, word in enumerate(word_list)}
voc_size = len(word_list) # 计算词汇表的大小
print(" 词汇表:", word_list) # 输出词汇表
print(" 词汇到索引的字典:", word_to_idx) # 输出词汇到索引的字典
print(" 索引到词汇的字典:", idx_to_word) # 输出索引到词汇的字典
print(" 词汇表大小:", voc_size) # 输出词汇表大小
python 复制代码
# 生成 Skip-Gram 训练数据
def create_skipgram_dataset(sentences, window_size=2):
    data = [] # 初始化数据
    for sentence in sentences: # 遍历句子
        sentence = sentence.split()  # 将句子分割成单词列表
        for idx, word in enumerate(sentence):  # 遍历单词及其索引
            # 获取相邻的单词,将当前单词前后各 N 个单词作为相邻单词
            for neighbor in sentence[max(idx - window_size, 0): 
                        min(idx + window_size + 1, len(sentence))]:
                if neighbor != word:  # 排除当前单词本身
                    # 将相邻单词与当前单词作为一组训练数据
                    data.append((neighbor, word))
    return data
# 使用函数创建 Skip-Gram 训练数据
skipgram_data = create_skipgram_dataset(sentences)
# 打印未编码的 Skip-Gram 数据样例(前 3 个)
print("Skip-Gram 数据样例(未编码):", skipgram_data[:3])
python 复制代码
# 定义 One-Hot 编码函数
import torch # 导入 torch 库
def one_hot_encoding(word, word_to_idx):    
    tensor = torch.zeros(len(word_to_idx)) # 创建一个长度与词汇表相同的全 0 张量  
    tensor[word_to_idx[word]] = 1  # 将对应词的索引设为 1
    return tensor  # 返回生成的 One-Hot 向量
# 展示 One-Hot 编码前后的数据
word_example = "Teacher"
print("One-Hot 编码前的单词:", word_example)
print("One-Hot 编码后的向量:", one_hot_encoding(word_example, word_to_idx))
# 展示编码后的 Skip-Gram 训练数据样例
print("Skip-Gram 数据样例(已编码):", [(one_hot_encoding(context, word_to_idx), 
          word_to_idx[target]) for context, target in skipgram_data[:3]])
python 复制代码
# 定义 Skip-Gram 模型
import torch.nn as nn # 导入 neural network
class SkipGram(nn.Module):
    def __init__(self, voc_size, embedding_size):
        super(SkipGram, self).__init__()
        # 从词汇表大小到嵌入大小的嵌入层(权重矩阵)
        self.input_to_hidden = nn.Embedding(voc_size, embedding_size)  
        # 从嵌入大小到词汇表大小的线性层(权重矩阵)
        self.hidden_to_output = nn.Linear(embedding_size, voc_size, bias=False) 
    def forward(self, X):
        hidden_layer = self.input_to_hidden(X)  # 生成隐藏层:[batch_size, embedding_size]
        output_layer = self.hidden_to_output(hidden_layer)  # 生成输出层:[batch_size, voc_size]
        return output_layer  
embedding_size = 2 # 设定嵌入层的大小,这里选择 2 是为了方便展示
skipgram_model = SkipGram(voc_size, embedding_size)  # 实例化 Skip-Gram 模型
print("Skip-Gram 模型:", skipgram_model)
python 复制代码
# 训练 Skip-Gram 类
learning_rate = 0.001 # 设置学习速率
epochs = 1000 # 设置训练轮次
criterion = nn.CrossEntropyLoss()  # 定义交叉熵损失函数
import torch.optim as optim # 导入随机梯度下降优化器
optimizer = optim.SGD(skipgram_model.parameters(), lr=learning_rate)  
# 开始训练循环
loss_values = []  # 用于存储每轮的平均损失值
for epoch in range(epochs):
    loss_sum = 0 # 初始化损失值
    for context, target in skipgram_data:        
        X = torch.tensor([word_to_idx[target]], dtype=torch.long)  # # 输入是中心词
        y_true = torch.tensor([word_to_idx[context]], dtype=torch.long)  # 目标词是周围词
        y_pred = skipgram_model(X)  # 计算预测值
        loss = criterion(y_pred, y_true)  # 计算损失
        loss_sum += loss.item() # 累积损失
        optimizer.zero_grad()  # 清空梯度
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数
    if (epoch+1) % 100 == 0: # 输出每 100 轮的损失,并记录损失
        print(f"Epoch: {epoch+1}, Loss: {loss_sum/len(skipgram_data)}")  
        loss_values.append(loss_sum / len(skipgram_data))
# 绘制训练损失曲线
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib.font_manager import FontProperties
font = FontProperties(fname='SimHei.ttf', size = 15)
# 绘制二维词向量图
#plt.rcParams["font.family"]=['SimHei'] # 用来设定字体样式
#plt.rcParams['font.sans-serif']=['SimHei'] # 用来设定无衬线字体样式
#plt.rcParams['axes.unicode_minus']=False # 用来正常显示负号
plt.plot(range(1, epochs//100 + 1), loss_values) # 绘图
plt.title(' 训练损失曲线 ', FontProperties = font) # 图题
plt.xlabel(' 轮次 ', FontProperties = font) # X 轴 Label
plt.ylabel(' 损失 ', FontProperties = font) # Y 轴 Label
plt.show() # 显示图
python 复制代码
# 输出 Skip-Gram 习得的词嵌入
print("Skip-Gram 词嵌入:")
for word, idx in word_to_idx.items(): # 输出每个词的嵌入向量
    print(f"{word}: {skipgram_model.input_to_hidden.weight[idx].detach().numpy()}")
python 复制代码
# 绘制二维词向量图
#plt.rcParams["font.family"]=['SimHei'] # 用来设定字体样式
#plt.rcParams['font.sans-serif']=['SimHei'] # 用来设定无衬线字体样式
#plt.rcParams['axes.unicode_minus']=False # 用来正常显示负号
fig, ax = plt.subplots() 
for word, idx in word_to_idx.items():
    # 获取每个单词的嵌入向量
    vec = skipgram_model.input_to_hidden.weight[idx].detach().numpy() 
    ax.scatter(vec[0], vec[1]) # 在图中绘制嵌入向量的点
    ax.annotate(word, (vec[0], vec[1]), fontsize=12) # 点旁添加单词标签
plt.title(' 二维词嵌入 ', FontProperties = font) # 图题
plt.xlabel(' 向量维度 1', FontProperties = font) # X 轴 Label
plt.ylabel(' 向量维度 2', FontProperties = font) # Y 轴 Label
plt.show() # 显示图

此外,因为nn.Embedding 是一个简单的查找表,所以input_to_hidden. weight的维度为[voc_size,embedding_size]。因此,当打印和可视化权重时,需要使用weight[idx] 来获取权重。

这个向量蕴含在 PyTorch的嵌入层中,可以通过embedding_size参数来调整它的维度。此处嵌入层的维度是2,但刚才说过,处理真实语料库时,嵌入层的维度一般来说有几百个,这样才可以习得更多的语义知识。其实,几百维的词向量,对于动辄拥有上万,甚至十万、百万个词的词汇表(《辞海》的词条数,总条目数近13万)来说,已经算是很"低"维、很稠密了。

所以,词向量或者说词嵌入的学习过程就是,通过神经网络来习得包含词的语义信息的向量,这个向量通常是几维到几百维不等,然后可以降维进行展示,以显示词和词之间的相似程度。如图所示。

这些词向量捕捉了词与词之间的关系之后,具有相似含义或用法的词在向量空间中会靠得更近。我们可以使用这些词向量作为其他自然语言处理任务(如文本分类、文本相似度比较、命名实体识别等)的输入特征。

Word2Vec之后的许多词嵌入方法,如G1oVe (Global Vectors for Word Representation)和fastText,也都是这样使用的。我们可以拿到别人已经训练好的词向量(G1oVe和fastText都提供现成的词向量供我们下载)作为输入,来完成我们的下游NLP任务;也可以利用PyTorch 的nn.Embedding,来针对特定语料库从头开始词嵌入的学习,然后再把学到的词向量(也就是经过nn.Embedding的参数处理后的序列信息)作为输入,完成下游NLP任务。

Word2Vec小结

Word2Vec对整个自然语言处理领域产生了巨大的影响。后来的许多词嵌入方法,如GloVe 和 fastText 这两种被广泛应用的词向量,都受到了Word2Vec的启发。如今,Word2Vec已经成为词嵌入领域的基石。它的出现使得更复杂的NLP任务,如文本分类、情感分析、命名实体识别、机器翻译等,处理起来更轻松。这主要是因为 Word2Vec 生成的词向量能够捕捉到单词之间的语义和语法关系。

然而,Word2Vec仍然存在一些局限性。

  • (1)词向量的大小是固定的。Word2Vec这种"在全部语料上一次习得,然后反复使用"的词向量被称为静态词向量。它为每个单词生成一个固定大小的向量,这限制了模型捕捉词义多样性的能力。在自然语言中,许多单词具有多种含义,但 Word2Vec无法为这些不同的含义生成多个向量表示。
  • (2)无法处理未知词汇。Word2Vec只能为训练过程中出现过的单词生成词向量。对于未知或低频词汇,Word2Vec无法生成合适的向量表示。虽然可以通过拼接词根等方法来解决这个问题,但这并非Word2Vec 本身的功能。

值得注意的是,Word2Vec本身并不是一个完善的语言模型,因为语言模型的目标是根据上下文预测单词,而Word2Vec主要关注生成有意义的词向量。尽管 CBOW和 Skip-Gram 模型在训练过程中学习了单词之间的关系,但它们并未直接对整个句子的概率分布进行建模。而后来的模型,如基于循环神经网络、长短期记忆网络和 Transformer 的模型,则通过对上下文进行建模,更好地捕捉到了语言结构,从而成为更为强大的语言模型。


学习的参考资料:

(1)书籍

利用Python进行数据分析

西瓜书

百面机器学习

机器学习实战

阿里云天池大赛赛题解析(机器学习篇)

白话机器学习中的数学

零基础学机器学习

图解机器学习算法

动手学深度学习(pytorch)

...

(2)机构

光环大数据

开课吧

极客时间

七月在线

深度之眼

贪心学院

拉勾教育

博学谷

慕课网

海贼宝藏

...

相关推荐
码蛊仙尊1 小时前
当我们想用GPU(nlp模型篇)
人工智能·自然语言处理
一粒马豆4 小时前
chromadb使用hugging face模型时利用镜像网站下载注意事项
python·embedding·chroma·词嵌入·hugging face·词向量·chromadb
智慧地球(AI·Earth)5 小时前
DeepSeek V3.1 横空出世:重新定义大语言模型的边界与可能
人工智能·语言模型·自然语言处理
金井PRATHAMA5 小时前
语义普遍性与形式化:构建深层语义理解的统一框架
人工智能·自然语言处理·知识图谱
失散131 天前
自然语言处理——04 注意力机制
人工智能·自然语言处理·注意力机制·seq2seq 架构
%KT%1 天前
简单聊聊多模态大语言模型MLLM
人工智能·语言模型·自然语言处理
Lntano__y1 天前
详细分析大语言模型attention的计算复杂度,从数学角度分析
人工智能·语言模型·自然语言处理
失散131 天前
自然语言处理——03 RNN及其变体
人工智能·rnn·自然语言处理·gru·lstm
B612 little star king1 天前
UNIKGQA论文笔记
论文阅读·人工智能·笔记·自然语言处理·知识图谱
勤劳的进取家1 天前
论文阅读:Do As I Can, Not As I Say: Grounding Language in Robotic Affordances
论文阅读·人工智能·机器学习·语言模型·自然语言处理