推荐系统之矩阵分解及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数据的头文件
#ifndef READANDWRITEDATA_H
#define READANDWRITEDATA_H
#include <iostream>
#include <fstream>
#include <vector>
#include <string> using namespace std; template <typename T>
vector<vector<T> > txtRead(string FilePath,int row,int col)
{
ifstream input(FilePath);
if (!input.is_open())
{
cerr << "File is not existing, check the path: \n" << FilePath << endl;
exit();
}
vector<vector<T> > data(row, vector<T>(col,));
for (int i = ; i < row; ++i)
{
for (int j = ; j < col; ++j)
{
input >> data[i][j];
}
}
return data;
} template<typename T>
void txtWrite(vector<vector<T> > Matrix, string dest)
{
ofstream output(dest);
vector<vector<T> >::size_type row = Matrix.size();
vector<T>::size_type col = Matrix[].size();
for (vector<vector<T> >::size_type i = ; i < row; ++i)
{
for (vector<T>::size_type j = ; j < col; ++j)
{
output << Matrix[i][j];
}
output << endl;
}
}
#endif
评价函数,这里还是采用RMSE来评价
#ifndef EVALUATE_H
#define EVALUATE_H
#include <cmath>
#include <vector>
using namespace std;
double ComputeRMSE(vector<vector<double> > predict, vector<vector<double> > test)
{
int Counter = ;
double sum = ;
for (vector<vector<double> >::size_type i = ; i < test.size(); ++i)
{
for (vector<double>::size_type j = ; j < test[].size(); ++j)
{
if (predict[i][j] && test[i][j])
{
++Counter;
sum += pow((test[i][j] - predict[i][j]), );
}
}
}
return sqrt(sum / Counter);
} #endif
最后是主程序
#include "Evaluate.h"
#include "ReadAndWriteData.h" #include <cmath>
#include <algorithm>
#include <vector>
#include <iostream> using namespace std; double InnerProduct(vector<double> A, vector<double> B) //计算两个向量的内积
{
double res = ;
for(vector<double>::size_type i = ; i < A.size(); ++i)
{
res += A[i] * B[i];
}
return res;
} template<typename T> //对矩阵(二维数组)进行转置操作
vector<vector<T> > Transpose(vector<vector<T> > Matrix)
{
unsigned row = Matrix.size();
unsigned col = Matrix[].size();
vector<vector<T> > Trans(col,vector<T>(row,));
for (unsigned i = ; i < col; ++i)
{
for (unsigned j = ; j < row; ++j)
{
Trans[i][j] = Matrix[j][i];
}
}
return Trans;
} vector<vector<double> > BiasedMF(vector<vector<double> > train, double lr, double penalty,
int maxItr)
{
unsigned row = train.size();
unsigned col = train[].size();
//计算全局平均分
double avg = ;
int Counter = ;
for (unsigned i = ; i < row; ++i)
{
for(unsigned j = ; j < col; ++j)
{
if (train[i][j])
{
avg += train[i][j];
++Counter;
}
}
}
avg /= Counter;
//初始化Items偏置
vector<double> ItemsBias(col,);
vector<vector<double> > Transtrain = Transpose(train);
for (unsigned i = ; i < col; ++i)
{
int Counter = ;
double sum = ;
for (unsigned j = ; j < row; ++j)
{
if (Transtrain[i][j])
{
sum += Transtrain[i][j] - avg;
++Counter;
} }
ItemsBias[i] = sum / ( + Counter);
} //初始化Users偏置
vector<double> UsersBias(row, );
for (unsigned i = ; i < row; ++i)
{
int Counter = ;
double sum = ;
for (unsigned j = ; j < col; ++j)
{
if (train[i][j])
{
sum += train[i][j] - avg - ItemsBias[j];
++Counter;
}
}
UsersBias[i] = sum / ( + Counter);
} //初始化Users和Items对应的矩阵
unsigned k = ;
vector<vector<double> > predict(row,vector<double>(col, ));
vector<vector<double> > Users(row, vector<double>(k, ));
vector<vector<double> > Items(col, vector<double>(k, )); //梯度下降迭代
double rmse = ;
int it = ;
while(it < maxItr)
{
for (unsigned i = ; i < row; ++i)
{
for (unsigned j = ; j < col; ++j)
{
predict[i][j] = InnerProduct(Users[i],Items[j]) + UsersBias[i]
+ ItemsBias[j];
}
}
double new_rmse = ComputeRMSE(predict, train);
if (new_rmse < rmse)
rmse = new_rmse;
cout << "第 "<< it << "次迭代:" << endl;
cout << "rmse is: " << rmse << endl;
for (unsigned i = ; i < row; ++i)
{
for (unsigned j = ; j < col; ++j)
{
if (train[i][j])
{
double err = train[i][j] - predict[i][j];
//更新User i 和Item j 的因子向量
for (unsigned t = ; t < k; ++t)
{
double tmp = Users[i][t];
Users[i][t] += lr *(err * Items[j][t] - penalty * Users[i][t]);
Items[j][t] += lr * (err * tmp - penalty * Items[j][t]);
}
//更新User i和Item j的偏差
double tmp = UsersBias[i] + ItemsBias[j] - avg;
UsersBias[i] += lr * (err - penalty * tmp);
ItemsBias[j] += lr * (err - penalty * tmp);
}
}
}
++it;
}
return predict;
} int main()
{
string FilePath1("E:\\Matlab code\\recommendation system\\data\\movielens\\train.txt");
string FilePath2("E:\\Matlab code\\recommendation system\\data\\movielens\\test.txt"); int row = ;
int col = ;
vector<vector<double> > train = txtRead<double>(FilePath1, row, col);
vector<vector<double> > predict = BiasedMF(train, 0.001, 0.003,);
txtWrite(predict, "predict.txt");
vector<vector<double> > test = txtRead<double>(FilePath2, , );
double rmse = ComputeRMSE(predict,test);
cout << "ProbeRMSE is " << rmse <<endl;
return ;
}
4.运行
下面是运行过程中的截图,可以看出运行过程中RMSE逐渐减小,表示与真实的历史评分矩阵差别在减小,由于时间关系没有运行完,根据以前在Matlab上的运行结果,最终的RMSE应该可以达到0.92左右,当然这只是在训练集上的RMSE,最终效果要测出在测试集上的RMSE, 要比上一篇讲到的基于用户的协同过滤好一些,关于用户和Items因子向量的初始化会对结果有一定影响,本文中只是全部初始化为0其实不太好,有兴趣的读者可以自己尝试其他分布函数来初始化,但是总体上不会有什么太大的影响,有什么问题可以联系我。


推荐系统之矩阵分解及C++实现的更多相关文章
- 【RS】Matrix Factorization Techniques for Recommender Systems - 推荐系统的矩阵分解技术
[论文标题]Matrix Factorization Techniques for Recommender Systems(2009,Published by the IEEE Computer So ...
- 推荐系统之矩阵分解及其Python代码实现
有如下R(5,4)的打分矩阵:(“-”表示用户没有打分) 其中打分矩阵R(n,m)是n行和m列,n表示user个数,m行表示item个数 那么,如何根据目前的矩阵R(5,4)如何对未打分的商品进行评分 ...
- 推荐系统之矩阵分解(MF)
一.矩阵分解 1.案例 我们都熟知在一些软件中常常有评分系统,但并不是所有的用户user人都会对项目item进行评分,因此评分系统所收集到的用户评分信息必然是不完整的矩阵.那如何跟据这个不完整矩阵中已 ...
- SVD++:推荐系统的基于矩阵分解的协同过滤算法的提高
1.背景知识 在讲SVD++之前,我还是想先回到基于物品相似的协同过滤算法.这个算法基本思想是找出一个用户有过正反馈的物品的相似的物品来给其作为推荐.其公式为:
- 推荐系统实践 0x0b 矩阵分解
前言 推荐系统实践那本书基本上就更新到上一篇了,之后的内容会把各个算法拿来当专题进行讲解.在这一篇,我们将会介绍矩阵分解这一方法.一般来说,协同过滤算法(基于用户.基于物品)会有一个比较严重的问题,那 ...
- 矩阵分解(Matrix Factorization)与推荐系统
转自:http://www.tuicool.com/articles/RV3m6n 对于矩阵分解的梯度下降推导参考如下:
- 推荐系统(recommender systems):预测电影评分--构造推荐系统的一种方法:低秩矩阵分解(low rank matrix factorization)
如上图中的predicted ratings矩阵可以分解成X与ΘT的乘积,这个叫做低秩矩阵分解. 我们先学习出product的特征参数向量,在实际应用中这些学习出来的参数向量可能比较难以理解,也很难可 ...
- 用Spark学习矩阵分解推荐算法
在矩阵分解在协同过滤推荐算法中的应用中,我们对矩阵分解在推荐算法中的应用原理做了总结,这里我们就从实践的角度来用Spark学习矩阵分解推荐算法. 1. Spark推荐算法概述 在Spark MLlib ...
- 简单的基于矩阵分解的推荐算法-PMF, NMF
介绍: 推荐系统中最为主流与经典的技术之一是协同过滤技术(Collaborative Filtering),它是基于这样的假设:用户如果在过去对某些项目产生过兴趣,那么将来他很可能依然对其保持热忱.其 ...
随机推荐
- day10 局部变量 全局变量 作用域前奏
规则命名以及基本介绍 name="LHF" # 顶头写的全局都可以调用的就是全局变量,命名规则要求大写全局变量 def chang_name(): # global name # ...
- 【CF809E】Surprise me!(动态规划,虚树,莫比乌斯反演)
[CF809E]Surprise me!(动态规划,虚树,莫比乌斯反演) 题面 洛谷 CodeForces 翻译: 给定一棵\(n\)个节点的树,每个点有一个权值\(a[i]\),保证\(a[i]\) ...
- [luogu1912][bzoj4196][NOI2015]软件管理器
题解 树剖模板题,每次改变是\(1\)或者是\(0\),区间求和和区间修改就可了. ac代码 # include <cstdio> # include <cstring> # ...
- LOJ [#115. 无源汇有上下界可行流](https://loj.ac/problem/115)
#115. 无源汇有上下界可行流 先扔个板子,上下界的东西一点点搞,写在奇怪的合集里面 Code: #include <cstdio> #include <cstring> # ...
- helm详解
helm 可以理解为 Kubernetes 的包管理工具,可以方便地发现.共享和使用为Kubernetes构建的应用. 一.基本概念1.Helm的三个基本概念Chart:Helm应用(package) ...
- 使用android快速开发框架afinal的FinalDb操作android数据库
http://my.oschina.net/yangfuhai/blog/87459 今天给大家介绍下#afinal#来操作android的数据库sqlite. #afinal#是一个android的 ...
- 【UVA10140】Prime Distance
题目大意:求出一个给定区间 [l, r] 内相邻素数之间的最大距离和最小距离. 题解:由于 l, r 的范围太大,没法直接用筛法得出区间的素数.考虑筛出区间的素数等价于筛掉区间内的所有和数, 根据算术 ...
- gcc-linaro-arm-linux-gnueabihf交叉编译器配置
系统Ubuntu14.04 版本:gcc 版本 4.7.3 20130328 (prerelease) (crosstool-NG linaro-1.13.1-4.7-2013.04-20130415 ...
- NOIP 普及组 2014 螺旋矩阵
传送门 https://www.cnblogs.com/violet-acmer/p/9898636.html 题解: 这道题挺有意思的,有点考思维吧. 大体思路是用四个pair<int ,in ...
- 目前最全的IT技术问答、社区、科技服务网站合集
资源网站 推荐一个资源丰富齐全的网站:风云社区(SCOEE),主要特点是提供的是纯净.优质.无广告.无附加东西的资源.资源很丰富,包括各类软件资源(mac.Windows.ios.ipad.安装等软件 ...