中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
基于深度學(xué)習(xí)的命名實體識別與關(guān)系抽取(下)


摘要:構(gòu)建知識圖譜包含四個主要的步驟:數(shù)據(jù)獲取、知識抽取、知識融合和知識加工。其中最主要的步驟是知識抽取。知識抽取包括三個要素:命名實體識別(NER)實體關(guān)系抽?。≧E)屬性抽取。其中屬性抽取可以使用python爬蟲爬取百度百科、維基百科等網(wǎng)站,操作較為簡單,因此命名實體識別(NER)和實體關(guān)系抽取(RE)是知識抽取中非常重要的部分,同時其作為自然語言處理(NLP)中最遇到的問題一直以來是科研的研究方向之一。

本文將以深度學(xué)習(xí)的角度,對命名實體識別和關(guān)系抽取進(jìn)行分析,在閱讀本文之前,讀者需要了解深度神經(jīng)網(wǎng)絡(luò)的基本原理、知識圖譜的基本內(nèi)容以及關(guān)于循環(huán)神經(jīng)網(wǎng)絡(luò)的模型。

本文的主要結(jié)構(gòu)如下,首先引入知識抽取的相關(guān)概念;其次對詞向量(word2vec)做分析;然后詳細(xì)講解循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)、長短期記憶神經(jīng)網(wǎng)絡(luò)(LSTM)、門控神經(jīng)單元模型(GRU);了解基于文本的卷積神經(jīng)網(wǎng)絡(luò)模型(Text-CNN);講解隱馬爾可夫模型(HMM)與條件隨機場等圖概率模型(CRF);詳細(xì)分析如何使用這些模型實現(xiàn)命名實體識別與關(guān)系抽取,詳細(xì)分析端到端模型(End-to-end/Joint);介紹注意力機制(Attention)及其NLP的應(yīng)用;隨后介紹知識抽取的應(yīng)用與挑戰(zhàn),最后給出TensorFlow源碼、推薦閱讀以及總結(jié)。本文基本總結(jié)了整個基于深度學(xué)習(xí)的NER與RC的實現(xiàn)過程以及相關(guān)技術(shù),篇幅會很長,請耐心閱讀

  • 九、卷積神經(jīng)網(wǎng)絡(luò)
  • 十、基于文本的卷積神經(jīng)網(wǎng)絡(luò)(Text-CNN)的關(guān)系抽取
  • 十一、基于依存關(guān)系模型的關(guān)系抽取
  • 十二、基于遠(yuǎn)程監(jiān)督的關(guān)系抽取
  • 十三、注意力機制(Attention)
  • 十四、基于注意力機制的命名實體識別與關(guān)系抽取
  • 十五、三元組的存儲——圖形數(shù)據(jù)庫
  • 十六、Tensorflow實現(xiàn)命名實體識別與關(guān)系抽取
  • 十七、推薦閱讀書籍
  • 十八、總結(jié)

九、卷積神經(jīng)網(wǎng)絡(luò)

卷積神經(jīng)網(wǎng)絡(luò)是神經(jīng)網(wǎng)絡(luò)的另一個演化體,其通常用于圖像處理、視頻處理中,在自然語言處理范圍內(nèi),常被用來進(jìn)行文本挖掘、情感分類中。因此基于文本類的卷積神經(jīng)網(wǎng)絡(luò)也廣泛的應(yīng)用在關(guān)系抽取任務(wù)中。

9.1 卷積運算

卷積神經(jīng)網(wǎng)絡(luò)主要通過卷積運算來實現(xiàn)對多維數(shù)據(jù)的處理,例如對一副圖像數(shù)據(jù),其像素為6x6,通過設(shè)計一個卷積核(或稱過濾器)filter來對該圖形數(shù)據(jù)進(jìn)行掃描,卷積核可以實現(xiàn)對數(shù)據(jù)的過濾,例如下面例子中的卷積核可以過濾出圖像中的垂直邊緣,也稱為垂直邊緣檢測器。

例如假設(shè)矩陣:

表示一個原始圖像類數(shù)據(jù),選擇卷積核(垂直邊緣檢測器):

然后從原始圖像左上方開始,一次向右、向下進(jìn)行掃描,掃描的窗口為該卷積核,每次掃描時,被掃描的9個數(shù)字分別與卷積核對應(yīng)的數(shù)字做乘積(element-wise),并求這9個數(shù)字的和。例如掃描的第一個窗口應(yīng)該為:

則最后生成新的矩陣:

以上的事例稱為矩陣的卷積運算。在卷積神經(jīng)網(wǎng)絡(luò)中,卷積運算包括如下幾個參數(shù):
(1)數(shù)據(jù)維度:即對當(dāng)前需要做卷積運算的矩陣的維度,通常為三維矩陣,維度為 $mnd4。
(2)卷積核維度:即卷積核的維度,記為 ,當(dāng)d為1時,為二維卷積核,通常對二維矩陣進(jìn)行卷積運算,當(dāng) 時為三維卷積核,對圖像類型數(shù)據(jù)進(jìn)行卷積運算。
(3)卷積步長stride:即卷積核所在窗口在輸入數(shù)據(jù)上每次滑動的步數(shù)。上面的事例中明顯步長為1。
(4)數(shù)據(jù)填充padding:在卷積操作中可以發(fā)現(xiàn)新生產(chǎn)的矩陣維度比原始矩陣維度變小,在一些卷積神經(jīng)網(wǎng)絡(luò)運算中,為了保證數(shù)據(jù)維度不變,設(shè)置padding=1,對原始矩陣擴充0。這種方式可以使得邊緣和角落的元素可以被多次卷積運算,也可以保證新生成的矩陣維度不變??梢杂嬎愠雒總€維度應(yīng)該向外擴充

9.2 卷積神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)

卷積神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)如圖所示:

卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)

其主要有輸入層、卷積層、池化層、全相連接層和輸出層組成,其中卷積層與池化層通常組合在一起,并在一個模型中循環(huán)多次出現(xiàn)。
(1)輸入層:輸入層主要是將數(shù)據(jù)輸入到模型中,數(shù)據(jù)通常可以是圖像數(shù)據(jù)(像素寬像素高三原色(3)),也可以是經(jīng)過處理后的多維數(shù)組矩陣。
(2)卷積層:卷積層主要是對當(dāng)前的輸入數(shù)據(jù)(矩陣)進(jìn)行卷積運算,9.1節(jié)簡單介紹了卷積運算,下面對卷積運算進(jìn)行符號化表示:

設(shè)當(dāng)前的輸入數(shù)據(jù)為圖像 ,其中 表示 的寬, 表示 的高, 表示 的層數(shù)。

設(shè)卷積神經(jīng)網(wǎng)絡(luò)有2次卷積和池化操作(如上圖),兩次卷積操作分別有 個卷積核 (每個卷積核各不相同,不同的卷積核可以對原始圖像數(shù)據(jù)進(jìn)行不同方面的特征提取),每個卷積核的維度設(shè)為 (卷積核維度一般取奇數(shù)個,使得卷積核可以以正中間的元素位置中心對稱,卷積核的層數(shù)需要與上一輪輸出的數(shù)據(jù)層數(shù)一致),因此對于第 次中的第 個卷積核的卷積操作即為:

其中 表示卷積后的矩陣的第p行第q列, 表示第 次卷積池化操作后的池化層值, 即為原始圖像數(shù)據(jù) 。

卷積運算后,將生成 個維度為 的新矩陣

Ps:公式是三層循環(huán)求和,在程序設(shè)計中可以使用矩陣的對應(yīng)位置求積。

**(3)池化層(polling)**:池化層作用是為了降低卷積運算后產(chǎn)生的數(shù)據(jù)維度,池化操作包括最大池化(max-polling)和平均池化(avg-polling)。

池化操作是一種特殊的卷積,其并不像卷積操作一樣逐個相乘,對于最大池化,是取當(dāng)前所在窗口所在的數(shù)據(jù)中最大的數(shù)據(jù);對于平均池化則是取當(dāng)前窗口所有值的平均值。池化層的窗口維度一般為2*2,窗口的滑動步長stride=2。例如對于9.1節(jié)中經(jīng)過卷積操作的矩陣的池化操作后應(yīng)為:

池化層可以通過提取出相對重要的特征來減少數(shù)據(jù)的維度(即減少數(shù)據(jù)量),即減少了后期的運算,同時可以防止過擬合。第 次卷積池化操作中,對第 個卷積核過濾生成的矩陣進(jìn)行最大或平均池化操作的符號表示如下:

池化操作后矩陣的數(shù)量不變,僅僅是維度變小了。

(4)全相連層:在多次卷積和池化運算后,將生成若干個小矩陣,通過concatenate操作,將其轉(zhuǎn)換為一維度的向量。例如有16個10*10的矩陣,其可以拼接成一個長度為400的向量。全相連層是通過構(gòu)建一個多層的BP神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),通過每一層的權(quán)重矩陣和偏向的前向傳播,將其不斷的降維。全相連層的輸入神經(jīng)元個數(shù)即為拼接形成向量的個數(shù),隱含層個數(shù)及每層的神經(jīng)元數(shù)量可自定義,輸出層的神經(jīng)元個數(shù)通常為待分類的個數(shù)。
(5)softmax層:與傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)和循環(huán)神經(jīng)網(wǎng)絡(luò)類似,最終的全相連層的輸出是每個類別的概率值,因此需要對其進(jìn)行softmax操作,則最大值所對應(yīng)的的類記為卷積神經(jīng)網(wǎng)絡(luò)的預(yù)測結(jié)果。

9.3 卷積神經(jīng)網(wǎng)絡(luò)的訓(xùn)練

卷積神經(jīng)網(wǎng)絡(luò)的訓(xùn)練與BP神經(jīng)網(wǎng)絡(luò)一樣,選擇損失函數(shù) 刻畫模型的誤差程度,選擇最優(yōu)化模型(梯度下降法)進(jìn)行最小化損失函數(shù)。

卷積神經(jīng)網(wǎng)絡(luò)的超參數(shù)包括卷積池化的次數(shù) 、每次卷積池化操作中卷積核的個數(shù) 和其維度 、卷積操作中是否填充(padding取值0或1)、卷積操作中的步長stride、池化層的窗口大?。ㄒ话闳?)及池化類型(一般取max-polling)、全相連的層數(shù)及隱含層神經(jīng)元個數(shù)、激活函數(shù)和學(xué)習(xí)率 等。參數(shù)包括卷積層的每個卷積核的值、全相連層中權(quán)重矩陣和偏向。卷積神經(jīng)網(wǎng)絡(luò)即通過梯度下降法不斷對參數(shù)進(jìn)行調(diào)整,以最小化損失函數(shù)。

卷積神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過程分為前向傳播和反向傳播,前向傳播時需要初始化相關(guān)的參數(shù)和超參數(shù),反向傳播則使用梯度下降法調(diào)參。梯度下降的優(yōu)化中也采用mini-batch法、采用Adam梯度下降策略試圖加速訓(xùn)練,添加正則項防止過擬合。

十、基于文本的卷積神經(jīng)網(wǎng)絡(luò)(Text-CNN)的關(guān)系抽取

卷積神經(jīng)網(wǎng)絡(luò)已經(jīng)成功地廣泛應(yīng)用與圖像識別領(lǐng)域,而對于文本類數(shù)據(jù)近期通過論文形式被提出。在2014年,紐約大學(xué)的Yoon Kim發(fā)表的《Convolutional Neural Networks for Sentence Classification》一文開辟了CNN的另一個可應(yīng)用的領(lǐng)域——自然語言處理。
??Yoon提出的Text-CNN與第九節(jié)的卷積神經(jīng)網(wǎng)絡(luò)有一定區(qū)別,但設(shè)計思想是一樣的,都是通過設(shè)計一個窗口并與數(shù)據(jù)進(jìn)行計算。

10.1 數(shù)據(jù)處理

基于文本的Text-CNN的輸入數(shù)據(jù)是預(yù)訓(xùn)練的詞向量word embeddings,詞向量可參考本文的第2節(jié)內(nèi)容。對于一句話中每個單詞均為 維的詞向量,因此對于長度為 的一句話則可用維度為 的矩陣 表示,如圖所示:

10.2 Text-CNN的結(jié)構(gòu)

(1)卷積層: 對某一句話對應(yīng)的預(yù)訓(xùn)練的詞向量矩陣維度為 ,設(shè)計一個過濾器窗口 ,其維度為 ,其中 即為詞向量的長度, 表示窗口所含的單詞個數(shù)(Yoon 在實驗中設(shè)置了 的取值為2、3、4)。其次不斷地滑動該窗口,每次滑動一個位置時,完成如下的計算:

其中 為非線性激活函數(shù), 表示該句子中第 的單詞組成的詞向量矩陣, 表示兩個矩陣的對應(yīng)位乘積, 表示當(dāng)前窗口位置的取值。
??因此對于長度為 的句子,維度為 的過濾器窗口將可以產(chǎn)生 個值組成的集合:

(2)最大池化層:為了能夠提取出其中最大的特征,Yoon對其進(jìn)行max-over-time操作,即取出集合 中的最大值 。另外可以分析得到max-over-time操作還可以解決每句話長度不一致的問題。

Text-CNN的結(jié)構(gòu)如圖所示:

(3)全相連層:對于 個過濾器窗口,將產(chǎn)生 個值組成的向量 ,Text-CNN通過設(shè)置一個全相連層,將該向量映射為長度為 的向量, 即為待預(yù)測的類的個數(shù),設(shè)置softmax激活函數(shù)即可轉(zhuǎn)換為各個類的概率值。

10.3 Text-CNN的訓(xùn)練

Text-CNN的前向傳播即為上圖所示流程。反向傳播采用梯度下降法
(1)正則化防止過擬合

對于 個過濾器窗口產(chǎn)生的向量 ,則輸出值為 ,為了防止過擬合,采用 dropout權(quán)重衰減法,表達(dá)式為:

其中 表示對應(yīng)位置相乘, 表示以概率 產(chǎn)生只含有0或1元素的矩陣, 則表示可能矩陣 中有一半的元素為1 。

在測試環(huán)境,則為了限制 范式的權(quán)重矩陣,設(shè)置 ,當(dāng) 時進(jìn)行梯度下降的調(diào)整。

Ps: 正則化權(quán)重衰減可參考:正則化方法:L1和L2 regularization、數(shù)據(jù)集擴增、dropout[1]

(2)超參數(shù)設(shè)置:Yoon設(shè)置了一系列超參數(shù)如下:

超參數(shù)取值
window( h h h值)3/4/5
dropout( p p p值)0.5
l 2 l_2 l2( s s s值)3
mini-batch50

10.4 Text-CNN應(yīng)用于關(guān)系抽取

Text-CNN在句子分類方面有不錯的效果,而對于關(guān)系抽取問題,可以將其視為句子分類任務(wù)。

假設(shè)長度為 具有 個實體的句子 ,其中實體分別為 ,其中 , 表示第 個實體的長度。該句子內(nèi)所有實體將組成一個集合 。任意取集合 中的兩個實體作為一個組合 ,其中 ,并考察其是否具有關(guān)系,關(guān)系表示為 ,其取值為 表示關(guān)系類標(biāo)個數(shù),即預(yù)設(shè)的試題關(guān)系的種類,每一個關(guān)系對應(yīng)一個整數(shù),0則表示沒有關(guān)系。因此對于一個包含 個實體 的句子 將產(chǎn)生 個關(guān)系組合

是將一句話 中不同組合的實體及其之間的所有單詞組成一個新子句,并將其對應(yīng)的word embeddings作為輸入數(shù)據(jù)。子句 ,其對應(yīng)的關(guān)系類標(biāo)為 ,因此對于 將有 個子句組成的樣本集 。

對于該樣本,由于每個子句的長度不一致,因此需要進(jìn)行填充0方式使各個子句長度一致,然后將其喂給Text-CNN。Text-CNN采用mini-batch法進(jìn)行梯度下降,其訓(xùn)練過程可參考10.3節(jié)內(nèi)容。

Ps:在諸多論文中都有表示,兩個實體是否具有關(guān)系與其相對距x離有關(guān)系,而一般來說兩者的距離超過25(即兩個實體之間有超過25個單詞),則兩個實體具有關(guān)系的概率將會趨近于0,因此認(rèn)為實體在一個句子內(nèi)會產(chǎn)生關(guān)聯(lián),而不在同一個句子內(nèi)認(rèn)為不具有關(guān)聯(lián)性,當(dāng)然這種假設(shè)也存在一定的問題,但在實驗中影響不大。

十一、基于依存關(guān)系模型的關(guān)系抽取

卷積神經(jīng)網(wǎng)絡(luò)可以很好的對實體與實體關(guān)系進(jìn)行分類,而在自然語言處理中,通常會對句子進(jìn)行句法分析,通過不同語言的語法規(guī)則建立起模型——依存關(guān)系。基于依存關(guān)系的關(guān)系抽取是該模型的一個應(yīng)用方向,其主要是通過句法分析實現(xiàn),而不是通過深度模型自動挖掘。本文雖然主要是以深度學(xué)習(xí)角度分析,但傳統(tǒng)的“淺層態(tài)”模型也需要了解,以方便將其與深度模型進(jìn)行整合。

11.1依存句法分析

基于依存關(guān)系模型的關(guān)系抽取也叫做開放式實體關(guān)系抽取,解決關(guān)系抽取的思路是對一個句子的詞性進(jìn)行預(yù)處理,例如對于一句話“馬云在杭州創(chuàng)辦了阿里巴巴”。不同于之前所講的深度模型,詞性分析則是對該句話中每一個詞進(jìn)行預(yù)先標(biāo)注,例如“馬云”、“杭州”和“阿里巴巴”被標(biāo)記為名詞,“在”和“了”被標(biāo)記為介詞,“創(chuàng)辦”被標(biāo)記為動詞。所謂的依存關(guān)系則體現(xiàn)在不同詞性的詞之間存在著依存關(guān)系路徑?!霸凇蓖ǔ:竺娓孛簿褪敲~,“創(chuàng)辦”動詞前通常為名詞,而“在…創(chuàng)辦了”便是一個依存關(guān)系。因此依存關(guān)系即為不同詞性的詞之間的關(guān)系結(jié)構(gòu),下標(biāo)列出了關(guān)于中文的依存標(biāo)注:

例如對于下面一句話,句子開頭設(shè)置一個“ROOT”作為開始,句子結(jié)束則為句號“?!保来骊P(guān)系可以表示為下圖:

在傳統(tǒng)的實體識別中,是通過基于規(guī)則的詞性分析實現(xiàn)的,最簡單的是正則表達(dá)式匹配,其次是使用NLP詞性標(biāo)注工具。通常認(rèn)為名詞是實體,因此實體可以通過詞性標(biāo)注實現(xiàn)抽取。因為詞性對每一個詞進(jìn)行了標(biāo)注,自然根據(jù)語法規(guī)則可以構(gòu)建起上圖所示的依存關(guān)系。每一個詞根據(jù)語法規(guī)則構(gòu)建起一條關(guān)系路徑。所有路徑的最終起始點即為句子的核心(HED)。

11.2 依存句法分析實現(xiàn)關(guān)系抽取

對于關(guān)系抽取問題,基于依存關(guān)系的關(guān)系抽取模型中,關(guān)系詞并非是預(yù)先設(shè)置的類別,而是存在于當(dāng)前的句子中。例如“馬云在杭州創(chuàng)辦了阿里巴巴”,預(yù)定義的關(guān)系可能是“創(chuàng)始人”,而“創(chuàng)始人”一詞在句子中不存在,但是句中存在一個與其相似的詞“創(chuàng)辦”。因此在句法分析中,能夠提取出核心詞“創(chuàng)辦”,該詞前面有一個名詞“杭州”,而“杭州”前面有一個介詞“在”,因此“在杭州”是一個介賓短語,依存路徑被標(biāo)記為POB,所以“杭州”不是“創(chuàng)辦”的主語,自然是“馬云”?!皠?chuàng)辦”一詞后面是助詞“了”可以省略,再往后則是名稱“阿里巴巴”,因此“創(chuàng)辦阿里巴巴”為動賓關(guān)系VOB,與上面的“探索機制”一樣。因此可分析得到語義為“馬云創(chuàng)辦阿里巴巴”,核心詞“創(chuàng)辦”即為關(guān)系,“馬云”和阿里巴巴則是兩個實體。
??因此基于依存關(guān)系的關(guān)系抽取算法步驟如下:
??(1)獲取句子 x x x;
??(2)對句子 x x x 進(jìn)行詞性標(biāo)注;
??(3)構(gòu)建依存關(guān)系路徑,并依據(jù)依存標(biāo)注表對路徑進(jìn)行標(biāo)注;
??(4)提取核心詞;
??(5)構(gòu)建起動賓結(jié)構(gòu),以核心詞為關(guān)系尋找主語和賓語作為兩個實體。

可以發(fā)現(xiàn),基于依存關(guān)系不僅可以抽取關(guān)系,也可以提取出其對應(yīng)的實體。因此包括基于深度學(xué)習(xí)模型在內(nèi),一種端到端的聯(lián)合實體識別與關(guān)系抽取被提出。

十二、基于遠(yuǎn)程監(jiān)督的關(guān)系抽取

參考博主寫的博文:基于監(jiān)督學(xué)習(xí)和遠(yuǎn)程監(jiān)督的神經(jīng)關(guān)系抽取[2]

十三、注意力機制(Attention)

基于注意力機制的模型目前有許多,本文暫提供兩種論文解讀,詳情博主兩篇論文解讀:
1.論文解讀:Attention Is All You Need[3]
2.論文解讀:Attention-Based Bidirectional Long Short-Term Memory Networks for Relation Classification[4]

十四、基于注意力機制的命名實體識別與關(guān)系抽取

應(yīng)用注意力機制實現(xiàn)命名實體識別和關(guān)系抽?。ㄒ舶ㄆ渌匀徽Z言處理任務(wù))成為常用之計,由于篇幅過大,本章節(jié)決定另開辟博文講述。

十五、知識的存儲——圖形數(shù)據(jù)庫

在通過一系列算法實現(xiàn)對非結(jié)構(gòu)化數(shù)據(jù)進(jìn)行命名實體識別和關(guān)系抽取之后,按照知識圖譜中知識抽取的步驟將其存儲在數(shù)據(jù)庫中。通常使用的數(shù)據(jù)庫為關(guān)系數(shù)據(jù)庫,即滿足第一和第二范式的“表格型”存儲模型,但關(guān)系型數(shù)據(jù)庫不能夠很直接的處理具有關(guān)系類別的數(shù)據(jù),即若表達(dá)不同數(shù)據(jù)之間的關(guān)系需要構(gòu)建外鍵,并通過外連接方式進(jìn)行關(guān)聯(lián),這對于百萬級別數(shù)據(jù)來說,對查詢速度以及后期的數(shù)據(jù)更新與維護(hù)都是不利的,因此需要一個能夠體現(xiàn)不同實體關(guān)系的數(shù)據(jù)庫。因此引入圖數(shù)據(jù)庫概念。

15.1 圖數(shù)據(jù)庫

圖數(shù)據(jù)庫包含的元素主要由結(jié)點、屬性、關(guān)系,如圖:

關(guān)系圖

“馬云”和“阿里巴巴”為實體,對于圖數(shù)據(jù)庫中的結(jié)點,“馬云” 與“阿里巴巴”二者實體之間有一個定向關(guān)系“創(chuàng)辦”,對于于數(shù)據(jù)庫中的關(guān)系,每個實體結(jié)點都有各自的屬性,即圖數(shù)據(jù)庫中的屬性,因此構(gòu)建圖數(shù)據(jù)庫的關(guān)鍵三要素即為實體1,實體2和關(guān)系,即為三元組。

Ps:常用的圖數(shù)據(jù)庫有 Neo4j,NoSQL,Titan,OrientDB和 ArangoDB。關(guān)于圖數(shù)據(jù)庫的概念可參考:圖數(shù)據(jù)庫,數(shù)據(jù)庫中的“黑科技”[5],初識圖數(shù)據(jù)與圖數(shù)據(jù)庫[6]。

15.2 Neo4j的簡介

Neo4j是由java實現(xiàn)的開源NOSQL圖數(shù)據(jù)庫,數(shù)據(jù)庫分為關(guān)系型和非關(guān)系型兩種類型。其中非關(guān)系型又分為Graph(圖形),Document(文檔),Cloumn Family(列式),以及Key-Value Store(KV),這四種類型數(shù)據(jù)庫分別使用不同的數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲。因此它們所適用的場景也不盡相同。(引自Neo4j簡介[7]

15.3 簡單的CQL語句

在手動編輯Neo4j數(shù)據(jù)庫時,需要編寫CQL語句實現(xiàn)對數(shù)據(jù)庫的操作。Java在實現(xiàn)對Neo4j時也需要編寫CQL語句,因此在知識抽取后的保存工作,需要對CQL語句有所了解,下面是簡單的CQL語句:
1、創(chuàng)建
(1)創(chuàng)建單個標(biāo)簽到結(jié)點

CREATE(結(jié)點名稱:標(biāo)簽名)

(2)創(chuàng)建多個標(biāo)簽到結(jié)點:

CREATE(結(jié)點名稱:標(biāo)簽名1:標(biāo)簽名2:....)

(3)創(chuàng)建結(jié)點和屬性:

CREATE(結(jié)點名稱:標(biāo)簽名{屬性1名稱:屬性值1,屬性2名稱:屬性值2,....})

(4)MERGE創(chuàng)建結(jié)點和屬性(檢測是否存在結(jié)點和屬性一模一樣的,若存在則不創(chuàng)建,不存在則創(chuàng)建)

MERGE(結(jié)點名稱:標(biāo)簽名{屬性1名稱:屬性值1,屬性2名稱:屬性值2,....})

2、檢索:
(1)檢索一個結(jié)點

MATCH(結(jié)點名稱:標(biāo)簽名) RETURN 結(jié)點名稱

(2)檢索一個結(jié)點的某個或多個屬性

MATCH(結(jié)點名稱:標(biāo)簽名) RETURN 結(jié)點名稱.屬性名稱1,結(jié)點名稱.屬性名稱2,...

(3)使用WHERE關(guān)鍵字搜索

MATCH(結(jié)點名稱:標(biāo)簽名) WHERE 標(biāo)簽名.屬性名 關(guān)系運算符(=>OR等) 數(shù)值 RETURN 結(jié)點名稱.屬性名稱1,結(jié)點名稱.屬性名稱2,...

Ps:其中WHERE 標(biāo)簽名.屬性名 IS NOT NULL可以過濾掉不存在該屬性值的結(jié)點
Ps:WHERE 標(biāo)簽名.屬性名 IN [100,102] 表示篩選出該屬性值介于100和102之間

(4)使用ORDER BY進(jìn)行排序

MATCH(結(jié)點名稱:標(biāo)簽名) RETURN 結(jié)點名稱.屬性名1,... ORDER BY 結(jié)點名稱.屬性名1

(DESC表示逆序,ASC表示正序)

(5)使用UNION進(jìn)行聯(lián)合查詢(要求具有相同的列名稱和數(shù)據(jù)類型,過濾掉重復(fù)數(shù)據(jù))

MATCH(結(jié)點名稱1.標(biāo)簽名)
RETURN 結(jié)點名稱1.屬性名1 as 屬性名s1,結(jié)點名稱1.屬性名2 as 屬性名s2,..
UNION
MATCH(結(jié)點名稱2.標(biāo)簽名)
RETURN 結(jié)點名稱2.屬性名1 as 屬性名s1,結(jié)點名稱2.屬性名2 as 屬性名s2,..

Ps:若使用UNION ALL,則不過濾重復(fù)行,其他與UNION一致

(6)LIMIT限制返回記錄個數(shù)

MATCH(結(jié)點名稱:標(biāo)簽名稱)
RETURN 結(jié)點名稱.屬性名1,...
LIMIT 5(限制最多返回5條數(shù)據(jù))

(7)SKIP跳過前面的記錄顯示后面的

MATCH(結(jié)點名稱:標(biāo)簽名稱)
RETURN 結(jié)點名稱.屬性名1,...
SKIP 5(跳過前5條數(shù)據(jù)顯示第6個及以后)

3、接點與關(guān)系

(1)添加新結(jié)點并為關(guān)系創(chuàng)建標(biāo)簽:

CREATE(結(jié)點1名稱:標(biāo)簽名1)-[關(guān)系名稱:關(guān)系標(biāo)簽名]->(結(jié)點2名稱:標(biāo)簽名2)

(2)為現(xiàn)有的結(jié)點添加關(guān)系:

MATCH(結(jié)點1名稱:標(biāo)簽名1),(結(jié)點2名稱:標(biāo)簽名2)
WHERE 條件
CREATE(結(jié)點1名稱)-[關(guān)系名稱:關(guān)系標(biāo)簽名]->(結(jié)點2名稱)

(3)為現(xiàn)有結(jié)點添加/修改屬性

MATCH(結(jié)點名稱:標(biāo)簽名)
SET 結(jié)點名稱.新屬性名 = 值
RETURN 結(jié)點名稱

4、刪除

(1)刪除結(jié)點:

MATCH(結(jié)點名稱:標(biāo)簽名) DELETE 結(jié)點名稱

(2)刪除結(jié)點及關(guān)系

MATCH(結(jié)點1名稱:標(biāo)簽名1)-[關(guān)系名稱]->(結(jié)點2名稱:標(biāo)簽名2)
DELETE 結(jié)點1名稱,結(jié)點2名稱,關(guān)系名稱

(3)刪除結(jié)點的屬性

MATCH(結(jié)點名稱:標(biāo)簽名)
REMOVE 結(jié)點名稱.屬性名,...
RETURN 結(jié)點名稱

(4)刪除結(jié)點的某個標(biāo)簽

MATCH(結(jié)點名稱:標(biāo)簽名1)
REMOVE 結(jié)點名稱:標(biāo)簽名2

十六、Tensorflow實現(xiàn)命名實體識別與關(guān)系抽取

本文以及詳細(xì)的分析了實現(xiàn)命名實體識別與關(guān)系抽取的各種模型,本節(jié)將提供兩個項目程序,一個是基于Bi-LSTM和CRF的命名實體識別,另一個是基于Bi-LSTM-LSTM和Text-CNN的端到端模型的實體與關(guān)系的聯(lián)合抽取。

16.1 基于Bi-LSTM和CRF的命名實體識別

(1)main函數(shù),主要解決控制臺參數(shù)獲取、數(shù)據(jù)獲取以及調(diào)用模型:

import tensorflow as tf
import numpy as np
import os, argparse, time, random
from model import BiLSTM_CRF
from utils import str2bool, get_logger, get_entity
from data import read_corpus, read_dictionary, tag2label, random_embedding

## Tensorflow的Session運行環(huán)境
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'  # default: 0
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
config.gpu_options.per_process_gpu_memory_fraction = 0.2  # need ~700MB GPU memory

## 控制臺輸入的超參數(shù)
parser = argparse.ArgumentParser(description='BiLSTM-CRF for Chinese NER task')
parser.add_argument('--train_data'type=str, default='data_path'help='train data source')
parser.add_argument('--test_data'type=str, default='data_path'help='test data source')
parser.add_argument('--batch_size'type=int, default=64, help='#sample of each minibatch')
parser.add_argument('--epoch'type=int, default=10, help='#epoch of training')
parser.add_argument('--hidden_dim'type=int, default=300, help='#dim of hidden state')
parser.add_argument('--optimizer'type=str, default='Adam'help='Adam/Adadelta/Adagrad/RMSProp/Momentum/SGD')
parser.add_argument('--CRF'type=str2bool, default=True, help='use CRF at the top layer. if False, use Softmax')
parser.add_argument('--lr'type=float, default=0.001, help='learning rate')
parser.add_argument('--clip'type=float, default=5.0, help='gradient clipping')
parser.add_argument('--dropout'type=float, default=0.5, help='dropout keep_prob')
parser.add_argument('--update_embedding'type=str2bool, default=True, help='update embedding during training')
parser.add_argument('--pretrain_embedding'type=str, default='random'help='use pretrained char embedding or init it randomly')
parser.add_argument('--embedding_dim'type=int, default=300, help='random init char embedding_dim')
parser.add_argument('--shuffle'type=str2bool, default=True, help='shuffle training data before each epoch')
parser.add_argument('--mode'type=str, default='demo'help='train/test/demo')
parser.add_argument('--demo_model'type=str, default='1521112368'help='model for test and demo')
args = parser.parse_args()

## 獲取數(shù)據(jù)(word embeddings)
#word2id:為每一個不重復(fù)的字進(jìn)行編號,其中UNK為最后一位
word2id = read_dictionary(os.path.join('.', args.train_data, 'word2id.pkl'))
print('\n========word2id=========\n',word2id)
if args.pretrain_embedding == 'random':
    #隨機生成詞嵌入矩陣(一共3905個字,默認(rèn)取300個特征,維度為3905*300)
    embeddings = random_embedding(word2id, args.embedding_dim)
else:
    embedding_path = 'pretrain_embedding.npy'
    embeddings = np.array(np.load(embedding_path), dtype='float32')
print('\n=========embeddings==========\n',embeddings,'\ndim(embeddings)=',embeddings.shape)

## read corpus and get training data獲取
if args.mode != 'demo':
    train_path = os.path.join('.', args.train_data, 'train_data')
    test_path = os.path.join('.', args.test_data, 'test_data')
    train_data = read_corpus(train_path)#讀取訓(xùn)練集
    test_data = read_corpus(test_path); test_size = len(test_data)#讀取測試集
    #print('\n==========train_data================\n',train_data)
    #print('\n==========test_data================\n',test_data)


## paths setting創(chuàng)建相應(yīng)文件夾目錄
paths = {}
# 時間戳就是一個時間點,一般就是為了在同步更新的情況下提高效率之用。
#就比如一個文件,如果他沒有被更改,那么他的時間戳就不會改變,那么就沒有必要寫回,以提高效率,
#如果不論有沒有被更改都重新寫回的話,很顯然效率會有所下降。
timestamp = str(int(time.time())) if args.mode == 'train' else args.demo_model
#輸出路徑output_path路徑設(shè)置為data_path_save下的具體時間名字為文件名
output_path = os.path.join('.', args.train_data+'_save', timestamp)
if not os.path.exists(output_path): os.makedirs(output_path)

summary_path = os.path.join(output_path, 'summaries')
paths['summary_path'] = summary_path
if not os.path.exists(summary_path): os.makedirs(summary_path)
model_path = os.path.join(output_path, 'checkpoints/')
if not os.path.exists(model_path): os.makedirs(model_path)
ckpt_prefix = os.path.join(model_path, 'model')
paths['model_path'] = ckpt_prefix
result_path = os.path.join(output_path, 'results')
paths['result_path'] = result_path
if not os.path.exists(result_path): os.makedirs(result_path)
log_path = os.path.join(result_path, 'log.txt')
paths['log_path'] = log_path
get_logger(log_path).info(str(args))

## 調(diào)用模型進(jìn)行訓(xùn)練
if args.mode == 'train':
    #創(chuàng)建對象model
    model = BiLSTM_CRF(args, embeddings, tag2label, word2id, paths, config=config)
    #創(chuàng)建結(jié)點,
    model.build_graph()

    ## hyperparameters-tuning, split train/dev
    # dev_data = train_data[:5000]; dev_size = len(dev_data)
    # train_data = train_data[5000:]; train_size = len(train_data)
    # print('train data: {0}\ndev data: {1}'.format(train_size, dev_size))
    # model.train(train=train_data, dev=dev_data)

    ## train model on the whole training data
    print('train data: {}'.format(len(train_data)))
    model.train(train=train_data, dev=test_data)  # use test_data as the dev_data to see overfitting phenomena

## 調(diào)用模型進(jìn)行測試
elif args.mode == 'test':
    ckpt_file = tf.train.latest_checkpoint(model_path)
    print(ckpt_file)
    paths['model_path'] = ckpt_file
    model = BiLSTM_CRF(args, embeddings, tag2label, word2id, paths, config=config)
    model.build_graph()
    print('test data: {}'.format(test_size))
    model.test(test_data)

## 根據(jù)訓(xùn)練并測試好的模型進(jìn)行應(yīng)用
elif args.mode == 'demo':
    ckpt_file = tf.train.latest_checkpoint(model_path)
    print(ckpt_file)
    paths['model_path'] = ckpt_file
    model = BiLSTM_CRF(args, embeddings, tag2label, word2id, paths, config=config)
    model.build_graph()
    saver = tf.train.Saver()
    with tf.Session(config=config) as sess:
        saver.restore(sess, ckpt_file)
        while(1):
            print('Please input your sentence:')
            demo_sent = input()
            if demo_sent == '' or demo_sent.isspace():
                print('bye!')
                break
            else:
                demo_sent = list(demo_sent.strip())
                demo_data = [(demo_sent, ['O'] * len(demo_sent))]
                tag = model.demo_one(sess, demo_data)
                PER, LOC, ORG = get_entity(tag, demo_sent)
                print('PER: {}\nLOC: {}\nORG: {}'.format(PER, LOC, ORG))

(2)數(shù)據(jù)處理module,處理數(shù)據(jù),包括對數(shù)據(jù)進(jìn)行編號,詞向量獲取以及標(biāo)注信息的處理等:

import sys, pickle, os, random
import numpy as np

## tags, BIO
tag2label = {'O': 0,
             'B-PER': 1, 'I-PER': 2,
             'B-LOC': 3, 'I-LOC': 4,
             'B-ORG': 5, 'I-ORG': 6
             }

#輸入train_data文件的路徑,讀取訓(xùn)練集的語料,輸出train_data
def read_corpus(corpus_path):
    '''
    read corpus and return the list of samples
    :param corpus_path:
    :return: data
    '
''
    data = []
    with open(corpus_path, encoding='utf-8') as fr:
        lines = fr.readlines()
    sent_, tag_ = [], []
    for line in lines:
        if line != '\n':
            [char, label] = line.strip().split()
            sent_.append(char)
            tag_.append(label)
        else:
            data.append((sent_, tag_))
            sent_, tag_ = [], []
    return data

#生成word2id序列化文件
def vocab_build(vocab_path, corpus_path, min_count):
    '''
 #建立詞匯表
    :param vocab_path:
    :param corpus_path:
    :param min_count:
    :return:
    '
''
    #讀取數(shù)據(jù)(訓(xùn)練集或測試集)
    #data格式:[(字,標(biāo)簽),...]
    data = read_corpus(corpus_path)
    word2id = {}
    for sent_, tag_ in data:
        for word in sent_:
            if word.isdigit():
                word = '<NUM>'
            elif ('\u0041' <= word <='\u005a') or ('\u0061' <= word <='\u007a'):
                word = '<ENG>'
            if word not in word2id:
                word2id[word] = [len(word2id)+1, 1]
            else:
                word2id[word][1] += 1
    low_freq_words = []
    for word, [word_id, word_freq] in word2id.items():
        if word_freq < min_count and word != '<NUM>' and word != '<ENG>':
            low_freq_words.append(word)
    for word in low_freq_words:
        del word2id[word]
    new_id = 1
    for word in word2id.keys():
        word2id[word] = new_id
        new_id += 1
    word2id['<UNK>'] = new_id
    word2id['<PAD>'] = 0
    print(len(word2id))
    #將任意對象進(jìn)行序列化保存
    with open(vocab_path, 'wb') as fw:
        pickle.dump(word2id, fw)

#將句子中每一個字轉(zhuǎn)換為id編號,例如['我','愛','中','國'] ==> ['453','7','3204','550']
def sentence2id(sent, word2id):
    '''
    :param sent:源句子
    :param word2id:對應(yīng)的轉(zhuǎn)換表
    :return:
    '
''
    sentence_id = []
    for word in sent:
        if word.isdigit():
            word = '<NUM>'
        elif ('\u0041' <= word <= '\u005a') or ('\u0061' <= word <= '\u007a'):
            word = '<ENG>'
        if word not in word2id:
            word = '<UNK>'
        sentence_id.append(word2id[word])
    return sentence_id

#讀取word2id文件
def read_dictionary(vocab_path):
    '''
    :param vocab_path:
    :return:
    '
''
    vocab_path = os.path.join(vocab_path)
    #反序列化
    with open(vocab_path, 'rb') as fr:
        word2id = pickle.load(fr)
    print('vocab_size:', len(word2id))
    return word2id

#隨機嵌入
def random_embedding(vocab, embedding_dim):
    '''
    :param vocab:
    :param embedding_dim:
    :return:
    '
''
    embedding_mat = np.random.uniform(-0.25, 0.25, (len(vocab), embedding_dim))
    embedding_mat = np.float32(embedding_mat)
    return embedding_mat

def pad_sequences(sequences, pad_mark=0):
    '''
    :param sequences:
    :param pad_mark:
    :return:
    '
''
    max_len = max(map(lambda x : len(x), sequences))
    seq_list, seq_len_list = [], []
    for seq in sequences:
        seq = list(seq)
        seq_ = seq[:max_len] + [pad_mark] * max(max_len - len(seq), 0)
        seq_list.append(seq_)
        seq_len_list.append(min(len(seq), max_len))
    return seq_list, seq_len_list

def batch_yield(data, batch_size, vocab, tag2label, shuffle=False):
    '''
    :param data:
    :param batch_size:
    :param vocab:
    :param tag2label:
    :param shuffle:隨機對列表data進(jìn)行排序
    :return:
    '
''
    #如果參數(shù)shuffle為true,則對data列表進(jìn)行隨機排序
    if shuffle:
        random.shuffle(data)
    seqs, labels = [], []
    for (sent_, tag_) in data:
     #將句子轉(zhuǎn)換為編號組成的數(shù)字序列
        sent_ = sentence2id(sent_, vocab)
        #將標(biāo)簽序列轉(zhuǎn)換為數(shù)字序列
        label_ = [tag2label[tag] for tag in tag_]
        #一個句子就是一個樣本,當(dāng)句子數(shù)量等于預(yù)設(shè)的一批訓(xùn)練集數(shù)量,便輸出該樣本
        if len(seqs) == batch_size:
            yield seqs, labels
            seqs, labels = [], []
        seqs.append(sent_)
        labels.append(label_)

    if len(seqs) != 0:
        yield seqs, labels

(3)二進(jìn)制讀寫

import os
def conlleval(label_predict, label_path, metric_path):
    '''
    :param label_predict:
    :param label_path:
    :param metric_path:
    :return:
    '
''
    eval_perl = './conlleval_rev.pl'
    with open(label_path, 'w') as fw:
        line = []
        for sent_result in label_predict:
            for char, tag, tag_ in sent_result:
                tag = '0' if tag == 'O' else tag
                char = char.encode('utf-8')
                line.append('{} {} {}\n'.format(char, tag, tag_))
            line.append('\n')
        fw.writelines(line)
    os.system('perl {} < {} > {}'.format(eval_perl, label_path, metric_path))
    with open(metric_path) as fr:
        metrics = [line.strip() for line in fr]
    return metrics

(4)字符串處理,對數(shù)據(jù)類標(biāo)轉(zhuǎn)換,對已生成的標(biāo)注序列進(jìn)行實體提?。?/p>

import logging, sys, argparse

#將字符串轉(zhuǎn)換為布爾型
def str2bool(v):
    # copy from StackOverflow
    if v.lower() in ('yes''true''t''y''1'):
        return True
    elif v.lower() in ('no''false''f''n''0'):
        return False
    else:
        raise argparse.ArgumentTypeError('Boolean value expected.')

#獲得實體(將標(biāo)簽序列找出相應(yīng)的標(biāo)簽組合并返回對應(yīng)的文字)
def get_entity(tag_seq, char_seq):
    PER = get_PER_entity(tag_seq, char_seq)
    LOC = get_LOC_entity(tag_seq, char_seq)
    ORG = get_ORG_entity(tag_seq, char_seq)
    return PER, LOC, ORG

def get_PER_entity(tag_seq, char_seq):
    length = len(char_seq)
    PER = []
    for i, (char, tag) in enumerate(zip(char_seq, tag_seq)):
        if tag == 'B-PER':
            if 'per' in locals().keys():
                PER.append(per)
                del per
            per = char
            if i+1 == length:
                PER.append(per)
        if tag == 'I-PER':
            per += char
            if i+1 == length:
                PER.append(per)
        if tag not in ['I-PER''B-PER']:
            if 'per' in locals().keys():
                PER.append(per)
                del per
            continue
    return PER

def get_LOC_entity(tag_seq, char_seq):
    length = len(char_seq)
    LOC = []
    for i, (char, tag) in enumerate(zip(char_seq, tag_seq)):
        if tag == 'B-LOC':
            if 'loc' in locals().keys():
                LOC.append(loc)
                del loc
            loc = char
            if i+1 == length:
                LOC.append(loc)
        if tag == 'I-LOC':
            loc += char
            if i+1 == length:
                LOC.append(loc)
        if tag not in ['I-LOC''B-LOC']:
            if 'loc' in locals().keys():
                LOC.append(loc)
                del loc
            continue
    return LOC

def get_ORG_entity(tag_seq, char_seq):
    length = len(char_seq)
    ORG = []
    for i, (char, tag) in enumerate(zip(char_seq, tag_seq)):
        if tag == 'B-ORG':
            if 'org' in locals().keys():
                ORG.append(org)
                del org
            org = char
            if i+1 == length:
                ORG.append(org)
        if tag == 'I-ORG':
            org += char
            if i+1 == length:
                ORG.append(org)
        if tag not in ['I-ORG''B-ORG']:
            if 'org' in locals().keys():
                ORG.append(org)
                del org
            continue
    return ORG

def get_logger(filename):
    logger = logging.getLogger('logger')
    logger.setLevel(logging.DEBUG)
    logging.basicConfig(format='%(message)s', level=logging.DEBUG)
    handler = logging.FileHandler(filename)
    handler.setLevel(logging.DEBUG)
    handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s: %(message)s'))
    logging.getLogger().addHandler(handler)
    return logger

(5)Bi-LSTM+CRF模型:

import numpy as np
import os, time, sys
import tensorflow as tf
from tensorflow.contrib.rnn import LSTMCell
from tensorflow.contrib.crf import crf_log_likelihood
from tensorflow.contrib.crf import viterbi_decode
from data import pad_sequences, batch_yield
from utils import get_logger
from eval import conlleval

#batch_size:批大小,一次訓(xùn)練的樣本數(shù)量
#epoch:使用全部訓(xùn)練樣本訓(xùn)練的次數(shù)
#hidden_dim:隱藏維度
#embeddinds:詞嵌入矩陣(字?jǐn)?shù)量*特征數(shù)量)
class BiLSTM_CRF(object):
    def __init__(self, args, embeddings, tag2label, vocab, paths, config):
        self.batch_size = args.batch_size
        self.epoch_num = args.epoch
        self.hidden_dim = args.hidden_dim
        self.embeddings = embeddings
        self.CRF = args.CRF
        self.update_embedding = args.update_embedding
        self.dropout_keep_prob = args.dropout
        self.optimizer = args.optimizer
        self.lr = args.lr
        self.clip_grad = args.clip
        self.tag2label = tag2label
        self.num_tags = len(tag2label)
        self.vocab = vocab
        self.shuffle = args.shuffle
        self.model_path = paths['model_path']
        self.summary_path = paths['summary_path']
        self.logger = get_logger(paths['log_path'])
        self.result_path = paths['result_path']
        self.config = config

    #創(chuàng)建結(jié)點
    def build_graph(self):
        self.add_placeholders()
        self.lookup_layer_op()
        self.biLSTM_layer_op()
        self.softmax_pred_op()
        self.loss_op()
        self.trainstep_op()
        self.init_op()

    #placeholder相當(dāng)于定義了一個位置,這個位置中的數(shù)據(jù)在程序運行時再指定
    def add_placeholders(self):
        self.word_ids = tf.placeholder(tf.int32, shape=[None, None], name='word_ids')
        self.labels = tf.placeholder(tf.int32, shape=[None, None], name='labels')
        self.sequence_lengths = tf.placeholder(tf.int32, shape=[None], name='sequence_lengths')
        self.dropout_pl = tf.placeholder(dtype=tf.float32, shape=[], name='dropout')
        self.lr_pl = tf.placeholder(dtype=tf.float32, shape=[], name='lr')
    
    def lookup_layer_op(self):
        #新建變量
        with tf.variable_scope('words'):
            _word_embeddings = tf.Variable(self.embeddings,
                                           dtype=tf.float32,
                                           trainable=self.update_embedding,
                                           name='_word_embeddings')
            #尋找_word_embeddings矩陣中分別為words_ids中元素作為下標(biāo)的值
            #提取出該句子每個字對應(yīng)的向量并組合起來
            word_embeddings = tf.nn.embedding_lookup(params=_word_embeddings,
                                                     ids=self.word_ids,
                                                     name='word_embeddings')
        #dropout函數(shù)是為了防止在訓(xùn)練中過擬合的操作,將訓(xùn)練輸出按一定規(guī)則進(jìn)行變換
        self.word_embeddings =  tf.nn.dropout(word_embeddings, self.dropout_pl)
        print('========word_embeddings=============\n',word_embeddings)

    #雙向LSTM網(wǎng)絡(luò)層輸出
    def biLSTM_layer_op(self):
        with tf.variable_scope('bi-lstm'):
            cell_fw = LSTMCell(self.hidden_dim)#前向cell
            cell_bw = LSTMCell(self.hidden_dim)#反向cell
            #(output_fw_seq, output_bw_seq)是一個包含前向cell輸出tensor和后向cell輸出tensor組成的元組,_為包含了前向和后向最后的隱藏狀態(tài)的組成的元組
            (output_fw_seq, output_bw_seq), _ = tf.nn.bidirectional_dynamic_rnn(
                cell_fw=cell_fw,
                cell_bw=cell_bw,
                inputs=self.word_embeddings,
                sequence_length=self.sequence_lengths,
                dtype=tf.float32)
            output = tf.concat([output_fw_seq, output_bw_seq], axis=-1)
            output = tf.nn.dropout(output, self.dropout_pl)
        with tf.variable_scope('proj'):
            #權(quán)值矩陣
            W = tf.get_variable(name='W',
                                shape=[2 * self.hidden_dim, self.num_tags],
                                initializer=tf.contrib.layers.xavier_initializer(),
                                dtype=tf.float32)
            #閾值向量
            b = tf.get_variable(name='b',
                                shape=[self.num_tags],
                                initializer=tf.zeros_initializer(),
                                dtype=tf.float32)
            s = tf.shape(output)
            output = tf.reshape(output, [-1, 2*self.hidden_dim])
            #tf.matmul矩陣乘法
            pred = tf.matmul(output, W) + b
            self.logits = tf.reshape(pred, [-1, s[1], self.num_tags])

    #損失函數(shù)
    def loss_op(self):
        #使用CRF的最大似然估計
        if self.CRF:
            #crf_log_likelihood作為損失函數(shù)
            #inputs:unary potentials,就是每個標(biāo)簽的預(yù)測概率值
            #tag_indices,這個就是真實的標(biāo)簽序列了
            #sequence_lengths,一個樣本真實的序列長度,為了對齊長度會做些padding,但是可以把真實的長度放到這個參數(shù)里
            #transition_params,轉(zhuǎn)移概率,可以沒有,沒有的話這個函數(shù)也會算出來
            #輸出:log_likelihood:標(biāo)量;transition_params,轉(zhuǎn)移概率,如果輸入沒輸,它就自己算個給返回
            #self.logits為雙向LSTM的輸出
            log_likelihood, self.transition_params = crf_log_likelihood(inputs=self.logits,
                                                                   tag_indices=self.labels,
                                                                   sequence_lengths=self.sequence_lengths)
            #tf.reduce_mean默認(rèn)對log_likelihood所有元素求平均
            self.loss = -tf.reduce_mean(log_likelihood)
        else:
            #交差信息熵
            losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits,
                                                                    labels=self.labels)
            mask = tf.sequence_mask(self.sequence_lengths)
            losses = tf.boolean_mask(losses, mask)
            self.loss = tf.reduce_mean(losses)
        tf.summary.scalar('loss', self.loss)

    def softmax_pred_op(self):
        if not self.CRF:
            self.labels_softmax_ = tf.argmax(self.logits, axis=-1)
            self.labels_softmax_ = tf.cast(self.labels_softmax_, tf.int32)

    def trainstep_op(self):
        with tf.variable_scope('train_step'):
            #global_step:Optional Variable to increment by one after the variables have been updated
            self.global_step = tf.Variable(0, name='global_step', trainable=False)
            #選擇優(yōu)化算法
            if self.optimizer == 'Adam':
                optim = tf.train.AdamOptimizer(learning_rate=self.lr_pl)
            elif self.optimizer == 'Adadelta':
                optim = tf.train.AdadeltaOptimizer(learning_rate=self.lr_pl)
            elif self.optimizer == 'Adagrad':
                optim = tf.train.AdagradOptimizer(learning_rate=self.lr_pl)
            elif self.optimizer == 'RMSProp':
                optim = tf.train.RMSPropOptimizer(learning_rate=self.lr_pl)
            elif self.optimizer == 'Momentum':
                optim = tf.train.MomentumOptimizer(learning_rate=self.lr_pl, momentum=0.9)
            elif self.optimizer == 'SGD':
                optim = tf.train.GradientDescentOptimizer(learning_rate=self.lr_pl)
            else:
                optim = tf.train.GradientDescentOptimizer(learning_rate=self.lr_pl)
            #根據(jù)優(yōu)化算法模型計算損失函數(shù)梯度,返回梯度和變量列表
            grads_and_vars = optim.compute_gradients(self.loss)
            #tf.clip_by_value(A, min, max)指將列表A中元素壓縮在min和max之間,大于max或小于min的值改成max和min
            #梯度修剪
            grads_and_vars_clip = [[tf.clip_by_value(g, -self.clip_grad, self.clip_grad), v] for g, v in grads_and_vars]
            #grads_and_vars_clip: compute_gradients()函數(shù)返回的(gradient, variable)對的列表并修剪后的
            #global_step:Optional Variable to increment by one after the variables have been updated
            self.train_op = optim.apply_gradients(grads_and_vars_clip, global_step=self.global_step)

    def init_op(self):
        self.init_op = tf.global_variables_initializer()
    #顯示訓(xùn)練過程中的信息
    def add_summary(self, sess):
        '''
        :param sess:
        :return:
        '
''
        self.merged = tf.summary.merge_all()
        #指定一個文件用來保存圖。
        self.file_writer = tf.summary.FileWriter(self.summary_path, sess.graph)

    def train(self, train, dev):
        '''
        :param train:
        :param dev:
        :return:
        '
''
        #創(chuàng)建保存模型對象
        saver = tf.train.Saver(tf.global_variables())
        with tf.Session(config=self.config) as sess:
            sess.run(self.init_op)
            self.add_summary(sess)       
            #循環(huán)訓(xùn)練epoch_num次
            for epoch in range(self.epoch_num):
                self.run_one_epoch(sess, train, dev, self.tag2label, epoch, saver)

    def test(self, test):
        saver = tf.train.Saver()
        with tf.Session(config=self.config) as sess:
            self.logger.info('=========== testing ===========')
            saver.restore(sess, self.model_path)
            label_list, seq_len_list = self.dev_one_epoch(sess, test)
            self.evaluate(label_list, seq_len_list, test)

    #demo
    def demo_one(self, sess, sent):
        '''
        :param sess:
        :param sent: 
        :return:
        '
''
        label_list = []
        #隨機將句子分批次,并遍歷這些批次,對每一批數(shù)據(jù)進(jìn)行預(yù)測
        for seqs, labels in batch_yield(sent, self.batch_size, self.vocab, self.tag2label, shuffle=False):
            #預(yù)測該批樣本,并返回相應(yīng)的標(biāo)簽數(shù)字序列
            label_list_, _ = self.predict_one_batch(sess, seqs)
            label_list.extend(label_list_)
        label2tag = {}
        for tag, label in self.tag2label.items():
            label2tag[label] = tag if label != 0 else label
        #根據(jù)標(biāo)簽對照表將數(shù)字序列轉(zhuǎn)換為文字標(biāo)簽序列
        tag = [label2tag[label] for label in label_list[0]]
        print('===mode.demo_one:','label_list=',label_list,',label2tag=',label2tag,',tag=',tag)
        return tag

    #訓(xùn)練一次
    def run_one_epoch(self, sess, train, dev, tag2label, epoch, saver):
        '''
        :param sess:
        :param train:訓(xùn)練集
        :param dev:驗證集
        :param tag2label:標(biāo)簽轉(zhuǎn)換字典
        :param epoch:當(dāng)前訓(xùn)練的輪數(shù)
        :param saver:保存的模型
        :return:
        '
''
        #訓(xùn)練批次數(shù)
        num_batches = (len(train) + self.batch_size - 1) // self.batch_size 
        start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
        #隨機為每一批分配數(shù)據(jù)
        batches = batch_yield(train, self.batch_size, self.vocab, self.tag2label, shuffle=self.shuffle)
        #訓(xùn)練每一批訓(xùn)練集
        for step, (seqs, labels) in enumerate(batches):
            sys.stdout.write(' processing: {} batch / {} batches.'.format(step + 1, num_batches) + '\r')
            step_num = epoch * num_batches + step + 1
            feed_dict, _ = self.get_feed_dict(seqs, labels, self.lr, self.dropout_keep_prob)
            _, loss_train, summary, step_num_ = sess.run([self.train_op, self.loss, self.merged, self.global_step],
                                                         feed_dict=feed_dict)
            if step + 1 == 1 or (step + 1) % 300 == 0 or step + 1 == num_batches:
                self.logger.info(
                    '{} epoch {}, step {}, loss: {:.4}, global_step: {}'.format(start_time, epoch + 1, step + 1,
                                                                                loss_train, step_num))
            self.file_writer.add_summary(summary, step_num)
            #保存模型
            if step + 1 == num_batches:
                #保存模型數(shù)據(jù)
                #第一個參數(shù)sess,這個就不用說了。第二個參數(shù)設(shè)定保存的路徑和名字,第三個參數(shù)將訓(xùn)練的次數(shù)作為后綴加入到模型名字中。
                saver.save(sess, self.model_path, global_step=step_num)
        self.logger.info('===========validation / test===========')
        label_list_dev, seq_len_list_dev = self.dev_one_epoch(sess, dev)
        #模型評估
        self.evaluate(label_list_dev, seq_len_list_dev, dev, epoch)

    def get_feed_dict(self, seqs, labels=None, lr=None, dropout=None):
        '''
        :param seqs:句子數(shù)字序列
        :param labels:對應(yīng)句子的標(biāo)簽數(shù)字序列
        :param lr:
        :param dropout:
        :return: feed_dict
        '
''
        #獲取填充0后的句子序列樣本(使得每個句子填充0后長度一致),同時記錄每個句子實際長度
        #例如:s = [['3','1','6','34','66','8'],['3','1','34','66','8'],['3','1','6','34']]
        #返回為[['3', '1', '6', '34', '66', '8'], ['3', '1', '34', '66', '8', 0], ['3', '1', '6', '34', 0, 0]],長度為 [6, 5, 4]
        word_ids, seq_len_list = pad_sequences(seqs, pad_mark=0)
        feed_dict = {self.word_ids: word_ids,
                     self.sequence_lengths: seq_len_list}
        if labels is not None:
            #為標(biāo)簽序列填充0并返回每個句子標(biāo)簽的實際長度
            labels_, _ = pad_sequences(labels, pad_mark=0)
            feed_dict[self.labels] = labels_
        if lr is not None:
            feed_dict[self.lr_pl] = lr
        if dropout is not None:
            feed_dict[self.dropout_pl] = dropout
        return feed_dict, seq_len_list

    def dev_one_epoch(self, sess, dev):
        '''
        :param sess:
        :param dev:
        :return:
        '
''
        label_list, seq_len_list = [], []
        for seqs, labels in batch_yield(dev, self.batch_size, self.vocab, self.tag2label, shuffle=False):
            label_list_, seq_len_list_ = self.predict_one_batch(sess, seqs)
            label_list.extend(label_list_)
            seq_len_list.extend(seq_len_list_)
        return label_list, seq_len_list

    #預(yù)測一批數(shù)據(jù)集
    def predict_one_batch(self, sess, seqs):
        '''
        :param sess:
        :param seqs:
        :return: label_list
                 seq_len_list
        '
''
        #將樣本進(jìn)行整理(填充0方式使得每句話長度一樣,并返回每句話實際長度)
        feed_dict, seq_len_list = self.get_feed_dict(seqs, dropout=1.0)
        #若使用CRF
        if self.CRF:
            logits, transition_params = sess.run([self.logits, self.transition_params],
                                                 feed_dict=feed_dict)
            label_list = []
            for logit, seq_len in zip(logits, seq_len_list):
                viterbi_seq, _ = viterbi_decode(logit[:seq_len], transition_params)
                label_list.append(viterbi_seq)
            return label_list, seq_len_list
        else:
            label_list = sess.run(self.labels_softmax_, feed_dict=feed_dict)
            return label_list, seq_len_list

    def evaluate(self, label_list, seq_len_list, data, epoch=None):
        '''
        :param label_list:
        :param seq_len_list:
        :param data:
        :param epoch:
        :return:
        '
''
        label2tag = {}
        for tag, label in self.tag2label.items():
            label2tag[label] = tag if label != 0 else label
        model_predict = []
        for label_, (sent, tag) in zip(label_list, data):
            tag_ = [label2tag[label__] for label__ in label_]
            sent_res = []
            if  len(label_) != len(sent):
                print(sent)
                print(len(label_))
                print(tag)
            for i in range(len(sent)):
                sent_res.append([sent[i], tag[i], tag_[i]])
            model_predict.append(sent_res)
        epoch_num = str(epoch+1) if epoch != None else 'test'
        label_path = os.path.join(self.result_path, 'label_' + epoch_num)
        metric_path = os.path.join(self.result_path, 'result_metric_' + epoch_num)
        for _ in conlleval(model_predict, label_path, metric_path):
            self.logger.info(_)

十七、推薦閱讀書籍

非常能夠感謝你能夠通讀整片文章,本人將針對基于深度學(xué)習(xí)的命名實體識別與關(guān)系抽取為主題,推薦學(xué)習(xí)文獻(xiàn)或書籍。

17.1 推薦的書籍

(1)《統(tǒng)計學(xué)習(xí)方法》(李航)
??這本書是主要針對自然語言處理領(lǐng)域編寫的統(tǒng)計學(xué)習(xí)方法。統(tǒng)計學(xué)習(xí)方法又叫做統(tǒng)計機器學(xué)習(xí)。這本書很細(xì)致的講解了幾大典型的機器學(xué)習(xí)算法,同時針對每一種算法的模型進(jìn)行了相關(guān)推導(dǎo)。另外書中還細(xì)講了圖概率模型、隱馬爾可夫模型和條件隨機場,這對于理解命名實體識別非常有利。

(2)《機器學(xué)習(xí)》(周志華)
??南京大學(xué)人工智能實驗室的主任周志華在機器學(xué)習(xí)和大數(shù)據(jù)挖掘領(lǐng)域內(nèi)是國內(nèi)外知名的“大佬”,他出版的《機器學(xué)習(xí)》西瓜書已經(jīng)成為IT領(lǐng)域內(nèi)的暢銷書。本人閱讀了該書,認(rèn)為該書對數(shù)學(xué)理論要求非常高,因此這本書需要在對基本的機器學(xué)習(xí)知識有所了解之后才適合讀。因此較為合理的應(yīng)先閱讀《統(tǒng)計學(xué)習(xí)方法》后再閱讀《機器學(xué)習(xí)》。

(3)《深度學(xué)習(xí)》([美] 伊恩·古德費洛)
??《深度學(xué)習(xí)》是美國數(shù)學(xué)家伊恩·古德費洛所編寫,由人民郵電出版社出版。該書主要以深度學(xué)習(xí)為主,再簡單介紹了機器學(xué)習(xí)的知識后,對深度學(xué)習(xí)進(jìn)行了多角度的分析,并介紹了多層感知機BP神經(jīng)網(wǎng)絡(luò)、循環(huán)神經(jīng)網(wǎng)絡(luò)、卷積神經(jīng)網(wǎng)絡(luò)、計算圖等內(nèi)容,是深度學(xué)習(xí)領(lǐng)域內(nèi)非常權(quán)威的書籍。因此在學(xué)習(xí)完基本的機器學(xué)習(xí)內(nèi)容之后,學(xué)習(xí)深度學(xué)習(xí)是必要的。

(4)另外推薦幾本數(shù)學(xué)類書籍,對數(shù)學(xué)方面薄弱的或者沒有學(xué)習(xí)的讀者提供學(xué)習(xí)的方向:
《微積分》、《線性代數(shù)·同濟(jì)版》、《矩陣論·華科版》、《最優(yōu)化方法及其應(yīng)用》、《最優(yōu)控制》等。

十八、總結(jié)

本文章(專欄)講述了基于深度學(xué)習(xí)的命名實體識別與關(guān)系抽取,細(xì)致的介紹了相關(guān)概念、涉及的模型以及通過公式推導(dǎo)和例子來對模型進(jìn)行分析,最后給出源碼和項目實例。

本人在研究知識圖譜過程中,對知識圖譜的關(guān)鍵步驟——知識抽取花了大量的時間,研究了深度神經(jīng)網(wǎng)絡(luò)模型、循環(huán)神經(jīng)網(wǎng)絡(luò)模型,長期記憶模型,在了解相關(guān)模型后認(rèn)識到了編碼解碼問題,而神經(jīng)網(wǎng)絡(luò)充當(dāng)著編碼,因此解決解碼問題就用到了概率圖模型(當(dāng)然LSTM也可以解碼)。

命名實體識別問題可以被認(rèn)為是序列標(biāo)注問題,關(guān)系抽求可以被認(rèn)為是監(jiān)督分類問題。在2017年幾篇端到端模型的文章紛紛呈現(xiàn),打破了傳統(tǒng)的pipeline模型,在實驗效果上非常出色。幾種端到端模型包括Bi-LSTM+LSTM+Text-CNN,Bi-LSTM+LSTM,Bi-LSTM+Independency-Tree等,這些模型目的是在命名實體抽取的過程中一并將關(guān)系挖掘出來,因此在模型的運行效率上有很大的改善。

除了端到端模型,一種基于注意力機制的模型映入眼簾,在幾篇學(xué)術(shù)論文中聲稱其模型比端到端模型更優(yōu),在實驗中也得到了體現(xiàn)?;谧⒁饬C制模型還需要進(jìn)行深入的研究。

關(guān)于基于深度學(xué)習(xí)的知識抽取,本文便到此結(jié)束,今后會不斷根據(jù)學(xué)術(shù)論文的情況進(jìn)行更新或添加。

參考資料

[1]

正則化方法:L1和L2 regularization、數(shù)據(jù)集擴增、dropout: https://blog.csdn.net/u012162613/article/details/44261657

[2]

基于監(jiān)督學(xué)習(xí)和遠(yuǎn)程監(jiān)督的神經(jīng)關(guān)系抽取: https://blog.csdn.net/qq_36426650/article/details/103219167

[3]

論文解讀:Attention Is All You Need: https://blog.csdn.net/qq_36426650/article/details/96449575

[4]

論文解讀:Attention-Based Bidirectional Long Short-Term Memory Networks for Relation Classification: https://blog.csdn.net/qq_36426650/article/details/88207917

[5]

圖數(shù)據(jù)庫,數(shù)據(jù)庫中的“黑科技”: http://www.sohu.com/a/220383268_784849

[6]

初識圖數(shù)據(jù)與圖數(shù)據(jù)庫: https://blog.csdn.net/m0_38068229/article/details/80629702

[7]

Neo4j簡介: https://www.cnblogs.com/atomicbomb/p/9897484.html

來源:https://wjn1996.blog.csdn.net/
作者:
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
TF Boys (TensorFlow Boys ) 養(yǎng)成記(二): TensorFlow 數(shù)據(jù)讀取
PyQt5 GUI:百度圖片下載器(文末附源碼)
【Python-tkGo】一鍵查找包含指定文本的文件
CRF的實現(xiàn)
【深度學(xué)習(xí)系列】PaddlePaddle之?dāng)?shù)據(jù)預(yù)處理
【干貨】TensorFlow實戰(zhàn)——圖像分類神經(jīng)網(wǎng)絡(luò)模型
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服