• Spark MLlib 之 Vector向量深入浅出


    Spark MLlib里面提供了几种基本的数据类型,虽然大部分在调包的时候用不到,但是在自己写算法的时候,还是很需要了解的。MLlib支持单机版本的local vectors向量和martix矩阵,也支持集群版本的matrix矩阵。他们背后使用的都是ScalaNLP中的Breeze。

    更多内容参考我的大数据学习之路

    Local Vector

    local vector是一种索引是0开始的整数、内容为double类型,存储在单机上的向量。MLlib支持两种矩阵,dense密集型和sparse稀疏型。一个dense类型的向量背后其实就是一个数组,而sparse向量背后则是两个并行数组——索引数组和值数组。比如向量(1.0, 0.0, 3.0)既可以用密集型向量表示为[1.0, 0.0, 3.0],也可以用稀疏型向量表示为(3, [0,2],[1.0,3.0]),其中3是数组的大小。

    接口为Vector,看源码可以看到它是用sealed修饰的,在scala里面这种关键字修饰的trait在进行match的时候必须把所有的情况都列出来,不然会报错。相当于强制你考虑向量的时候,必须考虑它是dense型的,还是sparse型的。

    sealed trait Vector extends Serializable {
      def size: Int // 向量的大小
      def toArray: Array[Double] //转换成普通的数组
    
      override def equals(other: Any): Boolean = { // 定义比较方法——感慨,原来这么优秀的框架背后也用穷举
        other match {
          case v2: Vector =>
            if (this.size != v2.size) return false
            (this, v2) match {
              case (s1: SparseVector, s2: SparseVector) =>
                Vectors.equals(s1.indices, s1.values, s2.indices, s2.values)
              case (s1: SparseVector, d1: DenseVector) =>
                Vectors.equals(s1.indices, s1.values, 0 until d1.size, d1.values)
              case (d1: DenseVector, s1: SparseVector) =>
                Vectors.equals(0 until d1.size, d1.values, s1.indices, s1.values)
              case (_, _) => util.Arrays.equals(this.toArray, v2.toArray)
            }
          case _ => false
        }
      }
    
      override def hashCode(): Int = { //好好领略hashcode的魅力
        var result: Int = 31 + size
        var nnz = 0
        this.foreachActive { (index, value) =>
          if (nnz < Vectors.MAX_HASH_NNZ) {
            if (value != 0) {
              result = 31 * result + index
              val bits = java.lang.Double.doubleToLongBits(value)
              result = 31 * result + (bits ^ (bits >>> 32)).toInt
              nnz += 1
            }
          } else {
            return result
          }
        }
        result
      }
    
      // 这里面的BV其实是breeze里面的vector,import breeze.linalg.{DenseVector => BDV, SparseVector => BSV, Vector => BV}
      // 也就是说,mllib里面的vector其实就是对breeze里面的vector封装了一层而已
      private[spark] def asBreeze: BV[Double]
      def apply(i: Int): Double = asBreeze(i)
      def copy: Vector = {
        throw new NotImplementedError(s"copy is not implemented for ${this.getClass}.")
      }
      def foreachActive(f: (Int, Double) => Unit): Unit
      def numActives: Int
      def numNonzeros: Int //零的个数
      def toSparse: SparseVector
      def toDense: DenseVector = new DenseVector(this.toArray) //创建Dense向量还真是简单啊
    
      def compressed: Vector = {
        val nnz = numNonzeros
        // A dense vector needs 8 * size + 8 bytes, while a sparse vector needs 12 * nnz + 20 bytes.
        if (1.5 * (nnz + 1.0) < size) {
          toSparse
        } else {
          toDense
        }
      }
    
      def argmax: Int //返回里面的最大值
    }
    

    Vector有两种实现方式——DenseVector,和SparseVector。

    import org.apache.spark.ml.linalg.{Vector,Vectors}
    import org.apache.spark.sql.SparkSession
    
    object DataTypes {
      def main(args: Array[String]): Unit = {
        val spark = SparkSession.builder().master("local[*]").appName("tf-idf").getOrCreate()
        spark.sparkContext.setLogLevel("WARN")
    
        // 创建dense vector
        val dv: Vector = Vectors.dense(1.0, 0.0, 3.0)
        // 创建sparse vector
        val sv1: Vector = Vectors.sparse(3, Array(0,2), Array(1.0,3.0))
        val sv2: Vector = Vectors.sparse(3, Seq((0, 1.0), (2,3.0)))
      }
    }
    

    其中sparse vector有两种创建方式,第一种是传入三个参数:向量大小、索引数组、索引数组对应的值数组;第二种方式是传入两个参数:向量大小、由索引和值组成的键值对数组。

    另外这个Vectors不仅仅有创建dense和sparse的方法,还有几个有用的功能,比如norm范数和sqdist距离。

    val norm1Vec = Vectors.dense(1.0,-1.0,2.0)
    // 第一范数,就是绝对值相加
    println(Vectors.norm(norm1Vec,1)) // 4.0
    // 第二番薯,就是平方和开根号
    println(Vectors.norm(norm1Vec,2)) // 2.449489742783178
    // 无限范数
    println(Vectors.norm(norm1Vec,1000)) //2.0
    
    val sq1 = Vectors.dense(1.0, 2.0, 3.0)
    val sq2 = Vectors.dense(2.0, 4.0, 6.0)
    println(Vectors.sqdist(sq1, sq2)) // (2-1)^2 + (4-2)^2 + (6-3)^2 = 14
    

    通过上面简单的一个Vector,还是能学到不少东西的。

    比如sealed关键字的使用,以及工厂方法:

    object xxxFactory{
        def x1: XXX
        def x2: XXX
        ...
    }
    trait XXX {}
    object X1 extends XXX {}
    object X2 extends XXX {}
    

    Labeled Point 有标签的向量

    这种labeled point其实内部也是一个vector,可能是dense也可能是sparse,不过多了一个标签列。在ML里面,labeled point通常用于有监督算法。这个label是double类型的,这样既可以用于回归算法,也可以用于分类。在二分类中,Label不是0就是1;在多分类中label可能从0开始,1,2,3,4....

    使用的时候很简单,直接new就可以了:

    // Create a labeled point with a positive label and a dense feature vector.
    val pos = LabeledPoint(1.0, Vectors.dense(1.0, 0.0, 3.0))
    
    // Create a labeled point with a negative label and a sparse feature vector.
    val neg = LabeledPoint(0.0, Vectors.sparse(3, Array(0, 2), Array(1.0, 3.0)))
    

    一般在准备训练集数据的时候,数据都是稀疏型的。MMLib支持在SVM和Liner线性回归中直接读取训练数据,但是需要满足下面的格式:

    label index1:value1 index2:value2 ...
    

    比如:

    val examples: RDD[LabeledPoint] = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
    
  • 相关阅读:
    memcached简单介绍及在django中的使用
    【Python】解决使用pyinstaller打包Tkinker程序报错问题
    【python】获取列表中最长连续数字
    【zabbix】zabbix忘记密码,重置密码
    【jenkins】jenkins实时显示python脚本输出
    【AWS】AWS云计算赋能数字化转型专题研讨会圆满落幕
    【深度学习】使用opencv在视频上添加文字和标记框
    【AWS】AWS云计算赋能数字化转型专题研讨会
    【AWS】订阅AWS论坛的RSS消息获取最新公告
    【saltstack】saltstack执行结果和事件存储到mysql
  • 原文地址:https://www.cnblogs.com/xing901022/p/9277343.html
Copyright © 2020-2023  润新知