然后,采用基于最近邻用户的协同过滤推荐算法,为当前活跃用户推荐歌曲;
对于歌词信息(英文)的歌曲,基于异构文本网络,通过单词嵌入计算歌曲之间的相似度,然后根据歌曲的历史记录推荐给用户
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());
// }