6.4 PyTorch实现Skipgram模型

欢迎订阅本专栏:《PyTorch深度学习实践》
订阅地址:https://blog.csdn.net/sinat_33761963/category_9720080.html

  • 第二章:认识Tensor的类型、创建、存储、api等,打好Tensor的基础,是进行PyTorch深度学习实践的重中之重的基础。
  • 第三章:学习PyTorch如何读入各种外部数据
  • 第四章:利用PyTorch从头到尾创建、训练、评估一个模型,理解与熟悉PyTorch实现模型的每个步骤,用到的模块与方法。
  • 第五章:学习如何利用PyTorch提供的3种方法去创建各种模型结构。
  • 第六章:利用PyTorch实现简单与经典的模型全过程:简单二分类、手写字体识别、词向量的实现、自编码器实现。
  • 第七章:利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。
  • 第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可视化、多个GPU并行计算。

skipgram是很经典的词向量模型,在大量的语料上进行训练,得到每个词的嵌入向量。

相关论文

  • “Distributed Representations of Sentences and Documents”
  • “Efficient estimation of word representations in vector space”

相关博客

  • 网上搜索“word2vec","skipgram"一搜一大把
  • 对词向量的系统了解可以参考我的博客:https://blog.csdn.net/sinat_33761963/article/details/53521149

现在来看看PyTorch如何实现Skipgram并训练后获取词向量。

6.4.1 准备数据

准备语料:一般情况下语料会存储在文本文件中,一句话为一行,用python都进来后存放在一个list中,每个元素是文本中的一行。接着对每行为本做分词,英文可以直接按照空格分,中文就需要用到分词器了,分割后就得到了以下代码中的corpus_list。

构建词典:这一步不可或缺,我们要将每个词都转换成数字代表的索引,方便模型识别,而模型输出的索引,也需要再转变为文字,方便人查看。因此,需要建立两个dict, 一个是{索引:词}, 一个是{词:索引},即代码中的ix2word, word2ix。

构建训练对:语料一方面用于构建词典,另一方面需要预处理成模型可以读入的训练对(x,y), skipgram是输入中心词,预测上下文词,因此其数据对应为(center_word, contenx_word),且需要转换成索引形式(center_word_ix, contenx_word_ix)

import torch
import torch.nn as nn
import torch.nn.functional as F

# 准备语料
corpus = ['he is a king',
          'she is a queen',
          'he is a man',
          'she is a woman',
          'warsaw is poland capital',
          'berlin is germany capital',
          'paris is france capital']
corpus_list = [sentence.split() for sentence in corpus]


# 构建词典
word2ix = {}
for sentence in corpus:
    for word in sentence.split():
        if word not in word2ix:
            word2ix[word] = len(word2ix)  # 为每个词都匹配一个索引
ix2word = {v:k for k, v in word2ix.items()}  # 将dict中的key与value互换位置
voc_size = len(word2ix)


# 构建训练对
WINDOWS = 2  # 取左右窗口的词作为context_word
pairs = []  # 存放训练对

for sentence in corpus_list:
    for center_word_index in range(len(sentence)):
        center_word_ix = word2ix[sentence[center_word_index]]
        for win in range(-WINDOWS, WINDOWS+1):
            contenx_word_index = center_word_index + win
            if 0 <= contenx_word_index <= len(sentence)-1 and contenx_word_index != center_word_index:
                context_word_ix = word2ix[sentence[contenx_word_index]]
                pairs.append((center_word_ix, context_word_ix))

6.4.2 构建SkipGram网络结构

x: 输入的x是一个大小为voc_dim的one-hot向量[0,0,…,1,…0]

嵌入矩阵:构建一个嵌入矩阵,它的大小是(emb_dim, voc_dim),emb_dim是词向量的维度, voc_dim是词典的大小。这个矩阵中的值是我们最终要求取的数据,因此它是参数,可以用nn.Parameter()来创建这个矩阵参数。
该矩阵乘以x会得到一个emb_dim大小的向量,就是forward函数中的变量emb

线性计算:参数W乘以emb,是一个线性计算的过程,输出voc_dim大小的向量

softmax计算:线性计算的输出再经过一个softmax后,输出大小与输入保持一致,但向量中的值变成了0-1的概率,即得到了词典中所有词作为输出x的下文词的概率。

前向计算结束。

注意:torch.nn.init.xavier_normal是初始化参数的一种方式,以避免参数过大或过小而阻碍正常训练。

class SkipGram(nn.Module):
    def __init__(self, voc_dim, emb_dim):
        super(SkipGram, self).__init__()
        # 初始化参数
        self.embedding_matrix = nn.Parameter(torch.FloatTensor(emb_dim, voc_dim))
        self.W = nn.Parameter(torch.FloatTensor(voc_dim, emb_dim))
        torch.nn.init.xavier_normal(self.embedding_matrix)
        torch.nn.init.xavier_normal(self.W)

    def forward(self, x):
        emb = torch.matmul(self.embedding_matrix, x)
        h = torch.matmul(self.W, emb)  # [voc_dim]
        log_softmax = F. log_softmax(h)  # [voc_dim]

        return log_softmax

6.4.3开始训练

还是老套路。

注意,这是一个为了演示的小例子,所以epoch, embedding_dim都设置的很小,在实际训练中要根据实际效果去设置,embedding_dim一般可以设置为100.

# 提前设置超参数
epoch = 10
lr = 1e-2
embedding_dim = 5

# 模型、优化器、损失
model = SkipGram(voc_size, embedding_dim)
optim = torch.optim.Adam(model.parameters(), lr=lr)
loss_f = torch.nn.NLLLoss()  

# 这是将索引变成词典大小的One-Hot向量的方法
def get_onehot_vector(ix):
    one_hot_vec = torch.zeros(voc_size).float()
    one_hot_vec[ix] = 1.0
    return one_hot_vec

# 迭代
for e in range(epoch):
    epoch_loss = 0

    for i, (center_ix, context_ix) in enumerate(pairs):
        optim.zero_grad()

        # 预处理好数据结构
        one_hot_vec = get_onehot_vector(center_ix)
        y_true = torch.Tensor([context_ix]).long()

        # 前向
        y_pred = model(one_hot_vec)
        loss = loss_f(y_pred.view(1, -1), y_true)

        # 后向
        loss.backward()
        epoch_loss += loss.data.item()

        # 梯度更新
        optim.step()

    if e % 2 == 0:
        print('epoch: %d, loss: %f' % (e, epoch_loss))
  
C:\Users\CC\Anaconda3\lib\site-packages\ipykernel_launcher.py:7: UserWarning: nn.init.xavier_normal is now deprecated in favor of nn.init.xavier_normal_.
  import sys
C:\Users\CC\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: nn.init.xavier_normal is now deprecated in favor of nn.init.xavier_normal_.
  
C:\Users\CC\Anaconda3\lib\site-packages\ipykernel_launcher.py:13: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  del sys.path[0]


epoch: 0, loss: 190.530474
epoch: 2, loss: 178.796980
epoch: 4, loss: 153.809181
epoch: 6, loss: 134.125972
epoch: 8, loss: 126.447176

6.4.4 预测

以上训练结束,我们得到了嵌入矩阵model.embedding_matrix,它的每一列代表一个词的嵌入向量。可以通过以下方式获取。

# # 3.预测:预测单词的向量并计算相似度
v1 = torch.matmul(model.embedding_matrix, get_onehot_vector((word2ix['he'])))
v2 = torch.matmul(model.embedding_matrix, get_onehot_vector((word2ix['she'])))
v3 = torch.matmul(model.embedding_matrix, get_onehot_vector((word2ix['capital'])))

print(v1)
print(v2)
print(v3)

s_v1_v2 = F.cosine_similarity(v1, v2, dim=0)
s_v1_v3 = F.cosine_similarity(v1, v3, dim=0)
print(s_v1_v2)
print(s_v1_v3)
tensor([ 0.7496, -1.2529, -1.1052,  0.3301, -0.9289], grad_fn=<MvBackward>)
tensor([ 0.2016, -1.9385, -0.7472,  0.0589, -0.8677], grad_fn=<MvBackward>)
tensor([-1.1139, -0.1073,  0.2193,  1.3546, -0.8456], grad_fn=<MvBackward>)
tensor(0.8998, grad_fn=<DivBackward1>)
tensor(0.0710, grad_fn=<DivBackward1>)
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页