Meilleure façon de mélanger deux tableaux numpy à l'unisson


239

J'ai deux tableaux numpy de formes différentes, mais avec la même longueur (dimension principale). Je veux mélanger chacun d'eux, de sorte que les éléments correspondants continuent de correspondre - c'est-à-dire les mélanger à l'unisson par rapport à leurs principaux indices.

Ce code fonctionne et illustre mes objectifs:

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

Par exemple:

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

Cependant, cela semble maladroit, inefficace et lent, et cela nécessite de faire une copie des tableaux - je préfère les mélanger en place, car ils seront assez grands.

Y a-t-il une meilleure façon de procéder? Une exécution plus rapide et une utilisation réduite de la mémoire sont mes principaux objectifs, mais un code élégant serait également bien.

Une autre pensée que j'avais était la suivante:

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)

Cela fonctionne ... mais c'est un peu effrayant, car je ne vois aucune garantie que cela continuera à fonctionner - cela ne ressemble pas au genre de chose qui est garantie de survivre à travers la version numpy, par exemple.


10
Six ans plus tard, je suis amusé et surpris par la popularité de cette question. Et dans une petite coïncidence délicieuse, pour Go 1.10 j'ai contribué math / rand.Shuffle à la bibliothèque standard . La conception de l'API rend trivial le mélange de deux tableaux à l'unisson, et cela est même inclus comme exemple dans les documents.
Josh Bleecher Snyder

Réponses:


72

Votre solution "effrayante" ne me semble pas effrayante. L'appel shuffle()de deux séquences de même longueur entraîne le même nombre d'appels au générateur de nombres aléatoires, et ce sont les seuls éléments "aléatoires" de l'algorithme de lecture aléatoire. En réinitialisant l'état, vous vous assurez que les appels au générateur de nombres aléatoires donneront les mêmes résultats dans le deuxième appel à shuffle(), de sorte que l'ensemble de l'algorithme générera la même permutation.

Si vous n'aimez pas cela, une solution différente serait de stocker vos données dans un tableau au lieu de deux dès le début, et de créer deux vues dans ce tableau unique simulant les deux tableaux que vous avez maintenant. Vous pouvez utiliser le tableau unique pour le mélange et les vues à toutes autres fins.

Exemple: supposons les tableaux aet bressemblons à ceci:

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

Nous pouvons maintenant construire un seul tableau contenant toutes les données:

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

Nous créons maintenant des vues simulant l'original aet b:

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

Les données de a2et b2sont partagées avec c. Pour mélanger les deux tableaux simultanément, utilisez numpy.random.shuffle(c).

Dans le code de production, vous essayez bien sûr d'éviter de créer l'original aet de bcréer tout de suite c, a2et b2.

Cette solution pourrait être adaptée au cas aet bavoir des dtypes différents.


Re: la solution effrayante: je crains juste que des tableaux de formes différentes puissent (en théorie) produire différents nombres d'appels au rng, ce qui provoquerait des divergences. Cependant, je pense que vous avez raison, il est peu probable que le comportement actuel change, et un doctest très simple rend la confirmation du comportement correct très facile ...
Josh Bleecher Snyder

J'aime votre approche suggérée et je pourrais certainement m'arranger pour que a et b commencent leur vie en tant que tableau c unifié. Cependant, a et b devront être contigus peu de temps après le brassage (pour un transfert efficace vers un GPU), donc je pense que, dans mon cas particulier, je finirais par faire des copies de a et b de toute façon. :(
Josh Bleecher Snyder

@Josh: Notez que cela numpy.random.shuffle()fonctionne sur des séquences mutables arbitraires, telles que les listes Python ou les tableaux NumPy. La forme du tableau n'a pas d'importance, seule la longueur de la séquence. Il est très peu probable que cela change à mon avis.
Sven Marnach

Je ne le savais pas. Cela me rend beaucoup plus à l'aise avec ça. Je vous remercie.
Josh Bleecher Snyder

@SvenMarnach: J'ai posté une réponse ci-dessous. Pouvez-vous nous dire si vous pensez que cela a du sens / est un bon moyen de le faire?
ajfbiw.s

353

Vous pouvez utiliser NumPy's indexation des tableaux :

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

Cela se traduira par la création de tableaux séparés mélangés à l'unisson.


13
Cela fait créer des copies, car il utilise l' indexation avancée. Mais bien sûr, il est plus rapide que l'original.
Sven Marnach

1
@mtrw: le simple fait que les tableaux d'origine ne soient pas modifiés ne signifie pas que les tableaux renvoyés sont des vues des mêmes données. Mais ce n'est pas le cas, car les vues NumPy ne sont pas suffisamment flexibles pour prendre en charge les vues permutées (ce ne serait pas souhaitable non plus).
Sven Marnach

1
@Sven - Je dois vraiment me renseigner sur les vues. @Dat Chu - Je viens d'essayer >>> 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()et j'ai obtenu 38 secondes pour la version OP et 27,5 secondes pour la mienne, pour 1 million d'appels chacune.
mtrw

3
J'aime vraiment la simplicité et la lisibilité de cela, et l'indexation avancée continue de me surprendre et de m'étonner; pour cela, cette réponse obtient facilement +1. Curieusement, cependant, sur mes (grands) ensembles de données, il est plus lent que ma fonction d'origine: mon original prend ~ 1,8 s pour 10 itérations, et cela prend ~ 2,7 s. Les deux chiffres sont assez cohérents. L'ensemble de données que j'ai utilisé pour tester a.shapeest (31925, 405)et b.shapeest (31925,).
Josh Bleecher Snyder

1
Peut-être que la lenteur a à voir avec le fait que vous ne faites pas les choses sur place, mais que vous créez plutôt de nouveaux tableaux. Ou avec une lenteur liée à la façon dont CPython analyse les index de tableau.
Íhor Mé


33

Solution très simple:

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

les deux tableaux x, y sont maintenant tous les deux mélangés au hasard de la même manière


5
C'est équivalent à la solution de mtrw. Vos deux premières lignes ne font que générer une permutation, mais cela peut être fait sur une seule ligne.
Josh Bleecher Snyder

19

James a écrit en 2015 une solution sklearn qui est utile. Mais il a ajouté une variable d'état aléatoire, qui n'est pas nécessaire. Dans le code ci-dessous, l'état aléatoire de numpy est automatiquement supposé.

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

Mélangez n'importe quel nombre de tableaux ensemble, en place, en utilisant uniquement 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)

Et peut être utilisé comme ça

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

Quelques points à noter:

  • L'assertion garantit que tous les tableaux d'entrée ont la même longueur le long de leur première dimension.
  • Les tableaux sont mélangés en place par leur première dimension - rien ne revient.
  • Graine aléatoire dans la plage positive d'int32.
  • Si un shuffle répétable est nécessaire, la valeur de départ peut être définie.

Après la lecture aléatoire, les données peuvent être divisées en utilisant np.splitou référencées en utilisant des tranches - selon l'application.


2
belle solution, cela a fonctionné parfaitement pour moi. Même avec des tableaux de 3+ axes
wprins

1
Ceci est la bonne réponse. Il n'y a aucune raison d'utiliser le np.random global lorsque vous pouvez contourner des objets à état aléatoire.
Erotemic

On RandomStatepourrait être utilisé en dehors de la boucle. Voir la réponse d'
bartolo-otrit

1
@ bartolo-otrit, le choix qui doit être fait dans la forboucle est de réaffecter ou de réamorcer un état aléatoire. Avec le nombre de tableaux passés dans une fonction de lecture aléatoire qui devrait être petit, je ne m'attendrais pas à une différence de performances entre les deux. Mais oui, rstate pourrait être assigné en dehors de la boucle et réamorcé à l'intérieur de la boucle à chaque itération.
Isaac B

9

vous pouvez créer un tableau comme:

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

puis mélangez-le:

np.random.shuffle(s)

utilisez maintenant ce s comme argument de vos tableaux. les mêmes arguments mélangés renvoient les mêmes vecteurs mélangés.

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

Vraiment, c'est la meilleure solution, et devrait être la solution acceptée! Il fonctionne même pour plusieurs (plus de 2) baies en même temps. L'idée est simple: il suffit de mélanger la liste d'index [0, 1, 2, ..., n-1], puis de réindexer les lignes des tableaux avec les index mélangés. Agréable!
Basj

5

Une manière dont le brassage sur place peut être effectué pour les listes connectées consiste à utiliser une graine (elle peut être aléatoire) et à utiliser numpy.random.shuffle pour effectuer le brassage.

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

C'est tout. Cela va mélanger a et b exactement de la même manière. Cela se fait également sur place, ce qui est toujours un plus.

EDIT, n'utilisez pas np.random.seed () utilisez plutôt 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)

Lorsque vous l'appelez, passez simplement une graine pour nourrir l'état aléatoire:

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

Production:

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

Edit: code fixe pour redéfinir l'état aléatoire


Ce code ne fonctionne pas. RandomStatechange d'état au premier appel aet bn'est pas mélangé à l'unisson.
Bruno Klein

@BrunoKlein Vous avez raison. J'ai corrigé le message pour redéfinir l'état aléatoire. De plus, même s'il n'est pas à l'unisson dans le sens où les deux listes sont mélangées en même temps, elles sont à l'unisson dans le sens où les deux sont mélangées de la même manière, et cela ne nécessite pas davantage de mémoire pour contenir un copie des listes (que OP mentionne dans sa question)
Adam Snaider

4

Il existe une fonction bien connue qui peut gérer cela:

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

La simple définition de test_size sur 0 évitera le fractionnement et vous donnera des données mélangées. Bien qu'il soit généralement utilisé pour diviser les données de train et de test, il les mélange également.
De la documentation

Fractionner des tableaux ou des matrices en train aléatoire et sous-ensembles de test

Utilitaire rapide qui encapsule la validation d'entrée et ensuite (ShuffleSplit (). Split (X, y)) et application pour entrer des données en un seul appel pour fractionner (et éventuellement sous-échantillonner) les données dans un oneliner.


Je ne peux pas croire que je n'y ai jamais pensé. Votre réponse est brillante.
Long Nguyen

2

Disons que nous avons deux tableaux: a et 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]]) 

On peut d'abord obtenir des indices de lignes en permutant la première dimension

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

Utilisez ensuite l'indexation avancée. Ici, nous utilisons les mêmes indices pour mélanger les deux tableaux à l'unisson.

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

Cela équivaut à

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

Pourquoi pas seulement a [indices ,:] ou b [indices ,:]?
Kev

1

Si vous voulez éviter de copier des tableaux, je suggère qu'au lieu de générer une liste de permutation, vous parcouriez chaque élément du tableau et le permutiez au hasard à une autre position du tableau

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]

Cela implémente l'algorithme de shuffle Knuth-Fisher-Yates.


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.html m'a fait hésiter à implémenter mes propres algorithmes de lecture aléatoire; il est en partie responsable de ma question. :) Cependant, vous avez tout à fait raison de souligner que je devrais envisager d'utiliser l'algorithme Knuth-Fisher-Yates.
Josh Bleecher Snyder

Bien repéré, j'ai corrigé le code maintenant. Quoi qu'il en soit, je pense que l'idée de base du brassage sur place est évolutive à un nombre arbitraire de tableaux et évite de faire des copies.
DaveP

Le code est toujours incorrect (il ne fonctionnera même pas). Pour le faire fonctionner, remplacez len(a)par reversed(range(1, len(a))). Mais ce ne sera pas très efficace de toute façon.
Sven Marnach

1

Cela semble être une solution très simple:

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

Avec un exemple, voici ce que je fais:

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
C'est plus ou moins équivalent à combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), juste plus lent. Comme vous utilisez de toute façon Numpy, une solution encore plus rapide serait de compresser les tableaux en utilisant Numpy combo = np.c_[images, labels], de mélanger et de décompresser à nouveau images, labels = combo.T. En supposant que labelset imagessont unidimensionnels Les tableaux NumPy de la même longueur pour commencer, ce sera facilement la solution la plus rapide. S'ils sont multidimensionnels, voir ma réponse ci-dessus.
Sven Marnach

Ok, ça a du sens. Merci! @SvenMarnach
ajfbiw.s

0

J'ai étendu random.shuffle () de python pour prendre un deuxième argument:

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]

De cette façon, je peux être sûr que le brassage se produit sur place et que la fonction n'est pas trop longue ou compliquée.


0

Utilisez simplement numpy ...

Fusionnez d'abord les deux tableaux d'entrée. Le tableau 1D correspond aux étiquettes (y) et le tableau 2D correspond aux données (x) et les mélange avec la shuffleméthode NumPy . Enfin, divisez-les et revenez.

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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.