word2vec的原理以及实现

word2vec是早期NLP的必要预处理过程,其用于生成词的向量表示(embeding)。

w_{embed} = f(word)=[0.1,0.2,...,0.21,0.32]

其将单词映射为固定长度的向量(embeding向量),而通过向量表示可以更好地表达不同词之间的相关性,因此会使得后续的分类、生成等NLP任务更好地学习训练。word2vec描述不同词之间的相关性,主要是指词同其上下文的其他词的共现性,主要有两种范式:

  • 跳元模型Skip-gram:其是假设通过中心词w_c生成其上下文w_{c\pm i},因此其目标是在中心词下,其上下文的条件概率P(w_{c-k},...,w_{c-1},w_{c+1},...,w_{c+k}|w_c)最大,即如下优化式子,C表示中心词的数量,k表示上下文窗口数。

\prod^C_c P(w_{c-k},...,w_{c-1},w_{c+1},...,w_{c+k}|w_c)=\prod^C_c \prod^k_{i=1} P(w_{c-i}|w_c)*P(w_{c+i}|w_c)

  • 连续词袋CBOW:其是假设通过上下文w_{c\pm i}生成中心词w_c,因此其目标在上下文下,其中心词生成条件概率P(w_c | w_{c-k},...,w_{c-1},w_{c+1},...,w_{c+k})最大,即如下优化式子:

\prod^C_c P(w_c|w_{c-k},...,w_{c-1},w_{c+1},...,w_{c+k})

 本文重点介绍跳元模型Skip-gram,为了求解上述式子,将上式求log转换为最小化下式:

Min - \sum^C_c \sum^{\pm k}_{i=\pm 1} log(P(w_{c+i}|w_c))

P(w_u|w_c)=\frac{P(w_u, w_c)}{P(w_c)}=\frac{P(w_u, w_c)}{\sum P(w_*, w_c)}

其中上式中的词与词间的联合分布P(w_u,w_c)可以由词向量相似度衡量,word2vec为了方便计算,通过exp形式进行度量:

P(w_u|w_c)=\frac{P(w_u, w_c)}{P(w_c)}=\frac{P(w_u, w_c)}{\sum P(w_*, w_c)}=\frac{e^{Ew_u * Ew_c}}{\sum e^{Ew_* *Ew_c}}

上式中的Ew表示词向量,word2vec就是通过Embeding模块实现由单词到词向量的转换,从而上面的Loss最小化。Embeding模块实际上类似由于一个全连接网络层,其输入是N维的one-hot向量(N是指全量词的个数),输出是L维的向量(L是词向量的长度),其参数量总共为N*L。

 word2vec主要是为求解上述Embeding模块的权重参数w_{c,h},其组成了中心词c的词向量Ew_c,可以求其偏导数如下:

\\ \frac{\partial logP(w_o|w_c)}{\partial Ew_c}\\ =\frac{\partial }{\partial Ew_c}(Ew_o*Ew_c-log(\sum P(w_*, w_c)))\\ =Ew_o-\frac{\sum Ew_* P(w_*, w_c)}{\sum P(w_*, w_c)}\\ =Ew_o-\sum P(w_*|w_c)Ew_*

以下我们通过paddle代码实现word2vec网络结构的定义:

class Word2Vec(nn.Layer):
    def __init__(self, num_embeddings, embedding_dim):
        super(Word2Vec, self).__init__() 
        self.embed = nn.Embedding(num_embeddings, embedding_dim, 
                weight_attr=paddle.ParamAttr(
                    name="center_embed",
                    initializer=paddle.nn.initializer.XavierUniform()))
        
    # 执行前向计算
    def forward(self, center, contexts_and_negatives=None):
        """Skip-Gram"""
        v = self.embed(center)
        if contexts_and_negatives is None:
            return v
        u = self.embed(contexts_and_negatives)
        pred = paddle.squeeze(paddle.bmm(v, u.transpose(perm=[0, 2, 1])), axis=1)
        return pred

上述定义中的pred用于表示Ew_u*Ew_v来描述两个词向量相乘项。

在训练时,我们将原来Loss转换为批量进行训练,另外P(w_u|w_c)的求解由于涉及到softmax计算,计算相对困难,因此一种我们将简化的方式进行训练(负采样)。

其首先定义w_u,w_c共同出现时,定义词u在中心词窗口k内的概率为:

P(D=1|w_u,w_c)=\sigma (Ew_u*Ew_c)=\frac{1}{1 + e^{-Ew_u*Ew_c}}

同理不在中心词窗口k内的概率为:

P(D=0|w_u,w_c)=1-\sigma (Ew_u*Ew_c)=1-\frac{1}{1 + e^{-Ew_u*Ew_c}}

此时条件概率可以表示为:

P(w_u|w_c)=P(D=1|w_u,w_c)*\prod_{*\sim P(w)} P(D=0|w_*,w_c)

此时batch内的loss可以表示为:

-\sum^B(\sum^{\pm k }_{\pm i}logP(D=1|w_{c+i},w_c)+\sum^h log(P(D=0|w_h,w_c))))

其中k表示正例的窗口大小,h表示负例数(即不在上下文窗口的词),上述loss函数可以用binary_cross_entropy_with_logits损失函数表示:

Out = -label * log(\sigma (logits))+(1-label)log(1 - \sigma (logits))

其中中label表示词是正或负例,logits即为Ew_u*Ew_v,因此我们可以设计如下的损失函数代码

class SigmoidBCELoss(nn.Layer):
    # 带掩码的二元交叉熵损失
    def __init__(self):
        super().__init__()

    def forward(self, inputs, label, mask):
        out = nn.functional.binary_cross_entropy_with_logits(
            logit=inputs, label=label, weight=mask, reduction="none")
        return out.mean(axis=1)

整体的paddle训练代码如下:

# 中心词
center_spec = paddle.static.InputSpec([None, 1], 'int64', 'center')
# 上下文正例词及负例词
context_spec = paddle.static.InputSpec([None, max_context_len], 'int64', 'contexts_and_negatives')
# 正例及负例的标识
label_spec = paddle.static.InputSpec([None, max_context_len], 'float32', 'label')
# mask,正例及负例以外的填充为0不参与训练
mask_spec = paddle.static.InputSpec([None, max_context_len], 'float32', 'mask')

model = paddle.Model(Word2Vec(num_embeddings, embedding_dim), [center_spec, context_spec], [label_spec, mask_spec])
model.prepare(
    optimizer=paddle.optimizer.Adam(learning_rate=learning_rate, parameters=model.parameters()),
    loss=SigmoidBCELoss()
)
model.fit(
    train_dataset, 
    valid_dataset,
    batch_size=batch_size,
    epochs=num_epochs, 
    eval_freq=1,
    shuffle=True,
    save_dir=save_model_dir,
    callbacks=[loss_print, vdl_record]
)

全局向量的词嵌入(GloVe)

GloVe主要在原来loss函数中引入了两点特性:

  • 引入全局共现权重,x_{u,c}表示词u和词c共现的次数,此时全局的损失函数可以表示为:

-\sum^N_u \sum^N_v x_{u,v} log(P(w_u|w_c))

  • 重新定义条件概率P(w_u|w_c)的计算,条件概率实际表示为x_{u,c}/x_c,假设P(w_u|w_c)\approx \alpha e^{(Ew_u*Ew_c)},此时学习目标为:

\alpha e^{(Ew_u*Ew_c)}-x_{u,c}/x_c=0\Rightarrow Ew_u*Ew_c+log(\alpha)-log(x_{u,c}) + log(x_c)=0

  • 此时GloVe的损失函数定义为:

\sum^N_u \sum^N_v h(x_{u,v})(Ew_u*Ew_v+a_u + b_v - log(x_{u,v}))^2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
word2vec是一种经典的词嵌入算法,最早由Google的Mikolov提出。它通过将词转化为向量的形式,实现了对词之间关系的定量度量和联系挖掘。word2vec算法有两种基本模型,分别是CBOW和Skip-Gram模型。CBOW模型通过上下文词预测目标词,而Skip-Gram模型则是通过目标词预测上下文词。这两种模型都是基于神经网络的训练方法,通过训练大量的语料库来学习词向量表示。这些词向量可以捕捉到词之间的语义和语法关系,从而可以应用于其他自然语言处理任务中。如果你对word2vec算法的原理感兴趣,可以参考\[1\]中的论文和\[2\]中的基础知识介绍。 #### 引用[.reference_title] - *1* *3* [深度学习方法(十七):word2vec算法原理(1):跳字模型(skip-gram) 和连续词袋模型(CBOW)](https://blog.csdn.net/xbinworld/article/details/90416529)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [学习:word2vec原理](https://blog.csdn.net/czp_374/article/details/86752999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值