Besserer Weg, um zwei numpy Arrays gleichzeitig zu mischen


238

Ich habe zwei numpy Arrays unterschiedlicher Form, aber mit der gleichen Länge (führende Dimension). Ich möchte jeden von ihnen so mischen, dass die entsprechenden Elemente weiterhin übereinstimmen - dh sie in Bezug auf ihre Leitindizes im Einklang mischen.

Dieser Code funktioniert und veranschaulicht meine Ziele:

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

Beispielsweise:

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

Dies fühlt sich jedoch klobig, ineffizient und langsam an und erfordert das Erstellen einer Kopie der Arrays. Ich würde sie lieber an Ort und Stelle mischen, da sie ziemlich groß sind.

Gibt es einen besseren Weg, dies zu tun? Schnellere Ausführung und geringere Speichernutzung sind meine Hauptziele, aber eleganter Code wäre auch schön.

Ein anderer Gedanke, den ich hatte, war folgender:

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)

Das funktioniert ... aber es ist ein wenig beängstigend, da ich wenig Garantie dafür sehe, dass es weiterhin funktioniert - es sieht nicht so aus, als würde es zum Beispiel in der Numpy-Version garantiert überleben.


9
Sechs Jahre später bin ich amüsiert und überrascht, wie beliebt diese Frage war. Und in einem wunderbaren Zufall habe ich für Go 1.10 math / rand.Shuffle zur Standardbibliothek beigetragen . Das Design der API macht es trivial, zwei Arrays gleichzeitig zu mischen, und dies ist sogar als Beispiel in den Dokumenten enthalten.
Josh Bleecher Snyder

Antworten:


71

Ihre "beängstigende" Lösung erscheint mir nicht beängstigend. Das Aufrufen shuffle()von zwei Sequenzen gleicher Länge führt zu der gleichen Anzahl von Aufrufen des Zufallszahlengenerators, und dies sind die einzigen "zufälligen" Elemente im Zufallsalgorithmus. Durch Zurücksetzen des Status stellen Sie sicher, dass die Aufrufe des Zufallszahlengenerators beim zweiten Aufruf dieselben Ergebnisse liefern shuffle(), sodass der gesamte Algorithmus dieselbe Permutation generiert.

Wenn Ihnen dies nicht gefällt, besteht eine andere Lösung darin, Ihre Daten von Anfang an in einem Array statt in zwei zu speichern und zwei Ansichten in diesem einzelnen Array zu erstellen, um die beiden Arrays zu simulieren, die Sie jetzt haben. Sie können das einzelne Array zum Mischen und die Ansichten für alle anderen Zwecke verwenden.

Beispiel: Nehmen wir die Arrays an aund bsehen folgendermaßen aus:

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

Wir können jetzt ein einzelnes Array erstellen, das alle Daten enthält:

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

Jetzt erstellen wir Ansichten, die das Original simulieren aund b:

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

Die Daten von a2und werden b2mit geteilt c. Verwenden Sie, um beide Arrays gleichzeitig zu mischen numpy.random.shuffle(c).

Im Produktionscode würden Sie natürlich versuchen, das Erstellen des Originals zu vermeiden aund büberhaupt und sofort zu erstellen c, a2und b2.

Diese Lösung könnte an den Fall angepasst werden aund bunterschiedliche dtypen haben.


Betreff: Die beängstigende Lösung: Ich mache mir nur Sorgen, dass Arrays mit unterschiedlichen Formen (möglicherweise) unterschiedliche Anzahlen von Anrufen an das Rng liefern könnten, was zu Divergenz führen würde. Ich denke jedoch, Sie haben Recht, dass sich das aktuelle Verhalten möglicherweise nicht ändern wird, und eine sehr einfache Prüfung macht es sehr einfach, das richtige Verhalten zu bestätigen ...
Josh Bleecher Snyder

Ich mag Ihren vorgeschlagenen Ansatz und könnte definitiv dafür sorgen, dass a und b als einheitliches c-Array ins Leben gerufen werden. A und b müssen jedoch kurz nach dem Mischen zusammenhängend sein (für eine effiziente Übertragung auf eine GPU), daher denke ich, dass ich in meinem speziellen Fall ohnehin Kopien von a und b erstellen würde. :(
Josh Bleecher Snyder

@Josh: Beachten Sie, dass numpy.random.shuffle()beliebige veränderbare Sequenzen wie Python-Listen oder NumPy-Arrays verarbeitet werden. Die Arrayform spielt keine Rolle, nur die Länge der Sequenz. Dies ist sehr unwahrscheinlich , meiner Meinung nach ändern.
Sven Marnach

Das wusste ich nicht. Das macht mich viel wohler damit. Danke dir.
Josh Bleecher Snyder

@SvenMarnach: Ich habe unten eine Antwort gepostet. Können Sie kommentieren, ob es Ihrer Meinung nach sinnvoll ist / ein guter Weg ist, dies zu tun?
Ajfbiw.s

350

Sie können die Array-Indizierung von NumPy verwenden :

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

Dies führt zur Erstellung separater unisono gemischter Arrays.


13
Dies macht Kopien erstellen, wie es erweiterte Indizierung verwendet. Aber natürlich ist es schneller als das Original.
Sven Marnach

1
@mtrw: Die bloße Tatsache, dass die ursprünglichen Arrays unberührt bleiben, bedeutet nicht, dass die zurückgegebenen Arrays Ansichten derselben Daten sind. Dies ist jedoch nicht der Fall, da NumPy-Ansichten nicht flexibel genug sind, um permutierte Ansichten zu unterstützen (dies wäre auch nicht wünschenswert).
Sven Marnach

1
@Sven - Ich muss wirklich etwas über Ansichten lernen. @Dat Chu - Ich habe gerade versucht, >>> 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()38 Sekunden für die OP-Version und 27,5 Sekunden für meine Version für jeweils 1 Million Anrufe zu erhalten.
mtrw

3
Ich mag die Einfachheit und Lesbarkeit sehr, und die erweiterte Indizierung überrascht und überrascht mich immer wieder. dafür bekommt diese Antwort leicht +1. Seltsamerweise ist es bei meinen (großen) Datensätzen langsamer als meine ursprüngliche Funktion: Mein Original benötigt ~ 1,8 Sekunden für 10 Iterationen, und dies dauert ~ 2,7 Sekunden. Beide Zahlen sind ziemlich konsistent. Der Datensatz, den ich zum Testen verwendet habe, a.shapeist (31925, 405)und b.shapeist (31925,).
Josh Bleecher Snyder

1
Vielleicht hat die Langsamkeit damit zu tun, dass Sie die Dinge nicht an Ort und Stelle erledigen, sondern stattdessen neue Arrays erstellen. Oder mit einer gewissen Langsamkeit, die damit zusammenhängt, wie CPython Array-Indizes analysiert.
Mhor Mé

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

Weitere Informationen finden Sie unter http://scikit-learn.org/stable/modules/generated/sklearn.utils.shuffle.html


1
Diese Lösung erstellt Kopien ( "Die ursprünglichen Arrays sind nicht betroffen" ), während die "beängstigende" Lösung des Autors dies nicht tut.
Bartolo-Otrit

Sie können jeden Stil wählen, wie Sie möchten
James

31

Sehr einfache Lösung:

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

Die beiden Arrays x, y werden nun auf dieselbe Weise zufällig gemischt


5
Dies entspricht der Lösung von mtrw. Ihre ersten beiden Zeilen erzeugen nur eine Permutation, dies kann jedoch in einer Zeile erfolgen.
Josh Bleecher Snyder

18

James schrieb 2015 eine sklearn- Lösung, die hilfreich ist. Aber er fügte eine zufällige Zustandsvariable hinzu, die nicht benötigt wird. Im folgenden Code wird automatisch der Zufallszustand von numpy angenommen.

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)

15
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

Mische eine beliebige Anzahl von Arrays an Ort und Stelle mit nur 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)

Und kann so verwendet werden

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

Ein paar Dinge zu beachten:

  • Die Zusicherung stellt sicher, dass alle Eingabearrays in ihrer ersten Dimension dieselbe Länge haben.
  • Arrays wurden durch ihre erste Dimension an Ort und Stelle gemischt - nichts kehrte zurück.
  • Zufälliger Startwert im positiven int32-Bereich.
  • Wenn ein wiederholbares Mischen benötigt wird, kann der Startwert eingestellt werden.

Nach dem Mischen können die Daten np.splitje nach Anwendung mithilfe von Slices aufgeteilt oder mit Slices referenziert werden.


2
schöne Lösung, das hat perfekt für mich funktioniert. Sogar mit Arrays von 3+ Achsen
wprins

1
Dies ist die richtige Antwort. Es gibt keinen Grund, das globale np.random zu verwenden, wenn Sie zufällige Statusobjekte weitergeben können.
Erotemie

Man RandomStatekönnte außerhalb der Schleife verwendet werden. Siehe Adam Snaiders Antwort
bartolo-otrit

1
@ bartolo-otrit, die Wahl, die in der forSchleife getroffen werden muss, ist, ob der zufällige Zustand neu zugewiesen oder neu gesetzt werden soll. Da die Anzahl der Arrays, die an eine Mischfunktion übergeben werden, voraussichtlich gering ist, würde ich keinen Leistungsunterschied zwischen beiden erwarten. Aber ja, rstate könnte außerhalb der Schleife zugewiesen und bei jeder Iteration innerhalb der Schleife neu gesetzt werden.
Isaac B

9

Sie können ein Array erstellen wie:

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

dann mische es:

np.random.shuffle(s)

Verwenden Sie dies jetzt als Argument für Ihre Arrays. Gleiche gemischte Argumente geben dieselben gemischten Vektoren zurück.

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

Dies ist wirklich die beste Lösung und sollte die akzeptierte sein! Es funktioniert sogar für viele (mehr als 2) Arrays gleichzeitig. Die Idee ist einfach: Mischen Sie einfach die Indexliste [0, 1, 2, ..., n-1] und indizieren Sie dann die Zeilen der Arrays mit den gemischten Indizes neu. Nett!
Basj

5

Eine Möglichkeit, das direkte Mischen für verbundene Listen durchzuführen, besteht darin, einen Startwert (der zufällig sein kann) und numpy.random.shuffle zum Mischen zu verwenden.

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

Das ist es. Dadurch werden sowohl a als auch b auf genau dieselbe Weise gemischt. Dies geschieht auch vor Ort, was immer von Vorteil ist.

BEARBEITEN, verwenden Sie nicht np.random.seed (), sondern verwenden Sie stattdessen 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)

Wenn Sie es aufrufen, geben Sie einfach einen beliebigen Startwert ein, um den zufälligen Status zu füttern:

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

Ausgabe:

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

Bearbeiten: Code behoben, um den Zufallsstatus neu zu setzen


Dieser Code funktioniert nicht. RandomStateändert den Status beim ersten Anruf aund bwird nicht gleichzeitig gemischt.
Bruno Klein

@ BrunoKlein Du hast recht. Ich habe den Beitrag repariert, um den zufälligen Zustand neu zu bestimmen. Auch wenn es nicht in dem Sinne unisono ist, dass beide Listen gleichzeitig gemischt werden, sind sie in dem Sinne unisono, dass beide auf die gleiche Weise gemischt werden, und es erfordert auch nicht mehr Speicher, um a zu halten Kopie der Listen (die OP in seiner Frage erwähnt)
Adam Snaider

4

Es gibt eine bekannte Funktion, die damit umgehen kann:

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

Wenn Sie test_size auf 0 setzen, wird das Teilen vermieden und Sie erhalten gemischte Daten. Obwohl es normalerweise zum Teilen von Zug- und Testdaten verwendet wird, werden diese auch gemischt.
Aus der Dokumentation

Teilen Sie Arrays oder Matrizen in zufällige Zug- und Testteilmengen auf

Schnelles Dienstprogramm, das die Eingabevalidierung und die nächste (ShuffleSplit (). Split (X, y)) und Anwendung zum Eingeben von Daten in einen einzelnen Aufruf zum Aufteilen (und optional zum Unterabtasten) von Daten in einem Oneliner umschließt.


Ich kann nicht glauben, dass ich nie daran gedacht habe. Ihre Antwort ist brillant.
Long Nguyen

2

Angenommen, wir haben zwei Arrays: a und 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]]) 

Wir können zuerst Zeilenindizes erhalten, indem wir die erste Dimension permutieren

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

Verwenden Sie dann die erweiterte Indizierung. Hier verwenden wir dieselben Indizes, um beide Arrays gleichzeitig zu mischen.

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

Dies entspricht

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

Warum nicht einfach a [Indizes:] oder b [Indizes:]?
Kev

1

Wenn Sie das Kopieren von Arrays vermeiden möchten, würde ich vorschlagen, dass Sie anstelle einer Permutationsliste jedes Element im Array durchgehen und es zufällig an eine andere Position im Array tauschen

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]

Dies implementiert den Knuth-Fisher-Yates-Shuffle-Algorithmus.


3
Codinghorror.com/blog/2007/12/the-danger-of-naivete.html hat mich vorsichtig gemacht, meine eigenen Shuffle-Algorithmen zu implementieren. Es ist teilweise dafür verantwortlich, dass ich diese Frage stelle. :) Sie weisen jedoch zu Recht darauf hin, dass ich die Verwendung des Knuth-Fisher-Yates-Algorithmus in Betracht ziehen sollte.
Josh Bleecher Snyder

Gut entdeckt, ich habe den Code jetzt behoben. Wie auch immer, ich denke, die Grundidee des direkten Mischens ist auf eine beliebige Anzahl von Arrays skalierbar und vermeidet das Erstellen von Kopien.
DaveP

Der Code ist immer noch falsch (er wird nicht einmal ausgeführt). Ersetzen Sie es len(a)durch reversed(range(1, len(a))). Aber es wird sowieso nicht sehr effizient sein.
Sven Marnach

1

Dies scheint eine sehr einfache Lösung zu sein:

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

Mit einem Beispiel mache ich Folgendes:

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
Dies ist mehr oder weniger gleichbedeutend mit combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo)nur langsamer. Da Sie Numpy sowieso verwenden, besteht eine noch viel schnellere Lösung darin, die Arrays mit Numpy zu komprimieren combo = np.c_[images, labels], zu mischen und erneut zu entpacken images, labels = combo.T. Unter der Annahme , dass labelsund imagessind eindimensionale Numpy Arrays die gleiche Länge zu beginnen, wird dies die schnellste Lösung leicht sein. Wenn sie mehrdimensional sind, siehe meine Antwort oben.
Sven Marnach

Ok das macht Sinn. Vielen Dank! @SvenMarnach
ajfbiw.s

0

Ich habe Pythons random.shuffle () erweitert, um ein zweites Argument zu nehmen:

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]

Auf diese Weise kann ich sicher sein, dass das Mischen an Ort und Stelle erfolgt und die Funktion nicht allzu lang oder kompliziert ist.


0

Verwenden Sie einfach numpy...

Führen Sie zuerst die beiden Eingabearrays 1D-Array mit Beschriftungen (y) und 2D-Array mit Daten (x) zusammen und mischen Sie sie mit der NumPy- shuffleMethode. Schließlich teilen Sie sie und kehren Sie zurück.

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.