最终词典的格式:
词语 词性(不要和jieba默认的词性重复)
各种输入法的词典
例如:https://pinyin.sogou.com/dict/cate/index/97?rf=dictindex
例如:https://shurufa.baidu.com/dict_list?cid=211
手动收集,根据目前的需求,我们可以手动收集如下词典
传智,传智播客,黑马程序员python,人工智能+python,c++等输入法的词典都是特殊格式,需要使用特殊的工具才能够把它转化为文本格式
工具名称:深蓝词库转换.exe
下载地址:https://github.com/studyzy/imewlconverter
下载使用不同平台的多个词典之后,把所有的txt文件合并到一起供之后使用
对句子进行分词之后,句子中不重要的词
常用停用词下载地址:https://github.com/goto456/stopwords
对于停用词的具体内容,不同场景下可能需要保留和去除的词语不一样
比如:词语哪个,很多场景可以删除,但是在判断语义的时候则不行
问答对有两部分,一部分是咨询老师整理的问答对,一部分是excel中的问答对,
最终我们需要把问答对分别整理到两个txt文档中,如下图(左边是问题,右边是答案):

Excel中的问答对如下图:

Excel中的问答对直接使用pandas就能够处理
python_qa_path = "./data/Python短问答-11月汇总.xlsx"
def load_duanwenda():
import pandas as pd
ret = pd.read_excel(python_qa_path)
column_list = ret.columns
assert '问题' in column_list and "答案" in column_list,"excel 中必须包含问题和答案"
for q,a in zip(ret["问题"],ret["答案"]):
q = re.sub("\s+"," ",q)
q = q.strip()
print(q,a)
后续在判断问题相似度的时候,需要有语料用来进行模型的训练,输入两个句子,输出相似度,这个语料不好获取,所以决定从百度知道入手,采集百度知道上面的相似问题,如下图所示:

上面采集的数据会存在部分噪声,部分问题搜索到的结果语义上并不是太相似
根据前面的问答对的内容,把问题大致分为了若干类型,对不同类型的问题设计模板,然后构造问题,问题模块如下:
templete = [
#概念
["{kc}","什么是{kc}","{kc}是什么","给我介绍一下{kc}","{kc}可以干什么","能简单说下什么是{kc}吗","我想了解{kc}"],
#课程优势
["{kc}课程有什么特点","{jgmc}的{kc}课程有什么特点","{jgmc}的{kc}课程有什么优势","为什么我要来{jgmc}学习{kc}","{jgmc}的{kc}课程有什么优势","为什么要到{jgmc}学习{kc}","{jgmc}的{kc}跟其他机构有什么区别?","为什么选择{jgmc}来学习{kc}?"],
#语言优势
#["{kc}","什么是{kc}","{kc}是什么","给我介绍一下{kc}","{kc}可以干什么","能简单说下什么是{kc}吗"],
#特点
["{kc}有什么特点","{kc}有什么优势","{kc}有什么亮点","{kc}有那些亮点","{kc}有那些优势","{kc}有那些特点","{kc}的亮点是什么","{kc}的优势是什么","{kc}的特点是什么"],
#发展前景
["{kc}的发展怎么样?","{kc}的前景怎么样?","{kc}的发展前景如何?","{kc}的未来怎样","{kc}的前景好么" ],
#就业
["{kc}好就业么","{kc}就业机会多么","{kc}的岗位多吗","{kc}工作好找吗","{kc}的市场需求怎么样","{kc}的就业环境怎么样"],
#就业方向
["{kc}学完以后能具体从事哪方面工作?","{kc}的就业岗位有哪些?","{kc}课程学完应聘哪方面工作?","{kc}可以从事哪方面工作?",],
#用途
["{kc}学完可以做什么","{kc}能干什么","学{kc}能干什么","能举例说下{kc}能做什么吗?","{kc}毕业了能干什么","{kc}主要应用在什么领域"],
#就业薪资
["{kc}学完工资多少","学完{kc}能拿多少钱","{kc}的就业薪资多少","{kc}就业的平均是工资多少"],
#学习难度
["{kc}简单么","{kc}容易么","{kc}课程容易么","{kc}上手快么","{kc}课程难么"],
#校区
["在那些城市开设了{kc}","哪里可以学习{kc}","学习{kc}可以去那些城市","{kc}在哪里开班了"],
#学费
["{kc}学费","{kc}多少钱","{kc}的学费多少","{kc}是怎么收费的?","学习{kc}要花多少钱","{kc}是怎么收费的","{kc}课程的价格","{kc}课程的价格是多少"],
#适合人群
["什么人可以学{kc}","哪些人可以学{kc}","学习{kc}有什么要求","学习{kc}需要那些条件","没有基础可以学{kc}吗","学历低可以学习{kc}吗?","成绩不好可以学习{kc}吗?","什么样的人适合学习{kc}?"],
#学习时间
["{kc}需要学多久","{kc}需要多久才能就业","{kc}需要学习多长时间","{kc}的学时是多少","{kc}的课时是多少","{kc}课时","{kc}课时长度","{kc}的课程周期?","0基础学{kc}多久能才就业"],
#学习内容
["{kc}学什么","{kc}学习那些内容","我们在{kc}中学习那些内容","在{kc}中大致都学习什么内容"],
#项目内容
["{kc}的项目有哪些","{kc}有哪些项目","{kc}上课都有哪些实战","{kc}做什么项目?","{kc}项目有多少个?","{kc}课程中有项目吗?"],
#学习某课程的好处
["为什么要学习{kc}?","学习{kc}有哪些好处?","学习{kc}的理由?","为什么我要来学习{kc}"],
#上课时间
["上课时间","你们那边每天的上课时间是怎样的呢?"],
#英语要求
["学习{kc}对英语有要求么","来{jgmc}学习对英语有要求吗?"]
]
其中大括号的内容kc表示课程,jgmc表示机构名称
接下来,需要完成两件事
stopwords = set([i.strip() for i in open(config.stopwords_path).readlines()])
def _cut_by_word(sentence):
# 对中文按照字进行处理,对英文不分为字母
sentence = re.sub("\s+"," ",sentence)
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
lib 下创建cut_sentence.py文件,完成分词方法的构建
import logging
import jieba
import jieba.posseg as psg
import config
import re
import string
#关闭jieba log输出
jieba.setLogLevel(logging.INFO)
#加载词典
jieba.load_userdict(config.keywords_path)
#单字分割,英文部分
letters = string.ascii_lowercase
#单字分割 去除的标点
filters= [",","-","."," "]
#停用词
stopwords = set([i.strip() for i in open(config.stopwords_path).readlines()])
def cut(sentence,by_word=False,use_stopwords=False,with_sg=False):
assert by_word!=True or with_sg!=True,"根据word切分时候无法返回词性"
if by_word:
return _cut_by_word(sentence)
else:
ret = psg.lcut(sentence)
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
def _cut_by_word(sentence):
# 对中文按照字进行处理,对英文不分为字母
sentence = re.sub("\s+"," ",sentence)
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
回顾之前的流程,可以发现文本分类的目的就是为了进行意图识别
在当前项目下,只有两种意图需要被识别出来,所以对应的是2分类的问题
可以想象,如果聊天机器人有多个功能,那么需要分类的类别就有多个,这样就是一个多分类的问题。例如,如果希望聊天机器人能够播报当前的时间,那么就需要准备关于询问时间的语料,同时其目标值就是一个新的类别。在训练后,通过这个新的模型,判断出用户询问的是当前的时间这个类别,那么就返回当前的时间。
同理,如果还希望聊天机器人能够播报未来某一天的天气,那么这个机器人就还需要增加一个新的进行分类的意图,重新进行训练
在前面的机器学习的课程中我们学习了朴素贝叶斯,决策树等方法都能够帮助我们进行文本的分类,那么具体该怎么做呢?
使用机器学习的方法进行文本分类的时候,为了让结果更好,经常从两个角度出发
前面简单回顾了使用机器学习如何来进行文本分类,那么使用深度学习该如何实现呢?
在深度学习中我们常见的操作就是:
文档地址:https://fasttext.cc/docs/en/support.html
fastText is a library for efficient learning of word representations and sentence classification.
fastText是一个单词表示学习和文本分类的库
优点:在标准的多核CPU上, 在10分钟之内能够训练10亿词级别语料库的词向量,能够在1分钟之内给30万多类别的50多万句子进行分类。
fastText 模型输入一个词的序列(一段文本或者一句话),输出这个词序列属于不同类别的概率。
git clone https://github.com/facebookresearch/fastText.gitcd fastTextpython setup.py install把数据准备为需要的格式
进行模型的训练、保存和加载、预测
#1. 训练
model = fastText.train_supervised("./data/text_classify.txt",wordNgrams=1,epoch=20)
#2. 保存
model.save_model("./data/ft_classify.model")
#3. 加载
model = fastText.load_model("./data/ft_classify.model")
textlist = [句子1,句子2]
#4. 预测,传入句子列表
ret = model.predict(textlist)
数据准备最终需要的形式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qVLupvux-1661260098567)(…/…/14深度学习/02深度学习/image/2.2/数据准备1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdVr7CXY-1661260098568)(…/…/14深度学习/02深度学习/image/2.2/数据准备2.png)]
以上格式是fastText要求的格式,其中chat、QA字段可以自定义,就是目标值,__label__之前的为特征值,需要使用\t进行分隔,特征值需要进行分词,__label__后面的是目标值
使用之前通过模板构造的样本和通过爬虫抓取的百度上的相似问题,
使用小黄鸡的语料,地址:https://github.com/fateleak/dgk_lost_conv/tree/master/results
对两部分文本进行分词、合并,转化为需要的格式
def prepar_data():
#小黄鸡 作为闲聊
xiaohaungji = "./corpus/recall/小黄鸡未分词.conv"
handle_chat_corpus(xiaohaungji)
# mongodb中的数据,问题和相似问题作为 问答
handle_mongodb_corpus()
def keywords_in_line(line):
"""相似问题中去除关键字不在其中的句子
"""
keywords_list = ["传智播客","传智","黑马程序员","黑马","python"
"人工智能","c语言","c++","java","javaee","前端","移动开发","ui",
"ue","大数据","软件测试","php","h5","产品经理","linux","运维","go语言",
"区块链","影视制作","pmp","项目管理","新媒体","小程序","前端"]
for keyword in keywords_list:
if keyword in line:
return True
return False
def handle_chat_corpus(path):
chat_num = 0
with open("./corpus/recall/text_classify.txt","a") as f:
for line in open(path,"r"):
if line.strip() == "E" or len(line.strip())<1:
continue
elif keywords_in_line(line):
continue
elif line.startswith("M"):
line = line[2:]
line = re.sub("\s+"," ",line)
line_cuted = " ".join(jieba_cut(line.strip())).strip()
lable = "\t__label__{}\n".format("chat")
f.write(line_cuted+lable)
chat_num +=1
print(chat_num)
def handle_QA_corpus():
by_hand_data_path = "./corpus/recall/手动构造的问题.json" #手动构造的数据
by_hand_data = json.load(open(by_hand_data_path))
qa_num = 0
f = open("./corpus/recall/text_classify.txt","a")
for i in by_hand_data:
for j in by_hand_data[i]:
for x in j:
x = re.sub("\s+", " ", x)
line_cuted = " ".join(jieba_cut(x.strip())).strip()
lable = "\t__label__{}\n".format("QA")
f.write(line_cuted + lable)
qa_num+=1
#mogodb导出的数据
for line in open("./corpus/recall/爬虫抓取的问题.csv"):
line = re.sub("\s+", " ", line)
line_cuted = " ".join(jieba_cut(line.strip()))
lable = "\t__label__{}\n".format("QA")
f.write(line_cuted + lable)
qa_num += 1
f.close()
print(qa_num)
是否可以把文本分割为单个字作为特征呢?
修改上述代码,准备一份以单个字作为特征的符合要求的文本
import logging
import fastText
import pickle
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG)
ft_model = fastText.train_supervised("./data/text_classify.txt",wordNgrams=1,epoch=20)
ft_model.save_model("./data/ft_classify.model")
训练完成后看看测试的结果
ft_model = fastText.load_model("./data/ft_classify.model")
textlist = [
# "人工智能 和 人工智障 有 啥 区别", #QA
# "我 来 学 python 是不是 脑袋 有 问题 哦", #QA
# "什么 是 python", #QA
# "人工智能 和 python 有 什么 区别", #QA
# "为什么 要 学 python", #QA
# "python 该 怎么 学", #CHAT
# "python", #QA
"jave", #CHAT
"php", #QA
"理想 很 骨感 ,现实 很 丰满",
"今天 天气 真好 啊",
"你 怎么 可以 这样 呢",
"哎呀 , 我 错 了",
]
ret = ft_model.predict(textlist)
print(ret)
首先需要对文本进行划分,分为训练集和测试集,之后再使用测试集观察模型的准确率
为了在项目中更好的使用模型,需要对模型进行简单的封装,输入文本,返回结果
这里可以使用把单个字作为特征和把词语作为特征的手段结合起来实现
"""
构造模型进行预测
"""
import fastText
import config
from lib import cut
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_mode.predict(python_qs_list)
python_qs_list = [" ".join(cut(sentence_info["sentence"],by_word=True))]
words_result = self.ft_word_mode.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