Modo migliore per mescolare due schiere insensibili all'unisono


239

Ho due schiere insensibili di forme diverse, ma con la stessa lunghezza (dimensione iniziale). Voglio mescolare ciascuno di essi, in modo tale che gli elementi corrispondenti continuino a corrispondere, ovvero mescolarli all'unisono rispetto ai loro indici principali.

Questo codice funziona e illustra i miei obiettivi:

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

Per esempio:

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

Tuttavia, questo sembra goffo, inefficiente e lento, e richiede una copia degli array: preferirei mescolarli sul posto, poiché saranno piuttosto grandi.

C'è un modo migliore per farlo? L'esecuzione più rapida e l'utilizzo della memoria inferiore sono i miei obiettivi principali, ma anche il codice elegante sarebbe bello.

Un altro pensiero che avevo era questo:

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)

Funziona ... ma è un po 'spaventoso, poiché vedo poche garanzie che continuerà a funzionare - non sembra il tipo di cosa che è garantito per sopravvivere nella versione intorpidita, per esempio.


10
Sei anni dopo, sono divertito e sorpreso da quanto sia stata popolare questa domanda. E in una piacevole coincidenza, per Go 1.10 ho contribuito matematica / rand. Shuffle alla libreria standard . Il design dell'API rende banale mescolare due array all'unisono e farlo è anche incluso come esempio nei documenti.
Josh Bleecher Snyder,

Risposte:


72

La tua soluzione "spaventosa" non mi sembra spaventosa. La chiamata shuffle()per due sequenze della stessa lunghezza comporta lo stesso numero di chiamate al generatore di numeri casuali e questi sono gli unici elementi "casuali" dell'algoritmo shuffle. Ripristinando lo stato, si assicura che le chiamate al generatore di numeri casuali forniranno gli stessi risultati nella seconda chiamata shuffle(), quindi l'intero algoritmo genererà la stessa permutazione.

Se non ti piace, una soluzione diversa sarebbe quella di archiviare i tuoi dati in un array anziché due fin dall'inizio, e creare due viste in questo singolo array simulando i due array che hai adesso. È possibile utilizzare l'array singolo per la riproduzione casuale e le visualizzazioni per tutti gli altri scopi.

Esempio: Supponiamo che gli array ae bsimile a questa:

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

Ora possiamo costruire un singolo array contenente tutti i dati:

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

Ora creiamo viste simulando l'originale ae b:

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

I dati di a2e b2sono condivisi con c. Per mescolare entrambi gli array contemporaneamente, utilizzare numpy.random.shuffle(c).

Nel codice di produzione, si sarebbe naturalmente cercare di evitare di creare l'originale ae bdel tutto e subito creare c, a2e b2.

Questa soluzione potrebbe essere adattata al caso in questione ae bavere diversi tipi.


Ri: la soluzione spaventosa: temo solo che matrici di forme diverse possano (concepibilmente) produrre numeri diversi di chiamate all'rng, il che provocherebbe divergenze. Tuttavia, penso che tu abbia ragione nel ritenere che il comportamento attuale sia probabilmente improbabile, e un test molto semplice semplifica la conferma del comportamento corretto ...
Josh Bleecher Snyder,

Mi piace il tuo approccio suggerito e potrei sicuramente fare in modo che aeb inizi la vita come un array c unificato. Tuttavia, aeb dovranno essere contigui poco dopo il mescolamento (per un trasferimento efficiente a una GPU), quindi penso che, nel mio caso particolare, finirò per fare comunque copie di aeb. :(
Josh Bleecher Snyder il

@Josh: nota che numpy.random.shuffle()opera su sequenze mutabili arbitrarie, come elenchi Python o array NumPy. La forma dell'array non ha importanza, ma solo la lunghezza della sequenza. È molto improbabile che ciò cambi a mio avviso.
Sven Marnach,

Non lo sapevo. Questo mi rende molto più a mio agio. Grazie.
Josh Bleecher Snyder,

@SvenMarnach: ho pubblicato una risposta qui sotto. Puoi commentare se pensi che abbia senso / sia un buon modo per farlo?
ajfbiw.s,

352

Puoi usare l' indicizzazione dell'array di NumPy :

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

Ciò comporterà la creazione di matrici separate all'unisono.


13
Questo fa di creare copie, in quanto utilizza l'indicizzazione avanzata. Ma ovviamente è più veloce dell'originale.
Sven Marnach,

1
@mtrw: il semplice fatto che le matrici originali non siano toccate non esclude che le matrici restituite siano viste degli stessi dati. Ma in realtà non lo sono, poiché le visualizzazioni NumPy non sono abbastanza flessibili da supportare le visualizzazioni consentite (neanche questo sarebbe desiderabile).
Sven Marnach,

1
@Sven - Devo davvero conoscere le visualizzazioni. @Dat Chu - Ho appena provato >>> 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 ottenuto 38 secondi per la versione dell'OP e 27,5 secondi per la mia, per 1 milione di chiamate ciascuno.
mtrw,

3
Mi piace molto la semplicità e la leggibilità di questo, e l'indicizzazione avanzata continua a sorprendermi e stupirmi; per questo questa risposta prontamente ottiene +1. Stranamente, però, sui miei (grandi) set di dati, è più lento della mia funzione originale: il mio originale impiega ~ 1.8s per 10 iterazioni e questo richiede ~ 2.7s. Entrambi i numeri sono abbastanza coerenti. Il set di dati che ho usato per testare a.shapeè (31925, 405)ed b.shapeè (31925,).
Josh Bleecher Snyder,

1
Forse, la lentezza ha a che fare con il fatto che non stai facendo le cose sul posto, ma stai invece creando nuove matrici. O con una certa lentezza legata al modo in cui CPython analizza gli indici di array.
Íhor Mé,


33

Soluzione molto semplice:

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

i due array x, y ora sono entrambi casualmente mescolati allo stesso modo


5
Ciò equivale alla soluzione di mtrw. Le tue prime due righe stanno solo generando una permutazione, ma ciò può essere fatto in una riga.
Josh Bleecher Snyder,

19

James ha scritto nel 2015 una soluzione sklearn utile. Ma ha aggiunto una variabile di stato casuale, che non è necessaria. Nel codice seguente, lo stato casuale da numpy viene assunto automaticamente.

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

Mischia un numero qualsiasi di matrici insieme, sul posto, usando solo 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 può essere usato in questo modo

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

Alcune cose da notare:

  • L'asserzione assicura che tutti gli array di input abbiano la stessa lunghezza lungo la loro prima dimensione.
  • Le matrici si mischiarono sul posto in base alla loro prima dimensione: non è tornato nulla.
  • Seme casuale nel range int32 positivo.
  • Se è necessario uno shuffle ripetibile, è possibile impostare il valore del seme.

Dopo lo shuffle, i dati possono essere divisi usando np.splito referenziati usando le sezioni - a seconda dell'applicazione.


2
bellissima soluzione, ha funzionato perfettamente per me. Anche con array di 3+ ​​assi
wprins

1
Questa è la risposta corretta Non vi è alcun motivo per utilizzare il np.random globale quando è possibile passare oggetti di stato casuali.
Erotemic

Uno RandomStatepotrebbe essere usato al di fuori del ciclo. Vedi la risposta di
bartolo-otrit

1
@ bartolo-otrit, la scelta che deve essere fatta nel forloop è se riassegnare o ridimensionare lo stato casuale. Con il numero di array passati in una funzione di shuffle che dovrebbe essere piccola, non mi aspetto una differenza di prestazioni tra i due. Ma sì, rstate potrebbe essere assegnato al di fuori del loop e ridimensionato all'interno del loop ad ogni iterazione.
Isaac B

9

puoi creare un array come:

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

quindi mescolalo:

np.random.shuffle(s)

ora usa questo s come argomento dei tuoi array. gli stessi argomenti mescolati restituiscono gli stessi vettori mescolati.

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

Davvero, questa è la soluzione migliore e dovrebbe essere quella accettata! Funziona anche per molti (più di 2) array contemporaneamente. L'idea è semplice: basta mescolare l'elenco degli indici [0, 1, 2, ..., n-1], quindi reindicizzare le righe degli array con gli indici mescolati. Bello!
Basj,

5

Un modo in cui è possibile eseguire il mescolamento sul posto per gli elenchi collegati è l'utilizzo di un seed (potrebbe essere casuale) e l'utilizzo di numpy.random.shuffle per eseguire il mescolamento.

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

Questo è tutto. Questo mescolerà sia a che b nello stesso identico modo. Anche questo viene fatto sul posto, il che è sempre un vantaggio.

EDIT, non usare np.random.seed () usa invece 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)

Quando lo chiami, passa qualsiasi seme per alimentare lo stato casuale:

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

Produzione:

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

Modifica: risolto il codice per ripetere il seeding dello stato casuale


Questo codice non funziona. RandomStatecambia stato in prima convocazione ed ae bnon sono mescolate all'unisono.
Bruno Klein,

@BrunoKlein Hai ragione. Ho corretto il post per ripetere il seeding dello stato casuale. Inoltre, anche se non è all'unisono nel senso che entrambi gli elenchi vengono mescolati allo stesso tempo, sono all'unisono nel senso che entrambi sono mescolati allo stesso modo, e inoltre non richiede più memoria per contenere un copia delle liste (menzionate dall'OP nella sua domanda)
Adam Snaider,

4

Esiste una funzione ben nota che può gestire questo:

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

L'impostazione di test_size su 0 eviterà la divisione e ti darà dati mescolati. Sebbene di solito sia usato per dividere i dati del treno e dei test, li mescola anche.
Dalla documentazione

Dividi array o matrici in treno casuale e sottoinsiemi di test

Utilità rapida che avvolge la convalida dell'input e la successiva (ShuffleSplit (). Split (X, y)) e l'applicazione per inserire i dati in una singola chiamata per dividere (e facoltativamente sottocampionare) i dati in un oneliner.


Non posso credere di non aver mai pensato a questo. La tua risposta è geniale.
Long Nguyen,

2

Supponiamo di avere due array: 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]]) 

Possiamo prima ottenere indici di riga permutando la prima dimensione

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

Quindi utilizzare l'indicizzazione avanzata. Qui stiamo usando gli stessi indici per mescolare entrambi gli array all'unisono.

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

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

Perché non solo a [indici ,:] ob [indici ,:]?
Kev

1

Se si desidera evitare la copia di array, suggerirei che invece di generare un elenco di permutazioni, si passa attraverso ogni elemento dell'array e lo si scambia casualmente in un'altra posizione dell'array

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]

Questo implementa l'algoritmo shuffle Knuth-Fisher-Yates.


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.html mi ha diffidato dell'implementazione dei miei algoritmi shuffle; è in parte responsabile della mia domanda. :) Tuttavia, hai ragione a sottolineare che dovrei prendere in considerazione l'uso dell'algoritmo Knuth-Fisher-Yates.
Josh Bleecher Snyder,

Ben individuato, ho corretto il codice ora. Ad ogni modo, penso che l'idea di base dello shuffling sul posto sia scalabile a un numero arbitrario di array evitando di fare copie.
DaveP,

Il codice è ancora errato (non verrà nemmeno eseguito). Per farlo funzionare, sostituire len(a)con reversed(range(1, len(a))). Ma non sarà comunque molto efficiente.
Sven Marnach,

1

Questa sembra una soluzione molto semplice:

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

Con un esempio, questo è quello che sto facendo:

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
Questo è più o meno equivalente a combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), solo più lento. Dato che stai usando Numpy comunque, una soluzione ancora più veloce sarebbe quella di comprimere le matrici usando Numpy combo = np.c_[images, labels], shuffle e decomprimere di nuovo images, labels = combo.T. Supponendo che labelse imagessono array NumPy monodimensionali della stessa lunghezza per cominciare, questo sarà facilmente la soluzione più veloce. Se sono multidimensionali, vedi la mia risposta sopra.
Sven Marnach,

Ok ha senso. Grazie! @SvenMarnach
ajfbiw.s,

0

Ho esteso random.shuffle () di python per prendere un secondo argomento:

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]

In questo modo posso essere sicuro che il mescolamento avvenga sul posto e che la funzione non sia troppo lunga o complicata.


0

Usa solo numpy...

In primo luogo unire i due array di input 1D array è etichette (y) e 2D array è dati (x) e mescolarli con il shufflemetodo NumPy . Finalmente dividerli e tornare.

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.