我有一组基于我想要创建分类模型的数据。每行都有以下形式:
user1,class1,product1
user1,class1,product2
user1,class1,product5
user2,class1,product2
user2,class1,product5
user3,class2,product1
大约有1M个用户,2个类和1M个产品。我接下来要做的是创建稀疏向量(MLlib已经支持的东西)但为了应用该函数,我必须首先创建密集向量(使用0)。换句话说,我必须将数据二进制化。什么是最简单(或最优雅)的方式?
鉴于我是MLlib的新手,请问您提供一个具体的例子?我正在使用MLlib 1.2。
修改
我最终得到了以下一段代码,但结果却很慢......还有其他想法,我只能使用MLlib 1.2吗?
val data = test11.map(x=> ((x(0) , x(1)) , x(2))).groupByKey().map(x=> (x._1 , x._2.toArray)).map{x=>
var lt : Array[Double] = new Array[Double](test12.size)
val id = x._1._1
val cl = x._1._2
val dt = x._2
var i = -1
test12.foreach{y => i += 1; lt(i) = if(dt contains y) 1.0 else 0.0}
val vs = Vectors.dense(lt)
(id , cl , vs)
}
答案 0 :(得分:9)
您可以使用spark.ml' s OneHotEncoder。
您首先使用:
OneHotEncoder.categories(rdd, categoricalFields)
categoricalField
是RDD
包含分类数据的索引序列。 categories
,给定数据集和作为分类变量的列索引,返回一个结构,对于每个字段,该结构描述数据集中存在的值。该映射旨在用作编码方法的输入:
OneHotEncoder.encode(rdd, categories)
返回矢量化RDD[Array[T]]
。
答案 1 :(得分:4)
如果使用内置OneHotEncoder
不是一个选项,并且你只有一个变量来实现穷人的单热,那么或多或少是直截了当的。首先,我们创建一个示例数据:
import org.apache.spark.mllib.linalg.{Vector, Vectors}
val rdd = sc.parallelize(List(
Array("user1", "class1", "product1"),
Array("user1", "class1", "product2"),
Array("user1", "class1", "product5"),
Array("user2", "class1", "product2"),
Array("user2", "class1", "product5"),
Array("user3", "class2", "product1")))
接下来,我们必须创建从值到索引的映射:
val prodMap = sc.broadcast(rdd.map(_(2)).distinct.zipWithIndex.collectAsMap)
和一个简单的编码功能:
def encodeProducts(products: Iterable[String]): Vector = {
Vectors.sparse(
prodMap.value.size,
products.map(product => (prodMap.value(product).toInt, 1.0)).toSeq
)
}
最后,我们可以将它应用于数据集:
rdd.map(x => ((x(0), x(1)), x(2))).groupByKey.mapValues(encodeProducts)
向上扩展以处理多个变量相对容易。
修改强>:
如果产品数量很大以使广播有用,则应该可以使用join
。首先,我们可以创建从产品到索引的类似映射,但将其保留为RDD:
import org.apache.spark.HashPartitioner
val nPartitions = ???
val prodMapRDD = rdd
.map(_(2))
.distinct
.zipWithIndex
.partitionBy(new HashPartitioner(nPartitions))
.cache
val nProducts = prodMapRDD.count // Should be < Int.MaxValue
接下来,我们重新设置输入RDD
,以便按产品索引PairRDD
:
val pairs = rdd
.map(rec => (rec(2), (rec(0), rec(1))))
.partitionBy(new HashPartitioner(nPartitions))
最后我们可以join
两个
def indicesToVec(n: Int)(indices: Iterable[Long]): Vector = {
Vectors.sparse(n, indices.map(x => (x.toInt, 1.0)).toSeq)
}
pairs.join(prodMapRDD)
.values
.groupByKey
.mapValues(indicesToVec(nProducts.toInt))
答案 2 :(得分:-1)
原始问题要求从非分类中指定分类要素的最简单方法。
在Spark ML中,您可以使用VectorIndexer的setMaxCategories方法,您无需指定字段 - 相反,它将理解为基数低于或等于给定数字的字段(在这种情况下) ,2)。
val indexer = new VectorIndexer()
.setInputCol("features")
.setOutputCol("indexed")
.setMaxCategories(10)
有关详细信息,请参阅this reply。