大家好,欢迎来到IT知识分享网。
一、项目准备阶段:
(一)什么是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.], 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号更新线…………………….
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/147825.html