Python + NumPy로 신경망 만들기 (0) — 시작하며: 왜, 무엇을, 어떤 순서로
이 시리즈는 PyTorch나 TensorFlow 없이, numpy만으로 MNIST 손글씨 숫자를 인식하는 신경망을 밑바닥부터 만든 기록입니다. 수학이나 인공지능 배경이 없어도 따라올 수 있게, 중학생에게 설명한다는 마음으로 썼습니다.
왜 이걸 했나
딥러닝 라이브러리를 쓰면 신경망은 열 줄이면 만들어집니다. nn.Linear, nn.ReLU, loss.backward(), optimizer.step(). 편리하지만, 그 네 줄 안에서 무슨 일이 일어나는지는 보이지 않습니다.
backward()는 정확히 무엇을 계산하나?optimizer.step()은 가중치를 어떻게 고치나?- 손실이 2.3026에서 시작하는 이유는?
- 왜 ReLU 같은 걸 중간에 끼워야 하나?
이 질문들에 “라이브러리가 해 주니까”가 아니라 내 손으로 짠 코드로 답하고 싶었습니다. 그래서 규칙을 하나 정했습니다.
numpy 외의 라이브러리는 쓰지 않는다. 데이터 읽기, 레이어, 손실 함수, 역전파, 학습 루프 전부 직접 만든다.
무엇을 만들었나
최종 결과는 이것입니다.
$ python train.py
epoch 0: loss = 0.5473
epoch 1: loss = 0.2454
epoch 2: loss = 0.1816
test accuracy: 0.9543
학습 사진 6만 장으로 3바퀴 학습한 뒤, 한 번도 본 적 없는 테스트 사진 1만 장 중 95.43%를 맞춥니다. 코드는 전부 합쳐 150줄 정도이고, 실행에 1초가 안 걸립니다.
구조는 이렇습니다.
mnist/
├── data.py # MNIST 파일 파싱, 전처리, 미니배치
├── layers.py # Linear, ReLU, Sequential (forward + backward)
├── losses.py # softmax, cross-entropy
└── optim.py # SGD
train.py # 조립해서 학습 + 평가
tests/ # 각 부품의 테스트, 수치 기울기 검사
notebooks/ # 만들면서 실험한 기록
docs/ # 이 시리즈
시리즈 순서
노트북 하나에 글 하나입니다. 만든 순서 그대로이고, 뒤로 갈수록 앞 글을 전제로 합니다.
| 글 | 내용 | 한 줄로 |
|---|---|---|
| 01 | 데이터 탐색 | 압축된 바이너리 파일을 열어 숫자 그림을 꺼내 보기 |
| 02 | DataLoader | 사진을 신경망이 먹을 수 있게 손질하고 64장씩 나눠 주기 |
| 03 | 모델 | 숫자 784개를 받아 “0~9 중 뭘까” 점수 10개로 바꾸기 |
| 04 | 손실 함수 | 점수를 확률로 바꾸고 “얼마나 틀렸나”를 숫자 하나로 |
| 05 | 역전파 | 손실에서 거꾸로 올라가며 “가중치를 어떻게 고칠지” 계산하기 |
| 06 | 학습 루프 | 지시서대로 가중치를 고쳐서 95% 만들기 |
글의 형식
글마다 같은 구조입니다.
- 한 줄 요약 — 이 단계에서 한 일
- 본문 — 실제로 실행한 코드와 실제로 찍힌 출력. 추상적인 설명 대신 “내가 직접 본 숫자”로
- 만들면서 겪은 실수 — 틀린 코드, 왜 틀렸는지, 어떻게 알아챘는지
- 용어 표 — 그 글에서 처음 나온 말
- 스스로 확인해 보기 — 답을 접어 둔 문제 몇 개
“실수” 절을 일부러 넣었습니다. 처음부터 맞는 코드만 보면 “왜 이렇게 짜야 하는지”가 안 보이는데, 틀린 코드와 그걸 잡아낸 과정을 보면 보입니다. 이 시리즈에서 가장 많이 배운 건 거의 전부 그 지점이었습니다.
이 시리즈를 관통하는 습관 세 가지
6편을 쓰고 나서 돌아보니, 매번 같은 세 가지가 반복됐습니다.
1. 정답을 아는 입력으로 확인한다. “행 합이 1”처럼 항상 참인 성질로 검사하면 테스트는 통과하지만 아무것도 확인하지 못합니다. [1000, 0, 0]을 넣으면 [1, 0, 0]이 나와야 한다처럼, 답을 미리 아는 입력을 넣어야 버그가 드러납니다. (04)
2. 공식은 수치로 검증한다. 역전파 공식은 맞는지 눈으로 알 수 없습니다. “입력을 아주 조금 밀어 보고 출력이 얼마나 변하나”를 직접 재서 공식과 비교했습니다. 이 검사가 버그 세 개를 잡았습니다. (05)
3. shape을 따라간다. numpy 코드의 버그 대부분은 shape 불일치입니다. 줄마다 # (64, 784)를 적고, 에러 메시지의 숫자를 읽고, 작은 숫자(3, 2, 4)로 먼저 테스트하는 습관이 가장 많이 시간을 아꼈습니다. (05)
따라 하려면
- Python 3과 numpy만 있으면 됩니다. Jupyter 노트북(VS Code 확장이면 충분)이 있으면 셀 단위로 실행하기 편합니다.
- MNIST 데이터는 01의 다운로드 명령으로 받습니다.
- 각 글에 그 단계의 코드가 전부 들어 있습니다. 따로 내려받을 저장소 없이, 글의 코드를 노트북에 붙여 넣고 한 셀씩 실행하면서 읽는 걸 추천합니다. 특히 “스스로 확인해 보기”는 답을 보기 전에 직접 돌려 보세요.
- 완성된 모듈(
data.py,layers.py,losses.py,optim.py,train.py)은 각 글의 마지막 절에 최종 형태로 정리되어 있습니다.
아직 안 한 것
95%는 “기본 모델이 동작한다”는 증명이지 최선이 아닙니다. 다음 글들에서 다룰 예정입니다.
- 하이퍼파라미터 실험: 중간 뉴런 수, 학습률, epoch 수를 바꿔 가며 정확도 표 만들기
- 옵티마이저: SGD → Adam
- 가중치 초기화:
* 0.01→ He 초기화 - 활성화 함수 비교: ReLU vs Sigmoid
- 손실 함수 비교: cross-entropy vs MSE
그럼 01. 데이터 탐색부터 시작합니다.