Word2Vec 二次采样——实现
Posted
技术标签:
【中文标题】Word2Vec 二次采样——实现【英文标题】:Word2Vec Subsampling -- Implementation 【发布时间】:2020-03-05 10:52:05 【问题描述】:我正在 Pytorch 和 Tensorflow2 中实现 Skipgram 模型。我对常用词的二次抽样的实施有疑问。从论文中逐字逐句,对词wi
进行二次采样的概率计算为
其中t
是自定义阈值(通常是一个小值,例如0.0001),f
是文档中单词的频率。尽管作者以不同但几乎相同的方式实现它,但让我们坚持这个定义。
在计算P(wi)
时,我们可以得到负值。例如,假设我们有 100 个单词,其中一个单词出现的频率非常高(我的数据集就是这种情况)。
import numpy as np
import seaborn as sns
np.random.seed(12345)
# generate counts in [1, 20]
counts = np.random.randint(low=1, high=20, size=99)
# add an extremely bigger count
counts = np.insert(counts, 0, 100000)
# compute frequencies
f = counts/counts.sum()
# define threshold as in paper
t = 0.0001
# compute probabilities as in paper
probs = 1 - np.sqrt(t/f)
sns.distplot(probs);
问:使用这种“概率”实现二次抽样的正确方法是什么?
作为附加信息,我看到keras 中的函数keras.preprocessing.sequence.make_sampling_table
采用了不同的方法:
def make_sampling_table(size, sampling_factor=1e-5):
"""Generates a word rank-based probabilistic sampling table.
Used for generating the `sampling_table` argument for `skipgrams`.
`sampling_table[i]` is the probability of sampling
the i-th most common word in a dataset
(more common words should be sampled less frequently, for balance).
The sampling probabilities are generated according
to the sampling distribution used in word2vec:
```
p(word) = (min(1, sqrt(word_frequency / sampling_factor) /
(word_frequency / sampling_factor)))
```
We assume that the word frequencies follow Zipf's law (s=1) to derive
a numerical approximation of frequency(rank):
`frequency(rank) ~ 1/(rank * (log(rank) + gamma) + 1/2 - 1/(12*rank))`
where `gamma` is the Euler-Mascheroni constant.
# Arguments
size: Int, number of possible words to sample.
sampling_factor: The sampling factor in the word2vec formula.
# Returns
A 1D Numpy array of length `size` where the ith entry
is the probability that a word of rank i should be sampled.
"""
gamma = 0.577
rank = np.arange(size)
rank[0] = 1
inv_fq = rank * (np.log(rank) + gamma) + 0.5 - 1. / (12. * rank)
f = sampling_factor * inv_fq
return np.minimum(1., f / np.sqrt(f))
【问题讨论】:
【参考方案1】:我更倾向于信任已部署的代码而不是论文,尤其是在 word2vec 这样的案例中,论文作者发布的原作者 word2vec.c
code 已被广泛使用并用作其他实现的模板。如果我们看一下它的子采样机制...
if (sample > 0)
real ran = (sqrt(vocab[word].cn / (sample * train_words)) + 1) * (sample * train_words) / vocab[word].cn;
next_random = next_random * (unsigned long long)25214903917 + 11;
if (ran < (next_random & 0xFFFF) / (real)65536) continue;
...我们看到那些计数很小的单词 (.cn
) 可能在原始公式中给出负值,而不是在这里给出大于 1.0
的值,因此永远不会小于 long
-random-masked-and-scaled 永远不会超过1.0
((next_random & 0xFFFF) / (real)65536
)。因此,似乎作者的意图是让原始公式的所有负值都表示“永不丢弃”。
根据 keras make_sampling_table()
的评论和实施,他们没有咨询实际的词频。相反,他们假设基于词序的类 Zipf 分布来合成模拟词频。
如果他们的假设成立——相关词来自具有类似 Zipf 频率分布的自然语言语料库——那么我希望他们的采样概率接近计算的下采样概率来自真实的频率信息。对于大多数用途而言,这可能已经“足够接近”了。
我不确定他们为什么选择这个近似值。也许他们通常过程的其他方面在这一步之前没有保持真实的频率,并且他们期望始终使用自然语言文本,其中假设的频率通常是真实的。
(幸运的是,因为人们经常想将频率归因于公共的词向量集,这些词向量已经丢失了真实计数,但仍然从最频繁到最不频繁排序,就在几天前,我写了an answer about simulating a fake-but-plausible distribution using Zipf's law - 类似于这个 keras 代码正在做的事情。)
但是,如果您使用的数据不符合他们的假设(如合成或描述的数据集),他们的采样概率将与您自己计算的完全不同,使用任何形式的使用真实词频的原始公式。
特别是,想象一个有一个令牌一百万次的分布,然后一百个令牌每个只出现 10 次。 “排名”列表中这一百个令牌的顺序是任意的——确实,它们都与频率有关。但是基于模拟的方法,通过在该排序上拟合 Zipfian 分布,实际上会对它们中的每一个进行非常不同的采样。一个 10 次出现的单词幸运地排在第 2 位,它的下采样率要低得多,就好像它出现的频率要高得多。并且第一等级的“高头”值,通过使其真实频率*低于*近似,将比其他情况下更少的下采样。这些效果似乎都不是有益的,也不符合频繁词下采样选项的精神——它应该只“稀释”非常频繁的词,并且在所有情况下,在原始语料库中留下频率相同的词大致在下采样的语料库中等价地呈现。
因此,对于您的情况,我会使用原始公式(probability-of-discarding-that-requires-special-handling-of-negative-values),或 word2vec.c
实用/反转实现(probability-of -keeping-that-saturates-at-1.0),而不是 keras 风格的近似。
(作为一个完全独立的说明,如果您使用负采样,则可能与您的数据集/目的相关:还有另一个参数控制负示例的相对采样,在早期实现中通常固定为0.75
,即one paper has suggested can usefully vary for non-natural-language token distributions & recommendation-related end-uses。这个参数在Python的gensim
实现中被命名为ns_exponent
,而只是a fixed power
value internal to a sampling-table pre-calculation in the original word2vec.c
code。)
【讨论】:
gojomo 非常感谢您提供全面而有见地的回复和许多参考! 嗨@gojomo,我注意到您是Gensim 的贡献者。我有一个与上述问题相关的问题。我想问你在 Gensim 中实际是如何实现子采样的。我尝试查看源代码,但我没有设法弄清楚。非常感谢。 知道为什么论文中的 (t=1e-5) 与这里可能的 (sample=1e-3) 变量不匹配github.com/tmikolov/word2vec/blob/… 您必须询问 tmikolov - 但是该参数没有明显的最佳值,这取决于您的数据和目标,并且该参数可以在更大的数据中变得更具侵略性(更低)。也许他们注意到大多数人正在使用更小的数据集并决定为常见的 cass 设置更好的默认值?以上是关于Word2Vec 二次采样——实现的主要内容,如果未能解决你的问题,请参考以下文章