本博客文章使用RapidsAI cuDF探讨了获取足够数据所面临的挑战以及由偏见数据集带来的限制
由Kris Manohar和Kevin Baboolal撰写
编辑注:我们非常高兴地宣布,这篇文章已被选为小猪AI和NVIDIA博客写作比赛的获胜者。
介绍
机器学习通过利用大量数据已经在各个领域实现了革命性变革。但是,在成本或稀缺性导致获得足够数据成为挑战的情况下,传统方法通常难以提供准确的预测。本博客探讨了小数据集所带来的限制,并揭示了TTLAB提出的创新解决方案,该方案利用最近邻方法和专用内核的威力。我们将深入探讨其算法的细节、优势以及GPU优化如何加速其执行。
小数据集的挑战
在机器学习中,拥有大量数据对于训练准确模型至关重要。然而,当面临仅有几百行的小数据集时,缺点变得明显。一个常见的问题是在某些分类算法(如朴素贝叶斯分类器)中遇到的零频率问题。当算法在测试过程中遇到未见过的类别值时,这种情况会导致该案例的零概率估计。同样,当测试集包含在训练集中缺失的值时,回归任务也会面临挑战。在这种情况下,甚至可能会发现在排除这些缺失特征后,所选择的算法得到了改进(尽管是次优的)。这些问题也会在具有高度不平衡类别的大型数据集中显现。
克服数据稀缺性
尽管训练-测试拆分通常可以缓解这些问题,但在处理小型数据集时仍存在一个隐藏的问题。强制算法基于较少的样本泛化可能会导致次优的预测。即使算法运行,它的预测也可能缺乏鲁棒性和准确性。获得更多数据的简单解决方案并不总是可行的,因为存在成本或可用性限制。在这种情况下,TTLAB提出的创新方法被证明是稳健和准确的。
TTLAB算法
TTLAB的算法解决了偏倚和有限数据集所带来的挑战。他们的方法涉及对训练数据集中的所有行进行加权平均,以预测测试样本中的目标变量的值。关键在于调整每个训练行的权重以适应每个测试行,基于一个参数化的非线性函数,该函数计算特征空间中两点之间的距离。虽然所使用的加权函数只有一个参数(随着距离增加,训练样本影响衰减率的速率),但优化该参数的计算工作量可能很大。通过考虑整个训练数据集,该算法提供了鲁棒的预测。这种方法已经在增强随机森林和朴素贝叶斯等流行模型的性能方面取得了显著成功。随着该算法的普及,正在进行努力进一步提高其效率。当前的实现涉及调整超参数kappa,这需要进行网格搜索。为了加快这个过程,正在探索一种后继二次逼近法,承诺更快的参数优化。此外,正在进行的同行评审旨在验证和改进该算法以实现更广泛的采用。
为了实现用于分类的TTLAB算法,for循环和numpy证明效率低下,导致运行时间非常长。链接的出版物中展示的CPU实现侧重于分类问题,展示了该方法的多功能性和功效。https://arxiv.org/pdf/2205.14779.pdf。该出版物还显示,该算法从向量化中获得了巨大的好处,暗示着可以从使用CuPy的GPU加速中获得进一步的速度提升。实际上,对于正在测试的大量数据集,要执行超参数调整和随机K折结果验证需要几周时间。通过利用GPU的威力,计算被有效地分配,结果得到了改善。
使用GPU加速执行
即使进行了向量化和.apply重构等优化,执行时间仍然不适用于实际应用程序。然而,通过GPU优化,运行时间大大缩短,从几个小时缩短到几分钟。这种显著的加速为在需要及时结果的情况下使用该算法开辟了可能性。
根据从CPU实现中得出的教训,我们尝试进一步优化我们的实现。为此,我们将层次向上移动到CuDF数据帧。使用CuDF将计算向量化到GPU上非常容易。对于我们来说,只需将import pandas更改为import CuDF(必须正确地向量化pandas即可)。
train_df["sum_diffs"] = 0
train_df["sum_diffs"] = train_df[diff_cols].sum(axis=1).values
train_df["d"] = train_df["sum_diffs"] ** 0.5
train_df["frac"] = 1 / (1 + train_df["d"]) ** kappa
train_df["part"] = train_df[target_col] * train_df["frac"]
test_df.loc[index, "pred"] = train_df["part"].sum() / train_df["frac"].sum()
进一步深入我们需要依赖于Numba内核。此时,事情变得棘手。回想一下为什么算法的预测是稳健的,因为每个预测都使用训练数据框中的所有行。然而,Numba内核不支持传递CuDF数据框。目前,我们正在尝试一些在Github上建议的技巧来处理这种情况。(https:/ /github.com/rapidsai/cudf/issues/13375)
目前,我们至少可以通过 .apply_rows
将原始计算传递给numba内核
def predict_kernel(F, T, numer, denom, kappa):
for i, (x, t) in enumerate(zip(F, T)):
d = abs(x - t) # the distance measure
w = 1 / pow(d, kappa) # parameterize non-linear scaling
numer[i] = w
denom[i] = d
_tdf = train_df[[att, target_col]].apply_rows(
predict_kernel,
incols={att: "F", "G3": "T"},
outcols={"numer": np.float64, "denom": np.float64},
kwargs={"kappa": kappa},
)
p = _tdf["numer"].sum() / _tdf["denom"].sum() # prediction - weighted average
此时,我们并未消除所有循环,但仅仅通过将大部分的计算推到Numba中,减少了CuDf的运行时间> 50%,使我们在标准的80-20训练测试分割中达到了约2至4秒。
总结
探索rapids,cupy和cudf库在各种机器学习任务中的能力是一段令人振奋和愉快的旅程。这些库已被证明是用户友好且易于理解的,使大多数用户能够访问。这些库的设计和维护值得称赞,使用户在必要时能够深入了解其复杂性。在一周的每天几个小时的时间里,我们从新手进阶到通过实施高度定制的预测算法来推动库的边界。我们的下一个目标是实现前所未有的速度,旨在突破从20K到30K的大型数据集的微秒障碍。达到这个里程碑后,我们计划将算法作为由rapids提供支持的pip软件包发布,使其可供更广泛的采用和使用。
Kris Manohar是特立尼达和多巴哥ICPC的执行董事。