연속 배열과 비 연속 배열의 차이점은 무엇입니까?


101

에서 NumPy와 수동 모양 변경 () 함수에 대한, 그것은 말한다

>>> a = np.zeros((10, 2))
# A transpose make the array non-contiguous
>>> b = a.T
# Taking a view makes it possible to modify the shape without modifying the
# initial object.
>>> c = b.view()
>>> c.shape = (20)
AttributeError: incompatible shape for a non-contiguous array

내 질문은 다음과 같습니다.

  1. 연속 및 비 연속 배열이란 무엇입니까? 인접 메모리 블록 이란 무엇입니까?
  2. 이 둘 사이에 성능 차이가 있습니까? 둘 중 하나를 언제 사용해야합니까?
  3. 전치가 배열을 비 연속적으로 만드는 이유는 무엇입니까?
  4. c.shape = (20)오류가 발생 incompatible shape for a non-contiguous array합니까?

답변 해 주셔서 감사합니다!

답변:


222

연속 배열은 깨지지 않은 메모리 블록에 저장된 배열 일뿐입니다. 배열의 다음 값에 액세스하려면 다음 메모리 주소로 이동합니다.

2D 배열을 고려하십시오 arr = np.arange(12).reshape(3,4). 다음과 같이 보입니다.

여기에 이미지 설명 입력

컴퓨터 메모리에의 값은 arr다음과 같이 저장됩니다.

여기에 이미지 설명 입력

이 수단은, arrA는 C 연속 때문에 어레이 행이 메모리의 연속 블록으로 저장된다. 다음 메모리 주소는 해당 행의 다음 행 값을 보유합니다. 한 열 아래로 이동하려면 세 블록을 건너 뛰면됩니다 (예 : 0에서 4로 건너 뛰는 것은 1,2, 3을 건너 뛰는 것을 의미합니다).

배열을로 전치 arr.T한다는 것은 인접한 행 항목이 더 이상 인접한 메모리 주소에 없기 때문에 C 연속성이 손실됨을 의미합니다. 그러나, arr.T이다 포트란 연속 때문에 열이 메모리의 연속 블록에는 :

여기에 이미지 설명 입력


성능 측면에서, 서로 옆에있는 메모리 주소에 액세스하는 것은 더 "확산"된 주소에 액세스하는 것보다 훨씬 빠릅니다 (RAM에서 값을 가져 오면 여러 인접 주소를 가져 와서 CPU에 캐시 할 수 있음). 연속 배열에 대한 작업이 종종 더 빠르다는 것을 의미합니다.

C 연속 메모리 레이아웃의 결과로 행 단위 연산은 일반적으로 열 단위 연산보다 빠릅니다. 예를 들어, 일반적으로

np.sum(arr, axis=1) # sum the rows

다음보다 약간 빠릅니다.

np.sum(arr, axis=0) # sum the columns

마찬가지로 포트란 연속 배열의 경우 열에 대한 작업이 약간 더 빠릅니다.


마지막으로 새 모양을 할당하여 Fortran 연속 배열을 평면화 할 수없는 이유는 무엇입니까?

>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array

이것이 가능해 지려면 NumPy는 다음 arr.T과 같이 행을 합쳐야합니다.

여기에 이미지 설명 입력

( shape속성을 직접 설정하는 것은 C 순서를 가정합니다. 즉 NumPy는 작업을 행 단위로 수행하려고합니다.)

이것은 불가능합니다. 모든 축에 대해 NumPy 는 배열의 다음 요소에 도달하기 위해 일정한 스트라이드 길이 (이동할 바이트 수) 를 가져야합니다 . arr.T이러한 방식으로 평면화 하려면 배열의 연속 값을 검색하기 위해 메모리에서 앞뒤로 건너 뛰어야합니다.

arr2.reshape(12)대신 작성 하면 NumPy는 arr2의 값을 새 메모리 블록에 복사합니다 (이 모양에 대한 원래 데이터에 대한 뷰를 반환 할 수 없기 때문에).


이해하는 데 어려움이 있습니다. 좀 더 자세히 설명해 주시겠습니까? 메모리에서 불가능한 순서의 최신 그래픽 표현에서 실제로 보폭은 제 생각에 일정합니다. 예를 들어 0에서 1로 이동하려면 스트라이드는 1 바이트 (각 요소가 바이트라고 가정 해 보겠습니다)이며 각 열에 대해 동일합니다. 마찬가지로 스트라이드는 행의 한 요소에서 다음 요소로 이동하기위한 4 바이트이며 또한 일정합니다.
Vesnog

2
@Vesnog 2D arr2를 1D 모양으로 변경하는 데 실패한 경우 (12,)C 순서를 사용합니다. 즉, 축 1이 축 0보다 먼저 풀린다는 의미입니다 (즉, 원하는 1D 배열을 만들기 위해 4 개의 행 각각을 나란히 배치해야 함). 일정한 스트라이드 길이 (방문하기 위해 점프 할 바이트)를 사용하여 버퍼에서이 정수 시퀀스 (0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11)를 읽는 것은 불가능합니다. 이러한 요소는 순서대로 4, 4, -7, 4, 4, -7, 4, 4, 7, 4, 4)입니다. NumPy에는 축당 일정한 보폭이 필요합니다.
Alex Riley

처음에는 새로운 배열을 만들 것이라고 생각했지만 이전 배열의 메모리를 사용합니다.
Vesnog

@AlexRiley 어레이가 더 neighter C 또는 F 순서로 표시되면 무대 뒤에서 어떤 일이 발생합니까? 예를 들어, 모든 NxD 배열 arr 및 print (arr [:, ::-1] .flags)를 가져옵니다. 이 상황에서 어떤 일이 발생합니까? 나는 배열이 실제로 C 또는 F 순서라고 생각하지만 그중 어느 것입니까? 그리고 두 플래그가 모두 거짓이면 어떤 numpy 최적화가 손실됩니까?

@Jjang : NumPy가 배열을 C 또는 F 순서로 간주하는지 여부는 전적으로 모양과 보폭에 따라 다릅니다 (기준은 여기에 있음 ). 그래서 동안은 arr[:, ::-1]같은 메모리 버퍼의 도면 arr이 ...는 "비표준"위해 버퍼에 이송 값을 가지고로 NumPy와는 C 또는 F 순서를 고려하지 않고는,
알렉스 라일리

12

12 개의 다른 배열 값이있는이 예제가 도움이 될 것입니다.

In [207]: x=np.arange(12).reshape(3,4).copy()

In [208]: x.flags
Out[208]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [209]: x.T.flags
Out[209]: 
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  ...

C order값은 생성 된 순서에 있습니다. 전치 사람이 아니다

In [212]: x.reshape(12,)   # same as x.ravel()
Out[212]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [213]: x.T.reshape(12,)
Out[213]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

둘 다 1d 뷰를 얻을 수 있습니다.

In [214]: x1=x.T

In [217]: x.shape=(12,)

의 모양 x도 변경할 수 있습니다.

In [220]: x1.shape=(12,)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-220-cf2b1a308253> in <module>()
----> 1 x1.shape=(12,)

AttributeError: incompatible shape for a non-contiguous array

그러나 조옮김의 모양은 변경할 수 없습니다. 는 data여전히 0,1,2,3,4...로서 액세스 액세스 할 수없는 위해, 0,4,8...1 차원 배열이다.

그러나 복사본은 x1변경할 수 있습니다.

In [227]: x2=x1.copy()

In [228]: x2.flags
Out[228]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [229]: x2.shape=(12,)

보는 strides것도 도움이 될 수 있습니다. strides는 다음 값에 도달하기 위해 얼마나 멀리 (바이트) 이동해야 하는지를 나타냅니다. 2d 배열의 경우 2 개의 stride 값이 있습니다.

In [233]: x=np.arange(12).reshape(3,4).copy()

In [234]: x.strides
Out[234]: (16, 4)

다음 행으로 이동하려면 16 바이트 단계, 다음 열만 4.

In [235]: x1.strides
Out[235]: (4, 16)

조옮김은 보폭의 순서를 전환합니다. 다음 행은 4 바이트, 즉 다음 숫자입니다.

In [236]: x.shape=(12,)

In [237]: x.strides
Out[237]: (4,)

모양을 변경하면 보폭도 변경됩니다. 버퍼를 한 번에 4 바이트 씩 이동하면됩니다.

In [238]: x2=x1.copy()

In [239]: x2.strides
Out[239]: (12, 4)

비록 x2처럼 보이는 x1, 그것은 다른 순서 값으로 자신의 데이터 버퍼를 가지고 있습니다. 다음 열은 이제 4 바이트 이상이고 다음 행은 12 (3 * 4)입니다.

In [240]: x2.shape=(12,)

In [241]: x2.strides
Out[241]: (4,)

에서처럼 x모양을 1d로 변경하면 보폭이 (4,).

의 경우 x1데이터를 0,1,2,...순서대로 사용하면를 줄 1d 보폭이 없습니다 0,4,8....

__array_interface__ 배열 정보를 표시하는 또 다른 유용한 방법입니다.

In [242]: x1.__array_interface__
Out[242]: 
{'strides': (4, 16),
 'typestr': '<i4',
 'shape': (4, 3),
 'version': 3,
 'data': (163336056, False),
 'descr': [('', '<i4')]}

x1데이터 버퍼 주소로써 동일하게 나타날 x이 데이터를 공유하는로. x2버퍼 주소가 다릅니다.

order='F'매개 변수를 copyreshape명령에 추가하여 실험 할 수도 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.