贝叶斯分类器伪代码_朴素贝叶斯测试驱动开发

朴素贝叶斯测试驱动开发案例

我们知道Foxmail微信之父张小龙的作品,该软件客户端在设置页面有一项“反垃圾”的设置项,支持使用贝叶斯过滤垃圾邮件

下文我们简述贝叶斯过滤垃圾邮件的原理和极简版实现。

06b43f445d32c8a51b5a0349f177d35c.png

按TDD的开发模式。开发前需要做3项准备:邮件数据准备、开发工具准备和朴素贝叶斯定理基础知识。

数据准备

原始邮件语料来自公开数据源:

https://spamassassin.apache.org/old/publiccorpus/20030228_easy_ham_2.tar.bz2 https://spamassassin.apache.org/old/publiccorpus/20030228_spam.tar.bz2

内容全部为英文,其中垃圾邮件为500份,正常邮件为1400份。为了专注测试驱动开发贝叶斯邮件分类器,本节已将数据进行清洗,每个文件包含一行邮件正文。其中一份内容示例如下:

lowest rates available for term life insurance take a moment and fill out our online form to see the low rate you qualify for save up to 70 from regular rates smokers accepted http www newnamedns com termlife representing quality nationwide carriers act now to easily remove your address from the list go to http www newnamedns com stopthemailplease please allow 48 72 hours for removal

我们只需读取文件,统计单词,然后实现朴素贝叶斯分类器即可。

测试工具

Python语言内置有断言,也可作为测试的工具之一,例如:

assert 1==2,'断言失败'

书中重点使用unittest包

使用示例如下:

import unittest
t_test = unittest.TestCase()
t_test.assertEqual(1,2,'断言失败')

朴素贝叶斯定理

(1) 条件概率

设事件A:5级大风;事件B:晴天。出现5级大风时是晴天的条件概率表示为式

假设获取到的100天的数据里A出现了5天,则P(A)=0.05,在5天里有一天是晴天则P(A and B)=0.01,所以P(B|A)=0.01/0.05=0.2。这种正向求解的方式又称正向概率.

(2) 朴素贝叶斯

计算条件概率的另一种方式是使用贝叶斯定理,另外一种场景是已知P(B|A),求P(A|B)即求反向概率:已知是晴天,出现5级大风的概率。

假设在100天里,晴天出现了50天,那么P(B)=0.5,带入式(1-4)可求得P(A|B)=0.02。当加入更多的事件,例如,A2表示气温,则问题的求解将变得更复杂,如上述事件概率表示为:P(B|A,A2),P(A,A2|B)。为此我们先看多变量,非独立联合条件概率分布的链式法则:请参考书中的1.4节。

以上是一个通用的表达式,但现实中计算复杂,例如求解P(B|A,A2),按贝叶斯公式得到:

以上不够简化,可以先按贝叶斯和链式法则展开?(?,?,?2)观察

假设?和?2独立,可简化为式:

以上即为朴素贝叶斯的计算方式。

对于所有的B来说,P(?,?2)都是固定的,所以此处将B扩展为B1—晴天,B2—雨天。当要判断晴天或雨天这样的二分类问题时,只要比较?(?1|?,?2) 和 ?(?2|?,?2)大小即可。这等价于判断“P(B1)P(A|B1)P(A2|B1)”和“P(B2)P(A|B2)P(A2|B2)”。

在下文的朴素贝叶斯邮件分类器中,B1和B2分别表示垃圾邮件和正常邮件,A则代表各个单词。此时式(1-9)的含义为:当某封邮件出现了某些单词的情况下,该邮件是否是垃圾邮件。

其求解方法只要分别在训练样本中的垃圾邮件和正常邮件中统计各单词的词频即可,而P(B)则表示垃圾邮件和正常邮件的一种先验概率,我们简单地以样本中的邮件情况作为先验,比如:

垃圾邮件P(B1)=500/(1400+500)= 0.2632;正常邮件P(B2)=1400/(1400+500)= 0.7368。

(3) 贝叶斯决策

我们按照贝叶斯决策理论,做如下二分类决策。

  • 垃圾邮件:P(垃圾|X) > P(正常|X)
  • 正常邮件:P(垃圾|X) ≤P(正常|X)

等号意为:一封正常邮件判断为垃圾邮件比一封垃圾邮件判断为正常邮件的危害要大

开发邮件分类器

按如下步骤开发邮件分类器:读取目录下文件、解析单词、训练、评分、评判、分类/预测/验证。

TDD开发流程:开发测试代码、开发功能代码、运行测试主函数,保证代码测试通过。

获取文件

先编写测试代码:setUp中准备好测试用数据和相关准备工作,编写读取文件的测试代码,函数名格式:test_xxx,此处为test_get_files_from_dir

def test_get_files_from_dir(self):
        a_bad = EmailClassifier.get_files_from_dir(self.spam_dir)
        a_good = EmailClassifier.get_files_from_dir(self.ham_dir)
        self.assertEqual(len(a_bad), 1)
        self.assertEqual(len(a_good), 2)

再编写功能代码:...

运行上述测试主函数,保证代码测试通过。

# 主函数
if __name__ == '__main__':
# 方式一:python -m unittest TestEmailClassifier
#unittest.main()
# 方式二
suite = unittest.defaultTestLoader.loadTestsFromTestCase(
    TestEmailClassifier)
    unittest.TextTestRunner().run(suite)

解析单词

接上述代码,在TestEmailClassifier类中编写测试代码

# 测试文本解析功能:全部小写,取集合
    # expectation : e ;a = actual
    def test_get_words_from_file(self):
        e = set(['notin', 'book', 'please'])
        a = EmailClassifier.get_words_from_file(self.tmp_file)
        self.assertSetEqual(a, e)

再编写功能代码:...

运行测试主函数,保证代码测试通过。

训练

朴素贝叶斯训练:统计单词。先编写测试代码

def test_train(self):
        self.clf.train()
        self.assertEqual(self.clf.total_count['spam'], 6)
        self.assertEqual(self.clf.total_count['ham'], 11)
        self.assertEqual(self.clf.training['ham']['please'], 2)
        self.assertEqual(self.clf.training['spam']['buy'], 1)

再编写功能代码:...

运行测试主函数,保证代码测试通过。

测试代码:输入邮件文件得出其评分

评分

测试代码:输入邮件文件得出其评分:

def test_score(self):          
        a = {
            'ham': round(2 / 3 * 1 / 12 * 3 / 12 * 3 / 12, 7)
            'spam': round(1 / 3 * 1 / 7 * 2 / 7 * 1 / 7, 7)
        }
        e = self.clf.score(self.tmp_file)
        self.assertDictEqual(a, e)
        

运行测试主函数,保证代码测试通过。

评判

测试代码:输入得分字典,进行贝叶斯决策。

def test_judge(self):
        t = self.clf.score(self.tmp_file)
        e = {'ham':0.0034722}
        a = self.clf.judge(t)
        self.assertDictEqual(a, e)

运行测试主函数,保证代码测试通过。

分类

测试代码:输入邮件文件,得出分类结果。

    def test_classify(self):
        e = {'ham':0.0034722}
        a = self.clf.classify(self.tmp_file)
        self.assertDictEqual(a, e)

运行测试主函数,保证代码测试通过。

最终的测试通过如图1-8所示:

e8dffbdeca5a54838b3076da15dbcc89.png

当测试和开发结束后,就完成了核心功能点开发。后续就要检查功能或微调。最后将测试代码和功能代码一并归档提交,只要保证测试通过,就能保证原功能正常,这极大地方便了后续迭代和维护。

小结

书中1.3节简述了软件工程的概念和机器学习中的软件工程方法,更多的是对读者工程思维的一种培养。具体实践内容包括:如何编码、如何测试和测试驱动的开发方法。1.4节以朴素贝叶斯测试驱动的开发案例结束本章。按照测试驱动的开发方法,测试完即完成核心功能的开发。这种开发方式除了多实践外,还需要逐渐改变开发的思维和习惯。

所有数据和代码(测试代码、功能代码)请参考本书的GitHub。

节选自《机器学习:软件工程方法与实现》第一章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值