2つの派手な配列を一斉にシャッフルするより良い方法


239

形状が異なるが、長さは同じ(先頭の次元)の2つの乱雑な配列があります。それぞれの要素をシャッフルして、対応する要素が引き続き対応するようにします。

このコードは機能し、私の目標を示しています:

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

例えば:

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

ただし、これは不格好で非効率的で低速に感じられ、配列のコピーを作成する必要があります。非常に大きくなるので、代わりに配列を入れ替えます。

これについてより良い方法はありますか?実行の高速化とメモリ使用量の削減が私の主な目標ですが、エレガントなコードもいいでしょう。

私が持っていたもう一つの考えはこれでした:

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)

これは機能します...しかし、それが機能することを保証することはほとんどないので、それは少し怖いです-たとえば、numpyバージョン全体で存続することが保証されているようなものではありません。


9
6年後、私はこの質問の人気の高さに驚き、面白がっていました。そして、うれしい偶然の一致で、Go 1.10のために、math / rand.Shuffleを標準ライブラリに提供しました。APIの設計により、2つの配列を同時にシャッフルすることは簡単です。これを行うことは、例としてドキュメントに含まれています。
Josh Bleecher Snyder 2017

回答:


72

あなたの「怖い」解決策は私には怖くないようです。呼び出しshuffle()乱数生成器への呼び出しの数が同じで、同じ長さの結果の二つの配列のために、これらはシャッフルアルゴリズムで唯一の「ランダム」な要素です。状態をリセットすることにより、乱数ジェネレーターへの呼び出しがへの2番目の呼び出しで同じ結果を与えることを保証するshuffle()ため、アルゴリズム全体で同じ順列が生成されます。

これが気に入らない場合は、別の解決策として、最初から2つではなく1つの配列にデータを格納し、現在の2つの配列をシミュレートするこの1つの配列に2つのビューを作成します。単一の配列をシャッフルに使用し、ビューを他のすべての目的に使用できます。

例:レッツは、配列を前提とaし、bこのような外観を:

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

これで、すべてのデータを含む単一の配列を作成できます。

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

次に、オリジナルaとをシミュレートするビューを作成しますb

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

a2およびのデータはとb2共有されていcます。両方のアレイを同時にシャッフルするには、を使用しますnumpy.random.shuffle(c)

生産コードでは、あなたはもちろん、オリジナルの作成を回避しようとするaと、b作成し、すぐにすべてに及びca2そしてb2

このソリューションは、さまざまなdtype ab持つケースに適応できます。


再:恐ろしい解決策:異なる形状の配列が(おそらく)rngへの異なる呼び出し数を生成し、それが発散を引き起こす可能性があることを心配しています。ただし、現在の動作がおそらく変更される可能性は低く、正しい動作の確認が非常に簡単なdoctestによって非常に簡単になると思うのは正しいと思います...
Josh Bleecher Snyder

私はあなたの提案するアプローチが好きで、aとbを統合されたc配列として開始するように間違いなく手配できます。ただし、(GPUへの効率的な転送のために)シャッフル後すぐにaとbが隣接している必要があるので、私の場合はとにかくaとbのコピーを作成することになると思います。:(
ジョシュブリーチャースナイダー、2011

@Josh:numpy.random.shuffle()PythonリストやNumPy配列などの任意の可変シーケンスで動作することに注意してください。配列の形状は重要ではなく、シーケンスの長さのみが重要です。これはとても私の意見では、変わる可能性ありません。
Sven Marnach、2011年

知らなかった。それは私にそれをはるかに快適にします。ありがとうございました。
ジョシュ

@SvenMarnach:以下に回答を投稿しました。それが理にかなっている/良い方法だと思うかどうかコメントできますか?
ajfbiw.s

351

NumPyの配列インデックスを使用できます。

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

これにより、ユニゾンシャッフルされた個別の配列が作成されます。


13
これ、高度なインデックス作成を使用するため、コピーを作成ます。もちろん、オリジナルよりも高速です。
Sven Marnach、2011年

1
@mtrw:元の配列がそのままであるという単なる事実は、返された配列が同じデータのビューであることを否定するものではありません。しかし、NumPyビューは順列ビューをサポートするのに十分な柔軟性がないため、実際にはそうではありません(これも望ましいことではありません)。
Sven Marnach、2011年

1
@Sven-私は本当にビューについて学ぶ必要があります。@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のバージョンは38秒、私のバージョンは27.5秒でした。
mtrw '05年

3
私はこれのシンプルさと読みやすさを本当に気に入っています。高度なインデックス作成は私を驚かせ、驚かせ続けています。そのため、この回答はすぐに+1されます。ただし、奇妙なことに、私の(大規模な)データセットでは、元の関数よりも遅くなります。元の関数は10回の反復で約1.8秒かかり、これには約2.7秒かかります。両方の数値は非常に一貫しています。私がテストに使用されるデータセットはいa.shapeである(31925, 405)b.shapeされます(31925,)
ジョシュブリーチャースナイダー、2011

1
多分、遅いのはあなたがその場で物事をしているのではなく、代わりに新しい配列を作成しているという事実に関係しているのかもしれません。または、CPythonが配列インデックスを解析する方法に関連するいくつかの遅延があります。
ÍhorME

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

詳細については、http://scikit-learn.org/stable/modules/generated/sklearn.utils.shuffle.htmlを参照してください


1
このソリューションはコピーを作成しますが「元の配列は影響を受けません」)、作成者の「怖い」ソリューションは作成しません。
bartolo-otrit

好きなスタイルを選択できます
James

33

非常にシンプルなソリューション:

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

2つの配列x、yは両方とも同じ方法でランダムにシャッフルされます。


5
これは、mtrwのソリューションと同等です。最初の2行は順列を生成しているだけですが、1行で実行できます。
ジョシュブリーチャースナイダー

19

Jamesは2015年に役立つsklearn ソリューションを書きました。しかし、彼はランダムな状態変数を追加しましたが、これは必要ありません。以下のコードでは、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

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)

そして、このように使用できます

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

注意すべきいくつかの点:

  • アサートは、すべての入力配列が最初の次元に沿って同じ長さであることを保証します。
  • 配列は最初の次元でインプレースでシャッフルされます-何も返されません。
  • 正のint32範囲内のランダムシード。
  • 反復可能なシャッフルが必要な場合は、シード値を設定できます。

シャッフル後、np.splitアプリケーションに応じて、データをスライスを使用して分割したり、スライスを使用して参照したりできます。


2
美しい解決策、これは私にとって完璧に働きました。3軸以上の配列でも
wprins

1
これが正解です。ランダムな状態オブジェクトを渡すことができる場合、グローバルnp.randomを使用する理由はありません。
エロティック

一つはRandomState、ループの使用外である可能性があります。Adam Snaiderの回答を
bartolo-otrit

1
@ bartolo-otrit、forループで行わなければならない選択は、ランダムな状態を再割り当てするか再シードするかです。シャッフル関数に渡される配列の数が少ないと予想されるので、2つの間のパフォーマンスの違いは期待できません。しかし、はい。rstateをループの外側に割り当て、各反復でループ内に再シードできます。
Isaac B

9

あなたは次のような配列を作ることができます:

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

次にシャッフルします:

np.random.shuffle(s)

このsを配列の引数として使用します。同じシャッフルされた引数は、同じシャッフルされたベクトルを返します。

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

本当に、これは最良の解決策であり、受け入れられるべきものです!多くの(2つを超える)アレイでも同時に機能します。アイデアは単純です。インデックスリスト[0、1、2、...、n-1]をシャッフルし、シャッフルされたインデックスで配列の行のインデックスを再作成するだけです。いいね!
Basj

5

接続されたリストに対してインプレースシャッフルを実行できる1つの方法は、シード(ランダムである可能性があります)を使用し、numpy.random.shuffleを使用してシャッフルを実行することです。

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

それでおしまい。これにより、aとbの両方がまったく同じ方法でシャッフルされます。これもインプレースで行われ、常にプラスになります。

編集、np.random.seed()を使用せず、代わりに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)

呼び出すときは、任意のシードを渡してランダムな状態をフィードします。

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

出力:

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

編集:ランダムな状態を再シードするようにコードを修正


このコードは機能しません。RandomState最初の呼び出しで状態を変え、aそしてb一斉にシャッフルされていません。
ブルーノクライン

@BrunoKleinあなたは正しいです。ランダムな状態を再シードするようにポストを修正しました。また、両方のリストが同時にシャッフルされるという意味では一致していませんが、両方が同じ方法でシャッフルされるという意味で一致しています。また、保持するためにより多くのメモリを必要としません。リストのコピー(OPが彼の質問で言及)
Adam Snaider '20

4

これを処理できるよく知られた関数があります:

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

test_sizeを0に設定するだけで分割が回避され、データがシャッフルされます。通常、トレーニングデータとテストデータを分割するために使用されますが、それらもシャッフルします。ドキュメント
から

配列または行列をランダムトレインとテストサブセットに分割する

入力検証とnext(ShuffleSplit()。split(X、y))およびアプリケーションをラップして、1つのライナーでデータを分割(およびオプションでサブサンプリング)するための単一の呼び出しにデータを入力するクイックユーティリティ。


私はこれについて考えたことがないなんて信じられない。あなたの答えは素晴らしいです。
Long Nguyen

2

aとbの2つの配列があるとします。

a = np.array([[1,2,3],[4,5,6],[7,8,9]])
b = np.array([[9,1,1],[6,6,6],[4,2,0]]) 

最初の次元を並べ替えることにより、最初に行インデックスを取得できます

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

次に、高度なインデックスを使用します。ここでは、同じインデックスを使用して両方の配列を同時にシャッフルしています。

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

これは

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

なぜa [indices ,:]またはb [indices ,:]だけではないのですか?
Kev

1

配列のコピーを避けたい場合は、順列リストを生成する代わりに、配列内のすべての要素を調べ、配列内の別の位置にランダムにスワップすることをお勧めします

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]

これは、Knuth-Fisher-Yatesシャッフルアルゴリズムを実装します。


3
codinghorror.com/blog/2007/12/the-danger-of-naivete.htmlのおかげで、自分のシャッフルアルゴリズムの実装には注意が必要です。私がこの質問をするのは部分的に責任があります。:)しかし、私はKnuth-Fisher-Yatesアルゴリズムの使用を検討する必要があることを指摘するのは非常に正しいです。
ジョシュブリーチャースナイダー、2011

よく見つけたので、コードを修正しました。とにかく、インプレースシャッフルの基本的な考え方は、コピーを作成しないように、任意の数の配列に拡張できると思います。
DaveP

コードはまだ正しくありません(実行されません)。それを動作させるために、置き換えるlen(a)ことでreversed(range(1, len(a)))。しかし、とにかくそれは非常に効率的ではありません。
Sven Marnach、2011年

1

これは非常に単純な解決策のようです:

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

例では、これは私がやっていることです:

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
これは多かれ少なかれと同等ですがcombo = zip(images, labels); shuffle(combo); im, lab = zip(*combo)、遅いだけです。とにかくNumpyを使用しているので、さらに高速な解決策はcombo = np.c_[images, labels]、Numpyを使用して配列を圧縮し、シャッフルし、再度解凍することimages, labels = combo.Tです。そもそも同じ長さの1次元のNumpy配列であるlabelsと仮定imagesすると、これは簡単に最速のソリューションになります。それらが多次元である場合は、上記の私の答えを参照してください。
Sven Marnach、2016

わかりました。ありがとう!@SvenMarnach
ajfbiw.s

0

Pythonのrandom.shuffle()を拡張して、2番目の引数を取ります。

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]

そうすることで、シャッフルがインプレースで行われ、関数が長すぎたり複雑になったりしないことが確実になります。


0

ただ使うnumpy...

最初に2つの入力配列をマージします。1D配列はlabels(y)で、2D配列はdata(x)で、NumPy shuffleメソッドでそれらをシャッフルします。最後にそれらを分割して戻ります。

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.