美文网首页
聊天机器人框架Chatterbot的使用与魔改(上)

聊天机器人框架Chatterbot的使用与魔改(上)

作者: pamisu | 来源:发表于2021-11-07 21:57 被阅读0次

最近整理代码发现我们对聊天模块用到的Chatterbot框架做了不少修改与扩展,所以简单记录一下瞎改过程。

Chatterbot是一个开源的聊天机器人框架,使用它可以快速构建出一个简单的闲聊型机器人。但它也存在着一定的局限性,比如鸡肋的学习功能、只能回复固定句子等等。如果只是要实现简单的语句匹配并给出对应回复,那么Chatterbot是还算不错的选择;如果想要做出更具有智能的机器人,那么还是得自己建立模型。

初识

基本用法可以通过官方文档学习,但还是有一些坑,可能会导致入门即放弃。

先来安装:

pip install chatterbot

尽管用了国内源,安装过程依然十分漫长,过程中如果有报错可以暂时忽略,先去喝杯茶或吃个饭,回来说不定就装好了。

在1-7挖了好几吨土之后,总算是安装好了,用官方的示例代码试试看:

main.py

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer

# 创建你的机器人
chatbot = ChatBot('正义骑士号')

# 训练你的机器人
# 这是训练对话
conversation = [
    "Hello",
    "Hi there!",
    "How are you doing?",
    "I'm doing great.",
    "That is good to hear",
    "Thank you.",
    "You're welcome.",
    "Never gonna give you up",
    "Never gonna let you down"
]
trainer = ListTrainer(chatbot)
trainer.train(conversation)

# 跟你的机器人对话
response = chatbot.get_response('Good morning!')
print(response)

从这段示例中可以看出Chatterbot的基本使用流程:定义机器人->训练->使用。

运行时可能会提示还缺少依赖,我这里提示少了pytz,将它装上就行。不出意外的话会输出训练进度与“Good morning!”的回复:

List Trainer: [####################] 100%
How are you doing?

出意外的话则可能会卡在这种地方,或者完全没有输出:

[nltk_data] Downloading package xxx to /Users/你的用户名/nltk_data...

这是由于用于语言处理的NLTK库正在龟速下载所需要的数据集,这种情况建议直接去github或gitee镜像手动下载,github地址,下载完后根据报错提示放到对应路径下即可,缺什么就放什么。这一步不做也没关系,之后需要对中文分词进行修改,暂时没有用到NLTK。

中文适配

如果现在就兴冲冲地投喂中文语料的话,会发现不仅训练时间长、回答相当耗时并且不准确,简直是鸡同鸭讲。这是因为Chatterbot的训练过程中,需要对语句进行分词、词性标注、转换上位词等处理,并存储为检索文本(search_text),但它默认没有实现对中文的分词,导致数据库中存储的都是完整句子而不是检索文本,这里需要自己改造。

训练后的中英文数据对比:


正确数据 错误数据

粗略阅读训练类ListTrainer的源码可以得知,我们的训练数据在这里被处理,并作为Statement被存储起来:

源码中的trainers.py

ListTrainer

可以确定改造目标就是get_bigram_pair_string方法,这个方法属于PosHypernymTagger类,而包含tagger的self.chatbot.storage对象为StorageAdapter类型。

那么至少需要扩展两个类:负责分词与词性标注的Tagger、负责存储适配的StorageAdapter。新建mybot模块与类对应的py文件:

Tagger

这里直接参考(抄)了github上fg607的代码 ,感谢这位老哥。

使用结巴中文分词进行分词处理,安装:

pip install jieba

创建data文件夹,放入停用词表文件与用户词典文件:

停用词表包含一些价值较低的、对检索没有帮助的词,比如“的”、“上”、“这个”等等,分词处理时会去除这些词;用户词典包含用户自定义的一些词,会被视为一个完整的词处理。

Tagger类:

taggers.py

import codecs
import os
import re
import jieba


class ChineseTagger(object):
    """
    Handling chinese text
    """

    def __init__(self, language=None):
        self.stopword = []
        cfp = codecs.open(os.path.dirname(__file__) + '/data/cn_stopwords.txt', 'r+', 'utf-8')  # 停用词的txt文件
        for line in cfp:
            for word in line.split():
                self.stopword.append(word)
        cfp.close()
        jieba.load_userdict(os.path.dirname(__file__) + '/data/user_dict.txt')

    def get_bigram_pair_string(self, text):
        """
        Return a string of text containing part-of-speech, lemma pairs.
        """
        bigram_pairs = []

        # 利用正则表达式去掉一些一些标点符号之类的符号。
        text = re.sub(r'\s+', ' ', str(text))  # trans 多空格 to空格
        text = re.sub(r'\n+', ' ', str(text))  # trans 换行 to空格
        text = re.sub(r'\t+', ' ', str(text))  # trans Tab to空格
        text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——;!,”。《》,。:“?、~@#¥%……&*()1234567①②③④)]+".\
                          encode().decode("utf8"), "".encode().decode("utf8"), text)

        wordlist = list(jieba.cut(str(text)))  # jieba.cut  把字符串切割成词并添加至一个列表
        for word in wordlist:
            if word not in self.stopword:  # 词语的清洗:去停用词
                if word != '\r\n' and word != ' ' and word != '\u3000'.encode().decode('unicode_escape') \
                        and word != '\xa0'.encode().decode('unicode_escape'):  # 词语的清洗:去全角空格
                    bigram_pairs.append(word)

        return ' '.join(bigram_pairs)

需要注意的是,这里并没有像PosHypernymTagger一样标注词性与转换上位词,不过影响似乎不大(也许)。

StorageAdapter

Tagger写好了,接下来要让它能被训练类ListTrainer引用到,在这二者之间的便是StorageAdapter。Chatterbot提供了设置项,让我们可以扩展自己的存储适配类。默认数据库是SQLite,但我们更习惯使用MongoDB,所以继承MongoDatabaseAdapter:

storage_adapter.py

from chatterbot import languages
from chatterbot.storage import MongoDatabaseAdapter
from mybot.tagging import ChineseTagger


class MyMongoDatabaseAdapter(MongoDatabaseAdapter):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 设置tagger
        self.tagger = ChineseTagger(language=kwargs.get(
            'tagger_language', languages.CHI
        ))

创建时设置数据库地址:

main.py

...
MONGO_URI = 'mongodb://localhost:27017/chatterbot'

# 创建你的机器人
chatbot = ChatBot(
    '正义骑士号',
    storage_adapter='mybot.MyMongoDatabaseAdapter',
    database_uri=MONGO_URI)
...

到这里中文适配就完成了,换上中文训练数据:

main.py

...
# 训练你的机器人
# 这是训练对话
conversation = [
    '你好',
    '您好,博士。',
    '戳戳',
    '“滴滴”',
    '行动开始!',
    '正义号,出发!',
    '你会用洗衣机吗?',
    '洗衣机也没有想象中那么难用。',
]
...

改成从终端获取用户输入:

main.py

...
# 获取回复
# response = chatbot.get_response('晚上好!')
# print(response)
# 对话
while True:
    try:
        user_input = input()
        bot_response = chatbot.get_response(user_input)
        print(bot_response)
    except (KeyboardInterrupt, EOFError, SystemExit):
        break
...

运行结果:

现在有正常的运行结果了,真是可喜可贺,不过部分对话还是有些奇怪。

一些问题

问题一:对于训练过的句子,正义骑士号能给出对应的答复;没有训练过的句子,则给出随机的答复,但“你会用洗衣机吗?”这样的问句也有可能被选中。

这个问题在了解get_response的整个处理过程后就能知晓原因,get_response内部大致分为以下几个步骤:

  1. 用户输入语句的预处理
  2. 用户输入语句处理,生成检索文本(search_text)
  3. 根据检索文本,在数据库中获取最佳匹配项,如果用户输入了没有训练过的句子,则为空
  4. 根据最佳匹配项的检索文本,搜索最佳匹配项对应的回复
  5. 如搜索到对应回复则返回回复内容,如果没有搜索到回复,此时返回预先设置的默认回复,如果没有默认回复,则从整个数据库中随机选取
  6. 对本次对话进行学习

所以问句也是可能会出现的,如果我们的机器人是偏向问答模式,希望问句仅作为模板,用来匹配用户输入语句,那么这里还需要修改。

问题二:发送“滴滴”,会收到“行动开始!”,发送“您好,博士。”,会收到“戳戳”。

根据训练类ListTrainer的逻辑,同一个列表中的句子会上下关联,也就是“你好”关联“您好,博士。”,“您好,博士。”关联“戳戳”,“戳戳”关联“滴滴”。如果不希望产生这样的关联关系,可以将问句与答句分在一组训练,即每次训练时列表中只保留两个句子。

问题三:重复运行训练代码,数据库中的训练数据会重复添加。

它大概希望我们自己做这个处理。

问题四:到底学了个啥?我也没看出来它哪里在学习。

对话产生后,Chatterbot会向数据库中添加这样的记录,与上一次的回复形成关联:

learn.png

嗯,就这样...没有然后了。

问题五:希望某些句子只匹配固定问句,不出现在随机回复的选取范围内。

如果这些问题对于你来说没有什么大碍,那么经过本篇文章的修改后Chatterbot基本可以愉快地使用了,可以拿github上的一些中文语料库训练看看效果。

下一篇将针对这些问题对Chatterbot进行一些魔改,严格来说可能会偏离它原本的设计思想,不过做人嘛,最紧要就是开心。

相关文章

网友评论

      本文标题:聊天机器人框架Chatterbot的使用与魔改(上)

      本文链接:https://www.haomeiwen.com/subject/eahyzltx.html