페이지 불러오는 중
1~9장을 한 번 본 뒤 다시 읽는 챕터입니다. NumPy에서 반복적으로 사람을 잡는 5가지 ① 파이썬 루프, ② view/copy 혼동, ③ 부동소수점 비교, ④ 정수 오버플로 / NaN 전파, ⑤ dtype·메모리 낭비 마지막에 자주 보는 에러 메시지 사전을 붙였습니다.
import numpy as npNumPy의 존재 이유입니다. 원소별 for는 거의 항상 배열 연산으로 바꿀 수 있습니다.
a = np.arange(1_000_000, dtype=np.float64)
# 나쁨: 파이썬 루프 (수백 ms)
out = np.empty_like(a)
for i in range(len(a)):
out[i] = a[i] ** 2 + 1
# 좋음: 벡터화 (수 ms, 수십~수백 배 빠름)
out = a ** 2 + 1루프를 못 없앨 때의 차선책은 다음과 같습니다.
np.where, np.select, 불리언 마스킹np.cumsum, np.add.at, np.bincountnp.lib.stride_tricks.sliding_window_view💡 이럴 때: 원소별
for가 보이면, 배열 연산으로 교체 가능한지부터 확인합니다. 큰 데이터에서 수십~수백 배 차이가 납니다.
a = np.arange(10)
sub = a[2:5] # 슬라이싱 → view
sub[:] = 0 # a[2:5]도 0이 됨!
a # array([0, 1, 0, 0, 0, 5, 6, 7, 8, 9])반대로, copy인 줄 모르고 원본이 바뀌길 기대하는 경우도 있습니다:
b = a[a > 3] # 불리언 인덱싱 → copy
b[:] = -1 # a는 그대로규칙:
.copy() 합니다..copy() 합니다 (호출자 데이터 오염 방지).np.shares_memory(a, b)로 확인합니다.0.1 + 0.2 == 0.3 # False ← 부동소수점은 == 로 비교하지 말 것
0.1 + 0.2 # 0.30000000000000004
np.isclose(0.1 + 0.2, 0.3) # True - 허용 오차 내 비교
np.allclose(arr1, arr2) # 배열 전체가 근사적으로 같은지 (bool 하나)
# 테스트에서
np.testing.assert_allclose(result, expected, rtol=1e-6)np.isclose는 atol(절대 오차)과 rtol(상대 오차)을 받습니다. 값이 0 근처면 atol이, 크면 rtol이 지배적입니다.
💡 이럴 때: 부동소수점 연산 결과를 검증하거나 단위 테스트에서 기대값과 비교할 때
==대신 씁니다.
np.array([1., np.nan, 3.]).sum() # nan - 하나만 있어도 전체가 nan
np.nansum(np.array([1., np.nan, 3.])) # 4.0 - nan 무시# (1) 배열 "생성" 시 범위 밖 파이썬 int → OverflowError (에러가 남)
# np.array([2**31], dtype=np.int32)
# → OverflowError: Python integer 2147483648 out of bounds for int32
# (2) 정수 배열끼리의 "연산" → 경고 없이 조용히 순환(wrap-around)
np.array([2**31 - 1], dtype=np.int32) + np.int32(1)
# array([-2147483648], dtype=int32) - 최댓값 + 1 → 최솟값카운트 누적, 팩토리얼, 해시 등에서 int32가 부족하면 조용히 음수가 나옵니다. 의심되면 dtype=np.int64 또는 float64를 씁니다.
a = np.ones((10_000, 10_000)) # float64 → 약 800MB
a = np.ones((10_000, 10_000), dtype=np.float32) # → 약 400MBfloat64 → float32: 메모리 절반입니다. 대부분의 전처리·추론에서 정밀도가 충분합니다.astype / 슬라이스 후 .copy() 남발: 매번 전체 배열이 복제됩니다.a += 1은 기존 버퍼를 재사용하고, a = a + 1은 새 배열을 생성합니다.a += 1 # 메모리 추가 없음
a *= 2
np.clip(a, 0, 1, out=a) # out= 로 결과를 기존 배열에 씀💡 이럴 때: 큰 배열을 반복 변형할 때.
a += 1,out=인자로 매 단계 새 배열 생성을 막아 메모리 피크를 낮춥니다.
for 문이 남아 있지 않은가?astype/copy 하고 있지 않은가?np.where/마스킹으로 바꿀 수 있는가?float64 → float32)out= 인자로 기존 배열에 쓸 수 있는가?
댓글
GitHub 계정으로 의견이나 질문을 남길 수 있습니다.