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

自動問答簡介

自動聊天機(jī)器人疯汁,也稱為自動問答系統(tǒng)辱士,由于所使用的場景不同棕孙,叫法也不一樣享完。自動問答(Question Answering在辆,QA)是指利用計(jì)算機(jī)自動回答用戶所提出的問題以滿足用戶知識需求的任務(wù)。不同于現(xiàn)有搜索引擎竿滨,問答系統(tǒng)是信息服務(wù)的一種高級形式队他,系統(tǒng)返回用戶的不再是基于關(guān)鍵詞匹配排序的文檔列表,而是精準(zhǔn)的自然語言答案寞蚌。近年來田巴,隨著人工智能的飛速發(fā)展,自動問答已經(jīng)成為倍受關(guān)注且發(fā)展前景廣泛的研究方向挟秤。

enter image description here

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

  1. 問句理解:給定用戶問題壹哺,自動問答首先需要理解用戶所提問題。用戶問句的語義理解包含詞法分析艘刚、句法分析管宵、語義分析等多項(xiàng)關(guān)鍵技術(shù),需要從文本的多個(gè)維度理解其中包含的語義內(nèi)容攀甚。

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

  3. 知識推理:自動問答中炸庞,由于語料庫、知識庫和問答庫本身的覆蓋度有限荚斯,并不是所有問題都能直接找到答案埠居。這就需要在已有的知識體系中,通過知識推理的手段獲取這些隱含的答案鲸拥。

縱觀自動問答研究的發(fā)展態(tài)勢和技術(shù)現(xiàn)狀拐格,以下研究方向或問題將可能成為未來整個(gè)領(lǐng)域和行業(yè)重點(diǎn)關(guān)注的方向:基于深度學(xué)習(xí)的端到端自動問答,多領(lǐng)域刑赶、多語言的自動問答捏浊,面向問答的深度推理,篇章閱讀理解撞叨、對話等金踪。

基于 Chatterbot 制作中文聊天機(jī)器人

ChatterBot 是一個(gè)構(gòu)建在 Python 上,基于一系列規(guī)則和機(jī)器學(xué)習(xí)算法完成的聊天機(jī)器人牵敷,具有結(jié)構(gòu)清晰胡岔,可擴(kuò)展性好,簡單實(shí)用的特點(diǎn)枷餐。

Chatterbot 安裝有兩種方式:

  • 使用 pip install chatterbot 安裝靶瘸;
  • 直接在 Github Chatterbot 下載這個(gè)項(xiàng)目,通過 python setup.py install 安裝,其中 examples 文件夾中包含幾個(gè)例子怨咪,可以根據(jù)例子加深自己的理解屋剑。

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

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è)置一點(diǎn)語料诗眨,體驗(yàn)基于規(guī)則的聊天機(jī)器人回答唉匾。

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer
Chinese_bot = ChatBot("Training demo") #創(chuàng)建一個(gè)新的實(shí)例
Chinese_bot.set_trainer(ListTrainer)
Chinese_bot.train([
    '親,在嗎匠楚?',
    '親巍膘,在呢',
    '這件衣服的號碼大小標(biāo)準(zhǔn)嗎?',
    '親芋簿,標(biāo)準(zhǔn)呢峡懈,請放心下單吧。',
    '有紅色的嗎益咬?',
    '有呢逮诲,目前有白紅藍(lán)3種色調(diào)。',
])

下面進(jìn)行測試:

# 測試一下
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é)果可以看出,這應(yīng)該完全是基于規(guī)則的判斷:

親冗锁,在嗎

親齐唆,在呢

有紅色的嗎?

有呢冻河,目前有白紅藍(lán)3種色調(diào)箍邮。

  1. 訓(xùn)練自己的語料。

本次使用的語料來自 QQ 群的聊天記錄叨叙,導(dǎo)出的 QQ 聊天記錄稍微處理一下即可使用锭弊,整個(gè)過程如下。

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

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

(2)接下來就可以訓(xùn)練模型了,由于整個(gè)語料比較大钮呀,訓(xùn)練過程也比較耗時(shí)剑鞍。

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

這里需要注意,如果訓(xùn)練過程很慢爽醋,可以在第一步中加入如下代碼蚁署,即只取前1000條進(jìn)行訓(xùn)練:

sec = sec[0:1000]

(3)最后,對訓(xùn)練好的模型進(jìn)行測試蚂四,可見訓(xùn)練數(shù)據(jù)是 QQ 群技術(shù)對話光戈,也看得出程序員們都很努力哪痰,整體想的都是學(xué)習(xí)。


enter image description here

以上只是簡單的 Chatterbot 演示久妆,如果想看更好的應(yīng)用妒御,推薦看官方文檔。

基于 Seq2Seq 制作中文聊天機(jī)器人

前面镇饺,我們講了序列數(shù)據(jù)處理模型,從 N-gram 語言模型到 RNN 及其變種送讲。這里我們講另外一個(gè)基于深度學(xué)習(xí)的 Seq2Seq 模型奸笤。

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

  • One To One 結(jié)構(gòu)异希,僅僅只是簡單的給一個(gè)輸入得到一個(gè)輸出健盒,此處并未體現(xiàn)序列的特征,例如圖像分類場景称簿。
  • One To Many 結(jié)構(gòu)扣癣,給一個(gè)輸入得到一系列輸出,這種結(jié)構(gòu)可用于生產(chǎn)圖片描述的場景憨降。
  • Many To One 結(jié)構(gòu)父虑,給一系列輸入得到一個(gè)輸出,這種結(jié)構(gòu)可用于文本情感分析授药,對一些列的文本輸入進(jìn)行分類士嚎,看是消極還是積極情感。
  • 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)會帶到下一個(gè)狀態(tài)中九火,而且每個(gè)輸入都會對應(yīng)一個(gè)輸出赚窃,我們最熟悉的應(yīng)用場景是字符預(yù)測,同樣也可以用于視頻分類岔激,對視頻的幀打標(biāo)簽勒极。

在 Many To Many 的兩種模型中,第四和第五種是有差異的虑鼎,經(jīng)典 RNN 結(jié)構(gòu)的輸入和輸出序列必須要等長辱匿,它的應(yīng)用場景也比較有限键痛。而第四種,輸入和輸出序列可以不等長匾七,這種模型便是 Seq2Seq 模型絮短,即 Sequence to Sequence。它實(shí)現(xiàn)了從一個(gè)序列到另外一個(gè)序列的轉(zhuǎn)換昨忆,比如 Google 曾用 Seq2Seq 模型加 Attention 模型實(shí)現(xiàn)了翻譯功能丁频,類似的還可以實(shí)現(xiàn)聊天機(jī)器人對話模型。經(jīng)典的 RNN 模型固定了輸入序列和輸出序列的大小邑贴,而 Seq2Seq 模型則突破了該限制席里。

Seq2Seq 屬于 Encoder-Decoder 結(jié)構(gòu),這里看看常見的 Encoder-Decoder 結(jié)構(gòu)拢驾〗贝牛基本思想就是利用兩個(gè) RNN,一個(gè) RNN 作為 Encoder繁疤,另一個(gè) RNN 作為 Decoder咖为。Encoder 負(fù)責(zé)將輸入序列壓縮成指定長度的向量,這個(gè)向量就可以看成是這個(gè)序列的語義稠腊,這個(gè)過程稱為編碼躁染,如下圖,獲取語義向量最簡單的方式就是直接將最后一個(gè)輸入的隱狀態(tài)作為語義向量麻养。也可以對最后一個(gè)隱含狀態(tài)做一個(gè)變換得到語義向量褐啡,還可以將輸入序列的所有隱含狀態(tài)做一個(gè)變換得到語義變量。

enter image description here

具體理論知識這里不再贅述鳖昌,下面重點(diǎn)看看备畦,如何通過 Keras 實(shí)現(xiàn)一個(gè) LSTM_Seq2Seq 自動問答機(jī)器人。

1. 語料準(zhǔn)備许昨。

語料我們使用 Tab 鍵 \t 把問題和答案區(qū)分懂盐,每一對為一行。其中糕档,語料為爬蟲爬取的工程機(jī)械網(wǎng)站的問答莉恼。

2. 模型構(gòu)建和訓(xùn)練。

第一步速那,引入需要的包:

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  
# 要訓(xùn)練的樣本數(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 模型定義荔烧、訓(xùn)練和保存:

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)

# 訓(xùn)練
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())

第七步,定義預(yù)測函數(shù)踊餐,先使用預(yù)模型預(yù)測景醇,然后編碼成漢字結(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. 模型預(yù)測。

首先吝岭,定義一個(gè)預(yù)測函數(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

然后就可以預(yù)測了:

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

總結(jié)

本文我們首先基于 Chatterbot 制作了中文聊天機(jī)器人三痰,并用 QQ 群對話語料自己嘗試訓(xùn)練。然后通過 LSTM 和 Seq2Seq 模型窜管,根據(jù)爬取的語料酒觅,訓(xùn)練了一個(gè)自動問答的模型,通過以上兩種方式微峰,我們們對自動問答有了一個(gè)簡單的入門。

參考文獻(xiàn)及推薦閱讀:

  1. 《中文信息處理發(fā)展報(bào)告(2016)》
  2. ChatterBot 文檔
  3. ChatterBot 的 GitHub
  4. Sutskever, Vinyals and Le (2014)
  5. 漫談四種神經(jīng)網(wǎng)絡(luò)序列解碼模型
  6. 怎樣導(dǎo)出 QQ 群里的所有聊天記錄抒钱?
  7. Keras 中的 LSTM_Seq2Seq 例子
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜓肆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谋币,更是在濱河造成了極大的恐慌仗扬,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕾额,死亡現(xiàn)場離奇詭異早芭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诅蝶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門退个,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人调炬,你說我怎么就攤上這事语盈。” “怎么了缰泡?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵刀荒,是天一觀的道長。 經(jīng)常有香客問我棘钞,道長缠借,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任宜猜,我火速辦了婚禮泼返,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宝恶。我一直安慰自己符隙,他們只是感情好趴捅,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著霹疫,像睡著了一般拱绑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丽蝎,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天猎拨,我揣著相機(jī)與錄音,去河邊找鬼屠阻。 笑死红省,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的国觉。 我是一名探鬼主播吧恃,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼麻诀!你這毒婦竟也來了痕寓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蝇闭,失蹤者是張志新(化名)和其女友劉穎呻率,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呻引,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡礼仗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逻悠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片元践。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖童谒,靈堂內(nèi)的尸體忽然破棺而出卢厂,到底是詐尸還是另有隱情,我是刑警寧澤惠啄,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布慎恒,位于F島的核電站,受9級特大地震影響撵渡,放射性物質(zhì)發(fā)生泄漏融柬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一趋距、第九天 我趴在偏房一處隱蔽的房頂上張望粒氧。 院中可真熱鬧,春花似錦节腐、人聲如沸外盯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饱苟。三九已至孩擂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箱熬,已是汗流浹背类垦。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留城须,地道東北人蚤认。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像糕伐,于是被迫代替她去往敵國和親砰琢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

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