spark一千篇旅游日记0009 之 spark小例子程序03

本文深入探讨了Apache Spark中Transformation和Action的基本操作,并通过具体案例展示了如何利用这些操作进行高效的并行计算,包括统计键值对数量、利用缓存提高迭代效率及最小二乘法的应用。

先看看一些常用的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依赖很少,只要数据量能在内存的存储范围之内,对磁盘读写只有第一次的读,和最后的输出.


总结

  1. transformation和action常用的方法
  2. 缓存机制cache
  3. spark中的多任务迭代解决办法(例3),使用broadcast实现共享
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值