Sheldon鎮(zhèn)樓~~~
貝葉斯定理是英國數(shù)學家貝葉斯提出的是趴,當時的目標是要解決所謂「逆概率」問題。在前貝葉斯時代處理概率問題的時候掸驱,總是先取一定假設(shè)(比如拋硬幣時毕贼,每次出現(xiàn)正反面的概率相同)鬼癣,然后在假設(shè)下討論一定事件的概率(比如說連續(xù)出現(xiàn) 10 次正面的概率)待秃≌掠簦「逆概率」則反過來考慮問題,比如說聊替,如果連續(xù)出現(xiàn) 10 次正面佃牛,我們想知道一次拋硬幣時出現(xiàn)正反面的概率俘侠。貝葉斯定理的相關(guān)論文在貝葉斯去世后才發(fā)表,此后法國大數(shù)學家拉普拉斯對這一理論進行了深入的研究爷速,使之成為我們今天使用的形式惫东,如下圖所示。
此公式表示兩個互換的條件概率之間的關(guān)系颓遏,他們通過聯(lián)合概率關(guān)聯(lián)起來叁幢,這樣使得我們知道P(D|H)的情況下去計算P(H|D)成為了可能,而我們的貝葉斯模型便是通過貝葉斯準則去計算某個樣本在不同類別條件下的條件概率并取具有最大條件概率的那個類型作為分類的預(yù)測結(jié)果黍判。
實現(xiàn)簡單貝葉斯分類器
下面以進行文本分類為目的使用Python實現(xiàn)一個樸素貝葉斯文本分類器.
為了計算條件概率顷帖,我們需要計算各個特征的在不同類別下的條件概率以及類型的邊際概率窟她,這就需要我們通過大量的訓練數(shù)據(jù)進行統(tǒng)計獲取近似值了,這也就是我們訓練我們樸素貝葉斯模型的過程.
針對不同的文本蔼水,我們可以將所有出現(xiàn)的單詞作為數(shù)據(jù)特征向量震糖,統(tǒng)計每個文本中出現(xiàn)詞條的數(shù)目(或者是否出現(xiàn)某個詞條)作為數(shù)據(jù)向量。這樣一個文本就可以處理成一個整數(shù)列表趴腋,并且長度是所有詞條的數(shù)目吊说,這個向量也許會很長,用于本文的數(shù)據(jù)集中的短信詞條大概一共3000多個單詞优炬。
def get_doc_vector(words, vocabulary):
''' 根據(jù)詞匯表將文檔中的詞條轉(zhuǎn)換成文檔向量
:param words: 文檔中的詞條列表
:type words: list of str
:param vocabulary: 總的詞匯列表
:type vocabulary: list of str
:return doc_vect: 用于貝葉斯分析的文檔向量
:type doc_vect: list of int
'''
doc_vect = [0]*len(vocabulary)
for word in words:
if word in vocabulary:
idx = vocabulary.index(word)
doc_vect[idx] = 1
return doc_vect
統(tǒng)計訓練的過程:
def train(self, dataset, classes):
''' 訓練樸素貝葉斯模型
:param dataset: 所有的文檔數(shù)據(jù)向量
:type dataset: MxN matrix containing all doc vectors.
:param classes: 所有文檔的類型
:type classes: 1xN list
:return cond_probs: 訓練得到的條件概率矩陣
:type cond_probs: dict
:return cls_probs: 各種類型的概率
:type cls_probs: dict
'''
# 按照不同類型記性分類
sub_datasets = defaultdict(lambda: []) # defaultdict:當字典里的key不存在但被查找時颁井,返回的不是keyError而是一個默認值
cls_cnt = defaultdict(lambda: 0)
for doc_vect, cls in zip(dataset, classes):
sub_datasets[cls].append(doc_vect)
cls_cnt[cls] += 1
# 計算類型概率
cls_probs = {k: v/len(classes) for k, v in cls_cnt.items()}
# 計算不同類型的條件概率
cond_probs = {}
dataset = np.array(dataset)
for cls, sub_dataset in sub_datasets.items():
sub_dataset = np.array(sub_dataset)
# Improve the classifier.
cond_prob_vect = np.log((np.sum(sub_dataset, axis=0) + 1)/(np.sum(dataset) + 2))
cond_probs[cls] = cond_prob_vect
return cond_probs, cls_probs
注意這里對于基本的條件概率直接相乘有兩處改進:
- 各個特征的概率初始值為1,分母上統(tǒng)計的某一類型的樣本總數(shù)的初始值是1蠢护,這是為了避免如果有一個特征統(tǒng)計的概率為0雅宾,則聯(lián)合概率也為零那自然沒有什么意義了, 如果訓練樣本足夠大時,并不會對比較結(jié)果產(chǎn)生影響.
- 由于各個獨立特征的概率都是小于1的數(shù)眉抬,累積起來必然會是個更小的書,這會遇到浮點數(shù)下溢的問題,因此在這里我們對所有的概率都取了對數(shù)處理,這樣在保證不會有損失的情況下避免了下溢的問題垃你。
獲取了統(tǒng)計概率信息后,我們便可以通過貝葉斯準則預(yù)測我們數(shù)據(jù)的類型了毒坛,這里我并沒有直接計算每種情況的概率腿箩,而是通過統(tǒng)計得到的向量與數(shù)據(jù)向量進行內(nèi)積獲取條件概率的相對值并進行相對比較做出決策的暇韧。
def classify(self, doc_vect, cond_probs, cls_probs):
''' 使用樸素貝葉斯將doc_vect進行分類.
'''
pred_probs = {}
for cls, cls_prob in cls_probs.items():
cond_prob_vect = cond_probs[cls]
pred_probs[cls] = np.sum(cond_prob_vect*doc_vect) + np.log(cls_prob)
return max(pred_probs, key=pred_probs.get)
進行短信分類
已經(jīng)構(gòu)建好了樸素貝葉斯模型涂乌,我們就可以使用此模型來統(tǒng)計數(shù)據(jù)并用來預(yù)測了。這里我使用了SMS垃圾短信語料庫中的垃圾短信數(shù)據(jù), 并隨機抽取90%的數(shù)據(jù)作為訓練數(shù)據(jù)荧库,剩下10%的數(shù)據(jù)作為測試數(shù)據(jù)來測試我們的貝葉斯模型預(yù)測的準確性。
當然在建立模型前我們需要將數(shù)據(jù)處理成模型能夠處理的格式:
ENCODING = 'ISO-8859-1'
TRAIN_PERCENTAGE = 0.9
def get_doc_vector(words, vocabulary):
''' 根據(jù)詞匯表將文檔中的詞條轉(zhuǎn)換成文檔向量
:param words: 文檔中的詞條列表
:type words: list of str
:param vocabulary: 總的詞匯列表
:type vocabulary: list of str
:return doc_vect: 用于貝葉斯分析的文檔向量
:type doc_vect: list of int
'''
doc_vect = [0]*len(vocabulary)
for word in words:
if word in vocabulary:
idx = vocabulary.index(word)
doc_vect[idx] = 1
return doc_vect
def parse_line(line):
''' 解析數(shù)據(jù)集中的每一行返回詞條向量和短信類型.
'''
cls = line.split(',')[-1].strip()
content = ','.join(line.split(',')[: -1])
word_vect = [word.lower() for word in re.split(r'\W+', content) if word]
return word_vect, cls
def parse_file(filename):
''' 解析文件中的數(shù)據(jù)
'''
vocabulary, word_vects, classes = [], [], []
with open(filename, 'r', encoding=ENCODING) as f:
for line in f:
if line:
word_vect, cls = parse_line(line)
vocabulary.extend(word_vect)
word_vects.append(word_vect)
classes.append(cls)
vocabulary = list(set(vocabulary))
return vocabulary, word_vects, classes
有了上面三個函數(shù)我們就可以直接將我們的文本轉(zhuǎn)換成模型需要的數(shù)據(jù)向量,之后我們就可以劃分數(shù)據(jù)集并將訓練數(shù)據(jù)集給貝葉斯模型進行統(tǒng)計。
# 訓練數(shù)據(jù) & 測試數(shù)據(jù)
ntest = int(len(classes)*(1-TRAIN_PERCENTAGE))
test_word_vects = []
test_classes = []
for i in range(ntest):
idx = random.randint(0, len(word_vects)-1)
test_word_vects.append(word_vects.pop(idx))
test_classes.append(classes.pop(idx))
train_word_vects = word_vects
train_classes = classes
train_dataset = [get_doc_vector(words, vocabulary) for words in train_word_vects]
訓練模型:
cond_probs, cls_probs = clf.train(train_dataset, train_classes)
剩下我們用測試數(shù)據(jù)來測試我們貝葉斯模型的預(yù)測準確度:
# 測試模型
error = 0
for test_word_vect, test_cls in zip(test_word_vects, test_classes):
test_data = get_doc_vector(test_word_vect, vocabulary)
pred_cls = clf.classify(test_data, cond_probs, cls_probs)
if test_cls != pred_cls:
print('Predict: {} -- Actual: {}'.format(pred_cls, test_cls))
error += 1
print('Error Rate: {}'.format(error/len(test_classes)))
參考鏈接:
https://www.jiqizhixin.com/articles/2017-09-19-6
https://zhuanlan.zhihu.com/p/27906640