我使用LibSVM格式的训练数据文件训练了我的模型,具体描述见http://spark.apache.org/docs/2.2.0/mllib-linear-methods.html。我使用的是其中的这部分代码
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")// 将数据分为训练集(60%)和测试集(40%)。val splits = data.randomSplit(Array(0.6, 0.4), seed = 11L)val training = splits(0).cache()val test = splits(1)// 运行训练算法构建模型val model = new LogisticRegressionWithLBFGS() .setNumClasses(10) .run(training)
但我的问题是,在预测时,如何为这种方式训练的模型准备特征?我想到的解决方案是保存一个中间文件,采用libsvm格式(使用虚拟标签,因为我只想进行预测),然后使用MLUtils.loadLibSVMFile
加载它,再将结果应用于训练好的模型进行预测。然而,这种策略非常低效,在我的系统中,这需要一个非常昂贵的collect()
调用。有什么方法可以将我的数据转换为正确的LabeledPoint
格式,而无需先将其转换为LibSVM格式(这需要一个中间文件)吗?我希望不必深入研究MLUtils.loadLibSVMFile
的内部结构来弄清楚libsvm格式的一行是如何转换为LabeledPoint
对象的。
附注:看起来Spark的ML管道是更好的方法,但我不想放弃我已经完成的所有工作,如果可以的话,我不想尝试这种策略。
回答:
当你已经完成模型训练并且只想使用它进行预测时,你不需要将数据转换为LabeledPoint
格式。你只需要一个向量(可以是密集的或稀疏的,详见这里)来进行预测。
val prediction = model.predict(features)
当然,也可以转换为LabeledPoint
,尽管这不是必需的。一个小例子:
val rdd = sc.parallelize(Array( (1, List(1.0,4.0,8.0)), (2, List(3.0,3.0,8.0)), (3, List(5.0,5.0,9.0))))val rdd2 = rdd.map{ case(k, vs) => LabeledPoint(k.toDouble, Vectors.dense(vs.toArray))}
在LibSVM格式和Spark向量之间转换是可能的。在你的LibSVM文件中,每行具有以下格式:
<label> <index1>:<value1> <index2>:<value2> ... <indexN>:<valueN>
这些索引是特征向量中的索引(训练和预测时顺序相同)。MLUtils.loadLibSVMFile()
将按照此格式创建LabeledPoint
,即每个LabeledPoint
看起来像这样:
LabeledPoint(label, Vectors.sparse(N, Array(index1-1, index2-1, ...), Array(value1, value2, ...)))
由于LibSVM文件指定了索引和值,因此示例中使用了SparseVector
。
在LibSVM中,索引从1开始,而更常见的惯例(包括创建SparseVector
)是从0开始,因此,在从LibSVM格式转换时,需要从索引中减去1。
按照这个方法,你可以轻松地自己创建向量来进行预测。