Backprop가 러스트로 재구현되면서 비로소 의미가 통했다

발행: (2026년 2월 2일 오후 03:15 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

위에 제공된 소스 링크 아래에 번역하고자 하는 텍스트를 입력해 주세요.
텍스트를 알려주시면, 코드 블록과 URL은 그대로 두고 내용만 한국어로 번역해 드리겠습니다.

소개

저는 PyTorch나 TensorFlow를 사용해 본 적이 없습니다. 제 머신러닝 배경은 NumPy와 scikit‑learn이었습니다: 모델을 훈련하고, 파라미터를 튜닝하며, 괜찮은 결과를 얻을 수 있었지만, 작동하는지를 설명하려고 하면 이해가 흔들렸습니다. 특히 역전파는 블랙 박스처럼 느껴졌습니다. 고수준에서는 단계들을 알았지만, 실제로 체감하지 못했습니다.

그래서 저는 머신러닝 라이브러리를 완전히 사용하지 않기로 하고, Rust로 신경망의 핵심을 처음부터 다시 구현했습니다. 그때 역전파가 드디어 이해가 되었습니다.

왜 추상화가 학습을 가리는가

문제는 NumPy나 scikit‑learn이 아니었습니다—그들은 약속한 대로 정확히 동작합니다. 문제는 이해에 실제로 중요한 모든 것을 추상화해 버렸다는 점이었습니다. 추상화를 제거하고 (자동 미분 없이, 단순 버퍼만 사용하고, 명시적인 인덱싱과 손으로 작성한 행렬 연산만 남겨두면) 신비가 사라졌습니다.

메모리 레이아웃 예시

let data = [1, 2, 3, 4, 5, 6];
let shape = (2, 3); // (rows, cols)

// Logical view
// [ 1  2  3 ]
// [ 4  5  6 ]

// Memory view (row‑major)
// [1][2][3][4][5][6]
//  0  1  2  3  4  5

Rust에서는 대충 전치(transpose)를 할 수 없습니다—인덱스가 메모리에서 어떻게 이동하는지 정확히 설명해야 합니다:

let index = row * cols + col;

그 제약이 모든 것을 바꾸었습니다. 그라디언트를 대충 넘겨줄 수 없으며, 명시적으로 계산하고 저장해야 합니다.

Source:

역전파가 실제로 무엇인지

역전파는 내가 직접 구현했을 때—기호적으로가 아니라 구체적인 기록 방식으로—비밀스러운 것이 아니었다. 이 과정은 세 가지 반복 작업으로 요약된다:

  1. 연쇄법칙 적용
  2. 전방 패스에서 중간값 재사용
  3. 행렬 연산을 통해 기울기를 뒤로 전달

전방 및 후방 패스

Forward pass:
X → [ Linear ] → [ Activation ] → ŷ → Loss

Backward pass:
∂Loss → [ dActivation ] → [ dLinear ] → ∂W, ∂X

이를 손으로 써 보면 몇 가지가 뚜렷이 보인다:

  • 기울기는 “흐르는” 것이 아니라 누적된다.
  • 실제 제약은 형태 정렬이며, 미적분이 아니다.
  • 대부분의 버그는 수학 자체가 아니라 차원에 대한 잘못된 가정에서 비롯된다.

간단한 계산 그래프

        ┌─── w1 ───┐
X ──► (+)         (+) ──► Loss
        └─── w2 ───┘

Backward:

[ \frac{\partial \text{Loss}}{\partial X} = \frac{\partial \text{Loss}}{\partial \text{path}_1}

  • \frac{\partial \text{Loss}}{\partial \text{path}_2} ]

역전파가 어려워 보였던 이유는 실제로 숫자가 어디에 존재하는지 보지 못했기 때문이다.

Source:

러스트의 학습 과정에서의 역할

러스트가 여기서 빠르기 때문에 중요한 것이 아니라, 관대하지 않기 때문에 중요합니다. 러스트는 다음을 직면하도록 강요합니다:

  • 텐서가 메모리에 어떻게 배치되는지
  • 데이터가 복사되는 시점과 재사용되는 시점
  • 어떤 연산이 새로운 버퍼를 할당하는지
  • 어떤 그래디언트가 어떤 순전파 값에 의존하는지

나는 의도적으로 서드파티 크레이트를 피하고 표준 라이브러리만 사용했습니다. 목표는 우아함이나 성능이 아니라 투명성이었습니다. 어떤 것이 동작한다면, 인덱스와 버퍼 수준에서 동작하는지 설명할 수 있기를 원했습니다.

단계별 구현

  1. 평탄한 버퍼를 기반으로 하는 텐서 타입
  2. 원소별 연산
  3. 전치, 축소, 행렬 곱셈
  4. 선형 회귀
  5. 역전파와 그래디언트 업데이트
  6. 끝까지 학습되는 작은 신경망

아무것도 최적화되지 않았습니다. 모든 것이 명시적입니다. 이것은 프레임워크가 아닙니다.

이 글을 시도해볼 사람

  • 소프트웨어 개발자로서 고수준 API를 넘어 신경망을 이해하고 싶은 사람
  • Rust를 배우는 독자로서 도전적이고 시스템 지향적인 프로젝트를 원한다면

역전파(backprop)가 여전히 “받아들여야 할 것”처럼 느껴지고 이해가 부족하다면, 한 번 직접 구현해 보는 것이 시간을 투자할 가치가 있다.

추가 읽을거리

전체 과정을 장(chapter) 스타일 가이드로 문서화했으며, 메모리 내 텐서부터 작동하는 신경망까지 모두 다룹니다. 전체 walkthrough를 여기에서 확인할 수 있습니다:

https://ai.palashkantikundu.in

Back to Blog

관련 글

더 보기 »