协同过滤(简称CF)是推荐系统最重要的思想之一。在早期,协同过滤几乎等同于推荐系统。主要的功能是预测和推荐。算法通过对用户历史行为数据的挖掘发现用户的偏好,基于不同的偏好对用户进行群组划分并推荐品味相似的商品。协同过滤推荐算法分为两类,分别是:1、基于用户的协同过滤算法(user-based collaboratIve filtering)(相似的用户可能喜欢相同物品);2、基于物品的协同过滤算法(item-based collaborative filtering)(相似的物品可能被同个用户喜欢)。
Java代码实现
Movie电影类:
package com.jun.entity;
/**
* @author junfeng.lin
* @date 2021/3/18 13:37
*/
public class Movie implements Comparable<Movie> {
public String movieName;
public int score;
public Movie(String movieName, int score) {
this.movieName = movieName;
this.score = score;
}
@Override
public String toString() {
return "Movie{" +
"movieName='" + movieName + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Movie o) {
return score > o.score ? -1 : 1;
}
}
User用户类:
package com.jun.entity;
import java.util.ArrayList;
import java.util.List;
/**
* @author junfeng.lin
* @date 2021/3/18 13:39
*/
public class User {
public String username;
public List<Movie> movieList = new ArrayList<>();
public User() {}
public User(String username) {
this.username = username;
}
public User set(String movieName, int score) {
this.movieList.add(new Movie(movieName, score));
return this;
}
public Movie find(String movieName) {
for (Movie movie : movieList) {
if (movie.movieName.equals(username)) {
return movie;
}
}
return null;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
'}';
}
}
Recommend逻辑计算类:
package com.jun.Algorithm;
import com.jun.entity.Movie;
import com.jun.entity.User;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author junfeng.lin
* @date 2021/3/18 13:42
*/
public class Recommend {
/**
* 在给定username的情况下,计算其他用户和它的距离并排序
* @param username
* @return
*/
private Map<Double, String> computeNearestNeighbor(String username, List<User> users) {
Map<Double, String> distances = new TreeMap<>();
User u1 = new User(username);
for (User user:users) {
if (username.equals(user.username)) {
u1 = user;
}
}
for (int i = 0; i < users.size(); i++) {
User u2 = users.get(i);
if (!u2.username.equals(username)) {
double distance = pearson_dis(u2.movieList, u1.movieList);
distances.put(distance, u2.username);
}
}
System.out.println("该用户与其他用户的皮尔森相关系数 -> " + distances);
return distances;
}
/**
* 计算2个打分序列间的pearson距离
* 选择公式四进行计算
* @param rating1
* @param rating2
* @return
*/
private double pearson_dis(List<Movie> rating1, List<Movie> rating2) {
int n=rating1.size();
List<Integer> rating1ScoreCollect = rating1.stream().map(A -> A.score).collect(Collectors.toList());
List<Integer> rating2ScoreCollect = rating2.stream().map(A -> A.score).collect(Collectors.toList());
double Ex= rating1ScoreCollect.stream().mapToDouble(x->x).sum();
double Ey= rating2ScoreCollect.stream().mapToDouble(y->y).sum();
double Ex2=rating1ScoreCollect.stream().mapToDouble(x->Math.pow(x,2)).sum();
double Ey2=rating2ScoreCollect.stream().mapToDouble(y->Math.pow(y,2)).sum();
double Exy= IntStream.range(0,n).mapToDouble(i->rating1ScoreCollect.get(i)*rating2ScoreCollect.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;
}
public List<Movie> recommend(String username, List<User> users) {
//找到最近邻
Map<Double, String> distances = computeNearestNeighbor(username, users);
String nearest = distances.values().iterator().next();
System.out.println("最近邻 -> " + nearest);
//找到最近邻看过,但是我们没看过的电影,计算推荐
User neighborRatings = new User();
for (User user:users) {
if (nearest.equals(user.username)) {
neighborRatings = user;
}
}
System.out.println("最近邻看过的电影 -> " + neighborRatings.movieList);
User userRatings = new User();
for (User user:users) {
if (username.equals(user.username)) {
userRatings = user;
}
}
System.out.println("用户看过的电影 -> " + userRatings.movieList);
//根据自己和邻居的电影计算推荐的电影
List<Movie> recommendationMovies = new ArrayList<>();
for (Movie movie : neighborRatings.movieList) {
if (userRatings.find(movie.movieName) == null) {
recommendationMovies.add(movie);
}
}
Collections.sort(recommendationMovies);
return recommendationMovies;
}
}
Demo测试类:
package com.jun.Main;
import com.jun.Algorithm.Recommend;
import com.jun.entity.Movie;
import com.jun.entity.User;
import java.util.ArrayList;
import java.util.List;
/**
* @author junfeng.lin
* @date 2021/3/18 14:07
*/
public class Demo {
public static void main(String[] args) {
//输入用户总量
List<User> users = new ArrayList<>();
users.add(new User("小明")
.set("中国合伙人", 50)
.set("太平轮", 30)
.set("荒野猎人", 45)
.set("老炮儿", 50)
.set("我的少女时代", 30)
.set("肖洛特烦恼", 45)
.set("火星救援", 50));
users.add(new User("小红")
.set("小时代4", 40)
.set("荒野猎人", 30)
.set("我的少女时代", 50)
.set("肖洛特烦恼", 50)
.set("火星救援", 30)
.set("后会无期", 30));
users.add(new User("小阳")
.set("小时代4", 20)
.set("中国合伙人", 50)
.set("我的少女时代", 30)
.set("老炮儿", 50)
.set("肖洛特烦恼", 45)
.set("速度与激情7", 50));
users.add(new User("小四")
.set("小时代4", 50)
.set("中国合伙人", 30)
.set("我的少女时代", 40)
.set("匆匆那年", 40)
.set("速度与激情7", 35)
.set("火星救援", 35)
.set("后会无期", 45));
users.add(new User("六爷")
.set("小时代4", 20)
.set("中国合伙人", 40)
.set("荒野猎人", 45)
.set("老炮儿", 50)
.set("我的少女时代", 20));
users.add(new User("小李")
.set("荒野猎人", 50)
.set("盗梦空间", 50)
.set("我的少女时代", 30)
.set("速度与激情7", 50)
.set("蚁人", 45)
.set("老炮儿", 40)
.set("后会无期", 35));
users.add(new User("隔壁老王")
.set("荒野猎人", 50)
.set("中国合伙人", 40)
.set("我的少女时代", 10)
.set("Phoenix", 50)
.set("甄嬛传", 40)
.set("The Strokes", 50));
users.add(new User("邻村小芳")
.set("小时代4", 40)
.set("我的少女时代", 45)
.set("匆匆那年", 45)
.set("甄嬛传", 25)
.set("The Strokes", 30));
Recommend recommend = new Recommend();
List<Movie> recommendationMovies = recommend.recommend("小明", users);
System.out.println("-----------------------");
System.out.println("推荐结果如下:");
for (Movie movie : recommendationMovies) {
System.out.println("电影:"+movie.movieName+" ,评分:"+movie.score);
}
}
}
运行结果:
参考资料
https://blog.csdn.net/MiSiTeLin/article/details/114978781