先看看一些常用的Transformation和Action操作吧,挺难记的,先别管,用到的时候再去查.
这是从spark官网上找的,我把它翻译了一下(保存可以查看高清图像):
本节的例子程序,需要一些RDD的基础知识:
关于RDD ,transformation和action的函数的区别可以参考:
http://blog.youkuaiyun.com/m0_37681914/article/details/71566518
1. 使用GroupByKey统计Key个数
object GroupByTest {
def main(args: Array[String]) {
val spark = SparkSession
.builder
.appName("GroupBy Test")
.getOrCreate()
//(看你怎么理解了,本人语文不太好,可能理解的和表达的不准确)
//并行处理任务个数
val numMappers = if (args.length > 0) args(0).toInt else 2
//要处理的数据量大小
val numKVPairs = if (args.length > 1) args(1).toInt else 1000
//定义byte数组大小
val valSize = if (args.length > 2) args(2).toInt else 1000
//并行处理任务个数
val numReducers = if (args.length > 3) args(3).toInt else numMappers
//生成数据,得到pairs1应该是(key为int,value为Array[valSize]),一共2000对键值对
val pairs1 = spark.sparkContext.parallelize(0 until numMappers, numMappers).flatMap { p =>
val ranGen = new Random
val arr1 = new Array[(Int, Array[Byte])](numKVPairs)
for (i <- 0 until numKVPairs) {
val byteArr = new Array[Byte](valSize)
ranGen.nextBytes(byteArr)
arr1(i) = (ranGen.nextInt(1000), byteArr)
}
arr1
}.cache()
println(pairs1.count())
//根据key分组,合并相同的key的项,合并的value为list
pairs1.groupByKey(numReducers).foreach(println(_))
println(pairs1.groupByKey(numReducers).count())
spark.stop()
}
}
本例子中,使用了
- flatMap() 和map类似, 只不过它会把集合或数组元素分解为普通元素
- cache() 将RDD数据缓存到内存中(在spark中,是惰性执行,两个action操作之间是没有直接联系的,第一个action计算需要的数据会随着第一个action消失,如果第二个action需要用到,那么就请缓存)
- count() 统计map中的个数,和java中map.size()功能一样
- groupByKey() 去除相同的key项,将value合并为list
2. cache的价值
object HdfsTest {
/** Usage: HdfsTest [file] */
def main(args: Array[String]) {
if (args.length < 1) {
System.err.println("Usage: HdfsTest <file>")
System.exit(1)
}
val spark = SparkSession
.builder
.appName("HdfsTest")
.getOrCreate()
val file = spark.read.text(args(0)).rdd
val mapped = file.map(s => s.length).cache()
for (iter <- 1 to 10) {
val start = System.currentTimeMillis()
for (x <- mapped) { x + 2 }
val end = System.currentTimeMillis()
println("Iteration " + iter + " took " + (end-start) + " ms")
}
spark.stop()
}
}
测试结果(本机测试文件)为:
第一次耗时在1500ms左右
后9次耗时平均耗时约为50ms
耗时比为 30 : 1
cache省去了重复读取,计算长度的操作.
3.最小二乘法的实现
我也没弄懂最小二乘法的公式 , 数学太差劲了.
能从百度百科上看懂的,也就是下面的解释了:
以最简单的一元线性模型来解释最小二乘法。什么是一元线性模型呢?监督学习中,如果预测的变量是离散的,我们称其为分类(如决策树,支持向量机等),如果预测的变量是连续的,我们称其为回归。回归分析中,如果只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。对于二维空间线性是一条直线;对于三维空间线性是一个平面,对于多维空间线性是一个超平面。
算法不懂,代码还是要学习的:
下面是本地计算的代码:
最好是对比两个代码结合看,能更好的有所收获
//.....其他代码省略
def main(args: Array[String]) {
/**
*.....
* 获取参数代码已省略
**/
// Initialize m and u randomly
var ms = Array.fill(M)(randomVector(F))
var us = Array.fill(U)(randomVector(F))
//每次迭代计算都需要用到上一次计算的结果
// Iteratively update movies then users
for (iter <- 1 to ITERATIONS) {
println(s"Iteration $iter:")
ms = (0 until M).map(i => updateMovie(i, ms(i), us, R)).toArray
us = (0 until U).map(j => updateUser(j, us(j), ms, R)).toArray
println("RMSE = " + rmse(R, ms, us))
println()
}
}
spark完整代码:
这个代码不用细看,主要看main方法中的spark代码
object SparkALS {
// Parameters set through command line arguments
var M = 0 // Number of movies
var U = 0 // Number of users
var F = 0 // Number of features
var ITERATIONS = 0
val LAMBDA = 0.01 // Regularization coefficient
def generateR(): RealMatrix = {
val mh = randomMatrix(M, F)
val uh = randomMatrix(U, F)
mh.multiply(uh.transpose())
}
def rmse(targetR: RealMatrix, ms: Array[RealVector], us: Array[RealVector]): Double = {
val r = new Array2DRowRealMatrix(M, U)
for (i <- 0 until M; j <- 0 until U) {
r.setEntry(i, j, ms(i).dotProduct(us(j)))
}
val diffs = r.subtract(targetR)
var sumSqs = 0.0
for (i <- 0 until M; j <- 0 until U) {
val diff = diffs.getEntry(i, j)
sumSqs += diff * diff
}
math.sqrt(sumSqs / (M.toDouble * U.toDouble))
}
def update(i: Int, m: RealVector, us: Array[RealVector], R: RealMatrix) : RealVector = {
val U = us.length
val F = us(0).getDimension
var XtX: RealMatrix = new Array2DRowRealMatrix(F, F)
var Xty: RealVector = new ArrayRealVector(F)
// For each user that rated the movie
for (j <- 0 until U) {
val u = us(j)
// Add u * u^t to XtX
XtX = XtX.add(u.outerProduct(u))
// Add u * rating to Xty
Xty = Xty.add(u.mapMultiply(R.getEntry(i, j)))
}
// Add regularization coefs to diagonal terms
for (d <- 0 until F) {
XtX.addToEntry(d, d, LAMBDA * U)
}
// Solve it with Cholesky
new CholeskyDecomposition(XtX).getSolver.solve(Xty)
}
def showWarning() {
System.err.println(
"""WARN: This is a naive implementation of ALS and is given as an example!
|Please use org.apache.spark.ml.recommendation.ALS
|for more conventional use.
""".stripMargin)
}
def main(args: Array[String]) {
//获取参数
var slices = 0
val options = (0 to 4).map(i => if (i < args.length) Some(args(i)) else None)
options.toArray match {
case Array(m, u, f, iters, slices_) =>
M = m.getOrElse("100").toInt
U = u.getOrElse("500").toInt
F = f.getOrElse("10").toInt
ITERATIONS = iters.getOrElse("5").toInt
slices = slices_.getOrElse("2").toInt
case _ =>
System.err.println("Usage: SparkALS [M] [U] [F] [iters] [slices]")
System.exit(1)
}
showWarning()
println(s"Running with M=$M, U=$U, F=$F, iters=$ITERATIONS")
val spark = SparkSession
.builder
.appName("SparkALS")
.getOrCreate()
val sc = spark.sparkContext
//获得矩阵
val R = generateR()
// Initialize m and u randomly
var ms = Array.fill(M)(randomVector(F))
var us = Array.fill(U)(randomVector(F))
//广播矩阵
// Iteratively update movies then users
val Rc = sc.broadcast(R)
var msb = sc.broadcast(ms)
var usb = sc.broadcast(us)
//重复计算iterations次
for (iter <- 1 to ITERATIONS) {
println(s"Iteration $iter:")
//下面的代码才是学习的重点:
//将0~M数组转为RDD数据集,让机器并行计算,将计算结果发送到driver机
ms = sc.parallelize(0 until M, slices)
.map(i => update(i, msb.value(i), usb.value, Rc.value))
.collect()
//driver机再重新广播ms,保证下一次迭代计算的是最新的结果
//这点是很容易遗忘的!!!!!
msb = sc.broadcast(ms) // Re-broadcast ms because it was updated
//同理
us = sc.parallelize(0 until U, slices)
.map(i => update(i, usb.value(i), msb.value, Rc.value.transpose()))
.collect()
usb = sc.broadcast(us) // Re-broadcast us because it was updated
//本地计算最终结果
println("RMSE = " + rmse(R, ms, us))
println()
}
spark.stop()
}
private def randomVector(n: Int): RealVector =
new ArrayRealVector(Array.fill(n)(math.random))
private def randomMatrix(rows: Int, cols: Int): RealMatrix =
new Array2DRowRealMatrix(Array.fill(rows, cols)(math.random))
}
很容易看出,spark将ms 和 us的计算移动到了work机上.
这个例子程序的目的就是学习这种编程的思维, 尽量将可以并行的计算移动到work机!要灵活掌握parallelize方法的使用.
spark此处的计算方式是:
从上图也能看出来,spark非常依赖网络传输;而对磁盘IO依赖很少,只要数据量能在内存的存储范围之内,对磁盘读写只有第一次的读,和最后的输出.
总结
- transformation和action常用的方法
- 缓存机制cache
- spark中的多任务迭代解决办法(例3),使用broadcast实现共享