第14課:動手制作自己的簡易聊天機器人

自動問答簡介

自動聊天機器人,也稱為自動問答系統(tǒng),由于所使用的場景不同,叫法也不一樣而涉。自動問答(Question Answering,QA)是指利用計算機自動回答用戶所提出的問題以滿足用戶知識需求的任務联予。不同于現(xiàn)有搜索引擎啼县,問答系統(tǒng)是信息服務的一種高級形式,系統(tǒng)返回用戶的不再是基于關鍵詞匹配排序的文檔列表沸久,而是精準的自然語言答案季眷。近年來,隨著人工智能的飛速發(fā)展麦向,自動問答已經成為倍受關注且發(fā)展前景廣泛的研究方向瘟裸。

enter image description here

自動問答主要研究的內容和關鍵科學問題如下:

  1. 問句理解:給定用戶問題客叉,自動問答首先需要理解用戶所提問題诵竭。用戶問句的語義理解包含詞法分析、句法分析兼搏、語義分析等多項關鍵技術卵慰,需要從文本的多個維度理解其中包含的語義內容。

  2. 文本信息抽取:自動問答系統(tǒng)需要在已有語料庫佛呻、知識庫或問答庫中匹配相關的信息裳朋,并抽取出相應的答案。

  3. 知識推理:自動問答中吓著,由于語料庫鲤嫡、知識庫和問答庫本身的覆蓋度有限送挑,并不是所有問題都能直接找到答案。這就需要在已有的知識體系中暖眼,通過知識推理的手段獲取這些隱含的答案惕耕。

縱觀自動問答研究的發(fā)展態(tài)勢和技術現(xiàn)狀,以下研究方向或問題將可能成為未來整個領域和行業(yè)重點關注的方向:基于深度學習的端到端自動問答诫肠,多領域司澎、多語言的自動問答,面向問答的深度推理栋豫,篇章閱讀理解挤安、對話等。

基于 Chatterbot 制作中文聊天機器人

ChatterBot 是一個構建在 Python 上丧鸯,基于一系列規(guī)則和機器學習算法完成的聊天機器人蛤铜,具有結構清晰,可擴展性好丛肢,簡單實用的特點昂羡。

Chatterbot 安裝有兩種方式:

  • 使用 pip install chatterbot 安裝;
  • 直接在 Github Chatterbot 下載這個項目摔踱,通過 python setup.py install 安裝虐先,其中 examples 文件夾中包含幾個例子,可以根據(jù)例子加深自己的理解派敷。

安裝過程如果出現(xiàn)錯誤蛹批,主要是需要安裝這些依賴庫:

chatterbot-corpus>=1.1,<1.2
mathparse>=0.1,<0.2
nltk>=3.2,<4.0
pymongo>=3.3,<4.0
python-dateutil>=2.6,<2.7
python-twitter>=3.0,<4.0
sqlalchemy>=1.2,<1.3
pint>=0.8.1

1. 手動設置一點語料,體驗基于規(guī)則的聊天機器人回答篮愉。

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer
Chinese_bot = ChatBot("Training demo") #創(chuàng)建一個新的實例
Chinese_bot.set_trainer(ListTrainer)
Chinese_bot.train([
    '親腐芍,在嗎?',
    '親试躏,在呢',
    '這件衣服的號碼大小標準嗎猪勇?',
    '親,標準呢颠蕴,請放心下單吧泣刹。',
    '有紅色的嗎?',
    '有呢犀被,目前有白紅藍3種色調椅您。',
])

下面進行測試:

# 測試一下
question = '親,在嗎'
print(question)
response = Chinese_bot.get_response(question)
print(response)
print("\n")
question = '有紅色的嗎寡键?'
print(question)
response = Chinese_bot.get_response(question)
print(response)

從得到的結果可以看出掀泳,這應該完全是基于規(guī)則的判斷:

親,在嗎

親,在呢

有紅色的嗎员舵?

有呢脑沿,目前有白紅藍3種色調。

2. 訓練自己的語料马僻。

本次使用的語料來自 QQ 群的聊天記錄捅伤,導出的 QQ 聊天記錄稍微處理一下即可使用,整個過程如下巫玻。

(1)首先載入語料丛忆,第二行代碼主要是想把每句話后面的換行 \n 去掉。

lines = open("QQ.txt","r",encoding='gbk').readlines()
sec = [ line.strip() for line in lines]

(2)接下來就可以訓練模型了仍秤,由于整個語料比較大熄诡,訓練過程也比較耗時。

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer
Chinese_bot = ChatBot("Training")
Chinese_bot.set_trainer(ListTrainer)
Chinese_bot.train(sec)

這里需要注意诗力,如果訓練過程很慢凰浮,可以在第一步中加入如下代碼,即只取前1000條進行訓練:

sec = sec[0:1000]

(3)最后苇本,對訓練好的模型進行測試袜茧,可見訓練數(shù)據(jù)是 QQ 群技術對話,也看得出程序員們都很努力瓣窄,整體想的都是學習笛厦。

enter image description here

以上只是簡單的 Chatterbot 演示,如果想看更好的應用俺夕,推薦看官方文檔裳凸。

基于 Seq2Seq 制作中文聊天機器人

前面,我們在第09課《一網打盡神經序列模型之 RNN 及其變種 LSTM劝贸、GRU》中講了序列數(shù)據(jù)處理模型姨谷,從 N-gram 語言模型到 RNN 及其變種。這里我們講另外一個基于深度學習的 Seq2Seq 模型映九。

從 RNN 結構說起梦湘,根據(jù)輸出和輸入序列不同數(shù)量 RNN ,可以有多種不同的結構件甥,不同結構自然就有不同的引用場合捌议。

  • One To One 結構,僅僅只是簡單的給一個輸入得到一個輸出嚼蚀,此處并未體現(xiàn)序列的特征禁灼,例如圖像分類場景。
  • One To Many 結構轿曙,給一個輸入得到一系列輸出,這種結構可用于生產圖片描述的場景。
  • Many To One 結構导帝,給一系列輸入得到一個輸出守谓,這種結構可用于文本情感分析,對一些列的文本輸入進行分類您单,看是消極還是積極情感斋荞。
  • Many To Many 結構,給一系列輸入得到一系列輸出虐秦,這種結構可用于翻譯或聊天對話場景平酿,將輸入的文本轉換成另外一系列文本。
  • 同步 Many To Many 結構悦陋,它是經典的 RNN 結構蜈彼,前一輸入的狀態(tài)會帶到下一個狀態(tài)中,而且每個輸入都會對應一個輸出俺驶,我們最熟悉的應用場景是字符預測幸逆,同樣也可以用于視頻分類,對視頻的幀打標簽暮现。

在 Many To Many 的兩種模型中还绘,第四和第五種是有差異的,經典 RNN 結構的輸入和輸出序列必須要等長栖袋,它的應用場景也比較有限拍顷。而第四種,輸入和輸出序列可以不等長塘幅,這種模型便是 Seq2Seq 模型菇怀,即 Sequence to Sequence。它實現(xiàn)了從一個序列到另外一個序列的轉換晌块,比如 Google 曾用 Seq2Seq 模型加 Attention 模型實現(xiàn)了翻譯功能爱沟,類似的還可以實現(xiàn)聊天機器人對話模型。經典的 RNN 模型固定了輸入序列和輸出序列的大小匆背,而 Seq2Seq 模型則突破了該限制呼伸。

Seq2Seq 屬于 Encoder-Decoder 結構,這里看看常見的 Encoder-Decoder 結構钝尸±ㄏ恚基本思想就是利用兩個 RNN,一個 RNN 作為 Encoder珍促,另一個 RNN 作為 Decoder铃辖。Encoder 負責將輸入序列壓縮成指定長度的向量,這個向量就可以看成是這個序列的語義猪叙,這個過程稱為編碼娇斩,如下圖仁卷,獲取語義向量最簡單的方式就是直接將最后一個輸入的隱狀態(tài)作為語義向量。也可以對最后一個隱含狀態(tài)做一個變換得到語義向量犬第,還可以將輸入序列的所有隱含狀態(tài)做一個變換得到語義變量锦积。

enter image description here

具體理論知識這里不再贅述,下面重點看看歉嗓,如何通過 Keras 實現(xiàn)一個 LSTM_Seq2Seq 自動問答機器人丰介。

1. 語料準備。

語料我們使用 Tab 鍵 \t 把問題和答案區(qū)分鉴分,每一對為一行哮幢。其中,語料為爬蟲爬取的工程機械網站的問答志珍。

2. 模型構建和訓練橙垢。

第一步,引入需要的包:

from keras.models import Model
from keras.layers import Input, LSTM, Dense
import numpy as np
import pandas as pd

第二步碴裙,定義模型超參數(shù)钢悲、迭代次數(shù)、語料路徑:

#Batch size 的大小
batch_size = 32  
# 迭代次數(shù)epochs
epochs = 100
# 編碼空間的維度Latent dimensionality 
latent_dim = 256  
# 要訓練的樣本數(shù)
num_samples = 5000 
#設置語料的路徑
data_path = 'D://nlp//ch13//files.txt'

第三步舔株,把語料向量化:

#把數(shù)據(jù)向量話
input_texts = []
target_texts = []
input_characters = set()
target_characters = set()

with open(data_path, 'r', encoding='utf-8') as f:
    lines = f.read().split('\n')
for line in lines[: min(num_samples, len(lines) - 1)]:
    #print(line)
    input_text, target_text = line.split('\t')
    # We use "tab" as the "start sequence" character
    # for the targets, and "\n" as "end sequence" character.
    target_text = target_text[0:100]
    target_text = '\t' + target_text + '\n'
    input_texts.append(input_text)
    target_texts.append(target_text)

    for char in input_text:
        if char not in input_characters:
            input_characters.add(char)
    for char in target_text:
        if char not in target_characters:
            target_characters.add(char)

input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
max_encoder_seq_length = max([len(txt) for txt in input_texts])
max_decoder_seq_length = max([len(txt) for txt in target_texts])

print('Number of samples:', len(input_texts))
print('Number of unique input tokens:', num_encoder_tokens)
print('Number of unique output tokens:', num_decoder_tokens)
print('Max sequence length for inputs:', max_encoder_seq_length)
print('Max sequence length for outputs:', max_decoder_seq_length)

input_token_index = dict(
    [(char, i) for i, char in enumerate(input_characters)])
target_token_index = dict(
    [(char, i) for i, char in enumerate(target_characters)])

encoder_input_data = np.zeros(
    (len(input_texts), max_encoder_seq_length, num_encoder_tokens),dtype='float32')
decoder_input_data = np.zeros(
    (len(input_texts), max_decoder_seq_length, num_decoder_tokens),dtype='float32')
decoder_target_data = np.zeros(
    (len(input_texts), max_decoder_seq_length, num_decoder_tokens),dtype='float32')

for i, (input_text, target_text) in enumerate(zip(input_texts, target_texts)):
    for t, char in enumerate(input_text):
        encoder_input_data[i, t, input_token_index[char]] = 1.
    for t, char in enumerate(target_text):
        # decoder_target_data is ahead of decoder_input_data by one timestep
        decoder_input_data[i, t, target_token_index[char]] = 1.
        if t > 0:
            # decoder_target_data will be ahead by one timestep
            # and will not include the start character.
            decoder_target_data[i, t - 1, target_token_index[char]] = 1.

第四步莺琳,LSTM_Seq2Seq 模型定義、訓練和保存:

encoder_inputs = Input(shape=(None, num_encoder_tokens))
encoder = LSTM(latent_dim, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)
# 輸出 `encoder_outputs` 
encoder_states = [state_h, state_c]

# 狀態(tài) `encoder_states` 
decoder_inputs = Input(shape=(None, num_decoder_tokens))
decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_inputs,
                       initial_state=encoder_states)
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)

# 定義模型
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)

# 訓練
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
model.fit([encoder_input_data, decoder_input_data], decoder_target_data,
          batch_size=batch_size,
          epochs=epochs,
          validation_split=0.2)
# 保存模型
model.save('s2s.h5')

第五步载慈,Seq2Seq 的 Encoder 操作:

encoder_model = Model(encoder_inputs, encoder_states)

decoder_state_input_h = Input(shape=(latent_dim,))
decoder_state_input_c = Input(shape=(latent_dim,))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]
decoder_outputs, state_h, state_c = decoder_lstm(
    decoder_inputs, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
decoder_outputs = decoder_dense(decoder_outputs)
decoder_model = Model(
    [decoder_inputs] + decoder_states_inputs,
    [decoder_outputs] + decoder_states)

第六步惭等,把索引和分詞轉成序列:

reverse_input_char_index = dict(
    (i, char) for char, i in input_token_index.items())
reverse_target_char_index = dict(
    (i, char) for char, i in target_token_index.items())

第七步,定義預測函數(shù)办铡,先使用預模型預測辞做,然后編碼成漢字結果:

def decode_sequence(input_seq):
    # Encode the input as state vectors.
    states_value = encoder_model.predict(input_seq)
    #print(states_value)

    # Generate empty target sequence of length 1.
    target_seq = np.zeros((1, 1, num_decoder_tokens))
    # Populate the first character of target sequence with the start character.
    target_seq[0, 0, target_token_index['\t']] = 1.

    # Sampling loop for a batch of sequences
    # (to simplify, here we assume a batch of size 1).
    stop_condition = False
    decoded_sentence = ''
    while not stop_condition:
        output_tokens, h, c = decoder_model.predict(
            [target_seq] + states_value)

        # Sample a token
        sampled_token_index = np.argmax(output_tokens[0, -1, :])
        sampled_char = reverse_target_char_index[sampled_token_index]
        decoded_sentence += sampled_char
        if (sampled_char == '\n' or
           len(decoded_sentence) > max_decoder_seq_length):
            stop_condition = True

        # Update the target sequence (of length 1).
        target_seq = np.zeros((1, 1, num_decoder_tokens))
        target_seq[0, 0, sampled_token_index] = 1.
        # 更新狀態(tài)
        states_value = [h, c]
    return decoded_sentence

3. 模型預測。

首先寡具,定義一個預測函數(shù):

def predict_ans(question):
        inseq = np.zeros((len(question), max_encoder_seq_length, num_encoder_tokens),dtype='float16')
        decoded_sentence = decode_sequence(inseq)
        return decoded_sentence

然后就可以預測了:

print('Decoded sentence:', predict_ans("挖機履帶掉了怎么裝上去"))

總結

本文我們首先基于 Chatterbot 制作了中文聊天機器人秤茅,并用 QQ 群對話語料自己嘗試訓練。然后通過 LSTM 和 Seq2Seq 模型童叠,根據(jù)爬取的語料框喳,訓練了一個自動問答的模型,通過以上兩種方式厦坛,我們們對自動問答有了一個簡單的入門五垮。

參考文獻及推薦閱讀:

  1. 《中文信息處理發(fā)展報告(2016)》
  2. ChatterBot 文檔
  3. ChatterBot 的 GitHub
  4. Sutskever, Vinyals and Le (2014)
  5. 漫談四種神經網絡序列解碼模型
  6. 怎樣導出 QQ 群里的所有聊天記錄?
  7. Keras 中的 LSTM_Seq2Seq 例子

如有侵權請聯(lián)系QQ:758230255刪除

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末杜秸,一起剝皮案震驚了整個濱河市放仗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撬碟,老刑警劉巖诞挨,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉撇,死亡現(xiàn)場離奇詭異,居然都是意外死亡亭姥,警方通過查閱死者的電腦和手機稼钩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門顾稀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來达罗,“玉大人,你說我怎么就攤上這事静秆×溉啵” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵抚笔,是天一觀的道長扶认。 經常有香客問我,道長殊橙,這世上最難降的妖魔是什么辐宾? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮膨蛮,結果婚禮上叠纹,老公的妹妹穿的比我還像新娘。我一直安慰自己敞葛,他們只是感情好誉察,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惹谐,像睡著了一般持偏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氨肌,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天鸿秆,我揣著相機與錄音,去河邊找鬼怎囚。 笑死卿叽,一個胖子當著我的面吹牛,可吹牛的內容都是我干的桩了。 我是一名探鬼主播附帽,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼井誉!你這毒婦竟也來了蕉扮?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颗圣,失蹤者是張志新(化名)和其女友劉穎喳钟,沒想到半個月后屁使,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡奔则,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年蛮寂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片易茬。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡酬蹋,死狀恐怖,靈堂內的尸體忽然破棺而出抽莱,到底是詐尸還是另有隱情范抓,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布食铐,位于F島的核電站匕垫,受9級特大地震影響,放射性物質發(fā)生泄漏虐呻。R本人自食惡果不足惜象泵,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斟叼。 院中可真熱鬧偶惠,春花似錦、人聲如沸犁柜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馋缅。三九已至扒腕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萤悴,已是汗流浹背瘾腰。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留覆履,地道東北人蹋盆。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像硝全,于是被迫代替她去往敵國和親栖雾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內容