Случайно распределить файлы в поезд / тест с заданным соотношением
В данный момент я пытаюсь создать сценарий установки, способный настроить рабочее пространство для меня так, чтобы мне не нужно было делать это вручную. Я начал делать это в 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 "${BLUE}Seperating 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 "${i}q;d") ${WORKSPACE_SETUP_ROOT}/../${WORKSPACE}/data/test/
else
##echo "train ${ran}"
cp -R $(ls -d */|sed "${i}q;d") ${WORKSPACE_SETUP_ROOT}/../${WORKSPACE}/data/train/
fi
##echo $(ls -d */|sed "${i}q;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 должно было проверить, правильно ли было выполнено разбиение, а если нет, то исправить это. Это было невозможно Я проверяю числа с плавающей запятой, и решение в целом стало скорее быстрым решением.
3 ответа
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')
[]
Смотрите также: Как переместить файл в Python
Вот простой пример, который использует 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[
... массив "а",$((...))
... чей индекс является целочисленной математической функцией.$RANDOM
является переменной bash, которая генерирует число случайных чисел (ish) от 0 до 32767, и наша формула делит знаменатель этого отношения на:${#a[@]}
эффективно умножаяRANDOM/32768
по количеству элементов в массиве "а".
Результатом всего этого является то, что мы выбираем случайный элемент массива, то есть случайный каталог.
Если вы действительно хотите работать с вашим "списком файлов" и предполагаете, что вы оставляете список потенциальных целей в массиве "a", вы можете заменить цикл for на цикл while:
while read f; do
mv -v "$f" "${a[$((RANDOM/(32768/${#a[@]})))]}"
done < /dir/file.txt
Теперь... эти решения разделяют результаты "равномерно". Вот что происходит, когда вы умножаете знаменатель. И поскольку они случайные, нет никакого способа гарантировать, что ваши случайные числа не поместят все ваши файлы в один каталог. Поэтому, чтобы расколоться, нужно быть более креативным.
Давайте предположим, что мы имеем дело только с двумя целями (так как я думаю, это то, что вы делаете). Если вы ищете 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
Выделено для удобства чтения, вот что у нас есть, с комментариями:
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 - это 25% от 32767, максимальное значение $RANDOM. Вы можете разделить вещи по своему вкусу в пределах этого диапазона, в том числе среди более чем 2.
Если вы хотите проверить результаты этого метода, подсчет результатов в массиве - это способ сделать это. Давайте создадим функцию оболочки, чтобы помочь с тестированием.
$ 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, затем мы запускаем функцию, вывод которой - массив счетчиков. Затем мы переопределяем массив "b" с разделением 33/67 (или около того) и снова запускаем функцию, чтобы продемонстрировать результаты.
Итак... Хотя вы, безусловно, могли бы использовать для этого python, вы почти наверняка можете достичь того, что вам нужно, с помощью bash и небольшой математики.
Вот первое сухое решение, чистый 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
Здесь 40% из перечисленных в files.txt идет в dev dir, а 60% - в tst
Это делает symliks вместо копирования, если вам нужны настоящие файлы, измените os.symlink
в shutil.copy2