给定一个比率,将文件随机分配到训练/测试中

Posted

技术标签:

【中文标题】给定一个比率,将文件随机分配到训练/测试中【英文标题】:Randomly distribute files into train/test given a ratio 【发布时间】:2017-01-05 18:01:49 【问题描述】:

我目前正在尝试制作一个能够为我设置工作区的设置脚本,这样我就不需要手动进行了。 我开始在 bash 中执行此操作,但很快意识到这样做效果不佳。

我的下一个想法是使用 python 来做,但似乎无法以正确的方式做到这一点。我的想法是制作一个列表(一个列表是一个 .txt 文件,其中包含所有数据文件的路径),打乱这个列表,然后将每个文件移动到我的火车目录或测试目录,给定比率....

但这是python,没有更简单的方法来做到这一点,似乎我正在做一个不必要的解决方法只是为了拆分文件。

bash 代码:

# Partition data randomly into train and test. 
cd $PATH_TO_DATASET
SPLIT=0.5 #train/test split
NUMBER_OF_FILES=$(ls $PATH_TO_DATASET |  wc -l) ## number of directories in the dataset
even=1
echo $NUMBER_OF_FILES

if [ `echo "$NUMBER_OF_FILES % 2" | bc` -eq 0 ]
then    
        even=1
        echo "Even is true"
else
        even=0
        echo "Even is false"
fi

echo -e "$BLUESeperating files in to train and test set!$NC"

for ((i=1; i<=$NUMBER_OF_FILES; i++))
do
    ran=$(python -c "import random;print(random.uniform(0.0, 1.0))")    
    if [[ $ran < $SPLIT ]]
    then 
        ##echo "test $ran"
        cp -R  $(ls -d */|sed "$iq;d") $WORKSPACE_SETUP_ROOT/../$WORKSPACE/data/test/
    else
        ##echo "train $ran"       
        cp -R  $(ls -d */|sed "$iq;d") $WORKSPACE_SETUP_ROOT/../$WORKSPACE/data/train/
    fi

    ##echo $(ls -d */|sed "$iq;d")
done    

cd $WORKSPACE_SETUP_ROOT/../$WORKSPACE/data
NUMBER_TRAIN_FILES=$(ls train/ |  wc -l)
NUMBER_TEST_FILES=$(ls test/ |  wc -l)

echo "$NUMBER_TRAIN_FILES and $NUMBER_TEST_FILES..."
echo $(calc $NUMBER_TRAIN_FILES/$NUMBER_OF_FILES)

if [[ $even = 1  ]] && [[ $NUMBER_TRAIN_FILES/$NUMBER_OF_FILES != $SPLIT ]]
    then 
    echo "Something need to be fixed!"
    if [[  $(calc $NUMBER_TRAIN_FILES/$NUMBER_OF_FILES) > $SPLIT ]]
    then
        echo "Too many files in the TRAIN set move some to TEST"
        cd train
        echo $(pwd)
        while [[ $NUMBER_TRAIN_FILES/$NUMBER_TEST_FILES != $SPLIT ]]
        do
            mv $(ls -d */|sed "1q;d") ../test/
            echo $(calc $NUMBER_TRAIN_FILES/$NUMBER_OF_FILES)
        done
    else
        echo "Too many files in the TEST set move some to TRAIN"
        cd test
        while [[ $NUMBER_TRAIN_FILES/$NUMBER_TEST_FILES != $SPLIT ]]
        do
            mv $(ls -d */|sed "1q;d") ../train/
            echo $(calc $NUMBER_TRAIN_FILES/$NUMBER_OF_FILES)
        done
    fi

fi   

我的问题是最后一部分。由于我随机选择数字,我不确定数据是否会按希望进行分区,我的最后一个 if 语句是检查分区是否正确,如果不正确,则修复它。这是不可能的,因为我正在检查浮点数,一般的解决方案更像是一个快速修复。

【问题讨论】:

我有兴趣查看一些示例数据以及您在 bash 中遇到问题的代码。 “赋值”是什么意思?你在移动文件吗?将数据插入数组?如果您还可以提供有关您用来决定发生什么情况的标准的更多信息,我们可能会提供有用的答案。 数据只是 .wav 文件。我的 bash 代码的问题是我尝试使用浮点数,这对于 bash 来说似乎并不理想。我将它从 data 文件夹移动/复制到 traintest 文件夹 好的,那么您使用什么标准来决定是否将某些内容发送到一个文件夹或另一个?你能在你的问题中包含你不工作的代码吗? 已添加代码.. 我只添加了部分代码,因为发布不必要的部分会很可笑.. 我添加了一个答案,向您展示了如何通过利用数组和参数扩展的力量单独使用 bash 来处理这个问题。为了将来参考,问题最好在回答Minimal, Complete, Verifiable Example 时得到解决。 【参考方案1】:

scikit-learn 来救援 =)

>>> import numpy as np
>>> from sklearn.cross_validation import train_test_split
>>> X, y = np.arange(10).reshape((5, 2)), range(5)
>>> X
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])
>>> y
[0, 1, 2, 3, 4]


# If i want 1/4 of the data for testing 
# and i set a random seed of 42.
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
>>> X_train
array([[4, 5],
       [0, 1],
       [6, 7]])
>>> X_test
array([[2, 3],
       [8, 9]])
>>> y_train
[2, 0, 3]
>>> y_test
[1, 4]

见http://scikit-learn.org/stable/modules/generated/sklearn.cross_validation.train_test_split.html


演示:

alvas@ubi:~$ mkdir splitfileproblem
alvas@ubi:~$ cd splitfileproblem/
alvas@ubi:~/splitfileproblem$ mkdir original
alvas@ubi:~/splitfileproblem$ mkdir train
alvas@ubi:~/splitfileproblem$ mkdir test
alvas@ubi:~/splitfileproblem$ ls
original  train  test
alvas@ubi:~/splitfileproblem$ cd original/
alvas@ubi:~/splitfileproblem/original$ ls
alvas@ubi:~/splitfileproblem/original$ echo 'abc' > a.txt
alvas@ubi:~/splitfileproblem/original$ echo 'def\nghi' > b.txt
alvas@ubi:~/splitfileproblem/original$ cat a.txt 
abc
alvas@ubi:~/splitfileproblem/original$ echo -e 'def\nghi' > b.txt
alvas@ubi:~/splitfileproblem/original$ cat b.txt 
def
ghi
alvas@ubi:~/splitfileproblem/original$ echo -e 'jkl' > c.txt
alvas@ubi:~/splitfileproblem/original$ echo -e 'mno' > d.txt
alvas@ubi:~/splitfileproblem/original$ ls
a.txt  b.txt  c.txt  d.txt

在 Python 中:

alvas@ubi:~/splitfileproblem$ ls
original  test  train
alvas@ubi:~/splitfileproblem$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> from sklearn.cross_validation import train_test_split
>>> os.listdir('original')
['b.txt', 'd.txt', 'c.txt', 'a.txt']
>>> X = y= os.listdir('original')
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
>>> X_train
['a.txt', 'd.txt', 'b.txt']
>>> X_test
['c.txt']

现在移动文件:

>>> for x in X_train:
...     os.rename('original/'+x , 'train/'+x)
... 
>>> for x in X_test:
...     os.rename('original/'+x , 'test/'+x)
... 
>>> os.listdir('test')
['c.txt']
>>> os.listdir('train')
['b.txt', 'd.txt', 'a.txt']
>>> os.listdir('original')
[]

另见:How to move a file in Python

【讨论】:

文件没有加载到python中......它们的实际文件需要从A移动到B.. 很高兴看到一个 bash 解决方案,我怀疑它涉及shufflemvawkls =) 这里的问题是文件必须随机分为训练和测试,给定的比率/拆分。 test_size参数是“比率”;P @alvas - 我提供了一个仅限 bash 的解决方案。它不使用shuffle,因为那是特定于供应商的(我相信仅限Linux)或awk,因为那完全是另一种语言,一切都可以在bash 中实现。至于mv,我认为它是“如何拆分随机集”的中心问题的次要问题。在这种情况下,由于well known pitfall,我希望不会看到解析ls 的答案。很好的答案,顺便说一句。我不知道 scikit-learn,能多接触 python 真是太好了。【参考方案2】:

这是一个使用 bash 的 $RANDOM 将内容移动到两个目标目录之一的简单示例。

$ touch 1..10
$ mkdir red blue
$ a=(*/)
$ RANDOM=$$
$ for f in [0-9]*; do mv -v "$f" "$a[$((RANDOM/(32768/$#a[@])))]"; done
1 -> red/1
10 -> red/10
2 -> blue/2
3 -> red/3
4 -> red/4
5 -> red/5
6 -> red/6
7 -> blue/7
8 -> blue/8
9 -> blue/9

此示例从创建 10 个文件和两个目标目录开始。它将一个数组设置为*/,该数组扩展为“当前目录中的所有目录”。然后它运行一个 for 循环,其中看起来像线路噪音。我会为你分解它。

"$a[$((RANDOM/(32768/$#a[@])+1))]" 是:

$a[ ...数组“a”, $((...)) ... 其下标为整数数学函数。 $RANDOM 是一个 bash 变量,它生成一个从 0 到 32767 的随机数(ish),我们的公式将该比率的分母除以: $#a[@],有效地将 RANDOM/32768 乘以数组“a”中的元素数。

所有这些的结果是我们选择了一个随机数组元素,也就是一个随机目录。

如果您真的想从“文件列表”开始工作,并且假设您将潜在目标列表留在数组“a”中,则可以将 for 循环替换为 while 循环:

while read f; do
  mv -v "$f" "$a[$((RANDOM/(32768/$#a[@])))]"
done < /dir/file.txt

现在......这些解决方案“平均”地分割结果。当你将分母相乘时,就会发生这种情况。而且因为它们是随机的,所以无法确保您的 random numbers won't put all your files into a single directory.因此,要分道扬镳,您需要更有创意。

假设我们只处理两个目标(因为我认为这就是您正在做的事情)。如果您正在寻找 25/75 拆分,请相应地分割随机数范围。

$ declare -a b=([0]="red/" [8192]="blue/")
$ for f in 1..10; do n=$RANDOM; for i in "$!b[@]"; do [ $i -gt $n ] && break; o="$b[i]"; done; mv -v "$f" "$o"; done

为了便于阅读,我们使用 cmets 列出了以下内容:

declare -a b=([0]="red/" [8192]="blue/")

for f in 1..10; do         # Step through our files...
  n=$RANDOM                  # Pick a random number, 0-32767
  for i in "$!b[@]"; do    # Step through the indices of the array of targets
    [ $i -gt $n ] && break   # If the current index is > than the random number, stop.
    o="$b[i]"              # If we haven't stopped, name this as our target,
  done
  mv -v "$f" "$o"            # and move the file there.
done

我们使用数组的索引来定义分割。 8192 是 32767 的 25%,即 $RANDOM 的最大值。您可以在此范围内随意拆分,包括超过 2 个。

如果你想测试这个方法的结果,在一个数组中计算结果是一种方法。让我们构建一个 shell 函数来帮助测试。

$ tester()  declare -A c=(); for f in 1..10000; do n=$RANDOM; for i in "$!b[@]"; do [ $i -gt $n ] && break; o="$b[i]"; done; ((c[$o]++)); done; declare -p c; 
$ declare -a b='([0]="red/" [8192]="blue/")'
$ tester
declare -A c='([blue/]="7540" [red/]="2460" )'
$ b=([0]="red/" [10992]="blue/")
$ tester
declare -A c='([blue/]="6633" [red/]="3367" )'

在第一行,我们定义了我们的函数。第二行将“b”数组设置为 25/75 分割,然后我们运行函数,其输出是计数器数组。然后我们用 33/67 分割(左右)重新定义“b”数组,并再次运行该函数以展示结果。

所以...虽然您当然可以为此使用 python,但您几乎可以肯定通过 bash 和一点数学来实现您所需要的。

【讨论】:

【参考方案3】:

这是第一个干切解决方案,纯 Python:

import sys, random, os

def splitdirs(files, dir1, dir2, ratio):
    shuffled = files[:]
    random.shuffle(shuffled)
    num = round(len(shuffled) * ratio)
    to_dir1, to_dir2 = shuffled[:num], shuffled[num:]
    for d in dir1, dir2:
        if not os.path.exists(d):
            os.mkdir(d)
    for file in to_dir1:
        os.symlink(file, os.path.join(dir1, os.path.basename(file)))
    for file in to_dir2:
        os.symlink(file, os.path.join(dir2, os.path.basename(file)))

if __name__ == '__main__':
    if len(sys.argv) != 5:
        sys.exit('Usage:  files.txt dir1 dir2 ratio'.format(sys.argv[0]))
    else:
        files, dir1, dir2, ratio = sys.argv[1:]
        ratio = float(ratio)
        files = open(files).read().splitlines()
        splitdirs(files, dir1, dir2, ratio)

[thd@aspire ~]$ python ./test.py ./files.txt dev tst 0.4 这里 files.txt 中列出的 40% 进入 dev 目录,60% -- 进入 tst

它会生成符号而不是复制,如果您需要真实文件,请将os.symlink 更改为shutil.copy2

【讨论】:

以上是关于给定一个比率,将文件随机分配到训练/测试中的主要内容,如果未能解决你的问题,请参考以下文章

将随机森林预测作为列添加到测试文件中

LDA训练过程(吉布斯采样)

使用 Pyspark 训练随机森林回归模型

Powershell根据给定的计数选择一个随机字母,并动态地将每个字母分配给一个唯一的变量?

如何将经过训练和测试的随机森林模型应用于 tidymodels 中的新数据集?

随机切分csv训练集和测试集