SparkML之回归(三)保序回归

来源:互联网 发布:mac ant 环境变量 编辑:程序博客网 时间:2024/04/19 19:12

在写這篇博客的时候,翻阅了一些互联网上的资料,发现文献[1]写的比较系统。所以推荐大家读读文献[1].但是出现了一些错误,所以我在此简述一些。如果推理不过去了。可以看看我的简述。

------------------------------------前言


背景:

(1)在医学领域药物剂量反应中,随着药物剂量的增加,疗效和副作用会呈现一定趋势。比如剂量越高,疗效越

高,剂量越高,毒性越大等

(2)评估药物在不同剂量水平下的毒性,并且建议一个对病人既安全又有效的剂量称为最大耐受剂量(Maximum Tolerated Dose)简称 MTD。

(3)随着药物的增加,药物的毒性是非减的。MTD被定义为毒性概率不超过毒性靶水平的最高剂量水平

(4)基于每个剂量水平下病人的毒性反应的比率估计不同,剂量水平下的毒性概率可能不是剂量水平的非减函

数,于是我们可以采用保序回归的方法



L2保序回归



L2保序回归算法

一些具体的定义和命题查看文献[1]




Spark源码分析(大图见附录)


  1. /** 
  2.  * 保序回归模型 
  3.  * 
  4.  * @param boundaries 用于预测的边界数组,它必须是排好顺序的。(分段函数的分段点数组) 
  5.  * @param predictions 保序回归的结果,即分段点x对应的预测值 
  6.  * @param isotonic 升序还是降序(true为升) 
  7.  */  
  8. @Since("1.3.0")  
  9. class IsotonicRegressionModel @Since("1.3.0") (  
  10.     @Since("1.3.0") val boundaries: Array[Double],  
  11.     @Since("1.3.0") val predictions: Array[Double],  
  12.     @Since("1.3.0") val isotonic: Boolean) extends Serializable with Saveable {  
  13.   
  14.   private val predictionOrd = if (isotonic) Ordering[Double] else Ordering[Double].reverse  
  15.   
  16.   require(boundaries.length == predictions.length)  
  17.   assertOrdered(boundaries)  
  18.   assertOrdered(predictions)(predictionOrd)  
  19.   
  20.   /** 
  21.    * A Java-friendly constructor that takes two Iterable parameters and one Boolean parameter. 
  22.    */  
  23.   @Since("1.4.0")  
  24.   def this(boundaries: java.lang.Iterable[Double],  
  25.       predictions: java.lang.Iterable[Double],  
  26.       isotonic: java.lang.Boolean) = {  
  27.     this(boundaries.asScala.toArray, predictions.asScala.toArray, isotonic)  
  28.   }  
  29.   
  30.   /** 序列顺序的检测 */  
  31.   private def assertOrdered(xs: Array[Double])(implicit ord: Ordering[Double]): Unit = {  
  32.     var i = 1  
  33.     val len = xs.length  
  34.     while (i < len) {  
  35.       require(ord.compare(xs(i - 1), xs(i)) <= 0,  
  36.         s"Elements (${xs(i - 1)}, ${xs(i)}) are not ordered.")  
  37.       i += 1  
  38.     }  
  39.   }  
  40.   
  41.   /** 
  42.    * 利用分段函数的线性函数,输入feature进行预测 
  43.    * 
  44.    * @param testData Features to be labeled. 
  45.    * @return Predicted labels. 
  46.    * 
  47.    */  
  48.   @Since("1.3.0")  
  49.   def predict(testData: RDD[Double]): RDD[Double] = {  
  50.     testData.map(predict)  
  51.   }  
  52.   
  53.   /** 
  54.    * 利用分段函数的线性函数,输入feature进行预测 
  55.    * 
  56.    * @param testData Features to be labeled. 
  57.    * @return Predicted labels. 
  58.    * 
  59.    */  
  60.   @Since("1.3.0")  
  61.   def predict(testData: JavaDoubleRDD): JavaDoubleRDD = {  
  62.     JavaDoubleRDD.fromRDD(predict(testData.rdd.retag.asInstanceOf[RDD[Double]]))  
  63.   }  
  64.   
  65.   /** 
  66.    * 利用分段函数的线性函数,输入feature进行预测 
  67.    * 
  68.    * @param testData Feature to be labeled. 
  69.    * @return Predicted label. 
  70.    *         1) 如果testdata可以精确匹配到一个边界数组,那么就返回对应的数值,如果多个,那么随机返回一个 
  71.    *         2) 如果testdata 低于或者高于所有的边界数组,那么返回第一个或者最后一个If testData is lower or higher than all boundaries then first or last prediction 
  72.    *         3) 如果testdat在两个边界数组之间,那么采用分段函数的线性插值方法得到的数值 
  73.    * 
  74.    */  
  75.   @Since("1.3.0")  
  76.   def predict(testData: Double): Double = {  
  77.   
  78.     def linearInterpolation(x1: Double, y1: Double, x2: Double, y2: Double, x: Double): Double = {  
  79.       y1 + (y2 - y1) * (x - x1) / (x2 - x1)  
  80.     }  
  81.   
  82.     val foundIndex = binarySearch(boundaries, testData)  
  83.     val insertIndex = -foundIndex - 1  
  84.   
  85.     // Find if the index was lower than all values,  
  86.     // higher than all values, in between two values or exact match.  
  87.     if (insertIndex == 0) {  
  88.       predictions.head  
  89.     } else if (insertIndex == boundaries.length) {  
  90.       predictions.last  
  91.     } else if (foundIndex < 0) {  
  92.       linearInterpolation(  
  93.         boundaries(insertIndex - 1),  
  94.         predictions(insertIndex - 1),  
  95.         boundaries(insertIndex),  
  96.         predictions(insertIndex),  
  97.         testData)  
  98.     } else {  
  99.       predictions(foundIndex)  
  100.     }  
  101.   }  
  102.   
  103.   /** A convenient method for boundaries called by the Python API. */  
  104.   private[mllib] def boundaryVector: Vector = Vectors.dense(boundaries)  
  105.   
  106.   /** A convenient method for boundaries called by the Python API. */  
  107.   private[mllib] def predictionVector: Vector = Vectors.dense(predictions)  
  108.   
  109.   @Since("1.4.0")  
  110.   override def save(sc: SparkContext, path: String): Unit = {  
  111.     IsotonicRegressionModel.SaveLoadV1_0.save(sc, path, boundaries, predictions, isotonic)  
  112.   }  
  113.   
  114.   override protected def formatVersion: String = "1.0"  
  115. }  
  116.   
  117. @Since("1.4.0")  
  118. object IsotonicRegressionModel extends Loader[IsotonicRegressionModel] {  
  119.   
  120.   import org.apache.spark.mllib.util.Loader._  
  121.   
  122.   private object SaveLoadV1_0 {  
  123.   
  124.     def thisFormatVersion: String = "1.0"  
  125.   
  126.     /** Hard-code class name string in case it changes in the future */  
  127.     def thisClassName: String = "org.apache.spark.mllib.regression.IsotonicRegressionModel"  
  128.   
  129.     /** Model data for model import/export */  
  130.     case class Data(boundary: Double, prediction: Double)  
  131.   
  132.     def save(  
  133.         sc: SparkContext,  
  134.         path: String,  
  135.         boundaries: Array[Double],  
  136.         predictions: Array[Double],  
  137.         isotonic: Boolean): Unit = {  
  138.       val sqlContext = SQLContext.getOrCreate(sc)  
  139.   
  140.       val metadata = compact(render(  
  141.         ("class" -> thisClassName) ~ ("version" -> thisFormatVersion) ~  
  142.           ("isotonic" -> isotonic)))  
  143.       sc.parallelize(Seq(metadata), 1).saveAsTextFile(metadataPath(path))  
  144.   
  145.       sqlContext.createDataFrame(  
  146.         boundaries.toSeq.zip(predictions).map { case (b, p) => Data(b, p) }  
  147.       ).write.parquet(dataPath(path))  
  148.     }  
  149.   
  150.     def load(sc: SparkContext, path: String): (Array[Double], Array[Double]) = {  
  151.       val sqlContext = SQLContext.getOrCreate(sc)  
  152.       val dataRDD = sqlContext.read.parquet(dataPath(path))  
  153.   
  154.       checkSchema[Data](dataRDD.schema)  
  155.       val dataArray = dataRDD.select("boundary""prediction").collect()  
  156.       val (boundaries, predictions) = dataArray.map { x =>  
  157.         (x.getDouble(0), x.getDouble(1))  
  158.       }.toList.sortBy(_._1).unzip  
  159.       (boundaries.toArray, predictions.toArray)  
  160.     }  
  161.   }  
  162.   
  163.   @Since("1.4.0")  
  164.   override def load(sc: SparkContext, path: String): IsotonicRegressionModel = {  
  165.     implicit val formats = DefaultFormats  
  166.     val (loadedClassName, version, metadata) = loadMetadata(sc, path)  
  167.     val isotonic = (metadata \ "isotonic").extract[Boolean]  
  168.     val classNameV1_0 = SaveLoadV1_0.thisClassName  
  169.     (loadedClassName, version) match {  
  170.       case (className, "1.0"if className == classNameV1_0 =>  
  171.         val (boundaries, predictions) = SaveLoadV1_0.load(sc, path)  
  172.         new IsotonicRegressionModel(boundaries, predictions, isotonic)  
  173.       case _ => throw new Exception(  
  174.         s"IsotonicRegressionModel.load did not recognize model with (className, format version):" +  
  175.         s"($loadedClassName, $version).  Supported:\n" +  
  176.         s"  ($classNameV1_0, 1.0)"  
  177.       )  
  178.     }  
  179.   }  
  180. }  
  181.   
  182. /** 
  183.  * Isotonic regression. 
  184.  * Currently implemented using parallelized pool adjacent violators algorithm. 
  185.  * Only univariate (single feature) algorithm supported. 
  186.  * 
  187.  * Sequential PAV implementation based on: 
  188.  * Tibshirani, Ryan J., Holger Hoefling, and Robert Tibshirani. 
  189.  *   "Nearly-isotonic regression." Technometrics 53.1 (2011): 54-61. 
  190.  *   Available from [[http://www.stat.cmu.edu/~ryantibs/papers/neariso.pdf]] 
  191.  * 
  192.  * Sequential PAV parallelization based on: 
  193.  * Kearsley, Anthony J., Richard A. Tapia, and Michael W. Trosset. 
  194.  *   "An approach to parallelizing isotonic regression." 
  195.  *   Applied Mathematics and Parallel Computing. Physica-Verlag HD, 1996. 141-147. 
  196.  *   Available from [[http://softlib.rice.edu/pub/CRPC-TRs/reports/CRPC-TR96640.pdf]] 
  197.  * 
  198.  * @see [[http://en.wikipedia.org/wiki/Isotonic_regression Isotonic regression (Wikipedia)]] 
  199.  */  
  200. @Since("1.3.0")  
  201. class IsotonicRegression private (private var isotonic: Boolean) extends Serializable {  
  202.   
  203.   /** 
  204.    * 构建IsotonicRegression实例的默认参数:isotonic = true 
  205.    * 
  206.    * @return New instance of IsotonicRegression. 
  207.    */  
  208.   @Since("1.3.0")  
  209.   def this() = this(true)  
  210.   
  211.   /** 
  212.    * 设置序列的参数(Sets the isotonic parameter). 
  213.    * 
  214.    * @param isotonic 序列是递增的还是递减的 
  215.    * @return This instance of IsotonicRegression. 
  216.    */  
  217.   @Since("1.3.0")  
  218.   def setIsotonic(isotonic: Boolean): this.type = {  
  219.     this.isotonic = isotonic  
  220.     this  
  221.   }  
  222.   
  223.   /** 
  224.    * 运行保序回归算法,来构建保序回归模型 
  225.    * @param input 输入一个 RDD 内部数据形式为 tuples (label, feature, weight) ,其中,label 是对每次计算都会改变 
  226.    *    feature 特征变量 你weight 权重(默认为1)         
  227.    * @return Isotonic regression model. 
  228.    */  
  229.   @Since("1.3.0")  
  230.   def run(input: RDD[(Double, Double, Double)]): IsotonicRegressionModel = {  
  231.     val preprocessedInput = if (isotonic) {  
  232.       input  
  233.     } else {  
  234.       input.map(x => (-x._1, x._2, x._3))  
  235.     }  
  236.   
  237.     val pooled = parallelPoolAdjacentViolators(preprocessedInput)  
  238.   
  239.     val predictions = if (isotonic) pooled.map(_._1) else pooled.map(-_._1)  
  240.     val boundaries = pooled.map(_._2)  
  241.   
  242.     new IsotonicRegressionModel(boundaries, predictions, isotonic)  
  243.   }  
  244.   
  245.   /** 
  246.    * Run pool adjacent violators algorithm to obtain isotonic regression model. 
  247.    * 
  248.    * @param input JavaRDD of tuples (label, feature, weight) where label is dependent variable 
  249.    *              for which we calculate isotonic regression, feature is independent variable 
  250.    *              and weight represents number of measures with default 1. 
  251.    *              If multiple labels share the same feature value then they are ordered before 
  252.    *              the algorithm is executed. 
  253.    * @return Isotonic regression model. 
  254.    */  
  255.   @Since("1.3.0")  
  256.   def run(input: JavaRDD[(JDouble, JDouble, JDouble)]): IsotonicRegressionModel = {  
  257.     run(input.rdd.retag.asInstanceOf[RDD[(Double, Double, Double)]])  
  258.   }  
  259.   
  260.   /** 
  261.    * Performs a pool adjacent violators algorithm (PAV算法). 
  262.    * @param input 输入的数据  形式为: (label, feature, weight). 
  263.    * @return 按照保序回归的定义,返回一个有序的序列 
  264.    */  
  265.   private def poolAdjacentViolators(  
  266.       input: Array[(Double, Double, Double)]): Array[(Double, Double, Double)] = {  
  267.   
  268.     if (input.isEmpty) {  
  269.       return Array.empty  
  270.     }  
  271.   
  272.     // Pools sub array within given bounds assigning weighted average value to all elements.  
  273.     def pool(input: Array[(Double, Double, Double)], start: Int, end: Int): Unit = {  
  274.       val poolSubArray = input.slice(start, end + 1)  
  275.   
  276.       val weightedSum = poolSubArray.map(lp => lp._1 * lp._3).sum  
  277.       val weight = poolSubArray.map(_._3).sum  
  278.   
  279.       var i = start  
  280.       while (i <= end) {  
  281.         input(i) = (weightedSum / weight, input(i)._2, input(i)._3)  
  282.         i = i + 1  
  283.       }  
  284.     }  
  285.   
  286.     var i = 0  
  287.     val len = input.length  
  288.     while (i < len) {  
  289.       var j = i  
  290.   
  291.       // Find monotonicity violating sequence, if any.  
  292.       while (j < len - 1 && input(j)._1 > input(j + 1)._1) {  
  293.         j = j + 1  
  294.       }  
  295.   
  296.       // If monotonicity was not violated, move to next data point.  
  297.       if (i == j) {  
  298.         i = i + 1  
  299.       } else {  
  300.         // Otherwise pool the violating sequence  
  301.         // and check if pooling caused monotonicity violation in previously processed points.  
  302.         while (i >= 0 && input(i)._1 > input(i + 1)._1) {  
  303.           pool(input, i, j)  
  304.           i = i - 1  
  305.         }  
  306.   
  307.         i = j  
  308.       }  
  309.     }  
  310.   
  311.     // For points having the same prediction, we only keep two boundary points.  
  312.     val compressed = ArrayBuffer.empty[(Double, Double, Double)]  
  313.   
  314.     var (curLabel, curFeature, curWeight) = input.head  
  315.     var rightBound = curFeature  
  316.     def merge(): Unit = {  
  317.       compressed += ((curLabel, curFeature, curWeight))  
  318.       if (rightBound > curFeature) {  
  319.         compressed += ((curLabel, rightBound, 0.0))  
  320.       }  
  321.     }  
  322.     i = 1  
  323.     while (i < input.length) {  
  324.       val (label, feature, weight) = input(i)  
  325.       if (label == curLabel) {  
  326.         curWeight += weight  
  327.         rightBound = feature  
  328.       } else {  
  329.         merge()  
  330.         curLabel = label  
  331.         curFeature = feature  
  332.         curWeight = weight  
  333.         rightBound = curFeature  
  334.       }  
  335.       i += 1  
  336.     }  
  337.     merge()  
  338.   
  339.     compressed.toArray  
  340.   }  
  341.   
  342.   /** 
  343.    * Performs并行PAV算法实现 
  344.    * 将pav应用在每个分区,之后再进行合并。 
  345.    * @param input Input data of tuples (label, feature, weight). 
  346.    * @return Result tuples (label, feature, weight) where labels were updated 
  347.    *         to form a monotone sequence as per isotonic regression definition. 
  348.    */  
  349.   private def parallelPoolAdjacentViolators(  
  350.       input: RDD[(Double, Double, Double)]): Array[(Double, Double, Double)] = {  
  351.     val parallelStepResult = input  
  352.       .sortBy(x => (x._2, x._1))  
  353.       .glom()  
  354.       .flatMap(poolAdjacentViolators)  
  355.       .collect()  
  356.       .sortBy(x => (x._2, x._1)) // Sort again because collect() doesn't promise ordering.  
  357.     poolAdjacentViolators(parallelStepResult)  
  358.   }  
  359. }  

spark实验


import org.apache.spark.mllib.regression.{IsotonicRegression, IsotonicRegressionModel}import org.apache.spark.{SparkConf, SparkContext}object IsotonicRegressionExample {  def main(args: Array[String]) {    val conf = new SparkConf().setAppName("IsotonicRegressionExample").setMaster("local")    val sc = new SparkContext(conf)    val data = sc.textFile("C:\\Users\\alienware\\IdeaProjects\\sparkCore\\data\\mllib\\sample_isotonic_regression_data.txt")    // Create label, feature, weight tuples from input data with weight set to default value 1.0.    val parsedData = data.map { line =>      val parts = line.split(',').map(_.toDouble)      (parts(0), parts(1), 1.0)    }    // Split data into training (60%) and test (40%) sets.    val splits = parsedData.randomSplit(Array(0.6, 0.4), seed = 11L)    val training = splits(0)    val test = splits(1)    // Create isotonic regression model from training data.    // Isotonic parameter defaults to true so it is only shown for demonstration    val model = new IsotonicRegression().setIsotonic(true).run(training)    // Create tuples of predicted and real labels.    val predictionAndLabel = test.map { point =>      val predictedLabel = model.predict(point._2)      (predictedLabel, point._1)    }    //predictionAndLabel.foreach(println)    /**      * (0.16868944399999988,0.31208567)(0.16868944399999988,0.35900051)(0.16868944399999988,0.03926568)(0.16868944399999988,0.12952575)(0.16868944399999988,0.0)(0.16868944399999988,0.01376849)(0.16868944399999988,0.13105558)(0.19545421571428565,0.13717491)(0.19545421571428565,0.19020908)(0.19545421571428565,0.19581846)(0.31718510999999966,0.29576747)(0.5322114566666667,0.4854666)(0.5368859433333334,0.49209587)(0.5602243760000001,0.5017848)(0.5701674724126985,0.58286588)(0.5801105688253968,0.64660887)(0.5900536652380952,0.65782764)(0.5900536652380952,0.63029067)(0.5900536652380952,0.63233044)(0.5900536652380952,0.33299337)(0.5900536652380952,0.36206017)(0.5900536652380952,0.56348802)(0.5900536652380952,0.48393677)(0.5900536652380952,0.46965834)(0.5900536652380952,0.45843957)(0.5900536652380952,0.47118817)(0.5900536652380952,0.51555329)(0.5900536652380952,0.56297807)(0.6881693,0.65119837)(0.7135390099999999,0.66598674)(0.861295255,0.91330954)(0.903875573,0.90719021)(0.9275879659999999,0.93115757)(0.9275879659999999,0.91942886)      */    // Calculate mean squared error between predicted and real labels.    val meanSquaredError = predictionAndLabel.map { case (p, l) => math.pow((p - l), 2) }.mean()    println("Mean Squared Error = " + meanSquaredError)    //Mean Squared Error = 0.010049744711808193    // Save and load model    model.save(sc, "target/tmp/myIsotonicRegressionModel")    val sameModel = IsotonicRegressionModel.load(sc, "target/tmp/myIsotonicRegressionModel")  }}







参考文献

1、http://wenku.baidu.com/link?url=rbcbI3L7M83F62Aey_kyGZk7kwuJxr5ZW61EqFH5T45umsdZOCrAbfpl8a1yuMyzObd1_kG-kQ9DPcSTl7wnoX6UyNN_gT5bBYh_p1yMgD7url=rbcbI3L7M83F62Aey_kyGZk7kwuJxr5ZW61EqFH5T45umsdZOCrAbfpl8a1yuMyzObd1_kG-kQ9DPcSTl7wnoX6UyNN_gT5bBYh_p1yMgD7

原创粉丝点击