1. 程式人生 > >機器學習第六篇

機器學習第六篇

文件分類

是機器智慧的一個應用,最有價值的包括垃圾資訊過濾

早期對垃圾資訊進行過濾所用的是基於規則的分類器使用時事先設計好一組規則,用以指明某條資訊是否屬於垃圾資訊。典型的規則包括英文大寫字母的過度使用、與醫藥相關的單詞,或是過於花哨的HTML用色

新:

程式在開始階段和逐漸收到更多訊息之後,根據人們提供給它的有關哪些是垃圾郵件,哪些不是垃圾郵件的資訊,不斷的進行學習

在選擇特徵集時須要做大量的權衡,而且還要不斷地進行調整

對文件的隨機蒐集很重要!!!!!

新建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方法針對目前為止見到過的特徵與分類,給出了一個精確的結果,但只根據以往見過的資訊,會令其在訓練的初期階段,對那些極少出現的單詞變得異常敏感。

    # 加權     -----不是很明白-----
    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'))

仔細思考就可以了喲~~~~~~~~~~ 

對特徵檢測的改進

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}

並沒有將片語連起來呀???

統計的大寫字母也沒用啊,懷疑這段程式碼有問題!

 

 Akismet

沒有看。。。。

小結

貝葉斯和費舍爾兩個分類器均為監督型學習方法。

貝葉斯分類器之所以經常被用於文件分類的原因是,與其他方法相比它所要求的計算資源更少。

神經網路是否會成為一種可行的替代方案,取決於訓練和查詢所要求的速度,以及實際執行的環境,神經網路的複雜性導致了其在理解上的困難。

神經網路和支援向量機有一個很大的優勢:他們可以捕捉到輸入特徵之間更為複雜的關係。在貝葉斯分類器中,每一個特徵都有一個針對各分類的概率值,將這些概率值組合起來之後就成為了一個整體上概率值,神經網路中,某個特徵的概率可能會依據其他特徵的存在或缺失而改變。

 

費時3天看完。。。。。。。。心涼涼。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。