從零開始造一個「智障」聊天機器人

騰訊DeepOcean原創文章:dopro.io/nlp_seq2seq…html

智能機器人在生活中隨處可見:iPhone裏會說話的siri、會下棋的阿法狗、調皮可愛的微軟小冰……她們都具備必定的智能,可以和人類進行交互。這些智能機器人很是神奇,看上去離咱們也十分遙遠,但其實只要咱們動動手,即可以造一個屬於本身的智能機器人。 
python

本文將教你從零開始造出一個智障,不對是「智能聊天機器人"。


要造一個聊天機器人,首先你須要瞭解一些相關概念——天然語言處理(NLP),它是一門融語言學、計算機科學、數學於一體的科學,研究讓電腦「懂」人類語言的方法。固然,它也包含不少分支:文本朗讀、語音識別、句法分析、天然語言生成、人機對話、信息檢索、信息抽取、文字校對、文本分類、自動文摘、機器翻譯、文字蘊含等等等。 git

看到這裏的朋友,千萬別被這些嚇跑。既然本文叫《從零開始造一個「智障」聊天機器人》那麼各位看官老爺不懂這些也沒有關係!跟着個人腳步一步一步作吧github


0x1 基本概念

這裏涉及到的原理基礎,沒興趣的看官老爺略過便可,不影響後續代碼實現。算法

01|神經網絡數據庫

人工智能的底層是」神經網絡「,許多複雜的應用(好比模式識別、自動控制)和高級模型(好比深度學習)都基於它。學習人工智能,必定是從它開始。 微信

那麼問題來了,什麼是神經網絡呢?簡單來講,神經網絡就是模擬人腦神經元網絡,從而讓計算機懂得」思考「。具體概念在這裏再也不贅述,網絡上有不少簡單易懂的解釋。 網絡

本文使用的的是循環神經網絡(RNN),咱們來看一個最簡單的基本循環神經網絡: app

從零開始造一個「智障」聊天機器人

雖然圖像看起來很抽象,可是實際很好理解。x、o、s是一個向量,x表明輸入層的值,o表明輸出層的值,s是隱藏層的值(這裏其實有不少節點);U、V是權重矩陣,U表明輸入層隱藏層權重矩陣,而V則表明隱藏層輸出層權重矩陣。那麼W是什麼呢?其實循環神經網絡隱藏層的值s不只僅由x、U決定,還會由上一次隱藏層的值s,而W就是上一次到隱藏層到這一次的權重矩陣,將其展開就是這樣:框架

從零開始造一個「智障」聊天機器人

這樣邏輯就清晰不少了,這即是一個簡單的循環神經網絡。而咱們的智障,不對是「智能聊天機器人"即是使用循環神經網絡,基於天然語言的詞法分析、句法分析不斷的訓練語料,並把語義分析都融入進來作的補充和改進。


02|深度學習框架

適合RNN的深度學習框架有不少,本文的聊天機器人基於Google開源的Tensorflow,從GayhubGithub的starts數即可以看出,Tensorflow是一個極其火爆的深度學習框架,而且能夠輕鬆地在cpu / gpu 上進行分佈式計算,下面羅列了一些目前主流深度學習框架的特性,你們能夠憑興趣選擇框架進行研究:

從零開始造一個「智障」聊天機器人


03|seq2seq模型

顧名思義,seq2seq 模型就像一個翻譯模型,輸入是一個序列(好比一個英文句子),輸出也是一個序列(好比該英文句子所對應的法文翻譯)。這種結構最重要的地方在於輸入序列和輸出序列的長度是可變的。 

舉個例子: 

在對話機器中:輸入(hello) -> 輸出 (你好)。 

輸入是1個英文單詞,輸出爲2個漢字。咱們提(輸入)一個問題,機器會自動生成(輸出)回答。這裏的輸入和輸出顯然是長度沒有肯定的序列(sequences)

咱們再舉一個長一點的例子: 

我教小黃雞說「大白天的作什麼好夢啊?」回答是「哦哈哈哈不用你管」。 

Step1應用雙向最大匹配算法分詞:雙向分詞結果,正向《大白天,的,作什麼,好夢,啊》;反向《大白天,的,作什麼,好夢,啊》。正向反向都是同樣的,因此不須要處理歧義問題。長詞優先選擇,「大白天」和「作什麼」。 

Step2:以「大白天」舉例,假設hash函數爲f(),並設f(大白天)指向首字hash表項[大,11,P]。因而由該表項指向「3字索引」,再指向對應「詞表」。 

Step3將結構體<大白天,…>插入隊尾。體中有一個Ans域,域中某一指針指向「哦哈哈哈不用你管」。 

這即是seq2seq的基本原理,原理和技術咱們都有了,下一步就是將它實現出來!


0x2 語料準備

瞭解完一些前置基礎,咱們話很少說,直接進入造智能聊天機器人的階段。首先咱們須要準備相關訓練的語料

01|語料整理

本次訓練的語料庫是從Github上下載的(Github用於對話系統的中英文語料:https://github.com/candlewill/Dialog_Corpus)。咱們下載其中的xiaohuangji50w_fenciA.conv(小黃雞語料)進行咱們的訓練。 

當咱們下載完後打開發現,它這個語料庫是這樣的: 

從零開始造一個「智障」聊天機器人

雖然這裏面的文字、對話咱們都能看懂,可是這些E、M、/都是些什麼鬼?其實從圖來看很容易理解,M即表明這句話,而E則表明一段對話的開始與結束。 

咱們拿到這些語料後,用代碼將其按照問/答分爲兩類"Question.txt"、"Answer.txt":

1import re
2import sys
3def prepare(num_dialogs=50000):
4    with open("xhj.conv"as fopen:
5        # 替換E、M等
6        reg = re.compile("EnM (.*?)nM (.*?)n")
7        match_dialogs = re.findall(reg, fopen.read())
8        # 使用5W條對話做爲訓練語料
9        if num_dialogs >= len(match_dialogs):
10            dialogs = match_dialogs
11        else:
12            dialogs = match_dialogs[:num_dialogs]
13        questions = []
14        answers = []
15        for que, ans in dialogs:
16            questions.append(que)
17            answers.append(ans)
18        # 保存到data/文件夾目錄下
19        save(questions, "data/Question.txt")
20        save(answers, "data/Answer.txt")
21def save(dialogs, file):
22    with open(file, "w"as fopen:
23        fopen.write("n".join(dialogs))

最終咱們獲得5W條問題與回答數據:

從零開始造一個「智障」聊天機器人

02|向量表映射創建

到這裏,你們可能會問,那麼這個"智能"聊天機器人是否是就是將咱們輸入的問題匹配Question.txt裏面的問題,而後再從Answer.txt找到相應回答進行輸出? 

固然不會是這麼簡單,本質上聊天機器人是基於問句的上下文環境產生一個新的回答,而非是從數據庫中拿出一條對應好的回答數據。

那麼機器怎麼知道該回答什麼呢?此處借用一下谷歌的seq2seq原理圖:

從零開始造一個「智障」聊天機器人


簡單來講就是:咱們輸入的每一句話,都會被機器拆成詞並向量化;這些詞做爲輸入層的向量,與權重矩陣進行計算後到隱藏層隱藏層輸出的向量再與權重矩陣進行計算,獲得最終向量。咱們再將此向量映射到詞向量庫時,即可獲得咱們想要的結果。 

在代碼上實現比較簡單,由於複雜底層邏輯的都由Tensorflow幫咱們完成了,咱們將詞彙表進行最終的梳理:

 1def gen_vocabulary_file(input_file, output_file): 
2    vocabulary = {}
3    with open(input_file) as f:
4        counter = 0
5        for line in f:
6            counter += 1
7            tokens = [word for word in line.strip()]
8            for word in tokens:
9                                # 過濾非中文 文字
10                if u'u4e00' <= word <= u'u9fff':
11                    if word in vocabulary:
12                        vocabulary[word] += 1
13                    else:
14                        vocabulary[word] = 1
15        vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True)
16        # 取前3500個經常使用漢字,vocabulary_size = 3500
17        if len(vocabulary_list) > vocabulary_size:
18            vocabulary_list = vocabulary_list[:vocabulary_size]
19        print(input_file + " 詞彙表大小:", len(vocabulary_list))
20        with open(output_file, "w"as ff:
21            for word in vocabulary_list:
22                ff.write(word + "n")
23        ff.close
複製代碼


0x3 開始訓練

01|訓練

在咱們的語料準備好以後,即可以開始我訓練,其實訓練自己是很簡單的,其核心是調用Tensorflow的Seq2SeqModel,不斷的進行循環訓練。下面是訓練的核心代碼與參數設置:

 1# 源輸入詞表的大小
2vocabulary_encode_size = 3500
3# 目標輸出詞表的大小
4vocabulary_decode_size = 3500
5#一種有效處理不一樣長度的句子的方法 
6buckets = [(510), (1015), (2025), (4050)]
7# 每層單元數目
8layer_size = 256
9# 網絡的層數。  
10num_layers = 3
11# 訓練時的批處理大小
12batch_size =  64
13# max_gradient_norm:表示梯度將被最大限度地削減到這個規範
14# learning_rate: 初始的學習率
15# learning_rate_decay_factor: 學習率衰減因子
16# forward_only: false意味着在解碼器端,使用decoder_inputs做爲輸入。例如decoder_inputs 是‘GO, W, X, Y, Z ’,正確的輸出應該是’W, X, Y, Z, EOS’。假設第一個時刻的輸出不是’W’,在第二個時刻也要使用’W’做爲輸入。當設爲true時,只使用decoder_inputs的第一個時刻的輸入,即’GO’,以及解碼器的在每一時刻的真實輸出做爲下一時刻的輸入。
17model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size,buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm= 5.0,batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.97, forward_only=False)
18
19config = tf.ConfigProto()
20config.gpu_options.allocator_type = 'BFC'  # 防止 out of memory
21
22with tf.Session(config=config) as sess:
23    # 恢復前一次訓練
24    ckpt = tf.train.get_checkpoint_state('.')
25    if ckpt != None:
26        print(ckpt.model_checkpoint_path)
27        model.saver.restore(sess, ckpt.model_checkpoint_path)
28    else:
29        sess.run(tf.global_variables_initializer())
30
31    train_set = read_data(train_encode_vec, train_decode_vec)
32    test_set = read_data(test_encode_vec, test_decode_vec)
33
34    train_bucket_sizes = [len(train_set[b]) for b in range(len(buckets))]
35    train_total_size = float(sum(train_bucket_sizes))
36    train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in range(len(train_bucket_sizes))]
37
38    loss = 0.0
39    total_step = 0
40    previous_losses = []
41    # 一直訓練,每過一段時間保存一次模型
42    while True:
43        random_number_01 = np.random.random_sample()
44        bucket_id = min([i for i in range(len(train_buckets_scale)) if train_buckets_scale[i] > random_number_01])
45
46        encoder_inputs, decoder_inputs, target_weights = model.get_batch(train_set, bucket_id)
47        _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, False)
48
49        loss += step_loss / 500
50        total_step += 1
51
52        print(total_step)
53        if total_step % 500 == 0:
54            print(model.global_step.eval(), model.learning_rate.eval(), loss)
55
56            # 若是模型沒有獲得提高,減少learning rate
57            if len(previous_losses) > 2 and loss > max(previous_losses[-3:]):
58                sess.run(model.learning_rate_decay_op)
59            previous_losses.append(loss)
60            # 保存模型
61            checkpoint_path = "chatbot_seq2seq.ckpt"
62            model.saver.save(sess, checkpoint_path, global_step=model.global_step)
63            loss = 0.0
64            # 使用測試數據評估模型
65            for bucket_id in range(len(buckets)):
66                if len(test_set[bucket_id]) == 0:
67                    continue
68                encoder_inputs, decoder_inputs, target_weights = model.get_batch(test_set, bucket_id)
69                _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True)
70                eval_ppx = math.exp(eval_loss) if eval_loss < 300 else float('inf')
71                print(bucket_id, eval_ppx)
複製代碼


 02|實際問答效果

若是咱們的模型一直在訓練,那麼機器怎麼知道在何時中止訓練呢?這個中止訓練的閥值又靠什麼去衡量?在這裏咱們引入一個語言模型評價指標——Perplexity。

① Perplexity是什麼:

PPL是用在天然語言處理領域(NLP)中,衡量語言模型好壞的指標。它主要是根據每一個詞來估計一句話出現的機率,並用句子長度做normalize,公式爲 :

從零開始造一個「智障」聊天機器人

S表明sentence,N是句子長度,p(wi)是第i個詞的機率。第一個詞就是 p(w1|w0),而w0是START,表示句子的起始,是個佔位符。 
這個式子能夠這樣理解,PPL越小,p(wi)則越大,一句咱們指望的sentence出現的機率就越高。 

還有人說,Perplexity能夠認爲是average branch factor(平均分支系數),即預測下一個詞時能夠有多少種選擇。別人在做報告時說模型的PPL降低到90,能夠直觀地理解爲,在模型生成一句話時下一個詞有90個合理選擇,可選詞數越少,咱們大體認爲模型越準確。這樣也能解釋,爲何PPL越小,模型越好。

對於咱們的訓練,其最近幾回的Perplexity以下:

從零開始造一個「智障」聊天機器人

截止發文時,此模型已經訓練了27h,其Perplexity仍然比較難收斂,因此模型的訓練真的須要一些耐心。若是有條件使用GPU進行訓練,那麼此速度將會大大提升。

咱們使用現階段的模型進行一些對話,發現已經初具雛形:

從零開始造一個「智障」聊天機器人

  

至此,咱們的「智能聊天機器人」已經大功告成!但不難看出,這個機器人仍是在不斷的犯傻,不少問題牛頭不對馬嘴,因此咱們又稱其爲「智障機器人」。

0x4 結語

至此咱們就從無到有訓練了一個問答機器人,雖然它還有點」智障「不太理解更多的詞彙,可是總體流程已經跑通,而且具備必定的效果。後面的工做就是不斷的完善其中的算法參數語料了。其中語料是特別關鍵的部分,大概會佔用到50%-70%的工做量,由於本文使用的是互聯網上已經處理好的語料,省去了很多時間。事實上大部分開發人員的時間都在進行語料預處理:數據清洗分詞詞性標註去停用詞等方面。 

後續有機會再和你們分享語料預處理這一塊。這裏有一個可愛的二維碼,你們記得關注喲~

從零開始造一個「智障」聊天機器人


相關文獻與參考資料:

從機器學習談起 (http://www.cnblogs.com/subconscious/p/4107357.html)

使用python實現神經網絡 (http://www.wildml.com/2015/09/implementing-a-neural-network-from-scratch/)

循環神經網絡 (https://zybuluo.com/hanbingtao/note/541458)

語言模型評價指標 (https://blog.csdn.net/index20001/article/details/78884646)

Tensorflow(https://github.com/google/seq2seq)


始發於微信公衆號: 騰訊DeepOcean

相關文章
相關標籤/搜索