背景知识:
二元的分类问题
比如“是否为乳腺癌二分类问题”,我们可以用线性回归的方法求出适合数据的一条直线:
鉴于线性回归模型只能预测连续的值,但是分类问题,我们需要输出0或1。我们可以预测是否为恶性肿瘤hθ(x)≥.05为恶性,hθ(x)<0.5为良性。
然而线性回归的鲁棒性很差,例如在图1.b的数据集上建立回归,因最右边噪点的存在,使回归模型在训练集上表现都很差。这主要是由于线性回归在整个实数域内敏感度一致,而分类范围,需要在[0,1]。
逻辑回归就是一种减小预测范围,将预测值限定为[0,1]间的一种回归模型,其回归方程与回归曲线如图2所示。逻辑曲线在z=0时,十分敏感,在z>>0或z<<0处,都不敏感,将预测值限定为(0,1)。
其实就是在线性回归的基础上,套用了一个逻辑函数:S形函数(Sigmoid function) 。
对模型的理解:
(1)
hθ(x)的作用是,对于给定的输入变量,根据选择的参数计算输出变量等于1 的可能性 :
例如,如果对于给定的 x,通过已经确定的参数计算得出 hθ(x)=0.7,则表示有 70%的几率 y 为正向类,相应地 y 为负向类的几率为 1-0.7=0.3。
LR与线性回归的不同点在于:为了将线性回归输出的很大范围的数,例如从负无穷到正无穷,压缩到0和1之间,这样的输出值表达为“可能性”才能说服广大民众。
损失函数的选择:
我们知道线性回归模型的损失函数为所有误差的平方和:
理论上说,我们也可以沿用这个定义,但是我们将公式(1) 代入之后得到的 损失函数为 非凸函数。 有很多局部最优解,这将影响梯度下降算法寻找全局最小值。
重新定义:
hθ(x)与 Cost(hθ(x),y)之间的关系如下图所示:
多类问题
最后,在我们需要做预测时,我们将所有的分类机都运行一遍,然后对每一个输入变量, 都选择最高可能性的输出变量。
正则化的逻辑回归模型
要最小化该代价函数,通过求导,得出梯度下降算法为:
注:看上去同线性回归一样,但是知道 hθ(x)=g(θTX),所以与线性回归不同。
应用场景:
分类问题的首选吧,如果它的效果不怎么样,那么可以将它的结果作为基准来参考,在基础上与其他算法进行比较
算法优劣:
优点:
●实现简单,广泛的应用于工业问题上;
●分类时计算量非常小,速度很快,存储资源低;
●便利的观测样本概率分数;
●对逻辑回归而言,多重共线性并不是问题,它可以结合L2正则化来解决该问题;(????)
缺点:
●当特征空间很大时,逻辑回归的性能不是很好;
●容易欠拟合,一般准确度不太高;
●不能很好地处理大量多类特征或变量;
●必须线性可分,对于非线性特征,需要进行转换;
算法评估:
对于LR分类模型的评估,常用AUC来评估。
实战案例:
某班级100名学生的语文和数学期末考试成绩,决定是否升学。根据打好标签的数据集,判断新的学生是否通过期末考试。
数据集:ex2data1.txt
语文 | 数学 | 是否通过 |
---|---|---|
34.62365962451697 | 78.0246928153624 | 0 |
60.18259938620976 | 86.30855209546826 | 1 |
。。 | 。。 | 。。 |
描述性分析:
代码:
Scala:
package com.dianping.www /** * Created by sun on 16/7/19. */ import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS import org.apache.spark.mllib.evaluation.MulticlassMetrics import org.apache.spark.mllib.linalg.Vectors import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.{SparkConf, SparkContext} object LR{ def main(args: Array[String]) { val conf = new SparkConf().setAppName("LR1.0") val sc = new SparkContext(conf) // split args val para = args(0).split(',') // load data and transform to LabeledPoint format. val data_tmp = sc.textFile(para(0)) val data = data_tmp.map( line => line.split(',')).map { r => val label = r(r.size - 1).toInt val features = r.slice(0,r.size -1).map(_.toDouble) LabeledPoint(label,Vectors.dense(features)) } // Split data into training (60%) and test (40%). val splits = data.randomSplit(Array(0.6, 0.4), seed = 11L) val training = splits(0).cache() val test = splits(1) // Run training algorithm to build the model val model = new LogisticRegressionWithLBFGS() .setNumClasses(para(1).toInt) //two class. .run(training) // clear Threshold // model.clearThreshold() // Compute raw scores on the test set. val predictionAndLabels = test.map { case LabeledPoint(label, features) => val prediction = model.predict(features) (prediction, label) } // Get evaluation metrics. val metrics = new MulticlassMetrics(predictionAndLabels) val precision = metrics.precision val recall = metrics.recall println("Precision = " + precision + ",Recall = " + recall) // Get AUC val metrics2 = new BinaryClassificationMetrics(predictionAndLabels) println("AreaUnderPR = " + metrics2.areaUnderPR() + "AUC = " + metrics2.areaUnderROC()) // Save and load model // model.save(sc, para(2)) sc.stop() } } |