筆記中理論部份來自 Andrew Ng 公開課,工程部份來自 spark 1.6. 我的理解 logistic regression 一般用于二元分類(binary classification), 有的翻譯成邏輯回歸,但是周志華的書是 對數(shù)回歸, 算了還是不翻譯。這一章涉及大量線性代數(shù),概率論和統(tǒng)計(jì)分布,順便復(fù)習(xí)下大一課程(:... 這篇本記寫得有點(diǎn)跳躍和凌亂。
分類和線性回歸
線性回歸預(yù)測的數(shù)據(jù)是連續(xù)的,正如房價問題。但是分類只對應(yīng)幾個離散值,常見如垃圾郵件,只有0或1(二元分類 binary classification). 但是分類也是構(gòu)建在線性回歸問題之上的,回顧線性方程
h(x)=??0 +??1??1 +??2??2 =∑???????? =??T??
分類就是在h(x)函數(shù)之上作用了g(z)的映射
g(z)=1/(1+e^(-z))
這層映射函數(shù)也叫做 sigmoid 函數(shù),圖形如下

其實(shí)很好理解,當(dāng)z值很大時 e^(-z)拉近0,那么 g(z) 就越接近1, 同樣當(dāng) z 值很小時,e^(-z) 就很大,那么 g(z) 就越接近0. 其實(shí)映射函數(shù)有多種選擇,在初中就有條件函數(shù),比如當(dāng) x >0 時 y=1, 當(dāng) x<=0 時 y =0 。之所以選擇 g(z) 這樣的 sigmoid 函數(shù),是因?yàn)檫B續(xù)性數(shù)學(xué)上求導(dǎo)方便(可能還有其它原因,比如正態(tài)分布),另外g(z) 函數(shù)求導(dǎo)還有一個很重要的特性

簡單的鏈?zhǔn)角髮?dǎo),蠻容易理解的,后面會用到。
極大似然估計(jì)MLE
likelihood 思想在貝葉斯公式中很常見(極大似然太燒腦,頭疼)
后驗(yàn)概率 = 先驗(yàn)概率 * 似然估計(jì)
這是一類人們對未知事物的一種估計(jì),認(rèn)為我們觀察到的(樣本),就是對應(yīng)事件(結(jié)果),發(fā)生的最大概率的可能。還有一個奧卡姆剃刀原則
對于觀察到的現(xiàn)象,有多種不同的解釋,那么往往采取原理最簡單的
機(jī)器學(xué)習(xí)三步
這段是在知乎上到的,原文 指出機(jī)器學(xué)習(xí)三個步驟:模型,目標(biāo)和算法。比如 linear , logistic regression 就是不同的模型,選好模型,那么目標(biāo)也就確定了。
對于linear regression, 目標(biāo)是使 loss function 取值最小,對應(yīng)最小二乘,算法就是梯度下降。那么對于 logistic regression 目標(biāo)就是使 likelihood 盡可能最大,算法有梯度下降,牛頓法等等。
為什么同是回歸,目標(biāo)函數(shù)選取不一樣呢?原因就在于 y 值是不同的,對于 linear,y 服從正態(tài)分布(y=??T??+e, 其中e是error 誤差,由于IID, 符合正態(tài)分布, 所以預(yù)測值y也服從),使用最小二乘讓 loss function 最小,就會擬合出最佳參數(shù)??。但是 logistic regression, y 值是離散的,非0即1,或是只有幾個,服從伯努利分布,無法計(jì)算 loss function,那么就返過來讓這個概率最大,就是最大似然估計(jì)。
公式推導(dǎo)
現(xiàn)在開始了萬惡的公式推導(dǎo),由于分類只是在線性函數(shù)上多了一層映射(sigmoid 函數(shù)),最終函數(shù)模型為
h(??) = g(??T??) = g(z)=1/(1+e^(-??T??))

其中 ??T 是 ??轉(zhuǎn)置,??T?? 是1xn行向量與nx1列向量相乘,結(jié)果為矢量。根據(jù)上一節(jié),我們的目標(biāo)是使概率最大,那么有如下公式:
p(y=1|x;??) = h(??)
p(y=0|x;??) = 1 - h(??)
!!! 這塊有些繞,為什么擬合的表達(dá)式可以做為概率呢? h(??) 和 linear 的 h(??) 不是一個,在 logistic regression中,這是經(jīng)過 sigmoid 函數(shù)映射的,取值在[0,1] 之間,所以可以做為概率表達(dá)式。此時有如下通用推導(dǎo)
p(y=|x;??) = h(??)^(y) * (1-h(??))^(1-y)
很好理解給定樣本 x, 和參數(shù) ??,那么 y (標(biāo)記)發(fā)生的概率,如果y=0, 公式等價于 1 - h(??),如果 y=1, 等價于h(??),由此變成了概率問題。那么給定 m 個樣本,假定L(??) 為極大似然估計(jì)值

這塊初次接觸會比較繞,我們將測試樣本 sample 標(biāo)記成兩個值,正例(y=1)和反例(y=0),但特征可能有幾百個,面臨的問題就是如何將離散的值與連續(xù)值h(??)做對應(yīng),logistic regression 就是將線性函數(shù)經(jīng)過 g(z) Sigmoid 映射到[0,1],再轉(zhuǎn)換成概率問題,也即求極大似然。( 好像還是沒解釋透徹...sad...
move on, 然后對 L(??) 取對數(shù),設(shè)為l(??)

由于我們是使 l 最大,所以使用梯度上升算法,有如下公式
?? = ?? + αl(??)'
這里有點(diǎn)頭暈,什么樣的目標(biāo)函數(shù)可以有極值呢?對于連續(xù)可微的目標(biāo)函數(shù),求導(dǎo),那么導(dǎo)數(shù)為0的就是極值,前提一定是凸函數(shù)。

這里用到了公式 g(z)' = g(z)(1 - g(z))

經(jīng)過求導(dǎo)最終梯度函數(shù)如上,和 linear regression 時的很相似啊(涉及到 General Linear Models),只不過這里 y, h(x) 什么的是0或1,而不是其它值。其實(shí)這里是梯度上升,但可以把 l(??) 乘以 -1, 那么此時變成了上節(jié)的梯度下降算法。
Spark工程
測試數(shù)據(jù)為 SVM格式:標(biāo)簽 特征ID: 特征值 特征ID:特征值 ......
1 319:237 320:254 321:254 322:109...
0 155:53 156:255 157:253 158:253...
0 128:73 129:253 130:227 131:73 132:21...
1 152:1 153:168 154:242 155:28 180:10...
打開 sparl-shell 準(zhǔn)備數(shù)據(jù)
實(shí)際生產(chǎn)環(huán)境測試集都很大,一般數(shù)據(jù)來源于 HDFS
scala> import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.mllib.util.MLUtils
scala> val data = MLUtils.loadLibSVMFile(sc, "/Users/dzr/code/spark-mllib-data/sample_libsvm_data.txt")
data: org.apache.spark.rdd.RDD[org.apache.spark.mllib.regression.LabeledPoint] = MapPartitionsRDD[10742] at map at MLUtils.scala:108
scala> data.count // 查看原始數(shù)據(jù)數(shù)量
res250: Long = 100
scala> data.take(1) // 查看一個樣本
res251: Array[org.apache.spark.mllib.regression.LabeledPoint] = Array((0.0,(692,[127,128,129,130,131,154,155,156,157,158,159,181,182,183,184,185,186,187,188,189,207,208,209,210,211,212,213,214,215,216,217,235,236,237,238,239,240,241,242,243,244,245,262,263,264,265,266,267,268,269,270,271,272,273,289,290,291,292,293,294,295,296,297,300,301,302,316,317,318,319,320,321,328,329,330,343,344,345,346,347,348,349,356,357,358,371,372,373,374,384,385,386,399,400,401,412,413,414,426,427,428,429,440,441,442,454,455,456,457,466,467,468,469,470,482,483,484,493,494,495,496,497,510,511,512,520,521,522,523,538,539,540,547,548,549,550,566,567,568,569,570,571,572,573,574,575,576,577,578,594,595,596,597,598,599,600,601,602,603,604,622,623,624,625,626,627,628,629,630,651,652,653,654,655,656,657],[51.0,159.0...
將樣本劃分為訓(xùn)練集和測試集
劃分有很多方法(書上講的很復(fù)雜),這里直接隨機(jī)劃分
scala> val splits = data.randomSplit(Array(0.6,0.4), seed = 11L)
splits: Array[org.apache.spark.rdd.RDD[org.apache.spark.mllib.regression.LabeledPoint]] = Array(MapPartitionsRDD[10743] at randomSplit at <console>:40, MapPartitionsRDD[10744] at randomSplit at <console>:40)
scala> val training = splits(0).cache
training: org.apache.spark.rdd.RDD[org.apache.spark.mllib.regression.LabeledPoint] = MapPartitionsRDD[10743] at randomSplit at <console>:40
scala> val test = splits(1)
test: org.apache.spark.rdd.RDD[org.apache.spark.mllib.regression.LabeledPoint] = MapPartitionsRDD[10744] at randomSplit at <console>:40
scala> training.count // 訓(xùn)練集數(shù)量
res252: Long = 57
scala> test.count // 測試集數(shù)量
res253: Long = 43
建立模型
scala> import org.apache.spark.mllib.evaluation._
import org.apache.spark.mllib.evaluation._
scala> import org.apache.spark.mllib.classification._
import org.apache.spark.mllib.classification._
scala> import org.apache.spark.mllib.regression._
import org.apache.spark.mllib.regression._
scala> val model = new LogisticRegressionWithLBFGS()
model: org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS = org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS@19d14c13
scala> val model = new LogisticRegressionWithLBFGS().setNumClasses(10).run(training)
model: org.apache.spark.mllib.classification.LogisticRegressionModel = org.apache.spark.mllib.classification.LogisticRegressionModel: intercept = 0.0, numFeatures = 6228, numClasses = 10, threshold = 0.5
通過模型得到, intercept = 0.0, numFeatures = 6228, numClasses = 10, threshold = 0.5
測試數(shù)據(jù)
scala> val predictionAndLabel = test.map {
| case LabeledPoint(label,feature) =>
| val prediction = model.predict(feature)
| (prediction, label)}
predictionAndLabel: org.apache.spark.rdd.RDD[(Double, Double)] = MapPartitionsRDD[10774] at map at <console>:66
scala> val testPredict = predictionAndLabel.take(20)
scala> for (i<- 0 to 20) {
| println(testPredict(i)._1 + "\t" + testPredict(i)._2)}
//預(yù)測值 真實(shí)值
1.0 1.0
1.0 1.0
0.0 0.0
1.0 1.0
0.0 0.0
0.0 0.0
1.0 1.0
1.0 1.0
1.0 1.0
0.0 0.0
1.0 1.0
1.0 1.0
0.0 0.0
1.0 1.0
0.0 0.0
0.0 0.0
1.0 1.0
1.0 1.0
0.0 0.0
0.0 0.0
誤差計(jì)算
通過測量誤差,發(fā)現(xiàn)精度為1,也就是模型對測試集完全有效
scala> val metrics = new MulticlassMetrics(predictionAndLabel)
metrics: org.apache.spark.mllib.evaluation.MulticlassMetrics = org.apache.spark.mllib.evaluation.MulticlassMetrics@18a62d7a
scala> val precision = metrics.precision
precision: Double = 1.0
Spark 2.0 這周五終于GA,普天同慶,scala 棒極了,路轉(zhuǎn)粉(:
小結(jié)
這里只是二元分類(binary classification), 原理還得再反復(fù)咀嚼消化,工程實(shí)踐最好?;貧w問題相當(dāng)于ML里的 hello world。 爭取下一篇筆記弄懂 GLMs,順便分析下 Spark GML 實(shí)現(xiàn)。