在我们的日常聊天中,情景才是最重要的。我们将使用 TensorFlow 构建一个聊天机器人框架,并且添加一些上下文处理机制来使得机器人更加智能。

“Whole World in your Hand” — Betty Newman-Maguire (http://www.bettynewmanmaguire.ie/)

你是否想过一个问题,为什么那么多的聊天机器人会缺乏会话情景功能?

鉴于上下文在所有的对话场景中的重要性,那么又该如何加入这个特性?

接下来,我们将创建一个聊天机器人的框架,并且以一个岛屿轻便摩托车租赁店为例子,建立一个对话模型。这个小企业的聊天机器人需要处理一些关于租赁时间,租赁选项等的简单问题。我们也希望这个机器人可以处理一些上下文的信息,比如查询同一天的租赁信息。如果可以解决这个问题,那么我们将节约很多的时间。

关于构建聊天机器人,我们通过以下三部进行:

  1. 我们会利用 TensorFlow 来编写对话意图模型。
  2. 接下啦,我们将构建一个处理对话的聊天机器人框架。
  3. 最后,我们将介绍如何将上下文信息合并到我们的响应式处理器中。

在模型中,我们将使用 tflearn 框架,这是一个 TensorFlow 的高层 API,并且我们将使用 IPython 作为开发工具。

1. 我们会利用 TensorFlow 来编写对话意图模型。

完整的 notebook 文档,可以点击这里。

对于一个聊天机器人框架,我们需要定义一个会话意图的结构。最简单方便的方式是使用一个 JSON 格式的文件,如下所示:

chat-bot intents

每个会话意图包含:

  • 标签(唯一的名称)
  • 模式(我们的神经网络文本分类器需要分类的句子)
  • 回应(一个将被用作回应的句子)

稍后,我们也会添加一些基本的上下文元素。

首先,我们来导入一些我们需要的包:

# things we need for NLP
import nltk
from nltk.stem.lancaster import LancasterStemmer
stemmer = LancasterStemmer()# things we need for Tensorflow
import numpy as np
import tflearn
import tensorflow as tf
import random

如果你还不了解 TensorFlow,那么可以学习一下这个教程或者这个教程。

# import our chat-bot intents file
import json
with open('intents.json') as json_data:intents = json.load(json_data)

代码中的 JSON 文件可以这里下载,接下来我们可以开始组织代码的文件,数据和分类器。

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)

我们创建了一个文件列表(每个句子),每个句子都是由一些词干组成,并且每个文档都属于一个特定的类别。

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 将和 taketakingtakers 等匹配。在实际过程中,我们可以删除一些无用的条目,但在这里已经足够了。

不幸的是,这种数据结构不能在 TensorFlow 中使用,我们需要进一步将这个数据进行转换:从单词转换到数字的张量。

# 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])

请注意,我们的数据顺序已经被打乱了。 TensorFlow 会选取其中的一些数据作为测试数据,用来测试训练的模型的准确度。

如果我们观察单个的 x 向量和 y 向量,那么这就是一个词袋模型,一个表示需要匹配的模式,一个表示匹配的目标。

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]

接下来,我们来构建我们的模型。

# 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 层神经网络模型,跟这篇文章中的是一样的。

interactive build of a model in tflearn

我们完成了这部分的工作,现在需要保存我们的模型和文档, 以便在后续的代码中可以使用它们。

# 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" ) )

构建我们的聊天机器人框架

这部分,完整的代码在这里。

我们将构建一个简单的状态机来处理响应,并且使用我们的在上一部分中提到的意图模型来作为我们的分类器。如果你想了解聊天机器人的工作原理,那么可以点击这里。

我们需要导入和上一部分相同的包,然后 un-pickle 我们的模型和句子,正如我们在上一部分中操作的。请记住,我们的聊天机器人框架与我们的模型是分开构建的 —— 除非意图模式改变了,那么我们需要重新运行我们的模型,否则不需要重构模型。如果拥有数百种意图和数千种模式,模型可能需要几分钟的时间才能构建完成。

# 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)

接下来,我们需要导入刚刚利用 TensorFlow(tflearn 框架)训练好的模型。请注意,你第一步还是需要去定义 TensorFlow 模型结构,正如我们在第一部分中做的那样。

# load our saved model
model.load('./model.tflearn')

在我们开始处理对话意图之前,我们需要一种从用户输入数据生词词袋的方法。而这个方法,跟我们前面所使用的方法是相同的。

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]

现在,我们可以开始构建我们的响应处理器了。

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() 函数来进行类别预测,这个方法非常快。模型返回的概率值和我们定义的意图是一直的,用来生成潜在的响应列表。

如果一个或多个分类结果高于阈值,那么我们会选取出一个与意图匹配的标签,然后处理。我们将我们的分类列表作为一个堆栈,并从这个堆栈中寻找一个适合的匹配,直到找到一个最好的或者直到堆栈变空。

我们来举一个例子,模型会返回最有可能的标签和其概率。

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” 术语对我们的模式是非常有用的(他们在选择意图时,有决定性的作用)。

我们现在从用户的输入数据中产生一个结果:

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.

接下来让我们结合一些基础的语境来设计一个聊天机器人,比如拖车租赁聊天机器人。

语境

我们想处理的是一个关于租赁摩托车的问题,并询问一些有关租金的事。对于用户问题的理解应该是非常容易的,语境非常清晰。如果用户询问 “today”,那么上下文的租赁信息就是进入时间框架,那么最好你还能指定是哪一个自行车,这样交流起来就不会浪费时间。

为了实现这一点,我们需要在框架中再加入一个概念 “state” 。这需要一个数据结构来维护这个新的概念和原来的意图。

因为我们需要我们的状态机是一个非常容易的维护,恢复和复制等等操作,所以我们需要把数据都保存在一个诸如字典的数据结构中,这是非常重要的。

接下来,我们给出基本语境的回复过程:

# 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)

我们的上下文状态是一个字典,它将包含每个用户的状态。我将为每个用户使用一个唯一的标识(例如,cell#)。这允许我们的框架和状态机同事维护多个用户的状态。

# 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']))

如果一个意图想要设置上下文信息,那么我们可以这样做:

{“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”}

如果另一个意图想要与上下文进行关联,那么可以这样做:

{“tag”: “today”,“patterns”: [“today”],“responses”: [“For rentals today please call 1–800-MYMOPED”, …],
“context_filter”: “rentalday”}

以这种方法构建的信息库,如果用户只是输入 "today" 而没有上下文信息,那么这个 “today” 的用户意图是不会被处理的。如果用户输入的 "today" 是对我们的一个时间回应,即触动了意图标签 "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" 意图用来清除上下文语境信息,这就像我们打招呼一样,标志着我们要开启一个新的对话。我们还添加了 "show_details" 参数,用来帮助我们看到程序里面的信息。

response("Hi there!", show_details=True)
context: ''
tag: greeting
Good to see you again

让我们再次尝试输入 "今天" 这个词,一些有趣的事情就发生了。

response('today')
We're open every day from 9am-9pmclassify('today')
[('today', 0.5322513580322266), ('opentoday', 0.2611265480518341)]

首先,我们对没有上下文信息的 "today" 的回应是不同的。我们的分类产生了 2 个合适的意图,但 "opentoday" 被选中了。所以这个随机性就比较大,上下文信息很重要!

response("thanks, your great")
Happy to help!

现在需要考虑的事情就是如何将对话放置到具体语境中了。

状态处理

没错,你的机器人将会成为你的私人机器人了,不再是那么大众化。除非你想要重建状态,重新加载你的模型和文档 —— 每次调用你的机器人框架,你都会需要加载一个模型状态。

这不是那么困难,你可以在自己的进程中运行一个有状态的聊天机器人框架,并使用 RPC(远程过程调用)或 RMI(远程方法调用)调用它,我推荐使用 Pyro

用户界面(客户端)通常是无状态的,例如:HTTP 或 SMS。

你的聊天机器人客户端将通过 Pyro 函数进行调用,你的状态服务将由它处理,是不是很赞。

这里有一个手把手教你如何构建一个 Twilio SMS 机器人客户端的方法,这里是一个构建 Facebook 机器人的方法。

不要将状态存储在局部变量中

所有状态信息都必须放在诸如字典之类的数据结构中,易于持久化,重新加载或者以原子状态进行复制。

每个用户的对话和上下文语境都会保存在用户 ID 下面,这个ID必须是唯一的。

我们会复制有些用户的对话信息来进行场景分析,如果这些信息被保存在临时变量中,那么就非常难来处理,这是一个最大的考虑。

所以,现在你已经学会了如何去构建一个聊天机器人框架,一个使它能记住上下文信息的机器人,已经如何分析文本。未来的聊天机器人也都是能分析上下文语境的,这是一个大趋势。

我们联想到意图的构建会影响上下文的对话反应,所以我们可以创建各种各样的会话环境。

快去动手试试吧!


来源:Medium

利用 TensorFlow 实现上下文的 Chat-bots相关推荐

  1. activate tensorflow_“量子固件”来了!利用 TensorFlow 提升量子计算硬件性能

    客座文章 / Michael J. Biercuk.Harry Slatyer 和 Michael Hush,来自Q-CTRL Google 近期宣布推出 TensorFlow Quantum,这是一 ...

  2. 利用Tensorflow进行自然语言处理(NLP)系列之一Word2Vec

    同步笔者CSDN博客(https://blog.csdn.net/qq_37608890/article/details/81513882). 一.概述 本文将要讨论NLP的一个重要话题:Word2V ...

  3. 利用TensorFlow实现多元线性回归

    利用TensorFlow实现多元线性回归,代码如下: # -*- coding:utf-8 -*- import tensorflow as tf import numpy as np from sk ...

  4. 利用TensorFlow和神经网络来处理文本分类问题

    利用TensorFlow和神经网络来处理文本分类问题 By 机器之心2017年8月23日 10:33 在这篇文章中,机器之心海外分析师对Medium(链接见文后)上的一篇热门博客进行了介绍,讨论了六个 ...

  5. python制作训练集_利用Tensorflow简单实现VGGNet,从数据集制作到训练完成测试

    VGGNet_TF 利用Tensorflow简单实现VGGNet,从数据集制作到训练完成测试 参考:<Tensorflow实战><Tensorflow 实战Google深度学习框架& ...

  6. 利用Tensorflow构建RNN并对序列数据进行建模

    利用Tensorflow构建RNN并对序列数据进行建模 对文本处理处理任务的方法中,一般将TF-IDF向量作为特征输入.显然的缺陷是:这种方法丢失了输入的文本序列中每个单词的顺序. 对一般的前馈神经网 ...

  7. 神经网络学习小记录2——利用tensorflow构建循环神经网络(RNN)

    神经网络学习小记录2--利用tensorflow构建循环神经网络(RNN) 学习前言 RNN简介 tensorflow中RNN的相关函数 tf.nn.rnn_cell.BasicLSTMCell tf ...

  8. 深度学习入门|利用Tensorflow复现Yolov1/v2

    深度学习入门|利用Tensorflow复现Yolov1/v2 使用PaddlePaddle解决论文复现问题 首先开始进行环境的配置 步骤1.安装Anaconda 官方网站:www.anaconda.c ...

  9. 利用tensorflow训练自己的图片数据集——数据准备

    昨天实现了一个简单的CNN网络.用了MNIST数据集,虽然看来对这个数据集用的很多,但是真正这个数据集是怎么在训练的时候被调用的,以及怎么把它换成自己的数据集都是一脸懵. 直接附上链接:MNIST数据 ...

最新文章

  1. 一个后台开发工程师的“窦娥冤”!
  2. 汇编语言--div指令
  3. ORA-01180: can not create datafile 1 :解决一例
  4. FatFs源码剖析(1)
  5. [MapReduce_add_4] MapReduce 的 join 操作
  6. 【2017级面向对象程序设计】第2次成绩排行
  7. Python编程从入门到实践~异常
  8. 三问(why?what?how?)金融领域的机器学习
  9. 比较abc大小的java_比较abc大小java
  10. 安装、配置文件-配置 CACTI 监控 MySQL 数据库状态-by小雨
  11. JWT token信息保存
  12. Kafka : kafka无法消费的情况
  13. android+委托列表,在Android适配器中使用委托者模式
  14. 动态延迟加载网页元素jQuery插件scrollLoading
  15. Ubuntu免密码输入关机脚本
  16. 在OBS中解决无法录制chrome的问题
  17. 【HAVENT原创】使用 Spring Boot 的 AOP 全局记录执行时间日志
  18. 安理工计算机专业分数线,安徽理工大学复试录取分数线
  19. Notepad++安装JsonViewer插件
  20. ASEMI代理AD633JRZ原装ADI车规级AD633JRZ

热门文章

  1. spring boot: Bean的初始化和销毁 (一般注入说明(三) AnnotationConfigApplicationContext容器 JSR250注解)...
  2. 批量域更改客户端本地administrator密码
  3. 倪光南:中国网络安全为何要强调“自主可控”?
  4. 数据量很大,分页查询很慢,有什么优化方案?
  5. Spring Job?Quartz?XXL-Job?年轻人才做选择,艿艿全莽~
  6. GitHub上读北大:覆盖AI高数等130多门课,讲义考题答案全都有,标星已3k+
  7. 【手写系列】写出我的第一个框架:迷你版Spring MVC
  8. 吴军:区块链催生了新的生产关系
  9. 对Java Inputstream的一次采访
  10. 框架:springboot组合spring、springmvc、mybatis的一个小demo