1. 程式人生 > >基於矩陣分解的推薦演算法(java程式碼實現)

基於矩陣分解的推薦演算法(java程式碼實現)

目前推薦系統中用的最多的就是矩陣分解方法,在Netflix Prize推薦系統大賽中取得突出效果。以使用者-專案評分矩陣為例,矩陣分解就是預測出評分矩陣中的缺失值,然後根據預測值以某種方式向用戶推薦。常見的矩陣分解方法有基本矩陣分解(basic MF),正則化矩陣分解)(Regularized MF),基於概率的矩陣分解(PMF)等。今天以“使用者-專案評分矩陣R(N×M)”說明三種分解方式的原理以及應用。
這裡寫圖片描述
使用者-專案評分矩陣
Basic MF:
Basic MF是最基礎的分解方式,將評分矩陣R分解為使用者矩陣U和專案矩陣S, 通過不斷的迭代訓練使得U和S的乘積越來越接近真實矩陣,矩陣分解過程如圖:
矩陣分解過程
這裡寫圖片描述


預測值接近真實值就是使其差最小,這是我們的目標函式,然後採用梯度下降的方式迭代計算U和S,它們收斂時就是分解出來的矩陣。我們用損失函式來表示誤差(等價於目標函式):
損失函式 公式1
這裡寫圖片描述
公式1中R_ij是評分矩陣中已打分的值,U_i和S_j相當於未知變數。為求得公式1的最小值,相當於求關於U和S二元函式的最小值(極小值或許更貼切)。通常採用梯度下降的方法:
梯度下降
這裡寫圖片描述
學習速率是學習速率,表示迭代的步長。其值為1.5時,通常以震盪形式接近極值點;若<1迭代單調趨向極值點;若>2圍繞極值逐漸發散,不會收斂到極值點。具體取什麼值要根據實驗經驗。

Regularized MF

正則化矩陣分解是Basic MF的優化,解決MF造成的過擬合問題。其不是直接最小化損失函式,而是在損失函式基礎上增加規範化因子,將整體作為損失函式。
這裡寫圖片描述

紅線表示正則化因子,在求解U和S時,仍然採用梯度下降法,此時迭代公式變為:(圖片擷取自相關論文,S和V等價)
梯度下降
這裡寫圖片描述
梯度下降結束條件:f(x)的真實值和預測值小於自己設定的閾值(很小的值,之前一直理解為是變數U和V的迭代值差小於閾值就行)

Java程式碼實現矩陣分解

public class Test {
     private static int N=5;//使用者的數目
     private static
int M=4;//產品的數目 private static int K=2;//特徵的數目 private static DecimalFormat df=new DecimalFormat("###.000"); public static void main(String[] args) { double[] R=new double[N*M]; double[] P=new double[N*K]; double[] Q=new double[N*M]; R[0]=5; R[1]=3; R[2]=0; R[3]=1; R[4]=4; R[5]=0; R[6]=0; R[7]=1; R[8]=1; R[9]=1; R[10]=0; R[11]=5; R[12]=1; R[13]=0; R[14]=0; R[15]=4; R[16]=0; R[17]=1; R[18]=5; R[19]=4; System.out.println("R矩陣"); for(int i=0;i<N;++i) { for(int j=0;j<M;++j){ System.out.print(R[i*M+j]+","); } System.out.println(); } //初始化P,Q矩陣,這裡簡化了,通常也可以對服從正態分佈的資料進行隨機數生成 for(int i=0;i<N;++i) { for(int j=0;j<K;++j) { P[i*K+j]=Math.random()%9; } } for(int i=0;i<K;++i) { for(int j=0;j<M;++j) { Q[i*M+j]=Math.random()%9; } } System.out.println("矩陣分解開始"); matrix_factorization(R,P,Q,N,M,K); System.out.println("重構出來的R矩陣"); for(int i=0;i<N;++i) { for(int j=0;j<M;++j) { double temp=0; for (int k=0;k<K;++k){ temp+=P[i*K+k]*Q[k*M+j]; } System.out.print(df.format(temp)+","); } System.out.println(); } } public static void matrix_factorization(double[] R,double[] P,double[] Q,int N,int M,int K){ int steps=5000; double alpha=0.0002; double beta=0.02; for(int step =0;step<steps;++step){ for(int i=0;i<N;++i) { for(int j=0;j<M;++j){ if(R[i*M+j]>0){ double eij = R[i*M+j]; for(int k=0;k<K;++k){ eij -= P[i*K+k]*Q[k*M+j]; } for(int k=0;k<K;++k){ P[i*K+k] +=alpha * (2 * eij * Q[k*M+j] - beta * P[i*K+k]); Q[k*M+j] +=alpha * (2 * eij * P[i*K+k] - beta * Q[k*M+j]); } } } } double loss=0; for(int i=0;i<N;++i){ for(int j=0;j<M;++j) { if(R[i*M+j]>0){ double eij =0; for(int k=0;k<K;++k){ eij += P[i*K+k]*Q[k*M+j]; } loss += Math.pow(R[i*M+j]-eij,2); for(int k=0;k<K;++k){ loss += (beta/2) * (Math.pow(P[i*K+k],2) + Math.pow(Q[k*M+j],2)); } } } } if(loss<0.001){ break; } if (step%1000==0){ System.out.println("loss:"+loss); } } } }