拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Surprise 模型选择指南:交叉验证、参数搜索与评估流程详解

Surprise 模型选择指南:交叉验证、参数搜索与评估流程详解
  • 机器学习
  • 人工智能

【免费下载链接】Surprise

A Python scikit for building and analyzing recommender systems

项目地址:https://gitcode.com/gh_mirrors/su/Surprise
点击查看免费下载

Surprise 的model_selection包为推荐系统算法提供了完整的交叉验证与超参数搜索工具链,其设计深受 scikit-learn 的model_selection模块启发。本文将围绕 model_selection.rst 展开,系统讲解交叉验证迭代器(KFold、RepeatedKFold、ShuffleSplit、LeaveOneOut、PredefinedKFold)、train_test_split拆分函数、cross_validate批量评估函数,以及GridSearchCV与RandomizedSearchCV两大参数搜索类,并深入其源码实现,帮助读者掌握如何在 Surprise 中科学地评估算法、调优参数并产出可复用的最佳模型。

model_selection 包概览

surprise/model_selection/目录下包含四个模块,对应三类能力:

模块核心内容源码路径
split交叉验证迭代器与数据集拆分函数surprise/model_selection/split.py
validationcross_validate交叉验证评估函数surprise/model_selection/validation.py
searchGridSearchCV/RandomizedSearchCV参数搜索surprise/model_selection/search.py

其中split模块提供了五种迭代器:KFold、RepeatedKFold、ShuffleSplit、LeaveOneOut与PredefinedKFold,以及train_test_split函数。validation模块的cross_validate负责在给定迭代器上完成"训练—预测—算指标"的全流程。search模块则以交叉验证为底层引擎,穷举或随机采样参数组合,筛选最优配置。

交叉验证迭代器:如何把数据集切成训练集与测试集

所有迭代器都遵循统一的接口:构造时传入折叠策略参数,调用split(data)返回一个生成器,逐次yield出(trainset, testset)二元组。其中trainset是 Trainset 对象,testset是原始评分三元组(用户 id、物品 id、真实评分)的列表,可直接喂给算法的fit()与test()。

统一入口 get_cv:参数归一化

无论是cross_validate还是搜索类,最终都会通过get_cv把用户传入的cv参数归一化为标准迭代器(见 split.py):

  • cv=None→ 默认使用KFold(n_splits=5);
  • cv为整数 → 等价于KFold(n_splits=cv);
  • cv为已实现split()方法的迭代器 → 直接使用;
  • 否则抛出ValueError。

这意味着几乎所有接受cv参数的 API 都共享同一套折叠策略,你可以自由地在"传一个数字"和"传一个自定义迭代器"之间切换。

KFold:最基础的 k 折交叉验证

KFold(n_splits=5, random_state=None, shuffle=True)将全部评分数据均分成n_splits份,每份轮流作为测试集,其余n_splits - 1份作为训练集(split.py)。

关键实现细节:

  • n_splits必须>= 2且小于评分总数,否则抛出ValueError;
  • 切分基于data.raw_ratings的索引,洗牌在索引数组上进行(get_rng(self.random_state).shuffle(indices)),因此不会原地修改原始评分列表;
  • 每次yield前通过data.construct_trainset(raw_trainset)与data.construct_testset(raw_testset)构建对象;
  • random_state仅在shuffle=True时生效:传int作为随机种子可保证多次调用split()得到完全相同的划分,适合复现实验。

典型用法见 examples/use_cross_validation_iterators.py:

from surprise import accuracy, Dataset, SVD from surprise.model_selection import KFold data = Dataset.load_builtin("ml-100k") kf = KFold(n_splits=3) algo = SVD() for trainset, testset in kf.split(data): algo.fit(trainset) predictions = algo.test(testset) accuracy.rmse(predictions, verbose=True)

每次迭代输出一行类似RMSE: 0.9374的结果,三折共三行。

RepeatedKFold:多次重复的 KFold

RepeatedKFold(n_splits=5, n_repeats=10, random_state=None)把KFold重复n_repeats次,每次使用不同的随机化(split.py)。其实现非常简洁:内部逐次构造KFold(n_splits=self.n_splits, random_state=rng, shuffle=True)并yield from cv.split(data)。get_n_folds()返回n_repeats * n_splits。当数据集规模有限、需要更稳健的误差估计时,这是一个低成本的高方差缓解方案。

ShuffleSplit:随机划分训练集与测试集

ShuffleSplit(n_splits=5, test_size=0.2, train_size=None, random_state=None, shuffle=True)与 K 折不同,它不保证每一折互不相同,只是每次都独立地随机抽取测试集(split.py)。

大小参数规则(由validate_train_test_sizes统一处理):

  • test_size:float表示测试集占评分总数的比例;int表示绝对条数;None时自动取"总评分 − 训练集大小";
  • train_size:同上;None时自动取"总评分 − 测试集大小";
  • 校验规则:两者都必须严格大于 0 且小于评分总数,二者之和不能超过评分总数,否则抛ValueError;
  • 实现上浮点比例会向上取整(ceil)计算测试集大小、向下取整(floor)计算训练集大小。

train_test_split正是它的一个特例封装。

train_test_split:一次性划分训练集与测试集

train_test_split(data, test_size=0.2, train_size=None, random_state=None, shuffle=True)返回(trainset, testset)二元组(split.py)。源码中它只是构造了一个ShuffleSplit(n_splits=1, ...)并取next(ss.split(data)),即"只随机划分一次、不进入交叉验证流程"。

文档明确提示:该函数不能用作交叉验证迭代器。完整示例见 examples/train_test_split.py:

from surprise import accuracy, Dataset, SVD from surprise.model_selection import train_test_split data = Dataset.load_builtin("ml-100k") trainset, testset = train_test_split(data, test_size=0.25) algo = SVD() algo.fit(trainset) predictions = algo.test(testset) accuracy.rmse(predictions)

输出形如RMSE: 0.9411。若想一步完成训练与预测,文档也给出了等价写法:predictions = algo.fit(trainset).test(testset)。

LeaveOneOut:每个用户恰好留出一条评分

LeaveOneOut(n_splits=5, random_state=None, min_n_ratings=0)保证每个用户在测试集中恰好有一条评分(split.py):

  • 实现先把raw_ratings按用户 id 聚合为user_ratings字典;
  • 每折中,对每个用户随机抽取一条评分进测试集,其余进训练集;
  • min_n_ratings用于过滤:只有评分条数大于该阈值的用户才会进入本折,其余用户被丢弃。例如min_n_ratings=2可确保每个用户训练集中至少有 2 条评分、测试集恰好 1 条;默认0表示只有 1 条评分的用户也会出现在测试集但不在训练集;
  • 若某折训练集为空(例如min_n_ratings设置过高),抛出ValueError提示。

该迭代器非常适合"评估冷启动用户推荐效果"这类场景。

PredefinedKFold:使用外部预定义折文件

当训练/测试折已经由文件预先定义好时(如 movielens-100k 自带的u1.base/u1.test…u5.base/u5.test),使用PredefinedKFold配合Dataset.load_from_folds()(dataset.py):

from surprise import accuracy, Dataset, Reader, SVD from surprise.model_selection import PredefinedKFold import os files_dir = os.path.expanduser("~/.surprise_data/ml-100k/ml-100k/") reader = Reader("ml-100k") 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): algo.fit(trainset) predictions = algo.test(testset) accuracy.rmse(predictions, verbose=True)

PredefinedKFold.split()直接读取每一对(train_file, test_file)并构建对应的 trainset/testset(split.py)。folds_files必须是一个(训练文件, 测试文件)元组组成的列表,即使只有一对文件也需用列表包裹。

cross_validate:一站式交叉验证评估

cross_validate(algo, data, measures=['rmse', 'mae'], cv=None, return_train_measures=False, n_jobs=1, pre_dispatch='2*n_jobs', verbose=False)是对上述迭代器的高层封装(validation.py),它自动完成"遍历折 → 训练 → 预测 → 计算指标 → 汇总输出"的整个流程。

参数说明:

  • algo:任意继承自 AlgoBase 的算法实例;
  • measures:要计算的指标名列表,必须是 accuracy 模块 中函数名的小写形式,默认['rmse', 'mae'],可用rmse、mse、mae、fcp等;
  • cv:迭代器、整数或None,规则与get_cv一致,默认 5 折 KFold;
  • return_train_measures:是否同时计算训练集上的指标,默认False;
  • n_jobs:并行评估的折数上限。-1表示使用全部 CPU;1表示完全串行(便于调试);小于-1时使用(n_cpus + n_jobs + 1)个 CPU,如-2表示"全部 CPU 减一";
  • pre_dispatch:控制并行时一次性派发的任务数,可为None(全部立即创建并派发,适合轻量快速任务)、整数(精确派发数)或字符串表达式(如默认的'2*n_jobs'),用于防止派发任务过多导致内存爆炸;
  • verbose:为True时打印每个折的指标与训练/测试耗时,并给出所有折的均值与标准差。

返回值是一个字典,包含:

  • test_<metric>:如test_rmse、test_mae,为各测试折指标组成的 numpy 数组;
  • train_<metric>:仅当return_train_measures=True时存在;
  • fit_time/test_time:各折的训练、测试耗时(秒)数组。

最基础的用法见 examples/basic_usage.py:

from surprise import Dataset, SVD from surprise.model_selection import cross_validate data = Dataset.load_builtin("ml-100k") algo = SVD() 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

注意measures传入"RMSE"/"MAE"大写也能工作,因为源码开头会执行measures = [m.lower() for m in measures]统一转小写。

从源码看,cross_validate的底层由fit_and_score辅助函数支撑(validation.py):它依次调用algo.fit(trainset)、algo.test(testset),用time.time()记录训练/测试耗时,再通过getattr(accuracy, m)动态取到对应指标函数逐项计算。全部折的结果通过 joblib 的Parallel并行执行后重组为上述字典。此外,fit_and_score也是GridSearchCV/RandomizedSearchCV复用同一套评估逻辑的基础。

GridSearchCV:穷举式参数搜索

GridSearchCV(algo_class, param_grid, measures=['rmse', 'mae'], cv=None, refit=False, return_train_measures=False, n_jobs=1, pre_dispatch='2*n_jobs', joblib_verbose=0)对参数网格做笛卡尔积穷举,在每个参数组合 × 每个折上执行训练与评估(search.py)。

参数要点:

  • algo_class:算法类(不是实例),如SVD;
  • param_grid:字典,键为算法参数名,值为候选值列表,所有组合都会被尝试;
  • measures:默认['rmse', 'mae'],决定用哪些指标来筛选最优参数;
  • cv:默认 5 折 KFold,规则同前;
  • refit:False(默认)只做搜索;True表示用measures中第一个指标的最优参数在整个数据集上重新训练;也可传指标名字符串(如'mae')指定用哪个指标选参。refit 后可用test()/predict()直接使用最佳模型。注意:当数据通过load_from_folds()加载时不能使用 refit,源码会在fit()中抛出ValueError拦截(search.py);
  • return_train_measures:为True时cv_results中会额外包含训练集指标;
  • n_jobs/pre_dispatch:与cross_validate语义一致;
  • joblib_verbose:控制 joblib 的日志详细程度。

完整示例见 examples/grid_search_usage.py:

from surprise import Dataset, SVD from surprise.model_selection import GridSearchCV 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) print(gs.best_score["rmse"]) # 最佳 RMSE 得分 print(gs.best_params["rmse"]) # 取得最佳 RMSE 的参数组合 algo = gs.best_estimator["rmse"] # 最佳 RMSE 对应的算法实例 algo.fit(data.build_full_trainset())

输出示例:

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

搜索结果属性

fit()之后,搜索对象暴露四个核心属性(均为"指标名 → 结果"的字典):

  • best_estimator[m]:在指标m上平均表现最优的算法实例;
  • best_score[m]:对应的最佳平均得分;
  • best_params[m]:取得该得分的参数组合;
  • best_index[m]:cv_results中对应参数组合的索引;
  • cv_results:包含全部折、全部参数组合的完整明细字典,可直接导入 pandas DataFrame 做进一步分析(pd.DataFrame.from_dict(gs.cv_results))。

cv_results的结构(每个键都是与参数组合数等长的数组):

键含义
split{i}_test_<metric>第i折上各参数组合的测试指标
mean_test_<metric>/std_test_<metric>跨折均值与标准差
rank_test_<metric>各参数组合在该指标上的排名(1 为最优)
mean_fit_time/mean_test_time及 std 变体各参数组合的平均训练/测试耗时
params全部参数组合字典的列表
param_<name>每个参数在各组合中的取值列表

排名与最优选择逻辑在源码中有明确区分:对于mae、rmse、mse这类"越小越好"的指标,best_index取mean_test_measures.argmin(),排名越小越优;对于fcp(Fraction of Concordant Pairs)这类"越大越好"的指标,则取argmax(),排名规则相应反转(search.py)。

嵌套字典参数(sim_options / bsl_options)的特殊写法

param_grid中如果包含sim_options(相似度参数)或bsl_options(基线参数)这类嵌套字典,需要把内层也写成"参数名 → 候选值列表"的形式。源码的_parse_options会先用itertools.product对嵌套字典做笛卡尔积展开,再与顶层参数组合(search.py)。文档给出的标准写法:

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], }, }

RandomizedSearchCV:随机采样式参数搜索

当参数空间很大时,穷举代价高昂。RandomizedSearchCV从参数分布中随机采样n_iter组配置(search.py),适合"先粗后细"(coarse-to-fine)的调参策略。

与GridSearchCV的差异集中在参数定义方式上:

  • param_distributions:键为参数名,值为分布对象(需提供rvs采样方法,如scipy.stats的分布)或候选值列表;
  • n_iter:采样的参数组合数量,默认10;
  • random_state:随机种子,控制从列表值中均匀采样时的可复现性(int、RandomState实例或None)。

采样逻辑(_sample_parameters,静态方法)值得注意:

  • 若所有参数都以列表给出,则先对列表做笛卡尔积生成完整网格,再在网格内无放回地随机抽取n_iter组;
  • 若至少有一个参数是分布对象,则逐次对每个参数有放回采样(列表参数均匀随机取一个,分布参数调用v.rvs(random_state=rnd));
  • 源码注释特别提示:连续型参数强烈建议使用连续分布;在 SciPy 0.16 之前,scipy.stats.distributions不接受自定义 RNG 实例,因此使用分布时设置random_state无法保证完全确定性(SciPy 0.16 起才保证)。

其余属性(best_estimator、best_score、best_params、best_index、cv_results)与GridSearchCV完全一致。

三种评估方式的选择建议

结合文档与源码,可以把整套工具归纳为三个层级:

  1. 快速评估:cross_validate(algo, data, cv=5)一行代码得到指标均值、标准差与耗时,适合对单个算法快速摸底;
  2. 精细控制:自行实例化迭代器(KFold、LeaveOneOut、PredefinedKFold等)并手写fit/test循环,适合定制评估流程或使用预定义折文件;
  3. 参数调优:GridSearchCV穷举小网格、RandomizedSearchCV采样大空间,配合refit=True直接产出在完整数据上重训的最佳模型,衔接线上预测。

三者共享同一套get_cv归一化与fit_and_score评估内核,因此无论走哪条路径,评估口径(指标、折叠策略、随机种子)都是一致的,实验结果可以放心互相参照。更多用法可继续阅读文档 getting_started.rst 中的"Use cross-validation iterators"与"Tune algorithm parameters with GridSearchCV"两节,以及仓库中的 examples 目录。

  • 机器学习
  • 人工智能

【免费下载链接】Surprise

A Python scikit for building and analyzing recommender systems

项目地址:https://gitcode.com/gh_mirrors/su/Surprise
点击查看免费下载

相关推荐

上一篇:【限时免费】 mT5_multilingual_XLSum:不止是多语言摘要这么简单
下一篇:深度揭秘:EdgeFlow如何用算法智慧重塑Blender边缘循环

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表