使用Python進行文本分類
要從文本中獲取特徵,需要先拆分文本,具體如何做呢?這裏的特徵是來自文本的詞條,一個詞條是字符的任意組合。可以把詞條想象爲單詞,也可以使用非單詞詞條,如URL、IP地址或者任意其他字符串。然後將每一個文本片段表示爲一個詞條向量,其中值爲1表示詞條出現在文檔中,0表示詞條未出現。
此處以社區留言爲例,爲了過濾侮辱性的言論,我們使用1 和 0 來代表是侮辱類和非侮辱類。
首先會給出將文本轉換爲數字向量的過程,然後介紹如何基於這些向量來計算條件概率,並在此基礎上構建分類器,最後介紹利用Python實現樸素貝葉斯過程中需要考慮的問題。
在進行樸素貝葉斯實戰前,建議先理解樸素貝葉斯的原理,我在自己的博客中介紹過 機器學習初涉–貝葉斯分類
1.準備數據,從文本中構建詞向量
我們將文本看成詞向量或者詞條向量,也就是說將橘子轉換爲向量。考慮出現在所有文檔中的所有單詞,再決定將哪些詞納入詞彙表或者說所要的詞彙集合,然後必須要將每一篇文檔轉換爲詞彙表上的向量。接下來我們正式開始。
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 侮辱性文字, 0 not
return postingList, classVec
def createVocabList(dataSet):
vocabSet = set([]) # create empty set
for document in dataSet:
vocabSet = vocabSet | set(document) # 並集
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet):
"""
:param vocabList: 詞彙表
:param inputSet: 某個文檔
:return: 文檔向量,向量的元素是0/1,分別表示詞彙表中的單詞在輸入文檔中是否出現。
"""
returnVec = [0] * len(vocabList) # 創建一個其中所有元素都是0的向量
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
已經完成了將一組單詞轉換爲一組數字,那麼如何使用這組數字來計算概率。現在已經知道了一個字是否出現在一篇文檔中,也知道文檔所屬的類別。那麼一個留言屬於侮辱性的還是非侮辱性的這兩個概率如何計算?
首先,通過類別i(侮辱性和非侮辱性留言)中文檔數除以總的文檔數來計算概率P(Ci),接下來計算P(W|Ci),這裏需要用到樸素貝葉斯假設,如果將W展開爲一個個獨立的特徵,那麼就可以將上述概率寫作:P(W0,W1,W2..Wn|Ci)。這裏假設所有詞都相互獨立,也稱爲條件獨立性假設,它意味着可以使用P(W0|Ci)P(W1|Ci)P(W2|Ci)..P(Wn|Ci)來計算上述概率。
函數僞代碼:
計算每個類別中的文檔數目
對每篇訓練文檔:
對每個類別:
如果詞條出現在文檔中——>增加該詞條的計數值
增加所有詞條的計數值
對每個類別:
對每個詞條:
將該詞條的數目除以總詞條數得到條件概率(也就是求P(Wn|Ci))
返回每個類別的條件概率
說明:這裏是將每個詞條視爲獨立的一個特徵,因此,需要計算出每一個詞條在Ci中出現的概率。
這部分的代碼如下:
def trainNB0(trainMatrix, trainCategory):
"""
:param trainMatrix: 文檔矩陣
:param trainCategory: 由每篇文檔類別所構成的向量
:return:
"""
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs) # P(Ci)
# 初始化概率.因爲計算P(W0|Ci)*P(W1|Ci)...P(W2|Ci)時會出現其中一個概率爲0,導致最後的結果也爲0,
# 因此,將初始化的出現次數設置爲1,並將默認的詞條總數設置爲2
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
# 對於每一篇文章
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 將出現的詞條對應計數加1
p1Denom += sum(trainMatrix[i]) # 詞條出現的總數加1
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 計算出現概率時,由於大部分因子非常小,會導致程序下溢或者得到不正確的答案,因此,解決辦法是通過求對數
p1Vect = log(p1Num / p1Denom) # 計算第一個類別中的P(W/Ci),即在Ci中每個詞條的出現概率
p0Vect = log(p0Num / p0Denom) # 另一個類別
return p0Vect, p1Vect, pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
:param vec2Classify: 待分類文檔的詞向量數組
:param p0Vec: 第一個類別的各個特徵出現的概率
:param p1Vec: 第二個類別的各個特徵出現的概率
:param pClass1: P(Ci)
:return:
"""
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # element-wise mult
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
def testingNB():
# 加載數據
listOPosts, listClasses = loadDataSet()
# 去重
myVocabList = createVocabList(listOPosts)
trainMat = []
# 創建文檔矩陣
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
# 得到每個特徵在每個類別中出現的概率數組,以及P(Ci)
p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))
# 測試數據
testEntry = ['love', 'my', 'dalmation']
# 計算測試數據的詞向量
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb)
# 第二組測試數據
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb)
運行的結果如下:
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
截止到目前爲止,我們將每個詞的出現與否視爲一個特徵,這可以描述爲詞集模型。如果一個詞在文檔中出現不止一次,這可能意味着僅僅通過詞出現與否表述爲特徵值是不夠的,需要幾記錄下詞的出現次數,這種方法被稱爲詞袋模型。若詞袋模型,可將setOfWordsVec()進行如下修改:
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec