ALS(最小交替二乘法)概述
协同过滤算法,又称为“集体计算方法”,是一种基于群体用户或者物品的典型推荐算法,也是目前常用的推荐算法中最经典的算法。
在SparkML中,只有一种协同过滤模型——ALS,它替代了传统的基于物品(ItemCF)和基于用户(UserCF)的协同过滤算法。
ALS的意思是交替最小二乘法(Alternating Least Squares),它只是是一种优化算法的名字,被用在求解spark中所提供的推荐系统模型的最优解。spark中协同过滤的文档中一开始就说了,这是一个基于模型的协同过滤(model-based CF),其实它是一种近几年推荐系统界大火的隐语义模型中的一种。隐语义模型又叫潜在因素模型,它试图通过数量相对少的未被观察到的底层原因,来解释大量用户和产品之间可观察到的交互。
KMeans概述
Kmeans算法是数据挖掘中一种常用的聚类方法,其基本思想和核心内容就是在算法开始时随机给定若干(K)个中心,按照最近距离原则将样本点分配到各个中心点,之后按平均法计算聚类集的中心点位置,从而重新确定新的中心点位置。这样不断地迭代下去直至聚类集内的样本满足阈值为止。
ALS+KMeans 实现基于用户隐语义特征向量的聚类
ALS算法的核心,其实是将一个用户评分矩阵 W® [r为这个矩阵的rank] 近似分解为两个rank小于用户评分矩阵W的矩阵 U(n)和V(m),且n<=r,m<=r。最后对U VT做笛卡尔积,取推荐的前N项。
KMeans算法,则是根据随机的中心点,按照用户特征向量聚类。
在SparkML中,使用ALS模型,可以得到基于矩阵W所分解而成的用户隐语义特征向量userFactor和物品隐语义特征向量Itemfactor,我们可以使用KMeans模型,对两个特征向量进行聚类。
基于隐语义用户向量的用户推荐
import spark.implicits._
//todo 使用als模型获取基于评分的 用户\物品 特征向量
//获取数据
val rating = spark.table("dw.dw_user_rating")
.select($"gid", $"game_id", $"rating")
//利用StringIndexer获取映射模型
val index_1 = new StringIndexer().setInputCol("gid").setOutputCol("gid_indexed").setHandleInvalid("keep")
val index_2 = new StringIndexer().setInputCol("game_id").setOutputCol("game_id_indexed").setHandleInvalid("keep")
// 获取管道模型
val stringIndexers = new Pipeline().setStages(Array(index_1, index_2)).fit(rating)
val indexDF = stringIndexers.transform(rating)
//数据处理
val transData = indexDF.select($"gid_indexed".cast("int"), $"game_id_indexed".cast("int"), $"rating".cast("float"))
.toDF("gid_indexed", "game_id_indexed", "rating")
//ALS模型训练
val alsObj = new ALS().setRank(10).setMaxIter(10).setRegParam(0.01).setUserCol("gid_indexed").setItemCol("game_id_indexed").setRatingCol("rating")
val alsModel = alsObj.fit(transData)
//todo
//用户特征向量数据处理
val userFactors = alsModel.userFactors
//获取到的用户特征向量为array[Float]类型,需要转换成DenseVector[Double]类
val inputDF = userFactors.rdd.map(row => {
(row.getAs[Int]("id"), Vectors.dense(row.getAs[Seq[Float]]("features").toArray.map(_.toDouble)))
}).toDF("id", "features")
//Kmeans模型训练
val kmsObj = new KMeans().setFeaturesCol("features").setK(1000).setMaxIter(10)
val kmsModel = kmsObj.fit(inputDF)
//模型中心点集合
val clusterCenters = kmsModel.clusterCenters
//计算每个用户特征向量到中心点的欧式距离,以离中心点最近的数据作为推荐模板
val df = kmsModel.transform(inputDF).rdd.map(row => {
val feature = row.getAs[DenseVector]("features").toArray
var minDis: Double = Double.MaxValue
var center = Vectors.zeros(0)
clusterCenters.foreach(v => {
val dis = v.toArray.zip(feature).map(t => (t._1 - t._2) * (t._1 - t._2)).reduce(_ + _)
if (minDis >= dis) {
center = v
minDis = dis
}
})
(row.getAs[Int]("id"), row.getAs[DenseVector]("features").toArray.map(_.toFloat), row.getAs[Int]("prediction"), minDis, center)
}).toDF("id", "features", "prediction", "min_dis", "center")
//数据处理
val refDF = df.select($"id", $"features", $"min_dis", $"prediction", $"center", row_number() over Window.partitionBy($"prediction").orderBy(asc("min_dis")) as "row_number")
.where($"row_number" === 1)
val test = refDF.select($"id", $"features")
//ALSModel中的userFactor变量为私有变量,因此需要利用反射机制修改
val field = classOf[ALSModel].getDeclaredField("userFactors")
println(field.getName)
field.setAccessible(true)
field.set(alsModel, test)
//result test
val test_1 = alsModel.recommendForAllUsers(10).join(refDF.select($"id" as "gid", $"prediction"), Seq("gid"), "left")
.select($"prediction", $"recommendations")
.join(df.select($"id", $"prediction"), Seq("prediction"), "right").select($"id" as "gid", $"recommendations" as "test_rec", $"prediction")