前面我们介绍了千人千面的个性化推荐系统的大致情况,为了接下来的千人千面的系统讲解,这篇文章我们使用java的方式来演示一下千人千面的个性化推荐系统。让大家先看看千人千面的实现效果。
整个流程是:
上图是一个简单的实现个性化推荐的整个流程图。下面我们就根据这个流程图来挨个进行演示一下:
一、准备训练数据集
这里我们既然要千人千面,那么肯定是需要获取用户的喜好的。所以这里我们先创建一些数据集来进行用户的定义,这里的数据集示例数据如下:
196 242 3 186 302 3 22 377 1 244 51 2 166 346 1
这份数据集的字段分别是:用户id,电影id,电影的打分分数。文末我们提供有数据集下载。
二、我们读取这里的数据,并且把他转换成DTO
2.1、首先声明一下DTO的类型,名为RelateDTO
/** * 关系数据 * */ @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode @ToString @Builder public class RelateDTO { //用户id private Integer useId; //电影id private Integer moduleId; //打分 private Integer index; }
2.2、然后编写读取文件的方法,把他转换成DTO
/**
* 方法描述: 读取基础数据
*
*/
public static List<RelateDTO> getData() {
folderPath = new FileDataSource().getClass().getResource("/ml-100k").getPath();
List<RelateDTO> relateList = Lists.newArrayList();
try {
// 读取文件,这里使用commom-io包进行读取即可
List<String> lines = FileUtils.readLines(new File(folderPath + "\\u.data"), "UTF-8");
for (String line : lines) {
String[] items = line.split("\t");
// 用户id
Integer userId = Integer.parseInt(items[0]);
// 电影id
Integer movieId = Integer.parseInt(items[1]);
// 用户对电影的打分分数
Integer rating = Integer.parseInt(items[2]);
RelateDTO dto = RelateDTO.builder().useId(userId).moduleId(movieId).index(rating).build();
relateList.add(dto);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return relateList;
}三、编写皮尔森计算函数
3.1、用户数据归类
我们从原始数据文件里面可以看到对应的是三个字段,用户id,电影id,用户的打分,那么我们需要把每一个用户所有的数据都进行下归类,代码如下:
// 根据用户id,对用户打分进行分组,形如{"userId":1,"user_movies_rates":[{"index":4,"moduleId":61,"useId":1},{"index":3,"moduleId":189,"useId":1}]}
Map<Integer, List<RelateDTO>> userMap = list.stream().collect(Collectors.groupingBy(RelateDTO::getUseId));3.2、编写皮尔森计算函数
/**
* 计算两个序列间的相关系数
*
* @param xList 这个是非当前用户的爱好打分
* @param yList 这个是当前用的爱好打分
* @return
*/
private double pearson_dis(List<RelateDTO> xList, List<RelateDTO> yList) {
// x坐标
List<Integer> xs = Lists.newArrayList();
// y坐标
List<Integer> ys = Lists.newArrayList();
xList.forEach(x -> {
yList.forEach(y -> {
// 如果两个数据的电影id相同的话
if (x.getModuleId() == y.getModuleId()) {
// 把他们的打分情况加进去
xs.add(x.getIndex());
ys.add(y.getIndex());
}
});
});
// 上面形成了一个新的x,y坐标,x坐标还是非当前用户的,y坐标是当前用户的,例如:x集合为[5,2],y集合为[5,4] 备注:这个数据是第一组的数据
return getRelate(xs, ys);
}/**
* 方法描述: 皮尔森(pearson)相关系数计算,协同过滤算法中的皮尔森相关系数公式
*
*
* (1)、当相关系数为0时,X和Y两变量无关系。
*
* (2)、当X的值增大(减小),Y值增大(减小),两个变量为正相关,相关系数在0.00与1.00之间。
*
* (3)、当X的值增大(减小),Y值减小(增大),两个变量为负相关,相关系数在-1.00与0.00之间。
*
*
* 通常情况下通过以下取值范围判断变量的相关强度: 相关系数 : 0.8-1.0 极强相关 0.6-0.8 强相关 0.4-0.6 中等程度相关
* 0.2-0.4 弱相关 0.0-0.2 极弱相关或无相关
*
*/
public static Double getRelate(List<Integer> xs, List<Integer> ys) {
// [5,2],[5,4]
int n = xs.size();
double Ex = xs.stream().mapToDouble(x -> x).sum();
double Ey = ys.stream().mapToDouble(y -> y).sum();
double Ex2 = xs.stream().mapToDouble(x -> Math.pow(x, 2)).sum();
double Ey2 = ys.stream().mapToDouble(y -> Math.pow(y, 2)).sum();
double Exy = IntStream.range(0, n).mapToDouble(i -> xs.get(i) * ys.get(i)).sum();
double numerator = Exy - Ex * Ey / n;
double denominator = Math.sqrt((Ex2 - Math.pow(Ex, 2) / n) * (Ey2 - Math.pow(Ey, 2) / n));
if (denominator == 0)
return 0.0;
return numerator / denominator;
}3.3、把计算出来的所有皮尔森相关系数进行排序
/**
* 在给定userId的情况下,计算其他用户和它的相关系数并排序
*
* @param userId
* @param list
* @return
*/
private Map<Double, Integer> computeNearestNeighbor(Integer userId, List<RelateDTO> list) {
// 根据用户id,对用户打分进行分组,形如{"userId":1,"user_movies_rates":[{"index":4,"moduleId":61,"useId":1},{"index":3,"moduleId":189,"useId":1}]}
Map<Integer, List<RelateDTO>> userMap = list.stream().collect(Collectors.groupingBy(RelateDTO::getUseId));
Map<Double, Integer> distances = new TreeMap<>();
userMap.forEach((k, v) -> {
// 这里根据group之后的数据,计算不同于传递进来的userid的用户的距离数据
if (k != userId) {
// 使用皮尔森系数相关公式计算相关度,
double distance = pearson_dis(v, userMap.get(userId));
// 最后行程一个相关度+userid的map集合,例如:(0.2,5)
distances.put(distance, k);
}
});
return distances;
}四、获取最相关的相关性数据
// 上面的相关度已经使用treemap进行排序了,默认是从小到大,在皮尔森算法中,相关度为1或者-1的时候,相关度是最大的,也就是皮尔森计算的是绝对值,所以我们一般取第一个用户即可。 Integer nearest = distances.values().iterator().next();
这个方法获取到的就是最相关的那一个用户。所以这里的nearest是最相关的用户id。
五、找出相关的电影列表进行推荐
5.1、获取相关用户看过的电影
上面我们获取到了最相关的用户,同时在前面也根据用户进行了group by分组操作。因此这里我们根据用户id,获取出当前用户看过的电影
// 最近邻用户看过电影列表 List<Integer> neighborItemList = userMap.get(nearest).stream().map(e -> e.getModuleId()).collect(Collectors.toList());
5.2、获取当前用户看过的电影
当前用户看过的电影就不需要再推荐了
// 指定用户看过电影列表 List<Integer> userItemList = userMap.get(userId).stream().map(e -> e.getModuleId()).collect(Collectors.toList());
5.3、找出两个电影的差集作为电影的推荐列表
把相似用户看过的电影排除掉当前用户已经看过的,就是我们最后想要得到的电影列表
// 找到最近邻看过,但是该用户没看过的电影,计算推荐,放入推荐列表
List<Integer> recommendList = new ArrayList<>();
for (Integer item : neighborItemList) {
if (!userItemList.contains(item)) {
recommendList.add(item);
}
}
Collections.sort(recommendList);以上我们就获取到了完整的电影推荐列表了。
六、测试获取对应的电影列表
public static void main(String[] args) {
List<ItemDTO> itemList = Recommend.guessUserLike(1);
System.out.println("------猜你可能喜欢---------------下列电影");
itemList.forEach(e -> System.out.println(e.getName()));
}6.1、这里我们测试传入用户id为1的用户,看下他推荐的是什么电影
6.2、这里我们测试传入用户id为2的用户,看下他推荐的是什么电影
6.3、这里我们测试传入用户id为3的用户,看下他推荐的是什么电影
以上我们可以看到每个人获取到的数据都是不一样的,这样就完成了很简单的千人千面的个性化推荐展示。
备注:
1、后期我们会演示通过大数据的方式完成千人千面的个性化展示,使其更趋近于生产环境的使用,本文主要是简单的编写一个演示示例,为了给大家看下整个千人千面的个性化展示的效果。
最后附上本文的代码下载和数据集下载,登录就可以看到。





还没有评论,来说两句吧...