捕获非整列、测试和验证拆分中的所有数据

Capturing all data in non-whole train, test, and validate splits

只是想知道是否存在针对此类问题的更好解决方案。

我们知道,对于偶数的 X/Y 百分比拆分,我们可以获得数据的精确拆分 - 例如,对于数据大小 10:

10 * .6 = 6
10 * .4 = 4
          10 

这样拆分数据很容易,而且我们可以保证我们拥有所有数据,不会丢失任何东西。然而,我挣扎的地方是不太友好的数字 - 取 11

11 * .6 = 6.6
11 * .4 = 4.4
          11

但是我们不能在 i = 6.6 处索引到一个数组。所以我们必须决定如何做到这一点。如果我们只取整数部分,我们将丢失 1 个数据点 -

First set = 0..6
Second set = 6..10

如果我们对数字进行下限,情况也是一样。

但是,如果我们取数字的上限:

First set = 0..7
Second set = 7..12

而且我们已经读完了数组的末尾。

当我们进行第 3 次或第 4 次拆分(例如 30、30、20、20)时,情况会变得更糟。

这类问题有标准的拆分过程吗?是否接受数据丢失?对于依赖数据(例如时间序列)来说,数据丢失似乎是不可接受的。

谢谢!

编辑:.6.4 是我选择的。它们可以是总和为 1.

的任意两个数字

不要使用 ciel()floor(),而是使用 round()。例如:

>>> round(6.6)
7.0

返回的值将是 float 类型。要获取整数值,请将其类型转换为 int as:

>>> int(round(6.6))
7

这将是您第一次 拆分 的值。要获得第二次拆分,请使用 len(data) - split1_val 进行计算。这将适用于 2 拆分问题的情况。

如果是3 split,取两个split的舍入值,取第3个split的值作为len(my_list) - val_split_1 - val_split2

以通用方式,对于 N 拆分

Take the round() value of N-1 split. And for the last value, do len(data) - "value of N round() values".

其中 len() 给出列表的长度。

首先,请注意您的问题并不像您声称的那样仅限于奇数大小的数组,而是任何大小的数组。你将如何对 10 个元素的数组进行 56%-44% 的拆分?还是 4 元素数组的 60%-40% 拆分?

没有标准程序。在许多情况下,程序员不太关心精确拆分,他们要么通过取整或四舍五入一个数量(第一组的大小),同时对另一个(数组长度 - 四舍五入的大小)取互补(数组长度 - 舍入大小)第二个的大小)。

在大多数情况下,当这是一次性计算并且不需要准确性时,这可能没问题。 你得问问自己你的要求是什么。例如:您是否采用了数千个 10 大小的数组,并且每次将它们拆分为 56%-44% 进行一些计算并返回结果?你必须问问自己你想要什么样的准确性。你关心你的结果是否最终是 60%-40% 的拆分还是 50%-50% 的拆分?

再举一个例子,假设您正在进行 25%-25%-25%-25% 的 4 向均分。如果您有 10 个元素并应用舍入技术,您最终会得到 3,3,3,1 个元素。这肯定会弄乱你的结果。

如果您确实关心所有这些不准确之处,那么第一步是考虑是否可以调整数组大小 and/or 拆分比率。

如果这些是一成不变的,那么对任何大小的数组的任何比率进行准确拆分的唯一方法是使其成为概率。您必须拆分多个数组才能工作(这意味着您必须多次将相同的拆分比率应用于相同大小的数组)。数组越多越好(或者您可以多次使用同一个数组)。

想象一下,您必须对 10 大小的数组进行 56%-44% 的拆分。这意味着您需要将其平均拆分为 5.6 个元素和 4.4 个元素

有很多方法可以达到 5.6 元素的平均值。最简单的一个(也是尝试序列中方差最小的一个)是有 60% 的时间有 6 个元素的集合,有 40% 的时间有 5 个元素的集合。

0.6*6 + 0.4*5 = 5.6

就代码而言,这是您每次可以决定集合大小的方法:

import random

array_size = 10
first_split = 0.56
avg_split_size = array_size * first_split 
floored_split_size = int(avg_split_size)

if avg_split_size > floored_split_size:
    if random.uniform(0,1) > avg_split_size - floored_split_size:
        this_split_size = floored_split_size
    else: 
        this_split_size = floored_split_size + 1    
else:
    this_split_size = avg_split_size

你可以让代码更紧凑,我只是在这里做了一个大纲,所以你明白了。我希望这有帮助。

让我们首先考虑将集合分成两部分。

n为我们拆分的元素个数,pq为比例,因此

p+q == 1

我断言小数点后的部分总和为 10所以我们应该在一个上使用 floorceil另一方,我们永远是对的。

这是一个执行此操作的函数以及一个测试。我留下了打印语句,但它们被注释掉了。

def simpleSplitN(n, p, q):
    "split n into proportions p and q and return indices"
    np = math.ceil(n*p)
    nq = math.floor(n*q)
    #print n, sum([np, nq]) #np and nq are the proportions
    return [0, np] #these are the indices we would use

#test for simpleSplitN
for i in range(1, 10):
    p = i/10.0;
    q = 1-p
    simpleSplitN(37, p, q);

对于喜欢数学的人来说,这里是小数部分总和为 1

的证明

-----------------------

我们可以将p*n表示为n/(1/p),所以通过除法算法我们得到整数kr

n == k*(1/p) + r0 <= r < (1/p)

因此r/(1/p) == p*r < 1

我们可以对q做同样的事情,得到

q*r < 1(这是不同的r)

需要注意的是 q*rp*r 小数点后的部分 当我们除以 n.

现在我们可以将它们加在一起(我们现在已经添加了下标)

0 <= p*(r_1) < 1 0 <= q*(r_2) < 1

=> 0 < p*r + q*r == p*n + q*n + k_1 + k_2 == n + k_1 + k_2 < 2

但是通过关闭整数,n + k_1 + k_2 是一个整数,所以

0 < n + k_1 + k_2 < 2

表示p*r + q*r必须是01。只有我们的n平分的情况下才会0

否则我们现在可以看到我们的小数部分总和为 1

-----------------------

我们可以做一个非常相似(但稍微复杂一些)的证明,将 n 分成任意数量(例如 N)部分,而不是将它们相加为 1 , 它们的总和将小于 N.

这是通用函数,它有用于验证目的的未注释打印语句。

import math
import random

def splitN(n, c):
    """Compute indices that can be used to split
    a dataset of n items into a list of proportions c
    by first dividing them naively and then distributing
    the decimal parts of said division randomly
    """
    nc = [n*i for i in c];
    nr = [n*i - int(n*i) for i in c] #the decimal parts
    N = int(round(sum(nr)))          #sum of all decimal parts
    print N, nc
    for i in range(0, len(nc)):
        nc[i] = math.floor(nc[i])
    for i in range(N):                  #randomly distribute leftovers
        nc[random.randint(1, len(nc)) - 1] += 1
    print n,sum(nc);                    #nc now contains the proportions
    out = [0]                           #compute a cumulative sum
    for i in range(0, len(nc) - 1):
        out.append(out[-1] + nc[i])
    print out
    return out

#test for splitN with various proportions
c = [.1,.2,.3,.4]
c = [.2,.2,.2,.2,.2]
c = [.3, .2, .2, .3]
for n in range( 10, 40 ):
    print splitN(n, c)

如果我们有剩菜,我们永远不会平分,所以我们随机分配它们,就像@Thanassis 说的那样。如果您不喜欢对 random 的依赖,那么您可以在开头或均匀的间隔添加它们。

我的两个函数都输出索引,但它们计算比例,因此可以稍微修改以根据用户偏好输出这些索引。