《自然语言处理学习之路》02 词向量模型Word2Vec,CBOW,Skip Gram

本文主要是学习参考莫烦老师的教学,对老师课程的学习,记忆笔记。
原文链接

书山有路勤为径,学海无涯苦作舟。

在这里插入图片描述

零、吃水不忘挖井人

请支持老师的原文
原文链接

一、计算机如何实现对于词语的理解

1.1 万物数字化

计算机只能够处理由数字组成的数据,图片就可以转化为数字。

计算机识别图片,就是在图片的数值中找到规律。
在这里插入图片描述
计算机理解语言也是一样,也需要将文字转换为数字,在数字之中找到规律。

计算机之所以可以明白语言的意思,是因为其将词云归类到一个正确的位置上。计算机对于词语的理解,就是计算机对于空间及其位置的理解。
在这里插入图片描述

1.2 距离的计算

当所有的东西都可以被数字化的时候,在可以计算的空间中 可以将用每一个点代表某一个事物,就可以实现每个点之间距离的计算。

离得越近,就可以说明他们越相似。聚类与分类
在这里插入图片描述
无论是声音,图片,视频,只要能被数值化,投射到同一个高纬度的空间,就可以计算这些点之间的距离,实现聚类与分类。

测距的方法:

  • 1)直接测距(空间中的点与点的直线距离)
  • 2)方向测距(不在意直线距离或者强度大小,只要在一个方向上就行)

在这里插入图片描述

角度测距与直线测距差别:

在词向量训练的过程中,相似词总会被聚集到一块地方且方向大概都相同。
所以如果只想测量两个词的相似度,角度信息也足够了。
但点与点的距离还透露了更多的信息,只要两个词总在一起出现,他们之间的关联性应该越强,距离应该也越近。
如果一个词不仅出现的频率高,而且任何句子中都能出现,比如“在”,“你”,“吗”这一类的词,为了得到这些词的位置,机器需要不断计算他们之间的相关性。
这些词每次训练的时候都想被拉扯到独立的空间,但是被太多不同方向的词拉来拉去,比如”在”这个字,训练“在这”的时候“在”字被拉扯到靠近“这”字的方向。
训练“在家”的时候,“在”字将会更靠近“家”字,后面的训练也一样,所以“在”字因为频率太高,和很多字都能混搭,它就算是之中机器认为的“中性词”/
越有区分力的词可能越远离中心地带,因为他们和其他词都不像,而越通用,在每种场景都有的词,就可能越靠近原点。这时,点与点的距离就能告诉我们词的频率性特征。

1.3 训练词向量

训练词向量可以不需要像监督学习那样给数据打上标签。只要有各种各样的文章数据,可以直接在原始语料上做无监督学习。

  • CBOW方法:
    训练时,取一小段文本,取出这些词的向量表示,比如取出除了“一”字以外的词向量,然后整合到一起,表示这些文字的整体向量,用这个整体向量预测最中间那个“一”。接下来在开始下一段文字的训练。有点像我们经常在做的完形填空问题。
    在这里插入图片描述
    在这里插入图片描述
    将这个窗口挪动一格,用前后文预测“段”字,接着将窗口依次这样扫过所有文字,用所有的前后文预测中间词。

这样计算机就能将前后文的关系搞清楚,挨得近的词他们的关系越亲密总出现在类似的上下文中间的词关系越亲密。向量在一定程度上也越相近。

  • Skip Gram方法
    在这里插入图片描述用中间词预测前后文也行。

这两种方法的假设:在某个词的周围,应该都是和这个词有关系的词,所以当我们预测关联词的时候也就会拉近这些关联词的距离,把相近的词聚集到一起,从而得到所有的词向量。

1.4 词向量的用法

可以直接把词向量当成词语特征输入进另一个模型里。
在这里插入图片描述

这样就能用更丰富的词向量信息来表示一个词语ID。在这种情况中,我们说词向量是一种预训练特征。用word2vec 的方法预先训练好了词语的特征表达,然后在其他场景中拿着预训练结果直接使用。

1.5 词向量的加减运算

在这里插入图片描述
男人减掉女人的词向量,差不多就约等于公猫减掉母猫的词向量。

二、训练词向量模型(Continuous Bag of Words)

2.1 基于深度学习的词语CBOW向量化

在这里插入图片描述
这样的模型的输入:

# 1
# 输入:[我,爱] + [烦,Python]
# 输出:莫

# 2
# 输入:[爱,莫] + [Python, ,]
# 输出:烦

# 3
# 输入:[莫,烦] + [,,莫]
# 输出:Python

# 4
# 输入:[烦,Python] + [莫,烦]
# 输出:,

通过在大数据量的短语或文章中学习这样的词语关系,这个模型就能理解要预测的词和前后文的关系。

2.2 tensorflow神经网络实现案例

语料数据


corpus = [
    # numbers
    "5 2 4 8 6 2 3 6 4",
    "4 8 5 6 9 5 5 6",
    "1 1 5 2 3 3 8",
    "3 6 9 6 8 7 4 6 3",
    "8 9 9 6 1 4 3 4",
    "1 0 2 0 2 1 3 3 3 3 3",
    "9 3 3 0 1 4 7 8",
    "9 9 8 5 6 7 1 2 3 0 1 0",

    # alphabets, expecting that 9 is close to letters
    "a t g q e h 9 u f",
    "e q y u o i p s",
    "q o 9 p l k j o k k o p",
    "h g y i u t t a e q",
    "i k d q r e 9 e a d",
    "o p d g 9 s a f g a",
    "i u y g h k l a s w",
    "o l u y a o g f s",
    "o p i u y g d a s j d l",
    "u k i l o 9 l j s",
    "y g i s h k j l f r f",
    "i o h n 9 9 d 9 f a 9",
]

下就是CBOW中的词向量组件,最为核心是 self.embeddings,词向量就存在于这里里面。

from tensorflow import keras
import tensorflow as tf

class CBOW(keras.Model):
    def __init__(self, v_dim, emb_dim):
        super().__init__()
        self.embeddings = keras.layers.Embedding(
            input_dim=v_dim, output_dim=emb_dim,  # [n_vocab, emb_dim]
            embeddings_initializer=keras.initializers.RandomNormal(0., 0.1),
        )
	def call(self, x, training=None, mask=None):
        # x.shape = [n, skip_window*2]
        o = self.embeddings(x)          # [n, skip_window*2, emb_dim]
        o = tf.reduce_mean(o, axis=1)   # [n, emb_dim]
        return o

在求loss的时候使用nce_loss能够大大加速softmax求loss的方式,它不关心所有词汇loss, 而是抽样选取几个词汇用来传递loss,因为如果考虑所有词汇,那么当词汇量大的时候,会很慢。

class CBOW(keras.Model):
    def __init__(self, v_dim, emb_dim):
        ...
        # noise-contrastive estimation
        self.nce_w = self.add_weight(
            name="nce_w", shape=[v_dim, emb_dim],
            initializer=keras.initializers.TruncatedNormal(0., 0.1))  # [n_vocab, emb_dim]
        self.nce_b = self.add_weight(
            name="nce_b", shape=(v_dim,),
            initializer=keras.initializers.Constant(0.1))  # [n_vocab, ]

        self.opt = keras.optimizers.Adam(0.01)

    # negative sampling: take one positive label and num_sampled negative labels to compute the loss
    # in order to reduce the computation of full softmax
    def loss(self, x, y, training=None):
        embedded = self.call(x, training)
        return tf.reduce_mean(
            tf.nn.nce_loss(
                weights=self.nce_w, biases=self.nce_b, labels=tf.expand_dims(y, axis=1),
                inputs=embedded, num_sampled=5, num_classes=self.v_dim))

    def step(self, x, y):
        with tf.GradientTape() as tape:
            loss = self.loss(x, y, True)
            grads = tape.gradient(loss, self.trainable_variables)
        self.opt.apply_gradients(zip(grads, self.trainable_variables))
        return loss.numpy()

开始训练:

from utils import process_w2v_data

def train(model, data):
    for t in range(2500):
        bx, by = data.sample(8)
        loss = model.step(bx, by)
        if t % 200 == 0:
            print("step: {} | loss: {}".format(t, loss))


if __name__ == "__main__":
    d = process_w2v_data(corpus, skip_window=2, method="cbow")
    m = CBOW(d.num_word, 2)
    train(m, d)

结果可视化:
在这里插入图片描述

三、训练词向量模型(Skip - Gram)

3.1 基于深度学习的词语Skip - Gram向量化

利用中间词的信息,预测左右词语的信息。
在这里插入图片描述
Skip-Gram 相比 CBOW 最大的不同,就是剔除掉了中间的那个 SUM 求和的过程。

3.2 tensorflow神经网络实现案例

语料:


corpus = [
    # numbers
    "5 2 4 8 6 2 3 6 4",
    "4 8 5 6 9 5 5 6",
    "1 1 5 2 3 3 8",
    "3 6 9 6 8 7 4 6 3",
    "8 9 9 6 1 4 3 4",
    "1 0 2 0 2 1 3 3 3 3 3",
    "9 3 3 0 1 4 7 8",
    "9 9 8 5 6 7 1 2 3 0 1 0",

    # alphabets, expecting that 9 is close to letters
    "a t g q e h 9 u f",
    "e q y u o i p s",
    "q o 9 p l k j o k k o p",
    "h g y i u t t a e q",
    "i k d q r e 9 e a d",
    "o p d g 9 s a f g a",
    "i u y g h k l a s w",
    "o l u y a o g f s",
    "o p i u y g d a s j d l",
    "u k i l o 9 l j s",
    "y g i s h k j l f r f",
    "i o h n 9 9 d 9 f a 9",
]

核心组件和CBOW一样:

from tensorflow import keras
import tensorflow as tf

class SkipGram(keras.Model):
    def __init__(self, v_dim, emb_dim):
        super().__init__()
        self.embeddings = keras.layers.Embedding(
            input_dim=v_dim, output_dim=emb_dim,  # [n_vocab, emb_dim]
            embeddings_initializer=keras.initializers.RandomNormal(0., 0.1),
        )
        ...

Skip-Gram 的前向比CBOW的前向更简单,只取embedding的过程。

class SkipGram(keras.Model):
    ...
    def call(self, x, training=None, mask=None):
        # x.shape = [n, ]
        o = self.embeddings(x)      # [n, emb_dim]
        return o

在计算loss时,为了避免词汇量大,带来的softmax计算复杂度高的问题,像CBOW一样,使用了NCE技术,在计算loss和反向传播的时候只考虑部分的负样本, 节约计算量。

class SkipGram(keras.Model):
    def __init__(self, v_dim, emb_dim):
        ...
        # noise-contrastive estimation
        self.nce_w = self.add_weight(
            name="nce_w", shape=[v_dim, emb_dim],
            initializer=keras.initializers.TruncatedNormal(0., 0.1))  # [n_vocab, emb_dim]
        self.nce_b = self.add_weight(
            name="nce_b", shape=(v_dim,),
            initializer=keras.initializers.Constant(0.1))  # [n_vocab, ]

        self.opt = keras.optimizers.Adam(0.01)

    # negative sampling: take one positive label and num_sampled negative labels to compute the loss
    # in order to reduce the computation of full softmax
    def loss(self, x, y, training=None):
        embedded = self.call(x, training)
        return tf.reduce_mean(
            tf.nn.nce_loss(
                weights=self.nce_w, biases=self.nce_b, labels=tf.expand_dims(y, axis=1),
                inputs=embedded, num_sampled=5, num_classes=self.v_dim))

    def step(self, x, y):
        with tf.GradientTape() as tape:
            loss = self.loss(x, y, True)
            grads = tape.gradient(loss, self.trainable_variables)
        self.opt.apply_gradients(zip(grads, self.trainable_variables))
        return loss.numpy()

用同一个词预测多个词:

# 原本应该是这样:
# 输入:莫
# 输出:[我,爱] + [烦,Python]

# 对同一批训练数据,你也可以这样
# 输入:莫 -> 输出:我
# 输入:莫 -> 输出:爱
# 输入:莫 -> 输出:烦
# 输入:莫 -> 输出:Python

训练的过程也和CBOW一样的,只是process_w2v_data()这里的参数需要变化一下,我们要拿的是给Skip-Gram定制的输入输出。

from utils import process_w2v_data

def train(model, data):
    for t in range(2500):
        bx, by = data.sample(8)
        loss = model.step(bx, by)
        if t % 200 == 0:
            print("step: {} | loss: {}".format(t, loss))


if __name__ == "__main__":
    d = process_w2v_data(corpus, skip_window=2, method="skip_gram")
    m = SkipGram(d.num_word, 2)
    train(m, d)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驭风少年君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值