1. ホーム
  2. パイソン

[解決済み】2つのnumpy配列を同時にシャッフルするためのより良い方法

2022-03-29 18:40:47

質問

形状は異なるが、同じ長さ(先頭の次元)の2つのnumpy配列があります。私は、対応する要素が対応し続けるように、それらのそれぞれをシャッフルしたい - すなわち、それらの先行インデックスに関して統一的にシャッフルする。

このコードは動作し、私の目標を示しています。

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のバージョン間で生き残ることが保証されているようなものではなさそうです。

どのように解決するのか?

あなたのquot;scare"解決策は、私には怖いものには見えません。 呼び方 shuffle() 同じ長さの2つのシーケンスに対して、同じ回数だけ乱数発生器を呼び出すことになりますし、シャッフルアルゴリズムの唯一の要素であるquot;random"も同じです。 状態をリセットすることで、乱数発生器の呼び出しが、2回目の shuffle() そのため、アルゴリズム全体が同じ並べ替えを生成することになります。

もしこれが気に入らないのであれば、別の方法として、最初からデータを2つではなく1つの配列に格納し、この1つの配列に対して、今ある2つの配列を模した2つのビューを作成することができます。 1 つの配列はシャッフル用に、2 つのビューはそれ以外の目的に使用できます。

例 たとえば、次のような配列があるとします。 ab はこのように見える。

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

これで、すべてのデータを含む1つの配列を構築することができます。

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

ここで、元の ab :

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

のデータは a2b2 とは共有されます。 c . 両方の配列を同時にシャッフルするには numpy.random.shuffle(c) .

実運用コードでは、もちろん、オリジナルの ab を作成し、すぐに c , a2b2 .

この解決策は、次のような場合にも適用できます。 ab は異なるdtypesを持ちます。