$$ \newcommand{\R}{\mathbb{R}} \newcommand{\E}{\mathbb{E}} \newcommand{\x}{\mathbf{x}} \newcommand{\y}{\mathbf{y}} \newcommand{\wv}{\mathbf{w}} \newcommand{\av}{\mathbf{\alpha}} \newcommand{\bv}{\mathbf{b}} \newcommand{\N}{\mathbb{N}} \newcommand{\id}{\mathbf{I}} \newcommand{\ind}{\mathbf{1}} \newcommand{\0}{\mathbf{0}} \newcommand{\unit}{\mathbf{e}} \newcommand{\one}{\mathbf{1}} \newcommand{\zero}{\mathbf{0}} \newcommand\rfrac[2]{^{#1}\!/_{#2}} \newcommand{\norm}[1]{\left\lVert#1\right\rVert} $$

交替最小二乘法(Alternating Least Squares)

描述

交替最小二乘法(ALS)算法将一个给定的$R$矩阵因式分解为 $U$ 和 $V$ 两个因子,例如 $R \approx U^TV$。 未知的行的维度被用作算法的参数,叫做潜在因子。 由于矩阵因式分解可以用在推荐系统的场景,$U$和$V$矩阵可以分别称为用户和商品矩阵。 用户矩阵的第i列用$u_i$表示,商品矩阵的第i列用$v_i$表示。 $R$ 矩阵称为评价矩阵可以用 表示。 为了找到用户和商品矩阵,如下问题得到了解决:

$\lambda$作为正则化因子,作为用户$i$评过分的商品数量, 作为商品$j$被评分的次数。 这个因式分解方案避免了称作加权$\lambda​$因式分解的过拟合。 细节可以在Zhou et al.的论文中找到。

通过修复$U$ 和 $V$矩阵,我们获得可以直接解析的二次形式。 问题的解决办法是保证总消耗函数的单调递减。通过对$U$ 或 $V$矩阵的这一步操作,我们逐步的改进了矩阵的因式分解。

$R$ 矩阵作为 $(i,j,r)$ 元组的疏松表示。$i$ 为行索引,$j$ 为列索引,$r$ 为 $(i,j)$ 位置上的矩阵值。

操作

ALS 是一个预测模型(Predictor)。 因此,它支持拟合(fit)与预测(predict)两种操作。

拟合

ALS用于评价矩阵的疏松表示过程的训练:

  • fit: DataSet[(Int, Int, Double)] => Unit

预测

ALS会对每个元组行列的所有索引进行评分预测:

  • predict: DataSet[(Int, Int)] => DataSet[(Int, Int, Double)]

参数

ALS的实现可以通过下面的参数进行控制:

参数 描述
NumFactors

底层模型中使用的潜在因子数目。等价于计算用户和商品向量的维度。 (默认值:10)

Lambda

因式分解的因子。 该值用于避免过拟合或者由于强生成导致的低性能。 (默认值:1)

Iterations

最大迭代次数。 (默认值:10)

Blocks

设定用户和商品矩阵被分组后的块数量。块越少,发送的冗余数据越少。然而,块越大意味着堆中需要存储的更新消息越大。如果由于OOM导致算法失败,试着降低块的数量。 (默认值:None)

Seed

用于算法生成初始矩阵的随机种子。 (默认值:0)

TemporaryPath

导致结果被立即存储到临时目录的路径。如果该值被设定,算法会被分为两个预处理阶段,ALS迭代和计算最后ALS半阶段的处理中阶段。预处理阶段计算给定评分矩阵的OutBlockInformation和InBlockInformation。每步的结果存储在特定的目录。通过将算法分为更多小的步骤,Flink不会在多个算子中分割可用的内存。这让系统可以处理更大的独立消息并提升总性能。 (默认值:None)

例子

// 从CSV文件读取输入数据集
val inputDS: DataSet[(Int, Int, Double)] = env.readCsvFile[(Int, Int, Double)](
  pathToTrainingFile)

// 设定ALS学习器
val als = ALS()
.setIterations(10)
.setNumFactors(10)
.setBlocks(100)
.setTemporaryPath("hdfs://tempPath")

// 通过一个map参数设置其他参数
val parameters = ParameterMap()
.add(ALS.Lambda, 0.9)
.add(ALS.Seed, 42L)

// 计算因式分解
als.fit(inputDS, parameters)

// 从CSV文件读取测试数据
val testingDS: DataSet[(Int, Int)] = env.readCsvFile[(Int, Int)](pathToData)

// 通过矩阵因式分解计算评分
val predictedRatings = als.predict(testingDS)