zoukankan      html  css  js  c++  java
  • 推荐系统之矩阵分解及C++实现

    1.引言

    矩阵分解(Matrix Factorization, MF)是传统推荐系统最为经典的算法,思想来源于数学中的奇异值分解(SVD), 但是与SVD 还是有些不同,形式就可以看出SVD将原始的评分矩阵分解为3个矩阵,而推荐本文要介绍的MF是直接将一个矩阵分解为两个矩阵,一个包含Users 的因子向量,另一个包含着Items 的因子向量。

    2.原理简介

    假如电影分为三类:动画片,武打片,纪录片,而某一部电影对应这三类的隶属度分别为 0, 0.2, 0.7,可以看出这是一部纪录片里面有些武打成分,现在给定某个用户对着三类电影的喜欢程度用0 到1 之间的值表示分别为 0.1,0.6,0.2, 可以看出该用户最喜欢武打片,而不怎么喜欢其他两种,于是可以预测用户对刚才的电影打分(喜欢程度)为:0*0.1+0.2*0.6+0.7*0.2 = 0.26

    矩阵分解的动机来源于此,因为利用用户的历史评分矩阵(参考我的上一篇推荐系统之协同过滤的原理及C++实现),如果能够得到反映每一用户的对每个Item喜好的因子向量,同时得到每个Item 属于每一类的隶属度向量,利用上面的方法就很容易得出每个用户对每个Item的预测评分,利用这个评分的高低就可以进行推荐高分的Items给相应的用户了.

    例如这个10*10的历史评分矩阵A, 可以分解为一个10 * 5 的矩阵 B 乘以一个5 * 10 的矩阵 C ,这样可以把 B 看做是用户偏好矩阵,里面包含着用户对每一类Items 的偏好程度的向量,B 的转置看作是包含着衡量每一个Item 属于5类的隶属度的向量,当然这个 5 可以是自己设定的任意值,但是原则上要求要比原来的矩阵A中的列数或者行数小 ,起到一个降维的作用。B 和 C的初始值可以随机初始化,然后B和C相乘得到评分,与历史真实评分对比,通过梯度下降算法不断调整B和C中的值,使得B和C相乘后得到的矩阵与真实的历史评分矩阵之间的差别越小越好,最终得到较好的B 和C 就可以用来预测用户对任意Item的评分了,更加详细的解释参考:Matrix_factorization_techniques_for_recommender_systems.pdf

    3.实现

    本次实现的是一个带偏置的矩阵分解,数据集是movielens.rar,已经处理成了矩阵形式

    读取和保存txt数据的头文件

     1 #ifndef READANDWRITEDATA_H
     2 #define READANDWRITEDATA_H
     3 #include <iostream>
     4 #include <fstream>
     5 #include <vector>
     6 #include <string>
     7 
     8 using namespace std;
     9 
    10 template <typename T>
    11 vector<vector<T> > txtRead(string FilePath,int row,int col)
    12 {
    13     ifstream input(FilePath);
    14     if (!input.is_open())
    15     {
    16         cerr << "File is not existing, check the path: 
    " <<  FilePath << endl;
    17         exit(1);
    18     }
    19     vector<vector<T> > data(row, vector<T>(col,0)); 
    20     for (int i = 0; i < row; ++i)
    21     {
    22         for (int j = 0; j < col; ++j)
    23         {
    24             input >> data[i][j];
    25         }
    26     }
    27     return data;
    28 }
    29 
    30 template<typename T>
    31 void txtWrite(vector<vector<T> > Matrix, string dest)
    32 {
    33     ofstream output(dest);
    34     vector<vector<T> >::size_type row = Matrix.size();
    35     vector<T>::size_type col = Matrix[0].size();
    36     for (vector<vector<T> >::size_type i = 0; i < row; ++i)
    37     {
    38         for (vector<T>::size_type j = 0; j < col; ++j)
    39         {
    40             output << Matrix[i][j];
    41         }
    42         output << endl;
    43     }
    44 }
    45 #endif

    评价函数,这里还是采用RMSE来评价

     1 #ifndef EVALUATE_H
     2 #define EVALUATE_H
     3 #include <cmath>
     4 #include <vector>
     5 using namespace std;
     6 double ComputeRMSE(vector<vector<double> > predict, vector<vector<double> > test)
     7 {
     8     int Counter = 0;
     9     double sum = 0;
    10     for (vector<vector<double> >::size_type i = 0; i < test.size(); ++i)
    11     {
    12         for (vector<double>::size_type j = 0; j < test[0].size(); ++j)
    13         {
    14             if (predict[i][j] && test[i][j])
    15             {
    16                 ++Counter;
    17                 sum += pow((test[i][j] - predict[i][j]), 2);
    18             }
    19         }
    20     }
    21     return sqrt(sum / Counter);
    22 }
    23 
    24 #endif

    最后是主程序

      1 #include "Evaluate.h"
      2 #include "ReadAndWriteData.h"
      3 
      4 #include <cmath>
      5 #include <algorithm>
      6 #include <vector>
      7 #include <iostream>
      8 
      9 using namespace std;
     10 
     11 
     12 double InnerProduct(vector<double> A, vector<double> B) //计算两个向量的内积
     13 {
     14     double res = 0;
     15     for(vector<double>::size_type i = 0; i < A.size(); ++i)
     16     {
     17         res += A[i] * B[i];
     18     }
     19     return res;
     20 }
     21 
     22 template<typename T> //对矩阵(二维数组)进行转置操作
     23 vector<vector<T> > Transpose(vector<vector<T> > Matrix)
     24 {
     25     unsigned row = Matrix.size();
     26     unsigned col = Matrix[0].size();
     27     vector<vector<T> > Trans(col,vector<T>(row,0));
     28     for (unsigned i = 0; i < col; ++i)
     29     {
     30         for (unsigned j = 0; j < row; ++j)
     31         {
     32             Trans[i][j] = Matrix[j][i];
     33         }
     34     }
     35     return Trans;
     36 }
     37 
     38 vector<vector<double> > BiasedMF(vector<vector<double> >  train, double lr, double penalty,
     39     int maxItr)
     40 {
     41     unsigned row = train.size();
     42     unsigned col = train[0].size();
     43     //计算全局平均分
     44     double avg = 0;
     45     int Counter = 0;
     46     for (unsigned i = 0; i < row; ++i)
     47     {
     48         for(unsigned j = 0; j < col; ++j)
     49         {
     50             if (train[i][j])
     51             {
     52                 avg += train[i][j];
     53                 ++Counter;
     54             }
     55         }
     56     }
     57     avg /= Counter;
     58     //初始化Items偏置
     59     vector<double> ItemsBias(col,0);
     60     vector<vector<double> > Transtrain = Transpose(train);
     61     for (unsigned i = 0; i < col; ++i)
     62     {
     63         int Counter = 0;
     64         double sum  = 0;
     65         for (unsigned j = 0; j < row; ++j)
     66         {
     67             if (Transtrain[i][j])
     68             {
     69                 sum +=  Transtrain[i][j] - avg;
     70                 ++Counter;
     71             }
     72                 
     73         }
     74         ItemsBias[i] = sum / (25 + Counter);
     75     }
     76 
     77     //初始化Users偏置
     78     vector<double> UsersBias(row, 0);
     79     for (unsigned i = 0; i < row; ++i)
     80     {
     81         int Counter = 0;
     82         double sum  = 0;
     83         for (unsigned j = 0; j < col; ++j)
     84         {
     85             if (train[i][j])
     86             {
     87                 sum +=  train[i][j] - avg - ItemsBias[j];
     88                 ++Counter;
     89             }
     90         }
     91         UsersBias[i] = sum / (10 + Counter);
     92     }
     93 
     94     //初始化Users和Items对应的矩阵
     95     unsigned k = 10;
     96     vector<vector<double> > predict(row,vector<double>(col, 0));
     97     vector<vector<double> > Users(row, vector<double>(k, 0));
     98     vector<vector<double> > Items(col, vector<double>(k, 0));
     99 
    100 
    101     //梯度下降迭代
    102     double rmse = 100;
    103     int it = 0;
    104     while(it < maxItr)
    105     {
    106         for (unsigned i = 0; i < row; ++i)
    107         {
    108             for (unsigned j = 0; j < col; ++j)
    109             {
    110                 predict[i][j] = InnerProduct(Users[i],Items[j]) + UsersBias[i]
    111                             + ItemsBias[j];
    112             }
    113         }
    114         double new_rmse = ComputeRMSE(predict, train);
    115         if (new_rmse < rmse)
    116             rmse = new_rmse;
    117         cout << ""<< it << "次迭代:" << endl;
    118         cout << "rmse is: " << rmse << endl;
    119         for (unsigned i = 0; i < row; ++i)
    120         {
    121             for (unsigned j = 0; j < col; ++j)
    122             {
    123                 if (train[i][j])
    124                 {
    125                     double err = train[i][j] - predict[i][j];
    126                     //更新User i 和Item j 的因子向量
    127                     for (unsigned t = 0; t < k; ++t)
    128                     {
    129                         double tmp = Users[i][t];
    130                         Users[i][t] += lr *(err * Items[j][t] - penalty * Users[i][t]);
    131                         Items[j][t] += lr * (err * tmp - penalty * Items[j][t]);
    132                     }
    133                     //更新User i和Item j的偏差
    134                     double tmp =  UsersBias[i] + ItemsBias[j] - avg;
    135                     UsersBias[i] += lr * (err - penalty * tmp);
    136                     ItemsBias[j] += lr * (err - penalty * tmp);    
    137                 }
    138             }
    139         }
    140         ++it;
    141     }
    142     return predict;
    143 }
    144 
    145 int main()
    146 {
    147   
    148 string FilePath1("E:\Matlab code\recommendation system\data\movielens\train.txt"); 149 string FilePath2("E:\Matlab code\recommendation system\data\movielens\test.txt"); 150 151 int row = 943; 152 int col = 1682; 153 vector<vector<double> > train = txtRead<double>(FilePath1, row, col); 154 vector<vector<double> > predict = BiasedMF(train, 0.001, 0.003,100); 155 txtWrite(predict, "predict.txt"); 156 vector<vector<double> > test = txtRead<double>(FilePath2, 462, 1591); 157 double rmse = ComputeRMSE(predict,test); 158 cout << "ProbeRMSE is " << rmse <<endl; 159 return 0; 160 }

    4.运行

    下面是运行过程中的截图,可以看出运行过程中RMSE逐渐减小,表示与真实的历史评分矩阵差别在减小,由于时间关系没有运行完,根据以前在Matlab上的运行结果,最终的RMSE应该可以达到0.92左右,当然这只是在训练集上的RMSE,最终效果要测出在测试集上的RMSE, 要比上一篇讲到的基于用户的协同过滤好一些,关于用户和Items因子向量的初始化会对结果有一定影响,本文中只是全部初始化为0其实不太好,有兴趣的读者可以自己尝试其他分布函数来初始化,但是总体上不会有什么太大的影响,有什么问题可以联系我。

  • 相关阅读:
    P6Spy 监控JDBC详细配置说明
    apache mina2.0核心架构
    Window.open
    FLEX做的网站
    采用CSS让DIV在网页中水平垂直居中
    VSFTP成功安装访问被拒绝问题 500 OOPS: cannot change directory:/home/ftp
    SvnServe(1.7)服务器配置
    运行 Flash builder 4.5 弹出对话框 failed to create the java virtual machine 解决方法
    c++中的隐藏、重载、覆盖(重写)
    golang/net/http
  • 原文地址:https://www.cnblogs.com/90zeng/p/matrix_factorization.html
Copyright © 2011-2022 走看看