文档分类

是机器智能的一个应用,最有价值的包括垃圾信息过滤

早期对垃圾信息进行过滤所用的是基于规则的分类器使用时事先设计好一组规则,用以指明某条信息是否属于垃圾信息。典型的规则包括英文大写字母的过度使用、与医药相关的单词,或是过于花哨的HTML用色

新:

程序在开始阶段和逐渐收到更多消息之后,根据人们提供给它的有关哪些是垃圾邮件,哪些不是垃圾邮件的信息,不断的进行学习

机器学习第六篇-LMLPHP

在选择特征集时须要做大量的权衡,而且还要不断地进行调整

对文档的随机搜集很重要!!!!!

新建docclass.py

def getwords(doc):
    splitter = re.compile('\\W*')
    # 根据非字母字符进行拆分
    words = [s.lower() for s in splitter.split(doc) if len(s) > 2 and len(s) < 20]

    # ****只返回一组不重复的单词****
    return dict([(w, 1) for w in words])

对分类器进行训练:

import re
import math

def sampletrain(c1):
    c1.train('Nobody owns the water.', 'good')
    c1.train('the quick rabbit jumps fences', 'good')
    c1.train('buy pharmaceuticals now', 'bad')
    c1.train('make quick money at the online casino', 'bad')
    c1.train('the quick brown fox jumps', 'good')

def getwords(doc):
    splitter = re.compile('\\W*')
    # 根据非字母字符进行拆分
    words = [s.lower() for s in splitter.split(doc) if len(s) > 2 and len(s) < 20]

    # ****只返回一组不重复的单词****
    return dict([(w, 1) for w in words])

class classifier:

    def __init__(self,getfeatures,filename=None):
        #统计特征/分类组合的数量
        self.fc={}
        #统计每个分类中的文档数量
        self.cc={}
        #得到特征集
        self.getfeatures=getfeatures

    # 增加对某一分类的计数值
    def incc(self, cat):
        self.cc.setdefault(cat, 0)
        self.cc[cat] += 1

    # 增加对特征/分类组合的计数值
    def incf(self, f, cat):
        self.fc.setdefault(f, {})
        self.fc[f].setdefault(cat, 0)
        self.fc[f][cat] += 1
        # print(self.fc)

    # 返回某一特征出现于某一分类中的次数
    def fcount(self, f, cat):
        if f in self.fc and cat in self.fc[f]:
            return float(self.fc[f][cat])
        return 0.0

    # 返回属于某一分类的内容项数量
    def catcount(self, cat):
        if cat in self.cc:
            return float(self.cc[cat])
        return 0

    # 所有内容项的数量
    def totalcount(self):
        return sum(self.cc.values())

    # 所有分类的列表
    def categories(self):
        return self.cc.keys()

    def train(self, item, cat):
        features = self.getfeatures(item)
        # 针对该分类为每个特征增加计数值
        for f in features:
            self.incf(f, cat)

        # 增加针对该分类的计数值
        self.incc(cat)

计算概率:P(good)+P(bad)!=1

    def fprob(self, f, cat):
        if self.catcount(cat) == 0: return 0
        # 特征在分类中出现的总次数,除以分类中包含内容项的总数
        # print(self.fc)
        # print(self.cc)
        return self.fcount(f, cat) / self.catcount(cat)

fprob方法针对目前为止见到过的特征与分类,给出了一个精确的结果,但只根据以往见过的信息,会令其在训练的初期阶段,对那些极少出现的单词变得异常敏感。

机器学习第六篇-LMLPHP

    # 加权     -----不是很明白-----
    def weightedprob(self, f, cat, prf, weight=1.0, ap=0.5):
        # 计算当前的概率值
        basicprob = prf(f, cat)

        # 统计特征在所有分类中出现的次数
        totals = sum([self.fcount(f, c) for c in self.categories()])

        # 计算加权平均
        bp = ((weight * ap) + (totals * basicprob)) / (weight + totals)
        return bp

么有看懂~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

对于一个过滤器而言,它最好应该有能力处理极少会出现的单词。

朴素分类器

一旦求出指定单词在一篇属于某个分类的文档中出现的概率,就需要有一种方法将各个单词的概率进行组合,从而得出整篇文档属于该分类的概率。

朴素即一个单词在属于某个指定分类的文档中出现的概率,与其他单词出现于该分类的概率是不相关的。

事实上,假设是不成立的。

但若不考虑假设的潜在缺陷,朴素贝叶斯分类器将被证明是一种非常有效的文档分类方法。

朴素贝叶斯分类器:通过将所有的概率相乘,计算出总的概率值,再乘以分类的概率。

**************************************************************普及贝叶斯**************************************************************

 

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

        # 将所有特征的概率相乘
        p = 1
        for f in features: p *= self.weightedprob(f, cat, self.fprob)
        return p

    # 一个文档属于某个分类的概率
    def prob(self, item, cat):
        catprob = self.catcount(cat) / self.totalcount()
        docprob = self.docprob(item, cat)
        return docprob * catprob

选择分类:判定某个内容项所属的分类

最简单的方法:

计算被考察内容在每个不同分类中的概率,然后选择概率最大的分类,但在某些应用中,承认不知道答案,要好过判断答案就是概率值稍大一些的分类

为每个分类定义一个最小阈值!

class naivebayes(classifier):
    def __init__(self, getfeatures):
        classifier.__init__(self, getfeatures)
        self.thresholds = {}

    def setthresholds(self, cat, t):
        self.thresholds[cat] = t

    def getthresholds(self, cat):
        if cat not in self.thresholds: return 1.0
        return self.thresholds[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

    # 一个文档属于某个分类的概率
    def prob(self, item, cat):
        catprob = self.catcount(cat) / self.totalcount()
        docprob = self.docprob(item, cat)
        return docprob * catprob

    def classify(self, item, default=None):
        probs = {}
        # 寻找概率最大的分类
        max = 0.0
        for cat in self.categories():
            probs[cat] = self.prob(item, cat)
            if probs[cat] > max:
                max = probs[cat]
                best = cat

        # 确保概率值超出阈值*次大概率值   ***************************
        for cat in probs:
            if cat == best: continue
            if probs[cat] * self.getthresholds(best) > probs[best]: return default
        return best

费舍尔方法

与朴素贝叶斯过滤器利用特征概率来计算整篇文章的概率文章的概率不同,费舍尔方法为文档中的每个特征都求得了分类的概率,然后又将这些概率组合起来,并判断其是否有可能构成一个随机集合。

import math
from docclass import classifier

class fisherclassifier(classifier):

    def cprob(self, f, cat):
        # 特征在该分类中出现的频率
        clf = self.fprob(f, cat)
        if clf == 0: return 0

        # 特征在所有分类中出现的频率
        freqsum = sum([self.fprob(f, c) for c in self.categories()])

        # 概率等于特征在该分类中出现的频率除以总体频率
        p = clf / (freqsum)
        return p

    def fisherprob(self, item, cat):
        # 将所有概率值相乘
        p = 1
        features = self.getfeatures(item)
        for f in features:
            p *= (self.weightedprob(f, cat, self.cprob))

        # 取自然对数,并乘以-2
        fscore = -2 * math.log(p)

        # 利用倒置对数卡方函数求得概率
        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 __init__(self, getfeatures):
        classifier.__init__(self, getfeatures)
        self.minimums = {}

    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):
        #循环遍历并寻找最佳结果
        best=default
        max=0.0
        for c in self.categories():
            p=self.fisherprob(item,c)
            #确保其超过下限值
            if p>self.getminimum(c) and p>max:
                best=c
                max=p
        return best



        

使用SQLite

只是将上面存在字典中的数据存到了数据库中而已~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class classifier:

    def __init__(self, getfeatures, filename=None):
        # 统计特征/分类组合的数量
        self.fc = {}
        # 统计每个分类中的文档数量
        self.cc = {}
        # 得到特征集
        self.getfeatures = getfeatures

    def setdb(self, dbfile):
        self.con = sqlite3.connect(dbfile)
        self.con.execute('create table if not exists fc(feature,category,count)')
        self.con.execute('create table if not exists cc(category,count)')

    # 增加对某一分类的计数值
    # def incc(self, cat):
    #     self.cc.setdefault(cat, 0)
    #     self.cc[cat] += 1
    def incc(self, cat):
        count = self.catcount(cat)
        if count == 0:
            self.con.execute("insert into cc values ('%s',1)" % (cat))
        else:
            self.con.execute("update cc set count=%d where category='%s'" % (count + 1, cat))

    # 增加对特征/分类组合的计数值
    # def incf(self, f, cat):
    #     self.fc.setdefault(f, {})
    #     self.fc[f].setdefault(cat, 0)
    #     self.fc[f][cat] += 1
    # 增加对特征/分类组合的计数值
    def incf(self, f, cat):
        count = self.fcount(f, cat)
        if count == 0:
            self.con.execute("insert into fc values('%s','%s',1)" % (f, cat))
        else:
            self.con.execute(
                "update fc set count=%d where feature='%s' and category='%s'" % (count + 1, f, cat))

    # 返回某一特征出现于某一分类中的次数
    # def fcount(self, f, cat):
    #     if f in self.fc and cat in self.fc[f]:
    #         return float(self.fc[f][cat])
    #     return 0.0

    def fcount(self, f, cat):
        res = self.con.execute('select count from fc where feature="%s" and category="%s"' % (f, cat)).fetchone()
        if res == None:
            return 0
        else:
            return float(res[0])

    # 返回属于某一分类的内容项数量
    # def catcount(self, cat):
    #     if cat in self.cc:
    #         return float(self.cc[cat])
    #     return 0
    def catcount(self, cat):
        res = self.con.execute('select count from cc where category="%s"' % (cat)).fetchone()
        if res == None:
            return 0
        else:
            return float(res[0])

    # 所有内容项的数量
    # def totalcount(self):
    #     return sum(self.cc.values())
    def totalcount(self):
        res=self.con.execute('select sum(count) from cc').fetchone()
        if res==None: return 0
        return res[0]

    # 所有分类的列表
    # def categories(self):
    #     return self.cc.keys()
    def categories(self):
        cur = self.con.execute('select category from cc')
        return [d[0] for d in cur]

    def train(self, item, cat):
        features = self.getfeatures(item)
        # 针对该分类为每个特征增加计数值
        for f in features:
            self.incf(f, cat)

        # 增加针对该分类的计数值
        self.incc(cat)
        self.con.commit()

    def fprob(self, f, cat):
        if self.catcount(cat) == 0: return 0
        # 特征在分类中出现的总次数,除以分类中包含内容项的总数
        # print(self.fc)
        # print(self.cc)
        return self.fcount(f, cat) / self.catcount(cat)

    # 加权     -----不是很明白-----
    def weightedprob(self, f, cat, prf, weight=1.0, ap=0.5):
        # 计算当前的概率值
        basicprob = prf(f, cat)

        # 统计特征在所有分类中出现的次数
        totals = sum([self.fcount(f, c) for c in self.categories()])

        # 计算加权平均
        bp = ((weight * ap) + (totals * basicprob)) / (weight + totals)
        return bp

过滤博客订阅源

import feedparser
import docclass3
import docclass4

#接受一个博客订阅源的URL文件名并对内容项进行分类
def read(feed,classifier):
    #得到订阅源的内容项并遍历循环
    f=feedparser.parse(feed)
    for entry in f['entries']:
        print()
        print('--------')
        #将内容打印输出
        print('Title:  '+str(entry['title'].encode('utf-8')))
        print('Publisher:  '+str(entry['publisher'].encode('utf-8')))
        print()
        print(str(entry['summary'].encode('utf-8')))

        #将所有文本组合在一起,为分类器构建一个内容项
        fulltext='%s\n%s\n%s' % (entry['title'],entry['publisher'],entry['summary'])
        #将当前分类的最佳推测结果打印输出
        print('Guess:  '+str(classifier.classify(fulltext)))

        #请求用户给出正确分类,并据此进行训练
        c1=input('Enter category:')
        classifier.train(fulltext,c1)


c1=docclass3.fisherclassifier(docclass4.getwords)
c1.setdb('python_feed.db') #仅当你使用的是SQLite
# read('python_search.xml',c1)

print(c1.cprob('python','prog'))
print(c1.cprob('python','snake'))
print(c1.cprob('python','monty'))
print(c1.fprob('eric','monty'))

仔细思考就可以了哟~~~~~~~~~~ 

对特征检测的改进

机器学习第六篇-LMLPHP

import feedparser
import docclass3
import docclass4
import re

#接受一个博客订阅源的URL文件名并对内容项进行分类
def read(feed,classifier):
    #得到订阅源的内容项并遍历循环
    f=feedparser.parse(feed)
    for entry in f['entries']:
        print()
        print('--------')
        #将内容打印输出
        print('Title:  '+str(entry['title'].encode('utf-8')))
        print('Publisher:  '+str(entry['publisher'].encode('utf-8')))
        print()
        print(str(entry['summary'].encode('utf-8')))

        #将所有文本组合在一起,为分类器构建一个内容项
        #fulltext='%s\n%s\n%s' % (entry['title'],entry['publisher'],entry['summary'])
        #将当前分类的最佳推测结果打印输出
        # print('Guess:  '+str(classifier.classify(fulltext)))
        #
        # #请求用户给出正确分类,并据此进行训练
        # c1=input('Enter category:')
        # classifier.train(fulltext,c1)
        print('Guess: ' + str(classifier.classify(entry)))

        # 请求用户给出正确分类,并据此进行训练
        c1 = input('Enter category: ')
        classifier.train(entry, c1)
        break

#
# c1=docclass3.fisherclassifier(docclass4.getwords)
# c1.setdb('python_feed.db') #仅当你使用的是SQLite
# read('python_search.xml',c1)

# print(c1.cprob('python','prog'))
# print(c1.cprob('python','snake'))
# print(c1.cprob('python','monty'))
# print(c1.fprob('eric','monty'))


#替代getwords
def entryfeatures(entry):
    splitter=re.compile('\\W*')
    f={}

    #提取标题中的单词并进行标示
    titlewords=[s.lower() for s in splitter.split(entry['title'])
                if len(s)>2 and len(s)<20]
    for w in titlewords: f['Title:'+w]=1   #*******************************************

    #提取摘要中的单词
    summarywords=[s.lower() for s in splitter.split(entry['summary'])
                  if len(s)>2 and len(s)<20]

    #统计大写单词
    uc=0
    for i in range(len(summarywords)):
        w=summarywords[i]
        f[w]=1
        if w.isupper(): uc+=1   #???????????????????????

        #将从摘要中获得的词组作为特征
        if i<len(summarywords)-1:
            twowords=' '.join(summarywords[i:i+1])
            f[twowords]=1     #*******************************************

    #保持文章创建者和发布者名字的完整性
    f['Publisher:'+entry['publisher']]=1         #*******************************************

    #UPPERCASE是一个‘虚拟’单词,用以指示存在过多的大写内容
    if float(uc)/len(summarywords)>0.3: f['UPPERCASE']=1
    print(f)
    return f


# c1=docclass3.fisherclassifier(entryfeatures)
# c1.setdb('python_feed.db')
# read('python_search.xml',c1)

s='Microsoft Hires Inventor of RubyCLR'
splitter=re.compile('\\W*')
f={}
summarywords=[s.lower() for s in splitter.split(s) if len(s)>2 and len(s)<20]
#统计大写单词
uc=0
for i in range(len(summarywords)):
    w=summarywords[i]
    print(w)
    f[w]=1
    if w.isupper(): uc+=1
    print(uc)


只是将getwords改成了entryfeatures,其他没什么变化,返回的字典形式如下

{'Title:new': 1, 'Title:baby': 1, 'Title:boy': 1, 'this': 1, 'new': 1, 'baby': 1, 'anthem': 1, 'and': 1, 'half': 1, 'month': 1, 'old': 1, 'ball': 1, 'python': 1, 'orange': 1, 'shaded': 1, 'normal': 1, 'pattern': 1, 'have': 1, 'held': 1, 'him': 1, 'about': 1, 'time': 1, 'since': 1, 'brought': 1, 'home': 1, 'tonight': 1, '00pm': 1, 'Publisher:Shetan Noir, the zombie belly dancer! - MySpace Blog': 1}

机器学习第六篇-LMLPHP

并没有将词组连起来呀???

统计的大写字母也没用啊,怀疑这段代码有问题!

 

 Akismet

没有看。。。。

小结

贝叶斯和费舍尔两个分类器均为监督型学习方法。

贝叶斯分类器之所以经常被用于文档分类的原因是,与其他方法相比它所要求的计算资源更少。

神经网络是否会成为一种可行的替代方案,取决于训练和查询所要求的速度,以及实际运行的环境,神经网络的复杂性导致了其在理解上的困难。

神经网络和支持向量机有一个很大的优势:他们可以捕捉到输入特征之间更为复杂的关系。在贝叶斯分类器中,每一个特征都有一个针对各分类的概率值,将这些概率值组合起来之后就成为了一个整体上概率值,神经网络中,某个特征的概率可能会依据其他特征的存在或缺失而改变。

 

费时3天看完。。。。。。。。心凉凉。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

10-06 16:11