作者:?[寒小陽](http://blog.csdn.net/han_xiaoyang?viewmode=contents)?&&?[龍心塵](http://blog.csdn.net/longxinchen_ml?viewmode=contents)?
時間:2016年2月。?
出處:[http://blog.csdn.net/han_xiaoyang/article/details/50629608](http://blog.csdn.net/han_xiaoyang/article/details/50629608)?
[http://blog.csdn.net/longxinchen_ml/article/details/50629613](http://blog.csdn.net/longxinchen_ml/article/details/50629613)?
聲明:版權所有,轉載請聯系作者并注明出處
### 1.引言
前兩篇博文介紹了樸素貝葉斯這個名字讀著”萌蠢”但實際上簡單直接高效的方法,我們也介紹了一下貝葉斯方法的一些細節。按照老規矩,『鋤頭』給你了,得負責教教怎么用和注意事項,也順便帶大家去除除草對吧。恩,此節作為更貼近實際應用的部分,將介紹貝葉斯方法的優缺點、常見適用場景和可優化點,然后找點實際場景擼點例子練練手,看看工具怎么用。
**PS:本文所有的python代碼和ipython notebook已整理至[github相應頁面](https://github.com/HanXiaoyang/naive_bayes),歡迎下載和嘗試。**
### 2.貝葉斯方法優缺點
既然講的是樸素貝葉斯,那博主保持和它一致的風格,簡單直接高效地丟干貨了:
* 優點
> 1. 對待預測樣本進行預測,**過程簡單速度快**(想想郵件分類的問題,預測就是分詞后進行概率乘積,在log域直接做加法更快)。
> 2. **對于多分類問題也同樣很有效**,復雜度也不會有大程度上升。
> 3. **在分布獨立這個假設成立的情況下**,貝葉斯分類器**效果奇好**,會略勝于邏輯回歸,同時我們**需要的樣本量也更少一點**。
> 4. 對于類別類的輸入特征變量,效果非常好。對于數值型變量特征,我們是默認它符合正態分布的。
* 缺點
> 1. 對于測試集中的一個類別變量特征,如果在訓練集里沒見過,直接算的話概率就是0了,預測功能就失效了。當然,我們前面的文章提過我們有一種技術叫做**『平滑』操作**,可以緩解這個問題,最常見的平滑技術是拉普拉斯估測。
> 2. 那個…咳咳,樸素貝葉斯算出的概率結果,比較大小還湊合,實際物理含義…恩,別太當真。
> 3. 樸素貝葉斯有分布獨立的假設前提,而**現實生活中這些predictor很難是完全獨立的**。
### 3.最常見應用場景
* 文本分類/垃圾文本過濾/情感判別:這大概會樸素貝葉斯應用做多的地方了,即使在現在這種分類器層出不窮的年代,在文本分類場景中,樸素貝葉斯依舊堅挺地占據著一席之地。原因嘛,大家知道的,因為多分類很簡單,同時在文本數據中,分布獨立這個假設基本是成立的。而垃圾文本過濾(比如垃圾郵件識別)和情感分析(微博上的褒貶情緒)用樸素貝葉斯也通常能取得很好的效果。
* 多分類實時預測:這個是不是不能叫做場景?對于文本相關的多分類實時預測,它因為上面提到的優點,被廣泛應用,簡單又高效。
* 推薦系統:是的,你沒聽錯,是用在推薦系統里!!樸素貝葉斯和協同過濾([Collaborative Filtering](https://en.wikipedia.org/wiki/Collaborative_filtering))是一對好搭檔,協同過濾是強相關性,但是泛化能力略弱,樸素貝葉斯和協同過濾一起,能增強推薦的覆蓋度和效果。
### 4.樸素貝葉斯注意點
這個部分的內容,本來應該在最后說的,不過為了把干貨集中放在代碼示例之前,先擱這兒了,大家也可以看完樸素貝葉斯的各種例子之后,回來再看看這些tips。
* 大家也知道,很多特征是連續數值型的,但是它們不一定服從正態分布,一定要想辦法把它們變換調整成滿足正態分布!!
* 對測試數據中的0頻次項,一定要記得平滑,簡單一點可以用『拉普拉斯平滑』。
* 先處理處理特征,把相關特征去掉,因為高相關度的2個特征在模型中相當于發揮了2次作用。
* 樸素貝葉斯分類器一般可調參數比較少,比如[scikit-learn中的樸素貝葉斯](http://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.MultinomialNB.html#sklearn.naive_bayes.MultinomialNB)只有拉普拉斯平滑因子alpha,類別先驗概率class_prior和預算數據類別先驗fit_prior。模型端可做的事情不如其他模型多,因此我們還是集中精力進行數據的預處理,以及特征的選擇吧。
* 那個,一般其他的模型(像logistic regression,SVM等)做完之后,我們都可以嘗試一下bagging和boosting等融合增強方法。咳咳,很可惜,對樸素貝葉斯里這些方法都沒啥用。原因?原因是這些融合方法本質上是減少過擬合,減少variance的。樸素貝葉斯是沒有variance可以減小。
### 5\. 樸素貝葉斯訓練/建模
理論干貨和注意點都說完了,來提提怎么快速用樸素貝葉斯訓練模型吧。博主一直提倡要站在巨人的肩膀上編程(其實就是懶…同時一直很擔憂寫出來的代碼的健壯性…),咳咳,我們又很自然地把scikit-learn拿過來了。scikit-learn里面有3種不同類型的樸素貝葉斯:
* **[高斯分布型](http://scikit-learn.org/stable/modules/naive_bayes.html#gaussian-naive-bayes)**:用于classification問題,假定屬性/特征是服從正態分布的。
* **[多項式型](http://scikit-learn.org/stable/modules/naive_bayes.html#multinomial-naive-bayes)**:用于離散值模型里。比如文本分類問題里面我們提到過,我們不光看詞語是否在文本中出現,也得看出現的次數。如果總詞數為n,出現詞數為m的話,說起來有點像擲骰子n次出現m次這個詞的場景。
* **[伯努利型](http://scikit-learn.org/stable/modules/naive_bayes.html#bernoulli-naive-bayes)**:這種情況下,就如之前博文里提到的bag of words處理方式一樣,最后得到的特征只有0(沒出現)和1(出現過)。
根據你的數據集,可以選擇scikit-learn中以上任意一種樸素貝葉斯,我們直接舉個簡單的例子,用高斯分布型樸素貝葉斯建模:
~~~
# 我們直接取iris數據集,這個數據集有名到都不想介紹了...
# 其實就是根據花的各種數據特征,判定是什么花
from sklearn import datasets
iris = datasets.load_iris()
iris.data[:5]
#array([[ 5.1, 3.5, 1.4, 0.2],
# [ 4.9, 3\. , 1.4, 0.2],
# [ 4.7, 3.2, 1.3, 0.2],
# [ 4.6, 3.1, 1.5, 0.2],
# [ 5\. , 3.6, 1.4, 0.2]])
#我們假定sepal length, sepal width, petal length, petal width 4個量獨立且服從高斯分布,用貝葉斯分類器建模
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
y_pred = gnb.fit(iris.data, iris.target).predict(iris.data)
right_num = (iris.target == y_pred).sum()
print("Total testing num :%d , naive bayes accuracy :%f" %(iris.data.shape[0], float(right_num)/iris.data.shape[0]))
# Total testing num :150 , naive bayes accuracy :0.960000
~~~
你看,樸素貝葉斯分類器,簡單直接高效,在150個測試樣本上,準確率為96%。
### 6.樸素貝葉斯之文本主題分類器
這是樸素貝葉斯最擅長的應用場景之一,對于不同主題的文本,我們可以用樸素貝葉斯訓練一個分類器,然后將其應用在新數據上,預測主題類型。
**6.1 新聞數據分類**
我們使用[搜狐新聞數據](http://www.sogou.com/labs/dl/cs.html)來實驗樸素貝葉斯分類器,這部分新聞數據包括it、汽車、財經、健康等9個類別,簡潔版數據解壓縮后總共16289條新聞,一篇新聞一個txt,我們把數據合并到一個大文件中,一行一篇文章,同時將新聞id(指明新聞的類別)放在文章之前,然后用ICTCLAS(python的話你也可以用[結巴分詞](https://github.com/fxsjy/jieba))進行分詞,得到以下的文本內容:?
?
我們隨機選取3/5的數據作為訓練集,2/5的數據作為測試集,采用互信息對文本特征進行提取,提取出1000個左右的特征詞。然后用樸素貝葉斯分類器進行訓練,實際訓練過程就是對于特征詞,統計在訓練集和各個類別出現的次數,測試階段做預測也是掃描一遍測試集,計算相應的概率。因此整個過程非常高效,完整的運行代碼如下:
~~~
# 這部分代碼基本純手擼的...沒有調用開源庫...大家看看就好...
#!encoding=utf-8
import sys, math, random, collections
def shuffle(inFile):
'''
簡單的亂序操作,用于生成訓練集和測試集
'''
textLines = [line.strip() for line in open(inFile)]
print "正在準備訓練和測試數據,請稍后..."
random.shuffle(textLines)
num = len(textLines)
trainText = textLines[:3*num/5]
testText = textLines[3*num/5:]
print "準備訓練和測試數據準備完畢,下一步..."
return trainText, testText
#總共有9種新聞類別,我們給每個類別一個編號
lables = ['A','B','C','D','E','F','G','H','I']
def lable2id(lable):
for i in xrange(len(lables)):
if lable == lables[i]:
return i
raise Exception('Error lable %s' % (lable))
def doc_dict():
'''
構造和類別數等長的0向量
'''
return [0]*len(lables)
def mutual_info(N,Nij,Ni_,N_j):
'''
計算互信息,這里log的底取為2
'''
return Nij * 1.0 / N * math.log(N * (Nij+1)*1.0/(Ni_*N_j))/ math.log(2)
def count_for_cates(trainText, featureFile):
'''
遍歷文件,統計每個詞在每個類別出現的次數,和每類的文檔數
并寫入結果特征文件
'''
docCount = [0] * len(lables)
wordCount = collections.defaultdict(doc_dict())
#掃描文件和計數
for line in trainText:
lable,text = line.strip().split(' ',1)
index = lable2id(lable[0])
words = text.split(' ')
for word in words:
wordCount[word][index] += 1
docCount[index] += 1
#計算互信息值
print "計算互信息,提取關鍵/特征詞中,請稍后..."
miDict = collections.defaultdict(doc_dict())
N = sum(docCount)
for k,vs in wordCount.items():
for i in xrange(len(vs)):
N11 = vs[i]
N10 = sum(vs) - N11
N01 = docCount[i] - N11
N00 = N - N11 - N10 - N01
mi = mutual_info(N,N11,N10+N11,N01+N11) + mutual_info(N,N10,N10+N11,N00+N10)+ mutual_info(N,N01,N01+N11,N01+N00)+ mutual_info(N,N00,N00+N10,N00+N01)
miDict[k][i] = mi
fWords = set()
for i in xrange(len(docCount)):
keyf = lambda x:x[1][i]
sortedDict = sorted(miDict.items(),key=keyf,reverse=True)
for j in xrange(100):
fWords.add(sortedDict[j][0])
out = open(featureFile, 'w')
#輸出各個類的文檔數目
out.write(str(docCount)+"\n")
#輸出互信息最高的詞作為特征詞
for fword in fWords:
out.write(fword+"\n")
print "特征詞寫入完畢..."
out.close()
def load_feature_words(featureFile):
'''
從特征文件導入特征詞
'''
f = open(featureFile)
#各個類的文檔數目
docCounts = eval(f.readline())
features = set()
#讀取特征詞
for line in f:
features.add(line.strip())
f.close()
return docCounts,features
def train_bayes(featureFile, textFile, modelFile):
'''
訓練貝葉斯模型,實際上計算每個類中特征詞的出現次數
'''
print "使用樸素貝葉斯訓練中..."
docCounts,features = load_feature_words(featureFile)
wordCount = collections.defaultdict(doc_dict())
#每類文檔特征詞出現的次數
tCount = [0]*len(docCounts)
for line in open(textFile):
lable,text = line.strip().split(' ',1)
index = lable2id(lable[0])
words = text.split(' ')
for word in words:
if word in features:
tCount[index] += 1
wordCount[word][index] += 1
outModel = open(modelFile, 'w')
#拉普拉斯平滑
print "訓練完畢,寫入模型..."
for k,v in wordCount.items():
scores = [(v[i]+1) * 1.0 / (tCount[i]+len(wordCount)) for i in xrange(len(v))]
outModel.write(k+"\t"+scores+"\n")
outModel.close()
def load_model(modelFile):
'''
從模型文件中導入計算好的貝葉斯模型
'''
print "加載模型中..."
f = open(modelFile)
scores = {}
for line in f:
word,counts = line.strip().rsplit('\t',1)
scores[word] = eval(counts)
f.close()
return scores
def predict(featureFile, modelFile, testText):
'''
預測文檔的類標,標準輸入每一行為一個文檔
'''
docCounts,features = load_feature_words()
docScores = [math.log(count * 1.0 /sum(docCounts)) for count in docCounts]
scores = load_model(modelFile)
rCount = 0
docCount = 0
print "正在使用測試數據驗證模型效果..."
for line in testText:
lable,text = line.strip().split(' ',1)
index = lable2id(lable[0])
words = text.split(' ')
preValues = list(docScores)
for word in words:
if word in features:
for i in xrange(len(preValues)):
preValues[i]+=math.log(scores[word][i])
m = max(preValues)
pIndex = preValues.index(m)
if pIndex == index:
rCount += 1
#print lable,lables[pIndex],text
docCount += 1
print("總共測試文本量: %d , 預測正確的類別量: %d, 樸素貝葉斯分類器準確度:%f" %(rCount,docCount,rCount * 1.0 / docCount))
if __name__=="__main__":
if len(sys.argv) != 4:
print "Usage: python naive_bayes_text_classifier.py sougou_news.txt feature_file.out model_file.out"
sys.exit()
inFile = sys.argv[1]
featureFile = sys.argv[2]
modelFile = sys.argv[3]
trainText, testText = shuffle(inFile)
count_for_cates(trainText, featureFile)
train_bayes(featureFile, trainText, modelFile)
predict(featureFile, modelFile, testText)
~~~
**6.2 分類結果**
運行結果如下,在6515條數據上,9個類別的新聞上,有84.1%的準確度:?
?
### 7\. Kaggle比賽之『舊金山犯罪分類預測』
**7.1 舊金山犯罪分類預測問題**
沒過癮對吧,確實每次學完一個機器學習算法,不在實際數據上倒騰倒騰,總感覺不那么踏實(想起來高中各種理科科目都要找點題來做的感覺)。好,我們繼續去Kaggle扒點場景和數據來練練手。正巧之前[Kaggle](https://www.kaggle.com/)上有一個分類問題,場景和數據也都比較簡單,我們拿來用樸素貝葉斯試試水。問題請戳[這里](https://www.kaggle.com/c/sf-crime)。
**7.2 背景介紹**
我們大致介紹一下,說的是『水深火熱』的大米國,在舊金山這個地方,一度犯罪率還挺高的,然后很多人都經歷過大到暴力案件,小到東西被偷,車被劃的事情。當地警方也是努力地去總結和想辦法降低犯罪率,一個挑戰是在給出犯罪的地點和時間的之后,要第一時間確定這可能是一個什么樣的犯罪類型,以確定警力等等。后來干脆一不做二不休,直接把12年內舊金山城內的犯罪報告都丟帶Kaggle上,說『大家折騰折騰吧,看看誰能幫忙第一時間預測一下犯罪類型』。犯罪報告里面包括`日期`,`描述`,`星期幾`,`所屬警區`,`處理結果`,`地址`,`GPS定位`等信息。當然,分類問題有很多分類器可以選擇,我們既然剛講過樸素貝葉斯,剛好就拿來練練手好了。
**7.3 數據一瞥**
數據可以在[Kaggle比賽數據頁面](https://www.kaggle.com/c/sf-crime/data)下載到,大家也可以在博主提供的[百度網盤地址](http://pan.baidu.com/s/1o6Wgch8)中下載到。我們依舊用pandas載入數據,先看看數據內容。
~~~
import pandas as pd
import numpy as np
#用pandas載入csv訓練數據,并解析第一列為日期格式
train=pd.read_csv('/Users/Hanxiaoyang/sf_crime_data/train.csv', parse_dates = ['Dates'])
test=pd.read_csv('/Users/Hanxiaoyang/sf_crime_data/test.csv', parse_dates = ['Dates'])
train
~~~
得到如下的結果:?

我們依次解釋一下每一列的含義:
* Date: 日期
* Category: 犯罪類型,比如 Larceny/盜竊罪 等.
* Descript: 對于犯罪更詳細的描述
* DayOfWeek: 星期幾
* PdDistrict: 所屬警區
* Resolution: 處理結果,比如說『逮捕』『逃了』
* Address: 發生街區位置
* X and Y: GPS坐標
train.csv中的數據時間跨度為12年,包含了90w+的記錄。另外,這部分數據,大家從上圖上也可以看出來,大部分都是『類別』型,比如犯罪類型,比如星期幾。
### 7.4 特征預處理
上述數據中類別和文本型非常多,我們要進行特征預處理,對于特征預處理的部分,我們在前面的博文[機器學習系列(3)*邏輯回歸應用之Kaggle泰坦尼克之災*](http://blog.csdn.net/han_xiaoyang/article/details/49797143)和[機器學習系列(6)從白富美相親看特征預處理與選擇(下)](http://blog.csdn.net/han_xiaoyang/article/details/50503115)都有較細的介紹。對于類別特征,我們用最常見的因子化操作將其轉成數值型,比如我們把犯罪類型用因子化進行encode,也就是說生成如下的向量:
~~~
星期一/Monday = 1,0,0,0,...
星期二/Tuesday = 0,1,0,0,...
星期三/Wednesday = 0,0,1,0,...
...
~~~
我們之前也提到過,用pandas的[get_dummies()](http://pandas.pydata.org/pandas-docs/version/0.13.1/generated/pandas.get_dummies.html)可以直接拿到這樣的一個二值化的01向量。Pandas里面還有一個很有用的方法[LabelEncoder](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html)可以用于對類別編號。對于已有的數據特征,我們打算做下面的粗略變換:
* 用LabelEncoder對犯罪類型做編號;
* 處理時間,在我看來,也許犯罪發生的時間點(小時)是非常重要的,因此我們會用Pandas把這部分數據抽出來;
* 對`街區`,`星期幾`,`時間點`用get_dummies()因子化;
* 做一些組合特征,比如把上述三個feature拼在一起,再因子化一下;
具體的數據和特征處理如下:
~~~
import pandas as pd
import numpy as np
from sklearn.cross_validation import train_test_split
from sklearn import preprocessing
#用LabelEncoder對不同的犯罪類型編號
leCrime = preprocessing.LabelEncoder()
crime = leCrime.fit_transform(train.Category)
#因子化星期幾,街區,小時等特征
days = pd.get_dummies(train.DayOfWeek)
district = pd.get_dummies(train.PdDistrict)
hour = train.Dates.dt.hour
hour = pd.get_dummies(hour)
#組合特征
trainData = pd.concat([hour, days, district], axis=1)
trainData['crime']=crime
#對于測試數據做同樣的處理
days = pd.get_dummies(test.DayOfWeek)
district = pd.get_dummies(test.PdDistrict)
hour = test.Dates.dt.hour
hour = pd.get_dummies(hour)
testData = pd.concat([hour, days, district], axis=1)
trainData
~~~
然后可以看到特征處理后的數據如下所示:?

### 7.5 樸素貝葉斯 VS 邏輯回歸
拿到初步的特征了,下一步就可以開始建模了。
因為之前的博客[機器學習系列(1)*邏輯回歸初步*](http://blog.csdn.net/han_xiaoyang/article/details/49123419),[機器學習系列(2)從初等數學視角解讀邏輯回歸](http://blog.csdn.net/han_xiaoyang/article/details/49332321),[機器學習系列(3)_邏輯回歸應用之Kaggle泰坦尼克之災](http://blog.csdn.net/han_xiaoyang/article/details/49797143)中提到過邏輯回歸這種分類算法,我們這里打算一并拿來建模,做個比較。?
還需要提到的一點是,大家參加Kaggle的比賽,一定要注意最后排名和評定好壞用的標準,比如說在現在這個多分類問題中,Kaggle的評定標準并不是precision,而是[multi-class log_loss](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.log_loss.html),這個值越小,表示最后的效果越好。
我們可以快速地篩出一部分重要的特征,搭建一個baseline系統,再考慮步步優化。比如我們這里簡單一點,就只取`星期幾`和`街區`作為分類器輸入特征,我們用scikit-learn中的`train_test_split`函數拿到訓練集和交叉驗證集,用樸素貝葉斯和邏輯回歸都建立模型,對比一下它們的表現:
~~~
ffrom sklearn.cross_validation import train_test_split
from sklearn import preprocessing
from sklearn.metrics import log_loss
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
import time
# 只取星期幾和街區作為分類器輸入特征
features = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
# 分割訓練集(3/5)和測試集(2/5)
training, validation = train_test_split(trainData, train_size=.60)
# 樸素貝葉斯建模,計算log_loss
model = BernoulliNB()
nbStart = time.time()
model.fit(training[features], training['crime'])
nbCostTime = time.time() - nbStart
predicted = np.array(model.predict_proba(validation[features]))
print "樸素貝葉斯建模耗時 %f 秒" %(nbCostTime)
print "樸素貝葉斯log損失為 %f" %(log_loss(validation['crime'], predicted))
#邏輯回歸建模,計算log_loss
model = LogisticRegression(C=.01)
lrStart= time.time()
model.fit(training[features], training['crime'])
lrCostTime = time.time() - lrStart
predicted = np.array(model.predict_proba(validation[features]))
log_loss(validation['crime'], predicted)
print "邏輯回歸建模耗時 %f 秒" %(lrCostTime)
print "邏輯回歸log損失為 %f" %(log_loss(validation['crime'], predicted))
~~~
實驗的結果如下:
?
我們可以看到目前的特征和參數設定下,樸素貝葉斯的log損失還低一些,另外我們可以明顯看到,樸素貝葉斯建模消耗的時間0.640398秒遠小于邏輯回歸建模42.856376秒。
考慮到犯罪類型可能和犯罪事件發生的小時時間點相關,我們加入小時時間點特征再次建模,代碼和結果如下:
~~~
from sklearn.cross_validation import train_test_split
from sklearn import preprocessing
from sklearn.metrics import log_loss
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
import time
# 添加犯罪的小時時間點作為特征
features = ['Friday', 'Monday', 'Saturday', 'Sunday', 'Thursday', 'Tuesday',
'Wednesday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
hourFea = [x for x in range(0,24)]
features = features + hourFea
# 分割訓練集(3/5)和測試集(2/5)
training, validation = train_test_split(trainData, train_size=.60)
# 樸素貝葉斯建模,計算log_loss
model = BernoulliNB()
nbStart = time.time()
model.fit(training[features], training['crime'])
nbCostTime = time.time() - nbStart
predicted = np.array(model.predict_proba(validation[features]))
print "樸素貝葉斯建模耗時 %f 秒" %(nbCostTime)
print "樸素貝葉斯log損失為 %f" %(log_loss(validation['crime'], predicted))
#邏輯回歸建模,計算log_loss
model = LogisticRegression(C=.01)
lrStart= time.time()
model.fit(training[features], training['crime'])
lrCostTime = time.time() - lrStart
predicted = np.array(model.predict_proba(validation[features]))
log_loss(validation['crime'], predicted)
print "邏輯回歸建模耗時 %f 秒" %(lrCostTime)
print "邏輯回歸log損失為 %f" %(log_loss(validation['crime'], predicted))
~~~
?
可以看到在這三個類別特征下,樸素貝葉斯相對于邏輯回歸,依舊有一定的優勢(log損失更小),同時訓練時間很短,這意味著模型雖然簡單,但是效果依舊強大。順便提一下,樸素貝葉斯1.13s訓練出來的模型,預測的效果在Kaggle排行榜上已經能進入Top 35%了,如果進行一些優化,比如特征處理、特征組合等,結果會進一步提高。
### 8\. Kaggle比賽之影評與觀影者情感判定
博主想了想,既然樸素貝葉斯最常見的應用場景就那么幾個,干脆我們都一并覆蓋得了。咳咳,對,還有一個非常重要的應用場景是情感分析(尤其是褒貶判定),于是我又上Kaggle溜達了一圈,扒下來一個類似場景的比賽。比賽的名字叫做[**當詞袋/Bag of words 遇上 爆米花/Bags of Popcorn**](https://www.kaggle.com/c/word2vec-nlp-tutorial/),地址為[https://www.kaggle.com/c/word2vec-nlp-tutorial/](https://www.kaggle.com/c/word2vec-nlp-tutorial/),有興趣的同學可以上去瞄一眼。
**8.1 背景介紹**
這個比賽的背景大概是:國外有一個類似[豆瓣電影](http://movie.douban.com/)一樣的[IMDB](http://www.imdb.com/),也是你看完電影,可以上去打個分,吐個槽的地方。然后大家就在想,有這么多數據,總得折騰點什么吧,于是乎,第一個想到的就是,贊的噴的內容都有了,咱們就來分分類,看看能不能根據內容分布褒貶。PS:很多同學表示,分個褒貶有毛線難的,咳咳,計算機比較笨,另外,語言這種東西,真心是博大精深的,我們隨手從豆瓣上抓了幾條《功夫熊貓3》影評下來,表示有些雖然我是能看懂,但是不處理直接給計算機看,它應該是一副『什么鬼』的表情。。。
?
多說一句,Kaggle原文引導里是用word2vec的方式將詞轉為詞向量,后再用deep learning的方式做的。深度學習好歸好,但是畢竟耗時耗力耗資源,我們用最最naive的樸素貝葉斯擼一把,說不定效果也能不錯,不試試誰知道呢。另外,樸素貝葉斯建模真心速度快,很多場景下,快速建模快速迭代優化正是我們需要的嘛。
**8.2 數據一瞥**
言歸正傳,回到Kaggle中這個問題上來,先瞄一眼數據。Kaggle數據頁面地址為[https://www.kaggle.com/c/word2vec-nlp-tutorial/data](https://www.kaggle.com/c/word2vec-nlp-tutorial/data),大家也可以到博主的[百度網盤](http://pan.baidu.com/s/1c1jX8nI)中下載。數據包如下圖所示:
?
其中包含有情緒標簽的訓練數據labeledTrainData,沒有情緒標簽的訓練數據unlabeledTrainData,以及測試數據testData。labeledTrainData包括id,sentiment和review3個部分,分別指代用戶id,情感標簽,評論內容。
解壓縮labeledTrainData后用vim打開,內容如下:

下面我們讀取數據并做一些基本的預處理(比如說把評論部分的html標簽去掉等等):
~~~
import re #正則表達式
from bs4 import BeautifulSoup #html標簽處理
import pandas as pd
def review_to_wordlist(review):
'''
把IMDB的評論轉成詞序列
'''
# 去掉HTML標簽,拿到內容
review_text = BeautifulSoup(review).get_text()
# 用正則表達式取出符合規范的部分
review_text = re.sub("[^a-zA-Z]"," ", review_text)
# 小寫化所有的詞,并轉成詞list
words = review_text.lower().split()
# 返回words
return words
# 使用pandas讀入訓練和測試csv文件
train = pd.read_csv('/Users/Hanxiaoyang/IMDB_sentiment_analysis_data/labeledTrainData.tsv', header=0, delimiter="\t", quoting=3)
test = pd.read_csv('/Users/Hanxiaoyang/IMDB_sentiment_analysis_data/testData.tsv', header=0, delimiter="\t", quoting=3 )
# 取出情感標簽,positive/褒 或者 negative/貶
y_train = train['sentiment']
# 將訓練和測試數據都轉成詞list
train_data = []
for i in xrange(0,len(train['review'])):
train_data.append(" ".join(review_to_wordlist(train['review'][i])))
test_data = []
for i in xrange(0,len(test['review'])):
test_data.append(" ".join(review_to_wordlist(test['review'][i])))
~~~
我們在ipython notebook里面看一眼,發現數據已經格式化了,如下:?

**8.3 特征處理**
緊接著又到了頭疼的部分了,數據有了,我們得想辦法從數據里面拿到有區分度的特征。比如說Kaggle該問題的引導頁提供的word2vec就是一種文本到數值域的特征抽取方式,比如說我們在第6小節提到的用互信息提取關鍵字也是提取特征的一種。比如說在這里,我們打算用在文本檢索系統中非常有效的一種特征:TF-IDF(term frequency-interdocument frequency)向量。每一個電影評論最后轉化成一個TF-IDF向量。對了,對于TF-IDF不熟悉的同學們,我們稍加解釋一下,TF-IDF是一種統計方法,用以評估一字詞(或者n-gram)對于一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨著它在文件中出現的次數成正比增加,但同時會隨著它在語料庫中出現的頻率成反比下降。這是一個能很有效地判定對評論褒貶影響大的詞或短語的方法。
那個…博主打算繼續偷懶,把scikit-learn中TFIDF向量化方法直接拿來用,想詳細了解的同學可以戳[sklearn TFIDF向量類](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html)。對了,再多說幾句我的處理細節,停用詞被我掐掉了,同時我在單詞的級別上又拓展到2元語言模型(對這個不了解的同學別著急,后續的博客介紹馬上就來),恩,你可以再加3元4元語言模型…博主主要是單機內存不夠了,先就2元上,湊活用吧…
~~~
from sklearn.feature_extraction.text import TfidfVectorizer as TFIV
# 初始化TFIV對象,去停用詞,加2元語言模型
tfv = TFIV(min_df=3, max_features=None, strip_accents='unicode', analyzer='word',token_pattern=r'\w{1,}', ngram_range=(1, 2), use_idf=1,smooth_idf=1,sublinear_tf=1, stop_words = 'english')
# 合并訓練和測試集以便進行TFIDF向量化操作
X_all = train_data + test_data
len_train = len(train_data)
# 這一步有點慢,去喝杯茶刷會兒微博知乎歇會兒...
tfv.fit(X_all)
X_all = tfv.transform(X_all)
# 恢復成訓練集和測試集部分
X = X_all[:len_train]
X_test = X_all[len_train:]
~~~
**8.4 樸素貝葉斯 vs 邏輯回歸**
特征現在我們拿到手了,該建模了,好吧,博主折騰勁又上來了,那個…咳咳…我們還是樸素貝葉斯和邏輯回歸都建個分類器吧,然后也可以比較比較,恩。?
『talk is cheap, I’ll show you the code』,直接放碼過來了哈。
~~~
# 多項式樸素貝葉斯
from sklearn.naive_bayes import MultinomialNB as MNB
model_NB = MNB()
model_NB.fit(X, y_train) #特征數據直接灌進來
MNB(alpha=1.0, class_prior=None, fit_prior=True)
from sklearn.cross_validation import cross_val_score
import numpy as np
print "多項式貝葉斯分類器20折交叉驗證得分: ", np.mean(cross_val_score(model_NB, X, y_train, cv=20, scoring='roc_auc'))
# 多項式貝葉斯分類器20折交叉驗證得分: 0.950837239
~~~
~~~
# 折騰一下邏輯回歸,恩
from sklearn.linear_model import LogisticRegression as LR
from sklearn.grid_search import GridSearchCV
# 設定grid search的參數
grid_values = {'C':[30]}
# 設定打分為roc_auc
model_LR = GridSearchCV(LR(penalty = 'L2', dual = True, random_state = 0), grid_values, scoring = 'roc_auc', cv = 20)
# 數據灌進來
model_LR.fit(X,y_train)
# 20折交叉驗證,開始漫長的等待...
GridSearchCV(cv=20, estimator=LogisticRegression(C=1.0, class_weight=None, dual=True,
fit_intercept=True, intercept_scaling=1, penalty='L2', random_state=0, tol=0.0001),
fit_params={}, iid=True, loss_func=None, n_jobs=1,
param_grid={'C': [30]}, pre_dispatch='2*n_jobs', refit=True,
score_func=None, scoring='roc_auc', verbose=0)
#輸出結果
print model_LR.grid_scores_
~~~
最后邏輯回歸的結果是`[mean: 0.96459, std: 0.00489, params: {'C': 30}]`
咳咳…看似邏輯回歸在這個問題中,TF-IDF特征下表現要稍強一些…不過同學們自己跑一下就知道,這2個模型的訓練時長真心不在一個數量級,邏輯回歸在數據量大的情況下,要等到睡著…另外,要提到的一點是,因為我這里只用了2元語言模型(2-gram),加到3-gram和4-gram,最后兩者的結果還會提高,而且樸素貝葉斯說不定會提升更快一點,內存夠的同學們自己動手試試吧^_^
### 9\. 總結
本文為樸素貝葉斯的實踐和進階篇,先丟了點干貨,總結了貝葉斯方法的優缺點,應用場景,注意點和一般建模方法。緊接著對它最常見的應用場景,抓了幾個例子,又來了一遍手把手系列,不管是對于文本主題分類、多分類問題(犯罪類型分類) 還是 情感分析/分類,樸素貝葉斯都是一個簡單直接高效的方法。尤其是在和邏輯回歸的對比中可以看出,在這些問題中,樸素貝葉斯能取得和邏輯回歸相近的成績,但是訓練速度遠快于邏輯回歸,真正的直接和高效。
- 前言
- 機器學習系列(1)_邏輯回歸初步
- 機器學習系列(2)_從初等數學視角解讀邏輯回歸
- 機器學習系列(3)_邏輯回歸應用之Kaggle泰坦尼克之災
- 手把手入門神經網絡系列(1)_從初等數學的角度初探神經網絡
- 手把手入門神經網絡系列(2)_74行代碼實現手寫數字識別
- 機器學習系列(4)_機器學習算法一覽,應用建議與解決思路
- 機器學習系列(5)_從白富美相親看特征預處理與選擇(上)
- 機器學習系列(6)_從白富美相親看特征預處理與選擇(下)
- 機器學習系列(7)_機器學習路線圖(附資料)
- NLP系列(2)_用樸素貝葉斯進行文本分類(上)
- NLP系列(3)_用樸素貝葉斯進行文本分類(下)
- NLP系列(4)_樸素貝葉斯實戰與進階