先了解一下关于朴素贝叶斯的几个小问题:
贝叶斯公式是怎么来的?
首先我们回顾一下什么是条件概率:, 意思是在 B 发生的条件下 A 发生的概率,就是下图中 AB 重叠的部分。
若有 n 个条件 B,已知在各个条件 B 发生的情况下 A 发生的概率,要求在这些条件下 A 发生的概率,则要用到 全概率公式:
若反向思考,已知 B 的概率和 A 的概率,也已知在条件 B 发生的情况下 A 发生的概率,由此可推出 贝叶斯公式:
朴素贝叶斯为什么“朴素naive”?
朴素贝叶斯(Navie Bayesian)中的“朴素”可以理解为是“简单、理想化”。因为朴素贝叶斯假设分类特征在类确定的条件下都是相互独立的,即满足条件独立假设。这个假设在实际情况下是很难满足的,使朴素贝叶斯法变得简单,有时会牺牲一定的分类准确率。
朴素贝叶斯的工作流程是怎样的?
- 准备阶段:
确定特征属性,并对每个特征属性进行适当划分,去除高度相关的属性,然后由人工对一部分待分类项进行分类,形成训练样本集合。这一阶段的输入是所有待分类数据,输出是特征属性和训练样本。 - 分类器训练阶段:
计算每个类别在训练样本中的出现频率及每个特征属性划分对每个分类别的条件概率估计,并将结果记录。 - 应用阶段:
使用分类器对待分类项进行分类,其输入是分类器和待分类项,输出是待分类项与类别的映射关系,并选出概率值最高所对应的类别。
动手练习
用sklearn
中的贝叶斯分类器来进行文档分类,文档包括训练数据和测试数据,一共分为4类。
读取文件的函数,这里用到jieba
分词工具将文档中的词提取出来:
import os
import jieba
import warnings
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics
warnings.filterwarnings('ignore')
# 对文本词进行切割,将文本中的词用jieba分词后用空格拼接起来
def cut_words(file_path):
words_with_spaces = ''
text = open(file_path, 'r', encoding='gb18030').read()
text_cut = jieba.cut(text)
for word in text_cut:
words_with_spaces += word + ' '
return words_with_spaces
# 读取目录下的文本文件, 将文本文件和label标记为可训练的数据
def load_file(file_dir, label):
file_list = os.listdir(file_dir)
words_list = []
labels_list = []
for file in file_list:
file_path = file_dir + '/' + file
words_list.append(cut_words(file_path))
labels_list.append(label)
return words_list, labels_list
训练数据和测试数据分别放在train
、test
文件夹中,需要用上面的函数读取出来用以训练:
# 训练数据
train_base_dir = 'text classification/train/'
test_base_dir = 'text classification/test/'
text_labels = ['女性', '体育', '文学', '校园']
label_num = len(text_labels)
# 读取数据
def load_data(base_dir):
data = []
labels = []
for i in range(label_num):
word_list, file_label = load_file(base_dir+text_labels[i], text_labels[i])
data += word_list
labels += file_label
return data, labels
# 读取训练数据
train_data, train_labels = load_data(train_base_dir)
# 读取测试数据
test_data, test_labels = load_data(test_base_dir)
还有停用词文件,以免无意义的词影响词频分析:
# 读取停用词
stop_words_path = 'text classification/stop/stopword.txt'
stop_words = open(stop_words_path, 'r', encoding='utf-8').read()
stop_words = stop_words.encode('utf-8').decode('utf-8-sig')
stop_words = stop_words.split('\n')
计算TF-IDF值
什么是 TF-IDF 值呢?
TF-IDF 值是一个统计方法,用以估计某个词语对于文件集或文档库中的其中一个文件的重要程度。
TF 是指 Term Frequency ,单词在该文档中的词频:
其中,表示第 j 篇文本中第 i 个词的 TF 值,表示第 j 篇文本中第 i 个词的出现频次, 则表示第 j 篇文本中所有词的出现频次,k 表示第 j 篇文本中的词汇量。
IDF 指 Inverse Document Frequency,指单词在整个文档库中的逆词频:
其中,表示第 j 篇文本中第 i 个词的 IDF 值, 表示数据库中文本的总数,则表示数据库中含词的文本数量,为了防止该词语在语料库中不存在分母为 0 的情况,取作为分母,取对数是为了防止过小而不方便计算。
由上两个公式可以看出来,
- TF 值能体现出词 i 在文本 j 中的重要程度,TF 值越高则该词在文档中越重要
- IDF 值则体现了词 i 在文本库中的区分度,一个词出现的文档数越少越能把该文档和其他文档区分开来,即 IDF 值越大区分度越高
TF-IDF 值为:
因此 TF-IDF 值越高越能将文档分类。
可以sklearn
中的TfidfVectorizer
来计算每个单词在每个文档中的 TF-IDF 值,其结果为每个文档中各个词的 TF-IDF 值矩阵。
由于文本的表示向量维度等同于词典的长度,因此结果将是个系数矩阵。
# 用TF-IDF计算单词的权重
tf = TfidfVectorizer(stop_words = stop_words, max_df = 0.5)
train_features = tf.fit_transform(train_data)
test_features = tf.transform(test_data)
参数 max_df 表示选取词的最大 DF 值,这个很好理解,若一个词的 DF 值很大,说明这个词很常见,对于文档分类的参考价值也就不大了,这里取 0.5
使用贝叶斯分类
在了解了 TF-IDF 的原理后,我们了解到 TF-IDF 矩阵是个稀疏矩阵,而贝叶斯模型的极大似然估计法会求概率值的乘积,若一个词的概率被计算为 0 则会影响整体的结果。
为了解决这样的问题,我们需要做平滑处理。
其中参数 alpha
为平滑参数,
- alpha = 1 时为 Laplace 平滑,就是采用加 1 的方式来统计没有出现过的单词概率
- 0 < alpha < 1 时为 Lidstone 平滑,Lidstone 平滑为 Laplace 的一般项,alpha 值越小迭代次数越多,精度越高
使用多项式贝叶斯分类器:
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB(alpha=0.0005).fit(train_features, train_labels)
pred_labels = clf.predict(test_features)
# accuracy
print('accuracy: ', metrics.accuracy_score(test_labels, pred_labels))
最后准确率为93%。
accuracy: 0.93
END