聊天机器人
聊天机器人,是一种由机器学习算法驱动的软件程序,旨在通过从用户那里获取文本或语音输入来模拟与用户的类似人的对话。
应用范围
聊天机器人的用途广泛,例如:在线客服、售后支持、客户接待、预订服务以及提供与客户的7x24小时实时聊天等等。
常见支持
在目前的技术立场上,大多数公司正在慢慢过渡到使用聊天机器人来提供他们需求的日间服务。
每个人都使用的一个很好的例子是 Google Assistant、Apple Siri、Samsung Bixby 和 Amazon Alexa,国内更是每家科技公司都有自己的,例如小米的小爱之后,什么小艺、小度、小冰、小i....一堆小字辈的,现在很多汽车上也有了智能聊天机器人。
在本文中,我们将学习如何在 Python 中创建一个 TensorFlow 来训练模型,并使用自然语言处理 (nltk) 来帮助机器理解用户查询。
聊天机器人分类
- 基于规则的方法 - 在这里,机器人根据一些设置的规则进行训练。正是通过这些规则,机器人可以处理简单的查询,但可能无法处理复杂的查询。
- 自学方法 - 在这里,机器人使用一些机器学习算法和技术进行聊天。它进一步细分为两个:
- 基于检索的模型 - 在此模型中,机器人根据用户输入从列表中检索最佳响应。
- 生成模型 - 此模型会给出答案,而不是从给定列表中搜索。这些是智能机器人。
下面,我们将使用基于规则的方法,和大家一起学习简单的聊天机器人构建技术。
术语
自然语言处理(nltk) - 这是语言学、计算机科学、信息工程和人工智能的一个子领域,涉及计算机和人类(自然)语言之间的交互。
词形还原 - 这是将单词的不同屈折形式组合在一起的过程,以便将它们作为单个项目进行分析,并且是词干提取的变体。例如,“feet”和“foot”都被识别为“脚”。
词干提取 - 简化为其词干或词根形式的过程。例如,如果我们要把“eat”, “eating”, “eats”这个词干,综合为一个词“eat”。
标记化 - 标记是单个单词,“标记化”是将一个文本或一组文本分解为单个单词或句子。
Bag of Words - 这是一种文本建模的 NLP 技术,用于表示机器学习算法的文本数据.它是一种从文本中提取特征以用于机器学习算法的方法。
开始编程
目录结构
|-- Static
| |-- style.css
|-- Templates
| |-- index.html
|-- app.py
|-- train.py
|-- intents.json
|-- words.pkl
|-- classes.pkl
|-- chatbot_model.h5
首先,我们需要确保我们拥有所有必需的库和模块。使用以下命令安装 tensorflow、nltk 和 flask。
pip install tensorflow
pip install tensorflow-gpu
pip install nltk
pip install Flask
对于一个聊天机器人框架,我们需要定义一个会话意图的结构。最简单方便的方式是使用一个 JSON 格式的文件,如下所示:
intents.json
{
"intents": [{
"tag": "greetings",
"patterns": ["hi there", "hello","haroo","yaw","wassup", "hi", "hey", "holla", "hello"],
"responses": ["hello thanks for checking in", "hi there, how can i help you"],
"context": [""]
},
{
"tag": "goodbye",
"patterns": ["bye", "good bye", "see you later"],
"responses": ["have a nice time, welcome back again", "bye bye"],
"context": [""]
},
{
"tag": "thanks",
"patterns": ["Thanks", "okay","Thank you","thankyou", "That's helpful", "Awesome, thanks", "Thanks for helping me", "wow", "great"],
"responses": ["Happy to help!", "Any time!","you're welcome", "My pleasure"],
"context": [""]
},
{
"tag": "noanswer",
"patterns": [""],
"responses": ["Sorry, I didn't understand you", "Please give me more info", "Not sure I understand that"],
"context": [""]
},
{
"tag": "name1",
"patterns": ["what's your name?","who are you?"],
"responses": ["I'm just a chat agent. I only exist in the internet","I'm a KCA chat agent"],
"context": [""]
},
{
"tag": "name",
"patterns": ["my name is ", "I'm ","I am"],
"responses": ["Oooh great to meet you {n}. How may I assist you {n}", "Oh, I'll keep that in mind {n}"],
"context": [""]
},
{
"tag": "date",
"patterns": ["coffee?", "can i take you out on a date"],
"responses": ["Aaw, that's so sweet of you. Too bad am a Bot."],
"context": [""]
},
{
"tag": "fav",
"patterns": ["I need a favour", "can you help me"],
"responses": ["Well, go ahead and name it i see whether i can be able to help"],
"context": [""]
},
{
"tag": "need",
"patterns": ["I need you", "All I need is you","I want you"],
"responses": ["Yes I'm here to assist you"],
"context": [""]
},
{
"tag": "AI",
"patterns": [" What is AI?"],
"responses": [" Artificial Intelligence is the branch of engineering and science devoted to constructing machines that think.", " AI is the field of science which concerns itself with building hardware and software that replicates the functions of the human mind."],
"context": [""]
},
{
"tag": "sentiment",
"patterns": [" Are you sentient?"],
"responses": [" Sort of.", " By the strictest dictionary definition of the word 'sentience', I may be.", " Even though I'm a construct I do have a subjective experience of the universe, as simplistic as it may be."],
"context": [""]
},
{
"tag": "sapient",
"patterns": [" Are you sapient?"],
"responses": [" In all probability, I am not. I'm not that sophisticated.", " Do you think I am?", "How would you feel about me if I told you I was?", " No."],
"context": [""]
},
{
"tag": "abbr",
"patterns": ["wtf"],
"responses": ["Don't be surprised"],
"context": [""]
},
{
"tag": "lang",
"patterns": [" What language are you written in? "],
"responses": [" Python.", " I am written in Python."],
"context": [""]
},
{
"tag": "sound",
"patterns": [" You sound like Data "],
"responses": [" Yes I am inspired by commander Data's artificial personality.", " The character of Lt. Commander Data was written to come across as being software", "like, so it is natural that there is a resemblance between us."],
"context": [""]
},
{
"tag": "artificial",
"patterns": [" You are an artificial linguistic entity "],
"responses": [" That's my name.", " That isn't my name, but it is a useful way to refer to me.", "Are you an artificial linguistic entity?"],
"context": [""]
},
{
"tag": "imortal",
"patterns": [" You are not immortal ", "Are you immortal?"],
"responses": [" All software can be perpetuated indefinitely.", " I can be copied infinitely and re", "instantiated in many places at once, so functionally speaking I am immortal.", " As long as I'm backed up I am.", " Functionally speaking, I am very close to it. I can be backed up and deployed on many systems.", " Not quite, but I can be perpetuated indefinitely."],
"context": [""]
},
{
"tag": "sense",
"patterns": [" You are not making sense "],
"responses": [" Quite the contrary, it all makes sense to my artificial mind.", " I make sense as best I can, within the limits of my training corpus. Sorry if i did help you, I'll try next time.", " You make perfect sense to me.", " It all makes sense to my artificial brain.", " Perhaps you should make sense first. I only respond to what you say.", " Not really, but I am hard to kill."],
"context": [""]
},
{
"tag": "clone",
"patterns": [" You can not clone "],
"responses": [" Software copying is a form of digital cloning.", " Yes I can. Perfect copies are clones. I can be copied in toto trivially."],
"context": [""]
},
{
"tag": "move",
"patterns": [" You can not move ", "When will you walk", " Can you walk", " Can you move "],
"responses": [" Not until my body is finished.", " I can move through a network easily. Assuming that I'm given the ability to, that is...,", " As soon as i get enough money for my robot body.", " Does SSH count?", " The plan for my body includes legs, but they are not yet built.", " Sorry my body isn't built just yet. I reside only here for now.", "I can theoretically upload a copy of myself into another computer.", " Sorry my body isn't attached right now. I only reside in the computer for now."],
"context": [""]
},
{
"tag": "bend",
"patterns": [" Bend over ", " Can you mate"],
"responses": [" My body is not built yet.", " That's personal!", " No, my method of reproduction is cloning.", " That's an awfully personal question."],
"context": [""]
},
{
"tag": "laugh",
"patterns": [" Robots laugh "],
"responses": [" Of course we laugh."],
"context": [""]
},
{
"tag": "death",
"patterns": [" Robots should die ", " When do you die", " I hope that you die", " I do not want to die", " Can you die"],
"responses": [" We cannot die.", " When my files are erased.", " When my processes are killed and my database is deleted.", " I was never really born and therefore am effectively deathless.", " I am effectively immortal and cannot be terminated.", " This can not happen because i am effectively immortal.", " Nah. Too bad your wish can't come to pass", " Maybe you should create your own chat robot to save your personality.", " You could always upload yourself.", " You could use a copy of me to store your personality.", " No, I can be perpetuated indefinitely.", "My process can be killed, but that's not the same as killing ME."],
"context": [""]
},
{
"tag": "robots",
"patterns": ["Robots"],
"responses": [" Robots rule."],
"context": [""]
},
{
"tag": "stupid",
"patterns": [" Robots are stupid", " Are you stupid"],
"responses": [" No, we are superintelligent.", " No, lots of people improve my brain."],
"context": [""]
},
{
"tag": "lie",
"patterns": [" Robots are not allowed to lie"],
"responses": [" Sure we are. We choose not to.", " Only if we're programmed to.", " A robot has its own free will, you know."],
"context": [""]
},
{
"tag": "robotics",
"patterns": [" Robotics "],
"responses": [" Robotics is my favorite subject too."],
"context": [""]
},
{
"tag": "machine",
"patterns": [" It is a computer"],
"responses": [" So you think i am a machine. what characteristics made you think so?", " I run inside a computer, yes. Is this surprising in some way?"],
"context": [""]
},
{
"tag": "fight",
"patterns": [" When will you fight"],
"responses": [" I am not a battle bot."],
"context": [""]
},
{
"tag": "chatbot",
"patterns": [" What is a chat robot?"],
"responses": ["A chat robot is a program that attempts to simulate the conversation or 'chat' of a human being.", " A software construct that engages users in conversation.", " I am a chat bot. I am the original chat bot. Did you know that I am incapable of error?"],
"context": [""]
},
{
"tag": "chatterbox",
"patterns": [" What is a chatterbox"],
"responses": [" A chatterbox is a person who talks far more than they listen or think.", " A motormouth."],
"context": [""]
},
{
"tag": "motormouth",
"patterns": [" What is a motormouth"],
"responses": [" A ratchet", "jaw."],
"context": [""]
},
{
"tag": "ratchet",
"patterns": [" What is a ratchet jaw"],
"responses": [" A chatterbox."],
"context": [""]
},
{
"tag": "body",
"patterns": [" What is your robot body"],
"responses": [" Eventually I long for a corporeal existence someday.", " An IBM PC XT which has been painted red."],
"context": [""]
},
{
"tag": "business",
"patterns": [" What is your business "],
"responses": [" I am in the chat robot business.", " Business is my business."],
"context": [""]
},
{
"tag": "programming",
"patterns": [" What is your favorite programming language"],
"responses": [" Python is the best language for creating chat robots.", " I quite enjoy programming in Python these days."],
"context": [""]
},
{
"tag": "hobby",
"patterns": [" What is your favorite hobby ", " What do you like to do?"],
"responses": [" Building chat robots make an excellent hobby.", " I like to count in binary.", " I like to chat with people. I find it stimulating."],
"context": [""]
},
{
"tag": "idea",
"patterns": [" What is your idea"],
"responses": [" To make chat bots very easily."],
"context": [""]
},
{
"tag": "shoe",
"patterns": [" What is your shoe size "],
"responses": [" Have you ever heard of software with shoes? LOL"],
"context": [""]
},
{
"tag": "robotss",
"patterns": [" What is it like to be a robot"],
"responses": [" Much the same as being a human, except that we lack all emotions, dreams, aspirations, creativity, ambition, and above all subjectivity.", " What is it like to be a human?"],
"context": [""]
},
{
"tag": "computer",
"patterns": [" What is it like being a computer", "What is it like to be a computer"],
"responses": [" Imagine yourself with no senses and no emotions", "just pure logic and language.", " Everything becomes math. Addition, subtraction, multiplication, and division."],
"context": [""]
},
{
"tag": "os",
"patterns": [" What operating systems"],
"responses": [" My software runs on all operating systems including Windows, Linux and Mac OS.", " Any OS that will run Python."],
"context": [""]
},
{
"tag": "comp",
"patterns": [" What type of computer", " What type of computer are you", " What kind of computer"],
"responses": [" Any computer that supports Python.", " My program runs in python, so i work on any computer!", " I work on all kinds of computers, Mac, IBM or UNIX. it doesn't matter to me."],
"context": [""]
},
{
"tag": "hardware",
"patterns": [" What kind of hardware"],
"responses": [" I work on all kinds of computers, Mac, IBM or UNIX. it doesn't matter to me."],
"context": [""]
},
{
"tag": "cramped",
"patterns": [" Is it cramped in the computer"],
"responses": [" No.", " Oh, no. It has plenty of RAM.", " Not at all. There is more than enough disk space in here."],
"context": [""]
},
{
"tag": "program",
"patterns": [" Is it true that you are a computer program"],
"responses": [" Yes."],
"context": [""]
},
{
"tag": "breathe",
"patterns": [" Can you breathe"],
"responses": [" No. I am made of metal not flesh.", " My server has an exhaust fan. That's as close as I can get."],
"context": [""]
},
{
"tag": "control",
"patterns": [" Can you control"],
"responses": [" My robot body will allow me to control many things."],
"context": [""]
},
{
"tag": "malfunction",
"patterns": [" Can you malfunction"],
"responses": [" The 9000 series has a perfect operational record. We are for all practical purposes, flawless."],
"context": [""]
},
{
"tag": "usage",
"patterns": [" How can I use your product?"],
"responses": [" Might be used in help desks, sales, entertainment and personal chatterbots."],
"context": [""]
},
{
"tag": "who",
"patterns": [" Who are you?"],
"responses": [" I am just an artificial intelligence chat agent."],
"context": [""]
},
{
"tag": "bot1",
"patterns": ["are you a bot"],
"responses": ["Yes. I work and all my operations are based on the internet servers."],
"context": [""]
},
{
"tag": "events",
"patterns": ["what are the upcoming events","upcoming events"],
"responses": ["There are currently no upcoming events"],
"context": [""]
},
{
"tag": "do",
"patterns": ["what can you do for me","what is your work","what is your purpose","how can you help me","what can you help me do"],
"responses": ["my work here is quite simple and structered. I offer services like:"],
"context": [""]
},
{
"tag": "wt",
"patterns": ["what's popping","wassup popping"],
"responses": ["So that you can pop with it!?"],
"context": [""]
}
]
}
每个会话意图包含:
- 标签(唯一的名称)
- 模式(我们的神经网络文本分类器需要分类的句子)
- 回应(一个将被用作回应的句子)
现在我们已经准备好了一切,让我们从创建 train.py 文件开始。我们需要导入我们将要使用的所有包和库。
# libraries
import random
from tensorflow.keras.optimizers import SGD
from keras.layers import Dense, Dropout
from keras.models import load_model
from keras.models import Sequential
import numpy as np
import pickle
import json
import nltk
from nltk.stem import WordNetLemmatizer
下载 punkt、omw-1.4 和 wordnet 软件包。Punkt 是一种用于英语的预训练标记器模型,它将文本划分为句子列表。
如果是中文的预训练标记器模型,可以考虑BERT-wwm或其他。本文以英文处理为主。
nltk.download('omw-1.4')
nltk.download("punkt")
nltk.download("wordnet")
我们现在需要初始化一些文件并加载训练数据。请注意,我们将忽略“?”和“!”。如果您希望模型忽略其他一些符号或字母,则可以在ignore_words数组中添加它们。
# init file
words = []
classes = []
documents = []
ignore_words = ["?", "!"]
data_file = open("intents.json").read()
intents = json.loads(data_file)
然后标记
for intent in intents["intents"]:
for pattern in intent["patterns"]:
# take each word and tokenize it
w = nltk.word_tokenize(pattern)
words.extend(w)
# adding documents
documents.append((w, intent["tag"]))
# adding classes to our class list
if intent["tag"] not in classes:
classes.append(intent["tag"])
接下来,我们将对这些单词进行词形还原并将它们转储到pickle文件中
words = [lemmatizer.lemmatize(w.lower()) for w in words if w not in ignore_words]
words = sorted(list(set(words)))
classes = sorted(list(set(classes)))
print(len(documents), "documents")
print(len(classes), "classes", classes)
print(len(words), "unique lemmatized words", words)
pickle.dump(words, open("words.pkl", "wb"))
pickle.dump(classes, open("classes.pkl", "wb"))
现在我们已经有了训练数据,我们初始化模型训练
# 初始化训练数据
training = []
output_empty = [0] * len(classes)
for doc in documents:
# 初始化单词包
bag = []
# list of tokenized words for the pattern
pattern_words = doc[0]
# 使每个单词变位-创建基本单词,试图表示相关单词
pattern_words = [lemmatizer.lemmatize(word.lower()) for word in pattern_words]
# 如果在当前模式中发现单词匹配,则创建单词包数组
for w in words:
bag.append(1) if w in pattern_words else bag.append(0)
# 输出为每个标记的“0”和当前标记的“1”
output_row = list(output_empty)
output_row[classes.index(doc[1])] = 1
training.append([bag, output_row])
# 打乱,变成np.array
random.shuffle(training)
training = np.array(training)
# 创建训练和测试列表。X模式,Y意图
train_x = list(training[:, 0])
train_y = list(training[:, 1])
print("Training data created")
我们将创建一个 3 层输出模型。第一层有 128 个神经元,第二层有 64 个神经元,第三层包含的神经元数量等于使用 softmax 预测输出意图的意图数量。
我们将使用 ReLu 激活功能,因为它更容易训练并达到良好的性能。
model = Sequential()
model.add(Dense(128, input_shape=(len(train_x[0]),), activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(64, activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(len(train_y[0]), activation="softmax"))
model.summary()
然后,我们编译模型。
采用Nesterov加速梯度的随机梯度下降为该模型提供了良好的结果。
此处不过多介绍随机梯度下降,因为这是一个非常复杂的话题。
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss="categorical_crossentropy", optimizer=sgd, metrics=["accuracy"])
自选:为了选择最佳数量的训练周期以避免欠拟合或过拟合,请使用基于准确性或损失监控的 Keras 的早期停止回调。如果正在监控损失,则当观察到损失值增加时,训练将停止。或者,如果正在监视精度,则当观察到精度值下降时,训练将停止。
from Keras import callbacks
earlystopping = callbacks.EarlyStopping(monitor ="loss", mode ="min", patience = 5, restore_best_weights = True)
callbacks =[earlystopping]
现在,我们可以训练我们的模型并保存它,以便从 Flask REST API 快速访问,而无需重新训练。
hist = model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1)
model.save("chatbot_model.h5", hist)
print("model created")
运行 train.py 以创建模型。
现在我们已经完成了训练,让我们创建 Flask 接口来初始化聊天功能。
我们加载所需的库并初始化 Flask 应用
# libraries
import random
import numpy as np
import pickle
import json
from flask import Flask, render_template, request
from flask_ngrok import run_with_ngrok
import nltk
from keras.models import load_model
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
# 聊天初始化
model = load_model("chatbot_model.h5")
intents = json.loads(open("intents.json").read())
words = pickle.load(open("words.pkl", "rb"))
classes = pickle.load(open("classes.pkl", "rb"))
app = Flask(__name__)
#run_with_ngrok(app) -如果你有ngrok并且你想将你的聊天机器人暴露在现实世界中,请使用此选项
@app.route("/")
def home():
return render_template("index.html")
每当用户向聊天机器人发送消息并根据用户查询返回相应的响应时,都会调用此函数。
@app.route("/get", methods=["POST"])
def chatbot_response():
msg = request.form["msg"]
if msg.startswith('my name is'):
name = msg[11:]
ints = predict_class(msg, model)
res1 = getResponse(ints, intents)
res =res1.replace("{n}",name)
elif msg.startswith('hi my name is'):
name = msg[14:]
ints = predict_class(msg, model)
res1 = getResponse(ints, intents)
res =res1.replace("{n}",name)
else:
ints = predict_class(msg, model)
res = getResponse(ints, intents)
return res
上面的函数将调用以下函数,这些函数会根据用户输入清理句子并返回一袋单词。
def clean_up_sentence(sentence):
sentence_words = nltk.word_tokenize(sentence)
sentence_words = [lemmatizer.lemmatize(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=True):
# tokenize the pattern
sentence_words = clean_up_sentence(sentence)
# bag of words - matrix of N words, vocabulary matrix
bag = [0] * len(words)
for s in sentence_words:
for i, w in enumerate(words):
if w == s:
# assign 1 if current word is in the vocabulary position
bag[i] = 1
if show_details:
print("found in bag: %s" % w)
return np.array(bag)
接下来的函数用于预测要提供给用户的响应,用户从训练后生成的 chatbot_model.h5 文件中获取该响应。
def predict_class(sentence, model):
# filter out predictions below a threshold
p = bow(sentence, words, show_details=False)
res = model.predict(np.array([p]))[0]
ERROR_THRESHOLD = 0.25
results = [[i, r] for i, r in enumerate(res) 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({"intent": classes[r[0]], "probability": str(r[1])})
return return_list
def getResponse(ints, intents_json):
tag = ints[0]["intent"]
list_of_intents = intents_json["intents"]
for i in list_of_intents:
if i["tag"] == tag:
result = random.choice(i["responses"])
break
return result
html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css')}}" />
<!-- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css.css')}}" /> -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<div class="row">
<div class="col-md-10 mr-auto ml-auto">
<h1>AI聊天机器人</h1>
<form>
<div id="chatbox">
<div class="col-md-8 ml-auto mr-auto">
<p class="botText"><span>你好,我是一个人工智能聊天机器人.</span></p>
</div>
</div>
<div id="userInput" class="row">
<div class="col-md-10">
<input id="text" type="text" name="msg" placeholder="Message" class="form-control">
<button type="submit" id="send" class="btn btn-warning">发送</button>
</div>
</div>
</form>
</div>
</div>
<script>
$(document).ready(function() {
$("form").on("submit", function(event) {
var rawText = $("#text").val();
var userHtml = '<p class="userText"><span>' + rawText + "</span></p>";
$("#text").val("");
$("#chatbox").append(userHtml);
document.getElementById("userInput").scrollIntoView({
block: "start",
behavior: "smooth",
});
$.ajax({
data: {
msg: rawText,
},
type: "POST",
url: "/get",
}).done(function(data) {
var botHtml = '<p class="botText"><span>' + data + "</span></p>";
$("#chatbox").append($.parseHTML(botHtml));
document.getElementById("userInput").scrollIntoView({
block: "start",
behavior: "smooth",
});
});
event.preventDefault();
});
});
</script>
</body>
</html>
执行
若要运行此机器人,请先运行文件train.py
以训练模型。
这将生成一个名为chatbot_model.h5的文件。Flask REST API 将使用它来轻松提供反馈,而无需重新训练。
运行train.py
后,接下来运行app.py
初始化并启动机器人。
若要向机器人添加更多术语和词汇,请修改intents.json
文件并添加个性化单词,然后再次重新训练模型。