深度学习——NLP-2.理解词语

深度学习——NLP-2.理解词语,第1张

2.1 词向量是什么

语言用数字表达

距离:1.点与点之间的距离,长度越短越相似。

并不在乎距离的长短就是强度的大小时,采用2.两个之间的夹角。

同一种类别的东西,总是聚集在某一块地方,距离就会很近。

某些词经常出现,比如“在”“你”“吗”这些,被称为“中性词”,越有区分力的词可能越远离中心地带。点与点的距离可以告诉我们词的频率属性。

在训练时,不需要人工打标签,只需要在原始语料上做训练,取一小段文本,取出这些词的向量表示。比如原文本“这是一段训练”,取出除了一以外的向量,然后整合成整体向量,去预测中间的“一”。然后再挪动选择的窗口,像上面一样,去预测下一个中间的文字。这样计算机就能搞清前后文的关系。

可以用前后文预测中间词,也可以用中间词预测前后文。

2.2 Continuous Bag of Words (CBOW) 2.2.1 什么是CBOW

深度学习中,将词语向量化。

CBOW是什么个东西呢?用一句话概述:挑一个要预测的词,来学习这个词前后文中词语和预测词的关系。

举个例子吧,有这样一句话。

我爱莫烦Python,莫烦Python通俗易懂。

模型在做的事情如图中所示,将这句话拆成输入和输出,用前后文的词向量来预测句中的某个词。

那么这个模型的输入输出可以是:

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

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

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

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

通过在大数据量的短语或文章中学习这样的词语关系,这个模型就能理解要预测的词前后文的关系。而图中彩色的词向量就是这种训练过程的一个副产品。

2.2.2 词向量的应用

词向量的几种典型应用:

  • 把这些对词语理解的向量通过特定方法组合起来,就可以有对某句话的理解了;
  • 可以在向量空间中找寻同义词,因为同义词表达的意思相近,往往在空间中距离也非常近;
  • 词语的距离换算。

词语距离计算这个比较有意思,比如可以拿词语做加减法。公猫 - 母猫 就约等于 男人 - 女人 如果我哪天想知道 莫烦Python 的友商有哪些,我可以做一下这样的计算

友商 = 莫烦Python - (腾讯 - 阿里)

2.2.3 CBOW代码实现

为了做一个有区分力的词向量,我做了一些假数据,想让计算机学会这些假词向量的正确向量空间。 

将训练的句子人工分为两派(数字派,字母派),虽然都是文本,但是期望模型能自动区分出在空间上,数字和字母是有差别的。因为数字总是和数字一同出现, 而字母总是和字母一同出现。并且我还在字母中安排了一个数字卧底,这个卧底的任务就是把字母那边的情报向数字通风报信。 所以期望的样子就是数字 9 不但靠近数字,而且也靠近字母。

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",
]
这就是最终的实验结果。可以看到内奸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),
        )
        ...

接下来就是模型的预测是如何进行的了,我们用classcall功能定义模型的前向预测部分。说白了,其实也就是把预测时的embedding词向量给拿出来, 然后求一个词向量平均,这样输出就够了。在用这个平均的向量预测一下目标值。

class CBOW(keras.Model):
    ...
    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)

值得注意的是,在数据处理方面,我没在代码中特别强调,但是可以看出,process_w2v_data() 这个功能需要确定我们的skip_window这个参数, 这个参数的作用就是,在我们要预测的词周围,我们要选取多少个词作为他的输入。如果skip_window=1则意味着我们选取这个词前后各1个词作为网络的输入, 如果skip_window=2则意味着我们选取这个词前后各2个词,以此类推。具体代码可以看写在 utils.py 当中的代码。

最后如果将学到的embedding向量结果进行可视化,就有了我们之前在文章前面看到的那个样子,观看一下字母和数字的距离,我们也能知道CBOW学会了这些词之间的内在联系。

2.2.4 思考扩展

我们已经能成功的训练出这些词向量了,除了可视化展示他们,我们还能怎么使用这些向量呢?在这里我举一个例子。

句子是由词语组成的,那么有一种理解句子的方式,就是将这个句子中所有词语的词向量都加起来,然后就变成了句子的理解。 不过这种空间上的向量相加,从直观上理解,就不是特别成立,因为它加出来以后,还是在这个词汇空间中的某个点, 你说它是句向量吧,好像也不行,说它是一个词的理解吧,好像也不对。

所以更常用的方式是将这些训练好的词向量当做预训练模型,然后放入另一个神经网络(比如RNN)当成输入,使用另一个神经网络加工后,训练句向量。

2.3 Skip-Gram

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/922276.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存