笔记中理论部份来自 Andrew Ng 公开课,工程部份来自 spark 1.6. 我的理解 logistic regression 一般用于二元分类(binary classification), 有的翻译成逻辑回归,但是周志华的书是 对数回归, 算了还是不翻译。这一章涉及大量线性代数,概率论和统计分布,顺便复习下大一课程(:... 这篇本记写得有点跳跃和凌乱。
分类和线性回归
线性回归预测的数据是连续的,正如房价问题。但是分类只对应几个离散值,常见如垃圾邮件,只有0或1(二元分类 binary classification). 但是分类也是构建在线性回归问题之上的,回顾线性方程
h(x)=𝜃0 +𝜃1𝑥1 +𝜃2𝑥2 =∑𝜃𝑖𝑥𝑖 =𝜃T𝑥
分类就是在h(x)函数之上作用了g(z)的映射
g(z)=1/(1+e^(-z))
这层映射函数也叫做 sigmoid 函数,图形如下
其实很好理解,当z值很大时 e^(-z)拉近0,那么 g(z) 就越接近1, 同样当 z 值很小时,e^(-z) 就很大,那么 g(z) 就越接近0. 其实映射函数有多种选择,在初中就有条件函数,比如当 x >0 时 y=1, 当 x<=0 时 y =0 。之所以选择 g(z) 这样的 sigmoid 函数,是因为连续性数学上求导方便(可能还有其它原因,比如正态分布),另外g(z) 函数求导还有一个很重要的特性
简单的链式求导,蛮容易理解的,后面会用到。
极大似然估计MLE
likelihood 思想在贝叶斯公式中很常见(极大似然太烧脑,头疼)
后验概率 = 先验概率 * 似然估计
这是一类人们对未知事物的一种估计,认为我们观察到的(样本),就是对应事件(结果),发生的最大概率的可能。还有一个奥卡姆剃刀原则
对于观察到的现象,有多种不同的解释,那么往往采取原理最简单的
机器学习三步
这段是在知乎上到的,原文 指出机器学习三个步骤:模型,目标和算法。比如 linear , logistic regression 就是不同的模型,选好模型,那么目标也就确定了。
对于linear regression, 目标是使 loss function 取值最小,对应最小二乘,算法就是梯度下降。那么对于 logistic regression 目标就是使 likelihood 尽可能最大,算法有梯度下降,牛顿法等等。
为什么同是回归,目标函数选取不一样呢?原因就在于 y 值是不同的,对于 linear,y 服从正态分布(y=𝜃T𝑥+e, 其中e是error 误差,由于IID, 符合正态分布, 所以预测值y也服从),使用最小二乘让 loss function 最小,就会拟合出最佳参数𝜃。但是 logistic regression, y 值是离散的,非0即1,或是只有几个,服从伯努利分布,无法计算 loss function,那么就返过来让这个概率最大,就是最大似然估计。
公式推导
现在开始了万恶的公式推导,由于分类只是在线性函数上多了一层映射(sigmoid 函数),最终函数模型为
h(𝜃) = g(𝜃T𝑥) = g(z)=1/(1+e^(-𝜃T𝑥))
其中 𝜃T 是 𝜃转置,𝜃T𝑥 是1xn行向量与nx1列向量相乘,结果为矢量。根据上一节,我们的目标是使概率最大,那么有如下公式:
p(y=1|x;𝜃) = h(𝜃)
p(y=0|x;𝜃) = 1 - h(𝜃)
!!! 这块有些绕,为什么拟合的表达式可以做为概率呢? h(𝜃) 和 linear 的 h(𝜃) 不是一个,在 logistic regression中,这是经过 sigmoid 函数映射的,取值在[0,1] 之间,所以可以做为概率表达式。此时有如下通用推导
p(y=|x;𝜃) = h(𝜃)^(y) * (1-h(𝜃))^(1-y)
很好理解给定样本 x, 和参数 𝜃,那么 y (标记)发生的概率,如果y=0, 公式等价于 1 - h(𝜃),如果 y=1, 等价于h(𝜃),由此变成了概率问题。那么给定 m 个样本,假定L(𝜃) 为极大似然估计值
这块初次接触会比较绕,我们将测试样本 sample 标记成两个值,正例(y=1)和反例(y=0),但特征可能有几百个,面临的问题就是如何将离散的值与连续值h(𝜃)做对应,logistic regression 就是将线性函数经过 g(z) Sigmoid 映射到[0,1],再转换成概率问题,也即求极大似然。( 好像还是没解释透彻...sad...
move on, 然后对 L(𝜃) 取对数,设为l(𝜃)
由于我们是使 l 最大,所以使用梯度上升算法,有如下公式
𝜃 = 𝜃 + αl(𝜃)'
这里有点头晕,什么样的目标函数可以有极值呢?对于连续可微的目标函数,求导,那么导数为0的就是极值,前提一定是凸函数。
这里用到了公式 g(z)' = g(z)(1 - g(z))
经过求导最终梯度函数如上,和 linear regression 时的很相似啊(涉及到 General Linear Models),只不过这里 y, h(x) 什么的是0或1,而不是其它值。其实这里是梯度上升,但可以把 l(𝜃) 乘以 -1, 那么此时变成了上节的梯度下降算法。
Spark工程
测试数据为 SVM格式:标签 特征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 准备数据
实际生产环境测试集都很大,一般数据来源于 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 // 查看原始数据数量
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...
将样本划分为训练集和测试集
划分有很多方法(书上讲的很复杂),这里直接随机划分
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 // 训练集数量
res252: Long = 57
scala> test.count // 测试集数量
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
测试数据
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)}
//预测值 真实值
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
误差计算
通过测量误差,发现精度为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 棒极了,路转粉(:
小结
这里只是二元分类(binary classification), 原理还得再反复咀嚼消化,工程实践最好。回归问题相当于ML里的 hello world。 争取下一篇笔记弄懂 GLMs,顺便分析下 Spark GML 实现。