두 개의 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()하므로 전체 알고리즘이 동일한 순열을 생성합니다.

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

예 :하자가 배열 가정 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만들 즉시 전혀와 c, a2와 b2.

이 솔루션은 경우에 적용 할 수 a및 b다른 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.