機器學習實戰—樸素貝葉斯及要點註解

書籍:《機器學習實戰》中文版
IDE:PyCharm Edu 4.02

環境:Adaconda3  python3.6


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from numpy import *
# 測試函數用的簡單實例
def loadDataSet():
    postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                 ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
                 ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
                 ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
                 ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
    classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not
    return postingList,classVec
# 創建一個包含在所有文檔中出現的不重複詞的列表
# 疑惑:每次運行此函數,得到的列表中,單詞的順序不同。
def createVocaList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)   #求集合並集
    return list(vocabSet)
# 構造固定長度的詞集模型
# 詞集模型:詞集中,每個詞只能出現一次
def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print("the word:%s is not in my Vocabulary!" % word)
    return returnVec
# 詞袋模型:詞袋中,允許每個詞出現多次
def bagOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec
# 參數估計
# 兩類問題
def trainNB(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # 初始化概率(分子和分母變量),且採取拉普拉斯平滑
    # 假設所有詞均出現過一次
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    # 分母加K。K代表類別數目,這裏只有兩類。
    p0Denom = 2.0
    p1Denom = 2.0
    # p0Num = zeros(numWords)
    # p1Num = zeros(numWords)
    # p0Denom = 0.0
    # p1Denom = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # p1Vect = p1Num/p1Denom
    # p0Vect = p0Num/p0Denom
    # 防止下溢出。對乘積取對數ln
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive
# 樸素貝葉斯準則
# vec2Classify:帶分類向量
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  #做平滑處理後,乘積變相加
    p0 = sum(vec2Classify * p0Vec) + log(1-pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
# 將以上函數封裝,測試函數的正確性
# 參數inputTest是待分類的列表
def testingNB(inputTest):
    listPosts,listClasses = loadDataSet()
    myVocabList = createVocaList(listPosts)
    trainMat = []
    for postinDoc in listPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb = trainNB(trainMat,listClasses) #p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
    thisDoc = setOfWords2Vec(myVocabList,inputTest)
    print(inputTest,"classified as: ",classifyNB(thisDoc,p0V,p1V,pAb))
#print(testingNB(['love','my','dalmation']))
# 例子:垃圾郵件分類
# 用正則表達式分離句子並將所有字母變爲小寫且過濾掉字母個數小於2的(URLd地址原因)
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\w*',bigString)
    return [word.lower() for word in listOfTokens if len(word)>2]
def spamTest():
    docList=[];classList=[];fullText=[]
    #spam和ham文件夾下各25個文本
    for i in range(1,26):
        wordList = textParse(open('email/spam/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)    #垃圾郵件標籤爲1
        wordList = textParse(open('email/ham/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocaList(docList)
    #原文trainingSet = range(50)
    trainingSet = list(range(50)) #本例共50封郵件
    testSet = []  #隨機選10封作爲測試集
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    #print(testSet,trainingSet)
    trainMat = [];trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2Vec(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pAb = trainNB(trainMat,trainClasses)
    errorCount = 0
    for docIndex in testSet:
        wordVector = bagOfWords2Vec(vocabList,docList[docIndex])
        if classifyNB(wordVector,p0V,p1V,pAb) != classList[docIndex]:
            errorCount += 1
            print("classification error",docList[docIndex])
    print('the error rate is: ',float(errorCount)/len(testSet))
print(spamTest())


註解:

1、def createVocaList(dataSet)函數中:符號“|”既可以用於集合求並集也可以是按位求或。

2、python中創建指定長度的列表

returnVec = [0]*len(vocabList)

或者 list(range(a,b,step))

3、numpy庫: * / 等都是指元素間的操作

4、體會append和extend的區別:

List 的兩個方法 extend 和 append 看起來類似,但實際上完全不同。extend 接受一個參數,這個參數總是一個 list,並且把這個 list 中的每個元素添加到原 list 中。append 接受一個參數,這個參數可以是任何數據類型,並且簡單地追加到 list 的尾部。

5、正則表達式

正則表達式是一種用來匹配字符串的強有力的武器。它的設計思想是用一種描述性的語言來給字符串定義一個規則,凡是符合規則的字符串,我們就認爲它“匹配”了,否則,該字符串就是不合法的。

基本的用字符描述字符:

\d可以匹配一個數字;\w可以匹配一個字母或數字;“.”可以匹配任意字符;

*表示任意個字符(包括0個);+表示至少一個字符;?表示0個或1個字符;

{n}表示n個字符;{n,m}表示n-m個字符;

\s可以匹配一個空格(也包括Tab等空白符);

特殊字符比如“-”需要用“\”轉義;

6、re模塊

Python提供re模塊,包含所有正則表達式的功能。由於Python的字符串本身也用\轉義,所以要特別注意。因此我們強烈建議使用Python的r前綴,就不用考慮轉義的問題。

即r'abc//t123'就代表字符 abc//t123

split方法:

切分字符串:用正則表達式切分字符串比用固定的字符更靈活,還可以把不規範的輸入轉化成正確的數組。

注意切分後,最後一個字符可能出現一個空字符。

[word.lower() for word in listOfTokens if len(word)>2]
此語句就將分離後的單詞中的空字符串剔除,同時,考慮到URL地址中有py,en等出現,爲了剔除這些字符,

使用iflen(word)>2,而沒有使用if len(word)>0.

7、拉普拉斯平滑

樸素貝葉斯方法有個缺點就是對數據係數問題過於敏感。

比如測試集中出現訓練集中未出現的單詞,則在參數估計(求p0V和p1V)時,求得的概率是零(樸素貝葉斯假設,連乘求結果),顯然是不合理的。

因此,假設每個單詞均出現過一次。具體操作:

初始化時,分子部分改爲

p0Num = ones(numWords)  
p1Num = ones(numWords)

分母部分加k(即類別數目,本文只有兩類:垃圾郵件和非垃圾郵件)

p0Denom = 2.0
p1Denom = 2.0
8、防止下溢出

根據樸素貝葉斯假設,採用連乘操作求概率,而其中有很小的值,因此最終結果可能過小,四捨五入後結果爲零。

解決方法:採用取對數的操作。因爲取對數操作後,f(x)與logf(x)在相同區域內同時增減且在相同點上取得極值。

9、運行問題

(1)'gbk' codec can't decode byte 0xae in position 199: illegal multibyte sequence

解決方法:ham文件夾中23.txt文件中亂碼,將第二段的SciFinance?is a derivatives pricing...問好換成空格。

(2)'range' object doesn't support item deletion

解決方法:python3中range返回一個range對象而不是列表。

將trainingSet = range(50)改爲trainingSet = list(range(50))

10、程序中從50個郵件中隨機選取10個作爲測試集,其餘40爲訓練集。

因此,每次運行結果可能不同。可以看做是留存交叉驗證,通過多次運行取平均錯誤率。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章