65.9K
CodeProject 正在变化。 阅读更多。
Home

群体智能,基于相似用户品味的项目推荐

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018年3月3日

CPOL

3分钟阅读

viewsIcon

15331

使用协同过滤查找具有相似品味的人,并根据其他人喜欢的物品进行自动推荐。

目录

问题

基于其他具有相似品味的用户的偏好,查找用户感兴趣的项目。

解决方案

一个协同过滤算法通常通过搜索一大群人并找到一小部分与你品味相似的人来工作。

它查看他们喜欢的其他东西,并将它们组合起来创建一个排名的建议列表。

其基本假设是,如果一个人A对某个问题的观点与另一个人B相同,则A更有可能对与B对不同问题的观点相同,而不是随机选择的人。

细则

收集偏好

我们正在使用的數據集是一個文本文件,其標題如下
#日期,动作,用户ID,用户名,文章ID,文章名称

我们需要将其转换为用户及其偏好的矩阵。

@Override
public Map<Integer, Map<Integer, Double>> getUsersArticlesRatings(List<UserAction> userActions) {
    //group by users
    Map<Integer, List<UserAction>> usersActions = userActions
            .stream()
            .collect(groupingBy(UserAction::getUserId));

    //group articles by id, calculate sum of ratings
    Map<Integer, Map<Integer, Double>> usersArticlesRatings = new HashMap<>(usersActions.size());
    for (Map.Entry<Integer, List<UserAction>> entry : usersActions.entrySet()) {
        final Map<Integer, Double> articlesRatings = entry
                .getValue()
                .stream()
                .collect(groupingBy(UserAction::getArticleId, ratingCalculator.getCollector()));
        usersArticlesRatings.put(entry.getKey(), articlesRatings);

    }
    return usersArticlesRatings;
}

用户操作可以是查看下调上调下载
无论如何表达偏好,我们都需要一种方法将它们映射到数值。

public class RatingCalculatorImpl implements RatingCalculator {
    private static Map<Action, Double> ACTION_WEIGHTS;

    static {
        ACTION_WEIGHTS = new HashMap<>();
        ACTION_WEIGHTS.put(Action.View, 1d);
        ACTION_WEIGHTS.put(Action.UpVote, 1d);
        ACTION_WEIGHTS.put(Action.DownVote, -1d);
        ACTION_WEIGHTS.put(Action.Download, 2d);
    }

    @Override
    public Collector<UserAction, ?, Double> getCollector() {
        return summingDouble(value -> ACTION_WEIGHTS.get(value.getAction()));
    }
}

寻找相似用户

在收集到人们喜欢的东西的数据后,我们需要一种方法来确定人们在品味上有多相似。我们通过将每个人与其他每个人进行比较并计算相似度得分来做到这一点。

我们将使用皮尔逊相关系数,它是衡量两个变量XY之间线性相关性的指标,或者衡量两组数据在直线上拟合的程度。

可视化:由**用户1**和**用户141**阅读的3篇文章的评分构成一条直线,相关性为1.0
public class SimilarityCalculatorPearsonCorrelation implements SimilarityCalculator {
    @Override
    public double calculateScore(Map<Integer, Double> firstUserPreferences, 
                                 Map<Integer, Double> secondUserPreferences) {
        List<Integer> commonArticles = getCommonArticles(firstUserPreferences.keySet(), 
                                       secondUserPreferences.keySet());

        if (commonArticles.isEmpty()) {
            return 0;
        }

        double sum1 = 0, sum2 = 0;
        double sumSq1 = 0, sumSq2 = 0;
        double sumProduct = 0;

        for (Integer articleId : commonArticles) {
            final Double user1Rating = firstUserPreferences.get(articleId);
            final Double user2Rating = secondUserPreferences.get(articleId);

            sum1 += user1Rating;
            sum2 += user2Rating;

            sumSq1 += Math.pow(user1Rating, 2);
            sumSq2 += Math.pow(user2Rating, 2);

            final double product = user1Rating * user2Rating;
            sumProduct += product;
        }

        // Calculate Pearson score
        int n = commonArticles.size();
        double num = sumProduct - (sum1 * sum2 / n);
        double den = Math.sqrt((sumSq1 - Math.pow(sum1, 2) / n) * (sumSq2 - Math.pow(sum2, 2) / n));
        if (den == 0) {
            return 0;
        }
        return num / den;
    }

    private List<Integer> getCommonArticles(Set<Integer> firstUserPreferences, 
                          Set<Integer> secondUserPreferences) {
        List<Integer> commonArticles = new ArrayList<>();

        for (Integer entry : firstUserPreferences) {
            if (secondUserPreferences.contains(entry)) {
                commonArticles.add(entry);
            }
        }
        return commonArticles;
    }
}

现在我们可以使用相似度得分来计算其他用户与给定用户的品味相比。

private List<Recommendation> createScoreMatrix(int userId, Map<Integer, Map<Integer, Double>> matrix) {
    Map<Integer, Double> userPreferences = matrix.get(userId) == null ? 
                                           new HashMap<>() : matrix.get(userId);
    List<Recommendation> recommendations = new ArrayList<>(matrix.size());

    for (Map.Entry<Integer, Map<Integer, Double>> entry : matrix.entrySet()) {
        final Integer otherUserId = entry.getKey();
        if (otherUserId == userId) {
            continue;
        }

        final Map<Integer, Double> otherUserPreferences = matrix.get(otherUserId);
        final double sim = similarityCalculator.calculateScore(userPreferences, otherUserPreferences);
        if (sim > 0) {
            // get articles not viewed by userId
            final Map<Integer, Double> preferences = otherUserPreferences
                    .entrySet()
                    .stream()
                    .filter(e -> !userPreferences.containsKey(e.getKey()))
                    .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
            final Recommendation recommendation = new Recommendation(otherUserId, sim, preferences);
            recommendations.add(recommendation);
        }
    }
    return recommendations;
}

推荐项目

使用相似度得分,人们可能会倾向于对具有相似品味的前n个用户进行排名,并寻找他尚未查看的文章,但这种方法可能会意外地找到那些没有评论他可能喜欢的某些文章的评论者。

我们需要通过生成对用户进行排名的加权分数来对文章进行评分。获取所有其他用户的投票,并将他们与给定用户的相似程度乘以他们给予每篇文章的分数。

此表显示了每个用户及其对用户1未评价的三篇文章(760、9和514)的评分的相关性得分。以S.x开头的列给出了相似度乘以评分的结果,因此与用户1相似的人对总分的贡献将大于与用户1不同的人。总行显示所有这些数字的总和。

我们可以直接使用总数来计算排名,但这样的话,由更多用户评论的文章将具有很大的优势。为了纠正这一点,我们需要除以评论该电影的所有评论者的相似度之和(表中的Sim. Sum行)。

private Map<Integer, WeightedScore> createWeightedScore(List<Recommendation> scoreMatrix) {
    Map<Integer, WeightedScore> totalRatings = new HashMap<>();
    scoreMatrix
            .forEach(recommendation -> recommendation
                    .getOtherUserPreferences()
                    .forEach((articleId, rating) -> {
                        if (!totalRatings.containsKey(articleId)) {
                            totalRatings.put(articleId, new WeightedScore(0d, 0d));
                        }
                        final WeightedScore weightedScore = totalRatings.get(articleId);

                        final double total = weightedScore.getRatingSum() + rating;
                        weightedScore.setRatingSum(total);

                        if (recommendation.otherUserPreferences.containsKey(articleId)) {
                            weightedScore.setSimSum(weightedScore.getSimSum() + 
                            recommendation.getSimilarity());
                        }

                        totalRatings.put(articleId, weightedScore);
                    }));
    return totalRatings;
}

现在用户1的推荐是

Article Id, Rating
875:5.0
48:4.0
182:4.0
2427:4.0
483:4.0

我们得到一个推荐文章列表和用户1将如何评价它们的预测分数。

代码

参考文献

© . All rights reserved.