<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>태훈의 Deep Learning</title>
        <link>https://velog.io/</link>
        <description>👋 인공지능을 통해 다음 세대가 더 나은 삶을 살도록</description>
        <lastBuildDate>Thu, 25 Jul 2024 12:15:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>태훈의 Deep Learning</title>
            <url>https://velog.velcdn.com/images/pre_f_86/profile/8f7fafe6-fa9e-48ea-b498-257656ed7e23/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 태훈의 Deep Learning. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pre_f_86" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[확률과 생성형 모델에 대한 회고]]></title>
            <link>https://velog.io/@pre_f_86/%ED%99%95%EB%A5%A0%EA%B3%BC-%EC%83%9D%EC%84%B1%ED%98%95-%EB%AA%A8%EB%8D%B8%EC%97%90-%EB%8C%80%ED%95%9C-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@pre_f_86/%ED%99%95%EB%A5%A0%EA%B3%BC-%EC%83%9D%EC%84%B1%ED%98%95-%EB%AA%A8%EB%8D%B8%EC%97%90-%EB%8C%80%ED%95%9C-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 25 Jul 2024 12:15:58 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>딥러닝 관련 공부를 하다가 연구원 인턴이 되었고, 어느새 정직원이 되어 생성모델과 관련된 지식을 접하게 되었다. 깊게 공부는 하지 않았지만 딥러닝을 하며 느낀 점은 <strong>&#39;수학이 그렇게 필요하지 않았다&#39;라는 착각</strong>을 가지고 있었고, 생성모델을 보면서 이 착각은 <strong>완전히 잘못되었다고 느껴졌다.</strong></p>
<p>생성모델에 대해 공부하고 논문을 보면 수식이 나오는데 이걸 볼때마다 진짜 큰 거부감이 느껴지며 참고 읽는다해도 이해하지 못해 좌절감만 느껴진다.</p>
<p>생성모델하면 기본적인 것이 무엇이냐고 누군가 물어본다면 <strong>&#39;확률&#39;</strong>이라는 답이 바로 나올 것 같다.</p>
<p><strong>그만큼 생성모델에 관련된 논문을 볼때마다 확률변수, 확률분포 등 확률이란 단어만 해도 수도 없이 보게 되었고, 이 마저도 쉽게 이해되지 않았다.</strong></p>
<p>앞으로도 계속 보게 될 확률에 대해 이해를 하고 싶어 계속 생각하다 보니 무언가를 깨닫게 되었다.</p>
<p>물론 하루 아침에 논문의 수식을 이해할 수 있는 수준에 도달한 것은 아니지만, 나와 같은 어려움을 겪는 사람들이 있을 것 같아 이 글을 쓰게 되었다.</p>
<h1 id="확률의-예시">확률의 예시</h1>
<p>지금까지 대충 이해한 확률은 <strong>&#39;특정 사건이 발생할 가능성&#39;</strong> 정도로만 이해하였고 실제 정의도 그렇다. </p>
<p>이에 대한 흔한 예시로 동전 던지기가 있다.</p>
<blockquote>
<p><strong>동전의 앞면이 나올 확률은 1/2야, 앞면과 뒷면 중에 하나가 나올 확률이니까.</strong></p>
</blockquote>
<p>확률과 통계를 공부하면서 정말 많이 들어본 간단하고 직관적인 예시지만 나에게는 큰 독이 되었다.</p>
<blockquote>
<p><strong>사건은 앞면 혹은 뒷면이고, 1/2는 가능성이구나!</strong></p>
</blockquote>
<p>여기서 큰 문제점은 사건과 가능성이라는 <strong>정의에만 집중</strong>을 한다는 것이고, 이 이상의 사건과 가능성의 <strong>관계에 대해서는 한 번도 생각하지 않았다는 것</strong>이다.</p>
<h1 id="확률-그리고-함수">확률, 그리고 함수</h1>
<p>확률을 배울 때 이미 확률 질량 함수, 확률 밀도 함수와 같이 &#39;함수&#39; 라는 개념을 많이 들어왔지만, <strong>그동안 &#39;정의&#39;에만 집중을 해왔던 나에게 &#39;함수&#39;라는 단어는 크게 중요하지 않아졌다.</strong></p>
<p>그저 &#39;A라는 사건이 일어날 확률&#39;으로만 해석해왔고 이게 함수라는 개념이라고는 생각하지 않았으며, 실제로 큰 문제가 되진 않았었다. (고등학교때 봐왔던 확률 문제에서는 사건 자체를 묘사하는 문제가 많았었다...)</p>
<blockquote>
<p><strong>확률은 함수로 표현이 가능한 것이다.</strong></p>
</blockquote>
<p>확률을 함수로 이해하면 확률의 개념을 더 깊이 이해할 수 있다. <strong>함수는 입력과 출력 간의 관계를 나타낸다. 확률 함수는 어떤 사건이 발생할 확률을 나타내는 함수이다.</strong> 이는 특정 사건이 일어날 가능성을 함수로 나타낸 것인데, 이 함수가 주어진 입력(사건)에 대해 출력을 확률로 반환한다는 의미이다.</p>
<p>예를 들어, 확률 질량 함수(Probability Mass Function)는 이산 확률 변수의 각 가능한 값에 대해 그 값이 일어날 확률을 나타낸다. 확률 밀도 함수(Probability Density Function)는 연속 확률 변수의 각 값에 대해 그 값이 일어날 가능성을 나타낸다. <strong>두 함수는 모두 확률 분포를 나타내는 함수로, 사건의 확률을 함수로 표현한 것이다.</strong></p>
<p>간단한 예시로 이를 살펴보면 조금 더 이해하기 쉬울 것이다.</p>
<h2 id="확률-질량-함수">확률 질량 함수</h2>
<p>확률 질량 함수에서 가장 흔한 예시로 <strong>동전 뒤집기</strong>의 경우에서 확률 변수를 동전이 앞면인 경우를 1 뒷면인 경우를 0이라고 할 때, 확률을 함수로 표현하면 다음과 같다.</p>
<p>$$
f(x)=
\begin{cases}
1/2, &amp; x=0 \
1/2, &amp; x=1
\end{cases}
$$</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/6f2d9562-3c5f-439b-8c26-a228be8aa89e/image.png" alt=""></p>
<h2 id="확률-밀도-함수">확률 밀도 함수</h2>
<p>그럼 반대로, 확률 밀도 함수에서 가장 흔한 예시인 <strong>정규 분포</strong>를 예시로 든다고 할 때 이를 함수로 표현하면 다음과 같다.</p>
<p>$$
f(x) \equiv \frac{1}{\sigma\sqrt{2\pi}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}
$$</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/69af3ebb-eac2-4348-b4f4-1ed4bdc65d78/image.png" alt=""></p>
<h1 id="확률과-생성모델">확률과 생성모델</h1>
<p>지금까지의 생성모델에서는 어떤 데이터를 생성하고자 할 때, 주어진 상태에서 <strong>&#39;높은 확률&#39;</strong>을 가진 데이터를 생성하도록 학습이 진행되고 있다.</p>
<p>아주 간단한 예를 들면 &#39;나는 밥을&#39; 뒤에 올 단어를 예측한다고 할 때에는 &#39;먹는다&#39;, &#39;먹었다&#39;와 같이 높은 확률로 올 단어를 선택하는 것과 유사하다고 보면 된다.</p>
<p>우리는 이 확률을 알기 위해서 <strong>&#39;확률 분포&#39;</strong>를 알아내야 한다.</p>
<h2 id="확률-분포란">확률 분포란?</h2>
<blockquote>
<p><strong>확률 분포는 확률 변수 또는 확률 변수가 취할 수 있는 값들의 범위와 그 값들이 나타날 확률 간의 관계이다.</strong></p>
</blockquote>
<p>위 정의에서 보면 결국 <strong>&#39;확률 변수와 그에 대한 확률 사이의 관계&#39;라고 이해</strong>할 수 있고, 이는 <strong>&#39;함수&#39;로 표현할 수 있다</strong>는 것을 알 수 있다.</p>
<blockquote>
<p><strong>그럼 &#39;그 함수&#39;만 알면 분포를 알 수 있겠네?</strong></p>
</blockquote>
<p>그러나 <strong>&#39;그 함수&#39;를 알아내기에는 생각보다 많이 어려운 일</strong>이다.</p>
<p>실제로 이미지 생성과 같은 복잡한 문제의 경우에는 수많은 경우의 수가 존재하고 정규분포, 균등분포와 같이 <strong>간단한 분포로 나타나지 않을 가능성이 높다.</strong></p>
<h2 id="딥러닝">딥러닝</h2>
<p>특정 데이터에 대한 분포가 어떤 함수로 표현이 될 수 있을지는 우리는 쉽게 알 수는 없지만 방법이 있다.</p>
<p>데이터가 주어지면 그에 맞는 함수를 제일 잘 찾아주는 <strong>딥러닝</strong> 방법론이다. </p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/fca18df5-1578-46c0-b130-7c477cff7794/image.gif" alt=""></p>
<p><strong>※ 출처 : <a href="https://medium.com/@jeff.lee.1990710/animation-of-overfitting-a-sin-wave-with-neural-network-453c52bb9264">Create animation of overfitting a Sin Wave with neural network - Medium</a></strong></p>
<p>즉, 생성 모델을 학습하기 위해서는 <strong>데이터를 기반으로 확률 분포를 잘 설명할 수 있는 함수를 근사시키는 것이 목적이라는 것을 알 수 있다.</strong></p>
<p>딥러닝에서는 이 분포를 근사하기 때문에 특정 조건에서 데이터를 어떻게 변화 시켜야 자연스러운지에 대해 알 수 있고, 이를 기반으로 데이터를 변화시켜 이미지, 텍스트, 오디오를 생성할 수 있게 되는 것이다.</p>
<h1 id="결론">결론</h1>
<p>딥러닝과 생성모델을 공부하면서 <strong>확률에 대한 이해가 중요하다는 것을 깨달았다.</strong> 확률은 단순히 사건의 가능성을 나타내는 것이 아니라, 이를 함수로 표현하고, 확률 변수를 통해 사건의 결과를 수치적으로 나타내는 중요한 개념이다.</p>
<p>처음 생성 모델에 대해 공부할 때 <strong>&#39;확률이 미분이 된다고?&#39;라는 생각이 들면서 혼란스러웠다.</strong> 특히 <strong>스코어 함수(Score Function)</strong>와 같이 함수로 이해하는 것이 필수인 개념을 접할 때 더더욱 혼란스러웠다. 스코어 함수는 확률 밀도 함수의 로그를 미분한 것으로, 확률 분포의 모양을 이해하는 데 중요한 역할을 한다.</p>
<p>이런 이해를 바탕으로 기존의 논문들을 다시 보니 그래도 어떤 개념인지는 직관적으로 이해할 수 있게 되었다. 앞으로도 여러 가지 확률적인 개념을 더 깊이 이해하고자 노력해야겠다는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Colab 장기간 학습 문제]]></title>
            <link>https://velog.io/@pre_f_86/Colab-%EC%9E%A5%EA%B8%B0%EA%B0%84-%ED%95%99%EC%8A%B5-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@pre_f_86/Colab-%EC%9E%A5%EA%B8%B0%EA%B0%84-%ED%95%99%EC%8A%B5-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 24 May 2024 02:06:42 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>Google에서 서비스 중인 Colab이라는 서비스는 사용자에게 무료 혹은 구독제 기반으로 고성능의 컴퓨팅 환경을 제공해줍니다.</p>
<p>특히 고성능의 GPU와 적지 않은 용량의 RAM을 무료로 제공해주기도 하고 유료가 되면 그만큼 더 나은 환경을 제공해줍니다.</p>
<p>이러한 특징 때문에 저는 개인 프로젝트, 실험, 대회 등 여러 딥러닝을 하게 될 때마다 구글 코랩을 이용하여 모델을 학습시키곤 하였습니다.</p>
<p>정말 좋은 환경을 제공해주기는 하지만 그만큼 여러 부분에서 문제상황이 생기기도 하였고 이를 해결하기 위해 많은 노력을 하기도 했습니다.</p>
<p>그래서 이번 글에서는 제가 겪었던 문제들을 해결했던 문제들 중 하나인 Colab 장기간 학습 문제에 대해서 다뤄보고자 합니다.</p>
<h1 id="제한적인-컴퓨팅-환경-제공">제한적인 컴퓨팅 환경 제공</h1>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c2457299-0a60-4e29-bd2f-0ad7a0b75293/image.png" alt=""></p>
<p>구글 Colab이 컴퓨팅 환경을 무료로 제공해주고는 하지만 결국 양은 제한되어 있기 때문에 컴퓨팅 환경을 최대한 많은 사람이 문제 없이 이용하게 하는 것이 구글의 입장에서 가장 중요합니다.</p>
<p>일반 유저들이 Colab에서 컴퓨팅 환경을 할당받고 사용하지 않는다면 그만큼 낭비라고 인식될 것입니다.</p>
<p>특히, CPU 대비 가격이 비싼 GPU의 경우에는 더더욱 중요하게 됩니다.</p>
<p>CPU의 경우 Colab에서는 사용량에 대한 제한이 없는 것으로 알고 있지만 GPU는 하루에 약 4시간 정도의 제한이 있는 것으로 알고 있습니다.</p>
<h1 id="세션-종료">세션 종료</h1>
<p>세션은 Colab으로부터 할당 받은 컴퓨팅 환경을 말합니다.</p>
<p>이전 절에서 말했 듯 Colab은 여러 사람에게 컴퓨팅 환경을 무리 없이 할당할 수 있도록 사용자의 활동을 감지합니다.</p>
<p>그렇기 때문에 세션 내에서 어떤 활동도 없을 경우 코랩은 자동으로 세션을 종료시키는 방식을 사용합니다.</p>
<p>그래서 이를 해결하기 위해 코랩에게 사용자가 활동하고 있다는 것처럼 느끼게 하기 위해서 자바스크립트를 이용해 특정 활동을 하도록 매크로를 실행시킵니다.</p>
<h2 id="해결-방법">해결 방법</h2>
<p>1.브라우저에서 F12를 눌러 개발자 모드로 들어갑니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/ed125cc6-b4a6-4876-8621-70de234f6ba6/image.png" alt=""></p>
<ol start="2">
<li><p>우측 Console에 들어가 아래의 자바스크립트 코드를 입력합니다.</p>
<p> <strong>※ 아래의 코드에서 1000은 1000ms로 1초를 의미합니다.</strong></p>
</li>
</ol>
<pre><code class="language-javascript">function ClickConnect(){
    console.log(&quot;Working&quot;); 
    document.querySelector(&quot;#top-toolbar &gt; colab-connect-button&quot;).shadowRoot.querySelector(&quot;#connect&quot;).click() 
}setInterval(ClickConnect,1000)</code></pre>
<h1 id="구글-드라이브-오류">구글 드라이브 오류</h1>
<p>이번에 설명할 오류는 Colab PRO+ 요금제를 사용하면서 제일 이해가 안되던 오류였던 거 같습니다.</p>
<p>Colab PRO+ 요금제의 경우 24시간 끊김 없이 GPU를 사용할 수 있다는 말에 결제를 하고 하루정도 학습을 시키려곤 하였었는데 항상 4시간 정도 학습을 하다가 데이터 로더 부분에서 파일이 존재하지 않는다는 오류가 나곤 했었습니다.</p>
<pre><code class="language-python">img = cv2.imread(&quot;./drive/test.jpg&quot;)
print(img)
&gt; None</code></pre>
<p>처음에는 4시간마다 끊긴다는 것도 인지하지 못하고 아래처럼 생각했던 거 같네요...</p>
<blockquote>
<p><strong>데이터 로더가 문제인가? 구글 드라이브에 파일을 업로드할 때 오류가 난 건가? 근데 왜 학습이 잘 되다가 끊기는 거지?</strong></p>
</blockquote>
<p>처음에는 대용량 데이터라서 업로드가 중간에 잘못되었나 싶었던 지라 며칠동안 구글드라이브로 재업로드 하고 압축 풀고 하는데 시간을 엄청 날렸었습니다.</p>
<p>4시간마다 학습이 끊기는 것을 보면 환경에서 문제가 되겠구나 싶었고 Colab 환경은 문제가 없을테니 구글 드라이브가 문제라고 생각했습니다.</p>
<p>그래서 Colab내의 Local 드라이브에서 파일을 불러오는데 문제가 없을까 하고 24시간 돌려봤는데 문제는 없었고 결국 구글 드라이브가 문제였던 걸 알았습니다.</p>
<h2 id="해결방법">해결방법</h2>
<p>Google Drive를 마운트 하고 드라이브 내에 있는 데이터 셋을 Local에 옮기시면 됩니다.</p>
<pre><code class="language-python">import shutil

from google.colab import drive
drive.mount(&#39;/content/drive&#39;)

from_dir = &quot;/content/drive/test&quot;
to_dir = &quot;./test&quot;

shutil.copytree(from_dir, to_dir)</code></pre>
<p>만일 대용량의 파일을 옮기시는데 어느 순간 파일이 안옮겨지고 멈춰져있는 현상이 발생하신다면 다음처럼 하시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/6345c348-4a8e-4916-ad42-c8035349d51a/image.png" alt=""></p>
<ol>
<li><p>마운트 된 폴더 내에 옮기고자 하는 폴더(test)가 보이게 디렉토리를 연다</p>
</li>
<li><p>아래의 자바스크립트 코드 내에 폴더 이름에 옮기고자 하는 폴더(test)를 입력한다.</p>
</li>
</ol>
<pre><code class="language-javascript">function ClickConnect(){
    console.log(&quot;Working&quot;); 
    document.querySelector(&#39;span.file-tree-name[title=&quot;폴더이름&quot;]&#39;).click();
}setInterval(ClickConnect,1000)</code></pre>
<pre><code class="language-javascript">function ClickConnect(){
    console.log(&quot;Working&quot;); 
    document.querySelector(&#39;span.file-tree-name[title=&quot;test&quot;]&#39;).click();
}setInterval(ClickConnect,1000)</code></pre>
<ol start="3">
<li>해당 자바스크립트 코드가 실행되고 test 폴더가 열렸다 닫혔다 하는 것을 확인하게 된다면 성공</li>
</ol>
<p><strong>※ 폴더를 열었다 닫았다 하는 과정에서 디렉토리가 갱신되면서 끊김 없이 옮겨지게 됩니다.</strong></p>
<h1 id="마무리">마무리</h1>
<p>Colab을 사용하면서 많은 문제점들이 있었지만 과거의 저처럼 문제를 만난 분들을 계실까 하고 이렇게 글 올려봅니다.</p>
<p>앞으로 더 생각나게 된다면 추가적으로 올리겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PyTorch] 가중치 고정]]></title>
            <link>https://velog.io/@pre_f_86/PyTorch-%EA%B0%80%EC%A4%91%EC%B9%98-%EA%B3%A0%EC%A0%95</link>
            <guid>https://velog.io/@pre_f_86/PyTorch-%EA%B0%80%EC%A4%91%EC%B9%98-%EA%B3%A0%EC%A0%95</guid>
            <pubDate>Mon, 06 May 2024 07:08:37 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>인공지능 모델의 레이어의 가중치를 고정(Freeze 혹은 Lock) 하고자 할 때 자료를 찾아보면 <strong>requires_grad, no_grad(), detach()</strong> 등 여러가지 방법을 사용합니다.</p>
<p>셋의 차이점을 검색하면 차이점에 대해 얘기를 하지만 뭔가 확 와닿지는 않았습니다.</p>
<blockquote>
<p><strong>어떨 때, 무엇을, 왜 써야하는가?</strong></p>
</blockquote>
<p>가중치 고정, Grad 계산하지 않음, Graph... 등 여러 이유가 있음에도 설명이 모호한 경우가 많았습니다.</p>
<p>그래서 <strong>이를 명확히 정리하고자 하여 이 글을 작성</strong>합니다.</p>
<p>글을 읽기 전 아래의 글을 먼저 이해하고 오시는 것을 추천합니다.(양은 좀 많지만 이해하는데 도움이 될 수 있습니다.)</p>
<ul>
<li><p><strong><a href="https://velog.io/@pre_f_86/series/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80">[딥러닝] 경사하강법 구현부터 학습까지</a> 시리즈</strong> : 경사하강법은 무엇이고 어떻게 모델을 학습시키는지에 대한 글</p>
</li>
<li><p><strong><a href="https://velog.io/@pre_f_86/series/PyTorch-AutoGrad%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80">[PyTorch] AutoGrad란 무엇인가?</a> 시리즈</strong> : PyTorch에서는  순전파와 역전파를 어떻게 하는가에 대한 글</p>
</li>
</ul>
<h2 id="가중치-고정이란">가중치 고정이란?</h2>
<p>실험을 진행하기 위해 PyTorch나 Tensorflow 같은 딥러닝 프레임워크를 쓰면서 학습을 진행하다보면 <strong>특정 가중치를 학습에 참여하지 못하게 하고 싶은 경우</strong>가 많습니다.</p>
<p>대표적으로 사전학습된(Pre-Trained) 모델을 수정하고 미세조정(Fine-Tuning)하는 과정처럼 <strong>기존의 가중치가 이미 충분한 역할(특징 추출, 특징 합성 등)을 한다고 판단할 때</strong>입니다.</p>
<p>다음의 과정을 통해 가중치 고정을 간단하게 보이겠습니다.</p>
<hr>
<p>다음과 같은 수식이 있다고 해봅시다.</p>
<p>$$
x+2 = 3
$$</p>
<p>1차적으로 계산을 통해 $x=1$임을 확신할 수 있습니다.</p>
<p>두번째 수식이 다음과 같이 주어진다고 하면</p>
<p>$$
x+y=4
$$</p>
<p>우리는 기존의 $x$를 바꾸지 않고 $y=3$인 것을 알 수 있습니다.</p>
<p>이를 인공지능 모델에 비유하면 다음과 같습니다.</p>
<p>$x$는 <strong>기존에 하나의 가중치로 이루어진 모델</strong>이며, $x=1$임을 알아가는 과정은 <strong>사전학습 과정</strong>이고, $x+y$는 <strong>두개의 가중치로 이루어진 수정된 모델 구조</strong>이고, $x=1$로 <strong>고정</strong>하여 <strong>미세조정</strong>하여 $y=3$인 것을 알아가는 과정</p>
<hr>
<blockquote>
<p>즉, <strong>가중치 고정이란</strong> 인공지능을 학습하는 과정에서 특정 가중치가 정답에 가깝다고 판단하는 경우 <strong>학습과정에 인위적으로 개입하여 가중치가 업데이트 되지 않도록</strong>하는 것이라고 할 수 있습니다.</p>
</blockquote>
<p>물론, 실제로 거대하고 복잡한 구조를 가진 모델 속에 있는 각 가중치들이 제대로 된 역할을 알기는 쉽지 않습니다.</p>
<p>그저 실험적으로 <strong>&#39;1~4번째 레이어는 충분한 특징에 대해서 학습했구나&#39;</strong> 정도만 알 수 있습니다.</p>
<p>그렇기 때문에 인공지능 모델의 성능을 향상시키고자 하는 경우 가중치 고정은 조심히 다루어야 합니다.</p>
<h3 id="가중치-고정-장점">가중치 고정 장점</h3>
<p>그렇다면 장점은 무엇이 있을까요?</p>
<p>사실 가중치 고정이라는 <strong>이론</strong>만으로는 장점을 찾아내기가 어렵습니다.</p>
<p>위에서 말한 것과 유사하게 변수를 줄여나가는 것도 장점이라고 하면 장점일 수도 있지만 <strong>변수가 수백만가지가 된다면 이 또한 의미가 없게 됩니다.</strong></p>
<p>그러나 컴퓨팅 연산으로 접근을 하게된다면 <strong>연산량의 장점</strong>이 있습니다.</p>
<p>간단하게 보게 된다면, <strong>100만개의 변수들을 업데이트 해야하는 경우에 절반을 고정하면 그중 절반의 연산만 하게 됩니다.</strong></p>
<p>또한 경사하강법에서 가중치를 업데이트 하기 위한 <strong>Gradient 계산을 하지 않음으로써 연산량을 그만큼 줄일 수 있게 됩니다.</strong></p>
<h2 id="pytorch-autograd">PyTorch Autograd</h2>
<p>우선 PyTorch의 AutoGrad에 대해 간단하게 설명하고 넘어가겠습니다.</p>
<p>PyTorch는 기본적으로 <strong>연산에서 각 변수의 기울기를 자동으로 계산하도록 해주는 AutoGrad</strong>를 가지고 있습니다.</p>
<p>특정 연산의 정보와 연산에 사용한 변수들에 대한 정보를 <strong>연산 그래프(Computational Graph)</strong>로 저장하고 있습니다.</p>
<p>이렇게 <strong>순전파 과정에서 연산 그래프에 대한 정보를 가지고 있기 때문에  알 수 있고 이를 기반으로 역전파가 가능하게 됩니다.</strong></p>
<h3 id="연산-그래프-computational-graph">연산 그래프 (Computational Graph)</h3>
<p>그렇다면 연산 그래프가 무엇인지에 대해 이해해보겠습니다.</p>
<p>연산 그래프라고 해서 어떤 복잡한 자료구조가 사용된다고 생각하실 수도 있지만 그렇지 않습니다.</p>
<p>아래처럼 간단하게 연산 과정을 표처럼 표현한다고 보시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/acca21fb-2527-474a-9b3e-be1663860a16/image.png" alt=""></p>
<p><strong>※ <a href="https://www.youtube.com/watch?v=MswxJw-8PvE&amp;t=219s">PyTorch Autograd Explained - In-depth Tutorial</a>에 과정이 정말 잘 설명되어 있습니다.</strong></p>
<p>위 표는 <strong>$c = a \times b$</strong> 에 대한 연산을 연산 그래프로 표현한 것입니다.</p>
<blockquote>
<p><strong>그럼 PyTorch에서는 이 연산 그래프를 어떻게 구성하는 것일까?</strong></p>
</blockquote>
<p>PyTorch에서는 <strong>연산 그래프에 대한 정보를 grad_fn을 통해 저장</strong>하고 있습니다.</p>
<p>grad_fn은 특정 <strong>Tensor가 생성될때 사용된 연산(사진 속 MulBackward)에 대한 정보</strong>와 <strong>입력(사진 속 ctx.saved_tensor)에 대한 정보</strong>가 담겨있습니다. </p>
<p>즉, 이전 노드(입력)에 대한 정보와 연산 정보를 가지고 있으므로 연산 그래프를 구성할 수 있게되고 그래프를 따라 역전파를 할 수 있게 됩니다.</p>
<h2 id="가중치-고정-방법">가중치 고정 방법</h2>
<p>PyTorch에서는 가중치 고정 방법은 여러가지가 있습니다.</p>
<p>대표적으로 <strong>no_grad(), requires_grad, detach(), Optimizer에 변수 등록</strong> 등이 있습니다.</p>
<p>이 네가지의 특징을 설명하면서 각각이 어떻게 작동하는지 보여드리겠습니다.</p>
<h3 id="경사하강법">경사하강법</h3>
<p>우선 가중치 고정 방법에 대해서 설명드리기 전 간단하게 <strong>경사하강법의 작동 원리</strong>를 설명 드리겠습니다. </p>
<p>경사하강법은 크게 3단계로 나뉘어 작동합니다.</p>
<ol>
<li><p><strong>순전파(Forward Propagation) 단계</strong> : 각 레이어에서의 연산을 진행하며 연산에 대한 정보를 저장합니다.(연산 그래프 정의)</p>
</li>
<li><p><strong>역전파(Back Propagation) 단계</strong> : 저장된 연산정보를 통해 Gradient를 계산하여 역으로 전파하여 출력에 대한 각 가중치의 미분값을 계산합니다.</p>
</li>
<li><p><strong>가중치 갱신(Weight Update) 단계</strong> : 계산된 Gradient 값을 통해 각 가중치의 값을 갱신합니다.</p>
</li>
</ol>
<p>가중치 고정은 <strong>각 단계 중 일부를 제어</strong>하면서 가중치를 고정합니다.</p>
<p>PyTorch에서 각 단계는 다음과 같이 구현됩니다.</p>
<pre><code class="language-python">import torch
import torch.nn as nn

# 함수 정의
def f(x):
    return x**2

# 파라미터 정의 
x = nn.Parameter(torch.rand(1))

# 옵티마이저 정의 : x라는 변수를 lr만큼 학습하겠다는 의미를 가집니다.
optimizer = torch.optim.SGD(params=[x],lr=0.1)

# 기존의 기울기 정보 초기화
optimizer.zero_grad()

# 순전파 : 주어진 함수(모델)에 맞게 연산을 진행함과 동시에 각 변수의 Gradient를 얻어냅니다.
out = f(x)

# 역전파 : 순전파를 통해 얻어낸 Gradient를 연쇄법칙을 이용해 각 파라미터의 기울기 값을 얻어냅니다.
out.backward()

# 갱신 : 선언할 때 입력받은 params의 기울기에 맞게 값을 변경합니다.
optimizer.step()</code></pre>
<p>이제 위에서 언급한 각 방법이 <strong>어떤 단계를 어떻게 제어하는지</strong> 알아가면서 <strong>각각의 장단점</strong>을 확인해보겠습니다.</p>
<h3 id="requires_grad">requires_grad</h3>
<p>우선 <strong>requires_grad의 값을 변경하는 방식은 가장 흔히 사용되는 방식</strong>입니다.</p>
<p>특정 Tensor a의 requires_grad를 True로 설정하게 되면 이를 이용한 <strong>연산의 결과 Tensor</strong>에 대해서 <strong>requires_grad를 True</strong>로 설정하게 되며, <strong>requires_grad가 True이므로 grad_fn도 지정</strong>되게 됩니다.</p>
<p>즉, <strong>특정 Tensor a와 연관된 모든 연산은 연산 그래프에 포함되게 됩니다.</strong>(연산 그래프의 예시 그림 참조)</p>
<blockquote>
<p><strong>그렇다면 여러 입력을 받는 연산(Add, Sub, Mul, Div 등)으로 인해 생긴 Tensor의 requires_grad는 어떻게 정해질까?</strong></p>
</blockquote>
<p><strong>두 Tensor 중 하나라도 requires_grad가 True인 경우 결과 Tensor의 requires_grad는 True가 됩니다. (or 연산)</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d687eb55-4caf-427f-88a4-464444681a48/image.png" alt=""></p>
<blockquote>
<p><strong>그렇다면 $c=a \times b$와 같은 연산을 할 때 각각의 requires_grad가 True, False면 역전파 과정에서 Tensor b에도 기울기가 저장되는 거 아니야?</strong></p>
</blockquote>
<p>물론 입력 Tensor a의 requires_grad가 True이기 때문에 결과 Tensor c의 requires_grad는 True로 grad_fn에는 MulBackward가 지정이 되긴 합니다.</p>
<p>하지만 <strong>grad_fn에서는 특정 입력의 requires_grad가 False인 경우에는 해당 노드에 대해서 Gradient를 전달하지 않습니다.</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/cf2ff538-e809-402f-80e5-db6384943688/image.png" alt=""></p>
<p><strong>※ 여기서 next_functions 는 특정 입력에 어떻게 Gradient를 전달할지에 대한 정보가 담겨있습니다. (None이면 전달 하지 않음)</strong></p>
<p>이렇기 때문에 <strong>역전파 단계에서 전달되는 Gradient가 없으므로 Tensor b의 grad는 None이 될 것</strong>이며 이 때문에 <strong>가중치 갱신 단계에서도 Tensor b의 가중치가 업데이트 되지 않습니다.</strong></p>
<p>requires_grad의 값을 변경하는 과정은 <strong>순전파 단계부터 제어를 하여 가중치를 고정하는 방식</strong>입니다.</p>
<p>또한 특정 연산에 대해 입력 Tensor들의 <strong>requires_grad가 모두 False로 두게 된다면</strong> 다음과 같은 장점이 있습니다.</p>
<ol>
<li><p><strong>메모리 사용량 감소</strong> : Gradient의 흐름이 필요가 없으므로 연산에 대한 정보, 입력에 대한 정보를 저장할 필요가 없어지게 됨</p>
</li>
<li><p><strong>역전파 연산량 감소</strong> : requires_grad가 False가 되고 grad_fn이 지정되지 않으면서 Gradient를 전달하지 못하게 되면서 불필요한 역전파가 사라지고 이에 따라 연산량이 감소하게 됩니다.</p>
</li>
</ol>
<h3 id="torchno_grad">torch.no_grad()</h3>
<p>no_grad() 방식은 다음과 같이 <strong>with문과 함께 사용</strong>됩니다.</p>
<pre><code class="language-python">with torch.no_grad():
    out = linear(x)
    out = activation(out)
    ...</code></pre>
<p>requires_grad 여부에 관계 없이 해당 <strong>with 문 안에서의 모든 연산에 대해서 requires_grad를 False라고 간주하고 연산</strong>하게 됩니다.</p>
<p>$c= a \times b$에 대한 결과를 보이기 위해 다음과 같은 예시 코드를 작성할 수 있습니다.</p>
<pre><code class="language-python">import torch

a = torch.tensor([1.], requires_grad=True)
b = torch.tensor([1.], requires_grad=True)

with torch.no_grad():
    c = a*b

d = a*b

print(c.grad_fn, c.requires_grad)
#&gt; None False
print(d.grad_fn, d.requires_grad)
#&gt; &lt;MulBackward0 object at 0x0000018998D6AD60&gt; True</code></pre>
<p>Tensor a, b 모두 requires_grad를 False로 간주하기 때문에 그 결과로 생성되는 Tensor는 requires_grad는 False이며 grad_fn은 None이 되게 됩니다.</p>
<p>위 방식이 이전 방식에 비해 좋은 점은 간단하고 <strong>쉽게 Gradient의 흐름을 제어</strong>할 수는 있지만 모델이 복잡해지는 경우 <strong>Tensor a, b에 의도치 않게 Gradient를 흐르게 하여 가중치가 고정되지 않을 수 있습니다.(Tensor d에 역전파가 되는 경우)</strong></p>
<h3 id="detach">detach()</h3>
<p><strong>requires_grad 방식과 torch.no_grad 방식</strong>은 연산을 <strong>진행하는 과정</strong>에서 연산 그래프를 제어하게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/16d84d8f-b79e-4151-97b8-e31461534312/image.png" alt=""></p>
<p>즉, Tensor a, b의 <strong>연산 중에 결과 Tenscor c의 requires_grad와 grad_fn이 정해집니다.</strong></p>
<p>그러나 <strong>detach</strong>는 <strong>연산이 완료되고 난 후의 시점</strong>에서 연산그래프를 제어합니다.</p>
<p>아래의 코드를 통해 살펴보면 다음과 같습니다.</p>
<pre><code class="language-python">import torch

a = torch.tensor([1.], requires_grad=True)
b = torch.tensor([1.], requires_grad=True)

c = a*b

print(c.grad_fn, c.requires_grad)
#&gt; &lt;MulBackward0 object at 0x00000189A63B7DF0&gt; True

d = c.detach()

print(c.grad_fn, c.requires_grad)
#&gt;&lt;MulBackward0 object at 0x00000189A63B7DF0&gt; True
print(d.grad_fn, d.requires_grad)
#&gt;None False</code></pre>
<p>물론 replace 연산이 아니기 때문에 Tensor c에 바로 적용되지는 않지만 이전의 방식들과는 달리 <strong>c가 생성된 이후에 detach()를 적용하여 연산그래프에서 제외</strong>시킵니다.(replace 연산으로 하려면 detach_() 사용)</p>
<p>위 방식은 <strong>no_grad 방식과 유사한 특징</strong>을 가지고 있지만, <strong>이전 방식들과는 달리 연산 후에 연산 그래프에서 제외하는 방식이기 때문에 연산량과 메모리에 대한 장점이 사라지게 됩니다.</strong></p>
<h3 id="optimizer에-변수-등록">Optimizer에 변수 등록</h3>
<p>이전까지의 방법들은 <strong>순전파 과정</strong>에서 연산그래프를 제어하여 <strong>Gradient의 흐름을 제어하여 가중치를 고정하</strong>였습니다.</p>
<p><strong>Optimizer에 변수를 등록하는 방식</strong>은 가중치 갱신 과정에서 <strong>변수들의 정보를 제어하여 가중치를 고정하는 방식</strong>입니다.</p>
<p>PyTorch에서 <strong>Optimizer의 역할은 계산된 Gradient를 기반으로 가중치를 갱신하는 역할</strong>을 하게 됩니다.</p>
<p>기존의 학습 코드를 다시 가져와서 살펴보며 설명하겠습니다.</p>
<pre><code class="language-python">import torch
import torch.nn as nn

# 함수 정의
def f(x):
    return x**2

# 파라미터 정의 
x = nn.Parameter(torch.rand(1))

# 옵티마이저 정의 : x라는 변수를 lr만큼 학습하겠다는 의미를 가집니다.
optimizer = torch.optim.SGD(params=[x],lr=0.1)

# 기존의 기울기 정보 초기화
optimizer.zero_grad()

# 순전파 : 주어진 함수(모델)에 맞게 연산을 진행함과 동시에 각 변수의 Gradient를 얻어냅니다.
out = f(x)

# 역전파 : 순전파를 통해 얻어낸 Gradient를 연쇄법칙을 이용해 각 파라미터의 기울기 값을 얻어냅니다.
out.backward()

# 갱신 : 선언할 때 입력받은 params의 기울기에 맞게 값을 변경합니다.
optimizer.step()</code></pre>
<p>보시는 것처럼 Optimizer는 선언됨과 동시에 <strong>params 변수에 학습시키고자하는 파라미터에 대한 정보를 받습니다.</strong></p>
<p>zero_grad()는 <strong>params 변수에 있는 파라미터들에 지금까지 저장된 Gradient 값을 0으로 초기화 하는 역할을 하고</strong>, <strong>step()은 지금까지 저장된 Gradient 값을 기반으로 파라미터 값을 변경하게 됩니다.</strong></p>
<p>즉, <strong>params로 주어지지 않은 변수에 대해서는 가중치를 갱신하지 않게됩니다.</strong></p>
<p>가령 Tensor a, b가 있다고 할 때 Optimizer의 <strong>params에 a에 대한 정보만 제공하는 경우</strong>를 살펴보겠습니다.</p>
<pre><code class="language-python">import torch

a = torch.tensor([1.], requires_grad=True)
b = torch.tensor([1.], requires_grad=True)

optimizer = torch.optim.SGD(params=[a],lr=0.1)

optimizer.zero_grad()

c = a * b

print(a.data, a.grad, a.grad_fn, a.requires_grad)
#&gt; tensor([1.]) None None True
print(b.data, b.grad, b.grad_fn, b.requires_grad)
#&gt; tensor([1.]) None None True
print(c.data, c.grad, c.grad_fn, c.requires_grad)
#&gt; tensor([1.]) None &lt;MulBackward0 object at 0x00000252C7D07190&gt; True

c.backward()

print(a.data, a.grad, a.grad_fn, a.requires_grad)
#&gt; tensor([1.]) tensor([1.]) None True
print(b.data, b.grad, b.grad_fn, b.requires_grad)
#&gt; tensor([1.]) tensor([1.]) None True
print(c.data, c.grad, c.grad_fn, c.requires_grad)
#&gt; tensor([1.]) None &lt;MulBackward0 object at 0x00000252C7D07B80&gt; True

optimizer.step()

print(a.data, a.grad, a.grad_fn, a.requires_grad)
#&gt; tensor([0.9000]) tensor([1.]) None True
print(b.data, b.grad, b.grad_fn, b.requires_grad)
#&gt; tensor([1.]) tensor([1.]) None True
print(c.data, c.grad, c.grad_fn, c.requires_grad)
#&gt; tensor([1.]) None &lt;MulBackward0 object at 0x00000252C7D07FD0&gt; True</code></pre>
<p><strong>※ optimizer.zero_grad()를 해야하는 이유는 step() 이후의 결과를 확인해보면 Tensor a, b의 grad가 유지되는 것을 확인할 수 있으며 다음 backward()를 실행한다면 새로운 Gradient가 grad에 계속 축적되게 됩니다.</strong></p>
<p>우선 결과부터 살펴본다면 <strong>Tensor a의 값은 1.0 에서 0.9000으로 변경</strong>이 되었지만 <strong>Tensor b의 값은 1.0에서 변하지 않은 것을 확인</strong>할 수 있습니다.</p>
<blockquote>
<p><strong>분명 Tensor a, b 모두 requires_grad가 True이고 c.backward() 이후에 정상적으로 Gradient가 역전파 되었는데 왜 갱신이 되지 않았지?</strong></p>
</blockquote>
<p>그 이유는 Optimizer의 params 변수에 Tensor b를 입력하지 않았기 때문에 Optimizer는 Tensor a의 값만 변경하게 됩니다.</p>
<p>즉, <strong>Tensor b의 requires_grad를 False로 둔 결과와 동일하게 나오게 됩니다.</strong></p>
<p>하지만 requires_grad가 모두 True로 되어 있기 때문에 <strong>불필요한 연산그래프가 발생하게 되며 이 때문에 requires_grad 방식에 비해 연산량, 메모리 사용량 감소의 장점이 사라질 수 있습니다.</strong></p>
<p><strong>※ step() 과정에서 파라미터 수가 줄어듦에 따라 연산량 감소하는 효과는 있지만 크게 감소하지는 않음</strong></p>
<h2 id="마무리">마무리</h2>
<p>이번 글에서는 가중치 고정을 위한 <strong>주요 방법 4가지와 그 원리에 대해서 설명</strong>하였습니다.</p>
<p>4가지 방법 모두 다른 특성을 가지고 있어 <strong>구조를 설계하는 과정에서 적절한 방법을 사용</strong>하면 될 거 같습니다.</p>
<p>저는 안전하게 고정하고자 하는 파라미터의 <strong>requires_grad를 False로 시켜두고 Optimizer의 params에 제외시키는 방식을 사용</strong>하고 있습니다.</p>
<p>다시 한 번 글에서 보여드린 예시코드를 기반으로 모델을 더 깊고 복잡하게 쌓으면서 중간중간 결과도 확인하시면서 익숙해지시면 나중에 Fine-Tuning처럼 가중치를 고정해야할 때 쉽게 하실 수 있으실 겁니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PyTorch] AutoGrad란 무엇인가? (2)]]></title>
            <link>https://velog.io/@pre_f_86/PyTorch-AutoGrad%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-2</link>
            <guid>https://velog.io/@pre_f_86/PyTorch-AutoGrad%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-2</guid>
            <pubDate>Wed, 01 May 2024 07:57:38 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>이전 글에서는 <strong>AutoGrad가 무엇이고 어떤 시스템을 기반으로 작동하는지에 대해 설명</strong>하는 글을 작성하였습니다.</p>
<p>이번 글에서는 <strong>AutoGrad가 어떻게 연산과 Gradient에 대한 정보를 기록하는지 그 과정에 대해 설명</strong>하겠습니다.</p>
<h1 id="autograd-작동-방식">AutoGrad 작동 방식</h1>
<p>경사하강법을 진행하기 위해서는 <strong>기울기에 대한 정보</strong>를 알고 있어야 합니다.</p>
<p>또 <strong>기울기를 알기 위해서</strong>는 <strong>연산에 대한 정보, 입력에 대한 정보</strong>를 알고 있어야 합니다.</p>
<p>그렇다면 <strong>PyTorch에서는 이런 정보들을 어떻게 저장하는지</strong>에 대해 알아보기 위해 간단한 예시를 들어 설명하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3476237e-2769-4613-b9fa-b50878d96a80/image.PNG" alt=""></p>
<h2 id="tensor-클래스">Tensor 클래스</h2>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/fb22730a-3a54-4a9d-a2c9-003dab1bfec8/image.png" alt=""></p>
<p>Tensor 클래스는 PyTorch에서 <strong>중요한 클래스 중 하나라고 생각합니다.</strong></p>
<p>Tensor 객체 안에는 여러가지 변수들과 메서드들이 담겨 있지만 <strong>주요 변수들</strong>에 대해 살펴보겠습니다.</p>
<ul>
<li><p><strong>data</strong> : 실질적인 값이 담겨있습니다.</p>
</li>
<li><p><strong>grad</strong> : 역전파된 Gradient에 대한 정보가 담겨있습니다.</p>
</li>
<li><p><strong>grad_fn</strong> : 이 Tensor가 생길때 사용된 연산이 어떤 것인지에 대한 정보가 담깁니다.</p>
</li>
<li><p><strong>is_leaf</strong> : 현재 텐서가 일반 Tensor인지(True) Tensor 사이의 연산으로 의해 생긴 Tensor인지(False)에 대한 정보입니다.</p>
</li>
<li><p><strong>requires_grad</strong> : Gradient 계산을 할 것인지에 대해 정의 됩니다.</p>
</li>
</ul>
<h2 id="tensor-연산">Tensor 연산</h2>
<p>위 예시에서 a*b를 수행하게 될 경우의 과정을 한 번 살펴보겠습니다.</p>
<ol>
<li><p>곱하기 과정을 진행할 경우 <strong>곱 연산을 담당하게 되는 Mul 연산을 실행</strong>하게 됩니다.</p>
</li>
<li><p>Mul 연산에서는 출력 정보를 담을 Tensor c 생성 합니다.</p>
<p> 2-1. Mul연산에서는 연산을 진행하면서 결과값을 Tensor c에 기록합니다.</p>
<p> 2-2. <strong>ctx.save_for_backward() 함수를 실행하여 입력 Tensor들 중 특정 Tensor에 대한 정보를 기록</strong>하게 됩니다.</p>
<p> 2-3. Tensor c의 grad_fn에 <strong>Mul 연산의 Backward 정보인 MulBackward 객체를 선언하여 저장합니다.</strong></p>
</li>
<li><p><strong>생성된 Tensor c</strong>는 아래와 같은 특성을 가진 Tensor가 됩니다.</p>
</li>
</ol>
<p><strong>Tensor c</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c63029ce-444a-4a83-b8d3-b43599fb83b6/image.png" alt=""></p>
<ul>
<li><p><strong>data</strong> : a의 데이터와 b의 데이터를 곱한 값</p>
</li>
<li><p><strong>grad</strong> : backward 연산을 진행하지 않았기에 값의 변화는 없습니다.</p>
</li>
<li><p><strong>grad_fn</strong> : 곱셈과 관련된 grad_fn인 MulBackward가 저장됩니다.</p>
</li>
<li><p><strong>is_leaf</strong> : Tensor 사이의 연산으로 생성되었으므로 False가 저장됩니다.</p>
</li>
<li><p><strong>requires_grad</strong> : Tensor a의 requires_grad가 True이므로 True가 저장됩니다.</p>
</li>
</ul>
<p><strong>※ 이때 연산에 사용된 Tensor 중 하나라도 requires_grad가 True인 경우 requires_grad가 True가 되게 됩니다.</strong></p>
<p>또한 이렇게 Tensor가 연산에 사용된 경우에 Tensor a, b, c는 각각 연산 그래프에 등록 됐다고 생각하시면 됩니다. (연산 그래프는 DAG로 연산 과정에 대해서 이 글의 썸네일처럼 노드로 표현한 것입니다.)</p>
<p>다음 절에서 이 과정에서 처음 보이는 <strong>ctx와 grad_fn은 무엇이고 어떤 역할</strong>을 하게되는지 알아보겠습니다.</p>
<h2 id="ctx">ctx</h2>
<p><strong>Python에서 ctx 자체는 self와 유사한 성격</strong>을 가지고 있습니다.</p>
<p><strong>하지만 PyTorch에서 ctx는 @staticmethod 라는 Decorator와 함께 사용되어 메소드 자체가 정적메소드로 정의되기 때문에 ctx는 하나의 인수로 처리</strong>됩니다.</p>
<p>이런 ctx가 어떤 정보가 가지고 있고 어떻게 연산에 사용된 Tensor를 다루는지에 대해서 확인해보고자 <strong>실제 코드를 찾아보았지만 찾기가 어려웠습니다...</strong></p>
<p>ctx와 관련된 <strong>saved_tensor, save_for_backward() <a href="https://pytorch.org/docs/stable/_modules/torch/autograd/function.html#FunctionCtx.save_for_backward">변수, 메서드</a></strong>는 실제로 <strong>c언어로 외부에 구현되어 있어 자세히 알 수는 없습니다.</strong> (한번 나중에 확인해보겠습니다!)</p>
<p>그렇기 때문에 <strong>saved_tensor와 save_for_backward()가 무엇인지만 간단하게 다루겠습니다.</strong></p>
<p>우선 ctx는 간단하게 PyTorch에서 <strong>연산에 사용된 변수를 다루는 하나의 객체</strong>라고 볼 수 있습니다.</p>
<ul>
<li><p><strong>ctx.save_for_backward(self, *tensors: torch.Tensor)</strong> : Tensor 연산 단계에서 사용된 변수들에 대해서 역전파 단계에서 필요한 변수들에 대해 저장하는 메소드입니다.</p>
</li>
<li><p><strong>ctx.saved_tensor</strong> : 기존에 저장한 변수들에 대한 정보가 담겨있는 변수입니다.</p>
</li>
</ul>
<p>곱셈을 담당하는 MulBackward 객체에서는 <strong>이러한 변수들에 대한 정보를 기반으로 Gradient를 계산할 수 있게 됩니다.</strong></p>
<p>가령 곱연산에서는 saved_tensor에 Tensor a, b에 대한 정보가 담겨있을 것이고, 역전파 단계에서 <strong>a에게는 Gradient로 b의 값을 b에게는 Gradient로 a의 값을 전달</strong>해주면 됩니다.</p>
<p>그렇다면 이렇게 입력 Tensor들의 정보를 기반으로 <strong>어떻게 Gradient를 전파하는지에 대해 다음 절에서 확인해보겠습니다.</strong></p>
<h2 id="grad_fn">grad_fn</h2>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/40487bbe-5699-43e0-a03e-ab2187a7c762/image.png" alt=""></p>
<p>grad_fn은 해당 Tensor 객체가 만들어질 때 <strong>사용된 연산과 사용된 Tensor 객체들에 대한 정보가 담긴 하나의 객체</strong>가 되고, <strong>Backward에서 중요한 역할을 하게 됩니다.</strong></p>
<p><strong>MulBackward 객체에서는 ctx.saved_tensors, next_functions가 있는 것을 확인</strong>하실 수 있습니다.</p>
<p>각각의 변수를 설명하면 다음과 같습니다.</p>
<ul>
<li><p><strong>ctx.saved_tensors</strong> : 연산에 사용된 실질적인 변수들에 대한 정보가 담겨있습니다.</p>
</li>
<li><p><strong>next_functions</strong> : 지금까지 축적된 Gradient를 Backward 하게 될 때, 다음에 넘겨줄 Backward 방식에 대한 정보가 담겨있습니다.</p>
</li>
</ul>
<p>그렇다면 위의 예시에서 c의 grad_fn에는 어떤 정보가 담기는지 확인해보겠습니다.</p>
<p>c의 grad_fn에서 next_functions를 보면 <strong>list 형식으로 (AccumulationGrad, 0)과 (None, 0)이라는 정보가 담겨있습니다.</strong></p>
<blockquote>
<p><strong>왜 list에 두개의 요소가 담겨있는가?</strong></p>
</blockquote>
<ul>
<li>우선 list가 두개의 요소를 가진 <strong>이유는 입력이 Tensor a, b로 두개</strong>이고, <strong>각 리스트의 요소는 순서대로 입력 순서에 해당하는 각 Tensor에 대한 Backward 연산 정보입니다.</strong></li>
</ul>
<blockquote>
<p><strong>그렇다면 Accumulation은 무엇이고 None은 무엇인가?</strong></p>
</blockquote>
<ul>
<li><strong>Accumulation</strong>은 지금까지 축적되어 온 <strong>Gradient를 해당 Tensor의 Grad에 저장하라는 의미</strong>를 가지며, <strong>None</strong>은 더이상 <strong>Gradient를 축적해 나아갈 필요가 없다는 것입니다.</strong></li>
</ul>
<blockquote>
<p><strong>그럼 왜 Tensor b에는 None이라는 함수가 지정된 것인가?</strong></p>
</blockquote>
<ul>
<li><p><strong>Tensor b의 requires_grad 변수</strong>를 살펴보면 <strong>False가 지정</strong>되어 있고 이는 <strong>Gradient 연산을 하지 않겠다는 의미</strong>를 가지고 있습니다. </p>
</li>
<li><p>이 과정은 Tensor 내부의 is_leaf, requires_grad 변수가 중요합니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/54cd2813-0f5b-4327-81e0-0bb50d477da6/image.png" alt=""></p>
<p>그렇다면 이 <strong>MulBackward는 어떻게 작동하는 것인가?</strong>에 대해서 다음 절에서 알아보겠습니다.</p>
<h3 id="operationadd-sub-mul">Operation(Add, Sub, Mul...)</h3>
<p>PyTorch에서는 <strong>널리 알려진 기본 연산</strong>인 사칙연산, 삼각함수, 지수, 로그 등에 대한 함수에 대한 <strong>Gradient Function에 대해 미리 정의</strong>해놨습니다.</p>
<p>이렇게 미리 정의된 연산에 대한 코드를 살펴보고자 여러 자료를 찾아봤지만 결국 못찾았습니다...</p>
<p>그래서 실제로 따로 <strong>grad_fn을 정의할 수 있는 코드를 기반으로 설명</strong>하고자 합니다.(내부적 동작은 유사하다고 판단) </p>
<p>어떤 <strong>$x$에 대한 함수 $P(x)$</strong>에 대해서 <strong>$P(x)=\frac{1}{2}(5x^3-3x), P&#39;(x) = \frac{3}{2}(5x^2-1)$로 정의</strong>한다고 할 경우 이 함수를 다음과 같이 <strong>AutoGrad에 정의</strong>할 수 있습니다.</p>
<pre><code class="language-python">import torch
import math


class LegendrePolynomial3(torch.autograd.Function):
    &quot;&quot;&quot;
    We can implement our own custom autograd Functions by subclassing
    torch.autograd.Function and implementing the forward and backward passes
    which operate on Tensors.
    &quot;&quot;&quot;

    @staticmethod
    def forward(ctx, input):
        &quot;&quot;&quot;
        In the forward pass we receive a Tensor containing the input and return
        a Tensor containing the output. ctx is a context object that can be used
        to stash information for backward computation. You can cache arbitrary
        objects for use in the backward pass using the ctx.save_for_backward method.
        &quot;&quot;&quot;
        ctx.save_for_backward(input)
        return 0.5 * (5 * input ** 3 - 3 * input)

    @staticmethod
    def backward(ctx, grad_output):
        &quot;&quot;&quot;
        In the backward pass we receive a Tensor containing the gradient of the loss
        with respect to the output, and we need to compute the gradient of the loss
        with respect to the input.
        &quot;&quot;&quot;
        input, = ctx.saved_tensors
        return grad_output * 1.5 * (5 * input ** 2 - 1)</code></pre>
<p>forward 메소드와 backward 메소드는 각각 다음과 같은 역할을 하게 됩니다.</p>
<ul>
<li><p><strong>forward</strong> : 순전파 단계에서 입력 Tensor($x$)를 저장하고 그 결과($P(x)$)를 출력합니다.</p>
</li>
<li><p><strong>backward</strong> : 축적되어 온 Gradient와 순전파 단계에서 저장된 Tensor($x$)를 기반으로 연산에 맞는 Gradient($P&#39;(x)$)를 전달합니다.</p>
</li>
</ul>
<p>그럼 위 클래스를 적용하게 되면 어떤 결과를 보이는지 확인해보겠습니다.</p>
<pre><code class="language-python">x= torch.tensor([1.],requires_grad=True)
print(x.data, x.is_leaf, x.grad, x.grad_fn, x.requires_grad)
# &gt; tensor([1.]) True None None True

y = LegendrePolynomial3.apply(x) # apply를 통해 grad 연산을 적용
print(y.data, y.is_leaf, y.grad, y.grad_fn, y.requires_grad)
# &gt; tensor([1.]) False None &lt;torch.autograd.function.LegendrePolynomial3Backward object at 0x000002027AEB7740&gt; True

print(y.grad_fn.next_functions)
# &gt; ((&lt;AccumulateGrad object at 0x000002027B579DC0&gt;, 0),)

y.backward()

print(x.data, x.is_leaf, x.grad, x.grad_fn, x.requires_grad)
# &gt; tensor([1.]) True tensor([6.]) None True

print(y.data, y.is_leaf, y.grad, y.grad_fn, y.requires_grad)
# &gt; tensor([1.]) False None &lt;torch.autograd.function.LegendrePolynomial3Backward object at 0x000002027AEB7740&gt; True</code></pre>
<p>이 과정에서 grad_fn의 역할을 확인하기 위해 <strong>네가지의 포인트</strong>를 확인해야합니다.</p>
<ol>
<li><p>순전파 과정에서 <strong>y.grad_fn</strong> 을 확인해보면 &lt;torch.autograd.function.LegendrePolynomial3Backward object at 0x000002027AEB7740&gt;를 확인할 수 있고 이는 <strong>grad_fn으로 직접 선언한 Polynomial3가 선언된 것을 확인할 수 있습니다.</strong></p>
</li>
<li><p>순전파 과정에서 <strong>y.grad_fn.next_functions</strong> 를 확인해보면 ((&lt;AccumulateGrad object at 0x000002027B579DC0&gt;, 0),)로 보아 <strong>입력은 하나이고 leaf Tensor이기 때문에 AccumulateGrad가 지정되어 있는 것을 확인할 수 있습니다.</strong></p>
</li>
<li><p>순전파 과정에서 <strong>y.data</strong>를 확인해보면 <strong>tensor([1.])으로 $P(1)=\frac{1}{2}(5(1)^3-3(1))=1$이 출력되는 것을 확인할 수 있습니다.</strong></p>
</li>
<li><p>역전파 y.backward() 이후의 <strong>x.grad</strong>를 확인해보면 <strong>$P&#39;(1)=\frac{3}{2}(5(1)^2 -1)=6$이 나오는 것을 확인할 수 있습니다. (grad_output=1 이므로[기본값])</strong></p>
</li>
</ol>
<p>즉, grad_fn에서는 순전파 과정과 역전파 과정에대한 <strong>연산 정보</strong>가 담겨있고, <strong>ctx</strong>를 기반으로 <strong>연산의 입력</strong>에 대해 다루게 되며, <strong>next_functions</strong>의 내용을 기반으로 <strong>계산된 Gradient를 전달</strong>하게 됩니다.</p>
<p>그렇다면 이렇게 계산된 <strong>Gradient를 전달하는 과정</strong>에 대해서 다음 절에서 더 자세히 다루겠습니다.</p>
<h1 id="역전파-과정">역전파 과정</h1>
<p>역전파 과정에서 Gradient를 전달하는 과정을 설명하기 위해 다음과 같이 $c = a \times b,d=c \times d$두개의 식을 통해 설명하겠습니다.</p>
<p>$$
c = a \times b
$$
<img src="https://velog.velcdn.com/images/pre_f_86/post/d23e880e-b906-4e88-aba3-8be6447b9ff5/image.png" alt=""></p>
<p>$$
e = c \times d
$$</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/15676ac0-a617-4982-a763-fd2a319364f5/image.PNG" alt=""></p>
<p>기본적인 연산의 흐름은 이전에 보았던 Tensor 연산과 크게 다르지 않습니다.</p>
<p>다른 점은 다음과 같습니다.</p>
<ol>
<li><p>Tensor b의 requires_grad 가 True가 되었고 이에 따라 <strong>c의 grad_fn의 next_functions의 두번째 튜플이 None이 아닌 AccumulateGrad 객체를 갖고 있다는 것</strong></p>
</li>
<li><p>Tensor c와 Tensor d와의 곱연산으로 생성된 <strong>Tensor e의 grad_fn의 next_functions의 첫번째 튜플은 AccumulateGrad가 아닌 MulBackward 객체를 갖고 있는 것</strong></p>
</li>
</ol>
<p>이러한 연산 그래프가 주어진다고 할 때 e.backward() 메소드를 실행하여 역전파를 진행한다고 할때 다음과 같이 진행됩니다.</p>
<ol>
<li><p><strong>e.backward()</strong>를 실행하면서 <strong>Gradient는 1로 기본값으로 e의 grad_fn인 MulBackward 전달</strong>합니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/4dc3345c-02f9-4799-adfb-318d4f501a8f/image.png" alt=""></p>
</li>
<li><p><strong>MulBackward 객체</strong>는 다음과 같은 <strong>backward() 메서드를 실행하여 Gradient를 반환</strong>합니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/0ea722ca-f4e5-4b61-be34-a135a0cc210d/image.png" alt=""></p>
<pre><code class="language-python"> @staticmethod
 def backward(ctx, grad_output):
     # grad_output = 1로 들어옴
     x1, x2 = ctx.saved_tensors # x1 = c, x2 = d
     return grad_output*x2, grad_output*x1 # c에 전달할 Gradient(=4), d에 전달할 Gradient(=6)</code></pre>
</li>
<li><p>계산된 Gradient를 <strong>next_functions에 있는 객체로 전달</strong>합니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/d94959a8-e78d-41fd-a49a-3b7b99eddf98/image.png" alt=""></p>
</li>
</ol>
<ol start="4">
<li><p>전달받은 Gradient를 통해 다음 연산을 진행합니다. [c:MulBackward, d:AccumulateGrad]</p>
<p> 4-1. <strong>MulBackward 객체는 이전과 동일한 backward() 메서드를 실행하여 Gradient를 반환</strong>합니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/88210e80-c95c-4858-a8d8-b5c0983168d3/image.png" alt=""></p>
<pre><code class="language-python">     @staticmethod
     def backward(ctx, grad_output):
         # grad_output = 4로 들어옴
         x1, x2 = ctx.saved_tensors # x1 = a, x2 = b
         return grad_output*x2, grad_output*x1 # c에 전달할 Gradient(=4*3), d에 전달할 Gradient(=4*2)</code></pre>
<p> 4-2. <strong>AccumulateGrad 객체</strong>는 전달받은 <strong>Gradient를 기반으로 d의 grad에 저장</strong>합니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/e188f4af-42e1-4190-b1f4-8dd336c8af11/image.png" alt=""></p>
</li>
<li><p>4-1에서 계산된 Gradient를 <strong>next_functions에 있는 객체로 전달</strong>합니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/69c80b9c-fc41-47e6-9267-300ed0b0ff6c/image.png" alt=""></p>
</li>
<li><p>전달받은 Gradient를 통해 다음 연산을 진행합니다. [a:AccumulateGrad, b:AccumulateGrad]</p>
<p> 6-1. <strong>AccumulateGrad 객체</strong>는 전달받은 <strong>Gradient를 기반으로 a의 grad에 저장합니다.</strong></p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/91235150-e3ad-46d5-bb45-cbf121ba904c/image.png" alt=""></p>
<p> 6-2. <strong>AccumulateGrad 객체</strong>는 전달받은 <strong>Gradient를 기반으로 b의 grad에 저장합니다.</strong></p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/e6f1028e-c9c8-4d08-9026-696814553678/image.png" alt=""></p>
</li>
<li><p>더 이상 전달될 Gradient가 없으므로 종료합니다.</p>
</li>
</ol>
<p>이렇게 간단하게 역전파 과정이 마무리 됩니다.</p>
<p><strong>지금까지 객체에 담긴 정보가 무엇이고 알아오는 과정은 굉장히 복잡한 것 같고 어려운 것 같았지만 이러한 이해를 바탕으로 역전파 과정을 살펴보면 매우 간단한 것을 알 수 있습니다.</strong></p>
<p>시간이 되신다면 <strong>자기가 구현하고 있는 모델의 연산 과정에 대해서 간단하게 그려보며 이해하면 더 쉽게 이해할 수 있으실 겁니다.</strong></p>
<h1 id="마무리">마무리</h1>
<p>이번 글에서는 <strong>AutoGrad의 작동 방식을 알기 위해 Tensor 연산 과정, ctx, grad_fn에 대한 이해를 가지면서 최종적으로 Backward의 과정을 따라가며 이해했습니다.</strong></p>
<p>이 글에 대해 <strong>이해를 하셨다면</strong> 앞으로 <strong>Gradient의 흐름을 제어하게 되는 경우에 문제없이 효율적으로 제어하실 수 있으실 겁니다.</strong></p>
<p>이 시리즈는 여기서 마치며 후에 <strong>Gradient의 흐름을 제어하는 방법인 requires_grad, torch.no_grad() 등에 대해서 다루는 글을 작성하겠습니다.</strong></p>
<p><strong>혹여나 틀린 부분이나 질문 사항 있으시면 편하게 댓글 달아주시면 됩니다.</strong></p>
<p>특히 코드 관련 자료 찾으신 분 계시다면 댓글 남겨주시면 감사드립니다!...</p>
<p>글을 작성하면서 ctx와 grad_fn을 찾아보면서 코드를 통해 그 과정을 깊게 이해하려고 하였으나 찾을 수가 없었습니다...</p>
<p><strong>References</strong></p>
<ul>
<li><p><strong><a href="https://discuss.pytorch.org/t/where-does-the-ctx-variable-come-from/191082">Where does the ctx variable come from? - discuss.pytorch.org</a></strong></p>
</li>
<li><p><strong><a href="https://www.askpython.com/python-modules/self-vs-ctx-pytorch">Understanding the Difference Between ‘self’ and ‘ctx’ in PyTorch - askpython</a></strong></p>
</li>
</ul>
<ul>
<li><p><strong><a href="https://stackoverflow.com/questions/49516188/difference-between-ctx-and-self-in-python">Difference between &#39;ctx&#39; and &#39;self&#39; in python? - stackoverflow</a></strong></p>
</li>
<li><p><strong><a href="https://pytorch.org/tutorials/beginner/examples_autograd/two_layer_net_custom_function.html">PyTorch: Defining New autograd Functions - pytorch.org/tutorials</a></strong></p>
</li>
<li><p><strong><a href="https://pytorch.org/docs/stable/generated/torch.autograd.function.FunctionCtx.save_for_backward.html">torch.autograd.function.FunctionCtx.save_for_backward - pytorch.org/docs</a></strong></p>
</li>
<li><p><strong><a href="https://pytorch.org/docs/stable/notes/extending.func.html">Extending torch.func with autograd.Function - pytorch.org/docs</a></strong></p>
</li>
<li><p><strong><a href="https://www.youtube.com/watch?v=MswxJw-8PvE">PyTorch Autograd Explained - In-depth Tutorial - Youtube</a></strong></p>
</li>
<li><p><strong><a href="https://wglite.medium.com/how-does-pytorch-calculate-gradient-a-programming-perspective-395988b68e35">How does PyTorch calculate gradient: a programming perspective - medium</a></strong></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PyTorch] AutoGrad란 무엇인가? (1)]]></title>
            <link>https://velog.io/@pre_f_86/PyTorch-AutoGrad%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-1</link>
            <guid>https://velog.io/@pre_f_86/PyTorch-AutoGrad%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-1</guid>
            <pubDate>Sat, 20 Apr 2024 18:24:16 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>PyTorch 프레임워크를 사용하면서 모델의 가중치를 고정하면서도, <strong>기존에 사용하던 방법들(requires_grad, no_grad(), detach() 등)의 차이와 언제 무엇을 사용해야하는지 몰랐습니다.</strong></p>
<p>그래서 <strong>requires_grad, no_grad(), detach() 등</strong> 여러 방법에 대해 정리하는 글을 작성하던 중 <strong>모두 가중치를 고정할 수 있다는 것은 알지만 그 이유에 대해서는 알지 못하였습니다.</strong> </p>
<p>결국 <strong>&#39;PyTorch의 순전파, 역전파 과정을 정확히 이해해야겠구나&#39;</strong>라는 생각이 들어 이 글을 먼저 작성하게 되었습니다.</p>
<p><strong>PyTorch에서 Gradient 계산을 하는 AutoGrad란 무엇이고 어떤 시스템을 따르는가?</strong>를 이해 하고자 합니다.</p>
<p>작동 방식만 알고자 하시는 분은 <strong><a href="https://velog.io/@pre_f_86/PyTorch-AutoGrad%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-2">[PyTorch] AutoGrad란 무엇인가? (2)</a></strong>로 넘어 가시면 됩니다.</p>
<h2 id="autograd">AutoGrad</h2>
<p>PyTorch를 사용하거나 관련 자료를 찾다보면 <strong>AutoGrad</strong>를 한번 쯤은 들어보셨을 겁니다.</p>
<blockquote>
<p><strong>Auto... Grad... 자동 미분? 뭐 그래디언트 계산해주는 거겠지, 자세히는 몰라도 돼~</strong></p>
</blockquote>
<p>저는 위처럼 생각했지만 <strong>모델의 구조가 복잡해지고 Gradient가 어떻게 흘러갈지 이해해야 하게 돼서 이제는 알아야 한다고 느껴집니다.</strong></p>
<p>그럼 AutoGrad란 무엇일까?</p>
<blockquote>
<p><strong>torch.autograd is PyTorch’s automatic differentiation engine that powers neural network training... [중략] Conceptually, autograd keeps a record of data (tensors) &amp; all executed operations (along with the resulting new tensors) in a directed acyclic graph (DAG) consisting of Function objects. - <a href="https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html">A Gentle Introduction to torch.autograd: pytorch.org</a></strong></p>
</blockquote>
<p>간단하게 설명하면 <strong>Directed Acyclic Graph(DAG)의 특성을 가진 연산을 진행</strong>하며 <strong>그 과정에서 발생하는 연산방법, 데이터 등에 대해 기록</strong>하고 <strong>Automatic Differentiation을 수행</strong>하여 <strong>학습이 가능하게 하는 것입니다.</strong></p>
<p>그렇다면 <strong>Directed Acyclic Graph</strong>는 뭐고 <strong>Automatic Differentiation</strong>은 무엇일까?</p>
<h3 id="directed-acyclic-graph-dag">Directed Acyclic Graph (DAG)</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c663fc25-b0bd-4600-8662-d9af0e5a7ee8/image.png" alt=""></p>
<blockquote>
<p><strong>In mathematics, particularly graph theory, and computer science, a directed acyclic graph (DAG) is a directed graph with no directed cycles. That is, it consists of vertices and edges (also called arcs), with each edge directed from one vertex to another, such that following those directions will never form a closed loop. - <a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">Directed acyclic graph: WIKIPEDIA</a></strong></p>
</blockquote>
<p>간단하게 말해서 <strong>정점(Vertex)과 간선(Edge)를 가진 그래프의 일종</strong>이라고 보면 됩니다.</p>
<p>Directed Acyclic Graph는 다음의 특성을 가집니다.</p>
<ul>
<li><p><strong>일방향성 특징</strong> : 간선 $e_1$로 연결된 두 정점 $v_1,v_2$는 $v_1 \rightarrow v_2$로만 향하거나 $v_2 \rightarrow v_1$로만 향할 수 있다.</p>
</li>
<li><p><strong>비순환적 특징</strong> : 특정 정점 $v$에서 시작하여 다시 정점 $v$로 돌아올 수 없다.</p>
</li>
</ul>
<p><strong>PyTorch의 AutoGrad는 연산 중 그래프를 만들게 될 때 DAG의 특징을 따르기 때문에 역전파 과정에서 이전 정점(파라미터)에 빠짐없이 Gradient를 전파할 수 있게됩니다.</strong></p>
<h3 id="automatic-differentiation-ad">Automatic Differentiation (AD)</h3>
<blockquote>
<p><strong>Automatic Differentiation : In mathematics and computer algebra, automatic differentiation (auto-differentiation, autodiff, or AD), also called algorithmic differentiation, computational differentiation, is a set of techniques to evaluate the partial derivative of a function specified by a computer program. - <a href="https://en.wikipedia.org/wiki/Automatic_differentiation">Automatic differentiation: WIKIPEDIA</a></strong></p>
</blockquote>
<p>Automatic Differentiation은 각 변수의 편미분 값을 구하기 위한 기술의 집합으로 주로 두가지로 나뉩니다.</p>
<ul>
<li><p><strong>Forward Accumulation</strong> : 입력 $x$로부터 시작해서 목표 변수로의 기울기를 알아낼 때 까지 기울기를 축적해 나아가는 방식. $\frac{\partial{w_i}}{\partial{x}}=\frac{\partial{w_i}}{w_{i-1}} \cdot \frac{w_{i-1}}{\partial{x}}$</p>
</li>
<li><p><strong>Reverse Accumulation</strong> : 출력 $y$로부터 시작해서 목표 변수로의 기울기를 알아낼 때 까지 기울기를 역으로 축적해 나아가는 방식 $\frac{\partial{y}}{\partial{w_i}}=\frac{\partial{y}}{w_{i+1}} \cdot \frac{w_{i+1}}{\partial{w_i}}$</p>
</li>
</ul>
<p>파이토치의 공식 논문을 살펴본다면 이 중 Reverse Accumulation을 사용한다고 나와있습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/20107d2b-459c-4fa8-ad1f-4be006e803cd/image.png" alt="Automatic differentiation in PyTorch - OpenReview"></p>
<p>그렇다면 이 <strong>Forward/Reverse Accumulation이 무엇</strong>이고 <strong>왜 Reverse Accumulation을 사용하는지</strong>에 대해 설명드리겠습니다.</p>
<p>두 방식에 대해 차이점에 대해 이해하기 위해 다음의 예시를 들겠습니다.</p>
<p>$$
\begin{matrix}
f(x,y)&amp; = &amp; ((x*y) + sin(x))  \
&amp;= &amp; ((w_1 * w_2) + sin(w_1)) \
&amp;= &amp; (w_3 + w_4) \
&amp;= &amp; w_5 \
\end{matrix}\
$$</p>
<h3 id="foward-accumultation">Foward Accumultation</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/cfe59dd7-1c29-4338-933f-dc0a6c2871a7/image.png" alt=""></p>
<blockquote>
<p><strong>먼저 미분을 수행할 독립변수를 고정하고 각 하위 표현식의 도함수를 재귀적으로 계산합니다.</strong></p>
</blockquote>
<p><strong>독립 변수 $x,y$ 중에서 $\frac{\partial{f}}{\partial{x}}=\frac{\partial{w_5}}{\partial{w_1}}$를 구하는 경우를 예시로 들어 다음과 같이 설명해보겠습니다.</strong></p>
<ol>
<li><p>우선 $\frac{\partial{w_1}}{\partial{w_1}}=1,\frac{\partial{w_2}}{\partial{w_2}}=0$로 초기화 하겠습니다.</p>
</li>
<li><p>$w_3$과 $w_4$를 각각 독립변수들에 대해 미분을 하게 되면 다음과 같습니다.</p>
<ul>
<li><p>$w_3&#39;=w_1&#39;w_2 + w_1w_2&#39;$ : 곱함수의 미분법</p>
</li>
<li><p>$w_4&#39;=cos(w_1)w_1&#39;$ : 삼각함수 미분법</p>
</li>
</ul>
</li>
<li><p>이 때 $w_1&#39; = \frac{\partial{w_1}}{\partial{w_1}}=1$ 이고 $w_2&#39;=\frac{\partial{w_2}}{\partial{w_2}}=0$이므로 $w_3&#39;=w_2$, $w_4&#39;=cos(w_1)$입니다.</p>
</li>
<li><p>$w_5$를 각 독립변수들에 대해 미분하게 되면 다음과 같습니다.</p>
<ul>
<li>$w_5&#39;= w_3&#39; + w_4&#39;$ : 일반적인 미분법</li>
</ul>
</li>
<li><p>이때 $w_3&#39;=w_2$, $w_4&#39;=cos(w_1)$ 이므로 $w_5&#39;= w_2+ cos(w_1)$이 됩니다.</p>
</li>
</ol>
<p><strong>이를 연쇄법칙으로 $\frac{\partial{f}}{\partial{x}}=\frac{\partial{w_5}}{\partial{w_1}}$를 구하는 과정을 보이면 다음과 같습니다.</strong></p>
<ol>
<li><p>$\frac{\partial{w_1}}{\partial{w_1}}=1$로 고정합니다.</p>
</li>
<li><p>$w_1$과 연관된 출력 $w_3,w_4$로 연쇄 법칙을 이어갑니다.</p>
<ul>
<li><p>$\frac{\partial{w_3}}{\partial{w_1}}\cdot \frac{\partial{w_1}}{\partial{w_1}}= w_2 \cdot 1$</p>
</li>
<li><p>$\frac{\partial{w_4}}{\partial{w_1}}\cdot \frac{\partial{w_1}}{\partial{w_1}}= cos(w_1) \cdot 1$</p>
</li>
</ul>
</li>
<li><p>$w_3,w_4$ 모두 출력 $w_5$와 연관되어 있으므로 연쇄법칙을 이어갑니다.</p>
<ul>
<li><p>$\frac{\partial{w_5}}{\partial{w_3}}\cdot\frac{\partial{w_3}}{\partial{w_1}}\cdot \frac{\partial{w_1}}{\partial{w_1}} = 1 \cdot w_2 \cdot 1$</p>
</li>
<li><p>$\frac{\partial{w_5}}{\partial{w_4}}\cdot\frac{\partial{w_4}}{\partial{w_1}}\cdot \frac{\partial{w_1}}{\partial{w_1}} = 1 \cdot cos(w_1) \cdot 1$</p>
</li>
</ul>
</li>
<li><p>이때 $w_5$에 두 경로로 들어오게 되므로 최종적으로 $\frac{\partial{w_5}}{\partial{w_1}}$는 다음과 같이 정의 됩니다.</p>
<ul>
<li>$\frac{\partial{w_5}}{\partial{w_3}}\cdot\frac{\partial{w_3}}{\partial{w_1}}\cdot \frac{\partial{w_1}}{\partial{w_1}}+\frac{\partial{w_5}}{\partial{w_4}}\cdot\frac{\partial{w_4}}{\partial{w_1}}\cdot \frac{\partial{w_1}}{\partial{w_1}}=1 \cdot w_2 \cdot 1 + 1 \cdot cos(w_1) \cdot 1= w_2+cos(w_1)$</li>
</ul>
</li>
</ol>
<p>위의 예시로 보면서 살펴 보았을 때 <strong>Foward Accumulation의 특징은 특정 입력 변수로부터 시작하여 특정 입력에 대한 모든 출력의 변화량을 계산하게 됩니다.</strong></p>
<p><strong>입력 변수의 개수가 많아지게 되면 각 입력 변수에 대해 위와 같은 과정을 한번 씩 진행해야 하므로 연산량이 크게 증가</strong>하게 됩니다.</p>
<p>이러한 이유로 <strong>Foward Accumulation은 입력이 적은 경우</strong>에 자주 쓰이며, <strong>특정 입력에 대한 출력의 변화량이 중요한 경우에 사용</strong>됩니다.</p>
<h3 id="reverse-accumultation">Reverse Accumultation</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/97d1fc9d-82ee-4b70-b91f-c41559594aed/image.png" alt=""></p>
<blockquote>
<p><strong>의존 변수는 고정되어 있으며 각 하위 표현식에 대해 재귀적으로 도함수를 계산합니다.</strong></p>
</blockquote>
<p><strong>의존 변수 $f(x,y)=w_5$ 에 대해서 $\frac{\partial{f}}{\partial{x}}=\frac{\partial{w_5}}{\partial{w_1}}$를 구하는 경우를 예시로 들어 다음과 같이 설명해보겠습니다.</strong></p>
<ol>
<li><p>우선 $\frac{\partial{w_5}}{\partial{w_5}}=1$로 초기화 하겠습니다.</p>
</li>
<li><p>$w_5$ 에 연관된 입력 $w_3,w_4$에 대한 변화량을 구하게 되면 다음과 같습니다.</p>
<ul>
<li><p>$\frac{\partial{w_5}}{\partial{w_3}}=1$</p>
</li>
<li><p>$\frac{\partial{w_5}}{\partial{w_4}}=1$</p>
</li>
</ul>
</li>
<li><p>$w_3&#39;$에 연관된 입력 $w_1,w_2$에 대한 변화량을 구하게 되면 다음과 같습니다.</p>
<ul>
<li><p>$\frac{\partial{w_3}}{\partial{w_1}}=w_2$</p>
</li>
<li><p>$\frac{\partial{w_3}}{\partial{w_2}}=w_1$</p>
</li>
</ul>
</li>
<li><p>동일하게 $w_4&#39;$에 연관된 입력 $w_1$에 대한 변화량을 구하게 되면 다음과 같습니다.</p>
<ul>
<li>$\frac{\partial{w_4}}{\partial{w_1}}=cos(w_1)$</li>
</ul>
</li>
<li><p>이때 $w_5$에서 $w_1$으로 들어오는 모든 경로를 연쇄법칙과 함께 표현하면 다음과 같습니다.</p>
<ul>
<li><p>$\frac{\partial{w_5}}{\partial{w_5}}\cdot\frac{\partial{w_5}}{\partial{w_3}}\cdot \frac{\partial{w_3}}{\partial{w_1}}=1\cdot 1 \cdot w_2$</p>
</li>
<li><p>$\frac{\partial{w_5}}{\partial{w_5}}\cdot\frac{\partial{w_5}}{\partial{w_4}}\cdot \frac{\partial{w_4}}{\partial{w_1}}=1\cdot 1 \cdot cos(w_1)$</p>
</li>
</ul>
</li>
<li><p>최종적으로 $\frac{\partial{w_5}}{\partial{w_1}}$을 다음과 같이 표현할 수 있습니다.</p>
<ul>
<li>$\frac{\partial{w_5}}{\partial{w_5}}\cdot\frac{\partial{w_5}}{\partial{w_3}}\cdot \frac{\partial{w_3}}{\partial{w_1}}+\frac{\partial{w_5}}{\partial{w_5}}\cdot\frac{\partial{w_5}}{\partial{w_4}}\cdot \frac{\partial{w_4}}{\partial{w_1}}=1\cdot 1 \cdot w_2+1\cdot 1 \cdot cos(w_1)=w_2+cos(w_1)$</li>
</ul>
</li>
</ol>
<p>위의 예시로 보면서 살펴 보았을 때 <strong>Reverse Accumulation의 특징은 특정 출력 변수로부터 시작하여 모든 입력들에 대한 특정 출력의 변화량을 계산하게 됩니다.</strong></p>
<p>위의 예시에서는 최종 출력이 $w_5$ 하나이지만, <strong>출력이 많아진다면 Foward Accumulation 과정과 동일하게 출력을 하나씩 고정하며 기울기 값을 알아내야 합니다.</strong></p>
<p>그렇기 때문에 이 방식은 출력이 많아지게 된다면 <strong>각 출력 변수에 대해 위와 같은 과정을 한번 씩 진행해야 하므로 연산량이 크게 증가</strong>하게 됩니다.</p>
<p>이러한 이유로 <strong>Reverse Accumulation은 출력이 적은 경우</strong>에 자주 쓰이며, <strong>입력에 대한 특정 출력의 변화량이 중요한 경우에 사용</strong>되게 됩니다.</p>
<p>이러한 특성때문에 <strong>많은 파라미터에 대한 출력의 변화량이 중요한 머신러닝과 딥러닝에서</strong>는 Reverse Accumulation이 주로 사용되게 됐으며, <strong>PyTorch 또한 이러한 흐름을 따라갑니다.</strong></p>
<h2 id="마무리">마무리</h2>
<p>이번 글은 <strong>&#39;AutoGrad란 무엇이고 어떤 시스템을 따르는가?&#39;</strong>에 대해서 설명하였습니다.</p>
<p>AutoGrad의 작동과정을 이해하기 전에 AutoGrad가 어떤 시스템을 따르는지 알고 가는 것이 좋다고 생각하였고 DAG와 AD를 이해하고 설명하였습니다.</p>
<p>DAG 부분까지는 쉽게 이해하였지만, <strong>Foward Accumulation</strong>과 <strong>Reverse Accumulation</strong> 사이의 차이를 이해하는데 어려워 고생하였습니다...</p>
<p>잘못 된 부분이 있다면 댓글 남겨주시면 감사드리겠습니다.</p>
<p>다음 글에서는 &#39;AutoGrad의 작동 과정&#39;에 대해서 설명을 드리도록 하겠습니다.</p>
<p><strong>References</strong></p>
<ol>
<li><p><strong><a href="https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html">A Gentle Introduction to torch.autograd - PyTorch.org</a></strong></p>
</li>
<li><p><strong><a href="https://openreview.net/forum?id=BJJsrmfCZ">Automatic differentiation in PyTorch - OpenReview</a></strong></p>
</li>
<li><p><strong><a href="https://stackoverflow.com/questions/64856195/what-is-tape-based-autograd-in-pytorch">What is tape-based autograd in Pytorch? - Stack Overflow</a></strong></p>
</li>
<li><p><strong><a href="https://en.wikipedia.org/wiki/Automatic_differentiation">Automatic differentiation - WIKIPEDIA</a></strong></p>
</li>
<li><p><strong><a href="https://rufflewind.com/2016-12-30/reverse-mode-automatic-differentiation">Reverse-mode automatic differentiation: a tutorial - Rufflewind&#39;s Scratchpad</a></strong></p>
</li>
<li><p><strong><a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">Directed acyclic graph - WIKIPEDIA</a></strong></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[SKT FLY AI Challenger 4기 수료 후기]]></title>
            <link>https://velog.io/@pre_f_86/SKT-FLY-AI-Challenger-4%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@pre_f_86/SKT-FLY-AI-Challenger-4%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 01 Mar 2024 09:36:44 GMT</pubDate>
            <description><![CDATA[<h1 id="최고의-경험">최고의 경험</h1>
<p><strong>좋은 팀을 만나 좋은 환경에서 수상까지!</strong></p>
<p><strong>FLY AI 수상자 인터뷰 : <a href="https://www.trendw.kr/news/articleView.html?idxno=10602">웹소설 숏폼 생성Ai 프로젝트, 노벨티(Novel T) ... - 트렌드 와칭</a></strong></p>
<p><strong>코드-GitHub : <a href="https://github.com/PreFKim/NovelT-SKT-FLY-AI-Passion2">NovelT-SKT-FLY-AI-Passion2</a></strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/094938b7-7d74-4f8c-bd15-cc7e6805d4bc/image.png" alt="광민이형, 지현이, 유리, 나, 준혁이"></p>
<h2 id="빠른-10주간의-교육">빠른 10주간의 교육</h2>
<p>SKT FLY AI에서 교육을 들으며 정말 바쁘게 방학을 보낸 거 같습니다...</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/531081fc-9bae-4fae-8cec-1430caafd814/image.png" alt=""></p>
<p>12월 말에 어리둥절하며 같이 교육받는 사람들과 인사를 하던게 정말 엊그제 같은데 벌써 수료식을 마쳤습니다.</p>
<p>수료를 하고 느낀 건데 다른 사람들과 더 많은 대화를 나누지 못한게 아쉬웠습니다... (사진도 너무 적은 거 같기도 하고..)</p>
<p>좋은 경험들을 해온 실력자도 많고, 열심히 하는 사람도 많고 정말 다양한 사람들이 모였습니다.</p>
<p>그래서 더 아쉬운 거 같습니다...</p>
<hr>
<p>교육을 받으며 제가 알고 있었다고 생각한 부분이 틀린 내용도 있고 새로 알게된 부분도 정말 많았습니다.</p>
<blockquote>
<p><strong>이래서 사람들의 다양한 생각이 중요한 거 같다.</strong></p>
</blockquote>
<p>또한 깃허브, 도커, 쿠버네티스, 애져에 대한 교육이 너무 뜻 깊었습니다.</p>
<p>특히 깃허브는 배우면서 패키지로 관리하는 방법을 따로 더 공부하고 아래처럼 코드 관리하는게 너무 편해졌습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f62db6a0-bdb9-45ee-87b4-e93ca02fc90f/image.png" alt=""></p>
<p>자세한 교육 내용들은 다른 수강생들 블로그에 자세히 적혀 있어서 참고하시면 될 거 같습니다.</p>
<h2 id="skt-fly-ai의-장단점">SKT FLY AI의 장단점?</h2>
<p>FLY AI의 후기들을 보고 제가 느낀점을 보니 정말로 수강생들의 피드백이 즉각 반영되는 것을 느낍니다.</p>
<p>단점은 별로 없던 거 같습니다.</p>
<h3 id="장점">장점</h3>
<ol>
<li><p><strong>정말 좋은 환경</strong></p>
<p> FLY AI를 수강하며 환경에 대한 불평불만은 한 번도 들은 적이 없는 거 같습니다.</p>
<p> 주로 활동은 보라매에서 하지만 프로젝트 기간에서는 T 타워에서 할 수 있습니다.</p>
<blockquote>
<p><strong>보라매</strong>는 <strong>공간이 매우 넓고 콘센트도 널널하고 간식도 많다</strong>는 특징이 있고 
 <strong>T 타워</strong>는 <strong>팀만의 개인적인 공간으로 조용하고 자유롭게 프로젝트를 진행</strong>하는 특징이 있습니다.</p>
</blockquote>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/a0677777-7f38-4f74-bd5a-b69f05a8dd9a/image.png" alt=""></p>
<p> 특히 <strong>매니저님들과 부장님이 수강생들의 피드백을 들어주시고자 노력해주시는 모습</strong>에서 수강생들을 챙겨주시려는 모습에 더 열심히 했던 거 같습니다. ( 감사합니다! )</p>
<p> 이번 수강생들에게 들었던 피드백들을 또 반영해서 다음 기수에 영향을 줄 것 같습니다.</p>
</li>
<li><p><strong>현실적인 프로젝트와 많은 피드백</strong></p>
<p> 진짜 중요한 부분이라고 생각합니다.</p>
<p> 프로젝트를 진행하면서 교육생들은 실제 현업과 비슷하게 <strong>시장성, 기술, ESG 부분</strong> 등 신경을 써야할 부분이 많습니다.</p>
<p> 이렇기 때문에 매주 수요일마다 교수님들에게 발표를 하며 발표 내용 중 추가, 삭제, 수정할 점 등에 대해서 자세하게 피드백 해주십니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/5e0b4890-6681-400c-8b63-f4d59195ad28/image.png" alt=""></p>
<blockquote>
<p><strong>프로젝트를 진행하며 신경써야할 부분이 너무 많아 괴롭지만 이런 부분을 신경쓰며 프로젝트를 기획하는 경험은 그 무엇보다 값진 경험이었다.</strong></p>
</blockquote>
<p> 그동안 개인적으로 진행하던 프로젝트와는 달리 자료조사, 시장성 등을 진지하게 하나하나 분석하며 어떤 문제점을 해결해 나아가야하는지를 알아가는 점이 신선했습니다.</p>
<p> <strong>특히 SKT에서 활동하고 계시는 현업자분을 멘토로 두게 되며 멘토님의 진심이 담긴 피드백을 받게 됩니다.</strong></p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/d03d6f42-314e-4534-9679-8725531eafbc/image.png" alt=""></p>
<p> <strong>※ 배주한 멘토님 너무 감사드립니다.</strong></p>
</li>
<li><p><strong>목요일의 아침</strong></p>
<p> 목요일 FLY AI의 아침은 항상 설레었습니다 ㅋㅋㅋ</p>
<p> 제가 제일 좋아하는 라면이 양은냄비로 꼬들꼬들하게 끓여져 나옵니다.</p>
<p> <img src="https://velog.velcdn.com/images/pre_f_86/post/83170f94-5cb1-4a49-b669-899a7eb886f7/image.png" alt=""></p>
<p> 정말 <strong>개인적인 취향이지만 저에게는 너무 큰 장점으로 다가왔습니다.</strong> 오게 된다면 꼭 드셔보세요!</p>
</li>
<li><p><strong>짧은 교육 기간</strong></p>
<p> 다른 KDT와는 달리 기간이 매우 짧은 편입니다. </p>
<p> 이 부분이 오히려 메리트로 다가왔던 점은 학기에 영향을 주지 않는다는 점이 있습니다.</p>
<blockquote>
<p>이제 4학년이 되어가는 시점에서 6개월의 교육을 받게 되는 경우 한 학기를 쉬어야 한다는 점이 있지만, <strong>SKT FLY AI는 방학기간에 진행되기 때문에 오히려 큰 메리트</strong>로 다가왔다.</p>
</blockquote>
<p> 미래에 대학원이나 빠른 취업 준비를 원하는 대학생에게는 꼭 추천하고 싶습니다.</p>
<p> KDT의 장점을 버리지 않고 방학 기간을 <strong>진짜 정말 알차게 보낼 수 있는 시간이 됩니다.</strong></p>
</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li><p><strong>짧은 교육 기간</strong></p>
<p> 이 부분은 사실 장점이자 단점인 거 같습니다...</p>
<p> 비전공자를 생각하여 파이썬 문법과 같은 기초 교육이 들어있긴 하지만 실질적으로 짧은 기간이기 때문에 <strong>독학은 필수적</strong>입니다.</p>
<p> 그래도 다행인 것은 6시 이후로 남아서 10시 까지 공부하고 귀가해도 된다는 점입니다.</p>
<blockquote>
<p>실제 수강생들 중에서 남아서 따로 더 공부하는 사람들도 많았고 수상하기도 했습니다!</p>
</blockquote>
</li>
</ol>
<h2 id="수료식">수료식</h2>
<p>바쁘고도 어지러운 하루가 된 거 같습니다.</p>
<h3 id="시연-부스-준비-08300920">시연 부스 준비 (08:30~09:20)</h3>
<p>아침부터 시작된 것은 포토존에서 간단한 사진 찍기와 각 팀의 프로젝트를 선보일 부스를 운영합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/63c91a92-0d96-4434-b51f-a5e3c64c40e7/image.png" alt=""></p>
<p>수료증을 보기 전까지는 크게 와닿지는 않았는데 이 사진을 보니 이제 진짜 SKT FLY AI를 마무리 한다는 생각이 와닿았습니다.</p>
<p>이제 힘든 프로젝트를 마무리한다는 마음에 홀가분 하기도 하고 한 켠으로는 아쉽기도 했습니다...</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f134e554-602c-406a-990d-b81927fd4b90/image.png" alt=""></p>
<p>급하게 들어와서 사진 한번 찍고 발표 시작 전 시연 부스를 운영합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d9fabd98-0989-426d-9593-ab794915190c/image.png" alt=""></p>
<h3 id="시연-부스운영-09200950">시연 부스운영 (09:20~09:50)</h3>
<p>이때 다들 부스 앞에 있어서 사진은 없지만...</p>
<p>심사위원분들께 저희 NovelT 서비스를 시연하며 어떤 방식으로 작동하는지 보여드립니다.</p>
<h3 id="발표-10001540">발표 (10:00~15:40)</h3>
<p>이제부터 각 팀이 준비한 발표내용을 기반으로 10분 발표하고 10분 동안 질의응답을 합니다.</p>
<p>저희는 12:00~12:20 사이에 발표를 진행하게 됐습니다.</p>
<p><strong>발표는 지현이가 담당</strong>하고, <strong>나머지 질의응답은 팀이 대답</strong>하는 방식으로 전략을 구성했습니다.</p>
<p>저는 <strong>기술 담당 질의응답을 맡게 되는데 처음엔 불안하다가 나중에는 해탈했습니다</strong></p>
<blockquote>
<p><strong>얼른 설명하고 싶고 모든 걸 보여주고 싶다.</strong></p>
</blockquote>
<p>나중에 이 생각은 질의응답 때 폭발하게 됩니다..</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/cfa2cb93-4448-4bcc-8825-67e11385fad6/image.png" alt=""></p>
<p>지현이가 발표를 정말 너무 깔끔하게 잘해서 이제는 <strong>존경스러울 경지</strong>까지 왔습니다.</p>
<p>발표가 끝나고 질의응답 시간이 되어 질문을 받게 되는데...</p>
<p>PPT에 기술적 내용이 간단하게 들어가서 그런지 모든 질문이 다 기술적 질문이었어서 거의 발표하는 것처럼 질문에 대한 대답을 이어갔습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/36f21015-5d3a-453f-b666-cf07eaff93f7/image.png" alt=""></p>
<p>긴장해서 그런지 발표 전의 생각이 터져버린 것처럼 모든 질문에 답을 끊임 없이 말하면서 움직였습니다.</p>
<p>팀원이 질의응답하는 모습을 보고 &quot;<strong>자기만의 세상에 빠져버린 CTO 같았다.</strong>&quot;, &quot;<strong>박찬호가 온 줄 알았다</strong>&quot;고 합니다. ㅋㅋㅋ </p>
<p>※ <strong>처음엔 팀원이랑 가까운 위치에서 가만히 질의응답하다가 심취해서 건너편으로 가있어서 중간에 웃참까지 했다고...</strong></p>
<p>그래도 질의응답 때 생각보다 답변을 잘 한 거 같아서 정말 마음이 푹 놓이고 &#39;진짜 끝났다&#39;라는 생각이 들어서 홀가분해졌습니다.</p>
<h3 id="2차-시연-16001630">2차 시연 (16:00~16:30)</h3>
<p>이때 SKT 부사장님께서 오셔서 각 팀들의 프로젝트 시연영상을 보시게 됩니다.</p>
<p><strong>정말 괜찮은 아이디어를 가진 팀에게는 나중에 한 번 만나서 얘기해보자고 제안까지 하십니다.(축하한다 성원아)</strong></p>
<h3 id="축사-및-영상시청-16301640">축사 및 영상시청 (16:30~16:40)</h3>
<p>모든 팀의 시연이 끝나고 SKT CLO 님의 축사를 기점으로 10주간의 활동을 담은 영상을 시청하게 됩니다.</p>
<p>영상에 내 얼굴이 몇번 나왔나부터 계산하면서 봤던 거 같아요 ㅋㅋ</p>
<h3 id="시상식-16401720">시상식 (16:40~17:20)</h3>
<p>대망의 시상식의 시간이 되었습니다.</p>
<p>순서는 <strong>개근상, 우수 교육생, 팀 수상의 순서</strong>로 진행 됐습니다.</p>
<p>면접으로 인해 조퇴도 있고 그래서 개근상은 못받았지만 <strong>&#39;우수 교육생은 받을 수 있겠지?&#39;</strong> 라는 생각을 했지만 결국 못받았습니다...</p>
<p>이후 팀 프로젝트 부문에서 시상을 시작합니다.</p>
<blockquote>
<p><strong>&#39;다른 팀들도 너무 잘한 거 같아서 4등안에 들기 어려울 거 같은데...&#39;</strong></p>
</blockquote>
<p>다른 팀들도 발표도 잘 했어서 정말 알 수 없을 거 같았습니다.</p>
<p>우수상 부터 시작하는데 처음에 저희 팀이 아니어서 더더욱 불안해 했던 거 같습니다.</p>
<p>다음 수상 팀을 발표하는데 &#39;열정 2조&#39;라는 소리가 들렸습니다.</p>
<blockquote>
<p><strong>열정 2조? 우리팀인가? 이거 좋아해도 되는 건가? 근데 우리팀 아니면 어쩌지? 열정 2조 우리팀 맞잖아?</strong></p>
</blockquote>
<p>0.1초도 안돼서 저 흐름으로 생각한 거 같습니다 ㅋㅋㅋ.</p>
<p>바로 팀원들이랑 눈 마주치고 수상한 거 맞구나 하고 기뻐서 소리치면서 나갔던 거 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/71c2586b-ea7b-4987-8d32-5d560e836bf9/image.png" alt=""></p>
<blockquote>
<p><strong>사진 너머까지 느껴지는 행복한 미소.</strong></p>
</blockquote>
<p><strong>그동안의 팀원들과 저의 고생을 상으로 보답받는 느낌이었습니다.</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/ca9df805-ff06-4178-aa51-f0e484d51ce7/image.png" alt=""></p>
<p><strong>※ 다들 발표 준비, 현업자 인터뷰, 개발하느라 너무 고생 많았어!</strong></p>
<h2 id="마무리">마무리</h2>
<h3 id="skt-fly-ai-4기">SKT FLY AI 4기</h3>
<blockquote>
<p><strong>간혹 지나가며 얘기하면서 제 벨로그에 있는 후기를 보셨다고 하시는 분들도 계셔서 혹시 몰라서 이렇게 글 남겨 봅니다! 더 많은 분들과 얘기를 나누어보지는 못하였지만 10주간 고생 많으셨습니다. 같이 공부하며 제가 전혀 생각하지 못했던 방법으로 문제 해결해가는 방식을 보여주시기도 하고, 저에게 깨달음을 주시던 분들도 계셨습니다. 정말 FLY AI를 지원하며 지원동기를 쓸 때 생각했던 모든 말들이 하나하나 이루어지는 것을 보니 이번에 지원하길 잘했다는 생각이 듭니다. 특히 수강생들의 환경에 신경 써주신 본부장님, 매니저님들, 교수님들 정말 감사드립니다.</strong></p>
</blockquote>
<h3 id="축하한다-이성원">축하한다 이성원</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/1cce8386-a5bc-4e49-8bbc-8f58cf80dac7/image.png" alt=""></p>
<p>저희 프로젝트를 진행하며 웹 소설 작가님께 받은 멘트를 올리며 마무리하겠습니다.</p>
<blockquote>
<p><strong>작게 반짝이며 물결치기 시작한 아이디어의 시작이 커다란 흐름이 되어 세상을 바꾸어 갈 수 있기를 진심으로 응원합니다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[딥러닝] 경사하강법 구현부터 학습까지 (2)]]></title>
            <link>https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-2</link>
            <guid>https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-2</guid>
            <pubDate>Sun, 04 Feb 2024 03:33:18 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p><strong><a href="https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-1">이전 글</a></strong>에서는 <strong>전체 함수와 그에 대한 미분 함수를 미리 정의</strong> 하여 각 가중치에 대한 함수의 기울기를 구하여 경사하강법을 적용하였습니다.</p>
<p>하지만 실제 함수는 생각보다 복잡하기 때문에 미분함수를 미리 정의하기는 어렵습니다.</p>
<p>이번 글에서는 <strong>위 문제점을 해결해 나아가는 과정을 설명</strong>하고 이후 <strong>순전파와 역전파에 대한 개념 설명</strong> 후 간단한  하도록 하겠습니다.</p>
<h2 id="미분함수-정의-방법의-문제점">&#39;미분함수 정의&#39; 방법의 문제점</h2>
<p>이전 글에서처럼 미분함수를 미리 정의 하여 각 가중치에 대한 함수의 기울기를 구하는 경우 다음과 같은 문제점이 발생합니다.</p>
<ul>
<li><p><strong>모델 구조 변경의 어려움</strong> : 
함수(레이어) $f(x)$로 구성된 모델에 대해 새로운 함수(레이어) $g(x)$를 추가하여 $g(f(x))$ 구조의 모델을 만든다고 할 때, <strong>모든 가중치에 대한 미분함수 $\frac{\partial{g(f(x))}}{\partial{w_{i}}}$,$\frac{\partial{g(f(x))}}{\partial{b_{i}}}$를 새로 정의</strong>해야합니다.</p>
</li>
<li><p><strong>ReLU 함수같은 함수 구현의 어려움</strong> : 
ReLU 함수의 수식은 다음과 같이 정의 됩니다.
$$
f(x)=
\begin{cases}
x, &amp; x&gt;0  \
0, &amp; x&lt;=0
\end{cases}\
$$
만약 복잡한 구조를 가진 함수 중간에 ReLU 함수를 넣게 된다면 <strong>ReLU 함수의 입력 값을 기록하며 이에 맞게 최종 미분 함수를 선택하도록 알고리즘을 작성</strong>해야 합니다.</p>
</li>
</ul>
<h3 id="미분-공식-활용-방법을-통한-해결">&#39;미분 공식 활용&#39; 방법을 통한 해결</h3>
<p>위의 문제를 간단히 미분 공식을 통해 해결할 수 있습니다.</p>
<p>$$
\frac{\partial{f(x,y)}}{\partial{x}} = \lim_{h\rightarrow0}{\frac{f(x+h,y)-f(x,y)}{h}}
$$
$$
\frac{\partial{f(x,y)}}{\partial{y}} = \lim_{h\rightarrow0}{\frac{f(x,y+h)-f(x,y)}{h}}
$$</p>
<p>여기서 $h$는 0에 매우 가까운 수라고 할 때 다음과 같이 코드로 구현할 수 있습니다.</p>
<h4 id="편미분-예시-코드">편미분 예시 코드</h4>
<p>구하고자 하는 변수의 값만 변화시키는 방식</p>
<pre><code class="language-python">def diff(func,input1,input2):
    ret = (func(*input1)-func(*input2))/1e-10
    return ret

def f(x,y):
    return x**2-2*y

eps = 1e-10
print(
    diff(f,[2+eps,0],[2,0]), 
    diff(f,[0,2+eps],[0,2])
)
# 4.000000330961484 -2.000000165480742

def custom_relu(x):
    if x&gt;0: return 2*x
    else : return 0

print(
    diff(custom_relu,[1+eps],[1]), 
    diff(custom_relu,[-1+eps],[-1])
)
# 2.000000165480742 0.0</code></pre>
<p>함수를 새로 만들더라도 <strong>변수에 대한 정보만 알고 있다면 간단하게 편미분이 가능</strong>한 모습을 볼 수 있습니다.</p>
<h4 id="합성함수">합성함수</h4>
<pre><code class="language-python">
def composite_function(x,y):
    return custom_relu(f(x,y))

print( #Custom ReLU 함수의 입력이 양수가 되는 경우
    diff(composite_function,[3+eps,2],[3,2]), 
    diff(composite_function,[3,2+eps],[3,2])
)
# 12.000000992884452 -4.000000330961484

print( #Custom ReLU 함수의 입력이 음수가 되는 경우
    diff(composite_function,[1+eps,2],[1,2]), 
    diff(composite_function,[1,2+eps],[1,2])
)
# 0.0 0.0</code></pre>
<p>두 함수를 합성하더라도 <strong>새로운 미분함수를 정의할 필요 없이 기울기값을 구할 수 있습니다.</strong></p>
<h3 id="새로운-함수의-중복-실행-문제점">새로운 &#39;함수의 중복 실행&#39; 문제점</h3>
<p>위의 두 문제를 해결했지만 여기서 또 다른 문제점이 발생합니다.</p>
<blockquote>
<p><strong>함수의 중복 실행</strong>
합성함수 $h(g(f(x_0,x_1,x_2,...,x_n)))$에서 각 변수 $x_i$에 대한 함성함수 기울기를 구하기 위해서 함수$g$와 함수 $h$를 중복해서 실행하게 됩니다.</p>
</blockquote>
<p>만약 <strong>전체 함수가 복잡해지고 연산이 많아질 경우</strong>에 이런 문제점은 <strong>연산속도에 악영향을 줍니다.</strong></p>
<p>이를 해결하기 위해 <strong>연쇄법칙(Chain Rule)을 활용한 순전파와 역전파가 등장</strong>합니다.</p>
<h2 id="연쇄법칙chain-rule">연쇄법칙(Chain Rule)</h2>
<blockquote>
<p><strong>연쇄법칙</strong> : 합성함수에 대한 미분방법 중 하나로 합성 함수의 미분값은 합성함수를 구성하는 <strong>각 함수의 목표 변수에 대한 함수의 편미분값의 곱</strong>으로 표현할 수 있다.</p>
</blockquote>
<p><strong>※ 자세한 정의는 <a href="https://namu.wiki/w/%EC%97%B0%EC%87%84%20%EB%B2%95%EC%B9%99">나무위키</a>를 참고해주세요</strong></p>
<p>아래와 같이 간단한 합성함수를 정의하고 $x_1, x_2$에 대한 편미분을 다음처럼 표현할 수 있습니다.(<strong>&#39;단일 변수 함수&#39;</strong>의 연쇄법칙)</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/66b3addb-5985-4315-a2dd-ba28d5d43b5d/image.png" alt=""></p>
<p>$$
i(g(f(x_1,x_2))) \rightarrow \frac{\partial{i(g(f(x_1,x_2)))}}{\partial{x_1}}, \quad \frac{\partial{i(g(f(x_1,x_2)))}}{\partial{x_2}}
$$</p>
<p>연쇄법칙은 위 과정을 다음과 같이 간단하게 여러 곱으로 표현할 수 있습니다.</p>
<p>$$
i(g(f(x_1,x_2))) \rightarrow \frac{\partial{f}}{\partial{x_1}} \cdot \frac{\partial{g(f)}}{\partial{f}} \cdot \frac{\partial{i(g(f))}}{\partial{g(f)}} 
$$
$$
i(g(f(x_1,x_2))) \rightarrow \frac{\partial{f}}{\partial{x_2}} \cdot \frac{\partial{g(f)}}{\partial{f}} \cdot \frac{\partial{i(g(f))}}{\partial{g(f)}} 
$$</p>
<p>위와는 달리 아래와 같이 중간에 여러 노드로 나뉘는 경우가 있을 수도 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/4a533a14-2f85-4c4d-abfe-9243e743a793/image.png" alt=""></p>
<p>이 경우에도 $x_1$에 대한 편미분을 간단하게 다음과 같이 표현할 수 있습니다.(<strong>&#39;다중 변수 함수&#39;</strong>의 연쇄법칙)</p>
<p>$$
i(g,h) \rightarrow \frac{\partial{f}}{\partial{x_1}} \cdot \frac{\partial{g(f)}}{\partial{f}} \cdot \frac{\partial{i(g(f))}}{\partial{g(f)}} +
\frac{\partial{f}}{\partial{x_1}} \cdot \frac{\partial{h(f)}}{\partial{f}} \cdot \frac{\partial{i(h(f))}}{\partial{h(f)}} 
$$</p>
<h4 id="응용-문제">응용 문제</h4>
<p>위의 내용을 기반으로 $\frac{\partial{j}}{\partial{x_1}}$을 한 번 수식으로 작성해보세요.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/da467c02-cbd1-4360-b879-691bfc10a86b/image.png" alt=""></p>
<h4 id="힌트">힌트</h4>
<p>$$
x \rightarrow f \rightarrow  h \rightarrow  j
\
x \rightarrow  f \rightarrow  i \rightarrow j
\
x \rightarrow g \rightarrow  h \rightarrow  j
\
x \rightarrow g \rightarrow  i \rightarrow j
$$</p>
<h4 id="답">답</h4>
<p>$$
j(h,h) \rightarrow 
\begin{matrix}
\frac{\partial{f}}{\partial{x_1}} \cdot \frac{\partial{h(f)}}{\partial{f}} \cdot \frac{\partial{j(h(f))}}{\partial{h(f)}} +\\
\frac{\partial{f}}{\partial{x_1}} \cdot \frac{\partial{i(f)}}{\partial{f}} \cdot \frac{\partial{j(i(f))}}{\partial{i(f)}} +\\</p>
<p>\frac{\partial{g}}{\partial{x_1}} \cdot \frac{\partial{h(g)}}{\partial{g}} \cdot \frac{\partial{j(h(g))}}{\partial{h(g)}} +\\
\frac{\partial{g}}{\partial{x_1}} \cdot \frac{\partial{i(g)}}{\partial{g}} \cdot \frac{\partial{j(i(g))}}{\partial{i(g)}} 
\end{matrix}
$$</p>
<h3 id="함수의-중복-실행-문제-해결">&#39;함수의 중복 실행&#39; 문제 해결</h3>
<p>연쇄법칙이 왜 &#39;<strong>함수의 중복 실행</strong>&#39; 문제점을 해결할 수 있는지에 대해 설명하겠습니다.</p>
<p>합성함수 $i(g(f(x_1,x_2))$에 대해 $x_1$와 $x_2$에 대한 편미분을 연쇄법칙을 이용해 표현하면 다음과 같습니다.</p>
<p>$$
i(g(f(x_1,x_2))) \rightarrow \frac{\partial{f}}{\partial{x_1}} \cdot \frac{\partial{g(f)}}{\partial{f}} \cdot \frac{\partial{i(g(f))}}{\partial{g(f)}} 
$$
$$
i(g(f(x_1,x_2))) \rightarrow \frac{\partial{f}}{\partial{x_2}} \cdot \frac{\partial{g(f)}}{\partial{f}} \cdot \frac{\partial{i(g(f))}}{\partial{g(f)}} 
$$</p>
<p>이 과정에서 $\frac{\partial{g(f)}}{\partial{f}}$와 $\frac{\partial{i(g(f))}}{\partial{g(f)}}$는 <strong>중복되는 값</strong>이기에 한 번만 구하면 됩니다.</p>
<p>이때 함수$g,i$를 실행할 필요가 없기 때문에 <strong>&#39;함수의 중복 실행&#39;</strong>문제를 해결할 수 있습니다.</p>
<p>이러한 연쇄법칙을 활용해 순전파와 역전파가 이루어집니다.</p>
<h2 id="순전파와-역전파">순전파와 역전파</h2>
<p>딥러닝에서 모델을 학습 하는 과정에서 순전파와 역전파는 필수입니다.</p>
<p><strong>순전파 과정</strong>에서는 각 레이어에서의 그래디언트를 계산하고 저장합니다.</p>
<p><strong>역전파 과정</strong>에서는 그래디언트를 이용하며 연쇄법칙을 통해 각 가중치에 대한 편미분 값을 구합니다.</p>
<h3 id="순전파foward-propagation">순전파(Foward Propagation)</h3>
<blockquote>
<p><strong>순전파</strong> : 주어진 입력에 대해 설계한 구조에 맞게 함수들을 실행하여 <strong>결과값을 얻고 그래디언트를 계산해 저장하는 단계</strong></p>
</blockquote>
<p>가령 다음과 같이 레이어를 정의하고 설명을 진행하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/eebcbca9-6718-4e3f-9522-a6ff50b33e75/image.png" alt=""></p>
<p>$$
f(X) = w_1x_1+w_2x_2 + b_1,
$$
$$
g(X) = w_3x_1+w_4x_2+b_2,
$$
$$
h(f,g) = w_5f+w_6g+b_3,
$$
$$
i(f,g) = w_7f+w_8g+b_4,
$$
$$
j(h,i) = h+2i
$$</p>
<p>각각 함수의 그래디언트를 구하면 다음과 같습니다.</p>
<p>$$
grad(f(X)) = 
\begin{bmatrix}
\frac{\partial{f(X)}}{\partial{w_1}}\\frac{\partial{f(X)}}{\partial{w_2}}\\frac{\partial{f(X)}}{\partial{b_1}}\\frac{\partial{f(X)}}{\partial{x_1}}\\frac{\partial{f(X)}}{\partial{x_2}}
\end{bmatrix}
= 
\begin{bmatrix}
x_1\x_2\1\w_1\w_2
\end{bmatrix}
$$</p>
<p>$$
grad(g(X)) = 
\begin{bmatrix}
\frac{\partial{g(X)}}{\partial{w_3}}\\frac{\partial{g(X)}}{\partial{w_4}}\\frac{\partial{g(X)}}{\partial{b_2}}\\frac{\partial{g(X)}}{\partial{x_1}}\\frac{\partial{g(X)}}{\partial{x_2}}
\end{bmatrix}
= 
\begin{bmatrix}
x_1\x_2\1\w_3\w_4
\end{bmatrix}
$$</p>
<p>$$
grad(h(f,g)) =
\begin{bmatrix}
\frac{\partial{h(f,g)}}{\partial{w_5}}\\frac{\partial{h(f,g)}}{\partial{w_6}}\\frac{\partial{h(f,g)}}{\partial{b_3}}\\frac{\partial{h(f,g)}}{\partial{f}}\\frac{\partial{h(f,g)}}{\partial{g}}
\end{bmatrix}
 = 
 \begin{bmatrix}
f\g\1\w_5\w_6
\end{bmatrix}
$$</p>
<p>$$
grad(i(f,g)) =
\begin{bmatrix}
\frac{\partial{i(f,g)}}{\partial{w_7}}\\frac{\partial{i(f,g)}}{\partial{w_8}}\\frac{\partial{i(f,g)}}{\partial{b_4}}\\frac{\partial{i(f,g)}}{\partial{f}}\\frac{\partial{i(f,g)}}{\partial{g}}
\end{bmatrix}
 = 
 \begin{bmatrix}
f\g\1\w_7\w_8
\end{bmatrix}
$$</p>
<p>$$
grad(j(h,i)) =
\begin{bmatrix}
\frac{\partial{j(h,i)}}{\partial{h}}\
\frac{\partial{j(h,i)}}{\partial{i}}
\end{bmatrix}
= 
\begin{bmatrix}
1\2
\end{bmatrix}
$$</p>
<p>순전파 과정에서는 이 그래디언트들이 저장되며 </p>
<p>함수 $f(X)$의 그래디언트를 구하는 코드는 다음과 같습니다.</p>
<pre><code class="language-python">def forward(func,vars):
    inputs = list(vars.values())
    out = func(*inputs)
    grad = {}
    for i,k in enumerate(vars.keys()):
        inputs[i] = inputs[i]+eps
        grad[k] = (func(*inputs)-out)/eps
        inputs[i] = inputs[i]-eps
    return out,grad

def f(w1,w2,b1,x1,x2):
    return w1*x1+w2*x2+b1

eps = 1e-10
vars = {&#39;w1&#39;:2,&#39;w2&#39;:3,&#39;b1&#39;:4,&#39;x1&#39;:5,&#39;x2&#39;:6}
forward(f,vars)
# (32,
#  {&#39;w1&#39;: 5.000018177270249,
#   &#39;w2&#39;: 5.999964969305438,
#   &#39;b1&#39;: 1.000017846308765,
#   &#39;x1&#39;: 2.00003569261753,
#   &#39;x2&#39;: 2.999982484652719})</code></pre>
<h3 id="역전파back-propagation">역전파(Back Propagation)</h3>
<blockquote>
<p><strong>역전파</strong> : 연쇄법칙을 활용해 기존에 구한 그래디언트를 역방향으로 곱하여 각 가중치에 대한 편미분값을 얻어내는 과정</p>
</blockquote>
<p>역전파는 그냥 연쇄 법칙이라고 보시면 됩니다. </p>
<p><strong>※ 연산 관점에서 역방향으로 연쇄법칙을 적용하는 방식이 더 효율적이기 때문에 역전파라고 하는 거 같습니다.</strong></p>
<p>먼저 $\frac{\partial{j(j,i)}}{\partial{w_1}}$을 구하기 위해 필요한 간선들만 남기면 다음과 같습니다.</p>
<blockquote>
<p><strong>주의하실 점</strong>은 $w_1$은 $f(x)$에만 연결되는 가중치이기 때문에 $g(x)$는 고려하지 않아도 됩니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/a38bb9b4-2ac5-441f-9a27-11c3ea388dd2/image.png" alt=""></p>
<p>이 구조를 통해 역전파의 과정을 설명드리면 다음과 같습니다.</p>
<p><strong>※ 그림의 원들을 모두 노드라고 하겠습니다.</strong></p>
<ol>
<li>노드$j$의 그래디언트($[1,2]$)를 연결된 변수들($h,i$)에 넘겨준다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/915de3de-07fe-4e62-85a2-7361745ecd3d/image.png" alt=""></p>
<ol start="2">
<li><p>노드$h,i$에서 다음과 같이 연산을 진행한다.</p>
<ul>
<li><p>노드$h$ : 노드$j$에서 받은 그래디언트 $1$을 현재 노드 $h$의 그래디언트 ($[f,g,1,w_5,w_6]$)에 곱하여 연결된 변수들($w_5,w_6,b_3,f,g$)에 넘겨준다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/89ce6088-cf7d-49a0-8eb0-d2a786fd3290/image.png" alt=""></p>
</li>
<li><p>노드$i$ : 노드$j$에서 받은 그래디언트 $2$를 현재 노드 $i$의 그래디언트 ($[f,g,1,w_7,w_8]$)에 곱하여 연결된 변수들($w_7,w_8,b_4,f,g$)에 넘겨준다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/e265c23d-1e85-4e72-9f22-a4a490ba8c01/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>노드$f$에서 다음과 같이 연산을 진행한다.</p>
<ul>
<li><p>노드$h$에서 받은 그래디언트 $1 \cdot w_5$을 현재 노드 $f$의 그래디언트 ($[x_1,x_2,1,w_1,w_2]$)에 곱하여 연결된 변수들($w_1,w_2,b_1,x_1,x_2$)에 넘겨준다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/2392f8a5-9d19-419e-b11e-071fd3e65116/image.png" alt=""></p>
</li>
<li><p>노드$i$에서 받은 그래디언트 $2 \cdot w_7$을 현재 노드 $f$의 그래디언트 ($[x_1,x_2,1,w_1,w_2]$)에 곱하여 연결된 변수들($w_1,w_2,b_1,x_1,x_2$)에 넘겨준다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/7f7e6c62-a986-4b93-b905-01606bb9f2fc/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>가중치 $w_1$에서는 노드$f$에서 받은 그래디언트들을 합하여 저장한다.</p>
<p> $$
 \frac{\partial{j(j,i)}}{\partial{w_1}} = 1 \cdot w_5 \cdot x_1+ 2 \cdot w_7 \cdot x_1
 $$</p>
</li>
</ol>
<p>이 과정에서 가중치 $w_1$뿐 아니라 위 그림과 연결된 가중치 $w_2,b_1,w_7,w_8,b_4,w_5,w_6,b_3$의 그래디언트도 계산되었습니다.</p>
<p>비교를 위해 합성함수 $j$를 $w_1$에 대해 편미분하면 다음과 같습니다.</p>
<p>$$
j = 
\begin{matrix}
&amp;w_5(w_1x_1+w_2x_2 + b_1)+w_6(w_3x_1+w_4x_2+b_2)+b_3+\
&amp;2(w_7(w_1x_1+w_2x_2 + b_1)+w_8(w_3x_1+w_4x_2+b_2)+b_4)
\end{matrix}
$$</p>
<p>$$
\frac{\partial{j}}{\partial{w_1}} = 1 \cdot w_5 \cdot x_1+2 \cdot w_7 \cdot x_1
$$</p>
<p>이를 통해 역전파의 값이 동일한 것을 확인할 수 있습니다.</p>
<p>다른 그래디언트들도 계산해 보시길 바랍니다.</p>
<p><strong>※역전파 코드를 작성하기 위해서는 순전파까지 작성해야 하므로 다음 절에서 다루겠습니다.</strong></p>
<h2 id="순전파-역전파-코드">순전파 역전파 코드</h2>
<p>이번 절에서는 간단하게 순전파 역전파를 구현해보겠습니다.</p>
<ul>
<li><strong>먼저 레이어에서 다룰 변수들을 먼저 설정해주겠습니다.</strong></li>
</ul>
<pre><code class="language-python">variables = {
    &#39;w1&#39;:1,&#39;w2&#39;:2,&#39;w3&#39;:3,&#39;w4&#39;:4,&#39;w5&#39;:5,&#39;w6&#39;:6,&#39;w7&#39;:7,&#39;w8&#39;:8,
    &#39;b1&#39;:9,&#39;b2&#39;:10,&#39;b3&#39;:11,&#39;b4&#39;:12,
    &#39;x1&#39;:13,&#39;x2&#39;:14
}</code></pre>
<ul>
<li><strong>이어서 레이어에서 사용할 함수를 선언해줍니다.</strong></li>
</ul>
<pre><code class="language-python">def weight_sum(w1,w2,b1,x1,x2):
    return w1*x1+w2*x2+b1

def summation(x1,x2):
    return x1+2*x2</code></pre>
<ul>
<li><strong>이전 절에서 정의한 함수들과 동일하게 레이어들을 작성합니다.</strong></li>
</ul>
<pre><code class="language-python">layers = {
    &#39;f&#39;:{ # 레이어 이름
        &#39;func&#39;:weight_sum, # 사용할 함수
        &#39;inputs&#39;:[&#39;w1&#39;,&#39;w2&#39;,&#39;b1&#39;,&#39;x1&#39;,&#39;x2&#39;], # 함수에 들어갈 입력
    },
    &#39;g&#39;:{
        &#39;func&#39;:weight_sum,
        &#39;inputs&#39;:[&#39;w3&#39;,&#39;w4&#39;,&#39;b2&#39;,&#39;x1&#39;,&#39;x2&#39;],
    },
    &#39;h&#39;:{
        &#39;func&#39;:weight_sum,
        &#39;inputs&#39;:[&#39;w5&#39;,&#39;w6&#39;,&#39;b3&#39;,&#39;f&#39;,&#39;g&#39;],
    },
    &#39;i&#39;:{
        &#39;func&#39;:weight_sum,
        &#39;inputs&#39;:[&#39;w7&#39;,&#39;w8&#39;,&#39;b4&#39;,&#39;f&#39;,&#39;g&#39;],
    },
    &#39;j&#39;:{
        &#39;func&#39;:summation,
        &#39;inputs&#39;:[&#39;h&#39;,&#39;i&#39;],
    }
}</code></pre>
<ul>
<li><strong>layers와 variables를 이용한 순전파 코드를 작성합니다.</strong></li>
</ul>
<pre><code class="language-python">forward_grads={}

def forward(name,eps=1e-10):
    inputs = [variables[k] for k in layers[name][&#39;inputs&#39;]] # 선택한 레이어의 입력 값을 variables에서 가져옴
    out = layers[name][&#39;func&#39;](*inputs) # 레이어 실행
    grad = {}
    for i,k in enumerate(layers[name][&#39;inputs&#39;]): 
        # 모든 변수에 대해서 목표로 하는 변수의 값만 값만 조절하여 편미분
        inputs[i] = inputs[i]+eps
        grad[k] = (layers[name][&#39;func&#39;](*inputs)-out)/eps
        inputs[i] = inputs[i]-eps
    variables[name] = out # 변수에 레이어 출력값 저장
    forward_grads[name] = grad # 순전파 그래디언트 저장
    return out</code></pre>
<ul>
<li><strong>전체 레이어에 대해 순전파를 진행합니다.</strong></li>
</ul>
<pre><code class="language-python">for name in layers.keys():
    out = forward(name)
    print(name,variables[name])
forward_grads</code></pre>
<ul>
<li><strong>역전파를 진행합니다.</strong></li>
</ul>
<pre><code class="language-python">backward_grads = {}
def backward(name,grad=1):
    for i,k in enumerate(layers[name][&#39;inputs&#39;]): #모든 레이어의 입력(이전노드)에 대해서 역전파 실행
        if layers.get(k) is not None: # 레이어인 경우
            backward(k,grad*forward_grads[name][k])
        else : # 가중치인 경우
            if backward_grads.get(k) is not None: 
                # 역전파된 기록이 있다면 더해주기
                backward_grads[k] = backward_grads[k] + grad*forward_grads[name][k]
            else :
                # 역전파된 기록이 없다면 새로 선언
                backward_grads[k] = grad*forward_grads[name][k]

backward(&#39;j&#39;) # 마지막 레이어에 대해 역전파를 진행.</code></pre>
<ul>
<li><strong>역전파가 잘 진행됐는지 확인합니다.</strong></li>
</ul>
<pre><code class="language-python">print(backward_grads[&#39;w1&#39;])
print(variables[&#39;w5&#39;]*variables[&#39;x1&#39;]+2*variables[&#39;w7&#39;]*variables[&#39;x1&#39;])
# 247.1311126871836
# 247</code></pre>
<h3 id="학습-코드">학습 코드</h3>
<pre><code class="language-python">def weight_sum(w1,w2,b1,x1,x2):
    return w1*x1+w2*x2+b1

def summation(x1,x2):
    return x1+2*x2

def mse(pred,true):
    return (pred-true)**2

layers = {
    # ... 이전코드와 동일
    &#39;loss&#39;:{
        &#39;func&#39;:mse,
        &#39;inputs&#39;:[&#39;j&#39;,&#39;y1&#39;],
    }
}



def forward(name,eps=1e-10):
    # ... 이전 코드와 동일
    return out

def backward(name,grad=1):
    # ... 이전 코드와 동일

def update(params,lr=1e-3): # 가중치 업데이트 코드
    for p in params:
        variables[p] = variables[p]-lr*backward_grads[p]

variables = { # 변수들 정보 저장
    # ... 이전 코드와 동일
}

params = [ # 학습될 파라미터 정보 저장
    &#39;w1&#39;,&#39;w2&#39;,&#39;w3&#39;,&#39;w4&#39;,&#39;w5&#39;,&#39;w6&#39;,&#39;w7&#39;,&#39;w8&#39;,
    &#39;b1&#39;,&#39;b2&#39;,&#39;b3&#39;,&#39;b4&#39;,
]

epochs=100
losses = []
forward_grads={}
for i in range(epochs):
    loss = []
    for j in range(100):
        backward_grads = {} # backward 정보 삭제
        variables[&#39;x1&#39;],variables[&#39;x2&#39;],variables[&#39;y1&#39;] = j/100,j/100,j/100
        for name in layers.keys(): # forward
            out = forward(name)
        backward(&#39;loss&#39;)
        update(params)
        loss.append(out)
    losses.append(sum(loss)/100)


print(variables) # 변수들 값 확인

# 학습이 잘 됐는지 확인
variables[&#39;x1&#39;],variables[&#39;x2&#39;] = 1000,1000
for name in layers.keys(): # forward
    out = forward(name)
print(variables[&#39;j&#39;])
# 999.987289779996


import matplotlib.pyplot as plt

plt.plot(losses)
plt.show()
</code></pre>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/ad7cd9f5-3f0e-4e21-a8db-2b75018c37c5/image.png" alt=""></p>
<h2 id="마무리">마무리</h2>
<p><strong>&#39;미분함수 정의&#39;</strong> 방법의 문제점을 <strong>&#39;미분 공식 활용&#39;</strong> 방법의 문제점을 <strong>연쇄법칙</strong>을 통해 해결하는 과정을 보이며 순전파와 역전파에 대해서 설명 드렸습니다.</p>
<p>그러나 <strong>&#39;미분공식과 연쇄법칙을 활용하는 방식&#39;</strong> 에도 문제가 있습니다.</p>
<p>각 함수의 그래디언트를 구하기 위해서 모든 변수에 한번씩 변화를 주어 편미분 값을 계산하는 방식을 사용했습니다.</p>
<p>이 과정에서 <strong>변수의 개수만큼 함수를 여러번 실행해야하는 단점</strong>이 있습니다.</p>
<p>이 문제뿐만이 아니라 모델 구조를 새로 만들 때 입력 정보, 변수 정보, 함수 등 <strong>새로 명시해주어야 할 정보가 많은 문제점</strong>이 있습니다.</p>
<p>다음 글에서는 이런 문제점을 해결하기 위해 <strong>클래스 구조를 활용하여 구현하는 방식</strong>에 대해 글을 적으려고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[딥러닝] 경사하강법 구현부터 학습까지 (1)]]></title>
            <link>https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-1</link>
            <guid>https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-1</guid>
            <pubDate>Sat, 27 Jan 2024 12:24:54 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>최근 &#39;SKT FLY AI&#39;에서 공부하며 순전파와 역전파에 대해 강의를 들으며 갑자기 <strong>&#39;순전파 역전파를 전개해본적이 있던가?&#39;</strong> 라는 생각이 들어 계산해보려 하니 간단한 레이어 조차 계산하지 못하였습니다.</p>
<p>그래서 <strong>경사하강법을 이해</strong>하고 <strong>순전파와 역전파를 구현하여 학습까지 진행하는 과정</strong>을 <strong>기록</strong>하고자 하고, 누군가에게 도움이 됐으면 하여 글을 작성해봅니다.</p>
<p>이번 글은 <strong>간단한 이론에 대해 얘기하고 구현에 관한 자세한 내용에 대해서는 <a href="https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-2">다음 글</a>에서 언급</strong>하겠습니다.</p>
<p>잘못된 내용이 있다면 얘기해주세요!</p>
<p>코드 구현 깃허브 : <strong><a href="https://github.com/PreFKim/MyTorch">MyTorch</a></strong></p>
<h2 id="경사하강법이란">경사하강법이란?</h2>
<blockquote>
<p><strong>경사하강법</strong> : 딥러닝에서 사용하는 학습 알고리즘으로 목표로 하는 값과의 차이(손실 함수로 정의)를 최소화 하는 방향으로 특정 값(파라미터)의 값을 조정하는 방법</p>
</blockquote>
<p><strong>그렇다면 어떻게 차이를 최소화 하는 방향으로 값을 조정할까?</strong></p>
<p>경사하강법의 <strong>핵심은 기울기</strong>에 있습니다.</p>
<h3 id="기울기란">기울기란?</h3>
<blockquote>
<p><strong>기울기</strong> : 특정 변수에 대한 함수 값의 변화량입니다.
도함수(Deviative), 편미분(Partial Deviative), 그래디언트(Gradient)라고도 합니다. (단, 상황에따라 사용되는 단어는 다음과 같이 달라집니다.)</p>
</blockquote>
<ul>
<li><strong>도함수(Deviative)</strong> : 변수가 하나인 단일 변수 함수에 대해서 변수의 변화량에 따른 대한 함수의 변화량 입니다.</li>
</ul>
<p>$$
f(x)=x^2 \rightarrow \frac{df(x)}{dx} = 2x
$$</p>
<ul>
<li><strong>편미분(Partial Deviative)</strong> : 변수가 여러개인 다중 변수 함수에 대해서 하나의 변수의 변화량에 따른 함수의 변화량을 의미합니다.</li>
</ul>
<p>$$
f(x,y)=x^2+y^2 \rightarrow \frac{\partial{f(x,y)}}{\partial{x}} = 2x \quad or \quad \frac{\partial{f(x,y)}}{\partial{y}} = 2y 
$$</p>
<ul>
<li><strong>그래디언트(Gradient)</strong> : 변수가 여러개인 다중 변수 함수에 대해서 각 변수의 변화량에 따른 함수의 변화량 집합(벡터)을 의미합니다.( <strong>함수 내부의 각 변수에 대한 편미분 집합</strong> )</li>
</ul>
<p>$$
f(x,y)=x^2+y^2 \rightarrow [\frac{\partial{f(x,y)}}{\partial{x}}, \frac{\partial{f(x,y)}}{\partial{y}}] = [2x,2y]
$$</p>
<h3 id="조정-방법">조정 방법</h3>
<p>가령, 어떤 함수의 기울기가 2라면 그 순간 함수의 값은 2만큼 커지고 있으며 -2라면 그 순간 함수의 값은 -2만큼 작아진다는 것을 의미합니다.</p>
<p>인공지능을 학습하기 위해서는 <strong>최소점</strong>으로 가야한다고 했으니 <strong>기울기가 양수라면 현재 값이 커지고 있으니 값이 작아지도록 반대 방향</strong>으로 가도록 해야합니다.</p>
<p>그래서 경사 하강법은 이 기울기의 반대(-)방향으로 향하도록 하여 최소화 하는 방향으로 값을 조정합니다.</p>
<p>이에 대한 식은 다음과 같습니다.</p>
<p>$$
x_{t} = x_{t-1} + lr * (-grad) \
= x_{t-1} - lr * grad
$$ </p>
<blockquote>
<p>여기서 $grad$는 변화시키고자 변수의 값에 의한 Loss의 변화량을 의미합니다.</p>
</blockquote>
<p>기울기 값을 통해 $x$를 갱신하는 과정이며 $lr$(학습률)을 의미합니다. (학습률에 대한 설명은 아래에서 진행하겠습니다.)</p>
<h3 id="경사하강법-시각화">경사하강법 시각화</h3>
<p>아래와 같이 단일 변수 함수를 정의하고 점 $(2,4)$에서 시작하여 최적점 $(0,0)$으로 간다고 해봅시다.</p>
<p>$$
f(x) = x^2
$$</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/47cad321-18d6-4798-b967-a5dd9c4c0ce2/image.png" alt=""></p>
<p>점 $(2,4)$를 $(0,0)$으로 이동시키기 위해서 저희는 $x$값을 조정해야합니다.</p>
<p>조정하기 위해 알아야 할 값 $grad$는 $grad = \frac{df(x)}{dx}$로 정의 됩니다.</p>
<p>그럼 $x$에 대한 기울기를 알아야 하므로 함수 $f(x)$를 $x$에 대해 미분을 진행합니다.</p>
<p>$$
grad = \frac{df(x)}{dx}=f&#39;(x)=2 \times x
$$</p>
<p>이를 통해 점 $(2,4)$에서의 기울기는 $4$임을 알 수 있습니다.</p>
<p>이를 이용해 경사하강법 수식에 적용하면 다음과 같습니다. (이때 $lr=1$)</p>
<p>$$
x_{t} = 2 - 4 = -2
$$</p>
<p>이렇게 $x$는 새로 갱신되어 $x=-2$가 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/25199315-4a64-49ae-bee6-90ffa0553c5c/image.png" alt=""></p>
<blockquote>
<p>$x=-2$ 일 때에도 새로 갱신하면 $x=2$인데 이러면 최적점으로 안가잖아?</p>
</blockquote>
<p>특수 케이스이긴 하지만 이런 문제를 해결할 수 있도록 하는 것이 바로 학습률 입니다.</p>
<p><strong>※(3,9) 에서도 가중치를 변화 시켜보시길 바랍니다.</strong></p>
<h3 id="학습률learning-rate">학습률(Learning Rate)</h3>
<blockquote>
<p><strong>학습률</strong> : 기울기의 크기를 조절하여 최적점에 도달할 수 있도록 하는 사람이 직접 설정해야하는 하이퍼 파라미터</p>
</blockquote>
<p>10번의 갱신(Step)과정을 통해 각 학습률에 따른 변화를 보이면 다음과 같습니다.</p>
<ul>
<li><strong>학습률이 큰 경우($lr=1.01$)</strong> : 이 경우에는 최적값으로 수렴하지 못하고 오히려 값이 커지는 방향으로 학습이 진행되는 것을 확인할 수 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/49507a9d-8eb2-49b4-8837-a8c6e882efa1/image.png" alt=""></p>
<ul>
<li><strong>학습률이 작은 경우($lr=0.01$)</strong> : 이 경우에는 최적값으로 수렴하기까지 오래 걸리며 더 많은 학습을 진행해야합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/57ec1bf2-9285-4120-a5a2-f48162f2c869/image.png" alt=""></p>
<ul>
<li><strong>학습률이 적정한 경우($lr=0.1$)</strong> : 10번의 갱신만으로도 최적점에 가까워지는 모습을 확인할 수 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3e10f425-5e8f-42ec-a455-b54b0af4db70/image.png" alt=""></p>
<ul>
<li><strong>학습률이 최적인 경우($lr=0.5$)</strong> : 이런 경우가 발생할 가능성은 매우 희박하지만 한번의 갱신만으로도 최적점으로 향하는 것을 확인할 수 있으며 학습률의 중요성을 보여준다고 생각합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/08a4204e-f729-4d63-b44c-e590dec01959/image.png" alt=""></p>
<p>위처럼 학습률의 영향으로 인해 학습의 횟수가 달라지므로 적절한 학습률을 찾는 것은 시간적, 자원적으로 중요한 요인이 됩니다.</p>
<p><strong>※위 실험에서 사용한 학습률은 일반적인 경우보다 큰 학습률입니다. 대부분 0.001 이하의 학습률을 사용하며 최적의 학습률은 실험적으로 알아내야합니다.</strong></p>
<h2 id="딥러닝에서의-경사하강법">딥러닝에서의 경사하강법</h2>
<p>지금까지의 내용은 경사하강법의 원리를 이해할 수 있도록 간단한 단일 변수 함수 $f(x)=x^2$을 예시로 드는 내용이었습니다.</p>
<p>딥러닝에서 대부분의 함수는 <strong>다중 변수 함수</strong>로 이루어져 있습니다.</p>
<p>각각 변수(가중치)를 갱신하기 위해서는 <strong>각 변수의 변화량이 함수의 변화량에 미치는 영향을 계산</strong>해야 합니다.</p>
<p>그래서 딥러닝에서는 <strong>편미분을 사용</strong>합니다.</p>
<blockquote>
<p><strong>편미분</strong>은 간단히 말해 <strong>목표로 하는 변수를 제외한 모든 변수를 모두 상수 취급하여 미분</strong>하는 과정입니다.</p>
</blockquote>
<h3 id="함수-설정">함수 설정</h3>
<p>딥러닝에서 가장 일반적인 함수 식은 다음과 같습니다. </p>
<p>$$
f(x)=w \times x + b
$$</p>
<blockquote>
<p><strong>주의하셔아 할 점</strong>은 위 식에서 학습시킬 파라미터는 <strong>$x$가 아닌 가중치 $w$와 $b$</strong>입니다. (이 부분에서 헷갈리시면 나중에 힘드실 겁니다.)</p>
</blockquote>
<p>$w$와 $b$는 각각 1로 초기화 해주어 $f(x) = x + 1$로 설정하겠습니다.</p>
<p>이후 함수 $f(x)$를 학습시키기 위한 데이터를 다음과 같이 준비하겠습니다.</p>
<ul>
<li><p>입력 : $X = [-2,-1,0,1,2]$</p>
</li>
<li><p>출력 : $Y = [-4,-1,2,5,8]$ ($3x+2를 적용한 값$)</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/0a67394f-b729-4b0f-8283-70335adc2f24/image.png" alt=""></p>
<h3 id="미분함수-정의">미분함수 정의</h3>
<p>기울기를 구하기 전 학습을 하기 위해 손실함수를 다음과 같이 설정하겠습니다. </p>
<p>$$
Loss(Pred,True) = (Pred-True)^2
$$</p>
<p>위 함수에서 $Pred$는 $f(x)$이며 $True$는 실제 값입니다.</p>
<p>따라서 최종적으로 최소화 시켜야 할 함수는 다음과 같습니다.</p>
<p>$$
Loss(x) = (Pred-True)^2 = ((wx+b)-True)^2\
$$</p>
<blockquote>
<p>위에서 말했듯 얼핏 보면 위 식을 최소화 시키기 위해 $x$를 변화시켜야할 것 같지만,
<strong>$x = [-2,-1,0,1,2]$로 고정되어 있는 상수와 같기 때문에 $w$와 $b$를 학습시켜야 합니다.</strong></p>
</blockquote>
<p>이를 정리하면 아래와 같습니다.</p>
<p>$$
Loss(x) = (wx)^2 + 2wbx + b^2 - 2wx(True)-2b(True) + (True)^2
$$</p>
<p><strong>$Loss(x)$ 를 최소화 시키기 위해서 $w$와 $b$값을 변화 시켜야 하기 때문에 최종적으로 편미분을 통해 $\frac{\partial{Loss(x)}}{\partial{w}}$와 $\frac{\partial{Loss(x)}}{\partial{b}}$를 구하면 다음과 같습니다.</strong></p>
<p>$$
\frac{\partial{Loss(x)}}{\partial{w}} = 2x^2w +2bx -2x(True)
$$
$$
\frac{\partial{Loss(x)}}{\partial{b}} = 2wx+ 2b -2(True)
$$</p>
<h3 id="경사하강법-적용하기">경사하강법 적용하기</h3>
<p>지금까지 기울기를 구하는 과정을 거쳤다면 이제는 기울기를 이용해 경사하강법으로 <strong>가중치를 갱신하는 과정</strong>을 보이겠습니다.</p>
<p>경사하강법을 이용해 가중치를 갱신하는 식은 다음과 같습니다.</p>
<p>$$
w_{new} = w - lr \times \frac{\partial{Loss(x)}}{\partial{w}}
$$</p>
<p>$$
b_{new} = b - lr \times \frac{\partial{Loss(x)}}{\partial{b}}
$$</p>
<h4 id="가중치-갱신">가중치 갱신</h4>
<p>학습률은 $lr = 0.1$로 설정하여 경사 하강법을 적용하면 다음과 같습니다.</p>
<p>데이터 중 한 쌍($x=-2, True=-4$)을 가져와 경사하강법을 적용하여 가중치를 갱신하면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/280cd035-8978-4bdc-9be9-cef35d7ec243/image.png" alt=""></p>
<p>$$
w_{new} = 1 - 0.1 \times -12 = 2.2
$$</p>
<p>$$
b_{new} = 1 - 0.1 \times 6 = 0.4
$$</p>
<p>다시 한 번 데이터 중 한 쌍($x=-1, True=-1$)을 가져와 경사하강법을 적용하여 가중치를 갱신하면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/6c8bf3a1-e2de-4181-bb72-8aa9845a672e/image.png" alt=""></p>
<p>$$
w_{new} = 2.2 - 0.1 \times 1.6 = 2.04
$$</p>
<p>$$
b_{new} = 0.4 - 0.1 \times -1.6 = 0.56
$$</p>
<p>이 과정을 $X$의 모든 값에 대해 두번 정도만 반복해준다면 $w=3.03, b=1.95$가 되어 $f(x)=3x+2$와 가까워지게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/0653bb7c-bb81-4d11-b368-52613d2bcafd/image.png" alt=""></p>
<pre><code class="language-python">import numpy as np
import matplotlib.pyplot as plt

w=1
b=1
X = np.array([-2,-1,0,1,2])
Y = np.array([-4,-1,2,5,8])

def f(x):
    return w*x+b

original = f(X)

lr = 0.1
epochs = 2

for i in range(epochs):
    for j in range(len(X)):
        plt.plot([-2,2],[0,0],color=&#39;black&#39;)
        plt.plot([0,0],[9,-5],color=&#39;black&#39;)
        plt.grid()
        plt.xticks(np.arange(-2,3,1))
        plt.yticks(np.arange(-5,9,1))

        # 원본 그래프
        plt.plot(X,original,label=&#39;Original&#39;) 
        x = X[j]
        y = Y[j]
        print(&quot;적용 전 :&quot;,w,b)

        # Loss 값 구하기
        loss = ((w*x+b)-y)**2

        # 기울기 값 구하기
        gradw = 2*x**2*w+2*b*x - 2*x*y             
        gradb = 2*w*x + 2*b - 2*y

        # 경사 하강법 적용
        w = w-lr*gradw
        b = b-lr*gradb

        print(&quot;기울기&quot;,gradw,gradb)
        print(&quot;Loss :&quot;,loss)
        print(&quot;적용 후 :&quot;,w,b)
        plt.plot(X,f(X),label=&#39;After&#39;,color=&#39;r&#39;)
        plt.scatter(X,Y,s=100,label=&#39;Target&#39;,color=&#39;orange&#39;)
        plt.legend()
        plt.show()

        print()</code></pre>
<h2 id="마무리">마무리</h2>
<p>이번 글은 경사하강법에 대해서 설명을 드렸습니다.</p>
<p>아직은 복잡한 예시는 설명드리지 않았지만 <strong>실제로는 더 복잡한 구조와 더 많은 파라미터에 대한 연산</strong>을 가지고 있습니다.</p>
<p>이러한 이유로 위와 같이 <strong>함수의 미분 함수를 미리 정의하는 방식으로 경사하강법을 구현할 경우 새로운 함수를 추가할 때 많은 복잡함이 발생될 수 있는 문제점이 있습니다.</strong></p>
<p><strong><a href="https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95-%EA%B5%AC%ED%98%84%EB%B6%80%ED%84%B0-%ED%95%99%EC%8A%B5%EA%B9%8C%EC%A7%80-2">다음 글</a></strong>에서는 이런 복잡함을 줄일 수 있는 순전파, 역전파에 대해서 설명하도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[BERTSUM 논문 리뷰]]></title>
            <link>https://velog.io/@pre_f_86/BERTSUM-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@pre_f_86/BERTSUM-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Sun, 14 Jan 2024 13:09:11 GMT</pubDate>
            <description><![CDATA[<p>본 페이지에서는 <a href="https://arxiv.org/abs/1908.08345">Text Summarization with Pretrained Encoders</a> 논문에 대해서 말하고자 합니다.</p>
<p>BERT에 대한 이해가 있으면 논문 읽기가 편하실 거 같아 아래의 링크와 함께 같이 보시는 걸 추천드립니다.</p>
<ul>
<li><a href="https://velog.io/@pre_f_86/BERT-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding</a></li>
</ul>
<hr>
<h2 id="1-intro">1. Intro</h2>
<p>기존의 언어 모델(ELMo, GPT, BERT)들은 감정 분석, QA(질문 답변), 자언여 추론 등 여러 Task에 대해서 좋은 성능을 냈습니다.</p>
<p>그러나 자연어 요약 부문에서는 좋은 성능을 내지 못합니다.</p>
<p>그 이유는 요약을 위해서는 문장 단위의 의미를 이해하고 이에 따른 요약이 필요하지만, 기존의 모델들은 토큰 단어의 의미를 이해하도록 학습 하기 때문에 요약의 성능이 저하됩니다.</p>
<p>그래서 본 논문에서는 이를 해결하기 위해 문장단위의 사전학습을 진행하는 BERT 모델을 기반으로 파인튜닝을 진행합니다.</p>
<p>자연어 요약은 크게 두가지로 나누어 집니다.</p>
<ol>
<li><p><strong>Extract(추출 요약)</strong> : 여러 문장들 중에서 주요한 문장들을 파악하고 선택(이진 분류)하여 적절한 요약문으로 추출하는 과정</p>
</li>
<li><p><strong>Abstract(추상 요약)</strong> : 주요 문장의 의미를 파악하고 의미에 맞게 적절한 요약문을 생성(자연어 추론)하는 과정</p>
</li>
</ol>
<p>저자가 말하는 본 논문의 장점은 다음과 같습니다.</p>
<p>본 논문에서는 어려운 방법론(강화 학습, 다양한 Encoder 사용)을 사용하는 것과는 달리 간단하게 위의 두 과정을 순차적으로 Fine-Tuning 하는 방식으로 추출 요약, 추상 요약 부분에서 SOTA를 달성할 수 있도록 하였습니다.</p>
<h2 id="2-why-bert">2. Why BERT?</h2>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/dbb3f357-00ea-47c5-b5a3-34269b34ef36/image.png" alt=""></p>
<p>위 이미지는 BERT 논문에 있는 이미지 입니다.</p>
<p>BERT는 문장에서 토큰화 된 단어 중 일부 토큰을 가리고 모델이 가려진 토큰을 예측하도록 하는 Masked Language Model(MLM)입니다.</p>
<p>기존의 Transformer 모델과 동일하게 BERT는 단어를 토큰으로 임베딩하는 Token Embedding 부분과 위치 정보를 추가해주는 Position Embedding을 추가합니다.</p>
<p>다른 점은 Segment Embedding이며 이는 각 단어가 어떤 문장에 속했는지에 대한 정보를 제공합니다.</p>
<p>예를 들면 다음과 같습니다.</p>
<blockquote>
<p>문장 1 : 나는 밥을 먹었다.
문장 2 : 너는 라면을 먹었다.</p>
</blockquote>
<p>BERT는 두 문장을 동시에 입력으로 넣기 위해 [SEP] 토큰을 기준으로 두 문장을 왼쪽과 오른쪽 나누고 각각 다른 Segment Embedding을 추가합니다.</p>
<p>이를 통해 BERT는 토큰 단어의 정보만을 학습하는 것이 아닌 문장 단위의 정보도 학습을 합니다.</p>
<p>이 때문에 본 논문에서는 문장 단위의 정보가 중요한 자연어 요약 문제에서 BERT를 사용한 것입니다.</p>
<h2 id="3-fine-tuning-bert-for-summarization">3. Fine-Tuning BERT for Summarization</h2>
<h3 id="31-summarization-encoderbertsum">3.1. Summarization Encoder(BERTSUM)</h3>
<p>2절에서 말했듯 BERT는 Segment Embedding을 통해 문장 단위의 정보를 어느정도 포함하고 있지만 다음의 이유로 토큰 단위의 정보를 더 우세하게 학습 하게 됩니다.</p>
<ol>
<li><p><strong>Mask된 토큰을 예측하기 때문에 Output Vector는 문장 대신 토큰 단어 토큰에 대한 정보를 담음</strong></p>
<p> 이러한 이유로 문장에 대한 정보를 주더라도 토큰에 대한 정보를 훨씬 더 우세하게 학습하게 됩니다.</p>
</li>
<li><p><strong>두개의 문장으로 이루어진 하나의 문장 쌍에 대해서만 적용되는 Segment Embedding</strong></p>
<p> BERT는 하나의 문장 쌍에 대해서만 사전학습을 하게 됩니다. 하지만 실제 문서와 같은 장문의 글은 여러 문장 쌍으로 이루어져 있기 때문에 요약에서 큰 성능 향상을 기대하기는 어렵습니다.</p>
</li>
</ol>
<p>그래서 본 논문의 저자들은 BERT 모델의 Encoder 구조를 다음과 같이 바꾸었습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/36427cd5-7252-4cf5-b622-f7a350bdaab5/image.PNG" alt=""></p>
<p><strong>※ 원래 BERT는 하나의 문장 쌍에 대해서만 학습하게 되는데 Fig1의 왼쪽은 잘못된 사진 같습니다.</strong></p>
<ol>
<li><p><strong>각 문장의 앞에 [CLS] 토큰을 삽입</strong></p>
<p> [CLS] 토큰은 각 문장에 대한 정보를 담고 있는 토큰이 됩니다.</p>
</li>
<li><p><strong>문서 내의 각 i번째 문장에 대해 번갈아가며(홀수, 짝수)에 따라 A, B 문장 정보 제공</strong></p>
<p> 문서가 $[Sent_{1}, Sent_{2}, ..., Sent_{n}]$ 으로 이루어 졌다면 임베딩 $[E_{A},E_{B}, ..., E]$를 제공합니다.</p>
</li>
<li><p><strong>긴 토큰을 가진 문서를 처리하기 위하여 입력 토큰 제한 수 변경</strong></p>
<p> 기본 BERT 모델은 토큰의 최대 길이가 512로 문서를 처리하기에는 너무 짧기 때문에 이를 해결하기 위하여 무작위로 초기화된 가중치를 Positional Encoding에 추가합니다.</p>
</li>
</ol>
<p>이 과정을 통해 본 논문의 저자들은 문서에 대한 Representation을 계층적으로 학습한다고 합니다.</p>
<ul>
<li><p>초기 레이어(Lower Layer) : 인접한 문장에 대한 정보</p>
</li>
<li><p>후기 레이어(Higher Layer) : 여러 문장에 대한 정보</p>
</li>
</ul>
<h3 id="32-extractive-summarizationbertsumext">3.2. Extractive Summarization(BERTSUMEXT)</h3>
<p>이 절을 설명하기 위해 우선 단어에 대한 정의를 먼저 하겠습니다.</p>
<ul>
<li><p>$Sent_{i}$ : $i$번 째 문장을 의미하며 $i$는 1부터 시작</p>
</li>
<li><p>$d$ : 문서를 의미하며 $[Sent_{1}, Sent_{2}, ..., Sent_{n}]$를 포함</p>
</li>
<li><p>$t_{i}$ : 인코더 BERTSUM 에서 $i$번째 [CLS] 토큰에 대응되는 출력 벡터</p>
</li>
<li><p>$y_{i} \in \left{0,1\right}$ : $Sent_{i}$ 문장($t_{i})에 대한 정답 값으로 이 문장을 요약할 내용에 포함 여부에 대한 이진 분류 정답 값</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/142fce64-914c-4ef5-92d3-af78d6b05764/image.PNG" alt=""></p>
<p>BERTSUMEXT는 BERTSUM의 출력 벡터에 Inter-Sentence Transformer 를 적용한 것과 같고, 다음과 같은 과정으로 작동합니다.</p>
<ol>
<li><p>이전 절에서 말한 테이터 구조를 BERTSUM의 입력으로 합니다. ( [CLS] 토큰에 대한 위치 정보(인덱스)를 기억 )</p>
</li>
<li><p>BERTSUM의 출력 벡터들 중 [CLS] 토큰에 대응되는 벡터($t_{i}$)들의 집합($T$)를 만들어 줍니다.</p>
</li>
<li><p>$T$에 대해서 Positional Embedding을 진행한 후 Transformer Encoder(BERTSUMEXT)에 입력으로 넣어줍니다.</p>
<p> $$
 h^{0} = PosEmb(T) \
 \check{h}^{l} = LN(h^{l-1}+MHAtt(h^{l-1})) \
 h^l = LN(\check{h}^{l}+FFN(\check{h}^{l}))
 $$</p>
</li>
<li><p>최종적으로 각 출력에 대해 FC Layer를 적용하여 Sigmoid 함수를 적용하여 각 문장에 대한 이진 분류를 할 수 있도록 합니다.</p>
<p> $$
 \hat{y}<em>{i} = \sigma(W</em>{o}h^{L}<em>{i}+b</em>{o})
 $$</p>
</li>
</ol>
<p>실제 공식 GitHub에서 코드를 확인하면 다음과 같습니다.</p>
<ul>
<li><p>PreSumm/src/models/model_builder.py </p>
<ul>
<li><p>모델 학습 함수 &quot;train_single_ext&quot; 내부의 Model 부분</p>
<ul>
<li>class ExtSummarizer(nn.Module)</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-python">class ExtSummarizer(nn.Module):
    def __init__(self, args, device, checkpoint):
        # ...
        self.ext_layer = ExtTransformerEncoder(self.bert.model.config.hidden_size,
                                args.ext_ff_size, args.ext_heads,args.ext_dropout, args.ext_layers) 
                                # Inter-Sentence Transformer+ Sigmoid FC Layer 부분
        # ...

    def forward(self, src, segs, clss, mask_src, mask_cls):
        top_vec = self.bert(src, segs, mask_src) # BERTSUM 부분의 출력 벡터들 != 집합 T
        sents_vec = top_vec[torch.arange(top_vec.size(0)).unsqueeze(1), clss] # 출력 벡터들 중 [CLS] 토큰에 대응 되는 벡터들 == 집합 T
        sents_vec = sents_vec * mask_cls[:, :, None].float()
        sent_scores = self.ext_layer(sents_vec, mask_cls).squeeze(-1) # Inter-Sentence Transformer+ Sigmoid FC Layer
        return sent_scores, mask_cls</code></pre>
<p>학습 파라미터는 다음과 같습니다.</p>
<ul>
<li><p><strong>Inter-Sentence Transformer의 레이어 수(Best)</strong> : 2</p>
</li>
<li><p><strong>Optimizer</strong> : Adam ($\beta_{1}= 0.9, \beta_{2}= 0.999$  )</p>
</li>
<li><p><strong>Loss</strong> : Binary Cross Entropy (예측값, 정답값(Gold Label))</p>
</li>
<li><p><strong>LR Scheduler</strong> : Warm-Up($warmup=10,000$)
  $$
  lr = 2e^{-3} \cdot min(step^{-0.5},step \cdot warmup^{-1.5})
  $$</p>
</li>
</ul>
<h3 id="33-abstractive-summarizationbertsumextabs">3.3. Abstractive Summarization(BERTSUMEXTABS)</h3>
<p>추상 요약은 앞서 말했듯이 추출요약과 달리 자연어 요약 문제를 생성을 통해 문제를 해결하고자 합니다.</p>
<p>그래서 본 논문의 저자들은 기초적인 Transformer의 Encoder-Decoder 방식을 사용합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3d0e0e80-2192-4627-ae11-001ba7e81418/image.PNG" alt=""></p>
<p>Encoder는 사전학습된 BERTSUM 가중치를 사용하며 Decoder는 무작위로 가중치를 초기화 합니다.</p>
<p>추출 요약 모델과 추상 요약 모델을 따로 학습하는 것이 아닌 순차적으로 추출 요약을 학습하고 추상 요약 모델을 학습하는 간단한 순서로 이루어집니다.</p>
<p><strong>※ 추출 요약을 활용하는 것이 추상 요약의 성능을 더 크게 향상시킨다고 합니다.</strong></p>
<p>이 과정에서 Encoder는 이미 사전학습 및 Fine-Tuning 된 상태이며 Decoder는 학습 초기 단계이기 때문에 같은 학습률(LR)로 학습을 진행하는 경우 다음과 같은 문제점이 발생합니다.</p>
<ul>
<li><p>Encoder : 이미 최적화된 상태에서 더 학습이 되는 경우 과적합이 발생할 가능성이 높음</p>
</li>
<li><p>Decoder : Encoder의 과적합을 방지하고자 학습을 적게하면 과소적합이 발생할 가능성이 높음</p>
</li>
</ul>
<p>이를 해결하기 위해 Encoder와 Decoder 부분의 학습률을 아래와 같이 다르게 설정하여 Fine-Tuning을 진행합니다. </p>
<p><strong>※ Decoder의 Layer수가 6인 점과 LR 부분을 제외하면 나머지 하이퍼 파라미터는 BERTSUMEXT와 동일</strong></p>
<p>$$
lr_{\epsilon} = \check{lr}<em>{\epsilon} \cdot min(step^{-0.5},step \cdot warmup</em>{\epsilon}^{-1.5})\
lr_{D} = \check{lr}<em>{D} \cdot min(step^{-0.5},step \cdot warmup</em>{D}^{-1.5})
$$</p>
<ul>
<li><p>$\check{lr}_{\epsilon} = 2e^{-3}$</p>
</li>
<li><p>$warmup_{\epsilon} = 20,000$</p>
</li>
<li><p>$\check{lr}_{D} = 0.1$</p>
</li>
<li><p>$warmup_{D} = 10,000$</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/01ff390a-31d2-4058-bc95-175a5f536644/image.PNG" alt=""></p>
<h2 id="4-마무리">4. 마무리</h2>
<p>본 논문은 요약 문제에 대해 직관적인 학습 방법을 사용하였고 복잡한 방법을 사용하지 않았다는 점이 인상 깊었던 거 같습니다.</p>
<p>특히 상대적으로 쉬운 추출 요약을 먼저 학습한 이후 더 어려운 문제인 추상 요약에 대해 학습하는 과정을 보며 Curriculum Learning(커리큘럼 학습)과 유사하다고 생각하였습니다.</p>
<blockquote>
<p>Curriculum Learning : 상대적으로 쉬운 데이터에 대해 학습하도록 하여 좋은 성능을 낼 때 더 어려운 데이터로 학습하여 모델의 성능을 점진적으로 향상시키는 것으로 일반화 향상에 도움이 된다. - ChatGPT</p>
</blockquote>
<p>자연어 요약 문제를 쉽게 생각했었지만 파면 팔 수록 더 어려운 문제인 거 같습니다...</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[SKT FLY AI Challenger 4기 합격 후기]]></title>
            <link>https://velog.io/@pre_f_86/SKT-FLY-AI-Challenger-4%EA%B8%B0-%EC%A7%80%EC%9B%90-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@pre_f_86/SKT-FLY-AI-Challenger-4%EA%B8%B0-%EC%A7%80%EC%9B%90-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 29 Nov 2023 18:19:09 GMT</pubDate>
            <description><![CDATA[<h1 id="처음으로-최종-합격을">처음으로 최종 합격을?</h1>
<p><strong>SKT FLY AI Challenger : <a href="https://www.skttechacademy.com/">SKT FLY AI 홈페이지</a></strong></p>
<p><strong>SKT FLY AI 수료 후기 : <a href="https://velog.io/@pre_f_86/SKT-FLY-AI-Challenger-4%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0">SKT FLY AI 4기 수료 후기 - Velog</a></strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/9750ddf3-19ea-4402-8a34-10a9769b1bfd/image.jpg" alt=""></p>
<h2 id="지원동기">지원동기</h2>
<p>SKT FLY AI에 대한 정보를 얻게 되어 알아보았는데 엄청 괜찮아 보였습니다.</p>
<p>비록 교육 기간이 매우 짧아서 걱정되기는 했지만 그래도 장점이 많아 보이기에 지원했습니다.</p>
<blockquote>
<p>후기를 보니까 Github, DevOps, Docker 등 협업, 코드 관리에 대한 내용을 배우네? 지금 내가 제일 필요한 거잖아?</p>
</blockquote>
<p>과거 ICT 인턴십에서 서류 합격 후에 면접을 내용 중에 깃허브나, 도커 등에 대해서 사용해본 적이 있냐고 질문 받았을 때 &quot;써본 경험은 없지만 회사에 들어가기 전 미리 공부를 해서 오겠습니다.&quot; 라고 답변을 했는데 반응이 싸늘했던 기억이 있었네요..</p>
<p>그렇다고 Colab 환경에서 혼자 프로젝트를 진행하다보니까 도커나 깃허브를 쓸 일이 더더욱 없었었던 거 같습니다...(코드 관리 해주지, 가상환경 좋지.)</p>
<p>짧은 교육기간 내에 깊게 배우기는 어렵지만 필요하면 그때 따로 알아가면 되겠지라는 생각도 있었습니다.</p>
<p>결정적으로 지원하게 된 계기는 5주차 이후 진행되는 프로젝트 때문입니다.</p>
<blockquote>
<p>배운지 얼마 안됐는데 바로 팀 프로젝트를 진행한다고?</p>
</blockquote>
<p>저는 이론으로 배우는 것보다는 실전으로 박으면서 공부하는게 더 잘 된다고 생각하기 때문에 이를 보고 지원할 결심을 하게 됐습니다.</p>
<h2 id="1-서류">1. 서류</h2>
<p>입력할 서류는 자기 소개 관련 500자 이내와 학력 등이 있었습니다. 포트폴리오는 PDF형식으로 제출 해야했습니다.</p>
<p>제 노션 포트폴리오를 PDF로 만들려고 했는데 데이터베이스 보기 방식이 갤러리 형식이 아니라 테이블 형식으로 보여서 별로 안이뻐 보였었네요...</p>
<p>갤러리 형식으로 보이게 하는게 이쁘고 눈에 잘 들어 오기도 해서 갤러리 형식으로 보이게 하려고 여러 방법을 막 시도 해봤던 거 같습니다.</p>
<p>결국 웹 페이지에서 인쇄하는 방식으로 보이는 그대로 PDF로 만들었습니다.</p>
<p>※ 주의하실 점은 한 번 제출하면 수정할 수 없으니까 신경써서 잘 제출해주세요.</p>
<h3 id="11-지원-동기-500자">1.1. 지원 동기 (500자)</h3>
<p>인공지능이 왜 필요하고 뭐가 중요하고 이를 어떻게 공부했는지 얘기하면서 인공지능에 대한 관심을 어필했습니다.</p>
<p>추가적으로 혼자 공부하면서 느낀 문제점을 적고 SKT FLY AI의 특성상 같이 공부하니까 같이 공부하는 것의 장점을 얘기했습니다.</p>
<h3 id="12-취업-이력-500자">1.2. 취업 이력 (500자)</h3>
<p>취업은 해본 적이 없어서 그냥 없습니다. 하고 끝냈습니다.</p>
<h3 id="13-자기-소개서-500자">1.3. 자기 소개서 (500자)</h3>
<p>인공지능을 공부하고 싶은 이유에 대해서 얘기하고 이 이유에 프로젝트를 한 동기까지 연관지었습니다.</p>
<h3 id="14-대외-활동-500자">1.4. 대외 활동 (500자)</h3>
<p>LG Aimers 활동과 Samsung AI Challenge 활동을 적으면서 좋은 성과를 냈던 경험을 얘기했습니다.</p>
<p>특히 대회 중 깨달은 점과 모르던 분야임에도 노력해서 상위권을 달성한 얘기를 작성했습니다.</p>
<h3 id="15-향후-계획-500자">1.5. 향후 계획 (500자)</h3>
<p>제가 하고 싶던 프로젝트에 대해서 주로 얘기했습니다. 구체적이지는 않지만 큰 틀이 어떻게 구성되어 있고 진행할 방향인지를 얘기했습니다.</p>
<h2 id="2-코딩-테스트">2. 코딩 테스트</h2>
<p>서류는 합격하였습니다.</p>
<p>합격이라는 소리는 언제 들어도 기분이 좋은 거 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/1ea05156-3dc6-4e5e-8817-ecf6023dd468/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d353d1aa-209f-4ebb-828e-76895b94a22e/image.PNG" alt=""></p>
<p>합격 소식은 문자와 메일로 옵니다.</p>
<p>서류 접수 기간이 하루 정도 늦춰져서 마감하자마자 하루만에 바로 합격 통보가 왔네요.</p>
<p>뭔가 크게 중요하지 않은 거 같은 느낌??</p>
<h3 id="21-주의사항">2.1. 주의사항</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/78d4f119-f533-4627-9c11-0de458ec5e56/image.png" alt=""></p>
<p>사전에 응시페이지 들어갈 수 있게 해주는데 이때 꼭 하셔야 합니다!</p>
<p>ICT 인턴십 때는 코테를 그냥 컴파일 잘 되고 이런 부분 확인하라고 했었어서 이번에도 그런 줄 알고 안들어갔습니다.....</p>
<p>당시에 프로그래머스에서 문제가 없었어서 괜찮겠지 싶었는데 제 생각이랑은 완전 달랐습니다.</p>
<blockquote>
<p>부정행위 방지를 위해서 시험 환경 제한사항, 카메라 연결 테스트 등에 대한 사전 점검(?)을 하니까 꼭 들어가 보셔야 합니다!!</p>
</blockquote>
<p>제한 사항을 모르고 카페에서 코테 시작하려다가 장소 옮기고 해서 40분 늦게 시작했네요..</p>
<h3 id="22-코딩테스트-문제">2.2. 코딩테스트 문제</h3>
<p>총 4문제로 이루어져 있습니다.</p>
<blockquote>
<p>준비는 <strong>프로그래머스 레벨 2 수준 문제</strong>들 풀면서 준비했던 거 같습니다.</p>
</blockquote>
<p>문제 유출은 안된다고 주의사항이 있으므로 간단하게 어떤 느낌인지만 말해드리겠습니다.</p>
<p>1번과 2번은 <strong>간단한 구현 문제</strong>였었어서 시간 초과나, 메모리, 오버플로우 등이 발생하는지 범위나 시간 복잡도 계산을 간단하게 했었는데 문제는 없었던 거 같습니다. (백준 기준 브론즈 1 ~ 실버 4정도 될 거 같았습니다.)</p>
<p>다음에 4번으로 바로 넘어갔는데 문제는 흔한 <strong>BFS 문제</strong>였던 거 같습니다. 조건만 잘 보시면 크게 무리 없이 해결하실 수 있으실 거 같습니다.</p>
<p>3번은 시간이 없어서 제대로 보지는 못했는데 문제 자체가 전체 경우의 수 찾는 문제이고 범위도 작았던 거 같아서 아마 <strong>완전 탐색</strong>으로 문제를 풀어나갔으면 될 거 같습니다.</p>
<h2 id="3-면접">3. 면접</h2>
<p><strong>SKT FLY AI는 다른 곳들과는 다르게 합격 점수가 코테+면접으로 정해지니 면접 꼭 보러 가셔야 합니다.</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/2face762-e9b0-483b-8d19-21e0ac69c43f/image.png" alt=""></p>
<p>면접 장소는 SKT타워에서 진행되는데 건물 진짜 크고 내부도 엄청 이쁩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/fec364d4-bf6b-4b3c-a7ee-6ddb001ccd51/image.jpg" alt=""></p>
<p>지하 2층인데도 엄청 이쁘게 되어있었습니다.</p>
<blockquote>
<p>여기서 교육받고 하면 진짜 기분 좋겠다 꼭 붙어야지 ㅋㅋ</p>
</blockquote>
<p>위처럼 생각하고 들어갔었는데 나중에 알고보니 교육은 SKT 보라매 사옥에서 진행된다고 하네요....</p>
<p>아무튼 면접 대기는 사진에서 오른쪽 벽 뒤에 공간에서 대기했습니다.</p>
<p>면접은 조별로 5명씩 들어가서 면접관님 두분이 면접을 진행해주십니다.</p>
<h3 id="31-면접-내용">3.1. 면접 내용</h3>
<p>면접 분위기는 밝고 엄청 좋았습니다. 특히 오른쪽에 계신 면접관님이 분위기를 풀어주려고 하셨습니다.</p>
<p>들어오자마자 앞에 거울 한 번 보여주고 싶다고 하시면서 저를 포함한 모든 지원자가 다 딱딱하게 굳어있다고 하셨네요 ㅋㅋ</p>
<blockquote>
<p>머리로는 아는데... 몸이 따라가주질 않는다....</p>
</blockquote>
<p>이후에 면접자 2명이 안온 얘기를 하는데 경쟁자 두명 줄여서 좋으시겠어요 하고 웃으셨습니다.</p>
<p>제가 느끼기에 어렵다 라고 생각이 드는 질문은 없었던 거 같습니다.</p>
<p>질문 과 답변들은 아래처럼 했습니다.</p>
<ul>
<li>자기소개 1분</li>
</ul>
<p>이 부분은 제가 자소서에 쓴 내용을 기반으로 왜 지원했는지 앞으로는 어떻게 교육을 들을 것인지 얘기했습니다.</p>
<ul>
<li>커리큘럼에 대해서 알고 있는가?</li>
</ul>
<p>자세히는 안나와있어서 블로그를 찾아보며 어떤 교육이 있다는 것을 알고 있습니다 정도로 얘기했습니다.</p>
<ul>
<li>프로젝트 어떤 거 했는지?</li>
</ul>
<p>주로 컴퓨터 비전 쪽을 했지만 그럼에도 기억에 남는 LG Aimers에 관한 얘기를 했습니다.</p>
<blockquote>
<p>끝인가요?</p>
</blockquote>
<p>하나 프로젝트를 길게 한 거 같아서 더 얘기 하면 안될 거 같다고 생각했는데 지금 와서 생각해보면 그냥 다 말해도 됐을 거 같습니다.</p>
<p>다른 프로젝트들도 설명 드리고 싶었는데 아쉬웠습니다.</p>
<ul>
<li>갈등 상황은 어떻게든 생길 것인데 갈등 관리 어떻게 하는 편인가?</li>
</ul>
<p>의견 문제를 위주로 얘기했습니다.</p>
<ul>
<li>SKT FLY AI 알게된 경로</li>
</ul>
<p>솔직하게 인공지능 정보 공유해주는 오픈채팅방에서 봤다고 말했습니다. </p>
<p>다른 지원자 분들은 학교 플랜카드나 공지사항(?)에서 보고 지원하셨다고 했습니다.</p>
<ul>
<li>교육이 시작되면 보라매 사옥에서 진행할 것인데 어디서 오는지? 힘들지는 않을까?</li>
</ul>
<p>1시간 거리긴 한데 집하고 역하고 가까워서 괜찮다고 했었던 거 같습니다.</p>
<ul>
<li>질문사항</li>
</ul>
<p>커리큘럼이나 프로젝트 진행 방향 같은 거 물어봤던 거 같습니다.</p>
<h2 id="4-최종-합격">4. 최종 합격</h2>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/4ffbbcf3-82cc-4e86-a08e-4e725d536151/image.png" alt=""></p>
<p>11월 마지막 주에 결과가 나온다고 했어서 기대하면서 기다렸습니다.</p>
<blockquote>
<p>면접 때 잘 얘기한 거 같고 코딩테스트도 나쁘지 않게 봤던 거 같은데, 이 정도면 합격 노려볼만 할 거 같은데?</p>
</blockquote>
<p>마지막 주 금요일 쯤에 결과가 나올줄 알았습니다. 근데 생각과는 달리 마지막 주 월요일 9시가 되자마자 문자가 왔고 바로 부모님한테 합격했다고 말한 거 같습니다.</p>
<p>같이 지원한 동기도 합격했다고 하니 더 기분이 좋았던 거 같습니다. ㅋㅋㅋㅋ</p>
<p>앞으로 만나게 될 사람들과 같이 열심히 해서 프로젝트까지 무사히 진행하며 좋은 결과 낼 수 있었으면 좋겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MIC 논문 리뷰]]></title>
            <link>https://velog.io/@pre_f_86/MIC-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@pre_f_86/MIC-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Sat, 25 Nov 2023 08:45:38 GMT</pubDate>
            <description><![CDATA[<p>본 페이지에서는 <a href="https://arxiv.org/abs/2212.01322">MIC: Masked Image Consistency for Context-Enhanced Domain Adaptation</a> 논문에 대해서 말하고자 합니다.</p>
<p>DACS, DAFormer와 직접적인 연관성이 있고 해당 페이지에만 있는 내용도 있으니 아래의 링크를 통해 같이 보시는 걸 추천드립니다.</p>
<ul>
<li><p><a href="https://velog.io/@pre_f_86/DACS-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DACS: Domain Adaptation via Cross-domain Mixed Sampling 논문 리뷰</a></p>
</li>
<li><p><a href="https://velog.io/@pre_f_86/DAFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DAFormer: Improving Network Architectures and Training Strategies for Domain-Adaptive Semantic Segmentation 논문 리뷰</a></p>
</li>
<li><p><a href="https://velog.io/@pre_f_86/HRDA-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">HRDA: Context-Aware High-Resolution Domain-Adaptive Semantic Segmentation 논문 리뷰</a></p>
</li>
</ul>
<hr>
<h2 id="1-intro">1. Intro</h2>
<p>기존의 UDA 방법론들은 시각적인 유사성에 기반에 UDA를 진행하였습니다.</p>
<p>즉, 이미지 내의 Context 정보를 완전히 활용하지 않았다는 것입니다.</p>
<p>아래의 사진으로 예를 들어보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d781b3c5-f3d1-4345-96a8-1f39e58f0af6/image.PNG" alt=""></p>
<p>Target Image만 보았을 때 실제로 어디가 보도블럭이고 어디가 도로인지 겉으로 보기에는 구분이 잘 안갑니다.</p>
<p>하지만 주변의 건물, 나무 등과 같이 이미지 내의 Context를 보면 보도블럭과 도로를 쉽게 구분할 수 있습니다.</p>
<p>이처럼 외적인 부분에 대해서만 학습한 모델(a)은 보도블럭과 도로를 잘 구분하지 못합니다.</p>
<p>반대로 주변 Context 정보에 대해서 학습한 모델(b)은 둘을 잘 구분하는 모습을 보입니다.</p>
<p>본 논문의 저자들은 Context 정보에 대해서도 학습시키기 위해서 Masked Image Consistency(MIC) 모듈을 소개합니다.</p>
<ol>
<li><p>Random Masking 방법을 통한 Spatial Context 정보 학습을 더 원활하게 하여 강건한 특징 활용</p>
</li>
<li><p>Classification, Semantic Segmentation, Object Detection 등과 같은 여러 Vision Task에서도 쉬운 적용과 성능 향상</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f9c551dc-88f6-420a-8118-007f041f0f52/image.PNG" alt=""></p>
<h2 id="2-term">2. Term</h2>
<p>본 논문을 이해하시기 전에 용어들이 어떤 것을 의미하는지 확인하시면 편하실 겁니다.</p>
<ul>
<li><p>$N_s$ : Source 데이터들의 개수 입니다.</p>
</li>
<li><p>$N_t$ : Target 데이터들의 개수 입니다.</p>
</li>
<li><p>$X^S =     \left{{x^{S}<em>k}\right}^{N_S}</em>{k=1}$ : Source 이미지들을 의미하며  $x^{S}_{k}$ 는 $k$번째 Source 이미지 입니다.</p>
</li>
<li><p>$Y^S =     \left{{y^{S}<em>k}\right}^{N_S}</em>{k=1}$ : Source 라벨들을 의미하며  $y^{S}_{k}$ 는 $k$번째 Source 라벨 입니다.</p>
</li>
<li><p>$X^T =     \left{{x^{T}<em>k}\right}^{N_T}</em>{k=1}$ : Target 이미지들을 의미하며  $x^{T}_{k}$ 는 $k$번째 Target 이미지 입니다.</p>
</li>
<li><p>$\theta$ : Student Model의 가중치를 의미합니다.</p>
</li>
<li><p>$\phi$ : Teacher Model의 가중치를 의미합니다.</p>
</li>
<li><p>$f_{\theta}$ : Student Model을 의미합니다.</p>
</li>
<li><p>$g_{\phi}$ : Teacher Model을 의미합니다.</p>
</li>
</ul>
<h2 id="3-method">3. Method</h2>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/844f9693-1aa9-40c9-854d-48cb48ce4317/image.PNG" alt=""></p>
<h3 id="31-unsupervised-domain-adaptation-uda">3.1. Unsupervised Domain Adaptation (UDA)</h3>
<h4 id="student-model-f_theta">Student Model ($f_{\theta}$)</h4>
<p>일반적으로 Classification이나 Segmentation에서 Source Domain에서 $f_{\theta}$를 학습시키기 위해서는 Categorical Cross Entropy를 사용합니다.</p>
<p>그 식은 다음과 같습니다.</p>
<p>$$
L^{S,cls/seg}<em>{k} = H(f</em>{\theta}(x^{S}<em>{k}),y^{S}</em>{k})
$$
$$</p>
<p>H(\hat{y},y) = - \sum^{H}<em>{i=1}\sum^{W}</em>{j=1} \sum^{C}<em>{c=1} y</em>{ijc} \log\hat{y}_{ijc}
$$</p>
<p>이때 Classification는 $H=W=1$인 경우입니다.</p>
<p>UDA를 위해서는 $X^T =     \left{{x^{T}<em>k}\right}^{N_T}</em>{k=1}$를 위한 Loss함수를 사용해야하는데 이를 $L^{T}_{k}$라고 합니다.</p>
<p>최종적인 Loss함수는 다음과 같습니다.</p>
<p>$$
Loss = \underset{\theta}{min}\frac{1}{N_{S}}\sum^{N_{S}}<em>{k=1}L^{S}</em>{k} + \frac{1}{N_{T}}\sum^{N_{T}}<em>{k=1}\lambda^{T}L^{T}</em>{k}
$$</p>
<p>DACS나 DAFormer의 경우 $\frac{1}{N_{T}}\sum^{N_{T}}<em>{k=1}\lambda^{T}L^{T}</em>{k}$
는 Mix된 이미지에 대한 Loss입니다.</p>
<h4 id="teacher-model-g_phi">Teacher Model ($g_{\phi}$)</h4>
<p>DACS나 DAFormer와 동일하게 Teacher Model($g_{\phi}$)의 가중치 $\phi$를 지수이동평균(Exponetial Moving Average)을 이용해 업데이트합니다.</p>
<p>그 식은 다음과 같습니다.</p>
<p>$$
\phi_{t+1} = \alpha\phi_t + (1-\alpha)\theta_t
$$</p>
<p>$t$시점의 $\alpha$는 다음과 같이 정해집니다.</p>
<p>$$
\alpha = max(\alpha_{max},1 - \frac{1}{t})
$$</p>
<p>EMA를 사용함으로써 이전 시점의 Student Model($f_{\theta}$)을 Ensemble하는 것과 같은 효과를 낼 수 있습니다.</p>
<p>이로 인해서 강건함을 증진시키고 Pseudo Label의 Temporal 안정성을 증대시킵니다.</p>
<p>특히 Student Model($f_{\theta}$)을 통해 가중치를 업데이트 하기 때문에 Context 정보에 대한 가중치도 가지고 있게 됩니다.</p>
<p>이러한 특성 때문에 Pseudo Label을 생성할 때 Teacher Model($g_{\phi}$)에 원본 Target 이미지($x^T$)를 입력으로 넣게 되면 시각적인 요소와 Context 요소를 둘 다 사용하기 때문에 고품질의 Psuedo Label을 생성하게 됩니다.</p>
<h3 id="32-masked-image-consistency-mic">3.2. Masked Image Consistency (MIC)</h3>
<p>Masking된 부분에 대해서 정확한 Segmentation을 하는 것은 사람의 벽뒤에 어떤 것이 있을까를 예측하는 것과 동일합니다.</p>
<p>즉, 주변의 상황에 대한 이해를 통해 안보이는 부분에 대해서 예측하는 것입니다.</p>
<p>Loss를 최소로 하기 위해서 결국에는 시각적인 정보 뿐만이 아닌 Context 정보를 활용하도록 모델에게 학습시키는 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/0a99cad3-ce03-4ffb-b0a7-aff48b022f60/image.PNG" alt=""></p>
<p>위에 있는 표를 보면 실제로 Masking을 한 것과 하지 않은 것의 차이를 보면 mIoU가 20정도로 큰 차이가 납니다.</p>
<p>이런 MIC의 과정은 Fig 3의 아래 부분과 같습니다.</p>
<p>이 과정을 설명하면 다음과 같습니다.</p>
<h4 id="masking">Masking</h4>
<ol>
<li><p>Masking Map을 만듭니다.</p>
<p> 패치들에 대해서 균등 분포를 따르도록 랜덤하게 0~1사이의 수를 입력합니다.</p>
<p> 이후에 각 패치의 값이 임계값 $r$을 넘으면 1 아니면 0으로 이진화합니다.</p>
<p> $$
 M_{mb+1:(m+1)b,\space nb+1:(n+1)b} = \space [v&gt;r] \space with \space v \space  \sim u(0,1)
 $$</p>
<p> 이때 $b$는 패치 크기이며 $r$은 이며 $m$과 $n$은 각각 다음과 같습니다.</p>
<p> $$
 m \in [0 .. W/ (b-1)],\
 n \in [0 .. W/( b-1)]
 $$</p>
</li>
<li><p>Target 이미지($x^T$)와 $M$을 픽셀별로 곱하여 Masking 이미지를 만들어 냅니다.</p>
<p> $$
 x^M = M \odot x^T
 $$</p>
</li>
</ol>
<p>이 과정을 코드로 보이면 다음과 같습니다.</p>
<pre><code class="language-python">import torch
import torch.nn.functional as F

def masking(input,mask_ratio = 0.5 , mask_size = 32):
    b,c,h,w = input.shape

    h_patch = h // mask_size
    w_patch = w // mask_size

    mask = (torch.rand((b,1,h_patch,w_patch),device=input.device) &gt; mask_ratio).float()

    mask = F.interpolate(mask,(h,w),mode=&#39;nearest&#39;)

    output = input.detach().clone() * mask

    return output</code></pre>
<h4 id="training">Training</h4>
<ol>
<li><p>Masking한 이미지 $x^M$를 Student Model의 입력으로 넣습니다.</p>
<p> $$
 \hat{y}^M = f_{\theta}(x^{M})
 $$</p>
</li>
<li><p>Masking 하기 전 이미지 $x^T$를 Teacher Model의 입력으로 넣어 Pseudo Label($p^T)을 생성합니다.</p>
<p> <strong>$p^T$ 생성</strong></p>
<ul>
<li><p><strong>Classification, Segmentation</strong></p>
<p>  $$
  p^{T,cls,seg}<em>{ij} = [c = \underset{c&#39;}{argmax}g</em>{\phi}(x^T)_{ijc&#39;}]
  $$</p>
</li>
<li><p><strong>Obeject Detection</strong></p>
<p>  임계치 $\delta$와 Non-Maximum Suppression 알고리즘을 통해 출력 결과를 필터링 하여 만들어 줌</p>
</li>
</ul>
</li>
<li><p>2번과 동일하게 Masking 하기 전 이미지 $x^T$를 Teacher Model의 입력으로 넣어 Pseudo Label($q^T)을 생성합니다.</p>
<p> <strong>$q^T$ 생성</strong></p>
<ul>
<li><p><strong>Classification</strong></p>
<p>  $$
  q^{T,cls} = \underset{c&#39;}{max}g_{\phi}(x^T)_{c&#39;} &gt; \tau
  $$</p>
</li>
<li><p><strong>Segmentation</strong></p>
<p>  $$
  q^{T,seg} = \frac{\sum^{H}<em>{i=1}\sum^{W}</em>{j=1}[\underset{c&#39;}{max}g_{\phi}(x^T)_{ijc&#39;} &gt; \tau]}{H \cdot W}
  $$</p>
</li>
<li><p><strong>Object Detection</strong></p>
<p>  각 Bounding Box에 Classification Brach에 대해서 Classification의 경우와 동일하게 적용</p>
</li>
</ul>
</li>
<li><p>Masking에 대한 Loss 계산합니다.</p>
<p> $$
 L^M =  q^T H(\hat{y}^M,p^T)
 $$</p>
</li>
</ol>
<p>최종적으로 MIC의 Loss는 다음과 같이 구성됩니다.</p>
<p>$$
L^{MIC} = \underset{\theta}{min}\frac{1}{N_{S}}\sum^{N_{S}}<em>{k=1}L^{S}</em>{k} + \frac{1}{N_{T}}\sum^{N_{T}}<em>{k=1}(\lambda^{T}L^{T}</em>{k}+\lambda^{M}L^{M}_{k})
$$</p>
<h2 id="4-performance">4. Performance</h2>
<p>기존의 SOTA와 비교해 추론 이미지를 보면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/6312290d-219d-4448-84af-b48e942da713/image.PNG" alt=""></p>
<p>실제로 MIC가 다른 방법론에 비해 도로와 보도블럭을 잘 구분하는 모습을 보여줍니다.</p>
<p>※ 하이퍼 파라미터에 대한 자세한 내용은 논문에 나오므로 해당 부분은 논문에서 확인해주시면 됩니다.</p>
<h3 id="41-context-aware">4.1 Context Aware</h3>
<blockquote>
<p>그렇다면 정말로 Context를 이해하는 것인가?</p>
</blockquote>
<p>위 질문에 대한 답은 아래의 사진을 보면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3cdf0c62-f6c9-4a61-a299-1527b87e094d/image.PNG" alt=""></p>
<p>모델에게 대부분을 Masking 한 후 일부분만 보여주며 모델에 입력으로 사용한 결과는 진짜 그럴 듯 하게 분할한 결과를 내놓는다는 것입니다.</p>
<h3 id="42-why-only-target">4.2 Why Only Target</h3>
<blockquote>
<p>Source에도 Masking을 하면 성능이 더 좋지 않을까?</p>
</blockquote>
<p>논문을 보면서 이렇게 생각을 했는데 저자들이 실제로 실험을 해주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/2a37d3ae-e300-47bc-8307-593e2c902907/image.PNG" alt=""></p>
<p>데이터셋에 따라서 Source와 Target 둘다 Masking을 하는 것이 좋을 때도 있고 안좋을 때도 있습니다.</p>
<p>이를 보면 모델 구조가 복잡해지지 않게 Target에만 하는 것이 더 편할 거 같습니다.</p>
<h3 id="43-performance-increase-ratio">4.3 Performance Increase Ratio</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/99f042d5-0555-445e-a154-79f340626708/image.PNG" alt=""></p>
<p>모델과 UDA 방법에 따른 성능 향상 차이를 보면 위의 표와 같습니다.</p>
<p>실제로 모든 패치들과의 유사도를 계산하는 Transformer에서 오히려 성능 개선이 훨씬 더 좋을 줄 알았는데 성능 향상에는 둘 다 큰 차이가 없습니다.</p>
<h2 id="5-마무리">5. 마무리</h2>
<p>간단한 Masking 방법을 통해 모델에게 Context 정보를 이해시킨다는 점이 인상 깊었던 거 같습니다.</p>
<p>새로운 도메인에 대해서 알지 못하는 인공지능 모델에게 Masking 부분을 예측하도록 해서 학습을 한다는 점은 오히려 성능이 안좋을 거 같다고 생각했습니다.</p>
<p>그러나 오히려 기존 UDA 방법론에 간단한 MIC 모듈을 추가함으로써 전체적으로 성능이 향상되는 것을 보면 Masking 방법의 힘을 알 수 있는 거 같습니다.</p>
<p>특히 기존의 UDA 방법론들과는 달리 Classification, Detection에서도 사용가능하다는 점이 큰 메리트라고 생각되며 이를 잘 활용하면 KeyPoint Detection에서도 활용이 가능할 거 같습니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[HRDA 논문 리뷰]]></title>
            <link>https://velog.io/@pre_f_86/HRDA-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@pre_f_86/HRDA-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Sat, 25 Nov 2023 08:37:01 GMT</pubDate>
            <description><![CDATA[<p>본 페이지에서는 <a href="https://arxiv.org/abs/2204.13132">HRDA: Context-Aware High-Resolution Domain-Adaptive Semantic Segmentation</a> 논문에 대해서 말하고자 합니다.</p>
<p>DACS, DAFormer와 직접적인 연관성이 있고 해당 페이지에만 있는 내용도 있으니 아래의 링크를 통해 같이 보시는 걸 추천드립니다.</p>
<ul>
<li><p><a href="https://velog.io/@pre_f_86/DACS-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DACS: Domain Adaptation via Cross-domain Mixed Sampling 논문 리뷰</a></p>
</li>
<li><p><a href="https://velog.io/@pre_f_86/DAFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DAFormer: Improving Network Architectures and Training Strategies for Domain-Adaptive Semantic Segmentation 논문 리뷰</a></p>
</li>
</ul>
<hr>
<h2 id="1-intro">1. Intro</h2>
<p>기존의 UDA 방법론들은 좋은 성능을 내고 있었습니다.</p>
<p>그러나 기존의 UDA 방법들은 Source Domain과 Target Domain을 동시에 학습시키며 Student, Teacher Model 두 모델 사용과 많은 Loss 함수 때문에 GPU Memory 사용량이 매우 큽니다.</p>
<p>이러한 제한으로 GPU Memory 사용량을 줄이기 위해 기존 UDA 방법론들은 이미지의 크기를 줄여서 학습하는 방식을 선택했습니다.</p>
<p>예를 들면 CityScapes 데이터셋에서 일반적인 지도 학습을 하는 경우 높은 해상도(High-Resolution[HR])를 가진 원본 이미지 크기인 $2048 \times 1024$를 입력으로 하지만 기존 UDA 방법론들은 절반의 크기인 $1024 \times 512$를 입력으로 합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/2bc4f917-b4d6-4c7d-b78a-d5dbcc20cdc7/image.PNG" alt=""></p>
<h3 id="11-low-resolutionlr-problem">1.1 Low-Resolution(LR) Problem</h3>
<p>이 과정에서 다음과 같은 문제가 발생합니다.</p>
<ul>
<li><p>적어진 해상도(Low-Resolution[LR])로 인해서 세부 특징(Fine-Detail)들을 학습하지 못함</p>
</li>
<li><p>위의 이유로 신호등이나 사람 같은 작은 물체에 대해서 잘 감지하지 못하는 경우가 발생함</p>
</li>
</ul>
<h3 id="12-high-resolutionhr-crop-and-problem">1.2 High-Resolution(HR) Crop and Problem</h3>
<p>위의 문제를 해결하기 위해서 그동안 해상도를 줄이지 않고 Crop 하는 방식(HR Crop)으로 세부 특징이 사라지는 문제와 메모리 연산의 문제를 해결했습니다(Fig. 1 a).</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3557bd75-3181-4665-8351-2c7da213a69e/image.png" alt=""></p>
<p>※ HR Crop이라고 하는 이유는 Crop을 했다고 하지만 해상도를 줄이지 않고 Crop을 했기 때문에 HR Crop이라고 합니다.</p>
<p>하지만 이 방법에도 다음과 같은 문제가 발생합니다.</p>
<ul>
<li><p>전체 이미지 중 일부만을 보기 때문에 Global Context(Long-Range Context)에 대한 정보에 대해서 학습하지 못함</p>
</li>
<li><p>강건한 특징을 학습해야 하는 UDA의 특성상 Context 정보를 잃게되면 UDA의 성능이 제한됨</p>
</li>
</ul>
<h3 id="13-high-resolutionhr-problem">1.3 High-Resolution(HR) Problem</h3>
<p>위의 문제들을 제외하더라도 고해상도를 사용하는 것에 대한 문제가 있습니다.</p>
<p>물론 고해상도 입력(HR Inputs)를 사용하는 것은 작은 물체를 더 잘 적응(Adaptation)하도록 해줍니다. 그러나 보도블럭과 같이 매우 큰 물체(Large Stuff-Regions)에 대해서는 오히려 잘 적응(Adaptation)하지 못합니다.</p>
<p>이 이유를 다음과 같이 설명합니다.</p>
<ul>
<li><p>고해상도인 이미지는 매우 큰 물체(Large Stuff-Regions)에 대해서 너무 많은 세부 특징을 가지고 있다. 이렇게 특정 도메인의 세부적인 특징(Domain-Specific HR Textures)을 학습하게 되면 UDA에서는 오히려 악 효과가 날 수 있음</p>
</li>
<li><p>반면에 저해상도 이미지의 경우는 적당히 특징을 학습하기 때문에 다른 도메인에 대해서도 적절한 성능을 낼 수 있음</p>
</li>
</ul>
<p>너무 많은 세부적인 특징을 학습해 다른 도메인에서는 오히려 악영향을 끼칠 수 있다는 점 때문에 위의 이유를 과적합과 비슷한 이유라고 생각합니다.</p>
<h3 id="14-solution">1.4 Solution</h3>
<p>그래서 본 논문은 위의 문제들을 해결하기 위해 LR과 HR을 적절히 사용하는 방법을 제안합니다.(Fig. 1 b)</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d04d75ef-6fbd-4fb5-8a68-acca543ab72b/image.png" alt=""></p>
<ul>
<li><p>LR Context Crop : 원본 이미지의 일부를 Crop하여 Resize 하여 LR 이미지를 얻습니다.</p>
<p>  LR Context Crop은 HR과 관련된 문제점들을 해결하기 위한 방법으로 Context정보와 큰 물체에 대해서 적절하게 학습하도록 하여 특정 도메인의 세부적인 특징에 과적합되지 않도록 합니다.</p>
</li>
<li><p>HR Detail Crop : LR Context Crop에서 Resize전 이미지 내에서 일부를 Crop하여 HR 이미지를 얻습니다.</p>
<p>  HR Detail Crop은 LR과 관련된 문제점들을 해결하기 위한 방법으로 세부적인 특징들을 얻어낼 수 있고 이에 따라 작은 물체를 더 잘 탐지합니다.</p>
</li>
<li><p>Input-Dependent Scale Attention : 특정 범위 내에 HR Crop의 결과에 대한 신뢰도를 나타냅니다.</p>
<p>  LR Context Crop을 만들기 위해서 처음 Crop한 범위 내에서 특정 Pixel의 클래스 값을 HR Detail Crop과 LR Context Crop을 이용한 출력을 어느정도의 비율로 선택할지 정합니다.</p>
</li>
</ul>
<p>자세한 내용은 이후에 다루겠습니다.</p>
<h2 id="2-term">2. Term</h2>
<p>본 논문을 이해하시기 전에 용어들이 어떤 것을 의미하는지 확인하시면 편하실 겁니다.</p>
<ul>
<li><p>$N_S$ : Source 데이터들의 개수 입니다.</p>
</li>
<li><p>$N_T$ : Target 데이터들의 개수 입니다.</p>
</li>
<li><p>$X^S =     \left{{x^{S,m}<em>{HR}}\right}^{N_S}</em>{m=1}$ : Source 이미지들을 의미하며  $x^{S,m}<em>{HR}$ 는 $m$번째 Source 이미지 입니다.($x^{S,m}</em>{HR} \in \R^{H_{S} \times W_{S} \times 3}$)</p>
</li>
<li><p>$Y^S =     \left{{y^{S,m}<em>{HR}}\right}^{N_S}</em>{m=1}$ : Source 라벨들을 의미하며  $y^{S,m}<em>{HR}$ 는 $m$번째 Source 라벨 입니다.($y^{S,m}</em>{HR} \in \left{0,1\right}^{H_{S} \times W_{S} \times 3}$)</p>
</li>
<li><p>$X^T =     \left{{x^{T,m}<em>{HR}}\right}^{N_T}</em>{m=1}$ : Target 이미지들을 의미하며  $x^{T,m}_{HR}$ 는 $m$번째 Target 이미지 입니다.</p>
</li>
<li><p>$\hat{y}^{S}<em>{LR}$ : $f</em>{\theta}(x^{S}_{LR})$의 결과를 의미합니다.</p>
</li>
<li><p>$\theta$ : Student Model의 가중치를 의미합니다.</p>
</li>
<li><p>$\phi$ : Teacher Model의 가중치를 의미합니다.</p>
</li>
<li><p>$f_{\theta}$ : Student Model을 의미합니다.</p>
</li>
<li><p>$g_{\phi}$ : Teacher Model을 의미합니다.</p>
</li>
<li><p>$\zeta(x,s)$ : 입력 $x_{HR} \in \R^{H \times W \times C}$을 Scale $s$만큼 Upsample($s$&gt;1)을 적용하거나 Downsample($s$&lt;1) 적용합니다. </p>
</li>
<li><p>$H(x), W(x)$ : X의 해상도에서 Height(H),Width(W)를 의미합니다.</p>
</li>
</ul>
<h2 id="3-unsupervised-domain-adaptation-uda">3. Unsupervised Domain Adaptation (UDA)</h2>
<h3 id="31-student-model-f_theta">3.1 Student Model ($f_{\theta}$)</h3>
<p>Segmentation에서 모델을 학습시키는 경우 주로 Cross Entropy Loss 를 사용합니다.</p>
<p>$$
L_{CE}(\hat{y},y,q) = - \sum^{H(y)}<em>{i=1}\sum^{W(y)}</em>{j=1} \sum^{C}<em>{c=1} q</em>{ij}y_{ijc} \log\zeta(\hat{y},\frac{H(y)}{H(\hat{y})})_{ijc}
$$</p>
<p>위에서 $\zeta(x,s)$가 존재하는 이유는 Segmentation에서 일반적으로 출력 Map의 해상도는 원래 라벨의 해상도보다 작은 경우가 많기 때문에 해상도를 맞춰주기 위함입니다.</p>
<p>기존의 UDA 방식들과 동일하게 Source Domain은 Label이 존재하므로 Source Domain에 대한 학습은 Cross Entropy Loss를 사용하며 다음과 같습니다.</p>
<p>$$
L^{S} = L_{CE}(\hat{y}^{S}<em>{LR},y^{S}</em>{LR},1)
$$</p>
<p>Target Domain의 경우 라벨이 없기 때문에 다음과 같이 Teacher Model을 활용하여 만든 Pseudo Label을 통해 $p^{T}$와 $q^{T}$를 만들어 학습을 진행합니다.</p>
<p>$p^{T}$는 Pseudo Label을 Class 축을 기준으로 Argmax를 적용한 것이며 일반적인 Segmentation Mask와 동일합니다.</p>
<p>$$
p^{T}<em>{LR,ijc} = [ c = \underset{c&#39;}{argmax} g</em>{\phi}(x^{T}<em>{LR})</em>{ijc&#39;}]
$$</p>
<p>$q^{T}$는 Pseudo Label에서 각 픽셀값의 최대값이 임계값 이상인 픽셀의 비율이며 이를 통해 Loss의 크기를 조절합니다.</p>
<p>$$
q^{T}<em>{LR,ijc} = \frac{\sum^{H(g</em>{\phi}(x^{T}<em>{LR}))}</em>{i=1}\sum^{W(g_{\phi}(x^{T}<em>{LR}))}</em>{j=1}[\underset{c&#39;}{max}g_{\phi}(x^T_{LR})<em>{ijc&#39;} &gt; \tau]}{H(g</em>{\phi}(x^{T}<em>{LR})) \cdot W(g</em>{\phi}(x^{T}_{LR}))}
$$</p>
<p>이를 통해 Target Loss는 다음과 같습니다.</p>
<p>$$
L^{T} = L_{CE}(\hat{y}^{T}<em>{LR},p^{T}</em>{LR},q^{T}_{LR})
$$</p>
<p>일반적인 UDA 방법론들에서 사용하던 Loss는 다음과 같습니다.</p>
<p>$$
L = L^{S} + \lambda L^{T}
$$</p>
<p>이 Loss를 통해 Student Model을 학습시킵니다.</p>
<h3 id="32-teacher-model-g_phi">3.2 Teacher Model ($g_{\phi}$)</h3>
<p>본 논문에서는 주로 DAFormer 방법을 따르며 HRDA에 대해서 실험합니다.</p>
<p>DAFormer의 학습 방법과 동일하게 Teacher Model($g_{\phi}$)의 가중치 $\phi$를 지수이동평균(Exponetial Moving Average)를 이용해 업데이트합니다.</p>
<p>그 식은 다음과 같습니다.</p>
<p>$$
\phi_{t+1} = \alpha\phi_t + (1-\alpha)\theta_t
$$</p>
<p>$t$시점의 $\alpha$는 다음과 같이 정해집니다.</p>
<p>$$
\alpha = max(\alpha_{max},1 - \frac{1}{t})
$$</p>
<p>EMA를 사용함으로써 이전 시점의 Student Model($f_{\theta}$)을 Temporal Ensemble하는 것과 같은 효과를 낼 수 있습니다.</p>
<p>이로 인해서 안정성을 가진 Pseudo 라벨을 만들어낼 수 있습니다.</p>
<h2 id="4-method">4. Method</h2>
<p>앞에서 말했듯 고해상도(HR)에서는 작은 물체들을 적응(Adaptation)하기 쉽습니다.</p>
<p>반대로 저해상도(LR)에서는 큰 물체(Large Stuff Regions)에 대해서 적응(Adaptation)하기 쉽습니다.</p>
<p>또한 UDA 방법의 특성상 지도학습을 하는 것보다 더 많은 GPU Memory를 사용한다는 점이 있습니다.</p>
<p>본 절에서는 위 문제에 대한 해결 방법을 다음과 같은 순서로 설명할 예정입니다. </p>
<ol>
<li><p><strong>LR Context Crop, HR Detail Crop</strong></p>
<p> 저해상도 특징과 고해상도의 특징을 어떻게 뽑아낼 것인가?</p>
</li>
<li><p><strong>Multi-Resolution Fusion with Scale Attention</strong></p>
<p> 그 특징들을 어떻게 활용할 것인가?</p>
</li>
<li><p><strong>Pseudo-Label Generation With Overlapping Sliding Window</strong></p>
<p> 전체 이미지(Test 단계)를 추론하거나 일부분(Pseudo Label 생성 단계)을 추론하는 경우에는 어떻게 할 것인가?</p>
</li>
</ol>
<p>위의 1번과 2번에 대한 내용은 아래 그림에서 a에 해당하는 내용이며 3번은 b에 해당하는 내용입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3804b204-095b-4f61-86f2-50e4cc6d95c7/image.png" alt=""></p>
<h3 id="41-lr-context-crop-hr-detail-crop">4.1 LR Context Crop, HR Detail Crop</h3>
<p>GPU 메모리 사용량의 증가는 다음과 같은 이유로 발생합니다.</p>
<ul>
<li><p>학습하고자 하는 여러 도메인</p>
</li>
<li><p>추가적인 모델( Student, Teacher )</p>
</li>
<li><p>여러개의 Loss 함수</p>
</li>
</ul>
<p>이러한 GPU 사용량 때문에 Resize 방법(LR, Context) 또는 Random Crop(HR, Detail) 방식을 사용하여 GPU 사용량을 줄였습니다.</p>
<p>그러나 1절에서 말한 LR과 HR의 각각의 단점들로 인해 각 방법은 UDA 성능을 제한하게 됩니다.</p>
<p>두 방법은 서로 보완이 가능하기 때문에 본 논문의 저자들은 두 방법을 활용하려고 합니다.</p>
<p>그렇다면 LR과 HR의 특징을 추출하는지 살펴보면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d1b8acd5-3d7e-4713-a758-037342010e33/image.png" alt=""></p>
<ul>
<li><p>초록색 사각형 : 원본 이미지 $x_{HR}$( $\R \in^{H \times W \times C}$ )입니다.</p>
</li>
<li><p>주황색 사각형 : 원본 이미지에서 $x_{c,HR}$( $\R \in^{sH_{d} \times sW_{d} \times C}$ ) 의 크기로 Random하게 Crop 합니다. 이후 Resize하여  LR Context Crop $x_{c}$( $\R \in^{H_{c} \times W_{c} \times C}$ )를 만듭니다.</p>
</li>
<li><p>파란색 사각형 : 주황색 사각형 $x_{c,HR}$ 내에서 $x_{d}$( $\R \in^{H_{d} \times W_{d} \times C}$ )의 크기로 Random 합니다.  HR Detail Crop 부분에 해당됩니다.</p>
</li>
</ul>
<h4 id="hr-context-crop-x_chr">HR Context Crop ($x_{c,HR}$)</h4>
<p>논문에서는 그냥 Context Crop이라고 하였지만 저는 헷갈리지 않게 HR Context Crop이라고 하였습니다.</p>
<p>주황색 사각형에 해당되며 이 부분을 만드는 수식은 다음과 같습니다.</p>
<p>$$
x_{c,HR} = x_{HR}[b_{c,1}:b_{c,2},b_{c,3}:b_{c,4}]
$$</p>
<p>이때 $b_{c,1}$과 $b_{c,3}$ 은 균등 분포를 따라 다음과 같이 무작위로 수가 정해집니다.</p>
<p>$$
\begin{matrix}
b_{c,1} \sim u \left{0,(H-sH_{c})/k \right} \cdot k &amp; b_{c,2} = b_{c,1} + sH_{c} ,\
b_{c,3} \sim u \left{0,(W-sW_{c})/k \right} \cdot k &amp; b_{c,4} = b_{c,3} + sW_{c}. 
\end{matrix}
$$</p>
<p>이때 $k = s \cdot o$이며 $s$는 Downsample Scale을 의미하고 $o$는 Segmentation Model의 Output Stride를 의미합니다.</p>
<h4 id="lr-context-crop-x_c">LR Context Crop ($x_{c}$)</h4>
<p>주황색 사각형($x_{c,HR}$)을 Downsample Scale $S$만큼 Resize 하여 LR Context Crop을 만들어 줍니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/e82f441a-4e30-40d8-a84d-87ebc68e5a11/image.png" alt=""></p>
<p>수식은 다음과 같습니다.</p>
<p>$$
x_{c} = \zeta(x_{c,HR},1/s)
$$</p>
<p>본 논문에서 $s$는 2로 정해집니다.</p>
<h4 id="hr-detail-crop-x_d">HR Detail Crop ($x_{d}$)</h4>
<p>파란색 사각형에 해당되는 부분입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/e2b2724f-26d3-4446-a94e-0f2144e86b0b/image.png" alt=""></p>
<p>수식은 다음과 같습니다.</p>
<p>$$
x_{d} = x_{c,HR}[b_{d,1}:b_{d,2},b_{d,3}:b_{d,4}]
$$</p>
<p>이때 $b_{c,1}$과 $b_{c,3}$ 은 균등 분포를 따라 다음과 같이 무작위로 수가 정해집니다.</p>
<p>$$
\begin{matrix}
b_{d,1} \sim u \left{0,(sH_{c}-H_{d})/k \right} \cdot k &amp; b_{d,2} = b_{d,1} + H_{d} ,\
b_{d,3} \sim u \left{0,(sW_{c}-W_{d})/k \right} \cdot k &amp; b_{d,4} = b_{d,3} + W_{d}. 
\end{matrix}
$$</p>
<h4 id="prediction">Prediction</h4>
<p>본 논문에서는 $H_{c} = H_{d},W_{c} = W_{d}$로 사용하며 $s=2$를 사용합니다.</p>
<p>특히 $s=2$를 사용하게 되므로 LR Context Crop은 HR Detail Crop에 비해 4배 더 많은 지역의 Context를 다루게 됩니다.</p>
<p>이렇게 동일한 크기의 LR Context Crop과 HR Detail Crop을 동일한 Feature Encoder ($f^E$)와 Semantic Decoder($f^D$)에 입력으로 넣어 Segmentation Map($\hat{y}$)을 얻습니다.</p>
<p>$$
\hat{y}<em>{c} = f^{S}(f^{E}(x</em>{c})) \in \R^{\frac{H_{c}}{o} \times \frac{W_{c}}{o} \times C}
$$</p>
<p>$$
\hat{y}<em>{d} = f^{S}(f^{E}(x</em>{d})) \in \R^{\frac{H_{d}}{o} \times \frac{W_{d}}{o} \times C}
$$</p>
<h3 id="42-multi-resolution-fusion-with-scale-attention">4.2 Multi-Resolution Fusion with Scale Attention</h3>
<p>LR Context Crop과 HR Detail Crop을 입력으로 한 출력을 따로 학습을 시킨다면 결국에 여러 해상도를 모델에게 학습시키는 것으로 끝날 것입니다.</p>
<p>모델에게 Context 정보와 Detail 정보를 학습 시키기 위해서는 두 출력을 적절히 활용해야합니다.</p>
<p>그래서 Scale Attention 개념과 이를 활용해 가중합을 통하여 최종 결과를 얻어내 Loss를 구합니다.</p>
<p>이를 위해 Scale Network($f^A$)를 통해 Scale Attention $a_c$를 예측하며 수식은 다음과 같습니다.</p>
<p>$$
a_c = \sigma(f^{A}(f^{E}(x_c))) \in [0,1]^{\frac{h_c}{o} \times \frac{w_c}{o}\times C}
$$</p>
<p>$\sigma$는 Sigmoid 함수를 의미합니다.</p>
<p>저자들은 $a_c$가 0인 경우에는 LR Context를 선택하고 1인 경우에는 HR Detail의 출력을 선택하도록 합니다.</p>
<p>이 Scale Attention을 어떻게 활용하는지에 대해서 그림과 함께 보시면 이해가 쉬우실 겁니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/0b34fcbc-2658-4e34-915f-da6a010a47bb/image.png" alt=""></p>
<h4 id="output-of-hr-detail-crop-haty_d">Output Of HR Detail Crop ($\hat{y}_d$)</h4>
<p>LR Context Crop의 출력 $\hat{y}_c$에서 Scale $s$만큼 Upsample 한 영역을 0으로 초기화 한 합니다. (Zero Padding을 하는 과정입니다.)</p>
<p>이후에 원래 LR Detail Crop 에서 실제 $x_{d}$가 존재하는 위치와 비슷하게 새로 만든 영역에 $\hat{y}_d$를 붙여넣습니다.</p>
<p>이를 수식으로 나타내면 다음과 같습니다.</p>
<p>$$
\hat{y}&#39;<em>{d}(i,j) = \begin{cases}\hat{y}_d(i-\frac{b</em>{d,1}}{o},j-\frac{b_{d,3}}{o}), &amp; if \quad \frac{b_{d,1}}{o} \le i &lt; \frac{b_{d,2}}{o} \land \frac{b_{d,3}}{o} \le j &lt; \frac{b_{d,4}}{o} \ 0 &amp; otherwise \end{cases} 
$$</p>
<p>결과물 $\hat{y}&#39;_{d} \in \R^{\frac{sH_c}{o} \times \frac{sW_c}{o} \times c}$를 얻습니다.</p>
<p>이 결과는 Scale Attention을 이용해 LR Context Crop의 결과와 혼합되어 최종 예측 결과를 얻습니다.</p>
<h4 id="scale-attentiona_c">Scale Attention($a_c$)</h4>
<p>LR Context Crop의 출력내에서 HR Detail Crop 출력 부분에는 $a_c$값을 그대로 입력하고 나머지 부분에는 0으로 채웁니다.</p>
<p>최종 출력에서 HR Detail Crop 부분을 제외한 나머지 부분에는 LR Context Crop의 값만 존재하므로 무조건 LR Context Crop의 값이 선택되도록 0이 되어야 합니다. </p>
<p>이 과정을 수식으로 표현하면 다음과 같습니다.</p>
<p>$$
a&#39;<em>c(i,j) = \begin{cases}a_c(i,j), &amp; if \quad \frac{b</em>{d,1}}{s \cdot o} \le i &lt; \frac{b_{d,2}}{s \cdot o} \land \frac{b_{d,3}}{s \cdot o} \le j &lt; \frac{b_{d,4}}{s \cdot o} \ 0 &amp; otherwise \end{cases}
$$</p>
<p>결과물 $a&#39;_c \in \R^{\frac{H_c}{o} \times \frac{W_c}{o}}$를 얻습니다.</p>
<h4 id="fuse-for-prediction-and-train">Fuse for Prediction And Train</h4>
<p>고해상도와 저해상도의 장점을 모두 활용하기 위해서는 지금까지의 출력결과인 $\hat{y}<em>{c}$, $\hat{y}&#39;</em>{d}$, $a&#39;_{c}$를 모두 사용하여야 합니다.</p>
<p>그 과정을 수식으로 나타내면 다음과 같습니다.</p>
<p>$$
\hat{y}<em>{c,F} = \zeta((1-a&#39;</em>{c})\odot\hat{y}<em>{c},s) + \zeta(a&#39;</em>{c},s)\odot \hat{y}_{d}
$$</p>
<p>최종 출력물인  $\hat{y}_{c,F}$를 통해 Loss를 계산하여 Student Model을 학습시킵니다.</p>
<p>이 과정에서 LR, HR을 혼합해 사용하여 최적의 가중치로 업데이트 되기 때문에 LR Context 정보와 HR Detail 정보를 적절히 사용해 학습할 수 있습니다.</p>
<p>Loss 함수는 다음과 같습니다.</p>
<p>$$
L^{S}<em>{HRDA} = (1-\lambda</em>{d})L_{ce}(\hat{y}^{S}<em>{c,F},y^{S}</em>{c,HR},1) + \lambda_{d} L_{CE}(\hat{y}^{S}<em>{d},y^{S}</em>{d},1)
$$</p>
<p>$$
L^{T}<em>{HRDA} = (1-\lambda</em>{d})L_{ce}(\hat{y}^{T}<em>{c,F},p^{T}</em>{c,F},q^{T}<em>{c,F}) + \lambda</em>{d} L_{CE}(\hat{y}^{T}<em>{d},p^{T}</em>{d},q^{T}_{d})
$$</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/e9e105ea-cd90-4e0a-96c5-8f867336d65a/image.png" alt=""></p>
<p>$\lambda_{d}$는 하이퍼 파라미터로 값이 높을 경우 HR Detail Crop에 더 집중하여  학습하고 낮을 수록 HR Detail과 LR Context를 혼합한 결과에 더 집중하여 학습합니다.</p>
<p>본 논문에서는 0.1을 선택하여 학습을 진행합니다.</p>
<h3 id="43-pseudo-label-generation-with-overlapping-sliding-window">4.3 Pseudo-Label Generation With Overlapping Sliding Window</h3>
<p>그렇다면 UDA과정에서 제일 중요한 방법인 Pseudo Label은 어떻게 만들고 추론과정에서 전체 이미지는 추론할까?</p>
<h4 id="pseudo-label">Pseudo Label</h4>
<p>HR Context Crop $x^{T}<em>{c,HR}$(LR Context로 만들기 전의 Crop 범위)을 위해서는 위해서는 Pseudo Label $p^T</em>{c,F}$가 필요합니다.</p>
<p>※ $p^T_{c,F}$는 $y^T_{c,F}$를 Class 방향으로 Argmax를 적용한 것입니다.</p>
<p>$\hat{y}^T_{c,F}$를 만드는 과정은 Source Domain과 비슷하며 다음과 같습니다.</p>
<p>$$
\hat{y}^T_{c,F} = \zeta((1-a^{T}<em>{c})\odot\hat{y}^{T}</em>{c},s) + \zeta(a^{T}<em>{c},s)\odot \hat{y}^T</em>{c,HR}
$$</p>
<p>여기서 Scale Attention $a^T_c$와 $\hat{y}^{T}_{c,HR}$을 중요하게 보셔야합니다.</p>
<p>이 부분에서는 $\hat{y}^T_{c,HR}$를 만들기 위해서 HR Detail Crop($x_d$)가 아닌 HR Context Crop $x^{T}_{c,HR}$이 필요합니다.</p>
<p>매우 큰 고해상도의 입력은 학습 과정에서는  메모리 제한으로 인해서 문제가 되지만 추론 과정에서는 문제가 되지 않습니다.</p>
<p>그러나 ViT 기반의 DAFormer을 사용하기 때문에 패치의 Position에 대한 영향이 내재적으로 존재하게 됩니다.</p>
<p>이 때문에 동일한 해상도를 입력으로 넣는 것이 성능이 좋게 나옵니다.</p>
<p>그래서 고해상도의 $\hat{y}^T_{c,HR}$를 얻기 위해서 Overlapping Sliding Window 방식을 통해 추론을 합니다.</p>
<p>그 방법은 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/ff25d361-7f63-4dd7-a370-10b169a20abf/image.png" alt=""></p>
<p>Context Crop 내에서 $H_d \times W_d$ 크기의 Window 를 $\frac{H_d}{2} \times \frac{W_d}{2}$의 Stride로 옮겨가며 예측하는 방법입니다.</p>
<p>이후의 결과에서 겹치는 부분들은 평균을 내며 이는 강건함을 증가시킨다고 합니다.</p>
<p>또한 병렬로 처리를 하기에 GPU 연산에 최적화 되었다고 할 수 있습니다.</p>
<p>아래는 제가 Samsung AI Challenge(Domain Adaptation)에서 사용한 방법과 비슷하다고 생각하여 추가해봅니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/97cbaf8c-ae29-438f-bca1-ee244d85597b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/6d8f3974-92d0-4200-aeed-fdbc7128e92a/image.png" alt=""></p>
<p>추가적으로 이 과정에서 $\hat{y}^T_{c,HR}$ 뿐만이 아니라 $a^T_c$ 또한 동일한 방법으로 전체 부분에 대한 Scale Attention을 얻으셔야 합니다.</p>
<h4 id="inference-entire-image">Inference Entire Image</h4>
<p>위의 방법은 Pseudo Label을 생성하는 과정입니다.</p>
<p>반대로 전체 이미지를 추론하는 과정은 다음과 같습니다.</p>
<p>전체 이미지 $x_{HR}$을 예측함으로써 전체이미지에 대한 출력 $\hat{y}_{F,HR}$가 결과로 나와야 합니다.</p>
<p>그러나 위에서와 같은 이유로 $\hat{y}_{F,HR}$를 만들기 위해서 Overlapping Sliding Window 방식을 사용해야 합니다.</p>
<p>여기서는 Window Size를 $sH_c \times sW_c$를 사용하며 Stride는 $\frac{sH_c}{2} \times \frac{sW_c}{2}$로 설정합니다.</p>
<p>그리고 각 Window에 대해서는 Pseudo Label을 생성하는 과정과 같은 방식으로 추론을 시작합니다.</p>
<h2 id="5-experiments">5. Experiments</h2>
<p>우선 본 논문에서 사용한 주요 실험 설정은 다음과 같습니다.</p>
<p><strong>데이터셋 해상도</strong></p>
<ul>
<li><p>CityScapes : $2048 \times 1024$ -&gt; $2048 \times 1024$ ( 기존 UDA 방법론 = $1024 \times 512$)</p>
</li>
<li><p>GTA : $1914 \times 1052$ -&gt; $2560\times 1520$ ( 기존 UDA 방법론 = $1280 \times 720$)</p>
</li>
<li><p>Synthia : $1280 \times 760$ -&gt; $2560 \times 1520$</p>
</li>
</ul>
<p><strong>UDA 하이퍼 파라미터</strong></p>
<ul>
<li><p>Learning Rate : Encoder(6e-5), Decoder(6e-4)</p>
</li>
<li><p>Batch Size : 2</p>
</li>
<li><p>Warmup</p>
</li>
<li><p>$\lambda_{st}$ : 1</p>
</li>
<li><p>$\alpha$ : 0.999</p>
</li>
<li><p>DACS 데이터 증강 사용</p>
</li>
</ul>
<p><strong>HRDA 하이퍼 파라미터</strong></p>
<ul>
<li><p>$h_c, w_c, h_d, w_d$ : 512</p>
</li>
<li><p>$s$ : 2</p>
</li>
<li><p>$\lambda_d$ : 0.1</p>
</li>
</ul>
<h3 id="51-result">5.1 Result</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/b12e25ac-7fe2-455a-8105-86cec1bc91df/image.PNG" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/02e617e6-2c69-43ae-8b84-910a4f3e62d2/image.PNG" alt=""></p>
<p>기존의 UDA와는 달리 큰 물체들에서도 좋은 성능을 보이며 작은 물체에 대해서는 빠짐없이 모두 좋은 성능을 내는 것을 확인할 수 있습니다.</p>
<h3 id="52-relative-crop-size-h--frach_ts">5.2 Relative Crop Size ($H / \frac{H_T}{s}$)</h3>
<blockquote>
<p>제가 생각하기에 이 부분이 이해하기 힘들었던 거 같았습니다. 원래는 성능 부분은 따로 리뷰에 안넣었는데 이거 때문에 이번 논문 리뷰에 성능 부분에 대한 설명도 넣어봤습니다.</p>
</blockquote>
<p>수식을 다음과 같이 변경해서 보면 직관적으로 이해하기 쉽습니다.</p>
<p>$$
\frac{sH}{H_T}
$$</p>
<p>$H_T$는 Target 이미지의 높이라고 보시면 될 거 같습니다.</p>
<p>$sH$는 Crop한 높이라고 보시면 됩니다.</p>
<p>$H$는 입력으로 사용한 Target의 높이 입니다.</p>
<p>아래 Fig. 4.와 5.를 보면 $LR, HR$에 관한 내용이 있습니다.</p>
<p>$LR$의 경우 $s=2$이므로 Crop 한 높이는 $2H$입니다.</p>
<p>즉, 입력으로 사용한 Target의 높이는 Crop한 높이의 절반이 되며 이는 LR Context Crop을 의미합니다.</p>
<p>반대로 $HR$의 경우 $s=1$이므로 Crop한 높이는 $H$입니다.</p>
<p>즉, Crop한 높이와 입력으로 사용한 Target의 높이가 동일하므로 HR Detail Crop을 의미합니다.</p>
<p>※ 실제로 GTA 5 -&gt; Cityscapes에서 HRDA의 성능은 73.8이며 이는 반올림 하면 74입니다. Fig. 5.에서 마지막 행을 보면 mIoU가 동일하며 이 때 $LR_{1.0}+HR_{0.5}$를 통해 비율을 계산하면 </p>
<p>근거는 다음과 같습니다.</p>
<ol>
<li><p>Table 1. 마지막 행과 Fig.5. 4행을 보면 GTA 5 -&gt; Cityscapes에서 클래스별 IoU가 동일함</p>
</li>
<li><p>CityScapes의 원본 이미지의 높이($H_T$)는 1024 인데 입력으로 들어가는 $H_{c,T},H_{d,T}$는 모두 512임</p>
</li>
<li><p>$LR_{1.0},HR_{0.5}$을 계산해보면 각각의 H는 모두 512가 나옴</p>
</li>
</ol>
<blockquote>
<p>제가 Relative Crop Size에 대한 내용에서 놓쳐서 쉽게 이해를 못한 것인지 아무리 세세한 설명을 찾아봐도 안나오더라구요... 일단 저렇게 억지 유도를 했습니다... 아시는 분 계시면 댓글 부탁드립니다...</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/9bc34a22-f84d-4ade-a15b-8d1f68314409/image.PNG" alt=""></p>
<p>위의 내용을 토대로 그래프를 이해하면 다음과 같습니다.</p>
<ul>
<li><p>Crop Size가 증가하면 증가할 수록 성능은 향상됨.</p>
</li>
<li><p>Crop 비율이 동일하면 HR Detail Crop의 성능이 더 높음.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/03a8b141-81b0-4733-aca4-0cd72672f25b/image.PNG" alt=""></p>
<p>위의 표는 클래스 별로 IoU를 계산한 표 입니다.</p>
<ul>
<li><p>행 1과 2 : LR Context Crop을 사용하는 것이 HR Detail Crop보다 도로나 보도블럭 같이 큰 부분에서는 더 좋은 성능을 냄.</p>
</li>
<li><p>행 1과 3 : LR Context Crop의 크기가 커질 수록 더 많은 Context로 인해 성능이 향상됨.</p>
</li>
</ul>
<p>※시간이 되신다면 논문의 5.5절 한번 보시면서 이해하시면 더 좋을 거 같습니다.</p>
<h2 id="6-마무리">6. 마무리</h2>
<p>고해상도 이미지와 저해상도 이미지를 적절히 혼합하여 학습을 했다는 점이 신기했습니다.</p>
<p>특히, 실제로 고해상도 이미지와 저해상도 이미지에서 본 논문에서 말한 장단점이 있다는 것을 처음 알았습니다.</p>
<p>하나 아쉬운 점은 Scale Attention을 진행하지 않고 Context 따로 Detail 따로 Loss를 줘서 학습하는 방법에 대한 실험이 있었다면 더 좋았을 거 같습니다.</p>
<p>이 논문은 Samsung AI Challenge 할 때 알았으면 더 좋았을 거 같습니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[DAFormer 논문 리뷰]]></title>
            <link>https://velog.io/@pre_f_86/DAFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@pre_f_86/DAFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Fri, 24 Nov 2023 12:46:17 GMT</pubDate>
            <description><![CDATA[<p>본 페이지에서는 <a href="https://arxiv.org/abs/2111.14887">DAFormer: Improving Network Architectures and Training Strategies for Domain-Adaptive Semantic Segmentation</a> 논문에 대해서 말하고자 합니다.</p>
<p>DAFormer 학습 방법에는 DACS의 학습 방법도 사용하였기 때문에 한 번 보시는 걸 추천드립니다.</p>
<ul>
<li><a href="https://velog.io/@pre_f_86/DACS-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DACS 논문 리뷰</a></li>
</ul>
<hr>
<h2 id="1-intro">1. Intro</h2>
<p>Semantic Segmentation 분야에서 라벨링에서 발생하는 비용이 너무 커 많은 Unsupervised Domain Adaptation(UDA) 방법론들이 등장했습니다.</p>
<p>그러나 본 논문 이전의 UDA 방법론들에서 사용하는 모델은 CNN기반의 DeepLab V2와 같이 오래된 모델을 사용하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/fc4c3d49-c010-477c-a387-469185d18e1e/image.png" alt=""></p>
<p>그나마 좋은 성능을 내던 DeepLabV2+ResNet101 구조는 지도 학습에서 mIoU가 65정도 되지만 최근 모델 구조들의 mIoU는 거의 85 이상의 성능을 냅니다.</p>
<p>이러한 성능 차이는 UDA 성능을 제대로 발휘하지 못하게 하며 Benchmark Guide에서도 문제가 될 것이라고 합니다.</p>
<p>그래서 본 논문은 비교적 최신구조의 Transformer Encoder를 사용하고 Decoder의 구조를 UDA에 맞게 변경합니다.</p>
<p>추가적으로 간단하지만 중요한 세가지 방법으로 UDA 과정에서 Source Domain에 과적합 되는 것을 방지할 수 있습니다.</p>
<p>간단하게 요약하자면 다음과 같습니다.</p>
<ol>
<li><p>기존의 논문들과는 다른 최신의 모델 구조 사용</p>
</li>
<li><p>Rare Class Sampling을 통해 UDA 과정에서 흔한 클래스에 편향되지 않도록 함</p>
</li>
<li><p>Thing-Class ImageNet Feature Distance 이라는 Loss함수 추가</p>
</li>
<li><p>ImageNet으로 사전 학습된 모델을 전이학습을 할 때 Learning Rate에 Warm up 스케쥴링 사용</p>
</li>
</ol>
<h2 id="2-term">2. Term</h2>
<p>본 논문을 이해하시기 전에 용어들이 어떤 것을 의미하는지 확인하시면 편하실 겁니다.</p>
<ul>
<li><p>$N_s$ : Source 데이터들의 개수 입니다.</p>
</li>
<li><p>$N_t$ : Target 데이터들의 개수 입니다.</p>
</li>
<li><p>$X_S =     \left{{x^{(i)}<em>S}\right}^{N_S}</em>{i=1}$ : Source 이미지들을 의미하며  $x^{(i)}_S$ 은 $i$번째 Source 이미지 입니다.</p>
</li>
<li><p>$Y_S =     \left{{y^{(i)}<em>S}\right}^{N_S}</em>{i=1}$ : Source 라벨들을 의미하며  $y^{(i)}_S$ 은 $i$번째 Source 라벨 입니다.</p>
</li>
<li><p>$X_T =     \left{{x^{(i)}<em>T}\right}^{N_T}</em>{i=1}$ : Target 이미지들을 의미하며  $x^{(i)}_T$ 은 $i$번째 Target 이미지 입니다.</p>
</li>
<li><p>$\theta$ : Student Model의 가중치를 의미합니다.</p>
</li>
<li><p>$\phi$ : Teacher Model의 가중치를 의미합니다.</p>
</li>
<li><p>$g_{\theta}$ : Student Model을 의미합니다.</p>
</li>
<li><p>$h_{\phi}$ : Teacher Model을 의미합니다.</p>
</li>
</ul>
<h2 id="3-self-training-st-for-uda">3 Self-Training (ST) for UDA</h2>
<h3 id="student-model-weight-update">Student Model Weight Update</h3>
<p>일반적으로 Source Domain에서 $g_{\theta}$를 학습시키기 위해서는 Categorical Cross Entropy를 사용합니다.</p>
<p>그 식은 다음과 같습니다.</p>
<p>$$
L^{(i)}<em>{S} = - \sum^{H \times W}</em>{j=1} \sum^{C}<em>{c=1} y^{(i,j,c)}</em>{S} \log g_{\theta}(x^{(i)}_{S})^{(j,c)}
$$</p>
<p>이렇게 학습을 진행한 모델은 Target Domain에서는 좋은 성능을 내지 못하는 경우가 많습니다.</p>
<p>그 이유는 Domain Gap 때문이라고 할 수 있습니다.</p>
<p>그러한 Domain Gap을 해결하기 위해서 그동안은 다음과 같은 방법을 사용했습니다.</p>
<ul>
<li><p>Adversarial Training</p>
</li>
<li><p>Self Training</p>
</li>
</ul>
<p>그러나 Adversarial Training 기법은 안정적으로 학습하지 못하고 당시에는 Self Training 기법이 더 좋은 성능을 내기 때문에 본 논문에서는 Self Training을 사용합니다.</p>
<p>Self Training에서는 Teacher Model $h_{\phi}$를 에 Target 이미지를 입력으로 넣어 클래스에 대한 확률맵을 가진 Pseudo Label을 생성합니다.</p>
<p>이 Pseudo Label을 다음과 같은 Loss 함수에 사용합니다.</p>
<p>$$
L^{(i)}<em>{T} = - \sum^{H \times W}</em>{j=1} \sum^{C}<em>{c=1} q^{(i)}_Tp^{(i,j,c)}</em>{T} \log g_{\theta}(x^{(i)}_{T})^{(j,c)}
$$</p>
<p>이때 $p^{(i,j,c)}_{T}$와 $q^{(i)}_T$는 다음과 같습니다.( [ ] 는 Iverson bracket 입니다. )</p>
<p>$$
p^{(i,j,c)}<em>{T} = [c=\underset{c&#39;}{argmax}(h</em>{\phi}(x^{(i)}_T)^{(j,c&#39;)})]
$$</p>
<p>$$
q^{(i)}<em>T = \frac{\sum^{H}</em>{j}{\sum^{W}<em>{k}{[max</em>{c&#39;}(h_{\phi}(x^{(i)}_T)^{(j,c&#39;)} &gt; \tau)}}}{H \times W}
$$</p>
<p>위 수식의 목적을 간단히 설명하면 다음과 같습니다.</p>
<ul>
<li><p>$p^{(i,j,c)}_{T}$ : 각 픽셀에 대해서 클래스를 기준으로 Argmax를 적용하며 가장 확률이 높은 클래스로 반환하는 것</p>
</li>
<li><p>$q^{(i)}_T$ : 각 픽셀의 클래스 확률에 대해서 가장 높은 확률값이 임계값 $\tau$를 넘는 픽셀의 비율</p>
</li>
</ul>
<p>즉, 모델의 출력에 대해서 임계값을 넘는 비율만큼 Loss를 전파한다는 의미입니다.</p>
<h3 id="teacher-model-weight-update">Teacher Model Weight Update</h3>
<p>Teacher Model $h_{\phi}$의 가중치는 Student Model $g_{\theta}$와는 다르게 학습을 통해 가중치가 업데이트 되지 않습니다.</p>
<p>본 논문에서 Teacher Model $h_{\phi}$의 가중치는 지수이동평균(Exponential Moving Average)[EMA] 을 통해 가중치를 업데이트합니다.</p>
<p>그 식은 다음과 같습니다.</p>
<p>$$
\phi_{t+1} = \alpha\phi_t + (1-\alpha)\theta_t
$$</p>
<p>$t$시점의 $\alpha$는 다음과 같이 정해집니다.</p>
<p>$$
\alpha = max(\alpha_{max},1 - \frac{1}{t})
$$</p>
<p>특히 Self Training의 경우 다음과 같은 상황에서 학습이 잘 된다고 합니다.</p>
<ul>
<li><p>Student Model $g_{\theta}$를 학습시킬 때에는 Target 이미지에 데이터 증강을 적용시키는 것이 좋음</p>
</li>
<li><p>Teacher Model에서 Pseudo Label을 생성할 때에는 데이터 증강을 적용하지 않은 Target 이미지를 입력으로 넣는 것이 좋음</p>
</li>
</ul>
<p>또한 DACS 논문에 나온 방법을 따랐으며 이에 Color Jitter, Gaussian blur와 ClassMix 기법을 사용하였습니다.</p>
<h2 id="4-daformer-network-architecture">4. DAFormer Network Architecture</h2>
<p>이전의 UDA 방법론들은 CNN기반의 모델 중 오래되었으며 간단한 DeepLabV2를 사용하였습니다.</p>
<p>CNN의 특성상 Transformer에 비해 새로운 도메인을 만나게 되는 경우 오히려 성능이 급감하는 모습을 보입니다.</p>
<p>즉, CNN은 새로운 도메인에 강건하지 못함을 의미합니다.</p>
<p>Transformer가 왜 CNN보다 강건한지에 대해서 논문의 저자들은 다음과 같이 말합니다.</p>
<blockquote>
<p>CNN과 Transformer의 Self-Attention 모두 가중합 방식을 사용하는 것은 동일하지만 CNN은 학습된 가중치를 사용해 가중합되어 정적으로 특징을 추출하는 반면에 Transformer는 유사도와 선호도를 기반으로 동적으로 특징을 추출한다.</p>
</blockquote>
<p>본 논문의 저자들은 UDA 기법은 강건한 모델일 수록 더 학습이 잘 된다고 생각하였고 이에 따라 CNN 대신 Transformer구조를 사용하였습니다.</p>
<p>DAFormer의 구조는 SegFormer의 구조를 기반으로 하는 모델입니다.</p>
<h3 id="encoder">Encoder</h3>
<p>본 논문에서 Encoder로는 SegFormer 논문에 나온 Mix-Transformer(MiT)를 사용하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f332212b-36fd-4e2d-b5da-ee81cfebe3b7/image.PNG" alt=""></p>
<ul>
<li><p>픽셀 단위로 정보를 알아내야하는 Semantic Segmentation에 맞게 고안된 구조</p>
</li>
<li><p>Attention 과정에서 Sequence Reduction을 통해 연산량 감소로 인해 고해상도에서도 괜찮은 성능</p>
</li>
<li><p>여러 층의 특징맵을 만들기 위해 Downsampling 기능을 Patch Merging을 통해 구현하였고 이로 인해 Local Continuity를 유지할 수 있음 (각각의 특징맵은 $F_i \in \R^{\frac{H}{2^{i+1}} \times \frac{W}{2^{i+1}} \times C_i}$임)</p>
</li>
</ul>
<p>위의 장점들로 하여 MiT를 Encoder로 사용하였습니다.</p>
<h3 id="decoder">Decoder</h3>
<p>기존의 SegFormer의 Decoder는 Local Information만을 사용하여 Context 정보가 부족해지게 되었고 이는 강건함을 해치게 되어 UDA에 악영향을 끼치게 됩니다.</p>
<p>이를 해결하기 위해 DAFormer에서는 SegFormer의 Decoder 구조를 수정합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/76ba3700-6e7b-4581-be87-6ef9ebe52ef8/image.png" alt=""></p>
<p>Fig2의 B가 DAFormer의 구조 입니다.</p>
<p>Stack 레이어에서의 기본적인 구조는 SegFormer와 동일하지만 SegFormer보다 적은 수의 채널($C_e$)을 사용합니다.</p>
<p>이후 Context Aware Feature Fusion 레이어에서는 ASPP와 비슷하게 서로 다른 Dilation Rates로 $3 \times 3$ Depthwise-Seprable Conv를 적용하여 Concatenate한 뒤 $1 \times 1$ Conv를 적용합니다. </p>
<p>DAFormer와는 다르게 Stack 레이어에서 채널수를 줄였고 Context Aware Feature Fusion에서 Depthwise-Separable Conv를 사용하기 때문에 파라미터의 수를 감소 시켰습니다.</p>
<p>이로 인해 Source Domain으로 과적합될 가능성을 줄였다고 볼 수 있습니다.</p>
<h3 id="performance">Performance</h3>
<h4 id="모델에-따른-성능표">모델에 따른 성능표</h4>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d4b4c567-eae8-47d4-9585-b7dedd857f6d/image.PNG" alt=""></p>
<h4 id="encoder에-decoder에-따른-성능표">Encoder에 Decoder에 따른 성능표</h4>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/49f59e32-84b6-4b28-a8e8-a682e4dd760f/image.PNG" alt=""></p>
<h4 id="encoder의-크기에-따른-성능표">Encoder의 크기에 따른 성능표</h4>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f650042c-09c5-45ef-ae73-d47d687f6695/image.PNG" alt=""></p>
<h2 id="5-training-strategies-for-uda">5. Training Strategies for UDA</h2>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/cd68682f-13f2-4c04-8ea6-ae8ea7eba886/image.png" alt=""></p>
<p>UDA에서 가장 큰 문제점은 Source Domain에 과적합되는 것입니다.</p>
<p>이 문제를 해결하기 위해 학습 방법에 대해서 여러 실험을 하였고 성능을 크게 올렸다고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/83e45610-0392-4dc0-acbd-cc0a259a1898/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/572bec9b-a468-4f3e-9d87-026eae569fe9/image.png" alt=""></p>
<h3 id="rare-class-sampling-rcs">Rare Class Sampling (RCS)</h3>
<p>본 논문의 저자들은 흔하지 않은 클래스들에 대한 UDA 성능이 매 학습에 따라 크게 달라지는 것을 확인했다고 합니다.</p>
<p>이 이유는 Random Seed에 따라 흔하지 않은 클래스 속한 이미지가 뽑히는 순서가 다르기 때문입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/08ec5f3c-6db2-4e7a-add9-133e1970f5fc/image.png" alt=""></p>
<p>학습 초기에 흔한 클래스가 자주 뽑히고 흔하지 않은 클래스가 뒤에 뽑히게 된다고 가정해봅시다.</p>
<p>모델은 초기에 흔한 클래스를 더 자주 배우게 되어 가중치가 흔한 클래스를 잘 예측하도록 편향되게 됩니다.</p>
<p>이 때문에 이후 흔하지 않은 클래스에 대해 배우려고 할 때 새로 배우기가 어렵습니다.</p>
<p>이는 Self Training에서 더 치명적입니다.</p>
<p>흔하지 않은 클래스에 대해 제대로 배우지 않은 Teacher Model이 생성한 Label로 학습을 진행하게 되면 결국 Student Model은 흔하지 않은 클래스에 대해서 아예 학습하지 못할 수도 있습니다.</p>
<p>이를 해결하기 위해서 흔하지 않은 클래스를 더 자주 뽑도록 합니다.</p>
<p>우선 현재 Source 데이터셋에 있는 라벨에 대해 특정 클래스에 대한 픽셀의 비율을 다음과 같이 계산합니다.</p>
<p>$$
f_c = \frac{\sum^{N_{S}}<em>{i=1}\sum^{H \times W}</em>{j=1}[y^{(i,j,c)}<em>{S}]}{N</em>{S} \cdot H \cdot W}
$$</p>
<p>비율 $f_c$가 낮을 수록 데이터셋에서 클래스 $c$의 비율이 낮다는 것입니다.</p>
<p>이후 Sampling 확률 $P(c)$에 대해서 다음과 같이 계산합니다.</p>
<p>$$
P(c) = \frac{e^{(1-f_{c})/T}}{\sum^{C}<em>{c&#39;=1}e^{(1-f</em>{c&#39;})/T}}
$$</p>
<p>$1-f_{c}$로 인해 비율이 낮은 클래스가 더 높은 $P(c)$를 갖게 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/e6ebec27-4967-4842-81d0-82369a9015c0/image.png" alt=""></p>
<p>이때 $T$는 확률분포 $P(c)$의 부드러움을 결정합니다.</p>
<p>$T$가 크면 $P(c)$의 분포가 균일 분포와 비슷해집니다.</p>
<p>반대로 $T$가 작으면 $P(c)$ 의 분포는 흔하지 않은 클래스(작은 $f_c$)를 더 자주 뽑도록 합니다.</p>
<p>대게 흔하지 않은 클래스(사람, 바이크 등)은 흔한 클래스(도로, 나무, 하늘 등)와 같은 이미지 내에 있을 확률이 높습니다. </p>
<p>이 때문에 흔하지 않은 클래스를 포함한 이미지를 주로 뽑는다고 해도 Sampling 비율은 비슷한 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c01d7ae6-9e27-486b-a3d0-d24f3ddf44ca/image.png" alt=""></p>
<p>이 Sampling 과정은 다음과 같습니다.</p>
<h4 id="전처리-과정">전처리 과정</h4>
<ol>
<li><p>모든 라벨에 대해서 어떤 라벨에 어떤 클래스가 얼마나 있는지를 정리하여 파일이나 메모리에 저장</p>
</li>
<li><p>각 클래스에 대해서 $f_c$를 미리 계산하여 파일이나 메모리에 저장</p>
</li>
<li><p>클래스 별로 해당 클래스를 포함한 라벨에 대한 정보를 파일이나 메모리에 저장</p>
</li>
</ol>
<h4 id="sampling-과정">Sampling 과정</h4>
<ol>
<li><p>미리 구해둔 $f_c$를 통해 분포 $P(c)$를 계산</p>
</li>
<li><p>분포 $P(c)$를 기반으로 확률적으로 클래스를 선택 ( $c$ ~ $P$ )</p>
</li>
<li><p>선택한 클래스가 들어있는 라벨들 중에 하나를 균등분포를 따라 선택함과 동시에 라벨에 맞는 이미지를 같이 불러옴 ( $x_{S}$ ~ uniform($X_{S,c}$))</p>
</li>
</ol>
<p>아래는 하이퍼 파라미터 $T$에 따른 성능차이 입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/07778bc7-8e80-4591-9ad1-ebc427a0d242/image.png" alt=""></p>
<h3 id="thing-class-imagenet-feature-distance-fd">Thing-Class ImageNet Feature Distance (FD)</h3>
<p>ImageNet의 데이터셋은 실제로 존재하는 사물들의 정보를 잘 담고 있다고 합니다.</p>
<p>그렇기 때문에 이런 ImageNet을 통해 사전학습을 한 모델은 고수준의 유의미한 의미론적 특징을 추출해낼 수 있습니다.</p>
<p>이런 의미론적 특징을 통해 UDA에서 구분하기 힘든 버스나 기차의 특징도 쉽게 구분할 수 있다고 합니다.</p>
<p>실제로 저자들은 DAFormer를 학습하면서 초기에는 버스나 기차를 잘 분할(Segment)하는 것을 확인할 수 있었지만 나중에는 분할해내지 못하는 것을 확인한다고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/4bf6a9f4-139e-4ea2-b7b7-ffdff64879eb/image.png" alt=""></p>
<p>이 이유는 학습 과정에서 ImageNet의 정보를 잃어간다고 판단하였고 또한 Source 데이터에 과적합 되어 간다고 판단하였습니다.</p>
<p>그래서 ImageNet의 정보를 잃지 않게 하기 위해서 모델의 Bottleneck Feature($F$)을 활용하여 Feature Distance(FD)를 구합니다.</p>
<p>Feature Distance의 식은 다음과 같습니다.</p>
<p>※ $F_{ImageNet}$은 ImageNet으로 사전 학습한 모델의 Bottleneck Feature입니다.</p>
<p>$$
d^{i,j} = || F_{ImageNet}(X^{(i)}<em>{S})^{(j)} - F</em>{\theta}(x^{(i)}<em>{S})^{(j)}  ||</em>{2}
$$</p>
<p>하지만 ImageNet은 도로, 하늘과 같은 배경을 포함한 Stuff-Class가 아닌 주로  차 또는 얼룩말과 같은 Thing-class에 대해서 주로 학습합니다.</p>
<p>그렇기 떄문에 Feature Distance Loss를 Thing-class에 대해서만 학습합니다.</p>
<p>그러기 위해서 $C_{things}$와 이진 Mask $M_{things}$를 활용합니다.</p>
<p>※ $C_{things}$는 Thing-Class의 집합입니다.</p>
<blockquote>
<p>클래스 정보가 0 : 하늘, 1 : 도로, 2 : 차 인 경우에 2번 클래스인 차만 Thing-Class이기 때문에 $C_{things}$는 다음과 같습니다.
$$
C_{things} = [2]
$$
Thing Class Feature Distance Loss 함수는 다음과 같습니다.</p>
</blockquote>
<p>$$
L^{(i)}<em>{FD} = \frac{\sum^{H</em>{F} \times W_{F}}<em>{j=1}d^{(i,j)} \cdot M^{(i,j)}</em>{things}}{\sum_{j} M^{(i,j)}_{things}}
$$</p>
<p>이 때 $M^{(i,j)}_{things}$ 은 다음과 같이 정의합니다.</p>
<p>$$
M^{(i,j)}<em>{things} = \sum^{C}</em>{c&#39;=1}y^{i,j,c&#39;}<em>{S,small} \cdot [ c&#39; \in C</em>{things} ]
$$</p>
<p>$y^{(i)}<em>{S,small}$ 은 라벨 $y^{(i)}</em>{S}$크기를 Average Pooling으로 크기를 $\frac{H}{H_{F}} \times \frac{W}{W_{F}}$로 줄인 것입니다.</p>
<p>이후 임계값 $r$를 넘으면 1 아니면 0으로 전환하여 이진 Mask로 바꾸어 줍니다.</p>
<p>식은 다음과 같습니다.</p>
<p>$$
y^{c}<em>{S,small} = [AvgPool(y^{c}</em>{S},H/H_{F},W/W_{F})&gt;r]
$$</p>
<blockquote>
<p>저는 이 부분을 이해할 때 $y^{(i)}_{S}$가 Bottleneck Feature Map을 의미하는 것으로 잘못 이해하고 구현하려 하다가 어떤 채널이 어떤 클래스와 연관된 거지 하며 한동안 이해하지 못하고 있었습니다...</p>
</blockquote>
<p>간단하게 과정을 설명하면 다음과 같습니다.</p>
<ol>
<li><p>Bottleneck Feature Map과 $y^{(i)}<em>{S}$의 해상도를 통해 $H</em>{F},W_{F}$를 구합니다.</p>
<p> 이후에 $L^{(i)}_{FD}$를 구할 때 해상도를 맞춰주기 위함입니다.</p>
</li>
<li><p>$y^{(i)}_{S}$에 AvgPool을 적용하여 해상도를 줄여줍니다. </p>
<p> Shape($H \times W \times C$ -&gt; $\frac{H}{H_{F}} \times \frac{W}{W_{F}} \times C$)</p>
<p> 패치단위로 크기를 줄이게 되므로 AvgPool 을 적용한 결과의 한 픽셀은 $H_{F} \times W_{F}$ 범위 내의 클래스 비율의 정보를 담고 있습니다.</p>
</li>
<li><p>임계값 $r$을 기준으로 1과 0으로 구분하여 이진화 하여 $y^{(i)}_{S,small}$을 완성합니다.</p>
<p> Shape($\frac{H}{H_{F}} \times \frac{W}{W_{F}} \times C$)</p>
<p> 해당 패치 내부에 특정 클래스 개수의 비율이 $r$을 초과하면 1이고 아니면 0이 됩니다. </p>
</li>
<li><p>$y^{(i)}<em>{S,small}$의 각 클래스 채널에 대해서 해당 클래스가 Thing-class에 속하는 경우에 해당 채널에는 1 나머지에는 0을 곱해주고 채널 축으로 전부 더해주어 $M^{(i,j)}</em>{things}$을 만들어줍니다.</p>
<p>Shape($\frac{H}{H_{F}} \times \frac{W}{W_{F}} \times C$ -&gt; $\frac{H}{H_{F}} \times \frac{W}{W_{F}}$)</p>
<p>$M^{(i,j)}_{things}$의 각 픽셀의 의미는 다음과 같습니다.</p>
<p>해당 패치내에 존재하는 클래스들의 비율이 $r$보다 큰 클래스들 중에서 Thing-class에 속하는 클래스의 갯수를 의미합니다.</p>
</li>
<li><p>$d^{(i,j)}$를 구하기 위해 두 Bottleneck Feature의 차이를 구하고 채널 축으로 2 Norm을 구합니다.</p>
<p> Shape($\frac{H}{H_{F}} \times \frac{W}{W_{F}} \times C$ -&gt; $\frac{H}{H_{F}} \times \frac{W}{W_{F}}$)</p>
</li>
<li><p>$d^{(i,j)}$와 $M^{(i,j)}<em>{things}$를  이용해 $L^{(i)}</em>{FD}$를 구합니다.</p>
<p> Shape($\frac{H}{H_{F}} \times \frac{W}{W_{F}}$ -&gt; 1)</p>
<p> 결국 하나의 Scalar 값이 나오게 되어 Loss로 계산이 가능하게 됩니다.</p>
</li>
</ol>
<p>$L_{FD}$까지 사용하여 본 논문에서 사용하는 최종적인 Loss함수는 다음과 같습니다.</p>
<p>$$
L^{(i)} = L^{(i)}<em>{S} + L^{(i)}</em>{T} + \lambda_{FD}L^{(i)}_{FD}
$$</p>
<h3 id="learning-rate-warmup-for-uda">Learning Rate Warmup for UDA</h3>
<p>선형적으로 학습률을 높이는 방법은 CNN과 Transformer 두 구조에서 모두 좋은 성능을 냈습니다.</p>
<p>Warmup 스케쥴링은 학습 초기에 큰 학습률이 Gradient 분포를 왜곡하는 것을 방지하면서 모델의 일반화 능력을 키워줍니다.</p>
<p>특히 이 Warmup 스케쥴링은 작은 학습률에서 시작하기 때문에 사전학습된 이미지 넷의 특징을 최대한 유지하기 때문에 Real World Domain의 정보를 유지할 수 있어 좋은 성능을 낼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/14225e2b-58d6-47e0-ab48-d5db560b6e53/image.PNG" alt=""></p>
<p>$t$시점의 학습률은 다음과 같이 설정됩니다.
$$
lr_t = lr_{base} \cdot t/t_{warmup}
$$</p>
<h2 id="6-마무리">6. 마무리</h2>
<p>UDA 과정에서 강건함의 문제를 인식하고 Transformer 계열을 시도하면서 UDA 맞게 모델 구조를 변경함으로 모델의 연산량을 줄이며 강건함을 키웠다는 점이 하나의 돌파구가 되었다고 생각합니다.</p>
<p>특히 모델 구조 뿐만 아니라 Class-Imbalance로 인한 성능 문제와 ImageNet 특징 활용 등에서 여러 성능 분석을 진행한 모습이 인상 깊었으며 이 실험을 하는 저자들의 노력이 대단함을 볼 수 있었습니다.</p>
<p>이후 MIC라는 논문과도 연관이 있으니 이해하고 가시면 좋을 듯 합니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[DACS 논문 리뷰]]></title>
            <link>https://velog.io/@pre_f_86/DACS-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@pre_f_86/DACS-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Fri, 24 Nov 2023 12:32:47 GMT</pubDate>
            <description><![CDATA[<p>본 페이지에서는 <a href="https://arxiv.org/abs/2007.08702">DACS: Domain Adaptation via Cross-domain Mixed Sampling</a> 논문에 대해서 말하고자 합니다.</p>
<hr>
<h2 id="1-intro">1. Intro</h2>
<p>본 논문은 Semantic Segmentation 분야의 Unsupervised Domain Adaptation 방법론 논문입니다.</p>
<p>기존 Segmentation 모델들은 학습 데이터와 비슷한 상황에서는 매우 잘 작동합니다. (분포가 비슷한 경우를 말함)</p>
<p>그러나 학습 데이터와 다른 분포를 가진 데이터를 사용하게 되면 성능이 급격히 감소하게 됩니다.</p>
<p>가장 좋은 예시는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f3157e7c-0443-474a-a53f-5ac2923fc1e9/image.PNG" alt=""></p>
<p>첫 사진은 GTA5 데이터셋이며 두번째 사진은 CityScape 데이터셋입니다.</p>
<p>사람이 보기에는 두 데이터셋의 차이가 그렇게 없지만 둘 중 하나의 데이터셋으로 학습한 모델은 다른 데이터셋에서는 성능이 급감하는 모습을 보입니다.</p>
<p>그 이유는 두 데이터의 분포 차이 때문입니다.</p>
<p>Domain Adaptation은 이런 도메인 사이의 차이를 줄이고자 하는 방법론입니다.</p>
<p>목표로 하는 도메인(Target Domain)의 라벨이 항상 존재하면 좋지만 실제로는 그렇지 않습니다.</p>
<p>특히 Segmentation 분야의 경우 새로운 라벨을 만드는데 시간과 비용이 크게 발생하기 때문에 더더욱 라벨이 존재하기 어렵습니다.</p>
<p>이러한 문제를 해결하기 위해서 Unsupervised Domain Adaptation(UDA)이 등장하게 되었습니다.</p>
<p>본 논문은 UDA를 조금 더 효율적으로 수행하도록 새로운 Mixing 기법을 소개합니다.</p>
<h2 id="2-unsupervised-domain-adaptationuda">2. Unsupervised Domain Adaptation(UDA)</h2>
<p>UDA는 Source Domain의 Label은 존재하지만 Target Domain에는 Label이 존재하지 않은 경우에 Domain Adaptation을 진행하는 것입니다.</p>
<p>UDA 기법은 주로 Pseudo Label을 사용하는 Semi-Supervised Learning 혹은 Self-Training 기법을 통해 진행됩니다.</p>
<p>본 논문 또한 Pseudo Label을 통해 UDA를 진행합니다.</p>
<h3 id="21-pseudo-label">2.1 Pseudo Label</h3>
<p>Pseudo Label은 잘 학습된 모델의 출력을 라벨로 사용하는 것입니다.</p>
<p>기존의 사람이 라벨링하는 과정은 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/728169e4-183f-4c30-9545-629f58b8015f/image.png" alt=""></p>
<p>사람이 해당 이미지를 보고 손수 튤립이라고 판단하여 튤립에 대한 클래스의 정보를 저장합니다.</p>
<p>Pseudo Label은 다음과 같습니다. </p>
<p>※ Segmentation은 픽셀단위 Classification이기 때문에 참고만 하시면 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/7ed1c58e-687d-47ec-89b9-10e6abe7c48c/image.png" alt=""></p>
<p>Pseudo Label은 모델이 예측한 클래스 확률과 그 확률에 Argmax를 적용한 클래스 정보를 말합니다. </p>
<p>이를 식으로 표현하면 다음과 같습니다.</p>
<p>$$
Prob = Confidence = f(x)
$$</p>
<p>$$
Cls = Argmax(f(x))
$$</p>
<p>$$ 
label = Cls,Prob
$$</p>
<p>본 논문에서는 이 Cls를 사용하여 Loss를 구하고 Confidence를 활용하여 Loss의 크기를 조절하여 학습합니다. </p>
<h3 id="22-semi-supervised-learngingssl">2.2 Semi-Supervised Learnging(SSL)</h3>
<p>본 논문에서는 SSL을 기반으로 UDA를 진행하게 됩니다.</p>
<p>Pseudo Label을 만드는 과정에서 생긴 클래스 벡터(확률 벡터) $Y_{Prob}$와 최종 클래스 $Y_{Cls}$를 사용합니다.</p>
<p>Pseudo Label에 대한 Loss 함수를 다음과 같이 정의합니다.</p>
<p>$$
L = \lambda \times CrossEntropy(Y_{Pred},Y_{Cls})
$$</p>
<p>※$Y_{Pred}$는 Student Model이 예측한 결과 입니다. Student Model은 이후에 설명하겠습니다.</p>
<p>이때 $\lambda$는 전체 이미지 중 Threshold를 넘는 픽셀의 수이며 다음과 같이 정의 됩니다.</p>
<p>$$
\lambda = \frac{\sum^{H}<em>{j}{\sum^{W}</em>{k}{(Y_{Prob} &gt; Threshold)}}}{H \times W}
$$</p>
<p>하나의 배치에 대해서 Loss를 계산하는 경우에는 다음과 같이 계산해야 합니다.</p>
<p>$$
L_{Batch} = \frac{\sum^{B}<em>{i}{Loss</em>{i}}}{B}
$$</p>
<p>위의 Loss를 통해 SSL의 성능이 크게 향상된 것은 맞으나 아직 문제점이 존재합니다.</p>
<ol>
<li><p>Loss의 특성상 예측하기 쉬운 이미지에 대해 편향되기 쉬움</p>
<p> Confidence가 일정 수준 이상인 이미지에 대해서만 Loss가 전파되기 때문에 일부 이미지에 편향될 가능성이 높습니다.</p>
</li>
<li><p>일부 이미지에 대해서만 학습을 하게될 수 있음</p>
<p> 1번과 비슷한 이유로 만약 편향되어 학습을 하게 되는 경우 일부 이미지에 과적합이 발생하고 이로 인해 다른 이미지에서 임계값을 넘은 픽셀이 없을 수 있어 Loss가 0이 될 가능성이 높아 오히려 성능이 감소될 수 있습니다.</p>
</li>
</ol>
<p>이를 더 잘 해결하기 위해서 본 논문에서는  Domain Adaptation via Cross domain mixed Sampling(DACS) 기법을 제안합니다.</p>
<h2 id="3-method">3 Method</h2>
<p>DACS 기법은 간단하게 Source Image와 Target Image를 섞고 Source Label과 Pseudo Label을 섞는 방식을 사용하여 성능이 향상됨을 보여줍니다.</p>
<h3 id="31-naive-mixing">3.1 Naive Mixing</h3>
<p>기존의 Mixing 기법은 Target Domain 에서 뽑힌 두 이미지에 대해서 Mix를 하였었습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/a195e874-776f-41a9-920d-30d7934bd98e/image.png" alt=""></p>
<p>성능은 향상되긴 하였지만 근본적으로 2절에서 말한 문제점은 해결하지 못합니다.</p>
<p>특히 두 이미지에 대해서 섞어서 만나게 되는 경계면이 크게 애매해지게 되어 자주 안나오는 SideWalk 클래스를 비슷한 Road로 예측하는 문제점도 발생하게 됩니다.</p>
<p>이를 Class Conflaction 이라고 합니다.</p>
<p>이런 점은 DACS 기법을 통해 해결이 가능하게 되었습니다.</p>
<h3 id="32-dacs">3.2 DACS</h3>
<p>DACS 는 기존의 Naive Mixing 방법과는 다르게 Source Domain과 Target Domain을 섞는 방식입니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/9a578558-d041-4f12-9d33-79b35c74a235/image.png" alt=""></p>
<p>DACS는 ClassMix 방식을 기반으로 하며 과정은 다음과 같습니다.</p>
<ol>
<li><p>Source Image에 존재하는 모든 클래스 중 절반의 클래스를 선택한다.</p>
</li>
<li><p>해당 클래스가 존재하는 위치의 픽셀들을 잘라내어 Target Image에 대응되는 픽셀에 붙여넣는다.</p>
</li>
<li><p>Psuedo Label에 동일하게 Source Label을 붙여넣는다.</p>
</li>
</ol>
<p>이 예시는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/a5db2299-8d92-42dd-8508-ada3a090ee63/image.png" alt=""></p>
<p>이 방법을 통해 성능이 크게 향상 됐다고 합니다.</p>
<p>이때 Pseudo Label에서 ClassMix를 적용한 부분은 Source Label에서 가져온 Label이므로 Confidence는 항상 1이 되어 Threshold를 넘게 됩니다.</p>
<p>이 때문에 예측하기 어려운 Target Image에 대해서도 $\lambda$가 0인 경우가 없으므로 적어도 조금씩은 학습에 반영되기 때문에 기존보다 편향될 확률이 적어지게 됩니다.</p>
<h4 id="resultnaive-dacs">Result(Naive, DACS)</h4>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d5fa5133-611a-4697-bfa0-5e162e90a469/image.png" alt=""></p>
<p>Naive 방법에 비해 DACS 기법은 확실히 도로와 보도블럭을 잘 구분하는 모습을 보여줍니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/f1272622-062f-413a-afb1-00423fd0b0ef/image.png" alt=""></p>
<p>수치적으로도 DACS 기법을 사용함으로써 성능이 크게 향상됨을 확인할 수 있습니다.</p>
<h3 id="33-loss">3.3 Loss</h3>
<p>최소화해야할 Loss는 다음과 같습니다.</p>
<p>$$
Loss = CELoss(f(X_s),Y_s) + \lambda \times CELoss(f(X_M),Y_M)
$$</p>
<p>$X_s$는 Source Image $Y_s$ Source Label $X_M$ Mixed Image $Y_M$ Mixed Label 입니다.</p>
<p>본 논문에서는 이미지들이 모두 모델 $f$에 입력으로 들어가지만 공식 코드에서 Target Image는 Teacher Model $g$에 들어가게 됩니다.</p>
<p>자세한 구조는 4절에서 다루겠습니다.</p>
<h2 id="4-overall-algorithm">4. Overall Algorithm</h2>
<p>우선 알고리즘을 설명하기 전 Teacher Model과 Student Model에 대해서 설명하고 가겠습니다.</p>
<ul>
<li><p>Teacher Model : UDA에서 Pseudo Label을 생성하는 Model로 이 모델의 가중치는 지수 이동 평균과 같은 알고리즘으로 Student Model을 기반으로 업데이트 됩니다.</p>
</li>
<li><p>Student Model : UDA에서 학습을 하는 모델로 Source 이미지와 Label과 Target 이미지와 Pseudo Label을 통해 학습을 진행합니다.</p>
</li>
</ul>
<p>간단하게 말해서 Teacher Model은 Target 이미지에 대한 라벨을 생성해서 Student Model 에게 Target 이미지에 대한 정보를 알려주는 것이기 때문에 Teacher Model이라고 하는 것입니다.</p>
<h3 id="41-dacs">4.1 DACS</h3>
<p>DACS의 전반적인 알고리즘은 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d19b0bf2-6068-4a11-9c26-385c13edbb19/image.png" alt=""></p>
<p>※ 실제 코드와 조금 다른 점이 있어 코드 기반으로 설명하겠습니다.</p>
<ol>
<li><p>모델 초기화</p>
</li>
<li><p>학습 에폭 수</p>
</li>
<li><p>Source Data로부터 이미지와 라벨을 추출</p>
</li>
<li><p>Target Data로부터 이미지를 추출</p>
</li>
<li><p>Target 이미지를 Teacher Model에 입력으로 넣어 Pseudo Label을 생성</p>
</li>
<li><p>Class Mix를 적용하여 Mixed 이미지와 라벨을 생성</p>
</li>
<li><p>Source 이미지와 Mixed 이미지를 Student Model에 입력으로 하여 예측값을 생성</p>
</li>
<li><p>예측값과 라벨을 통해 Loss를 계산</p>
</li>
<li><p>BackPropagation 진행</p>
</li>
<li><p>SGD를 기반으로 Student Model 가중치 Update 후 Teacher Model의 가중치는 Student Model의 가중치를 기반으로 EMA를 통해 Update 진행</p>
</li>
</ol>
<h3 id="42-architecture-flow">4.2 Architecture Flow</h3>
<p>이를 간단히 흐름도를 통해 보이면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/37082b2f-7e50-4924-9733-73e7d786d919/image.png" alt=""></p>
<p>여기서 Teacher Model은 Student Model과 동일한 모델이지만 파라미터(가중치)를 지수이동평균(EMA)을 통해 업데이트합니다.</p>
<p>Student Model의 학습될 파라미터들을 $F(\theta)$ Teacher Model의 파라미터들을 $G(\theta)$라고 한다면 각 에폭당 Teacher Model의 파라미터는 다음과 같습니다.</p>
<p>$$
G_{epoch}(\theta) = \alpha \times G_{epoch-1}(\theta) + (1-\alpha) \times F_{epoch}(\theta)
$$</p>
<p>$$
\alpha = min(1-\frac{1}{epoch},max Alpha)
$$</p>
<p>이렇게 학습을 진행하며 일관적으로 학습을 하게 된다고 합니다.</p>
<p>여기서 중요한 점은 Teacher Model은 Pseudo Label을 생성하기 위한 용도이므로 Dropout 등을 사용하지 않는 Eval 모드(Pytorch에서)로 진입해두어야 합니다.</p>
<h3 id="43-similarity-between-source-and-target">4.3 Similarity Between Source and Target</h3>
<p>본 논문의 저자들은 GTA 5 -&gt; CityScapes 로의 UDA 성능이 SYNTHIA -&gt; CityScapes로의 UDA 성능보다 좋다는 것을 보여줍니다.</p>
<p>이를 설명하기 위해 저자들은 Source와 Target에서의 Cityscapes와 GTA5의 유사도가 SYNTHIA와의 유사도보다 높았기에라고 설명합니다.</p>
<p>즉, DACS 기법의 특성상 적절한 위치에 있는 Class가 섞이는 경우에 더 합리적이기 때문이라고 합니다.</p>
<h2 id="5-마무리">5. 마무리</h2>
<p>본 논문에서 사용한 방법론은 이후에 다룰 UDA 관련 논문인 DAFormer, MIC에서도 사용되기 때문에 알고가면 좋습니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[SegFormer 논문 리뷰]]></title>
            <link>https://velog.io/@pre_f_86/SegFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@pre_f_86/SegFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Fri, 24 Nov 2023 12:25:59 GMT</pubDate>
            <description><![CDATA[<p>본 페이지에서는 <a href="https://arxiv.org/abs/2105.15203">SegFormer: Simple and Efficient Design for Semantic
 Segmentation with Transformers</a> 논문에 대해서 말하고자 합니다.</p>
<hr>
<h2 id="1-intro">1. Intro</h2>
<p>본 논문은 ViT계열의 Segmentation 모델인 SegFormer 모델을 발표를 합니다.</p>
<p>기존 Segmentation 모델들은 FCN을 이어 여러 CNN을 활용한 Segmentation 모델들이 발전 해왔습니다.</p>
<p>과거에 Segmentation은 주로 두 방법에 의해 연구가 되었습니다.</p>
<ol>
<li><p>특징을 잘 추출하기 위한 Encoder 모델 즉, Backbone 모델을 개선하여 성능을 향상시키는 방법 </p>
</li>
<li><p>이미지 내의 문맥 정보를 효율적으로 추출하기 위한 모듈과 연산을 추가하여 성능을 향상시키는 방법</p>
</li>
</ol>
<p>이후 ViT의 등장으로 ViT를 Segmentation에 활용하고자 ViT를 Encoder에 CNN을 Decoder에 사용하는 방식으로 좋은 성능을 내는 SETR의 등장이 있었지만 다음과 같은 문제가 있었습니다.</p>
<ol>
<li>ViT는 CNN과 달리 하나의 저해상도 특징들만을 사용한다.</li>
</ol>
<blockquote>
<p>CNN은 Convolution 연산과 Pooling 연산을 통해 이미지의 여러 해상도를 사용하는 것과 달리 ViT는 고정된 크기의 해상도만들 사용하는 것을 말합니다.</p>
</blockquote>
<ol start="2">
<li>이미지 크기에 따라 크게 증가하는 연산량</li>
</ol>
<blockquote>
<p>ViT는 동일한 패치크기를 사용할 때 이미지의 크기가 커지면 패치가 그만큼 늘어나게 되고 모든 패치들과의 유사도를 계산하는 Self-Attention의 특성상 연산량이 제곱배로 늘어나게 된다.</p>
</blockquote>
<p>이를 해결하기 위해 여러 PVT, Swin Transformer, Twins 등 여러 방법론들이 등장했지만 주로 Encoder에 대한 것을 다룰 뿐 Decoder는 전혀 다루지 않았습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/b4c484a5-ef2b-4fac-bf9f-e44886cc23f8/image.PNG" alt=""></p>
<p>SegFormer의 주요 특징은 다음과 같습니다.</p>
<ol>
<li><p>계층적 구조의 Transformer Encoder를 사용하여 다양한 Scale의 특징들을 활용할 수 있다고 합니다.</p>
</li>
<li><p>Positional Encoding을 사용하지 않았고 이에 따라 학습 때 사용한 데이터의 이미지의 해상도와 다른 크기의 이미지를 사용한다고 하여도 성능감소가 크지 않았습니다.</p>
</li>
<li><p>간단한 구조의 Decoder를 사용하며 Encoder의 여러 계층에서 얻어낸 특징들을 통합하여 사용하였고 이로 인해 Global한 문맥정보와 Local한 정보를 모두 잘 활용할 수 있다고 합니다.</p>
</li>
</ol>
<hr>
<h2 id="2-segformer">2. SegFormer</h2>
<p>SegFormer는 다음과 같은 특징을 가집니다.</p>
<ol>
<li><p>계층적 구조의 Encoder : 고해상도의 Coarse 특징들과 저해상도의 Fine-Grained 특징들을 추출합니다. 특히 Positional Embedding을 사용하지 않았고 이에 따라 추론시 다른 크기의 이미지를 사용해도 성능이 크게 감소하지 않습니다.</p>
</li>
<li><p>경량화된 Decoder : 더 적은 연산량을 가지며 Encoder에서 얻어낸 모든 특징들을 모두 활용해 최종 출력을 얻어냅니다.</p>
</li>
</ol>
<p>구조에 대한 그림은 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/20841272-dce7-4d70-990e-7f74c690387f/image.PNG" alt=""></p>
<p>간략한 구조 설명은 다음과 같습니다.</p>
<ol>
<li><p>입력이미지에 대해 $4 \times 4$크기의 패치로 나눕니다.</p>
</li>
<li><p>계층적 구조의 Encoder에 넣어 원본 이미지 크기의 {1/4, 1/8, 1/16, 1/32}의 특징맵을 얻어냅니다.</p>
</li>
<li><p>Encoder에서 얻어낸 모든 특징맵을 활용해 Decoder를 통해 최종 결과를 출력합니다</p>
</li>
</ol>
<h3 id="21-hierarchical-transformer-encoder">2.1 Hierarchical Transformer Encoder</h3>
<p>본 논문에서 Transformer Encoder의 이름을 Mix Transformer(MiT)라고 지었습니다.</p>
<p>MiT의 주요 특징을 자세히 설명하면 다음과 같습니다.</p>
<h4 id="hierarchical-feature-representation">Hierarchical Feature Representation</h4>
<p>ViT의 구조는 다음과 같습니다.</p>
<p>각 격자는 하나의 Patch라고 가정합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/116dd3b2-6ea9-428a-9004-4c436c59858e/image.PNG" alt=""></p>
<p>동일한 수의 Patch를 토대로 연산을 진행하며 Patch의 수가 변하지 않습니다.</p>
<p>MiT의 구조는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/e1de321a-2429-4f47-ace4-ed5be002b87d/image.PNG" alt=""></p>
<p>이 구조는 CNN과 유사한 형태로 고해상도의 Coarse한 특징들과 저해상도의 Fine-Grained 특징들을 얻어 Segmentation에서 더욱 좋은 성능을 낼 수 있다고 합니다.</p>
<h4 id="overlapped-patch-merging">Overlapped Patch Merging</h4>
<p>기존의 ViT 계열의 모델들에서 사용되던 Patch Merging은 인접한 Patch들을 붙이는 방법으로만 작동 됐습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/45999cfc-3693-42d8-9cd9-46c540a884a1/image.png" alt=""></p>
<p>이러한 구조 때문에 다른 부분으로 병합된 다른 패치와의 정보는 단절되게 됩니다.</p>
<p>이를 해결하기 위해 Overlapped Patch Merging을 고안하여 다른 패치와의 정보를 교환할 수 있도록 합니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/04786b3e-6d7c-4d3b-9819-c248b6675a86/image.png" alt=""></p>
<p>위와 같이 왼쪽 위의 패치에서는 1, 2, 3번 패치 오른쪽 위에서는 2, 3, 4번 패치와 각각 Self-Attention을 진행하기 때문에 정보가 단절되지 않습니다. </p>
<p>Conv 연산과 비슷하게 Kernel Size(K), Stride(S), Padding(P)를 정의하여 비슷한 원리로 Patch를 병합합니다.</p>
<p>논문에서 사용한 (K, S, P) 는 각각 (7, 4, 3) , (3, 2, 1) 입니다.</p>
<h4 id="efficient-self-attention">Efficient Self-Attention</h4>
<p>Encoder의 연산에서 병목이 되는 주요한 원인 중 하나는 Self-Attention 레이어의 연산량 입니다.</p>
<p>Atention 연산은 다음과 같은 수식으로 이루어집니다.</p>
<p>$$
Attention(Q,K,V) = Softmax(\frac{QK^T}{\sqrt{d_{head}}})V
$$</p>
<p>이 때 패치의 수가 $N$이라면 시간 복잡도는 $O(N^2)$이 됩니다. </p>
<p>따라서 이를 해결하기 위해 본 논문에서는 Reduction Ratio $R$을 사용하여 시간 복잡도를 줄이고자 합니다.</p>
<p>그 과정은 다음과 같습니다.</p>
<p>$$
\hat{K} = Reshape(\frac{N}{r},C \cdot R)
$$</p>
<p>$$
K = Linear(C \cdot R, C )(\hat{K})
$$</p>
<p>이로 인해 Attention 연산의 시간 복잡도는 $O(N^2)$에서 $O(\frac{N^2}{R})$로 줄어듭니다.</p>
<p>본 논문에서 $R$은 stage-1 ~ stage-4에서 각각 [64, 16, 4, 1]로 설정됩니다.</p>
<h4 id="mix-ffn">Mix-FFN</h4>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/4ac6fae2-1d82-476b-a6fe-1a20fea2ff04/image.png" alt=""></p>
<p>기존의 ViT는 Positional Encoding을 사용하여 각 패치에 위치정보를 제공하였습니다.</p>
<p>이 방식은 결국 모델에 다른 해상도의 이미지가 들어올 경우 성능이 크게 감소되는 결과를 야기합니다.</p>
<p>본 논문에서는 이를 해결하기 위해 Mix-FFN을 소개합니다.</p>
<p>Mix-FFN은 지역 정보를 유출하는 Zero Padding의 영향을 고려하여 기존의 Feed Foward Network에 직접적으로 $3 \times 3$ Conv연산을 적용합니다.</p>
<p>변경된 FFN의 식은 다음과 같습니다.</p>
<p>$$
MixFFN(x) = MLP(GELU(Conv_{3 \times 3}(MLP(x))))+x
$$</p>
<p>본 논문에서는 $3 \times 3$ Conv 연산으로 인해 Positional Encoding을 충분히 대체할 수 있다고 합니다.</p>
<p>또한 Depth-Wise Conv를 사용하였고 이로 인해 파라미터의 수를 줄이고 효율성을 증가시켰다고 합니다.</p>
<h3 id="22-lightweight-all-mlp-decoder">2.2 Lightweight All-MLP Decoder</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/8fc3af31-6d22-4b6c-8b14-cf111f376675/image.png" alt=""></p>
<p>기존의 방법론들과는 다르게 SegFormer는 Decoder 부분에도 변화를 주었습니다.</p>
<p>SegFormer는 매우 경량화된 Decoder를 사용함에도 좋은 성능을 내게 됩니다.</p>
<p>이 이유는 계층적 구조의 Transformer Encoder가 CNN보다 넓은 Effective Recpetive Field(ERF)를 가지고 있기 때문이라고 합니다.</p>
<p>Decoder의 수식은 다음과 같습니다.</p>
<p>$$
\hat{f} = Linear(C_i,C)(F_i) \
\hat{f} = Upsample(\frac{W}{4} \times \frac{W}{4})(\hat{F_i}) \
F = Linear(4C,C)(Concat(\hat{F_i}))\
M = Linear(C,N_{cls})(F)
$$</p>
<p>이때 $\hat{f_i}$는 각 Encoder의 출력이며 $c_i$는 각 Encoder의 채널 수입니다.</p>
<h4 id="effective-receptive-field-analysis">Effective Receptive Field Analysis</h4>
<p>Semantic Segmentation에서 넓은 Receptive Field를 가지면서 문맥 정보를 파악하는 것은 가장 큰 문제였습니다.</p>
<p>본 논문은 Receptive Field에 대해서 시각화를 하였는데 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/bd8af1c5-bc79-4281-bede-38e84a3b7f7f/image.PNG" alt=""></p>
<p>DeepLab V3+ 모델과 SegFormer의 ERF를 시각화한 것입니다.</p>
<p>Deeplab V3+의 ERF를 살펴보면 Stage-4가 될때 까지 SegFormer보다 상대적으로 작습니다.</p>
<p>SegFormer의 Receptive Field 특징은 빈 부분이 없이 골고루 인식하며 이로 인해 Encoder만으로도 Global Context 또한 잘 인식할 수 있다는 점이 장점입니다.</p>
<p>결국 CNN Encoder의 제한된 Receptive Field로 인해 ASPP와 같은 무거운 모듈을 사용해야 비어있는 Receptive Field를 반영하여 문맥 정보를 잘 파악할 수 있습니다.</p>
<p>하지만 SegFormer는 MiT Encoder의 골고루 퍼진 Receptive Field 때문에 간단한 Decoder 하나만으로도 넓은 Receptive Field를 가진다고 합니다.</p>
<h3 id="23-relationship-to-setr">2.3 Relationship to SETR</h3>
<p>이 부분은 SegFormer와 SETR의 차이점과 장점을 얘기합니다.</p>
<ul>
<li><p>SETR과는 달리 더 작은 Imagenet-1K로 사전학습을 하였습니다.</p>
</li>
<li><p>SegFormer의 Encoder는 계층적 구조를 가지고 있기 때문에 고해상도의 Coarse한 특징과 저해상도의 Fine한 특징을 잘 얻을 수 있습니다.</p>
</li>
<li><p>Positional Embedding을 사용하지 않았고 이에 따라 추론시 다른 크기의 이미지를 사용해도 성능이 크게 감소하지 않습니다.</p>
</li>
<li><p>가벼운 Decoder의 사용으로 더 적은 연산량을 가집니다.</p>
</li>
</ul>
<h2 id="3-model-architecture">3. Model Architecture</h2>
<h3 id="실험-내용들">실험 내용들</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/1d74141a-ab66-4a33-84d1-daa5599ec016/image.PNG" alt=""></p>
<h3 id="다른-모델들과의-성능-비교">다른 모델들과의 성능 비교</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/391ebef3-d872-4610-88a3-526bd3f37fb1/image.PNG" alt=""></p>
<h3 id="encoder-크기에-따른-하이퍼-파라미터">Encoder 크기에 따른 하이퍼 파라미터</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/b9d57b6e-0366-4b83-b1b0-f5db4a72a1a1/image.PNG" alt=""></p>
<h2 id="4-마무리">4. 마무리</h2>
<p>Segmemtation 분야에서 그동안의 ViT 계열의 모델들과는 달리 Decoder 구조에도 신경을 쓴 점과 Positional Encoding을 없앤 점이 인상 깊은 논문 입니다.</p>
<p>대부분의 실험 내용에 대해서는 잘 나와있지만 Self Attention에서 하이퍼 파라미터 $r$에 대한 실험 결과가 없는 것이 조금 아쉬운 거 같습니다.</p>
<p>그럼에도 Transformer를 사용하면서도 CNN계열보다 더 나은 FPS와 더 나은 성능을 제공한다는 점이 크게 매력으로 다가오는 것 같습니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023 Samsung AI Challenge : Camera-Invariant Domain Adaptation 회고]]></title>
            <link>https://velog.io/@pre_f_86/2023-Samsung-AI-Challenge-Camera-Invariant-Domain-Adaptation-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@pre_f_86/2023-Samsung-AI-Challenge-Camera-Invariant-Domain-Adaptation-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 16 Nov 2023 08:01:09 GMT</pubDate>
            <description><![CDATA[<h1 id="포기하지-말고-끝까지">포기하지 말고 끝까지!</h1>
<p><strong>대회 중 포기할까 고민을 정말 많이 했지만 버텨내서 상위권까지!</strong></p>
<p>※코드와 PPT는 제 Github나 아래 DACON URL에서 코드 공유 게시판에 올려두었습니다.</p>
<p>GitHub : <a href="https://github.com/PreFKim/Unsupervised-Domain-Adaptation-with-Distortion-Aware">Unsupervised-Domain-Adaptation-with-Distortion-Aware</a></p>
<p>DACON URL : <a href="https://dacon.io/competitions/official/236132/overview/description">2023 Samsung AI Challenge : Camera-Invariant Domain Adaptation</a></p>
<p>참고한 UDA 방법론 논문 리뷰 : <a href="https://velog.io/@pre_f_86/DACS-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DACS</a>, <a href="https://velog.io/@pre_f_86/DAFormer-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">DAFormer</a>, <a href="https://velog.io/@pre_f_86/MIC-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">MIC</a></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/92e94c3d-19b6-4425-8a32-dc8a7b9ea79f/image.png" alt=""></p>
<p><strong>PUBLIC SCORE</strong>
<img src="https://velog.velcdn.com/images/pre_f_86/post/f0e8656b-ced3-4e96-b268-1b84e328135e/image.png" alt=""></p>
<p><strong>PRIVATE SCORE</strong>
<img src="https://velog.velcdn.com/images/pre_f_86/post/49eba564-44d7-42a0-b6c8-8d896c266009/image.png" alt=""></p>
<h2 id="대회-입문">대회 입문</h2>
<p>LG Aimers 3기에서 온라인 해커톤을 진행하며 처음 대회에 나가며 DACON을 접했고 대회가 마냥 재미있었다.</p>
<p>이후 다른 대회가 무엇이 있나 보던 중에 Samsung AI Challenge 관련 대회 3개를 보게 되었다.</p>
<p>나는 주로 Segmentation에 대해서 공부하였고 리더보드 성능(mIoU)도 0.1정도를 달성하고 있어 쉬운 대회라고 생각하여 Camera-Invariant Domain Adaptation 분야를 선택해 대회를 참여했다.( 0.1이면 그런 이유가 있을텐데.... )</p>
<p>처음에 Domain Adaptation이라는 것을 보지 못하고 그냥 자율주행용 Image Segmentation 대회라고 생각하였다.</p>
<p>막상 대회에 참여 하려고 하니 데이터 셋에 라벨이 없는 Target Data가 있는 것을 보았고 많이 당황했다.</p>
<blockquote>
<p><strong>라벨이 없는 데이터를 사용해 학습에 사용한다고?</strong></p>
</blockquote>
<p>항상 라벨이 있는 데이터만 다루다가 라벨이 없는 데이터를 다루는 것은 처음이라 다음에 비슷한 대회를 참여하자 생각하기도 했지만 그냥 머리 박고 해보자라는 생각으로 나의 두번째 대회에 입문했다.</p>
<h2 id="대회-규칙">대회 규칙</h2>
<p>LG Aimers 온라인 해커톤에서 규칙을 제대로 읽지 않아 오프라인 해커톤 참여를 못했었다.</p>
<p>아래는 LG Aimers 온라인 해커톤 평가 방식이다.
<img src="https://velog.velcdn.com/images/pre_f_86/post/3a64dca6-61db-4261-a724-b97c44a92b36/image.png" alt="LG Aimers 온라인 해커톤 평가 방식"></p>
<p>1차평가가 Private Score 100% 라고 해서 상위 30개의 팀만 2차평가 대상인줄 알고 Private 45등을 하여 안되는 줄 알고 그냥 넘어갔다.</p>
<p>나중에 DACON에서 연락이와 PPT나 파일 제출을 안한 것이 맞는지 물어봐서 다시 규칙을 확인해보니 진출을 &#39;희망&#39;하는 팀이라고 되어 있었다...(나중에 알고보니 최대 53등까지 오프라인 해커톤에 진출했다)</p>
<p>기회를 그냥 날린 거 같아서 많이 아쉬웠다.</p>
<p>이런 아픈 경험을 안고 대회 규칙부터 자세히 살펴 보았고 그 중 평가 방식을 주로 보았다.</p>
<p>아래는 삼성 AI Challenge 평가방식이다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/19c4ffa1-d14b-4c1f-844d-4a44adaf070b/image.png" alt="2023 Samsung AI Challenge : Camera-Invariant Domain Adaptation 대회 규칙"></p>
<p>이 규칙을 보고 대회 진행 방향성을 다음과 같이 생각했다.</p>
<ol>
<li><p><strong>Unsupervised Domain Adaptation(UDA)기법을 사용할 것(대회 목적)</strong>
본 대회의 목적 자체가 Source Domain과 Target Domain간의 Gap을 줄이는 것이 목적이다. 특히, 주어진 데이터셋을 충분히 활용하기 위해서 라벨이 없는 Target 이미지도 사용하여야 함은 분명하기 때문이다.</p>
</li>
<li><p><strong>나만의 Domain Adaptation 기법을 생각할 것(창의성)</strong>
본 대회는 다른 UDA 기법들과는 달리 카메라 왜곡에 의한 Domain Gap을 줄이고자 하는 것이다. 확실히 SOTA 모델들을 보면 GTA5, Cityscape 등 왜곡을 고려한 Domain Adaptation 기법은 보이지 않는다. Domain Adaptation에 대해 잘 알지 못하기에 완전히 새로운 방법을 제안하기보다 기존의 방식을 기반으로 본 대회에 맞게 방법론을 고치는 방향으로 가기로 했다.</p>
</li>
<li><p><strong>연산량을 최대한 줄여볼 것(적용 가능성)</strong>
알고리즘을 실제 현업에서 적용하기 위해 고려해야할 부분은 결국 알고리즘의 성능과 처리 속도이다. 단기간 내에 연산량을 신경쓰며 구조, 모델, 파이프라인을 모두 고칠 수는 없지만 가능한 연산량을 줄이며 좋은 성능을 내도록 노력하며 대회를 참여하려고 하였다.</p>
</li>
</ol>
<h2 id="대회-중">대회 중</h2>
<h3 id="방법론-선택">방법론 선택</h3>
<p>대회는 9월 1일부터 시작을 하였다.</p>
<p>Paper with code 사이트에서 Unsupervised Domain Adaptation 관련 논문들을 읽어보는데 쉽지 않았다.</p>
<p>대부분 데이터셋에서 좋은 성능을 내는 <a href="https://arxiv.org/abs/2212.01322">MIC</a>논문을 보니 <a href="https://arxiv.org/abs/2304.13615">DAFormer</a> 기반으로 한 논문이고 DAFormer는 <a href="https://arxiv.org/abs/2007.08702">DACS</a>에서 사용한 기법을 사용한다고 한다. </p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/da9b01ab-37c5-4a56-89f8-6ab5c83e18d1/image.png" alt="">
※MIC 구조</p>
<p>구현 내용도 자세히 안나와있고 코드를 봐도 처음보는 mmSegmentation을 사용해서 코드 구조도 파악하기 어려웠었다. 그래서 다른 사람들이 논문 리뷰를 한 자료가 있나 찾아보아도 없었다.</p>
<p>이러다 보니 정말 논문 보고 코드 보고 머리 박으면서 최대한 자세히 구현하려고 노력하였다.</p>
<p>어느정도 파이프라인을 구현하고 Colab에서 Tesla T4를 사용해 약 3000장의 이미지(이미지 해상도 256x512)를 학습해보니 한 에폭당 28분이 나왔다.</p>
<p>논문에서는 40000에폭 학습했다고 한다.</p>
<p>이 때문에 여러 실험을 진행하기 어려울 듯 하여 이미지 크기 줄이기, 데이터 양 1/10로 줄이기로 학습시간을 에폭당 2분으로 줄여 여러 실험이 가능하게 됐고 이에 많은 실험을 진행했다.</p>
<h3 id="양선형-보간법을-이용한-해상도-변경">양선형 보간법을 이용한 해상도 변경</h3>
<p>OpenCV로 Segmentation Mask의 해상도를 줄여 저장하는 과정에서 발생한 문제이다.</p>
<p>파이프라인을 작성하며 Segmentation Map을 시각화 했더니 이미지 내에 차량 보닛이 있는 위치인데 하늘에 해당하는 클래스가 입혀져 있었다.</p>
<p>어디서 문제가 발생했나 생각해보니 Resize 과정에서 발생했을 수도 있다고 생각했다.</p>
<p><strong>※ 나중에 알고보니 데이터셋 문제였다...</strong></p>
<p>그동안 이론적으로는 알고 있었으나 실제로 이 문제를 만나는 것은 처음이라 고민을 조금 많이 했다.</p>
<p>그렇다고 이웃 보간법을 이용해 해상도를 변경하기에는 그냥 기분이 안내켰다.</p>
<blockquote>
<p>어떻게 하면 이웃 픽셀을 고려하며 Segmentation Map의 크기를 조절할 수 있을까?</p>
</blockquote>
<p>이런 고민을 하며 정리한 내용은 <a href="https://velog.io/@pre_f_86/Segmentation%EC%97%90%EC%84%9C%EC%9D%98-Resize-%EB%AC%B8">Segmentation과 보간법</a> 게시글에 작성해뒀습니다 ㅎㅎ.</p>
<p>이러한 생각은 아래와 같은 생각으로 이어졌고 모델 구조 개선을 통한 성능 향상으로 이어졌다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/5f58434b-1bce-4e71-88ef-27fdd12db214/image.png" alt="">
※ 서면 평가용 PPT 속 내용(제 깃허브나 본 대회 DACON 코드 공유란에 올렸습니다.)</p>
<h3 id="seed-고정을-왜-하지">Seed 고정을 왜 하지?</h3>
<p>처음 여러 실험을 진행할 때 다음과 같은 고민을 많이 했다.</p>
<blockquote>
<p>왜 Seed를 고정하며 성능 비교를 할까? 분명 변인 통제를 위한 것이긴 한데 만약 해당 Seed에서만 성능이 좋은 것이면 의미 없는 것이 아닌가?</p>
</blockquote>
<p>이전에 프로젝트를 진행하거나 연구를 할 때는 모두 Seed를 고정하고 진행했는데 왜 이런 생각이 들었는지는 모르겠다.</p>
<p>결과는 Seed를 고정하지 않아서 무엇이 성능이 좋아진 것인지 파악하기 어려웠다.</p>
<p>이거 때문에 Colab 컴퓨팅 파워를 많이 날렸다.....</p>
<p>또 학습 때마다 성능이 매번 달라져서 재현도 못하고 실험 성능을 평균적으로 확인하기에는 학습이 너무 오래 걸리는 점이 걸렸다.</p>
<p>그래서 9월 1일에 시작한 대회지만 제대로 된 기록은 9월 11일부터 작성했다.</p>
<p>그래도 Seed를 고정하고 실험하니 결과 비교도 수월하고 신경쓸게 적어져 마음이 너무 편해졌다.</p>
<p><strong>※ 다음부터는 무조건 Seed를 고정해야겠다!</strong></p>
<h3 id="sota-방법론-구조변경">SOTA 방법론 구조변경</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/2a56934e-34b9-443c-8a9b-43e9f2c342c6/image.png" alt=""></p>
<p>성능 그래프에서 보면 알 수 있지만 20일까지는 성능이 급 상승 하다가 이후로는 성능 변동이 크게 없었다.</p>
<p>데이터 증강 확률 조정, 기법, Validation 데이터 사용, 하이퍼 파라미터 튜닝 등 여러 실험을 했지만 미미한 증가만 있었다.</p>
<p>이제 대회 데이터셋에 맞게 왜곡에 대응할 수 있도록 MIC의 구조를 변경하고자 하였다.</p>
<p>MIC를 사용해서 학습을 하는데 성능은 0.5370 정도가 나오는데 실제 추론 사진을 살펴보던 중 신호등같은 작은 물체를 잘 예측하지 못하던 것을 확인했다.</p>
<p>데이터 증강 중에 Elastic과 Affine으로 인해 원본 이미지에서 작은 물체의 정보가 사라진다고 생각하였다.</p>
<p>실제로 Elastic과 Affine을 제거하면 전체 성능이 감소하는 결과를 보이지만 작은 물체는 잘 구분한다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/879a6e1d-b06a-48f2-a79c-2822159382ed/image.png" alt=""></p>
<p>왼쪽은 Source 이미지이고 오른쪽은 Target 이미지다.</p>
<p>Target의 가장자리 부분을 보면 왜곡에 의해 Source보다는 회전되어 있는 것처럼 보인다.</p>
<p>성능이 감소하는 이유는 Elastic과 Affine은 그런 왜곡에 대해서 반응을 하기 때문에 이를 제거하면 성능이 감소하는 것이다 라고 판단하였다.</p>
<blockquote>
<p>Elastic과 Affine을 사용하면서 이로 인한 정보 손실을 어떻게 방지할 수 있을까?</p>
</blockquote>
<p>간단한 해결책은 Elastic과 Affine 증강을 사용한 것과 사용하지 않은 이미지를 둘다 모델에 학습시키는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/66400a02-688f-48f5-8821-c8e212fc3531/image.png" alt=""></p>
<p>여러 실험 끝에 위와 같은 구조를 생각했다.</p>
<ul>
<li><p>Rare Class Sampling 삭제 : 사람, 라이더, 보도블럭 등 흔하지 않은 클래스를 더 자주 추출해서 모델에 학습시키는 것인데 데이터셋이 크지 않아 중복되는 데이터가 많아져 모델 학습에 악영향을 줌</p>
</li>
<li><p>DACS 삭제 : 오히려 성능에 악영향을 주는 듯 하여 삭제</p>
</li>
<li><p>Feature Distance 삭제 : 이 또한 성능에 악영향을 주어 삭제</p>
</li>
</ul>
<p>변경점</p>
<ul>
<li>Affine과 Elastic을 적용하는 Distortion 증강 부분과 적용하지 않는 일반 증강으로 나누어 모델에 입력으로 넣음</li>
</ul>
<p>이 구조로 인해 실제로 작은 물체도 잘 예측하게 되었고 성능도 0.5434로 상승하였다.</p>
<p>비록 큰 상승폭은 아니지만 작은 물체도 잘 구분하게 되었다는 점이 이점으로 다가왔다.</p>
<p>이후 데이터셋의 수를 1/10만 사용하는 것이 아닌 전체를 사용하여 0.5655까지 올렸다.</p>
<blockquote>
<p>성능 향상 마지막 희망이던 데이터 셋 전체 사용해도 성능이 많이 안오르네... 포기해야하나?</p>
</blockquote>
<p>10등 커트라인 점수가 약 0.59이고 여태 실험들로 거의 다 시도해본 거 같아서 포기할까 고민했었다.</p>
<p>그래도 뭔가 찾아봐서 해봐야지 하고 뭐가 있을까 고민을 해보았고 해상도를 키워보자라는 생각을 했던 거 같았다.</p>
<h3 id="해상도와-transformer의-연산량">해상도와 Transformer의 연산량</h3>
<p>연산량은 추론과도 관계가 있지만 학습 시간과도 관계가 있다.</p>
<p>Transformer의 Self-Attention 연산의 특성상 패치의 크기가 동일하다면 이미지 크기가 2배로 커질 경우 패치의 수는 4배가 되어 연산량이 4배가 된다.</p>
<p>이 때문에 $1024 \times 2048$ 해상도의 이미지를 $256 \times 512$로 줄이고 학습을 한 것이다.</p>
<blockquote>
<p>해상도를 키우면서 연산량을 줄일 수는 없을까?</p>
</blockquote>
<p>큰 해상도를 학습하는데 필요한 시간과 자원이 너무 크기 때문에 최대한 효율적인 방법을 생각해보았다.</p>
<p>결국 Crop 방법만이 생각이 난다.</p>
<p>DAFormer의 Encoder는 SegFormer에서 MiT를 사용하는데 MiT의 장점은 학습과 추론에서 이미지의 크기가 달라져도 성능 감소가 적다는 것이다.</p>
<p>그렇다고 해서 성능 감소가 없는 것은 아니기 때문에 Crop을 하여도 원본이미지 전체를 추론할 수 있는 방법을 생각해냈다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/08fa840b-b301-463d-aa66-616d4fca175a/image.png" alt=""></p>
<p>데이터셋 이미지를 $512 \times 1024$로 Resize 하고 $256 \times 512$로 Crop한 다음 학습을 한다.</p>
<p>Crop한 이미지가 $256 \times 512$이기 때문에 $256 \times 512$의 해상도를 가진 하나의 이미지 전체를 넣고 학습하는 것과 같은 연산량을 가진다.</p>
<p>또한 실제 추론 단계에서도 Crop된 이미지들을 Batch로 묶어 추론하기에 메모리 이동에 의한 연산 시간을 제외하면 추론속도는 거의 비슷하다고 생각했다.</p>
<p>그리고 제일 중요한 점은 아래처럼 Crop, Affine, Elastic으로 왜곡에 대응도 가능하다고 생각했다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/496ae2bb-af89-4c1c-9ad5-4970aa43ca0d/image.png" alt=""></p>
<blockquote>
<p>모든 이론은 확실하네 빨리 학습하고 제출해보자</p>
</blockquote>
<p>우선은 $256 \times 512$ 이미지를 $128 \times 256$으로 Crop하고 학습을 진행했다.</p>
<p>한 A100 GPU로 한 에폭당 8분 걸리던 것이 T4 GPU로 한 에폭당 9분이 걸린다.</p>
<p>기분 좋게 모든 학습이 마무리 되고 제출을 하였는데 오히려 성능이 0.01정도 감소했다...</p>
<blockquote>
<p>하.... 왜지? 성능이 더 좋아질 줄 알았는데? 뭐가 문제인 거야</p>
</blockquote>
<p>Crop으로 인해 일부 지역만을 보기에 Global Context를 잃는다는 점을 생각하지 않았다.</p>
<p>그래도 성능 차이가 많이 안나니 이미지 크기를 키우면 성능은 오르겠다고 판단하여 이미지 크기를 키우고 학습하여 결과를 제출했다.</p>
<p>결과는 성능이 올라 7위가 됐었고 보자마자 소리내며 엄청 기뻐했던 거 같다.</p>
<p>이후 기존보다 더 가벼운 모델을 사용했는데 오히려 성능이 증가했다.</p>
<p>이 모델들의 결과를 Ensemble 기법을 사용해 Public 6위를 달성하고 대회를 마무리 했다.</p>
<p>포기하지 않길 정말 잘 한 거 같다.</p>
<h2 id="대회가-끝나고">대회가 끝나고</h2>
<p>밤을 새며 대회 마무리 후 점수 채점을 하는데에 추가 시간이 걸린다고 했다.</p>
<blockquote>
<p>이 정도면 10위 안에도 들고 수상까지 할 수 있는 거 아니야??</p>
</blockquote>
<p>그래서 조금 자고 일어나려고 하는데 기대감이 너무 커 잠을 잘 수가 없었다.</p>
<p>기다리다 보니 결과가 뜨고 Private 9등을 달성하였다.</p>
<p>순위가 낮아진 것에 대한 아쉬움과 동시에 서면 평가 기회가 생겼다는 것에 너무 기분이 좋았다.</p>
<p>그러고 거의 기절하듯 잠 든 거 같다.</p>
<h3 id="ppt-작성">PPT 작성</h3>
<p>결과 발표 후 며칠동안 PPT만 만들다가 대면평가인줄 알고 발표용 PPT를 만들고 있었다.</p>
<p>나중에 알았지만 서면 평가용 PPT였다. (앞으로는 꼼꼼하게 다 잘 봐야겠다....)</p>
<p>그래서 PPT내용을 급하게 수정하고 코드 주석 작성 등 여러 수정을 급하게 진행하여 제출하였다.</p>
<p>필요한 부분도 다 작성한 것 같고 디자인도 괜찮다는 생각으로 계속 수상하는 상상을 했다.</p>
<p>그리고 대회 중 제일 중요한 점을 잘못 알고 있었다.</p>
<p>본 대회는 자율 주행용 적용 가능성이 아니었다.</p>
<blockquote>
<p><strong>카메라 왜곡을 신경쓰고 현업에서 사용 가능한 Domain Adaptation 알고리즘을 개발하는 것</strong></p>
</blockquote>
<p>대회 처음에는 방향성을 잘 잡다가 Crop과 Slide Prediction에서 추론 속도 신경쓰다가 변경된 UDA 구조에 대해서 자세한 설명이나 성능 비교를 올리지 못했다.</p>
<p>많이 아쉬웠다..</p>
<h2 id="마무리">마무리</h2>
<p>정말 어렵지만 재밌는 주제를 가진 대회였다.</p>
<p>잠을 줄이고 논문 찾아보고 구현하고 학습하는 것 때문에 바쁘다는 생각을 하긴 했지만 이 느낌이 싫지는 않았다.</p>
<p>하나 아쉬운 점은 시간이 좀 걸릴 거 같아 급하다는 생각으로 논문을 겉으로만 훌훌 읽어버리고 제대로 보지 않아서 중요한 디테일한 부분들을 놓쳤던 거 같다.</p>
<p>Pseudo Label을 만들 때는 Augmentation을 적용하지 않아야 하는데 적용 하고 입력으로 넣어서 성능 하락으로 이어진 거 같다...(다음에는 시간이 걸리더라도 확실하게 이해하고 구현해야겠다.)</p>
<p>다른 사람들의 방법론을 보면 나와 다른 방법으로 좋은 성능을 낸다는 점과 배울 점이 정말 많은 것 같다.</p>
<p>Test Time Augmentation 기법은 성능 올리기 좋은 방법인 거 같고 기회가 된다면 다음 대회에서도 사용해보고 싶다.</p>
<p>특히 모델에만 집중하고 학습 방법론(Contrastive Learning, Continual Learning, Domain Adaptation 등)은 생각하지 못하던 나에게 새로운 눈을 뜨게 해준 것 같다.</p>
<p>앞으로는 모델만 찾아보는게 아니라 여러 논문도 찾아봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LG Aimers 3기 Phase 2 온라인 해커톤 회고]]></title>
            <link>https://velog.io/@pre_f_86/LG-Aimers-3%EA%B8%B0-Phase-2-%EC%98%A8%EB%9D%BC%EC%9D%B8-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@pre_f_86/LG-Aimers-3%EA%B8%B0-Phase-2-%EC%98%A8%EB%9D%BC%EC%9D%B8-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 12 Nov 2023 09:31:04 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터-분석과-소통의-중요성">데이터 분석과 소통의 중요성</h1>
<p><strong>성능 향상을 위해서 모델만을 중요하게 생각했던 나에게 데이터의 중요성을!</strong></p>
<p><a href="https://velog.io/@pre_f_86/LG-Aimers-3%EA%B8%B0-Phase-1-%EC%98%A8%EB%9D%BC%EC%9D%B8-%EA%B5%90%EC%9C%A1-%ED%9B%84%EA%B8%B0">LG Aimers Phase 1 후기</a></p>
<p>LG Aimers : <a href="https://www.lgaimers.ai/">LG Aimers 홈페이지</a></p>
<p>LG Aimers Online Hackathon (DACON) : <a href="https://dacon.io/competitions/official/236129/overview/description">온라인 채널 제품 판매량 예측 AI 온라인 해커톤</a></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c96148a2-0346-457b-b99f-c1e7cd391822/image.png" alt=""></p>
<p><strong>PUBLIC SCORE</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/8f46df7d-4ade-4795-a3a1-d7a0cce8c91d/image.png" alt=""></p>
<p><strong>PRIVATE SCORE</strong></p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/aa0f0709-77c4-4162-8d0d-6a3666475c23/image.png" alt=""></p>
<h2 id="대회-시작-전">대회 시작 전</h2>
<p>LG Aimers 3기 Phase 1 온라인 강의를 듣고 Phase 2가 시계열 데이터를 통해 판매량 예측이라는 것을 알았다.</p>
<p>그래서 대회 시작 7일 전부터 정형 데이터나 시계열 데이터를 어떻게 분석하고 활용해서 성능을 올리는지에 대한 자료를 많이 찾아봤다.</p>
<p>그럼에도 정형 데이터나 시계열 데이터를 다뤄보지 않았던 나는 불안해졌고 팀을 꼭 구해야겠다고 생각했다.</p>
<p>대회 시작 2일 전 쯤인가부터 사람들이 팀원을 모으기 시작해서 대회 경험이나 시계열 데이터를 다뤄본 사람들에게 연락을 남기고 했지만 아무 팀도 가지 못했었다.</p>
<p>이미지 데이터만 다뤄본 나의 큰 욕심이었던 거 같다.</p>
<blockquote>
<p>잘하는 사람이 아니어도 다 같이 열심히 해서 올라가보자.</p>
</blockquote>
<p>위처럼 생각하고 팀을 찾던 중에 글 하나가 올라왔고 거기에서 같이 시작하기로 했다.</p>
<h2 id="1주차">1주차</h2>
<p>첫 대회라 그런지 어떻게 될지 몰라서 급하게 이것저것 다 하려고 하였다.(지금 돌이켜 보면 여유롭게 해도 될 것 같다.)</p>
<p>대회 시작하자마자 데이터를 다운 받고 데이터 분석을 위해 데이터구조를 내가 미리 공부하며 봤던 데이터 구조와 비슷하게 수정하고 여러 파일들을 하나의 DataFrame으로 합쳤다.</p>
<p><strong>수정 전</strong>
<img src="https://velog.velcdn.com/images/pre_f_86/post/62dd9ab6-3865-463b-b5a5-e4ea44d61798/image.png" alt=""></p>
<p><strong>수정 후</strong>
<img src="https://velog.velcdn.com/images/pre_f_86/post/e773ef91-941d-4b51-a280-f9810959335b/image.png" alt=""></p>
<p>이렇게 수정한 후에 나름대로 여러 분석을 한다고 하는데 딱히 유의미한 분석은 얻지 못했다....</p>
<p>3일 동안의 분석 후 팀원들과 첫 회의를 진행했다.</p>
<p>대회에서의 첫 회의여서 무엇부터 정하고 해야할지 잘 몰라서 팀원들의 말을 따라갔던 것 같다.</p>
<p>회의 날짜, 인사이트 공유, 코드 공유를 어떤식으로 할지 대충 정하고 마지막으로 역할 분담을 진행하였다.</p>
<p>다들 데이터 분석경험이 없다고 하여 모델을 찾아 구현해오겠다고 하였다.</p>
<p>모델링에는 어느정도 자신 있었지만... 그래도 데이터 분석은 해야하고 나름 중요하다고 생각했기 때문에 나는 데이터 분석을 하겠다고 말했던 것 같다.</p>
<p>이렇게 팀이 만들어지고 대회를 진행하게 되니까 설레기도 하고 재밌다는 감정이 들었던 거 같다.</p>
<h2 id="2주차">2주차</h2>
<p>나름 데이터를 열심히 분석하면서 얻은 결과들을 팀원들과 연락하며 공유해서 어떻게 활용하면 좋을지 이야기 해주면서 한주를 보냈던 거 같다.</p>
<p>가장 기억에 남는 것은 내가 분석한 방법이 실제로 큰 성능을 향상시킨 것이 너무 기뻤다는 것이다.</p>
<p>$$
개당 판매금액 = \frac{판매금액}{판매량}
$$</p>
<p>제품의 개당 판매금액이 일정할 것이라고 판단하여 개당판매금액 속성을 만들었고 실제로 이 속성이 일정하다는 것을 확인했다.</p>
<p>또한 판매금액이 판매량보다 변화량이 적고 경향성이 일정한 것을 확인했다.</p>
<p>이를 활용해 판매량을 직접 예측하는 것보다 판매금액과 개당 판매금액을 예측하여 판매량을 예측하는 것이 더 좋은 성능을 낼 것이라고 생각했다.</p>
<p>$$
판매량 = max(\frac{판매금액}{개당판매금액},0)
$$</p>
<p>판매량을 바로 예측하는 방법의 성능이 0.46이었는데 저 방법을 통해 0.52로 큰 성능 향상을 이뤄냈다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/67752828-f4df-4ea9-ab1b-f867d7150feb/image.png" alt=""></p>
<p>새벽에 너무 기분이 좋아서 캡처했던 기억이 있다.</p>
<p>이후 2주차 회의에서 팀명을 정하는 과정에서 팀장을 정하기로 하였고 팀장으로 제가 추천되었고 팀장이 됐다.</p>
<p>너무 급격히 진행되어서 당황했다.</p>
<p>얼떨결에 팀장이 되었지만 아마 팀원들과 의견 공유하느라 연락을 하던 이유가 컸던 거 같다.</p>
<h2 id="3주차">3주차</h2>
<p>그간 팀원들에게 답답함이 생겨났고 계속 혼자 힘들어 하고 있었다.(아마 데이터 분석이 잘 안되고 있었던 이유도 포함 되어있었을 것 같다...)</p>
<p>다들 모델링 파트를 맡고 연구 중이라고 하는데 성능 비교를 위한 기초모델을 잡지도 않고 구조 개선, 하이퍼 파라미터 튜닝에 관한 실험이 전혀 없던 것이다.</p>
<p>너무 답답하여 회의 시간에 대회 기간 동안 어떤 실험을 진행 했는지 물어봤는데 다들 &quot;~모델을 학습시켜봤습니다&quot;가 대답의 끝이었다.</p>
<p>다른 내용은 더 없었다.</p>
<p>솔직히 팀원들을 믿고 모델링을 맡긴 것인데 3주동안 튜닝 없이 학습을 했다 정도가 끝이어서 많이 실망했다....(나도 데이터 분석 처음이고 열심히 하면서 모델 학습도 돌려서 결과 확인했는데...)</p>
<p>물론 중간중간 어떤 걸 했는지 세세하게 물어보지 않은 내 잘못도 있다.</p>
<blockquote>
<p>모델링, 데이터 분석 둘 다 어려워 하면 차라리 데이터 분석을 같이 하자</p>
</blockquote>
<p>다들 모델링이 중요하다고 생각하여 모델링을 했지만 만족스럽지는 않았다.</p>
<p>그래서 데이터 분석의 중요성과 실험 기록의 중요성을 언급하며 근거 자료를 보여주며 팀원을 설득했던 거 같다.</p>
<p>실험 가이드라인 이상치 확인 가이드라인을 제공하였고 15890개의 제품에 대해서 각자 같은 비율로 나누어 이상치 분석을 진행하였다.</p>
<p>확실히 얘기하고 나니 마음도 편해지고 다들 방향성을 잡게되어 이전보다 실험 속도도 빨라졌다.</p>
<h4 id="협업에서-팀원과의-자세한-의사소통을-하는-것은-중요하다-라는-것을-알게되었다">협업에서 팀원과의 자세한 의사소통을 하는 것은 중요하다 라는 것을 알게되었다.</h4>
<h2 id="4주차">4주차</h2>
<p>4주차에 데이터 분석 방향성을 잘못 잡은 거 같다...(팀원들에게는 미안했다..)</p>
<p>이전까지는 나름 성능향상을 이루고 있었지만 제품별 상품 설명을 보다가 상품 설명 내에 제품군과 조금 동떨어진 상품 내용이 존재하는 것을 확인했고 여기에 꽂히게 됐다...</p>
<p>제품 군 내에 상품이 잘못 분류 되어 섞여있다고 판단했고 이는 아래와 같이 이상치를 일으켰다고 판단했다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/4a82e403-6472-410d-8b18-c291683cd819/image.png" alt=""></p>
<p>이 때문에 팀원들을 설득하여 모든 상품 내용을 분석하였고 잘못된 것을 바로 잡고 다시 그래프를 그렸는데 달라지는 건 없었다...</p>
<p>마지막 주차인데 3일을 날렸던 거 같아서 팀원들에게 미안했었다...</p>
<p>여차저차 시간이 지나고 마지막 날 모델 결과에 대해 Ensemble을 진행 하였고 마지막까지 성능을 올리게 되었고 Public 37등 Private 45등으로 마무리 하였다.</p>
<h2 id="대회가-마무리-되고">대회가 마무리 되고</h2>
<p>길었던 대회가 마무리 되었다.</p>
<p>대회 내내 정말 신기했던 것 같다.</p>
<p>LSTM의 문제가 장기 의존 문제가 있다고 생각해서 당연히 시계열에서도 Transformer계열의 모델이 있고 더 좋은 성능을 낼 것이라고 판단했었다.</p>
<p>특히 LSTM이 나름 오래된 모델이라고 생각하였어서 더 안좋은 성능을 낼 것이라고 생각하기도 했다.</p>
<p>데이터를 분석하여 더 좋은 특징들을 뽑아내고 모델에게 주니 더 적은 학습으로도 Transformer 모델보다 좋은 성능을 냈다.</p>
<p>평소 모델 구조를 수정해서 성능을 조금씩 올렸엇지만 데이터를 잘 처리 하여 모델을 학습하니 성능이 크게 향상되는 것을 보고 놀랐다.</p>
<p>이 대회를 통해 데이터의 중요성을 깊이 체험한 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/96b59c39-bb3e-4762-b25b-c1c38e5e0bb6/image.png" alt=""></p>
<p>Private 등수가 나오기 전 Public 등수가 37등이었고 36등까지 97명이었어서 본선 진출을 기대하고 있었다.</p>
<p>아쉽게 Private 등수가 많이 떨어지게 되어 안되겠다고 생각했다.</p>
<p>팀원들도 다들 결과를 보고 서로에게 고생했다고 인사를 나누었다.</p>
<p>그렇게 대회가 잊혀지던 중 DACON에서 전화가 왔는데 들어보니 확인전화였다.</p>
<blockquote>
<p>&quot;LG-99 팀에서 코드, 가중치, PPT를 제출하지 않으셔서 누락된 것인지 물어보려고 전화했어요&quot;</p>
</blockquote>
<p>&quot;저희 탈락 아닌가요?&quot; 하고 물어보니 제출했으면 가능성이 있었다고 한다....</p>
<p>팀원 모두가 탈락인줄 알고 PPT를 만들 생각도 보낼 생각도 하지 않았다.</p>
<p>규칙을 자세히 보니 &#39;<strong>오프라인 해커톤 진출을 희망하는 팀은 코드 제출 후 코드 평가(검증)</strong>&#39;이라고 나와 있다.</p>
<p>Private 30등 이내도 아니고 100명 미달도 아니어서 진출 못한 줄 알았었다....</p>
<p>그래도 제출을 안한 거는 맞으니 안했다고 했다.</p>
<p>오프라인 진출 대상자를 보니 더 낮은 점수를 받은 팀들도 진출한 것을 보았다.</p>
<p>그래도 제출했더라도 그 결과는 몰랐을 것 같기 때문에 그나마 좀 괜찮은 것 같았다.</p>
<p>첫 대회였지만 상위 7%라는 좋은 성적을 받은 것으로 위로를 하며 대회를 완전히 마무리 하였다.</p>
<p><strong>여러모로 아쉬운 대회였지만 좋은 경험과 좋은 깨달음을 얻었던 것 같다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LG Aimers 3기 Phase 1 온라인 교육 후기]]></title>
            <link>https://velog.io/@pre_f_86/LG-Aimers-3%EA%B8%B0-Phase-1-%EC%98%A8%EB%9D%BC%EC%9D%B8-%EA%B5%90%EC%9C%A1-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@pre_f_86/LG-Aimers-3%EA%B8%B0-Phase-1-%EC%98%A8%EB%9D%BC%EC%9D%B8-%EA%B5%90%EC%9C%A1-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 12 Nov 2023 09:23:31 GMT</pubDate>
            <description><![CDATA[<h1 id="기초지식의-부재">기초지식의 부재</h1>
<p>LG Aimers : <a href="https://www.lgaimers.ai/">LG Aimers 홈페이지</a></p>
<p>※ LG Aimers에 합격했다고 해서 중간에 포기한다고 문제될 것도 없으니 그냥 일단 신청하는게 무조건 좋은 거 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/8f96a744-bd10-452b-b5eb-94aaaf27d115/image.png" alt=""></p>
<h2 id="한달-동안의-온라인-교육">한달 동안의 온라인 교육</h2>
<p>7월 1일 부터 7월 31일 한달동안 온라인 교육을 들었다.</p>
<p>1주동안 두개의 모듈을 들었어야 했는데 각 모듈은 다음과 같은 강의를 하였다.</p>
<blockquote>
</blockquote>
<ol>
<li>AI 윤리 : AI의 기본 윤리에 대한 설명</li>
<li>머신러닝을 위한 수학 : 선형대수 고유값 분해, 특이값 분해 등에 관한 설명</li>
<li>수요예측 : 수요 예측을 왜 하는지, 어떻게 하는지</li>
<li>Bayesian 확률 : 통계 기법</li>
<li>지도학습 : 선형회귀, 분류, Gradients Descent 등에 대한 설명</li>
<li>강화학습 : 강화학습에 대한 설명</li>
<li>시계열 데이터 : 시계열 데이터가 무엇이고 이를 다루는 방법론에 대한 설명</li>
<li>해커톤 설명 : 해커톤에 도움이 될 사전 지식</li>
</ol>
<p>이렇게 봐도 한달 안에 완벽히 하기 힘들 것이라는 것을 알 수 있다.</p>
<p>특히 모듈 2, 4는 수학을 깊게 공부하지 않은 나에게는 너무 어려웠다.</p>
<p>딥러닝에 쓰이는 것은 알겠는데 직관적으로 와닿지는 않았다.</p>
<p>저 강의때문에 멘탈이 나가서 선형 대수 책을 샀는데 제대로 읽어보진 않았다. (읽어야 하는데..)</p>
<blockquote>
<p>확실히 사전 지식이 있으면 온라인 교육을 들을 때 큰 도움이 된다.</p>
</blockquote>
<p>필요한 부분에 대한 추가 강의는 따로 다른 곳에서 가져와 주소를 올려주니 크게 걱정은 안해도 된다.</p>
<h2 id="도움이-된-점">도움이 된 점</h2>
<p>위에서 사전 지식이 필요하다고 하긴 했지만 사전지식 없이도 생각하면서 강의 듣고 복습하다보면 도움은 됐을 것 같다.</p>
<p>그리고 Phase 2의 내용에 대해서 어떤 부분을 신경 쓰며 해커톤을 진행할지 알려줬던 부분이 도움이 많이 됐던 것 같다.</p>
<h3 id="mean-square-errormse-vs-binary-crossentropybce">Mean Square Error(MSE) vs Binary CrossEntropy(BCE)</h3>
<p>※ (수정) 아래 내용은 보면 틀린 내용입니다. MLE에 관한 내용이 추가되어야 합니다.</p>
<p>부끄럽지만 당시 나는 왜 MSE와 BCE를 언제 왜 쓰는지 감으로만 대충 알고 그냥 논문에 나온대로 모델,코드를 구현하며 자기만족을 하고 있었다.(Gray Scale 픽셀값 예측하는데 BCE 쓰기도 했었다.)</p>
<p>이는 당연한게 회귀에 대해서 전혀 모르고 있었기 때문이다.</p>
<p>처음부터 Image Segmentation, Image Classification을 먼저 시작하고 무식하게 머리 박으면서 공부해서 그런 거 같기도 하다.(하필 둘 다 분류문제이다.)</p>
<p>회귀라는 단어를 들을 때마다 머리 속에는 아래같은 생각을 자주 하곤 했다.</p>
<blockquote>
<p>회귀? 그거 정형데이터로 판매량 같은 숫자를 예측하는 거 아니야?</p>
</blockquote>
<p>정말 아무것도 몰랐던 거 같다.</p>
<p>회귀 내용에 대한 강의를 들으며 회귀? 분류? 뭐가 다른 거지 하면서 곱씹어보다가 깨달았다.</p>
<p>이산적인 데이터를 예측하기 위한 것이면 분류, 연속적인 데이터를 예측하기 위한 것은 회귀라고 지금은 생각한다.</p>
<blockquote>
<p>그러면 Heatmap 기반 자세추정도 이산 적인 클래스가 아닌 Heatmap 라벨처럼 값이 연속적으로 나와야 하니까 자세 추정도 회귀라고 할 수 있겠구나.
또 객체 검출에서 Bbox의 크기를 예측하는 것도 연속적인 숫자니까 회귀라고 하는 것이고</p>
</blockquote>
<h3 id="module-8-해커톤-설명">Module 8 해커톤 설명</h3>
<p>강의를 들었다고 해서 결국 온라인 해커톤에서 그걸 바로 적용할 수 있을 거라고는 생각하지 않았다.</p>
<p>그래서 대회를 어떻게 진행해야 하는 거지? 하면서 고민을 진짜 많이 하고 걱정도 많이 됐다.</p>
<p>그래도 다행인 거는 Module 8에서 해커톤에서 신경써야할 부분에 대해서 많이 설명해주었다.</p>
<blockquote>
<p>제품군에 대해서 판매량은 일정할 것이니 이런 점을 유의 하면서 분석을 진행해주세요</p>
</blockquote>
<p>자세히는 기억 안나지만 이런식으로 알려주신다.</p>
<p>그렇게 Module 8을 들으면서 데이터로 주어질 속성이 무엇이 있을까를 미리 생각해보기도 했다.(제품군, 판매금액, 분류 등과 비슷한 언급이 많았고 실제로도 대회 데이터 속성에 있었다.)</p>
<p>또 시계열 데이터 EDA 방법들을 찾아 공부하고 이를 Module 8에서 예상해본 특징들에 어떻게 활용해볼까 고민도 진짜 많이 했던 거 같다.</p>
<p>실제로 온라인 해커톤에서 도움이 된 부분이 있었는데 다른 대회들보다 많이 어려웠던 기억이 있다....</p>
<p><strong>※ 다른 거는 설렁설렁 들어도 마지막 대회 관련 모듈은 꼭 집중해서 들으시면 온라인 해커톤에 도움이 될 거 같습니다.</strong></p>
<h2 id="마무리">마무리</h2>
<p>결국 자기가 스스로 알고자 하는게 중요한 거 같다.</p>
<p>스스로 무엇이 부족한지를 알고 그걸 찾아서 공부하는 것은 어디서든 변하지 않는 정답인 것 같다.</p>
<p>어디든 머리 속에 지식을 강제로 넣어주지는 않으니 진짜 인공지능을 공부하고 싶으면 LG Aimers에 참여해 공부하고 대회를 참여해보는 것도 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Segmentation과 보간법]]></title>
            <link>https://velog.io/@pre_f_86/Segmentation%EC%97%90%EC%84%9C%EC%9D%98-Resize-%EB%AC%B8</link>
            <guid>https://velog.io/@pre_f_86/Segmentation%EC%97%90%EC%84%9C%EC%9D%98-Resize-%EB%AC%B8</guid>
            <pubDate>Thu, 26 Oct 2023 12:17:35 GMT</pubDate>
            <description><![CDATA[<p>Image Segmentation 과정을 진행하면서 Mask 파일을 Resize를 할 때 주의해야할 점이 있다.</p>
<p>바로 보간법에 따른 결과 차이이다.</p>
<p>우선 보간법이란 무엇인가?</p>
<h2 id="보간interpolation이란">보간(Interpolation)이란?</h2>
<blockquote>
<p>보간은 두 점을 사이의 값을 어떠한 값으로 채워 연결하는 방법이다. 이미지에서 두 점은 두 픽셀을 의미한다.</p>
</blockquote>
<p>이미지의 크기를 변환하는 과정에서 보간이 자주 쓰인다.</p>
<p>가령 $3 \times 3$의 해상도를 갖는 이미지를 $5\times 5$ 크기의 이미지로 키운다고 해보자</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c88abeda-13fd-4e47-a87c-fb3f562833ba/image.png" alt=""></p>
<blockquote>
<p>이미지의 빈 공간을 어떻게 채울까?</p>
</blockquote>
<p>이미지의 빈 공간을 채우기 위해서 결국 보간법이 사용된다.</p>
<p>대표적으로 사용하는 이미지 보간법에는 두가지가 있다.</p>
<h3 id="이웃보간법neighborhood-interpolation">이웃보간법(Neighborhood Interpolation)</h3>
<p>빈 공간을 이웃한 값으로 채우는 방법으로 가장 간단한 방법이다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/34381eb3-4f10-437d-88c2-62a35a63cbdc/image.png" alt=""></p>
<h3 id="양선형보간법bi-linear-interpolation">양선형보간법(Bi-Linear Interpolation)</h3>
<p>두 점 사이의 빈 공간을 각 거리간 비율로 계산하는 방법이다.</p>
<p>가령 두 점의 값이 x, y이며 두 점 사이를 양선형 보간법으로 채우는 경우 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/9a0965cc-d39b-48f5-a79d-6830d3c79f59/image.png" alt=""></p>
<p>이를 활용해 이미지에 적용하면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/c4178755-8c86-4e03-b880-30b1c9d04569/image.png" alt=""></p>
<h2 id="mask-resize에서-문제점">Mask Resize에서 문제점</h2>
<p>Segmentation Mask 파일에서 각 픽셀의 값은 클래스의 값을 의미한다.</p>
<p>가령 {0: 배경, 1: 강아지, 2: 비행기, 3: 고양이}의 클래스 값을 가진다고 정의해보고 이를 이용해 가상의 Segmentation Mask를 만들면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3d207a2c-3e03-47d8-b425-52b19675d147/image.png" alt=""></p>
<p>이 Segmentation Mask에 위 두 보간법을 적용한 결과를 보이고 문제점을 보면 다음과 같다.</p>
<h3 id="이웃-보간법">이웃 보간법</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/52f52c98-cf93-484c-9c73-2798a50076b9/image.png" alt=""></p>
<p>무난한 보간법이지만 주변의 픽셀들을 고려하지 않는다는 점이 있다.</p>
<h3 id="양선형-보간법">양선형 보간법</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/a6ce7dfb-a0c5-40bb-9206-6130f164ccb0/image.png" alt=""></p>
<p>주변픽셀들을 고려하여 보간을 하지만 적절하지 않다.</p>
<p>그 이유는 클래스 번호 사이의 관계 때문이다. </p>
<p>0번과 2번 사이에서 양선형 보간을 통해 1을 얻어냈다고 하지만 0(배경)과 2(비행기)사이의 평균이 1(강아지)를 의미하지 않는다.</p>
<p>특히 양선형 보간법의 특성상 정수 값이 나오지 않게 되는데 이는 클래스 번호의 의미와도 맞지 않는다.</p>
<p>이러한 특성 때문에 Mask를 Resize 해주기 위해서는 보간법을 주의하며 해야한다.</p>
<p>그렇다면 만약 양선형보간법을 쓰고 싶다면 어떻게 해야할까?</p>
<h2 id="mask에서의-양선형보간법">Mask에서의 양선형보간법</h2>
<p>Mask에서 양선형보간법을 사용하기 위해서는 One-Hot Encoding과 argmax를 활용하면 된다.</p>
<blockquote>
<p><strong>One-Hot Encoding이란?</strong>
클래스의 갯수에 맞게 픽셀의 공간을 확장시켜 클래스 번호에 맞는 공간에는 1 나머지 공간에는 0을 통해 클래스를 인식시키는 방법</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/ca8a2bf7-1b34-4122-8b23-4c5e6b9571e6/image.png" alt=""></p>
<blockquote>
<p><strong>Argmax란?</strong>
특정 축으로 볼 때 제일 큰 값의 인덱스를 리턴하는 것</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3745ce79-be87-4fcf-8306-09cfd0ab659c/image.png" alt=""></p>
<p>양선형 보간법을 적용하는 과정은 다음과 같다.</p>
<ol>
<li>One-Hot Encoding을 적용한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/3a61e983-784f-41c9-949c-7d3a1d7370ad/image.png" alt=""></p>
<ol start="2">
<li>Resize를 진행한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/262216ad-d056-4d8e-b3f3-06a38a99879a/image.png" alt=""></p>
<ol start="3">
<li>Argmax를 적용한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/0e6b5317-7a09-497d-87a8-b58155a022ba/image.png" alt=""></p>
<h3 id="결과-비교">결과 비교</h3>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/2a4b4b41-5a7b-480f-8b97-f3953f9ae8d2/image.png" alt=""></p>
<h2 id="결론">결론</h2>
<p>이웃 보간법도 충분히 쓸만하지만 주변 픽셀의 관계를 고려하여 Resize를 하고 싶은 경우에는 One-Hot Encoding과 Argmax를 같이 사용하는 것이 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[딥러닝에서 Bottleneck에 대한 실험]]></title>
            <link>https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%97%90%EC%84%9C-Bottleneck%EC%97%90-%EB%8C%80%ED%95%9C-%EC%8B%A4%ED%97%98</link>
            <guid>https://velog.io/@pre_f_86/%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%97%90%EC%84%9C-Bottleneck%EC%97%90-%EB%8C%80%ED%95%9C-%EC%8B%A4%ED%97%98</guid>
            <pubDate>Fri, 04 Aug 2023 18:43:28 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p>딥러닝에서 ResNet, MobileNet 등 여러 모델에서 Bottleneck 구조를 사용합니다.</p>
<blockquote>
<p>왜 Bottleneck 구조를 사용할까?</p>
</blockquote>
<p>위에 대한 질문에서 Bottleneck은 다음과 같은 역할을 한다고 생각합니다.</p>
<ol>
<li><p>채널을 줄이며 이후 이어질 레이어에 대한 연산량 감소</p>
</li>
<li><p>비선형성 증가(추가된 레이어에 비선형 함수가 추가되는 경우)</p>
</li>
<li><p>좁아진 채널에 의한 중요한 특징 추출 </p>
</li>
<li><p>중복을 줄인 효율적인 특징 추출</p>
</li>
</ol>
<p>1번과 2번은 너무도 잘 알려진 사실이고 쉽게 이해할 수 있지만 3번과 4번은 자주 들어본 말은 아닐 것입니다.</p>
<p>이번 글은 이 3번과 4번을 시각화 할 수 있도록 PyTorch를 통해 간단한 실험을 할 예정입니다.</p>
<h2 id="근거">근거</h2>
<p>3번 4번에 대한 생각을 가진 근거는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/5ae6bf29-1723-4701-a6bc-87d17a77f49c/image.PNG" alt=""></p>
<p>위와 같은 레이어 이외의 다른 레이어는 없다고 가정합니다.</p>
<p>처음에는 8개의 특징들이 있지만 이 특징들을 4개의 특징으로 압축해야하고 몇몇 특징들을 적게 가져오거나 버려야 하기 때문에 정보의 손실이 발생합니다.</p>
<p>모델은 주어진 데이터에 맞는 최적의 값으로 각 가중치를 학습시켜 특징들을 추출할 것입니다.</p>
<p>즉, 정보를 버릴 수 밖에 없는 상황이라면 모델은 주어진 데이터를 잘 설명할 수 있는 특징(최적의 특징)들만 최대한 남기려고 할 것입니다.</p>
<p>이러한 근거로 3번에 대한 생각으로 이어졌습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/203bd316-b097-40eb-b02b-e8cca7d22ce6/image.PNG" alt=""></p>
<p>반대로 위와 같이 채널의 수가 많아지는 상황이라고 한다면 기존의 특징들을 조합해 더 많은 특징을 추출할 수 있게 됩니다.</p>
<p>채널의 수가 많아지면 더 많은 특징을 추출할 수 있어서 좋을 수는 있으나 연산량이나 메모리 관점에서 비효율적일 수 있습니다.</p>
<p>특히 블록 내의 두 특징이 가중치가 비슷한 경우가 발생할 수 있고 이를 중복된 특징이라고 합니다.</p>
<p>중복된 특징이 많아진다는 것은 결국 비효율적으로 특징을 추출했다는 것이라고 생각합니다.</p>
<p>이러한 근거로 4번에 대한 생각으로 이어졌습니다.</p>
<h2 id="실험-환경">실험 환경</h2>
<p>3번 4번 내용에 대한 간단한 실험을 할 예정입니다.</p>
<p>실험을 할 모델 구조는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/pre_f_86/post/d7cf7056-da98-40b0-a489-e845f8877d9e/image.PNG" alt=""></p>
<p>간단한 숫자를 입력으로 하여 그대로 출력하도록 하는 매우 간단한 모델입니다.</p>
<p>모델 학습에 대한 데이터와 Hyper Paramater는 다음과 같습니다.</p>
<ul>
<li>입력 데이터 : [0, 99] 범위의 정수</li>
<li>출력 데이터 : [0, 99] 범위의 정수</li>
<li>Optimizer : Adam</li>
<li>Lerning Rate : 1e-3</li>
<li>Epoch : 100</li>
<li>Loss : Mean Square Error</li>
</ul>
<p>실험 과정은 다음과 같습니다.</p>
<ol>
<li><p>초기 모델의 각 레이어(입력,레이어1,레이어2,출력)의 채널 수를(1,32,32,1)로 지정 하고 위의 설정으로 학습합니다.</p>
</li>
<li><p>레이어 1에 대한 변경 없이 레이어 2의 채널 수를 각각 8(Bottleneck)와 128(Non Bottleneck)으로 설정하여 주어진 설정으로 다시 학습합니다.</p>
</li>
<li><p>레이어 2에서 서로다른 두 가중치의 차이를 구하여 차이가 제일 작은 두개의 가중치를 출력한다.</p>
</li>
<li><p>3의 결과를 통해 중복된 가중치를 확인함과 동시에 최종 성능에 큰 차이가 있는지 확인한다.</p>
</li>
</ol>
<h2 id="실험코드">실험코드</h2>
<h3 id="1-모델-초기화-데이터-초기화">1. 모델 초기화, 데이터 초기화</h3>
<pre><code class="language-python"># 라이브러리
import torch
import torch.nn as nn
import numpy as np
import random

# 데이터 초기화
sz = 100
input = torch.tensor(list(range(sz)),dtype=torch.float32).reshape(sz,1)
output = torch.tensor(list(range(sz)),dtype=torch.float32).reshape(sz,1)

original_channels = 32
# 모델 작성
class mymodel(nn.Module):
    def __init__(self):
        super(mymodel,self).__init__()
        self.linear = nn.Linear(1,original_channels)
        self.linear2 = nn.Linear(original_channels,original_channels)
        self.out = nn.Linear(original_channels,1)

    def forward(self,x):
        out1 = self.linear(x)
        out2 = self.linear2(out1)
        out3 = self.out(out2)

        return out1,out2,out3
</code></pre>
<h3 id="2-모델-학습-함수-작성">2. 모델 학습 함수 작성</h3>
<pre><code class="language-python">
def train(epoch,bottleneck):
    #Linear2 파라미터 초기화 Randomness 제거

    model.linear2 = nn.Linear(original_channels,bottleneck)
    model.out = nn.Linear(bottleneck,1)

    optim = torch.optim.Adam(model.parameters(),1e-3)
    for i in range(epoch):
        optim.zero_grad()

        out1,out2,out3 = model(input)

        criterion = nn.MSELoss()

        loss = criterion(out3,output)

        loss.backward()

        optim.step()

    #마지막 학습 전 loss 출력
    print(loss)

    with torch.no_grad():
        #비슷한 가중치 찾아내서 출력
        min = 1000000
        mini = 0
        minj = 0

        for i in range(bottleneck):
            for j in range(i+1,bottleneck):
                sub = torch.sum(torch.abs(model.linear2.weight[i]-model.linear2.weight[j]))/original_channels
                if (sub&lt;=min):
                    mini=i
                    minj=j
                    min = sub

        print(model.linear2.weight[mini])
        print(model.linear2.weight[minj])
        print(min)</code></pre>
<h3 id="3-실험-진행-코드">3. 실험 진행 코드</h3>
<pre><code class="language-python">
compare_channel_list = [original_channels//4,original_channels,original_channels*4]
epoch = 100
for bottleneck in compare_channel_list:
    #각 실험별 Seed 고정
    seed = 10000
    deterministic = True
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    if deterministic:
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False

    # 모델 선언
    model = mymodel()

    # Unfreeze
    for i in model.parameters():
        i.requires_grad=True

    model = mymodel()
    # Bottleneck 학습
    print(f&quot;\nBottleneck : {bottleneck}&quot;)
    train(epoch,bottleneck=bottleneck)
    print()</code></pre>
<h2 id="결과">결과</h2>
<p>모든 코드 실행시 출력은 아래에 나와있습니다.</p>
<p>최종적인 결론은 다음과 같습니다.</p>
<ul>
<li><p>본 실험 그대로 할 경우 Loss의 차이는 크게 없는 것으로 보이며 비슷한 두 가중치의 유사도가 Bottleneck &gt; Original &gt; Non Bottleneck임을 확인할 수 있습니다.</p>
</li>
<li><p>데이터가 적고 간단하기에 약간의 Hyper Paramater 변화에도 성능에 큰 차이를 일으킨다는 점이 있습니다.</p>
</li>
<li><p>여러 Hyper Paramater로 실험하더라도 유사도에 대한 결과는 항상 비슷하게 나왔습니다.</p>
</li>
</ul>
<h3 id="original">Original</h3>
<pre><code>Bottleneck : 32
tensor(0.0327, grad_fn=&lt;MseLossBackward0&gt;)
tensor([-0.1005, -0.0892,  0.0406,  0.1634, -0.0930, -0.0646, -0.0296,  0.0730,
         0.0956, -0.0660,  0.0227, -0.1237, -0.0440, -0.0545, -0.1033, -0.0948,
        -0.0555, -0.0421,  0.0174,  0.0411, -0.0317, -0.1123, -0.0312, -0.0024,
         0.0306,  0.0440, -0.1417,  0.0863, -0.1388,  0.0147,  0.0610,  0.1370],
       requires_grad=True)
tensor([ 0.0328,  0.0493, -0.1636, -0.0456, -0.0965, -0.0486, -0.0233,  0.0164,
         0.0632, -0.0133,  0.1040, -0.0665, -0.0449, -0.1542, -0.0833,  0.1915,
        -0.0613,  0.1244, -0.0450,  0.0476, -0.1852, -0.1056,  0.1859, -0.0685,
        -0.1257,  0.0430, -0.0490,  0.0883, -0.0847, -0.0327,  0.0477,  0.0384],
       requires_grad=True)
tensor(0.0796)</code></pre><h3 id="bottleneck">Bottleneck</h3>
<pre><code>
Bottleneck : 8
tensor(0.0354, grad_fn=&lt;MseLossBackward0&gt;)
tensor([-0.0092, -0.0476, -0.0979,  0.0840,  0.0314,  0.0732, -0.0192, -0.0119,
         0.1228,  0.1942,  0.1335,  0.1377,  0.0784, -0.0794, -0.1055,  0.0812,
         0.2063, -0.0891,  0.0041,  0.1664,  0.0559,  0.0270, -0.1117, -0.1162,
         0.0452, -0.0664,  0.0555,  0.0897, -0.1405,  0.1602,  0.1077, -0.2005],
       requires_grad=True)
tensor([ 0.0025,  0.1422,  0.1027,  0.0229, -0.0285,  0.1808, -0.1276,  0.0699,
         0.1532,  0.1905, -0.0721,  0.1933, -0.1822,  0.0881, -0.1045,  0.0837,
         0.0391, -0.0030,  0.0688, -0.0794, -0.0162,  0.0293, -0.1803, -0.1033,
        -0.2029, -0.0846,  0.1163, -0.0197, -0.1709,  0.1865,  0.0993, -0.1084],
       requires_grad=True)
tensor(0.0894)
</code></pre><h3 id="non-bottleneck">Non Bottleneck</h3>
<pre><code>Bottleneck : 128
tensor(0.0337, grad_fn=&lt;MseLossBackward0&gt;)
tensor([ 0.0780, -0.1490, -0.1028, -0.0582,  0.0137,  0.1581,  0.0408, -0.1516,
         0.0989,  0.1572, -0.0914, -0.0469,  0.1070,  0.1179, -0.0554, -0.1081,
         0.0397, -0.0492, -0.0518, -0.0335,  0.0180,  0.0886, -0.0080,  0.0429,
         0.0058,  0.0606,  0.0514, -0.1043,  0.1415, -0.1502, -0.1582, -0.0168],
       requires_grad=True)
tensor([ 0.1208, -0.0554, -0.1297, -0.0882, -0.0025,  0.1689, -0.0483, -0.1753,
         0.0915, -0.0550, -0.0595, -0.1108, -0.0713,  0.1125, -0.0311, -0.1130,
        -0.0325, -0.0082, -0.0183, -0.1631,  0.0311,  0.0136,  0.1174, -0.0964,
         0.0824,  0.1570,  0.0624, -0.1177,  0.1512, -0.1024,  0.1574, -0.0709],
       requires_grad=True)
tensor(0.0661)</code></pre><hr>
<p>참고</p>
<ul>
<li><p><a href="https://www.baeldung.com/cs/neural-network-bottleneck">https://www.baeldung.com/cs/neural-network-bottleneck</a></p>
</li>
<li><p><a href="https://ai.stackexchange.com/questions/4864/what-are-bottlenecks-in-neural-networks">https://ai.stackexchange.com/questions/4864/what-are-bottlenecks-in-neural-networks</a></p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>