自己動手寫word2vec (四):CBOW和skip-gram模型


系列所有帖子
自己動手寫word2vec (一):主要概念和流程
自己動手寫word2vec (二):統計詞頻
自己動手寫word2vec (三):構建Huffman樹
自己動手寫word2vec (四):CBOW和skip-gram模型


CBOW和skip-gram應該可以說算是word2vec的核心概念之一了。這一節我們就來仔細的闡述這兩個模型。其實這兩個模型有很多的相通之處,所以這裏就以闡述CBOW模型爲主,然後再闡述skip-gram與CBOW的不同之處。這一部分的代碼放在pyword2vec.py文件中

1.CBOW模型

之前已經解釋過,無論是CBOW模型還是skip-gram模型,都是以Huffman樹作爲基礎的。而Huffman樹的構建在前一節已經講過咯,這裏就不再重複。值得注意的是,Huffman樹中非葉節點存儲的中間向量的初始化值是零向量,而葉節點對應的單詞的詞向量是隨機初始化的。

1.1 訓練的流程

那麼現在假設我們已經有了一個已經構造好的Huffman樹,以及初始化完畢的各個向量,可以開始輸入文本來進行訓練了。

訓練的過程如下圖所示,主要有輸入層(input),映射層(projection)和輸出層(output)三個階段。

這裏寫圖片描述

輸入層即爲某個單詞A周圍的n-1個單詞的詞向量。如果n取5,則詞A(可記爲w(t))前兩個和後兩個的單詞爲w(t-2),w(t-1),w(t+1),w(t+2)。相對應的,那4個單詞的詞向量記爲v(w(t-2)),v(w(t-1)),v(w(t+1)),v(w(t+2))。從輸入層到映射層比較簡單,將那n-1個詞向量相加即可。而從映射層到到輸出層則比較繁瑣,下面單獨講

1.2 從映射層到輸出層

要完成這一步驟,需要藉助之前構造的Huffman樹。從根節點開始,映射層的值需要沿着Huffman樹不斷的進行logistic分類,並且不斷的修正各中間向量和詞向量。

舉個例子, 比如說有下圖所示的Huffman樹

這裏寫圖片描述

此時中間的單詞爲w(t),而映射層輸入爲
pro(t)=v(w(t-2))+v(w(t-1))+v(w(t+1))+v(w(t+2))

假設此時的單詞爲“足球”,即w(t)=“足球”,則其Huffman碼可知爲d(t)=”1001”(具體可見上一節),那麼根據Huffman碼可知,從根節點到葉節點的路徑爲“左右右左”,即從根節點開始,先往左拐,再往右拐2次,最後再左拐。

既然知道了路徑,那麼就按照路徑從上往下依次修正路徑上各節點的中間向量。在第一個節點,根據節點的中間向量Θ(t,1)和pro(t)進行Logistic分類。如果分類結果顯示爲0,則表示分類錯誤(應該向左拐,即分類到1),則要對Θ(t,1)進行修正,並記錄誤差量。

接下來,處理完第一個節點之後,開始處理第二個節點。方法類似,修正Θ(t,2),並累加誤差量。接下來的節點都以此類推。

在處理完所有節點,達到葉節點之後,根據之前累計的誤差來修正詞向量v(w(t))。

這樣,一個詞w(t)的處理流程就結束了。如果一個文本中有N個詞,則需要將上述過程在重複N遍,從w(0)~w(N-1)。

1.3 CBOW模型的僞代碼描述

將模型形象化的描述過以後,還需要以更精確的方式將模型的流程確定下來。
首先,我們需要先引入一些符號以便於更清晰的表達。

這裏寫圖片描述

那麼根據word2vec中的數學,流程可以表述爲

這裏寫圖片描述

其中σ表示sigmoid函數,η表示學習率。學習率越大,則判斷錯誤的懲罰也越大,對中間向量的修正跨度也越大。

1.4 CBOW模型的代碼描述

爲了提高複用性,代碼主要由兩部分組成,分別是__Deal_Gram_CBOW和__GoAlong_Huffman。後者負責最核心部分,也就是與huffman相關的部分,前者負責剩下的功能,包括修正詞向量等

def __Deal_Gram_CBOW(self,word,gram_word_list):

        if not self.word_dict.__contains__(word):
            return

        word_huffman = self.word_dict[word]['Huffman']
        gram_vector_sum = np.zeros([1,self.vec_len])
        for i in range(gram_word_list.__len__())[::-1]:
            item = gram_word_list[i]
            if self.word_dict.__contains__(item):
                gram_vector_sum += self.word_dict[item]['vector'] #將周圍單詞的詞向量相加
            else:
                gram_word_list.pop(i)

        if gram_word_list.__len__()==0:
            return

        e = self.__GoAlong_Huffman(word_huffman,gram_vector_sum,self.huffman.root) #與Huffman相關方法

        for item in gram_word_list:
            self.word_dict[item]['vector'] += e
            self.word_dict[item]['vector'] = preprocessing.normalize(self.word_dict[item]['vector']) #修正詞向量
def __GoAlong_Huffman(self,word_huffman,input_vector,root):

        node = root     #從root開始 自頂向下
        e = np.zeros([1,self.vec_len])  #將誤差初始化爲零向量
        for level in range(word_huffman.__len__()):  # 一層層處理
            huffman_charat = word_huffman[level] # 根據Huffman碼獲知當前節點應該將輸入分到哪一邊
            q = self.__Sigmoid(input_vector.dot(node.value.T))
            grad = self.learn_rate * (1-int(huffman_charat)-q) # 計算當前節點的誤差
            e += grad * node.value # 累加誤差
            node.value += grad * input_vector #修正當前節點的中間向量
            node.value = preprocessing.normalize(node.value) # 歸一化
            if huffman_charat=='0': #將當前節點切換到路徑上的下一節點
                node = node.right
            else:
                node = node.left
        return e

2. skip-gram模型

skip-gram與CBOW相比,只有細微的不同。skip-gram的輸入是當前詞的詞向量,而輸出是周圍詞的詞向量。也就是說,通過當前詞來預測周圍的詞。如下圖所示


這裏寫圖片描述

由於輸出有n-1個詞,所以要對於一個詞來講,上述沿着huffman樹從頂到底的過程要循環n-1遍。。。其僞碼描述如下

這裏寫圖片描述

其代碼描述如下,與huffman有關的代碼上面已經貼過了,就不再重複

def __Deal_Gram_SkipGram(self,word,gram_word_list):

        if not self.word_dict.__contains__(word):
            return

        word_vector = self.word_dict[word]['vector']
        for i in range(gram_word_list.__len__())[::-1]:
            if not self.word_dict.__contains__(gram_word_list[i]):
                gram_word_list.pop(i)

        if gram_word_list.__len__()==0:
            return

        for u in gram_word_list:
            u_huffman = self.word_dict[u]['Huffman']
            e = self.__GoAlong_Huffman(u_huffman,word_vector,self.huffman.root)
            self.word_dict[word]['vector'] += e
            self.word_dict[word]['vector'] = preprocessing.normalize(self.word_dict[word]['vector'])
發佈了88 篇原創文章 · 獲贊 591 · 訪問量 147萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章