1.引言

假如你经营着一家网店,里面卖各种商品(Items),有很多用户在你的店里面买过东西,并对买过的Items进行了评分,我们称之为历史信息,现在为了提高销售量,必须主动向用户推销产品,所以关键是要判断出用户除了已经买过的商品之外还会喜欢哪些商品,这就需要利用用户购买商品过程产生的历史信息。协同过滤通常分为基于用户的协同过滤和基于商品的协同过滤。

  • 基于用户的协同过滤:利用用户之间的相似度进行推荐
  • 基于物品的协同过滤:利用物品之间的相似度进行推荐

2.原理

关于协同过滤的原理网上到处都有,思想很简单,这里就不赘述,下面举一个简单的实例来说明基于用户的协同过滤:

上面每一行代表一个用户,每一列代表一个商品,比如第2行第一列的3表示用户2对商品1的评分为3,0代表对应的用户还没有购买过该商品,现在想预测用户2对商品4的评分:

  • 找出对商品4评过分的用户:用户1,3,5,8,9,10,评分分别为:4, 2, 1, 3, 3, 1
  • 分别计算用户2与用户1,3,5,8,9,10之间的相似度,相似度的计算方法有很多,常用的分为3类:欧氏距离,余弦相似度,皮尔逊相关系数,网上很容易查到,这里以常用的余弦相关系数说明:

     要计算用户2与用户1之间的相似度,首先找到二者都评过分的商品为:商品1, 2, 9, 10,用户1对这4个商品的评分向量为r1=[5 3 4 4],用户2对这4个商品评分向量为r2=[3 1 1 2];所谓余弦相似度就是利用两个向量之间夹角的余弦值来衡量两个向量之间的相似度,显然夹角越小,余弦值就越大,两个向量就越靠近,即二者越相似,于是用户2和用户1之间的相似度就为sim2_1=(5*3 + 3*1 + 4*1 + 4*2)/ (||r1|| * ||r2||) = 0.953, 其中||r||代表向量r的模长或者2范数,类似地分别计算出用户2与用户3 5 8 9 10之间的sim2_3,sim2_5,sim2_8,sim2_9,sim2_10

  • 最后利用相似度加权得到用户2对商品4的预测评分:predict = 4*sim2_1 + 2*sim2_3 + 1*sim2_5 + 3*sim2_8 + 3*sim2_9 + 1*sim2_10
  • 基于物品相似度就是与上面计算过程几乎相似,只是计算的是物品之间的相似度

3.实现

关于Matlab的实现可以参考:http://blog.csdn.net/google19890102/article/details/28112091,这里我用C++实现,并用movielens.rar进行测试,这个数据集是包括训练集和测试集,已经处理成矩阵形式。

  • 首先给出读取训练数据和保存预测结果的头文件
  • #ifndef LOAD_H
    #define LOAD_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> 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 "load.h"
#include "evaluate.h"
#include <vector>
#include <string>
#include <cmath>
#include <assert.h>
using namespace std; double norm(vector<double> A)
{
double res = ;
for(vector<double>::size_type i = ; i < A.size(); ++i)
{
res += pow(A[i], );
}
return sqrt(res);
} 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;
} double ComputeSim(vector<double> A, vector<double> B, int method)
{
switch (method)
{
case ://欧氏距离
{
vector<double> C;
for(vector<double>::size_type i = ; i < A.size(); ++i)
{
C.push_back((A[i] - B[i]));
}
return / ( + norm(C));
break;
}
case ://皮尔逊相关系数
{
double A_mean = ;
double B_mean = ;
for(vector<double>::size_type i = ; i < A.size(); ++i)
{
A_mean += A[i];
B_mean += B[i];
}
A_mean /= A.size();
B_mean /= B.size();
vector<double> C(A);
vector<double> D(B);
for(vector<double>::size_type i = ; i < A.size(); ++i)
{
C[i] = A[i] - A_mean;
D[i] = B[i] - B_mean;
}
assert(norm(C) * norm(D));
return InnerProduct(C,D) / (norm(C) * norm(D));
break;
}
case :
{
assert(norm(A) * norm(B));
return InnerProduct(A,B) / (norm(A) * norm(B));
break;
}
default:
{
cout << " Choose method:" << endl;
cout << "0:欧氏距离\n1:皮尔逊相关系数\n2:余弦相似度\n";
return -;
}
} } void FindCommon(vector<double> A, vector<double> B, vector<double> &C, vector<double> &D)
{
for(vector<double>::size_type i = ; i < A.size(); ++i)
{
if (A[i] && B[i])
{
C.push_back(A[i]);
D.push_back(B[i]);
}
}
} vector<vector<double> > UserBasedCF(vector<vector<double> > train, int usersNum, int itemsNum)
{
vector<vector<double> > predict(usersNum, vector<double>(itemsNum, ));
for (int i = ; i < usersNum; ++i) //对每个用户进行预测
{
//找出user i未评分的item j,预测user i 对item j的评分
for (int j = ; j < itemsNum; ++j)
{ if (train[i][j])
continue;
//如果item j没有被user i评过分,找出对 item j评过分的用户
else
{
vector<double> sim;
vector<double> historyScores;
for (int k = ; k < usersNum; ++k)
{
//如果user k对item j 评过分,计算user k与user i的相似度 if (train[k][j])//找出对item j 评过分的user k
{
// 为了计算user k与user i的相似度,必须找出二者共同评过分的items
// 把二者对共同评过分的items的评分分别存储在两个vector中
vector<double> commonA,commonB;
FindCommon(train[i], train[k], commonA, commonB);
//如果二者存在共同评过分的items,计算相似度
if (!commonA.empty())
{
sim.push_back(ComputeSim(commonA, commonB, ));
// 把user k对item j 的历史评分记录下来
historyScores.push_back(train[k][j]);
}
} }
// 计算出所有与user i存在共同评过分的items的users与user i之间的相似度,
// 保存在sim中,这些users对目标items j(即user i没有评过分)的历史评分记
// 录在historyScores中。利用这两个vector,计算出相似度加权平均分作为预
// 测user i对item j的评分
double SimSum = ;
if (!sim.empty())
{
for(vector<double>::size_type m = ; m < sim.size(); ++m)
{
SimSum += sim[m];
}
predict[i][j] = InnerProduct(sim, historyScores) / (SimSum);
cout << "User "<< i << " 对第 " << j << " 个Item的评分为 " << predict[i][j] << endl;
}
}
}
}
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 = UserBasedCF(train, row, col);
txtWrite(predict, "predict.txt");
vector<vector<double> > test = txtRead<double>(FilePath2, , );
double rmse = ComputeRMSE(predict,test);
cout << "RMSE is " << rmse <<endl;
return ;
}

4.运行

由于程序没有优化,循环比较多,时间比较长,程序没写好,如果读者有兴趣帮我优化,请联系我,多谢,欢迎有兴趣的可以自己构造一个小点的数据集试一试,以前我用这个数据在Matlab中运行的RMSE是1左右,所以如果读者运行结果得到测试集上的RMSE是0.9-1.3之间问题应该不大,如果偏离太多,程序设计可能就有问题。

推荐系统之协同过滤的原理及C++实现的更多相关文章

  1. [Recommendation System] 推荐系统之协同过滤(CF)算法详解和实现

    1 集体智慧和协同过滤 1.1 什么是集体智慧(社会计算)? 集体智慧 (Collective Intelligence) 并不是 Web2.0 时代特有的,只是在 Web2.0 时代,大家在 Web ...

  2. 推荐系统入门:作为Rank系统的推荐系统(协同过滤)

    知乎:如何学习推荐系统? 知乎:协同过滤和基于内容的推荐有什么区别? 案例:推荐系统实战?  数据准备:实现推荐栏位:重构接口:后续优化. 简书:实现实时推荐系统的三种方式?基于聚类和协同过滤:基于S ...

  3. 推荐系统(协同过滤,slope one)

    1.推荐系统中的算法: 协同过滤: 基于用户 user-cf 基于内容 item –cf slop one 关联规则 (Apriori 算法,啤酒与尿布) 2.slope one 算法 slope o ...

  4. Music Recommendation System with User-based and Item-based Collaborative Filtering Technique(使用基于用户及基于物品的协同过滤技术的音乐推荐系统)【更新】

    摘要: 大数据催生了互联网,电子商务,也导致了信息过载.信息过载的问题可以由推荐系统来解决.推荐系统可以提供选择新产品(电影,音乐等)的建议.这篇论文介绍了一个音乐推荐系统,它会根据用户的历史行为和口 ...

  5. 协同过滤 CF & ALS 及在Spark上的实现

    使用Spark进行ALS编程的例子可以看:http://www.cnblogs.com/charlesblc/p/6165201.html ALS:alternating least squares ...

  6. CF(协同过滤算法)

    1 集体智慧和协同过滤 1.1 什么是集体智慧(社会计算)? 集体智慧 (Collective Intelligence) 并不是 Web2.0 时代特有的,只是在 Web2.0 时代,大家在 Web ...

  7. 协同过滤(CF)算法

    1 集体智慧和协同过滤 1.1 什么是集体智慧(社会计算)? 集体智慧 (Collective Intelligence) 并不是 Web2.0 时代特有的,只是在 Web2.0 时代,大家在 Web ...

  8. RS:关于协同过滤,矩阵分解,LFM隐语义模型三者的区别

    项亮老师在其所著的<推荐系统实战>中写道: 第2章 利用用户行为数据 2.2.2 用户活跃度和物品流行度的关系 [仅仅基于用户行为数据设计的推荐算法一般称为协同过滤算法.学术界对协同过滤算 ...

  9. 1分钟了解协同过滤,pm都懂了

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/z50L2O08e2u4afToR9A/article/details/79565720 projec ...

随机推荐

  1. java监控工具VisualVM

    java监控工具VisualVM https://visualvm.github.io/ https://visualvm.github.io/documentation.html https://h ...

  2. 【 Gym - 101138D 】Strange Queries (莫队算法)

    BUPT2017 wintertraining(15) #4B Gym - 101138D 题意 a数组大小为n.(1 ≤ n ≤ 50 000) (1 ≤ q ≤ 50 000)(1 ≤ ai ≤  ...

  3. 洛谷CF868F Yet Another Minimization Problem(动态规划,决策单调性,分治)

    洛谷题目传送门 貌似做所有的DP题都要先搞出暴力式子,再往正解上靠... 设\(f_{i,j}\)为前\(i\)个数分\(j\)段的最小花费,\(w_{l,r}\)为\([l,r]\)全在一段的费用. ...

  4. 【BZOJ4830】[HNOI2017]抛硬币(组合计数,拓展卢卡斯定理)

    [BZOJ4830][HNOI2017]抛硬币(组合计数,拓展卢卡斯定理) 题面 BZOJ 洛谷 题解 暴力是啥? 枚举\(A\)的次数和\(B\)的次数,然后直接组合数算就好了:\(\display ...

  5. 洛谷 P1078 文化之旅 解题报告

    P1078 文化之旅 题目描述 有一位使者要游历各国,他每到一个国家,都能学到一种文化,但他不愿意学习任何一种文化超过一次(即如果他学习了某种文化,则他就不能到达其他有这种文化的国家).不同的国家可能 ...

  6. HDU 1069 Monkey and Banana / ZOJ 1093 Monkey and Banana (最长路径)

    HDU 1069 Monkey and Banana / ZOJ 1093 Monkey and Banana (最长路径) Description A group of researchers ar ...

  7. 【洛谷P5018】对称二叉树

    题目大意:定义对称二叉树为每个节点的左右子树交换后与原二叉树仍同构的二叉树,求给定的二叉树的最大对称二叉子树的大小. 代码如下 #include <bits/stdc++.h> using ...

  8. 关于setvbuf()函数的详解

    为什么要使用setvbuf函数 如果你的内存足够大,可以把文件IO的BUF设置大一些,这样每次你用fopen/fread/fwrite/fscanf/fprintf语句的时候,都会在内存里操作,减少内 ...

  9. kindeditor上传图片的大小在哪控制

    请修改修改了multiimage.js 的imageSizeLimit = K.undef(self.imageSizeLimit, '3MB') 大小设置级可以

  10. Python常用内置模块之xml模块

    xml即可扩展标记语言,它可以用来标记数据.定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言.从结构上,很像HTML超文本标记语言.但他们被设计的目的是不同的,超文本标记语言被设计用来显示 ...