의료 AI(딥러닝) 공부 일기

Ch 프롤로그2. numpy & matplotlib

ignuy 2024. 7. 4.

Numpy Tutorial

Numerical Python의 줄임말. 파이썬에서 산술 계산을 위한 가장 중요한 패키지 중 하나이다. 수학, 과학 계산을 위한 대부분의 패키지는 Numpy의 배열 객체를 데이터 교환을 위한 공통 언어처럼 사용한다.

ndarray

N차원의 배열 객체. 대규모 데이터 집합을 담을 수 있는 빠르고 유연한 자료구조.

파이썬의 list와 비교가 자주 되는데 ndarray는 항상 같은 타입의 데이터들만 들어갈 수 있다.

물론 파이썬의 list와 마찬가지로 ndarray도 iteration 가능하다.

객체에 바로 .append를 지원하지 않음. 데이터 하나 추가하고 싶으면 numpy의 인터페이스 활용해야 한다. 

사칙 연산도 체계가 다르다. ndarray의 경우 원소가 추가되는 것이 아닌 broadcasting이 일어나서 모든 원소에 대해 5를 더하는 꼴이 되어버림(broadcasting에 대한 자세한 설명은 후술).

ndarray를 생성하는 방법은 여러가지이다. 상황에 맞게 사용할 수 있도록 사용법을 익혀두도록 하자.

  • np.array()
  • np.asarray() ← 결과로 반환된 ndarray가 파라미터와 같은 곳을 참조함
  • np.zeros((3, 4)) ← 0으로 채워진 3행 4열의 행렬
  • np.ones((5,2)) ← 1로 채워진 5행 2열의 행렬
  • np.full((2,3), 4) ← 4로 채워진 2행 3열의 행렬
  • np.empty((3,3)) ← 3행 3열 행렬의 메모리 공간만 마련함
  • np.zeros_like(a) ← a 행렬의 모양만 가져오고 0으로 채운 행렬
  • np.ones_like(a)
  • np.full_like(a, 2)
  • np.empty_like(a)
  • np.eye(3)
  • np.identity(3) ← eye와 마찬가지로 3행 3렬의 항등행렬을 만듦(대각선 방향만 1인 행렬)

indexing과 slicing

배열을 자르거나 가져올 때, indexing과 slicing을 사용할 수 있다. 둘 다 가능하지만 중요한 차이점이 있으니 명심하자.

indexing을 사용하면 항상 랭크가 감소한다. 변면 slicing은 차원이 유지된다.

b 객체의 shape을 3행 4열로 고친 줄이 주석 처리되어 있는데 실제 실행 결과엔 반영되어 있다. 몇개 연습하다가 주석처리한게 스크린샷이 찍히고 말았다ㅠㅠ.

위 예시처럼 3행 4열의 b 배열 객체를 indexing을 통해 b[1]을 print해보면 shape이 (4,)인 배열 객체가 만들어지고 slicing으로 b[1:2]을 print해보면 shape이 [1,4]인 배열 객체가 만들어진 것을 볼 수 있다. indexing과 slicing은 배열의 일부분을 가져올 때 배열의 " rank 변동"이라는 차이점이 있음에 유의하자.

indexing과 slicing을 같이 사용해도 마찬가지이다. indexing의 갯수만큼 랭크가 감소한다. 아래 예시를 참고하자.

Boolean indexing

boolean 조건식으로 배열을 indexing할 수 있다. 아래 예시와 같은 활용이 가능하다. 단, c[bool_idx]는 차원을 무시하면서 나오는 것을 주의하자. 

Fancy indexing

정수 인덱싱이라고도 한다. 아래처럼 3행, 5행, 1행, 0행을 차례대로 가져온다. 

더 고차원적으로 활용할 수도 있다. 아래처럼 1행0열, 5행3열, 7행1열, 2행2열을 가져오는 동작도 가능하다.

Transpose

축을 변경하는 동작이다.

shape이 (2, 2, 4)인 f 배열에 f.transpose(1, 0, 2)과정을 수행하면 1번축, 0번축, 2번축으로 축 순서를 재구성하라는 뜻이 된다. 동일한 동작을 swapaxes로도 할 수 있다. swapaxes(0 ,1)은 0번 축과 1번 축을 바꾸라는 의미이다.

물론, 조금 더 범용적으로 쓰일 수 있는 것은 transpose이다. tranospose(2, 1, 0)한다면 f 배열의 shape이 (4,2,2)로 달라지게 된다.

런 기능을 왜쓰냐?
tensorflow, pytorch 등의 라이버리리들은 이미지 같은 데이터를 표현할 때 순서가 다르다. 텐서플로는 찻번째 차원이 이미지의 세로 길이이고, 두번째 차원이 가로 길이, 그 다음에 채널 RGB 등이 오는데 파이토치는 채널이 먼저오고 그 다음 height, 그 다음 width 등이 된다.
여러 라이브러리를 동시에 활용하면서 호환을 맞추려면 축을 반드시 바꾸어 주어야 한다.

Numpy 연산 동작

간단한 연산을 연습해 보자. 아래와 같이 np.add(), np.divide() 같은 함수를 universal function이라고 부른다.

덧셈, 뺄셈, 곱셈, 나눗셈 모두 해당하는 자리의 엘리먼트끼리 진행하게 된다.

그렇다면 행렬 곱(matrix multiplication)은 어떻게 표현할까?

matmul(@)를 사용한다.

추가적으로 도움이 되는 연산을 알아보자. 행렬의 모든 원소를 더하기 위해서 sum()을 사용한다.

원소를 더할 때, 축을 선택할 수도 있다. 이때 축(axis)은 각 축에 해당되는 index이다. axis = 0에 대해 sum을 하는 것은 0번 축 혹은 차원이 없어지는 방향으로 원소들을 모두 더하라는 이야기이다. 즉, 위 예시에서 sum1의 경우, z[0,:] + z[1,:]의 연산을 하라는 의미이고, sum2와 sum3의 경우 z[:,0]+z[:,1]+z[:,2]+z[:,3]+z[:,4]의 연산을 하라는 의미가 된다.

Broadcasting

numpy가 산술 연산을 수행할 때, 다른 모양의 배열로 작업할 수 있게 해주는 강력한 메커니즘이다. 브로드캐스팅을 의도하지 않았는데 일어나서 의도된 계산 결과와 다르게 나올 수 있으므로 주의해야 한다. Broadcasting을 지원하는 함수들을 보편 함수라고 한다.

종종 더 작은 배열과 더 큰 배열이 있을 때 더 작은 배열을 여러번 사용하여 더 큰 배열에서 어떤 연산을 수행하기를 원할 때가 있다. 예를 들어, '행렬의 각 행에 상수 벡터를 추가하려고 한다' 가정하면 numpy에서는 다음과 같이 작성한다.

z = x + y는 broadcasting으로 인해 x가 shape이 (4,3)이고 y가 shape이 (3) 인데도 마치 y가 shape이 (4,3)인 것처럼 작동한다. 각 행은 y의 사본이고, 합계는 요소별로 수행되는 것처럼 보인다.

두 개의 배열을 브로드캐스팅하는 것은 다음 규칙을 따른다.

1. 배열의 랭크가 같지 않으면 두 모양이 같은 길이가 될 때까지 배열의 낮은 랭크쪽에 1을 붙인다.
2. 두 배열은 차원에서 크기가 같거나 배열 중 하나의 차원에 크기가 1 인 경우 차원에서 호환 가능하다고 판단한다.
3. 배열은 모든 차원에서 호환되면 함께 broadcast 될 수 있다.
4. Broadcast 후 각 배열은 두 개의 입력 배열의 요소 모양 최대 개수와 동일한 모양을 가진 것처럼 동작한다.
5. 한 배열의 크기가 1이고 다른 배열의 크기가 1보다 큰 차원에서 첫 번째 배열은 마치 해당 차원을 따라 복사 된 것처럼 작동한다.

 

실제로 동작하는 방식을 따라가보자.

  1. A와 B의 모양을 생각한다.
  2. 두 배열이 len(A.shape) == len(B.shape)인지 확인을 한다.
  3. 같지 않은 경우에는 두 배열의 모양 길이가 같아질때까지 적은 쪽의 shape 앞에 1 을 추가해준다.
    • 예: (5,3)–>(1,5,3)
  4. shape이 1인 곳은 복사가 된다.
  • 예: shape의 변화는 아래와 같다.
    • (5, 3)+(3,)
    • (5, 3)+(1, 3)
    • (5, 3) + (5, 3)
    • (5, 3)

아래 예시를 보자.

x는 shape이 (1,3), y는 shape이 (1,2)이다. 더하기 연산을 수행하는데 broadcasting 조건에 맞지 않아 에러를 던졌다.

  1. rank가 같은지 비교한다. ⇒ 같다.
  2. 각 축의 값이 같은지 비교한다. ⇒ 1은 같으니까 넘어가고, 3과 2를 비교. 다르면? 둘중의 하나가 1인지 봐야한다. 1이면 복사하면 된다. 1이 아니면 연산에 실패한다.

y의 0번째 축과 1번째 축을 바꾸어서(swapaxes(0, 1)) shape을 (2,1)로 만들어보고 다시 더하기 연산을 해보자.

  1. rank가 같은지 비교한다. ⇒ 같다.
  2. 각 축의 값이 같은지 비교한다. ⇒ 2랑 1을 비교한다. 한쪽이 1 이니까 복사해서 2처럼 흉내낸다. 다음 랭크도 3과 1을 비교한다. 한쪽이 1이니까 복사해서 3처럼 흉내낸다.
  3. 둘다 2행 3열이 됐으며 원소끼리 더하기를 수행한다. ⇒ 결과는 따라서 shape이 (2,3)인 배열 객체이다.

딥러닝에서는 상당히 고차원의 데이터를 다룰 일이 많기 때문에 shape에 주의해야한다. tensorflow나 pytorch 등 라이브러리 대부분이 broadcasting을 지원하기 때문에 반드시 명심하자. 모르면 꼼짝없이 당한다.

Shape 변경

아래 예시를 살펴보자. a를 reshape(4,-1)하면 shape이 (4,6)인 배열이 나온다. 원소간의 순서는 바뀌지 않고 shape만 바뀐다.

차원을 하나 더 추가하고 싶으면?

“앞에 전부 다”라는 의미로  을 쓰고 np.newaxis 와 함께 붙여서 배열의 shape을 고쳐주면 새로운 차원이 하나 더 생성된다. 동일한 방법으로 아래처럼 np.expand_dims()를 통해서 차원을 늘릴 수 있다. 이 인터페이스는 axis의 인덱스를 적고 몇번째 차원에 새로운 축을 넣을 건지 선택할 수 있다.

Concatenate & Stack

배열을 concatenate를 활용해서 아래처럼 냅다 합칠 수도 있다. 이 때도 "axis" argument 를 통해 몇번째 차원에 합쳐버릴 것인지 선택할 수 있다.

비슷한 연산을 하는 것으로 stack도 있다.

concatenate와 유사한 동작을 하는데 마지막 축(aixs = -1)에 대해서 a와 b 배열을 합쳐달라는 의미로 함수를 사용하면 shape이 (1,4,6,2)인 배열이 나온다. 즉, 새로운 차원을 만들고 거기에 가져다 붙인다.

아래 두번째 예시처럼, 첫번째 축에 새로운 차원을 만들고 거기에 배열을 붙이는 작업도 물론 가능하다. 차원이 변동되는것이 concatenate와 stack의 가장 큰 차이점이다.

 

Matplotlib Tutorial

Matplotlib은 Python에서 사용할 수 있는 시각화 라이브러리로 주로 2D 그래프를 위한 패키지이다. 파이썬에서 매트랩과 유사한 인터페이스를 지원하기 위해 시작되었고, IPython과 협력으로 대화형 시각화를 지원한다.

matplotlib을 import 하고 본격적으로 시작해보자. 우리는 matplotlib 패키지 중에서도 주로 pyplot을 사용할 것이다.

위처럼 2차원 그래프를 자동으로 그려준다.

엥? np.arange(10)은 shape이 (1,)인데 어떻게 x축과 y축 상의 2차원 그래프로 나오는거지?
        ⇒ 자동으로 들어온 데이터가 y값으로 그려지게 되고 x는 인덱스에 맞게 자동으로 할당된다.

 

matplotlib에서 그래프는 figure라는 큰 틀을 만들고 그 안에서 ax라는 subplot을 추가해서 만든다.

위처럼 figure instance를 생성하면 임의의 사이즈를 가진 인스턴스가 만들어지고 add_subplot을 하여 show()를 실행하면 빈 subplot이 만들어진다. figure의 크기가 마음에 안든다면, figsize를 통해 사이즈를 조절할 수 있다.

 

하나의 figure에 여러 개의 subplot을 생성할수도 있다. subplot의 알규먼트는 총 몇개의 subplot이 들어가는지, 어디에 위치시킬 건지를 의미하게 된다. fig.add_subplot(2,2,1)의 경우 2행 2열로 figure에 subplot을 넣을 거고 해당 subplot은 첫번째 것임을 의미한다.

matplotlib의 인덱스는 0부터 시작하지 않음에 주의하자.

 

그렇게 그래프를 그리는 가장 기본적인 방법은 아래와 같다.

plt.plot은 제일 최근에 생성된 subplot에 그래프를 그리도록 되어있다. 즉, subplot을 쭉 만들어두고 상황에 맞게 그래프를 그리는 작업을 수행하지 못한다는 의미이다. 좀 불편하다. 이를 극복하기 위해 subplot의 instance를 ax1, ax2 등에 받고 ax.plot()을 수행할 수 있다.

plot 종류별 사용법

bar plot

bar plot은 ax.bar와 같이 사용하며 다음과 같은 인자를 받는다.

  • x: x축에 사용될 값
  • height: y축에 사용될 값
  • width: 각 bar의 width
  • bottom: y축의 base 값
  • align: 정렬(center or edge)

histogram plot

histogram plot은 data의 분포를 나타낼 때 사용하며 주로 다음과 같은 인자를 받는다.

  • x: 입력 data
  • bins: histogram을 나타낼 x축의 구간(정수 입력시 구간의 갯수, list와 같은 sequence 입력시는 sequence 값이 구간으로 설정됨)
  • range: x축 범위 (x.min, x.max)로 표현
  • density: 확률 분포로 나타낼 것인지 여부

line plot

line plot은 가장 기본적인 plot으로 x, y data의 관계를 선으로 표시해주는 그래프이다.

  • x: x data
  • y: y data
  • fmt: 색상, 마커, 선 스타일 등 설정(자세한 내용은 공식 docs 참조)

scatter plot

line plot과 유사하게 x, y data의 관계를 점으로 표시해준다. scatter plot은 data 간의 유사도와 같은 상관관계를 파악할 때 많이 사용된다.

한 subplot에 여러개 graph 겹쳐 그리기

방법은 간단하다. subplot 하나 만들어두고 계속 plot으로 데이터를 집어넣으면 그래프가 그만큼 나온다.

여러개 subplot 한번에 만들기

댓글