本文原作者:岳夕涵,经授权后发布。
背景
用户搜索的关键词是对其兴趣的一个很重要的反映。然而我们发现,当用户搜索“阿丽塔”、“猫爪杯”等新词后,之后的推荐中却始终没有相关的文章出现。
这些新词在新入库文章中出现之后很快就会加入到tag表中。然而我们并没有直接将tag作为特征(之前实验过将tag作为特征,会出现严重的过拟合),而是把tag转换成词向量。由于旧的词向量并不包含这些新词,因此新词的相关文章就很难通过模型打分出现在推荐结果中。一种解决方式是训练并使用新的词向量,然而由于新旧词向量并不在同一向量空间中,更新到新版本的词向量需要重算文章向量、积累训练数据、重新训练模型等步骤,切换周期较长。
于是我们采用固定旧词词向量,训练练新词词向量,使词向量的向量空间不发生转移。
方案
一种方式是通过tensorflow实现,而我们选择修改word2vec的c源码,原因在于:训练速度快、不需要重新实现训练数据预处理。
新词一出现就计算得到的词向量可能不准确,但是如果不计算又推不出相关文章。权衡后,决定当词语出现次数超过fix_count(默认1000)之后才固定,低于该值则在词向量更新的时候重新计算。
还有一些词(如人名、机构名),只是短期出现,于是设定当出现次数超过output_count(默认100)才会输出其词向量到文件中,同时只要出现次数超过min_count(默认5)就会作为独立词出现在训练过程中。
实现
1、取消对hierarchical softmax和聚类的支持
由于负采样更加简单且效果不错,方便考虑,去掉hs相关代码,强制采用负采样。同样出于方便也删掉了对聚类的支持。
2、生成词表
老逻辑是如果有词表文件就读入该文件,否则读语料生成词表。增量更新的时候需要上一个版本的词表文件用来计算哪些词的向量要固定,所以强制要求保存词表。
读好词表后,按照词频从大到小排序,找到词频小于fix_count的词的起始位置(unfixed_index),词向量更新就从unfixed_index之后开始更新。
然后将词频清0,从训练语料中读入词。经过这一步,新词也会被写入词表中。
如果没有提供老版本词向量,则按word2vec的老逻辑处理。
另外由于LearnVocabFromTrainFile会在ReadVocab之后运行了,于是添加代码防止其抹掉已建好的词表。
SortVocab和ReduceVocab不能改变和删除词表中unfixed_index之前的词。
3、初始化词向量
word2vec接下来就是在InitNet分配和随机初始化词向量了,需要在这个函数的末尾读入老版词向量并使用这些值初始化unfixed_index之前的词。
4、词向量增量更新
代码中syn0是最终输出的词向量,syn1neg是word2vec负采样中使用的辅助词向量。词向量更新过程是一个求梯度下降的过程,目标函数是最小化二次损失函数(y-p)^2,样本为正时y是1,为负时y是0,p是context词的词向量(对cbow是平均后的向量)与target词的辅助向量做内积再取sigmoid之后的值。
这里不用关心求梯度的过程,只用在最后一步加入若小于unfixed_index就不更新否者更新的逻辑。