朴素贝叶斯算法:
一、原理简介
朴素贝叶斯分类器是生成学习算法,是最简单的有向概率图模型。
假设:特征条件独立性假设
对已知类别,假设所有属性相互独立。
1.1 原理
原理
1.2 期望风险最小化
期望风险最小化
1.3 算法
算法
1.4 贝叶斯估计
贝叶斯估计
二、算法流程
1. 对数据进行类别编码及单词编码,同时划分训练集和测试集;
2. 统计频次(2种方式:多项式和伯努利+拉普拉斯平滑),得到先验概率和类条件概率;
3.
三、代码实例
1. 数据
链接:https://pan.baidu.com/s/1X5FtrhhhCzlYC1-Y1jIPfQ
提取码:a9oh
新闻数据的一部分,只为测试代码用。
2. python实现
2.1 数据转换
convert_data函数:实现类别编码及单词编码,同时划分训练集和测试集,并保存。
数据格式:类别标签+单词编码+“#”+文章名,每篇文章一行,每行的元素空格分隔
import os, random, sys
# 测试数据路径
file_path = 'E:/Pycharm/badou/Data/NewsData/test_newsdata'
# 新闻数据
# file_path = 'E:/Pycharm/badou/Data/NewsData/all_NewsData'
# 训练集和测试集的输出文件路径
TrainOutFilePath = './NB_mid_data/train.data'
TestOutFilePath = './NB_mid_data/test.data'
# 划分数据集的比例,0.8为训练
TrainPercent = 0.8
# 打开文件,往里面写数据
train_out_file = open(TrainOutFilePath, 'w', encoding='utf-8')
test_out_file = open(TestOutFilePath, 'w', encoding='utf-8')
# 类别定义字典进行编码
label_dict = {'business': 0, 'yule': 1, 'it': 2, 'sports': 3, 'auto': 4}
word_encoding = dict() # {词: 编码}
word_list = [] # 根据len(wordList)作为新加入词的编码
def convert_data():
i = 0
tag = 0
for filename in os.listdir(file_path):
print(filename)
# 更新对应文件的类别标签变量tag
if filename.find('business') != -1:
tag = label_dict['business']
elif filename.find('yule') != -1:
tag = label_dict['yule']
elif filename.find('it') != -1:
tag = label_dict['it']
elif filename.find('sports') != -1:
tag = label_dict['sports']
else:
tag = label_dict['auto']
i += 1
rd = random.random()
# 默认输出为测试集,最终0.8-1部分属于test
outfile = test_out_file
if rd < TrainPercent:
outfile = train_out_file
if i % 100 == 0:
print('{0} files processed!'.format(i))
# 逐个打开文件,
infile = open(os.path.join(file_path, filename), 'r', encoding='utf-8')
outfile.write(str(tag) + ' ')
content = infile.read().strip()
words = content.replace('n', ' ').split(' ')
# print(content)
# print(words)
for word in words:
if len(word.strip()) < 1:
continue
# 当词不在字典中时,将词加入列表中,并将词进行编码
if word not in word_encoding:
word_list.append(word)
word_encoding[word] = len(word_list)
outfile.write(str(word_encoding[word]) + ' ')
# print(word_encoding)
outfile.write('#' + filename + 'n')
infile.close()
print(i, 'files loaded!')
print('{0} unique words found!'.format(len(word_list)))
if __name__ == '__main__':
# train和test数据结构:
# 每行一篇文章 tag + 文章出现的单词编码 + #文章名全称
convert_data()
train_out_file.close()
test_out_file.close()
2.2 加载数据
nb_load_data函数:加载2.1中的训练集数据。
nb_compute_model函数:将统计的频次转化成概率。
nb_savemodel函数:存储模型。
import math
# 训练数据和测试数据路径
train_out_file = 'E:/Pycharm/badou/NaiveBayes/NB_mid_data/train.data'
test_out_file = 'E:/Pycharm/badou/NaiveBayes/NB_mid_data/test.data'
# 模型存储路径
NaiveBayes_modelfile = 'E:/Pycharm/badou/NaiveBayes/NB_mid_data/NB_model'
# 初始化参数
DefaultFreq = 0.1
ClassDefaultProb = {}
ClassFeatDic = {} # x_j和y_i的矩阵 count
ClassFeatProb = dict() # 概率
ClassFreq = dict() # y_i的数组 count
ClassProb = dict()
word_dict = {} # 词编码
# def Dedup(items):
# tmp_dic = {}
# for item in items:
# if item not in tmp_dic:
# tmp_dic[item] = True
# return tmp_dic.keys()
def nb_load_data():
i = 0
infile = open(train_out_file, 'r', encoding='utf-8')
# 逐行single line读取
sline = infile.readline().strip()
while len(sline) > 0:
# 如果在不写文件名数据时,这段可以忽略不要
pos = sline.find('#')
if pos > 0:
# 只取标签和单词编码内容
sline = sline[:pos].strip()
words = sline.split(' ')
if len(words) < 1:
print('Format error!')
classID = int(words[0])
if classID not in ClassFeatDic:
ClassFeatDic[classID] = dict()
ClassFeatProb[classID] = dict()
ClassFreq[classID] = 0
ClassFreq[classID] += 1
words = words[1:]
# binary distribution,remove duplicate words
# words = Dedup(words)
# multiple distribution
for wid in words:
if len(wid) < 1:
continue
word_id = int(wid)
if word_id not in word_dict:
word_dict[word_id] = 1
# word_id不在矩阵中,初始化为1,在的话频次统计+1
if word_id not in ClassFeatDic[classID]:
ClassFeatDic[classID][word_id] = 1
else:
ClassFeatDic[classID][word_id] += 1
i += 1
# 本篇文章读取完毕,读取下一篇文章
sline = infile.readline().strip()
infile.close()
print(i, "instances loaded!")
print(len(ClassFreq), 'classes!', len(word_dict), 'words!')
print('{class: {feat_encoding: count}}', ClassFeatDic)
print('{class: {feat_encoding: prob}}', ClassFeatProb)
"""
nb_compute_model函数:将原有count做成概率
"""
def nb_compute_model():
sum = 0.0 # 统计yi
for freq in ClassFreq.values():
sum += freq
for classid in ClassFreq.keys():
ClassProb[classid] = float(ClassFreq[classid]) / sum
for classid in ClassFeatDic.keys():
class_sum = 0.0 # 此类别单词计数
for word_id in ClassFeatDic[classid].keys():
class_sum += ClassFeatDic[classid][word_id]
# 平滑处理 (+1平滑)
# class_sum是当前这个类别classid中,所有单词出现的总数量
# len(word_dict)词典大小,表示整个语料中单词的种类数(不一样的词的数量)
# 分母变大(3,7)=>(0.3, 0.7), (3/12,7/12,2/12)
# 新的一篇文章中,可能出现语料库中未出现的词
new_sum = float(class_sum + len(word_dict)) * DefaultFreq
for wid in ClassFeatDic[classid].keys():
ClassFeatProb[classid][wid] = float(ClassFeatDic[classid][wid] + DefaultFreq) / new_sum
ClassDefaultProb[classid] = float(DefaultFreq) / new_sum
print('{class: {feat_encoding: prob}}:', ClassFeatProb)
# 存储模型
def nb_savemodel():
outfile = open(NaiveBayes_modelfile, 'w', encoding='utf-8')
for classid in ClassFreq.keys():
outfile.write(str(classid) + ' ' + str(ClassProb[classid]) + ' ' + str(ClassDefaultProb[classid]))
outfile.write('n')
# 每行一篇文章,单词空格分开,每篇文章换行标识
for classid in ClassFeatDic.keys():
for word_id in ClassFeatDic[classid].keys():
outfile.write(str(word_id) + str(ClassFeatDic[classid][word_id]))
outfile.write(' ')
outfile.write('n')
outfile.close()
2.3 加载模型
# 加载模型p(yi)和p(xj|yi)
def nb_loadmodel():
global word_dict
word_dict = {}
global ClassFeatProb
ClassFeatProb = {} # p(xj|yi)矩阵
global ClassDefaultProb
ClassDefaultProb = {} # 默认概率
global ClassProb
ClassProb = {} # p(yi)
infile = open(NaiveBayes_modelfile, 'r', encoding='utf-8')
sline = infile.readline().strip()
items = sline.split(' ')
if len(items) < 3 * 5:
print('Model format error!')
i = 0
while i < len(items):
classid = int(items[i])
ClassFeatProb[classid] = {}
i += 1
if i >= len(items):
print('Model format error!')
return
ClassProb[classid] = float(items[i])
i += 1
ClassDefaultProb[classid] = float(items[i])
i += 1
for classid in ClassProb.keys():
sline = infile.readline().strip()
items = sline.split(' ')
i = 0
while i < len(items):
wid = int(items[i])
if wid not in word_dict:
word_dict[wid] = 1
i += 1
ClassFeatProb[classid][wid] = float(items[i])
i += 1
infile.close()
print('{0} classes! {1} words!'.foramt(len(ClassProb), len(word_dict)))
2.4 模型预测&模型评估
nb_predict函数:模型预测。
nb_evaluate函数:模型评估,此处评估指标较单一,可自行尝试多种评估指标。
# 模型预测
def nb_predict():
global word_dict
global ClassFeatProb
global ClassDefaultProb
global ClassProb
true_label_list = []
pred_label_list = []
infile = open(test_out_file, 'r', encoding='utf-8')
sline = infile.readline().strip()
scoreDic = {}
iline = 0
while len(sline)>0:
iline += 1
if iline % 10 ==0:
print('{0} lines finished!'.format(iline))
position = sline.find('#')
if position > 0:
sline = sline[:position].strip()
words = sline.split()
classid = int(words[0])
true_label_list.append(classid)
words = words[1:]
# 取先验概率
for classid in ClassProb.keys():
# 计算这篇文章对应所有类别的概率都要计算
scoreDic[classid] = math.log(ClassProb[classid])
# 不在字典中的词丢掉
for word in words:
if len(word) <1:
continue
wid = int(word)
if wid not in word_dict:
continue
for classid in ClassProb.keys():
# 字典中没有出现过该类别中出现的词时,用该类别的默认概率参与计算
if wid not in ClassFeatProb[classid]:
scoreDic[classid] += math.log(ClassDefaultProb[classid])
else:
scoreDic[classid] += math.log(ClassFeatProb[classid][wid])
# 概率最大的类别作为预测类别放入pred_label_list中
maxProb = max(scoreDic.values())
for classid in scoreDic.keys():
if scoreDic[classid] == maxProb:
pred_label_list.append(classid)
sline = infile.readline().strip()
infile.close()
print(len(pred_label_list), len(true_label_list))
return true_label_list, pred_label_list
# 模型评估
def nb_evaluate(true_list, pred_list):
accuracy = 0
i = 0
while i < len(true_list):
if pred_list[i] == true_list[i]:
accuracy += 1
i += 1
accuracy = float(accuracy) / float(len(true_list))
print('accuracy is {0}'.format(accuracy))
if __name__ == '__main__':
nb_load_data()
nb_compute_model()
nb_savemodel()
true_list, pred_list = nb_predict()
nb_evaluate(true_list, pred_list)
四.适用场景
1. 广告投放,根据不同人群进行定价;
2. 新闻分类,query分类,商品分类,网页分类,垃圾邮件过滤,网页排序......
五、优缺点
优点:1. 性能相当好,速度快,可以避免维度灾难;
2. 支持大规模数据的并行学习,且天然的支持增量学习。
缺点:1. 条件独立性假设有时候并不合理;
2. 无法给出分类概率,难以应用于需要分类概率的场景。
六、补充说明
1. 特征条件独立性假设存在的意义
如果没有特征条件独立性假设,条件概率是所有属性上的联合概率,难以从有限的训练样本直接估计而得。
基于有限训练样本直接估计联合概率,在计算上将遭遇组合爆炸问题,在数据上遭遇样本稀疏问题;属性越多,问题越严重。
2. 频次统计方式
1)多项式,有一个单词在一篇文章中出现10次,这个单词的频次是10。
2)伯努利,有一个单词在一篇文章中出现10次,这个单词的频次是1。
实践中可以两种方式都试试。
3. 拉普拉斯平滑
拉普拉斯平滑避免了因训练样本不充分而导致概率估值为零的问题,修正过程所引入的先验的影响也会逐渐变得可忽略,使得估值渐趋向于实际概率值。
拉普拉斯平滑,在两个方面,一个是先验概率,一个是类条件概率,分子通常+1,先验概率的原始分母+数据集的类别种类数,类条件概率的原始分母+该属性特征可能的取值个数。
4. 模型提升空间
1) 语料大小、根据停用词表去除停用词、切词的大小、根据tf(或idf)进行筛选(剔除高频出现的词,阈值可尝试调整)。
2) 正负样本比例不均衡,对负样本进行重采样,或对负样本进行加权,或调整评估/损失函数。
5. 增量学习&在线学习