这个方法还是比较简单的,但load方法可以作为入口,了解Spark的MLLib的机理,再看算法的具体步骤就不会混乱了。load方法最重要的作用是,将读取的外部数据转换成MLLib算法可以使用的数据结构LabeledPoint,具体如下:
突然想起一件事,我在群上看到有人问自己写的算法执行的时候变成串行而不是并行了,很多原因都是因为map调用了写在map外面的函数,在map过程中,map外面的函数跟变量是保存在driver上的,worker此时不能访问driver,spark会识别到这种情况,从而把所有计算放在driver上以串行计算,解决方法也很简单,把map调用的方法写到map里面就可以了。
def loadLibSVMFile(
sc: SparkContext,
path: String,
numFeatures: Int,
minPartitions: Int): RDD[LabeledPoint] = {
val parsed = sc.textFile(path, minPartitions) //读取文件
.map(_.trim) //消除每行的两边空格
.filter(line => !(line.isEmpty || line.startsWith("#"))) //排除非空行和以#开头的行
.map { line =>
val items = line.split(' ') //空格分隔处理过的每行,返回一个array
val label = items.head.toDouble //构造常量label存储行头并转换成Double
val (indices, values) = items.tail.filter(_.nonEmpty).map { item =>
//取数组尾部,对每一行进行下面的处理
val indexAndValue = item.split(':') //以:号分隔数据每个元素,存入indexAndValue
val index = indexAndValue(0).toInt - 1 // 将向量下标修改为从0开始
val value = indexAndValue(1).toDouble //将向量转换成Double
(index, value)
}.unzip
(label, indices.toArray, values.toArray) //将获得的标签、向量下标、特征向量组成元祖 }
// 判断特征数是否已定义,如无,则利用上面得到的向量下标计算
val d = if (numFeatures > 0) {
numFeatures
} else {
parsed.persist(StorageLevel.MEMORY_ONLY)
parsed.map { case (label, indices, values) =>
indices.lastOption.getOrElse(0)
}.reduce(math.max) + 1
}
//最后,将元组转换成MLLib转用的LabeledPoint并返回
parsed.map { case (label, indices, values) =>
LabeledPoint(label, Vectors.sparse(d, indices, values))
}
}
可以看到,这个load方法并不是通用的,对应不同数据格式,我们需要编写对应的load方法,输出标准就是这个load方法的结尾了。然后对应不同的MLLib算法,load方法也是不一样的。。。开源的自由带来了不同一也是麻烦