文章目录

  • 词向量简介
  • PTB 数据集
  • Skip-gram的pytorch实现

词向量简介

ont-hot向量表示单词简单,但是不能表现出词语词之间的相似度
word2vec词嵌入可以解决上面的问题。word2vec将词表示成一个定长的向量,然后通过在语料库中的预训练使得这些向量能够学习到词与词之间的相似关系和类比关系。
word2vec有两种基本假设,一种是基于CBOW,另一种是基于Skip-gram。如果是用一个词语作为输入,来预测它周围的上下文,那这个模型叫做Skip-gram 模型而如果是拿一个词语的上下文作为输入,来预测这个词语本身,则是CBOW模型。
下图是
下图是Skip-gram模型

简单来说,word2vec是用一个一层的神经网络把one-hot形式的稀疏词向量映射称为一个n维(n一般为几百)的稠密向量的过程。为了加快模型训练速度,其中的tricks包括Hierarchical softmax,negative sampling, Huffman Tree等

PTB 数据集

这里我们使用经典的 PTB 语料库进行训练。PTB (Penn Tree Bank) 是一个常用的小型语料库,它采样自《华尔街日报》的文章,包括训练集、验证集和测试集。我们将在PTB训练集上训练词嵌入模型。

Skip-gram的pytorch实现

import collections
import math
import random
import sys
import time
import os
import numpy as np
import torch
from torch import nn
import torch.utils.data as Datawith open('ptb.train.txt', 'r') as f:lines = f.readlines() # 该数据集中句子以换行符为分割raw_dataset = [st.split() for st in lines] # st是sentence的缩写,单词以空格为分割
print('# sentences: %d' % len(raw_dataset))# 对于数据集的前3个句子,打印每个句子的词数和前5个词
# 句尾符为 '' ,生僻词全用 '' 表示,数字则被替换成了 'N'
for st in raw_dataset[:3]:print('# tokens:', len(st), st[:5])counter = collections.Counter([tk for st in raw_dataset for tk in st]) # tk是token的缩写
counter = dict(filter(lambda x: x[1] >= 5, counter.items())) # 只保留在数据集中至少出现5次的词idx_to_token = [tk for tk, _ in counter.items()]
token_to_idx = {tk: idx for idx, tk in enumerate(idx_to_token)}
dataset = [[token_to_idx[tk] for tk in st if tk in token_to_idx]for st in raw_dataset] # raw_dataset中的单词在这一步被转换为对应的idx
num_tokens = sum([len(st) for st in dataset])#二次采样操作。越高频率的词一般意义不大,根据公式高频词越容易被过滤。准确来说,应该是降频操作。既不希望超高频被完全过滤,又希望减少高频词对训练的影响。
def discard(idx):'''@params:idx: 单词的下标@return: True/False 表示是否丢弃该单词'''return random.uniform(0, 1) < 1 - math.sqrt(1e-4 / counter[idx_to_token[idx]] * num_tokens)subsampled_dataset = [[tk for tk in st if not discard(tk)] for st in dataset]
print('# tokens: %d' % sum([len(st) for st in subsampled_dataset]))def compare_counts(token):
#展示了discard操作之后,剩下词的数量return '# %s: before=%d, after=%d' % (token, sum([st.count(token_to_idx[token]) for st in dataset]), sum([st.count(token_to_idx[token]) for st in subsampled_dataset]))print(compare_counts('the'))
print(compare_counts('join'))def get_centers_and_contexts(dataset, max_window_size):'''@params:dataset: 数据集为句子的集合,每个句子则为单词的集合,此时单词已经被转换为相应数字下标max_window_size: 背景词的词窗大小的最大值@return:centers: 中心词的集合contexts: 背景词窗的集合,与中心词对应,每个背景词窗则为背景词的集合'''centers, contexts = [], []for st in dataset:if len(st) < 2:  # 每个句子至少要有2个词才可能组成一对“中心词-背景词”continuecenters += stfor center_i in range(len(st)):window_size = random.randint(1, max_window_size) # 随机选取背景词窗大小indices = list(range(max(0, center_i - window_size),min(len(st), center_i + 1 + window_size)))indices.remove(center_i)  # 将中心词排除在背景词之外contexts.append([st[idx] for idx in indices])return centers, contextsall_centers, all_contexts = get_centers_and_contexts(subsampled_dataset, 5)tiny_dataset = [list(range(7)), list(range(7, 10))]
print('dataset', tiny_dataset)
for center, context in zip(*get_centers_and_contexts(tiny_dataset, 2)):print('center', center, 'has contexts', context)#skip_gram向前计算
def skip_gram(center, contexts_and_negatives, embed_v, embed_u):'''@params:center: 中心词下标,形状为 (n, 1) 的整数张量contexts_and_negatives: 背景词和噪音词下标,形状为 (n, m) 的整数张量embed_v: 中心词的 embedding 层embed_u: 背景词的 embedding 层@return:pred: 中心词与背景词(或噪音词)的内积,之后可用于计算概率 p(w_o|w_c)'''v = embed_v(center) # shape of (n, 1, d)u = embed_u(contexts_and_negatives) # shape of (n, m, d)pred = torch.bmm(v, u.permute(0, 2, 1)) # bmm((n, 1, d), (n, d, m)) => shape of (n, 1, m)return pred#负采样近似加快程序运行时间
def get_negatives(all_contexts, sampling_weights, K):'''@params:all_contexts: [[w_o1, w_o2, ...], [...], ... ]sampling_weights: 每个单词的噪声词采样概率K: 随机采样个数@return:all_negatives: [[w_n1, w_n2, ...], [...], ...]'''all_negatives, neg_candidates, i = [], [], 0population = list(range(len(sampling_weights)))for contexts in all_contexts:negatives = []while len(negatives) < len(contexts) * K:if i == len(neg_candidates):# 根据每个词的权重(sampling_weights)随机生成k个词的索引作为噪声词。# 为了高效计算,可以将k设得稍大一点i, neg_candidates = 0, random.choices(population, sampling_weights, k=int(1e5))neg, i = neg_candidates[i], i + 1# 噪声词不能是背景词if neg not in set(contexts):negatives.append(neg)all_negatives.append(negatives)return all_negativessampling_weights = [counter[w]**0.75 for w in idx_to_token]
all_negatives = get_negatives(all_contexts, sampling_weights, 5)class MyDataset(torch.utils.data.Dataset):def __init__(self, centers, contexts, negatives):assert len(centers) == len(contexts) == len(negatives)self.centers = centersself.contexts = contextsself.negatives = negativesdef __getitem__(self, index):return (self.centers[index], self.contexts[index], self.negatives[index])def __len__(self):return len(self.centers)def batchify(data):'''用作DataLoader的参数collate_fn@params:data: 长为batch_size的列表,列表中的每个元素都是__getitem__得到的结果@outputs:batch: 批量化后得到 (centers, contexts_negatives, masks, labels) 元组centers: 中心词下标,形状为 (n, 1) 的整数张量contexts_negatives: 背景词和噪声词的下标,形状为 (n, m) 的整数张量masks: 与补齐相对应的掩码,形状为 (n, m) 的0/1整数张量labels: 指示中心词的标签,形状为 (n, m) 的0/1整数张量'''max_len = max(len(c) + len(n) for _, c, n in data)centers, contexts_negatives, masks, labels = [], [], [], []for center, context, negative in data:cur_len = len(context) + len(negative)centers += [center]contexts_negatives += [context + negative + [0] * (max_len - cur_len)]masks += [[1] * cur_len + [0] * (max_len - cur_len)] # 使用掩码变量mask来避免填充项对损失函数计算的影响labels += [[1] * len(context) + [0] * (max_len - len(context))]batch = (torch.tensor(centers).view(-1, 1), torch.tensor(contexts_negatives),torch.tensor(masks), torch.tensor(labels))return batchbatch_size = 256
num_workers = 0 if sys.platform.startswith('win32') else -1dataset = MyDataset(all_centers, all_contexts, all_negatives)
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True,collate_fn=batchify, num_workers=num_workers)
for batch in data_iter:for name, data in zip(['centers', 'contexts_negatives', 'masks','labels'], batch):print(name, 'shape:', data.shape)break#采用交叉熵损失函数
class SigmoidBinaryCrossEntropyLoss(nn.Module):def __init__(self):super(SigmoidBinaryCrossEntropyLoss, self).__init__()def forward(self, inputs, targets, mask=None):'''@params:inputs: 经过sigmoid层后为预测D=1的概率targets: 0/1向量,1代表背景词,0代表噪音词@return:res: 平均到每个label的loss'''inputs, targets, mask = inputs.float(), targets.float(), mask.float()res = nn.functional.binary_cross_entropy_with_logits(inputs, targets, reduction="none", weight=mask)res = res.sum(dim=1) / mask.float().sum(dim=1)return resloss = SigmoidBinaryCrossEntropyLoss()def sigmd(x):return - math.log(1 / (1 + math.exp(-x)))
print('%.4f' % ((sigmd(1.5) + sigmd(-0.3) + sigmd(1) + sigmd(-2)) / 4)) # 注意1-sigmoid(x) = sigmoid(-x)
print('%.4f' % ((sigmd(1.1) + sigmd(-0.6) + sigmd(-2.2)) / 3))embed_size = 200 #200维的词向量
net = nn.Sequential(nn.Embedding(num_embeddings=len(idx_to_token), embedding_dim=embed_size),nn.Embedding(num_embeddings=len(idx_to_token), embedding_dim=embed_size))def train(net, lr, num_epochs):device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')print("train on", device)net = net.to(device)optimizer = torch.optim.Adam(net.parameters(), lr=lr)for epoch in range(num_epochs):start, l_sum, n = time.time(), 0.0, 0for batch in data_iter:center, context_negative, mask, label = [d.to(device) for d in batch]pred = skip_gram(center, context_negative, net[0], net[1])l = loss(pred.view(label.shape), label, mask).mean() # 一个batch的平均。lossoptimizer.zero_grad()l.backward()optimizer.step()l_sum += l.cpu().item()n += 1print('epoch %d, loss %.2f, time %.2fs'% (epoch + 1, l_sum / n, time.time() - start))train(net, 0.01, 5)
#不知道为什么没办法调用GPU,GPU跑起来略慢,一个小时才跑好
#测试模型
def get_similar_tokens(query_token, k, embed):'''@params:query_token: 给定的词语k: 近义词的个数embed: 预训练词向量'''W = embed.weight.datax = W[token_to_idx[query_token]]# 添加的1e-9是为了数值稳定性cos = torch.matmul(W, x) / (torch.sum(W * W, dim=1) * torch.sum(x * x) + 1e-9).sqrt()_, topk = torch.topk(cos, k=k+1)topk = topk.cpu().numpy()for i in topk[1:]:  # 除去输入词print('cosine sim=%.3f: %s' % (cos[i], (idx_to_token[i])))get_similar_tokens('fine', 3, net[0])
#可能是因为数据集比较小,因此结果并不准确

word2vec的pytorch实现相关推荐

  1. 词向量介绍以及Word2Vec的pytorch实现

    词向量 在自然语言处理任务中,首先需要考虑字.词如何在计算机中表示.通常,有两种表示方式:one-hot表示和分布式表示 one-hot表示 把每个词表示为一个长向量.这个向量的维度是词表大小,向量中 ...

  2. word2Vec之Pytorch实现_理论部分

    目录 目录 1.n-gram 2.神经网络语言模型 3.word2Vec 4.训练技巧 4.1重采样 4.2负采样 4.3层序softmax 1.n-gram 一句话由许多词构成,例如:"I ...

  3. PyTorch实现Word2Vec

    本文主要是使用PyTorch复现word2vec论文 PyTorch中的nn.Embedding 实现关键是nn.Embedding()这个API,首先看一下它的参数说明 其中两个必选参数num_em ...

  4. 20221107学习word2vec

    [随便写写,个人理解] 一.word2vec 起初用于语言处理[将中文.英文换成计算机可以识别的语言,也就是词向量] 可以通过多种方法进行模型的训练[pytorch.tensorflow.python ...

  5. 机器学习竞赛必备基础知识_Word2Vec

    1  简介 本文我们主要介绍词嵌入中一种非常经典的算法,Word2Vec,早期Word2Vec主要被用在文本类的问题中,但是现在做比赛的朋友应该都发现了,几乎一半的传统数据竞赛都会用到Word2Vec ...

  6. PyTorch-09 循环神经网络RNNLSTM (时间序列表示、RNN循环神经网络、RNN Layer使用、时间序列预测案例、RNN训练难题、解决梯度离散LSTM、LSTM使用、情感分类问题实战)

    PyTorch-09 循环神经网络RNN&LSTM (时间序列表示.RNN循环神经网络.RNN Layer使用.时间序列预测案例(一层的预测点的案例).RNN训练难题(梯度爆炸和梯度离散)和解 ...

  7. Pytorch+Text-CNN+Word2vec+电影评论情感分析实战

    文章目录 0.前言 1.电影评论数据集 2.数据读取 3.数据预处理 4.准备训练和测试集 5.加载词向量模型Word2vec 6.定义网络 7.训练网络 8.测试网络和可视化 9.总结 0.前言 很 ...

  8. word2vec理解及pytorch实现

    word2vec理解及pytorch实现 word2vec优点 1.低维稠密 2.蕴含语义信息 Skip-gram模型 1.训练样本 2.skip-gram 负采样 negative sample 欠 ...

  9. Pytorch实现word2vec(Skip-gram训练方式)

    示例代码和语料库来自于博客:(宝藏博主,其它博客对学习NLP很有用) https://wmathor.com/index.php/archives/1443/ https://wmathor.com/ ...

最新文章

  1. RunTime技术总结
  2. 1.3 Quick Start中 Step 3: Create a topic官网剖析(博主推荐)
  3. Silverlight中如何自己写方法将DataTable转换为PagedCollectionView数据(动态创建类)
  4. BAT执行DOS命令查找本地浏览器
  5. 使用NavigationUI更新UI组件
  6. html-javascript前端页面刷新重载的方法汇总
  7. Oracle 20c 新特性:XGBoost 机器学习算法和 AutoML 的支持
  8. 【作者面对面问答】包邮送《Redis 5设计与源码分析》5本
  9. ubuntu下的vim与ctags
  10. VB / VS 多语言软件设计
  11. 如何激发孩子的想象力_如何培养孩子想象力
  12. 智慧城市数字孪生技术方案,建设可视化系统
  13. echarts柱状图自定义颜色
  14. 漂亮的字体 手写_20种漂亮的草书和手写字体可供下载
  15. 【axios】get和post请求用法
  16. 西安非全日制计算机研究生哪所学校好,报考陕西非全日制研究生有哪些学校可以选择?...
  17. 一篇文章,助你实现认知突破,重获新生
  18. 百度地图线路颜色_山东到底发展成了什么样子,这两张地图不会说谎
  19. 好东西为什么卖不动,店铺选址开店必读!
  20. SpringSecurity - 用户动态授权 及 动态角色权限

热门文章

  1. 因为此版本的应用程序不支持其项目类型(.csproj)”之解
  2. python .txt文件转.csv文件-ok
  3. 如何用手机快捷设计品牌宣传的海报图片和视频
  4. Win10睡眠唤醒后显示网络电缆被拔出怎么办
  5. ElectronBot支线项目
  6. VMware上面实现Ubuntu和Windows文件的复制粘贴功能(以及虚拟机当中插入U盘能够显示)
  7. Python实现邮箱自动群发工资条
  8. html最多显示两行,css 实现两行或多行文本溢出显示省略号(...)
  9. 25岁裸辞转行5G网络优化工程师:比盲目赶路更为重要的,是知道方向—分享优橙小故事
  10. 搞笑小品剧本-- 搞笑《英雄》——气晕张艺谋