基於用戶的協同過濾算法的電影推薦(稀疏矩陣)含代碼和Netflix數據

推薦問題是大數據的典型應用,利用已知的用戶瀏覽歷史和評分等行爲,猜測用戶興趣,進行個性化的推薦

數據集

用戶對電影的打分(1-5),共有10000個用戶和10000部電影。

  • 用戶id列表 users
    10000行,1列,表示用戶的id
  • 電影名稱列表 movie_titles
    17770行,3列,格式爲電影id,年份,名稱
    在這裏插入圖片描述
  • 評分數據 netflix_data
    861萬行,每行爲一次打分,包括用戶id 電影id 分數 打分日期,各項之間用空格分開,其中用戶id均出現在users.txt中,電影id爲1-10000的整數
    在這裏插入圖片描述

取評分數據的80%爲訓練集netflix_train(689萬),剩餘的20%爲測試集netflix_test(172萬條)。

數據處理

實驗使用全量數據。
將輸入文件整理成維度爲用戶*電影的矩陣 X,其中Xij對應用戶 i 對電影 j 的打分。對於分數未知的項,採取全定爲 0的處理方法。

  1. 從users.txt中獲得userID和i的關係:
data_user = importdata('users.txt');
user_map = containers.Map({0},{0});
for i = 1:size(data_user)
    user_map(data_user(i))= i;
end
  1. 讀取netflix數據

讀取netflix_train.txt文件,整理成維度爲用戶*電影的矩陣X_train

% 訓練集
X_train=zeros(10000,10000);
[u,m,f,d] = textread('netflix_train.txt','%d %d %d %s');
for i = 1:size(u)
    X_train(user_map(u(i)),m(i)) = f(i);
end

讀取netflix_test.txt文件,整理成維度爲用戶*電影的矩陣X_test

% 測試集
X_test = zeros(10000,10000);
[u,m,f,~] = textread('netflix_test.txt','%d %d %d %s');
for i = 1:size(u)
    X_test(user_map(u(i)),m(i)) = f(i);
end
  1. 轉化爲稀疏矩陣,

由於本實驗中 X 矩陣非常稀疏,使用稀疏矩陣運算,避免使用 for 循環。
在Matlab中使用spones()獲得指示變量矩陣,用來記錄哪些是用戶已有打分的記錄。

S=sparse(X) — 將矩陣X轉化爲稀疏矩陣的形式,即矩陣X中任何零元素去除,非零元素及其下標(索引)組成矩陣S。如果X本身是稀疏的,sparse(X)返回S

舉例:

>> a=[1,0,2;0,0,1;0,0,6];
>> a
a =
     1     0     2
     0     0     1
     0     0     6
>> b=sparse(a)
b =
   (1,1)        1
   (1,3)        2
   (2,3)        1
   (3,3)        6

實驗代碼:
R = spones(S) 生成與 S 具有相同稀疏結構的矩陣 R,但 1 位於非零位置

X_test = sparse(X_test);
X_train = sparse(X_train);
% 指示變量
A_test = spones(X_test);
A_train = spones(X_train);

  1. 保存一下數據

將X_train、X_test和A_train、A_test數據變量保存在X_matrix_sparse.mat文件中

save('X_matrix_sparse','X_train','X_test','A_test','A_train');
clear u m f d i data_user user_map;

協同過濾算法

協同過濾(Collaborative Filtering)是最經典的推薦算法之一,包含基於 user 的協同過濾和基於 item 的協同過濾兩種策略。本次,實現基於用戶的協同過濾算法。算法的思路非常簡單,當我們需要判斷用 戶 i 是否喜歡電影 j,只要看與 i 相似的用戶,看他們是否喜歡電影 j,並根據相似度對他們的打分進行加權平均。 用公式表達,就是:
在這裏插入圖片描述
其中,X(i)表示用戶 i 對所有電影的打分,對應到我們的問題中,就是 X 矩陣中第 i 行對應的 10000 維的向 量( 未 知 記 爲 0 )。
𝑠𝑖𝑚(𝑋(𝑖), 𝑋(𝑘))表示用戶 i 和用戶 k,對於電影打分的相似度,可以採用兩個向量的 cos 相似度來表示, 即:
在這裏插入圖片描述
通過上面的公式,就可以對測試集中的每一條記錄,計算用戶可能的打分。

  1. 構造相似度矩陣

關於歐式距離與餘弦相似度的計算,在 MATLAB 中都有相應的函數。
夾角餘弦距離Cosine distance(‘cosine’),歐幾里德距離Euclidean distance(‘euclidean’),標準歐幾里德距離Standardized Euclidean distance(‘seuclidean’)等等

使用pdist()函數得到餘弦距離,1-pdist()得到兩兩用戶間的餘弦相似度cos_sim,轉化爲相似度矩陣X_sim,X_sim(i, j)表示用戶i與用戶j之間的相似度。
代碼:

load('X_matrix_sparse.mat');

% 計算餘弦相似度矩陣
cos_sim = 1-pdist(X_test,'cosine');

X_sim = zeros(N,N);
cursor = 1;
for i = 1:N
    X_sim(i,i) = 0;
    for j = i+1:N
        X_sim(i,j) = cos_sim(cursor);
        X_sim(j,i) = cos_sim(cursor);
        cursor = cursor + 1;
    end
end
  1. 打分結果矩陣

使用協同過濾算法得到預測的打分結果。
相似度矩陣X_sim與測試集X_test相乘,再點除對X_sim進行列求和的結果。
在這裏插入圖片描述
即:X_score = (X_sim*X_t)./sum(X_sim,2);

改進的加權平均
在該步驟的加權平均時,考慮到很多用戶對電影的打分時未知的,所以在加羣平均時,只計算對j電影有打分的用戶。改後公式:
在這裏插入圖片描述
轉化爲矩陣計算:
在這裏插入圖片描述
使用指標矩陣A_test作爲輔助,代碼實現

X_score = (X_sim*X_test)./(X_sim*A_test);
X_score = X_score.*(1-A_test) + X_test;
X_score(isnan(X_score))=0;

查看預測結果(前10行前10列):
在這裏插入圖片描述
4. 計算RMSE值

採用 RMSE(Root Mean Square Error,均方根誤差)作爲評價指標,計算公式爲:
在這裏插入圖片描述

其中 Test 爲所有測試樣本組成的集合,𝑋𝑖𝑗爲預測值,𝑋̃𝑖𝑗爲實際值。
在RMSE的計算過程中,同樣只關注於有評分的記錄,也就是隻計算在Train在有記錄的評分:
在這裏插入圖片描述

n = sum(sum(A_train==1));
RMSE = sqrt(sum(sum((X_score.*A_train-X_train).^2))/n);

輸出:RMSE= 1.0225

基於梯度下降的矩陣分解算法

  1. 給定 k,λ參數
    對於給定 k=50, λ = 0.01 的情況,畫出迭代過程中目標函數值和測試集上 RMSE 的變化,給出最終的 RMSE,並對結果進行簡單分析。
    對給定的行爲矩陣 X,將其分解爲 U,V 兩個矩陣的乘積,使 UV 的乘積 在已知值部分逼近 X,即:Xm∗n ≈ Um∗k 𝑉𝑛∗𝑘𝑇,它們的乘積矩陣可以用來預測
    X 的未知部分。
    使用梯度下降法優化求解這個問題。目標函數是:
    在這裏插入圖片描述

當目標函數取得最小值時,算法得到最優解。首先,我們對 U 和 V 分別求 偏導,結果如下:
在這裏插入圖片描述
之後,我們迭代對 U 和 V 進行梯度下降,具體算法如下:
在這裏插入圖片描述
取k = 50,λ = 0.01,α = 0.0001,隨機生成矩陣U和V。 代碼實現如下:

i = 1;
test_len = sum(sum((A_test)~=0)); 
times = 300; % 迭代次數上限
RMSE = zeros(times,1);
 
 
J = zeros(times,1);
J(i) = 0.5*(norm(A_train.*(X_train-U*V'),'fro'))^2 + lambda*(norm(U,'fro')^2+norm(V,'fro')^2);
RMSE(i)= sqrt(sum(sum((U*V'.*A_test-X_test).^2))/test_len); 
while i < times
    tic
    i = i+1;
    j = 0.5*(norm(A_train.*(X_train-U*V'),'fro'))^2 + lambda*(norm(U,'fro')^2+norm(V,'fro')^2);
    J(i) = j;
    if j > J(i-1) break;end
    U = U - alpha*( A_train.*(U*V'-X_train)*V + 2*lambda*U );
    V = V - alpha*( (A_train.*(U*V'-X_train))'*U + 2*lambda*V );
    RMSE(i)= sqrt(sum(sum((U*V'.*A_test-X_test).^2))/test_len);
    time_toc(i)=time_toc(i-1)+toc;
end
  

保存預測結果信息:

i = i - 1;
X_score = U*V';
iterations = i-1; save('X_md.mat','k','alpha','lambda','X_score','RMSE','J','iteratio ns');

指標量:
min RMSE = 0.91865,J = 2.8920e+06 迭代次數:137 次,耗時 727.826 秒

對於前 100 個電影的真實數據和預測數據的差異如圖
在這裏插入圖片描述
將每次迭代的目標函數值和測試集上的 RMSE 值 plot 出來,觀察算法收斂 過程中這兩個指標的變化。
在這裏插入圖片描述
可以看出在第 1 次到第 40 次的迭代過程中,J 值和 RMSE 值都迅速下降,但是在在迭代次數大於 40 之後,J 值和 RMSE 下降都不太明顯。
取不同的迭代次數作爲終止條件:
在這裏插入圖片描述
做 RMSE 和 time 隨迭代次數的變化曲線:
在這裏插入圖片描述

取 RMSE 和 time 曲線的交點,以 40 次迭代次數作爲算法的終止條件。

(2) 調整 k,λ參數
調整 k 的值(如 20,50)和 λ 的值(如 0.001,0.1),比較最終 RMSE 的效果,選取最優的參數組合。
在這裏插入圖片描述
當取 40 次迭代次數作爲算法的終止條件時,λ參數的取值對結果影響不顯 著,參數 k 的取值對結果有一定的影響。

對比

協同過濾算法相對矩陣分解算法,RMSE 值偏大,但是時耗相對較小。當有
新的用戶信息或電影信息加入時,協同過濾算法只用進行相對少量的計算就可以。
矩陣分解算法,將高維 User-Item 評分矩陣映射爲兩個低維用戶和物品矩陣, 從而降低了矩陣的維度,且可以得到用戶的喜好傾向,以及電影的特性 矩陣分 解方法,解決了數據稀疏性問題,且方便在用戶特徵向量和物品特徵向量中添加
其它影響因素。

兩算法都具有冷啓動的問題,無法給新用戶進行評分預測,也就無法給新用 戶進行電影推薦。

數據和代碼資源:
在我的資源裏面可以下載運行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章