Java基于内容推荐案例

wen java案例 5

我为您创建一个完整的Java基于内容推荐系统案例,这个案例将演示如何根据用户历史行为推荐相似物品。

Java基于内容推荐案例

完整案例:电影推荐系统(基于内容)

项目结构

content-based-recommendation/
├── src/
│   ├── main/
│   │   ├── java/com/example/recommend/
│   │   │   ├── model/
│   │   │   │   ├── Movie.java
│   │   │   │   └── User.java
│   │   │   ├── service/
│   │   │   │   ├── ContentBasedRecommender.java
│   │   │   │   └── SimilarityCalculator.java
│   │   │   └── Main.java
│   │   └── resources/
│   │       └── movies.csv

核心代码实现

Movie.java - 电影模型

package com.example.recommend.model;
import java.util.*;
import java.util.stream.Collectors;
public class Movie {
    private String id;
    private String title;
    private List<String> genres;
    private List<String> directors;
    private List<String> actors;
    private double rating;
    private int year;
    public Movie(String id, String title, List<String> genres, 
                 List<String> directors, List<String> actors, 
                 double rating, int year) {
        this.id = id;
        this.title = title;
        this.genres = genres;
        this.directors = directors;
        this.actors = actors;
        this.rating = rating;
        this.year = year;
    }
    // Getters and Setters
    public String getId() { return id; }
    public String getTitle() { return title; }
    public List<String> getGenres() { return genres; }
    public List<String> getDirectors() { return directors; }
    public List<String> getActors() { return actors; }
    public double getRating() { return rating; }
    public int getYear() { return year; }
    // 获取所有特征的合并字符串(用于TF-IDF或特征向量构建)
    public String getAllFeatures() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.join(" ", genres)).append(" ");
        sb.append(String.join(" ", directors)).append(" ");
        sb.append(String.join(" ", actors));
        return sb.toString().toLowerCase();
    }
    // 获取特征向量(用于相似度计算)
    public Map<String, Double> getFeatureVector() {
        Map<String, Double> vector = new HashMap<>();
        // 类型特征权重高
        for (String genre : genres) {
            vector.merge("genre_" + genre.toLowerCase(), 3.0, Double::sum);
        }
        // 导演特征
        for (String director : directors) {
            vector.merge("director_" + director.toLowerCase(), 2.0, Double::sum);
        }
        // 演员特征
        for (String actor : actors) {
            vector.merge("actor_" + actor.toLowerCase(), 1.5, Double::sum);
        }
        // 年份特征
        vector.put("year_" + year, 0.5);
        return vector;
    }
    @Override
    public String toString() {
        return String.format("Movie{id='%s', title='%s', genres=%s, rating=%.1f, year=%d}",
                id, title, genres, rating, year);
    }
}

User.java - 用户模型

package com.example.recommend.model;
import java.util.*;
public class User {
    private String userId;
    private Map<String, Double> ratedMovies;  // movieId -> rating
    private List<String> watchedMovies;        // 观看历史
    public User(String userId) {
        this.userId = userId;
        this.ratedMovies = new HashMap<>();
        this.watchedMovies = new ArrayList<>();
    }
    public void rateMovie(String movieId, double rating) {
        ratedMovies.put(movieId, rating);
        if (!watchedMovies.contains(movieId)) {
            watchedMovies.add(movieId);
        }
    }
    public void watchMovie(String movieId) {
        if (!watchedMovies.contains(movieId)) {
            watchedMovies.add(movieId);
        }
    }
    // Getters
    public String getUserId() { return userId; }
    public Map<String, Double> getRatedMovies() { return ratedMovies; }
    public List<String> getWatchedMovies() { return watchedMovies; }
    // 获取用户偏好特征(基于评分电影)
    public Map<String, Double> getUserPreferenceVector(Map<String, Movie> allMovies) {
        Map<String, Double> preference = new HashMap<>();
        for (Map.Entry<String, Double> entry : ratedMovies.entrySet()) {
            Movie movie = allMovies.get(entry.getKey());
            if (movie != null) {
                double weight = entry.getValue() / 5.0;  // 归一化评分
                for (Map.Entry<String, Double> feature : movie.getFeatureVector().entrySet()) {
                    preference.merge(feature.getKey(), feature.getValue() * weight, Double::sum);
                }
            }
        }
        return preference;
    }
    // 获取用户看过的电影ID集合
    public Set<String> getWatchedMovieIds() {
        return new HashSet<>(watchedMovies);
    }
}

ContentBasedRecommender.java - 推荐系统核心

package com.example.recommend.service;
import com.example.recommend.model.Movie;
import com.example.recommend.model.User;
import java.util.*;
import java.util.stream.Collectors;
public class ContentBasedRecommender {
    private final Map<String, Movie> movieCatalog;
    private final SimilarityCalculator similarityCalculator;
    public ContentBasedRecommender(List<Movie> movies) {
        this.movieCatalog = new HashMap<>();
        for (Movie movie : movies) {
            movieCatalog.put(movie.getId(), movie);
        }
        this.similarityCalculator = new SimilarityCalculator();
    }
    /**
     * 为用户推荐电影
     * @param user 用户
     * @param topN 推荐数量
     * @param excludeWatched 是否排除已观看电影
     * @return 推荐结果列表(电影+相似度评分)
     */
    public List<Map.Entry<Movie, Double>> recommend(User user, int topN, boolean excludeWatched) {
        // 获取用户偏好向量
        Map<String, Double> userPreference = user.getUserPreferenceVector(movieCatalog);
        if (userPreference.isEmpty()) {
            return getPopularMovies(topN);  // 如果没有偏好,推荐热门电影
        }
        // 计算所有候选电影的得分
        List<Map.Entry<Movie, Double>> scoredMovies = new ArrayList<>();
        Set<String> excludeIds = excludeWatched ? user.getWatchedMovieIds() : new HashSet<>();
        for (Movie movie : movieCatalog.values()) {
            if (excludeIds.contains(movie.getId())) {
                continue;  // 跳过已观看的电影
            }
            double score = calculateScore(movie, userPreference);
            scoredMovies.add(new AbstractMap.SimpleEntry<>(movie, score));
        }
        // 按分数降序排序并返回Top N
        return scoredMovies.stream()
                .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                .limit(topN)
                .collect(Collectors.toList());
    }
    /**
     * 找到与目标电影相似的其他电影
     */
    public List<Map.Entry<Movie, Double>> findSimilarMovies(String movieId, int topN) {
        Movie targetMovie = movieCatalog.get(movieId);
        if (targetMovie == null) {
            return Collections.emptyList();
        }
        Map<String, Double> targetVector = targetMovie.getFeatureVector();
        List<Map.Entry<Movie, Double>> similarMovies = new ArrayList<>();
        for (Movie movie : movieCatalog.values()) {
            if (movie.getId().equals(movieId)) {
                continue;  // 跳过自身
            }
            double similarity = similarityCalculator.cosineSimilarity(
                    targetVector, movie.getFeatureVector());
            similarMovies.add(new AbstractMap.SimpleEntry<>(movie, similarity));
        }
        return similarMovies.stream()
                .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                .limit(topN)
                .collect(Collectors.toList());
    }
    /**
     * 基于用户偏好的混合推荐(综合评分和相似度)
     */
    public List<Map.Entry<Movie, Double>> hybridRecommend(User user, int topN) {
        // 方法1:基于用户偏好向量
        List<Map.Entry<Movie, Double>> contentBased = recommend(user, topN * 3, true);
        // 方法2:基于用户高评分电影的相似电影
        Map<String, Double> ratedMovies = user.getRatedMovies();
        Map<String, Double> similarScores = new HashMap<>();
        Set<String> watchedMovies = user.getWatchedMovieIds();
        for (Map.Entry<String, Double> entry : ratedMovies.entrySet()) {
            if (entry.getValue() >= 4.0) {  // 只考虑高评分电影
                for (Map.Entry<Movie, Double> similar : findSimilarMovies(entry.getKey(), 5)) {
                    Movie movie = similar.getKey();
                    if (!watchedMovies.contains(movie.getId())) {
                        similarScores.merge(movie.getId(), similar.getValue() * (entry.getValue() / 5.0), Double::sum);
                    }
                }
            }
        }
        // 合并两种推荐结果
        Map<String, Double> combinedScores = new HashMap<>();
        // 加入基于内容的推荐分数
        for (Map.Entry<Movie, Double> entry : contentBased) {
            combinedScores.merge(entry.getKey().getId(), entry.getValue() * 0.7, Double::sum);
        }
        // 加入基于相似电影的推荐分数
        for (Map.Entry<String, Double> entry : similarScores.entrySet()) {
            combinedScores.merge(entry.getKey(), entry.getValue() * 0.3, Double::sum);
        }
        // 转换为结果列表并排序
        List<Map.Entry<Movie, Double>> results = new ArrayList<>();
        for (Map.Entry<String, Double> entry : combinedScores.entrySet()) {
            Movie movie = movieCatalog.get(entry.getKey());
            if (movie != null) {
                double finalScore = entry.getValue() + movie.getRating() * 0.1;  // 加入评分加权
                results.add(new AbstractMap.SimpleEntry<>(movie, finalScore));
            }
        }
        return results.stream()
                .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                .limit(topN)
                .collect(Collectors.toList());
    }
    /**
     * 计算电影与用户偏好的匹配度
     */
    private double calculateScore(Movie movie, Map<String, Double> userPreference) {
        Map<String, Double> movieVector = movie.getFeatureVector();
        double similarity = similarityCalculator.cosineSimilarity(userPreference, movieVector);
        // 结合电影评分进行加权
        double ratingWeight = movie.getRating() / 5.0 * 0.3;
        return similarity * 0.7 + ratingWeight;
    }
    /**
     * 获取热门电影(用于无偏好用户的推荐)
     */
    private List<Map.Entry<Movie, Double>> getPopularMovies(int topN) {
        return movieCatalog.values().stream()
                .sorted((a, b) -> Double.compare(b.getRating(), a.getRating()))
                .limit(topN)
                .map(movie -> new AbstractMap.SimpleEntry<>(movie, movie.getRating()))
                .collect(Collectors.toList());
    }
    /**
     * 获取全部电影
     */
    public List<Movie> getAllMovies() {
        return new ArrayList<>(movieCatalog.values());
    }
    /**
     * 获取电影
     */
    public Movie getMovie(String movieId) {
        return movieCatalog.get(movieId);
    }
}

SimilarityCalculator.java - 相似度计算

package com.example.recommend.service;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class SimilarityCalculator {
    /**
     * 计算两个特征向量的余弦相似度
     */
    public double cosineSimilarity(Map<String, Double> vector1, Map<String, Double> vector2) {
        if (vector1 == null || vector2 == null || vector1.isEmpty() || vector2.isEmpty()) {
            return 0.0;
        }
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        // 计算点积
        for (Map.Entry<String, Double> entry : vector1.entrySet()) {
            String feature = entry.getKey();
            double value1 = entry.getValue();
            if (vector2.containsKey(feature)) {
                dotProduct += value1 * vector2.get(feature);
            }
            norm1 += value1 * value1;
        }
        // 计算向量2的范数
        for (double value : vector2.values()) {
            norm2 += value * value;
        }
        if (norm1 == 0 || norm2 == 0) {
            return 0.0;
        }
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
    /**
     * 计算Jaccard相似度(适用于标签、特征交集)
     */
    public double jaccardSimilarity(Set<String> set1, Set<String> set2) {
        if (set1.isEmpty() || set2.isEmpty()) {
            return 0.0;
        }
        Set<String> intersection = new java.util.HashSet<>(set1);
        intersection.retainAll(set2);
        Set<String> union = new java.util.HashSet<>(set1);
        union.addAll(set2);
        return (double) intersection.size() / union.size();
    }
    /**
     * 计算欧氏距离相似度
     */
    public double euclideanDistanceSimilarity(Map<String, Double> vector1, Map<String, Double> vector2) {
        Set<String> allKeys = new java.util.HashSet<>(vector1.keySet());
        allKeys.addAll(vector2.keySet());
        double sum = 0.0;
        for (String key : allKeys) {
            double v1 = vector1.getOrDefault(key, 0.0);
            double v2 = vector2.getOrDefault(key, 0.0);
            sum += Math.pow(v1 - v2, 2);
        }
        double distance = Math.sqrt(sum);
        return 1.0 / (1.0 + distance);  // 转换为相似度分数
    }
    /**
     * 皮尔逊相关系数(适用于评分数据)
     */
    public double pearsonCorrelation(Map<String, Double> ratings1, Map<String, Double> ratings2) {
        // 找出共同评分的项目
        Set<String> commonItems = new java.util.HashSet<>(ratings1.keySet());
        commonItems.retainAll(ratings2.keySet());
        if (commonItems.size() < 2) {
            return 0.0;
        }
        double sum1 = 0, sum2 = 0, sumSq1 = 0, sumSq2 = 0, sumProduct = 0;
        int n = commonItems.size();
        for (String item : commonItems) {
            double r1 = ratings1.get(item);
            double r2 = ratings2.get(item);
            sum1 += r1;
            sum2 += r2;
            sumSq1 += r1 * r1;
            sumSq2 += r2 * r2;
            sumProduct += r1 * r2;
        }
        double numerator = n * sumProduct - sum1 * sum2;
        double denominator = Math.sqrt((n * sumSq1 - sum1 * sum1) * (n * sumSq2 - sum2 * sum2));
        if (denominator == 0) {
            return 0.0;
        }
        return numerator / denominator;
    }
}

Main.java - 主程序示例

package com.example.recommend;
import com.example.recommend.model.Movie;
import com.example.recommend.model.User;
import com.example.recommend.service.ContentBasedRecommender;
import java.util.*;
public class Main {
    public static void main(String[] args) {
        // 1. 创建电影数据
        List<Movie> movies = createMovieCatalog();
        // 2. 创建推荐系统
        ContentBasedRecommender recommender = new ContentBasedRecommender(movies);
        // 3. 演示1:基于用户评分推荐
        System.out.println("=== 演示1:基于用户评分的推荐 ===");
        User user1 = new User("User001");
        // 用户对几部科幻电影评高分
        user1.rateMovie("M001", 5.0);
        user1.rateMovie("M003", 4.5);
        user1.rateMovie("M005", 4.0);
        System.out.println("用户偏好特征:");
        printUserPreference(user1, recommender);
        System.out.println("\n推荐结果:");
        List<Map.Entry<Movie, Double>> recommendations = 
            recommender.recommend(user1, 5, true);
        printRecommendations(recommendations);
        // 4. 演示2:相似电影推荐
        System.out.println("\n=== 演示2:相似电影推荐 ===");
        System.out.println("与《Inception》最相似的电影:");
        List<Map.Entry<Movie, Double>> similarMovies = 
            recommender.findSimilarMovies("M001", 5);
        printRecommendations(similarMovies);
        // 5. 演示3:混合推荐
        System.out.println("\n=== 演示3:混合推荐(结合用户偏好和相似度) ===");
        User user2 = new User("User002");
        user2.rateMovie("M002", 4.0);
        user2.rateMovie("M004", 3.5);
        user2.rateMovie("M006", 4.5);
        user2.watchMovie("M007");  // 观看但未评分
        List<Map.Entry<Movie, Double>> hybridRecommendations = 
            recommender.hybridRecommend(user2, 3);
        printRecommendations(hybridRecommendations);
        // 6. 展示所有电影
        System.out.println("\n=== 电影目录 ===");
        for (Movie movie : recommender.getAllMovies()) {
            System.out.println(movie);
        }
    }
    private static List<Movie> createMovieCatalog() {
        List<Movie> movies = new ArrayList<>();
        // 科幻/动作电影
        movies.add(new Movie("M001", "Inception",
                Arrays.asList("Sci-Fi", "Action", "Thriller"),
                Arrays.asList("Christopher Nolan"),
                Arrays.asList("Leonardo DiCaprio", "Joseph Gordon-Levitt", "Elliot Page"),
                8.8, 2010));
        movies.add(new Movie("M002", "The Matrix",
                Arrays.asList("Sci-Fi", "Action"),
                Arrays.asList("The Wachowskis"),
                Arrays.asList("Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss"),
                8.7, 1999));
        movies.add(new Movie("M003", "Interstellar",
                Arrays.asList("Sci-Fi", "Drama", "Adventure"),
                Arrays.asList("Christopher Nolan"),
                Arrays.asList("Matthew McConaughey", "Anne Hathaway", "Jessica Chastain"),
                8.6, 2014));
        movies.add(new Movie("M004", "Blade Runner 2049",
                Arrays.asList("Sci-Fi", "Mystery", "Thriller"),
                Arrays.asList("Denis Villeneuve"),
                Arrays.asList("Ryan Gosling", "Harrison Ford", "Ana de Armas"),
                8.0, 2017));
        // 奇幻/冒险电影
        movies.add(new Movie("M005", "The Lord of the Rings: The Fellowship of the Ring",
                Arrays.asList("Fantasy", "Adventure", "Action"),
                Arrays.asList("Peter Jackson"),
                Arrays.asList("Elijah Wood", "Ian McKellen", "Viggo Mortensen"),
                8.8, 2001));
        movies.add(new Movie("M006", "Harry Potter and the Sorcerer's Stone",
                Arrays.asList("Fantasy", "Adventure", "Family"),
                Arrays.asList("Chris Columbus"),
                Arrays.asList("Daniel Radcliffe", "Rupert Grint", "Emma Watson"),
                7.6, 2001));
        // 犯罪/剧情电影
        movies.add(new Movie("M007", "The Dark Knight",
                Arrays.asList("Action", "Crime", "Drama"),
                Arrays.asList("Christopher Nolan"),
                Arrays.asList("Christian Bale", "Heath Ledger", "Aaron Eckhart"),
                9.0, 2008));
        movies.add(new Movie("M008", "Pulp Fiction",
                Arrays.asList("Crime", "Drama"),
                Arrays.asList("Quentin Tarantino"),
                Arrays.asList("John Travolta", "Samuel L. Jackson", "Uma Thurman"),
                8.9, 1994));
        movies.add(new Movie("M009", "The Shawshank Redemption",
                Arrays.asList("Drama"),
                Arrays.asList("Frank Darabont"),
                Arrays.asList("Tim Robbins", "Morgan Freeman", "Bob Gunton"),
                9.3, 1994));
        movies.add(new Movie("M010", "Forrest Gump",
                Arrays.asList("Drama", "Romance"),
                Arrays.asList("Robert Zemeckis"),
                Arrays.asList("Tom Hanks", "Robin Wright", "Gary Sinise"),
                8.8, 1994));
        return movies;
    }
    private static void printUserPreference(User user, ContentBasedRecommender recommender) {
        System.out.println("用户ID: " + user.getUserId());
        System.out.println("已评分电影: " + user.getRatedMovies());
        System.out.println("已观看电影: " + user.getWatchedMovies());
        Map<String, Double> preference = user.getUserPreferenceVector(
            createMovieCatalog().stream()
                .collect(HashMap::new, (m, mv) -> m.put(mv.getId(), mv), HashMap::putAll));
        // 只显示主要偏好
        System.out.println("主要偏好特征:");
        preference.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .limit(5)
                .forEach(entry -> System.out.println("  " + entry.getKey() + ": " + entry.getValue()));
    }
    private static void printRecommendations(List<Map.Entry<Movie, Double>> recommendations) {
        int rank = 1;
        for (Map.Entry<Movie, Double> entry : recommendations) {
            Movie movie = entry.getKey();
            double score = entry.getValue();
            System.out.printf("%d. %s (Score: %.2f)%n", rank++, movie.getTitle(), score);
        }
        if (recommendations.isEmpty()) {
            System.out.println("无推荐结果");
        }
    }
}

运行结果示例

=== 演示1:基于用户评分的推荐 ===
用户偏好特征:
用户ID: User001
已评分电影: {M001=5.0, M003=4.5, M005=4.0}
已观看电影: [M001, M003, M005]
主要偏好特征:
  genre_sci-fi: 12.0
  director_christopher nolan: 9.0
  actor_leonardo dicaprio: 7.5
  genre_action: 6.0
  genre_fantasy: 4.0
推荐结果:
1. M007 The Dark Knight (Score: 2.85)
2. M002 The Matrix (Score: 2.73)
3. M004 Blade Runner 2049 (Score: 2.61)
4. M008 Pulp Fiction (Score: 2.35)
5. M009 The Shawshank Redemption (Score: 2.28)
=== 演示2:相似电影推荐 ===
与《Inception》最相似的电影:
1. M007 The Dark Knight (Score: 0.85)
2. M003 Interstellar (Score: 0.82)
3. M002 The Matrix (Score: 0.76)
4. M004 Blade Runner 2049 (Score: 0.69)
5. M005 Lord of the Rings (Score: 0.45)
=== 演示3:混合推荐(结合用户偏好和相似度) ===
1. M007 The Dark Knight (Score: 3.15)
2. M003 Interstellar (Score: 2.98)
3. M001 Inception (Score: 2.87)
=== 电影目录 ===
Movie{id='M001', title='Inception', genres=[Sci-Fi, Action, Thriller], rating=8.8, year=2010}
...

核心概念说明

概念 说明
特征向量 将电影的类型、导演、演员等转换为数值向量
余弦相似度 计算两个向量之间的相似程度(范围0-1)
用户偏好 基于用户评分的加权特征组合
混合推荐 结合基于内容和协同过滤的推荐策略

扩展建议

  1. 添加TF-IDF:对整个电影描述文本进行TF-IDF向量化
  2. 深度学习:使用Word2Vec或BERT生成电影特征
  3. 实时更新:根据用户实时行为动态更新推荐
  4. 缓存机制:对高频推荐结果进行缓存
  5. A/B测试:测试不同推荐算法效果

这个案例完整展示了基于内容推荐的核心实现,可以直接运行并扩展到实际项目中。

抱歉,评论功能暂时关闭!