为何Skorch GPBinaryClassifier远慢于scikit-learn GaussianProcessClassifier?
问题背景
针对640维连续特征的二分类任务,分别在约600、6000个训练样本的数据集上测试:从scikit-learn的GaussianProcessClassifier(RBF核)切换到skorch+GPytorch框架,预期借助GPU加速提升训练速度、甚至优化精度,但实际结果是两者精度相近,skorch+GPytorch的训练速度慢一个数量级以上,即使启用GPU也无改善。
核心疑问
- 是否在skorch的使用方式上存在错误,导致性能劣化?
- GPytorch与scikit-learn的数值近似方法,是否会因数据集差异表现出不同的速度特性?
现有skorch+GPytorch实现代码
class GPClassificationModel(ApproximateGP): def __init__(self, train_x, init_lengthscale = 3.): variational_distribution = CholeskyVariationalDistribution(train_x.size(0)) variational_strategy = UnwhitenedVariationalStrategy( self, train_x, variational_distribution, learn_inducing_locations=False ) super(GPClassificationModel, self).__init__(variational_strategy) self.mean_module = gpytorch.means.ConstantMean() self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel()) self.covar_module.base_kernel.lengthscale = init_lengthscale def forward(self, x): mean_x = self.mean_module(x) covar_x = self.covar_module(x) latent_pred = gpytorch.distributions.MultivariateNormal(mean_x, covar_x) # print(latent_pred.mean) return latent_pred model = GPBinaryClassifier(GPClassificationModel, module__train_x = X_train, criterion__num_data=len(X_train), device = device, batch_size = 32, lr=0.5, max_epochs=400)
原因分析
1. 近似方法本质差异
scikit-learn的GaussianProcessClassifier默认采用拉普拉斯近似,这是针对二分类任务的解析近似方法,计算复杂度为O(n³),但经过高度优化的CPU实现(如Cholesky分解的高效计算)在小到中等规模数据集上速度极快。
而你当前的GPytorch实现采用变分高斯过程(VGP),但设置了train_x.size(0)个诱导点——即诱导点数量等于训练样本数,完全丧失了变分GP的高效性,退化为精确GP训练。精确GP的O(n³)复杂度在GPU上未必能超越scikit-learn的CPU优化实现,再加上小批量训练的额外迭代开销,整体速度自然大幅落后。
2. skorch使用的关键问题
- 诱导点设置错误:诱导点数量等于样本数,变分GP失去降维优势,计算量陡增。
- 训练超参数不合理:学习率0.5过大,易导致训练不稳定,需要更多迭代收敛;batch_size=32过小,GPU并行计算能力未充分利用,小批量调度的 overhead 占比过高。
优化建议
1. 重构变分GP的诱导点设置
将诱导点数量减少至远小于训练样本数(如600样本设50-100个,6000样本设200-500个),将计算复杂度降至O(m²n)(m为诱导点数量),同时开启诱导点位置学习:
# 随机采样100个诱导点 inducing_points = X_train[torch.randperm(X_train.size(0))[:100]] variational_distribution = CholeskyVariationalDistribution(inducing_points.size(0)) variational_strategy = UnwhitenedVariationalStrategy( self, inducing_points, variational_distribution, learn_inducing_locations=True )
2. 调整训练超参数
- 增大batch_size至256或512,充分利用GPU并行能力,减少迭代次数;
- 降低学习率至0.01-0.1,搭配
ReduceLROnPlateau学习率调度器,提升训练稳定性; - 启用早停(EarlyStopping)策略,无需固定400个epochs,通常几十次迭代即可收敛。
3. 改用GPytorch的拉普拉斯近似实现
若想对齐scikit-learn的近似逻辑,可使用GPytorch的拉普拉斯近似实现,兼顾GPU加速与近似效率:
class LaplaceGPClassificationModel(gpytorch.models.ExactGP): def __init__(self, train_x, train_y, likelihood): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ConstantMean() self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel()) def forward(self, x): mean_x = self.mean_module(x) covar_x = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean_x, covar_x) # 初始化拉普拉斯近似的likelihood与准则 likelihood = gpytorch.likelihoods.BernoulliLikelihood() model = LaplaceGPClassificationModel(X_train, y_train, likelihood) criterion = gpytorch.mlls.LaplaceMarginalLogLikelihood(likelihood, model)
4. 优化数据传输
确保X_train为PyTorch Tensor类型并直接加载至GPU,避免CPU与GPU间频繁的数据传输消耗。
总结
scikit-learn在小到中等规模数据集上的速度优势,源于其拉普拉斯近似的高效CPU优化实现;你当前的GPytorch实现因诱导点设置错误退化为精确GP,再加上不合理的训练超参数,导致速度大幅落后。通过调整诱导点数量、优化训练策略或切换至拉普拉斯近似,可显著提升GPytorch模型的训练速度,同时保持精度。
内容的提问来源于stack exchange,提问作者owenl

