推荐歌曲

该博客介绍了音乐推荐系统中的一些核心算法,包括基于最近邻用户的协同过滤推荐算法,利用歌词信息(英文)的歌曲通过单词嵌入计算歌曲相似度,以及整个系统的不同组件如DataTranslate、Hybrid、Listener等的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

然后,采用基于最近邻用户的协同过滤推荐算法,为当前活跃用户推荐歌曲;
对于歌词信息(英文)的歌曲,基于异构文本网络,通过单词嵌入计算歌曲之间的相似度,然后根据歌曲的历史记录推荐给用户
package top.wangruns.trackstacking.algorithm;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

public class CollaborativeFiltering {

/**
 * 基于最近邻用户产生协同过滤的推荐结果
 * @param userIdList
 * 用户Id列表
 * @param userKNNMatrix
 * 用户KNN矩阵
 * @param user2songRatingMatrix
 * 用户歌曲“评分”矩阵
 * @param songIdList
 * 歌曲Id列表
 * @param n
 * 推荐的前n首歌曲
 * @return
 * 用户歌曲推荐结果矩阵.userId,[recSongId1,recSongId2...recSongIdn]
 */
public static Map<Integer, Integer[]> userKNNBasedCF(List<Integer> userIdList,
		final Map<Integer, Integer[]> userKNNMatrix, final Map<Integer, float[]> user2songRatingMatrix,
		final List<Integer> songIdList, final int n) {
	// TODO Auto-generated method stub
	final Map<Integer,Integer[]> user2songRecMatrix=new HashMap<Integer, Integer[]>();
	userIdList.forEach(new Consumer<Integer>() {

		public void accept(Integer curUserId) {
			// TODO Auto-generated method stub
			Integer[] knnIdArray=userKNNMatrix.get(curUserId);
			/**
			 * 对于每一首当前用户没有听过的歌曲
			 * 协同得分为:
			 * 其k个最近邻用户对该歌曲的“评分”的聚合
			 */
			float[] curUserRatings=user2songRatingMatrix.get(curUserId);
			//为用户建立一个最小堆来存放最高的前n首歌曲
			MininumHeap mininumHeap=new MininumHeap(n);
			for(int i=0;i<curUserRatings.length;i++) {
				//对于没有听过的歌曲
				/**
				 * 这里需要注意的是,浮点数不能用==来比较...之前竟然犯了这个低级的错误...
				 * 故这里用 curUserRatings[i]<0.01f 来表示 curUserRatings[i]==0f
				 */
				if(curUserRatings[i]<0.01f) {
					for(int knnIndex=0;knnIndex<knnIdArray.length;knnIndex++) {
						int knnId=knnIdArray[knnIndex];
						float[] knnUserRatings=user2songRatingMatrix.get(knnId);
						curUserRatings[i]+=knnUserRatings[i];
					}
					//这里的聚合策略取均值
					curUserRatings[i]/=knnIdArray.length;
					int curSongId=songIdList.get(i);
					//放入堆中
					mininumHeap.addElement(new TreeNode(curSongId,curUserRatings[i]));
				}
			}
			/**
			 * 对该用户没有听过的歌曲,协同得分完成,选取n个得分最高的项目作为推荐
			 */
			int trueNumber=n;
			//如果推荐的歌曲少于计划推荐的n首(处理歌曲很少的情况)
			if(mininumHeap.getCurHeapSize()<n) {
				trueNumber=mininumHeap.getCurHeapSize();
			}
			Integer[] curUserRecSongId=new Integer[trueNumber];
			for(int i=0;i<trueNumber;i++) {
				int recSongId=mininumHeap.getArray()[i].id;
				curUserRecSongId[i]=recSongId;
			}
			user2songRecMatrix.put(curUserId, curUserRecSongId);
			
		}
		
	});
	return user2songRecMatrix;
}

}

package top.wangruns.trackstacking.algorithm;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import top.wangruns.trackstacking.model.Collection;
import top.wangruns.trackstacking.model.DownloadRecord;
import top.wangruns.trackstacking.model.PlayRecord;
import top.wangruns.trackstacking.model.User;

public class DataTranslate {
private final static float PLAY_SCORE=1f;
private final static float DOWNLOAD_SCORE=2f;
private final static float COLLECTION_SCORE=5f;
private final static float MAX_SCORE=10f;
private final static int SONG_ID_SET_KEY=0;

/**
 * 构建用户频率矩阵来近似用户评分,对于某些系统而言,我们是不可能获取到用户对某些项目的评分的,但是我们可以利用用户的
 * 行为习惯来反映用户的“评分”,比如一个用户常常收听某一首歌,那么我们可以推断该用户喜欢该歌曲的可能性很大.
 * 总分10分,主动播放一次1分,下载2分,收藏5分,如果超过10分,按10分计算.
 * @param userIdList 
 * 用户Id列表
 * @param songIdList 
 * 歌曲Id列表
 * @param downloadList
 * 用户的下载记录列表
 * @param playList
 * 用户的播放记录列表
 * @param collectionList
 * 用户的收藏记录列表
 * @return
 * 用户Id-歌曲Id 频率矩阵
 */
public static Map<Integer, float[]> getFrequencyMatrix(List<Integer> userIdList, final List<Integer> songIdList,
		List<DownloadRecord> downloadList, List<PlayRecord> playList, List<Collection> collectionList) {
	// TODO Auto-generated method stub
	final Map<Integer,float[]> user2songRatingMatrix=new HashMap<Integer, float[]>();
	final int songLen=songIdList.size();
	//获取用户-歌曲 下载映射
	final Map<Integer,Map<Integer,Set<Integer>>> userId2songIdDownloadMap=getUserId2songIdRecordMap(downloadList,false);
	//获取用户-歌曲 收藏映射
	final Map<Integer, Map<Integer, Set<Integer>>> userId2songIdCollectionMap=getUserId2songIdRecordMap(collectionList,false);
	//获取用户-歌曲-次数 播放映射
	final Map<Integer, Map<Integer, Set<Integer>>> userId2songIdPlayMap=getUserId2songIdRecordMap(playList,true);
	
	userIdList.forEach(new Consumer<Integer>() {

		public void accept(Integer userId) {
			// TODO Auto-generated method stub
			float[] curUserRatingArray=new float[songLen];
			int songIndex=0;
			//处理每一首歌曲
			for(Integer songId:songIdList) {
				/**
				 * 处理下载,这里不考虑下载次数
				 */
				if(userId2songIdDownloadMap.get(userId)!=null && userId2songIdDownloadMap.get(userId).get(SONG_ID_SET_KEY).contains(songId)) {
					//当前用户下载过的歌曲
					curUserRatingArray[songIndex]+=DOWNLOAD_SCORE;
				}
				
				/**
				 * 处理收藏,这里没有次数
				 */
				if(userId2songIdCollectionMap.get(userId)!=null && userId2songIdCollectionMap.get(userId).get(SONG_ID_SET_KEY).contains(songId)) {
					//当前用户收藏的歌曲
					curUserRatingArray[songIndex]+=COLLECTION_SCORE;
				}
				
				/**
				 * 处理播放,考虑播放次数
				 */
				if(userId2songIdPlayMap.get(userId)!=null && userId2songIdPlayMap.get(userId).get(SONG_ID_SET_KEY).contains(songId)) {
					//当前用户播放过的歌曲
					int count=userId2songIdPlayMap.get(userId).get(songId).iterator().next();
					curUserRatingArray[songIndex]+=PLAY_SCORE + count;
				}
				
				/**
				 * 处理最大得分,超过最大得分,记为最大得分
				 */
				if(curUserRatingArray[songIndex]>MAX_SCORE) {
					curUserRatingArray[songIndex]=MAX_SCORE;
				}
				//处理下一首歌
				songIndex++;
			}
			//处理完一个用户
			user2songRatingMatrix.put(userId, curUserRatingArray);
		}
		
	});
	return user2songRatingMatrix;
}

/**
 * 获取用户Id - 歌曲Id 的映射Map
 * @param recordList
 * 包含userId,songId的记录列表
 * @param isCount
 * 是否需要计数。如果true,则Integer[1]存放计数。
 * @return
 * 两层Map
 * 第一层Map<Integer,Map> 每个userId拥有一个自己的Map:
 * userId,userSetMap
 * 
 * 第二层Map<Integer,Set> 用户自己的Map里面存放两个东西:
 * (1)为每首歌曲计数songId,CountSet;
 * (2)存放出现过的歌曲songSetFlay,SongIdSet:
 */
private static <T> Map<Integer, Map<Integer, Set<Integer>>> getUserId2songIdRecordMap(final List<T> recordList,final boolean isCount) {
	// TODO Auto-generated method stub
	final Map<Integer, Map<Integer, Set<Integer>>> userId2songIdRecordMap=new HashMap<Integer, Map<Integer, Set<Integer>>>();
	
	recordList.forEach(new Consumer<T>() {

		public void accept(T t) {
			// TODO Auto-generated method stub
			try {
				//利用反射获和泛型获取不同类型表的相同属性
				Field userIdField=t.getClass().getDeclaredField("userId");
				Field songIdField=t.getClass().getDeclaredField("songId");
				userIdField.setAccessible(true);
				songIdField.setAccessible(true);
				int userId=userIdField.getInt(t);
				int songId=songIdField.getInt(t);
				//不需要计数
				if(!isCount) {
					//map外层的userId已经存在
					if(userId2songIdRecordMap.containsKey(userId)) {
						//获取当前用户的记录集合Map
						Map<Integer,Set<Integer>> curRecordSetMap=userId2songIdRecordMap.get(userId);
						//将当前歌曲添加到当前用户的记录集合中
						curRecordSetMap.get(SONG_ID_SET_KEY).add(songId);
					}else {
						Map<Integer,Set<Integer>> curRecordSetMap=new HashMap<Integer, Set<Integer>>();
						//创建记录歌曲Id的集合
						Set<Integer> curSongIdSet=new HashSet<Integer>();
						curSongIdSet.add(songId);
						curRecordSetMap.put(SONG_ID_SET_KEY, curSongIdSet);
						userId2songIdRecordMap.put(userId, curRecordSetMap);
					}
				}else {
					//map外层的userId已经存在
					if(userId2songIdRecordMap.containsKey(userId)) {
						//获取当前用户的记录集合Map
						Map<Integer,Set<Integer>> curRecordSetMap=userId2songIdRecordMap.get(userId);
						//将当前歌曲添加到当前用户的记录集合中
						curRecordSetMap.get(SONG_ID_SET_KEY).add(songId);
						
						//计数
						count(songId,curRecordSetMap);
						
					}else {
						Map<Integer,Set<Integer>> curRecordSetMap=new HashMap<Integer, Set<Integer>>();
						//创建记录歌曲Id的集合
						Set<Integer> curSongIdSet=new HashSet<Integer>();
						curSongIdSet.add(songId);
						curRecordSetMap.put(SONG_ID_SET_KEY, curSongIdSet);
						userId2songIdRecordMap.put(userId, curRecordSetMap);
						
						//计数
						count(songId,curRecordSetMap);
						
					}
				}
				
			}catch (NoSuchFieldException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		private void count(int songId, Map<Integer, Set<Integer>> curRecordSetMap) {
			// TODO Auto-generated method stub
			/**
			 * 计数,如果Map<songId,count>已经存在,则直接计数+1
			 */
			if(curRecordSetMap.containsKey(songId)) {
				//获取当前用户歌曲的计数集合(只有一个元素)
				Set<Integer> curCountSet=curRecordSetMap.get(songId);
				int cnt=curCountSet.iterator().next()+1;
				curCountSet.clear();
				curCountSet.add(cnt);
			}else {
				Set<Integer> curCountSet=new HashSet<Integer>();
				curCountSet.add(1);
				curRecordSetMap.put(songId, curCountSet);
			}
		}
		
		
	});
	return userId2songIdRecordMap;
}

// private static Map<Integer, Integer>getUserId2songIdDownloadMap(List downloadList) {
// // TODO Auto-generated method stub
// final Map<Integer,Integer> userId2songIdDownloadMap=new HashMap<Integer, Integer>();
// downloadList.forEach(new Consumer() {
//
// public void accept(DownloadRecord t) {
// // TODO Auto-generated method stub
// if(!userId2songIdDownloadMap.containsKey(t.getUserId())) {
// userId2songIdDownloadMap.put(t.getUserId(), t.getSongId());
// }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值