İki numpy dizisini birlikte karıştırmanın daha iyi yolu


239

Farklı şekillerde, ancak aynı uzunlukta (önde gelen boyut) iki numpy dizim var. Her birini karıştırmak istiyorum, böylece karşılık gelen elemanlar karşılık gelmeye devam ediyor - yani önde gelen endekslerine göre bunları birlikte karıştırın.

Bu kod işe yarar ve hedeflerimi gösterir:

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

Örneğin:

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

Bununla birlikte, bu tıknaz, verimsiz ve yavaş hissediyor ve dizilerin bir kopyasını almayı gerektiriyor - oldukça büyük olacakları için onları yerinde karıştırmayı tercih ederim.

Bununla ilgili daha iyi bir yol var mı? Daha hızlı yürütme ve daha az bellek kullanımı birincil hedeflerimdir, ancak zarif kod da iyi olurdu.

Başka bir düşünce vardı:

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)

Bu işe yarıyor ... ama biraz korkutucu, çalışmaya devam edeceğine dair çok az garanti görüyorum - örneğin, numpy sürümünde hayatta kalmayı garanti eden bir şey gibi görünmüyor.


9
Altı yıl sonra, bu sorunun ne kadar popüler olduğunu merak ettim ve şaşırdım. Ve biraz keyifli bir tesadüfle, Go 1.10 için matematik / rand'a katkıda bulundum.Standart kütüphaneye karıştırın . API'nın tasarımı, iki diziyi birlikte karıştırmayı önemsiz kılar ve bunu dokümanlardaki örnek olarak bile içerir.
Josh Bleecher Snyder

Yanıtlar:


72

"Korkunç" çözümün bana korkutucu gelmiyor. shuffle()Aynı uzunlukta iki dizinin çağrılması , rasgele sayı üretecine aynı sayıda çağrı ile sonuçlanır ve bunlar, karıştırma algoritmasındaki tek "rastgele" öğelerdir. Durumu sıfırlayarak, rasgele sayı üretecine yapılan çağrıların ikinci çağrıda aynı sonuçları vermesini sağlarsınız shuffle(), böylece tüm algoritma aynı permütasyonu üretecektir.

Bunu beğenmezseniz, verilerinizi en başından iki yerine bir dizide depolamak ve şu anda sahip olduğunuz iki diziyi simüle eden bu tek dizide iki görünüm oluşturmak farklı bir çözüm olacaktır. Karıştırma için tek diziyi ve diğer tüm amaçlar için görünümleri kullanabilirsiniz.

Örnek: Biraz sert geçebilir diziler üstlenecek ave bbunun gibi bir görünüm:

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

Artık tüm verileri içeren tek bir dizi oluşturabiliriz:

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

Şimdi orijinali simüle eden görünümler oluşturuyoruz ave b:

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

Verileri a2ve b2ile paylaşılıyor c. Her iki diziyi aynı anda karıştırmak için kullanın numpy.random.shuffle(c).

Üretim kodunda, elbette orijinalini yaratmaktan kaçınmak için çalışacağını söyledi ave boluşturmak hemen hiç ve c, a2ve b2.

Bu çözelti halinde adapte olabilir ave bfarklı dtypes sahiptir.


Re: korkutucu çözüm: Sadece farklı şekillerdeki dizilerin farklılaşmaya neden olacak şekilde rng'e farklı sayıda çağrı yapabileceğinden endişeleniyorum. Ancak, mevcut davranışın belki de değişme olasılığının doğru olduğunu düşünüyorum ve çok basit bir doctest doğru davranışı doğrulamayı çok kolay hale getiriyor ...
Josh Bleecher Snyder

Önerilen yaklaşımınızı beğeniyorum ve kesinlikle bir ve c başlangıç ​​yaşamını birleşik bir c dizisi olarak ayarlayabilirim. Bununla birlikte, a ve b'nin karıştırmadan kısa bir süre sonra (GPU'ya verimli aktarım için) bitişik olması gerekecektir, bu yüzden özel durumumda yine de a ve b'nin kopyalarını oluşturacağım. :(
Josh Bleecher Snyder

@Josh: numpy.random.shuffle()Python listeleri veya NumPy dizileri gibi isteğe bağlı değiştirilebilir dizilerde çalışan not . Dizi şekli önemli değil, sadece dizinin uzunluğu. Bu benim görüşüme göre değişme olasılığı çok düşük.
Sven Marnach

Bunu bilmiyordum. Bu beni çok daha rahat ettiriyor. Teşekkür ederim.
Josh Bleecher Snyder

@SvenMarnach: Aşağıda bir cevap gönderdim. Bunun mantıklı / iyi bir yol olup olmadığını düşünebilir misiniz?
ajfbiw.s

351

NumPy'nin dizi indekslemesini kullanabilirsiniz :

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

Bu, ayrı bir tekli karışık dizilerin oluşturulmasına neden olacaktır.


13
Bu , gelişmiş dizinleme kullandığından kopyalar oluşturur. Ama tabii ki orijinalinden daha hızlı.
Sven Marnach

1
@mtrw: Yalnızca orijinal dizilere dokunulmadığı gerçeği, döndürülen dizilerin aynı verilerin görünümleri olduğunu geçersiz kılmaz. Ancak, bunlar aslında değildir, çünkü NumPy görünümleri izin verilen görünümleri destekleyecek kadar esnek değildir (bu da arzu edilmez).
Sven Marnach

1
@Sven - Gerçekten görüşler hakkında bilgi edinmek zorundayım. @Dat Chu - >>> 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()OP sürümü için yeni denedim ve 38 saniye, her biri için 1 milyon çağrıda 27.5 saniye aldım.
mtrw

3
Bunun basitliğini ve okunabilirliğini gerçekten çok seviyorum ve gelişmiş dizinleme beni şaşırtmaya ve şaşırtmaya devam ediyor; bunun için bu cevap kolayca +1 alır. Garip bir şekilde, (büyük) veri kümelerimde, orijinal işlevimden daha yavaş: orijinalim 10 yineleme için ~ 1.8s alır ve bu ~ 2.7s alır. Her iki sayı da oldukça tutarlıdır. Test için kullanılan veri kümesi I gelmiştir a.shapeolduğunu (31925, 405)ve b.shapebir (31925,).
Josh Bleecher Snyder

1
Belki de yavaşlık, bir şeyleri yerinde yapmamanız, bunun yerine yeni diziler yaratmanızla ilgilidir. Veya CPython'un dizi dizinlerini nasıl ayrıştırdığıyla ilgili bir miktar yavaşlıkla.
Méhor Mé


33

Çok basit bir çözüm:

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

iki x, y dizisi şimdi aynı şekilde rastgele karıştırılır


5
Bu mtrw'nin çözümüne eşdeğerdir. İlk iki hattınız sadece bir permütasyon üretiyor, ancak bu bir satırda yapılabilir.
Josh Bleecher Snyder

19

James, 2015 yılında faydalı bir sklearn çözümü yazdı . Ancak, gerekli olmayan rastgele bir durum değişkeni ekledi. Aşağıdaki kodda, numpy'den rastgele durum otomatik olarak kabul edilir.

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

Yalnızca NumPy kullanarak istediğiniz sayıda diziyi yerinde karıştırın.

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)

Ve böyle kullanılabilir

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

Unutulmaması gereken birkaç nokta:

  • Bu iddia tüm girdi dizilerinin ilk boyutları boyunca aynı uzunlukta olmasını sağlar.
  • Diziler ilk boyutlarına göre yerinde karıştırıldı - hiçbir şey geri gelmedi.
  • Pozitif int32 aralığında rastgele tohum.
  • Tekrarlanabilir bir karıştırmaya ihtiyaç duyulursa, tohum değeri ayarlanabilir.

Karıştırma işleminden sonra veriler np.split, uygulamaya bağlı olarak dilimler kullanılarak bölünebilir veya dilimler kullanılarak başvurulabilir.


2
güzel çözüm, bu benim için mükemmel çalıştı. 3+ eksenli dizilerle bile
wprins

1
Bu doğru cevap. Rasgele durum nesnelerini dolaşabildiğinizde global np.random'u kullanmanız için bir neden yoktur.
Erotemic

Biri RandomStatedöngü dışında kullanılabilir. Adam Snaider'ın cevabı
bartolo-otrit

1
@ bartolo-otrit, fordöngüde yapılması gereken seçim rastgele durumun yeniden atanması veya yeniden atanmasıdır. Dizilerin sayısının küçük olması beklenen bir karıştırma işlevine geçirilmesiyle, ikisi arasında bir performans farkı beklemem. Ancak evet, rstate döngü dışında atanabilir ve her yinelemede döngü içinde yeniden kullanılabilir.
Isaac B

9

şöyle bir dizi oluşturabilirsiniz:

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

sonra karıştırın:

np.random.shuffle(s)

şimdi bu dizileri dizilerinizin argümanı olarak kullanın. aynı karıştırılmış argümanlar aynı karıştırılmış vektörleri döndürür.

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

Gerçekten, bu en iyi çözüm ve kabul edilen çözüm olmalı! Aynı anda birçok (2'den fazla) dizi için bile çalışır. Fikir basit: dizin listesini [0, 1, 2, ..., n-1] karıştırıp dizilerin satırlarını karıştırılmış dizinlerle yeniden dizine ekleyin. Güzel!
Basj

5

Bağlı listeler için yerinde karıştırmanın bir yolu, bir tohum kullanmak (rastgele olabilir) ve karıştırmayı yapmak için numpy.random.shuffle kullanmaktır.

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

Bu kadar. Bu hem a hem de b'yi aynı şekilde karıştırır. Bu da her zaman bir artı olan yerinde yapılır.

DÜZENLE, np.random.seed () kullanmayın, bunun yerine np.random.RandomState kullanın

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

Arama yaparken rastgele durumu beslemek için herhangi bir tohumdan geçin:

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

Çıktı:

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

Düzenleme: Rastgele durumu yeniden tohumlamak için sabit kod


Bu kod çalışmıyor. RandomStateilk çağrıda durumu değiştirir ave bbirlikte karıştırılmaz.
Bruno Klein

@BrunoKlein Haklısın. Direği rastgele durumu yeniden tohumlamak için sabitledim. Ayrıca, her iki listenin aynı anda karıştırılması anlamında uyum içinde olmasa da, her ikisinin de aynı şekilde karıştırılması anlamında uyum içinde ve aynı zamanda listesinin bir kopyası (OP'nin sorusunda bahsedildiği gibi)
Adam Snaider

4

Bunun üstesinden gelebilecek iyi bilinen bir işlev vardır:

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

Test_size değerini 0 olarak ayarlamak, bölünmeyi önler ve karıştırılmış veriler verir. Genellikle tren ve test verilerini bölmek için kullanılsa da, bunları da karıştırır.
Gönderen dokümantasyon

Dizileri veya matrisleri rastgele tren ve test alt kümelerine ayırın

Bir onelinerdaki verileri bölmek (ve isteğe bağlı olarak alt örnekleme) için tek bir çağrıya veri girmek için giriş doğrulamasını ve sonraki (ShuffleSplit (). Bölünmüş (X, y)) ve uygulamayı saran hızlı yardımcı program.


Bunu hiç düşünmediğime inanamıyorum. Cevabınız mükemmel.
Uzun Nguyen

2

Diyelim ki iki dizimiz var: a ve 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]]) 

İlk olarak ilk boyuta izin vererek satır indeksleri elde edebiliriz

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

Ardından gelişmiş dizinleme kullanın. Burada her iki diziyi birlikte karıştırmak için aynı indeksleri kullanıyoruz.

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

Bu şuna eşdeğerdir:

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

Neden sadece bir [indeks ,:] veya b [endeks ,:] değil?
Kev

1

Dizileri kopyalamaktan kaçınmak istiyorsanız, bir permütasyon listesi oluşturmak yerine dizideki her öğeden geçmenizi ve dizideki başka bir konuma rastgele değiştirmenizi öneririm

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]

Bu, Knuth-Fisher-Yates shuffle algoritmasını uygular.


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.html beni kendi karışık çalma algoritmalarımı uygulamaktan sakındı ; bu soruyu sormamdan kısmen sorumlu. :) Ancak, Knuth-Fisher-Yates algoritmasını kullanmayı düşünmem gerektiğini belirtmek için çok haklısınız.
Josh Bleecher Snyder

İyi gördüm, şimdi kodu düzelttim. Her neyse, yerinde karıştırmanın temel fikrinin, kopya yapmaktan kaçınarak keyfi sayıda diziye ölçeklenebilir olduğunu düşünüyorum.
DaveP

Kod hala yanlış (çalışmaz bile). O işi yapmak için değiştirmek len(a)yoluyla reversed(range(1, len(a))). Ama yine de çok verimli olmayacak.
Sven Marnach

1

Bu çok basit bir çözüm gibi görünüyor:

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

Bir örnekle, bunu yapıyorum:

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
Bu aşağı yukarı eşdeğerdir combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), sadece daha yavaştır. Yine de Numpy kullandığınızdan, Numpy combo = np.c_[images, labels], shuffle ve unzip kullanarak dizileri sıkıştırmak çok daha hızlı bir çözüm olacaktır images, labels = combo.T. Varsayarsak labelsve imagesbaşlamak için aynı uzunlukta tek boyutlu Numpy diziler vardır, bu kolayca en hızlı çözüm olacaktır. Çok boyutlularsa, yukarıdaki cevabıma bakın.
Sven Marnach

Tamam bu mantıklı. Teşekkürler! @SvenMarnach
ajfbiw.s

0

İkinci bir arg almak için python's random.shuffle () genişletilmiş:

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]

Bu şekilde karıştırma işleminin yerinde gerçekleştiğinden ve işlevin çok uzun veya karmaşık olmadığından emin olabilirim.


0

Sadece kullanın numpy...

İlk olarak iki giriş dizisini birleştirin 1D dizisi etiketler (y) ve 2D dizisi veri (x) 'dir ve bunları NumPy shuffleyöntemiyle karıştırır . Sonunda onları ayır ve geri dön.

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.