sao chép mảng 2D sang chiều thứ 3, N lần (Python)


105

Tôi muốn sao chép một mảng 2D phức tạp sang một chiều thứ ba. Ví dụ, với mảng (2D) numpy:

import numpy as np
arr = np.array([[1,2],[1,2]])
# arr.shape = (2, 2)

chuyển nó thành ma trận 3D với N bản sao như vậy trong một chiều không gian mới. Hành động arrvới N = 3, đầu ra sẽ là:

new_arr = np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]])
# new_arr.shape = (3, 2, 2)

Câu trả lời:


146

Có lẽ cách sạch nhất là sử dụng np.repeat:

a = np.array([[1, 2], [1, 2]])
print(a.shape)
# (2,  2)

# indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the
# array along, (you can achieve the same effect by indexing with None, see below)
b = np.repeat(a[:, :, np.newaxis], 3, axis=2)

print(b.shape)
# (2, 2, 3)

print(b[:, :, 0])
# [[1 2]
#  [1 2]]

print(b[:, :, 1])
# [[1 2]
#  [1 2]]

print(b[:, :, 2])
# [[1 2]
#  [1 2]]

Phải nói rằng, bạn thường có thể tránh lặp lại hoàn toàn các mảng của mình bằng cách sử dụng phát sóng . Ví dụ: giả sử tôi muốn thêm một (3,)vectơ:

c = np.array([1, 2, 3])

đến a. Tôi có thể sao chép nội dung của a3 lần trong chiều thứ ba, sau đó sao chép nội dung của chai lần ở cả chiều thứ nhất và thứ hai, sao cho cả hai mảng của tôi đều như vậy (2, 2, 3), sau đó tính tổng của chúng. Tuy nhiên, việc này đơn giản và nhanh chóng hơn nhiều:

d = a[..., None] + c[None, None, :]

Đây, a[..., None]có hình dạng (2, 2, 1)c[None, None, :]có hình dạng (1, 1, 3)*. Khi tôi tính tổng, kết quả được 'phát sóng' dọc theo các kích thước của kích thước 1, cho tôi kết quả là hình dạng (2, 2, 3):

print(d.shape)
# (2,  2, 3)

print(d[..., 0])    # a + c[0]
# [[2 3]
#  [2 3]]

print(d[..., 1])    # a + c[1]
# [[3 4]
#  [3 4]]

print(d[..., 2])    # a + c[2]
# [[4 5]
#  [4 5]]

Broadcasting là một kỹ thuật rất mạnh mẽ vì nó tránh được chi phí bổ sung liên quan đến việc tạo các bản sao lặp đi lặp lại của các mảng đầu vào của bạn trong bộ nhớ.


* Mặc dù tôi đã bao gồm chúng để rõ ràng, nhưng các Nonechỉ số vào ckhông thực sự cần thiết - bạn cũng có thể làm a[..., None] + c, tức là phát một (2, 2, 1)mảng chống lại một (3,)mảng. Điều này là do nếu một trong các mảng có ít kích thước hơn mảng kia thì chỉ các kích thước theo sau của hai mảng cần phải tương thích. Để đưa ra một ví dụ phức tạp hơn:

a = np.ones((6, 1, 4, 3, 1))  # 6 x 1 x 4 x 3 x 1
b = np.ones((5, 1, 3, 2))     #     5 x 1 x 3 x 2
result = a + b                # 6 x 5 x 4 x 3 x 2

Để xác minh rằng điều này thực sự mang lại cho kết quả đúng, bạn cũng có thể in ra b[:,:,0], b[:,:,1]b[:,:,2]. Mỗi lát cắt kích thước thứ ba là một bản sao của mảng 2D ban đầu. Đây không phải là điều hiển nhiên khi chỉ nhìn vào print(b).
ely

Sự khác biệt giữa None và np.newaxis là gì? Khi tôi thử nghiệm nó, nó cho kết quả tương tự.
nguyên khối

1
@wedran Họ hoàn toàn giống nhau - np.newaxischỉ là bí danh củaNone
ali_m.

27

Một cách khác là sử dụng numpy.dstack. Giả sử bạn muốn lặp lại a num_repeatsthời gian ma trận :

import numpy as np
b = np.dstack([a]*num_repeats)

Bí quyết là quấn ma trận athành một danh sách gồm một phần tử, sau đó sử dụng *toán tử để sao chép các phần tử trong danh sách nàynum_repeats lần.

Ví dụ, nếu:

a = np.array([[1, 2], [1, 2]])
num_repeats = 5

Điều này lặp lại mảng [1 2; 1 2]5 lần trong chiều thứ ba. Để xác minh (trong IPython):

In [110]: import numpy as np

In [111]: num_repeats = 5

In [112]: a = np.array([[1, 2], [1, 2]])

In [113]: b = np.dstack([a]*num_repeats)

In [114]: b[:,:,0]
Out[114]: 
array([[1, 2],
       [1, 2]])

In [115]: b[:,:,1]
Out[115]: 
array([[1, 2],
       [1, 2]])

In [116]: b[:,:,2]
Out[116]: 
array([[1, 2],
       [1, 2]])

In [117]: b[:,:,3]
Out[117]: 
array([[1, 2],
       [1, 2]])

In [118]: b[:,:,4]
Out[118]: 
array([[1, 2],
       [1, 2]])

In [119]: b.shape
Out[119]: (2, 2, 5)

Cuối cùng, chúng ta có thể thấy rằng hình dạng của ma trận là 2 x 2, với 5 lát cắt trong chiều thứ ba.


Làm thế nào để điều này so sánh với reshape? Nhanh hơn? cho cấu trúc giống nhau? Nó chắc chắn gọn gàng hơn.
Ander Biguri,

@AnderBiguri Tôi chưa bao giờ đánh giá tiêu chuẩn ... Tôi đưa điều này vào đây chủ yếu để hoàn thiện. Sẽ rất thú vị với thời gian và xem sự khác biệt.
rayryeng

1
Tôi vừa thực hiện img = np.dstack ([arr] * 3) và hoạt động tốt! Cảm ơn bạn
thanos.a

1
Tôi có thể đề xuất một đầu ra đã xem để đạt hiệu quả. Là một bài viết cũ, mọi người có thể đã bỏ lỡ điều đó. Đã thêm một giải pháp cho Q&A này.
Divakar

1
IMHO là giải pháp dễ đọc nhất, nhưng sẽ thật tuyệt nếu so sánh nó với các phương pháp khác.
mrgloom

16

Sử dụng chế độ xem và nhận thời gian chạy miễn phí! Mở rộng chungn-dim các mảng thànhn+1-dim

Được giới thiệu trong NumPy1.10.0 , chúng ta có thể tận dụng numpy.broadcast_tođể tạo một 3Dkhung nhìn vào 2Dmảng đầu vào một cách đơn giản . Lợi ích sẽ là không tốn thêm bộ nhớ và thời gian chạy hầu như miễn phí. Điều này sẽ rất cần thiết trong trường hợp các mảng lớn và chúng ta có thể làm việc với các khung nhìn. Ngoài ra, điều này sẽ hoạt động vớin-dim các trường hợp .

Tôi sẽ sử dụng từ này stackthay copyvì người đọc có thể nhầm lẫn nó với việc sao chép các mảng tạo ra các bản sao bộ nhớ.

Xếp chồng dọc theo trục đầu tiên

Nếu chúng ta muốn xếp chồng đầu vào arrdọc theo trục đầu tiên, giải pháp np.broadcast_tođể tạo 3Dchế độ xem sẽ là:

np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here

Xếp chồng dọc theo trục thứ ba / cuối cùng

Để xếp chồng đầu vào arrdọc theo trục thứ ba, giải pháp để tạo 3Dchế độ xem sẽ là:

np.broadcast_to(arr[...,None],arr.shape+(3,))

Nếu chúng ta thực sự cần một bản sao bộ nhớ, chúng ta luôn có thể thêm vào .copy()đó. Do đó, các giải pháp sẽ là -

np.broadcast_to(arr,(3,)+arr.shape).copy()
np.broadcast_to(arr[...,None],arr.shape+(3,)).copy()

Đây là cách xếp chồng hoạt động cho hai trường hợp, được hiển thị cùng với thông tin hình dạng của chúng cho một trường hợp mẫu -

# Create a sample input array of shape (4,5)
In [55]: arr = np.random.rand(4,5)

# Stack along first axis
In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[56]: (3, 4, 5)

# Stack along third axis
In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[57]: (4, 5, 3)

(Các) giải pháp tương tự sẽ hoạt động để mở rộng n-dimđầu vào để n+1-dimxem đầu ra dọc theo trục đầu tiên và trục cuối cùng. Hãy cùng khám phá một số trường hợp mờ cao hơn -

Trường hợp đầu vào 3D:

In [58]: arr = np.random.rand(4,5,6)

# Stack along first axis
In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[59]: (3, 4, 5, 6)

# Stack along last axis
In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[60]: (4, 5, 6, 3)

Trường hợp đầu vào 4D:

In [61]: arr = np.random.rand(4,5,6,7)

# Stack along first axis
In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[62]: (3, 4, 5, 6, 7)

# Stack along last axis
In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[63]: (4, 5, 6, 7, 3)

và như thế.

Thời gian

Hãy sử dụng một 2Dtrường hợp mẫu lớn và lấy thời gian và xác minh đầu ra là a view.

# Sample input array
In [19]: arr = np.random.rand(1000,1000)

Hãy chứng minh rằng giải pháp được đề xuất là một quan điểm thực sự. Chúng tôi sẽ sử dụng xếp chồng dọc theo trục đầu tiên (kết quả sẽ rất giống với xếp chồng dọc theo trục thứ ba) -

In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape))
Out[22]: True

Hãy lấy thời gian để cho thấy rằng nó hầu như miễn phí -

In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape)
100000 loops, best of 3: 3.56 µs per loop

In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape)
100000 loops, best of 3: 3.51 µs per loop

Là một lượt xem, tăng Ntừ 3lên 3000không thay đổi gì về thời gian và cả hai đều không đáng kể về đơn vị thời gian. Do đó, hiệu quả cả về bộ nhớ và hiệu suất!


3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

Chỉnh sửa @ Mr.F, để duy trì thứ tự thứ nguyên:

B=B.T

Điều này dẫn đến một mảng N x 2 x 2 đối với tôi, ví dụ: B.shapebản in (N, 2, 2)cho bất kỳ giá trị nào N. Nếu bạn chuyển đổi Bvới B.Tthì nó khớp với đầu ra mong đợi.
ely

@ Mr.F - Bạn nói đúng. Điều này sẽ phát sóng dọc theo chiều đầu tiên, và làm như vậy B[0], B[1],...sẽ cung cấp cho bạn những lát đúng, mà tôi sẽ tranh luận và nói rằng nhân dễ dàng hơn để gõ thay vì sử dụng B[:,:,0], B[:,:,1]vv
rayryeng

Nó có thể dễ nhập hơn, nhưng ví dụ: nếu bạn đang làm điều này với dữ liệu hình ảnh, nó phần lớn sẽ không chính xác, vì hầu như tất cả các thuật toán sẽ mong đợi các quy ước của đại số tuyến tính được sử dụng cho các lát 2D của kênh pixel. Thật khó để tưởng tượng một ứng dụng mà bạn bắt đầu với một mảng 2D, xử lý các hàng và cột theo một quy ước nhất định, sau đó bạn muốn nhiều bản sao của cùng một thứ mở rộng thành một trục mới, nhưng đột nhiên bạn muốn trục đầu tiên thay đổi ý nghĩa thành là trục mới ...
ely

@ Mr.F - Ồ chắc chắn rồi. Tôi không thể đoán được những ứng dụng nào bạn sẽ muốn sử dụng ma trận 3D trong tương lai. Điều đó đang được nói, tất cả phụ thuộc vào ứng dụng. FWIW, tôi thích nó hơn B[:,:,i]và đó là những gì tôi đã quen.
rayryeng

2

Đây là một ví dụ truyền phát thực hiện chính xác những gì được yêu cầu.

a = np.array([[1, 2], [1, 2]])
a=a[:,:,None]
b=np.array([1]*5)[None,None,:]

Sau đó b*alà kết quả mong muốn và (b*a)[:,:,0]tạo ra array([[1, 2],[1, 2]]), là kết quả ban đầu a, như vậy (b*a)[:,:,1], v.v.


1

Điều này hiện cũng có thể được thực hiện bằng cách sử dụng np.tile như sau:

import numpy as np

a = np.array([[1,2],[1,2]])
b = np.tile(a,(3, 1,1))

b.shape
(3,2,2)

b
array([[[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]]])
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.