机器学习算法实践:朴素贝叶斯 (Naive Bayes)

简介:

机器学习算法实践:朴素贝叶斯 (Naive Bayes)


前言

上一篇《机器学习算法实践:决策树 (Decision Tree)》总结了决策树的实现,本文中我将一步步实现一个朴素贝叶斯分类器,并采用SMS垃圾短信语料库中的数据进行模型训练,对垃圾短信进行过滤,在最后对分类的错误率进行了计算。

正文

与决策树分类和k近邻分类算法不同,贝叶斯分类主要借助概率论的知识来通过比较提供的数据属于每个类型的条件概率, 将他们分别计算出来然后预测具有最大条件概率的那个类别是最后的类别。当然样本越多我们统计的不同类型的特征值分布就越准确,使用此分布进行预测则会更加准确。

贝叶斯准则

朴素贝叶斯分类器中最核心的便是贝叶斯准则,他用如下的公式表示:

此公式表示两个互换的条件概率之间的关系,他们通过联合概率关联起来,这样使得我们知道p(B|A) 的情况下去计算p(A|B) 成为了可能,而我们的贝叶斯模型便是通过贝叶斯准则去计算某个样本在不同类别条件下的条件概率并取具有最大条件概率的那个类型作为分类的预测结果。

使用条件概率来进行分类

这里我通俗的介绍下如何通过条件概率来进行分类,假设我们看到了一个人的背影,想通过他背影的一些特征(数据)来判断这个人的性别(类别),假设其中涉及到的特征有: 是否是长发, 身高是否在170以上,腿细,是否穿裙子。当我们看到一个背影便会得到一个特征向量用来描述上述特征(1表示是,0表示否): ω=[0,1,1,0]

贝叶斯分类便是比较如下两个条件概率:

  • p(男生|ω) ,ω 等于 [0,1,1,0 的条件下此人是男生的概率
  • p(女生|ω)) ,ω 等于 [0,1,1,0] 的条件下此人是女生的概率

若p(男生|ω)>p(女生|ω) , 则判定此人为男生, 否则为女生

那么p(男生|ω) 怎么求呢? 这就要上贝叶斯准则了

根据贝叶斯准则,

写成好理解些的便是:

如果特征之间都是相互独立的(条件独立性假设),那么便可以将上述条件概率改写成:

这样我们就能计算当前这个背影属于男生和属于女生的条件概率了。

实现自己的贝叶斯分类器

贝叶斯分类器实现起来非常的简单, 下面我以进行文本分类为目的使用Python实现一个朴素贝叶斯文本分类器.

为了计算条件概率,我们需要计算各个特征的在不同类别下的条件概率以及类型的边际概率,这就需要我们通过大量的训练数据进行统计获取近似值了,这也就是我们训练我们朴素贝叶斯模型的过程.

针对不同的文本,我们可以将所有出现的单词作为数据特征向量,统计每个文本中出现词条的数目(或者是否出现某个词条)作为数据向量。这样一个文本就可以处理成一个整数列表,并且长度是所有词条的数目,这个向量也许会很长,用于本文的数据集中的短信词条大概一共3000多个单词。

 
  1. def get_doc_vector(words, vocabulary): 
  2.  
  3.     ''' 根据词汇表将文档中的词条转换成文档向量 
  4.  
  5.   
  6.  
  7.     :param words: 文档中的词条列表 
  8.  
  9.     :type words: list of str 
  10.  
  11.   
  12.  
  13.     :param vocabulary: 总的词汇列表 
  14.  
  15.     :type vocabulary: list of str 
  16.  
  17.   
  18.  
  19.     :return doc_vect: 用于贝叶斯分析的文档向量 
  20.  
  21.     :type doc_vect: list of int 
  22.  
  23.     ''
  24.  
  25.     doc_vect = [0]*len(vocabulary) 
  26.  
  27.   
  28.  
  29.     for word in words: 
  30.  
  31.         if word in vocabulary: 
  32.  
  33.             idx = vocabulary.index(word) 
  34.  
  35.             doc_vect[idx] = 1 
  36.  
  37.   
  38.  
  39.     return doc_vect  

统计训练的过程的代码实现如下:

 
  1. def train(self, dataset, classes): 
  2.  
  3.     ''' 训练朴素贝叶斯模型 
  4.  
  5.   
  6.  
  7.     :param dataset: 所有的文档数据向量 
  8.  
  9.     :type dataset: MxN matrix containing all doc vectors. 
  10.  
  11.   
  12.  
  13.     :param classes: 所有文档的类型 
  14.  
  15.     :type classes: 1xN list 
  16.  
  17.   
  18.  
  19.     :return cond_probs: 训练得到的条件概率矩阵 
  20.  
  21.     :type cond_probs: dict 
  22.  
  23.   
  24.  
  25.     :return cls_probs: 各种类型的概率 
  26.  
  27.     :type cls_probs: dict 
  28.  
  29.     ''
  30.  
  31.     # 按照不同类型记性分类 
  32.  
  33.     sub_datasets = defaultdict(lambda: []) 
  34.  
  35.     cls_cnt = defaultdict(lambda: 0) 
  36.  
  37.   
  38.  
  39.     for doc_vect, cls in zip(dataset, classes): 
  40.  
  41.         sub_datasets[cls].append(doc_vect) 
  42.  
  43.         cls_cnt[cls] += 1 
  44.  
  45.   
  46.  
  47.     # 计算类型概率 
  48.  
  49.     cls_probs = {k: v/len(classes) for k, v in cls_cnt.items()} 
  50.  
  51.   
  52.  
  53.     # 计算不同类型的条件概率 
  54.  
  55.     cond_probs = {} 
  56.  
  57.     dataset = np.array(dataset) 
  58.  
  59.     for cls, sub_dataset in sub_datasets.items(): 
  60.  
  61.         sub_dataset = np.array(sub_dataset) 
  62.  
  63.         # Improve the classifier. 
  64.  
  65.         cond_prob_vect = np.log((np.sum(sub_dataset, axis=0) + 1)/(np.sum(dataset) + 2)) 
  66.  
  67.         cond_probs[cls] = cond_prob_vect 
  68.  
  69.   
  70.  
  71.     return cond_probs, cls_probs  

注意这里对于基本的条件概率直接相乘有两处改进:

  1. 各个特征的概率初始值为1,分母上统计的某一类型的样本总数的初始值是1,这是为了避免如果有一个特征统计的概率为0,则联合概率也为零那自然没有什么意义了, 如果训练样本足够大时,并不会对比较结果产生影响.
  2. 由于各个独立特征的概率都是小于1的数,累积起来必然会是个更小的书,这会遇到浮点数下溢的问题,因此在这里我们对所有的概率都取了对数处理,这样在保证不会有损失的情况下避免了下溢的问题。

获取了统计概率信息后,我们便可以通过贝叶斯准则预测我们数据的类型了,这里我并没有直接计算每种情况的概率,而是通过统计得到的向量与数据向量进行内积获取条件概率的相对值并进行相对比较做出决策的。

 
  1. def classify(self, doc_vect, cond_probs, cls_probs): 
  2.  
  3.     ''' 使用朴素贝叶斯将doc_vect进行分类. 
  4.  
  5.     ''
  6.  
  7.     pred_probs = {} 
  8.  
  9.     for cls, cls_prob in cls_probs.items(): 
  10.  
  11.         cond_prob_vect = cond_probs[cls] 
  12.  
  13.         pred_probs[cls] = np.sum(cond_prob_vect*doc_vect) + np.log(cls_prob) 
  14.  
  15.     return max(pred_probs, key=pred_probs.get)  

进行短信分类

已经构建好了朴素贝叶斯模型,我们就可以使用此模型来统计数据并用来预测了。这里我使用了SMS垃圾短信语料库中的垃圾短信数据, 并随机抽取90%的数据作为训练数据,剩下10%的数据作为测试数据来测试我们的贝叶斯模型预测的准确性。

当然在建立模型前我们需要将数据处理成模型能够处理的格式:

 
  1. ENCODING = 'ISO-8859-1' 
  2.  
  3. TRAIN_PERCENTAGE = 0.9 
  4.  
  5.   
  6.  
  7. def get_doc_vector(words, vocabulary): 
  8.  
  9.     ''' 根据词汇表将文档中的词条转换成文档向量 
  10.  
  11.   
  12.  
  13.     :param words: 文档中的词条列表 
  14.  
  15.     :type words: list of str 
  16.  
  17.   
  18.  
  19.     :param vocabulary: 总的词汇列表 
  20.  
  21.     :type vocabulary: list of str 
  22.  
  23.   
  24.  
  25.     :return doc_vect: 用于贝叶斯分析的文档向量 
  26.  
  27.     :type doc_vect: list of int 
  28.  
  29.     ''
  30.  
  31.     doc_vect = [0]*len(vocabulary) 
  32.  
  33.   
  34.  
  35.     for word in words: 
  36.  
  37.         if word in vocabulary: 
  38.  
  39.             idx = vocabulary.index(word) 
  40.  
  41.             doc_vect[idx] = 1 
  42.  
  43.   
  44.  
  45.     return doc_vect 
  46.  
  47.   
  48.  
  49. def parse_line(line): 
  50.  
  51.     ''' 解析数据集中的每一行返回词条向量和短信类型. 
  52.  
  53.     ''
  54.  
  55.     cls = line.split(',')[-1].strip() 
  56.  
  57.     content = ','.join(line.split(',')[: -1]) 
  58.  
  59.     word_vect = [word.lower() for word in re.split(r'\W+', content) if word] 
  60.  
  61.     return word_vect, cls 
  62.  
  63.   
  64.  
  65. def parse_file(filename): 
  66.  
  67.     ''' 解析文件中的数据 
  68.  
  69.     ''
  70.  
  71.     vocabulary, word_vects, classes = [], [], [] 
  72.  
  73.     with open(filename, 'r', encoding=ENCODING) as f: 
  74.  
  75.         for line in f: 
  76.  
  77.             if line: 
  78.  
  79.                 word_vect, cls = parse_line(line) 
  80.  
  81.                 vocabulary.extend(word_vect) 
  82.  
  83.                 word_vects.append(word_vect) 
  84.  
  85.                 classes.append(cls) 
  86.  
  87.     vocabulary = list(set(vocabulary)) 
  88.  
  89.   
  90.  
  91.     return vocabulary, word_vects, classes  

有了上面三个函数我们就可以直接将我们的文本转换成模型需要的数据向量,之后我们就可以划分数据集并将训练数据集给贝叶斯模型进行统计。

 
  1. # 训练数据 & 测试数据 
  2.  
  3. ntest = int(len(classes)*(1-TRAIN_PERCENTAGE)) 
  4.  
  5.   
  6.  
  7. test_word_vects = [] 
  8.  
  9. test_classes = [] 
  10.  
  11. for i in range(ntest): 
  12.  
  13.     idx = random.randint(0, len(word_vects)-1) 
  14.  
  15.     test_word_vects.append(word_vects.pop(idx)) 
  16.  
  17.     test_classes.append(classes.pop(idx)) 
  18.  
  19.   
  20.  
  21. train_word_vects = word_vects 
  22.  
  23. train_classes = classes 
  24.  
  25.   
  26.  
  27. train_dataset = [get_doc_vector(words, vocabulary) for words in train_word_vects]  

训练模型:

 
  1. cond_probs, cls_probs = clf.train(train_dataset, train_classes) 

剩下我们用测试数据来测试我们贝叶斯模型的预测准确度:

 
  1. # 测试模型 
  2.  
  3. error = 0 
  4.  
  5. for test_word_vect, test_cls in zip(test_word_vects, test_classes): 
  6.  
  7.     test_data = get_doc_vector(test_word_vect, vocabulary) 
  8.  
  9.     pred_cls = clf.classify(test_data, cond_probs, cls_probs) 
  10.  
  11.     if test_cls != pred_cls: 
  12.  
  13.         print('Predict: {} -- Actual: {}'.format(pred_cls, test_cls)) 
  14.  
  15.         error += 1 
  16.  
  17.   
  18.  
  19. print('Error Rate: {}'.format(error/len(test_classes)))  

随机测了四组,错误率分别为:0, 0.037, 0.015, 0. 平均错误率为1.3%

测完了我们尝试下看看不同类型短信各个词条的概率分布是怎样的吧:

 
  1. # 绘制不同类型的概率分布曲线 
  2.  
  3. fig = plt.figure() 
  4.  
  5. ax = fig.add_subplot(111) 
  6.  
  7. for cls, probs in cond_probs.items(): 
  8.  
  9.     ax.scatter(np.arange(0, len(probs)), 
  10.  
  11.                probs*cls_probs[cls], 
  12.  
  13.                label=cls, 
  14.  
  15.                alpha=0.3) 
  16.  
  17.     ax.legend() 
  18.  
  19.   
  20.  
  21. plt.show() 

试试决策树

上一篇我们基于ID3算法实现了决策树,同样是分类问题,我们同样可以使用我们的文本数据来构建用于分类短信的决策树,当然唯一比较麻烦的地方在于如果按照与贝叶斯相同的向量作为数据,则属性可能会非常多,我们在构建决策树的时候每层树结构都是递归通过遍历属性根据信息增益来选取最佳属性进行树分裂的,这样很多的属性可能会对构建决策树这一过程来说会比较耗时.那我们就试试吧…

 
  1. # 生成决策树 
  2.  
  3. if not os.path.exists('sms_tree.pkl'): 
  4.  
  5.     clf.create_tree(train_dataset, train_classes, vocabulary) 
  6.  
  7.     clf.dump_tree('sms_tree.pkl'
  8.  
  9. else
  10.  
  11.     clf.load_tree('sms_tree.pkl'
  12.  
  13.   
  14.  
  15. # 测试模型 
  16.  
  17. error = 0 
  18.  
  19. for test_word_vect, test_cls in zip(test_word_vects, test_classes): 
  20.  
  21.     test_data = get_doc_vector(test_word_vect, vocabulary) 
  22.  
  23.     pred_cls = clf.classify(test_data, feat_names=vocabulary) 
  24.  
  25.     if test_cls != pred_cls: 
  26.  
  27.         print('Predict: {} -- Actual: {}'.format(pred_cls, test_cls)) 
  28.  
  29.         error += 1 
  30.  
  31.   
  32.  
  33. print('Error Rate: {}'.format(error/len(test_classes)))  

随机测了两次,错误率分别为:0.09, 0.0

效果还算不错

我们还是用Graphviz可视化看一下决策树都选取了那些词条作为判别标准(这时候决策树的好处就体现出来了)。

  

可见决策树的深度并不是很深,如果分类类型一多,估计深度增加上去决策树可能会有些麻烦。

总结

本文我们使用Python一步步实现了朴素贝叶斯分类器,并对短信进行了垃圾短信过滤,同样的数据我们同决策树的分类效果进行了简单的比较。本文相关代码实现:https://github.com/PytLab/MLBox/tree/master/naive_bayes 。决策树过滤垃圾短信的脚本在https://github.com/PytLab/MLBox/tree/master/decision_tree

参考

  • 《Machine Learning in Action》
  • 实例详解贝叶斯推理的原理
  • 大道至简:朴素贝叶斯分类器
作者:佚名
来源:51CTO
相关文章
|
16天前
|
机器学习/深度学习 算法 搜索推荐
Machine Learning机器学习之决策树算法 Decision Tree(附Python代码)
Machine Learning机器学习之决策树算法 Decision Tree(附Python代码)
|
8天前
|
机器学习/深度学习 自然语言处理 算法
|
24天前
|
机器学习/深度学习 分布式计算 算法
大模型开发:你如何确定使用哪种机器学习算法?
在大型机器学习模型开发中,选择算法是关键。首先,明确问题类型(如回归、分类、聚类等)。其次,考虑数据规模、特征数量和类型、分布和结构,以判断适合的算法。再者,评估性能要求(准确性、速度、可解释性)和资源限制(计算资源、内存)。同时,利用领域知识和正则化来选择模型。最后,通过实验验证和模型比较进行优化。此过程涉及迭代和业务需求的技术权衡。
|
26天前
|
机器学习/深度学习 前端开发 算法
利用机器学习优化Web前端性能的探索与实践
本文将介绍如何利用机器学习技术来优化Web前端性能,探讨机器学习在前端开发中的应用,以及通过实际案例展示机器学习算法对前端性能优化的效果。通过结合前端技术和机器学习,提升Web应用的用户体验和性能表现。
|
29天前
|
机器学习/深度学习 数据采集 算法
构建高效机器学习模型:从数据处理到算法优化
【2月更文挑战第30天】 在数据驱动的时代,构建一个高效的机器学习模型是实现智能决策和预测的关键。本文将深入探讨如何通过有效的数据处理策略、合理的特征工程、选择适宜的学习算法以及进行细致的参数调优来提升模型性能。我们将剖析标准化与归一化的差异,探索主成分分析(PCA)的降维魔力,讨论支持向量机(SVM)和随机森林等算法的适用场景,并最终通过网格搜索(GridSearchCV)来实现参数的最优化。本文旨在为读者提供一条清晰的路径,以应对机器学习项目中的挑战,从而在实际应用中取得更精准的预测结果和更强的泛化能力。
|
1月前
|
机器学习/深度学习 算法 生物认证
基于深度学习的人员指纹身份识别算法matlab仿真
基于深度学习的人员指纹身份识别算法matlab仿真
|
26天前
|
传感器 算法 计算机视觉
基于肤色模型和中值滤波的手部检测算法FPGA实现,包括tb测试文件和MATLAB辅助验证
该内容是关于一个基于肤色模型和中值滤波的手部检测算法的描述,包括算法的运行效果图和所使用的软件版本(matlab2022a, vivado2019.2)。算法分为肤色分割和中值滤波两步,其中肤色模型在YCbCr色彩空间定义,中值滤波用于去除噪声。提供了一段核心程序代码,用于处理图像数据并在FPGA上实现。最终,检测结果输出到"hand.txt"文件。
|
1月前
|
机器学习/深度学习 算法 计算机视觉
基于yolov2深度学习网络的视频手部检测算法matlab仿真
基于yolov2深度学习网络的视频手部检测算法matlab仿真
|
1月前
|
算法
【MATLAB】语音信号识别与处理:移动中位数滤波算法去噪及谱相减算法呈现频谱
【MATLAB】语音信号识别与处理:移动中位数滤波算法去噪及谱相减算法呈现频谱
22 2
|
1月前
|
算法
【MATLAB】语音信号识别与处理:一维信号NLM非局部均值滤波算法去噪及谱相减算法呈现频谱
【MATLAB】语音信号识别与处理:一维信号NLM非局部均值滤波算法去噪及谱相减算法呈现频谱
38 1

热门文章

最新文章