spark的机器学习库(MLlib)下有简单的回归分析方法,今天只说最简单的线性回归,spark提供有两个回归分析库(mllib和ml),我学习的时候在网上也查了不少资料,有一个奇怪的现象是网上关于spark回归分析的资料基本全是mllib,关于ml的基本没见到,根据官方文档我自己对两个库的方法都做了测试,发现mllib做出的结果不是很正确
6,15,7,8,1,21,16,45,45,33,22
11,31,12,15,1,44,34,88,90,67,54
上面是我用来测试的一组数据,用mllib计算得到的系数a=-6.977555728270526E260,而有ml得到的系数为0.44543491975396066,不知道是不是我数据量少的原因,很明显mllib的结果是有问题的。此外,spark官网对于我们的学习给出这样的建议
This page documents sections of the MLlib guide for the RDD-based API (the spark.mllib package). Please see the MLlib Main Guide for the DataFrame-based API (the spark.ml package), which is now the primary API for MLlib.
也就是说ml库是spark目前主要支持的库,基于这两个原因,今天在这里主要说说ml库如何实现回归分析。
mllib回归的资料网上有很多,这里有一些不错的资料仅供参考:
spark官方文档
http://cos.name/2015/04/spark-beginner-1/
http://blog.selfup.cn/747.html
下面进入正题,说说ml库的回归分析,下面是完整代码
import org.apache.log4j.PropertyConfigurator
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.ml.regression.LinearRegression
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{DoubleType, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Row, SQLContext, types}
import org.apache.spark.mllib.linalg.{VectorUDT, Vectors}
import org.apache.spark.mllib.regression.LabeledPoint
import scala.io.Source
object Regression extends App{
val conf=new SparkConf().setAppName("regression")
val sc=new SparkContext(conf)
val sqc=new SQLContext(sc)
val date=Source.fromFile("data/data1.txt").getLines().map{line=>
val parts=line.split(",")
val a=Vectors.dense(parts(1).split(" ").map(_.toDouble))
val b=parts(0).toDouble
LabeledPoint(b,a)
//LabeledPoint(parts(0).toDouble,Vectors.dense(parts(1).split(" ").map(_.toDouble)))
}
//val df=sqc.createDataFrame(d,schema)
val df=sqc.createDataFrame(sc.parallelize(date.toSeq))
//val training=Source.fromFile("data/data.txt").getLines()
val lr=new LinearRegression()
.setMaxIter(10)//set maximum number of iterations
.setRegParam(0.3)//Set the regularization parameter.
.setElasticNetParam(0.8)//Set the ElasticNet mixing parameter.
// Fit the model
val lrModel = lr.fit(df)
// Print the coefficients and intercept for linear regression
println(s"Coefficients: ${lrModel.coefficients} Intercept: ${lrModel.intercept}")
// Summarize the model over the training set and print out some metrics
val trainingSummary = lrModel.summary
println(s"numIterations: ${trainingSummary.totalIterations}")
println(s"objectiveHistory: ${trainingSummary.objectiveHistory.toList}")
trainingSummary.residuals.show()
println(s"RMSE: ${trainingSummary.rootMeanSquaredError}")
println(s"r2: ${trainingSummary.r2}")
}
其实代码很少,整个过程都很简单,管方文档已经写的很清楚了,唯一的难点就是
val lrModel = lr.fit(df)
这是的df是DataFrame格式,官方文档是利用
val df=spark.read.format("libsvm")
.load("data/mllib/sample_linear_regression_data.txt")
从文件中直接读取数据,读取后df就是DataFrame格式,但是实际使用时,我们的数据可能是其它函数计算的结果,因此,如何把格式的数据(比如数组,其它任何结果转数组都是很容易的)转为DataFrame就是问题的难点了(对于我这样的初学都来说,对于熟悉scala的人来说可能都不是个事),翻启遍了scala的文档,终于找到一个函数sqc.createDataFrame(rdd: RDD[A])
,SQLContext有一个方法createDataFrame可以把RDD 转为DataFrame,那么接下来的问题就是如何把数组转为我们需要的RDD格式了,那么我们需要的RDD到底是什么格式呢,调试跟踪发现是LabeledPoint,它的定义如下:
case class LabeledPoint @Since("1.0.0") (
@Since("0.8.0") label: Double,
@Since("1.0.0") features: Vector) {
override def toString: String = {
s"($label,$features)"
}
}
其中label是因变量,它是一个Double数据,而features则是自变量,它是一个Vector,知道了格式构造起来就简单了,比如上面的代码,它是这样构造的:
val date=Source.fromFile("data/data1.txt").getLines().map{line=>
val parts=line.split(",")
val a=Vectors.dense(parts(1).split(" ").map(_.toDouble))
val b=parts(0).toDouble
LabeledPoint(b,a)
}
这里虽然还是从文件中读的数据,但和官方文档的本质区别在于它是把读得的数据分割成数组然后构造RDD,笔者这里要说的是如何把一个数据组造成DataFrame,如果我们要用到的数据都存在文件中当然不用这么麻烦,直接读就可以了,但是笔者遇到的问题是,我的数据是其它函数计算得到 ,它是放在一个数组中的,所以才有了这篇博客。如果大家有更好的方法,欢迎探讨