Cách tốt hơn để xáo trộn hai mảng numpy trong unison


239

Tôi có hai mảng numpy có hình dạng khác nhau, nhưng có cùng chiều dài (kích thước hàng đầu). Tôi muốn xáo trộn từng người trong số họ, sao cho các yếu tố tương ứng tiếp tục tương ứng - tức là trộn chúng lại với nhau theo các chỉ số hàng đầu của họ.

Mã này hoạt động và minh họa các mục tiêu của tôi:

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

Ví dụ:

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

Tuy nhiên, điều này cảm thấy cồng kềnh, không hiệu quả và chậm chạp, và nó đòi hỏi phải tạo một bản sao của các mảng - tôi muốn thay đổi chúng tại chỗ, vì chúng sẽ khá lớn.

Có cách nào tốt hơn để đi về điều này? Thực thi nhanh hơn và sử dụng bộ nhớ thấp hơn là mục tiêu chính của tôi, nhưng mã thanh lịch cũng sẽ rất tốt.

Một ý nghĩ khác mà tôi có là:

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)

Điều này hoạt động ... nhưng nó hơi đáng sợ, vì tôi thấy một chút đảm bảo rằng nó sẽ tiếp tục hoạt động - chẳng hạn như nó không phải là thứ được đảm bảo để tồn tại qua phiên bản numpy, chẳng hạn.


9
Sáu năm sau, tôi thích thú và ngạc nhiên về mức độ phổ biến của câu hỏi này. Và trong một chút trùng hợp thú vị, cho Go 1.10 tôi đã đóng góp math / rand.Shuffle cho thư viện chuẩn . Thiết kế của API làm cho việc trộn lẫn hai mảng không đồng nhất và làm như vậy thậm chí còn được đưa vào làm ví dụ trong các tài liệu.
Josh Bleecher Snyder

Câu trả lời:


72

Giải pháp "đáng sợ" của bạn không có vẻ đáng sợ đối với tôi. Việc gọi shuffle()hai chuỗi có cùng độ dài dẫn đến cùng một số lượng cuộc gọi đến trình tạo số ngẫu nhiên và đây là các phần tử "ngẫu nhiên" duy nhất trong thuật toán xáo trộn. Bằng cách đặt lại trạng thái, bạn đảm bảo rằng các cuộc gọi đến trình tạo số ngẫu nhiên sẽ cho kết quả tương tự trong cuộc gọi thứ hai shuffle(), do đó toàn bộ thuật toán sẽ tạo ra cùng một hoán vị.

Nếu bạn không thích điều này, một giải pháp khác sẽ là lưu trữ dữ liệu của bạn trong một mảng thay vì hai ngay từ đầu và tạo hai chế độ xem vào mảng duy nhất mô phỏng hai mảng bạn có bây giờ. Bạn có thể sử dụng mảng duy nhất để xáo trộn và các khung nhìn cho tất cả các mục đích khác.

Ví dụ: Chúng ta hãy giả sử các mảng abtrông như thế này:

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

Bây giờ chúng ta có thể xây dựng một mảng duy nhất chứa tất cả dữ liệu:

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

Bây giờ chúng tôi tạo các khung nhìn mô phỏng bản gốc ab:

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

Dữ liệu của a2b2được chia sẻ với c. Để xáo trộn cả hai mảng đồng thời, sử dụng numpy.random.shuffle(c).

Trong mã sản xuất, tất nhiên bạn sẽ cố gắng tránh tạo bản gốc avà hoàn btoàn và ngay lập tức tạo c, a2b2.

Giải pháp này có thể được điều chỉnh cho phù hợp với trường hợp đó abcó các loại khác nhau.


Re: giải pháp đáng sợ: Tôi chỉ lo lắng rằng các mảng có hình dạng khác nhau có thể (có thể hình dung) mang lại số lượng cuộc gọi khác nhau đến rng, điều này sẽ gây ra sự phân kỳ. Tuy nhiên, tôi nghĩ bạn đã đúng rằng hành vi hiện tại có lẽ khó thay đổi, và một tài liệu rất đơn giản giúp việc xác nhận hành vi đúng rất dễ dàng ...
Josh Bleecher Snyder

Tôi thích cách tiếp cận được đề xuất của bạn, và chắc chắn có thể sắp xếp để có một và b bắt đầu cuộc sống như một mảng c thống nhất. Tuy nhiên, a và b sẽ cần được tiếp tục ngay sau khi xáo trộn (để chuyển hiệu quả sang GPU), vì vậy tôi nghĩ rằng, trong trường hợp cụ thể của mình, cuối cùng tôi cũng sẽ tạo ra các bản sao của a và b. :(
Josh Bleecher Snyder

@Josh: Lưu ý rằng numpy.random.shuffle()hoạt động trên các chuỗi có thể thay đổi tùy ý, chẳng hạn như danh sách Python hoặc mảng NumPy. Hình dạng mảng không quan trọng, chỉ có độ dài của chuỗi. Điều này rất khó thay đổi theo ý kiến ​​của tôi.
Sven Marnach

Tôi không biết điều đó. Điều đó làm cho tôi thoải mái hơn nhiều với nó. Cảm ơn bạn.
Josh Bleecher Snyder

@SvenMarnach: Tôi đã đăng câu trả lời dưới đây. Bạn có thể nhận xét về việc bạn nghĩ nó có ý nghĩa / là một cách tốt để làm điều đó?
ajfbiw.s

351

Bạn có thể sử dụng lập chỉ mục mảng NumPy :

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

Điều này sẽ dẫn đến việc tạo ra các mảng không bị xáo trộn riêng biệt.


13
Điều này không tạo ra các bản sao, vì nó sử dụng lập chỉ mục nâng cao. Nhưng tất nhiên là nhanh hơn bản gốc.
Sven Marnach

1
@mtrw: Thực tế là các mảng ban đầu không được xử lý không vượt quá các mảng được trả về là các khung nhìn của cùng một dữ liệu. Nhưng thực tế thì không, vì các chế độ xem NumPy không đủ linh hoạt để hỗ trợ các chế độ xem được cho phép (điều này cũng không được mong muốn).
Sven Marnach

1
@Sven - Tôi thực sự phải tìm hiểu về quan điểm. @Dat Chu - Tôi vừa thử >>> 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()và có 38 giây cho phiên bản của OP và 27,5 giây cho phiên bản của tôi, cho mỗi 1 triệu cuộc gọi.
mtrw

3
Tôi thực sự thích sự đơn giản và dễ đọc của điều này, và lập chỉ mục nâng cao tiếp tục làm tôi ngạc nhiên và ngạc nhiên; cho rằng câu trả lời này dễ dàng được +1. Mặc dù, thật kỳ lạ, trên bộ dữ liệu (lớn) của tôi, nó chậm hơn chức năng ban đầu của tôi: bản gốc của tôi mất ~ 1,8 giây cho 10 lần lặp và điều này mất ~ 2,7 giây. Cả hai con số đều khá nhất quán. Bộ dữ liệu tôi sử dụng để kiểm tra đã a.shape(31925, 405)b.shape(31925,).
Josh Bleecher Snyder

1
Có lẽ, sự chậm chạp phải làm với thực tế là bạn không làm việc tại chỗ, mà thay vào đó là tạo ra các mảng mới. Hoặc với một số sự chậm chạp liên quan đến cách CPython phân tích các chỉ mục mảng.
Í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)

Để tìm hiểu thêm, hãy xem http://scikit-learn.org/urdy/modules/generated/sklearn.utils.shuffle.html


1
Giải pháp này tạo ra các bản sao ( "Các mảng ban đầu không bị ảnh hưởng" ), trong khi giải pháp "đáng sợ" của tác giả thì không.
bartolo-otrit

Bạn có thể chọn bất kỳ phong cách nào bạn muốn
James

32

Giải pháp rất đơn giản:

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

hai mảng x, y bây giờ đều được xáo trộn ngẫu nhiên theo cùng một cách


5
Điều này tương đương với giải pháp của mtrw. Hai dòng đầu tiên của bạn chỉ tạo ra một hoán vị, nhưng điều đó có thể được thực hiện trong một dòng.
Josh Bleecher Snyder

19

James đã viết vào năm 2015 một giải pháp sklearn rất hữu ích. Nhưng ông đã thêm một biến trạng thái ngẫu nhiên, không cần thiết. Trong đoạn mã dưới đây, trạng thái ngẫu nhiên từ numpy được tự động giả định.

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

Xáo trộn bất kỳ số lượng mảng nào với nhau, tại chỗ, chỉ sử dụng 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)

Và có thể được sử dụng như thế này

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

Một số điều cần lưu ý:

  • Khẳng định đảm bảo rằng tất cả các mảng đầu vào có cùng độ dài dọc theo chiều thứ nhất của chúng.
  • Mảng xáo trộn tại chỗ bởi chiều thứ nhất của chúng - không có gì trở lại.
  • Hạt ngẫu nhiên trong phạm vi int32 tích cực.
  • Nếu cần trộn ngẫu nhiên lặp lại, giá trị hạt giống có thể được đặt.

Sau khi xáo trộn, dữ liệu có thể được phân chia bằng cách sử dụng np.splithoặc được tham chiếu bằng các lát - tùy thuộc vào ứng dụng.


2
giải pháp đẹp, điều này làm việc hoàn hảo cho tôi. Ngay cả với các mảng của trục 3+
wprins

1
Đây là câu trả lời chính xác. Không có lý do để sử dụng np.random toàn cầu khi bạn có thể vượt qua các đối tượng trạng thái ngẫu nhiên.
Erotemic

Người RandomStateta có thể được sử dụng bên ngoài vòng lặp. Xem câu trả lời
bartolo-otrit

1
@ bartolo-otrit, sự lựa chọn phải được thực hiện trong forvòng lặp là liệu có thể gán lại hoặc xác định lại trạng thái ngẫu nhiên hay không. Với số lượng các mảng được truyền vào một chức năng xáo trộn dự kiến ​​là nhỏ, tôi sẽ không mong đợi một sự khác biệt hiệu suất giữa hai mảng. Nhưng vâng, rstate có thể được chỉ định bên ngoài vòng lặp và được đặt lại bên trong vòng lặp trên mỗi lần lặp.
Isaac B

9

bạn có thể tạo một mảng như:

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

sau đó xáo trộn nó:

np.random.shuffle(s)

bây giờ sử dụng s này làm đối số của mảng của bạn. các đối số xáo trộn giống nhau trả về các vectơ xáo trộn giống nhau.

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

Thực sự, đây là giải pháp tốt nhất, và nên được chấp nhận! Nó thậm chí hoạt động cho nhiều (hơn 2) mảng cùng một lúc. Ý tưởng rất đơn giản: chỉ cần xáo trộn danh sách chỉ mục [0, 1, 2, ..., n-1], sau đó giới thiệu lại các hàng của mảng với các chỉ mục được xáo trộn. Đẹp!
Basj

5

Một cách mà việc xáo trộn tại chỗ có thể được thực hiện cho các danh sách được kết nối là sử dụng một hạt giống (có thể là ngẫu nhiên) và sử dụng numpy.random.shuffle để thực hiện xáo trộn.

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

Đó là nó. Điều này sẽ xáo trộn cả a và b theo cùng một cách chính xác. Điều này cũng được thực hiện tại chỗ luôn luôn là một điểm cộng.

EDIT, không sử dụng np.random.seed () sử dụng np.random.RandomState thay thế

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

Khi gọi nó chỉ cần vượt qua trong bất kỳ hạt giống nào để cung cấp trạng thái ngẫu nhiên:

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

Đầu ra:

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

Chỉnh sửa: Đã sửa mã để chọn lại trạng thái ngẫu nhiên


Mã này không hoạt động. RandomStatethay đổi trạng thái trong cuộc gọi đầu tiên abkhông bị xáo trộn đồng loạt.
Bruno Klein

@BrunoKlein Bạn nói đúng. Tôi đã sửa bài để gieo lại trạng thái ngẫu nhiên. Ngoài ra, mặc dù nó không đồng nhất theo nghĩa là cả hai danh sách bị xáo trộn cùng một lúc, chúng vẫn đồng nhất theo nghĩa là cả hai đều được xáo trộn theo cùng một cách, và nó cũng không cần thêm bộ nhớ để giữ bản sao của danh sách (mà OP đề cập trong câu hỏi của anh ấy)
Adam Snaider

4

Có một chức năng nổi tiếng có thể xử lý việc này:

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

Chỉ cần đặt test_size thành 0 sẽ tránh bị chia tách và cung cấp cho bạn dữ liệu bị xáo trộn. Mặc dù nó thường được sử dụng để phân chia dữ liệu đào tạo và kiểm tra, nhưng nó cũng xáo trộn chúng.
Từ tài liệu

Tách các mảng hoặc ma trận thành các tập con thử nghiệm và thử nghiệm ngẫu nhiên

Tiện ích nhanh bao bọc xác thực đầu vào và tiếp theo (ShuffleSplit (). Split (X, y)) và ứng dụng để nhập dữ liệu vào một lệnh gọi để tách dữ liệu (và tùy chọn lấy mẫu con) trong một oneliner.


Tôi không thể tin rằng tôi không bao giờ nghĩ về điều này. Câu trả lời của bạn thật tuyệt vời.
Long Nguyễn

2

Nói rằng chúng ta có hai mảng: a và 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]]) 

Trước tiên chúng ta có thể có được các chỉ số hàng bằng cách hoán vị kích thước đầu tiên

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

Sau đó sử dụng lập chỉ mục nâng cao. Ở đây chúng tôi đang sử dụng cùng một chỉ mục để trộn lẫn cả hai mảng.

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

Điều này tương đương với

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

Tại sao không chỉ là [chỉ số ,:] hoặc b [chỉ số ,:]?
Kev

1

Nếu bạn muốn tránh sao chép mảng, thì tôi sẽ đề nghị thay vì tạo danh sách hoán vị, bạn đi qua mọi phần tử trong mảng và hoán đổi ngẫu nhiên nó sang vị trí khác trong mảng

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]

Điều này thực hiện thuật toán xáo trộn Knuth-Fisher-Yates.


3
mã hóa kinh dị.com / blog / 2007/12 / the-hard- of- naivete.html đã khiến tôi cảnh giác khi thực hiện các thuật toán xáo trộn của riêng mình; đó là một phần trách nhiệm cho câu hỏi này của tôi. :) Tuy nhiên, bạn rất đúng khi chỉ ra rằng tôi nên xem xét sử dụng thuật toán Knuth-Fisher-Yates.
Josh Bleecher Snyder

Cũng phát hiện ra, tôi đã sửa mã bây giờ. Dù sao, tôi nghĩ rằng ý tưởng cơ bản của việc xáo trộn tại chỗ có thể mở rộng thành một số lượng các mảng tùy ý để tránh tạo ra các bản sao.
DaveP

Mã vẫn không chính xác (thậm chí nó sẽ không chạy). Để làm cho nó hoạt động, thay thế len(a)bằng reversed(range(1, len(a))). Nhưng dù sao nó cũng sẽ không hiệu quả.
Sven Marnach

1

Đây có vẻ là một giải pháp rất đơn giản:

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

Với một ví dụ, đây là những gì tôi đang làm:

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
Điều này ít nhiều tương đương với combo = zip(images, labels); shuffle(combo); im, lab = zip(*combo), chỉ chậm hơn. Vì dù sao bạn cũng đang sử dụng Numpy, nên một giải pháp nhanh hơn nhiều sẽ là nén các mảng bằng Numpy combo = np.c_[images, labels], xáo trộn và giải nén lạiimages, labels = combo.T . Giả sử rằng labelsimageslà các mảng Numpy một chiều có cùng độ dài để bắt đầu, đây sẽ dễ dàng là giải pháp nhanh nhất. Nếu chúng là đa chiều, xem câu trả lời của tôi ở trên.
Sven Marnach

Được rồi, cái đó có lý. Cảm ơn! @SvenMarnach
ajfbiw.s

0

Tôi đã mở rộng Random.shuffle () của python để lấy một đối số thứ hai:

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]

Bằng cách đó tôi có thể chắc chắn rằng việc xáo trộn xảy ra tại chỗ và chức năng không quá dài hoặc phức tạp.


0

Chỉ cần sử dụng numpy...

Đầu tiên hợp nhất hai mảng đầu vào mảng 1D là nhãn (y) và mảng 2D là dữ liệu (x) và trộn chúng với shufflephương thức NumPy . Cuối cùng tách chúng ra và trở về.

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.