Python Numpy

ROK·2022년 1월 6일
2

AIFFEL

목록 보기
4/17

Numpy

Numpy는 Numerical Python의 약자, 과학 계산용 고성능 컴퓨팅과 데이터 분석에 필요한 파이썬 패키지

기본 패키지 관리자인 pip에는 없어서 따로 설치해야 한다. conda를 사용한다면 기본적으로 포함 되어 있기도 하므로 conda list | grep numpy로 설치 여부를 먼저 확인한다.

설치 명령어 pip install numpy

Numpy 공식 사이트에 소개된 Numpy의 장점

  • 빠르고 메모리를 효율적으로 사용하여 벡터의 산술 연산과 브로드캐스팅 연산을 지원하는 다차원 배열 ndarray 데이터 타입을 지원한다.
  • 반복문을 작성할 필요 없이 전체 데이터 배열에 대해 빠른 연산을 제공하는 다양한 표준 수학 함수를 제공한다.
  • 배열 데이터를 디스크에 쓰거나 읽을 수 있다. (파일로 저장한다)
  • 선형대수, 난수발생기, 푸리에 변환 가능, C/C++ 포트란으로 쓰여진 코드를 통합한다.

Numpy를 사용하기 위해 ndarray객체를 생성해야 한다. ndarray 객체를 이용하면 파이썬에서 사용하는 대규모 데이터 집합을 n차원 배열로 담을 수 있다. ndarray를 그냥 array라고 부르기도 한다.

ndarray 만들기

ndarray 객체는 arange()array([])로 만들 수 있다.

import numpy as np

# 아래 A와 B는 결과적으로 같은 ndarray 객체를 생성한다.
A = np.arange(5)
B = np.array([0,1,2,3,4]) 	# 파이썬 리스트를 numpy ndarray로 변환
C = np.array([0,1,2,3,'4']) # A,B와 다른 형태
D = np.ndarray((5,), np.int64, np.array([0,1,2,3,4]))
# A,B와 같은 결과를 내지만, B의 방법을 권장한다.

print(A)
print(type(A))
print("---------------------")
print(B)
print(type(B))
print("---------------------")
print(C)
print(type(C))
print("---------------------")
print(D)
print(type(D))
print("---------------------")

출력
[0 1 2 3 4]
<class 'numpy.ndarray'>
--------------------------
[0 1 2 3 4]
<class 'numpy.ndarray'>
--------------------------
['0' '1' '2' '3' '4']
<class 'numpy.ndarray'>
--------------------------
[0 1 2 3 4]
<class 'numpy.ndarray'>

C는 '4'가 하나 다를 뿐인데 0,1,2,3이 모두 문자열로 바뀌었다.
numpy.ndarray도 array이므로 모든 요소의 자료형이 동일해야 한다.
(파이썬의 유연함 : 문자열을 모두 숫자로 바꿀 수 는 없지만 숫자를 모두 문자열로 바꿀 수는 있다.)
그래서 숫자만 있던 ndarray에 문자열이 들어가면 모든 숫자를 문자열로 해석해서 array의 요건을 맞춰준다.

크기 (size, shape, ndim)

  • ndarray.size
  • ndarray.shape
  • ndarray.ndim
  • reshape()

size, shape, ndim은 각각 행렬 내 원소의 개수, 행렬의 모양, 행렬의 축(axis)의 개수를 의미한다.
reshape() 메서드는 행렬의 모양을 바꿔준다. 주의할 점은 모양을 바꾸기 전,후의 행렬의 총 원소 개수(size)가 맞아야 한다.

A = np.arange(10).reshape(2,5)	 # 길이 10의 1차원 행렬을 2X5 2차원 행렬로 바꿔본다

print("행렬의 모양:", A.shape)
print("행렬의 축 개수:", A.ndim)
print("행렬 내 원소의 개수:", A.size)

2X5 행렬은 원소 10개와 행렬의 모양이 맞지만, 3X3으로 reshape하면 모양과 원소의 개수가 맞지 않아 에러가 난다

A = np.arange(10)
print('A: ', A)
B = np.arange(10).reshape(2,5)
print('B: ', B)
C = np.arange(10).reshape(3,3)
print('C: ', C)

C에서 에러가 발생했다.
ValueError: cannot reshape array of size 10 into shape (3,3)

type

Numpy 라이브러리 내부의 자료형들은 파이썬 내장함수와 동일하지만 헷갈리는 기능이 있다. 바로 type()dtype() 메서드이다.

  • Numpy : numpy.array.dtype
  • 파이썬 : type()

예시

A= np.arange(6).reshape(2, 3)
print(A)
print(A.dtype)
print(type(A))
print("-------------------------")

B = np.array([0, 1, 2, 3, 4, 5])  
print(B)
print(B.dtype)
print(type(B))
print("-------------------------")

C = np.array([0, 1, 2, 3, '4', 5])
print(C)
print(C.dtype)
print(type(C))
print("-------------------------")

D = np.array([0, 1, 2, 3, [4, 5], 6])  # 이런 ndarray도 만들어질까요?
print(D)
print(D.dtype)
print(type(D))

Numpy의 원소는 꼭 동일한 데이터 type이어야 하며, Numpy의 메서드 dtype은 Numpy ndarray의 원소 데이터 타입을 반환한다. 반면 파이썬 내장함수 type(A)을 사용하면 행렬 A의 자료형이 반환된다.

주의 깊게 볼 점은 D의 경우이다
원소 하나를 list로 바꿨는데, Numpy의 원소는 돌일한 데이터 type이어야 하므로 에러가 발생할 줄 알았지만 정상적으로 ndarray가 만들어졌다.
대신 D.dtypeobject를 리턴했다. object는 파이썬 최상위 클래스로 Numpy는 dtypeobject로 지정해서라도 행렬 내 dtype을 일치시켰다. 이후에 이런 상황을 방지하기 위해 이 기능을 deprecate 시켰다.

deprecate는 더 이상 관리하지 않는 기능을 뜻한다. 메서드를 deprecate 할 예정일 때는 기능은 성공적으로 실행시키고 error가 아닌 warning 문구로 경고만 해주는데, 이는 그 기능을 더 이상 못 쓰는 것은 아니지만 이제부터 관리하지 않으니 오류가 날 확률이 높다는 뜻이다.
일반적으로는 코드 수정 방향을 알려주기 때문에 메시지를 잘 읽어보는 것이 좋다.

출력문에 적힌대로 아래와 같이 수정한다. 직접적으로 dtype을 입력해 오류를 방지한다.

D = np.array([0,1,2,3,[4,5],6], dtype=object)
print(D)
print(D.dtype)
print(type(D))

행렬 내부 원소 확인, Numpy가 행렬 내부의 원소의 data type을 실제로 변경하는지 확인

C = np.array([0,1,2,3,'4',5])
print(C[0])
print(type(C[0]))
print(C[4])
print(type(C[4]))
print("------------------------------")

D = np.array([0,1,2,3,[4,5],6], dtype=object)
print(D[0])
print(type(D[0]))
print(D[4])
print(type(D[4]))

C[0]는 행렬 안에 문자열 '0'으로 들어가 있지만, C[0]의 숫자 0은 행렬안에 정수 0으로 들어가 있다.

Numpy는 아주 똑똑하다. 어떤 경우라도 Array 답게 연산속도를 최적화하도록 원소들을 관리하지만, 필요에 따라 효율적인 방법으로 data type을 변화시켜 관리하고 있다.

특수 행렬

Numpy는 수학적으로 의미가 있는 행렬들을 함수로 제공하고 있다.

  • 단위행렬 : eye()
  • 0 행렬 : zeros()
  • 1 행렬 : ones()

코드 예시

# 단위행렬
np.eye(3)

# 0 행렬
np.zeros([2,3])

# 1 행렬
np.ones([3,3])

브로드 캐스트

Numpy의 강력한 연산 기능 중 하나인 브로드캐스트(broadcast)연산
ndarray 객체에 상수 연산을 하면 각각의 원소에 어떻게 연산이 적용되는지 확인한다.

A = np.arange(9).reshape(3,3)
A

A * 2

A + 2

일반적인 연산 결과가 나오는 것을 알 수 있다.

A = np.arange(9).reshape(3,3)

# 3X3 행렬에 1X3 행렬을 더한다
B = np.array([1,2,3])
print("A : ", A)
print("B : ", B)
print("\nA+B : ", A+B)

# 3X3 행렬에 3X1 행렬을 더한다
C = np.array([[1], [2], [3]])
print("A : ", A)
print("C : ", C)
print("\nA+C : ", A+C)

# 3X3 행렬에 1X2행렬을 더할 수는 없다.
D = np.array([1,2])
print("A : ", A)
print("D : ", D)
print("\nA+D : ", A+D) # 에러 발생

결과값을 보면 직관적으로 알 수 있다. 이해가 어렵다면 Numpy.org에서 제공하는 설명자료 참고

ndarray와 상수, 또는 서로 크기가 다른 ndarray끼리 산술연산이 가능한 기능을 브로드캐스팅이라고 한다. Numpy가 파이썬 내장 리스트와 구별되는 큰 특징 중 하나.
둘의 차이를 아래 예시를 통해 비교해본다.

print([1,2]+[3,4])
print([1,2]+3)

print(np.array([1,2])+np.array([3,4]))
print(np.array([1,2])+3)

슬라이스와 인덱싱

Numpy도 파이썬 내장 리스트와 비슷한 스라이스와 인덱싱 연산을 제공한다

# 3X3 행렬의 첫 번째 행을 구한다
A = np.arange(9).reshpae(3,3)
print("A:", A)
B = A[0]
print("B:", B)

# 0, 1을 인덱싱하면 A의 첫 번째 행에서 두 번째 값을 참조한다
# 두 결과는 같다
print(A[0,1])
print(B[1])

# 슬라이싱도 비슷한 결과를 보여준다
A[:-1]

위 그림과 같이 배열의 맨 오른쪽 열을 슬라이싱하고 싶다면

# 1번
print(A[:,2:])
print("--------------")
print(A[:,1:])
print("--------------")
print(A[:,:])
print("--------------")

# 2번
print(A[:,-1:])
print("--------------")
print(A[:,-2:])
print("--------------")
print(A[:,-3:])

# 두 방법 모두 같은 값을 출력

슬라이싱이 어렵다면 계속해서 연습해본다

A[1,:2]

A[:2, 1:]

A[:,-1]

random

Numpy에서도 다양한 난수를 의사 난수를 지원하고, 자주 쓰인다.
주로 많이 쓰이는 함수

  • np.random.randint()
  • np.random.choice()
  • np.random.permutation()
  • np.random.normal()
  • np.random.uniform()
    대표적인 예시이며 np.random 패키지 안에는 엄청나게 많은 기능이 내장되어 있다.

의사 난수 생성 예시

print(np.random.random()) 		# 0~1 사이의 실수형 난수 하나를 생성
print(np.random.randint(0,10)) 	# 0~9 사이 1개 정수형 난수 하나를 생성
print(np.random.choice([0,1,2,3,4,5,6,7,8,9])) 	# 리스트에 주어진 값 중 하나를 랜덤하게 고른다

# 무작위로 섞인 배열을 만들어준다, 밑에 두 값은 동일
print(np.random.permutation(10))
print(np.random.permutation([0,1,2,3,4,5,6,7,8,9]))

# 어떤 분포를 따르는 변수를 임의로 표본추출해준다

# 정규분포
print(np.random.normal(loc=0, scale=1, size=5))		# 평균(loc), 표준편차(scale), 추출 개수(size)

# 균등분포
print(np.random.uniform(low=1, high=1, size=5)) 	# 최소(low), 최대(high), 추출개수(size)

전치행렬

행렬의 행과 열을 맞바꾸기, 행렬의 축을 서로 바꾸기 등에 사용되는 기능

  • arr.T : 행렬의 행과 열 맞바꾸기
  • np.transpose : 축을 기준으로 행렬의 행과 열 바꾸기
A = np.arange(24).reshape(2,3,4)
print("A : ", A)		# A는 (2,3,4) 모양의 행렬
print("A의 전치행렬 : ", A.T)
print("A의 전치행렬의 shape : ", A.T.shape)	# A의 전치행렬은 (4,3,2) 모양의 행렬

# np.transpose는 행렬의 축을 어떻게 변환해 줄지 임의로 지정해 줄 수 있다.
B = np.transpose(A, (2,0,1))
print("A : ", A)		# A는 (2,3,4) 모양의 행렬
print("B : ", B)		# B는 A의 3,1,2 번째 축을 자신의 1,2,3번째 축으로 가진 행렬
print("B.shape : ", B.shape)	# B는 (4,2,3) 모양의 행렬

# np.transpose(A, (2,1,0))은 A.T와 같다

print("B.shape : ", B.shape)

Numpy로 기본 통계 데이터 계산해 보기

Numpy의 주요 기능 중 하나인 통계 데이터 계산 기능을 이용해 본다.
Numpy에는 많은 통계 관련 수식을 함수로 제공해주는데, 함수들을 이용해 평균, 표준편차, 중앙값을 계산하면 예시와 같이 나타낼 수 있다.

import numpy as np

def numbers():
	X = []
   	number = input("Enter a number (<Enter key> to quit)")
   	while number != "":
   		try:
			x = float(number)
            X.append(x)
		except ValueError:
			print('>>> Not a Number! Ignored...')
		number = input("Enter a number (<Enter key> to quit)")
	return X
    
def main():
	nums = numbers()		# 파이썬 리스트
   	num = np.array(nums)	# 리스트를 Numpy ndarray로 변환
	print("합", num.sum())
   	print("평균값", num.mean())
   	print("표준편차", num.std())
   	print("중앙값", np.median(num))	# num.median()이 아님
    
main()

데이터 행렬 변환

Numpy를 이용하여 다양한 데이터를 표현하는 방법을 정리한 블로그

위 블로그에는 다양한 데이터들을 행렬 변환 하는 방법에 대해서 자세하게 설명되어 있다
그 중 이미지를 행렬변환하는 연습을 해본다.

픽셀과 이미지

디지털로 표현되는 이미지는 수많은 점으로 구성되어 있다. 이 점을 픽셀이라고 부른다

아래 이미지는 동일한 사이즈의 이미지에 대해 픽셀이 1개(1x1)일 때부터 10,000개(100x100)일 때 이미지의 해상도를 보여준다

이미지와 픽셀의 관계

  • 이미지는 수많은 점(픽셀)들로 구성되어 있다.
  • 각각의 픽셀은 R,G,B 3개 요소의 튜플로 색상이 표시된다 (RGB = Red,Green,Blue)
    • 흰색(White) : (255,255,255)
    • 검정색(Black) : (0,0,0)
    • 빨간색(Red) : (255,0,0)
    • 파란색(Blue) : (0,0,255)
    • 녹색(Green) : (0,255,0)
    • 노란색(Yellow) : (255,255,0)
    • 보라색(Purple) : (128,0,128)
    • 회색(Gray) : (128,128,128)
  • 흑백 이미지의 경우 Gray 스케일로 나타내는데, 0~255 범위 숫자 1개의 튜플 값이다.
  • Color는 투명도를 포함하는 A(alpha)를 포함해 RGBA 4개로 표시하기도 한다.
  • 아래 그림처럼 Image의 좌표는 일반적으로 왼쪽 위를 (0,0)으로 표시하고, 오른쪽과 아래로 내려갈 수록 좌표가 증가한다.

이미지와 관련된 파이썬 라이브러리

  • matplotlib
  • PIL

이미지 데이터를 처리하기 위해 추가로 matplotlib와 PIL 라이브러리를 이용한다.
이 두 라이브러리는 이미지 파일을 열고, 자르고, 복사하고, RGB 색상 값을 가져오는 등 이미지 파일과 관련된 몇 가지 작업을 빠르게 수행한다.
이렇게 처리한 파일을 Numpy를 이용해 행렬로 빠르게 연산해서 이미지를 더 빠르게 작업한다.

import matplotlib as mpl
import PIL

print(f'# matplotlib : {mpl.__version__}')
print(f'#PIL: {PIL.__version__}')

# 버전 확인

간단한 이미지 조작

예제를 연습하면서 익힌다.
이미지 조작에 쓰이는 메서드

  • open : Image.open()
  • size : Image.size()
  • filename : Image.filename
  • crop : `Image.crop((x0, y0, xt, yt))
  • resize : `Image.resize((w,h))
  • save : Image.save()

open
자유의 여신상 파일을 예제로 연습한다.

from PIL import Image, ImageColor
import os
img_path = "newyork.jpg"
img = Image.open(img_path)
print(img_path)
print(type(img))
img

Image.open()이라는 메서드를 이용해 이미지 파일을 open했다. 이렇게 얻은 오브젝트 imgPIL.JpegImagePlugin.JpegImageFile 타입을 가지고 있다.

size
계속 해서 관련 메서드를 사용해본다

img.size		# 이미지 사이즈 가로X세로가 튜플 값으로 반환

W, H = img.size
print((W, H))

print(img.format)		# 이미지 파일의 타입(jpeg, png 등)
print(img.mode)			# 색상 정보 결과 : RGB

**이미지 자르기**
이미지를 자를 때에는 `.crop()` 메서드를 이용한다. 인자는 튜플값으로 입력, 가로 세로의 시작점과 가로, 세로의 종료점 총 4개를 입력한다.

```python
img.crop((30, 30, 100, 100))

사진이 잘려서 출력됨

저장
자른 이미지를 저장해본다.

# 새로운 이미지 파일명
cropped_img_path = "cropped_img.jpg"
img.crop((30,30,100,100)).save(cropped_img_path)
print("저장 완료")

저장이 잘 됐는지 확인해 본다
리눅스 명령어
$ ls ~/저장한 위치/cropped_img.jpg

행렬로 변환
이미지 파일을 행렬로 변환하기

import numpy as np
img_arr = np.array(img)
print(type(img))
print(type(img_arr))
print(img_arr.shape)
print(img_arr.ndim)

참고 : 위에서 img는 리스트 타입이 아니라 PIL.JpegImagePlugin.JpegImageFile이라는 타입을 가지고 있는데 어떻게 np.array(img)가 정상적으로 작동했는가?
PIL.JpegImagePlugin.JpegImageFilePIL.Image.Image라는 클래스를 상속받은 타입이다.(참고)
PIL.Image.Image 클래스는 리스트를 상속받지는 않았지만 __array_interface__라는 속성이 정의되어 있어 Pillow라이브러리는 이미지를 Numpy ndarray로 변환 가능하다.

컬러이미지 파일이기 때문에 변환된 행렬은 < Height X Width X RGB Channel >의 모양이며, 3차원의 형태이다.

행렬의 값 확인

img_arr

흑백 모드
컬러 이미지 파일을 열 때, 흑백 모드로 사진을 열 수도 있다. Image.open().convert('L')로 모드를 조정할 수 있다. (Pillow의 이미지 처리 옵션에 대한 상세한 정보

img_g = Image.open(img_path).convert('L')
img_g

행렬로 변환하는 방법은 위와 동일하다

img_g_arr = np.array(img_g)
print(type(img_g_arr))
print(img_g_arr.shape)
print(img_g_arr.ndim)

img_g_arr

단, 흑백이므로 반환된 행렬은 < Height X Width > 2차원이다.

Get Color
색상값은 RGB로 표현되는데 getcolor()는 각 색상이 어떤 RGB값을 가지는지 보여준다.

red = ImageColor.getcolor('RED', 'RGB')
reda = ImageColor.getcolor('red', 'RGBA')
yellow = ImageColor.getcolor('yellow', 'RGB')
print(red)
print(reda)
print(yellow)

딥러닝에서 이미지 조작은 Data augmentation을 할 때 많이 사용된다.
Data augmentation은 말 그대로 데이터를 증강하는 것으로 딥러닝에서 데이터의 개수를 늘릴 때 사용하는 기법이다. 참고

profile
하루에 집중하자

0개의 댓글