【推荐系统实战】:C++实现基于用户的协同过滤(UserCollaborativeFilter)

好早的时候就打算写这篇文章,但是还是参加阿里大数据竞赛的第一季三月份的时候实验就完成了,硬生生是拖到了十一假期,自己也是醉了。。。找工作不是很顺利,希望写点东西回顾一下知识,然后再攒点人品吧,只能如此了。

一、问题背景

二、基于用户的协同过滤算法介绍

三、数据结构和实验过程设计

四、代码

一、问题背景

首先介绍一下问题的背景,现在我有四个月的用户、品牌数据<user,brand>,即用户在这四个月中的某一天购买了某个品牌(当然为了简化算法模型,将购买时间省去,后面再说)。即现在有这四个月的数据,如何为用户推荐他们感兴趣的产品下个月购买?当然解决这个问题的算法和模型有很多很多种,现在就解释一下协同过滤算法。

二、基于用户的协同过滤算法介绍(User Collaborative Filter)

基于邻域的算法是推荐系统中最基本的算法,该算法不仅在学术界得到了深入的研究,而且在工业界也有广泛的应用。基于邻域的算法分为两大类,一类是基于用户的协同过滤算法,另一类是基于物品的协同过滤算法。这里只介绍一种基于用户的协同过滤算法。

在一个在线个性化推荐系统中,当一个用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的、而用户A没有听说过的物品推荐给A。这种方法称为基于用户的协同过滤算法

从上面的描述可以看到,基于用户的协同过滤算法主要包括两个步骤:

(1)找到和目标用户相似的用户集合。

(2)找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。

步骤1的关键就是计算两个用户的兴趣相似度。这里,协同过滤算法主要利用行为的相似度计算兴趣的相似度。给定用户u和用户v,令N(u)表示用户u曾经有过正反馈的物品集合(在我们的问题背景之下也就是用户u曾经买过的物品集合),令N(v)为用户v曾经有过正反馈的物品集合。那么,我们可以通过如下的Jaccard公式简单的计算u和v的兴趣相似度:

或者通过余弦相似度计算:

对于下图的用户行为记录

在该例中,用户A对物品{a,b,c}有过行为,用户B对物品{a,c}有过行为,利用余弦相似度公式计算用户A和用户B的兴趣相似度,以及A和C、D的相似度:

那么,如果两两用户都利用余弦相似度计算相似度。这种方法的时间复杂度是O(|U|*|U|),这在用户数很大的情况下十分耗时。事实上,很多用户相互之间并没有相同的物品产生行为,即很多时候N(u)和N(v)的交集为0。那么,一种高效的算法就是首先计算出交集不为0的用户对{u,v},然后再对这种情况除以分母|N(u)UN(v)|(或者另一种根号形式)。

为此,可以首先建立物品到用户的倒查表,对于每个物品都保存对该物品产生过行为的用户列表。令系数矩阵C[u][v]=|N(u)并N(v)|。那么,假设用户u和用户v同时属于倒排表中K个物品对应的用户列表,就有C[u][v]=K。从而,可以扫描倒查表中每个物品对应的用户列表,将用户列表中的两两用户对应的C[u][v]加1,最终就可以得到所有用户之间不为0的C[u][v]。

对于上图的用户行为记录建立物品-用户的倒排表

建立一个4*4的用户相似度矩阵W,对于物品a,将W[A][B]和W[B][A]加1,对于物品b,将W[A][C]和W[C][A]加1,以此类推。扫描完所有物品后,我们可以得到最终的W矩阵。这里的W是余弦相似度中的分子部分,然后将W除以分母可以得到最终的用户兴趣相似度。

得到用户之间的兴趣相似度后,UserCF算法会给用户推荐和他兴趣最相似的K个用户喜欢的物品。如下的公式度量了UserCF算法中用户u对物品i的感兴趣程度:

S(u,K):包含和用户u兴趣最接近的K个用户。

N(i):对物品i有过行为的用户集合

Wuv:用户u和用户v的兴趣相似度

Rvi:用户v对物品i的兴趣(这里都为1)

说明:i物品是用户u之前没有接触过的。那么用户u对物品i的感性却程度的计算过程可以分为几个步骤:

①找到与用户u最近的K个用户(通过用户相似度矩阵

②通过K个用户和N(i)(对物品i有过购买的用户集合)的交集得到K个用户中对i感兴趣的若干用户集合v[]。

③将用户u和集合v[]中每一个用户v[i]之间的相似度累加的总和即为用户u对于物品i的感兴趣程度

三、数据结构和实验过程设计

3.1:输入输出

·input:前三个月用户和用户对应所购买的物品

·output:

1)为每个用户所推荐的物品

2)根据得到的结果然后和第四个月的数据求得准确率precision和召回率recall

在此介绍一下准确率precision和召回率recall:

准确率:

注:

N 为参赛队预测的用户数

pBrandsi为对用户i 预测他(她)会购买的品牌列表个数

hitBrandsi对用户i预测的品牌列表与用户i真实购买的品牌交集的个数

召回率:

注:

M 为实际产生成交的用户数量

bBrandsi为用户i 真实购买的品牌个数

hitBrandsi预测的品牌列表与用户i真实购买的品牌交集的个数

3.2:数据结构设计

map< int,int > userid_id:将userid映射到从0开始的依次递增的数值(id:0~n-1),为后续映射成矩阵做准备。

map< int,int > id_userid:与userid_id正好相反

map< int , set<int> >  user_brands:用户-物品表:用户以及用户对应购买的物品列表

map< int , set<int> >  id_brands:将user_brands依据userid_id转换成为id_brands

map< int , set<int> >  brand_ids:物品-用户倒排表

set<int> brand_all :所有品牌的集合

map< int , set<int> >  user_brand_rec :为用户推荐的品牌集合

dobule sim_mat[MAX][MAX] :用户之间的相似程度矩阵

3.3:算法过程:

<span style="font-size:14px;">1).读入数据,将数据用user_brands存起来,同时建立userid_id,再建立id_brands
2).遍历id_brands得到物品-用户倒排表brand_ids,根据倒排表得到用户的相似矩阵sim_mat
3).为每个用户推荐产品:
    3.1).求用户u买过的brand和所有brand的差集得到用户u没有买过的物品集合brand_unused;
    3.2).用户u对每一个没用过的物品i的兴趣p(u,i):
        3.2.1).找到与用户u最近的k个用户
        对于每一个没用过的物品i:
        3.2.2).找出这k个用户中对物品i有过行为的用户v[]√
        3.2.3).将用户u和v[j]的兴趣相似度累加
    3.3).取前m个最感兴趣的brand推荐给用户;</span>

四、代码

UCF.cc(用户协同过滤核心代码):

#include<iostream>
#include<fstream>
#include<stdio.h>
#include<map>
#include<set>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAX = 1000;

class UserCF
{
private:
        int k , m ; //k: the most k persons interested in the brand i ; m : choose the former m brands which user u are most interested in
		map< int,int > userid_id;
		map< int,int > id_userid;
		map< int,set<int> > user_brands;
		map< int,set<int> > id_brands;
		map< int,set<int> > brand_ids;
		set<int> brand_all;
		map< int,set<int> > user_brand_rec;
		ifstream fin;
		ofstream fout;
		double sim_mat[MAX][MAX];
		typedef struct sim_idx
		{
			double sim;
			int idx;
			bool operator > (const sim_idx &other) const
			{
				return sim > other.sim;
			}
		}sim_idx;
		typedef struct brand_interest
		{
			double Int; // Int = interest
			int brand;
			bool operator > (const brand_interest &other) const
			{
				return Int > other.Int;
			}
		}brand_interest;

public:
        UserCF(int _k , int _m):k(_k),m(_m)
		{
			fin.open("user_brand_m123.txt");
			fout.open("rec_result_by_m123.txt");
			if(!fin||!fout)
			{
				cout<<"can not open the file"<<endl;
				exit(1);
			}

			//userid_id , id_userid , user_brands , brand_all
			int userid,brandid,i=0;
			while(fin>>userid>>brandid)
			{
				if( user_brands[userid].empty() )
				{
					userid_id[userid] = i;
					id_userid[i] = userid;
					i++;
				}
				user_brands[userid].insert(brandid);
				brand_all.insert(brandid);
			}

			//id_brands
			map< int,set<int> > :: iterator it = user_brands.begin();
			while( it!=user_brands.end() )
			{
				id_brands[ userid_id[it->first] ] = it->second;
				it++;
			}
			bzero(sim_mat,0);
		}

		void get_sim_mat()
		{
			get_reverse_table();
			map< int,set<int> > :: iterator it = brand_ids.begin();
			while(it!=brand_ids.end())
			{
				vector<int> tmp( it->second.begin(),it->second.end() );
				int len = tmp.size();
				//for each brand , traverse all two pair users , sim_mat increment
				for(int i=0;i<len;i++)
					for(int j=i+1;j<len;j++)
					{
						sim_mat[ tmp[i] ][ tmp[j] ]+=1;
						sim_mat[ tmp[j] ][ tmp[i] ]+=1;
					}
					it++;
			}

			int len = id_userid.size();
			for(int i=0;i<len;i++)
				for(int j=0;j<len;j++)
				{
					sim_mat[i][j] /= sqrt( user_brands[ id_userid[i] ].size() * user_brands[ id_userid[j] ].size()  );
					sim_mat[j][i] = sim_mat[i][j];
				}
			cout<<endl;
		}

		//brand_ids - brand : userid1,userid2,userid3...
		void get_reverse_table()
		{
			map< int,set<int> > :: iterator it = id_brands.begin();
			while( it!=id_brands.end() )
			{
				set<int> tmp = it->second;
				set<int> :: iterator it2 = tmp.begin();
				while(it2!=tmp.end())
				{
					brand_ids[*it2].insert(it->first);
					it2++;
				}
				it++;
			}
		}

		set<int> get_rec_brand_set_by_user(int userid)
		{
			//3.1
			set<int> brand_unused;
			/* set_difference:find different set between two set
			 * function : get brand set that userid has never bought before
			 */
			set_difference(brand_all.begin(),brand_all.end(),user_brands[userid].begin(),user_brands[userid].end(),inserter( brand_unused , brand_unused.begin() ) );	

			sim_idx simidx;
			vector<sim_idx> vec_sim_idx;
			int len = userid_id.size();
			int id = userid_id[userid];
			for(int i=0;i<len;i++)
			{
				simidx.sim=sim_mat[id][i];
				simidx.idx=i;
				vec_sim_idx.push_back(simidx);
			}
			sort( vec_sim_idx.begin(),vec_sim_idx.end(),greater<sim_idx>() ); //order by desc

			//3.2.1
			set<int> rec_ids;
			vector<sim_idx> :: iterator it = vec_sim_idx.begin();
			for(int i=0;i<k;i++)
			{
				rec_ids.insert( (*it).idx );
				it++;
			}

			set<int> rec_brand;
			set<int> :: iterator itt = brand_unused.begin();
			vector<brand_interest> vec_bi; //userid's interest level toward brand
			brand_interest bi;
			while( itt!=brand_unused.end() )
			{
				//3.2.2
				vector<int> newset; // or set<int> newset
				set<int> ids = brand_ids[*itt];
				set_intersection(rec_ids.begin(),rec_ids.end(),ids.begin(),ids.end(),inserter( newset,newset.begin() ));
				if(newset.empty())
				{
					itt++;
					continue;
				}
				double interest = 0.0;
				int len = newset.size();
				for(int i=0;i<len;i++)
				{
					interest += sim_mat[ userid_id[userid] ][ newset[i] ];
				}
				//3.2.3
				bi.brand = *itt;
				bi.Int = interest;
				vec_bi.push_back(bi);
				itt++;
			}//while

			//3.3
			for(int i=0;i<m&&i<vec_bi.size();i++)
			{
				rec_brand.insert(vec_bi[i].brand);
			}

			return rec_brand;

		}

		void recommend()
		{
			map< int,int > :: iterator it = userid_id.begin();
			while( it!=userid_id.end() )
			{
				user_brand_rec[it->first] = get_rec_brand_set_by_user(it->first);
				it++;
			}
		}//recommend

		void print()
		{
			//write recommendation result <user,brand> to file
			map< int,set<int> > :: iterator it = user_brand_rec.begin();
			while( it!=user_brand_rec.end() )
			{
				set<int> tmp = it->second;
				set<int> :: iterator it2 = tmp.begin();
				while(it2!=tmp.end())
				{
					fout<<it->first<<" "<<*it2<<endl;
					it2++;
				}
				it++;
			}

		}//print

		~UserCF()
		{
			userid_id.clear();
			user_brands.clear();
			id_brands.clear();
			brand_ids.clear();
			brand_all.clear();
			user_brand_rec.clear();
			fin.close();
			fout.close();
		}

}; 

int main(int argc , char *argv[])
{
    if(argc!=3)
    {
        cout<<"Usage : ./a.out k m"<<endl;
        exit(1);
    }
    int k = atoi(argv[1]) ;
    int m = atoi(argv[2]) ;

    UserCF ucf(k,m);
	ucf.get_sim_mat();
	ucf.recommend();
#if 1
    ucf.print();
#endif
    return 0;
}

cal_precision_recall.cc(计算准确率和召回率代码):

#include<iostream>
#include<fstream>
#include<map>
#include<set>
#include<algorithm>

using namespace std;

int main()
{
	ifstream fin , fin1;
	fin.open("rec_result_by_m123.txt");
	fin1.open("user_brand_m4.txt");
	if(!fin||!fin1)
	{
		cout<<"can not open file"<<endl;
		exit(1);
	}

	map< int,set<int> > fore_user_brands; //recommendation result
	map< int,set<int> > real_user_brands; //real result

	int user , brand;
	while(fin>>user>>brand)
	{
		fore_user_brands[user].insert(brand);
	}
	while(fin1>>user>>brand)
	{
		real_user_brands[user].insert(brand);
	}

	double precision = 0 , recall = 0;
	double fore_total_brand = 0;
	double real_total_brand = 0;
	double intersection = 0;
	double F = 0;

	map< int,set<int> > :: iterator it = fore_user_brands.begin();
	map< int,set<int> > :: iterator itt = real_user_brands.begin();
	while(it!=fore_user_brands.end())
	{
		fore_total_brand += (it->second).size();
		it++;
	}
	while(itt!=real_user_brands.end())
	{
		real_total_brand += (itt->second).size();
		itt++;
	}

	it = fore_user_brands.begin();
	while(it!=fore_user_brands.end())
	{
		set<int> fore , real , newset;
		fore = it->second;
		real = real_user_brands[it->first];
		//set_intersection:get intersection of two sets
		set_intersection(fore.begin(),fore.end(),real.begin(),real.end(),inserter(newset,newset.begin()));
		intersection += newset.size();
		if(newset.size()!=0)
		{
			set<int> :: iterator itnew = newset.begin();
			cout<<"user : "<<it->first<<" brand : ";
			while(itnew!=newset.end())
			{
				cout<<*itnew<<" ";
				itnew++;
			}
			cout<<endl;
		}
		it++;
	}

	precision = intersection/fore_total_brand;
	recall = intersection/real_total_brand;
	F = (2*precision*recall)/(precision+recall);
	cout<<"fore_total_brand = "<<fore_total_brand<<endl;
	cout<<"real_total_brand = "<<real_total_brand<<endl;
	cout<<"intersection = "<<intersection<<endl;
	cout<<"precision = "<<precision<<endl;
	cout<<"recall = "<<recall<<endl;
	cout<<"F = "<<F<<endl;

	return 0;
}

makefile:

target:
		g++ UCF.cc
		./a.out  5  5
		g++  cal_precision_recall.cc
		./a.out

clean:
		rm result.txt a.out

实验结果:

k=5,m=5(k即对某个品牌最感兴趣的前k个人;m即推荐给用户的前m个品牌):

在改变k和m的值情况下得到的结果,可见F值并没有太大的改变。

可见协同过滤这个算法对于这个问题背景并不是非常适用,效果很差,因此要根据不同的业务来选择什么样的模型和算法,这个还需要不断的学习。

源代码以及数据:http://yunpan.cn/cgqHHyV3Q8bIX (提取码:46cc)

UCF暂时就介绍到这里,当然这个算法还有很多可以改进的,比如在计算用户相似度的时候可以加上时间上的因素,还有惩罚用户u和用户v共同兴趣列表中热门物品对他们相似度的影响等。

参考资料:

1.《推荐系统实战》

2.www.cplusplus.com

转载注明出处:http://blog.csdn.net/lavorange/article/details/22584373

时间: 2024-08-05 23:58:16

【推荐系统实战】:C++实现基于用户的协同过滤(UserCollaborativeFilter)的相关文章

基于用户的协同过滤

最近在看推荐系统.主要是看<智能web算法>和<推荐系统实战>这两本书.<智能web算法>中推荐系统只花一个章节来讲.<推荐系统实战>整本书都是在讲推荐的内容.有兴趣的朋友可以看看.在此慢慢写下笔记与诸位来宾交流交流 推荐系统应用广泛.推荐的方式也多种多样.比较常用的有三种方式.1.社会化推荐:2.协同过滤推荐:3.基于内容的推荐.而协同过滤推荐又可以分为基于用户的协同过滤推荐(UserCF)和基于物品的协同过滤推荐(ItemCF).本文写的是基于用户的协同

基于用户的协同过滤推荐算法原理和实现

在推荐系统众多方法中,基于用户的协同过滤推荐算法是最早诞生的,原理也较为简单.该算法1992年提出并用于邮件过滤系统,两年后1994年被 GroupLens 用于新闻过滤.一直到2000年,该算法都是推荐系统领域最著名的算法. 本文简单介绍基于用户的协同过滤算法思想以及原理,最后基于该算法实现园友的推荐,即根据你关注的人,为你推荐博客园中其他你有可能感兴趣的人. 基本思想 俗话说"物以类聚.人以群分",拿看电影这个例子来说,如果你喜欢<蝙蝠侠>.<碟中谍>.&l

(数据挖掘-入门)基于用户的协同过滤之最近邻

主要内容: 1.什么是基于用户的协同过滤 2.python实现 1.什么是基于用户协同过滤: 协同过滤:Collaborative Filtering,一般用于推荐系统,如京东,亚马逊等电商网站上的“购买该物品的用户还喜欢/购买”之类的栏目都是根据协同过滤推荐出来的. 基于用户的协同过滤:User-based CF,通过不同用户对item(物品)的评分来评测用户之间的相似性,基于用户之间的相似性做出推荐. 这里介绍一种最简单的过滤方法:最近邻,即找到与某用户最相似的用户,将该用户喜欢的物品(而某

基于用户的协同过滤推荐算法

什么是推荐算法 推荐算法最早在1992年就提出来了,但是火起来实际上是最近这些年的事情,因为互联网的爆发,有了更大的数据量可以供我们使用,推荐算法才有了很大的用武之地. 最开始,所以我们在网上找资料,都是进yahoo,然后分门别类的点进去,找到你想要的东西,这是一个人工过程,到后来,我们用google,直接搜索自 己需要的内容,这些都可以比较精准的找到你想要的东西,但是,如果我自己都不知道自己要找什么肿么办?最典型的例子就是,如果我打开豆瓣找电影,或者我去 买说,我实际上不知道我想要买什么或者看

基于用户的协同过滤算法

基于用户的协同过滤算法-参考<推荐系统实践>一书,作者:项亮 1 import random 2 import math 3 class UserBasedCF: 4 def __init__(self,datafile = None): 5 self.datafile = datafile 6 self.readData() 7 self.splitData(3,47) 8 def readData(self,datafile = None): 9 """ 10

推荐算法之基于用户的协同过滤

基于用户的的协同过滤算法是推荐统统最古老的算法,简称UserCF.该算法的诞生一定程度上标志着推荐系统的诞生.本文将对UserCF算法原理进行讲解,并且基于Movielens数据集给出实现代码供大家交流学习. 基本原理 在一个在线个性化推荐系统中,当一个用户A需要个性化推荐时,先找到和他相似兴趣的其他用户,然后把那些用户喜欢的而用户A没有听说过的物品推荐给用户A.这种方法就称为基于用户的协同过滤算法.该算法主要包括两个步骤: 找到和目标用户兴趣相似的用户集合 找到这个集合中用户喜欢的且目标用户没

Mahout实现基于用户的协同过滤算法

Mahout中对协同过滤算法进行了封装,看一个简单的基于用户的协同过滤算法. 基于用户:通过用户对物品的偏好程度来计算出用户的在喜好上的近邻,从而根据近邻的喜好推测出用户的喜好并推荐. 图片来源 程序中用到的数据都存在MySQL数据库中,计算结果也存在MySQL中的对应用户表中. package com.mahout.helloworlddemo; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.

基于用户的协同过滤算法(UserCF)

基于用户的协同过滤算法: 找到和目标用户相似的用户集合 找到这个集合中用户喜欢的但目标用户没有听过的物品 #encoding: utf-8 from Similarity import Person from Sort import select_sort file=open('user_bookmark','r') filew=open('user_bookRecommend','w') #加载训练集 trainSet={} while True: line=file.readline().s

推荐算法之基于用户的协同过滤算法

协同过滤是推荐算法中最基本的算法,主要分为基于用户的协同过滤算法和基于物品的协同过滤算法. 这篇文章主要介绍基于用户的协同过滤算法,简单来说,要给用户u作推荐,那么只要找出那些和u之前的行为类似的用户,即和u比较像的用户,把他们的行为推荐给用户u即可.所以基于用户的系统过滤算法包括两个步骤:1)找到和目标用户兴趣相似的用户集合  2)找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户. 第一步的关键点在于计算用户之间的相似度,相似度一般通过Jaccard公式或者余弦相似度即可求