【Spark ml源码系列】Spark ML 和 Spark MLlib 中vector转换用法用途示例中文源码详解

Spark ML 和 Spark MLlib 中vector用途用法转换示例点击这里看全文

Vector用途

在 Spark ML 和MLlib中,Vector(向量)是一个重要的数据类型,用于表示特征向量或模型预测结果。Vector 在机器学习中有广泛的应用,以下是在 Spark ML 中使用 Vector 的一些常见用途:

  1. 特征表示:在机器学习任务中,特征工程是一个关键步骤。Vector 用于表示样本的特征向量,其中每个维度对应一个特征。通过将特征值组织成 Vector,可以方便地将特征向量传递给模型进行训练和预测。

  2. 模型输入:在使用 Spark 构建机器学习模型时,许多算法和组件要求输入为 Vector 类型的特征向量。模型的训练和预测通常涉及到处理和操作 Vector 对象。

  3. 特征转换:Spark 提供了许多特征转换的方法,例如标准化、归一化、特征选择等。这些方法通常以 Vector 作为输入,并返回一个新的 Vector 作为输出,用于进行特征转换和处理。

  4. 模型预测结果:在使用训练好的模型进行预测时,模型通常会返回一个包含预测结果的 Vector。这个 Vector 可以提供关于样本的预测概率、类别分布等信息。

  5. 特征组合:有时候,为了构建更丰富和复杂的特征表示,需要对多个特征进行组合。Vector 可以方便地将多个特征组合成一个向量,以便于进行模型训练和预测。

总而言之,Spark 中的 Vector 主要用于特征表示、模型输入、特征转换和模型预测结果等机器学习任务中。它提供了一种灵活和方便的方式来处理和操作特征向量,使得在 Spark ML 中进行机器学习任务更加高效和便捷。

Vector互换

在 Spark MLlib 和 Spark ML 之间进行向量对象的相互转换时,应使用以下方法:

1.Spark MLlib 转 Spark ML

org.apache.spark.mllib.linalg.Vector 转换为 org.apache.spark.ml.linalg.Vector:使用 Vectors.asML 方法将 MLlib 的向量转换为 Spark ML 的向量。

适用场景

(1)与 Spark ML 的流水线 Pipeline 兼容:如果您正在使用 Spark ML 的流水线(Pipeline)构建机器学习模型,并且希望在流水线中使用 MLlib 的向量作为输入数据或特征向量,您可以将 MLlib 的向量转换为 Spark ML 的向量类型,以便与 Spark ML 的流水线兼容。

(2)使用 Spark ML 的算法和组件:Spark ML 提供了一套现成的算法和组件,可以在 DataFrame 和 Dataset 上直接操作。这些算法和组件通常使用 Spark ML 的向量类型作为输入。如果您已经使用了 MLlib 的向量作为输入数据或特征向量,您可能需要将其转换为 Spark ML 的向量类型,以便能够使用 Spark ML 的算法和组件。

2.Spark ML 转 Spark MLlib:

org.apache.spark.ml.linalg.Vector 转换为 org.apache.spark.mllib.linalg.Vector:使用 Vectors.fromML 方法将 Spark ML 的向量转换为 MLlib 的向量。

适用场景

(1)使用 Spark MLlib 的算法和组件:如果您正在使用 Spark MLlib 提供的算法和组件构建机器学习模型,并且希望在模型中使用 Spark ML 的向量作为输入数据或特征向量,您可以将 Spark ML 的向量转换为 Spark MLlib 的向量类型,以便与 Spark MLlib 的算法和组件兼容。

(2)与基于 RDD 的旧版代码兼容:在某些情况下,您可能仍然使用基于 RDD 的旧版 Spark MLlib 代码。而 Spark ML 是基于 DataFrame 和 Dataset 构建的新一代库。如果您已经使用了 Spark ML 的向量作为输入数据或特征向量,并且需要将其转换为 Spark MLlib 的向量类型,以便与基于 RDD 的旧版代码兼容。

vector代码示例

import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.linalg.{
   Vector => MLVector}
import org.apache.spark.mllib.linalg.{
   Vector => MllibVector, Vectors => MllibVectors}

object VectorConversionExample {
   
  def main(args: Array[String]): Unit = {
   
    // 创建 SparkSession
    val spark = SparkSession.builder()
      .appName("VectorConversionExample")
      .master("local")
      .getOrCreate()

    // 创建 Spark MLlib 的向量
    val mllibVector = MllibVectors.dense(1.0, 2.0, 3.0)

    // 将 Spark MLlib 的向量转换为 Spark ML 的向量
    val mlVector: MLVector = mllibVector.asML

    // 将 Spark ML 的向量转换为 Spark MLlib 的向量
    val mllibVector2: MllibVector = MllibVectors.fromML(mlVector)

    // 打印转换后的结果
    println(mllibVector)
    println(mlVector)
    println(mllibVector2)
//  [1.0,2.0,3.0]
//  [1.0,2.0,3.0]
//  [1.0,2.0,3.0]
  }
}

Vector中文源码

Spark MLlib

trait Vector(asML)

package org.apache.spark.mllib.linalg

import java.lang.{
   Double => JavaDouble, Integer => JavaInteger, Iterable => JavaIterable}
import java.util

import scala.annotation.varargs
import scala.collection.JavaConverters._
import scala.language.implicitConversions

import breeze.linalg.{
   DenseVector => BDV, SparseVector => BSV, Vector => BV}
import org.json4s.DefaultFormats
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods.{
   compact, parse => parseJson, render}

import org.apache.spark.SparkException
import org.apache.spark.annotation.{
   AlphaComponent, Since}
import org.apache.spark.ml.{
   linalg => newlinalg}
import org.apache.spark.mllib.util.NumericParser
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions.{
   GenericInternalRow, UnsafeArrayData}
import org.apache.spark.sql.types._
/**
 * 表示一个数字向量,其索引类型为Int,值类型为Double。
 *
 * @note 用户不应该实现此接口。
 */
@SQLUserDefinedType(udt = classOf[VectorUDT])
@Since("1.0.0")
sealed trait Vector extends Serializable {
   

  /**
   * 向量的大小。
   */
  @Since("1.0.0")
  def size: Int

  /**
   * 将实例转换为double数组。
   */
  @Since("1.0.0")
  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
    }
  }

  /**
   * 返回向量的哈希码值。哈希码基于向量的大小和前128个非零元素,
   * 使用类似于 `java.util.Arrays.hashCode` 的哈希算法。
   */
  override def hashCode(): Int = {
   
    // 这是一个参考实现。它在foreachActive中调用return,速度较慢。
    // 子类应该使用优化的实现覆盖它。
    var result: Int = 31 + size
    var nnz = 0
    this.foreachActive {
    (index, value) =>
      if (nnz < Vectors.MAX_HASH_NNZ) {
   
        // 忽略稀疏和稠密之间的比较的显式0
        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
  }

  /**
   * 将实例转换为breeze向量。
   */
  private[spark] def asBreeze: BV[Double]

  /**
   * 获取第i个元素的值。
   * @param i 索引
   */
  @Since("1.1.0")
  def apply(i: Int): Double = asBreeze(i)

  /**
   * 对所有稠密和稀疏向量的活动元素应用函数`f`。
   *
   * @param f 函数接受两个参数,第一个参数是带有类型`Int`的向量的索引,
   *          第二个参数是具有类型`Double`的相应值。
   */
  @Since("1.6.0")
  def foreachActive(f: (Int, Double) => Unit): Unit

  /**
   * 活动条目的数量。"活动条目"是明确存储的元素,无论其值如何。
   *
   * @note 非活动条目的值为0。
   */
  @Since("1.4.0")
  def numActives: Int

  /**
   * 非零元素的数量。这扫描所有活动值并计算非零值。
   */
  @Since("1.4.0")
  def numNonzeros: Int

  /**
   * 将此向量转换为删除所有显式零的稀疏向量。
   */
  @Since("1.4.0")
  def toSparse: SparseVector = toSparseWithSize(numNonzeros)

  /**
   * 在已知大小的情况下,将此向量转换为删除所有显式零的稀疏向量。
   * 当已经知道非零元素的数量时,使用此方法可以避免重新计算非零元素的数量。例如:
   * {
   {
   {
   *   val nnz = numNonzeros
   *   val sv = toSparse(nnz)
   * }}}
   *
   * 如果`nnz`未指定,则抛出[[java.lang.ArrayIndexOutOfBoundsException]]。
   */
  private[linalg] def toSparseWithSize(nnz: Int): SparseVector

  /**
   * 将此向量转换为稠密向量。
   */
  @Since("1.4.0")
  def toDense: DenseVector = new DenseVector(this.toArray)

  /**
   * 返回一个稠密或稀疏格式的向量,其中使用存储空间较少的格式。
   */
  @Since("1.4.0")
  def compressed: Vector = {
   
    val nnz = numNonzeros
    // 稠密向量需要8 * size + 8字节,而稀疏向量需要12 * nnz + 20字节。
    if (1.5 * (nnz + 1.0) < size) {
   
      toSparseWithSize(nnz)
    } else {
   
      toDense
    }
  }

  /**
   * 找到最大元素的索引。在出现绑定时返回第一个最大元素。
   * 如果向量长度为0,则返回-1。
   */
  @Since("1.5.0")
  def argmax: Int

  /**
   * 将向量转换为JSON字符串。
   */
  @Since("1.6.0")
  def toJson: String

  /**
   * 将此向量转换为新的mllib-local表示。
   * 这不会复制数据;它只复制引用。
   */
  @Since("2.0.0")
  def asML: newlinalg.Vector
}

class VectorUDT


/**
 * :: AlphaComponent ::
 *
 * [[org.apache.spark.sql.Dataset]]通过[[VectorUDT]]与SQL轻松交互的用户定义类型。
 */
@AlphaComponent
class VectorUDT extends UserDefinedType[Vector] {
   

  override def sqlType: StructType = {
   
    // type: 0 = sparse, 1 = dense
    // 我们仅对密集向量使用“values”,对于稀疏向量,使用“size”,“indices”和“values”。
    // “values”字段可为空,因为我们以后可能希望添加二进制向量,该向量使用“size”和“indices”,但不使用“values”。
    StructType(Seq(
      StructField("type", ByteType, nullable = false),
      StructField("size", IntegerType, nullable = true),
      StructField("indices", ArrayType(IntegerType, containsNull = false), nullable = true),
      StructField("values", ArrayType(DoubleType, containsNull = false), nullable = true)))
  }

  override def serialize(obj: Vector): InternalRow = {
   
    obj match {
   
      case SparseVector(size, indices, values) =>
        val row = new GenericInternalRow(4)
        row.setByte(0, 0)
        row.setInt(1, size)
        row.update(2, UnsafeArrayData.fromPrimitiveArray(indices))
        row.update(3, UnsafeArrayData.fromPrimitiveArray(values))
        row
      case DenseVector(values) =>
        val row = new GenericInternalRow(4)
        row.setByte(0, 1)
        row.setNullAt(1)
        row.setNullAt(2)
        row.update(3, UnsafeArrayData.fromPrimitiveArray(values))
        row
    }
  }

  override def deserialize(datum: Any): Vector = {
   
    datum match {
   
      case row: InternalRow =>
        require(row.numFields == 4,
          s"VectorUDT.deserialize given row with length ${
     row.numFields} but requires length == 4")
        val tpe = row.getByte(0)
        tpe match {
   
          case 0 =>
            val size = row.getInt(1)
            val indices = row.getArray(2).toIntArray()
            val values = row.getArray(3).toDoubleArray()
            new SparseVector(size, indices, values)
          case 1 =>
            val values = row.getArray(3).toDoubleArray()
            new DenseVector(values)
        }
    }
  }

  override def pyUDT: String = "pyspark.mllib.linalg.VectorUDT"

  override def userClass: Class[Vector] = classOf[Vector]

  override def equals(o: Any): Boolean = {
   
    o match {
   
      case v: VectorUDT => true
      case _ => false
    }
  }

  // 请参阅[SPARK-8647],它可以在不使用常数no.的情况下获得所需的恒定哈希码。
  override def hashCode(): Int = classOf[VectorUDT].getName.hashCode()

  override def typeName: String = "vector"

  private[spark] override def asNullable: VectorUDT = this
}

object Vectors(fromML)


/**
 * [[org.apache.spark.mllib.linalg.Vector]]的工厂方法。
 * 我们不使用名称“Vector”,因为Scala默认导入了“scala.collection.immutable.Vector”。
 */
@Since("1.0.0")
object Vectors {
   

  /**
   * 从值创建一个密集向量。
   */
  @Since("1.0.0")
  @varargs
  def dense(firstValue: Double, otherValues: Double*): Vector =
    new DenseVector((firstValue +: otherValues).toArray)

  // 使用虚拟隐式避免与由@varargs生成的签名冲突。
  /**
   * 从double数组创建一个密集向量。
   */
  @Since("1.0.0")
  def dense(values: Array[Double]): Vector = new DenseVector(values)

  /**
   * 使用索引数组和值数组创建一个稀疏向量。
   *
   * @param size 向量大小。
   * @param indices 索引数组,必须严格递增。
   * @param values 值数组,必须与indices具有相同的长度。
   */
  @Since("1.0.0")
  def sparse(size: Int, indices: Array[Int], values: Array[Double]): Vector =
    new SparseVector(size, indices, values)

  /**
   * 使用无序(索引,值)对创建一个稀疏向量。
   *
   * @param size 向量大小。
   * @param elements 向量元素,为(索引,值)对。
   */
  @Since("1.0.0")
  def sparse(size: Int, elements: Seq[(Int, Double)]): Vector = {
   
    val (indices, values) = elements.sortBy(_._1).unzip
    var prev = -1
    indices.foreach {
    i =>
      require(prev < i, s"Found duplicate indices: $i.")
      prev = i
    }
    require(prev < size, s"You may not write an element to index $prev because the declared " +
      s"size of your vector is $size")

    new SparseVector(size, indices.toArray, values.toArray)
  }

  /**
   * 使用无序(索引,值)对以Java友好的方式创建一个稀疏向量。
   *
   * @param size 向量大小。
   * @param elements 向量元素,为(索引,值)对。
   */
  @Since("1.0.0")
  def sparse(size: Int, elements: JavaIterable[(JavaInteger, JavaDouble)]): Vector = {
   
    sparse(size, elements.asScala.map {
    case (i, x) =>
      (i.intValue(), x.doubleValue())
    }.toSeq)
  }

  /**
   * 创建一个全零向量。
   *
   * @param size 向量大小
   * @return 零向量
   */
  @Since("1.1.0")
  def zeros(size: Int): Vector = {
   
    new DenseVector(new Array[Double](size))
  }

  /**
   * 将`Vector.toString`的结果字符串解析为[[Vector]]。
   */
  @Since("1.1.0")
  def parse(s: String): Vector = {
   
    parseNumeric(NumericParser.parse(s))
  }

  /**
   * 将向量的JSON表示解析为[[Vector]]。
   */
  @Since("1.6.0")
  def fromJson(json: String): Vector = {
   
    implicit val formats = DefaultFormats
    val jValue = parseJson(json)
    (jValue \ "type").extract[Int] match {
   
      case 0 => // 稀疏
        val size = (jValue \ "size").extract[Int]
        val indices = (jValue \ "indices").extract[Seq[Int]].toArray
        val values = (jValue \ "values").extract[Seq[Double]].toArray
        sparse(size, indices, values)
      case 1 => // 密集
        val values = (jValue \ "values").extract[Seq[Double]].toArray
        dense(values)
      case _ =>
        throw new IllegalArgumentException(s"Cannot parse $json into a vector.")
    }
  }

  private[mlli
  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值