NLP之基于词嵌入(WordVec)的嵌入矩阵生成并可视化
词嵌入
@
1.理论
1.1 为什么使用词嵌入?
- one-hot向量(长度为词库大小,去重排序,一个one-hot仅在单词序号处取1,其余均为0)可以表示词,但是各个单词的one-hot乘积均为0,也就是看不出关联.
- 所以可以用特征化的嵌入向量来表示单词(矩阵列不是序号,而是n个特征,所需空间远少于列长为词库大小的one-hot),然后根据t-SNE算法把n维数据嵌入到而且平面,可以直观看到一些词的相近程度
\(嵌入向量=嵌入矩阵*onehot向量\)
1.2 词嵌入的类比推理
观察向量man、woman、king和queen的嵌入向量,可以发现 \(man-woman≈king-queen\), 则可以知道单词之间的类比;同时根据向量差也可以看出,man和woman、king和queen在哪些特征上有差别(例如gender)
两个单词之间的相似度可以用余弦相似度衡量
1.3 学习词嵌入
学习词嵌入 -> 建立一个语言模型:
预测下一个单词 初始化一个参数矩阵,乘以各单词的one-hot向量,得到嵌入向量,把这些嵌入向量作为输入x放进神经网络,假设嵌入向量长度为n,句子已知单词数为m,则softmax分类器得到的向量维度是m×n,相当于把几个嵌入向量叠加在一起(实际中会设置一个固定的历史窗口,例如每次只看前/后k个词(也可同时看前和后),即窗口大小=k,,softmax层输入向量维度=k×n)
需要在m*n个可能的输出中预测这个单词(每个隐藏层包括softmax层都有自己的参数W和b)
1.4 Word2Vec & Skip-Gram(跳字模型)
采用Skip-Gram模型的Word2Vec算法: 基本步骤与1.3 学习词嵌入
相同。不同之处在于,每次给定一个上下文词,然后选择目标词,但是每次并不只选前k个词,而是在一定词距内随机选择一个词
(从目标字词推测原始语句)
1.5 分级&负采样
由于softmax层向量维度过大,因此实践中往往会采用分级或负采样两种方法降低计算成本
1.5.1 分级
每次并不会直接精确到属于n个词中的哪一个词,而是采用二分类法,不断划分直至最后。分级方法的计算成本与log2(词汇表大小)成正比。
Tips:为了进一步降低成本,常用词往往会放到离树顶更近的位置。
1.5.2 负采样
对于每一个上下文词,在已选择正确的目标词,即有正类样本的基础上,会特意生成一系列负类样本。相当于一个样本一次迭代进行了多次训练。
1.6 Glove词向量
Glove算法是一种十分简便的词嵌入算法.
- 设词库大小为n,设X_ij是单词i在单词j上下文(假设是左右各k个词)中出现的次数,即X_ij是一个能够获取单词i和j出现位置相近时或是彼此接近的频率的计数器
- 遍历训练集,计算单词i在不同单词j上下文中一共出现的总次数,利用梯度下降法将差距最小化
- \(minimize{\sum_{i=1}^n}{\sum_{j=1}^n}f(X_{ij})(θ_i^Te_j+b_i+b_j'-logX_{ij})^2\)
2.实验
2.1 实验步骤
- 数据预处理,得到单词字典、样本数等基本数据
- 构建模型
- 构建跳字模型:遍历数据,依次把第2个词~倒数第2个词作为目标词,然后对于每个target依次选择前、后一个词作为上下文词,将每一对(目标词,上下文词)加入跳字模型
- Word2Vec模型:2个全连接层
- 训练
- 根据batch_size随机抽取样本,代入数据
- 把模型输出值和真实值相比,求得误差损失函数,运用Adam动量法梯度下降
- 嵌入矩阵可视化
"""
Task: 生成单词的特征嵌入矩阵,并进行图形化显示
Author: ChengJunkai @github.com/Cheng0829
Email: chengjunkai829@gmail.com
Date: 2022/09/05
Reference: Tae Hwan Jung(Jeff Jung) @graykode
"""
import numpy as np
import torch, os, sys, time
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
'''1.数据预处理'''
def pre_process(sentences):
# 分词
word_sequence = " ".join(sentences).split() # ['apple', 'banana', 'fruit', 'banana', 'orange', 'fruit', 'orange', 'banana', 'fruit', 'dog', 'cat', 'animal', 'cat', 'monkey', 'animal', 'monkey', 'dog', 'animal']
# 去重
word_list = []
'''
如果用list(set(word_sequence))来去重,得到的将是一个随机顺序的列表(因为set无序),
这样得到的字典不同,保存的上一次训练的模型很有可能在这一次不能用
(比如上一次的模型预测碰见i:0,love:1,就输出you:2,但这次模型you在字典3号位置,也就无法输出正确结果)
'''
for word in word_sequence:
if word not in word_list:
word_list.append(word)
# 生成字典
word_dict = {w:i for i,w in enumerate(word_list)} # 注意:单词是键,序号是值
# 词库大小:8
vocab_size = len(word_list)
return word_sequence, word_list, word_dict, vocab_size
'''2-1:原版跳字模型'''
def Skip_Grams_original(sentences, word_sequence, word_dict):
'''
原版:不同语句之间的边界词,会组成(target,context)
'''
'''
固定窗口大小(前后各一个词)
依次把第2个词~倒数第2个词作为目标词,然后对于每个target依次选择前、后一个词作为上下文词.
将每一对(目标词,上下文词)加入跳字模型
'''
skip_grams = []
for i in range(1, len(word_sequence)-1):
target = word_dict[word_sequence[i]] # 目标词序号键值
context_1 = word_dict[word_sequence[i-1]] # 目标词的前一个词的序号
context_2 = word_dict[word_sequence[i+1]] # 目标词的后一个词的序号
# 添加(目标词,上下文词)样本对
skip_grams.append([target, context_1])
skip_grams.append([target, context_2])
return skip_grams
'''2-1:跳字模型-cjk'''
def Skip_Grams(sentences, word_sequence, word_dict):
'''
对于原版进行了一些修改,当构建跳字模型时,
仅在一个语句内挑选(target,context),不同句子之间不会产生输入输出对
'''
'''
固定窗口大小(前后各一个词)
依次把第2个词~倒数第2个词作为目标词,然后对于每个target依次选择前、后一个词作为上下文词.
将每一对(目标词,上下文词)加入跳字模型
'''
skip_grams = []
for i in range(len(sentences)):
sentences_i_list = sentences[i].split(' ')
length_sentences = len(sentences_i_list)
for j in range(1,length_sentences-1):
target = word_dict[sentences_i_list[j]] # 目标词序号键值
context_1 = word_dict[sentences_i_list[j-1]] # 目标词的前一个词的序号
context_2 = word_dict[sentences_i_list[j+1]] # 目标词的后一个词的序号
skip_grams.append([target, context_1])
skip_grams.append([target, context_2])
return skip_grams
'''2-2:Word2Vec模型'''
class Word2Vec(nn.Module): # nn.Module是Word2Vec的父类
def __init__(self): # (输入/输出层大小,嵌入层大小)
'''
super().__init__()
继承父类的所有方法(),比如nn.Module的add_module()和parameters()
'''
super().__init__()
'''2个全连接层
troch.nn.Linear(in_features_size, out_features_size)
输入向量:(batch_size, in_features_size)
输出向量:(batch_size, out_features_size)
'''
self.W = nn.Linear(vocab_size, embedding_size, bias=False) # 输入层:vocab_size > embedding_size Weight
self.WT = nn.Linear(embedding_size, vocab_size, bias=False) # 输出层:embedding_size > vocab_size Weight
# Tips:W和WT不是转换关系
def forward(self, X): # input_batch
hidden_layer = self.W(X)
hidden_layer = hidden_layer.to(device)
output_layer = self.WT(hidden_layer)
output_layer = output_layer.to(device)
''' X --W--> hidden_layer --WT--> output_layer '''
# X:[batch_size, vocab_size]
# hidden_layer:[batch_size, embedding_size]
# output_layer:[batch_size, vocab_size]
return output_layer
'''3-1:从跳字模型中随机抽样输入输出对(target,context),构建输入输出向量矩阵'''
def random_batch(skip_grams, batch_size, vocab_size):
input_batch = []
target_batch = []
random_index = np.random.choice(range(len(skip_grams)), batch_size, replace=False)
"""np.random.choice(L,num,replace=True):从数组/列表/元组中随机抽取num个元素
L:数组/列表/元组
num:选择元素的个数
replace=True(Default)则可以取相同数字; replace=False则不能取相同数字
"""
for i in random_index:
# target
# np.eye(8)生成8x8对角矩阵 -> one-hot向量
input_batch.append(np.eye(vocab_size)[skip_grams[i][0]])
# context
target_batch.append(skip_grams[i][1])
"""
input_batch存储随机选择的target词向量,作为神经网络的输入
target_batch存储对应的context单词,作为真实的输出
由输入input_batch得到的输出output与目标值(真实输出)target_batch相比较,即可得到损失函数值
"""
input_batch = np.array(input_batch) # (batch_size, vocab_size)
target_batch = np.array(target_batch) # (batch_size)
# type: array -> tensor
'''input_batch包含batch_size个one-hot向量'''
input_batch = torch.Tensor(input_batch) # [batch_size, vocab_size]
'''target_batch包含batch_size个context词的序号'''
target_batch = torch.LongTensor(target_batch) # [batch_size]
input_batch = input_batch.to(device)
target_batch = target_batch.to(device)
return input_batch, target_batch
"""i.e.
input_batch:
[[0. 1. 0. 0. 0. 0. 0. 0.],
[0. 0. 0. 0. 0. 0. 0. 1.]]
target_batch:
[6 0]
即选出的输入输出对是(2,6)和(7,0)
"""
if __name__ == '__main__':
device = ['cuda:0' if torch.cuda.is_available() else 'cpu'][0]
embedding_size = 3 # 嵌入矩阵大小,即样本特征数,即嵌入向量的"长度"
batch_size = 2 # 批量大小
"""batch_size:表示单次传递给程序用以训练的参数个数
假设训练集有1000个数据,若设置batch_size=100,那么程序首先会用数据集第1-100个数据来训练模型。
当训练完成后更新权重,再使用第101-200的个数据训练,用完训练集中的1000个数据后停止
Pros:可以减少内存的使用、提高训练的速度(因为每次完成训练后都会更新权重值)
Cons:使用少量数据训练时可能因为数据量较少而造成训练中的梯度值较大的波动。
"""
chars = '*' * 20
sentences = ["apple banana fruit", "banana orange fruit", "orange banana fruit",
"dog cat animal", "cat monkey animal", "monkey dog animal"]
'''1.数据预处理'''
word_sequence, word_list, word_dict, vocab_size = pre_process(sentences)
'''2.构建模型'''
'''2-1:构建跳字模型Skip_Grams''' # 上下一个词,[(target,context)......]
skip_grams = Skip_Grams(sentences, word_sequence, word_dict)
'''2-2:构建模型''' # Word2Vec():两个全连接层 input->hidden->output
model = Word2Vec() # (输入/输出层大小,嵌入层大小)
model.to(device)
# 交叉熵损失函数,用于解决二分类或多分类问题,其内部会自动加上Softmax层
criterion = nn.CrossEntropyLoss()
# Adam动量法比随机梯度下降SGD更好
optimizer = optim.Adam(model.parameters(), lr=0.001)
'''
>>> model.parameters())
(W): Linear(in_features=8, out_features=2, bias=False)
(WT): Linear(in_features=2, out_features=8, bias=False)
'''
if os.path.exists('model_param.pt') == True:
# 加载模型参数到模型结构
model.load_state_dict(torch.load('model_param.pt', map_location=device))
'''3.训练'''
loss_record = []
for epoch in range(50000):
'''3-1:从跳字模型中随机抽样输入输出对(target,context),构建输入输出向量矩阵'''
input_batch, target_batch = random_batch(skip_grams, batch_size, vocab_size)
# print(input_batch, target_batch)
'''3-2:导入模型进行(代码格式固定)'''
# model->forward()
output = model(input_batch)
output = output.to(device)
optimizer.zero_grad() # 把梯度置零,即把loss关于weight的导数变成0.
# output : [batch_size, vocab_size]
# target_batch : [batch_size,] (LongTensor, not one-hot)
loss = criterion(output, target_batch) # 将输出与真实目标值对比,得到损失值
loss.backward() # 将损失loss向输入侧进行反向传播,梯度累计
optimizer.step() # 根据优化器对W、b和WT、bT等参数进行更新(例如Adam动量法和随机梯度下降法SGD)
if loss >= 0.01: # 连续30轮loss小于0.01则提前结束训练
loss_record = []
else:
loss_record.append(loss.item())
if len(loss_record) == 30:
torch.save(model.state_dict(), 'model_param.pt')
break
if (epoch + 1) % 1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'Loss = {:.6f}'.format(loss))
torch.save(model.state_dict(), 'model_param.pt')
W, WT = model.parameters() # W即为嵌入矩阵
'''4.Draw'''
print(input_batch)
print(W, WT)
# 根据嵌入矩阵,可视化各个单词的特征值
for i, word in enumerate(word_list): # 枚举词库中每个单词
x, y = W[0][i].item(), W[1][i].item()
plt.scatter(x, y) # 散点图
plt.annotate(word, xy=(x,y))
'''plt.annotate(s='str',xy=(x,y)......)函数用于标注文字
s:文本注释内容
xy:被注释的坐标点
color 设置字体颜色 color={'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}
'''
plt.show()
NLP之基于词嵌入(WordVec)的嵌入矩阵生成并可视化的更多相关文章
- 斯坦福NLP课程 | 第1讲 - NLP介绍与词向量初步
作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...
- Deep Learning In NLP 神经网络与词向量
0. 词向量是什么 自然语言理解的问题要转化为机器学习的问题,第一步肯定是要找一种方法把这些符号数学化. NLP 中最直观,也是到目前为止最常用的词表示方法是 One-hot Representati ...
- NLP笔记:词向量和语言模型
NLP问题如果要转化为机器学习问题,第一步是要找一种方法把这些符号数学化. 有两种常见的表示方法: One-hot Representation,这种方法把每个词表示为一个很长的向量.这个向量的维度是 ...
- html5权威指南:嵌入另一张HTML文档、通过插件嵌入内容、嵌入数字表现形式
嵌入另一张HTML文档.通过插件嵌入内容.嵌入数字表现形式:http://www.cnblogs.com/yc-755909659/p/5928125.html
- 文本情感分析(一):基于词袋模型(VSM、LSA、n-gram)的文本表示
现在自然语言处理用深度学习做的比较多,我还没试过用传统的监督学习方法做分类器,比如SVM.Xgboost.随机森林,来训练模型.因此,用Kaggle上经典的电影评论情感分析题,来学习如何用传统机器学习 ...
- 【CV知识学习】【转】beyond Bags of features for rec scenen categories。基于词袋模型改进的自然场景识别方法
原博文地址:http://www.cnblogs.com/nobadfish/articles/5244637.html 原论文名叫Byeond bags of features:Spatial Py ...
- 基于eclipse的mybatis映射代码自动生成的插件
基于eclipse的mybatis映射代码自动生成的插件 分类: JAVA 数据库 工具相关2012-04-29 00:15 2157人阅读 评论(9) 收藏 举报 eclipsegeneratori ...
- 基于eclipse的mybatis映射代码自动生成的插件http://blog.csdn.net/fu9958/article/details/7521681
基于eclipse的mybatis映射代码自动生成的插件 分类: JAVA 数据库 工具相关2012-04-29 00:15 2157人阅读 评论(9) 收藏 举报 eclipsegeneratori ...
- SSM 框架基于ORACLE集成TKMYBATIS 和GENERATOR自动生成代码(Github源码)
基于前一个博客搭建的SSM框架 https://www.cnblogs.com/jiangyuqin/p/9870641.html 源码:https://github.com/JHeaven/ssm- ...
随机推荐
- 前端监控系列1| 字节的前端监控SDK是怎样设计的
作者:彭莉,火山引擎 APM 研发工程师,2020年加入字节,负责前端监控 SDK 的开发维护.平台数据消费的探索和落地. 摘要 字节内部应用环境多样( Web 应用.小程序.Electron 应用. ...
- Nginx 代理Vue项目出现Invalid Host header
说明 使用 Nginx 的 upstream 对 Vue 项目做负载均衡时,代理的地址无法访问目标地址,且页面报错: Invalid Host header(无效主机头) 分析 检查 Nginx 的 ...
- mybatis 09: 动态sql --- part1
作用 可以定义代码片段 可以进行逻辑判断 可以进行循环处理(批量处理),使条件判断更为简单 使用方式 通过mybatis中与动态sql有关的标签来实现 < sql >标签 + < i ...
- Spring源码-Bean生命周期总览
- 01_Linux基础-部署-VMware-Xshell-Xftp-内核-安迪比尔定理
01_Linux基础-部署-VMware-Xshell-Xftp-内核-安迪比尔定理 博客:https://blog.csdn.net/cpen_web CentOS开源 免费 --- CentOS是 ...
- KingbaseES 数据库大小写敏感特性
针对不同版本.是否启用大小写敏感,特征汇总如下:
- 天天写SQL,这些神奇的特性你知道吗?
摘要:不要歪了,我这里说特性它不是 bug,而是故意设计的机制或语法,你有可能天天写语句或许还没发现原来还能这样用,没关系我们一起学下涨姿势. 本文分享自华为云社区<[云驻共创]天天写 SQL, ...
- 思维导图学《On Java》基础卷 + 进阶卷
说明 目录 思维导图 导读 第 1 章 什么是对象 第 3 章 一切都是对象 第 6 章 初始化和清理 第 7 章 实现隐藏 第 8 章 复用 第 9 章 多态 第 10 章 接口 第 11 章 内部 ...
- C#,根据路径获取某个数字开头的所有文件夹,并获取最新文件夹进行替换文件
项目需求获取某路径下为1开头文件夹,并替换最新文件夹内容,话不多说,上代码 private void Form1_Load(object sender, EventArgs e) { try { st ...
- 论文解读(RvNN)《Rumor Detection on Twitter with Tree-structured Recursive Neural Networks》
论文信息 论文标题:Rumor Detection on Twitter with Tree-structured Recursive Neural Networks论文作者:Jing Ma, Wei ...