協同過濾-矩陣分解推薦 java實現

1、下圖是待處理的數據,代碼使用數據和下圖一樣:
這裏寫圖片描述

2、思路:將一個“movie-user的評分矩陣”按照下面兩圖所示分解成兩個矩陣theta和x,我們可以將其對應的理解爲”user-特徵”和”movie-特徵”兩個矩陣(特徵類似上一篇文章中romance,action),n爲“特徵”的數目。爲什麼“movie-user的評分矩陣”可以分解,以及分解的n的大小如何確定我目前還沒搞懂,在代碼中,直接初始化”user-特徵”和”movie-特徵”兩個矩陣,然後令n=2。然後採用梯度下降法不斷的對”user-特徵”和”movie-特徵”中的每一個參數進行更新。
這裏寫圖片描述

這裏寫圖片描述

3、根據下圖公式採用梯度下降法不斷的對”user-特徵”和”movie-特徵”中的每一個參數進行更新,並加上正則化的作用:
這裏寫圖片描述

4、對上圖的公式對”user-特徵”和”movie-特徵”矩陣中的每一個參數進行求偏導數,偏導數的公式如下圖:
這裏寫圖片描述

5、下面是使用java進行了代碼實現:


public class CollaborativeFilter {
    private static int[][] rate_set = { { 5, 5, 0, 0 }, { 5, -1, -1, 0 },
        { -1, 4, 0, -1 }, { 0, 0, 5, 4 }, { 0, 0, 5, -1 } };//5*4的評分矩陣,5個movie,4個user
    private static int m=5;//電影的數目
    private static int u=4;//用戶的數目
    private static int n=2;//特徵的數目
    private static double[][] x=new double[m][n];//電影-特徵 矩陣
    private static double[][] x_partial=new double[m][n];//電影-特徵 矩陣每個參數的偏導數
    private static double[][] theta=new double[u][n];//用戶-特徵 矩陣
    private static double[][] theta_partial=new double[u][n];//用戶-特徵 矩陣每個參數的偏導數
    private static double alpha=0.05;//偏導數學習步長
    private static double lambda=1.0;//正則化參數
    public static void main(String[] args){
        init();
        int i,j,k,times=0;
        //學習的次數
        while (times < 100) {
            show(times++);

            // 對x矩陣的參數x[i][k]求偏導數
            for (i = 0; i < m; i++)
                for (k = 0; k < n; k++) {
                    x_partial[i][k] = 0.0;
                    for (j = 0; j < u; j++)
                        if (rate_set[i][j] != -1) {
                            x_partial[i][k] += (getPredict(i, j) - rate_set[i][j])* theta[j][k];
                        }
                    x_partial[i][k] += lambda * x[i][k];
                }

            // 對theta矩陣的參數theta[j][k]求偏導數
            for (j = 0; j < u; j++)
                for (k = 0; k < n; k++) {
                    theta_partial[j][k] = 0.0;
                    for (i = 0; i < m; i++)
                        if (rate_set[i][j] != -1) {
                            theta_partial[j][k] += (getPredict(i, j) - rate_set[i][j])
                                    * x[i][k];
                        }
                    theta_partial[j][k] += lambda * theta[j][k];
                }

            // 更新 電影-特徵 矩陣x 
            for (i = 0; i < m; i++)
                for (k = 0; k < n; k++)
                    x[i][k] -= alpha * x_partial[i][k];
            // 更新  用戶-特徵 矩陣theta
            for (j = 0; j < u; j++)
                for (k = 0; k < n; k++)
                    theta[j][k] -= alpha * theta_partial[j][k];
        }
    }

    private static void show(int times) {
        int i,j,k;
        double min=0.0;
        System.out.println("第"+times+"次學習後, 電影-特徵 矩陣x 和 用戶-特徵 矩陣theta 爲:");
        for (i = 0; i < m; i++){
            for (k = 0; k < n; k++)
                if(k==0)
                    System.out.print(x[i][k]);
                else
                    System.out.println(","+x[i][k]);
        }
        System.out.println();
        for (j = 0; j < u; j++){
            for (k = 0; k < n; k++)
                if(k==0)
                    System.out.print(theta[j][k]);
                else
                    System.out.println(","+theta[j][k]);
        }

        min=getMin();
        System.out.println("時,代價函數的值爲"+min);
        System.out.println();
    }

    //求得代價函數的值
    private static double getMin() {
        double min=0.0,cost=0.0,x_regul=0.0,theta_regul=0.0;
        double pred;
        int i,j;
        for(i=0;i<m;i++){
            for(j=0;j<u;j++){
                if(rate_set[i][j]!=-1){
                    pred=getPredict(i,j);
                    cost+=(pred-rate_set[i][j])*(pred-rate_set[i][j]);
                }
            }
        }

        for(i=0;i<m;i++)
            for(j=0;j<n;j++)
                x_regul+=x[i][j]*x[i][j];

        for(i=0;i<u;i++)
            for(j=0;j<n;j++)
                theta_regul+=theta[i][j]*theta[i][j];

        min=cost+x_regul+theta_regul;
        return min;
    }
    //求得兩個向量的內積
    private static double getPredict(int i, int j) {
        double pre=0.0;
        for(int k=0;k<n;k++)
            pre+=x[i][k]*theta[j][k];
        return pre;
    }

    private static void init() {
        int i=0,j=0;
        for(i=0;i<m;i++)
            for(j=0;j<n;j++)
            //這裏的賦值是我手算驗證代碼時爲了方便而採用的數據,是隨便取的
                x[i][j]=1.0*(i%3+j%2-1);
            //  x[i][j]=Math.random();
        for(i=0;i<u;i++)
            for(j=0;j<n;j++)
                theta[i][j]=1.0*(-i%3+j%2+1);
            //  theta[i][j]=Math.random();
    }
}

6、運行結果:
這裏寫圖片描述

7、最後附上Coursera課程中老師講的思路:
這裏寫圖片描述

8、在上面的過程中我設置學習步長alpha=0.05,能得到正確的結果,但是原來我是設置alpha=0.5的,這導致了下圖的情況(代價函數隨着學習次數增加而增加),我還一度認爲是代碼寫錯了,後面手算髮現每個參數的偏微分相對參數值實在太大,進而縮小了alpha爲0.05,從而得到與預想相符的結果。以後繼續學習後,再看一下alpha是如何確定更爲科學。
這裏寫圖片描述
下圖是出現8中錯誤的原因:
這裏寫圖片描述
注:部分圖片來源爲 機器學習-吳恩達 中的視頻截圖
本人是菜鳥一個,歡迎大家指出我的錯誤,和我交流,開心O(∩_∩)O~~

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