基于协同过滤算法的推荐系统

推荐系统的描述

我们以电影推荐系统来看一下怎么样以机器学习的角度来描述推荐系统。我们记 $n_u$ 为用户的数量,$n_m$ 为电影的数量,$r(i,j) = 1$ 表示用户 j 对电影 i 进行过了评价,$y^{(i,j)}$ 就是它的分数。$r(i,j) = 0$ 表示用户还没观看过这个电影,也没评分过。我们假设用户看过电影后,一定会给电影一个评分,如果没有给,默认评分为零。这样,我们的电影推荐系统的任务,就是根据用户的评分,预测出那些用户还未观看的电影的评分,从而把那些用户可能会给出较高评分的电影推荐给用户。

基于内容的推荐算法

我们依然以电影推荐系统为例,我们假设 $\theta^{(j)}$ 表示用户 j 的参数,$x^{(i)}$ 为电影 i 的特征向量 (比如爱情电影,动作电影,好吧,如你所愿,可能还有爱情动作片)。这样,用户 j 对电影 i 的预测评分为 $(\theta^{(j)})^T (x^{(i)})$。

接下来的目标,是怎么样获得用户 j 的参数 $\theta^{(j)}$?这个实际上是个线性回归问题,即我们利用用户对现有电影的所有的评分学习出其参数 $\theta^{(j)}$。根据线性回归算法的成本公式,我们的目标是求解线性回归算法的成本函数的最小值时,$\theta^{(j)}$ 的值。假设 $m^{(j)}$ 是用户 j 评价过的电影的总数。n 为电影的特征数。

$$ J(\theta^{(j)}) = \frac{1}{2m^{(j)}} \left[ \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \lambda \sum_{k=1}^n (\theta_k^{(j)})^2 \right] $$

这里的 $\theta^{(j)} \in R^{n+1}$。累加器部分是指用户所有己评分的电影进行累加。

求解 $\theta^{(j)}$ 的过程就是最小化成本函数的过程。在数学上,我们可以稍微改造一下,得到基于内容的推荐算法的目标函数

求解用户 j 的参数 $\theta^{(j)}$

求下面这个成本函数的最小值,就得到了用户 j 的参数 $\theta^{(j)}$

$$ \frac{1}{2} \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \frac{\lambda}{2} \sum_{k=1}^n (\theta_k^{(j)})^2 $$

求解所有用户的参数 $\theta^{(1)}, \theta^{(2)}, … , \theta^{(n_u)}$

叠加所有的用户,求下面成本函数的最小值,就得到了所有用户的参数。

$$ \frac{1}{2} \sum_{j=1}^{n_u} \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \frac{\lambda}{2} \sum_{j=1}^{n_u} \sum_{k=1}^n (\theta_k^{(j)})^2 $$

根据线性回归算法介绍过的内容,针对针对 $k=0$,我们不取正则项。则其参数迭代公式为:

$$ \theta_k^{(j)} = \theta_k^{(j)} - \alpha \left( \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T - y^{(i)} \right) x_k^{(i)} \right) $$

根据线性回归算法介绍过的内容,针对 $k \ne 0$,包含正则项的迭代公式。其参数迭代公式为:

$$ \theta_k^{(j)} = \theta_k^{(j)} - \alpha \left( \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T x^{(i)} - y^{(i)} \right) x_k^{(i)} + \lambda \theta_k^{(j)} \right) $$

其中 $\alpha$ 是学习率。

协同过滤算法 (Collaborative Filtering)

基于内容的推荐算法需要对推荐对象提取特征,构成特征向量。我们还是拿电影推荐系统为例,需要对电影进行特征提取,如爱情片,动作片,然后对所有的电影进行特征采集,即针对每个电影写出其爱情成分是多少分,动作成分是多少分。这在工程上工作量非常大。

换一个方法,我们可以在用户注册的时候,让用户告诉我们他的偏好,比如用户喜欢哪些类型的电影。即我们通过调查问卷,事先知道了用户 j 的参数 $\theta^{(j)}$。再根据用户的对看过的电影的评分数据,去推断出电影属于哪种类型,即电影的特征向量 $x^{(i)}$。针对用户 j 没有看过的电影 i ,根据 $(\theta^{(j)})^T (x^{(i)})$ 预测出用户可能的评分,根据预测出来的评分高低,去决定是否向用户推荐这部电影。

怎么样从数学上描述这个过程呢?

计算电影 i 的特征向量 $x^{(i)}$

选取合适的 $x^{(i)}$ ,以便让下面的公式值最小。这是求解电影 i 的特征向量 $x^{(i)}$ 的过程。

$$ \frac{1}{2} \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \frac{\lambda}{2} \sum_{k=1}^n (x_k^{(i)})^2 $$

求解所有电影的特征 $x^{(1)}, x^{(2)}, … , x^{(n_m)}$

选取合适的 $x^{(1)}, x^{(2)}, … , x^{(n_m)}$ ,以便让下面的公式值最小。

$$ \frac{1}{2} \sum_{i=1}^{n_m} \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \frac{\lambda}{2} \sum_{i=1}^{n_m} \sum_{k=1}^n (x_k^{(i)})^2 $$

协同过滤算法

实际工程应用上,事先知道用户 j 的参数 $\theta^{(j)}$ 也是件很难的事。我们从之前的内容知道,有了所有的用户参数 $\theta^{(j)}$,我们就可以估算出电影的特征向量 $x^{(i)}$。或者如果有了电影的特征向量 $x^{(i)}$,我们可以算出用户的偏好参数 $\theta^{(j)}$。这看起来象是个鸡生蛋和蛋生鸡的问题。

实际上,协同过滤算法就是为了解决这个问题的。

  1. 先随机估算出用户参数 $\theta^{(j)}$
  2. 利用估算出来的 $\theta^{(j)}$ 和用户看过的电影的评分数据,估算出特征向量 $x^{(i)}$
  3. 利用估算出来的特征向量 $x^{(i)}$,反向估算出用户偏好参数 $\theta^{(j)}$
  4. 重复步骤 2 ,直到用户参数 $\theta^{(j)}$ 和特征向量 $x^{(i)}$ 收敛到一个合适的值

这就是协同过滤算法的核心步骤。协同过滤的一个重要的效应是,当用户对某个电影进行评分时,会帮助算法学习电影的特征,这有利于系统向所有用户推荐合适的电影,同时也让算法更好地学习到用户的偏好。这样所有用户不停地使用系统的过程中,无形中在协同过滤,帮助系统学习出更好的参数,从而更准确地推荐出用户喜欢的电影。

协同过滤算法的实现

上一节描述的协同过滤算法需要不停地计算 $\theta^{(j)}$ 和 $x^{(i)}$,这样算法的效率实际上是比较低的。从数学角度,一个更高效的算法是把计算 $\theta^{(j)}$ 和 $x^{(i)}$ 的两个成本函数合并起来,得到以 $\theta^{(j)}$ 和 $x^{(i)}$ 为参数的总成本函数,最小化这个成本函数,就可以同时求出 $\theta^{(j)}$ 和 $x^{(i)}$ 的值。

总成本函数为:

$$ J = \frac{1}{2} \sum_{(i,j):r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \frac{\lambda}{2} \sum_{i=1}^{n_m} \sum_{k=1}^n (x_k^{(i)})^2 + \frac{\lambda}{2} \sum_{j=1}^{n_u} \sum_{k=1}^n (\theta_k^{(j)})^2 $$

累加器 $\sum_{(i,j):r(i,j)=1}$ 表示针对每个用户评价过的所有的电影进行累加。后两项分别是 $\theta^{(j)}$ 的正则项和 $x^{(i)}$ 的正则项。需要注意的是,总成本函数里 $\theta^{(j)}$ 和 $x^{(i)}$ 都是 n 维向量,即不包含偏置向量 $x_0$。

这样我们可以更新协同过滤算法的实现步骤:

  • 用较小的随机数来初始化 $x^{(1)}, x^{(2)}m, … ,x^{(n_m)}, \theta^{(1)}, \theta^{(2)}, … , \theta^{(n_u)}$。为什么要用较小的随机数来初始化,而不全用零呢?这是因为我们需要让这些变量具有不同的初始值,以便不会让两个变量变成同一个特征。
  • 最小化成本函数 $J(x^{(1)}, x^{(2)}m, … ,x^{(n_m)}, \theta^{(1)}, \theta^{(2)}, … , \theta^{(n_u)})$ ,可以使用梯度下降或其他的优化过的高级算法。其参数迭代公式为

$$ \theta_k^{(j)} = \theta_k^{(j)} - \alpha \left( \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T x^{(i)} - y^{(i)} \right) x_k^{(i)} + \lambda \theta_k^{(j)} \right) $$

$$ x_k^{(i)} = x_k^{(i)} - \alpha \left( \sum_{i:r(i,j)=1} \left( (\theta^{(j)})^T x^{(i)} - y^{(i)} \right) \theta_k^{(i)} + \lambda x_k^{(j)} \right) $$

  • 学习出参数后,针对一个用户 j 的参数为 $\theta^{(j)}$ ,针对这个用户没有看过的电影,学习到的特征为 $x^{(i)}$,那么可以预测到这个用户对这个电影的评分将是 $(\theta^{(j)})^T x^{(i)}$。

需要特别注意的是,此处我们没有加偏置变量 $x_0$,也不存在 $\theta_0$。

协同过滤算法的向量化实现

低秩矩阵分解 (Low Rank Matrix Factorization)

使用协同过滤算法算出所有用户的参数 $\theta^{(j)}$ 和所有电影的特征 $x^{(i)}$ 之后,我们可以针对某个用户 j 对电影 i 的评分做出预测,预测公式为 $(\theta^{(j)})^T x^{(i)}$ 。如果我们想要一次性计算所有用户对所有电影的评分,我们可以写成下面的矩阵:

$$ \begin{bmatrix} (\theta^{(1)})^T x^{(1)} & (\theta^{(2)})^T x^{(1)} & \cdots & (\theta^{(n_u)})^T x^{(1)} \\ (\theta^{(1)})^T x^{(2)} & (\theta^{(2)})^T x^{(2)} & \cdots & (\theta^{(n_u)})^T x^{(2)} \\ \vdots & \vdots & \vdots & \vdots \\ (\theta^{(1)})^T x^{(n_m)} & (\theta^{(2)})^T x^{(n_m)} & \cdots & (\theta^{(n_u)})^T x^{(n_m)} \\ \end{bmatrix} $$

这是个 $n_m \times n_u$ 的矩阵,其中 $n_m$ 是电影个数,$n_u$ 是用户数。我们把所有的电影特征 $x^{(i)}$ 写成一个 $n_m \times n$ 的矩阵,其中 n 是电影的特征个数。

$$ X = \begin{bmatrix} - & (x^{(1)})^T & - \\ - & (x^{(2)})^T & - \\ - & \vdots & - \\ - & (x^{(n_m)})^T & - \\ \end{bmatrix} $$

再把所有的用户参数 $\theta^{(j)}$ 写成一个 $n_u \times n$ 的矩阵:

$$ \Theta = \begin{bmatrix} - & (\theta^{(1)})^T & - \\ - & (\theta^{(2)})^T & - \\ - & \vdots & - \\ - & (\theta^{(n_u)})^T & - \\ \end{bmatrix} $$

利用矩阵相乘的法则,公式 $Y = X \Theta^T$ 就可以一次性算出所有用户对所有电影的评分。其中 $X \in R^{n_m \times n}, \Theta^T \in R^{n \times n_u}$,其内积 $Y \in R^{n_m \times n_u}$。

计算量问题

针对大型的电影网站,电影数量是非常庞大的,用户量可能更庞大。对这样一个大的矩阵进行运算,其计算量将大的惊人的。从协同过滤算法的核心原则来看,系统需要经常计算这个矩阵,最坏的情况是每当有用户对一个新电影进行评分时就需要计算一次。详细讨论这个问题已经超出本文的范围,但可以简单谈一些策略:

  • 选择合适的计算周期 我们不一定在每次用户提交一个新的电影评分数据时就计算,而是一天甚至几天计算一次。这样的结果就是,只要数据库的读写分离做得好,即使我们用很有限的硬件资源,也可以完成预期的计算目标。因为我们的计算时效性要求不强,每次计算完,更新参数后,我们的推荐系统就变得更准确一点了。
  • 使用分布式计算
  • 使用量子计算机 量子计算机的运算速度上比目前的计算机要提高 N 个数量级。

推荐相似的电影

我们还是以电影为例,假设我们已经通过协同过滤算法学习到了所有的电影特征 $x^{(i)}$。假设这个时候用户在浏览电影 i ,我们要推荐 5 部相似的电影给用户。怎么样找到这 5 部相似的电影呢?

我们可以遍历所有的电影,通过公式 $| x^{(i)} - x^{(k)} |$ 找出和正在浏览的电影“距离最小”,即相似度最高的 5 部电影。

均值归一化 Mean Normalization

假设现在有个新用户 j 没有对任何电影进行打分。那么根据协同过滤算法的成本函数:

$$ J = \frac{1}{2} \sum_{(i,j):r(i,j)=1} \left( (\theta^{(j)})^T (x^{(i)}) - y^{(i,j)} \right)^2 + \frac{\lambda}{2} \sum_{i=1}^{n_m} \sum_{k=1}^n (x_k^{(i)})^2 + \frac{\lambda}{2} \sum_{j=1}^{n_u} \sum_{k=1}^n (\theta_k^{(j)})^2 $$

第一部分将为 0 ,因为对用户 j ,没有 $r(i,j)=1$ 的项。第二部分将为一个固定值,因为已经学习好了电影的特征,所以 $x_k^{(i)}$ 将是常量,所以针对新用户 j 问题将简化为最小化 $\frac{\lambda}{2} \sum_{j=1}^{n_u} \sum_{k=1}^n (\theta_k^{(j)})^2$ ,这个计算结果将使 $\theta^{(j)}$ 为全 0 。

这样的话,这个用户对所有电影的评分将预测为 0 ,我们也就没有什么他喜欢的电影推荐给他了。

怎么样解决这个问题呢?

简单地讲,如果一个新用户没有评分过任何电影,我们可以预测这个用户对新电影的评分为这个电影的评分的平均值。用数学的语言来描述就是,我们需要先对电影评分数据进行均值归一处理。

假设我们有所有的电影评分矩阵 $Y \in R^{n_m \times n_u}$,对其按行求平均值,得到 $\mu \in R^{n_m}$。然后我们计算 $Y - \mu$ 作为我们协同过滤算法的训练数据集。这样训练出来 $\theta^{(j)}$ 和 $x^{(i)}$ 之后,针对新用户 j 对电影 i 的预测评分公式将变成 $(\theta^{(j)})^T x^{(i)} + \mu_i$。

还有一种情况是如果有一部新的电影,所有人都没有评分过,那么这个电影的特征值将全为 0 。我们也可以用均值归一法来处理,但这样处理是否合理需要从业务层面去理解。比如一部新电影,所有人都没看过,也没评分过,我们就不应该推荐给任何人。从业务层面,一个新电影可能会有专门的展示区域,比如“新片速递”。

总结

协同推荐算法就像魔术一样,解决了鸡生蛋和蛋生鸡的问题,从无到有构建了用户的偏好和电影的特征。当然,这里没有魔术,协同过滤算法的关键是用户不停地使用系统,使用过程中不停地完善推荐系统。


Post by Joey Huang under ml on 2015-12-19(Saturday) 22:36. Tags: machine-learning,


Powered by Pelican and Zurb Foundation. Theme by Kenton Hamaluik.