作者:禅与计算机程序设计艺术
1.简介
为了能够在Facebook Messenger上与用户进行实时的沟通,开发者需要自行构建聊天机器人。然而,构建聊天机器人的过程却很复杂,需要掌握众多的技术、技能和知识。本文通过详细地阐述了如何用Python语言构建一个Facebook Messenger聊天机器人,并分享一些创建聊天机器人的过程中可能遇到的问题,希望可以帮助读者更好地理解和应用聊天机器人技术。
2.技术选型及环境准备
Python编程语言
首先,需要安装Python编程语言。如果您还没有安装过Python,可以从https://www.python.org/downloads/下载安装包安装。本文使用Python 3版本。
技术栈
这里我们主要基于以下两个技术栈进行讨论:
- Natural Language Processing(NLP)
- Dialogflow API
Natural Language Processing(NLP)
自然语言处理(NLP)是指计算机从文本或语音中提取出有意义的信息的能力,也是信息搜索、分析和决策的基础。其涉及的技术领域包括分词、词性标注、命名实体识别、句法分析等。相关的工具和库包括 NLTK、SpaCy、TextBlob等。
Dialogflow API
Dialogflow是Google推出的开源对话系统,它可以通过API调用的方式跟业务人员进行聊天。其中,Dialogflow提供了一个用于构建聊天机器人的界面,方便非技术人员创建自己的对话模型,实现自动回复功能。
安装依赖库
本文中,我们将使用以下三个Python库来构建我们的聊天机器人:
-
pip install nltk
:NLTK是一个强大的自然语言处理库。 -
pip install spacy
:SpaCy是一个用于处理文本的高效NLP库。 -
pip install dialogflow_fulfillment
:dialogflow_fulfillment模块可以轻松地创建Dialogflow聊天机器人的Fullfilment响应函数,它会根据请求的内容进行自动回复。
构建环境
除了安装Python编程语言和依赖库外,还需创建一个虚拟环境。以下命令可创建一个名为“chatbot”的虚拟环境:
python -m venv chatbot
激活虚拟环境后,即可安装所需的第三方库:
cd chatbot
source bin/activate
pip install nltk spacy dialogflow_fulfillment
示例数据集
为了方便了解我们要建模的对话场景,我们可以使用一个开源的数据集——Stanford Question Answering Dataset(SQuAD)。该数据集包含超过50,000个提问-回答对,其中每个问题都与一段对应的答案相对应。SQuAD数据集适合于训练和测试聊天机器人的性能,可以让我们更加了解这个领域。
下载数据集后,解压到当前目录下:
wget https://rajpurkar.github.io/SQuAD-explorer/dataset/train-v1.1.json
wget https://rajpurkar.github.io/SQuAD-explorer/dataset/dev-v1.1.json
3.数据预处理
数据概览
首先,加载并检查数据集中的第一个样本:
import json
with open('train-v1.1.json', 'r') as f:
data = json.load(f)['data']
sample = data[0]
print(sample['title']) # print the title of the first sample
print(len(sample['paragraphs'])) # number of paragraphs in this sample
输出结果如下:
"Super_Bowl_50:_The_Final_Four_(2018)"
17
由数据集名称和第一个样本的标题可以看出,这是一份关于超级碗比赛的问答数据集。数据集中共含有17个不同的段落(paragraph),每一段包含若干个QA对。
paragraph = sample['paragraphs'][0]
print(paragraph['context']) # the context for this paragraph
for qa in paragraph['qas']:
question = qa['question']
answer_text = qa['answers'][0]['text']
print("Q:", question)
print("A:", answer_text)
print()
输出结果如下:
In Super Bowl 50, France defeated United States and became the new champions with a score of 78-75. The American team achieved this by holding on to their lead after being beaten during the opening game. However, they were not able to replicate that success when facing off against the Italian side at Wimbledon, where they lost three straight games. With only seven minutes left before the match ended, it is clear that there is still much work to be done for the French side.
Q: When did the American players gain control of the ball?
A: After the two minute violation, it was down to Aaron Ball himself who moved the ball from the hands of <NAME> to his feet.
Q: How many points did the United States lose?
A: Two scores out of five allowed them to hold onto the winning start and keep playing the last minute. They eventually won over the Italians and beat them 75-78.
... more QA pairs...
注意到,每一QA对的前面都有一个“Q:”,后面跟着的问题,然后有一个“A:”,最后才是相应的答案。每个段落的上下文都用一个长字符串表示。
预处理步骤
数据预处理通常包含以下几个步骤:
- 分词:将文档切分成词组。
- 词形还原:将所有词汇转换为标准形式,例如将动词“play”还原为现在时态。
- 词性标注:给每个词赋予一个词性标签,如名词、动词、副词等。
- 命名实体识别:识别出文档中的人名、地名、机构名等专有名词。
- 句法分析:对文本中的词句结构进行分析,分析语法关系和语义。
在本文中,由于NLTK提供了相当全面的分词功能,所以不再重复。但是,由于SpaCy可以进行更多的处理,所以我们使用SpaCy代替NLTK进行预处理。另外,我们将使用spaCy中的命名实体识别器(NER)来识别问题、实体和答案中的专有名词。因此,需要安装并导入以下模块:
import os
from collections import defaultdict
import json
import random
import numpy as np
import tensorflow as tf
import spacy
from spacy.lang.en import English
from spacy.tokenizer import Tokenizer
nlp = English()
ner = nlp.create_pipe("ner")
nlp.add_pipe(ner)
接下来,定义一个函数来读取数据集并进行预处理:
def read_data(filename):
"""Read JSON file and process into list of examples"""
data = []
with open(filename, "r") as f:
dataset = json.load(f)["data"]
for article in dataset:
for paragraph in article["paragraphs"]:
context = paragraph["context"].lower().strip()
doc = nlp(context)
entities = [(ent.text, ent.label_) for ent in doc.ents if len(ent)>1]
qas = paragraph["qas"]
for qa in qas:
id_ = qa["id"]
question = qa["question"].lower().strip()
ans = qa["answers"][0]["text"].lower().strip()
example = {"id": id_,
"context": context,
"entities": entities,
"question": question,
"answer": ans}
data.append(example)
return data
此处,我们先将所有文字转化为小写并删除空白符,然后用spaCy对文档进行解析。我们只保留那些长度超过1的实体(即专有名词),并将它们存储在“entities”列表里。我们还对问题和答案也做同样的处理。
接下来,我们需要定义实体分类器(entity classifier)来区分这些专有名词。我们使用TensorFlow来训练一个简单卷积神经网络(CNN),用来判断输入文本是否包含某个实体类型。我们把它保存在一个JSON文件里,这样就可以直接加载它来进行预测了。
模型构建
接下来,我们将定义一个模型,用来训练和预测。
数据准备
首先,读取数据集并进行预处理:
train_data = read_data('train-v1.1.json')
dev_data = read_data('dev-v1.1.json')
接下来,我们随机打乱数据集:
random.shuffle(train_data)
然后,我们将数据集划分为训练集和验证集:
split_idx = int(len(train_data)*0.9)
train_set = train_data[:split_idx]
dev_set = train_data[split_idx:]
情感分析
在训练之前,我们还需要定义情感分析模型,用来将问题和答案的情感标签标注出来。比如,“你真棒!”和“恭喜你获得奖励”的情感标签应该是正向的,而“你不是好人”和“我很生气”的情感标签应该是负向的。
我们将使用TextBlob库来实现情感分析模型:
from textblob import TextBlob
polarity_tags = {'pos': 1, 'neg': -1, 'neu': 0}
def analyze_sentiment(sentence):
blob = TextBlob(sentence).sentiment.polarity
tag = polarity_tags[blob.classification]
intensity = abs(blob.polsby_popper())
return tag*intensity
对于每个语句,我们通过TextBlob的sentiment
方法计算其情感得分,并且根据得分的分类结果确定它的情感标签。我们还计算了每条语句的Polsby-Popper
指数,该指数衡量了一个句子的复杂程度,用于调整语句的情感得分。
生成器
我们将使用生成器(generator)函数来为训练集生成批次数据。生成器接收训练集作为输入,生成训练样本的一个批量。
def generate_batch(data, batch_size=32):
while True:
batches = [data[i: i+batch_size] for i in range(0, len(data), batch_size)]
for batch in batches:
context_arr = []
entity_arr = []
question_arr = []
answer_arr = []
sentiment_arr = []
for example in batch:
context_arr.append(example['context'])
e_spans = [[span.start, span.end, label] for label, span in zip([ent[1] for ent in example['entities']],
[doc.char_span(*ent[:2]) for doc, ent in zip([nlp(c) for c in example['contexts']],
example['entities'])])]
e_labels = [' '.join([str(l) for l in labels]) for _, _, labels in e_spans]
entity_arr.append([''.join([(t if i==j else '') for j, t in enumerate(e)]) for i, e in enumerate(e_labels)])
question_arr.append(example['question'])
answer_arr.append(example['answer'])
sentiment_arr.append(analyze_sentiment(example['question']))
sentiment_arr.append(analyze_sentiment(example['answer']))
yield ({'context': context_arr,
'entities': entity_arr},
{'answer_output': answer_arr})
生成器返回的是一个元组,包含两个字典:一个包含问题、实体和上下文信息的字典,另一个包含答案的字典。
编码器
接下来,我们将定义编码器(encoder)函数。编码器函数接收训练样本的输入,对它们进行编码,并将它们作为模型的输入。
class Encoder(tf.keras.Model):
def __init__(self, vocab_size, embedding_dim, max_length, num_filters, filter_sizes):
super(Encoder, self).__init__()
self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length)
self.convs = tf.keras.layers.TimeDistributed(tf.keras.layers.Conv2D(num_filters, kernel_size=(filter_size, embedding_dim)))
self.flatten = tf.keras.layers.Flatten()
def call(self, inputs):
x = self.embedding(inputs)
x = tf.expand_dims(x, axis=-1)
x = self.convs(x)
x = self.flatten(x)
return x
编码器采用嵌入层将词汇映射到低维空间,并将每个单词表示成固定大小的时间序列,再用多个卷积核进行特征抽取。最后,我们将每个时间步的特征拼接起来,得到整个文档的整体表示。
实体分类器
实体分类器(entity classifier)函数接受文档表示作为输入,输出每种类型的实体的概率分布。
class EntityClassifier(tf.keras.Model):
def __init__(self, num_classes):
super(EntityClassifier, self).__init__()
self.dense1 = tf.keras.layers.Dense(256, activation='relu')
self.dropout1 = tf.keras.layers.Dropout(0.5)
self.dense2 = tf.keras.layers.Dense(num_classes, activation='softmax')
def call(self, inputs):
x = self.dense1(inputs)
x = self.dropout1(x)
output = self.dense2(x)
return output
实体分类器采用一个两层神经网络,第一层神经元数量为256,第二层神经元数量为实体类别个数,输出每个实体的概率分布。
模型构建
最后,我们将构造整个模型:
class ChatBot(tf.keras.Model):
def __init__(self, encoder, entity_classifier, max_length, max_entities):
super(ChatBot, self).__init__()
self.encoder = encoder
self.entity_classifier = entity_classifier
self.max_length = max_length
self.max_entities = max_entities
@staticmethod
def _pad_sequences(sequence_list, max_length):
sequence_lengths = [len(seq) for seq in sequence_list]
padded_seqs = pad_sequences(sequence_list, padding='post', maxlen=max_length)
return padded_seqs, sequence_lengths
def call(self, inputs):
contexts, entities = inputs
context_padded, context_lengths = self._pad_sequences(contexts, self.max_length)
entity_padded, entity_lengths = self._pad_sequences([[""]] * len(contexts), self.max_entities)
encoded_docs = self.encoder(context_padded)
entity_vectors = tf.reduce_sum(encoded_docs[:, :, None,:] *
self.entity_classifier.embedding(np.array(entities)),
axis=2) / np.sqrt(self.encoder.embedding_dim)
entity_logits = self.entity_classifier(entity_vectors)
outputs = {
"entity_logits": entity_logits,
"context_lengths": context_lengths,
"entity_lengths": entity_lengths
}
return outputs
模型由三部分组成:编码器、实体分类器和ChatBot主体。编码器和实体分类器分别对输入数据进行编码和分类。
ChatBot主体函数的作用是在已编码的文档和实体向量上进行实体分类。它还将原始数据进行padding,保证输入数据具有相同长度。
创建模型对象
最后一步,我们将创建一个模型对象,传入配置参数,并编译它。文章来源:https://www.toymoban.com/news/detail-714887.html
model = ChatBot(encoder=Encoder(vocab_size=len(nlp.vocab)+1, embedding_dim=300, max_length=100,
num_filters=100, filter_sizes=[3]),
entity_classifier=EntityClassifier(num_classes=len(set(entity_types))),
max_length=100,
max_entities=20)
optimizer = tf.keras.optimizers.Adam()
loss_fn = tf.keras.losses.CategoricalCrossentropy()
metrics = {'accuracy'}
model.compile(optimizer=optimizer,
loss={'answer_output': loss_fn},
metrics=metrics)
模型对象是一个Keras模型,包含Encoder、实体分类器和ChatBot主体。我们设定了损失函数和优化器。文章来源地址https://www.toymoban.com/news/detail-714887.html
到了这里,关于对话机器人:使用 Python TensorFlow 训练 Chatbot 聊天机器人的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!