word2Vec理解

一、Efficient Estimation of Word Representations in Vector Space

论文中提到了skip-gram 和 CBOW,这里重点理解skip-gram

1.1原始Skip-gram

python 复制代码
import torch
import torch.nn as nn
class SkipGram(nn.Module):
    def __init__(self, vocab_size, embed_dim):
        super().__init__()
        # Projection layer
        self.embedding = nn.Embedding(
            vocab_size,
            embed_dim
        )
        # Output layer
        self.output = nn.Linear(
            embed_dim,
            vocab_size
        )
    def forward(self, center_word):
        h = self.embedding(center_word)
        logits = self.output(h)
        return logits

训练skip-gram

I love deep learning, windows = 2,即中心词前一个词,和后一个词

center=love, love, deep, deep

target = I , deep, love, learning

训练代码

python 复制代码
model = SkipGram(vocab_size, 300)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(
    model.parameters(),
    lr=1e-3
)
for center, target in loader:
    logits = model(center)
    loss = criterion(
        logits,
        target
    )
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

logits = model(center) 中的logits 是 词汇表中每个词出现的概率

target 是 词汇表中,第几个词

如果窗口变成5, 例如 0:I 1:love 2:deep 3:learning 4:and 5:neural 6:networks 7:very 8:much

以 neural 为中心词,windows = 5 , 可以生成10个样本

(neural , I) (neural , love ) (neural , deep ) (neural , learning) (neural , and )

(neural , networks ) (neural , very ) (neural , much)

如果是 CBOW模式,只能生成1个样本

([w0 w1 w2 w3 w4,)

w6 w7 w8 w9 w10],

w5)

  • 在word2Vec 中,windows=5指的是,左右各5个
  • 根据上面的例子,可以看到CBOW的基本思想是,根据上下文,预测中心词,所以训练样本是中心词的左右各5个词,目标是中心词;Skip-Gram 是根据中心词,预测周围词,所以输入样本是这样的,中性词,左边第3个中性词,左边第2个中性词,左边第1个中性词,右边第1个中性词,右边第2个中性词,右边第3个

二、 Distributed Representations of Words and Phrasesand their Compositionality

观察上面的网络模型发现,例如词向量的维度300,如果词汇表是10w,那么网络每次都输出10w x 300,然后softmax(10w个词),计算量很大,从我们的认知来说,其实每次预测,根本就不用将词汇表里面的词汇都预测一遍,词与之间是有联系的,真正需要更新的其实只是一个正样本词和少量负样本词。

2.1 Negative Sampling

正常情况下,skip-gram 中的 I love deep learning

中心词:love 上下文: I deep,训练对input -> target: love -> I love -> deep

预测这两个,需要在整个词表中做softmax

在 Negative Sampling 思想中,

正样本: (love, I)

负样本:(love, apple)(love, banana) (love, car) (love, tiger)(love, phone)

变成了二分类:是不是上下文词?

原来的损失函数:

−logP(wo∣wI)-logP(w_o|w_I)−logP(wo∣wI)

变成了:

logσ(−vOTvI)+∑i=1klogσ(−vNiTvI)log\sigma(-v_O^Tv_I)+\sum_{i=1}^klog\sigma(-v_{N_i}^Tv_I)logσ(−vOTvI)+i=1∑klogσ(−vNiTvI)

其中:正样本1个,负样本k个,通常k=5

SkipGramNS

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F


class SkipGramNS(nn.Module):

    def __init__(self, vocab_size, embed_dim):
        super().__init__()
        # syn0
        self.in_embed = nn.Embedding(
            vocab_size,
            embed_dim
        )
        # syn1neg
        self.out_embed = nn.Embedding(
            vocab_size,
            embed_dim
        )

    def forward(
        self,
        center_words,
        pos_words,
        neg_words
    ):
        """
        center_words : [B]
        pos_words : [B]
        neg_words : [B,K]
        """
        center = self.in_embed(
            center_words
        )
        pos = self.out_embed(
            pos_words
        )

        # 正样本
        pos_score = torch.sum(
            center * pos,
            dim=1
        )
        pos_loss = F.logsigmoid(
            pos_score
        )

        # 负样本
        neg = self.out_embed(
            neg_words
        )
        neg_score = torch.bmm(
            neg,
            center.unsqueeze(2)
        ).squeeze(2)
        neg_loss = F.logsigmoid(
            -neg_score
        ).sum(1)
        loss = -(pos_loss + neg_loss)
        return loss.mean()

2.2、SkipGramNS我的理解

  • 之前的SkipGram的输入是中心词,以及中心词的一个窗口内的上下文词,SkipGramNS需要再输入k=5该中心词的随机采样词,例如一下:
    center = love
    positive = I
    negative = apple, car, banana, phone, tiger
  • 有两个词向量nn.Embedding,中心词独享一个,正样本和负样本共享一个nn.Embedding;
  • 正样本分数,是正样本词向量和中心词向量的向量内积;
  • 负样本分数,是负样本词向量和中心词向量的向量内积;
  • 模型需要拉进正样本和中心词之间的距离,拉大负样本和中心词的距离,因此模型的loss为loss=−logσ(spos)−∑(logσ(−sneg))loss = -log\sigma(s_{pos})-\sum(log\sigma(-s_{neg}))loss=−logσ(spos)−∑(logσ(−sneg))
    从而推动正样本内积越来越大、负样本内积越来越小,使中心词靠近真实上下文词,远离随机采样词。

2.3、SkipGramNS我的思考

2.3.1 、为什么中心词一个nn.Embedding,正样本和负样本一个,为什么不能三个输入共享一个;

理论上正样本、负样本和中心词,使用一个nn.Embedding也可以,这里作者之所以使用两个,是认为中心词和正负样本在整个优化过程中扮演不同的角色,如果两种角色是相互矛盾的,那么放到一起学习,学到的向量必然是不好的,分开学习,可以让两种角色的特征都学的很好;如果两种角色是相互依赖,相互促进的,那么三个共享一个nn.Embedding效果最好。有时候用几层卷积,几个分支,很多时候是根据设计者对当前任务的理解。

2.3.2、原始的SkipGram的优化目标是,为什么可以由预测词汇表中所有词的概率,求最大的那个,如此丝滑的将优化目标变为,正样本与中心词相似度-负样本与中心词相似度呢?

这里有很多思考,可以看,初版的skip-gram 最后一层的思想:所有词汇表中,我的目标词汇的概率最大,其实就是分类的思想,但是这里词汇表(类别)是如此之大,导致计算效率特别低,

1、导致计算量大的是词汇表的个数,为什么会用到词汇表的个数呢,因为最后一层使用softmax让目标词汇的概率最大,从而逼迫网络学习的词汇向量逼近目标词汇,远离非目标词汇;

2、经分析,softmax的目的其实是学习词向量,并不是真的去预测是哪个词汇,那么是不是可以跳过softmax的方法,于是NS顺势提取,就是不绕弯子了,之间在向量层上计算损失,就是输入正样本的向量与中心向量距离近,负样本向量与中心向量距离远;

3、对,这个思想和人脸中的Triplet Loss(三元组损失)是一致的,通过Triplet Loss训练后的人脸图片,可以生成人脸特征向量,用来做比对。

2.3.3 word2vec 和 lstm 、transformer的区别

1、skip-gram,的目标函数是

P(wo∣wc)P(w_o|w_c)P(wo∣wc)

例如I love deep learning,窗口=2,训练样本(love → I),(love → deep), (love → learning),所以,skip-gram只关系哪些词经常一起出现,但是它不关心语法,顺序,和长距离依赖,本质上skip-gram学的是词共现统计,而不是语言生成,结构如下:

bash 复制代码
 中心词id
   ↓
Embedding
   ↓
词向量 vc
   ↓
与目标词向量点积
   ↓
sigmoid
   ↓
loss

2、LSTM是语言模型:

P(wt∣w1,...,wt−1)P(w_t|w_1,...,w_{t-1})P(wt∣w1,...,wt−1)

例如 I love deep ? 预测:learning,在lstm 中,embedding 只是输入特征,核心是隐藏层ttt_ttt携带的上下文信息,结构如下:

bash 复制代码
Embedding
    ↓
LSTM
    ↓
Hidden State
    ↓
Linear
    ↓
Softmax
    ↓
整个词表

3、Transformer也是语言模型:

P(wt∣w1,...,wt−1)P(w_t|w_1,...,w_{t-1})P(wt∣w1,...,wt−1)

但是上下文建模换成了self-Attention:

H=Attention(Q,K,V)H = Attention(Q,K,V)H=Attention(Q,K,V),所以transformer学到的特征更强

2.3.4 lstm 、transformer都有自己的nn.Embedding, word2vec的作用是什么

1、 在word2vec出现之前,词用的ID来表示,例如cat -> 1001,dog -> 2003, apple -> 501,模型不知道cat 和 dog比较接近,cat 和 apple 不接近;

2、 word2vec出来后,cat , dog, apple 使用向量表示之后,这些向量可以表示语义,所以很多模型直接拿word2vec的结果来用,例如后面之间接LSTM,经常会看到:

python 复制代码
embedding = nn.Embedding.from_pretrained(
    word2vec_weight
)

3、后来,BERT 和 GPT 出现后,发现模型完全可以自己学Embedding,不需要先训练word2vec,再训练模型,直接端到端训练。现在大模型训练流程:

bash 复制代码
文本
 ↓
Tokenizer
 ↓
随机初始化Embedding
 ↓
Transformer
 ↓
预测下一个Token

4、之前说过,word2vec提取的是静态词之前的关系,后面会看到BERT Embedding GPT Embedding 等不是独立模型,它们是Transformer训练过程中产生的表示。