스택큐힙리스트

numpy.array 모양 (R,1)과 (R,)의 차이점 본문

카테고리 없음

numpy.array 모양 (R,1)과 (R,)의 차이점

스택큐힙리스트 2023. 4. 3. 04:40
반응형

numpy에서 일부 작업들은 (R, 1) 모양으로 반환되지만 일부 작업들은 (R,) 모양으로 반환됩니다. 이는 행렬 곱셈을 더 귀찮게 만듭니다. 왜냐하면 명시적인 #reshape 모양이 필요하기 때문입니다. 예를 들어, 행렬 #M ,가 주어졌을 때, #numpy.dot(M[:,0], numpy.ones((1, R))) 를 수행하려면 #R 가 행의 수인데 (물론, 열에 대해서도 동일한 문제가 발생합니다), #M[:,0] 모양은 (R,)이고, numpy.ones((1, R)) 모양은 (1, R)이기 때문에 #matrices are not aligned 에러가 발생합니다.

제 질문은 다음과 같습니다:

#(R, 1) 와 (R,) 의 차이점은 무엇인가요? 말 그대로 숫자 목록과 각 목록이 하나의 숫자만 포함하는 목록의 목록입니다. 왜 더 쉬운 행렬 곱셈을 위해 mpy so t 를 #(R, 1) 보다는 (R,) 에 유리하도록 설계하지 않는지 궁금합니다.

우리는 위의 예시와 같이 명시적으로 형태를 바꾸지 않고 더 나은 방법이 있을까요? numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

답변 1

1. NumPy에서 모양의 의미

당신은 실제로 숫자 목록과 모든 목록이 오직 숫자만을 포함하는 리스트 목록인 것을 안다고 쓰지만, 이것은 조금 도움이 되지 않는 방법입니다.

NumPy 배열을 생각하는 가장 좋은 방법은, 그것들이 두 부분으로 구성된다는 것입니다. 첫 번째는 데이터 버퍼이며, 이것은 단순한 원시 요소의 블록이고 두 번째는 데이터 버퍼를 해석하는 방법을 설명하는 뷰입니다.

예를 들어, 12개의 정수 배열을 생성하면:

>>> a = numpy.arange(12)

>>> a

array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

그러면 a 는 이렇게 구성된 데이터 버퍼입니다.

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐

│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │

└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

그리고 데이터를 해석하는 방법을 설명하는 뷰:

>>> a.flags

C_CONTIGUOUS : True

F_CONTIGUOUS : True

OWNDATA : True

WRITEABLE : True

ALIGNED : True

UPDATEIFCOPY : False

>>> a.dtype

dtype('int64')

>>> a.itemsize

8

>>> a.strides

(8,)

>>> a.shape

(12,)

여기에서 모양 (12,) 는 0에서 11까지 단일 인덱스에 의해 배열이 색인화된다는 것을 의미합니다. 개념적으로, 만약 우리가 이 단일 인덱스를 i 로 레이블을 지정한다면, 배열 a 는 이와 같이 보입니다:

i= 0 1 2 3 4 5 6 7 8 9 10 11

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐

│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │

└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

만약 우리가 배열을 # $! *& *! ^ $ & 로 변경하면, 이는 데이터 버퍼를 변경하지 않습니다. 대신, 데이터를 다른 방식으로 해석하는 새로운 뷰를 만듭니다. 따라서 다음과 같이합니다:

>>> b = a.reshape((3, 4))

# $ ^^! # && $ & 는 데이터 버퍼가 a!@ 와 같지만, 이제 두 개의 인덱스로 인덱싱됩니다. 두 인덱스는 각각 0에서 2와 0에서 3까지 실행됩니다. 두 인덱스를 각각 i!@와 j!@라고 라벨링하면 b 배열은 다음과 같아집니다:

i= 0 0 0 0 1 1 1 1 2 2 2 2

j= 0 1 2 3 0 1 2 3 0 1 2 3

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐

│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │

└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

그것은 의미한다:

>>> b[2,1]

9

두 번째 인덱스가 빠르게 변하고 첫 번째 인덱스가 느리게 변하는 것을 볼 수 있습니다. 이것을 반대로 선호한다면, order 매개변수를 지정할 수 있습니다.

>>> c = a.reshape((3, 4), order='F')

이것은 다음과 같이 인덱싱되는 배열을 결과로 냅니다.

i= 0 1 2 0 1 2 0 1 2 0 1 2

j= 0 0 0 1 1 1 2 2 2 3 3 3

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐

│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │

└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

이는 의미하다:

>>> c[2,1]

5

이제 크기가 1인 하나 이상의 차원을 갖는 배열의 형태가 무엇을 의미하는지 명확해야 합니다. 이후에:

>>> d = a.reshape((12, 1))

배열 d는 두 개의 인덱스로 인덱싱되며, 첫 번째 인덱스는 0에서 11까지 실행되고 두 번째 인덱스는 항상 0입니다.

i= 0 1 2 3 4 5 6 7 8 9 10 11

j= 0 0 0 0 0 0 0 0 0 0 0 0

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐

│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │

└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

그렇게:

>>> d[10,0]

10

길이 1의 차원은 자유로움(어떤 의미에서) 이므로, 당신이 마음대로 할 수 없는 것은 없습니다:

>>> e = a.reshape((1, 2, 1, 6, 1))

이렇게 인덱스가 지정된 배열을 주면,

i= 0 0 0 0 0 0 0 0 0 0 0 0

j= 0 0 0 0 0 0 1 1 1 1 1 1

k= 0 0 0 0 0 0 0 0 0 0 0 0

l= 0 1 2 3 4 5 0 1 2 3 4 5

m= 0 0 0 0 0 0 0 0 0 0 0 0

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐

│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │

└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

그래서:

>>> e[0,1,0,0,0]

6

배열이 구현되는 방식에 대한 자세한 내용은 NumPy internals documentation 를 참조하십시오.

2. 무엇을 할까요?

numpy.reshape은 새로운 뷰를 생성하기 때문에 필요할 때마다 사용해도 두렵지 않습니다. 다른 방식으로 배열을 인덱싱하려는 경우에 사용하는 올바른 도구입니다.

그러나 긴 계산에서는 보통 처음부터 적절한 형태의 배열을 구성할 수 있도록 배열을 재구성하고 전치하는 횟수를 최소화할 수 있습니다. 그러나 재구성이 필요한 상황이 어떤 맥락에서 발생했는지 실제로 보지 않으면 무엇을 변경해야 하는지 알기 어렵습니다.

당신의 질문에서 예시는 다음과 같습니다.

numpy.dot(M[:,0], numpy.ones((1, R)))

하지만 이것은 현실적이지 않습니다. 먼저, 이 표현:

M[:,0].sum()

결과를 더 간단하게 계산합니다. 그리고, 0열에 대해 정말로 특별한 것이 있나요? 아마 당신이 진짜 필요한 것은 다음과 같을 수도 있습니다:

M.sum(axis=0)

답변 2

넘파이는 파이썬에서 대규모 다차원 배열과 행렬 연산을 수행하는 라이브러리입니다. 넘파이에서 배열은 N-차원 배열 객체인 ndarray 클래스로 표현됩니다. 이러한 ndarray 객체는 속성 중 하나인 shape을 통해 배열의 차원 크기를 알 수 있습니다.

넘파이 배열의 shape은 일반적으로 (R, C)와 같은 형태로 표현됩니다. 여기서 R은 행의 수를 나타내는 정수이고, C는 열의 수를 나타내는 정수입니다. 그러나, 때로는 (R, 1)과 (R,)과 같은 다른 형태로 배열이 생성될 수 있습니다.

(R, 1) 형태의 배열은 열 벡터를 나타냅니다. 즉, R개의 행과 1개의 열을 가지며, 각 요소는 하나의 값을 가집니다. 열 벡터는 2D 배열이지만, 내적이나 외적과 같은 선형 대수 연산에서 벡터로 사용됩니다.

반면, (R,) 형태의 배열은 일차원 배열을 나타냅니다. 즉, R개의 요소를 가지며, 각 요소는 하나의 값을 가집니다. 이러한 배열은 벡터로 해석될 수 있지만, 다른 1D 배열과 차이점이 있습니다. (R,) 배열은 일차원 배열이지만, (R, 1) 배열은 2D 배열입니다.

이러한 차이점은 연산에서 중요합니다. 예를 들어, (R, 1) 배열과 (1, R) 배열 간의 외적 연산에서는 R x R의 2D 배열이 결과로 반환됩니다. 반면, (R,) 배열과 (R,) 배열 간의 외적 연산에서는 R의 스칼라 값이 결과로 반환됩니다. 따라서, 배열의 모양은 배열의 사용 방법과 연산 결과에 영향을 미치는 중요한 요소 중 하나입니다.

요약하면, 넘파이 배열의 shape은 배열의 차원 크기를 나타내며, 일반적으로 (R, C)와 같은 형태로 표현됩니다. (R, 1) 형태의 배열은 열 벡터를, 반면 (R,) 형태의 배열은 일차원 배열을 나타냅니다. 이러한 차이점은 연산에서 중요하며, 배열의 모양은 배열의 사용 방법과 연산 결과에 영향을 미칩니다.

반응형
Comments