Лучший способ перетасовать два массива numpy в унисон

У меня есть два массива numpy разных форм, но с одинаковой длиной (ведущее измерение). Я хочу перетасовать каждый из них, так что соответствующие элементы продолжают соответствовать, т.е. Перетасовывать их в унисон относительно их ведущих индексов.

Этот код работает и иллюстрирует мои цели:

def shuffle_in_unison(a, b):
    assert len(a) == len(b)
    shuffled_a = numpy.empty(a.shape, dtype=a.dtype)
    shuffled_b = numpy.empty(b.shape, dtype=b.dtype)
    permutation = numpy.random.permutation(len(a))
    for old_index, new_index in enumerate(permutation):
        shuffled_a[new_index] = a[old_index]
        shuffled_b[new_index] = b[old_index]
    return shuffled_a, shuffled_b

Например:

>>> a = numpy.asarray([[1, 1], [2, 2], [3, 3]])
>>> b = numpy.asarray([1, 2, 3])
>>> shuffle_in_unison(a, b)
(array([[2, 2],
       [1, 1],
       [3, 3]]), array([2, 1, 3]))

Однако это кажется неуклюжим, неэффективным и медленным, и для этого требуется создать копию массивов - я бы предпочел перетасовать их на месте, так как они будут довольно большими.

Есть ли лучший способ сделать это? Более быстрое выполнение и использование более низкой памяти - мои основные цели, но элегантный код тоже будет приятным.

Еще одна мысль, которая у меня была, заключалась в следующем:

def shuffle_in_unison_scary(a, b):
    rng_state = numpy.random.get_state()
    numpy.random.shuffle(a)
    numpy.random.set_state(rng_state)
    numpy.random.shuffle(b)

Это работает... но это немного страшно, так как я не вижу никакой гарантии, что он будет продолжать работать - это не похоже на то, что гарантировало бы, например, выжить в версии numpy.

Ответ 1

Ваше "страшное" решение для меня не выглядит страшным. Вызов shuffle() для двух последовательностей одинаковой длины приводит к тому же количеству вызовов генератора случайных чисел, и это единственные "случайные" элементы в алгоритме тасования. Сбрасывая состояние, вы гарантируете, что вызовы генератора случайных чисел будут давать одинаковые результаты во втором вызове shuffle(), поэтому весь алгоритм будет генерировать ту же самую перестановку.

Если вам это не нравится, другим решением будет хранить ваши данные в одном массиве вместо двух с самого начала и создать два представления в этом одиночном массиве, имитирующем два массива, которые у вас есть сейчас. Вы можете использовать одиночный массив для перетасовки и представления для всех других целей.

Пример: предположим, что массивы a и b выглядят следующим образом:

a = numpy.array([[[  0.,   1.,   2.],
                  [  3.,   4.,   5.]],

                 [[  6.,   7.,   8.],
                  [  9.,  10.,  11.]],

                 [[ 12.,  13.,  14.],
                  [ 15.,  16.,  17.]]])

b = numpy.array([[ 0.,  1.],
                 [ 2.,  3.],
                 [ 4.,  5.]])

Теперь мы можем построить один массив, содержащий все данные:

c = numpy.c_[a.reshape(len(a), -1), b.reshape(len(b), -1)]
# array([[  0.,   1.,   2.,   3.,   4.,   5.,   0.,   1.],
#        [  6.,   7.,   8.,   9.,  10.,  11.,   2.,   3.],
#        [ 12.,  13.,  14.,  15.,  16.,  17.,   4.,   5.]])

Теперь мы создаем представления, имитирующие оригинальные a и b:

a2 = c[:, :a.size//len(a)].reshape(a.shape)
b2 = c[:, a.size//len(a):].reshape(b.shape)

Данные a2 и b2 разделяются с помощью c. Чтобы перетасовать оба массива одновременно, используйте numpy.random.shuffle(c).

В производственном коде вы, конечно, попытаетесь избежать создания исходных a и b вообще и сразу же создать c, a2 и b2.

Это решение может быть адаптировано к случаю, когда a и b имеют разные типы dtypes.

Ответ 2

Вы можете использовать NumPy индексирование массива:

def unison_shuffled_copies(a, b):
    assert len(a) == len(b)
    p = numpy.random.permutation(len(a))
    return a[p], b[p]

Это приведет к созданию отдельных массивов unison-shuffled.

Ответ 4

Очень простое решение:

randomize = np.arange(len(x))
np.random.shuffle(randomize)
x = x[randomize]
y = y[randomize]

два массива x, y теперь оба случайным образом перетасовываются таким же образом

Ответ 5

Джеймс написал в 2015 году решение sklearn, которое полезно. Но он добавил случайную переменную состояния, которая не нужна. В приведенном ниже коде случайное состояние от numpy автоматически принимается.

X = np.array([[1., 0.], [2., 1.], [0., 0.]])
y = np.array([0, 1, 2])
from sklearn.utils import shuffle
X, y = shuffle(X, y)

Ответ 6

Смешайте любое количество массивов вместе, на месте, используя только NumPy.

import numpy as np


def shuffle_arrays(arrays, set_seed=-1):
    """Shuffles arrays in-place, in the same order, along axis=0

    Parameters:
    -----------
    arrays : List of NumPy arrays.
    set_seed : Seed value if int >= 0, else seed is random.
    """
    assert all(len(arr) == len(arrays[0]) for arr in arrays)
    seed = np.random.randint(0, 2**(32 - 1) - 1) if set_seed < 0 else set_seed

    for arr in arrays:
        rstate = np.random.RandomState(seed)
        rstate.shuffle(arr)

И можно использовать как это

a = np.array([1, 2, 3, 4, 5])
b = np.array([10,20,30,40,50])
c = np.array([[1,10,11], [2,20,22], [3,30,33], [4,40,44], [5,50,55]])

shuffle_arrays([a, b, c])

Несколько замечаний:

  • Утверждение гарантирует, что все входные массивы имеют одинаковую длину вдоль их первого измерения.
  • Массивы перетасовывались на место по их первому измерению - ничего не возвращалось.
  • Случайное семя в пределах положительного диапазона int32.
  • Если требуется повторная тасовка, можно установить значение семени.

После перетасовки данные могут быть разделены с помощью np.split или ссылки с использованием срезов - в зависимости от приложения.

Ответ 7

вы можете создать массив:

s = np.arange(0, len(a), 1)

затем перетасовать его:

np.random.shuffle(s)

теперь используйте это как аргумент ваших массивов. те же перетасованные аргументы возвращают те же перетасованные векторы.

x_data = x_data[s]
x_label = x_label[s]

Ответ 8

Один из способов, с помощью которого перемещение на месте может быть выполнено для подключенных списков, - это использование семени (это может быть случайным) и использование numpy.random.shuffle для перетасовки.

# Set seed to a random number if you want the shuffling to be non-deterministic.
def shuffle(a, b, seed):
   np.random.seed(seed)
   np.random.shuffle(a)
   np.random.seed(seed)
   np.random.shuffle(b)

Это. Это будет тасовать как a, так и b точно так же. Это также делается на месте, что всегда является плюсом.

EDIT, не используйте np.random.seed() используйте np.random.RandomState вместо этого

def shuffle(a, b, seed):
   rand_state = np.random.RandomState(seed)
   rand_state.shuffle(a)
   rand_state.seed(seed)
   rand_state.shuffle(b)

Когда вы вызываете его, просто передавайте в любое семя для подачи случайного состояния:

a = [1,2,3,4]
b = [11, 22, 33, 44]
shuffle(a, b, 12345)

Выход:

>>> a
[1, 4, 2, 3]
>>> b
[11, 44, 22, 33]

Изменение: Исправлен код для повторного засеивания случайного состояния

Ответ 9

Существует известная функция, которая может справиться с этим:

from sklearn.model_selection import train_test_split
X, _, Y, _ = train_test_split(X,Y, test_size=0.0)

Просто установка test_size в 0 позволит избежать расщепления и дать вам перетасованные данные. Хотя он обычно используется для разделения данных поезда и теста, он тоже перемешивает их.
Из документации

Сплит-массивы или матрицы в случайные поезда и тестовые подмножества

Быстрая утилита, которая включает проверку ввода и следующую (ShuffleSplit(). Split (X, y)) и приложение для ввода данных в один вызов для разделения (и, возможно, подвыборки) данных в oneliner.

Ответ 10

from np.random import permutation
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data #numpy array
y = iris.target #numpy array

# Data is currently unshuffled; we should shuffle 
# each X[i] with its corresponding y[i]
perm = permutation(len(X))
X = X[perm]
y = y[perm]

Ответ 11

Скажем, у нас есть два массива: a и b.

a = np.array([[1,2,3],[4,5,6],[7,8,9]])
b = np.array([[9,1,1],[6,6,6],[4,2,0]]) 

Сначала мы можем получить индексы строк путем перестановки первого измерения

indices = np.random.permutation(a.shape[0])
[1 2 0]

Затем используйте расширенную индексацию. Здесь мы используем одни и те же индексы, чтобы перетасовать оба массива в унисон.

a_shuffled = a[indices[:,np.newaxis], np.arange(a.shape[1])]
b_shuffled = b[indices[:,np.newaxis], np.arange(b.shape[1])]

Это эквивалентно

np.take(a, indices, axis=0)
[[4 5 6]
 [7 8 9]
 [1 2 3]]

np.take(b, indices, axis=0)
[[6 6 6]
 [4 2 0]
 [9 1 1]]

Ответ 12

Если вы хотите избежать копирования массивов, я бы предположил, что вместо создания списка перестановок вы просматриваете каждый элемент в массиве и произвольно меняете его в другую позицию в массиве

for old_index in len(a):
    new_index = numpy.random.randint(old_index+1)
    a[old_index], a[new_index] = a[new_index], a[old_index]
    b[old_index], b[new_index] = b[new_index], b[old_index]

Это реализует алгоритм перетасовки Knuth-Fisher-Yates.

Ответ 13

Пример: это то, что я делаю:

combo = []
for i in range(60000):
    combo.append((images[i], labels[i]))

shuffle(combo)

im = []
lab = []
for c in combo:
    im.append(c[0])
    lab.append(c[1])
images = np.asarray(im)
labels = np.asarray(lab)

Ответ 14

Я добавил python random.shuffle(), чтобы взять второй аргумент arg:

def shuffle_together(x, y):
    assert len(x) == len(y)

    for i in reversed(xrange(1, len(x))):
        # pick an element in x[:i+1] with which to exchange x[i]
        j = int(random.random() * (i+1))
        x[i], x[j] = x[j], x[i]
        y[i], y[j] = y[j], y[i]

Таким образом, я могу быть уверен, что перетасовка происходит на месте, а функция не слишком длинная или сложная.