[AI] 개발자의 딥러닝 입문기 #2

개발자의 딥러닝 입문기 시리즈

  1. 입문기 인트로 — 학습 로드맵과 멘탈 모델

  2. 개발 환경 준비와 numpy 기초(현재글)

  3. PyTorch 기본 — 학습 루프 5줄이 전부 (예정)

  4. CNN 과 오토인코더 — 라벨 없이 학습하기 (예정)

  5. 군집·시각화와 입문기 회고 (예정)


개발 환경 준비 (Stage 0)

가상환경을 만들고 필요한 패키지를 설치한다. Phase 1 에서 쓸 것들을 한 번에 설치했다.

1
2
3
4
python3 -m venv .venv
source .venv/bin/activate

pip install torch torchvision numpy pillow matplotlib scikit-learn

설치 확인은 한 줄이면 된다.

1
2
python -c "import torch; print(torch.__version__, torch.backends.mps.is_available())"
# 2.13.0 True

mps.is_available() 은 Apple Silicon Mac 의 GPU 가속(MPS) 사용 가능 여부다. True 가 나왔지만, 사실 입문 단계의 데이터 규모(작은 이미지 수십~수백 장)는 CPU 로도 몇 분이면 충분하다. GPU 는 데이터가 커진 뒤의 고민이니 False 여도 걱정할 필요 없다.

torch 설치가 첫 관문에서 가장 자주 막히는 지점입니다. 실패 시 pytorch.org 의 “Get Started” 에서 OS 조합으로 생성되는 설치 명령을 그대로 쓰시면 됩니다.


numpy — 배열이 곧 데이터다 (Stage 1)

ML 에서 모든 데이터는 다차원 배열이다. 이미지도, 신호도, 모델의 파라미터도 전부 배열이다. 그래서 numpy 의 핵심은 함수 암기가 아니라 shape(배열의 모양)을 항상 의식하는 습관이다. ML 버그의 절반은 shape 불일치에서 나온다.

shape 과 axis

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

a = np.arange(15).reshape(3, 5)

print(a.shape)   # (3, 5)  ← 3행 5열
print(a.ndim)    # 2       ← 축(차원) 개수
print(a.dtype)   # int64   ← 요소 타입

print(a[0, 4])   # 인덱싱: 0행 4열의 원소 하나
print(a[1])      # 슬라이싱: 1행 전체
print(a[:, 0])   # 슬라이싱: 모든 행의 0열 = 첫 번째 열

여기서 가장 중요한 개념이 axis 다. axis 는 별개의 무언가가 아니라 shape 의 자리 번호다.

1
2
3
4
a.shape = (3, 5)
           ↑  ↑
        axis 0  axis 1
        (행 방향) (열 방향)

a.mean(axis=0) 은 “axis 0(행) 방향으로 눌러서 평균” — 즉 열별 평균이 된다. 지금은 당연해 보이지만, 이 개념을 대충 넘어간 대가를 아래에서 치르게 된다.

이미지를 배열로 열기

1
2
3
4
5
6
from PIL import Image

x = np.array(Image.open("sample.png"))

print(x.shape)   # (1024, 1024, 4)
print(x.dtype)   # uint8

(1024, 1024, 4) — 세로(H) 1024, 가로(W) 1024, 채널(C) 4 인 배열이다. PNG 라서 채널이 RGBA 4개다. 컴퓨터에게 이미지란 그저 숫자가 채워진 행렬이고, 일반적인 이미지는 빛의 세기를 8비트(0~255)로 표현한다.

정규화 — 0~255 를 0.0~1.0 으로

1
2
3
4
x_normalized = x / 255.0

print(x_normalized.dtype)          # float64
print(x_normalized.min(), x_normalized.max())  # 0.0 1.0

픽셀 값을 0.0~1.0 비율로 바꾸는 이유: 큰 숫자를 그대로 쓰면 학습 계산에서 오차가 커지거나 수렴이 느려질 수 있어, 모델이 안정적으로 특징을 배우도록 입력 스케일을 통일하는 것이다.

한 가지 주의 — /255 는 8비트 이미지라서 맞는 것이다. 16비트 데이터(0~65535)를 그대로 255 로 나누면 값이 0~257 범위가 되어 정규화가 안 된 것과 같다. 나누는 수는 데이터 포맷이 보장하는 최대값이어야 하고, 일반형은 (x - min) / (max - min) 이다.

그리고 x / 255.0 에서 스칼라 하나가 100만 개 픽셀 전체에 적용되는 것이 브로드캐스팅(broadcasting) 이다 — numpy 가 작은 피연산자를 배열 크기에 맞춰 자동 확장해 요소별 연산을 수행하는 규칙으로, for 문 없이 배열 전체를 다루는 numpy 스타일의 근간이다.


🐛 조용히 틀리는 버그를 만나다

확인 과제 중 하나가 “이미지를 좌우 반전해 표시하기”였다. 처음에 이렇게 썼다.

1
x_flipped = np.flip(x, axis=2)   # 좌우 반전... 일까?

실행하면 에러가 나지 않는다. 이미지도 표시된다. 그런데 뭔가 색이 괴상하다. 좌우는… 안 뒤집혔다.

원인은 axis 였다. 이 이미지의 shape 은 (1024, 1024, 4) 다.

1
2
3
4
x.shape = (1024, 1024, 4)
             ↑      ↑    ↑
          axis 0  axis 1  axis 2
           세로↕    가로↔   채널(RGBA)

axis=2채널 축이다. 그러니 좌우가 아니라 RGBA → ABGR 로 색상 순서가 뒤집힌 것이다. 알파값이 R 자리로 들어갔으니 색이 괴상할 수밖에. 좌우(가로) 반전은 axis=1 이 맞다.

1
x_flipped = np.flip(x_normalized, axis=1)   # 진짜 좌우 반전

검증으로 확인하면 명확하다.

1
2
np.array_equal(np.flip(x, axis=1), x[:, ::-1])       # True  — 좌우 반전
np.array_equal(np.flip(x, axis=2), x[:, :, ::-1])    # True  — 채널 뒤집기

이 버그가 남긴 것

1편에서 말한 멘탈 모델 2번 — “틀려도 에러가 나지 않는다” 를 첫 실습에서 바로 체감했다. 일반 개발이었다면 컴파일러나 테스트가 잡아줬을 실수가, 여기서는 “그럴듯한 이미지”로 조용히 지나간다. 이번엔 색이 괴상해서 눈에 띄었지만, 만약 흑백 1채널 이미지였다면 axis=2 는 아예 아무 일도 안 하거나 다른 형태로 조용히 틀렸을 것이다.

교훈 두 가지:

  • axis 는 shape 의 자리 번호다. 연산 전에 shape 을 찍고, 어느 자리를 다루는지 확인한다.
  • 결과를 눈으로 본다. 숫자만 믿지 말고 시각화로 확인하는 습관 — ML 에서는 이것이 테스트 코드의 절반을 대신한다.

덧붙이면, axis 개념은 도메인과도 연결된다. 예컨대 레이더 단면 데이터라면 가로축(주행 방향) 반전은 “반대 방향으로 주행한 것”이라 물리적으로 성립하지만, 세로축(깊이/시간 방향) 반전은 현실에 존재하지 않는 데이터를 만든다. 데이터 증강(augmentation)에서 어느 axis 를 뒤집어도 되는가가 도메인 물리의 문제가 되는 것이다.


마무리

배운 것 요지
환경 venv + torch 설치, MPS 확인(입문 규모는 CPU 로 충분)
shape/axis axis = shape 의 자리 번호. 연산 전 shape 확인 습관
정규화 입력 스케일 통일. 나누는 수는 포맷의 최대값/255 는 8비트 한정
브로드캐스팅 스칼라·작은 배열을 자동 확장해 요소별 연산
교훈 ML 버그는 에러 없이 그럴듯하게 틀린다 — 시각화로 눈 확인

다음 편은 Stage 2, PyTorch 기본이다. Tensor·Dataset·nn.Module, 그리고 “학습 루프는 5줄이 전부” 라는 것을 확인한다.

댓글남기기