環境
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正則化,支持多分類。