<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>BanYeah's velog</title>
        <link>https://velog.io/</link>
        <description>𝚂𝙺𝙺𝚄 𝙲𝚂𝙴 𝟸𝟹</description>
        <lastBuildDate>Sat, 05 Jul 2025 14:22:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>BanYeah's velog</title>
            <url>https://velog.velcdn.com/images/banyeah_/profile/a3fa83d1-687b-4bbd-a92d-02c160c09d60/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. BanYeah's velog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/banyeah_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[솦토링 2주차] WSL 설치 및  Linux 명령어, Vim 편집기, Makefile]]></title>
            <link>https://velog.io/@banyeah_/%EC%86%A6%ED%86%A0%EB%A7%81-2%EC%A3%BC%EC%B0%A8-WSL-%EC%84%A4%EC%B9%98-%EB%B0%8F-Linux-%EB%AA%85%EB%A0%B9%EC%96%B4-Vim-%ED%8E%B8%EC%A7%91%EA%B8%B0</link>
            <guid>https://velog.io/@banyeah_/%EC%86%A6%ED%86%A0%EB%A7%81-2%EC%A3%BC%EC%B0%A8-WSL-%EC%84%A4%EC%B9%98-%EB%B0%8F-Linux-%EB%AA%85%EB%A0%B9%EC%96%B4-Vim-%ED%8E%B8%EC%A7%91%EA%B8%B0</guid>
            <pubDate>Sat, 05 Jul 2025 14:22:58 GMT</pubDate>
            <description><![CDATA[<p>지난 회차에 이어 교내 멘토링 프로그램을 위해 정리한 내용을 블로그 포스트로도 작성하려고 한다. <del>사실 멘토링은 진작에 끝났지만, 미루고 미루다가 이제야...</del></p>
<p>또한, 해당 내용 말고도 자료구조와 C++ 클래스와 관련된 내용도 멘토링을 진행했었지만, 더 좋은 포스트들이 많을 듯해서 이 포스트를 마지막으로 하려고 한다.
<br></p>
<h2 id="1-wsl-설치">1. WSL 설치</h2>
<p>Windows에서 Linux 개발환경을 쉽게 만들고, Linux 명령어와 도구를 사용하기 위해 WSL로 Ubuntu(Linux 배포판)를 설치한다.  또한, Linux 환경에서는 복잡하게 MingGW 설치없이 C/C++ 컴파일을 진행할 수 있다.
<br></p>
<p>🔗<a href="https://learn.microsoft.com/ko-kr/windows/wsl/install"> <strong>WSL 설치 메뉴얼</strong></a></p>
<p>먼저 WSL을 설치하기 전에 <code>Linux용 Windows 하위 시스템</code>을 활성화해야 한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/59ddb7ae-2088-44e7-8bf8-f8362b955fc5/image.png" alt=""></p>
<p>Windows 기능 켜기/끄기에 들어가서 활성화한다.</p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/447f043a-e33c-47ed-be73-c3b1697587d2/image.png" alt="">
<br></p>
<p>명령 프롬프트를 관리자 권한으로 실행하고 아래 명령어 입력해, WSL을 설치한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/92ea3e3c-4f63-49a7-a99b-f125de5fd1c1/image.png" alt=""></p>
<pre><code class="language-bash">wsl --install</code></pre>
<p>계정의 비밀번호를 입력할 때는 화면에 글씨가 표시되지 않는 것이 정상이니 당황하지 않도록 하자.
<br>
<br></p>
<p>Microsoft Store에서 Ubuntu를 검색해, Ubuntu 터미널 환경을 설치하고 실행한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/58665f47-2c08-4ea5-8ea8-1c82360bb6ff/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/e7d92825-986f-4895-b5bd-50619cebcdd6/image.png" alt="">
<br></p>
<p>파일탐색기에서는 <code>Linux &gt; Ubuntu &gt; home &gt; (사용자명)</code>으로 접근할 수 있다. 파일탐색기에서 편하게 파일 이동 및 복붙이 가능하다.
<img src="https://velog.velcdn.com/images/banyeah_/post/4244ff5f-d055-402b-aa5a-2a71819df394/image.png" alt="">
<br></p>
<h3 id="vscode에서-wsl-원격-연결">VSCode에서 WSL 원격 연결</h3>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/aad1a33b-14da-4a00-ad68-9d0e396851ab/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/30f14fb3-4a88-43e2-b9e5-9c038fefa1c5/image.png" alt="">
<br></p>
<hr>
<h2 id="2-linux-명령어">2. Linux 명령어</h2>
<blockquote>
<p>Linux는 GUI(Graphical User Interface) 환경이 아닌 터미널 환경에서 사용하게 되는 경우가 훨씬 많다.
WSL에서는 파일 탐색기를 통해 아래 작업들을 수행할 수도 있지만, SSH를 이용해 원격 서버에 접속할 경우에는 이런 기능을 사용할 수 없다. 따라서 기본적인 Linux 명령어들을 익혀보자.</p>
</blockquote>
<br>

<table>
<thead>
<tr>
<th>주요 명령어</th>
<th>설명</th>
<th>부가 설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>ls</code></td>
<td>현재/작업 디렉터리의 내용을 나열한다.</td>
<td><code>ls -a</code> 처럼 <code>-a</code> 옵션을 사용하면, <code>.</code>으로 시작하는 숨김 파일과 디렉터리도 함께 표시된다. <br> <code>ls -l</code> 처럼 <code>-l</code> 옵션을 사용하면, 파일의 권한, 소유자, 크기 등의 상세 정보를 긴 형식으로 확인할 수 있다. <br> <code>ls -al</code> 로 두 옵션을 모두 적용할 수 있다.</td>
</tr>
<tr>
<td><code>pwd</code></td>
<td>현재/작업 디렉터리의 경로를 절대 경로 형식으로 출력한다.</td>
<td>절대 경로는 <code>/</code>(루트 디렉터리)부터 시작하는 전체 경로이며, <br> 상대 경로는 <code>./</code>(현재 디렉터리), <code>../</code>(부모 디렉터리)를 기준으로 하는 경로이다.</td>
</tr>
<tr>
<td><code>cd [경로]</code></td>
<td>셸의 작업 디렉터리를 변경한다.</td>
<td></td>
</tr>
<tr>
<td><code>touch [파일명]</code></td>
<td>파일의 접근 시간과 수정 시간을 현재 시각으로 업데이트하며, 만약 지정한 파일이 존재하지 않으면 빈 파일을 새로 생성한다.</td>
<td></td>
</tr>
<tr>
<td><code>mkdir [디렉터리명]</code></td>
<td>새 디렉터리를 생성한다.</td>
<td></td>
</tr>
<tr>
<td><code>echo [문자열]</code></td>
<td>문자열이나 변수 값을 출력한다.</td>
<td><code>echo -e [문자열]</code> 처럼 <code>-e</code> 옵션을 사용하면, 이스케이프 문자를 해석하여 출력한다.</td>
</tr>
<tr>
<td><code>cat [파일명]</code></td>
<td>파일의 내용을 연결하여 표준 출력에 출력한다.</td>
<td></td>
</tr>
<tr>
<td><code>mv [src] [dest]</code></td>
<td>파일을 이동하거나 이름을 변경한다. 즉, <code>[src]</code> 파일을 <code>[dest]</code> 로 이동시킨다.</td>
<td></td>
</tr>
<tr>
<td><code>cp [src] [dest]</code></td>
<td>파일과 디렉터리를 복사한다. 즉, <code>[src]</code> 파일/디렉터리를 <code>[dest]</code> 로 복사한다.</td>
<td><code>cp -r [src] [dest]</code> 처럼 <code>-r</code> 옵션을 사용하면, 재귀적으로 디렉터리 내 모든 파일을 복사한다.</td>
</tr>
<tr>
<td><code>rm [파일명]</code></td>
<td>파일 또는 디렉터리를 삭제한다.</td>
<td><code>rm -r [파일명]</code>처럼 <code>-r</code> 옵션을 사용하면, 재귀적으로 디렉터리 내 모든 파일을 제거한다. <br> <code>rm -f [파일명]</code>처럼 <code>-f</code> 옵션을 사용하면, 파일을 강제로 삭제한다. <br> ※ 주의: <code>sudo rm -rf /</code> 명령어는 관리자 권한으로(<code>sudo</code>) 루트 디렉터리(<code>/</code>)를 강제로 재귀적으로 삭제한다(<code>rm -rf</code>), 즉 시스템 전체를 삭제한다는 의미로, <strong>절대로 실행해서는 안된다</strong>.</td>
</tr>
<tr>
<td><code>chmod +x [파일명]</code></td>
<td>파일에 실행 권한을 부여한다.</td>
<td></td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h2 id="3-vim-편집기">3. Vim 편집기</h2>
<blockquote>
<p>Linux는 터미널 환경에서 작업하게 되는 경우가 많다. Vim은 이러한 터미널 환경에서 사용하는 코드 편집기이다. <del>본인은 Vim이 싫어서 매번 VSCode 쓰고 있긴 하지만…</del></p>
</blockquote>
<br>
Ubuntu 터미널을 열어 아래 명령어를 실행해 Vim을 설치한다.

<pre><code class="language-bash">sudo apt update
sudo apt install vim</code></pre>
<p><code>vim --version</code> 명령어로 정상적으로 설치가 완료되었는지 확인한다.
<br></p>
<p>아래 명령어로 파일을 Vim 편집기로 열 수 있다.</p>
<pre><code class="language-bash">vim [파일명, 예) main.c]</code></pre>
<p>*파일이 없는 경우에는 자동으로 파일을 생성한 뒤에 Vim 편집기로 연다.
<br>
<br></p>
<p>Vim 편집기는 크게 두 가지 모드, <strong>명령어 모드</strong>와 <strong>입력 모드</strong>로 나뉘어진다. 기본 상태는 명령어 모드이며, <code>i</code>키를 눌러 입력 모드에 진입할 수 있다. 반대로 명령어 모드로 돌아가려면 <code>ESC</code> 키를 누른다.
<br></p>
<h3 id="vim-명령어">Vim 명령어</h3>
<p>명령어 모드에서 사용하는 주요 명령어를 소개하겠다.</p>
<table>
<thead>
<tr>
<th>주요 명령어</th>
<th>설명</th>
<th>부가 설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>:w</code></td>
<td>파일을 저장한다.</td>
<td></td>
</tr>
<tr>
<td><code>:q</code></td>
<td>Vim 편집기를 종료한다. 이때, 수정한 내용이 있지만 저장하지 않은 상태면, 경고 메세지를 출력하고 종료되지 않는다.</td>
<td><code>:q!</code> 로 강제로 종료할 수 있다. <br> <code>:wq</code> 로 저장하고 종료할 수 있다.</td>
</tr>
<tr>
<td><code>:[줄 번호]</code></td>
<td>해당 줄로 커서가 이동한다.</td>
<td></td>
</tr>
<tr>
<td><code>:d</code></td>
<td>현재 커서가 위치한 줄을 삭제한다.</td>
<td><code>:d[삭제할 줄 수]</code>로 현재 줄 포함 <code>[삭제할 줄 수]</code> 만큼 삭제할 수 있다.</td>
</tr>
<tr>
<td><code>u</code></td>
<td>실행을 취소한다. (Undo)</td>
<td></td>
</tr>
<tr>
<td><code>.</code></td>
<td>마지막 명령을 반복한다.</td>
<td></td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p>간단한 실습을 위해, <code>main.c</code>에 아래 코드를 작성하고 저장한다.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main() {
    // ANSI 색 코드 배열 (빨 주 노 초 파 남 보)
    const char* colors[] = {
        &quot;\033[31m&quot;, // 빨강
        &quot;\033[33m&quot;, // 주황
        &quot;\033[93m&quot;, // 노랑 (밝은 노랑)
        &quot;\033[32m&quot;, // 초록
        &quot;\033[34m&quot;, // 파랑
        &quot;\033[36m&quot;, // 남색 (청록)
        &quot;\033[35m&quot;  // 보라
    };

    for (int i = 0; i &lt; 7; i++) {
        printf(&quot;%s★ &quot;, colors[i]);
    }
    printf(&quot;\033[0m\n&quot;);

    return 0;
}</code></pre>
<br>

<p>작성한 C언어 소스코드를 컴파일하기 위해, 아래 명령어를 실행해 GCC를 설치한다.</p>
<pre><code class="language-bash">sudo apt update
sudo apt install build-essential</code></pre>
<p>이후, <code>gcc --version</code>명령어를 실행하여 제대로 설치가 완료되었는지 확인한다.
<br></p>
<p><code>gcc -o main main.c</code> 로  컴파일을 진행하며, <code>./main</code> 으로 실행 파일을 실행한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/84eab20d-aa84-4157-abd6-9544a55cd746/image.png" alt=""></p>
<p>*<code>gcc</code>는 C언어의 컴파일러이며, <code>-o [파일명]</code> 옵션을 사용하여 컴파일 결과로 생성될 실행 파일 이름을 지정할 수 있다.
<br></p>
<p>🔗 <a href="https://velog.io/@banyeah_/Vim-%EC%93%B0%EA%B8%B0-%EC%8B%AB%EC%9D%80%EB%8D%B0">(선택) Vim Plug-in 설치</a>
<br></p>
<hr>
<h2 id="4-makefile">4. Makefile</h2>
<p>Makefile은 여러 소스 파일 간의 의존 관계를 정의하여, <code>make</code>라는 단 하나의 명령어만으로 소스 코드 전체를 간단하게 컴파일할 수 있게 해준다. 또한, 변경된 파일만을 다시 컴파일함으로써 빌드 시간을 단축시킬 수 있다. </p>
<p>Makefile은 C/C++ 프로젝트에서 널리 사용되며, <code>gcc</code>, <code>g++</code>, <code>ld</code> 명령어를 반복해서 직접 입력하지 않아도 되기 때문에 자동화된 빌드 관리 도구로 유용하다.
<br>
<br></p>
<p>이번에도 간단한 실습을 위해, <code>func.h</code>, <code>func.c</code>, <code>main.c</code> 파일을 생성한다.</p>
<p><code>func.h</code> 파일의 내용:
(<code>func.c</code>에서 정의된 함수들을 다른 파일에서 사용할 수 있도록 선언해 놓은 헤더 파일)</p>
<pre><code class="language-c">void printHello();
void printName(char *name);
void printIntro(int age, char *region);
void printThx();</code></pre>
<br>

<p><code>func.c</code> 파일의 내용</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &quot;func.h&quot;

void printHello()
{
    printf(&quot;안녕하세요!\n&quot;);
    printf(&quot;만나서 반갑습니다~\n&quot;);
}

void printName(char *name)
{
    printf(&quot;제 이름은 %s입니다.\n&quot;, name);
}

void printIntro(int age, char *region)
{
    printf(&quot;저는 %d살이고, %s 지역에서 살다가 왔습니다.\n&quot;, age, region);
}

void printThx()
{
    printf(&quot;잘 부탁드립니다!!!\n&quot;);
}</code></pre>
<br>

<p><code>main.c</code> 파일의 내용</p>
<pre><code class="language-c">#include &quot;func.h&quot;

int main()
{
    char name[] = &quot;홍길동&quot;;
    int age = 20;
    char region[] = &quot;동에 번쩍 서에 번쩍&quot;;

    printHello();
    printName(name);
    printIntro(age, region);
    printThx();

    return 0;
}</code></pre>
<br>

<h3 id="c언어의-컴파일-진행-과정">C언어의 컴파일 진행 과정</h3>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/552b8080-b5c7-4312-9117-3e9d78eba0a8/image.jpg" alt=""></p>
<ol>
<li><p><strong>전처리 (Preprocessing)</strong></p>
<ul>
<li><p><code>#include</code>, <code>#define</code>, <code>#ifdef</code> 같은 전처리 지시문을 처리한다.</p>
</li>
<li><p>헤더 파일(<code>&lt;stdio.h&gt;</code>)을 포함하며, 매크로 치환을 진행한다.</p>
<ul>
<li><code>#include &lt;stdio.h&gt;</code> 에 <code>printf</code> 함수의 선언이 존재하며 이를 바탕으로 컴파일 단계에서 문법 검사를 진행한다.</li>
</ul>
</li>
<li><p><code>.c</code> 파일을 전처리한 결과로 <code>.i</code> 파일이 생성된다.</p>
</li>
</ul>
</li>
<li><p><strong>컴파일 (Compilation)</strong></p>
<ul>
<li><p><code>.i</code> 파일을 어셈블리 코드(<code>.s</code>)로 변환한다.</p>
</li>
<li><p>문법 검사와 중간 코드를 생성하는 것을 포함한다.</p>
</li>
</ul>
</li>
<li><p><strong>어셈블 (Assembly)</strong></p>
<ul>
<li>어셈블리 코드(<code>.s</code>)를 기계어(<code>.o,</code> 오브젝트 파일)로 번역한다.</li>
</ul>
</li>
<li><p><strong>링크 (Linking)</strong></p>
<ul>
<li><p>여러 <code>.o</code> 파일 또는 라이브러리(<code>.a</code>/<code>.so</code>)를 결합한다.</p>
<ul>
<li>링커가 <code>printf</code>의 정의를 glibc(예: <code>/usr/lib/.../libc.so</code>)에서 찾아 연결한다.</li>
</ul>
</li>
<li><p>프로그램의 시작 주소(entry point)를 확인하고 그 위치를 실행 파일에 기록하기 위해, main 함수의 위치를 확인한다.</p>
</li>
</ul>
</li>
</ol>
<br>

<h3 id="makefile-작성">Makefile 작성</h3>
<p>소스 코드가 고작 3개 정도일 경우, <code>gcc -o program main.c func.c</code> 와 같은 명령어를 통해 간단히 컴파일해도 크게 문제가 없다.</p>
<p>그러나 컴파일은 많은 자원을 소모하는 작업이며, 소스 코드가 수십 개에 달하는 경우, 단일 명령어로 모든 파일을 매번 다시 컴파일하는 것은 매우 비효율적이다.  특히 수정되지 않은 파일까지 매번 다시 컴파일하게 되면 컴파일 시간이 불필요하게 증가하게 된다.</p>
<p>이때 Makefile로, 변경된 파일만을 골라서 컴파일할 수 있어 전체 컴파일 시간을 크게 줄일 수 있다.
<br></p>
<p><strong>Makefile 기본 문법</strong></p>
<pre><code class="language-makefile">&lt;target&gt;: &lt;dependencies&gt;
    &lt;recipe&gt;</code></pre>
<ul>
<li><code>target</code><ul>
<li>생성할 파일의 이름 (예: <code>main.o</code>, <code>program</code>)이거나,</li>
<li>특정 동작의 이름 (예: <code>clean</code>, <code>all</code>)일 수 있다.</li>
<li><code>make &lt;target&gt;</code> 명령을 실행하면 이 <code>target</code>에 해당하는 작업이 수행된다.</li>
</ul>
</li>
<li><code>dependencies</code><ul>
<li><code>target</code>을 만들기 위해 필요한 파일 목록(의존성)이다.</li>
<li>의존성에 포함된 파일 중 하나라도 수정되면, 해당 <code>target</code>에 해당하는 작업이 다시 수행된다.</li>
</ul>
</li>
<li><code>recipe</code><ul>
<li>실제로 실행할 Shell 명령어들이다. (예: <code>gcc</code>, <code>mv</code>, <code>rm</code> 등)</li>
<li>반드시 들여쓰기를 해야하며, 이 들여쓰기는 반드시 공백이 아닌 <code>Tab</code>문자여야 한다.<br>

</li>
</ul>
</li>
</ul>
<p><strong>Makefile 변수</strong></p>
<pre><code class="language-makefile">TARGET = program
CXX = gcc

$(TARGET): main.c func.c func.h
    $(CXX) -o $(TARGET) main.c func.c</code></pre>
<p>반복적으로 사용되는 값을 하나의 이름으로 정의하여, 유지보수와 가독성을 높일 수 있도록 도와준다.</p>
<p>변수는 <code>변수이름 = 값</code> 형태로 정의하며, 사용할 때는 <code>$(변수이름)</code> 형태로 참조한다.
<br></p>
<p><strong>Makefile 자동 변수</strong></p>
<pre><code class="language-makefile">TARGET = program
CXX = gcc

$(TARGET): main.o func.o
    $(CXX) -o $@ $^

main.o: main.c func.h
    $(CXX) -c main.c

func.o: main.c func.h
    $(CXX) -c func.c</code></pre>
<ul>
<li><code>$@</code>: targer의 이름</li>
<li><code>$^</code>: 모든 의존성 목록</li>
<li><code>$?</code>: target보다 더 최근에 수정된 의존성 목록<br>
<br>

</li>
</ul>
<p>실습에 사용할 <code>Makefile</code> 파일 내용</p>
<pre><code class="language-makefile">TARGET = program
CXX = gcc

$(TARGET): main.o func.o
    $(CXX) -o $@ $^

main.o: main.c func.h
    $(CXX) -c main.c

func.o: func.c func.h
    $(CXX) -c func.c

clean:
    rm -f *.o $(TARGET)</code></pre>
<p>위의 <code>Makefile</code>이 C 언어 프로젝트에서 일반적으로 사용되는 형태로, 소스 파일들을 오브젝트 파일로 컴파일한 후 링크하여 실행 파일을 생성하는 과정을 간단히 정의하였다.</p>
<p>아래의 <code>Makefile</code>은 C언어 컴파일의 각 단계를 명확히 확인할 수 있도록 작성하였다.</p>
<pre><code class="language-makefile">TARGET = program
CXX = gcc

all: $(TARGET)

# 1. 전처리 단계 (.c → .i)
main.i: main.c func.h
    $(CXX) -E main.c -o $@

func.i: func.c func.h
    $(CXX) -E func.c -o $@

# 2. 컴파일 단계 (.i → .s)
main.s: main.i
    $(CXX) -S main.i -o $@

func.s: func.i
    $(CXX) -S func.i -o $@

# 3. 어셈블 단계 (.s → .o)
main.o: main.s
    $(CXX) -c main.s -o $@

func.o: func.s
    $(CXX) -c func.s -o $@

# 4. 링크 단계 (.o → 실행 파일)
$(TARGET): main.o func.o
    $(CXX) $^ -o $@

clean:
    rm -f *.i *.s *.o $(TARGET)</code></pre>
<br>

<p><strong>Makefile 실행</strong>
<img src="https://velog.velcdn.com/images/banyeah_/post/0c458699-241c-4c05-8679-12892a2dd91b/image.png" alt=""></p>
<p><code>make &lt;target&gt;</code>명령어를 사용하면, 해당 <code>target</code>에 정의된 작업을 수행할 수 있으며, <code>make</code>만 입력하면, Makefile 내에서 가장 먼저 정의된 <code>target</code> 이 기본적으로 실행된다.
<br></p>
<hr>
<h2 id="5-선택-anaconda-설치">5. (선택) Anaconda 설치</h2>
<p>🔗 <a href="https://www.anaconda.com/download/success"><strong>Anaconda 설치</strong></a></p>
<ol>
<li><p>우클릭하여 다운로드 링크를 복사하고, Ubuntu 터미널을 열어 아래 명령어를 입력한다.
 <img src="https://velog.velcdn.com/images/banyeah_/post/e6dd700a-b502-4545-adbd-d869616cae85/image.png" alt=""></p>
<pre><code class="language-bash"> wget [다운로드 링크]</code></pre>
 <br>
</li>
<li><p>다운로드 받은 Anaconda sh 파일을 sh로 실행한다.</p>
<pre><code class="language-bash"> sh [다운로드 받은 파일, 예) Anaconda3-2024.06-1-Linux-x86_64.sh]</code></pre>
 <br>
</li>
<li><p>conda 명령어 설정을 하기 위해, <code>vi ~/.bashrc</code>로 .bashrc 파일을 열고, 파일의 맨 마지막에 아래 코드를 작성한다.</p>
<pre><code class="language-bash"> export PATH=~/anaconda3/bin:~/anaconda3/condabin:$PATH</code></pre>
 <br>
</li>
<li><p><code>source ~/.bashrc</code>로 수정한 내용을 적용하고, <code>conda -V</code>로 버전을 확인한다.
 <img src="https://velog.velcdn.com/images/banyeah_/post/cf3c58a7-30a4-4431-8161-aa063cbcd43f/image.png" alt=""></p>
</li>
</ol>
<br>

<p><strong>[참고] conda 가상환경 만들기</strong></p>
<p><code>conda create -n [이름] python=[버전]</code>로 원하는 Python 버전의 가상환경을 만든다.</p>
<p><code>conda update -n base -c defaults conda</code>로 conda를 업데이트하고,</p>
<p><code>conda activate [이름]</code>로 가상 환경에 접속한다.</p>
<p><code>conda deactivate</code>로 가상 환경에서 나갈 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[솦토링 1주차] GCC 컴파일러 설치 및 VSCode Debugging 도구 사용]]></title>
            <link>https://velog.io/@banyeah_/%EC%86%A6%ED%86%A0%EB%A7%81-1%EC%A3%BC%EC%B0%A8-GCC-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-%EC%84%A4%EC%B9%98-%EB%B0%8F-VSCode-Debugging-%EB%8F%84%EA%B5%AC-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@banyeah_/%EC%86%A6%ED%86%A0%EB%A7%81-1%EC%A3%BC%EC%B0%A8-GCC-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-%EC%84%A4%EC%B9%98-%EB%B0%8F-VSCode-Debugging-%EB%8F%84%EA%B5%AC-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Tue, 08 Apr 2025 01:15:27 GMT</pubDate>
            <description><![CDATA[<p>교내에서 진행하는 멘토링 프로그램에 멘토로 참여하게 되었고, C/C++ 멘토링을 담당할 예정이다. 아래는 멘토링을 진행하기 위해, 정리한 자료들을 모아둔 것이다. 1학년 때 열심히 인터넷을 헤맸던 기억이 있는데, 다시 자료들을 모으다보니 그때 기억도 새록새록 나는 것 같다.
<br></p>
<h2 id="1-vscode-설치">1. VSCode 설치</h2>
<p>IDE는 통합 개발 환경(Integrated Development Environment)을 뜻하며, 프로그래밍을 효율적으로 할 수 있도록 도와주는 소프트웨어 애플리케이션이며, 대표적으로, Visual Studio, Visual Studio Code(VSCode), eclipse, IntelliJ IDEA, Android Studio 등이 있다.</p>
<blockquote>
<p>Visual Studio는 C/C++ 개발에서 주로 사용하며, VSCode는 Visual Studio 가벼운 버전으로 볼 수 있다. 다만, VSCode에서 C/C++ 컴파일을 진행하려면 추가적인 설정이 필요하다.</p>
</blockquote>
<blockquote>
<p>VSCode는 Python 개발, 프론트엔드 웹 개발(HTML, CSS, TS 등)에도 주로 활용되는 등 활용 범주가 굉장히 넓고 여러 확장들이 존재하며, WSL, SSH, Docker, Github 등와 연동해서 사용하는 데도 편리하다.</p>
</blockquote>
<blockquote>
<p>eclipse는 2학년 때 주로 수강하는 ‘JAVA 프로그래밍 실습’ 수업에서 사용하게 될 Java를 기반으로 하는 IDE이다. 그러나 본인은 IntelliJ IDEA(Java, Kotlin)를 더 편하게 사용한 경험이 있다.</p>
</blockquote>
<blockquote>
<p>Android Studio는 3학년에 주로 수강하는 ‘모바일 앱 프로그래밍 실습’ 수업에서 사용하게 될 IDE이다. IntelliJ IDEA를 기반으로 만들어졌다고 한다.</p>
</blockquote>
<p>🔗 <a href="https://code.visualstudio.com/">VSCode 설치</a>
<br></p>
<p><strong>VSCode 확장 설치</strong></p>
<ol>
<li>한국어 언어 확장 설치
<img src="https://velog.velcdn.com/images/banyeah_/post/58746497-ff17-46fe-9450-fbb141c06697/image.png" alt=""></li>
</ol>
<ol start="2">
<li><p>Python 확장 설치
<img src="https://velog.velcdn.com/images/banyeah_/post/1e75df70-0673-4d42-b4a8-af75eba4500c/image.png" alt=""></p>
<ul>
<li>(선택) Black Formatter 설치
<img src="https://velog.velcdn.com/images/banyeah_/post/98c402d6-a2df-475d-a5c7-2ff4babe015b/image.png" alt=""></li>
</ul>
</li>
</ol>
<ol start="3">
<li>C/C++ 확장 설치
<img src="https://velog.velcdn.com/images/banyeah_/post/7060ced3-3127-4d1c-b948-d0a889d3c327/image.png" alt=""></li>
</ol>
<ol start="4">
<li>Makefile 확장 설치
<img src="https://velog.velcdn.com/images/banyeah_/post/0cf5b8d1-5978-4d43-a6f5-268f8c4fe1de/image.png" alt=""><br>


</li>
</ol>
<hr>
<h2 id="2-anaconda-설치">2. Anaconda 설치</h2>
<p>Anaconda는 Python 패키지와 환경을 쉽게 관리할 수 있도록 한다. 또한, Jupyter Notebook(<code>.ipynb</code> )을 쉽게 설치할 수 있는 방법이기도 하다.</p>
<blockquote>
<p>신입생 프로그래밍언어(Python) 사전교육에 참여하였다면 이미 설치가 되어있을 것이다.</p>
</blockquote>
<blockquote>
<p>특히 인공지능 분야에서 각 모델에 맞는 Python 버전 및 패키지 버전 (예. python 3.11.11, numpy =1.25.0, tensorflow=2.15.0 등)이 설치된 가상환경을 만들어 편리하게 사용하기 위해 사용한다.</p>
</blockquote>
<blockquote>
<p>데이터 분석에 있어서도 Jupyter Notebook은 각 셀을 개별적으로 실행하면서, 데이터 전처리, 시각화, 모델링 등을 단계별로 수행하고 결과를 바로 확인할 수 있다는 점에서 매우 편리하게 활용되고 있다. 또한, Markdown 문서도 지원하고 있다. 🔗 <a href="https://gist.github.com/ihoneymon/652be052a0727ad59601">Markdown 문법 설명</a></p>
</blockquote>
<p>🔗<a href="https://www.anaconda.com/download/success">Anaconda 설치</a></p>
<blockquote>
<p>Python이 이미 설치되어 있는 경우, Anaconda와 충돌이 날 수 있으므로 Python을 삭제하고나서 Anaconda를 설치하거나, 그냥 Python이 설치되어 있다는 것에 만족하고 Anaconda를 설치하지 않는 방법이 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/738d0e6e-2aa4-4964-b40d-cec2d0029fec/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/0d74f3ef-ccd3-44cc-a9cb-042ee2e9cd49/image.png" alt="">
<br></p>
<p>cmd에서 <code>python --version</code>명령어를 실행하여 제대로 설치가 완료되었는지 확인한다.<img src="https://velog.velcdn.com/images/banyeah_/post/6f5c4533-6181-4f6b-a1c3-9e81bea3dfb2/image.png" alt="">
<br></p>
<p>VSCode에서 간단한 python 코드 작성 후, 우측 상단의 실행 버튼을 눌러 코드를 실행할 수 있다.<img src="https://velog.velcdn.com/images/banyeah_/post/e993fe12-eb28-4f45-b55c-21a625a3349c/image.png" alt="">
<br></p>
<hr>
<h2 id="3-mingwminimalist-gnu-for-windows-설치">3. MinGW(<strong>Minimalist GNU for Windows</strong>) 설치</h2>
<p>MinGW는 GNU gcc 컴파일러(C언어 컴파일)를 Windows 환경에서 사용할 수 있도록 만든 도구이다.</p>
<blockquote>
<p>VSCode Debugging 도구를 사용할 생각이 없다면 굳이 설치하지 않아도 괜찮다. WSL에서 GCC를 활용해서도 C언어 컴파일이 가능하기 때문이다. (다음 자료 내용)</p>
</blockquote>
<p>🔗<a href="https://winlibs.com/">MinGW-w64 설치</a></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/a2dd2568-216a-4947-bcb7-f8c66178833c/image.png" alt=""></p>
<p>다운받은 .zip 파일을 압축 해제하고, <code>mingw64</code> 폴더를 C 드라이브(<code>C:\mingw64</code>)로 이동시킨다.
<br></p>
<p>환경변수 검색 후, <code>시스템 환경 변수 편집</code> 클릭<img src="https://velog.velcdn.com/images/banyeah_/post/6bd80b3e-03c8-4004-8e16-96d39368e357/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/b675eaf2-8ef5-4ac4-a5a1-aeb8dfa0a2e8/image.png" alt="">
<br></p>
<p><code>Path</code> 선택 후, <code>편집</code> 클릭<img src="https://velog.velcdn.com/images/banyeah_/post/c494db4c-9564-4809-aa74-196bda546a02/image.png" alt="">
<br></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/b1701d86-f71a-4022-a3ff-f3f7e8cee8d2/image.png" alt=""><code>새로 만들기</code> 클릭 후, <code>C:\mingw64\bin</code> 입력
<br></p>
<p>cmd에서 <code>gcc --version</code>명령어를 실행하여 제대로 설치가 완료되었는지 확인한다. <img src="https://velog.velcdn.com/images/banyeah_/post/229b48f1-5be4-4cfa-b6cb-feab70c42d72/image.png" alt="">
<br></p>
<hr>
<h2 id="4-vscode에서-cc-컴파일">4. VSCode에서 C/C++ 컴파일</h2>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/015612ec-f31b-4d3c-85b6-bea8acce3189/image.png" alt=""></p>
<p>C/C++을 사용할 폴더를 열고 (예. <code>C</code>), 내부에 <code>.vscode</code> 폴더(VSCode 설정 파일들을 저장하는 곳)를 생성한다. 이후, 해당 폴더 내부에 각각 <code>c_cpp_properties.json</code> 파일과 <code>tasks.json</code> 파일을 생성한다.</p>
<p>※ 이때, <strong>폴더의 경로에 한국어</strong>가 있으면 오류가 발생한다. (예. 사용자명이 한국어인 경우)
<br></p>
<p><code>c_cpp_properties.json</code> 파일 내용</p>
<pre><code class="language-json">{
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;Win32&quot;,
            &quot;includePath&quot;: [
                &quot;${workspaceFolder}/**&quot;
            ],
            &quot;defines&quot;: [
                &quot;_DEBUG&quot;,
                &quot;UNICODE&quot;,
                &quot;_UNICODE&quot;
            ],
            &quot;windowsSdkVersion&quot;: &quot;10.0.22000.0&quot;,
            &quot;compilerPath&quot;: &quot;C:/mingw64/bin/g++.exe&quot;,
            &quot;cStandard&quot;: &quot;c17&quot;,
            &quot;cppStandard&quot;: &quot;c++17&quot;,
            &quot;intelliSenseMode&quot;: &quot;windows-gcc-x64&quot;
        }
    ],
    &quot;version&quot;: 4
}</code></pre>
<br>

<p><code>tasks.json</code> 파일 내용</p>
<pre><code class="language-json">{
    &quot;version&quot;: &quot;2.0.0&quot;,
    &quot;runner&quot;: &quot;terminal&quot;,
    &quot;type&quot;: &quot;shell&quot;,
    &quot;echoCommand&quot;: true,
    &quot;presentation&quot;: {
        &quot;reveal&quot;: &quot;always&quot;
    },
    &quot;tasks&quot;: [
        {
            &quot;label&quot;: &quot;save and compile for C++&quot;,
            &quot;command&quot;: &quot;g++&quot;,
            &quot;args&quot;: [
                &quot;${file}&quot;,
                &quot;-g&quot;,
                &quot;-o&quot;,
                &quot;${fileDirname}/${fileBasenameNoExtension}&quot;
            ],
            &quot;group&quot;: &quot;build&quot;,
            &quot;problemMatcher&quot;: {
                &quot;fileLocation&quot;: [
                    &quot;relative&quot;,
                    &quot;${workspaceRoot}&quot;
                ],
                &quot;pattern&quot;: {
                    &quot;regexp&quot;: &quot;^(.*):(\\d+):(\\d+):\\s+(warning error):\\s+(.*)$&quot;,
                    &quot;file&quot;: 1,
                    &quot;line&quot;: 2,
                    &quot;column&quot;: 3,
                    &quot;severity&quot;: 4,
                    &quot;message&quot;: 5
                }
            }
        },
        {
            &quot;label&quot;: &quot;save and compile for C&quot;,
            &quot;command&quot;: &quot;gcc&quot;,
            &quot;args&quot;: [
                &quot;${file}&quot;,
                &quot;-g&quot;,
                &quot;-o&quot;,
                &quot;${fileDirname}/${fileBasenameNoExtension}&quot;
            ],
            &quot;group&quot;: &quot;build&quot;,
            &quot;problemMatcher&quot;: {
                &quot;fileLocation&quot;: [
                    &quot;relative&quot;,
                    &quot;${workspaceRoot}&quot;
                ],
                &quot;pattern&quot;: {
                    &quot;regexp&quot;: &quot;^(.*):(\\d+):(\\d+):\\s+(warning error):\\s+(.*)$&quot;,
                    &quot;file&quot;: 1,
                    &quot;line&quot;: 2,
                    &quot;column&quot;: 3,
                    &quot;severity&quot;: 4,
                    &quot;message&quot;: 5
                }
            }
        },
        {
            &quot;label&quot;: &quot;execute&quot;,
            &quot;command&quot;: &quot;cmd&quot;,
            &quot;group&quot;: &quot;test&quot;,
            &quot;args&quot;: [
                &quot;/C&quot;,
                &quot;${fileDirname}\\${fileBasenameNoExtension}&quot;
            ]
        }
    ]
}</code></pre>
<br>

<h3 id="cc-컴파일-및-실행">C/C++ 컴파일 및 실행</h3>
<p>테스트를 위해, 간단한 C언어 코드를 작성한다.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main() {
    printf(&quot;Hello, world!\n&quot;);
    return 0;
}</code></pre>
<br>

<p>설정에서 <code>바로 가기 키</code>로 들어가, ‘작업: 빌드 작업 실행’과 ‘작업: 테스트 작업 실행’의 단축키를 각각
<code>Ctrl + Alt + C</code>와 <code>Ctrl + Alt + R</code>로 변경한다.<img src="https://velog.velcdn.com/images/banyeah_/post/ce60e26f-ab1b-464f-909c-b3f83808608d/image.png" alt=""></p>
<p>이후</p>
<ul>
<li><code>Ctrl + Alt + C</code>를 눌러 <code>save and compile for C</code> 작업으로 컴파일을 진행하고,</li>
<li><code>Ctrl + Alt + R</code>을 누르면 생성된 실행 파일(<code>.exe</code>)이 실행된다.<br>

</li>
</ul>
<hr>
<h2 id="5-vscode-debugging-도구-사용">5. VSCode Debugging 도구 사용</h2>
<p>프로그램이 내가 의도한대로 잘 동작하고 있는지 확인하는 방법은 무엇일까? 혹은 내가 작성한 프로그램에서 의도치 않은 오류가 발생하고 있는 경우에는 어떻게 오류의 발생 원인을 발견할 수 있을까?</p>
<ol>
<li>프로그램의 실행 값이 올바른지 확인한다.
→ 모든 경우에 대해 실행 값이 올바를 것이라고 확신할 수 있을까?</li>
<li>프로그램의 중간마다 <code>printf</code>를 활용해 변수들의 값을 확인한다.
→ 변수가 너무 많으면 어떡하지? 그리고 매번 <code>printf</code>를 작성하는 것이 귀찮지 않을까?</li>
<li>프로그램의 끝에서부터 주석 처리를 진행하며, 어느 부분에서 오류를 발생시키는지 확인한다.
→ 이것만으로는 오류의 발생 원인을 찾기에 부족할 수 있지 않을까?</li>
</ol>
<p>따라서 프로그램의 실행을 한 줄씩 따라가며, 동시에 코드 실행에 따라 변화하는 변수의 값을 확인할 수 있도록(약간의 제약이 있긴 하지만…) 도와주는 Debugging 도구를 VSCode에서 사용해보자.
<br></p>
<p><code>.vscode</code> 폴더 내부에 <code>launch.json</code> 파일을 생성한다.</p>
<p><code>launch.json</code> 파일 내용</p>
<pre><code class="language-json">{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;:&quot;build and debug for C++&quot;,
            &quot;type&quot;: &quot;cppdbg&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;program&quot;: &quot;${fileDirname}\\${fileBasenameNoExtension}.exe&quot;,
            &quot;args&quot;: [],
            &quot;stopAtEntry&quot;: false,
            &quot;cwd&quot;: &quot;C:/mingw64/bin&quot;,
            &quot;environment&quot;: [],
            &quot;externalConsole&quot;: false,
            &quot;MIMode&quot;: &quot;gdb&quot;,
            &quot;miDebuggerPath&quot;: &quot;C:\\mingw64\\bin\\gdb.exe&quot;,
            &quot;setupCommands&quot;: [
                {
                    &quot;description&quot;: &quot;Enable pretty-printing for gdb&quot;,
                    &quot;text&quot;: &quot;-enable-pretty-printing&quot;,
                    &quot;ignoreFailures&quot;: true
                }
            ],
            &quot;preLaunchTask&quot;: &quot;save and compile for C++&quot;
        },
        {
            &quot;name&quot;:&quot;build and debug for C&quot;,
            &quot;type&quot;: &quot;cppdbg&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;program&quot;: &quot;${fileDirname}\\${fileBasenameNoExtension}.exe&quot;,
            &quot;args&quot;: [],
            &quot;stopAtEntry&quot;: false,
            &quot;cwd&quot;: &quot;C:/mingw64/bin&quot;,
            &quot;environment&quot;: [],
            &quot;externalConsole&quot;: false,
            &quot;MIMode&quot;: &quot;gdb&quot;,
            &quot;miDebuggerPath&quot;: &quot;C:\\mingw64\\bin\\gdb.exe&quot;,
            &quot;setupCommands&quot;: [
                {
                    &quot;description&quot;: &quot;Enable pretty-printing for gdb&quot;,
                    &quot;text&quot;: &quot;-enable-pretty-printing&quot;,
                    &quot;ignoreFailures&quot;: true
                }
            ],
            &quot;preLaunchTask&quot;: &quot;save and compile for C&quot;
        }
    ]
}</code></pre>
<br>

<h3 id="cc-디버깅">C/C++ 디버깅</h3>
<p>테스트를 위해, 간단하지 않은 C언어 코드를 작성한다.</p>
<p>🔗<a href="https://www.acmicpc.net/problem/1541">아래 코드와 관련된 문제(1541번: 잃어버린 괄호)</a></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

int main()
{
    char exp[51];
    scanf(&quot;%s&quot;, exp);

    int result = 0, flag = 0, num = 0;
    char op = 0;
    for (int i = 0; i &lt; strlen(exp); i++)
    {
        if (&#39;0&#39; &lt;= exp[i] &amp;&amp; exp[i] &lt;= &#39;9&#39;)
        {
            num *= 10;
            num += exp[i] - &#39;0&#39;;
            continue;
        }

        if (op == 0)
            result = num;
        else if (op == &#39;+&#39;)
        {
            if (flag)
                result -= num;
            else
                result += num;
        }
        else
        {
            flag = 1;
            result -= num;
        }
        op = exp[i];
        num = 0;
    }

    // 마지막 숫자 처리
    if (op == 0)
        result = num;
    else if (op == &#39;+&#39;)
    {
        if (flag)
            result -= num;
        else
            result += num;
    }
    else
    {
        flag = 1;
        result -= num;
    }

    printf(&quot;%d\n&quot;, result);

    return 0;
}</code></pre>
<ol>
<li>코드 옆 빨간점을 클릭하여 중단점(프로그램 실행이 멈추는 부분)을 추가하고, 
<code>build and debug for C</code>을 선택해 디버깅을 시작한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/4f968ba2-5557-40c0-95f9-70a862ffe943/image.png" alt=""></li>
</ol>
<ol>
<li>디버깅 시작 후, 등장한 터미널에 <code>55-50+40</code>을 입력하여 넣는다.
<img src="https://velog.velcdn.com/images/banyeah_/post/9ae66505-0568-4d63-a26b-706435d46044/image.png" alt=""></li>
</ol>
<ol>
<li><code>F10</code>이나 좌상단 버튼을 눌러 코드를 한 줄씩 실행할 수 있다.
만약, 함수 내부에 들어가지 않도록 한 줄씩 실행하고 싶다면 <code>F11</code>이나 좌상단 두번째 버튼을, 
다음 중단점으로 이동하고 싶다면 <code>F5</code>이나 좌상단 첫번째 버튼을 이용한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/02105c9a-0e05-4c3b-9d2b-0bd8962855c2/image.png" alt=""></li>
</ol>
<ol>
<li>결과값으로 55-(50+40), 즉 -35가 나오는 것을 확인할 수 있다.<br>

</li>
</ol>
<hr>
<h2 id="6-선택-vscode에서-formatter-사용">6. (선택) VSCode에서 Formatter 사용</h2>
<p>변수/함수/클래스명을 작성하는데 있어서 여러 코드 스타일들이 존재하고 있다. 대표적으로,</p>
<table>
<thead>
<tr>
<th>코드 스타일</th>
<th>예시</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Camel Case</td>
<td><code>myVariableName</code></td>
<td>소문자로 시작하여, 단어의 경계마다 대문자를 사용한다. (Java, JavaScript 등에서 많이 사용)</td>
</tr>
<tr>
<td>Pascal Case</td>
<td><code>MyVariableName</code></td>
<td>모든 단어의 첫 글자를 대문자로 한다. (주로 클래스명에 사용)</td>
</tr>
<tr>
<td>Snake Case</td>
<td><code>my_variable_name</code></td>
<td>단어 사이를 <code>_</code>로 구분한다. (Python에서 주로 사용)</td>
</tr>
<tr>
<td>SCREAMING_SNAKE_CASE</td>
<td><code>MY_CONSTANT_NAME</code></td>
<td>상수명을 표현할 때 주로 사용한다.</td>
</tr>
</tbody></table>
<p>가 있다. 
<br></p>
<p>다른 사람들과 협업할 때는 주로 이러한 세세한 부분들을 통일하여 코드의 가독성을 높힌다. 또한, Github를 통한 버전 관리 시, 코드 스타일 차이로 인한 불필요한 변경사항을 줄일 수 있다.</p>
<p>그러나 띄어쓰기(공백)와 같은 세부적인 스타일은 사람마다 편차가 크고, 개발자가 매번 일관되게 관리하기 어려운 부분이다.</p>
<pre><code class="language-c">// 1번 예시
if(x==5){console.log(&quot;hi&quot;);}</code></pre>
<pre><code class="language-c">// 2번 예시
if (x == 5) {
    console.log(&quot;hi&quot;);
}</code></pre>
<pre><code class="language-c">// 3번 예시
if (x == 5) 
{
    console.log(&quot;hi&quot;);
}</code></pre>
<p>따라서 자동 코드 Fomatter를 활용해, 스타일을 통일하는 것이 협업과 유지보수 측면에서 효과적이다.</p>
<p>아래와 같이 설정에 들어가, Formatter 사용을 설정한다.<img src="https://velog.velcdn.com/images/banyeah_/post/bdf6d070-d88d-4444-aee8-4261ad02b546/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/66835f43-17f3-47f7-bdc2-3d1b6b75e984/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/080979a0-9163-4a29-b3e9-8e03ac57a246/image.png" alt="">*Python에서는 Black Formatter로 설정한다.</p>
<p>이후 작성한 코드를 저장하면, 자동으로 1, 2번 예시를 3번 예시로 포맷팅을 진행하는 것을 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[xv6 설치하고 실행]]></title>
            <link>https://velog.io/@banyeah_/xv6-%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-%EC%8B%A4%ED%96%89</link>
            <guid>https://velog.io/@banyeah_/xv6-%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-%EC%8B%A4%ED%96%89</guid>
            <pubDate>Sun, 16 Mar 2025 14:35:51 GMT</pubDate>
            <description><![CDATA[<p>운영체제 과목 과제로 이용되며 악명이 높은 xv6를 설치하고 실행해보자.</p>
<p>(사실 과제 서버에서는 필요한 패키지들이 이미 설치되어 있기 때문에 크게 문제가 될 것이 없다. 하지만 과제 서버에서는 <strong>VSCode 사용이 불가</strong>하다는 치명적인 문제가 있기에, 로컬 환경에서 xv6를 설치해보려고 한다.)</p>
<p>xv6 설치 환경으로는 Ubuntu 20.04 버전이 추천되지만, 본인은 <code>Ubuntu 22.04</code> 버전에서 설치를 진행해보겠다.</p>
<hr>
<h2 id="xv6-repository-clone">xv6 repository clone</h2>
<p><a href="https://github.com/mit-pdos/xv6-riscv.git">🔗xv6 repository</a></p>
<pre><code class="language-bash">git clone https://github.com/mit-pdos/xv6-riscv.git</code></pre>
<pre><code class="language-bash">cd xv6-riscv
make qemu</code></pre>
<br>

<p>제대로 설치 및 실행이 완료되었다면 아래와 같은 터미널 메세지를 확인할 수 있으며, <code>ls</code>를 입력해 ls 명령어 실행 결과를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/aa67c312-d865-4740-9b72-e021e910be34/image.png" alt=""></p>
<p><code>Ctrl+a</code>를 누른 후, <code>x</code>를 눌러 xv6를 종료할 수 있다.
<br></p>
<p>xv6 실행에 필요한 패키지가 설치되어있지 않은 상황이라면, 당연히 제대로 실행이 이루어지지 않을 것이다. 아래에서 xv6 실행에 필요한 대표적인 두 패키지 설치를 진행해보겠다.
<br></p>
<hr>
<h2 id="qemu-qemu-system-riscv-패키지-설치">QEMU qemu-system-riscv 패키지 설치</h2>
<pre><code class="language-bash">sudo apt update
sudo apt install software-properties-common</code></pre>
<pre><code class="language-bash">sudo add-apt-repository ppa:canonical-server/server-backports
sudo apt update
sudo apt install qemu-system</code></pre>
<br>

<p>설치 후 버전 확인</p>
<pre><code class="language-bash">qemu-system-riscv64 --version</code></pre>
<p><code>QEMU emulator version 9.0.2</code> 버전이 설치된 것을 확인할 수 있었다.
<br></p>
<hr>
<h2 id="risc-v-gnu-compiler-toolchain-설치">RISC-V GNU Compiler Toolchain 설치</h2>
<p>RISC-V GNU Compiler Toolchain은 xv6 레포지토리의 README 하단에서도 설치가 필요함을 명시하고 있다. </p>
<blockquote>
<p>You will need a RISC-V &quot;newlib&quot; tool chain from
<a href="https://github.com/riscv/riscv-gnu-toolchain">https://github.com/riscv/riscv-gnu-toolchain</a>, and qemu compiled for riscv64-softmmu.</p>
</blockquote>
<br>

<ol start="0">
<li><p>Toolchain 설치에 앞서 필수 패키지들을 설치</p>
<pre><code class="language-bash">sudo apt update
sudo apt-get install autoconf automake autotools-dev curl python3 python3-pip python3-tomli libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build git cmake libglib2.0-dev libslirp-dev</code></pre>
<br>
</li>
<li><p>레포지토리 클론 및 newlib 서브모듈 업데이트</p>
<pre><code class="language-bash">git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
</code></pre>
</li>
</ol>
<p>git submodule update --init --recursive newlib</p>
<pre><code>&lt;br&gt;

2. 설치 경로 생성 후 쓰기 권한 부여
```bash
sudo mkdir -p /opt/riscv
sudo chown $USER:$USER /opt/riscv</code></pre><br>

<ol start="3">
<li><p>환경변수 설정</p>
<pre><code class="language-bash">echo &#39;export PATH=/opt/riscv/bin:$PATH&#39; &gt;&gt; ~/.bashrc
source ~/.bashrc</code></pre>
<br>
</li>
<li><p>Toolchain 설치 진행 (<strong>대략 2시간</strong> 가까이 소요된다)</p>
<pre><code class="language-bash">./configure --prefix=/opt/riscv
make</code></pre>
<br>

</li>
</ol>
<p>설치 후 버전 확인</p>
<pre><code class="language-bash">riscv64-unknown-elf-gcc --version</code></pre>
<p><code>riscv64-unknown-elf-gcc (g04696df09) 14.2.0</code> 버전이 설치된 것을 확인할 수 있었다. (설치 이후 레포지토리는 삭제해도 문제가 없다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문 리뷰] Session-based Recommendations with Recurrent Neural Networks]]></title>
            <link>https://velog.io/@banyeah_/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-Session-based-Recommendations-with-Recurrent-Neural-Networks</link>
            <guid>https://velog.io/@banyeah_/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-Session-based-Recommendations-with-Recurrent-Neural-Networks</guid>
            <pubDate>Sat, 11 Jan 2025 17:59:21 GMT</pubDate>
            <description><![CDATA[<p>논문: <a href="https://arxiv.org/abs/1511.06939">https://arxiv.org/abs/1511.06939</a></p>
<blockquote>
<p>많은 전자상거래 추천 시스템(특히 소규모 소매업체)과 대부분의 뉴스 및 미디어 사이트는 <strong>사용자 ID를 장기적으로 추적하지 않는다.</strong> 
따라서 실제 추천 시스템은 <strong>짧은 세션 기반 데이터만을 바탕으로 추천</strong>해야 하는 문제에 직면하며, 이 경우 행렬 분해 접근법(일반적인 추천 시스템에서 사용)은 정확도가 떨어질 수 있다.</p>
</blockquote>
<h2 id="1-introduction">1. INTRODUCTION</h2>
<ul>
<li><p>많은 사용자가 소규모 전자상거래 사이트에서 단 한두 번의 세션만을 가지며, 특정 도메인(예: 분류 광고 사이트)에서는 사용자 행동이 종종 세션 기반 특성을 보인다. 
따라서 사용자의 후속 세션은 <strong>독립적으로 처리</strong>되어야 한다.</p>
</li>
<li><p>대부분의 세션 기반 추천 시스템은 <strong>사용자 프로필을 사용하지 않는 비교적 간단한 방법</strong>(예: item-item 유사도 등)으로 구성된다. 
그러나 이러한 방법들은 <u>종종 사용자의 마지막 클릭이나 선택만을 고려하며, 과거 클릭 정보를 무시</u>한다.</p>
</li>
<li><p><strong>RNN 모델</strong>을 활용한 세션 기반 추천에서는 사용자가 웹사이트에서 처음 클릭한 항목을 초기 입력으로 간주할 수 있다.
그 다음 초기 입력을 바탕으로 모델에 출력(추천)을 요청하고, 사용자가 클릭할 때마다 <strong>이전 클릭 데이터를 기반으로</strong> 출력(추천)을 생성한다.</p>
</li>
<li><p>사용자가 관심을 가질 수 있는 상위 항목(Top items)에 모델링 파워를 집중시키기 위해, RNN 모델 학습에 순위 손실(Ranking loss)함수를 사용한다.</p>
<br>

</li>
</ul>
<h2 id="3-recommendations-with-rnns">3. RECOMMENDATIONS WITH RNNS</h2>
<h3 id="31-customizing-the-gru-model">3.1 CUSTOMIZING THE GRU MODEL</h3>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/332841e3-d136-4a94-989b-d17a103c15d1/image.png" alt=""></p>
<ul>
<li><p>GRU 기반 RNN 모델을 사용했으며, 네트워크의 <strong>입력은 세션의 현재 상태</strong>이고 <strong>출력은 각 항목이 세션에서 다음으로 선택될 확률</strong>(선호도)이다.</p>
<ul>
<li>세션의 현재 상태는 (1) <strong>현재 이벤트의 항목</strong>이거나 <em>(2) 세션 내의 모든 이벤트일 수 있다.</em>
(1)에서는 <strong>1-of-N 인코딩</strong>(입력 벡터의 길이가 항목 수와 동일하며, 활성 항목에 해당하는 좌표만 1이고 나머지는 0인 방식)이 사용되고, <em>(2)에서는 이러한 표현의 가중합을 사용하며 이전에
발생한 이벤트는 가중치가 감소한다.</em></li>
<li>추후 실험 결과에서 세션 내 모든 이벤트를 입력으로 넣는 것이 현재 이벤트 항목만을 입력으로 넣는 것에 비해 추가적인 정확도 향상을 가져오지 않는 것이 드러났다.
<em>(GRU가 LSTM의 특성을 가지기에 어찌 보면 당연한 것)</em></li>
</ul>
</li>
<li><p><em>입력에서 이후에 임베딩 레이어를 추가할 수 있다.</em></p>
<ul>
<li><em>추후 실험 결과에서 항목의 임베딩을 사용하는 것이 약간 더 나쁜 결과를 보여주었기에, 1-of-N 인코딩을 유지했다고 한다.</em></li>
</ul>
</li>
<li><p><em>여러 개의 GRU layers를 사용할 경우, 이전 레이어의 은닉 상태가 다음 레이어의 입력으로 사용된다. 또한, 입력은 선택적으로 네트워크의 더 깊은 GRU 레이어에 연결될 수 있으며, 이는 성능 향상에 기여하는 것으로 확인되었다.</em></p>
<ul>
<li>추후 실험 결과에서 <strong>단일 GRU 레이어</strong>가 가장 우수한 성능을 보였다.
<em>(일반적으로 세션의 수명이 짧아 다양한 해상도의 여러 시간 스케일이 적절히 표현될 필요가 없기 때문으로 추정된다)</em></li>
<li><strong>GRU 레이어의 크기를 늘리는 것</strong>은 성능을 향상시켰음을 확인했다.</li>
</ul>
</li>
<li><p>Feedforward layers를 마지막 레이어와 출력 사이에 추가할 수 있다.</p>
<ul>
<li>추후 실험 결과에서 GRU 레이어 이후에 추가적인 Feedforward layers을 추가하는 것은 도움이 되지 않았다고 한다.</li>
<li>출력 레이어의 활성화 함수로 tanh를 사용하는 것이 유익함을 발견했다.<br>

</li>
</ul>
</li>
</ul>
<h3 id="311-session-parallel-mini-batches">3.1.1 SESSION-PARALLEL MINI-BATCHES</h3>
<ul>
<li>자연어 처리에서의 순차적 미니 배치는 본 연구에 적합하지 않다.
따라서 <strong>세션 병렬 미니배치</strong>(session-parallel mini-batches)를 사용한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/caeb52b6-2a30-465f-99b7-33cbdeaf65d2/image.png" alt=""></li>
</ul>
<ol>
<li>먼저, 세션의 순서를 생성한다.</li>
<li>그 다음, 첫 번째 미니 배치의 입력을 활성 세션의 첫 번째 이벤트($𝑖<em>{1,1}$)로 구성하고, 원하는 출력은 해당 세션의 두 번째 이벤트($𝑖</em>{1,2}$)가 된다.</li>
<li>두 번째 미니 배치는 두 번째 이벤트로 구성되며, 이러한 방식으로계속한다.</li>
<li>만약 세션이 끝나면, <u>다음 사용 가능한 세션을 그 자리에 배치</u>한다.
이때 세션은 독립적인 것으로 가정하므로, <strong>전환이 발생할 때 적절히 은닉 상태를 초기화</strong>한다.<br>

</li>
</ol>
<h4 id="312-sampling-on-the-output">3.1.2 SAMPLING ON THE OUTPUT</h4>
<ul>
<li><p>모든 항목에 대해 점수를 계산하는 것은 알고리즘 복잡도 증가(→ 시간 증가)로 인해 실효성이 떨어진다.</p>
</li>
<li><p>따라서 출력에서 샘플링을 수행하여 항목의 작은 하위 집합에 대해서만 점수를 계산해야 한다.</p>
</li>
<li><p>이로 인해, 일부 가중치만 업데이트 되므로, 원하는 출력(positive examples) 외에도 <strong>몇몇 부정적인 예제(negative examples)에 대한 점수를 계산</strong>하고 원하는 출력이 높은 순위를 갖도록 가중치를 조정해야 한다.</p>
<ul>
<li>임의의 누락된 이벤트에 대한 자연스러운 해석은 ‘사용자가 해당 항목의 존재를 알지 못했기 때문에 상호작용이 없었다’이다. </li>
<li>그러나 <strong>항목이 인기있을수록</strong> 사용자가 항목을 알고 있을 가능성이 높아지며, <strong>누락된 이벤트가 싫어함(dislike)를 나타낼</strong> 가능성이 높다.</li>
</ul>
</li>
<li><p><strong>항목의 인기도에 비례하여 항목을 샘플링</strong>(popularity-based sampling)해야 한다.</p>
<ul>
<li>각 학습 데이터(세션)마다 별도의 샘플링을 생성하는 대신, <u>미니배치의 다른 학습 데이터에서 항목을 가져와 부정적인 예제로 사용</u>한다.</li>
<li>미니 배치의 다른 학습 데이터에 항목이 포함될 가능성이 그 항목의 인기도에 비례하기 때문이다.</li>
<li>이 접근법으로 샘플링 단계를 생략함으로써 계산 시간을 단축할 수 있다.<br>

</li>
</ul>
</li>
</ul>
<h4 id="313-ranking-loss">3.1.3 RANKING LOSS</h4>
<p>추천 시스템의 핵심은 <strong>항목의 관련성에 기반한 순위 매기기(relevance-based ranking)</strong>이다.</p>
<ol>
<li><p><strong>Pointwise Ranking</strong>: 항목의 점수 또는 순위를 서로 <strong>독립적으로 추정</strong>하며, 관련 항목의 순위가 낮아야 한다(1에 가까워야)는 방식으로 손실(loss)을 정의한다.</p>
<ul>
<li>추후 실험에서 Pointwise Ranking Loss(예: Cross-entropy, MRR 최적화)는 정규화를 적용했음에도 불구하고 <strong>일반적으로 불안정</strong>했다. <em>원하는 항목에 대해 높은 점수를 독립적으로 얻으려 하면서 부정적인 샘플에 대한 밀어내기가 작게 적용되기 때문으로 추정된다.</em></li>
</ul>
</li>
<li><p><strong>Pairwise Ranking</strong>: <strong>긍정적 항목과 부정적 항목의 쌍을 비교</strong>하여 점수 또는 순위를 평가한다. 긍정적 항목의 순위가 부정적 항목보다 낮아야 한다는 제약을 손실로 강제한다.</p>
</li>
<li><p><em>Listwise Ranking: 모든 항목의 점수와 순위를 사용하여 이를 완벽한 순서(perfect ordering)와 비교한다. 하지만 정렬(sorting)을 포함하기 때문에 계산 비용이 크며, 일반적으로 자주 사용되지 않는다.</em></p>
<ul>
<li><em>관련 항목이 하나뿐인 경우, 리스트 방식 순위 매기기(listwise ranking)는 쌍 방식 순위 매기기(pairwise ranking)를 통해 해결될 수 있다.</em></li>
</ul>
</li>
</ol>
<p>따라서 두 가지 Pairwise Ranking을 사용했다.</p>
<ul>
<li><p><strong>BPR</strong>(Bayesian Personalized Ranking):
pairwise ranking loss를 사용하는 <strong>행렬 분해</strong>(matrix factorization) 방법이다.
긍정적인 항목의 점수를 샘플링된 여러 부정적인 항목들과 비교하고 그 평균을 손실로 사용하여, 두 항목의 <strong>예측값 사이의 차이를 최대화</strong>하는 방향으로 학습한다.</p>
<p>$$
L_s = -\frac{1}{N_S} \cdot \sum_{j=1}^{N_S} \log (\sigma(\hat{r}<em>{s,i} - \hat{r}</em>{s,j}))</p>
<p>$$</p>
</li>
</ul>
<ul>
<li>$N_S$: 샘플의 크기.</li>
<li>$\hat{r}_{s,i}$: 항목 $k$에 대한 점수.</li>
<li>$i$: 해당 세션에서 긍정적인 항목(세션의 다음 항목).</li>
<li>$j$: 부정적인 샘플들.</li>
</ul>
<ul>
<li><p><strong>TOP1</strong>:</p>
</li>
<li><p><em>관련 항목의 상대적인 순위*</em>(relative rank)의 정규화된 근사값을 나타낸다.</p>
<p> $$
 \frac{1}{N_S} \cdot \sum_{j=1}^{N_S} I{\hat{r}<em>{s,j} &gt; \hat{r}</em>{s,i}} = \frac{1}{N_S} \cdot \sum_{j=1}^{N_S} \sigma{\hat{r}<em>{s,j} - \hat{r}</em>{s,i}} 
 $$</p>
<p> 하지만 $i$의 점수가 높아지도록 최적화하는 동안, <u>특정 긍정적인 항목이 부정적인 예제로 작용하면서 점수가 점점 더 높아지는 불안정성</u>이 발생할 수 있다. 따라서 부정적인 항목의 점수를 0 근처로 유지하기 위해 손실에 정규화 항을 추가했다.</p>
<p> $$
 L_s = \frac{1}{N_S} \cdot \sum_{j=1}^{N_S} \sigma(\hat{r}<em>{s,j} - \hat{r}</em>{s,i}) + \sigma((\hat{r}_{s,j})^2)
 $$</p>
<br>
<br>

</li>
</ul>
<h2 id="4-experiments">4. EXPERIMENTS</h2>
<h3 id="dataset">DATASET</h3>
<ul>
<li><p>RecSys Challenge 2015 (<strong>RSC15</strong>):</p>
</li>
<li><p><em>전자 상거래 사이트*</em>의 클릭 스트림을 포함하며, 일부는 구매 이벤트로 끝나는 경우도 있다.</p>
<ul>
<li>학습 세트를 이용하며, 클릭 이벤트만 유지한다. </li>
<li>길이가 1인 세션은 제거한다.</li>
<li>테스트는 이후 하루 동안의 세션을 사용한다. 이때, 각 세션은 학습 또는 테스트 세트에 할당되며, 세션 중간에서 데이터를 분할하지 않는다.</li>
<li>협업 필터링 특성 상, 테스트 세트에서 클릭된 항목이 학습 세트에 포함되지 않는 경우, 해당 클릭은 테스트 세트에서 제거한다.</li>
</ul>
</li>
<li><p><strong>VIDEO</strong>:
Youtube와 유사한 <strong>동영상 OTT 비디오 서비스 플랫폼</strong>에서 수집했다. 일정 시간 이상 비디오를 시청한 이벤트들을 수집했다.</p>
<ul>
<li>Bot에 의해 생성되었을 가능성이 높은 매우 긴 세션을 필터링한다.</li>
<li>학습 세트는 수집 기간(약 2개월)의 마지막 하루를 제외한 모든 세션으로 구성하고, 테스트 세트는 수집 기간의 마지막 하루 동안의 세션으로 구성한다.<br>

</li>
</ul>
</li>
</ul>
<h3 id="evaluation">EVALUATION</h3>
<ul>
<li><p>평가는 세션의 이벤트를 하나씩 제공하고, 다음 이벤트의 항목 순위를 확인하는 방식으로 이루어졌다.</p>
</li>
<li><p>항목들은 점수에 따라 내림차순으로 정렬되며, 이 목록에서의 위치가 항목의 순위이다.</p>
<ul>
<li>RSC15 데이터셋에서는 학습 세트의 37,483개 모든 항목의 순위를 매겼다.</li>
<li>VIDEO 데이터셋에서는 항목 수가 너무 많아 비실용적이었기 때문에, 원하는 항목을 상위 30,000개의 가장 인기 있는 항목과 비교하여 순위를 매겼다.<br>

</li>
</ul>
</li>
</ul>
<p><strong>평가 지표</strong></p>
<ul>
<li>Recall@20: 테스트 사례에서 원하는 항목이 상위 20개 항목에 포함된 비율. <em>(절대적인 순서가 중요하지 않은 경우)</em></li>
<li>MRR@20: 원하는 항목의 역순위의 평균으로 순위가 20을 초과하면 역순위는 0으로 설정. <em>(추천 순서가 중요한 경우)</em><br>

</li>
</ul>
<p><strong>Baseline 비교</strong></p>
<ul>
<li>Item-KNN이 명백하게 우수한 성능을 보였다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/b1fbb634-a101-4553-84fd-858e2ebf1fd9/image.png" alt="">
<br></p>
<h3 id="43-results">4.3 RESULTS</h3>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/db816454-91e3-4ab0-ac92-0e76b4652cbd/image.png" alt=""></p>
<ul>
<li><p>GRU 기반 접근법은 두 데이터 셋에서 Item-KNN에 비해 두 평가 지표 모두에서 상당한 성능 향상을 보였다.</p>
</li>
<li><p><u>은닉 유닛 수를 늘리면</u> <strong>Pairwise Loss(TOP1, BPR)에 대한 결과가 더욱 개선</strong>되지만, Cross-entropy의 정확도는 감소한다. 그러나 Pairwise Loss의 개선이 기존 Cross-entropy의 정확도보다 뛰어나다.</p>
<ul>
<li>유닛 수를 늘리는 것이 학습 시간을 증가시키기는 하지만, GPU에서는 100 → 1000의 이동이 <strong>크게 비싸지않다</strong>.
(추천 시스템에서는 새로운 사용자와 항목이 자주 추가되기 때문에, <strong>잦은 재학습이 필요</strong>하다)</li>
<li>따라서 Pairwise Ranking Loss를 사용하는 것을 권장한다.</li>
</ul>
</li>
<li><p><strong>TOP1 Loss가 두 데이터셋에서 약간 더 나은 성능</strong>을 보이며, Item-KNN에 비해 20-30%정도 정확도가 향상되었다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문 리뷰] Predicting Learners Need for Recommendation Using Dynamic Graph-Based Knowledge Tracing]]></title>
            <link>https://velog.io/@banyeah_/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-Predicting-Learners-Need-for-Recommendation-Using-Dynamic-Graph-Based-Knowledge-Tracing</link>
            <guid>https://velog.io/@banyeah_/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-Predicting-Learners-Need-for-Recommendation-Using-Dynamic-Graph-Based-Knowledge-Tracing</guid>
            <pubDate>Thu, 09 Jan 2025 14:04:03 GMT</pubDate>
            <description><![CDATA[<p>논문: <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC7334686/">https://pmc.ncbi.nlm.nih.gov/articles/PMC7334686/</a></p>
<blockquote>
<p>학습자의 과거 성과를 기반으로 
<strong>특정 시점에서 추천이 필요한 학습자를 능동적으로 구별</strong>하는 기능</p>
</blockquote>
<br>

<h2 id="1-introduction">1. INTRODUCTION</h2>
<p><strong>동적 그래프 기반 지식 추적(dynamic graph-based knowledge tracing) 접근법</strong>에서 <strong>시계열 노드 분류(time-series node classification)</strong>를 제안한다.</p>
<ul>
<li><p>동적 그래프 기반 지식 추적 접근법은 &#39;Adaptive neural network for node classification in dynamic networks&#39;에서 제안되었다.</p>
</li>
<li><p><strong>학습자</strong><u>를 노드로 모델링</u>하여, <u>특정 지식 개념을 기반으로</u> <strong>학습자를 그래프에서 그룹화</strong>한다. 이 과정에서 노드와 그래프의 토폴로지는 학습자의 지식 추적에 맞춰 <u>시간에 따라 변화</u>한다.</p>
<ul>
<li><strong>Gated Recurrent Unit(GRU) 네트워크</strong>와 <strong>Attention Neural Network(ANN)</strong>을 활용하여, 노드로 표현된 학습자와 그 이웃 노드의 정보를 집계해 특징 표현(feature representation)을 학습하고, 각 시간 단계에서 네트워크의 토폴로지 정보를 추출한다.</li>
<li><em>GRU(Gated Recurrent Unit)는 LSTM의 간소화된 버전이다.</em></li>
</ul>
</li>
</ul>
<br>

<h2 id="2-proposed-approach">2. PROPOSED APPROACH</h2>
<h3 id="problem-definition">Problem Definition</h3>
<blockquote>
<p><strong>지도 학습 기반 노드 분류(supervised node classification)</strong></p>
</blockquote>
<ul>
<li><p><u>강의 내용</u>을 $G = (\zeta_1, \zeta_2, ..., \zeta_T)$로 구조화했으며, 여기서 <u>$T$는 시간 단계</u>(time steps)의 수를 나타낸다. $\zeta_t = (V, A_t, X_t, C)$는 <u>시간 단계 $t$에서의 그래프</u>이자, 노드 집합 $V$를 포함한 그래프를 의미한다. $N= ∣V∣$는 그래프 내 학습자/노드의 수를 나타낸다.</p>
</li>
<li><p><u>노드(학습자)들은 특정 지식 개념 $C$를 의존 관계로 공유</u>하며, $C = {C_1, C_2, ..., C_m}$는 $m$개의 존재하는 지식 개념으로 구성된다.</p>
</li>
<li><p><u>$A_t \in \mathbb{R}^{N \times N}$는 노드 간 연결을 설명하는</u> <strong>인접 행렬</strong>(adjacency matrix)로, $A_{ij} = 1$은 시간 $t$에 노드 $i$와 $j$가 공유하는 지식 개념 $C$가 있음을 나타내고, 연결이 없으면 $A_{ij} = 0$으로 표시된다.</p>
</li>
<li><p>$X_t \in \mathbb{R}^{N \times f}$는 <strong>노드 속성 행렬</strong>(node attribute matrix)로, $f$는 학습자를 표현하는 <u>속성(feature)의 차원 수</u>입니다.</p>
</li>
<li><p>시간 단계에 따라 $A_t$​와 $X_t$는 변화하지만, $V$와 $C$는 모든 시간 단계에서 고정된다.</p>
</li>
</ul>
<br>

<h3 id="dynamic-graph-based-knowledge-tracing">Dynamic Graph Based Knowledge Tracing</h3>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/4d52c52b-94ea-47b9-b9a7-b00a75b2d531/image.png" alt=""></p>
<ol>
<li><p>먼저, 튜터는 <u>사용할 지식 개념을 선택</u>한다. <u>지식 추적 데이터셋</u>은 <u>시간 단계에 따라 변화하는 동적 그래프로 변환</u>되며, 각 노드는 학습자를 나타내고 학습자의 이전 지식에서 추출 및 집계된 <u>속성 특징(attribute features)을 포함</u>한다. 생성된 그래프의 모든 학습자는 교사가 선택한 동일한 지식 개념을 공유한다.</p>
</li>
<li><p>동적 그래프에서의 노드 분류(Node Classification) 아이디어는 <strong>네트워크 구조 정보</strong>와 <strong>노드 속성 정보를 통합</strong>하는데 있다. 이를 위해 두 개의 GRU(Gated Recurrent Unit) 네트워크(A-GRU와 T-GRU)를 사용한다. </p>
<ol>
<li><p>Attention Neural Network를 통해 관련 노드 정보를 캡처하고 <u>중요한 이웃 노드들을 집계</u>합니다. <u>이웃 표현(neighbour representation)과 이전 상태의 노드 특징 벡터를 결합</u>하여 새로운 GRU 상태 벡터 $h^A_t \in \mathbb{R}^{d_h}$를 생성하며, 이는 A-GRU를 나타낸다.</p>
</li>
<li><p>T-GRU의 경우, <u>서로 다른 시간 단계에서 노드/학습자의 토폴로지 컨텍스트 벡터를 고려하여</u> GRU 상태 벡터 $h^T_t \in \mathbb{R}^{d_h}$를 생성한다.</p>
</li>
<li><p><strong>속성-토폴로지 Attention</strong>은 <strong>각 시간 단계에서 속성과 토폴로지의 중요도를 결정</strong>하며, 상태 벡터 $h^T_t$와 $h^A_t$를 받아 각각 Attention 값 $\beta^A_t$와 $\beta^T_t$를 계산한다. 따라서, 시간 단계 $t$에서의 최종 상태 벡터는 다음과 같이 정의된다:</p>
<p>$$
h_t = [(\beta^T_t \times h^T_t) \oplus (\beta^A_t \times h^A_t)] \in \mathbb{R}^{2d_h}
$$</p>
</li>
<li><p><strong>그래프 구조의 시간적 변화를 감지</strong>하기 위해 <strong>Temporal Attention</strong>이 추가되었다. Attention 모델은 상태 $h_t$를 입력받아 각 상태에 대한 Attention 값 $\alpha_t$를 출력한다. 다중 헤드 셀프 Attention을 활용하여 노드의 최종 벡터 표현은 다음과 같이 정의된다:</p>
<p>$$
\alpha \times H \in \mathbb{R}^{2d_h}
$$</p>
<ul>
<li>$H = [h_1, ..., h_t]$: 모든 $h_t$를 연결한 벡터.</li>
<li>$\alpha \in \mathbb{R}^T$는 서로 다른 시간 단계의 Attention 값.</li>
</ul>
</li>
</ol>
</li>
<li><p>마지막으로, 교차 엔트로피 손실(cross-entropy loss)과 Softmax 함수를 사용해 노드 레이블을 추정한다. 시간 단계에 걸쳐 선택된 지식 개념에서 <strong>낮은 지식 획득을 나타내는 노드(학습자)만 추천 시스템에 입력</strong>되며, 해당 지식 개념에 맞는 학습 자료와 함께 제공된다.</p>
</li>
</ol>
<br>

<h2 id="3-experiment">3. EXPERIMENT</h2>
<h3 id="31-dataset">3.1 Dataset</h3>
<p>ASSISTments 학습 플랫폼에서 제공된 데이터셋을 활용했으며, 관련 특징을 추출 및 집계하여 재구성하고 레이블링하였다. 학습자를 나타내기 위해 총 8가지 특징(소요 시간, 정답 수, 힌트 요청 수, 시도 횟수, 좌절 점수, 지루함 점수, 혼란 점수, 집중도 점수)을 선택했다. 각 학습자는 지식 획득이 낮아 추천이 필요한지 여부를 나타내는 <strong>이진 값으로 레이블링</strong>되었다.</p>
<p>레이블링된 데이터를 기반으로 &quot;덧셈과 뺄셈 정수(Addition and Subtraction Integers)&quot;라는 지식 개념을 예시로 선택했다. <u>해당 데이터에 따르면 학습자의 42%가 문제를 겪으며 추천이 필요하다</u>고 나타났다. 이후, 선택된 지식 개념을 기반으로 <u>동적 그래프를 생성</u>하였으며, <u>특정 시간 단계(10)에 걸쳐 과제를 완료한 모든 학습자들을 연결</u>한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/5e8d4372-6ebe-4d85-8c80-b283aa4dfa03/image.png" alt="">
<br></p>
<h3 id="32-results-and-discussion">3.2 Results and Discussion</h3>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/185bcbbd-a8d7-40ea-83e2-ffa55690ba49/image.png" alt=""></p>
<ul>
<li><p>여러 실험을 거친 후, 다음과 같은 매개변수에서 제안된 모델이 가장 높은 성능을 보였다: 
<code>batch size = 2048, learning rate = 0.001, epochs = 30, 상태 벡터 크기(dhd_hdh​) = 12</code></p>
</li>
<li><p>이 모델은 그래프에서 각 학습자를 나타내는 선택된 <strong>특징의 중요성</strong>(A-GRU)과 동일한 지식 개념을 공유하는 학습자 간의 연결을 나타내는 <strong>그래프 토폴로지</strong>(T-GRU)를 결합한다. <strong>시간 단계에 따른 그래프의 동적 표현</strong>을 활용하여, 정적 그래프 스냅샷에만 의존하는 정적 방법보다 학습자의 지식 획득을 더 효과적으로 모델링한다.</p>
</li>
<li><p>이 모델은 학습자별로 추천 필요성을 높은 정확도로 예측할 수 있으며, 이를 통해 학습자 탈락률을 크게 줄일 수 있다. 또한, 낮은 학습 성과를 보이는 학습자를 위한 적응형 시스템 구축에도 도움을 줄 것이다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문 리뷰] Scalable and Equitable Math Problem Solving Strategy Prediction in Big Educational Data]]></title>
            <link>https://velog.io/@banyeah_/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-Scalable-and-Equitable-Math-Problem-Solving-Strategy-Prediction-in-Big-Educational-Data-3pzs4jub</link>
            <guid>https://velog.io/@banyeah_/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-Scalable-and-Equitable-Math-Problem-Solving-Strategy-Prediction-in-Big-Educational-Data-3pzs4jub</guid>
            <pubDate>Thu, 09 Jan 2025 13:40:01 GMT</pubDate>
            <description><![CDATA[<p>논문: <a href="https://arxiv.org/abs/2308.03892">https://arxiv.org/abs/2308.03892</a></p>
<blockquote>
<p>학생의 문제 해결 전략을 이해하는 것은 지능형 튜터링 시스템(Intelligent Tutoring Systems, ITS) 및 적응형 교육 시스템(Adaptive Instructional Systems, AIS)을 통한 효과적인 수학 학습에 큰 영향을 미칠 수 있다.</p>
</blockquote>
<blockquote>
<p>문제 해결에 사용된 전략은 학생의 숙련도를 나타낼 수 있다. 또한, 잘못된 전략에서 드러나는 <u>특정 오개념을 바로잡기 위한 개인화 학습을 제공</u>하고, <u>전략을 개선하기 위한 특정 문제를 설계</u>하며, 학생의 사고 방식을 반영한 지도로 학생의  좌절감을 최소화할 수 있다.</p>
</blockquote>
<br>

<h2 id="purpose">PURPOSE</h2>
<blockquote>
<p>수학 학습에 있어서 확장 가능하고 공정한 
<strong>학생의 문제 풀이 전략 예측 모델</strong>을 개발하는 것이 논문의 목표이다.</p>
</blockquote>
<ul>
<li><p><strong>확장 가능성</strong>: 
전체 데이터의 일부만을 <strong>샘플링</strong>하더라도 효율적인 학습 가능한 것.</p>
</li>
<li><p><strong>공정성</strong>: 
(숙련도나 기술 수준에 차이가 존재하는) 학습자 유형에 따라 모델 정확도가 다르지 않은 것.</p>
</li>
</ul>
<br>

<h2 id="dataset">DATASET</h2>
<blockquote>
<p>미국 수학 교육 플랫폼인 MATHia를 활용한 실제 학생들의 학습 데이터를 사용했다.</p>
</blockquote>
<ul>
<li><p>학생과 컴퓨터 간의 상호작용 및 <strong>학생의 문제 해결 행동에 대한 로그</strong>를 포함한 것이다. 
(예: <strong>사용된 지식 구성 요소(Knowledge Component)</strong>, <strong>해당 단계(KC)가 올바르게 완료되었는지</strong> 여부, 힌트가 필요한지 여부 등)</p>
</li>
<li><p>PSLC datashop을 통해 공개적으로 이용 가능하다고 논문에 적혀 있었으나, 실제 데이터셋을 다운받아 확인해보기 어려웠다.
<a href="https://pslcdatashop.web.cmu.edu/DatasetInfo?datasetId=345">Bridge to Algebra 2008-2009 (BA08)</a>,
<a href="https://pslcdatashop.web.cmu.edu/Project?id=720">Carnegie Learning MATHia 2019-2020 (CL19)</a>.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/0c66e8fc-add3-4ac7-b367-ce3ecf72803f/image.png" alt=""></p>
<ul>
<li>연립방정식 문제를 해결하는 3가지 전략으로,
유사한 색상은 유사한 단계(KC)를 나타내며, <strong>여러 전략들은 완전히 동일하지 않더라도 유사하거나 대칭적인 경우가 많다.</strong></li>
</ul>
<br>

<h2 id="3-proposed-approach">3. PROPOSED APPROACH</h2>
<blockquote>
<p>학생 $𝑆$와 문제 $𝑃$에 대해, 
<strong>$𝑆$가 $𝑃$를 해결하는데 사용할 전략(KC 시퀀스)을 예측하는 것</strong>이
논문에서 해결하고자하는 과제이다.</p>
</blockquote>
<h3 id="31-mvec-embeddings">3.1 MVec Embeddings</h3>
<p>Node2Vec와 유사한 접근법을 활용하되, KC $K$에 대한 학생 $S$의 <strong>숙련도(Mastery)</strong>를 이용해 그래프에서 경로를 샘플링하여 임베딩을 학습하는 것이다.</p>
<p>숙련도 값은 Attention 모델을 활용해 각 KC가 문제 해결에서 차지하는 역할(중요도)를 포함하여 계산한다.</p>
<br>

<h4 id="관계-그래프-gve의-구성">관계 그래프 $G=(V,E)$의 구성</h4>
<ul>
<li><p>훈련 데이터의 각 학생과 문제, KC를 노드 $V∈V$로 표현한다.</p>
</li>
<li><p>학생 $S$가 문제 $P$를 해결하는 데 KC $K$를 사용하는 경우, 두 개의 간선 $E,E′∈E$가 생성된다.</p>
<ul>
<li>$E$: 학생 노드와 KC 노드를 연결.</li>
<li>$E′$: KC 노드와 문제 노드를 연결.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/1598e808-2461-4ce5-82f1-43bf1a904c20/image.png" alt=""></p>
<ul>
<li><p>좌측 그래프는 3명의 학생, 문제, 그리고 KC에 대한 예시 그래프이며, 
우측은 임베딩을 학습하기 위해, <u>그래프에서 경로를 샘플링</u>한 결과이다.</p>
</li>
<li><p>간단한 샘플링 전략 $Q$는 노드의 이웃을 무작위로 샘플링하는 것이지만,
<u>학생이 KC를 문제에 성공적으로 적용했다면, 해당 간선은 더 큰 중요도를 부여받아야 한다.</u> 따라서 Attention 모델을 훈련하여 그래프 $G$의 간선에 대한 샘플링 확률을 추정한다.</p>
</li>
</ul>
<br>

<h4 id="숙련도cfa-정량화">숙련도(CFA) 정량화</h4>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/e20f590f-e268-45a0-8be6-af2d645ef224/image.png" alt=""></p>
<ul>
<li><p>세 학생이 각 문제에 대해 KCs를 적용할 기회를 갖는 모습을 설명한다.</p>
</li>
<li><p><strong>CFA(Correct First Attempt)</strong></p>
<ul>
<li>각 KC 시퀀스에 대해, 학생이 <u>해당 KC를 적용할 기회</u>를 가졌을 때 <u>첫 번째 시도에서 올바르게 해결했는지(Correct, 1)</u> 또는 잘못했는지(Wrong, 0)를 예측하는 값이다.</li>
<li>1번 학생은 동일한 KC을 적용하는데 있어 일관성이 부족하다.
(앞선 두번의 KC K는 올바르게 해결하지 못하였지만, 뒤에서는 올바르게 해결한 모습 등으로 CFA 값이 0과 1 사이의 값을 가짐)
반면, 2, 3번 학생은 동일한 KC를 일관되게 적용하므로, 숙련도가 높다고 볼 수 있다.</li>
</ul>
</li>
<li><p>교육과정 구조(단원 → 차시)를 기반으로 Attention 모델을 학습시켰다.</p>
</li>
<li><p>각 차시에서 학생이 해결한 문제 $P$를 선택하고, 
$P$에서 사용된 <strong>각 KC에 대한 CFA 값을 예측</strong>하도록 모델을 훈련했다</p>
</li>
<li><p>(CFA 값 예측을 위한) <strong>Attention 모델 구조</strong></p>
<ul>
<li><p>인코더-디코더(encoder-decoder) 구조로, <u>입력은 KC 시퀀스</u>로 구성되며, 인코더는 이 시퀀스를 잠재 표현으로 매핑하고 <u>디코더는 CFA 값을 하나씩 디코딩</u>한다.</p>
</li>
<li><p><strong>Attention 가중치</strong> 계산:</p>
<p>$$
\text{Attention}(\gamma, \kappa, \eta) = \text{softmax}\left(\frac{\gamma \kappa^T}{\sqrt{d_k}} \eta\right)
$$</p>
<ul>
<li>$γ, κ, η$: 각각 쿼리(query), 키(key), 값(value) 행렬.</li>
<li>$d_k$​: 잠재 표현의 차원.</li>
<li><strong>각 KC가 문제 해결에서 차지하는 역할(중요도)</strong>을 나타낸다고 볼 수 있다.</li>
</ul>
</li>
<li><p>KC $K$에 대한 학생 $S$와 문제 $P$의 <strong>숙련도(CFA) 투영</strong> 계산:</p>
<p>$$
\alpha(S, P, K) = \frac{\sum_{v \in \pi(a_i)} v}{\sum_{v \in \pi(a_i)} v + \sum_{v&#39; \in \bar{\pi}(a_i)} v&#39;}
$$</p>
<ul>
<li>$π(⋅)$: 입력 벡터에서 모델이 <u>해당 단계를 올바르게 해결했다고 예측한 값</u>만 추출.</li>
<li>$\bar{\pi}(\cdot)$: $π(⋅)$의 보완 집합으로, 학생이 해당 단계를 틀렸다고 예측한 값 추출.</li>
<li>$i$: <u>$K$가 사용된 모든 인스턴스의 합산.</u></li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h4 id="그래프-g에서-경로-샘플링">그래프 $G$에서 경로 샘플링</h4>
<p>분리된 분포(아래의 식)를 사용해서 <strong>그래프 G에서 경로를 샘플링</strong>한다.</p>
<p>$$
Q(S)⋅Q(K∣S)⋅Q(P∣K,S)
$$<br><br></p>
<ul>
<li><p>$Q(S)$: 학생 노드를 샘플링할 확률.</p>
<ul>
<li>균등 분포로 가정 (즉, 모든 학생이 동일한 확률로 선택됨).</li>
</ul>
</li>
<li><p>$Q(K∣S)$: 학생 $S$에게 주어진 $K$(Knowledge Component)를 샘플링할 확률. 
$$
Q(K|S) = \frac{1}{n} \sum_p \alpha(S, P, K)
$$   </p>
<ul>
<li>$n$: 학생 $S$가 KC $K$를 적용할 기회.</li>
<li>$\alpha(S, P, K)$: 학생 $S$의 문제 $P$에 대한 KC $K$의 숙련도.</li>
</ul>
</li>
<li><p>$Q(P∣K,S)$: S와 K가 주어졌을 때 문제 P를 샘플링할 확률.</p>
</li>
</ul>
<p>$$
Q(P∣K,S)=α(S,P,K)
$$</p>
<br>

<h4 id="mvec-임베딩-학습">MVec 임베딩 학습</h4>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/da8f1265-b676-4330-8a94-cc8960c51ef5/image.png" alt=""></p>
<ol>
<li>$Q(S)$에서 무작위로 학생 $S$ 샘플링.</li>
<li>$Q(K∣S)$에서 $K$를 샘플링.</li>
<li>$Q(P∣K,S)$에서 $P$를 샘플링.</li>
<li>Word2Vec 모델을 통해 이웃 노드를 사용하여, 경로의 각 노드 예측.</li>
<li>Word2Vec 모델의 은닉 계층에서 임베딩 학습.</li>
</ol>
<hr>
<br>

<h3 id="32-non-parametric-clustering">3.2 Non-Parametric Clustering</h3>
<p>DP-Means HDP 클러스터링 기법을 활용한 비모수적 접근법을 이용하되, <strong>전략 대칭성</strong>에 기반하여 <strong>학생과 문제의 MVec 임베딩을 공동으로 클러스터링</strong>하는 것이다.</p>
<p>이후 전체 데이터가 아닌 생성된 <strong>각 클러스터에서 샘플을 선택하여 모델을 훈련</strong>함으로써 정확도를 희생하지 않고 <strong>확장성을 확보</strong>할 수 있다.</p>
<br>

<ul>
<li><p><strong>대칭성(symmetry)에 기반한 비모수적 접근법</strong>을 통해 <strong>학생과 문제</strong>의 MVec 임베딩을 <strong>공동으로 클러스터링</strong>한다.</p>
</li>
<li><p>데이터셋 $D$에 대해, $S$는 학생 집합이고 $P$는 문제 집합이다.</p>
</li>
</ul>
<blockquote>
<p>Definition 1. 전략 불변 분할 (strategy-invariant partitioning)</p>
</blockquote>
<ul>
<li><p>분할 ${S_i}<em>{i=1}^{k_1}$와 ${P_j}</em>{j=1}^{k_2}$로 정의.</p>
</li>
<li><p>모든 $i$, $j$에 대해:
만약 $S,S′∈S_i​$이고 $P,P′∈P_j$​라면, $S$와 $S′$은 각각 $P$와 $P′$에 대해 동일한 전략을 따른다.</p>
</li>
<li><p>$k_1$​: 학생 분할/클러스터의 수, $k_2$​: 문제 분할/클러스터의 수.</p>
</li>
<li><p>전략 불변 분할을 통해 전체 데이터가 아닌 <u>각 클러스터에서 샘플을 선택하여 모델을 훈련</u>함으로써 <u>정확도를 희생하지 않고 확장성을 확보</u>할 수 있다.</p>
<ul>
<li><em>Introduction에서 DNN 모델이 전체 데이터 분포를 기반으로 학습할 때, 다수 그룹에게 편향될 수 있다는 점을 지적한 부분과 관련이 있다.</em></li>
</ul>
</li>
</ul>
<br>

<h4 id="접근법-공식화">접근법 공식화</h4>
<ul>
<li>학생 집합: $S={x_{i1}}<em>{i=1}^{N}$, 문제 집합: $P={x</em>{j2}}_{j=1}^{M}$.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/a0624a6a-c6af-42fc-8d35-6ace38e9f75f/image.png" alt=""></p>
<ul>
<li><p><strong>로컬 클러스터</strong>(Local Clusters): <u>학생 및 문제 각각의 클러스터.</u></p>
</li>
<li><p><strong>글로벌 클러스터</strong>(Global Clusters): <u>학생 클러스터와 문제 클러스터를 결합</u>한 클러스터.</p>
</li>
<li><p><strong>DP-Means HDP 클러스터링</strong>(2.3에서 다룸)</p>
<p>  $$
  \sum_{p=1}^{g} \sum_{x_{ij} \in \ell_p} |x_{ij} - \mu_p|^2 + \lambda_\ell k + \lambda_g g
  $$</p>
<ul>
<li>전역 페널티 $λ_g$의 값이 클수록 적은 수의 거친(cloarse) 클러스터가 생성되고, 페널티 값이 작을수록 많은 수의 세밀한(fine-grained) 클러스터가 생성된다.</li>
</ul>
<ol>
<li><p>초기 클러스터링: $\ell_1, \ell_2, \ldots, \ell_g$는 현재의 글로벌 클러스터.</p>
</li>
<li><p>각 클러스터 내의 전략 대칭성을 기반으로 점수 $S(\ell_1, \ldots, \ell_g)$를 계산한다.</p>
</li>
<li><p>반복(iteration)마다 <u>점수가 개선될 경우, $λ_g$를 점진적으로 감소</u>시켜 더 세밀한 클러스터를 생성한다. (Coarse-to-fine refinement 접근법)</p>
</li>
</ol>
</li>
</ul>
<hr>
<br>

<h3 id="33-refining-clusters-using-symmetry">3.3 Refining Clusters using Symmetry</h3>
<p>전략을 MVec 임베딩과 <strong>위치 인코딩</strong>의 조합으로 표현하며, Smith-Waterman(SW) 알고리즘을 이용하여 위치 임베딩 간의 정렬을 계산함으로써 <strong>전략 대칭성을 계산</strong>하는 것이다.
<br></p>
<ul>
<li><p><strong>글로벌 클러스터</strong>: 각 글로벌 클러스터는 <strong>전략 집합</strong>을 암묵적으로 나타낸다.</p>
<ul>
<li><em>학생-문제 쌍 $(s,p)$이 클러스터에 속할 경우, 이는 $s$가 문제 $p$를 해결할 때 사용한 전략을 나타낸다.</em></li>
</ul>
</li>
<li><p><strong>클러스터 내 전략 간의 대칭성</strong>(symmetric similarity)을 정량화하는 것이 목표이다.</p>
<br>

</li>
</ul>
<h4 id="전략의-근사적-정렬-및-대칭성-계산">전략의 근사적 정렬 및 대칭성 계산</h4>
<ul>
<li><p>각 전략은 <strong>MVec임베딩</strong>과 <strong>위치 인코딩</strong>(positional encodings)의 조합으로 표현된다.</p>
</li>
<li><p>전략에서 특정 KC K를 다음 벡터로 표현:</p>
<p>  $$
  \vec{K} = \vec{K}_e + \vec{K}_p
  $$</p>
<ul>
<li>$\vec{K}_e$​: $K$의 MVec 임베딩.</li>
<li>$\vec{K}_p$​: $K$의 전략 내 위치 인코딩.</li>
</ul>
</li>
<li><p>전략 간 대칭성을 계산하기 위해, 우리는 <strong>위치 임베딩</strong> 간의 정렬을 계산할 때, <strong>Smith-Waterman 알고리즘</strong>(SW)을 이용한다.</p>
<ul>
<li><p><u>두 시퀀스 간 가능한 최적의 정렬을 계산</u>하기 위해 <u>지역 탐색(local search)</u> 수행한다.</p>
</li>
<li><p>유사도 함수가 필요하며, 두 KC 간의 유사도로, $s(K,K′)=\vec{K}^\top \vec{K}&#39;$으로 설정된다. 즉, $K$와 $K′$의 <u>위치 임베딩 간의 코사인 유사도</u>이다.</p>
</li>
<li><p>정렬에 <u>갭(gap, 비어있는 단계)</u>을 남기는 비용을 나타내는 갭 페널티를 필요로 하며, 대칭성을 갭에 대해 불변으로 유지하기 위해 갭 페널티를 0으로 설정한다. 
<em>즉, <u>두 전략이 대칭적이라면 전략에 추가 단계를 포함하는 것이 허용</u>하는 것이다.</em></p>
</li>
<li><p><em>로컬 정렬에 기반한 스코어링 행렬(scoring matrix)을 반복적으로 계산한다. 최적의 정렬에 대한 스코어를 제공하는 스코어링 행렬을 계산하는 최악의 경우 복잡도는 $O(m \cdot n)$이며, 여기서 $m$과 $n$은 전략의 길이를 나타낸다.</em></p>
</li>
</ul>
</li>
</ul>
<br>

<h4 id="전략-대칭성-정량화-및-클러스터링-대칭성-점수">전략 대칭성 정량화 및 클러스터링 대칭성 점수</h4>
<ul>
<li><p>두 전략 $K$와 $K′$ (길이 $n$과 $m$) 간의 대칭성을 SW 알고리즘을 기반으로 정렬 $L(K,K′)$을 통해 계산한다.</p>
<ul>
<li><em>정렬에는 $K$와 $K′$ 각각에서 매칭되거나 정렬된 KC 쌍 또는 갭(gap, 즉 $K$의 KC가 $K′$의 어떤 KC와도 정렬되지 못한 경우)이 포함된다.</em></li>
</ul>
</li>
<li><p>$K$와 $K′$ 간의 대칭 점수 계산:</p>
<p>  $$
  r(K, K&#39;) = \frac{1}{\max(n, m)} \sum_{(K, K&#39;) \in L(K, K&#39;)} (\vec{K}^\top \vec{K}&#39;)
  $$</p>
<ul>
<li>$(K,K′)∈L(K,K′)$: 정렬된 KC 쌍.</li>
<li>$\vec{K}^\top \vec{K}&#39;$: 두 KC의 코사인 유사도. 따라서 $0≤r(K,K′)≤1$.</li>
</ul>
</li>
<li><p>클러스터링 대칭성 추정:</p>
<p>  $$
  S(\ell_1, \ldots, \ell_g) = \frac{1}{g} \sum_{p=1}^g \frac{1}{Z_p} \sum_{K, K&#39; \in T(\ell_p)} r(K, K&#39;)
  $$</p>
<ul>
<li>$T(ℓ_p​)$: 클러스터 $ℓ_p​$에 포함된 모든 전략의 집합.</li>
<li>$Z_p = \frac{2}{|T(\ell_p)|(|T(\ell_p)|-1)}$​: 정규화 항.</li>
<li>$S(ℓ_1​,\dots,ℓ_g​)$의 값이 클수록 $ℓ_1,…,ℓ_g$에 속하는 클러스터링이 높은 전략 대칭성을 가진다.</li>
<li><em>반복(iteration) 중 대칭성 점수 $S$가 감소하지 않는 한, 또는 고정된 반복 횟수 내에서 $\lambda_g$를 $\epsilon$만큼 감소시키면서 $\lambda_g$를 조정한다.(Coarse-to-fine refinement 접근법)</em></li>
</ul>
</li>
</ul>
<hr>
<br>

<h3 id="34-training-the-model">3.4 Training the Model</h3>
<p>학생과 문제 벡터를 입력으로 받아 KC 시퀀스를 출력으로 생성하는 <strong>One-to-Many LSTM 구조</strong>를 사용하여 전략을 예측한다.</p>
<br>
<br>

<h2 id="4-experiments">4. EXPERIMENTS</h2>
<h3 id="43-comparison-to-baselines">4.3 Comparison to Baselines</h3>
<ol>
<li><p>Shakya et al. 의 접근법(<strong>CS</strong>):
 동일한 데이터셋에서 중요도 샘플링(importance sampling)을 사용해 LSTM을 훈련하는 특화된 접근법.
 <em>다만, 숙련도(mastery)나 근사 대칭성(approximate symmetries)을 고려하지 않아 다양한 훈련 인스턴스를 효과적으로 찾지 못한다.</em></p>
</li>
<li><p>계층적 샘플링(<strong>GS</strong>, Group Sampling):
학생이 해결한 문제의 수에 비례하여 샘플링하는 접근법.</p>
</li>
<li><p>랜덤 샘플링(<strong>RS</strong>, Random Sampling):
학생과 문제를 <strong>균등하게 무작위로 샘플링</strong>하는 단순한 방법.</p>
</li>
<li><p><strong>어텐션 샘플링</strong>(<strong>AS</strong>, Attention Sampling):
논문에서의 접근법으로, 대칭성과 숙련도를 고려한 샘플링을 수행하는 접근법.</p>
</li>
</ol>
<hr>
<br>

<h3 id="44-results-and-discussion">4.4 Results and Discussion</h3>
<h4 id="accuracy--scalability">Accuracy &amp; Scalability</h4>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/1f2ae5f1-5c6a-4661-973a-74142eb08e25/image.png" alt=""></p>
<ul>
<li>AS는 BA08 데이터셋(1.6M)의 1% 미만으로 80% 이상의 테스트 정확도를 달성했다.</li>
<li>AS에서 추가적인 숙련도 임베딩 생성 및 비모수적 클러스터링 처리가 필요했음에도 가장 빠른 성능을 보였다.</li>
<li>전체 데이터셋을 사용한 모델 훈련 시도에서 모델 수렴에 실패했다.</li>
</ul>
<hr>
<h4 id="ablation-study">Ablation Study</h4>
<p>접근법에 각 구성 요소를 단계적으로 추가하면서, 훈련 데이터 샘플 크기 변화에 따른 테스트 정확도를 관찰했다.</p>
<ul>
<li><p>No Symmetries(<strong>NS</strong>): 
대칭성을 사용하지 않으며, 클러스터링을 무작위로 수행했다.</p>
</li>
<li><p>StrategySymmetries(<strong>SS</strong>): 
대칭성을 사용하되, <u>숙련도를 이용하여 임베딩을 학습하지 않았다.</u>
<em>Word2Vec 입력으로 학생($S$), 문제($P$), KC($K$)의 삼중 쌍 $(S,P,K)$만 사용하여 임베딩을 생성했다.</em></p>
</li>
<li><p>SS + Mastery(<strong>SS + MS</strong>): 
대칭성과 함께, 숙련도를 포함하여 MVec 임베딩을 학습했다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/483ce160-e150-460b-bb53-0470ed8d3c67/image.png" alt=""></p>
<hr>
<h4 id="fairness">Fairness</h4>
<ol>
<li><p><strong>성취 수준이 다른</strong> 학생그룹에 대해 정확도가 크게 다르지 않는지 확인했다.</p>
</li>
<li><p><strong>희귀 전략을 사용</strong>하는 하위 문제 그룹에서 차별적 오류가 발생하는지 확인했다.
<em>문제 섹션을 $𝐸𝑑𝑖𝑡𝐷𝑖𝑠𝑡𝑎𝑛𝑐𝑒$를 이용해 계산한 전략 간 분산에 따라 
5개의 하위 그룹으로 나누었다. ($𝐸𝑑𝑖𝑡𝐷𝑖𝑠𝑡𝑎𝑛𝑐𝑒 = \frac{변화된 단계 수}{총 단계 수}$)</em></p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/398d094d-e811-45e4-af84-87d13893bab2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vim을 편리하게 사용해보자!]]></title>
            <link>https://velog.io/@banyeah_/Vim-%EC%93%B0%EA%B8%B0-%EC%8B%AB%EC%9D%80%EB%8D%B0</link>
            <guid>https://velog.io/@banyeah_/Vim-%EC%93%B0%EA%B8%B0-%EC%8B%AB%EC%9D%80%EB%8D%B0</guid>
            <pubDate>Thu, 31 Oct 2024 12:25:00 GMT</pubDate>
            <description><![CDATA[<p>Vim보다 VSCode가 훨씬 편리한 탓에, Vim을 정말 정말 쓰기 싫었지만</p>
<p>학교 수업 과제를 학교 Linux 서버에서만 작성하라고 하시더라...
(서버에서는 서버 용량 문제로 VSCode에서 ssh 원격 접속을 금지하는 중)
<del>Cheating 탐지를 위해, 사용한 shell 명령어를 확인한다나 뭐래나...</del></p>
<p>결국 방법은 Vim을 그나마 쓸만하게 만드는 것인가 싶어서
시작하는 Vim Plug-in 잔뜩 설치하기...!!
(하지만 결론은 로컬에서 코드를 작성한 후, 서버로 옮겨적기만 했습니다)</p>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/470fee99-26bd-467f-9cf7-5bdab3bfbea1/image.png" alt=""></p>
<h2 id="1-vimrc-설치">1. vimrc 설치</h2>
<p><a href="https://github.com/amix/vimrc?tab=readme-ov-file">https://github.com/amix/vimrc?tab=readme-ov-file</a></p>
<pre><code class="language-bash">git clone --depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime
sh ~/.vim_runtime/install_awesome_vimrc.sh</code></pre>
<br>

<h3 id="11-vimrc-단축키-요약">1.1. vimrc 단축키 요약</h3>
<ul>
<li><code>:tabnew &lt;파일 이름&gt;</code>: 새 탭 열기</li>
<li><code>:tabnext</code>,<code>gt</code> 키: 다음 탭으로 이동</li>
<li><code>:tabprevious</code>,<code>gT</code> 키: 이전 탭으로 이동</li>
<li><code>_gt</code> 키: _번째 탭으로 이동</li>
<li><code>:tabclose</code>: 탭 닫기</li>
<li><code>Ctrl + w</code>:탭 이동</li>
</ul>
<hr>
<br>

<h2 id="2-nerdtree-설치">2. NERDTree 설치</h2>
<p><a href="https://github.com/preservim/nerdtree">https://github.com/preservim/nerdtree</a></p>
<pre><code class="language-bash"># vim-plug 설치 (이미 설치한 경우 건너뛰기)
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim</code></pre>
<br>

<p><code>~/.vim_runtime/my_configs.vim</code>에 아래 내용 추가</p>
<pre><code>call plug#begin(&#39;~/.vim/plugged&#39;)
&quot; NERDTree 플러그인 추가
Plug &#39;preservim/nerdtree&#39;   &quot; NERDTree 플러그인 추가
call plug#end()</code></pre><br>

<p>Vim을 연 후, 다음 명령어로 플러그인 설치</p>
<pre><code>:PluginInstall</code></pre><br>

<p>이후 다시<code>~/.vim_runtime/my_configs.vim</code>에 아래 내용 추가</p>
<pre><code>&quot; NERDTree를 자동으로 열고, 실행 결과 창 닫기
autocmd VimEnter * NERDTree
autocmd! User PlugClean,PlugInstall,PlugUpdate,PlugUpgrade quit     </code></pre><br>

<h3 id="21-nerdtree-단축키-요약">2.1. NERDTree 단축키 요약</h3>
<ul>
<li><code>o</code>: 폴더 열기/닫기 또는 파일 열기</li>
<li><code>t</code>: 새 탭에서 파일 열기</li>
<li><code>i</code>: 수직 분할 창에서 파일 열기</li>
<li><code>s</code>: 수평 분할 창에서 파일 열기</li>
</ul>
<hr>
<br>

<h2 id="3-vimcolorscheme-설치">3. VimColorScheme 설치</h2>
<p>vimrc에도 colorscheme이 존재하지만 죄다 마음에 안드는 관계로...
<br></p>
<p><a href="https://github.com/rafi/awesome-vim-colorschemes">https://github.com/rafi/awesome-vim-colorschemes</a></p>
<pre><code class="language-bash">git clone https://github.com/rafi/awesome-vim-colorschemes.git \
    ~/awesome-vim-colorschemes
mv ~/awesome-vim-colorschemes/colors ~/.vim/colors
rm -rf ~/awesome-vim-colorschemes</code></pre>
<br>

<p><code>~/.vim_runtime/my_configs.vim</code>에 아래 내용 추가</p>
<pre><code>&quot;ColorScheme 적용
if has(&quot;syntax&quot;)
     syntax on
endif

set autoindent
set cindent
set nu

colo onehalfdark

set laststatus=2
set statusline=\ %&lt;%l:%v\ [%P]%=%a\ %h%m%r\ %F\</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI에서 진행 상황을 전송하기 (근데, 동시성처리를 곁들인...)]]></title>
            <link>https://velog.io/@banyeah_/FastAPI%EC%97%90%EC%84%9C-%EC%A7%84%ED%96%89-%EC%83%81%ED%99%A9%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0-%EA%B7%BC%EB%8D%B0-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%B2%98%EB%A6%AC%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8</link>
            <guid>https://velog.io/@banyeah_/FastAPI%EC%97%90%EC%84%9C-%EC%A7%84%ED%96%89-%EC%83%81%ED%99%A9%EC%9D%84-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0-%EA%B7%BC%EB%8D%B0-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%B2%98%EB%A6%AC%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8</guid>
            <pubDate>Sat, 05 Oct 2024 10:44:40 GMT</pubDate>
            <description><![CDATA[<p><em>글을 작성하기 전에 미리 말을 하자면, 학교에서 진행한 프로젝트에서 사용한 코드라 <strong>야매</strong>라는 점을 꼭 말하고 싶다. 실제로 아래처럼 굴린다면 무슨 문제가 생길 지 장담하지 못한다...</em></p>
<br>
<br>

<h3 id="🫠-fastapi에서-진행-상황을-전송하려는-이유">🫠 FastAPI에서 진행 상황을 전송하려는 이유</h3>
<ol>
<li><p>현재 프로젝트에서 사용하는 API가 <strong>여러 단계의 API를 연속적</strong>으로 호출하는 형태임.</p>
<ul>
<li>예를 들자면, &#39;<strong>파스타 요리 API</strong>&#39;를 만드는데, 그 API 내부에서 &#39;재료 준비 API&#39;, &#39;물 끓이기 API&#39;, &#39;면 삶기 API&#39;, &#39;소스 만들기 API&#39;, ...를 연속적으로 호출하는 상황.<br>
</li>
</ul>
</li>
<li><p>프론트엔드 측에서 <strong>현재 진행 상황을 공유</strong>받고 싶어함.</p>
<ul>
<li>즉, 내 파스타가 어느 단계까지 만들어 졌는지, 그 단계까지 진행한 결과는 어떻게 되는지를 표시하고자 함. (현재, 재료 준비를 완료했고, 무슨무슨 재료를 준비했다 등) </li>
</ul>
</li>
</ol>
<br>

<hr>
<h2 id="😵-문제-상황">😵 문제 상황</h2>
<p>우리는 <strong>파스타 네 개를 동시에 만들고</strong> API로 각 파스타의 진행 상황을 공유받고 싶었다.</p>
<br>

<h3 id="🤪-기존-처리-방식">🤪 기존 처리 방식</h3>
<p>기존 처리 방식은 &#39;파스타 네 개를 동시에 만드는 것&#39;을 프론트에게 떠넘기는 작전이었다. <del>(아무래도 내가 프론트와 백 모두 하고 있었기에 가능한 작전이 아니었을까...)</del>
<br></p>
<p><strong>장점</strong></p>
<ul>
<li>FastAPI에서 StreamingResponse로 진행 상황을 전송하는 코드를 GPT에게서 얻을 수 있었다. <del>(구현이 쉬웠음)</del></li>
</ul>
<br>

<p><strong>단점</strong></p>
<ul>
<li>프론트엔드에서 동시성 처리를 하는 것이 일반적이지 않다.</li>
<li>프론트엔드 작업을 Streamlit로 해서 Python을 사용했기에 동시성 처리가 가능했지, 아마 React에서 TS를 썼다면 불가능 했을지도...</li>
</ul>
<br>

<h4 id="기존-프론트엔드">기존 프론트엔드</h4>
<ol>
<li>Thread를 이용해 동시에 4개의 API에 요청</li>
<li>각 API의 진행 상황을  Thread ID를 추가해 Queue에 Put</li>
<li>Queue에서 Get하면서 각 파스타의 진행 상황을 프론트엔드에 표시</li>
</ol>
<ul>
<li>이때 중요한 것은 각 파스타 요리의 성공/실패 여부를 확인해서, <strong>API 응답이 종료되었는지를 감지해야</strong>한다.</li>
</ul>
<br>

<h4 id="기존-백엔드-코드-일부">기존 백엔드 코드 일부</h4>
<pre><code class="language-python">import os
from dotenv import load_dotenv
from fastapi import APIRouter
from fastapi.responses import StreamingResponse

import json
import httpx
from urllib.parse import urlencode

load_dotenv()
public_api_endpoint = os.getenv(&quot;PUBLIC_API_ENDPOINT&quot;) 
# 배포 시를 고려해 .env를 이용한 전역 변수 사용

router = APIRouter()

async def pasta_cooking(***):
    try:
        #### 재료 준비 ####
        query = {***}
        query_string = urlencode(query)

        url = f&quot;{public_api_endpoint}/material/?{query_string}&quot;

        async with httpx.AsyncClient() as client:
            response = await client.get(url) # 재료 준비 API 호출

        if response.status_code != 200: # 응답 오류
            raise Exception(response.json())

        result = response.json()[&quot;data&quot;]

        yield json.dumps({
            &quot;step&quot;: &quot;재료 준비&quot;,
            &quot;result&quot;: result
        })+ &quot;\n&quot; # 진행 상황 전송, &#39;\n&#39;으로 전송이 끝났음을 표시


        #### 물 끓이기 ####
        url = f&quot;{public_api_endpoint}/water_boil/&quot;

        async with httpx.AsyncClient() as client:
            response = await client.get(url) # 물 끓이기 API 호출

        if response.status_code != 200: # 응답 오류
            raise Exception(response.json())

        result = response.json()[&quot;data&quot;]

        yield json.dumps({
            &quot;step&quot;: &quot;물 끓이기&quot;,
            &quot;result&quot;: result
        })+ &quot;\n&quot; # 진행 상황 전송, &#39;\n&#39;으로 전송이 끝났음을 표시


        #### 이후 내용 중략... ####


    except Exception as e:
        yield json.dumps({
            &quot;step&quot;: &quot;파스타 요리 실패&quot;,
            &quot;result&quot;: str(e),
        })+ &quot;\n&quot;


@router.get(
    &quot;/pasta/&quot;,
    summary=&quot;***&quot;,
    description=&quot;***&quot;,
    tags=[&quot;Pasta&quot;],
    responses={
        200: {
            &quot;description&quot;: &quot;***&quot;,
            &quot;content&quot;: {
                &quot;application/json&quot;: {
                    &quot;example&quot;: {
                        &quot;step&quot;: &quot;&quot;,
                        &quot;result&quot;: {
                            &quot;&quot;: &quot;&quot;
                        },
                    }
                }
            },
        },
        422: { # 실제로 422 에러를 날리지는 않지만, 요리 실패 시를 명시하기 위해
            &quot;description&quot;: &quot;Fail to cooking a pasta&quot;,
            &quot;content&quot;: {
                &quot;application/json&quot;: {
                    &quot;example&quot;: {
                        &quot;step&quot;: &quot;파스타 요리 실패&quot;,
                        &quot;result&quot;: &quot;A error message&quot;,
                    }
                }
            },
        },
    },
)
async def pasta(***):
    return StreamingResponse(
        pasta_cooking(***), 
        media_type=&quot;text/event-stream&quot;
    )</code></pre>
<br>

<h3 id="🤯-새로운-처리-방식">🤯 새로운 처리 방식</h3>
<p><em>새로운 처리 방식을 들여 온 이유는 단지 배포한 Streamlit 웹앱에서 문제가 생겼기 때문이다. (근데 문제 원인은 프론트에 작업을 떠넘긴 것이 아니라, &#39;파스타 요리 실패&#39; 응답을 보내는 로직이 모종의 이유로 작동하지 않았기 때문이었다...)</em></p>
<br>

<p><strong>장점</strong></p>
<ul>
<li>백엔드에서 동시성 처리를 해서 프론트엔드로 보내는 것이 일반적인 방식이다.</li>
</ul>
<br>

<p><strong>단점</strong></p>
<ul>
<li>FastAPI에서 StreamingResponse로 진행 상황을 전송하는 함수를 동시성 처리하는 코드의 예시를 찾을 수 없었다. <del>(구현이 어려웠음)</del></li>
</ul>
<br>

<h4 id="새로운-프론트엔드-코드-일부">새로운 프론트엔드 코드 일부</h4>
<pre><code class="language-python">import os
import json
import requests
import sseclient
from dotenv import load_dotenv
from urllib.parse import urlencode

load_dotenv()
public_api_endpoint = os.getenv(&quot;PUBLIC_API_ENDPOINT&quot;)

query = {***}
query_string = urlencode(query)

url = (f&quot;{public_api_endpoint}/pasta/?{query_string}&amp;pasta_num=4&quot;
with requests.get(url, headers={&quot;Accept&quot;: &quot;text/event-stream&quot;}, stream=True) as response:
    client = sseclient.SSEClient(response)
    for event in client.events():
        if event.event == &quot;end&quot;:
            break # API 응답 종료
        if event.data:
            client_data = json.loads(event.data)
            ## 내용 생략 ##</code></pre>
<br>

<h4 id="새로운-백엔드-코드-일부">새로운 백엔드 코드 일부</h4>
<p><em>방식 자체는 &#39;기존 프론트엔드&#39;처럼 동시에 함수를 실행하되, 각각의 진행 상황을 ID를 추가해 Queue에 저장하고 빼내는 방식으로 유사함</em></p>
<pre><code class="language-python">import os
from dotenv import load_dotenv
from fastapi import APIRouter
from fastapi.responses import StreamingResponse

import json
import httpx
import asyncio
from typing import AsyncGenerator
from urllib.parse import urlencode

load_dotenv()
public_api_endpoint = os.getenv(&quot;PUBLIC_API_ENDPOINT&quot;) 
# 배포 시를 고려해 .env를 이용한 전역 변수 사용

router = APIRouter()

async def pasta_cooking(pasta_id: int, queue: asyncio.Queue, ***):
    try:
        #### 재료 준비 ####
        query = {***}
        query_string = urlencode(query)

        url = f&quot;{public_api_endpoint}/material/?{query_string}&quot;

        async with httpx.AsyncClient() as client:
            response = await client.get(url) # 재료 준비 API 호출

        if response.status_code != 200: # 응답 오류
            raise Exception(response.json())

        result = response.json()[&quot;data&quot;]

        await queue.put({
            &quot;id&quot;: pasta_id,
            &quot;step&quot;: &quot;재료 준비&quot;,
            &quot;result&quot;: result
        })


        #### 물 끓이기 ####
        url = f&quot;{public_api_endpoint}/water_boil/&quot;

        async with httpx.AsyncClient() as client:
            response = await client.get(url) # 물 끓이기 API 호출

        if response.status_code != 200: # 응답 오류
            raise Exception(response.json())

        result = response.json()[&quot;data&quot;]

        await queue.put({
            &quot;id&quot;: pasta_id,
            &quot;step&quot;: &quot;물 끓이기&quot;,
            &quot;result&quot;: result
        })


        #### 이후 내용 중략... ####


    except Exception as e:
        await queue.put({
            &quot;id&quot;: pasta_id,
            &quot;step&quot;: &quot;파스타 요리 실패&quot;,
            &quot;result&quot;: str(e),
        })


async def n_pasta_cooking(
    queue: asyncio.Queue, pasta_num: int
) -&gt; AsyncGenerator[str, None]:
    pasta_finish = 0 # 실패/완성된 파스타 수
    while pasta_finish &lt; pasta_num:
        data = await queue.get()
        if data[&quot;step&quot;] == &quot;파스타 요리 실패&quot; or data[&quot;step&quot;] == &quot;파스타 요리 완성&quot;:
            pasta_finish += 1
        yield f&quot;data: {json.dumps(data)}\n\n&quot;

    yield &quot;event: end\ndata: Stream closed\n\n&quot; # 파스타 요리 종료


@router.get(
    &quot;/pasta/&quot;,
    summary=&quot;***&quot;,
    description=&quot;***&quot;,
    tags=[&quot;Pasta&quot;],
    responses={
        200: {
            &quot;description&quot;: &quot;***&quot;,
            &quot;content&quot;: {
                &quot;application/json&quot;: {
                    &quot;example&quot;: {
                        &quot;id&quot;: 0,
                        &quot;step&quot;: &quot;&quot;,
                        &quot;result&quot;: {
                            &quot;&quot;: &quot;&quot;
                        },
                    }
                }
            },
        },
        422: { # 실제로 422 에러를 날리지는 않지만, 요리 실패 시를 명시하기 위해
            &quot;description&quot;: &quot;Fail to cooking a pasta&quot;,
            &quot;content&quot;: {
                &quot;application/json&quot;: {
                    &quot;example&quot;: {
                        &quot;id&quot;: 0,
                        &quot;step&quot;: &quot;파스타 요리 실패&quot;,
                        &quot;result&quot;: &quot;A error message&quot;,
                    }
                }
            },
        },
    },
)
async def pasta(***, pasta_num: int = 1):
    queue: asyncio.Queue = asyncio.Queue()

    pasta_set = []
    for i in range(pasta_num):
        pasta_id = i
        pasta = asyncio.create_task(pasta_cooking(pasta_id, queue, ***))
        pasta_set.append(pasta)

    return StreamingResponse(
        n_pasta_cooking(queue, pasta_num), media_type=&quot;text/event-stream&quot;
    )</code></pre>
<br>

<p>분명 공식 문서에서의 설명이나 이런 시도를 했던 누군가를 찾을 수 있지 않을까 싶었지만 <del>(GPT 마저도...)</del>, 결국 찾지 못해서 구현에 시간이 오래 걸렸다. 구현 자체는 프론트엔드에서의 동시성처리가 백엔드로 넘어간 것뿐이라서 고생에 비해 크게 달라진 것이 없는게 아쉬울 뿐이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LangChain에서 OpenAI API의 n 파라미터 사용]]></title>
            <link>https://velog.io/@banyeah_/LangChain%EC%97%90%EC%84%9C-OpenAI-API%EC%9D%98-n-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@banyeah_/LangChain%EC%97%90%EC%84%9C-OpenAI-API%EC%9D%98-n-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Mon, 30 Sep 2024 05:45:44 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-n-파라미터를-사용하려던-이유">🤔 n 파라미터를 사용하려던 이유</h3>
<ol>
<li><p>첫 번째 이유는 단순히 가설이었다. </p>
<ul>
<li><p>n 파리미터를 통해 받는 n개의 응답은 서로 unique하지 않을까?</p>
</li>
<li><p>서로 다른 n개의 응답이 필요한데, 단지 temperature와 top_p를 올리는 것만으로는 해결이 안되었기 때문이다.</p>
</li>
<li><p><del>결론은 대충 예상하긴 했지만 n번 호출과 비슷하게 작동하는 것으로 보였다. (동일한 응답이 존재했기 때문...)</del></p>
<br>
</li>
</ul>
</li>
<li><p>두 번째 이유는 input tokens에서의 이점이다.</p>
<ul>
<li>확인 결과, input tokens는 n 파라미터 사용 시가 1회 호출 시와 동일했기 때문</li>
<li>하지만 당연하게도 completion tokens는 n회 만큼 증가했다.</li>
</ul>
</li>
</ol>
<br>

<h3 id="😥-문제의-배경">😥 문제의 배경</h3>
<ol>
<li>LangChain 라이브러리의 JsonOutputParser 사용<ul>
<li>지금이야 OpenAI에서 Structured Output을 제공하지만, 몇달 전만해도 그렇지하지 않았기 때문에...</li>
<li>아마 이것도 곧 OpenAI의 Structured Output을 이용하도록 변경할 듯</li>
</ul>
</li>
</ol>
<br>

<ol start="2">
<li>공식 문서에서의 JsonOutputParser에 n 파라미터를 붙이는 것으로 n 파라미터가 <strong>작동하지 않음</strong><ul>
<li>공식문서 링크: <a href="https://python.langchain.com/v0.2/docs/how_to/output_parser_json/">https://python.langchain.com/v0.2/docs/how_to/output_parser_json/</a></li>
<li>기존 코드의 요약본은 아래와 같다. (민감한 부분은 ***로 블라인드 처리)<pre><code class="language-python">from langchain_core.prompts import ChatPromptTemplate
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
</code></pre>
</li>
</ul>
</li>
</ol>
<p>prompt = ChatPromptTemplate.from_messages(
    [
        (&quot;system&quot;, &quot;***&quot;),
        (&quot;user&quot;, &quot;{user_input}&quot;),
    ]
)</p>
<p>llm = ChatGooroomeeAI(model=&quot;***&quot;)
model = prompt | llm.bind(temperature=1.0, maxTokens=1000, n=4)</p>
<p>async def gen(prompt_template, <strong><em>):
    output_parser = JsonOutputParser(pydantic_object=OutputFormat)
    prompt = PromptTemplate(
        template=prompt_template,
        input_variables=[
            &quot;</em></strong>&quot;,
            &quot;<strong>*&quot;,
            &quot;*</strong>&quot;,
        ],
        partial_variables={
            &quot;format_instructions&quot;: output_parser.get_format_instructions()
        },
    )</p>
<pre><code>chain = prompt | model | output_parser
response = chain.invoke(
    {
        &quot;***&quot;: ***,
        &quot;***&quot;: ***,
        &quot;***&quot;: ***,
    }
)
return response</code></pre><p>class OutputFormat(BaseModel):
    <strong><em>: str = Field(description=&quot;</em></strong>&quot;)
    <strong><em>: str = Field(description=&quot;</em></strong>&quot;)
    process: dict[str, str] = Field(
        description=&quot;&quot;&quot;응답 생성 과정으로, &#39;Step _&#39;을 key로 하고 해당 Step의 과정을 진행한 내용을 value로 한다.
        응답 예시)
        Step 1: 응답 생성 과정의 첫번째 과정 진행, 
        Step 2: 응답 생성 과정의 두번째 과정 진행,
        ...&quot;&quot;&quot;
    )</p>
<pre><code>
&lt;br&gt;

### 😎 문제 해결 방법
1. 먼저 동일한 문제를 겪는 사람을 구글 검색을 통해 찾았다. ~~(경험 상 이런 문제는 GPT가 크게 도움이 되지 않기 때문에...)~~
    * 그리고 두 개를 찾을 수 있었다. 해당 링크는 아래 첨부한다.
    * 링크 1: https://github.com/langchain-ai/langchain/issues/6227
    * 링크 2: https://github.com/langchain-ai/langchain/issues/8789

&lt;br&gt;

2. 해결 코드 작성 과정은 아래와 같다.
    1. 먼저 위 코드처럼 
    ```python
    chain = prompt | model | output_parser
    ```
    이렇게 pipe로 연결된 경우에서 chain.\_generate()를 호출할 수가 없었다.

    &lt;br&gt;

    2. 따라서 링크 1의 내용처럼
    ```python
    response = llm._generate([[message]])
    ```
    위의 프롬프트 내용을 message로 변경해야했다.

    &lt;br&gt;

    3. 그 뒤로는 계속 변수 type 문제 때문에 헤맸었다. ~~(계속 호출이 불가능한 type이네 뭐네 오류 메세지가 떴음)~~ 사실 n개의 응답을 개별로 분리해 parsing하는 부분에서도 헤맸었다.

    &lt;br&gt;

    4. 결과적으로 해당 문제를 해결한 코드는 아래와 같다.
```python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field

llm = ChatOpenAI(model=&quot;***&quot;, temperature=1.0, max_tokens=2000, n=4)
# max_tokens 값을 올려야 응답이 중간에 끊기는 것을 방지 가능

async def gen(prompt_template, ***):
    output_parser = JsonOutputParser(pydantic_object=OutputFormat)
    prompt = ChatPromptTemplate.from_messages(
        [
            (&quot;system&quot;, &quot;***&quot;),
            (&quot;user&quot;, prompt_template), # 바로 prompt_template 사용
        ]
    )

    response = llm._generate( # llm._generate 호출
        prompt.invoke( # prompt에 변수 삽입
            {
                &quot;***&quot;: ***,
                &quot;***&quot;: ***,
                &quot;***&quot;: ***,
                &quot;format_instructions&quot;: output_parser.get_format_instructions(),
            }
        ).to_messages() # message로 형식으로 변경
    )

    ## n개의 응답을 parsing하고 dict으로 분리 ##
    response_dict = {}
    for idx, res in enumerate(response.generations):
        response_dict[idx] = output_parser.parse(res.text) 
        # outputParser를 이용한 parsing

    return response_dict</code></pre><br>

<p>나와 동일한 방식으로 <code>ChatPromptTemplate</code>을 사용하면서 n 파라미터를 사용하려고 시도한 경우를 찾지 못해서 해결에 난항을 겪었었다.
이 글을 보게 될 다른 이는 금방 해결할 수 있기를 바라며 글을 남긴다.</p>
<br>
<br>

<p><em>langchain n param
langchain n param not work</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Streamlit 페이지 AWS에 배포]]></title>
            <link>https://velog.io/@banyeah_/Streamlit-%ED%8E%98%EC%9D%B4%EC%A7%80-AWS%EC%97%90-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@banyeah_/Streamlit-%ED%8E%98%EC%9D%B4%EC%A7%80-AWS%EC%97%90-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Fri, 28 Jun 2024 14:34:55 GMT</pubDate>
            <description><![CDATA[<h3 id="인스턴스-생성">인스턴스 생성</h3>
<ol>
<li><p>상단 검색바에 <code>EC2</code> 검색</p>
</li>
<li><p>왼쪽 사이드 바에서 <code>인스턴스</code> 클릭</p>
</li>
<li><p>우상단 <code>인스턴스 시작</code> 클릭
<img src="https://velog.velcdn.com/images/banyeah_/post/416dd2d4-f075-4f8a-930c-c2592bee51fc/image.png" alt="">    </p>
</li>
<li><p>인스턴스 이름 작성 후, <code>Ubuntu</code> 선택
<img src="https://velog.velcdn.com/images/banyeah_/post/e7bccc89-9aaf-498c-b82a-b5db2cfc81da/image.png" alt=""></p>
</li>
<li><p><code>키 페어</code> 생성 후, 저장 필수!
이후 인스턴스를 SSH로 접속하려면 필요하다.
<img src="https://velog.velcdn.com/images/banyeah_/post/e21ea653-f665-40bb-94cd-b4a5755cea2c/image.png" alt=""></p>
</li>
<li><p>Anaconda 설치를 해야하기에 16GiB로 설정
<img src="https://velog.velcdn.com/images/banyeah_/post/5fb81929-e816-4e38-9d0d-4aacdb240188/image.png" alt=""></p>
</li>
<li><p><code>인스턴스 시작</code> 클릭  </p>
<br>
</li>
</ol>
<hr>
<h3 id="ssh로-인스턴스-접속">SSH로 인스턴스 접속</h3>
<ol>
<li><p>생성한 인스턴스 체크 후, 우상단 <code>인스턴스 시작</code> 클릭
<img src="https://velog.velcdn.com/images/banyeah_/post/f8749a65-f4e1-48e6-9c77-76cfbf701364/image.png" alt=""></p>
</li>
<li><p><code>SSH 클라이언트</code> 탭 선택 후, 아래 <code>예</code>를 복사한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/5225dfc2-1820-439d-bc03-bf7cd275700e/image.png" alt=""></p>
</li>
<li><p>Windows PowerShell을 열어서 복사한 내용을 붙여넣고, 키 페어의 위치에 맞게 경로를 수정한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/e72da636-35c8-423b-85de-bf11250e3ac4/image.png" alt=""></p>
</li>
</ol>
<hr>
<h3 id="conda-설치">Conda 설치</h3>
<ol>
<li><p><a href="https://www.anaconda.com/download/success">https://www.anaconda.com/download/success</a> 에서 다운로드 링크를 우클릭해서 복사한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/14aa5b51-17fa-4a4e-a155-6e9a352d22e8/image.png" alt=""></p>
</li>
<li><p><code>wget [복사한 링크]</code>로 아나콘다를 다운로드한다.</p>
</li>
<li><p>다운로드 받은 파일을 sh로 실행한다. 
<img src="https://velog.velcdn.com/images/banyeah_/post/21a57281-9ae4-45f8-bc3a-4bc0fb8716f8/image.png" alt=""></p>
</li>
<li><p>conda 명령어 설정을 한다.
<code>vi ~/.bashrc</code>로 .bashrc 편집기를 열고</p>
<p>파일의 맨 마지막에 아래 문구를 넣는다.</p>
<pre><code class="language-bash">export PATH=~/anaconda3/bin:~/anaconda3/condabin:$PATH</code></pre>
<p><code>source ~/.bashrc</code>로 수정한 내용을 적용한다.
<code>conda -V</code>를 실행해서 버전이 확인되면 끝!</p>
<p>만약 버전 확인이 안되는 경우, 인스턴스를 중지했다가 다시 시작한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/9179e9b1-8f9f-4431-b3ff-bc1f15bfc9e1/image.png" alt=""></p>
</li>
<li><p>conda 가상 환경 접속
<code>python --version</code>으로 설치된 버전을 확인 후,
<code>conda create -n [이름] python=[버전]</code>로 가상환경을 만든다.   </p>
<p><code>conda update -n base -c defaults conda</code>로 conda를 업데이트하고
<code>conda activate [이름]</code>로 가상 환경에 접속한다.</p>
</li>
</ol>
<hr>
<h3 id="streamlit-실행">Streamlit 실행</h3>
<ol>
<li><p><code>git clone [깃허브 주소]</code>로 실행할 streamlit app 파일을 가져온다. </p>
<p>아마도 private repository라면 로그인을 해야하는데 Password에 <code>token</code>을 넣는다.</p>
<p>Settings -&gt; Developer settings -&gt; Personal acess tokens -&gt; Tokens (classic)에서 새 token을 만든다.
token을 만들 때는 repo, read:org, gist에 체크를 해준다.</p>
<p>생성된 token은 한번만 보여지니 안전한 곳에 저장하자.   </p>
<br>
</li>
<li><p>Streamlit 설치</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade
sudo apt install python3-pip
pip install streamlit</code></pre>
<p>을 순서대로 실행하고</p>
<p>streamlit app 파일이 있는 디렉토리로 이동해서
<code>streamlit run [실행파일].py</code>를 실행한다.</p>
<p>이때 필요한 라이브러리가 있다면 설치해준다.
ex) langchain 등</p>
<br>
</li>
<li><p>이때 그냥 실행하면 Windows Powershell을 종료했을 때 Streamlit 또한 같이 종료되기 때문에,</p>
<pre><code class="language-bash">nohup streamlit run [실행파일].py</code></pre>
<p>으로 실행할 수도 있다.</p>
<p>나중에 streamlit app 실행을 종료하려면</p>
<pre><code class="language-bash">ps -ef | grep streamlt
kill -9 [PID 값]</code></pre>
<p>을 입력하면 된다.
<img src="https://velog.velcdn.com/images/banyeah_/post/36f3008e-1e8d-437c-b63c-c04fa05a3a78/image.png" alt=""></p>
</li>
</ol>
<hr>
<h3 id="페이지-접속">페이지 접속</h3>
<ol>
<li><p>보안 규칙 수정
파란색 글씨로 표시된 <code>(launch-wizard-4)</code>를 클릭한다.
<img src="https://velog.velcdn.com/images/banyeah_/post/f26a1c18-2b7c-4b6f-9a80-3bae6c2ba11d/image.png" alt=""></p>
<p>이후 <code>인바운드 규칙 편집</code>으로 들어가서
아래 사진처럼 HTTPS, HTTP와 사용자 지정 TCP에서 8501 포트(Streamlit이 사용하는 포트)를 추가해준다.
<img src="https://velog.velcdn.com/images/banyeah_/post/98e7c299-9a08-48d8-acbd-e5e20cd17116/image.png" alt=""></p>
</li>
<li><p>퍼블릭 IPv4 주소를 복사하고 뒤에 <code>:8501</code> 덧붙여 주소에 붙여넣으면 접속할 수 있다.
<img src="https://velog.velcdn.com/images/banyeah_/post/742e429a-5df0-41b4-9e05-c2dd6ba26b6e/image.png" alt=""></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[136. Single Number]]></title>
            <link>https://velog.io/@banyeah_/136.-Single-Number</link>
            <guid>https://velog.io/@banyeah_/136.-Single-Number</guid>
            <pubDate>Fri, 31 May 2024 15:18:18 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>문제 해결 강의에서 leetcode의 Single Number와 비슷한 문제 해결 고민을 한 내용을 정리해 보았다.
이후에 Single Number 문제를 해결한 코드를 제시하겠다.
<br></p>
<p>문제 내용 요약은 아래와 같다.</p>
<blockquote>
<p>자연수가 여러 개 제시된다. 이때 하나의 수만 한 개만 제시되고, 나머지 수들은 두 개씩 제시된다. 한 개만 제시된 수를 구하라.</p>
</blockquote>
<br>

<hr>
<h3 id="a안-한-인덱스의-짝-존재-여부-확인">A안) 한 인덱스의 짝 존재 여부 확인</h3>
<p>(정렬없이) 인덱스 0의 짝 존재 여부 확인 -&gt; 인덱스 1의... -&gt; 인덱스 2의... -&gt; 인덱스 n-1의... 를 순서대로 확인하는 방법이다.</p>
<p>이때, 확인된 짝의 인덱스를 범위 밖의 값으로 변경하거나 linked list 자료형을 이용해 아예 값을 제공하는 방법으로 약간 시간을 줄일 수 있을 것 같다.</p>
<p>시간 복잡도는 O($n^2$)이다.
<br></p>
<hr>
<h3 id="b안-check-배열-이용">B안) check 배열 이용</h3>
<p>check 배열을 따로 두고 해당 배열의 인덱스를 제공되는 수로 생각하여, 수가 확인되면 값을 1로 다시 확인되면 0으로 바꾸는 방법이다.</p>
<p>결론적으로 check 배열에서 홀로 값이 1인 인덱스 값이 답이 된다.</p>
<p>시간 복잡도는 O($n$)이다.</p>
<p>다만, 입력 범위가 작을 때 유효한 방법이고, 입력 값의 범위가 크다면 메모리 초과 위험이 발생한다. 
<br></p>
<hr>
<h3 id="c안-정렬-이후-확인">C안) 정렬 이후 확인</h3>
<p>O($n logn$) 시간 복잡도의 정렬 알고리즘을 사용해 정렬하고, 연속된 두 수가 짝을 이루는 지 확인하는 방법이다.</p>
<p>시간 복잡도는 O($nlogn$)이다.
<br></p>
<hr>
<h3 id="d안-hashing">D안) Hashing</h3>
<p>Hashing을 이용하는 방법이다. Hash 함수를 잘 구현한다면 시간복잡도를 O($n$)으로 낮출 수 있으며, check 배열을 사용하는 B안에 비해서 메모리 사용을 줄일 수 있다.</p>
<p>다만, 구현 방법이 굉장히 까다로울 것이다.
<br></p>
<hr>
<h3 id="e안-비트-연산-이용">E안) 비트 연산 이용</h3>
<p>비트 연산 중에서 ^(Xor)를 이용하는 방법이다. 같은 두 수를 ^(Xor)하면 0이 되고, 결합 법칙(세 수 이상의 연산에서 순서를 바꿔도 결과가 동일)이 성립한다.</p>
<p>따라서 제시된 모든 수를 ^(Xor)하고 난 뒤 남은 수는 한 개만 제시된 수이다.
<br></p>
<hr>
<br>
<br>

<h3 id="single-number-문제-해결-코드">Single Number 문제 해결 코드</h3>
<p>🔗<a href="https://leetcode.com/problems/single-number/">https://leetcode.com/problems/single-number/</a>
<img src="https://velog.velcdn.com/images/banyeah_/post/cc160fea-439c-4d6b-ac7e-d66697553d14/image.png" alt=""></p>
<pre><code class="language-c">int singleNumber(int* nums, int numsSize) {
    /* using check array */
    char pos[300001] = {0};
    char neg[300001] = {0};

    for (int i = 0; i &lt; numsSize; i++) {
        if (nums[i] &gt;= 0)
            pos[nums[i]] = pos[nums[i]] == 0? 1 : 0; // toggle
        else
            neg[-nums[i]] = neg[-nums[i]] == 0? 1 : 0; // toggle
    }

    for (int i = 0; i &lt; 300001; i++) {
        if (pos[i] == 1)
            return i;

        if (neg[i] == 1)
            return -i;
    }

    return 0;
}</code></pre>
<p>위의 코드는 leetcode의 Single Number 문제의 경우, 입력 범위가 작기 때문에 간단한 B안을 이용해서 해결한 것이다.
<br></p>
<p>반면, 아래 코드는 비트 연산자를 이용해 해결해 보았다.</p>
<pre><code class="language-c">int singleNumber(int* nums, int numsSize) {
    /* using bitwise operator */
    int target = nums[0];
    for (int i = 1; i &lt; numsSize; i++)
        target ^= nums[i];

    return target;
}</code></pre>
<p>실행 시간의 경우, check 배열을 이용하는 코드가 13ms, 비트 연산을 이용하는 코드가 11ms가 나온다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitHub Profile README 꾸미기]]></title>
            <link>https://velog.io/@banyeah_/GitHub-Profile-README-%EA%BE%B8%EB%AF%B8%EA%B8%B0</link>
            <guid>https://velog.io/@banyeah_/GitHub-Profile-README-%EA%BE%B8%EB%AF%B8%EA%B8%B0</guid>
            <pubDate>Thu, 30 May 2024 11:31:33 GMT</pubDate>
            <description><![CDATA[<h2 id="미리보는-완성본">미리보는 완성본</h2>
<p><img src="https://velog.velcdn.com/images/banyeah_/post/f8e9d42d-22b8-45ae-91d9-ea02cd11e07f/image.jpeg" alt="">
🔗<a href="https://github.com/BanYeah/BanYeah?tab=readme-ov-file">https://github.com/BanYeah/BanYeah?tab=readme-ov-file</a></p>
<hr>
<h2 id="시작">시작</h2>
<p>먼저 본인의 GitHub 아이디로 된 Public 레포지토리를 만든다.
이때, README.md 파일 추가 옵션에 체크한다.</p>
<p>그리고 해당 README.md 파일에 아래 내용들을 채우면 된다.</p>
<br>

<hr>
<h2 id="header-꾸미기">Header 꾸미기</h2>
<p>🔻 예시 코드
<img src="https://velog.velcdn.com/images/banyeah_/post/929b82bb-c489-4f04-9935-ff6a1f842153/image.png" alt=""></p>
<pre><code>![header](https://capsule-render.vercel.app/api?type=venom&amp;color=2B2D3D&amp;height=200&amp;section=header&amp;text=BanYeah.&amp;fontColor=000000&amp;fontSize=64&amp;stroke=BE95E4)</code></pre><br>

<p>아래 링크에서
🔗<a href="https://github.com/kyechan99/capsule-render">https://github.com/kyechan99/capsule-render</a>
사용한 속성에 대한 설명을 확인할 수 있다. 
(아래는 type 속성 값마다 header의 형태)
<img src="https://velog.velcdn.com/images/banyeah_/post/95f0f057-4ec4-4f00-8a81-280dc51c6463/image.png" alt="">
<br></p>
<hr>
<h2 id="github-stats와-많이-사용한-언어">GitHub Stats와 많이 사용한 언어</h2>
<p>🔻 예시 코드
<img src="https://velog.velcdn.com/images/banyeah_/post/b3e84f08-aa24-4e1c-bb20-b03dc6d2563e/image.png" alt=""></p>
<pre><code>&lt;a href=&quot;https://github.com/BanYeah&quot;&gt;&lt;img align=&quot;center&quot; style=&quot;height:180px&quot; src=&quot;https://github-readme-stats.vercel.app/api?username=BanYeah&amp;show_icons=true&amp;include_all_commits=true&amp;theme=material-palenight&amp;hide_border=true&quot; alt=&quot;BanYeah&#39;s github stats&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://github.com/BanYeah&quot;&gt;&lt;img align=&quot;center&quot; style=&quot;height:180px&quot; src=&quot;https://github-readme-stats.vercel.app/api/top-langs/?username=BanYeah&amp;layout=compact&amp;theme=material-palenight&amp;hide_border=true&quot; /&gt;&lt;/a&gt;</code></pre><p>(바로가기를 위해, a 태그를 추가)
<br></p>
<p>아래 링크에서
🔗<a href="https://github.com/anuraghazra/github-readme-stats/blob/master/themes/README.md">https://github.com/anuraghazra/github-readme-stats/blob/master/themes/README.md</a>
theme 종류를 확인할 수 있다.
<br></p>
<hr>
<h2 id="프로그래밍-역량">프로그래밍 역량</h2>
<p>🔻 예시 코드 일부
<img src="https://velog.velcdn.com/images/banyeah_/post/e2b9ff12-4d3b-4840-bff6-4da4171df372/image.png" alt=""></p>
<pre><code>### Languages
&lt;img src=&quot;https://img.shields.io/badge/c-A8B9CC?style=flat-square&amp;logo=c&amp;logoColor=white&quot;&gt; &lt;/t&gt;
&lt;img src=&quot;https://img.shields.io/badge/c++-00599C?style=flat-square&amp;logo=cplusplus&amp;logoColor=white&quot;&gt; 
&lt;img src=&quot;https://img.shields.io/badge/Python-3776AB?style=flat-square&amp;logo=Python&amp;logoColor=white&quot;/&gt;  &lt;/br&gt;
&lt;img src=&quot;https://img.shields.io/badge/CSS3-1572B6?style=flat-square&amp;logo=CSS3&amp;logoColor=white&quot;/&gt;  &lt;/t&gt;
&lt;img src=&quot;https://img.shields.io/badge/HTML5-E34F26?style=flat-square&amp;logo=HTML5&amp;logoColor=white&quot;/&gt; 
&lt;img src=&quot;https://img.shields.io/badge/JavaScript-F7DF1E?style=flat-square&amp;logo=JavaScript&amp;logoColor=white&quot;/&gt;
&lt;img src=&quot;https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&amp;logo=TypeScript&amp;logoColor=white&quot;/&gt;</code></pre><br>

<p>아래 링크에서 브랜드별 로고와 색상을 확인하고
🔗<a href="https://simpleicons.org/?q=vel">https://simpleicons.org/?q=vel</a></p>
<pre><code>&lt;img src=&quot;https://img.shields.io/badge/{배지에 표시할 글자}-{색상}?style=flat-square&amp;logo={브랜드 이름}&amp;logoColor=white&quot;&gt;</code></pre><p>위 코드의 { } 내부를 채우면 된다.
<br></p>
<p>배지 style의 종류는 아래와 같다.
🔗<a href="https://shields.io/badges/ansible-role">https://shields.io/badges/ansible-role</a>
<img src="https://velog.velcdn.com/images/banyeah_/post/f605bde4-ab6b-4087-9f7f-9cd2bcb6bcfa/image.png" alt="">
<br></p>
<hr>
<h2 id="solvedac-프로필">Solved.ac 프로필</h2>
<p>🔻 예시 코드
<img src="https://velog.velcdn.com/images/banyeah_/post/de22a777-9cd1-4454-9a6d-c123a309ca39/image.png" alt=""></p>
<pre><code>[![Solved.ac프로필](http://mazassumnida.wtf/api/v2/generate_badge?boj=04smailing)](https://solved.ac/04smailing)</code></pre><p>아래 링크에서 자세한 내용을 확인할 수 있다.
🔗<a href="https://github.com/mazassumnida/mazassumnida">https://github.com/mazassumnida/mazassumnida</a>
<br></p>
<hr>
<p>자신만의 독창적인 GitHub 프로필을 꾸미는데 도움이 되었길 바랍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GDB Tutorial]]></title>
            <link>https://velog.io/@banyeah_/GDB-Tutorial</link>
            <guid>https://velog.io/@banyeah_/GDB-Tutorial</guid>
            <pubDate>Mon, 13 May 2024 16:10:44 GMT</pubDate>
            <description><![CDATA[<p>아래 내용은 학교 과제로 Reverse Engineering을 하기 위해서, gdb를 공부한 내용을 정리하였다.
<br></p>
<h2 id="gdb-사전-준비">GDB 사전 준비</h2>
<hr>
<p>먼저 gdb를 Ubuntu에 설치한다.</p>
<pre><code>$ sudo apt update
$ sudo apt upgrade
$ sudo apt install gdb</code></pre><p>그리고 어셈블리어를 편하게 보기 위해서, gdb plugin인 gef를 설치해주었다. 아래 링크를 참고해서 설치하면 된다.
<a href="https://github.com/hugsy/gef">https://github.com/hugsy/gef</a>
<br>
<br></p>
<h2 id="gdb-실행">GDB 실행</h2>
<hr>
<p>Ubuntu에서 실행파일을 gdb로 실행한다.</p>
<pre><code>$ gdb ./실행파일</code></pre><br>

<p>본인은 AT&amp;T 문법이 더 편해서 따로 설정했다.</p>
<pre><code>$ set disassembly-flavor att</code></pre><br>

<p>main 함수 확인</p>
<pre><code>$ disas main</code></pre><br>

<p>프로그램 시작</p>
<pre><code>$ start</code></pre><br>

<p>내가 실행하는 파일은 _start부터 실행되었다.
따라서 main 함수부터 확인하기 위해서, main 함수 맨 위에 breakpoint를 설정하고, breakpoint까지 이동했다.</p>
<pre><code>$ b main
$ r</code></pre><br>

<p>gdb 종료</p>
<pre><code>$ quit</code></pre><br>
<br>

<h2 id="gdb-명령어">GDB 명령어</h2>
<hr>
<ol>
<li><p>프로그램 시작</p>
<pre><code>$ start</code></pre><br>
</li>
<li><p>프로그램 실행</p>
<pre><code>$ r
OR
$ run</code></pre><p>breakpoint가 있다면 그 위치에서 멈춘다.</p>
<br>
</li>
<li><p>명령어 하나씩 실행</p>
<pre><code>$ si
OR
$ step</code></pre><p>이때, 함수를 호출한다면 함수 내부로 들어간다.</p>
<br>

</li>
</ol>
<pre><code>$ ni</code></pre><p>반대로, ni는 함수를 호출하더라도 함수 내부로 들어가지 않는다.
<br></p>
<pre><code>$ si 5</code></pre><p>이렇게 5줄을 실행할 수도 있다. 
<br></p>
<ol start="4">
<li><p>현재 함수를 완료</p>
<pre><code>$ fin
OR
$ finish</code></pre><br>
</li>
<li><p>breakpoint 설정</p>
<pre><code>$ b *0x12a3
OR
$ b *main+0</code></pre><br>
</li>
<li><p>breakpoint 나열</p>
<pre><code>$ i b</code></pre><br>
</li>
<li><p>breakpoint 제거</p>
<pre><code>$ del 1</code></pre><p>위에서 1은 6번에서 확인한 breakpoint Num이다.</p>
<br>
</li>
<li><p>breakpoint까지 실행</p>
<pre><code>$ continue 1</code></pre><br>

</li>
</ol>
<h2 id="메모">메모</h2>
<hr>
<pre><code>lea  0xb51(%rip), %rsi
mov  %rbx, %rdi
call ... &lt;strcspn@plt&gt;</code></pre><p>로 첫 번째 인자로 %rbx 값을, 두 번째 인자로 0xb51(%rip)를 준 상황일 때, 0xb51(%rip)의 값을 확인하는 방법은 다음과 같다.</p>
<pre><code>$ x/s $rip + 0xb51</code></pre>]]></description>
        </item>
    </channel>
</rss>