Python实现 利用朴素贝叶斯模型(NBC)进行问句意图分类
目录
朴素贝叶斯分类(NBC)
程序简介
分类流程
字典(dict)构造:用于jieba分词和槽值替换
数据集构建
代码分析
另外:点击右下角魔法阵上的【显示目录】,可以导航~~
朴素贝叶斯分类(NBC)
这篇博客的重点不在于朴素贝叶斯分类的原理,而在于怎么用朴素贝叶斯分类器解决实际问题。所以这边我就简单介绍以下我们使用的模型。
NBC模型所需估计的参数很少,对缺失数据不太敏感,算法也比较简单。贝叶斯方法是以贝叶斯原理为基础,使用概率统计的知识对样本数据集进行分类。它假设特征条件之间相互独立,先通过已给定的训练集,以特征词之间独立作为前提假设,学习从输入到输出的联合概率分布,再基于学习到的模型,输入X求出使得后验概率最大的输出Y。
程序简介
这是一个糖尿病医疗智能问答的子模块。目的是把糖尿病患者提出的问题对其意图进行分类,以便后续根据知识图谱对问句进行回答。
对于意图分类这个子模块,我的思路是自己构建数据集和类别,再对问句进行分词,槽值替换,去停用词,根据特征词构建one-hot向量后,调用skearn模块的朴素贝叶斯接口建模,对问句进行分类。
要解决这个意图分类问题,有以下几点需要注意:
- 在缺少大量而准确的数据集的情况下,深度学习模型并不是一个很好的选择,没有足够多的样本,难以训练好那么多的参数。于是我在这里选择朴素贝叶斯的方法。
- 由于缺少现成的数据集,我自己构建了数据集。这里有些讲究:每个类别最好数量均衡;每个类别要注意包含一些关键词,各个关键词数量均衡,搭配均衡。
- “一型糖尿病可以吃草莓吗?“ 与 ”二型糖尿病可以吃苹果吗?“在意图上是一个类别的问题,然而重叠的特征词似乎只有“可以”,“吃”,“吗”,而这些有点类似于停用词了。但我们人类能看出它们是一个意图,是因为“一型糖尿病”,“二型糖尿病”,“草莓”,“苹果”这些实词。但我们不可能在数据集里穷举所有的病名和水果名甚至其它食物,那如何让它们被识别为一类呢?所以这里我进行了槽值替换。将所有病名都替换为”[DISEASE]";所有蔬菜水果都替换为"[FOOD]"。替换后再统计词频进行训练,效果就比较好。
最终我实现了这样一个分类器,它的接口为
- 输入:糖尿病患者提出的问句
- 输出:问句的意图分类
github地址:https://github.com/PengJiazhen408/Naive-Bayesian-Classifier
分类流程
问句-->槽值替换-->分词-->根据vocab.txt 生成特征向量 --> 模型预测生成标签 --> 标签转换为类别(中文)
如:
| 问句 | 槽值替换后 | 分词后 | one-hot 特征词向量 | 标签 | 类别 |
|---|---|---|---|---|---|
| 糖尿病可以吃草莓吗 | [DISEASE]可以吃[FOOD]吗 | [/DISEASE/]/可以/吃/[/FOOD/]/吗 | [1, 0, 0, 0, 0, 0, 0, 0, 1 ....] | 2 | 饮食 |
| 胰岛素的副作用 | [DRUG]的副作用 | [/DRUG/]/的/副作用 | [0, 0, 0, 1, 0, 0, 0, 0, 0 ....] | 5 | 用药情况 |
| 糖尿病吃什么药 | [DISEASE]吃什么药 | [/DISEASE/]/吃什么/药 | [1, 0, 0, 0, 0, 0, 0, 0, 0 ....] | 3 | 用药治疗 |
| 糖尿病高血糖怎么治 | [DISEASE][DISEASE]怎么治 | [/DISEASE/]/[/DISEASE/]/怎么/治 | [2, 0, 0, 0, 0, 0, 0, 1, 0 ....] | 0 | 治疗 |
字典(dict)构造:用于jieba分词和槽值替换
类别
| 文件名 | 内容 | 例子 |
|---|---|---|
| category.txt | 食物集合名 | 如:海鲜,早餐等 |
| check.txt | 检查项目名 | 如:测血糖,抽血等 |
| department.txt | 科室名 | 如:内分泌科,儿科等 |
| disease.txt | 疾病名 | 如:糖尿病,一型糖尿病等 |
| drug.txt | 药物名 | 如:胰岛素,二甲双胍等 |
| food.txt | 食物和水果 | 如:苹果,绿豆等 |
| style.txt | 生活方式名 | 如:运动,跑步,洗澡等 |
| symptom.txt | 症状名 | 如:头疼,腹泻等 |
格式
一个词一行
每个文件第一个词是类别名,如[DISEASE],[DRUG] 用于槽值替换
数据集构建
训练集(train_data.txt): 各类50例
测试集(test_data.txt): 各类5或10例
格式:问句 类别(中文)
注意事项:每个类别要注意包含一些关键词,各个关键词数量均衡,搭配均衡
代码分析
共有4个主程序:
- data_pro.py 处理原始数据集,生成类别文件,将类别转换为数字标签并存为文件
- extract.py 根据训练数据,经过槽值替换,分词,人工筛选等步骤,生成 vocab.txt
- main.py 模型训练,测试,单句预测
- predict.py 根据训练好的模型,批量预测
data_pro.py: 由train_data.txt, test_data.txt生成 class.txt, train.txt, test.txt
导入模块
from utils import open_data
from random import shuffle
import os
加载原始数据集(train_data.txt, test_data.txt)
def open_data(path):
contents, labels = [], []
with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
lin = line.strip()
if not lin:
continue
content, label = lin.split('\t')
contents.append(content)
labels.append(label)
return contents, labels
# 加载原始数据
x_train, c_train = open_data('data_orig/train_data.txt')
x_test, c_test = open_data('data_orig/test_data.txt')
对类别列表去重,生成class.txt
# 识别所有类,生成类别列表和字典,并保存类别列表
if not os.path.exists('data'):
os.mkdir('data')
class_list = list(set(c_train))
with open('data/class.txt', 'w', encoding='utf-8') as f:
f.writelines(content+'\n' for content in class_list)
class_dict = {}
for i, item in enumerate(class_list):
class_dict[item] = str(i)
将每个问句的类别(中文)转换为标签(数字)
打乱数据集,保存在train.txt, test.txt
def pro_data(x, c, str):
y = [class_dict[i] for i in c]
all_data = list(zip(x, y))
shuffle(all_data)
x[:], y[:] = zip(*all_data)
folder = 'data/'
save_sample(folder, str, x, y)
return x, y
def save_sample(data_folder, str, x, y):
path = data_folder + str + '.txt'
with open(path, 'w', encoding='utf-8') as f:
for i in range(len(x)):
content = x[i] + '\t' + y[i] + '\n'
f.write(content)
# 类别转换为标签, 打乱顺序, 保存
x_train, y_train = pro_data(x_train, c_train, 'train')
x_test, y_test = pro_data(x_test, c_test, 'test')
extract.py: 由train.txt, stopwords.txt 生成 特征列表 vocab.txt
加载train.txt,提取其所有问句;加载stopwords.txt,生成停用词表
def open_data(path):
contents, labels = [], []
with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
lin = line.strip()
if not lin:
continue
content, label = lin.split('\t')
contents.append(content)
labels.append(label)
return contents, labels
# 加载问句列表和停用词表
questions, _ = open_data('data/train.txt')
stopwords = [line.strip() for line in open("data/stopwords.txt", 'r', encoding="utf-8").readlines()]
对问句槽值替换。步骤:
- 加载dict中的文件,生成同义词表的字典,key:每个词,value:该词所在文件第一个词
- 问句分词
- 遍历问句的每个词,用同义词表进行替换
- 返回替换后的句子
如:
| 原句 | 分词 | 替换 |
|---|---|---|
| 糖尿病可以吃草莓吗 | 糖尿病/可以/吃/草莓/吗 | DISEASE]可以吃[FOOD]吗 |
| 胰岛素的副作用 | 胰岛素/的/副作用 | [DRUG]的副作用 |
| 糖尿病吃什么药 | 糖尿病/吃什么/药 | [DISEASE]吃什么药 |
| 糖尿病高血糖怎么治 | 糖尿病/高血糖/怎么/治 | [DISEASE][DISEASE]怎么治 |
def load_jieba():
# jieba加载词典
for _, _, filenames in os.walk('dict'):
for filename in filenames:
jieba.load_userdict(os.path.join('dict', filename))
# jieba分词时,对下列这些词继续往下分
del_words = ['糖尿病人', '常用药', '药有', '感冒药', '特效药', '止疼药', '中成药', '中药', '止痛药', '降糖药', '单药', '喝啤酒',
'西药', '怎样才能', '要测', '要验', '能测', '能验', '喝酒', '喝奶', '吃糖', '喝牛奶', '吃肉', '茶好', '吃水果']
# jieba分词时,不要把下列这些词分开
add_words = ['DISEASE', 'SYMPTOM', 'CHECK', 'FOOD', 'STYLE', 'CATEGORY', '会不会', '能不能', '可不可以', '是不是', '要不要',
'应不应该', '啥用', '什么用', '吃什么', '喝什么']
for word in del_words:
jieba.del_word(word)
for word in add_words:
jieba.add_word(word)
def synonym_sub(question):
# dict文件夹中的每个文件是一个同义词表
# 1读取同义词表:并生成一个字典。
combine_dict = {}
for _, _, filenames in os.walk('dict'):
for filename in filenames:
fpath = os.path.join('dict', filename)
# 加载同义词
synonyms = []
with open(fpath, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
synonyms.append(line.strip())
for i in range(1, len(synonyms)):
combine_dict[synonyms[i]] = synonyms[0]
# with open('synonym.txt', 'w', encoding='utf-8') as f:
# f.write(str(combine_dict))
# 2将语句切分
seg_list = jieba.cut(question, cut_all=False)
temp = "/".join(seg_list)
# print(temp)
# 3
final_sentence = ""
for word in temp.split("/"):
if word in combine_dict:
word = combine_dict[word]
final_sentence += word
else:
final_sentence += word
# print(final_sentence)
return final_sentenc
load_jieba()
# 槽值替换
for i in range(len(questions)):
questions[i] = synonym_sub(questions[i])
分词,统计各个词词频,按照词频从大到小对词语排序,生成词语列表
词语列表除去停用词表中的词语;除去长度为1的词;加上对分类有用的长度为1的词,如 ‘吃’,‘药’,‘治’等
将词语列表(也即特征列表)保存在vocab.txt
# 分词
words = jieba.cut("\n".join(questions), cut_all=False)
print(words)
# 统计词频
word_count = {}
stopwords = [line.strip() for line in open("data/stopwords.txt", 'r', encoding="utf-8").readlines()]
for word in words:
if word not in stopwords:
if len(word) == 1:
continue
word_count[word] = word_count.get(word, 0) + 1
items = list(word_count.items())
items.sort(key=lambda x: x[1], reverse=True)
# vocab中添加的单字
single = ['吃', '喝', '药', '能', '治', '啥', '病', '查', '测', '检', '验', '酒', '奶', '糖']
with open('data/vocab.txt', 'w', encoding='utf-8') as f:
for item in items:
f.write(item[0]+'\n')
for word in single:
f.write(word + '\n')
停用词表构造:人工筛选出vocab.txt中对分类无意义的词,加入到stopwords.txt中。再重复运行extract.py
main.py: 模型训练,测试,预测
导入模块,预设路径
from utils import *
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics
import numpy as np
from termcolor import colored
import matplotlib.pyplot as plt
import seaborn as sns
import pickle
import pandas as pd
data_dir = 'data'
加载train.txt, test.txt, class.txt
# 加载训练集 测试集 类别
q_train, y_train = open_data(os.path.join(data_dir,'train.txt'))
q_test, y_test = open_data(os.path.join(data_dir, 'test.txt'))
classes = [line.strip() for line in open(os.path.join(data_dir, 'class.txt'), 'r', encoding="utf-8").readlines()]
模型训练:训练集问句-->槽值替换-->分词-->根据vocab.txt 生成特征向量 --> (特征向量,标签) 作为模型输入 --> 训练模型
def qes2wb(questions):
# 加载vocab
data_folder = 'data/'
vocab = [line.strip() for line in open(data_folder+'vocab.txt', 'r', encoding="utf-8").readlines()]
# 问句,槽值替换后, 分词,转换为词袋向量
vecs = []
load_jieba()
for question in questions:
sen = synonym_sub(question)
# print(sen)
words = list(jieba.cut(sen, cut_all=False))
# print('/'.join(words))
vec = [words.count(v) for v in vocab]
vecs.append(vec)
return vecs
# 转为词袋模型
x_train = qes2wb(q_train)
# 建模
model = MultinomialNB()
model.fit(x_train, y_train)
# 保存模型
with open('MultinomialNB.pkl', 'wb') as f:
pickle.dump(model, f)
模型测试:测试集问句-->槽值替换-->分词-->根据vocab.txt 生成特征向量 --> 模型预测生成预测标签
def qes2wb(questions):
# 加载vocab
data_folder = 'data/'
vocab = [line.strip() for line in open(data_folder+'vocab.txt', 'r', encoding="utf-8").readlines()]
# 问句,槽值替换后, 分词,转换为词袋向量
vecs = []
load_jieba()
for question in questions:
sen = synonym_sub(question)
# print(sen)
words = list(jieba.cut(sen, cut_all=False))
# print('/'.join(words))
vec = [words.count(v) for v in vocab]
vecs.append(vec)
return vecs
# 转为词袋模型
x_test = qes2wb(q_test)
# 测试
p_test = model.predict(x_test)
y_test = np.array(y_test)
p_test = np.array(p_test)
模型评价:预测标签与真实标签比对,输出评价指标,分析 bad cases, 可视化混淆矩阵
# 输出模型测试结果
print(metrics.classification_report(y_test, p_test, target_names=classes))
# for i, c in enumerate(classes):
# print("%d: %s" % (i, c), end='\t')
# print('\n')
# print(metrics.classification_report(y_test, p_test))
# 输出错误
errors = []
for i in range(len(y_test)):
if y_test[i] != p_test[i]:
errors.append((y_test[i], q_test[i], p_test[i]))
print('---Bad Cases---')
for y, q, p in sorted(errors):
print('Truth: %-20s Query: %-30s Predict: %-20s' % (classes[int(y)], q, classes[int(p)]))
# 混淆矩阵
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# 用来正常显示负号
plt.rcParams['axes.unicode_minus'] = False
# 计算混淆矩阵
confusion = metrics.confusion_matrix(y_test, p_test)
plt.clf()
plt.title('分类混淆矩阵')
sns.heatmap(confusion, square=True, annot=True, fmt='d', cbar=False,
xticklabels=classes,
yticklabels=classes,
linewidths=0.1, cmap='YlGnBu_r')
plt.ylabel('实际')
plt.xlabel('预测')
plt.xticks(rotation=-13)
plt.savefig('分类混淆矩阵.png', dpi=100)
模型评价结果:
precision recall f1-score support
治疗 0.91 1.00 0.95 10
检查项目 0.91 1.00 0.95 10
饮食 0.90 0.90 0.90 10
用药治疗 1.00 1.00 1.00 10
并发症 1.00 1.00 1.00 10
用药情况 0.77 1.00 0.87 10
遗传与传染 1.00 1.00 1.00 10
病因 0.83 1.00 0.91 5
其它 0.89 0.80 0.84 10
症状 1.00 0.50 0.67 10
保健与护理 1.00 1.00 1.00 10
accuracy 0.92 105
macro avg 0.93 0.93 0.92 105
weighted avg 0.93 0.92 0.92 105
---Bad Cases---
Truth Query Predict
保健与护理 糖尿病如何降低血糖 遗传与传染
病因 糖尿病是如何造成的 保健与护理
其它 压力大会引起糖尿病吗 病因
其它 糖尿病并发症咋办 并发症
其它 糖尿病并发症应注意什么 并发症
其它 糖尿病并发症怎么治 治疗
其它 糖尿病并发症的病因 并发症

单句预测:输入问句-->提取特征(槽值替换 --> 分词 --> 转换为向量)--> 模型预测 --> 输出预测的意图
def qes2wb(questions):
# 加载vocab
data_folder = 'data/'
vocab = [line.strip() for line in open(data_folder+'vocab.txt', 'r', encoding="utf-8").readlines()]
# 问句,槽值替换后, 分词,转换为词袋向量
vecs = []
load_jieba()
for question in questions:
sen = synonym_sub(question)
# print(sen)
words = list(jieba.cut(sen, cut_all=False))
# print('/'.join(words))
vec = [words.count(v) for v in vocab]
vecs.append(vec)
return vecs
# 单句预测
while True:
query = input(colored('请咨询:', 'green'))
x_query = qes2wb([query])
# print(x_query)
p_query = model.predict(x_query)
# print(p_query)
print('意图: ' + classes[int(p_query[0])])
预测结果:
请咨询:糖尿病可以吃草莓吗
意图: 饮食
请咨询:胰岛素的副作用
意图: 用药情况
请咨询:糖尿病吃什么药
意图: 用药治疗
请咨询:糖尿病高血糖怎么治
意图: 治疗
predict.py: 批量预测
导入模块,预设路径
from utils import *
import pandas as pd
import pickle
import os
classes_dir = 'data'
data_dir = 'predict_data'
加载class.txt(类别),question.csv(问句列表),模型
批量预测:问句 -->提取特征(槽值替换 --> 分词 --> 转换为向量)--> 模型预测
保存结果到result.csv
# 加载类别和问句
classes = [line.strip() for line in open(os.path.join(classes_dir, 'class.txt'), 'r', encoding="utf-8").readlines()]
sentence_csv = pd.read_csv(os.path.join(data_dir, 'question.csv'), sep='\t', names=['title'])
sentences = sentence_csv['title'].tolist()
# 加载模型
if os.path.exists('MultinomialNB.pkl'):
with open('MultinomialNB.pkl', 'rb') as f:
model = pickle.load(f)
else:
raise Exception("Please run main.py first!")
# 预测
x_query = qes2wb(sentences)
p_query = model.predict(x_query)
results = [classes[int(p)] for p in p_query]
# 保存结果
dataframe = pd.DataFrame({'title': sentences, 'classes': results})
dataframe.to_csv(os.path.join(data_dir, 'result.csv'), index=False, sep=',')
result.csv
| title | classes |
|---|---|
| 糖尿病可以吃草莓吗 | 饮食 |
| 胰岛素的副作用 | 用药情况 |
| 糖尿病吃什么药 | 用药治疗 |
| 糖尿病高血糖怎么治 | 治疗 |
Python实现 利用朴素贝叶斯模型(NBC)进行问句意图分类的更多相关文章
- 机器学习Matlab打击垃圾邮件的分类————朴素贝叶斯模型
该系列来自于我<人工智能>课程回顾总结,以及实验的一部分进行了总结学习机 垃圾分类是有监督的学习分类最经典的案例,本文首先回顾了概率论的基本知识.则以及朴素贝叶斯模型的思想.最后给出了垃圾 ...
- PGM:贝叶斯网表示之朴素贝叶斯模型naive Bayes
http://blog.csdn.net/pipisorry/article/details/52469064 独立性质的利用 条件参数化和条件独立性假设被结合在一起,目的是对高维概率分布产生非常紧凑 ...
- 统计学习1:朴素贝叶斯模型(Numpy实现)
模型 生成模型介绍 我们定义样本空间为\(\mathcal{X} \subseteq \mathbb{R}^n\),输出空间为\(\mathcal{Y} = \{c_1, c_2, ..., c_K\ ...
- 一步步教你轻松学朴素贝叶斯模型算法Sklearn深度篇3
一步步教你轻松学朴素贝叶斯深度篇3(白宁超 2018年9月4日14:18:14) 导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果.所以很受欢迎,对 ...
- Python机器学习算法 — 朴素贝叶斯算法(Naive Bayes)
朴素贝叶斯算法 -- 简介 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法.最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Baye ...
- Python实现nb(朴素贝叶斯)
Python实现nb(朴素贝叶斯) 运行环境 Pyhton3 numpy科学计算模块 计算过程 st=>start: 开始 op1=>operation: 读入数据 op2=>ope ...
- 利用朴素贝叶斯算法进行分类-Java代码实现
http://www.crocro.cn/post/286.html 利用朴素贝叶斯算法进行分类-Java代码实现 鳄鱼 3个月前 (12-14) 分类:机器学习 阅读(44) 评论(0) ...
- 第十三次作业——回归模型与房价预测&第十一次作业——sklearn中朴素贝叶斯模型及其应用&第七次作业——numpy统计分布显示
第十三次作业——回归模型与房价预测 1. 导入boston房价数据集 2. 一元线性回归模型,建立一个变量与房价之间的预测模型,并图形化显示. 3. 多元线性回归模型,建立13个变量与房价之间的预测模 ...
- 11.sklearn中的朴素贝叶斯模型及其应用
#1.使用朴素贝叶斯模型对iris数据集进行花分类 #尝试使用3种不同类型的朴素贝叶斯: #高斯分布型,多项式型,伯努利型 from sklearn import datasets iris=data ...
随机推荐
- Jenkins 基础篇-凭证配置
我们在正式开始使用 Jenkins 之前还要先配置一些凭证,这些凭证的作用主要是用于认证,例如我们要从代码仓库拉取代码,就需要用到代码仓库的账号密码或者密钥:我们要登录远程服务器也需要用户名密码或者密 ...
- pytest + allure
参考allure官网: https://docs.qameta.io/allure/#_pytest 1.使用命令pip install pytest-allure安装 注意:我遇到的一个现象,用命令 ...
- 3层-CNN卷积神经网络预测MNIST数字
3层-CNN卷积神经网络预测MNIST数字 本文创建一个简单的三层卷积网络来预测 MNIST 数字.这个深层网络由两个带有 ReLU 和 maxpool 的卷积层以及两个全连接层组成. MNIST 由 ...
- CodeGen用户定义的扩展令牌
CodeGen用户定义的扩展令牌 用户定义的扩展令牌是一种特殊的令牌,开发人员可以确定令牌的名称以及在代码生成过程中遇到令牌时要插入的值. CodeGen支持多种机制,允许通过以下方式实现用户定义的令 ...
- H.265 HD 和H.265 4K Video Encoder IP Core
H.265 HD 和H.265 4K Video Encoder IP Core H.265 HD Video Encoder IP Core(H.265 HD Video/Audio Encoder ...
- Java设计模式(4:里氏替换原则和合成复用原则详解
一.里氏替换原则 如果说实现开闭原则的关键步骤就是抽象化,那么基类(父类)和子类的继承关系就是抽象化的具体实现,所以里氏替换原则就是对实现抽象化的具体步骤的规范.即:子类可以扩展基类(父类)的功能,但 ...
- 1482. 制作 m 束花所需的最少天数
2021-05-09 LeetCode每日一题 链接:https://leetcode-cn.com/problems/minimum-number-of-days-to-make-m-bouquet ...
- Java抽象类、继承及多态和适配器的实现
Java继承 方法重写是Java语言多态的特性,必须满足以下条件 在子类中,方法名称与父类方法名称完全相同 方法的参数个数和类型完全相同,返回类型完全相同 方法的访问修饰符访问级别不低于父类同名方法的 ...
- UF_CSYS 坐标系操作
Open C UF_CSYS_ask_csys_info 获取WCS坐标系的原点坐标和矩阵标识UF_CSYS_ask_matrix_of_object 获得对象 ...
- NX二次开发-获取WCS标识
函数:UF_CSYS_ask_wcs() 函数说明:获取工作坐标系对象的标识. 用法: 1 #include <uf.h> 2 #include <uf_csys.h> 3 e ...