<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Chaeho&apos;s Blog</title><description>채호야 놀자</description><link>https://blog.chaeho.dev/</link><language>ko</language><item><title>AutoEncoder와 생성모델</title><link>https://blog.chaeho.dev/posts/%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D/autoencoder%EC%99%80-%EC%83%9D%EC%84%B1%EB%AA%A8%EB%8D%B8/post/</link><guid isPermaLink="true">https://blog.chaeho.dev/posts/%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D/autoencoder%EC%99%80-%EC%83%9D%EC%84%B1%EB%AA%A8%EB%8D%B8/post/</guid><pubDate>Thu, 31 Jul 2025 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;!-- 그동안 포스트를 쓸 때 문체를 어떻게할까 고민했는데 아직도 어떻게 해야할지 모르겠다..&amp;lt;br&amp;gt;그래서 일단 여러가지 스타일의 글을 두루두루 써보고 결정하기로 했습니다ㅎㅎ --&amp;gt;&lt;/p&gt;
&lt;p&gt;생성모델에 대한 이야기 두 번째&lt;/p&gt;
&lt;p&gt;저번 포스트에서는 생성모델의 근간이 되는 이론인 &apos;매니폴드&apos;에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p&gt;어떤 규칙이 있는 고차원 데이터가 저차원 공간인 &lt;strong&gt;매니폴드/잠재공간&lt;/strong&gt; 에 매핑될 수 있는데,&amp;lt;br&amp;gt;
생성모델은 이 고차원 데이터를 잠재공간에 매핑시키는 것이 목표라고 설명드렸습니다.&lt;/p&gt;
&lt;p&gt;이번에는 오토인코더가 무엇인지, 오토인코더가 어떻게 고차원 데이터를 잠재공간에 매핑시키고 다시 복원하는지, 복원이 어떻게 생성과 연계되는지 알아보겠습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;AutoEncoder(오토인코더)는 신경망입니다.&amp;lt;br&amp;gt;
입력으로 들어온 데이터를 적은 차원으로 압축하고, 다시 복원하는 것을 목표로 설계 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;AutoEncoder&quot; /&gt;&lt;/p&gt;
&lt;p&gt;위는 오토인코더의 신경망 구조를 시각화한 사진입니다.&lt;/p&gt;
&lt;p&gt;노드의 수가 6개에서부터 점점 줄어들더니 bottleneck 부분에서 2개로 가장 적고, 그 다음부터는 점점 원래대로 다시 늘어나 6개가 되는 구조를 보이고 있습니다.&lt;/p&gt;
&lt;p&gt;6개에서 2개로 줄어드는 부분은 고차원 원본 데이터를 잠재공간에 매핑하는 과정이고, 2개에서 6개가 되는 부분은 잠재공간에 있는 데이터를 다시 원본 데이터로 복원하는 과정입니다.&lt;/p&gt;
&lt;p&gt;6개에서 2개가 되는 부분은 &lt;strong&gt;Encoder(인코더)&lt;/strong&gt; 라고 부르고, 2개에서 6개가 되는 부분은 &lt;strong&gt;Decoder(디코더)&lt;/strong&gt; 라고 부릅니다. &amp;lt;br&amp;gt;
2개인 부분은 &lt;strong&gt;잠재공간(latent space)&lt;/strong&gt; 라고 부릅니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Encoder(인코더)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-1.png&quot; alt=&quot;Encoder&quot; /&gt;&lt;/p&gt;
&lt;p&gt;빨간 부분이 &lt;strong&gt;Encoder(인코더)&lt;/strong&gt; 입니다.&lt;/p&gt;
&lt;p&gt;인코더는 고차원의 데이터를 점진적으로 차원을 줄여 최종적으로 저차원의 잠재공간에 매핑하는 역할을 합니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 원래는 이미지 데이터처럼 고차원 데이터였던 것도 인코더를 통과하고 나면 이미지의 주요 특징만 추출된 저차원 데이터로 바뀌게 됩니다. --&amp;gt;&lt;/p&gt;
&lt;p&gt;$$
\mathbf{x} \in \mathbb{R}^n \
\mathbf{z} = f_{\theta}(\mathbf{x}) \in \mathbb{R}^m, \quad m &amp;lt; n
$$&lt;/p&gt;
&lt;p&gt;$n$차원 원본 데이터 $\mathbf{x}$가 인코더 $f_{\theta}$에 입력되면 $n$보다 작은 $m$차원 잠재공간 위의 데이터인 $z$가 출력됩니다. &amp;lt;br&amp;gt;
여기서 $\mathbf{x}$와 $	\mathbf{z}$는 실수범위의 수를 가지는 벡터로 표현됩니다.&lt;/p&gt;
&lt;p&gt;이 과정을 &lt;strong&gt;Encoding(인코딩)&lt;/strong&gt; 한다고 표현합니다.&lt;/p&gt;
&lt;p&gt;제가 계속 &apos;잠재공간에 매핑한다&apos;라고 표현했던 부분도 이 부분입니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Latent Vector(잠재벡터)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;latentvector&quot; /&gt;&lt;/p&gt;
&lt;p&gt;인코더에서 나온 출력, $\mathbf{z}$입니다.&lt;/p&gt;
&lt;p&gt;$n$차원인 원본 데이터보다 더 작은 $m$차원으로 표현됩니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Decoder(디코더)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;Decoder&quot; /&gt;&lt;/p&gt;
&lt;p&gt;$$
\hat{\mathbf{x}} = g_{\phi}(\mathbf{z}) = g_{\phi}(f_{\theta}(\mathbf{x})) \in \mathbb{R}^n
$$&lt;/p&gt;
&lt;p&gt;디코더 $g_{\phi}(\mathbf{z})$는 인코더에서 나온 잠재벡터 $z$를 다시 원본에 가까운 $\hat{\mathbf{x}}$로 복원하는 역할을 합니다.&amp;lt;br&amp;gt;
$\hat{\mathbf{x}}$은 $\mathbf{x}$와 마찬가지로 $n$차원의 실수 범위의 백터값입니다.&lt;/p&gt;
&lt;p&gt;$\hat{\mathbf{x}}$은 $\mathbf{x}$와는 다르며, $	\mathbf{x}$에 근사시킨 값입니다.&lt;/p&gt;
&lt;p&gt;디코더의 입-출력 과정을 &lt;strong&gt;Decoding(디코딩)&lt;/strong&gt; 한다고 표현합니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이 오토인코더라는 신경망이 어떤 구조로 이루어져있는지는 어느정도 이해하셨을 겁니다.&lt;/p&gt;
&lt;p&gt;하지만 의문이 드는게 하나 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;차원이 줄어들고 다시 늘어났을 뿐인데!!! 어떻게 데이터의 주요 특징구조를 포함한 잠재공간을 만들고, 어떻게 복원하는거죠?!?!?!&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그 이유를 알기 위해서 학습 방법을 알아야합니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;학습 방법&lt;/h2&gt;
&lt;h3&gt;학습 목표&lt;/h3&gt;
&lt;p&gt;오토인코더의 학습 목표는 $\mathbf{x}$를 입력했을 때, 출력 $\hat{\mathbf{x}}$가 $\mathbf{x}$와 비슷해지도록(근사, approximation) 하는 것이 목표입니다.&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;text-align: center; font-size: 1.2em; font-weight: bold;&quot;&amp;gt;
학습 목표 : 입력값과 출력값이 같아지도록 하는 것
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;결국 데이터에 라벨링이 되어있을 필요가 없기에 오토인코더의 학습은 비지도학습 혹은 자기지도학습(self-supervised learning)으로 분류 됩니다.&lt;/p&gt;
&lt;h3&gt;Loss Function(손실함수)&lt;/h3&gt;
&lt;p&gt;손실함수도 학습 목표에 맞는, 특정 값에 근사할 수 있는 것을 사용합니다.&lt;/p&gt;
&lt;p&gt;대표적으로 MSE(평균제곱오차, Mean Squared Error)를 사용합니다.&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}(\mathbf{x}, \hat{\mathbf{x}}) = \frac{1}{n} \sum_{i=1}^{n} (x_i - \hat{x}_i)^2
$$&lt;/p&gt;
&lt;p&gt;MSE에 대해서는 다들 잘 아실거라고 생각합니다.&lt;/p&gt;
&lt;h3&gt;학습 절차&lt;/h3&gt;
&lt;p&gt;오토인코더의 학습 절차는 아래와 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;입력 $\mathbf{x}$를 인코더에 넣어 잠재벡터 $\mathbf{z} = f_\theta(\mathbf{x})$ 출력&lt;/li&gt;
&lt;li&gt;디코더를 통해 복원값 $\hat{\mathbf{x}} = g_\phi(\mathbf{z})$ 출력&lt;/li&gt;
&lt;li&gt;손실 함수 $\mathcal{L}(\mathbf{x}, \hat{\mathbf{x}})$ 계산&lt;/li&gt;
&lt;li&gt;손실을 최소화하도록 파라미터 $\theta$, $\phi$를 업데이트&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 절차를 반복하면서 오토인코더는 점점 학습데이터에 대한 복원을 잘하는 형태로 학습됩니다.&lt;/p&gt;
&lt;h3&gt;단순히 차원을 줄였는데 어떻게 특징을 학습하는가?&lt;/h3&gt;
&lt;p&gt;이전에 했던 질문입니다.&lt;/p&gt;
&lt;p&gt;오토인코더를 제대로 학습 시킨 경우에는 인코더의 출력 $\mathbf{z}$는 디코더가 복원할 수 있는 형태로 이루어져 있습니다.&lt;/p&gt;
&lt;p&gt;만약 디코더 없이 인코더만 학습 시켰다고 가정 해봅시다. 인코더의 출력은 어떤 의의를 가지게 될까요?&lt;/p&gt;
&lt;p&gt;애초에 디코더가 없기에 학습 목표를 위한 손실함수를 적용할 수 없을테고, 출력 결과물에 정답이 없게 될테고, 인코더의 출력은...&amp;lt;br&amp;gt;
원본 입력의 데이터와 관계 없는, 아무 의미 없는 그저 차원만 줄인 데이터가 될 것입니다.&lt;/p&gt;
&lt;p&gt;그럼 제대로 학습했다면요?&amp;lt;br&amp;gt;
바로 앞전에 설명했듯이 $\mathbf{z}$는 디코더가 복원할 수 있는 데이터이고, 제대로 학습했다면 디코더가 복원할 수 있는 $\mathbf{z}$가 인코더로 부터 잘 만들어질 것입니다.&lt;/p&gt;
&lt;p&gt;$\mathbf{z}$는 원본데이터를 압축하고 다시 복원하는 과정에서 다른 사람이 보기엔 의미없는 데이터라도 우리가 &lt;strong&gt;학습 시킨 디코더 관점에서는 의미 있는 데이터&lt;/strong&gt; 가 됩니다.&amp;lt;br&amp;gt;
왜냐하면, 디코더는 잠재벡터를 보고 원본 데이터를 추론할 수 있으니까요.&lt;/p&gt;
&lt;p&gt;그래서 $\mathbf{z}$에는 원본 데이터로 복원할 수 있는 주요한 특징들이 포함되는 것입니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;흐음..&lt;/p&gt;
&lt;p&gt;잠재벡터에서 이미지를 그대로 복원하는 이 디코딩 과정이 과연 &lt;strong&gt;생성&lt;/strong&gt; 이라고 할 수 있을까요?&amp;lt;br&amp;gt;
참고로 복원은 재구성(reconstruction)이라고도 부릅니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;mean of reconstruction&quot; /&gt;
&lt;img src=&quot;image-6.png&quot; alt=&quot;mean of generation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;복원(復原)은 말 그대로 복원이고, &lt;strong&gt;생성은 &apos;이전에 없던 데이터를 만드는 행위&apos;&lt;/strong&gt; 로 다르게 해석해야 합니다.&amp;lt;br&amp;gt;
단어 자체로도 다른 뜻이고, 특히 생성모델 관점에서는 엄연히 분리되는 개념이니 헷갈리시면 안됩니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이 오토인코더는 오로지 복원을 위해서 설계, 학습 되었습니다.&lt;/p&gt;
&lt;p&gt;그럼 이전에 없던 데이터를 만드는 것은 어떻게 할 수 있을까요? &amp;lt;br&amp;gt;
이전 포스트에서도 예시로 간략하게 설명했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;ex&quot; /&gt;&lt;/p&gt;
&lt;p&gt;잠재벡터의 값은 이미지를 복원하기 위한 특징값들이기 때문에 이 값들을 조정한다면 입력값과 다른 이미지가 출력으로 나오게 됩니다.&lt;/p&gt;
&lt;p&gt;이전에 없는 데이터가 나오도록 잠재벡터를 조정하여 디코딩한다면 이는 데이터가 &lt;strong&gt;&apos;생성&apos;&lt;/strong&gt; 된 것이라고 볼 수 있을 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;문제는 이 오토인코더는 오로지 복원을 위해서 설계되었기 때문에 잠재벡터를 그냥 아무렇게 조정한다고 인간에게 유의미한 결과가 나오기 힘들다는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-7.png&quot; alt=&quot;ae vs vae latent space visualization&quot; /&gt;&lt;/p&gt;
&lt;p&gt;위 사진은 오토인코더의 손실함수별 잠재공간의 데이터 분포에 대한 사진입니다.&amp;lt;br&amp;gt;
왼쪽이 방금 소개한 오토인코더에 복원(재구성)을 위한 손실함수만 사용했을 때의 분포입니다.&lt;/p&gt;
&lt;p&gt;여기서 만약 &apos;복원을 위한 손실함수만 사용한 오토인코더&apos;에서 어느 잠재벡터 값을 미세하게 이동시켜서 복원했을 때, 과연 그 학습데이터의 경향을 따르는 벡터는 유의미한 데이터일까요?&lt;/p&gt;
&lt;p&gt;아닐 가능성이 커보이는 것은 옆의 사진의 Only KL Divergence, Combination 그래프를 보면 이해할 수 있습니다.&lt;/p&gt;
&lt;p&gt;왼쪽의 Only Reconstruction Loss의 데이터 분포는 고르지 않습니다. 비어있는 부분도 많습니다.&lt;/p&gt;
&lt;p&gt;생성을 위해서 잠재벡터를 이동시켰다고 한들, 그곳이 학습시에 비어있는 공간이었다면 의미 없는 데이터가 나올 가능성이 큽니다.&lt;/p&gt;
&lt;p&gt;그래서 생성을 위해서는 이 잠재공간의 데이터분포를 고르고 촘촘하게 만들어야합니다.&lt;/p&gt;
&lt;p&gt;이 잠재공간의 데이터분포를 고르고 촘촘하게 만드는 방법은 다음 포스트에서 설명드리겠습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이번 포스트에서는 오토인코더가 무엇인지, 복원과 생성에 대한 기본 개념을 알아보았습니다.&lt;/p&gt;
&lt;p&gt;긴 글 읽어주셔서 감사합니다 :)&lt;/p&gt;
&lt;p&gt;아 그리고! 그동안 포스트를 쓸 때 문체를 어떻게할까 고민했는데 아직도 어떻게 해야할지 모르겠습니다... 굉장히 어렵네요.. &amp;lt;br&amp;gt;
그래서 일단 여러가지 스타일의 글을 두루두루 써보고 결정하기로 했습니다ㅎㅎ&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>매니폴드와 생성모델</title><link>https://blog.chaeho.dev/posts/%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D/%EB%A7%A4%EB%8B%88%ED%8F%B4%EB%93%9C%EC%99%80-%EC%83%9D%EC%84%B1%EB%AA%A8%EB%8D%B8/post/</link><guid isPermaLink="true">https://blog.chaeho.dev/posts/%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D/%EB%A7%A4%EB%8B%88%ED%8F%B4%EB%93%9C%EC%99%80-%EC%83%9D%EC%84%B1%EB%AA%A8%EB%8D%B8/post/</guid><pubDate>Sat, 26 Jul 2025 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번에는 내가 지금까지 인공지능에 대해서 공부하면서 가장 흥미롭다고 느낀 것을 간략하게 설명하려고 한다.&lt;/p&gt;
&lt;p&gt;이것에 대한 궁금증은 생성모델으로부터 시작된다...&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;흠.. 내가 쓰고 있는 이미지 생성모델의 출력은 768*768인데 이 정도 해상도 크기면 이미지를 뽑는 경우의 수가 무한할텐데.. 어떻게 저렇게 내가 원하는 이미지만 쏙쏙 뽑아낼까?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이런 질문은 어떻게 보면 간단하게 대답할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;그건 이미지 생성모델이 당신이 제공하는 조건에 맞게 여러 단계를 거쳐서 노이즈를 제거하면서 이미지를 만들어냈기 때문이에요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그런데 이런 답변은 내 궁금증을 해소하기 어려웠다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;아니!!! 그렇다면!! 어떻게 내가 제공한 조건에 맞게 &lt;strong&gt;필요한 노이즈&lt;/strong&gt; 만 제거한 것인가요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;내가 궁금했던 내용과 원하는 답변은 &lt;em&gt;&lt;strong&gt;매니폴드&lt;/strong&gt;&lt;/em&gt; 라는 개념에서 찾을 수 있었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;이미지 생성모델은 주어진 조건에 맞춰 어떤 노이즈를 제거해야 할지를 결정하는 &lt;strong&gt;매니폴드&lt;/strong&gt; 를 학습했기 때문입니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;픽셀이 한 개 있다고 해보자, 이 경우에는 1차원으로 픽셀 1개의 밝기를 모두 표현할 수 있을 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(0): 검정색, (0.5): 회색, (1): 하얀색
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 두 개는? 이 경우에는 2차원으로 밝기를 표현할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(0,0), (1,0), (0,1), (1,1), (0.5,0.5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 세 개는? 이 경우에는 3차원으로 밝기를 표현할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(0,0,0), (1,0,0), (1,1,1), (0.5,1,0.5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;픽셀들의 집합, 즉 이미지는 이런 식으로 &lt;strong&gt;벡터 공간의 한 점&lt;/strong&gt;에 대응될 수 있다.&lt;/p&gt;
&lt;p&gt;위에서 이야기했던 768*768 크기의 이미지도 벡터 공간의 한 점에 대응될 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;사람에게 3차원 공간에서 하얀색 이미지를 하나 뽑아달라고 요청했을 때, 어렵지 않게 바로 이미지를 뽑아낼 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(1,1,1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3차원 공간은 좌표계를 사용해서 직관적으로 이해할 수 있을 만큼 단순하기 때문이고, &apos;하얀색 이미지&apos;도 생각하기 어렵지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;그렇다면 아까의 예시로 사람에게 768 * 768 * 3의 공간에서 &apos;채호가 잠자고 있는 모습&apos;의 이미지를 뽑아달라고 하면 어떨까?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(0,1,0.2,0.3,0.9,1,0,0.2,0,0,0,0.1,0.7,0.8,0.9,0.3 ... 0.3)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1,769,472차원에서 &apos;채호가 잠자고 있는 모습&apos;의 이미지를 뽑기란 상당히 힘들 것이다... 머리가 터지는 수준을 넘어 계산이 불가능하다. &amp;lt;br&amp;gt;
사람은 물론 각각이 차원이 실수 범위인 이 이미지 데이터는 컴퓨터도 계산하기 힘들어 보인다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;여기서 &lt;strong&gt;매니폴드 가설&lt;/strong&gt; 은 1,769,472차원을 다 쓰지 않더라도 &apos;채호가 잠자고 있는 모습&apos;을 표현할 수 있는 방법을 제시한다.&lt;/p&gt;
&lt;p&gt;매니폴드 가설을 간략하게 설명하자면...&amp;lt;br&amp;gt;
&lt;em&gt;&lt;strong&gt;매니폴드란 고차원 공간에 존재하는 데이터가 사실은 더 낮은 차원에 놓여있다는 것이고,&lt;/strong&gt;&lt;/em&gt;&amp;lt;br&amp;gt;
&lt;em&gt;&lt;strong&gt;매니폴드 가설은 대부분의 자연적인 고차원 데이터가 저차원 구조인 매니폴드 위에 존재한다고 보는 가설이다.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;참고로 여기서 말하는 &apos;자연적&apos; 이라는 말은 가볍게 &apos;완전히 무작위가 아닌, 사람이 만들었거나, 어떤 구조를 따르는&apos; 정도로 이해하고 넘어가면 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이 매니폴드를 설명할 때 자주 사용되는 예시가 바로 &lt;strong&gt;스위스롤&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display: flex; justify-content: center;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1867d08d1e256e46.gif&quot; alt=&quot;미카롤빵&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;롤빵은 본질적으로 2차원 평면에서 존재하는 데이터를 돌돌 말아서 3차원 공간에 표현한 것이라고 볼 수 있다. &amp;lt;br&amp;gt;
3차원 공간에서 돌돌 말린 롤빵을 이걸 잘 펼쳐서 다시 2차원 공간 위에 놓을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;롤빵예시&quot; /&gt;&lt;/p&gt;
&lt;p&gt;왼쪽 사진은 스위스롤의 원본 공간(3차원 데이터)에서의 모습을 시각화한 것이고, 오른쪽 사진은 스위스롤의 본질을 담은 매니폴드 공간 위에서의 모습을 시각화 한 것이다.&lt;/p&gt;
&lt;p&gt;차원이 2차원에서 3차원으로 3차원에서 2차원으로 달라졌지만, 데이터의 본질적인 구조는 그대로 유지되었다. &amp;lt;br&amp;gt;
사진에서 점에 표시된 색깔로 구분할 수 있는데, 가까운 점들의 거리나 이웃관계는 유지되었다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;바로 이게 생성모델이 하려는 일이고, 생성모델 설계의 가장 근본적인 목표인데, &lt;strong&gt;고차원 공간에 분포된 데이터들에서 내재된 저차원 구조를 파악&lt;/strong&gt;하는 것이다.&amp;lt;br&amp;gt;
다른 말로는 &lt;strong&gt;고차원 데이터를 저차원의 매니폴드 공간에 효과적으로 대응되도록 학습시키는 것&lt;/strong&gt; 이 목표라고 볼 수 있겠다.&lt;/p&gt;
&lt;p&gt;생성모델을 다룰 때에는 매니폴드를 &lt;strong&gt;잠재공간(latent space)&lt;/strong&gt; 라고 부르고, 매니폴드 위에 놓여진 벡터 데이터를 &lt;strong&gt;잠재벡터(latent vector)&lt;/strong&gt; 라고 부른다.&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display: flex; justify-content: center;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;vae mnist 2d&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;
잠재공간의 흥미로운 특징을 알 수 있는 사례가 있어서 사진을 하나 가져왔다. &amp;lt;br&amp;gt;
위 사진은 mnist 데이터셋을 2차원 잠재공간에 매핑시켜 위치에 맞게 배치한 사진이다.&lt;/p&gt;
&lt;p&gt;28*28=764차원을 2차원으로 줄여 표현한 것도 흥미로운 일이지만..&lt;/p&gt;
&lt;p&gt;무엇보다 신기한건... &amp;lt;br&amp;gt;
데이터의 변화가 부드럽고 연속된다는 것이다.&lt;/p&gt;
&lt;p&gt;제일 위의 행을 보면 9에서 0으로 바뀌는 부분이 9에서 6에서, 6에서 0까지 스무스하게 바뀌는 것을 볼 수 있다. &amp;lt;br&amp;gt;
각 숫자가 명확한 부분이 있고, 무슨 숫자인지 알아보기 어려운 부분있는데, 표시한 곡선을 잘 보면 선 위에 놓인 데이터는 연속적으로 바뀌는 것을 볼 수 있다.&amp;lt;br&amp;gt;
이 곡선 위에 표시된 데이터들 중에서 무슨 숫자인지 알 수 없는 일부는 mnist 데이터셋에 실제로 없는 잠재공간이 생성한 데이터일 것이다.&lt;/p&gt;
&lt;p&gt;하지만 이 연속은 단순히 &lt;strong&gt;이미지 형태만의 연속&lt;/strong&gt; 을 의미하는 것이 아니다.&amp;lt;br&amp;gt;
잠재공간이 흥미로운건 잠재공간에서 움직인 데이터는 &lt;strong&gt;의미도 연속&lt;/strong&gt; 된다는 것이다.&amp;lt;br&amp;gt;
원본공간에서 잠재공간으로의 변환은 단순히 차원만 축소된 것으로 볼 것이 아니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이해하기 어려울 수 있을 수 있으니... 예를 들어서 사람 이미지를 표현하는 잠재벡터가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이 잠재벡터에는 사람을 표현하는 각종 정보들이 각각의 축으로 표현되어있다.&lt;/p&gt;
&lt;p&gt;예를 들어서 눈의 크기를 나타내는 축의 값을 바꾼다고 해보자, 값이 0인 사람은 눈이 매우 작을 것이고, 0.3인 사람은 눈이 작을 것이고, 0.5인 사람은 평균, 0.8인 사람은 크고, 1인 사람은 매우 클 것이다.&lt;/p&gt;
&lt;p&gt;(눈의 크기, 코의 크기)를 (1,1)로 하면 눈과 코가 매우 큰 사람이고, (0,1)로 하면 눈은 매우 작지만 코가 매우 큰 사람일 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;의미도 연속&lt;/strong&gt; 된다는 것은 이런 것이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;0_dwtvGrRWRAUJuZm4.gif&quot; alt=&quot;vae human interp&quot; /&gt;&lt;/p&gt;
&lt;p&gt;여기서 성별이나 인종 등을 표현하는 축의 값을 조정한다면 위의 사진과 같이 실제 학습된 값들 사이에 있는 존재하지 않는 데이터를 뽑아낼 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이런식으로 실제 사람의 얼굴을 표현하는 잠재공간에서 애니메이션 그림체 캐릭터의 얼굴을 뽑고 싶다고 했을 떄, 원하는 이미지가 출력 될까?&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display: flex; justify-content: center;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;명확하게 표현되지 않을 가능성이 높다. 왜냐하면 이 잠재공간에서의 데이터는 실제 사람의 얼굴을 표현하는 축으로만 이루어졌기 떄문이다.&lt;/p&gt;
&lt;p&gt;그렇기에 잠재공간에서 표현 가능한 정도는 어느 데이터를 학습 시키느냐에 따라 달려있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이해가 되었다면 어느정도 감이 잡힐 것이다.&lt;/p&gt;
&lt;p&gt;&apos;채호가 잠자고 있는 모습&apos;의 이미지는 잠재공간에 매핑될 수 있고, 이 데이터의 각각의 축은 &lt;code&gt;(골아떨어진정도, 팔을벌린정도, 엎드린정도, 피곤해보이는정도, ...)&lt;/code&gt; 로 표현될 것이다.&lt;/p&gt;
&lt;p&gt;그래서 이 매니폴드 가설을 따르는 모델을 설계/학습시켜 원본공간과 잠재공간의 이동을 자유롭게한다면, &apos;채호가 잠자고 있는 모습&apos;의 이미지를 쉽게 만들 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;게다가 간단한 수정으로 의미적으로 자연스러운 &apos;대머리 채호가 잠자는 모습&apos;도 만들 수 있을 것이다. ~내머리카락..하..~&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이번에는 생성모델이 어떻게 고차원 데이터를 만들어낼 수 있는지 그 근간을 알아보았다.&lt;/p&gt;
&lt;p&gt;다음에는 실제 신경망 모델의 예시를 들어서 어떻게 이미지 데이터를 잠재공간에 매핑시키고 다시 복원하는지 알아보겠다.&lt;/p&gt;
&lt;p&gt;다음은 오토인코더에 대한 글로 돌아오겠다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;잠재공간에 대해서 처음 공부할 때 아래의 게시물을 참고하였습니다. 좋은 글 작성해주셔서 감사합니다 (꾸벅)&lt;/p&gt;
&lt;p&gt;https://arca.live/b/alpaca/106037905&lt;/p&gt;
&lt;p&gt;제 글을 읽고서 잘 이해가 안되는 분이 있다면, 이 글도 같이 읽어보시면 도움이 될 것입니다.&lt;/p&gt;
</content:encoded></item><item><title>BFS를 사용해서 위키 크롤링하기</title><link>https://blog.chaeho.dev/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/bfs%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EC%9C%84%ED%82%A4-%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%98%EA%B8%B0/post/</link><guid isPermaLink="true">https://blog.chaeho.dev/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/bfs%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EC%9C%84%ED%82%A4-%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%98%EA%B8%B0/post/</guid><pubDate>Fri, 25 Jul 2025 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;RAG에 대해서 공부하다가 문득 그런 생각이 들었다.&lt;/p&gt;
&lt;p&gt;&quot;흠.. 내가 원하는 도메인의 정보를 다 때려 넣을 수 있으면 좋겠는데...&quot;&lt;/p&gt;
&lt;p&gt;만약 내가 원하는 도메인의 데이터만 가져올 수 있다면... 내가 즐기는 게임, 애니메이션, 만화의 IP에 특화된 RAG를 구축할 수 있지 않을까하는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;가장 먼저 떠오른건 &lt;strong&gt;위키(Wiki)&lt;/strong&gt; 였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위키피디아나 나무위키와 같은 위키는 문서 양이 방대해서, 대부분의 경우에 어느 도메인이건 가리지 않고 자세하게 설명되어있다.&lt;/p&gt;
&lt;p&gt;그렇다고 위키에 등록되어있는 문서들 중에서 내가 원하는 도메인의 모든 문서를 일일이 하나하나 다 수작업으로 수집하는 것은 너무 오래걸리고 힘든 일이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;나무위키 예시&quot; /&gt;&lt;/p&gt;
&lt;p&gt;위 사진과 같이 하나의 게임에 대해서 수 많은 문서들이 존재하는데 이것을 수작업으로 작업한다는건... &lt;s&gt;끔찍하다&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이러한 노동집약적 문제를 해결하기 위해서 문서 데이터를 수집하는 것을 &lt;strong&gt;셀레니움(Selenium)을 사용한 동적 크롤링으로 자동화&lt;/strong&gt; 하기로 했다.&lt;/p&gt;
&lt;p&gt;근데... 크롤링을 하는데 어떻게 해야할까? 현재로 정해진 기준은 아무 것도 없다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;그러던 도중 알고리즘 수업시간에 배?운 그래프 탐색 알고리즘들이 생각났다. &lt;s&gt;사실 이미 알고 있었다&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;여기서 핵심 아이디어가 나오게 된다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;위키 자체를 거대한 그래프로 보고, 문서를 노드로, 문서에 있는 하이퍼링크를 간선으로 취급하자!&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;도메인의 root가 되는 문서를 하나 정해주면, 그 하이퍼링크에 연결된 문서들은 root 문서와 관련된 문서들이기 때문에 도메인 동일성이 어느정도 보장 된다고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;그래프 탐색 알고리즘에는 크게 BFS(넓이 우선 탐색)과 DFS(깊이 우선 탐색)이 있는데,&lt;/p&gt;
&lt;p&gt;지금의 경우에는 &lt;strong&gt;BFS를 적용&lt;/strong&gt; 하는 것이 맞다고 생각했다. 아래 사진은 학교에서 발표했을 때 사용한 자료의 일부이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-1.png&quot; alt=&quot;BFS vs DFS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;BFS에서 max-depth를 2로 설정했을 때에는 PARA에 관련된 문서들이 골고루 수집된 것을 볼 수 있는데,&lt;/p&gt;
&lt;p&gt;DFS에서 max-depth를 2로 설정했을 때에는 인공지능에 관련된 문서들만 편향적으로 수집 되었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이것이 내가 BFS를 선택한 이유이다. 깊이를 우선으로 할 경우에는 탐색 경로가 특정 가지로만 빠질 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;특정 가지로 빠지게 된다면, 내가 원하는 root 문서의 도메인의 대해서 폭넓게 데이터를 수집할 수 없는 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;아래는 내가 구현한 BFS로 위키 문서를 크롤링하는 코드이다.&lt;/p&gt;
&lt;p&gt;실제 작동하는 코드는 아니고 보기 좋게 추상화를 시킨 코드이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def bfs_crawl(self, start_url, start_title, max_depth=2):

    queue = [(start_title, start_url)]
    results = []

    for depth in range(max_depth + 1):
        if not queue:
          break

        current_level = list(set(queue))
        queue.clear()

        for title, url in current_level:
            print(f&quot;[Depth {depth}] {title}&quot;)
            self.driver.get(url)

            # 문서 본문 가져오기
            content = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.XPATH, &apos;본문 XPath&apos;))
            )

            # 표 및 광고 요소 제거
            self.driver.execute_script(&quot;...표 제거 JS...&quot;, content)
            self.driver.execute_script(&quot;...광고 제거 JS...&quot;, content)

            # 문서 텍스트 저장
            results.append((title, content.text))

            # 내부 링크 수집
            links = content.find_elements(By.XPATH, &apos;.//a&apos;)
            for link in links:
                linked_title = link.get_attribute(&apos;title&apos;)
                linked_url = link.get_attribute(&apos;href&apos;)

                if self._is_valid_link(linked_title, linked_url, title):
                    queue.append((linked_title, linked_url)) # 큐에 탐색할 노드 추가

    return results

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기본적인 BFS 구현에서 그래프가 위키로 바뀌었고, 위키 문서의 내부의 데이터를 처리하는 부분 말고는 크게 다른 것은 없다.&lt;/p&gt;
&lt;p&gt;&lt;s&gt;방문노드처리하는거깜빡ㅎㅎ&lt;/s&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;코드는 실제로 잘 작동했고, 본래 목적에 맞게 RAG 파이프라인까지 구축하도록 간단한 프로그램을 만들었다.&lt;/p&gt;
&lt;p&gt;아래는 github repo와 작동 영상이다.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;GonGe1018/Wiki-RAG&quot;}&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/M9kzcWVkmiE&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;다 만들고 보니 몇가지 개선할 문제점이 발견 되었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h4&gt;큐에 추가되는 문서가 root 문서의 도메인과 동일함을 보장할 수 없다.&lt;/h4&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이유 : 하이퍼링크를 농담을 위해서 쓰기도하고, 동음이의어와 혼동을 방지하기 위해서 하이퍼링크를 사용하기 한다.&lt;/li&gt;
&lt;li&gt;해결 방법 : LLM을 사용해서 같은 도메인인지 판단시키는 방법이 있을 것이다. 물론 이 방법은 LLM에게 학습되지 않은 도메인의 경우 혼동이 일어날 수 있겠지만, 적어도 완전 관련 없는 문서는 배제할 수 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h4&gt;depth가 깊어지면 탐색이 너무 느려진다.&lt;/h4&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이유 : 셀레니움을 사용한다는 것은 실제 브라우저를 켜서 동적으로 크롤링하는 것을 의미하기에, 어쩔 수 없는 문제이다. 게다가, 위키 사이트 자체에서 봇을 사용한 데이터 수집을 제한하기 위해서 각종 장치를 도입해서 더 느려지는 것도 있을 것이다. 1번 문제와 중복으로 도메인과 관련 없는 문서가 마구마구 큐에 추가 되어서 느려지는 이유도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;일단 확실하게 필요한 것은...  &lt;s&gt;깜빡한 방문노드 처리&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;큐에 추가되는 문서의 도메인 동일성을 판단하는 것이다. 실제로 탐색을 돌려봤을 때 농담과 부연 설명을 위한 문서가 너무 많이 추가 되어서 수집된 데이터의 도메인이 흐려지는 문제가 발생했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;수업시간에 배운 내용을 내 필요한 용도에 맞게 고민하고, 가공해서 사용한 경험이라서 좋은 경험을 했다고 생각한다.&lt;/p&gt;
&lt;p&gt;오랜만에 재밌는 미니 프로젝트였다.&lt;/p&gt;
</content:encoded></item><item><title>컴퓨터가 미분하는 방법: 자동 미분(automatic differentiation)</title><link>https://blog.chaeho.dev/posts/%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D/%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B0%80-%EB%AF%B8%EB%B6%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%9E%90%EB%8F%99%EB%AF%B8%EB%B6%84/post/</link><guid isPermaLink="true">https://blog.chaeho.dev/posts/%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D/%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B0%80-%EB%AF%B8%EB%B6%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%9E%90%EB%8F%99%EB%AF%B8%EB%B6%84/post/</guid><pubDate>Thu, 24 Jul 2025 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최적화 분야에서는 미분이 필수적으로 사용된다.&lt;br /&gt;
최솟값을 구함으로써 최적화를 위한 비용을 줄일 수 있는 경우가 많기 때문이다.&lt;/p&gt;
&lt;p&gt;최적화의 대표적인 예로 &lt;strong&gt;경사하강법&lt;/strong&gt;이 있는데, 미분을 어떻게 사용하는지 잘 보여주는 예시이다.
&lt;img src=&quot;./gd.gif&quot; alt=&quot;GradientDescent&quot; /&gt;
경사하강법은 신경망의 $(\text{정답값} - \text{예측값})$을 나타내는 손실 함수의 최소값을 찾는 것이 목적이다.&lt;br /&gt;
손실 함수의 최소가 갖는 의의는 $(\text{정답값} - \text{예측값})$이 최소일 때, 즉 예측이 가장 정확할 때를 의미한다.&lt;br /&gt;
그래서 손실 함수의 최소를 찾는 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면 손실 함수의 최소를 어떻게 찾을까?&amp;lt;br&amp;gt;
우리는 이미 수학 II, 미적분 시간에 함수의 최소를 구하는 방법을 배웠다. 기울기가 $0$이면서 좌, 우의 $y$값보다 작은 지점이 최소가 된다.&amp;lt;br&amp;gt;
이 원리는 경사하강법에도 똑같이 적용된다.&lt;/p&gt;
&lt;p&gt;그런데 한 가지 문제가 있다.&lt;/p&gt;
&lt;p&gt;우리는 기울기가 $0$인 지점을 구할 때 인수분해하거나 식을 조작하는 방법으로 찾았지만, 컴퓨터에 같은 방식을 적용하기에는 경우의 수가 너무 많아 그렇게 할 수 없다.&lt;br /&gt;
예를 들어 $y&apos; = x^2 + 2x + 1$을 완전제곱식으로 고쳐서 바로 $y&apos; = 0$이 되는 $x$를 구하는 건 계산에 익숙해진 인간만 가능한 일이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;그래서 컴퓨터로 미분을 할 때에는 다른 방법을 사용해야 한다.&lt;/p&gt;
&lt;p&gt;크게 두 가지 예시가 있는데,&lt;br /&gt;
첫 번째는 우리가 이미 고등학교 수업 시간에 도함수의 정의에서 배운 &lt;code&gt;수치 미분(numerical differentiation)&lt;/code&gt; 을 프로그램으로 구현하는 것이고, 두 번째는 &lt;code&gt;자동 미분(automatic differentiation)&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;수치 미분을 프로그램으로 구현하는 건 매우 간단하다.&lt;br /&gt;
$$
\frac{f(x+h) - f(x-h)}{2h}
$$
여기서 $h$를 $0.001$과 같이 매우 작은 값으로 두고 계산하면, $\lim$ 기호 없이도 미분을 근사할 수 있다.&lt;/p&gt;
&lt;p&gt;그런데 이러한 수치 미분 구현에는 심각한 문제점이 있다.&lt;br /&gt;
$h$를 너무 크게 두면 값이 부정확할 수 있고, $h$를 너무 작게 두면 부동소수점 특성상 오차가 발생한다.&lt;br /&gt;
또한, 함수의 변수 개수(차원)가 늘어날수록 계산 복잡도가 기하급수적으로 증가한다.&lt;/p&gt;
&lt;p&gt;이러한 문제를 해결한 것이 자동 미분이다.&lt;br /&gt;
자동 미분은 함수를 가장 기본적인 연산의 합성 형태로 바꿔, 연쇄 법칙(chain rule)을 활용해서 미분하는 방식이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이해하기 어려울 수 있으니 예시를 설명하겠다.&lt;/p&gt;
&lt;p&gt;예를 들어 함수 $f(x) = x^2 + x$ 가 있다고 하자.&lt;br /&gt;
이 함수를 다음과 같이 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;$f(x) = \text{add}(x^2, x) = \text{add}(\text{mul}(x, x), x)$&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./computational-graph.png&quot; alt=&quot;연산 그래프&quot; /&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 함수의 연산 관계를 나타낸 그래프를 활용하면 자동 미분에 대한 직관적 이해가 쉽다.&amp;lt;br&amp;gt;
자동 미분에는 forward와 backward가 있지만, 여기서는 &lt;strong&gt;backward(역전파)&lt;/strong&gt; 에 대해 설명한다.&amp;lt;br&amp;gt;
기본 아이디어는 함수의 최종 노드 $E$에서 그래프의 끝에 있는 $A, B, D$까지 chain rule로 연결해서 미분하는 것이다.&lt;/p&gt;
&lt;p&gt;$x$에 $2$가 들어간다고 생각하고, $E$부터 시작해보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;가장 먼저 $E$를 $E$에 대해 미분하면
$$
\frac{d E}{d E} = 1
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;그 다음 $C$와 $D$에 대해 미분한다.
$$
\frac{d E}{d C} = \frac{d E}{d E} \cdot \frac{\partial E}{\partial C} = 1 \times 1 = 1
$$
$$
\frac{d E}{d D} = \frac{d E}{d E} \cdot \frac{\partial E}{\partial D} = 1 \times 1 = 1
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이어서 $C$의 이전 노드 $A$와 $B$에 대해 미분한다.&lt;br /&gt;
$C = A \times B$이므로
$$
\frac{\partial C}{\partial A} = B = 2, \quad \frac{\partial C}{\partial B} = A = 2
$$
$$
\frac{d E}{d A} = \frac{d E}{d C} \cdot \frac{\partial C}{\partial A} = 1 \times 2 = 2
$$
$$
\frac{d E}{d B} = \frac{d E}{d C} \cdot \frac{\partial C}{\partial B} = 1 \times 2 = 2
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;모든 미분이 끝났으므로,
$$
\frac{d E}{d A} + \frac{d E}{d B} + \frac{d E}{d D} = 2 + 2 + 1 = 5
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;실제 미분값 $f&apos;(x) = 2x + 1$에서 $f&apos;(2) = 5$가 나오므로, 결과가 동일함을 확인할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;이 예시에서는 덧셈, 곱셈만 사용되었지만 실제로는 $\exp$, $\log$의 미분부터 ReLU, Sigmoid 같은 복잡한 함수의 미분도 구현해야 한다.&lt;/p&gt;
&lt;p&gt;자동 미분은 이러한 연산을 미리 구현해두고 필요할 때 가져다 쓰는 식으로 동작한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;::github{repo=&quot;GonGe1018/python-differentiation&quot;}&lt;/p&gt;
&lt;p&gt;위 repo에서 간단한 자동 미분 구현체를 볼 수 있다!&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>small-world와 HNSW 알고리즘</title><link>https://blog.chaeho.dev/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/small-world%EC%99%80-hnsw-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/post/</link><guid isPermaLink="true">https://blog.chaeho.dev/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/small-world%EC%99%80-hnsw-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/post/</guid><pubDate>Thu, 24 Jul 2025 14:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;원본은 2025년 7월 16일에 작성되었습니다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이전에 RAG에 대해서 공부를 하다가 메타에서 만든 벡터 검색 라이브러리, FAISS를 사용한 적이 있다.&amp;lt;br&amp;gt;
나는 내가 사용하는 기술이 어떻게 동작하는지 이해하지 못하면 불안해하는 타입이라 FAISS와 라이브러리가 어떻게 벡터를 검색하는지 열심히 공부했다.&lt;/p&gt;
&lt;p&gt;그중에서 몇가지 오늘날 벡터 검색을 대표하는 이론과 알고리즘을 소개하려고 한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“나랑 연예인은 몇번의 인연을 거쳤을 때 닿게 될까?”&lt;/strong&gt; 이런 생각을 누구나 한 번쯤 해보았을 것이다.&amp;lt;br&amp;gt;
연예인과 나는 절대로 닿을 수 없을 것 같지만, 사실 알고보면 아무리 많아도 6단계만 거치면 연예인에게 닿을 수 있다.&lt;/p&gt;
&lt;p&gt;더 극적인 예시를 들어보겠다. 선린인터넷고의 평범한 학생인 유채호와 미국 대통령은 사실 6단계만 거치면 만날 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. 선린인터넷고등학교 소프트웨어과 학생 유채호
2. 유채호를 지도하는 선린인터넷고등학교의 교사A
3. 교사A의 오랜친구 대학 교수B
4. 교수B와 학회에서 만난 미국인 교수C
5. 교수C와 정책을 연구한 미국 고위공무원D
6. 고위공무원D의 보고를 받는 미국 대통령
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;유채호 학생은 미국 대통령이 아니라 어느 국가의 원수와도 닿을 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이를 설명하는 이론으로 &lt;code&gt;small-world network&lt;/code&gt;가 있다.
&lt;img src=&quot;./smallworld.png&quot; alt=&quot;Small World Network&quot; /&gt;
만약 인간 관계를 나타낸 그래프가 있다고 해보자, 이 그래프에서 사람 한명은 균일하게 근처의 10명만 알고 지낸다. 근처의 사람들만 알고 지내기 때문에 멀리 있는 사람과 닿으려면 상당한 간선을 거쳐야한다.&lt;/p&gt;
&lt;p&gt;만약 여기서 랜덤하게 몇명의 사람들을 멀리 있는 사람들과 연결 시키면 어떻게 될까? 아주 멀리 떨어져 있더라도 적은 수의 간선을 통해서 목표한 곳까지 도달할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;이것을 고속도로와 지방도로 이해하기 쉬운데, &lt;strong&gt;멀리 있는 사람들과 연결된 간선은 일종의 고속도로가 된 셈이고, 가까운 사람끼리 이어진 간선은 지방도&lt;/strong&gt;로 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이것은 아무리 거리가 먼 노드라고 할지라도 중간에 빠르게 이어주는 몇개의 고속도로를 찾으면 빠르게 목표까지 도달 할 수 있다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;이러한 형태의 네트워크를 small-world network라고 부른다. 이 내용은 1997년 네이쳐지에 게재된 Collective dynamics of‘small-world’ networks 논문에서 설명되었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;이러한 small-world network 이론은 벡터 검색에 사용될 수 있는데 대표적인 예시가 &lt;code&gt;HSNW(Hierarchical Navigable Small World graphs)&lt;/code&gt;알고리즘이다.&lt;/p&gt;
&lt;p&gt;이 알고리즘은 small-world 이론을 활용한 알고리즘으로, 마찬가지로 그래프 기반으로 검색을 하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./hnsw.png&quot; alt=&quot;HNSW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;대표적인 특징은 그래프가 &lt;strong&gt;여러 계층(layer)로 구성&lt;/strong&gt;되어 있는 것이다. 이러한 층은 서로 연결 되어있다.&lt;/p&gt;
&lt;p&gt;가장 위의 층의 그래프는 이전에 설명한 small-world 예시처럼 고속도로의 역할을 한다. 각 벡터 노드들을 랜덤하게 이어서 각 벡터들 간에 거리가 긴 고속도로가 생기게 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위층부터 아래층으로 내려갈 수록 그래프에 이어진 벡터 노드들은 많아지고 간선은 더 촘촘해져 고속도로에서 지방도에 가까워진다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;HNSW 알고리즘의 검색을 쉽게 비유해보겠다. 예를 들어서 서울에서 부산에 있는 자갈치시장을 찾아가는 경우는 아래와 같이 표현할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. 서울역에서 KTX로 부산역까지 이동 (고속 이동)
2. 부산역에서 지하철로 남포역까지 이동 (좀 더 촘촘한 이동)
3. 남포역에서 도보로 자갈치시장까지 이동 (정밀하게 최종 목적지 도달)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 비유는 아래와 같은 설명에 대응될 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. layer2에서 검색 대상과 가까운 노드로 이동한다
2. layer1에서도 한번 더 검색 가까운 노드로 이동한다.
3. layer0에서 검색 검색 노드에 근사하여 도달한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;HNSW 알고리즘은 이처럼 계층적 small-world 그래프를 빌드하여 대규모 데이터에도 적은 단계로 빠르게 검색 대상에 도달 할 수 있다.&lt;/p&gt;
&lt;p&gt;또한, 속도와 정확도를 모두 잡은 대표적인 알고리즘으로써, FAISS 이외에도 대부분의 벡터DB의 검색 알고리즘으로 사용되고 있다.&lt;/p&gt;
</content:encoded></item><item><title>블로그를 만들었습니다</title><link>https://blog.chaeho.dev/posts/%EC%9D%BC%EA%B8%B0-%EB%B0%8F-%EA%B8%B0%ED%83%80/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4/post/</link><guid isPermaLink="true">https://blog.chaeho.dev/posts/%EC%9D%BC%EA%B8%B0-%EB%B0%8F-%EA%B8%B0%ED%83%80/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4/post/</guid><description>제가 블로그를 만든 이유는 말이죠..</description><pubDate>Wed, 23 Jul 2025 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2025년 7월 24일 오전 1시 역사적인 순간을 맞이했습니다.&amp;lt;br&amp;gt;
드디어 제게도 블로그가 생긴 것입니다.&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;사실 고민을 많이 했습니다. 고3씩이나 되어서 공부할 시간을 할애해서까지 인터넷에 글을 쓰는 게 맞는 건지 고민했습니다.&amp;lt;br&amp;gt;
하지만 결국 블로그를 만들게 되었고 확신한 데에는 확신할 만한 확실한 이유가 있었습니다.&lt;/p&gt;
&lt;p&gt;평소에 글을 쓰는 것을 좋아하는 저는 제가 공부한 내용이나 각종 잡생각을 어딘가에 꼭 업로드하거나 메모 해두곤 합니다.&amp;lt;br&amp;gt;
문제는 그렇게 열심히 쓴 글들이 각종 플랫폼에 파편화되어 흩뿌려져 나중에 다시 찾아보기 어렵다는 것입니다.&lt;/p&gt;
&lt;p&gt;그래서 블로그를 만들어서 제가 쓴 글들을 한곳으로 모으고자 한 것입니다.&amp;lt;br&amp;gt;
시간이 더 지나서 제가 열심히 썼던 각종 글들이 잊혀지기 전에 지금이라도 아카이브하려고 합니다.&lt;/p&gt;
&lt;p&gt;기존에 쓴 글을 아카이빙할 때에는 원본이 언제 작성되었는지도 같이 표기하겠습니다.&lt;/p&gt;
&lt;p&gt;또, 시간이 날 때마다 제가 공부했던 내용들을 추가로 정리해서 업로드하겠습니다.&amp;lt;br&amp;gt;
제대로 공부한 것이 맞는지 검토하는 작업은 언젠간 꼭 해야 하는 과업이기에 블로그를 만든 지금... 그냥 하겠다고 선언하겠습니다!&lt;/p&gt;
</content:encoded></item></channel></rss>