推荐系统之协同过滤的原理及C++实现

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 4 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(1);
        }
        vector<vector<T> > data(row, vector<T>(col,0));
        for (int i = 0; i < row; ++i)
        {
            for (int j = 0; 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[0].size();
        for (vector<vector<T> >::size_type i = 0; i < row; ++i)
        {
            for (vector<T>::size_type j = 0; j < col; ++j)
            {
                output << Matrix[i][j];
            }
            output << endl;
        }
    }
    #endif
  • 再给出评价预测好坏的计算RMSE的头文件
 1 #ifndef EVALUATE_H
 2 #define EVALUATE_H
 3 #include <cmath>
 4 #include <vector>
 5
 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 "load.h"
  2 #include "evaluate.h"
  3 #include <vector>
  4 #include <string>
  5 #include <cmath>
  6 #include <assert.h>
  7 using namespace std;
  8
  9 double norm(vector<double> A)
 10 {
 11     double res = 0;
 12     for(vector<double>::size_type i = 0; i < A.size(); ++i)
 13     {
 14         res += pow(A[i], 2);
 15     }
 16     return sqrt(res);
 17 }
 18
 19 double InnerProduct(vector<double> A, vector<double> B)
 20 {
 21     double res = 0;
 22     for(vector<double>::size_type i = 0; i < A.size(); ++i)
 23     {
 24         res += A[i] * B[i];
 25     }
 26     return res;
 27 }
 28
 29 double ComputeSim(vector<double> A, vector<double> B, int method)
 30 {
 31     switch (method)
 32     {
 33     case 0://欧氏距离
 34         {
 35             vector<double> C;
 36             for(vector<double>::size_type i = 0; i < A.size(); ++i)
 37             {
 38                 C.push_back((A[i] - B[i]));
 39             }
 40             return 1 / (1 + norm(C));
 41             break;
 42         }
 43     case 1://皮尔逊相关系数
 44         {
 45             double A_mean = 0;
 46             double B_mean = 0;
 47             for(vector<double>::size_type i = 0; i < A.size(); ++i)
 48             {
 49                 A_mean += A[i];
 50                 B_mean += B[i];
 51             }
 52             A_mean /= A.size();
 53             B_mean /= B.size();
 54             vector<double> C(A);
 55             vector<double> D(B);
 56             for(vector<double>::size_type i = 0; i < A.size(); ++i)
 57             {
 58                 C[i] = A[i] - A_mean;
 59                 D[i] = B[i] - B_mean;
 60             }
 61             assert(norm(C) * norm(D));
 62             return InnerProduct(C,D) / (norm(C) * norm(D));
 63             break;
 64         }
 65     case 2:
 66         {
 67             assert(norm(A) * norm(B));
 68             return InnerProduct(A,B) / (norm(A) * norm(B));
 69             break;
 70         }
 71     default:
 72         {
 73             cout << " Choose method:" << endl;
 74             cout << "0:欧氏距离\n1:皮尔逊相关系数\n2:余弦相似度\n";
 75             return -1;
 76         }
 77     }
 78
 79 }
 80
 81 void FindCommon(vector<double> A, vector<double> B, vector<double> &C, vector<double> &D)
 82 {
 83     for(vector<double>::size_type i = 0; i < A.size(); ++i)
 84     {
 85         if (A[i] && B[i])
 86         {
 87             C.push_back(A[i]);
 88             D.push_back(B[i]);
 89         }
 90     }
 91 }
 92
 93
 94 vector<vector<double> > UserBasedCF(vector<vector<double> > train, int usersNum, int itemsNum)
 95 {
 96     vector<vector<double> > predict(usersNum, vector<double>(itemsNum, 0));
 97     for (int i = 0; i < usersNum; ++i) //对每个用户进行预测
 98     {
 99         //找出user i未评分的item j,预测user i 对item j的评分
100         for (int j = 0; j < itemsNum; ++j)
101         {
102
103
104             if (train[i][j])
105                 continue;
106             //如果item j没有被user i评过分,找出对 item j评过分的用户
107             else
108             {
109                 vector<double> sim;
110                 vector<double> historyScores;
111                 for (int k = 0; k < usersNum; ++k)
112                 {
113                     //如果user k对item j 评过分,计算user k与user i的相似度
114
115                     if (train[k][j])//找出对item j 评过分的user k
116                     {
117                         // 为了计算user k与user i的相似度,必须找出二者共同评过分的items
118                         // 把二者对共同评过分的items的评分分别存储在两个vector中
119                         vector<double> commonA,commonB;
120                         FindCommon(train[i], train[k], commonA, commonB);
121                         //如果二者存在共同评过分的items,计算相似度
122                         if (!commonA.empty())
123                         {
124                             sim.push_back(ComputeSim(commonA, commonB, 2));
125                             // 把user k对item j 的历史评分记录下来
126                             historyScores.push_back(train[k][j]);
127                         }
128                     }
129
130                 }
131                 // 计算出所有与user i存在共同评过分的items的users与user i之间的相似度,
132                 // 保存在sim中,这些users对目标items j(即user i没有评过分)的历史评分记
133                 // 录在historyScores中。利用这两个vector,计算出相似度加权平均分作为预
134                 // 测user i对item j的评分
135                 double SimSum = 0;
136                 if (!sim.empty())
137                 {
138                     for(vector<double>::size_type m = 0; m < sim.size(); ++m)
139                     {
140                         SimSum += sim[m];
141                     }
142                 predict[i][j] = InnerProduct(sim, historyScores) / (SimSum);
143                 cout << "User "<< i << " 对第 " << j << " 个Item的评分为 " << predict[i][j] << endl;
144                 }
145             }
146         }
147     }
148     return predict;
149 }
150
151 int main()
152 {
153     string FilePath1("E:\\Matlab code\\recommendation system\\data\\movielens\\train.txt");
154     string FilePath2("E:\\Matlab code\\recommendation system\\data\\movielens\\test.txt");
155
156     int row = 943;
157     int col = 1682;
158     vector<vector<double> > train = txtRead<double>(FilePath1, row, col);
159     vector<vector<double> > predict = UserBasedCF(train, row, col);
160     txtWrite(predict, "predict.txt");
161     vector<vector<double> > test = txtRead<double>(FilePath2, 462, 1591);
162     double rmse = ComputeRMSE(predict,test);
163     cout << "RMSE is " << rmse <<endl;
164     return 0;
165 }

4.运行

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

时间: 2024-10-07 17:09:54

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

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

http://yidianzixun.com/n/09vv1FRK?s=1 完全摘抄自网页 1 集体智慧和协同过滤 1.1 什么是集体智慧(社会计算)? 集体智慧 (Collective Intelligence) 并不是 Web2.0 时代特有的,只是在 Web2.0 时代,大家在Web 应用中利用集体智慧构建更加有趣的应用或者得到更好的用户体验.集体智慧是指在大量的人群的行为和数据中收集答案,帮助你对整个人群得到统计意义上的结论,这些结论是我们在单个个体上无法得到的,它往往是某种趋势或者人群

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

1 集体智慧和协同过滤 1.1 什么是集体智慧(社会计算)? 集体智慧 (Collective Intelligence) 并不是 Web2.0 时代特有的,只是在 Web2.0 时代,大家在 Web 应用中利用集体智慧构建更加有趣的应用或者得到更好的用户体验.集体智慧是指在大量的人群的行为和数据中收集答案,帮助你对整个人群得到统计意义上的结论,这些结论是我们在单个个体上无法得到的,它往往是某种趋势或者人群中共性的部分. Wikipedia 和 Google 是两个典型的利用集体智慧的 Web

【推荐系统】协同过滤之基于用户的最近邻推荐

1.算法简介 协同过滤(collaborative filtering)的核心思想:利用其他用户的行为来预测当前用户.协同过滤算法是推荐系统中最基本的,同时在业界广为使用.根据使用的方法不同,可以分为基于用户(user-based).基于物品(item-based)的最近邻推荐. 基于用户的最近邻推荐的主要思想与kNN有点相似:对于一个给定的评分集,找出与当前用户u口味相近的k个用户:然后,对于用户u没有见过的物品p,利用k个近邻对p进行预测评分.由此引出了两个问题,一是如何度量用户与用户间的相

【推荐系统】协同过滤--高度稀疏数据下的数据清理(皮尔逊相关系数)

向量之间的相似度 度量向量之间的相似度方法很多了,你可以用距离(各种距离)的倒数,向量夹角,Pearson相关系数等. 皮尔森相关系数计算公式如下: 分子是协方差,分子是两个变量标准差的乘积.显然要求X和Y的标准差都不能为0. 因为,所以皮尔森相关系数计算公式还可以写成: 当两个变量的线性关系增强时,相关系数趋于1或-1. 用户评分预测 用户评分预测的基本原理是: step1.如果用户i对项目j没有评过分,就找到与用户i最相似的K个邻居(使用向量相似度度量方法) step2.然后用这K个邻居对项

[原]【推荐系统】协同过滤之基于用户的最近邻推荐

1.算法简介 协同过滤(collaborative filtering)的核心思想:利用其他用户的行为来预测当前用户.协同过滤算法是推荐系统中最基本的,同时在业界广为使用.根据使用的方法不同,可以分为基于用户(user-based).基于物品(item-based)的最近邻推荐. 基于用户的最近邻推荐的主要思想与kNN有点相似:对于一个给定的评分集,找出与当前用户u口味相近的k个用户:然后,对于用户u没有见过的物品p,利用k个近邻对p进行预测评分.由此引出了两个问题,一是如何度量用户与用户间的相

推荐系统之协同过滤

这个转自csdn,很贴近工程. 协同过滤(Collective Filtering)可以说是推荐系统的标配算法. 在谈推荐必谈协同的今天,我们也来谈一谈基于KNN的协同过滤在实际的推荐应用中的一些心得体会. 我们首先从协同过滤的两个假设聊起. 两个假设: 用户一般会喜欢与自己喜欢物品相似的物品 用户一般会喜欢与自己相似的其他用户喜欢的物品 上述假设分别对应了协同过滤的两种实现方式:基于物品相似(item_cf)及基于用户相似(user_cf). 因此,协同过滤在实现过程中,最本质的任务就是计算相

《推荐系统》--协同过滤推荐

<Recommender System An Introduction>,第二章,协同过滤推荐. 定义 协同过滤推荐方法的主要思想是,利用已有用户群过去的行为或意见预测当前用户最可能喜欢哪些东西或对哪些东西感兴趣.此类型的推荐系统当前在业界广泛使用. 纯粹的协同方法的输入数据只有给定的用户-物品评分矩阵,输出数据一般有以下几种类型: (1)表示当前用户对物品喜欢或不喜欢程度的预测数值: (2)n项推荐物品的列表. 基于用户的最近邻推荐 主要思想 这是一种早期方法,user-based near

机器学习----推荐系统之协同过滤算法

(一)问题描述 电影评分,下图中5部电影,4个人进行评分,评分从0-5,并且为整数,问号处表示没有评分. (二)基于内容的推荐系统 给每部电影添加两个features,针对这个问题中分别为romatic和action,范围为1-5,并且给出一部电影这两个参数就已知. 这里设,每部电影由xi表示,xi为一个3*1的向量,第一个x0为截距1,第二个为romantic指数,第三个为action指数.每个人的评分也由一个3*1的向量表示,第二个和第三个分别表示每个人对romantic和action的喜欢

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

1.推荐系统中的算法: 协同过滤: 基于用户 user-cf 基于内容 item –cf slop one 关联规则 (Apriori 算法,啤酒与尿布) 2.slope one 算法 slope one 算法是基于不同物品之间的评分差的线性算法,预测用户对物品评分的个性化算法.slope one 算法是由daniel 教授在2005年提出.主要分为2步 1. 计算物品之间评分差的平均值,记为物品间的评分偏差: 2.根据物品间的评分偏差和用户的历史评分,给用户生成预测评分高的推荐物品列表. 实例