一、Word2Vec程序解析

word2vec主要的层次结构

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:

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

TrainModel:

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

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

第四部分  

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

word2vec源码解读相关推荐

  1. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

  2. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  3. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  4. nodeJS之eventproxy源码解读

    1.源码缩影 !(function (name, definition) { var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD ...

  5. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  6. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

  7. Feflow 源码解读

    Feflow 源码解读 Feflow(Front-end flow)是腾讯IVWEB团队的前端工程化解决方案,致力于改善多类型项目的开发流程中的规范和非业务相关的问题,可以让开发者将绝大部分精力集中在 ...

  8. spring-session源码解读 sesion

    2019独角兽企业重金招聘Python工程师标准>>> spring-session源码解读 sesion 博客分类: java spring 摘要: session通用策略 Ses ...

  9. 前端日报-20160527-underscore 源码解读

    underscore 源码解读 API文档浏览器 JavaScript 中加号操作符细节 抛弃 jQuery,拥抱原生 JS 从 0 开始学习 GitHub 系列之「加入 GitHub」 js实现克隆 ...

最新文章

  1. 使用 SQL Server Mobile 创建移动应用程序
  2. 动视暴雪员工大罢工:因CEO被曝多次包庇高管性侵下属、本人曾性骚扰助理
  3. PHP的echo和print小谈
  4. Rancher upgrade webhook之CI/CD
  5. spring中使用注解代替xml配置
  6. SAP Spartacus 的页面设计思路
  7. 瞒不住了,难怪.NET进大厂这么难!
  8. 配置apache支持PHP(win7)
  9. Codeforces Round #368 (Div. 2) C. Pythagorean Triples
  10. 小说网jsp源码_基于jsp+mysql的JSP小说网
  11. 表单的提交方式POST和GET
  12. 005-垃圾收集算法
  13. 推荐一款源代码统计分析、开发工作量估算、测试缺陷预测的开发工具 —— 代码统计分析工具(SourceCounter)
  14. as3 greensock_GreenSock 3 Web动画:了解GSAP的新功能
  15. 中国移动彩信业务资料集合
  16. 好东西都在这里,不点下看看吗(博客目录导航,持续更新中...)
  17. SMAA算法详解 - SMAAEdgeDetectionVS
  18. PWM电流源型逆变器
  19. JavaScript:原生JS实现Facebook实时消息抓捕
  20. phpyun人才系统 短信配置教程

热门文章

  1. python——温度换算(以字母结尾)
  2. Python 之圆周率 π 的计算
  3. Ncut源码编译错误的解决方法
  4. python调用百度AI接口实现人像分割
  5. android+模拟人体扫描,人体模型模拟器app
  6. 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.2 藝術畫 Python 製作生成式藝術略覽
  7. 人工智能资料整理总结
  8. python 酷炫效果_六种酷炫Python运行进度条效果的实现代码
  9. “猪”事大吉 | 为什么猪是最后一个生肖?
  10. Matlab 斜率和曲率,曲率_与闪电共舞_新浪博客