Cara yang lebih baik untuk mengocok dua array numpy bersamaan


239

Saya memiliki dua array numpy dengan bentuk yang berbeda, tetapi dengan panjang yang sama (dimensi terdepan). Saya ingin mengocok masing-masing, sehingga elemen yang sesuai terus berkorespondensi - yaitu mengocoknya bersamaan sehubungan dengan indeks utama mereka.

Kode ini berfungsi, dan menggambarkan tujuan saya:

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

Sebagai contoh:

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

Namun, ini terasa kikuk, tidak efisien, dan lambat, dan perlu membuat salinan array - saya lebih suka mengocoknya di tempat, karena mereka akan cukup besar.

Apakah ada cara yang lebih baik untuk melakukan ini? Eksekusi lebih cepat dan penggunaan memori yang lebih rendah adalah tujuan utama saya, tetapi kode yang elegan juga bagus.

Satu pemikiran lain yang saya miliki adalah ini:

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)

Ini berfungsi ... tapi ini sedikit menakutkan, karena saya melihat sedikit jaminan itu akan terus bekerja - itu tidak terlihat seperti hal yang dijamin untuk bertahan hidup di seluruh versi numpy, misalnya.


10
Enam tahun kemudian, saya terhibur dan kaget dengan betapa populernya pertanyaan ini terbukti. Dan dalam sedikit kebetulan yang menyenangkan, untuk Go 1.10 saya berkontribusi matematika / rand. Shuffle ke perpustakaan standar . Desain API membuatnya sepele untuk mengocok dua array secara bersamaan, dan melakukannya bahkan dimasukkan sebagai contoh dalam dokumen.
Josh Bleecher Snyder

Jawaban:


72

Solusi "menakutkan" Anda tidak tampak menakutkan bagi saya. Memanggil shuffle()dua urutan dengan panjang yang sama menghasilkan jumlah panggilan yang sama ke generator angka acak, dan ini adalah satu-satunya elemen "acak" dalam algoritma shuffle. Dengan mengatur ulang keadaan, Anda memastikan bahwa panggilan ke generator nomor acak akan memberikan hasil yang sama pada panggilan kedua shuffle(), sehingga seluruh algoritme akan menghasilkan permutasi yang sama.

Jika Anda tidak suka ini, solusi yang berbeda adalah menyimpan data Anda dalam satu array bukan dua sejak awal, dan membuat dua tampilan ke dalam array tunggal ini mensimulasikan dua array yang Anda miliki sekarang. Anda dapat menggunakan array tunggal untuk pengocokan dan tampilan untuk semua tujuan lain.

Contoh: Mari kita asumsikan array adan bterlihat seperti ini:

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

Kami sekarang dapat membuat satu array yang berisi semua data:

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

Sekarang kami membuat tampilan yang mensimulasikan yang asli adan b:

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

Data a2dan b2dibagikan dengan c. Untuk mengocok kedua array secara bersamaan, gunakan numpy.random.shuffle(c).

Dalam kode produksi, Anda tentu saja akan mencoba untuk menghindari membuat yang asli adan bsama sekali dan segera membuat c, a2dan b2.

Solusi ini dapat disesuaikan dengan kasus itu adan bmemiliki dtypes yang berbeda.


Solusi menakutkan: Saya hanya khawatir bahwa array bentuk yang berbeda dapat (dibayangkan) menghasilkan jumlah panggilan yang berbeda ke rng, yang akan menyebabkan perbedaan. Namun, saya pikir Anda benar bahwa perilaku saat ini mungkin tidak akan berubah, dan doctest yang sangat sederhana membuat konfirmasi perilaku yang benar menjadi sangat mudah ...
Josh Bleecher Snyder

Saya suka pendekatan yang disarankan Anda, dan pasti bisa mengatur untuk memiliki dan memulai hidup sebagai c array terpadu. Namun, a dan b perlu bersebelahan segera setelah pengocokan (untuk transfer efisien ke GPU), jadi saya pikir, dalam kasus khusus saya, saya akhirnya akan membuat salinan a dan b. :(
Josh Bleecher Snyder

@Josh: Catatan yang numpy.random.shuffle()beroperasi pada urutan yang bisa berubah-ubah, seperti daftar Python atau array NumPy. Bentuk array tidak masalah, hanya panjang urutannya. Ini sangat tidak mungkin berubah menurut pendapat saya.
Sven Marnach

Saya tidak tahu itu. Itu membuat saya jauh lebih nyaman dengan itu. Terima kasih.
Josh Bleecher Snyder

@SvenMarnach: Saya mengirim jawaban di bawah ini. Bisakah Anda mengomentari apakah menurut Anda itu masuk akal / adalah cara yang baik untuk melakukannya?
ajfbiw.s

352

Anda dapat menggunakan pengindeksan array NumPy :

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

Ini akan menghasilkan penciptaan array yang dikocok secara terpisah.


13
Ini tidak membuat salinan, karena menggunakan pengindeksan maju. Tapi tentu saja lebih cepat dari aslinya.
Sven Marnach

1
@ mtrw: Fakta bahwa array asli tidak tersentuh tidak membatalkan bahwa array yang dikembalikan adalah pandangan dari data yang sama. Tetapi mereka memang tidak, karena pandangan NumPy tidak cukup fleksibel untuk mendukung pandangan yang diijinkan (ini juga tidak diinginkan).
Sven Marnach

1
@Ven - Saya benar-benar harus belajar tentang pandangan. @Dat Chu - Saya baru saja mencoba >>> 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()dan mendapatkan 38 detik untuk versi OP, dan 27,5 detik untuk saya, masing-masing untuk 1 juta panggilan.
mtrw

3
Saya sangat menyukai kesederhanaan dan keterbacaan ini, dan pengindeksan lanjut terus mengejutkan dan membuat saya takjub; untuk itu jawaban ini dengan mudah mendapat +1. Anehnya, pada dataset saya (besar), ini lebih lambat dari fungsi asli saya: dokumen asli saya mengambil ~ 1.8s untuk 10 iterasi, dan ini membutuhkan ~ 2.7s. Kedua angka itu cukup konsisten. Dataset yang saya gunakan untuk menguji a.shapeadalah (31925, 405)dan b.shapesekarang (31925,).
Josh Bleecher Snyder

1
Mungkin, kelambatan ini ada hubungannya dengan fakta bahwa Anda tidak melakukan hal-hal di tempat, tetapi malah menciptakan array baru. Atau dengan beberapa kelambatan terkait dengan bagaimana CPython mem-parsing indeks-array.
Íhor Mé


33

Solusi yang sangat sederhana:

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

kedua array x, y sekarang keduanya diacak secara acak dengan cara yang sama


5
Ini setara dengan solusi mtrw. Dua baris pertama Anda hanya menghasilkan permutasi, tetapi itu bisa dilakukan dalam satu baris.
Josh Bleecher Snyder

19

James menulis pada tahun 2015 solusi sklearn yang sangat membantu. Namun dia menambahkan variabel keadaan acak, yang tidak diperlukan. Dalam kode di bawah ini, keadaan acak dari numpy diasumsikan secara otomatis.

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

Kocok sejumlah array secara bersamaan, di tempat, hanya menggunakan 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)

Dan bisa digunakan seperti ini

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

Beberapa hal yang perlu diperhatikan:

  • Pernyataan tersebut memastikan bahwa semua array input memiliki panjang yang sama di sepanjang dimensi pertama mereka.
  • Array dikocok di tempat oleh dimensi pertama mereka - tidak ada yang kembali.
  • Benih acak dalam kisaran int32 positif.
  • Jika diperlukan pengocokan acak, nilai seed dapat diatur.

Setelah acak, data dapat dipisah menggunakan np.splitatau direferensikan menggunakan irisan - tergantung pada aplikasi.


2
solusi yang indah, ini bekerja sempurna untuk saya. Bahkan dengan array 3+ sumbu
wprins

1
Ini jawaban yang benar. Tidak ada alasan untuk menggunakan global np.random ketika Anda bisa membagikan objek keadaan acak.
Erotemik

Satu RandomStatedapat digunakan di luar loop. Lihat jawaban
bartolo-otrit

1
@ Bartolo-Otrit, pilihan yang harus dibuat dalam forloop adalah apakah akan menetapkan kembali atau memulai kembali keadaan acak. Dengan jumlah array yang diteruskan ke fungsi pengocokan diharapkan kecil, saya tidak akan mengharapkan perbedaan kinerja antara keduanya. Tapi ya, rstate dapat ditugaskan di luar loop dan di-reseed di dalam loop pada setiap iterasi.
Isaac B

9

Anda dapat membuat array seperti:

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

lalu kocok:

np.random.shuffle(s)

sekarang gunakan ini sebagai argumen dari array Anda. argumen dikocok yang sama mengembalikan vektor dikocok yang sama.

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

Sungguh, ini adalah solusi terbaik, dan harus menjadi yang diterima! Ia bahkan bekerja untuk banyak (lebih dari 2) array pada saat yang bersamaan. Idenya sederhana: cukup kocok daftar indeks [0, 1, 2, ..., n-1], dan kemudian masukkan kembali baris array dengan indeks yang diacak. Bagus!
Basj

5

Salah satu cara di mana pengocokan di tempat dapat dilakukan untuk daftar terhubung adalah menggunakan seed (itu bisa acak) dan menggunakan numpy.random.shuffle untuk melakukan pengocokan.

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

Itu dia. Ini akan mengocok a dan b dengan cara yang sama persis. Ini juga dilakukan di tempat yang selalu merupakan nilai tambah.

Sunting, jangan gunakan np.random.seed () gunakan np.random.RandomState sebagai gantinya

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

Saat memanggilnya, berikan saja biji apa saja untuk memberi makan kondisi acak:

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

Keluaran:

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

Sunting: Memperbaiki kode untuk menabur kembali keadaan acak


Kode ini tidak berfungsi. RandomStateperubahan status pada panggilan pertama dan adan btidak dikocok bersamaan.
Bruno Klein

@ BrunoKlein Anda benar. Saya memperbaiki postingan untuk mengunggah kembali keadaan acak. Juga, meskipun tidak serempak dalam arti kedua daftar dikocok secara bersamaan, mereka serentak dalam arti bahwa keduanya dikocok dengan cara yang sama, dan juga tidak memerlukan lebih banyak memori untuk menyimpan salinan daftar (yang OP sebutkan dalam pertanyaannya)
Adam Snaider

4

Ada fungsi terkenal yang bisa menangani ini:

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

Hanya dengan menetapkan test_size ke 0 akan menghindari pemisahan dan memberikan Anda data acak. Meskipun biasanya digunakan untuk membagi data kereta dan menguji, itu mengocoknya juga.
Dari dokumentasi

Pisahkan susunan atau matriks menjadi rangkaian acak kereta dan uji

Utilitas cepat yang membungkus validasi input dan selanjutnya (ShuffleSplit (). Split (X, y)) dan aplikasi untuk memasukkan data ke dalam satu panggilan untuk memisahkan (dan secara opsional melakukan subsampling) data dalam oneliner.


Saya tidak percaya saya tidak pernah memikirkan hal ini. Jawaban Anda brilian.
Long Nguyen

2

Katakanlah kita memiliki dua array: a dan 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]]) 

Pertama-tama kita dapat memperoleh indeks baris dengan mengijinkan permutasi dimensi pertama

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

Kemudian gunakan pengindeksan lanjutan. Di sini kita menggunakan indeks yang sama untuk mengocok kedua array secara bersamaan.

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

Ini setara dengan

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

Mengapa tidak hanya [indeks ,:] atau b [indeks ,:]?
Kev

1

Jika Anda ingin menghindari menyalin array, maka saya akan menyarankan bahwa alih-alih menghasilkan daftar permutasi, Anda pergi melalui setiap elemen dalam array, dan menukar secara acak ke posisi lain dalam 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]

Ini mengimplementasikan algoritma shuffle Knuth-Fisher-Yates.


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.html telah membuat saya berhati-hati dalam mengimplementasikan algoritma shuffle saya sendiri; sebagian bertanggung jawab atas saya menanyakan pertanyaan ini. :) Namun, Anda sangat tepat untuk menunjukkan bahwa saya harus mempertimbangkan untuk menggunakan algoritma Knuth-Fisher-Yates.
Josh Bleecher Snyder

Terlihat dengan baik, saya sudah memperbaiki kodenya sekarang. Ngomong-ngomong, saya pikir ide dasar pengocokan di tempat adalah scalable ke sejumlah array sewenang-wenang dan menghindari membuat salinan.
DaveP

Kode masih salah (bahkan tidak akan berjalan). Untuk membuatnya berfungsi, ganti len(a)dengan reversed(range(1, len(a))). Tapi toh itu tidak akan sangat efisien.
Sven Marnach

1

Ini sepertinya solusi yang sangat sederhana:

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

Dengan sebuah contoh, inilah yang saya lakukan:

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
Ini kurang lebih setara dengan combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), hanya lebih lambat. Karena Anda tetap menggunakan Numpy, solusi yang jauh lebih cepat adalah combo = np.c_[images, labels]meng -zip array menggunakan Numpy , shuffle, dan unzip lagi images, labels = combo.T. Dengan asumsi bahwa labelsdan imagesmerupakan array Numpy satu dimensi dengan panjang yang sama untuk memulai, ini akan dengan mudah solusi tercepat. Jika mereka multidimensi, lihat jawaban saya di atas.
Sven Marnach

Ok itu masuk akal. Terima kasih! @SvenMarnach
ajfbiw.s

0

Saya menambah python secara acak.shuffle () untuk mengambil argumen kedua:

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]

Dengan begitu saya bisa yakin bahwa pengocokan terjadi di tempat, dan fungsinya tidak terlalu panjang atau rumit.


0

Cukup gunakan numpy...

Pertama menggabungkan dua array input array 1D adalah label (y) dan array 2D adalah data (x) dan mengocoknya dengan shufflemetode NumPy . Akhirnya pisahkan mereka dan kembali.

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)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.