如何构建自己的预测算法

本页介绍如何使用 Surprise 构建自定义预测算法。

基础知识

想亲手实践一下吗?太棒了。

创建自己的预测算法非常简单:算法只是一个派生自 AlgoBase 的类,它有一个 estimate 方法。这个方法是由 predict() 方法调用的。它接受一个 **内部** 用户 ID 和一个 **内部** 项目 ID(参见 此说明),并返回估计评分 \(\hat{r}_{ui}\)

来自文件 examples/building_custom_algorithms/most_basic_algorithm.py
from surprise import AlgoBase, Dataset
from surprise.model_selection import cross_validate


class MyOwnAlgorithm(AlgoBase):
    def __init__(self):

        # Always call base method before doing anything.
        AlgoBase.__init__(self)

    def estimate(self, u, i):

        return 3


data = Dataset.load_builtin("ml-100k")
algo = MyOwnAlgorithm()

cross_validate(algo, data, verbose=True)

这个算法是我们能想到的最简单的:它只是预测评分为 3,无论用户和项目是什么。

如果你想存储关于预测的额外信息,你也可以返回一个包含指定详细信息的字典

def estimate(self, u, i):

    details = {
        'info1' : 'That was',
        'info2' : 'easy stuff :)'
    }
    return 3, details

这个字典将作为 details 字段存储在 prediction 中,并可用于 后续分析

fit 方法

现在,让我们创建一个稍微聪明一点的算法,它预测训练集(trainset)中所有评分的平均值。由于这是一个不依赖于当前用户或项目的常量值,我们宁愿一次性计算好。这可以通过定义 fit 方法来实现

来自文件 examples/building_custom_algorithms/most_basic_algorithm2.py
class MyOwnAlgorithm(AlgoBase):
    def __init__(self):

        # Always call base method before doing anything.
        AlgoBase.__init__(self)

    def fit(self, trainset):

        # Here again: call base method before doing anything.
        AlgoBase.fit(self, trainset)

        # Compute the average rating. We might as well use the
        # trainset.global_mean attribute ;)
        self.the_mean = np.mean([r for (_, _, r) in self.trainset.all_ratings()])

        return self

    def estimate(self, u, i):

        return self.the_mean

例如,在交叉验证过程的每个折叠中,fit 方法会由 cross_validate 函数调用(但你也可以 自己调用它)。在做任何事情之前,你应该调用基类的 fit() 方法。

请注意,fit() 方法返回 self。这允许使用像 algo.fit(trainset).test(testset) 这样的表达式。

trainset 属性

一旦基类 fit() 方法返回,所有关于当前训练集(评分值等)所需的信息都存储在 self.trainset 属性中。这是一个 Trainset 对象,包含许多对预测有用的属性和方法。

为了说明其用法,让我们创建一个算法,它预测所有评分的平均值、用户平均评分和项目平均评分之间的平均值

来自文件 examples/building_custom_algorithms/mean_rating_user_item.py
    def estimate(self, u, i):

        sum_means = self.trainset.global_mean
        div = 1

        if self.trainset.knows_user(u):
            sum_means += np.mean([r for (_, r) in self.trainset.ur[u]])
            div += 1
        if self.trainset.knows_item(i):
            sum_means += np.mean([r for (_, r) in self.trainset.ir[i]])
            div += 1

        return sum_means / div

请注意,在 fit 方法中计算所有用户的平均值是一个更好的主意,这样可以避免多次进行相同的计算。

当预测不可能时

你的算法可以决定是否能够产生预测。如果预测不可能,你可以引发 PredictionImpossible 异常。你需要先导入它

from surprise import PredictionImpossible

这个异常会被 predict() 方法捕获,估计值 \(\hat{r}_{ui}\) 会根据可以重写的 default_prediction() 方法设置。默认情况下,它返回训练集(trainset)中所有评分的平均值。

使用相似度和基线

如果你的算法使用了相似性度量或基线估计,你需要在 __init__ 方法中接受 bsl_optionssim_options 作为参数,并将它们传递给基类。关于如何使用这些参数,请参见 使用预测算法 部分。

方法 compute_baselines()compute_similarities() 可以在 fit 方法中调用(或在其他任何地方)。

来自文件 examples/building_custom_algorithms/.with_baselines_or_sim.py
class MyOwnAlgorithm(AlgoBase):
    def __init__(self, sim_options={}, bsl_options={}):

        AlgoBase.__init__(self, sim_options=sim_options, bsl_options=bsl_options)

    def fit(self, trainset):

        AlgoBase.fit(self, trainset)

        # Compute baselines and similarities
        self.bu, self.bi = self.compute_baselines()
        self.sim = self.compute_similarities()

        return self

    def estimate(self, u, i):

        if not (self.trainset.knows_user(u) and self.trainset.knows_item(i)):
            raise PredictionImpossible("User and/or item is unknown.")

        # Compute similarities between u and v, where v describes all other
        # users that have also rated item i.
        neighbors = [(v, self.sim[u, v]) for (v, r) in self.trainset.ir[i]]
        # Sort these neighbors by similarity
        neighbors = sorted(neighbors, key=lambda x: x[1], reverse=True)

        print("The 3 nearest neighbors of user", str(u), "are:")
        for v, sim_uv in neighbors[:3]:
            print(f"user {v} with sim {sim_uv:1.2f}")

        # ... Aaaaand return the baseline estimate anyway ;)
        bsl = self.trainset.global_mean + self.bu[u] + self.bi[i]
        return bsl

欢迎随意探索 prediction_algorithms 包的源代码,以了解可以实现哪些功能。