本代码来源于github项目地址,项目使用python实现一个简单的基于知识图谱的电影问答系统,相关搭建过程见博客300行python代码从零开始构建基于知识图谱的电影问答系统-目录:。这里通过横向比较基于知识图谱的问答系统1和基于知识图谱的问答系统2,来分析说明不同之处。本项目的与前两个项目在实体提取和意图预测方面存在不同之处。前面两个项目的实体提取通过AC树匹配实现,本项目则直接通过jieba分词中的词性分类来实现,意图预测部分,本项目与基于知识图谱的问答系统2](https://blog.csdn.net/weixin_44023339/article/details/100012074)相同,都是采用基于Tfidf特征的朴素贝叶斯来实现的,但本项目给出了朴素贝叶斯的训练过程。
jieba不仅可以分词,还可以对词性进行标注,具体可参见jieba分词词性说明。项目基于知识图谱实体类别与名称构建字典,然后加载到jieba中,实现输入语句相关实体的提取。
用户字典每行一个词,格式为:
词语 词频 词性
其中词频是一个数字,词性为自定义的词性,要注意的是词频数字和空格都要是半角的。
// userdict.txt
英雄 15 nm
袁咏仪 15 nr
战争 15 ng
字典加载方式为
// 字典加载
import jieba
jieba.load_userdict("./userdict.txt")
由于nr在jieba中的原有词性也是人名,因此随便问一个人名主演的电影,程序都会产生知识图谱的查询操作,但会返回没有结果,因此可以把字典中的nr、ng改为其他非已有词性,提高程序效率。
但从后面的意图分类训练来看,演员是用nnt表示,因此项目在此方面存在不一致,统一同nnt表示演员是合适的,不会与已有的词性混淆,ng表示类型,nm表示电影。
jieba通过jieba.posseg进行词性标注。
// 字典加载
from jieba import posseg
# raw_question="刘德华主演了什么电影?"
clean_question = re.sub("[\s+\.\!\/_,$%^*(+\"\')]+|[+——()?【】“”!,。?、~@#¥%……&*()]+","",raw_question)
question_seged=posseg.cut(str(clean_question))
result=[]
for w in question_seged:
temp_word=f"{w.word}/{w.flag}"
result.append(temp_word) #result=['刘德华/nr', '主演/n', '了/ul', '什么/r', '电影/n']
#为后续的预测和查询存储数据
word, flag = w.word,w.flag
question_word.append(str(word).strip())
question_flag.append(str(flag).strip())
在意图分类方面,项目给出了训练数据构建、模型训练和实际预测等整个环节的代码。其中文本数据采用tfidf特征向量化,分类模型采用MultinomialNB多项式朴素贝叶斯模型。
问句意图共分为13种类别,每种类别样本数据存放在一个文本内,文件名中有类别的序号,典型格式如下
//文件名【6】某演员出演过的类型电影有哪些.txt
nnt演过哪些ng电影
nnt演哪些ng电影
nnt演过ng电影
nnt演过什么ng电影
nnt演过ng电影
nnt演过的ng电影有哪些
nnt出演的ng电影有哪些
“【6】某演员出演过的类型电影有哪些.txt”为文件名,在训练数据构建过程中,7被用于表示类别,
// 构建训练数据集
file_list=getfilelist("./data/question/") # 遍历所有文件
for one_file in file_list: # 获取文件名中的数字
num = re.sub(r'\D', "", one_file) # 替换文件名字符串的非数字字符为空,即提取字符中的数字,'\D'表示非数字字符
if str(num).strip()!="": # 如果该文件名有数字,则读取该文件
label_num=int(num) # 设置当前文件下的数据标签,即该文件内数据对应的类别标签,对应训练样本的y数据
with(open(one_file,"r",encoding="utf-8")) as fr: # 读取文件内容
data_list=fr.readlines()
for one_line in data_list: #一行为一条训练样本
word_list=list(jieba.cut(str(one_line).strip()))
train_x.append(" ".join(word_list)) #每条训练样本的x数据有通过空格相连的词组构成
train_y.append(label_num)
模型采用sklearn模块的朴素贝叶斯模型,先把字符向量化,然后在进行训练
// 构建训练数据集
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import TfidfVectorizer
tv = TfidfVectorizer()
train_data = tv.fit_transform(X_train).toarray() #tfidf向量化模型是基于全部训练数据训练得到,后续预测的时候也需要使用
clf = MultinomialNB(alpha=0.01) #建立MultinomialNB模型
clf.fit(train_data, y_train) #训练MultinomialNB模型
意图预测包括两部内容,首先对输入问句通过jieba进行词性标注获取的question_word、question_flag进行替换处理,即替换nr、ng、nm词性对应的词为词性,以与意图预测的训练样本相匹配。
// 问句实体替换为词性
for item in ['nr','nm','ng']: #nr应该为nnt
while (item in self.question_flag):
ix=self.question_flag.index(item) #查找相应词性的位置
self.question_word[ix]=item #词替换为词性
self.question_flag[ix]=item+"ed" #修改词性,表示已替换了
str_question="".join(self.question_word) #此处没必要合并,可以直接预测
question_template_num=self.classify_model.predict(str_question)
def predict(self,question):
question=[" ".join(list(jieba.cut(question)))] #如何前面不合并,此处没必要处理
test_data=self.tv.transform(question).toarray() #用训练号的词向量化模型,向量化输入问句
y_predict = self.model.predict(test_data)[0] #返回概率最大的预测类别
该项目与基于知识图谱的问答系统2基本相同,不同之处在于详细给出了意图分类的训练过程,代码比较详细。