训练数据的分布与 Test/Prediction 的分布

Distribution of the Training Data vs Distribution of the Test/Prediction

训练数据代表的分布是否需要反映测试数据和你预测的数据的分布?我能否通过查看每个特征的分布并将该分布与我预测或测试的数据进行比较来衡量训练数据的质量?理想情况下,训练数据应充分代表现实世界的分布。

简短回答:类似的范围是个好主意。 长答案:有时这不是问题(很少),但让我们看看什么时候。

在理想情况下,您的模型会完美 捕捉真实现象。想象最简单的情况:线性模型 y = x。如果训练数据是无噪声的(或具有可容忍的噪声)。您的线性回归自然会落在约等于 y = x 的模型上。即使在训练范围之外,模型的泛化也将近乎完美。如果你的火车数据是 {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10}。测试点 500,将很好地映射到函数上,返回 500。

在大多数建模场景中,几乎肯定不会出现这种情况。如果训练数据充足并且模型适当复杂(仅此而已),那么您就是黄金。

问题是很少有函数(和相应的自然现象)——尤其是当我们考虑非线性函数时——如此干净地扩展到训练范围之外的数据。想象一下根据员工舒适度对办公室温度进行抽样。如果你只看 40 度到 60 度的温度。线性函数在训练数据中表现出色。奇怪的是,如果您在 60 到 80 之间进行测试,映射就会崩溃。在这里,问题是对您声称数据具有足够代表性的信心。

现在让我们考虑噪音。想象一下,您完全知道现实世界的函数是什么:正弦波。更好的是,您会被告知其振幅和相位。你不知道的是它的频率。您在 1 到 100 之间有一个非常可靠的采样,您拟合的函数非常好地映射到训练数据。现在,如果噪声刚好足够,您可能会轻而易举地错误地估计频率。当您在训练范围附近进行测试时,结果还不错。 训练范围外,事情开始变得不稳定。当您离训练范围越来越远时,实函数和函数会根据它们的相对频率发散和收敛。有时,残差看起来很好;有时他们很可怕。

您检查变量分布的想法有问题:变量之间的相互作用。即使每个变量在训练和测试中都适当平衡, 变量之间的关系也可能不同(联合分布)。举一个纯粹人为的例子,假设您要预测一个人在任何给定时间怀孕的可能性。在您的训练集中,女性年龄在 20 到 30 岁之间,男性年龄在 30 到 40 岁之间。在测试中,男性和女性的比例相同,但年龄范围颠倒了。独立地,变量看起来非常匹配!但是在你的训练集中,你可以很容易地得出结论,"only people under 30 get pregnant." 奇怪的是,你的测试集会表现出完全相反的情况!问题在于您的预测是根据多变量 space 做出的,但您考虑的分布是单变量的。然而,考虑连续变量相对于彼此的联合分布(并适当考虑分类变量)是一个好主意。理想情况下,您的拟合模型应该可以访问与您的测试数据相似的范围。

从根本上说,问题是关于从有限训练中进行外推 space。如果训练中的模型拟合space泛化,则可以泛化;最终,拥有一个真正分布良好的训练集通常是最安全的,这样可以最大限度地提高您捕捉到底层函数复杂性的可能性。

真是个有趣的问题!我希望答案是有见地的;当想到资源时,我将继续在此基础上进行构建!如果还有任何问题,请告诉我!

编辑:我认为未来的读者应该阅读评论中提出的观点。 理想情况下,训练数据不应以任何方式影响测试数据。这包括检查分布、联合分布等。有了足够的数据,训练数据中的分布应该收敛于测试数据中的分布(想想均值,大数定律)。匹配分布的操作(例如 train/test 拆分之前的 z 评分)从根本上使性能指标向有利于您的方向倾斜。用于拆分训练和测试数据的适当技术类似于用于交叉验证的分层 k 折。

抱歉延迟回复。经过几个月的迭代后,我实施了以下解决方案并将其推向了生产环境,并且运行良好。

这里的问题归结为如何在执行交叉验证时减少 training/test 分数方差。这很重要,因为如果您的方差很高,那么选择最佳模型的信心就会下降。测试数据对训练数据的代表性越强,交叉验证集的测试分数差异就越小。分层交叉验证通过确保在所有 test/train 集合中保留标签 class 比例来解决这个问题,特别是当存在显着 class 不平衡时。然而,这并没有解决特征分布的问题。

在我的例子中,我有一些特征是非常强大的预测因子,但它们的分布也非常倾斜。这导致我的测试分数出现显着差异,这使得我更难有信心地选择一个模型。本质上,解决方案是确保标签与特征集的联合分布在 test/train 集合中保持不变。有很多方法可以做到这一点,但一个非常简单的方法是在生成测试集和训练集时简单地逐个获取每个列桶范围(如果连续)或标签(如果分类)并从这些桶中采样。请注意,桶很快变得非常稀疏,尤其是当您有很多分类变量时。此外,存储桶的列顺序会极大地影响采样输出。下面是一个解决方案,我首先对标签进行分类(与分层 CV 相同),然后对其他 1 个特征(最重要的特征(称为 score_percentage)进行采样,这是预先知道的)。

def train_test_folds(self, label_column="label"):
    # train_test is an array of tuples where each tuple is a test numpy array and train numpy array pair.
    # The final iterator would return these individual elements separately.

    n_folds = self.n_folds
    label_classes = np.unique(self.label)

    train_test = []
    fmpd_copy = self.fm.copy()
    fmpd_copy[label_column] = self.label
    fmpd_copy = fmpd_copy.reset_index(drop=True).reset_index()
    fmpd_copy = fmpd_copy.sort_values("score_percentage")

    for lbl in label_classes:
        fmpd_label = fmpd_copy[fmpd_copy[label_column] == lbl]
        # Calculate the fold # using the label specific dataset
        if (fmpd_label.shape[0] < n_folds):
            raise ValueError("n_folds=%d cannot be greater than the"
                             " number of rows in each class."
                             % (fmpd_label.shape[0]))
        # let's get some variance -- shuffle within each buck
        # let's go through the data set, shuffling items in buckets of size nFolds
        s = 0
        shuffle_array = fmpd_label["index"].values
        maxS = len(shuffle_array)
        while s < maxS:
            max = min(maxS, s + n_folds) - 1
            for i in range(s, max):
                j = random.randint(i, max)
                if i < j:
                    tempI = shuffle_array[i]
                    shuffle_array[i] = shuffle_array[j]
                    shuffle_array[j] = tempI
            s = s + n_folds
#        print("shuffle s =",s," max =",max, " maxS=",maxS)
        fmpd_label["index"] = shuffle_array
        fmpd_label = fmpd_label.reset_index(drop=True).reset_index()
        fmpd_label["test_set_number"] = fmpd_label.iloc[:, 0].apply(
            lambda x: x % n_folds)
        print("label ", lbl)

        for n in range(0, n_folds):
            test_set = fmpd_label[fmpd_label["test_set_number"]
                                  == n]["index"].values
            train_set = fmpd_label[fmpd_label["test_set_number"]
                                   != n]["index"].values
            print("for label ", lbl, " test size is ",
                  test_set.shape, " train size is ", train_set.shape)
            print("len of total size", len(train_test))

            if (len(train_test) != n_folds):
                # Split doesnt exist. Add it in.
                train_test.append([train_set, test_set])
            else:
                temp_arr = train_test[n]
                temp_arr[0] = np.append(temp_arr[0], train_set)
                temp_arr[1] = np.append(temp_arr[1], test_set)
                train_test[n] = [temp_arr[0], temp_arr[1]]

    return train_test

随着时间的推移,我意识到整个问题都属于协变量偏移的范畴,这是机器学习中一个研究得很好的领域。 Link 下面或只搜索 google 协变量偏移。这个概念是如何检测并确保您的预测数据与训练数据具有相似的分布。这在功能中 space 但理论上你也可以有标签漂移。

https://www.analyticsvidhya.com/blog/2017/07/covariate-shift-the-hidden-problem-of-real-world-data-science/