Continuous Bag of Words (CBOW)
学习资料:
- 本节的全部代码
- 代码依赖的 utils.py 和 visual.py 在这里找到
- 我制作的 词向量 短片简介
- Continuous Bag-of-Word (CBOW) 论文
- Tensorflow 的词向量可视化网页
怎么了 ¶
之前我们在介绍搜索引擎的时候,提到过TF-IDF算法, 这种算法是通过一种统计学的方式来用文章中的词的重要程度,转化成向量来表示一篇文章的。 向量化思维在机器学习中也非常常见,我们可以认为,一张图片是一个向量,一篇文章是一个向量,一句话也可以是一个向量。 这样的向量化表示优点也很明显,就是能被计算机计算,是计算机能够理解的模式。
那么我们还能更加细化能被向量化的东西吗?比如说有没有词的向量呢?词的向量有怎么得到呢? 这里的短片简介将会清楚的阐述这样的过程。
什么是CBOW ¶
CBOW 是 Continuous Bag-of-Word 的简称,同篇论文中, 还有另外一个一起提出的,十分相似的模型,Skip-Gram, 我们会在下一节内容中继续阐述Skip-Gram. 那么这个CBOW是什么个东西呢?用一句话概述:挑一个要预测的词,来学习这个词前后文中词语和预测词的关系。
举个例子吧,有这样一句话。
我爱莫烦Python,莫烦Python通俗易懂。
模型在做的事情如图中所示,将这句话拆成输入和输出,用前后文的词向量来预测句中的某个词。
那么这个模型的输入输出可以是:
# 1
# 输入:[我,爱] + [烦,Python]
# 输出:莫
# 2
# 输入:[爱,莫] + [Python, ,]
# 输出:烦
# 3
# 输入:[莫,烦] + [,,莫]
# 输出:Python
# 4
# 输入:[烦,Python] + [莫,烦]
# 输出:,
通过在大数据量的短语或文章中学习这样的词语关系,这个模型就能理解要预测的词
和前后文
的关系。而图中彩色的词向量就是这种训练过程的一个副产品。
词向量的应用 ¶
词向量的几种典型应用:
- 把这些对词语理解的向量通过特定方法组合起来,就可以有对某句话的理解了;
- 可以在向量空间中找寻同义词,因为同义词表达的意思相近,往往在空间中距离也非常近;
- 词语的距离换算。
词语距离计算这个比较有意思,比如可以拿词语做加减法。公猫 - 母猫
就约等于 男人 - 女人
。
如果我哪天想知道 莫烦Python
的友商有哪些,我可以做一下这样的计算
友商 = 莫烦Python - (腾讯 - 阿里)
秀代码 ¶
为了做一个有区分力的词向量,我做了一些假数据,想让计算机学会这些假词向量的正确向量空间。 如果你习惯直接看所有源码,那么源码在这里。
我将训练的句子人工分为两派(数字派,字母派),虽然都是文本,但是期望模型能自动区分出在空间上,数字和字母是有差别的。因为数字总是和数字一同出现,
而字母总是和字母一同出现。并且我还在字母中安排了一个数字卧底,这个卧底的任务就是把字母那边的情报向数字通风报信。
所以期望的样子就是数字 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已经被暴露啦~
我们就开始写模型吧,这个模型还相对比较简单。tensorflow 2.0 版本之后,我比较推荐使用keras来编写模型。如果对这一套还不是很熟悉的朋友, 可以参考一下我写的一些简易 TF2.+ 的代码, 如果你会 TF1.+,或者 Pytorch,那么你可能只需要10分钟的入门时间。
下面就是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),
)
...
接下来就是模型的预测是如何进行的了,我们用class
的call
功能定义模型的前向预测部分。说白了,其实也就是把预测时的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学会了这些词之间的内在联系。
思考括展 ¶
我们已经能成功的训练出这些词向量了,除了可视化展示他们,我们还能怎么使用这些向量呢?在这里我举一个例子。
句子是由词语组成的,那么有一种理解句子的方式,就是将这个句子中所有词语的词向量都加起来,然后就变成了句子的理解。 不过这种空间上的向量相加,从直观上理解,就不是特别成立,因为它加出来以后,还是在这个词汇空间中的某个点, 你说它是句向量吧,好像也不行,说它是一个词的理解吧,好像也不对。
所以更常用的方式是将这些训练好的词向量当做预训练模型,然后放入另一个神经网络(比如RNN)当成输入,使用另一个神经网络加工后,训练句向量。
总结 ¶
词向量是词语的向量表示,我们的计算机最擅长使用这种数字化向量表示来计算和理解词语。所以词向量对于理解词语甚至是句子都有很强的适用性。
下一节我们来见识一下另一种词向量训练方法Skip-Gram,我个人更喜欢后者,
因为后者没有我觉得说不太通的词向量相加
过程。
降低知识传递的门槛
莫烦很常从互联网上学习知识,开源分享的人是我学习的榜样。 他们的行为也改变了我对教育的态度: 降低知识传递的门槛。 免费 奉献我的所学正是受这种态度的影响。 通过 【赞助莫烦】 能让我感到认同,我也更有理由坚持下去。
想当算法工程师拿高薪?转行AI无门道?莫烦也想祝你一臂之力,市面上机构繁杂, 经过莫烦的筛选,七月在线脱颖而出, 莫烦和他们合作,独家提供大额 【培训优惠券】, 让你更有机会接触丰富的教学资源、培训辅导体验, 祝你找/换工作/学习顺利~