파이썬은 편리하고 유연하지만 계산 속도만 보면 다른 언어에 비해 현저히 느리다. 이 때문에 파이썬 생태계에는 이를 보완해 파이썬을 사용한 대규모 계산을 더 빠르고 편리하게 해주는 툴이 있다. 그 중 대표적인 넘파이(NumPy)는 개발자와 데이터 과학자들이 대규모 컴퓨팅을 위해 사용하는 가장 일반적인 파이썬 툴이다. 넘파이는 모두 C, C++ 및 포트란과 같은 고속 언어로 작성된 코드를 기반으로 하는, 배열과 행렬을 다루기 위한 라이브러리와 기법을 제공한다. 또한 모든 넘파이 연산은 파이썬 런타임 외부에서 실행되므로 파이썬 자체의 한계에 영향을 받지 않는다.

ⓒ Getty Image Bank

파이썬의 배열과 행렬 수학에 넘파이 사용하기

많은 수학 연산, 특히 머신 러닝 또는 데이터 과학의 수학 연산에서는 행렬, 즉 숫자 목록이 사용된다. 파이썬에서 하는 단순한 방법은 구조체 즉, 일반적으로 파이썬 list에 숫자를 저장한 후 이 구조체를 루프로 순환하면서 그 안의 모든 요소에 대해 연산을 수행하는 것이다. 그러나 이는 각 요소가 파이썬 객체와 머신 네이티브 숫자 사이를 왔다 갔다 변환해야 하므로 느리고 비효율적이다.

넘파이는 정수 또는 부동소수점과 같은 머신 네이티브 숫자 형식에 최적화된 특수한 배열 형식을 제공한다. 배열의 차원 수는 일정하지 않지만 각 배열은 균일한 데이터 형식인 dtype을 사용해 기반 데이터를 표현한다. 간단한 예를 들면 다음과 같다.

import numpy as np

np.array([0, 1, 2, 3, 4, 5, 6])

이 예제는 제공된 목록에서 1차원 넘파이 배열을 생성한다. 이 배열에는 dtype을 지정하지 않았으므로 제공된 데이터를 통해 플랫폼에 따라 32비트 또는 64비트 부호 있는 정수가 될 것으로 자동으로 추론된다. dtype을 명시적으로 지정하려면 다음과 같이 하면 된다. 여기서 np.uint32는 이름에서 알 수 있듯이 부호 없는 32비트 정수의 dtype이다.

np.array([0, 1, 2, 3, 4, 5, 6], dtype=np.uint32)

일반 파이썬 객체를 넘파이 배열을 위한 dtype으로 사용하는 것도 가능하지만 이렇게 하면 파이썬 대비 넘파이로 얻을 수 있는 성능 향상이 없다. 넘파이는 파이썬 네이티브 형식인 복소수, Decimal보다 머신 네이티브 숫자 형식인 int, float에서 가장 뛰어난 성능을 발휘한다.

넘파이가 파이썬의 배열 수학 속도를 높이는 방법

넘파이가 빠른 주된 이유는 파이썬의 객체 형식 대신 머신 네이티브 데이터 형식을 사용하기 때문이다. 또한 각 요소를 개별적으로 처리할 필요 없이 배열을 다룰 수 있는 방법을 제공한다는 점도 빠른 속도의 또 다른 이유다. 넘파이 배열은 많은 부분에서 전통적인 파이썬 객체와 같으므로 일반적인 파이썬 은유를 사용해 작업하려는 생각이 들 수 있다. 0~1000의 숫자로 넘파이 배열을 만들려면 다음과 같이 하면 된다.

x = np.array([_ for _ in range(1000)])

단, 위 구문은 동작은 하지만, 파이썬이 목록을 만들고 넘파이가 이 목록을 배열로 변환하기까지 시간이 소요되므로 그만큼 성능이 저하된다. 반면 같은 작업을 다음과 같이 넘파이 자체 내에서 훨씬 더 효율적으로 할 수 있다.

x = np.arange(1000)

0 또는 다른 아무 초기 값 배열을 만들거나 기존 데이터 집합, 버퍼 또는 기타 소스를 사용하는 등 다른 많은 종류의 넘파이 내장 연산을 사용해 루프 없이 새 배열을 만들 수 있다.

넘파이가 속도를 올리는 또 다른 중요한 방법은 대규모로 작업 시 배열 요소를 개별적으로 처리할 필요가 없도록 하는 것이다. 앞서 언급했듯이 넘파이 배열의 동작은 편의성을 위해 다른 파이썬 객체와 상당히 비슷하다. 예를 들어 목록과 같이 인덱싱하면 arr[0]은 넘파이 배열의 첫 번째 요소에 액세스한다. 이를 통해 배열의 개별 요소를 설정하거나 읽을 수 있다. 반면 배열의 모든 요소를 수정할 때는 넘파이의 "브로드캐스팅(broadcasting)" 기능을 사용하는 것이 최선이다. 파이썬의 루프 없이 전체 배열 또는 한 부분에서 연산을 실행할 수 있다. 이것 역시 성능에 민감한 모든 작업을 넘파이 자체에서 할 수 있도록 하기 위한 것이다. 예를 들면 다음과 같다.

x1 = np.array(

[np.arange(0, 10),