机器学习 — 逻辑回归分类(预测)

逻辑回归

        首先 : 逻辑回归是一种 线性 有监督 分类 模型

何为线性?

         逻辑回归本质就是多元线性回归,只是线性回归的结果范围是 负无穷到正无穷 , 而逻辑回归是 0到1之间,也是逻辑回归对线性回归做了 归一化。     

      

         逻辑回归值在 0到1之间,0~0.5为负值,0.5~1为正值,也就是逻辑回归是做 二分类 的,那如何做到多分类呢?

                        其实可以分解为做多个二分类,即对每个分类号都进行一次计算,求出该输入数据属于在这个分类的概率(二分类:是这个分类,不是这个分类),这样求出属于每个分类的概率,其中最大概率的分类为这个输入数据的分类。

何为有监督?

         机器学习中分为 有监督机器学习 和 无监督机器学习 :

                有监督其实就是 有训练集 , 即 既有X也有Y , 有Y则可以根据Y调整模型。

                无监督同理就是 只有X没有Y , 比如聚类,推荐中 就是 只给定 X,求出哪些X属于一类。

         即分类中用来分类的训练数据中,是要人为的给定分类,并在训练中根据正确的分类号调整模型,使之达到最优解。


综上可得

        逻辑回归是一种用于分类的模型,就相当于 y=f(x),表明输入与输出(类别)的关系

      最常见问题有如医生治病时的望、闻、问、切, 之后判定病人是否生病或生了什么病,
                其中的望闻问切就是输入,即特征数据,
                判断是否生病就相当于获取因变量y,即分类结果。
                二分类:要么A类 要么B类
                        比如人的身体状况为2中:1.健康;2生病
                        健康的可能性是1,生病的可能性就是0
                        健康的可能性是0.8,生病的可能性是0.2


逻辑回归预测过程:

    根据影响结果的维度构建多元线性方程,而方程的的代入数据求解由MLlib完成, 然后得到方程,在对方程的Y做归一化 减少到 0~1之间

    那 如何归一化呢 ? 就是如下公式

            

    对多元线性回归方程做了归一化之后,就是如下图:


    而逻辑回归又会对归一化之后的值 做二分类 (0~0.5为一类,0.5~1为另一类): 

代码: 

    数据:

1 1:57 2:0 3:0 4:5 5:3 6:5 
1 1:56 2:1 3:0 4:3 5:4 6:3 
1 1:27 2:0 3:0 4:4 5:3 6:4 
1 1:46 2:0 3:0 4:3 5:2 6:4 
1 1:75 2:1 3:0 4:3 5:3 6:2 
1 1:19 2:1 3:0 4:4 5:4 6:4 
1 1:49 2:0 3:0 4:4 5:3 6:3 
1 1:25 2:1 3:0 4:3 5:5 6:4 
...........

    LogisticRegressionWithLBFGS() : 拟牛顿法

object LogisticRegression1 {

  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, "./SparkMLlib/健康状况训练集.txt")
    val splits = inputData.randomSplit(Array(0.7, 0.3), seed = 1L)  // 划分训练集与测试集, 设置seed参数可以使每次随机划分的数据一样
    val (trainingData, testData) = (splits(0), splits(1))
    val lr = new LogisticRegressionWithLBFGS()  // LogisticRegressionWithLBFGS
    val model = lr.run(trainingData)  // 训练得模型
    val result = testData.map { point =>
          Math.abs( point.label - model.predict(point.features) )   // 正确值与预测值相减
    }
    println("正确率=" + (1.0 - result.mean())) // result.mean() 平均值即错误值
    println(model.weights.toArray.mkString(" "))  //输出w
    println(model.intercept)  // 输出截距
  }

}

结果:


根据需求的变通 去除固定阈值0.5

         癌症病人的判断? 两种错误情况

                假如病人是癌症:判断成不是癌症

                假如病人是非癌症: 判断是癌症

         明显第一种情况是不能接受的,这样就可以将阈值降低,虽然整体的错误率变大了,但是规避了一些不能接受的风险

         clearThreshold() : 

object LogisticRegression4 {

  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))
    val (trainingData, testData) = (splits(0), splits(1))
    val lr = new LogisticRegressionWithLBFGS()
    lr.setIntercept(true)
    val model = lr.run(trainingData).clearThreshold() //去除阈值
    val errorRate = testData.map{ p=>
      val score = model.predict(p.features)
      // 癌症病人宁愿判断出得癌症也别错过一个得癌症的病人
      val result = score>0.4 match {case true => 1 ; case false => 0}  //自己判断是否是癌症
      Math.abs(result-p.label)
    }.mean()
    println(1-errorRate)
  }
}


那是怎么样求解W的呢?

逻辑回归是有监督的:

        即根据求出的模型带入源数据,看求出的结果与正确结果比较,使之误差达到最小,而求得w的最优解。

         

添加截距 W0 : 

         将 Z 放入带入 y的归一化公式 , 而分类的阈值为 0.5 :

            

         即    , 也就是 : 

               

       那我们画出  的图:

         

    看图可得,右上部分与左下部分的方程值是 互为正负的 , 也就是 线把两个分类的数据给分开了: 

        

     而由   可得,当 W0  为 0 时,直线是过原点的,如下图:

        

     那 有w0 还是 无w0 好呢?  有w0好,因为如果没有w0,直线就会原点,但是有很多情况的需要分类的点,是过原点的直线无法切分的,如下图:

            

    代码: lr.setIntercept(true) 
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")
    val splits = inputData.randomSplit(Array(0.7, 0.3))
    val (trainingData, testData) = (splits(0), splits(1))
    val lr = new LogisticRegressionWithLBFGS()
    // 设置要有W0
    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)
  }
}

    注释 lr.setIntercept(true) 结果为:

   

    添加截距后结果为:

    

线性不可分: 

        一种特殊情况,线性不可分: 

                

        这时候一种解决方案:映射至高维 , 即增加维度:

            

        例子: 例如添加一个新维度 等于 X1*X2

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, "./SparkMLlib/线性不可分数据集.txt")
      .map { labelpoint =>
        val label = labelpoint.label
        val feature = labelpoint.features
        val array = Array(feature(0), feature(1), 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))
    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)
  }

}

        不增加维度的正确率为: 

     

        增加新维度正确率为:

        


梯度下降法:

        上面讲了两种方法让 方程 在大的范围内确立了方程求解的可能性 ,

         而其中一种方程求解方法 — 梯度下降法 是以更快的方法调整参数w使方程逼近最优解 

                SGD: 随机梯度下降法    LogisticRegressionWithSGD()

                                                          另外一种算法为  拟牛顿法  :  LogisticRegressionWithLBFGS()  

         这里讲解随机梯度下降法:

        首先:    

         判断求出的参数是否为最优解是 计算有模型算出的预测值与正确值之间的误差 判断的

         误差方程为:  (不需要理解)

                    

        站在数学的角度上来说,即误差方程值最小化 即最优解:

                    

                

                1、极值分为极大值和极小值

                2、已经由数学证明上述误差函数为凹函数,即只有极小值,无极大值

                3、因此求解最小值的过程就为求解极值的过程

        对误差方程求导,求极值就是让导数等于0:

                

        但是求解合适的参数很困难,可以看到几乎无法求解

                

        已知参数,代入公式计算结果却很容易:

                    

       对于一组参数   

                 

         对于多组参数: 

                      

                  可以看到 随着参数组的变化,误差值在慢慢减小,最终达到一个凹点即极小值 ,即如果误差减少就继续减小,如果变大就回退回去。

         把误差函数当成一座山,梯度就是往前走时陡峭程度的数字化表现:

                

         最优解是达到山底, 绝对值越大,此时山越陡峭

                1、梯度 > 0,往前走是上山 ,往回走

                2、梯度 < 0,往前走是下山,每次往下走一定的距离(Step步长),停下来再寻找另外一个坡度最陡(斜率最大)的方向往下走,

                3、如此循环,直到谷底(斜率为0)。

         公式:

         

    //逻辑回归训练,两个参数,迭代次数和步长,生产常用调整参数
    val lr = new LogisticRegressionWithSGD()
    // 设置W0截距
    lr.setIntercept(true)

    // 设置梯度下降的步长
    lr.optimizer.setStepSize(0.1)

    // 最大迭代次数
    lr.optimizer.setNumIterations(10)

    val model: LogisticRegressionModel = lr.run(la)


添加新评论