Mejor manera de barajar dos matrices numpy al unísono


239

Tengo dos matrices numpy de diferentes formas, pero con la misma longitud (dimensión principal). Quiero barajar cada uno de ellos, de modo que los elementos correspondientes sigan correspondiendo, es decir, barajarlos al unísono con respecto a sus índices principales.

Este código funciona e ilustra mis 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 ejemplo:

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

Sin embargo, esto se siente torpe, ineficiente y lento, y requiere hacer una copia de los arreglos; prefiero mezclarlos en su lugar, ya que serán bastante grandes.

¿Hay una mejor manera de hacerlo? Mis objetivos principales son una ejecución más rápida y un menor uso de memoria, pero un código elegante también sería bueno.

Otro pensamiento que tuve fue este:

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)

Esto funciona ... pero da un poco de miedo, ya que veo pocas garantías de que continuará funcionando; no parece el tipo de cosa que se garantiza que sobrevivirá a través de la versión numpy, por ejemplo.


10
Seis años después, estoy divertido y sorprendido por lo popular que resultó ser esta pregunta. Y en una pequeña coincidencia encantadora, para Go 1.10 contribuí con matemática / rand. Mezcle a la biblioteca estándar . El diseño de la API hace que sea trivial mezclar dos matrices al unísono, e incluso se incluye como ejemplo en los documentos.
Josh Bleecher Snyder

Respuestas:


72

Su solución "aterradora" no me parece aterradora. Llamar shuffle()a dos secuencias de la misma longitud da como resultado la misma cantidad de llamadas al generador de números aleatorios, y estos son los únicos elementos "aleatorios" en el algoritmo aleatorio. Al restablecer el estado, se asegura de que las llamadas al generador de números aleatorios darán los mismos resultados en la segunda llamada a shuffle(), por lo que todo el algoritmo generará la misma permutación.

Si no le gusta esto, una solución diferente sería almacenar sus datos en una matriz en lugar de dos desde el principio, y crear dos vistas en esta matriz única simulando las dos matrices que tiene ahora. Puede usar la matriz única para barajar y las vistas para todos los demás fines.

Ejemplo: asumamos las matrices ay btengamos este aspecto:

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

Ahora podemos construir una matriz única que contenga todos los datos:

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

Ahora creamos vistas simulando el original ay b:

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

Los datos de a2y b2se comparten con c. Para barajar ambas matrices simultáneamente, use numpy.random.shuffle(c).

En el código de producción, por supuesto, tratará de evitar crear el original ay bcrearlo de inmediato c, a2y b2.

Esta solución podría adaptarse al caso ay btener diferentes tipos.


Re: la solución aterradora: solo me preocupa que las matrices de diferentes formas puedan (posiblemente) producir diferentes números de llamadas al rng, lo que causaría divergencia. Sin embargo, creo que tienes razón en que es poco probable que cambie el comportamiento actual, y un doctest muy simple hace que confirmar el comportamiento correcto sea muy fácil ...
Josh Bleecher Snyder

Me gusta su enfoque sugerido, y definitivamente podría hacer arreglos para que ayb comiencen la vida como una matriz c unificada. Sin embargo, ayb tendrán que ser contiguos poco después de barajar (para una transferencia eficiente a una GPU), por lo que creo que, en mi caso particular, terminaría haciendo copias de ayb de todos modos. :(
Josh Bleecher Snyder

@ Josh: Tenga en cuenta que numpy.random.shuffle()opera en secuencias mutables arbitrarias, como listas de Python o matrices NumPy. La forma de la matriz no importa, solo la longitud de la secuencia. Es muy poco probable que esto cambie en mi opinión.
Sven Marnach

No lo sabia. Eso me hace mucho más cómodo con eso. Gracias.
Josh Bleecher Snyder

@SvenMarnach: publiqué una respuesta a continuación. ¿Puedes comentar si crees que tiene sentido / es una buena manera de hacerlo?
ajfbiw.s

352

Puede usar la indexación de matriz de NumPy :

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

Esto dará como resultado la creación de matrices separadas mezcladas al unísono.


13
Esto hace crear copias, ya que utiliza la indexación avanzada. Pero, por supuesto, es más rápido que el original.
Sven Marnach

1
@mtrw: El mero hecho de que las matrices originales no se hayan tocado no supera que las matrices devueltas sean vistas de los mismos datos. Pero en realidad no lo son, ya que las vistas NumPy no son lo suficientemente flexibles como para admitir vistas permutadas (esto tampoco sería deseable).
Sven Marnach

1
@Sven - Realmente tengo que aprender sobre las vistas. @Dat Chu: acabo de probar >>> 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()y obtuve 38 segundos para la versión del OP, y 27.5 segundos para la mía, para 1 millón de llamadas cada uno.
mtrw

3
Realmente me gusta la simplicidad y la legibilidad de esto, y la indexación avanzada continúa sorprendiéndome y asombrándome; para eso esta respuesta obtiene fácilmente +1. Sin embargo, por extraño que parezca, en mis conjuntos de datos (grandes), es más lento que mi función original: mi original toma ~ 1.8s por 10 iteraciones, y esto toma ~ 2.7s. Ambos números son bastante consistentes. El conjunto de datos que solía probar tiene a.shapees (31925, 405)y b.shapees (31925,).
Josh Bleecher Snyder

1
Tal vez, la lentitud tiene que ver con el hecho de que no estás haciendo las cosas en el lugar, sino que estás creando nuevas matrices. O con cierta lentitud relacionada con la forma en que CPython analiza los índices de matriz.
Íhor Mé


33

Solución muy simple:

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

las dos matrices x, y ahora se mezclan aleatoriamente de la misma manera


55
Esto es equivalente a la solución de mtrw. Sus primeras dos líneas solo están generando una permutación, pero eso se puede hacer en una sola línea.
Josh Bleecher Snyder

19

James escribió en 2015 una solución sklearn que es útil. Pero agregó una variable de estado aleatorio, que no es necesaria. En el siguiente código, se supone automáticamente el estado aleatorio de 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)

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

Mezcle cualquier número de matrices juntas, en el lugar, 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)

Y se puede usar así

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

Algunas cosas a tener en cuenta:

  • La afirmación asegura que todas las matrices de entrada tengan la misma longitud a lo largo de su primera dimensión.
  • Las matrices se barajaron en el lugar por su primera dimensión, nada regresó.
  • Semilla aleatoria dentro del rango positivo int32.
  • Si se necesita una combinación aleatoria repetible, se puede establecer el valor de inicialización.

Después de la combinación aleatoria, los datos se pueden dividir utilizando np.splito referenciados mediante sectores, según la aplicación.


2
Hermosa solución, esto funcionó perfecto para mí. Incluso con matrices de 3+ ejes
wprins

1
Esta es la respuesta correcta. No hay ninguna razón para usar el np.random global cuando puede pasar objetos de estado aleatorio.
Erotémico

Uno RandomStatepodría usarse fuera del bucle. Ver la respuesta de
bartolo-otrit

1
@ bartolo-otrit, la elección que se debe hacer en el forbucle es si reasignar o reiniciar el estado aleatorio. Dado que se espera que el número de matrices que se pasan a una función aleatoria sea pequeño, no esperaría una diferencia de rendimiento entre las dos. Pero sí, rstate podría asignarse fuera del ciclo y reiniciarse dentro del ciclo en cada iteración.
Isaac B

9

puedes hacer una matriz como:

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

luego barajarlo:

np.random.shuffle(s)

ahora use esto como argumento de sus matrices. los mismos argumentos barajados devuelven los mismos vectores barajados.

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

Realmente, esta es la mejor solución, ¡y debería ser la aceptada! Incluso funciona para muchas (más de 2) matrices al mismo tiempo. La idea es simple: simplemente baraje la lista de índice [0, 1, 2, ..., n-1], y luego vuelva a indexar las filas de las matrices con los índices barajados. ¡Agradable!
Basj

5

Una forma de barajar en el lugar para listas conectadas es usar una semilla (podría ser aleatoria) y usar numpy.random.shuffle para barajar.

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

Eso es. Esto barajará tanto a como b exactamente de la misma manera. Esto también se hace en el lugar, lo que siempre es una ventaja.

EDITAR, no use np.random.seed () use np.random.RandomState en su lugar

def shuffle(a, b, seed):
   rand_state = np.random.RandomState(seed)
   rand_state.shuffle(a)
   rand_state.seed(seed)
   rand_state.shuffle(b)

Cuando lo llame, simplemente pase cualquier semilla para alimentar el estado aleatorio:

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

Salida:

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

Editar: código fijo para volver a sembrar el estado aleatorio


Este código no funciona RandomStatecambia de estado en la primera llamada y ay bno se barajan al unísono.
Bruno Klein

@BrunoKlein Tienes razón. Arreglé la publicación para volver a sembrar el estado aleatorio. Además, aunque no está al unísono en el sentido de que ambas listas se barajan al mismo tiempo, están al unísono en el sentido de que ambas se barajan de la misma manera, y tampoco requiere más memoria para contener un copia de las listas (que OP menciona en su pregunta)
Adam Snaider

4

Hay una función bien conocida que puede manejar esto:

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

Solo establecer test_size en 0 evitará la división y le dará datos barajados. Aunque generalmente se usa para dividir el tren y probar datos, también los baraja.
De la documentación

Divide matrices o matrices en trenes aleatorios y subconjuntos de prueba

Utilidad rápida que envuelve la validación de entrada y la siguiente (ShuffleSplit (). Split (X, y)) y la aplicación para ingresar datos en una sola llamada para dividir (y opcionalmente submuestrear) datos en una línea.


No puedo creer que nunca haya pensado en esto. Tu respuesta es brillante.
Long Nguyen

2

Digamos que tenemos dos matrices: a y 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]]) 

Primero podemos obtener índices de fila permutando la primera dimensión

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

Luego use indexación avanzada. Aquí estamos usando los mismos índices para mezclar ambas matrices al unísono.

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

Esto es 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 qué no solo a [índices ,:] o b [índices ,:]?
Kev

1

Si desea evitar copiar matrices, le sugiero que en lugar de generar una lista de permutación, revise todos los elementos de la matriz y lo cambie aleatoriamente a otra posición en la 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]

Esto implementa el algoritmo aleatorio Knuth-Fisher-Yates.


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.html me ha hecho desconfiar de implementar mis propios algoritmos aleatorios; es en parte responsable de que yo haga esta pregunta. :) Sin embargo, tienes razón al señalar que debería considerar el uso del algoritmo Knuth-Fisher-Yates.
Josh Bleecher Snyder

Bien visto, he arreglado el código ahora. De todos modos, creo que la idea básica de barajar en el lugar es escalable a un número arbitrario de matrices y evita hacer copias.
DaveP

El código sigue siendo incorrecto (ni siquiera se ejecutará). Para que funcione, reemplace len(a)por reversed(range(1, len(a))). Pero no será muy eficiente de todos modos.
Sven Marnach

1

Esto parece una solución muy 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

Con un ejemplo, esto es lo que estoy haciendo:

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
Esto es más o menos equivalente a combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), solo que más lento. Como está utilizando Numpy de todos modos, una solución mucho más rápida sería comprimir las matrices usando Numpy combo = np.c_[images, labels], barajar y descomprimir nuevamente images, labels = combo.T. Asumiendo que labelsy imagesson matrices unidimensionales de Numpy de la misma longitud para empezar, esta será fácilmente la solución más rápida. Si son multidimensionales, vea mi respuesta más arriba.
Sven Marnach

Ok, eso tiene sentido. ¡Gracias! @SvenMarnach
ajfbiw.s

0

Extendí random.shuffle () de python para tomar un 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]

De esa manera, puedo estar seguro de que la mezcla se realiza en el lugar y que la función no es demasiado larga o complicada.


0

Solo usa numpy ...

Primero combine las dos matrices de entrada. La matriz 1D son etiquetas (y) y la matriz 2D son datos (x) y las baraja con el shufflemétodo NumPy . Finalmente sepárelos y regrese.

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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.