<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Jongsu Liam Kim | Blog</title>
 <link href="https://blog.liam.kim/atom.xml" rel="self"/>
 <link href="https://blog.liam.kim/"/>
 <updated>2025-12-25T22:39:05+09:00</updated>
 <id>https://blog.liam.kim</id>
 <author>
   <name></name>
   <email></email>
 </author>

 
 <entry>
   <title>From EBM to NCSN</title>
   <link href="https://blog.liam.kim/posts/2025/12/EBM-to-NCSN/"/>
   <updated>2025-12-13T02:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2025/12/EBM-to-NCSN</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;이전 &lt;a href=&quot;https://blog.liam.kim/posts/2025/11/VAE-to-DDPMs/&quot;&gt;포스트&lt;/a&gt;에 이어서 &lt;a class=&quot;citation&quot; href=&quot;#lai2025principles&quot;&gt;[1]&lt;/a&gt;를 바탕으로 NCSN(Noise Conditional Score Network)을 깊게 파보는 포스트를 작성하려고 한다.&lt;/p&gt;

&lt;p&gt;Deep Learning Model은 보통 &lt;em&gt;확률&lt;/em&gt;을 출력하기를 원한다. 하지만, 딥러닝에서 확률의 대전제인 &lt;em&gt;‘모든 확률의 합은 1’&lt;/em&gt;을 만족시키기 위해서는 차원의 저주때문에 계산적으로 많은 비용이 든다.&lt;/p&gt;

&lt;p&gt;기존 모델들은 이 계산을 피하기 위해 모델 구조를 억지로 단순화하거나(Normalizing Flow), 아예 확률 계산을 포기하는(GAN) 타협을 했다. 
하지만 이를 타협하지 않고, 확률을 Energy와 결합하여 ‘데이터와 비슷하면 낮은 에너지, 비슷하지 않으면 높은 에너지라고 정의하자. 합이 1이 되는 건 나중에 생각하고, 가장 표현력이 좋은 네트워크를 쓰자’라는 접근을 취한 Energy Based Model (EBMs)가 있다.&lt;/p&gt;

&lt;p&gt;Energy Based Model (EBMs)은 분포 $p(\mathbf{x})$를 데이터 밀도가 높은 영역에서는 낮은 에너지를 갖는 Energy Landscape으로 표현한다.
EBM에서의 샘플링은 일반적으로 &lt;strong&gt;Langevin Dynamics&lt;/strong&gt;를 통해 이루어지며, 샘플을 고밀도 영역으로 이동시키기 위해 Energy Landscape의 기울기, 즉 &lt;strong&gt;Score Function&lt;/strong&gt; $\nabla_{\mathbf{x}} \log p(\mathbf{x})$를 정의하고 추적한다.&lt;/p&gt;

&lt;p&gt;여기서의 핵심은 Energy 혹은 확률 분포를 정확히 아는 것이 아닌 그 변화량만 추적한다는 것이다. 이를 통해 &lt;strong&gt;Score Function을 알면 계산 불가능한 정규화 상수 $Z$ 없이도 샘플 생성이 가능&lt;/strong&gt;하다. Score는 샘플을 확률이 더 높은 방향으로 안내하는 벡터 필드 역할을 한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score-based Diffusion Models&lt;/strong&gt;은 깨끗한 데이터 분포 대신, &lt;strong&gt;가우시안 노이즈가 점진적으로 추가된 분포들의 시퀀스&lt;/strong&gt;를 고려한다. 이렇게 노이즈가 가해진 분포의 Score는 학습하기가 상대적으로 용이하며, 모델은 이 시퀀스에 대한 Score Function들을 근사적으로 학습한다.&lt;/p&gt;

&lt;p&gt;학습된 Score Function들은 잡음이 낀 샘플을 &lt;strong&gt;단계적인 잡음 제거(progressive denoising)&lt;/strong&gt;를 통해 데이터 분포로 되돌리는 강력한 벡터 필드를 형성한다. &lt;strong&gt;Noise Conditional Score Networks (NCSN)&lt;/strong&gt;는 이러한 Score-based 모델의 대표적인 구현체이다.&lt;/p&gt;

&lt;h2 id=&quot;energy-based-models&quot;&gt;Energy Based Models&lt;/h2&gt;

&lt;p&gt;$\mathbf{x} \in \mathbb{R}^\mathcal{D}$을 data point라고 하자.
EBM은 확률 밀도를 에너지 함수 $E_\phi (\mathbf{x})$로 정의한다.
$E_\phi (\mathbf{x})$는 &lt;strong&gt;낮은 에너지가 높은 확률밀도가 되도록&lt;/strong&gt; $\phi$를 통해 파라미터화한다. 이는 다음과 같이 수식으로 표현할 수 있다.&lt;/p&gt;

\[\begin{align}
p_\phi (\mathbf{x}) := \dfrac{\exp{(-E_\phi (\mathbf{x}))}}{Z_\phi}, \quad Z_\phi := \int_{\mathbb{R}^\mathcal{D}} \exp{(-E_\phi (\mathbf{x}))} d \mathbf{x}
\end{align}\]

&lt;p&gt;이 때 $Z_\phi$는 Partition function이라 불리우며 확률에서의 normalization을 보장하는 함수이다.&lt;/p&gt;

\[\int_{\mathbb{R}^\mathcal{D}} p_\phi (\mathbf{x}) d \mathbf{x} = 1\]

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/01-Illustration-of-EBM-training.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of EBM training&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/01-Illustration-of-EBM-training.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of EBM training&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of EBM training&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;EBM은 학습을 통해 나쁜 데이터(빨간색)에서 확률 밀도를 낮추고 (에너지 상승), 반대로 좋은 데이터는(파란색) 확률 밀도를 높이는(에너지 하강) 방식으로 훈련 한다.&lt;/p&gt;

&lt;p&gt;Partition function $Z_\phi$는 전체 확률의 합을 1로 맞추기 때문에 에너지 값 자체의 값은 중요하지 않고, &lt;strong&gt;에너지의 상대적인 값만이 중요&lt;/strong&gt;하다. 
이 과정에서 한쪽의 에너지를 낮추기 위해선 (확률을 높이면), 다른쪽의 에너지를 높여야하는 과정(확률을 낮추는 과정)이 필수이다. 
풍선같이 한쪽을 누르면 다른쪽이 부푸는 작업이라고 할 수 있고, EBM은 이런 trade-off를 만족하면서 전체의 균형 (에너지는 항상 일정)을 맞춰야하는 작업이다.&lt;/p&gt;

&lt;h3 id=&quot;challenges-of-maximum-likelihood-training-in-ebms&quot;&gt;Challenges of Maximum Likelihood Training in EBMs&lt;/h3&gt;

&lt;p&gt;MLE(Maximum Likelihood Estimation)을 통해 EBM을 훈련하는 것은 다음과 같은 loss function을 통해 표현할 수 있다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{MLE}(\phi) &amp;amp;= \mathbb{E}_{p_{\textrm{data}} (\mathbf{x})} \left[ \log \dfrac{\exp{(-E_\phi (\mathbf{x}))}}{Z_\phi} \right] \\
&amp;amp;= - \underbrace{\mathbb{E}_{p_{\textrm{data}}} [E_\phi (\mathbf{x})]}_{\textrm{lowers energy of data}} - \underbrace{\log \int \exp{(-E_\phi (\mathbf{x}))} d \mathbf{x}}_{\textrm{global regularization}}
\end{align}\]

&lt;p&gt;이 때 $Z_\phi = \int \exp{(-E_\phi (\mathbf{x}))} d\mathbf{x}$이다.
첫번쨰 term은 실제 데이터의 에너지를 낮추고, 두번째 term은 $Z_\phi$를 통해 전체 normalization을 컨트롤하는 역할이다.
하지만, 고차원에서 $\log{Z_\phi}$와 그것의 gradient를 계산하는 것은 계산 비용이 무한대에 가까워서 거의 불가능하다고 볼 수 있다. 이를 해결하고자 하는 방법 중 하나가 score matching이다.&lt;/p&gt;

&lt;p&gt;즉 결론은 EBM을 훈련하기 위해서는 Partition function $Z_\phi = \int \exp{(-E_\phi (\mathbf{x}))} d\mathbf{x}$ 계산이 필요하고, 이는 intractable하기 때문에 score matching을 사용한다.&lt;/p&gt;

&lt;h3 id=&quot;score-matching&quot;&gt;Score Matching&lt;/h3&gt;

&lt;h4 id=&quot;motivation-what-is-the-score&quot;&gt;Motivation: What Is the Score?&lt;/h4&gt;

&lt;p&gt;단순히 말해서 Score는 Gradient이다. 
$\mathbb{R}^\mathcal{D}$의 $p(\mathbf{x})$가 있을 때, &lt;em&gt;score function은 log-density의 gradient&lt;/em&gt;로 정의된다.&lt;/p&gt;

\[\begin{align}
\mathbf{s}(\mathbf{x}) := \nabla_\mathbf{x} \log p(\mathbf{x}), \quad \mathbf{s}: \mathbb{R}^\mathcal{D} \rightarrow \mathbb{R}^\mathcal{D}
\end{align}\]

&lt;p&gt;Score function 아래 그림처럼 높은 확률 쪽으로 향하는 &lt;strong&gt;벡터 필드(벡터장)&lt;/strong&gt;인 것이다. 1차원 적으로는 Figure 1의 기울기라고 보면 된다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/02-Illustration-of-score-vector-fields.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of score vector fields&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/02-Illustration-of-score-vector-fields.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of score vector fields&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of score vector fields&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;이를 통해 다음과 같은 이점이 있다.&lt;/p&gt;

&lt;h5 id=&quot;normalization-constant-z_phi로-부터의-자유&quot;&gt;Normalization constant $Z_phi$로 부터의 자유&lt;/h5&gt;

&lt;p&gt;기존의 $p_\phi (\mathbf{x}) := \dfrac{\exp{(-E_\phi (\mathbf{x}))}}{Z_\phi}$의 정의처럼 Unnormalized density $\bar{p} (\mathbf{x})$가 있다하면 다음과 같이 간략하게 쓸 수 있다.&lt;/p&gt;

\[\begin{equation*}
p(\mathbf{x}) = \dfrac{\bar{p}(\mathbf{x})}{Z}, \quad Z = \int \bar{p} (\mathbf{x}) d \mathbf{x}
\end{equation*}\]

&lt;p&gt;Gradient를 구하기 위해 미분을 하면 $Z$는 $\mathbf{x}$에 대해서는 상수이기 때문에 (모든 $\mathbf{x}$에 대해 적분하는 값이므로) $\nabla_\mathbf{x} Z=0$이 된다.&lt;/p&gt;

\[\begin{equation*}
\nabla_\mathbf{x} \log p(\mathbf{x}) = \nabla_\mathbf{x} \log \bar{p}(\mathbf{x}) -\underbrace{\nabla_{\mathbf{x}} \log Z}_{=0} = \nabla_\mathbf{x} \bar{p}(\mathbf{x})
\end{equation*}\]

&lt;p&gt;즉 gradient 입장에서는 $\nabla_\mathbf{x} \log p(\mathbf{x}) = \nabla_\mathbf{x} \bar{p}(\mathbf{x})$인 것이다.&lt;/p&gt;

&lt;h5 id=&quot;a-complete-representation&quot;&gt;A Complete Representation&lt;/h5&gt;

&lt;p&gt;확률 밀도 함수 $p(\mathbf{x})$ 대신 $\mathbf{s}(\mathbf{x})$를 쓰면 표현력이 떨어지거나 정보의 손실이 일어나지 않을까라는 생각을 할 수 있다.&lt;/p&gt;

\[\begin{equation*}
\log p(\mathbf{x})  = \log p(\mathbf{x}_0)  + \int_0^1 \mathbf{s} (\mathbf{x}_0 + t(\mathbf{x} - \mathbf{x}_0))^{\mathsf{T}} (\mathbf{x} - \mathbf{x}_0) dt
\end{equation*}\]

&lt;p&gt;위 식은 \(\mathbf{x}(t) = \mathbf{x}_0 + t(\mathbf{x} - \mathbf{x}_0)\), \(\mathbf{s}(x) := \nabla_\mathbf{x} \log p(\mathbf{x})\), 그리고 다음과 같은 Chain rule을 사용하여 구할 수 있다.&lt;/p&gt;

\[\begin{align*}
\dfrac{d}{dt} \log p(\mathbf{x}(t)) &amp;amp;= \nabla_\mathbf{x} \log p(\mathbf{x}(t)) \cdot \dfrac{d \mathbf{x}(t)}{dt} \\
&amp;amp;= \mathbf{s}(\mathbf{x}) \cdot (\mathbf{x} - \mathbf{x}_0) \\
&amp;amp;= \mathbf{s}(\mathbf{x})^{\mathsf{T}}(\mathbf{x} - \mathbf{x}_0) \\
&amp;amp;= \mathbf{s}(\mathbf{x}_0 + t(\mathbf{x} - \mathbf{x}_0))^{\mathsf{T}}(\mathbf{x} - \mathbf{x}_0)
\end{align*}\]

&lt;p&gt;여기서 $\dfrac{d}{dt} \log p(\mathbf{x}(t))$를 적분식으로 풀면 위 식이 나오게 된다.
이 떄 $\mathbf{x}_0$은 reference point이고, $\log p (\mathbf{x}_0)$는 $\int \log p (\mathbf{x}) = 1$로 만드는 normalization을 위해 고정이 되므로 $\mathbf{s}$로부터 $p(\mathbf{x})$를 구할 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;training-ebms-via-score-matching&quot;&gt;Training EBMs via Score Matching&lt;/h3&gt;

&lt;p&gt;다시 한번 정리해보면 모델의 확률 밀도 $p_\phi (\mathbf{x})$ 대신에 모델의 score $\nabla_\mathbf{x} \log p_\phi (\mathbf{x}) = -\nabla_\mathbf{x} E_\phi (\mathbf{x})$를 타겟으로 한다.
EBM은 Score matching을 통해 확률 밀도 $p$대신에 model score (모델의 score) $\nabla_\mathbf{x} p_\phi (\mathbf{x})$와 data score (unknown) $\nabla_\mathbf{x} p_{\textrm{data}} (\mathbf{x})$의 차이를 최소화 하는 방향으로 훈련을 진행한다.&lt;/p&gt;

&lt;p&gt;그리고 $\nabla_\mathbf{x} p_{\textrm{data}} (\mathbf{x})$는 unknown이라 구할 수 없으므로 부분적분을 통해 $p_{\textrm{data}} (\mathbf{x})$를 소거하고, 이를 기대값($\mathbb{E}$) 형태의 연산으로 바꾼다.
진짜 데이터가 있는데 왜 unknown이냐하면, data가 있는 부분에서는 확률이 1이고 나머지는 0인 dirac delta로 표현되는 확률 분포가 나오는데 이는 기울기를 구하기 어렵다.
이 과정을 다음과 같이 표현한다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{SM} (\phi) &amp;amp;=\dfrac{1}{2} \mathbb{E}_{p_{\textrm{data}}(\mathbf{x})} \|\nabla_\mathbf{x} p_\phi (\mathbf{x}) -  \nabla_\mathbf{x} p_{\textrm{data}} (\mathbf{x})\|^2_2 \\
&amp;amp;=\mathbb{E}_{p_{\textrm{data}}(\mathbf{x})} \left[ Tr(\nabla_\mathbf{x}^2 E_\phi (\mathbf{x})) + \dfrac{1}{2} \|\nabla_\mathbf{x} E_\phi (\mathbf{x}) \|^2_2 \right] + C
\end{align}\]

&lt;p&gt;이 때 $\nabla_\mathbf{x}^2 E_\phi (\mathbf{x})$는 $E_\phi$의 Hessian이고 $C$는 constant이다.&lt;/p&gt;

&lt;p&gt;이 전체적인 과정은 뒤에서 자세하게 증명하겠지만, 일단 지금까지 흐름을 정리하면 이렇다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;$p_\phi (\mathbf{x})$에 집중하는 EBM대신 gradient인 score function에 대해 정리한다.
    &lt;ol&gt;
      &lt;li&gt;$p_\phi (\mathbf{x})$를 사용하는 경우 partition function $Z$가 필요하다.&lt;/li&gt;
      &lt;li&gt;그러나 고차원에서 $Z$를 구하는 것은 매우 계산적으로 비효율 적이고, 모델에서 샘플링해서 MCMC를 써도 오래 걸린다. 즉 intractable하다.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Score Matching을 통해 모델과 데이터의 Score의 차이를 감소하는 방향으로 EBM을 훈련한다.&lt;/li&gt;
  &lt;li&gt;Data Score는 unknown이므로 Score Matching함수를 실제 데이터에 대한 함수 (\(\mathbb{E}_{p_{\textrm{data}}(\mathbf{x})}\))로 변환한다.
    &lt;ol&gt;
      &lt;li&gt;데이터가 존재하지 않는 구역과 존재하는 구역 사이의 gradient는 구하기 어렵다.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;이를 통해 다음과 같은 효과를 얻는다.
    &lt;ol&gt;
      &lt;li&gt;Partition function $Z$제거&lt;/li&gt;
      &lt;li&gt;MCMC와 같은 모델 샘플링을 회피&lt;/li&gt;
      &lt;li&gt;단점은 $\nabla_\mathbf{x}^2 E_\phi (\mathbf{x})$가 2차 미분이라 계산량이 높다는 것&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;langevin-sampling-with-score-functions-inference&quot;&gt;Langevin Sampling with Score Functions (Inference)&lt;/h3&gt;

&lt;p&gt;앞서 설명했듯, 훈련과정을 통해 score function  \(\mathbf{s}_\phi (\mathbf{x}) = \nabla_\mathbf{x} \log p_\phi (\mathbf{x}) \approx -\nabla_\mathbf{x} E_\phi (\mathbf{x})\)를 구한다.
실제 추론 단계에서는 Langevin Dynamics을 사용한다.
이는 무작위 초기 지점(Random noise)으로부터 학습된 Score를 따라 데이터 밀도 가 높은 영역(High Density Region)으로 점진적으로 이동하는 과정이다.
이는 단순히 기울기만으로 최적화하지 않고, 노이즈를 주입하여 샘플링하는 것이 핵심이다.
노이즈가 없다면 가장 가까운 Local Minima에 갇혀 항상 똑같거나 획일화된 이미지가 생성될 위험이 있다.
즉, 노이즈는 생성 결과의 다양성을 확보하는 안전장치 역할을 한다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/03-Illustration-of-Langevin-sampling.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of Langevin sampling&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/03-Illustration-of-Langevin-sampling.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of Langevin sampling&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of Langevin sampling&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h4 id=&quot;discrete-time-langevin-dynamics&quot;&gt;Discrete-Time Langevin Dynamics&lt;/h4&gt;

&lt;p&gt;원래 Langevin Dynamics는 브라운 운동을 설명하기 위해 나온 개념이다. 에너지가 낮은 상태로 가려는 확정적인 힘 Drift와 분자 운동으로 인한 무작위적인 힘인 Diffusion에 움직이는 분자운동을 묘사한다.&lt;/p&gt;

&lt;p&gt;현재까지 본 EBM입장에서는 Score가 Drift가 되고 여기에 Noise(일반적으론 Guassian Noise)가 들어가면서 Diffusion 역할을 한다.&lt;/p&gt;

&lt;p&gt;이를 Discrete-time관점에서 작성하면 다음과 같다. 실제로도 스텝별로 노이즈를 추가하면서 Langevin Dynamics를 적용하기 때문에 아래 식이 더 익숙할 것이다.&lt;/p&gt;

\[\begin{align}
\mathbf{x}_{n+1} = \mathbf{x}_n - \eta \nabla_\mathbf{x} E_\phi (\mathbf{x}) + \sqrt{2\eta} \mathbf{\epsilon}_n, \quad n=0,1,2,\dots,
\end{align}\]

&lt;p&gt;이 때 $\mathbf{x}_0$은 Gaussian 분포와 같이 특정 분포에서 초기화 되고 $\eta &amp;gt;0$은 step size, 그리고 $\mathbf{\epsilon}_n \sim \mathcal{N}(\mathbf{0}, \mathbf{I})$은 Gaussian noise이다.&lt;/p&gt;

&lt;p&gt;이 때 Score function의 정의 \(\nabla_\mathbf{x} \log p_\phi (\mathbf{x}) \approx -\nabla_\mathbf{x} E_\phi (\mathbf{x})\)를 결합하면 다음 식으로 변환된다.&lt;/p&gt;

\[\begin{align}
\mathbf{x}_{n+1} = \mathbf{x}_n + \eta \nabla_\mathbf{x} \log p_\phi (\mathbf{x}) + \sqrt{2\eta} \mathbf{\epsilon}_n, \quad n=0,1,2,dots,
\end{align}\]

&lt;h4 id=&quot;continuous-time-langevin-dynamics&quot;&gt;Continuous-Time Langevin Dynamics&lt;/h4&gt;

&lt;p&gt;만약 $\eta \rightarrow 0$가 된다면 즉 step size가 매우 작아진다면
위 식은 Continuous process로 생각할 수 있고, 이를 Langevin Stochastic Diffusion Equations (SDE)라고 한다.&lt;/p&gt;

\[\begin{align}
d\mathbf{x} = \nabla_\mathbf{x} \log p_\phi (\mathbf{x} (t)) dt + \sqrt{2} d \mathbf{w} (t)
\end{align}\]

&lt;p&gt;이 때 $\mathbf{w}(t)$는 Wiener process라고 하며 Standard Brownian motion을 나타낸다.
보통은 Continuous-Time Langevin Dynamics인 SDE를 먼저 배우고 위 식을 Euler-Maruyama discretization을 통해 Discrete-Time Langevin Dynamics으로 넘어간다.
하지만, NCSN입장에선 처음에는 step을 명시적으로 지정하는 Discrete 버전을 것을 먼저 찾았고, &lt;a class=&quot;citation&quot; href=&quot;#song2020score&quot;&gt;[2]&lt;/a&gt;을 통해 Continous한 SDE로 일반화하였기 때문에 반대로 소개한다.&lt;/p&gt;

&lt;h4 id=&quot;why-langevin-sampling&quot;&gt;Why Langevin Sampling?&lt;/h4&gt;

&lt;p&gt;Langevin Sampling의 기원을 물리적인 관점, 특히 유체 속 입자의 &lt;strong&gt;브라운 운동(Brownian Motion)&lt;/strong&gt;에서 찾아보자.&lt;/p&gt;

&lt;p&gt;고전적인 뉴턴역학(Newtonian dynamics)에서는 물체의 움직임은 $F=ma$에 따라 힘과 가속도의 관계로 설명된다.
하지만 꿀물처럼 점성이 매우 높은 유체 속에 있는 가벼운 입자를 생각해보면, 이 경우 관성(Inertia)의 영향은 무시할 수 있고, 입자의 속도는 가해지는 힘에 비례하게 된다(Overdamped Langevin Dynamics).&lt;/p&gt;

&lt;p&gt;따라서 어떤 포텐셜 에너지 장(Force field) 안에서 입자의 움직임은 다음과 같은 1차 미분방정식(ODE)으로 단순화하여 표현할 수 있다.&lt;/p&gt;

\[\begin{equation}
d \mathbf{x} (t) = - \nabla_\mathbf{x} E_\phi (\mathbf{x}(t)) dt
\end{equation}\]

&lt;p&gt;이 식은 &lt;em&gt;deterministic&lt;/em&gt;하게 Energy가 낮은 쪽으로 particle을 이동시키는 역할을 한다.
그러나 앞서 언급한 것처럼 이런 경우 local minima에 빠질 확률이 높다.
이를 회피하기 위해 Langevin dynamics는 다음과 같은 SDE를 통해 stochastic perturbation을 주입한다. 이 때 $d \mathbf{w}(t)$는 Standard Brownian motion이다.&lt;/p&gt;

\[\begin{equation}
d \mathbf{x} (t) = - \nabla_\mathbf{x} E_\phi (\mathbf{x}(t)) dt + \underbrace{\sqrt{2} d \mathbf{w}(t)}_{\textrm{injected noise}}
\end{equation}\]

&lt;p&gt;노이즈 항은 Local minima를 벗어나게 해주며 시간이 충분히 흐르면 입자의 궤적(trajectory)이 에너지 함수에 대응하는 볼츠만 분포(Boltzmann distribution)를 따르게 만든다.&lt;/p&gt;

\[\begin{equation}
p_\phi (\mathbf{x}) \propto e^{-E_\phi (\mathbf{x})}
\end{equation}\]

&lt;p&gt;결국 EBM은 샘플들을 높은 밀도의 확률로 이동시키는 Score라는 Force field를 학습하게 된다.
Langevin sampling을 iteratively하게 적용하면 우리가 원래 목표로 했던 target distribution인 data의 distribution을 얻게 되는 것이다.&lt;/p&gt;

&lt;p&gt;참고로 예전에도 &lt;a href=&quot;https://blog.liam.kim/posts/2020/08/External-Forcing-Turbulence/&quot;&gt;난류의 External forcing에서 Langevin dyanmics에 관한 글&lt;/a&gt;을 작성한 적이 있다.&lt;/p&gt;

&lt;h4 id=&quot;inherent-challenges-of-langevin-sampling&quot;&gt;Inherent Challenges of Langevin Sampling&lt;/h4&gt;

&lt;p&gt;그러나 실제 데이터 분포는 복잡하고 고차원으로 이루어져있다. 이를 멀리 떨어져 있는 수많은 계곡이 있는 거친 지형(Rugged Landscape)처럼 생각할 수 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/04-Rugged-Loss-Landscape.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.nature.com/articles/s41467-025-58532-9&amp;quot;&amp;gt;
Rugged Loss Landscape&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/04-Rugged-Loss-Landscape.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.nature.com/articles/s41467-025-58532-9&amp;quot;&amp;gt;
Rugged Loss Landscape&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.nature.com/articles/s41467-025-58532-9&quot;&gt;
Rugged Loss Landscape&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;하지만 Langevin dynamics는 현재 위치에서 조금씩만 이동하는 국소적인 업데이트(Local update) 방식이다.
때문에 하나의 계곡(Mode)에 들어가면, 데이터가 없는 허허벌판(Low density region)을 건너 다른 계곡으로 이동하는 데 너무 많은 시간이 걸리게 된다.&lt;/p&gt;

&lt;p&gt;게다가, 고차원으로 갈수록 데이터 간의 거리는 멀어지고 대부분 빈 공간으로 가득차게 된다. 예를 들어 2차원 hypercube(정사각형)안에서의 2차원 hypersphere(원)의 비율은 $\pi/4=78.5\%$이지만, 10차원만 되어도 $\pi^5/120\cdot 2^10 = 0.25\%$ 즉 99.75%가 빈공간이다.&lt;/p&gt;

&lt;p&gt;또한 하이퍼 파라미터인 스텝 사이즈, 노이즈 크기, 반복 횟수 등에 너무 민감해지므로 이를 해결하기 위해 NCSN(Noise Conditional Score Network)가 등장하게 된다.&lt;/p&gt;

&lt;h2 id=&quot;from-energy-based-to-score-based-generative-models&quot;&gt;From Energy-Based to Score-Based Generative Models&lt;/h2&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/05-Illustration-of-Score-Matching.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of Score Matching&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-12-13-EBM-to-NCSN/05-Illustration-of-Score-Matching.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of Score Matching&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of Score Matching&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;지금까지 살펴봤듯 데이터 확률 분포 자체를 직접 학습하는 것이 아니라, 그것의 Gradient인 Score만 학습해도 충분하다는 것을 확인했다.&lt;/p&gt;

&lt;p&gt;이를 모델링 관점에서 보면 신경망이 예측한 Score인 $\mathbf{s}_\phi (\mathbf{x})$와 실제 데이터의 ground truth score $\mathbf{s}(\mathbf{x})$의 차이를 MSE Loss를 이용하여 최소화하는 과정을 통해 학습한다. 물론 앞서 언급했듯 실제 Score는 알 수 없기 때문에 Score Matching기법을 통해 간접적으로 학습한다.&lt;/p&gt;

&lt;p&gt;시각적으로 보면 위 그림에서 ‘신경망이 만든 벡터장’을 ‘실제 데이터가 만드는 벡터장’과 똑같은 모양이 되도록 끼워 맞추는 과정이라고 이해할 수 있다.&lt;/p&gt;

&lt;p&gt;현재 섹션에서는 이 과정을 좀 더 수식 증명과 함께 분석하고자 한다.&lt;/p&gt;

&lt;h3 id=&quot;training-with-score-matching&quot;&gt;Training with Score Matching&lt;/h3&gt;
&lt;h4 id=&quot;score-matching-1&quot;&gt;Score Matching&lt;/h4&gt;

&lt;p&gt;앞서 언급했던 것처럼 모델의 score와 실제 score를 근사시키는 방향으로 모델을 훈련한다.&lt;/p&gt;

\[\begin{align}
\mathbf{s}_\phi (\mathbf{x}) \approx \mathbf{s} (\mathbf{x})
\end{align}\]

&lt;p&gt;이는 다음과 같은 MSE Loss를 사용함을 뜻한다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}_{SM} (\phi) =\dfrac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\textrm{data}}(\mathbf{x})} \left[ \| \mathbf{s}_\phi (\mathbf{x}) -  \mathbf{s} (\mathbf{x}) \|^2_2 \right]
\end{equation}\]

&lt;h4 id=&quot;tractable-score-matching&quot;&gt;Tractable Score Matching&lt;/h4&gt;

&lt;p&gt;하지만 앞서 언급한 것 중 하나가 \(\mathbf{s}_\phi (\mathbf{x})\) 를 구하는 것은 intractable하다는 것이다.&lt;/p&gt;

&lt;p&gt;그러나 &lt;a class=&quot;citation&quot; href=&quot;#hyvarinen2005estimation&quot;&gt;[3]&lt;/a&gt;에서는 부분적분을 통해 \(\mathcal{L}_{SM} (\phi)\)를 \(mathbf{s}_\phi (\mathbf{x})\)와 data sample만으로 외존하는 형태(\(\tilde{\mathcal{L}}_{SM} (\phi)\))로 변환했다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}_{SM} (\phi) = \tilde{\mathcal{L}}_{SM} (\phi) + C
\end{equation}\]

&lt;p&gt;이 때 \(\tilde{\mathcal{L}}_{SM} (\phi)\)은 다음과 같이 표현한다.&lt;/p&gt;

\[\begin{equation}
\tilde{\mathcal{L}}_{SM} (\phi) := \mathbb{E}_{\mathbf{x} \sim p_{\textrm{data}}(\mathbf{x})} \left[ Tr(\nabla_\mathbf{x}\mathbf{s}_\phi (\mathbf{x})) + \dfrac{1}{2} \| \mathbf{s}_\phi (\mathbf{x}) \|^2_2 \right]
\end{equation}\]

&lt;p&gt;이 때 $C$는 상수이며 $\phi$에 의존하지 않는다. 또한 이렇게 구한 minimizer 는 \(\mathbf{s}^* (\cdot) = \nabla_\mathbf{x} \log p (\cdot)\)이다.&lt;/p&gt;

&lt;p&gt;이는 다음과 같이 증명할 수 있다.&lt;/p&gt;

&lt;p&gt;우선 \(\mathcal{L}_{\text{SM}}(\phi)\)의 Squared difference를 전개한다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{\text{SM}}(\phi) 
&amp;amp;= \frac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \|\mathbf{s}_{\phi}(\mathbf{x})\|_2^2 - 2 \langle \mathbf{s}_{\phi}(\mathbf{x}), \mathbf{s}(\mathbf{x}) \rangle + \|\mathbf{s}(\mathbf{x})\|_2^2 \right] \\
&amp;amp;= \frac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \|\mathbf{s}_{\phi}(\mathbf{x})\|_2^2 \right] - \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \langle \mathbf{s}_{\phi}(\mathbf{x}), \mathbf{s}(\mathbf{x}) \rangle \right]  \\
&amp;amp;\quad + \frac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \|\mathbf{s}(\mathbf{x})\|_2^2 \right]
\end{align}\]

&lt;p&gt;여기서 문제되는 term은 \(\mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \langle \mathbf{s}_{\phi}(\mathbf{x}), \mathbf{s}(\mathbf{x}) \rangle \right]\)이다. pdf에서는 cross-product term이라고 했지만 이건 이차식을 전개할때의 cross-product이지 벡터의 외적을 뜻하는 cross-product가 아니다.&lt;/p&gt;

&lt;p&gt;\(p_{\text{data}}(\mathbf{x})\)가 $0$이 아니라면 \(\nabla_\mathbf{x} \log p_{\text{data}}(\mathbf{x}) = \dfrac{\nabla_\mathbf{x} p_{\text{data}}(\mathbf{x})}{p_{\text{data}}(\mathbf{x})}\) (로그 미분식) 을 사용할 수 있다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \langle \mathbf{s}_{\phi}(\mathbf{x}), \mathbf{s}(\mathbf{x}) \rangle \right] 
&amp;amp;= \int \left( \mathbf{s}_\phi (\mathbf{x})^\mathsf{T} \mathbf{s} (\mathbf{x}) \right) p_{\text{data}}(\mathbf{x}) d \mathbf{x} \\ 
&amp;amp;= \int \left( \mathbf{s}_\phi (\mathbf{x})^\mathsf{T} \nabla \log p_{\text{data}}(\mathbf{x}) \right) p_{\text{data}}(\mathbf{x}) d \mathbf{x} \\ 
&amp;amp;= \int \left( \mathbf{s}_\phi (\mathbf{x})^\mathsf{T} \dfrac{\nabla_\mathbf{x} p_{\text{data}}(\mathbf{x})}{p_{\text{data}}(\mathbf{x})} \right) p_{\text{data}}(\mathbf{x}) d \mathbf{x} \\
&amp;amp;= \int \mathbf{s}_\phi (\mathbf{x})^\mathsf{T} \nabla_\mathbf{x} p_{\text{data}}(\mathbf{x}) d \mathbf{x} \\
&amp;amp;= \sum_{i=1}^D \int \mathbf{s}^{(i)}_\phi (\mathbf{x}) \partial x_i p_{\text{data}}(\mathbf{x}) d \mathbf{x}
\end{align*}\]

&lt;p&gt;이 때 $ \mathbf{s}^{(i)}_\phi (\mathbf{x})$는 score function의 i-th component이고, 앞으로 진행할 부분 적분을 손쉽게 하기 위해 내적을 성분별 곱의 합으로 표현한다.&lt;/p&gt;

\[\begin{equation*}
\mathbf{s}_\phi (\mathbf{x})= \left( \mathbf{s}^{(1)}_\phi (\mathbf{x}), \mathbf{s}^{(2)}_\phi (\mathbf{x}), \dots, \mathbf{s}^{(D)}_\phi (\mathbf{x}) \right)
\end{equation*}\]

&lt;p&gt;이제 부분 적분을 적용하면 되는데, 데이터와 같은 경우는 경계를 가정하기 힘들기 때문에 일단 어떤 ball 안에 있다고 가정한다.&lt;/p&gt;

&lt;p&gt;만약 $u,v$가 반지름 $R &amp;gt; 0$를 가지는 ball \(\mathbb{B}(\mathbf{0}, R) \subset \mathbf{R}^D\) 위의 실수 함수라고 가정하면,
$i=1,2,\dots, D$에서 다음 부분 적분이 성립한다. 이 때, $\nu = (\nu_1, \dots, \nu_D)$는 boundary \(\partial \mathbb{B}(\mathbf{0}, R)\)로 향하는 outward unit normal function이며, $dS$는 \(\partial \mathbb{B}(\mathbf{0}, R)\)의 surface measure이다.&lt;/p&gt;

\[\begin{equation*}
\int_{\mathbb{B}(\mathbf{0}, R)} u \partial_{x_i} v d \mathbf{x} = -\int_{\mathbb{B}(\mathbf{0}, R)} v \partial_{x_i} u d \mathbf{x} + \int_{\partial \mathbb{B}(\mathbf{0}, R)} u v \nu_i d S 
\end{equation*}\]

&lt;p&gt;위 식에 \(u(\mathbf{x}) := \mathbf{s}^{(1)}_\phi (\mathbf{x})\), \(v(\mathbf{x}) := p_{\text{data}}(\mathbf{x})\)를 적용하고 다음을 가정한다. 왜냐하면 실제 데이터 분포 \(p_{\text{data}}(\mathbf{x})\)는 중심에서 멀어질수록 확률밀도는 지수적으로 감소하는 경우가 많기 때문이다. 그리고 신경망 \(\mathbf{s}^{(1)}_\phi (\mathbf{x})\) 은 아무리 커져도 대부분 polynomial형태로 증가한다. 따라서 \(p_{\text{data}}(\mathbf{x})\)가 압도하게 되고, 다음 가정이 성립한다. 그리고 이렇게 되면 부분적분의 두번쨰 항은 0이 된다.&lt;/p&gt;

\[\begin{equation*}
| u(\mathbf{x}) u(\mathbf{x}) | \rightarrow 0 \quad \textrm{ as } R \rightarrow \infty
\end{equation*}\]

&lt;p&gt;모든걸 합치면&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \langle \mathbf{s}_{\phi}(\mathbf{x}), \mathbf{s}(\mathbf{x}) \rangle \right] &amp;amp;=
\sum_{i=1}^D \int \mathbf{s}^{(i)}_\phi (\mathbf{x}) \partial x_i p_{\text{data}}(\mathbf{x}) d \mathbf{x} \\
&amp;amp;= - \sum_{i=1}^D \int \partial x_i \mathbf{s}^{(i)}_\phi (\mathbf{x})  p_{\text{data}}(\mathbf{x}) d \mathbf{x} \\
&amp;amp;= - \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ Tr(\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x}))\right]
\end{align*}\]

&lt;p&gt;이 때 \(\partial x_i \mathbf{s}^{(i)}_\phi (\mathbf{x})\)가 Jacobian의 diagonal term이 되기 때문에 $\sum$을 통해 Trace만 남게 된다.&lt;/p&gt;

&lt;p&gt;그러면 원래 \(\mathcal{L}_{\text{SM}}(\phi)\)와 결합하면&lt;/p&gt;

\[\begin{align*}
\mathcal{L}_{\text{SM}}(\phi) &amp;amp;= 
\frac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \|\mathbf{s}_{\phi}(\mathbf{x})\|_2^2 \right] - \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \langle \mathbf{s}_{\phi}(\mathbf{x}), \mathbf{s}(\mathbf{x}) \rangle \right]  \\
&amp;amp;\quad + \frac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ \|\mathbf{s}(\mathbf{x})\|_2^2 \right] \\
&amp;amp;= \underbrace{\mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ Tr(\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x})) + \dfrac{1}{2} \|\mathbf{s}_{\phi}(\mathbf{x})\|_2^2 \right]}_{\tilde{\mathcal{L}}_{\text{SM}}(\phi)} \\
&amp;amp;\quad + \underbrace{\dfrac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})}  \left[  \|\mathbf{s}(\mathbf{x})\|_2^2 \right]}_{C}
\end{align*}\]

&lt;h4 id=&quot;stationarity-from-the-magnitude-term&quot;&gt;Stationarity from the Magnitude Term&lt;/h4&gt;
&lt;p&gt;Loss funciton \(\tilde{\mathcal{L}}_{\text{SM}}(\phi)\)의 첫번쨰 항인 \(\dfrac{1}{2} \|\mathbf{s}_{\phi}(\mathbf{x})\|_2^2\)는 모델의 score의 크기를 0으로 만드는 역할을 한다. (\(\mathbf{s}_{\phi}(\mathbf{x}) \rightarrow 0\))
이 항은 \(p_{\text{data}}(\mathbf{x})\)에 대한 기대값 안에 포함되어 있어 데이터 밀도가 높은 영역 (High density region)에서 더 강력하게 작용한다. 
즉 데이터가 많이 존재하는 곳에서는 Score(gradient 역할)은 0으로 수렴하게 되어 입자가 더 이상 이동하지 않고 머무르게 하는 stationary point를 형성한다.&lt;/p&gt;

&lt;h4 id=&quot;concavity-when-the-field-is-approximately-a-gradient&quot;&gt;Concavity When the Field is (Approximately) a Gradient&lt;/h4&gt;

&lt;p&gt;Loss funciton \(\tilde{\mathcal{L}}_{\text{SM}}(\phi)\)의 두번쨰 항인 \(Tr(\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x}))\)을 최소화한다는 것은 벡터장이 발산(Divergence)를 Negative로 만든다는 것이다. 즉 한 점으로 모이는 수렴(Sink)역할을 유도한다.&lt;/p&gt;

&lt;p&gt;기하학적으로는 어떤 potential \(u\)에 대한 scalar function \(u: \mathbb{R}^D \rightarrow \mathbb{R}\)이 존재하고 \(\mathbf{s}_\phi = \nabla_\mathbf{x} u\)라고 가정해보자.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Jacobian은 Hessian이 된다. (\(\nabla_\mathbf{x} \mathbf{s}_\phi = \nabla^2_\mathbf{x} u\))&lt;/li&gt;
  &lt;li&gt;Divergence는 Hessian의 trace가 된다. (\(\nabla_\mathbf{x} \cdot \mathbf{s}_\phi (\mathbf{x}) = Tr(\nabla^2_\mathbf{x} u(\mathbf{x}))\))&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;데이터가 존재하는 정지점(stationary point, \(\mathbf{x}_*\))에서 taylor expansion을 수행하면
1차항은 \(\mathbf{s}_\phi (\mathbf{x}_*) = \nabla_{\mathbf{x}_*} u(\mathbf{x}_*) = \mathbf{0}\)이 되고,
2차항인 Hessian이 지배적인 항이 된다.
이 때 \(Tr(\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x}))\)가 음수가 되도록 학습하므로 해당 지점이 모든 방향에서 위로 볼록(Concave)한 형태, 즉 극대점(Local minimum)이 된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;요약하자면:&lt;/strong&gt; Score matching은 데이터가 있는 곳(정상)은 평평하게(Stationary) 만들고, 그 주변은 정상을 향해 모여들도록 산처럼 위로 볼록한(Concave) 형태를 띠게 만드는 과정이다.&lt;/p&gt;

&lt;h4 id=&quot;sampling-with-langevin-dynamics&quot;&gt;Sampling with Langevin Dynamics&lt;/h4&gt;

&lt;p&gt;이렇게 구한 Score ($\mathbf{s}_{\phi^x})$를 가지고 추론할 때 Dontinous-time Langevin Dynamics를 적용하면 다음과 같다.&lt;/p&gt;

\[\begin{equation}
\mathbf{x}_{n+1} = \mathbf{x}_{n} + \eta \mathbf{s}_{\phi^x} (\mathbf{x}_n) + \sqrt{2\eta} \mathbf{\epsilon}_n, \quad \mathbf{\epsilon}_n \sim \mathcal{N}(\mathbf{0}, \mathbf{I})
\end{equation}\]

&lt;p&gt;이를 Continous-time Langevin SDE형태로 작성하면 다음과 같다. Dontinous-time Langevin Dynamics는 Continous-time Langevin SDE에 the Euler–Maruyama discretization을 적용한 형태라고 보면 된다.&lt;/p&gt;

\[\begin{equation}
d\mathbf{x} = \eta \mathbf{s}_{\phi^x} (\mathbf{x}(t)) dt + \sqrt{2}d \mathbf{w} (t)
\end{equation}\]

&lt;h2 id=&quot;denoising-score-matching&quot;&gt;Denoising Score Matching&lt;/h2&gt;

&lt;h3 id=&quot;sliced-score-matching-and-hutchinsons-estimator&quot;&gt;Sliced Score Matching and Hutchinson’s Estimator&lt;/h3&gt;
&lt;p&gt;\(\begin{equation*}
\tilde{\mathcal{L}}_{\text{SM}}(\phi) = \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})} \left[ Tr(\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x})) + \dfrac{1}{2} \|\mathbf{s}_{\phi}(\mathbf{x})\|_2^2 \right]
\end{equation*}\)&lt;/p&gt;

&lt;p&gt;이 Loss function은 기존 \(\mathcal{L}_{\text{SM}}(\phi)\)보다는 tractable하나 앞서 언급한 것처럼 trace of Jacobian인 \(Tr(\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x}))\)이 quadratic하다는 것이 문제였다. 이를 해결하기 위해 &lt;em&gt;모든 차원의 Jacobian이 아닌 단면만 보고 1차원적인 기울기&lt;/em&gt;를 통해 trace 항을 대체한다.&lt;/p&gt;

&lt;p&gt;만약 \(D\)차원에서 isotropic random vector \(\mathbf{u} \in \mathbb{R}^D\)가 존재한다고 가정하자. (Rademacher나 standard Gaussian이면 된다.)
이 때 \(\mathbb{E} [\mathbf{u}] = 0\)고 \(\mathbb{E} [\mathbf{u} \mathbf{u}^{\mathsf{T}}]= \mathbf{I}\)를 만족한다.&lt;/p&gt;

&lt;p&gt;이 경우 Hutchinson’s identity는 다음을 만족한다.&lt;/p&gt;

\[\begin{align}
Tr(A) &amp;amp;= \mathbb{E}_\mathbf{u} [ \mathbf{u}^\mathsf{T} \mathbf{A} \mathbf{u}] \\
\mathbb{E}_\mathbf{u} [(\mathbf{u}^\mathsf{T} \mathbf{s}_\phi(\mathbf{x}))^2] &amp;amp;= \| \mathbf{s}_\phi (\mathbf{x}) \|^2_2
\end{align}\]

&lt;p&gt;그러면 기존 Loss function에서 trace term은 대체가 되고 다음과 같이 다시 쓸 수 있다.&lt;/p&gt;

\[\begin{equation*}
\tilde{\mathcal{L}}_{\text{SM}}(\phi) = \mathbb{E}_{\mathbf{x}, \mathbf{u}} \left[ \mathbf{u}^\mathsf{T} (\nabla_\mathbf{x} \mathbf{s}_\phi (\mathbf{x}))\mathbf{u} + \dfrac{1}{2} (\mathbf{u}^\mathsf{T} \mathbf{s}_{\phi}(\mathbf{x}) )^2 \right]
\end{equation*}\]

&lt;p&gt;이렇게 변형된 Loss function은 \(\mathbf{J}\)를 Jacobian을 전부 계산하는 것이 아닌 결합법칙을 사용하여 
Jacobian-vector-product 혹은 Vector-Jacboian-product 형태로 행렬과 벡터의 곱으로 효과적으로 계산한다.
이는 랜덤한 방향에서의 모델의 변화량 혹은 움직임을 보는 것으로 데이터 포인트에서는 loss가 stationary하게 유지될 것이다.
그러나, low manifolds에 이미지가 의존하게 되어 벡터 필드 score가 불안정하게 될 가능성이 높다.
즉, sliced score matching으로는 data point 근처에서 concave를 유지하게 만드는 constraint가 없다.
또한, Jacobian을 통으로 계산하는 것보단 낫지만 JVP/VJP를 통해 여러번 행렬-벡터 연산을 수행하면서 계산이 많아지고 variance가 생기는 문제도 있다.
이를 해결하기 위해 Denoising Score Matching이 등장하게 된다.&lt;/p&gt;

&lt;h3 id=&quot;training&quot;&gt;Training&lt;/h3&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#vincent2011connection&quot;&gt;[4]&lt;/a&gt;에서는 Denosing Score Matching을 통해 
이론적으로 principled하고 현실적으로도 robust하고 scalable한 솔루션을 제공하고자 했다.&lt;/p&gt;

&lt;p&gt;DSM은 Loss function을 원점에서 다시 생각한다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{SM} (\phi) &amp;amp;=\dfrac{1}{2} \mathbb{E}_{p_{\textrm{data}}(\mathbf{x})} \|\mathbf{s}_\phi (\mathbf{x})-  \nabla_\mathbf{x} p_{\textrm{data}} (\mathbf{x})\|^2_2
\end{align}\]

&lt;p&gt;여기서 맨 처음 문제가 되었던건 \(\nabla_\mathbf{x} p_{\textrm{data}} (\mathbf{x})\) 이 항이 intractable하다는 것이다.&lt;/p&gt;

&lt;h4 id=&quot;vincents-solution-by-conditioning&quot;&gt;Vincent’s Solution by Conditioning&lt;/h4&gt;

&lt;p&gt;Vincent는 이 문제를 scale \(\sigma\)를 따르는 조건부 분포(known) \(p_\sigma (\tilde{\mathbf{x}} \mid \mathbf{x})\)를 도입하여 
데이터 \(\mathbf{x} \sim p_{data}\)에 노이즈를 주입하는 방식을 제안했다.&lt;/p&gt;

&lt;p&gt;이 때 신경망 \(p_\sigma (\tilde{\mathbf{x}})\)은 다음과 같이 정의되는 marginal perturbed distribution의 score을 근사하도록 gkrtmqgksek.&lt;/p&gt;

\[p_\sigma (\tilde{\mathbf{x}}) = \int p_\sigma (\tilde{\mathbf{x}} \mid \mathbf{x}) p_{data} (\mathbf{x}) d \mathbf{x}\]

&lt;p&gt;따라서 최소화할 Loss는 다음과 같다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{SM} (\phi; \sigma) := \dfrac{1}{2} \mathbb{E}_{\tilde{\mathbf{x}} \sim p_\sigma} \left[ \|\mathbf{s}_\phi (\tilde{\mathbf{x}};\sigma) -  \nabla_\tilde{\mathbf{x}} \log p_\sigma (\tilde{\mathbf{x}})\|^2_2 \right] 
\end{align}\]

&lt;p&gt;그냥 단순히 \(\sigma\)와 \(\tilde{\mathbf{x}}\)를 도입한 것에 지나지 않아 \(\nabla_\tilde{\mathbf{x}} \log p_\sigma (\tilde{\mathbf{x}})\)가 여전히 intractable하지만, Vincent는 결국 \(\mathbf{x} \sim p_{data}\)에 conditioning을 추가하면 다음과 같은 Loss가 되고 이는 위의 Loss와 동일하다는 것을 보였다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{DSM} (\phi; \sigma) := \dfrac{1}{2} \mathbb{E}_{\mathbf{x} \sim p_{data}, \tilde{\mathbf{x}} \sim p_\sigma (\cdot \mid \mathbf{x})} \left[ \|\mathbf{s}_\phi (\tilde{\mathbf{x}};\sigma) -  \nabla_\tilde{\mathbf{x}} \log p_\sigma (\tilde{\mathbf{x}} \mid \mathbf{x})\|^2_2 \right] 
\end{align}\]

&lt;p&gt;이렇게 구해서 최적화된 신경망 (optimal minimizer) \(\mathbf{s}^*\)은 conditioning을 처음 도입했을 때 신경망이 도달하고자 했던 목표
\(\nabla_\tilde{\mathbf{x}} \log p_\sigma (\tilde{\mathbf{x}})\)가 된다.&lt;/p&gt;

\[\begin{equation}
\mathbf{s}^* (\tilde{\mathbf{x}}; \sigma) = \nabla_{\tilde{\mathbf{x}}} \log p_\sigma (\tilde{\mathbf{x}})
\end{equation}\]

&lt;h4 id=&quot;equivalence-of-mathcall_sm-and-mathcall_dsm&quot;&gt;Equivalence of \(\mathcal{L}_{SM}\) and \(\mathcal{L}_{DSM}\)&lt;/h4&gt;

&lt;p&gt;위 내용을 formalize하면 다음과 같다.&lt;/p&gt;

&lt;p&gt;어떤 고정된 noise scale \(\sigma &amp;gt; 0\)에 대해서 다음을 만족한다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}_{SM} (\phi ; \sigma) = \mathcal{L}_{DSM} (\phi ; \sigma) + C
\end{equation}\]

&lt;p&gt;이 떄 $C$는 파라미터 $\phi$와 무관한 상수이며 두 Loss가 만드는 minimizer \(s^*(\cdot ; \sigma\)는 다음과 같다.&lt;/p&gt;

\[\begin{equation}
\mathbf{s}^* (\tilde{\mathbf{x}}; \sigma)  = \nabla_{\tilde{\mathbf{x}}} \log p_\sigma (\tilde{\mathbf{x}}), \quad \text{for almost every } \tilde{\mathbf{x}}
\end{equation}\]

&lt;p&gt;증명은 각 MSE Loss를 전개하면 $\phi$관련된 term은 소거되고 관련없는 term만 $C$로 남게 된다.&lt;/p&gt;

&lt;p&gt;이를 통해 DDPM부터 내려온 다음과 같은 테크닉을 알 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;데이터 포인트 $\mathbf{x}$에 대해 conditioning을 사용해서 intractable한 loss를 tractable한 loss로 바꾼다. (MOnte Carlo Estimation 대상)&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;special-case-additive-gaussian-noise&quot;&gt;Special Case: Additive Gaussian Noise&lt;/h4&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;
&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;lai2025principles&quot;&gt;[1]C.-H. Lai, Y. Song, D. Kim, Y. Mitsufuji, and S. Ermon, “The Principles of Diffusion Models,” &lt;i&gt;arXiv preprint arXiv:2510.21890&lt;/i&gt;, 2025.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;song2020score&quot;&gt;[2]Y. Song, J. Sohl-Dickstein, D. P. Kingma, A. Kumar, S. Ermon, and B. Poole, “Score-based generative modeling through stochastic differential equations,” &lt;i&gt;arXiv preprint arXiv:2011.13456&lt;/i&gt;, 2020.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;hyvarinen2005estimation&quot;&gt;[3]A. Hyvärinen and P. Dayan, “Estimation of non-normalized statistical models by score matching.,” &lt;i&gt;Journal of Machine Learning Research&lt;/i&gt;, vol. 6, no. 4, 2005.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;vincent2011connection&quot;&gt;[4]P. Vincent, “A connection between score matching and denoising autoencoders,” &lt;i&gt;Neural computation&lt;/i&gt;, vol. 23, no. 7, pp. 1661–1674, 2011.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>From VAEs to DDPMs</title>
   <link href="https://blog.liam.kim/posts/2025/11/VAE-to-DDPMs/"/>
   <updated>2025-11-02T02:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2025/11/VAE-to-DDPMs</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;최근에 올라온 이 책 저자가 저자라 그런지 너무나도 깔끔하고 아름답다.
&lt;a class=&quot;citation&quot; href=&quot;#lai2025principles&quot;&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;따라서 한번 챕터별로 정리하면서 읽어보려고 한다. 특히 현재 챕터는 다음 글이 같이 떠올랐고 같이 읽어보면 좋을 것 같다.
&lt;a class=&quot;citation&quot; href=&quot;#dieleman2022diffusion&quot;&gt;[2]&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;review-of-bayesian-inference&quot;&gt;Review of Bayesian Inference&lt;/h2&gt;

&lt;p&gt;베이즈 추론(Bayesian Inference)에서 &lt;strong&gt;훈련&lt;/strong&gt;과 &lt;strong&gt;추론(예측)&lt;/strong&gt;은 모두 베이즈 정리를 기반으로 하지만, 초점과 입출력이 다르다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;훈련(Training): 데이터 $\mathcal{D}$을 사용해 모델의 파라미터 $\theta$에 대한 믿음을 업데이트(추론)&lt;/li&gt;
  &lt;li&gt;추론(Inference): 업데이트된 믿음을 사용해 새로운 데이터 $\mathcal{D}_{new}$를 예측하는 과정&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;bayes-rule&quot;&gt;Bayes Rule&lt;/h3&gt;
&lt;p&gt;관찰된 &lt;strong&gt;결과(Data)&lt;/strong&gt;를 바탕으로 관찰되지 않은 &lt;strong&gt;원인(Parameter)&lt;/strong&gt;을 추론하는 것&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;원인: $\theta$
    &lt;ul&gt;
      &lt;li&gt;알고자 하는 목표&lt;/li&gt;
      &lt;li&gt;모델의 파라미터, 시스템의 hidden state 등&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;결과: $\mathcal{D}$
    &lt;ul&gt;
      &lt;li&gt;관찰한 것&lt;/li&gt;
      &lt;li&gt;실험 결과, 수집된 데이터 샘플&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

\[\begin{equation}
P(\theta \mid \mathcal{D}) = \dfrac{P(\mathcal{D} \mid \theta) P(\theta)}{P(\mathcal{D})}
\end{equation}\]

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Posterior:&lt;/p&gt;

\[P(\theta \mid \mathcal{D})\]

    &lt;ul&gt;
      &lt;li&gt;(원인 $\mid$ 결과)&lt;/li&gt;
      &lt;li&gt;정의: 결과($\mathcal{D}$)를 관찰했을 때, 이 현상의 원인이 $\theta$일 확률.&lt;/li&gt;
      &lt;li&gt;의미:
        &lt;ul&gt;
          &lt;li&gt;데이터를 본 후 업데이트된 &lt;em&gt;믿음&lt;/em&gt; 또는 &lt;em&gt;지식&lt;/em&gt;&lt;/li&gt;
          &lt;li&gt;훈련의 최종 목표&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Likelihood:&lt;/p&gt;

\[P(\mathcal{D} \mid \theta)\]

    &lt;ul&gt;
      &lt;li&gt;(결과 $\mid$ 원인)&lt;/li&gt;
      &lt;li&gt;정의: 원인이 $\theta$라고 가정할 때, 관찰한 결과($\mathcal{D}$)가 나타날 확률.&lt;/li&gt;
      &lt;li&gt;의미:
        &lt;ul&gt;
          &lt;li&gt;특정 파라미터($\theta$)가 주어진 데이터를 얼마나 잘 설명하는지를 나타내는 값&lt;/li&gt;
          &lt;li&gt;데이터가 모델에 &lt;em&gt;적합&lt;/em&gt;한 정도를 측정&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Prior
    &lt;ul&gt;
      &lt;li&gt;(원인)&lt;/li&gt;
      &lt;li&gt;정의: 결과($\mathcal{D}$)를 관찰하기 전에, 원인이 $\theta$일 것이라고 믿는 초기 확률&lt;/li&gt;
      &lt;li&gt;의미:
        &lt;ul&gt;
          &lt;li&gt;데이터와 무관하게 우리가 이미 가지고 있는 사전 지식이나 가정&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Evidence
    &lt;ul&gt;
      &lt;li&gt;(결과)&lt;/li&gt;
      &lt;li&gt;정의: 우리가 관찰한 결과($\mathcal{D}$)가 나타날 총 확률&lt;/li&gt;
      &lt;li&gt;의미:
        &lt;ul&gt;
          &lt;li&gt;
            &lt;p&gt;모든 가능한 원인($\theta$)에 대해 가중 평균을 낸 값&lt;/p&gt;

\[P(\mathcal{D}) = \int P(\mathcal{D} \mid \theta) P(\theta) d\theta\]
          &lt;/li&gt;
          &lt;li&gt;실제로는 Posterior를 확률 분포로 만들기 위한 정규화 상수&lt;/li&gt;
          &lt;li&gt;계산이 매우 어려워 MCMC 같은 샘플링 기법을 사용하여 계산&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;훈련-training&quot;&gt;훈련 (Training)&lt;/h3&gt;

&lt;p&gt;데이터($\mathcal{D}$)를 사용해 파라미터($\theta$)의 사후 확률 분포($P(\theta \mid \mathcal{D})$)를 찾는 과정&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;목표: 데이터가 주어졌을 때, 모델 파라미터($\theta$)가 어떠한 분포를 따르는지 알아내는 과정
    &lt;ul&gt;
      &lt;li&gt;$P(\theta \mid \mathcal{D})$를 구하는 것&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;입력 (Inputs):
    &lt;ul&gt;
      &lt;li&gt;데이터 (Data): $\mathcal{D}$&lt;/li&gt;
      &lt;li&gt;사전 확률 (Prior): $P(\theta)$ (우리가 $\theta$에 대해 미리 가정한 분포)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;가능도 함수 (Likelihood Model): $P(\mathcal{D} \mid \theta)$ (데이터 생성 과정을 정의하는 모델)&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;과정 (Process):
    &lt;ul&gt;
      &lt;li&gt;베이즈 정리를 적용: $P(\theta \mid \mathcal{D}) \propto P(\mathcal{D} \mid \theta) P(\theta)$ (참고: $P(\mathcal{D})$는 $\theta$에 대해 상수이므로 비례 관계로 표현)&lt;/li&gt;
      &lt;li&gt;실제로는 $P(\mathcal{D})$ 계산이 어렵거나 $P(\theta \mid \mathcal{D})$가 복잡한 형태일 경우, MCMC(Markov Chain Monte Carlo)나 변분 추론(Variational Inference) 같은 근사(approximation) 방식을 사용해 $P(\theta \mid \mathcal{D})$에서 샘플을 추출하거나 근사 분포를 찾습니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;출력 (Output):
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;사후 확률 분포 (Posterior Distribution): $P(\theta \mid \mathcal{D})$&lt;/strong&gt;
        &lt;ul&gt;
          &lt;li&gt;단일 값이 아닌, $\theta$가 가질 수 있는 값들에 대한 확률 분포.
            &lt;ul&gt;
              &lt;li&gt;(예: $\theta$는 95% 확률로 [2.5, 3.5] 사이에 있다.)&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;추론-inference&quot;&gt;추론 (Inference)&lt;/h3&gt;

&lt;p&gt;훈련을 통해 얻은 사후 확률 분포($P(\theta \mid \mathcal{D})$)를 사용해, 아직 보지 못한 새로운 데이터($\mathcal{D}_{new}$)가 어떨지 예측하는 과정&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;목표: 이미 관찰한 데이터 $\mathcal{D}$를 바탕으로, 새로운 데이터 \(\mathcal{D}_{new}\)의 분포인 사후 예측 분포(\(P(\mathcal{D}_{new} \mid \mathcal{D})\))를 구하기&lt;/li&gt;
  &lt;li&gt;입력 (Inputs):
    &lt;ul&gt;
      &lt;li&gt;사후 확률 분포 (Posterior): $P(\theta \mid \mathcal{D})$ (훈련 단계의 출력)&lt;/li&gt;
      &lt;li&gt;가능도 함수 (Likelihood Model): $P(\mathcal{D}_{new} \mid \theta)$ (새로운 데이터를 생성할 모델)&lt;/li&gt;
      &lt;li&gt;(필요시) 새로운 입력값 (New Input): $x_{new}$ (예: \(\mathcal{D}_{new} = y_{new}\)를 예측하기 위한 $x_{new}$)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;과정(Posterior Predictive Distribution)
    &lt;ul&gt;
      &lt;li&gt;$\theta$ 값을 하나로 &lt;em&gt;확정&lt;/em&gt;한다면, $P(\mathcal{D}_{new} \mid \theta)$이 고정된 예측값&lt;/li&gt;
      &lt;li&gt;베이즈 추론에서는 $\theta$가 $P(\theta \mid \mathcal{D})$라는 ‘분포’를 가짐 ($\theta$ 값에 불확실성이 존재)&lt;/li&gt;
      &lt;li&gt;가능한 모든 $\theta$ 값에 대해 예측(\(P(\mathcal{D}_{new} \mid \theta)\))을 수행하고 이를 해당 $\theta$가 맞을 확률($P(\theta \mid \mathcal{D})$)로 가중 평균&lt;/li&gt;
    &lt;/ul&gt;

\[\begin{equation}
P(\mathcal{D}_{new} \mid \mathcal{D}) = \int \underbrace{P(\mathcal{D}_{new} \mid \theta)}_{\text{Likelihood (예측)}} \underbrace{P(\theta \mid \mathcal{D})}_{\text{Posterior (가중치)}} d\theta
\end{equation}\]
  &lt;/li&gt;
  &lt;li&gt;출력
    &lt;ul&gt;
      &lt;li&gt;사후 예측 분포 (Posterior Predictive Distribution): $P(\mathcal{D}_{new} \mid \mathcal{D})$&lt;/li&gt;
      &lt;li&gt;$\mathcal{D}_{new}$에 대한 예측을 확률 분포로 제공
        &lt;ul&gt;
          &lt;li&gt;예: “새로운 환자의 생존 확률은 70%”가 아니라, “생존 확률은 95% 신뢰구간으로 [0.65, 0.75] 사이에 있다”처럼 예측의 불확실성(uncertainty)까지 포함&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;&lt;strong&gt;1. 훈련 (Training / Parameter Inference)&lt;/strong&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;&lt;strong&gt;2. 추론 (Inference / Prediction)&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;목적&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;데이터($\mathcal{D}$)를 통해 &lt;strong&gt;파라미터($\theta$)의 분포&lt;/strong&gt;를 학습&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;학습된 &lt;strong&gt;파라미터 분포&lt;/strong&gt;를 사용해 &lt;strong&gt;새 데이터($\mathcal{D}_{new}$)&lt;/strong&gt; 예측&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;핵심 질문&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;“데이터를 보니, 모델 파라미터($\theta$)는 무엇일까?”&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;“지금까지의 데이터와 모델을 보니, 다음 데이터($D_{new}$)는 어떨까?”&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;주요 수식&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Posterior&lt;/strong&gt; &lt;br /&gt; $P(\theta \mid \mathcal{D}) \propto P(\mathcal{D} \mid \theta) P(\theta)$&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Posterior Predictive&lt;/strong&gt; &lt;br /&gt; \(P(\mathcal{D}_{new} \mid \mathcal{D}) = \int P(\mathcal{D}_{new} \mid \theta) P(\theta \mid \mathcal{D}) d\theta\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;입력 (Input)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;* Data ($\mathcal{D}$) &lt;br /&gt; * Prior ($P(\theta)$) &lt;br /&gt; * Likelihood ($P(\mathcal{D} \mid \theta)$)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;* &lt;strong&gt;Posterior ($P(\theta \mid \mathcal{D})$)&lt;/strong&gt; (훈련의 결과물) &lt;br /&gt; * Likelihood ($P(\mathcal{D}_{new} \mid \theta)$)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;출력 (Output)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Posterior Distribution&lt;/strong&gt; &lt;br /&gt; $P(\theta \mid \mathcal{D})$ &lt;br /&gt; (파라미터 $\theta$에 대한 확률 분포)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Posterior Predictive Distribution&lt;/strong&gt; &lt;br /&gt; \(P(\mathcal{D}_{new} \mid \mathcal{D})\) &lt;br /&gt; (새 데이터 $\mathcal{D}_{new}$에 대한 확률 분포)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;deep-generative-modeling-dgm&quot;&gt;Deep Generative Modeling (DGM)&lt;/h2&gt;

&lt;p&gt;생성모델은 고차원 데이터로부터 확률 분포를 학습하여 데이터셋과 유사한 새로운 샘플을 생성해 내는 것이다.&lt;/p&gt;

&lt;p&gt;훈련 데이터 분포를 $p_{\text{data}}$라고 하고, 모델의 확률분포를 $p_\phi (x)$라고 가정하자.
이 떄 모델의 파라미터 $\phi$를 훈련하여 $p_{\text{data}}$를 $p_\phi (x)$에 근사하게 만드는것이 생성모델의 목표이다.
두 분포가 얼마나 멀리 떨어져있는가? (KL Divergence 등) 를 측정하는것이 loss function의 역할을 하게 된다.&lt;/p&gt;

\[\begin{equation}
p_{\phi^*} (\mathbf{x}) \approx p_{\text{data}} (\mathbf{x})
\end{equation}\]

&lt;h3 id=&quot;training-dgm&quot;&gt;Training DGM&lt;/h3&gt;
&lt;p&gt;모델 family $\lbrace p_{\phi} \rbrace$는 discrepancy $\mathcal{D} (p_{\text{data}}, p_\phi)$&lt;/p&gt;

\[\begin{equation}
\phi^* \in \mathop{\arg\min}\limits_{\phi} \mathcal{D} (p_{\text{data}}, p_\phi)
\end{equation}\]

&lt;p&gt;이 때 $p_{\text{data}}$는 알 수 없는 값이므로, 데이터로부터 i.i.d. samples을 뽑아서 추정한다.&lt;/p&gt;

&lt;h4 id=&quot;kl-divergence&quot;&gt;KL Divergence&lt;/h4&gt;
&lt;p&gt;$\mathcal{D} (p_{\text{data}}, p_\phi)$ ($p_\phi$로 $p_{\text{data}}$를 모델링할 때 잃어버리는 정보량) 로 가장 많이 쓰이는 것이 KL Divergence이다.&lt;/p&gt;

\[\begin{align}
\mathcal{D}_{\mathrm{KL}}(p_{\text{data}} \| p_{\phi})
&amp;amp;:= \int p_{\text{data}}(\mathbf{x})
\log \frac{p_{\text{data}}(\mathbf{x})}{p_{\phi}(\mathbf{x})}
\, d\mathbf{x} \\
&amp;amp;= \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}}
\left[ \log p_{\text{data}}(\mathbf{x}) - \log p_{\phi}(\mathbf{x}) \right].
\end{align}\]

&lt;p&gt;하지만 forward KL \(\mathcal{D}_{\mathrm{KL}}(p_{\text{data}} \| p_{\phi})\)를 최소화하는 과정은 모드 커버링(mode covering)이라는 생성하게 된다. 이는, 데이터가 존재하는 영역 $p_{\text{data}} &amp;gt; 0$이지만, 모델이 그 영역에 확률을 주지 않으면 $p_{\phi}(\mathbf{x}) = 0$이 되고&lt;/p&gt;

&lt;p&gt;\(\begin{equation*}
\log \frac{p_{\text{data}}(\mathbf{x})}{p_{\phi}(\mathbf{x})} = + \infty
\end{equation*}\)
가 되어 KL이 무한대가 되기 때문에 데이터가 존재하는 모든 영역에 확률을 주려고 한다.&lt;/p&gt;

&lt;p&gt;그러나 반대로 reverse KL \(\mathcal{D}_{\mathrm{KL}}(p_{\phi} \| p_{\text{data}} )\)에서는 데이터가 존재하는 일부 영역을 놓쳐도 큰 손해가 나지 않아 페널티가 크지 않다. 이를 큰 모드에 집중하는 mode seeking 이라고 한다.&lt;/p&gt;

&lt;p&gt;또한 $p_{\text{data}}$는 직접 구할 수 없으므로 forward KL은 다음과 같이 쓸 수 있다. 이 떄, \(\mathcal{H}(p_{\text{data}})
:= - \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}}\)는 데이터의 entropy이다.&lt;/p&gt;

\[\begin{align}
\mathcal{D}_{\mathrm{KL}}(p_{\text{data}} \| p_{\phi})
&amp;amp;= \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}}
\left[
\log \frac{p_{\text{data}}(\mathbf{x})}{p_{\phi}(\mathbf{x})}
\right] \\
&amp;amp;= - \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}}
\left[ \log p_{\phi}(\mathbf{x}) \right]
+ \mathcal{H}(p_{\text{data}}),
\end{align}\]

&lt;p&gt;즉 KL을 최소화하는 것은 MLE와 같은 말이다.&lt;/p&gt;

\[\begin{align}
\min_{\phi} \, \mathcal{D}_{\mathrm{KL}}(p_{\text{data}} \| p_{\phi})
\;\Longleftrightarrow\;
\max_{\phi} \,
\mathbb{E}_{\mathbf{x} \sim p_{\text{data}}}
\left[ \log p_{\phi}(\mathbf{x}) \right].
\end{align}\]

&lt;h2 id=&quot;variational-autoencodervae&quot;&gt;Variational Autoencoder(VAE)&lt;/h2&gt;

&lt;p&gt;기존의 Autoencoder는 고정된 latent vector가 있지만, VAE에서는 latent vector를 확률 분포로 만들어 generative model로 만들어버림&lt;/p&gt;

&lt;h3 id=&quot;probabilistic-encoder-and-decoder&quot;&gt;Probabilistic Encoder and Decoder&lt;/h3&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/01-VAE.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a VAE&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/01-VAE.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a VAE&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of a VAE&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h4 id=&quot;construction-of-decoder-generator&quot;&gt;Construction of Decoder (Generator)&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;$\mathbf{x}$: 관찰된 변수. 우리가 보는 이미지&lt;/li&gt;
  &lt;li&gt;$\mathbf{z}$: 잠재 변수 (latent variable). 이미지에 숨겨진 feature&lt;/li&gt;
  &lt;li&gt;가정:
    &lt;ul&gt;
      &lt;li&gt;가우시안 같은 단순한 prior 분포($\mathbf{z} \sim p_{\text{prior}} := \mathbf{\mathcal{N}(0, I)}$)로부터 $\mathbf{x}$를 생성&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;$\mathbf{x}$와 $\mathbf{z}$의 생성
    &lt;ul&gt;
      &lt;li&gt;Decoder 분포를 $p_\theta(\mathbf{x} \mid \mathbf{z})$라고 했을때,&lt;/li&gt;
      &lt;li&gt;Sampling: $\mathbf{z} \sim p_{\text{prior}}$&lt;/li&gt;
      &lt;li&gt;Decoding: $\mathbf{x} \sim p_\theta(\mathbf{x} \mid \mathbf{z})$&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAE는 다음과 같은 &lt;strong&gt;marignal likelihood&lt;/strong&gt;에 의해 latent-variable generative model로 정의된다.
이게 $p_\theta(\mathbf{x} \mid \mathbf{z})$는 $z$라는 원인으로부터 $x$가 생성(결과)이므로 (결과 $\mid$ 원인)인 likelihood이기 때문이다.&lt;/p&gt;

\[\begin{equation}
p_\phi (x) = \int p_\phi(\mathbf{x} \mid \mathbf{z}) p(\mathbf{z}) d\mathbf{z}
\end{equation}\]

&lt;p&gt;이상적으로는 decoder 파라미터 $\phi$는 marignal likelihood를 최대화해서 학습할 수 있지만. $p(\mathbf{z})$를 구하기가 힘들다.&lt;/p&gt;

&lt;p&gt;왜냐하면 $\mathbf{z}$가 $m$개의 변수라고 하고, 각 변수마다 $n$개씩 쪼개서 계산한다 하더라도 $m \times n$이라는 엄청난 수의 grid point가 필요하기 때문이다. 따라서 이를 변수를 바꿔서 계산(변분법)하게 된다.&lt;/p&gt;

&lt;h4 id=&quot;construction-of-encoder-inference-network&quot;&gt;Construction of Encoder (Inference Network)&lt;/h4&gt;

&lt;p&gt;반대로 $\mathbf{x}$로 부터 $\mathbf{z}$를 생성하기 위해서는 $p_\theta(\mathbf{z} \mid \mathbf{x})$가 필요하고 베이즈 정리에 따라 다음과 같이 계산한다.&lt;/p&gt;

\[\begin{equation*}
p_\phi(\mathbf{z} \mid \mathbf{x}) = \dfrac{p_\phi(\mathbf{x} \mid \mathbf{z}) p(\mathbf{z})}{p(\mathbf{x})}
\end{equation*}\]

&lt;p&gt;Decoder와 마찬가지로 $p(\mathbf{x})$를 일반적인 계산으로 구하기 어렵다. (intractable) 즉, $\mathbf{x}$로부터 $\mathbf{z}$를 구하는 것은 불가능하다.&lt;/p&gt;

&lt;p&gt;따라서, &lt;strong&gt;intractable posterior를 tractable approximation으로 교체&lt;/strong&gt;하는 것이 필수적이며 이걸 변분법적 접근이라고 한다.
Encoder 네트워크 $q_\theta(\mathbf{z} \mid \mathbf{x})$를 가정해서 다음과 같이 정의하자.&lt;/p&gt;

\[\begin{equation*}
q_\theta(\mathbf{z} \mid \mathbf{x}) \approx p_\phi(\mathbf{z} \mid \mathbf{x})
\end{equation*}\]

&lt;p&gt;여기서 $q_\theta(\mathbf{z} \mid \mathbf{x})$는 근사 사후 확률(Approximate Posterior)가 된다.&lt;/p&gt;

&lt;p&gt;즉 Decoder는 Likelihood $\phi_\phi(x \mid z)$를, Encoder는 Approximate posterior $q_\theta(z \mid x)$를 훈련한다.&lt;/p&gt;

&lt;h3 id=&quot;training-via-the-evidence-lower-bound-elbo&quot;&gt;Training via the Evidence Lower Bound (ELBO)&lt;/h3&gt;

&lt;p&gt;지금까지 내용을 정리해보자면, 생성 모델인 모델이 얼마나 데이터 $x$를 잘 설명하는가?이고, 이는 데이터의 가능도 $\log p_\phi(\mathbf{x})$를 최대화하여 이루어진다. 그러나, VAE에서는 $\log p_\phi(\mathbf{x})$를 직접 구하려면 모든 $\mathbf{z}$에 대해 구해야해서 어렵다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;VAE(생성 모델)의 목표: Marginal Likelihood $\log p_\phi(\mathbf{x})$의 최대화&lt;/li&gt;
  &lt;li&gt;필요한 것: $p (\mathbf{z} \mid \mathbf{x})$&lt;/li&gt;
  &lt;li&gt;문제점:
    &lt;ol&gt;
      &lt;li&gt;$p_\phi (x) = \int p_\phi(\mathbf{x} \mid \mathbf{z}) p(\mathbf{z}) d\mathbf{z}$의 모든 $\mathbf{z}$에 대해서 구하는건 불가능&lt;/li&gt;
      &lt;li&gt;$\mathbf{z}$에 대해 샘플링해서 구하자니 효율적으로 샘플링하지 않으면 대부분 0이 나올 가능성이 높음&lt;/li&gt;
      &lt;li&gt;$\mathbf{x}$가 주어졌을때 $\mathbf{x}$를 만들었을 $\mathbf{z}$의 분포 $p(\mathbf{z} \mid \mathbf{x})$가 필요&lt;/li&gt;
      &lt;li&gt;
\[p_\phi (\mathbf{z} \mid \mathbf{x}) = \frac{p_\phi(\mathbf{x} \mid \mathbf{z}) p(\mathbf{z})}{p(\mathbf{x})}\]
      &lt;/li&gt;
      &lt;li&gt;$p(\mathbf{x})$를 알아야함&lt;/li&gt;
      &lt;li&gt;목표로 다시 회귀. $\log p_\phi(\mathbf{x})$를 구할 수가 없음&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;해결책
    &lt;ol&gt;
      &lt;li&gt;True \(p_\phi (\mathbf{z} \mid \mathbf{x})\)는 찾을 수 없음&lt;/li&gt;
      &lt;li&gt;\(q_\theta (\mathbf{z} \mid \mathbf{x}) \approx p_\phi (\mathbf{z} \mid \mathbf{x})\)를 훈련하여 $q_\theta (z \mid x)$를 사용하자 (Variational Inference)&lt;/li&gt;
      &lt;li&gt;즉 $\log p_\phi(\mathbf{x})$를 최대화를 변수를 바꿔서 (변분 추론, Varaitional Inference)를 통해서 \(q_\theta (z \mid x)\)를 사용하자.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이렇게 된것이다. 여기서 ELBO가 나오게 된다.&lt;/p&gt;

&lt;h4 id=&quot;deriving-elbo&quot;&gt;Deriving ELBO&lt;/h4&gt;

&lt;p&gt;우리의 목표는 \(p_\phi (\mathbf{z} \mid \mathbf{x})\)와 근사한 \(q_\theta (z \mid x)\)를 찾는 것이다.
또한 $p_\phi (\mathbf{z})$를 $q_\theta (z \mid x)$로 변수를 바꾸는 것이 목표이다.
즉, 기존의 $\log p_\phi(\mathbf{x})$를 $q_\theta (z \mid x)$에 대해서 표현하는 것이다.&lt;/p&gt;

\[\begin{equation*}
p_\phi (\mathbf{z} \mid \mathbf{x}) = F \left( q_\theta (z \mid x) \right)
\end{equation*}\]

&lt;p&gt;이러면 $q_\theta (z \mid x)$에 대해 $\log p_\phi(\mathbf{x})$를 평균내어도 원래의 $\log p_\phi(\mathbf{x})$를 얻을 수 있어야 한다.&lt;/p&gt;

\[\begin{align*}
\log p_\phi(\mathbf{x}) &amp;amp;= \log \int p_\phi(\mathbf{x,z}) d\mathbf{z}\\
&amp;amp;=\log \int q_\theta (z \mid x) \dfrac{p_\phi(\mathbf{x,z})}{q_\theta (z \mid x)} d\mathbf{z} \\
&amp;amp;= \log \mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \dfrac{ p_\phi(\mathbf{x,z})}{q_\theta (z \mid x)} \right] \\
&amp;amp;\geq \mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log \left( \dfrac{p_\phi(\mathbf{x, z})}{q_\theta (z \mid x)} \right) \right] \\
&amp;amp;\equiv \mathcal{L} (\phi, \theta ; \mathbf{x}) \; \text{(ELBO)}
\end{align*}\]

&lt;p&gt;여기서 나온 ELBO는 다음과 같이 분해된다.&lt;/p&gt;

\[\begin{align*}
\mathcal{L} (\phi, \theta ; \mathbf{x}) &amp;amp;= \mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log \left( \dfrac{p_\phi(\mathbf{x, z})}{q_\theta (z \mid x)} \right) \right]  \\
&amp;amp;= \mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x, z}) - \log q_\theta (z \mid x) \right] \\
&amp;amp;= \mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x \mid z})p_\phi(\mathbf{z}) - \log q_\theta (z \mid x) \right] \\
&amp;amp;= \mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x \mid z}) + \log p_\phi(\mathbf{z}) - \log q_\theta (z \mid x) \right] \\
&amp;amp;= \underbrace{\mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x \mid z}) \right]}_{\text{Reconstruction Term}} +  \underbrace{\mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{z}) - \log q_\theta (z \mid x) \right]}_{\text{Latent Regularization}} \\

&amp;amp;= \underbrace{\mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x \mid z}) \right]}_{\text{Reconstruction Term}} - \underbrace{ \mathcal{D}_{KL} (q_\theta (z \mid x) \| p(\mathbf{z}))}_{\text{Latent Regularization}} \\

\end{align*}\]

&lt;p&gt;이 때 ELBO는 두 가지 term으로 구성된다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reconstruction (디코더 $z -&amp;gt; x$)
    &lt;ul&gt;
      &lt;li&gt;
\[\mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x \mid z}) \right]\]
      &lt;/li&gt;
      &lt;li&gt;방향: $\mathbf{z} \rightarrow \mathbf{x}$ (Decoder, 생성)&lt;/li&gt;
      &lt;li&gt;목표: 데이터 충실도 (Data Fidelity). 즉, $z$로부터 원본 $x$를 얼마나 잘 복원하는가?&lt;/li&gt;
      &lt;li&gt;최대화가 목표 (그래서 +)&lt;/li&gt;
      &lt;li&gt;디코더 출력 분포 $p_\phi (\mathbf{x} \mid \mathbf{z})$가 실제 데이터 분포 \(p_{\text{data}} (\mathbf{x})\) (입력 $x$로 대표됨)을 모델링할 떄의 비용
        &lt;ul&gt;
          &lt;li&gt;
\[D_{KL}(p_{\text{data}} (\mathbf{x}) || p_\phi (\mathbf{x} \mid \mathbf{z}))\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Latent KL (인코더 $x -&amp;gt; z$)
    &lt;ul&gt;
      &lt;li&gt;
\[\mathcal{D}_{KL} (q_\theta (z \mid x) \| p(\mathbf{z}))\]
      &lt;/li&gt;
      &lt;li&gt;방향: $\mathbf{x} \rightarrow \mathbf{z}$ (Encoder, 압축/추론)&lt;/li&gt;
      &lt;li&gt;목표: 잠재 공간 정규화. 즉, 인코더가 $z$를 $x$로 매핑할 때, 그 $z$의 분포가 우리가 원하는 단순한 형태 (보통 Gaussian인 $p(x)$)를 따르도록 강제&lt;/li&gt;
      &lt;li&gt;최소화가 목표 (그래서 -)&lt;/li&gt;
      &lt;li&gt;단순한 사전 분포 $p(x)$가 인코더의 출력분포 $q_\theta (z \mid x)$를 모델링할 때의 비용
        &lt;ul&gt;
          &lt;li&gt;
\[D_{KL}(q_\theta (z \mid x) || p(x))\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;여기서 다음과 같이 Trade-off가 있다.&lt;/p&gt;

&lt;p&gt;복원력에 초점을 두면 Reconstruction은 최대화할 수 있겠지만, 암기(memorization)에만 초점을 둬 \(q_\theta (z \mid x)\)가 뾰족해지고, Latent KL \(D_{KL}(q_\theta (z \mid x) || p(x))\)은 커지게된다.
반대로 잠재 공간을 너무 단순하게 만들면, \(q_\theta (z \mid x)\)와 \(p(x)\)가 같아진다고 볼 수 있고,
이는 인코더가 $x$의 정보를 $z$에 반영하지 않아 (독립) 디코더의 입장에서는 \(p_{\text{data}} (\mathbf{x})\)의 평균을 내놓게 될 뿐이다.
이로 인해 Reconstruction \(\mathbb{E}_{\mathbf{z} \sim q_\theta (z \mid x)} \left[ \log p_\phi(\mathbf{x \mid z}) \right]\)은 커지게 된다.&lt;/p&gt;

&lt;h4 id=&quot;blurry-generations-in-vaes&quot;&gt;Blurry Generations in VAEs&lt;/h4&gt;

&lt;p&gt;VAE를 실제로 훈련하다보면 Blurry하게 생성되는 경우가 많다. 처음에는 Autoencoder보다 더 안좋은 결과 같아서 당황하지만, 알고보면 당연하다.&lt;/p&gt;

&lt;p&gt;어떤 고정된 Gaussian encoder \(q_{enc} (\mathbf{z} \mid \mathbf{x})\)와 Gaussian Decoder \(p_{dec} (\mathbf{x} \mid \mathbf{z})\)가 있다고 하자. 이는 Encoder와 Decoder를 모두 생각해야하는 기존 VAE의 문제를 Decoder에만 집중하게만 해준다.
이 떄, \(p_{dec} (\mathbf{x} \mid \mathbf{z})\)은 Gussian 관점에서 다음과 같이 표현할 수 있다.&lt;/p&gt;

\[\begin{equation}
p_{dec} (\mathbf{x} \mid \mathbf{z}) = \mathcal{N}(\mathbf{x} ; \mu(\mathbf{z}), \sigma^2 \mathbf{I})
\end{equation}\]

&lt;p&gt;이러면 \(p_{dec}\)를 Gaussian으로 고정했지만 \(\mu\)는 변할 수 있다.
그래서 $\mu(\mathbf{z})$은 autoencoder의 decoder와 비슷한 decoder network에 대응된다고 하자.&lt;/p&gt;

&lt;p&gt;임의의 Encoder \(q_{enc} (\mathbf{z} \mid \mathbf{x})\)에서 ELBO를 최적화하는 것은 다음과 같은 Reconstruction Error을 최소화 하는 것과 같다.
왜냐하면 Latent KL \(\mathcal{D}_{KL} (q_\theta (z \mid x) \| p(\mathbf{z}))\)은 고정된 encoder \(q_\theta (z \mid x)\)와 고정된 prior $p(\mathbf{z})$로 인해 상수가 되기 때문이다.&lt;/p&gt;

\[\begin{equation}
\mathop{\arg\min}\limits_{\mu} \mathbb{E}_{p_{\text{data}}, q_{enc} (\mathbf{z} \mid \mathbf{x})} \left[ \| \mathbf{x} - \mathbf{\mu} (\mathbf{z}) \|^2\right]
\end{equation}\]

&lt;p&gt;이는 \(\mathbf{\mu} (\mathbf{z})\)에 대한 least square problem과 같고, 따라서 그 해는 다음과 같다.
\(\begin{equation}
\mathbf{\mu}^* (\mathbf{z}) = \mathbb{E}_{q_{enc} (\mathbf{x} \mid \mathbf{z})} [\mathbf{x}]
\end{equation}\)&lt;/p&gt;

&lt;p&gt;이 떄 중요한 것은 \(q_{enc} (\mathbf{x} \mid \mathbf{z})\)이라는 건데, 오타가 아니고 정말 \(x\)와 \(z\)의 방향이 바뀐 것이다.
이는 별도의 \(p_{dec} (\mathbf{x} \mid \mathbf{z})\)가 아닌 일종의 역함수처럼 \(q_{enc}\)의 방향을 뒤집은 것이고 다음과 같이 정의한다.&lt;/p&gt;

\[\begin{equation}
q_{enc} (\mathbf{x} \mid \mathbf{z}) = \dfrac{q_{enc} (\mathbf{z} \mid \mathbf{x}) p_{\text{data}} (\mathbf{x})}{p_{\text{prior}} (\mathbf{z})}
\end{equation}\]

&lt;p&gt;$\mathbf{\mu}^*$을 기대값의 형태로 풀어쓰고 여기에 다시 \(q_{enc} (\mathbf{x} \mid \mathbf{z})\)를 대입해서 정리해볼 수 있다.
참고로 베이즈 정리에 따른 적분식 $P(B) = \int P(B \mid A) P(A) dA$를 사용한다.&lt;/p&gt;

\[\begin{align*}
\mathbf{\mu}^* (\mathbf{z}) &amp;amp;= \mathbb{E}_{q_{enc} (\mathbf{x} \mid \mathbf{z})} [\mathbf{x}] \\
&amp;amp;= \int \mathbf{x} \cdot  q_{enc} (\mathbf{x} \mid \mathbf{z}) d\mathbf{x} \\
&amp;amp;= \int \mathbf{x} \cdot \dfrac{q_{enc} (\mathbf{z} \mid \mathbf{x}) p_{\text{data}} (\mathbf{x})}{p_{\text{prior}} (\mathbf{z})} d\mathbf{x} \\
&amp;amp;= \dfrac{1}{p_{\text{prior}} (\mathbf{z})} \int \mathbf{x} \cdot q_{enc} (\mathbf{z} \mid \mathbf{x}) p_{\text{data}} (\mathbf{x}) d\mathbf{x} \\
&amp;amp;= \dfrac{1}{\int q_{enc}(\mathbf{z} \mid \mathbf{x}) p_{\text{data}}(\mathbf{x}) d\mathbf{x}} \int \mathbf{x} \cdot q_{enc} (\mathbf{z} \mid \mathbf{x}) p_{\text{data}} (\mathbf{x}) d\mathbf{x} \\
&amp;amp;= \dfrac{1}{\mathbb{E}_{p_{\text{data}}} q_{enc} (\mathbf{z} \mid \mathbf{x})} \int \mathbf{x} \cdot q_{enc} (\mathbf{z} \mid \mathbf{x}) p_{\text{data}} (\mathbf{x}) d\mathbf{x} \\
&amp;amp;= \dfrac{1}{\mathbb{E}_{p_{\text{data}}} q_{enc} (\mathbf{z} \mid \mathbf{x})} \mathbb{E}_{p_{\text{data}}} [q_{enc} (\mathbf{z} \mid \mathbf{x})] \\
\end{align*}\]

&lt;p&gt;다시 정리하면 다음과 같이 표현된다.&lt;/p&gt;

\[\begin{equation}
\mathbf{\mu}^* (\mathbf{z}) =
\dfrac{\mathbb{E}_{p_{\text{data}}} [q_{enc} (\mathbf{z} \mid \mathbf{x}) \mathbf{x}]}{\mathbb{E}_{p_{\text{data}}} [q_{enc} (\mathbf{z} \mid \mathbf{x})]}
\end{equation}\]

&lt;p&gt;만약 서로 다른 입력 \(\mathbf{x} \neq \mathbf{x}&apos;\)이 있고, latent space에서 일부 겹친다고 가정하자.
예를 들어 \(q_{enc} (\cdot \mid \mathbf{x})\) 와 \(q_{enc} (\cdot \mid \mathbf{x&apos;})\)의 support들이 서로 겹치는 것이다.&lt;/p&gt;

&lt;p&gt;그 말은 $\mathbf{\mu}^* (\mathbf{z})$은 여러 입력을 평균내게되는데 (말 그대로 평균이니까)
이는 겹치는 구간에서는 서로 상관이 없어도 단순 평균을 낸다는 것이다. 따라서 상관없는 입력끼리 평균을 내버리면 blurry한 결과가 나오게 된다.&lt;/p&gt;

&lt;p&gt;즉,
L2 손실(Reconstruction Error)을 최소화하는 최적의 Decoder \(\mu^*(\mathbf{z})\)는
&lt;strong&gt;어떤 \(\mathbf{z}\)가 주어졌을 때, 그 \(\mathbf{z}\)를 만들었을 가능성이 있는 모든 원본 \(\mathbf{x}\)들의 ‘평균’을 출력&lt;/strong&gt;하게 된다는 것이다.&lt;/p&gt;

&lt;h2 id=&quot;variational-perspective-ddpm&quot;&gt;Variational Perspective: DDPM&lt;/h2&gt;

&lt;p&gt;기존 책에는 HVAE와 왜 HVAE로도 기존 VAE의 문제를 완전히 해결하지 못하는지에 대한 이야기가 담겨있다.
요약하면 (1) Encoder의 문제: 깊은 모델이어도 결국 Encoder는 Gaussian으로 근사하고, multi-peaked인 경우에는 이를 모두 포함하기 위해 매우 loose한 Guassian으로 근사하게 된단 점, (2) Decoder의 문제: Decoder가 너무 강력해서 $\mathbf{z}$를 무시하게 되어 controllable하지 않다.라는 문제를 지적했다.&lt;/p&gt;

&lt;p&gt;DDPM(Denoising Diffusion Probabilistic Models)은 이를 해결하기 위해 Encoder를 고정하고 Decoder만을 학습한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The forward pass (Fixed Encoder):
    &lt;ul&gt;
      &lt;li&gt;\(p(\mathbf{x}_i \mid \mathbf{x}_{i-1})\)이라는 transition kernel을 통해 데이터에 Gaussian noise를 서서히 주입하여 데이터를 왜곡한다.&lt;/li&gt;
      &lt;li&gt;결국 데이터는 isotropic Gaussian data로 전환되게 되고, 이 과정은 항상 일정하므로 고정된 Encoder라고 할 수 있다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The reverse Denoising Process (Learnables Decoder):
    &lt;ul&gt;
      &lt;li&gt;Decoder에서는 parameterized된 \(p_{\phi}(\mathbf{x}_{i_1} \mid\mathbf{x}_{i})\)을 통해 노이즈 주입을 &lt;strong&gt;reverse&lt;/strong&gt;하는 과정을 배우게 된다.&lt;/li&gt;
      &lt;li&gt;VAE에서 한번에 denoising하는 것보다는 훨씬 더 controllable하다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/02-DDPM.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a DDPM&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/02-DDPM.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a DDPM&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of a DDPM&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;the-forward-pass-fixed-encoder&quot;&gt;The forward pass (Fixed Encoder)&lt;/h3&gt;

&lt;p&gt;앞서 말했던 것처럼 forward process는 다음 그림과 같이 단계적으로 노이즈를 추가해서 심플한 prior $p_{\text{prior}} := \mathcal{N}(\mathbf{0, I})$으로 만드는 과정이다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/03-DDPM-forward.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a DDPM forward process&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/03-DDPM-forward.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a DDPM forward process&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of a DDPM forward process&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h4 id=&quot;fixed-gaussian-kernels&quot;&gt;Fixed Gaussian Kernels&lt;/h4&gt;

&lt;p&gt;여기서 &lt;strong&gt;단계적&lt;/strong&gt;이라는 말은 동적인 모델의 파라미터가 아닌 &lt;strong&gt;고정된 Gaussian Kernel&lt;/strong&gt;에 의해 지배된다.
개인적으로 DDPM을 처음 접할 때 가장 헷갈리는 notation이 $;$과 $,$의 차이인데 우선 $;$는 확률변수와 파라미터를 구분할 때 쓴다.
아래의 식은 &lt;strong&gt;\(\mathbf{x}_{i-1}\)이 주어졌을 때 \(\mathbf{x}_i\)가 나타날 확률은, $x_i$를 확률 변수로 하고 (\(\sqrt{1-\beta_i^2} \mathbf{x}_{i-1}\))를 평균으로 \((\beta_i^2 \mathbf{I})\)를 공분산으로 갖는 정규분포를 따른다&lt;/strong&gt;라는 의미를 가진다.
반면 뒤에 나오는 $,$는 확률 변수가 여러 개임을 뜻한다. 예를 들어 \(p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)\)는 \(\mathbf{x}_{i}, \mathbf{x}_0\) 두 확률변수가 주어졌을 때의 \(\mathbf{x}_{i-1}\)의 조건부 확률을 뜻한다.&lt;/p&gt;

\[\begin{align}
p(\mathbf{x}_i \mid \mathbf{x}_{i-1}) := \mathcal{N}(\mathbf{x}_i ; \sqrt{1-\beta_i^2} \mathbf{x}_{i-1}, \beta_i^2 \mathbf{I})
\end{align}\]

&lt;p&gt;조금 더 디테일하게 설명하자면, 맨 처음에는 실제 데이터 분포 $p_{\text{data}}$로부터 샘플링된 $\mathbf{x}_0$부터 시작한다.
그리고 \(\lbrace \beta_i \rbrace_{i=1}^L\)는 서서히 증가하는 noise scheudle이다.
$\beta_i \in \lbrace 0, 1 \rbrace$는 step $i$에 주입되는 Gaussian noise의 양을 결정한다.
만약 $\alpha_i := \sqrt{1-\beta^2}$라고 가정하면 다음과 같이 단순히 iterative하게 노이즈 주입하는 식인 것일 뿐이다.&lt;/p&gt;

\[\begin{align}
\mathbf{x}_i = \alpha_i \mathbf{x}_{i-1} + \beta_i \mathbf{\epsilon}_i
\end{align}\]

&lt;p&gt;이 때, $\mathbf{\epsilon}_i \sim \mathcal{N}(\mathbf{0}, \mathbf{I})$는 i.i.d.인 noise이다.&lt;/p&gt;

&lt;h4 id=&quot;perturbation-kernel-and-prior-distribution&quot;&gt;Perturbation Kernel and Prior Distribution&lt;/h4&gt;

&lt;p&gt;위 섹션은 스텝간의 kernel에 대해 설명했다면 이제 전체 스텝 즉 $\mathbf{x}_0$과 $\mathbf{x}_i$에 대해 식을 정리하면 다음과 같다.&lt;/p&gt;

\[\begin{align}
p(\mathbf{x}_i \mid \mathbf{x}_0) &amp;amp;:= \mathcal{N}(\mathbf{x}_i ; \bar{\alpha}_i \mathbf{x}_0, (1-\bar{\alpha}_i^2)\mathbf{I}) \\
\bar{\alpha}_i &amp;amp;:= \Pi_{k=1}^i \sqrt{1-\beta_k^2} = \Pi_{k=1}^i \alpha_k
\end{align}\]

&lt;p&gt;마찬가지로 위에서 iterative하게 표현한 식도 $\mathbf{x}_0$에 대해서 다음과 같이 쓸 수 있다.&lt;/p&gt;

\[\begin{align}
\mathbf{x}_i = \bar{\alpha}_i \mathbf{x}_0 + (1-\bar{\alpha}_i^2) \mathbf{\epsilon}_i, \quad \mathbf{\epsilon}_i \sim \mathcal{N}(\mathbf{0}, \mathbf{I})
\end{align}\]

&lt;p&gt;결국 마지막 step $L$로 가면 forward process는 $\mathcal{N}(\mathbf{0}, \mathbf{I})$로 수렴한다.&lt;/p&gt;

\[\begin{equation}
p_L (\mathbf{x}_L \mid \mathbf{x}_0) \longrightarrow \mathcal{N}(\mathbf{0}, \mathbf{I}) \quad \text{as} \quad L \rightarrow \infty
\end{equation}\]

&lt;p&gt;이 과정을 통해 $p_{\text{data}}$는 점차 $\mathcal{N} (\mathbf{0, I})$가 되고, $\mathbf{x}_0$에 의존하지 않는 prior가 된다.&lt;/p&gt;

\[\begin{equation}
p_{\text{prior}} := \mathcal{N}(\mathbf{0}, \mathbf{I})
\end{equation}\]

&lt;h3 id=&quot;reverse-denoising-process-learnable-decoder&quot;&gt;Reverse Denoising Process (Learnable Decoder)&lt;/h3&gt;

&lt;p&gt;Encoder는 고정된 스케줄이니 모델이라고 할 수는 없지만 실제 DDPM의 코어는 모델로 learnable한 Decoder에 있다.
다음 그림과 같이 $x_{L} \sim p_{\text{prior}}$로부터 시작해서 denoise과정을 통해 일관성 있고(coherent) 의미있는(meaningful) 데이터 분포를 도출하는 것이 Decoder의 목적이다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/04-DDPM-reverse.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a DDPM forward process&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/04-DDPM-reverse.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of a DDPM forward process&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of a DDPM forward process&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;즉 &lt;strong&gt;복잡한 분포 \(\mathbf{x}_i \sim p_i (\mathbf{x})\)를 고려할 때, 정확하게 혹은 최소한 효과적으로 reverse transition kernel \(p(\mathbf{x}_{i-1} \mid \mathbf{x}_{i})\)를 근사할 수 있는가?&lt;/strong&gt;라는 질문으로 문제를 정의할 수 있다.&lt;/p&gt;

&lt;h4 id=&quot;overview-modeling-and-training-objective&quot;&gt;Overview: Modeling and Training Objective&lt;/h4&gt;

&lt;p&gt;위에서 살펴봤듯이, 최종 목적은 unknown true reverse transition kernel \(p(\mathbf{x}_{i-1} \mid \mathbf{x}_{i})\)를 추정하는 것이다.
이를 추정하는 커널을 parameterized reverse transition kernel \(p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})\)이라고 하자.
그러면 위 문제를 다음과 같은 KL Divergence를 minimize하는 문제로 바꿀 수 있다.&lt;/p&gt;

\[\begin{equation}
\text{minimize} \quad \mathbb{E}_{p_i (\mathbf{x}_i)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right]
\end{equation}\]

&lt;p&gt;하지만 \(p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})\)를 추정하는 것은 쉽지 않다.
어떻게 보면 VAE때랑 비슷한데&lt;/p&gt;

&lt;p&gt;\(\begin{equation}
p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) = p (\mathbf{x}_{i} \mid \mathbf{x}_{i-1}) \underbrace{\dfrac{p_{i-1} (\mathbf{x}_{i-1})}{p_{i} (\mathbf{x}_{i})}}_{\text{intractable}}
\end{equation}\)
이기 때문인데, 이는 \(p_{i} (\mathbf{x}_{i})\)를 표현하는 다음 식에서 원본 데이터의 $p_{\text{data}}$의 분포는 샘플링된 데이터로부터 추정할 뿐이지 실제 분포는 일반적으로는 알 수 없기 때문이다.&lt;/p&gt;

\[\begin{equation}
p_{i} (\mathbf{x}_{i}) = \int p_{i} (\mathbf{x}_{i} \mid \mathbf{x}_0) p_{\text{data}} (\mathbf{x}_0) d \mathbf{x}_0
\end{equation}\]

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;비교 항목&lt;/th&gt;
      &lt;th&gt;VAE (Variational Autoencoder)&lt;/th&gt;
      &lt;th&gt;DDPM (Denoising Diffusion)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;알고 싶은 분포&lt;/td&gt;
      &lt;td&gt;true posterior  $p(\mathbf{z} \mid \mathbf{x})$&lt;/td&gt;
      &lt;td&gt;true reverse kernel  $p(\mathbf{x}_{i-1} \mid \mathbf{x}_i) $&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;베이즈 정리&lt;/td&gt;
      &lt;td&gt;$p(\mathbf{z} \mid \mathbf{x}) = \frac{p(\mathbf{x} \mid \mathbf{z}) p(\mathbf{z})}{p(\mathbf{x})} $&lt;/td&gt;
      &lt;td&gt;\(p(\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) = \frac{p(\mathbf{x}_{i} \mid \mathbf{x}_{i-1}) p_{i-1}(\mathbf{x}_{i-1})}{p_{i}(\mathbf{x}_{i})}\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Intractable 항&lt;/td&gt;
      &lt;td&gt;\(p(\mathbf{x}) = \int p(\mathbf{x} \mid \mathbf{z}) p(\mathbf{z}) d\mathbf{z}\)&lt;/td&gt;
      &lt;td&gt;\(p_i(\mathbf{x}_i) = \int p_i(\mathbf{x}_i \mid \mathbf{x}_0) p_{\text{data}}(\mathbf{x}_0) d\mathbf{x}_0\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;해결책 (근사)&lt;/td&gt;
      &lt;td&gt;근사 posterior:  \(q_\phi(\mathbf{z} \mid \mathbf{x})\)&lt;/td&gt;
      &lt;td&gt;근사 reverse kernel:  \(p_\phi(\mathbf{x}_{i-1} \mid \mathbf{x}_{i})\)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;overcoming-intractability-with-conditioning&quot;&gt;Overcoming Intractability with Conditioning&lt;/h4&gt;

&lt;p&gt;$p_{i} (\mathbf{x}_{i})$를 직접 계산하는건 intractable하다는 것은 알았다. 이제 DDPM의 가장 중요한 직관이 등장한다.
바로 &lt;strong&gt;Conditioning을 통해 $p_{i} (\mathbf{x}_{i})$ 대신에 \(p_{i} (\mathbf{x}_{i} \mid \mathbf{x})\)를 사용하는 것&lt;/strong&gt;이다.&lt;/p&gt;

\[\begin{equation}
p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) = p (\mathbf{x}_{i} \mid \mathbf{x}_{i-1}) \underbrace{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x})}{p (\mathbf{x}_{i} \mid \mathbf{x})}}_{\text{tractable}}
\end{equation}\]

&lt;p&gt;이것이 가능한 것은 Encoder process가 markov property라 직전 state에만 의존한다는 것이고 \(p (\mathbf{x}_{i} \mid \mathbf{x}_{i-1},\mathbf{x})=p(\mathbf{x}_i \mid \mathbf{x}_{i-1})\), 모든 관련된 distribution이 Gaussian이라 계산이 쉽다는 것이다.&lt;/p&gt;

&lt;p&gt;이에 다음과 같은 정리를 도출할 수 있다. $\phi$랑 무관한 상수 $C$가 있다고 하면,&lt;/p&gt;

\[\begin{equation*}
\begin{aligned}
&amp;amp; \mathbb{E}_{p_i (\mathbf{x}_i)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] = \\
&amp;amp; \mathbb{E}_{p_{\text{data}} (\mathbf{x})} \mathbb{E}_{p(\mathbf{x}_i \mid \mathbf{x}) }
\left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right]  + C
\end{aligned}
\end{equation*}\]

&lt;p&gt;이를 증명하려면, 기대값의 정의와 확률의 Chain rule을 사용하고, 로그 함수의 특징을 사용한다.
다만 증명의 편의를 위해 \(\mathbb{E}_{p_{\text{data}} (\mathbf{x})} \mathbb{E}_{p(\mathbf{x}_i \mid \mathbf{x}) }
\left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right]\)를 분해하는 것부터 증명한다. 어차피 상수는 다른 쪽으로 넘기면 그만이다.&lt;/p&gt;

\[\begin{align*}
p(\mathbf{x}_0, \mathbf{x_i}) &amp;amp;= p_{\text{data}} (\mathbf{x}_0) p(\mathbf{x}_i \mid \mathbf{x}_0)
\end{align*}\]

&lt;p&gt;이므로 다음과 같이 $\mathbb{E}$를 하나만 쓰는 식으로 줄일 수 있다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_{p_{\text{data}} (\mathbf{x})} \mathbb{E}_{p(\mathbf{x}_i \mid \mathbf{x}) } \left[ f(\mathbf{x}_0, \mathbf{x}_i) \right] &amp;amp;=\int p_{\text{data}} (\mathbf{x}) \left( \int p(\mathbf{x}_i \mid \mathbf{x}) f(\mathbf{x}_0, \mathbf{x}_i)  d\mathbf{x}_i \right) d \mathbf{x}_0 \\
&amp;amp;= \int \int p_{\text{data}} (\mathbf{x})  p(\mathbf{x}_i \mid \mathbf{x}) f(\mathbf{x}_0, \mathbf{x}_i)  d\mathbf{x}_i d \mathbf{x}_0 \\
&amp;amp;= \int \int p(\mathbf{x}_0, \mathbf{x_i}) p(\mathbf{x}_i \mid \mathbf{x}) f(\mathbf{x}_0, \mathbf{x}_i)  d\mathbf{x}_i d \mathbf{x}_0 \\
&amp;amp;= \mathbb{E}_{\mathbf{x}_0, \mathbf{x_i}} [ f(\mathbf{x}_0, \mathbf{x}_i) ]
\end{align*}\]

&lt;p&gt;따라서 \(\mathbb{E}_{p_{\text{data}} (\mathbf{x})} \mathbb{E}_{p(\mathbf{x}_i \mid \mathbf{x}) }
\left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right]\)에 위의 기대값 식을 사용하고, 다시 기대값의 정의를 이용하여 풀어보면 다음과 같다.&lt;/p&gt;

\[\begin{equation*}
\begin{aligned}
&amp;amp; \mathbb{E}_{p (\mathbf{x}_i, \mathbf{x}_0)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] = \\
&amp;amp; \int \int p (\mathbf{x}_0, \mathbf{x}_i) \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i},\mathbf{x}_{0}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})) d \mathbf{x}_0 d \mathbf{x}_i
\end{aligned}
\end{equation*}\]

&lt;p&gt;로 표현되고, KL Divergence의 정의를 통해 $\mathcal{D}_{KL}$는 다음과 같은 식으로 풀 수 있다.&lt;/p&gt;

\[\begin{equation*}
\begin{aligned}
&amp;amp;\mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))  = \\
&amp;amp; \int p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0) \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} d \mathbf{x}_{i-1}
\end{aligned}
\end{equation*}\]

&lt;p&gt;그러면 두 식을 합쳐서 triple integral로 표현할 수 있다.
\(\begin{equation*}
\begin{aligned}
&amp;amp;\mathbb{E}_{p (\mathbf{x}_0, \mathbf{x}_i)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] = \\
&amp;amp; \int \int \int p (\mathbf{x}_0, \mathbf{x}_i) p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0) \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} d \mathbf{x}_{i-1} d \mathbf{x}_0 d \mathbf{x}_i
\end{aligned}
\end{equation*}\)&lt;/p&gt;

&lt;p&gt;여기에 probability의 chain rule을 적용해보자.&lt;/p&gt;

\[\begin{equation*}
p(\mathbf{x}_0, \mathbf{x}_i) = p(\mathbf{x}_i) p(\mathbf{x}_0 \mid \mathbf{x}_i)
\end{equation*}\]

&lt;p&gt;그러면 위 triple integral은 다음과 같이 표현된다.&lt;/p&gt;

\[\begin{equation*}
\begin{aligned}
&amp;amp;\mathbb{E}_{p (\mathbf{x}_0, \mathbf{x}_i)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right]  \\
&amp;amp;= \int p(\mathbf{x}_i) \int p(\mathbf{x}_0 \mid \mathbf{x}_i) \int p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0) \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} d \mathbf{x}_{i-1} d \mathbf{x}_0 d \mathbf{x}_i
\end{aligned}
\end{equation*}\]

&lt;p&gt;이는 결국 또다시 기대값의 정의와 부합되므로 세 개의 기대값의 조합으로 표현할 수 있다.&lt;/p&gt;

\[\begin{equation*}
\begin{aligned}
&amp;amp;\mathbb{E}_{p (\mathbf{x}_0, \mathbf{x}_i)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] = \\
&amp;amp; \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathbb{E}_{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)} \left[ \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} \right] \right] \right]
\end{aligned}
\end{equation*}\]

&lt;p&gt;이제 안쪽 log를 다음과 같이 풀어쓸 수 있다.&lt;/p&gt;

\[\begin{equation*}
\begin{aligned}
\log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} = \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} + \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}}
\end{aligned}
\end{equation*}\]

&lt;p&gt;이 로그식을 위의 기대값식과 결합하면 다음과 같이 전개가 된다. 두번째 term이 전개되는 이유는 $\mathbf{x}_0$과 관련이 없기 때문이다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_{p (\mathbf{x}_0, \mathbf{x}_i)} &amp;amp; \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] \\
&amp;amp;= \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathbb{E}_{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)} \left[ \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)}{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} \right] \right] \right] \\
&amp;amp;+ \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathbb{E}_{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)} \left[ \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} \right] \right] \right] \\
&amp;amp;= \mathbb{E}_{p(\mathbf{x}_i)} \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathcal{D}_{KL}
(p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x}_0) \| p (\mathbf{x}_{i-1} \mid \mathbf{x}_i)) \right] \\
&amp;amp;+ \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathbb{E}_{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}, \mathbf{x}_0)} \left[ \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} \right] \right] \right] \\
&amp;amp;= \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathcal{D}_{KL}
(p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x}_0) \| p (\mathbf{x}_{i-1} \mid \mathbf{x}_i)) \right] \right] \\
&amp;amp;+ \mathbb{E}_{p(\mathbf{x}_i)}  \left[ \mathbb{E}_{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})} \left[ \log{\dfrac{p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}{p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})}} \right] \right] \\
&amp;amp;= \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathcal{D}_{KL}
(p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x}_0) \| p (\mathbf{x}_{i-1} \mid \mathbf{x}_i)) \right] \right] \\
&amp;amp;+ \mathbb{E}_{p(\mathbf{x}_i)}  \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})) \right]
\end{align*}\]

&lt;p&gt;이를 정리하면 다음과 같다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_{p (\mathbf{x}_0, \mathbf{x}_i)} &amp;amp; \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] \\
&amp;amp;= \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathcal{D}_{KL}
(p(\mathbf{x} _{i-1} \mid \mathbf{x}_i, \mathbf{x}_0) \| p (\mathbf{x}_{i-1} \mid \mathbf{x}_i)) \right] \right] \\
&amp;amp;+ \mathbb{E}_{p(\mathbf{x}_i)}  \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})) \right] \\
&amp;amp;= \mathbb{E}_{p(\mathbf{x}_i)}  \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})) \right] + C
\end{align*}\]

&lt;p&gt;여기서 $C$는 모델 파라미터 $\phi$에 의존하지 않는 항이고 단순 noise scheduling에 결정되는 항이므로 상수라는 것이 성립된다.&lt;/p&gt;

\[\begin{equation*}
C&apos; := \mathbb{E}_{p(\mathbf{x}_i)} \left[ \mathbb{E}_{p(\mathbf{x}_0 \mid \mathbf{x}_i)} \left[ \mathcal{D}_{KL}
(p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x}_0) \| p (\mathbf{x}_{i-1} \mid \mathbf{x}_i)) \right] \right]
\end{equation*}\]

&lt;p&gt;이걸 $C’$를 역으로 넘기면 Equivalence Between Marginal and Conditional KL Minimization라는 Theorem을 정의할 수 있다.
$-$는 상수니까 상관없으므로 $C = -C’$라고 하면 맨 처음 증명하고자 했던 그 식이 나온다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_{p(\mathbf{x}_i)} &amp;amp;\left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i})) \right] \\
&amp;amp;= \mathbb{E}_{p (\mathbf{x}_0, \mathbf{x}_i)} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] + C \\
&amp;amp;= \mathbb{E}_{p_{\text{data}} (\mathbf{x})} \mathbb{E}_{p (\mathbf{x}_i \mid \mathbf{x})} \left[ \mathcal{D}_{KL} (p (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_{i}))\right] + C
\end{align*}\]

&lt;p&gt;이 식은 DDPM을 관통하는 중요한 원리이다. 현재 포스트와 같은 Variational한 접근뿐만 다른 접근에서도 통찰하는 원리인 것이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;marginal distribution의 KL divergence를 minimize하는 것은 실제로는 특정 condiitional distribution을 minimize하는 것과 같다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이것을 바탕으로 다음과 같은  Reverse Conditional Transition Kerne에 대한 Lemma를 도출할 수 있다.&lt;/p&gt;

&lt;p&gt;\(p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x})\)는 다음과 같이 closed Gaussian form으로 표현된다.&lt;/p&gt;

\[\begin{align}
p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x}) = \mathcal{N} (\mathbf{x}_{i-1};\mathbf{\mu}(\mathbf{x}_i, \mathbf{x}, i ), \sigma^2 (i) \mathbf{I})
\end{align}\]

&lt;p&gt;이 때 각 $\mathbf{\mu}$와 $\sigma^2 (i)$ 는 노이즈 스케줄링을 담당하는 $\beta_i$에 의해 다음과 같이 정의된다.
$\alpha_i$와 $\bar{\alpha}_i$도 참고를 위해 다시 작성했다.&lt;/p&gt;

\[\begin{align}
\mathbf{\mu}(\mathbf{x}_i, \mathbf{x}, i ) &amp;amp;:=
\dfrac{\bar{\alpha}_{i-1} \beta^2_i}{1-\bar{\alpha}_{i}^2 } \mathbf{x}+ \dfrac{(1-\bar{\alpha}^2_{i-1}) \alpha_i}{1-\bar{\alpha}^2_{i}} \mathbf{x}_i \\
\sigma^2 (i) &amp;amp;:= \dfrac{1-\bar{\alpha}_{i-1}}{1-\bar{\alpha}_{i} } \beta^2_i \\
\alpha_i &amp;amp;= \sqrt{1-\beta^2} \\
\bar{\alpha}_i &amp;amp;= \Pi_{k=1}^i \sqrt{1-\beta_k^2} = \Pi_{k=1}^i \alpha_k
\end{align}\]

&lt;h4 id=&quot;modeling-of-reverse-transition-kernel&quot;&gt;Modeling of Reverse Transition Kernel&lt;/h4&gt;

&lt;p&gt;결국 남은 문제는 Reverse Transition Kernel \(p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_i)\)를 어떻게 모델링할 것인가가 남는다.
이전 섹션 Theorem과 Lemma을 결합하면 각 reverse transition kernel은 다음과 같이 정의될 수 있다.&lt;/p&gt;

\[\begin{align}
p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_i) := \mathcal{N}(\mathbf{x}_{i-1};\mathbf{\mu}_\phi (\mathbf{x}_i, i), \sigma^2(i)\mathbf{I})
\end{align}\]

&lt;p&gt;이 때 $\mathbf{\mu}_\phi (\cdot, i): \mathbb{R}^D \rightarrow \mathbb{R}^D$는 learnable한 mean function이고,
$\sigma^2(i) \geq 0$은 Lemma에 정의된 것처럼 positive한 양수이다.&lt;/p&gt;

&lt;p&gt;결국 loss function은 모든 step에 대해 평균을 낸 KL Divergence이다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{\text{diffusion}} (\mathbf{x}_0 ; \phi) := \sum_{i=1}^L \mathbb{E}_{p(\mathbf{x}_i \mid \mathbf{x}_0)} \left[ \mathcal{D}_{KL} (p(\mathbf{x}_{i-1} \mid \mathbf{x}_i, \mathbf{x}_0) \| p_\phi (\mathbf{x}_{i-1} \mid \mathbf{x}_i)) \right]
\end{align}\]

&lt;p&gt;그러면 Gaussian 의 정의와 위에서 parameterized된 $\mu$의 힘을 빌리면&lt;/p&gt;

\[\begin{align*}
\mathcal{L}_{\text{diffusion}} (\mathbf{x}_0 ; \phi) = \sum_{i=1}^L \dfrac{1}{\sqrt{2\sigma^2 (i)}} \| \mathbf{\mu}_\phi (\mathbf{x}_i, i) - \mathbf{\mu} (\mathbf{x}_i, \mathbf{x}_0, i)  \|^2_2 + C
\end{align*}\]

&lt;p&gt;$C$는 minimize 할 때는 필요가 없는 상수이고, 위 식을 전체 data distribution \(\mathbf{x}_0 \sim p_{\text{data}}\)에 대해 평균을 내면 다음과 같은 DDPM의 최종 loss function이 도출된다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{\text{DDPM}} (\phi) := \sum_{i=1}^L \dfrac{1}{\sqrt{2\sigma^2 (i)}} \mathbb{E}_{\mathbf{x}_0} \mathbb{E}_{p(\mathbf{x}_i \mid \mathbf{x}_0)} \left[ \| \mathbf{\mu}_\phi (\mathbf{x}_i, i) - \mathbf{\mu} (\mathbf{x}_i, \mathbf{x}_0, i)  \|^2_2 \right]
\end{align}\]

&lt;h3 id=&quot;practical-choices-of-predictions-and-loss&quot;&gt;Practical Choices of Predictions and Loss&lt;/h3&gt;

&lt;p&gt;하지만 실제로는 위 loss를 직접 사용하는 일은 없다. 왜냐하면, \(\| \mathbf{\mu}_\phi (\mathbf{x}_i, i) - \mathbf{\mu} (\mathbf{x}_i, \mathbf{x}_0, i)  \|^2_2\) 이 값이 복잡한 $\mathbf{\mu}$는 복잡하여 예측하기도 어렵고, 타임스텝에 따라 $\mu$의 중요도가 변하며 (노이즈가 적은 초반에는 $\mu$에 민감, 후반에는 noise에 의해 $\mu$가 묻힘), 앞으로 증명하겠지만 $\epsilon$이 $\mu$와 동등(reparameterization trick)하기 때문이다.&lt;/p&gt;

&lt;h4 id=&quot;epsilon-prediction&quot;&gt;$\epsilon$-prediction&lt;/h4&gt;

&lt;p&gt;DDPM forward process를 다시 떠올려 보면 noise level $i$에서 생성되는 noisy sample \(x_i \sim p(\mathbf{x}_i \mid \mathbf{x})\)는 다음과 같은 식에 의해서 생성된다.&lt;/p&gt;

\[\begin{equation}
\mathbf{x}_i = \bar{\alpha}_i \mathbf{x}_0 + \sqrt{1- \bar{\alpha}_i^2} \epsilon, \quad \mathbf{x}_0 \sim p_{\text{data}}, \quad \epsilon \sim \mathcal{N}(\mathbf{0, I})
\end{equation}\]

&lt;p&gt;이걸로 \(\mathbf{\mu}(\mathbf{x}_i, \mathbf{x}, i )\)를 \(\mathbf{\mu}(\mathbf{x}_i, \mathbf{x}_0, i )\)로 바꾼 다음 다시 전개해보자. 이 때 목표로 하는 것은 $\mathbf{x}_0$을 $\mathbf{x}_i, \mathbf{\epsilon}$으로 치환하는 것이다. 따라서 \(\mathbf{x}_i\) 식을 \(\mathbf{x}_0\)에 대해서 다시 정리한다.&lt;/p&gt;

\[\begin{align*}
\mathbf{x}_0 = \dfrac{1}{\bar{\alpha}_i} \left( \mathbf{x}_i - \sqrt{1 - \bar{\alpha}_i^2} \mathbf{\epsilon}\right)
\end{align*}\]

&lt;p&gt;이 때, \(\bar{\alpha}_i = \alpha_i \bar{\alpha}_{i-1}\) 과 \(\beta_i^2 = 1 - \alpha_i^2\)를 조합하면&lt;/p&gt;

\[\begin{align*}
\mathbf{\mu}(\mathbf{x}_i, \mathbf{x}_0, i ) &amp;amp;=
\dfrac{\bar{\alpha}_{i-1} \beta^2_i}{1-\bar{\alpha}_{i}^2 } \mathbf{x}_0 + \dfrac{(1-\bar{\alpha}^2_{i-1}) \alpha_i}{1-\bar{\alpha}^2_{i}} \mathbf{x}_i \\
\mathbf{\mu}(\mathbf{x}_i, \mathbf{\epsilon}, i ) &amp;amp;= \dfrac{\bar{\alpha}_{i-1} \beta^2_i}{1-\bar{\alpha}_{i}^2 }  \dfrac{1}{\bar{\alpha}_i}  \left( \mathbf{x}_i - \sqrt{1 - \bar{\alpha}_i^2} \mathbf{\epsilon}\right) +\dfrac{(1-\bar{\alpha}^2_{i-1}) \alpha_i}{1-\bar{\alpha}^2_{i}} \mathbf{x}_i \\
&amp;amp;= \dfrac{A}{\bar{\alpha}_i} (\mathbf{x}_i - \sqrt{1 - \bar{\alpha}_i^2} \mathbf{\epsilon}) + B \mathbf{x}_i \\
&amp;amp;= \left(\dfrac{A}{\bar{\alpha}_i}  + B\right) \mathbf{x}_i - \dfrac{A}{\bar{\alpha}_i} \sqrt{1 - \bar{\alpha}_i^2}  \mathbf{\epsilon} \\
&amp;amp;= \dfrac{1}{\alpha_i} \mathbf{x}_i - \dfrac{1 - \alpha_i^2}{\alpha_i \sqrt{1 - \bar{\alpha}_i^2}}  \mathbf{\epsilon} \\
&amp;amp;= \dfrac{1}{\alpha_i} \left(\mathbf{x}_i -  \dfrac{1 - \alpha_i^2}{\sqrt{1 - \bar{\alpha}_i^2}} \mathbf{\epsilon} \right)
\end{align*}\]

&lt;p&gt;모델을 이용하여 \(\mathbf{\mu}_\phi\)를 \(\mathbf{\epsilon}_\phi (\mathbf{x}_i, i)\)로 parameterize하면 다음과 같다.&lt;/p&gt;

\[\begin{align}
\mathbf{\mu}_\phi(\mathbf{x}_i, i ) = \dfrac{1}{\alpha_i} \left(\mathbf{x}_i -  \dfrac{1 - \alpha_i^2}{\sqrt{1 - \bar{\alpha}_i^2}} \underbrace{\mathbf{\epsilon}_\phi (\mathbf{x}_i, i)}_{\epsilon\text{-prediction}} \right)
\end{align}\]

&lt;p&gt;이를 원래 Loss인 \(\mathcal{L}_{\text{DDPM}} (\phi)\)에 넣으면 다음과 같이 reparameterization이 일어난다.&lt;/p&gt;

\[\begin{align}
\| \mathbf{\mu}_\phi (\mathbf{x}_i, i) - \mathbf{\mu} (\mathbf{x}_i, \mathbf{x}_0, i)  \|^2_2 &amp;amp;\propto
\| \mathbf{\epsilon}_\phi (\mathbf{x}_i, i) - \mathbf{\epsilon} \|^2_2 \\
\| \mathbf{\mu}_\phi (\mathbf{x}_i, i) - \mathbf{\mu} (\mathbf{x}_i, \mathbf{x}_0, i)  \|^2_2 &amp;amp;= C_i
\| \mathbf{\epsilon}_\phi (\mathbf{x}_i, i) - \mathbf{\epsilon} \|^2_2 \\
\end{align}\]

&lt;p&gt;참고로 \(\propto\)를 \(=\)로 바꾸는 과정에서 $i$에 의존하는 weight $C_i$가 들어간다.&lt;/p&gt;

&lt;h4 id=&quot;epsilon-prediction-is-a-noise-detective&quot;&gt;$\epsilon$-prediction is a noise detective&lt;/h4&gt;

&lt;p&gt;이렇게 DDPM은 노이즈 탐지기(noise detective)의 역할을 수행한다.
복잡한 $\mathbf{\mu}_i$대신에 $\mathbf{\epsilon}_i$을 예측하면서 특정 스텝 $i$에 들어가는 노이즈의 분포를 예측하는 것이다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;noisy한 이미지를 보고&lt;/li&gt;
  &lt;li&gt;“이 이미지에 어떤 노이즈가 섞여있나?”를 분석하여&lt;/li&gt;
  &lt;li&gt;그 노이즈를 정확히 복원 (모델의 output)&lt;/li&gt;
  &lt;li&gt;그걸 빼서 $x_0$에 가까운 값을 얻음&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;과 같은 작용을 한다. 모델의 출력은 noise고 해당 step에서 noise만 뺀 뒤 $\mu$를 계산하고 posterior mean \(\mathbf{\mu}(\mathbf{x}_i, \mathbf{\epsilon}_i , i)\)을 계산한 다음 \(x_{i-1}\)을 샘플링 하는 것이다.&lt;/p&gt;

&lt;p&gt;이 때 모델은 UNet이나 Transformer같은 백본 모델을 써서 입력 이미지의 noisy한 feature를 분석해
noise component(\(\epsilon\))를 예측하는 map으로 반환한다.&lt;/p&gt;

&lt;h4 id=&quot;simplified-loss-with-epsilon-prediction&quot;&gt;Simplified Loss with $\epsilon$-prediction&lt;/h4&gt;

&lt;p&gt;$\epsilon$-prediction에서 \(\mathbf{\mu}\)와의 연결을 위해 $C_i$라는 $i$에 dependent한 weight를 도입했는데,
실제로는 이 weight가 없어도 된다는 것이 밝혀졌다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}_{\text{simple}}(\phi)
:= \mathbb{E}_{i}\,
   \mathbb{E}_{\mathbf{x} \sim p_{\text{data}}(\mathbf{x})}\,
   \mathbb{E}_{\epsilon \sim \mathcal{N}(0, \mathbf{I})}
   \left[ \left\| \epsilon_{\phi}(\mathbf{x}_i, i) - \epsilon \right\|_2^2 \right]
\end{align}\]

&lt;p&gt;왜냐하면 \(\mathcal{L}_{\text{simple}}(\phi)\)나 \(\mathcal{L}_{\text{DDPM}}(\phi)\)이나 결국 동일한 문제를 푸는 least square problem이기 때문에 동일한 optimal solution \(\epsilon^* (x_i, i)\)를 가지기 때문이다.&lt;/p&gt;

&lt;p&gt;$\epsilon$-prediction 모델은 $\mathbf{x}_i$가 주어졌을 때, forward diffusion 과정에서 실제로 들어간 노이즈 $\mathbf{\epsilon}$의 조건부 기대값을 출력한다.&lt;/p&gt;

\[\begin{equation}
\epsilon^* (x_i, i) = \mathbb{E} [\mathbf{\epsilon} \mid \mathbf{x}_i ]
\end{equation}\]

&lt;p&gt;\(\mathbf{x}_i\)는&lt;/p&gt;

\[\begin{equation}
\mathbf{x}_i = \bar{\alpha}_i \mathbf{x}_0 + \sqrt{1-\bar{\alpha}^2_i } \mathbf{\epsilon}
\end{equation}\]

&lt;p&gt;이렇게 $\mathbf{x}_0$와 $\epsilon$이 섞여서 구성되어 있다.&lt;/p&gt;

&lt;p&gt;우리의 입력은 $\mathbf{x}_i$이고, $\mathbf{\epsilon}$은 target이지만 훈련할때는 알고있다. 일종의 $\hat{y}$같은 것이기 때문이다.&lt;/p&gt;

&lt;p&gt;즉 기존 딥러닝 모델과 마찬가지로 다음 문제를 푸는 것이 주 목적이다. \(\min_\phi \mathbb{E} \| \hat{y}(\phi) - y \|^2\)와 다를 바가 없는 식이다.&lt;/p&gt;

\[\begin{equation}
\min_\phi \mathbb{E} \| \mathbf{\epsilon}_\phi (\mathbf{x}_i, i) - \mathbf{\epsilon} \|^2
\end{equation}\]

&lt;p&gt;L2 regression의 기본 성질에 따라, 모델은 $\mathbf{\epsilon}$자체를 학습하는 것이 아니라 $\mathbf{x}_i$를 보고 가능한 $\mathbf{\epsilon}$들의 평균(조건부 기대값)을 추정한다.&lt;/p&gt;

&lt;p&gt;알고보니까 \(\| \mathbf{\mu}_\phi (\mathbf{x}_i, i) - \mathbf{\mu} \|^2\) 이거나 \(w_i \| \mathbf{\epsilon}_\phi (\mathbf{x}_i, i) - \mathbf{\epsilon} \|^2\)이거나 \(\| \mathbf{\epsilon}_\phi (\mathbf{x}_i, i) - \mathbf{\epsilon} \|^2\) 결국 \(\epsilon^* (x_i, i)\)를 찾는 문제라는 것이 증명되었고, 그러면 제일 간단한 \(\| \mathbf{\epsilon}_\phi (\mathbf{x}_i, i) - \mathbf{\epsilon} \|^2\)를 쓰는 것이 당연한 것이다.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;책에는 $x$-prediction이나 Sampling에 관한 더 재밌는 얘기가 많지만 이정도로 충분한 것같다.
결국 VAE는 $z$로부터 한번에 $x$를 생성하려고 했으나, $x$가 multi-peak분포라면 모두를 포함하기 위해 더 넓은 Gaussian이 되어야 하고, average효과가 나오게 된다. 이로 인해 blurry한 이미지 생성이 발생한다.&lt;/p&gt;

&lt;p&gt;그러나, DDPM은 이걸 refine하는 과정을 추가하여 high to low방식으로 noise를 추가하여 초반에는 global한 structure를 후반에는 detail을 추가하는 방식으로 해결하였다. 그러나 이 과정은 iterative하기 때문에 필연적으로 느린 생성 속도를 유발하게 된다.
요즘은 이 생성속도를 해결하기 위한 수치해석적 방법을 결합한 생성방법이 존재하며, 다른 챕터에서 그것을 다루고 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/05-DDPM-sampling.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of DDPM sampling with clean prediction&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-11-02-VAE-to-DDPMs/05-DDPM-sampling.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://www.arxiv.org/abs/2510.21890&amp;quot;&amp;gt;
Illustration of DDPM sampling with clean prediction&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://www.arxiv.org/abs/2510.21890&quot;&gt;
Illustration of DDPM sampling with clean prediction&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;
&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;lai2025principles&quot;&gt;[1]C.-H. Lai, Y. Song, D. Kim, Y. Mitsufuji, and S. Ermon, “The Principles of Diffusion Models,” &lt;i&gt;arXiv preprint arXiv:2510.21890&lt;/i&gt;, 2025.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;dieleman2022diffusion&quot;&gt;[2]S. Dieleman, “Diffusion models are autoencoders.” 2022. Available at: https://benanne.github.io/2022/01/31/diffusion.html&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Preventing Overfitting</title>
   <link href="https://blog.liam.kim/posts/2025/10/Preventing-Overfitting-With-Norms-and-BN/"/>
   <updated>2025-10-08T02:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2025/10/Preventing-Overfitting-With-Norms-and-BN</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;머신러닝의 궁극적인 목적은 무엇일까? 그것은 일반화(generalization)이다. 훈련 데이터의 학습을 통해 시그널(signal) 혹은 패턴(pattern)을 학습해, 처음 보는 데이터에 대해서도 정확한 예측을 해내는 것을 요구한다.&lt;/p&gt;

&lt;p&gt;하지만 모델은 종종 엉뚱한 길로 빠진다. 진짜 패턴을 넘어 데이터에 섞인 ‘노이즈(Noise)’까지 통째로 &lt;strong&gt;암기(Memorization)&lt;/strong&gt;해버리는 경우가 종종 있다. 이처럼 훈련 데이터는 완벽하게 외웠지만 실전에서는 성능이 하락하는 현상을 &lt;strong&gt;과적합(Overfitting)&lt;/strong&gt;이라 부른다.&lt;/p&gt;

&lt;p&gt;최근 몇 년간 Double descent, grokking, memorization vs. generalization까지 이어지는 논문들의 흐름에서 기존 전통적인 머신러닝 이론에서의 오버피팅이라는 단어는 새롭게 재정의되고 있다. 오버피팅이 단순한 훈련/테스트 성능 곡선을 넘어, ‘암기’와 ‘일반화’ 사이의 치열한 싸움임을 보여주고 있다.&lt;/p&gt;

&lt;p&gt;과적합의 근본적인 원인은 모델 복잡도 제어 실패에 있다. 모델의 복잡도를 적절히 제어하면, 모델은 ‘노이즈’처럼 사소하고 특이한 정보는 무시하고, 데이터 전체를 관통하는 중요하고 반복적인 ‘패턴’에만 집중하게 되어 일반화에 성공한다. 그러나, 모델이 너무 복잡하면(자유도가 높으면) 노이즈까지 암기해버리고, 이는 곧 일반화의 실패로 이어진다.&lt;/p&gt;

&lt;p&gt;따라서 이 글에서는 &lt;strong&gt;일반화(generalization)&lt;/strong&gt;라는 최종 목표를 달성하기 위해, 모델 복잡도를 측정하는 수학적 도구인 Norm부터 시작하여, 이를 제어하는 정규화(Regularization) 기법들을 거쳐, 궁극적으로 일반화 성능을 어떻게 향상시킬 수 있는지에 대해 설명하고자 한다.&lt;/p&gt;

&lt;h2 id=&quot;generalization-error&quot;&gt;Generalization Error&lt;/h2&gt;

&lt;p&gt;우선 첫번쨰 해야할 질문은 “기계가 데이터를 통해 학습하는 것이 과연 가능한가?”이다.
한정된 &lt;strong&gt;훈련 데이터셋(sample)&lt;/strong&gt;만을 가지고 있을 때,
여기서 얻은 &lt;strong&gt;훈련 오차 ($E_{in}$)&lt;/strong&gt;가 &lt;strong&gt;일반화 오차 ($E_out$)&lt;/strong&gt;와 비슷할 것이라고 어떻게 장담할 수 있을까?&lt;/p&gt;

&lt;h3 id=&quot;learning-feasibility&quot;&gt;Learning Feasibility&lt;/h3&gt;

&lt;p&gt;위 질문은 다음과 같이 2개의 문제로 분류할 수 있다. 어떤 모델 혹은 hypothesis $g$가 있다고 하자.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;일반화 오차를 훈련오차와 가깝다고 어떻게 확신할 수 있을까? (일반화 문제) ($E_{out}(g) \approx E_{in}(g)$)&lt;/li&gt;
  &lt;li&gt;훈련 오차를 충분히 작게만들 수 있을까? (최적화 문제) $E_{in}(g) \approx 0$&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;우선 2.는  우리에게 주어진 모델 후보들 중에서 실제로 훈련 데이터를 가장 잘 설명하는 모델을 찾아낼 수 있느냐의 문제이고
답은 현재 가지고 있는 여러 모델들을 학습시키는 방법으로 훈련하면 된다이다.
이건 &lt;strong&gt;최적화(Optimization)문제&lt;/strong&gt;라고 할 수 있다.
Ordinary Least Squares를 푸는 방법부터 시작해서 딥러닝에서 쓰는 Gradient descent같은 방법이 그 예이다.
하지만 모든 모델이 다 작동하는건 아니다. 어떤 모델은 훈련 오차를 작게 만드는데 실패하기도 한다. 즉, 이건 보장할 수 없다.&lt;/p&gt;

&lt;p&gt;1.은 우리가 $E_{in}(g)$를 얼마나 신뢰하며, 실제($E_{out}(g)$)에서도 비슷할 것이라고 말할 수 있는가?에 대한 문제이다.
이에 대한 답은 호프딩 부등식(Hoeffding’s inequality)이다.&lt;/p&gt;

&lt;h3 id=&quot;hoeffdings-inequality&quot;&gt;Hoeffding’s inequality&lt;/h3&gt;

&lt;p&gt;최종 목표 hypothesis $g$에 대한 $E_{out}(g) \approx 0$은 확인할 방법은 없다.
실제 target function $f$는 모를 것이다. 그러나 $E_{in}(g)$는 알 수 있다.
이걸 호프딩의 부등식(Hoeffding’s inequality)을 통해 교환할 수 있다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;최종 목표: $E_{out}$을 낮추는 것.&lt;/li&gt;
  &lt;li&gt;호프딩의 부등식: $E_{out}$은 $E_{in}$과 비슷할 확률이 높다.&lt;/li&gt;
  &lt;li&gt;실행 계획 (교환): 그러므로 우리가 할 수 있는 $E_{in}$을 낮추는 데 집중하자! 그러면 높은 확률로 $E_{out}$도 낮아질 것이다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;호프딩의 부등식은 확률론에 기반하여 &lt;strong&gt;“샘플의 평균은 실제 전체의 평균과 비슷할 확률이 높다”&lt;/strong&gt;는 것을 수학적으로 보장한다.
충분한 양의 데이터를 샘플링했다면, 훈련 오차($E_{in}(g)$)가 일반화 오차($E_{out}(g)$)와 크게 다르지 않을 것이라 말해준다.&lt;/p&gt;

\[\begin{equation}
\mathbb{P} ( | E_{in}(g) - E_{out}(g)| &amp;gt; \epsilon  ) \leq 2M e^{-2\epsilon^2 N}
\end{equation}\]

&lt;p&gt;각각 term의 의미는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;$E_{in}(g)$: 훈련 데이터셋에서의 오차 (in-sample error)&lt;/li&gt;
  &lt;li&gt;$E_{out}(g)$: 전체 데이터 분포에 대한 실제 오차 (out-of-sample error)&lt;/li&gt;
  &lt;li&gt;$\epsilon$: 허용 오차 한계 (tolerance)&lt;/li&gt;
  &lt;li&gt;$M$: 가설 집합(Hypothesis set, $\mathcal{H}$) 안에 있는 가설(모델)의 개수&lt;/li&gt;
  &lt;li&gt;$N$: 훈련 데이터 개수&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hoeffding’s inequality를 통해 두가지 사실을 알 수 있다.&lt;/p&gt;

&lt;p&gt;첫 번쨰는 일반화 문제(1)에서는 실제 target function $f$는 전혀 중요하지 않다는 것이다. $f$는 무엇인지 절대 알 수 없고 통제할 수 있는 대상도 아니라는 것이다.
다만, $f$의 문제는 최적화 문제(2)에서는 문제가 될 수 있다. 복잡한 $f$ 일수록, 학습하기가 힘들어지기 때문이다.
이는 &lt;em&gt;Hoeffding’s inequality는 최적화 문제(2)에 답을 줄 수가 없다&lt;/em&gt;라는 사실과 일맥상통한다.&lt;/p&gt;

&lt;p&gt;두 번째 사실은 가설 집합 ($\mathcal{H}$)의 복잡도는 다음과 같은 trade-off를 던져준다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;일반화(Generalization, (1)) 관점:
    &lt;ul&gt;
      &lt;li&gt;$\mathcal{H}$는 단순할수록 좋다.&lt;/li&gt;
      &lt;li&gt;이는 $M$이 작을 수록 hoeffding’s inequality의 우변이 작아지기 때문에 말이 된다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;최적화(Optimization, (2)) 관점:
    &lt;ul&gt;
      &lt;li&gt;$\mathcal{H}$는 복잡할수록 좋다.&lt;/li&gt;
      &lt;li&gt;이는 $g$의 후보군을 늘려줘서 더 적은 오차를 생성할 $g$를 찾아낼 확률을 높아내기에 좋다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;여기서의 $M$ 즉 Complexity of $\mathcal{H}$이 모델 복잡도이며, 이 글에서 주요 주제로 다룰 주제이다.&lt;/p&gt;

&lt;h3 id=&quot;generalization-error-bound&quot;&gt;Generalization Error Bound&lt;/h3&gt;

&lt;p&gt;Hoeffding’s inequality식을 역으로 풀어서 오차경계 즉, Generalization Error Bound를 구할 수 있다.
즉, 신뢰도(confidence)를 얻으려면, 감수해야 할 오차의 한계($\epsilon$)가 얼마인가?에 대한 답이라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;이는 가장 피하고 싶은 상황 즉, 훈련 오차($E_{in}(g)$)와 실제 오차($E_{out}(g)$)의 차이가 클 확률을 Hoeffding’s inequality를 통해
얼마나 작은지 (bounded) 제한하여 알려주는 것이다.&lt;/p&gt;

&lt;p&gt;우선 도입해야할 것은 신뢰도 $\delta$이다. $\delta$는 감수할 수 있는 위험의 크기이다.
$\delta=0.05$라고 하면 5%의 위험을 감수하고, 95%의 신뢰도를 원한다는 의미인 것이다.&lt;/p&gt;

\[P(\textrm{Bad Event}) \leq \delta\]

&lt;p&gt;그리고 $\delta$로부터 $\epsilon$은 다음과 같이 구할 수 있다.&lt;/p&gt;

\[\begin{align}
\mathbb{P} ( | E_{in}(g) - E_{out}(g)| &amp;gt; \epsilon  ) &amp;amp;\leq 2M e^{-2\epsilon^2 N} \\
\mathbb{P} ( | E_{in}(g) - E_{out}(g)| &amp;gt; \epsilon  ) &amp;amp;\leq \delta \\
\delta &amp;amp;:= 2M e^{-2\epsilon^2 N} \\
\dfrac{\delta}{2M} &amp;amp;= e^{-2\epsilon^2 N} \\
\ln{\left(\dfrac{\delta}{2M}\right)} &amp;amp;= -2 \epsilon^2 N \\
\ln{\left(\dfrac{2M}{\delta}\right)} &amp;amp;= 2 \epsilon^2 N \\
\epsilon^2 &amp;amp;= \ln{\left(\dfrac{2M}{\delta}\right)} \dfrac{1}{2N} \\
\epsilon &amp;amp;= \sqrt{\dfrac{1}{2N} \ln{\left(\dfrac{2M}{\delta}\right)} } \\
\end{align}\]

&lt;p&gt;이렇게 구한 $\epsilon$으로 $E_{in}(g)$와 $E_{out}(g)$을 넣으면 $1-\delta$의 확률로 다음을 만족함을 알 수 있다.&lt;/p&gt;

\[\begin{equation}
E_{out}(g) \leq E_{in}(g) + \sqrt{\dfrac{1}{2N} \ln{\left(\dfrac{2M}{\delta}\right)} }
\end{equation}\]

&lt;p&gt;즉 $1-2M e^{-2\epsilon^2 N}$의 확률로 우리가 원하는 훈련 오차와 실제오차가 작을 확률인 $| E_{in}(g) - E_{out}(g)| \leq \epsilon$를 만족한다는 것이고,
이는 $E_{out}(g) \leq E_{in}(g) + \epsilon$을 만족하며, 이 때 $\epsilon = \sqrt{\dfrac{1}{2N} \ln{\left(\dfrac{2M}{\delta}\right)} }$을 만족한다는 것을 알 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;effective-number-of-hypothesis&quot;&gt;Effective Number of Hypothesis&lt;/h3&gt;

&lt;p&gt;문제점은 가설공간의 크기 $M$은 무한(infinite)이 될 수 있다는 점이다. 이러면 $\epsilon$이 아무 의미가 없어진다.
하지만 실제로 모든 $M$개의 가설이 필요한 것이 아니다. 실제로 유효한 (effective) 가설의 개수만 세면 된다.
이를 위해 성장 함수(growth function)을 도입하여 $M$ 대신 $m_{\mathcal{H}} (N)$으로 대체한다.&lt;/p&gt;

&lt;p&gt;growth function을 도입한 이유는 실제로 M개가 있다고 해도 그 중에서 중복도 많을 것이고, 실제 유효한 가설은 데이터의 개수 $N$에 영향을 받을 것이라는 가정이 담긴 함수이다.&lt;/p&gt;

&lt;p&gt;정리하면 가설 공간(Hypothesis Set) $\mathcal{H}$에 대해서 다음과 같이 growth function $m_{\mathcal{K}} (N)$을 정의할 수 있다.
이 때 $| \dot |$은 set의 cardinality를 의미하며, 집합의 원소의 개수를 의미하는 기호이다.&lt;/p&gt;

\[\begin{equation}
m_{\mathcal{H}}(N) = \max_{x_1, \cdots, x_N \in \mathcal{X}} | \mathcal{H}(x_1, \cdots, x_N) |
\end{equation}\]

&lt;p&gt;$M$과 $m_{\mathcal{H}} (N)$의 차이는 $\mathcal{X}$의 전체를 보느냐 아니면, $N$ points에 한정해서 보느냐의 차이이다.&lt;/p&gt;

&lt;p&gt;Binary classification에서는 가설공간이 가질 수 있는 값은 $\lbrace -1, +1 \rbrace$ 이렇게 두가지 뿐이다.
&lt;a class=&quot;citation&quot; href=&quot;#abu2012learning&quot;&gt;[1]&lt;/a&gt; 에서는 이를 dichotomics(이분법)이라고 표현한다.
그러면 $N$개의 data points에 대해서 총 $2^N$개의 경우의 수가 생긴다. 따라서 dichotomics에서는 $m_{\mathcal{H}}(N)$은 다음과 같이 bound된다.&lt;/p&gt;

\[m_{\mathcal{H}}(N) \leq 2^N\]

&lt;p&gt;물론 $2^N$도 작은 수는 아니겠지만, 무한대($\infty$)보다는 유한한 값이므로 개선된 결과이다.
즉 무한한 $M$을 제한하기 위해, 가설 집합의 복잡도를 측정하는 방식을 &lt;em&gt;가설의 총개수&lt;/em&gt;에서 &lt;em&gt;$N$개의 데이터에 대한 표현력&lt;/em&gt;으로 바꾸는 방법이다.&lt;/p&gt;

&lt;p&gt;하지만 지수적인 것을 더 줄일 수는 없을까?&lt;/p&gt;

&lt;h3 id=&quot;break-point&quot;&gt;Break Point&lt;/h3&gt;

&lt;p&gt;실제로는 모든 경우의 수를 다 고려할 필요는 없다. 왜냐하면 linear classifier라고 가정했을 때 표현이 불가능한 조합도 있기 때문이다.&lt;/p&gt;

&lt;p&gt;예를 들어 $m_{\mathcal{H}}(3)$은 다음 두 경우 때문에 $2^3=8$이 아닌 6이 된다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-Preventing-Overfitting-With-Norms-and-BN/01-MH(3).png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://web.cs.ucla.edu/~chohsieh/teaching/CS260_Winter2019/lecture7.pdf&amp;quot;&amp;gt;
mH(3)&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-Preventing-Overfitting-With-Norms-and-BN/01-MH(3).png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://web.cs.ucla.edu/~chohsieh/teaching/CS260_Winter2019/lecture7.pdf&amp;quot;&amp;gt;
mH(3)&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://web.cs.ucla.edu/~chohsieh/teaching/CS260_Winter2019/lecture7.pdf&quot;&gt;
mH(3)&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;$m_{\mathcal{H}}(4)$ 또한 다음 두 경우 때문에 $2^4=16$이 아닌 14가 된다.&lt;/p&gt;
&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-Preventing-Overfitting-With-Norms-and-BN/02-MH(4).png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://web.cs.ucla.edu/~chohsieh/teaching/CS260_Winter2019/lecture7.pdf&amp;quot;&amp;gt;
mH(4)&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-Preventing-Overfitting-With-Norms-and-BN/02-MH(4).png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://web.cs.ucla.edu/~chohsieh/teaching/CS260_Winter2019/lecture7.pdf&amp;quot;&amp;gt;
mH(4)&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://web.cs.ucla.edu/~chohsieh/teaching/CS260_Winter2019/lecture7.pdf&quot;&gt;
mH(4)&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;이 때 break point $k$는 다음과 같이 정의할 수 있다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If no data set of size $k$ can be shattered by $\mathcal{H}$, then $k$ is said to be a break point for $\mathcal{H}$&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;여기서 “shattered”라는 말이 이해하기가 쉽지 않았는데, 한글로는&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;어떻게 $k$개의 데이터 포인트를 가져와 배치하더라도 가설 집합 $\mathcal{H}$가 모든 가능한 분류 패턴($2^k$개)을 만들어낼 수 없다면, 이 $k$를 $\mathcal{H}$의 break point(브레이크 포인트)라고 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;그리고 수학적인 증명은 생략하겠지만, $k$가 존재한다면, $m_{\mathcal{H}}(k) &amp;lt; 2^k$가 되고&lt;/p&gt;

\[\begin{equation}
m_{\mathcal{H}}(N) \leq \sum_{i=0}^{k-1} \begin{pmatrix} N \\ i \end{pmatrix}
\end{equation}\]

&lt;p&gt;가 되고, 이는 $N$에 대한 $k-1$차 다항식(polynomial)이다. 지수함수 $2^N$이 다항함수로 줄어든 것이다!&lt;/p&gt;

&lt;p&gt;위의 그림을 다시 살펴보면, 3개의 점이 일직선(colinear)상에 있으면 (+1, -1, +1) 혹은 (-1, +1, -1) 조합은 분류할 수 없는 것이다. 그러나, 삼각형 모향으로 배치하면 3개의 점으로는 모든 linear classifier가 동작하게 된다. 즉 3은 break point가 될 수 없다.&lt;/p&gt;

&lt;p&gt;그러나 4개의 포인트를 가정해보자. 4개의 포인트는 위 그림처럼 XOR패턴이 아니더라도 어떤방식으로든 저 데이터셋에서는 모델로 분류할 수 있는 모든 패턴을 만들 수 없다. 이런 경우 break point $k=4$가 된다.&lt;/p&gt;

&lt;p&gt;단 하나의 배치에서라도 shatter(주어진 위치에 있는 포인트들에 대해 모델이 모든 &lt;strong&gt;분류 패턴&lt;/strong&gt;을 만들기)에 실패하는 것만으로는 부족하고, 가능한 모든 배치에서 shatter가 불가능해야 break point를 정의할 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;vc-dimension&quot;&gt;VC Dimension&lt;/h3&gt;

&lt;p&gt;VC Dimension은 The Vapnik-Chervonenkis dimension의 줄임말이며, $d_{vc}(\mathcal{H})$이라고 한다.
$d_{vc}$는 Break point $k$와 $k = d_{vc} + 1$의 관계를 가진다.
break point $k$는 shatter가 실패하는 데이터 포인트 수를 나타냈다면, $d_{vc}$는 모델이 다룰 수 있는 최대의 데이터 포인트를 나타낸다.&lt;/p&gt;

&lt;p&gt;VC dimension은 결국 모델이 얼마나 다양한 데이터를 ‘깨뜨릴(shatter)’ 수 있는지에 대한 조합론적(combinatorial) 척도라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;VC dimension은 모델이 어디까지 가능한가?를 나타내주는 단위이며, 모델이 가진 &lt;strong&gt;자유도(degrees of freedom)&lt;/strong&gt;나 &lt;strong&gt;표현력(expressive power)&lt;/strong&gt;을 직접적으로 나타내기 때문에 break point보다 더 선호된다.&lt;/p&gt;

&lt;h3 id=&quot;vc-generalization-bound&quot;&gt;VC Generalization Bound&lt;/h3&gt;
&lt;p&gt;이를 이용하여 $m_{\mathcal{H}}(N)$를 다시 작성하면 다음과 같고&lt;/p&gt;

\[\begin{equation}
m_{\mathcal{H}}(N) \leq \sum_{i=0}^{d_{vc}} \begin{pmatrix} N \\ i \end{pmatrix}
\end{equation}\]

&lt;p&gt;귀납법을 통해 더 유용하게 다음과 같이 표현된다.&lt;/p&gt;

\[\begin{equation}
m_{\mathcal{H}}(N) \leq N^{d_{vc}} + 1
\end{equation}\]

&lt;p&gt;이 VC dimension은 기존 Error bound의 문제를 획기적으로 해결할 수 있다.
원래의 이 식을&lt;/p&gt;

\[\begin{equation}
E_{out}(g) \leq E_{in}(g) + \sqrt{\dfrac{1}{2N} \ln{\left(\dfrac{2M}{\delta}\right)} }
\end{equation}\]

&lt;p&gt;아래와 같이 만들고, $\ln$과 $1/N$덕분에 $N$이 크다면 매우 작은 값으로 수렴하게 된다. 따라서 $E_{out}(g) \approx E_{in}(g)$가 가능해지는 것이다.
\(\begin{equation}
E_{out}(g) \leq E_{in}(g) + \sqrt{\dfrac{1}{2N} \ln{\left(\dfrac{2 m_{\mathcal{H}}(2N)}{\delta}\right)} }
\end{equation}\)&lt;/p&gt;

&lt;p&gt;근데 왜 $m_{\mathcal{H}}(N)$이 아니라 $m_{\mathcal{H}}(2N)$인 것인가?&lt;/p&gt;

&lt;p&gt;그것은 $E_{out}(g)$은 데이터셋이 무한이기 때문에 비교하기가 쉽지 않기 떄문이다. 그래서 $E_{in}(g)$의 데이터셋을 $\mathcal{D}$라고 하면, 동일한 분포의 같은 크기의 데이터셋인 $\mathcal{D’}$을 만들고 $E_{in}(g)$ in $\mathcal{D}$와 $E_{in}(g)$ in $\mathcal{D’}$를 비교하는 문제로 바꿔서 해결하고자 한다.
따라서 두 데이터셋 $\mathcal{D}$와 $\mathcal{D}’$ 각각의 크기인 $N$을 합쳐서 $2N$에 대하여 growth function을 $m_{\mathcal{H}}(2N)$로 사용한다.&lt;/p&gt;

&lt;p&gt;여기서 중요한 가정은 &lt;strong&gt;동일한 분포의 데이터셋&lt;/strong&gt;이라는 것이다. train set과 test set이 동일한 분포가 아니면 $E_{out}(g) \approx E_{in}(g)$은 성립될 수 없다는 머신러닝의 기본적인 가정이 여기에서 나오는 것이다.&lt;/p&gt;

&lt;p&gt;최종적으로는 VC Generalization Bound는 일부 상수가 조정되어 다음과 같이 표현된다.&lt;/p&gt;

\[\begin{equation}
E_{out}(g) \leq E_{in}(g) + \sqrt{\dfrac{8}{N} \ln{\dfrac{4m_{\mathcal{H}}(2N)}{\delta}} }
\end{equation}\]

&lt;p&gt;그리고 이를 VC dimension $d_{vc}$를 포함하면 다음과 같이 된다.&lt;/p&gt;

\[\begin{equation}
E_{out}(g) \leq E_{in}(g) + \sqrt{\dfrac{8}{N} \ln{\dfrac{4 ((2N)^{d_{vc}} + 1) }{\delta}} }
\end{equation}\]

&lt;h2 id=&quot;rademacher-complexity&quot;&gt;Rademacher Complexity&lt;/h2&gt;

&lt;h3 id=&quot;lessons-from-vc-dimension&quot;&gt;Lessons from VC dimension&lt;/h3&gt;

&lt;p&gt;지금까지 모델의 복잡도에 대해 알아봤다. Generalization Bound를 다시한번 간단하게 표현하면 다음과 같다.&lt;/p&gt;

&lt;p&gt;\(\begin{equation}
\textrm{Test error} \leq \textrm{Train error} + \mathcal{O}(\textrm{Model Complexity})
\end{equation}\)
우리의 목표인 Train error와 Test error의 격차를 줄이려면 Model Complexity를 최대한 억제해야함을 알 수 있었고, 이를 VC dimension을 통해 컨트롤할 수 있음을 알았다.&lt;/p&gt;

&lt;p&gt;VC dimension은 이론적으로는 훌륭하다. 그러나, 현실적으로 복잡한 모델의 complexity를 vc dimension을 통해 구할 수 없다.
왜냐하면 VC dimension은 단순한 binary classificatiion을 기준으로 계산한 것이기 떄문이다.
SVM만 되어도 VC dimension을 구하기가 매우 어려워진다.&lt;/p&gt;

&lt;p&gt;또한 VC dimension은 worst case인 경우를 가정하기 때문에  모든 데이터 포인트를 생각해서 계산해야한다.
그래서 데이터가 간단한 분포를 따르더라도 VC dimension은 변하지 않는다. VC dimension은 데이터의 ‘분포’나 ‘배치’와는 무관하게 최악의 경우를 가정하기 때문이다.&lt;/p&gt;

&lt;p&gt;따라서 데이터 분포를 고려한 새로운 모델 복잡도가 필요한 시점이다.&lt;/p&gt;

&lt;h3 id=&quot;what-is-rademacher-complexity&quot;&gt;What is Rademacher Complexity?&lt;/h3&gt;

&lt;p&gt;이렇게 등장하는것이 Rademacher Complexity이다.
Rademacher Complexity는 &lt;em&gt;만약 어떤 모델이 완전한 무작위 노이즈(random noise)에도 잘 들어맞는다면, 그 모델은 매우 복잡하고 과적합될 위험이 높다&lt;/em&gt; 라는 가정에서 시작한다.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#mohri2018foundations&quot;&gt;[2]&lt;/a&gt;에서는 이를 Empirical Rademacher Complexity라고 정의한다.
기존 hypothesis set $\mathcal{H}$대신에 특정 loss function $L: \mathcal{y} \times \mathcal{y} \rightarrow \mathbb{R}$을 정의하고 $\mathcal{Z} = \mathcal{X} \times \mathcal{Y}$위에서의 loss function들의 집합을 $\mathcal{g}$라고 정의한다.&lt;/p&gt;

\[\begin{equation}
\mathcal{g} = \lbrace g: (x, y) 	\mapsto L(h(x), y): h \in \mathcal{H}\rbrace
\end{equation}\]

&lt;p&gt;이를 일반화하여 $\mathcal{g}$를 $\mathcal{Z} \mapsto [a, b]$에 해당하는 함수들의 집합,
$S = (z_1, \cdots, z_m)$을 m개의 &lt;strong&gt;샘플링된&lt;/strong&gt; $\mathcal{Z}$라고 가정한다.
이 때 Empirical Rademacher Complexity는 다음과 같다.&lt;/p&gt;

\[\begin{equation}
\widehat{\mathfrak{R}}_S(\mathcal{G})  = \mathbb{E}_{\mathbf{\sigma}} \left[
\sup_{g \in \mathcal{G}} \dfrac{1}{m} \sum_{i=1}^{m}  \sigma_i g(z_i) \right],
\end{equation}\]

&lt;p&gt;이 때 $\mathbf{\sigma} = (\sigma_1, \dots, \sigma_m)^{\mathsf{T}}$이고, $\sigma_i$는
$\lbrace -1, +1 \rbrace$로 중 추출된 independent uniform random variables이다.&lt;/p&gt;

&lt;p&gt;즉 위에서 말한대로 데이터 샘플($S$)에 대해 모델 집합($\mathcal{g}$)이 얼마나 잘 끼워맞출 수 있는가를 평균적으로 측정한 값이다.&lt;/p&gt;

&lt;p&gt;이를 내적의 형태로는 다음과 같이 쓸 수도 있다.&lt;/p&gt;

\[\begin{equation}
\widehat{\mathfrak{R}}_S(\mathcal{G})  = \mathbb{E}_{\mathbf{\sigma}} \left[
\sup_{g \in \mathcal{G}} \dfrac{\mathcal{\mathbb{\sigma}}\mathcal{\mathbb{g}_S}}{m} \right],
\end{equation}\]

&lt;p&gt;Empirical Rademacher Complexity를 일반화하여 특정 샘플 $S$가 아닌 샘플링 과정 전체에 대한 복잡도를 기대값으로 표현할 수 있다. 그것이 Rademacher Complexity의 정의이다.&lt;/p&gt;

&lt;p&gt;$\mathcal{D}$를 데이터 분포라고 하자. $S$는 $\mathcal{D}$로 추출된 특정 샘플링(샘플링 사이즈는 $m$)이라고 했을 때,
여러번 샘플링을 반복한 Empirical Rademacher Complexity을 Rademacher Complexity라고 하며 다음과 같이 정의한다.&lt;/p&gt;

\[\begin{equation}
\mathfrak{R}_m(\mathcal{G}) =
\mathbb{E}_{S \sim \mathcal{D}^m}  \left[ \widehat{\mathfrak{R}}_S(\mathcal{G}) \right]
\end{equation}\]

&lt;p&gt;두 Rademacher Complexity의 차이는 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Empirical Rademacher Complexity
    &lt;ul&gt;
      &lt;li&gt;$\widehat{\mathfrak{R}}_S(\mathcal{G})$&lt;/li&gt;
      &lt;li&gt;&lt;em&gt;특정 샘플 S에서 측정한 복잡도&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;주어진 학습 데이터로 측정된 오차 (train error) 혹은 표본 평균&lt;/li&gt;
      &lt;li&gt;이번에 뽑은 데이터셋 기준으로 모델 클래스가 얼마나 복잡한가?&lt;/li&gt;
      &lt;li&gt;랜덤 노이즈 $\sigma$에 의존&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Rademacher Complexity
    &lt;ul&gt;
      &lt;li&gt;$\mathfrak{R}_m (\mathcal{G})$&lt;/li&gt;
      &lt;li&gt;&lt;em&gt;샘플링 과정 전체에 대한 기대 복잡도&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;실제 데이터에 대한 복잡도 오차 (test error) 혹은 모평균&lt;/li&gt;
      &lt;li&gt;데이터를 여러 번 무작위로 뽑는다면, 평균적으로 모델 클래스가 얼마나 복잡할까?&lt;/li&gt;
      &lt;li&gt;랜덤 노이즈 $\sigma$와 샘플링된 데이터셋 $S$에 의존&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;generalization-bounds-based-on-rademacher-complexity&quot;&gt;Generalization bounds based on Rademacher Complexity&lt;/h3&gt;

&lt;p&gt;Rademacher Complexity에 기반한 Generalization bounds은 다음과 같다.&lt;/p&gt;

&lt;p&gt;$\mathcal{G}$를 $\mathcal{Z} \mapsto [0, 1]$의 매핑이라고 하자.
$m$의 크기로 $S$로부터 i.i.d 샘플링했을 때 $g \in S$ 각각에 대해,
VC dimension처럼 신뢰도 $\delta$를 사용하면 최소 $1-\delta$의 확률로 다음이 성립한다.&lt;/p&gt;

\[\begin{align}
\mathbb{E}[g(\mathcal{z})] &amp;amp;\leq \dfrac{1}{m} \sum_{i=1}^m g(z_i) + 2 \mathfrak{R}_m (\mathcal{G}) + \sqrt{\dfrac{\log{\dfrac{1}{\delta}}}{2m}} \\
\mathbb{E}[g(\mathcal{z})] &amp;amp;\leq \dfrac{1}{m} \sum_{i=1}^m g(z_i) + 2 \widehat{\mathfrak{R}}_S (\mathcal{G}) + 3\sqrt{\dfrac{\log{\dfrac{2}{\delta}}}{2m}}
\end{align}\]

&lt;p&gt;이것의 증명은 다음과 같다.&lt;/p&gt;

&lt;p&gt;함수 클래스 $\mathcal{G}$는 데이터 포인트 $\mathbf{z}$를 입력받아 실수 값을 출력하는 함수 $g$들의 집합이다.&lt;br /&gt;
머신러닝 맥락에서 각 함수 $g$는 특정 모델($h$)의 loss function, 즉 $g(\mathbf{z}) = \ell(h(\mathbf{x}), y)$에 해당할 수 있다.&lt;/p&gt;

&lt;p&gt;이때, $\mathbb{E}[g]$는 데이터의 실제 분포 $\mathcal{D}$에 대한 $g$의 &lt;strong&gt;기대값(진짜 성능)&lt;/strong&gt;을 의미하며, 이 값을 알 수 없다.&lt;/p&gt;

&lt;p&gt;대신 다음과 같이 주어진 샘플 $S = (\mathbf{z}_1, \dots, \mathbf{z}_m)$에 대한 $g$의 &lt;strong&gt;경험적 평균(측정된 성능)&lt;/strong&gt;을 정의할 수 있다.&lt;/p&gt;

\[\begin{equation}
\widehat{\mathbb{E}}_S [g(\mathcal{z})] := \frac{1}{m} \sum_{i=1}^{m} g(z_i)
\end{equation}\]

&lt;p&gt;이 둘의 차이를 $\Phi(S)$라고 하자.&lt;/p&gt;

\[\begin{align}
\Phi(S) := \sup_{g \in \mathcal{G}} \left( \mathbb{E}[g] - \widehat{\mathbb{E}}_S[g] \right)
\end{align}\]

&lt;p&gt;이는 &lt;strong&gt;특정 샘플 $S$ 위에서&lt;/strong&gt;, 함수 클래스 $\mathcal{G}$에 속한 모든 함수 $g$ 중에서, &lt;strong&gt;‘진짜 성능($\mathbb{E}[g]$)’과 ‘측정된 성능($\widehat{\mathbb{E}}_S[g]$)’ 간의 차이(일반화 갭)가 가장 큰 최악의 경우(supremum)가 얼마인지&lt;/strong&gt;를 나타낸다.
즉, 이 샘플 $S$가 얼마나 우리를 ‘긍정적으로 속일 수 있는지’에 대한 척도이다.&lt;/p&gt;

&lt;p&gt;만약 $S$의 포인트 하나만을 달리하는 $S’$를 또 정의할 수 있고 달라지는 샘플을 $S$에서의 $z_m$과 $S’$에서의 $z^{‘}_m$이라고 한다면,
supremumd의 차이는 차이의 supremum보다 커질 수 없으므로 (\(|\sup{A} - \sup{B}| \leq \sup{|A-B|}\)) 다음과 같은 관계를 만족한다.
이는 $\mathcal{G}$를 $\mathcal{Z} \mapsto [0, 1]$의 매핑이라고 했기 때문에 차이의 최대가 1이기 때문이다.&lt;/p&gt;

\[\begin{align}
\Phi(S) - \Phi(S&apos;) \leq \sup_{g \in \mathcal{G}} \left( \widehat{\mathbb{E}}_S[g] -  \widehat{\mathbb{E}}_{S&apos;}[g] \right) = \sup_{g \in \mathcal{G}} \dfrac{g(z_m) - g(z^{&apos;}_m)}{m} \leq \dfrac{1}{m}
\end{align}\]

&lt;p&gt;마찬가지로 $\Phi(S’) - \Phi(S) \leq 1/m$라고 증명할 수 있다. 따라서 $|\Phi(S’) - \Phi(S)| \leq 1/m$이다.
즉 $\Phi(S)$는 bounded difference를 만족한다.&lt;/p&gt;

&lt;p&gt;McDiarmid’s Inequality는 이렇게 bounded difference관계가 있을 때 적용할 수 있는 부등식이다.
입력이 조금 변해도 출력이 많이 변하지 않는 함수라면, 그 함수는 집중(concentration)된다는 것이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;만약 $f(z_1, \dots, z_m)$이 각 입력 $z_i$에 대해, $|f(z_1,\dots,z_i,\dots,z_m) - f(z_1,\dots,z^{‘}_i,\dots,z_m)| \leq c_i$를 만족한다면 (bounded difference),
\(P (f(S) - \mathbb{E}[f(S)] \geq t ) \leq \exp{\left( \dfrac{-2t^2}{\sum_{i=1}^m c_i^2}\right)}\)을 만족한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;여기서 위에서 구한 bounded difference를 조합하여 $\sum_{i=1}^m c_i^2 = m \left( \dfrac{1}{m^2} \right) = \dfrac{1}{m}$을 만족한다.&lt;/p&gt;

&lt;p&gt;\(P (f(S) - \mathbb{E}[f(S)] \geq t ) \leq \exp{(-2mt^2)}\)
가 되고 기존 신뢰도 $\delta$를 이용하여 이 확률을 $1 - \delta / 2$라고 가정하면 (변수변환을 위한 단순 교체), 다음과 같이 구할 수 있다.&lt;/p&gt;

\[\exp{(-2mt^2)} = \delta / 2 \rightarrow t = \sqrt{\dfrac{\log(2/\delta)}{2}}\]

&lt;p&gt;이 결과를 McDiarmid’s Inequality에 다시 넣어서 정리하면, 다음과 같다.
\(\Phi(S) \leq \mathbb{E}_S [\Phi(S)] + \sqrt{\dfrac{\log(2/\delta)}{2}}\)&lt;/p&gt;

&lt;p&gt;그리고 $\mathbb{E}_S [\Phi(S)]$은 다음 과정을 통해 Rademacher Complexity로 변환될 수 있다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E}_S [\Phi(S)] &amp;amp;= \mathbb{E}_S \left[ \sup_{g \in \mathcal{G}} (\mathbb{E}[g] - \widehat{\mathbb{E}}_S[g]) \right] \\
&amp;amp;= \mathbb{E}_{S} \left[ \sup_{g \in \mathcal{G}} \mathbb{E}_{S&apos;} \left[ \widehat{\mathbb{E}}_{S&apos;}(g) - \widehat{\mathbb{E}}_S (g) \right] \right] \\
&amp;amp;\le \mathbb{E}_{S, S&apos;} \left[ \sup_{g \in \mathcal{G}} (\widehat{\mathbb{E}}_{S&apos;}[g] - \widehat{\mathbb{E}}_S[g]) \right] \\
&amp;amp;= \mathbb{E}_{S, S&apos;} \left[ \sup_{g \in \mathcal{G}} \frac{1}{m} \sum_{i=1}^m (g(\mathbf{z}&apos;_i) - g(\mathbf{z}_i)) \right] \\
&amp;amp;= \mathbb{E}_{S, S&apos;, \mathbf{\sigma}} \left[ \sup_{g \in \mathcal{G}} \frac{1}{m} \sum_{i=1}^m \sigma_i (g(\mathbf{z}&apos;_i) - g(\mathbf{z}_i)) \right] \\
&amp;amp;\le \mathbb{E}_{\mathbf{\sigma}, S, S&apos;} \left[ \sup_{g \in \mathcal{G}} \frac{1}{m} \sum_{i=1}^m \sigma_i g(\mathbf{z}&apos;_i) + \sup_{g \in \mathcal{G}} \frac{1}{m} \sum_{i=1}^m -\sigma_i g(\mathbf{z}_i) \right] \\
&amp;amp;= \mathbb{E}_{\mathbf{\sigma}, S&apos;} \left[ \sup_{g \in \mathcal{G}} \frac{1}{m} \sum_{i=1}^m \sigma_i g(\mathbf{z}&apos;_i) \right] + \mathbb{E}_{\mathbf{\sigma}, S} \left[ \sup_{g \in \mathcal{G}} \frac{1}{m} \sum_{i=1}^m \sigma_i g(\mathbf{z}_i) \right] \\
&amp;amp;= \mathbb{E}_{\mathbf{\sigma}, S&apos;}[\hat{\mathfrak{R}}_{S&apos;}(\mathcal{G})] + \mathbb{E}_{\mathbf{\sigma}, S}[\hat{\mathfrak{R}}_S(\mathcal{G})] \\
&amp;amp;= 2 \mathfrak{R}_m (\mathcal{G})
\end{align*}\]

&lt;p&gt;증명 과정에서 다음과 같은 사실들이 사용 되었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\mathbb{E}[g] = \mathbb{E}&lt;em&gt;{S’} \left[ \widehat{\mathbb{E}}&lt;/em&gt;{S’} (g) \right]$&lt;/li&gt;
  &lt;li&gt;Rademacher variable $\sigma_i$: uniformly distributed independent random variable from $\lbrace -1, +1\rbrace$&lt;/li&gt;
  &lt;li&gt;$\mathbb{E}&lt;em&gt;{S’}[\hat{\mathfrak{R}}&lt;/em&gt;{S’}(\mathcal{G})] = \mathbb{E}_{S}[\hat{\mathfrak{R}}_S(\mathcal{G})]$: 어차피 random variable이기때문에 기대값(expectation)에 영향을 주지 않음&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;자잘한 계산을 거치면, 결국 원래 증명하고자 했던 이 식이 나오게된다.&lt;/p&gt;

\[\begin{align}
\mathbb{E}[g(\mathcal{z})] &amp;amp;\leq \dfrac{1}{m} \sum_{i=1}^m g(z_i) + 2 \mathfrak{R}_m (\mathcal{G}) + \sqrt{\dfrac{\log{\dfrac{1}{\delta}}}{2m}} \\
\mathbb{E}[g(\mathcal{z})] &amp;amp;\leq \dfrac{1}{m} \sum_{i=1}^m g(z_i) + 2 \widehat{\mathfrak{R}}_S (\mathcal{G}) + 3\sqrt{\dfrac{\log{\dfrac{2}{\delta}}}{2m}}
\end{align}\]

&lt;p&gt;여기서 각 term은 다음과 같은 의미를 지닌다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(\mathbb{E}[g(\mathcal{z})]\) : True risk. 모델의 실제 성능인 test error에 해당한다.&lt;/li&gt;
  &lt;li&gt;\(\dfrac{1}{m} \sum_{i=1}^m g(z_i)\) : Empirical risk. 훈련데이터에 대한 평균 손실 즉 training error에 해당한다.&lt;/li&gt;
  &lt;li&gt;\(2 \mathfrak{R}_m (\mathcal{G})\) : Rademacher Complexity. 모델의 복잡도를 나타내는 항.&lt;/li&gt;
  &lt;li&gt;\(\sqrt{\dfrac{\log{\dfrac{1}{\delta}}}{2m}}\) : Confidence. 확률적인 신뢰도를 뜻하며, 최소 $1-\delta$의 확률로 이 generalization bound가 성립한다라는 것을 의미. 샘플 크기 $m$이 클 수록 이 값은 작아져서 상한선이 더욱 타이트해짐.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결론은 이렇다. training error(학습이 잘 되었다고 가정)와 test error를 차이를 줄이려면 다음 조건이 필요하다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Rademacher Complexity를 줄인다 = 모델의 복잡도를 줄인다&lt;/li&gt;
  &lt;li&gt;Confidence term을 줄인다 = 데이터를 늘린다&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;하지만 Rademacher Complexity는 $\mathfrak{R}_m (\mathcal{G})$ 즉, $\mathcal{G}$에 영향을 받는 함수이고, 이는 함수 공간의 크기 $\mathcal{G}$에 영향을 받는 값이라고 알 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;norm&quot;&gt;Norm&lt;/h2&gt;

&lt;p&gt;위 섹션의 결과는 &lt;strong&gt;일반화를 위해서는&lt;/strong&gt; 훈련의 품질을 높이고 데이터의 양을 늘리는 것도 중요하지만,  &lt;strong&gt;함수 공간의 크기를 줄여야한다&lt;/strong&gt;라는 결론에 도달한다. 그러면 &lt;strong&gt;신경망 모델에서 함수 공간의 크기라는 추상적인 개념을 구체적으로 어떻게 측정할 수 있는가?&lt;/strong&gt;라는 질문에 또다시 봉착하게 된다. 그 답이 바로 모델을 구성하는 파라미터의 “Norm”이다.&lt;/p&gt;

&lt;p&gt;Norm은 벡터나 행렬뿐 아니라 함수 공간에 존재하는 원소들까지 일정한 &lt;strong&gt;기준(norm)&lt;/strong&gt;에 따라 크기를 측정하는 추상화된 거리 함수다. 즉, Norm은 다양한 형태의 공간에서 ‘크기’라는 개념을 일관된 방식으로 정의하기 위한 수학적 도구다.&lt;/p&gt;

&lt;h3 id=&quot;definition-of-norm&quot;&gt;Definition of Norm&lt;/h3&gt;
&lt;p&gt;다음 조건을 만족할 때 norm이라고 할 수 있다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;양의 정부호 (Positive Definiteness): 0벡터만 길이가 0&lt;/p&gt;

\[\| x \| = 0 \iff x=0\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;동차성 (Homogeneity / Scalar Multiplication): 스칼라배를 하면 크기도 그 절댓값만큼 변함&lt;/p&gt;

\[\| \alpha x \| = 0 = |\alpha| \|  x \|\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;삼각 부등식 (Triangle Inequality): 두 벡터의 합의 길이는 각 길이의 합보다 크지 않습니다.&lt;/p&gt;

\[\| x + y \|  \leq \| x \| + \| y \|\]
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;examples-of-norm&quot;&gt;Examples of Norm&lt;/h3&gt;

&lt;p&gt;단순 norm을 설명하기보다 가중치 입장에서의 norm에 대해 설명하고자 한다.&lt;/p&gt;

&lt;p&gt;이를 위해 $y = w_1 x_1 + w_2 x_2 + c$형태의 least square problem에서 가중치 공간(weight space)을 기준으로 설명하고자 한다.
위 모델의 가중치 공간에서는 $x, y$축이 $w_1, w_2$이고 $z$ 축은 loss 값이라고 할 수 있다. 이때 각각의 norm은 다음과 같은 의미를 가진다.&lt;/p&gt;

&lt;h4 id=&quot;l1-norm-absolute-value-norm&quot;&gt;L1 Norm (Absolute-value norm)&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;정의: $$|W|_1 = \sum&lt;/td&gt;
          &lt;td&gt;w_{ij}&lt;/td&gt;
          &lt;td&gt;$$&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
  &lt;li&gt;직관:
    &lt;ul&gt;
      &lt;li&gt;가중치를 위한 예산의 제약: 모든 가중치의 절대값 총합은 특정 값 혹은 예산(C)를 넘을 수 없다고 제약을 거는것&lt;/li&gt;
      &lt;li&gt;다이아몬드 형태의 제약: 2차원 가중치 공간에서 $|w_1| + |w_2| \leq C$의 예산조건은 기하하적으로 다이아몬드 형태의 제약을 생성&lt;/li&gt;
      &lt;li&gt;꼭짓점 효과: loss function의 등고선은 다이아몬드와 만나 최적점을 찾을 때 다이아몬드의 꼭짓점에 닿을 확률이 높음&lt;/li&gt;
      &lt;li&gt;Feature selection 효과: 꼭짓점은 $w_1, w_2$축 위에 있으므로 한 축을 선택하면 다른 축은 0으로 만들어버리는 결과&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;역할: 불필요한 가중치를 정확히 0으로 만들어 &lt;strong&gt;희소성(Sparsity)&lt;/strong&gt;을 유도하고, 이는 곧 피처 선택(Feature Selection) 효과를 발생&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;l2-norm-euclidean-norm&quot;&gt;L2 Norm (Euclidean Norm)&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;정의: \(\|W\|_F = \sqrt{\sum w_{ij}^2}\)&lt;/li&gt;
  &lt;li&gt;직관:
    &lt;ul&gt;
      &lt;li&gt;원점으로부터의 거리 제한: 원점 $(0, 0)$으로부터의 $(w_1, w_2)$까지의 Euclidean distance&lt;/li&gt;
      &lt;li&gt;원의 형태: $\sqrt{w_1^2 + w_2^2} \leq C$는 가중치 공간에서 원 형태로 존재&lt;/li&gt;
      &lt;li&gt;원의 효과: 한쪽 축이 아닌 $(w_1, w_2)$ 중간의 어중간한 지점에서 접점 발생&lt;/li&gt;
      &lt;li&gt;weight decay: 모든 축의 &lt;strong&gt;퍼센티지에 따른 감쇠&lt;/strong&gt;효과. 모든 weight을 줄이지만 큰 weight는 더 큰 값으로 줄어드는 효과.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;역할:
    &lt;ul&gt;
      &lt;li&gt;모든 가중치의 크기를 전반적으로 작게 유지하여, 모델이 지나치게 복잡해지는 것을 막고 과적합을 방지하는 부드러운 모델을 생성&lt;/li&gt;
      &lt;li&gt;L1 norm과 달리 파라미터의 크기를 축소할 뿐 파라미터 자체를 제거하지는 않는다. 다른 말로는 파라미터 간의 비율 관계를 바꾸지는 않는다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L1과 L2 norm은 다음 그림을 같이 보면 이해가 쉽다.&lt;/p&gt;
&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-Preventing-Overfitting-With-Norms-and-BN/03-L1-L2-norm.gif&quot; type=&quot;image/gif&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://x.com/itayevron/status/1328421322821693441&amp;quot;&amp;gt;
Why does L1 regularization induce sparse models?&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-Preventing-Overfitting-With-Norms-and-BN/03-L1-L2-norm.gif&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://x.com/itayevron/status/1328421322821693441&amp;quot;&amp;gt;
Why does L1 regularization induce sparse models?&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://x.com/itayevron/status/1328421322821693441&quot;&gt;
Why does L1 regularization induce sparse models?&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h4 id=&quot;spectral-norm&quot;&gt;Spectral Norm&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;정의: \(\|W\|_\sigma = \max_{\|\mathbf{x}\|_2=1} \|\mathbf{Wx}\|_2\) (최대 특이값)&lt;/li&gt;
  &lt;li&gt;직관:
    &lt;ul&gt;
      &lt;li&gt;관점의 전환: L1, L2 Norm이 가중치 &lt;em&gt;벡터의 크기&lt;/em&gt;를 봤다면, Spectral Norm은 가중치 &lt;strong&gt;행렬&lt;/strong&gt; W를 입력 벡터를 다른 벡터로 바꾸는 &lt;em&gt;변환기(Linear Operator)&lt;/em&gt;라고 생각하고 &lt;strong&gt;행렬의 증폭량&lt;/strong&gt;을 봄&lt;/li&gt;
      &lt;li&gt;최대 증폭률(Stretch): 이 변환기가 입력을 &lt;em&gt;최대 몇 배까지 증폭시킬 수 있는가?&lt;/em&gt;를 나타냄. 즉, 어떤 방향의 입력이 들어왔을 때 가장 민감하게 반응하여 최대로 길어지는지를 측정&lt;/li&gt;
      &lt;li&gt;안정성 측정: 이 &lt;em&gt;최대 증폭률&lt;/em&gt;이 너무 크면, 입력의 작은 노이즈나 변화에도 출력이 폭발적으로 변하는 불안정한 모델이 됨. 반대로 이 값을 제어하면, 입력이 조금 바뀌어도 출력이 급격히 변하지 않는 안정적인 모델을 만들 수 있음&lt;/li&gt;
      &lt;li&gt;L-$\infty$ Norm은 벡터의 max값을 취한다면, Spectral Norm은 선형변환(matrix)의 크기배율의 최대값을 찾는 문제&lt;/li&gt;
      &lt;li&gt;즉 Spectral Norm은 SVD를 통해 정의되는 문제로 행렬의 maximum singular value을 찾는 문제로 귀결&lt;/li&gt;
    &lt;/ul&gt;

\[\|W\|_\sigma := \sup_{x \neq 0}\dfrac{\| Ax \|_2}{\| x \|_2} = \max_{\| x \|_2=1} \| Ax \|_2\]
  &lt;/li&gt;
  &lt;li&gt;역할:
    &lt;ul&gt;
      &lt;li&gt;함수의 변화율 상한선인 &lt;strong&gt;립시츠 상수(Lipschitz Constant)&lt;/strong&gt;를 직접 제어하여 모델의 안정성을 향상&lt;/li&gt;
      &lt;li&gt;GAN 훈련 시 판별자와 생성자의 학습 균형을 맞추는 역할&lt;/li&gt;
      &lt;li&gt;적대적 공격(Adversarial Attack)에 대한 방어력을 높이는 역할 수행&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;lipschitz-constant&quot;&gt;Lipschitz Constant&lt;/h5&gt;

&lt;p&gt;Lipschitz Constant는 함수의 최대 기울기(증가율)을 제한하는 속도 제한 장치같은 역할을 한다.
Lipschitz Continuous한 함수 $f$는 입력값 $x_1, x_2$가 변할 때 함수 출력값 $f(x_1), f(x_2)$가 그 입력값의 변화량보다 일정한 상수($K$)배 이상으로 멀어지지 않는다.&lt;/p&gt;

\[\begin{align}
|f(x_1) - f(x_2)| \leq K |x_1 - x_2|
\end{align}\]

&lt;p&gt;신경망에서는 특정 Layer의 spectral norm값이 그 레이어의 lipschitz constant역할을 한다.&lt;/p&gt;

&lt;h2 id=&quot;regularization-in-practice&quot;&gt;Regularization in Practice&lt;/h2&gt;

&lt;p&gt;앞서 살펴본 Norm들은 모델의 복잡도를 제한하는 정규화(Regularization)의 수학적 기반이다. 이제 실제 딥러NING 모델 학습 시 이 원리가 어떻게 적용되는지 대표적인 두 가지 기법을 통해 알아보도록 하겠다.&lt;/p&gt;

&lt;p&gt;특히 이 파트는 &lt;a class=&quot;citation&quot; href=&quot;#min2025mlmasterclass&quot;&gt;[3]&lt;/a&gt;의 &lt;em&gt;레슨 5. 엇나가는 학습 모델을 어떻게 제어하나&lt;/em&gt;의 내용을 많이 참고하였다.&lt;/p&gt;

&lt;h3 id=&quot;weight-decay&quot;&gt;Weight Decay&lt;/h3&gt;

&lt;p&gt;실은 지금까지 정규화(Regularization) 방법으로 weight decay에 관한 얘기를 계속해서 해왔다. 일반화 오류를 줄이기 위해 모델의 복잡도를 줄여야했고, 모델의 복잡도를 신경망에서 측정하기 위해서 norm을 설명했다. Norm을 통해 측정된 모델의 복잡도는 Loss function에 넣어 loss와 함께 학습과정에서 minimize시키도록 한다.&lt;/p&gt;

&lt;p&gt;이러한 접근법을 &lt;strong&gt;가중치 감쇠(Weight Decay)&lt;/strong&gt;라고 부르며, 어떤 Norm을 사용하느냐에 따라 모델에 미치는 영향과 목적이 달라진다.&lt;/p&gt;

\[L(W) = L_{\textrm{data}} (W)+ \lambda R(W)\]

&lt;p&gt;이 때 각각은 다음의 의미를 가진다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;$L(W)$: 최종적으로 최소화해야 할 전체 loss function&lt;/li&gt;
  &lt;li&gt;$L_{\textrm{data}}$: 데이터에 대한 loss function (Original loss)&lt;/li&gt;
  &lt;li&gt;$\lambda$: 정규화의 강도를 조절하는 하이퍼파라미터&lt;/li&gt;
  &lt;li&gt;$R(W)$: 정규화 항(Regularization Term), $| W|^2_2$ 혹은 $| W|_1$&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;ridge-regularization-l2-norm&quot;&gt;Ridge Regularization (L2 Norm)&lt;/h4&gt;

\[L(W) = L_{\textrm{data}} (W)+ \lambda \| W\|^2_2\]

&lt;p&gt;앞서 말한 것처럼 일정한 비율(%)로 weight를 감소시켜 모든 weight의 절대적인 크기를 줄인다.
Weight vector의 방향을 바꾸지 않고 크기만 줄이는 것이다.
이를 통해 자연스럽게 함수 전체의 기울기가 낮아지고, 특정 feature 하나에 크게 의존하지 않는 robust한 모델이 탄생하게 된다.
Robust하다는 것은 &lt;em&gt;덜 예민하다&lt;/em&gt;라는 것이고, 이를 Smoothing 효과라고 한다.&lt;/p&gt;

&lt;p&gt;L2 norm의 제곱을 하는 이유는 $\sqrt{}$를 제거하여 미분을 편하게 하려는 계산상의 목적이 있고,
L2 norm에 더욱 큰 페널티를 줘서 weight decay효과를 더 극대화시키려는 목적이 있다.&lt;/p&gt;

&lt;h4 id=&quot;lasso-regularization-l1-norm&quot;&gt;Lasso Regularization (L1 Norm)&lt;/h4&gt;

\[L(W) = L_{\textrm{data}} (W)+ \lambda \| W\|_1\]

&lt;p&gt;Norm에서 보여준 것처럼 L1 regularization을 하면 weight에서 일정한 값을 빼는 효과를 준다.
이 때문에 중요도가 낮은 weight들은 빼는 과정에서 0이 되거나 매우 작은 값이 되어버려
수많은 weight 중에서 일부만 살아남는 희소성(sparsity)를 만들게 된다.
이로 인해 feature를 아예 제거하는 feature selection 효과를 가지게 된다.&lt;/p&gt;

&lt;h4 id=&quot;regularization-based-on-spectral-norm&quot;&gt;Regularization based on Spectral Norm&lt;/h4&gt;

\[L(W) = L_{\textrm{data}} (W)+ \lambda \| W\|_\sigma\]

&lt;p&gt;Spectrum Norm 기반 Regularzation은 weight의 크기 자체보다, weight matrix가 만드는 변환의 안정성에 초점을 맞춘다.&lt;/p&gt;

&lt;p&gt;각 레이어의 Lipschitz Constant $K$가 Spectral Norm값과 같기 떄문에 MLP (함수 $f=f_L \circ \dots \circ f_1$)에서는 다음과 같이 레이어별 Lipschitz Constant를 곱하게 된다.&lt;/p&gt;

\[\begin{equation*}
K_f \leq \sum_{i=1}^L K_{f_i}= \sum_{i=1}^L \| W_i \|_\sigma
\end{equation*}\]

&lt;p&gt;위의 loss function을 최소화하는 것은 각 레이어의 spectrum norm을 줄여, 결과적으로 전체 신경망의 Lipschitz constant bound을 직접 제어하는 것을 의미한다. 이는 입력의 작은 변화에 출력이 폭발적으로 변하는 것을 막아 모델을 안정시키는 역할을 한다.&lt;/p&gt;

&lt;p&gt;GAN에서는 &lt;a class=&quot;citation&quot; href=&quot;#miyato2018spectral&quot;&gt;[4]&lt;/a&gt;을 토대로 이 원리를 더 직접적으로 적용한 스펙트럴 정규화(Spectral Normalization)라는 기법을 사용한다.&lt;/p&gt;

&lt;p&gt;이는 loss function에 항을 추가하는 대신, 학습 과정에서 매번 weight matrix $W$를 자신의 spectrum norm으로 직접 나누어주는 방식이다.&lt;/p&gt;

\[W_{SN} = \dfrac{W}{ \| W_i \|_\sigma}\]

&lt;p&gt;이 방법을 통해 각 레이어 weight matrix의 spectrum norm을 항상 1로 만들어, 판별자(discriminator)의 Lipschitz constant를 1로 강제하는 효과를 만든다.&lt;/p&gt;

&lt;p&gt;GAN에서는 학습의 안정성은 판별자(Discriminator)의 성능에 크게 좌우된다.
판별자는 생성자에게 유의미한 피드백을 주기 위해 입력의 미세한 차이에 민감하게 반응하면서도, 데이터의 다양한 특징(feature)들을 종합적으로 활용해야 한다.&lt;/p&gt;

&lt;p&gt;L2 Regularization과 같은 기존 방식들은 여기서 딜레마가 발생한다.
&lt;a class=&quot;citation&quot; href=&quot;#miyato2018spectral&quot;&gt;[4]&lt;/a&gt;에 따르면
기존 방식들은 판별자의 민감도를 높이려는 과정에서 의도치 않게 weight matrix을 low-rank로 만드는 경향이 있다.
행렬의 rank가 낮아진다는 것은 판별자가 소수의 feature에만 의존하게 된다는 의미이며, 이는 결국 생성자에게 질 낮은 피드백을
주게 된다.&lt;/p&gt;

&lt;p&gt;반면, SN(Spectral Normalization)은 행렬의 rank와는 독립적으로 오직 최대 singular value(최대 민감도)만을 제어한다.
따라서 판별자가 Lipschitz constant bound를 유지하면서 다양한 feature를 활용할 수 있게 해준다.&lt;/p&gt;

&lt;h3 id=&quot;batch-normalization&quot;&gt;Batch Normalization&lt;/h3&gt;

&lt;p&gt;Batch Normalization은 학습 시 미니배치(mini-batch) 단위로 데이터의 평균과 분산을 계산하여 각 레이어의 입력을 정규화하는 방식이다.&lt;/p&gt;

&lt;p&gt;그런데 Batch Normalization은 또 다루면 너무 길어지므로 자세한건 다른 포스트에서 다루도록 하겠다.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;지금까지 일반화(Generalization) 성능을 올리기 위해 모델 복잡도를 줄이는 방법을 살펴보았다. 모델의 일반화 성능을 올리기 위해서는 훈련 데이터와 테스트 데이터가 동일분포라고 가정할 때 (1) 충분한 훈련 데이터양을 확보하고 (2) 훈련의 품질을 높여 좋은 모델을 찾아내고 (3) 모델의 복잡도를 낮춰야한다. 이 중 (3)에 집중하여 이론적으로 가설 공간의 크기를 줄이는 것이 모델의 복잡도를 낮추는 역할이다. 특히 신경망에서는 추상적이었던 모델의 복잡도 혹은 가설 공간의 크기는 모델을 구성하는 파라미터들의 &lt;strong&gt;Norm&lt;/strong&gt;으로 실체화시켜서 측정이 가능하다. Norm을 통해 직접적으로 weight를 줄이는 Weight Decay 방법을 알아보았다.
간접적으로 배치의 스케일을 조정하여 간접적으로 weight를 줄이는 Batch Normalization이 있지만 너무 길어져서 생략한다.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.cs.cmu.edu/~atalwalk/teaching/winter17/cs260/index.html&quot;&gt;CS260 Machine Learning Algorithms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;abu2012learning&quot;&gt;[1]Y. S. Abu-Mostafa, M. Magdon-Ismail, and H.-T. Lin, &lt;i&gt;Learning from data&lt;/i&gt;, vol. 4. AMLBook New York, 2012.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;mohri2018foundations&quot;&gt;[2]M. Mohri, A. Rostamizadeh, and A. Talwalkar, &lt;i&gt;Foundations of machine learning&lt;/i&gt;. MIT press, 2018.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;min2025mlmasterclass&quot;&gt;[3]민재식, &lt;i&gt;머신 러닝 마스터 클래스: 기본기를 바로잡는 9가지 레슨&lt;/i&gt;. 도서출판 인사이트, 2025.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;miyato2018spectral&quot;&gt;[4]T. Miyato, T. Kataoka, M. Koyama, and Y. Yoshida, “Spectral normalization for generative adversarial networks,” &lt;i&gt;arXiv preprint arXiv:1802.05957&lt;/i&gt;, 2018.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Singular Value Decomposition</title>
   <link href="https://blog.liam.kim/posts/2025/10/SVD/"/>
   <updated>2025-10-08T01:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2025/10/SVD</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;SVD(Singular Value Decomposition)는 LLM을 공부하다보면 참 이곳저곳에서 많이 나오는 알고리즘이다.
특히 &lt;a class=&quot;citation&quot; href=&quot;#strang2019linear&quot;&gt;[1]&lt;/a&gt;에서도 &lt;strong&gt;Data Science meets linear algebra in the SVD&lt;/strong&gt;라고 시작하고,
&lt;a href=&quot;https://youtu.be/pRM_P6UfdIc?t=411&quot;&gt;Low Level Technicals of LLMs: Daniel Han&lt;/a&gt;에서도 그 중요성을 강조하고 있다.&lt;/p&gt;

&lt;p&gt;그래서 오늘은 SVD에 대해 좀 더 집중적으로 탐구해보고자 한다.&lt;/p&gt;

\[\mathbf{M} = \mathbf{U} \mathbf{\Sigma} \mathbf{V}^\top\]

&lt;h2 id=&quot;basic-problems-of-linear-algebra&quot;&gt;Basic problems of Linear Algebra&lt;/h2&gt;
&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#strang2019linear&quot;&gt;[1]&lt;/a&gt;에서는 가장 기본적인 선형대수(Linear Algebra)의 문제로 다음 5가지를 꼽고 있다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;$\mathbf{A}\mathbf{x}=\mathbf{b}$&lt;/li&gt;
  &lt;li&gt;$\mathbf{A}\mathbf{x}=\lambda \mathbf{x}$&lt;/li&gt;
  &lt;li&gt;$\mathbf{A}\mathbf{v}= \sigma \mathbf{u}$&lt;/li&gt;
  &lt;li&gt;Minimize $\lVert \mathbf{A}\mathbf{x} \rVert^2 / \lVert \mathbf{x} \rVert^2$&lt;/li&gt;
  &lt;li&gt;Factor the matrix $\mathbf{A}$&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그리고 각각에 대해 중요한 질문과 답을 던진다.
이 글에서는 $\mathbf{A}\mathbf{x}=\mathbf{b}$ $\mathbf{A}\mathbf{v}= \sigma \mathbf{u}$를 중점적으로 볼 예정이다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\mathbf{A}\mathbf{x}=\mathbf{b}$
    &lt;ul&gt;
      &lt;li&gt;Solution $\mathbf{x}$는 존재하는가? 즉, vector $\mathbf{b}$는 $\mathbf{A}$의 column space에 속한다고 할 수 있는가?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;$\mathbf{A}\mathbf{x}=\lambda \mathbf{x}$
    &lt;ul&gt;
      &lt;li&gt;$\mathbf{A}\mathbf{x}$는 $\mathbf{x}$와 같은 방향을 유지하는가? 즉, 복잡한 $\mathbf{A}$를 $\lambda$로 단순화 시킬 수 있다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;$\mathbf{A}\mathbf{v}= \sigma \mathbf{u}$
    &lt;ul&gt;
      &lt;li&gt;벡터 2개 $\mathbf{u}$와 $\mathbf{v}$가 존재하고, $\mathbf{A}$는 rectangular하며 data로 가득찼을 때, 과연 어떤 data matrix가 중요한가? 여기서 해답은 SVD인 $\sigma \mathbf{u} \mathbf{v}^\mathsf{T}$이다. (PCA: Principle Component Analysis)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Minimization and Fatorization
    &lt;ul&gt;
      &lt;li&gt;근본적인 응용수학인 문제로 귀결되며, SVD의 $\mathbf{u}$와 $\mathbf{v}$를 찾는 문제로 귀결된다. 즉, Data를 가장 잘 설명하는 선형대수는 무엇인가?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;matrix-vector-multiplication-mathbfax&quot;&gt;Matrix-vector multiplication $\mathbf{Ax}$&lt;/h2&gt;

&lt;p&gt;궁극적인 목표인 SVD를 가기 위해서 matrix multiplication ($\mathbf{AB}$)을 알아야하고, 그걸 위해서는 matrix-vector multiplication ($\mathbf{Ax}$)을 먼저 알아야한다.&lt;/p&gt;

&lt;h3 id=&quot;linear-combination-of-vectors&quot;&gt;Linear combination of vectors&lt;/h3&gt;
&lt;p&gt;다음과 같은 샘플 $\mathbf{A}$를 다음과 같이 $a_1$과 $a_2$의 조합이라고 생각해보면, 다음과 같이 inner product of rows와 combinations of the columns 2가지 방식으로 생각할 수 있다.&lt;/p&gt;

\[\begin{align*}
A &amp;amp;= \begin{bmatrix}
  2 &amp;amp; 3 \\
  2 &amp;amp; 4 \\
  3 &amp;amp; 7
\end{bmatrix}
= \bigl[\, \mathbf{a}_1 \ \mathbf{a}_2 \,\bigr] \\
x &amp;amp;= \begin{bmatrix}
  x_1 \\
  x_2
\end{bmatrix} \\
Ax &amp;amp;= \begin{bmatrix}
  2 &amp;amp; 3 \\
  2 &amp;amp; 4 \\
  3 &amp;amp; 7
\end{bmatrix} \begin{bmatrix}
  x_1 \\
  x_2
\end{bmatrix} = \begin{bmatrix}
  2 x_1 + 3 x_2 \\
  2 x_1 + 4 x_2 \\
  3 x_1 + 7 x_2
\end{bmatrix} \\
Ax &amp;amp;= x_1 \begin{bmatrix}
  2 \\
  2 \\
  3
\end{bmatrix} + x_2 \begin{bmatrix}
  3 \\
  4 \\
  7
\end{bmatrix}  = \begin{bmatrix}
  2 x_1 + 3 x_2 \\
  2 x_1 + 4 x_2 \\
  3 x_1 + 7 x_2
\end{bmatrix} = x_1 a_1 + x_2 a_2
\end{align*}\]

&lt;p&gt;즉, $\mathbf{Ax}$는 $\mathbf{A}$의 columns의 linear combination이며,
$\mathbf{A}$의 column들을 &lt;strong&gt;Column Space&lt;/strong&gt;라고 한다.&lt;/p&gt;

&lt;p&gt;선형대수를 처음 배우면 이 &lt;strong&gt;Space&lt;/strong&gt;란 말이 추상적이라 어려운데, 방향의 조합으로 만들어지는 &lt;strong&gt;공간&lt;/strong&gt;이라고 생각하면 된다.
일반적으로 쉽게 접하는 2차원 평면은 cartesian coordinate인 $x$축 즉 $(1, 0)$과 $y$축 즉 $(0, 1)$이라는 두 방향의 조합으로 결정되는 공간인 것이다.&lt;/p&gt;

&lt;p&gt;즉, 다음과 같이 정의할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matrix $\mathbf{A}$의 column vector들의 선형 결합(linear combination)은 $\mathbf{A}$의 열공간(Column space)을 이룬다.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;independence-and-rank-of-mathbfa&quot;&gt;Independence and Rank of $\mathbf{A}$&lt;/h3&gt;

&lt;p&gt;위에서 언급한 $x$축과 $y$축을 생각해보자.
벡터 $(1,0)$과 $(0,1)$을 잡으면 이 두 벡터를 조합해 2차원 공간 $\mathbb{R}^2$의 모든 점을 표현할 수 있다.&lt;/p&gt;

&lt;p&gt;그런데 벡터를 다르게 잡으면 문제가 생길 수 있다. 예를 들어 $(1,0)$과 $(3,0)$을 고르면 어떨까? $(3,0)$은 사실 $(1,0)$을 세 배 한 것이므로, 이 둘을 조합해도 결국 $x$축 위의 점들만 얻을 수 있다. 즉, $y$축 방향은 전혀 표현하지 못한다.&lt;/p&gt;

&lt;p&gt;이처럼 어떤 벡터들이 서로 “겹치는” 정보를 담고 있으면, 그 벡터들로는 더 넓은 공간을 만들어내지 못한다. 이런 상황을 일반적으로 &lt;strong&gt;선형 종속(linear dependence)&lt;/strong&gt;이라고 부르고, 반대로 벡터들이 서로 겹치지 않고 독립적으로 공간을 구성할 수 있을 때 &lt;strong&gt;선형 독립(linear independence)&lt;/strong&gt;이라고 한다.&lt;/p&gt;

&lt;p&gt;이런 차이를 일반적으로 설명하기 위해, 먼저 &lt;strong&gt;span&lt;/strong&gt;이라는 개념을 정의한다.&lt;/p&gt;

&lt;p&gt;정의 (Span). 벡터 집합 $ S = \lbrace v_1, v_2, \dots, v_k \rbrace $가 주어졌을 때,&lt;/p&gt;

\[\begin{equation}
\textrm{Span}(S) = \lbrace a_1 v_1 + a_2 v_2 + \cdots a_k v_k \mid a_1, a_2, \cdots, a_k \in \mathbb{R} \rbrace
\end{equation}\]

&lt;p&gt;라고 하며 이는 $S$에 속한 벡터들의 모든 선형 결합으로 얻을 수 있는 벡터들의 집합을 뜻한다.&lt;/p&gt;

&lt;p&gt;이제 이 개념을 통해, $(1,0)$과 $(0,1)$은 $\mathbb{R}^2$ 전체를 span하지만, $(1,0)$과 $(3,0)$은 $x$축만 span한다는 사실을 보다 정확히 표현할 수 있다.&lt;/p&gt;

&lt;p&gt;여기서 다시 선형 독립의 개념을 떠올려보면, 벡터들이 서로 중복된 정보를 담지 않아야 원하는 공간 전체를 span할 수 있다는 점이 드러난다.&lt;/p&gt;

&lt;p&gt;따라서 어떤 벡터 공간을 생각했을 때, 그 공간의 모든 벡터를 표현할 수 있어야 한다는 조건(즉, span이 공간 전체여야 한다)과, 그 벡터들이 서로 겹치지 않는 조건(즉, 선형 독립이어야 한다)을 동시에 만족하는 벡터 집합을 &lt;strong&gt;기저(basis)&lt;/strong&gt;라고 하며 다음과 같이 정의할 수 있다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;어떤 벡터 공간에서,&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;그 공간 전체를 span하고,&lt;/li&gt;
    &lt;li&gt;벡터들이 선형 독립일 때&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;그 벡터 집합을 basis라고 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;예를 들어 $\mathbb{R}^2$에서는 $(1,0)$과 $(0,1)$이 대표적인 basis이다. 두 벡터는 서로 독립적이고, 두 벡터의 조합으로 평면 위의 모든 벡터를 만들 수 있기 때문이다.
반대로 $(1,0)$과 $(3,0)$은 $x$축만 span하므로 $\mathbb{R}^2$의 basis가 될 수 없다.&lt;/p&gt;

&lt;p&gt;따라서 basis는 공간 전체를 표현할 수 있는 최소한의 벡터 집합이라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;Span과 basis가 처음에 헷갈릴 수 있는데, Span은 주어진 벡터들로 만들 수 있는 전체 &lt;strong&gt;공간&lt;/strong&gt;이고, basis는 &lt;strong&gt;그 공간을 대표하는 선형 독립적인 벡터&lt;/strong&gt;이다. basis 자체는 여러개 존재할 수 있으나, basis의 개수는 항상 같고 이를 &lt;strong&gt;차원(dimension)&lt;/strong&gt;으로 일정하다.&lt;/p&gt;

&lt;p&gt;이제 어떤 행렬 $\mathbf{A}$의 column space를 생각해 보자. 그 공간의 차원, 즉 기저 벡터의 개수를 rank라고 한다. 다시 말해 rank는 column space의 차원과 같으며, 행렬의 독립적인 열(column)의 개수를 세는 것과 같다. 예를 들어, $\mathbb{R}^2$에서 $(1,0),(0,1) $은 서로 독립이므로 rank는 2가 되고,
(1,0),(3,0)처럼 종속된 벡터들만 있다면 rank는 1이 된다. 따라서 rank는 기저 선택과 관계없이 항상 유일하게 정해진다.&lt;/p&gt;

&lt;p&gt;마지막으로, &lt;strong&gt;차원(dimension)&lt;/strong&gt;은 일반적인 벡터 공간의 크기를 말하는 개념이고, &lt;strong&gt;rank&lt;/strong&gt;는 특별히 &lt;em&gt;“행렬이 만드는 column space(또는 row space)의 차원”&lt;/em&gt;을 의미한다. 즉, rank는 &lt;em&gt;“행렬에 의해 결정되는 특정한 공간의 차원”&lt;/em&gt;이라고 볼 수 있다.
행렬이 없는 차원은 여러가지 의미가 있을 수 있지만, 우리가 보통 아는 그 차원을 생각하면 된다. 직선 $\mathbb{R}^1$은 1차원, 평면 $\mathbb{R}^2$은 2차원, 3차원 공간 $\mathbb{R}^3$은 3차원인 것이다.
그리고 rank는 이러한 일반적인 차원 개념 속에서 “특정 행렬이 실제로 만들어낼 수 있는 방향(독립적인 열/행 벡터)의 수”를 말한다. 따라서 rank는 차원의 특수한 경우라고 이해할 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;matrix-matrix-multiplication-mathbfab&quot;&gt;Matrix-matrix multiplication $\mathbf{AB}$&lt;/h2&gt;

&lt;p&gt;행렬의 곱셈을 설명하는데는 두 가지 관점이 있다.
한 가지는 Inner product 관점이며, 이는 행렬 원소 하나인 &lt;strong&gt;scalar&lt;/strong&gt;를 계산하는데 초점을 둔다.
또 한 가지는 Outer product관점인데, 이는 행렬을 원소가 아니라 여러 &lt;strong&gt;rank one matrix&lt;/strong&gt;의 합으로 본다.&lt;/p&gt;

&lt;p&gt;보통 inner product 관점으로 많이 설명한다. 계산절차를 설명하기도 쉽고, 행렬곱셈을 두 행렬의 기하학적인 정렬이라고 이해하기도 쉽다.
하지만 outer product관점이 머신러닝 관점에서 더 유용하다. 왜냐하면 행렬을 여러 기본 성분(rank one matrix)으로 쪼개서 보는 관점이다.
특히 이번 글에서 설명하는 SVD는 이 중에서 중요한 성분만 추출하는 것이기 때문에 outer product관점으로 접근하는것이 훨씬 이해하기가 쉽다.&lt;/p&gt;

&lt;h3 id=&quot;inner-product-perspective-row--column&quot;&gt;Inner product perspective (Row × Column)&lt;/h3&gt;

&lt;p&gt;일반적으로 matrix끼리의 곱셈은 내적(inner product)로 설명을 한다.&lt;/p&gt;

&lt;p&gt;$\mathbf{AB}$에서 $\mathbf{A}$의 &lt;strong&gt;row&lt;/strong&gt;와 $\mathbf{B}$의 &lt;strong&gt;column&lt;/strong&gt;을 곱한다는 관점이다.&lt;/p&gt;

&lt;p&gt;$\mathbf{A}$의 2번쨰 row와 $\mathbf{B}$의 3번쨰 column vector의 내적이 $\mathbf{C}=\mathbf{AB}$의 원소 $c_{23}$이 되는 것이다.&lt;/p&gt;

\[\begin{bmatrix}
  \cdot    &amp;amp; \cdot    &amp;amp; \cdot \\
  a_{21}   &amp;amp; a_{22}   &amp;amp; a_{23} \\
  \cdot    &amp;amp; \cdot    &amp;amp; \cdot
\end{bmatrix}
\begin{bmatrix}
  \cdot &amp;amp; \cdot &amp;amp; b_{13} \\
  \cdot &amp;amp; \cdot &amp;amp; b_{23} \\
  \cdot &amp;amp; \cdot &amp;amp; b_{33}
\end{bmatrix} =
\begin{bmatrix}
  \cdot &amp;amp; \cdot &amp;amp; \cdot \\
  \cdot &amp;amp; \cdot &amp;amp; c_{23} \\
  \cdot &amp;amp; \cdot &amp;amp; \cdot
\end{bmatrix}\]

&lt;p&gt;이고,&lt;/p&gt;

\[\begin{equation*}
c_{23} = a_{21} b_{13} + a_{22} b_{23} + a_{23} b_{33} = \sum_{k=1}^{3} a_{2k} b_{k3}
\end{equation*}\]

&lt;p&gt;즉
\(\begin{equation}
c_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}
\end{equation}\)&lt;/p&gt;

&lt;h3 id=&quot;outer-product-perspective-column--row&quot;&gt;Outer product perspective (Column × Row)&lt;/h3&gt;

&lt;p&gt;하지만 행렬 곱셈 $\mathbf{AB}$를 포현하는 다른 방식으로는 Outer product의 합으로 설명할 수 있는 방법이 있다.
이는 $\mathbf{A}$의 &lt;strong&gt;열(column)&lt;/strong&gt;과 $\mathbf{B}$의 &lt;strong&gt;행(row)&lt;/strong&gt;을 곱하는 관점이다.&lt;/p&gt;

\[\begin{equation}
AB =
\begin{bmatrix}
\,|\, &amp;amp; &amp;amp; \,|\, \\
a_{1} &amp;amp; \cdots &amp;amp; a_{n} \\
\,|\, &amp;amp; &amp;amp; \,|\,
\end{bmatrix}
\begin{bmatrix}
- b_1^{*} - \\
\vdots \\
- b_n^{*} -
\end{bmatrix}
= a_{1} b_{1}^{*} + a_{2} b_{2}^{*} + \cdots + a_{n} b_{n}^{*}
\end{equation}\]

&lt;p&gt;이 때, 개별적인 column $\times$ row 방식은 vector가 아니라 &lt;strong&gt;rank-one matrix&lt;/strong&gt;를 만들고 rank-one matrix의 합이 $\mathbf{AB}$를 구성한다.&lt;/p&gt;

&lt;p&gt;$\mathbf{A}$의 column vector 하나를 $u$, $\mathbf{B}$의 row vector 하나를 $v^\mathsf{T}$라고 할 때,
모든 열(column)은 $u$의 배수이고, 모든 행(row)는 $v^\mathsf{T}$의 배수이다.&lt;/p&gt;

\[\begin{equation}
uv^{\mathsf{T}} = \begin{bmatrix}
  2 \\
  2 \\
  1
\end{bmatrix}
\begin{bmatrix}
  3 &amp;amp; 4 &amp;amp; 6
\end{bmatrix} =
\begin{bmatrix}
  6 &amp;amp; 8 &amp;amp; 12 \\
  6 &amp;amp; 8 &amp;amp; 12 \\
  3 &amp;amp; 4 &amp;amp; 6
\end{bmatrix}
\end{equation}\]

\[uv^{\mathsf{T}} = \begin{bmatrix}
  6 &amp;amp; 8 &amp;amp; 12 \\
  6 &amp;amp; 8 &amp;amp; 12 \\
  3 &amp;amp; 4 &amp;amp; 6
\end{bmatrix}\]

&lt;p&gt;이것이 rank-one matrix의 특징이고, 이 rank-one matrix는 행렬의 기저 역할을 한다.
모든 nonzero 벡터의 외적 $uv^{\mathsf{T}}$는 rank one이며 모든 행렬의 &lt;strong&gt;building block&lt;/strong&gt;라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;이걸 확장하면 행렬 곱셈은 여러 개의 외적 = rank one matrix = building block들의 합이라고 볼 수 있다.&lt;/p&gt;

&lt;h4 id=&quot;geometric-interpretation-of-outer-product-perspective&quot;&gt;Geometric interpretation of outer product perspective&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Rank-one rank과 기본 공간 (Rank-one 행렬의 row space = $v$ 방향 (line through v))
    &lt;ul&gt;
      &lt;li&gt;하나의 rank-one matrix $\mathbf{u}\mathbf{v}^{\mathsf{T}}$은 한 쌍의 기본 공간을 정의한다.&lt;/li&gt;
      &lt;li&gt;Row Space: 이 행렬의 Row Space는 입력 방향을 결정하는 벡터 &lt;strong&gt;v&lt;/strong&gt;가 만드는 1차원 공간(선)이다.&lt;/li&gt;
      &lt;li&gt;Column Space: 이 행렬의 Column Space는 출력 방향을 결정하는 벡터 &lt;strong&gt;u&lt;/strong&gt;가 만드는 1차원 공간(선)이다..&lt;/li&gt;
      &lt;li&gt;즉, 이 행렬은 입력을 v 방향으로 ‘측정’하여, 그 결과를 u 방향으로 ‘사상(mapping)’시키는 가장 단순한 변환기이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;여러 rank-one의 조합 → row space의 span이 확장
    &lt;ul&gt;
      &lt;li&gt;모든 행렬 A는 이러한 Rank-one 행렬(벽돌)들의 합으로 표현될 수 있다. $\mathbf{A} = \Sigma \sigma_i u_i v^{\mathsf{T}}_i$가 된다.&lt;/li&gt;
      &lt;li&gt;즉, 각 rank-one 행렬 $u_i v^{\mathsf{T}}_i$는 특정 입력방향 $v_i$에 반응하여 특정 출력방향 $u_i$로 결과를 내보낸다.&lt;/li&gt;
      &lt;li&gt;Row Space
        &lt;ul&gt;
          &lt;li&gt;행렬 $\mathbf{A}$의 row space는 입력방향 벡터 $v_i$가 모여서 만드는 공간(span)이다.&lt;/li&gt;
          &lt;li&gt;이 공간은 행렬 $\mathbf{A}$가 &lt;em&gt;의미 있게&lt;/em&gt; 반응할 수 있는 모든 입력 벡터의 집합이다.&lt;/li&gt;
          &lt;li&gt;만약 어떤 벡터가 이 Row space에 포함되어 있지 않다면(직교한다면), 행렬 A는 그 벡터를 영벡터($\mathbf{0}$)로 변환한다.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Column Space
        &lt;ul&gt;
          &lt;li&gt;행렬 $\mathbf{A}$의 column space는 출력방향 벡터 $u_i$가 모여서 만드는 공간(span)이다.&lt;/li&gt;
          &lt;li&gt;이 공간은 행렬 $\mathbf{A}$가 만들어낼 수 있는 모든 가능한 결과 벡터의 집합이다.&lt;/li&gt;
          &lt;li&gt;어떤 입력 벡터 $\mathbf{x}$를 사용하든, 그 결과인 $A\mathbf{x}$는 반드시 이 column space 안에 존재하게 된다.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;from-matrix-factorization-to-svd&quot;&gt;From Matrix Factorization to SVD&lt;/h2&gt;

&lt;p&gt;머신러닝에서 중요한것은 어떤 것을 feature 혹은 signal로 볼 것인가이다.
노이즈를 제거하고 핵심 정보를 추출하기 위해 &lt;strong&gt;행렬(matrix)을 다양한 방식으로 분해(factorization)&lt;/strong&gt;할 수 있다.&lt;/p&gt;

&lt;p&gt;어떤 matrix $\mathbf{A}$를 $\mathbf{CR}$로 분해한다고 가정하자.
이 때 $\mathbf{C}$와 $\mathbf{R}$을 구성하는 방식은 다양하며, LU, QR, 고유분해(diagonalization) 등의 기법이 있고, 이 모든 것을 일반화한 가장 강력한 방식이 바로 SVD이다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;LU Decomposition (Elimination)
    &lt;ul&gt;
      &lt;li&gt;$\mathbf{A} = \mathbf{LU}$&lt;/li&gt;
      &lt;li&gt;$\mathbf{A}$를 Gauss elimination으로 행(row)을 조합해 upper triangular matrix $\mathbf{U}$를 만들고, 소거 과정에서의 계수(coefficient)들을 모아서 lower triangular matrix $\mathbf{L}$를 생성한다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;QR Decomposition (Orthogonalization)
    &lt;ul&gt;
      &lt;li&gt;$\mathbf{A} = \mathbf{QR}$&lt;/li&gt;
      &lt;li&gt;Gram-Schmidt변환을 통해 열(column)을 직교화(Orthogonalizing)한다.&lt;/li&gt;
      &lt;li&gt;$\mathbf{Q}$는 orthonormal columns ( $Q^{\mathsf{T}}Q=I$ ), $\mathbf{R}$은 upper triangular matrix이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Diagonalization
    &lt;ul&gt;
      &lt;li&gt;$\mathbf{A} = \mathbf{X \Lambda} \mathbf{X}^{-1}$&lt;/li&gt;
      &lt;li&gt;$\mathbf{A}$는 Square matrix이고 diagonalize한다면, eigenvector matrix $\mathbf{X}$와 eigenvalue로 이루어진 diagonal matrix인 $\mathbf{\Lambda}$로 분해할 수 있다.&lt;/li&gt;
      &lt;li&gt;단, $\mathbf{X}$는 orthogonal matrix가 아닐 수 있고, 모든 행렬에서 diagonalize가 가능한 것은 아니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Orthogonal Diagonalization (for symmetric matrices)
    &lt;ul&gt;
      &lt;li&gt;$\mathbf{S} = \mathbf{Q \Lambda} \mathbf{Q}^{\mathsf{T}}$&lt;/li&gt;
      &lt;li&gt;$\mathbf{S}$는 Symmetric matrix ($\mathbf{S}=\mathbf{S}^\mathsf{T}$)이면, 항상 orthogonal diagnoalization이 가능하다.&lt;/li&gt;
      &lt;li&gt;$\mathbf{Q}$는 $\mathbf{S}$의 Orthonormal eigenvectors로 이루어진 orthonormal matrix이고, $\Lambda$는 $\mathbf{S}$의 eigenvalue들인 $\lambda_1, \dots, \lambda_n$로 이루어진 diagonal matrix이다.&lt;/li&gt;
      &lt;li&gt;이는 3. Diagonalization의 특수한 경우로, symmetric matrix에서는 eigenvector들이 orthogonal하므로, $\mathbf{X}^{-1}$ 대신 $\mathbf{Q}^\mathsf{T}$가 사용된다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;SVD
    &lt;ul&gt;
      &lt;li&gt;$\mathbf{A} = \mathbf{U \Sigma} \mathbf{V}^{\mathsf{T}}$&lt;/li&gt;
      &lt;li&gt;모든 (square matrix &amp;amp; non-square matrix) 행렬 $\mathbf{A}$에서 항상 성립한다.&lt;/li&gt;
      &lt;li&gt;$\mathbf{U}$와 $\mathbf{V}$는 orthonormal singular vectors들로 이루어진 matrix이고, $\mathbf{\Sigma}$는 singular values들로 이루어진 diagonal matrix이다.&lt;/li&gt;
      &lt;li&gt;SVD는 LU, QR, orthogonal decomposition을 모두 일반화한 경우이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;definition-of-svd&quot;&gt;Definition of SVD&lt;/h2&gt;

&lt;p&gt;위에서 본 것처럼 SVD는 아무 Matrix $\mathbf{A}$를 다음과 같이 분해할 수 있음을 뜻한다.&lt;/p&gt;

&lt;p&gt;$\mathbf{A} = \mathbf{U \Sigma} \mathbf{V}^{\mathsf{T}}$&lt;/p&gt;

&lt;p&gt;이 때 각 행렬은 다음과 같은 특성을 지닌다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\mathbf{U}$: $\mathbf{A}$의 column space를 orthonormal basis(orthornomal singular vectors)로 표현&lt;/li&gt;
  &lt;li&gt;$\mathbf{\Sigma}$: Singular value로 이루어진 diagonal matrix&lt;/li&gt;
  &lt;li&gt;$\mathbf{V}$: $\mathbf{A}$의 row space를 orthonormal basis(orthornomal singular vectors)로 표현&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SVD(Singular Value Decomposition)는 모든 행렬을 회전과 스케일링으로 ‘쪼개서’ 이해할 수 있게 해주는 강력한 도구이다.
이는 데이터 압축, 노이즈 제거 등 다양한 분야에서 쓰이기 떄문에 기하학적 해석을 이해해두면 여러모로 유용하다.&lt;/p&gt;

&lt;h3 id=&quot;matrix-linear-transformation&quot;&gt;Matrix Linear Transformation&lt;/h3&gt;

&lt;p&gt;2x2 행렬은 평면의 점을 선형 변환(linear transformation)하는 연산자로 볼 수 있다.
(A) 늘이기(stretching), (B) 줄이기 (compression), ((C) 회전(rotation), (D) 반사(reflection), (E) 전단(shear)으로 나타낼 수 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-SVD-in-detail/01-matrix-linear-transformations.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://gregorygundersen.com/blog/2018/12/10/svd/&amp;quot;&amp;gt;
Our original square under different types of transformations: (A) stretched, (B) compressed, (C) rotated, (D) reflected or flipped, and (E) sheared&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-SVD-in-detail/01-matrix-linear-transformations.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://gregorygundersen.com/blog/2018/12/10/svd/&amp;quot;&amp;gt;
Our original square under different types of transformations: (A) stretched, (B) compressed, (C) rotated, (D) reflected or flipped, and (E) sheared&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://gregorygundersen.com/blog/2018/12/10/svd/&quot;&gt;
Our original square under different types of transformations: (A) stretched, (B) compressed, (C) rotated, (D) reflected or flipped, and (E) sheared&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;만약 선형이 아니라면, 다음 그림에서 (A)는 (C)처럼 변할 것이다. ((B)는 선형 변환)&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-SVD-in-detail/02-nonlinear-transformation.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://gregorygundersen.com/blog/2018/12/10/svd/&amp;quot;&amp;gt;
(A) Our original square under a linear transformation M (B) and a nonlinear transformation M′(C).&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-SVD-in-detail/02-nonlinear-transformation.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://gregorygundersen.com/blog/2018/12/10/svd/&amp;quot;&amp;gt;
(A) Our original square under a linear transformation M (B) and a nonlinear transformation M′(C).&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://gregorygundersen.com/blog/2018/12/10/svd/&quot;&gt;
(A) Our original square under a linear transformation M (B) and a nonlinear transformation M′(C).&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;geometrical-interpretations-of-svd&quot;&gt;Geometrical interpretations of SVD&lt;/h3&gt;

&lt;p&gt;여기서
(A) Streching (늘리기): 축 방향으로 길이만 바꿈
(B) Compression (줄이기): 축 방향으로 길이만 바꿈
(C) Rotation (회전): 방향만 바꿈 (길이 보존)
(D) Reflection or Flip (반사 혹은 뒤집기): 축 방향으로 방향만 반전
(E) 전단(shear): 한 축을 고정하고 다른 축을 평행이동시켜 모양을 “기울임”&lt;/p&gt;

&lt;p&gt;(A)와 (B)는 결국 크기의 문제이지 길이를 바꾸는 “Scaling”관점에서 하나고,
(C)와 (D)또한 방향을 바꾼다는 점에서 같은 속성을 지닌다.&lt;/p&gt;

&lt;p&gt;(E) 전단(shear)는 직각 구조(Orthogonality)를 깨뜨려 길이와 각도를 바꾸기 때문에, 단순한 회전이나 스케일링보다 다루기 까다롭다.
하지만 &lt;strong&gt;모든 Shear 변환도 다음과 같이 Rotation + Scaling + Rotation의 조합으로 표현&lt;/strong&gt;할 수 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-SVD-in-detail/03-SVD-geometric-interpretation.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://en.wikipedia.org/wiki/Singular_value_decomposition&amp;quot;&amp;gt;
Illustration of the singular value decomposition UΣV⁎ of a real 2 × 2 matrix M.&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-SVD-in-detail/03-SVD-geometric-interpretation.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://en.wikipedia.org/wiki/Singular_value_decomposition&amp;quot;&amp;gt;
Illustration of the singular value decomposition UΣV⁎ of a real 2 × 2 matrix M.&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://en.wikipedia.org/wiki/Singular_value_decomposition&quot;&gt;
Illustration of the singular value decomposition UΣV⁎ of a real 2 × 2 matrix M.&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;요약하자면, 모든 행렬(matrix)는 선형 변환이라고 할 수 있고, 이는 Scaling과 Rotation의 조합으로 표현될 수 있다.
그리고 SVD는 임의의 행렬을 ‘회전-스케일링-회전’의 조합으로 분해하는 강력한 방법이다.&lt;/p&gt;

&lt;h3 id=&quot;why-singular&quot;&gt;Why “Singular”?&lt;/h3&gt;

&lt;p&gt;기하학적 해석을 통해 SVD는 &lt;strong&gt;회전($\mathbf{V}^\mathsf{T}$) ➜ 확대/축소($\Sigma$) ➜ 회전($\mathbf{U}$)&lt;/strong&gt;의 세 단계로 분해된다는 것을 알 수 있었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Singular value ($\sigma_i$): $\Sigma$ 행렬의 대각 성분으로, 각 축 방향으로 얼마나 확대 또는 축소되는지를 나타내는 &lt;strong&gt;‘배율’&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Right singular vectors ($v_i$): 확대/축소되기 전의 입력 벡터 방향(축)&lt;/li&gt;
  &lt;li&gt;Left singular vectors ($u_i$): 확대/축소되기 전의 입력 벡터 방향(축)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;만약 Singular value $\sigma_i$가 $0$이라면, 변환 배율이 0이라는 소리이고, &lt;strong&gt;해당 축 방향의 모든 정보는 소멸된다는 것을 의미&lt;/strong&gt;한다.
즉 3차원이었던 정보가 2차원 혹은 1차원으로 변환되게 된다.&lt;/p&gt;

&lt;p&gt;이 경우 행렬은 non-intertible 상태가 되며 singular point가 생기기 때문에 &lt;strong&gt;SINGULAR&lt;/strong&gt;이라는 명칭을 붙였다.&lt;/p&gt;

&lt;p&gt;만약 총 $n$개 singular values가 있고, $r$개가 nonzero singular values라고 할 때,
이들을 다음과 같이 내림차순으로 정렬한다고 하자.&lt;/p&gt;

\[\begin{align*}
\sigma_1 &amp;amp;\geq \sigma_2 \geq \cdots \sigma_r \geq 0 \\
\sigma_{r+1} &amp;amp;= \sigma_{r+2} = \cdots \sigma_n = 0
\end{align*}\]

&lt;p&gt;그러면 다음과 같이 $\mathbf{A}v = \sigma u$를 만족한다.&lt;/p&gt;

\[\begin{align*}
\mathbf{A}v_1 &amp;amp;= \sigma_1 u_1 \\
\mathbf{A}v_2 &amp;amp;= \sigma_1 u_2 \\
&amp;amp;\cdots \\
\mathbf{A}v_r &amp;amp;= \sigma_1 u_r \\
\mathbf{A}v_{r+1} &amp;amp;= 0 \\
\mathbf{A}v_{r+2} &amp;amp;= 0 \\
&amp;amp;\cdots \\
\mathbf{A}v_{n} &amp;amp;= 0 \\
\end{align*}\]

&lt;p&gt;Matrix form으로는 다음과 같다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-10-08-SVD-in-detail/04-schematic-SVD.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://gregorygundersen.com/blog/2018/12/10/svd/&amp;quot;&amp;gt;
The canonical diagram of the SVD decomposition of a matrix M&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-10-08-SVD-in-detail/04-schematic-SVD.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://gregorygundersen.com/blog/2018/12/10/svd/&amp;quot;&amp;gt;
The canonical diagram of the SVD decomposition of a matrix M&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://gregorygundersen.com/blog/2018/12/10/svd/&quot;&gt;
The canonical diagram of the SVD decomposition of a matrix M&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;참고로 $\mathbf{V}$와 $\mathbf{U}$는 orthogonal하므로,
$\mathbf{V}^\mathsf{T}=\mathbf{V}^{-1}$와 $\mathbf{U}^\mathsf{T}=\mathbf{U}^{-1}$를 만족한다.&lt;/p&gt;

\[\begin{align}
\mathbf{AV_r} &amp;amp;= \mathbf{U_r} \Sigma_r \qquad  \\
\overset{m \times n}{\left[ A \right]} \overset{n \times n}{\begin{bmatrix} \, v_1 \; \Big| \; \dots \; \Big| \; v_n \, \end{bmatrix}}
&amp;amp;= \overset{m \times m}{\begin{bmatrix} \, u_1 \; \Big| \; \dots \; \Big| \; u_m \, \end{bmatrix}}
\overset{m \times n}{
\left[
\begin{array}{ccc|c}
\sigma_1 &amp;amp;        &amp;amp;        &amp;amp; 0 \\
         &amp;amp; \ddots &amp;amp;        &amp;amp; \vdots \\
         &amp;amp;        &amp;amp; \sigma_r &amp;amp; 0 \\
\hline
0        &amp;amp; \cdots &amp;amp; 0        &amp;amp; 0
\end{array}
\right]
}
\end{align}\]

&lt;p&gt;여기서는 $\Sigma$가 square matrix라는 보장이 없지만,
$\Sigma$에서 0부분을 전부 제거하면 square matrix가 되고 rank $r$에 대해 The reduced form of SVD가 된다.&lt;/p&gt;

\[\begin{align}
\mathbf{AV} &amp;amp;= \mathbf{U} \Sigma \qquad  \\
\overset{m \times n}{\left[ A \right]} \overset{n \times r}{\begin{bmatrix} \, v_1 \; \Big| \; \dots \; \Big| \; v_r \, \end{bmatrix}}
&amp;amp;= \overset{m \times r}{\begin{bmatrix} \, u_1 \; \Big| \; \dots \; \Big| \; u_r \, \end{bmatrix}}
\overset{r \times r}{
\left[
\begin{array}{ccc}
\sigma_1 &amp;amp;        &amp;amp;          \\
         &amp;amp; \ddots &amp;amp;          \\
         &amp;amp;        &amp;amp; \sigma_r \\
\end{array}
\right]
}
\end{align}\]

&lt;h2 id=&quot;proof-of-svd-existence&quot;&gt;Proof of SVD Existence&lt;/h2&gt;

&lt;p&gt;SVD는 과연 모든 matrix에 존재하는가?에 대한 증명을 우선하고자 한다.&lt;/p&gt;

&lt;p&gt;우선 Symmetric matrix $\mathbf{S} \in \mathcal{R}^{n \times n}$ 에 대해서 위에서 살펴봤던 Orthogonal Diagonalization (for symmetric matrices)이 성립한다.
실은 이것을 Spectrum theorem이라고 한다.
\(\mathbf{S} = \mathbf{Q \Lambda} \mathbf{Q}^{\mathsf{T}}\)&lt;/p&gt;

&lt;p&gt;이 증명의 큰 틀은 임의의 $\mathbf{M} \in \mathbb{R}^{m \times n}$를 spectrum theorem을 적용하여 SVD form을 구하는 것이다.&lt;/p&gt;

&lt;p&gt;$\mathbf{M}$의 rank을 $r$이라고 하면,
$\mathbf{M}^\mathsf{T} \mathbf{M}$는 symmetric matrix가 되고, positive semidefinite matrix $(x^\mathsf{T} A x \geq 0)$이다.&lt;/p&gt;

&lt;p&gt;참고로 Positive semidefinite matrix은 다음과 같이 quadaratic form에 의해 증명된다.
\(\begin{align}
x^\mathsf{T} \mathbf{M}^\mathsf{T} \mathbf{M} x = (Mx)^\mathsf{T} (Sx) = \| Mx \|^2 \geq  0
\end{align}\)&lt;/p&gt;

&lt;p&gt;Positive semidefinite matrix는 diagnoalize되며 spectrum theorem에 의해 eigenvalue decomposition이 가능하다.&lt;/p&gt;

\[\mathbf{\mathbf{M}^\mathsf{T} \mathbf{M}} = \mathbf{\mathbf{V} \mathbf{\Lambda}} \mathbf{\mathbf{V}}^{\mathsf{T}} = \sum_{i=1}^n \lambda_i v_i v_i^{\mathsf{T}} = = \sum_{i=1}^n (\sigma_i)^2 v_i v_i^{\mathsf{T}}\]

&lt;p&gt;이 때, $\mathbf{V}$의 컬럼은 $\mathbf{M}^\mathsf{T} \mathbf{M}$의 eigenvectors들이며 orthonormal matrix이고,
$r \leq n$일 때, $r = \textrm{rank}(\mathbf{M}) = \textrm{rank}(\mathbf{M}^\mathsf{T} \mathbf{M})$이다.
또한, $\sigma_i$는 singular value라고 정의하면 $\sigma_i = \sqrt{\lambda_i}$를 만족한다.&lt;/p&gt;

&lt;p&gt;$i$번째의 eigenvector는 다음을 만족한다. (Eigenvalue, Eigenvector 정의: $\mathbf{A} x = \lambda x$)&lt;/p&gt;

\[\mathbf{M}^\mathsf{T} \mathbf{M} v_i = (\sigma_i)^2 v_i\]

&lt;p&gt;만약 full rank matrix라면 ($\sigma_i &amp;gt; 0$ for all $i$) 새롭게 $u_i$를 다음과 같이 정의할 수 있다.
\(u_i = \dfrac{\mathbf{M} v_i}{\sigma_i}\)&lt;/p&gt;

&lt;p&gt;$u_i$는 $\mathbf{M}\mathbf{M}^\mathsf{T}$의 eigenvector인데 이는 다음 식으로 증명될 수 있다.&lt;/p&gt;

\[\begin{align}
\mathbf{M}\mathbf{M}^\mathsf{T} u_i &amp;amp;= \mathbf{M}\mathbf{M}^\mathsf{T} \left( \dfrac{\mathbf{M} v_i}{\sigma_i} \right) \\
&amp;amp;= \mathbf{M} \left(\mathbf{M}^\mathsf{T} \mathbf{M} \right) \dfrac{v_i}{\sigma_i}  \\
&amp;amp;= \mathbf{M} \left( \sigma_i \right)^2 v_i \dfrac{1}{\sigma_i}  \\
&amp;amp;= \left( \sigma_i \right)^2 \left(\mathbf{M}  v_i \dfrac{1}{\sigma_i} \right)  \\
&amp;amp;= \left( \sigma_i \right)^2 u_i \\
\end{align}\]

&lt;p&gt;즉 정리하자면&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\mathbf{V} \in \mathbb{R}^{n \times n}$는 $\mathbf{M}^\mathsf{T} \mathbf{M}$의 orthonormal eigenvectors&lt;/li&gt;
  &lt;li&gt;$\mathbf{U} \in \mathbb{R}^{m \times m}$는 $\mathbf{M} \mathbf{M}^\mathsf{T}$의 orthonormal eigenvectors&lt;/li&gt;
  &lt;li&gt;$\sigma_i^2 (i \leq r)$는 $\mathbf{M}^\mathsf{T} \mathbf{M}$와 $\mathbf{M} \mathbf{M}^\mathsf{T}$의 nonzero eigenvalues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;따라서 matrix form으로 정리하면&lt;/p&gt;

\[\begin{align}
\mathbf{U} &amp;amp;= \mathbf{M V \Sigma^{-1}} \\
\mathbf{U} \Sigma &amp;amp;= \mathbf{M V} \\
\mathbf{U} \Sigma \mathbf{V}^{-1} &amp;amp;= \mathbf{M} \\
\mathbf{U} \Sigma \mathbf{V}^{\mathsf{T}} &amp;amp;= \mathbf{M} \\
\mathbf{M} &amp;amp;= \mathbf{U} \Sigma \mathbf{V}^{\mathsf{T}}
\end{align}\]

&lt;p&gt;이 떄, singular value가 0인 경우는 일단 제외하고, 나중에 0을 제외한 full rank에 대해서 SVD를 구한다음 0을 추가한다고 생각하면 된다.&lt;/p&gt;

&lt;p&gt;이 증명은 &lt;a href=&quot;https://gregorygundersen.com/blog/2018/12/20/svd-proof/&quot;&gt;이 글의 증명&lt;/a&gt;을 충실하게 따랐다. 개인적으로는 가장 직관적이고 쉽게 증명한 글이라고 생각한다.&lt;/p&gt;

&lt;h2 id=&quot;svd의-특성들&quot;&gt;SVD의 특성들&lt;/h2&gt;

&lt;h3 id=&quot;spectral-norm-of-a-matrix&quot;&gt;spectral norm of a matrix&lt;/h3&gt;

&lt;p&gt;어떤 임의의 matrix $\mathbf{A} \in \mathbf{R}^{m \times n}$의 SVD를 $\mathbf{A} = \mathbf{U \Sigma V^{\mathsf{T}}}$에서 $\sigma_1 \geq \sigma_2 \geq \cdots$라 하자.&lt;/p&gt;

&lt;p&gt;그러면 다음과 같이 spectral norm $\left( \dfrac{| \mathbf{A}x |}{| x |} \right)$ 최대는 $x=v_1$ (첫번쨰 right singular vector)에서 달성된다.&lt;/p&gt;

\[\begin{align}
\max_{x\neq 0} \dfrac{\| \mathbf{A}x \|}{\| x \|} = \sigma_1
\end{align}\]

&lt;p&gt;증명은 다음과 같다.&lt;/p&gt;

\[\dfrac{\| \mathbf{A}x \|^2}{\| x \|^2} = \dfrac{x^{\mathsf{T}} \mathbf{A}^{\mathsf{T}} \mathbf{A} x}{x^{\mathsf{T}} x} = \dfrac{x^{\mathsf{T}} \mathbf{S} x}{x^{\mathsf{T}} x}\]

&lt;p&gt;이 때 rayleigh quotient $ \dfrac{x^{\mathsf{T}} \mathbf{S} x}{x^{\mathsf{T}} x} $는 $x_1, \dots, x_n$에 의존하므로 각각의 원소에 대해서 다음과 같이 미분할 수 있다.&lt;/p&gt;

\[\begin{align*}
\dfrac{\partial}{\partial x_i} \left( x^{\mathsf{T}} x \right) &amp;amp;= \dfrac{\partial}{\partial x_i} (x_1^2 + \cdots + x_i^2 + \cdots + x_n^2) = 2(x)_i \\
\dfrac{\partial}{\partial x_i} (x^{\mathsf{T}} \mathbf{S} x) &amp;amp;= \dfrac{\partial}{\partial x_i} \left( \sum_i \sum_j S_{ij} x_i x_j \right) = 2 \sum_j S_{ij} x_j = 2 (\mathbf{S}x)
\end{align*}\]

&lt;p&gt;이 정보를 가지고 $i=1,\dots, n$에 대해 $\dfrac{x^{\mathsf{T}} \mathbf{S} x}{x^{\mathsf{T}} x} $를 미분하고 $x^{\mathsf{T}} x$로 양분을 나누면 다음과 같이 정리된다.
\(\begin{align}
(x^{\mathsf{T}} x) \dfrac{\partial}{\partial x_i}(x^{\mathsf{T}} \mathbf{S} x) - (x^{\mathsf{T}} \mathbf{S} x) \dfrac{\partial}{\partial x_i} \left( x^{\mathsf{T}} x \right) &amp;amp;= 0 \\
(x^{\mathsf{T}} x) (2 (\mathbf{S}x)_i) - (x^{\mathsf{T}} \mathbf{S} x)  2(x)_i &amp;amp;= 0 \\
2 \left( (x^{\mathsf{T}} x) \mathbf{S}x - (x^{\mathsf{T}} \mathbf{S} x)  x_i \right) &amp;amp;= 0 \\
(x^{\mathsf{T}} x) \mathbf{S}x &amp;amp;= (x^{\mathsf{T}} \mathbf{S} x) x \\
 \mathbf{S}x &amp;amp;= \dfrac{(x^{\mathsf{T}} \mathbf{S} x)}{(x^{\mathsf{T}} x)} x \\
 \mathbf{S}x &amp;amp;= \lambda x
\end{align}\)&lt;/p&gt;

&lt;p&gt;이 때 $\lambda := \dfrac{(x^{\mathsf{T}} \mathbf{S} x)}{(x^{\mathsf{T}} x)}$이다.&lt;/p&gt;

&lt;p&gt;결국 $\mathbf{S} = \mathbf{A^{\mathsf{T}}A}$의 eigenvalue를 찾는것으로 문제가 좁혀졌고, 이 때의 eigenvalue는 $\lambda_1= \sigma_1^2$이며, eigenvector는 $x=v_1$이다. 첫번쨰 singular value를 구한셈이다.&lt;/p&gt;

&lt;p&gt;두번째 singular value부터는 $\left( \dfrac{| \mathbf{A}x |}{| x |} \right)$ 최대를 구하는 같은 문제에 $v_1^{\mathsf{T}} x = 0$ 조건을 넣어서 풀면 된다. 이는 Langrange multiplier에 제약조건($g(x)$)을 넣어서로 풀 수 있다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}(x_1, \dots, x_n, \lambda) = f(x_1, \dots, x_n) - \lambda (g(x_1, \dots, x_n) - c)
\end{align}\]

&lt;h3 id=&quot;singular-vector-of-mathbfamathsft&quot;&gt;Singular vector of $\mathbf{A}^\mathsf{T}$&lt;/h3&gt;

&lt;p&gt;SVD는 $\mathbf{A} = \mathbf{U \Sigma V^{\mathsf{T}}}$를 통해 $v$의 row space(입력)를 $u$의 column space(출력)으로 연결시켜주는 역할을 한다.
$\mathbf{A}^\mathsf{T} = \mathbf{V \Sigma^{\mathsf{T}} U^{\mathsf{T}}}$에서는 반대로 $u$의 row space를 $v$의 column space로 연결시킨다.&lt;/p&gt;

&lt;h3 id=&quot;svd-of-symmetric-block-matrix&quot;&gt;SVD of symmetric block matrix&lt;/h3&gt;

&lt;p&gt;\(\mathbf{S} = \begin{bmatrix}
  0 &amp;amp; \mathbf{A} \\
  \mathbf{A}^{\mathsf{T}} &amp;amp; 0
\end{bmatrix}\)
는 $r$개의 pair positive / negative eigenvalue를 가지게 되고 다음과 같은 $(m+n,)$의 크기를 가지는 eigenvector들을 가지게 된다.
\(\begin{bmatrix}
u_k \\
v_k
\end{bmatrix}, \begin{bmatrix}
-u_k \\
v_k
\end{bmatrix}\)&lt;/p&gt;

&lt;h3 id=&quot;ab-and-ba&quot;&gt;AB and BA&lt;/h3&gt;

&lt;p&gt;$\mathbf{A} \in \mathbb{R}^{m \times n}, \mathbf{B} \in \mathbb{R}^{n \times m}$이면,
$\mathbf{AB}$와 $\mathbf{BA}$는 같은 eigenvalue를 가지게 된다.
증명은 매우 심플한데, $ABx = \lambda x, BAB x = \lambda Bx,BA (Bx) = \lambda (Bx) $이기 때문에 $Bx$를 하나의 vector $y$라고 생각하면 eigenvalue의 정의에 따라 증명된다. $Bx$가 eigenvector가 되는건 덤이다.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://gregorygundersen.com/blog/2018/10/24/matrices/&quot;&gt;A Geometrical Understanding of Matrices&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gregorygundersen.com/blog/2018/12/10/svd/&quot;&gt;Singular Value Decomposition as Simply as Possible&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jeremykun.com/2016/04/18/singular-value-decomposition-part-1-perspectives-on-linear-algebra/&quot;&gt;Singular Value Decomposition Part 1: Perspectives on Linear Algebra&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kexue.fm/archives/4208&quot;&gt;SVD分解(一)：自编码器与人工智能&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kexue.fm/archives/10407&quot;&gt;低秩近似之路（二）：SVD&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kexue.fm/archives/10878&quot;&gt;SVD的导数&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bvanderlei.github.io/jupyter-guide-to-linear-algebra/Planar_Transformations.html&quot;&gt;Transformations in a Plane&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;strang2019linear&quot;&gt;[1]G. Strang and others, &lt;i&gt;Linear algebra and learning from data&lt;/i&gt;, vol. 4. Wellesley-Cambridge Press Cambridge, 2019.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Attention Mechanism 최적화와 KV Cache 계산</title>
   <link href="https://blog.liam.kim/posts/2025/01/Attention-Mechanism-and-KV-Cache/"/>
   <updated>2025-01-18T01:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2025/01/Attention-Mechanism-and-KV-Cache</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;기존의 SDPA(Scaled Dot Product Attention)를 효율화하는 여러가지 방법이 있다.
대표적으로 &lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt; 에도 나오는
MHA(Multi-Head Attention)부터 시작해서, MQA(Multi-Query Attention), GQA(Grouped-Query Attention), 그리고 MLA(Multi-head Latent Header Attention)에 대해 알아보고 KV Cache가 얼마나 optimize되는지 알아보고자 한다.&lt;/p&gt;

&lt;h2 id=&quot;sdpa-scaled-dot-product-attention&quot;&gt;SDPA (Scaled Dot Product Attention)&lt;/h2&gt;

&lt;p&gt;Attention 메커니즘이야 워낙 유명하고 예전에도 &lt;a href=&quot;https://blog.liam.kim/posts/2024/03/What-Is-K-Q-V-in-Transformer/&quot;&gt;이에 대한 글&lt;/a&gt;을 쓴 적이 있다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(\textrm{batch\_size}\): 배치 사이즈&lt;/li&gt;
  &lt;li&gt;\(\textrm{seq}\): sequence length&lt;/li&gt;
  &lt;li&gt;\(d_{\textrm{model}}\): 모델의 hidden representation size. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden_size&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multi Head를 고려하지 않는다고 가정하자.
Input $X$가 \(\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}}\)일 때,
모델이 실질적으로 훈련하는 weight matrix $W^Q$, $W^K$, $W^V$는 각각 다음과 같은 dimension을 가진다.&lt;/p&gt;

\[W^Q \in \mathbb{R}^{d_{\textrm{model}} \times d_{\textrm{model}}}\]

\[W^K \in \mathbb{R}^{d_{\textrm{model}} \times d_{\textrm{model}}}\]

\[W^V \in \mathbb{R}^{d_{\textrm{model}} \times d_{\textrm{model}}}\]

&lt;p&gt;이는 $Q, K, V$는 BMM(batch matrix-matrix product)을 통해 다음과 같은 dimension을 가짐을 뜻한다.&lt;/p&gt;

\[Q = X W^Q \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}}}\]

\[K = X W^K \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}}}\]

\[V = X W^V \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}}}\]

&lt;p&gt;Attention score \(Q K^\mathsf{T}\)는 다음과 같이 계산되고,&lt;/p&gt;

\[Q \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}}}\]

\[K^T \in \mathbb{R}^{\textrm{batch\_size} \times d_{\textrm{model}} \times \textrm{seq} }\]

\[Q K^\mathsf{T} \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times \textrm{seq} }\]

&lt;p&gt;Attention weight \(\textrm{Softmax}\left(\dfrac{QK^T}{\sqrt{d_k}}\right)\) 또한 \(Q K^\mathsf{T}\)와 같은 dimension을 가진다.&lt;/p&gt;

\[\textrm{Softmax}\left(\dfrac{QK^T}{\sqrt{d_k}}\right) \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times \textrm{seq} }\]

&lt;p&gt;이렇게 구해진 Attention weight \(Q K^\mathsf{T}\)는 일종의 가중치 역할을 하며 이를 \(V\)와 곱해서 Attention output을 생성하게 된다.
Attention output 은 다음과 같다.&lt;/p&gt;

\[\textrm{Attention}(Q,K,V) = \textrm{Softmax}\left(\dfrac{QK^T}{\sqrt{d_k}}\right)V \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}} }\]

&lt;p&gt;이 뒤에 layernorm이나 FC(Fully Connected) Layer등의 이야기는 생략한다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/01-SDPA.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&amp;quot;&amp;gt;Summarizing the self-attention mechanism&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/01-SDPA.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&amp;quot;&amp;gt;Summarizing the self-attention mechanism&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&quot;&gt;Summarizing the self-attention mechanism&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h2 id=&quot;mha-multi-head-attention&quot;&gt;MHA (Multi-Head Attention)&lt;/h2&gt;

&lt;p&gt;MHA를 하는 이유는 “아 다르고 어 다르다”라는 속담을 생각하면 쉽다.
같은 표현이라도 다른 의미로 받아들여질 수 있도록 모델을 학습시키기 위함이다.
MHA를 통해 모델은 입력의 다양한 위치에 대해 더 풍부하게 이해할 수 있게 된다.&lt;/p&gt;

&lt;p&gt;Head를 사용하는 가장 기본적인 방법으로, &lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt;에 나와있는 방법이다.&lt;/p&gt;

&lt;p&gt;추가되는 파라미터는 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(d_{\textrm{head}}\): attention head의 사이즈&lt;/li&gt;
  &lt;li&gt;\(n_{\textrm{head}}\) : Attention head 수 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_attention_heads&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;\(d_{\textrm{model}}\)을 \(n_{\textrm{head}}\)개의 head로 쪼개서 학습시킨다고 보면 된다.
&lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt;에서는 $n_{\textrm{head}}=8$로 놓고 병렬적으로 계산하도록 하였다.
\(d_{\textrm{head}} = d_{\textrm{model}} / n_{\textrm{head}}\)으로 정의하므로, 계산량은 같다. 원래 512개의 \(d_{\textrm{model}}\)을 사용하던걸 \(d_{\textrm{head}} = 64\)을 $n_{\textrm{head}}=8$ 번 수행하는 것이다.&lt;/p&gt;

&lt;p&gt;일반화를 위해 $Q, K, V$에 대해 헤드를 분리해서 다음과 같이 표현한다.
MHA에서는 \(n_{\textrm{head}}\)가 고정이므로 \(d_q = d_k = d_v\)이다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(d_q\): 각 attention head에서의 query vector 사이즈&lt;/li&gt;
  &lt;li&gt;\(d_k\): 각 attention head에서의 key vector 사이즈&lt;/li&gt;
  &lt;li&gt;\(d_v\): 각 attention head에서의 value vector 사이즈&lt;/li&gt;
  &lt;li&gt;\(n_{\textrm{head}}\): Attention head 수 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_attention_heads&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Input $X$가 \(\textrm{batch\_size} \times \textrm{seq} \times d_{\textrm{model}}\)일 때,
weight matrix $W^Q$, $W^K$, $W^V$는 $d_q, d_k, d_v$에 의해 다음과 같이 변한다.&lt;/p&gt;

\[W^Q \in \mathbb{R}^{d_{\textrm{model}} \times d_q}\]

\[W^K \in \mathbb{R}^{d_{\textrm{model}} \times d_k}\]

\[W^V \in \mathbb{R}^{d_{\textrm{model}} \times d_v}\]

&lt;p&gt;$Q, K, V$는 다음과 같이 변한다.&lt;/p&gt;

\[Q = X W^Q \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_q}\]

\[K = X W^K \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_k}\]

\[V = X W^V \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_v}\]

&lt;p&gt;여기서 \(Q K^\mathsf{T}\)를 연산하기 위해서는 \(d_q = d_k\)라는 조건이 필요하고 해당 조건이 맞다고 하면, \(Q K^\mathsf{T}\)는 다음과 같이 계산된다.&lt;/p&gt;

\[Q K^\mathsf{T} \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times \textrm{seq} }\]

&lt;p&gt;각 head의 attention output은 다음과 같다.&lt;/p&gt;

\[\textrm{head}_i = \textrm{Attention}(Q,K,V) = \textrm{Softmax}\left(\dfrac{QK^T}{\sqrt{d_k}}\right)V \in \mathbb{R}^{\textrm{batch\_size} \times n_{\textrm{head}} \cdot d_v \times d_{\textrm{model}} }\]

&lt;p&gt;이렇게 각 head별로 계산된 attention을 concat으로 계산하면&lt;/p&gt;

\[\textrm{MultiHead}(Q,K,V) = \textrm{Concat}(\textrm{head}_1, \dots, \textrm{head}_i) W^O \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times n_{\textrm{head}} \cdot d_v}\]

&lt;p&gt;가 되고 여기서 \(W^O\)만 다음과 같은 dimension을 가진다.&lt;/p&gt;

\[W^O \in \mathbb{R}^{n_{\textrm{head}} \cdot d_v \times d_{\textrm{model}}}\]

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/02-MHA.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&amp;quot;&amp;gt;Multi-head attention: self-attention with multiple heads&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/02-MHA.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&amp;quot;&amp;gt;Multi-head attention: self-attention with multiple heads&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&quot;&gt;Multi-head attention: self-attention with multiple heads&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;\(d_q = d_k\)이어야 하지만, \(d_v\)는 다를 수는 있다.
&lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt; 논문에서는
\(d_q = d_k = d_v = d_{\textrm{model}} / n_{\textrm{head}} = 64\)를 사용하였으나,
어차피 \(Q K^\mathsf{T}\)는 \(\textrm{batch\_size} \times \textrm{seq} \times \textrm{seq}\)의 차원을 가지므로, \(V\)와 차원을 무관한 차원을 가져도 된다. 따라서, 아래 그림과 같이 $d_v$를 다르게 하고 사용해도 된다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/03-MHA-n-tokens.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&amp;quot;&amp;gt;Multi-head attention: focused on the matrix dimensions&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/03-MHA-n-tokens.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&amp;quot;&amp;gt;Multi-head attention: focused on the matrix dimensions&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://magazine.sebastianraschka.com/p/understanding-and-coding-self-attention&quot;&gt;Multi-head attention: focused on the matrix dimensions&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;또한 결과적으로 head 수 만큼 쪼개서 계산하는 것뿐이므로 기존의 SDPA와 연산량 자체는 동일하다.&lt;/p&gt;

&lt;h2 id=&quot;kv-cache&quot;&gt;KV Cache&lt;/h2&gt;

&lt;p&gt;KV Cache는 Autoregressive Decoder 모델에서 효율적인 계산을 위해 사용하는 기법으로,
Self-Attention의 계산 비용을 줄이는 데 중요한 역할을 한다.
이를 이해하기 위해 먼저 MHA의 계산 구조와 비용을 살펴보겠습니다.&lt;/p&gt;

&lt;p&gt;SDPA이나 MHA이나 계산비용은 같으므로 MHA기준으로 설명해본다면 다음과 같은 프로세스를 거친다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;batch_size를 무시할 때, 입력 시퀀스 \(\textrm{seq}\)에서 매번 \(Q, K, V\)를 계산하게 된다.&lt;/li&gt;
  &lt;li&gt;\(Q K^\mathsf{T}\)를 내적을 통해 계산하여 \(\textrm{seq} \times \textrm{seq}\)의 행렬을 생성한다.&lt;/li&gt;
  &lt;li&gt;Softmax를 적용하여 attention score를 계산한다.&lt;/li&gt;
  &lt;li&gt;Attention score를 \(V\)와 곱해 Attention output을 생성한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;kv-cache를-적용하지-않았을-때의-계산-비용&quot;&gt;KV Cache를 적용하지 않았을 때의 계산 비용&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;\(Q K^\mathsf{T}\) 내적 계산 비용
 Decoder only model이라 가정할 때, \(Q\)가 현재 디코더 스텝 $t$의 쿼리 벡터이고,
 \(K\)와 \(V\)는 이전 디코더 스텝의 출력을 기반으로 계산된다.
 \(T\)를 전체 시퀀스 길이(\(\textrm{seq}\)), \(d_k\)를 Key/Query 벡터의 차원으로 가정하면,&lt;/p&gt;

\[Q K^\mathsf{T} \in \mathbb{R}^{(T \times d_k) \cdot (d_k \times T)}\]

    &lt;p&gt;이는&lt;/p&gt;

\[Q K^\mathsf{T} \in \mathbb{R}^{T \times T}\]

    &lt;p&gt;로 수렴한다.&lt;/p&gt;

    &lt;p&gt;각 내적의 연산은 \(O(d_k)\)이고, 이를 \(T \times T\) 행렬에 수행하게 되므로
 \(Q K^\mathsf{T} = O(T^2 \cdot d_k)\)의 비용이 필요하게 된다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Softmax 계산 비용 (attention score 계산비용)
 \(T \times T\) 행렬의 각 원소에 대해 Softmax 함수를 적용하면 되므로, \(O(T^2)\)이다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Attention output 계산 비용
 Attention score 행렬 \(T \times T\)와 \(V\) 행렬 \(T \times d_v\)의 곱이다.
 벡터 내적으로 생각해서 계산한다면,
 각 원소는 \(O(T)\)만큼 비용이 들고 이를 \(T\times d_v\)만큼 계산해야하므로,
 총 계산 비용은 \(O(T^2 \cdot d_v)\)가 필요하다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MHA의 계산 비용
 1.부터 3.까지의 계산 비용을 합하면 \(O(T^2 \cdot d_k) + O(T^2) + O(T^2 \cdot d_v)\)이다.
 그리고 보통 MHA에서는 $d_k = d_v$로 놓는 경우가 많기 때문에 $d = d_k = d_v$라고 할 수 있다.&lt;/p&gt;

    &lt;p&gt;따라서 총합하면 &lt;strong&gt;각 query step $t$마다 다음과 같이 계산 비용이 quadratic하게 증가&lt;/strong&gt;하며, 이를 $d$를 사용하여 근사할 수 있다.&lt;/p&gt;

\[O(t^2 \cdot d_k) + O(t^2) + O(t^2 \cdot d_v) \approx O(t^2 \cdot d)\]

    &lt;p&gt;이를 모든 Step에 대해 누적하면&lt;/p&gt;

\[\sum_{t=1}^T O(t^2 \cdot d) = O (T^3 \cdot d)\]

    &lt;p&gt;즉, sequence length가 길어질 수록 전체 비용이 cubic하게 증가한다.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;kv-cache-원리&quot;&gt;KV Cache 원리&lt;/h3&gt;

&lt;p&gt;일반적인 Self-Attention에서 \(Q\)는 단일 입력 토큰 (\(x_t\))이라고 생각하면 되고,
\(K, V\)는 입력 토큰의 집합인 입력 시퀀스 (\(X=[x_1, x_2, \dots, x_T]\))에 대해서 생성된다.
따라서 입력 시퀀스에 대해 계산된 \(K, V\)를 매 \(Q\)마다 모두 다시 계산할 필요가 없다.&lt;/p&gt;

&lt;p&gt;이를 Decoder only 모델에 대해서도 다시 생각해보자면,
Query라는건 decoder step에서의 신규 토큰,
Key는 모델이 “attend”해야할 기존 context,
Value는 이전 context에 대한 가중치합(weighted sum)라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;이 때, 이전 스텝에서 사용한 Key, Value는 유지하면서 신규 토큰에 대해서만 계산하고 \(T\)쪽 차원을 점진적으로 늘리면 계산 비용을 아낄 수 잇다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/04-KV-Cache.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://developer-qa.nvidia.com/blog/mastering-llm-techniques-inference-optimization/&amp;quot;&amp;gt;An illustration of the key-value caching mechanism
&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/04-KV-Cache.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://developer-qa.nvidia.com/blog/mastering-llm-techniques-inference-optimization/&amp;quot;&amp;gt;An illustration of the key-value caching mechanism
&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://developer-qa.nvidia.com/blog/mastering-llm-techniques-inference-optimization/&quot;&gt;An illustration of the key-value caching mechanism
&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;kv-cache를-적용할-때의-계산-비용&quot;&gt;KV Cache를 적용할 때의 계산 비용&lt;/h3&gt;

&lt;p&gt;Autoregressive한 Decoder only 모델에서도 전체 타입스텝에 대해 누적하면 \(O(T^2 \cdot d)\)의 계산 비용이 필요하다.
하지만, KV Cache를 사용하는 순간 다음과 같이 계산비용이 감소하게 된다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;\(Q K^{\mathsf{T}}\) 내적 계산 비용
 기존에는 \(Q K^{\mathsf{T}} \in \mathbb{R}^{T \times T}\)를 전부 계산했다면, 이제는 신규 query 토큰 \(q_k\)와 \(K_{\textrm{past}}\)의 내적만 계산하면 된다.&lt;/p&gt;

\[q_t K^\mathsf{T} \in \mathbb{R}^{(1 \times d_k) (T_{\textrm{past}} \times d_k)^\mathsf{T}}\]

    &lt;p&gt;따라서 비용은 \(O(T_{\textrm{past}} \cdot d_k)\)이다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Softmax 계산 비용 (attention score 계산비용)&lt;/p&gt;

    &lt;p&gt;\(q_t\)에 대해 Softmax를 적용하므로 비용은 다음과 같이 감소한다.&lt;/p&gt;

\[O(T_{\textrm{past}})\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Attention output 계산 비용&lt;/p&gt;

    &lt;p&gt;새로운 attention score와 \(V_{\textrm{past}}\)의 곱으로 계산되며,
 \(V_{\textrm{past}}\in\mathbb{R}^{T_{\textrm{past}} \times d_v}\) 이므로,
 다음과 같이 계산된다.
 \(\textrm{Softmax}\left(\dfrac{Q K^\mathsf{T}}{\sqrt{d_k}}\right) V \in \mathbb{R}^{(1 \times T_{\textrm{past}})} \mathbb{R}^{T_{\textrm{past}} \times d_v}\)&lt;/p&gt;

    &lt;p&gt;따라서 계산 비용은 다음과 같다.&lt;/p&gt;

\[O(T_{\textrm{past}}) \cdot d_v\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MHA의 계산 비용
 따라서 총합하면 &lt;strong&gt;각 query step $t$마다 다음과 같이 linear하게 계산 비용이 증가&lt;/strong&gt;하고,&lt;/p&gt;

\[O(t_{\textrm{past}} \cdot d_k) + O(t_{\textrm{past}}) + O(t_{\textrm{past}} \cdot d_v) \approx O(t_{\textrm{past}} \cdot d)\]

    &lt;p&gt;이를 모든 시퀀스에 대해 종합하면, 전체 time step에 대해 quadratic한 계산 비용이 든다.&lt;/p&gt;

\[\sum_{t=1}^T O(t \cdot d) = O(T^2  \cdot d)\]
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;mqa-multi-query-attention&quot;&gt;MQA (Multi-Query Attention)&lt;/h2&gt;

&lt;p&gt;이렇게 $K$와 $V$를 재활용하는 것이 중요해지자, 아예 Key와 Value를 여러개의 head로 만드는 것이 아닌,
하나의 Key Value로 공유하자는 아이디어가 나왔다.
&lt;a class=&quot;citation&quot; href=&quot;#shazeer2019fast&quot;&gt;[2]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;기존의 MHA와 MQA를 비교하면 다음 그림의 맨 왼쪽과 오른쪽 그림을 비교하면 된다.
Query는 유지되지만, Key와 Value는 하나임을 알 수 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/05-MHA-GHA-MQA.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2305.13245&amp;quot;&amp;gt;A comparison of different attention mechanisms. (MHA, GQA, MQA)&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/05-MHA-GHA-MQA.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2305.13245&amp;quot;&amp;gt;A comparison of different attention mechanisms. (MHA, GQA, MQA)&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://arxiv.org/abs/2305.13245&quot;&gt;A comparison of different attention mechanisms. (MHA, GQA, MQA)&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;MHA에서는 전체 시퀀스 \(T\)에 대해 각 head $i$에 대한 \(Q_i, K_i, V_i\)는 다음과 같았다.&lt;/p&gt;

\[\mathbf{Q}_i \in \mathbb{R}^{T \times d_k}, \mathbf{K}_i \in \mathbb{R}^{T \times d_k}, \mathbf{V}_i \in \mathbb{R}^{T \times d_v}\]

\[\begin{align}
\textbf{head}_i &amp;amp;= \textrm{Attention} (\mathbf{Q}_i, \mathbf{K}_i, \mathbf{V}_i) \\
\textrm{MHA}(Q, K, V) &amp;amp;= \textrm{Concat}(\textbf{head}_1, \dots, \textbf{head}_{n_{\textrm{head}}})W^O
\end{align}\]

&lt;p&gt;그러나, MQA에서는 다음과 같이 변화한다.&lt;/p&gt;

\[\mathbf{Q}_i \in \mathbb{R}^{T \times d_k}, \mathbf{K}_\textrm{shared} \in \mathbb{R}^{T \times d_k}, \mathbf{V}_\textrm{shared} \in \mathbb{R}^{T \times d_v}\]

\[\begin{align}
\textbf{head}_i &amp;amp;= \textrm{Attention} (\mathbf{Q}_i, \mathbf{K}_\textrm{shared}, \mathbf{V}_\textrm{shared}) \\
\textrm{MQA}(Q, K, V) &amp;amp;= \textrm{Concat}(\textbf{head}_1, \dots, \textbf{head}_{n_{\textrm{head}}})W^O
\end{align}\]

&lt;p&gt;\(\mathbf{Q}_i\)는 결국 \(n_{\textrm{head}}\)만큼 계산량이 늘어나지만, \(\mathbf{K}_\textrm{shared}\)와 \(\mathbf{V}_\textrm{shared}\)는 공유되기 때문에 매우 적은 메모리로도 decoding을 할 수 있게 되었다. 적은 KV cache로 메모리 부담을 줄이고 inference 속도를 향상시킬 수 있게 된 것이다.&lt;/p&gt;

&lt;p&gt;그러나, 하나의 key와 value를 사용하기 때문에 MHA보다는 표현력을 학습하는데 있어 일부 떨어질 수 밖에 없다.&lt;/p&gt;

&lt;h2 id=&quot;gqa-grouped-query-attention&quot;&gt;GQA (Grouped Query Attention)&lt;/h2&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#ainslie2023gqa&quot;&gt;[3]&lt;/a&gt; 에서는 위 MQA의 문제점을 해결하기 위해 MHA와 MQA의 절충안을 제시했다.&lt;/p&gt;

&lt;p&gt;다음 그림의 가운데가 GQA이다. MQA처럼 하나의 Key Value를 쓰지는 않지만, 그렇다고 MHA처럼 헤드 개수만큼 만들지도 않는다.
일종의 그룹을 만들어서 $K, V$를 사용하는 방법으로 메모리 사용량도 줄이고 표현력도 잘 학습될 수 있도록 한 것이다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/05-MHA-GHA-MQA.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2305.13245&amp;quot;&amp;gt;A comparison of different attention mechanisms. (MHA, GQA, MQA)&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/05-MHA-GHA-MQA.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2305.13245&amp;quot;&amp;gt;A comparison of different attention mechanisms. (MHA, GQA, MQA)&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://arxiv.org/abs/2305.13245&quot;&gt;A comparison of different attention mechanisms. (MHA, GQA, MQA)&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;$K$와 $V$를 위해 각 헤드 $i$대신 $g(i)$라는 group index를 도입하였다. \(n_{\textrm{head}}\)를 $G$개의 그룹으로 만든 것이다.
GQA에서는 $\mathbf{Q}, \mathbf{K}, \mathbf{V}$를 각 헤드 $i$나 $g(i)$에 대해서 다음과 같이 표현할 수 있다.&lt;/p&gt;

\[\mathbf{Q}_i \in \mathbb{R}^{T \times d_k}, \mathbf{K}_{g(i)} \in \mathbb{R}^{T \times d_k}, \mathbf{V}_{g(i)} \in \mathbb{R}^{T \times d_v}\]

\[\begin{align}
\textbf{head}_i &amp;amp;= \textrm{Attention} (\mathbf{Q}_i, \mathbf{K}_\textrm{g(i)}, \mathbf{V}_\textrm{g(i)}) \\
\textrm{GQA}(Q, K, V) &amp;amp;= \textrm{Concat}(\textbf{head}_1, \dots, \textbf{head}_{n_{\textrm{head}}})W^O
\end{align}\]

&lt;p&gt;만약 $G$가 1이면 MQA를 $G$가  \(n_{\textrm{head}}\)이면 MHA를 표현할 수 있게 되었다. 이 방법은 Llama 3 모델에 적용되어 8B 모델이 Llama 2의 7B 모델과 유사한 inference 효율에 기여함을 보여주었다.
&lt;a class=&quot;citation&quot; href=&quot;#dubey2024llama&quot;&gt;[4]&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;mla-multi-head-latent-attention&quot;&gt;MLA (Multi-head Latent Attention)&lt;/h2&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#liu2024deepseek&quot;&gt;[5]&lt;/a&gt;에서는 LoRA의 아이디어를 빌려온
Low-Rank Key-Value Joint Compression을 개발하였다.
이는 Key와 Value 매트릭스를 캐싱하는 대신에, low rank vector인 $C^{KV}$에 압축된 형태로 표현한다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/06-MHA-GHA-MQA-MLA.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2405.04434&amp;quot;&amp;gt;A comparison of different attention mechanisms. (MHA, GQA, MQA, MLA)&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/06-MHA-GHA-MQA-MLA.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2405.04434&amp;quot;&amp;gt;A comparison of different attention mechanisms. (MHA, GQA, MQA, MLA)&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://arxiv.org/abs/2405.04434&quot;&gt;A comparison of different attention mechanisms. (MHA, GQA, MQA, MLA)&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;$K$와 $V$를 새로운 low rank vector인 $c^{KV}_t \in \mathbb{R}^{d_c}$에 대해 표현하면 다음과 같다. 이 때, 새로운 차원 \(d_c \ll d_h n_{\textrm{head}}\)은 KV compression dimension이며 기존 head를 사용할 때의 차원보다 매우 작기 때문에 효율적이다.&lt;/p&gt;

&lt;p&gt;\(\begin{align}
c^{KV}_t &amp;amp;= W^{DKV} \mathbf{h}_t \\
\mathbf{k}^C_t = W^{UK} c_t^{KV} \\
\mathbf{v}^C_t = W^{UV} c_t^{KV} \\
\end{align}\)
이 때, \(W^{DKV} \in \mathbb{R}^{d_c \times d}\)는 key-value에 대한 down-projection matrix ($D$)를,
\(W^{UV},W^{UK} \in \mathbb{R}^{ d_h n_{\textrm{head}} \times d_c}\)는 key-value에 대한 up-projection matrix ($U$)를 나타낸다.&lt;/p&gt;

&lt;p&gt;Deepseek-V2 논문에서는 모델 훈련 과정에서의 activation memory를 줄이기 위해서 query에 대해서도 비슷한 접근을 취하였다.&lt;/p&gt;

\[\begin{align}
c^{Q}_t &amp;amp;= W^{DQ} \mathbf{h}_t \\
\mathbf{q}^C_t = W^{UQ} c_t^{Q}
\end{align}\]

&lt;p&gt;마찬가지로 query compression vector \(c^Q_t \in \mathbb{R}^{d^{\prime}_c}\)는 query compression dimension \(d^{\prime}_c (\ll d_h n_{\textrm{head}})\) 을 가진다.
또한, down-projection matrix와 up-projection matrix도 \(W^{DQ}\in\mathbb{R}^{d^{\prime}_c \times d}\), \(W^{UQ} \in \mathbb{R}^{d_h n_{\textrm{head}} \times d^{\prime}_c}\) 의 차원을 가진다.&lt;/p&gt;

&lt;h3 id=&quot;rope-decoupling&quot;&gt;RoPE decoupling&lt;/h3&gt;
&lt;p&gt;하지만 이렇게 되면 RoPE(Rotary Position Embedding)을 적용하기가 까다로워진다. 왜냐하면, RoPE는 key와 query의 위치에 따라 결정되기 때문이다.&lt;/p&gt;

&lt;p&gt;이를 해결하기 위해서 RoPE를 위한 헤드별 추가적인 $Q$와 $K$ 벡터를 생성한다. 이 때 RoPE를 위해 decoupled된 dimension을 \(d^R_h\)라고 하면, 추가적으로 생성되는 query와 key 벡터는 \(\mathbf{q}^R_{t,i} \in \mathbb{R}^{d^R_h}\)와 \(\mathbf{k}^R_{t,i} \in \mathbb{R}^{d^R_h}\)라고 표현할 수 있다.&lt;/p&gt;

&lt;p&gt;\(\mathbf{q}^R_{t,i}\)와 \(\mathbf{k}^R_{t,i}\)는 기존에 만들어진 압축된 \(\mathbf{q}^C_{t,i}\)와 \(\mathbf{k}^C_{t,i}\)와 concat되어서 query와 key로 사용되게 된다.&lt;/p&gt;

\[\begin{align}
[\mathbf{q}^R_{t,1}; \mathbf{q}^R_{t,2}; \dots; \mathbf{q}^R_{t,i}] = \mathbf{q}^R_t &amp;amp;= \textrm{RoPE}(W^{QR}c^Q_t) \\
\mathbf{k}^R_t &amp;amp;= \textrm{RoPE}(W^{KR}h_t) \\
\mathbf{q}_{t,i} &amp;amp;= [\mathbf{q}^C_{t,i};\mathbf{q}^R_t] \\
\mathbf{k}_{t,i} &amp;amp;= [\mathbf{k}^C_{t,i};\mathbf{k}^R_t] \\
\mathbf{o}_{t,i} &amp;amp;= \sum_{j=1}^t \textrm{Softmax}_j \left( \dfrac{\mathbf{q}_{t,i}^T \mathbf{k}_{t,i}}{\sqrt{d_h + d^R_h}} \right) \mathbf{v}^C_{j,i} \\
\mathbf{u}_t &amp;amp;= W^O [\mathbf{o}_{t,1};\mathbf{o}_{t,2}; \dots; \mathbf{o}_{t,n_{\textrm{head}}}]
\end{align}\]

&lt;p&gt;이 때, \(W^{QR}\in\mathbb{R}^{d^R_h n_{\textrm{head}} \times d^{\prime}_c}\)와 \(W^{KR}\in\mathbb{R}^{d^R_h n_{\textrm{head}} \times d}\)는 RoPE를 MLA와 decoupling하기 위해 만든 weight matrix이다.&lt;/p&gt;

&lt;p&gt;따라서 RoPE를 적용했을 경우 캐싱되는 것은 \(c^{Q}_t\) 뿐만 아니라 \(\mathbf{k}^R_t\)도 포함한다.&lt;/p&gt;

&lt;p&gt;모든 과정은 다음 그림에서 확인할 수 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/07-MLA.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2405.04434&amp;quot;&amp;gt;Details of MLA&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2025-01-18-Attention-Mechanism-and-KV-Cache/07-MLA.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://arxiv.org/abs/2405.04434&amp;quot;&amp;gt;Details of MLA&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://arxiv.org/abs/2405.04434&quot;&gt;Details of MLA&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h2 id=&quot;tpatensor-product-attention&quot;&gt;TPA(Tensor Product Attention)&lt;/h2&gt;

&lt;p&gt;이 논문은 아직 자세히 읽어보지 않았지만, 지금까지의 MHA, MQA, GQA, MLA에 대한 정리를 잘해서 읽기 좋다.
&lt;a class=&quot;citation&quot; href=&quot;#zhang2025tensor&quot;&gt;[6]&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;kv-cache-메모리-크기-구하기&quot;&gt;KV Cache 메모리 크기 구하기&lt;/h2&gt;

&lt;p&gt;그럼 과연 KV Cache는 얼마나 필요할까? 심플하게 MHA라고 가정해보자.&lt;/p&gt;

&lt;p&gt;각 헤드별 $K$와 $V$는 다음과 같이 위에서 정의하였다.&lt;/p&gt;

\[K = X W^K \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_k}\]

\[V = X W^V \in \mathbb{R}^{\textrm{batch\_size} \times \textrm{seq} \times d_v}\]

&lt;p&gt;batch size도 1이고 토큰 하나에 대해서 생각해보면, 모든 헤드에 대해 생각해야하고, 레이어도 여러개인 경우를 생각해봤을 때
\(K\)와 \(V\)에 대해서는 다음과 같은 메모리가 필요하다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2 $\cdot$ num_layers $\cdot$ (num_attention_heads $\cdot$ head_dim) $\cdot$ precision_in_bytes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;여기서 각 변수는 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$2$: $K$와 $V$에 대해서 수행하기 때문에 2를 곱한다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_layers&lt;/code&gt;: 레이어수&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_attention_heads&lt;/code&gt; $\cdot$ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head_dim&lt;/code&gt;: 모델의 차원 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden_size&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;precision_in_bytes&lt;/code&gt;: sizeof(타입). float16 혹은 bfloat16인 경우 2, float8인 경우 1, float32인 경우 4.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이를 여러개의 토큰과 batch size에 대해 확장할 수 있다.&lt;/p&gt;

&lt;p&gt;전체 KV Cache는 다음과 같은 공식이 나온다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;batch_size $\cdot$ sequence_length $\cdot$ 2 $\cdot$ num_layers $\cdot$ (num_attention_heads $\cdot$ head_dim) $\cdot$ precision_in_bytes&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;batch_size&lt;/code&gt;: 말 그대로 배치 사이즈&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sequence_length&lt;/code&gt;: context length를 넣으면 되므로, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_position_embeddings&lt;/code&gt; 값을 사용하는게 맞다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;mha-kv-cache-공식&quot;&gt;MHA KV Cache 공식&lt;/h3&gt;

&lt;p&gt;위에서 살펴본 것이 MHA이다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;batch_size $\cdot$ sequence_length $\cdot$ 2 $\cdot$ num_layers $\cdot$ (num_attention_heads $\cdot$ head_dim) $\cdot$ precision_in_bytes&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;mqa-kv-cache-공식&quot;&gt;MQA KV Cache 공식&lt;/h3&gt;
&lt;p&gt;MQA의 경우 하나의 $K$, $V$를 공유한다.
허깅페이스 모델 config.json에 따르면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_head&lt;/code&gt;=1로 주어진다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;batch_size $\cdot$ sequence_length $\cdot$ 2 $\cdot$ num_layers $\cdot$ (num_key_value_heads $\cdot$ head_dim) $\cdot$ precision_in_bytes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;이 때, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head_dim = hidden_size // num_attention_heads&lt;/code&gt;로 계산된다.&lt;/p&gt;

&lt;h3 id=&quot;gqa-kv-cache-공식&quot;&gt;GQA KV Cache 공식&lt;/h3&gt;
&lt;p&gt;GQA의 경우 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_head&lt;/code&gt;개의 $K$, $V$를 공유한다.
그래서 MQA와 식은 같다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;batch_size $\cdot$ sequence_length $\cdot$ 2 $\cdot$ num_layers $\cdot$ (num_key_value_heads $\cdot$ head_dim) $\cdot$ precision_in_bytes&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;결론&quot;&gt;결론&lt;/h3&gt;
&lt;p&gt;huggingface model의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.json&lt;/code&gt;의 경우 &lt;a href=&quot;https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/configuration_llama.py#L48-L55&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_head&lt;/code&gt;&lt;/a&gt;라는 변수를 따로 주기 때문에 MHA, MQA, GQA 모두 대응할 수 있다.&lt;/p&gt;

&lt;p&gt;다음과 같은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_head&lt;/code&gt; 조건에 따라 MHA, MQA, GQA가 적용된다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MHA: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_heads=num_attention_heads&lt;/code&gt;인 경우&lt;/li&gt;
  &lt;li&gt;MQA: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_heads=1&lt;/code&gt;인 경우&lt;/li&gt;
  &lt;li&gt;GQA: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_heads!=1 and num_key_value_heads!=num_attention_heads&lt;/code&gt; (else) 인 경우&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이에 따라 KV Cache 공식은 다음과 같다. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head_dim = hidden_size // num_attention_heads&lt;/code&gt;)
이 때, sequence_length는 보수적으로 context window length(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_position_embeddings&lt;/code&gt;)를 따르는게 좋다고 생각한다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;batch_size $\cdot$ sequence_length $\cdot$ 2 $\cdot$ num_layers $\cdot$ (num_key_value_heads $\cdot$ head_dim) $\cdot$ precision_in_bytes&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;llama-3-8b-예시&quot;&gt;Llama 3 8B 예시&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;컨텍스트 길이 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_position_embeddings&lt;/code&gt;): 8192 (최대 시퀀스 길이)&lt;/li&gt;
  &lt;li&gt;히든 크기 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hidden_size&lt;/code&gt;): 4096 (각 토큰이 표현되는 벡터 크기)&lt;/li&gt;
  &lt;li&gt;어텐션 헤드 수 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_attention_heads&lt;/code&gt;): 32 (병렬 어텐션 헤드의 수)&lt;/li&gt;
  &lt;li&gt;Key/Value 헤드 수 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_key_value_heads&lt;/code&gt;): 8 (K/V 캐시의 헤드 수)&lt;/li&gt;
  &lt;li&gt;히든 레이어수 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_hidden_layers&lt;/code&gt;): 32&lt;/li&gt;
  &lt;li&gt;데이터 타입 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch_dtype&lt;/code&gt;): bfloat16 (2 바이트 per value)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이에 따라 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head_dim = 4096 // 32 = 128&lt;/code&gt;이며, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;batch_size = 1&lt;/code&gt;, 최대 context length인 8192을 적용하면, 다음과 같다.&lt;/p&gt;

&lt;p&gt;1 $\cdot$ 8192 $\cdot$ 2 $\cdot$ 32 $\cdot$ 8 $\cdot$ 128 $\cdot$ 2 = 1073741824 = 1GB&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer-qa.nvidia.com/blog/mastering-llm-techniques-inference-optimization/&quot;&gt;Mastering LLM Techniques: Inference Optimization&lt;/a&gt;
 &lt;a href=&quot;https://developer.nvidia.com/ko-kr/blog/mastering-llm-techniques-inference-optimization/&quot;&gt;(한국어)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@zaiinn440/mha-vs-mqa-vs-gqa-vs-mla-c6cf8285bbec&quot;&gt;MHA vs MQA vs GQA vs MLA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;vaswani2017attention&quot;&gt;[1]A. Vaswani, “Attention is all you need,” &lt;i&gt;Advances in Neural Information Processing Systems&lt;/i&gt;, 2017.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;shazeer2019fast&quot;&gt;[2]N. Shazeer, “Fast transformer decoding: One write-head is all you need,” &lt;i&gt;arXiv preprint arXiv:1911.02150&lt;/i&gt;, 2019.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;ainslie2023gqa&quot;&gt;[3]J. Ainslie, J. Lee-Thorp, M. de Jong, Y. Zemlyanskiy, F. Lebrón, and S. Sanghai, “Gqa: Training generalized multi-query transformer models from multi-head checkpoints,” &lt;i&gt;arXiv preprint arXiv:2305.13245&lt;/i&gt;, 2023.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;dubey2024llama&quot;&gt;[4]A. Dubey &lt;i&gt;et al.&lt;/i&gt;, “The llama 3 herd of models,” &lt;i&gt;arXiv preprint arXiv:2407.21783&lt;/i&gt;, 2024.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;liu2024deepseek&quot;&gt;[5]A. Liu &lt;i&gt;et al.&lt;/i&gt;, “Deepseek-v2: A strong, economical, and efficient mixture-of-experts language model,” &lt;i&gt;arXiv preprint arXiv:2405.04434&lt;/i&gt;, 2024.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;zhang2025tensor&quot;&gt;[6]Y. Zhang &lt;i&gt;et al.&lt;/i&gt;, “Tensor Product Attention Is All You Need,” &lt;i&gt;arXiv preprint arXiv:2501.06425&lt;/i&gt;, 2025.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Apptainer Setup Guide</title>
   <link href="https://blog.liam.kim/posts/2024/05/Apptainer-Setup-Guide/"/>
   <updated>2024-05-23T00:01:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2024/05/Apptainer-Setup-Guide</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;2024년 5월 기준으로 설명한 글임을 명시한다.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Reproducible Research의 일환으로 많은 사람들이 컨테이너를 이용한 가상화 기술을 활용하고 있다.
이를 위해서 보통 Docker기반의 컨테이너를 많이 사용한다.&lt;/p&gt;

&lt;p&gt;Docker는 서비스를 올릴때는 더할나위 없이 좋은 툴이지만,
Multi-user가 있는 HPC환경에서는 적합하지 않다.
왜냐하면 HPC는 개개인이 별도의 시스템을 사용하는 것이 아닌
네트워크를 통한 스토리지 서버를 구축하여 다수의 유저가 이를 공유해서 사용한다.&lt;/p&gt;

&lt;p&gt;즉, previleged user가 아닌 유저가 사용하는 경우가 대부분이다.&lt;/p&gt;

&lt;p&gt;하지만 Docker 컨테이너의 기본 유저는 root이기 때문에 Docker 컨테이너 내부에서 스토리지를 마운트해서 작업한다고 해보자
Docker 내부에서 쓴 파일들은 스토리지 밖에서는 권한문제때문에 쉽게 수정할수도 없고,
더욱 위험한건 root권한을 가지고 다름 사람들의 디렉토리를 건드릴 수도 있다.&lt;/p&gt;

&lt;p&gt;물론 Docker의 유저를 바꾸면 된다.
그러나, 이런 경우 같은 내용의 이미지일지라도 유저가 다르다는 이유하나만으로 여러개의 이미지를 만들 수 밖에 없다.&lt;/p&gt;

&lt;p&gt;이런 문제로 HPC를 위한 컨테이너인 Singularity라는 컨테이너 기술이 생겼고, 이제는 Linux Foundation안에서 Apptainer라는 이름을 사용하고 있다.&lt;/p&gt;

&lt;p&gt;이 포스트에서는 Docker에 비해 널리 알려지지 않은 Apptainer의 설치와 사용방법을 소개하고자 한다.
이전 포스트에서 설명한 &lt;a href=&quot;https://blog.liam.kim/posts/2024/05/Slurm-Setup-Guide/&quot;&gt;Slurm&lt;/a&gt;과 결합하면
많은 ML연구자들과 HPC와 연관된어 있는 연구자들이 도움을 많이 받을 것이라 본다.&lt;/p&gt;

&lt;h2 id=&quot;basic-concepts-of-apptainer&quot;&gt;Basic Concepts of Apptainer&lt;/h2&gt;

&lt;h2 id=&quot;install-apptainer&quot;&gt;Install Apptainer&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/apptainer/apptainer/blob/main/INSTALL.md#install-system-dependencies&quot;&gt;시스템 의존성 패키지&lt;/a&gt;들을 설치한다. 어차피 prebuilt package 쓸거라 큰 상관은 없어보이기는 하지만, fakeroot같은 추가기능을 위해서 설치하면 좋을 것 같다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;c&quot;&gt;# Ensure repositories are up-to-date&lt;/span&gt;
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update

 &lt;span class=&quot;c&quot;&gt;# Install debian packages for dependencies&lt;/span&gt;
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     build-essential &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     libseccomp-dev &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     pkg-config &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     uidmap &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     squashfs-tools &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     fakeroot &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     cryptsetup &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     tzdata &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     curl wget git &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     autoconf &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     automake &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     libtool &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     pkg-config &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     libfuse3-dev &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     zlib1g-dev &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     libssl-dev  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     uuid-dev
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Apptainer는 여러가지 추가기능이 있다. 그 중에서도 개인적으로 생각했을 때 중요한 기능은 다음과 같다. &lt;a href=&quot;https://apptainer.org/docs/admin/main/installation.html#system-requirements&quot;&gt;공식 문서&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;unprevileged user namespace : non-previleged user가 컨테이너를 실행할 수 있게 한다.&lt;/li&gt;
      &lt;li&gt;fakeroot : 컨테이너 내부에서는 root처럼 작동해서 패키지 설치들을 가능하게 한다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;또한 Apptainer에는 두 가지 모드가 있다. 여기서는 보통 계산을 돌리는 용도기 때문에 sandbox모드를 쓸 이유가 없고, SIF파일을 사용한다고 가정한다. SIF파일을 써야 fakeroot를 사용할때 제약이 많이 없어진다.
    &lt;ul&gt;
      &lt;li&gt;sandbox : 컨테이너를 수정할 수 있는 모드&lt;/li&gt;
      &lt;li&gt;SIF File : 읽기전용 모드. 패키지 설치등은 할 수 있지만 컨테이너를 다시 만들면 초기화&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;이제 본격적으로 설치해보자. PPA가 따로 있어서 매우 심플하다.
Apptainer로 루트 권한이 필요한 작업을 많이 하기 때문에 setuid 기능을 사용할 것이고 이를 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apptainer-suid&lt;/code&gt;를 설치한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; software-properties-common
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;add-apt-repository &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; ppa:apptainer/ppa
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; apptainer-suid
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;버전을 확인한다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; $ apptainer --version
 apptainer version 1.3.1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;build-and-push-image&quot;&gt;Build and Push Image&lt;/h2&gt;

&lt;p&gt;Apptainer의 자체 문법을 사용해도 되지만, 딥러닝을 위해 NVIDIA Docker image를 활용해보려고 한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;예를 들어 다음과 같은 ngc이미지로 만드는 Dockerfile이 있다고 해보자.&lt;/p&gt;

    &lt;div class=&quot;language-Dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; FROM nvcr.io/nvidia/tensorrt:24.04-py3

 &lt;span class=&quot;c&quot;&gt;# 필수 패키지 업데이트 및 설치&lt;/span&gt;
 RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
     build-essential \
     wget \
     curl \
     git \
     libssl-dev \
     libbz2-dev \
     libreadline-dev \
     libsqlite3-dev \
     zlib1g-dev \
     libncurses5-dev \
     libncursesw5-dev \
     xz-utils \
     tk-dev \
     libffi-dev \
     liblzma-dev \
     libgdbm-dev \
     libxml2-dev \
     libxmlsec1-dev \
     &amp;amp;&amp;amp; apt-get clean \
     &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

 # Python 소스 다운로드 및 빌드
 ENV PYTHON_VERSION=3.12.3
 RUN wget https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz \
     &amp;amp;&amp;amp; tar -xf Python-$PYTHON_VERSION.tgz \
     &amp;amp;&amp;amp; cd Python-$PYTHON_VERSION \
     &amp;amp;&amp;amp; ./configure --enable-optimizations \
     &amp;amp;&amp;amp; make -j $(nproc) \
     &amp;amp;&amp;amp; make altinstall \
     &amp;amp;&amp;amp; cd .. \
     &amp;amp;&amp;amp; rm -rf Python-$PYTHON_VERSION Python-$PYTHON_VERSION.tgz

 &lt;span class=&quot;c&quot;&gt;# 심볼릭 링크 설정&lt;/span&gt;
 RUN ln -s /usr/local/bin/python3.12 /usr/bin/python3 \
     &amp;amp;&amp;amp; ln -s /usr/local/bin/pip3.12 /usr/bin/pip3

 &lt;span class=&quot;c&quot;&gt;# 작업 디렉토리 설정&lt;/span&gt;
 WORKDIR /app

 &lt;span class=&quot;c&quot;&gt;# 필요한 패키지 설치 (여기서는 예시로, 실제로 필요한 패키지를 대체하세요)&lt;/span&gt;
 COPY requirements.txt requirements.txt
 RUN pip3 install --no-cache-dir -r requirements.txt

 &lt;span class=&quot;c&quot;&gt;# 애플리케이션 코드 복사&lt;/span&gt;
 COPY . .
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;이걸 Docker 이미지로 먼저 빌드한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; docker buildx build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; hello:0.1 &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; Dockerfile
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Docker 이미지로 Apptainer 이미지(SIF파일)로 변환한다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; apptainer build hello_apptainer_v0.1.sif docker://hello:0.1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Apptainer로 OCI Artificats이기 때문에 필요한 경우 Harbor같은 OCI Registries에도 push 수 있다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; apptainer push hello_apptainer_v0.1.sif oras://&amp;lt;harbor_URL&amp;gt;/hello_org/hello:0.1.0
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Pull할때는 다음과 같이 하면 된다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; apptainer pull --name &amp;lt;DOWNLOADED_SIF_FILENAME&amp;gt;.sif oras://&amp;lt;harbor_URL&amp;gt;/hello_org/hello:0.1.0
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;slurm-job-script&quot;&gt;Slurm Job Script&lt;/h2&gt;

&lt;p&gt;Apptainer의 중요한 옵션들을 알려주려고 한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exec&lt;/code&gt; : 어떤 명령어를 실행할때 사용하는 apptainer 명령어이다.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--nv&lt;/code&gt; : 이 옵션은 NVIDIA GPU를 사용하는 컨테이너를 실행할때 필요한 설정들을 자동으로 불러온다. 호스트의 드라이버, cuDNN같은 라이브러리도 자동으로 불러오기 때문에 컨테이너 안에서 추가로 설치할 이유가 없다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--bind&lt;/code&gt; : 파일 시스템을 마운트할때 좋다. NFS를 마운트할때도 좋고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/&lt;/code&gt;와 같이 공통적으로 쓰는 디렉토리를 마운트할 때 좋다.
 이러면, 코드에서는 항상 같은 폴더(예를 들면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data&lt;/code&gt;)같은 prefix를 고정하고 실험을 돌릴 수 있게 된다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--env&lt;/code&gt; : 환경변수를 설정할 때 좋다. job script에 다음과 같이 설정하고 사용한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;PYENV_ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.pyenv

 apptainer &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PYENV_ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PYENV_ROOT&lt;/span&gt; blahblah.sif /bin/bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;python my_script.py&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fakeroot&lt;/code&gt; : 앞서 설명한 것처럼 마치 root인것처럼 실행하게 해준다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--writable-tmpfs&lt;/code&gt; : 임시 파일 시스템을 사용해 마치 컨테이너 내부 파일을 변경하는 것처럼 보이게 해준다.
 컨테이너가 종료되면 모든 변경사항들이 사라진다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/bin/bash -c &apos;커맨드1; 커맨드2; 커맨드3&apos;&lt;/code&gt; : 여러개의 명령어를 실행하게 해준다.
 예를 들면, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyenv&lt;/code&gt;를 컨테이너 내부에서 쓰려면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export PATH&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval &quot;$(pyenv init -)&quot;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval &quot;$(pyenv virtualenv init -)&quot;&lt;/code&gt;와 같은 쉘 명령어를 전부 실행해야하는데 이를 커맨드1, 커맨드2 등에 매핑시켜서 진행할 수 있다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;이렇게 해서 최종 스크립트를 살펴보면 다음과 같다.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;#!bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --job-name=잡이름&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --nodes=1&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --ntasks-per-node=1       # 프로세스 수 (MPI RANK 수 혹은 num_workers)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --cpus-per-task=1         # 프로세스 별 Thread 수 수&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --mem=128GB               # 메모리 제한&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --partition=파티션이름      # 파티션 이름&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --gres=gpu:장치이름:장치수   # GRES 자원선택&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --output=%x-%j.log        # 잡이름-잡넘버.log 형식으로 output 파일 생성&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# uv를 통해 &apos;hello_world.py&apos; 실행&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;uv run python hello_world.py&apos;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;SIF_FILE_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/path/where_sif_file_exists.sif&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DATA_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$HOME/data&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HF_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$HOME/.cache/huggingface&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UV_PYTHON_INSTALL_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$HOME/.local/share/uv/python&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UV_TOOL_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$HOME/.local/share/uv/tools&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 1. $HOME/data를 컨테이너 안에서 /data로 bind&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 2. $HF_HOME, $UV_PYTHON_INSTALL_DIR, $UV_TOOL_DIR 환경변수 전달&lt;/span&gt;
apptainer &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--nv&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--bind&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$DATA_DIR&lt;/span&gt;:/data:rw &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--bind&lt;/span&gt; /dev/shm:/dev/shm &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HF_HOME&lt;/span&gt;:&lt;span class=&quot;nv&quot;&gt;$HF_HOME&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$UV_PYTHON_INSTALL_DIR&lt;/span&gt;:&lt;span class=&quot;nv&quot;&gt;$UV_PYTHON_INSTALL_DIR&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--env&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$UV_TOOL_DIR&lt;/span&gt;:&lt;span class=&quot;nv&quot;&gt;$UV_TOOL_DIR&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--writeable-tmpfs&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$SIF_FILE_PATH&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    /bin/bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;eval &quot;. $HOME/.local/bin/uv/env&quot;;
    eval &quot;\$CMD&quot;;&apos;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job.sh&lt;/code&gt;라고 저장한다면 다음과 같이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbatch&lt;/code&gt; 명령어를 통해 실행시킬 수 있다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;sbatch job.sh
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Slurm Setup Guide</title>
   <link href="https://blog.liam.kim/posts/2024/05/Slurm-Setup-Guide/"/>
   <updated>2024-05-19T00:01:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2024/05/Slurm-Setup-Guide</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;2024년 5월 기준으로 설명한 글임을 명시한다.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;난 Jupyter를 싫어한다.&lt;/p&gt;

&lt;p&gt;실험과 Literate programming 관점에서는 최적의 툴이지만, 대부분 잘못 사용한다고 생각한다. 많은 data scientist들이 작성하는 Jupyter 코드들은 문서는 없고 코드만 있다. 특히 industry에서 production을 위한 코드를 Jupyter로 짜는 것은 무책임하다고 생각하고 있다.
게다가 GPU관리 측면에서 Jupyter가 GPU 자원을 낭비하는 만악의 근원이라고 생각하고 있다.
Google Colab처럼 timeout도 정해두고 잘 관리되면 괜찮지만, 대부분은 시간별로 할당하고 사용자가 GPU를 알아서 반납할때까지 점유하도록 한다.
코드를 작성하거나 편집할때는 GPU자원이 필요한 것이 아니기 때문에 실제 GPU가 작동하는 시간은 점유하고 있는 시간에 비해 적을 수 밖에 없다.&lt;/p&gt;

&lt;p&gt;그러기에 최대한 오래 점유하려고 하고, 다른 사람들은 해당 사용자가 GPU반납하기만을 기다리게 된다.
다른 사용자들은 이런 경험을 겪은 후에는 본인도 오래 점유하려고 하고, 결국 악순환으로 인해 언제나 GPU는 부족하게 된다.
가게로 치면 GPU 회전율이 낮은것이다.&lt;/p&gt;

&lt;p&gt;이를 해결하는 방법 중 하나가 job scheduler를 이용하여 batch system을 쓰는것이다.
batch system을 쓰는 것은 interactive하게 코드를 작성하는 Jupyter보다 사용하기에는 조금 더 어려울 수 있겠지만,
필요할 때만 GPU를 할당받고 효율적으로 사용할 수 있다.
참고로 로깅이나 plot을 실시간으로 보는 interactive함을 원한다면 &lt;a href=&quot;https://wandb.ai/site&quot;&gt;Weight &amp;amp; Biases&lt;/a&gt;나 &lt;a href=&quot;https://www.tensorflow.org/tensorboard&quot;&gt;Tensorboard&lt;/a&gt;같은 툴을 사용하면 되고, 디버깅은 디버깅용 노드를 따로 마련하는 방법이 있을 수 있다.&lt;/p&gt;

&lt;p&gt;Job scheduler 중에서도 GPU 클러스터에서 가장 많이 사용하는 것이 &lt;a href=&quot;https://slurm.schedmd.com/documentation.html&quot;&gt;&lt;strong&gt;slurm&lt;/strong&gt;&lt;/a&gt;이다.
어쩄든, slurm을 처음부터 세팅하는 것은 생각보다 어렵다. 왜냐하면, slurm설정이 처음 보면 난해하기 때문이다. 그래서 이를 알려주고자 한다.&lt;/p&gt;

&lt;p&gt;여기에 HPC 환경을 위한 Container인 Apptainer(former Singularity)도 같이 설정해서 컨테이너 환경에서 Reproducible한 연구가 될 수 있도록 가이드할 예정이다.&lt;/p&gt;

&lt;h3 id=&quot;batch-system&quot;&gt;Batch System&lt;/h3&gt;

&lt;p&gt;slurm을 셋업하기 앞서, batch system의 전체적인 구조 및 workflow를 소개하고자 한다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-05-10-Slurm-Setup-Guide/01-Schematic-Batch-System.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://hpc-wiki.info/hpc/File:Batch_System.png&amp;quot;&amp;gt;Schematic of how users can access the batch system&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-05-10-Slurm-Setup-Guide/01-Schematic-Batch-System.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://hpc-wiki.info/hpc/File:Batch_System.png&amp;quot;&amp;gt;Schematic of how users can access the batch system&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://hpc-wiki.info/hpc/File:Batch_System.png&quot;&gt;Schematic of how users can access the batch system&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;우선, 유저는 로그인 노드(login node) 혹은 메인 노드(main node)라는 서버에 접속해서 모든것을 수행한다. 유저는 계산 노드(computing node)에 접근할 수 없다.
그리고 job script 파일을 통해 job scheduler에 계산 혹은 실험(job)을 submit하고 job scheduler는 유저가 작성한 job script 파일을 보고 스케줄링 시스템에 따라 적절한 계산노드에 job을 할당한다.
만약에 남는 자원이 없다면 대기열(queue)에 등록하고 자리가 빌 때까지 기다리게 된다.&lt;/p&gt;

&lt;p&gt;로그인 노드와 각 계산 노드는 당연히 동일한 유저가 있어야 하고, NAS 같이 별도의 파일서버가 있어서 파일 시스템도 공유해야 job 결과를 메인노드에서도 확인할 수 있게 된다. 이를 이해하기 위해서는 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EA%B3%B5%EA%B0%9C_%ED%82%A4_%EC%95%94%ED%98%B8_%EB%B0%A9%EC%8B%9D&quot;&gt;공개 키 암호방식&lt;/a&gt;에 대해서는 필수적으로 공부할 필요가 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-05-10-Slurm-Setup-Guide/02-Scheduler.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://hpc-wiki.info/hpc/File:Scheduler.png&amp;quot;&amp;gt;Schematic of how a scheduler may distribute jobs onto nodes&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-05-10-Slurm-Setup-Guide/02-Scheduler.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://hpc-wiki.info/hpc/File:Scheduler.png&amp;quot;&amp;gt;Schematic of how a scheduler may distribute jobs onto nodes&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://hpc-wiki.info/hpc/File:Scheduler.png&quot;&gt;Schematic of how a scheduler may distribute jobs onto nodes&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;주의할 점은 Job scheduler는 단순히 비어있는 공간에 유저가 요청한 자원을 할당한다는 점이다. 만약에 Job schduler에는 1개의 GPU를 사용한다고 명시했는데, 코드 상에서 강제로 GPU를 2개 사용해버리면 다른 유저가 사용하고 있는 GPU를 같이 사용하게 돼서 문제가 생길 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;setup-cluster&quot;&gt;Setup Cluster&lt;/h2&gt;

&lt;p&gt;데모를 위해 GCP(Google Cloud Platform)를 사용해서 가상의 HPC 클러스터를 설정해보겠다.
사람마다 클러스터 환경이 조금씩 다르기에 초기 셋업도 같이 공유하고자 하는 것이 목적이다.&lt;/p&gt;

&lt;h3 id=&quot;create-project&quot;&gt;Create Project&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;GCP에서 slurm-demo라는 프로젝트를 생성&lt;/li&gt;
  &lt;li&gt;빌링 설정&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;setup-vpc-virtual-private-cloud-network&quot;&gt;Setup VPC (Virtual Private Cloud) Network&lt;/h3&gt;

&lt;h4 id=&quot;assumption&quot;&gt;Assumption&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;클러스터 노드들이 같은 네트워크 안에 묶여있어야 한다.&lt;/li&gt;
  &lt;li&gt;일반적으로 계산 노드들은 외부망과 차단되어 있다. (메인 노드 제외)&lt;/li&gt;
  &lt;li&gt;로그인 노드 = 계산 노드일 때도 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;method&quot;&gt;Method&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;GCP에서 VPC network 선택&lt;/li&gt;
  &lt;li&gt;Enable Compute Engine API&lt;/li&gt;
  &lt;li&gt;Create VPC network 선택&lt;/li&gt;
  &lt;li&gt;다음과 같이 설정 (단순하게 하기 위해 최대한 자동 설정을 사용한다.) 나머지는 건드리지 않는다.
    &lt;ol&gt;
      &lt;li&gt;Name : hpc-cluster-vpc&lt;/li&gt;
      &lt;li&gt;Subnet creation mode : Automatic&lt;/li&gt;
      &lt;li&gt;Firewall rules : hpc-cluster-vpc-allow-ssh&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;만들고 나서 Firewall에 allow-internal 항목이 있는지 체크&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;setup-login-node&quot;&gt;Setup Login Node&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Name : hpc-node-login&lt;/li&gt;
  &lt;li&gt;Region과 Zone을 고른다.
    &lt;ul&gt;
      &lt;li&gt;Zone : us-west4&lt;/li&gt;
      &lt;li&gt;Region : us-west4-a&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Machine Configuration
    &lt;ul&gt;
      &lt;li&gt;E2 선택 후 다음 프리셋 선택&lt;/li&gt;
      &lt;li&gt;Preset : e2-standard-4 (4 vCPU, 2 core, 16 GB memory)&lt;/li&gt;
      &lt;li&gt;VM provisioning model : 가격 절감을 위해 Spot 선택&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Boot disk
    &lt;ul&gt;
      &lt;li&gt;OS : Ubuntu&lt;/li&gt;
      &lt;li&gt;Version : Ubuntu 24.04 LTS (built on 5/16)&lt;/li&gt;
      &lt;li&gt;Size : 120 GB&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Advanced options
    &lt;ol&gt;
      &lt;li&gt;Networking
        &lt;ul&gt;
          &lt;li&gt;Hostname : slurm-demo.hpc-node-login&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Network interfaces 위에서 만든 VPC를 붙인다.
        &lt;ul&gt;
          &lt;li&gt;Network : hpc-cluster-vpc&lt;/li&gt;
          &lt;li&gt;Subnetwork : hpc-cluster-vpc IPv4&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Network Service Tier : Standard&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;setup-compute-node-template&quot;&gt;Setup Compute Node Template&lt;/h3&gt;

&lt;p&gt;우선 Compute template을 만들어서 생성하는게 편하다.
Virtual machines -&amp;gt; Instance templates -&amp;gt; Create Instance Template를 클릭하여 다음과 같이 설정한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Name : hpc-node-compute-template&lt;/li&gt;
  &lt;li&gt;Region과 Zone을 고른다.
    &lt;ul&gt;
      &lt;li&gt;Zone : us-east5&lt;/li&gt;
      &lt;li&gt;Region : us-east5-a&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Machine Configuration
    &lt;ul&gt;
      &lt;li&gt;GPU type : NVIDIA T4&lt;/li&gt;
      &lt;li&gt;Number of GPUs : 2&lt;/li&gt;
      &lt;li&gt;Machine type : n1-standard-1&lt;/li&gt;
      &lt;li&gt;VM provisioning model : 가격 절감을 위해 Spot 선택&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Boot disk
    &lt;ul&gt;
      &lt;li&gt;OS : Ubuntu&lt;/li&gt;
      &lt;li&gt;Version : Ubuntu 24.04 LTS (built on 5/16)&lt;/li&gt;
      &lt;li&gt;Size : 80 GB&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Advanced options
    &lt;ol&gt;
      &lt;li&gt;Networking
        &lt;ul&gt;
          &lt;li&gt;Hostname : slurm-demo.hpc-node-compute&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Network interfaces 위에서 만든 VPC를 붙인다.
        &lt;ul&gt;
          &lt;li&gt;Network : hpc-cluster-vpc&lt;/li&gt;
          &lt;li&gt;Subnetwork : hpc-cluster-vpc IPv4&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Network Service Tier : Standard&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;그 다음 instance를 만들 때 New VM instance from template을 클릭한뒤 템플릿대로 생성한다.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;노드 구성은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;1 Login node (main node)&lt;/li&gt;
  &lt;li&gt;1 CPU compute node (= login node)
    &lt;ul&gt;
      &lt;li&gt;Login node에서 job을 수행할 수 있도록 할 예정이다.&lt;/li&gt;
      &lt;li&gt;이렇게 하는 이유는 GPU instance는 비싸기 때문에 CPU job을 우선적으로 세팅하고 테스트할 예정이기 때문이다.&lt;/li&gt;
      &lt;li&gt;그 다음에 GPU compute node를 추가해서 확인할 예정&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;2 GPU compute node
    &lt;ul&gt;
      &lt;li&gt;각 노드 당 2개의 T4를 가지고 있다고 가정한다.&lt;/li&gt;
      &lt;li&gt;이 클러스터의 총 GPU는 NVIDIA T4 4대이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;slurm-setup-guide-cpu&quot;&gt;Slurm Setup Guide (CPU)&lt;/h2&gt;

&lt;p&gt;자 이제 Login node instance를 실행하고 다음과 같이 slurm을 설치한다. (&lt;strong&gt;Ubuntu 24.04 LTS 기준&lt;/strong&gt;)&lt;/p&gt;

&lt;h3 id=&quot;install-slurm&quot;&gt;Install Slurm&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;System upgrade. 기본적으로 시스템을 최신상태로 유지한다. 만약 nvidia driver가 미리 깔려있었다면, nvidia driver가 업데이트될 수도 있는데, 이러면 driver mismatch 에러가 나면서 nvidia-smi부터 안되기 시작할 수 있다. 그러기에 다음 명령어 수행 후 재부팅을 한번 진행하면 좋다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt upgrade &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;다음 패키지들을 설치한다. 패키지 목록은 &lt;a href=&quot;https://github.com/pyenv/pyenv/wiki#suggested-build-environment&quot;&gt;pyenv wiki&lt;/a&gt;에서 가져온 Suggested build environment인데, 설치하다보면 어차피 많이 겹쳐서 같이 설치하면 좋다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; build-essential libssl-dev zlib1g-dev &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
 libbz2-dev libreadline-dev libsqlite3-dev curl git &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
 libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;slurm을 설치한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;slurm-wlm slurm-wlm-doc
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mailutils&lt;/code&gt;를 설치해서 slurm이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/bin/mail&lt;/code&gt;이 없다고 complain하는걸 막는다.
 &lt;a href=&quot;https://gist.github.com/ckandoth/2acef6310041244a690e4c08d2610423&quot;&gt;single_machine_slurm_on_ubuntu&lt;/a&gt;를 참고했다.&lt;/p&gt;

    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; mailutils
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;어떤 메일 시스템을 사용할지 물어보는데, 외부와 메일을 주고 받지는 않을 것이기 때문에 local system용으로 설정한다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; 맨 아래에 hostname을 &lt;strong&gt;추가&lt;/strong&gt;한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; 10.182.0.4 slurm-demo.hpc-node-login
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;ping&lt;/code&gt;을 통해 체크해본다.&lt;/p&gt;
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; ping slurm-demo.hpc-node-login
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;나중을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spool&lt;/code&gt; 디렉토리도 만들어준다. 왜인지는 몰라도, 자동으로 만들어지지 않아서 나중에 에러가 생긴다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /var/spool/slurmctld
 &lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /var/spool/slurmd
 &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;slurm:slurm /var/spool/slurmctld
 &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;slurm:slurm /var/spool/slurmd
 &lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;755 /var/spool/slurmctld/
 &lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;755 /var/spool/slurmd/
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;pid 파일을 위한 디렉토리도 만들어준다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /var/run/slurm
 &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;slurm:slurm /var/run/slurm
 &lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;755 /var/run/slurm
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;본격적인 Setup에 앞서 인증을 위해 MUNGE를 설치하고 slurm Accounting을 위해 MariaDB를 셋업한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;install-munge&quot;&gt;Install MUNGE&lt;/h3&gt;

&lt;p&gt;MUNGE (MUNGE Uid ‘N’ Gid Emporium)는 HPC환경을 위한 인증서비스이다.
인프라 관리 초보 시절에 가장 이해가 안되던 부분이 바로 다양한 노드에 있는 동일한 유저들을 어떻게 서로 인증 하는지 궁금했다.
리눅스의 유저와 그룹 그리고 UID, GID에 대한 이해가 있다면 모든 노드에 같은 UID, GID를 공유해야한다는 점을 알아두어야 한다.
MUNGE는 그 위에서 작동한다. MUNGE는 관리자 권한(privileged permission), 예약된 포트, 또는 플랫폼 특화 방법을 사용하지 않고 인증 정보를 생성하고 검증할 수 있다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;MUNGE를 설치한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;munge libmunge-dev libmunge2
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;MUNGE key를 생성한다. 해당 키는 다른 노드에 복사해야 인증이 이루어질 수 있기에 잘 보관해야한다.
 &lt;strong&gt;그러기에 Login node에서만, 그것도 MUNGE 처음 설치할 때만 수행하는 작업이다.&lt;/strong&gt;
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; /usr/sbin/mungekey &lt;span class=&quot;nt&quot;&gt;--create&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;만약 이미 키가 존재한다면, 있다고 에러가 뜰 수 있다. 찝찝하면 다시 지우고 다시 만들어도 된다.&lt;/p&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; sudo rm /etc/munge/munge.key
 sudo /usr/sbin/mungekey --create
 sudo ls /etc/munge -alh
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;/usr/sbin/mungekey&lt;/code&gt;대신에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/sbin/create-munge-key -r&lt;/code&gt;를 사용하는 경우도 있다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;키의 소유주를 munge유저로 바꿔야 한다. 바꾸기 전에 우선 munge 유저가 존재하는지 체크해본다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /etc/passwd | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;munge
 &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /etc/group | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;munge
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;체크한 다음, 파일 유저와 그룹을 munge로 바꿔준다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;munge:munge /etc/munge/munge.key
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;파일 권한도 400으로 바꿔서 파일 소유주만 읽을 수 있도록 한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;400 /etc/munge/munge.key
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;munge를 재시작한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart munge
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;munge가 정상적으로 작동하는지 테스트한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; munge &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; | unmunge
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;다음과 같이 STATUS에 Success가 나오면 정상이다.&lt;/p&gt;
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;munge &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; | unmunge
 STATUS:          Success &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 ENCODE_HOST:     slurm-demo.hpc-node-login &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;10.182.0.4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 ENCODE_TIME:     2024-05-19 07:44:43 +0000 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1716104683&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 DECODE_TIME:     2024-05-19 07:44:43 +0000 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1716104683&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 TTL:             300
 CIPHER:          aes128 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 MAC:             sha256 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 ZIP:             none &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 UID:             MYUSERNAME &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1001&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 GID:             MYUSERNAME &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1002&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 LENGTH:          0
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;install-mariadb&quot;&gt;Install MariaDB&lt;/h3&gt;

&lt;p&gt;slurm에는 Accounting이라는 기능이 있다. Job scheduler의 회계같은 기능이라고 보면 되는데,
이 기능은 job이 사용한 리소스등을 기록하는 역할을 하고 자원 제한(reousrce limit)등에 이용할 수 있다.
여튼, accounting을 사용하기 위해서는 어딘가 기록을 해야하는데, 아무래도 파일보다는 DB에 기록하는게 좋다.
유저들이 자기의 job을 조회하는 등에서 파일은 불리한 점이 많고, 점점 지날수록 용량도 많이 차지하기 때문이다.
그러기 위해서 MariaDB를 설정하고자 한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;MariaDB 설치
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; mariadb-server mariadb-client libmariadb-dev-compat
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;설치후 MariaDB initial setup을 한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;mysql_secure_installation
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;ol&gt;
      &lt;li&gt;처음 루트 패스워드를 입력한다. 처음이므로 엔터를 입력 “Enter current password for root (enter for none): “&lt;/li&gt;
      &lt;li&gt;Root password를 변경할 예정이므로 unix_socket authenication을 사용하지 않는다. “Switch to unix_socket authentication [Y/n] n”&lt;/li&gt;
      &lt;li&gt;새로운 root password를 설정한다. “Change the root password? [Y/n] y”&lt;/li&gt;
      &lt;li&gt;익명 유저 로그인을 막았다. “Remove anonymous users? [Y/n] y”&lt;/li&gt;
      &lt;li&gt;보안을 위해 root login을 remote에서 하는걸 막는다. “Disallow root login remotely? [Y/n] n”&lt;/li&gt;
      &lt;li&gt;test database를 제거한다. “Remove test database and access to it? [Y/n] y”&lt;/li&gt;
      &lt;li&gt;지금까지 설정한것을 반영하기 위해 privilege table를 reload한다. “Reload privilege tables now? [Y/n] y”&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;slurm accounting table을 만들기 위해 root로 로그인한다. (다음 명령어 입력후 위에서 설정한 패스워드를 입력한다.)
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;mysql &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; root &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;다음과 같은 MySQL shell이 보일 것이다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; MariaDB &lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;none&lt;span class=&quot;o&quot;&gt;)]&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;MySQL shell에서 accounting을 위한 DATABASE를 만든다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; CREATE DATABASE slurm_acct_db&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Slurm의 DB 패스워드를 “SOME_SLURM_PASSWORD”라고 하고, 다음과 같이 slurm을 위한 DB user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm&lt;/code&gt;를 생성한다.
 host는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;를 강제해서 로컬에서만 연결할 수 있게 하였다.
 물론 보안상 root 유저와 다른 패스워드를 사용해야한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; CREATE USER &lt;span class=&quot;s1&quot;&gt;&apos;slurm&apos;&lt;/span&gt;@&lt;span class=&quot;s1&quot;&gt;&apos;localhost&apos;&lt;/span&gt; IDENTIFIED BY &lt;span class=&quot;s1&quot;&gt;&apos;SOME_SLURM_PASSWORD&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;DB user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm&lt;/code&gt;에게 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm_acct_db&lt;/code&gt;의 모든 권한을 부여한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; GRANT ALL PRIVILEGES ON slurm_acct_db.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; TO &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;slurm&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;@&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;localhost&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;위의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GRANT ALL PRIVILEGES&lt;/code&gt;를 바로 반영하기 위해 PRIVILEGES table을 reload한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; FLUSH PRIVILEGES&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;MySQL 쉘을 나간다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; EXIT&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;slurm에서 DB를 연결하기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurmdbd&lt;/code&gt;패키지를 설치한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; slurmdbd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://slurm.schedmd.com/accounting.html#slurm-accounting-configuration-before-build&quot;&gt;Slurm Accounting Configuration Before Build&lt;/a&gt;를 참고하여 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/mysql/my.cnf&lt;/code&gt;파일의 다음 항목을 적절히 조정한다.
공식 문서에서 예시로 든 값은 다음과 같다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;mysqld]
&lt;span class=&quot;nv&quot;&gt;innodb_buffer_pool_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4096M
&lt;span class=&quot;nv&quot;&gt;innodb_log_file_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;64M
&lt;span class=&quot;nv&quot;&gt;innodb_lock_wait_timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;900
&lt;span class=&quot;nv&quot;&gt;max_allowed_packet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;16M
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;MariaDB와 slurmdbd를 재시작한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart mysqld
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart slurmdbd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;setup-slurm&quot;&gt;Setup slurm&lt;/h3&gt;

&lt;p&gt;이제 본격적으로 slurm 환경설정을 해야한다.
여기부터는 각자 시스템마다 다른 환경을 지니고 있어 시스템 사양 특히 CPU와 메모리를 알아둘 필요가 있다.
현재는 login node와 compute node가 같은 노드이므로 서버를 바꾸지 않고 바로 진행해보도록 하겠다.&lt;/p&gt;

&lt;h4 id=&quot;find-system-information&quot;&gt;Find System Information&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Memory 알아내기
    &lt;ul&gt;
      &lt;li&gt;slurm configuration의 RealMemory에 해당하는 값을 알 필요가 있다.&lt;/li&gt;
      &lt;li&gt;RealMemory는 Megabytes단위를 적어주면 되는데 다음과 같은 명령어를 입력하고 “Total”에 해당하는 값을 적어주면 된다.
        &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  free &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;예를 들어 본 데모에서는 16GB VM을 설정했고 다음과 같은 output이 나왔다. 이 때 RealMemory는 15990이 될 예정이다.
        &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;free &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt;
                 total        used        free      shared  buff/cache   available
  Mem:           15990         694       14162           0        1430       15295
  Swap:              0           0           0
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;CPU 정보 알아내기
    &lt;ul&gt;
      &lt;li&gt;slurm configuration의 CPUs, Sockets, CoresPerSocket, ThreadsPerCore를 알아내야한다. 아마 가장 실수하기 좋을 부분일 것이다.&lt;/li&gt;
      &lt;li&gt;각각은 다음과 같은 의미를 지닌다.
        &lt;ul&gt;
          &lt;li&gt;CPUs : 노드의 logical processor의 개수. 생략할 경우, Boards(메인보드수인데 보통은 1), Sockets, CoresPerSocket, ThreadsPerCore의 곱으로 결정된다.&lt;/li&gt;
          &lt;li&gt;Sockets : 노드의 physical processor의 개수.&lt;/li&gt;
          &lt;li&gt;CoresPerSocket : 소켓 하나의 Core 수&lt;/li&gt;
          &lt;li&gt;ThreadsPerCore : Physical core하나에 논리적인 Thread 수&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;예를 들어 &lt;a href=&quot;https://www.amd.com/ko/products/processors/server/epyc/4th-generation-9004-and-8004-series/amd-epyc-9354.html&quot;&gt;AMD EPYC 9354&lt;/a&gt;를 사용한다고 하자.
        &lt;ul&gt;
          &lt;li&gt;보통 2P를 쓴다. 즉 해당 CPU 2개를 한 보드에 꼽아서 쓴다. 따라서 Sockets는 2이다.&lt;/li&gt;
          &lt;li&gt;CoresPerSocket은 해당 스펙의 CPU 코어수 즉 32이다.&lt;/li&gt;
          &lt;li&gt;ThreadsPerCore은 Intel의 HyperThreading, AMD의 SMT를 생각하면 된다.
  해당 CPU Spec에 쓰레드 수는 64, CPU 코어 수는 32 이므로 ThreadsPerCore는 2이다.&lt;/li&gt;
          &lt;li&gt;CPUs는 Sockets * CoresPerSocket * ThreadsPerCore = 2 * 32 * 2 = 128이다.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;리눅스 커맨드 상에서는 다음 명령어를 사용한다.
        &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /proc/cpuinfo
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;그러나 너무 길어서 보기가 어려운데, 그럴 때는 model name으로 위의 페이지처럼 스펙 찾아서 작성하는게 편하다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;configuration-file-slurmconf&quot;&gt;Configuration File (slurm.conf)&lt;/h4&gt;

&lt;p&gt;자 이제 본격적으로 slurm.conf 파일을 작성할 필요가 있다.
항목이 많지만, 이걸 slurm 공식 사이트에서 자동으로 생성해준다. (웹에서는 최신버전만 지원)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://slurm.schedmd.com/configurator.html&quot;&gt;Slurm Version 23.11 Configuration Tool&lt;/a&gt;로 이동한다.&lt;/p&gt;

&lt;p&gt;이제 다음과 같은 항목만 작성한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Cluster Name - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClusterName&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;말 그대로 클러스터 이름이다. 알아서 작성한다. 데모에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hpc-demo-cluster&lt;/code&gt;라고 지정했다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ControlMachine - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmCtldHost&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;Slurm Control Host, 즉 Login node의 hostname을 적어주면 된다.&lt;/li&gt;
      &lt;li&gt;login node의 쉘에서 리눅스 명령어 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hostname&lt;/code&gt;을 실행해서 나온 값을 적어준다.&lt;/li&gt;
      &lt;li&gt;본 데모에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm-demo.hpc-node-login&lt;/code&gt;이라고 하였다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Compute Machines
    &lt;ul&gt;
      &lt;li&gt;계산 노드에 대해서 작성해주는 곳이다.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NodeName&lt;/code&gt; : 계산 노드의 hostname을 적는 곳이다.
        &lt;ul&gt;
          &lt;li&gt;기본값이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux[1-32]&lt;/code&gt;처럼 같은 사양의 노드는 한번에 작성할 수 있다.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PartionName&lt;/code&gt;
        &lt;ul&gt;
          &lt;li&gt;노드의 그룹을 만들 수 있고 이를 Partition이라고 한다.&lt;/li&gt;
          &lt;li&gt;적당한 이름을 지정하면 된다. 본 데모에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cpu&lt;/code&gt;이라고 지정했다.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CPUs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sockets&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoresPerSocket&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThreadsPerCore&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RealMemory&lt;/code&gt;
        &lt;ul&gt;
          &lt;li&gt;위 섹션에서 찾은 값으로 작성한다.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Event Logging
    &lt;ul&gt;
      &lt;li&gt;Compute Machines부터 Event Logging 전까지는 특별한 사항이 없으면 건드릴 일이 없다.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/slurm&lt;/code&gt;에 로그파일을 몰아넣는다. 참고로 이 로그 파일들은 &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-22-04&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logrotate&lt;/code&gt;&lt;/a&gt;를 사용하여 관리하면 과도하게 로그파일이 커지는 것을 막을 수 있다.
        &lt;ul&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmctldLogFile&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/slurm/slurmctld.log&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmdLogFile&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/slurm/slurmd.log&lt;/code&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Job Completion Logging
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileTxt&lt;/code&gt;로 설정하고 다음 파일에 저장하도록 한다. 정말 큰 HPC 시스템이 아니면 이 로그는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logrotate&lt;/code&gt;를 안써도 상관없었다.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JobCompLoc&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/slrum/job_completions&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Job Accounting Gather
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Linux&lt;/code&gt;를 선택 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cgroup&lt;/code&gt;을 성공하지 못했다.)&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JobAcctGatherFrequency&lt;/code&gt;는 알아서 설정해준다. 디폴트 써도 상관없었다.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JobAcctGatherFrequency&lt;/code&gt; : 30&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Job Accounting Storage
    &lt;ul&gt;
      &lt;li&gt;다음과 같이 설정한다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AccountingStoragePass&lt;/code&gt;는 MUNGE가 알아서 해줄 것이기 때문에 빈칸으로 둔다.
        &lt;ul&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmDBD&lt;/code&gt; 선택&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AccountingStorageLoc&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm_acct_db&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AccountingStorageHost&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AccountingStoragePort&lt;/code&gt; : 6819&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AccountingStorageUser&lt;/code&gt; : slurm&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이렇게 하고 Submit을 누른다음 나온 설정파일을 복사하고, 일부분을 수정해야한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#UnkillableStepTimeout=60&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UnkillableStepTimeout=240&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;리소스를 많이 점유하는 job들은 반환하는데 시간이 오래걸리기 때문에 좀 더 오래 기다려준다.&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://slurm.schedmd.com/slurm.conf.html#OPT_UnkillableStepTimeout&quot;&gt;UnkillableStepTimeout&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#JobAcctGatherTypejobacct_gather/linux=&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JobAcctGatherType=jobacct_gather/linux&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;버그인듯&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmctldPidFile=/var/run/slurmctld.pid&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmctldPidFile=/var/run/slurm/slurmctld.pid&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;디렉토리 권한 문제로 변경&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmdPidFile=/var/run/slurmd.pid&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlurmdPidFile=/var/run/slurm/slurmd.pid&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;디렉토리 권한 문제로 변경&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SelectTypeParameters=CR_CPU_Memory&lt;/code&gt; 추가
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SelectType&lt;/code&gt;은  Slurm이 작업(job)을 실행할 노드와 자원을 선택하는 방법을 정의한다.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SelectTypeParameters=CR_CPU_Memory&lt;/code&gt;은 Slurm이 CPU와 메모리를 함께 관리하여 작업이 CPU와 메모리를 동시에 요청할 수 있게 해준다.&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://slurm.schedmd.com/slurm.conf.html#OPT_SelectTypeParameters&quot;&gt;SelectTypeParameter&lt;/a&gt;을 참고&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그리고, 해당 내용을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/slurm/slurm.conf&lt;/code&gt;로 저장한다. 파일이 없으면 만들어야 한다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# slurm.conf file generated by configurator.html.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Put this file on all nodes of your cluster.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# See the slurm.conf man page for more information.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ClusterName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;hpc-demo-cluster
&lt;span class=&quot;nv&quot;&gt;SlurmctldHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login
&lt;span class=&quot;c&quot;&gt;#SlurmctldHost=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#DisableRootJobs=NO&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#EnforcePartLimits=NO&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#Epilog=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#EpilogSlurmctld=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#FirstJobId=1&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MaxJobId=67043328&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#GresTypes=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#GroupUpdateForce=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#GroupUpdateTime=600&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobFileAppend=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobRequeue=1&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobSubmitPlugins=lua&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#KillOnBadExit=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#LaunchType=launch/slurm&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#Licenses=foo*4,bar&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MailProg=/bin/mail&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MaxJobCount=10000&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MaxStepCount=40000&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MaxTasksPerNode=512&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MpiDefault=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MpiParams=ports=#-#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PluginDir=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PlugStackConfig=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PrivateData=jobs&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ProctrackType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;proctrack/cgroup
&lt;span class=&quot;c&quot;&gt;#Prolog=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PrologFlags=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PrologSlurmctld=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PropagatePrioProcess=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PropagateResourceLimits=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PropagateResourceLimitsExcept=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#RebootProgram=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ReturnToService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;SlurmctldPidFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/run/slurm/slurmctld.pid
&lt;span class=&quot;nv&quot;&gt;SlurmctldPort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6817
&lt;span class=&quot;nv&quot;&gt;SlurmdPidFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/run/slurm/slurmd.pid
&lt;span class=&quot;nv&quot;&gt;SlurmdPort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6818
&lt;span class=&quot;nv&quot;&gt;SlurmdSpoolDir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/spool/slurmd
&lt;span class=&quot;nv&quot;&gt;SlurmUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm
&lt;span class=&quot;c&quot;&gt;#SlurmdUser=root&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SrunEpilog=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SrunProlog=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;StateSaveLocation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/spool/slurmctld
&lt;span class=&quot;c&quot;&gt;#SwitchType=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#TaskEpilog=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TaskPlugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;task/affinity,task/cgroup
&lt;span class=&quot;c&quot;&gt;#TaskProlog=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#TopologyPlugin=topology/tree&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#TmpFS=/tmp&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#TrackWCKey=no&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#TreeWidth=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#UnkillableStepProgram=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#UsePAM=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# TIMERS&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#BatchStartTimeout=10&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#CompleteWait=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#EpilogMsgTime=2000&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#GetEnvTimeout=2&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#HealthCheckInterval=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#HealthCheckProgram=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;InactiveLimit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;KillWait&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;30
&lt;span class=&quot;c&quot;&gt;#MessageTimeout=10&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ResvOverRun=0&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;MinJobAge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;300
&lt;span class=&quot;c&quot;&gt;#OverTimeLimit=0&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SlurmctldTimeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;120
&lt;span class=&quot;nv&quot;&gt;SlurmdTimeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;300
&lt;span class=&quot;nv&quot;&gt;UnkillableStepTimeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;240
&lt;span class=&quot;c&quot;&gt;#VSizeFactor=0&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;Waittime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# SCHEDULING&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#DefMemPerCPU=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#MaxMemPerCPU=0&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SchedulerTimeSlice=30&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SchedulerType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sched/backfill
&lt;span class=&quot;nv&quot;&gt;SelectType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;/cons_tres
&lt;span class=&quot;nv&quot;&gt;SelectTypeParameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;CR_CPU_Memory
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# JOB PRIORITY&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityFlags=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityType=priority/multifactor&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityDecayHalfLife=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityCalcPeriod=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityFavorSmall=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityMaxAge=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityUsageResetPeriod=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityWeightAge=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityWeightFairshare=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityWeightJobSize=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityWeightPartition=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PriorityWeightQOS=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# LOGGING AND ACCOUNTING&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#AccountingStorageEnforce=0&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;AccountingStorageHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;localhost
&lt;span class=&quot;nv&quot;&gt;AccountingStoragePort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6819
&lt;span class=&quot;nv&quot;&gt;AccountingStorageType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;accounting_storage/slurmdbd
&lt;span class=&quot;nv&quot;&gt;AccountingStorageUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm
&lt;span class=&quot;c&quot;&gt;# AccountingStoragePass=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# AccountingStoreFlags=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobCompHost=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;JobCompLoc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/log/slurm/job_completions
&lt;span class=&quot;c&quot;&gt;#JobCompParams=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobCompPass=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobCompPort=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;JobCompType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jobcomp/filetxt
&lt;span class=&quot;c&quot;&gt;#JobCompUser=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobContainerType=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;JobAcctGatherFrequency&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;30
&lt;span class=&quot;nv&quot;&gt;JobAcctGatherType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jobacct_gather/linux
&lt;span class=&quot;nv&quot;&gt;SlurmctldDebug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info
&lt;span class=&quot;nv&quot;&gt;SlurmctldLogFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/log/slurm/slurmctld.log
&lt;span class=&quot;nv&quot;&gt;SlurmdDebug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info
&lt;span class=&quot;nv&quot;&gt;SlurmdLogFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/log/slurm/slurmd.log
&lt;span class=&quot;c&quot;&gt;#SlurmSchedLogFile=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SlurmSchedLogLevel=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#DebugFlags=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# POWER SAVE SUPPORT FOR IDLE NODES (optional)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SuspendProgram=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ResumeProgram=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SuspendTimeout=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ResumeTimeout=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ResumeRate=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SuspendExcNodes=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SuspendExcParts=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SuspendRate=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SuspendTime=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# COMPUTE NODES&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;CPUs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 &lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;15990 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UNKNOWN
&lt;span class=&quot;nv&quot;&gt;PartitionName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;cpu &lt;span class=&quot;nv&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ALL &lt;span class=&quot;nv&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YES &lt;span class=&quot;nv&quot;&gt;MaxTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;INFINITE &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UP
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;slurm.conf&lt;/code&gt;의 소유주는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm:slurm&lt;/code&gt;으로 한다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;slurm:slurm /etc/slurm/slurm.conf
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;또한 State를 저장하기 위해 empty file을 만들어준다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;sudo touch&lt;/span&gt; /var/spool/slurmctld/trigger_state
&lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;slurm:slurm /var/spool/slurmctld/trigger_state
&lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;644 /var/spool/slurmctld/trigger_state
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;configuration-file-slurmdbdconf&quot;&gt;Configuration File (slurmdbd.conf)&lt;/h4&gt;

&lt;p&gt;Job accounting을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurmdbd.conf&lt;/code&gt;도 다음과 같이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/slurm/slurmdbd.conf&lt;/code&gt;에 만들어준다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Example slurmdbd.conf file.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# See the slurmdbd.conf man page for more information.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Archive info&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ArchiveJobs=yes&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ArchiveDir=&quot;/tmp&quot;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ArchiveSteps=yes&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#ArchiveScript=&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#JobPurge=12&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#StepPurge=1&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Authentication info&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;AuthType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;auth/munge
&lt;span class=&quot;c&quot;&gt;#AuthInfo=/var/run/munge/munge.socket.2&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# slurmDBD info&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DbdAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;localhost
&lt;span class=&quot;nv&quot;&gt;DbdHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;localhost
&lt;span class=&quot;c&quot;&gt;#DbdPort=7031&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SlurmUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm
&lt;span class=&quot;c&quot;&gt;#MessageTimeout=300&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DebugLevel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;verbose
&lt;span class=&quot;c&quot;&gt;#DefaultQOS=normal,standby&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;LogFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/log/slurm/slurmdbd.log
&lt;span class=&quot;nv&quot;&gt;PidFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/run/slurm/slurmdbd.pid
&lt;span class=&quot;c&quot;&gt;#PluginDir=/usr/lib/slurm&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#PrivateData=accounts,users,usage,jobs&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#TrackWCKey=yes&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Database info&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;StorageType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;accounting_storage/mysql
&lt;span class=&quot;nv&quot;&gt;StorageHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;localhost
&lt;span class=&quot;nv&quot;&gt;StoragePort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3306
&lt;span class=&quot;nv&quot;&gt;StoragePass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;SOME_SLURM_PASSWORD
&lt;span class=&quot;nv&quot;&gt;StorageUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm
&lt;span class=&quot;nv&quot;&gt;StorageLoc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm_acct_db
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;slurmdbd.conf&lt;/code&gt;의 권한은 600으로 바꿔준다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;slurm:slurm /etc/slurm/slurmdbd.conf
&lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;600 /etc/slurm/slurmdbd.conf
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;run-slurm&quot;&gt;Run slurm&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;본격적으로 slurm을 가동해볼 차례이다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart slurmdbd
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart slurmctld
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart slurmd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;안되면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo systemctl status 데몬이름&lt;/code&gt;을 통해서 오류와 로그파일을 보고 설정파일을 고치면 된다.&lt;/li&gt;
  &lt;li&gt;다음 명령어를 통해 node의 상태를 확인한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; sinfo
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;다음과 같이 IDLE상태면 정상이다.&lt;/p&gt;
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sinfo
 PARTITION AVAIL  TIMELIMIT  NODES  STATE NODELIST
 cpu&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;         up   infinite      1   idle slurm-demo.hpc-node-login
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;만약 STATE가 idle이 아니면 노드 설정이 잘못되었다고 볼 수 있다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt; 설정을 체크해본다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;setup-accounting&quot;&gt;Setup Accounting&lt;/h4&gt;

&lt;p&gt;slurm의 Accounting을 사용하기 위해서는 Accounting에 클러스터와 유저를 등록해야한다.
&lt;a href=&quot;https://wiki.fysik.dtu.dk/Niflheim_system/Slurm_accounting/#create-accounts&quot;&gt;이 링크&lt;/a&gt;의 매뉴얼이 Accounting을 이해하는데 많은 도움이 될 것이다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;클러스터를 등록한다. 등록이 이미 되어있으면, 이미 등록되었다고 나올 것이다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr add cluster hpc-demo-cluster
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;다음 명령어를 통해 클러스터를 확인해본다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr show clusters
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Account를 등록한다. 일종의 유저 그룹이라고 할 수 있다. &lt;a href=&quot;https://slurm.schedmd.com/accounting.html#account-options&quot;&gt;Account&lt;/a&gt;
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr add account acc_group &lt;span class=&quot;nv&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Some Departments&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Organization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;acc_group
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;Account는 계층구조로 이루어질 수도 있다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acc_group&lt;/code&gt; 밑에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acc_sub_group&lt;/code&gt;이 존재할 수도 있다.&lt;/p&gt;

    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr add account acc_sub_group &lt;span class=&quot;nv&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Some Sub Departments&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Organization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;acc_sub_group &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;top_group
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;다음 명령어를 통해 Account를 확인할 수 있다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr show account
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;이제 Accoutning User를 등록한다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DefaultAccount&lt;/code&gt;는 무조건 지정해야한다. &lt;a href=&quot;https://slurm.schedmd.com/accounting.html#user-options&quot;&gt;Account User&lt;/a&gt; 이 때, xxx는 시스템 유저 아이디랑 매칭시켜야 정확한 관리가 가능하다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr create user &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;xxx &lt;span class=&quot;nv&quot;&gt;DefaultAccount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;yyy
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;User는 여러개의 Account에 등록할 수 있다. 이때는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sacctmgr add&lt;/code&gt;를 통해 유저를 다른 account에 지정한다.&lt;/p&gt;
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr add user xxx &lt;span class=&quot;nv&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;yyy
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;다음 명령어들을 통해 Accounting User 현황을 확인할 수 있다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr show user
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr show user &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sacctmgr show account &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; xxx
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;use-slurm&quot;&gt;Use slurm&lt;/h3&gt;

&lt;h4 id=&quot;submit-job-test&quot;&gt;Submit Job (Test)&lt;/h4&gt;

&lt;p&gt;기본적으로는 srun을 통해 간단하게 slurm을 테스트해볼 수 있다.
다음은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cpu&lt;/code&gt; 파티션에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo &quot;Running in cpu partition&quot;&lt;/code&gt;을 실행시켜서 slurm을 테스트 하는 경우이다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;srun &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; cpu &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Running in cpu partition&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;실행시킬 경우 다음과 같이 출력된다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;srun &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; cpu &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Running in cpu partition&quot;&lt;/span&gt;
Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;cpu partition
&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;잡 로그를 확인해보기 위해 accounting 기능을 확인해본다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;sacct
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;특정 시간 범위의 조회의 경우 다음과 같은 명령어로 체크할 수 있다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;sacct &lt;span class=&quot;nt&quot;&gt;--starttime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2023-05-01 &lt;span class=&quot;nt&quot;&gt;--endtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2023-05-02
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;--user&lt;/code&gt;를 사용한다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;sacct &lt;span class=&quot;nt&quot;&gt;--user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;username
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;FAILED&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CANCELLED&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIMEOUT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COMPLETED&lt;/code&gt; 등이 있다)&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;sacct &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;COMPLETED
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;submit-job-job-script&quot;&gt;Submit Job (Job Script)&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;srun&lt;/code&gt; 옵션을 매번 작성하기는 쉽지 않다. 따라서 보통 잡 스크립트(job script)파일을 작성하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbatch&lt;/code&gt; 명령어를 submit한다.&lt;/p&gt;

&lt;p&gt;위에서 실행한 잡은 다음 스크립트를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbatch job_script.sh&lt;/code&gt;로 실행한것과 같다.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --job-name=hello_world      # 작업 이름&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --output=hello_world.out    # 표준 출력 파일 이름&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --error=hello_world.err     # 표준 에러 파일 이름&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --time=00:05:00             # 작업 시간 제한 (HH:MM:SS)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --partition=cpu             # 파티션 이름&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --ntasks=1                  # 총 실행할 작업 수&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --cpus-per-task=1           # 작업당 CPU 코어 수&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#SBATCH --mem=1G                    # 작업당 메모리 요구량&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 실행할 명령어&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Running in cpu partition&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;sbatch&lt;/code&gt; 명령어로 실행시키면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello_world.out&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello_world.err&lt;/code&gt;파일이 생성되고 에러는 없으므로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello_world.err&lt;/code&gt;파일은 빈 파일, 그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello_world.out&lt;/code&gt;파일에는 “Running in cpu partition”가 출력되어서 나온다.&lt;/p&gt;

&lt;p&gt;돌아가고 있는 job은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;squeue&lt;/code&gt; 명령어를 통해 확인할 수 있다.&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;squeue
JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;REASON&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    4       cpu hello_wo some_use  R       0:01      1 slurm-demo.hpc-node-login
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;돌아가고 있는 job을 취소하는것은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;squeue&lt;/code&gt;를 통해 JOBID를 확인하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scancel&lt;/code&gt; 명령어를 사용한다.&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;scancel 4
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;slurm-setup-guide-gpu&quot;&gt;Slurm Setup Guide (GPU)&lt;/h2&gt;

&lt;h3 id=&quot;add-gpu-node-to-slurm&quot;&gt;Add GPU Node to slurm&lt;/h3&gt;

&lt;p&gt;지금까지는 CPU만 있는 노드에 slurm을 세팅해봤다.
하지만 이제 GPU 노드를 추가해보고자 한다.&lt;/p&gt;

&lt;p&gt;계산 노드 2개를 다음과 같이 각각 만들어준다.&lt;/p&gt;

&lt;h3 id=&quot;setup-compute-node-in-gcp&quot;&gt;Setup Compute Node in GCP&lt;/h3&gt;

&lt;p&gt;xx를 01과 02로 해서 2개를 만들었다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Name : hpc-node-compute-xx&lt;/li&gt;
  &lt;li&gt;Region과 Zone을 고른다.
    &lt;ul&gt;
      &lt;li&gt;Zone : us-west4&lt;/li&gt;
      &lt;li&gt;Region : us-west4-a&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Machine Configuration
    &lt;ul&gt;
      &lt;li&gt;N1 선택 후 다음 프리셋 선택&lt;/li&gt;
      &lt;li&gt;Preset : n1-standard-1&lt;/li&gt;
      &lt;li&gt;VM provisioning model : GCP에서 Spot으로 T4 GPU를 절대 할당받을수 없었기에 눈물을 머금고 Standard&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Boot disk
    &lt;ul&gt;
      &lt;li&gt;OS : Ubuntu&lt;/li&gt;
      &lt;li&gt;Version : Ubuntu 24.04 LTS (built on 5/16)&lt;/li&gt;
      &lt;li&gt;Size : 80 GB&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Advanced options
    &lt;ol&gt;
      &lt;li&gt;Networking
        &lt;ul&gt;
          &lt;li&gt;Hostname : slurm-demo.hpc-node-computexx&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Network interfaces 위에서 만든 VPC를 붙인다.
        &lt;ul&gt;
          &lt;li&gt;Network : hpc-cluster-vpc&lt;/li&gt;
          &lt;li&gt;Subnetwork : hpc-cluster-vpc IPv4&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Network Service Tier : Standard&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;즉 지금 다음과 같이 3개의 노드가 있다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;hpc-node-login (10.182.0.4)
    &lt;ul&gt;
      &lt;li&gt;login 노드 및 cpu용 compute 겸용&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;hpc-node-compute01  (10.182.0.2)
    &lt;ul&gt;
      &lt;li&gt;gpu용 compute&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;hpc-node-compute02  (10.182.0.3)
    &lt;ul&gt;
      &lt;li&gt;gpu용 compute&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;세 노드에 user는 모두 같은 구성이다. (UID=1001인 MYUSERNAME이 있다고 가정)&lt;/p&gt;

&lt;p&gt;진행하기 전에 각 노드 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hosts&lt;/code&gt; 파일에 hostname을 추가해준다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;10.182.0.4 slurm-demo.hpc-node-login
10.182.0.2 slurm-demo.hpc-node-compute01
10.182.0.3 slurm-demo.hpc-node-compute02
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;install-cuda&quot;&gt;Install CUDA&lt;/h3&gt;

&lt;p&gt;각 compute노드마다 다음과 같이 패키지를 업데이트하고 CUDA를 설치한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;디바이스 확인 및 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubuntu-drivers-common&lt;/code&gt; 설치
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt upgrade &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;lspci | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; nvidia
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ubuntu-drivers-common
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;nvidia driver 확인
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ubuntu-drivers devices
 udevadm hwdb is deprecated. Use systemd-hwdb instead.
 udevadm hwdb is deprecated. Use systemd-hwdb instead.
 udevadm hwdb is deprecated. Use systemd-hwdb instead.
 udevadm hwdb is deprecated. Use systemd-hwdb instead.
 ERROR:root:aplay &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;not found
 &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; /sys/devices/pci0000:00/0000:00:05.0 &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;
 modalias : pci:v000010DEd00001EB8sv000010DEsd000012A2bc03sc02i00
 vendor   : NVIDIA Corporation
 model    : TU104GL &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Tesla T4]
 driver   : nvidia-driver-535-server - distro non-free
 driver   : nvidia-driver-535 - distro non-free recommended
 driver   : nvidia-driver-470-server - distro non-free
 driver   : nvidia-driver-470 - distro non-free
 driver   : xserver-xorg-video-nouveau - distro free &lt;span class=&quot;nb&quot;&gt;builtin&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;현재 드라이버 기준으로 가장 최신인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-driver-535-server&lt;/code&gt;를 설치한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;nvidia-driver-535-server
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;재부팅
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;reboot
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;CUDA 설치. 이제는 NVIDIA Repo를 추가 안해도 바로 CUDA 설치가 되는 듯하다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;nvidia-cuda-toolkit
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;nvidia-smi로 GPU driver가 제대로 로드되었는지 확인해본다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; nvidia-smi
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;ssh--munge-key-configuration&quot;&gt;SSH &amp;amp; MUNGE Key configuration&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;로그인 노드로 다시 돌아가서 SSH Key를 생성한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; ssh-keygen &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; ed25519
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;키 파일명들을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compute-node&lt;/code&gt;로 바꿔준다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;mv&lt;/span&gt; ~/.ssh/id_ed25519 ~/.ssh/compute-node
 &lt;span class=&quot;nb&quot;&gt;mv&lt;/span&gt; ~/.ssh/id_ed25519.pub ~/.ssh/compute-node.pub
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;ssh agent를 편하게 관리하는 &lt;a href=&quot;https://www.funtoo.org/Funtoo:Keychain&quot;&gt;keychain&lt;/a&gt; 설치
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;keychain
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;다음을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.bashrc&lt;/code&gt;에 추가해서 키를 등록한다
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;keychain &lt;span class=&quot;nt&quot;&gt;--eval&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--agents&lt;/span&gt; ssh compute-node&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source&lt;/code&gt;를 통해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt;를 다시 로드한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/.bashrc

 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; keychain 2.8.5 ~ http://www.funtoo.org
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Starting ssh-agent...
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Adding 1 ssh key&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: compute-node
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; ssh-add: Identities added: compute-node
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;혹시 모르니 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/config&lt;/code&gt; 도 다음과 같이 만들어준다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; Host slurm-demo.hpc-node-compute01
     HostName slurm-demo.hpc-node-compute01
     User MYUSERNAME
     IdentityFile ~/.ssh/compute-node

 Host slurm-demo.hpc-node-compute02
     HostName slurm-demo.hpc-node-compute02
     User MYUSERNAME
     IdentityFile ~/.ssh/compute-node
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;public key를 클립보드에 복사한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/.ssh/compute-node.pub
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;계산 노드에 들어가서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh&lt;/code&gt; 디렉토리를 만들고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authorized_keys&lt;/code&gt;에 public key를 넣어준다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh&lt;/code&gt; 와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/authorized_keys&lt;/code&gt;는 보안을 위해 소유자만 접근할 수 있는 권한을 설정해준다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; ~/.ssh
 &lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;700 ~/.ssh
 &lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;PUBIC_KEY_복사한거 &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.ssh/authorized_keys
 &lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;600 ~/.ssh/authorized_keys
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;로그인 노드로 다시 돌아가 접속이 되는지 테스트해본다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; ssh MYUSERNAME@slurm-demo.hpc-node-compute-01
 ssh MYUSERNAME@slurm-demo.hpc-node-compute-02
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;install-slurm-1&quot;&gt;Install slurm&lt;/h3&gt;

&lt;p&gt;각 계산노드에도 다음과 같이 slurm을 설치한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;slurm 설치 (compute node이므로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurmd&lt;/code&gt;만 설치)
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;slurmd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;MUNGE key를 ssh 디렉토리에 복사해놓는다. (아무 디렉토리에 복사하기엔 양심에 찔림) 그리고 소유주도 임시적으로 바꿔준다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo cp&lt;/span&gt; /etc/munge/munge.key ~/.ssh/munge.key
 &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;MYUSERNAME:MYUSERNAME ~/.ssh/munge.key
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;로그인 노드에서 기존에 MUNGE Key만든것을 계산노드로 전송한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; scp ~/.ssh/munge.key MYUSERNAME@slurm-demo.hpc-node-compute01:/home/MYUSERNAME/.ssh/munge.key
 scp ~/.ssh/munge.key MYUSERNAME@slurm-demo.hpc-node-compute02:/home/MYUSERNAME/.ssh/munge.key
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;각 계산 노드로 들어가 MUNGE Key를 확인하고 소유주를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;munge&lt;/code&gt;로 바꾼뒤 원래 있어야할 경로(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/munge/&lt;/code&gt;)로 복사한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;munge:munge ~/.ssh/munge.key
 &lt;span class=&quot;nb&quot;&gt;sudo mv&lt;/span&gt; ~/.ssh/munge.key /etc/munge/munge.key
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;계산 노드에서 MUNGE를 재시작한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart munge
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;slurmconf-modification&quot;&gt;slurm.conf Modification&lt;/h3&gt;

&lt;p&gt;기존의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;의 node와 partition부분에 새로운 compute노드를 추가하고, 이 설정을 동일하게 모든 노드에 업데이트해야한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;로그인 노드로 들어가서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;의 맨 마지막 부분 다음 부분에 주목한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;CPUs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 &lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;15990 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UNKNOWN
 &lt;span class=&quot;nv&quot;&gt;PartitionName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;cpu &lt;span class=&quot;nv&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ALL &lt;span class=&quot;nv&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YES &lt;span class=&quot;nv&quot;&gt;MaxTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;INFINITE &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UP
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;기존 cpu partition의 Nodes부분만 수정하고(계속 쓸테니), 새롭게 GPU node와 partition을 추가한다. 여기서 조심해야할 것은 NodeName에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;이 너무 많으면 slurm이 node를 제대로 인식 못할 수 있다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;CPUs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 &lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;15990 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UNKNOWN
 &lt;span class=&quot;nv&quot;&gt;PartitionName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;cpu &lt;span class=&quot;nv&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YES &lt;span class=&quot;nv&quot;&gt;MaxTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;INFINITE &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UP

 &lt;span class=&quot;c&quot;&gt;# Define the types of GRES available&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;GresTypes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu

 &lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute01 &lt;span class=&quot;nv&quot;&gt;Gres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu:2 &lt;span class=&quot;nv&quot;&gt;CPUs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3661 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UNKNOWN
 &lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute02 &lt;span class=&quot;nv&quot;&gt;Gres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu:2 &lt;span class=&quot;nv&quot;&gt;CPUs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3661 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UNKNOWN

 &lt;span class=&quot;nv&quot;&gt;PartitionName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu &lt;span class=&quot;nv&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute[01-02] &lt;span class=&quot;nv&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YES &lt;span class=&quot;nv&quot;&gt;MaxTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;INFINITE &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UP
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;로그인 노드에서 scp를 사용해서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;를 전파한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo cp&lt;/span&gt; /etc/slurm/slurm.conf ~/slurm.conf
 &lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;MYUSERNAME:MYUSERNAME ~/slurm.conf
 scp ~/slurm.conf MYUSERNAME@slurm-demo.hpc-node-compute01:/home/MYUSERNAME/slurm.conf
 scp ~/slurm.conf MYUSERNAME@slurm-demo.hpc-node-compute02:/home/MYUSERNAME/slurm.conf
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;각 계산노드에 들어가서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;를 로그인 노드에서 복사한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;로 교체해준다. 파일이 이미 있는 경우 백업을 해준다.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; sudo chown root:root ~/slurm.conf
 sudo mv /etc/slurm/slurm.conf /etc/slurm/slurm.conf.backup
 sudo mv ~/slurm.conf /etc/slurm/slurm.conf
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;그리고 GPU 2개를 가정했을 때, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/gres/gres.conf&lt;/code&gt;를 만들어준다. &lt;a href=&quot;https://manpages.ubuntu.com/manpages/focal/en/man5/gres.conf.5.html&quot;&gt;공식 문서&lt;/a&gt;를 참고하면 좋다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;c&quot;&gt;# Node-specific GRES configuration for slurm-demo.hpc-node-compute-01&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu &lt;span class=&quot;nv&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tesla &lt;span class=&quot;nv&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/nvidia0
 &lt;span class=&quot;nv&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu &lt;span class=&quot;nv&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tesla &lt;span class=&quot;nv&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/nvidia1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;c&quot;&gt;# Node-specific GRES configuration for slurm-demo.hpc-node-compute-02&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu &lt;span class=&quot;nv&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tesla &lt;span class=&quot;nv&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/nvidia0
 &lt;span class=&quot;nv&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu &lt;span class=&quot;nv&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tesla &lt;span class=&quot;nv&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/nvidia1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;로그인 노드의 slurm데몬들을 재시작한다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart slurmctld
 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart slurmd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;계산 노드의 slurm데몬들을 재시작한다.
 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shell
 sudo systemctl restart slurmd
&lt;/code&gt;shell&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurmd&lt;/code&gt;의 동작상태를 확인해본다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl status slurmd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gres.conf&lt;/code&gt;은 계산 노드 로컬에 저장되므로 따로따로 관리되지만, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;은 모두 통일되어야 한다. 따라서 만약에 Resource limit등으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;를 수정했다면 이를 모든 계산노드에 전파할 필요가 있다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;만약 안된다면 방화벽 문제일 수도 있다. 클라우드라면 VPC에 Firewall Rule이 등록되었는지 확인하자(Default port: 6817, 6818)
그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ufw&lt;/code&gt;가 활성화 되어있다면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ufw&lt;/code&gt;로 모든 노드의 방화벽 룰을 등록해주자.
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; sudo ufw allow 6817/tcp
 sudo ufw allow 6818/tcp
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;로그인 노드로 돌아와서 노드 상태를 확인한다. 다음과 같이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unk*&lt;/code&gt;이면 UNKNOWN상태라는 뜻이다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sinfo
PARTITION AVAIL  TIMELIMIT  NODES  STATE NODELIST
cpu          up   infinite      1   idle slurm-demo.hpc-node-login
gpu&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;         up   infinite      2   unk&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; slurm-demo.hpc-node-compute[01-02]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;idle상태가 되어야 해당 노드를 사용할 수 있다. idle로 강제로 바꿔주자.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;scontrol update &lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute01 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;RESUME
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;scontrol update &lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute02 &lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;RESUME
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;srun&lt;/code&gt;으로 slurm을 테스트해본다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;srun &lt;span class=&quot;nt&quot;&gt;--nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nt&quot;&gt;--ntasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nt&quot;&gt;--partition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu nvidia-smi
Wed May 22 18:20:31 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.161.08             Driver Version: 535.161.08   CUDA Version: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|&lt;span class=&quot;o&quot;&gt;=========================================&lt;/span&gt;+&lt;span class=&quot;o&quot;&gt;======================&lt;/span&gt;+&lt;span class=&quot;o&quot;&gt;======================&lt;/span&gt;|
|   0  Tesla T4                       Off | 00000000:00:04.0 Off |                    0 |
| N/A   77C    P0              30W /  70W |      2MiB / 15360MiB |      0%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
|   1  Tesla T4                       Off | 00000000:00:05.0 Off |                    0 |
| N/A   77C    P0              33W /  70W |      2MiB / 15360MiB |      8%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|&lt;span class=&quot;o&quot;&gt;=======================================================================================&lt;/span&gt;|
|  No running processes found                                                           |
+---------------------------------------------------------------------------------------+
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;노드 상태도 확인해본다.
    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;scontrol show nodes
&lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute01 &lt;span class=&quot;nv&quot;&gt;Arch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;x86_64 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;CPUAlloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;CPUEfctv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CPUTot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CPULoad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.00
&lt;span class=&quot;nv&quot;&gt;AvailableFeatures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ActiveFeatures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;Gres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu:2
&lt;span class=&quot;nv&quot;&gt;NodeAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute01 &lt;span class=&quot;nv&quot;&gt;NodeHostName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute01 &lt;span class=&quot;nv&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;23.11.4
&lt;span class=&quot;nv&quot;&gt;OS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Linux 6.8.0-1007-gcp &lt;span class=&quot;c&quot;&gt;#7-Ubuntu SMP Sat Apr 20 00:58:31 UTC 2024&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3661 &lt;span class=&quot;nv&quot;&gt;AllocMem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;FreeMem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3055 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;Boards&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;IDLE &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;TmpDisk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;Weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;Owner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;N/A &lt;span class=&quot;nv&quot;&gt;MCS_label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;N/A
&lt;span class=&quot;nv&quot;&gt;Partitions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu
&lt;span class=&quot;nv&quot;&gt;BootTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T17:56:01 &lt;span class=&quot;nv&quot;&gt;SlurmdStartTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T18:18:36
&lt;span class=&quot;nv&quot;&gt;LastBusyTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T18:20:32 &lt;span class=&quot;nv&quot;&gt;ResumeAfterTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;None
&lt;span class=&quot;nv&quot;&gt;CfgTRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1,mem&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3661M,billing&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;AllocTRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CapWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a
&lt;span class=&quot;nv&quot;&gt;CurrentWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;AveWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;ExtSensorsJoules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a &lt;span class=&quot;nv&quot;&gt;ExtSensorsWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;ExtSensorsTemp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a

&lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute02 &lt;span class=&quot;nv&quot;&gt;Arch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;x86_64 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;CPUAlloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;CPUEfctv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CPUTot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;CPULoad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.00
&lt;span class=&quot;nv&quot;&gt;AvailableFeatures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ActiveFeatures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;Gres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu:2
&lt;span class=&quot;nv&quot;&gt;NodeAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute02 &lt;span class=&quot;nv&quot;&gt;NodeHostName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-compute02 &lt;span class=&quot;nv&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;23.11.4
&lt;span class=&quot;nv&quot;&gt;OS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Linux 6.8.0-1007-gcp &lt;span class=&quot;c&quot;&gt;#7-Ubuntu SMP Sat Apr 20 00:58:31 UTC 2024&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3661 &lt;span class=&quot;nv&quot;&gt;AllocMem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;FreeMem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3105 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;Boards&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;IDLE &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;TmpDisk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;Weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;Owner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;N/A &lt;span class=&quot;nv&quot;&gt;MCS_label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;N/A
&lt;span class=&quot;nv&quot;&gt;Partitions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpu
&lt;span class=&quot;nv&quot;&gt;BootTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T17:00:28 &lt;span class=&quot;nv&quot;&gt;SlurmdStartTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T18:18:29
&lt;span class=&quot;nv&quot;&gt;LastBusyTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T18:17:03 &lt;span class=&quot;nv&quot;&gt;ResumeAfterTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;None
&lt;span class=&quot;nv&quot;&gt;CfgTRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1,mem&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3661M,billing&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;AllocTRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CapWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a
&lt;span class=&quot;nv&quot;&gt;CurrentWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;AveWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;ExtSensorsJoules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a &lt;span class=&quot;nv&quot;&gt;ExtSensorsWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;ExtSensorsTemp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a

&lt;span class=&quot;nv&quot;&gt;NodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;Arch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;x86_64 &lt;span class=&quot;nv&quot;&gt;CoresPerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2
&lt;span class=&quot;nv&quot;&gt;CPUAlloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;CPUEfctv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 &lt;span class=&quot;nv&quot;&gt;CPUTot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 &lt;span class=&quot;nv&quot;&gt;CPULoad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.00
&lt;span class=&quot;nv&quot;&gt;AvailableFeatures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ActiveFeatures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;Gres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;NodeAddr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;NodeHostName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;slurm-demo.hpc-node-login &lt;span class=&quot;nv&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;23.11.4
&lt;span class=&quot;nv&quot;&gt;OS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Linux 6.8.0-1007-gcp &lt;span class=&quot;c&quot;&gt;#7-Ubuntu SMP Sat Apr 20 00:58:31 UTC 2024&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RealMemory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;15990 &lt;span class=&quot;nv&quot;&gt;AllocMem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;FreeMem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;14952 &lt;span class=&quot;nv&quot;&gt;Sockets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;Boards&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;IDLE &lt;span class=&quot;nv&quot;&gt;ThreadsPerCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 &lt;span class=&quot;nv&quot;&gt;TmpDisk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;Weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;Owner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;N/A &lt;span class=&quot;nv&quot;&gt;MCS_label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;N/A
&lt;span class=&quot;nv&quot;&gt;Partitions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;cpu
&lt;span class=&quot;nv&quot;&gt;BootTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T14:43:47 &lt;span class=&quot;nv&quot;&gt;SlurmdStartTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T17:04:34
&lt;span class=&quot;nv&quot;&gt;LastBusyTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2024-05-22T17:17:21 &lt;span class=&quot;nv&quot;&gt;ResumeAfterTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;None
&lt;span class=&quot;nv&quot;&gt;CfgTRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4,mem&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;15990M,billing&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4
&lt;span class=&quot;nv&quot;&gt;AllocTRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CapWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a
&lt;span class=&quot;nv&quot;&gt;CurrentWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;AveWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;ExtSensorsJoules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a &lt;span class=&quot;nv&quot;&gt;ExtSensorsWatts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;ExtSensorsTemp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n/a
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;p&gt;Slurm을 쓰면서 가장 많이 겪는 문제 중 하나가 갑자기 노드가 drain상태에 빠지는 것이다.
만약, 특정 노드(예를 들어 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm-demo.hpc-node-compute01&lt;/code&gt;)이 drain상태에 빠졌다면 수동으로 다음과 같이 복구할 수 있다.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;scontrol: update NodeName=slurm-demo.hpc-node-compute01 State=DOWN Reason=&quot;undraining&quot;
scontrol: update NodeName=slurm-demo.hpc-node-compute01 State=RESUME
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이 문제의 원인을 한동안 몰랐는데, 최근에 이유를 추측할 수 있었다. Slurm job이 종료가 될 때, 돌고 있는 프로세스에 &lt;a href=&quot;https://en.wikipedia.org/wiki/Signal_(IPC)#SIGTERM&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt;&lt;/a&gt; signal을 보내게 되는데, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt;을 보낸 후 어느정도 지나면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGKILL&lt;/code&gt;을 보낸다. 근데 만약 특정 시간이 지나도 Job 종료가 안되면 drain상태에 빠지는 것으로 추측한다.&lt;/p&gt;

&lt;p&gt;특히, 서버의 자원이 대용량이 되어가면서 메모리 등을 반환하는데 시간이 예전보다 더 걸리는 경우가 많아서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slurm.conf&lt;/code&gt;의 timeout 시간들을 기본값보다 조금씩 늘려보는것도 나쁘지 않을 것 같다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;To cancel a job, invoke scancel without --signal option. This will send first a SIGCONT to all steps to eventually wake them up followed by a SIGTERM, then wait the KillWait duration defined in the slurm.conf file and finally if they have not terminated send a SIGKILL. This gives time for the running job/step(s) to clean up.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;하지만, 자동으로 완전히 해결할 방법은 딱히 없어보인다.
timeout시간도 어느정도가 적정선인지는 시스템마다 경험적으로 알아내는 수밖에 없다.
모니터링을 열심히 하거나 cron으로 drain된 노드가 있으면 자동으로 undrain해주는 스크립트를 돌리거나 하는 방법밖에는 없어보인다.&lt;/p&gt;

&lt;h2 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;이렇게 slurm이 잘 되는 것을 확인했다. 많은 도움이 되었기를 바란다.
자세한건 이제 &lt;a href=&quot;https://slurm.schedmd.com/documentation.html&quot;&gt;공식 문서&lt;/a&gt;를 살펴보면서 바꿔보면 된다.&lt;/p&gt;

&lt;p&gt;계산노드에 똑같은 작업을 반복해서 하기 귀찮다면 Ansible같은 여러 자동화툴이 존재하니까 써보면 나쁘지 않을 것같다.&lt;/p&gt;

&lt;p&gt;그리고 다음 포스트에서는 Docker 대신 사용할 수 있는 Apptainer를 설치하고 이를 slurm에서 어떻게 돌릴 수 있는지 알아보겠다.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Tensor Program II</title>
   <link href="https://blog.liam.kim/posts/2024/05/Tensor-Program-2/"/>
   <updated>2024-05-18T11:30:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2024/05/Tensor-Program-2</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#yang2022tensor&quot;&gt;[1]&lt;/a&gt;와 &lt;a class=&quot;citation&quot; href=&quot;#yang2023spectral&quot;&gt;[2]&lt;/a&gt;를
리뷰하기에 앞서 &lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;를 살펴보기로 하겠다.&lt;/p&gt;

&lt;p&gt;이 논문의 핵심은 NTK를 확장하여 MLP뿐만 아니라 다른 어떤 아키텍처에서도 동일한 이론을 적용할 수 있음을 보인다.
NTK가 중요하다는 것은 알고있었지만, 너무 이상적인 이론이라고 생각하고 있었는데, 이 논문을 통해서 많은 궁금증이 풀린 경험이 있기에 소개한다.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#yang2019wide&quot;&gt;[4]&lt;/a&gt; 논문도 같이 보는게 맞으나, 다음 버전에서 잘 요약해주기도 했다고 생각하기도 하고, 무엇보다 양이 너무 많아서 생략한다.&lt;/p&gt;

&lt;h3 id=&quot;neural-tangent-kernel-ntk&quot;&gt;Neural Tangent Kernel (NTK)&lt;/h3&gt;

&lt;p&gt;먼저 알아야할 것은 Neural Tangent Kernel(NTK)이다. NTK를 통해 nonlinear한 모델을 linear하게 만들어서 training dynamics를 해석할 수 있는 길이 열렸다고 볼 수 있다.&lt;/p&gt;

&lt;p&gt;NTK는 이름 그대로 커널(Kernel)이지만, 딥러닝을 이해하는데 있어 중요한 개념이다.
머신러닝에서의 커널이란 고차원의 특성 공간(feature space)로 데이터를 변환하는 함수를 뜻한다. 아래의 그림처럼 2차원에서 linear함수로 분류가 되지 않는 데이터도 3차원으로 변환하면 hyperplane에 의해 분류가 될 수 있음을 알 수 있다. 또한 커널을 이용하면 고차원으로 매핑하지 않고도 내적(inner product)을 간단하게 계산할 수 있는 커널 트릭(kernel trick)을 사용할 수 있게 해준다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-04-10-Tensor-Program-2/01-kernel.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-04-10-Tensor-Program-2/01-kernel.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://medium.com/@zxr.nju/what-is-the-kernel-trick-why-is-it-important-98a98db0961d&amp;quot;&amp;gt;What is the kernel trick? Why is it important?t&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-04-10-Tensor-Program-2/01-kernel.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://medium.com/@zxr.nju/what-is-the-kernel-trick-why-is-it-important-98a98db0961d&amp;quot;&amp;gt;What is the kernel trick? Why is it important?t&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://medium.com/@zxr.nju/what-is-the-kernel-trick-why-is-it-important-98a98db0961d&quot;&gt;What is the kernel trick? Why is it important?t&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;NTK는 테일러 전개(taylor expansion)를 통해 무한한 너비(infinite width)를 가지는 simple 2-hidden layer를 랜덤 초기값(initialization)이어도 결정론적(deterministic)인 선형 함수(linear function)로 변환해주는 역할을 수행하는 이론적인 틀이라고 요약할 수 있다. &lt;a class=&quot;citation&quot; href=&quot;#jacot2018neural&quot;&gt;[5]&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;ntk-beyond-intuition&quot;&gt;NTK: Beyond Intuition&lt;/h4&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;의 표현에 따르면 NTK는 수학적으로는 다음과 같이 표현한다.
어떤 parameter $\theta$에 의존하는 함수 $f$ (추후에 모델이 되는 함수)에 대해서, 초기 파라미터 $\theta_0$ 기준으로 $f$를 $\theta$과 입력값 $x$ 대해 다음과 같이 확장할 수 있다.
이 때, $\langle , \rangle$은 내적이며, 우변은 선형 모델(linear model)처럼 작동한다.&lt;/p&gt;

\[\begin{align}
f(x; \theta) - f(x; \theta_0) \approx \langle \nabla_\theta f(x; \theta_0), \theta - \theta_0 \rangle
\end{align}\]

&lt;p&gt;위에서 언급했듯이 우변은 선형 모델처럼 작동한다. 위 식을 선형 모델의 형태로 다시 작성하면 다음과 같다. 이는 어떻게보면 $(fx; \theta)$의 $w$에 대한 1st order taylor expansion이라고 볼 수 있다.&lt;/p&gt;

\[\begin{align}
f(x; \theta) \approx f(x; \theta_0) + \nabla_\theta f(x; \theta_0)^\mathsf{T} (\theta - \theta_0)
\end{align}\]

&lt;p&gt;위 식에서 $f(x; \theta_0)$는 초기값, \(\nabla_\theta f(x; \theta_0)\)는 $\theta_0$에 의존하므로 상수라고 생각할 수 있고, $\theta - \theta_0$는 정확히는 변수 $\theta$에 대한 선형 모델로 볼 수 있다.
하지만, 모델 함수 gradient를 찾는것은 선형(linear)이지 않기에 모델 함수는 비선형(nonlinear)이라고 생각할 수 있다.&lt;/p&gt;

&lt;p&gt;그러나 NTK는 \(\nabla_\theta f(x; \theta_0)\)를 input featurizer 혹은 feature map이라고 불리우는 nonlinear 함수라고 증명한다.&lt;/p&gt;

&lt;p&gt;물론 이 가정은 $\theta$와 $\theta_0$의 차이가 크지 않을 때만 잘 작동한다. 신경망(neural network)에서는 작은 learning rate로 매우 적은 시간에 훈련했을 때만 성립할 수 있다.&lt;/p&gt;

&lt;p&gt;width가 클 수록 즉 무한 너비(infinite-width) 네트워크에서 $\theta_0$이 랜덤하게 잘 초기화되었다면, weight들이 변화량($\theta - \theta_0$에 대한 기대값이 거의 0에 가깝다. 여기에 대한 직관적인 설명은 width가 클 수록 모델의 output에 영향을 주는 weight들이 많아지기 떄문에, $\theta$의 작은 변화라도 $f$에는 영향이 클 수 있다는 점이다. 따라서 weight가 굉장히 조금만 움직이게 되고 이는 모델이 lienar하게 작동할 수 있게 된다.&lt;/p&gt;

&lt;p&gt;내가 느끼기엔 이 가정은 수치해석에서의 &lt;a href=&quot;https://en.wikipedia.org/wiki/Euler_method&quot;&gt;Euler Method&lt;/a&gt;의 가정과 별로 차이가 없어보인다. Unstable하지만 않는다면 복잡한 식이어도 매우 작은 time step을 가정한다면 (비효율적이지만) linear하게 근사해서 풀 수 있기 때문이다.&lt;/p&gt;

&lt;p&gt;NTK 논문 &lt;a class=&quot;citation&quot; href=&quot;#jacot2018neural&quot;&gt;[5]&lt;/a&gt;은 이 직관을 infinite width 모델이기만 하면 어떤 데이터든간에 적용할 수 있음을 보였다. 이를 통해, 비선형 모델도 선형처럼 해석이 가능해지고, 이는 training dynamics를 해석할 수 있게 만들어준다.&lt;/p&gt;

&lt;h4 id=&quot;ntk-gradient-flow&quot;&gt;NTK: Gradient Flow&lt;/h4&gt;

&lt;p&gt;NTK논문은 gradient flow라는 것을 제시하여 gradient descent에서의 training dynamics를 해석하고자 한다.&lt;/p&gt;

&lt;p&gt;모델 weight $\theta$가 업데이트되는 과정을 다음과 같이 나타내면, 이를 learning rate $\eta$를 일종의 time처럼 생각하는 1D ODE로 표현할 수 있게 되고, ODE의 해를 구하면 언제나 해가 존재할 수 있음을 증명했다. 우선 $L$을 $f$에 대한 loss function라고 하자.&lt;/p&gt;

\[\begin{align}
\theta_{k+1} &amp;amp;= \theta_k - \eta \nabla_\theta L(\theta_k) \\
\dfrac{\theta_{k+1} - \theta_k}{\eta} &amp;amp;= -\nabla_\theta L(\theta_k) \\
\dfrac{d \theta(t)}{dt} &amp;amp;= -\nabla_\theta L(\theta (t)) \\
\end{align}\]

&lt;p&gt;마지막 식은 1D ODE 가장 기본적인 형태 그 자체라고 할 수 있다. 하지만, 그 주체가 $\theta$일 뿐. 그래서 Gradient Flow라고 이름붙였다고 생각한다.&lt;/p&gt;

&lt;p&gt;여기서 loss function $\mathcal{L}$을 MSE function이라고 가정하고, $f^*$를 정답 레이블이라고 하자. Loss function을 풀어써서 미분을 적용하면 다음과 같다.&lt;/p&gt;

\[\begin{align}
\dfrac{d \theta(t)}{dt} &amp;amp;= -\nabla_\theta \mathcal{L}(\theta (t)) \\
\dot{\theta}(t) &amp;amp;= -\nabla_\theta (f(\theta) - f^*)^2\\
\dot{\theta} &amp;amp;= -(\nabla_\theta f(\theta)) (f(\theta) - f^*)
\end{align}\]

&lt;p&gt;이를 $f$에 적용하기 위해 Chain rule를 적용한다.&lt;/p&gt;

\[\begin{align}
\dot{f}(\theta) &amp;amp;= \dfrac{d f(\theta(t))}{d \theta(t)} \dfrac{d \theta(t)}{dt} = \nabla_\theta f(\theta)^\mathsf{T} \dot{\theta} \\
\dot{f}(\theta) &amp;amp;= \nabla_\theta f(\theta)^\mathsf{T} \dot{\theta} = -\nabla_\theta f(\theta)^\mathsf{T} \nabla_\theta f(\theta) (f(\theta) - f^*) \\
\end{align}\]

&lt;p&gt;여기서 나온 \(\nabla_\theta f(\theta)^\mathsf{T} \nabla_\theta\)를 &lt;strong&gt;NTK(Neural Tangent Kernel)&lt;/strong&gt;이라고 정의한다.&lt;/p&gt;

&lt;p&gt;좀 더 자세한 내용은 원 논문과 &lt;a class=&quot;citation&quot; href=&quot;#jacot2018neural&quot;&gt;[5]&lt;/a&gt; &lt;a href=&quot;https://rajatvd.github.io/NTK/&quot;&gt;이 블로그&lt;/a&gt;에 정리가 잘 되어있다. 개인적으로는 논문은 어려워서 이해가 잘 안됐지만, 해당 블로그가 정말 쉽게 잘 설명되어 있어서 읽기 좋았다.&lt;/p&gt;

&lt;h4 id=&quot;ntk-ntk-init&quot;&gt;NTK: NTK INIT&lt;/h4&gt;

&lt;p&gt;위의 &lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;의 표현으로 다시 바꾸고 정리하면 다음과 같다. $f(x; \theta)$를 파라미터 $\theta$와 input $x$에 대한 신경망이라고 할 때, $\mathcal{L}$을 Loss, $y$를 label라고 하자. 서로 다른 input $x$와 $\bar{x}$에 대해서 NTK $\Theta$를 다음과 같이 정의할 수 있다.&lt;/p&gt;

\[\begin{align}
f_t - f_{t-1} &amp;amp;\approx -\eta \mathcal{\Theta} \mathcal{L}&apos; (f_t, y) \\
\Theta (x, \bar{x}) &amp;amp;\stackrel{\text{def}}{=} \langle \nabla_\theta f(x; \theta_0), \nabla_\theta  f(\bar{x}; \theta_0) \rangle
\end{align}\]

&lt;p&gt;또한 &lt;a class=&quot;citation&quot; href=&quot;#jacot2018neural&quot;&gt;[5]&lt;/a&gt;에서 보여줬듯이 $\theta$가 랜덤하게 잘 intialized되었고, $f$의 width가 충분히 크다면 (infinite-width), $\Theta$는 deterministic한 $\mathring{\Theta}$로 수렴한다.&lt;/p&gt;

&lt;p&gt;이를 수학적으로 표현하면, $L$개의 hidden layer를 가지며, layer $l$의 width를 $n^l$이라고 할 때, NTK $\Theta (x, \bar{x})$는 $\theta$가 랜덤이어도 deterministic한 kernel $\mathring{\Theta} (x, \bar{x})$으로 수렴한다.&lt;/p&gt;

\[\begin{align}
\Theta \stackrel{p}{\rightarrow} \mathring{\Theta} \textrm{ as } n^1, \dots, n^L \rightarrow \infty \textrm{ in that sequence}
\end{align}\]

&lt;h4 id=&quot;ntk-ntk-train&quot;&gt;NTK: NTK TRAIN&lt;/h4&gt;

&lt;p&gt;수렴 여부뿐만 아니라 이 MLP $f$의 훈련과정을 생각해보자. Loss function $\mathcal{L}$을 사용하여 gradient descent로 train하는 하는 시간을 $t$라고 정의하자. 처음 가우시안 랜덤변수로 초기화한 MLP를 $f_0$, 시간에 따른 MLP를 $f_t$라고 했을 때, 어떤 고정된 시간 $T$에 대해서 width가 충분히 크다면 MLP 모델은 $\mathring{f}$로 수렴한다.&lt;/p&gt;

\[\begin{align}
f_t &amp;amp;\rightarrow \mathring{f}_t \textrm{ for all } t &amp;lt; T, \textrm{ where } f_0 \rightarrow \mathring{f}_0 \\
\partial_t \mathring{f}_t &amp;amp;= -\eta \mathring{\Theta} \cdot \nabla_f \mathcal{L}(\mathring{f}_t)
\end{align}\]

&lt;p&gt;위에서도 유도했듯이 위 식은 true label $f^*$에 대해 1D ODE로 변환될 수 있다.&lt;/p&gt;

\[\begin{align}
\mathring{f}_t - f^* = e^{-\eta t \mathring{\Theta}} (f_0 - f^*)
\end{align}\]

&lt;h2 id=&quot;ntk-decomposition&quot;&gt;NTK Decomposition&lt;/h2&gt;

&lt;p&gt;MLP용 NTK를 다른 모델(RNN, transformer 등)에 확장하기 위해서는 기존 MLP 표현법에 조금 변화가 필요하다. 왜냐하면, &lt;a class=&quot;citation&quot; href=&quot;#jacot2018neural&quot;&gt;[5]&lt;/a&gt; 원 논문의 방법으로는 MLP가 귀납적(inductive)으로 표현되어 있어서 확장하기가 어렵기 때문이다. 이렇게 변형된 표현의 의미를 이해하는 것이 &lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;의 핵심적인 내용이다.&lt;/p&gt;

&lt;p&gt;원래의 방식을 NTK parameterization이라고 하는데 다음과 같이 정의한다.&lt;/p&gt;

&lt;p&gt;input $\xi \in \mathbb{R}^{n^0}$, output dimension $n^{L+1}=1$이라고 할 떄, MLP를 $f(\xi; \theta) = W^{L+1} x^L(\xi)$라고 표현하면, $l=2, \dots, L$에 대해서 재귀적으로 다음과 같이 정의할 수 있다.&lt;/p&gt;

\[\begin{align}
h^l(\xi) &amp;amp;= W^l x^{l-1}(\xi) + b^l \in \mathbb{R}^{n^l} \\
x^l(\xi) &amp;amp;= \phi(h^l(\xi)) \\
h^1(\xi) &amp;amp;= W^1 \xi + b^1 \in \mathbb{R}^{n^1}
\end{align}\]

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-04-10-Tensor-Program-2/02-NTK-parameterization.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;NTK Parameterization&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-04-10-Tensor-Program-2/02-NTK-parameterization.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;NTK Parameterization&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  NTK Parameterization
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;MLP Parameter는 \(\theta = \{ w^l \in \mathbb{R}^{n^l \times n^{l-1}}\}_{l=1}^{L+1} \cup \{ b^l \in \mathbb{R}^{n^l }\}_{l=1}^{L}\)로 정의되고, $W^l$은 $w^l$을 \(\sqrt{n^{l-1}}\)로 나눠준 값으로 정의한다. \(W^l= \dfrac{1}{\sqrt{n^{l-1}}} w^l\) 여기서 $\phi$는 activation function이다. 이는 &lt;a class=&quot;citation&quot; href=&quot;#poole2016exponential&quot;&gt;[6]&lt;/a&gt;로부터 내려오는 유구한 notation이다.&lt;/p&gt;

&lt;p&gt;이제 NTK parameterization을 NTK의 정의에 결합시킨다.&lt;/p&gt;

\[\begin{align}
\Theta (\xi, \bar{\xi}) &amp;amp;= \langle \nabla_\theta f(\xi; \theta_0), \nabla_\theta  f(\bar{\xi}; \theta_0) \rangle \\
&amp;amp;= \sum_{l=1}^{L+1} \langle \nabla_{w^{l}} f(\xi),\nabla_{w^{l}} f(\bar{\xi}) \rangle + \sum_{l=1}^L \langle \nabla_{b^{l}} f(\xi),\nabla_{b^{l}} f(\bar{\xi}) \rangle
\end{align}\]

&lt;p&gt;\(W^l= \dfrac{1}{\sqrt{n^{l-1}}} w^l\)와 chain rule을 고려하면, $\nabla_{w^{l}} f(\xi)$는 다음과 같이 두 matrix의 곱으로 표현할 수 있으며 이는 $n^l \times 1 $와 $1 \times n^{l-1}$의 곱인 $ n^l \times n^{l-1}$ matrix이다.&lt;/p&gt;

\[\begin{align}
\nabla_{w^{l}} f(\xi) = \left( \dfrac{1}{\sqrt{n^{l-1}}} \nabla_{h^l} f(\xi) \right) \left( x^{l-1}(\xi)^\mathsf{T} \right)
\end{align}\]

&lt;p&gt;편의를 위해 논문에 나온 abbreviation($\bullet = \bullet (\xi)$,$\bar{\bullet} = \bullet (\bar{\xi})$)을 사용하고,
\(dh^l = \sqrt{n^{l}} \nabla_{h^{l}} f(\xi)\)와 \(d\bar{h}^l = \sqrt{n^{l}} \nabla_{h^{l}} f(\bar{\xi})\)이라는 것을 정의하면,&lt;/p&gt;

\[\begin{align*}
\nabla_{w^{l}} f(\xi) &amp;amp;= \dfrac{1}{\sqrt{n^{l-1}}} \nabla_{h^l} f(\xi)  x^{l-1}(\xi)^\mathsf{T}  \\
&amp;amp;= \dfrac{1}{\sqrt{n^{l-1}} \sqrt{n^{l}}} \sqrt{n^{l}} \nabla_{h^l} f(\xi)  x^{l-1}(\xi)^\mathsf{T} \\
&amp;amp;= \dfrac{1}{\sqrt{n^{l} n^{l-1}}} dh^l x^{l-1}(\xi)^\mathsf{T}
\end{align*}\]

\[\begin{align*}
\nabla_{w^{l}} f(\bar{\xi}) &amp;amp;= \dfrac{1}{\sqrt{n^{l-1}}} \nabla_{h^l} f(\bar{\xi})  x^{l-1}(\bar{\xi})^\mathsf{T} \\
&amp;amp;= \dfrac{1}{\sqrt{n^{l-1}} \sqrt{n^{l}}} \sqrt{n^{l}} \nabla_{h^l} f(\bar{\xi})  x^{l-1}(\bar{\xi})^\mathsf{T} \\
&amp;amp;= \dfrac{1}{\sqrt{n^{l} n^{l-1}}} d\bar{h}^l x^{l-1}(\bar{\xi})^\mathsf{T}
\end{align*}\]

&lt;p&gt;자 이제 원래 내적(inner product)에 넣어서 계산해보자. 내적을 trace inner product로 표현하고, cyclic property of trace inner product ($Tr(ABC) = Tr(BCA) = Tr(CBA)$)를 사용하면 다음과 같다.&lt;/p&gt;

\[\begin{align*}
\langle \nabla_{w^{l}} f(\xi),\nabla_{w^{l}} f(\bar{\xi}) \rangle &amp;amp;= \dfrac{1}{n^{l} n^{l-1}} \langle dh^l x^{l-1 \mathsf{T}}, d\bar{h}^l \bar{x}^{l-1 \mathsf{T}} \rangle \\
&amp;amp;=\dfrac{1}{n^{l} n^{l-1}} Tr\left( \left(dh^l x^{l-1 \mathsf{T}} \right)^\mathsf{T} d\bar{h}^l \bar{x}^{l-1 \mathsf{T}} \right) \\
&amp;amp;=\dfrac{1}{n^{l} n^{l-1}} Tr\left( x^{l-1} dh^{l \mathsf{T}} d\bar{h}^l \bar{x}^{l-1 \mathsf{T}} \right) \\
&amp;amp;=\dfrac{1}{n^{l} n^{l-1}} Tr\left( x^{l-1} \left(dh^{l \mathsf{T}} d\bar{h}^l \right) \bar{x}^{l-1 \mathsf{T}} \right) \\
&amp;amp;=\dfrac{1}{n^{l} n^{l-1}} Tr\left( \left(dh^{l \mathsf{T}} d\bar{h}^l \right) \bar{x}^{l-1 \mathsf{T}} x^{l-1}  \right) \\
&amp;amp;=\left(\dfrac{dh^{l \mathsf{T}} d\bar{h}^l}{n^{l}} \right)  \left( \dfrac{\bar{x}^{l-1 \mathsf{T}} x^{l-1}}{n^{l-1}} \right)\\
&amp;amp;=\left(\dfrac{dh^{l \mathsf{T}} d\bar{h}^l}{n^{l}} \right)  \left( \dfrac{x^{l-1 \mathsf{T}} \bar{x}^{l-1}}{n^{l-1}} \right)\\
\end{align*}\]

&lt;p&gt;마지막에 $Tr$이 사라지는 것은 $dh^{l \mathsf{T}} \in \mathbb{R}^{1\times n^l}, d\bar{h}^l \in \mathbb{R}^{n^l \times 1}$이고, $\bar{x}^{l-1 \mathsf{T}} \in \mathbb{R}^{1\times n^{l-1}}, x^{l-1} \in  \mathbb{R}^{n^{l-1} \times 1}$이라서 각각 scalar 값이 나오기 때문이다. 그러기에 맨 마지막 식에서 \(\bar{x}^{l-1 \mathsf{T}} x^{l-1}\)이 \(x^{l-1 \mathsf{T}} \bar{x}^{l-1}\)로 변환될 수 있다.&lt;/p&gt;

&lt;p&gt;결론적으로 NTK를 두 입력 $\xi, \bar{\xi}$에 대해 decompose하면 \(x^{l-1 \mathsf{T}} \bar{x}^{l-1}\)와 \(dh^{l \mathsf{T}} d\bar{h}^{l \mathsf{T}} / n^l\)의 곱으로 표현할 수 있고, 이는 각각 forward와 backward quantity라고 간주할 수 있다. 다음 두 섹션은 각 quantity가 어떤 값으로 수렴하는지에 대한 논의를 진행하고자 한다.&lt;/p&gt;

&lt;h3 id=&quot;limits-of-forward-quantities-xl-mathsft-barxl--nl&quot;&gt;Limits of Forward Quantities $x^{l \mathsf{T}} \bar{x}^{l} / n^l$&lt;/h3&gt;

&lt;p&gt;기존에 &lt;a class=&quot;citation&quot; href=&quot;#poole2016exponential&quot;&gt;[6]&lt;/a&gt;, &lt;a class=&quot;citation&quot; href=&quot;#schoenholz2016deep&quot;&gt;[7]&lt;/a&gt;에서 딥러닝을 mean-field theory로 설명하고자 했다. 이 논문들에서 나온 아이디어를 바탕으로 $\bar{x}^{l \mathsf{T}} x^{l}$를 분석할 수 있다.&lt;/p&gt;

&lt;p&gt;평균장 이론(mean field theory)은 원래 통계물리학에서 각 개별입자가 전체 시스템의 평균적 효과에 의해 영향을 받는다고 가정하여 계의 거동을 설명하는 이론이다. 물리학에서의 복잡계를 딥러닝이라고 간주하면, 각 뉴런을 입자에 대응시킬 수 있고, 입자의 상호작용을 평균적인 필드에 대해서 설명하는 mean field theory를 딥러닝에 적용할 수 있게 된다. 유체역학에서 control volume을 사용하는것과 아이디어는 비슷하다고 생각된다. 자세한 내용은 다음 영상을 확인하면 좋다.&lt;/p&gt;

&lt;style&gt;.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }&lt;/style&gt;
&lt;div class=&quot;embed-container&quot;&gt;    &lt;iframe title=&quot;YouTube video player&quot; width=&quot;640&quot; height=&quot;390&quot; src=&quot;//www.youtube.com/embed/FlR8CvyaE4I&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;여기서 중요한 것은 mean field theory를 적용할때는 각 요소는 독립적으로 행동한다고 가정하기 때문에, weight와 bias는 각각 가우시안 분포를 따른다고 가정한다.&lt;/p&gt;

&lt;p&gt;자 이 이론을 가지고 \(\dfrac{x^{l \mathsf{T}} \bar{x}^{l}}{n}\)이 다음과 같이 deterministic한 scalar \(C^l(\xi, \bar{\xi})\)로 수렴한다는 것을 보이고자 한다.&lt;/p&gt;

\[\begin{align}
\dfrac{x^{l \mathsf{T}} \bar{x}^{l}}{n} \rightarrow C^l(\xi, \bar{\xi})
\end{align}\]

&lt;p&gt;\(\dfrac{x^{l \mathsf{T}} \bar{x}^l}{n}\)는 두 벡터 \(x^l\)와 \(\bar{x}^l\)의 내적의 평균이다.
또한 \((x^l_\alpha, \bar{x}^l_\alpha)\)는 직관적으로 roughly i.i.d.라고 가정할 수 있다. 이렇게 되는 이유는 weight와 bias는 guassian이지만 지속적으로 weight와 bias가 곱해지고 더해지기 때문에 레이어가 지날 수록 다른 input \(\xi, \bar{\xi}\)에 대해서 correlated되었다고 생각할 수 있기 때문이다.&lt;/p&gt;

&lt;p&gt;공교롭게도, Covariance의 기본적인 정의는 다음과 같다. 여기서 쓰이는 \(\bar{x}, \bar{y}\)는 확률 변수 \(X\)와 \(Y\)의 평균을 뜻한다.&lt;/p&gt;

\[\begin{align}
Cov(X, Y) \approx \dfrac{1}{n} \sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y})
\end{align}\]

&lt;p&gt;그리고 확률 변수 \(X\)와 \(Y\)가 Gaussian 분포라면 평균은 0이기 때문에 위 식은 다음과 같이 변한다.&lt;/p&gt;

\[\begin{align}
\mathrm{Cov}(X, Y) \approx \dfrac{1}{n} \sum_{i=1}^n x_i y_i
\end{align}\]

&lt;p&gt;그리고 두 벡터 \(\mathbf{x}, \mathbf{y}\)의 내적의 평균은 일반식으로 다음과 같이 표현된다.&lt;/p&gt;

\[\begin{align}
\dfrac{1}{n} \mathbf{x} \cdot \mathbf{y} = \dfrac{1}{n} \sum_{i=1}^n x_i y_i
\end{align}\]

&lt;p&gt;동일하지 않은가! 즉, \(\dfrac{x^{l \mathsf{T}} \bar{x}^l}{n}\)는 결국 공분산(Covariance)를 구하는 문제로 환원될 수 있다.&lt;/p&gt;

&lt;p&gt;이를 확인하기 위해 레이어 $l$의 요소 \(\alpha \in [n^l]\)를 기준으로 MLP Layer를 풀어써보고자 한다. $\alpha$에 대해서 좌표 $(W^l x^{l-1})$는 다음과 같이 표현할 수 있다.&lt;/p&gt;

\[\begin{align}
(W^l x^{l-1})_\alpha = \sum_{\beta=1}^n W_{\alpha \beta}^l x_\beta^{l-1}
\end{align}\]

&lt;p&gt;이는 \(h^l(\xi) = W^l x^{l-1}(\xi) + b^l\)을 각 원소 좌표 $\alpha, \beta$에 대해 풀어쓴거라고 볼 수 있다. \(W_{\alpha \beta}^l x_\beta^{l-1}\)는 roughly i.i.d. random variable 이다. 처음 레이어에서는 완벽하게 i.i.d.를 맞춰서 샘플링하더라도 훈련이 진행되면서 weight의 분포가 달라지거나 레이어 간의 상관관계가 생겨서 i.i.d.가정이 깨지기 때문이다. 하지만 i.i.d.처럼 취급한다. 그런 이유로 Gaussian distribution을 따르는 roughly i.i.d. 랜덤변수의 평균은 0이고, \(W_{\alpha \beta}^l x_\beta^{l-1}\)의 분산은 다음과 같이 구할 수 있다. ($Var(X)=E(X^2) - [E(X)]^2$)&lt;/p&gt;

\[\begin{align*}
\mathbb{E}(W^l x^{l-1})^2_\alpha &amp;amp;= \mathbb{E}((W^l x^{l-1})^2_\alpha)\\
&amp;amp;= \mathbb{E}\left(\left(\sum_{\beta=1}^n W_{\alpha \beta}^l x_\beta^{l-1}\right)^2\right) \\
&amp;amp;= \mathbb{E}\left(\left(W_{\alpha 1}x_1 + W_{\alpha 2}x_2 + \cdots + W_{\alpha n}x_n\right)^2\right) \\
&amp;amp;= \mathbb{E}(W_{\alpha 1}^2 x_1^2 + W_{\alpha 2}^2 x_2^2 + \cdots + W_{\alpha n}^2 x_n^2 + 2 W_1 x_1 W_2 x_2 ) \\
&amp;amp;= \mathbb{E}(W_{\alpha 1}^2 x_1^2 + W_{\alpha 2}^2 x_2^2 + \cdots + W_{\alpha n}^2 x_n^2 ) \\
&amp;amp;= \mathbb{E}(W_{\alpha 1}^2 x_1^2) + \mathbb{E}(W_{\alpha 2}^2 x_2^2) + \cdots + \mathbb{E}(W_{\alpha n}^2 x_n^2 ) \\
&amp;amp;= \mathbb{E}(W_{\alpha 1}^2) \mathbb{E}(x_1^2) + \mathbb{E}(W_{\alpha 2}^2) \mathbb{E}(x_2^2) + \cdots + \mathbb{E}(W_{\alpha n}^2) \mathbb{E}(x_n^2 ) \\
&amp;amp;= \sum_{\beta=1}^n \mathbb{E}((W_{\alpha \beta}^l)^2) \mathbb{E}((x_{\beta}^{l-1})^2) \\
&amp;amp;= \lVert x \rVert^2 / n^{l-1} \\
&amp;amp;\approx C^{l-1} (\xi, \xi)
\end{align*}\]

&lt;p&gt;위 식에 필요한 정보는 roughly i.i.d. 특성에 의해 \(\mathbb{E}(2 W_1 x_1 W_2 x_2 ) = 2 \mathbb{E}(W_1) \mathbb{E}(x_1) \mathbb{E}(W_2) \mathbb{E}(x_2) = 0\)이 된다는 점과, NTK의 Lecun initialization에 의해 \(W_{\alpha \beta}^l \sim \mathcal{N} \left(0, \dfrac{1}{n^l}\right)\) 라는 점이다. 여기서 $C$는 어떤 deterinstic한 scalar값이다.&lt;/p&gt;

&lt;p&gt;위의 분산과 Central Limit Theorem을 적용하면 \((W^l x^{l-1})_\alpha \sim \mathcal{N} (0, C^{l-1} (\xi, \xi))\)이 되며 마찬가지로 $\bar{x}$에 대해서도 \((W^l \bar{x}^{l-1})_\alpha \sim \mathcal{N} (0, C^{l-1} (\bar{\xi}, \bar{\xi}) )\) 로 나타낼 수 있다. 두 랜덤 변수 \(((W^l x^{l-1})_\alpha, (W^l \bar{x}^{l-1})_\alpha )\), 즉 \((x^{l-1}_\alpha, \bar{x}^{l-1}_\alpha)\)는 jointly Gaussian이며 이들의 covariance는 \(C^{l-1} (\xi, \bar{\xi})\) 이다.&lt;/p&gt;

&lt;p&gt;\(x^{l} (\xi) = \phi (h^l (\xi))\) 이고, \(h\)는 이미 선형 변환(linear transform)이라는 점을 알고 있고, \(phi\)는 activation function에 의한 비선형 변환(nonlinear transform)이라는 것을 알 수 있다. roughly i.i.d. 특성 덕분에 \((x^l_\alpha, \bar{x}^l_\alpha)\)는 \((\phi(\xi), \phi(\bar{\xi}))\)와 같은 분포라고 이야기할 수 있다.
그러면 \(\mathbb{E}(\phi(\xi))=0, \mathbb{E}(\phi(\bar{\xi}))=0\) 특성과 결합하면 Covariance는 다음과 같이 표현이 가능하다.&lt;/p&gt;

\[\begin{align}
\mathrm{Cov}(x^l_\alpha, \bar{x}^l_\alpha) &amp;amp;= \mathrm{Cov}(\phi(\xi), \phi(\bar{\xi}) \\
&amp;amp;= \mathbb{E}(\phi(\xi), \phi(\bar{\xi}) - \mathbb{E}(\phi(\xi))\mathbb{E}(\phi(\bar{\xi}) \\
&amp;amp;= \mathbb{E}(\phi(\xi), \phi(\bar{\xi})
\end{align}\]

&lt;p&gt;Lecun initialization에 의해 bias는 $b \sim \mathcal{N} (0, 1)$이라고 정의되며, 이를 종합하면 이 전 layer의 scalar \(C^{l-1}\)에 의존하는
&lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;의 Eq. (6)이 나오게 된다.&lt;/p&gt;

\[\begin{align}
C^{l} (\xi, \bar{\xi}) = \mathbb{E} (\phi(\xi) \phi(\bar{\xi})), \textrm{ where } \\
(\xi, \bar{\xi}) \sim \mathcal{N} \left(0, \begin{pmatrix}
C^{l-1} (\xi, \xi) &amp;amp; C^{l-1} (\xi, \bar{\xi}) \\
C^{l-1} (\xi, \bar{\xi}) &amp;amp; C^{l-1} (\bar{\xi}, \bar{\xi})
\end{pmatrix}
  + 1 \right)
\end{align}\]

&lt;h3 id=&quot;limits-of-backward-quantities-dhl-mathsft-dbarhl--nl&quot;&gt;Limits of Backward Quantities $dh^{l \mathsf{T}} d\bar{h}^{l} / n^l$&lt;/h3&gt;

&lt;p&gt;우선 각 layer의 크기가 다르면 복잡하므로 \(n^1 = \cdots = n^L\)이라고 가정한 뒤 시작한다. 그리고 \(dh^l = \sqrt{n^{l}} \nabla_{h^{l}} f(\xi)\)을 정의했던 것처럼 \(dx^l_\alpha\)를 \((W^{l+1 \mathsf{T}}dh^{l+1})_\alpha\)이라고 정의한다. 이는 레이어 $l+1$에서 $l$로 전파되는 gradient이다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Backpropagation을 돌이켜보면 forward propagation은 다음과 같이 전파된다.
  \(\begin{align*}
  h^l (\xi) = W^l x^{l-1} (\xi) + b^l
  \end{align*}\)&lt;/li&gt;
  &lt;li&gt;각 레이어의 결과 $h^l$은 activation function $\phi$를 통해 최종 출력 $x^l$로 변환된다.
  \(\begin{align*}
  x^l = \phi(h^l)
  \end{align*}\)&lt;/li&gt;
  &lt;li&gt;최종 출력 레이어에서 loss function $\mathcal{L}$에 대한 출력 값의 gradient를 계산한다.
  \(\begin{align*}
  \nabla_{x^L} \mathcal{L}
  \end{align*}\)&lt;/li&gt;
  &lt;li&gt;여기서 나온 $\nabla_{x^L} \mathcal{L}$의 backpropagation 과정의 첫번째 gradient이다.
각 레이어 l에 대해 activation function의 미분 $\phi’$를 적용해서 기울기를 구한다. 이 때 $\odot$은 Hadamard product를 의미한다.
  \(\begin{align*}
  \nabla_{h^l} \mathcal{L} = \nabla_{x^l} \mathcal{L} \odot \phi&apos;(h^l)
  \end{align*}\)&lt;/li&gt;
  &lt;li&gt;MLP 레이어의 weight와 bias에 대해 loss에 대한 gradient를 계산한다. 이 때 bias는 batch 전체의 weight를 더해주어야 한다.
  \(\begin{align*}
  \nabla_{W^l} \mathcal{L} = \nabla_{h^l} \mathcal{L} \cdot (x^{l-1})^{\mathsf{T}}, \nabla_{b^l} \mathcal{L} &amp;amp;= \sum \nabla_{h^l} \mathcal{L}
  \end{align*}\)&lt;/li&gt;
  &lt;li&gt;현재 레이어 $l$의 weight $W$의 trace를 사용하여 이전 레이어 $l-1$의 출력에 대한 gradient를 계산한다.
  \(\begin{align*}
  \nabla_{x^{l-1} \mathcal{L}} = (W^l)^{\mathsf{T}} \nabla_{h^l} \mathcal{L}
  \end{align*}\)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 과정을 새로운 \(d x^l_\alpha\)의 정의와 결합하면 다음과 같다. 위에서 사용한 $\nabla$대신 $d$를 써준다고 생각하면 이해하기 쉽다.&lt;/p&gt;

\[\begin{align}
d x^l_\alpha &amp;amp;\stackrel{\text{def}}{=} (W^{l+1 \mathsf{T}}dh^{l+1})_\alpha \\
&amp;amp;= (W^{l+1 \mathsf{T}} (dx^{l+1} \odot \phi&apos;(h^{l+1})))_\alpha \\
&amp;amp;= \sum_\beta W^{l+1}_{\beta \alpha} dx^{l+1}_{\beta} \phi&apos; (h^{l+1}_\beta)
\end{align}\]

&lt;p&gt;전부가 i.i.d.라서 central limit theorem을 사용하면 좋겠지만, \(h^{l+1}_\beta\)는 모든 $\gamma$에 대해 \(W^{l+1}_{\beta \gamma}\)에 의존하기 때문에 i.i.d. 를 만족하지 못한다.
그러나, &lt;a class=&quot;citation&quot; href=&quot;#poole2016exponential&quot;&gt;[6]&lt;/a&gt;와 (missing reference)에 따르면, 이 의존성은 무시해도 된다고 한다. 이는 mean field theory에서 뉴런의 크기 $n^l$이 크다면, weight와 bias는 독립적으로 작용하여 이전 레이어의 영향을 받지 않고 $h$는 이런 $W$와 $b$의 weighted sum이라고 표현할 수 있기 때문이다.&lt;/p&gt;

&lt;p&gt;이를 통해 &lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;에서는 다음과 같은 아주 재미있는 Heuristic을 사용하게 된다. &lt;a class=&quot;citation&quot; href=&quot;#schoenholz2016deep&quot;&gt;[7]&lt;/a&gt;의 Section 4와  &lt;a class=&quot;citation&quot; href=&quot;#yang2017mean&quot;&gt;[8]&lt;/a&gt;의 Axiom 3.2를 참고하면 되겠다. Forward와 Backward pass의 weight가 서로 독립적이라니 이 얼마나 재밌는 가정이지 않은가!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Heuristic 4.1 (gradient independent assumption, or GIA), For any matrix \(W\), we assume \(W^{\mathsf{T}}\) used in backprop is independent from \(W\) used in forward pass.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;여튼, 위 가정과 함께 Limits of Forward Quantities \(\bar{x}^{l-1 \mathsf{T}} x^{l-1}\)에서 했던 방식을 똑같이 적용할 수 있다. Backward pass의 weight는 forward pass때의 weight와 독립적이니 동일한 방식을 의심없이 적용할 수 있게 되었다.
그러면 $x$대신에 $dh$로 기호를 바꾼셈이 되어버려서 \(d x^{l}_\alpha\)는 \(\mathcal{N}(0, \lVert d h^{l+1} \rVert^2 / n^{l+1})\) 분포를 따르며 \(\alpha\)에 대해 roughly i.i.d.를 만족한다고 할 수 있다.
또한 pair \((dx^l_\alpha, d \bar{x}^l_\alpha) \stackrel{\text{def}}{=} ((W^{l+1 \mathsf{T}}dh^{l+1})_\alpha, (W^{l+1 \mathsf{T}}d \bar{h}^{l+1})_\alpha)\) 역시 zero mean과 \(\lVert d h^{l+1} d \bar{h}^{l+1} \rVert^2 / n^{l+1}\)를 만족하는 \(\alpha\)에 대한 i.i.d.분포라고 할 수 있다. (jointly Gaussian)
\(dx^l_\alpha\)뿐만 아니라 \(h\)로 확장하면 \((h^{l}, \bar{h}^{l})\) 역시 roughly i.i.d이기 때문에  \((dh^{l}_\alpha, \bar{h}^{l}_\alpha) = (d x_\alpha^{l} \phi&apos;(h^l_\alpha), d \bar{x}_\alpha^{l} \phi&apos;(\bar{h}^l_\alpha) )\) 도 비슷한 결과를 가진다고 얘기할 수 있다.&lt;/p&gt;

&lt;p&gt;이로써 Backward quantities $dh^{l \mathsf{T}} d\bar{h}^{l} / n^l$에 도달하였다.
이전 섹션에서의 $C$대신 Scalar $D^l (\xi, \bar{\xi})$를 도입하여 variance를 표현하면 다음과 같은 backward quantities의 covariance는 $D$로 수렴한다.&lt;/p&gt;

\[\begin{align}
\dfrac{dh^{l \mathsf{T}} d\bar{h}^{l}}{n^l} \rightarrow D^l (\xi, \bar{\xi})
\end{align}\]

&lt;p&gt;그리고 $D^l (\xi, \bar{\xi})$ 도 다음과 같은 재귀함수로 정의된다.&lt;/p&gt;

\[\begin{align}
D^l (\xi, \bar{\xi}) &amp;amp;= \mathbb{E}_{\eta \bar{\eta}} \mathbb{E} \phi&apos;(\xi) \phi&apos;(\bar{\xi}) = D^{l+1} (\xi, \bar{\xi}) \mathbb{E} \phi&apos;(\xi) \phi&apos;(\bar{\xi}) \\
\textrm{ where } (\eta, \bar{\eta}) &amp;amp;\sim \mathcal{N} \left(0, \begin{pmatrix}
D^{l+1} (\xi, \xi) &amp;amp; D^{l+1} (\xi, \bar{\xi}) \\
D^{l+1} (\xi, \bar{\xi}) &amp;amp; D^{l+1} (\bar{\xi}, \bar{\xi})
\end{pmatrix}\right) \\
(\xi, \bar{\xi}) &amp;amp;\sim \mathcal{N} \left(0, \begin{pmatrix}
C^{l} (\xi, \xi) &amp;amp; C^{l} (\xi, \bar{\xi}) \\
C^{l} (\xi, \bar{\xi}) &amp;amp; C^{l} (\bar{\xi}, \bar{\xi})
\end{pmatrix}
  + 1 \right)
\end{align}\]

&lt;h3 id=&quot;foward-quantities-xl-mathsft-barxl--nl--backward-quantities-dhl-mathsft-dbarhl--nl&quot;&gt;Foward Quantities $x^{l \mathsf{T}} \bar{x}^{l} / n^l$ + Backward Quantities $dh^{l \mathsf{T}} d\bar{h}^{l} / n^l$&lt;/h3&gt;

&lt;p&gt;이전에 NTK Decomposition을 사용하여 다음과 같이 유도하였다.&lt;/p&gt;

\[\begin{align*}
\langle \nabla_{w^{l}} f(\xi),\nabla_{w^{l}} f(\bar{\xi}) \rangle = \left(\dfrac{dh^{l \mathsf{T}} d\bar{h}^l}{n^{l}} \right)  \left( \dfrac{\bar{x}^{l-1 \mathsf{T}} x^{l-1}}{n^{l-1}} \right)
\end{align*}\]

&lt;p&gt;지금까지 각 항에 대해 정의한 $C$와 $D$에 대해 표현하면 다음과 같다.&lt;/p&gt;

\[\begin{align*}
\langle \nabla_{w^{l}} f(\xi),\nabla_{w^{l}} f(\bar{\xi}) \rangle = C^{l-1} (\xi, \bar{\xi}) D^{l} (\xi, \bar{\xi}), \forall l \in [2, L]
\end{align*}\]

&lt;p&gt;마찬가지로, bias에 대해서도 $\nabla_{b^l} f(\xi) = \nabla_{h^l} f(\xi) = dh^l / \sqrt{n^l}$이므로,&lt;/p&gt;

\[\begin{align*}
\langle \nabla_{b^{l}} f(\xi),\nabla_{b^{l}} f(\bar{\xi}) \rangle = D^{l} (\xi, \bar{\xi}), \forall l \in [2, L]
\end{align*}\]

&lt;p&gt;기존 NTK 정의와 결합하면
\(\begin{align}
\Theta (\xi, \bar{\xi}) &amp;amp;= \langle \nabla_\theta f(\xi; \theta_0), \nabla_\theta  f(\bar{\xi}; \theta_0) \rangle \\
&amp;amp;= \sum_{l=1}^{L+1} \langle \nabla_{w^{l}} f(\xi),\nabla_{w^{l}} f(\bar{\xi}) \rangle + \sum_{l=1}^L \langle \nabla_{b^{l}} f(\xi),\nabla_{b^{l}} f(\bar{\xi}) \rangle \\
&amp;amp;= \sum_{l=1}^{L+1} C^{l-1} (\xi, \bar{\xi}) D^{l} (\xi, \bar{\xi}) + \sum_{l=1}^L D^{l} (\xi, \bar{\xi}) \\
\end{align}\)&lt;/p&gt;

&lt;p&gt;이를 통해 MLP에 대해서 기존 NTK보다 훨씬 심플하게 $C$와 $D$라는 Scalar의 곱으로 표현하였다. 이 논문의 키 포인트는 NTK로부터 해당 식을 유도하고, 이를 다른 아키텍처 즉 CNN이나 RNN으로 확장하고자 하는 것이다.&lt;/p&gt;

&lt;h2 id=&quot;ntk---any-architecture&quot;&gt;NTK -&amp;gt; Any Architecture&lt;/h2&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#yang2020tensor&quot;&gt;[3]&lt;/a&gt;은 지금까지의 과정이 과연 generalized될 수 있는가에 대해 독자들이 생각할 수 있는 질문에 대한 답을 미리 준비해 놓았다. 지금까지의 과정은 MLP였기 때문에 가능한 것이 아닌가라는 의심은 당연한 것이기 때문이다.&lt;/p&gt;

&lt;h3 id=&quot;ntk-decomposition을-의미있게-일반화할-수-있는가&quot;&gt;NTK Decomposition을 의미있게 일반화할 수 있는가?&lt;/h3&gt;

&lt;p&gt;다음 분해는 MLP라는 가정하에서 이루어졌다. 그러나 \(\frac{dh^{l \mathsf{T}} d\bar{h}^l}{n^l}\)같은 값들이 수렴할지 어떻게 알 수 있으며, 발산하지 않는다는 보장이 어디 있는가?
\(\begin{align}
\Theta (\xi, \bar{\xi}) &amp;amp;= \langle \nabla_\theta f(\xi; \theta_0), \nabla_\theta  f(\bar{\xi}; \theta_0) \rangle \\
&amp;amp;= \sum_{l=1}^{L+1} \langle \nabla_{w^{l}} f(\xi),\nabla_{w^{l}} f(\bar{\xi}) \rangle + \sum_{l=1}^L \langle \nabla_{b^{l}} f(\xi),\nabla_{b^{l}} f(\bar{\xi}) \rangle \\
\end{align}\)&lt;/p&gt;

&lt;p&gt;이에 대해 저자는 NTK를 NTK Decomposition, 즉 inner product의 곱셈의 합으로 분해할 수 있도록 일반화할 수 있다고 생각한다. NTK가 수렴한다면 말이다. 이는 다음 섹션 Strategy for Computing the Infinite-Width NTK에서 어떻게 일반화할지 보여줄 예정이다.&lt;/p&gt;

&lt;h3 id=&quot;gia를-계속-가정해도-되는가&quot;&gt;GIA를 계속 가정해도 되는가?&lt;/h3&gt;

&lt;p&gt;아까도 재밌다고 언급했는데, forward pass와 backward pass의 gradient가 서로 독립적이라는 가정이 과연 지속적으로 유효한 가정인가에 대한 의문은 있을 수 있다.&lt;/p&gt;

&lt;p&gt;이에 대한 저자는 다음 조건하에서 GIA를 만족한다고 한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Conditon 1 (Simple GIA Check) The output layer (like \(W^{L+1}\) in the MLP above) is sampled independently and with zero mean from all other parameters and is not used anywhere else in the interior of the network&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이는 Backpropgation의 경우 output layer를 통해 forward pass와 backward pass가 상호작용할 수 있기 때문이다.
자세한 것은 Strategy for Computing the Infinite-Width NTK에서 다룰 예정이다.&lt;/p&gt;

&lt;h3 id=&quot;현대의-복잡한-신경망에-대해-적용할-수-있는가&quot;&gt;현대의 복잡한 신경망에 대해 적용할 수 있는가?&lt;/h3&gt;

&lt;p&gt;CNN, RNN, LSTM 등 뿐만 아니라 ResNet, transformer에도 NTK Decompositon을 적용할 수 있는가에 대해서 당연히 의문이 들 수 밖에 없다.&lt;/p&gt;

&lt;p&gt;저자 말로는 저자가 만든 NETSOR\(\mathsf{T}\) 언어 (NETSOR의 확장판)로 표현할 수 있는 네트워크는 적용할 수 있다고 한다. NETSOR을 처음 봤을 때는 이걸 굳이 따로 만들어야 하는 이유가 있나라는 의문이 있었는데, 이제 조금 납득이 간다. 하지만, 이미 포스트가 너무 길기 때문에 NETSOR은 다른 포스트에서 다룰 예정이다.&lt;/p&gt;

&lt;h2 id=&quot;strategy-for-computing-the-infinite-width-ntk&quot;&gt;Strategy for Computing the Infinite-Width NTK&lt;/h2&gt;

&lt;p&gt;위에서 MLP에 적용한 NTK Decomposition을 일반적인 방법론으로 설명하고자 한다.&lt;/p&gt;

&lt;h3 id=&quot;the-canonical-decomposition&quot;&gt;The Canonical Decomposition&lt;/h3&gt;

&lt;p&gt;NTK Decomposition에 대해서 일반적인 방법론으로 설명하고자 한다.&lt;/p&gt;

&lt;p&gt;우선 준비물을 알아보자. 일단 $\xi \in \mathbb{R}^d$를 입력으로 하고 출력은 scalar인 신경망 \(f(\xi)\)가 필요하다. 신경망 \(f(\xi)\)은 weight \(W \in \mathbb{R}^{n\times m}\)과 bias \(b \in \mathbb{R}^{n}\) 으로 이루어져있으며, 어떤 벡터 \(y(\xi) \in \mathbb{R}^n\), \(z(\xi) \in \mathbb{R}^m\)에 대해서 \(y(\xi) = W z(\xi)\) 형식으로 이루어진다.
그동안 다루었던 MLP를 예로 들면 레이어 \(l\)에 대해서 \(y(\xi) = h^l (\xi), z(\xi) = x^{l-1}(\xi)\)라고 할 수 있으며 모든 weight들이 같다면 (\(W^1 = W^2 = \cdots = W^L\)) \((y,z) = \{ (h^2, x^1), \dots, (h^L, x^{L-1})\}\)이라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;\(W\)를 바로 사용하기보다 \(\omega \in \mathbb{R}^{n\times m}\)에 대해 \(W = \dfrac{1}{\sqrt{m}} \omega\)라고 factorize한 뒤 \(\omega\)에 포커싱한다. 이런 경우 \(f\)에 대한 NTK \(\Theta\)는 다음과 같이 sum의 형태로 나타나게 된다.&lt;/p&gt;

\[\begin{align}
\Theta(\xi, \bar{\xi}) = \sum_\omega \langle \nabla_\omega f(\xi), \nabla_\omega f(\bar{\xi}) \rangle + \sum_b \langle \nabla_b f(\xi), \nabla_b f(\bar{\xi}) \rangle
\end{align}\]

&lt;p&gt;MLP의 경우, \(W^1 = W^2 = \cdots = W^L \in \mathbb{R}^{n \times n}\)와 \(W=\dfrac{1}{\sqrt{n}} \omega\)에 따라서, \(\nabla_\omega f(\xi) = \dfrac{1}{n} \sum_{l=1}^{L-1} dh^{l+1} x^{l \mathsf{T}}\) 이고 다음과 같이 분해했다.&lt;/p&gt;

\[\begin{align*}
\langle \nabla_\omega f(\xi), \nabla_\omega f(\bar{\xi}) \rangle &amp;amp;= \dfrac{1}{n^2} \langle \sum_{l=1}^{L-1} dh^{l+1}x^{l \mathsf{T}}, \sum_{\mathscr{l}=1}^{L-1} d\bar{h}^{\mathscr{l}+1}\bar{x}^{\mathscr{l} \mathsf{T}} \rangle\\
&amp;amp;= \dfrac{1}{n^2} \sum_{l,\mathscr{l}=1}^{L-1} \langle dh^{l+1}x^{l \mathsf{T}}, d\bar{h}^{\mathscr{l}+1}\bar{x}^{\mathscr{l} \mathsf{T}}  \rangle \\
&amp;amp;= \sum_{l,\mathscr{l}=1}^{L-1} \dfrac{dh^{l+1 \mathsf{T}} d\bar{h}^{\mathscr{l+1}}}{n} \dfrac{x^{l \mathsf{T}} \bar{x}^{\mathscr{l}}}{n}
\end{align*}\]

&lt;p&gt;일반적인 케이스로 확장하면, \(f\)의 두 입력 \(\xi, \bar{\xi}\)에 대해서 \(\bar{y} = y(\bar{\xi}), \bar{z} = z(\bar{\xi}), dy=\sqrt{n}\nabla_y f(\xi), d\bar{y} = \sqrt{n} \nabla_\bar{y} f(\xi)\)라고 하면, \(\nabla_\omega f\)는 다음과 같이 표현된다.&lt;/p&gt;

\[\begin{align}
\langle \nabla_\omega f(\xi), \nabla_\omega f(\bar{\xi}) \rangle &amp;amp;= \dfrac{1}{m} \langle \nabla_W f(\xi), \nabla_W f(\bar{\xi}) \rangle \\
&amp;amp;= \dfrac{1}{mn} \left\langle \sum_{y,z} dy \; z^\mathsf{T}, \sum_{\bar{y},\bar{z}} d\bar{y} \; \bar{z}^\mathsf{T} \right\rangle \\
&amp;amp;= \dfrac{1}{mn} \sum_{y,z,\bar{y}, \bar{z}} \langle  dy \; z^\mathsf{T}, d\bar{y} \; \bar{z}^\mathsf{T} \rangle \\
&amp;amp;= \sum_{y,z,\bar{y}, \bar{z}} \dfrac{dy^{\mathsf{T}} d\bar{y}}{n} \dfrac{z^\mathsf{T} \bar{z}}{m}
\end{align}\]

&lt;p&gt;이 summation은 \(y=Wz, \bar{y} = W \bar{z}\)를 포함한 모든 행렬 곱셈에 대해서 이루어진다.&lt;/p&gt;

&lt;p&gt;\(w\)와 \(b\)가 NTK parameterization에 의해 standard Gaussian 분포에서 추출된다면 \(\dfrac{dy^{\mathsf{T}} d\bar{y}}{n}\)와 \(\dfrac{z^\mathsf{T} \bar{z}}{m}\)가 각각 determinisitc하게 limit \(D^{y,\bar{y}} (\xi, \bar{\xi})\), \(C^{y,\bar{y}} (\xi, \bar{\xi})\)로 수렴할 것이다. (다음 섹션에서 증명할 예정) 마찬가지로 \(\nabla_b f(\xi), \nabla_\bar{b} f(\bar{\xi})\)도 \(D^b (\xi, \bar{\xi})\)로 수렴한다면 Limiting NTK Kernel \(\mathring{\Theta}\)은 다음과 같이 정리된다.&lt;/p&gt;

\[\begin{align}
\mathring{\Theta} (\xi, \bar{\xi}) = \sum_{\textrm{weight} W} \sum_{\substack{y,z:y=Wz \\ \bar{y},\bar{z}:\bar{y}=W\bar{z}}} D^{y,\bar{y}} (\xi, \bar{\xi}) C^{y,\bar{y}} (\xi, \bar{\xi}) + \sum_{\textrm{bias } b} D^b (\xi, \bar{\xi})
\end{align}\]

&lt;h3 id=&quot;c와-d를-구하는-직관적인-규칙들&quot;&gt;\(C\)와 \(D\)를 구하는 직관적인 규칙들&lt;/h3&gt;

&lt;p&gt;결국 NTK Decomposition은 \(C\)와 \(D\)를 어떻게 구하냐의 문제로 귀결된다. GIA Check Condition을 만족한다면 (output layer가 독립적으로 샘플링 되고 zero mean을 가진다면), 이번 섹션에서 다루는 직관은 \(C\)와 \(D\)를 계산하는데 있어 핵심적인 아이디어이다.&lt;/p&gt;

&lt;p&gt;Wide Neural Network를 가정하자. (width \(n &amp;gt;&amp;gt; 1\)) (pre-)activation vector \(x \in \mathbb{R}^n\)는 roughly i.i.d. coordinate을 가지고 있다고 할 수 있으며 이 coordinate들은 랜덤 변수 \(Z^x\)에서 추출되었다고 표현한다. 이는 벡터의 원소의 분포가 roughly i.i.d.라는 말과 다름 없지만 벡터의 성분이 하나의 coordinate처럼 생각할 수 있기에 표현할 수 있는 말이다.
하지만 \(x \in \mathbb{R}^n\)에 대한 랜덤변수 집합 \(\{Z^x \}_x\)은 correlated되었을 가능성이 있다.
그것은  좌표 \(\alpha \in [n]\)에 대해 \(\{ x_\alpha \}_x\)가 이미 correlated되어있을 수 있기 때문이다. 하지만, \(\alpha\)에 대해 roughly i.i.d.를 만족한다.&lt;/p&gt;

&lt;p&gt;따라서 \(n \rightarrow \infty\)일 때, 벡터 \(x, y \in \mathbb{R}^n\)은 다음 식을 만족하며 이것은 \(C\)와 \(D\)를 구할 때 필요한 형태이다.&lt;/p&gt;

\[\begin{align}
x^\mathsf{T} y / n \rightarrow \mathbb{E} Z^x Z^y
\end{align}\]

&lt;p&gt;복잡해보인다. 그러나 설명을 좀 더 하자면 결국 우리가 원하는 것은 \(x^\mathsf{T} y / n\)의 형태를 어떻게 구하냐이고, 이는 roughly i.i.d.를 만족하는 랜덤변수 \(Z\)에 의해 기대값으로 표현될 수 있다. \(x\)와 \(y\)를 곱하고 이를 \(n\)으로 나누는 것은 기대값(평균)을 구하는 것과 큰 차이가 없다.&lt;/p&gt;

&lt;p&gt;따라서, 다음과 같은 2가지 규칙을 이용하여 activation function에 해당하는 &lt;strong&gt;Nonlin&lt;/strong&gt;규칙과 Weihgt에 해당하는 &lt;strong&gt;MatMul&lt;/strong&gt;규칙을 정의하고 이를 이용하면 재귀적으로 \(Z^x\)를 계산할 수 있어 \(C\)와 \(D\)를 구할 수 있다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Nonlin&lt;/strong&gt; 어떤 고정된 \(k\) (\(n\rightarrow \infty\)일때의 constant)에 대해서 \(\phi : \mathbb{R}^k \rightarrow \mathbb{R}\) 함수에 대해서 다음과 같이 표현될 수 있다.
\(\begin{align}
Z^{\phi(x^1, \dots, x^k)} = \phi(Z^{x^1}, \dots, Z^{x^k})
\end{align}\)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;MatMul&lt;/strong&gt; \(\mathbb{R}^n\)의 벡터의 집합 \(\mathcal{X}\)와 행렬 \(W \in \mathbb{R}^{n\times n}\)이 있을 때, \(W_{\alpha \beta} \sim \mathcal{N}(0, \sigma_W^2 /n )\)을 만족하면 다음과 같은 랜덤변수 \(\{Z^{Wx} : x\in\mathcal{X}\}\)은 jointly Gaussian이고 zero mean을 만족하며 다음과 같은 covariance를 가진다.
\(\begin{align}
\mathrm{Cov}(Z^{Wx}, Z^{W\bar{x}}) = \sigma_W^2 \mathbb{E} Z^{x} Z^{\bar{x}}, \textrm{   for any } x, \bar{x} \in \mathcal{X}
\end{align}\)
만약, 또 다른 \(\mathbb{R}^n\) 벡터 집합 \(\mathcal{Y}\)가 있고 \(W \neq \bar{W}\)이면, \(\{Z^{Wx} : x\in\mathcal{X}\}\)는 \(\{Z^{\bar{W}y} : y\in\mathcal{Y}\}\)와 독립적이다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;여기에 몇 가지 Remark가 더 붙는다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Remark 6.1. 규칙 2번은 \(W\)가 \(\mathcal{X}\)의 벡터와 correlated되더라도 성립한다. 예를 들면, \(x, \bar{x} \in \mathcal{X}\)일 때 \(x=W\bar{x}\) 이거나 \(x=W^\mathsf{T} \bar{x}\)여도 성립한다.&lt;/li&gt;
  &lt;li&gt;Remark 6.2. 규칙 2번에서 \(\bar{W} = W^\mathsf{T}\)이면, \(\{Z^{\bar{W}y} : y\in\mathcal{Y}\}\)와 \(\{Z^{Wx} : x\in\mathcal{X}\}\)은 독립적이라는 의미이다. 이는 GIA Simple Check Condition에 따라 GIA가 적용되는 원리와 같다.&lt;/li&gt;
  &lt;li&gt;Remark 6.3. 고정된 차원의 입력 \(\xi\)을 Wide neural network 계산하기 위해서, 위의 규칙들을 \(\xi\)에 바로 적용하지 않고 첫번쨰 레이어 임베딩인 \(W \xi \in \mathbb{R}^n\)부터 적용한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;규칙 1번 &lt;strong&gt;Nonlin&lt;/strong&gt;은 쉽게 이해할 수 있다. nonlinear function을 적용한 벡터 \(x^i\)들의 집합 \(Z\)나 각 집합 \(Z^{x^i}\)에 nonlinear function을 적용한 것들을 비교하나 전체 집합으로 보면 같기 때문이다.&lt;/p&gt;

&lt;p&gt;규칙 2번 &lt;strong&gt;MatMul&lt;/strong&gt; 또한 Limit of Foward Quantities \(x^{l \mathsf{T}} \bar{x}^{l} / n^l\) 섹션에서 봤던 직관을 생각하면 이해할 수 있다. Weight \(W \in \mathbb{R}^{n \times n}\)가 \(W_{\alpha \beta} \sim \mathcal{N}(0, \sigma_W^2 /n)\)을 따를 때 zero mean을 유지하는 선형 변환(linear transformation)은 기존 입력값 $x$의 분포를 바꾸지 못한다. 따라서 공분산을 계산할 때 \(Z^{Wx}\)대신에 \(Z^x\)를 써도 무방하고 zero mean이기 때문에 forward quantities 섹션에서 다음 식과 같이 covariance를 구했던것과 같은 직관을 사용할 수 있다.&lt;/p&gt;

\[\begin{align}
\mathrm{Cov}(x^l_\alpha, \bar{x}^l_\alpha) &amp;amp;= \mathrm{Cov}(\phi(\xi), \phi(\bar{\xi}) \\
&amp;amp;= \mathbb{E}(\phi(\xi), \phi(\bar{\xi}) - \mathbb{E}(\phi(\xi))\mathbb{E}(\phi(\bar{\xi}) \\
&amp;amp;= \mathbb{E}(\phi(\xi), \phi(\bar{\xi})
\end{align}\]

&lt;p&gt;Remark 6.1.의 경우에는 roughly i.i.d.이기 때문에 가능한 것이다.
Remark 6.2.의 경우는 \(\bar{W} \equiv W^{\mathsf{T}} \neq W\)이기 때문에 \(\mathcal{Y}\)와 독립이라고 할 수 있다.
Remark 6.3.의 경우는 \(\xi\)는 특정 차원에 고정되어있기 때문에 해당 룰을 바로 적용하기 힘들다. 그러나, weight는 Wide network를 가정하고 있기 때문에 첫번쨰 레이어만 한번 변환을 거치고 적용할 수 있다. 그렇게 되면 induction에 의해 나머지 레이어도 같은 룰을 적용할 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;rnn&quot;&gt;RNN&lt;/h3&gt;

&lt;p&gt;위의 룰을 RNN에 적용해보자. RNN은 시간 \(t\)에 대해 현재의 입력 \(\xi^t\)과 이전 상태(state) \(s^{t-1}\)에 기반해서 현재 상태(state) \(s^t\)를 다음과 같이 업데이트 된다.&lt;/p&gt;

\[\begin{align}
s^t (\xi) = \phi(g^t (\xi) + u^t (\xi) + b), \; g^t(\xi) = W s^{t-1} (\xi), \; u^t(\xi) = U \xi^t
\end{align}\]

&lt;p&gt;추가적인 기호를 설명하자면 input sequence는 \(\xi = \{ \xi^1, \dots, \xi^t, \dots, \xi^t \in \mathbb{R}^d \}\),
nonlinear activation function을 \(\phi\), weight는 \(W \in \mathbb{R}^{n \times n}, U \in \mathbb{R}^{n \times d}\), 그리고 bias \(b \in \mathbb{R}^n\)이다. 출력을 위한 output weight를 \(v \in \mathbb{R}^n\)이라고 하면 최종 시간 \(T\)에서의 상태 \(s^T (\xi)\)와 결합한 RNN의 output은 \(v^{\mathsf{T}} s^T (\xi) \in \mathbb{R}\)이라고 할 수 있다.
이전과 마찬가지로, 각 weight들이 다음과 같은 분포를 따른다고 하자. \(W_{\alpha \beta} \sim \mathcal{N}(0, 1/n), U_{\alpha \beta} \sim \mathcal{N}(0, 1/d), b_\alpha \sim \mathcal{N} (0, 1), v_\alpha \sim \mathcal{0, 1}\). 그러면 Condition 1은 자동으로 만족하고 위에서 언급한 &lt;strong&gt;Nonlin&lt;/strong&gt;과 &lt;strong&gt;MatMul&lt;/strong&gt; 규칙도 만족한다.&lt;/p&gt;

&lt;p&gt;또 다른 input \(\bar{\xi} = \{ \bar{\xi}^1, \dots, \bar{\xi}^t, \dots, \bar{\xi}^t \in \mathbb{R}^d \}\)도 가정해 볼 수 있다. 물론, \(\xi = \bar{\xi}\)도 가능하다.
이제 지금까지의 규칙을 적용해서 NTK Decomposition을 수행하면 된다. RNN에서의 weight matrix는 \(W\)와 \(U\) 두 개가 있다. 각각 기존의 룰을 적용해서 문제를 정의해보면 다음과 같다.
우선 \(W\)은 \(g^t(\xi) = W s^{t-1} (\xi)\)을 만족하므로 이전에 살펴보았던 double sum(\(\sum_{y,z}, \sum_{\bar{y}, \bar{z}}\))처럼 표현해 볼 수 있다.&lt;/p&gt;

\[\begin{align*}
\langle \nabla_\omega f(\xi), \nabla_\omega f(\bar{\xi}) \rangle = \sum_{y,z,\bar{y}, \bar{z}} \dfrac{dy^{\mathsf{T}} d\bar{y}}{n} \dfrac{z^\mathsf{T} \bar{z}}{m}
\end{align*}\]

&lt;p&gt;위 식을 \(\{g^t, s^{t-1}\}, \{\bar{g}^t, \bar{s}^{t-1}\}\)에 대해 적용하면, 우리가 풀어야할 문제는 어떤 sequence \(t\)와 \(r\)에 대해서 \(\dfrac{s^{t \mathsf{T}} \bar{s}^r}{n}, \dfrac{g^{t \mathsf{T}} \bar{g}^r}{n}\)을 푸는 것으로 환원된다.
마찬가지로, weight \(U\)는 \(u^t(\xi) = U \xi^t\)이므로, \(\{u^t, \xi^{t-1}\}, \{\bar{u}^t, \bar{\xi}^{t-1}\}\)에 대해 double sum의 형태로 바꾸면, \(\dfrac{u^{t \mathsf{T}} \bar{u}^r}{n}, \dfrac{\xi^{t \mathsf{T}} \bar{\xi}^r}{d}\)을 구하는 것으로 바뀌지만 \(\dfrac{\xi^{t \mathsf{T}} \bar{\xi}^r}{d}\)은 weight가 아니라서 constant이므로 계산할 필요가 없다.&lt;/p&gt;

&lt;h4 id=&quot;forward&quot;&gt;Forward&lt;/h4&gt;

&lt;p&gt;섹션 “\(C\)와 \(D\)를 구하는 직관적인 규칙들”에서 나온 프레임워크를 적용하기 위해서는 벡터들과 행렬들을 랜덤변수 \(Z\)로 변환해야 한다.
우선, 고정된 input dimension \(d\)가 있고, vector dimension \(n \rightarrow \infty\)라고 해보자.
\(g^t, u^t, s^t, b\)는 \(Z^{g^t}, Z^{u^t}, Z^{s^t}, Z^{b}\)에서 추출한 i.i.d. coordinate를 가지고 있다고 생각한다, 이 말은 앞서 언급했던 것처럼 i.i.d. 변수라는 의미와 같다.
이제 하나씩 살펴보자.
가장 간단한 변수는 \(b\)이다. \(Z^b = \mathcal{N} (0, 1)\)이라고 할 수 있다.
그 다음은 \(u\)이다. \(\{Z^{u^t}, Z^{\bar{u}^t}\}\)는 zero mean을 가지고 covariance \(\mathrm{Cov}(Z^{u^t}, Z^{\bar{u}^t) = \xi^{t \mathsf{T}} \bar{\xi}^r /d\)를 가지는 jointly Gaussian 분포라고 할 수 있다.
지금까지는 Canonical Decomposition에서 MLP example과 같이 비교적 쉽게 이해할 수 있는 방법으로 적용한 것이고, 그 다음은 vector에서 vector로 변환하는 \(g^t(\xi) = W s^{t-1} (\xi)\)과 같은 경우에도 적용해야한다.
&lt;strong&gt;MatMul&lt;/strong&gt; 규칙을 적용하면 \(\{Z^{g^t}, Z^{\bar{g}^r}\}\)는 zero mean에 다음과 같은 Covariance를 가지고 있다.&lt;/p&gt;

\[\begin{align}
\mathrm{Cov}(Z^{g^t}, Z^{\bar{g}^r}) = \mathbb{E} Z^{s^{t-1}}, Z^{\bar{s}^{r-1}}
\end{align}\]

&lt;p&gt;이 모든 것을 종합하면 MLP에서 \(C^l (\xi, \bar{xi})\)를 구했던 것과 같은 방식을 통해 다음과 같이 재귀식 형태로 정리할 수 있다.&lt;/p&gt;

\[\begin{align}
\mathbb{E} Z^{s^{t}}, Z^{\bar{s}^{r}} &amp;amp;=
    \mathbb{E}  \phi(Z^{g^{t}} + Z^{u^{t}} + Z^{b})
                \phi(Z^{\bar{g}^{r}} + Z^{\bar{u}^{r}} + Z^{b}) \\
&amp;amp;=  \mathbb{E}  \phi (\xi_1) \phi (\xi_2) \\
\textrm{where } (\xi_1, \xi_2) &amp;amp;\sim \mathcal{N} \left(0, \mathbb{E} \begin{pmatrix}
\left(Z^{s^{t-1}}\right)^2 &amp;amp; Z^{s^{t-1}} Z^{\bar{s}^{r-1}} \\
Z^{\bar{s}^{r-1}} Z^{s^{t-1}} &amp;amp; \left(Z^{\bar{s}^{r-1}}\right)^2
\end{pmatrix}  + \dfrac{\xi^{t \mathsf{T}} \xi^r}{d} + 1 \right)
\end{align}\]

&lt;p&gt;이를 통해 limit \(C^{s^t, \bar{s}^r} (\xi, \bar{\xi})\)은 다음과 같이 계산된다.&lt;/p&gt;

\[\begin{align}
C^{s^t, \bar{s}^r} (\xi, \bar{\xi}) = \lim_{n \rightarrow \infty} \dfrac{s^{t \mathsf{T}} \bar{s}^r }{n} = \mathbb{E} (Z^{s^t} Z^{\bar{s}^r})
\end{align}\]

&lt;h4 id=&quot;backward&quot;&gt;Backward&lt;/h4&gt;

&lt;p&gt;위에서 Backpropagation을 돌아봤듯이, RNN의 backpropagation은 다음과 같이 정의할 수 있다.&lt;/p&gt;

\[\begin{align}
d s^{t-1} = W^{\mathsf{T} dg^t, \; dg^{t} = du^t = \phi&apos;(g^t + u^t + b)  \odot d s^t
\end{align}\]

&lt;p&gt;MLP에서 처럼 \(d s^t\)는 \(W\)때문에 의존성이 걸려서 strict하게 i.i.d.라고 생각할 수 없지만 기존 논문의 가정을 이용하여 i.i.d.라고 가정한다.
따라서, \(d s^t\)를 \(Z^{ds^t}\)에서 추출된 i.i.d. coordinate라고 생각할 수 있고, 다음을 만족한다.&lt;/p&gt;

\[\begin{align}
\mathbb{E} Z^{ds^t} Z^{d\bar{s}^r} &amp;amp;= \mathbb{E} Z^{du^{t+1}} Z^{d\bar{u}^{r+1}} \\
&amp;amp;= \mathbb{E} \phi&apos;(Z^{g^{t+1}} + Z^{u^{t+1}} + Z^{b}) Z^{ds^{t+1}} \phi&apos;(Z^{\bar{g}^{r+1}} + Z^{u^{r+1}} + Z^{b}) Z^{d \bar{s}^{r+1}} \\
&amp;amp;= \mathbb{E} Z^{ds^{t+1}} Z^{d \bar{s}^{r+1}} \mathbb{E} \phi&apos;(Z^{g^{t+1}} + Z^{u^{t+1}} + Z^{b})  \phi&apos;(Z^{\bar{g}^{r+1}} + Z^{u^{r+1}} + Z^{b})  \\
&amp;amp;= \mathbb{E} Z^{ds^{t+1}} Z^{d \bar{s}^{r+1}} \mathbb{E} \phi&apos;(\xi_1)  \phi&apos;(\xi_2)  \\
\textrm{where } (\xi_1, \xi_2) &amp;amp;\sim \mathcal{N}\left(0, \mathbb{E} \begin{pmatrix}
\left(Z^{s^{t}}\right)^2 &amp;amp; Z^{s^{t}} Z^{\bar{s}^{r}} \\
Z^{\bar{s}^{r}} Z^{s^{t}} &amp;amp; \left(Z^{\bar{s}^{r}}\right)^2
\end{pmatrix}  + \dfrac{\xi^{t \mathsf{T}} \xi^r}{d} + 1 \right)
\end{align}\]

&lt;p&gt;마찬가지로, 위의 재귀식은 다음과 같은 limit \(D^{s^t, \bar{s}^r} (\xi, \bar{\xi})\)은 다음과 같이 정리된다\dots&lt;/p&gt;

\[\begin{align}
&amp;amp; D^{s^t, \bar{s}^r} (\xi, \bar{\xi}) = \lim_{n \rightarrow \infty} \dfrac{ds^{t \mathsf{T}} d\bar{s}^r }{n} = \mathbb{E} Z^{ds^t} Z^{\bar{ds}^r}\\
&amp;amp;= D^{u^{t+1}, \bar{u}^{r+1}} (\xi, \bar{\xi}) = \lim_{n \rightarrow \infty} \dfrac{du^{t+1 \mathsf{T}} d\bar{u}^{r+1} }{n} = \mathbb{E} Z^{du^{t+1}} Z^{\bar{du}^{r+1}}
\end{align}\]

&lt;p&gt;이렇게 \(C\)와 \(D\)를 알았으니, 다음 식을 이용해서 NTK를 구할 수 있다.&lt;/p&gt;

\[\begin{align}
\mathring{\Theta} (\xi, \bar{\xi}) = \sum_{\textrm{weight} W} \sum_{\substack{y,z:y=Wz \\ \bar{y},\bar{z}:\bar{y}=W\bar{z}}} D^{y,\bar{y}} (\xi, \bar{\xi}) C^{y,\bar{y}} (\xi, \bar{\xi}) + \sum_{\textrm{bias } b} D^b (\xi, \bar{\xi})
\end{align}\]

&lt;h3 id=&quot;simple-gia-check의-중요성&quot;&gt;Simple GIA Check의 중요성&lt;/h3&gt;

&lt;p&gt;Simple GIA Check Condition은 output layer가 다른 레이어들의 모든 파라미터와 독립이어야 하고, 내트워크 내부의 다른 파트에서 사용하지 않는다는 조건이다.&lt;/p&gt;

&lt;h4 id=&quot;simple-gia-check을-만족하지-않는-경우&quot;&gt;Simple GIA Check을 만족하지 않는 경우&lt;/h4&gt;

&lt;p&gt;예를 들어, 마지막 임베딩 레이어의 평균을 내서 output으로 삼는다면 이 조건이 깨지게 된다. 만약 평균이 내면 어떻게 GIA에 작용되는지 살펴보자.&lt;/p&gt;

&lt;p&gt;심플하게 2-hidden-layer network를 가정하자&lt;/p&gt;

\[\begin{align}
x^1 &amp;amp;= W^1 \xi + 1 \\
h^2 &amp;amp;= W^2 x^1 \\
x^2 &amp;amp;= \phi(h^2)  \\
y &amp;amp;= \mathbb{1}^\mathsf{T} x^2 / n \\
\phi(z) &amp;amp;= z^2
\end{align}\]

&lt;p&gt;각 벡터와 행렬의 차원은 다음과 같다.
\(\xi = 0 \in \mathbb{R}^d, y\in \mathbb{R}, x^1, h^2, x^2 \in \mathbb{R}^n,
W^1 \mathbb{R}^{n\times d}, W^2 \in \mathbb{R}^{n\times n},
W^1_{\alpha \beta} \sim \mathcal{N} (0, 1/d), W^2_{\alpha \beta} \sim \mathcal{N} (0, 1/n)\)
만약에 \(dx^2 = n \dfrac{dy}{dx^2}\)부터 시작하면, backpropgation은 다음과 같이 정리할 수 있다.&lt;/p&gt;

\[\begin{align*}
dx^2 = 1, dh^2 = 2h^2  \odot 1 = 2h^2, dx^1 = W^{2 \mathsf{T}} dh^2 = 2W^{2 \mathsf{T}} h^2 = 2W^{2 \mathsf{T}} W^{2} x^2
\end{align*}\]

&lt;p&gt;&lt;strong&gt;MatMul&lt;/strong&gt; 에 의해서 \(h^2\)는 \(Z^{h^2} = \mathcal{N} (0, 1)\)에서 추출한 coordinate를 가진다고 할 수 있고,
\(dh^2\)또한 \(Z^{dh^2} = 2Z^{h^2} = \mathcal{N}(0, 2)\)에서 추출한 coordinate라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;기존 가정을 그대로 사용해서 \(W^{2 \mathsf{T}}\)와 \(W^2\)가 독립이라고 하자. 그러면 \(dx^1\)또한 \(\mathcal{N}(0, 2)\)에서 추출한 coordinate가 되어야 한다.
그러나, 다음과 같은 식을 통해 \(\mathbb{E} dx^1\)는 \(0\)이 되지 않는다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E} dx^1_\alpha &amp;amp;= 2 \mathbb{E} \sum_{\beta, \gamma} W^2_{\beta \alpha} W^2_{\beta \gamma} x^1_\gamma  \\
&amp;amp;= 2 \sum_\beta \mathbb{E} \left((W^2_{\beta \gamma})^2 x^1_\alpha\right) + 2 \sum_\beta \sum_{\gamma \neq \alpha} \mathbb{E} (W^2_{\beta \alpha} W^2_{\beta \gamma} x^1_\gamma ) \\
&amp;amp;= 2\mathbb{E} x^1_\alpha + 0 \\
&amp;amp;= 2\mathbb{E} x^1_\alpha \\
&amp;amp;= 2 \neq 0
\end{align*}\]

&lt;p&gt;\(\mathbb{E} (W^2_{\beta \gamma})^2 = 1\)을 만족하는 반면,
\(W^2_{\beta_\alpha}, W^2_{\beta \gamma}, x^1_\gamma\)가 독립이기 때문에 각각의 기대값의 곱으로 바뀔 수 있고
이는 위의 정의에 따라 0이다.
\(2 \sum_\beta \sum_{\gamma \neq \alpha} \mathbb{E} (W^2_{\beta \alpha} W^2_{\beta \gamma} x^1_\gamma) =
2 \sum_\beta \sum_{\gamma \neq \alpha} \mathbb{E} W^2_{\beta \alpha}  \mathbb{E} W^2_{\beta \gamma} \mathbb{E} x^1_\gamma = 0\)&lt;/p&gt;

&lt;h4 id=&quot;simple-gia-check이-gia를-만족하는-직관&quot;&gt;Simple GIA Check이 GIA를 만족하는 직관&lt;/h4&gt;

&lt;p&gt;만약 이전처럼 평균을 내는 것이 아니라 마지막 레이어가 전부 독립이라면,
\(v_\alpha \sim \mathcal{N}(0,1)\)에서 추출한 \(v\)를 바탕으로
\(y= v^\mathsf{T} x^2 / \sqrt{n}\) 처럼 되고, Simple GIA Check Condition을 만족하게 된다.
\(\mathbb{E} v_b (W^2_{\beta \gamma})^2 x^1_\alpha = \mathbb{E} v_b \mathbb{E} (W^2_{\beta \gamma})^2 \mathbb{E} x^1_\alpha = 0\)
그렇게 되면 \(dx^2 = \sqrt{n} \dfrac{dy}{dx^2}\)에서 시작하는 backpropgation을 다시 계산할 수 있게 된다. (\(v\) 추가)&lt;/p&gt;

&lt;p&gt;따라서 마찬가지로 \(dx^1\)의 기대값을 구하게되면 독립인 \(v_\beta\)의 영향때문에 모든 항이 0이 된다.&lt;/p&gt;

\[\begin{align*}
\mathbb{E} dx^1_\alpha &amp;amp;= 2 \sum_\beta \mathbb{E} \left( v_\beta (W^2_{\beta \gamma})^2 x^1_\alpha\right) + 2 \sum_\beta \sum_{\gamma \neq \alpha} \mathbb{E} (v_\beta W^2_{\beta \alpha} W^2_{\beta \gamma} x^1_\gamma ) \\
&amp;amp;= 0
\end{align*}\]

&lt;p&gt;즉, 마지막 layer가 \(W\)와 \(W^{\mathsf{T}}\)가 서로 연결(correlated)될 가능성을 차단하는 것이다.
이것이 Simple GIA Check Condition이 GIA를 만드는 직관이다.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;지금까지, 일반적인 뉴럴 네트워크를 NTK를 확장하는 방법을 알아보았다.
다른 아키텍처를 지니더라도 NTK의 특성을 적용할 수 있는 근거를 마련할 수 있었다.&lt;/p&gt;

&lt;p&gt;결국 이 논문을 3줄 요약하면 다음과 같다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;NTK는 inner product 2개(forward &amp;amp; backward)의 곱(product)로 표현할 수 있고, 각각을 \(C\)와 \(D\)로 표현하며, 이 둘은 covariance의 limit을 통해 표현 가능하다.&lt;/li&gt;
  &lt;li&gt;GIA를 만족하는 뉴럴 네트워크는 1.처럼 변환할 수 있다. (NTK화) 이걸 쉽게 확인하는 건 NETSOR\(\mathsf{T}\)를 만족하는지만 확인하면 되는데 이는 이 포스트의 한계를 넘어섰기에 생략한다.&lt;/li&gt;
  &lt;li&gt;GIA는 forward와 backward pass에서의 weight들이 서로 관련이 없다는 가정인데,
이는 output layer가 zero mean을 가지고 다른 파라미터(weight)들과 서로 독립적이며,
뉴럴 네트워크 다른 곳에서 사용하지 않는다는 조건만 만족하면 성립한다.
간단하게 말해서 output layer를 평균낸다는가 하는 일을 저지르지 않으면 된다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;근데, Greg Yang은 천재인가? 이걸 혼자 썼다고? 아니다. 그는 천재다.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;yang2022tensor&quot;&gt;[1]G. Yang &lt;i&gt;et al.&lt;/i&gt;, “Tensor programs v: Tuning large neural networks via zero-shot hyperparameter transfer,” &lt;i&gt;arXiv preprint arXiv:2203.03466&lt;/i&gt;, 2022.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;yang2023spectral&quot;&gt;[2]G. Yang, J. B. Simon, and J. Bernstein, “A spectral condition for feature learning,” &lt;i&gt;arXiv preprint arXiv:2310.17813&lt;/i&gt;, 2023.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;yang2020tensor&quot;&gt;[3]G. Yang, “Tensor programs ii: Neural tangent kernel for any architecture,” &lt;i&gt;arXiv preprint arXiv:2006.14548&lt;/i&gt;, 2020.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;yang2019wide&quot;&gt;[4]G. Yang, “Wide feedforward or recurrent neural networks of any architecture are gaussian processes,” &lt;i&gt;Advances in Neural Information Processing Systems&lt;/i&gt;, vol. 32, 2019.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;jacot2018neural&quot;&gt;[5]A. Jacot, F. Gabriel, and C. Hongler, “Neural tangent kernel: Convergence and generalization in neural networks,” &lt;i&gt;Advances in neural information processing systems&lt;/i&gt;, vol. 31, 2018.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;poole2016exponential&quot;&gt;[6]B. Poole, S. Lahiri, M. Raghu, J. Sohl-Dickstein, and S. Ganguli, “Exponential expressivity in deep neural networks through transient chaos,” &lt;i&gt;Advances in neural information processing systems&lt;/i&gt;, vol. 29, 2016.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;schoenholz2016deep&quot;&gt;[7]S. S. Schoenholz, J. Gilmer, S. Ganguli, and J. Sohl-Dickstein, “Deep information propagation,” &lt;i&gt;arXiv preprint arXiv:1611.01232&lt;/i&gt;, 2016.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;yang2017mean&quot;&gt;[8]G. Yang and S. Schoenholz, “Mean field residual networks: On the edge of chaos,” &lt;i&gt;Advances in neural information processing systems&lt;/i&gt;, vol. 30, 2017.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>What is Attention Mechanism? (The Meaning of K, Q, V)</title>
   <link href="https://blog.liam.kim/posts/2024/03/What-Is-K-Q-V-in-Transformer/"/>
   <updated>2024-03-24T11:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2024/03/What-Is-K-Q-V-in-Transformer</id>
   <content type="html">&lt;h2 id=&quot;why-k-q-v&quot;&gt;Why K, Q, V?&lt;/h2&gt;

&lt;p&gt;예전에 Transformer에서 K, Q, V의 의미가 무엇이냐는 질문을 받았을 때 갑자기 머리가 멍해지면서 제대로 답변을 못한 적이 있었다.
그런데 막상 찾아보면, 그 의미를 명확히 전달해주는 글은 잘 없었다.
원래 논문&lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt;을 찾아보라고 보통 애기하지만,
이걸 제대로 보려면 &lt;a class=&quot;citation&quot; href=&quot;#bengio2000neural&quot;&gt;[2]&lt;/a&gt;,
&lt;a class=&quot;citation&quot; href=&quot;#bahdanau2014neural&quot;&gt;[3]&lt;/a&gt;,
&lt;a class=&quot;citation&quot; href=&quot;#sutskever2014sequence&quot;&gt;[4]&lt;/a&gt;,
&lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt;로 이어지는 흐름을 전부 이해해야 한다고 생각한다.&lt;/p&gt;

&lt;p&gt;과거에 내가 Transformer를 배운건 &lt;a href=&quot;https://jalammar.github.io/illustrated-transformer/&quot;&gt;The Illustrated Transformer&lt;/a&gt;를 통해서였지만, 이걸 봐도 그래서 K, Q, V가 뭔데?라는 의문은 여전히 해소하지 못한대로 라이브러리에 구현한걸 그대로 가져다 썼다.
하지만 이제는 그때와 같은 이해도는 아닐뿐더러
리서처 입장에서는 최근 흐름상 점점 더 Transformer의 K, Q, V를 근본적으로 건드리는 논문들도 많아지기 때문에,
엔지니어 입장에서는 KV Cache 같은 걸 적용해야하는 상황이 생기기 때문에 이 문제를 단순 라이브러리 적용으로 해결할 수는 없을 것이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;그러나 이 질문을 쉽게 설명하기는 힘들다. 굉장한 논문들이지만, 이 많은 논문들을 다 읽고 이해하는건 쉬운 일이 아니기 때문이다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이에 대해 고민을 하다가 정말 좋은 설명을 찾았다. 최근에 &lt;a href=&quot;https://www.youtube.com/watch?v=VMj-3S1tku0&amp;amp;list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ&amp;amp;index=2&quot;&gt;Andrej Karpathy의 NN: Zero to Hero&lt;/a&gt;가 그것이다. 이 플레이리스트를 보고 따라하면서, 머리에 해머를 맞은듯한 충격을 받았다. (참고로 딥러닝을 처음 접하는 분들에게 딥러닝을 어떻게 배우냐고 질문이 들어오면 이 플레이리스트를 먼저 추천해주고 싶다.) 내가 그동안 너무 어렵게 생각했던 부분도 있었고, 잘못 생각하고 있던 부분이 있다는걸 깨달았다. 여기에 위 질문에 대한 답이 있었다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;정확히는 &lt;a href=&quot;https://www.youtube.com/watch?v=kCc8FmEb1nY&quot;&gt;Let’s build GPT: from scratch, in code, spelled out.&lt;/a&gt;와 &lt;a href=&quot;&amp;quot;https://www.youtube.com/watch?v=XfpMkf4rD6E&amp;quot;&quot;&gt;Stanford CS25: V2 I Introduction to Transformers w/ Andrej Karpathy&lt;/a&gt;가 그 답이었다.&lt;/strong&gt; 이 두 비디오는 Transformer를 설명하는 최고의 강의라고 생각한다. 그래서 Attention에서 K,Q,V가 어떻게 나오게 되었는지 정리를 해보았다.&lt;/p&gt;

&lt;style&gt;.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }&lt;/style&gt;
&lt;div class=&quot;embed-container&quot;&gt;    &lt;iframe title=&quot;YouTube video player&quot; width=&quot;640&quot; height=&quot;390&quot; src=&quot;//www.youtube.com/embed/kCc8FmEb1nY&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2 id=&quot;language-model-and-text-generation&quot;&gt;Language Model and Text Generation&lt;/h2&gt;

&lt;p&gt;일단 우리가 생각하는 언어 모델(Language Model)에 대해서 생각해볼 필요가 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/01-text-generation.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/01-text-generation.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://bansalh944.medium.com/text-generation-using-lstm-b6ced8629b03&amp;quot;&amp;gt;Text Generation Using LSTM&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/01-text-generation.png&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://bansalh944.medium.com/text-generation-using-lstm-b6ced8629b03&amp;quot;&amp;gt;Text Generation Using LSTM&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://bansalh944.medium.com/text-generation-using-lstm-b6ced8629b03&quot;&gt;Text Generation Using LSTM&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;텍스트 생성(Text generation) 관점에서의 언어모델이란, 이전의 문맥(Context)를 통해 다음 단어(실제로 토큰)의 확률 분포를 예측하고, 가장 높은 확률의 단어를 샘플링한 뒤, 가장 높은 확률의 단어를 &lt;strong&gt;생성&lt;/strong&gt;한다고 볼 수 있다.&lt;/p&gt;

&lt;p&gt;이 때 할 수 있는 질문은 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;단어만으로 충분한가?&lt;/li&gt;
  &lt;li&gt;단어의 확률은 어떻게 계산할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;text-to-numbers&quot;&gt;Text to Numbers&lt;/h2&gt;

&lt;h3 id=&quot;단어만으로-충분한가&quot;&gt;단어만으로 충분한가?&lt;/h3&gt;

&lt;p&gt;그럴수도 있고, 아닐 수도 있다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World! My name is blah blah. Let’s delve deep into the meaning of transformer. Language Model is so capricious!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;같은 문장이 있을때, My나 name같은 단어는 쉬우니까 하나로 생각할 수 있지만,
capricious같은 단어는 하나의 단어로 생각할수도 있고 뒷부분의 ous같은 부분은 다른데서도 재사용가능하니까 쪼갤 수 있어보인다.
이렇게 단어보다 조금 더 잘게 쪼갠 파트를 모델에서 숫자로 변환하서 학습하게 된다.&lt;/p&gt;

&lt;p&gt;이렇게 &lt;strong&gt;문장을 모델에서 사용할 수 있는 단위(토큰, Token)로 만드는것을 토큰화(Tokenize)&lt;/strong&gt;라고 하고, 그 작업을 해주는 프로그램을 토크나이저(Tokenizer)라고 한다. 단어를 토큰으로 사용할 수도 있지만 이러면 토큰의 수가 너무 많아지기 때문에, 요즘에는 단어보다는 조금 더 작은 단위(subword) 토크나이저를 많이 쓴다.&lt;/p&gt;

&lt;p&gt;그래서 실제로 어떻게 나누는 것일까? GPT4는 위 문장을 다음과 같이 분리한다.&lt;/p&gt;
&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/02-tokenize-eng.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://huggingface.co/spaces/Xenova/the-tokenizer-playground&amp;quot;&amp;gt;&amp;quot;the-tokenizer-playground&amp;quot;에서 테스트 가능&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/02-tokenize-eng.png&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://huggingface.co/spaces/Xenova/the-tokenizer-playground&amp;quot;&amp;gt;&amp;quot;the-tokenizer-playground&amp;quot;에서 테스트 가능&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://huggingface.co/spaces/Xenova/the-tokenizer-playground&quot;&gt;&quot;the-tokenizer-playground&quot;에서 테스트 가능&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;한글은 어떨까? 아래를 보면 훨씬 복잡해보인다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;안녕, 세상아! 내 이름은 아무거나야. 트랜스포머에 대해 깊이 파헤쳐 보자! 언어 모델은 너무 변덕스러워&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/03-tokenize-kor.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://huggingface.co/spaces/Xenova/the-tokenizer-playground&amp;quot;&amp;gt;&amp;quot;the-tokenizer-playground&amp;quot;에서 테스트 가능&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/03-tokenize-kor.png&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://huggingface.co/spaces/Xenova/the-tokenizer-playground&amp;quot;&amp;gt;&amp;quot;the-tokenizer-playground&amp;quot;에서 테스트 가능&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://huggingface.co/spaces/Xenova/the-tokenizer-playground&quot;&gt;&quot;the-tokenizer-playground&quot;에서 테스트 가능&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;단어의-확률은-어떻게-계산될-수-있는가&quot;&gt;단어의 확률은 어떻게 계산될 수 있는가?&lt;/h3&gt;

&lt;p&gt;딥러닝을 사용하든, 데이터로부터 단어의 단순 빈도수를 측정하여 확률을 측정하든 단순히 확률을 계산할 할 수 있는 방법은 많다.&lt;/p&gt;

&lt;p&gt;언어 모델링은 어떻게 보면 다중 클래스 분류 문제(Multiclass Classification Problem)라고 할 수 있다.
사람도 그렇다. 말을 하다보면 문장의 순서라는게 있고, 갑자기 뜬금없는 단어가 튀어나오는 경우는 잘 없다.
어떤 특정한 단어가 선택지에 있는 것이고, 그 중에서 가장 적절한 단어를 사람이 선택하는 것이다.
모델도 특정 단어셋이 있고, 그 중에서 가장 확률이 높은 단어를 선택한다. 이 때, &lt;a href=&quot;https://blog.liam.kim/posts/2024/03/Logit-Sigmoid-Softmax/&quot;&gt;Cross Entropy&lt;/a&gt;를 많이 사용한다.&lt;/p&gt;

&lt;h3 id=&quot;토크나이저-그리고-임베딩&quot;&gt;토크나이저 그리고 임베딩&lt;/h3&gt;

&lt;p&gt;토크나이저는 단어를 더 작은 단위(subword, character chunk)로 쪼개고, 이를 정수(token id)에 매핑한다. 컴퓨터는 문자를 이해하지 못하기 때문에 이렇게 숫자 형태로 변형되어야 계산이 가능하다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/04-tokenization.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/04-tokenization.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://geoffrey-geofe.medium.com/tokenization-vs-embedding-understanding-the-differences-and-their-importance-in-nlp-b62718b5964a&amp;quot;&amp;gt;Tokenization vs. Embedding: Understanding the Differences and Their Importance in NLP&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/04-tokenization.png&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://geoffrey-geofe.medium.com/tokenization-vs-embedding-understanding-the-differences-and-their-importance-in-nlp-b62718b5964a&amp;quot;&amp;gt;Tokenization vs. Embedding: Understanding the Differences and Their Importance in NLP&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://geoffrey-geofe.medium.com/tokenization-vs-embedding-understanding-the-differences-and-their-importance-in-nlp-b62718b5964a&quot;&gt;Tokenization vs. Embedding: Understanding the Differences and Their Importance in NLP&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;하지만, 이렇게 단순히 하나의 숫자로 표현된 토큰은 정보량이 적다. 토큰의 위치라던가 의미는 다양할 수 있기 때문이다. 따라서 이를 각 토큰을 벡터로 변환하여 임베딩을 생성하게 된다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/05-embedding.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/05-embedding.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://geoffrey-geofe.medium.com/tokenization-vs-embedding-understanding-the-differences-and-their-importance-in-nlp-b62718b5964a&amp;quot;&amp;gt;Tokenization vs. Embedding: Understanding the Differences and Their Importance in NLP&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/05-embedding.png&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://geoffrey-geofe.medium.com/tokenization-vs-embedding-understanding-the-differences-and-their-importance-in-nlp-b62718b5964a&amp;quot;&amp;gt;Tokenization vs. Embedding: Understanding the Differences and Their Importance in NLP&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://geoffrey-geofe.medium.com/tokenization-vs-embedding-understanding-the-differences-and-their-importance-in-nlp-b62718b5964a&quot;&gt;Tokenization vs. Embedding: Understanding the Differences and Their Importance in NLP&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h2 id=&quot;numbers-to-generation-single-head-attention&quot;&gt;Numbers to Generation: Single-head Attention&lt;/h2&gt;

&lt;p&gt;토크나이저와 임베딩을 활용해서 어떻게든 텍스트를 컴퓨터가 해석할 수 있는 숫자로 바꾸었다. 그럼 다음 토큰은 어떻게 예측되는 것일까?
가장 단순하게 예측하는 방법은 평균을 내는 것이다.&lt;/p&gt;

&lt;p&gt;Bigram(이전 2개의 단어를 고려해서 다음 단어를 예측) 모델이 있다고 가정하자. 다음과 같이 이전 단어 (파란색) 2개를 참조해서 다음 단어 (빨간색) 단어를 예측하는 형태라고 보면 된다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/06-bigram.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;Bigram model&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/06-bigram.png&quot; style=&quot;width: 100%; &quot; title=&quot;Bigram model&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  Bigram model
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;일반적으로 &lt;em&gt;김밥과 라면을 “걷는다”&lt;/em&gt; 라고는 하지는 않는다. &lt;em&gt;“걷는다”&lt;/em&gt;라는건 김밥과 라면이라는 단어에 비해 확률이 낮은 단어이기 때문이다.
그림처럼 &lt;em&gt;“먹도록”&lt;/em&gt;이라는 단어가 더 자연스럽다.&lt;/p&gt;

&lt;p&gt;하지만 이럴 경우, 두 단어 이전에 나온 문맥(Context)을 반영하기는 쉽지 않다. 그러기에 다음과 같이 그 이전 단어까지 포함한 문맥을 파악해서 생성할 필요가 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/07-LM-context.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;Context&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/07-LM-context.png&quot; style=&quot;width: 100%; &quot; title=&quot;Context&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  Context
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;version-1-average&quot;&gt;Version 1: Average&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=2832&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;가장 간단하게 문맥을 생성하는 방법은 이전 단어들의 평균을 내는 것이다&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/08-LM-context-mean.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;Context&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/08-LM-context-mean.png&quot; style=&quot;width: 100%; &quot; title=&quot;Context&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  Context
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;위 그림을 봐도 이전 단어들에 동등한 가중지(weight)를 줄 뿐이다. 수학적으로 각 단어의 임베딩을 $[\mathbf{x}_0, \mathbf{x}_1, \cdots, \mathbf{x}_n]$라고 표현할 수 있는데, 가중치(weight)와 결합하면 다음과 같이 표현할 수 있다. (elementwise sum)&lt;/p&gt;

\[\begin{equation}
\mathbf{x_n} = \sum_{i=1}^{n-1} \dfrac{1}{n-1} \mathbf{x_i}
\end{equation}\]

&lt;p&gt;이 때 가중치(weight) $\textrm{wei}$는 $\dfrac{1}{n-1}$가 된다. 예를 들어 &lt;em&gt;“오늘”&lt;/em&gt;이라는 단어의 임베딩이 $[0.1, 0.5]$ 이고, &lt;em&gt;“점심은”&lt;/em&gt;이라는 단어의 임베딩은 $[0.6, 0.7]$이라고 하면, &lt;em&gt;“김밥과”&lt;/em&gt;라는 단어는 $[0.35, 0.6]$이 되는 것이다.&lt;/p&gt;

&lt;p&gt;이를 행렬(matrix) 연산으로 어떻게 표현할까? $\mathbf{x}$가 각 단어를 뜻한다고 가정하고 임베딩 크기(embedding size)를 2라고 가정하자. 그러면 각 행은 $\mathbf{x}&lt;em&gt;{1}$은 *오늘*, $\mathbf{x}&lt;/em&gt;{2}$는 &lt;em&gt;점심은&lt;/em&gt; 등으로 매핑된다. 한번에 4개의 단어까지 본다고 가정하고(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context_size=4&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time_length=4&lt;/code&gt;) 임베딩 크기는 2(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;embed_size=2&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel_size=2&lt;/code&gt;)라고 가정했을 때, $\mathbf{x}$는 $4 \times 2$ 행렬이다.&lt;/p&gt;

&lt;p&gt;이 때 다음 단어의 임베딩 예측값은 평균을 나타내는 가중치 행렬 $\textrm{wei}$과의 현재 단어의 임베딩 $x$ 행렬의 곱셈으로 표현이 가능하다.&lt;/p&gt;

\[\begin{bmatrix}
\mathbf{x}^{&apos;}_2 \\
\mathbf{x}^{&apos;}_3 \\
\mathbf{x}^{&apos;}_4 \\
\mathbf{x}^{&apos;}_5 \\
\end{bmatrix} =
\begin{bmatrix}
1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
0.5 &amp;amp; 0.5 &amp;amp; 0 &amp;amp; 0 \\
0.33 &amp;amp; 0.33 &amp;amp; 0.33 &amp;amp; 0 \\
0.25 &amp;amp; 0.25 &amp;amp; 0.25 &amp;amp; 0.25
\end{bmatrix}
\begin{bmatrix}
\mathbf{x}_1 \\
\mathbf{x}_2 \\
\mathbf{x}_3 \\
\mathbf{x}_4 \\
\end{bmatrix}\]

&lt;p&gt;기호 대신 임베딩 벡터 자체를 넣어서 표현하면 (임베딩 벡터 자체는 랜덤하다)&lt;/p&gt;

\[\begin{bmatrix}
0.1 &amp;amp; 0.5 \\
0.35 &amp;amp; 0.6 \\
0.33 &amp;amp; 0.693 \\
0.35 &amp;amp; 0.725 \\
\end{bmatrix} =
\begin{bmatrix}
1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
0.5 &amp;amp; 0.5 &amp;amp; 0 &amp;amp; 0 \\
0.33 &amp;amp; 0.33 &amp;amp; 0.33 &amp;amp; 0 \\
0.25 &amp;amp; 0.25 &amp;amp; 0.25 &amp;amp; 0.25
\end{bmatrix}
\begin{bmatrix}
0.1 &amp;amp; 0.5 \\
0.6 &amp;amp; 0.7 \\
0.3 &amp;amp; 0.9 \\
0.4 &amp;amp; 0.8 \\
\end{bmatrix}\]

&lt;p&gt;그러면 가중치 행렬 $\textrm{wei}$는 어떻게 만들어야 할까?&lt;/p&gt;

\[\begin{bmatrix}
1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
0.5 &amp;amp; 0.5 &amp;amp; 0 &amp;amp; 0 \\
0.33 &amp;amp; 0.33 &amp;amp; 0.33 &amp;amp; 0 \\
0.25 &amp;amp; 0.25 &amp;amp; 0.25 &amp;amp; 0.25
\end{bmatrix}\]

&lt;p&gt;그것은 1로 채워진 lower triangular matrix에 행별로 더한값을 나눠주면 된다. 코드로는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyTorch&lt;/code&gt;의 &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.tril.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tril&lt;/code&gt;&lt;/a&gt;과 &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.sum.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sum&lt;/code&gt;&lt;/a&gt;을 이용한다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;wei = torch.tril(torch.ones(4, 4))
# [1 0 0 0]
# [1 1 0 0]
# [1 1 1 0]
# [1 1 1 1]
wei = wei / torch.sum(w, 1, keepdims=True)
# [1 0 0 0] / 1.0
# [1 1 0 0] / 2.0
# [1 1 1 0] / 3.0
# [1 1 1 1] / 4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;version-2-matrix-multiplication&quot;&gt;Version 2: Matrix Multiplication&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=3117&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;여기에 Batch까지 고려하면 batch multiplication까지 갈 수 있다. 현재까지는 $x$를 ($T \times C$) 즉, (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time_length&lt;/code&gt; $\times$ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel_size&lt;/code&gt;) 행렬만 생각했지만,
($B \times T \times C$) 즉, (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;batch_size&lt;/code&gt; $\times$ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time_length&lt;/code&gt; $\times$ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel_size&lt;/code&gt;) 행렬까지 있다고 가정하자.&lt;/p&gt;

&lt;p&gt;우리의 $\textrm{wei}$ 행렬은 $T \times T$이므로, $(T \times T) \cdot (B \times T \times C)$ 형태의 곱셈이 된다.
PyTorch는 똑똑하기 때문에 $(T \times T)$에 batch dimension를 자동을 추가하여 $(B \times T \times T) \cdot (B \times T \times C) = (B \times T \times C)$ 행렬 곱셈을 수행한다. (Batch Matrix Multiply)&lt;/p&gt;

&lt;h3 id=&quot;version-3-adding-softmax&quot;&gt;Version 3: Adding Softmax&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=3282&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;지금까지는 직접 평균을 냈으나, 이제는 지금까지의 평균과정을 softmax형태로 변환해보고자 한다. 지금은 모든 토큰의 확률이 같고 정해져 있기에 상관없지만, 나중에는 모델을 통해 logit형태로 가중치가 나올것이고 이를 softmax를 이용하여 확률로 변환시키기에 필요하다.
우선 코드를 보자.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PyTorch&lt;/code&gt;의 &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.Tensor.masked_fill.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;masked_fill&lt;/code&gt;&lt;/a&gt;과 &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.nn.functional.softmax.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;softmax&lt;/code&gt;&lt;/a&gt;를 사용하였다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;T = 4
tril = torch.tril(torch.ones(T, T))
wei = torch.zeros(T, T)
# [0 0 0 0]
# [0 0 0 0]
# [0 0 0 0]
# [0 0 0 0]
wei = wei.masked_fill(tril == 0, float(&apos;-inf&apos;))
# [0 -inf -inf -inf]
# [0    0 -inf -inf]
# [0    0   0  -inf]
# [0    0   0     0]
wei = F.softmax(wei, dim=-1)
# [1.00 0.00 0.00 0.00]
# [0.50 0.50 0.00 0.00]
# [0.33 0.33 0.33 0.00]
# [0.25 0.25 0.25 0.25]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우선 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;masekd_fill&lt;/code&gt;을 통해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tril&lt;/code&gt;의 0인 부분을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-inf&lt;/code&gt;로 대체한다. 그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;softmax&lt;/code&gt;를 취하면, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-inf&lt;/code&gt;는 지수 함수 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp&lt;/code&gt;에 의해 0이 되고, 나머지 0값들은 지수함수를 적용하면 1이 되지만 행 별로(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dim=-1&lt;/code&gt;) 더해진 값에 대해 나눠지므로 위에서 그동안 봤던 $\textrm{w}$랑 동일한 행렬이 된다.&lt;/p&gt;

&lt;p&gt;이는 두 가지 의미가 있는데, 우선 현재 단어(or 토큰)는 미래의 단어(or 토큰)을 알지 못한다. 이는 당연하다. 미래의 일을 어찌 알겠는가?
또 다른 의미는 softmax를 이용하면 &lt;strong&gt;과거 토큰들이 서로 얼마나 관계를 지니고 있는지 알려준다는 점&lt;/strong&gt;이다. 예를 들어 어떤 단어는 바로 이전 단어에 강한 영향을 받을 수 있고, 아니면 좀 더 이전의 단어에 영향을 크게 받을 수도 있다. 후자의 대표적인 예시는 대명사의 활용일 때이다. 예를 들면, &lt;em&gt;“홍길동은 조선시대에 태어났다. 그는 의적이었다.”&lt;/em&gt;라는  문장에서 &lt;em&gt;그는&lt;/em&gt;이라는 단어는 &lt;em&gt;태어났다&lt;/em&gt;가 아닌 &lt;em&gt;홍길동은&lt;/em&gt;과 더 가까운 단어이다. 수치적인 다른 예시로는 전자는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0.001, 0.0001, ..., 0.9]&lt;/code&gt; 이런식으로 표현할 수 있을 것이고, 후자는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0.001, 0.7, ..., 0.01]&lt;/code&gt; 이런식으로 표현될 수도 있다.&lt;/p&gt;

&lt;p&gt;지금까지는 단순 평균을 냈지만, 단순 평균보다는 특정 부분의 단어에 집중하는게 상식적으로 더 맞는말이다. 이를 수학적으로 softmax가 &lt;strong&gt;이전 단어들간의 친화도(affinity)를 종합(aggregation)하는 역할을 수행&lt;/strong&gt;하도록 하는 것이다. 또한 미래 단어의 영향을 배제하게 하기 위해서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-inf&lt;/code&gt;를 채워 단절시킨다. 그래서 &lt;strong&gt;“어텐션”(Attention) 매커니즘&lt;/strong&gt;인 것이다. (정확히는 Self-Attention)&lt;/p&gt;

&lt;h3 id=&quot;version-4-self-attention&quot;&gt;Version 4: Self Attention&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=3719&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;해당 영상에는 positional encoding얘기도 했지만, 너무 길어지기에 일단 스킵한다. 지금까지는 모든 위치에 대해서 단순 평균을 냈기 때문에 위치를 고려할 필요가 없었다. 그러나 어텐션 메커니즘은 해당 단어 근처가 아닌 먼 위치의 정보가 중요할 수 있기에 위치의 정보도 모델에 포함시킬 필요가 있고, 이를 위해 postional encoding을 사용한다. 하지만 이 이야기를 더 하면 너무 길어지므로 다른 포스트로 따로 작성할 예정이다.&lt;/p&gt;

&lt;p&gt;다시 본론으로 돌아오자.&lt;/p&gt;

&lt;p&gt;이전까지는 각 토큰(or 단어, 이제는 토큰으로 명칭을 통일한다)의 관계(affinity)는 이전 토큰들의 평균으로 구했다.
그러나 단순 평균만으로는 복잡한 토큰들의 관계를 표현하기에는 부족하다.
그러기에 과거의 토큰의 정보를 가져 오되, 데이터에 기반해서 토큰의 관계를 계산할 필요가 있다.&lt;/p&gt;

&lt;p&gt;이를 위해 모든 토큰은 두 벡터, query와 key를 생성한다.
Karpathy의 표현을 빌리자면 query vector는 what am I looking for, key vector는 what do I contain이라고 표현하는데 이 표현이 가장 직관적인 설명이라고 생각한다.
한국어로 표현하면 query 벡터는 현재 바라보고 있는 토큰 그 자체(관심 대상)이며, key 벡터는 다른 토큰이 가지고 있는 정보(비교 대상)이다.
토큰간의 친화도(affinity) 혹은 관계란 현재 바라보는 토큰이 다른 토큰들의 정보와 얼만큼 관련있는지에 따라 달라지며, 이는 query가 key와 얼마나 잘 맞는지에 대한 것이라고 할 수 있다.
이를 정량적으로 계산하는 방법은 query과 key간의 내적(dot product)를 통해 가중치를 계산하는 방법이다. 이 가중치, 즉 dot product값이 클 수록 query와 key가 잘 매칭된다는 의미이다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;B, T, C = 4, 8, 32
x = torch.randn(B, T, C) # B=batch_size, T=time_size(token_length), C=channel_size

# single head attention
head_size = 16
key = nn.Linear(C, head_size, bias=False)
query = nn.Linear(C, head_size, bias=False)
k = key(x)  # (B, T, head_size)
q = query(x)  # (B, T, head_size)
# batch multiplication
wei = q @ k.transpose(-2, -1) # (B, T, head_size) @ (B, head_size, T) = (B, T, T)
# [[[-1.75  2.15 -1.21  0.23],
#   [ 0.35 -0.21 -0.56  0.25],
#   [ 1.21 -0.91  0.19  2.10],
#   [ 0.52  0.21 -0.12 -0.35]],...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;여기서 나오는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wei&lt;/code&gt;는 raw affinity 그 자체라고 할 수 있다. 여기에 이전 Version에서 한 것처럼 masking을 통해 미래의 토큰간의 관계를 차단하고, softmax를 취하면 확률을 구할 수 있다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;tril = torch.tril(torch.ones(T, T))
wei = wei.masked_fill(tril == 0, float(&apos;-inf&apos;))
wei = F.softmax(wei, dim=-1)
# [[[1.00 0.00 0.00 0.00],
#   [0.21 0.79 0.00 0.00],
#   [0.14 0.67 0.19 0.00],
#   [0.33 0.11 0.21 0.35]],...]
out = wei @ x
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;하지만 실제로는 query와 key로부터 나온 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wei&lt;/code&gt;는 token(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;)과 다이렉트로 소통하지는 않는다.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;대신 value vector 라고 불리우는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v&lt;/code&gt;를 사용한다. value vector는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;대신 사용하는, 실제로 친화도(affinity)를 적용할 대상이라고 할 수 있다. 영상에서도 value는 what I communicate to라고 표현하고 있다. 다른 표현으로는 what I will provide라고도 생각할 수 있다. &lt;a href=&quot;https://x.com/akshay_pachaar/status/1728028317633421393?s=20&quot;&gt;출처&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;v = nn.Linear(C, head_size, bias=False)
out = wei @ v
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;summary-single-head-attention&quot;&gt;Summary (Single-head Attention)&lt;/h3&gt;

&lt;p&gt;Attention(여기서는 Self-attention) 메커니즘은 &lt;strong&gt;데이터 의존적인(data dependent) 커뮤니케이션 메커니즘&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;일반적인 weight을 사용하게 되면 훈련중에 고정된 weight로 특정 위치의 토큰만 커뮤니케이션하게 된다.
그러나 Attention 메커니즘을 사용하면, 데이터에 따라서 다른 위치의 다른 토큰과 커뮤니케이션을 할 수 있다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://x.com/akshay_pachaar/status/1728028307323781613/photo/1&amp;quot;&amp;gt;Attention: A communication mechanism from @akshay_pachaar&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/09-Attention-A-communication-mechanism.jpg&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://x.com/akshay_pachaar/status/1728028307323781613/photo/1&amp;quot;&amp;gt;Attention: A communication mechanism from @akshay_pachaar&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://x.com/akshay_pachaar/status/1728028307323781613/photo/1&quot;&gt;Attention: A communication mechanism from @akshay_pachaar&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;커뮤니케이션은 위의 그림처럼 표현할 수 있다. 각 토큰간의 확률은 위 그림과 같이 그래프로 표현이 되고, 여기서 가장 중요한 것은 왼쪽의 행렬 즉 토큰들간의 attention weights를 구하는 것이다. 이는 다음과 같이 구할 수 있다.&lt;/p&gt;

&lt;p&gt;Input Embedding을 $X$라고 할 때, $X$에 weight $W^Q$, $W^K$, $W^V$를 곱해서 $Q, K, V$를 만든다.
이는 어떻게 보면 새로운 임베딩이라고 해석할 수 있다.&lt;/p&gt;

&lt;p&gt;배치 사이즈를 $B$, 총 토큰의 사이즈를 $T$, 원래 임베딩 길이를 $d_{model}$(Version 4에서의 $C$)라고 했을 때, $X$의 shape는 $(B, T, d_{model})$ 이라 표현이 가능하다. head size를 $d_k$라고 하면, weight $W^Q$의 shape는 $(d_{model} \times d_k)$, $W^K$는 $(d_{model} \times d_k)$, 그리고 $W^V$는 $(d_{model} \times d_v)$ 라고 할 수 있다.
수학적으로는 &lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt; 논문처럼 $W^Q_i \in \mathbb{R}^{d_{model} \times d_k}$, $W^K_i \in \mathbb{R}^{d_{model} \times d_k}$, $W^V_i \in \mathbb{R}^{d_{model} \times d_v}$ 라고 표현한다.&lt;/p&gt;

&lt;p&gt;Attention weights를 구하기 위해 먼저 attention scores를 구한다.
Attention scores는 i번쨰 토큰이라고 생각할 수 있는 Query $Q_i$를 j번째 토큰이라고 생각할 수 있는 Key $K_j$와 내적(dot product)를 통해 구할 수 있다.  attention score는 $(B, T, T)$의 형태로 나타내어지며, 배치 하나의 경우 위 그림의 왼쪽 행렬와 같은 꼴이 된다.
이를 Gradient의 안정성을 위해 attention head size $d_k$를 이용하여 $\sqrt{d_k}$로 scaling한다.&lt;/p&gt;

&lt;p&gt;이렇게 만든 attention score를 softmax를 취해서 확률의 형태로 만든다. 이게 attention weights이다. Attention weights는 각 토큰간의 관계를 확률적 가중치로 표현한 것이라고 해석할 수 있다.&lt;/p&gt;

&lt;p&gt;마지막으로 이렇게 만든 attention weight와 실제 우리가 적용해야할 $V$와 곱해서 attention output, 즉 데이터 의존적인 (data dependent) context vector를 생성한다. $W^Q, W^K, W^V$는 모델 훈련을 하고 나면 고정된 값이 되지만, attention output은 data에 따라 매번 변한다. 이를 수식과 그림으로 표현하면 다음과 같다.&lt;/p&gt;

\[\begin{align}
\textrm{Attention}(Q, K, V) = \textrm{softmax}\left( \dfrac{QK^T}{\sqrt{d_k}} \right) V
\end{align}\]

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://twitter.com/akshay_pachaar/status/1728028328723149165/photo/1&amp;quot;&amp;gt;Self Attention Clearly Explained! from @akshay_pachaar&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/10-Self-Attention-Clearly-Explained.jpg&quot; style=&quot;width: 100%; &quot; title=&quot;&amp;lt;a href=&amp;quot;https://twitter.com/akshay_pachaar/status/1728028328723149165/photo/1&amp;quot;&amp;gt;Self Attention Clearly Explained! from @akshay_pachaar&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://twitter.com/akshay_pachaar/status/1728028328723149165/photo/1&quot;&gt;Self Attention Clearly Explained! from @akshay_pachaar&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h2 id=&quot;numbers-to-generation-more-topics&quot;&gt;Numbers to Generation: More topics&lt;/h2&gt;

&lt;h3 id=&quot;multi-head-attention&quot;&gt;Multi-head Attention&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=4919&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;하지만, 하나의 attention score만 의존하는 것보다 다양한 관점에서 attention score를 얻는게 더 우수하다고 생각할 수 있다.
단어 하나가 여러 의미를 가질 수 있는 것은 일반적으로 생각해봤을 때 매우 당연한 이야기이다.
이렇게 여러 개의 key, query, value weights를 통해서 다양한 context를 파악하고자 하는 것이 multi-head attention이다.&lt;/p&gt;

&lt;p&gt;이렇게 나눈 key, query, value matrix를 종합적으로 판단하기 위해 병합작업이 필요한데, 원 논문 &lt;a class=&quot;citation&quot; href=&quot;#vaswani2017attention&quot;&gt;[1]&lt;/a&gt;에서는 단순히 연결(concatenation)연산을 통해서 수행하였다. 이렇게 해서 얻는 Multi-head attention의 가장 큰 장점은 각 head의 계산은 독립적으로 수행될 수 있다는 점이고, 이는 곧 병렬적으로 수행할 수 있음을 뜻한다.&lt;/p&gt;

&lt;p&gt;복잡하게 느껴질 수 잇겠지만, 단순하게 생각하면 기존의 Single-head attention을 하나의 “head”라고 간주하고 여러 번 수행하는 것 뿐이다.&lt;/p&gt;

&lt;p&gt;문제는, 여러 개의 key, query, value를 쓰면 당연히 계산 비용(computation cost)가 올라간다. 따라서 새로운 head size를 기존 head size를 head수만큼 나눠서 정한다. 이러면, head를 쪼개서 multi-head attention을 수행하는 것과 동일하므로 계산 비용면에서는 기존과 동일하다.&lt;/p&gt;

&lt;p&gt;이를 수식으로 표현하면 다음과 같다.&lt;/p&gt;

\[\begin{align}
\textrm{MultiHead}(Q, K, V) &amp;amp;= \textrm{Concat}(\textrm{head}_1, \dots, \textrm{head}_n)W^O \\
\textrm{where }\textrm{head}_i &amp;amp;= \textrm{Attention}(Q W^Q_i, K W^K_i, V W^V_i)
\end{align}\]

&lt;p&gt;그러면 기존의 다음과 같던 Single-head Self Attention Mechanism이&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/11-Single-head-Self-Attention.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&amp;quot;&amp;gt;Single-head Self Attention mechanism&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/11-Single-head-Self-Attention.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&amp;quot;&amp;gt;Single-head Self Attention mechanism&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&quot;&gt;Single-head Self Attention mechanism&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;다음과 같이 Multi-head attention 확장된다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-24-Transformer/12-Multi-head-Self-Attention.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&amp;quot;&amp;gt;Multi-head Self Attention mechanism&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg rounded shadow&quot; src=&quot;/assets/images/post/2024-03-24-Transformer/12-Multi-head-Self-Attention.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&amp;quot;&amp;gt;Multi-head Self Attention mechanism&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&quot;&gt;Multi-head Self Attention mechanism&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;feed-forward-network&quot;&gt;Feed-Forward Network&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=5066&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;각 Self Attention head에서 logit을 계산하기 직전에 Feed-Forward Network (MLP + activation function)를 추가한다.
느낌상 하나쯤 넣어주는게 더 안정적이지 않을까 생각했는데, Karpathy의 설명이 너무 좋았다.&lt;/p&gt;

&lt;p&gt;위에서 Attention은 커뮤니케이션 메커니즘이라고 설명했다.
각 토큰마다 Self Attention을 적용해서 데이터에 대한 수집은 끝났고, 모델 입장에서는 각 토큰에 대해 추가적으로 생각할 시간이 더 필요하다는 설명이었다.
여기서 추가한 Feed-Forward Network은 이렇게 토큰별로 심도있는 처리를 담당한다.&lt;/p&gt;

&lt;h3 id=&quot;residual-connections&quot;&gt;Residual Connections&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=5209&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 Attention을 활용한 transformer 아키텍처의 문제점은 deep하다는 것이다.
심층 신경망(Deep Neural Network, DNN)의 단점 중 하나는 모델이 깊어질수록 기울기 소실(vanishing gradient)와 기울기 폭발(exploding gradient) 등의 문제로 인해 학습이 어려워진다는 점이다.
네트워크들이 주로 곱셈으로 이루어져있기에 어찌보면 당연한 현상이다. $0.1 \times 0.1 \times \cdots$ 혹은
$1.1 \times 1.1 \times \cdots$ 와 같은 일이 발생하면 기하급수적으로 값이 변하는 것은 당연하기 때문이다.
게다가 activation function을 적용하면 극단적인 값들의 기울기는 0에 가까운 값으로 변할 수 있으므로 기울기 소실이 잘 발생할 수 있다.&lt;/p&gt;

&lt;p&gt;이런 현상을 최소화하기 위해 ResNets(Residual Connection, Skip Connections)이 transformer에도 적용되었다. &lt;a class=&quot;citation&quot; href=&quot;#he2016deep&quot;&gt;[5]&lt;/a&gt;
Transformer 아키텍처는 여러 개의 attention block이 연결되어 이루어져있는데, 각 블록을 전부 연결하는 것이 아니라
중간 중간 건너뛰어서 계산하기도 한다.&lt;/p&gt;

&lt;h3 id=&quot;layer-normalization&quot;&gt;Layer Normalization&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=5572&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transformer 학습의 안정화를 위해 적용한 또 다른 방법은 layer normalization이다. &lt;a class=&quot;citation&quot; href=&quot;#ba2016layer&quot;&gt;[6]&lt;/a&gt;
이 방법은 batch normalization과 유사하지만, 대상을 batch가 아닌 layer에 적용했다.&lt;/p&gt;

&lt;p&gt;각 레이어마다 나온 출력값들을 일정한 분포가 유지되도록 조정해서 activation function이 적용되어도 기울기 소실(vanishing gradient) 등의 문제가 발생하지 않도록 도와준다. 이를 어려운 말로 학습 과정에서의 내부 공변량 변화(internal covariate shift) 문제를 줄이기 위해 정규화(regularization)한다고 표현한다. Layer normalization은 레이어마다 적용하는 것이기 때문에 배치 사이즈과는 무관하고, 깊은 네트워크일수록 유리하다.&lt;/p&gt;

&lt;h3 id=&quot;dropout&quot;&gt;Dropout&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/kCc8FmEb1nY?t=5873&quot;&gt;Let’s build GPT에서의 해당 부분&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;심층 신경망의 또다른 문제점은 과적합(overfitting)으로 인해 일반화 성능(generalization)이 떨어진다는 점이다.
이 현상의 원인 중 하나는 파라미터 수가 매우 많아서 훈련 데이터에 대해서 과도하게 학습될 가능성이 있기 때문이다.
이를 해결하기 위해 나온 방법 중 하나가 &lt;a class=&quot;citation&quot; href=&quot;#srivastava2014dropout&quot;&gt;[7]&lt;/a&gt;에서 나온 Dropout이다.&lt;/p&gt;

&lt;p&gt;이 방법은 굉장히 심플한데 훈련(training)할 때 그냥 랜덤하게 일부 뉴런(neuron)을 비활성화 시켜서 학습하고 추론(inference)시에는 모든 뉴론을 활성화시킨 네트워크를 사용한다. 이렇게 하면 모델이 특정 뉴런이나 특정 뉴런 조합에 과도하게 의존하는 것을 방지할 수 있다.
또한 랜덤으로 비활성화 시킨 네트워크를 각각 다른 네트워크처럼 생각하면 앙상블(ensemble) 모델 학습시키는 것과 같은 방식이라고 간주할 수도 있다.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;지금까지 어텐션 매커니즘에 대해서 알아보았다. 이 글을 쓴 2024년에도 딥러닝에 있어서 가장 중요한 알고리즘 중 하나라고 할 수 있겠다.
또한 transformer 자체가 워낙 무겁기 때문에 이를 경량화하기 위한 여러 방법들은 이 어텐션 매커니즘을 최적화하는 방법들이 많고,
다양한 논문들이 transformer의 근본을 건드리거나 개선하려고 노력하고 있다.
그러기에 2024년에도 Back to Basics의 관점으로 다시 한번 복습하기 위해 이 포스트를 작성하였다. 아쉬운 건 Decoder입장에서만 작성했고, Encoder와의 차이점, 그리고 Cross Attention 부분도 넣었어야 했으나 너무 지쳐서 포기했다. 다른 자료에 설명이 잘 되어있으니 참고하면 되겠다.&lt;/p&gt;

&lt;p&gt;참고로 &lt;a href=&quot;https://www.youtube.com/watch?v=kCc8FmEb1nY&quot;&gt;Let’s build GPT&lt;/a&gt;뿐만 아니라 여러가지 다른 좋은 포스트와 책, 글들이 많기에 기록하고자 한다. (다만 다 영어다.)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://jalammar.github.io/illustrated-transformer/&quot;&gt;The Illustrated Transformer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/akshay_pachaar/status/1728028328723149165&quot;&gt;Tweets from @akshay_pachaar&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://towardsai.net/p/nlp/getting-meaning-from-text-self-attention-step-by-step-video&quot;&gt;Getting Meaning from Text: Self-attention Step-by-step Video&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html&quot;&gt;Understanding and Coding the Self-Attention Mechanism of Large Language Models From Scratch&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;현재 이 내용은 &lt;a href=&quot;https://www.manning.com/books/build-a-large-language-model-from-scratch&quot;&gt;Build a Large Language Model (From Scratch)&lt;/a&gt;라는 책으로 쓰여지고 있다. (Livebook)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4Bdc55j80l8&quot;&gt;Illustrated Guide to Transformers Neural Network: A step by step explanatio&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;[Visualizing Attention, a Transformer’s Heart&lt;/td&gt;
          &lt;td&gt;Chapter 6, Deep Learning](https://www.youtube.com/watch?v=eMlx5fFNoYc)&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;vaswani2017attention&quot;&gt;[1]A. Vaswani &lt;i&gt;et al.&lt;/i&gt;, “Attention is all you need,” &lt;i&gt;Advances in neural information processing systems&lt;/i&gt;, vol. 30, 2017, Available at: &lt;a href=&quot;https://arxiv.org/abs/1706.03762&quot;&gt;https://arxiv.org/abs/1706.03762&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;bengio2000neural&quot;&gt;[2]Y. Bengio, R. Ducharme, and P. Vincent, “A neural probabilistic language model,” &lt;i&gt;Advances in neural information processing systems&lt;/i&gt;, vol. 13, 2000, Available at: &lt;a href=&quot;https://dl.acm.org/doi/10.5555/944919.944966&quot;&gt;https://dl.acm.org/doi/10.5555/944919.944966&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;bahdanau2014neural&quot;&gt;[3]D. Bahdanau, K. Cho, and Y. Bengio, “Neural machine translation by jointly learning to align and translate,” &lt;i&gt;arXiv preprint arXiv:1409.0473&lt;/i&gt;, 2014, Available at: &lt;a href=&quot;https://arxiv.org/abs/1409.0473&quot;&gt;https://arxiv.org/abs/1409.0473&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;sutskever2014sequence&quot;&gt;[4]I. Sutskever, O. Vinyals, and Q. V. Le, “Sequence to sequence learning with neural networks,” &lt;i&gt;Advances in neural information processing systems&lt;/i&gt;, vol. 27, 2014, Available at: &lt;a href=&quot;https://arxiv.org/abs/1409.3215&quot;&gt;https://arxiv.org/abs/1409.3215&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;he2016deep&quot;&gt;[5]K. He, X. Zhang, S. Ren, and J. Sun, “Deep residual learning for image recognition,” in &lt;i&gt;Proceedings of the IEEE conference on computer vision and pattern recognition&lt;/i&gt;, 2016, pp. 770–778.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;ba2016layer&quot;&gt;[6]J. L. Ba, J. R. Kiros, and G. E. Hinton, “Layer normalization,” &lt;i&gt;arXiv preprint arXiv:1607.06450&lt;/i&gt;, 2016.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;srivastava2014dropout&quot;&gt;[7]N. Srivastava, G. Hinton, A. Krizhevsky, I. Sutskever, and R. Salakhutdinov, “Dropout: a simple way to prevent neural networks from overfitting,” &lt;i&gt;The journal of machine learning research&lt;/i&gt;, vol. 15, no. 1, pp. 1929–1958, 2014.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Logit, Sigmoid, Softmax, and Cross-Entropy</title>
   <link href="https://blog.liam.kim/posts/2024/03/Logit-Sigmoid-Softmax/"/>
   <updated>2024-03-09T20:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2024/03/Logit-Sigmoid-Softmax</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;개인적으로 정리도 할 겸 그리고 다른 포스트에서 이 부분을 설명할 때 글이 너무 길어져서 분리해서 작성하는 포스트이다. 최대한 직관적으로 적어보려고 노력하였다.&lt;/p&gt;

&lt;h2 id=&quot;logit&quot;&gt;logit&lt;/h2&gt;

&lt;p&gt;도박을 생각해보자. odds란 도박의 승률을 나타내는 중요한 지표이다. 확률이 이길 확률($p$)과, 전체 경우의 수(이김 + 짐)의 합(1)으로 $\dfrac{p}{(p + (1-p))} = \dfrac{p}{1} = p$ 표시되는 형태라면, odds는 이길 확률($p$)과 질 확률($1-p$)의 비율로 표시된다. odds는 &lt;strong&gt;이길 확률이 질 확률에 비해 몇 배냐 더 큰 것인가?&lt;/strong&gt;라는 것을 표현하는 지표이다.&lt;/p&gt;

\[\begin{equation}
\textrm{odds} = \dfrac{p}{1-p}
\end{equation}\]

&lt;p&gt;참고로 왜 확률이 아니라 odds를 쓰냐고 하면, odds가 계산이 쉽기 때문이다. odds의 표기법은 여러가지가 있는데, British Odds($(1-p)/p$)로 표현할 때, odds가 $ 32/7 $ 이라고 하자. 확률로 계산하면 승리확률이 $ \dfrac{7}{32+7} = 0.17 $이고, 여기에 배당금까지 계산하려면 복잡하다. 하지만 odds 계산으로는 7만원을 걸면 32만원을 딸 수 있다고 계산할 수 있다. (이기면 39만원 보유)&lt;/p&gt;

&lt;p&gt;다시 돌아와서 odds에 로그를 취한 것을 log odds이라고 하고 이를 확장하여 함수의 형태로 표현하면 logistic unit, 줄여서 &lt;strong&gt;logit&lt;/strong&gt; 라고 한다. 로그를 취한 이유는 일종의 트릭이라고 볼 수 있는데, 로그를 취하면 함수의 특성 (증가,감소나 극점의 유지)을 유지시키면서도 복잡한 곱셈이나 나눗셈 연산을 덧셈과 뺄셈으로 바꿀 수 있기에 계산의 편의성을 위해 사용한다고 보면 된다.&lt;/p&gt;

\[\begin{equation}
\textrm{logit}(x) := \ln{\dfrac{x}{1-x}}
\end{equation}\]

&lt;p&gt;logit이 log odds에서 출발하기는 했지만, 일반적으로는 모델을 통해 나온 출력값을 뜻한다. 예를 들면 어떤 모델을 통해 count값이 나왔다고 가정했을 때, logit은 그 count값이 될 수 있다. 날 것의 숫자 그 자체인 것이다.
하지만, 이러면 확률로 변환이 안 되어있어 계산하기가 번거롭기에 확률로 바꿔주는 도구가 필요하다.&lt;/p&gt;

&lt;h2 id=&quot;sigmoid&quot;&gt;sigmoid&lt;/h2&gt;

&lt;p&gt;그러면 logit함수를 다시 확률로 바꾸려면 어떻게 해야할까? 위에서 logit함수를 정의했고, 이는 확률(p)로부터 정의되기 때문에, 다음과 같이 위의 logit함수의 역함수(inverse function)을 취하면 확률을 다시 구할 수 있다.&lt;/p&gt;

\[\begin{align*}
x &amp;amp;= \textrm{logit}(y) \equiv \ln{\dfrac{y}{1-y}} \\
e^x &amp;amp;= \dfrac{y}{1-y} \\
(1-y) e^x &amp;amp;= y \\
e^x &amp;amp;= y (1 + e^x) \\
y &amp;amp;= \dfrac{e^x}{1+e^x} = \dfrac{1}{1+e^{-x}}
\end{align*}\]

&lt;p&gt;그리고 이 함수를 sigmoid 함수라고 한다.&lt;/p&gt;

\[\begin{equation}
\textrm{sigmoid}(x) := \dfrac{1}{1+e^{-x}}
\end{equation}\]

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/01-sigmoid.svg&quot; type=&quot;image/svg&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://en.wikipedia.org/wiki/Sigmoid_function&amp;quot;&amp;gt;Sigmoid function&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg align-items-center&quot; src=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/01-sigmoid.svg&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://en.wikipedia.org/wiki/Sigmoid_function&amp;quot;&amp;gt;Sigmoid function&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://en.wikipedia.org/wiki/Sigmoid_function&quot;&gt;Sigmoid function&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;sigmoid 함수를 통해 어떤 값 logit을($-\infty, \infty$) 확률의 범위인 ${0, 1}$사이로 한정(clamping) 혹은 압축할 수 있다. 그래서 sigmoid를 logitstic function이라고 하기도 한다.&lt;/p&gt;

&lt;h2 id=&quot;softmax&quot;&gt;softmax&lt;/h2&gt;

&lt;p&gt;하지만 sigmoid는 logit scalar, 즉 logit 하나에 대해 확률로 변환을 수행하는 함수이다. 비선형함수로 만들기 위한 activation function이라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;그러나 logit &lt;strong&gt;vector&lt;/strong&gt;에 대해 확률 분포 &lt;strong&gt;vector&lt;/strong&gt;로 변환하는 함수가 필요하다. 즉, 여러 개의 logit이 있을 때 이를 확률 분포로 변환하고자 할 때는 다음과 같이 &lt;strong&gt;softmax&lt;/strong&gt;함수를 사용한다.&lt;/p&gt;

&lt;p&gt;정리하자면, sigmoid는 logit을 확률로 변환하는 함수, softmax는 logit vector를 확률 분포로 바꿔주는 함수라고 할 수 있다.&lt;/p&gt;

\[\begin{align}
\textrm{softmax}(x_i) &amp;amp;:= \dfrac{e^{x_i}}{\sum_{j=1}^K e^{x_j}} \\
\end{align}\]

&lt;p&gt;보통 softmax라고 불리우는 이 함수는 &lt;strong&gt;softargmax&lt;/strong&gt; 혹은 &lt;strong&gt;normalized exponential function&lt;/strong&gt;라는 명칭으로 이해해야 자연스럽다. 왜냐하면 위 함수의 form은 vector $\mathbf{x}$에 대해서 \(LogSumExp(x_i) = \log{\sum_i \exp{x_i}}\)와 유사하며, 이는 $max$함수의 soft한 버전 즉 softmax라고 불리우기 때문이다.&lt;/p&gt;

&lt;p&gt;softmax함수는 벡터에 대해 적용할 수 있기 때문에 앞에서 본 sigmoid는 2개 (Win/Loss)의 클래스에 대해 분류하는 문제에 적용할 수 있는 문제에 많이 쓰이기도 하고, 이를 K개의 클래스로 확장할 때는 softmax를 많이 쓴다.&lt;/p&gt;

&lt;p&gt;logit과 sigmoid 입장에서 softmax를 해석하면, 단순하게 odds로 바꾸어서 생각하면 된다.
어떤 logit(log odds) $X$가 있을 때, 지수 함수(exponential function)를 취하면 $\textrm{odds} \in [0, +\infty)$ 형태가 된다.&lt;/p&gt;

\[\begin{align*}
e^x = e^{\log{\textrm{odds}}} = \textrm{odds}
\end{align*}\]

&lt;p&gt;이를 확률로 만들기 위해 분모는 다 더하고 (모든 클래스의 확률의 합은 1), 분자는 하나의 odds만 남기면 된다.&lt;/p&gt;

\[\begin{align*}
\textrm{softmax} =  \dfrac{\textrm{Single odds}}{\textrm{Sum of odds}} = \dfrac{e^{x_i}}{\sum_{j=1}^K e^{x_j}}
\end{align*}\]

&lt;p&gt;참고로 다시 역으로 softmax로부터 sigmoid로 유도하면 다음과 같다.
(어떤 binary classification output $x$를 $x = x_0 - x_1$이라고 정의)&lt;/p&gt;

\[\begin{align*}
\textrm{sigmoid} = \dfrac{e^{x_0}}{e^{x_0} + e^{x_1}} = \dfrac{1}{1 + \dfrac{e^{x_1}}{e^{x_0}}} =
\dfrac{1}{1 + e^{x_1-x_0}} = \dfrac{1}{1 + e^{-(x_0 - x_1)}} = \dfrac{1}{1 + e^{-x}}
\end{align*}\]

&lt;h2 id=&quot;multinomial-logistic-regression-softmax-regression&quot;&gt;Multinomial Logistic Regression (Softmax Regression)&lt;/h2&gt;

&lt;p&gt;softmax는 다중 분류 문제(MultiClass Classification Problem)에 사용된다. 이 떄 사용하는 방법을 다항 로지스틱 회귀(Multinomial Logistic Regression)이라고 한다.&lt;/p&gt;

&lt;p&gt;이진 분류(Binary Classification)에서는 단순히 Yes/No로 판별할 확률만 알면 됐다. 아래 그림처럼 logits를 sigmoid함수에 통과시켜 확률을 얻은 뒤, 확률에 따라 자동차인지 아닌지 분류하면 되는 문제였다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/02-sigmoid-logistic-regression.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://wandb.ai/amanarora/Written-Reports/reports/Understanding-Logits-Sigmoid-Softmax-and-Cross-Entropy-Loss-in-Deep-Learning--Vmlldzo0NDMzNTU3&amp;quot;&amp;gt;Logtistic Regression&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg align-items-center&quot; src=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/02-sigmoid-logistic-regression.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://wandb.ai/amanarora/Written-Reports/reports/Understanding-Logits-Sigmoid-Softmax-and-Cross-Entropy-Loss-in-Deep-Learning--Vmlldzo0NDMzNTU3&amp;quot;&amp;gt;Logtistic Regression&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://wandb.ai/amanarora/Written-Reports/reports/Understanding-Logits-Sigmoid-Softmax-and-Cross-Entropy-Loss-in-Deep-Learning--Vmlldzo0NDMzNTU3&quot;&gt;Logtistic Regression&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;그러나, 다중 분류 문제에서는 logits를 softmax에 통과시켜 각 클래스에 속할 확률을 구한 뒤, 가장 높은 확률의 클래스를 선택하는 문제로 변화하게 된다. 이런 문제를 Multinomial Logistic Regression 혹은 softmax regression이라고 한다.&lt;/p&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/03-softmax-multinomial-logistic-regression.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://wandb.ai/amanarora/Written-Reports/reports/Understanding-Logits-Sigmoid-Softmax-and-Cross-Entropy-Loss-in-Deep-Learning--Vmlldzo0NDMzNTU3&amp;quot;&amp;gt;Multinomial Logtistic Regression&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg align-items-center&quot; src=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/03-softmax-multinomial-logistic-regression.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://wandb.ai/amanarora/Written-Reports/reports/Understanding-Logits-Sigmoid-Softmax-and-Cross-Entropy-Loss-in-Deep-Learning--Vmlldzo0NDMzNTU3&amp;quot;&amp;gt;Multinomial Logtistic Regression&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://wandb.ai/amanarora/Written-Reports/reports/Understanding-Logits-Sigmoid-Softmax-and-Cross-Entropy-Loss-in-Deep-Learning--Vmlldzo0NDMzNTU3&quot;&gt;Multinomial Logtistic Regression&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h2 id=&quot;cross-entropy-loss&quot;&gt;Cross Entropy Loss&lt;/h2&gt;

&lt;p&gt;그러면 이런건 어떻게 학습시켜야할까? 일반적인 확률적 경사하강법(SGD, Stochastic Gradient Descent)에서는
loss function $\mathcal{L}$을 정의하고 loss function을 통해 정답과 출력의 차이를 최대한 좁히도록 모델 파라미터를 업데이트하는 방식을 취한다.&lt;/p&gt;

&lt;p&gt;지도 학습(Supervised Learning) 분류 문제에서 출력(output)을 $\hat{y}$, 그리고 대상 혹은 정답(target)을 $y$이라고 지정하면,
loss function $\mathcal{L}$은 다음과 같이 정의할 수 있다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}(\hat{y}, y) = \textrm{A difference between } \hat{y} \textrm{ and } y
\end{equation}\]

&lt;p&gt;하지만 처음부터 다중 분류(Multiclass)로 접근하면 복잡하니까 단순하게 이진분류(Binary Classification)으로 돌아가보자.&lt;/p&gt;

&lt;p&gt;어떤 모델 $f$가 파라미터 $\theta$에 의존한다고 했을때 입력 $x$에 대해 정의되는 모델의 logit을 $f_\theta(x)$이라고 정의할 수 있다.
이를 확률로 바꾸면 $\textrm{sigmoid}(f_\theta(x))$라고 할 수 있고, 이 때 sigmoid를 $\sigma$로 표현하기도 한다. 해당 파라미터 $\theta$와 데이터 인덱스 $i$에 대한 입력과 출력을 $x_i$과 $\hat{y}_{\theta, i}$라고 표현하면 이 내용을 다음과 같은 식으로 표현이 가능하다.&lt;/p&gt;

\[\begin{equation}
\hat{y}_{\theta, i} = \sigma \left( f_\theta(x_i) \right)
\end{equation}\]

&lt;h3 id=&quot;what-is-machine-learning&quot;&gt;What is “Machine Learning”?&lt;/h3&gt;

&lt;p&gt;그러면 본질적인 문제로 돌아가보자 Machine Learning, 즉 기계 학습이란 어떤 의미일까?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;새로운 데이터가 관찰하여 기존의 가설 혹은 모델에 대한 신뢰도를 점진적으로 업데이트하는 확률적 과정이다.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;초기이든 아니면 기존의 가설($H$, hypothesis)이 존재하든, 이를 확률로 나타내면 prior \(P(H)\) prior이다.&lt;/li&gt;
  &lt;li&gt;이 때 새로운 데이터 $D$가 주어졌다고 하자.&lt;/li&gt;
  &lt;li&gt;새로운 데이터 \(D\)에 대해 가설이 얼마나 가능한지에 대한 신뢰도는 \(P(D \rvert H)\)라고 할 수 있다. (likelihood) (현상)&lt;/li&gt;
  &lt;li&gt;prior과 likelihood를 곱한 뒤 이를 전체 데이터의 확률 (evidence) \(P(D)\)로 나눌 수 있다.&lt;/li&gt;
  &lt;li&gt;그리고 그렇게 정한 최종 결과를 사후 확률 (posterior) \(P(H \rvert D)\)이라고 하며 이를 종합하면 베이즈 정리(Bayes’ Theorem)이다. (원인)&lt;/li&gt;
&lt;/ul&gt;

\[\begin{equation}
P(H\rvert D) = \dfrac{P(D\rvert H) P(H)}{ P(D)}
\end{equation}\]

\[\begin{equation}
\textrm{posterior} = \dfrac{\textrm{likelihood} \cdot \textrm{prior}}{ \textrm{evidence}}
\end{equation}\]

&lt;p&gt;다시 정리하면, 모델 훈련은 기존 evidence(데이터)에 대한 가설(hypothesis)의 신뢰도(likelihood)를 업데이트하는 과정이다. 궁극적으로는 posterior(사후 확률)을 추론하여 원인을 파악하고자 하는 것이 목적이다.&lt;/p&gt;

&lt;p&gt;posterior \(P(H \rvert D)\)는 결국 다음 학습의 prior \(P(H)\)가 되어 지속적으로 업데이트 된다.&lt;/p&gt;

&lt;h3 id=&quot;likelihood&quot;&gt;Likelihood&lt;/h3&gt;

&lt;p&gt;위에서 언급한 분류문제에서는 모델이 정답 클래스를 예측할 확률에 초점을 맞췄다. (분류문제라고 했을 때)
하지만, 이는 본질적으로 &lt;strong&gt;모델의 매개변수가 주어진 훈련 데이터에 얼마나 잘 맞는지를 측정&lt;/strong&gt;하는 것과 관련이 있다.
여기서 얘기하는 얼마나 잘 맞는지에 대한 적합도, 즉
&lt;strong&gt;데이터가 특정 모델 매개변수를 얼마나 ‘지지’하거나 ‘가능하게 하는지’의 척도 혹은 신뢰도&lt;/strong&gt;를 &lt;strong&gt;가능도(likelihood)&lt;/strong&gt;라고 한다.
우도라고 표현하는 경우도 있지만, 가능도 혹은 기여도라고 해석하는게 직관적이다. 영어로 보면 “Like”란 단어는 좋아하다라는 의미도 있지만 ‘가깝다’, ‘유사함’의 의미로 생각하면 이해가 쉬울 것이라고 생각한다.&lt;/p&gt;

&lt;p&gt;처음에 이해하기 어려운 개념이다. 그러나, 알고보면 그동안 사람들이 매번 하는 것이다.&lt;/p&gt;

&lt;p&gt;예를 들어 소개팅을 나간다고 하자, 소개팅에서 연애로 발전할 확률을 구할 수도 있다. 이는 특정 분포 (그동안 경험(prior)을 통해 보유)를 통해 새로운 소개팅 이성에 대해 성공할 확률을 계산할 수 있다. 이는 &lt;strong&gt;분포로부터 데이터를 추정&lt;/strong&gt;이라고 볼 수 있다. 즉, &lt;strong&gt;확률(Probability)&lt;/strong&gt;이다. (분포 고정)&lt;/p&gt;

&lt;p&gt;반대로 상대방 이성에 대한 정보(데이터)가 소개팅 성공에 얼마나 기여할까를 측정할 수도 있다. 프로필사진을 더 신뢰할 수도 있고, 주변인들의 전언을 더 신뢰할 수도 있다. (모델 파라미터) 경험이 쌓일수록 내가 어떤 정보를 더 신뢰해야하는가에 대해 통찰력이 생길것이다. 이는 &lt;strong&gt;데이터로부터 분포를 추정&lt;/strong&gt;이라고 볼 수 있다. 즉, &lt;strong&gt;가능도(Likelihood)&lt;/strong&gt;이다. (데이터 고정)&lt;/p&gt;

&lt;p&gt;이를 수식으로 얘기하면 각각 조건부확률(Conditional Probability)를 이용하여 분포를 포현하는 모델 파라미터 $\theta$와 데이터 $x$에 대해 다음과 같이 정의할 수 있다.&lt;/p&gt;

\[\begin{align}
\textrm{Probability} &amp;amp; := P(X | \theta) \; (\textrm{fixed } \theta) \\
\textrm{Likelihood} &amp;amp; := L(\theta | x) = P(X=x | \theta) \; (\textrm{fixed } X) \\
\end{align}\]

&lt;h3 id=&quot;mlemaximum-likelihood-estimation-and-log-likelihood&quot;&gt;MLE(Maximum Likelihood Estimation) and Log-likelihood&lt;/h3&gt;

&lt;p&gt;모델은 주어진 데이터를 통해 확률분포를 예측하는 걸 훈련하는게 목적이라고 정의한다고 했다.
prior에 상관하지 않을 때 이 말은 가능도를 최대화 해야한다는 것과 치환할 수 다. (Maximize Likelihood)&lt;/p&gt;

&lt;p&gt;그리고 가능도를 최대화하도록 모델 파라미터를 추정하는 것을 MLE(Maximum Likelihood Estimation)이라고 한다.&lt;/p&gt;

&lt;p&gt;MLE를 어떻게 쉽게 할 수 있을까? 그건 위에서도 사용한 로그를 이용하면 된다. 곱셈이 덧셈으로 바뀌어서 계산이 쉬워지기 때문이다.&lt;/p&gt;

&lt;p&gt;아까의 예시를 이어서 들면, 전혀 모르는 사람들과 지속적으로 소개팅을 한다면 각각은 독립적인 사건이라고 볼 수 있다.
물론 소개팅이 안돼서 심리적으로 위축돼서 더 잘 안될수도 있겠지만, 강철멘탈이라고 가정하자.&lt;/p&gt;

&lt;p&gt;수학에서 독립 시행의 확률은 곱셈으로 정의된다.&lt;/p&gt;

\[\begin{align}
P(A \cap B) = P(A) P(B)
\end{align}\]

&lt;p&gt;logit에서처럼 곱셈보다는 덧셈이 계산하기가 훨씬 편하다. 따라서, 확률도 Log를 씌워보자.&lt;/p&gt;

\[\begin{align}
\log{P(A \cap B)} = \log{P(A)} + \log{P(B)}
\end{align}\]

&lt;p&gt;가능도(Likelihood)도 마찬가지이다. 새로운 데이터가 들어올때마다 곱셈보다 지속적으로 더해줄 수 있는 형태를 만드는게 좋다.&lt;/p&gt;

\[\begin{align}
\log{L(A \cap B)} = \log{L(A)} + \log{L(B)}
\end{align}\]

&lt;p&gt;이렇게 로그를 씌운 형태를 로그 가능도(log-likelihood)라고 한다.&lt;/p&gt;

&lt;p&gt;그러면 MLE의 곱셈은 로그 가능도(log-likelihood)를 이용하면 덧셈으로 변경된다.&lt;/p&gt;

\[\begin{align}
L(\theta | x) = P(X=x | \theta) &amp;amp;= \prod_{k=1}^n P(x_k | \theta) \\
\log{L(\theta | x)} = \log{P(X=x | \theta)} &amp;amp;= \sum_{k=1}^n \log{P(x_k | \theta)}
\end{align}\]

&lt;h3 id=&quot;back-to-binary-classification&quot;&gt;Back to Binary Classification&lt;/h3&gt;

&lt;p&gt;만약, 입력 $x$에 대해서 출력 $Y$ (0 or 1)를 만들때 그 출력을 다음과 같이 표현해보자. $x$랑 $Y$ 모두 데이터이고, 모델 파라미터 $\theta$가 주어졌을 때의 $x$에 대한 확률 $p$를 표현하기 위해 ;을 사용했다.&lt;/p&gt;

\[\begin{align}
P(Y=1 | X=x) = p(x; \theta)
\end{align}\]

&lt;p&gt;조건부 가능도(Conditional Likelihood)는 다음과 같이 표현가능하다.&lt;/p&gt;

\[\begin{equation}
L(\theta | y) = \prod_{i=1}^n  P( Y = y_i | X = x_i) = \prod_{i=1}^n \hat{y}_{\theta,i}^y (1-\hat{y}_{\theta,i})^{1-y}
\end{equation}\]

&lt;p&gt;이 중에서&lt;/p&gt;

\[\begin{align*}
\hat{y}_{\theta,i}^y (1-\hat{y}_{\theta,i})^{1-y} = \begin{cases}
    \hat{y}_{\theta,i}^y \; &amp;amp;\textrm{ where } y == 1 \\
    (1-\hat{y}_{\theta,i}) \; &amp;amp;\textrm{ where } y == 0 \\
\end{cases}
\end{align*}\]

&lt;p&gt;이 식은 이항분포(Bernoulli distribution) 확률밀도 함수에서 가져온 식인데, 각 $y$에 따라 동작을 달리한다.&lt;/p&gt;

&lt;p&gt;이 조건부 가능도(conditional likelihood)를 최대화 하기 위해 로그를 사용하면&lt;/p&gt;

\[\begin{align}
\log{L(\theta | y)} &amp;amp;= \sum_{i=1}^n  \log{P( Y = y_i | X = x_i)} \\
    &amp;amp;= \sum_{i=1}^n \log{\hat{y}_{\theta,i}^{y_i} (1-\hat{y}_{\theta,i})^{1-y_i}} \\
    &amp;amp;= \sum_{i=1}^n \log{\hat{y}_{\theta,i}^{y_i}} + \sum_{i=1}^n  \log{(1-\hat{y_i}_{\theta,i})^{1-y_i}} \\
    &amp;amp;= \sum_{i=1}^n y_i\log{\hat{y}_{\theta,i}} + \sum_{i=1}^n  (1-y_i)\log{(1-\hat{y}_{\theta,i})}
\end{align}\]

&lt;p&gt;하지만 loss function입장에서는 최소화를 하는것을 지향하기 때문에 $-$를 붙여서 Negative Log-likehood(NLL)를 최소화하는 문제로 바꿔준다.&lt;/p&gt;

&lt;p&gt;따라서 이진 분류(Binary Classification) 문제의 목적은 가능도를 최대화 하는것이 목적이며, 이는 loss function $\mathcal{L}$은 $n$개의 레이블에 대해서 Negative Log-likelihood, $-\log{L}$을 최소화하는 것과 같다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}(y, \hat{y}) = -\sum_{i=1}^n y_i\log{\hat{y}_{\theta,i}} - \sum_{i=1}^n  (1-y_i)\log{(1-\hat{y}_{\theta,i})}
\end{equation}\]

&lt;p&gt;여기서 $\theta$를 생략하고 $n$개의 데이터에 대해 평균을 내면 다음과 같이 정리할 수 있다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}(y, \hat{y}) = - \dfrac{1}{n} \sum_{i=1}^n \left[ y_i\log{\hat{y}_{i}} + (1-y_i)\log{(1-\hat{y}_{i})} \right]
\end{equation}\]

&lt;p&gt;이 때 예측 확률 $\hat{y}$는 sigmoid 함수를 사용하여 표현할 수 있다.&lt;/p&gt;

\[\begin{align}
\textrm{sigmoid}(z) = \sigma(z) &amp;amp;= \dfrac{1}{1 + e^{-z}} \\
\hat{y} &amp;amp;= \sigma(f_\theta(x))
\end{align}\]

&lt;h3 id=&quot;extend-to-multiclass-classification&quot;&gt;Extend to Multiclass Classification&lt;/h3&gt;

&lt;p&gt;이를 다중 클래스로 확장하면 원래 목적이었던 Cross Entropy Loss를 구할 수 있다.&lt;/p&gt;

&lt;p&gt;sigmoid 에서 softmax로 확장한것처럼
이진 분류(Binary Classification)의 Negative Log-likelihood을 Mutli-Class Classification의 Cross Entropy Loss로 확장할 수 있다.&lt;/p&gt;

\[\begin{align}
\textrm{sigmoid}(z) &amp;amp;= \dfrac{1}{1+e^{-z}} \\
\textrm{softmax}(z_i) &amp;amp;= \dfrac{e^{z_i}}{\sum_{j=1}^K e^{z_j}}
\end{align}\]

&lt;p&gt;우선 이진 분류의 loss function을 데이터 한 개에 대해 표현하면 다음과 같다. ($i$는 그래서 생략)
이 떄 $c$는 클래스(class) 혹은 레이블의 약자 c이다.&lt;/p&gt;

\[\begin{align}
\textrm{sigmoid}(z) &amp;amp;= \dfrac{1}{1+e^{-z}} \\
\hat{y} &amp;amp;= \textrm{sigmoid}(f_\theta(x)) \\
\mathcal{L}(y, \hat{y}) &amp;amp;= -y\log{\hat{y}}- (1-y)\log{(1-\hat{y})} \\
 &amp;amp;= - \sum_{c=1}^2 y_c \log{\hat{y}_{c}}
\end{align}\]

&lt;p&gt;맨 밑줄은 일반적인 표기법은 아니지만, 다중 분류로의 확장을 위해 표기법을 변경하였다.
이진 분류에서 1번과 2번 클래스를 다음과 같이 정의하고 \(y^1 = y\), \(y^2 = 1-y\),
확률도 \(\log{\hat{y}_\theta^1} = \log{\hat{y}}\),  \(\log{\hat{y}_\theta^2} = 1-\log{\hat{y}}\) 이렇게 표현할 수 있기 때문에 $\sum$으로 묶어서 표현할 수 있다.&lt;/p&gt;

&lt;p&gt;이런 구조는 $\textrm{레이블} \times \textrm{레이블의 확률}$의 합의 형태를 띈다.&lt;/p&gt;

&lt;p&gt;그러면 이 구조를 유지하면서 다중 분류를 위해 2개의 class를 $K$개의 class로 확장하면, 다음과 같이 확장할 수 있게 된다.
표기법을 살짝 변경했는데, 위첨자(superscript)를 표현하던 클래스를&lt;/p&gt;

\[\begin{align}
\mathcal{L}(y, \hat{y}) &amp;amp;= - \sum_{k=1}^K y_k \log{\hat{y}_{k}}
\end{align}\]

&lt;p&gt;위에서 다중 분류 문제에서 레이블의 확률을 구할 때 sigmoid대신 softmax를 사용한다고 했으므로&lt;/p&gt;

\[\begin{align}
\textrm{softmax}(z_i) &amp;amp;= \dfrac{e^{z_i}}{\sum_{j=1}^K e^{z_j}} \\
\hat{y} &amp;amp;= \textrm{softmax}(f_\theta(x)) \\
\mathcal{L}(y, \hat{y}) &amp;amp;= - \sum_{k=1}^K y_k \log{\hat{y}_k}
\end{align}\]

&lt;p&gt;그럼 다음과 같이 모델 \(\hat{y}_{\theta, i} = \textrm{softmax} \left( f_\theta(x_i) \right)\)와 정답 클래스 c에 대해 Cross Entropy Loss를 유도해보도록 하자.&lt;/p&gt;

&lt;p&gt;위의 loss function은 실제 클래스 $c$에 대해서만 $y_c=1$이기 때문에 나머지 $y_k  = 0 \textrm{ where } k \neq c$이다. 따라서, $\sum_{k=1}^K$은 없어지고 $\log{\hat{y}_c}$ 만 남게 된다.&lt;/p&gt;

\[\begin{align}
\mathcal{L}(y, \hat{y}) &amp;amp;= - \sum_{k=1}^K y_k \log{\hat{y}_k} \\
&amp;amp;= -\log{\hat{y}_c}
\end{align}\]

&lt;p&gt;여기서 softmax함수의 정의를 $\hat{y}_c$에 대입하면 Cross Entropy Loss는 다음만 남게 된다.&lt;/p&gt;

\[\begin{equation}
\mathcal{L}(y, \hat{y}) = -\log{\dfrac{\exp{f_\theta(x_c)}}{\sum_{k=1}^K \exp{f_\theta(x_k)}}}
\end{equation}\]

&lt;h2 id=&quot;information-theory&quot;&gt;Information Theory&lt;/h2&gt;

&lt;p&gt;그러면 왜 Cross Entropy라고 불리울까? 엔트로피는 무질서도 아니었나? 크로스 엔트로피는 또 무슨 말일까? 이 부분을 좀 더 깊게 설명해보고자 한다.
쉽게 이해하기 어려운 개념이나 &lt;a href=&quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&quot;&gt;이 블로그 글&lt;/a&gt;과 &lt;a href=&quot;https://horizon.kias.re.kr/18474/&quot;&gt;HORIZON에 연재된 글&lt;/a&gt;이 이해에 많은 도움을 주었고, 이를 정리해보고자 한다. 같이 읽어보는 걸 추천한다.&lt;/p&gt;

&lt;h3 id=&quot;information&quot;&gt;Information&lt;/h3&gt;

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/04-Obervation-Entropy.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/04-Obervation-Entropy.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&amp;quot;&amp;gt;The path from an observation to the use of a model. Entropy oversees all these steps since they all relate back to the idea of surprisal.&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg align-items-center&quot; src=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/04-Obervation-Entropy.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&amp;quot;&amp;gt;The path from an observation to the use of a model. Entropy oversees all these steps since they all relate back to the idea of surprisal.&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&quot;&gt;The path from an observation to the use of a model. Entropy oversees all these steps since they all relate back to the idea of surprisal.&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;사람은 항상 관찰하고 발견한다. 하지만, 둘은 같은 개념이 아니다. 발견은 관찰로부터 이루어진다.
평소와 다른 무엇인가를 관찰했을 때 이를 발견이라고 할 수 있다.
정보는 &lt;strong&gt;얼만큼 평소와 다른가, 즉 놀람의 정도(surprisal)&lt;/strong&gt;에 의해 정의될 수 있다.
하지만 놀람이란 감정적인 단어다.
과학적으로 해석하기 위해 이 &lt;strong&gt;놀람의 정도를 정량화&lt;/strong&gt;하면 &lt;strong&gt;불확실성(Uncertainty)&lt;/strong&gt;이 된다.
그리고, 그 불확실성은 확률(Probability)에 의해 측정된다.
낮은 확률의 사건은 불확실성이 높은 사건이고 이는 많은 정보량을 얻을 수 있다.
이렇게 확률로 측정되어지는 정량화된 불확실성은 우리에게 예측(prediction)과 설명(explainability)을 제공해준다.&lt;/p&gt;

&lt;p&gt;자 이제 수학적으로 어떻게 접근해볼지 생각해보자.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;he fundamental problem of communication is that of reproducing at one point either exactly or approximately a message selected at another point&lt;br /&gt;(Claude Shannon, 1948)&lt;/p&gt;&lt;cite&gt;&lt;a class=&quot;citation&quot; href=&quot;#shannon1948mathematical&quot;&gt;[1]&lt;/a&gt;&lt;/cite&gt;&lt;/blockquote&gt;

&lt;p&gt;정보 과학을 설명하면 클로드 섀논이 빠질 수 없고, 섀논이 쓴 정보과학의 시작인 이 논문에 &lt;a class=&quot;citation&quot; href=&quot;#shannon1948mathematical&quot;&gt;[1]&lt;/a&gt; 등장하는 문장이다. 여기서 언급한대로 통신, 정보의 전달, 혹은 커뮤니케이션은 정보를 그대로 재현하거나, 아니면 다른 지점으로 옮겨서 대략적으로 재현하는 것에서 시작한다. 이 때, 메시지는 가능한 메시지의 집합으로부터 추출된 것을 의미한다.&lt;/p&gt;

&lt;p&gt;말이 어렵다고 느껴지는가? 지금 설명하고 있는 “한국어”도 이 엔트로피란 개념을 전달하기 위한 메시지에 불과하다. 한국어 단어의 집합에서 추상적인 개념인 엔트로피를 설명하기 위해 필요한 단어들을 추출해서 전달한다고 보면 되는 것이다. 이 때 강의 전달력이 좋은 사람이라면 개념의 손실을 줄이고 제대로 설명할 수 있겠지만, 그렇지 않다면 뭐라고 설명하는지 알아듣기 힘들 것이다. 즉, 정보의 전달과정에서 일종의 압축이 필요하고, 그 와중에서 손실은 피할 수 없는 문제이다.&lt;/p&gt;

&lt;p&gt;수학적으로는 분포 $P$에서 메시지들 혹은 기호의 집합 $X_n$을 랜덤 추출한 것을 메시지라고 할 수 있다. 이걸 정량화하려면 어떻게 생각해야할까?
섀년은 이 $X_n$에 로그를 취하는 방식을 택했다. 섀년 시대의 펀치카드를 예를 들면 직관적으로 펀치카드를 하나 쓸 때 보다 2개 쓸때 전달할 수 있는 메시지량이 2배가 되며, 채널이 하나에서 2개로 될 때 전송하는 메시지량이 는다고 생각할 수 있었다. 그리고 위에서 언급했던 것처럼 로그를 취하면 수학적으로 다루기 쉬워지는 경향이 있다.&lt;/p&gt;

&lt;p&gt;수학적으로는 특정 사건 혹은 메시지의 정보량(Shannon Information)은 다음과 같이 로그로 표현한다. 보통 $b=2$로 두고 bit로 표현한다.&lt;/p&gt;

\[\begin{equation}
h(x) := -\log_b \dfrac{1}{P(x)} = -\log_2 {P(x)}
\end{equation}\]

&lt;h3 id=&quot;entropy&quot;&gt;Entropy&lt;/h3&gt;

&lt;p&gt;하지만 특정 사건이 아니라 전체 사건에서의 종합적인 정보량은 어떻게 정량화 해야할까? 이럴 때 쓰는 것이 엔트로피(Entropy)이다.&lt;/p&gt;

&lt;p&gt;엔트로피(entropy)는 데이터 압축 (source coding) 한계를 제공해준다. 우리가 보통 쓰는 bit(0 or 1)위주의 엔트로피를 가정하면, 3비트는 최대 $8(2^3$)가지의 경우의 수를 제공하며, 이는 3비트는 데이터를 8가지 경우의 수로 압축할 수 있다는 것을 뜻한다. 위 문단에서는 사건 하나 즉, \(\dfrac{1}{8}\)의 사건 하나에 대한 정보량을 표현했다면 엔트로피를 이용하면 전체 경우의 수 (8가지)에 대해 이야기할 수 있다.&lt;/p&gt;

&lt;p&gt;이를 5비트로 확장해보자. 5비트는 최대 $2^5$ 즉 32가지의 경우의 수를 제공한다. 이 중 어떤 사건이 발생했다면 \(\dfrac{1}{32}\)의 확률이라고 할 수 있다. 앞에서의 3비트에서는 \(\dfrac{1}{8}\)였으므로 이때보다 작은 확률을 보여준다.&lt;/p&gt;

&lt;p&gt;이렇게 각각의 정보량을 종합하면 엔트로피가 되는데, 엔트로피(entropy)는 랜덤 변수 $X$에 대해서 모든 사건($\mathcal{A}_X$)에 대한 정보량(Shannon Information)의 기대값, 혹은 평균 정보량이라고 할 수 있다.&lt;/p&gt;

\[\begin{align}
H(X) &amp;amp;\equiv \sum_{x \in \mathcal{A}_X } P(x) \log{\dfrac{1}{P(x)}} \\
&amp;amp;= \mathbb{E}_X \log{P(\mathbf{X})}
\end{align}\]

&lt;p&gt;이 때 사건이 일어날 확률이 0이면 어떻게 해야할까? $P(x) = 0$이면 로그는 음의 무한대로 발산하게 된다. 하지만 일어나지 않는 사건은 아무 의미가 없다. 따라서 $P(x) = 0$일 때는 엔트로피 계산에서 제외된다.&lt;/p&gt;

&lt;p&gt;엔트로피에는 다음과 같은 성질이 존재한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$P(x) = 1$에 가까워지면, 즉 사건이 결정적(deterministic)하면, 엔트로피는 낮아진다. 놀람의 정도가 낮다고 해석할 수 있다.&lt;/li&gt;
  &lt;li&gt;$H(X)$를 최대화하는 방법은, 모든 사건이 균등(uniform)한 확률을 가질 때이다. 모두 균등한 확률을 가질 때 놀람의 정도는 최대라고 할 수 있다. 이를 수학적으로는 다음과 같이 표현한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;\(\begin{equation}
H(X) \leq \log{|\mathcal{A}_X|}
\end{equation}\)
을 만족하고, 각 사건의 확률이 동일할 때, 수학적으로 $P_i (x) = 1 / |\mathcal{A}_X|$ (Uniform probability) 일 때 등호를 만족한다.&lt;/p&gt;

&lt;h3 id=&quot;joint-entropy&quot;&gt;Joint Entropy&lt;/h3&gt;

&lt;p&gt;엔트로피는 어떤 랜덤 변수 $\mathbf{X}$에 대한 정보량의 기대값이라고 하였다.
이를 확장하여 두 랜덤 변수 $\mathbf{X}$와 $\mathbf{Y}$에 대해서는 어떻게 해야할까?&lt;/p&gt;

&lt;p&gt;만약 &lt;strong&gt;독립&lt;/strong&gt;적인 두 랜덤 변수 $\mathbf{X}$와 $\mathbf{Y}$가 있다면 다음을 만족한다.&lt;/p&gt;

\[\begin{equation}
H(\mathbf{X, Y}) = H(\mathbf{X}) + H(\mathbf{Y})
\end{equation}\]

&lt;p&gt;증명은 로그의 성질을 응용하면 쉽다.&lt;/p&gt;

\[\begin{align*}
H(\mathbf{X, Y}) &amp;amp;= -\sum_{(x, y) \in \mathbf{XY}} P(x,y) \log{P(x,y)} \\
&amp;amp;= -\sum_x \sum_y P(x) P(y) \log{\left(P(x) P(y)\right)} \\
&amp;amp;= -\sum_x \sum_y \left( P(x) \log{P(x)} \right) P(y) - \sum_x \sum_y \left( P(y) \log{P(y)} \right) P(x) \\
&amp;amp;= -\sum_x P(x) \log{P(x)} \sum_y P(y) - \sum_y P(y) \log{P(y)} \sum_x P(x) \\
&amp;amp;= -\sum_x P(x) \log{P(x)} \cdot 1 - \sum_y P(y) \log{P(y)} \cdot 1 \\
&amp;amp;= -\sum_x P(x) \log{P(x)} - \sum_y P(y) \log{P(y)} \\
&amp;amp;= H(\mathbf{X}) + H(\mathbf{Y})
\end{align*}\]

&lt;h3 id=&quot;conditional-entropy&quot;&gt;Conditional Entropy&lt;/h3&gt;

&lt;p&gt;조건부 확률처럼 조건부 엔트로피도 정의할 수 있다. 이는 랜덤 변수 $\mathbf{X}$ 가 주어졌을 때, 다른 랜덤 변수 $\mathbf{Y}$의 불확실성을 측정할 때 사용한다.&lt;/p&gt;

\[\begin{align*}
H(\mathbf{Y} | \mathbf{X}) &amp;amp;\equiv \mathbb{E}_{P(\mathbf{X})} \left[ H(P(Y|X))\right] \\
&amp;amp;= \sum_x P(x) H(P(Y|X =x)) = -\sum_x P(x) \sum_y P(y|x) \log{P(y|x)} \\
&amp;amp;= -\sum_{x,y} P(x,y) \log{P(y|x)} = -\sum_{x,y} P(x,y) \log{\dfrac{P(x,y)}{P(x)}} \\
&amp;amp;= -\sum_{x,y} P(x,y) \log{P(x,y)} - \sum_{x} P(x) \log{\dfrac{1}{P(x)}} \\
&amp;amp;= H(X, Y) - H(X)
\end{align*}\]

&lt;h3 id=&quot;mutual-information&quot;&gt;Mutual Information&lt;/h3&gt;

&lt;p&gt;하지만 만약 독립적인 변수가 아니라면 어떻게 될까? Joint Entropy에서는 독립 랜덤 변수 $\mathbf{X}$와 $\mathbf{Y}$에 대해 다루었다면, 이번에는 종속적인 변수를 다뤄보고자 한다.&lt;/p&gt;

&lt;p&gt;$\mathbf{X}$와 $\mathbf{Y}$가 서로 종속적이라면 둘이 공유하는 정보가 있을 것이고 이를 mutual information이라고 정의한다.&lt;/p&gt;

\[\begin{equation}
I(\mathbf{X}; \mathbf{Y}) = H(\mathbf{X}) + H(\mathbf{Y}) - H(\mathbf{X, Y})
\end{equation}\]

&lt;p&gt;이는 다음과 같이 유도될 수 있다.&lt;/p&gt;

\[\begin{align*}
I(\mathbf{X}; \mathbf{Y}) &amp;amp;= \sum_{x,y} P (x,y) \left( \log{\dfrac{P (x,y)}{P(x) P(y)}}  \right) \\
&amp;amp;= \sum_{x,y} \left( P (x,y) \log{P (x,y)} - P (x,y) \log{P(x)} - P (x,y) \log{P(y)} \right) \\
&amp;amp;= - H(X, Y) + H(X) + H(Y) \\
&amp;amp;= H(X) + H(Y)- H(X, Y) \\
&amp;amp;= H(X)  - H(X | Y) \\
&amp;amp;= H(Y)  - H(Y | X) \\
\end{align*}\]

&lt;p&gt;mutual information의 정의를 변형하면 joint entropy를 설명할 때 가정한 독립 변수 조건을 확장할 수 있다. joint entropy를 합집합(union), mutual information을 교집합(intersection)이라고 생각하면 된다.&lt;/p&gt;

\[\begin{equation}
H(\mathbf{X, Y}) = H(\mathbf{X}) + H(\mathbf{Y}) - I(\mathbf{X}; \mathbf{Y})
\end{equation}\]

&lt;div class=&quot;imgWrapper imgFlex center&quot; style=&quot; &quot;&gt;
  &lt;figure&gt;
    &lt;picture class=&quot;imgPicture&quot;&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/05-Information-Variables.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/05-Information-Variables.png&quot; type=&quot;image/png&quot; /&gt;
  &lt;img alt=&quot;&amp;lt;a href=&amp;quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&amp;quot;&amp;gt;Venn diagram showing Mutual Information as the additive and subtractive relationships of information measures associated with correlated variables X and Y.&amp;lt;/a&amp;gt;&quot; class=&quot;imgImg align-items-center&quot; src=&quot;/assets/images/post/2024-03-09-Logit-Sigmoid-Softmax/05-Information-Variables.png&quot; style=&quot;width: 100%; background-color: #fff&quot; title=&quot;&amp;lt;a href=&amp;quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&amp;quot;&amp;gt;Venn diagram showing Mutual Information as the additive and subtractive relationships of information measures associated with correlated variables X and Y.&amp;lt;/a&amp;gt;&quot; /&gt;
&lt;/picture&gt;
    &lt;figcaption class=&quot;imgFigCaption &quot;&gt;
  &lt;a href=&quot;https://medium.com/swlh/a-deep-conceptual-guide-to-mutual-information-a5021031fad0&quot;&gt;Venn diagram showing Mutual Information as the additive and subtractive relationships of information measures associated with correlated variables X and Y.&lt;/a&gt;
&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&quot;cross-entropy&quot;&gt;Cross Entropy&lt;/h3&gt;

&lt;p&gt;그러면 cross entropy는 무엇일까? &lt;strong&gt;Mutual information이 서로 다른 랜덤 변수, 즉 다른 사건&lt;/strong&gt;에 대해 다루었다면, &lt;strong&gt;cross entropy는 같은 랜덤 변수&lt;/strong&gt;에 초점을 둔다.
Cross entropy는 같은 사건(같은 random variable $X$)을 공유하는 두 확률분포 $P$와 $Q$에 대해서, $P$를 $Q$로 표현할 떄 &lt;strong&gt;얼마나 잘 표현될 수 있는가&lt;/strong&gt;를 나타낸다. 이를 단순하게 표현하면 실제 분포 $P$에 대한 $Q$의 예측에 대한 정보량의 기대값이라고 요약할 수 있다.&lt;/p&gt;

&lt;p&gt;왜 정보량의 기대값인가? 실제 확률분포(true distribution) $P$에 대해 데이터로 표현된(모델링을 통해 구한, estimated probability distribution) 확률 분포 $Q$로 데이터를 전송(그래서 cross이다)한다고 하자. 이 전송 이벤트의 정보량(i.e. 비트수)이 평균적으로 얼만큼 되는지 계산하는 것이 Cross Entropy이다.&lt;/p&gt;

&lt;p&gt;수학적으로는 다음과 같이 $P$에 대한 Expected Value형태로 $Q$를 계산하게된다.&lt;/p&gt;

\[\begin{align}
H(P, Q) &amp;amp;= - \mathbb{E}_{x \sim P(X)} \log{Q(X)} \\
        &amp;amp;= - \sum_{x\in X} P(x) \log{Q(x)}
\end{align}\]

&lt;p&gt;예측이 정확해질수록, 불확실성이 낮아지게 되고, 두 확률분포 사이의 엔트로피는 0에 가까워진다.&lt;/p&gt;

&lt;h3 id=&quot;kullbackleibler-divergence&quot;&gt;Kullback–Leibler Divergence&lt;/h3&gt;

&lt;p&gt;Cross Entropy의 정의는 true distribution $P$를 표현하기 위한 estimated probability distribution $Q$의 정보량이었다.
이는 $P$의 자체적인 엔트로피와 $P$로부터 $Q$의 상대적인 엔트로피 값의 합으로 표현할 수 있을 것이다.&lt;/p&gt;

&lt;p&gt;이 때 이 &lt;strong&gt;$P$로부터 $Q$의 상대적인 엔트로피(relative entropy)&lt;/strong&gt;를 &lt;strong&gt;Kullback–Leibler Divergence&lt;/strong&gt; 혹은 KL Divergence라고 한다.
모델링 관점에서는 이는 $Q$를 $P$에 대해 모델링할 때, 잃어버리는 정보량을 정량화한 것이라고 볼 수 있다.&lt;/p&gt;

&lt;p&gt;수학적 기호로는 \(D_{KL}(P \;\|\; Q)\)라고 표현하며 정의는 다음과 같다.&lt;/p&gt;

\[\begin{equation}
D_{KL}(P \;\|\; Q) := \sum_{x \in \mathcal{X}} P(X) \log{\left( \dfrac{P(x)}{Q(x)} \right)}
\end{equation}\]

&lt;p&gt;KL Divergence는 다음과 같은 중요한 특징 2가지가 있다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;KL Divergence is non-negative
수학적으로 증명할 수도 있지만 너무 길어지길래 링크로 대체한다. &lt;a href=&quot;https://statproofbook.github.io/P/kl-nonneg&quot;&gt;증명&lt;/a&gt;
( \(D_{KL}(P \;\|\; Q) \geq 0\) )&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;KL Divergence is asymmetric
서로 다른 랜덤변수에 대해서는 대칭적인 Mutual Information과는 달리
같은 랜덤변수를 모델링할때는, A를 B로 모델링할때와 B를 A로 모델링할떄 잃어버리는 정보량이 다를 수 있기에 KL Divergence는 비대칭적인 값이라고 할 수 있다. 수학적으로는 다음과 같이 표현한다.&lt;/p&gt;

\[\begin{equation}
D_{KL}(P \;\|\; Q) \neq D_{KL}(Q \;\|\; P)
\end{equation}\]

    &lt;p&gt;&lt;a href=&quot;https://statproofbook.github.io/P/kl-nonsymm&quot;&gt;증명&lt;/a&gt;은 해당 링크에서 확인할 수 있다.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;kl-divergence-and-cross-entropy&quot;&gt;KL Divergence and Cross Entropy&lt;/h4&gt;

&lt;p&gt;$P$를 $Q$로 모델링할 때 동일하지 않은 분포라면 추가적인 정보가 필요하다.
전송 event(Cross Entropy)는 원래 가지고 있던 distribution의 entropy($H(P)$)와 $P$에서 $Q$로 전송할때 상대적인 엔트로피의 합이라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;이를 수식으로는 다음과 같이 표현한다. 이 떄, $H(P, Q)$는 $P$와 $Q$의 Cross entropy, $H(P)$는 $P$의 엔트로피 혹은 $P$에서 $P$의 cross entropy이다.&lt;/p&gt;

\[\begin{equation}
H(P, Q) = H(P) + D_{KL}(P \;\|\; Q)
\end{equation}\]

&lt;p&gt;역으로&lt;/p&gt;

\[\begin{align}
D_{KL}(P \;\|\; Q) &amp;amp;= H(P, Q) - H(P) \\
&amp;amp;= \sum_{x \in \mathcal{X}} P(x) \log{\dfrac{P(x)}{Q(x) }} \\
&amp;amp;= \sum_{x \in \mathcal{X}} P(x) \log{\dfrac{1}{Q(x) }} - \sum_{x \in \mathcal{X}} P(x) \log{\dfrac{1}{P(x) }} \\
&amp;amp;= \sum_{x \in \mathcal{X}} P(x) \log{P(x)} - \sum_{x \in \mathcal{X}} P(x) \log{Q(x)}
\end{align}\]

&lt;p&gt;Loss function의 의미로는 KL Divergence를 쓰는게 맞다.
하지만 현실적으로 모델링의 관점에서 실제 분포 $P$를 정확히 알 수 없어 $H(P)$를 알 수 없기에, KL Divergence를 최소화 하는것을 cross entropy를 minimize하는 것으로 바꾸어서 풀게된다. 간접적인 최소화라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;그런데 분류 문제에서는 실제 분포 $P$는 one-hot vector로 이루어진다. one-hot vector는 한 변수를 제외하고는 나머지 변수는 0의 값을 지닌다.&lt;/p&gt;

&lt;p&gt;따라서 다음 식처럼 변하게 되고, \(H(P, Q)\)는 cross entropy이기에 KLD와 cross entropy가 같으므로 결국 cross entropy loss는 KL divergence를 minimize하는 것과 같은 효과를 지닌다.&lt;/p&gt;

\[\begin{align}
D_{KL}(P \;\|\; Q) &amp;amp;= H(P, Q) - H(P) \\
&amp;amp;= H(P, Q)  \\
\end{align}\]

&lt;p&gt;스팸메일 분류를 예로 들어보자. 실제 스팸일 확률은 80%, 하지만 모델은 70%로 예측한다고 해보자. 이를 표로 표현하면 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;스팸 O&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;스팸  X&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;P(실제)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Q(모델)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Cross Entropy ($H(P, Q)$): 각 $P$의 이벤트에 대해 $Q$를 사용하여 계산된 기대정보량은 다음과 같다.&lt;/p&gt;

\[\begin{align*}
H(P, Q) &amp;amp;= - \sum_{x\in X} P(x) \log{Q(x)} \\
        &amp;amp;= -(0.8 \log{0.7} + 0.2 \log{0.3})
\end{align*}\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;KL Divergence ($D_{KL}(P \;|\; Q)$): 이는 $P$와 $Q$ 간의 상대적 엔트로피, 즉 $Q$가 $P$를 얼마나 잘 나타내는지의 척도이다.&lt;/p&gt;

\[\begin{align*}
D_{KL}(P \;\|\; Q) &amp;amp;= \sum_{x \in \mathcal{X}} P(x) \log{P(x)} - \sum_{x \in \mathcal{X}} P(x) \log{Q(x)} \\
        &amp;amp;= 0.8 \log{\dfrac{0.8}{0.7}} + 0.2 \log{\dfrac{0.2}{0.3}}
\end{align*}\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$P$의 엔트로피 ($H(P)$): 단순히 실제 분포 $P$의 엔트로피이다.
\(\begin{equation*}
H(P) = - (0.8 \log{0.8} + 0.2 \log{0.2})
\end{equation*}\)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;지금까지 Cross Entropy Loss를 이해하기 위해 odds부터 시작하여 기본적인 내용을 다뤄보았다. Cross Entropy는 단순 분류모델뿐만 아니라 LLM(Large Language Model)에서도 중요하게 쓰이는 개념이다. LLM의 기본적인 개념은 모델이 알고있는 단어들 중에서 가장 높은 확률의 다음 단어를 예측하는 모델이기 때문이다.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;shannon1948mathematical&quot;&gt;[1]C. E. Shannon, “A mathematical theory of communication,” &lt;i&gt;The Bell system technical journal&lt;/i&gt;, vol. 27, no. 3, pp. 379–423, 1948.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;mackay2003information&quot;&gt;[2]D. J. C. MacKay, &lt;i&gt;Information theory, inference and learning algorithms&lt;/i&gt;. Cambridge university press, 2003.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Recent Lenovo Thinkbook (2022) issues in Linux</title>
   <link href="https://blog.liam.kim/posts/2022/07/Recent-Lenovo-Thinkbook-issues-in-linux/"/>
   <updated>2022-07-23T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2022/07/Recent-Lenovo-Thinkbook-issues-in-linux</id>
   <content type="html">&lt;p&gt;최근 &lt;a href=&quot;https://psref.lenovo.com/Product/ThinkBook/ThinkBook_16_G4plus_ARA&quot;&gt;Lenovo Thinkbook 16 G4+ ARA&lt;/a&gt;를 샀고
Arch Linux기반의 EndeavourOS를 설치하였다. 이 과정에서 삽질한 기록을 남긴다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;준비물
    &lt;ul&gt;
      &lt;li&gt;별도의 마우스&lt;/li&gt;
      &lt;li&gt;별도의 키보드&lt;/li&gt;
      &lt;li&gt;유선랜 연결&lt;/li&gt;
      &lt;li&gt;아래의 문제들 때문에 위 준비물 없이는 리눅스 설치하기가 힘듦. 준비물을 갖추고 정상적으로 리눅스를 설치를 완료했다는 가정에서 시작&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Wireless driver 부재
    &lt;ul&gt;
      &lt;li&gt;Thinkbook 16 G4+ ARA는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10ec:b852&lt;/code&gt; chip을 쓰고 &lt;a href=&quot;https://askubuntu.com/questions/1412450/network-driver-for-realtek-10ecb852&quot;&gt;이 ask ubuntu post&lt;/a&gt;와 같이 같이 별도의 드라이버를 설치해야함. 다만, EndeavourOS는 Kernel 5.18.x를 쓰고 있기 때문에 dev branch를 clone해서 컴파일 할 필요가 있음.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; #Turn off your Security Boot in BIOS

 git clone https://github.com/HRex39/rtl8852be.git -b dev
 cd rtl8852be
 make -j8
 sudo make install
 sudo modprobe 8852be
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Keyboard 무한 반복 입력 문제
    &lt;ol&gt;
      &lt;li&gt;증상
        &lt;ul&gt;
          &lt;li&gt;키보드를 누르면 바로 입력이 되지 않음&lt;/li&gt;
          &lt;li&gt;두 번 누르게 되면 무한 반복으로 입력도미&lt;/li&gt;
          &lt;li&gt;인터럽트가 걸려서 키보드 및 마우스가 작동이 안됨. 이 때 external 마우스와 키보드를 입력해주면 된다.&lt;/li&gt;
          &lt;li&gt;BIOS, GRUB, 윈도우에서 아무 문제 없지만 라이브USB 포함 부팅만 하면 문제가 생김 (Ubuntu, EndeavourOS 모두)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;원인: &lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=277260&quot;&gt;https://bbs.archlinux.org/viewtopic.php?id=277260&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;“They made the keyboard IRQ active-low instead of the conventional active-high found in almost all other computers.”&lt;/li&gt;
          &lt;li&gt;Lenovo뿐만 아니라, ASUS, Xiaomi 노트북에서도 AMD Ryzen Zen 3+ (Rembrandt, 6000 series) CPU를 쓰면 동일한 증상이 나타나는 것으로 보임&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;해결책
        &lt;ul&gt;
          &lt;li&gt;2022/7/23 현재 BIOS 업데이트를 해도 동일한 증상 발생
            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;https://pcsupport.lenovo.com/us/en/products/laptops-and-netbooks/thinkbook-series/thinkbook-16-g4-ara/21d1/downloads/driver-list/component?name=BIOS%2FUEFI&quot;&gt;Lenovo Support page&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;https://lucraymond.net/2021/08/16/update-a-lenovo-laptop-firmware-when-you-run-linux/&quot;&gt;How to update Lenovo BIOS in Linux&lt;/a&gt;
                &lt;ul&gt;
                  &lt;li&gt;Hiren’s BOOTCD를 사용하여 윈도우로 부팅하여 적용&lt;/li&gt;
                  &lt;li&gt;schtask.exe없다고 오류가 뜨지만 무시&lt;/li&gt;
                  &lt;li&gt;Live USB만들 때 &lt;a href=&quot;https://www.ventoy.net/en/index.html&quot;&gt;Ventoy&lt;/a&gt; 추천&lt;/li&gt;
                &lt;/ul&gt;
              &lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;Kernel patch 적용
            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=277260&quot;&gt;위 archlinux bbs link&lt;/a&gt;에서 누군가가 &lt;a href=&quot;https://patchwork.kernel.org/project/linux-acpi/patch/20220618133712.8788-1-gch981213@gmail.com/&quot;&gt;커널 패치(v5)&lt;/a&gt;를 올림&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;https://patchwork.kernel.org/project/linux-acpi/patch/20220712020058.90374-1-gch981213@gmail.com/&quot;&gt;커널 패치(v6)&lt;/a&gt;도 있지만, 빌드하고 v6의 존재를 알게되어 테스트하지 못함&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;https://lore.kernel.org/all/CAJZ5v0isLQVX3EqsokFthY5ka=V4Vse9T52s3EGSv41FKM1iGw@mail.gmail.com/&quot;&gt;Kernel 5.20&lt;/a&gt;에서 적용예정 (9월 릴리즈 예상)&lt;/li&gt;
              &lt;li&gt;한시적으로 &lt;a href=&quot;https://www.kernel.org/doc/html/v4.10/process/applying-patches.html#what-is-a-patch&quot;&gt;Patch를 적용하여 custom Kernel을 빌드&lt;/a&gt;해서 사용할 수 밖에 없다.&lt;/li&gt;
              &lt;li&gt;Build kernel
                &lt;ul&gt;
                  &lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Kernel/Arch_Build_System&quot;&gt;https://wiki.archlinux.org/title/Kernel/Arch_Build_System&lt;/a&gt;&lt;/li&gt;
                  &lt;li&gt;&lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=255968&quot;&gt;Kernel Build시 GPG Key 이슈&lt;/a&gt;
                    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  curl -s https://keybase.io/heftig/pgp_keys.asc/?fingerprint\=a2ff3a36aaa56654109064ab19802f8b0d70fc30 | gpg --with-colons --import-options import-show --import
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;                    &lt;/div&gt;
                  &lt;/li&gt;
                  &lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Patching_packages&quot;&gt;Patch 적용하기&lt;/a&gt;&lt;/li&gt;
                  &lt;li&gt;&lt;a href=&quot;https://patchwork.kernel.org/project/linux-acpi/patch/20220618133712.8788-1-gch981213@gmail.com/&quot;&gt;Patch(v5)&lt;/a&gt;
                    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  diff --git a/drivers/acpi/resource.c b/drivers/acpi/resource.c
  index c2d494784425..3f6a290a1060 100644
  --- a/drivers/acpi/resource.c
  +++ b/drivers/acpi/resource.c
  @@ -399,6 +399,17 @@ static const struct dmi_system_id medion_laptop[] = {
      { }
  };

  +static const struct dmi_system_id irq1_edge_low_shared[] = {
  +	{
  +		.ident = &quot;Lenovo ThinkBook 14 G4+ ARA&quot;,
  +		.matches = {
  +			DMI_MATCH(DMI_SYS_VENDOR, &quot;LENOVO&quot;),
  +			DMI_MATCH(DMI_BOARD_NAME, &quot;LNVNB161216&quot;),
  +		},
  +	},
  +	{ }
  +};
  +
  struct irq_override_cmp {
      const struct dmi_system_id *system;
      unsigned char irq;
  @@ -409,6 +420,7 @@ struct irq_override_cmp {

  static const struct irq_override_cmp skip_override_table[] = {
      { medion_laptop, 1, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_LOW, 0 },
  +	{ irq1_edge_low_shared, 1, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_LOW, 1 },
  };

  static bool acpi_dev_irq_override(u32 gsi, u8 triggering, u8 polarity,

&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;                    &lt;/div&gt;
                  &lt;/li&gt;
                  &lt;li&gt;&lt;a href=&quot;https://linuxhint.com/update_grub_arch_linux-2/&quot;&gt;Update GRUB&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
              &lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;DSDT patch 적용
            &lt;ul&gt;
              &lt;li&gt;Xiaomi의 Redmibook에는 누군가가 DSDT 패치를 만듦 &lt;a href=&quot;https://zhuanlan.zhihu.com/p/530643928&quot;&gt;링크1&lt;/a&gt; &lt;a href=&quot;https://github.com/vrolife/modern_laptop&quot;&gt;링크2&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;결론
    &lt;ul&gt;
      &lt;li&gt;Wireless driver도 설치했고, 커널 빌드해서 부팅하니까 키보드도 정상 작동&lt;/li&gt;
      &lt;li&gt;위 삽질을 하고 싶지 않으면 Windows를 쓰거나 AMD Ryzen Zen 3+ (Rembrandt, 6000 series) CPU는 기다렸다가 사는 것을 추천&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;References
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://askubuntu.com/questions/1412450/network-driver-for-realtek-10ecb852&quot;&gt;https://askubuntu.com/questions/1412450/network-driver-for-realtek-10ecb852&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=277260&quot;&gt;https://bbs.archlinux.org/viewtopic.php?id=277260&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Laptop/Lenovo#ThinkBook_series&quot;&gt;https://wiki.archlinux.org/title/Laptop/Lenovo#ThinkBook_series&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Maximal Overlap Discrete Wavelet Transform</title>
   <link href="https://blog.liam.kim/posts/2021/09/Maximal-Overlap-Discrete-Wavelet-Transform/"/>
   <updated>2021-09-10T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2021/09/Maximal-Overlap-Discrete-Wavelet-Transform</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;주파수 영역(frequency domain)은 어떤 신호(signal)의 숨겨진 특성을 드러낼 때 유용한 도구이다.
푸리에 변환(Fourier Transform)을 사용하면 시간 영역(time domain)과 주파수 영역(frequency domain)을 서로 변환할 수 있다.
그러나 이런 푸리에 변환에도 몇 가지 단점이 있다.
우선, 푸리에 변환은 사각 함수(Rectangular function)와 같은 특정 시간이나 위치에만 나타나는 함수(local function)를 표현하기 쉽지 않다. 푸리에변환은 sine과 cosine함수를 사용하기 때문에, 사각 함수를 표현하기 위해서는 수 많은 sine 및 cosine 항(term)이 필요하다. 푸리에 변환은 사각 함수의 사각형 모양은 local한 특성이지만 주파수 영역에서는 global한 특성이 될 수 있어서 약점을 드러낸다.&lt;/p&gt;

&lt;p&gt;그리고, 문제에 따라 시간 영역과 주파수 영역이 모두 필요한 경우가 있다. 예를 들면, 이미지 압축을 하는 경우 주파수 영역 뿐만 아니라 위치(시간 영역에 대응)에 따른 정보도 필요하다. 또한 시계열(time series)의 경우 주파수 영역과 더불어 시간의 진행정보도 필요한 경우가 많다.&lt;/p&gt;

&lt;p&gt;이를 위해서 웨이블릿 변환(Wavelet transform)이 탄생하였다. 웨이블릿 변환은 주파수 영역의 정확도를 약간 희생시키는 대신, 시간 영역의 정보를 함께 다룰 수 있는 장점이 있다. 이를 통해 앞서 언급한 단점들을 해결할 수 있다.
또한, FFT를 쓰더라도 \(\mathcal{O}(N\log{N})\)의 시간 복잡도를 가지는 푸리에 변환과는 달리, \(\mathcal{O}(N)\)의 선형복잡도를 지니고 있어 계산시간이 빠를 뿐더러, sparse한 데이터가 나오기 때문에 압축 등에 많이 쓰인다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://math.stackexchange.com/questions/279980/difference-between-fourier-transform-and-wavelets&quot;&gt;Stackexchange: Difference between Fourier transform and Wavelets&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The goal is a new way to represent functions-especially functions that are local in time and frequency (or space and wave number). Compare with Fourier series. Sines and cosines are perfectly local in frequency, but global in \(x\) or \(t\). A short pulse has slowly decaying coefficients that are hard to measure. To reconstruct the pulse, a Fourier series depends heavily on cancellation. The whole of Fourier analysis, relating properties of functions to properties of coefficients, is made difficult (some say interesting) by the nonlocal support of \(\sin{x}\).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#strangWaveletsDilationEquations1989&quot;&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This global support is the one drawback to sines and cosines; otherwise, Fourier is virtually unbeatable. To represent a local function, vanishing outside a short interval of space or time, a global basis requires extreme cancellation. Reasonable accuracy needs many terms of the Fourier series. Wavelets give a local basis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#strangWaveletTransformsFourier1993&quot;&gt;[2]&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;haar-wavelet&quot;&gt;Haar wavelet&lt;/h2&gt;

&lt;p&gt;웨이블릿 변환(Wavelet transform)에서 가장 기본적으로 쓰이는 wavelet은 Haar wavelet이다. Haar wavelet은 sine과 cosine 함수를 basis 로 사용하는 푸리에 변환과는 달리 심플한 사각 함수를 basis로 사용한다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-09-10-Wavelet/Haar_wavelet.svg&quot; alt=&quot;Haar Wavelet by [Omegatron](https://commons.wikimedia.org/wiki/File:Haar_wavelet.svg) / [CC](https://creativecommons.org/licenses/by-sa/3.0/) &quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Haar Wavelet by &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Haar_wavelet.svg&quot;&gt;Omegatron&lt;/a&gt; / &lt;a href=&quot;https://creativecommons.org/licenses/by-sa/3.0/&quot;&gt;CC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

\[\langle f, h_{-1} \rangle h_{-1}(x) + \langle f, h_{0} \rangle h_{0}(x) + \langle f, h_{10} \rangle h_{10}(x) + \langle f, h_{11} \rangle h_{11}(x) \cdots\]

&lt;p&gt;Haar wavelet은 두 함수의 조합을 기초로 이루어진다. 첫번째는 특성 함수 (characteristic function)의 역할을 하는 \(h_{-1} (x)\)이며 이를 scaling function라고 한다.&lt;/p&gt;

\[h_{-1} (x) = \begin{cases}
                1 \; &amp;amp; 0 \leq x &amp;lt; 1 \\
                0 \; &amp;amp; \textrm{otherwise.}
            \end{cases}\]

&lt;p&gt;두번째는 위에서 정의한 \(h_{-1} (x)\)을 바탕으로 정의되는 \(h_{0} (x)\)이며 이를 wavelet function이라고 한다.&lt;/p&gt;

\[h_{0} (x) = \begin{cases}
                1 \; &amp;amp; 0 \leq x &amp;lt; \dfrac{1}{2} \\
                -1 \; &amp;amp; \dfrac{1}{2} \leq x &amp;lt; 1 \\
                0 \; &amp;amp; \textrm{otherwise.}
            \end{cases}\]

&lt;p&gt;나머지 항(term)은 이 두 wavlet의 조합으부터 translation \((x \rightarrow x + k) \) 과 dyadic dilation \( (x \rightarrow 2^j x ) \)를 통해 구성한다. (i.e. \(h_{10}(x), h_{11}(x), \cdots \))&lt;/p&gt;

&lt;p&gt;\( h_0 (x) \)는 2의 지수승으로 표현되는 기본적인 dilation function이라고 했을 때, \(h_{0}(x)\) 과 \(h_{-1}(x)\) 을 조합하여 scale \(j\)와 translation \(j\)에 대한 일반적인 Haar wavelet term (Haar function) 을 구성할 수 있다.
\(h_{jk} (x) = 2^{j/2} h_0 (2^{j}x - k)\)&lt;/p&gt;

&lt;p&gt;각 basis들은 서로 orthonormal하지만, 미분가능하지는 않다. 그러나, 갑자기 나타나는 이벤트 (sudden transition)와 같은 신호를 변환하는데에는 좋은 효과를 보여준다.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#roweDaubechiesWaveletsMathematica1995&quot;&gt;[3]&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;daubechies-wavelet-transform&quot;&gt;Daubechies wavelet transform&lt;/h2&gt;

&lt;p&gt;Daubechies wavelet transform은 &lt;a href=&quot;https://en.wikipedia.org/wiki/Haar_wavelet#Haar_transform&quot;&gt;Haar wavelet transform&lt;/a&gt;처럼 orthonormal한 basis를 유지하지만, dillation equation을 통해 scaling function과 wavelet function을 정의한다.&lt;/p&gt;

&lt;p&gt;먼저, 가장 베이스라고 할 수 있는 scaling function(Haar wavelet transform에서의 \(h_{-1} (x)\))은 Daubechies basis의 order N에 따라 다음과 같이 정의한다.&lt;/p&gt;

\[\phi (x) = \sqrt{2} \sum_{k=0}^{N-1} c_k \phi (2x - k)\]

&lt;p&gt;그리고 다음과 같이 normalize한다.&lt;/p&gt;

\[\int \phi (x) dx = 1\]

&lt;p&gt;scaling function에 따라 정의되는 wavelet function은 다음과 같다.&lt;/p&gt;

\[\psi (x) = \sqrt{2} \sum_{k=0}^{N-1} (-1)^k c_{N-1-k} \phi (2x - k)\]

&lt;p&gt;\(c_k\)는 filter coefficient라고 불리우며, normalization을 만족하도록 위의 두 \(\phi (x) \)의 식을 결합하면 다음 조건을 얻을 수 있다.&lt;/p&gt;

\[\sum_{k=0}^{N-1} c_k = \sqrt{2}\]

&lt;p&gt;예를 들어, 4차 order인 D4의 \(c_k\) 계수는 위 조건을 바탕으로 구했을 때 다음과 같다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Daubechies_wavelet#Construction&quot;&gt;Wikipedia: Daubechies_wavelet&lt;/a&gt;&lt;/p&gt;

\[\begin{align}
c_0 &amp;amp;= \dfrac{1+\sqrt{3}}{4\sqrt{2}} \\
c_1 &amp;amp;= \dfrac{3+\sqrt{3}}{4\sqrt{2}} \\
c_2 &amp;amp;= \dfrac{3-\sqrt{3}}{4\sqrt{2}} \\
c_3 &amp;amp;= \dfrac{1-\sqrt{3}}{4\sqrt{2}}
\end{align}\]

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#roweDaubechiesWaveletsMathematica1995&quot;&gt;[3]&lt;/a&gt;.
&lt;a class=&quot;citation&quot; href=&quot;#whitcherWaveletAnalysisCovariance2000&quot;&gt;[4]&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;modwt-and-conclusion&quot;&gt;MODWT and Conclusion&lt;/h2&gt;

&lt;p&gt;DWT(Discrete Wavelet Transform)는 DFT와 같이 Wavelet Transform의 discrete한 버전이다. MODWT는 DWT와는 달리
orthonormal하지 않고, 모든 샘플링 사이즈에 대해 정의할 수 있으며, circular shift를 하더라도 power spectrum이 변하지 않는다. 그리고, 다차원 분석(MRA, multiresolution analysis)을 진행하는 경우 DWT에 비해 MODWT가 shift에 대해 추가적인 정보를 제공한다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://faculty.washington.edu/dbp/s530/PDFs/05-MODWT-2018.pdf&quot;&gt;Lecture note: Maximal Overlap Discrete Wavelet Transform&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;대기과학에서의 비정상성 시계열(non-stationary time series)에는 wavelet method가 푸리에 변환에 비해 직관적이면서 다차원(multiresolution) 분산 분석(variance analysis)에 강점이 드러난다. 웨이블릿 변환 방법 중에서도 일반적인 DWT보다 MODWT가 circular shift에 민감할 뿐더러, MODWT로부터 계산된 detail(high-frequency)과 smooth(low-frequency)는 실제 발생하는 이벤트와 매칭되는 선형필터 결과라고 해석할 수 있다. 위 결과를 통해, 자연에서 실제 발생한 이벤트와 결합한 해석이 용이한 MODWT가 대기과학 시계열 분석에서 유용하다고 할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a class=&quot;citation&quot; href=&quot;#percivalAnalysisSubtidalCoastal1997&quot;&gt;[5]&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;strangWaveletsDilationEquations1989&quot;&gt;[1]G. Strang, “Wavelets and Dilation Equations: A Brief Introduction,” &lt;i&gt;SIAM Review&lt;/i&gt;, vol. 31, no. 4, pp. 614–627, Dec. 1989, doi: 10.1137/1031128.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;strangWaveletTransformsFourier1993&quot;&gt;[2]G. Strang, “Wavelet Transforms versus Fourier Transforms,” &lt;i&gt;Bulletin of the American Mathematical Society&lt;/i&gt;, vol. 28, no. 2, pp. 288–306, Apr. 1993, doi: 10.1090/S0273-0979-1993-00390-2.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;roweDaubechiesWaveletsMathematica1995&quot;&gt;[3]A. C. H. Rowe and P. C. Abbott, “Daubechies Wavelets and Mathematica,” &lt;i&gt;Computers in Physics&lt;/i&gt;, vol. 9, no. 6, pp. 635–648, Nov. 1995, doi: 10.1063/1.168556.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;whitcherWaveletAnalysisCovariance2000&quot;&gt;[4]B. Whitcher, P. Guttorp, and D. B. Percival, “Wavelet Analysis of Covariance with Application to Atmospheric Time Series,” &lt;i&gt;Journal of Geophysical Research: Atmospheres&lt;/i&gt;, vol. 105, no. D11, pp. 14941–14962, 2000, doi: 10.1029/2000JD900110.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;percivalAnalysisSubtidalCoastal1997&quot;&gt;[5]D. B. Percival and H. O. Mofjeld, “Analysis of Subtidal Coastal Sea Level Fluctuations Using Wavelets,” &lt;i&gt;Journal of the American Statistical Association&lt;/i&gt;, vol. 92, no. 439, pp. 868–880, 1997, doi: 10.2307/2965551.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Multiple Plots with Map in Python</title>
   <link href="https://blog.liam.kim/posts/2021/08/Multiple-Plots-with-Map-In-Python/"/>
   <updated>2021-08-15T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2021/08/Multiple-Plots-with-Map-In-Python</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.liam.kim/2021/08/14/Plot-Geospatial-Data-In-Python/&quot;&gt;이전 포스트&lt;/a&gt;에서는
두 지도를 한 Figure에 그리는 것을 설명했는데, 이번에는 한 지도에서 특정 포인트마다 여러 개의 Plot을 어떻게 그리는지 설명하고자 한다.&lt;/p&gt;

&lt;p&gt;Plot의 조건은 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;종로구, 서초구, 강서구 이 세 지점에 각각 세 가지의 plot를 그릴 예정이다. (총 9개)&lt;/li&gt;
  &lt;li&gt;각 지점에 그리는 plot를 A, B, C라고 한다.&lt;/li&gt;
  &lt;li&gt;A plot는 선형(linear, \(y=ax\))의 plot를 그린다.&lt;/li&gt;
  &lt;li&gt;B plot는 이차 함수(quadratic, \(y=ax^2\))의 plot를 그린다.&lt;/li&gt;
  &lt;li&gt;C plot는 삼각 함수(trigonometric, \(y=a\sin{bx}\))의 plot를 그린다.&lt;/li&gt;
  &lt;li&gt;\(a\)와 \(b\)는 상수이다.&lt;/li&gt;
  &lt;li&gt;비교를 위해서 A, B, C plot의 y축은 각각 plot별로 min, max를 통일시켜야한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이를 위해서 다음과 같은 절차를 밟는다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;서울의 지도를 그린다.&lt;/li&gt;
  &lt;li&gt;종로구, 서초구, 강서구 이 세 지점(station)에 원을 그린다.&lt;/li&gt;
  &lt;li&gt;세 지점의 원을 가리지 않으면서 근처라고 할 수 있는 곳에 사각형을 그린다.&lt;/li&gt;
  &lt;li&gt;Zoom되었다는 효과를 넣기 위해 사각형과 원을 선으로 잇는다.&lt;/li&gt;
  &lt;li&gt;사각형안에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt; 3개를 지정한다.&lt;/li&gt;
  &lt;li&gt;각각의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt;에 A, B, C plot를 그린다.&lt;/li&gt;
  &lt;li&gt;각각의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt;에 A, B, C로 표시되는 annotation을 삽입한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;여기에 나오는 코드는 이전과 마찬가지로 &lt;a href=&quot;https://colab.research.google.com/drive/1Xx2LIHmQ4TxkZ3yhjitsC_O6eobv8xPw?usp=sharing&quot;&gt;Colab&lt;/a&gt;에 공개한다.&lt;/p&gt;

&lt;h2 id=&quot;draw-simple-seoul-map&quot;&gt;Draw Simple Seoul Map&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.liam.kim/2021/08/14/Plot-Geospatial-Data-In-Python/&quot;&gt;이전 포스트&lt;/a&gt;에서 썼던 서울의 지도 데이터를 이용하여 그림을 그린다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;# download seoul geojson data
seoul_url = &apos;https://github.com/southkorea/seoul-maps/raw/master/kostat/2013/json/seoul_municipalities_geo_simple.json&apos;
seoul_df = gpd.read_file(seoul_url)
seoul_df.plot(figsize=(7.22, 7.22),
                  color=&apos;none&apos;, edgecolor=&apos;#333&apos;, facecolor=&apos;none&apos;, alpha=0.3)
fig = plt.gcf()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-15-Multiple-Plots-in-Map/Seoul-station.svg&quot; alt=&quot;Seoul Map&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;draw-circle-in-station-location&quot;&gt;Draw Circle in Station Location&lt;/h2&gt;

&lt;h3 id=&quot;station-information&quot;&gt;Station Information&lt;/h3&gt;

&lt;p&gt;각각의 station 정보를 따로 저장한다. 각 station의 이름을 key로 하는 dictionary로 조금이나마 중복된 코드를 줄이고자 하였다. 각 파라미터는 try-and-error로 정해진 하드 코딩되는 값이다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;stations_map = {
    &apos;종로구&apos;: {
        &apos;lat&apos;: 127.0050,
        &apos;lon&apos;: 37.5720,
        &apos;box_left&apos;: 0.45,
        &apos;box_bottom&apos;: 0.72,
        &apos;annot_x&apos;: 0.001,
        &apos;annot_y&apos;: -0.012,
        &apos;eng_name&apos;: &apos;Jongno&apos;,
        &apos;loc&apos;: &apos;lower left&apos;,
        &apos;loc0&apos;: 3,
        &apos;loc1&apos;: 4,
        &apos;posx&apos;: 20,
        &apos;posy&apos;: 20,
        &apos;a&apos;: 1.0,
        &apos;b&apos;: 1.0},
    &apos;강서구&apos;: {
        &apos;lat&apos;: 126.8351,
        &apos;lon&apos;: 37.5447,
        &apos;box_left&apos;: 0.01,
        &apos;box_bottom&apos;: 0.48,
        &apos;annot_x&apos;: 0.008,
        &apos;annot_y&apos;: -0.008,
        &apos;eng_name&apos;: &apos;Gangseo&apos;,
        &apos;loc&apos;: &apos;lower left&apos;,
        &apos;loc0&apos;: 3,
        &apos;loc1&apos;: 4,
        &apos;posx&apos;: 0,
        &apos;posy&apos;: -20,
        &apos;a&apos;: 2.0,
        &apos;b&apos;: 2.0},
    &apos;서초구&apos;: {
        &apos;lat&apos;: 126.9945,
        &apos;lon&apos;: 37.5046,
        &apos;box_left&apos;: 0.42,
        &apos;box_bottom&apos;: 0.04,
        &apos;annot_x&apos;: 0.005,
        &apos;annot_y&apos;: 0.004,
        &apos;eng_name&apos;: &apos;Seocho&apos;,
        &apos;loc&apos;: &apos;lower left&apos;,
        &apos;loc0&apos;: 1,
        &apos;loc1&apos;: 2,
        &apos;posx&apos;: 0,
        &apos;posy&apos;: -20,
        &apos;a&apos;: 3.0,
        &apos;b&apos;: 3.0}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;plot-circle-for-stations&quot;&gt;Plot Circle for Stations&lt;/h3&gt;

&lt;p&gt;각각의 station의 위치를 표시하는 원을 그린다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;for station_name in stations_map.keys():
    lat = stations_map[station_name][&apos;lat&apos;]
    lon = stations_map[station_name][&apos;lon&apos;]

    aspect = ax.get_aspect()
    point_r = 0.008
    p = Ellipse((lat, lon), point_r, point_r / aspect,  zorder=6)
    ax.add_artist(p)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-15-Multiple-Plots-in-Map/Seoul-station-circle.svg&quot; alt=&quot;Station Circles in Seoul Map&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;draw-rectangles&quot;&gt;Draw Rectangles&lt;/h1&gt;

&lt;p&gt;각 plot의 너비와 높이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&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;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h&lt;/code&gt;의 크기는 Axes의 상대적인 크기를 사용하기로 하였다. 따라서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform=ax.transAxes&lt;/code&gt; 전달 인자를 사용한다.&lt;/p&gt;

&lt;p&gt;각 Rectangle은 3개의 plot이 가로로 있는 형태를 그릴 예정이므로 기본적으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3*w + h&lt;/code&gt;의 크기를 지닌다고 할 수 있다. 하지만, 좌표축 레이블(axis label)이나 tick의 존재 때문에 padding이 필요하다. 이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w_pad&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h_pad&lt;/code&gt;라 하면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt;의 30%, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h&lt;/code&gt;의 15%로 지정하였다. 직사각형의 크기는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3*w+3.8*w_pad&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h+5.2*h_pad&lt;/code&gt;로 지정하였다. 3.8와 5.2은 큰 의미가 있는 것은 아니고 plot를 실제로 그리고 조정하면서 try-and-error로 여백을 조정하다보니 그렇게 되었다.&lt;/p&gt;

&lt;p&gt;이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;을 이용하여 그린다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;station_map&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;box_left&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;box_bottom&lt;/code&gt;은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;의 xy 즉, 왼쪽 아래 좌표를 나타낸다. 이 또한 try-and-error로 위에서 그렸던 원을 가리지 않으면서 서로 겹치지도 않는 적절한 위치를 찾아서 조정하였다.&lt;/p&gt;

&lt;p&gt;다음 그림을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle object&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rects&lt;/code&gt;라는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dictionary&lt;/code&gt;에 따로 저장하였다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;# set size of rectangle according to Axes coordinate
w, h = 0.13, 0.13
w_pad, h_pad = w*0.3, h*0.15

rect_w = 3*w + 3.8*w_pad
rect_h = h + 5.2*h_pad
rects = {}

for station_name in stations_map.keys():
    rect = Rectangle((stations_map[station_name][&apos;box_left&apos;],
        stations_map[station_name][&apos;box_bottom&apos;]),
        rect_w, rect_h, transform=ax.transAxes,
        linewidth=0.5, edgecolor=&apos;k&apos;, facecolor=&apos;white&apos;, zorder=6)
    ax.add_artist(rect)
    rects[station_name] = rect
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-15-Multiple-Plots-in-Map/Seoul-station-rect.svg&quot; alt=&quot;Station Circles and Rectangles in Seoul Map&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;draw-line-between-circle-and-rectangle&quot;&gt;Draw Line between Circle and Rectangle&lt;/h1&gt;

&lt;p&gt;Station의 정보를 확대해서 보여준다는 의미로 선을 그릴 필요가 있다. &lt;a href=&quot;https://blog.liam.kim/2021/08/14/Plot-Geospatial-Data-In-Python/&quot;&gt;이전 포스트&lt;/a&gt;에서 설명한 것과 같이 선을 그리면 된다.&lt;/p&gt;

&lt;p&gt;여기서 중요한 것은 좌표축인데, circle의 위치를 알려주는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;px&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;py&lt;/code&gt;는 위도와 경도로 된, 즉 데이터에 기반한 좌표이다 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ax.transData&lt;/code&gt;). 반면에, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Axes&lt;/code&gt;의 상대적인 크기에 기반한 좌표이다 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ax.transAxes&lt;/code&gt;). 따라서 이 둘을 통일할 필요가 있다. 이는 &lt;a href=&quot;https://matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Transform.inverted&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inverted&lt;/code&gt;&lt;/a&gt; method와 &lt;a href=&quot;https://matplotlib.org/stable/tutorials/advanced/transforms_tutorial.html#the-transformation-pipeline&quot;&gt;transformation pipeline&lt;/a&gt;을 이용하면 해결할 수 있다. 이를 정리하면 다음 코드이다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;axis_to_data = ax.transAxes + ax.transData.inverted()
x0, y0 = axis_to_data.transform(rects[station_name].xy)
x1, y1 = axis_to_data.transform((rects[station_name].xy[0] + rect_w, rects[station_name].xy[1] + rect_h))
px, py = lat, lon
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;Rectangle&lt;/code&gt;을 그릴 때 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zorder=6&lt;/code&gt;을 지정했는데, 이는 선이 plot을 가리지 않게 하기 위해서 지정하였다.&lt;/p&gt;

&lt;p&gt;선을 그리는 전체 코드는 다음과 같다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;for station_name in stations_map.keys():
    lat = stations_map[station_name][&apos;lat&apos;]
    lon = stations_map[station_name][&apos;lon&apos;]

    # transformation pipeline
    axis_to_data = ax.transAxes + ax.transData.inverted()
    x0, y0 = axis_to_data.transform(rects[station_name].xy)
    x1, y1 = axis_to_data.transform((rects[station_name].xy[0] + rect_w, rects[station_name].xy[1] + rect_h))
    px, py = lat, lon
    if stations_map[station_name][&apos;loc0&apos;] == 1:
        # upper right
        verts_0 = [(px, py), (x1, y1), (px, py)]
    elif stations_map[station_name][&apos;loc0&apos;] == 2:
        # upper left
        verts_0 = [(px, py), (x0, y1), (px, py)]
    elif stations_map[station_name][&apos;loc0&apos;] == 3:
        # lower left
        verts_0 = [(px, py), (x0, y0), (px, py)]
    elif stations_map[station_name][&apos;loc0&apos;] == 4:
        # lower right
        verts_0 = [(px, py), (x1, y0), (px, py)]
    codes_0 = [mpath.Path.MOVETO, mpath.Path.LINETO, mpath.Path.CLOSEPOLY]

    if stations_map[station_name][&apos;loc1&apos;] == 1:
        # upper right
        verts_1 = [(px, py), (x1, y1), (px, py)]
    elif stations_map[station_name][&apos;loc1&apos;] == 2:
        # upper left
        verts_1 = [(px, py), (x0, y1), (px, py)]
    elif stations_map[station_name][&apos;loc1&apos;] == 3:
        # lower left
        verts_1 = [(px, py), (x0, y0), (px, py)]
    elif stations_map[station_name][&apos;loc1&apos;] == 4:
        # lower right
        verts_1 = [(px, py), (x1, y0), (px, py)]
    codes_1 = [mpath.Path.MOVETO, mpath.Path.LINETO, mpath.Path.CLOSEPOLY]

    path_0 = mpath.Path(verts_0, codes_0)
    path_1 = mpath.Path(verts_1, codes_1)

    patch_0 = ax.add_patch(mpatches.PathPatch(path_0, facecolor=&apos;k&apos;, lw=0.5))
    patch_1 = ax.add_patch(mpatches.PathPatch(path_1, facecolor=&apos;k&apos;, lw=0.5))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-15-Multiple-Plots-in-Map/Seoul-station-line.svg&quot; alt=&quot;Station Circles, Rectangles, and Lines in Seoul Map&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;plot-in-insets&quot;&gt;Plot in Insets&lt;/h2&gt;

&lt;p&gt;여러 개의 plot을 한 figure 안에 그려야하므로, 이번에도 inset을 사용한다. 여기가 이제 상당히 고통스러운 부분이다. 위에서 언급한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w_pad&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h_pad&lt;/code&gt;, 그리고 axis label와 ticklabel을 고려한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w_offset&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h_offset&lt;/code&gt;을 잘 조정해서 가장 적절한 여백값을 찾아야한다. tick의 숫자의 크기, 폰트의 크기 등에 따라 달라질 수 있으며 이는 plot를 같이 그려야 체크할 수 있기에 plot 또한 같이 그린다.&lt;/p&gt;

&lt;p&gt;이 때, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zorder&lt;/code&gt;는 default값인 5, 그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;에서 설정한 6보다 큰 7을 설정함으로써 다른 요소들에 의해 가려지지 않도록 한다.&lt;/p&gt;

&lt;p&gt;그리고 조건대로 각각의 plot 유형마다 한계값(ylim)을 통일시킨다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;insets = {}
w_offset = 1.5 * w_pad
h_offset = 3 * h_pad
nx = 101

def style_insets(insets):
    for inset in insets:
        inset.set_title(&quot;&quot;)
        # show grid
        inset.xaxis.grid(True, visible=True, which=&apos;major&apos;)
        inset.yaxis.grid(True, visible=True, which=&apos;major&apos;)

        # small tick label
        for tick in inset.xaxis.get_major_ticks():
            tick.label.set_fontsize(&apos;xx-small&apos;)
        for tick in inset.yaxis.get_major_ticks():
            tick.label.set_fontsize(&apos;xx-small&apos;)

        # ticks are close to axis
        inset.tick_params(axis=&apos;x&apos;, which=&apos;major&apos;, pad=1)
        inset.tick_params(axis=&apos;y&apos;, which=&apos;major&apos;, pad=1)
        # x axis label is close to x axis
        inset.set_xlabel(&apos;x&apos;, fontsize=&apos;x-small&apos;, labelpad=1.5)

    # y axis label is shown in first plot only
    # y axis label is close to y axis
    insets[0].set_ylabel(&apos;y&apos;, fontsize=&apos;small&apos;, labelpad=0.5)

# initialize min/max by max/min of &apos;float&apos;
tot_ylims = {
        0: [np.finfo(&apos;float&apos;).max, np.finfo(&apos;float&apos;).min],
        1: [np.finfo(&apos;float&apos;).max, np.finfo(&apos;float&apos;).min],
        2: [np.finfo(&apos;float&apos;).max, np.finfo(&apos;float&apos;).min]}

for station_name in stations_map.keys():
    axin0 = ax.inset_axes(bounds=[stations_map[station_name][&apos;box_left&apos;] + w_offset,
                                stations_map[station_name][&apos;box_bottom&apos;] + h_offset,
                                w, h], transform=ax.transAxes, zorder=7)
    axin1 = ax.inset_axes(bounds=[stations_map[station_name][&apos;box_left&apos;] + w + w_pad + w_offset,
                                stations_map[station_name][&apos;box_bottom&apos;] + h_offset,
                                w, h], transform=ax.transAxes, zorder=7)
    axin2 = ax.inset_axes(bounds=[stations_map[station_name][&apos;box_left&apos;] + 2*w + 2*w_pad + w_offset,
                                stations_map[station_name][&apos;box_bottom&apos;] + h_offset,
                                w, h], transform=ax.transAxes, zorder=7)
    # store inset for later use
    insets[station_name] = [axin0, axin1, axin2]

    # prepare data
    xs0 = np.linspace(-5.0, 5.0, num=nx, endpoint=True)
    xs1 = np.linspace(-3.0, 3.0, num=nx, endpoint=True)
    xs2 = np.linspace(-2.0*np.pi, 2.0*np.pi, num=nx, endpoint=True)
    ys0 = float(stations_map[station_name][&apos;a&apos;]) * xs0
    ys1 = float(stations_map[station_name][&apos;a&apos;]) * np.power(xs1, 2)
    ys2 = float(stations_map[station_name][&apos;a&apos;]) * \
        np.sin(float(stations_map[station_name][&apos;b&apos;])*xs2)

    # plot to inset
    axin0.plot(xs0, ys0, color=&apos;k&apos;)
    axin1.plot(xs1, ys1, color=&apos;g&apos;)
    axin2.plot(xs2, ys2, color=&apos;r&apos;)

    # store min/max of ylim
    tot_ylims[0][0] = min(tot_ylims[0][0], axin0.get_ylim()[0])
    tot_ylims[1][0] = min(tot_ylims[1][0], axin1.get_ylim()[0])
    tot_ylims[2][0] = min(tot_ylims[2][0], axin2.get_ylim()[0])

    tot_ylims[0][1] = max(tot_ylims[0][1], axin0.get_ylim()[1])
    tot_ylims[1][1] = max(tot_ylims[1][1], axin1.get_ylim()[1])
    tot_ylims[2][1] = max(tot_ylims[2][1], axin2.get_ylim()[1])

    # customize style of inset
    style_insets([axin0, axin1, axin2])

# set same y limit per plot type
for station_name in stations_map.keys():
    insets[station_name][0].set_ylim(tot_ylims[0][0], tot_ylims[0][1])
    insets[station_name][1].set_ylim(tot_ylims[1][0], tot_ylims[1][1])
    insets[station_name][2].set_ylim(tot_ylims[2][0], tot_ylims[2][1])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-15-Multiple-Plots-in-Map/Seoul-station-plot.svg&quot; alt=&quot;Multiple Plots per Station in Seoul Map&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;annotation&quot;&gt;Annotation&lt;/h2&gt;

&lt;p&gt;Plot이 많은 경우에는 이를 인용하기 위해 Annotation이 필요하다. 각 Circle (station)에 대한 annotation, 그리고 각 plot의 유형(A, B, C)에 대한 annotation을 다음과 같은 코드로 구현한다.&lt;/p&gt;

&lt;p&gt;Circle의 annotation 위치는 station 별로 따로 설정하고(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xycoords=&apos;data&apos;&lt;/code&gt;), 각 plot의 유형(A, B, C)에 대한 annotation은 각 inset에서의 상대적인 위치(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xycoords=&apos;axes fraction&apos;&lt;/code&gt;)에 대해 고정된 위치를 세팅한다 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(-0.18, 1.08)&lt;/code&gt;). 이 또한 plot하면서 조정해야하는 수치이다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;# slice alphabets by length of types
multipanel_labels = np.array(list(string.ascii_uppercase)[:3])

for (i, station_name) in enumerate(stations_map.keys()):
    lat = stations_map[station_name][&apos;lat&apos;]
    lon = stations_map[station_name][&apos;lon&apos;]
    # annotate station name on Axes
    ax.annotate(stations_map[station_name][&apos;eng_name&apos;], (lat + stations_map[station_name][&apos;annot_x&apos;],
                                lon + stations_map[station_name][&apos;annot_y&apos;]),
                        xycoords=&apos;data&apos;,
                        fontsize=&apos;medium&apos;)
    # annotate type of plot on inset
    for ii in range(3):
        insets[station_name][ii].annotate(
            multipanel_labels[ii], (-0.18, 1.08),
            xycoords=&apos;axes fraction&apos;,
            fontsize=&apos;medium&apos;, fontweight=&apos;bold&apos;)
fig.tight_layout(pad=0.15)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-15-Multiple-Plots-in-Map/Seoul-station-annot.svg&quot; alt=&quot;Multiple Plots per Station in Seoul Map&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;이걸 그리던 2021년 2월 당시에 상당히 고민해서 그린 거였는데, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt;을 계층적인 구조로 그리려고 시도했던게 복잡성을 키운 셈이 되어버려서 잘 그려지지 않았다. 따로따로 생각하고 약간의 try-and-error를 첨가하니 오히려 더 쉽게 그려져서 허망했던 기억이 난다. 다만, 6개월 사이에 &lt;a href=&quot;https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplot.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subplot&lt;/code&gt;&lt;/a&gt;이라는게 생겼기 때문에 좀 더 쉽게 그릴 방법이 있지 않을까 생각한다.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Plot Geospatial Points in Python</title>
   <link href="https://blog.liam.kim/posts/2021/08/Plot-Geospatial-Data-In-Python/"/>
   <updated>2021-08-14T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2021/08/Plot-Geospatial-Data-In-Python</id>
   <content type="html">&lt;p&gt;학위 논문 심사 발표 준비를 하면서 introduction용으로 지도에 point를 찍어서 표현할 필요가 있었다. 그것도 한중일 지도와 서울 지도를 같이 보여줘야 했기에 두 지도를 동시에 보여줄 필요가 있었다. 즉 다음 조건을 만족하는 그림을 그리고자 했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;두 지도를 한번에 보여줄 수 있어야 한다.&lt;/li&gt;
  &lt;li&gt;point는 위도(latitude)와 경도(longitude)로 주어진다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Python으로 그린 그 과정을 정리한 글이다. 아래의 모든 코드는 &lt;a href=&quot;https://colab.research.google.com/drive/1piFRokmhxutjR1H4ZzkldFCrveYEo-xg?usp=sharing&quot;&gt;Google Colab&lt;/a&gt;에서 확인할 수 있다.
변수 네이밍이 좀 많이 구린데 (0, 1 인덱스의 오용 등등) 당시 급하게 짰던거라 양해바란다.&lt;/p&gt;

&lt;h2 id=&quot;data&quot;&gt;Data&lt;/h2&gt;

&lt;p&gt;당연히 데이터가 필요하다. 그리고 패키지가 필요하다. 여러개 찾아봤는데 러닝커브 짧고 (빨리 만들어야해서 금방 가져다 쓸수 있는게 필요했다), 문서화 잘 되어있던 걸 찾다가 &lt;a href=&quot;https://geopandas.org/index.html&quot;&gt;GeoPandas&lt;/a&gt;를 고르게 되었다. 포맷 적당하고, matplotlib랑 호환도 잘 돼서 내가 쓰기 편했다. 또 NUMFOCUS에서 지원받으니 어느정도 maintain되는 패키지이지 않을까 생각했다.&lt;/p&gt;

&lt;p&gt;이제 포맷을 정해야하는데 공간 정보 데이터를 다루는 포맷이 여러개가 있다. GeoPandas에서는 Shapefile, GeoJSON, GeoPackage를 지원하는 것 같다. 나는 JSON이 편하니깐 GeoJSON을 골랐다. 예전에 Shapefile써봤는데 너무 어려웠다. 간단하게 윤곽만 보이면 돼서 그냥 편한거 쓰자 하는 생각에 GeoJSON을 골랐다. TopoJSON이 더 컴팩트하고 좋은거 같은데, 데이터 구하기가 힘들었다. 어차피 파일 다운받아서 그릴건데 용량이 뭔 상관인가 싶어서 그냥 GeoJSON을 쓰기로 했다.&lt;/p&gt;

&lt;p&gt;한중일(CJK) 데이터는 &lt;a href=&quot;https://datahub.io/core/geo-countries&quot;&gt;DataHub&lt;/a&gt;라는 곳에서, 서울시 데이터는 &lt;a href=&quot;https://github.com/southkorea/seoul-maps/&quot;&gt;seoul-maps&lt;/a&gt;에서 구했다. 한중일만 따로 있는게 아니고, 전세계의 지도 데이터이기 때문에 실질적으로 사용할 때는 위도와 경도의 범위 제한을 통해서 한중일만 plot하면 된다.&lt;/p&gt;

&lt;h2 id=&quot;load-data&quot;&gt;Load Data&lt;/h2&gt;

&lt;p&gt;심플하다. 그냥 &lt;a href=&quot;https://geopandas.org/docs/reference/api/geopandas.read_file.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt;&lt;/a&gt;에 url이든 파일 이름이든 넣으면 알아서 파싱해서 가져온다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;import geopandas as gpd

# download countries geojson data
cjk_url = &apos;https://datahub.io/core/geo-countries/r/countries.geojson&apos;
cjk_df = gpd.read_file(cjk_url)

# download seoul geojson data
seoul_url = &apos;https://github.com/southkorea/seoul-maps/raw/master/kostat/2013/json/seoul_municipalities_geo_simple.json&apos;
seoul_df = gpd.read_file(seoul_url)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;plot-cjk-map&quot;&gt;Plot CJK Map&lt;/h2&gt;
&lt;p&gt;GeoPandas 자체적으로 &lt;a href=&quot;https://geopandas.org/docs/reference/api/geopandas.GeoDataFrame.plot.html&quot;&gt;plot&lt;/a&gt;함수를 지원하기 때문에 plot해주면 된다.
API 문서를 보면 나오듯이, matplotlib axes instance로 return이 되기 때문에 한번 그리고 나면 나머지는 matplotlib만 생각하면 된다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;# plot CJK
ax = cjk_df.plot(figsize=(7.22, 6.22), alpha=0.8, color=&apos;#fff&apos;,
                    edgecolor=&apos;#777&apos;)
ax.set_facecolor(&apos;#add8e6&apos;)
fig = plt.gcf()
ax.set_xlim((116, 132))
ax.set_ylim((32, 45))
ax.set_aspect(1.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;바다를 그리기 위해서 facecolor를 푸른색 계열로 지정해주었다. 이부분이 좀 어려운데, 덧칠의 개념이라고 생각하면 편하다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;흰색(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color=&apos;#fff&apos;&lt;/code&gt;)에 alpha값을 0.8(80%의 투명도)로 설정한다.&lt;/li&gt;
  &lt;li&gt;위를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#add8e6&lt;/code&gt;으로 덧칠한다. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ax.set_facecolor(&apos;#add8e6&apos;)&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;저렇게 육지와 바다가 구분된 색으로 이쁘게 나온다. 색은 아마 구글 맵 색을 따왔던 걸로 기억한다. 여기서 중요한건 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plot&lt;/code&gt; 함수 옵션에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;facecolor&lt;/code&gt;를 넣으면 안되고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ax.set_facecolor&lt;/code&gt;로 따로 코드를 작성해야 잘 나온다. 이유는 사실 잘 모르겠다.&lt;/p&gt;

&lt;p&gt;그리고 x축과 y축 범위를 경도와 위도를 참고해서 적절히 설정하면 우리나라가 가운데 있으면서 중국과 일본이 일부분 보이는 그런 그림이 나온다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-14-Geospatial/CJK.png&quot; alt=&quot;China-Korea-Japan Image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;plot-seoul-map&quot;&gt;Plot Seoul Map&lt;/h2&gt;

&lt;p&gt;요동 반도 근처에 박스를 그려서 그 안에 서울 지도를 넣으려고 한다. 다음과 같은 프로세스를 밟는다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;서울의 실제 &lt;strong&gt;위치&lt;/strong&gt;를 그리기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;의 &lt;a href=&quot;https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Rectangle.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;&lt;/a&gt;로 작은 박스를 생성&lt;/li&gt;
  &lt;li&gt;서울의 &lt;strong&gt;확대된 맵&lt;/strong&gt;을 그리기 위해 CJK 맵을 그렸던 Axes에 &lt;a href=&quot;https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.inset_axes.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset axes&lt;/code&gt;&lt;/a&gt;로 큰 박스를 그리고 서울 지도를 임베딩&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;그러면 다음과 같은 코드를 사용하면 된다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;seoul_lat = [126.83, 127.09]
seoul_lon = [37.5, 37,57]

# create small box(Rectangle) for Seoul
seoul_sbox = (seoul_lat[0], seoul_lon[0])
seoul_lbox = (117, 38)
lbox_size = 6
sbox_size = 0.3

rect = Rectangle((seoul_lat[0], seoul_lon[0]),
                    sbox_size, sbox_size,
                    linewidth=0.5, edgecolor=&apos;k&apos;, facecolor=&apos;white&apos;, zorder=6)
ax.add_artist(rect)

# create large box(inset) for zoomed Seoul
axin_seoul = ax.inset_axes(bounds=[seoul_lbox[0], seoul_lbox[1],
                                   lbox_size, lbox_size],
                           transform=ax.transData, alpha=0.4, zorder=6)

# plot Seoul
seoul_df.plot(ax=axin_seoul, color=&apos;none&apos;,
                edgecolor=&apos;#333&apos;, facecolor=&apos;none&apos;, alpha=0.3, zorder=6)
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;small-box&quot;&gt;Small Box&lt;/h3&gt;

&lt;p&gt;서울의 실제 위치를 그리는 작은 박스를 그릴 것이다.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;의 첫번째 전달인자로는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anchor point&lt;/code&gt;를 지정한다.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anchor point&lt;/code&gt;는 “일반적으로” 박스의 왼쪽 아래의 좌표를 뜻한다.
서울의 적절한 위도와 경도 범위를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seoul_lat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seoul_lon&lt;/code&gt;이라 정의하고,
경도와 위도의 크기로 0.3도 정도의 박스를 그린다고 가정하고 이를
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbox_size&lt;/code&gt;라는 변수로 지정하였다. 즉 경도상으로는 126.83°부터 127.13°까지, 위도상으로는 37.5°부터 37.8°까지를 그린다.&lt;/p&gt;

&lt;h3 id=&quot;large-box&quot;&gt;Large Box&lt;/h3&gt;

&lt;p&gt;이제 실제 서울 지도를 지도에 표시할 차례이다. 요동반도 근처 적당한 크기 (6도)의 박스를 그릴 예정이고,
위치를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seoul_lbox&lt;/code&gt; 변수, 그리고 크기를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lbox_size&lt;/code&gt;라는 변수에 대입하였다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;과는 다르게 기존 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Axes&lt;/code&gt;에 또다른 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plot&lt;/code&gt;이 추가되는 개념이기 때문에 matplotlib의 &lt;a href=&quot;https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.inset_axes.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset axes&lt;/code&gt;&lt;/a&gt;을 사용한다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;과는 다르게 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anchor point&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;box size&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bounds=[117, 38, 6, 6]&lt;/code&gt;에 한번에 넣는데, 경도상으로는 117°부터 121°까지, 위도상으로는 38°부터 44°까지를 그린다.을 지정한다. 이때 위도와 경도를 넣기 위해 좌표계산을 좌표의 상대적인 비율이 아니라 데이터의 절대적인 값으로 인식할 수 있게 해야한다. 그러기에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform=ax.transData&lt;/code&gt; 또한 전달인자로 넣는다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform&lt;/code&gt;에 대한 것은 &lt;a href=&quot;https://matplotlib.org/stable/tutorials/advanced/transforms_tutorial.html&quot;&gt;공식 문서&lt;/a&gt;에 설명이 잘 되어 있다.&lt;/p&gt;

&lt;p&gt;이렇게 생성한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt;을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;axin_seoul&lt;/code&gt;라는 변수로 지정하고, 이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geopandas&lt;/code&gt;의 plot함수에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ax&lt;/code&gt; 전달인자로 넣는다. 그 결과는 다음과 같다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt;의 위치를 지정하는 방법은 &lt;a href=&quot;https://matplotlib.org/stable/gallery/axes_grid1/inset_locator_demo.html&quot;&gt;데모 페이지&lt;/a&gt;가 설명이 잘 되어있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-14-Geospatial/CJK-Seoul.png&quot; alt=&quot;China-Korea-Japan and Seoul Image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;draw-lines-between-small-box-and-large-box&quot;&gt;Draw Lines between Small Box and Large Box&lt;/h3&gt;

&lt;p&gt;위의 그림으로 끝내면 작은 박스(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rectangle&lt;/code&gt;)와 큰 박스(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt;)의 관계를 알기가 어렵다. 따라서 둘을 직선으로 이어서 작은 박스를 확대한 것이 큰 박스임을 나타내고자 한다.
이는 다음과 같은 코드로 그릴 수 있다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;# connect rect to inset
x0, y0 = (seoul_lbox[0], seoul_lbox[1] + 0.5)
x1, y1 = (seoul_lbox[0] + lbox_size, seoul_lbox[1] + lbox_size - 0.5)
px0, py0 = (seoul_sbox[0], seoul_sbox[1])
px1, py1 = (seoul_sbox[0] + sbox_size, seoul_sbox[1] + sbox_size)
verts_0 = [(px0, py0), (x0, y0), (px0, py0)]
verts_1 = [(px1, py1), (x1, y1), (px1, py1)]
codes_0 = [mpath.Path.MOVETO, mpath.Path.LINETO, mpath.Path.CLOSEPOLY]
codes_1 = [mpath.Path.MOVETO, mpath.Path.LINETO, mpath.Path.CLOSEPOLY]

path_0 = mpath.Path(verts_0, codes_0)
path_1 = mpath.Path(verts_1, codes_1)

patch_0 = ax.add_patch(mpatches.PathPatch(path_0, facecolor=&apos;k&apos;, lw=0.5))
patch_1 = ax.add_patch(mpatches.PathPatch(path_1, facecolor=&apos;k&apos;, lw=0.5))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;우선 선은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[matplotlib.path.Path](https://matplotlib.org/stable/api/path_api.html)&lt;/code&gt;를 사용하여 그린다. 그리고 &lt;a href=&quot;https://matplotlib.org/stable/tutorials/advanced/path_tutorial.html&quot;&gt;튜토리얼&lt;/a&gt;이 매우 많은 도움이 되었다.&lt;/p&gt;

&lt;p&gt;선은 두 점을 잇는다고 할 수 있지만, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;에서의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;는 두 점을 &lt;strong&gt;왕복&lt;/strong&gt; 한다고 생각하였으며, 마지막으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLOSEPOLY&lt;/code&gt;를 code로 설정한다고 생각하였다.
시작점으로 이동하기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MOVETO&lt;/code&gt;, 목표하는 점으로 선을 긋기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LINETO&lt;/code&gt;, 다시 돌아와서 마무리 짓기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLOSEPOLY&lt;/code&gt;, 이렇게 세 가지의 코드를 지정한다. 이것이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib.path.Path&lt;/code&gt;의 두번째 전달인자에 들어가는 코드이다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib.path.Path&lt;/code&gt;의 첫번째 인자인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertex&lt;/code&gt;는 각 포인트를 뜻한다. 이는 큰 박스에서 작은 박스로 설정했는데, 이건 순서는 상관없는 것 같다. 코드가 복잡해보이는데, 큰 박스의 왼쪽 아래(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x0, y0&lt;/code&gt;)에서 작은 박스의 왼쪽 아래(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;px0, py0&lt;/code&gt;), 그리고 큰 박스의 오른쪽 위(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x1, y1&lt;/code&gt;)에서 작은 박스의 오른쪽 위(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;px1, py1&lt;/code&gt;)로 설정한 것 뿐이다.&lt;/p&gt;

&lt;p&gt;이렇게 생성한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mpatches.PathPatch&lt;/code&gt;를 통해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Patch&lt;/code&gt;로 변환하고, 이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ax.add_patch&lt;/code&gt;를 불러와서 원래의 맵에 추가하면 된다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-14-Geospatial/CJK-Seoul-line.png&quot; alt=&quot;China-Korea-Japan and Seoul Image with line&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;draw-points-in-seoul-map&quot;&gt;Draw Points in Seoul Map&lt;/h1&gt;

&lt;p&gt;Dictionary로 된 points를 서울 지도에 표시해 줄 필요가 있다.&lt;/p&gt;

&lt;p&gt;Aspect ratio를 고려하여 원으로 그리기 위해 &lt;a href=&quot;https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.patches.Ellipse.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib.patches.Ellipse&lt;/code&gt;&lt;/a&gt;를 사용하였다.
실제 점은 원처럼 보여야하지만 지도 자체의 aspect ratio가 1이 아니었기 때문에 타원으로 그리고 비율을 조정한 것이다. 전달인자는 앞에서의 도형들과 같이 위치와 크기, 그리고 색 등의 전달인자이다. 종로구, 강서구와 서초구에는 두 배 크고, 색도 다른 색으로 지정하였다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;stations_latlon = {
        &quot;중구&quot; : [126.9747, 37.5643],
        &quot;종로구&quot; : [127.0050, 37.5720],
        &quot;용산구&quot; : [127.0048, 37.5400],
        &quot;광진구&quot; : [127.0925, 37.5472],
        &quot;성동구&quot; : [127.0419, 37.5432],
        &quot;중랑구&quot; : [127.0940, 37.5849],
        &quot;동대문구&quot; : [127.0289, 37.5758],
        &quot;성북구&quot; : [127.0273, 37.6067],
        &quot;도봉구&quot; : [127.0290, 37.6542],
        &quot;은평구&quot; : [126.9348, 37.6098],
        &quot;서대문구&quot; : [126.9378, 37.5767],
        &quot;마포구&quot; : [126.9456, 37.5498],
        &quot;강서구&quot; : [126.8351, 37.5447],
        &quot;구로구&quot; : [126.8897, 37.4985],
        &quot;영등포구&quot; : [126.8974, 37.5250],
        &quot;동작구&quot; : [126.9715, 37.4809],
        &quot;관악구&quot; : [126.9271, 37.4874],
        &quot;강남구&quot; : [127.0481, 37.5176],
        &quot;서초구&quot; : [126.9945, 37.5046],
        &quot;송파구&quot; : [127.1165, 37.5218],
        &quot;강동구&quot; : [127.1368, 37.5450],
        &quot;금천구&quot; : [126.9083, 37.4524],
        &quot;강북구&quot; : [127.0288, 37.6379],
        &quot;양천구&quot; : [126.8587, 37.5234],
        &quot;노원구&quot; : [127.0679, 37.6574]}

point_r = 0.012
aspect = axin_seoul.get_aspect()

for station, loc in stations_latlon.items():
    lat, lon = loc[0], loc[1]
    p = Ellipse((lat, lon), point_r, point_r / aspect, fc=&apos;#1A4E66&apos;, zorder=7)
    if station == &apos;강서구&apos; or station == &apos;서초구&apos;:
        p = Ellipse((lat, lon), 2.0 * point_r, 2.0 * (point_r / aspect),
                    fc=&apos;#E26C22&apos;, zorder=7)
    if station == &apos;종로구&apos;:
        p = Ellipse((lat, lon), 2.0 * point_r, 2.0 * (point_r / aspect),
                    fc=&apos;#00A1F1&apos;, zorder=7)
    axin_seoul.add_artist(p)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-14-Geospatial/CJK-Seoul-points.png&quot; alt=&quot;China-Korea-Japan and Seoul Image with points&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;hide-axis&quot;&gt;Hide Axis&lt;/h2&gt;

&lt;p&gt;위도, 경도가 꼭 표시되어야 할 필요가 없는 정보였기 때문에 axis자체를 숨기기로 하였다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
axin_seoul.xaxis.set_visible(False)
axin_seoul.yaxis.set_visible(False)
plt.tight_layout()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;짜잔! 다음과 같이 깔끔하게 그려진 서울이 확대된 한중일 지도가 그려졌다!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/2021-08-14-Geospatial/CJK-Seoul-final.png&quot; alt=&quot;China-Korea-Japan and Seoul Final Image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;위 방법의 핵심은 다음과 같다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Geospatial한 데이터를 어떻게 그릴것인가? -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeoJSON&lt;/code&gt;을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeoPandas&lt;/code&gt;를 통해서 사용&lt;/li&gt;
  &lt;li&gt;어떻게 두 지도를 한 Figure에 그릴 수 있는가? -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inset&lt;/code&gt; 사용&lt;/li&gt;
  &lt;li&gt;어떻게 선과 포인트를 그릴 수 있는가? -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ellipse&lt;/code&gt; 사용&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;처음에 어렵긴 해도 example 몇 개만 보다보면 그릴만 했다. 이 글을 보시는 분들에게 많은 도움이 되었으면 좋겠다.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Topology Overlap Matrix</title>
   <link href="https://blog.liam.kim/posts/2021/08/Topological-Overlap-Matrix/"/>
   <updated>2021-08-13T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2021/08/Topological-Overlap-Matrix</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;WGCNA(WeiGhted Correlation Network Analysis) 논문을 보다가 Topology Overlap Matrix의 이해를 돕고자 간단하게 메모하면서 정리하는 글이다. 다음 논문들을 참고하였고, 실제 내용은 &lt;a class=&quot;citation&quot; href=&quot;#zhang_general_2005&quot;&gt;[1]&lt;/a&gt;의 2.4절을 정리한 것이다.
&lt;a class=&quot;citation&quot; href=&quot;#langfelder_wgcna_2008&quot;&gt;[2], [1]&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;measure-of-node-dissimilarity&quot;&gt;Measure of Node Dissimilarity&lt;/h2&gt;

&lt;p&gt;논문에 나온대로 Co-expression network analysis의 목적은 node이 tightly connected이 되었는지 감지하여 clustering하는 것이라고 할 수 있다. &lt;a class=&quot;citation&quot; href=&quot;#zhang_general_2005&quot;&gt;[1]&lt;/a&gt;.
이를 위해 clustering method와 함께 node dissimilarity measure를 사용한다.&lt;/p&gt;

&lt;p&gt;이중에서 Ravasz algorithm을 사용한다
&lt;a class=&quot;citation&quot; href=&quot;#ravasz_hierarchical_2002&quot;&gt;[3]&lt;/a&gt;.
Ravasz algorithm은 similarity measure을 기준으로 쓰여져있지만,
WGCNA에서는 dissimlarity measure를 사용한다. 이는 simliarity measure를 먼저 정의한 다음 이를 반전시키는 방법을 쓰면 된다.&lt;/p&gt;

&lt;p&gt;The topological overlap matrix (TOM), \(\Omega = [\omega_{ij}]\) 는 다음과 같이 정의한다.&lt;/p&gt;

&lt;h2 id=&quot;the-topological-overlap-matrix-tom-in-ravasz-algorithm&quot;&gt;The Topological Overlap Matrix (TOM) in Ravasz Algorithm&lt;/h2&gt;

&lt;p&gt;Node simliarity는 어떻게 정의될 수 있을까?
위 식이 어떻게 정의가 되게 되었는지 이해가 안돼서 이 글을 쓰게 되었고, Ravasz algorithm을 찾아보았다 &lt;a class=&quot;citation&quot; href=&quot;#ravasz_hierarchical_2002&quot;&gt;[3]&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;노드의 connectivity가 높다면, 다시말하면 clustering이 이루어진다면 공유하는 이웃 노드(neighbor)들도 많을 것이다.&lt;/li&gt;
  &lt;li&gt;하지만 단순히 neighbor의 개수는 simlarity의 척도가 되지 못한다. normalization을 안했기 때문에 비교하기가 힘들기 때문이다.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;따라서 노드의 각 페어를 \(i, j\)라 하면,
TOM은 neighbor의 개수를 connectivity로 나누어주어야 한다. 이게 Ravasz algorithm에서 정의하는 TOM이다. Ravasz 논문에서의 notation을 그대로 가져다 쓰면 다음과 같이 표현할 수 있다.&lt;/p&gt;

\[\Omega_{ij} = \dfrac{J_{ij}}{\min{\{k_i, k_j\}}}\]

    &lt;p&gt;\(J_{ij}\)는 노드 \(i\)와 \(j\)가 공유하는 neighbor의 개수,
  \(k_i\) 는 \(i\) 노드에서 다른 노드로의 direct connection의 개수라고 할 수 있다 (node connectivity).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-topological-overlap-matrix-tom-in-wgcna&quot;&gt;The Topological Overlap Matrix (TOM) in WGCNA&lt;/h2&gt;

&lt;p&gt;WGCNA에서는 위에서 정의한 TOM을 확장하여 다음과 같이 정의한다.&lt;/p&gt;

\[\omega_{ij} = \dfrac{l_{ij} + a_{ij}}{\min{\{k_i,k_j\}}+1-a_{ij}}\]

&lt;p&gt;\(l_{ij}=\sum_u a_{iu} a_{uj}\)이며 \(k_i = \sum_{u} a_{iu}\)는 node connectivity를 나타낸다. \(l_{ij}\)는 Ravasz algorithm에서의 neighbor의 수, 즉 \(J_{ij}\)에 해당함을 알 수 있다. \(a_{ij}\)는 adjacency matrix의 weight이다. shared되는 neighbor수에 weight를 주고싶다면 \(0&amp;lt;a_{ij}&amp;lt;1\)의 값을 주면 되는 것이고, 그렇지 않다면 0 혹은 1을 주면 된다.&lt;/p&gt;

&lt;h3 id=&quot;extreme-of-omega_ij&quot;&gt;Extreme of \(\omega_{ij}\)&lt;/h3&gt;

&lt;p&gt;unweighted network라고 할 때 \(\omega_{ij}\)의 극단적인 케이스는 논문에 나온 것처럼 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(\omega_{ij}=1\)
    &lt;ol&gt;
      &lt;li&gt;노드 \(i, j\) 중에서 더 적은 노드를 \(i\)라고 할 때 (\(\min{\{k_i,k_j\}}\) 때문), 노드 \(i\)의 모든 이웃 노드는 노드 \(j\)의 이웃이다.&lt;/li&gt;
      &lt;li&gt;그리고, \(i\)와 \(j\)는 연결되어있다.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;\(\omega_{ij}=0\)
    &lt;ul&gt;
      &lt;li&gt;노드 \(i, j\)는 서로 연결되어 있지 않다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;range-of-omega_ij&quot;&gt;Range of \(\omega_{ij}\)&lt;/h3&gt;

&lt;p&gt;\(0 \leq \omega_{ij} \leq 1\)인가? 그렇다.&lt;/p&gt;

&lt;p&gt;Proof.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;\(l_{ij} \leq \min{\{\sum_{u \neq j} a_{iu}, \sum_{u \neq i} a_{uj}\}}\) 이므로, \(l_{ij} \leq \min{\{k_i, k_j\}} - a_{ij}\) 이다. \(l_{ij}\)는 neighbor의 수이므로, 당연히 connectivity보다는 작을 수 밖에 없다.&lt;/li&gt;
  &lt;li&gt;따라서 \(0 \leq a_{ij} \leq 1\)이므로 \(0 \leq \omega_{ij} \leq 1\)이다.  1.에서 \(\)\(l_{ij} \leq \min{\{k_i, k_j\}} - a_{ij}\)의 양변을 \(\min{\{k_i, k_j\}}\)로 나누면 자명하다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;dissimilarity-measure&quot;&gt;Dissimilarity measure&lt;/h2&gt;

&lt;p&gt;결론적으로 심플하게 Similarity measure를 opposite하게 만들면 된다.&lt;/p&gt;

\[d_{ij}^\omega = 1 - \omega_{ij}\]

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;zhang_general_2005&quot;&gt;[1]B. Zhang and S. Horvath, “A general framework for weighted gene co-expression network analysis,” &lt;i&gt;Statistical Applications in Genetics and Molecular Biology&lt;/i&gt;, vol. 4, p. Article17, 2005, doi: 10.2202/1544-6115.1128.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;langfelder_wgcna_2008&quot;&gt;[2]P. Langfelder and S. Horvath, “WGCNA: an R package for weighted correlation network analysis,” &lt;i&gt;BMC Bioinformatics&lt;/i&gt;, vol. 9, no. 1, p. 559, Dec. 2008, doi: 10.1186/1471-2105-9-559.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;ravasz_hierarchical_2002&quot;&gt;[3]E. Ravasz, A. L. Somera, D. A. Mongru, Z. N. Oltvai, and A. L. Barabási, “Hierarchical organization of modularity in metabolic networks,” &lt;i&gt;Science (New York, N.Y.)&lt;/i&gt;, vol. 297, no. 5586, pp. 1551–1555, Aug. 2002, doi: 10.1126/science.1073374.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>ANSYS Fluent Batch mode로 실행하기</title>
   <link href="https://blog.liam.kim/posts/2020/10/ANSYS-Journal-File-Batch-Execution/"/>
   <updated>2020-10-14T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2020/10/ANSYS-Journal-File-Batch-Execution</id>
   <content type="html">&lt;p&gt;내가 담당하는 일은 아니지만, 연구실에서 2년전부터 ANSYS Fluent를 사용하고 있다. 그동안의 사용방식은 GUI로 바로 실행하는 형태였는데, 효율적 HPC 자원 관리를 위해 잡스케줄러를 사용해서 batch mode로 전환하고자 한다.&lt;/p&gt;

&lt;p&gt;ANSYS는 프로그램의 사이즈 치고는 공개된 문서가 찾기 힘들고 리셀러 홈페이지에서 문서보기도 좀 복잡하기도 해서 batch mode로 어떻게 실행하는지 알기가 힘들었다. 게다가 난 내 일도 아니라서 ANSYS를 잘 쓸 줄 몰라서 더더욱 알기 힘들었다. 우리 연구실은 SGE를 주로 쓰는데 ANSYS에서 GUI형태로 SGE Job submission을 지원하기는 하지만 queue나 parallel environment설정을 어떻게 하는지 몰라서 포기했기에 어쩔 수 없이 아래와 같은 텍스트 방식을 고집할 수 밖에 없었다.&lt;/p&gt;

&lt;p&gt;참고로 이번에도 역시 나의 구세주 &lt;a href=&quot;https://www.ksc.re.kr/gsjw/jcs/sft#docout-7&quot;&gt;KISTI 매뉴얼&lt;/a&gt;과 &lt;a href=&quot;https://docs.rescale.com/articles/ansys-start-here/&quot;&gt;Rescale 문서&lt;/a&gt;가 큰 도움이 되었다.&lt;/p&gt;

&lt;p&gt;ANSYS를 batch 모드로 실행하는 과정은 간략하게 다음과 같다.&lt;/p&gt;

&lt;h1 id=&quot;ansys-batch-mode-방식-간략하게&quot;&gt;ANSYS batch mode 방식 (간략하게)&lt;/h1&gt;
&lt;ol&gt;
  &lt;li&gt;ANSYS GUI Command를 텍스트로 실행할 수 있는 Journal file 생성&lt;/li&gt;
  &lt;li&gt;fluent 실행시 -i 옵션을 통해 journal file을 input으로 넣어줌&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;하지만 journal file의 문법이 참 찾기가 어려운데, 위에서도 설명했듯이 ANSYS는 GUI프로그램이지만 이를 Text로 실행할 수 있게 해주는 TUI(Text User Interface)가 존재하는데 이를 기록한 것이 journal file이다. TUI는 자체 커맨드도 있지만, 기본적으로는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Scheme_(programming_language)&quot;&gt;Scheme&lt;/a&gt;의 또 다른 dialect라고 할 수 있겠다. &lt;strong&gt;단순하게 말하면 ANSYS Console에서 실행하는 명령어&lt;/strong&gt;이다. Scheme은 학부 때 과제로 경험해보고, 마법사책(SICP) 볼 때 말고는 접할 일이 없던 언어라 무척 당황했는데, 다행히 단순하게 돌릴때 Scheme문법을 사용할 일은 없었다.&lt;/p&gt;

&lt;p&gt;애니웨이, ANSYS Fluent를 돌릴 때 크게 두 가지 케이스가 있는데 하나는 stationary simulation일 때고, 하나는 traisient simulation이다. 명령어가 살짝 다르기 때문에 각각의 journal file을 작성하고 이를 jobscript에서 fluent 실행할 때 넣어주는 방식을 취하기로 했다.&lt;/p&gt;

&lt;h2 id=&quot;ansys-batch-mode-방식-자세하게-ansys-2020-r1-기준&quot;&gt;ANSYS batch mode 방식 (자세하게), ANSYS 2020 R1 기준&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;ANSYS Fluent를 GUI로 실행한다.&lt;/li&gt;
  &lt;li&gt;시뮬레이션에 필요한 Mesh 설정 및 각종 파라미터를 설정한다. TUI로 이 과정을 할 수도 있지만, 명령어를 다 알기도 힘들고, 최대한 journal file을 간단하게 만들고자 했다.&lt;/li&gt;
  &lt;li&gt;이를 case file로 저장한다. (압축해서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.cas.gz&lt;/code&gt;로 내보내기를 추천) 그렇게 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.cas.gz&lt;/code&gt; 파일이 생성된다.&lt;/li&gt;
  &lt;li&gt;mesh파일을 보통 case파일과 같은 폴더에 넣고 작업한다고 가정하고 2~3을 진행했다.&lt;/li&gt;
  &lt;li&gt;서버를 쓰면, mesh 파일과 case file를 특정 디렉토리에 넣는다.&lt;/li&gt;
  &lt;li&gt;다음과 같이 journal file을 작성한다.
    &lt;ol&gt;
      &lt;li&gt;Stationary
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; (set! *cx-exit-on-error* #t)

 ;; batch options
 /file/set-batch-options yes yes yes yes
 ;; read case file
 /file/read-case wst.cas.gz
 ;; disable HDF5
 /file/cff-files no
 ;; initialize the solution
 /solve/initialize/initialize/
 ;; save residuals plot as &quot;residual-xxxx.jpg&quot; at every 10 iteration, xxxx is a iteration number
 /solve/execute-commands/add-edit save_residuals 10 &quot;iteration&quot; &quot;/solve/monitors/residual/plot? yes /display/set-window 1 /display/save-picture residual-%i.jpg&quot;
 ;; iterate 100 step
 /solve/iterate 100
 ;; write data file as &quot;wst.data&quot;
 /file/write-case-data wst.data
 ;; exit FLUENT
 /exit yes
 ;; blank line at end

&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;Transient
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; (set! *cx-exit-on-error* #t)

 /file/set-batch-options yes yes yes yes
 ;; read case file
 /file/read-case wst.cas.gz
 ;; disable HDF5 output
 /file/cff-files no
 ;; initialize the solution
 /solve/initialize/initialize/
 ;; save residuals plot as &quot;residual.jpg&quot; at every 10 time-step
 /solve/execute-commands/add-edit save_residuals 10 &quot;time-step&quot; &quot;/solve/monitors/residual/plot? yes /display/set-window 1 /display/save-picture residual-%t.jpg&quot;
 ;; time step interval for auto-save
 /file/auto-save/data-frequency 10
 ;; set suffix (in this case, time-step) for auto-saved files
 /file/auto-save append-file-name-with time-step 6
 ;; time step size (dt)
 /solve/set/transient-controls/time-step-size 8.33333e-5
 ;; iterate 10 time step, each time step has 20 iteration,
 /solve/dual-time-iterate 10 20
 ;; write data file
 /file/write-case-data wst.data
 ;; exit FLUENT
 /exit yes
 ;; blank line at end

&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
        &lt;p&gt;Line by Line으로 설명하자면&lt;/p&gt;
        &lt;ul&gt;
          &lt;li&gt;batch option
   ```
   (set! &lt;em&gt;cx-exit-on-error&lt;/em&gt; #t)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;/file/set-batch-options yes yes yes yes
   ```
 이 부분은, overlap 되는 부분이 있긴 한데, ANSYS에서 File-&amp;gt;Batch Options의 설정이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(set! *cx-exit-on-error* #t)&lt;/code&gt; 이 커맨드는 Scheme문법의 ANSYS GUI 커맨드로 Exit on error를 체크하는 것이고, 아래 TUI 커맨드 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/file/set-batch-options yes yes yes yes&lt;/code&gt;는 원래는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Confirm File Overwrite&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hide Questions&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Exit on Error&lt;/code&gt; 이 세 가지를 묻는 옵션이었으나 20.1 기준으로는 실제 콘솔에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/file/set-batch-options&lt;/code&gt;을 해봤을 경우 한 가지 더 묻는데, 지금 기억이 안나서 스킵.. 여튼 이걸 다 yes하는 이유는 job 실행시 저거 묻는다고 멈추는데 서버에서 그걸 interactive하게 대답하기 힘드므로 그걸 무시하기 위해서이다.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;Read case file
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  ;; read case file
  /file/read-case wst.cas.gz
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;journal file의 주석은 Scheme을 따라 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;;&lt;/code&gt;로 처리하였다. 단순하게 case file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wst.cas.gz&lt;/code&gt;를 읽는 명령어&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Disabling HDF5 output
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  ;; disable HDF5 output
  /file/cff-files no
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;개인적으로 시뮬레이션 output HDF5를 선호하는 편인데, ANSYS에서 끄는 이유는 &lt;a href=&quot;https://forum.ansys.com/discussion/17423/reading-h5-file-into-ansys-for-post-processing&quot;&gt;CFD-Post에서 지원을 안해서&lt;/a&gt;, 근데 20.1부터 HDF5 output이 디폴트로 설정되어 있다. (..) 언젠가는 지원해주겠지만 일단은 지금은 끄자.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Solver initalization, 이것도 뭐 GUI에서 하는 그 intialization
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  ;; initialize the solution
  /solve/initialize/initialize/
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Plot residual
    &lt;ul&gt;
      &lt;li&gt;우리 연구실에서 GUI를 선호하는 이유 중 하나가 실시간으로 residual 확인하면서 계산이 터지는지 안터지는지 확인하고 싶어서인데, 최근에서야 이걸 알아냈다. 처음에는 복잡하게 텍스트파일로 매번 출력해서 다운받고 다른 프로그램으로 그래프 그려서 보려고 했는데, 더 편하게 바로 plot 해줄 수 있는 명령어가 있다.&lt;/li&gt;
      &lt;li&gt;기본적인 원리는 ANSYS에서 iteration 혹은 time-step별로 실행할 수 있는 커맨드를 추가할 수 있는 execute commands 기능을 사용하는 것이다. residual을 특정 iteration 혹은 time-step마다 출력하게 하고 이를 그림으로 출력하는게 그 원리&lt;/li&gt;
      &lt;li&gt;Stationary simulation에서는 다음 명령에서 iteration 기준으로 10번째마다 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;residual-xxxx.png&lt;/code&gt;를 출력하는 것. 여기서 xxxx는 iteration number를 말한다. &lt;em&gt;이렇게 하면 그림파일이 많이 나오겠지만, 이렇게 안하면 overwrite할거냐고 물어보면서 플랏이 제대로 그려지지 않는다.&lt;/em&gt; 이를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save_residuals&lt;/code&gt;라는 커맨드로 저장한다.
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  ;; save residuals plot as &quot;residual-xxxx.jpg&quot; at every 10 iteration, xxxx is a iteration number
  /solve/execute-commands/add-edit save_residuals 10 &quot;iteration&quot; &quot;/solve/monitors/residual/plot? yes /display/set-window 1 /display/save-picture residual-%i.jpg&quot;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;Transient simulation의 경우는 time step 기준으로 10번째마다 위에서 설명한 것 같이 파일을 출력한다.
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  ;; save residuals plot as &quot;residual.jpg&quot; at every 10 time-step
  /solve/execute-commands/add-edit save_residuals 10 &quot;time-step&quot; &quot;/solve/monitors/residual/plot? yes /display/set-window 1 /display/save-picture residual-%t.jpg&quot;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;너무 자주 plot하면 시뮬레이션이 느려진다&lt;/strong&gt; 적당히 조절하자&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Auto-save
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  ;; time step interval for auto-save
  /file/auto-save/data-frequency 10
  ;; set suffix (in this case, time-step) for auto-saved files
  /file/auto-save append-file-name-with time-step 6
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;ul&gt;
      &lt;li&gt;Transient simultation은 시간이 오래 걸려서 중간 중간 저장하는게 중요한데 이 저장하는 frequency와 filename을 변경하는 옵션이다. 지금 같은 경우 10 step 마다 저장하고, 중간 저장 suffix을 time-step으로 지정하는 것. 그런데 이 옵션 제대로 테스트 안해봐서 확실하진 않다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Simulation
    &lt;ul&gt;
      &lt;li&gt;Stationary simulation
        &lt;ul&gt;
          &lt;li&gt;이건 단순하다. 100 stp의 iteration을 돌린다.
            &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;;; iterate 100 step
/solve/iterate 100
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;            &lt;/div&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Transient simulation
        &lt;ul&gt;
          &lt;li&gt;transient한 경우는 time step size(소위 말하는 dt)를 설정하고, 각 step 마다 몇 번 iteration을 돌리는 지 설정하고 돌려야하는데, 이렇게 돌리는게 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/solve/dual-time-iterate&lt;/code&gt;에서 총 time step(10)과 각 iteration별 time step(20)을 매개변수로 넘기면 된다.
            &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;;; time step size (dt)
/solve/set/transient-controls/time-step-size 8.33333e-5
;; iterate 10 time step, each time step has 20 iteration,
/solve/dual-time-iterate 10 20
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;            &lt;/div&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Write case
    &lt;ul&gt;
      &lt;li&gt;계산이 끝나면 당연히 결과물을 저장해아하니깐, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/file/write-case-data&lt;/code&gt; 명령어를 쓴다.
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;;; write data file
/file/write-case-data wst.data
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Exit Fluent
    &lt;ul&gt;
      &lt;li&gt;다른 journal file에도 있길래 넣었는데, 이거 안넣으면 제대로 종료가 안되는 모양이다. 괜히 제대로 종료안되면 라이센스는 라이센스대로 점유하고 자원은 자원대로 못 쓸테니 써줘야한다.
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;;; exit FLUENT
/exit yes
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Blank line
    &lt;ul&gt;
      &lt;li&gt;혹시 몰라 EOF(End of File)를 위해 넣었다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Job scheduler file 작성 (SGE 기준)&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; #!/bin/bash
 #$ -cwd
 #$ -V
 #$ -N 잡이름
 #$ -S /bin/sh
 #$ -j y
 #$ -q 큐이름
 #$ -pe ParallelEnvironment이름 코어수

 # Load module even you run jobs
 module purge
 module load ansys/20.1/fluent

 cpus=코어수

 # execute Fluent
 fluent 3ddp -rsh -t${cpus} -gu -i wst.in &amp;gt; wst.output
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;각자 서버마다 사정이 있으므로 나머지는 알아서 하면 되지만, 맨 마지막 줄은 반드시 저렇게 해야한다.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;3ddp : 3D Double Precision&lt;/li&gt;
      &lt;li&gt;-rsh : rsh 방식으로 remote connection 구축, 이건 서버마다 환경이 다르므로 바꿔도 된다. 디폴트는 ssh방식&lt;/li&gt;
      &lt;li&gt;-t{cpus} : 굳이 이처럼 cpus변수 안만들고 직접 숫자 넣어줘도 된다. 다만 잡스케줄러의 코어수와는 맞춰주자.&lt;/li&gt;
      &lt;li&gt;-gu : GUI는 안쓰지만 그래픽은 쓰는 옵션. 일반적인 batch mode이면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-g&lt;/code&gt;를 써야하지만 residual plot때문에 그래픽이 필요하므로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-gu&lt;/code&gt;로 바꿔줬다.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i wst.in&lt;/code&gt; : 위에서 저장한 journal file을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wst.in&lt;/code&gt;이라고 저장했다면, 여기서 실행할 때 ‘i’nput으로 넣어주는 것.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt; wst.output&lt;/code&gt; : fluent 실행결과를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wst.output&lt;/code&gt;으로 저장하는 건데 어차피 SGE의 경우 job id에 따라 output이 따로 나오고, ANSYS transcript file(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.trn&lt;/code&gt;)이 따로 생성되기 때문에 없애도 상관없을 것 같다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Job submit : 위에서 저장한 잡스크립트 파일을 SGE의 job submit 명령어인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qsub&lt;/code&gt;을 통해 제출&lt;/p&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;qsub 잡스크립트파일
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이게 다이다.
복잡하지만, Journal file을 잘 만들면 GUI로 안해도 Text로 어느정도 대체할 수 있고 이를 fluent 실행시 input으로 넣어주면 된다. 사실 &lt;strong&gt;계속 수정중이고, 테스트도 계속하고 있어서 완벽하다 할 수는 없다&lt;/strong&gt;. 그래서 보통은 내부 매뉴얼로 만들고 마는 문서인데, 너무나 문서를 찾기가 힘들어서 필요로 하시는 분들도 있을 것 같고, 정리도 할 겸 적어보았다.&lt;/p&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ksc.re.kr/gsjw/jcs/sft#docout-7&quot;&gt;KISTI Manual&lt;/a&gt; : 국가슈퍼컴퓨팅센터 -&amp;gt; 기술지원 -&amp;gt; 소프트웨어&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.rescale.com/articles/ansys-start-here/&quot;&gt;Rescale 문서&lt;/a&gt; : FAQ에 Residual plot 내용이 있었다.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.ksc.re.kr/attachment/cfile9.uf@99D2954E5C0F6D9916BDE4.pdf&quot;&gt;ANSYS Fluent Getting Start guide&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.afs.enea.it/project/neptunius/docs/fluent/html/tuilist/main_pre.htm&quot;&gt;ANSYS Fluent TUI Command List&lt;/a&gt; 오래된 사이트라 그런지 TLS 에러가 나는데 크롬의 경우 고급눌러서 “안전하지 않음으로 이동”을 누르면 들어가진다. 참고로 12.0 기준이라 달라진 명령어가 있는데 이는 콘솔창에서 help 명령어 등을 통해 제대로 된 명령어 및 매개변수를 체크하고 사용하기 바란다.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://davis68.github.io/me498cf-fa16/resources/flec08/handout-tui-scheme.pdf&quot;&gt;Fluent - Scheme 기초&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cfdresearch.com/wp-content/uploads/2018/08/scheme-programing-Javurek.pdf&quot;&gt;Fluent - Scheme 문서&lt;/a&gt; 거의 유일한 Fluent에서의 Scheme 문서. 무려 ANSYS 5,6 기준이지만 아직도 적용되는게 많다. 이것도 독일어로 된 문서가 따로 있고, 그걸 번역한 것&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://cfdyna.com/CFDHT/FluentErrors.html&quot;&gt;Fluent Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Environment Modules 사용하기</title>
   <link href="https://blog.liam.kim/posts/2020/10/Environment-Modules/"/>
   <updated>2020-10-12T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2020/10/Environment-Modules</id>
   <content type="html">&lt;p&gt;오랫동안 연구실에서 서버관리를 담당해왔다. 처음에는 단순 서버 on/off 정도만 하는 일이었는데, 별것도 아닌걸로 업체 통하면 시간도 오래걸리고 설명하기도 복잡하다보니 내가 처리하게 되었다. 그러다보니 점점 일이 늘어나서 서버 구축포함 대부분의 이슈를 내 선에서 해결하고자 노력하고 있다.&lt;/p&gt;

&lt;p&gt;그런데 처음에는 다들 Fortran만 써서 상관없었지만 연구주제가 다양해지면서 다양한 환경을 구축할 필요가 생겼다. 다른 컴파일러, 다른 언어, 다른 서버 등등. 당연히 환경변수를 건드려야할 일이 많았다. 하지만, 유저들 대부분 환경변수가 뭔지도 모르는 사람들이다. 그래서 그동안 사용한 방법은 curl을 통해 미리 작성된 .bashrc를 받게 하는 것이었다. 디폴트 bashrc를 변경할 수도 있었겠지만, 상황에 따라 각자의 bashrc를 업데이트해야하는 경우도 생겨서 좀 더 안전하게 가려고 했다. 이 방법은 유연하게 대처하기 힘들고 특이사항이 생기면 내가 직접 수정해줘야하고, 환경변수가 지속적으로 append되는 경우 불필요하게 환경변수가 중복되는 경우가 생기기도 했다. 그러나, bashrc를 직접 건드리다가 PATH 같은 변수들을 날려먹고 나한테 찾아오는 경우가 종종 있었기에 나쁘지 않다고 생각했다.&lt;/p&gt;

&lt;p&gt;그러다가 &lt;a href=&quot;https://www.ksc.re.kr/gsjw/jcs/hd#docout-0&quot;&gt;KISTI 누리온 매뉴얼&lt;/a&gt;을 접할 기회가 생겼는데, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;이라는 걸 쓴다는걸 알게 되었다. 우리 연구실보다도 훨씬 많은 사용자와 많은 환경을 지원해야하는 KISTI에서 각자 환경에 맡게 module을 load해서 쓰는 방법이었다. 패키지 페이지는 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://modules.sourceforge.net/&quot;&gt;Environment Modules&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;홈페이지 소개란에 있던 이 패키지의 사용목적은 정확히 내가 원하는 그것이었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Typically users initialize their environment when they log in by setting environment information for every application they will reference during the session. The Environment Modules package is a tool that simplify shell initialization and lets users easily modify their environment during the session with modulefiles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;어차피 현재까지 연구실 서버에서 쓰는 환경이 compiler 버전 변경, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Python&lt;/code&gt; 사용시 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pyenv&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Anaconda&lt;/code&gt; 방식 변경, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CUDA&lt;/code&gt; 사용 여부, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ANSYS&lt;/code&gt; 사용여부 정도라서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt; 정도만 append 하는 정도라 복잡한 기능을 쓸 필요는 없었다. 그래서 단순하게 사용법만을 소개해둔다.&lt;/p&gt;

&lt;h2 id=&quot;사용법&quot;&gt;사용법&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;의 사용법은 쉽다. 유저들에겐 아래 명령어들만 숙지시키면 된다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module avail&lt;/code&gt; : 사용가능한 모듈 보기 (줄여서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module av&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module load MODULENAME&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module add MODULENAME&lt;/code&gt; : module avail로 확인한 특정 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MODULENAME&lt;/code&gt; load&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module unload MODULENAME&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module rm MODULENAME&lt;/code&gt; : module avail로 확인한 특정 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MODULENAME&lt;/code&gt; unload&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module list&lt;/code&gt; : 현재 사용중인 모듈 출력&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module purge&lt;/code&gt; : 현재 사용중인 &lt;strong&gt;모든&lt;/strong&gt; 모듈 삭제&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Job scheduler 사용시 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module purge&lt;/code&gt; 시키고, 다음 예시와 같이 필요한 module을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt; 하도록 했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;  module purge
  module load gcc/10.1
  module load gcc/10.1/fftw3/3.3.8
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;설치&quot;&gt;설치&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://modules.readthedocs.io/en/latest/INSTALL.html#installation-instructions&quot;&gt;modules의 INSTALL 문서&lt;/a&gt; 를 참고해서 모듈을 설치한다. 일반적인 소스컴파일 과정인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./configure&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make install&lt;/code&gt;을 따른다. CentOS의 경우 심플하게 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install environment-modules&lt;/code&gt; 써도 된다.&lt;/li&gt;
  &lt;li&gt;Configuration section을 따라 initialization을 실행한다. 나는 유저들의 bashrc에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source PREFIX/init/bash&lt;/code&gt; 를 넣는걸 선호했다. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; 사용시) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PREFIX&lt;/code&gt;는 default가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/Modules&lt;/code&gt; (소스 컴파일 default 값)이거나 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/share/Modules&lt;/code&gt;(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum 설치시&lt;/code&gt;)로 잡힌다.&lt;/li&gt;
  &lt;li&gt;Configuration에서 default로 불러들일 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module path&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modulefiles&lt;/code&gt;를 지정하는 파트가 있는데 사용자들이 어떤걸 쓸지 어떻게 알고 정하나 싶어서 나는 지정하지 않았다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modules&lt;/code&gt;의 원리는 다음과 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;지정된 위치 (디폴트는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/Modules/modulefiles&lt;/code&gt;)의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modulefiles&lt;/code&gt;를 읽고 그 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modulefiles&lt;/code&gt;를 읽어서 필요한 환경변수를 추가하거나 삭제하는 것.&lt;/li&gt;
  &lt;li&gt;modulefiles 디렉토리내의 서브 디렉토리는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module list&lt;/code&gt;에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;로 처리된다. (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcc/10.1/fftw3/3.3.8&lt;/code&gt;은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PREFIX/modulefiles/gcc/10.1/fftw3/3.3.8&lt;/code&gt;라는 모듈 파일이 존재하는 것이다) 실제로 가보면 예제들이 몇개 있는데 대부분 참고용이니 다른디렉토리에 복사해두고 지웠다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;modulefile-생성하기&quot;&gt;modulefile 생성하기&lt;/h2&gt;

&lt;p&gt;그럼 제일 중요한 modulefile은 어떻게 구성되어있냐 하면, &lt;a href=&quot;https://modules.readthedocs.io/en/latest/modulefile.html&quot;&gt;자체 문법&lt;/a&gt;이 있다. 하지만 이걸 다 알고 쓸 필요는 없다. 일반적인 쉘 스크립트를 modulefiles로 자동으로 변환해주는 파이썬 스크립트가 패키지 안에 때문!&lt;/p&gt;

&lt;p&gt;예를 들어,&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;gcc 사용을 위해 환경변수 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt;가 필요하다고 하자.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; export PATH=/APP/gcc/10.1/bin:$PATH
 export LD_LIBRARY_PATH=/APP/gcc/10.1/bin:$LD_LIBRARY_PATH
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&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;_bashrc_gcc10&lt;/code&gt;이라고 저장한다음에&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;이를 다음 명령어를 통해 modulefile로 출력한다.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; PREFIX/bin/createmodule.sh _bashrc_gcc10 &amp;gt; modules_gcc10
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;이라고 하면 modulefile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modules_gcc10&lt;/code&gt;이 만들어진다. python script도 있다.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; python PREFIX/bin/createmodule.py _bashrc_gcc10 &amp;gt; modules_gcc10
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;이렇게 만들어진 modulefile을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modulefiles&lt;/code&gt; 위치에 복사하면 끝. 구조적으로 관리가 필요하다면 modulefiles 디렉토리를 환경에 따라 디렉토리 구조로 바꿔주면 된다.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; cp modules_gcc10 PREFIX/modulefiles/gcc10
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;바로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module avail&lt;/code&gt;을 통해 확인할 수 있다. (reboot 불필요)&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; ---------------- PREFIX/modulefiles ----------------
 gcc10
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;삭제도 그냥 single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modulefile&lt;/code&gt;이나 서브 디렉토리를 삭제하면 된다.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;참 쉽죠?&lt;/p&gt;

&lt;h2 id=&quot;pros-and-cons&quot;&gt;Pros and Cons&lt;/h2&gt;

&lt;h3 id=&quot;pros&quot;&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;사용자들이 위험하게 .bashrc나 .bash_profile을 건드릴 필요가 없다.&lt;/li&gt;
  &lt;li&gt;PATH등이 불필요하게 append돼서 duplicate될 일이 없다.&lt;/li&gt;
  &lt;li&gt;관리자 입장에서 module의 추가, 삭제 및 수정이 매우 쉽고 편리하다.&lt;/li&gt;
  &lt;li&gt;문서의 Cookbook section을 보면 알겠지만, 다양한 방식으로 사용자화가 가능하다.&lt;/li&gt;
  &lt;li&gt;제일 중요한것, &lt;strong&gt;유저들이 사용하기 편리하다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;cons&quot;&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;아직까지 단점을 모르겠다.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>비개발자들에게도 권하는 개발자 도구들</title>
   <link href="https://blog.liam.kim/posts/2020/10/Developer-Tools-for-Everyone/"/>
   <updated>2020-10-05T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2020/10/Developer-Tools-for-Everyone</id>
   <content type="html">&lt;p&gt;생각외로 많은 이공계 학생들이 프로그래밍을 못한다. CS가 아니면 대부분.. 특히 시뮬레이션 위주의 과학계산 연구를 하는 사람들도 알고리즘 자체는 복잡하지만 코드 아키텍처는 간단한 프로그래밍을 주로 한다. OOP니 FP니 없어도 Init - Read - Compute - Write 구조의 코드로 수십년동안 시뮬레이션 하는 경우도 있다. 오랫동안 대학원 생활을 하면서 이런 비 CS출신 이공계학생들을 보니 다음과 같은 특징이 있다는 걸 알게 되었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;그 전에는 코딩경험이 전무하거나, C/Fortran, 혹은 Matlab 문법 정도만을 안다.&lt;/li&gt;
  &lt;li&gt;리눅스는 물론이고 CLI환경에 대해 익숙하지 않다.&lt;/li&gt;
  &lt;li&gt;대부분 개발자 툴에 대해 모르거나 필요성을 느끼지 못한다.&lt;/li&gt;
  &lt;li&gt;그렇다고 새로운 툴을 적극적으로 배우려고 하지 않는데, 그 이유는 지금쓰고 있는 정도로도 결과가 잘 나오기 때문이다. 즉, 필요성을 느끼지 못한다.&lt;/li&gt;
  &lt;li&gt;레거시(Legacy) 코드가 존재하는데 연식이 십년단위인 것도 존재하며, 버전관리 및 리팩토링은 이루어지지 않고, 연구주제에 따라 고쳐쓰다보니 파편화가 심각하다. 이에 따른 버그들이 실재하지만, 버그 존재 자체를 인지하지 못하거나 회피한다.&lt;/li&gt;
  &lt;li&gt;레거시 코드 중에서 문서가 남겨지는 코드들은 극소수이며, 대부분 도제방식으로 코드 사용법을 알려준다. 따라서 시간이 지날수록 누락되는 부분이 있고, 이 부분은 블랙박스처럼 여겨지며 코드 개선을 더욱 어렵게 한다.&lt;/li&gt;
  &lt;li&gt;디버깅은 화면 출력(print문 등등)을 사용함. 디버거를 사용할 줄 알는 사람이 매우 적다.&lt;/li&gt;
  &lt;li&gt;수치해석 코드의 경우 대부분의 코드가 Init-Read-Loop-Write형식의 구조이며, 실수(real number, floating point number)로 출력된 결과물을 눈으로 보고 사람이 판단해야하는 경우가 많기 때문에 테스트를 작성하기가 쉽지 않음.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이를 개선하기 위한 각종 툴들이 존재하나, 이를 정리한 문서가 별로 없었는데 마침 얼마전에 &lt;a href=&quot;https://missing.csail.mit.edu/&quot;&gt;The Missing Semester of Your CS Education&lt;/a&gt; 라는 과목을 알게 되었다.
(&lt;a href=&quot;https://missing-semester-kr.github.io/?fbclid=IwAR023hFv8pI84e4eow1qSc2U9pNo2FR0ptLYM41bkRtqy2jA0hzvuGLHM7k&quot;&gt;한글 번역&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;위 강의에서 대상은 “Your CS Education” 이라고 되어있지만, 실제로는 비전공자 포함 모든 프로그래머를 대상으로 하는 것이 아닌가 하는 생각이 들었다.&lt;/p&gt;

&lt;p&gt;CS에서도 코드가 잘 관리되지 않는 경우가 많지만, 그래도 많은 사람들이 이를 쉽게 해결하기 위해 여러가지 툴을 개발하였고, 이 툴들을 많이 쓰고 또 잘 쓰고자 노력한다. 그렇기에 자연스럽게 배울 수 있는 기회도 많고, 배울 수 밖에 없는 상황이 만들어지기도 한다. 하지만, 과학계산 분야는 위에서 언급하다시피 툴이 있는지도 모르고, 안다 하더라도 배울 필요성을 잘 못 느낄 뿐더러, 배운다고 하더라도 문서로 배우기보단 누군가 강의하고 이를 받아들이기를 매우 선호한다. (물론 내 선입견일수도 있지만, 내 경험상 그렇다는 얘기. 물론 잘 관리되는 곳도 있다.) 수업을 따로 만들지 않는 이상 이런 걸 찾아보는 사람은 드물고 배우는 것도 힘겨워하는 것 같았다.&lt;/p&gt;

&lt;p&gt;여튼, 위에서 언급한 MIT강의가 딱 이런 사람들을 위한 강의가 아닌가 싶다. 이런 툴들이 강의를 듣거나 설명을 듣는다고 한번에 해결될 문제는 아니지만, 그래도 툴의 존재도 모르는 것보다는 낫지 않은가. 한국의 Top-down 선호 정서상 교수님들이 이런 툴들을 알고 쓰기를 장려한다면 내가 다른 학생들에게 추천하는 것보다 훨씬 쉽게 해결될 문제이지만, 사실 교수님들도 이런걸 잘 아시는 분들을 본 적이 드물다. 당장 연구실적이 급하긴 하니깐 이해는 하지만 장기적으로 볼때는 모두가 이런 툴들을 쓰는게 낫지 않겠는가. 나도 툴을 잘 쓴다고는 어디가서 이야기하진 못하지만, 그래도 안쓰는것보단 낫다고 생각한다. 그동안 여러번 연구실에 이런 툴들을 소개하고 추천해줬지만 대부분 연구 결과가 급하기에 강제적으로 쓰게 하지 않는 이상 쉽지 않다. 그런 사실을 잘 알기에 이 강의를 더욱 추천하고, 학교에서 수업형태로 가르쳤으면 좋겠다.&lt;/p&gt;

&lt;p&gt;또한, 이 강의에서 설명하지 못한 부분도 몇 가지가 있는데, 그 중 중요한 걸 뽑자면&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;(영어로 쓰여진) 개발 문서를 읽고 이해하는 방법&lt;/li&gt;
  &lt;li&gt;검색하는 법&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;정도가 있다. 1,2번이 된다면 위의 강의가 그렇게 필수적인게 아니다. 이게 된다면 툴 이름만 알려줘도 알아서 찾아서 쓸 수 있기 때문. 그러나 1,2번은 그동안 경험적으로 체득한 결과라 생각해서 쉽게 설명하기 어려웠는데 마침 최근에 &lt;a href=&quot;https://boxnwhis.kr/2020/09/27/ir-for-developers.html?fbclid=IwAR1J7ydMI5JWBxXyBn8wbbDA-sr-QP7HYtGPILOBH2uX2AIsm1spZyTNQDQ&quot;&gt;개발자를 위한 정보 검색 팁&lt;/a&gt; 이라는 글을 알게 되었다. 이 글이야 말로 내가 고민하던 문제를 완벽하게 설명해준 글이다. 이걸 미리 알았다면 난 그동안 고생을 덜 해도 되지 않았을까 하는 생각이 들 정도로 퀄리티도 훌륭하고 체계적으로 잘 설명해주신 포스트다.&lt;/p&gt;

&lt;p&gt;지금까지 소개한 두 링크를 잘 숙지한다면 비개발자도 충분히 일의 효율성을 높일 수 있을 것이라 생각한다. 강추!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://missing.csail.mit.edu/&quot;&gt;The Missing Semester of Your CS Education&lt;/a&gt;
(&lt;a href=&quot;https://missing-semester-kr.github.io/?fbclid=IwAR023hFv8pI84e4eow1qSc2U9pNo2FR0ptLYM41bkRtqy2jA0hzvuGLHM7k&quot;&gt;한글 번역&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://boxnwhis.kr/2020/09/27/ir-for-developers.html?fbclid=IwAR1J7ydMI5JWBxXyBn8wbbDA-sr-QP7HYtGPILOBH2uX2AIsm1spZyTNQDQ&quot;&gt;개발자를 위한 정보 검색 팁&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>External Forcing of Homogeneous Isotropic Turbulence</title>
   <link href="https://blog.liam.kim/posts/2020/08/External-Forcing-Turbulence/"/>
   <updated>2020-08-25T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2020/08/External-Forcing-Turbulence</id>
   <content type="html">&lt;p&gt;(This content is originally written by &lt;a href=&quot;https://scholar.google.com/citations?user=8fMRupoAAAAJ&amp;amp;hl=ko&quot;&gt;Kyongmin Yeo&lt;/a&gt;’s manual)&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;The small scale statistics of turbulence are important research topic.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Small-scale behavior in turbulent flows tends to be characterized by statistical homoegenity, isotropy, and universality. Because of this universality we can hope to&lt;br /&gt;understand small-scale behavior by studying the simplest turbulent flows, i.e. homoegeneous, isotropic turbulence.&lt;/p&gt;&lt;cite&gt;&lt;a class=&quot;citation&quot; href=&quot;#eswaran_examination_1988&quot;&gt;[1]&lt;/a&gt;&lt;/cite&gt;&lt;/blockquote&gt;

&lt;p&gt;To maintain statistically stationary turbulence, adding force to low wavenumber (large scale) velocity components artificially. Therfore, external force term is added to Navier-Stokes equation&lt;/p&gt;

\[\dfrac{d \hat{u}_i}{dt} = - i \kappa_{i} \hat{P} + \hat{H}_i - \nu \kappa^2 \hat{u}_i + \hat{f}_i\]

&lt;p&gt;where \(\hat{f}_i\) is a external forcing term.&lt;/p&gt;

&lt;p&gt;The forcing \(\hat{f}_i\) is applied to circle of low wavenumber band. \(\hat{f}_i\) is defined as the projection of a vector \(\hat{\mathbf{b}}\) onto the plane normal to the wavenumber vector \(\mathbf{\kappa}\) to ensure divergence-free condition.&lt;/p&gt;

\[\hat{f}_{i} = \hat{b}_{i} - \dfrac{\kappa_i}{\kappa^2} \kappa_j \hat{b}_{j}\]

&lt;p&gt;So, how we define vector \(\hat{\mathbf{b}}\)?
Eswaran &amp;amp; Pope suggested stochastic forcing &lt;a class=&quot;citation&quot; href=&quot;#eswaran_examination_1988&quot;&gt;[1]&lt;/a&gt;.
They define 3D complex vector \(\hat{\mathbf{b}}\) which is non-zero in the range \(0 &amp;lt; \kappa &amp;lt; \kappa_f \), in which \(\kappa_f\) is the maximum forcing wavenumber. This can be interpreted as forcing to sphere in wavenumber space.&lt;/p&gt;

&lt;p&gt;They used Uhlenbeck-Ornstein process to generate \(\hat{\mathbf{b}} = \hat{b} (\kappa, t) \) with following properties, the average and the correlation.&lt;/p&gt;

\[\begin{align}
  \langle \hat{b} (\kappa, t) \rangle &amp;amp;= 0 \\
  \langle \hat{b} (\kappa, t) \hat{b}^* (\kappa, t + s) \rangle &amp;amp;=  2\sigma^2 \delta_{ij} \exp{(-s/T_L)}
\end{align}\]

&lt;p&gt;where an asterisk dentoes a complex conjugate, angle bracket is the ensemble average, \( \delta_{ij} \) is the Kronecker delta. \( \sigma^2 \) and \( T_L \) are the variance and time-scale of UO process. Obviously, if \( T_L \) increases with fixed \(\sigma \), the correlation will converge to zero. This is by no means the desired result, so \( \epsilon^* \equiv \sigma^2 T_L \) is fixed.&lt;/p&gt;

&lt;p&gt;The three-dimensional vector \(\hat{\mathbf{b}}\) is composed of six independent Uhlenbeck-Ornstein process.&lt;/p&gt;

\[\hat{\mathbf{b}} = \begin{bmatrix} UO1 \\ UO3 \\ UO5 \\ \end{bmatrix} + i \begin{bmatrix} UO2 \\ UO4 \\ UO6 \\ \end{bmatrix}\]

&lt;h2 id=&quot;solving-uhlenbeck-ornstein-process&quot;&gt;Solving Uhlenbeck-Ornstein process&lt;/h2&gt;

&lt;p&gt;Each stochastic process, \(UO1 \) ~ \( UO6\), is chosen so as to satisfy the &lt;a href=&quot;https://en.wikipedia.org/wiki/Langevin_equation&quot;&gt;Langevin equation&lt;/a&gt; with a time scale \(T^f_L\) and stadnard deviation \(\sigma_f\).&lt;/p&gt;

&lt;p&gt;In &lt;a class=&quot;citation&quot; href=&quot;#wojnowicz_ornstein-uhlenbeck_2012&quot;&gt;[2]&lt;/a&gt;, UO process are defined as&lt;/p&gt;

\[dx_t = \dfrac{(\mu - x_t)}{\tau} dt + \sqrt{\dfrac{2\nu}{\tau}} dW_t\]

&lt;p&gt;After applying zero mean property of forcing term and adjusting parameters makes above eqaution to&lt;/p&gt;

\[dUO = - \dfrac{UO}{T^f_L} \Delta t + \left( \dfrac{2\sigma^2_f}{T^f_L} \right)^{1/2} dW_t\]

&lt;p&gt;in which \(W_t\) denotes a Wiener process satisfying&lt;/p&gt;

\[dW_t \sim \mathcal{N} (0, \Delta t)\]

&lt;p&gt;&lt;a href=&quot;http://physics.gu.se/~frtbm/joomla/media/mydocs/LennartSjogren/kap6.pdf&quot;&gt;The analytical solution of the Langevin equation&lt;/a&gt; is given by following equation, which describes the Browninan motion of particle.&lt;/p&gt;

\[\begin{align}
x(t) &amp;amp;= x_0 + \int_0^t v(s) ds \\
v(t) &amp;amp;= e^{-t/T^f_L} v_0 + \dfrac{1}{m} \int^t_0 e^{-(t-s)/T^f_L} dW(s)
\end{align}\]

&lt;p&gt;where \(x_0 = x(0)\) and \(v_0 = v(0) \). The forcing \(\hat{f}_i\) term can be viewed as forcing acclereration, then \(UO \) can be denoted to \(v(t)\).&lt;/p&gt;

\[UO(t) = UO(0) e^{-t/T^f_L} + e^{-t/T^f_L} \int^t_{0} e^{s/T^f_L} (2\sigma^2_f/T^f_L)^{1/2}dW_s\]

&lt;p&gt;Above solution can be solved discretely by applying Itô integral. With RK3 method, UO process discretized solution is&lt;/p&gt;

\[UO^{n+1} = e^{-(a_n+b_n)\Delta t / T^f_L}\left[ UO^{n} + e^{s/T^f_L} (2\sigma^2_f/T^f_L)^{1/2}dW_s dW^n \right]\]

&lt;p&gt;in which discretized Wiener process is&lt;/p&gt;

\[dW^n \sim \mathcal{N} (0, (a_n + b_n) \Delta t)\]

&lt;p&gt;This is the extension of &lt;a href=&quot;https://en.wikipedia.org/wiki/Euler%E2%80%93Maruyama_method&quot;&gt;Euler-Maruyama method&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;estimating-reynolds-number&quot;&gt;Estimating Reynolds Number&lt;/h2&gt;

&lt;h3 id=&quot;input-parameters&quot;&gt;Input parameters&lt;/h3&gt;

&lt;p&gt;The input parameters are \( \kappa_0 \) (the lowest wavenumber), \( \kappa_\textrm{max} \) (the highest wavenumber), \( K_F \) (the maximum wavenumber of the forced modes), \( \nu \) (the kinematic viscosity), \( T_L \) (the forcing time scale, time scale in UO process), and \( \epsilon^* = \sigma^2 T_L \).&lt;/p&gt;

&lt;p&gt;The nondimensional parameters are \(\kappa_{\textrm{max}} / \kappa_0 \), \( K_{F} / \kappa_0\),&lt;/p&gt;

\[\begin{align}
Re^* &amp;amp;\equiv \epsilon^* \kappa_0^{-4/3} / \nu \\
T^*_L &amp;amp;\equiv T_L {\epsilon^{*}}^{1/3} \kappa_0^{2/3}
\end{align}\]

&lt;h3 id=&quot;given-parameters&quot;&gt;Given parameters&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;\( \nu \) : Fluid viscosity&lt;/li&gt;
  &lt;li&gt;\( \beta \) : constant (\( \beta=0.8 \))&lt;/li&gt;
  &lt;li&gt;\( \kappa_0 \) : smallest wavenumber&lt;/li&gt;
  &lt;li&gt;\( \kappa_f \) : maximum forcing wavenumber&lt;/li&gt;
  &lt;li&gt;\( T_L \) : Forcing time scale&lt;/li&gt;
  &lt;li&gt;\( \epsilon^* \equiv \sigma^2 T_L \) where \( \sigma \) is a forcing amplitude, usually just given by constant&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;assumptions&quot;&gt;Assumptions&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;\( \epsilon \propto N_f \epsilon^* \)&lt;/li&gt;
  &lt;li&gt;\( T_e \approx \dfrac{\beta}{(N_f \epsilon^* \kappa^{2}_0)^{1/3}}\) (posteriori assumption)&lt;/li&gt;
  &lt;li&gt;\( \kappa^{-1}_{0}\) : Integral length scales&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;computed-parameters&quot;&gt;Computed parameters&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;\( N_f \) : The number of forced modes, \( \kappa &amp;lt; \kappa_f \), counted manually&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Predicted energy dissipation,&lt;/p&gt;

\[\begin{align}
    T_e &amp;amp;= \dfrac{\beta}{(N_f \epsilon^* \kappa^2_{0})^{1/3}} \\
    T^*_L &amp;amp;\equiv T_L {\epsilon^{*}}^{1/3} \kappa_0^{2/3} \\
    \epsilon^*_{T} &amp;amp;= \epsilon \\
        &amp;amp;\equiv \dfrac{4\epsilon^* T_e N_f}{T_L + T_e} \\
        &amp;amp;= \dfrac{4 \epsilon^* N_f}{1 + T^*_{L} N^{1/3}_{F}/\beta}
  \end{align}\]
  &lt;/li&gt;
  &lt;li&gt;Predicted Kolmogorov microscale
\(\eta_{T} \equiv (\nu^3 / \epsilon^*_T)\)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;predicted--re-&quot;&gt;Predicted \( Re \)&lt;/h3&gt;
&lt;p&gt;Using above parameters Taylor Reynolds number is estimated by&lt;/p&gt;

\[Re \simeq \dfrac{8.5}{(\eta_{T} \kappa_0)^{5/6} N^{2/9}_{F}}\]

&lt;h1 id=&quot;references&quot;&gt;References&lt;/h1&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;eswaran_examination_1988&quot;&gt;[1]V. Eswaran and S. B. Pope, “An examination of forcing in direct numerical simulations of turbulence,” &lt;i&gt;Computers and Fluids&lt;/i&gt;, 1988, doi: 10.1016/0045-7930(88)90013-8.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;wojnowicz_ornstein-uhlenbeck_2012&quot;&gt;[2]M. T. Wojnowicz, “The Ornstein-Uhlenbeck Process In Neural Decision-Making: Mathematical Foundations And Simulations Suggesting The Adaptiveness Of Robustly Integrating Stochastic Neural Evidence,” phdthesis, 2012. Available at: https://digital.lib.washington.edu:443/researchworks/handle/1773/21760&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>(Note) Tox workflow</title>
   <link href="https://blog.liam.kim/posts/2017/07/Tox-Workflow/"/>
   <updated>2017-07-17T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2017/07/Tox-Workflow</id>
   <content type="html">&lt;h2 id=&quot;what-do-i-need-to-install&quot;&gt;What do I need to install&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox&lt;/code&gt; : virtualenv for testing&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox-pyenv&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyenv&lt;/code&gt; plugin for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pytest&lt;/code&gt; : testing framework&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipreqs&lt;/code&gt; : generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requirements.txt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-do-i-need-to-do&quot;&gt;What do I need to do&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Checkout &lt;a href=&quot;https://github.com/audreyr/cookiecutter&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cookiecutter&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;create virtualenv using pyenv&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pyenv &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;some versions&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;tox tox-pyenv
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pyenv &lt;span class=&quot;nb&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_env&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;some versions&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; directory and change some settings&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Why I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&lt;/code&gt; directory?
        &lt;ul&gt;
          &lt;li&gt;Check Hynek’s &lt;a href=&quot;https://hynek.me/articles/testing-packaging/&quot;&gt;post&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;As the post says, modify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;example-of-executing-tox&quot;&gt;Example of executing tox&lt;/h2&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pyenv &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;3.8.2
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pyenv virtualenv &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; Python3.8 3.8.2 my_env
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;tox tox-pyenv
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pyenv &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;my_env 3.8.2
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;write code and tests then&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pipreqs &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tox
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Navier Stokes Equation Solver for Homogeneous Isotropic Turbulence</title>
   <link href="https://blog.liam.kim/posts/2017/07/Psuedo-spectral-method/"/>
   <updated>2017-07-17T12:00:00+09:00</updated>
   <id>https://blog.liam.kim/posts/2017/07/Psuedo-spectral-method</id>
   <content type="html">&lt;p&gt;(This content is originally written by &lt;a href=&quot;https://scholar.google.com/citations?user=8fMRupoAAAAJ&amp;amp;hl&quot;&gt;Kyongmin Yeo&lt;/a&gt;’s manual)&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;The spectral method is solving certain differential equation by some “basis function”, typically sinusoids with Fourier method.
With the Navier-Stokes equation, it can remove presssure term in N-S equation and solve viscous term analytically.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Removing pressure term is huge performance advantage&lt;/li&gt;
  &lt;li&gt;Accurate result because differential operator doesn’t depends on grid size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Only can be applied to periodic domain&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;governing-equation&quot;&gt;Governing Equation&lt;/h2&gt;

&lt;h3 id=&quot;navier-stokes-equation-to-rotational-form&quot;&gt;Navier-Stokes equation to rotational form&lt;/h3&gt;
&lt;p&gt;Original Navier-Stokes equation in &lt;strong&gt;convection form&lt;/strong&gt; is&lt;/p&gt;

\[\begin{align}
\dfrac{\partial u_i}{\partial t} &amp;amp;= -\dfrac{\nabla p}{\rho} - (u \cdot \nabla) u + \nu \nabla^2 u \\
\nabla \cdot u &amp;amp;= 0
\end{align}\]

&lt;p&gt;Using following vector identity,&lt;/p&gt;

\[\begin{align}
\dfrac{1}{2} \nabla (A \cdot A) = (A \cdot \nabla) A + A \times (\nabla \times A)
\end{align}\]

&lt;p&gt;The Navier-Stoke sequations in &lt;strong&gt;rotational form&lt;/strong&gt; can be obatained.
The reason is explained in the paper, &lt;em&gt;Numerical Simulation of Incompressible Flows Within Simple Boundaries. I. Galerkin (Spectral) Representations&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;The reason is that pseudospectral approximation to the rotation, rather than Reynolds stress, form of the nonlinear terms of the Navier~Stokes equations semiconserves (cf. &lt;a class=&quot;citation&quot; href=&quot;#Orszag1971&quot;&gt;[1]&lt;/a&gt;, Numerical simulation of incompressible flows within simple boundaries: Accuracy, Section 3) energy so that aliasing errors, although present, can not directly cause unconditional nonlinear instability&lt;/p&gt;&lt;cite&gt;&lt;a class=&quot;citation&quot; href=&quot;#Orszag1971a&quot;&gt;[2]&lt;/a&gt;&lt;/cite&gt;&lt;/blockquote&gt;

\[\begin{align}
\dfrac{\partial u_i}{\partial t} &amp;amp;= -\dfrac{\partial P}{\partial x_i} + H_i + \nu \nabla^2 u \\
\dfrac{\partial u_i}{\partial x_i} &amp;amp;= 0
\end{align}\]

&lt;p&gt;where&lt;/p&gt;

\[\begin{align}
P &amp;amp;= \dfrac{p}{\rho} + \dfrac{1}{2} u_j u_j \\
H_i &amp;amp;= \epsilon_{i,j,k} u_j \omega_k = u \times (\nabla \times u)
\end{align}\]

&lt;h3 id=&quot;removing-pressure-term&quot;&gt;Removing pressure term&lt;/h3&gt;

&lt;p&gt;The pressure Poisson equation can be obatained by taking divergence from Navier-Stokes equation in rotational form&lt;/p&gt;

\[\begin{align}
\nabla^2 P = \dfrac{\partial H_j}{\partial x_j}
\end{align}\]

&lt;p&gt;Expanding N-S equation and Poisson equation to Fourier space gives&lt;/p&gt;

\[\begin{align}
\dfrac{d \hat{u}_i }{d t} &amp;amp;= -i \kappa_i \hat{P} + \hat{H}_i - \nu \kappa^2 \hat{u}_i \\
-\kappa^2 \hat{P} &amp;amp;= i \kappa_j \hat{H}_j
\end{align}\]

&lt;p&gt;Combining two equation and then&lt;/p&gt;

\[\begin{align}
\dfrac{d \hat{u}_i }{d t} &amp;amp;= -i \kappa_i \left( -i \dfrac{\kappa_j}{\kappa^2} \hat{H}_j \right ) + \hat{H}_i - \nu \kappa^2 \hat{u}_i \\
\dfrac{d \hat{u}_i }{d t} &amp;amp;= -\dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i - \nu \kappa^2 \hat{u}_i
\end{align}\]

&lt;p&gt;where \( \kappa \) is a wavenumber. Final Navier Stokes equation is obtained without pressure term&lt;/p&gt;

\[\begin{align}
\dfrac{d \hat{u}_i }{d t} &amp;amp;= -\dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i - \nu \kappa^2 \hat{u}_i
\end{align}\]

&lt;h3 id=&quot;treating-viscous-term-analytically&quot;&gt;Treating viscous term analytically&lt;/h3&gt;

&lt;p&gt;To treat a viscous terms analytically, multiply following formula to Navier Stokes equation w/o pressure form&lt;/p&gt;

\[f(t) = e^{\nu \kappa^2 t}\]

&lt;p&gt;Then the equation becomes..&lt;/p&gt;

\[\begin{align}
\left[ \dfrac{d \hat{u}}{dt} + \nu \kappa^2 \hat{u}_j \right] f(t) &amp;amp;= \left[ - \dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i \right ] f(t) \\
f(t) \dfrac{d \hat{u}}{dt} + (\nu \kappa^2 f(t))\hat{u}_j &amp;amp;= \left[ - \dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i \right ] f(t) \\
f(t) \dfrac{d \hat{u}}{dt} + (\nu \kappa^2 e^{\nu \kappa^2 t})\hat{u}_j  &amp;amp;= \left[ - \dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i \right ] f(t) \\
f(t) \dfrac{d \hat{u}}{dt} + \left(\dfrac{d e^{\nu \kappa^2 t}}{dt}\right)\hat{u}_j  &amp;amp;= \left[ - \dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i \right ] f(t) \\
\dfrac{d \hat{u}_i f(t)}{dt} &amp;amp;= \left[ - \dfrac{\kappa_i \kappa_j}{\kappa^2} \hat{H}_j + \hat{H}_i \right ] f(t)
\end{align}\]

&lt;p&gt;this can be more simpler by introducing new term \(\widehat{NL}\)&lt;/p&gt;

\[\begin{equation}
\dfrac{d \hat{u}_i e^{\nu \kappa^2 t}}{dt} = \widehat{NL} e^{\nu \kappa^2 t}
\end{equation}\]

&lt;h3 id=&quot;time-discretization-by-rk3-method&quot;&gt;Time Discretization by RK3 method&lt;/h3&gt;

&lt;p&gt;For low-storage RK3 method (2-register, 3-stage, 3rd order), the coefficients are given by following table
&lt;a class=&quot;citation&quot; href=&quot;#Lundbladh:1999vy&quot;&gt;[3]&lt;/a&gt;, &lt;a class=&quot;citation&quot; href=&quot;#Yu1992&quot;&gt;[4]&lt;/a&gt;, &lt;a class=&quot;citation&quot; href=&quot;#Wray1990&quot;&gt;[5]&lt;/a&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;order&lt;/th&gt;
      &lt;th&gt;\(a_n\)&lt;/th&gt;
      &lt;th&gt;\(b_n\)&lt;/th&gt;
      &lt;th&gt;\(c_n\)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1st&lt;/td&gt;
      &lt;td&gt;8/15&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2nd&lt;/td&gt;
      &lt;td&gt;5/12&lt;/td&gt;
      &lt;td&gt;-17/60&lt;/td&gt;
      &lt;td&gt;8/15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3rd&lt;/td&gt;
      &lt;td&gt;3/4&lt;/td&gt;
      &lt;td&gt;-5/12&lt;/td&gt;
      &lt;td&gt;2/3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Assume equations are given by following form,&lt;/p&gt;

\[\dfrac{\partial Q}{\partial t} = R(Q)\]

&lt;p&gt;The low-storage RK3 method applied to the above equation using RK3 coefficients.&lt;/p&gt;

\[\begin{align}
Q^1 &amp;amp;= Q^n + \Delta t \left( \dfrac{8}{15} R^n \right)  \\
Q^2 &amp;amp;= Q^1 + \Delta t \left( \dfrac{5}{12} R^n  - \dfrac{17}{60} R^1\right) \\
Q^{n+1} &amp;amp;= Q^2 + \Delta t \left( \dfrac{3}{4} R^n  - \dfrac{5}{12} R^2\right)
\end{align}\]

&lt;p&gt;Before applying RK3 method to Navier-Stokes equation, apply low-storage RK3 method to reaction-diffusion equation&lt;/p&gt;

\[\begin{align}
\dfrac{\partial \psi}{\partial t} &amp;amp;= G + L \psi \\
\psi^{n+1} &amp;amp;= \psi^{n}  + a_n \Delta t G^n + b_n \Delta t G^{n-1} + (a_n + b_n) \Delta t \left(\dfrac{L \psi^{n+1} + L \psi^n}{2} \right)
\end{align}\]

&lt;p&gt;Then the Navier Stokes equation w/o pressure term can be discretized by above method&lt;/p&gt;

\[\dfrac{d \hat{u} e^{\nu \kappa^2 t}}{dt} = \widehat{NL} e^{\nu \kappa^2 t}\]

&lt;p&gt;Discretization of LHS&lt;/p&gt;

\[\begin{align}
LHS = \dfrac{\hat{u}^{n+1}_i e^{\nu \kappa^2 (t+a_n \Delta t + b_n \Delta t)} - \hat{u}^{n}_i e^{\nu \kappa^2 t}} {\Delta t}
\end{align}\]

&lt;p&gt;Discretization of RHS  (denoting RHS as \( G \))&lt;/p&gt;

\[\begin{aligned}
RHS &amp;amp;= \widehat{NL}^n e^{\nu \kappa^2 t} \\
&amp;amp;= a_n\widehat{NL}^n e^{\nu \kappa^2 t} + b_n\widehat{NL}^{n - 1} e^{\nu \kappa^2 (t - a_{n-1} \Delta t - b_{n-1} \Delta t)}
\end{aligned}\]

&lt;p&gt;Compensating \( e^{\nu \kappa^2 t} \) on both sides&lt;/p&gt;

\[\begin{align}
  \hat{u}^{n+1}_i e^{\nu \kappa^2 (a_n \Delta t + b_n \Delta t)} - \hat{u}^{n}_i =
    \begin{aligned}[t]
    &amp;amp; a_n \Delta t \widehat{NL}^n \\
    &amp;amp;+ b_n \Delta t \widehat{NL}^{n - 1} e^{\nu \kappa^2 (- a_{n-1} \Delta t - b\_{n-1} \Delta t)}
  \end{aligned}
\end{align}\]

&lt;p&gt;Finally&lt;/p&gt;

\[\begin{align}
\hat{u}^{n+1}_i =
  \begin{aligned}[t]
  &amp;amp;\left[ a_n \Delta t \widehat{NL}^n + \hat{u}^{n}_i \right ] e^{-\nu \kappa^2 (a_n + b_n) \Delta t} \\
  &amp;amp; + b_n \Delta t \widehat{NL}^{n - 1} e^{-\nu \kappa^2 (a_n + b_n + a_{n-1}+ b_{n-1}) \Delta t}
  \end{aligned}
\end{align}\]

&lt;p&gt;or&lt;/p&gt;

\[\begin{align}
\hat{u}^{n+1}_i &amp;amp;=
  \begin{aligned}[t]
  &amp;amp;\left[ a_n \Delta t \widehat{NL}^n + \hat{u}^{n}_i \right ] e^{-\nu \kappa^2 (c_n - c_{n+1}) \Delta t} \\
  &amp;amp; + b_n \Delta t \widehat{NL}^{n - 1} e^{-\nu \kappa^2 (c_{n-1} - c_{n+1}) \Delta t}
  \end{aligned}
\end{align}\]

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol class=&quot;bibliography&quot;&gt;&lt;li&gt;&lt;span id=&quot;Orszag1971&quot;&gt;[1]S. A. Orszag, “Numerical Simulation of Incompressible Flows Within Simple Boundaries. I. Galerkin (Spectral) Representations,” &lt;i&gt;Studies in Applied Mathematics&lt;/i&gt;, vol. 50, no. 4, pp. 293–327, 1971, doi: 10.1002/sapm1971504293.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;Orszag1971a&quot;&gt;[2]S. A. Orszag, “Numerical simulation of incompressible flows within simple boundaries: Accuracy,” &lt;i&gt;Journal of Fluid Mechanics&lt;/i&gt;, vol. 49, no. 1, pp. 75–112, 1971, doi: 10.1017/S0022112071001940.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;Lundbladh:1999vy&quot;&gt;[3]A. Lundbladh, S. Berlin, M. Skote, and C. Hildings, “An efficient spectral method for simulation of incompressible flow over a flat plate,” &lt;i&gt;TRITA-MEK Technical {\ldots}&lt;/i&gt;, 1999.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;Yu1992&quot;&gt;[4]S. T. Yu, Y. L. P. Tsai, and K. C. Hsieh, “Runge-Kutta methods combined with compact difference schemes for the unsteady Euler equations,” &lt;i&gt;NASA Technical Memorandum&lt;/i&gt;, Jan. 1992, Available at: http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19930006613.pdf&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span id=&quot;Wray1990&quot;&gt;[5]A. A. Wray, “Minimal storage time advancement schemes for spectral methods,” Jan. 1990.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;
</content>
 </entry>
 

</feed>
