Лучший способ перетасовать два массива 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, например.

11 149

11 ответов:

ваше "страшное" решение не кажется мне страшным. Звоню 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.

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

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

это приведет к созданию отдельных массивов, перемешанных в унисон.

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, random_state=0)

более подробно см. В разделе http://scikit-learn.org/stable/modules/generated/sklearn.utils.shuffle.html

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

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

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

Джеймс написал в 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)

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

# 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 точно так же. Это также делается на месте, что всегда является плюсом.

редактировать, не использовать np.случайность.seed () использовать np.случайность.Вместо 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]

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

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

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

затем перетасуйте:

np.random.shuffle(s)

Теперь используйте этот s в качестве аргумента ваших массивов. одинаковым аргументы вернуть одинаковым векторов.

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

перемешать любое количество массивов вместе, на месте, используя только 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 или ссылаться с помощью срезов - в зависимости от приложения.

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

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]

это реализует алгоритм перемешивания кнута-Фишера-Йейтса.

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

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)

я расширил случайный python.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]

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