word2vec源码解读

来源:互联网 发布:温州管家婆软件 编辑:程序博客网 时间:2024/06/05 05:42

一、Word2Vec程序解析

word2vec主要的层次结构

picture1


vocab是一个结构体数组。

*Vocab_hash是一个hash链表。

vocab存入词的时候实际是按照先后顺序存储的。为了方便查找,在词存入的时候顺便把词在链表中的位置存入到vocab_hash中,而该词的vocab_hash位置有hash(word)决定,这样查找起来很快。

 

ReadWord:逐个字符读入词(一个汉字是不是拆成两个字符读入呢?)

GetWordHash: hash = hash*257+word[i];

SearchVocab: 在vocab中查找对应的词,返回-1是没有找到,否则返回vocab_hash[hash]。

ReadWordIndex: 返回词在vocab中的位置。

AddWordToVocab:向vocab中插入新词,并在vocab_hash中插入新词的位置。

SortVocab:把vocab中的所有词整理了一遍,出现次数少于最低次数的丢掉,并重新分配了空间。

ReduceVocab:也是重新整理,将出现次数少的词干掉,只是并不重新分配空间,只是将次数不达标的词对应的vocab空间free掉。每被执行一次,min_reduce自增一次。(此函数是为保证vocab最大容量为21M而做的,如果trainfile里的词量太大,只有保留频次高的词。)

 

LearnVocabFromTrainFile:

picture2



SaveVocab:将vocab中的word和cn写入到输出文件中。

 

TrainModel:

其实网络的实现都是在TrainModelThread中,神经网络分成多线程计算,计算完成之后再进行k-mean聚类。TrainModel生成线程,配置线程。

picture3



InitNet:给第一层syn0、syn1、syn1neg分配空间。并给syn0赋初始值。并生成二叉树。

CreateBinaryTree:生成一棵节点数为2*vocab_size+1个节点的huffman树,并根据词频给给每个词设定其在huffman树的位置。

TrainModelThread:实现神经网络。(下一节细看这块。)


1.      TrainModelTread的流程图


总的来说是这样的:

(1).所有训练集中的词被等分成n份(n为线程数),所有的词都会迭代5次(5次是默认值,这个可以在参数中设置),因此,每个线程会反复读5次自己管辖内的词。

(2).每次按照句子来读入词,一次读入一句,一句读入后,逐个词进入神经网络训练。等这句话的所有词都训练完成后,再读入下一句。

(3).当读到线程管辖文件尾时,迭代计数器自减,如果减为0了,则跳出最外层循环,整个训练结束;如果还没有减到0,则将读文件的指针移到线程管辖文件的头部。重新开始下一次迭代。

(4).每处理10000个词,就需要更新1次alpha。

(5). 逐个词进入神经网络训练,虽然设置了window,但是,并不是5个词进行一次神经网络训练,而是在in->hidden做向量累加时,随机计算窗口量,窗口数量有window这么多种(3-11个之间),以当前输入词为中心,累加其前后的词的向量。

(窗口大小随机,但有范围,以当前词为中心,(除最开始,和最末尾))。

2.      神经网络对应程序推导

以下推导是根据神经网络中主要的算式为主线,红色为其后备推导过程,以此来分析整个神经网络。


这里,首先,在intiNet()定义syn0是一个V*L维度的大矩阵,L为每个词的向量维度。它存的是vocab中所有词的L维的向量,已经给此矩阵负了随机初始值。Word是词在vocab中的位置。

在上一节提到,在做in->hidden的累加时,词窗口大小是随机的。这里neu1[c]就是窗口中词的向量累加。(c为维度计数,L范围内循环,W为窗口词个数)。

 



cw是窗口中词的数量,这里相当于是把做成平均值。

 


其中piont为词在huffman树中到根结点的路径,point[d]是其往上推的第d个父结点。这个内积和为其自身向量与各父结点的内积和,取这样一个内积和的好处目前还没有搞清楚。

 


这是求f的sigmoid函数输出。这里没有直接计算,而是换成了查表的方式,应该是为了加快速度。但是查表就意味着把这个函数离散化了,就会存在离散误差。这里,给f定了取值范围为(-6,6),而定了表的元素总量为1000。带入可得


这个公式怎么就是sigmoid函数了呢?

首先来看看,是如何定义的

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. for (i = 0; i < EXP_TABLE_SIZE; i++) {  
  2.     expTable[i] = exp((i / (real)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() table  
  3.     expTable[i] = expTable[i] / (expTable[i] + 1);                   // Precompute f(x) = x / (x + 1)  
  4.   }  

也就是


再来看看sigmoid函数的定义


其取值范围为(0,1)。可转化为


那么


i的范围(0,1000),z的范围为(-6,6),离散化后步长为12/1000。

那么中各元素的值就是(0.0024726232,0.99752737768)之间非线性增加的值。

接下来令P=(f+6)*1000/12,f取值范围(-6,6),P的取值范围正好是(0,1000),覆盖表中所有的元素。

因此,④就是sigmoid函数的离散化形式。

 



code是父结点的标签,1为右结点,0为左结点。d依然是往上推的第d个父结点。这是梯度计算公式。由于0<f<1,>0,这样的话,父结点为左结点对应的梯度为正,为右结点的梯度为负。

 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];  
  2. for (c = 0; c < layer1_size; c++) syn1[c + l2] += g * neu1[c];  

更新向量。Neu1e[],每个词的向量误差为各父结点各次迭代向量乘梯度的和。然后把父结点的向量叠加到该词当前向量值中,实际上,向量误差就是自己前面d次迭代出来的向量参数乘梯度。

 


[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  2.          c = sentence_position - window + a;  
  3.          if (c < 0) continue;  
  4.          if (c >= sentence_length) continue;  
  5.          last_word = sen[c];  
  6.          if (last_word == -1) continue;  
  7.          for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c];  


把误差叠加到每个词的向量当中。这个误差实际上是各父结点的向量乘梯度,包含了该父结点所有叶子结点的向量,因此,不但同一个父结点下来的两个子结点有关联了,连与叶子结点出现在同一个句子,且位置相近的词也关联起来了。


第三部分、word2vec之TrainModelThread程序详细注解

[cpp] view plain copy
  1. <pre name="code" class="cpp">void *TrainModelThread(void *id) {  
  2.   long long a, b, d, cw, word, last_word, sentence_length = 0, sentence_position = 0;  
  3.   long long word_count = 0, last_word_count = 0, sen[MAX_SENTENCE_LENGTH + 1];  
  4.   long long l1, l2, c, target, label, local_iter = iter;  
  5.   unsigned long long next_random = (long long)id;  
  6.   real f, g;  
  7.   clock_t now;  
  8.   real *neu1 = (real *)calloc(layer1_size, sizeof(real));  //只有输入层需要,隐含层是一个累加和,输出层存入huffman树中。  
  9.   real *neu1e = (real *)calloc(layer1_size, sizeof(real));  
  10.   FILE *fi = fopen(train_file, "rb");  
  11.   fseek(fi, file_size / (long long)num_threads * (long long)id, SEEK_SET);  
  12.   while (1) {  
  13.   
  14.       /************每10000个词左右重新计算一次alpha.**********************/  
  15.     if (word_count - last_word_count > 10000) {      
  16.       word_count_actual += word_count - last_word_count;  
  17.       last_word_count = word_count;  
  18.       if ((debug_mode > 1)) {  
  19.         now=clock();  
  20.         printf("%cAlpha: %f  Progress: %.2f%%  Words/thread/sec: %.2fk  ", 13, alpha,  
  21.          word_count_actual / (real)(iter * train_words + 1) * 100,  
  22.          word_count_actual / ((real)(now - start + 1) / (real)CLOCKS_PER_SEC * 1000));  
  23.         fflush(stdout);  
  24.       }  
  25.       alpha = starting_alpha * (1 - word_count_actual / (real)(iter * train_words + 1));  
  26.       if (alpha < starting_alpha * 0.0001) alpha = starting_alpha * 0.0001;  
  27.     }  
  28.       
  29.     /**********************读入一个句子,或者文章长于1000,则分成两句***************************************/  
  30.     //将句子中每个词的vocab位置存入到sen[]  
  31.     //每次读入一句,但读一句后等待这句话处理完之后再读下一句。  
  32.     if (sentence_length == 0) {  //只有在一句执行完之后,,才会取下一句  
  33.       while (1) {  
  34.         word = ReadWordIndex(fi);  //读fi中的词,返回其在vocab中的位置。  
  35.         if (feof(fi)) break;  
  36.         if (word == -1) continue;  
  37.         word_count++;  
  38.         if (word == 0) break// 第0个词存的是句子结束符</s>,因此,这里一次性送入sen的就是一个句子或一篇文章。  
  39.         // The subsampling randomly discards frequent words while keeping the ranking same  
  40.         if (sample > 0) {  
  41.           //  
  42.           real ran = (sqrt(vocab[word].cn / (sample * train_words)) + 1) * (sample * train_words) / vocab[word].cn;  
  43.           next_random = next_random * (unsigned long long)25214903917 + 11;  
  44.           if (ran < (next_random & 0xFFFF) / (real)65536) continue;  //(next_random & 0xFFFF) / (real)65536 应该是个小于1的值。也就是说ran 应该大于1.  
  45.         }  
  46.         sen[sentence_length] = word;  //sen存的是词在vocab中的位置。  
  47.         sentence_length++;  
  48.         if (sentence_length >= MAX_SENTENCE_LENGTH) break;  //文章超过1000个词则分成两个句子。  
  49.       }  
  50.       sentence_position = 0;  
  51.     }  
  52.   
  53.     /**************************************************处理到文件尾的话,迭代数递减,***********************************/  
  54.     //所有的词(这里单个线程处理其对应的词)会被执行local_iter次。这5次神经网络的参数不是重复的,而是持续更新的,像alpha、syn0。  
  55.     //单个线程处理的词是一样的,这个后续可以看看有没可优化的地方。  
  56.     if (feof(fi) || (word_count > train_words / num_threads)) {  //train_file被读到末尾了,或者一个线程已经完成了它的份额。  
  57.       word_count_actual += word_count - last_word_count;  
  58.       local_iter--;  //读完所有词之后进行5次迭代是个啥意思?  也就是这些词不是过一次这个网络就行了,而是5词。  
  59.       if (local_iter == 0) break;   //只有这里才是跳出最外层循环的地方。  
  60.       word_count = 0;  
  61.       last_word_count = 0;  
  62.       sentence_length = 0;  
  63.       //移动文件流读写位置,从距文件开头file_size / (long long)num_threads * (long long)id 位移量为新的读写位置  
  64.       fseek(fi, file_size / (long long)num_threads * (long long)id, SEEK_SET);  //将文件读指针重新移到到此线程所处理词的开头。  
  65.       continue;  
  66.     }  
  67.   
  68.     /*******************************进入神经网络******************************/  
  69.     word = sen[sentence_position];  //从句首开始,虽然window=5,或别的,但是,以  
  70.     if (word == -1) continue;  
  71.     for (c = 0; c < layer1_size; c++) neu1[c] = 0;  
  72.     for (c = 0; c < layer1_size; c++) neu1e[c] = 0;  
  73.     next_random = next_random * (unsigned long long)25214903917 + 11;  
  74.     //这个点没有固定下来,导致窗口也是随机的,可以看看这点是否可以优化。  
  75.     b = next_random % window;  //b取0-4之间的随机值。  
  76.     if (cbow) {  //train the cbow architecture  
  77.       // in -> hidden  
  78.       cw = 0;  
  79.       //窗口大小随机,但有范围(3-11,窗口大小为单数,一共5种,因此,window实际可以理解为窗口变化的种数),以当前词为中心,(除最开始,和最末尾)  
  80.       for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  81.         c = sentence_position - window + a;  //给c赋值  
  82.         if (c < 0) continue;  
  83.         if (c >= sentence_length) continue;  
  84.         last_word = sen[c];  
  85.         if (last_word == -1) continue;  
  86.   
  87.         //累加词对应的向量。双重循环下来就是窗口额定数量的词每一维对应的向量累加。  
  88.         //累加后neu1的维度依然是layer1_size。  
  89.         //从输入层过度到隐含层。  
  90.         for (c = 0; c < layer1_size; c++) neu1[c] += syn0[c + last_word * layer1_size];     
  91.         cw++;  //进入隐含层的词个数。  
  92.       }  
  93.       if (cw) {  
  94.         for (c = 0; c < layer1_size; c++) neu1[c] /= cw;  //归一化处理。  
  95.         //遍历该叶子节点对应的路径,也就是每个父结点循环一次,这是什么原理呢?  
  96.         //这样一来,越是词频低的词,迭代层数越多,  
  97.         //每个词都要从叶子结点向根结点推一遍。  
  98.         //这样的话可以通过父结点,建立叶子结点之间的联系。  
  99.         if (hs) for (d = 0; d < vocab[word].codelen; d++) {   
  100.           f = 0;  
  101.           l2 = vocab[word].point[d] * layer1_size;  
  102.           // Propagate hidden -> output  
  103.           for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1[c + l2];  //做内积  这个内积是什么原理呢?  
  104.           if (f <= -MAX_EXP) continue//不在范围内的内积丢掉  
  105.           else if (f >= MAX_EXP) continue;  //-6<f<6  
  106.           else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];  //sigmod函数, f=expTab[(int)((f+6)*1000/12)]  
  107.           // 'g' is the gradient multiplied by the learning rate  
  108.           g = (1 - vocab[word].code[d] - f) * alpha; //计算梯度  
  109.           // Propagate errors output -> hidden  
  110.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];  //计算向量误差,实际就是各父结点的向量和乘梯度。  
  111.           // Learn weights hidden -> output  
  112.           for (c = 0; c < layer1_size; c++) syn1[c + l2] += g * neu1[c];  //更新父结点们的向量值,父结点的向量就是各叶子结点各次向量的累加。  
  113.           //关系就是这样建立起来的,各叶子结点的向量都累加进入了每一个父结点中,因此,拥有相同父结点的词就会联系起来了。  
  114.         }  
  115.         // NEGATIVE SAMPLING  
  116.         if (negative > 0) for (d = 0; d < negative + 1; d++) {  //有负样本,处理负样本  
  117.           if (d == 0) {  
  118.             target = word;  
  119.             label = 1;  //正样本  
  120.           } else {  
  121.             next_random = next_random * (unsigned long long)25214903917 + 11;  
  122.             target = table[(next_random >> 16) % table_size];  
  123.             if (target == 0) target = next_random % (vocab_size - 1) + 1;  
  124.             if (target == word) continue;  
  125.             label = 0;  //负样本  
  126.           }  
  127.           l2 = target * layer1_size;  
  128.           f = 0;    //以下和上面差不多。  
  129.           for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1neg[c + l2];  
  130.           if (f > MAX_EXP) g = (label - 1) * alpha;  
  131.           else if (f < -MAX_EXP) g = (label - 0) * alpha;  
  132.           else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;     
  133.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];  
  134.           for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * neu1[c];  
  135.         }  
  136.         // hidden -> in  
  137.         for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  138.           c = sentence_position - window + a;  
  139.           if (c < 0) continue;  
  140.           if (c >= sentence_length) continue;  
  141.           last_word = sen[c];  
  142.           if (last_word == -1) continue;  
  143.           for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c];  //用误差更新向量(输入层参数),直接将误差叠加到输入向量上,这样好吗?  
  144.         }  
  145.       }  
  146.     } else {  //train skip-gram  
  147.       for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  148.         c = sentence_position - window + a;  
  149.         if (c < 0) continue;  
  150.         if (c >= sentence_length) continue;  
  151.         last_word = sen[c];  
  152.         if (last_word == -1) continue;  
  153.         l1 = last_word * layer1_size;  //和cbw相比少了做输入向量累加。  
  154.         for (c = 0; c < layer1_size; c++) neu1e[c] = 0;  
  155.         // HIERARCHICAL SOFTMAX  
  156.         if (hs) for (d = 0; d < vocab[word].codelen; d++) {  
  157.           f = 0;  
  158.           l2 = vocab[word].point[d] * layer1_size;  
  159.           // Propagate hidden -> output  
  160.           for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1[c + l2];  //做内积  
  161.           if (f <= -MAX_EXP) continue;  
  162.           else if (f >= MAX_EXP) continue;  
  163.           else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];  
  164.           // 'g' is the gradient multiplied by the learning rate  
  165.           g = (1 - vocab[word].code[d] - f) * alpha;  
  166.           // Propagate errors output -> hidden  
  167.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];  
  168.           // Learn weights hidden -> output  
  169.           for (c = 0; c < layer1_size; c++) syn1[c + l2] += g * syn0[c + l1];  
  170.         }  
  171.         // NEGATIVE SAMPLING  
  172.         if (negative > 0) for (d = 0; d < negative + 1; d++) {  
  173.           if (d == 0) {  
  174.             target = word;  
  175.             label = 1;  
  176.           } else {  
  177.             next_random = next_random * (unsigned long long)25214903917 + 11;  
  178.             target = table[(next_random >> 16) % table_size];  
  179.             if (target == 0) target = next_random % (vocab_size - 1) + 1;  
  180.             if (target == word) continue;  
  181.             label = 0;  
  182.           }  
  183.           l2 = target * layer1_size;  
  184.           f = 0;  
  185.           for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2];  
  186.           if (f > MAX_EXP) g = (label - 1) * alpha;  
  187.           else if (f < -MAX_EXP) g = (label - 0) * alpha;  
  188.           else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;  
  189.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];  
  190.           for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1];  
  191.         }  
  192.         // Learn weights input -> hidden  
  193.         for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];  
  194.       }  
  195.     }  
  196.     sentence_position++;  
  197.     if (sentence_position >= sentence_length) {  
  198.       sentence_length = 0;  
  199.       continue;  
  200.     }  
  201.   }  
  202.   fclose(fi);  
  203.   free(neu1);  
  204.   free(neu1e);  
  205.   pthread_exit(NULL);  
  206. }  

第四部分  

word2vec之霍夫曼树的实现

1.      霍夫曼树的基本要点

判定过程最优的二叉树是霍夫曼树。

路径:树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。

路径长度:路径上的分支数目称为路径长度。

树的路径长度:从根结点到每一个叶子结点的路径长度之和就是这棵树的路径长度。

带权值的路径长度:如果树中每一个叶子结点都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带权路径长度。

 

树的带权值路径长度:


带权路径最小的二叉树就是判定过程最优二叉树,即霍夫曼二叉树。其中,权值越大的叶子结点越接近根结点。

 

2.      Word2vec中霍夫曼树的实现

其中,count存各个词的词频,就是叶子结点的权值;binary存霍夫曼编码,parent_node存各父结点的位置。由于满二叉树的节点数m=2n-1, n为叶子结点数,所以以上3个数组的元素个数均为2*vocab_size-1.前n个存叶子结点,后n-1个存父结点。

 

具体步骤:

以后各次循环如步骤2,直到2n-1也被赋值,整棵树建立完成。


程序如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void CreateBinaryTree() {  
  2.   long long a, b, i, min1i, min2i, pos1, pos2, point[MAX_CODE_LENGTH];  
  3.   char code[MAX_CODE_LENGTH];  
  4.   long long *count = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));  //节点的权重,叶子节点的权重为cn,父节点的权重为其子结点权重和。  
  5.   long long *binary = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));   //表明左右结点,左结点为0,右节点为1.  
  6.   long long *parent_node = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));  //父结点。  
  7.   for (a = 0; a < vocab_size; a++) count[a] = vocab[a].cn;  
  8.   for (a = vocab_size; a < vocab_size * 2; a++) count[a] = 1e15;  
  9.   pos1 = vocab_size - 1;  
  10.   pos2 = vocab_size;  
  11.   // Following algorithm constructs the Huffman tree by adding one node at a time  
  12.   for (a = 0; a < vocab_size - 1; a++) {  
  13.     // First, find two smallest nodes 'min1, min2'  
  14.     if (pos1 >= 0) {  
  15.       if (count[pos1] < count[pos2]) {  //根据词频来确定位置  词频作为权值?  
  16.         min1i = pos1;  
  17.         pos1--;  
  18.       } else {  
  19.         min1i = pos2;  
  20.         pos2++;  
  21.       }  
  22.     } else {  
  23.       min1i = pos2;  
  24.       pos2++;  
  25.     }  
  26.     if (pos1 >= 0) {  
  27.       if (count[pos1] < count[pos2]) {  
  28.         min2i = pos1;  
  29.         pos1--;  
  30.       } else {  
  31.         min2i = pos2;  
  32.         pos2++;  
  33.       }  
  34.     } else {  
  35.       min2i = pos2;  
  36.       pos2++;  
  37.     }  
  38.     count[vocab_size + a] = count[min1i] + count[min2i];  //把权值赋给父结点  
  39.     parent_node[min1i] = vocab_size + a; //定义上面两点的父结点。  
  40.     parent_node[min2i] = vocab_size + a;  
  41.     binary[min2i] = 1;   //给右结点贴标签。  
  42.   }  
  43.   // Now assign binary code to each vocabulary word  
  44.   for (a = 0; a < vocab_size; a++) {  
  45.     b = a;  
  46.     i = 0;  
  47.     while (1) {  
  48.       code[i] = binary[b];  //code存huffman编码。  
  49.       point[i] = b;  //point专门存父结点  
  50.       i++;  
  51.       b = parent_node[b];  
  52.       if (b == vocab_size * 2 - 2) break;  
  53.     }  
  54.     vocab[a].codelen = i;  //huffman编码的长度。  code和codelen可以用于定位词的位置。  
  55.     vocab[a].point[0] = vocab_size - 2;  
  56.     for (b = 0; b < i; b++) {  //将每个词对应的huffman路径写到point里。  
  57.       vocab[a].code[i - b - 1] = code[b];  
  58.       vocab[a].point[i - b] = point[b] - vocab_size;  //每个结点都存入其所有的父结点的位置。  
  59.     }  
  60.   }  
  61.   free(count);  
  62.   free(binary);  
  63.   free(parent_node);  
  64. }  

word2vec源代码解


[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //  Copyright 2013 Google Inc. All Rights Reserved.  
  2. //  
  3. //  Licensed under the Apache License, Version 2.0 (the "License");  
  4. //  you may not use this file except in compliance with the License.  
  5. //  You may obtain a copy of the License at  
  6. //  
  7. //      http://www.apache.org/licenses/LICENSE-2.0  
  8. //  
  9. //  Unless required by applicable law or agreed to in writing, software  
  10. //  distributed under the License is distributed on an "AS IS" BASIS,  
  11. //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  12. //  See the License for the specific language governing permissions and  
  13. //  limitations under the License.  
  14.   
  15. #include <stdio.h>  
  16. #include <stdlib.h>  
  17. #include <string.h>  
  18. #include <math.h>  
  19. #include <pthread.h>  
  20. #include <time.h>  
  21.   
  22. #define MAX_STRING 100  
  23. #define EXP_TABLE_SIZE 1000  
  24. #define MAX_EXP 6  
  25. #define MAX_SENTENCE_LENGTH 1000  
  26. #define MAX_CODE_LENGTH 40  
  27.   
  28. const int vocab_hash_size = 30000000;  // Maximum 30 * 0.7 = 21M words in the vocabulary  
  29.   
  30. typedef float real;                    // Precision of float numbers  
  31.   
  32. struct vocab_word {  
  33.   long long cn;  
  34.   int *point;  
  35.   char *word, *code, codelen;  
  36. };  
  37.   
  38. char train_file[MAX_STRING], output_file[MAX_STRING];  
  39. char save_vocab_file[MAX_STRING], read_vocab_file[MAX_STRING];  
  40. struct vocab_word *vocab;  
  41. int binary = 0, cbow = 1, debug_mode = 2, window = 5, min_count = 5, num_threads = 12, min_reduce = 1;  
  42. int *vocab_hash;  
  43. long long vocab_max_size = 1000, vocab_size = 0, layer1_size = 100;  
  44. long long train_words = 0, word_count_actual = 0, iter = 5, file_size = 0, classes = 0;  
  45. real alpha = 0.025, starting_alpha, sample = 1e-3;  
  46. real *syn0, *syn1, *syn1neg, *expTable;  
  47. clock_t start;  
  48.   
  49. int hs = 0, negative = 5;  //右参数决定。  
  50. const int table_size = 1e8;  
  51. int *table;  
  52.   
  53. void InitUnigramTable() {  
  54.   int a, i;  
  55.   long long train_words_pow = 0;  
  56.   real d1, power = 0.75;  
  57.   table = (int *)malloc(table_size * sizeof(int));  
  58.   for (a = 0; a < vocab_size; a++) train_words_pow += pow(vocab[a].cn, power);  
  59.   i = 0;  
  60.   d1 = pow(vocab[i].cn, power) / (real)train_words_pow;  
  61.   for (a = 0; a < table_size; a++) {  
  62.     table[a] = i;  
  63.     if (a / (real)table_size > d1) {  
  64.       i++;  
  65.       d1 += pow(vocab[i].cn, power) / (real)train_words_pow;  
  66.     }  
  67.     if (i >= vocab_size) i = vocab_size - 1;  
  68.   }  
  69. }  
  70.   
  71. // Reads a single word from a file, assuming space + tab + EOL to be word boundaries  
  72. void ReadWord(char *word, FILE *fin) {  
  73.   int a = 0, ch;  
  74.   while (!feof(fin)) {  
  75.     ch = fgetc(fin);  
  76.     if (ch == 13) continue;  
  77.     if ((ch == ' ') || (ch == '\t') || (ch == '\n')) {  
  78.       if (a > 0) {  
  79.         if (ch == '\n') ungetc(ch, fin);  
  80.         break;  
  81.       }  
  82.       if (ch == '\n') {  
  83.         strcpy(word, (char *)"</s>");  
  84.         return;  
  85.       } else continue;  
  86.     }  
  87.     word[a] = ch;  
  88.     a++;  
  89.     if (a >= MAX_STRING - 1) a--;   // Truncate too long words  
  90.   }  
  91.   word[a] = 0;  
  92. }  
  93.   
  94. // Returns hash value of a word  
  95. int GetWordHash(char *word) {  
  96.   unsigned long long a, hash = 0;  
  97.   for (a = 0; a < strlen(word); a++) hash = hash * 257 + word[a];  
  98.   hash = hash % vocab_hash_size;  
  99.   return hash;  
  100. }  
  101.   
  102. // Returns position of a word in the vocabulary; if the word is not found, returns -1  
  103. int SearchVocab(char *word) {  
  104.   unsigned int hash = GetWordHash(word);  
  105.   while (1) {  
  106.     if (vocab_hash[hash] == -1) return -1;  
  107.     if (!strcmp(word, vocab[vocab_hash[hash]].word)) return vocab_hash[hash];  
  108.     hash = (hash + 1) % vocab_hash_size;  
  109.   }  
  110.   return -1;  
  111. }  
  112.   
  113. // Reads a word and returns its index in the vocabulary  
  114. int ReadWordIndex(FILE *fin) {  
  115.   char word[MAX_STRING];  
  116.   ReadWord(word, fin);  
  117.   if (feof(fin)) return -1;  
  118.   return SearchVocab(word);  
  119. }  
  120.   
  121. // Adds a word to the vocabulary  
  122. //vocb存入词的时候实际是按照先后顺序存储的。  
  123. //为了方便查找,在词存入的时候顺便把词在链表中的位置存入到vocab_hash中,  
  124. //而该词的vocab_hash位置有hash(word)决定,这样查找起来很快。  
  125. int AddWordToVocab(char *word) {  
  126.   unsigned int hash, length = strlen(word) + 1;  
  127.   if (length > MAX_STRING) length = MAX_STRING;  
  128.   vocab[vocab_size].word = (char *)calloc(length, sizeof(char));  //为词分配一个对应大小的空间,并赋给vocab.word。  
  129.   strcpy(vocab[vocab_size].word, word);  
  130.   vocab[vocab_size].cn = 0;  
  131.   vocab_size++;  
  132.   // Reallocate memory if needed  
  133.   if (vocab_size + 2 >= vocab_max_size) {  
  134.     vocab_max_size += 1000;  
  135.     vocab = (struct vocab_word *)realloc(vocab, vocab_max_size * sizeof(struct vocab_word));  
  136.   }  
  137.   hash = GetWordHash(word);  
  138.   while (vocab_hash[hash] != -1) hash = (hash + 1) % vocab_hash_size;  
  139.   vocab_hash[hash] = vocab_size - 1;   
  140.   return vocab_size - 1;  
  141. }  
  142.   
  143. // Used later for sorting by word counts  
  144. int VocabCompare(const void *a, const void *b) {  
  145.     return ((struct vocab_word *)b)->cn - ((struct vocab_word *)a)->cn;  //比较词频  
  146. }  
  147.   
  148. // Sorts the vocabulary by frequency using word counts  
  149. void SortVocab() {  
  150.   int a, size;  
  151.   unsigned int hash;  
  152.   // Sort the vocabulary and keep </s> at the first position  
  153.   qsort(&vocab[1], vocab_size - 1, sizeof(struct vocab_word), VocabCompare);  //根据词频对vocab中的词进行排序。  
  154.   for (a = 0; a < vocab_hash_size; a++) vocab_hash[a] = -1;  //所有vocab_hash清0。  
  155.   size = vocab_size;  
  156.   train_words = 0;  
  157.   for (a = 0; a < size; a++) {  
  158.     // Words occuring less than min_count times will be discarded from the vocab  
  159.     if ((vocab[a].cn < min_count) && (a != 0)) {  //vocab中出现次数少于最小次数的词丢掉。(这是个办法)。  
  160.       vocab_size--;  
  161.       free(vocab[a].word);  
  162.     } else {  
  163.       // Hash will be re-computed, as after the sorting it is not actual  
  164.       hash=GetWordHash(vocab[a].word);  
  165.       while (vocab_hash[hash] != -1) hash = (hash + 1) % vocab_hash_size;  //其实前面已经全部清为-1了。  
  166.       vocab_hash[hash] = a;  
  167.       train_words += vocab[a].cn;  //累积训练词的数量。  
  168.     }  
  169.   }  
  170.   vocab = (struct vocab_word *)realloc(vocab, (vocab_size + 1) * sizeof(struct vocab_word));  //重新分配空间做啥?  
  171.   // Allocate memory for the binary tree construction  
  172.   for (a = 0; a < vocab_size; a++) {  
  173.     vocab[a].code = (char *)calloc(MAX_CODE_LENGTH, sizeof(char));  
  174.     vocab[a].point = (int *)calloc(MAX_CODE_LENGTH, sizeof(int));  
  175.   }  
  176. }  
  177.   
  178. // Reduces the vocabulary by removing infrequent tokens  
  179. void ReduceVocab() {  
  180.   int a, b = 0;  
  181.   unsigned int hash;  
  182.   for (a = 0; a < vocab_size; a++) if (vocab[a].cn > min_reduce) {  //如果一个单词的出现次数大于最小削减次数,才能保留下来。  
  183.     vocab[b].cn = vocab[a].cn;  
  184.     vocab[b].word = vocab[a].word;  
  185.     b++;  
  186.   } else free(vocab[a].word);  
  187.   vocab_size = b;  
  188.   for (a = 0; a < vocab_hash_size; a++) vocab_hash[a] = -1;  
  189.   for (a = 0; a < vocab_size; a++) {  
  190.     // Hash will be re-computed, as it is not actual  
  191.     hash = GetWordHash(vocab[a].word);  
  192.     while (vocab_hash[hash] != -1) hash = (hash + 1) % vocab_hash_size;  
  193.     vocab_hash[hash] = a;  
  194.   }  
  195.   fflush(stdout);  
  196.   min_reduce++; //如果下次再次调用此函数,说明词汇量又大了,min_reduce不增加,则满足不了下次reduce的量了。  
  197. }  
  198.   
  199. // Create binary Huffman tree using the word counts  
  200. // Frequent words will have short uniqe binary codes  
  201. void CreateBinaryTree() {  
  202.   long long a, b, i, min1i, min2i, pos1, pos2, point[MAX_CODE_LENGTH];  
  203.   char code[MAX_CODE_LENGTH];  
  204.   long long *count = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));  
  205.   long long *binary = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));  
  206.   long long *parent_node = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));  
  207.   for (a = 0; a < vocab_size; a++) count[a] = vocab[a].cn;  
  208.   for (a = vocab_size; a < vocab_size * 2; a++) count[a] = 1e15;  
  209.   pos1 = vocab_size - 1;  
  210.   pos2 = vocab_size;  
  211.   // Following algorithm constructs the Huffman tree by adding one node at a time  
  212.   for (a = 0; a < vocab_size - 1; a++) {  
  213.     // First, find two smallest nodes 'min1, min2'  
  214.     if (pos1 >= 0) {  
  215.       if (count[pos1] < count[pos2]) {  //根据词频来确定位置  
  216.         min1i = pos1;  
  217.         pos1--;  
  218.       } else {  
  219.         min1i = pos2;  
  220.         pos2++;  
  221.       }  
  222.     } else {  
  223.       min1i = pos2;  
  224.       pos2++;  
  225.     }  
  226.     if (pos1 >= 0) {  
  227.       if (count[pos1] < count[pos2]) {  
  228.         min2i = pos1;  
  229.         pos1--;  
  230.       } else {  
  231.         min2i = pos2;  
  232.         pos2++;  
  233.       }  
  234.     } else {  
  235.       min2i = pos2;  
  236.       pos2++;  
  237.     }  
  238.     count[vocab_size + a] = count[min1i] + count[min2i];  
  239.     parent_node[min1i] = vocab_size + a;  
  240.     parent_node[min2i] = vocab_size + a;  
  241.     binary[min2i] = 1;  
  242.   }  
  243.   // Now assign binary code to each vocabulary word  
  244.   for (a = 0; a < vocab_size; a++) {  
  245.     b = a;  
  246.     i = 0;  
  247.     while (1) {  
  248.       code[i] = binary[b];  
  249.       point[i] = b;  
  250.       i++;  
  251.       b = parent_node[b];  
  252.       if (b == vocab_size * 2 - 2) break;  
  253.     }  
  254.     vocab[a].codelen = i;  
  255.     vocab[a].point[0] = vocab_size - 2;  
  256.     for (b = 0; b < i; b++) {  //将每个词对应的huffman路径写到point里。  
  257.       vocab[a].code[i - b - 1] = code[b];  
  258.       vocab[a].point[i - b] = point[b] - vocab_size;  
  259.     }  
  260.   }  
  261.   free(count);  
  262.   free(binary);  
  263.   free(parent_node);  
  264. }  
  265.   
  266. //这个函数主要是把训练集中的词读入vocab;  
  267. //如果词量超出了最大限制,则根据word出现的次数reduce掉次数少的词。  
  268. //读完后整理vocab,将出现次数未达到最小次数的词丢掉。  
  269. void LearnVocabFromTrainFile() {  
  270.   char word[MAX_STRING];  
  271.   FILE *fin;  
  272.   long long a, i;  
  273.   for (a = 0; a < vocab_hash_size; a++) vocab_hash[a] = -1;  
  274.   fin = fopen(train_file, "rb");  
  275.   if (fin == NULL) {  
  276.     printf("ERROR: training data file not found!\n");  
  277.     exit(1);  
  278.   }  
  279.   vocab_size = 0;  
  280.   AddWordToVocab((char *)"</s>");  
  281.   while (1) {  
  282.     ReadWord(word, fin);  
  283.     if (feof(fin)) break;  
  284.     train_words++;  
  285.     if ((debug_mode > 1) && (train_words % 100000 == 0)) {  
  286.       printf("%lldK%c", train_words / 1000, 13);  
  287.       fflush(stdout);  
  288.     }  
  289.     i = SearchVocab(word);  
  290.     if (i == -1) {  
  291.       a = AddWordToVocab(word);  
  292.       vocab[a].cn = 1;  
  293.     } else vocab[i].cn++;  
  294.     if (vocab_size > vocab_hash_size * 0.7) ReduceVocab();  //保证vocab里面的词汇量小于21M。  
  295.   }  
  296.   SortVocab();  
  297.   if (debug_mode > 0) {  
  298.     printf("Vocab size: %lld\n", vocab_size);  
  299.     printf("Words in train file: %lld\n", train_words);  
  300.   }  
  301.   file_size = ftell(fin);  
  302.   fclose(fin);  
  303. }  
  304.   
  305. void SaveVocab() {  //将Vocab中的word和cn存入输出文件  
  306.   long long i;  
  307.   FILE *fo = fopen(save_vocab_file, "wb");  
  308.   for (i = 0; i < vocab_size; i++) fprintf(fo, "%s %lld\n", vocab[i].word, vocab[i].cn);  
  309.   fclose(fo);  
  310. }  
  311.   
  312. void ReadVocab() {  
  313.   long long a, i = 0;  
  314.   char c;  
  315.   char word[MAX_STRING];  
  316.   FILE *fin = fopen(read_vocab_file, "rb");  
  317.   if (fin == NULL) {  
  318.     printf("Vocabulary file not found\n");  
  319.     exit(1);  
  320.   }  
  321.   for (a = 0; a < vocab_hash_size; a++) vocab_hash[a] = -1;  
  322.   vocab_size = 0;  
  323.   while (1) {  
  324.     ReadWord(word, fin);  
  325.     if (feof(fin)) break;  
  326.     a = AddWordToVocab(word);  
  327.     fscanf(fin, "%lld%c", &vocab[a].cn, &c);  
  328.     i++;  
  329.   }  
  330.   SortVocab();  
  331.   if (debug_mode > 0) {  
  332.     printf("Vocab size: %lld\n", vocab_size);  
  333.     printf("Words in train file: %lld\n", train_words);  
  334.   }  
  335.   fin = fopen(train_file, "rb");  
  336.   if (fin == NULL) {  
  337.     printf("ERROR: training data file not found!\n");  
  338.     exit(1);  
  339.   }  
  340.   fseek(fin, 0, SEEK_END);  
  341.   file_size = ftell(fin);  
  342.   fclose(fin);  
  343. }  
  344.   
  345. void InitNet() {  
  346.   long long a, b;  
  347.   unsigned long long next_random = 1;  
  348.   a = posix_memalign((void **)&syn0, 128, (long long)vocab_size * layer1_size * sizeof(real));//分配空间?  
  349.   if (syn0 == NULL) {printf("Memory allocation failed\n"); exit(1);}  
  350.   if (hs) {  
  351.     a = posix_memalign((void **)&syn1, 128, (long long)vocab_size * layer1_size * sizeof(real));  
  352.     if (syn1 == NULL) {printf("Memory allocation failed\n"); exit(1);}  
  353.     for (a = 0; a < vocab_size; a++) for (b = 0; b < layer1_size; b++)  //syn1[vocab_size*layer1_size]  
  354.      syn1[a * layer1_size + b] = 0;   
  355.   }  
  356.   if (negative>0) {  //有负样本的话  
  357.     a = posix_memalign((void **)&syn1neg, 128, (long long)vocab_size * layer1_size * sizeof(real));  
  358.     if (syn1neg == NULL) {printf("Memory allocation failed\n"); exit(1);}  
  359.     for (a = 0; a < vocab_size; a++) for (b = 0; b < layer1_size; b++)  
  360.      syn1neg[a * layer1_size + b] = 0;  
  361.   }  
  362.   for (a = 0; a < vocab_size; a++) for (b = 0; b < layer1_size; b++) {  
  363.     next_random = next_random * (unsigned long long)25214903917 + 11;  //等同于rand()  
  364.     syn0[a * layer1_size + b] = (((next_random & 0xFFFF) / (real)65536) - 0.5) / layer1_size;  //给syn0赋初始值,。  
  365.   }  
  366.   CreateBinaryTree(); //生成二叉树。  
  367. }  
  368.   
  369. void *TrainModelThread(void *id) {  
  370.   long long a, b, d, cw, word, last_word, sentence_length = 0, sentence_position = 0;  
  371.   long long word_count = 0, last_word_count = 0, sen[MAX_SENTENCE_LENGTH + 1];  
  372.   long long l1, l2, c, target, label, local_iter = iter;  
  373.   unsigned long long next_random = (long long)id;  
  374.   real f, g;  
  375.   clock_t now;  
  376.   real *neu1 = (real *)calloc(layer1_size, sizeof(real));  //只有输入层需要,隐含层是一个累加和,输出层存入huffman树中。  
  377.   real *neu1e = (real *)calloc(layer1_size, sizeof(real));  
  378.   FILE *fi = fopen(train_file, "rb");  
  379.   fseek(fi, file_size / (long long)num_threads * (long long)id, SEEK_SET);  
  380.   while (1) {  
  381.     if (word_count - last_word_count > 10000) {  
  382.       word_count_actual += word_count - last_word_count;  
  383.       last_word_count = word_count;  
  384.       if ((debug_mode > 1)) {  
  385.         now=clock();  
  386.         printf("%cAlpha: %f  Progress: %.2f%%  Words/thread/sec: %.2fk  ", 13, alpha,  
  387.          word_count_actual / (real)(iter * train_words + 1) * 100,  
  388.          word_count_actual / ((real)(now - start + 1) / (real)CLOCKS_PER_SEC * 1000));  
  389.         fflush(stdout);  
  390.       }  
  391.       alpha = starting_alpha * (1 - word_count_actual / (real)(iter * train_words + 1));  
  392.       if (alpha < starting_alpha * 0.0001) alpha = starting_alpha * 0.0001;  
  393.     }  
  394.     //读入句子。  
  395.     if (sentence_length == 0) {  //句首?是从句首开始读。  
  396.       while (1) {  
  397.         word = ReadWordIndex(fi);  //读fi中的词,返回其在vocab中的位置。  
  398.         if (feof(fi)) break;  
  399.         if (word == -1) continue;  
  400.         word_count++;  
  401.         if (word == 0) break;  
  402.         // The subsampling randomly discards frequent words while keeping the ranking same  
  403.         if (sample > 0) {  
  404.           //  
  405.           real ran = (sqrt(vocab[word].cn / (sample * train_words)) + 1) * (sample * train_words) / vocab[word].cn;  
  406.           next_random = next_random * (unsigned long long)25214903917 + 11;  
  407.           if (ran < (next_random & 0xFFFF) / (real)65536) continue;  //(next_random & 0xFFFF) / (real)65536 应该是个小于1的值。也就是说ran 应该大于1.  
  408.         }  
  409.         sen[sentence_length] = word;  
  410.         sentence_length++;  
  411.         if (sentence_length >= MAX_SENTENCE_LENGTH) break;  
  412.       }  
  413.       sentence_position = 0;  
  414.     }  
  415.     if (feof(fi) || (word_count > train_words / num_threads)) {  //train_file被读到末尾了,或者一个线程已经完成了它的份额。  
  416.       word_count_actual += word_count - last_word_count;  
  417.       local_iter--;  
  418.       if (local_iter == 0) break;  
  419.       word_count = 0;  
  420.       last_word_count = 0;  
  421.       sentence_length = 0;  
  422.       //移动文件流读写位置,从距文件开头file_size / (long long)num_threads * (long long)id 位移量为新的读写位置  
  423.       fseek(fi, file_size / (long long)num_threads * (long long)id, SEEK_SET);  
  424.       continue;  
  425.     }  
  426.     word = sen[sentence_position];  
  427.     if (word == -1) continue;  
  428.     for (c = 0; c < layer1_size; c++) neu1[c] = 0;  
  429.     for (c = 0; c < layer1_size; c++) neu1e[c] = 0;  
  430.     next_random = next_random * (unsigned long long)25214903917 + 11;  
  431.     b = next_random % window;  //b取1-4之间的随机值。  
  432.     if (cbow) {  //train the cbow architecture  
  433.       // in -> hidden  
  434.       cw = 0;  
  435.       for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  436.         c = sentence_position - window + a;  //给c赋值  
  437.         if (c < 0) continue;  
  438.         if (c >= sentence_length) continue;  
  439.         last_word = sen[c];  
  440.         if (last_word == -1) continue;  
  441.   
  442.         //累加词对应的向量。双重循环下来就是窗口额定数量的词每一维对应的向量累加。  
  443.         //累加后neu1的维度依然是layer1_size。  
  444.         //从输入层过度到隐含层。  
  445.         for (c = 0; c < layer1_size; c++) neu1[c] += syn0[c + last_word * layer1_size];     
  446.         cw++;  //进入隐含层的词个数。  
  447.       }  
  448.       if (cw) {  
  449.         for (c = 0; c < layer1_size; c++) neu1[c] /= cw;  //归一化处理。  
  450.         if (hs) for (d = 0; d < vocab[word].codelen; d++) {  //遍历huffman树叶子节点  
  451.           f = 0;  
  452.           l2 = vocab[word].point[d] * layer1_size;  
  453.           // Propagate hidden -> output  
  454.           for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1[c + l2];  //做内积  
  455.           if (f <= -MAX_EXP) continue//不在范围内的内积丢掉  
  456.           else if (f >= MAX_EXP) continue;  
  457.           else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];  //sigmod函数  
  458.           // 'g' is the gradient multiplied by the learning rate  
  459.           g = (1 - vocab[word].code[d] - f) * alpha; //计算梯度  
  460.           // Propagate errors output -> hidden  
  461.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];  //计算隐含层到输出层神经元的误差  
  462.           // Learn weights hidden -> output  
  463.           for (c = 0; c < layer1_size; c++) syn1[c + l2] += g * neu1[c];  //更新输出参数。  
  464.         }  
  465.         // NEGATIVE SAMPLING  
  466.         if (negative > 0) for (d = 0; d < negative + 1; d++) {  //有负样本,处理负样本  
  467.           if (d == 0) {  
  468.             target = word;  
  469.             label = 1;  //正样本  
  470.           } else {  
  471.             next_random = next_random * (unsigned long long)25214903917 + 11;  
  472.             target = table[(next_random >> 16) % table_size];  
  473.             if (target == 0) target = next_random % (vocab_size - 1) + 1;  
  474.             if (target == word) continue;  
  475.             label = 0;  //负样本  
  476.           }  
  477.           l2 = target * layer1_size;  
  478.           f = 0;    //以下和上面差不多。  
  479.           for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1neg[c + l2];  
  480.           if (f > MAX_EXP) g = (label - 1) * alpha;  
  481.           else if (f < -MAX_EXP) g = (label - 0) * alpha;  
  482.           else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;     
  483.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];  
  484.           for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * neu1[c];  
  485.         }  
  486.         // hidden -> in  
  487.         for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  488.           c = sentence_position - window + a;  
  489.           if (c < 0) continue;  
  490.           if (c >= sentence_length) continue;  
  491.           last_word = sen[c];  
  492.           if (last_word == -1) continue;  
  493.           for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c];  //用误差更新向量(输入层参数),直接将误差叠加到输入向量上,这样好吗?  
  494.         }  
  495.       }  
  496.     } else {  //train skip-gram  
  497.       for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {  
  498.         c = sentence_position - window + a;  
  499.         if (c < 0) continue;  
  500.         if (c >= sentence_length) continue;  
  501.         last_word = sen[c];  
  502.         if (last_word == -1) continue;  
  503.         l1 = last_word * layer1_size;  //和cbw相比少了做输入向量累加。  
  504.         for (c = 0; c < layer1_size; c++) neu1e[c] = 0;  
  505.         // HIERARCHICAL SOFTMAX  
  506.         if (hs) for (d = 0; d < vocab[word].codelen; d++) {  
  507.           f = 0;  
  508.           l2 = vocab[word].point[d] * layer1_size;  
  509.           // Propagate hidden -> output  
  510.           for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1[c + l2];  //做内积  
  511.           if (f <= -MAX_EXP) continue;  
  512.           else if (f >= MAX_EXP) continue;  
  513.           else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];  
  514.           // 'g' is the gradient multiplied by the learning rate  
  515.           g = (1 - vocab[word].code[d] - f) * alpha;  
  516.           // Propagate errors output -> hidden  
  517.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];  
  518.           // Learn weights hidden -> output  
  519.           for (c = 0; c < layer1_size; c++) syn1[c + l2] += g * syn0[c + l1];  
  520.         }  
  521.         // NEGATIVE SAMPLING  
  522.         if (negative > 0) for (d = 0; d < negative + 1; d++) {  
  523.           if (d == 0) {  
  524.             target = word;  
  525.             label = 1;  
  526.           } else {  
  527.             next_random = next_random * (unsigned long long)25214903917 + 11;  
  528.             target = table[(next_random >> 16) % table_size];  
  529.             if (target == 0) target = next_random % (vocab_size - 1) + 1;  
  530.             if (target == word) continue;  
  531.             label = 0;  
  532.           }  
  533.           l2 = target * layer1_size;  
  534.           f = 0;  
  535.           for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2];  
  536.           if (f > MAX_EXP) g = (label - 1) * alpha;  
  537.           else if (f < -MAX_EXP) g = (label - 0) * alpha;  
  538.           else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;  
  539.           for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];  
  540.           for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1];  
  541.         }  
  542.         // Learn weights input -> hidden  
  543.         for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];  
  544.       }  
  545.     }  
  546.     sentence_position++;  
  547.     if (sentence_position >= sentence_length) {  
  548.       sentence_length = 0;  
  549.       continue;  
  550.     }  
  551.   }  
  552.   fclose(fi);  
  553.   free(neu1);  
  554.   free(neu1e);  
  555.   pthread_exit(NULL);  
  556. }  
  557.   
  558. void TrainModel() {  
  559.   long a, b, c, d;  
  560.   FILE *fo;  
  561.   pthread_t *pt = (pthread_t *)malloc(num_threads * sizeof(pthread_t));  //用于声明线程ID。  
  562.   printf("Starting training using file %s\n", train_file);  
  563.   starting_alpha = alpha;  
  564.   if (read_vocab_file[0] != 0) ReadVocab(); else LearnVocabFromTrainFile();  //读入词到vocab。  
  565.   if (save_vocab_file[0] != 0) SaveVocab();  
  566.   if (output_file[0] == 0) return;  
  567.   InitNet();  //初始化Net  
  568.   if (negative > 0) InitUnigramTable();  
  569.   start = clock();  
  570.   for (a = 0; a < num_threads; a++) pthread_create(&pt[a], NULL, TrainModelThread, (void *)a);  //pthread是线程,这里生成线程  
  571.   for (a = 0; a < num_threads; a++) pthread_join(pt[a], NULL); //让主线程等待子线程执行结束后,主线程再结束。这样,防止主线程很快执行完后,退出,上一行创建的子线程没有机会执行。  
  572.   fo = fopen(output_file, "wb");  
  573.   if (classes == 0) {  
  574.     // Save the word vectors  
  575.     fprintf(fo, "%lld %lld\n", vocab_size, layer1_size);  
  576.     for (a = 0; a < vocab_size; a++) {  
  577.       fprintf(fo, "%s ", vocab[a].word);  
  578.       if (binary) for (b = 0; b < layer1_size; b++) fwrite(&syn0[a * layer1_size + b], sizeof(real), 1, fo);  
  579.       else for (b = 0; b < layer1_size; b++) fprintf(fo, "%lf ", syn0[a * layer1_size + b]);  
  580.       fprintf(fo, "\n");  
  581.     }  
  582.   } else {  
  583.     // Run K-means on the word vectors  //K-Mean后续再看具体过程  
  584.     int clcn = classes, iter = 10, closeid;  
  585.     int *centcn = (int *)malloc(classes * sizeof(int));  
  586.     int *cl = (int *)calloc(vocab_size, sizeof(int));  
  587.     real closev, x;  
  588.     real *cent = (real *)calloc(classes * layer1_size, sizeof(real));  
  589.     for (a = 0; a < vocab_size; a++) cl[a] = a % clcn;  
  590.     for (a = 0; a < iter; a++) {  
  591.       for (b = 0; b < clcn * layer1_size; b++) cent[b] = 0;  
  592.       for (b = 0; b < clcn; b++) centcn[b] = 1;  
  593.       for (c = 0; c < vocab_size; c++) {  
  594.         for (d = 0; d < layer1_size; d++) cent[layer1_size * cl[c] + d] += syn0[c * layer1_size + d];  
  595.         centcn[cl[c]]++;  
  596.       }  
  597.       for (b = 0; b < clcn; b++) {  
  598.         closev = 0;  
  599.         for (c = 0; c < layer1_size; c++) {  
  600.           cent[layer1_size * b + c] /= centcn[b];  
  601.           closev += cent[layer1_size * b + c] * cent[layer1_size * b + c];  
  602.         }  
  603.         closev = sqrt(closev);  
  604.         for (c = 0; c < layer1_size; c++) cent[layer1_size * b + c] /= closev;  
  605.       }  
  606.       for (c = 0; c < vocab_size; c++) {  
  607.         closev = -10;  
  608.         closeid = 0;  
  609.         for (d = 0; d < clcn; d++) {  
  610.           x = 0;  
  611.           for (b = 0; b < layer1_size; b++) x += cent[layer1_size * d + b] * syn0[c * layer1_size + b];  
  612.           if (x > closev) {  
  613.             closev = x;  
  614.             closeid = d;  
  615.           }  
  616.         }  
  617.         cl[c] = closeid;  
  618.       }  
  619.     }  
  620.     // Save the K-means classes  
  621.     for (a = 0; a < vocab_size; a++) fprintf(fo, "%s %d\n", vocab[a].word, cl[a]);  
  622.     free(centcn);  
  623.     free(cent);  
  624.     free(cl);  
  625.   }  
  626.   fclose(fo);  
  627. }  
  628.   
  629. int ArgPos(char *str, int argc, char **argv) {  
  630.   int a;  
  631.   for (a = 1; a < argc; a++) if (!strcmp(str, argv[a])) {  
  632.     if (a == argc - 1) {  
  633.       printf("Argument missing for %s\n", str);  
  634.       exit(1);  
  635.     }  
  636.     return a;  
  637.   }  
  638.   return -1;  
  639. }  
  640.   
  641. int main(int argc, char **argv) {  
  642.   int i;  
  643.   if (argc == 1) {  
  644.     printf("WORD VECTOR estimation toolkit v 0.1c\n\n");  
  645.     printf("Options:\n");  
  646.     printf("Parameters for training:\n");  
  647.     printf("\t-train <file>\n");  
  648.     printf("\t\tUse text data from <file> to train the model\n");  
  649.     printf("\t-output <file>\n");  
  650.     printf("\t\tUse <file> to save the resulting word vectors / word clusters\n");  
  651.     printf("\t-size <int>\n");  
  652.     printf("\t\tSet size of word vectors; default is 100\n");  
  653.     printf("\t-window <int>\n");  
  654.     printf("\t\tSet max skip length between words; default is 5\n");  
  655.     printf("\t-sample <float>\n");  
  656.     printf("\t\tSet threshold for occurrence of words. Those that appear with higher frequency in the training data\n");  
  657.     printf("\t\twill be randomly down-sampled; default is 1e-3, useful range is (0, 1e-5)\n");  
  658.     printf("\t-hs <int>\n");  
  659.     printf("\t\tUse Hierarchical Softmax; default is 0 (not used)\n");  
  660.     printf("\t-negative <int>\n");  
  661.     printf("\t\tNumber of negative examples; default is 5, common values are 3 - 10 (0 = not used)\n");  
  662.     printf("\t-threads <int>\n");  
  663.     printf("\t\tUse <int> threads (default 12)\n");  
  664.     printf("\t-iter <int>\n");  
  665.     printf("\t\tRun more training iterations (default 5)\n");  
  666.     printf("\t-min-count <int>\n");  
  667.     printf("\t\tThis will discard words that appear less than <int> times; default is 5\n");  
  668.     printf("\t-alpha <float>\n");  
  669.     printf("\t\tSet the starting learning rate; default is 0.025 for skip-gram and 0.05 for CBOW\n");  
  670.     printf("\t-classes <int>\n");  
  671.     printf("\t\tOutput word classes rather than word vectors; default number of classes is 0 (vectors are written)\n");  
  672.     printf("\t-debug <int>\n");  
  673.     printf("\t\tSet the debug mode (default = 2 = more info during training)\n");  
  674.     printf("\t-binary <int>\n");  
  675.     printf("\t\tSave the resulting vectors in binary moded; default is 0 (off)\n");  
  676.     printf("\t-save-vocab <file>\n");  
  677.     printf("\t\tThe vocabulary will be saved to <file>\n");  
  678.     printf("\t-read-vocab <file>\n");  
  679.     printf("\t\tThe vocabulary will be read from <file>, not constructed from the training data\n");  
  680.     printf("\t-cbow <int>\n");  
  681.     printf("\t\tUse the continuous bag of words model; default is 1 (use 0 for skip-gram model)\n");  
  682.     printf("\nExamples:\n");  
  683.     printf("./word2vec -train data.txt -output vec.txt -size 200 -window 5 -sample 1e-4 -negative 5 -hs 0 -binary 0 -cbow 1 -iter 3\n\n");  
  684.     return 0;  
  685.   }  
  686.   output_file[0] = 0;  
  687.   save_vocab_file[0] = 0;  
  688.   read_vocab_file[0] = 0;  
  689.   if ((i = ArgPos((char *)"-size", argc, argv)) > 0) layer1_size = atoi(argv[i + 1]);  
  690.   if ((i = ArgPos((char *)"-train", argc, argv)) > 0) strcpy(train_file, argv[i + 1]);  
  691.   if ((i = ArgPos((char *)"-save-vocab", argc, argv)) > 0) strcpy(save_vocab_file, argv[i + 1]);  
  692.   if ((i = ArgPos((char *)"-read-vocab", argc, argv)) > 0) strcpy(read_vocab_file, argv[i + 1]);  
  693.   if ((i = ArgPos((char *)"-debug", argc, argv)) > 0) debug_mode = atoi(argv[i + 1]);  
  694.   if ((i = ArgPos((char *)"-binary", argc, argv)) > 0) binary = atoi(argv[i + 1]);  
  695.   if ((i = ArgPos((char *)"-cbow", argc, argv)) > 0) cbow = atoi(argv[i + 1]);  
  696.   if (cbow) alpha = 0.05;  
  697.   if ((i = ArgPos((char *)"-alpha", argc, argv)) > 0) alpha = atof(argv[i + 1]);  
  698.   if ((i = ArgPos((char *)"-output", argc, argv)) > 0) strcpy(output_file, argv[i + 1]);  
  699.   if ((i = ArgPos((char *)"-window", argc, argv)) > 0) window = atoi(argv[i + 1]);  //窗口大小,也就是n  
  700.   if ((i = ArgPos((char *)"-sample", argc, argv)) > 0) sample = atof(argv[i + 1]);  
  701.   if ((i = ArgPos((char *)"-hs", argc, argv)) > 0) hs = atoi(argv[i + 1]);  
  702.   if ((i = ArgPos((char *)"-negative", argc, argv)) > 0) negative = atoi(argv[i + 1]);  
  703.   if ((i = ArgPos((char *)"-threads", argc, argv)) > 0) num_threads = atoi(argv[i + 1]);   //线程数  
  704.   if ((i = ArgPos((char *)"-iter", argc, argv)) > 0) iter = atoi(argv[i + 1]);   //迭代  
  705.   if ((i = ArgPos((char *)"-min-count", argc, argv)) > 0) min_count = atoi(argv[i + 1]);   //设定词最少出现次数。  
  706.   if ((i = ArgPos((char *)"-classes", argc, argv)) > 0) classes = atoi(argv[i + 1]);  
  707.   vocab = (struct vocab_word *)calloc(vocab_max_size, sizeof(struct vocab_word));  
  708.   vocab_hash = (int *)calloc(vocab_hash_size, sizeof(int));  
  709.   expTable = (real *)malloc((EXP_TABLE_SIZE + 1) * sizeof(real));  
  710.   for (i = 0; i < EXP_TABLE_SIZE; i++) {  
  711.     expTable[i] = exp((i / (real)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() table  
  712.     expTable[i] = expTable[i] / (expTable[i] + 1);                   // Precompute f(x) = x / (x + 1)  
  713.   }  
  714.   TrainModel();  
  715.   return 0;  
  716. }  

0 0
原创粉丝点击