본문 바로가기

AIFFLE/INFO

numpy 정수형 데이터의 처리방식으로 인한 오류

728x90

학습 내용

python에서의 정수형 데이터 처리 방식을 이해한다
numpy의 정수형 데이터 처리방식을 이해한다 (C언어)

 

들어가며

p 값의 변화에 따른 norm 값의 변화를 직접 확인해보고자 아래 코드를 실행시키다 보니 19부터 numpy 패키지를 통해 계산한 값과 직접 하드코딩으로 계산한 값의 차이가 나타나기 시작했다. 이에 대한 힌트를 찾기 위해 numpy github를 찾아봤다.

- np.linalg.norm(x, ord=p)의 처리방식을 비교하여 이 문제를 해결할 수 있었다.

https://github.com/numpy/numpy/blob/d35cd07ea997f033b2d89d349734c61f5de54b0d/numpy/linalg/linalg.py#L2536

numpy 내부 방식

위 데이터는 정수형으로 저장되어 있어서 Python에서 정수형 데이터와 실수형 데이터의 처리 방식이 달라서 발생한 문제였다.

# x와 p를 바꾸어가며 norm 값이 어떻게 변하는지 실험해봅시다!
# --------------------------- #
x = np.array([1,10,1,1,1])
x = x.astype('float')
p = 19
# --------------------------- #

norm_x = np.linalg.norm(x, ord=p)
making_norm = (sum(x**p))**(1/p)
print("result of numpy package norm function : %0.5f "%norm_x) 
print("result of making norm : %0.5f "%making_norm)

위 처럼 코드를 수정하면 문제없이 같은 값으로 나오는 것을 볼 수 있다.

 

하지만 위 해결 방식에서 든 의문점이 있었다.

파이썬의 정수는 크기의 제한이 없다.

import sys

t1 = sys.maxsize
t2 = sys.maxsize + 1 #이것도 type int

print(t1)
print(t2)
print(type(t1))
print(type(t2))

 

- 파이썬은 임의 정밀도 방식을 채택하여 값이 커질 때마다 4비트씩 메모리를 더 할당하여 더해줌으로써 메모리가 허용하는 최대한의 정수를 표현할 수 있게 한다.

위 처럼 2 ** 30을 기준으로 4비트씩 크기가 커지는 것을 확인할 수 있다.

그렇다면 본질적인 문제는 무엇인가?

- 따라서 실제로 값을 계산 해보았고, 이 문제는 numpy 에서 일어나는 문제임을 알게되었다.

numpy array 내부의 문제였다!
2개의 type이 다르게 나온다

 

 

위의 개념을 토대로 새로운 해결방안을 하나더 도출 할 수 있게 되었다.

  • np.array의 type을 object로 바꾸어도 문제가 해결된다.

object 타입을 쓰면 python의 int형을 사용할 수 있게 된다


결론

위 문제는 NumPy 배열을 사용할 때 발생한 int64 자료형의 오버플로우 문제라고 볼 수 있다.

NumPy 배열에서는 기본적으로 고정 크기 정수형 (int32, int64 등)을 사용하므로, 이러한 자료형의 범위를 초과하는 큰 수를 연산할 때 오버플로우가 발생할 수 있다.

 

NumPy의 정수형 오버플로우 문제

NumPy는 내부적으로 C 언어의 배열 구조를 사용하므로, 데이터 타입의 크기가 고정되어 있다.

int64의 경우, 64비트 정수로 표현 가능한 값의 범위는 다음과 같다:

  • 최소값: -2^63 (-9223372036854775808)
  • 최대값: 2^63 - 1 (9223372036854775807)

이를 넘는 값을 계산할 때 오버플로우가 발생한다.


문제 해결 방법

 

1. numpy.float64 사용하기

부동 소수점 타입으로 변환하면, 큰 수를 다룰 때 오버플로우 문제를 피할 수 있다.

하지만 근사값이 사용되기 때문에 일부 정밀도가 손실될 수 있다.

import numpy as np

x = np.array([1, 10, 1, 1, 1], dtype=np.float64)

p = 19

norm_x = np.linalg.norm(x, ord=p)
making_norm = (sum(x**p))**(1/p)
print(f"Result of numpy package norm function: {norm_x:.5f}")
print(f"Result of homemade norm: {making_norm:.5f}")

 

2. object 데이터 타입 사용하기

Python의 무제한 정수형을 NumPy 배열에서 사용하려면, 데이터 타입을 object로 설정하면 된다.

이렇게 하면 NumPy는 Python의 무제한 정수를 사용할 수 있게 된다.

import numpy as np

x = np.array([1, 10, 1, 1, 1], dtype=object)
p = 19

# NumPy의 무제한 정수형을 이용한 연산
norm_x = np.linalg.norm(x, ord=p)
making_norm = (sum(x**p))**(1/p)

print(f"Result of numpy package norm function: {norm_x:.5f}")
print(f"Result of homemade norm: {making_norm:.5f}")

이 코드를 사용하면, 무제한 정수형을 사용하여 계산할 수 있으므로 오버플로우 문제가 발생하지 않는다.

 

요약

  • NumPy 배열에서 int64 타입은 고정된 크기를 가지므로, 큰 수의 연산에서 오버플로우 문제가 발생할 수 있다.
  • 이를 해결하기 위해 부동 소수점 타입 (np.float64) 또는 object 타입을 사용하여 무제한 정수형 연산을 수행할 수 있다.
  • object 타입을 사용하면 Python의 무제한 정수형을 활용할 수 있어, 큰 수의 계산에서도 정밀도를 유지할 수 있게 된다.

이러한 방법을 통해, NumPy 배열을 사용할 때 큰 수의 연산 문제를 쉽게 해결할 수 있다.


회고

  • float로 변환하기만 하는 쉬운 해결방법이였지만 그 속에 담긴 구체적인 의미를 풀어내다 보니 많은 걸 배울 수 있었다
  • float의 부동소수점 이야기까지 찾아보게 되었는데 추후 그와 관련한 이야기도 정리해봐야겠다

 


참고자료

- numpy github : https://github.com/numpy/numpy/blob/d35cd07ea997f033b2d89d349734c61f5de54b0d/numpy/linalg/linalg.py#L2536

- https://kevinitcoding.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%ACPython-%EC%A0%95%EC%88%98Integer-1%ED%83%84-%EC%9E%84%EC%9D%98-%EC%A0%95%EB%B0%80%EB%8F%84%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC

728x90