두 개의 numpy 배열을 동시에 섞는 더 좋은 방법


239

모양이 다른 두 개의 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)

이것은 효과가 있지만 ... 계속 작동한다는 보장이 거의 없기 때문에 조금 무섭습니다.


9
6 년 후, 나는이 질문이 얼마나 인기가 있는지 놀랍고 놀랐습니다. 약간의 유쾌한 우연의 일치로 Go 1.10의 경우 표준 라이브러리에 math / rand.Shuffle을 기고했습니다 . API의 디자인은 두 개의 배열을 한 번에 섞는 것이 쉽지 않으며 그렇게하는 것도 문서에 예제로 포함됩니다.
Josh Bleecher Snyder

답변:


72

당신의 "무서운"해결책은 나에게 무서운 것처럼 보이지 않습니다. 호출 shuffle()난수 생성기에 대한 호출 같은 수의 같은 길이 결과의 두 시퀀스, 이들은 셔플 알고리즘에서 유일하게 "임의의"요소입니다. 상태를 재설정하면 난수 생성기에 대한 호출이에 대한 두 번째 호출에서 동일한 결과를 제공 shuffle()하므로 전체 알고리즘이 동일한 순열을 생성합니다.

이 방법이 마음에 들지 않으면 처음부터 두 개가 아닌 하나의 배열에 데이터를 저장하고이 단일 배열에 두 개의 뷰를 만들어 현재 두 배열을 시뮬레이션하는 다른 솔루션이 있습니다. 셔플 링에는 단일 배열을 사용하고 다른 모든 용도로는보기를 사용할 수 있습니다.

예 :하자가 배열 가정 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.]])

이제 모든 데이터를 포함하는 단일 배열을 구성 할 수 있습니다.

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)

의 데이터 a2와는 b2함께 공유됩니다 c. 두 어레이를 동시에 섞으려면을 사용하십시오 numpy.random.shuffle(c).

프로덕션 코드에서는 물론 원본을 만드는 피하려고 것입니다 ab만들 즉시 전혀와 c, a2b2.

이 솔루션은 경우에 적용 할 수 ab다른 dtypes 있습니다.


Re : 무서운 해결책 : 다른 모양의 배열이 rng에 대해 다른 수의 호출을 생성하여 발산을 일으킬 수 있다고 걱정합니다. 그러나, 나는 당신이 현재 행동이 변경 될 가능성이 없다고 생각하며, 매우 간단한 doctest는 올바른 행동을 확인하는 것을 매우 쉽게 만듭니다 ...
Josh Bleecher Snyder

나는 당신의 제안 된 접근법을 좋아하며, a와 b가 통합 c 배열로 인생을 시작하도록 확실히 준비 할 수 있습니다. 그러나 a와 b는 셔플 링 직후 (GPU로 효율적으로 전송하기 위해) 인접해야하므로 내 경우에는 어쨌든 a와 b의 사본을 만들 것이라고 생각합니다. :(
Josh Bleecher Snyder

@Josh : numpy.random.shuffle()Python 목록 또는 NumPy 배열과 같은 임의의 가변 시퀀스에서 작동합니다. 배열 모양은 중요하지 않으며 시퀀스 길이 만 중요합니다. 이다 매우 내 의견으로는 변경 가능성.
Sven Marnach

나는 몰랐다. 그렇게하면 훨씬 편해집니다. 감사합니다.
Josh Bleecher Snyder

@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

1
@mtrw : 원래 배열이 그대로 유지된다는 사실만으로도 반환 된 배열이 동일한 데이터의 뷰라는 것을 배제하지는 않습니다. 그러나 NumPy보기는 순열 된보기를 지원하기에 충분히 유연하지 않기 때문에 실제로는 그렇지 않습니다 (바람직하지 않습니다).
Sven Marnach

1
@ 스벤-나는 정말 견해에 대해 배워야합니다. @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 초, 각각 1 백만 건의 통화를 시도했습니다 .
mtrw

3
나는 이것의 단순성과 가독성을 정말로 좋아하며 고급 인덱싱은 계속해서 나를 놀라게하고 놀라게합니다. 이 답변은 +1이됩니다. 이상하게도 내 (대형) 데이터 세트에서는 원래 기능보다 느립니다. 원본은 10 반복 동안 ~ 1.8 초가 걸리고 ~ 2.7 초가 걸립니다. 두 숫자는 모두 일관성이 있습니다. 테스트에 사용한 데이터 세트 a.shape(31925, 405)b.shape입니다 (31925,).
Josh Bleecher Snyder

1
어쩌면 속도가 느려져서 작업을 수행하지 않고 대신 새로운 배열을 생성한다는 사실과 관련이있을 수 있습니다. 또는 CPython이 배열 인덱스를 구문 분석하는 방법과 관련하여 속도가 느려집니다.
Íhor Mé

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]

두 배열 x, y는 이제 같은 방식으로 무작위로 섞입니다.


5
이것은 mtrw의 솔루션과 동일합니다. 처음 두 줄은 순열을 생성하지만 한 줄에서 수행 할 수 있습니다.
Josh Bleecher Snyder 2016 년

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을 사용할 이유가 없습니다.
Erotemic

하나 RandomState는 루프 외부에서 사용될 수 있습니다. 11:14의 Adam Snaider의 답변
bartolo-otrit

1
@ bartolo-otrit, for루프 에서 선택해야 할 사항은 무작위 상태를 재할 당할지 또는 다시 시드할지 여부입니다. 셔플 링 함수에 전달되는 배열 수가 적을수록 두 성능 간의 차이는 없을 것으로 예상됩니다. 그러나 그렇습니다. rstate는 루프 외부에 할당되고 각 반복에서 루프 내부에 다시 ​​시드 될 수 있습니다.
이삭 B

9

다음과 같은 배열을 만들 수 있습니다.

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

그런 다음 섞으십시오.

np.random.shuffle(s)

이제이 인수를 배열의 인수로 사용하십시오. 같은 셔플 된 인수는 같은 셔플 된 벡터를 반환합니다.

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

실제로 이것이 최선의 해결책이며 받아 들여 져야합니다! 심지어 많은 (2 개 이상의) 어레이에서 동시에 작동합니다. 아이디어는 간단합니다. 인덱스 목록 [0, 1, 2, ..., n-1]을 섞은 다음 섞은 인덱스로 배열의 행을 다시 인덱싱하십시오. 좋은!
Basj

5

연결된 목록에 대해 적절한 셔플 링을 수행 할 수있는 한 가지 방법은 시드 (임의로 할 수 있음)를 사용하고 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한마음으로 단행되지 않습니다.
Bruno Klein

@BrunoKlein 당신이 맞아요. 무작위 상태를 다시 시드하도록 게시물을 수정했습니다. 또한 두 목록이 동시에 섞여 있다는 의미에서 일관되지는 않지만 둘 다 같은 방식으로 섞여 있다는 의미에서 일관되어 있으며, 더 많은 메모리를 필요로하지 않습니다. 목록의 사본 (OP가 그의 질문에 언급)
Adam Snaider

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)) 및 응용 프로그램을 랩핑하여 단일 라이너로 데이터를 분할 (및 선택적으로 서브 샘플링)하기 위해 단일 호출로 데이터를 입력하는 빠른 유틸리티입니다.


나는 이것을 결코 생각하지 않았다는 것을 믿을 수 없다. 당신의 대답은 훌륭합니다.
Long Nguyen

2

두 개의 배열이 있다고 가정 해 봅시다 : a와 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]]) 

먼저 1 차원을 치환하여 행 인덱스를 얻을 수 있습니다

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 알고리즘 사용을 고려해야한다고 지적 할 수 있습니다.
Josh Bleecher Snyder

잘, 나는 지금 코드를 수정했습니다. 어쨌든, 내부 셔플 링의 기본 아이디어는 복사를 피하는 임의의 수의 배열로 확장 가능하다고 생각합니다.
DaveP

코드가 여전히 올바르지 않습니다 (실행되지 않습니다). 그것을 작동하게하려면 교체 len(a)에 의해 reversed(range(1, len(a))). 그러나 어쨌든 그것은 효율적이지 않을 것입니다.
Sven Marnach

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를 사용하고 있기 때문에 훨씬 빠른 해결책은 Numpy를 사용하여 어레이를 압축하고 combo = np.c_[images, labels]셔플하고 다시 압축 해제하는 것 images, labels = combo.T입니다. labels그리고 images같은 길이의 1 차원 Numpy 배열 이라고 가정하면 , 이것이 가장 빠른 솔루션 일 것입니다. 그들이 다차원이라면 위의 대답을 참조하십시오.
Sven Marnach

알겠습니다. 감사! @SvenMarnach
ajfbiw.s

0

파이썬의 random.shuffle ()을 확장하여 두 번째 인수를 취했습니다.

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

먼저 두 입력 배열을 병합하십시오. 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.