Melhor maneira de embaralhar duas matrizes numpy em uníssono


239

Eu tenho duas matrizes numpy de formas diferentes, mas com o mesmo comprimento (dimensão inicial). Quero embaralhar cada um deles, de modo que os elementos correspondentes continuem a corresponder - ou seja, embaralhe-os em uníssono com relação aos seus principais índices.

Este código funciona e ilustra meus objetivos:

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

Por exemplo:

>>> 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]))

No entanto, isso parece desajeitado, ineficiente e lento, e exige uma cópia das matrizes - eu prefiro embaralhá-las no lugar, pois elas são muito grandes.

Existe uma maneira melhor de fazer isso? Execução mais rápida e menor uso de memória são meus principais objetivos, mas um código elegante também seria bom.

Um outro pensamento que eu tinha era o seguinte:

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)

Isso funciona ... mas é um pouco assustador, pois vejo pouca garantia de que continuará funcionando - não parece o tipo de coisa que é garantida para sobreviver em uma versão numpy, por exemplo.


10
Seis anos depois, estou divertido e surpreso com o quão popular essa questão provou ser. E em uma coincidência deliciosa, para o Go 1.10, contribuí com math / rand.Shuffle na biblioteca padrão . O design da API torna trivial a organização aleatória de duas matrizes em uníssono, e isso é incluso como exemplo nos documentos.
Josh Bleecher Snyder

Respostas:


72

Sua solução "assustadora" não me parece assustadora. A chamada shuffle()de duas seqüências do mesmo comprimento resulta no mesmo número de chamadas para o gerador de números aleatórios, e esses são os únicos elementos "aleatórios" no algoritmo de reprodução aleatória. Ao redefinir o estado, você garante que as chamadas para o gerador de números aleatórios fornecerão os mesmos resultados na segunda chamada para shuffle(), para que todo o algoritmo gere a mesma permutação.

Se você não gostar disso, uma solução diferente seria armazenar seus dados em uma matriz em vez de duas desde o início, e criar duas visualizações nessa matriz única, simulando as duas matrizes que você tem agora. Você pode usar a matriz única para embaralhar e as visualizações para todos os outros fins.

Exemplo: Vamos assumir as matrizes ae ter a seguinte baparência:

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.]])

Agora podemos construir uma única matriz contendo todos os dados:

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.]])

Agora criamos visualizações simulando o original ae b:

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

Os dados de a2e b2são compartilhados com c. Para embaralhar as duas matrizes simultaneamente, use numpy.random.shuffle(c).

No código de produção, você iria, claro, tentar evitar a criação do original ae bem tudo e imediatamente criar c, a2e b2.

Esta solução pode ser adaptada ao caso ae bter diferentes tipos.


Re: a solução assustadora: Só me preocupo que matrizes de formas diferentes possam (concebivelmente) produzir números diferentes de chamadas para o rng, o que causaria divergência. No entanto, eu acho que você está certo de que o comportamento atual é talvez improvável que a mudança, e um doctest muito simples faz confirmando o comportamento correto muito fácil ...
Josh Bleecher Snyder

Eu gosto da sua abordagem sugerida e definitivamente posso organizar a e b começar a vida como uma matriz c unificada. No entanto, aeb precisa ser contíguo logo após o embaralhamento (para transferência eficiente para uma GPU), então acho que, no meu caso particular, acabaria fazendo cópias de aeb de qualquer maneira. :(
Josh Bleecher Snyder

@ Josh: Observe que numpy.random.shuffle()opera em seqüências mutáveis ​​arbitrárias, como listas Python ou matrizes NumPy. A forma da matriz não importa, apenas o comprimento da sequência. É muito improvável que isso mude na minha opinião.
Sven Marnach

Eu não sabia disso. Isso me deixa muito mais confortável com isso. Obrigado.
Josh Bleecher Snyder

@SvenMarnach: Postei uma resposta abaixo. Você pode comentar se acha que faz sentido / é uma boa maneira de fazê-lo?
ajfbiw.s

352

Você pode usar a indexação de array do NumPy :

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

Isso resultará na criação de matrizes separadas por uníssono.


13
Isso faz criar cópias, já que usa indexação avançada. Mas é claro que é mais rápido que o original.
precisa saber é o seguinte

1
@ mtrw: O simples fato de as matrizes originais serem intocadas não impede que as matrizes retornadas sejam vistas dos mesmos dados. Mas, na verdade, não são, pois as visualizações do NumPy não são flexíveis o suficiente para suportar as visualizações permutadas (isso também não seria desejável).
Sven Marnach

1
@ Sven - Eu realmente tenho que aprender sobre pontos de vista. @Dat Chu - Eu apenas tentei >>> t = timeit.Timer(stmt = "<function>(a,b)", setup = "import numpy as np; a,b = np.arange(4), np.arange(4*20).reshape((4,20))")>>> t.timeit()e recebi 38 segundos para a versão do OP e 27,5 segundos para a minha, para 1 milhão de chamadas cada.
MTRW

3
Eu realmente gosto da simplicidade e legibilidade disso, e a indexação avançada continua a me surpreender e me surpreender; por isso, esta resposta recebe +1. Curiosamente, no entanto, nos meus (grandes) conjuntos de dados, é mais lento que minha função original: meu original demora ~ 1,8s para 10 iterações, e isso leva ~ 2,7s. Ambos os números são bastante consistentes. O conjunto de dados que usei para testar tem a.shapeé (31925, 405)e b.shapeé (31925,).
Josh Bleecher Snyder

1
Talvez a lentidão tenha a ver com o fato de você não estar fazendo as coisas no local, mas criando novas matrizes. Ou com alguma lentidão relacionada à maneira como o CPython analisa os índices de matriz.
Íhor Mé


33

Solução muito simples:

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

as duas matrizes x, y agora são aleatoriamente embaralhadas da mesma maneira


5
Isso é equivalente à solução do mtrw. Suas duas primeiras linhas estão apenas gerando uma permutação, mas isso pode ser feito em uma linha.
Josh Bleecher Snyder,

19

James escreveu em 2015 uma solução sklearn que é útil. Mas ele adicionou uma variável de estado aleatória, que não é necessária. No código abaixo, o estado aleatório de numpy é automaticamente assumido.

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)

16
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]

12

Misture aleatoriamente qualquer número de matrizes, no local, usando apenas 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)

E pode ser usado assim

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])

Algumas coisas a serem observadas:

  • A declaração garante que todas as matrizes de entrada tenham o mesmo comprimento ao longo de sua primeira dimensão.
  • Matrizes embaralhadas no local por sua primeira dimensão - nada retornou.
  • Semente aleatória dentro da faixa int32 positiva.
  • Se for necessário um shuffle repetível, o valor da semente pode ser definido.

Após a reprodução aleatória, os dados podem ser divididos usando np.splitou referenciados usando fatias - dependendo do aplicativo.


2
solução bonita, isso funcionou perfeito para mim. Mesmo com matrizes de 3 ou mais eixos
wprins 01/11/19

1
Essa é a resposta correta. Não há razão para usar o np.random global quando você pode passar objetos de estado aleatório.
Erotêmico

Um RandomStatepoderia ser usado fora do loop. Veja a resposta de
bartolo-otrit 14/03

1
@ bartolo-otrit, a escolha que deve ser feita no forloop é se é necessário reatribuir ou reenviar o estado aleatório. Com o número de matrizes sendo passado para uma função de reprodução aleatória que espera ser pequena, eu não esperaria uma diferença de desempenho entre as duas. Mas sim, o rstate pode ser atribuído fora do loop e processado novamente dentro do loop em cada iteração.
Isaac B

9

você pode criar uma matriz como:

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

embaralhe:

np.random.shuffle(s)

Agora use este s como argumento de suas matrizes. os mesmos argumentos embaralhados retornam os mesmos vetores embaralhados.

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

Realmente, esta é a melhor solução e deve ser a aceita! Até funciona para muitas (mais de 2) matrizes ao mesmo tempo. A idéia é simples: basta embaralhar a lista de índices [0, 1, 2, ..., n-1] e reindexar as linhas das matrizes com os índices embaralhados. Agradável!
Basj

5

Uma maneira pela qual o embaralhamento no local pode ser feito para listas conectadas é usar uma semente (pode ser aleatória) e usar numpy.random.shuffle para fazer o embaralhamento.

# 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)

É isso aí. Isso embaralha ambos aeb exatamente da mesma maneira. Isso também é feito no local, o que é sempre uma vantagem.

EDIT, não use np.random.seed () use 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)

Ao chamá-lo, basta passar qualquer semente para alimentar o estado aleatório:

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

Resultado:

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

Edit: Corrigido código para re-seedear o estado aleatório


Este código não funciona. RandomStatemuda de estado na primeira chamada e ae bnão são embaralhadas em uníssono.
Bruno Klein

@BrunoKlein Você está certo. Corrigi o post para re-propagar o estado aleatório. Além disso, mesmo que não seja uníssono no sentido de as duas listas serem embaralhadas ao mesmo tempo, elas são uníssonas no sentido de que ambas são embaralhadas da mesma maneira e também não requer mais memória para manter um cópia das listas (que OP menciona na sua pergunta)
Adam Snaider

4

Há uma função conhecida que pode lidar com isso:

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

Apenas definir test_size como 0 evitará dividir e fornecer dados aleatórios. Embora seja geralmente usado para dividir dados de trem e teste, eles também são embaralhados.
Da documentação

Dividir matrizes ou matrizes em subconjuntos aleatórios de trem e teste

Utilitário rápido que envolve a validação de entrada e a próxima (ShuffleSplit (). Split (X, y)) e aplicativo para inserir dados em uma única chamada para dividir (e opcionalmente subamostrar) dados em um oneliner.


Não acredito que nunca pensei nisso. Sua resposta é brilhante.
Long Nguyen

2

Digamos que temos duas matrizes: a e 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]]) 

Podemos primeiro obter índices de linha permutando a primeira dimensão

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

Em seguida, use a indexação avançada. Aqui estamos usando os mesmos índices para embaralhar as duas matrizes em uníssono.

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

Isso é equivalente a

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]]

Por que não apenas a [índices ,:] ou b [índices ,:]?
Kev

1

Se você quiser evitar copiar matrizes, sugiro que, em vez de gerar uma lista de permutações, você percorra todos os elementos da matriz e troque aleatoriamente para outra posição na matriz

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]

Isso implementa o algoritmo de reprodução aleatória de Knuth-Fisher-Yates.


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.html me deixou desconfiado de implementar meus próprios algoritmos de reprodução aleatória; é em parte responsável por eu fazer esta pergunta. :) No entanto, você tem razão em apontar que devo considerar o uso do algoritmo Knuth-Fisher-Yates.
Josh Bleecher Snyder,

Bem descoberto, corrigi o código agora. De qualquer forma, acho que a idéia básica de embaralhar no local é escalável para um número arbitrário de matrizes que evita fazer cópias.
DaveP

O código ainda está incorreto (nem será executado). Para fazê-lo funcionar, substitua len(a)por reversed(range(1, len(a))). Mas não será muito eficiente de qualquer maneira.
Sven Marnach

1

Parece uma solução muito simples:

import numpy as np
def shuffle_in_unison(a,b):

    assert len(a)==len(b)
    c = np.arange(len(a))
    np.random.shuffle(c)

    return a[c],b[c]

a =  np.asarray([[1, 1], [2, 2], [3, 3]])
b =  np.asarray([11, 22, 33])

shuffle_in_unison(a,b)
Out[94]: 
(array([[3, 3],
        [2, 2],
        [1, 1]]),
 array([33, 22, 11]))

0

Com um exemplo, é isso que estou fazendo:

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)

1
Isso é mais ou menos equivalente a combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), apenas mais lento. Como você está usando o Numpy de qualquer maneira, uma solução ainda mais rápida seria compactar as matrizes usando o Numpy combo = np.c_[images, labels], embaralhar e descompactar novamente images, labels = combo.T. Supondo que labelse imagessejam matrizes Numpy unidimensionais do mesmo comprimento, essa será facilmente a solução mais rápida. Se eles são multidimensionais, veja minha resposta acima.
Sven Marnach

Ok, isso faz sentido. Obrigado! @SvenMarnach
ajfbiw.s

0

Estendi o random.shuffle () do python para pegar um segundo argumento:

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]

Dessa forma, posso ter certeza de que o embaralhamento acontece no local e a função não é muito longa ou complicada.


0

Basta usar numpy...

Primeiro mescle as duas matrizes de entrada 1D array é labels (y) e 2D array é data (x) e as embaralha com o shufflemétodo NumPy . Finalmente divida-os e volte.

import numpy as np

def shuffle_2d(a, b):
    rows= a.shape[0]
    if b.shape != (rows,1):
        b = b.reshape((rows,1))
    S = np.hstack((b,a))
    np.random.shuffle(S)
    b, a  = S[:,0], S[:,1:]
    return a,b

features, samples = 2, 5
x, y = np.random.random((samples, features)), np.arange(samples)
x, y = shuffle_2d(train, test)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.