<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>yg log --graph --oneline --all</title>
    <description>프로그래밍, AI논문 리뷰, 툴, 환경등을 소개합니다.</description>
    <link>https://yg2127.github.io/</link>
    <atom:link href="https://yg2127.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 15 Sep 2026 16:14:22 +0900</pubDate>
    <lastBuildDate>Tue, 15 Sep 2026 16:14:22 +0900</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
    
      <item>
        <title>2. CIGPose 구현 해설 - 관절 벡터 하나를 바꾸면 어디까지 학습될까?</title>
        <description>&lt;nav class=&quot;paper-series&quot; aria-label=&quot;CIGPose 시리즈&quot;&gt;
  &lt;span class=&quot;paper-series__label&quot;&gt;CIGPose 읽기&lt;/span&gt;
  &lt;a href=&quot;/2026/09/cigpose.html&quot;&gt;1. 아이디어·실험·한계&lt;/a&gt;
  &lt;a href=&quot;/2026/09/cigpose-implementation.html&quot; aria-current=&quot;page&quot;&gt;2. 구현·수식·gradient&lt;/a&gt;
&lt;/nav&gt;

&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/cigpose.html&quot;&gt;1편&lt;/a&gt;에서는 CIGPose가 왜 불확실한 관절 특징을 바꾸는지 살펴봤다. 그런데 코드를 읽기 시작하면 질문이 조금 달라진다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“관절 특징이 512차원이라는데, 그 안에 좌표가 들어 있는 건가?”&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;“손목 특징을 바꾸면 원래 손목을 뽑은 인코더는 학습을 못 하는 건가?”&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;“관찰 경로를 고정한다면서 같은 GNN을 어떻게 학습하지?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이 글은 그 질문들을 하나씩 따라간다. 먼저 전체 지도를 보고, 각 모듈의 입력과 출력을 확인한 뒤, 마지막에 두 손실의 gradient를 연결한다. 계산은 조금 길지만 어디에서 무엇이 바뀌는지 정리하면 모듈 이름만 외울 때보다 훨씬 편하다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;읽는 기준&lt;/strong&gt;&lt;br /&gt;
공식 저장소의 &lt;a href=&quot;https://github.com/53mins/CIGPose/tree/b7444d83d6a47efb3a492039e2fcf9dd43b90498&quot;&gt;commit b7444d8&lt;/a&gt;과 &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;CIGPose-x · COCO-WholeBody · 384×288 설정&lt;/a&gt;을 기준으로 한다. 학습을 직접 재현한 실험 기록이 아니라, 원문·보충자료와 해당 소스의 정적 분석을 대조한 해설이다. 다른 모델 크기나 커밋에서는 설정이 달라질 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-1-전체-텐서-지도부터&quot;&gt;Part 1. 전체 텐서 지도부터&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-05-cigpose-architecture.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-05-cigpose-architecture.png&quot; alt=&quot;CIGPose 전체 구조와 학습 중 사용하는 두 경로&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 5. 먼저 위쪽의 Encoder → CIM → HGNN → prediction을 따라가고, 아래의 세부 구조는 HGNN 절에서 다시 보자.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;아래 표의 크기는 &lt;strong&gt;[코드 기반 재구성]&lt;/strong&gt;이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;는 배치 크기다. 한 샘플은 원본 사진 전체가 아니라, 사람 영역을 변환한 crop이다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;순서&lt;/th&gt;
      &lt;th&gt;코드에서 하는 일&lt;/th&gt;
      &lt;th&gt;출력 크기&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;RGB 사람 crop 입력&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 3, 384, 288]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;CSPNeXt-x backbone&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 1280, 12, 9]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;7×7 convolution, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;final_layer&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 12, 9]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;공간축 flatten&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 108]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;ScaleNorm + Linear&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;GAU&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;CIM의 일부 특징 치환&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;HGNN&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;최종 x / y 선형 head&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 576]&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 768]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;가장 먼저 주의할 것은 &lt;strong&gt;config의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input_size=(288, 384)&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(W, H)&lt;/code&gt; 순서&lt;/strong&gt;라는 점이다. PyTorch 이미지 텐서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, C, H, W]&lt;/code&gt;이므로 순서가 바뀐다. x축이 576개, y축이 768개인 이유도 여기에 있다.&lt;/p&gt;

&lt;p&gt;그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;에서 133은 관절 수지만, &lt;strong&gt;512는 좌표의 수가 아니다.&lt;/strong&gt; 각 관절을 표현하는 학습된 feature의 차원이다. 좌표 후보 bin으로 바뀌는 것은 마지막 선형 head를 통과한 뒤다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/heads/coord_cls_heads/cig_head.py&quot;&gt;CIGHead&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-2-keypoint-encoder--이미지가-관절-토큰이-되기까지&quot;&gt;Part 2. Keypoint Encoder — 이미지가 관절 토큰이 되기까지&lt;/h2&gt;

&lt;h3 id=&quot;cspnext와-77-convolution&quot;&gt;CSPNeXt와 7×7 convolution&lt;/h3&gt;

&lt;p&gt;선택한 설정은 MMDetection의 CSPNeXt-x를 backbone으로 사용한다. config에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_scope_=&apos;mmdet&apos;&lt;/code&gt;가 있으므로, 저장소에 이름이 같은 파일이 있다고 해서 그것이 실제 runtime의 backbone이라고 단정하면 안 된다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;모델 설정&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;큰 흐름은 downsampling으로 공간 해상도를 줄이고, CSP 계열 블록과 SPP로 특징을 처리하는 것이다. 역할을 구분해보면 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;구성&lt;/th&gt;
      &lt;th&gt;이해할 포인트&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Downsampling&lt;/td&gt;
      &lt;td&gt;공간 크기를 줄여 더 넓은 영역의 특징을 다룬다&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CSP 계열 블록&lt;/td&gt;
      &lt;td&gt;특징을 두 경로로 처리하고 다시 결합한다. 모든 short branch를 단순 identity로 생각하면 안 된다&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SPP&lt;/td&gt;
      &lt;td&gt;서로 다른 크기의 pooling 결과를 결합해 여러 수용영역의 정보를 모은다&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이 글에서는 backbone 내부의 모든 convolution을 나열하기보다, CIGPose head로 넘어오는 경계부터 구체적으로 보자. 최종 feature map은 1280개 채널과 12×9 공간 위치를 가진다. 여기에 출력 채널이 133개인 7×7 convolution을 적용한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: 선택 config와 CIGHead의 특징 처리]&lt;/strong&gt;&lt;/p&gt;

\[[B,1280,12,9]
\xrightarrow{\operatorname{Conv}_{7\times7}}
[B,133,12,9]
\xrightarrow{\operatorname{flatten}}
[B,133,108]\]

&lt;p&gt;7×7은 커널 크기, 133은 출력 채널 수다. 적절한 padding으로 공간 크기를 유지한다. 각 출력 채널의 12×9 위치를 펼치면 관절별 108차원 벡터가 된다.&lt;/p&gt;

&lt;p&gt;그렇다고 이때의 각 채널이 이미 정답 관절 좌표를 뜻하는 것은 아니다. 뒤의 좌표 예측 손실을 통해 관절별 표현이 학습되는 구조라고 이해하면 된다.&lt;/p&gt;

&lt;h3 id=&quot;scalenorm과-linear는-다른-일을-한다&quot;&gt;ScaleNorm과 Linear는 다른 일을 한다&lt;/h3&gt;

&lt;p&gt;ScaleNorm은 벡터의 크기를 정규화하고, Linear는 학습한 가중치로 특징을 다른 차원으로 옮긴다. ‘정규화해서 512차원이 된다’가 아니라 &lt;strong&gt;정규화 후 Linear가 108차원을 512차원으로 바꾸는 것&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: ScaleNorm의 연산 형태]&lt;/strong&gt;&lt;/p&gt;

\[\operatorname{ScaleNorm}(v)
=\frac{g\,v}{\max(\lVert v\rVert_2/\sqrt d,\epsilon)}\]

&lt;p&gt;여기서 차원 수는 d, 학습되는 크기 파라미터는 g다. 벡터의 성분 사이 비율을 유지하면서 크기를 조절한다. 이어지는 bias 없는 Linear가 관절별 108차원 벡터를 512차원으로 사상한다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/transformer.py#L384-L420&quot;&gt;ScaleNorm 구현&lt;/a&gt; · &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/rtmcc_block.py&quot;&gt;RTMCC 구현&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;gau는-관절-사이에서-정보를-섞는다&quot;&gt;GAU는 관절 사이에서 정보를 섞는다&lt;/h3&gt;

&lt;p&gt;GAU, Gated Attention Unit은 &lt;strong&gt;133개의 관절 토큰 사이의 정보를 섞는 단계&lt;/strong&gt;다. 이 설정에서는 입력과 출력 모두 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;다.&lt;/p&gt;

&lt;p&gt;정규화한 입력을 선형 변환하고 SiLU를 적용한 뒤 세 덩어리로 나눈다. 아래 크기는 &lt;strong&gt;[코드 기반 재구성]&lt;/strong&gt;이다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;텐서&lt;/th&gt;
      &lt;th&gt;크기&lt;/th&gt;
      &lt;th&gt;역할&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;U&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 1024]&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;모은 정보를 조절하는 gate&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;V&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 1024]&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;다른 토큰에 전달할 특징&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;R&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 256]&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;query와 key를 만드는 공통 base&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;총 2304차원을 1024, 1024, 256으로 자르는 셈이다. 여기서는 정준 임베딩 테이블과 기호가 겹치지 않도록 코드의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt;를 R로 적었다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: GAU, 위치 인코딩·상대 위치 편향이 꺼진 설정]&lt;/strong&gt;&lt;/p&gt;

\[Q=R\odot\gamma_Q+\beta_Q,\qquad
K_{\mathrm{attn}}=R\odot\gamma_K+\beta_K\]

\[A=\operatorname{ReLU}\left(\frac{QK_{\mathrm{attn}}^\top}{\sqrt{256}}\right)^2\]

\[F=r\odot F_{\mathrm{in}}+W_o\left(U\odot(AV)\right)\]

&lt;p&gt;소문자 r은 512차원의 학습 가능한 residual scale이다. 여기서 A는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 133]&lt;/code&gt;, AV는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 1024]&lt;/code&gt;다. 마지막 투영으로 다시 512차원이 된다.&lt;/p&gt;

&lt;p&gt;익숙한 Transformer와 비슷해 보여도 이 구현의 attention kernel은 &lt;strong&gt;softmax가 아니라 ReLU를 제곱한 값&lt;/strong&gt;이다. U와 곱하는 부분은 원소별 gating이다. 또 query·key를 만드는 첫 식은 곱셈뿐 아니라 덧셈까지 포함한 affine transformation이다. 이런 작은 구분을 놓치면 GAU를 그냥 ‘일반 attention’으로 설명하게 된다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/rtmcc_block.py&quot;&gt;RTMCCBlock&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-3-cim--점수를-계산하고-고르고-바꾼다&quot;&gt;Part 3. CIM — 점수를 계산하고, 고르고, 바꾼다&lt;/h2&gt;

&lt;h3 id=&quot;초기-simcc는-선택용이다&quot;&gt;초기 SimCC는 선택용이다&lt;/h3&gt;

&lt;p&gt;GAU가 만든 관절 특징을 x축과 y축의 초기 선형 head에 각각 넣는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: 초기 예측 head]&lt;/strong&gt;&lt;/p&gt;

\[h^{\mathrm{init}}_{k,x}=W_x f_k+b_x,\qquad
h^{\mathrm{init}}_{k,y}=W_y f_k+b_y\]

&lt;p&gt;좌표의 split ratio가 2이므로 x축은 288×2=576개, y축은 384×2=768개 bin을 갖는다. 여기서 나오는 값은 &lt;strong&gt;logits&lt;/strong&gt;다. 확률로 사용하려면 각 축에 softmax를 적용한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: x축 정규화, y축도 같은 방식]&lt;/strong&gt;&lt;/p&gt;

\[P_{b,k,x}(i)=\frac{\exp(h^{\mathrm{init}}_{b,k,x,i})}
{\sum_{\ell=0}^{575}\exp(h^{\mathrm{init}}_{b,k,x,\ell})}\]

&lt;p&gt;b는 배치 안의 사람 crop 번호, k는 관절 번호, i는 x축 좌표 bin이다. y축의 bin index와 혼동하지 말자.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-07-posterior-distributions.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-07-posterior-distributions.png&quot; alt=&quot;차폐된 발목과 보이는 무릎의 좌표 분포 비교&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 보충자료 Fig. 7. 저자들은 분포의 퍼짐을 통해 위치 판단의 모호성을 설명한다. 이 그림이 특정 공개 커밋의 초기 head가 어떻게 학습되었는지까지 증명하는 것은 아니다.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;top-k는-배치-전체가-아니라-사람별로-고른다&quot;&gt;Top-k는 배치 전체가 아니라 사람별로 고른다&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 식 (2), 배치 index를 추가한 표기]&lt;/strong&gt;&lt;/p&gt;

\[s_c(b,k)=1-\frac12\left(\max_i P_{b,k,x}(i)+\max_j P_{b,k,y}(j)\right)\]

&lt;p&gt;점수가 큰 관절을 사람마다 선택한다. 검토한 설정은 &lt;strong&gt;학습에서 13개, 검증·추론에서 1개&lt;/strong&gt;를 고른다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.topk(..., dim=1)&lt;/code&gt;이 관절축에서 실행되는 이유다.&lt;/p&gt;

&lt;p&gt;이 설정은 고정 개수를 고르는 방식이다. 모든 관절이 비교적 확실해도 Top-1은 하나를 선택한다. ‘점수가 임계값을 넘은 관절만 바꾼다’는 설명은 별도의 threshold 전략에 해당한다. &lt;a href=&quot;/assets/cigpose/table-09-intervention-strategies.png&quot;&gt;보충자료 Table 9&lt;/a&gt;도 이 두 전략을 구분해서 비교한다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/heads/coord_cls_heads/cig_head.py&quot;&gt;CIM 구현&lt;/a&gt; · &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;설정&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;정준-임베딩은-공유하지만-선택은-이미지마다-다르다&quot;&gt;정준 임베딩은 공유하지만, 선택은 이미지마다 다르다&lt;/h3&gt;

&lt;p&gt;정준 임베딩 테이블은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Embedding(133, 512)&lt;/code&gt;다. 133개 관절 유형마다 512차원 벡터 하나가 있다. 표준편차 0.01의 정규분포로 초기화하고 학습 중 최적화한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[보충자료: C절 식 (8)]&lt;/strong&gt;&lt;/p&gt;

\[f&apos;_{b,k}=(1-m_{b,k})f_{b,k}+m_{b,k}z_k\]

&lt;p&gt;마스크가 1이면 공유 테이블의 벡터, 0이면 현재 이미지의 원래 특징을 사용한다. 공유 테이블에는 배치축이 없지만, 어떤 행을 가져올지는 이미지별 점수에 따라 달라진다.&lt;/p&gt;

&lt;p&gt;코드의 핵심 동작을 줄이면 아래와 같다. 아래 블록은 &lt;strong&gt;[코드 기반 재구성]&lt;/strong&gt;이며 설명용 발췌다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intervened_mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# (batch indices, keypoint indices)
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;canonical&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;canonical_features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f_prime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_kpts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f_prime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;canonical&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone()&lt;/code&gt; 자체는 gradient를 끊지 않는다. 다만 덮어쓴 위치에서는 값의 출처가 원래 특징에서 정준 임베딩으로 바뀐다. 뒤에서 loss를 계산하면 그 위치의 gradient는 정준 임베딩으로 이어진다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-04-umap-canonical-embeddings.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-04-umap-canonical-embeddings.png&quot; alt=&quot;관절 특징과 정준 임베딩을 비교한 UMAP 시각화&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 4. 이미지별 특징은 여러 점으로, 관절별 공유 정준 벡터는 하나의 점으로 표현된다. 관절 종류 사이의 구분은 볼 수 있지만, 하나의 공유 벡터가 한 점이라는 사실만으로 모든 문맥 편향이 제거되었다고 결론 내릴 수는 없다.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;코드에서-추가로-확인해야-했던-부분&quot;&gt;코드에서 추가로 확인해야 했던 부분&lt;/h3&gt;

&lt;p&gt;여기에는 그냥 넘어가기 어려운 지점이 있다. 학습 함수는 초기 head에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f_kpts.detach()&lt;/code&gt;를 넣고, CIM 안에서는 초기 logits의 softmax·점수·Top-k를 모두 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.no_grad()&lt;/code&gt; 안에서 계산한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;따라서 검토한 학습 경로에서는 초기 예측 head로 loss gradient가 연결되지 않는다.&lt;/strong&gt; 초기 x/y head는 최종 x/y head와도 별개다. ‘선택용이므로 인코더만 detach했다’는 말로 끝낼 수 없고, 초기 head 자체를 학습시키는 경로가 따로 있는지도 확인해야 한다.&lt;/p&gt;

&lt;p&gt;검토한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CIGHead.loss()&lt;/code&gt;와 설정에서는 그 head를 직접 지도하는 별도 손실을 찾지 못했다. 그러므로 이 코드만 근거로 초기 head를 ‘학습된 uncertainty estimator’라고 단정하지 않는다. 실제 공개 체크포인트에서의 상태나 전체 학습 재현은 별도 검증이 필요하다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/heads/coord_cls_heads/cig_head.py&quot;&gt;학습 함수&lt;/a&gt; · &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;config&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-4-hgnn--관절과-부위를-번갈아-본다&quot;&gt;Part 4. HGNN — 관절과 부위를 번갈아 본다&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-05-hgnn-detail.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-05-hgnn-detail.png&quot; alt=&quot;Hierarchical GNN의 MLP, EdgeConv, 그룹 gate와 residual 연결&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 5 하단을 원문에서 추출. 아래 수식은 이 개요 그림을 공개 코드의 실제 연산 순서로 풀어 쓴 것이다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;이제 CIM 출력은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 512]&lt;/code&gt;다. HGNN의 최종 출력도 같은 크기다. &lt;strong&gt;크기를 유지한다고 같은 정보인 것은 아니다.&lt;/strong&gt; 연결된 관절과 부위 정보를 반영해 표현을 바꾼다.&lt;/p&gt;

&lt;h3 id=&quot;-입력-투영과-국소-edgeconv&quot;&gt;① 입력 투영과 국소 EdgeConv&lt;/h3&gt;

&lt;p&gt;먼저 입력을 Linear → BatchNorm → SiLU로 처리한다. BatchNorm을 위해 배치와 관절을 합쳐 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B×133, 512]&lt;/code&gt;로 다룬다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: HGNN 입력 투영]&lt;/strong&gt;&lt;/p&gt;

\[x_i=\operatorname{SiLU}\left(\operatorname{BN}(W_{\mathrm{in}}f&apos;_i+b_{\mathrm{in}})\right)\]

&lt;p&gt;선택한 config의 골격 연결 180쌍은 양방향 360개 edge로 바뀐다. 각 edge에서 &lt;strong&gt;현재 관절의 특징과 이웃과의 차이&lt;/strong&gt;를 이어 붙인다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: 국소 EdgeConv와 집계]&lt;/strong&gt;&lt;/p&gt;

\[m_{i\leftarrow j}=\operatorname{SiLU}\left(
\operatorname{BN}\left(W_p[x_i\,\Vert\,(x_j-x_i)]\right)\right)\]

\[v_i=\operatorname{BN}\left(\sum_{j\in\mathcal N(i)}m_{i\leftarrow j}\right)\]

&lt;p&gt;512차원 두 개를 이어 붙여 1024차원이 되고, 1×1 Conv가 이를 512차원으로 바꾼다. 여기서 1×1은 이미지의 2차원 이웃을 훑는 필터라고 생각하기보다, &lt;strong&gt;edge 특징의 채널을 변환하는 연산&lt;/strong&gt;으로 보면 된다.&lt;/p&gt;

&lt;p&gt;그렇다면 이웃의 정보는 어디에 들어갈까? 이미 concat 입력에 이웃과의 차이가 들어 있고, 연결된 이웃의 메시지를 더하는 단계에서도 관계를 사용한다. 코드의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index_add_&lt;/code&gt;는 합 집계다. 이 구현을 max aggregation이라고 설명하면 틀린다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/hierarchical_gnn.py&quot;&gt;EdgeConv·HGNN 구현&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;-관절을-의미-그룹으로-묶기&quot;&gt;② 관절을 의미 그룹으로 묶기&lt;/h3&gt;

&lt;p&gt;다음 단계에서는 관절 특징을 512차원에서 128차원으로 줄이고, 그룹 안의 관절을 평균낸다. 선택 config에는 32개 그룹이 있다. 그룹은 서로 겹칠 수 있다. 얼굴 전체 그룹과 눈·코·입 그룹이 함께 있는 식이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: 채널 축소와 그룹 평균]&lt;/strong&gt;&lt;/p&gt;

\[d_i=\operatorname{SiLU}\left(\operatorname{BN}(W_dv_i+b_d)\right),\qquad
g_e=\frac1{|e|}\sum_{i\in e}d_i\]

&lt;p&gt;이 단계의 크기는 &lt;strong&gt;[코드 기반 재구성]&lt;/strong&gt;으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 128] → [B, 32, 128]&lt;/code&gt;이다. 논문이 semantic hypergraph라고 부르는 관계를 코드에서는 &lt;strong&gt;관절의 그룹 소속과 그룹 특징 간 그래프 연산&lt;/strong&gt;으로 구현한다.&lt;/p&gt;

&lt;p&gt;각 그룹은 자기 자신을 제외한 다른 31개 그룹과 연결된다. 따라서 그룹 그래프는 992개의 방향성 edge를 갖는다. 이 그래프에서 다시 EdgeConv를 적용해 전신의 부위 정보를 교환한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: 그룹 간 메시지 집계]&lt;/strong&gt;&lt;/p&gt;

\[u_e=\sum_{q\ne e}\operatorname{SiLU}\left(
\operatorname{BN}\left(W_h[g_e\,\Vert\,(g_q-g_e)]\right)\right)\]

&lt;h3 id=&quot;-그룹-gate를-다시-관절로-돌려주기&quot;&gt;③ 그룹 gate를 다시 관절로 돌려주기&lt;/h3&gt;

&lt;p&gt;그룹의 128차원 특징을 Linear로 512차원으로 늘리고 sigmoid를 적용한다. 이렇게 만든 각 성분은 0과 1 사이의 조절 계수다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: 그룹 gate와 관절별 평균]&lt;/strong&gt;&lt;/p&gt;

\[a_e=\sigma(W_au_e+b_a),\qquad
a_i=\frac1{|\mathcal E_i|}\sum_{e\in\mathcal E_i}a_e\]

&lt;p&gt;한 관절이 여러 그룹에 포함되면 그 그룹들의 gate를 평균한다. 어떤 그룹에도 포함되지 않은 관절에는 코드가 gate 1을 유지한다. 선택 config에서 실제로 어떤 그룹에 속하는지는 그룹 정의를 기준으로 확인해야 한다.&lt;/p&gt;

&lt;p&gt;이제 처음의 국소 특징과 원소별로 곱한 뒤 residual을 더한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: HGNN 한 층의 출력]&lt;/strong&gt;&lt;/p&gt;

\[x_i^{\mathrm{out}}=\operatorname{SiLU}\left(x_i+v_i\odot a_i\right)\]

&lt;p&gt;이 식을 말로 풀면 &lt;strong&gt;“연결된 이웃에서 모은 특징을 전신 그룹의 정보로 조절하고, 기본 특징에 더한다”&lt;/strong&gt;다. 잔차로 더하는 것은 입력 투영 후의 특징이다. 논문 본문의 간략한 gate 식에는 생략된 투영·정규화·잔차까지 공개 코드에는 들어 있으므로, 위 식들은 논문 원문 식과 구분했다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-5-마지막-simcc-head--이제-좌표를-만든다&quot;&gt;Part 5. 마지막 SimCC head — 이제 좌표를 만든다&lt;/h2&gt;

&lt;p&gt;HGNN 출력에 서로 다른 두 선형층 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cls_gx&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cls_gy&lt;/code&gt;를 적용한다. &lt;strong&gt;[코드 기반 재구성]&lt;/strong&gt;으로 x logits는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 576]&lt;/code&gt;, y logits는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[B, 133, 768]&lt;/code&gt;이다. CIM의 초기 head와 이름도, 파라미터도 다르다.&lt;/p&gt;

&lt;p&gt;학습에서는 이 logits와 정답 SimCC label을 비교한다. 추론에서는 각 축의 최대 위치를 선택하고 split ratio로 나눈다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[코드 기반 재구성: SimCC decode의 좌표 선택, split ratio=2]&lt;/strong&gt;&lt;/p&gt;

\[\hat x_k=\frac{\arg\max_i h_{k,x}(i)}2,\qquad
\hat y_k=\frac{\arg\max_j h_{k,y}(j)}2\]

&lt;p&gt;이 좌표는 변환된 사람 crop의 좌표다. 원본 이미지에 관절을 그리려면 crop을 만들 때의 변환을 되돌리는 과정까지 필요하다. 모델 출력 벡터, crop 내부 좌표, 원영상 좌표를 같은 것으로 취급하면 안 된다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/codecs/simcc_label.py&quot;&gt;SimCC decoder&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;loss-and-gradients&quot;&gt;Part 6. 두 손실을 실제 학습 경로에 연결하기&lt;/h2&gt;

&lt;h3 id=&quot;keypoint-loss는-교체된-관절에만-걸리지-않는다&quot;&gt;Keypoint loss는 교체된 관절에만 걸리지 않는다&lt;/h3&gt;

&lt;p&gt;먼저 정답 자세를 맞히는 기본 손실이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 식 (5)]&lt;/strong&gt;&lt;/p&gt;

\[\mathcal L_{\mathrm{kpt}}=\sum_{k=1}^{K}w_k
D_{\mathrm{KL}}\left(Q_k\parallel P(Y_k\mid do(F))\right)\]

&lt;p&gt;정답 분포가 Q, 반사실 경로의 최종 예측이 비교 대상이다. 이 식의 개입 표기는 논문의 표기를 따른다. 실제 코드에서는 특징을 바꾼 경로의 출력을 사용한다.&lt;/p&gt;

&lt;p&gt;핵심은 &lt;strong&gt;개입하지 않은 관절도 기본 손실에 들어간다&lt;/strong&gt;는 것이다. 정답 가중치가 유효하면 모두 자세를 맞혀야 한다. 반대로 정답 가중치가 0인 키포인트는 이 손실에 기여하지 않는다. 가중치는 치환 마스크가 아니다.&lt;/p&gt;

&lt;p&gt;실제 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KLDiscretLoss&lt;/code&gt;에는 논문의 간략한 식보다 세부 계산이 더 있다. 선택 설정은 예측 logits에 beta=10을 곱하고, 정답 label에도 softmax를 적용한다. 각 축의 bin에 대해 평균을 구한 뒤 가중치를 곱하고, 배치·관절의 합을 K로 나눈다. &lt;strong&gt;코드의 loss 숫자를 논문 식의 단순 KL 합과 같은 척도로 비교하면 안 된다.&lt;/strong&gt; &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/losses/classification_loss.py&quot;&gt;KLDiscretLoss&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;consistency-loss는-멀쩡한-관절의-예측을-기준에-묶는다&quot;&gt;Consistency loss는 멀쩡한 관절의 예측을 기준에 묶는다&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 식 (6)]&lt;/strong&gt;&lt;/p&gt;

\[\mathcal L_{\mathrm{cf}}=\frac1{|S|}\sum_{k\in S}
D_{\mathrm{KL}}\left(\operatorname{sg}[P(Y_k\mid F)]\parallel P(Y_k\mid do(F))\right)\]

&lt;p&gt;관찰 경로의 예측은 고정된 목표다. 반사실 경로가 그 목표에서 불필요하게 멀어지는 것을 막는다. 코드에서 실제로 선택하는 mask는 다음과 같다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;stable_mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intervened_mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keypoint_weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;출처: &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/losses/causal_consistency_loss.py&quot;&gt;CounterfactualConsistencyLoss&lt;/a&gt;. 개입하지 않았다는 조건과 정답 가중치가 유효하다는 조건을 동시에 사용한다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;KL의 방향도 확인해야 한다. PyTorch의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kl_div&lt;/code&gt;는 첫 인자로 예측의 log probability, 두 번째 인자로 목표 probability를 받는다. 이 코드가 계산하는 것은 &lt;strong&gt;관찰 분포를 기준으로 한 반사실 분포의 KL&lt;/strong&gt;, 즉 논문 식과 같은 방향이다. 함수의 인자 순서를 수식의 분포 순서로 그대로 옮기면 헷갈린다.&lt;/p&gt;

&lt;p&gt;두 좌표축의 KL을 더해 유효한 안정 키포인트 수로 평균하고, loss 내부에서 가중치 0.1을 곱한다. 따라서 설정을 따라 코드를 구현할 때 바깥에서 다시 0.1을 곱하면 중복 적용이다. 안정 키포인트가 하나도 없으면 코드가 이 손실을 0으로 반환한다.&lt;/p&gt;

&lt;h3 id=&quot;같은-gnn인데-한쪽만-고정할-수-있나&quot;&gt;같은 GNN인데 한쪽만 고정할 수 있나?&lt;/h3&gt;

&lt;p&gt;학습 함수에서 경로를 나누면 다음과 같다. &lt;strong&gt;[코드 기반 재구성: 핵심 동작을 줄인 의사코드]&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 초기 logits는 선택용. CIM 내부의 점수·Top-k도 no_grad 안에 있다.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initial&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initial_heads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pred_cf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;final_heads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hgnn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 동일한 가중치를 쓰지만 이 forward에는 autograd graph를 만들지 않는다.
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;no_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pred_obs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;final_heads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hgnn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;loss_kpt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;supervised_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pred_cf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss_cf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;consistency_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pred_obs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pred_cf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loss_kpt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loss_cf&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# 0.1은 consistency_loss 내부에 반영됨
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;같은 파라미터를 공유해도 &lt;strong&gt;어떤 forward 계산의 gradient를 추적하는지는 경로마다 정할 수 있다.&lt;/strong&gt; 관찰 경로로는 gradient가 돌아가지 않고, 반사실 경로로는 GNN과 최종 head를 학습한다. &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/heads/coord_cls_heads/cig_head.py&quot;&gt;CIGHead.loss&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;여기서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_grad()&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval()&lt;/code&gt;과도 다르다. 전자는 autograd 기록을 끄고, 후자는 BatchNorm·Dropout 등의 동작 모드를 바꾼다. 이 학습 함수는 관찰 경로를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval()&lt;/code&gt;로 전환하지 않는다. 따라서 ‘고정 목표’는 &lt;strong&gt;역전파 기준으로 고정&lt;/strong&gt;이라는 의미이지, BatchNorm 통계까지 모두 고정한다는 뜻이 아니다.&lt;/p&gt;

&lt;h3 id=&quot;결국-어느-파라미터까지-gradient가-가는가&quot;&gt;결국 어느 파라미터까지 gradient가 가는가?&lt;/h3&gt;

&lt;p&gt;아래 표는 &lt;strong&gt;[코드 기반 재구성: 검토한 학습 경로의 도달 가능성]&lt;/strong&gt;이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reachable&lt;/code&gt;은 그래프상 연결될 수 있다는 뜻이며, 매 샘플에서 반드시 0이 아닌 gradient가 생긴다는 보장은 아니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;파라미터 또는 경로&lt;/th&gt;
      &lt;th&gt;Keypoint loss&lt;/th&gt;
      &lt;th&gt;Consistency loss&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Backbone · 7×7 Conv · 투영 · GAU&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt; — 유지된 특징을 통해&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt; — 반사실 경로의 유지된 특징을 통해&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;선택된 정준 임베딩 행&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt; — GNN의 특징 혼합 등을 통해 안정 관절에도 영향을 줄 수 있음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;해당 배치에서 선택되지 않은 정준 임베딩 행&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;not involved&lt;/strong&gt; — lookup되지 않음&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;not involved&lt;/strong&gt; — lookup되지 않음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HGNN&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt; — 반사실 경로&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt; — 반사실 경로&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;최종 x/y head&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;reachable&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;초기 x/y head&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;blocked by no_grad&lt;/strong&gt; — 선택 경로만 사용&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;blocked by no_grad&lt;/strong&gt; — 선택 경로만 사용&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;부모 RTMCCHead에서 생성됐으나 대체된 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mlp&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cls_x&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cls_y&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;not involved&lt;/strong&gt; — CIGHead의 이 학습 경로에서는 호출하지 않음&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;not involved&lt;/strong&gt; — 이 학습 경로에서는 호출하지 않음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;관찰 경로의 forward&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;not involved&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;blocked by no_grad / detach&lt;/strong&gt; — 목표만 제공&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Top-k 선택 자체&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;blocked by no_grad&lt;/strong&gt; — 이산 선택&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;blocked by no_grad&lt;/strong&gt; — 이산 선택&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;두 가지를 더 구분하자.&lt;/p&gt;

&lt;p&gt;첫째, 원래 관절 특징을 덮어쓴 &lt;strong&gt;그 위치의 직접 경로&lt;/strong&gt;는 끊기지만, Encoder 전체가 학습되지 않는 것은 아니다. 유지된 다른 관절 특징으로 gradient가 갈 수 있고, GAU 앞쪽에서는 토큰 사이의 관계도 이미 섞여 있다.&lt;/p&gt;

&lt;p&gt;둘째, Consistency loss는 &lt;strong&gt;바꾸지 않은 관절의 출력&lt;/strong&gt;에서 계산되지만, 그 출력은 GNN을 통해 &lt;strong&gt;바꾼 관절의 특징&lt;/strong&gt;에도 의존할 수 있다. 그래서 이 손실도 선택된 정준 임베딩을 학습시킬 수 있다. ‘loss를 계산한 관절’과 ‘gradient를 받는 모든 파라미터’는 같은 범위가 아니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[보충자료: C절 식 (9), 치환 마스크를 고정했을 때]&lt;/strong&gt;&lt;/p&gt;

\[\frac{\partial\mathcal L}{\partial z_k}
=\sum_{b=1}^{B}m_{b,k}\frac{\partial\mathcal L}{\partial f&apos;_{b,k}}\]

&lt;p&gt;선택된 이미지들에서 같은 관절의 정준 벡터로 gradient가 모인다. 선택되지 않은 행은 이 loss의 gradient를 받지 않지만, 그것을 곧바로 ‘optimizer step 후 파라미터 값이 절대 안 바뀐다’로 확대하면 안 된다. optimizer의 상태나 weight decay에 따른 갱신은 loss gradient와 구분해야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-7-코드까지-읽고-나면-설명이-조금-달라진다&quot;&gt;Part 7. 코드까지 읽고 나면 설명이 조금 달라진다&lt;/h2&gt;

&lt;p&gt;처음에는 “불확실한 관절을 기준 벡터로 바꾸고 GNN으로 보완한다”로 이해했다. 코드를 읽고 나니 그 문장에 조건들이 붙었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;바꾸는 대상은 &lt;strong&gt;좌표가 아니라 특징 벡터&lt;/strong&gt;다.&lt;/li&gt;
  &lt;li&gt;기준 벡터는 이미지마다 새로 만드는 것이 아니라 &lt;strong&gt;관절 유형별 공유 파라미터&lt;/strong&gt;다.&lt;/li&gt;
  &lt;li&gt;선택한 config는 &lt;strong&gt;학습 Top-13, 추론 Top-1&lt;/strong&gt;을 사용한다.&lt;/li&gt;
  &lt;li&gt;초기 선택용 head와 최종 좌표 head는 별개이며, &lt;strong&gt;검토한 학습 코드에서 초기 head로 이어지는 loss gradient는 확인되지 않는다.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;기본 손실은 유효한 모든 관절, 일관성 손실은 개입하지 않은 유효 관절의 출력에서 계산한다.&lt;/li&gt;
  &lt;li&gt;관찰 경로는 gradient를 막지만, 같은 HGNN과 최종 head는 반사실 경로에서 학습된다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;내가 이 구현에서 가장 오래 헷갈렸던 것은 &lt;strong&gt;‘어디를 바꿨는가’, ‘어디에서 loss를 계산하는가’, ‘어디까지 gradient가 흐르는가’가 모두 다른 질문&lt;/strong&gt;이라는 점이었다. 세 개를 나눠 보고 나니, 정준 임베딩이 어떻게 학습되는지도 훨씬 선명해졌다.&lt;/p&gt;

&lt;p&gt;인과적 목표와 특징 치환의 관계는 여전히 따져볼 부분이 남는다. 하지만 적어도 모델이 실제로 하는 계산을 분리해서 설명할 수는 있다. 논문의 이름에 담긴 해석과 코드의 연산을 함께 보되, 한쪽을 다른 쪽의 증명으로 대신하지 않는 것이 이 리뷰에서 가져가고 싶은 읽기 방식이다.&lt;/p&gt;

&lt;h2 id=&quot;참고-자료와-코드-위치&quot;&gt;참고 자료와 코드 위치&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/html/Li_CIGPose_Causal_Intervention_Graph_Neural_Network_for_Whole-Body_Pose_Estimation_CVPR_2026_paper.html&quot;&gt;논문&lt;/a&gt; · &lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/supplemental/Li_CIGPose_Causal_Intervention_CVPR_2026_supplemental.zip&quot;&gt;보충자료&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/tree/b7444d83d6a47efb3a492039e2fcf9dd43b90498&quot;&gt;검토 기준 커밋&lt;/a&gt; · &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;CIGPose-x 설정&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/heads/coord_cls_heads/cig_head.py&quot;&gt;CIGHead와 CIM — 특징 치환, 학습·추론 경로&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/transformer.py#L384-L420&quot;&gt;ScaleNorm — transformer.py의 정규화 구현&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/rtmcc_block.py&quot;&gt;RTMCCBlock — GAU&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/utils/hierarchical_gnn.py&quot;&gt;HGNNModule — EdgeConv, 그룹 평균, gate, residual&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/losses/classification_loss.py&quot;&gt;KLDiscretLoss — 실제 정규화와 reduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/models/losses/causal_consistency_loss.py&quot;&gt;CounterfactualConsistencyLoss — mask, KL 방향, loss weight&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/mmpose/codecs/simcc_label.py&quot;&gt;SimCCLabel — 좌표 decode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Tue, 08 Sep 2026 00:00:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/cigpose-implementation.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/cigpose-implementation.html</guid>
        
        <category>deep-learning</category>
        
        <category>cigpose</category>
        
        <category>paper-review</category>
        
        <category>pytorch</category>
        
        <category>graph</category>
        
        <category>pose-estimation</category>
        
        
        <category>Paper Review</category>
        
      </item>
    
      <item>
        <title>1. CIGPose 논문 리뷰 - 가려진 관절을 문맥에 맡겨도 될까?</title>
        <description>&lt;nav class=&quot;paper-series&quot; aria-label=&quot;CIGPose 시리즈&quot;&gt;
  &lt;span class=&quot;paper-series__label&quot;&gt;CIGPose 읽기&lt;/span&gt;
  &lt;a href=&quot;/2026/09/cigpose.html&quot; aria-current=&quot;page&quot;&gt;1. 아이디어·실험·한계&lt;/a&gt;
  &lt;a href=&quot;/2026/09/cigpose-implementation.html&quot;&gt;2. 구현·수식·gradient&lt;/a&gt;
&lt;/nav&gt;

&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;CIGPose를 공부하면서 가장 오래 붙잡고 있었던 질문은 &lt;strong&gt;“관절 특징을 다른 벡터로 바꾸는 게 왜 인과 개입이지?”&lt;/strong&gt;였다. 전체 구조만 보면 이미지에서 특징을 뽑고, 일부를 바꾸고, GNN에 넣는 모델이다. 그런데 논문은 여기에 &lt;em&gt;confounder&lt;/em&gt;, &lt;em&gt;backdoor adjustment&lt;/em&gt;, &lt;em&gt;counterfactual&lt;/em&gt;이라는 말을 붙인다. 구조는 따라갈 만한데, 그 구조가 왜 필요한지를 설명하려니 훨씬 어려워졌다.&lt;/p&gt;

&lt;p&gt;그래서 이번에는 문제에서 출발해보려고 한다. 모델이 사람을 보고 자세를 추정한다고 생각했는데, 사실은 사람 옆의 물체를 보고 답을 찍고 있었다면? 그리고 그 물체가 사라지거나 엉뚱한 곳에 나타나면?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;CIGPose: Causal Intervention Graph Neural Network for Whole-Body Pose Estimation&lt;/strong&gt;&lt;br /&gt;
Bohao Li, Zhicheng Cao, Huixian Li, Yangming Guo · CVPR 2026&lt;br /&gt;
&lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/html/Li_CIGPose_Causal_Intervention_Graph_Neural_Network_for_Whole-Body_Pose_Estimation_CVPR_2026_paper.html&quot;&gt;논문&lt;/a&gt; · &lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/supplemental/Li_CIGPose_Causal_Intervention_CVPR_2026_supplemental.zip&quot;&gt;보충자료&lt;/a&gt; · &lt;a href=&quot;https://github.com/53mins/CIGPose&quot;&gt;공식 코드&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;한 줄로 요약하면, &lt;strong&gt;CIGPose는 불확실한 관절 특징을 학습 가능한 관절별 기준 벡터로 바꾸고, 주변 관절과 전신의 관계를 이용해 자세를 다시 추정하는 모델&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;이 글은 그 아이디어와 실험을 중심으로 읽는다. “그래서 실제 텐서는 어떻게 움직이는데?”가 궁금하다면 &lt;a href=&quot;/2026/09/cigpose-implementation.html&quot;&gt;2편의 구현 해설&lt;/a&gt;에서 코드와 수식까지 이어서 볼 수 있다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-1-why-cigpose--사람-대신-의자를-보고-있었다면&quot;&gt;Part 1. Why CIGPose? — 사람 대신 의자를 보고 있었다면&lt;/h2&gt;

&lt;h3 id=&quot;관절-133개를-한꺼번에-찾는-문제&quot;&gt;관절 133개를 한꺼번에 찾는 문제&lt;/h3&gt;

&lt;p&gt;Whole-body pose estimation은 몸통과 팔다리뿐 아니라 얼굴과 손가락까지 함께 찾는 작업이다. COCO-WholeBody에서는 몸 17개, 발 6개, 얼굴 68개, 손 42개를 합쳐 &lt;strong&gt;133개 키포인트&lt;/strong&gt;를 다룬다.&lt;/p&gt;

&lt;p&gt;팔꿈치 몇 개를 찾는 것도 쉽지 않은데, 작게 찍힌 손가락과 가려진 얼굴까지 찾아야 한다. CIGPose는 사람 영역을 먼저 얻은 뒤 그 crop 안에서 자세를 추정하는 &lt;strong&gt;top-down 방식&lt;/strong&gt;이다. 사람을 찾는 검출 단계와, crop 안에서 관절을 찾는 단계는 구분해야 한다.&lt;/p&gt;

&lt;h3 id=&quot;문맥은-도움이-되지만-지름길이-되기도-한다&quot;&gt;문맥은 도움이 되지만, 지름길이 되기도 한다&lt;/h3&gt;

&lt;p&gt;설명을 위해 이런 학습 데이터를 생각해보자. 앉아 있는 사람 뒤에는 대부분 의자가 있다. 모델은 사람의 관절 배치를 자세히 보지 않고도 “의자가 보이네? 앉아 있겠군”이라는 규칙으로 꽤 많은 문제를 맞힐 수 있다.&lt;/p&gt;

&lt;p&gt;그러다 서 있는 사람 뒤에 의자가 나타나면 어떻게 될까? 의자의 등받이를 사람의 몸통처럼 해석하는 식으로 틀릴 수 있다. &lt;strong&gt;자세를 설명하는 증거와, 자세와 자주 함께 등장했던 배경을 혼동한 것&lt;/strong&gt;이다. 이것이 이 논문이 문제 삼는 지름길 학습과 허위 상관관계다. 의자 예시는 직관을 위한 상황이고, 실제 논문의 비교는 아래 그림에서 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-01-overview.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-01-overview.png&quot; alt=&quot;CIGPose의 성능 비교와 문맥 때문에 잘못된 자세를 추정하는 사례&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 1. 위쪽은 성능 비교, 아래쪽은 RTMPose-x와 CIGPose-x의 예측 비교다. 배경 구조에 예측이 끌려가는 부분을 보자. 이미지를 누르면 크게 볼 수 있다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;문맥을 모두 없애자는 이야기는 아니다. 손목이 가려졌을 때 팔꿈치와 어깨가 어디 있는지는 분명 좋은 단서다. 문제는 &lt;strong&gt;어떤 문맥을 믿고, 어떤 시각적 단서를 덜 믿을 것인가&lt;/strong&gt;다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-2-관찰-분포와-개입-분포--문맥의-가중치가-달라진다&quot;&gt;Part 2. 관찰 분포와 개입 분포 — 문맥의 가중치가 달라진다&lt;/h2&gt;

&lt;h3 id=&quot;관찰한-상관관계와-알고-싶은-효과&quot;&gt;관찰한 상관관계와 알고 싶은 효과&lt;/h3&gt;

&lt;p&gt;논문은 이 문제를 SCM, 즉 Structural Causal Model로 표현한다. 기호부터 정리하면 다음과 같다. 아래 표는 논문 3.1절의 정의를 설명용으로 정리한 것이다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;기호&lt;/th&gt;
      &lt;th&gt;의미&lt;/th&gt;
      &lt;th&gt;이 글에서 떠올릴 대상&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;이미지 X&lt;/td&gt;
      &lt;td&gt;입력 이미지&lt;/td&gt;
      &lt;td&gt;사람을 잘라낸 RGB 이미지&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;특징 F&lt;/td&gt;
      &lt;td&gt;키포인트 임베딩&lt;/td&gt;
      &lt;td&gt;관절마다 가진 특징 벡터&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;문맥 C&lt;/td&gt;
      &lt;td&gt;관측되지 않은 시각적 교란 요인&lt;/td&gt;
      &lt;td&gt;차폐 물체, 복잡한 배경, 조명&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;예측 Y&lt;/td&gt;
      &lt;td&gt;추정한 자세&lt;/td&gt;
      &lt;td&gt;각 키포인트의 좌표 분포&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-02-scm-and-cim.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-02-scm-and-cim.png&quot; alt=&quot;구조적 인과 모형과 CIM의 특징 선택 및 치환 과정&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 2. 왼쪽은 저자들이 제시한 인과 모형, 오른쪽은 CIM의 구현 아이디어다. 오른쪽에서는 점수를 계산한 뒤 일부 특징만 빨간 기준 벡터로 바뀌는 과정을 보면 된다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;관찰 데이터에서 예측하는 분포는, 각 문맥에서의 예측을 &lt;strong&gt;그 특징과 함께 나타나는 문맥의 빈도&lt;/strong&gt;로 가중한 것으로 풀어 쓸 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도: 조건부확률의 전확률 공식]&lt;/strong&gt;&lt;/p&gt;

\[P(Y\mid F=f)=\sum_c P(Y\mid F=f,C=c)P(C=c\mid F=f)\]

&lt;p&gt;의자 예시로 돌아가면, 특정 몸통 특징을 가진 학습 이미지에 의자가 자주 나왔을수록 의자가 있는 상황의 예측이 크게 반영된다. 마지막 가중치는 ‘허위 상관관계의 확률’이라는 별도 수치가 아니라, &lt;strong&gt;특징이 주어졌을 때 문맥이 나타날 조건부확률&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;논문은 대신 다음의 개입 분포를 목표로 제시한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 식 (1), 특징을 f로 고정한 표기]&lt;/strong&gt;&lt;/p&gt;

\[P(Y\mid do(F=f))=\sum_c P(Y\mid F=f,C=c)P(C=c)\]

&lt;p&gt;차이는 마지막 가중치다. 특정 특징과 문맥이 함께 나타난 빈도 대신, 문맥 자체의 분포를 사용한다. 이것이 논문이 제시하는 backdoor adjustment의 목표다. 이 식을 적용하려면 저자들이 가정한 인과 모형과 조정 조건이 성립해야 한다.&lt;/p&gt;

&lt;h3 id=&quot;그런데-배경의-모든-경우를-어떻게-더하지&quot;&gt;그런데 배경의 모든 경우를 어떻게 더하지?&lt;/h3&gt;

&lt;p&gt;바로 이 부분이 걸린다. ‘의자가 있음/없음’ 정도면 간단해 보이지만, 실제 이미지의 문맥은 물체, 조명, 가림, 배경 질감이 뒤섞여 있다. 모든 문맥에 라벨이 달려 있는 것도 아니다.&lt;/p&gt;

&lt;p&gt;논문도 이 합을 직접 계산할 수 없다고 말한다. 그래서 &lt;strong&gt;CIM이라는 실용적인 대체 방법&lt;/strong&gt;을 제안한다. 교란의 영향을 받았을 법한 관절 특징을 찾아, 현재 이미지에서 가져오지 않은 기준 벡터로 바꾸는 것이다.&lt;/p&gt;

&lt;p&gt;여기서 두 층위를 나눠두면 뒤가 편해진다. 위의 식은 &lt;strong&gt;논문의 인과적 목표&lt;/strong&gt;, 다음에 나오는 특징 치환은 &lt;strong&gt;그 목표를 구현하려고 제안한 방법&lt;/strong&gt;이다. 둘의 연결은 마지막에 다시 따져보자.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-3-cigpose는-실제로-무엇을-바꾸는가&quot;&gt;Part 3. CIGPose는 실제로 무엇을 바꾸는가&lt;/h2&gt;

&lt;h3 id=&quot;전체-구조부터-보기&quot;&gt;전체 구조부터 보기&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-05-cigpose-architecture.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-05-cigpose-architecture.png&quot; alt=&quot;CIGPose 전체 구조와 계층적 GNN의 세부 구조&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 5, 원문 PDF 5쪽에서 고해상도로 재추출. 위쪽은 전체 흐름, 아래쪽은 Hierarchical GNN이다. 학습할 때 원래 특징을 사용하는 관찰 경로가 함께 등장한다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;크게 네 단계로 볼 수 있다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Keypoint Encoder:&lt;/strong&gt; 이미지에서 관절별 특징을 만든다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CIM:&lt;/strong&gt; 불확실성 점수가 높은 관절 특징을 정준 임베딩으로 바꾼다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Hierarchical GNN:&lt;/strong&gt; 가까운 관절과 전신 부위 사이의 관계를 반영한다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;SimCC Head:&lt;/strong&gt; x축과 y축의 좌표 분포를 예측한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;포인트는 2번과 3번의 연결이다. 관절 특징을 바꾸고 끝내면 이미지에 맞는 자세를 얻기 어렵다. &lt;strong&gt;바꾼 특징을 다른 관절과 함께 해석해야 한다.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;cim--어느-관절을-덜-믿을-것인가&quot;&gt;CIM ① 어느 관절을 덜 믿을 것인가&lt;/h3&gt;

&lt;p&gt;SimCC는 좌표를 x축과 y축의 1차원 분류 문제로 표현한다. x축에서 어디에 있을지, y축에서 어디에 있을지 각각 분포를 만든다.&lt;/p&gt;

&lt;p&gt;분포가 한 위치에 뾰족하게 모이면 모델이 한 후보를 강하게 선택하고 있다는 뜻이다. 반대로 여러 위치에 퍼지면 어느 후보인지 애매하다. 논문은 각 축의 최대 확률을 이용해 점수를 만든다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 식 (2)]&lt;/strong&gt;&lt;/p&gt;

\[s_c(k)=1-\frac12\left(\max P_{k,x}+\max P_{k,y}\right)\]

&lt;p&gt;직관을 위한 숫자를 넣어보면 이렇다. 아래 값은 실제 실험에서 측정한 수치가 아니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;설명용 예시&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;x축 최대 확률&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;y축 최대 확률&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;교란 점수&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;한 위치에 비교적 집중&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.8&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.9&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;여러 위치에 분산&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.75&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt; 최대 확률의 평균을 1에서 빼므로, 집중도가 낮을수록 점수가 커진다. 다만 실제 좌표 bin 수나 분포 설정에 따라 확률값의 크기는 달라진다. 이 숫자 자체를 개입 임계값으로 해석하면 안 된다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-03-confounder-score.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-03-confounder-score.png&quot; alt=&quot;가시 키포인트와 차폐 키포인트의 교란 점수 분포 비교&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 3. 차폐된 키포인트 쪽의 점수가 더 높다는 비교로, 저자들은 이 점수를 대리 지표로 사용하는 근거를 제시한다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;이름은 ‘교란 점수’지만, 실제 계산은 &lt;strong&gt;예측 분포의 집중도를 이용한 모호성 점수&lt;/strong&gt;다. 교란 원인을 직접 측정하거나, 불확실성을 원인별로 분리하는 계산은 아니다. 논문의 검증도 어떤 상황에서 이 점수가 유용한지를 뒷받침하는 근거로 읽는 것이 적절하다.&lt;/p&gt;

&lt;h3 id=&quot;cim--정준-임베딩은-정답-좌표인가&quot;&gt;CIM ② 정준 임베딩은 정답 좌표인가?&lt;/h3&gt;

&lt;p&gt;처음에는 ‘정준’이라는 말 때문에 표준적인 사람 자세나 평균 좌표를 떠올리기 쉽다. 하지만 여기서 정준 임베딩은 &lt;strong&gt;좌표가 아니라 학습 가능한 특징 벡터&lt;/strong&gt;다. 관절 종류마다 하나씩 있으며, 여러 이미지가 같은 테이블을 공유한다.&lt;/p&gt;

&lt;p&gt;예를 들어 왼쪽 손목 특징을 바꾸기로 했다면, 현재 이미지에서 얻은 왼쪽 손목 벡터 대신 테이블의 왼쪽 손목 벡터를 넣는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 식 (3)]&lt;/strong&gt;&lt;/p&gt;

\[f&apos;_k=\begin{cases}
z_k,&amp;amp;k\text{가 개입 대상으로 선택되었을 때}\\
f_k,&amp;amp;\text{그 외}
\end{cases}\]

&lt;p&gt;&lt;strong&gt;같은 기준 벡터를 넣는다고 모든 사람의 손목 좌표가 같아지는 것은 아니다.&lt;/strong&gt; 뒤의 GNN이 함께 들어온 어깨, 팔꿈치, 몸통 등의 특징을 반영하고, 최종 head가 그 결과를 좌표 분포로 바꾼다.&lt;/p&gt;

&lt;p&gt;정준 임베딩은 별도의 관절 데이터셋에서 미리 학습해 가져오는 것이 아니라, 보충자료와 공개 코드에서 확인할 수 있는 학습 파라미터다. 무작위로 초기화한 뒤 전체 모델의 손실을 통해 함께 학습한다. &lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/supplemental/Li_CIGPose_Causal_Intervention_CVPR_2026_supplemental.zip&quot;&gt;보충자료 C절&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;hgnn--가까운-관절에서-전신-관계까지&quot;&gt;HGNN — 가까운 관절에서 전신 관계까지&lt;/h3&gt;

&lt;p&gt;GNN은 두 수준의 관계를 사용한다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;단계&lt;/th&gt;
      &lt;th&gt;관계를 보는 단위&lt;/th&gt;
      &lt;th&gt;하는 일&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;국소 골격 그래프&lt;/td&gt;
      &lt;td&gt;관절과 연결된 이웃 관절&lt;/td&gt;
      &lt;td&gt;어깨·팔꿈치·손목처럼 연결된 특징을 모은다&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;의미 그룹 간 그래프&lt;/td&gt;
      &lt;td&gt;손, 팔, 얼굴 등의 그룹&lt;/td&gt;
      &lt;td&gt;그룹끼리 정보를 교환하고 관절 특징을 조절할 gate를 만든다&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;손목이 애매할 때 팔꿈치를 보는 것이 첫 단계라면, 팔의 특징을 몸 전체의 관계 속에서 다시 조절하는 것이 두 번째 단계다. 논문은 이를 local-to-global 구조로 설명한다.&lt;/p&gt;

&lt;p&gt;여기서 ‘관계’는 사람이 미리 정해둔 연결을 이용하지만, &lt;strong&gt;그 연결에서 어떤 특징을 전달하고 얼마나 반영할지는 학습된다.&lt;/strong&gt; 이전에 정리한 &lt;a href=&quot;/2026/05/gcn.html&quot;&gt;GCN 리뷰&lt;/a&gt;와 연결되는 부분이다. 다만 CIGPose의 실제 연산은 그 글의 기본 GCN 전파식을 그대로 사용하는 것이 아니라 EdgeConv와 그룹 gate를 사용한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-4-바꾸면서도-멀쩡한-관절은-지켜야-한다&quot;&gt;Part 4. 바꾸면서도 멀쩡한 관절은 지켜야 한다&lt;/h2&gt;

&lt;p&gt;학습할 때는 두 경로를 사용한다. 일부 특징을 바꾼 &lt;strong&gt;반사실 경로&lt;/strong&gt;가 최종 예측을 학습하고, 원래 특징으로 예측한 &lt;strong&gt;관찰 경로&lt;/strong&gt;가 안정적인 키포인트의 기준을 제공한다. 추론할 때는 반사실 경로를 사용한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문: 3.4절 전체 목적 함수]&lt;/strong&gt;&lt;/p&gt;

\[\mathcal L=\mathcal L_{\mathrm{kpt}}+\lambda\mathcal L_{\mathrm{cf}}\]

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;손실&lt;/th&gt;
      &lt;th&gt;비교 대상&lt;/th&gt;
      &lt;th&gt;적용 대상&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Keypoint loss&lt;/td&gt;
      &lt;td&gt;최종 예측과 정답 SimCC 분포&lt;/td&gt;
      &lt;td&gt;정답 가중치가 유효한 키포인트, 개입 여부와 무관&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Counterfactual consistency loss&lt;/td&gt;
      &lt;td&gt;관찰 경로의 예측과 반사실 경로의 예측&lt;/td&gt;
      &lt;td&gt;개입하지 않은 키포인트 중 정답 가중치가 유효한 것&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;예를 들어 왼쪽 손목만 바꿨다고 해보자. GNN에서는 관절들이 서로 연결되어 있으므로, 손목의 변화가 멀쩡했던 팔꿈치에도 영향을 줄 수 있다. 일관성 손실은 &lt;strong&gt;그 팔꿈치의 예측까지 불필요하게 흔들리지 않도록&lt;/strong&gt; 제약한다.&lt;/p&gt;

&lt;p&gt;관찰 경로는 gradient를 차단한 기준으로 사용한다. 그렇다고 GNN 전체가 학습되지 않는 것은 아니다. 같은 GNN 파라미터가 반사실 경로에서는 학습된다. 이 부분은 &lt;a href=&quot;/2026/09/cigpose-implementation.html#loss-and-gradients&quot;&gt;2편의 손실·gradient 해설&lt;/a&gt;에서 따로 풀어본다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-5-실험-결과--gnn과-cim의-기여&quot;&gt;Part 5. 실험 결과 — GNN과 CIM의 기여&lt;/h2&gt;

&lt;h3 id=&quot;같은-크기의-모델끼리-비교하기&quot;&gt;같은 크기의 모델끼리 비교하기&lt;/h3&gt;

&lt;p&gt;아래는 논문 Table 1에서 관련 행과 열을 발췌한 것이다. 입력은 모두 &lt;strong&gt;384×288&lt;/strong&gt;, 지표는 &lt;strong&gt;COCO-WholeBody V1.0의 Whole-body AP&lt;/strong&gt;, 원문 표는 flip test를 사용한다. 여기서는 UBody를 추가하지 않은 RTMPose와 CIGPose를 비교한다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;방법&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;GFLOPs&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Whole-body AP&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;RTMPose-l&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10.1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64.8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CIGPose-l&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10.7&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;66.3&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RTMPose-x&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;18.1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;65.3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CIGPose-x&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;18.7&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;67.0&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;em&gt;출처: 논문 Table 1 발췌. &lt;a href=&quot;/assets/cigpose/table-01-coco-wholebody-results.png&quot;&gt;원문 표 전체 보기&lt;/a&gt;. AP는 원문 표의 백분율 스케일이며, 수치는 직접 재현한 결과가 아니라 논문 보고값이다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;l 모델은 &lt;strong&gt;1.5 AP 포인트&lt;/strong&gt;, x 모델은 &lt;strong&gt;1.7 AP 포인트&lt;/strong&gt; 높아진다. x 모델에 UBody를 추가한 설정은 67.5 AP지만, 그것은 추가 학습 데이터를 사용한 별도 조건이므로 위의 네 행과 구분해서 봐야 한다.&lt;/p&gt;

&lt;h3 id=&quot;gnn만-넣어도-꽤-오르는데&quot;&gt;GNN만 넣어도 꽤 오르는데?&lt;/h3&gt;

&lt;p&gt;이 논문에서 가장 유심히 볼 표는 ablation이라고 생각한다. 인과 개입이라는 이름이 강렬해도, 모델에는 GNN도 새로 들어간다. 전체 성능 향상이 모두 CIM 덕분이라고 읽으면 안 된다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;구성 — x 모델 기준&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;AP&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;baseline 대비&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;RTMPose-x baseline&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;65.3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CIM만 추가&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;66.1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+0.8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;국소 골격 GNN만 추가&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;66.3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+1.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;국소 + 의미 그룹 GNN&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;66.8&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+1.5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CIM + 전체 GNN&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;67.0&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;+1.7&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;em&gt;출처: 논문 Table 4에서 x 모델 행을 발췌·재배열. &lt;a href=&quot;/assets/cigpose/table-04-ablation.png&quot;&gt;원문 ablation 표 보기&lt;/a&gt;. 각 증가량은 해당 표의 점수 차이다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;이 표에서는 전체 GNN을 붙였을 때 이미 66.8 AP이고, 여기에 CIM을 더하면 0.2 AP 포인트가 추가된다. CIM 단독으로도 향상이 있지만, 이 실험에서 &lt;strong&gt;구조적 관계 추론의 기여 역시 크다.&lt;/strong&gt; 각 모듈의 효과는 결합 방식에 따라 달라지므로 증가량을 독립적인 기여도로 단순 합산하지는 말자.&lt;/p&gt;

&lt;h3 id=&quot;숫자로-보기-어려운-변화&quot;&gt;숫자로 보기 어려운 변화&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-06-qualitative-comparison.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-06-qualitative-comparison.png&quot; alt=&quot;어려운 이미지에서 입력, RTMPose-x, CIGPose-x를 비교한 자세 예측 결과&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 논문 Fig. 6. 각 비교는 입력 이미지 → RTMPose-x → CIGPose-x 순서다. 복잡한 배경과 차폐 근처에서 관절 배치가 어떻게 달라졌는지 보면 된다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;정성적 예시는 구조적으로 더 자연스러워진 결과를 보여준다. 다만 선택된 사례 몇 장이 모든 배경이나 모든 가림 상황에서의 강건성을 보장하지는 않는다. 전체 지표, ablation, 그리고 실패 사례를 함께 봐야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;part-6-읽고-나서-남은-질문&quot;&gt;Part 6. 읽고 나서 남은 질문&lt;/h2&gt;

&lt;h3 id=&quot;특징-치환은-backdoor-평균과-같은-계산인가&quot;&gt;특징 치환은 backdoor 평균과 같은 계산인가?&lt;/h3&gt;

&lt;p&gt;처음의 질문으로 돌아왔다. 논문은 모든 문맥에 대해 평균하는 식을 목표로 제시하지만, 구현은 불확실한 관절을 골라 정준 벡터로 바꾼다. &lt;strong&gt;코드 안에서 문맥의 분포를 추정하고 그 합을 직접 계산하지는 않는다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;보충자료 A절은 저자들이 이 치환을 인과적 근사로 해석하는 근거를 설명한다. 하지만 그것을 곧바로 “일반적인 이미지 분포에서 backdoor 평균과 정확히 같은 계산임을 보였다”로 받아들이기는 어렵다. 목표, 근사의 가정, 실험으로 확인된 효과를 구분하는 편이 낫다.&lt;/p&gt;

&lt;p&gt;Fig. 2(b)도 저자들이 제안한 모형으로 읽어야 한다. 표준적인 do-연산은 개입한 변수로 &lt;strong&gt;들어오는 연결&lt;/strong&gt;을 제거하는 방식으로 정의된다. 그림에서 교란 연결에 그려진 X 표시를 보고, “특징을 바꾸면 모든 배경 영향이 실제로 끊겼다”고 단정하는 것은 설명을 건너뛰는 셈이다. &lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/supplemental/Li_CIGPose_Causal_Intervention_CVPR_2026_supplemental.zip&quot;&gt;논문 3.1절·보충자료 A절&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;정준 벡터가 현재 이미지와 무관한 공유 파라미터라는 점도, 학습 데이터의 모든 편향으로부터 자유롭다는 뜻은 아니다. 학습 데이터로 최적화되는 파라미터이기 때문이다. 이 구분이 논문을 읽으며 가장 신경 쓰였던 부분이다.&lt;/p&gt;

&lt;h3 id=&quot;불확실한데-맞는-자세-확신하는데-틀린-자세&quot;&gt;불확실한데 맞는 자세, 확신하는데 틀린 자세&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/cigpose/figure-08-failure-cases.png&quot;&gt;&lt;img src=&quot;/assets/cigpose/figure-08-failure-cases.png&quot; alt=&quot;CIGPose의 드문 자세 과잉 교정과 가로등 거짓 양성 실패 사례&quot; /&gt;&lt;/a&gt;
&lt;em&gt;출처: 보충자료 Fig. 8·F절. 위쪽은 드물지만 타당한 자세를 과하게 교정할 수 있는 사례, 아래쪽은 가로등을 사람으로 오인한 사례다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;보충자료는 두 가지 한계를 설명한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;드물지만 올바른 자세:&lt;/strong&gt; 익숙하지 않아서 불확실하다는 이유로 타당한 특징까지 바꿔버릴 수 있다. 개인의 실제 자세보다 평범한 패턴에 가까운 결과를 선호할 위험이다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;확신에 찬 오류:&lt;/strong&gt; 가로등처럼 사람을 닮은 구조에서 확신하며 틀리면 불확실성 기반 점수가 이를 잘 잡지 못한다. 특히 사람 검출부터 잘못된 top-down 입력을 CIM만으로 해결한다고 볼 수 없다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;논문은 이를 낮은 점수가 개입을 우회하는 문제로 설명한다. 한편 &lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;검토한 공개 설정&lt;/a&gt;은 추론에서도 Top-1을 선택한다. 따라서 코드 기준으로는 ‘관절을 하나도 바꾸지 않는다’기보다, &lt;strong&gt;점수의 순위가 확신에 찬 오류를 식별하고 거부할 기준이 되기 어렵다&lt;/strong&gt;고 구분해 적는 편이 정확하다.&lt;/p&gt;

&lt;h3 id=&quot;내-생각&quot;&gt;내 생각&lt;/h3&gt;

&lt;p&gt;CIGPose에서 흥미로웠던 것은 모든 관절 특징을 끝까지 같은 신뢰도로 사용하는 대신, &lt;strong&gt;의심스러운 특징을 바꾸고 관계를 이용해 보완한다&lt;/strong&gt;는 설계였다. 여기에 “멀쩡했던 예측은 지키자”는 일관성 손실까지 붙으니 각 구성의 목적이 연결된다.&lt;/p&gt;

&lt;p&gt;반면 인과 추론의 언어로 문제를 정리하는 것과, 구현의 인과적 타당성을 입증하는 것은 다른 단계다. 이 논문은 그 사이를 어떻게 읽어야 하는지 연습하기 좋은 사례이기도 했다. 좋은 결과가 나왔다는 사실과, 그 결과를 설명하는 이론이 어디까지 검증되었는지를 함께 따져보게 된다.&lt;/p&gt;

&lt;p&gt;다음 글에서는 이 아이디어를 &lt;strong&gt;실제 코드의 텐서 흐름과 gradient 경로&lt;/strong&gt;로 내려가 본다. 관절 벡터 하나가 바뀌면 어느 파라미터가 학습되는지까지 보면, 정준 임베딩이 무엇인지도 훨씬 구체적으로 이해할 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/html/Li_CIGPose_Causal_Intervention_Graph_Neural_Network_for_Whole-Body_Pose_Estimation_CVPR_2026_paper.html&quot;&gt;CIGPose 논문 — CVF Open Access&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/supplemental/Li_CIGPose_Causal_Intervention_CVPR_2026_supplemental.zip&quot;&gt;공식 보충자료 — 특히 A·C·F절&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose&quot;&gt;저자들의 공식 GitHub 저장소&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/tree/b7444d83d6a47efb3a492039e2fcf9dd43b90498&quot;&gt;이 글의 코드 대조 기준: commit b7444d8&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/53mins/CIGPose/blob/b7444d83d6a47efb3a492039e2fcf9dd43b90498/mmpose/projects/cigpose/wholebody_2d_keypoint/cigpose-x_8xb32-420e_coco-wholebody-384x288.py&quot;&gt;CIGPose-x COCO-WholeBody 설정&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Tue, 08 Sep 2026 00:00:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/cigpose.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/cigpose.html</guid>
        
        <category>deep-learning</category>
        
        <category>cigpose</category>
        
        <category>paper-review</category>
        
        <category>pose-estimation</category>
        
        <category>causal-inference</category>
        
        <category>graph</category>
        
        
        <category>Paper Review</category>
        
      </item>
    
      <item>
        <title>2. FFDP 수식과 학습 구조 - 좌표가 복원되는 과정을 따라가기</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/papers/ffdp/&quot;&gt;1편&lt;/a&gt;에서는 FFDP가 가려진 관절을 왜 주파수 영역에서 복원하는지 살펴봤다. 이제 블록 이름을 실제 계산으로 바꿔 볼 차례다.&lt;/p&gt;

&lt;p&gt;손필기에는 한 관절의 좌표를 두 개씩 묶어 평균과 차이를 계산한 흔적이 있다. 이어서 프레임과 관절 좌표를 행렬로 적고, DWT 식의 $m,n,k_1,k_2$가 어디를 가리키는지 하나씩 추적했다. 마지막에 남은 질문은 더 구체적이었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“인코더가 정확하게 어떤 입력, 어떤 출력을 해서 어떻게 학습을 하게 되는건지가 중요한 거 아냐?”&lt;/p&gt;

  &lt;p&gt;“인코더, 디코더의 입출력과 역전파 시 어디가 학습되는지, 어느 부분의 가중치가 업데이트 되는지 다 알려줘.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이 글은 그 질문에 답하기 위해 좌표 하나에서 출발한다. DWT로 네 부대역을 만들고, 학습된 계수 맵으로 조절한 뒤, 검출 좌표 중심의 확산과 DDIM을 거쳐 2D·3D 동작이 학습되는 경로를 따라간다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;읽는 기준&lt;/strong&gt;&lt;br /&gt;
논문 본문과 보충자료로 확인한 사실, 계산을 이해하기 위해 이번에 만든 예시를 구분한다. FFDP 공식 코드 공개 주소는 확인 시점에 접근할 수 없었다. 따라서 wavelet family, 경계 처리, 관절 수와 배열 순서, coefficient map의 활성함수, 정확한 DDIM schedule은 구현 사실로 단정하지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-한-관절의-좌표를-평균과-차이로-나눈다&quot;&gt;1. 한 관절의 좌표를 평균과 차이로 나눈다&lt;/h2&gt;

&lt;p&gt;손필기의 첫 계산은 다음 시계열이었다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 손필기에 남은 평균·반차 방식의 Haar 예시&lt;/strong&gt;&lt;/p&gt;

\[z=[10,12,14,30,31,32,33,34].\]

&lt;p&gt;인접한 값을 두 개씩 묶고 평균과 반차를 계산한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[L_1[k]=\frac{z[2k]+z[2k+1]}{2},
\qquad
H_1[k]=\frac{z[2k]-z[2k+1]}{2}.\]

&lt;p&gt;그러면 첫 level의 결과는 다음과 같다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[L_1=[11,22,31.5,33.5],
\qquad
H_1=[-1,-8,-0.5,-0.5].\]

&lt;p&gt;$H_1$의 두 번째 값이 $-8$인 이유는 $14$와 $30$의 차이가 크기 때문이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[H_1[1]=\frac{14-30}{2}=-8.\]

&lt;p&gt;이 값은 3번째와 4번째 위치 사이에 큰 변화가 있다는 사실을 드러낸다. 하지만 여기서 바로 “$-8$은 잡음이다”라고 말할 수는 없다. 사람이 실제로 팔을 빠르게 움직여도 큰 고주파 계수가 생기고, 가려진 관절의 검출 좌표가 튀어도 같은 현상이 생긴다.&lt;/p&gt;

&lt;p&gt;DWT가 해 주는 일은 변화의 위치와 크기를 다른 성분으로 펼쳐 보이는 데까지다. 그 변화가 보존할 동작인지 억제할 오류인지는 뒤의 학습 모델이 주변 프레임과 관절의 관계를 보고 판단해야 한다.&lt;/p&gt;

&lt;h3 id=&quot;더-깊은-level은-더-넓은-구간을-본다&quot;&gt;더 깊은 level은 더 넓은 구간을 본다&lt;/h3&gt;

&lt;p&gt;$L_1$에 같은 연산을 한 번 더 적용하면 다음 결과를 얻는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[L_2=[16.5,32.5],
\qquad
H_2=[-5.5,-1].\]

&lt;p&gt;$H_1$의 원소 하나는 원래 좌표 두 개의 차이를 요약한다. $H_2$의 원소 하나는 첫 level에서 두 쌍을 평균 낸 값들의 차이이므로 원래 좌표 네 개 범위에 대응한다. 손필기에서 이를 “마치 심층 convolution 연산처럼”이라고 적은 이유도 각 level에서 보는 시간 범위가 넓어지기 때문이다.&lt;/p&gt;

&lt;p&gt;다만 이것은 다중 level을 이해하기 위한 예시다. &lt;strong&gt;FFDP 보충자료가 밝힌 실제 decomposition level은 1&lt;/strong&gt;이다. 또한 위 계산은 $1/2$를 쓰는 평균·반차 방식이다. 정규직교 Haar에서 자주 쓰는 $1/\sqrt2$ 정규화와 같지 않으며, FFDP가 어떤 wavelet family와 정규화를 썼는지는 공개 문헌에 명시되지 않았다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-한-줄-좌표가-프레임과-관절의-행렬이-된다&quot;&gt;2. 한 줄 좌표가 프레임과 관절의 행렬이 된다&lt;/h2&gt;

&lt;p&gt;한 관절의 한 좌표만 보면 입력은 $z$라는 벡터다. 실제 모델은 여러 프레임의 여러 관절을 함께 본다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 2D 키포인트 시퀀스의 형태&lt;/strong&gt;&lt;/p&gt;

\[P_t\in\mathbb{R}^{N\times2J}.\]

&lt;ul&gt;
  &lt;li&gt;$N$: 한 입력 window의 프레임 수&lt;/li&gt;
  &lt;li&gt;$J$: 사용하는 관절 수&lt;/li&gt;
  &lt;li&gt;$2J$: 각 관절의 $x,y$ 좌표를 펼친 열의 수&lt;/li&gt;
  &lt;li&gt;$t$: diffusion의 noise level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;[보충자료]&lt;/strong&gt; FFDP의 temporal window는 36프레임이다. 따라서 한 번의 모델 호출은 36프레임 전체의 좌표 배열을 받는다. 손필기에는 $36\times28$, 즉 14관절을 가정한 크기가 적혀 있었지만, FFDP 본문과 보충자료만으로 실제 $J=14$와 열 순서를 확정할 수는 없다. OcMotion과 연결된 다른 저장소에서 14관절을 썼다는 사실도 FFDP 모델의 입력 크기를 대신 증명하지 않는다.&lt;/p&gt;

&lt;h3 id=&quot;mn이-움직이는-두-축&quot;&gt;$m,n$이 움직이는 두 축&lt;/h3&gt;

&lt;p&gt;논문의 DWT 식은 두 축을 한꺼번에 처리한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (3)&lt;/strong&gt;&lt;/p&gt;

\[y_{h,v}[k_1,k_2]
=\sum_m\sum_n
P[m,n]f_h[m-2k_1]f_v[n-2k_2],
\qquad h,v\in\{L,H\}.\]

&lt;p&gt;이 글에서는 다음과 같이 고정해서 읽는다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;기호&lt;/th&gt;
      &lt;th&gt;이 글에서의 의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;$m$&lt;/td&gt;
      &lt;td&gt;입력의 프레임축 인덱스&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$n$&lt;/td&gt;
      &lt;td&gt;펼친 관절·좌표축 인덱스&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$k_1$&lt;/td&gt;
      &lt;td&gt;시간축 필터링 뒤 출력 위치&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$k_2$&lt;/td&gt;
      &lt;td&gt;관절·좌표축 필터링 뒤 출력 위치&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$f_L$&lt;/td&gt;
      &lt;td&gt;low-pass filter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$f_H$&lt;/td&gt;
      &lt;td&gt;high-pass filter&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;손필기 3쪽에서는 $m$을 시간축, $n$을 관절·좌표축으로 적었지만 5쪽에서는 둘을 반대로 적었다. 계산의 핵심은 기호 이름 자체가 아니라 &lt;strong&gt;각 필터가 어느 축을 따라 이동하는가&lt;/strong&gt;다. 그래서 여기서는 논문 식의 첫 합을 시간축, 두 번째 합을 펼친 좌표축으로 일관되게 사용한다.&lt;/p&gt;

&lt;h3 id=&quot;stride-2가-입력을-두-칸씩-묶는다&quot;&gt;stride 2가 입력을 두 칸씩 묶는다&lt;/h3&gt;

&lt;p&gt;1차원 low-pass 식부터 보면 인덱스가 선명해진다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 손필기의 1차원 식을 정리한 형태&lt;/strong&gt;&lt;/p&gt;

\[y[k]=\sum_m z[m]f_L[m-2k].\]

&lt;p&gt;$f_L[0]$과 $f_L[1]$에서만 값이 있고 나머지는 0이라고 하자. 그러면 $k=0$일 때는 $z[0],z[1]$, $k=1$일 때는 $z[2],z[3]$만 남는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[\begin{aligned}
y[0]&amp;amp;=z[0]f_L[0]+z[1]f_L[1],\\
y[1]&amp;amp;=z[2]f_L[0]+z[3]f_L[1],\\
y[2]&amp;amp;=z[4]f_L[0]+z[5]f_L[1].
\end{aligned}\]

&lt;p&gt;설명용 필터에서 support 밖의 값은 0이므로 해당 항은 계산에서 제외된다. 복잡해 보이는 $m-2k$는 결국 필터를 두 칸씩 옮기는 인덱스다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/ffdp/explainer-dwt-grid.svg&quot;&gt;&lt;img src=&quot;/assets/ffdp/explainer-dwt-grid.svg&quot; alt=&quot;DWT가 시간축과 펼친 관절 좌표축을 따라 이동하는 작은 행렬 예시&quot; /&gt;&lt;/a&gt;
&lt;em&gt;[설명용 유도] 작은 행렬에 평균·반차 필터를 적용해 LL·LH·HL·HH가 만들어지는 위치를 나타낸 그림이다. FFDP의 미명시 wavelet 설정을 재현한 그림은 아니다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;그림의 입력과 결과는 다음과 같다. 첫 글자는 시간축, 두 번째 글자는 펼친 좌표 열축의 filter다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 실행으로 검증한 계산&lt;/strong&gt;&lt;/p&gt;

\[P=
\begin{bmatrix}
10&amp;amp;12&amp;amp;20&amp;amp;22\\14&amp;amp;16&amp;amp;24&amp;amp;26\\18&amp;amp;20&amp;amp;28&amp;amp;30\\30&amp;amp;32&amp;amp;40&amp;amp;50
\end{bmatrix},\]

\[LL=\begin{bmatrix}13&amp;amp;23\\25&amp;amp;37\end{bmatrix},\quad
LH=\begin{bmatrix}-1&amp;amp;-1\\-1&amp;amp;-3\end{bmatrix},\]

\[HL=\begin{bmatrix}-2&amp;amp;-2\\-6&amp;amp;-8\end{bmatrix},\quad
HH=\begin{bmatrix}0&amp;amp;0\\0&amp;amp;2\end{bmatrix}.\]

&lt;p&gt;같은 평균·반차 규칙으로 iDWT를 적용하면 원래 $P$가 정확히 복원된다. 계산은 &lt;a href=&quot;/assets/code/ffdp-study/verify_examples.py&quot;&gt;검증 스크립트&lt;/a&gt;에서 다시 실행할 수 있다.&lt;/p&gt;

&lt;p&gt;2차원에서는 시간축과 관절·좌표축에 low/high filter를 조합해 네 부대역을 만든다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;부대역&lt;/th&gt;
      &lt;th&gt;시간축&lt;/th&gt;
      &lt;th&gt;펼친 관절·좌표축&lt;/th&gt;
      &lt;th&gt;읽을 수 있는 성질&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;LL&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
      &lt;td&gt;두 축에서 완만한 변화&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LH&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;시간에는 완만하고 인접 열 사이 차이는 큰 변화&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HL&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
      &lt;td&gt;시간에는 빠르고 인접 열에서는 비슷한 변화&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HH&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;두 축 모두에서 빠른 변화&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;여기서 “LH는 팔을 든 동작”, “HH는 떨림”처럼 동작 이름을 고정하면 안 된다. 부대역은 필터 응답의 종류다. 실제 움직임의 의미는 관절 배열, 주변 프레임, 모델이 학습한 관계까지 함께 봐야 정해진다.&lt;/p&gt;

&lt;p&gt;펼친 관절·좌표축에는 더 근본적인 질문도 남는다. 배열에서 이웃한 열이 골격에서 이웃한 관절이라는 보장은 없다. $x,y$가 어떤 순서로 배치되는지에 따라 high-pass 응답도 달라진다. 정확한 joint ordering이 공개되지 않은 현재에는 “공간축 DWT가 골격의 국소 이웃을 직접 본다”고 단정할 수 없다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-계수를-만든-뒤-모델이-배율을-학습한다&quot;&gt;3. 계수를 만든 뒤 모델이 배율을 학습한다&lt;/h2&gt;

&lt;p&gt;DWT는 고정 변환이다. 같은 입력과 같은 filter를 넣으면 같은 부대역을 만든다. DWT 자체가 데이터에서 “이 값은 잡음”이라는 규칙을 배우지는 않는다.&lt;/p&gt;

&lt;p&gt;FFDP의 prior network는 네 부대역을 받아 refined coefficient와 coefficient map을 함께 출력한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (4)&lt;/strong&gt;&lt;/p&gt;

\[\hat y_{h,v},m_{h,v}=F_{h,v}(y).\]

&lt;p&gt;그리고 둘을 원소별로 곱한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] coefficient selection&lt;/strong&gt;&lt;/p&gt;

\[\bar y_{h,v}=m_{h,v}\odot\hat y_{h,v}.\]

&lt;p&gt;$\odot$는 같은 위치의 값을 곱하는 Hadamard product다. 예를 들어 한 위치에서 refined coefficient가 $-8$이고 map 값이 $0.2$라면 출력은 $-1.6$이 된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[(-8)\times0.2=-1.6.\]

&lt;p&gt;이 숫자는 곱의 역할만 보여 주는 예시다. 논문은 $m$의 범위나 활성함수를 밝히지 않았으므로 실제 값이 $0$과 $1$ 사이인지도 확인할 수 없다.&lt;/p&gt;

&lt;p&gt;논문이 이 과정을 coefficient &lt;strong&gt;selection&lt;/strong&gt;이라고 부르지만, top-k나 binary mask를 사용한다고 명시하지는 않는다. 확인 가능한 정의는 각 위치에서 예측한 값들을 원소별로 곱하는 연산이다. 모든 고주파를 제거하는 fixed cutoff로 해석해서도 안 된다.&lt;/p&gt;

&lt;p&gt;필터링한 네 부대역은 iDWT로 다시 좌표 배열이 된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 논문 식 (5)를 압축한 표기&lt;/strong&gt;&lt;/p&gt;

\[P=\operatorname{iDWT}(\bar y_{LL},\bar y_{HL},\bar y_{LH},\bar y_{HH}).\]

&lt;p&gt;이 간단한 표기는 논문 식 (5)의 합을 역할 중심으로 줄여 쓴 것이다. 모델은 좌표를 직접 한 번에 회귀하는 대신, 주파수 위치별 계수를 조절한 뒤 좌표 영역으로 돌아온다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-가우시안은-좌표를-고치는-장치가-아니라-출발점이다&quot;&gt;4. 가우시안은 좌표를 고치는 장치가 아니라 출발점이다&lt;/h2&gt;

&lt;p&gt;손필기 다음 부분에는 “가림 영역의 키포인트를 가우시안으로 억제한다는 의미”라는 질문이 있었다. 여기에는 분포와 denoiser의 역할이 섞여 있다.&lt;/p&gt;

&lt;p&gt;가우시안은 가림 잡음을 억제하지 않는다. 신뢰하기 어려운 검출 좌표의 불확실성을 표현하고 초기 좌표 표본을 만드는 역할을 한다. 실제 복원은 학습된 network가 담당한다.&lt;/p&gt;

&lt;p&gt;한 관절의 검출 좌표가 다음과 같다고 하자.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 손필기의 좌표 예시&lt;/strong&gt;&lt;/p&gt;

\[p=(100,200).\]

&lt;p&gt;논문은 불신뢰 관절에 대해 이 좌표를 평균으로 하고 단위행렬을 공분산으로 하는 분포를 둔다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 불신뢰 관절의 초기분포&lt;/strong&gt;&lt;/p&gt;

\[\mathcal N(p,I).\]

&lt;p&gt;여기서 모델에 들어가는 것은 “가우시안”이라는 수학 객체 자체가 아니다. 분포에서 뽑은 구체적인 2차원 좌표다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] $\mathcal N(p,I)$에서 좌표 표본을 만드는 정의&lt;/strong&gt;&lt;/p&gt;

\[\epsilon\sim\mathcal N(0,I),
\qquad
p_T=p+\epsilon.\]

&lt;p&gt;같은 $p$에서도 표본은 매번 달라질 수 있다. 하지만 random sample과 학습 가능한 복원 함수는 충돌하지 않는다. 같은 정답을 여러 잡음 상태로 보여 주면 network는 주변 관절과 프레임을 이용해 깨끗한 좌표를 되찾는 공통 규칙을 학습할 수 있다.&lt;/p&gt;

&lt;p&gt;confidence는 어느 관절을 믿을지 나누는 기준이다. &lt;strong&gt;[보충자료]&lt;/strong&gt; FFDP는 ViTPose++-B와 threshold 0.7을 사용한다. 다만 0.7은 보충자료에서 선택한 설정이지 모든 detector와 dataset에 보편적인 임계값은 아니다. confidence가 관절별 가우시안 분산으로 직접 바뀐다는 설명도 논문에는 없다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-forward-식은-정답에서-검출-좌표-쪽으로-이동한다&quot;&gt;5. forward 식은 정답에서 검출 좌표 쪽으로 이동한다&lt;/h2&gt;

&lt;p&gt;일반 diffusion은 깨끗한 데이터를 표준정규분포 쪽으로 흐리게 만드는 설명으로 자주 배운다. FFDP는 detector 좌표라는 중심을 활용한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (1)&lt;/strong&gt;&lt;/p&gt;

\[q(p_t\mid\hat p_0)
=p+\sqrt{\hat\alpha_t}(\hat p_0-p)
+\sqrt{1-\hat\alpha_t}\epsilon,
\qquad
\epsilon\sim\mathcal N(0,I).\]

&lt;p&gt;&lt;strong&gt;[논문 원문] 누적 계수&lt;/strong&gt;&lt;/p&gt;

\[\hat\alpha_t=\prod_{i=0}^{t}\alpha_i.\]

&lt;p&gt;기호를 좌표의 종류로 읽어 보자.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;기호&lt;/th&gt;
      &lt;th&gt;의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;$p$&lt;/td&gt;
      &lt;td&gt;2D detector가 낸 관절 좌표&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$\hat p_0$&lt;/td&gt;
      &lt;td&gt;학습 때 사용하는 깨끗한 정답 좌표&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$p_t$&lt;/td&gt;
      &lt;td&gt;noise level $t$의 noisy 좌표 표본&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$\epsilon$&lt;/td&gt;
      &lt;td&gt;표준정규분포에서 뽑은 잡음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$\hat\alpha_t$&lt;/td&gt;
      &lt;td&gt;처음부터 $t$까지 곱한 누적 계수&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;누적 계수가 1에 가까우면 정답 좌표의 영향이 크다. 0에 가까워질수록 중심이 detector 좌표 $p$ 쪽으로 이동하고 잡음 비중이 커진다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 식 (1)의 조건부 평균&lt;/strong&gt;&lt;/p&gt;

\[\mathbb E[p_t\mid\hat p_0,p]
=(1-\sqrt{\hat\alpha_t})p
+\sqrt{\hat\alpha_t}\hat p_0.\]

&lt;p&gt;$p_T=p+\epsilon$은 $\mathcal N(p,I)$에서 표본을 만드는 정의다. 이와 달리 forward 식 (1)의 유한한 마지막 시점이 그 표본과 같아지는지는 $\hat\alpha_T$에 달려 있다. $\hat\alpha_T$가 충분히 작으면 식 (1)이 detector 중심 가우시안에 &lt;strong&gt;가까워진다&lt;/strong&gt;고 설명할 수 있지만, 공개된 schedule에서 정확히 0인지는 확인되지 않았다. 두 식은 학습과 추론에 서로 다른 forward 공식을 쓴다는 뜻이 아니다.&lt;/p&gt;

&lt;p&gt;또한 이 확산은 불신뢰 관절에 적용된다. 논문은 reliable keypoint가 forward와 reverse diffusion에서 원래 관측값을 유지한다고 명시한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-36프레임과-ddim-5-step은-다른-축이다&quot;&gt;6. 36프레임과 DDIM 5 step은 다른 축이다&lt;/h2&gt;

&lt;p&gt;대화에서 가장 오래 걸린 지점은 두 종류의 시간이 같은 문자 주변에 나타난다는 점이었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“5번 반복한다고 했는데 어떻게 5번 반복해서 36프레임이 나오는거야?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;36은 영상 프레임 축의 길이다. DDIM 5 step은 같은 36프레임 좌표 배열을 서로 다른 noise level에서 다섯 번 정제하는 계산 축이다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/ffdp/explainer-diffusion-axes.svg&quot;&gt;&lt;img src=&quot;/assets/ffdp/explainer-diffusion-axes.svg&quot; alt=&quot;36프레임 영상축과 DDIM의 noise-level 축을 분리한 그림&quot; /&gt;&lt;/a&gt;
&lt;em&gt;36프레임 전체가 각 sampling step을 통과한다. 다섯 번의 모델 호출이 다섯 프레임을 생성하는 구조가 아니다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[보충자료]&lt;/strong&gt; 학습 diffusion timestep은 100이고, 추론은 DDIM 5 step을 사용한다. 이것은 다음 뜻이다.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;한 모델 평가의 입력: 36프레임 전체의 noisy keypoint sequence
                     ↓
다음 noise level:    같은 36프레임 전체의 다음 상태 sequence
                     ↓
최종 출력:           같은 window에 대한 복원 결과
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;손필기에는 100개 중 “무작위 5개”를 고른다는 설명도 있었다. 하지만 보충자료는 5-step DDIM이라고만 밝힌다. 매 추론마다 무작위 시점을 고르는지, 어떤 index를 선택하는지, noise schedule과 DDIM의 $\eta$가 무엇인지는 명시하지 않았다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;100,75,50,25,0&lt;/code&gt;처럼 상태 다섯 개를 나열하면 상태 사이 이동은 네 번이라는 점도 조심해야 한다. 이 숫자를 FFDP의 정확한 다섯 번 모델 호출 schedule로 사용할 수 없다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-현재-입력과-깨끗한-좌표-예측을-ddim-식에-넣는다&quot;&gt;7. 현재 입력과 깨끗한 좌표 예측을 DDIM 식에 넣는다&lt;/h2&gt;

&lt;p&gt;논문은 reverse distribution을 다음처럼 쓴다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (2)&lt;/strong&gt;&lt;/p&gt;

\[q(P_{t-1}\mid P_t)
=\mathcal N\!\left(P_{t-1};\mu_\alpha(P_t),\tilde\gamma_tI\right).\]

&lt;p&gt;이어 모델이 매 timestep마다 $P_0$를 예측하고, 식 (1)을 이용해 다음 상태를 구성한다고 설명한다. 그러나 $\mu_\alpha$의 닫힌식과 DDIM update를 본문에 풀어 쓰지는 않는다.&lt;/p&gt;

&lt;p&gt;그래서 다음 계산은 FFDP의 공개 구현을 옮긴 것이 아니다. FFDP 식 (1)을 detector-centered residual 좌표로 바꾸고, 원 DDIM의 deterministic update를 적용해 현재 입력과 깨끗한 예측의 역할을 보여 주는 유도다.&lt;/p&gt;

&lt;p&gt;먼저 detector 좌표 $p$를 원점으로 옮긴다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[z_t=P_t-p,
\qquad
z_0=P_0-p.\]

&lt;p&gt;그러면 forward 식은 표준 diffusion 형태가 된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[z_t=\sqrt{\bar\alpha_t}z_0
+\sqrt{1-\bar\alpha_t}\epsilon.\]

&lt;p&gt;현재 noisy input $P_t$와 모델이 방금 예측한 clean pose $P_0^{\mathrm{pred},(t)}$를 이용하면, 둘과 일관된 예측 잡음을 역산할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도]&lt;/strong&gt;&lt;/p&gt;

\[\epsilon_t^{\mathrm{pred}}
=\frac{
P_t-p-\sqrt{\bar\alpha_t}
\left(P_0^{\mathrm{pred},(t)}-p\right)
}{\sqrt{1-\bar\alpha_t}}.\]

&lt;p&gt;여기서 $P_t$는 현재 상태에, $P_0^{\mathrm{pred},(t)}$는 clean coordinate 항에 들어간다. 이전 DDIM 결과를 식의 정답 자리에 단순 대입하는 계산이 아니다.&lt;/p&gt;

&lt;p&gt;더 이른 noise level $s&amp;lt;t$로 이동할 때 deterministic DDIM, 즉 $\eta=0$인 설명용 갱신은 다음과 같다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도: DDIM 원 논문의 $\eta=0$ 구조를 FFDP residual 좌표에 적용]&lt;/strong&gt;&lt;/p&gt;

\[P_s
=p+\sqrt{\bar\alpha_s}
\left(P_0^{\mathrm{pred},(t)}-p\right)
+\sqrt{1-\bar\alpha_s}\epsilon_t^{\mathrm{pred}}.\]

&lt;p&gt;$\bar\alpha_t$는 현재 noise level, $\bar\alpha_s$는 이동할 noise level의 누적 계수다. 모델을 다시 호출하면 새로운 $P_s$에서 clean pose를 다시 예측하므로 $\epsilon^{\mathrm{pred}}$도 매 step 새로 계산된다.&lt;/p&gt;

&lt;p&gt;작은 숫자로 대입하면 각 항이 더 잘 보인다. 검출 좌표 $p=[100,200]$, clean prediction $[104,192]$, 예측 잡음 $[0.25,-0.5]$, $\bar\alpha_t=0.25$, $\bar\alpha_s=0.64$로 둔다. 먼저 forward 관계로 만든 현재 상태는 $P_t=[102.216506\ldots,195.566987\ldots]$다. 이 $P_t$와 clean prediction을 예측 잡음 식에 넣으면 $[0.25,-0.5]$가 다시 나오고, update 뒤 좌표는 $P_s=[103.35,193.3]$이 된다. 이 값들은 스크립트로 검산한 교육용 예시이며 FFDP의 실제 schedule이나 출력이 아니다.&lt;/p&gt;

&lt;p&gt;이 유도의 경계는 분명하다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;FFDP 문헌은 DDIM 5 step을 사용한다고 밝힌다.&lt;/li&gt;
  &lt;li&gt;FFDP가 실제로 $\eta=0$을 썼는지는 밝히지 않는다.&lt;/li&gt;
  &lt;li&gt;선택 timestep, $\alpha$ schedule, variance 설정도 공개 문헌에 없다.&lt;/li&gt;
  &lt;li&gt;따라서 위 식은 &lt;strong&gt;[설명용 유도]&lt;/strong&gt;이며 FFDP 실제 sampler의 확정 구현이 아니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;초기 표본을 뽑은 뒤 매 step에서 detector를 다시 실행하거나 가우시안에서 독립 표본을 다시 뽑는 것도 아니다. 현재 상태를 network가 정제해 다음 상태로 넘긴다. reliable keypoint는 그 과정에서 원래 관측 좌표로 유지된다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-stage-1은-2d-복원-규칙을-학습한다&quot;&gt;8. Stage 1은 2D 복원 규칙을 학습한다&lt;/h2&gt;

&lt;p&gt;이제 수식을 module과 loss에 연결할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] Stage 1의 전체 흐름. [보충자료] Encoder와 2D decoder의 구성.&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GT 2D keypoint + detector 좌표 + noise level
    → noisy 2D sequence P_t 구성
    → 고정 DWT
    → spatial Transformer / temporal Transformer 기반 prior encoder
    → 2D decoder: LayerNorm + Linear
    → refined coefficient × coefficient map
    → 고정 iDWT
    → clean 2D prediction P_0^pred
    → L1 keypoint loss
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (6)&lt;/strong&gt;&lt;/p&gt;

\[\mathcal L_{keyp}=|P-\hat P|.\]

&lt;p&gt;논문은 이를 $L_1$ keypoint loss라고 설명한다. 합인지 평균인지 같은 reduction 세부는 명시하지 않는다.&lt;/p&gt;

&lt;p&gt;학습 표본과 예측 함수를 함께 쓰면 역할이 더 잘 보인다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] Stage 1의 계산 관계를 압축한 표기&lt;/strong&gt;&lt;/p&gt;

\[P_0^{\mathrm{pred}}
=\operatorname{iDWT}\!\left(
F_\theta(\operatorname{DWT}(P_t))
\right),
\qquad
\mathcal L_{2D}
=\lVert P_0^{\mathrm{pred}}-P_0^{GT}\rVert_1.\]

&lt;p&gt;$F_\theta$는 실제 논문의 한 함수 이름이 아니라 prior encoder, 2D decoder, coefficient scaling을 묶어 나타낸 설명용 기호다.&lt;/p&gt;

&lt;p&gt;Stage 1에서 학습되는 것은 prior encoder와 2D decoder의 parameter다. DWT와 iDWT filter는 고정 연산이라 업데이트할 parameter가 없다. ViTPose detector까지 이 loss로 공동 학습한다는 근거도 없다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[보충자료]&lt;/strong&gt; prior encoder는 spatial information과 temporal information을 위한 서로 분리된 두 Transformer를 사용한다. &lt;strong&gt;2D decoder는 Transformer가 아니라 LayerNorm과 Linear로 구성된다.&lt;/strong&gt; head 수, layer 수, embedding dimension은 공개 문헌에 명시되지 않았다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;9-stage-2는-고정한-prior를-3d-복원에-사용한다&quot;&gt;9. Stage 2는 고정한 prior를 3D 복원에 사용한다&lt;/h2&gt;

&lt;p&gt;Stage 1이 끝나면 논문은 학습된 prior encoder를 고정한다. 이어 3D Transformer decoder가 3D motion의 wavelet subband와 shape parameter를 예측한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (7)&lt;/strong&gt;&lt;/p&gt;

\[Y_{h,v},\beta=D(y,z,I).\]

&lt;ul&gt;
  &lt;li&gt;$y$: DWT로 분해한 2D 입력 subband&lt;/li&gt;
  &lt;li&gt;$z$: 고정된 prior encoder가 만든 latent embedding&lt;/li&gt;
  &lt;li&gt;$I$: RGB image에서 추출한 ViT feature&lt;/li&gt;
  &lt;li&gt;$Y_{h,v}$: 3D motion의 예측 subband&lt;/li&gt;
  &lt;li&gt;$\beta$: 사람의 shape parameter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예측한 3D subband는 iDWT를 거쳐 motion parameter가 된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (8)&lt;/strong&gt;&lt;/p&gt;

\[x=\operatorname{iDWT}(Y).\]

&lt;p&gt;논문의 사전 정의에서 $x$는 pose $\theta$와 translation $\tau$로 구성된다. SMPL을 통해 3D joint와 vertex를 얻고, 카메라로 2D에 재투영한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[논문 원문] 식 (9)&lt;/strong&gt;&lt;/p&gt;

\[\mathcal L
=\mathcal L_{smpl}
+\mathcal L_{joint}
+\mathcal L_{verts}
+\mathcal L_{keyp}.\]

&lt;p&gt;&lt;strong&gt;[논문 원문] 손실 항의 정의&lt;/strong&gt;&lt;/p&gt;

\[\mathcal L_{smpl}
=\lVert[\beta,\theta]-[\hat\beta,\hat\theta]\rVert_2^2,\]

\[\mathcal L_{joint}
=\lVert J_{3D}-\hat J_{3D}\rVert_2^2,
\qquad
\mathcal L_{vert}
=\lVert V_{3D}-\hat V_{3D}\rVert_2^2.\]

&lt;p&gt;$\mathcal L_{keyp}$는 재투영한 2D keypoint에 식 (6)의 $L_1$ loss를 적용한다. 논문 식에는 네 항의 단순 합으로 적혀 있지만, 실제 reduction과 loss weight는 공개 문헌에 명시되지 않았다. 이를 모두 구현 가중치 1이라고 단정하지 않는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[보충자료]&lt;/strong&gt; 재투영에는 CLIFF perspective camera를 사용한다. principal point는 image center, focal length는 $\sqrt{w^2+h^2}$로 추정한다.&lt;/p&gt;

&lt;p&gt;Stage 2에서 확실히 말할 수 있는 업데이트 범위는 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;구성요소&lt;/th&gt;
      &lt;th&gt;Stage 1&lt;/th&gt;
      &lt;th&gt;Stage 2&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;DWT/iDWT filter&lt;/td&gt;
      &lt;td&gt;고정&lt;/td&gt;
      &lt;td&gt;고정&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Prior encoder&lt;/td&gt;
      &lt;td&gt;2D loss로 학습&lt;/td&gt;
      &lt;td&gt;논문에서 freeze 명시&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2D decoder&lt;/td&gt;
      &lt;td&gt;2D loss로 학습&lt;/td&gt;
      &lt;td&gt;Stage 2의 사용·동결 범위 미명시&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3D Transformer decoder&lt;/td&gt;
      &lt;td&gt;해당 없음&lt;/td&gt;
      &lt;td&gt;3D·재투영 loss로 학습&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RGB ViT feature extractor&lt;/td&gt;
      &lt;td&gt;해당 없음&lt;/td&gt;
      &lt;td&gt;freeze/update 여부 미명시&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ViTPose detector&lt;/td&gt;
      &lt;td&gt;입력 keypoint 제공&lt;/td&gt;
      &lt;td&gt;공동 학습 근거 없음&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;“Stage 2에서 prior encoder를 고정한다”는 사실로 “ViT도 고정한다”거나 “ViT는 반드시 3D decoder와 함께 fine-tuning한다”고 결론 내릴 수 없다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;10-고정된-idwt를-지나도-gradient는-흐른다&quot;&gt;10. 고정된 iDWT를 지나도 gradient는 흐른다&lt;/h2&gt;

&lt;p&gt;고정된 연산에는 학습할 parameter가 없다. 그렇다고 그 연산의 앞쪽까지 gradient가 갈 수 없다는 뜻은 아니다.&lt;/p&gt;

&lt;p&gt;iDWT를 고정 선형 변환 $A$로 단순화하고, decoder 출력 coefficient를 $y_\theta$라고 하자.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[설명용 유도] 고정 선형 변환을 지나는 연쇄법칙&lt;/strong&gt;&lt;/p&gt;

\[x=Ay_\theta,
\qquad
\frac{\partial\mathcal L}{\partial y_\theta}
=A^\top\frac{\partial\mathcal L}{\partial x}.\]

&lt;p&gt;$A$ 자체를 바꾸지는 않지만, $A^\top$을 통해 decoder 출력에 대한 gradient를 계산할 수 있다. 그래서 좌표 영역이나 SMPL 결과에 둔 loss로 주파수 영역 coefficient를 예측한 decoder를 학습할 수 있다.&lt;/p&gt;

&lt;p&gt;Stage 1에서는 $L_1$ loss의 gradient가 iDWT와 coefficient scaling을 지나 2D decoder와 prior encoder로 흐른다. Stage 2에서는 SMPL·joint·vertex·reprojection loss의 gradient가 3D decoder 쪽으로 흐르지만 prior encoder의 parameter는 freeze되어 업데이트되지 않는다.&lt;/p&gt;

&lt;p&gt;여기서도 계산 가능성과 실제 optimizer 설정은 구분해야 한다. 문헌은 encoder freeze를 밝히지만, 공개 코드가 없으므로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach&lt;/code&gt; 위치, optimizer parameter group, ViT의 update 여부를 확인할 수 없다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;계산을-끝내고-다시-보는-전체-경로&quot;&gt;계산을 끝내고 다시 보는 전체 경로&lt;/h2&gt;

&lt;p&gt;처음에는 DWT가 평균과 차이를 만든다는 사실만 보였다. 전체 경로를 따라가면 각 단계의 책임이 나뉜다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;DWT는 좌표 변화를 네 부대역으로 펼친다.&lt;/li&gt;
  &lt;li&gt;Prior network는 refined coefficient와 coefficient map을 예측한다.&lt;/li&gt;
  &lt;li&gt;$m\odot\hat y$는 각 위치의 coefficient를 학습된 배율로 조절한다.&lt;/li&gt;
  &lt;li&gt;iDWT는 조절된 부대역을 다시 좌표로 복원한다.&lt;/li&gt;
  &lt;li&gt;검출 좌표 중심 가우시안은 불신뢰 관절의 초기 표본을 만든다.&lt;/li&gt;
  &lt;li&gt;Forward 식은 학습 때 GT와 detector 좌표 사이의 noisy sample을 만든다.&lt;/li&gt;
  &lt;li&gt;DDIM은 같은 36프레임 배열을 여러 noise level에서 반복해 정제한다.&lt;/li&gt;
  &lt;li&gt;Stage 1은 2D 복원 prior를 학습하고, Stage 2는 그 encoder를 고정해 3D decoder를 학습한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이제 $-8$ 같은 큰 high-frequency coefficient를 다시 보면 질문도 달라진다. “이 값이 잡음인가”를 DWT 하나에 묻는 대신, &lt;strong&gt;어떤 주변 관측을 조건으로 network가 이 위치의 배율을 어떻게 조절했는가&lt;/strong&gt;를 물어야 한다.&lt;/p&gt;

&lt;p&gt;아직 재현을 위해 남은 정보도 많다. wavelet family와 boundary mode, joint ordering, coefficient map의 범위, Transformer 세부 구조, noise schedule, DDIM의 $\eta$와 selected timestep은 공개 문헌에서 확인되지 않았다. 관절 순서를 바꾸거나 frame을 shift했을 때 coefficient가 어떻게 달라지는지, 실제 급변과 검출 오류가 어떤 부대역에서 구분되는지도 아직 수행하지 않은 후속 실험이다.&lt;/p&gt;

&lt;p&gt;이 미확인 항목을 구현 기본값으로 채우지 않는 것이 중요하다. 현재 확인할 수 있는 수식과 학습 경로는 분명하지만, 독립 재현에서 선택한 값은 논문의 확정 설정과 따로 기록해야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Buzhen Huang et al., &lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/html/Huang_Occluded_Human_Body_Capture_with_Frequency_Domain_Denoising_Prior_CVPR_2026_paper.html&quot;&gt;&lt;em&gt;Occluded Human Body Capture with Frequency Domain Denoising Prior&lt;/em&gt;&lt;/a&gt;, CVPR 2026.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/papers/Huang_Occluded_Human_Body_Capture_with_Frequency_Domain_Denoising_Prior_CVPR_2026_paper.pdf&quot;&gt;FFDP 공식 본문 PDF&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openaccess.thecvf.com/content/CVPR2026/supplemental/Huang_Occluded_Human_Body_CVPR_2026_supplemental.pdf&quot;&gt;FFDP 공식 보충자료 PDF&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Jiaming Song, Chenlin Meng, Stefano Ermon, &lt;a href=&quot;https://arxiv.org/abs/2010.02502&quot;&gt;&lt;em&gt;Denoising Diffusion Implicit Models&lt;/em&gt;&lt;/a&gt;. 이 글의 deterministic DDIM 식은 원 논문의 구조를 FFDP residual 좌표에 적용한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[설명용 유도]&lt;/code&gt;다.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 08 Sep 2026 00:00:00 +0900</pubDate>
        <link>https://yg2127.github.io/papers/ffdp/math-and-training/</link>
        <guid isPermaLink="true">https://yg2127.github.io/papers/ffdp/math-and-training/</guid>
        
        <category>deep-learning</category>
        
        <category>ffdp</category>
        
        <category>paper-review</category>
        
        <category>pose-estimation</category>
        
        <category>diffusion</category>
        
        <category>wavelet</category>
        
        <category>dwt</category>
        
        <category>ddim</category>
        
        
        <category>Paper Review</category>
        
      </item>
    
      <item>
        <title>6. PyTorch 06 - Strided CNN과 Depthwise Separable CNN</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;MLP는 MNIST 이미지를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(784,)&lt;/code&gt;로 펼쳐서 처리한다. 숫자의 모양을 이루는 인접 픽셀 관계도 함께 납작하게 만들어버린다. CNN은 작은 kernel을 이미지 위로 움직이며 지역 패턴을 먼저 찾는다.&lt;/p&gt;

&lt;p&gt;Day5 실습에서는 기본 CNN을 그대로 만드는 대신 두 가지 변형을 구현했다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Max Pooling을 stride가 2인 convolution으로 바꾼 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StridedCNN&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;convolution을 depthwise와 pointwise로 나눈 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DepthwiseCNN&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;코드를 작성하면서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Conv2d&lt;/code&gt;의 인자는 정리했지만, layer를 통과할 때마다 &lt;strong&gt;왜 공간 크기가 26, 14, 12, 7로 바뀌는지&lt;/strong&gt;, 그리고 depthwise convolution을 썼는데도 &lt;strong&gt;왜 전체 parameter가 더 많아졌는지&lt;/strong&gt;가 더 중요한 질문으로 남았다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-conv2d에서-먼저-볼-네-가지&quot;&gt;1. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Conv2d&lt;/code&gt;에서 먼저 볼 네 가지&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;in_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;out_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;인자&lt;/th&gt;
      &lt;th&gt;의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in_channels&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;입력 feature map의 채널 수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out_channels&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;만들 filter 수이자 출력 채널 수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kernel_size&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;filter의 공간 크기&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stride&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;filter가 한 번에 이동하는 간격&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;padding&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;입력 테두리에 추가하는 값의 폭&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dilation&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;kernel 원소 사이의 간격&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;입력·출력 채널을 연결하는 묶음 수&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;공간 출력 크기는 한 축을 기준으로 다음처럼 계산한다.&lt;/p&gt;

\[H_{out}=\left\lfloor
\frac{H_{in}+2P-D(K-1)-1}{S}+1
\right\rfloor\]

&lt;p&gt;여기서 $K$는 kernel, $S$는 stride, $P$는 padding, $D$는 dilation이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-stridedcnn-downsampling도-학습시키기&quot;&gt;2. StridedCNN: downsampling도 학습시키기&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StridedCNN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;down1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;down2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReLU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;down1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;down2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Max Pooling은 정해진 영역의 최댓값을 남긴다. 여기서는 stride 2 convolution이 공간 크기를 줄인다. downsampling에 쓰는 filter weight도 loss를 통해 학습된다는 차이가 있다.&lt;/p&gt;

&lt;p&gt;shape를 한 단계씩 계산하면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;32 * 7 * 7&lt;/code&gt;이 어디서 나왔는지 보인다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;단계&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;출력 shape&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;입력&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 1, 28, 28)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conv1&lt;/code&gt;, $K=3$&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 16, 26, 26)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;down1&lt;/code&gt;, $K=2,S=2,P=1$&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 16, 14, 14)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conv2&lt;/code&gt;, $K=3$&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 32, 12, 12)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;down2&lt;/code&gt;, $K=2,S=2,P=1$&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 32, 7, 7)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flatten&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 1568)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 256)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc2&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 10)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;실습 코드에서 계산된 총 parameter는 414,202개였다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-depthwise-separable-convolution&quot;&gt;3. Depthwise Separable Convolution&lt;/h2&gt;

&lt;p&gt;일반 convolution은 각 출력 filter가 모든 입력 채널을 함께 본다. 입력 채널이 $C_{in}$, 출력 채널이 $C_{out}$이면 weight 수는 bias를 제외하고 다음과 같다.&lt;/p&gt;

\[C_{out}\times C_{in}\times K\times K\]

&lt;p&gt;Depthwise separable convolution은 이 일을 두 단계로 나눈다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Depthwise convolution&lt;/strong&gt;: 채널마다 공간 filter 하나를 적용&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pointwise convolution&lt;/strong&gt;: $1\times1$ convolution으로 채널 정보를 결합&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;depthwise_conv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;in_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;out_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dilation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pointwise_conv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;in_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;out_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups=32&lt;/code&gt;이고 입력·출력 채널도 32이므로 각 filter는 입력 채널 하나만 본다. depthwise 단계에서는 채널 사이를 섞지 않고 공간 패턴만 추출한다. 이어지는 pointwise 단계가 32개 채널을 조합해 64개 출력 채널을 만든다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;방식&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;weight 수&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;일반 $3\times3$, 32→64&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$64\times32\times3\times3=18,432$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Depthwise $3\times3$&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$32\times1\times3\times3=288$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pointwise $1\times1$&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$64\times32=2,048$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;분리한 합계&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;2,336&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이 convolution block만 보면 weight가 크게 줄어든다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-dilation을-넣은-depthwisecnn&quot;&gt;4. dilation을 넣은 DepthwiseCNN&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DepthwiseCNN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;depthwise_conv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;dilation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pointwise_conv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReLU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MaxPool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;depthwise_conv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pointwise_conv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dilation=2&lt;/code&gt;인 $3\times3$ kernel의 실제 receptive field 크기는 $5\times5$다.&lt;/p&gt;

\[K_{effective}=D(K-1)+1=2(3-1)+1=5\]

&lt;p&gt;따라서 padding이 1이어도 공간 크기가 유지되지 않는다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;단계&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;출력 shape&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;입력&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 1, 28, 28)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conv1&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 32, 26, 26)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;첫 번째 pool&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 32, 14, 14)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dilated depthwise conv&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 32, 12, 12)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;pointwise conv&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 64, 12, 12)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;두 번째 pool&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 64, 7, 7)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;flatten&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 3136)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;출력&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 10)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-depthwise를-썼는데-왜-전체-모델은-더-클까&quot;&gt;5. Depthwise를 썼는데 왜 전체 모델은 더 클까?&lt;/h2&gt;

&lt;p&gt;노트북이 출력한 parameter 수는 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;모델&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;전체 parameter&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StridedCNN&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;414,202&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DepthwiseCNN&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;808,394&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Depthwise block은 분명 효율적인데 전체 모델은 약 두 배가 됐다. 원인은 convolution이 아니라 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1&lt;/code&gt;이다.&lt;/p&gt;

&lt;p&gt;DepthwiseCNN은 마지막 feature map이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(64, 7, 7)&lt;/code&gt;이므로 flatten 결과가 3,136차원이다.&lt;/p&gt;

\[3136\times256+256=803,072\]

&lt;p&gt;총 808,394개 중 803,072개가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1&lt;/code&gt;에 몰려 있다. 반면 StridedCNN은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(32, 7, 7)&lt;/code&gt;을 펼쳐 1,568차원이므로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1&lt;/code&gt; parameter가 401,664개다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Depthwise convolution은 &lt;strong&gt;해당 convolution 연산&lt;/strong&gt;을 줄여준다. 모델의 출력 채널을 늘린 뒤 큰 feature map을 그대로 Linear layer에 연결하면 전체 parameter는 오히려 커질 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이런 구조에서는 global average pooling처럼 공간 축을 먼저 요약하는 방식도 검토할 수 있다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-저장된-학습-결과-비교&quot;&gt;6. 저장된 학습 결과 비교&lt;/h2&gt;

&lt;p&gt;두 노트북 모두 5 epoch, AdamW, 같은 MNIST split을 사용해 다음 결과를 기록했다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;모델&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Final train acc&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Final test acc&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Train-test gap&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;StridedCNN&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;99.42%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;98.78%&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.64%p&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DepthwiseCNN&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;99.14%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;98.61%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.53%p&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이 실행에서는 StridedCNN의 test accuracy가 0.17%p 높았다. 그러나 여기서 “strided convolution이 depthwise convolution보다 좋다”고 결론 내릴 수는 없다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;입력·출력 채널 수가 서로 다르다.&lt;/li&gt;
  &lt;li&gt;downsampling 방식도 다르다.&lt;/li&gt;
  &lt;li&gt;전체 parameter 수가 약 두 배 차이 난다.&lt;/li&gt;
  &lt;li&gt;seed 하나, 실행 한 번의 결과다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;비교하려면 채널 수와 parameter budget을 맞추고 여러 seed에서 평균과 분산을 확인해야 한다. 현재 결과는 &lt;strong&gt;두 구현이 정상적으로 학습됐다는 관찰값&lt;/strong&gt;으로 보는 것이 적절하다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-weight-변화량-추적하기&quot;&gt;7. weight 변화량 추적하기&lt;/h2&gt;

&lt;p&gt;실습에서는 각 layer의 현재 weight를 초기값·직전 epoch와 비교했다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;initial_weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;named_parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.weight&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;상대 변화율은 weight 크기가 서로 다른 layer도 비교하기 위해 기준 weight의 norm으로 나눈다.&lt;/p&gt;

\[\text{relative change}(\%)=
100\times\frac{\|W_t-W_{ref}\|_2}
{\|W_{ref}\|_2+\epsilon}\]

&lt;p&gt;직전 epoch를 기준으로 하면 update가 점점 안정되는지 볼 수 있고, 초기값을 기준으로 하면 학습 전체에서 어느 layer가 많이 이동했는지 볼 수 있다. 단, norm 변화량이 크다고 그 layer가 더 중요하다는 뜻은 아니다. parameter 규모와 최적화 환경을 함께 봐야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;헷갈리기-쉬운-포인트&quot;&gt;헷갈리기 쉬운 포인트&lt;/h2&gt;

&lt;h4 id=&quot;maxpool2d에-input-size와-output-size를-적지-않는-이유는&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaxPool2d&lt;/code&gt;에 input size와 output size를 적지 않는 이유는?&lt;/h4&gt;

&lt;p&gt;Pooling layer는 입력 feature map에 같은 규칙을 적용하므로 Linear처럼 고정된 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in_features&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out_features&lt;/code&gt;를 받을 필요가 없다. kernel, stride, padding과 실제 입력 크기로 출력 공간 크기가 계산된다.&lt;/p&gt;

&lt;h4 id=&quot;groups는-채널을-groups개씩-묶는다는-뜻인가&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups&lt;/code&gt;는 채널을 groups개씩 묶는다는 뜻인가?&lt;/h4&gt;

&lt;p&gt;정확히는 입력·출력 채널의 연결을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups&lt;/code&gt;개의 독립된 묶음으로 나눈다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups=1&lt;/code&gt;이면 모든 입력 채널이 모든 출력 채널에 연결된다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups=in_channels&lt;/code&gt;이고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out_channels=in_channels&lt;/code&gt;이면 채널마다 filter 하나를 적용하는 depthwise convolution이 된다.&lt;/p&gt;

&lt;h4 id=&quot;padding1이면-항상-크기가-유지되나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;padding=1&lt;/code&gt;이면 항상 크기가 유지되나?&lt;/h4&gt;

&lt;p&gt;아니다. kernel, stride, dilation을 함께 봐야 한다. 특히 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kernel_size=3&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dilation=2&lt;/code&gt;의 effective kernel은 5이므로 stride 1에서 크기를 유지하려면 일반적으로 padding 2가 필요하다.&lt;/p&gt;

&lt;h4 id=&quot;logits에-softmax를-넣고-crossentropyloss를-써야-하나&quot;&gt;logits에 softmax를 넣고 CrossEntropyLoss를 써야 하나?&lt;/h4&gt;

&lt;p&gt;학습 시에는 넣지 않는다. 모델은 logits를 반환하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossEntropyLoss&lt;/code&gt;에 그대로 전달한다. 확률을 표시할 때만 softmax를 적용한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;정리하며&quot;&gt;정리하며&lt;/h2&gt;

&lt;p&gt;CNN 구조를 읽을 때 layer 이름만 보면 연결이 잘 보이지 않았다. 이번 실습에서는 각 단계의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(C,H,W)&lt;/code&gt;를 직접 계산하자 구조가 선명해졌다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;StridedCNN은 downsampling filter까지 학습한다.&lt;/li&gt;
  &lt;li&gt;Depthwise convolution은 채널별 공간 연산을, pointwise convolution은 채널 결합을 담당한다.&lt;/li&gt;
  &lt;li&gt;dilation은 parameter 수를 늘리지 않고 receptive field를 넓힌다.&lt;/li&gt;
  &lt;li&gt;전체 parameter는 convolution 하나의 효율보다 마지막 feature map과 Linear layer에서 더 크게 좌우될 수 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결국 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Conv2d&lt;/code&gt;를 이해하는 가장 확실한 방법도 shape를 끝까지 따라가는 것이었다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B,C,H,W)&lt;/code&gt;가 어디서 어떻게 바뀌는지만 놓치지 않으면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flatten → Linear&lt;/code&gt;의 입력 크기도 계산으로 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/pytorch-05-mnist-training-evaluation.html&quot;&gt;이전 글: PyTorch 05 - MNIST 학습과 평가 루프 완성하기&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/docs/stable/generated/torch.nn.Conv2d.html&quot;&gt;Conv2d 공식 문서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/docs/stable/generated/torch.nn.MaxPool2d.html&quot;&gt;MaxPool2d 공식 문서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1704.04861&quot;&gt;MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 09:50:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/pytorch-06-cnn-convolutions.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/pytorch-06-cnn-convolutions.html</guid>
        
        <category>pytorch</category>
        
        <category>cnn</category>
        
        <category>conv2d</category>
        
        <category>strided-convolution</category>
        
        <category>depthwise-convolution</category>
        
        
        <category>Deep Learning</category>
        
      </item>
    
      <item>
        <title>5. PyTorch 05 - MNIST 학습과 평가 루프 완성하기</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;Dataset과 DataLoader를 만들고 나면 드디어 학습 루프가 나온다. 그런데 이쯤부터 코드 한 줄의 의미는 알면서도 전체 흐름을 놓치기 쉬웠다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden_dim&lt;/code&gt;을 왜 인자로 받을까? 그냥 256이라고 쓰면 안 되나?&lt;/li&gt;
  &lt;li&gt;logits는 확률인가?&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.eval()&lt;/code&gt;만 호출하면 gradient도 꺼지나?&lt;/li&gt;
  &lt;li&gt;batch마다 나온 평균 loss를 왜 다시 batch 크기와 곱할까?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이번 글에서는 MNIST MLP의 입력부터 최종 평가까지를 &lt;strong&gt;텐서 shape와 상태 변화&lt;/strong&gt; 두 축으로 따라간다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;한 줄 요약: &lt;strong&gt;학습은 mini-batch마다 parameter를 바꾸는 과정이고, 평가는 parameter를 고정한 채 모든 샘플의 합계에서 loss와 accuracy를 계산하는 과정이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-두-개의-은닉층을-가진-mlp&quot;&gt;1. 두 개의 은닉층을 가진 MLP&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MLP2Layer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hidden_dim_1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hidden_dim_2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hidden_dim_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReLU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_dim_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hidden_dim_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_dim_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fc2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;MNIST 배치 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 1, 28, 28)&lt;/code&gt;를 Linear layer에 넣기 위해 한 이미지의 채널·높이·너비를 펼친다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start_dim=1&lt;/code&gt;이 중요하다. 0번 축인 batch는 보존하고 나머지만 합친다.&lt;/p&gt;

\[(B,1,28,28)\rightarrow(B,784)\]

&lt;p&gt;이후 shape 흐름은 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;단계&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;출력 shape&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;입력 이미지&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 1, 28, 28)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flatten(start_dim=1)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 784)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1 + ReLU&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 256)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc2 + ReLU&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 256)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 10)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-hidden_dim을-인자로-받는-이유&quot;&gt;2. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden_dim&lt;/code&gt;을 인자로 받는 이유&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MLP2Layer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hidden_dim_1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hidden_dim_2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;256을 클래스 안에 직접 적어도 현재 모델은 동작한다. 인자로 받는 이유는 &lt;strong&gt;모델 구조와 실험 설정을 분리하기 위해서&lt;/strong&gt;다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;small_model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MLP2Layer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;wide_model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MLP2Layer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;클래스 코드를 수정하지 않고도 모델 폭을 바꿀 수 있고, 설정값을 로그에 남기거나 하이퍼파라미터 실험을 자동화하기 쉽다. 기본값 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=256&lt;/code&gt;이 있으므로 아무 인자도 주지 않으면 원래 구조가 유지된다.&lt;/p&gt;

&lt;p&gt;저장된 모델의 parameter는 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;parameter&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;shape&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1.weight&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(256, 784)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc1.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(256,)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc2.weight&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(256, 256)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fc2.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(256,)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.weight&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10, 256)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10,)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;총 학습 가능 parameter는 269,322개다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-logits와-crossentropyloss&quot;&gt;3. logits와 CrossEntropyLoss&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CrossEntropyLoss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AdamW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;모델 출력 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 10)&lt;/code&gt;의 각 값은 확률이 아니라 &lt;strong&gt;logit&lt;/strong&gt;, 즉 클래스별 원시 점수다. 음수일 수도 있고 합이 1일 필요도 없다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossEntropyLoss&lt;/code&gt;는 logits와 클래스 인덱스 target을 받는다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;입력 logits: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 10)&lt;/code&gt;의 floating-point tensor&lt;/li&gt;
  &lt;li&gt;정답 targets: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B,)&lt;/code&gt;의 integer tensor, 각 값은 0~9&lt;/li&gt;
  &lt;li&gt;출력 loss: 기본 평균 reduction이 적용된 scalar tensor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;학습 전에 softmax를 직접 적용할 필요가 없다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossEntropyLoss&lt;/code&gt;가 내부에서 log-softmax와 negative log-likelihood에 해당하는 계산을 안정적으로 결합한다.&lt;/p&gt;

&lt;p&gt;확률이 필요한 추론 단계에서만 다음처럼 변환한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;probabilities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;softmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;predictions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;argmax&lt;/code&gt;는 softmax 전후에 같은 클래스 인덱스를 반환하므로 예측 클래스만 필요할 때는 logits에 바로 호출할 수 있다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-mini-batch-학습-루프&quot;&gt;4. mini-batch 학습 루프&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;train_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;train_acc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;test_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;test_acc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EPOCHS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;train_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;non_blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cuda&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;non_blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cuda&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zero_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_to_none&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;한 epoch에 60,000장을 모두 사용하지만 parameter update는 60,000장을 한 번에 본 뒤 일어나는 것이 아니다. batch size가 128이라면 대략 469개의 mini-batch마다 update가 한 번씩 일어난다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;데이터 이동 → gradient 초기화 → forward → loss → backward → update&lt;/strong&gt;가 mini-batch마다 반복된다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-평가-함수는-왜-따로-만들까&quot;&gt;5. 평가 함수는 왜 따로 만들까?&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;total_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_correct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_samples&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inference_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;predictions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_correct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;predictions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_samples&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;average_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_samples&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;accuracy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_correct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_samples&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;average_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accuracy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;평가에서는 parameter를 바꾸지 않는다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.eval()&lt;/code&gt;로 layer 동작을 평가 모드로 바꾸고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.inference_mode()&lt;/code&gt;로 Autograd 기록도 끈다. 둘은 서로 다른 역할이라 함께 사용한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-평균-loss에-batch-크기를-다시-곱하는-이유&quot;&gt;6. 평균 loss에 batch 크기를 다시 곱하는 이유&lt;/h2&gt;

&lt;p&gt;기본 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossEntropyLoss&lt;/code&gt;는 현재 batch의 샘플 loss를 평균낸 값 하나를 반환한다. 모든 batch의 평균을 단순히 다시 평균내면 마지막 batch가 작을 때 각 샘플의 가중치가 달라진다.&lt;/p&gt;

&lt;p&gt;예를 들어 전체 샘플 수가 batch size로 나누어떨어지지 않으면 마지막 batch에는 더 적은 샘플이 들어 있다. 그래서 batch 평균을 먼저 합계로 되돌린다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;total_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;모든 합계를 모은 뒤 전체 샘플 수로 나눈다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;average_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_samples&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 해야 데이터셋 전체의 &lt;strong&gt;샘플당 평균 loss&lt;/strong&gt;가 된다.&lt;/p&gt;

&lt;p&gt;accuracy도 같은 원리로 맞은 개수를 먼저 더한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;total_correct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predictions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;accuracy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_correct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_samples&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-epoch-결과-기록하기&quot;&gt;7. epoch 결과 기록하기&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;train_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;train_acc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;train_eval_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;test_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_acc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;train_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;train_acc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train_acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;test_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test_acc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;test_acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;저장된 5 epoch 실행 결과는 다음과 같았다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Epoch&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Train loss&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Train acc&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Test loss&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Test acc&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.1481&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;95.75%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.1515&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;95.38%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.0780&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;97.61%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.0948&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;96.91%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.0581&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;98.17%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.0911&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;97.14%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.0403&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;98.76%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.0776&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;97.56%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.0335&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;98.91%&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.0801&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;97.37%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Train 성능은 마지막까지 좋아졌지만 Test loss와 accuracy는 4 epoch에서 가장 좋고 5 epoch에 소폭 나빠졌다. 한 번의 실행만으로 과적합을 단정할 수는 없지만, &lt;strong&gt;훈련 성능만 보고 마지막 epoch를 무조건 선택하면 안 되는 신호&lt;/strong&gt;는 확인할 수 있다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-그래프로-확인할-때-생긴-실수&quot;&gt;8. 그래프로 확인할 때 생긴 실수&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;epoch_axis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;train_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;epoch_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;train_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Train Loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;epoch_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test_loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Test Loss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;epoch_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;train_acc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Train Accuracy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;epoch_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test_acc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Test Accuracy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tight_layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;실습 중에는 x축을 256개로 만들고 y값은 5개만 넣어 shape 오류가 났다. x축 길이는 hidden dimension이 아니라 기록한 epoch 수와 같아야 한다.&lt;/p&gt;

&lt;p&gt;또 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plt.subplots(1, 2)&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;axes&lt;/code&gt;는 Axes 하나가 아니라 NumPy 배열이다. 따라서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;axes.legend()&lt;/code&gt;가 아니라 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;axes[0].legend()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;axes[1].legend()&lt;/code&gt;처럼 각 subplot에 호출해야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;헷갈리기-쉬운-포인트&quot;&gt;헷갈리기 쉬운 포인트&lt;/h2&gt;

&lt;h4 id=&quot;logits-10개는-클래스별-확률인가&quot;&gt;logits 10개는 클래스별 확률인가?&lt;/h4&gt;

&lt;p&gt;아니다. 서로 비교하기 위한 원시 점수다. 확률이 필요하면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;softmax(logits, dim=1)&lt;/code&gt;을 적용한다.&lt;/p&gt;

&lt;h4 id=&quot;평가할-때-왜-modeleval과-inference_mode를-둘-다-쓰나&quot;&gt;평가할 때 왜 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.eval()&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inference_mode()&lt;/code&gt;를 둘 다 쓰나?&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval()&lt;/code&gt;은 Dropout·BatchNorm 같은 layer의 행동을 바꾸고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inference_mode()&lt;/code&gt;는 gradient 기록을 끈다. 하나가 다른 하나를 대신하지 않는다.&lt;/p&gt;

&lt;h4 id=&quot;hidden_dim256을-인자로-받는-것이-항상-더-좋은가&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden_dim=256&lt;/code&gt;을 인자로 받는 것이 항상 더 좋은가?&lt;/h4&gt;

&lt;p&gt;한 번만 쓰는 고정 모델이라면 상수로 적어도 된다. 여러 구조를 비교하거나 설정을 재사용할 때 인자로 분리한 장점이 커진다. 중요한 건 숫자를 어디에 적느냐보다 &lt;strong&gt;layer 사이의 입력·출력 차원이 일치하도록 연결하는 것&lt;/strong&gt;이다.&lt;/p&gt;

&lt;h4 id=&quot;이미지-시각화에서-mnist-평균과-표준편차를-왜-쓰나&quot;&gt;이미지 시각화에서 MNIST 평균과 표준편차를 왜 쓰나?&lt;/h4&gt;

&lt;p&gt;transform에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Normalize((0.1307,), (0.3081,))&lt;/code&gt;를 적용했을 때 원래 밝기로 되돌리기 위해 쓴다. 현재 실습처럼 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToTensor()&lt;/code&gt;만 사용했다면 역정규화 없이 바로 그려야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;정리하며&quot;&gt;정리하며&lt;/h2&gt;

&lt;p&gt;MNIST 학습 코드를 한 문장으로 읽으면 다음과 같다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DataLoader가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B,1,28,28)&lt;/code&gt; 배치를 제공하고 → MLP가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B,10)&lt;/code&gt; logits를 만들고 → CrossEntropyLoss가 scalar loss를 만들고 → backward가 gradient를 계산하고 → AdamW가 parameter를 바꾸며 → 평가 함수가 전체 샘플 기준 loss와 accuracy를 계산한다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;각 줄의 역할뿐 아니라 shape와 상태가 언제 바뀌는지 함께 보면 긴 학습 루프도 훨씬 덜 복잡해진다.&lt;/p&gt;

&lt;p&gt;다음 글에서는 이미지의 공간 구조를 버리고 펼쳐버리는 MLP 대신, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Conv2d&lt;/code&gt;로 공간 정보를 처리한다. Max Pooling을 strided convolution으로 바꾸는 모델과 depthwise·pointwise convolution을 분리한 모델까지 비교한다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/pytorch-04-mnist-dataset-dataloader.html&quot;&gt;이전 글: PyTorch 04 - MNIST Dataset과 DataLoader 이해하기&lt;/a&gt; · &lt;a href=&quot;/2026/09/pytorch-06-cnn-convolutions.html&quot;&gt;다음 글: PyTorch 06 - Strided CNN과 Depthwise Separable CNN&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html&quot;&gt;CrossEntropyLoss 공식 문서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.eval&quot;&gt;Module.eval 공식 문서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/docs/stable/generated/torch.autograd.grad_mode.inference_mode.html&quot;&gt;inference_mode 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 09:40:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/pytorch-05-mnist-training-evaluation.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/pytorch-05-mnist-training-evaluation.html</guid>
        
        <category>pytorch</category>
        
        <category>mnist</category>
        
        <category>mlp</category>
        
        <category>training-loop</category>
        
        <category>evaluation</category>
        
        
        <category>Deep Learning</category>
        
      </item>
    
      <item>
        <title>4. PyTorch 04 - MNIST Dataset과 DataLoader 이해하기</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;XOR에서는 데이터 네 개를 직접 텐서로 만들었다. MNIST는 학습 이미지가 60,000장이니 같은 방식으로 관리하기 어렵다. 여기서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dataset&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DataLoader&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform&lt;/code&gt;이 한꺼번에 등장한다.&lt;/p&gt;

&lt;p&gt;처음 코드를 읽으면서 가장 헷갈렸던 질문은 이것이었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;train_dataset&lt;/code&gt;이 transform을 통해 텐서로 바뀌었는데, 반복문에서는 왜 다시 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images&lt;/code&gt;라는 변수로 받아도 괜찮지? 그리고 흑백 이미지인데 shape의 채널은 왜 1이지?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;답은 &lt;strong&gt;Dataset은 전체 데이터를 가진 객체이고, DataLoader가 거기서 꺼낸 텐서 배치를 원하는 변수 이름에 할당한다&lt;/strong&gt;는 데 있다. 변수 이름은 데이터의 타입을 바꾸지 않는다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-dataset과-transform의-역할&quot;&gt;1. Dataset과 transform의 역할&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torchvision&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datasets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transforms&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mnist_transform&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transforms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToTensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datasets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MNIST&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;./data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mnist_transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;test_dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datasets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MNIST&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;./data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mnist_transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datasets.MNIST&lt;/code&gt;는 이미지와 정답을 보관하고, 인덱스로 샘플을 꺼내는 방법을 정의한 Dataset 객체다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# torch.Size([1, 28, 28])
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# torch.float32
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# int
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform=ToTensor()&lt;/code&gt;는 샘플을 꺼내는 순간 PIL 이미지 또는 NumPy 배열을 PyTorch 텐서로 변환한다. 일반적인 8-bit 이미지라면 픽셀값도 0~255 정수에서 0~1 범위의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float32&lt;/code&gt;로 바뀐다.&lt;/p&gt;

&lt;p&gt;Dataset 자체가 거대한 텐서 한 장으로 변하는 것은 아니다. &lt;strong&gt;Dataset이 샘플을 반환할 때 적용할 변환 규칙을 가진다&lt;/strong&gt;고 보는 편이 정확하다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-흑백-이미지의-채널은-왜-1인가&quot;&gt;2. 흑백 이미지의 채널은 왜 1인가&lt;/h2&gt;

&lt;p&gt;MNIST 이미지 한 장의 shape은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 28, 28)&lt;/code&gt;이다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;축&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;크기&lt;/th&gt;
      &lt;th&gt;의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td&gt;채널 수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28&lt;/td&gt;
      &lt;td&gt;세로 픽셀 수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;W&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28&lt;/td&gt;
      &lt;td&gt;가로 픽셀 수&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;흑백에 검정과 흰색 두 상태가 있으니 채널이 2일 것 같았지만, 채널 수는 가능한 색의 개수가 아니다. 흑백 이미지는 각 픽셀의 &lt;strong&gt;밝기 값 하나&lt;/strong&gt;만 있으면 표현할 수 있어 채널이 1개다. 중간 회색까지 포함해도 한 채널의 숫자 크기로 나타낸다.&lt;/p&gt;

&lt;p&gt;RGB 이미지는 한 픽셀을 빨강·초록·파랑 값 세 개로 표현하므로 채널이 3개다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 28, 28)&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;은 “흑백 클래스가 하나”라는 뜻이 아니라 &lt;strong&gt;픽셀마다 읽을 feature plane이 밝기 하나&lt;/strong&gt;라는 뜻이다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-dataloader는-무엇을-더하는가&quot;&gt;3. DataLoader는 무엇을 더하는가&lt;/h2&gt;

&lt;p&gt;Dataset이 샘플 하나를 꺼내는 규칙이라면, DataLoader는 여러 샘플을 모아 mini-batch로 공급한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch.utils.data&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;BATCH_SIZE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;train_loader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BATCH_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# torch.Size([128, 1, 28, 28])
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# torch.Size([128])
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iter(train_loader)&lt;/code&gt;는 배치를 차례로 꺼내는 iterator를 만들고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next(...)&lt;/code&gt;는 그중 첫 배치를 반환한다. 반환값 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(images, targets)&lt;/code&gt;를 두 변수로 unpacking한 것이다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images&lt;/code&gt;라는 이름 때문에 이미지 객체로 되돌아간 것이 아니다. 변수 이름은 마음대로 정할 수 있고, 실제 값은 이미 transform을 거친 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.Tensor&lt;/code&gt;다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-배치-shape-b-1-28-28-읽기&quot;&gt;4. 배치 shape &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, 1, 28, 28)&lt;/code&gt; 읽기&lt;/h2&gt;

&lt;p&gt;DataLoader는 샘플 128개의 이미지 텐서를 새로운 batch 축에 쌓는다.&lt;/p&gt;

\[(1,28,28)\times128\ \text{samples}
\longrightarrow (128,1,28,28)\]

&lt;p&gt;일반적으로 이미지 배치는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B, C, H, W)&lt;/code&gt; 순서를 사용한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;: 한 번에 모델로 보내는 샘플 수&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt;: 이미지 채널 수&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;W&lt;/code&gt;: 높이와 너비&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;target은 이미지마다 숫자 정답 하나이므로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(B,)&lt;/code&gt;, 즉 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(128,)&lt;/code&gt;이다. 10개 클래스라고 해서 target shape이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(128, 10)&lt;/code&gt;인 것은 아니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossEntropyLoss&lt;/code&gt;는 각 샘플의 클래스 인덱스 하나를 정답으로 받는다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-학습용-loader와-평가용-loader를-나누는-이유&quot;&gt;5. 학습용 loader와 평가용 loader를 나누는 이유&lt;/h2&gt;

&lt;p&gt;실습 코드에서는 같은 train Dataset으로 두 loader를 만들었다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;train_loader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;train_eval_loader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;test_loader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;test_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;train_loader&lt;/code&gt;는 학습할 때 매 epoch 샘플 순서를 섞는다. 모델이 우연한 데이터 순서에 의존하는 것을 줄이고 mini-batch 구성을 바꾼다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;train_eval_loader&lt;/code&gt;는 학습 데이터 전체의 loss와 accuracy를 일정한 순서로 측정한다. 평가 값 자체는 순서와 무관하지만, 섞을 필요가 없다는 의도가 분명해지고 결과를 재현하기 쉽다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test_loader&lt;/code&gt;도 같은 이유로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shuffle=False&lt;/code&gt;를 사용한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-shuffle도-재현하려면-generator가-필요하다&quot;&gt;6. shuffle도 재현하려면 Generator가 필요하다&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;SEED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manual_seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manual_seed_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;train_generator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;train_generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manual_seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SEED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;train_loader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;generator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train_generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;모델 초기 가중치만 고정해도 완전한 재현이 되지는 않는다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shuffle=True&lt;/code&gt;가 만드는 샘플 순서도 학습 결과에 영향을 주므로 DataLoader가 사용할 난수 생성기를 함께 고정한다.&lt;/p&gt;

&lt;p&gt;CUDA의 완전한 결정론은 사용 연산과 환경에 따라 추가 설정이 필요하고 성능이 느려질 수도 있다. seed를 적었다고 모든 환경에서 bit 단위로 같은 결과가 보장되는 것은 아니라는 점도 기억해야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-num_workers-pin_memory-non_blocking&quot;&gt;7. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_workers&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin_memory&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;non_blocking&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;GPU 학습 예제에서 자주 보이는 옵션들이다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;use_pin_memory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cuda&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;train_loader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;train_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num_workers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pin_memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;use_pin_memory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;generator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train_generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;옵션&lt;/th&gt;
      &lt;th&gt;역할&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_workers&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Dataset에서 샘플을 읽고 transform하는 별도 worker process 수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin_memory=True&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;CPU batch를 page-locked memory에 준비해 CUDA 전송을 효율화&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;non_blocking=True&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;조건이 맞으면 CPU→GPU 복사를 비동기적으로 요청&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;non_blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cuda&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;worker 수가 많다고 항상 빠른 것은 아니다. CPU core 수, 저장장치, transform 비용, 운영체제에 따라 적정값이 다르다. 노트북 환경에서 worker 생성이 멈추거나 느리다면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; 또는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;부터 확인하는 편이 안전하다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-모델에-넣기-전에-shape-확인하기&quot;&gt;8. 모델에 넣기 전에 shape 확인하기&lt;/h2&gt;

&lt;p&gt;학습을 시작하기 전에 배치 하나를 통과시켜 보면 shape 오류를 빠르게 발견할 수 있다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;check_images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_targets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train_eval_loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;check_images_device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inference_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;check_logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check_images_device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Input shape :&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Logits shape:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Target shape:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;저장된 실습 결과는 다음과 같았다.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Input shape : torch.Size([128, 1, 28, 28])
Logits shape: torch.Size([128, 10])
Target shape: torch.Size([128])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이미지 128장이 들어가고, 각 이미지마다 숫자 0~9의 점수 10개가 나온다. 정답은 이미지마다 클래스 인덱스 하나다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;헷갈리기-쉬운-포인트&quot;&gt;헷갈리기 쉬운 포인트&lt;/h2&gt;

&lt;h4 id=&quot;images로-받으면-tensor가-이미지-타입으로-바뀌나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images&lt;/code&gt;로 받으면 tensor가 이미지 타입으로 바뀌나?&lt;/h4&gt;

&lt;p&gt;아니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images&lt;/code&gt;는 변수 이름일 뿐이고, 값은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.Tensor&lt;/code&gt;다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;batch_images&lt;/code&gt;나 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;라고 이름을 바꿔도 동작은 같다.&lt;/p&gt;

&lt;h4 id=&quot;dataset에-transform을-넣으면-전체-60000장이-즉시-변환되나&quot;&gt;Dataset에 transform을 넣으면 전체 60,000장이 즉시 변환되나?&lt;/h4&gt;

&lt;p&gt;일반적인 torchvision Dataset에서는 샘플을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataset[index]&lt;/code&gt;로 꺼낼 때 transform이 적용된다. DataLoader는 필요한 인덱스의 샘플을 불러와 batch로 묶는다.&lt;/p&gt;

&lt;h4 id=&quot;totensor를-썼는데-mnist-평균표준편차로-다시-복원해야-하나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToTensor()&lt;/code&gt;를 썼는데 MNIST 평균·표준편차로 다시 복원해야 하나?&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToTensor()&lt;/code&gt;만 사용했다면 이미지는 단순히 0~1 범위로 변환됐으므로 그대로 시각화하면 된다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;squeeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imshow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gray&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Normalize((0.1307,), (0.3081,))&lt;/code&gt;까지 적용했을 때만 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image * 0.3081 + 0.1307&lt;/code&gt;로 역정규화한다. 실습 코드처럼 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToTensor()&lt;/code&gt;만 적용한 뒤 역정규화를 한 번 더 하면 픽셀값이 달라진다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;정리하며&quot;&gt;정리하며&lt;/h2&gt;

&lt;p&gt;MNIST 데이터 흐름은 다음과 같다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;원본 이미지 → Dataset이 인덱스로 샘플 선택 → transform이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,28,28)&lt;/code&gt; 텐서 생성 → DataLoader가 128개를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(128,1,28,28)&lt;/code&gt;로 묶음 → 학습 루프가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;images, targets&lt;/code&gt;로 unpacking → device로 이동&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이제 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for images, targets in train_loader:&lt;/code&gt; 한 줄 안에서 무엇이 나오는지 알 수 있다. 다음 글에서는 이 배치가 MLP를 통과하고, loss와 accuracy가 계산되는 전체 학습·평가 루프를 이어서 본다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/pytorch-03-autograd-training-loop.html&quot;&gt;이전 글: PyTorch 03 - backward부터 detach까지, Autograd 흐름 읽기&lt;/a&gt; · &lt;a href=&quot;/2026/09/pytorch-05-mnist-training-evaluation.html&quot;&gt;다음 글: PyTorch 05 - MNIST 학습과 평가 루프 완성하기&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/docs/stable/data.html&quot;&gt;PyTorch DataLoader 공식 문서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/vision/stable/generated/torchvision.transforms.ToTensor.html&quot;&gt;torchvision ToTensor 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 09:30:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/pytorch-04-mnist-dataset-dataloader.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/pytorch-04-mnist-dataset-dataloader.html</guid>
        
        <category>pytorch</category>
        
        <category>mnist</category>
        
        <category>dataset</category>
        
        <category>dataloader</category>
        
        <category>batch</category>
        
        
        <category>Deep Learning</category>
        
      </item>
    
      <item>
        <title>3. PyTorch 03 - backward부터 detach까지, Autograd 흐름 읽기</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;XOR 모델을 만들고 나니 더 큰 의문이 생겼다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt;에는 모델을 전달하지 않는데, 어떻게 hidden layer와 output layer의 가중치를 전부 찾아가서 gradient를 계산하지?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;처음에는 Python 코드가 위에서 아래로 실행되니 각 줄은 계산이 끝난 숫자만 다음 줄에 넘긴다고 생각했다. 하지만 PyTorch 텐서는 값만 저장하지 않는다. gradient가 필요한 연산에서는 &lt;strong&gt;그 값이 어떤 연산을 거쳐 만들어졌는지&lt;/strong&gt;도 계산 그래프로 연결한다.&lt;/p&gt;

&lt;p&gt;이 글은 XOR 학습 루프를 기준으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zero_grad → forward → loss → backward → step&lt;/code&gt;을 따라가며 Autograd의 역할을 정리한 기록이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-학습-루프-전체부터-보기&quot;&gt;1. 학습 루프 전체부터 보기&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zero_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;다섯 줄의 역할은 명확히 분리되어 있다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;단계&lt;/th&gt;
      &lt;th&gt;코드&lt;/th&gt;
      &lt;th&gt;일어나는 일&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zero_grad()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;이전 step에서 parameter의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt;에 누적된 gradient 제거&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model(X)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;현재 parameter로 logits 계산, 계산 그래프 생성&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;criterion(logits, y)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;logits에서 scalar loss까지 그래프 연결&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;그래프를 역방향으로 따라 parameter의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt; 계산&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optimizer.step()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt;를 읽어 parameter 값 갱신&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backward()&lt;/code&gt;가 gradient를 계산하고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;step()&lt;/code&gt;이 그 gradient를 사용해 실제 parameter를 바꾼다. 둘을 한 동작으로 생각하면 학습 코드가 자꾸 헷갈린다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-lossbackward는-모델을-어떻게-아는가&quot;&gt;2. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt;는 모델을 어떻게 아는가&lt;/h2&gt;

&lt;p&gt;forward 과정은 다음처럼 연결되어 있다.&lt;/p&gt;

\[X \rightarrow \text{Linear}_1 \rightarrow \tanh
\rightarrow \text{Linear}_2 \rightarrow \text{logits}
\rightarrow \text{BCEWithLogitsLoss} \rightarrow \text{loss}\]

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logits = model(X)&lt;/code&gt;가 실행될 때, logits 텐서에는 output layer의 연산이 연결된다. 다시 output layer의 입력은 Tanh 결과와 연결되고, Tanh 결과는 hidden layer와 연결된다. 마지막에 만든 loss에서 출발해 이 연결을 거꾸로 따라가면 모든 parameter에 도달한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그래서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backward()&lt;/code&gt;에 모델을 다시 전달할 필요가 없다. loss가 이미 모델의 연산 그래프 끝에 있기 때문이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt;는 “이 모델을 미분해”라는 명령이 아니라 &lt;strong&gt;“이 loss가 만들어진 경로를 역추적해, 도달 가능한 leaf parameter들의 gradient를 계산해”&lt;/strong&gt;라는 명령에 가깝다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-gradient는-어디에-저장될까&quot;&gt;3. gradient는 어디에 저장될까?&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backward()&lt;/code&gt;가 parameter 값을 직접 바꾸지는 않는다. 각 parameter의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt; 속성에 미분값을 저장한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# (4, 2)
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# (1, 4)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;gradient의 shape은 해당 parameter와 같다. optimizer는 생성될 때 받은 parameter들을 기억하고 있다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Adam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;따라서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optimizer.step()&lt;/code&gt;은 등록된 각 parameter의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt;를 읽어 Adam 규칙으로 값을 갱신할 수 있다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-왜-매번-zero_grad를-호출할까&quot;&gt;4. 왜 매번 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zero_grad()&lt;/code&gt;를 호출할까?&lt;/h2&gt;

&lt;p&gt;PyTorch는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backward()&lt;/code&gt;를 호출할 때 기존 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt;를 덮어쓰지 않고 더한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zero_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이 누적 방식은 여러 mini-batch의 gradient를 모으는 gradient accumulation에서 유용하다. 하지만 일반적인 학습 루프에서는 지난 step의 gradient가 섞이면 안 되므로 forward 전에 초기화한다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zero_grad(set_to_none=True)&lt;/code&gt;를 쓰면 0으로 채운 텐서를 유지하는 대신 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;으로 돌려 메모리 작업을 줄일 수 있다. 다음 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backward()&lt;/code&gt;에서 필요한 gradient가 새로 생성된다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-detach는-무엇을-끊는가&quot;&gt;5. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt;는 무엇을 끊는가&lt;/h2&gt;

&lt;p&gt;학습 도중 parameter 값을 기록한다고 하자.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt;는 현재 텐서와 같은 값을 바라보지만, 반환된 텐서를 기존 계산 그래프에서 분리한다. 이 텐서를 사용한 이후 연산은 원래 parameter 쪽으로 gradient를 보내지 않는다.&lt;/p&gt;

&lt;p&gt;하지만 한 가지 함정이 있다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt; 결과는 원본과 storage를 공유한다. optimizer가 원본 parameter를 갱신하면 “과거 값을 저장했다”고 생각한 snapshot도 함께 달라질 수 있다.&lt;/p&gt;

&lt;p&gt;그래서 독립적인 과거 값을 보관하려면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone()&lt;/code&gt;까지 필요하다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;previous_params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;표현&lt;/th&gt;
      &lt;th&gt;그래프 연결&lt;/th&gt;
      &lt;th&gt;원본과 storage 공유&lt;/th&gt;
      &lt;th&gt;과거 값 보관에 적합&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parameter&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;O&lt;/td&gt;
      &lt;td&gt;원본&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parameter.detach()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt;O&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parameter.clone()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;O&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parameter.detach().clone()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt;X&lt;/td&gt;
      &lt;td&gt;O&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone()&lt;/code&gt;을 먼저 하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt;해도 최종 결과는 그래프에서 분리된 독립 복사본이지만, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach().clone()&lt;/code&gt;이 “그래프를 끊고 값을 복사한다”는 의도를 읽기 쉽다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-no_grad와-detach의-차이&quot;&gt;6. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_grad()&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt;의 차이&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt;는 특정 텐서 하나를 그래프에서 분리한다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.no_grad()&lt;/code&gt;는 context 안에서 실행되는 연산 전체에 대해 gradient 기록을 끈다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;no_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;squared_change&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previous&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previous&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previous_params&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;delta_history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;squared_change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;가중치 변화량은 학습을 관찰하기 위한 통계일 뿐, 다음 loss를 만드는 데 쓰이지 않는다. 이 계산까지 그래프에 기록하면 메모리와 계산을 낭비한다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with&lt;/code&gt;는 이 설정을 해당 블록에만 임시로 적용하는 Python context manager 문법이다. 블록을 빠져나오면 gradient 기록 설정은 원래대로 돌아온다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-gradient가-흐르는-경로와-막히는-경로&quot;&gt;7. gradient가 흐르는 경로와 막히는 경로&lt;/h2&gt;

&lt;p&gt;XOR 코드의 변수들을 기준으로 정리하면 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;대상&lt;/th&gt;
      &lt;th&gt;loss에서 도달 가능?&lt;/th&gt;
      &lt;th&gt;이유&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.weight&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;reachable&lt;/td&gt;
      &lt;td&gt;logits를 직접 만드는 parameter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden.weight&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;reachable&lt;/td&gt;
      &lt;td&gt;Tanh와 output layer를 거쳐 loss에 연결&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;previous_params&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;blocked by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;관찰용 복사본이며 그래프에서 분리&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delta_history&lt;/code&gt; 계산&lt;/td&gt;
      &lt;td&gt;blocked by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_grad&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;parameter 변화량 기록용&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.item()&lt;/code&gt; 결과&lt;/td&gt;
      &lt;td&gt;not involved&lt;/td&gt;
      &lt;td&gt;Python 숫자로 꺼내 기록만 함&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prediction = probability &amp;gt;= 0.5&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;not involved&lt;/td&gt;
      &lt;td&gt;평가용 이산 예측, 학습 loss에 사용하지 않음&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;학습에 필요한 경로는 loss까지 끊기지 않고 이어져야 한다. 반대로 로그·그래프·평가를 위한 계산은 의도적으로 그래프 밖에서 수행한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-실제-변화량을-기록해보니&quot;&gt;8. 실제 변화량을 기록해보니&lt;/h2&gt;

&lt;p&gt;실습에서는 매 epoch 이후 현재 parameter와 직전 parameter 사이의 L2 거리를 계산했다.&lt;/p&gt;

\[\|\theta_t-\theta_{t-1}\|_2
=\sqrt{\sum_i(\theta_{t,i}-\theta_{t-1,i})^2}\]

&lt;p&gt;저장된 실행 결과 일부는 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Epoch&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Loss&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;parameter 변화량&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.75977838&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.20615497&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.67387134&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.12946829&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;100&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.02529514&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.02763611&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;500&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.00228525&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.00497251&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1000&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.00076190&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.00263150&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2000&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.00022706&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.00149049&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;loss가 줄어드는 동안 parameter 변화량도 함께 작아졌다. “학습이 안정된다”는 말을 막연하게 쓰는 대신, 실제 update 크기를 기록해 확인한 셈이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;헷갈리기-쉬운-포인트&quot;&gt;헷갈리기 쉬운 포인트&lt;/h2&gt;

&lt;h4 id=&quot;학습-중간에-lossdetach를-하면-어떻게-될까&quot;&gt;학습 중간에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.detach()&lt;/code&gt;를 하면 어떻게 될까?&lt;/h4&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;detached_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;detached_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detached_loss&lt;/code&gt;는 계산 그래프와 끊어져 있고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requires_grad=False&lt;/code&gt;이므로 정상적인 역전파를 할 수 없다. 숫자 기록은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.item()&lt;/code&gt;이나 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.detach()&lt;/code&gt;로 해도 되지만, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backward()&lt;/code&gt;는 연결된 원래 loss에 호출해야 한다.&lt;/p&gt;

&lt;h4 id=&quot;modeleval이-gradient를-꺼주나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.eval()&lt;/code&gt;이 gradient를 꺼주나?&lt;/h4&gt;

&lt;p&gt;아니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval()&lt;/code&gt;은 Dropout과 BatchNorm 같은 layer의 동작 모드를 바꾼다. gradient 기록을 끄려면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.no_grad()&lt;/code&gt; 또는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.inference_mode()&lt;/code&gt;가 별도로 필요하다.&lt;/p&gt;

&lt;h4 id=&quot;torchinference_mode는-no_grad와-같은가&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.inference_mode()&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_grad()&lt;/code&gt;와 같은가?&lt;/h4&gt;

&lt;p&gt;둘 다 평가 시 gradient 기록을 막는다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inference_mode()&lt;/code&gt;는 추론 전용으로 더 많은 Autograd 추적을 비활성화해 추가 최적화가 가능하지만, 그 안에서 만든 텐서를 이후 gradient 계산에 섞는 용도에는 제약이 더 크다. 순수 평가라면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inference_mode()&lt;/code&gt;가 의도를 잘 드러낸다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;정리하며&quot;&gt;정리하며&lt;/h2&gt;

&lt;p&gt;학습 루프는 한 덩어리의 마법이 아니었다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;forward가 그래프를 만들고 → loss가 그래프의 끝이 되고 → backward가 gradient를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.grad&lt;/code&gt;에 저장하고 → optimizer가 parameter를 갱신한다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_grad&lt;/code&gt;는 모두 “값을 보관하거나 관찰하되 학습 그래프에는 넣지 않기 위한 도구”였다. 셋의 역할을 구분하고 나니 왜 학습 코드와 기록 코드가 다른 방식으로 작성되는지 이해할 수 있었다.&lt;/p&gt;

&lt;p&gt;다음 글에서는 작은 XOR 텐서에서 벗어나 MNIST 60,000장을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dataset&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DataLoader&lt;/code&gt;로 공급하는 과정을 따라간다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/pytorch-02-xor-neural-network.html&quot;&gt;이전 글: PyTorch 02 - XOR로 이해하는 신경망 구조와 순전파&lt;/a&gt; · &lt;a href=&quot;/2026/09/pytorch-04-mnist-dataset-dataloader.html&quot;&gt;다음 글: PyTorch 04 - MNIST Dataset과 DataLoader 이해하기&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 09:20:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/pytorch-03-autograd-training-loop.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/pytorch-03-autograd-training-loop.html</guid>
        
        <category>pytorch</category>
        
        <category>autograd</category>
        
        <category>backward</category>
        
        <category>detach</category>
        
        <category>optimizer</category>
        
        
        <category>Deep Learning</category>
        
      </item>
    
      <item>
        <title>2. PyTorch 02 - XOR로 이해하는 신경망 구조와 순전파</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;PyTorch 예제는 흔히 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class Model(nn.Module)&lt;/code&gt;로 시작한다. 처음에는 클래스를 왜 만들어야 하는지도 낯설었고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Linear(2, 4)&lt;/code&gt;를 보고 &lt;strong&gt;입력이 4개인데 왜 weight가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 2)&lt;/code&gt;인지&lt;/strong&gt;도 헷갈렸다.&lt;/p&gt;

&lt;p&gt;XOR은 이 질문들을 확인하기 좋은 작은 문제다. 데이터는 네 개뿐이지만 직선 하나로 분리할 수 없어서 은닉층과 비선형 활성화 함수가 꼭 필요하다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;한 줄 요약: &lt;strong&gt;XOR의 네 샘플을 한 번에 넣으면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Linear(2,4)&lt;/code&gt;가 각 샘플의 feature 2개를 은닉 feature 4개로 바꾸고, Tanh가 선형 모델로는 만들 수 없는 결정경계를 만든다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-xor은-왜-직선-하나로-풀리지-않을까&quot;&gt;1. XOR은 왜 직선 하나로 풀리지 않을까?&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;입력&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;정답&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, 0)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 0)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;대각선으로 마주 보는 두 점끼리 같은 클래스다. 아무리 직선을 움직여도 한쪽에 0 두 개, 반대쪽에 1 두 개만 남길 수 없다. 그래서 입력을 새로운 feature 공간으로 옮긴 뒤 그 공간에서 분리해야 한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-nnmodule로-모델-만들기&quot;&gt;2. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module&lt;/code&gt;로 모델 만들기&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch.nn&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;XOR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;hidden_output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tanh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;XOR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module&lt;/code&gt;은 PyTorch 모델의 기본 클래스다. 이를 상속하면 내부에 등록한 layer와 parameter를 PyTorch가 추적할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super().__init__()&lt;/code&gt;은 부모인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module&lt;/code&gt;의 초기화를 실행한다. 이 호출이 있어야 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.hidden&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.output&lt;/code&gt;에 넣는 layer들이 하위 모듈로 정상 등록되고, 이후 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.parameters()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.to(device)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state_dict()&lt;/code&gt; 같은 기능이 작동한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-nnlinear2-4가-만드는-것&quot;&gt;3. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Linear(2, 4)&lt;/code&gt;가 만드는 것&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Linear(in_features=2, out_features=4)&lt;/code&gt;는 다음 연산을 수행한다.&lt;/p&gt;

\[h=XW^\top+b\]

&lt;p&gt;PyTorch가 저장하는 parameter shape은 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;parameter&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;shape&lt;/th&gt;
      &lt;th&gt;의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden.weight&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 2)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;은닉 unit 4개가 각각 입력 feature 2개를 읽음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4,)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;은닉 unit마다 bias 하나&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.weight&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 4)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;출력 unit 1개가 은닉 feature 4개를 읽음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.bias&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;출력 unit의 bias&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;입력 배치 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 2)&lt;/code&gt;인 이유는 &lt;strong&gt;샘플 4개 × 샘플당 feature 2개&lt;/strong&gt;이기 때문이다. 여기서 앞의 4와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden.weight&lt;/code&gt; 앞의 4는 같은 숫자지만 의미가 전혀 다르다.&lt;/p&gt;

\[(4,2)@(2,4)+(4,)=(4,4)\]

&lt;ul&gt;
  &lt;li&gt;첫 번째 4: batch에 들어 있는 샘플 수&lt;/li&gt;
  &lt;li&gt;두 번째 4: 각 샘플에서 만들어낸 은닉 feature 수&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그래서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden.weight&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 4)&lt;/code&gt;일 필요는 없다. batch 크기는 parameter의 shape을 결정하지 않는다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-forward를-shape로-읽기&quot;&gt;4. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward&lt;/code&gt;를 shape로 읽기&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hidden_output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tanh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;단계&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;shape&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;입력 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 2)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.hidden(x)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 4)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tanh(...)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 4)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.output(...)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 1)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;반환 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logits&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 1)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;모델 인스턴스를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model(X)&lt;/code&gt;처럼 호출하면 PyTorch의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module.__call__()&lt;/code&gt;이 내부에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward(X)&lt;/code&gt;를 실행한다. 사용자가 직접 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.forward(X)&lt;/code&gt;를 호출할 수도 있지만, hook과 같은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module&lt;/code&gt; 기능이 적용되는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model(X)&lt;/code&gt; 방식이 일반적이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-tanh가-필요한-이유&quot;&gt;5. Tanh가 필요한 이유&lt;/h2&gt;

&lt;p&gt;은닉층 두 개를 쌓았더라도 사이에 활성화 함수가 없다면 전체는 여전히 하나의 선형변환이다.&lt;/p&gt;

\[W_2(W_1X+b_1)+b_2=(W_2W_1)X+(W_2b_1+b_2)\]

&lt;p&gt;층을 여러 개 둔 의미가 사라진다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tanh&lt;/code&gt;는 원소별 비선형 변환을 넣어 입력을 휘어진 feature 공간으로 보낸다. 그 결과 출력층의 직선 하나가 원래 입력 공간에서는 XOR을 가르는 비선형 경계가 된다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“은닉층이 있어서 XOR을 푼다”보다 &lt;strong&gt;“은닉층 사이에 비선형성이 있어서 XOR을 푼다”&lt;/strong&gt;가 더 정확한 표현이다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-왜-출력에-sigmoid가-없을까&quot;&gt;6. 왜 출력에 Sigmoid가 없을까?&lt;/h2&gt;

&lt;p&gt;모델은 마지막 layer의 원시 점수인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logits&lt;/code&gt;를 그대로 반환한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;criterion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BCEWithLogitsLoss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Adam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BCEWithLogitsLoss&lt;/code&gt;는 Sigmoid와 Binary Cross Entropy를 하나로 합친 손실함수다. 모델 안에서 Sigmoid를 먼저 적용한 뒤 이 손실에 넣으면 Sigmoid를 두 번 적용하는 셈이 된다.&lt;/p&gt;

&lt;p&gt;학습할 때는 logits를 손실함수에 바로 넣고, 확률이 필요한 평가 단계에서만 Sigmoid를 적용한다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inference_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;probability&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sigmoid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;probability&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;값&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;shape&lt;/th&gt;
      &lt;th&gt;범위·의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logits&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;제한 없는 원시 점수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;probability&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;0~1 사이 클래스 1 확률&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prediction&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;0 또는 1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;기본 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reduction=&quot;mean&quot;&lt;/code&gt;으로 평균낸 스칼라&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;여기서 loss의 shape이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 1)&lt;/code&gt;이라고 생각하기 쉽다. 개별 원소의 손실은 네 개지만, 기본 설정에서 평균을 내므로 최종 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss&lt;/code&gt;는 0차원 스칼라 텐서다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-실제-학습-결과&quot;&gt;7. 실제 학습 결과&lt;/h2&gt;

&lt;p&gt;2000 epoch 학습한 노트북에서는 다음 결과가 저장되었다.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Input         Target     Probability     Prediction
[0, 0]             0       0.000003             0
[1, 0]             1       0.999754             1
[0, 1]             1       0.999753             1
[1, 1]             0       0.000412             0

Accuracy: 100.00%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;출력 확률이 네 점을 모두 정확히 구분했다. 중요한 건 이 결과를 외운 것이 아니라, &lt;strong&gt;입력 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4,2)&lt;/code&gt;가 은닉 표현 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4,4)&lt;/code&gt;를 거쳐 logits &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4,1)&lt;/code&gt;로 바뀌는 과정&lt;/strong&gt;을 읽을 수 있게 된 것이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;헷갈리기-쉬운-포인트&quot;&gt;헷갈리기 쉬운 포인트&lt;/h2&gt;

&lt;h4 id=&quot;in_features-out_features를-안-써도-되나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in_features&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out_features&lt;/code&gt;를 안 써도 되나?&lt;/h4&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in_features&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out_features&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;두 코드는 같다. 앞의 코드는 위치 인자로 전달하고, 뒤의 코드는 키워드 인자로 전달한다. 처음 구조를 읽을 때는 키워드를 쓰면 의미가 더 분명하다.&lt;/p&gt;

&lt;h4 id=&quot;흑백-분류라-출력이-두-개여야-하나&quot;&gt;흑백 분류라 출력이 두 개여야 하나?&lt;/h4&gt;

&lt;p&gt;XOR은 이진분류이므로 logit 하나만 출력해도 된다. Sigmoid를 통과한 값 하나가 클래스 1일 확률이고, 클래스 0의 확률은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1 - p&lt;/code&gt;다. 서로 배타적인 두 클래스에 logits 두 개와 CrossEntropyLoss를 쓰는 방식도 가능하지만, 현재 코드는 logit 하나와 BCEWithLogitsLoss를 택했다.&lt;/p&gt;

&lt;h4 id=&quot;modeltrain과-modeleval이-xor에서도-결과를-바꾸나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.train()&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.eval()&lt;/code&gt;이 XOR에서도 결과를 바꾸나?&lt;/h4&gt;

&lt;p&gt;현재 모델에는 Dropout이나 BatchNorm이 없어서 두 모드의 forward 계산은 같다. 그래도 학습·평가 단계의 의도를 명시하고, 나중에 그런 layer가 추가됐을 때 코드가 올바르게 동작하도록 모드를 전환한다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;정리하며&quot;&gt;정리하며&lt;/h2&gt;

&lt;p&gt;XOR 모델은 작지만 PyTorch 모델의 기본 골격을 모두 갖고 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module&lt;/code&gt; 상속 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt;에서 layer 등록 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward&lt;/code&gt;에서 데이터 흐름 정의 → logits 반환 → 손실함수로 학습 → 평가에서 확률로 변환&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;다음 글에서는 이 모델이 어떻게 스스로 가중치를 바꾸는지 살펴본다. 특히 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loss.backward()&lt;/code&gt;에 모델을 전달하지 않았는데도 어떻게 모든 layer의 gradient를 찾는지, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detach()&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_grad()&lt;/code&gt;는 왜 필요한지 정리한다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/pytorch-01-tensor-linear-regression.html&quot;&gt;이전 글: PyTorch 01 - 텐서로 선형회귀를 직접 풀어보기&lt;/a&gt; · &lt;a href=&quot;/2026/09/pytorch-03-autograd-training-loop.html&quot;&gt;다음 글: PyTorch 03 - backward부터 detach까지, Autograd 흐름 읽기&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 09:10:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/pytorch-02-xor-neural-network.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/pytorch-02-xor-neural-network.html</guid>
        
        <category>pytorch</category>
        
        <category>neural-network</category>
        
        <category>xor</category>
        
        <category>nn-module</category>
        
        <category>linear-layer</category>
        
        
        <category>Deep Learning</category>
        
      </item>
    
      <item>
        <title>1. PyTorch 01 - 텐서로 선형회귀를 직접 풀어보기</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;PyTorch를 처음 보면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tensor&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reshape&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;item&lt;/code&gt;처럼 짧은 메소드가 계속 등장한다. 각각의 정의는 금방 찾을 수 있었지만, 정작 코드를 읽을 때는 &lt;strong&gt;왜 여기서 텐서 모양을 바꾸고, 왜 1로 채운 열을 붙이는지&lt;/strong&gt;가 잘 이어지지 않았다.&lt;/p&gt;

&lt;p&gt;이번 실습에서는 신경망부터 만들지 않고, 10개의 점에 가장 잘 맞는 직선을 최소제곱법으로 구했다. 덕분에 PyTorch 코드의 기본 문법이 결국 하나의 행렬식으로 모이는 과정을 볼 수 있었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;한 줄 요약: &lt;strong&gt;입력 벡터를 설계행렬 $X=[1,x]$로 만들면, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.linalg.lstsq&lt;/code&gt;와 행렬곱만으로 선형회귀를 구현할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-데이터를-열벡터로-만들기&quot;&gt;1. 데이터를 열벡터로 만들기&lt;/h2&gt;

&lt;p&gt;먼저 10개의 $x$, $y$ 값을 GPU의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt; 텐서로 만든다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cuda&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cpu&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.8147&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9058&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.1270&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9134&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.6324&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;mf&quot;&gt;0.0975&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2785&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5469&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9575&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9649&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.8576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9706&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2572&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8854&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8003&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;mf&quot;&gt;0.1419&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4218&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9157&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7922&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9595&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tensor()&lt;/code&gt;에는 행이나 열을 정하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dim&lt;/code&gt; 인자가 없다. 먼저 데이터로 1차원 텐서를 만든 뒤 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reshape(-1, 1)&lt;/code&gt;로 열벡터를 만든다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;코드&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;shape&lt;/th&gt;
      &lt;th&gt;의미&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tensor([...])&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10,)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;원소 10개인 1차원 텐서&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.reshape(-1, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10, 1)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;10행 1열의 열벡터&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;여기서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-1&lt;/code&gt;은 “이 축의 크기는 전체 원소 수에 맞춰 자동으로 계산해 달라”는 뜻이다. 열을 1개로 고정했으므로 행은 자동으로 10이 된다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-왜-1로-채운-열을-붙일까&quot;&gt;2. 왜 1로 채운 열을 붙일까?&lt;/h2&gt;

&lt;p&gt;구하려는 직선은 다음과 같다.&lt;/p&gt;

\[\hat y = b + wx\]

&lt;p&gt;모든 샘플을 한 번에 계산하려면 이를 행렬곱으로 바꿔야 한다.&lt;/p&gt;

\[\begin{bmatrix}
\hat y_1 \\
\hat y_2 \\
\vdots \\
\hat y_{10}
\end{bmatrix}
=
\begin{bmatrix}
1 &amp;amp; x_1 \\
1 &amp;amp; x_2 \\
\vdots &amp;amp; \vdots \\
1 &amp;amp; x_{10}
\end{bmatrix}
\begin{bmatrix}
b \\
w
\end{bmatrix}\]

&lt;p&gt;첫 번째 열의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;이 절편 $b$와 곱해지고, 두 번째 열의 $x$가 기울기 $w$와 곱해진다. 코드에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ones_like&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;으로 이 설계행렬을 만든다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ones_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.ones_like(x)&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;와 shape, dtype, device가 같은 1 텐서를 만든다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dim=1&lt;/code&gt;은 두 텐서를 열 방향으로 붙인다.&lt;/li&gt;
  &lt;li&gt;따라서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X.shape&lt;/code&gt;은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10, 2)&lt;/code&gt;가 된다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;처음에는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feat = [ones, x]&lt;/code&gt; 같은 리스트만으로도 계산할 수 있을 것 같았다. 하지만 리스트는 텐서 두 개를 담은 Python 컨테이너일 뿐이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lstsq&lt;/code&gt;에 넣으려면 열들이 결합된 &lt;strong&gt;하나의 2차원 텐서&lt;/strong&gt;가 필요하다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-최소제곱해-구하기&quot;&gt;3. 최소제곱해 구하기&lt;/h2&gt;

&lt;p&gt;관측값이 직선 위에 정확히 놓이지 않으므로 $X\theta=y$를 완벽하게 만족하는 해는 없다. 대신 잔차 제곱합을 가장 작게 만드는 $	heta=[b,w]^\top$를 구한다.&lt;/p&gt;

\[\theta^*=\underset{\theta}{\arg\min}\;\|X\theta-y\|_2^2\]

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lstsq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;solution&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;intercept&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;slope&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;shape만 따라가도 연산이 보인다.&lt;/p&gt;

\[(10,2)@(2,1)=(10,1)\]

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;weight&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(2, 1)&lt;/code&gt; 텐서이고, 첫 번째 원소가 절편, 두 번째 원소가 기울기다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X @ weight&lt;/code&gt;는 10개 입력의 예측값을 한 번에 계산한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lstsq&lt;/code&gt;가 차원을 알아서 만들어준다고 생각하기보다, &lt;strong&gt;입력 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;의 열 개수가 구해야 할 계수의 개수를 결정한다&lt;/strong&gt;고 이해하는 편이 정확하다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-item은-언제-쓰는가&quot;&gt;4. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;item()&lt;/code&gt;은 언제 쓰는가&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# tensor([0.1941], device=&apos;cuda:0&apos;, dtype=torch.float64)
&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 0.194080159812...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;weight[0]&lt;/code&gt;은 원소가 하나여도 여전히 텐서다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.item()&lt;/code&gt;은 원소가 하나인 텐서를 Python 숫자로 바꾼다. 출력 문자열에 넣거나 일반 Python 값이 필요한 순간에 유용하다.&lt;/p&gt;

&lt;p&gt;반대로 이후 계산을 계속 PyTorch에서 수행한다면 굳이 일찍 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;item()&lt;/code&gt;을 호출할 필요가 없다. 텐서에서 Python 숫자로 나오는 순간 autograd 연결과 device 정보도 함께 사라지기 때문이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-예측-결과-평가하기&quot;&gt;5. 예측 결과 평가하기&lt;/h2&gt;

&lt;p&gt;실습에서는 RMSE와 결정계수 $R^2$도 텐서 연산으로 직접 계산했다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;rmse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_pred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ss_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_pred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ss_tot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ss_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ss_tot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;회귀식: y = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slope&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;x &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intercept&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;R2 = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RMSE = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rmse&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;저장된 노트북의 실행 결과는 다음과 같았다.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;회귀식: y = 0.811304x +0.194080
R2 = 0.833847
RMSE = 0.118827
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-다항회귀로-확장해보니&quot;&gt;6. 다항회귀로 확장해보니&lt;/h2&gt;

&lt;p&gt;직선의 설계행렬이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, x]&lt;/code&gt;였다면 $d$차 다항회귀는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, x, x², ..., xᵈ]&lt;/code&gt;로 확장하면 된다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;make_poly_features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;features&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ones_like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;power&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;degree&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;power&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fit_poly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;X_poly&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;make_poly_features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lstsq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_poly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;solution&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;차수가 $d$이면 설계행렬은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10, d+1)&lt;/code&gt;, 가중치는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(d+1, 1)&lt;/code&gt;이다. 이 실험에서 차수를 1부터 8까지 높이자 훈련 RMSE는 계속 줄었지만, LOOCV RMSE는 2차 이후 다시 커졌다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;차수&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Train RMSE&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;LOOCV RMSE&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.118827&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.144755&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.066986&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.097774&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.063891&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.162496&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.052451&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.357053&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.002012&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;23.941179&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;8차식은 훈련점 10개를 거의 완벽하게 통과하지만, 한 점을 빼고 다시 맞추면 오차가 폭발했다. &lt;strong&gt;훈련 오차가 작다는 사실만으로 좋은 모델이라고 말할 수 없는 이유&lt;/strong&gt;가 숫자로 드러난다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;헷갈리기-쉬운-포인트&quot;&gt;헷갈리기 쉬운 포인트&lt;/h2&gt;

&lt;h4 id=&quot;torchtensor-dim1로-열벡터를-만들-수-있나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tensor([...], dim=1)&lt;/code&gt;로 열벡터를 만들 수 있나?&lt;/h4&gt;

&lt;p&gt;그렇게 쓸 수 없다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch.tensor&lt;/code&gt;는 데이터를 받아 텐서를 생성하고, 축 모양은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reshape&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsqueeze&lt;/code&gt; 등으로 바꾼다.&lt;/p&gt;

&lt;h4 id=&quot;weight의-크기는-pytorch가-임의로-정하나&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;weight&lt;/code&gt;의 크기는 PyTorch가 임의로 정하나?&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(N, P)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(N, K)&lt;/code&gt;라면 해의 shape은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(P, K)&lt;/code&gt;다. 이 실습에서는 입력 열이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, x]&lt;/code&gt; 두 개이므로 계수도 절편과 기울기 두 개다.&lt;/p&gt;

&lt;h4 id=&quot;cuda-텐서를-바로-matplotlib에-넣어도-되나&quot;&gt;CUDA 텐서를 바로 Matplotlib에 넣어도 되나?&lt;/h4&gt;

&lt;p&gt;Matplotlib은 CUDA 텐서를 직접 처리하지 못한다. 그래프를 그릴 때만 다음처럼 CPU의 NumPy 배열로 옮긴다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x_plot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;squeeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;정리하며&quot;&gt;정리하며&lt;/h2&gt;

&lt;p&gt;이번 실습에서 가장 중요했던 것은 메소드 이름보다 shape였다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;열벡터 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10,1)&lt;/code&gt; → 1열을 붙인 설계행렬 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10,2)&lt;/code&gt; → 최소제곱 계수 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(2,1)&lt;/code&gt; → 행렬곱으로 예측 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(10,1)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이 흐름이 보이기 시작하면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reshape&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ones_like&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lstsq&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;item()&lt;/code&gt;이 따로 놀지 않는다. 모두 $X\theta=\hat y$를 코드로 옮기기 위한 단계였다.&lt;/p&gt;

&lt;p&gt;다음 글에서는 선형 경계 하나로 풀 수 없는 XOR 문제를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nn.Module&lt;/code&gt;로 구현한다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2026/09/pytorch-02-xor-neural-network.html&quot;&gt;다음 글: PyTorch 02 - XOR로 이해하는 신경망 구조와 순전파&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 09:00:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/pytorch-01-tensor-linear-regression.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/pytorch-01-tensor-linear-regression.html</guid>
        
        <category>pytorch</category>
        
        <category>tensor</category>
        
        <category>linear-regression</category>
        
        <category>least-squares</category>
        
        <category>matrix</category>
        
        
        <category>Deep Learning</category>
        
      </item>
    
      <item>
        <title>1. OcclusionGateNet 개발기 1 — 선글라스를 쓰면 운전자 모니터링은 어떻게 될까?</title>
        <description>&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/2026/06/tabpfn.html&quot;&gt;TabPFN 글&lt;/a&gt;에서 캡스톤 프로젝트가 우수상을 받았다고 잠깐 언급했었다. 이번에는 그 프로젝트 이야기를 해보려고 한다. 이름은 &lt;strong&gt;OcclusionGateNet&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;운전자 모니터링 시스템, 흔히 &lt;strong&gt;DMS(Driver Monitoring System)&lt;/strong&gt; 라고 부르는 것을 만들었다. 운전자가 어떤 행동을 하는지, 어디를 보는지, 손은 어떻게 두고 있는지, 대화하고 있는지를 영상으로 분류하는 프로젝트다.&lt;/p&gt;

&lt;p&gt;여기까지만 보면 얼굴과 자세를 추출해서 분류기에 넣으면 될 것 같다. 그런데 운전자는 항상 카메라를 향해 얼굴을 드러내고 있지 않는다. 선글라스를 쓰기도 하고, 손으로 입을 가리기도 한다. 밤에는 조명 조건도 달라진다. &lt;strong&gt;잘 보이는 얼굴을 잘 분류하는 것과, 얼굴이 가려져도 판단을 유지하는 것은 다른 문제&lt;/strong&gt;였다.&lt;/p&gt;

&lt;p&gt;우리가 집중한 것은 두 번째였다. 한 줄로 설명하면 이렇다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;가려진 얼굴의 랜드마크를 복원하고, 얼굴이 얼마나 보이는지를 이용해 얼굴·자세 정보를 섞는 운전자 모니터링 시스템.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;나는 팀에서 얼굴 랜드마크 복원 파트를 맡았고, 융합 모델과 ablation 실험에도 함께 참여했다. 그래서 이 개발기는 완성된 구조를 소개하는 데서 끝내지 않고, 복원 모델을 가져와 학습시키면서 어디서 막혔는지까지 담으려고 한다. 데이터와 학습 기록까지 함께 정리하려니 나눠 쓸 이야기가 꽤 많아졌다.&lt;/p&gt;

&lt;h2 id=&quot;part-1-어두운-것과-가려진-것은-어떻게-다를까&quot;&gt;Part 1. 어두운 것과 가려진 것은 어떻게 다를까?&lt;/h2&gt;

&lt;h3 id=&quot;nir-카메라를-쓰면-해결되는-문제&quot;&gt;NIR 카메라를 쓰면 해결되는 문제&lt;/h3&gt;

&lt;p&gt;프로젝트에서는 &lt;strong&gt;근적외선(Near-Infrared, NIR)&lt;/strong&gt; 영상을 사용했다. 야간 운전자 영상을 다루기 위한 선택이다. 다만 여기서 바로 구분해야 하는 것이 있다. 카메라가 어두운 환경에서 영상을 얻을 수 있다는 것과, 가려진 부위의 정보를 얻을 수 있다는 것은 같지 않다.&lt;/p&gt;

&lt;p&gt;예를 들어 손으로 입을 가린 장면을 생각해보자. 화면을 더 밝게 만든다고 손 뒤의 입 모양이 관측되지는 않는다. 모델 입장에서는 입력이 어두운 경우와 &lt;strong&gt;관측해야 할 정보 자체가 빠진 경우&lt;/strong&gt;를 다르게 다뤄야 한다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;문제&lt;/th&gt;
      &lt;th&gt;프로젝트에서 대응한 부분&lt;/th&gt;
      &lt;th&gt;남는 문제&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;야간·조명 변화&lt;/td&gt;
      &lt;td&gt;NIR 영상 사용, NIR 데이터로 모델 조정&lt;/td&gt;
      &lt;td&gt;센서와 촬영 환경이 달라질 때의 일반화&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;눈·입 등의 가림&lt;/td&gt;
      &lt;td&gt;가시성 추정, 랜드마크 복원&lt;/td&gt;
      &lt;td&gt;가린 영역의 실제 움직임은 직접 관측되지 않음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;손상된 얼굴 정보가 분류에 들어감&lt;/td&gt;
      &lt;td&gt;가시성을 조건으로 한 얼굴·자세 융합&lt;/td&gt;
      &lt;td&gt;복원이 그럴듯해도 분류에 항상 도움이 되는 것은 아님&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이 마지막 줄이 중요하다. 얼굴 점을 예쁘게 복원하는 것만으로 최종 운전자 행동 분류가 좋아졌다고 말할 수는 없다. &lt;strong&gt;복원된 정보가 실제 판단에 도움이 되는지&lt;/strong&gt;를 따로 확인해야 한다.&lt;/p&gt;

&lt;h3 id=&quot;처음의-eye-pose에서-face-pose-occ로&quot;&gt;처음의 Eye-Pose에서 Face-Pose-Occ로&lt;/h3&gt;

&lt;p&gt;초기 구상은 눈과 몸의 자세를 함께 보는 &lt;strong&gt;Eye-Pose&lt;/strong&gt;였다. 눈은 시선과 관련이 있고, 몸의 자세는 휴대전화 사용이나 물건을 집는 행동을 설명할 수 있다.&lt;/p&gt;

&lt;p&gt;그런데 눈을 주요 단서로 사용하는 구조라면, 눈이 가려지는 순간 가장 중요한 입력부터 흔들린다. 그래서 최종 구조에서는 얼굴 전체 랜드마크를 사용하고, 여기에 부위별 가시성을 별도의 입력으로 추가했다. 보고서에서 &lt;strong&gt;Face-Pose-Occ&lt;/strong&gt;로 정리한 방향이다.&lt;/p&gt;

&lt;p&gt;단순히 입력 하나를 더 붙인 것은 아니다. 가시성 정보는 두 군데에서 쓰인다. 앞에서는 &lt;strong&gt;어느 좌표를 복원 좌표로 바꿀지&lt;/strong&gt; 결정하고, 뒤에서는 &lt;strong&gt;어떤 얼굴 부위와 모달리티를 얼마나 참고할지&lt;/strong&gt; 조절한다.&lt;/p&gt;

&lt;h2 id=&quot;part-2-전체-구조를-먼저-한-번-보자&quot;&gt;Part 2. 전체 구조를 먼저 한 번 보자&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/occlusiongatenet/architecture-blog.svg&quot; alt=&quot;OcclusionGateNet의 얼굴·몸 두 스트림과 가시성 기반 융합 구조&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;저장소의 아키텍처 그림을 바탕으로, 이 글에서는 구조 설명에 집중하도록 개별 모델의 성능 숫자를 제외했다. 실제 추론 경로는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;full_system&lt;/code&gt; 구현을 기준으로 설명한다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;왼쪽에서 오른쪽으로 따라가면 다음과 같다.&lt;/p&gt;

&lt;h3 id=&quot;-얼굴과-몸에서-서로-다른-단서를-얻는다&quot;&gt;① 얼굴과 몸에서 서로 다른 단서를 얻는다&lt;/h3&gt;

&lt;p&gt;얼굴 영상에서는 YOLO 얼굴 검출기로 crop을 만들고, MediaPipe FaceMesh로 &lt;strong&gt;478개 얼굴 랜드마크&lt;/strong&gt;를 추출한다. 별도의 가시성 CNN은 왼쪽 눈·오른쪽 눈·코·입이 얼마나 보이는지 추정한다.&lt;/p&gt;

&lt;p&gt;몸 영상에서는 YOLO-Pose로 &lt;strong&gt;17개 관절점&lt;/strong&gt;을 추출한다. 얼굴이 눈과 입의 세부 변화를 제공한다면, 자세는 팔과 상체의 큰 움직임을 제공하는 식이다.&lt;/p&gt;

&lt;p&gt;여기서 랜드마크는 이미지 자체가 아니라 &lt;strong&gt;점의 위치&lt;/strong&gt;다. 픽셀 전체를 최종 분류기에 넣는 대신, 얼굴과 몸의 구조를 좌표 시퀀스로 전달한다.&lt;/p&gt;

&lt;h3 id=&quot;-가려졌다고-판단한-얼굴-부위를-복원한다&quot;&gt;② 가려졌다고 판단한 얼굴 부위를 복원한다&lt;/h3&gt;

&lt;p&gt;복원 경로에는 &lt;strong&gt;ORFormer와 HGNet&lt;/strong&gt;이 들어간다. ORFormer가 얼굴 구조를 나타내는 reference heatmap을 만들고, HGNet이 이를 얼굴 영상과 함께 받아 랜드마크를 예측한다.&lt;/p&gt;

&lt;p&gt;이때 복원하는 것은 선글라스를 지운 얼굴 사진이 아니다. &lt;strong&gt;뒤에서 사용할 얼굴 구조와 좌표&lt;/strong&gt;다. 복원 이미지가 사람 눈에 자연스러운지보다, 필요한 위치 정보를 얼마나 유지하는지가 문제다.&lt;/p&gt;

&lt;p&gt;그리고 MediaPipe 좌표를 전부 버리지 않는다. 관측 가능한 부위는 기존 좌표를 유지하고, 가림으로 판정한 부위만 정렬된 복원 좌표로 바꾼다. 이 좌표 교체와 뒤의 feature fusion은 이름에 gate가 들어가더라도 서로 다른 연산이다.&lt;/p&gt;

&lt;h3 id=&quot;-여러-프레임을-모아-네-가지-출력을-낸다&quot;&gt;③ 여러 프레임을 모아 네 가지 출력을 낸다&lt;/h3&gt;

&lt;p&gt;최종 분류기는 &lt;strong&gt;48프레임 창&lt;/strong&gt;의 얼굴·자세·가시성 정보를 받아 네 가지 과제를 함께 수행한다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;출력&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;현재 분류 코드의 클래스 수&lt;/th&gt;
      &lt;th&gt;예시&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Action&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;11&lt;/td&gt;
      &lt;td&gt;정상 운전, 문자 입력, 통화, 물 마시기 등&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gaze&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;9&lt;/td&gt;
      &lt;td&gt;전방, 좌우 방향, 미러, 인포테인먼트 등&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hands&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
      &lt;td&gt;운전대에 둔 손: 양손, 왼손만, 오른손만, 없음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Talk&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td&gt;대화 여부&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;자료를 정리하면서 가장 먼저 확인한 것 중 하나가 이 클래스 수였다. 발표에서는 이해를 돕기 위해 과제를 간단히 묶어 표현한 부분이 있지만, 실제 학습의 정답 공간은 위와 같다. 특히 gaze를 단순한 ‘전방/비전방’ 이진 분류로 읽으면 결과를 잘못 해석하게 된다.&lt;/p&gt;

&lt;h2 id=&quot;part-3-이-프로젝트에서-확인하고-싶었던-것&quot;&gt;Part 3. 이 프로젝트에서 확인하고 싶었던 것&lt;/h2&gt;

&lt;p&gt;모델을 만들고 숫자 하나를 얻는 것보다 중요한 질문이 있었다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;첫째, 얼굴이 가려져도 기존 성능을 얼마나 유지하는가?&lt;/strong&gt; 같은 클립의 clean/masked 조건을 비교해야 가림으로 인해 무엇이 달라졌는지 보기 쉽다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;둘째, 복원 모듈이 실제로 필요한가?&lt;/strong&gt; MediaPipe만으로 분류했을 때와 복원 좌표까지 사용했을 때를 비교해야 한다. 복원 결과 그림만으로는 답할 수 없는 질문이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;셋째, 가시성을 알려주는 것이 단순 결합보다 나은가?&lt;/strong&gt; 얼굴과 자세를 이어 붙이는 것에서 끝내지 않고, 가시성을 입력으로 주었을 때와 gate를 제거했을 때를 나눠 봐야 한다.&lt;/p&gt;

&lt;p&gt;실험에서는 이 차이가 특히 gaze에 나타났다. 다만 모든 과제에서 모든 모듈이 이긴 것은 아니다. 예를 들어 얼굴을 제거한 모델이 action에서 더 높은 점수를 낸 결과도 있다. 이 부분까지 포함해야 ‘어떤 정보가 어느 과제에 도움이 되었는가’를 이야기할 수 있다. 구체적인 수치와 해석은 6편에서 다룬다.&lt;/p&gt;

&lt;h2 id=&quot;앞으로-쓸-이야기&quot;&gt;앞으로 쓸 이야기&lt;/h2&gt;

&lt;p&gt;이 시리즈는 다음 흐름으로 구성했다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;편&lt;/th&gt;
      &lt;th&gt;중심 질문&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1. 문제와 전체 구조&lt;/td&gt;
      &lt;td&gt;왜 가림을 별도로 다뤄야 했을까?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2. 데이터&lt;/td&gt;
      &lt;td&gt;보이지 않는 얼굴의 정답은 어디서 얻을까?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3. 복원 모델&lt;/td&gt;
      &lt;td&gt;ORFormer의 codebook과 messenger는 무엇을 할까?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4. 학습 기록&lt;/td&gt;
      &lt;td&gt;왜 학습을 돌려도 같은 code만 사용했을까?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5. 융합 모델&lt;/td&gt;
      &lt;td&gt;복원한 얼굴을 얼마나 믿어야 할까?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6. 실험&lt;/td&gt;
      &lt;td&gt;어떤 모듈이 실제로 도움이 되었을까?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7. 데모와 회고&lt;/td&gt;
      &lt;td&gt;모델들을 연결하면 바로 시스템이 될까?&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;마지막에는 영종도에서 야간 영상을 촬영한 기록과 실제 통합 코드의 실패 처리도 다룰 예정이다. 여기서 소개하는 시스템은 캡스톤에서 구현하고 실험한 프로토타입이다. 영상 데모, 저장된 평가 결과, 실제 차량에서 요구하는 검증은 구분해서 읽으면 좋겠다.&lt;/p&gt;

&lt;p&gt;다음 편에서는 모델보다 먼저 데이터부터 살펴본다. &lt;strong&gt;가려진 얼굴을 복원하라고 학습시키려면, 가려지기 전 얼굴의 좌표를 어떻게 준비해야 할까?&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;참고한-자료&quot;&gt;참고한 자료&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/yg2127/OcclusionGateNet&quot;&gt;OcclusionGateNet 저장소&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/yg2127/OcclusionGateNet/blob/17d4211887ea4bd4848a382b24569d76c96f9961/docs/final_report.pdf&quot;&gt;최종 보고서 — 문제 정의·구조·역할 분담&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/yg2127/OcclusionGateNet/tree/17d4211887ea4bd4848a382b24569d76c96f9961/full_system&quot;&gt;통합 추론 코드와 실행 안내&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;이 시리즈의 코드 설명은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;17d4211&lt;/code&gt; 커밋을 기준으로 한다. 학습 당시 기록과 현재 구현이 다른 곳은 해당 편에서 구분했다.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 07 Sep 2026 00:00:00 +0900</pubDate>
        <link>https://yg2127.github.io/2026/09/occlusiongatenet-01-introduction.html</link>
        <guid isPermaLink="true">https://yg2127.github.io/2026/09/occlusiongatenet-01-introduction.html</guid>
        
        <category>deep-learning</category>
        
        <category>computer-vision</category>
        
        <category>dms</category>
        
        <category>occlusion</category>
        
        <category>capstone</category>
        
        
        <category>Project</category>
        
      </item>
    
  </channel>
</rss>
