Word2Vec

Word2Vec

  • 背景知识
    • 语言模型
    • 词表示
  • 对比模型
    • NNLM
    • RNNLM
  • 模型结构
    • skip-gram模型
    • CBOW模型
  • 关键技术
    • Hierarchical Softmax (层次Softmax)
    • 负采样(Negative Sampling)
  • 模型复杂度分析
  • 代码实现

背景知识

Word2Vec是一种词向量的表示方式,是在论文《Efficient Estimation of Word Representations in
Vector Space》中提出,有两种网络模型来训练(Skip-gram和CBOW模型)。由Word2Vec训练出来的词向量可以提升下游任务的性能、效果。

语言模型

语言模型是计算一个句子是句子的概率模型,即判断该句子是否合乎语义和语法。语言模型的建立方法有:基于专家语法规则的语言模型、统计语言模型、神经网络语言模型。
**基于专家语法规则的语言模型:**是由语言学专家设置一套规则而形成的语言模型,例如主谓宾结构的。
**统计语言模型:**基于统计概率的语言模型,即判断这个句子形成的概率。P(S)是句子的概率,P(W_i)是每个单词的概率。
P ( S ) = P ( W 1 , W 2 , . . . , W n ) = P ( W 1 ) P ( W 2 ∣ W 1 ) P ( W 3 ∣ W 1 W 2 ) . . . P ( W n ∣ W 1 W 2 W 3 . . . W n − 1 ) P{\left({S}\right)}=P{\left({W_1,W_2,...,W_n}\right)}=P{\left({W_1}\right)}P{\left( {W_2\left|W_1\right. }\right)}P{\left( {W_3\left|W_1W_2\right. }\right)}...P{\left( {W_n\left|W_1W_2W_3...W_n-1\right. }\right)} P(S)=P(W1,W2,...,Wn)=P(W1)P(W2W1)P(W3W1W2)...P(WnW1W2W3...Wn1)
每个单词的概率是基于一定文档(语料)来求取的,即有一定数量的文档,统计每个单词在所有文档中出现的次数,再除以所有单词的总数,进而求得该单词的频率,用频率代替概率。
P ( W i ) = P ( c o u n t ( W i ) ) / N P{\left({W_i}\right)}=P{\left({count(W_i)}\right)}/N P(Wi)=P(count(Wi))/N
统计语言模型中的平滑操作: 由于基于一定数量的文档而形成的词典并不能囊括世界上所有的单词,再者由于新词的产生,一定会出现一些词或词组是在语料中没有出现的,但是在现实中这种词或词组是真实存在的。为解决这一问题,可使用平滑操作,即对所有词初始状态就有一个很小的概率。方法:Laplace Smoothing(加1平滑),即每个词在原先出现次数的基础上加1.
马尔可夫假设: 即下一个词的出现仅仅依赖于前面的一个词或几个词。基于该假设有经典的语言模型:N-gram模型。常见的N-gram模型有:1-gram、2-gram、3-gram模型。下一个词的出现依赖于前N-1个单词。
语言模型的评价指标:困惑度
P ( S ) = P ( W 1 , W 2 , . . . , W n ) = P ( W 1 ) P ( W 2 ∣ W 1 ) P ( W 3 ∣ W 1 W 2 ) . . . P ( W n ∣ W 1 W 2 W 3 . . . W n − 1 ) P{\left({S}\right)}=P{\left({W_1,W_2,...,W_n}\right)}=P{\left({W_1}\right)}P{\left( {W_2\left|W_1\right. }\right)}P{\left( {W_3\left|W_1W_2\right. }\right)}...P{\left( {W_n\left|W_1W_2W_3...W_n-1\right. }\right)} P(S)=P(W1,W2,...,Wn)=P(W1)P(W2W1)P(W3W1W2)...P(WnW1W2W3...Wn1)
困惑度:
P P ( S ) = P ( W 1 , W 2 , . . . , W n ) − 1 / n PP(S)=P{\left({W_1,W_2,...,W_n}\right)}^{-1/n} PP(S)=P(W1,W2,...,Wn)1/n
句子概率越大,语言模型越号,困惑度越小。

词表示

one-hot表示 形成一个V维的词表,每个词在其中一个位置上表示为1,其他位置为0.
分布式词表示 每个词都是一个维度为D的一维向量,这种表示方法可以判断单词之间的相似度。

对比模型

NNLM

NNLM,即前馈神经网络语言模型。根据前n-1个单词来预测第n个单词的概率。Word2Vec_第1张图片

RNNLM

RNNLM,基于循环神经网络的语言模型。在每个时间步预测一个单词,在预测第n个单词时使用了前n-1个单词的信息。
Word2Vec_第2张图片

模型结构

Word2Vec中有两种网络模型结构:skip-gram和cbow。Word2Vec的基本思想是句子中相近的词之间是由联系的,所以用词来预测词。其中skip-gram模型是中心词预测周围词,cbow模型是使用周围词预测中心词。

skip-gram模型

Word2Vec_第3张图片
P ( W i − 1 ∣ W i ) = e x p ( u w i − 1 T ∗ v w i ) ∑ w = 1 V e x p ( u w T ∗ v w i ) P{\left( {W_{i-1}\left|W_i\right. }\right)}=\frac{exp(u_{w_{i-1}}^T*v_{w_i})}{\begin{matrix} \sum_{w=1}^V exp(u_w^T*v_{w_i})\end{matrix}} P(Wi1Wi)=w=1Vexp(uwTvwi)exp(uwi1Tvwi)

CBOW模型

Word2Vec_第4张图片
计算方式:
Word2Vec_第5张图片

关键技术

使用关键技术来降低模型复杂度:层次softmax和负采样。

Hierarchical Softmax (层次Softmax)

层次Softmax的基本思想是将求Softmax的计算转换为求Sigmoid的计算,采用Huffman树的方式,即带权重路径最短二叉树。
层次softmax的构建
Word2Vec_第6张图片
Skip-gram模型
Word2Vec_第7张图片
Cbow模型
Word2Vec_第8张图片

负采样(Negative Sampling)

负采样的基本思想是:增大正样本的概率,减小负样本的概率。
单词采样频率的确定:
Word2Vec_第9张图片
归一化的用处在于:减少频率大的词的抽样概率,增加频率小的词的抽样概率。
负采样方法:
在这里插入图片描述
重采样:
Word2Vec_第10张图片
Word2Vec_第11张图片

模型复杂度分析

模型复杂度:O=E✖T✖Q
其中:O是训练复杂度
E是训练迭代次数
T是数据集大小
Q是模型计算复杂度
网络模型的计算复杂度是由网络中的参数个数决定的。 Word2Vec_第12张图片

代码实现

Skip-Gram-NEG模型实现:

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

class SkipGramModel(nn.Module):
    def __init__(self,vocab_size,embed_size):#定义一个词表大小、词向量维度大小
        super(SkipGramModel,self).__init__()
        self.vocab_size = vocab_size
        self.embed_size = embed_size
        self.w_embeddings = nn.Embedding(vocab_size,embed_size)#定义中心词维度
        self.v_embeddings = nn.Embedding(vocab_size,embed_size)#定义周围词维度
        self._init_emb()

    def _init_emb(self):#词向量初始化方法
         initrange = 0.5 / self.embed_size
         self.w_embeddings.weight.data.uniform_(-initrange,initrange)#符合正态分布
         self.v_embeddings.weight.data.uniform_(-0,0)#符合正态分布

    def forward(self,pos_w,pos_v,neg_v):
        emb_w = self.w_embeddings(torch.LongTensor(pos_w).cuda()) #转化为tensor,大小为 mini_batch_size * emb_dimension
        emb_v = self.v_embeddings(torch.LongTensor(pos_v).cuda())
        neg_emb_v = self.v_embeddings(torch.LongTensor(neg_v).cuda()) # 转换为tensor,大小为negative_sampling_number * mini_batch_size * emb_dimension
        score = torch.mul(emb_w,emb_v)

        score = torch.sum(score,dim=1)
        score = torch.clamp(score,max=10,min=10)
        score = F.logsigmoid(score) #根据skip-gram网络结构计算正例损失函数

        neg_score = torch.bmm(neg_emb_v,emb_w.unsqueeze(2))
        neg_score = torch.clamp(neg_score,max=10,min=10)
        neg_score = F.logsigmoid(-1*neg_score)#计算负例损失函数

        loss = -torch.sum(score)-torch.sum(neg_score)#总的损失
        return loss

    def save_embedding(self,id2word,file_name):#保存词向量
         embedding_1 = self.w_embeddings.weight.data.cpu().numpy()
         embedding_2 = self.v_embeddings.weight.data.cpu().numpy()
         embedding = (embedding_1+embedding_2)/2
         fout = open(file_name,'w')
         fout.write('%d %d\n' % (len(id2word)))
         for wid,w in id2word.items():
             e = embedding[wid]
             e = ' '.join(map(lambda x:str(x),e))
             fout.write('%d %d\n' % (w,e))

CBOW-NGE模型实现:

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np


class CBOWModel(nn.Module):
    def __init__(self, vocab_size, emb_size):
        super(CBOWModel, self).__init__()
        self.vocab_size = vocab_size
        self.emb_size = emb_size
        self.u_embeddings = nn.Embedding(self.vocab_size, self.emb_size, sparse=True)
        self.w_embeddings = nn.Embedding(self.vocab_size, self.emb_size, sparse=True)
        self._init_embedding()  # 初始化

    def _init_embedding(self):
        int_range = 0.5 / self.emb_size
        self.u_embeddings.weight.data.uniform_(-int_range, int_range)
        self.w_embeddings.weight.data.uniform_(-0, 0)


    def forward(self, pos_u, pos_w, neg_w):
        pos_u_emb = []  # 上下文embedding
        for per_Xw in pos_u:
            # 上下文矩阵的第一维不同词值不同,如第一个词上下文为c,第二个词上下文为c+1,需要统一化
            per_u_emb = self.u_embeddings(torch.LongTensor(per_Xw).cuda())  # 对上下文每个词转embedding
            per_u_numpy = per_u_emb.data.cpu().numpy()  # 转回numpy,好对其求和
            per_u_numpy = np.sum(per_u_numpy, axis=0)
            per_u_list = per_u_numpy.tolist()  # 为上下文词向量Xw的值
            pos_u_emb.append(per_u_list)  # 放回数组
        pos_u_emb = torch.FloatTensor(pos_u_emb).cuda()  # 转为tensor 大小 [ mini_batch_size * emb_size ]
        pos_w_emb = self.w_embeddings(torch.LongTensor(pos_w).cuda())  # 转换后大小 [ mini_batch_size * emb_size ]
        neg_w_emb = self.w_embeddings(
            torch.LongTensor(neg_w).cuda())  # 转换后大小 [ mini_batch_size*negative_sampling_number  * emb_size ]
        # 计算梯度上升( 结果 *(-1) 即可变为损失函数 ->可使用torch的梯度下降)
        score_1 = torch.mul(pos_u_emb, pos_w_emb)  # Xw.T * θu
        score_2 = torch.sum(score_1, dim=1)  # 点积和
        score_3 = F.logsigmoid(score_2)  # log sigmoid (Xw.T * θu)
        neg_score_1 = torch.bmm(neg_w_emb, pos_u_emb.unsqueeze(2))  # batch_size*negative_sampling_number
        neg_score_2 = F.logsigmoid((-1) * neg_score_1)
        loss = torch.sum(score_3) + torch.sum(neg_score_2)
        return -1 * loss

    # 存储embedding
    def save_embedding(self, id2word_dict, file_name):
        embedding = self.u_embeddings.weight.data.numpy()
        file_output = open(file_name, 'w')
        file_output.write('%d %d\n' % (self.vocab_size, self.emb_size))
        for id, word in id2word_dict.items():
            e = embedding[id]
            e = ' '.join(map(lambda x: str(x), e))
            file_output.write('%s %s\n' % (word, e))

你可能感兴趣的:(项目实战)