如何推荐下一个成就

5 投票
2 回答
669 浏览
提问于 2025-04-15 12:41

简短版本:

我有一个和StackOverflow类似的系统。用户可以获得成就。我有的成就比SO多得多,差不多有1万个,每个用户大约有几百个成就。那么,你会怎么建议用户下一个应该尝试的成就呢?

详细版本:

在django中,我是这样建模的(只展示重要部分):

class User(models.Model):
    alias = models.ForeignKey(Alias)

class Alias(models.Model):
    achievements = models.ManyToManyField('Achievement', through='Achiever')

class Achievement(models.Model):
    points = models.IntegerField()

class Achiever(models.Model):
    achievement = models.ForeignKey(Achievement)
    alias = models.ForeignKey(Alias)
    count = models.IntegerField(default=1)

我的算法是找到每个和当前登录用户有共同成就的其他用户,然后查看他们的所有成就,并按出现次数排序:

def recommended(request) :
    user = request.user.get_profile()

    // The final response
    r = {}

    // Get all the achievements the user's aliases have received 
    // in a set so they aren't double counted
    achievements = set()
    for alias in user.alias_set.select_related('achievements').all() :
        achievements.update(alias.achievements.all())

    // Find all other aliases that have gotten at least one of the same
    // same achievements as the user
    otherAliases = set()
    for ach in achievements :
        otherAliases.update(ach.alias_set.all())

    // Find other achievements the other users have gotten in addition to
    // the shared ones.
    // And count the number of times each achievement appears
    for otherAlias in otherAliases :
        for otherAch in otherAlias.achievements.all() :
            r[otherAch] = r.get(otherAch, 0) + 1

    // Remove all the achievements that the user has already gotten
    for ach in achievements :
        r.pop(ach)

    // Sort by number of times the achievements have been received
    r = sorted(r.items(), lambda x, y: cmp(x[1], y[1]), reverse=True)

    // Put in the template for showing on the screen
    template_values = {}
    template_values['achievements'] = r

但是这个过程运行得非常慢,而且总是返回整个列表,其实用户只需要前几个最重要的成就来追求。

所以,我欢迎大家推荐其他算法和/或代码改进的方法。如果你能给我一个好的推荐算法,我会在我的系统中给你一个成就奖励 :)

2 个回答

2

我建议你把前面提到的三个步骤(成就、其他别名、计数)合并成一个SQL语句。现在你发出了很多查询请求,然后在Python中处理成千上万的行数据,这个工作其实应该交给数据库来做。比如下面这段代码

for otherAlias in otherAliases : #For every single other user
    for otherAch in otherAlias.achievements.all() : #execute a query
        r[otherAch] = r.get(otherAch, 0) + 1

会发出成千上万的复杂查询。

相反,你可以通过SQL来完成这个任务,方法是将“Achiever”表与自身连接,条件是别名ID不同而成就ID相同。然后你可以根据成就ID进行分组,并计算数量。

在下面的查询中,表“B”是其他用户的成就,而“Achiever”是我们自己的成就。如果其他用户分享了某个成就,他们在“B”中会为每个分享的成就出现一次。接着,我们根据别名ID进行分组,并计算他们出现的次数,这样你就能得到一个包含ID和计数的整洁表格。

这段代码非常粗略(这里没有SQL可用)

SELECT B.Alias_id, COUNT(B.achievement_id) 
  FROM Achiever, Achiever as B 
  WHERE Achiever.achievement_id == B.achievement_id 
     AND Achiever.Alias_id == <insert current user alias here>;
  GROUP BY B.Alias_id

如果这个方法按我想的那样工作,你将得到一个包含其他用户别名的表格,以及他们与当前用户共享的成就数量。

接下来,你要写一个SQL语句,使用上面的查询作为“内部选择”——可以叫它用户。然后将这个结果与当前用户的成就表和Achiever表连接。你可能只想关注与当前用户相似的前10个用户。

我现在没时间写一个好的查询,但你可以看看JOIN语句,连接这10个用户和当前用户之间的成就ID——如果不存在,就把这个ID设置为NULL。然后只筛选出那些结果为NULL的行(未完成的成就)。

3

一种推荐成就的方法是看看有多少用户已经获得了这些成就,然后推荐那些受欢迎的成就。当用户完成了这些成就后,再逐渐推荐那些稍微不那么受欢迎的。不过,这种方法有个简单的假设,就是大家都想追求热门成就。这可能会导致热门成就变得更热门,而不那么受欢迎的成就就会被忽视。好在这种方法不需要太多资源,运行起来也很快。(只需保持一个成就列表和每个成就被完成的次数)

另一种方法是使用一些机器学习算法,试图根据用户已经获得的成就来猜测他们可能会追求哪些成就。我觉得k最近邻算法在这里会表现得不错。你可以设定一个阈值,只输出高于这个阈值的成就。现在,我不确定这种方法是否比你现在的方案更快,但你可以在用户获得新成就时运行推荐系统,保存前五个推荐,然后在需要推荐时再把这些推荐给用户。

希望这些对你有帮助。=)

撰写回答