环境
spark-1.6
python3.5
一、有无截距
对于逻辑回归分类,就是找到z那条直线,不通过原点有截距的直线与通过原点的直线相比,有截距更能将数据分类的彻底。
package com.bjsxt.lr import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS} import org.apache.spark.mllib.util.MLUtils import org.apache.spark.{SparkConf, SparkContext} /** * 逻辑回归 健康状况训练集 */ object LogisticRegression { def main(args: Array[String]) { val conf = new SparkConf().setAppName("spark").setMaster("local[3]") val sc = new SparkContext(conf) //加载 LIBSVM 格式的数据 这种格式特征前缀要从1开始 val inputData = MLUtils.loadLibSVMFile(sc, "健康状况训练集.txt") val splits = inputData.randomSplit(Array(0.7, 0.3), seed = 1L) val (trainingData, testData) = (splits(0), splits(1)) val lr = new LogisticRegressionWithLBFGS() // lr.setIntercept(true) val model = lr.run(trainingData) val result = testData .map{point=>Math.abs(point.label-model.predict(point.features)) } println("正确率="+(1.0-result.mean())) /** *逻辑回归算法训练出来的模型,模型中的参数个数(w0....w6)=训练集中特征数(6)+1 */ println(model.weights.toArray.mkString(" ")) println(model.intercept) sc.stop() } }
package com.bjsxt.lr import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS, LogisticRegressionWithSGD} import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.mllib.util.MLUtils import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /** * 有无截距 */ object LogisticRegression2 { def main(args: Array[String]) { val conf = new SparkConf().setAppName("spark").setMaster("local[3]") val sc = new SparkContext(conf) val inputData: RDD[LabeledPoint] = MLUtils.loadLibSVMFile(sc, "w0测试数据.txt") /** * randomSplit(Array(0.7, 0.3))方法就是将一个RDD拆分成N个RDD,N = Array.length * 第一个RDD中的数据量和数组中的第一个元素值相关 */ val splits = inputData.randomSplit(Array(0.7, 0.3),11L) val (trainingData, testData) = (splits(0), splits(1)) val lr = new LogisticRegressionWithSGD // 设置要有W0,也就是有截距 lr.setIntercept(true) val model=lr.run(trainingData) val result=testData.map{labeledpoint=>Math.abs(labeledpoint.label-model.predict(labeledpoint.features)) } println("正确率="+(1.0-result.mean())) println(model.weights.toArray.mkString(" ")) println(model.intercept) } }
二、线性不可分问题
对于线性不可分问题,可以使用升高维度的方式转换成线性可分问题。低维空间的非线性问题在高维空间往往会成为线性问题。
package com.bjsxt.lr import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS, LogisticRegressionWithSGD} import org.apache.spark.mllib.linalg.Vectors import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.mllib.util.MLUtils import org.apache.spark.{SparkConf, SparkContext} /** * 线性不可分 ----升高维度 */ object LogisticRegression3 { def main(args: Array[String]) { val conf = new SparkConf().setAppName("spark").setMaster("local[3]") val sc = new SparkContext(conf) // 解决线性不可分我们来升维,升维有代价,计算复杂度变大了 val inputData = MLUtils.loadLibSVMFile(sc, "线性不可分数据集.txt") .map { labelpoint => val label = labelpoint.label val feature = labelpoint.features //新维度的值,必须基于已有的维度值的基础上,经过一系列的数学变换得来 val array = Array(feature(0), feature(1), feature(0) * feature(1)) val convertFeature = Vectors.dense(array) new LabeledPoint(label, convertFeature) } val splits = inputData.randomSplit(Array(0.7, 0.3),11L) val (trainingData, testData) = (splits(0), splits(1)) val lr = new LogisticRegressionWithLBFGS() lr.setIntercept(true) val model = lr.run(trainingData) val result = testData .map { point => Math.abs(point.label - model.predict(point.features)) } println("正确率=" + (1.0 - result.mean())) println(model.weights.toArray.mkString(" ")) println(model.intercept) } }
三、调整分类阈值
在一些特定的场景下,如果按照逻辑回归默认的分类阈值0.5来进行分类的话,可能存在一些潜在的风险,比如,假如使用逻辑回归预测一个病人得癌症的概率是0.49,那么按照0.5的阈值,病人推测出来是没有得癌症的,但是49%的概率得癌症,比例相对来说得癌症的可能性也是很高,那么我们就可以降低分类的阈值,比如将阈值设置为0.3,小于0.3认为不得癌症,大于0.3认为得癌症,这样如果病人真的是癌症患者,规避掉了0.49概率下推断病人是不是癌症的风险。
降低阈值会使逻辑回归整体的正确率下降,错误率增大,但是规避了一些不能接受的风险。
package com.bjsxt.lr import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS, LogisticRegressionWithSGD} import org.apache.spark.mllib.util.MLUtils import org.apache.spark.{SparkConf, SparkContext} /** * 设置分类阈值 */ object LogisticRegression4 { def main(args: Array[String]) { val conf = new SparkConf().setAppName("spark").setMaster("local[3]") val sc = new SparkContext(conf) /** * LabeledPoint = Vector+Y */ val inputData = MLUtils.loadLibSVMFile(sc, "健康状况训练集.txt") val splits = inputData.randomSplit(Array(0.7, 0.3),11L) val (trainingData, testData) = (splits(0), splits(1)) val lr = new LogisticRegressionWithLBFGS() lr.setIntercept(true) // val model = lr.run(trainingData) // val result = testData // .map{point=>Math.abs(point.label-model.predict(point.features)) } // println("正确率="+(1.0-result.mean())) // println(model.weights.toArray.mkString(" ")) // println(model.intercept) /** * 如果在训练模型的时候没有调用clearThreshold这个方法,那么这个模型预测出来的结果都是分类号 * 如果在训练模型的时候调用clearThreshold这个方法,那么这个模型预测出来的结果是一个概率 */ val model = lr.run(trainingData).clearThreshold() val errorRate = testData.map{p=> //score就是一个概率值 val score = model.predict(p.features) // 癌症病人宁愿判断出得癌症也别错过一个得癌症的病人 val result = score>0.3 match {case true => 1 ; case false => 0} Math.abs(result-p.label) }.mean() println(1-errorRate) } }
四、鲁棒性调优
鲁棒是Robust的音译,也就是健壮和强壮的意思,比如说,计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能不死机、不崩溃,就是该软件的鲁棒性,那么算法的鲁棒性就是指这个算法的抗干扰能力强。
package com.bjsxt.lr import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS, LogisticRegressionWithSGD} import org.apache.spark.mllib.optimization.{L1Updater, SquaredL2Updater} import org.apache.spark.mllib.util.MLUtils import org.apache.spark.{SparkConf, SparkContext} /** * 鲁棒性调优 * 提高模型抗干扰能力 */ object LogisticRegression5 { def main(args: Array[String]) { val conf = new SparkConf().setAppName("spark").setMaster("local[3]") val sc = new SparkContext(conf) val inputData = MLUtils.loadLibSVMFile(sc, "健康状况训练集.txt") val splits = inputData.randomSplit(Array(0.7, 0.3),100) val (trainingData, testData) = (splits(0), splits(1)) /** * LogisticRegressionWithSGD 既有L1 又有L2正则化(默认) */ val lr = new LogisticRegressionWithSGD() lr.setIntercept(true) // lr.optimizer.setUpdater(new L1Updater()) lr.optimizer.setUpdater(new SquaredL2Updater) /** * LogisticRegressionWithLBFGS 既有L1 又有L2正则化(默认) */ // val lr = new LogisticRegressionWithLBFGS() // lr.setIntercept(true) // lr.optimizer.setUpdater(new L1Updater) // lr.optimizer.setUpdater(new SquaredL2Updater) /** * 这块设置的是我们的lambda,越大越看重这个模型的推广能力,一般不会超过1,0.4是个比较好的值 */ lr.optimizer.setRegParam(0.4) val model = lr.run(trainingData) val result=testData .map{point=>Math.abs(point.label-model.predict(point.features)) } println("正确率="+(1.0-result.mean())) println(model.weights.toArray.mkString(" ")) println(model.intercept) } }
五、归一化数据
多个维度特征的量级不同,会导致训练出来模型中不同特征对应的w参数差异很大,容易导致参数小的特征对目标函数的影响被覆盖,所以需要对每个特征的数据进行归一化处理,以减少不同量级的特征数据覆盖其他特征对目标函数的影响。
归一化数据可以使各个特征维度对目标函数的影响权重一致,提高迭代的求解的收敛速度。
最大最小值归一化: ,缺点是抗干扰能力弱,受离群值影响比较大,中间容易没有数据。最大最小值归一化后的数据落在[0,1]之间。假设某个特征下有一组数据:1,2,3,4,5,100那么对数据使用最大最小值归一化后的值为:0,2/99,3/99,4/99,1。中间没有数据,受离群值100的影响大。
package com.bjsxt.lr import org.apache.spark.SparkConf import org.apache.spark.SparkContext import org.apache.spark.ml.feature.MinMaxScaler import org.apache.spark.sql.SQLContext import org.apache.spark.mllib.linalg.DenseVector import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.mllib.linalg.Vectors import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS /** * 最大最小值归一化 */ object LogisticRegression7 { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("spark").setMaster("local") val sc = new SparkContext(conf) val sqlContext = new SQLContext(sc) /** * 加载生成的DataFrame自动有两列:label features */ val df = sqlContext.read.format("libsvm").load("环境分类数据.txt") // df.show() /** * MinMaxScaler fit需要DataFrame类型数据 * setInputCol:设置输入的特征名 * setOutputCol:设置归一化后输出的特征名 * */ val minMaxScalerModel = new MinMaxScaler() .setInputCol("features") .setOutputCol("scaledFeatures") .fit(df) /** * 将所有数据归一化 */ val features = minMaxScalerModel.transform(df) features.show() val normalizeInputData = features.rdd.map(row=>{ val label = row.getAs("label").toString().toDouble val dense = (row.getAs("scaledFeatures")).asInstanceOf[DenseVector] new LabeledPoint(label,dense) }) val splits = normalizeInputData.randomSplit(Array(0.7, 0.3),11L) val (trainingData, testData) = (splits(0), splits(1)) val lr=new LogisticRegressionWithLBFGS() lr.setIntercept(true) val model = lr.run(trainingData) val result=testData.map{point=>Math.abs(point.label-model.predict(point.features)) } println("正确率="+(1.0-result.mean())) println(model.weights.toArray.mkString(" ")) println(model.intercept) } }
方差归一化: ,其中u是样本的均值, 是样本的标准差(方差的开方,方差:所有点与均值的差值平方和)。方差归一化抗干扰能力强,和所有数据有关,求标准差需要所有的值介入,若有离群值,会被抑制下来。但是归一化后的数据最终的结果不一定落在0到1之间。
注意:理论上一个模型算法如果拿到训练集所有的特征一起训练模型就要归一化数据。决策树算法可以不归一化数据。
package com.bjsxt.lr import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS, LogisticRegressionWithSGD} import org.apache.spark.mllib.feature.StandardScaler import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.mllib.util.MLUtils import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.ml.feature.MinMaxScaler import org.apache.spark.sql.SQLContext /** * 方差归一化 */ object LogisticRegression6 { def main(args: Array[String]) { val conf = new SparkConf().setAppName("spark").setMaster("local[3]") val sc = new SparkContext(conf) val sqlContext = new SQLContext(sc) /** * scalerModel 这个对象中已经有每一列的均值和方差 * withStd:代表的是方差归一化 * withMean:代表的是均值归一化 * scalerModel:存放每一列的方差值 * * withMean默认为false, withStd默认为true * 当withMean=true,withStd=false时,向量中的各元素均减去它相应的均值。 * 当withMean=true,withStd=true时,各元素在减去相应的均值之后,还要除以它们相应的标准差。 * */ val inputData = MLUtils.loadLibSVMFile(sc, "环境分类数据.txt") val vectors = inputData.map(_.features) val scalerModel = new StandardScaler(withMean=true, withStd=true).fit(vectors) val normalizeInputData = inputData.map{point => val label = point.label //对每一条数据进行了归一化 val features = scalerModel.transform(point.features.toDense) println(features) new LabeledPoint(label,features) } val splits = normalizeInputData.randomSplit(Array(0.7, 0.3),100) val (trainingData, testData) = (splits(0), splits(1)) val lr=new LogisticRegressionWithLBFGS() // val lr = new LogisticRegressionWithSGD() lr.setIntercept(true) val model = lr.run(trainingData) val result=testData.map{point=>Math.abs(point.label-model.predict(point.features)) } println("正确率="+(1.0-result.mean())) println(model.weights.toArray.mkString(" ")) println(model.intercept) } }
六、调整数据的正负值-均值归一化
均值归一化是将原来的特征值减去这个特征在数据集中的均值,这样就会使x的各个维度取值上有正有负,在迭代求 参数时,能减少迭代的次数。
七、训练方法选择
训练逻辑回归的方法有:SGD和L-BFGS,两者的区别为:
SGD:随机从训练集选取数据训练,不归一化数据,需要专门在外面进行归一化,支持L1,L2正则化,不支持多分类。
L-BFGS:所有的数据都会参与训练,算法融入方差归一化和均值归一化。支持L1,L2正则化,支持多分类。