如何在Python和Flask中创建一个人工智能聊天机器人
Dennis Maina
Dennis Maina

Published on 19th November, 2022 (10 months ago) ● Updated on 19th November, 2022

如何在Python和Flask中创建一个人工智能聊天机器人

(9 minutes read)

也许你听说过这个词,并想知道:这个聊天机器人是什么,它的用途是什么,我真的需要一个吗,我怎么才能创建一个?如果你只是想建立自己的简单聊天机器人,本文将带你了解为自己创建一个聊天机器人的所有步骤。让我们直接进入。

预备工作。

    • Python编程知识
    • Flask框架知识
    • 机器学习和算法知识

目录

什么是聊天机器人?

聊天机器人用在哪里?

你需要一个聊天机器人吗?

聊天机器人的分类

使用的术语

开始编程

什么是聊天机器人?

聊天机器人,也被称为人工聊天代理,是一种由机器学习算法驱动的软件程序,旨在通过接受用户的文本或语音输入,模拟与用户进行类似人与人之间的对话。

它在哪里使用?

聊天机器人有广泛的用途,我们无法阐述它可以使用的所有可能性。但基本上,你会在以下方面发现它们。服务台、交易处理、客户支持、预订服务,以及为客户提供全天候的实时聊天。

我需要一个吗?

嗯,这是一个个性化的意见,人们必须做一个成本效益分析,并决定这是否是一个值得的项目。

在目前的技术立场上,大多数公司正在慢慢过渡到使用聊天机器人来提供他们需要的日常服务。大家都在使用的一个好例子是谷歌助理、苹果Siri、三星Bixby和亚马逊Alexa。

在这篇文章中,我们将学习如何在Python中使用TensorFlow来训练模型,并使用自然语言处理(nltk)来帮助机器理解用户查询。

聊天机器人有两大类。

    1. 基于规则的方法 - 在这里,机器人是根据一些设定的规则来训练的。正是根据这些规则,机器人可以处理简单的查询,但不能处理复杂的查询。
    2. 自学方法 - 在这里,机器人使用一些机器学习算法和技术进行聊天。它被进一步细分为两类。
      • 基于检索的模型 - 在这个模型中,机器人根据用户的输入,从一个列表中检索出最佳响应。
      • 生成模型 - 这种模式提出了一个答案,而不是从一个给定的列表中搜索。这些是智能机器人。

在这个聊天机器人中,我们将使用基于规则的方法。

遇到的术语

自然语言处理(nltk) - 这是语言学、计算机科学、信息工程和人工智能的一个子领域,涉及计算机和人类(自然)语言之间的互动。
Lemmatization - 这是一个将一个词的不同转折形式组合在一起的过程,因此它们可以作为一个单独的项目进行分析,是词干的一种变体。例如,"foot "和 "foot "都被识别为 "foot"。
词干化--这是一个将转折词还原为其词干、词基或词根形式的过程。例如,如果我们将单词 "eat"、"eating"、"eats "作为词干,其结果将是单一单词 "eat"。
代号化 - 代号是单独的词,"代号化 "是将一个文本或一组文本分解成单独的词或句子。
词袋--这是一种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

我们的intents.json文件看起来应该是这样的。  这里 很抱歉,这是一个相当长的文件,我不能把它嵌入这里。
现在我们已经准备好了一切,让我们开始创建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是一个预先训练好的英语标记器模型,将文本划分为一个句子列表。

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

接下来,我们将对这些词进行词法处理,并将其倾倒在一个泡菜文件中

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

现在我们有了训练数据,我们初始化模型训练

# initializing training data
training = []
output_empty = [0] * len(classes)
for doc in documents:
    # initializing bag of words
    bag = []
    # list of tokenized words for the pattern
    pattern_words = doc[0]
    # lemmatize each word - create base word, in attempt to represent related words
    pattern_words = [lemmatizer.lemmatize(word.lower()) for word in pattern_words]
    # create our bag of words array with 1, if word match found in current pattern
    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 (for each pattern)
    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. X - patterns, Y - intents
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文件现在应该看起来像这样。

# 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
lemmatizer = WordNetLemmatizer()
nltk.download('omw-1.4')
nltk.download("punkt")
nltk.download("wordnet")


# init file
words = []
classes = []
documents = []
ignore_words = ["?", "!"]
data_file = open("intents.json").read()
intents = json.loads(data_file)

# words
for intent in intents["intents"]:
    for pattern in intent["patterns"]:

        # 对每个词进行符号化处理
        w = nltk.word_tokenize(pattern)
        words.extend(w)
        # adding documents
        documents.append((w, intent["tag"]))

        # 在我们的班级列表中添加班级
        if intent["tag"] not in classes:
            classes.append(intent["tag"])

# lemmatizer
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 = []
    # 该模式的标记化单词列表
    pattern_words = doc[0]
    # 对每个词进行词法处理--创建基础词,以试图代表相关的词。
    pattern_words = [lemmatizer.lemmatize(word.lower()) for word in pattern_words]
    # 如果在当前的模式中找到匹配的词,则创建我们的词袋数组,其中有1个词。
    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")

# actual training
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"])

#可选的片段 - 我对它进行了评论,因为我没有使用它。

# from keras import callbacks 
# earlystopping = callbacks.EarlyStopping(monitor ="loss", mode ="min", patience = 5, restore_best_weights = True)
# callbacks =[earlystopping]

# 拟合和保存模型
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()


# chat initialization
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) -Use this option if you have ngrok and you want to expose your chatbot to the real world

@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

一般来说,我们的app.py应该看起来像这样。

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


# chat initialization
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__)

@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


# 返回词包数组。对于袋中存在于句子中的每个词,0或1。
def bow(sentence, words, show_details=True):
    # 对模式进行标记
    sentence_words = clean_up_sentence(sentence)
    # 词包--N个词的矩阵,词汇矩阵
    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)


def predict_class(sentence, model):
    # 过滤掉低于阈值的预测结果
    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]
    # 按概率强度排序
    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


if __name__ == "__main__":
    app.run()

最后,我们有index.html文件,我就不详细介绍了,因为它是基本的HTML和CSS。

<!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>Dentrice AI ChatBot</h1>
    <form>
        <div id="chatbox">
            <div class="col-md-8 ml-auto mr-auto">
                <p class="botText"><span>Hi! I'm Your bot.</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">Send</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>
 
这就是你的工作聊天机器人。

关于所有文件的完整代码,请访问我的GitHub资源库 这里. 如果你觉得有帮助,别忘了给我打星。

如果你没有足够的计算能力来训练或需要很多时间,也可以在那里访问预训练的模型。

需要一个项目的开发人员吗?请联系我们 DentriceDev Solutions

编码快乐的开发者们。


Comments (0)

Dennis Maina

Dennis Maina

https://dentricedev.com

CEO and Founder of DentriceDev Solutions.

18

Articles

July '22

Joined date

Domain Name Registration & Hosting
Domain Name Registration & Hosting

HostPinnacle Kenya is the best and cheapest web hosting company in Kenya with world-class web hosting packages and affordable web design offers. Apart from that we offer free life-time SSL certificate, affordable domain registration in Kenya and free whois privacy. We have an award-winning support team available 24/7/365 to help you with your queries.

Do you want to write with us? Register and start blogging.

Register Login

Thank you for your support!

We deliver the best web products