基於矩陣分解的推薦演算法(java程式碼實現)
阿新 • • 發佈:2019-01-01
目前推薦系統中用的最多的就是矩陣分解方法,在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);
}
}
}
}