1. 程式人生 > >開源專案kcws程式碼分析--基於深度學習的分詞技術

開源專案kcws程式碼分析--基於深度學習的分詞技術

分詞原理

本小節內容參考待字閨中的兩篇博文:

簡單的說,kcws的分詞原理就是:

  1. 對語料進行處理,使用word2vec對語料的字進行嵌入,每個字特徵為50維。
  2. 得到字嵌入後,用字嵌入特徵餵給雙向LSTM, 對輸出的隱層加一個線性層,然後加一個CRF就得到本文實現的模型。
  3. 於最優化方法,文字語言模型類的貌似Adam效果更好, 對於分類之類的,貌似AdaDelta效果更好。

另外,字元嵌入的表示可以是純預訓練的,但也可以在訓練模型的時候再fine-tune,一般而言後者效果更好。對於fine-tune的情形,可以在字元嵌入後,輸入雙向LSTM之前加入dropout進一步提升模型效果。

figure1

具體的解決方案,基於雙向LSTM與CRF的神經網路結構

如上圖所示,單層圓圈代表的word embedding輸入層,菱形代表學習輸入的決定性方程,雙層圓圈代表隨機變數。資訊流將輸入層的word embedding送到雙向LSTM, l(i)代表word(i)和從左邊傳入的歷史訊號,r(i)代表word(i)以及從右邊傳入的未來的訊號,用c(i)連線這兩個向量的資訊,代表詞word(i)。

先說Bi-LSTM這個雙向模型,LSTM的變種有很多,基本流程都是一樣的,文獻中同樣採用上一節說明的三個門,控制送入memory cell的輸入資訊,以及遺忘之前階段資訊的比例,再對細胞狀態進行更新,最後用tanh方程對更新後的細胞狀態再做處理,與sigmoid疊加相乘作為最終輸出。具體的模型公式見下圖,經過上一節的解釋,這些符號應該不太陌生了。

公式

雙向LSTM說白了,就是先從左至右,順序學習輸入詞序列的歷史資訊,再從右至左,學習輸入詞序列未來影響現在的資訊,結合這兩種方式的最終表示有效地描述了詞的內容,在多種標註應用上取得了好效果。

如果說雙向LSTM並不特殊,這個結構中另一個新的嘗試,就是將深度神經網路最後學出來的結果,作為特徵,用CRF模型連線起來,用P來表示雙向LSTM神經網路學習出來的打分輸出矩陣,它是一個 nxk 的矩陣,n是輸入詞序列個數,k是標記型別的數目, P(ij)指的是在一個輸入句子中,第i個詞在第j個tag標記上的可能性(打分)。另外一個特徵函式是狀態轉移矩陣 A,A(ij) 代表從tag i轉移到tag j的可能性(打分),但這個轉移矩陣實際上有k+2維,其中包括句子的開始和結束兩個狀態,用公式表示如下圖:

s(X,y)=i=0nAyi,yi+1+i=1nPi,yi

在給定輸入序列X,最終定義的輸出y序列的概率,則使用softmax函式表示如下:

p(y|X)=es(X,y)y˘YXes(X,y˘)

而在訓練學習目標函式的時候,要優化的就是下面這個預測輸出標記序列的log概率,其中 Y(X)代表的是所有可能的tag標記序列集合,那麼最後學習得到的輸出,就是概率最大的那個標記序列。如果只是模擬輸出的bigram互動影響方式,採用動態規劃即可求解下列方程。

log(p(y|X))=s(X,y)log(y˘YXes(X,y˘))
=s(X,y)logaddy˘YXs(X,y˘)
y=argmaxy˘YXs(X,y˘)

至此,基於雙向LSTM與CRF的神經網路結構已經介紹完畢,文獻中介紹的是在命名實體識別方面的一個實踐應用,這個思路同樣可以用在分詞上。具體的實踐和調參,也得應場景而異,Koth在上一篇部落格中已經給出了自己的踐行,讀者們可以借鑑參考。

程式碼結構與實踐

主要的程式碼在目錄kcws/kcws/train路徑下。(寫這篇文章的時候發現K大神做了更新,我主要還是分析之前的程式碼)

  • process_anno_file.py 將人民網2014訓練語料進行單字切割,包括標點符號
  • generate_training.py 生成單字的vector之後,處理每篇訓練語料,以“。”劃分為句子,對每個句子,如果長度大於MAX_LEN(預設設定為80,在程式碼裡改),不處理,Long lines加一,如果長度小於MAX_LEN,則生成長度為160的vector,前80為單字在字典中的index,不足80的補0,後80為每個字對應的SBME標記(S表示單字,B表示開始,M表示中間,E表示結尾)。
  • filter_sentence.py 將語料切分為訓練集和測試集,作者將含有兩個字以下的句子過濾掉,剩下的按照二八分,測試集最多8000篇。
  • train_cws_lstm.py 主要訓練程式碼。

作者在專案主頁上很詳細的寫了構建和訓練的步驟,按照這些步驟來實踐一下不算難,我主要遇到了以下幾個問題:

  1. 之前安裝tensorflow的時候沒有用bazel,不瞭解bazel的工作方式和原理,但是這個專案必須要用,因為需要用到third_party中word2vec的類。(可以將word2vec的某些類構建為python可以import的類?)
  2. 已安裝的0.8.0版本的tensorflow沒有實現crf,需要升級。
  3. 安裝tensorflow 0.11.0版本後執行,出現PyUnicodeUCS4_AsUTF8String的錯誤,查詢後發現是當前安裝的python預設是unicode=ucs2,需要重新編譯安裝python。編譯的時候設定./configure –enable-unicode=ucs4 。
  4. numpy,scipy都需要重新build,setup。

主要程式碼分析

def main(unused_argv):
    curdir = os.path.dirname(os.path.realpath(__file__))
    trainDataPath = tf.app.flags.FLAGS.train_data_path
    if not trainDataPath.startswith("/"):
        trainDataPath = curdir + "/" + trainDataPath
    graph = tf.Graph()
    with graph.as_default():
        model = Model(FLAGS.embedding_size, FLAGS.num_tags,
                  FLAGS.word2vec_path, FLAGS.num_hidden)
        print("train data path:", trainDataPath)
        # 讀取訓練集batch大小的feature和label,各為80大小的陣列
        X, Y = inputs(trainDataPath)  
        # 讀取測試集所有資料的feature和label,各為80大小的陣列
        tX, tY = do_load_data(tf.app.flags.FLAGS.test_data_path) 
        # 計算訓練集的損失
        total_loss = model.loss(X, Y)  
        # 使用AdamOptimizer優化方法
        train_op = train(total_loss)  
        # 在測試集上做評測
        test_unary_score, test_sequence_length = model.test_unary_score() 
        # 建立Supervisor管理模型的分散式訓練
        sv = tf.train.Supervisor(graph=graph, logdir=FLAGS.log_dir) 
        with sv.managed_session(master='') as sess:
            # actual training loop
            training_steps = FLAGS.train_steps
            for step in range(training_steps):
                if sv.should_stop():
                    break
                try:
                    _, trainsMatrix = sess.run(
                    [train_op, model.transition_params])
                    # for debugging and learning purposes, see how the loss gets decremented thru training steps
                    if step % 100 == 0:
                        print("[%d] loss: [%r]" % (step, sess.run(total_loss)))
                    if step % 1000 == 0:
                        test_evaluate(sess, test_unary_score,
                                  test_sequence_length, trainsMatrix,
                                  model.inp, tX, tY)
                except KeyboardInterrupt, e:
                    sv.saver.save(sess,
                              FLAGS.log_dir + '/model',
                              global_step=step + 1)
                    raise e
            sv.saver.save(sess, FLAGS.log_dir + '/finnal-model')
            sess.close()

Class Model:

def __init__(self, embeddingSize, distinctTagNum, c2vPath, numHidden):
    self.embeddingSize = embeddingSize
    self.distinctTagNum = distinctTagNum
    self.numHidden = numHidden
    self.c2v = self.load_w2v(c2vPath)
    self.words = tf.Variable(self.c2v, name="words")
    with tf.variable_scope('Softmax') as scope:
        self.W = tf.get_variable(
            shape=[numHidden * 2, distinctTagNum],
            initializer=tf.truncated_normal_initializer(stddev=0.01),
            name="weights",
            regularizer=tf.contrib.layers.l2_regularizer(0.001))
        self.b = tf.Variable(tf.zeros([distinctTagNum], name="bias"))
    self.trains_params = None
    self.inp = tf.placeholder(tf.int32,
                              shape=[None, FLAGS.max_sentence_len],
                              name="input_placeholder")
    pass

def length(self, data):
    used = tf.sign(tf.reduce_max(tf.abs(data), reduction_indices=2))
    length = tf.reduce_sum(used, reduction_indices=1)
    length = tf.cast(length, tf.int32)
    return length

def inference(self, X, reuse=None, trainMode=True):
    word_vectors = tf.nn.embedding_lookup(self.words, X) # 按照X順序返回self.words中的第X行,返回的結果組成tensor。
    length = self.length(word_vectors)
    # length是shape為[batch_size]大小值為句子長度的vector
    length_64 = tf.cast(length, tf.int64)
    if trainMode:  # 訓練的時候啟用dropout,測試的時候關鍵dropout
        word_vectors = tf.nn.dropout(word_vectors, 0.5)  # 將word_vectors按照50%的概率丟棄某些詞,tf增加的一個處理是將其餘的詞scale 1/0.5
    with tf.variable_scope("rnn_fwbw", reuse=reuse) as scope:
        forward_output, _ = tf.nn.dynamic_rnn(
            tf.nn.rnn_cell.LSTMCell(self.numHidden),
            word_vectors,
            dtype=tf.float32,
            sequence_length=length,
            scope="RNN_forward")
        backward_output_, _ = tf.nn.dynamic_rnn(
            tf.nn.rnn_cell.LSTMCell(self.numHidden),
            inputs=tf.reverse_sequence(word_vectors,
                                       length_64,
                                       seq_dim=1),
            # 訓練和測試的時候,inputs的格式不同。訓練時,tensor shape是[batch_size, max_time,input_size]
            # 測試時,tensor shape是[max_time,batch_size,input_size].
            # tf.reverse_sequence作用就是指定在列上操作(batch_dim表示按行操作)
            dtype=tf.float32,
            sequence_length=length,
            scope="RNN_backword")
    # tf.nn.dynamic_rnn(cell, inputs, sequence_length,time_major,...)主要引數:
    # cell:搭建好的網路,這裡用LSTMCell(num_cell),num_cell表示一個lstm單元輸出的維數(100)
    # inputs:word_vectors,它的shape由time_major決定,預設是false,即[batch_size,max_time,input_size],如果是測試
    #           過程,time_major設定為True,shape為[max_time,batch_size,input_size],這裡直接做了reverse,省去了time_major設定。
    #         其中,batch_size=100, max_time=80句子最大長度,input_size字的向量的長度。
    # sequence_length:shape[batch_size]大小的值為句子最大長度的tensor。
    # 輸出:
    #   outputs:[batch_size, max_time, cell.output_size]
    #   state: shape取決於LSTMCell中state_size的設定,返回Tensor或者tuple。

    backward_output = tf.reverse_sequence(backward_output_,
                                          length_64,
                                          seq_dim=1)
    # 這裡的reverse_sequence同上。
    output = tf.concat(2, [forward_output, backward_output])
    # 連線兩個三維tensor,2表示按照列連線(0表示縱向,1表示行)
    # 連線後,output的shape:[batch_size, max_time, 2*cell.output_size],即[100, 80, 2*50]
    output = tf.reshape(output, [-1, self.numHidden * 2])
    # reshape後,output的shape:[batch_size, self.numHidden * 2],即[100, 200]
    matricized_unary_scores = tf.batch_matmul(output, self.W)
    # 得到未歸一化的CRF輸出
    # 點乘W的shape[ 100*2, 4],生成[batch_size, 4]大小的matricized_unary_scores
    unary_scores = tf.reshape(
        matricized_unary_scores,
        [-1, FLAGS.max_sentence_len, self.distinctTagNum])
    # reshape後,unary_scores大小為[batch_size,80, 4]
    return unary_scores, length

def loss(self, X, Y):
    P, sequence_length = self.inference(X)
    # CRF損失計算,訓練的時候使用,測試的時候用viterbi解碼
    log_likelihood, self.transition_params = tf.contrib.crf.crf_log_likelihood(
        P, Y, sequence_length)
    # crf_log_likelihood引數(inputs,tag_indices, sequence_lengths)
    #   inputs:大小為[100, 80, 4]的tensor,CRF層的輸入
    #   tag_indices:大小為[100, 80]的矩陣
    #   sequence_length:大小 [100]值為80的向量。
    # 輸出:
    #   log_likelihood:[batch_size]大小的vector,log-likelihood值
    #   transition_params:[4,4]大小的矩陣
    loss = tf.reduce_mean(-log_likelihood)
    return loss

def load_w2v(self, path): #返回(num+2)*50大小的二維矩陣,其中第一行全是0,最後一行是每個詞向量維度的平均值。
    fp = open(path, "r")
    print("load data from:", path)
    line = fp.readline().strip()
    ss = line.split(" ")
    total = int(ss[0])
    dim = int(ss[1])
    assert (dim == (FLAGS.embedding_size))
    ws = []
    mv = [0 for i in range(dim)]
    # The first for 0
    ws.append([0 for i in range(dim)])
    for t in range(total):
        line = fp.readline().strip()
        ss = line.split(" ")
        assert (len(ss) == (dim + 1))
        vals = []
        for i in range(1, dim + 1):
            fv = float(ss[i])
            mv[i - 1] += fv
            vals.append(fv)
        ws.append(vals)
    for i in range(dim):
        mv[i] = mv[i] / total
    ws.append(mv)
    fp.close()
    return np.asarray(ws, dtype=np.float32)

def test_unary_score(self):
    P, sequence_length = self.inference(self.inp,
                                        reuse=True,
                                        trainMode=False)
    return P, sequence_length

其他的程式碼比較簡單,個人覺得不必要做深入分析。

總結

近一兩個月開始學習TensorFlow,程式碼看了一些,但是總感覺臨門差那麼一腳。革命尚未完成,同志們仍需努力!

相關推薦

開源專案kcws程式碼分析--基於深度學習技術

分詞原理 本小節內容參考待字閨中的兩篇博文: 簡單的說,kcws的分詞原理就是: 對語料進行處理,使用word2vec對語料的字進行嵌入,每個字特徵為50維。 得到字嵌入後,用字嵌入特徵餵給雙向LSTM, 對輸出的隱層加一個線性層,然後加一個CRF就

NLP+詞法系列(二)︱中文技術簡述、深度學習實踐(CIPS2016、超多案例)

詞法分析是將輸入句子從字序列轉化為詞和詞性序列, 句法分析將輸入句子從詞序列形式轉化為樹狀結構,從而刻畫句子的詞法和句法結構。 一、詞法分析的難題 1、詞的定義和生詞問題、未登入詞(新詞) 特別是在網際網路時代,

基於深度學習時間序列預測系統專案需求分析心得

專案第一次迭代已經進入了尾聲,在我們小組剛確定這個專案的時候,花了兩個周的時間來確定專案的需求。以下是我們在進行需求分析的一些心得。   需求分析過程:   (1) 小組內部進行討論:在進行團隊專案開發之初,我們在需求分析還有資料庫設計上花了很多時間,首先是進行多次需求分析的團隊會議,小組人員

基於深度學習的影象檢索 image retrieval based on deep learning (code ,程式碼

本次程式碼分享主要是用的caffe框架,至於caffe框架的安裝過程不再說明。程式碼修改自“cross weights”的一篇2016年的文章,但是名字忘記了,誰記得,提醒我下。 一、環境要求         1、python &nb

基於深度學習分析與檢索海量短視訊內容

在RTC 2018 實時網際網路大會上,美圖雲視覺技術總監趙麗麗分享了美圖在短視訊領域的AI技術應用,內容主要包括三部分:美圖短視訊的業務場景,基於此業務場景所做的短視訊內容分析和檢索技術,以及遇到的問題與相應的解決方案。最後是平臺構建過程中的一些思考。以下是演講內容整理。 美圖在短視訊領域的代表產品就

唐宇迪博士實戰程式碼教學視訊課程全集,帶你一起資料分析深度學習

唐宇迪,同濟大學計算機博士,專注於機器學習與計算機視覺領域,人工智慧與資料科學領域培訓專家,上海人工智慧協會核心主幹。參與多個國家級計算機視覺與資料探勘專案,主要研究面部識別與特徵構造,異常識別等領域。多年資料領域培訓經驗,具有豐富的教學講解經驗,出品多套機器學習與深度學習系

語音識別——基於深度學習的中文語音識別系統實現(程式碼詳解)

文章目錄 利用thchs30為例建立一個語音識別系統 1. 特徵提取 2. 模型搭建 搭建cnn+dnn+ctc的聲學模型 3. 訓練準備 下載資料

基於深度學習的影象語義分析及其應用

本文 轉自“火光搖曳”部落格:語義分析的一些方法(三),主要論述了基於深度學習方法的影象語義分析,包括圖片分類、圖片搜尋、圖片標註(image2text、image2sentence),以及訓練深度神經網路的一些tricks,並介紹語義分析方法在騰訊廣點通上的實際應用。以下

基於深度學習的群體目標流量分析

群體目標流量分析 大場景下(多攝像機 如正門、偏門)估計進出人數 已有方法 檢測+跟蹤 過程 首先檢測人,之後用諸如粒子濾波、KCF等方式跟蹤軌跡,判斷了通過影象的虛擬線,進而決策 缺點 檢測和跟蹤都需要比較穩定,但是在一些密集場合檢測

Amazon Rekognition常見問題_基於深度學習的影象分析服務問題

問:什麼是 Amazon Rekognition? Amazon Rekognition 作為一項服務,能夠讓您輕鬆地將功能強大的視覺化分析新增到應用程式。藉助 Rekognition Image,您可以輕鬆構建功能強大的應用程式來搜尋、驗證和組織數百萬個影象

論文分析--《基於深度學習的人臉表情識別演算法研究_宋新慧》

1.摘要:文章中提出的演算法:1)針對靜態的影象:細節感知遷移網路      資料集:CK+;Kaggle2)針對視訊序列:利用多工學習的遞迴神經網路      資料集:I-PFE2.本文工作:1)基於細節感知遷移網路的人臉表情識別:傳統特徵提取方法存在的問題:光照、角度等影

實戰 | 基於深度學習模型VGG的影象識別(附程式碼

def train():    data_dim = 3 * 32 * 32    class_dim = 10    image = paddle.layer.data(        name="image", type=paddle.data_type.dense_vector(data_dim))  

基於深度學習的自然語言處理》中文PDF+英文PDF+學習分析

機器學習 生成 統計學 alt 依存句法分析 詞向量 tle 工程應用 互聯 我們做自然語言處理的,主要是進行文本分析,作為人工智能的領域之一,也一定會應用深度神經網絡進行處理。 近年來快速發展的深度學習技術為解決自然語言處理問題的解決提供了一種可能的思路,已成為有效推動

基於深度學習方法的dota2遊戲資料分析與勝率預測(python3.6+keras框架實現)

很久以前就有想過使用深度學習模型來對dota2的對局資料進行建模分析,以便在英雄選擇,出裝方面有所指導,幫助自己提升天梯等級,但苦於找不到資料來源,該計劃擱置了很長時間。直到前些日子,看到社群有老哥提到說OpenDota網站(https://www.opendota.com/)提供有一整套的介面可以獲取dot

基於深度學習的圖像語義分割技術概述之5.1度量標準

-s 公平性 的確 由於 表示 n-2 sub 包含 提升 本文為論文閱讀筆記,不當之處,敬請指正。 A Review on Deep Learning Techniques Applied to Semantic Segmentation:原文鏈接 5.1度量標準 為何需

基於深度學習的病毒檢測技術無需沙箱環境,直接將樣本文件轉換為二維圖片,進而應用改造後的卷積神經網絡 Inception V4 進行訓練和檢測

進制 思科 開發 主題 需求 做的 病毒 無法 大於 話題 3: 基於深度學習的二進制惡意樣本檢測 分享主題:全球正在經歷一場由科技驅動的數字化轉型,傳統技術已經不能適應病毒數量飛速增長的發展態勢。而基於沙箱的檢測方案無法滿足 APT 攻擊的檢測需求,也受到多種反沙箱技術的

【OCR技術系列之四】基於深度學習的文字識別(3755個漢字)

架構 indices 編碼 協調器 論文 準備 分享 深度 ast 上一篇提到文字數據集的合成,現在我們手頭上已經得到了3755個漢字(一級字庫)的印刷體圖像數據集,我們可以利用它們進行接下來的3755個漢字的識別系統的搭建。用深度學習做文字識別,用的網絡當然是CNN,那具

基於深度學習生成音樂

cto ont -name 編碼 mp3文件 script 取值 style bsp 之前在看Andrew Ng 的deep learning 視頻教程,在RNN 這一節的課後作業裏,實現了一個基於deepjazz的music generator,實驗之後發現產生的結

基於深度學習做命名實體識別

note 深度學習 以及 效果 數據集 pre 之前 得到 高達 基於CRF做命名實體識別系列 用CRF做命名實體識別(一) 用CRF做命名實體識別(二) 用CRF做命名實體識別(三) 摘要 1. 之前用CRF做了命名實體識別,效果還可以,最高達到0.9293,當然這是自己

【原創】總結大創項目-基於深度學習的智能紅綠燈調控系統

部門 圖像識別 痛苦 支持 軟件 醫療 要求 穩定 車道檢測 一、產品定位分析   (註:以下調研均發生於2017年5月前。)   由於此次項目最初是為了參加Intel舉辦的某屆基於深度學習的創新應用比賽,當時召集了小組成員集思廣益,想一些具有創意的點子作為此次