入门

基本用法

自动交叉验证

Surprise 包含了一系列内置的算法数据集供您使用。最简单的形式下,只需几行代码即可运行交叉验证过程

来自文件 examples/basic_usage.py
from surprise import Dataset, SVD
from surprise.model_selection import cross_validate


# Load the movielens-100k dataset (download it if needed),
data = Dataset.load_builtin("ml-100k")

# We'll use the famous SVD algorithm.
algo = SVD()

# Run 5-fold cross-validation and print results
cross_validate(algo, data, measures=["RMSE", "MAE"], cv=5, verbose=True)

结果应如下所示(实际值可能因随机性而异)

Evaluating RMSE, MAE of algorithm SVD on 5 split(s).

            Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std
RMSE        0.9311  0.9370  0.9320  0.9317  0.9391  0.9342  0.0032
MAE         0.7350  0.7375  0.7341  0.7342  0.7375  0.7357  0.0015
Fit time    6.53    7.11    7.23    7.15    3.99    6.40    1.23
Test time   0.26    0.26    0.25    0.15    0.13    0.21    0.06

The load_builtin() 方法会在数据集尚未下载时提供下载 movielens-100k 数据集 的选项,并将其保存在您的主目录下的 .surprise_data 文件夹中(您也可以选择将其保存在其他位置)。

这里我们使用了著名的 SVD 算法,但还有许多其他算法可用。详见使用预测算法

cross_validate() 函数根据 cv 参数运行交叉验证过程,并计算一些准确率度量。这里我们使用经典的 5 折交叉验证,但也可以使用更高级的迭代器(详见此处)。

训练集-测试集划分与 fit() 方法

如果您不想运行完整的交叉验证过程,可以使用 train_test_split() 根据给定大小对训练集和测试集进行采样,并使用您选择的准确率 指标。您需要使用 fit() 方法在训练集上训练算法,并使用 test() 方法返回从测试集进行的预测

来自文件 examples/train_test_split.py
from surprise import accuracy, Dataset, SVD
from surprise.model_selection import train_test_split

# Load the movielens-100k dataset (download it if needed),
data = Dataset.load_builtin("ml-100k")

# sample random trainset and testset
# test set is made of 25% of the ratings.
trainset, testset = train_test_split(data, test_size=0.25)

# We'll use the famous SVD algorithm.
algo = SVD()

# Train the algorithm on the trainset, and predict ratings for the testset
algo.fit(trainset)
predictions = algo.test(testset)

# Then compute RMSE
accuracy.rmse(predictions)

结果

RMSE: 0.9411

请注意,您可以使用以下一行代码训练和测试算法

predictions = algo.fit(trainset).test(testset)

在某些情况下,您的训练集和测试集已经由文件定义好了。请参阅本节来处理此类情况。

在完整训练集上训练与 predict() 方法

显然,我们也可以简单地将算法拟合到整个数据集,而不是运行交叉验证。这可以通过使用 build_full_trainset() 方法来实现,该方法将构建一个 trainset 对象

来自文件 examples/predict_ratings.py
from surprise import Dataset, KNNBasic

# Load the movielens-100k dataset
data = Dataset.load_builtin("ml-100k")

# Retrieve the trainset.
trainset = data.build_full_trainset()

# Build an algorithm, and train it.
algo = KNNBasic()
algo.fit(trainset)

现在我们可以通过直接调用 predict() 方法来预测评分。假设您对用户 196 和物品 302 感兴趣(请确保它们在训练集中!),并且您知道真实评分 \(r_{ui} = 4\)

来自文件 examples/predict_ratings.py
uid = str(196)  # raw user id (as in the ratings file). They are **strings**!
iid = str(302)  # raw item id (as in the ratings file). They are **strings**!

# get a prediction for specific users and items.
pred = algo.predict(uid, iid, r_ui=4, verbose=True)

结果应为

user: 196        item: 302        r_ui = 4.00   est = 4.06   {'actual_k': 40, 'was_impossible': False}

注意

predict() 使用**原始(raw)**ID(请阅读此文了解原始 ID 和内部 ID)。由于我们使用的数据集是从文件中读取的,原始 ID 是字符串(即使它们代表数字)。

到目前为止,我们使用了内置数据集,但您当然可以使用自己的数据集。这将在下一节中解释。

使用自定义数据集

Surprise 包含一系列内置的数据集,但您当然可以使用自定义数据集。加载评分数据集可以从文件(例如 csv 文件)或 pandas DataFrame 进行。无论哪种方式,您都需要定义一个 Reader 对象,以便 Surprise 能够解析文件或 DataFrame。

  • 要从文件(例如 csv 文件)加载数据集,您需要使用 load_from_file() 方法

    来自文件 examples/load_custom_dataset.py
    import os
    
    from surprise import BaselineOnly, Dataset, Reader
    from surprise.model_selection import cross_validate
    
    # path to dataset file
    file_path = os.path.expanduser("~/.surprise_data/ml-100k/ml-100k/u.data")
    
    # As we're loading a custom dataset, we need to define a reader. In the
    # movielens-100k dataset, each line has the following format:
    # 'user item rating timestamp', separated by '\t' characters.
    reader = Reader(line_format="user item rating timestamp", sep="\t")
    
    data = Dataset.load_from_file(file_path, reader=reader)
    
    # We can now use this dataset as we please, e.g. calling cross_validate
    cross_validate(BaselineOnly(), data, verbose=True)
    

    有关读者以及如何使用它们的更多详细信息,请参阅 Reader 文档。

    注意

    正如您在上一节中已经了解到的,Movielens-100k 数据集是内置的,因此加载数据集的一种更快方法是使用 data = Dataset.load_builtin('ml-100k')。当然,在此处我们将忽略此方法。

  • 要从 pandas DataFrame 加载数据集,您需要使用 load_from_df() 方法。您还需要一个 Reader 对象,但只需指定 rating_scale 参数。DataFrame 必须有三列,依次对应用户(原始)ID、物品(原始)ID 和评分。因此,每一行对应一个给定的评分。这没有限制性,因为您可以轻松地重新排列 DataFrame 的列。

    来自文件 examples/load_from_dataframe.py
    import pandas as pd
    
    from surprise import Dataset, NormalPredictor, Reader
    from surprise.model_selection import cross_validate
    
    
    # Creation of the dataframe. Column names are irrelevant.
    ratings_dict = {
        "itemID": [1, 1, 1, 2, 2],
        "userID": [9, 32, 2, 45, "user_foo"],
        "rating": [3, 2, 4, 3, 1],
    }
    df = pd.DataFrame(ratings_dict)
    
    # A reader is still needed but only the rating_scale param is required.
    reader = Reader(rating_scale=(1, 5))
    
    # The columns must correspond to user id, item id and ratings (in that order).
    data = Dataset.load_from_df(df[["userID", "itemID", "rating"]], reader)
    
    # We can now use this dataset as we please, e.g. calling cross_validate
    cross_validate(NormalPredictor(), data, cv=2)
    

    DataFrame 最初看起来像这样

          itemID  rating    userID
    0       1       3         9
    1       1       2        32
    2       1       4         2
    3       2       3        45
    4       2       1  user_foo
    

使用交叉验证迭代器

对于交叉验证,我们可以使用替我们完成所有繁重工作的 cross_validate() 函数。但为了更好地控制,我们也可以实例化一个交叉验证迭代器,并使用迭代器的 split() 方法和算法的 test() 方法对每个拆分进行预测。这里是一个示例,我们使用经典的 K 折交叉验证过程进行 3 次拆分

来自文件 examples/use_cross_validation_iterators.py
from surprise import accuracy, Dataset, SVD
from surprise.model_selection import KFold

# Load the movielens-100k dataset
data = Dataset.load_builtin("ml-100k")

# define a cross-validation iterator
kf = KFold(n_splits=3)

algo = SVD()

for trainset, testset in kf.split(data):

    # train and test algorithm.
    algo.fit(trainset)
    predictions = algo.test(testset)

    # Compute and print Root Mean Squared Error
    accuracy.rmse(predictions, verbose=True)

结果可能如下所示,例如:

RMSE: 0.9374
RMSE: 0.9476
RMSE: 0.9478

还可以使用其他交叉验证迭代器,如 LeaveOneOut 或 ShuffleSplit。请此处查看所有可用的迭代器。Surprise 的交叉验证工具的设计受到了优秀的 scikit-learn API 的启发。


交叉验证的一个特殊情况是当折叠(folds)已由某些文件预定义时。例如,movielens-100K 数据集已经提供了 5 个训练和测试文件(u1.base, u1.test … u5.base, u5.test)。Surprise 可以通过使用 surprise.model_selection.split.PredefinedKFold 对象来处理这种情况

来自文件 examples/load_custom_dataset_predefined_folds.py
import os

from surprise import accuracy, Dataset, Reader, SVD
from surprise.model_selection import PredefinedKFold

# path to dataset folder
files_dir = os.path.expanduser("~/.surprise_data/ml-100k/ml-100k/")

# This time, we'll use the built-in reader.
reader = Reader("ml-100k")

# folds_files is a list of tuples containing file paths:
# [(u1.base, u1.test), (u2.base, u2.test), ... (u5.base, u5.test)]
train_file = files_dir + "u%d.base"
test_file = files_dir + "u%d.test"
folds_files = [(train_file % i, test_file % i) for i in (1, 2, 3, 4, 5)]

data = Dataset.load_from_folds(folds_files, reader=reader)
pkf = PredefinedKFold()

algo = SVD()

for trainset, testset in pkf.split(data):

    # train and test algorithm.
    algo.fit(trainset)
    predictions = algo.test(testset)

    # Compute and print Root Mean Squared Error
    accuracy.rmse(predictions, verbose=True)

当然,没有任何阻止您只加载一个训练文件和一个测试文件。然而,folds_files 参数仍然需要是一个 list

使用 GridSearchCV 调整算法参数

cross_validate() 函数报告给定参数集在交叉验证过程中的准确率指标。如果您想知道哪种参数组合能产生最佳结果,GridSearchCV 类就能派上用场了。给定一个参数 dict,这个类会详尽地尝试所有参数组合,并报告任何准确率度量(在不同拆分上平均)的最佳参数。它的设计很大程度上受到了 scikit-learn 的 GridSearchCV 的启发。

这里是一个示例,我们尝试了 SVD 算法的参数 n_epochs, lr_allreg_all 的不同值。

来自文件 examples/grid_search_usage.py
from surprise import Dataset, SVD
from surprise.model_selection import GridSearchCV

# Use movielens-100K
data = Dataset.load_builtin("ml-100k")

param_grid = {"n_epochs": [5, 10], "lr_all": [0.002, 0.005], "reg_all": [0.4, 0.6]}
gs = GridSearchCV(SVD, param_grid, measures=["rmse", "mae"], cv=3)

gs.fit(data)

# best RMSE score
print(gs.best_score["rmse"])

# combination of parameters that gave the best RMSE score
print(gs.best_params["rmse"])

结果

0.961300130118
{'n_epochs': 10, 'lr_all': 0.005, 'reg_all': 0.4}

我们在这里评估在 3 折交叉验证过程中平均 RMSE 和 MAE,但可以使用任何交叉验证迭代器

一旦调用了 fit()best_estimator 属性会提供一个带有最佳参数集的算法实例,我们可以随意使用它

来自文件 examples/grid_search_usage.py
# We can now use the algorithm that yields the best rmse:
algo = gs.best_estimator["rmse"]
algo.fit(data.build_full_trainset())

注意

字典参数,例如 bsl_optionssim_options,需要特殊处理。请参阅下面的使用示例

param_grid = {
    'k': [10, 20],
    'sim_options': {
        'name': ['msd', 'cosine'],
        'min_support': [1, 5],
        'user_based': [False],
    },
}

当然,两者可以结合使用,例如对于 KNNBaseline 算法

param_grid = {
    'bsl_options': {
        'method': ['als', 'sgd'],
        'reg': [1, 2],
    },
    'k': [2, 3],
    'sim_options': {
        'name': ['msd', 'cosine'],
        'min_support': [1, 5],
        'user_based': [False],
    },
}

为了进一步分析,cv_results 属性包含了所有需要的信息,可以导入到 pandas DataFrame 中

来自文件 examples/grid_search_usage.py
results_df = pd.DataFrame.from_dict(gs.cv_results)

在我们的示例中,cv_results 属性如下所示(浮点数已格式化)

'split0_test_rmse': [1.0, 1.0, 0.97, 0.98, 0.98, 0.99, 0.96, 0.97]
'split1_test_rmse': [1.0, 1.0, 0.97, 0.98, 0.98, 0.99, 0.96, 0.97]
'split2_test_rmse': [1.0, 1.0, 0.97, 0.98, 0.98, 0.99, 0.96, 0.97]
'mean_test_rmse':   [1.0, 1.0, 0.97, 0.98, 0.98, 0.99, 0.96, 0.97]
'std_test_rmse':    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
'rank_test_rmse':   [7 8 3 5 4 6 1 2]
'split0_test_mae':  [0.81, 0.82, 0.78, 0.79, 0.79, 0.8, 0.77, 0.79]
'split1_test_mae':  [0.8, 0.81, 0.78, 0.79, 0.78, 0.79, 0.77, 0.78]
'split2_test_mae':  [0.81, 0.81, 0.78, 0.79, 0.78, 0.8, 0.77, 0.78]
'mean_test_mae':    [0.81, 0.81, 0.78, 0.79, 0.79, 0.8, 0.77, 0.78]
'std_test_mae':     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
'rank_test_mae':    [7 8 2 5 4 6 1 3]
'mean_fit_time':    [1.53, 1.52, 1.53, 1.53, 3.04, 3.05, 3.06, 3.02]
'std_fit_time':     [0.03, 0.04, 0.0, 0.01, 0.04, 0.01, 0.06, 0.01]
'mean_test_time':   [0.46, 0.45, 0.44, 0.44, 0.47, 0.49, 0.46, 0.34]
'std_test_time':    [0.0, 0.01, 0.01, 0.0, 0.03, 0.06, 0.01, 0.08]
'params':           [{'n_epochs': 5, 'lr_all': 0.002, 'reg_all': 0.4}, {'n_epochs': 5, 'lr_all': 0.002, 'reg_all': 0.6}, {'n_epochs': 5, 'lr_all': 0.005, 'reg_all': 0.4}, {'n_epochs': 5, 'lr_all': 0.005, 'reg_all': 0.6}, {'n_epochs': 10, 'lr_all': 0.002, 'reg_all': 0.4}, {'n_epochs': 10, 'lr_all': 0.002, 'reg_all': 0.6}, {'n_epochs': 10, 'lr_all': 0.005, 'reg_all': 0.4}, {'n_epochs': 10, 'lr_all': 0.005, 'reg_all': 0.6}]
'param_n_epochs':   [5, 5, 5, 5, 10, 10, 10, 10]
'param_lr_all':     [0.0, 0.0, 0.01, 0.01, 0.0, 0.0, 0.01, 0.01]
'param_reg_all':    [0.4, 0.6, 0.4, 0.6, 0.4, 0.6, 0.4, 0.6]

如您所见,每个列表的大小与参数组合的数量相同。它对应于下表

split0_test_rmse

split1_test_rmse

split2_test_rmse

mean_test_rmse

std_test_rmse

rank_test_rmse

split0_test_mae

split1_test_mae

split2_test_mae

mean_test_mae

std_test_mae

rank_test_mae

mean_fit_time

std_fit_time

mean_test_time

std_test_time

params

param_n_epochs

param_lr_all

param_reg_all

0.99775

0.997744

0.996378

0.997291

0.000645508

7

0.807862

0.804626

0.805282

0.805923

0.00139657

7

1.53341

0.0305216

0.455831

0.000922113

{‘n_epochs’: 5, ‘lr_all’: 0.002, ‘reg_all’: 0.4}

5

0.002

0.4

1.00381

1.00304

1.00257

1.00314

0.000508358

8

0.816559

0.812905

0.813772

0.814412

0.00155866

8

1.5199

0.0367117

0.451068

0.00938646

{‘n_epochs’: 5, ‘lr_all’: 0.002, ‘reg_all’: 0.6}

5

0.002

0.6

0.973524

0.973595

0.972495

0.973205

0.000502609

3

0.783361

0.780242

0.78067

0.781424

0.00138049

2

1.53449

0.00496203

0.441558

0.00529696

{‘n_epochs’: 5, ‘lr_all’: 0.005, ‘reg_all’: 0.4}

5

0.005

0.4

0.98229

0.982059

0.981486

0.981945

0.000338056

5

0.794481

0.790781

0.79186

0.792374

0.00155377

5

1.52739

0.00859185

0.44463

0.000888907

{‘n_epochs’: 5, ‘lr_all’: 0.005, ‘reg_all’: 0.6}

5

0.005

0.6

0.978034

0.978407

0.976919

0.977787

0.000632049

4

0.787643

0.784723

0.784957

0.785774

0.00132486

4

3.03572

0.0431101

0.466606

0.0254965

{‘n_epochs’: 10, ‘lr_all’: 0.002, ‘reg_all’: 0.4}

10

0.002

0.4

0.986263

0.985817

0.985004

0.985695

0.000520899

6

0.798218

0.794457

0.795373

0.796016

0.00160135

6

3.0544

0.00636185

0.488357

0.0576194

{‘n_epochs’: 10, ‘lr_all’: 0.002, ‘reg_all’: 0.6}

10

0.002

0.6

0.963751

0.963463

0.962676

0.963297

0.000454661

1

0.774036

0.770548

0.771588

0.772057

0.00146201

1

3.0636

0.0597982

0.456484

0.00510321

{‘n_epochs’: 10, ‘lr_all’: 0.005, ‘reg_all’: 0.4}

10

0.005

0.4

0.973605

0.972868

0.972765

0.973079

0.000374222

2

0.78607

0.781918

0.783537

0.783842

0.00170855

3

3.01907

0.011834

0.338839

0.075346

{‘n_epochs’: 10, ‘lr_all’: 0.005, ‘reg_all’: 0.6}

10

0.005

0.6

命令行用法

Surprise 也可以通过命令行使用,例如

surprise -algo SVD -params "{'n_epochs': 5, 'verbose': True}" -load-builtin ml-100k -n-folds 3

运行以下命令查看详细用法

surprise -h