基於樸素貝葉斯的中文文字分類器(python實現,非呼叫)
阿新 • • 發佈:2019-01-06
本文將用樸素貝葉斯原理做一箇中文文字分類器。樸素貝葉斯完全可以勝任多分類任務。為了方便,這裡就先做個2分類的。理論部分:https://blog.csdn.net/montecarlostyle/article/details/79870860
我們事先準備兩類中文郵件,一類是有些報刊編輯發的徵稿廣告,另一類是一些支付資訊(正常通訊的郵件太少了,不好找)。
我們的目的
有了理論準備之後,我們知道,如果要完成一個新郵件的預測,我就必須要先知道兩種(注意不是兩個)概率:至於這個公式怎麼來的,理論部分有推到,這裡就不再贅述了。
如果這個郵件是這個樣子的:“我是XX編輯,我現在徵XXX稿。……”。那麼在我們這個例子裡, =P(隨便從測試集抽取一個郵件,它的類別 == 徵稿廣告), =P(隨便從測試集抽取一個郵件,它的類別 == 支付資訊);還有在兩個類別下,郵件特徵出現的概率:=P(“我是XX編輯,我現在徵XXX稿。……” | 這篇郵件是徵稿廣告),=P(“我是XX編輯,我現在徵XXX稿。……” | 這篇郵件是支付資訊)。
有了這兩類概率,我們對比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)