前言
正文
與決策樹分類和k近鄰分類算法不同,貝葉斯分類主要借助機率論的知識來通過比較提供的資料屬于每個類型的條件機率,
将他們分别計算出來然後預測具有最大條件機率的那個類别是最後的類别。當然樣本越多我們統計的不同類型的特征值分布就越準确,使用此分布進行預測則會更加準确。
貝葉斯準則
樸素貝葉斯分類器中最核心的便是貝葉斯準則,他用如下的公式表示:
此公式表示兩個互換的條件機率之間的關系,他們通過聯合機率關聯起來,這樣使得我們知道p(b|a) 的情況下去計算p(a|b)
成為了可能,而我們的貝葉斯模型便是通過貝葉斯準則去計算某個樣本在不同類别條件下的條件機率并取具有最大條件機率的那個類型作為分類的預測結果。
使用條件機率來進行分類
這裡我通俗的介紹下如何通過條件機率來進行分類,假設我們看到了一個人的背影,想通過他背影的一些特征(資料)來判斷這個人的性别(類别),假設其中涉及到的特征有:
是否是長發, 身高是否在170以上,腿細,是否穿裙子。當我們看到一個背影便會得到一個特征向量用來描述上述特征(1表示是,0表示否):
ω=[0,1,1,0]
貝葉斯分類便是比較如下兩個條件機率:
p(男生|ω) ,ω 等于 [0,1,1,0 的條件下此人是男生的機率
p(女生|ω)) ,ω 等于 [0,1,1,0] 的條件下此人是女生的機率
若p(男生|ω)>p(女生|ω) , 則判定此人為男生, 否則為女生
那麼p(男生|ω) 怎麼求呢? 這就要上貝葉斯準則了
根據貝葉斯準則,
寫成好了解些的便是:
如果特征之間都是互相獨立的(條件獨立性假設),那麼便可以将上述條件機率改寫成:
這樣我們就能計算目前這個背影屬于男生和屬于女生的條件機率了。
實作自己的貝葉斯分類器
貝葉斯分類器實作起來非常的簡單, 下面我以進行文本分類為目的使用python實作一個樸素貝葉斯文本分類器.
為了計算條件機率,我們需要計算各個特征的在不同類别下的條件機率以及類型的邊際機率,這就需要我們通過大量的訓練資料進行統計擷取近似值了,這也就是我們訓練我們樸素貝葉斯模型的過程.
針對不同的文本,我們可以将所有出現的單詞作為資料特征向量,統計每個文本中出現詞條的數目(或者是否出現某個詞條)作為資料向量。這樣一個文本就可以處理成一個整數清單,并且長度是所有詞條的數目,這個向量也許會很長,用于本文的資料集中的短信詞條大概一共3000多個單詞。
def get_doc_vector(words, vocabulary):
''' 根據詞彙表将文檔中的詞條轉換成文檔向量
: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 train(self, dataset, classes):
''' 訓練樸素貝葉斯模型
:param dataset: 所有的文檔資料向量
: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: [])
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,分母上統計的某一類型的樣本總數的初始值是1,這是為了避免如果有一個特征統計的機率為0,則聯合機率也為零那自然沒有什麼意義了, 如果訓練樣本足夠大時,并不會對比較結果産生影響.
由于各個獨立特征的機率都是小于1的數,累積起來必然會是個更小的書,這會遇到浮點數下溢的問題,是以在這裡我們對所有的機率都取了對數處理,這樣在保證不會有損失的情況下避免了下溢的問題。
擷取了統計機率資訊後,我們便可以通過貝葉斯準則預測我們資料的類型了,這裡我并沒有直接計算每種情況的機率,而是通過統計得到的向量與資料向量進行内積擷取條件機率的相對值并進行相對比較做出決策的。
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)
進行短信分類
已經建構好了樸素貝葉斯模型,我們就可以使用此模型來統計資料并用來預測了。這裡我使用了sms垃圾短信語料庫中的垃圾短信資料, 并随機抽取90%的資料作為訓練資料,剩下10%的資料作為測試資料來測試我們的貝葉斯模型預測的準确性。
當然在建立模型前我們需要将資料處理成模型能夠處理的格式:
encoding = 'iso-8859-1'
train_percentage = 0.9
return doc_vect
def parse_line(line):
''' 解析資料集中的每一行傳回詞條向量和短信類型.
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):
''' 解析檔案中的資料
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
有了上面三個函數我們就可以直接将我們的文本轉換成模型需要的資料向量,之後我們就可以劃分資料集并将訓練資料集給貝葉斯模型進行統計。
# 訓練資料 & 測試資料
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)
剩下我們用測試資料來測試我們貝葉斯模型的預測準确度:
# 測試模型
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)))
随機測了四組,錯誤率分别為:0, 0.037, 0.015, 0. 平均錯誤率為1.3%
測完了我們嘗試下看看不同類型短信各個詞條的機率分布是怎樣的吧:
# 繪制不同類型的機率分布曲線
fig = plt.figure()
ax = fig.add_subplot(111)
for cls, probs in cond_probs.items():
ax.scatter(np.arange(0, len(probs)),
probs*cls_probs[cls],
label=cls,
alpha=0.3)
ax.legend()
plt.show()
試試決策樹
上一篇我們基于id3算法實作了決策樹,同樣是分類問題,我們同樣可以使用我們的文本資料來建構用于分類短信的決策樹,當然唯一比較麻煩的地方在于如果按照與貝葉斯相同的向量作為資料,則屬性可能會非常多,我們在建構決策樹的時候每層樹結構都是遞歸通過周遊屬性根據資訊增益來選取最佳屬性進行樹分裂的,這樣很多的屬性可能會對建構決策樹這一過程來說會比較耗時.那我們就試試吧…
# 生成決策樹
if not os.path.exists('sms_tree.pkl'):
clf.create_tree(train_dataset, train_classes, vocabulary)
clf.dump_tree('sms_tree.pkl')
else:
clf.load_tree('sms_tree.pkl')
pred_cls = clf.classify(test_data, feat_names=vocabulary)
随機測了兩次,錯誤率分别為:0.09, 0.0
效果還算不錯
我們還是用graphviz可視化看一下決策樹都選取了那些詞條作為判别标準(這時候決策樹的好處就展現出來了)。
可見決策樹的深度并不是很深,如果分類類型一多,估計深度增加上去決策樹可能會有些麻煩。
總結
本文我們使用python一步步實作了樸素貝葉斯分類器,并對短信進行了垃圾短信過濾,同樣的資料我們同決策樹的分類效果進行了簡單的比較。本文相關代碼實作:https://github.com/pytlab/mlbox/tree/master/naive_bayes
。決策樹過濾垃圾短信的腳本在https://github.com/pytlab/mlbox/tree/master/decision_tree
參考
《machine learning in action》
執行個體詳解貝葉斯推理的原理
大道至簡:樸素貝葉斯分類器
作者:佚名
來源:51cto