日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

【论文解读】NLP重铸篇之Word2vec

發(fā)布時(shí)間:2025/3/8 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【论文解读】NLP重铸篇之Word2vec 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.


論文標(biāo)題:Efficient Estimation of Word Representations in Vector Space
論文鏈接:https://arxiv.org/pdf/1301.3781.pdf
復(fù)現(xiàn)代碼地址:https://github.com/wellinxu/nlp_store/blob/master/papers/word2vec.py


論文標(biāo)題:word2vec Parameter Learning Explained
論文鏈接:https://arxiv.org/pdf/1411.2738.pdf

word2vec原論文講得比較簡(jiǎn)單,幾乎沒(méi)有細(xì)節(jié),本文會(huì)根據(jù)另一篇論文【word2vec Parameter Learning Explained】,來(lái)詳細(xì)介紹兩種加速方法。本文使用python+tensorflow2.0來(lái)復(fù)現(xiàn)word2vec模型,所以模型中的反向梯度計(jì)算與參數(shù)優(yōu)化更新,都是使用的tf中的自動(dòng)求導(dǎo)與優(yōu)化器實(shí)現(xiàn),也因此本文中只涉及到word2vec的兩種結(jié)構(gòu)(CBOW與Skip-gram)及兩種加速方式(Huffman樹(shù)-層次softmax和負(fù)采樣)從輸入到loss的前向計(jì)算,完整代碼已開(kāi)源,具體請(qǐng)查看https://github.com/wellinxu/nlp_store。

重鑄系列會(huì)分享論文的解析與復(fù)現(xiàn),主要是一些經(jīng)典論文以及前沿論文,但知識(shí)還是原汁原味的好,支持大家多看原論文。分享內(nèi)容主要來(lái)自于原論文,會(huì)有些整理與刪減,以及個(gè)人理解與應(yīng)用等等,其中涉及到的算法復(fù)現(xiàn)都會(huì)開(kāi)源在:https://github.com/wellinxu/nlp_store 。

  • 引言

  • 模型結(jié)構(gòu)

    • CBOW結(jié)構(gòu)

    • skip-gram結(jié)構(gòu)

    • softmax的loss計(jì)算

  • Huffman樹(shù)——層次softmax

    • Huffman樹(shù)的構(gòu)建

    • Huffman樹(shù)壓縮為數(shù)組

    • Huffman樹(shù)loss計(jì)算

  • 負(fù)采樣

    • 采樣權(quán)重調(diào)整

    • 負(fù)采樣loss計(jì)算

  • 模型訓(xùn)練

  • 論文結(jié)果

  • 論文之外

  • 參考

引言

word2vec的目標(biāo)是從十億量級(jí)的文章跟百萬(wàn)量級(jí)的詞匯中學(xué)習(xí)出高質(zhì)量的詞向量表示,實(shí)驗(yàn)結(jié)果表明,其向量可以達(dá)到類似“江蘇-南京+杭州≈浙江”的效果。

模型結(jié)構(gòu)

在word2vec之前,已經(jīng)有不少關(guān)于詞的連續(xù)表示模型提出來(lái),比如LSA(Latent Semantic Analysis)跟LDA( Latent Dirichlet Allocation)。跟LAS相比,word2vec效果更好,跟LDA相比,word2vec的計(jì)算速度更快。
論文中提出了兩種新的模型結(jié)構(gòu):CBOW、Skip-gram,跟NNLM(Feedforward Neural Net Language Model)相比,word2vec為了追求更簡(jiǎn)單的模型結(jié)構(gòu),刪除了中間的非線性隱藏層,盡管這樣可能會(huì)讓詞向量沒(méi)有NNLM的更加精確,但這可以讓模型在更大的數(shù)據(jù)集上高效訓(xùn)練。
兩種結(jié)構(gòu)都是利用中心詞跟上下文互為輸入與輸出,上下文就是中心詞前后的n個(gè)詞,實(shí)驗(yàn)表明,提高上下文的范圍(即n的大小),可以提高詞向量的質(zhì)量,但這也加大了計(jì)算復(fù)雜度。

CBOW結(jié)構(gòu)


第一種模型結(jié)構(gòu)是CBOW(Continuous Bag-of-Words Model),其思想是利用中心詞前面n個(gè)詞跟后面n個(gè)詞來(lái)預(yù)測(cè)中心詞(n可自定義),如上圖所示,用的是前面2個(gè)詞跟后面2個(gè)詞預(yù)測(cè)當(dāng)前詞,輸入層直接求和變成投影層,沒(méi)有其他任何操作,然后直接預(yù)測(cè)中心詞。整體公式可直接表示為:

其中f表示將projection映射為output的變換函數(shù),是其中的參數(shù),f的具體方式后續(xù)會(huì)講解。

Skip-gram結(jié)構(gòu)


第二種模型結(jié)構(gòu)是Skip-gram(Continuous Skip-gram Model),其思想與CBOW相反:利用中心詞預(yù)測(cè)中心詞上下文的n個(gè)詞(n可自定義),如上圖所示,利用中心詞預(yù)測(cè)上下文各2個(gè)詞,輸入層直接等價(jià)變化到投影層,然后預(yù)測(cè)上下文的詞。整體公式可以直接表示為:

本文復(fù)現(xiàn)過(guò)程中,將Skip-gram結(jié)構(gòu)視為多標(biāo)簽預(yù)測(cè)問(wèn)題,word2vec的源碼中應(yīng)該是將每個(gè)輸出拆開(kāi)(參考【2】得出的結(jié)論,并未真正看過(guò)源碼),組成多個(gè)單分類問(wèn)題。從過(guò)程上來(lái)說(shuō),這兩種方式更新參數(shù)的順序會(huì)有差別,有些類似于批梯度下降與隨機(jī)梯度下降的區(qū)別,從工程上來(lái)說(shuō),word2vec源碼中是簡(jiǎn)化了問(wèn)題,處理更簡(jiǎn)單。

softmax的loss計(jì)算

如果用softmax來(lái)取代上面式子中的f函數(shù),那么ouput的計(jì)算方式可具體為:

其中表示預(yù)測(cè)為第i個(gè)詞的概率,表示第i個(gè)詞的輸出權(quán)重向量,N表示詞表大小。
這是一個(gè)比較標(biāo)準(zhǔn)的分類問(wèn)題,所以使用負(fù)對(duì)數(shù)似然概率或者交叉熵來(lái)計(jì)算其loss,具體的CBOW結(jié)構(gòu)的loss可表示為:

其中j表示中心詞的索引,表示預(yù)測(cè)是真實(shí)中心詞的概率。
Skip-gram結(jié)構(gòu)的loss則可表示為:

其中M是上下文的數(shù)量,表示預(yù)測(cè)是第j個(gè)上下文詞的概率。
根據(jù)上面兩種模型結(jié)構(gòu)以及l(fā)oss計(jì)算,可以完成相應(yīng)代碼,因?yàn)槟P筒魂P(guān)心輸出,只關(guān)心詞權(quán)重的更新,所以會(huì)在模型內(nèi)部直接計(jì)算loss,具體代碼如下:

import?tensorflow?as?tf import?numpy?as?np import?random import?timeclass?BaseWord2vecModel(tf.keras.models.Model):#?當(dāng)前的實(shí)現(xiàn)沒(méi)有batch維度,是一個(gè)樣本一個(gè)樣本進(jìn)行訓(xùn)練def?__init__(self,?voc_size,?emb_dim,?is_huffman=True,?is_negative=False):super(BaseWord2vecModel,?self).__init__()self.voc_size?=?voc_sizeself.is_huffman?=?is_huffmanself.is_negative?=?is_negativeself.embedding?=?tf.keras.layers.Embedding(voc_size,?emb_dim)self.layer_norm?=?tf.keras.layers.LayerNormalization(name="layer_norm",?axis=-1,?epsilon=1e-12,?dtype=tf.float32)if?not?self.is_huffman?and?not?is_negative:#?不使用huffman樹(shù)也不使用負(fù)采樣,所有詞的輸出參數(shù)self.output_weight?=?self.add_weight(shape=(voc_size,?emb_dim),?initializer=tf.zeros_initializer,?trainable=True)self.softmax?=?tf.keras.layers.Softmax()if?self.is_huffman:#?所有節(jié)點(diǎn)的參數(shù),huffman樹(shù)壓縮為數(shù)組的時(shí)候,保留了所有葉子節(jié)點(diǎn),所以數(shù)組長(zhǎng)度為2*voc_size#?也可以選擇只保留非葉子節(jié)點(diǎn),這樣長(zhǎng)度可減半self.huffman_params?=?tf.keras.layers.Embedding(2*voc_size,?emb_dim,?embeddings_initializer=tf.keras.initializers.zeros)self.huffman_choice?=?tf.keras.layers.Embedding(2,?1,?weights=(np.array([[-1],?[1]]),))if?self.is_negative:#?負(fù)采樣時(shí),每個(gè)詞的輸出參數(shù)self.negative_params?=?tf.keras.layers.Embedding(voc_size,?emb_dim,?embeddings_initializer=tf.keras.initializers.zeros)def?call(self,?inputs,?training=None,?mask=None):#?x:[context_len]#?huffman_label:?[label_size,?code_len]#?huffman_index:?[label_size,?code_len]#?y?:?[label_size]#?negative_index:?[negatuve_num]x,?huffman_label,?huffman_index,?y,?negative_index?=?inputs#?skip-gram的context_len就是1x?=?self.embedding(x)????#?[context_len,?emb_dim]x?=?self.layer_norm(x)????#?層標(biāo)準(zhǔn)化,自己添加非模型原始結(jié)構(gòu)#?skip-gram單個(gè)求和就是本身x?=?tf.reduce_sum(x,?axis=-2)????#?[emb_dim]loss?=?0if?not?self.is_huffman?and?not?self.is_negative:#?不使用huffman樹(shù)也不使用負(fù)采樣,則使用原始softmaxoutput?=?tf.einsum("ve,e->v",?self.output_weight,?x)????#?[voc_size]output?=?self.softmax(output)y_index?=?tf.one_hot(y,?self.voc_size)????#?[label_size,?voc_size]y_index?=?tf.reduce_sum(y_index,?axis=0)????#?[voc_size]l?=?tf.einsum("a,a->a",?output,?y_index)????#?[voc_size]loss?-=?tf.reduce_sum(l)

從上面計(jì)算公式可以看出,當(dāng)詞表量級(jí)特別大的時(shí)候(百萬(wàn)級(jí)別),的分母計(jì)算以及后續(xù)反向傳播更新所有的計(jì)算將會(huì)非常耗時(shí),這種昂貴的計(jì)算代價(jià)使得在大語(yǔ)料或者大詞表情況下訓(xùn)練變得不可能。要解決這個(gè)問(wèn)題,一個(gè)直覺(jué)的方法是限制每個(gè)訓(xùn)練樣本必須更新的輸出向量的數(shù)量,后續(xù)會(huì)介紹兩種方式來(lái)實(shí)現(xiàn)這一點(diǎn),分別是層次softmax與負(fù)采樣。

Huffman樹(shù)——層次softmax

層次softmax是一種高效計(jì)算softmax的方法,其使用二叉樹(shù)來(lái)表示詞表中的所有詞,每一個(gè)詞都必須是樹(shù)的葉子結(jié)點(diǎn),對(duì)于每一個(gè)結(jié)點(diǎn),都存在唯一的路徑從根結(jié)點(diǎn)到當(dāng)前葉子結(jié)點(diǎn),該路徑就被用來(lái)估計(jì)此葉子結(jié)點(diǎn)表示的詞出現(xiàn)的概率。理論上說(shuō),可以使用任何形式的樹(shù)來(lái)計(jì)算層次softmax,word2vec里面使用的是二叉Huffman樹(shù)來(lái)進(jìn)行訓(xùn)練。因?yàn)閔uffman樹(shù)中權(quán)重越高的結(jié)點(diǎn)越靠近根結(jié)點(diǎn),這樣頻率高的詞的路徑就越短,計(jì)算的次數(shù)也就越少,從而可以進(jìn)一步提高訓(xùn)練速度。

Huffman樹(shù)的構(gòu)建

給定n個(gè)結(jié)點(diǎn),每個(gè)結(jié)點(diǎn)都有一個(gè)權(quán)重,構(gòu)造一棵二叉樹(shù),如果它的帶權(quán)路徑長(zhǎng)度最小,則稱為最優(yōu)二叉樹(shù),也稱為Huffman樹(shù)。給定n個(gè)值作為n個(gè)結(jié)點(diǎn)的權(quán)重,可以通過(guò)下面方法構(gòu)造huffman樹(shù):

  • 將看成是有n棵樹(shù)的森林(每棵樹(shù)只有一個(gè)結(jié)點(diǎn))。

  • 在森林中選出兩個(gè)根節(jié)點(diǎn)權(quán)重最小的樹(shù)合并,分別作為新樹(shù)的左右子樹(shù),新樹(shù)根結(jié)點(diǎn)的權(quán)重為左右子樹(shù)根結(jié)點(diǎn)權(quán)重之和。

  • 刪除森林中選取的兩棵樹(shù),并將新樹(shù)添加到森林。

  • 重復(fù)上面兩步操作,直到森林里就剩一棵樹(shù),該樹(shù)就是huffman樹(shù)。


  • 如上圖所示,顯示了一個(gè)簡(jiǎn)單huffman樹(shù)的構(gòu)造過(guò)程,在本文的實(shí)現(xiàn)中,將葉子結(jié)點(diǎn)優(yōu)先放在右子樹(shù)上,但這并不是必要的,也可以將權(quán)重大(或小)的結(jié)點(diǎn)放在右子樹(shù)上。
    為了實(shí)現(xiàn)其構(gòu)造過(guò)程,我們需要先定義結(jié)點(diǎn)類,如下所示:

    class?Node(object):def?__init__(self,?key,?value):self.key?=?key????#?本文代碼里huffman中,非葉子節(jié)點(diǎn)都為Noneself.value?=?value????#?權(quán)重#?編碼,即重跟節(jié)點(diǎn)走到本節(jié)點(diǎn)的方向,0表示左子樹(shù),1表示右子樹(shù)#?010表示跟節(jié)點(diǎn)->左子樹(shù)->右子樹(shù)->左子樹(shù)(本節(jié)點(diǎn))self.code?=?[]????#?記錄當(dāng)前節(jié)點(diǎn)在整個(gè)huffman樹(shù)中的編碼self.index?=?0????#?第幾個(gè)節(jié)點(diǎn),壓縮為數(shù)組用,即為該節(jié)點(diǎn)在數(shù)組型的huffman樹(shù)種的索引位置self.left?=?Noneself.right?=?Nonedef?combine(self,?node):#?兩棵樹(shù)(兩個(gè)節(jié)點(diǎn))合并成一個(gè)新樹(shù),葉子節(jié)點(diǎn)放在新樹(shù)的右子樹(shù)上new_node?=?Node(None,?self.value?+?node.value)if?self.key?is?not?None:new_node.right?=?selfnew_node.left?=?nodeelse:new_node.right?=?nodenew_node.left?=?selfreturn?new_node

    Node類中定義了權(quán)重(value)、詞(key)、編碼(code)、索引位置(index)以及左右結(jié)點(diǎn),此外還提供了combine方法,用來(lái)合并兩棵子樹(shù)。定義好結(jié)點(diǎn),就可以通過(guò)以下方式來(lái)構(gòu)建huffman樹(shù):

    class?HuffmanTree(object):#?用一個(gè)數(shù)組表示huffman樹(shù)的所有非葉子節(jié)點(diǎn)#?用word_code_map表記錄根到每個(gè)葉子節(jié)點(diǎn)的編碼def?__init__(self,?words):start?=?time.time()words.sort()??#?根據(jù)頻率排序self.words?=?words????#?[(value:頻次,?key:詞)],由小到大排序self.word_code_map?=?{}????#?詞在huffman樹(shù)中的編碼映射self.nodes_list?=?[]?????#?壓縮為數(shù)組的huffman樹(shù)self.build_huffman_tree()print("build?huffman?tree?end,time:",?time.time()-start)def?build_huffman_tree(self):#?構(gòu)建huffman樹(shù)#?每個(gè)元素都構(gòu)成單節(jié)點(diǎn)的樹(shù),并按照權(quán)重重大到小排列#?合并權(quán)重最小的兩個(gè)子樹(shù),并以權(quán)重和作為新樹(shù)的權(quán)重#?將新樹(shù)按照權(quán)重大小插入到序列中#?重復(fù)上述兩步,直到只剩一棵樹(shù)nodes?=?[Node(key,?value)?for?value,?key?in?self.words]while?len(nodes)?>?1:a_node?=?nodes.pop(0)b_node?=?nodes.pop(0)new_node?=?a_node.combine(b_node)left,?right?=?0,?len(nodes)l?=?right?-?1i?=?right?>>?1while?True:if?nodes?and?nodes[i].value?>=?new_node.value:if?i?==?0?or?nodes[i-1].value?<?new_node.value:nodes.insert(i,?new_node)breakelse:right?=?ii?=?(left+right)?>>?1else:if?i?==?0?or?i?==?l:nodes.insert(i,?new_node)breakleft?=?ii?=?(left+right)?>>?1

    其中輸入words是以(詞頻,詞)為元素的list,并以詞頻的大小由小到大排好序。

    Huffman樹(shù)壓縮為數(shù)組

    構(gòu)建完huffman樹(shù),可以根據(jù)其設(shè)計(jì)二進(jìn)制前綴編碼,也稱Huffman編碼,下圖展示了一個(gè)huffman樹(shù)的編碼示例,左子樹(shù)為0右子樹(shù)為1(這個(gè)可自由設(shè)定),如“巴西”就可以編碼為“011”。在后續(xù)計(jì)算過(guò)程中,不僅需要知道詞的huffman編碼,還需要知道該詞經(jīng)過(guò)了哪些結(jié)點(diǎn),為了方便根據(jù)編碼得知結(jié)點(diǎn),我們將樹(shù)形結(jié)構(gòu)壓縮為數(shù)組型結(jié)構(gòu)(并不是必要的,也可以通過(guò)原始huffman樹(shù),獲取該編碼經(jīng)過(guò)的結(jié)點(diǎn)),如上圖所示,huffman樹(shù)按照層次遍歷的順序跟數(shù)組的索引一一對(duì)應(yīng),數(shù)組中的值表示當(dāng)前結(jié)點(diǎn)左結(jié)點(diǎn)的索引值,如果數(shù)組的值跟索引相同,則表示該結(jié)點(diǎn)是葉子節(jié)點(diǎn)(其實(shí)也可以只將非葉子結(jié)點(diǎn)壓縮到數(shù)組里面,因?yàn)楹罄m(xù)計(jì)算中不需要葉子結(jié)點(diǎn))。
    根據(jù)上述需求,我們對(duì)huffman樹(shù)進(jìn)行一次層次遍歷就可以實(shí)現(xiàn),具體代碼如下:

    ????????#?將樹(shù)壓縮為數(shù)組#?數(shù)組中每一個(gè)元素都是樹(shù)種的一個(gè)節(jié)點(diǎn),數(shù)組中的值表示該節(jié)點(diǎn)的左節(jié)點(diǎn)的位置,如果值跟索引一樣大,表示此節(jié)點(diǎn)是葉子節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)#?跟節(jié)點(diǎn)?root.index?=?0#?葉子節(jié)點(diǎn)?nodes_list[node.index]?=?node.index#?非葉子節(jié)點(diǎn)?nodes_list[node.index]?=?node.left.index#?非葉子節(jié)點(diǎn)?nodes_list[node.index]?+?1?=?node.right.indexstack?=?[nodes[0]]while?stack:new_stack?=?[]for?node?in?stack:node.index?=?len(self.nodes_list)if?node.key?is?not?None:???#?葉子結(jié)點(diǎn)self.word_code_map[node.key]?=?node.code??#?保存編碼self.nodes_list.append(node)??#?在數(shù)組中添加葉子結(jié)點(diǎn)本身if?node.right:node.right.code?=?node.code?+?[1]new_stack.append(node.right)if?node.left:#?在數(shù)組相應(yīng)位置添加該結(jié)點(diǎn)的左結(jié)點(diǎn)self.nodes_list.append(node.left)node.left.code?=?node.code?+?[0]new_stack.append(node.left)stack?=?new_stackself.nodes_list?=?[node.index?for?node?in?self.nodes_list]

    將huffman樹(shù)壓縮為數(shù)組后,就可以根據(jù)以下公式計(jì)算每個(gè)編碼所經(jīng)歷的結(jié)點(diǎn):

    所有路徑的第一個(gè)結(jié)點(diǎn)都是根結(jié)點(diǎn)

    其中表示編碼的長(zhǎng)度,表示第i個(gè)編碼值,表示經(jīng)過(guò)的第i個(gè)結(jié)點(diǎn)的位置。比如“巴西”的編碼值是“011”,則可以計(jì)算第一個(gè)結(jié)點(diǎn)位置是0,第二個(gè)是,第三個(gè)是,所以“巴西”這個(gè)詞經(jīng)過(guò)的內(nèi)部結(jié)點(diǎn)位置依次為0-1-4(不算巴西本身這個(gè)結(jié)點(diǎn))。
    根據(jù)上面公式,我們可以構(gòu)建通過(guò)huffman樹(shù)處理的訓(xùn)練數(shù)據(jù):

    ????????#?構(gòu)建huffman數(shù)據(jù)if?self.is_huffman:huffman_tree?=?HuffmanTree(self.words)????#?根據(jù)詞與詞頻,構(gòu)建huffman樹(shù)for?labels?in?self.ys:tlabels?=?[]for?label?in?labels:tlabels.append(np.array(huffman_tree.word_code_map[label]))index?=?[]for?tlabel?in?tlabels:tem_index?=?[0]for?l?in?tlabel[:-1]:ind?=?huffman_tree.nodes_list[tem_index[-1]]?+?ltem_index.append(ind)index.append(np.array(tem_index))self.huffman_label.append(tlabels)??#?獲取標(biāo)簽詞在huffman樹(shù)中的編碼self.huffman_index.append(index)????#?獲取標(biāo)簽詞在huffman樹(shù)中的編碼上對(duì)應(yīng)的所有非葉子節(jié)點(diǎn)

    Huffman樹(shù)loss計(jì)算

    Huffman樹(shù)中每個(gè)詞出現(xiàn)的概率,是將該詞到根結(jié)點(diǎn)路徑上的每個(gè)結(jié)點(diǎn)出現(xiàn)的概率相乘,具體公式如下:

    其中是編碼長(zhǎng)度,是第i個(gè)編碼值,為0時(shí)表示是往左結(jié)點(diǎn)走,此時(shí)用計(jì)算概率,為1時(shí)則用來(lái)計(jì)算概率;是上面兩種結(jié)構(gòu)的投影層結(jié)果,表示進(jìn)過(guò)的第i個(gè)結(jié)點(diǎn)的位置,表示第j個(gè)結(jié)點(diǎn)的參數(shù)。
    跟一般softmax相比,原來(lái)需要計(jì)算與更新N(詞表大小)次參數(shù),現(xiàn)在只需要計(jì)算與更新次,的大小跟接近,從而大大提高了計(jì)算效率。
    上面已經(jīng)得到單個(gè)詞似然函數(shù),如果是CBOW模型結(jié)構(gòu),那Huffman的loss則可以取其負(fù)對(duì)數(shù):

    當(dāng)模型結(jié)構(gòu)是Skip-gram結(jié)構(gòu)時(shí),會(huì)有多個(gè)輸出,此時(shí)的Huffman的loss則應(yīng)該為:

    其中表示輸出的個(gè)數(shù),表示第j個(gè)輸出詞的似然概率。
    根據(jù)公式,則huffman相關(guān)代碼為:

    ????????#?huffman樹(shù)loss計(jì)算if?self.is_huffman:for?tem_label,?tem_index?in?zip(huffman_label,?huffman_index):#?獲取huffman樹(shù)編碼上的各個(gè)節(jié)點(diǎn)參數(shù)huffman_param?=?self.huffman_params(tem_index)????#?[code_len,?emb_dim]#?各節(jié)點(diǎn)參數(shù)與x點(diǎn)積huffman_x?=?tf.einsum("ab,b->a",?huffman_param,?x)????#?[code_len]#?獲取每個(gè)節(jié)點(diǎn)是左節(jié)點(diǎn)還是右節(jié)點(diǎn)tem_label?=?tf.squeeze(self.huffman_choice(tem_label),?axis=-1)????#?[code_len]#?左節(jié)點(diǎn):sigmoid(-WX),右節(jié)點(diǎn)sigmoid(WX)l?=?tf.sigmoid(tf.einsum("a,a->a",?huffman_x,?tem_label))????#?[code_len]l?=?tf.math.log(l)loss?-=?tf.reduce_sum(l)

    負(fù)采樣

    負(fù)采樣的思想比層次softmax更加直接:為了解決softmax要計(jì)算和更新的參數(shù)太多的問(wèn)題,負(fù)采樣每次只計(jì)算和更新幾個(gè)參數(shù)。也就是說(shuō),原來(lái)每個(gè)樣本進(jìn)行訓(xùn)練的時(shí)候,需要從所有詞匯中選出某個(gè)或某幾個(gè)詞,現(xiàn)在只需要從某個(gè)小的詞集里面選出某個(gè)或者某幾個(gè)詞,因?yàn)樾≡~集的數(shù)量遠(yuǎn)小于原本詞表數(shù)量,所以計(jì)算量與需要更新的參數(shù)都小很多,從而大大提高訓(xùn)練速度。

    采樣權(quán)重調(diào)整

    根據(jù)負(fù)采樣的思想,預(yù)測(cè)的詞肯定需要在小詞集中出現(xiàn),然后只需要從其他詞中抽取一些詞作為負(fù)樣本(負(fù)采樣的由來(lái))。顯而易見(jiàn)的,可以通過(guò)詞頻計(jì)算詞的分布概率從而進(jìn)行抽樣,但為了降低詞頻過(guò)高的詞被抽的概率,以及提高詞頻過(guò)低的詞被抽的概率,word2vec中將頻率進(jìn)行了0.75次冪運(yùn)算,然后計(jì)算詞的分布概率。相關(guān)代碼,可參考:

    ????def?build_word_dict(self):#?構(gòu)建詞典,獲取詞頻self.words?=?[]????#?(頻率,id)self.word_map?=?{}????#?{詞:id}word_num_map?=?{}????#?頻率for?doc?in?self.docs:if?len(doc)?<?self.windows:?continuefor?word?in?doc:if?word?not?in?self.word_map.keys():self.word_map[word]?=?len(self.word_map)self.words.append(word)word_num_map[word]?=?word_num_map.get(word,?0)?+?1#?詞頻設(shè)為原本的0.75次方,根據(jù)詞頻進(jìn)行負(fù)采樣的時(shí)候,可以降低高詞頻的概率,提高低詞頻的概率(高詞頻的概率仍然大于低詞頻的概率)word_num_map?=?{k:np.power(v,?0.75)?for?k,?v?in?word_num_map.items()}num?=?sum(word_num_map.values())word_num_map?=?{k:?v/num?for?k,?v?in?word_num_map.items()}self.words?=?[(word_num_map[w],?self.word_map[w])?for?w?in?self.words]self.words.sort()????#?根據(jù)頻率排序self.voc_size?=?len(self.words)????#?詞表大小self.id_word_map?=?{v:?k?for?k,?v?in?self.word_map.items()}??#?{id:詞}

    有了每個(gè)詞的概率,可以將每個(gè)詞映射到0-1之間的一段范圍之內(nèi),然后生成隨機(jī)數(shù),根據(jù)其落在的空間判斷抽取的詞,遇到正例的詞則跳過(guò),不斷重復(fù),直到抽取到一定的負(fù)樣本數(shù)量,具體代碼如下:

    ????????#?構(gòu)建負(fù)采樣數(shù)據(jù)if?self.is_negative:#?如果"我?愛(ài)?你?啊"出現(xiàn)的概率分別是0.4,0.2,0.3,,0.1,#?那么word_end_p就為[0.4,0.6,0.9,?1.0],即[0.4,0.4+0.2,0.4+0.2+0.3,0.4+0.2+0.3+0.1]word_end_p?=?[self.words[0][0]]????#?每個(gè)詞出現(xiàn)的概率段for?i?in?range(1,?self.voc_size):word_end_p.append(word_end_p[-1]+self.words[i][0])#?為每一條訓(xùn)練數(shù)據(jù)抽取負(fù)樣本for?y?in?self.ys:indexs?=?[]while?len(indexs)?<?self.negative_num?*?len(y):index?=?self._binary_search(random.random(),?word_end_p,?0,?self.voc_size-1)#?隨機(jī)抽取一個(gè)詞,不能再標(biāo)簽中也不能已經(jīng)被抽到if?index?not?in?indexs?and?index?not?in?y:indexs.append(index)self.negative_index.append(np.array(indexs))def?_binary_search(self,?n,?nums,?start,?end):#?二分查找,查找n在nums[start:end]數(shù)組的那個(gè)位置if?start?==?end:?return?endmid?=?(start+end)?>>?1if?nums[mid]?>=?n:return?self._binary_search(n,?nums,?start,?mid)return?self._binary_search(n,?nums,?mid+1,?end)

    負(fù)采樣loss計(jì)算

    負(fù)采樣的基本思想是減少更新的參數(shù)數(shù)量,具體操作又是怎樣的呢?簡(jiǎn)單的說(shuō)就是,每一個(gè)樣本,對(duì)于所有抽樣出來(lái)的詞(包括正例詞跟負(fù)例詞),都做一個(gè)二分類,正例詞計(jì)算正例的概率,負(fù)例詞計(jì)算負(fù)例的概率,目標(biāo)是使得所有概率的乘積最大。更具體的解釋及相關(guān)公式可以參考【3】。
    這樣就可以得到輸出概率:

    其中表示輸出詞(正例詞)的索引集合,表示第i個(gè)輸出詞的負(fù)例詞索引集合。當(dāng)模型結(jié)構(gòu)是CBOW時(shí),輸出詞只有一個(gè),當(dāng)模型結(jié)構(gòu)是Skip-gram時(shí),才是多個(gè)。
    這時(shí)候可以計(jì)算loss:

    根據(jù)公式,相關(guān)代碼如下:

    ????????#?負(fù)采樣loss計(jì)算if?self.is_negative:y_param?=?self.negative_params(y)????#?[label_size,?emb_dim]negative_param?=?self.negative_params(negative_index)????#?[negative_num,?emb_dim]y_dot?=?tf.einsum("ab,b->a",?y_param,?x)????#?[label_size]y_p?=?tf.math.log(tf.sigmoid(y_dot))????#?[label_size]negative_dot?=?tf.einsum("ab,b->a",?negative_param,?x)????#?[negative_num]negative_p?=?tf.math.log(tf.sigmoid(-negative_dot))????#?[negative_num]l?=?tf.reduce_sum(y_p)?+?tf.reduce_sum(negative_p)loss?-=?l

    以下引用為錯(cuò)誤思想
    根據(jù)負(fù)采樣思想,可以得到每個(gè)正例樣本輸出的概率:

    其中表示索引為i的輸出詞的預(yù)測(cè)概率,表示所有正例詞跟采樣得到的負(fù)例詞索引集合,是上面兩種結(jié)構(gòu)的投影層結(jié)果。
    此時(shí),根據(jù)負(fù)對(duì)數(shù)似然或者交叉熵可以計(jì)算loss函數(shù),當(dāng)模型是CBOW結(jié)構(gòu)時(shí):

    其中表示輸出詞的概率。
    當(dāng)模型是Skip-gram結(jié)構(gòu)時(shí):

    其中表示第j個(gè)輸出詞的概率,表示輸出的個(gè)數(shù)。
    根據(jù)公式,相關(guān)代碼如下:

    ????????#?負(fù)采樣loss計(jì)算if?self.is_negative:y_param?=?self.negative_params(y)????#?[label_size,?emb_dim]negative_param?=?self.negative_params(negative_index)????#?[negative_num,?emb_dim]y_dot?=?tf.einsum("ab,b->a",?y_param,?x)????#?[label_size]y_exp?=?tf.exp(y_dot)????#?[label_size]negative_dot?=?tf.einsum("ab,b->a",?negative_param,?x)????#?[negative_num]negative_exp?=?tf.exp(negative_dot)????#?[negative_num]y_sum?=?tf.reduce_sum(y_exp)????#?分子negative_sum?=?tf.reduce_sum(negative_exp)????#?負(fù)樣本的分母loss?-=?tf.math.log(y_sum/(y_sum+negative_sum))

    模型訓(xùn)練

    上面已經(jīng)講述了整個(gè)word2vec模型的前向傳播環(huán)節(jié),本次模型復(fù)現(xiàn)使用的是tensorflow2的框架,所以反向傳播以及參數(shù)更新過(guò)程都使用的是框架的自動(dòng)求導(dǎo)等功能,本文中也不會(huì)講述反向傳播相關(guān)原理及公式。模型整體訓(xùn)練代碼如下:

    class?Word2vec(object):def?__init__(self,?docs=None,?emb_dim=100,?windows=5,?negative_num=10,?is_cbow=True,?is_huffman=True,?is_negative=False,?epochs=3,?save_path=None):self.docs?=?docs????#?[[我?是?一段?文本],[這是?第二段?文本]]self.windows?=?windows????#?窗口長(zhǎng)度self.emb_dim?=?emb_dim????#?詞向量維度self.is_cbow?=?is_cbow???#?是否使用CBOW模式,False則使用SG模式self.is_huffman?=?is_huffman????#?是否使用huffman樹(shù)self.is_negative?=?is_negative????#?是否使用負(fù)采樣self.huffman_label?=?[]????#?huffman數(shù)據(jù)的標(biāo)簽,判斷每次選擇左子樹(shù)還是右子樹(shù)self.huffman_index?=?[]???#?huffman數(shù)據(jù)的編碼,用來(lái)獲取編碼上節(jié)點(diǎn)的權(quán)重self.negative_index?=?[]????#?負(fù)采樣的詞索引self.negative_num?=?negative_num????#?負(fù)采樣數(shù)量self.epochs?=?epochs????#?訓(xùn)練輪次self.save_path?=?save_path????#?模型保存路徑if?docs:??#?訓(xùn)練模型self.build_word_dict()????#?構(gòu)建詞典,獲取詞頻self.create_train_data()????#?創(chuàng)建訓(xùn)練數(shù)據(jù)self.train()????#?進(jìn)行訓(xùn)練if?self.save_path:self.save_txt(self.save_path)????#?保存詞向量elif?self.save_path:??#?直接加載詞向量self.load_txt(self.save_path)def?train(self):sample_num?=?len(self.xs)????#?樣本數(shù)量optimizer?=?tf.optimizers.SGD(0.025)????#?優(yōu)化器#?基礎(chǔ)word2vec模型self.model?=?BaseWord2vecModel(self.voc_size,?self.emb_dim,?self.is_huffman,?self.is_negative)#?模型訓(xùn)練for?epoch?in?range(self.epochs):print("start?epoch?%d"?%?epoch)i?=?0for?inputs?in?zip(self.xs,?self.huffman_label,?self.huffman_index,self.ys,?self.negative_index):with?tf.GradientTape()?as?tape:loss?=?self.model(inputs)grads?=?tape.gradient(loss,?self.model.trainable_variables)optimizer.apply_gradients(zip(grads,?self.model.trainable_variables))if?i?%?1000?==?0:print("-%s->%d/%d"?%?("-"?*?(i?*?100?//?sample_num),?i,?sample_num))i?+=?1#?獲取詞向量self.word_embeddings?=?self.model.embedding.embeddings.numpy()norm?=?np.expand_dims(np.linalg.norm(self.word_embeddings,?axis=1),?axis=1)self.word_embeddings?/=?norm????#?歸一化

    論文結(jié)果

    如下表所示,論文中設(shè)置了5種語(yǔ)義問(wèn)題與9種句法問(wèn)題來(lái)測(cè)試向量效果,通過(guò)類似vec(big)-vec(bigger)≈vec(small)-vec(smaller)的方式來(lái)判斷。

    測(cè)試結(jié)果如下圖所示,在相同數(shù)據(jù)上訓(xùn)練的640維詞向量,CBOW在句法層面上表現(xiàn)得較好,Skip-gram在語(yǔ)義層面上表現(xiàn)得更好,總體來(lái)說(shuō)還是Skip-gram更好些。

    論文也對(duì)比了訓(xùn)練語(yǔ)料、向量維度以及訓(xùn)練輪次對(duì)結(jié)果的影響,實(shí)驗(yàn)結(jié)果表明,訓(xùn)練語(yǔ)料越多效果更好,600維的結(jié)果比300維的結(jié)果更優(yōu),3輪的訓(xùn)練結(jié)果比1輪的訓(xùn)練結(jié)果更好。

    論文之外

    word2vec是2013年提出的模型,限于當(dāng)時(shí)的算力,在大規(guī)模的語(yǔ)料上進(jìn)行訓(xùn)練,確實(shí)需要更簡(jiǎn)單的模型以及一些加速訓(xùn)練方式;之后提出的fasttext在模型結(jié)構(gòu)上可以說(shuō)跟word2vec一樣,主要區(qū)別在輸入上,fasttext增加了詞的形態(tài)特征;基于算力的提升,最近的預(yù)訓(xùn)練模型參數(shù)越來(lái)越大,比如BERT、XLNET,甚至有GPT3這種龐然大物。
    再反過(guò)來(lái)看word2vec,是不是用現(xiàn)在的算力訓(xùn)練,就不需要層次softmax或者負(fù)采樣的加速方式了呢?這還是要分情況討論,雖然說(shuō)像BERT這樣的模型,都是直接用的softmax進(jìn)行計(jì)算,這是因?yàn)橛?xùn)練BERT的人(機(jī)構(gòu)、公司)具有遠(yuǎn)超普通情況下的算力,而且BERT這種輸出維度也只有幾萬(wàn)大小,詞向量的輸出維度則會(huì)有百萬(wàn)大小(可以想象下詞跟字在數(shù)量上的差別),所以自己進(jìn)行預(yù)訓(xùn)練的時(shí)候,需要根據(jù)擁有的算力和輸出維度來(lái)判斷是否要使用加速手段。

    參考

    【1】基于tf2的word2vec模型復(fù)現(xiàn):https://github.com/wellinxu/nlp_store/blob/master/papers/word2vec.py
    【2】word2vec 中的數(shù)學(xué)原理詳解:https://www.cnblogs.com/peghoty/p/3857839.html
    【3】word2vec Explained_Deriving Mikolov et al.’s Negative-Sampling Word-Embedding Method:https://arxiv.org/pdf/1402.3722v1.pdf

    往期精彩回顧適合初學(xué)者入門人工智能的路線及資料下載機(jī)器學(xué)習(xí)及深度學(xué)習(xí)筆記等資料打印機(jī)器學(xué)習(xí)在線手冊(cè)深度學(xué)習(xí)筆記專輯《統(tǒng)計(jì)學(xué)習(xí)方法》的代碼復(fù)現(xiàn)專輯 AI基礎(chǔ)下載機(jī)器學(xué)習(xí)的數(shù)學(xué)基礎(chǔ)專輯 獲取本站知識(shí)星球優(yōu)惠券,復(fù)制鏈接直接打開(kāi): https://t.zsxq.com/qFiUFMV 本站qq群704220115。加入微信群請(qǐng)掃碼:

    總結(jié)

    以上是生活随笔為你收集整理的【论文解读】NLP重铸篇之Word2vec的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。