基于Spark的分類模型

之前介紹過Apache Spark的基本概念以及環(huán)境準備,本篇以分類算法為入口,主要熟悉下Spark的Python API,重點不在算法,而是API的熟悉,具體的分類算法會給出相應的Wiki鏈接,感興趣的可以一起交流下。

分類模型種類

我們將討論Spark中常見的三種分類模型:線性模型、決策樹和樸素貝葉斯模型。線性模型, 簡單而且相對容易擴展到非常大的數(shù)據(jù)集;決策樹是一個強大的非線性技術(shù),訓練過程計算量大 并且較難擴展(幸運的是,MLlib會替我們考慮擴展性的問題),但是在很多情況下性能很好;樸 素貝葉斯模型簡單、易訓練,并且具有高效和并行的優(yōu)點(實際中,模型訓練只需要遍歷所有數(shù) 據(jù)集一次)。而且,樸素 貝葉斯模型可以作為一個很好的模型測試基準,用于比較其他模型的性能。

線性模型

線性模型的核心思想是對樣本的預測結(jié)果(通常稱為目標或者因變量)進行建模,即對輸入變量(特征或者自變量)應用簡單的線性預測函數(shù),具體


screenshot
screenshot

給定輸入數(shù)據(jù)的特征向量和相關(guān)的目標值,存在一個權(quán)重向量能夠最好對數(shù)據(jù)進行擬合,擬 合的過程即最小化模型輸出與實際值的誤差。這個過程稱為模型的擬合、訓練或者優(yōu)化。本文主要關(guān)注的線性模型包括邏輯回歸和線性支持向量機。需要進一步了解線性模型和損失函數(shù)的細節(jié),可參考[Spark二分類部分](http://spark.apache.org/docs/latest/mllib-linear- methods.html#binary-classification).

樸素貝葉斯模型

樸素貝葉斯是一個概率模型,通過計算給定數(shù)據(jù)點在某個類別的概率來進行預測。樸素貝葉斯模型假定每個特征分配到某個類別的概率是獨立分布的(假定各個特征之間條件獨立)。
基于這個假設(shè),屬于某個類別的概率表示為若干概率乘積的函數(shù),其中這些概率包括某個特 2 征在給定某個類別的條件下出現(xiàn)的概率(條件概率),以及該類別的概率(先驗概率)。這樣使得 模型訓練非常直接且易于處理。類別的先驗概率和特征的條件概率可以通過數(shù)據(jù)的頻率估計得 到。分類過程就是在給定特征和類別概率的情況下選擇最可能的類別。
另外還有一個關(guān)于特征分布的假設(shè),即參數(shù)的估計來自數(shù)據(jù)。MLlib實現(xiàn)了多項樸素貝葉斯 (multinomial nai?ve Bayes),其中假設(shè)特征分布是多項分布,用以表示特征的非負頻率統(tǒng)計。具體請參考Spark文檔中對應的關(guān)于[樸素貝葉斯的介紹](http://spark.apache.org/do cs/latest/mllib-naive-bayes.html), 如果想了解模型的數(shù)學公司,可參考[維基百科](http://en.wi kipedia. org/wiki/Naive_Bayes_classifier)。

決策樹

決策樹是一個強大的非概率模型,它可以表達復雜的非線性模式和特征相互關(guān)系。決策樹在 很多任務(wù)上表現(xiàn)出的性能很好,相對容易理解和解釋,可以處理類屬或者數(shù)值特征,同時不要求 輸入數(shù)據(jù)歸一化或者標準化。決策樹非常適合應用集成方法(ensemble method),比如多個決策 樹的集成,稱為決策樹森林。

下圖截圖了其他博客的一張決策樹的圖,比如新來一個用戶:無房產(chǎn),單身,年收入55K,那么根據(jù)上面的決策樹,可以預測他無法償還債務(wù)(藍色虛線路徑)。


screenshot
screenshot

決策樹算法是一種自上而下始于根節(jié)點(或特征)的方法,在每一個步驟中通過評估特征分裂的信息增益,最后選出分割數(shù)據(jù)集最優(yōu)的特征。信息增益通過計算節(jié)點不純度(即節(jié)點標簽不相似或不同質(zhì)的程度)減去分割后的兩個子節(jié)點不純度的加權(quán)和。對于分類任務(wù),這里有兩個評估方法用于選擇最好分割:基尼不純和熵。要進一步了解決策樹算法和不純度估計,請參考[Spark官方文檔](http://spark.apache.org/docs/latest/mllib-decision-
tree.html)

分類模型實例

上面講了關(guān)于分類的一些概念,都是點到為止,本文的重點在于Spark的Python API熟悉,如想了解具體分類模型,還需要進一步Google對應的算法介紹,本文最后也會給出一些相關(guān)的鏈接。
接下來我們就按照做數(shù)據(jù)的一般流程來演示這個分類模型是如何構(gòu)建的,主要包括數(shù)據(jù)準備(特征提取)、模型訓練、模型使用、模型評估以及模型改進等。

數(shù)據(jù)準備

本文使用的數(shù)據(jù)集來自Kaggle比賽,由StumbleUpon提供,比賽的問題涉及網(wǎng)頁中推薦的頁面是短暫(短暫 存在,很快就不流行了)還是長久(長時間流行)。

開始之前,為了讓Spark更好地操作數(shù)據(jù),我們需要刪除文件第一行的列頭名稱。進入數(shù)據(jù) 的目錄(這里用PATH表示),然后輸入如下命令刪除第一行并且通過管道保存到以 train_noheader.tsv命名的新文件中:

> sed 1d train.tsv > train_noheader.tsv

接下來,我們啟動spark的python命令行工具,命令如下,如果你熟悉Python,那你應該對IPython不會陌生,IPython是個非常有用的家伙,有興趣的可以了解下

> IPYTHON=1 pyspark

首先我們來引入相關(guān)的依賴包,下面是本文需要的全部依賴包,主要都是分類模型相關(guān)的。

from pyspark.mllib.regression import LabeledPoint
from pyspark.mllib.classification import LogisticRegressionWithSGD
from pyspark.mllib.classification import SVMWithSGD
from pyspark.mllib.classification import NaiveBayes
from pyspark.mllib.tree import DecisionTree, DecisionTreeModel
from pyspark.mllib.evaluation import BinaryClassificationMetrics
from pyspark.mllib.regression import LabeledPoint

依賴包引入之后,我們就可以使用對應的API來讀入元數(shù)據(jù)了,通過觀察records.first()的輸出,大概對數(shù)據(jù)有個大致的了解。開始四列分別包含URL、頁面的ID、 原始的文本內(nèi)容和分配給頁面的類別。接下來22列包含各種各樣的數(shù)值或者類屬特征。最后一列 為目標值,?1為長久,0為短暫。

data_path = "/Users/caolei/WorkSpace/hack-spark/data/evergreen"
records = sc.textFile(data_path + "/train-no-header.tsv").map(lambda line : line.split("\t"))
records.first()

我們將用簡單的方法直接對數(shù)值特征做處理。因為每個類屬變量是二元的,對這些變量已有 一個用1-of-編碼的特征,于是不需要額外提取特征。由于數(shù)據(jù)格式的問題,我們做一些數(shù)據(jù)清理的工作,在處理過程中把額外的(")去掉。數(shù) 據(jù)集中還有一些用"?"代替的缺失數(shù)據(jù),本例中,我們直接用0替換那些缺失數(shù)據(jù),而且定義了抽取特征和標簽的函數(shù)。

records_trimmed = records.map(lambda record : map(lambda field : field.replace("\"", ""), record))
records_trimmed.cache()

def replace_special_simbal(a):
if a == "?":
return 0.0
else:
return float(a)

def extract_label(record):
return record[len(record)-1]

def extract_features(record):
return map(lambda field : replace_special_simbal(field), record[4:len(record) -1 ])

在清理和處理缺失數(shù)據(jù)后,我們提取最后一列的標記變量以及第5列到第25列的特征矩陣。 將標簽變量轉(zhuǎn)換為Int值,特征向量轉(zhuǎn)換為Double數(shù)組。最后,我們將標簽和和特征向量轉(zhuǎn)換為 LabeledPoint實例。

data = records_trimmed.map(lambda r : LabeledPoint(extract_label(r), extract_features(r))).filter(lambda record : record.features.array.size == 22)
data.cache()

模型訓練

其實不管是數(shù)據(jù)挖掘還是機器學習,或者更牛逼的AI,個人理解,重點在數(shù)據(jù),如果數(shù)據(jù)準備和處理的得當,那模型和算法都是太大問題,下面是模型訓練的代碼。

# 模型訓練,包括線性回歸,SVNM,決策樹和樸素貝葉斯
numIterations = 10
lrModel = LogisticRegressionWithSGD.train(data, numIterations)
svmModel = SVMWithSGD.train(data, numIterations)

maxTreeDepth = 5
dtModel = DecisionTree.trainClassifier(data, numClasses=2, categoricalFeaturesInfo={},impurity='gini', maxDepth=maxTreeDepth, maxBins=32)

# 樸素樸貝葉斯要求特征值非負
def deal_negative(a):
if a == "?" or float(a) < 0.0:
return 0.0
else:
return float(a)

def extract_no_negative_features(record):
return map(lambda field : deal_negative(field), record[4:len(record) -1 ])


data_for_bayes = records_trimmed.map(lambda r : LabeledPoint(extract_label(r), extract_no_negative_features(r))).filter(lambda record : record.features.array.size == 22)
bayesModel = NaiveBayes.train(data_for_bayes)

模型使用

模型訓練出來之后,應該將模型用在測試數(shù)據(jù)上,簡單起見,我們直接將模型運用在訓練數(shù)據(jù)上,還是那句話,重點在熟悉API,而不是算法和效果。以邏輯回歸為例:

lrModel.predict(data.first().features) # 輸出1
data.first().label #輸出0

這個結(jié)果說明我們預測錯了,你可以嘗試其他的數(shù)據(jù)點,來看看模型訓練的是否滿足要求,其中這塊主要是由模型評估來保證的。

模型評估

在使用模型做預測時,如何知道預測到底好不好呢?換句話說,應該知道怎么評估模型性能。 通常在二分類中使用的評估方法包括:預測正確率和錯誤率、準確率和召回率、準確率?召回率曲線下方的面積、ROC曲線、ROC曲線下的面積和F-Measure。

預測的正確率和錯誤率

在二分類中,預測正確率可能是最簡單評測方式,正確率等于訓練樣本中被正確分類的數(shù)目除以總樣本數(shù)。類似地,錯誤率等于訓練樣本中被錯誤分類的樣本數(shù)目除以總樣本數(shù)。我們通過對輸入特征進行預測并將預測值與實際標簽進行比較,計算出模型在訓練數(shù)據(jù)上的 正確率。將對正確分類的樣本數(shù)目求和并除以樣本總數(shù),得到平均分類正確率:

## 正確率和錯誤率
lrTotalCorrect = data.map(lambda r : 1 if (lrModel.predict(r.features) == r.label) else 0).reduce(lambda x, y : x + y)
lrAccuracy = lrTotalCorrect / float(data.count()) # 0.5136044023234485

svmTotalCorrect = data.map(lambda r : 1 if (svmModel.predict(r.features) == r.label) else 0).reduce(lambda x, y : x + y)
svmAccuracy = svmTotalCorrect / float(data.count()) #0.5136044023234485

nbTotalCorrect = data_for_bayes.map(lambda r : 1 if (bayesModel.predict(r.features) == r.label) else 0).reduce(lambda x, y : x + y)
nbAccuracy = nbTotalCorrect / float(data_for_bayes.count()) #0.5799449709568939


dt_predictions = dtModel.predict(data.map(lambda x: x.features))
labelsAndPredictions = data.map(lambda x: x.label).zip(dt_predictions)
dtTotalCorrect = labelsAndPredictions.map(lambda r : 1 if (r[0] == r[1]) else 0).reduce(lambda x, y : x + y)
dtAccuracy = dtTotalCorrect / float(data.count()) #0.654234179150107

最后發(fā)現(xiàn),四個模型的準確率都在50%到60% 左右,跟隨即差不多,這說明我們的模型訓練的很不好。

準確率和召回率

在信息檢索中,準確率通常用于評價結(jié)果的質(zhì)量,而召回率用來評價結(jié)果的完整性。
在二分類問題中,準確率定義為真陽性的數(shù)目除以真陽性和假陽性的總數(shù),其中真陽性是指 被正確預測的類別為1的樣本,假陽性是錯誤預測為類別1的樣本。如果每個被分類器預測為類別 1的樣本確實屬于類別1,那準確率達到100%。
召回率定義為真陽性的數(shù)目除以真陽性和假陰性的和,其中假陰性是類別為1卻被預測為0 的樣本。如果任何一個類型為1的樣本沒有被錯誤預測為類別0(即沒有假陰性),那召回率達到 100%。
通常,準確率和召回率是負相關(guān)的,高準確率常常對應低召回率,反之亦然。為了說明這點, 假定我們訓練了一個模型的預測輸出永遠是類別1。因為總是預測輸出類別1,所以模型預測結(jié)果 不會出現(xiàn)假陰性,這樣也不會錯過任何類別1的樣本。于是,得到模型的召回率是1.0。另一方面, 假陽性會非常高,意味著準確率非常低(這依賴各個類別在數(shù)據(jù)集中確切的分布情況)。
準確率和召回率在單獨度量時用處不大,但是它們通常會被一起組成聚合或者平均度量。二 者同時也依賴于模型中選擇的閾值。
直覺上來講,當閾值低于某個程度,模型的預測結(jié)果永遠會是類別1。因此,模型的召回率 為1,但是準確率很可能很低。相反,當閾值足夠大,模型的預測結(jié)果永遠會是類別0。此時,模 型的召回率為0,但是因為模型不能預測任何真陽性的樣本,很可能會有很多的假陰性樣本。不 僅如此,因為這種情況下真陽性和假陽性為0,所以無法定義模型的準確率。
圖5-8所示的準確率-召回率(PR)曲線,表示給定模型隨著決策閾值的改變,準確率和召回 率的對應關(guān)系。PR曲線下的面積為平均準確率。直覺上,PR曲線下的面積為1等價于一個完美模 型,其準確率和召回率達到100%。

ROC曲線和AUC

ROC曲線在概念上和PR曲線類似,它是對分類器的真陽性率?假陽性率的圖形化解釋。
真陽性率(TPR)是真陽性的樣本數(shù)除以真陽性和假陰性的樣本數(shù)之和。換句話說,TPR是 真陽性數(shù)目占所有正樣本的比例。這和之前提到的召回率類似,通常也稱為敏感度。
假陽性率(FPR)是假陽性的樣本數(shù)除以假陽性和真陰性的樣本數(shù)之和。換句話說,FPR是 假陽性樣本數(shù)占所有負樣本總數(shù)的比例。
和準確率和召回率類似,ROC曲線(圖5-9)表示了分類器性能在不同決策閾值下TPR對FPR 的折衷。曲線上每個點代表分類器決策函數(shù)中不同的閾值。
ROC下的面積(通常稱作AUC)表示平均值。同樣,AUC為1.0時表示一個完美的分類器,0.5則表示一個隨機的性能。于是,一個模型的AUC為0.5時和隨機猜測效果一樣。下面是計算
PR和ROC的代碼。

# Compute raw scores on the test set
lrPredictionAndLabels = data.map(lambda lp: (float(lrModel.predict(lp.features)), lp.label))
# Instantiate metrics object
lrmetrics = BinaryClassificationMetrics(lrPredictionAndLabels)
# Area under precision-recall curve
print("Area under PR = %s" % lrmetrics.areaUnderPR)
# Area under ROC curve
print("Area under ROC = %s" % lrmetrics.areaUnderROC)


svmPredictionAndLabels = data.map(lambda lp: (float(svmModel.predict(lp.features)), lp.label))
svmMetrics = BinaryClassificationMetrics(svmPredictionAndLabels)
print("Area under PR = %s" % svmmetrics.areaUnderPR)
print("Area under ROC = %s" % svmmetrics.areaUnderROC)

bayesPredictionAndLabels = data_for_bayes.map(lambda lp: (float(bayesModel.predict(lp.features)), lp.label))
bayesMetrics = BinaryClassificationMetrics(bayesPredictionAndLabels)
print("Area under PR = %s" % bayesMetrics.areaUnderPR)
print("Area under ROC = %s" % bayesMetrics.areaUnderROC)

邏輯回歸和SVM的AUC的結(jié)果在0.5左右,表明這兩個模型并不比隨機好。樸素貝葉斯模型 和決策樹模型性能稍微好些,AUC分別是0.58和0.65。但是,在二分類問題上這個性能并不是非 常好。

模型改進

根據(jù)上面的結(jié)果,我們已經(jīng)確定我們訓練出的模型很不少,但問題出在哪里呢?想想看,我們只是簡單地把原始數(shù)據(jù)送進了模型做訓練。事實上,我們并沒有把所有數(shù)據(jù)用 在模型中,只是用了其中易用的數(shù)值部分。同時,我們也沒有對這些數(shù)值特征做太多分析,包括特征標準化,歸一化處理,異常數(shù)據(jù)處理,模型參數(shù)調(diào)優(yōu)等。接下來計劃寫一篇專文來介紹這些。

總結(jié)

本篇主要是熟悉API為主,所以很多細節(jié)都是點到為止,在學習的過程中,也了解了一些Scala的東西,感覺用Scala比用Python還要簡潔,學習的路還很長啊。

參考文章

Spark官網(wǎng)
Spark機器學習
留一交叉驗證

相加式平滑
過擬合和欠擬合
正 則 化

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容