포트폴리오에 신경망 탑재 — TensorFlow·서버 없이, 145KB
출처: Dev.to

NumPy만으로 네트워크를 처음부터 학습하고, int8로 양자화한 뒤, 의존성 없는 JavaScript 약 80줄로 실행 — 브라우저가 Python과 1e-6 수준까지 일치한다는 패리티 테스트 포함.
왜 이렇게 할까? MNIST는 이미 해결된 문제다
숫자 인식은 머신러닝의 “Hello World”다 — 그래서 바로 사용했다. 모델 자체가 핵심은 아니다. 핵심은 모델 주변의 모든 작업이며, 이는 실제 프로덕션에서도 중요한 부분이다: 프레임워크 없이 학습하기, 배포용으로 압축하기, 제약된 환경에서 추론하기, 그리고 배포된 시스템이 학습된 모델과 일치함을 증명하기.
학습: NumPy와 수학만으로
네트워크는 784→128→64→10 MLP이며, 순전파, 역전파, Adam 옵티마이저를 모두 손으로 구현했다. 자동 미분도, 프레임워크도 없다:
# backward pass, by hand
dz3 = (probs - y_batch) / batch_size
grads_w[2] = a2.T @ dz3
da2 = dz3 @ weights[2].T
dz2 = da2 * (z2 > 0) # ReLU mask
grads_w[1] = a1.T @ dz2
...

특히 시프트 증강(shift augmentation) 은 그림 시연에 중요하다. MNIST의 숫자는 중앙에 정렬돼 있지만, 사람은 어디에든 그린다. 무작위로 이동된 복제본으로 학습하면 모델이 부정확한 위치에도 강인해진다. 추론 시에도 MNIST와 같은 전처리(바운딩 박스로 자르고, 20×20 박스로 스케일링, 무게 중심으로 중앙 정렬)를 적용하면 실제 손그림도 안정적으로 분류한다. 최종 테스트 정확도: 98.2%.
압축: 15줄 안에 int8
float32 가중치 파일은 약 430 KB다. 대칭 int8 양자화로 약 4배 압축한다:
scale = np.abs(w).max() / 127.0
q = np.clip(np.round(w / scale), -127, 127).astype(np.int8)
레이어당 하나의 스케일 팩터만 사용하고, 가중치는 JSON에 base64로 저장한다. 전체 145 KB, 양자화 후 테스트 정확도는 float와 동일— 98.2%.
추론: 순수 JavaScript 약 80줄
브라우저에서는 로드 시 가중치를 한 번 디양자화하고, 추론은 ReLU와 softmax가 포함된 세 번의 행렬‑벡터 곱으로 이루어진다. 약 109 K 곱셈‑덧셈 연산은 현대 디바이스라면 마이크로초 수준에 불과하다. TensorFlow.js는 필요 없다(런타임 자체가 수 MB이지만, 전체 모델은 145 KB에 불과).
채용 담당자에게 실제로 보여줄 부분
배포된 모델과 학습된 모델 사이의 드리프트는 실제 프로덕션에서 치명적인 실패 원인이다. 그래서 JS 엔진을 Python 모델과 직접 비교한다: 학습 시 내보낸 10개의 고정 숫자와 기대 확률을 Node에서 검증한다.
max prob diff vs Python: 1.14e-6
correct: 10/10 — PARITY OK
추론 코드를 바꿔서 수치적 일치를 깨뜨리면, 방문자가 알기 전에 CI가 이를 감지한다. 이 습관— 배포 아티팩트를 검증하고, 단순히 학습 결과만 확인하지 않는다—은 정확도 한 포인트보다 더 큰 가치를 가진다.
시도해 보기 (형편없이 그려도 잘 작동합니다): rs-03.github.io/portfolio-website/demos
출처: github.com/rs-03/portfolio-website — 학습 스크립트, 추론 엔진, 패리티 테스트.
