Word2Vec源码技巧分析(C语言)

本文深入解析word2vec源码实现技巧,包括sigmoid近似求解、window_size灵活运用、低频词与高频词处理策略、参数初始化方法、负采样算法及模型整体框架分析。

  这篇博客主要讲解word2vec源码(c语言)中的一些技巧,通过这些技巧从而更好的理解word2vec.

1 sigmoid的近似求解

先来看看sigmoid的公式和函数以及导数曲线:

g ( z ) = 1 1 + e − z \bm{ g(z)=\frac{1}{1+e^{-z}} } g(z)=1+ez1

在这里插入图片描述
从函数曲线中我可以看出,sigmoid的取值在(0,1)之间。
在源码中作者为了减少计算量,通过将[-6,6)进行划分(划分成1000份),来近似的取值。
接下来先看源码的划分和使用
划分代码:

 for (i = 0; i < EXP_TABLE_SIZE; i++) {
   
   
    expTable[i] = exp((i / (real)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() table
    expTable[i] = expTable[i] / (expTable[i] + 1);                   // Precompute f(x) = x / (x + 1)
  }

使用代码:这里的MAX_EXP=6,f是要传给sigmoid的数值。

if (f <= -MAX_EXP) continue;
else if (f >= MAX_EXP) continue;
//将f对应的sigmoid的值从数组中取出来,这里的操作可以看成之前对sigmoid操作的逆操作。
else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];

接下来开始分析,可以将第一段代码看成编码操作,那么第二段代码就是解码操作;

在这里插入图片描述
上图中的?就是f,因为代码段1中的x和代码段2中的f都是要传给sigmoid的所以是相等的。从而可以求出i(更新f)。
关于代码段1中x的划分,我是这样理解的,i/1000*12 代表长度上的比例-6是为了区分正负。

2 window_size的说明

首先假设window=c,在源码中,context(w)其实不一定是2c,实际上是中心词的两边都是w-b个词,
看一下源码:

 b = next_random % window;
 for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {
   
   
        c = sentence_position - window + a;
        if (c < 0) continue;
        if (c >= sentence_length) continue;
        last_word = sen[c];
        if (last_word == -1) continue;
	    。。。。。
      }

这里b的取值就是 [0,c-1]. 可以取值代入试一下。

3 低频词和高频词的处理

  • 首先是低频词的处理,在低频词的处理上,源码主要在两个点使用,

  • 1 在构建词典时候判断词典大小是否大于预设值的0.7,若大于则开始去除低频词(这里min_count=5),同时min_count++。

  • 2 在构建完成时,按照词频进行排序,再次去除低频词。

  • 这两个区别在于,1中是在构建过程中进行的,也就是说去除完之后还要继续添加,还会存在低频词,所以执行了2.

  • 接下来是高频词的处理,首先需要说明的是,代码是以行为单位进行的,所以在读取一行单词的时候,可能会存在一些很高频的词,对我们的模型并不影响,例如,的,是。。。。。这个时候把他们顾虑掉并不影响模型整体。来看一下代码(其中sample是预设的阈值默认0.001):
    还需要解释的是cn是词频,train_words所有词的词频的和。

  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;
        }

上述代码公式是这样子的(tw代替train_words, s代表sample,ran是p)sample取值范围是【0,1e-5】越小对高频词打压越严重:
在这里插入图片描述
从上面的公式中f越大代表,cn越大,f越大,p越小,然后随机生成一个(0,1)之间的数,若是p越小,则生成的这个数越容易比p大,也就是越容易去除。

4 参数的初始化

源码中词向量对应的是syn0,syn1对应的是非叶子节点的向量,syn1neg是采样的点对应的向量。
这里syn1和syn1neg的初始化都是0,而syn0 的初始化区间是 [ − 0.5 m , 0.5 m ] [-\frac{0.5}{m},\frac{0.5}{m}] [m0.5,m0.5],其中m是词向量的大小。

5 负采样

  • 负采样也是根据词频来进行采样的,cn越高,被采样的概率就越大,借用百度百科的解释:判断两个单词是不是一对上下文词(context)与目标词(target),如果是一对,则是正样本,如果不是一对,则是负样本。
  • 采样得到一个上下文词和一个目标词,生成一个正样本(positive example),生成一个负样本(negative example),则是用与正样本相同的上下文词,再在字典中随机选择一个单词,这就是负采样(negative sampling)。
  • 这里有点类似我小时候玩的一个游戏转动指针指针停在不同的地方有不同的奖品,扇形区域越小得到的奖品越好,这里也是同样的到理。
    接下来看源码:
void InitUnigramTable() {
   
   
  int a, i;
  double train_words_pow = 0;
  double d1, power = 0.75;
  //table_size是对[0,1]的等距离分割。这里的table_size是一个很大的数值1e8。
  table = (int *)malloc(table_size * sizeof(int));
  for (a = 0; a <
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值