协同过滤(ALS)

Spark ALS算法进行矩阵分解,U * V = Q

如果数据不是运行在集群上,而是运行在本地,为了保证内存充足,在启动spark-shell时需要指定参数--driver-memory 6g

    1. 数据集

艺术家点播数据集:

用户和艺术家的关系是通过其他行动隐含提现出来的,例如播放歌曲或专辑,而不是通过显式的评分或者点赞得到的。这被称为隐式反馈数据。现在的家用电视点播也是这样,用户一般不会主动评分。

数据集下载地址是http://www.iro.umontreal.ca/~lisa/datasets/profiledata_06-May-2005.tar.gz

    1. 数据处理

Spark MLibALS算法实现有一个小缺点:它要求用户和产品的ID必须是数值型,并且是32位非负整数,这意味着大于Integer.MAX_VALUE2147483647)的ID是非法的。我们首先看看数据集是否满足要求:

scala> val rawUserArtistData = sc.textFile("D:/Workspace/AnalysisWithSpark/src/main/java/advanced/chapter3/profiledata_06-May-2005/user_artist_data.txt")

rawUserArtistData: org.apache.spark.rdd.RDD[String] = D:/Workspace/AnalysisWithSpark/src/main/java/advanced/chapter3/profiledata_06-May-2005/user_artist_data.txt MapPartitionsRDD[1] at textFile at <console>:27

 

scala> rawUserArtistData.map(_.split(' ')(0).toDouble).stats()

res0: org.apache.spark.util.StatCounter = (count: 24296858, mean: 1947573.265353, stdev: 496000.544975, max: 2443548.000000, min: 90.000000)

 

scala> rawUserArtistData.map(_.split(' ')(1).toDouble).stats()

res1: org.apache.spark.util.StatCounter = (count: 24296858, mean: 1718704.093757, stdev: 2539389.040171, max: 10794401.000000, min: 1.000000)

过滤数据

val rawArtistData = sc.textFile("hdfs:///user/ds/artist_data.txt")

val artistByID = rawArtistData.flatMap { line =>

    val (id, name) = line.span(_ != '\t')

    if (name.isEmpty) {

        None

    } else {

        try {

            Some((id.toInt, name.trim))

        } catch {

            case e: NumberFormatException => None

        }

    }

}

 

 

    1. 训练算法

首先将数据转换成Rating

import org.apache.spark.mllib.recommendation._

val bArtistAlias = sc.broadcast(artistAlias)

val trainData = rawUserArtistData.map { line =>

    val Array(userID, artistID, count) = line.split(' ').map(_.toInt)

    val finalArtistID =

    bArtistAlias.value.getOrElse(artistID, artistID)

    Rating(userID, finalArtistID, count)

}.cache()

 

val model = ALS.trainImplicit(trainData, 10, 5, 0.01, 1.0)

//查看特征变量:

model.userFeatures.mapValues(_.mkString(", ")).first()

 

//我们可以对此用户做出5个推荐:

val recommendations = model.recommendProducts(2093760, 5)

recommendations.foreach(println)

 

    1. 选择超参数

计算AUC这部分代码没有试。AUC(Area Under ROC Curve)ROC(Receiver Operating Characteristic,受试者工作特征)线,它源于二战中用于敌机检测的雷达信号分析技术。在非均等代价下,ROC曲线不能直接反映出学习器的期望总体代价,而代价曲线则可达到该目的。

机器学习常涉及两类参数:一类是算法的参数,亦称超参数,数目常在10以内;另一类是模型的参数,数目可能很多。前者通常是由人工设定多个参数候选值后产生模型,后者则是通过学习来产生多个候选模型。
ALS.trainImplicit()的参数包括以下几个:

rank
  模型的潜在因素的个数,即用户-特征产品-特征矩阵的列数;一般来说,它也是矩阵的阶。

iterations
  矩阵分解迭代的次数;迭代的次数越多,花费的时间越长,但分解的结果可能会更好。

lambda
  标准的过拟合参数;值越大越不容易产生过拟合,但值太大会降低分解的准确度。lambda取较大的值看起来结果要稍微好一些。

alpha
  控制矩阵分解时,被观察到的用户-产品交互相对没被观察到的交互的权重。40是最初ALS论文的默认值,这说明了模型在强调用户听过什么时的表现要比强调用户没听过什么时要好。

 

    1. 应用(产生推荐)

val someUsers = allData.map(_.user).distinct().take(100)

val someRecommendations =

    someUsers.map(userID => model.recommendProducts(userID, 5))

someRecommendations.map(

    recs => recs.head.user + " -> " + recs.map(_.product).mkString(", ")

).foreach(println)

 

    1. Rating数据代码

def run(filename: String) {

    println("mlALS is running!")

    val data = getData(filename)

    println(s"dataLen:${data.count}")

 

    val als = new ALS()

      .setMaxIter(5)

      .setRegParam(0.01)

      .setUserCol("newUserId")

      .setItemCol("newMovieId")

      .setRatingCol("newRating");

 

    val split = data.randomSplit(Array(0.8, 0.2))

    val model = als.fit(split(0));

    println(s"userCol name:${model.getUserCol}")

 

    val predictions = model.transform(split(1))

    val evaluator = new RegressionEvaluator()

      .setMetricName("rmse")

      .setLabelCol("newRating")

      .setPredictionCol("prediction")

    val rmse = evaluator.evaluate(predictions)

   

    println(s"Root-mean-square error = $rmse")

   

    val userCommdmodel.recommendForAllUsers(10)

    userCommd.show()

  }

 

  def getData(filename: String) :Dataset[Row] = {

    val sqlContext = new SQLContext(sc.sparkContext)

    val df = sqlContext.load("com.databricks.spark.csv", Map("path" -> filename, "header" -> "true"))

    df.columns.map(col => println(s"col:${col}"))

   

    val newdf = df.select(df.col("userId").cast(IntegerType).as("newUserId"),

        df.col("movieId").cast(IntegerType).as("newMovieId"),

        df.col("rating").cast(DoubleType).as("newRating"),

        df.col("timestamp").cast(LongType).as("newTimeStamp"))

   

    newdf

  }

 

    1. 数据正则化

 

val dataFrame = sqlContext.read.format("libsvm").load("data/libsvm.txt")

 

    // L1正则化

    val normalizer = new Normalizer().setInputCol("features").setOutputCol("normFeatures")

      // 设置 L1正则化

      .setP(1.0)

    // 正则化转换

val l1NormData = normalizer.transform(dataFrame)

  // L2正则化

     val l2InfNormData = normalizer.transform(dataFrame, normalizer.p -> 2)

    l2InfNormData.foreach(println)

转载于:https://my.oschina.net/u/778683/blog/1831259

### ALS协同过滤简介 ALS(Alternating Least Squares)是一种基于矩阵分解的协同过滤推荐算法,在机器学习领域广泛应用。其核心思想是将用户-物品交互数据表示为一个稀疏矩阵 \( R \),并通过交替最小二乘法将其分解为两个低维矩阵:用户隐因子矩阵 \( P \) 和物品隐因子矩阵 \( Q \)[^1]。 #### 基本原理 假设存在一个大小为 \( m \times n \) 的评分矩阵 \( R \),其中 \( m \) 表示用户数量,\( n \) 表示物品数量。ALS的目标是对该矩阵进行分解,使得: \[ R \approx PQ^\top \] 这里,\( P \) 是一个 \( m \times k \) 的矩阵,代表用户的隐因子;\( Q \) 是一个 \( n \times k \) 的矩阵,代表物品的隐因子。参数 \( k \) 定义了隐因子的数量,通常远小于原始矩阵维度[^2]。 为了找到最佳的 \( P \) 和 \( Q \)ALS采用迭代方式分别固定其中一个矩阵并更新另一个矩阵,直到收敛为止。具体而言,当固定 \( P \) 时,可以通过最小化损失函数计算得到新的 \( Q \);反之亦然[^3]。 --- ### 实现方法 以下是ALS的核心实现步骤及其对应的代码片段说明: #### 数据准备 输入是一个稀疏矩阵 \( R \),可以由显式反馈(如用户评分)或隐式反馈(如点击次数、购买记录等)构建而成。对于大规模数据集,常使用分布式框架(如Apache Spark)处理[^5]。 #### 损失函数定义 ALS通过优化以下带正则化的平方误差目标函数来寻找最优解: \[ L(P, Q) = \sum_{(i,j)\in \Omega} (r_{ij} - p_i q_j^\top)^2 + \lambda (\|p_i\|^2 + \|q_j\|^2) \] 其中: - \( r_{ij} \) 表示用户 \( i \) 对物品 \( j \) 的真实评分; - \( p_i \) 和 \( q_j \) 分别对应第 \( i \) 个用户的隐因子向量和第 \( j \) 个物品的隐因子向量; - \( \lambda \) 控制模型复杂度以防止过拟合[^4]。 #### 迭代过程 每次迭代分为两步: 1. **固定 \( P \)** 并更新 \( Q \)。 2. **固定 \( Q \)** 并更新 \( P \)。 每一步都可以转换为独立的小型线性回归问题,利用闭式解高效求得新值。 下面是Python中基于NumPy的一个简单实现示例: ```python import numpy as np def als(R, latent_factors=10, iterations=10, lambda_reg=0.1): """ 使用ALS实现矩阵分解 参数: R: 用户-物品评分矩阵 (m x n), 可能含有缺失值 latent_factors: 隐因子数k iterations: 迭代次数 lambda_reg: 正则化系数 返回: P: 用户隐因子矩阵 (m x k) Q: 物品隐因子矩阵 (n x k) """ m, n = R.shape P = np.random.rand(m, latent_factors) Q = np.random.rand(n, latent_factors) for _ in range(iterations): # 更新Q for j in range(n): Aj = np.dot(P.T, P) + lambda_reg * np.eye(latent_factors) Vj = np.zeros((latent_factors,)) for i in range(m): if R[i][j] > 0: Vj += R[i][j] * P[i] Q[j] = np.linalg.solve(Aj, Vj) # 更新P for i in range(m): Bi = np.dot(Q.T, Q) + lambda_reg * np.eye(latent_factors) Ui = np.zeros((latent_factors,)) for j in range(n): if R[i][j] > 0: Ui += R[i][j] * Q[j] P[i] = np.linalg.solve(Bi, Ui) return P, Q ``` 上述代码实现了基本的ALS流程,适用于小型稠密矩阵场景。如果面对更大规模的数据,则需借助Spark MLlib或其他工具完成分布式的ALS训练。 --- ### 性能优势与局限性 **优点** - 收敛速度快,尤其在高纬度稀疏矩阵下表现良好。 - 易于扩展至分布式环境,支持海量数据处理。 **缺点** - 初始随机初始化可能导致局部最优而非全局最优。 - 质量依赖超参调优(如隐因子数目、正则项权重等),调试成本较高。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值