自己动手写贝叶斯分类器给图书分类

611 查看

背景与目的

首先,这是一个机器学习初学者兼非数学科班出身的非典型工程师的自学记录。所以本文不会特别理论,也不会太深入地讲解公式,但是会非常有目的性,针对一个特别现实的问题,从头开始分享解决方案,包括某些优化方案。

 

从问题开始

我们要解决的问题,是对图书进行二元分类。分类的依据是图书的tag。这些tag可能来自专家,或者编辑,或者用户。例如“外国文学”,“侦探”,“计算机”,“python”都属于tag。

出于我们的小小实验项目的需求,简化问题,我们现在要把图书分为“人文”或者“非人文”两类。所以,正如上文所说,这是一个对图书进行的二元分类问题。

例如,《计算机科学导论》,它的标签有“计算机”“科学”“经典”“导论”,它属于“非人文”。《麦田里的守望者》,它的标签有“小说”“文学”“美国”,它属于“人文”。我们的分类器有能力根据一本书的标签,自动地将其归类为“人文”或者“非人文”。试试看,蛮有意思的!

为了解决这个问题,我们给出若干个前提:

  • 任何一本书只可能归类为“人文”或“非人文”中的一类
  • 1本书有1个或以上的tag
  • 所有书都没有“人文”和“非人文” 的tag(什么?你不相信?看看亚马逊京东就知道了)
  • 你需要很少的概率知识,比如什么是概率?条件概率又是什么?

 

使用python和numpy

我们将使用python作为这个实验项目的编程语言。

numpy是一个python的科学计算库,需要你自行安装。因为我们的程序涉及一些简单的矩阵运算,用numpy可以大大简化编程工作量。

 

基本原理

贝叶斯分类器的工作原理

还是需要了解一定的理论知识的,别担心,这部分很快就过去。我会直接结合要解决的问题来讲解。

基本上,用贝叶斯分类是要解决一个这样的问题:已知一本书有这些tag:tag1,tag2,tag3……它属于“人文”分类的概率是多少?属于“非人文”分类的概率呢?

假设p1表示在这种情况下,它属于“人文”的概率,p2表示这种情况下,它属于“非人文”的概率。

如果p1>p2,那么这本书就属于“人文”,反过来就是“非人文”。我们不考虑p1=p2的情况。

很简单,不是么?

所以,问题就变成了,如何通过tag1,tag2,tag3…来计算p1和p2?毕竟,只要知道了这两个值,我们的最终问题就解决了。

条件概率

其实,这是一个条件概率的问题。所谓条件概率,就是求:在已知b发生的情况下,a发生的概率。我们写做:p(a|b)。

结合我们的实际问题,那就是在tag1,tag2,tag3…已经发生的情况下(也就是这本书的tag就是tag1,tag2,tag3…),这本书属于“人文”和“非人文”的概率。我们写做:
p(cate1|tag1,tag2,tag3…) 意思是在tag1,tag2,tag3…发生的情况下,这本书属于cate1的概率(cate1=“人文”)

p(cate2|tag1,tag2,tag3…) 意思是在tag1,tag2,tag3…发生的情况下,这本书属于cate2的概率(cate2=“非人文”)

这里的p(cate1|tag1,tag2,tag3…)其实就是上面说的p1,我们这里用更为专业的方法来写。

条件概率怎么求呢?这就是贝叶斯公式:

这个意思就是:想要求p(a|b),而你又知道p(b|a),p(a)和p(b)的值,那你就可以通过p(b|a)*p(a)/p(b)来求得p(a|b)。

换成我们要解决的实际问题,等于:

翻译为人话,那就是你想求p(cate1|tag1,tag2,tag3…),而你现在知道:

  • p(tag1,tag2,tag3…|cate1)的值,也就是你知道在一本书已经被分类为“人文”的情况下,tag1,tag2,tag3…一起出现的概率
  • p(cate1),也就是所有被标记为“人文”分类的书,(在训练集中)在所有书(“人文”和“非人文”)中出现的概率
  • p(tag1,tag2,tag3…),也就是tag1,tag2,tag3…(在训练集)所有tag中出现的概率

也就是说,我们只要挨个求出上述3项,我们就可以求出p(cate1|tag1,tag2,tag3…)了。同样,p(cate2|tag1,tag2,tag3…)也可以求出。

这里有个值得注意的技巧,上述3项中,其实第3项不需要我们计算。因为我们的目的是比较p(cate1|tag1,tag2,tag3…)与p(cate2|tag1,tag2,tag3…)的大小,不是为了得到实际的值,由于上述公式里的分母p(tag1,tag2,tag3…)是一样的,所以,我们只需要比较分子的大小就可以了。也就是比较:

p(tag1,tag2,tag3…|cate1) * p(cate1),

与p(tag1,tag2,tag3…|cate2) * p(cate2)的大小

这样可以省去我们一些计算。

朴素贝叶斯

那么,如何计算p(tag1,tag2,tag3…|cate1)呢?这里要用到朴素贝叶斯的概念,就是说,我们认为,在一本书中的标签里,每个标签都是相互独立的,与对方是否出现没有关系。也就是说“计算机”和“经典”出现的概率互不相关,不会因为“计算机”出现了就导致“经典”出现的概率高。

既然是相互独立,那么,p(tag1,tag2,tag3…|cate1)就等于:

 

p(tag1,tag2,tag3…|cate2)就等于:

 

也就是说,我们可以计算每一个tag,分别在“人文”和“非人文”书籍的所有tag中出现的概率,然后将它们乘起来,就得到我们想要的。

举例分析

我们现在有一本书《计算机科学导论》,它的标签是“计算机”“科学”“理论”“经典”“导论”,我们想知道在这几个标签出现的情况下,《计算机科学导论》分别属于“人文”和“非人文”的概率。

那么,我们已经有了什么呢?幸运的是,我们目前手头有10本书,已知其中6本是“人文”,4本是“非人文”。这10本书,经过排重,一共有70个不同的标签,“计算机”,“科学”,“理论”,“导论”也在其中。

基于此,我们可以得出,p(cate1)=6/10=0.6,p(cate2)=1-0.6=0.4。也就是说“人文”书在所有书中的概率是0.6,“非人文”是0.4。

接下来就是p(tag1,tag2,tag3…|cate1)和p(tag1,tag2,tag3…|cate2)了。也就是,我们要算出,在“人文”类里的所有书中,“计算机”“科学”“理论”“经典”“导论”这几个tag在“人文”书的所有tag里出现的概率。同样,我们还要算出,在“非人文”类里的所有书中,上述这几个tag在所有“非人文”书中的所有tag里出现的概率。计算的方法,就是将每个tag在“人文”和“非人文”中出现的概率,相乘,然后再分别乘以0.6和0.4。

然后比较一下大小就可以了。也就是比较p(cate1) x p(tag1,tag2,tag3…|cate1)与p(cate2) x p(tag1,tag2,tag3…|cate2)的大小。

 

开始动手

1.准备训练集

几乎所有的机器学习都需要训练集。贝叶斯分类也一样。事实上,我们上面所说的我们##已知##的数据,就是训练集。上面例子中举出的那10本书,以及这10本书所有排重后的tag,就是我们的训练集;而0.6和0.4这两个概率,还有p1(tag1,tag2,tag3…|cate1)和p2(tag1,tag2,tag3…|cate2),就是我们基于训练集的数据计算出来的,机器学习管这叫“训练”。

基于我们的问题,我们需要准备100本书,人为地分为“人文”和“非人文”两类,并且收集将这些书的所有tag。这些书如何获得?你可以爬取亚马逊或者豆瓣上的书籍资源。

2.形成tag集

将上述所说的tag,用python里的列表来保存,我们令其为dicts.dicts里的每一个元素是一个tag,例如:

3.计算训练集中“人文”和“非人文”的概率

非常简单,如我们的例子所说,假设这训练集中的这100本书,有60本是“人文”,那么p(cate1)=60/100=0.6。p(cate2)=1-p(cate1)=0.4。这里我们用变量:

4.计算tag集中每个tag在训练集“人文”书籍中的tag出现的概率

首先,我们基于训练集构造一个列表,这个列表里的每一项又是一个列表,这个列表里的每一项,不是1就是0。1表示这个词典中这个位置的tag是这本书的一个tag。

举例:假设我们的dicts是这样的:

这个列表对应的是“人文”类。

每一行代表训练集中“人文”类的一本书。第一行对应的书是《麦田里的守望者》,它的标签是“小说”,“经典”,“美国”。第二行对应的书是《可预测的非理性》,它的标签是“心理”,“行为”,“美国”。注意,我们是用整个tag集dicts来表示一本书的tag。所以,第一行第1列(我们从0开始计数)的1,表示《每天里的守望者》有一个’小说’的tag(对应dicts里的第1列);第一行第2列的0,表示《麦田里的守望者》这本书没有’心理’这个tag(对应dicts里的第2列)。同理,我们看到第一行和第二行的第7列都是1,说明《麦田里的守望者》和《可预测的非理性》都有’美国’这个tag。

有了这样的数据,我们就很好计算了。现在以计算p(tag1|cate1)为例。对于tag1,我们计算出在训练集里“人文”的所有书中,tag1出现了多少次。例如:在训练集里,“人文”有60本,其中40本书都有“经典”这个tag,那么我们就令num_of_tag1=40。按照这个方法,我们求出每个tag出现了多少次,比如:num_of_tag2=32,num_of_tage=18……

然后,我们求出在“人文”类里,所有书的tag标签总数(注意这里是不排重的)。例如“人文”类有2本书,第一本书的标签是“散文”“经典”“外国”,第二本是“经典”“小说”。那么,所有书的tag标签总数就是3+2=5。现在,我们求出训练集里所有100本的tag标签总数。假设总数是700。我们令total_cate1=700。

于是,tag1在“人文”类里出现的概率:p(tag1|cate1) = num_of_tag1 / total_cate1 = 40/700 = 0.057。同理,我们得出p(tag2|cate1),p(tag3|cate1)…

利用numpy,我们可以很方便地用几句代码来实现这个过程。