1. 程式人生 > >R語言與機器學習學習筆記(分類演算法)(3)樸素貝葉斯

R語言與機器學習學習筆記(分類演算法)(3)樸素貝葉斯

演算法三:樸素貝葉斯演算法

在貝葉斯決策中,對於先驗概率p(y),分為已知和未知兩種情況。
1. p(y)已知,直接使用貝葉斯公式求後驗概率即可;
2. p(y)未知,可以使用聶曼-皮爾遜決策(N-P決策)來計算決策面。
最大最小損失規則主要就是使用解決最小損失規則時先驗概率未知或難以計算的問題的。

前兩個演算法都被要求做出一個艱難的決定,給出資料所屬分類的明確答案,但往往因為分類特徵統計不足,或者分類特徵選擇有誤導致了錯誤的分類結果,哪怕是訓練集也有可能出現不能正確分類的情形。這時,前兩種方法都如同現實生活一樣是用“少數服從多數”的辦法來做出決策。正如帕斯卡指出的:“少數服從多數未必是因為多數人更正義,而是多數人更加強力”,所以為了保證“少數人的權利”,我們要求分類器給出一個最優的猜測結果,同時給出猜測的概率估計值。

貝葉斯統計基礎

在說樸素貝葉斯演算法之前,還是要說說貝葉斯統計,關於貝葉斯統計,這裡只給出最最最基本的簡述,其餘的還請參閱further reading中的《數學之美番外篇:平凡而又神奇的貝葉斯方法》

先說貝葉斯公式:

定義:設A、B是兩個事件,且P(A)>0,稱

P(B|A)=P(AB)/P(A)

為在事件A發生的條件下事件B發生的條件概率。

相關公式:

乘法公式 P(XYZ)=P(Z|XY)P(Y|X)P(X)

全概率公式 P(X)=P(X|Y1)+ P(X|Y2)+…+ P(X|Yn)

貝葉斯公式:

如上所示,其中P(A|B)是在B發生的情況下A發生的可能性。在貝葉斯定理中,每個名詞都有約定俗成的名稱:

P(A)是A的先驗概率或邊緣概率。之所以稱為"先驗"是因為它不考慮任何B方面的因素。

P(A|B)是已知B發生後A的條件概率(直白來講,就是先有B而後=>才有A),也由於得自B的取值而被稱作A的後驗概率。

P(B|A)是已知A發生後B的條件概率(直白來講,就是先有A而後=>才有B),也由於得自A的取值而被稱作B的後驗概率。

P(B)是B的先驗概率或邊緣概率,也作標準化常量。

按這些術語,Bayes定理可表述為:後驗概率 = (相似度*先驗概率)/標準化常量,也就是說,後驗概率與先驗概率和相似度的乘積成正比。另外,比例 P(B|A)/P(B)也有時被稱作標準相似度,Bayes定理可表述為:後驗概率 =標準相似度*先驗概率。

樸素貝葉斯

再說說樸素貝葉斯,樸素貝葉斯在英文中叫做naive Bayes,是不是這個貝葉斯方法too simple,sometimes naive呢?我們一起來看看他的基本假設:條件獨立性。

給定類標號A,樸素貝葉斯分類器在估計類條件概率時假設屬性之間條件獨立。條件獨立假設可以形式化的表達如下:

P(B|A)=P(b1|A)*P(b2|A)*…*P(bn|A)

其中每個訓練樣本可用一個屬性向量B=(b1,b2,b3,…,bn)表示,各個屬性之間條件獨立。

比如,對於一篇文章,

“Good good study, Day day up.”

可以用一個文字特徵向量來表示,x=(Good, good, study, Day, day , up)。一般各個詞語之間肯定不是相互獨立的,有一定的上下文聯絡。但在樸素貝葉斯文字分類時,我們假設個單詞之間沒有聯絡,可以用一個文字特徵向量來表示這篇文章,這就是“樸素”的來歷。

有了條件獨立假設,就不必計算X和Y的每一種組合的類條件概率,只需對給定的Y,計算每個xi的條件概率。後一種方法更實用,因為它不需要很大的訓練集就能獲得較好的概率估計。

其實這種條件獨立也不是在日常中看不到,比如Markov過程,再比如我們前面說的脊椎動物資料集的各個指標都可以看作條件獨立的(前者是嚴格的,後者是近似的)

我們為了說明這個問題,使用Tom Mitchell的《機器學習》一書的playing tennis資料集(點選這裡下載本文所有程式碼及用到資料集)來說明這個問題。R程式碼如下:

data <-read.csv("D:/R/data/playing tennis.csv")

data<-data[,-1]#去掉了日期這一個沒有可作為分類變數價值的變數

prior.yes<-sum(data[,5] =="Yes") / length(data[,5]);

prior.no<-sum(data[,5] =="No") / length(data[,5]);

bayespre<- function(condition) {

post.yes <-

sum((data[,1] == condition[1]) & (data[,5] == "Yes")) /sum(data[,5] == "Yes") *

sum((data[,2] == condition[2]) & (data[,5] == "Yes")) /sum(data[,5] == "Yes") *

sum((data[,3] == condition[3]) & (data[,5] == "Yes")) /sum(data[,5] == "Yes") *

sum((data[,4] == condition[4]) & (data[,5] == "Yes")) /sum(data[,5] == "Yes") *

prior.yes;

post.no <-

sum((data[,1] == condition[1]) & (data[,5] == "No")) /sum(data[,5] == "No") *

sum((data[,2] == condition[2]) & (data[,5] == "No")) /sum(data[,5] == "No") *

sum((data[,3] == condition[3]) & (data[,5] == "No")) /sum(data[,5] == "No") *

sum((data[,4] == condition[4]) & (data[,5] == "No")) /sum(data[,5] == "No") *

prior.no;

return(list(prob.yes = post.yes,

prob.no = post.no,

prediction = ifelse(post.yes>=post.no, "Yes", "No")));

}

測試:

bayespre(c("Rain","Hot","High","Strong"))

bayespre(c("Sunny","Mild","Normal","Weak"))

bayespre(c("Overcast","Mild","Normal","Weak"))

上面三個測試集輸出結果為:

>bayespre(c("Rain","Hot","High","Strong"))

$prob.yes

[1] 0.005291005

$prob.no

[1] 0.02742857

$prediction

[1] "No"

>bayespre(c("Sunny","Mild","Normal","Weak"))

$prob.yes

[1] 0.02821869

$prob.no

[1] 0.006857143

$prediction

[1] "Yes"

>bayespre(c("Overcast","Mild","Normal","Weak"))

$prob.yes

[1] 0.05643739

$prob.no

[1] 0

$prediction

[1] "Yes"

我們同樣可以來訓練一下我們之前提到的脊椎動物資料集(略去程式碼),來看看分類效果:

>bayespre(animals,c("no","yes","no","sometimes","yes"))

$prob.mammals

[1] 0

$prob.amphibians

[1] 0.1

$prob.fishes

[1] 0

$prob.reptiles

[1] 0.0375

$prediction

[1] amphibians

Levels: amphibians birds fishesmammals reptiles

這裡我們仍然沒有區分出是兩棲動物還是爬行動物,但是至少它告訴我們選擇時要考慮到爬行動物這種可能,而不是像決策樹那樣告訴你他是兩棲動物。

>bayespre(animals,c("no","yes","no","yes","no"))

$prob.mammals

[1] 0.0004997918

$prob.amphibians

[1] 0

$prob.fishes

[1] 0.06666667

$prob.reptiles

[1] 0

$prediction

[1] fishes

Levels: amphibians birds fishesmammals reptiles

這個是第三條資料作為測試資料的,也就是得到了正確分類,他告訴我們有極小的可能他是哺乳動物,我們可以忽略它,畢竟兩個概率相差太大了

> bayespre(animals,c("yes","no","no","yes","no"))

$prob.mammals

[1] 0.0179925

$prob.amphibians

[1] 0

$prob.fishes

[1] 0.01666667

$prob.reptiles

[1] 0

$prediction

[1] mammals

Levels: amphibians birds fishesmammals reptiles

這個分類相當不好,兩個分類的概率也相差無幾,我們確實需要考慮。

這至少告訴了我們兩個事實:這個學習器的分類效果不太好;這個資料集的生物特徵統計資訊不夠。

除此以外,我們還發現這個學習器處理不了他沒見過的情況,以playing tennis資料為例:

假設有來了一個新樣本 x1= (Outlook = Foggy,Temprature = Hot,Humidity = High,Wind =Strong),要求對其分類。我們來開始計算:

>bayespre(c("foggy","Hot","High","Strong"))

$prob.yes

[1] 0

$prob.no

[1] 0

$prediction

[1] "Yes"

計算到這裡,大家就會意識到,這裡出現了一個新的屬性值,在訓練樣本中所沒有的。如果有一個屬性的類條件概率為0,則整個類的後驗概率就等於0,我們可以直接得到後驗概率P(Yes | x1)= P(No | x1)=0,這時二者相等,無法分類。(雖說程式設定時我遵從疑罪從無的思想偏向了正例)

當訓練樣本不能覆蓋那麼多的屬性值時,都會出現上述的窘境。簡單的使用樣本比例來估計類條件概率的方法太脆弱了,尤其是當訓練樣本少而屬性數目又很大時。

如何解決?引入m估計(m-estimate)方法來估計條件概率:

P(xi|yj)=(nc+mp)/(n+m)

n是類yj中的樣本總數,nc是類yj中取值xi的樣本數,m是稱為等價樣本大小的引數,而p是使用者指定的引數。如果沒有訓練集(即n=0),則P(xi|yj)=p, 因此p可以看作是在類yj的樣本中觀察屬性值xi的先驗概率。等價樣本大小決定先驗概率和觀測概率nc/n之間的平衡,提高了估計的穩健性。

樸素貝葉斯方法是一個很特別的方法,所以值得介紹一下。在眾多的分類模型中,應用最為廣泛的兩種分類模型是決策樹模型(Decision Tree Model)和樸素貝葉斯模型(Naive Bayes Model,NBC)。樸素貝葉斯模型發源於古典數學理論,有著堅實的數學基礎,以及穩定的分類效率。

同時,NBC模型所需估計的引數很少,對缺失資料不太敏感,演算法也比較簡單。理論上,NBC模型與其他分類方法相比具有最小的誤差率。但是實際上並非總是如此,這是因為NBC模型假設屬性之間相互獨立,這個假設在實際應用中往往是不成立的,這給NBC模型的正確分類帶來了一定影響。在屬性個數比較多或者屬性之間相關性較大時,NBC模型的分類效率比不上決策樹模型。而在屬性相關性較小時,NBC模型的效能最為良好。(所以在文字分類時能夠用關鍵詞就更好了)

R語言中Naive Bayes的實現函式

R的e1071包的naiveBayes函式提供了naive bayes的具體實現,其用法如下:

## S3 method for class 'formula'

naiveBayes(formula, data, laplace = 0, ..., subset, na.action = na.pass)

## Default S3 method:

naiveBayes(x, y, laplace = 0, ...)

我們以titanic資料集為例,看看titanic上的符合怎樣條件的人更容易得救:

data(Titanic)

m <- naiveBayes(Survived ~ ., data = Titanic)

m

R中的文字處理工具

在介紹貝葉斯文字挖掘之前,我想我們先得把R處理文字的一些工具簡單的介紹一下,比如處理文字的tm包,R語言處理正則表示式之類的,關於tm包你可以參閱的是tm的幫助文件《Introduction to the tm Package Text Mining in R》,關於正則表示式可以參閱furtherreading 的《文字(字串)處理與正則表示式》

先說tm包,在tm 中匯入資料需要函式Corpus(或VCorpus),函式的用法如下:

Corpus(x,

readerControl = list(reader = x$DefaultReader, language = "en"),...)

對於這些資料來源(x),tm 包提供了一些相關的函式,比如:DirSource(處理目錄)、 VectorSource(由文件構成的向量)、 DataframeSource(資料框)等。

一旦匯入了訓練文件,需要後續文件處理,比如填充、停止詞去除。(在英文裡,有些單詞是會發生變化,比如我們要識別cat 這個字元,但還可能有cats 等單詞,這時候就需要進行填充(stemming)將他們視為一個詞,但遺憾的是在tm包中英文中有些不規則的動詞過去式可能沒法識別為一個詞)在tm 包裡,這些函式都歸到資訊轉化裡面,其主要函式就是tm_map(),這個函式可以通過maps 方式將轉化函式實施到每一個單詞上。tm_map()的主要用法如下:

tm_map(x, FUN, ..., useMeta = FALSE, lazy = FALSE)

提供的FUN常用的有as.PlainTextDocument(將xml轉化為純文字)、stripWhitespace(去除多餘空白)、tolower(轉化為小寫)、removeWords(去除停止詞)、stemDocument(填充)等。

Dictionary() 函式常用於在文字挖掘中展現相關的詞條時。當將字典(Dictionary)傳遞到DocumentTermMatrix() 以後,生成的矩陣會根據字典提取計算詞彙出現在每篇文件的頻率。(這個在之後會有例子,就不多說了)

再介紹字串的處理,分割函式:strsplit。使用格式為:

strsplit(x, split, fixed = FALSE, perl = FALSE, useBytes = FALSE)

主要引數說明:

X:字串向量,每個元素都將單獨進行拆分。

Split:為拆分位置的字串向量,預設為正則表示式匹配(fixed=FALSE)fixed=TRUE,表示使用普通文字匹配或正則表示式的精確匹配。

Perl:表示可以選擇是否相容Perl的正則表示式的表述方式。

樸素貝葉斯在文字挖掘中的演算法

下面開始介紹Naive Bayes演算法:

計算每個類別中的文件數目:

for每篇訓練文件:

for每個類別:

if 詞條in 文件:增加該詞條計數值,增加所有詞條計數值

For 每個類別:

For 每個詞條

Prob=詞條數目/總詞條數目

Return prob

舉例說明,比如我要對我最近閱讀的文獻,有spc與doe兩類,他們的關鍵詞列表如下(部分關鍵詞)

docIdKey wordclass

1“Adaptive weighting” “run length” “control chart”spc

2“run length” “control chart” spc

3“control chart” “EWMA” “run length”spc

4“D-Efficiency” ”Main Effect” “Quadratic Effect”doe

給定一個新樣本”control chart” “run length””main effect” “EWMA”,對其進行分類。

該文字用屬性向量表示為d=(”control chart” ,“run length” ,”main effect” ,“EWMA”),類別集合為Y={spc,doe}。類spc下總共有8個單詞,類doe下總共有3個單詞,訓練樣本單詞總數為11,因此P(spc)=8/11, P(doe)=3/11。部分類條件概率計算如下(使用m估計):

P(”control chart”| spc)=(3+1)/(8+7)=4/15=2/7

P(”main effect”| spc) = (0+1)/(8+7)=1/15

P(”control chart”|doe)=(0+1)/(7+3)=0.1

分母中的8,是指spc類別下訓練樣本的關鍵詞總數,7是指訓練樣本有有7個不同的關鍵詞,3是指doe類下共有3個關鍵詞詞。

利用類條件概率,開始計算後驗概率,

P(spc |d)=4/15*4/15*1/15*8/11≈0.003447811

P(doe|d)= 0.1*0.1*0.2*0.1*3/11≈5.454545e-05

因此,這個文件屬於類別spc。

基於樸素貝葉斯的郵件分類

下面來說說樸素貝葉斯分類器在文字分類中的應用。下面是一個使用Naive Bayes分類垃圾郵件的很小的例子,資料來自《機器學習實戰》,資料集已上傳至百度雲盤。

這個郵件集合分為兩個資料夾ham,spam,各有25封郵件。其中spam中的是廣告郵件。這裡我們從各資料夾中抽取2封作為測試集,其餘作為訓練集。作為垃圾郵件分類器,我們總假定不能判決(即兩個類別的概率相差不足一個數量級的,這個判別標準需要根據訓練的情況確定)的為正常郵件。

R程式碼:

1、建立詞袋:

library(tm)

txt1<-"D:/R/data/email/ham"

txtham<-Corpus(DirSource(txt1),readerControl=list(language= "en"))

txtham<-tm_map(txtham,stripWhitespace)

txtham<-tm_map(txtham,tolower)

txtham<-tm_map(txtham,removeWords,stopwords("english"))

txtham<-tm_map(txtham,stemDocument)

txt2<-"D:/R/data/email/spam"

txtspam<-Corpus(DirSource(txt2),readerControl=list(language= "en"))

txtspam<-tm_map(txtspam,stripWhitespace)

txtspam<-tm_map(txtspam,tolower)

txtspam<-tm_map(txtspam,removeWords,stopwords("english"))

txtspam<-tm_map(txtspam,stemDocument)

2、詞彙計數(包括詞類數目與詞量數目)

dtm1<-DocumentTermMatrix(txtham)

n1<-length(findFreqTerms(dtm1,1))

dtm2<-DocumentTermMatrix(txtspam)

n2<-length(findFreqTerms(dtm2,1))

setwd("D:/R/data/email/spam")

name<-list.files(txt2)

data1<-paste("spam",1:23)

lenspam<-0

for(i in 1:length(names)){

assign(data1[i],scan(name[i],"character"))

lenspam<-lenspam+length(get(data[i]))

}

setwd("D:/R/data/email/ham")

names<-list.files(txt1)

data<-paste("ham",1:23)

lenham<-0

for(i in 1:length(names)){

assign(data[i],scan(names[i],"character"))

lenham<-lenham+length(get(data[i]))

}

3、naive Bayes模型建立(使用m估計,p=1/m,m為詞彙總數)

prob<-function(char,corp,len,n){

d<-Dictionary(char)

re<-DocumentTermMatrix(corp, list(dictionary = d));

as.matrix(re)

dtm<-DocumentTermMatrix(corp)

n<-length(findFreqTerms(dtm, 1))

prob<-(sum(re[,1])+1)/(n+len)

return(prob)

}

testingNB<-function(sentences){

pro1<-0.5

pro2<-0.5

for(i in1:length(sentences)){

pro1<-pro1*prob(sentences[i],txtham,lenham,n1)

}

for(i in1:length(sentences)){

pro2<-pro2*prob(sentences[i],txtspam,lenspam,n2)

}

return(list(prob.ham = pro1,

prob.span =pro2,

prediction =ifelse(pro1>=pro2/10, "ham", "spam")))

}

4、測試(利用test裡的4封郵件,僅以ham2.txt,spam1.txt為例)

#讀取文件,並且實現分詞與填充

email<-scan("D:/R/data/email/test/ham2.txt","character")

sentences<-unlist(strsplit(email,",|\\?|\\;|\\!"))#分詞

library(Snowball)#實現填充

a<-tolower(SnowballStemmer(sentences))# 實現填充併除去大小寫因素

#測試

testingNB(a)

輸出結果:

$prob.ham

[1] 3.537766e-51

$prob.span

[1] 4.464304e-51

$prediction

[1] "ham"

類似的使用spam1.txt得到結果:

$prob.ham

[1] 5.181995e-95

$prob.span

[1] 1.630172e-84

$prediction

[1] "spam"

可以看出分類效果一般,但是典型的廣告郵件還是可以區分出來的。對於分類而言,我們使用tm包來做naive Bayes多少有些高射炮打蚊子的意思,因為這裡面除了去除停止詞外我們幾乎沒有用到tm的更多不能利用base包實現的東西了(比如詞類統計可以使用table,字典的查詢可以使用grep, regexpr來實現它)。但是去掉停止詞對於這樣的文字分類是十分重要的,可以讓分類準確率更高。

另外,tm在文字挖掘方面還有很多值得學習的東西,我也正在學習如何使用它。附上一個十分有價值的學習連線:劉思喆的《R語言環境下的文字挖掘》本文也有很大一部分是在參考他的成果上完成的。

Further Reading:

關於bayesian統計:劉未鵬:數學之美番外篇:平凡而又神奇的貝葉斯方法

關於R語言文字挖掘:Bentley-2012:R語言tm工具包進行文字挖掘實驗

劉思喆:R語言環境下的文字挖掘

關於tm包:Ingo Feinerer:Introduction to the tm Package Text Mining in R(R提供的tm包介紹)

關於R語言文字處理:xx: R語言進階之二:文字(字串)處理與正則表示式