詞向量背景介紹本章我們介紹詞的向量表征,也稱(chēng)為word embedding。詞向量是自然語(yǔ)言處理中常見(jiàn)的一個(gè)操作,是搜索引擎、廣告系統(tǒng)、推薦系統(tǒng)等互聯(lián)網(wǎng)服務(wù)背后常見(jiàn)的基礎(chǔ)技術(shù)。 在這些互聯(lián)網(wǎng)服務(wù)里,我們經(jīng)常要比較兩個(gè)詞或者兩段文本之間的相關(guān)性。為了做這樣的比較,我們往往先要把詞表示成計(jì)算機(jī)適合處理的方式。最自然的方式恐怕莫過(guò)于向量空間模型(vector space model)。 在這種方式里,每個(gè)詞被表示成一個(gè)實(shí)數(shù)向量(one-hot vector),其長(zhǎng)度為字典大小,每個(gè)維度對(duì)應(yīng)一個(gè)字典里的每個(gè)詞,除了這個(gè)詞對(duì)應(yīng)維度上的值是1,其他元素都是0。 One-hot vector雖然自然,但是用處有限。比如,在互聯(lián)網(wǎng)廣告系統(tǒng)里,如果用戶輸入的query是“母親節(jié)”,而有一個(gè)廣告的關(guān)鍵詞是“康乃馨”。雖然按照常理,我們知道這兩個(gè)詞之間是有聯(lián)系的——母親節(jié)通常應(yīng)該送給母親一束康乃馨;但是這兩個(gè)詞對(duì)應(yīng)的one-hot vectors之間的距離度量,無(wú)論是歐氏距離還是余弦相似度(cosine similarity),由于其向量正交,都認(rèn)為這兩個(gè)詞毫無(wú)相關(guān)性。 得出這種與我們相悖的結(jié)論的根本原因是:每個(gè)詞本身的信息量都太小。所以,僅僅給定兩個(gè)詞,不足以讓我們準(zhǔn)確判別它們是否相關(guān)。要想精確計(jì)算相關(guān)性,我們還需要更多的信息——從大量數(shù)據(jù)里通過(guò)機(jī)器學(xué)習(xí)方法歸納出來(lái)的知識(shí)。 在機(jī)器學(xué)習(xí)領(lǐng)域里,各種“知識(shí)”被各種模型表示,詞向量模型(word embedding model)就是其中的一類(lèi)。通過(guò)詞向量模型可將一個(gè) one-hot vector映射到一個(gè)維度更低的實(shí)數(shù)向量(embedding vector),如。在這個(gè)映射到的實(shí)數(shù)向量表示中,希望兩個(gè)語(yǔ)義(或用法)上相似的詞對(duì)應(yīng)的詞向量“更像”,這樣如“母親節(jié)”和“康乃馨”的對(duì)應(yīng)詞向量的余弦相似度就不再為零了。 詞向量模型可以是概率模型、共生矩陣(co-occurrence matrix)模型或神經(jīng)元網(wǎng)絡(luò)模型。在用神經(jīng)網(wǎng)絡(luò)求詞向量之前,傳統(tǒng)做法是統(tǒng)計(jì)一個(gè)詞語(yǔ)的共生矩陣。是一個(gè) 大小的矩陣,表示在所有語(yǔ)料中,詞匯表
但這樣的傳統(tǒng)做法有很多問(wèn)題: 基于神經(jīng)網(wǎng)絡(luò)的模型不需要計(jì)算存儲(chǔ)一個(gè)在全語(yǔ)料上統(tǒng)計(jì)的大表,而是通過(guò)學(xué)習(xí)語(yǔ)義信息得到詞向量,因此能很好地解決以上問(wèn)題。在本章里,我們將展示基于神經(jīng)網(wǎng)絡(luò)訓(xùn)練詞向量的細(xì)節(jié),以及如何用PaddlePaddle訓(xùn)練一個(gè)詞向量模型。 效果展示本章中,當(dāng)詞向量訓(xùn)練好后,我們可以用數(shù)據(jù)可視化算法t-SNE[4]畫(huà)出詞語(yǔ)特征在二維上的投影(如下圖所示)。從圖中可以看出,語(yǔ)義相關(guān)的詞語(yǔ)(如a, the, these; big, huge)在投影上距離很近,語(yǔ)意無(wú)關(guān)的詞(如say, business; decision, japan)在投影上的距離很遠(yuǎn)。
另一方面,我們知道兩個(gè)向量的余弦值在的區(qū)間內(nèi):兩個(gè)完全相同的向量余弦值為1, 兩個(gè)相互垂直的向量之間余弦值為0,兩個(gè)方向完全相反的向量余弦值為-1,即相關(guān)性和余弦值大小成正比。因此我們還可以計(jì)算兩個(gè)詞向量的余弦相似度: similarity: 0.899180685161 please input two words: big huge please input two words: from company similarity: -0.0997506977351 以上結(jié)果可以通過(guò)運(yùn)行 模型概覽在這里我們介紹三個(gè)訓(xùn)練詞向量的模型:N-gram模型,CBOW模型和Skip-gram模型,它們的中心思想都是通過(guò)上下文得到一個(gè)詞出現(xiàn)的概率。對(duì)于N-gram模型,我們會(huì)先介紹語(yǔ)言模型的概念,并在之后的訓(xùn)練模型中,帶大家用PaddlePaddle實(shí)現(xiàn)它。而后兩個(gè)模型,是近年來(lái)最有名的神經(jīng)元詞向量模型,由 Tomas Mikolov 在Google 研發(fā)[3],雖然它們很淺很簡(jiǎn)單,但訓(xùn)練效果很好。 語(yǔ)言模型在介紹詞向量模型之前,我們先來(lái)引入一個(gè)概念:語(yǔ)言模型。 語(yǔ)言模型旨在為語(yǔ)句的聯(lián)合概率函數(shù)建模, 其中表示句子中的第i個(gè)詞。語(yǔ)言模型的目標(biāo)是,希望模型對(duì)有意義的句子賦予大概率,對(duì)沒(méi)意義的句子賦予小概率。 這樣的模型可以應(yīng)用于很多領(lǐng)域,如機(jī)器翻譯、語(yǔ)音識(shí)別、信息檢索、詞性標(biāo)注、手寫(xiě)識(shí)別等,它們都希望能得到一個(gè)連續(xù)序列的概率。 以信息檢索為例,當(dāng)你在搜索“how long is a football bame”時(shí)(bame是一個(gè)醫(yī)學(xué)名詞),搜索引擎會(huì)提示你是否希望搜索"how long is a football game", 這是因?yàn)楦鶕?jù)語(yǔ)言模型計(jì)算出“how long is a football bame”的概率很低,而與bame近似的,可能引起錯(cuò)誤的詞中,game會(huì)使該句生成的概率最大。 對(duì)語(yǔ)言模型的目標(biāo)概率,如果假設(shè)文本中每個(gè)詞都是相互獨(dú)立的,則整句話的聯(lián)合概率可以表示為其中所有詞語(yǔ)條件概率的乘積,即:
然而我們知道語(yǔ)句中的每個(gè)詞出現(xiàn)的概率都與其前面的詞緊密相關(guān), 所以實(shí)際上通常用條件概率表示語(yǔ)言模型:
N-gram neural model在計(jì)算語(yǔ)言學(xué)中,n-gram是一種重要的文本表示方法,表示一個(gè)文本中連續(xù)的n個(gè)項(xiàng)。基于具體的應(yīng)用場(chǎng)景,每一項(xiàng)可以是一個(gè)字母、單詞或者音節(jié)。 n-gram模型也是統(tǒng)計(jì)語(yǔ)言模型中的一種重要方法,用n-gram訓(xùn)練語(yǔ)言模型時(shí),一般用每個(gè)n-gram的歷史n-1個(gè)詞語(yǔ)組成的內(nèi)容來(lái)預(yù)測(cè)第n個(gè)詞。 Yoshua Bengio等科學(xué)家就于2003年在著名論文 Neural Probabilistic Language Models [1] 中介紹如何學(xué)習(xí)一個(gè)神經(jīng)元網(wǎng)絡(luò)表示的詞向量模型。文中的神經(jīng)概率語(yǔ)言模型(Neural Network Language Model,NNLM)通過(guò)一個(gè)線性映射和一個(gè)非線性隱層連接,同時(shí)學(xué)習(xí)了語(yǔ)言模型和詞向量,即通過(guò)學(xué)習(xí)大量語(yǔ)料得到詞語(yǔ)的向量表達(dá),通過(guò)這些向量得到整個(gè)句子的概率。用這種方法學(xué)習(xí)語(yǔ)言模型可以克服維度災(zāi)難(curse of dimensionality),即訓(xùn)練和測(cè)試數(shù)據(jù)不同導(dǎo)致的模型不準(zhǔn)。注意:由于“神經(jīng)概率語(yǔ)言模型”說(shuō)法較為泛泛,我們?cè)谶@里不用其N(xiāo)NLM的本名,考慮到其具體做法,本文中稱(chēng)該模型為N-gram neural model。 我們?cè)谏衔闹幸呀?jīng)講到用條件概率建模語(yǔ)言模型,即一句話中第個(gè)詞的概率和該句話的前個(gè)詞相關(guān)。可實(shí)際上越遠(yuǎn)的詞語(yǔ)其實(shí)對(duì)該詞的影響越小,那么如果考慮一個(gè)n-gram, 每個(gè)詞都只受其前面
給定一些真實(shí)語(yǔ)料,這些語(yǔ)料中都是有意義的句子,N-gram模型的優(yōu)化目標(biāo)則是最大化目標(biāo)函數(shù):
其中表示根據(jù)歷史n-1個(gè)詞得到當(dāng)前詞的條件概率,表示參數(shù)正則項(xiàng)。
圖2展示了N-gram神經(jīng)網(wǎng)絡(luò)模型,從下往上看,該模型分為以下幾個(gè)部分:
- 對(duì)于每個(gè)樣本,模型輸入, 輸出句子第t個(gè)詞為字典中 每個(gè)輸入詞首先通過(guò)映射矩陣映射到詞向量。
其中表示第個(gè)樣本第類(lèi)的真實(shí)標(biāo)簽(0或1),表示第i個(gè)樣本第k類(lèi)softmax輸出的概率。 Continuous Bag-of-Words model(CBOW)CBOW模型通過(guò)一個(gè)詞的上下文(各N個(gè)詞)預(yù)測(cè)當(dāng)前詞。當(dāng)N=2時(shí),模型如下圖所示:
具體來(lái)說(shuō),不考慮上下文的詞語(yǔ)輸入順序,CBOW是用上下文詞語(yǔ)的詞向量的均值來(lái)預(yù)測(cè)當(dāng)前詞。即:
其中為第個(gè)詞的詞向量,分類(lèi)分?jǐn)?shù)(score)向量 ,最終的分類(lèi)采用softmax,損失函數(shù)采用多類(lèi)分類(lèi)交叉熵。 Skip-gram modelCBOW的好處是對(duì)上下文詞語(yǔ)的分布在詞向量上進(jìn)行了平滑,去掉了噪聲,因此在小數(shù)據(jù)集上很有效。而Skip-gram的方法中,用一個(gè)詞預(yù)測(cè)其上下文,得到了當(dāng)前詞上下文的很多樣本,因此可用于更大的數(shù)據(jù)集。
如上圖所示,Skip-gram模型的具體做法是,將一個(gè)詞的詞向量映射到個(gè)詞的詞向量(表示當(dāng)前輸入詞的前后各個(gè)詞),然后分別通過(guò)softmax得到這個(gè)詞的分類(lèi)損失值之和。 數(shù)據(jù)準(zhǔn)備數(shù)據(jù)介紹本教程使用Penn Treebank (PTB)(經(jīng)Tomas Mikolov預(yù)處理過(guò)的版本)數(shù)據(jù)集。PTB數(shù)據(jù)集較小,訓(xùn)練速度快,應(yīng)用于Mikolov的公開(kāi)語(yǔ)言模型訓(xùn)練工具[2]中。其統(tǒng)計(jì)情況如下:
數(shù)據(jù)預(yù)處理本章訓(xùn)練的是5-gram模型,表示在PaddlePaddle訓(xùn)練時(shí),每條數(shù)據(jù)的前4個(gè)詞用來(lái)預(yù)測(cè)第5個(gè)詞。PaddlePaddle提供了對(duì)應(yīng)PTB數(shù)據(jù)集的python包 預(yù)處理會(huì)把數(shù)據(jù)集中的每一句話前后加上開(kāi)始符號(hào) 如"I have a dream that one day" 一句提供了5條數(shù)據(jù): <s> I have a dream I have a dream that have a dream that one a dream that one day dream that one day <e> 最后,每個(gè)輸入會(huì)按其單詞次在字典里的位置,轉(zhuǎn)化成整數(shù)的索引序列,作為PaddlePaddle的輸入。 編程實(shí)現(xiàn)本配置的模型結(jié)構(gòu)如下圖所示:
首先,加載所需要的包: import math import paddle.v2 as paddle 然后,定義參數(shù): embsize = 32 # 詞向量維度 hiddensize = 256 # 隱層維度 N = 5 # 訓(xùn)練5-Gram 用于保存和加載word_dict和embedding table的函數(shù) # save and load word dict and embedding table def save_dict_and_embedding(word_dict, embeddings): with open("word_dict", "w") as f: for key in word_dict: f.write(key + " " + str(word_dict[key]) + "\n") with open("embedding_table", "w") as f: numpy.savetxt(f, embeddings, delimiter=',', newline='\n') def load_dict_and_embedding(): word_dict = dict() with open("word_dict", "r") as f: for line in f: key, value = line.strip().split(" ") word_dict[key] = int(value) embeddings = numpy.loadtxt("embedding_table", delimiter=",") return word_dict, embeddings 接著,定義網(wǎng)絡(luò)結(jié)構(gòu):
def wordemb(inlayer): wordemb = paddle.layer.table_projection( input=inlayer, size=embsize, param_attr=paddle.attr.Param( name="_proj", initial_std=0.001, learning_rate=1, l2_rate=0, sparse_update=True)) return wordemb
paddle.init(use_gpu=False, trainer_count=3) # 初始化PaddlePaddle word_dict = paddle.dataset.imikolov.build_dict() dict_size = len(word_dict) # 每個(gè)輸入層都接受整形數(shù)據(jù),這些數(shù)據(jù)的范圍是[0, dict_size) firstword = paddle.layer.data( name="firstw", type=paddle.data_type.integer_value(dict_size)) secondword = paddle.layer.data( name="secondw", type=paddle.data_type.integer_value(dict_size)) thirdword = paddle.layer.data( name="thirdw", type=paddle.data_type.integer_value(dict_size)) fourthword = paddle.layer.data( name="fourthw", type=paddle.data_type.integer_value(dict_size)) nextword = paddle.layer.data( name="fifthw", type=paddle.data_type.integer_value(dict_size)) Efirst = wordemb(firstword) Esecond = wordemb(secondword) Ethird = wordemb(thirdword) Efourth = wordemb(fourthword)
contextemb = paddle.layer.concat(input=[Efirst, Esecond, Ethird, Efourth])
hidden1 = paddle.layer.fc(input=contextemb, size=hiddensize, act=paddle.activation.Sigmoid(), layer_attr=paddle.attr.Extra(drop_rate=0.5), bias_attr=paddle.attr.Param(learning_rate=2), param_attr=paddle.attr.Param( initial_std=1. / math.sqrt(embsize * 8), learning_rate=1))
predictword = paddle.layer.fc(input=hidden1, size=dict_size, bias_attr=paddle.attr.Param(learning_rate=2), act=paddle.activation.Softmax())
cost = paddle.layer.classification_cost(input=predictword, label=nextword) 然后,指定訓(xùn)練相關(guān)的參數(shù):
parameters = paddle.parameters.create(cost) adagrad = paddle.optimizer.AdaGrad( learning_rate=3e-3, regularization=paddle.optimizer.L2Regularization(8e-4)) trainer = paddle.trainer.SGD(cost, parameters, adagrad) 下一步,我們開(kāi)始訓(xùn)練過(guò)程。
def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: print "Pass %d, Batch %d, Cost %f, %s" % ( event.pass_id, event.batch_id, event.cost, event.metrics) if isinstance(event, paddle.event.EndPass): result = trainer.test( paddle.batch( paddle.dataset.imikolov.test(word_dict, N), 32)) print "Pass %d, Testing metrics %s" % (event.pass_id, result.metrics) with open("model_%d.tar"%event.pass_id, 'w') as f: trainer.save_parameter_to_tar(f) trainer.train( paddle.batch(paddle.dataset.imikolov.train(word_dict, N), 32), num_passes=100, event_handler=event_handler) Pass 0, Batch 0, Cost 7.870579, {'classification_error_evaluator': 1.0}, Testing metrics {'classification_error_evaluator': 0.999591588973999} Pass 0, Batch 100, Cost 6.136420, {'classification_error_evaluator': 0.84375}, Testing metrics {'classification_error_evaluator': 0.8328699469566345} Pass 0, Batch 200, Cost 5.786797, {'classification_error_evaluator': 0.8125}, Testing metrics {'classification_error_evaluator': 0.8328542709350586} ... 訓(xùn)練過(guò)程是完全自動(dòng)的,event_handler里打印的日志類(lèi)似如上所示: 經(jīng)過(guò)30個(gè)pass,我們將得到平均錯(cuò)誤率為classification_error_evaluator=0.735611。 保存詞典和embedding訓(xùn)練完成之后,我們可以把詞典和embedding table單獨(dú)保存下來(lái),后面可以直接使用 # save word dict and embedding table embeddings = parameters.get("_proj").reshape(len(word_dict), embsize) save_dict_and_embedding(word_dict, embeddings) 應(yīng)用模型訓(xùn)練模型后,我們可以加載模型參數(shù),用訓(xùn)練出來(lái)的詞向量初始化其他模型,也可以將模型查看參數(shù)用來(lái)做后續(xù)應(yīng)用。 查看詞向量PaddlePaddle訓(xùn)練出來(lái)的參數(shù)可以直接使用 embeddings = parameters.get("_proj").reshape(len(word_dict), embsize) print embeddings[word_dict['apple']] [-0.38961065 -0.02392169 -0.00093231 0.36301503 0.13538605 0.16076435 -0.0678709 0.1090285 0.42014077 -0.24119169 -0.31847557 0.20410083 0.04910378 0.19021918 -0.0122014 -0.04099389 -0.16924137 0.1911236 -0.10917275 0.13068172 -0.23079982 0.42699069 -0.27679482 -0.01472992 0.2069038 0.09005053 -0.3282454 0.12717034 -0.24218646 0.25304323 0.19072419 -0.24286366] 修改詞向量獲得到的embedding為一個(gè)標(biāo)準(zhǔn)的numpy矩陣。我們可以對(duì)這個(gè)numpy矩陣進(jìn)行修改,然后賦值回去。 def modify_embedding(emb): # Add your modification here. pass modify_embedding(embeddings) parameters.set("_proj", embeddings) 計(jì)算詞語(yǔ)之間的余弦距離兩個(gè)向量之間的距離可以用余弦值來(lái)表示,余弦值在的區(qū)間內(nèi),向量間余弦值越大,其距離越近。這里我們?cè)?code>calculate_dis.py中實(shí)現(xiàn)不同詞語(yǔ)的距離度量。 用法如下: from scipy import spatial emb_1 = embeddings[word_dict['world']] emb_2 = embeddings[word_dict['would']] print spatial.distance.cosine(emb_1, emb_2) 0.99375076448 總結(jié)本章中,我們介紹了詞向量、語(yǔ)言模型和詞向量的關(guān)系、以及如何通過(guò)訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型獲得詞向量。在信息檢索中,我們可以根據(jù)向量間的余弦?jiàn)A角,來(lái)判斷query和文檔關(guān)鍵詞這二者間的相關(guān)性。在句法分析和語(yǔ)義分析中,訓(xùn)練好的詞向量可以用來(lái)初始化模型,以得到更好的效果。在文檔分類(lèi)中,有了詞向量之后,可以用聚類(lèi)的方法將文檔中同義詞進(jìn)行分組。希望大家在本章后能夠自行運(yùn)用詞向量進(jìn)行相關(guān)領(lǐng)域的研究。 |
|
來(lái)自: 冬城溪酒之秦文 > 《待分類(lèi)》