文章目录

  • 1. 整体思路
  • 2. 数据准备
  • 3. 构造共现矩阵
  • 4. 得到序列
  • 5. 创建数据管道
  • 6. 模型构建
  • 7. 模型训练
  • 8. 加载模型测试

1. 整体思路

在这个算法中,为了使得效果比较有对比性,我们仍然采用前面word2vec算法实现时的数据来进行GloVe模型的实现,为此,这里的数据处理和数据准备(即剔除标点、分词、得到数据与编号的字典)过程都可以拿过来用,当然这里还多了一步,构造共现矩阵的步骤。因为GloVe模型实际上就是对word2vec模型的一种改进,只不过训练的参数多了两个偏置项,损失的函数也发生了变化而已,所以,构建模型时,我们也可以采用 nn.Embedding 层,这样,构建数据管道的时候,只需要传入具体的数字即可,不需要传入One-hot编码,且数据管道那里还少了负采样的步骤,最后就是利用构建的模型对数据进行训练得到词向量了。

2. 数据准备

按照word2vec的实现中对数据处理的方法,将文本中的中文提取出来,并将每一句话使用 jieba 进行分词存储在 test.txt 文件中后,就需要对文本中的词进行编号,得到编号与词的映射以及词与编号的映射,还需要设置一个最大窗口数 MAX_SIZEMAX\_SIZEMAX_SIZE,提取出频次在 MAX_SIZE−1MAX\_SIZE-1MAX_SIZE1 的词语,然后剩下的词语全部归在 <UNK><UNK><UNK> 即未知词下面。代码如下:

from collections import Counter
import numpy as np# 最大词数
MAX_SIZE = 10000
# 训练的词向量维度
embedding_size = 100
# 单边窗口数
single_win_size = 3
# 最大词频
x_max = 100
batch_size = 32
lr = 1e-3
epoch = 5with open("data/test.txt", 'r', encoding='utf-8') as f:content = f.read().split(" ")words = dict(Counter(content).most_common(MAX_SIZE-1))
words['<UNK>'] = len(content) - np.sum(list(words.values()))word2idx = {word:i for i, word in enumerate(words.keys())}
idx2word = {i:word for i, word in enumerate(words.keys())}

3. 构造共现矩阵

在GloVe模型中起了很大作用的就是共现矩阵,所以构建共现矩阵十分重要。

首先,共现矩阵是一个 10000×1000010000 \times 1000010000×10000 维度的大小,我们用行索引代表中心词,列索引代表背景词,大致逻辑如下:遍历每个中心词及其窗口内的词,将出现在中心词窗口内的背景词对应索引(中心词,背景词)的共现次数+1,窗口不断滑动,直到窗口滑动到末尾,返回构建的共现矩阵。代码如下:

def get_co_occurrence_matrix(content, word2idx):'''构建共现矩阵:param content: 文章分词后的列表:param word2idx: 字典(词:ID):return: 共现矩阵'''# 初始化共现矩阵matrix = np.zeros((MAX_SIZE, MAX_SIZE), np.int32)# 单词列表转为编码content_encode = [word2idx.get(w, MAX_SIZE - 1) for w in content]# 遍历每一个中心词for i, center_id in enumerate(content_encode):# 取得同一窗口词在文中的索引pos_indices = list(range(i - single_win_size, i)) + list(range(i + 1, i + single_win_size + 1))# 取得同一窗口的词索引,避免越界,使用取模操作window = [j % len(content) for j in pos_indices]# 取得词对应的IDwindow_id = [content_encode[j] for j in window]# 使得中心词对应的背景词次数+1for j in window_id:matrix[center_id][j] += 1return matrixmatrix = get_co_occurrence_matrix(content, word2idx)

4. 得到序列

在GloVe的训练中,由于要创建共现矩阵,但是,共现矩阵中肯定不可能是每个元素与每个元素都有关系,即共现矩阵中必定会有值为0的元素,这种元素由于带入惩罚函数后惩罚是0,所以代入损失得到后损失值也是0,对我们的训练时没有任何帮助的,反而会加大我们的训练量(没错,我试过)并且在使用log函数时还需要判断是否为0,所以,在这里我们选择仅对非零元素进行训练,这就需要首先提取出非零元素的标号,然后使用数据管道提供的index来进行索引,得到具体的行列索引值。这里先实现得到非零元素的序列这一步。代码如下:

def get_nozero(matrix):# 得到矩阵中非零元素的索引index_nozero = []for i in range(MAX_SIZE):for j in range(i+1):if matrix[i][j] != 0:# 将[i,j]和[j,i]都添加进去是为了让一个词在中心词矩阵和背景词矩阵都得到训练index_nozero.append([i,j])index_nozero.append([j,i])return index_nozeroindex_nozero = get_nozero(matrix)

5. 创建数据管道

我们再来看看要求的损失函数需要用到的数据
loss=∑i,kf(xik)(viTuk+bi+bk−log⁡xik)2loss=\sum_{i,k}f(x_{ik})(v_i^Tu_k + b_i+b_k-\log x_{ik})^2 loss=i,kf(xik)(viTuk+bi+bklogxik)2由于 vi,uk,bi,bkv_i,u_k,b_i,b_kvi,uk,bi,bk 都是训练的数据,所以我们的数据只需要传入 f(xik),xikf(x_{ik}),x_{ik}f(xik),xik 即可,如果将惩罚函数重新建立一个矩阵的话,那么所消耗的时间与空间将会特别大(我试过,确实很慢),所以我们选择将其在数据管道中进行实现。

数据管道中要取得的元素是非零元素的索引,所以这里的 __len__ 方法返回的长度应该为 index_nozero 的长度。

最后,考虑一下我们需要从数据中拿到什么。从损失函数的表达式中,我们可以看出,需要的是元素 xikx_{ik}xik 及其惩罚 f(xik)f(x_{ik})f(xik),并且损失函数中的向量 vi,ukv_i,u_kvi,uk 的角标与 xikx_{ik}xik 角标一致,所以还需要返回行列的索引值。代码如下

from torch.utils.data import Dataset, DataLoader
import torchclass GloVeDataset(Dataset):def __init__(self, matrix, index_nozero):super(GloVeDataset, self).__init__()  # 第一行必须是这个self.matrix = torch.Tensor(matrix)self.index_nozero = index_nozerodef __len__(self):return len(index_nozero)def __getitem__(self, idx):row = self.index_nozero[idx][0]column = self.index_nozero[idx][1]# 这里后面必须是张量数据,否则拼接的时候会报错x_ik = torch.tensor([self.matrix[row][column]])punish_x = torch.tensor([x_ik ** (0.75) if x_ik < x_max else 1])return row, column, x_ik, punish_xglove_dataset = GloVeDataset(matrix, index_nozero)
dataloader = DataLoader(glove_dataset, batch_size, shuffle=True)

打印一下数据管道的输出如下

for i, (row, clolumn, x_il, punish_x) in enumerate(dataloader):print(row)print(clolumn)print(x_il)print(punish_x)break

tensor([1775, 9, 5402, 7567, 2833, 263, 185, 174, 1200, 3400, 893, 760,
689, 765, 0, 5547, 537, 631, 8, 1331, 2072, 19, 225, 1478,
0, 51, 712, 4165, 192, 5550, 669, 2781])
tensor([ 598, 40, 71, 420, 1279, 3015, 1272, 3649, 3736, 1710, 94, 4074,
3233, 720, 6686, 5241, 179, 9079, 635, 7341, 157, 393, 287, 2015,
2983, 7931, 707, 2992, 5846, 926, 898, 1398])
tensor([[ 2.],
[20.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 2.],
[ 1.],
[ 2.],
[ 1.],
[ 3.],
[ 1.],
[ 9.],
[ 1.],
[ 1.],
[ 1.],
[ 2.],
[ 1.],
[ 3.],
[ 1.],
[ 1.],
[ 1.],
[ 1.],
[ 2.],
[ 1.],
[ 1.]])
tensor([[1.6818],
[9.4574],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.6818],
[1.0000],
[1.6818],
[1.0000],
[2.2795],
[1.0000],
[5.1962],
[1.0000],
[1.0000],
[1.0000],
[1.6818],
[1.0000],
[2.2795],
[1.0000],
[1.0000],
[1.0000],
[1.0000],
[1.6818],
[1.0000],
[1.0000]])

6. 模型构建

接下来就是模型的构建了。模型构建首先我们要知道我们需要训练的参数是什么。第一点首先是词向量,因为GloVe是word2vec模型改进而来的,所以关于中心词向量以及背景词向量的训练时必须保留的;第二点是偏置项,从下面损失函数公式中我们可以清晰的看到,偏置项 bi,bkb_i,b_kbi,bk 也是我们需要训练的参数。
loss=∑i,kf(xik)(viTuk+bi+bk−log⁡xik)2loss=\sum_{i,k}f(x_{ik})(v_i^Tu_k + b_i+b_k-\log x_{ik})^2 loss=i,kf(xik)(viTuk+bi+bklogxik)2明白了这些点后,就可以开始构建模型了。

class GloVe(nn.Module):def __init__(self, vocab_size, embed_size):super(GloVe, self).__init__()self.vocab_size = vocab_sizeself.embed_size = embed_size# 中心词矩阵self.center_embed = nn.Embedding(self.vocab_size, self.embed_size)# 背景词矩阵self.backgroud_embed = nn.Embedding(self.vocab_size, self.embed_size)# 中心词偏置,偏置为一个常数,故为1维self.center_bias = nn.Embedding(self.vocab_size, 1)# 背景词偏置self.backgroud_bias = nn.Embedding(self.vocab_size, 1)def forward(self, row, column, x_ik, punish_x):'''注意输入是按批次输入的,所以其维度与批次一样:param row: [batch_size]:param column: [batch_size]:param x_ik: [batch_size, 1]:param punish_x: [batch_size, 1]:return:'''v_i = self.center_embed(row) # [batch_size, embed_size]u_k = self.backgroud_embed(column) # [batch_size, embed_size]b_i = self.center_bias(row)  # [batch_size, 1]# 需要将其变为一维才能正常做加法b_i = b_i.squeeze(1)  # [batch_size]b_k = self.backgroud_bias(column)  # [batch_size, 1]b_k = b_k.squeeze(1)  # [batch_size]x_ik = x_ik.squeeze(1)  # [batch_size]punish_x = punish_x.squeeze(1)  # [batch_size]# 按照损失函数计算损失即可loss = punish_x * (torch.mul(v_i, u_k).sum(dim=1) + b_i + b_k - torch.log(x_ik)) ** 2return lossdef get_predic_vec(self):# 采用作者的方法,返回两者相加的权重return self.center_embed.weight.data.cpu().numpy()+self.backgroud_embed.weight.data.cpu().numpy()device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = GloVe(MAX_SIZE, embedding_size).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=lr)

7. 模型训练

最后则是模型的训练了,模型训练按照前面的格式来就行,这一块比较简单。

def train_model():#训练模型for e in range(epoch):for i, (row, clolumn, x_il, punish_x) in enumerate(dataloader):row = row.to(device)clolumn = clolumn.to(device)x_il = x_il.to(device)punish_x = punish_x.to(device)optimizer.zero_grad()loss = model(row, clolumn, x_il, punish_x).mean()loss.backward()optimizer.step()if i % 1000 == 0:print('epoch', e, 'iteration', i, loss.item())torch.save(model.state_dict(), "data/glove-{}.th".format(embedding_size))train_model()

8. 加载模型测试

上面训练好了模型后,我们可以加载模型对词的相关性进行一个测试,测试采用与前面的word2vec一样的方法,输入一个词,按照余弦相似度返回与这个词最相关的100个词,因为我文本也是与word2vec实验时用的一样的,所以这里的测试可以看出两者测试的一个差别。

from sklearn.metrics.pairwise import cosine_similaritydef find_word(word):'''计算并输出与输入词最相关的100个词:param word: 输入词:return:'''# 加载模型model = GloVe(MAX_SIZE, embedding_size)model.load_state_dict(torch.load("data/glove-100.th"))# 获取中心词矩阵embedding_weight = model.get_predic_vec()# 得到词与词向量的字典word2embedding = {}for i in words:word2embedding[i] = embedding_weight[word2idx[i]]# 得到输入词与其他词向量的余弦相似度other = {}for i in words:if i == word:continue# 计算余弦相似度other[i] = cosine_similarity(word2embedding[word].reshape(1, -1), word2embedding[i].reshape(1, -1))# 对余弦相似度按从大到小排序other = sorted(other.items(), key=lambda x: x[1], reverse=True)count = 0# 输出排序前100的相似度词语for i, j in other:print("({},{})".format(i, j))count += 1if count == 100:breakfind_word('大师')

测试结果如下所示,碍于篇幅,就不放全部的结果了。

(弗兰德,[[0.4906645]])
(听,[[0.44961697]])
(点头,[[0.38447148]])
(老师,[[0.37548357]])
(道,[[0.36903727]])
(教导,[[0.36127198]])
(点,[[0.35702538]])
(说,[[0.32980374]])
(告诉,[[0.316578]])
(目光,[[0.31207395]])
(一眼,[[0.3024309]])
(明白,[[0.29733318]])
(唐昊,[[0.29704148]])
(二龙,[[0.29656404]])
(众人,[[0.29131022]])
(微笑,[[0.29005405]])
(赵无极,[[0.2833815]])
(身边,[[0.28156656]])
(摇头,[[0.28018552]])
(孩子,[[0.2790551]])
(淡然,[[0.2748983]])
(风致,[[0.27243102]])
(柳,[[0.26889658]])
(指点,[[0.2659605]])
(摇,[[0.2657492]])
(问道,[[0.26130816]])
(说道,[[0.25864923]])
(话,[[0.25843865]])
(愣,[[0.2583086]])
(唐三,[[0.25819662]])
(不禁,[[0.25735095]])
(一句,[[0.2567986]])
(看着,[[0.25605386]])
(眼中,[[0.25521308]])
(叹息,[[0.24495934]])
(研究,[[0.24350488]])

全部代码可以在我的Github仓库进行查看。

NLP模型(二)——GloVe实现相关推荐

  1. NLP(二十五)实现ALBERT+Bi-LSTM+CRF模型

      在文章NLP(二十四)利用ALBERT实现命名实体识别中,笔者介绍了ALBERT+Bi-LSTM模型在命名实体识别方面的应用.   在本文中,笔者将介绍如何实现ALBERT+Bi-LSTM+CRF ...

  2. NLP(二)文本生成 --VAE与GAN模型和迁移学习

    NLP(二)文本生成 --VAE与GAN模型和迁移学习 VAE与GAN模型和迁移学习 1. Auto Encoder 自编码器 1.1 结构 1.2 核心思想 1.3 损失函数 1.4 Denoisi ...

  3. 【NLP】from glove import Glove的使用、模型保存和加载

    1 引言 不要被stackflow的上的一个的回答所误导. 2 使用方法举例 # 语料 sentense = [['你', '是', '谁'], ['我', '是', '中国人']] corpus_m ...

  4. 神经网络并不是尚方宝剑,我们需要正视深度 NLP 模型的泛化问题

    来源:AI 科技评论 前段时间的文章<顶会见闻系列:ACL 2018,在更具挑战的环境下理解数据表征及方法评价>中,我们介绍了 ACL 大会上展现出的 NLP 领域的最新研究风向和值得关注 ...

  5. 攻击NLP模型:通用对抗触发器入门

    背景和重要性 对于非全局触发器(只对特定模型和输入奏效).为了写公式方便起见,我们假设触发器是在正常输入之前. 我们用f表示特定模型,t表示正常输入,tadvt_{adv}tadv​表示触发器,L表示 ...

  6. NLP模型也有“老师”了!装上这个开源库,1毫秒纠正语法错误

    杨净 发自 凹非寺 量子位 报道 | 公众号 QbitAI 当NLP模型产生了语法错误,怎么办? 比如,He wants that you send him an email. 没关系,现在可以像小时 ...

  7. 如何0代码、快速定制企业级NLP模型?百度技术大咖在线解析,可报名

    位来 发自 凹非寺 量子位 报道 | 公众号 QbitAI 近几年以预训练为代表的NLP技术取得了爆发式发展,新技术新模型层出不穷. 在新时代背景下,如何将最先进的NLP领域科研成果,高效地应用到产业 ...

  8. 百分点认知智能实验室:NLP模型开发平台在舆情分析中的设计和实践(下)

    编者按 NLP模型开发平台是以快速打造智能业务为核心目标,无需机器学习专业知识,模型创建-数据上传-数据标注(智能标注.数据扩充)-模型训练-模型发布-模型校验全流程可视化便捷操作,短时间内即可获得高 ...

  9. 推荐:常见NLP模型的代码实现(基于TensorFlow和PyTorch)

    推荐github上的一个NLP代码教程:nlp-tutorial,教程中包含常见的NLP模型代码实现(基于TensorFlow和Pytorch),而且教程中的大多数NLP模型都使用少于100行代码. ...

最新文章

  1. 使用关键点进行小目标检测
  2. BZOJ 3626: [LNOI2014]LCA
  3. matlab学习记录之基本操作整理
  4. 如何快速判断某 URL 是否在 20 亿的网址 URL 集合中?
  5. C++11特性:override
  6. 【转】WCF Data Service 使用小结 (一)—— 了解OData协议
  7. Vue + Element UI 实现 登陆注册基本demo实例
  8. linux SHH 免密码登录 配置
  9. [转] js中的钩子机制(hook)
  10. (翻译)开始iOS 7中自动布局教程(二)
  11. WEP无线网络密码破解
  12. Android Lottie动画
  13. MySQL中对索引的理解 特点 优势_深入理解MySQL索引和优化
  14. python金融衍生品_Python 金融数据分析:单一风险衍生品估值丨数析学院
  15. 【生信】全基因组测序(WGS)
  16. C语言基础-计算一个整数各个位数之和
  17. matplotlib plot画图不弹框
  18. 年度征文 | 回顾2022,展望2023(我难忘的2022,我憧憬的2023)
  19. 济南公积金 销户 提取
  20. 机制设计专栏(2)-说一说IC机制

热门文章

  1. 根据开始时间与结束时间,计算季度
  2. 闲鱼架构专家,详解亿级C2C电商平台,商品体系架构如何搭建?
  3. 【小记】steam 神奇软件
  4. [附源码]计算机毕业设计Python+uniapp基于Android的自来水收费系统3e359(程序+源码+LW+远程部署)
  5. 基于单片机的晾衣架仿真设计(#0053)
  6. 基于centos 安装配置环境
  7. 无胁科技-TVD每日漏洞情报-2022-9-29
  8. php服务器搭建iis,PHP+IIS 服务器环境的搭建
  9. “校园的雨 ”——落花,散了一地
  10. java 文件去除扩展名_Java操作——获取文件扩展名,去掉文件扩展名