基于项目的协同过滤java实现(Item-Based CF)

简介

随着电商网站中用户数量的迅速增长,基于用户的协同过滤User-based CF存在计算用户之间相似度时复杂度太高,不利于及时为用户产生个性化推荐。相比用户数量,电商网站上的产品数量则相对较少,基于项目的协同过滤(Item-Based CF)被亚马逊提出,并应用于亚马逊网站上。Item-Based CF的原理是计算产品之间的相似度,并根据用户已经购买过的产品为该用户提供相似的产品。由于电商网站上,产品的种类与数量相对固定,产品之间的相似性可以通过线下直接计算后再直接为用户推荐,大大节约了线上推荐的计算复杂度。
本文的是基于java实现Item-Based CF,所用数据与之前User-based CF结构相同。

Java代码实现

Item-Based CF的java实现主要分为三部分(1)用户产品交互历史读取;(2)产品相似度计算;(3)为用户推荐产品。

数据输入格式:
在这里插入图片描述
第一列表示用户ID,第二列为电影ID,第三列为用户对电影的评分,第四列对应的是电影在IMBD网站的ID(这个ID这里用不到)。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class Item_CF {
	static Map<String,Integer> itemIDMap = new HashMap<String,Integer>();//产品ID列表  产品-id
	static Map<Integer,String> idToItemMap = new HashMap<Integer,String>();//产品ID转产品原名称 id-产品	
	static Map<String, HashMap<String, Double>> itemMap = new HashMap<>(); //针对每个产品,存储所有用户对该产品的评分 
	
	static Map<String,Integer> userIDMap = new HashMap<String,Integer>();//用户ID列表
	static Map<Integer,String> idToUserMap = new HashMap<Integer,String>();//用户ID转用户原名称	
	static Map<String,HashMap<String,Double>> userMap = new HashMap<String,HashMap<String, Double>>(); //针对每个用户,记录用户对于产品的评分
	static double[][] simMatrix; //产品之间的相似矩阵
	static int TOP_K = 25;  //选择的相似item的数量
	static int TOP_N = 20;  //定义最长推荐列表
	
	public static void main(String[] args) throws IOException {
		readUI();
		item_similarity();
		recommend();	
	}

	//读取用户UI交互
	public static void readUI() throws IOException{			
		String uiFile = "data\\training_topicAttack_101.txt";
		BufferedReader  bfr_ui = new BufferedReader(new InputStreamReader(new FileInputStream(new File(uiFile)),"UTF-8"));  		
		String line;
		String[] SplitLine; 
		
		int itemId = 0; //产品计数
		int userId = 0;//用户计数
		while((line = bfr_ui.readLine()) != null){	
			SplitLine = line.split("\t");
			//如果不包含当前产品,存入产品map以及产品idmap中
			if(!itemIDMap.containsKey(SplitLine[1])) {	
				HashMap<String, Double> currentUserMap = new HashMap<>();//存入当前的用户评分
				currentUserMap.put(SplitLine[0], Double.parseDouble(SplitLine[2]));	//用户-评分						
				itemMap.put(SplitLine[1], currentUserMap); //在itemMap中存入产品-评分			
				itemIDMap.put(SplitLine[1], itemId);
				idToItemMap.put(itemId, SplitLine[1]);			
				itemId ++;
			}else {  //如果已经存在,进行Map更新
				HashMap<String, Double> currentUserMap = itemMap.get(SplitLine[1]); //获取已有产品所包含的评分
				currentUserMap.put(SplitLine[0], Double.parseDouble(SplitLine[2]));//加入新的用户-评分
				itemMap.put(SplitLine[1], currentUserMap);			
			}			
				
			//如果不包含当前的用户,存入map中
			if(!userMap.containsKey(SplitLine[0])) {
				
				userIDMap.put(SplitLine[0], userId);
				idToUserMap.put(userId, SplitLine[0]);
				
				userId++;		
				//新建Map用于存储当前用户的评分列表
				HashMap<String, Double> curentUserMap = new HashMap<String,Double>(); 
				//将当前用户评分加入当前评分列表中
				curentUserMap.put(SplitLine[1], Double.parseDouble(SplitLine[2]));
				userMap.put(SplitLine[0], curentUserMap);
			}else { //如果已存在当前用户,将该用户先前的评分拿出来,再加入新的评分
				HashMap<String, Double> curentUserMap = userMap.get(SplitLine[0]);
				curentUserMap.put(SplitLine[1], Double.parseDouble(SplitLine[2]));
				userMap.put(SplitLine[0], curentUserMap);				
			}		
		}
	}
	
	
	//获取产品之间的相似性
	public static void item_similarity() {
		//初始化用户相似矩阵
		simMatrix = new double[itemMap.size()][itemMap.size()];	
		int itemCount = 0;
		//循环每个产品计算相似性:Jaccard 相似性
		for(Map.Entry<String, HashMap<String, Double>> itemEntry_1 : itemMap.entrySet()) {
			System.out.println("计算"+itemCount);
			//获取为当前产品评分的所有用户
			Set<String> ratedUserSet_1 = new HashSet<>();
			for(Map.Entry<String, Double> userEntry : itemEntry_1.getValue().entrySet()) {
				//将已评分用户存入set集合中
				ratedUserSet_1.add(userEntry.getKey());
			}
			
			int ratedUserSize_1 = ratedUserSet_1.size();//第一个产品所有评论数
			
			//循环其他产品
			for(Map.Entry<String, HashMap<String, Double>> itemEntry_2 : itemMap.entrySet()) {
				//首先判断第二个产品的id是否大于第一个,是的话再进行计算,避免重复计算
				if(itemIDMap.get(itemEntry_2.getKey())>itemIDMap.get(itemEntry_1.getKey())) {
					//同样获取为当前产品评分的所有用户
					Set<String> ratedUserSet_2 = new HashSet<>();
					for(Map.Entry<String, Double> userEntry : itemEntry_2.getValue().entrySet()) {
						ratedUserSet_2.add(userEntry.getKey());
					}
					//通过jaccard相似度计算产品相似度

					int ratedUserSize_2 = ratedUserSet_2.size();//第二个产品所有评论数					
					int sameUerSize = interCount(ratedUserSet_1,ratedUserSet_2); //取两个集合的交集的数量
					
					double similarity = sameUerSize/(Math.sqrt(ratedUserSize_1*ratedUserSize_2));	
					//把相似性存入相似矩阵中
					simMatrix[itemIDMap.get(itemEntry_1.getKey())][itemIDMap.get(itemEntry_2.getKey())] = similarity;
					simMatrix[itemIDMap.get(itemEntry_2.getKey())][itemIDMap.get(itemEntry_1.getKey())] = similarity;					
				}						
			}
			itemCount++;
			
//			for (int i = 0; i < simMatrix.length; i++) {
//				for (int j = 0; j < simMatrix.length; j++) {
//					System.out.print(simMatrix[i][j]+" ");
//				}
//				System.out.println();
//			}			
		}
	}
	
	//根据产品的相似性进行推荐
	public static void recommend() throws IOException{
		String resultFile = "data//topicAttack_101_ItemCF_result.txt";
		BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(resultFile)),"UTF-8"));
		
		//根据item相似度获取每个item最相似的TOP_K个产品
		Map<Integer, HashSet<Integer>> nearestItemMap = new HashMap<>();
		
		for(int i = 0;i<itemMap.size();i++) {
			Map<Integer, Double> simMap = new HashMap<>();
			for(int j = 0;j<itemMap.size();j++) {
				simMap.put(j,simMatrix[i][j]);	
			}
			
			//对产品相似性进行排序
			simMap = sortMapByValues(simMap);
		
			int simItemCount = 0;
			HashSet<Integer> nearestItemSet = new HashSet<>();
			for(Map.Entry<Integer, Double> entry : simMap.entrySet()) {
				if(simItemCount<TOP_K) {
					nearestItemSet.add(entry.getKey()); //获取相似itemID存入集合中				
					simItemCount++;
				}else
					break;
			}
			//相似物品结果存入map中
			nearestItemMap.put(i,nearestItemSet);
		}
		
		//循环每个用户,循环每个产品,计算用户对没有买过的产品的打分,取TOP_N得分最高的产品进行推荐	
		for(int i = 0;i<userMap.size();i++) {
			System.out.println("为用户"+i+"推荐");			
			//获取当前用户所有评论过的产品
			HashSet<Integer> currentUserSet = new HashSet<>();
			Map<String,Double> preRatingMap = new HashMap<String,Double>();
			
			for(Map.Entry<String, Double> entry :userMap.get(idToUserMap.get(i)).entrySet()) {
				currentUserSet.add(itemIDMap.get(entry.getKey())); //将该用户评论过的产品以产品id的形式存入集合中
			}
			
			//循环每个产品
			for(int j = 0;j<itemMap.size();j++) {
				double preRating = 0;
				double sumSim = 0;
				
				//首先判断用户购买的列表中是否包含当前商品,如果包含直接跳过
				if(currentUserSet.contains(j))
					continue;	
				
				//判断当前产品的近邻中是否包含这个产品
				Set<Integer> interSet = interSet(currentUserSet, nearestItemMap.get(j));//获取当前用户的购买列表与产品相似品的交集
				
				//如果交集为空,则该产品预测评分为0				
				if(!interSet.isEmpty()) {
					for(int item :interSet) {
						sumSim += simMatrix[j][item];
						preRating += simMatrix[j][item]* userMap.get(idToUserMap.get(i)).get(idToItemMap.get(item));
						
					}
					
					if(sumSim != 0) {
						preRating = preRating/sumSim; //如果相似性之和不为0计算得分,否则得分为0
					}else
						preRating = 0;									
				}else  //如果交集为空的话,直接评分为0
					preRating = 0;
				preRatingMap.put(idToItemMap.get(j), preRating);
			}
			preRatingMap = sortMapByValues(preRatingMap);
			
			if(!preRatingMap.isEmpty()) {
				bfw.append(idToUserMap.get(i)+":");
			}
			
			//推荐TOP_N个产品
			int recCount = 0;
			for(Map.Entry<String, Double> entry : preRatingMap.entrySet()) {
				if(recCount < TOP_N) {
					bfw.append(entry.getKey() + " ");
					recCount ++;
					bfw.flush();
				}					
			}
			bfw.newLine();
			bfw.flush();
		}	
		bfw.flush();
		bfw.close();
	}	
	
	//求两个集合交集
	public static int interCount(Set<String> set_a,Set<String> set_b) {
		int samObj = 0;
		for(Object obj:set_a) {
			if(set_b.contains(obj))
				samObj++;
		}	
		return samObj;
	}
	//求两个集合交集的数量	
	public static Set<Integer> interSet(Set<Integer> set_a,Set<Integer> set_b) {
		Set<Integer> tempSet = new HashSet<>();
		for(Object obj:set_a) {
			if(set_b.contains(obj))
				tempSet.add((Integer) obj);
		}	
		return tempSet;
	}
	
	//对map进行从大到小排序
	public static <K extends Comparable, V extends Comparable> Map<K, V> sortMapByValues(Map<K, V> aMap) {
		HashMap<K, V> finalOut = new LinkedHashMap<>();
		aMap.entrySet().stream().sorted((p1, p2) -> p2.getValue().compareTo(p1.getValue())).collect(Collectors.toList())
				.forEach(ele -> finalOut.put(ele.getKey(), ele.getValue()));
		return finalOut;
	}
}

数据输出格式:
在这里插入图片描述
其中,第一列表示用户ID,后面是为该用户推荐的TOP_N个电影。

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值