1. 程式人生 > >基於樸素貝葉斯的中文文字分類器(python實現,非呼叫)

基於樸素貝葉斯的中文文字分類器(python實現,非呼叫)

本文將用樸素貝葉斯原理做一箇中文文字分類器。樸素貝葉斯完全可以勝任多分類任務。為了方便,這裡就先做個2分類的。理論部分:https://blog.csdn.net/montecarlostyle/article/details/79870860
我們事先準備兩類中文郵件,一類是有些報刊編輯發的徵稿廣告,另一類是一些支付資訊(正常通訊的郵件太少了,不好找)。
我們的目的
有了理論準備之後,我們知道,如果要完成一個新郵件的預測,我就必須要先知道兩種(注意不是兩個)概率:P(Y=ci)P(X(j)=x(j)|Y=ci).至於這個公式怎麼來的,理論部分有推到,這裡就不再贅述了。
如果這個郵件是這個樣子的:“我是XX編輯,我現在徵XXX稿。……”。那麼在我們這個例子裡,

P(Y=c1) =P(隨便從測試集抽取一個郵件,它的類別 == 徵稿廣告),P(Y=c2) =P(隨便從測試集抽取一個郵件,它的類別 == 支付資訊);還有在兩個類別下,郵件特徵出現的概率:P(X(j)=x(j)|Y=c1)=P(“我是XX編輯,我現在徵XXX稿。……” | 這篇郵件是徵稿廣告),P(X(j)=x(j)|Y=c2)=P(“我是XX編輯,我現在徵XXX稿。……” | 這篇郵件是支付資訊)。
有了這兩類概率,我們對比P(“我是XX編輯,我現在徵XXX稿。……” | 這篇郵件是徵稿廣告)*P(隨便從測試集抽取一個郵件,它的類別 == 徵稿廣告)
$P(“我是XX編輯,我現在徵XXX稿。……” | 這篇郵件是支付資訊)*P(隨便從測試集抽取一個郵件,它的類別 == 支付資訊)的概率,哪個概率大,我們就把這個文字分到哪一類。
把整個文字拆成片語成的列表
問題來了,每個人寫的句子風格都不一樣,很少有兩個句子每個字都相同,直接統計句子的出現頻率是不行的。這就需要再把郵件細化,但是中文又不同與英文,中文一個詞與另一個詞之間沒有空格分隔。這個時候,就需要分詞了。這裡不手動分詞,而使用bosonnlp,它可以達到98%的分詞正確率。需要去官網註冊帳號,註冊並不是很麻煩。具體使用方法請看官方文件。分詞完成之後,還需要對文字進行清洗,去除其中的符號空格和換行符。我們用zhon庫匯出中文標點,然後再用正則表示式匹配去除。分詞例子:“我是XX編輯,我現在徵XX稿”結果是[“我”, “是”, “XX”,“編輯”, “我”, “現在”, “徵”, “XXX”, “稿”]。這樣我們就有了更精細的“詞”作為特徵來計算條件概率了。
生成詞集,把文字轉化成向量

如果給我們一篇中文文字,使用剛才的方法,我們可以得到一個由很多次組成的列表。例如[“我”, “是”, “XX”,“編輯”, “我”, “現在”, “徵”, “XXX”, “稿”],我們要計算條件概率P(“我是XX編輯,我現在徵XX稿。……” | 這篇郵件是徵稿廣告)就變成了P(“我”,”是”,”XX”,”編輯”,“我”,”現在”,”徵”,”XXX”,”稿”|這篇郵件是徵稿廣告)。我們看到,”我”詞出現了兩次。接下來,特徵表述有兩個思路,一個是隻考慮“我”,“是”等詞出現與否,出現計1,不出現計0;另一個思路是考慮這些詞的出現次數。
1.生成詞集,結果:[“我”, “是”, “XX”,“編輯”, “現在”, “徵”, “XXX”, “稿”]。
2.我們採用第二種方式,考慮詞的出現頻率。文字就變成了[2, 1, 1, 1, 1, 1, 1, 1]
實戰中,詞集需要根據很多很多文字的詞生成。所以,某個文字的向量裡很大機率有0的存在。這裡我們使用拉普拉斯平滑,把每個詞出現的頻率統一拉高1次。
把文字向量組成矩陣,計算每個詞出現的頻率
為了方便操作,這裡使用numpy庫。我們接著把所有某類文字向量一行一行疊加,形成矩陣。再縱向相加,就能得到每個詞的出現頻次了(結果是一個行向量)。這個頻數再除以這一類郵件所有詞出現的總次數,就能得到某個特徵出現的條件概率(還是個行向量),為了防止多個較小的概率相乘得到接近0的數,我們對概率取log對數,不影響最終結果。剩下的就剩乘法操作啦。
我們看一下程式的執行結果
from naiveBayes import *
p1Vec, p2Vec, wordSet, pAbusive = bayesTrain(‘email/T/’, ‘email/F/’)
bayesClass(p1Vec, p2Vec, wordSet, pAbusive, ‘email/test1.txt’)

沒統計錯誤率,兩類訓練郵件加起來才10封。看起來效果還不錯…

#----------------------------------------------------- 
#   function:Naive Bayes chinese text classifier 
#   author:hanshuo   
#   date:2018-4-11
#   tools:Python 2.7.6 numpy bosonnlp zhon
#   system:linux or Windows
#   extra:Text separation service from Bosonnlp
#-----------------------------------------------------
#-*- encoding: utf-8 -*-

from __future__ import print_function, unicode_literals
from bosonnlp import BosonNLP
import re
from zhon.hanzi import punctuation
import numpy as np
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

def textGet(fileName):
    f = open(fileName)
    text = f.read()
    return text.strip().strip('\n')

def bossonDivide(text):
#也可以不使用boson,需要替換為其他分詞函式
    nlp = BosonNLP('replace with your boson key')
    #這兒需要把'replace with your boson key'換成你帳號的boson key
    result = nlp.tag(text, space_mode = 0, oov_level = 3, t2s = 1, special_char_conv = 1)

    wordList = []
    for d in result:
        wordList.append(d['word'])

    listOfWords = []
    for eachList in wordList:
        listOfTokens = []
        for eachWord in eachList:
            listOfTokens.append(re.sub(ur"[%s]+" %punctuation, "", eachWord))
        listOfWords.append([eachWord for eachWord in listOfTokens if eachWord != ''])

    return listOfWords


def textTrain(class_1Texts, class_2Texts):
#輸入兩類文字,型別為巢狀列表。輸出兩個文字矩陣,一個詞集
    pass
    wordSet = []
    listOfTrain1 = []
    listOfTrain2 = []

    for eachText in class_1Texts:
        textVec = [0] * len(wordSet)
        for word in eachText:
            if word in wordSet:
                textVec[wordSet.index(word)] += 1
            else:
                wordSet.append(word)
                textVec.append(1)
        listOfTrain1.append(textVec)

    for eachText in class_2Texts:
        textVec = [0] * len(wordSet)
        for word in eachText:
            if word in wordSet:
                textVec[wordSet.index(word)] += 1
            else:
                wordSet.append(word)
                textVec.append(1)
        listOfTrain2.append(textVec)

    numOfWords = len(wordSet)

    for everyRow in listOfTrain1:
        everyRow.extend([0] * (numOfWords - len(everyRow)))
    for everyRow in listOfTrain2:
        everyRow.extend([0] * (numOfWords - len(everyRow)))

    arrayOfTrain1 = np.array(listOfTrain1)
    arrayOfTrain2 = np.array(listOfTrain2)

    return arrayOfTrain1, arrayOfTrain2, wordSet

def bayesTrain(filePath_1, filePath_2):
#輸入兩個資料夾集合,每個資料夾存放一類文字,無視檔名稱。輸出:兩個條件概率向量,一個詞集,一個正例文字比例
    from os import listdir
    file_1Names = listdir(filePath_1)
    file_2Names = listdir(filePath_2)
    text_1Raw = []
    text_2Raw = []
    for eachFile in file_1Names:
        text_1Raw.append(textGet(filePath_1 + eachFile))
    for eachFile in file_2Names:
        text_2Raw.append(textGet(filePath_2 + eachFile))
    text_1 = bossonDivide(text_1Raw)
    text_2 = bossonDivide(text_2Raw)
    #return texts_1, texts_2
    array_1Train, array_2Train, wordSet = textTrain(text_1, text_2)

    p1Num = np.ones(len(wordSet))
    p2Num = np.ones(len(wordSet))
    p1Denom = 2.0
    p2Denom = 2.0
    p1Num = array_1Train.sum(axis = 0) + p1Num
    p2Num = array_2Train.sum(axis = 0) + p2Num
    p1Denom += p1Num.sum()
    p2Denom += p2Num.sum()
    p1Vec = np.log(p1Num/p1Denom)
    p2Vec = np.log(p2Num/p2Denom)
    pAbusive = len(file_1Names) / float(len(file_1Names)+len(file_2Names))
    return p1Vec, p2Vec, wordSet, pAbusive

def classifyNB(vec2Classify,p1Vec,p2Vec,pClass1):
#輸入待測文字向量,兩個條件概率向量,一個正例比例。輸出文字的測試類別
    p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
    p2=sum(vec2Classify*p2Vec)+np.log(1.0-pClass1)
    if p1>p2:
        return 1
    else:
        return 2

def bayesClass(p1Vec, p2Vec, wordSet, pAbusive, testTextName):
#輸入兩個條件概率向量,一個詞集,正例比例,待測文字檔名。函式會打印出這個文字的測試類別
    testTextVec = [0] * len(wordSet)
    testText = textGet(testTextName)
    testWordList = bossonDivide(testText)[0]
    for word in testWordList:
        if word in wordSet:
            testTextVec[wordSet.index(word)] += 1
    arrayTest = np.array(testTextVec)
    c = classifyNB(arrayTest, p1Vec, p2Vec, pAbusive)
    print(c)