集体智慧编程学习之分类系统

欢迎关注我的个人博客blog.timene.com

在前面《集体智慧编程学习之聚类系统》中,我对收藏的一些电子书做了聚类。当时是在没有任何前验知识的情况下,采用K-均值聚类的算法将书籍分为经济类,心理类,摄影类等等。今天,我打算手工给书籍分类,我先把特别特别特别喜欢的书整理出来,然后分好类。分好搞得我精疲力尽,却发现还有大量的特别特别喜欢的,特别喜欢的,喜欢的,还好的,不喜欢的。。。就这么手工分了一小部分,我已经完全不想再手工分下去了。我已经分了一小部分,能不能根据已经分好的这部分去区分其他的书籍呢?也就是我现在有了一些前验知识,能不能把这些知识利用起来?

要分类,肯定要根据一些特征来做,特征是判断内容中具有或者缺失的信息。对电子书分类这事,内容就是书籍文档,特征就是文档中的单词或者词组。这和聚类系统中说过的一样,不同分类的书籍这些特征是不同的。

首先来分词整理这些特征

def getwords(doc):
  splitter=re.compile('\\W*')
  print doc
  # Split the words by non-alpha characters
  words=[s.lower() for s in splitter.split(doc) 
          if len(s)>2 and len(s)<20]
  
  # Return the unique set of words only
  return dict([(w,1) for w in words])

定义一个代表分类器的类,这个类对分类器到目前为止所掌握的信息进行封装:

class classifier:
  def __init__(self,getfeatures,filename=None):
    self.fc={}
    self.cc={}
    self.getfeatures=getfeatures

  def incf(self,f,cat):
    self.fc.setdefault(f, {})
    self.fc[f].setdefault(cat, 0)
    self.fc[f][cat] += 1

  def incc(self,cat):
    self.cc.setdefault(cat, 0)
    self.cc[cat] += 1

  def fcount(self, f, cat):
    if f in self.fc and cat in self.fc[f]:
      return self.fc[f][cat]
    else:
      return 0.0

  def catcount(self,cat):
    if cat in self.cc:
      return float(self.cc[cat])
    else:
      return 0.0

  def categories(self):
    return self.cc.keys()

  def totalcount(self):
    return sum(self.cc.values())
可以训练模型喽,很简单的一个函数:

  def train(self,item,cat):
    features=self.getfeatures(item)
    for f in features:
      self.incf(f,cat)
    self.incc(cat)
训练好之后,可以计算特征f属于cat分类的概率了,也就是cat分类下书籍的总量除以cat分类下具有特征f的的书籍数:

  def fprob(self,f,cat):
    if self.catcount(cat)==0: return 0
    return self.fcount(f,cat)/self.catcount(cat)

到了这里,模型建好了,根据这个模型如何对未分类的书籍分类呢?可以用朴素贝叶斯分类,也可以用费舍尔分类。


(一)贝叶斯分类:贝叶斯的初衷很简单:盒子里有十个球,六个绿色的,四个蓝色的,我随手一摸,摸出蓝色的概率很容易知道。如果我知道盒子里有十个球,但不知道几个绿的几个蓝的,我随手一摸是个绿的,那盒子里会有几个绿球几个蓝球呢?贝叶斯公式也很简单,用来解决这种条件概率的调换,根据训练模型很容易知道某个分类下某个特征的概率,怎么计算这个特征可能会属于这个分类的概率。

朴素贝叶斯公式假设一篇文章中的各个词组是不相关的,这样,计算整本书籍的概率,就是所有词组概率之积:

  def docprob(self,item,cat):
    features=self.getfeatures(item)   

    p=1
    for f in features: p*=self.weightedprob(f,cat,self.fprob)
    return p

根据贝叶斯公式,P(类目|书籍)=P(书籍|类目)*P(类目)/P(书籍),我们不考虑P(书籍),这个值在计算书籍属于那个类目时是不会变化的

  def prob(self,item,cat):
    catprob=self.catcount(cat)/self.totalcount()
    docprob=self.docprob(item,cat)
    return docprob*catprob

这样,要预计一本书属于那个类目,只要计算这本书属于所有类目的概率,取其中概率最大的即是。这里我再引深一点,取概率最大在有些场合并不合适,比如分类邮件,垃圾邮件和非垃圾邮件,我们宁可让几份垃圾邮件进入正常邮件,如果把非垃圾邮件误判为垃圾邮件就很不好了。这时我们在做判断时加上一个阈值,比如判断为垃圾邮件的概率大于判断为正常邮件的概率的几倍,我们就确认为垃圾邮件过滤,否则我们就不判断。

def setthreshold(self,cat,t):
    self.thresholds[cat]=t
    
  def getthreshold(self,cat):
    if cat not in self.thresholds: return 1.0
    return self.thresholds[cat]
  
  def classify(self,item,default=None):
    probs={}
    # Find the category with the highest probability
    max=0.0
    for cat in self.categories():
      probs[cat]=self.prob(item,cat)
      if probs[cat]>max: 
        max=probs[cat]
        best=cat


    # Make sure the probability exceeds threshold*next best
    for cat in probs:
      if cat==best: continue
      if probs[cat]*self.getthreshold(best)>probs[best]: return default
    return best



(二)费舍尔分类:
贝叶斯方法是计算书籍中所有词组在某个类目下的存在率之积,来判断这本书籍会属于那个分类的概率。费舍尔方法是直接根据词组属于某个分类的存在率来估计书籍的分类,这个存在率的计算是P=该类目中该词组的数量/该词组出现总数。这样判断一本书籍属于那个类目就简单了,该书籍所有词组的存在率P之积中取个最大的即可。

  def cprob(self,f,cat):
    # The frequency of this feature in this category    
    clf=self.fprob(f,cat)
    if clf==0: return 0

    # The frequency of this feature in all the categories
    freqsum=sum([self.fprob(f,c) for c in self.categories()])

    # The probability is the frequency in this category divided by
    # the overall frequency
    p=clf/(freqsum)
    
    return p

  def fisherprob(self,item,cat):
    # Multiply all the probabilities together
    p=1
    features=self.getfeatures(item)
    for f in features:
      p*=(self.weightedprob(f,cat,self.cprob))


    # Take the natural log and multiply by -2
    fscore=-2*math.log(p)


    # Use the inverse chi2 function to get a probability
    return self.invchi2(fscore,len(features)*2)
  
  def invchi2(self,chi, df):
    m = chi / 2.0
    sum = term = math.exp(-m)
    for i in range(1, df//2):
        term *= m / i
        sum += term
    return min(sum, 1.0)

代码中取了各词组概率值之积后,有做了一些小调整
和贝叶斯一样,为了避免误判的情况,我们也加上阈值,这里的阈值不是倍数关系,而是给了一个概率最小值:

  def setminimum(self,cat,min):
    self.minimums[cat]=min
  
  def getminimum(self,cat):
    if cat not in self.minimums: return 0
    return self.minimums[cat]
  def classify(self,item,default=None):
    # Loop through looking for the best result
    best=default
    max=0.0
    for c in self.categories():
      p=self.fisherprob(item,c)
      # Make sure it exceeds its minimum
      if p>self.getminimum(c) and p>max:
        best=c
        max=p
    return best


这样,分类结束了,原理和代码都很简单,希望自己能把自己说清楚。

你可能感兴趣的:(数据挖掘,机器学习,分类,贝叶斯,费舍尔)