文档章节

利用 TensorFlow 实现上下文的 Chat-bots

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:26
字数 3966
阅读 6
收藏 1
点赞 0
评论 0

在我们的日常聊天中,情景才是最重要的。我们将使用 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 sentence
        w = nltk.word_tokenize(pattern)
        # add to our words list
        words.extend(w)
        # add to documents in our corpus
        documents.append((w, intent['tag']))
        # add to our classes list
        if 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 words
    bag = []
    # list of tokenized words for the pattern
    pattern_words = doc[0]
    # stem each word
    pattern_words = [stemmer.stem(word.lower()) for word in pattern_words]
    # create our bag of words array
    for 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 tag
    output_row = list(output_empty)
    output_row[classes.index(doc[1])] = 1

    training.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 pattern
    sentence_words = nltk.word_tokenize(sentence)
    # stem each word
    sentence_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 pattern
    sentence_words = clean_up_sentence(sentence)
    # bag of words
    bag = [0]*len(words)  
    for s in sentence_words:
        for i,w in enumerate(words):
            if w == s: 
                bag[i] = 1
                if 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 model
    results = model.predict([bow(sentence, words)])[0]
    # filter out predictions below a threshold
    results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]
    # sort by strength of probability
    results.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 probability
    return return_list

def response(sentence, userID='123', show_details=False):
    results = classify(sentence)
    # if we have a classification then find the matching intent tag
    if results:
        # loop as long as there are matches to process
        while results:
            for i in intents['intents']:
                # find a tag matching the first result
                if i['tag'] == results[0][0]:
                    # a random response from the intent
                    return 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 AMEX

response('what kind of mopeds do you rent?')
We rent Yamaha, Piaggio and Vespa mopeds

response('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 model
    results = model.predict([bow(sentence, words)])[0]
    # filter out predictions below a threshold
    results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]
    # sort by strength of probability
    results.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 probability
    return return_list

def response(sentence, userID='123', show_details=False):
    results = classify(sentence)
    # if we have a classification then find the matching intent tag
    if results:
        # loop as long as there are matches to process
        while results:
            for i in intents['intents']:
                # find a tag matching the first result
                if i['tag'] == results[0][0]:
                    # set context for this intent if necessary
                    if '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 conversation
                    if 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 intent
                        return 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 necessary
                    if '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 conversation
                    if 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 intent
                        return 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-9pm

classify('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

© 著作权归作者所有

共有 人打赏支持
AllenOR灵感
粉丝 10
博文 2139
码字总数 82983
作品 0
程序员
tensorflow入门---第三章

tensorflow程序分为两个阶段: 第一个阶段:定义计算图所有的计算 第二个阶段:执行计算 第一节:计算模型—–计算图 第二节:数据模型—–张量 第三节:运行模型—–会话 第一节:计算图 计...

cttacm ⋅ 05/05 ⋅ 0

斯坦福tensorflow教程(一) tensorflow概述

课程链接:https://web.stanford.edu/class/cs20si/syllabus.html Tensorflow简介 中文官网 为什么选择tensorflow Python 接口 便捷性/灵活性:可以将计算模型部署到一个或多个桌面、服务器、...

致Great ⋅ 05/08 ⋅ 0

TensorFlow实践——Multilayer Perceptron

本文是在Softmax Regression的基础上增加了一个隐含层,实现了Multilayer Perceptron的一个模型,Multilayer Perceptron是深度学习模型的基础,对于Softmax Regression的TensorFlow实现,可以...

google19890102 ⋅ 04/26 ⋅ 0

在阿里云Kubernetes容器服务上打造TensorFlow实验室

简介 TensorFLow是深度学习和机器学习最流行的开源框架,它最初是由Google研究团队开发的并致力于解决深度神经网络的机器学习研究,从2015年开源到现在得到了广泛的应用。特别是Tensorboard...

必嘫 ⋅ 04/20 ⋅ 0

Kubeflow实战系列: 利用TFJob运行分布式TensorFlow

介绍 本系列将介绍如何在阿里云容器服务上运行Kubeflow, 本文介绍如何使用运行分布式模型训练。 第一篇:阿里云上使用JupyterHub 第二篇:阿里云上小试TFJob 第三篇:利用TFJob运行分布式Ten...

Mr_zebra ⋅ 06/14 ⋅ 0

13- 深度学习之神经网络核心原理与算法-TensorFlow介绍与框架挑选

TensorFlow以及TensorFlow的应用 支持深度学习的框架。torch caffe TensorFlow 简介 使用图(Graph)来表示计算任务 图中的节点被称为op(operation) 一个op获取0个或多个tensor,执行计算,产生...

天涯明月笙 ⋅ 06/01 ⋅ 0

史上最全TensorFlow学习资源汇总

来源 悦动智能(公众号ID:aibbtcom) 本篇文章将为大家总结TensorFlow纯干货学习资源,非常适合新手学习,建议大家收藏。 ▌一 、TensorFlow教程资源 1)适合初学者的TensorFlow教程和代码示...

悦动智能 ⋅ 04/12 ⋅ 0

【干货】史上最全的Tensorflow学习资源汇总,速藏!

一 、Tensorflow教程资源: 1)适合初学者的Tensorflow教程和代码示例:(https://github.com/aymericdamien/TensorFlow-Examples)该教程不光提供了一些经典的数据集,更是从实现最简单的“Hel...

技术小能手 ⋅ 04/16 ⋅ 0

TensorFlow应用实战-18-Policy Gradient算法

Policy Gradient算法 policy Gradient算法不止一种。 有兴趣的话: 深度增强学习之Policy Gradient方法1 https://zhuanlan.zhihu.com/p/21725498 A3c实现3d赛车游戏: 成果展示 numworkders是 ...

天涯明月笙 ⋅ 06/15 ⋅ 0

开发者注意啦,谷歌宣布开源 Swift for TensorFlow

雷锋网(公众号:雷锋网) AI 研习社按,今年三月,谷歌在 TensorFlow 开发者峰会上公开演示了 Swift for TensorFlow,近日,TensorFlow 官网宣布 Swift for TensorFlow 已在 GitHub 上开源,地...

思颖 ⋅ 04/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

RabbitMQ学习以及与Spring的集成(三)

本文介绍RabbitMQ与Spring的简单集成以及消息的发送和接收。 在RabbitMQ的Spring配置文件中,首先需要增加命名空间。 xmlns:rabbit="http://www.springframework.org/schema/rabbit" 其次是模...

onedotdot ⋅ 19分钟前 ⋅ 0

JAVA实现仿微信红包分配规则

最近过年发红包拜年成为一种新的潮流,作为程序猿对算法的好奇远远要大于对红包的好奇,这里介绍一种自己想到的一种随机红包分配策略,还请大家多多指教。 算法介绍 一、红包金额限制 对于微...

楠木楠 ⋅ 31分钟前 ⋅ 0

Python 数电表格格式化 xlutils xlwt xlrd的使用

需要安装 xlutils xlwt xlrd 格式化前 格式化后 代码 先copy读取的表格,然后按照一定的规则修改,将昵称中的学号提取出来替换昵称即可 from xlrd import open_workbookfrom xlutils.copy ...

阿豪boy ⋅ 今天 ⋅ 0

面试题:使用rand5()生成rand7()

前言 读研究生这3 年,思维与本科相比变化挺大的,这几年除了看论文、设计方案,更重要的是学会注重先思考、再实现,感觉更加成熟吧,不再像个小P孩,人年轻时总会心高气傲。有1 道面试题:给...

初雪之音 ⋅ 今天 ⋅ 0

Docker Toolbox Looks like something went wrong

Docker Toolbox 重新安装后提示错误:Looks like something went wrong in step ´Checking if machine default exists´ 控制面板-->程序与应用-->启用或关闭windows功能:找到Hyper-V,如果处......

随你疯 ⋅ 今天 ⋅ 0

Guacamole 远程桌面

本文将Apache的guacamole服务的部署和应用,http://guacamole.apache.org/doc/gug/ 该链接下有全部相关知识的英文文档,如果水平ok,可以去这里仔细查看。 一、简介 Apache Guacamole 是无客...

千里明月 ⋅ 今天 ⋅ 0

nagios 安装

Nagios简介:监控网络并排除网络故障的工具:nagios,Ntop,OpenVAS,OCS,OSSIM等开源监控工具。 可以实现对网络上的服务器进行全面的监控,包括服务(apache、mysql、ntp、ftp、disk、qmail和h...

寰宇01 ⋅ 今天 ⋅ 0

AngularDart注意事项

默认情况下创建Dart项目应出现以下列表: 有时会因为不知明的原因导致列表项缺失: 此时可以通过以下步骤解决: 1.创建项目涉及到的包:stagehand 2.执行pub global activate stagehand或pub...

scooplol ⋅ 今天 ⋅ 0

Java Web如何操作Cookie的添加修改和删除

创建Cookie对象 Cookie cookie = new Cookie("id", "1"); 修改Cookie值 cookie.setValue("2"); 设置Cookie有效期和删除Cookie cookie.setMaxAge(24*60*60); // Cookie有效时间 co......

二营长意大利炮 ⋅ 今天 ⋅ 0

【每天一个JQuery特效】淡入淡出显示或隐藏窗口

我是JQuery新手爱好者,有时间就练练代码,防止手生,争取每天一个JQuery练习,在这个博客记录下学习的笔记。 本特效主要采用fadeIn()和fadeOut()方法显示淡入淡出的显示效果显示或隐藏元...

Rhymo-Wu ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部