Python3下如何使Word2Vec每次运行结果一致



最近在使用Word2Vec时发现一个奇怪的问题,那就是每次运行出来的结果不一致,这就是得程序复现带来了很多麻烦。多方查阅资料后终于解决了这个难题,下面说一下我的解决方案。

查阅Word2Vec的官方文档,在seed参数哪里可以发现这样的解释:

seed (int) – Seed for the random number generator. Initial vectors for each word are seeded with a hash of the concatenation of word + str(seed). Note that for a fully deterministically-reproducible run, you must also limit the model to a single worker thread (workers=1), to eliminate ordering jitter from OS thread scheduling. (In Python 3, reproducibility between interpreter launches also requires use of the PYTHONHASHSEED environment variable to control hash randomization)

也就是说在python2中想要使Word2Vec每次运行结果一致,只要设置seed并且将workers设为1就可以了,但是在python3中还要设置PYTHONHASHSEED的环境变量。

接下来就是设置PYTHONHASHSEED的环境变量。在设置环境变量时,在python程序内通过os.environ好像并不能起到作用,必须把PYTHONHASHSEED设置到环境变量中。

右击计算机(我的电脑)-》选择属性-》在弹出的面板中选择高级系统设置-》选择环境变量

在用户变量哪里新建一个用户变量,添加PYTHONHASHSEED,并随机设置一个整数值就可以了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u52KyFlN-1614331825491)(https://imgkr.cn-bj.ufileos.com/b6d461b7-3594-498e-854c-f84edb96a00a.png)]
然后确定退出,重新运行python程序,发现每次跑出结果一致,问题解决。

参数设定:

model=Doc2Vec(doc,seed=255,workers=1,size=10,epochs=5)

seed=255 #必须固定成一个整数
workers=1 #wordkers必须是1,否会会并行

关于gensim中doc2vec的使用参考

TaggedDocumnet 和TaggedLineDocument

前者的输入有两个参数:一行分词后的文本,标签;
后者的输入:分词之后的文本文件,每个文本占一行.

库版本的差异

python的gensim有2.3.0和3.4.0两个版本,加载模型推测文本向量时所用的gensim版本一定要和训模型时使用的版本一样。

推测文本向量时的注意事项

我想计算两个本文的语义相似,一开始我用如下方式推测文本向量,然后计算两个向量的余弦值:

model = Doc2Vec.load("model.model",mmap='r')
#推测文本的向量
Vector1 = model.infer_vector(text1,steps=6,alpha=0.025)
Vector2 = model.infer_vector(text2,steps=6,alpha=0.025)

后来我发现模型load一次,用这个模型对同一段文本推测两次,两次结果是不一样的,即下面的v1和v2是不一样的:

model = Doc2Vec.load("model.model",mmap='r')
#推测文本的向量
v1 = model.infer_vector(text1,steps=6,alpha=0.025)
v2 = model.infer_vector(text1,steps=6,alpha=0.025)

解决方法: model.random.seed(0)

  • 即模型加载后,每次推测之前都要设置model.random.seed()参数,但具体怎么设置并没有查到详细资料,举例如下:
model = Doc2Vec.load("model.model",mmap='r')
model.random.seed(0)
v1 = model.infer_vector(text1,steps=6,alpha=0.025)
model.random.seed(0)
v2 = model.infer_vector(text1,steps=6,alpha=0.025)
  • 每次推测之前都要加载一次模型,这种方式太耗时,不适合频繁使用,举例如下:
model = Doc2Vec.load("model.model",mmap='r')
v1 = model.infer_vector(text1,steps=6,alpha=0.025)
model = Doc2Vec.load("model.model",mmap='r'
v2 = model.infer_vector(text1,steps=6,alpha=0.025)
  • steps参数设置大一点,只是有人这么说,我没有试过。

讨论

问题

加载保存的模型后,我使用infer_vector方法来推断新句子的向量。但是,再次推断同一句子的向量会导致非常不同的结果。问题在于,在关闭我的应用程序并重新启动它之后,infer_vector方法再次提供与上一个会话中相同的向量。(第一个向量与第一个应用程序运行中的第一个向量相同,依此类推)。我猜这是一个错误,新的推断句子会影响模型,而该错误在停止应用程序时不会保存。因此,重新启动后再次具有相同的值。

解答1

如果负采样(negative参数)或频繁单词下采样(sample参数)是您配置的一部分,则训练和推理有时都使用随机数生成器。

在某些方面,代码尝试播种该源,以便使用完全相同顺序的完全相同的输入集运行是相同的-但即使是最细微的差异也会有所改变。对于infer_vector,我们目前不尝试在每次推断之前重置RNG以确定性。因此,第二轮实际上使用的是与第一轮稍有不同的随机抽奖集。

您是否正在使用负采样和/或常用单词降采样?如果是这样,则可能是您所观察到的原因。如果示例文本和模型具有充分的信息功能,并且已经完成了足够的推断梯度下降操作,则后续运行的向量仍应非常相似。(并且由于这些技术取决于一些随机选择和近似值,因此对于特定输入没有单一的“正确”向量,只是“在所提供的限制下找到的最佳”)。

如果您需要这种确定性,则应该可以通过在每次调用之前将model.random属性显式重置为新的和确定性的种子RandomState实例来强制执行该确定性infer_vector()。(我们可以考虑将其作为train_*方法的一个选项添加,但这可能不是正确的默认值…)

请注意,每次使用dm=1,或negative大于0的任何值或任何sample有效值都会导致不同的结果。

解答2

推论只是训练的一种约束形式,因此上面的答案也适用于此:对同一文本进行后续运行以得出不同的结果是正常的。但是,后续运行的结果应具有相似的质量-在特定的受约束情况下,相对于冻结的Doc2Vec模型推断单个文本的矢量,这意味着这些生成的矢量通常应该彼此非常接近。

您可以infer_vector()通过提供一个大于默认值的epochs参数来获得“更紧密”的结果。如果这样做没有帮助,则可能还有其他问题。您的模型可能功率不足/过拟合,因此文本的向量不会被强制放到单个位置,而是可以很好地适合多个位置。文字可能太短,尤其是在忽略了词汇以外的单词之后。您可能提供的是字符串,而不是令牌列表,并且纯字符串将仅被视为单字母令牌列表(其中很少有已知单词)。