朴素贝叶斯分类理解

步奏一:准备数据集

  • 收集数据:收集与分类任务相关的数据集,比如网安数据,日常生活对话数据
  • 数据标注:以我的aichat bot为例,为网安的数据标注为”1”,日常生活数据标注为”0”
  • 划分数据(可选):将数据集划分为训练集和测试集。训练集用于训练模型,测试集用于评估模型的性能。通常,可按照 70% - 30% 或 80% - 20% 的比例进行划分。

步奏二:获取离散矩阵

  • 即使用如:
    self.vectorizer = CountVectorizer():创建一个 CountVectorizer 对象,用于将文本数据转换为数值特征矩阵。
  • 关于这个数字序列矩阵:
    X = self.vectorizer.fit_transform(texts)
    fit_transform CountVectorizer 的一个方法,它完成两个操作:
    fit:统计文本中出现的所有词语,构建词汇表。
    简单地说:就是使用fit方法求得该文本每个词语的TF-IDF权重,
    然后,采取transformer将每个句子的每个词语的权重标注出来,获得以下的离散矩阵:
1
2
3
4
5
6
7
8
9
t_matrix: 
array([[0.70710678, 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0.70710678],
[0. , 0. , 0.57735027, 0. , 0.57735027,
0. , 0.57735027, 0. , 0. , 0. ],
[0. , 0. , 0. , 0.70710678, 0. ,
0. , 0. , 0.70710678, 0. , 0. ],
[0. , 0.57735027, 0. , 0. , 0. ,
0.57735027, 0. , 0. , 0.57735027, 0. ]])

transform:将每个文本转换为一个向量,向量的每个元素表示对应词语在该文本中出现的次数。最终得到的 X 是一个稀疏矩阵,每一行代表一个文本,每一列代表一个词语。如上图。
最后获得数字矩阵X每一行代表一个文本,每一列代表一个词语。

步奏三:训练方法

  • 首先初始化方法:
    self.clf = MultinomialNB():创建一个 MultinomialNB 对象,即多项式朴素贝叶斯分类器。
  • 这里展开说说skleran库的几种贝叶斯算法:

各类的特点和适用场景

1. naive_bayes.BernoulliNB

  • 特点
    • 基于伯努利分布,适用于特征为二元(布尔)变量的情况,即特征只有两种取值,通常表示为 0 和 1。
    • 在文档分类中,它只关注单词是否出现,而不考虑单词出现的次数。
    • 假设特征之间相互独立,这是朴素贝叶斯算法的基本假设。
  • 适用场景
    • 文本分类任务中,当只关心某个特征词是否在文档中出现,而不关心其出现频率时,例如垃圾邮件过滤,只需要知道某些特定的关键词是否在邮件中出现来判断是否为垃圾邮件。
    • 特征是二元属性的场景,如疾病诊断中某些症状是否存在。

2. naive_bayes.CategoricalNB

  • 特点
    • 用于处理分类特征,即特征可以取多个离散值。
    • 它会根据每个特征的不同取值来计算条件概率。
  • 适用场景
    • 当特征是分类变量时适用,例如在预测天气时,特征可以是“晴天”“多云”“雨天”等分类值;在用户行为分析中,特征可以是用户的不同操作类型(点击、浏览、购买等)。

3. naive_bayes.GaussianNB

  • 特点
    • 假设特征变量服从高斯(正态)分布。
    • 对于连续型特征,它通过计算特征的均值和方差来估计条件概率。
  • 适用场景
    • 当特征是连续型数值且大致符合正态分布时使用,例如人的身高、体重、血压等生理指标;在金融领域,股票价格的波动等连续型数据也可能符合高斯分布。

4. naive_bayes.MultinomialNB

  • 特点
    • 适用于特征变量是离散型数据且符合多项分布的情况。
    • 在文档分类中,它考虑单词出现的次数或 TF - IDF 值等,比伯努利朴素贝叶斯更能捕捉文本的细节信息。
  • 适用场景
    • 文本分类任务,如新闻分类、情感分析等,通过统计单词出现的频率或 TF - IDF 值来判断文档的类别。
    • 计数数据的分类问题,例如商品销售记录中不同商品的销售数量。

5. naive_bayes.ComplementNB

  • 特点
    • 是多项式朴素贝叶斯的改进版本,尤其适用于处理不平衡数据集。
    • 它通过考虑每个类别的“反类”信息来进行分类,减少了对训练集中主导类别的依赖。
  • 适用场景
    • 当数据集存在类别不平衡问题时,如在医疗诊断中,患病样本和健康样本数量差异较大;在网络安全检测中,正常流量和攻击流量的比例不均衡。

在 AI 聊天机器人中分类用户问题的选择

如果想在 AI 聊天机器人中把用户的问题分成网安问题和寒暄问题,建议使用 naive_bayes.MultinomialNB,原因如下:

  • 文本信息利用充分:用户的问题是以文本形式呈现的,MultinomialNB 可以考虑单词出现的次数或 TF - IDF 值等,能够更全面地利用文本中的信息。例如,在网安问题中,可能会多次出现“黑客”“漏洞”等关键词,这些关键词的出现频率对于分类有重要意义,而 MultinomialNB 可以很好地捕捉到这些信息。
  • 适合文本分类场景:文本分类是 MultinomialNB 的典型应用场景,它在处理这种基于文本特征进行分类的任务上有较好的表现。相比之下,BernoulliNB 只考虑单词是否出现,会丢失部分信息;GaussianNB 适用于连续型数据,不符合文本特征的离散性质;CategoricalNB 更侧重于处理分类特征,对于文本中丰富的词汇信息利用不够充分;ComplementNB 主要用于处理类别不平衡问题,如果数据集没有明显的类别不平衡,MultinomialNB 通常是更好的选择。
  • 然后self.clf.fit(X, labels):使用特征矩阵 X 和对应的类别标签 labels 对分类器进行训练。

步奏四: 保存模型方法 save_model

1
2
3
4
5
6
7
8
def save_model(self, model_path='naive_bayes_model.pkl', vectorizer_path='count_vectorizer.pkl'):
"""
保存训练好的模型和特征提取器
:param model_path: 模型保存路径
:param vectorizer_path: 特征提取器保存路径
"""
joblib.dump(self.clf, model_path)
joblib.dump(self.vectorizer, vectorizer_path)

save_model 方法用于将训练好的分类器和特征提取器保存到指定的文件中。
joblib.dump(self.clf, model_path):将分类器 self.clf 保存到 model_path 指定的文件中。
joblib.dump(self.vectorizer, vectorizer_path):将特征提取器 self.vectorizer 保存到 vectorizer_path 指定的文件中。

步奏五: 加载模型方法 load_model

1
2
3
4
5
6
7
8
def load_model(self, model_path='naive_bayes_model.pkl', vectorizer_path='count_vectorizer.pkl'):
"""
加载训练好的模型和特征提取器
:param model_path: 模型保存路径
:param vectorizer_path: 特征提取器保存路径
"""
self.clf = joblib.load(model_path)
self.vectorizer = joblib.load(vectorizer_path)

load_model 方法用于从指定的文件中加载之前保存的分类器和特征提取器。
self.clf = joblib.load(model_path):从 model_path 指定的文件中加载分类器并赋值给 self.clf
self.vectorizer = joblib.load(vectorizer_path):从 vectorizer_path 指定的文件中加载特征提取器并赋值给 self.vectorizer

步奏六:分类方法 classify_question

1
2
3
4
5
6
7
8
9
10
11
def classify_question(self, question):
"""
对问题进行分类
:param question: 待分类的问题文本
:return: 分类结果,0 表示寒暄日常问题,1 表示网络安全类问题
"""
if self.clf is None or self.vectorizer is None:
raise ValueError("模型或特征提取器未加载,请先调用 load_model 方法。")
question_vector = self.vectorizer.transform([question])
predicted_label = self.clf.predict(question_vector)[0]
return predicted_label

classify_question 方法用于对输入的问题文本进行分类。
if self.clf is None or self.vectorizer is None::检查分类器和特征提取器是否已经加载,如果没有加载则抛出 ValueError 异常。
question_vector = self.vectorizer.transform([question]):使用已加载的特征提取器将输入的问题文本转换为向量表示。
predicted_label = self.clf.predict(question_vector)[0]:使用已加载的分类器对转换后的向量进行预测,得到分类结果。predict 方法返回一个包含预测标签的数组,这里取第一个元素作为最终的分类结果。
return predicted_label:返回分类结果。
综上所述,这个类提供了一个完整的基于朴素贝叶斯算法的文本分类解决方案,包括训练、保存、加载和分类功能。

关于朴素贝叶斯分类器性能提升的经验

使用SMOTE方法

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from imblearn.over_sampling import SMOTE

#......(对数据集的处理略)

self.vectorizer = TfidfVectorizer(stop_words = stop_words_list)
self.sm = SMOTE()
self.clf = MultinomialNB()

# 训练模型以及使用SMOTE过采样
def train(self, train_data):
clean_text = []
texts, labels = zip(*train_data)
for text in texts:
text = jieba.lcut(re.sub(r'[^\w\s]', '', text).lower().strip())
text = [element for element in text if element != '\t']#筛除\t
clean_text.append(text)
new_list = [" ".join(sublist) for sublist in clean_text]
X = self.vectorizer.fit_transform(new_list)
Y =labels = np.array(labels)
X_res, Y_res = self.sm.fit_resample(X, Y)
self.clf.fit(X_res, Y_res)
  • 如上,我们为了使不同类型数据集数量趋于平衡,可以使用imblearn库中的SOMTE类对数据处理(这里是指整体数据)
  • 对于使用数字的标签,如0,1,请使用numpy库中np.array方法转化输入数据.
  • 再将原数据中的XY列表传入SMOTE类中fit_resample方法进行处理后输出.
  • 最后传入MultinomiaNB库中训练,获得最终训练数据

有关数据的处理

实例代码:

1
2
3
4
5
6
7
8
9
10
def train(self, train_data):
clean_text = []
texts, labels = zip(*train_data)
for text in texts:
text = jieba.lcut(re.sub(r'[^\w\s]', '', text).lower().strip())
text = [element for element in text if element != '\t']#筛除\t
# print(f"这是刚分词后的数据:{text}")
clean_text.append(text)
new_list = [" ".join(sublist) for sublist in clean_text]
X = self.vectorizer.fit_transform(new_list)
  • 数据集如下:
1
2
3
4
data = [ ('什么是ai	人工智能是工程和科学的分支,致力于构建思维的机器。',0),
('你写的是什么语言 蟒蛇',0),
('你听起来像数据 是的,我受到指挥官数据的人工个性的启发',0),
('你是一个人工语言实体 那是我的名字。',0)]
  • 首先明确,输入self.vectorizer.fit_transformer方法内的数据应该为由多个句子组成的数组
  • TfidiVectorizer对每个元素的默认处理方法是以空格为分割线,将每个元素内的句子分割开来.并且计算每个元素内词语的TF-TDF权重以及再继续组成矩阵,所以,若是直接传入如下数据集:
1
["你是什么东西 我是智能聊天机器人","今天天气如何 还好"]
  • 会导致TfidfVectorizer.fit_transformer方法直接把一句话认定为一个词语并进行数据计算,这会导致重大的数据分析错误
  • 解决方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#对传入数据进行预处理
#首先,使用zip解包的数据,如texts,是以元组的形式保存第一个位置元素的
"""
比如:
[ ('什么是ai 人工智能是工程和科学的分支,致力于构建思维的机器。',0),
('你写的是什么语言 蟒蛇',0),
('你听起来像数据 是的,我受到指挥官数据的人工个性的启发',0),
('你是一个人工语言实体 那是我的名字。',0)]
"""
#texts解包后就为:
#("什么是ai\t人工智能是工程和科学的分支,致力于构建思维的机器。","xx","xx")
#所以我们需要对其进行处理:
def train(self, train_data):
clean_text = []
texts, labels = zip(*train_data)
for text in texts:#对于元组里的每个元素,即每句话
text = jieba.lcut(re.sub(r'[^\w\s]', '', text).lower().strip())#使用jieba.lcut方法进行中文分词
text = [element for element in text if element != '\t']#筛除\t
clean_text.append(text)
#这里clean_text会表现为一个二维列表,列表中元素也是列表
new_list = [" ".join(sublist) for sublist in clean_text]
#最后一步:对于列表中的列表,即sublist,可以使用" ".join方法将sublist其中元素连接起来
#该方法也会打开列表括号使列表原来的"[]"变为左右引号,这样我们就实现了目标的转化:
#生成一个以空格已经分好中文词语的句子为元素的列表供TfidiVectorizer读取
  • 注意,sklearn中类方法读取都是使用列表数据