NLP简单项目实战——ChatBOT(一)

一、项目准备阶段:

(一)什么是ChatBOT:

        ChatBOT即交流机器人,目前主要有三类:QABOT、TASKBOT、CHATBOT。本项目实战中主要实现两个简单的QABOT、CHATBOT。

        1.QABOT常见实现手段:

        QABOT的常见流程,给出一个问题,经过神经网络或者其他模型提取出关键字,然后根据关键字去搜寻答案的过程。主要分为信息检索与知识图谱,信息检索就是字面意思。知识图谱则是存储知识与知识之间的关系,将问答转换为查询语句,实现推理,也就是上面的提取关键词过程。主要实现方式:tfidf、SVM、朴素贝叶斯、RNN、CNN。

        2.CHATBOT的常见实现手段:

        信息检索加seq2seq方法。

(二)需求分析和流程介绍:

        需要实现一个聊天机器人,该机器人可以起到智能客服的作用。由于语料的限制,准备了相关的编程问题,回答什么是python、python有什么优势等问题。

        实现流程:        

        后面的QA机器人是问答模型内容,我们将在前面将基础功能进行完善,后续实现问答模型只用实现内部细节就好了。

        实际运作流程:

        接受用户的问题之后对问题进行基础的处理,然后对问题进行分类判断意图。如果用户希望咨询问题就调用问答模型,希望进行闲聊就调用闲聊模型。

        1.闲聊模型:

        使用seq2seq模型,该模型包括了对文本的embedding、编码层、attention机制的处理、解码层这几个内容。

        2.问答模型:

        使用召回和排序的机制进行实现,实现速度和准确率。

        具体细节:

        问题分析:对问题的基础性处理包括分词、词性的获取、词向量的获取。

        问题召回:通过机器学习的方法进行海选,找到相似问题的前k个。

        问题排序:通过深度学习模型计算准确率然后进行排序

        设置阈值:返回结果

(三)环境配置:

        需要在虚拟环境下完成该项目,所以需要用Anaconda去建立一个新环境,对于的torch等包也要下载好。

        常见conda命令:

        conda create --name 环境名 python=3.x

        conda create -n B --clone A 克隆已有环境

        conda list 列出所有已安装的包

        conda activate 环境名 激活环境

        conda deactivate 环境名 退出环境

        还要安装fasttext,用于文本分类,直接在对应环境pip install fasttext。

        pysparnn直接去Github下载zip文件,然后复制到对应环境的scripts中,然后在当前文件路径中cmd运行pip install 对应文件名以及文件后缀,完成安装。

        jieba的下载:pip install jieba即可

        便完成了基本环境的配置。

        由于我没有在base环境下安装包,我是直接在我之前学习Pytorch里面安装包的。所以为了方便我直接克隆了Pytorch环境,然后下载了一些其他的包。最后配置一个叫ChatBOT的环境成功,如图:

(四)语料准备:

        1.字典下载

        构建如下项目结构,其中corpus用于存放读取语料库、prepare_corpus用于准备语料库。config用于存放一些配置信息,比如文件路径。

        jieba常用方法:lcut用于切分句子返回一个列表、load_userdict用于加载用户词典。 

        在test_user_dict中测试一下jieba是否能正常使用:

        然后我们用自己创建的用户词典,user_dict区划分该句子:

         在test_user_dict定义一个函数用于测试用户词典。

        然后再main函数中运行对应函数,结果和用jieba自带词典效果一样。         由于是自己创造的词典,为了和方便分词并与jieba所默认的词性进行区分,我们在自己创造的词典的内容后加入kc即可(任意名字)。

        上面就是手动创造词典的方式,为了获取更多的语料,我们可以去输入法官网下载相关词典。

        搜狗输入法官网:https://pinyin.sogou.com/dict/cate/index/97?rf=dictindex  

        下载了一个计算机词汇大全,显然这是特殊的文件格式,我们可以用github上已有的转换器将该文件转换为txt文件,

        下载地址:https://github.com/studyzy/imewlconverter,当然是在Release版本中找对应的操作系统。

         下载完毕,由于该程序基于C#开发,所以还需要下载.Net 8.0,正常安装即可。

        接下来我们只用将下载好的scel文件导入进去,就可以获得txt文件了。

        转换成功! 

         

         词典内容,我们不太需要拼音,所以需要简单处理下该文件。

        用这段代码处理一下该文件,

import config


if __name__ == '__main__':
    f = open('sogou_dict1.txt', 'w+')
    f1 = open(config.user_dict_path2, 'rb+') #user_dict_path2即转换文件对象的路径
    line = list(f1.readline().strip().split())
    line = [str(line[i], 'utf-8') for i in range(len(line))]
    while line:
        f.writelines(line[-1] + ' sgjsj')
        f.write('\n')
        line = list(f1.readline().strip().split())
        line = [str(line[i], 'utf-8') for i in range(len(line))]

        处理结果如下,词性应该是sgjsj,我最开始打错了,就是搜狗计算机的意思。:

        2.停用词:

        即分词后句子不重要的词语,常见停用词下载:https://github.com/goto456/stopwords

        3.问答对:

        利用学习课程整理好的问答对,把问题分成以下类型。

        概念问题、课程优势、语言优势等类型。

        kc指的是课程、jgmc表示机构名称。 

        4.字典展示:

(五)文本分词:

        定义一个lib包用于存放基本的方法,比如分词、停用词获取。

        分词api实现:cut_words负责实现中英文字符切分,cut负责判断条件,如果不用分为单个词,则使用jieba的lcut方法进行分词。

"""
分词
"""
import logging
import jieba
import jieba.posseg as psg #posseg是lcut所在的库
import config
import re
import string

#加载词典
jieba.load_userdict(config.user_dict_path)

"""
分词api
"""
#关闭jiebalog输出即jieba的日志输出
"""
logging优先级,从低到高
DEBUG
INFO
WARNING
ERROR
CRITICAL
"""
jieba.setLogLevel(logging.INFO) #logging用于记录程序运行的信息

#单字分割,英文部分
letters = string.ascii_lowercase #小写字母
#单字分割 去除的标点
filters= [",", "-", ".", " "]
#停用词
stopwords = set([i.strip() for i in open(config.stopwords_path,encoding='utf-8').readlines()])
def cut_word(sentence):
    """实现中英文分词"""
    # 对中文按照字进行处理,对英文不分为字母
    sentence = re.sub("\s+", " ", sentence) #匹配空白,如空格、tab,将这些转换为单个空白符
    sentence = sentence.strip()
    result = []
    temp = ""
    for word in sentence:
        if word.lower() in letters:
            temp += word.lower()
        else:
            if temp != "":  # 不是字母
                result.append(temp)
                temp = ""
            if word.strip() in filters:  # 标点符号
                continue
            else:  # 是单个字
                result.append(word)
    if temp != "":  # 最后的temp中包含字母
        result.append(temp)
    return result

def cut(sentence, by_words=False, use_stopwords=False, with_sg=False):
    """
    :param sentence: 传入参数
    :param by_words: 是否按照单个字进行分词
    :param use_stopwords:是否使用停用词
    :param with_sg:是否返回词性
    :return:
    """
    assert by_words != True or with_sg != True, "根据word切分时候无法返回词性"
    if by_words:
        return cut_word(sentence)
    else:
        ret = psg.lcut(sentence) #按照用户提供的词典进行分词
        #jieba获取词性直接.flag就好了
        if use_stopwords:
            ret = [(i.word, i.flag) for i in ret if i.word not in stopwords]
        if not with_sg:
            ret = [i.word for i in ret]
        return ret

        使用jieba进行词语分类:

        使用自定义的cut_word分类:

         启用体用词与词性的jieba分类,可以看到,问号被丢弃了:

        获取停用词可以另外创建一个py文件,如果每次分词都要计算一遍停用词速度会有所降低。可以采用在main中获取stopwords。

(六)文本分类:

        文本分类的目的就是为了意图识别。这是因为文本分类之后得到的是词向量,然后再对词向量进行关键词抽取,这些工作都是为意图识别打下基础。文本分类往往是一个多分类问题,可以理解为你希望机器人回答什么问题,就有多少种意图,就有多少个类别。

        机器学习的分类方法:朴素贝叶斯和决策树

        机器学习的文本分类流程:特征工程、模型构建、训练、评估。

        如果效果不好就删除一下不太重要的词语,也可以用集成学习方法。

        深度学习分类方法就比较熟悉了:文本embedding、多次线性和非线性的变换、根据变换结果计算得到损失函数、然后反向传播更新原来参数,然后输出较好的结果。

        但是显然,我们此次项目的重点不在文本分类中,如果自己去设计并训练模型,效率实在是太低了,必须要在文本分类的过程中采取比较好的方案。于是选择了fasttext,速度快准确率高、便于使用。

        fasttext基本使用如下:

        建立模型:fastText.train_supervised(f,wordNgrams=1.epoch=),wordNgrams指的是一组数据中有几个词语。

        保存模型:save_model(f)

        加载:load_model(f)

        predict:预测

        fasttext要求格式为data+‘\t’+__label__+目标值。

        1.语料准备:

        准备两个内容:问题文本以及闲聊文本。

        问题文本:采用课程提供的文本以及模板构造的文本(如果有需要私聊我即可)

        闲聊文本:使用小黄鸡语料

        下载地址:  https://github.com/fateleak/dgk_lost_conv/tree/master/results 

        准备好即可~

        创建如下目录:

         在config文件下添加如下路径:

        具体代码:

import json

import pandas
import config
from lib.cut_sentence import cut
from tqdm import tqdm#进度条库
def keywords_in_line(line):
    """判断是否line中有符合要求的词"""
    keywords = ["传智播客", "传智", "黑马程序员", "黑马", "python",
    "人工智能", "c语言", "c++", "java", "javaee", "前端", "移动开发", "ui",
    "ue", "大数据", "软件测试", "php", "h5", "产品经理", "linux", "运维", "go语言",
    "区块链", "影视制作", "pmp", "项目管理", "新媒体", "小程序", "前端"]
    for keyword in keywords:
        if keyword in line:
            return True
    return False
def process_xiaohuangji(file):
    """处理小黄鸡的语料,小黄鸡的语料分为E开头和M开头。M开头有两个,M1为问答、M2为回答"""
    num = 0
    for line in tqdm(open(config.xiao_huang_ji_path, encoding='utf-8').readlines(), desc='xiaohuangji'): #读取数据
        if line.startswith('E'):
            flag = 0
            continue
        elif line.startswith('M'):
            if flag == 0: #第一个M出现
                line = line[1:].strip()
                flag = 1
            else:
                continue #不需要回答

        lined_cuted = ' '.join(cut(line))
        if not keywords_in_line(lined_cuted):
            lined_cuted = lined_cuted + '\t' + '__label__chat'
            num += 1
            file.write(lined_cuted + '\n')
    return num
def process_at_hand(file):
    """
    json是JavaScript类型的格式,python提供json.load方法去加载该文件格式。
    该文件打开后可以发现是一个字典,所有需要先拿到对应的keys
    """
    num = 0
    total_lines = json.loads(open(config.by_hand_path, encoding='utf-8').read())
    for key in total_lines.keys():
        for lines in tqdm(total_lines[key], desc='byhand'):
            for line in lines:
                if '校区' in line:
                    lined_cuted = ' '.join(cut(line))
                    lined_cuted = lined_cuted + '\t' + '__label__QA'
                    num += 1
                    file.write(lined_cuted + '\n')
    return num
def process_crawled_data(file):
    """处理抓取的数据"""
    num = 0
    for line in tqdm(open(config.pachong_path, encoding='utf-8'), desc='crawled_data'):
        lined_cuted = ' '.join(cut(line))
        num += 1
        lined_cuted = lined_cuted + '\t' + '__label__QA'
        file.write(lined_cuted + '\n')
    return num
def process():
    f = open(config.classify_corpus_path, 'a', encoding='utf-8')
    #处理小黄鸡
    num_chat = process_xiaohuangji(f)
    #处理手动构造数据
    num_q = process_at_hand(f)
    #处理爬虫数据
    num_q += process_crawled_data(f)

    f.close()
    print(num_chat, num_q)

        然后在main函数中执行process()即可 :

        2.分类模型构建(意图识别): 

        构建一个新的python包,用于封装模型的创建和加载,并创建一个用于测试模型的py文件。

        代码:

import fasttext
import config

"""
构建模型
"""
def build_classify_model():
    #输入数据中只包含一个词语
    model = fasttext.train_supervised(config.classify_corpus_path, wordNgrams=1, epoch=20, minCount=5)
    model.save_model(config.classify_model_path)
"""
加载模型
"""
def get_classify_model():
    model = fasttext.load_model(config.classify_model_path)
    return model

         测试代码:

"""
用于测试构建的model
"""

from classify.build_mode import build_classify_model,get_classify_model

if __name__ == '__main__':
    build_classify_model()

        完成模型的构建

         实例测试一下:

"""
用于测试构建的model
"""

from classify.build_mode import build_classify_model, get_classify_model

if __name__ == '__main__':
    model = get_classify_model()
    text = ['您 吃 饭 了 吗', '今 天 天 气 非 常 好', 'python', 'python 好 学 么']
    print(model.predict(text))

"""
([['__label__chat'], ['__label__chat'], ['__label__QA'], ['__label__chat']], [array([1.00001], dtype=float32), array([1.0000087], dtype=float32), array([0.8048437], dtype=float32), array([1.0000099], dtype=float32)])

"""

        模型认为第一个句子、第二个句子是是聊天的可能性比较大,第三个句子是问题的可能比较大,第四个句子可能是聊天的可能性比较大。但我们在项目中提到过,和编程语言有关的都是问题。为了更好的进行模型评估,我们可以将数据进行切分为训练集与数据集。

        在config中准备两个路径,一个是测试集路径,一个是训练集路径。将build_classify_corpus的代码修改为:

        用random的choice方法去从flags中随机挑选一个值,为0则为训练集,为1则为测试集。

import json

import pandas
import config
from lib.cut_sentence import cut
from tqdm import tqdm#进度条库
import random
flags = [0, 0, 0, 0, 1] #五分之一的数据作为测试集,五分之四的数据作为训练集
def keywords_in_line(line):
    """判断是否line中有符合要求的词"""
    keywords = ["传智播客", "传智", "黑马程序员", "黑马", "python",
    "人工智能", "c语言", "c++", "java", "javaee", "前端", "移动开发", "ui",
    "ue", "大数据", "软件测试", "php", "h5", "产品经理", "linux", "运维", "go语言",
    "区块链", "影视制作", "pmp", "项目管理", "新媒体", "小程序", "前端"]
    for keyword in keywords:
        if keyword in line:
            return True
    return False
def process_xiaohuangji(f_train, f_test):
    """处理小黄鸡的语料,小黄鸡的语料分为E开头和M开头。M开头有两个,M1为问答、M2为回答"""
    num_train = 0
    num_test = 0
    for line in tqdm(open(config.xiao_huang_ji_path, encoding='utf-8').readlines(), desc='xiaohuangji'): #读取数据
        if line.startswith('E'):
            flag = 0
            continue
        elif line.startswith('M'):
            if flag == 0: #第一个M出现
                line = line[1:].strip()
                flag = 1
            else:
                continue #不需要回答

        lined_cuted = ' '.join(cut(line))
        if not keywords_in_line(lined_cuted):
            lined_cuted = lined_cuted + '\t' + '__label__chat'
            if random.choice(flags) == 0: #随机从列表中选择一个值
                f_train.write(lined_cuted + '\n')
                num_train += 1
            else:
                f_test.write(lined_cuted + '\n')
                num_test += 1
    return num_train, num_test
def process_at_hand(f_train, f_test):
    """
    json是JavaScript类型的格式,python提供json.load方法去加载该文件格式。
    该文件打开后可以发现是一个字典,所有需要先拿到对应的keys
    """
    num_train = 0
    num_test = 0
    total_lines = json.loads(open(config.by_hand_path, encoding='utf-8').read())
    for key in total_lines.keys():
        for lines in tqdm(total_lines[key], desc='byhand'):
            for line in lines:
                if '校区' in line:
                    lined_cuted = ' '.join(cut(line))
                    lined_cuted = lined_cuted + '\t' + '__label__QA'
                    if random.choice(flags) == 0:  # 随机从列表中选择一个值
                        f_train.write(lined_cuted + '\n')
                        num_train += 1
                    else:
                        f_test.write(lined_cuted + '\n')
                        num_test += 1
    return num_train, num_test
def process_crawled_data(f_train, f_test):
    """处理抓取的数据"""
    num_train = 0
    num_test = 0
    for line in tqdm(open(config.pachong_path, encoding='utf-8'), desc='crawled_data'):
        lined_cuted = ' '.join(cut(line))
        lined_cuted = lined_cuted + '\t' + '__label__QA'
        if random.choice(flags) == 0:  # 随机从列表中选择一个值
            f_train.write(lined_cuted + '\n')
            num_train += 1
        else:
            f_test.write(lined_cuted + '\n')
            num_test += 1
    return num_train, num_test
def process():
    f_train = open(config.classify_corpus_train_path, 'a', encoding='utf-8')
    f_test = open(config.classify_corpus_test_path, 'a', encoding='utf-8')
    #处理小黄鸡
    num_chat_train, num_chat_test = process_xiaohuangji(f_train, f_test)
    #处理手动构造数据
    num_q_train, num_q_test = process_at_hand(f_train, f_test)
    #处理爬虫数据
    _a, _b = process_crawled_data(f_train, f_test)
    num_q_train += _a
    num_q_test += _b
    f_train.close()
    f_test.close()
    print(f'聊天语料训练集数量{num_chat_train}, 聊天语料测试集数量{num_chat_test}')
    print(f'问题语料训练集数量{num_q_train}, 问题语料测试集数量{num_q_test}')

        调整完后,训练集约有17000词、测试集约有3000词。

        将模型封装为一个类,并判断准确率:

"""
构造模型进行预测
"""
import fasttext
import config
from lib import cut_sentence


class Classify:
    def __init__(self):
        self.ft_word_model = fasttext.load_model(config.fasttext_word_model_path)
        self.ft_model = fasttext.load_model(config.fasttext_model_path)

    def is_qa(self,sentence_info):
        python_qs_list = [" ".join(sentence_info["cuted_sentence"])]
        result = self.ft_model.predict(python_qs_list)

        python_qs_list = [" ".join(cut_sentence.cut(sentence_info["sentence"],by_word=True))]
        words_result = self.ft_word_model.predict(python_qs_list)

        acc,word_acc = self.get_qa_prob(result,words_result)
        if acc>0.95 or word_acc>0.95:
            #是QA
            return True
        else:
            return False

    def get_qa_prob(self,result,words_result):
        label, acc, word_label, word_acc = zip(*result, *words_result)
        label = label[0]
        acc = acc[0]
        word_label = word_label[0]
        word_acc = word_acc[0]
        if label == "__label__chat":
            acc = 1 - acc
        if word_label == "__label__chat":
            word_acc = 1 - word_acc
        return acc,word_acc

        只需要在config中添加 fasttext_word_model_path、fasttext_model_path的路径,然后再将对应模型进行训练即可。若需要评测按字分和按词分方法的准确性,只需要调用该类中的方法即可。

        3.fasttext原理:

        3.1神经网络模型

        显然,fasttext给我们带来了便利,下面了解一下该模型的原理。

        fasttext的架构仅有三层,输入层、隐含层、输出层。

        3.2N-garm特点

        输入层是对文本进行embedding之后的向量具有N-garm特征,隐藏层是对于输入数据进求和平均,输出层输出文档对应标签。

        N-garm即一种词袋模型,一种统计词频的手段,相比于单纯统计单词出现次数的其他方法,N-garm还考虑前面出现的词语。主要方法为,第n个词的出现与前n-1个词相关。对于fasttext的输入层而言,不仅有我们自己输入的数据,还要经过N-garm处理的词语进行输入。

        3.3对传统softmax的优化方法-层次softmax

        理解层次的softmax,先要理解哈夫曼树。哈夫曼树我们都比较熟悉了,这里主要用到哈夫曼编码,就是任意字符的编码都不是另外一个字符编码的前缀。通过哈夫曼编码,对于一个文本序列而言,此时最小带权路径长度就是报文的最短长度,保证报文总编码长度最小。

        而层次的softmax,就是将哈夫曼树的对应权重设置为参数,通过向后传播不断更新该参数,损失函数认为对数似然函数。

        3.4negative sampling

        从除当前label之外的其他label选择几个为负样本,作为出现负样本的概率添加到损失函数。借此提高训练速度、模拟真实场景的噪声情况,让模型的稳健性更强。

        因为负数样本用Relu或leaky-Relu函数会将数值映射为0或者接近为0得极大负数,使得在参数更新过程中,直接不用计算对应参数,大大减少了计算量。

        


4.8号更新线.........................        

  

  • 65
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!对于BERT情感分类实战,您可以按照以下步骤进行: 1. 数据准备: - 收集和整理情感分类的训练数据集,一般包含文本和对应的情感类别(如正面、负面、中性等)。 - 划分数据集为训练集和测试集,确保数据集的均衡性和随机性。 2. BERT模型介绍: - BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的自然语言处理模型,通过双向Transformer编码器学习语义表示。 - 可以选择使用基于TensorFlow或PyTorch实现的BERT模型,或者直接使用已经训练好的BERT模型进行微调。 3. 模型微调: - 使用训练集对BERT模型进行微调,即在预训练的BERT模型基础上,通过训练集进行进一步的学习。 - 这里的微调过程包括输入数据的预处理、构建分类任务的模型结构、定义损失函数和优化算法等。 4. 模型评估: - 使用测试集对训练好的BERT模型进行评估,计算分类准确率、精确率、召回率等指标,评估模型在情感分类任务上的性能。 5. 预测与应用: - 使用训练好的BERT模型对新的文本进行情感分类预测,得到情感类别的预测结果。 - 可以将该模型应用于各种情感分析任务,如舆情监测、评论情感分析等。 以上是BERT情感分类实战的一般流程,您可以根据具体需求和数据特点进行相应调整和优化。希望能对您有所帮助!如有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值