首页 > 代码库 > 推荐系统

推荐系统


本文结构:
1. 推荐系统
2. 常用方法
1. 简介
2. 模型 cost, gradient 表达式
3. 代码实现
3. 应用实例

参考:
Coursera-Andrew Ng 的 Machine Learning
Sirajology 的 Recommendation Systems - Learn Python for Data Science


1. 推荐系统

根据用户的兴趣特点和购买行为,向用户推荐用户感兴趣的信息和商品。
为用户节省时间,还能挖掘可能用户自己都不知道的潜在兴趣点。

生活中的例子:喜马拉雅上根据我听过的书推荐相关的内容,效果不错,推荐的很多我都会订阅。Youtube上根据我看过的视频推荐内容,如果我在追剧,它会把最新的剧集放在我首页,还有我可能感兴趣的电影。

还有很多例子和方法,以及冷启动等关键问题,推荐大家看《推荐系统实战》这本书,之前去听新浪微博的分享,这本书是他们推荐系统部门的必备材料。


2. 常用模型

方法有基于内容,基于物品,基于用户和协同过滤等。

基于内容,物品,用户的推荐就是把相关的特征表达为向量形式后,计算它们之间的距离,根据相似度高的来为你推荐。

技术分享

基于内容,例如,要判断文章间的相似性,就可以把每篇文章,按照它里面用到的单词,把一篇文章表达为向量的形式,然后计算文章之间的距离。

如果我们有一个用户对物品的打分矩阵,那么通过计算行向量间的距离,可以计算出物品之间的相似性,计算列向量的距离,可以得到用户间的相似性。

根据距离的定义公式,计算出向量间的距离,找到最相近的几个对象,再取平均值就可以作为预测值。

协同过滤

我们要解决一个推荐问题时,很自然的可以想到,用户为什么喜欢这些物品,应该是因为这些物品具有某些特点,而用户刚好对这些特点感兴趣。

但具体是哪些特点呢,每个特点又该用什么值来量化呢,这一步可能并不好做。协同过滤就可以提供一个解决方案,即使你并不知道这些特点都有什么,即使你并不知道各个特点都是多少值,你仍然可以得到预测结果。

技术分享

技术分享

它的模型表示为,x\_i 为电影 i 具有的特点向量,y\_j 为用户 j 对所有特点的偏好值向量。二者相乘就得到用户 j 对电影 i 的打分,然后可以用来和实际分数进行比较:
技术分享

  1. 写出所有用户对所有电影的预测分数与实际分数的平方差公式,再加上 theta 和 x 的正则项,就得到了目标函数,我们要使这个函数达到最小值。
    技术分享

  2. 然后计算 cost function 对 theta 和 x 的偏导:
    技术分享

  3. 接着可以用梯度下降法迭代求 cost function 的最小值。

最后可以用计算得到的 theta 和 x 相乘,得到打分矩阵中未知的部分。

技术分享


3. 代码实现

下面是用 matlab 实现的协同过滤。
其中的计算大多用矩阵表达,这样比写循环要快而且简洁,代码很简单,也可以很容易地用python写出来。

完整代码链接

1. 引入数据

图中可见用户对电影的评分热点图。

%  Load data
load (‘ex8_movies.mat‘);

imagesc(Y);
ylabel(‘Movies‘);
xlabel(‘Users‘);

技术分享

2. 写出 cost function 和 gradient 的表达式

其中 J 为 cost function,X\_grad,Theta\_grad 为相应的梯度。
先写了没有正则项的形式,在这基础上又加上了正则项,当然可以合二为一直接写带有正则项的。

需要注意的是,在数据矩阵中,我们只将有打分的地方拿来计算,
所以在代码中我们用 R 点乘误差矩阵,这个 R 的意思,R(i, j) = 1 时,说明用户 j 对电影 i 有评分,为 0 时就是没有打分。

function [J, grad] = cofiCostFunc(params, Y, R, num_users, num_movies, ...
                                  num_features, lambda)

% Unfold the U and W matrices from params
X = reshape(params(1:num_movies*num_features), num_movies, num_features);
Theta = reshape(params(num_movies*num_features+1:end), ...
                num_users, num_features);

% ========= without Regularization
M = X*Theta‘-Y;
Z = R.*M;   
J = 0.5 * sum( sum(Z.*Z) );

M_grad = X*Theta‘-Y;
Z_grad = R.* M_grad;
X_grad = ( Z_grad )*Theta;   
Theta_grad = ( Z_grad )‘*X; 

% ========= Regularization

Theta_regu = sum( sum(Theta.*Theta) )*lambda/2;
X_regu = sum( sum(X.*X) )*lambda/2;
J = J + Theta_regu + X_regu;

X_grad = X_grad + lambda*X;
Theta_grad = Theta_grad + lambda*Theta;


% Fold the U and W matrices to params
grad = [X_grad(:); Theta_grad(:)];

end

3.用随机梯度下降来训练模型

先随机初始化 X 和 Theta。
定义 ‘MaxIter’ 迭代次数和 ‘lambda’ 正则项之后,
调用 ‘fmincg’ 训练模型,得到 cost function 达到最小时对应的 X 和 Theta。

% Set Initial Parameters (Theta, X)
X = randn(num_movies, num_features);
Theta = randn(num_users, num_features);

initial_parameters = [X(:); Theta(:)];

% Set options for fmincg
options = optimset(‘GradObj‘, ‘on‘, ‘MaxIter‘, 100);

% Set Regularization
lambda = 10;
theta = fmincg (@(t)(cofiCostFunc(t, Y, R, num_users, num_movies, ...
                                num_features, lambda)), ...
                initial_parameters, options);

% Unfold the returned theta back into U and W
X = reshape(theta(1:num_movies*num_features), num_movies, num_features);
Theta = reshape(theta(num_movies*num_features+1:end), ...
                num_users, num_features);

4.得到预测值

用 X * Theta’ 得到预测值,其中第一列是目标用户的向量。
再根据打分进行排序,输出前10个推荐影片。

p = X * Theta‘;
my_predictions = p(:,1) + Ymean;

movieList = loadMovieList();

[r, ix] = sort(my_predictions, ‘descend‘);
fprintf(‘\\nTop recommendations for you:\\n‘);
for i=1:10
    j = ix(i);
    fprintf(‘Predicting rating %.1f for movie %s\\n‘, my_predictions(j), ...
            movieList{j});
end

4. 用 python 的 lightFM 库实现

完整代码见 Sirajology 的 Github

今天会用到三个库, Numpy, Scipy, LightFM。
先从 lightfm 这个库里引入需要的数据,还有 LightFM 用来构建模型。

import numpy as np
from lightfm.datasets import fetch_movielens
from lightfm import LightFM

接下来导入数据。
它有1k的用户对1700部电影的100k的评分数据。每个用户对二十个左右的电影进行评分,分数由1到5,以字典的形式存储。

#fetch data and format it
data = fetch_movielens(min_rating=4.0)

接下来建立模型
定义它的损失函数为 wrap,即给定一个用户来找到与他相似的用户,并且得到他们喜欢的电影的排序,用这个排序作为对新用户的预测。用 fit 函数来训练模型。

#create model
model = LightFM(loss=‘warp‘)
#train model
model.fit(data[‘train‘], epochs=30, num_threads=2)

接下来就是建立推荐的函数 sample_recommendation

先从训练数据中得到我们有多少个用户和电影。
对每个用户,取到他们已经喜欢的电影的前三个,
再取 model.predict 得到的预测值中,排名前三的 top_items。
打印出来可以观察一下。

def sample_recommendation(model, data, user_ids):

    #number of users and movies in training data
    n_users, n_items = data[‘train‘].shape

    #generate recommendations for each user we input
    for user_id in user_ids:

        #movies they already like
        known_positives = data[‘item_labels‘][data[‘train‘].tocsr()[user_id].indices]

        #movies our model predicts they will like
        scores = model.predict(user_id, np.arange(n_items))

        #rank them in order of most liked to least
        top_items = data[‘item_labels‘][np.argsort(-scores)]

        #print out the results
        print("User %s" % user_id)
        print("     Known positives:")

        for x in known_positives[:3]:
            print("        %s" % x)

        print("     Recommended:")

        for x in top_items[:3]:
            print("        %s" % x)

在调用函数时只需要传入 model,data 和 user_ids 即可。

sample_recommendation(model, data, [3, 25, 450])

历史技术博文链接汇总

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    推荐系统