Spark ML 和 Spark MLlib 中vector用途用法转换示例点击这里看全文
文章目录
Vector用途
在 Spark ML 和MLlib中,Vector(向量)是一个重要的数据类型,用于表示特征向量或模型预测结果。Vector 在机器学习中有广泛的应用,以下是在 Spark ML 中使用 Vector 的一些常见用途:
-
特征表示:在机器学习任务中,特征工程是一个关键步骤。Vector 用于表示样本的特征向量,其中每个维度对应一个特征。通过将特征值组织成 Vector,可以方便地将特征向量传递给模型进行训练和预测。
-
模型输入:在使用 Spark 构建机器学习模型时,许多算法和组件要求输入为 Vector 类型的特征向量。模型的训练和预测通常涉及到处理和操作 Vector 对象。
-
特征转换:Spark 提供了许多特征转换的方法,例如标准化、归一化、特征选择等。这些方法通常以 Vector 作为输入,并返回一个新的 Vector 作为输出,用于进行特征转换和处理。
-
模型预测结果:在使用训练好的模型进行预测时,模型通常会返回一个包含预测结果的 Vector。这个 Vector 可以提供关于样本的预测概率、类别分布等信息。
-
特征组合:有时候,为了构建更丰富和复杂的特征表示,需要对多个特征进行组合。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