图学习参考资料 词向量word2vec( 六 )

3.2网络定义定义skip-gram的网络结构,用于模型训练 。在飞桨动态图中,对于任意网络,都需要定义一个继承自fluid.dygraph.Layer的类来搭建网络结构、参数等数据的声明 。同时需要在forward函数中定义网络的计算逻辑 。值得注意的是,我们仅需要定义网络的前向计算逻辑,飞桨会自动完成神经网络的后向计算,代码如下:
#这里我们使用的是paddlepaddle的1.8.0版本#一般来说,在使用fluid训练的时候,我们需要通过一个类来定义网络结构,这个类继承了fluid.dygraph.Layerclass SkipGram(fluid.dygraph.Layer):def __init__(self, vocab_size, embedding_size, init_scale=0.1):#vocab_size定义了这个skipgram这个模型的词表大小#embedding_size定义了词向量的维度是多少#init_scale定义了词向量初始化的范围,一般来说,比较小的初始化范围有助于模型训练super(SkipGram, self).__init__()self.vocab_size = vocab_sizeself.embedding_size = embedding_size#使用paddle.fluid.dygraph提供的Embedding函数 , 构造一个词向量参数#这个参数的大小为:[self.vocab_size, self.embedding_size]#数据类型为:float32#这个参数的名称为:embedding_para#这个参数的初始化方式为在[-init_scale, init_scale]区间进行均匀采样self.embedding = Embedding(size=[self.vocab_size, self.embedding_size],dtype='float32',param_attr=fluid.ParamAttr(name='embedding_para',initializer=fluid.initializer.UniformInitializer(low=-0.5/embedding_size, high=0.5/embedding_size)))#使用paddle.fluid.dygraph提供的Embedding函数,构造另外一个词向量参数#这个参数的大小为:[self.vocab_size, self.embedding_size]#数据类型为:float32#这个参数的名称为:embedding_para_out#这个参数的初始化方式为在[-init_scale, init_scale]区间进行均匀采样#跟上面不同的是,这个参数的名称跟上面不同,因此,#embedding_para_out和embedding_para虽然有相同的shape , 但是权重不共享self.embedding_out = Embedding(size=[self.vocab_size, self.embedding_size],dtype='float32',param_attr=fluid.ParamAttr(name='embedding_out_para',initializer=fluid.initializer.UniformInitializer(low=-0.5/embedding_size, high=0.5/embedding_size)))#定义网络的前向计算逻辑#center_words是一个tensor(mini-batch),表示中心词#target_words是一个tensor(mini-batch) , 表示目标词#label是一个tensor(mini-batch),表示这个词是正样本还是负样本(用0或1表示)#用于在训练中计算这个tensor中对应词的同义词 , 用于观察模型的训练效果def forward(self, center_words, target_words, label):#首先,通过embedding_para(self.embedding)参数,将mini-batch中的词转换为词向量#这里center_words和eval_words_emb查询的是一个相同的参数#而target_words_emb查询的是另一个参数center_words_emb = self.embedding(center_words)target_words_emb = self.embedding_out(target_words)#center_words_emb = [batch_size, embedding_size]#target_words_emb = [batch_size, embedding_size]#我们通过点乘的方式计算中心词到目标词的输出概率,并通过sigmoid函数估计这个词是正样本还是负样本的概率 。word_sim = fluid.layers.elementwise_mul(center_words_emb, target_words_emb)word_sim = fluid.layers.reduce_sum(word_sim, dim = -1)word_sim = fluid.layers.reshape(word_sim, shape=[-1])pred = fluid.layers.sigmoid(word_sim)#通过估计的输出概率定义损失函数,注意我们使用的是sigmoid_cross_entropy_with_logits函数#将sigmoid计算和cross entropy合并成一步计算可以更好的优化,所以输入的是word_sim,而不是predloss = fluid.layers.sigmoid_cross_entropy_with_logits(word_sim, label)loss = fluid.layers.reduce_mean(loss)#返回前向计算的结果,飞桨会通过backward函数自动计算出反向结果 。return pred, loss3.3网络训练完成网络定义后 , 就可以启动模型训练 。我们定义每隔100步打印一次Loss,以确保当前的网络是正常收敛的 。同时,我们每隔10000步观察一下skip-gram计算出来的同义词(使用 embedding的乘积),可视化网络训练效果,代码如下:
batch_size = 512epoch_num = 3embedding_size = 200step = 0learning_rate = 0.001#定义一个使用word-embedding查询同义词的函数#这个函数query_token是要查询的词,k表示要返回多少个最相似的词,embed是我们学习到的word-embedding参数#我们通过计算不同词之间的cosine距离 , 来衡量词和词的相似度#具体实现如下 , x代表要查询词的Embedding,Embedding参数矩阵W代表所有词的Embedding#两者计算Cos得出所有词对查询词的相似度得分向量,排序取top_k放入indices列表def get_similar_tokens(query_token, k, embed):W = embed.numpy()x = W[word2id_dict[query_token]]cos = np.dot(W, x) / np.sqrt(np.sum(W * W, axis=1) * np.sum(x * x) + 1e-9)flat = cos.flatten()indices = np.argpartition(flat, -k)[-k:]indices = indices[np.argsort(-flat[indices])]for i in indices:print('for word %s, the similar word is %s' % (query_token, str(id2word_dict[i])))#将模型放到GPU上训练(fluid.CUDAPlace(0)),如果需要指定CPU,则需要改为fluid.CPUPlace()with fluid.dygraph.guard(fluid.CUDAPlace(0)):#通过我们定义的SkipGram类,来构造一个Skip-gram模型网络skip_gram_model = SkipGram(vocab_size, embedding_size)#构造训练这个网络的优化器adam = fluid.optimizer.AdamOptimizer(learning_rate=learning_rate, parameter_list = skip_gram_model.parameters())#使用build_batch函数,以mini-batch为单位,遍历训练数据,并训练网络for center_words, target_words, label in build_batch(dataset, batch_size, epoch_num):#使用fluid.dygraph.to_variable函数,将一个numpy的tensor , 转换为飞桨可计算的tensorcenter_words_var = fluid.dygraph.to_variable(center_words)target_words_var = fluid.dygraph.to_variable(target_words)label_var = fluid.dygraph.to_variable(label)#将转换后的tensor送入飞桨中,进行一次前向计算,并得到计算结果pred, loss = skip_gram_model(center_words_var, target_words_var, label_var)#通过backward函数,让程序自动完成反向计算loss.backward()#通过minimize函数,让程序根据loss,完成一步对参数的优化更新adam.minimize(loss)#使用clear_gradients函数清空模型中的梯度,以便于下一个mini-batch进行更新skip_gram_model.clear_gradients()#每经过100个mini-batch,打印一次当前的loss,看看loss是否在稳定下降step += 1if step % 100 == 0:print("step %d, loss %.3f" % (step, loss.numpy()[0]))#经过10000个mini-batch,打印一次模型对eval_words中的10个词计算的同义词#这里我们使用词和词之间的向量点积作为衡量相似度的方法#我们只打印了5个最相似的词if step % 10000 == 0:get_similar_tokens('one', 5, skip_gram_model.embedding.weight)get_similar_tokens('she', 5, skip_gram_model.embedding.weight)get_similar_tokens('chip', 5, skip_gram_model.embedding.weight)

推荐阅读