基于物品的协同过滤-电影推荐

先说推荐系统的基本架构:

推荐系统最基本的两个算法:

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

基于物品的协同过滤(ItemCF)

思路图:



两者类似但思路不同。下面以ItemCF为例,用最笨的方法,用java代码实现:


(貌似Python更为简洁,可惜不会唉。。。)

完整代码见:github


逻辑代码:


package com;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.junit.Test;

/**
 * ItemCF 基于物品的协同过滤算法
 * 和用户历史上感兴趣的物品越相似的物品,越有可能在用户的推荐列表中获得比较高的排名
 * 格式化输入——>获取用户物品倒排表——>获取每个电影的评论用户数——>计算相似度矩阵——>预测评分——>推荐结果
 */
@SuppressWarnings("resource")
public class MovieRecommend 
{
	private static Collection<UserContent> list=new ArrayList<UserContent>();
	private static Map<String,Integer> movieMap=new LinkedHashMap<String,Integer>();//统计电影总数
	private static Map<String,ArrayList<String>> userMap=new LinkedHashMap<String,ArrayList<String>>();//存放用户-评过分的电影
	private static Map<String,Integer> movieUserMap=new LinkedHashMap<String,Integer>();//存放每个电影的用户数量
	private static Map<String,Double> similaryMap=new LinkedHashMap<String, Double>();//存放相似度
	private static Map<String,Double> resultMap=new LinkedHashMap<String, Double>();//存放推荐结果
	
	/**
	 * 对数据预处理,将评分信息存入集合
	 */
	public ArrayList<UserContent> readData() throws IOException{
		BufferedReader read=new BufferedReader(new FileReader("C:/Users/zzd/Desktop/ml-100k/u.data"));
		String str;
		while((str=read.readLine())!=null){
			String[] split = str.split("\t");//只获取文件中的前三个数据
			UserContent uc=new UserContent();
			uc.setUserId(split[0]);
			uc.setMovieId(split[1]);
			uc.setScore(split[2]);
			list.add(uc);
		}
		return (ArrayList<UserContent>)list;
	}
	
	/**
	 * 计算电影间的相似度矩阵
	 */
	public int[][] movieSimilarty(ArrayList<UserContent> list){
		/**
		 * 获取用户-电影倒排表
		 */
		for(int i=0;i<list.size();i++){//map中不允许存放重复数据,利用这个特点计算出共有多少部电影
			UserContent uc=(UserContent) list.get(i);
			String movieId = uc.getMovieId();
			movieMap.put(movieId, 1);
			
			String userId = uc.getUserId();
			if(userMap.containsKey(userId)){//集合中存在userId
				ArrayList<String> movieList=userMap.get(userId);;//获取之前的list 每个用户评过分的电影
				if(movieList!=null&&!movieList.contains(movieId)){//电影不存在
					movieList.add(movieId);
					userMap.remove(userId);//乱序的
					userMap.put(userId, movieList);
				}
			}else{//集合中不存在userId
				ArrayList<String> movieList1=new ArrayList<String>();
				movieList1.add(movieId);
				userMap.put(userId, movieList1);
			}
		}
		
		/***
		 * 统计每个电影被多少用户评分过
		 */
		int movieCount=movieMap.size();//电影总数
		int movie[][]=new int[movieCount][movieCount];//相似度矩阵
		
		for(Entry<String, ArrayList<String>> e:userMap.entrySet()){
			ArrayList<String> movieList = (ArrayList<String>)e.getValue();
			for(int i=0;i<movieList.size();i++){
				String movieId = movieList.get(i);
				if(movieUserMap.containsKey(movieId)){//如果存在
					int count=movieUserMap.get(movieId).intValue()+1;
					movieUserMap.remove(movieId);
					movieUserMap.put(movieId, count);
				}else{//不存在
					movieUserMap.put(movieId, 1);
				}
			}
		}
		
		/***
		 * 构建矩阵
		 */
		
		/*不用排序了,因为数据中电影id是从0-1682连续的,若是不连续的要排序
		//获取所有movieId,并排序,与二维数组的下标对应
		int movieId[]=new int[movieCount];
		int i=0;
		for(Entry<String, Integer> e:movieMap.entrySet()){
			movieId[i++]=Integer.parseInt(e.getKey());
		}
		Arrays.sort(movieId);//排序
		for (int j : movieId) {
			System.out.println(j);
		}*/
		
		for(Entry<String,ArrayList<String>> e:userMap.entrySet()){//遍历建立矩阵
			ArrayList<String> movieList = e.getValue();
			int movieId[]=new int[movieCount];//存放电影Id
			int n=0;
			for(int i=0;i<movieList.size();i++){//将电影Id放到数组里
				movieId[n++]=Integer.parseInt(movieList.get(i));
			}
			//对同一个用户评分的任意两个电影,矩阵中都+1
			if(movieId.length>1){//至少要有两个电影
				for(int i=0;i<movieId.length-1;i++){
					for(int j=i+1;j<movieId.length;j++){
						if(movieId[i]!=0&&movieId[j]!=0){
							movie[movieId[i]-1][movieId[j]-1]+=1;
							movie[movieId[j]-1][movieId[i]-1]+=1;
						}
					}
				}
			}
		}
		
		/**
		 * 测试
		 */
		/*System.out.println("电影总数:"+movieCount); //test1
		
		System.out.println("每个用户评过分的电影");
		for(Entry<String, ArrayList<String>> e:userMap.entrySet()){  //test2
			System.out.print(e.getKey()+":\t[");
			ArrayList<String> list2 = (ArrayList<String>)e.getValue();
			for(int i=0;i<list2.size();i++){
				System.out.print(list2.get(i)+",");
			}
			System.out.println("]");
		}
		
		System.out.println("每个电影的用户数");
		for(Entry<String,Integer> e:movieUserMap.entrySet()){  //test3
			System.out.println(e.getKey()+": "+e.getValue()+"人");
		}
		
		System.out.println("所有电影:");//test4
		for(Entry e:movieMap.entrySet()){
			System.out.print(e.getKey()+" ");
		}
		System.out.println();
		
		System.out.println("相似度矩阵");
		for(int i=0;i<movie.length;i++){//test5
			for(int j=0;j<movie.length;j++){
				System.out.print(movie[i][j]+" ");
			}
			System.out.println();
		}
		*/
		return movie;
	}
	
	/**
	 * 计算推荐结果
	 * 对一个用户推荐,选取k个相似度最接近的,排序后推荐前n个
	 */
	public void result(int movie[][],String userId,int k,int n){
		/**
		 *预测用户对其他未评过分的电影的喜爱程度
		 *遍历电影集合
		 */
		
		for(Entry<String ,Integer> e:movieMap.entrySet()){
			String movieId=e.getKey();
			ArrayList<String> list = userMap.get(userId);//用户评过分的电影集合
			if(list.contains(movieId)){
				continue;
			}else{//对未评过分的电影预测评分
				//计算相似度
//				System.out.println("推荐电影:"+movieId);
				for(int i=0;i<movieMap.size();i++){
					if(list.contains(String.valueOf(i+1))){//该用户必须包含被推荐电影的相似电影
						int sum=movie[i][Integer.parseInt(movieId)-1];
						int count=movieUserMap.get(movieId);
						int count1=movieUserMap.get(String.valueOf(i+1));
						if(sum==0||count==0||count1==0){
							continue;
						}
						double similary=((double)sum)/Math.sqrt((double)(count*count1));//最终相似度
						similaryMap.put(String.valueOf(i+1), similary);
//						System.out.println(movieId+"和电影"+(i+1)+"的相似度为:"+similary);
					}
				}
				
				//排序 选取k个和该电影相似度最高的(因为相似度不高的参与运算会不准确!)
				//用二维数组,对value排序,第一行放相似度,第二行放与之对应的电影id
				double similarySort[][]=new double[2][movieMap.size()]; 
				int i=0;
				for(Entry<String,Double> e1:similaryMap.entrySet()){//存进数组
					double similary=e1.getValue();
					int movieId1=Integer.parseInt(e1.getKey());
					similarySort[0][i]=similary;
					similarySort[1][i++]=movieId1;
				}
				for(int i1=0;i1<movieMap.size()-1;i1++){//对相似度排序,电影id也跟着交换
					for(int j1=i1+1;j1<movieMap.size();j1++){
						if(similarySort[0][i1]<similarySort[0][j1]){
							double t;
							t=similarySort[0][i1];
							similarySort[0][i1]=similarySort[0][j1];
							similarySort[0][j1]=t;
							int t1;
							t1=(int) similarySort[1][i1];
							similarySort[1][i1]=similarySort[1][j1];
							similarySort[1][j1]=t1;
						}
					}
				}
				
				
				//计算用户对某电影的评分
				double recommend=0;
				//从排序后的数组中取前k个最接近的相似度,计算
				for(int m=0;m<k;m++){
					String movieId1=String.valueOf((int)similarySort[1][m]);
					Double similary=similarySort[0][m];
					Iterator<UserContent> iterator = this.list.iterator();
					while(iterator.hasNext()){//找到用户的评分
						UserContent uc = iterator.next();
						if(uc.getMovieId().equals(movieId1)){
							recommend+=(similary*Integer.parseInt(uc.getScore()));
							break;//list中有重复的用户id
						}
				}
			}
//				System.out.println("对电影"+movieId+"的评分为:"+recommend);
				
				/*对所有的电影进行计算,而不是前k个
				 for(Entry<String,Double> e1:similaryMap.entrySet()){
					String movieId1=e1.getKey();
						Double similary = e1.getValue();
						//相似度*用户对另一个的评分 
						Iterator<UserContent> iterator = this.list.iterator();
						while(iterator.hasNext()){//找到用户的评分
							UserContent uc = iterator.next();
							if(uc.getMovieId().equals(movieId1)){
								recommend+=(similary*Integer.parseInt(uc.getScore()));
								break;//list中有重复的用户id
							}
						}
				}
				*/
				
//				System.out.println(recommend);
				similaryMap.clear();//将集合清除!!!
				resultMap.put(movieId, recommend);//将结果放到集合中
			}
		}
		//推荐结果取前n个
		double resultSort[][]=new double[2][resultMap.size()]; 
		int j=0;
		for(Entry<String,Double> e1:resultMap.entrySet()){//存进数组
			double recommend1=e1.getValue();
			int movieId1=Integer.parseInt(e1.getKey());
			resultSort[0][j]=recommend1;
			resultSort[1][j++]=movieId1;
		}
		for(int i1=0;i1<resultMap.size()-1;i1++){//对相似度排序,电影id也跟着交换
			for(int j1=i1+1;j1<resultMap.size();j1++){
				if(resultSort[0][i1]<resultSort[0][j1]){
					double t;
					t=resultSort[0][i1];
					resultSort[0][i1]=resultSort[0][j1];
					resultSort[0][j1]=t;
					int t1;
					t1=(int) resultSort[1][i1];
					resultSort[1][i1]=resultSort[1][j1];
					resultSort[1][j1]=t1;
				}
			}
		}
		//输出结果
		System.out.println("推荐结果:"+"取前"+n+"条");
		for(int m=0;m<n;m++){
			System.out.println("电影id:"+(int)resultSort[1][m]+"   推荐指数:"+resultSort[0][m]);
		}
		
	}
	
}


测试:


package com;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;


public class Test {
	
	@org.junit.Test
	public void test1() throws IOException{
		ArrayList list = new MovieRecommend().readData();
		Iterator<UserContent> iterator = list.iterator();
		while(iterator.hasNext()){
			UserContent uc = iterator.next();
			System.out.println(uc.getUserId()+"\t"+uc.getMovieId()+"\t"+uc.getScore());//myeclipse输出竟然是乱序的!
		}
	}
	
	@org.junit.Test
	public void test2() throws IOException{
		MovieRecommend mr = new MovieRecommend();
		ArrayList<UserContent> list = mr.readData();
		int[][] movie = mr.movieSimilarty(list);
		mr.result(movie, "1", 10,10);//对用户1推荐,邻居为10,推荐10条
	}
}

数据来自 MovieLens ml-100k 


测试结果为:

1.取和物品i相似度最接近的k个物品进行计算



2.取所有的物品的相似度进行计算



对id为1的用户进行推荐,但是和书上用Python写的结果不一样,但是对一些小数据集是准确的,有可能是写法不一致吧。


准备用mapreduce实现一下,然后对比一下结果。


欢迎各位留言指正!




猜你喜欢

转载自blog.csdn.net/qq_32563713/article/details/76668760