机器学习算法实现解析——word2vec源码解析
在wrod2vec工具中,有如下的幾個比較重要的概念:
- CBOW
- Skip-Gram
- Hierarchical Softmax
- Negative Sampling
其中CBOW和Skip-Gram是word2vec工具中使用到的兩種不同的語言模型,而Hierarchical Softmax和Negative Sampling是對以上的兩種模型的具體的優化方法。
在word2vec工具中,主要的工作包括:
- 預處理。即變量的聲明,全局變量的定義等;
- 構建詞庫。即包含文本的處理,以及是否需要有指定詞庫等;
- 初始化網絡結構。即包含CBOW模型和Skip-Gram模型的參數初始化,Huffman編碼的生成等;
- 多線程模型訓練。即利用Hierarchical Softmax或者Negative Sampling方法對網絡中的參數進行求解;
- 最終結果的處理。即是否保存和以何種形式保存。
對于以上的過程,可以由下圖表示:
在接下來的內容中,將針對以上的五個部分,詳細分析下在源代碼中的實現技巧,以及簡單介紹我在讀代碼的過程中對部分代碼的一些思考。
1. 預處理
在預處理部分,對word2vec需要使用的參數進行初始化,在word2vec中是利用傳入的方式對參數進行初始化的。
在預處理部分,實現了sigmoid函數值的近似計算。在利用神經網絡模型對樣本進行預測的過程中,需要對其進行預測,此時,需要使用到sigmoid函數,sigmoid函數的具體形式為:
σ(x)=11+e?x=ex1+ex\sigma \left ( x \right )=\frac{1}{1+e^{-x}}=\frac{e^x}{1+e^x}σ(x)=1+e?x1?=1+exex?
如果每一次都請求計算sigmoid值,對性能將會有一定的影響,當sigmoid的值對精度的要求并不是非常嚴格時,可以采用近似計算。在word2vec中,將區間[?6,6]\left [ -6,6 \right ][?6,6](設置的參數MAX_EXP為6)等距離劃分成EXP_TABLE_SIZE等份,并將每個區間中的sigmoid值計算好存入到數組expTable中,需要使用時,直接從數組中查找。計算sigmoid值的代碼如下所示:
expTable = (real *)malloc((EXP_TABLE_SIZE + 1) * sizeof(real));// 申請EXP_TABLE_SIZE+1個空間// 計算sigmoid值 for (i = 0; i < EXP_TABLE_SIZE; i++) {expTable[i] = exp((i / (real)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() tableexpTable[i] = expTable[i] / (expTable[i] + 1); // Precompute f(x) = x / (x + 1) }注意:在上述代碼中,作者使用的是小于EXP_TABLE_SIZE,實際的區間是[?6,6)\left [ -6,6 \right )[?6,6)。
2. 構建詞庫
在word2vec源碼中,提供了兩種構建詞庫的方法,分別為:
- 指定詞庫:ReadVocab()方法
- 從詞的文本構建詞庫:LearnVocabFromTrainFile()方法
2.1. 構建詞庫的過程
在這里,我們以從詞的文本構建詞庫為例。構建詞庫的過程如下所示:
在這部分中,最主要的工作是對文本進行處理,包括低頻詞的處理,hash表的處理等等。首先,會在詞庫中增加一個“< /s>”的詞,同時,在讀取文本的過程中,將換行符“\n”也表示成該該詞,如:
if (ch == '\n') {strcpy(word, (char *)"</s>");// 換行符用</s>表示return; }在循環的過程中,不斷去讀取文件中的每一個詞,并在詞庫中進行查找,若存在該詞,則該詞的詞頻+1,否則,在詞庫中增加該詞。在詞庫中,是通過哈希表的形式存儲的。最終,會過濾掉一些低頻詞。
在得到最終的詞庫之前,還需根據詞庫中的詞頻對詞庫中的詞進行排序。
2.2. 對詞的哈希處理
在存儲詞的過程中,同時保留這兩個數組:
- 存儲詞的vocab
- 存儲詞的hash的vocab_hash
其中,在vocab中,存儲的是詞對應的結構體:
// 詞的結構體 struct vocab_word {long long cn; // 出現的次數int *point; // 從根結點到葉子節點的路徑char *word, *code, codelen;// 分別對應著詞,Huffman編碼,編碼長度 };在vocab_hash中存儲的是詞在詞庫中的Index。
在對詞的處理過程中,主要包括:
- 計算詞的hash值:
- 檢索詞是否存在。如不存在則返回-1,否則,返回該詞在詞庫中的索引:
在這個過程中,使用到了線性探測的開放定址法處理沖突,開放定址法就是一旦發生沖突,就去尋找下一個空的散列地址。
- 不存在,則插入新詞。
在這個過程中,除了需要將詞增加到詞庫中,好需要計算該詞的hash值,并將vocab_hash數組中的值標記為索引。
2.3. 對低頻詞的處理
在循環讀取每一個詞的過程中,當出現“vocab_size > vocab_hash_size * 0.7”時,需要對低頻詞進行處理。其中,vocab_size表示的是目前詞庫中詞的個數,vocab_hash_size表示的是初始設定的hash表的大小。
在處理低頻詞的過程中,通過參數“min_reduce”來控制,若詞出現的次數小于等于該值時,則從詞庫中刪除該詞。
在刪除了低頻詞后,需要重新對詞庫中的詞進行hash值的計算。
2.4. 根據詞頻對詞庫中的詞排序
基于以上的過程,程序已經將詞從文件中提取出來,并存入到指定的詞庫中(vocab數組),接下來,需要根據每一個詞的詞頻對詞庫中的詞按照詞頻從大到小排序,其基本過程在函數SortVocab中,排序過程為:
qsort(&vocab[1], vocab_size - 1, sizeof(struct vocab_word), VocabCompare);保持字符“< \s>”在最開始的位置。排序后,根據“min_count”對低頻詞進行處理,與上述一樣,再對剩下的詞重新計算hash值。
至此,整個對詞的處理過程就已經結束了。加下來,將是對網絡結構的處理和詞向量的訓練。
3. 初始化網絡結構
有了以上的對詞的處理,就已經處理好了所有的訓練樣本,此時,便可以開始網絡結構的初始化和接下來的網絡訓練。網絡的初始化的過程在InitNet()函數中完成。
3.1. 初始化網絡參數
在初始化的過程中,主要的參數包括詞向量的初始化和映射層到輸出層的權重的初始化,如下圖所示:
在初始化的過程中,映射層到輸出層的權重都初始化為000,而對于每一個詞向量的初始化,作者的初始化方法如下代碼所示:
for (a = 0; a < vocab_size; a++) for (b = 0; b < layer1_size; b++) {next_random = next_random * (unsigned long long)25214903917 + 11;// 1、與:相當于將數控制在一定范圍內// 2、0xFFFF:65536// 3、/65536:[0,1]之間syn0[a * layer1_size + b] = (((next_random & 0xFFFF) / (real)65536) - 0.5) / layer1_size;// 初始化詞向量 }首先,生成一個很大的next_random的數,通過與“0xFFFF”進行與運算截斷,再除以65536得到[0,1]\left [ 0,1 \right ][0,1]之間的數,最終,得到的初始化的向量的范圍為:[?0.5m,0.5m]\left [ -\frac{0.5}{m},\frac{0.5}{m}\right ][?m0.5?,m0.5?],其中,mmm為詞向量的長度。
3.2. Huffman樹的構建
在層次Softmax中需要使用到Huffman樹以及Huffman編碼,因此,在網絡結構的初始化過程中,也需要初始化Huffman樹。在生成Huffman樹的過程中,首先定義了333個長度為vocab_size*2+1的數組:
long long *count = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long)); long long *binary = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long)); long long *parent_node = (long long *)calloc(vocab_size * 2 + 1, sizeof(long long));其中,count數組中前vocab_size存儲的是每一個詞的對應的詞頻,后面初始化的是很大的數,已知詞庫中的詞是按照降序排列的,因此,構建Huffman樹的過程如下所示(對于Huffman樹的原理,可以參見博文“數據結構和算法——Huffman樹和Huffman編碼”):
首先,設置兩個指針pos1和pos2,分別指向最后一個詞和最后一個詞的后一位,從兩個指針所指的數中選擇出最小的值,記為min1i,如pos1所指的值最小,此時,將pos1左移,再比較pos1和pos2所指的數,選擇出最小的值,記為min2i,將他們的和存儲到pos2所指的位置。并將此時pos2所指的位置設置為min1i和min2i的父節點,同時,記min2i所指的位置的編碼為1,如下代碼所示:
// 設置父節點 parent_node[min1i] = vocab_size + a; parent_node[min2i] = vocab_size + a; binary[min2i] = 1;// 設置一個子樹的編碼為1構建好Huffman樹后,此時,需要根據構建好的Huffman樹生成對應節點的Huffman編碼。假設,上述的數據生成的最終的Huffman樹為:
此時,count數組,binary數組和parent_node數組分別為:
在生成Huffman編碼的過程中,針對每一個詞(詞都在葉子節點上),從葉子節點開始,將編碼存入到code數組中,如對于上圖中的“R”節點來說,其code數組為{1,0},再對其反轉便是Huffman編碼:
vocab[a].codelen = i;// 詞的編碼長度 vocab[a].point[0] = vocab_size - 2; for (b = 0; b < i; b++) {vocab[a].code[i - b - 1] = code[b];// 編碼的反轉vocab[a].point[i - b] = point[b] - vocab_size;// 記錄的是從根結點到葉子節點的路徑 }注意:這里的Huffman樹的構建和Huffman編碼的生成過程寫得比較精簡。
3.3. 負樣本選中表的初始化
如果是采用負采樣的方法,此時還需要初始化每個詞被選中的概率。在所有的詞構成的詞典中,每一個詞出現的頻率有高有低,我們希望,對于那些高頻的詞,被選中成為負樣本的概率要大點,同時,對于那些出現頻率比較低的詞,我們希望其被選中成為負樣本的頻率低點。這個原理于“輪盤賭”的策略一致(詳細可以參見“優化算法——遺傳算法”)。在程序中,實現這部分功能的代碼為:
// 生成負采樣的概率表 void InitUnigramTable() {int a, i;double train_words_pow = 0;double d1, power = 0.75;table = (int *)malloc(table_size * sizeof(int));// int --> intfor (a = 0; a < vocab_size; a++) train_words_pow += pow(vocab[a].cn, power);// 類似輪盤賭生成每個詞的概率i = 0;d1 = pow(vocab[i].cn, power) / train_words_pow;for (a = 0; a < table_size; a++) {table[a] = i;if (a / (double)table_size > d1) {i++;d1 += pow(vocab[i].cn, power) / train_words_pow;}if (i >= vocab_size) i = vocab_size - 1;} }在實現的過程中,沒有直接使用每一個詞的頻率,而是使用了詞的0.750.750.75次方。
4、多線程模型訓練
以上的各個部分是為訓練詞向量做準備,即準備訓練數據,構建訓練模型。在上述的初始化完成后,接下來就是根據不同的方法對模型進行訓練,在實現的過程中,作者使用了多線程的方法對其進行訓練。
4.1、多線程的處理
為了能夠對文本進行加速訓練,在實現的過程中,作者使用了多線程的方法,并對每一個線程上分配指定大小的文件:
// 利用多線程對訓練文件劃分,每個線程訓練一部分的數據 fseek(fi, file_size / (long long)num_threads * (long long)id, SEEK_SET);注意:這邊的多線程分割方式并不能保證每一個線程分到的文件是互斥的。對于其中的原因,可以參見“Linux C 編程——多線程”。
這個過程可以通過下圖簡單的描述:
在實現多線程的過程中,作者并沒有加鎖的操作,而是對模型參數和詞向量的修改可以任意執行,這一點類似于基于隨機梯度的方法,訓練的過程與訓練樣本的訓練是沒有關系的,這樣可以大大加快對詞向量的訓練。拋開多線程的部分,在每一個線程內執行的是對模型和詞向量的訓練。
作者在實現的過程中,主要實現了兩個模型,即CBOW模型和Skip-gram模型,在每個模型中,又分別使用到了兩種不同的訓練方法,即層次Softmax和Negative Sampling方法。
對于CBOW模型和Skip-gram模型的理解,首先必須知道統計語言模型(Statistic Language Model)。
在統計語言模型中的核心內容是:計算一組詞語能夠成為一個句子的概率。
為了能夠求解其中的參數,一大批參數求解的方法被提出,在其中,就有word2vec中要使用的神經概率語言模型。具體的神經概率語言模型可以參見“”。
4.2. CBOW模型
CBOW模型和Skip-gram模型是神經概率語言模型的兩種變形形式,其中,在CBOW模型中包含三層,即輸入層,映射層和輸出層。對于CBOW模型,如下圖所示:
在CBOW模型中,通過詞wtw_twt?的前后詞wt?2w_{t-2}wt?2?,wt?1w_{t-1}wt?1?,wt+1w_{t+1}wt+1?和wt+2w_{t+2}wt+2?來預測當前詞wtw_twt?。此處的窗口的大小window為2。
4.3.1. 從輸入層到映射層
首先找到每個詞對應的詞向量,并將這些詞的詞向量相加,程序代碼如下所示:
// in -> hidden // 輸入層到映射層 cw = 0; for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {c = sentence_position - window + a;// sentence_position表示的是當前的位置// 判斷c是否越界if (c < 0) continue;if (c >= sentence_length) continue;last_word = sen[c];// 找到c對應的索引if (last_word == -1) continue;for (c = 0; c < layer1_size; c++) neu1[c] += syn0[c + last_word * layer1_size];// 累加cw++; }當累加完窗口內的所有的詞向量的之后,存儲在映射層neu1中,并取平均,程序代碼如下所示:
for (c = 0; c < layer1_size; c++) neu1[c] /= cw;// 計算均值當取得了映射層的結果后,此時就需要使用Hierarchical Softmax或者Negative Sampling對模型進行訓練。
4.3.2. Hierarchical Softmax
Hierarchical Softmax是word2vec中用于提高性能的一項關鍵的技術。由Hierarchical Softmax的原理可知,對于詞w,其對數似然函數為:
L(w)=∑j∈point{(1?djw)?log[σ(XwTθjw)]+djw?log[1?σ(XwTθjw)]}L\left ( w \right )=\sum _{j\in point}\left \{ \left ( 1-d_j^w \right )\cdot log\left [ \sigma \left ( X_w^T\theta _j^w \right ) \right ]+d_j^w\cdot log\left [ 1-\sigma \left ( X_w^T\theta _j^w \right ) \right ] \right \}L(w)=j∈point∑?{(1?djw?)?log[σ(XwT?θjw?)]+djw??log[1?σ(XwT?θjw?)]}
其中,jjj表示的詞w對應的Huffman編碼中的每一個編碼的下標,djwd_j^wdjw?表示的是第jjj個Huffman編碼,且djw∈{0,1}d_j^w\in \left \{ 0,1 \right \}djw?∈{0,1},這是一個求和的過程,因此,對于Huffman編碼中的每一個編碼,上述的對數似然函數為:
L(w,j)=(1?djw)?log[σ(XwTθjw)]+djw?log[1?σ(XwTθjw)]L\left ( w,j \right )=\left ( 1-d_j^w \right )\cdot log\left [ \sigma \left ( X_w^T\theta _j^w \right ) \right ]+d_j^w\cdot log\left [ 1-\sigma \left ( X_w^T\theta _j^w \right ) \right ]L(w,j)=(1?djw?)?log[σ(XwT?θjw?)]+djw??log[1?σ(XwT?θjw?)]
在此,變量為XwX_wXw?和θjw\theta _j^wθjw?,此時,分別對XwX_wXw?和θjw\theta _j^wθjw?求偏導數:
?L(w,j)?θjw=[1?djw?σ(XwTθjw)]Xw\frac{\partial L\left ( w,j \right )}{\partial \theta _j^w}=\left [ 1-d_j^w-\sigma \left ( X_w^T\theta _j^w \right ) \right ]X_w?θjw??L(w,j)?=[1?djw??σ(XwT?θjw?)]Xw?
?L(w,j)?Xw=[1?djw?σ(XwTθjw)]θjw\frac{\partial L\left ( w,j \right )}{\partial X_w}=\left [ 1-d_j^w-\sigma \left ( X_w^T\theta _j^w \right ) \right ]\theta _j^w?Xw??L(w,j)?=[1?djw??σ(XwT?θjw?)]θjw?
因此,對于θjw\theta _j^wθjw?的更新公式為:
θjw=θjw+η[1?djw?σ(XwTθjw)]Xw\theta _j^w=\theta _j^w+\eta \left [ 1-d_j^w-\sigma \left ( X_w^T\theta _j^w \right ) \right ]X_wθjw?=θjw?+η[1?djw??σ(XwT?θjw?)]Xw?
在word2vec源碼中,為了能夠加快計算,作者在開始的時候存儲了一份Sigmoid的值,因此,對于$\sigma \left ( X_w^T\theta _j^w \right ) $需要從expTable中查詢到對應的值。
for (d = 0; d < vocab[word].codelen; d++) {// word為當前詞// 計算輸出層的輸出f = 0;l2 = vocab[word].point[d] * layer1_size;// 找到第d個詞對應的權重// Propagate hidden -> outputfor (c = 0; c < layer1_size; c++) f += neu1[c] * syn1[c + l2];// 映射層到輸出層if (f <= -MAX_EXP) continue;else if (f >= MAX_EXP) continue;else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];// Sigmoid結果// 'g' is the gradient multiplied by the learning rateg = (1 - vocab[word].code[d] - f) * alpha;// Propagate errors output -> hiddenfor (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];// 修改映射后的結果// Learn weights hidden -> outputfor (c = 0; c < layer1_size; c++) syn1[c + l2] += g * neu1[c];// 修改映射層到輸出層之間的權重 }對于窗口內的詞的向量的更新,則是利用窗口內的所有詞的梯度之和∑?L(w,j)?Xw\sum \frac{\partial L\left ( w,j \right )}{\partial X_w}∑?Xw??L(w,j)?來更新,如程序代碼所示:
// hidden -> in // 以上是從映射層到輸出層的修改,現在返回修改每一個詞向量 for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {c = sentence_position - window + a;if (c < 0) continue;if (c >= sentence_length) continue;last_word = sen[c];if (last_word == -1) continue;// 利用窗口內的所有詞向量的梯度之和來更新for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c]; }4.3.3. Negative Sampling
與Hierarchical Softmax一致,Negative Sampling也是一種加速計算的方法,在Negative Sampling方法中使用的是隨機的負采樣,在CBOW模型中,已知詞www的上下文,需要預測詞www,對于給定的上下文,詞www即為正樣本,其他的樣本為負樣本,此時我們需要根據詞頻從剩下的詞中挑選出最合適的負樣本,實現的代碼如下所示:
// 標記target和label if (d == 0) {// 正樣本target = word;label = 1; } else {// 選擇出負樣本next_random = next_random * (unsigned long long)25214903917 + 11;target = table[(next_random >> 16) % table_size];// 從table表中選擇出負樣本// 重新選擇if (target == 0) target = next_random % (vocab_size - 1) + 1;if (target == word) continue;label = 0; }當選擇出了正負樣本,此時的損失函數為:
l(w)=σ(XwTθw)?∏u∈NEG(w)[1?σ(XwTθu)]l\left ( w \right )=\sigma \left ( X_w^T\theta ^w \right )\cdot \prod _{u\in {NEG\left ( w \right )}}\left [ 1- \sigma \left ( X_w^T\theta ^u \right )\right ]l(w)=σ(XwT?θw)?u∈NEG(w)∏?[1?σ(XwT?θu)]
其對數似然函數為:
L=logl(w)=log∏u∈w∪NEG(w){[σ(XwTθu)]Lw(u)?[1?σ(XwTθu)]1?Lw(u)}L=log\; l\left ( w \right )=log\prod _{u\in {{w}\cup NEG\left ( w \right )}}\left \{ \left [ \sigma \left ( X_w^T\theta ^u \right ) \right ]^{L^w\left ( u \right )}\cdot \left [ 1- \sigma \left ( X_w^T\theta ^u \right )\right ]^{1-L^w\left ( u \right )} \right \}L=logl(w)=logu∈w∪NEG(w)∏?{[σ(XwT?θu)]Lw(u)?[1?σ(XwT?θu)]1?Lw(u)}
即為:
∑u∈w∪NEG(w){Lw(u)?log[σ(XwTθu)]+[1?Lw(u)]?log[1?σ(XwTθu)]}\sum _{u\in {{w}\cup NEG\left ( w \right )}}\left \{ {L^w\left ( u \right )}\cdot log\left [ \sigma \left ( X_w^T\theta ^u \right ) \right ]+\left [ 1-L^w\left ( u \right ) \right ]\cdot log \left [ 1- \sigma \left ( X_w^T\theta ^u \right )\right ] \right \}u∈w∪NEG(w)∑?{Lw(u)?log[σ(XwT?θu)]+[1?Lw(u)]?log[1?σ(XwT?θu)]}
取:
L(w,u)=Lw(u)?log[σ(XwTθu)]+[1?Lw(u)]?log[1?σ(XwTθu)]L\left ( w,u \right )={L^w\left ( u \right )}\cdot log\left [ \sigma \left ( X_w^T\theta ^u \right ) \right ]+\left [ 1-L^w\left ( u \right ) \right ]\cdot log \left [ 1- \sigma \left ( X_w^T\theta ^u \right )\right ]L(w,u)=Lw(u)?log[σ(XwT?θu)]+[1?Lw(u)]?log[1?σ(XwT?θu)]
在此,變量為XwX_wXw?和θu\theta ^uθu,此時,分別對XwX_wXw?和θjw\theta _j^wθjw?求偏導數:
?L(w,u)?θu=[Lw(u)?σ(XwTθu)]Xw\frac{\partial L\left ( w,u \right )}{\partial \theta ^u}=\left [ L^w\left ( u \right )- \sigma \left ( X_w^T\theta ^u \right )\right ]X_w?θu?L(w,u)?=[Lw(u)?σ(XwT?θu)]Xw?
?L(w,u)?Xw=[Lw(u)?σ(XwTθu)]θu\frac{\partial L\left ( w,u \right )}{\partial X_w}=\left [ L^w\left ( u \right )- \sigma \left ( X_w^T\theta ^u \right )\right ]\theta ^u?Xw??L(w,u)?=[Lw(u)?σ(XwT?θu)]θu
因此,更新的代碼為:
if (f > MAX_EXP) g = (label - 1) * alpha; else if (f < -MAX_EXP) g = (label - 0) * alpha; else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2]; for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * neu1[c];對詞向量的更新與Hierarchical Softmax中一致。
4.3. Skip-gram模型
而Skip-gram模型與CBOW正好相反,在Skip-gram模型中,則是通過當前詞wtw_twt?來預測其前后詞wt?2w_{t-2}wt?2?,wt?1w_{t-1}wt?1?,wt+1w_{t+1}wt+1?和wt+2w_{t+2}wt+2?,Skip-gram模型如下圖所示:
4.3.1. Hierarchical Softmax
由上述的分析,我們發現,在Skip-gram模型中,其計算方法與CBOW模型很相似,不同的是,在Skip-gram模型中,需要使用當前詞分別預測窗口中的詞,因此,這是一個循環的過程:
for (a = b; a < window * 2 + 1 - b; a++) if (a != window)對于向量的更新過程與CBOW模型中的Hierarchical Softmax一致:
c = sentence_position - window + a; if (c < 0) continue; if (c >= sentence_length) continue; last_word = sen[c]; if (last_word == -1) continue; l1 = last_word * layer1_size; for (c = 0; c < layer1_size; c++) neu1e[c] = 0; // HIERARCHICAL SOFTMAX if (hs) for (d = 0; d < vocab[word].codelen; d++) {f = 0;l2 = vocab[word].point[d] * layer1_size;// Propagate hidden -> output// 映射層即為輸入層for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1[c + l2];if (f <= -MAX_EXP) continue;else if (f >= MAX_EXP) continue;else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];// 'g' is the gradient multiplied by the learning rateg = (1 - vocab[word].code[d] - f) * alpha;// Propagate errors output -> hiddenfor (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];// Learn weights hidden -> outputfor (c = 0; c < layer1_size; c++) syn1[c + l2] += g * syn0[c + l1]; } // Learn weights input -> hidden for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];4.3.2. Negative Sampling
與上述一致,在Skip-gram中與CBOW中的唯一不同是在Skip-gram中是循環的過程。代碼的實現類似與上面的Hierarchical Softmax。
注釋版的word2vec源碼已經上傳到Github中:Github:word2vec.c
參考文獻
[1] word2vec 中的數學原理詳解(一)目錄和前言
[2] word2vec 中的數學原理詳解(二)預備知識
[3] word2vec 中的數學原理詳解(三)背景知識
[4] word2vec 中的數學原理詳解(四)基于 Hierarchical Softmax 的模型
[5] word2vec 中的數學原理詳解(五)基于 Negative Sampling 的模型
[6] word2vec 中的數學原理詳解(六)若干源碼細節
[7] 深度學習與自然語言處理(1)_斯坦福cs224d Lecture 1
[8] Google Word2vec 學習手札
[9] Deep Learning in NLP (一)詞向量和語言模型
[10] Neural Probabilistic Language Model, word2vec來龍去脈
[11] word2vec原理概述
[12] 自己動手寫word2vec (一):主要概念和流程
[13] The amazing power of word vectors
[14] word2vec前世今生
總結
以上是生活随笔為你收集整理的机器学习算法实现解析——word2vec源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Pillow图片格式转换
- 下一篇: php随机生成昵称,PHP基于自定义类随