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

自動問答簡介

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

image

自動問答主要研究的內(nèi)容和關(guān)鍵科學問題如下:

  1. 問句理解:給定用戶問題,自動問答首先需要理解用戶所提問題虚吟。用戶問句的語義理解包含詞法分析寸认、句法分析、語義分析等多項關(guān)鍵技術(shù)串慰,需要從文本的多個維度理解其中包含的語義內(nèi)容偏塞。

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

  3. 知識推理:自動問答中数尿,由于語料庫、知識庫和問答庫本身的覆蓋度有限怜姿,并不是所有問題都能直接找到答案慎冤。這就需要在已有的知識體系中疼燥,通過知識推理的手段獲取這些隱含的答案沧卢。

縱觀自動問答研究的發(fā)展態(tài)勢和技術(shù)現(xiàn)狀,以下研究方向或問題將可能成為未來整個領(lǐng)域和行業(yè)重點關(guān)注的方向:基于深度學習的端到端自動問答醉者,多領(lǐng)域但狭、多語言的自動問答,面向問答的深度推理撬即,篇章閱讀理解立磁、對話等。

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

ChatterBot 是一個構(gòu)建在 Python 上剥槐,基于一系列規(guī)則和機器學習算法完成的聊天機器人唱歧,具有結(jié)構(gòu)清晰,可擴展性好粒竖,簡單實用的特點颅崩。

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. 手動設(shè)置一點語料,體驗基于規(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種色調(diào)恨锚。',
])

下面進行測試:

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

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

親,在嗎

親,在呢

有紅色的嗎他挎?

有呢筝尾,目前有白紅藍3種色調(diào)。

  1. 訓練自己的語料办桨。

本次使用的語料來自 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 群技術(shù)對話,也看得出程序員們都很努力太颤,整體想的都是學習苞俘。

image

以上只是簡單的 Chatterbot 演示,如果想看更好的應用龄章,推薦看官方文檔吃谣。

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

前面,我們講了序列數(shù)據(jù)處理模型做裙,從 N-gram 語言模型到 RNN 及其變種岗憋。這里我們講另外一個基于深度學習的 Seq2Seq 模型。

從 RNN 結(jié)構(gòu)說起锚贱,根據(jù)輸出和輸入序列不同數(shù)量 RNN 仔戈,可以有多種不同的結(jié)構(gòu),不同結(jié)構(gòu)自然就有不同的引用場合拧廊。

  • One To One 結(jié)構(gòu)监徘,僅僅只是簡單的給一個輸入得到一個輸出,此處并未體現(xiàn)序列的特征吧碾,例如圖像分類場景凰盔。
  • One To Many 結(jié)構(gòu),給一個輸入得到一系列輸出倦春,這種結(jié)構(gòu)可用于生產(chǎn)圖片描述的場景户敬。
  • Many To One 結(jié)構(gòu)落剪,給一系列輸入得到一個輸出,這種結(jié)構(gòu)可用于文本情感分析尿庐,對一些列的文本輸入進行分類忠怖,看是消極還是積極情感。
  • Many To Many 結(jié)構(gòu)抄瑟,給一系列輸入得到一系列輸出凡泣,這種結(jié)構(gòu)可用于翻譯或聊天對話場景,將輸入的文本轉(zhuǎn)換成另外一系列文本锐借。
  • 同步 Many To Many 結(jié)構(gòu)问麸,它是經(jīng)典的 RNN 結(jié)構(gòu),前一輸入的狀態(tài)會帶到下一個狀態(tài)中钞翔,而且每個輸入都會對應一個輸出,我們最熟悉的應用場景是字符預測席舍,同樣也可以用于視頻分類布轿,對視頻的幀打標簽。

在 Many To Many 的兩種模型中来颤,第四和第五種是有差異的汰扭,經(jīng)典 RNN 結(jié)構(gòu)的輸入和輸出序列必須要等長,它的應用場景也比較有限福铅。而第四種萝毛,輸入和輸出序列可以不等長,這種模型便是 Seq2Seq 模型滑黔,即 Sequence to Sequence笆包。它實現(xiàn)了從一個序列到另外一個序列的轉(zhuǎn)換,比如 Google 曾用 Seq2Seq 模型加 Attention 模型實現(xiàn)了翻譯功能略荡,類似的還可以實現(xiàn)聊天機器人對話模型庵佣。經(jīng)典的 RNN 模型固定了輸入序列和輸出序列的大小,而 Seq2Seq 模型則突破了該限制汛兜。

Seq2Seq 屬于 Encoder-Decoder 結(jié)構(gòu)巴粪,這里看看常見的 Encoder-Decoder 結(jié)構(gòu)≈嗝基本思想就是利用兩個 RNN肛根,一個 RNN 作為 Encoder,另一個 RNN 作為 Decoder漏策。Encoder 負責將輸入序列壓縮成指定長度的向量派哲,這個向量就可以看成是這個序列的語義,這個過程稱為編碼哟玷,如下圖狮辽,獲取語義向量最簡單的方式就是直接將最后一個輸入的隱狀態(tài)作為語義向量一也。也可以對最后一個隱含狀態(tài)做一個變換得到語義向量,還可以將輸入序列的所有隱含狀態(tài)做一個變換得到語義變量喉脖。

image

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

1. 語料準備舆蝴。

語料我們使用 Tab 鍵 \t 把問題和答案區(qū)分,每一對為一行题诵。其中洁仗,語料為爬蟲爬取的工程機械網(wǎng)站的問答。

2. 模型構(gòu)建和訓練性锭。

第一步赠潦,引入需要的包:

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 
#設(shè)置語料的路徑
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)

第六步拳恋,把索引和分詞轉(zhuǎn)成序列:

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ù),先使用預模型預測谬运,然后編碼成漢字結(jié)果:

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

  1. 模型預測隙赁。

首先,定義一個預測函數(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("挖機履帶掉了怎么裝上去"))

總結(jié)

本文我們首先基于 Chatterbot 制作了中文聊天機器人吩谦,并用 QQ 群對話語料自己嘗試訓練鸳谜。然后通過 LSTM 和 Seq2Seq 模型,根據(jù)爬取的語料式廷,訓練了一個自動問答的模型咐扭,通過以上兩種方式,我們們對自動問答有了一個簡單的入門滑废。

參考文獻及推薦閱讀:

  1. 《中文信息處理發(fā)展報告(2016)》
  2. ChatterBot 文檔
  3. ChatterBot 的 GitHub
  4. Sutskever, Vinyals and Le (2014)
  5. 漫談四種神經(jīng)網(wǎng)絡(luò)序列解碼模型
  6. 怎樣導出 QQ 群里的所有聊天記錄蝗肪?
  7. Keras 中的 LSTM_Seq2Seq 例子
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蠕趁,隨后出現(xiàn)的幾起案子薛闪,更是在濱河造成了極大的恐慌,老刑警劉巖俺陋,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豁延,死亡現(xiàn)場離奇詭異昙篙,居然都是意外死亡,警方通過查閱死者的電腦和手機诱咏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門苔可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袋狞,你說我怎么就攤上這事焚辅。” “怎么了苟鸯?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵同蜻,是天一觀的道長。 經(jīng)常有香客問我早处,道長湾蔓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任陕赃,我火速辦了婚禮卵蛉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘么库。我一直安慰自己,他們只是感情好甘有,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布诉儒。 她就那樣靜靜地躺著,像睡著了一般亏掀。 火紅的嫁衣襯著肌膚如雪忱反。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天滤愕,我揣著相機與錄音温算,去河邊找鬼。 笑死间影,一個胖子當著我的面吹牛注竿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魂贬,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼巩割,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了付燥?” 一聲冷哼從身側(cè)響起宣谈,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎键科,沒想到半個月后闻丑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漩怎,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年嗦嗡,在試婚紗的時候發(fā)現(xiàn)自己被綠了勋锤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡酸钦,死狀恐怖怪得,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卑硫,我是刑警寧澤徒恋,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站欢伏,受9級特大地震影響入挣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜硝拧,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一径筏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧障陶,春花似錦滋恬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鼓寺,卻和暖如春勋拟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妈候。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工敢靡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苦银。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓啸胧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親墓毒。 傳聞我的和親對象是個殘疾皇子吓揪,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容