- 這是本人在cousera上學習機器學習的筆記,不能保證其正確性,謹慎參考
- 看完這幾課後
collaborative-filtering
collaborative-filtering-algorithm
vectorization-low-rank-matrix-factorization
同時參考下面的這篇文章:
SVD在推薦系統中的應用詳解以及算法推導
後自己用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~~