利用 TensorFlow 实现上下文的 Chat-bots
在我們的日常聊天中,情景才是最重要的。我們將使用 TensorFlow 構(gòu)建一個聊天機器人框架,并且添加一些上下文處理機制來使得機器人更加智能。
“Whole World in your Hand”?—?Betty Newman-Maguire (http://www.bettynewmanmaguire.ie/)
你是否想過一個問題,為什么那么多的聊天機器人會缺乏會話情景功能?
鑒于上下文在所有的對話場景中的重要性,那么又該如何加入這個特性?
接下來,我們將創(chuàng)建一個聊天機器人的框架,并且以一個島嶼輕便摩托車租賃店為例子,建立一個對話模型。這個小企業(yè)的聊天機器人需要處理一些關(guān)于租賃時間,租賃選項等的簡單問題。我們也希望這個機器人可以處理一些上下文的信息,比如查詢同一天的租賃信息。如果可以解決這個問題,那么我們將節(jié)約很多的時間。
關(guān)于構(gòu)建聊天機器人,我們通過以下三部進行:
在模型中,我們將使用?tflearn?框架,這是一個?TensorFlow?的高層 API,并且我們將使用?IPython?作為開發(fā)工具。
1. 我們會利用 TensorFlow 來編寫對話意圖模型。
完整的 notebook 文檔,可以點擊這里。
對于一個聊天機器人框架,我們需要定義一個會話意圖的結(jié)構(gòu)。最簡單方便的方式是使用一個 JSON 格式的文件,如下所示:
chat-bot intents
每個會話意圖包含:
- 標(biāo)簽(唯一的名稱)
- 模式(我們的神經(jīng)網(wǎng)絡(luò)文本分類器需要分類的句子)
- 回應(yīng)(一個將被用作回應(yīng)的句子)
稍后,我們也會添加一些基本的上下文元素。
首先,我們來導(dǎo)入一些我們需要的包:
import nltk from nltk.stem.lancaster import LancasterStemmer stemmer = LancasterStemmer() import numpy as np import tflearn import tensorflow as tf import random如果你還不了解 TensorFlow,那么可以學(xué)習(xí)一下這個教程或者這個教程。
# import our chat-bot intents file import json with open('intents.json') as json_data:intents = json.load(json_data)代碼中的 JSON 文件可以這里下載,接下來我們可以開始組織代碼的文件,數(shù)據(jù)和分類器。
words = [] classes = [] documents = [] ignore_words = ['?'] # loop through each sentence in our intents patterns for intent in intents['intents']:for pattern in intent['patterns']:# tokenize each word in the sentencew = nltk.word_tokenize(pattern)# add to our words listwords.extend(w)# add to documents in our corpusdocuments.append((w, intent['tag']))# add to our classes listif intent['tag'] not in classes:classes.append(intent['tag'])# stem and lower each word and remove duplicates words = [stemmer.stem(w.lower()) for w in words if w not in ignore_words] words = sorted(list(set(words)))# remove duplicates classes = sorted(list(set(classes)))print (len(documents), "documents") print (len(classes), "classes", classes) print (len(words), "unique stemmed words", words)我們創(chuàng)建了一個文件列表(每個句子),每個句子都是由一些詞干組成,并且每個文檔都屬于一個特定的類別。
27 documents 9 classes ['goodbye', 'greeting', 'hours', 'mopeds', 'opentoday', 'payments', 'rental', 'thanks', 'today'] 44 unique stemmed words ["'d", 'a', 'ar', 'bye', 'can', 'card', 'cash', 'credit', 'day', 'do', 'doe', 'good', 'goodby', 'hav', 'hello', 'help', 'hi', 'hour', 'how', 'i', 'is', 'kind', 'lat', 'lik', 'mastercard', 'mop', 'of', 'on', 'op', 'rent', 'see', 'tak', 'thank', 'that', 'ther', 'thi', 'to', 'today', 'we', 'what', 'when', 'which', 'work', 'you']比如,詞干?tak?將和?take,taking,takers?等匹配。在實際過程中,我們可以刪除一些無用的條目,但在這里已經(jīng)足夠了。
不幸的是,這種數(shù)據(jù)結(jié)構(gòu)不能在 TensorFlow 中使用,我們需要進一步將這個數(shù)據(jù)進行轉(zhuǎn)換:從單詞轉(zhuǎn)換到數(shù)字的張量。
# create our training data training = [] output = [] # create an empty array for our output output_empty = [0] * len(classes)# training set, bag of words for each sentence for doc in documents:# initialize our bag of wordsbag = []# list of tokenized words for the patternpattern_words = doc[0]# stem each wordpattern_words = [stemmer.stem(word.lower()) for word in pattern_words]# create our bag of words arrayfor w in words:bag.append(1) if w in pattern_words else bag.append(0)# output is a '0' for each tag and '1' for current tagoutput_row = list(output_empty)output_row[classes.index(doc[1])] = 1training.append([bag, output_row])# shuffle our features and turn into np.array random.shuffle(training) training = np.array(training)# create train and test lists train_x = list(training[:,0]) train_y = list(training[:,1])請注意,我們的數(shù)據(jù)順序已經(jīng)被打亂了。 TensorFlow 會選取其中的一些數(shù)據(jù)作為測試數(shù)據(jù),用來測試訓(xùn)練的模型的準(zhǔn)確度。
如果我們觀察單個的?x?向量和?y?向量,那么這就是一個詞袋模型,一個表示需要匹配的模式,一個表示匹配的目標(biāo)。
train_x example: [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1] train_y example: [0, 0, 1, 0, 0, 0, 0, 0, 0]接下來,我們來構(gòu)建我們的模型。
# reset underlying graph data tf.reset_default_graph() # Build neural network net = tflearn.input_data(shape=[None, len(train_x[0])]) net = tflearn.fully_connected(net, 8) net = tflearn.fully_connected(net, 8) net = tflearn.fully_connected(net, len(train_y[0]), activation='softmax') net = tflearn.regression(net)# Define model and setup tensorboard model = tflearn.DNN(net, tensorboard_dir='tflearn_logs') # Start training (apply gradient descent algorithm) model.fit(train_x, train_y, n_epoch=1000, batch_size=8, show_metric=True) model.save('model.tflearn')這個模型使用的是 2 層神經(jīng)網(wǎng)絡(luò)模型,跟這篇文章中的是一樣的。
interactive build of a model in tflearn
我們完成了這部分的工作,現(xiàn)在需要保存我們的模型和文檔, 以便在后續(xù)的代碼中可以使用它們。
# save all of our data structures import pickle pickle.dump( {'words':words, 'classes':classes, 'train_x':train_x, 'train_y':train_y}, open( "training_data", "wb" ) )構(gòu)建我們的聊天機器人框架
這部分,完整的代碼在這里。
我們將構(gòu)建一個簡單的狀態(tài)機來處理響應(yīng),并且使用我們的在上一部分中提到的意圖模型來作為我們的分類器。如果你想了解聊天機器人的工作原理,那么可以點擊這里。
我們需要導(dǎo)入和上一部分相同的包,然后?un-pickle?我們的模型和句子,正如我們在上一部分中操作的。請記住,我們的聊天機器人框架與我們的模型是分開構(gòu)建的 —— 除非意圖模式改變了,那么我們需要重新運行我們的模型,否則不需要重構(gòu)模型。如果擁有數(shù)百種意圖和數(shù)千種模式,模型可能需要幾分鐘的時間才能構(gòu)建完成。
# restore all of our data structures import pickle data = pickle.load( open( "training_data", "rb" ) ) words = data['words'] classes = data['classes'] train_x = data['train_x'] train_y = data['train_y']# import our chat-bot intents file import json with open('intents.json') as json_data:intents = json.load(json_data)接下來,我們需要導(dǎo)入剛剛利用 TensorFlow(tflearn 框架)訓(xùn)練好的模型。請注意,你第一步還是需要去定義 TensorFlow 模型結(jié)構(gòu),正如我們在第一部分中做的那樣。
# load our saved model model.load('./model.tflearn')在我們開始處理對話意圖之前,我們需要一種從用戶輸入數(shù)據(jù)生詞詞袋的方法。而這個方法,跟我們前面所使用的方法是相同的。
def clean_up_sentence(sentence):# tokenize the patternsentence_words = nltk.word_tokenize(sentence)# stem each wordsentence_words = [stemmer.stem(word.lower()) for word in sentence_words]return sentence_words# return bag of words array: 0 or 1 for each word in the bag that exists in the sentence def bow(sentence, words, show_details=False):# tokenize the patternsentence_words = clean_up_sentence(sentence)# bag of wordsbag = [0]*len(words) for s in sentence_words:for i,w in enumerate(words):if w == s: bag[i] = 1if show_details:print ("found in bag: %s" % w)return(np.array(bag)) p = bow("is your shop open today?", words) print (p) [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0]現(xiàn)在,我們可以開始構(gòu)建我們的響應(yīng)處理器了。
ERROR_THRESHOLD = 0.25 def classify(sentence):# generate probabilities from the modelresults = model.predict([bow(sentence, words)])[0]# filter out predictions below a thresholdresults = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]# sort by strength of probabilityresults.sort(key=lambda x: x[1], reverse=True)return_list = []for r in results:return_list.append((classes[r[0]], r[1]))# return tuple of intent and probabilityreturn return_listdef response(sentence, userID='123', show_details=False):results = classify(sentence)# if we have a classification then find the matching intent tagif results:# loop as long as there are matches to processwhile results:for i in intents['intents']:# find a tag matching the first resultif i['tag'] == results[0][0]:# a random response from the intentreturn print(random.choice(i['responses']))results.pop(0)傳遞給?response()?的每個句子都會被分類。我們分類器使用?model.predict()?函數(shù)來進行類別預(yù)測,這個方法非常快。模型返回的概率值和我們定義的意圖是一直的,用來生成潛在的響應(yīng)列表。
如果一個或多個分類結(jié)果高于閾值,那么我們會選取出一個與意圖匹配的標(biāo)簽,然后處理。我們將我們的分類列表作為一個堆棧,并從這個堆棧中尋找一個適合的匹配,直到找到一個最好的或者直到堆棧變空。
我們來舉一個例子,模型會返回最有可能的標(biāo)簽和其概率。
classify('is your shop open today?') [('opentoday', 0.9264171123504639)]請注意,“is your shop open today?” 不是這個意圖中的任何模式:“pattern : ["Are you open today?", "When do you open today?", "What are your hours today?"]”。但是,“open” 和 “today” 術(shù)語對我們的模式是非常有用的(他們在選擇意圖時,有決定性的作用)。
我們現(xiàn)在從用戶的輸入數(shù)據(jù)中產(chǎn)生一個結(jié)果:
response('is your shop open today?') Our hours are 9am-9pm every day再來一些例子:
response('do you take cash?') We accept VISA, Mastercard and AMEXresponse('what kind of mopeds do you rent?') We rent Yamaha, Piaggio and Vespa mopedsresponse('Goodbye, see you later') Bye! Come back again soon.接下來讓我們結(jié)合一些基礎(chǔ)的語境來設(shè)計一個聊天機器人,比如拖車租賃聊天機器人。
語境
我們想處理的是一個關(guān)于租賃摩托車的問題,并詢問一些有關(guān)租金的事。對于用戶問題的理解應(yīng)該是非常容易的,語境非常清晰。如果用戶詢問 “today”,那么上下文的租賃信息就是進入時間框架,那么最好你還能指定是哪一個自行車,這樣交流起來就不會浪費時間。
為了實現(xiàn)這一點,我們需要在框架中再加入一個概念 “state” 。這需要一個數(shù)據(jù)結(jié)構(gòu)來維護這個新的概念和原來的意圖。
因為我們需要我們的狀態(tài)機是一個非常容易的維護,恢復(fù)和復(fù)制等等操作,所以我們需要把數(shù)據(jù)都保存在一個諸如字典的數(shù)據(jù)結(jié)構(gòu)中,這是非常重要的。
接下來,我們給出基本語境的回復(fù)過程:
# create a data structure to hold user context context = {}ERROR_THRESHOLD = 0.25 def classify(sentence):# generate probabilities from the modelresults = model.predict([bow(sentence, words)])[0]# filter out predictions below a thresholdresults = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]# sort by strength of probabilityresults.sort(key=lambda x: x[1], reverse=True)return_list = []for r in results:return_list.append((classes[r[0]], r[1]))# return tuple of intent and probabilityreturn return_listdef response(sentence, userID='123', show_details=False):results = classify(sentence)# if we have a classification then find the matching intent tagif results:# loop as long as there are matches to processwhile results:for i in intents['intents']:# find a tag matching the first resultif i['tag'] == results[0][0]:# set context for this intent if necessaryif 'context_set' in i:if show_details: print ('context:', i['context_set'])context[userID] = i['context_set']# check if this intent is contextual and applies to this user's conversationif not 'context_filter' in i or \(userID in context and 'context_filter' in i and i['context_filter'] == context[userID]):if show_details: print ('tag:', i['tag'])# a random response from the intentreturn print(random.choice(i['responses']))results.pop(0)我們的上下文狀態(tài)是一個字典,它將包含每個用戶的狀態(tài)。我將為每個用戶使用一個唯一的標(biāo)識(例如,cell#)。這允許我們的框架和狀態(tài)機同事維護多個用戶的狀態(tài)。
# create a data structure to hold user context context = {}我們在意圖處理流程中,添加了上下文信息,具體如下:
if i['tag'] == results[0][0]:# set context for this intent if necessaryif 'context_set' in i:if show_details: print ('context:', i['context_set'])context[userID] = i['context_set']# check if this intent is contextual and applies to this user's conversationif not 'context_filter' in i or \(userID in context and 'context_filter' in i and i['context_filter'] == context[userID]):if show_details: print ('tag:', i['tag'])# a random response from the intentreturn print(random.choice(i['responses']))如果一個意圖想要設(shè)置上下文信息,那么我們可以這樣做:
{“tag”: “rental”,“patterns”: [“Can we rent a moped?”, “I’d like to rent a moped”, … ],“responses”: [“Are you looking to rent today or later this week?”],“context_set”: “rentalday”}如果另一個意圖想要與上下文進行關(guān)聯(lián),那么可以這樣做:
{“tag”: “today”,“patterns”: [“today”],“responses”: [“For rentals today please call 1–800-MYMOPED”, …], “context_filter”: “rentalday”}以這種方法構(gòu)建的信息庫,如果用戶只是輸入 "today" 而沒有上下文信息,那么這個 “today” 的用戶意圖是不會被處理的。如果用戶輸入的 "today" 是對我們的一個時間回應(yīng),即觸動了意圖標(biāo)簽 "rental" ,那么這個意圖將會被處理。
response('we want to rent a moped') Are you looking to rent today or later this week?response('today') Same-day rentals please call 1-800-MYMOPED我們上下文信息也改變了:
context {'123': 'rentalday'}我們定義我們的 "greeting" 意圖用來清除上下文語境信息,這就像我們打招呼一樣,標(biāo)志著我們要開啟一個新的對話。我們還添加了 "show_details" 參數(shù),用來幫助我們看到程序里面的信息。
response("Hi there!", show_details=True) context: '' tag: greeting Good to see you again讓我們再次嘗試輸入 "今天" 這個詞,一些有趣的事情就發(fā)生了。
response('today') We're open every day from 9am-9pmclassify('today') [('today', 0.5322513580322266), ('opentoday', 0.2611265480518341)]首先,我們對沒有上下文信息的 "today" 的回應(yīng)是不同的。我們的分類產(chǎn)生了 2 個合適的意圖,但 "opentoday" 被選中了。所以這個隨機性就比較大,上下文信息很重要!
response("thanks, your great") Happy to help!現(xiàn)在需要考慮的事情就是如何將對話放置到具體語境中了。
狀態(tài)處理
沒錯,你的機器人將會成為你的私人機器人了,不再是那么大眾化。除非你想要重建狀態(tài),重新加載你的模型和文檔 —— 每次調(diào)用你的機器人框架,你都會需要加載一個模型狀態(tài)。
這不是那么困難,你可以在自己的進程中運行一個有狀態(tài)的聊天機器人框架,并使用 RPC(遠(yuǎn)程過程調(diào)用)或 RMI(遠(yuǎn)程方法調(diào)用)調(diào)用它,我推薦使用?Pyro
用戶界面(客戶端)通常是無狀態(tài)的,例如:HTTP 或 SMS。
你的聊天機器人客戶端將通過 Pyro 函數(shù)進行調(diào)用,你的狀態(tài)服務(wù)將由它處理,是不是很贊。
這里有一個手把手教你如何構(gòu)建一個 Twilio SMS 機器人客戶端的方法,這里是一個構(gòu)建 Facebook 機器人的方法。
不要將狀態(tài)存儲在局部變量中
所有狀態(tài)信息都必須放在諸如字典之類的數(shù)據(jù)結(jié)構(gòu)中,易于持久化,重新加載或者以原子狀態(tài)進行復(fù)制。
每個用戶的對話和上下文語境都會保存在用戶 ID 下面,這個ID必須是唯一的。
我們會復(fù)制有些用戶的對話信息來進行場景分析,如果這些信息被保存在臨時變量中,那么就非常難來處理,這是一個最大的考慮。
所以,現(xiàn)在你已經(jīng)學(xué)會了如何去構(gòu)建一個聊天機器人框架,一個使它能記住上下文信息的機器人,已經(jīng)如何分析文本。未來的聊天機器人也都是能分析上下文語境的,這是一個大趨勢。
我們聯(lián)想到意圖的構(gòu)建會影響上下文的對話反應(yīng),所以我們可以創(chuàng)建各種各樣的會話環(huán)境。
快去動手試試吧!
來源:Medium
總結(jié)
以上是生活随笔為你收集整理的利用 TensorFlow 实现上下文的 Chat-bots的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python运行异常 Original
- 下一篇: 汉语自然语言处理工具包下载