<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jinnk0.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 28 Nov 2025 08:46:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jinnk0.log</title>
            <url>https://velog.velcdn.com/images/slow_runner/profile/751bafe5-b718-4472-bd12-700b331db3dd/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jinnk0.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/slow_runner" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[RabbitMQ 재시도 로직 구현]]></title>
            <link>https://velog.io/@slow_runner/RabbitMQ-%EC%9E%AC%EC%8B%9C%EB%8F%84-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@slow_runner/RabbitMQ-%EC%9E%AC%EC%8B%9C%EB%8F%84-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 28 Nov 2025 08:46:06 GMT</pubDate>
            <description><![CDATA[<h2 id="rabbitmq-리스너의-에러-처리">RabbitMQ 리스너의 에러 처리</h2>
<p>RabbitMQ는 기본적으로 리스너에서 에러가 발생하면 해당 메시지를 다시 큐에 집어넣고 재발송을 하게 된다. 이러한 처리 방식의 문제점은 일시적인 에러가 아니라 당장 해결할 수 없는 에러(ex. DB 접근 차단)가 발생할 경우 무한히 재시도를 하게 된다는 것이다.</p>
<h3 id="재시도-차단">재시도 차단</h3>
<p>가장 단순한 해결 방법은, 리스너에서 에러가 발생해도 다시 큐에 집어넣지 않는 것이다. 이 부분은 리스너에 Requeue 옵션을 false로 하여 제어할 수 있다. </p>
<pre><code class="language-java">@Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);

        factory.setDefaultRequeueRejected(false);

        return factory;
    }</code></pre>
<p>하지만 이런 방식으로 처리하면 에러가 발생하면 메시지는 유실되고, 재시도를 할 수 없게 된다. </p>
<h2 id="dlx-dlq를-활용한-에러-처리">DLX, DLQ를 활용한 에러 처리</h2>
<p>DLX는 Dead Letter Exchange, DLQ는 Dead Letter Queue의 줄임말으로 실패한 메시지를 처리하기 위한 Exchange와 Queue를 의미한다. 특별하게 느껴지지만 크게 다르지 않다. 그저 특정 Queue에서 메시지를 처리하지 못하고 에러가 발생할 경우 DLX로 전송하여 처리하게 된다. </p>
<h3 id="dlx로-실패한-메시지-전송">DLX로 실패한 메시지 전송</h3>
<p>메시지를 처리하는 중 에러가 발생했을 때 해당 메시지를 DLX를 보내기 위해서는 먼저 Queue의 &#39;x-dead-letter-exchange&#39;를 DLX로 지정해야 한다. 그럼 실패한 메시지(Dead Letter)를 해당 Exchange로 전송하게 된다. </p>
<pre><code class="language-java">@Bean
    public Queue hubQueue() {
        return QueueBuilder.durable(hubProperties.queue())
                // hubQueue의 DLX 기능 활성화
                .withArgument(&quot;x-dead-letter-exchange&quot;, DLX)
                .withArgument(&quot;x-dead-letter-routing-key&quot;, RETRY_ROUTING_KEY)
                .build();
    }</code></pre>
<p>그리고 AmqpRejectAndDontRequeueException을 발생시키거나, 메시지를 수동으로 승인/거부 처리할 수 있도록 한 뒤 메시지를 거절하면 DLX로 전송되게 된다.</p>
<pre><code class="language-java">@Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);

        // 메시지 승인/거절 수동으로 처리하도록 설정
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);

        return factory;
    }</code></pre>
<pre><code class="language-java">// 메시지를 큐로 재전송하지 않고 거절
channel.basicNack(tag, false, false);</code></pre>
<p>DLX로 전송된 실패한 메시지는 DLX와 바인딩된 DLQ로 전송되어 보관된다.</p>
<h2 id="재시도-로직-구현">재시도 로직 구현</h2>
<p>하지만 실패한 메시지를 바로 DLX로 보내면 재시도하면 해결될 수 있는 일시적인 에러일 경우, 메시지를 바로 처리하지 못하고 비효율적으로 처리하게 된다. 따라서 재시도 횟수를 제한하여 무한히 재시도되지 않도록 하고, 재시도 횟수를 초과하면 DLQ로 전송되어 보관되도록 구현하려고 한다.</p>
<h3 id="retryqueue">RetryQueue</h3>
<p>DLX와 바인딩된 큐에 DLQ뿐만 아니라 RetryQueue를 함께 바인딩하고, &#39;x-dead-letter-exchange&#39;를 작업 Queue가 바인딩된 Exchange로 지정한다. 그리고 실패한 메시지를 전송할 때 사용할 &#39;x-dead-letter-routing-key&#39;를 RetryQueue에 바인딩된 라우팅키로 설정한다. 그리고 ttl을 설정하면 지정된 시간이 지나면 RetryQueue에 있는 메시지를 실패한 것으로 간주하고 &#39;x-dead-letter-exchange&#39;에 지정된 Exchange로 전송하게 된다.</p>
<pre><code class="language-java">@Bean
    public Queue retryQueue() {
        return QueueBuilder.durable(RETRY)
                .withArgument(&quot;x-message-ttl&quot;, 3000)
                .withArgument(&quot;x-dead-letter-exchange&quot;, hubProperties.exchange())
                .withArgument(&quot;x-dead-letter-routing-key&quot;, hubProperties.routingKey())
                .build();
    }</code></pre>
<p>그러면 실패한 메시지는 우선 RetryQueue로 전송되고, 일정 시간 대기 후 다시 해당 Queue로 전송되어 재시도를 할 수 있게 된다.</p>
<h3 id="재시도-횟수-제한">재시도 횟수 제한</h3>
<p>이제 재시도 횟수를 제한하려면 해당 메시지가 몇 번째 재시도 중인지 알아야 한다. RabbitMQ는 &#39;x-dead-letter-exchange&#39; 설정을 통해 실패한 메시지를 전송할 경우, 메시지의 헤더에 &#39;xDeath&#39;를 추가하고 해당 헤더에 리스트 형태로 몇번째 재시도 중인지 기록한다.</p>
<pre><code class="language-java">     // 메시지의 x-death 헤더에서 현재까지의 재시도 횟수를 추출
    private int getRetryCount(Message message) {
        MessageProperties properties = message.getMessageProperties();
        Map&lt;String, Object&gt; headers = properties.getHeaders();

        Object xDeathHeader = headers.get(&quot;x-death&quot;);

        if (xDeathHeader instanceof List&lt;?&gt; deathList) {

            if (!deathList.isEmpty()) {

                Object lastDeathEntry = deathList.getLast();

                if (lastDeathEntry instanceof Map&lt;?, ?&gt; deathMap) {
                    Object countObject = deathMap.get(&quot;count&quot;);

                    if (countObject instanceof Number) {
                        return ((Number) countObject).intValue();
                    }
                }
            }
        }

        // x-death 헤더가 없거나 파싱에 실패하면 초기 상태(재시도 횟수 0)로 간주
        return 0;
    }</code></pre>
<p>이제 재시도 횟수를 읽어와 지정한 재시도 횟수를 초과하면 재시도를 중단하고 실패처리하도록 할 수 있다.</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<h4 id="routing-key-변경-문제">Routing Key 변경 문제</h4>
<p>하지만 문제가 발생했다. RetryQueue를 통해 다시 돌아온 메시지의 라우팅키가 변경된다는 것이다. # 패턴을 활용하여 여러 라우팅키를 받고 있었던 경우 라우팅키가 변경되면 특정 라우팅키에 해당하는 로직을 실행할 수 없게 된다.</p>
<p>이 문제를 해결하기 위해 xDeath 헤더를 이용했다. xDeath 헤더에는 재시도 횟수 뿐만 아니라 다양한 정보를 기록하고 있다.
<img src="https://velog.velcdn.com/images/slow_runner/post/361a7a98-c713-4394-9fcb-26d4c215592f/image.png" alt="">
xDeath 헤더의 내용을 잘보면 리스트 형태로, 최신순으로 배열되어 있다. 그리고 각 시점마다의 라우팅키를 기록하고 있는 것을 확인할 수 있다. 따라서 xDeath 헤더의 리스트의 마지막 요소의 routing-keys를 확인하면 해당 메시지가 최초로 가지고 있던 라우팅키를 확인할 수 있다.</p>
<pre><code class="language-java">    /**
     * 메시지의 Original Routing Key를 반환
     * - x-death 헤더가 존재하면, 리스트의 마지막 항목(가장 오래된 이벤트)에서 추출
     */
    private String getOriginalRoutingKey(Message message) {
        MessageProperties properties = message.getMessageProperties();
        Map&lt;String, Object&gt; headers = properties.getHeaders();
        Object xDeathHeader = headers.get(&quot;x-death&quot;);

        // x-death 헤더가 존재하고 List 형태인 경우
        if (xDeathHeader instanceof List&lt;?&gt; deathList) {

            if (!deathList.isEmpty()) {
                // 리스트의 마지막 항목(가장 오래된/최초 이벤트)을 참조
                Object firstDeathEntry = deathList.getLast();

                if (firstDeathEntry instanceof Map&lt;?, ?&gt; deathMap) {
                    // RabbitMQ가 기록한 라우팅 키 리스트
                    Object routingKeys = deathMap.get(&quot;routing-keys&quot;);

                    if (routingKeys instanceof List&lt;?&gt; routingKeyList) {
                        if (!routingKeyList.isEmpty()) {
                            // 리스트의 첫 번째 키를 반환
                            return routingKeyList.getFirst().toString();
                        }
                    }
                }
            }
        }

        // x-death 헤더가 없거나 파싱에 실패하면, 현재 메시지가 받은 키를 반환
        return properties.getReceivedRoutingKey();
    }</code></pre>
<h4 id="dlq로-최종-실패-메시지-전송">DLQ로 최종 실패 메시지 전송</h4>
<p>여기까지 구현하면 모든 문제가 끝난 줄 알았지만, 이제 최종적으로 재시도 횟수를 초과했을 때 이 메세지를 어떻게 처리할 것인지를 구현해야 한다. 재시도 횟수를 초과했을 때 그냥 메시지를 거절하게 되면, &#39;x-dead-letter-routing-key&#39;에 설정된 값에 따라 다시 RetryQueue로 전송하게 된다. 그럼 또 다시 무한히 재시도하는 문제가 반복되는 것이다.</p>
<p>따라서 재시도 횟수를 초과할 경우, 해당 메시지는 승인 처리하여 폐기하고, rabbitTemplate을 활용하여 DLQ로 실패한 메시지를 전송한다.</p>
<pre><code class="language-java">if (retryCount &gt;= MAX_RETRIES) {
                rabbitTemplate.convertAndSend(RabbitConfig.DLX, RabbitConfig.DLQ_ROUTING_KEY, message);
                channel.basicAck(tag, false);
                log.error(&quot;재시도 횟수 제한 초과, 메시지 처리 실패&quot;, e);
            } else {
                channel.basicNack(tag, false, false);
                log.warn(&quot;처리 실패, {}번째 재시도&quot;, retryCount + 1, e);
            }</code></pre>
<p>이러면 재시도 횟수만큼 RetryQueue로 보내 일정 시간이 지난 후 재시도하고, 재시도 횟수를 초과할 경우 해당 메시지를 폐기하여 재시도 루프를 끊고, 실패한 메시지는 최종적으로 DLQ로 보내 추후 실패 원인을 분석할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[파이썬] 백준 6064 카잉 달력 문제 풀이 (중국인의 나머지 정리)]]></title>
            <link>https://velog.io/@slow_runner/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B0%B1%EC%A4%80-6064-%EC%B9%B4%EC%9E%89-%EB%8B%AC%EB%A0%A5-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-%EC%A4%91%EA%B5%AD%EC%9D%B8%EC%9D%98-%EB%82%98%EB%A8%B8%EC%A7%80-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@slow_runner/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B0%B1%EC%A4%80-6064-%EC%B9%B4%EC%9E%89-%EB%8B%AC%EB%A0%A5-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-%EC%A4%91%EA%B5%AD%EC%9D%B8%EC%9D%98-%EB%82%98%EB%A8%B8%EC%A7%80-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 07 Mar 2025 12:25:53 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<h3 id="문제-출처">문제 출처</h3>
<p><a href="https://www.acmicpc.net/problem/6064">백준 6064번 - 카잉 달력</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p>이 문제는 주어진 두 주기 $M$과 $N$에 대해, 어떤 해(year)가 주어진 값 $x$와 $y$를 동시에 만족하는지 찾는 문제다. $x$는 $M$으로 나눈 나머지이고, $y$는 $N$으로 나눈 나머지인 값을 의미한다.</p>
<p>주어진 값들을 통해, 두 조건을 동시에 만족하는 가장 작은 year를 구하는 것이 목표다. </p>
<p><strong>입력</strong>은 테스트 케이스마다 $M$, $N$, $x$, $y$의 값을 받아서, 이를 만족하는 year를 출력해야 한다.</p>
<ul>
<li>$M$은 첫 번째 주기의 주기</li>
<li>$N$은 두 번째 주기의 주기</li>
<li>$x$는 $M$으로 나눈 나머지</li>
<li>$y$는 $N$으로 나눈 나머지</li>
</ul>
<p><strong>출력</strong>은 주어진 두 조건을 동시에 만족하는 가장 작은 year를 구하는 것이다. 만약 해가 존재하지 않으면 -1을 출력해야 한다.</p>
<hr>
<h2 id="브루트포스-알고리즘">브루트포스 알고리즘</h2>
<p>처음에는 이 문제를 <strong>브루트포스</strong> 방식으로 접근할 수 있다. 주어진 $M$과 $N$에 대해, 처음 주어진 $x$값에서부터 시작해서 $M$만큼 증가시키면서 $N$으로 나눈 나머지가 $y$와 일치하는지를 확인하는 방식이다. 이 방법은 간단하고 직관적이다.</p>
<p>예를 들어, 주어진 $x$에서부터 시작해 $M$씩 더하면서, 그 값이 $N$으로 나누었을 때 $y$와 맞는지 체크하는 방식이다. 이 방법은 맞는 해를 찾으면 바로 출력하고, 찾을 수 없으면 -1을 출력하게 된다.</p>
<h3 id="코드-구현">코드 구현</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

T = int(input())
for _ in range(T):
    M, N, x, y = map(int, input().split())

    found = False
    year = x
    while year &lt;= M * N:
        if (year - 1) % N + 1 == y:
            print(year)
            found = True
            break
        year += M

    if not found:
        print(-1)</code></pre>
<h3 id="시간-복잡도">시간 복잡도</h3>
<p>이 방식은 단순하고 쉽게 구현할 수 있지만, <strong>시간 복잡도가 높아질 수 있다</strong>. 최악의 경우 시간 복잡도는 $\mathcal{O}(M \times N)$으로, $M \times N$까지 반복할 수 있기 때문에, M, N이 커질 수록 매우 비효율적이다. 실제로 제출 후 채점 시간이 굉장히 오래걸렸다.</p>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/e3ae32bd-d3a2-44e0-9ede-7ae7120c899a/image.png" alt=""></p>
<p>테스트 케이스가 많아질수록 상당한 시간이 소요되기 때문에 좀 더 효율적인 방법, 좀 더 빠르게 해결할 수 있는 방법은 없을까 계속 고민하던 중, 이 문제의 유형 분류에 처음 보는 유형이 있다는 사실을 알게 되었다.</p>
<hr>
<h2 id="중국인의-나머지-정리crt">중국인의 나머지 정리(CRT)</h2>
<p>사실 처음 보는 유형이어서 무심코 넘겼는데 <strong>중국인의 나머지 정리</strong>(Chinese Remainder Theorem, CRT)라는 이론이 존재하고, 이 방법을 사용하면 이 문제를 브루트포스 방식으로 접근하지 않아도 좀 더 효율적으로 풀 수 있다.</p>
<p>여기서 해결하고자 하는 문제는 두 개의 주기 $M$과 $N$에 대해 나머지가 각각 $x$와 $y$인 해를 구하는 문제로, 서로소인 두 모듈러 방정식이 있을 때 모든 모듈러 방정식을 만족하는 하나의 해를 구하는 CRT의 전형적인 문제 중 하나다.</p>
<p>중국인의 나머지 정리에서는 다음과 같은 두 조건을 동시에 만족하는 값을 구할 수 있다.</p>
<p>$$
\text{year} \equiv x \pmod{M}
$$
$$
\text{year} \equiv y \pmod{N}
$$</p>
<p>이 두 식을 동시에 만족하는 값을 구하기 위해서는 <strong>확장된 유클리드 알고리즘</strong>을 사용하여 모듈러 역원을 구하고, 이를 이용해 문제를 해결할 수 있다.</p>
<h3 id="과정">과정</h3>
<ol>
<li><p><strong>주어진 두 합동식</strong>:
$$
\text{year} \equiv x \pmod{M}
$$
$$
\text{year} \equiv y \pmod{N}
$$</p>
</li>
<li><p><strong>식 변환</strong>:
첫 번째 식에서 $\text{year} = kM + x$라는 형태로 변환할 수 있다.
이를 두 번째 합동식에 대입하면:
$$
kM + x \equiv y \pmod{N}
$$</p>
</li>
<li><p><strong>식 정리</strong>:
이를 $kM \equiv y - x \pmod{N}$로 바꿀 수 있다. 이때 $k$를 구하기 위해서는 모듈러 역원과 확장된 유클리드 알고리즘을 사용해야 한다.</p>
<p>$M^{-1}$을 모듈러 역원이라고 하면 다음을 만족한다. </p>
<blockquote>
<p>$M \times M^{-1} \equiv 1 \pmod{N}$</p>
</blockquote>
<p>즉, $kM \equiv y - x \pmod{N}$의 양변에 $M^{-1}$을 곱하면 나눗셈을 대신할 수 있다.
이를 통해 $k$의 최종 식을 구할 수 있다.</p>
<blockquote>
<p>$k \equiv (y - x) \times M^{-1} \pmod{N}$</p>
</blockquote>
</li>
</ol>
<ol start="4">
<li><strong>최종 계산</strong>:
$k$를 구한 후 이를 원래 식에 대입하여 해를 구할 수 있다.</li>
</ol>
<h3 id="코드-구현-1">코드 구현</h3>
<pre><code class="language-python">import sys
from math import gcd

def extended_gcd(a, b):
    &quot;&quot;&quot;확장된 유클리드 알고리즘을 이용해 ax ≡ 1 (mod b)의 해 x를 찾음&quot;&quot;&quot;
    if b == 0:
        return a, 1, 0
    g, x1, y1 = extended_gcd(b, a % b)
    return g, y1, x1 - (a // b) * y1

def solve(M, N, x, y):
    &quot;&quot;&quot;중국인의 나머지 정리를 이용해 최소의 year 찾기&quot;&quot;&quot;
    # (x, y)에서 x와 y를 1-based index에서 0-based index로 변환
    x -= 1
    y -= 1

    # M * k ≡ (y - x) (mod N) 풀기
    g, m_inv, _ = extended_gcd(M, N)

    # 해가 존재하지 않는 경우
    if (y - x) % g != 0:
        return -1

    # k 계산 (M의 역원을 곱해서 (y - x) / g를 찾기)
    k = ((y - x) // g * m_inv) % (N // g)

    # year 계산
    year = M * k + x + 1  # 다시 1-based index로 변환

    return year

# 입력 처리
input = sys.stdin.readline
T = int(input())

for _ in range(T):
    M, N, x, y = map(int, input().split())
    print(solve(M, N, x, y))</code></pre>
<h3 id="시간-복잡도-1">시간 복잡도</h3>
<p>중국인의 나머지 정리를 사용하여 문제를 해결할 경우, 확장된 유클리드 알고리즘을 실행하는 과정과 이를 통해 구한 모듈러 역원을 이용해 해를 구하는 과정이 필요하다.</p>
<p>확장된 유클리드 알고리즘을 실행하는 과정의 시간복잡도는 $\mathcal{O}(\log N)$이고, 해를 구하는 과정의 시간 복잡도는 $\mathcal{O}(\log N)$이다.</p>
<p>즉, 전체 시간 복잡도는 $\mathcal{O}(\log N)$이 된다.</p>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/05883e83-df5a-47bb-86b0-46ece9c83529/image.png" alt=""></p>
<p>이 방식은 M, N이 커져도 굉장히 효율적으로 빠르게 계산이 가능하다. </p>
<hr>
<h2 id="결론">결론</h2>
<p>이번 문제를 풀면서 두 가지 방식의 차이를 확실히 느낄 수 있었다. <strong>브루트포스 방식</strong>은 코드가 간단하고 직관적이지만 실행 속도가 느린 반면, <strong>중국인의 나머지 정리</strong>를 사용한 방식은 코드가 조금 더 복잡하지만, 훨씬 더 빠른 속도를 자랑했다. 두 방식의 특징을 비교해보면 다음과 같다</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>코드 간결성</th>
<th>시간 복잡도</th>
<th>실행 시간</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong>브루트포스 방식</strong></td>
<td>간단</td>
<td>O(n^2)</td>
<td>상대적으로 오래 걸림</td>
<td>직관적이고 구현이 쉬움</td>
</tr>
<tr>
<td><strong>중국인의 나머지 정리</strong></td>
<td>복잡</td>
<td>O(log n)</td>
<td>매우 빠름</td>
<td>효율적이고 최적화된 방법, 구현이 어려울 수 있음</td>
</tr>
</tbody></table>
<h3 id="정리">정리</h3>
<p>브루트포스 방식은 직관적이고 쉽게 구현할 수 있지만, 시간이 오래 걸린다. 반면, 중국인의 나머지 정리 방식은 성능 면에서 뛰어나지만 구현이 복잡하다. 이 문제를 통해 두 가지 방식의 장단점을 잘 파악할 수 있었고, 문제에 따라 적합한 방법을 선택하는 것이 중요함을 깨달았다.</p>
<p>결국, <strong>어떤 방식이 더 좋은지</strong>는 문제의 성격에 따라 다를 수 있기 때문에 다양한 접근을 고려하는 것이 중요하다. 이번 문제는 그 점에서 정말 유익하고 재미있는 경험이었다.</p>
<hr>
<h2 id="기타-추가-설명">기타 추가 설명</h2>
<h4 id="모듈러-역원">모듈러 역원</h4>
<p>모듈러 연산은 어떤 수를 m으로 나눴을 때 그 나머지를 구하는 연산이다. 그리고 모듈러 역원은 다음 식을 만족하는 b를 찾는 것이다.</p>
<blockquote>
<p>$a \times b \equiv 1 \pmod{m}$</p>
</blockquote>
<p>즉, 어떤 수 a와 b를 곱했을 때 m으로 나눈 나머지가 1이 되는 b를 찾는 것이다. 이 때 b를 모듈러 역원이라고 한다.</p>
<p>모듈러 역원이 존재하려면, a와 m이 서로소여야 한다. </p>
<h4 id="유클리드-알고리즘">유클리드 알고리즘</h4>
<p>유클리드 알고리즘은 두 수의 GCD(최대공약수)를 빠르게 구하는 방법이다.
기본적인 원리는 a와 b의 최대공약수는 b와 a를 b로 나눈 나머지의 최대공약수와 같다는 것이다. 이 원리를 이용하여 최대공약수를 빠르게 구할 수 있다.</p>
<pre><code class="language-python">def gcd(a, b):
    while b:  # b가 0이 아닐 때만 실행
        a, b = b, a % b  # a를 b로 바꾸고, b를 a % b로 변경
    return a</code></pre>
<h4 id="배주-항등식">배주 항등식</h4>
<p>배주 항등식은 두 정수 a와 b의 최대공약수 gcd(a, b)를 두 수의 선형 결합으로 나타낼 수 있다는 이론이다.</p>
<p>즉, a와 b가 주어졌을 때, $\gcd(a, b) = d$라면, 다음과 같은 형태로 d를 나타낼 수 있다.</p>
<blockquote>
<p>$a \cdot x + b \cdot y = d$</p>
</blockquote>
<p>이 때 d가 1이라면 x는 a의 모듈러 역원이다.</p>
<p>유클리드 알고리즘은 두 수의 최대공약수를 구하는 과정에서 그 나머지를 선형 결합으로 표현하는데, 이 선형 결합의 계수가 x, y이고, 배주 항등식의 해가 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] 업스테이지 기업 해커톤 프로젝트 회고]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-%EC%97%85%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%80-%EA%B8%B0%EC%97%85-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-%EC%97%85%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%80-%EA%B8%B0%EC%97%85-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 22 Feb 2025 09:57:03 GMT</pubDate>
            <description><![CDATA[<h1 id="자영업자들을-위한-음악-큐레이션-서비스">자영업자들을 위한 음악 큐레이션 서비스</h1>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/bf130901-26ee-41d7-b20a-5bbaf0c4cc61/image.png" alt=""></p>
<h2 id="프로젝트-개요">프로젝트 개요</h2>
<p><strong>목표:</strong><br>소규모 매장을 운영하는 자영업자들이 매장 분위기에 맞는 음악을 쉽게 추천받을 수 있도록 하는 서비스 개발  </p>
<p><strong>사용 기술:</strong><br><code>Python</code>, <code>FastAPI</code>, <code>React</code>, <code>PyTorch</code>, <code>Faiss</code>, <code>MongoDB</code>, <code>PostgreSQL</code>, <code>Redis</code>, <code>Apache Airflow</code>, <code>Docker</code>  </p>
<hr>
<h2 id="프로젝트-진행-과정">프로젝트 진행 과정</h2>
<h3 id="주요-기능">주요 기능</h3>
<ul>
<li><strong>온보딩 시스템을 통한 신규 유저 임베딩 생성</strong>  <ul>
<li>가게 분위기에 맞는 태그 선택 후, 관련 음악 중 선호하는 트랙 선택하여 유저 임베딩 생성  </li>
</ul>
</li>
<li><strong>사용자가 제시한 플레이리스트와 어울리는 음악 추천</strong>  <ul>
<li>LightGCN을 활용하여 추천 후보곡을 생성하고, BiEncoder를 이용해 플레이리스트와의 유사도를 고려한 Reranking 적용  </li>
</ul>
</li>
<li><strong>OCR API를 이용한 플레이리스트 이미지 인식</strong>  <ul>
<li>기본적으로 스포티파이 API를 활용한 플레이리스트 가져오기 기능 제공</li>
<li>하지만 스포티파이에 플레이리스트를 보유하고 있지 않을 경우 플레이리스트 캡쳐 이미지 사용 가능</li>
<li>플레이리스트 이미지 캡처 업로드 시 OCR API을 통해 트랙명과 아티스트명 추출  </li>
</ul>
</li>
</ul>
<hr>
<h2 id="기술적-도전과-해결-방법">기술적 도전과 해결 방법</h2>
<h3 id="어려웠던-점">어려웠던 점</h3>
<ol>
<li>OCR API를 통해 이미지에서 <strong>트랙명/아티스트명만 정확히 추출</strong>하는 문제  </li>
<li><strong>외부 API 호출 및 DB 접근 최적화</strong>를 통한 응답 속도 개선  </li>
</ol>
<h3 id="해결-방법">해결 방법</h3>
<blockquote>
<ol>
<li>OCR API를 통해 이미지에서 <strong>트랙명/아티스트명만 정확히 추출</strong>하는 문제 </li>
</ol>
</blockquote>
<ul>
<li>OCR 전처리를 통해 트랙명/아티스트명을 제외한 불필요한 정보 제거  </li>
<li>OCR API 결과가 트랙명/아티스트명 순서대로 제공된다는 점을 활용하여 텍스트 후처리 로직 개선  </li>
</ul>
<blockquote>
<ol start="2">
<li><strong>외부 API 호출 및 DB 접근 최적화</strong>를 통한 응답 속도 개선  </li>
</ol>
</blockquote>
<ul>
<li><strong>비동기 병렬 처리 적용</strong>하여 외부 API 호출 속도 최적화</li>
<li><strong>DB 쿼리 개선</strong>으로 DB 접근 속도 최적화  </li>
<li><strong>Redis를 활용한 캐싱으로 반복적인 요청 최소화</strong>  </li>
</ul>
<hr>
<h2 id="결과-및-개선점">결과 및 개선점</h2>
<h3 id="프로젝트-성과">프로젝트 성과</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>주요 성과</td>
<td>OCR을 활용한 자동 플레이리스트 분석 기능 구현, 추천 모델의 정확도 개선</td>
</tr>
<tr>
<td>성능 개선</td>
<td>API 응답 속도를 50% 이상 단축, DB 최적화 및 캐싱 도입</td>
</tr>
<tr>
<td>배운 점</td>
<td>모델 서빙의 최적화 방법, 비동기 프로그래밍을 통한 성능 개선, 협업 과정에서의 커뮤니케이션 중요성</td>
</tr>
</tbody></table>
<h3 id="향후-개선할-점">향후 개선할 점</h3>
<ul>
<li>코드 스타일 및 관리 강화 (리팩토링, 코드 리뷰 프로세스 적용)  </li>
<li>쿼리 최적화를 더욱 심화하여 불필요한 데이터 접근 최소화 및 성능 개선</li>
<li>추천 모델의 개인화 수준 향상 (사용자의 피드백 반영)  </li>
</ul>
<h3 id="프로젝트-깃허브-링크">프로젝트 깃허브 링크</h3>
<p><a href="https://github.com/jinnk0/level4-recsys-finalproject-hackathon-recsys-02-lv3/tree/main">GitHub - 자영업자들을 위한 음악 큐레이션 서비스</a></p>
<hr>
<h2 id="마무리">마무리</h2>
<p>이번 프로젝트를 통해 단순한 기능 구현을 넘어 <strong>사용자의 경험을 고려하는 것이 중요함을 다시금 확인</strong>할 수 있었다.
특히, 실제 서비스에서 응답 속도가 중요한 요소임을 체감하며, 이를 개선하기 위한 최적화 방안을 심층적으로 고민해볼 기회가 되었다.</p>
<p>또한, 팀원들과의 협업을 통해 <strong>명확한 역할 분배와 원활한 커뮤니케이션이 프로젝트 진행을 얼마나 효율적으로 만드는지를 경험</strong>할 수 있었다.
개발 과정에서 예상치 못한 문제에 직면했을 때, 혼자 해결하려 하기보다 팀원들과 논의하는 것이 더 효과적일 수 있다는 점도 배웠다.</p>
<p>무엇보다, 단순히 모델 개발과 구현에 그치는 것이 아니라, <strong>서비스 단계까지 고려할 때 훨씬 더 많은 요소를 고민해야 한다는 점을 실감</strong>했다. 앞으로도 이 부분을 지속적으로 연구하고 학습하며, 더 나은 서비스를 제공하기 위해 노력할 계획이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 19]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-19</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-19</guid>
            <pubDate>Fri, 27 Dec 2024 09:47:51 GMT</pubDate>
            <description><![CDATA[<h2 id="19주차">19주차</h2>
<p>저번주까지는 프로젝트에 집중하느라 강의까지 신경쓸 겨를이 없지 않을까 하는 걱정을 했었다.
막상 이번주 강의 내용을 확인해보니 <code>모델 경량화</code>라는게 그 동안 고민하던 문제와 연결되는 부분이 있어서 굉장히 흥미롭게 들었다.</p>
<p>프로젝트를 진행하면서 모델의 실시간 서빙을 해보고 싶다는 욕심이 있었는데, 그렇게 하려면 모델 추론 속도를 높이는 것이 중요했다.
그래서 무거운 모델을 좀 더 가볍게 만들어서 학습과 추론 속도를 높일 수 있지 않을까하는 고민을 하고 있었는데, 마침 이 고민이 강의 주제와 맞물렸다. 
덕분에 프로젝트 준비로 바쁜 와중에도 강의 내용을 의욕적으로 들을 수 있었다.</p>
<h2 id="주간-학습-내용">주간 학습 내용</h2>
<h4 id="📍pruning">📍Pruning</h4>
<p>가지치기라고 할 수 있는 기법으로, 신경망 모델에서 노드나 간선을 제거하여 파라미터 수를 줄이고, 모델의 크기와 계산 비용을 줄이는 기법이다.</p>
<p>Pruning은 4가지 관점에서 방법론을 구분해볼 수 있다.</p>
<blockquote>
<p>1) <strong>Structure</strong> : Pruning을 수행하는 단위에 따른 구분
2) <strong>Scoring</strong> : Pruning 할 파라미터를 선정하는 방법에 따른 구분
3) <strong>Scheduling</strong> : Pruning 및 fine-tuning을 언제, 얼마나 할 것인지에 따른 구분
4) <strong>Initialization</strong> : 재학습 및 fine-tuning을 진행할 때 파라미터 초기화 방식에 따른 구분</p>
</blockquote>
<p>Structure에 따른 구분에서는 모델 구조에 대한 변경없이 개별 파라미터 단위로 Pruning을 진행하는 <strong>Unstructured Pruning</strong>과 레이어 단위로 Pruning을 진행해서 모델 구조가 변경되는 <strong>Structured Pruning</strong>이 있다.</p>
<p>Scoring에 따른 구분에서는 중요도를 계산하는 방법과, 계산된 중요도를 반영하는 단위로 또 구분할 수 있다. 
중요도를 계산하는 방법은 파라미터별 절댓값을 중요도로 사용하는 방법과, 레이어별로 $L^p$-norm을 중요도로 사용하는 방법이 있다.
계산된 중요도를 반영할 때는 모델 전체에서 일정 기준 이하의 중요도를 가진 파라미터를 Pruning하는 <strong>Global Pruning</strong> 방식과, 특정 단위 별로 중요도를 비교하여 Pruning하는 <strong>Local Pruning</strong> 방식이 있다.</p>
<p>Scheduling에 따른 구분에서는 한번만 재학습을 수행하는 <strong>One-shot</strong> 방식과, 여러번에 걸쳐서 재학습을 수행하는 <strong>Recursive</strong> 방식이 있다.</p>
<p>Initialization에 따른 구분에서는 재학습할 때, Pruning된 상태 그대로 fine-tuning을 진행하는 <strong>Weight-preserving</strong> 방식과, 파라미터를 랜덤으로 초기화한 후 fine-tuning을 진행하는 <strong>Weight-reinitializing</strong> 방식이 있다.</p>
<h4 id="📍knowledge-distillation">📍Knowledge Distillation</h4>
<p>크고 복잡한 고성능의 Teacher 모델로부터 지식을 전달받아서 작고 간소화된 Student 모델을 학습시키는 기법이다.</p>
<p>보통 증류(전달)할 지식의 종류에 따라 아래와 같이 분류한다.</p>
<blockquote>
<p>1) <strong>Logit-based</strong> : Teacher 모델의 logit값을 사용
2) <strong>Output-based</strong> (Imitation-Learning) : Teacher 모델의 output 사용
3) <strong>Feature-based</strong> : Teacher 모델의 중간 레이어 feature/representation 값 사용</p>
</blockquote>
<p>이러한 방식은 Teacher 모델의 특성에 따라 적용 가능한 방식이 달라진다.
내부 구조, 파라미터 등을 모두 알 수 있는 White-box 모델인 경우에는 세 가지 방식을 모두 사용할 수 있지만, Black-box 모델인 경우에는 Output-based 방식만 사용할 수 있다. </p>
<h4 id="📍quantization">📍Quantization</h4>
<p>숫자의 정밀도를 낮춰서 모델을 경량화하는 기법이다.
FP32 등 정밀하게 숫자를 표현하는 자료형을 INT8과 같이 정밀도가 낮은 자료형으로 변경하여 모델을 경량화한다.</p>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩모델 실시간 서빙과 관련한 내용 더 알아보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 18]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-18</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-18</guid>
            <pubDate>Fri, 20 Dec 2024 07:32:43 GMT</pubDate>
            <description><![CDATA[<h2 id="18주차">18주차</h2>
<p>이번주는 드디어 최종 프로젝트에 대한 기업 해커톤 공지가 올라왔다.
아직 최종 프로젝트 주제가 정해지진 않았지만, 그래도 막연하던 기업 해커톤에 대해 감을 잡을 수 있었던 것 같다.</p>
<h4 id="기업-해커톤-주제">기업 해커톤 주제</h4>
<p>이제 정말 프로젝트 주제를 정해야할 때가 되었다. 지금까지 팀에서 생각해 본 아이디어로는 장소 추천에 대한 이야기가 좀 많이 나왔다.
프로젝트 주제자체는 재밌어보이는 아이디어가 많이 나왔는데, 실현 가능성을 함께 생각하다보니 결론을 내기 쉽지 않은 것 같다.</p>
<p>그래도 팀원 모두 공통적으로 최종 프로젝트를 진행하면서 달성하기를 원하는 내용에 대해서 정리를 해보았다.</p>
<p>1) 기술적 챌린지 </p>
<blockquote>
<p>단순히 Hugging Face 등을 통해 모델을 가져오거나 하는 것에 그치지 않고 직접 당면한 문제에 대한 해결법을 고안하여 모델을 튜닝하거나 구현해보고 싶다는 것이 공통적인 의견이었다.</p>
</blockquote>
<p>2) 뻔하지 않은 데이터셋</p>
<blockquote>
<p>어떤 주제를 선택하든 데이터를 수집하는 것이 가장 중요한 과제이다. 공공 데이터셋과 같은 공개된 데이터를 활용하는 것도 좋지만 크롤링을 통해 주제에 맞고 팀에서 필요하다고 판단한 정보가 포함되어 있는 데이터를 직접 수집하거나 확장해보고 싶다.</p>
</blockquote>
<p>3) 매력적인 추천 결과</p>
<blockquote>
<p>매력적인 추천 결과, 설명 가능한 추천 시나리오에 많은 관점을 두기로 했다. 이것이 기업 해커톤에서 많은 기업들이 원하는 비즈니스 가치 달성과도 연관된다고 생각한다.</p>
</blockquote>
<p>4) 모델 서빙 및 배포</p>
<blockquote>
<p>단순히 모델을 구현하는 것에서 그치지 않고, 해당 모델을 활용한 서비스를 구현해보고 싶다는 것 또한 공통적인 의견이었다. 그 과정에서 쿠버네티스와 같은, 기업에서 많이 활용되지만 경험해보기 어려운 기술들을 활용해보고 싶다.</p>
</blockquote>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩프로젝트 주제 구체화
🚩프로젝트 아키텍처 설계</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 17]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-17</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-17</guid>
            <pubDate>Fri, 13 Dec 2024 09:11:37 GMT</pubDate>
            <description><![CDATA[<h2 id="17주차-회고">17주차 회고</h2>
<p>이번주 강의 주제는 <strong>Product Serving</strong>이었다.
개인적으로 좀 기대를 했던 주제이기도 했는데, AI 모델을 서빙하는 서버 구조가 일반적인 백엔드 서버 구조와 어떤 다른점이 있을지가 궁금했었다.🤔
전에 백엔드를 공부했었다보니, 금방 강의 진도를 끝낼 수 있을 것이라고 생각했는데 생각외로 실습 과정에서 에러도 많이 발생하고, 새로운 것들이 많아서 강의 진도가 늦어졌다.
그래도 2주에 걸쳐서 진행되는 주제이다보니 이번주에 전체 10강 중 절반은 끝내야겠다 생각했었는데, 무사히 딱 절반까지는 끝낼 수 있었다.</p>
<h4 id="주간-학습-내용">주간 학습 내용</h4>
<p>이번주에 학습한 내용은 크게 세 가지였다.</p>
<blockquote>
<p>1) <strong>Airflow</strong>
2) Poetry
3) <strong>FastAPI</strong></p>
</blockquote>
<p>Product Serving에 앞서 모델을 서빙하는 방식을 정해야하는 데, 모델을 서빙하는 방식에는 두 가지가 있다.</p>
<blockquote>
<p>1) <strong>Batch Serving</strong>
2) <strong>Online Serving</strong></p>
</blockquote>
<p>Batch Serving은 특정 단위로 모델 예측값을 생성한 뒤에 저장해두었다가, 묶음 단위로 서빙하는 것을 말한다. Online Serving은 클라이언트에서 요청이 오면 그때마다 바로 모델 예측을 생성하여 실시간으로 서빙하는 것이다.</p>
<p>여기서 <strong>Airflow는 Batch Serving</strong>을 위한 것이고, <strong>FastAPI는 Online Serving</strong>을 위한 것이라고 할 수 있다.
Poetry는 그동안 requirements.txt로 관리하던 개발 환경을 좀 더 효율적이고 쉽게 관리할 수 있도록 하는 도구이다.</p>
<p>나는 FastAPI에 대한 내용을 기대하고 있었는데 생각보다 Airflow에 관한 내용이 재미있었다. FastAPI는 기존에 백엔드 서버에서 REST API를 구현하는 방식과 유사한 부분이 많았는데, Airflow는 색다른 내용이라 실습하는게 굉장히 재미있었다.</p>
<p>Airflow는 <strong>DAG</strong>을 사용하여 작업의 흐름과 순서를 정의하고, 이 DAG을 <strong>Scheduler</strong>를 통해 관리하고 스케줄링한다. 그리고 스케줄에 따라 <strong>Operator</strong>로 정의된 작업 클래스들을 <strong>Executor</strong>로 실행하는 구조이다. 이 Airflow를 통해 일정 주기로 모델을 새로 학습시키고, 예측을 생성하여 묶음 단위로 서빙하는 Batch Serving을 구현할 수 있다.</p>
<p>FastAPI는 자바로 REST API를 구현하는 것과 구조적으로 크게 다르진 않았지만, 인상적이었던건 Swagger를 따로 라이브러리를 설정해주지 않아도 자동으로 생성해준다는 점이었다. 또한 <strong>Pydantic</strong>으로 데이터 Validation과 Config 관리를 하는 부분은 좀 더 깊게 학습해보고 싶다.</p>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩FastAPI를 좀 더 활용해서 디테일한 모델 서빙 과정을 구현해보기
🚩최종 프로젝트 아이디어 생각해보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] AEs (Autoencoders)]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-AEs-Autoencoders</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-AEs-Autoencoders</guid>
            <pubDate>Fri, 06 Dec 2024 07:23:01 GMT</pubDate>
            <description><![CDATA[<h2 id="ae-autoencoder">AE (Autoencoder)</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/82ec94d3-068e-4ebd-b55a-4eedb3df1cf6/image.png" alt="">
Encoder와 Decoder로 구성되어 입력된 데이터를 복원하도록 학습하는 모델이다.</p>
<blockquote>
<p><strong>Encoder</strong> : 입력 이미지를 저차원의 잠재 공간(Latent Space)로 매핑하여 잠재 변수(z)로 변환한다.
<strong>Decoder</strong> : 잠재 변수를 입력으로 하여 원본 이미지를 복원한다.</p>
</blockquote>
<p>학습 과정에서 목적 함수로 reconstruction loss를 사용한다. reconstruction loss는 원본 이미지와 복원 이미지의 차이를 최소화하기 위한 것으로, 보통 MSE, MAE 등을 활용한다. </p>
<h2 id="vae-variational-autoencoder">VAE (Variational Autoencoder)</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/c8ba3d38-4292-4a5a-8528-45ca80fe189e/image.png" alt=""></p>
<p>Autoencoder와 기본적으로 동일한 구조를 갖지만, 잠재 공간을 단순히 잠재 변수로 변환하는 것이 아니라 잠재 공간의 분포를 가정하여 학습한다.</p>
<p>모델 학습을 위한 목적함수로 기존의 reconstruction loss 뿐만 아니라 가정한 잠재 공간의 분포를 반영하기 위해 KL divergence를 함께 정의하여 사용한다. </p>
<h2 id="vq-vae-vector-quantized-variational-autoencoder">VQ-VAE (Vector Quantized-Variational Autoencoder)</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/5047e055-e2a0-4730-a52a-d6297fa71ab2/image.png" alt="">
기본적인 구조는 VAE와 동일하지만, 잠재 공간(Codebook)의 분포를 가정할 때 연속적인 구조가 아닌 이산적인 구조를 가정하여 사용한다.</p>
<p>이산적인 구조를 가정하면 연속적인 구조보다 효율적인 데이터 압축과 표현이 가능하고, 고차원 데이터의 복잡성을 줄일 수 있다. 또한 불연속적인 구조로 인해 모델이 더욱 명확한 구분을 학습할 수 있다.</p>
<p>이러한 이산적인 잠재 공간은 이미지, 텍스트, 음성 등과 같은 데이터에 더욱 적합하다.</p>
<p>학습 과정에서 목적함수는 기존의 reconstruction loss를 사용하고, 별개로 잠재공간(codebook)을 나타내는 잠재 벡터 e와 encoder를 stop-gradient를 통해 따로 계산한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] GANs (Generative Adversarial Networks)]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-GANs-Generative-Adversarial-Networks</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-GANs-Generative-Adversarial-Networks</guid>
            <pubDate>Fri, 06 Dec 2024 06:40:03 GMT</pubDate>
            <description><![CDATA[<h2 id="gans-generative-adversarial-networks">GANs (Generative Adversarial Networks)</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/3fdf28b1-6824-4a58-872d-d33575801aec/image.png" alt=""></p>
<p>GAN은 <strong>판별자(Discriminator)</strong>와 <strong>생성자(Generator)</strong>로 구분되는 구조를 가진다. </p>
<p><strong>판별자와 생성자는 서로 적대적인 관계</strong>로, 훈련 과정에서 판별자는 생성자가 생성해낸 이미지가 진짜인지 가짜인지를 구분하고, 생성자는 판별자가 이미지를 분간할 수 없도록 진짜와 가까운 이미지를 생성한다.</p>
<p>GAN 모델을 정의하고 학습시키기 위해 생성 모델 G와 판별 모델 D를 정의해야 한다.</p>
<h3 id="생성-모델-g">생성 모델 G</h3>
<blockquote>
<p><strong>입력</strong> : 잠재 공간(latent space)에서 샘플링한 무작위 노이즈 벡터(z)
<strong>출력</strong> : 진짜 데이터와 구분이 어려운 생성 데이터</p>
</blockquote>
<p>생성 모델 G는 학습 데이터의 분포를 모사하여, 그와 흡사한 데이터를 생성하는 방향으로 학습한다. </p>
<h3 id="판별-모델-d">판별 모델 D</h3>
<blockquote>
<p><strong>입력</strong> : 생성 모델 G가 생성한 가짜 데이터
<strong>출력</strong> : 입력으로 받은 데이터가 실제 데이터일 확률을 0~1 사이의 확률로 출력</p>
</blockquote>
<p>판별 모델 D는 생성 모델 G가 생성해낸 데이터가 실제 데이터인지 가짜 데이터인지를 판별하기 위한 방향으로 학습한다.</p>
<p>결론적으로 두 모델이 서로 경쟁적으로 학습하면서 GAN이 높은 품질의 생성 데이터를 만들어낼 수 있도록 한다.</p>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/a100951e-0f8a-4529-9b1d-93fdee1b7fe9/image.png" alt=""></p>
<p>즉, 생성 모델 G는 $log(1-D(G(z)))$ 값을 최소로 만들기 위한 방향으로 학습하고, 판별 모델 D는 $log(D(x))$ 값을 최대로 만들기 위한 방향으로 학습한다.</p>
<h2 id="cgan-conditional-generative-adversarial-networks">cGAN (Conditional Generative Adversarial Networks)</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/f9fc3e98-4b9a-4a94-8754-828e1fec1f09/image.png" alt=""></p>
<p>기존의 GAN 모델에 조건(Condition)을 추가한 모델이다.
여기서 조건은 클래스 레이블, 특정 속성, 데이터 특성 등이 포함될 수 있다.</p>
<p>전반적인 구조는 GAN과 동일하지만 레이블(y) 형태의 조건을 D, G의 입력에 추가한다. 이를 바탕으로 데이터를 생성하는 과정하거나 판별하는 과정에서 데이터를 제어할 수 있다.</p>
<h2 id="pix2pix">Pix2Pix</h2>
<p>cGAN에서 조건을 이미지로 받아 새로운 이미지를 생성하는 경우를 뜻한다. 즉, 이미지를 입력으로 받아 해당 이미지를 조건으로 목표 이미지를 생성한다.</p>
<p>입력 이미지와 출력 이미지 간의 1:1 매핑 관계를 갖는다. 지도 학습 방식을 사용하기 때문에 학습 과정에 반드시 입력, 출력 이미지 쌍이 필요하다.</p>
<h2 id="cyclegan">CycleGAN</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/dd4612ff-df95-4056-b624-6dbfee5aacdd/image.png" alt=""></p>
<p>Pix2pix 방식에서 학습을 위해 많은 입출력 쌍의 이미지가 필요하다는 단점을 보완하기 위한 모델이다.</p>
<p>비지도 학습 방식을 사용하기 때문에 입출력 쌍의 이미지가 존재하지 않아도 학습이 가능하다. </p>
<p>각 도메인에서 이미지를 제공받아 도메인 별로 이미지를 변환하고, 다시 원래대로 변환한 뒤 재복원된 이미지와 원래의 입력 데이터가 최대한 유사하도록 학습한다. 이 것을 Cycle Consistency Loss라고 한다. 여기서 도메인은 이미지의 특성을 의미한다. (ex. 우는 표정, 웃는 표정, 화난 표정 등)</p>
<p>CycleGAN은 학습 과정에서 두 가지 목적함수를 사용한다.</p>
<ol>
<li>기존의 GAN에서 사용하는 $L_{GAN}$</li>
<li>Cycle Consistency Loss인 $L_{cyc}$</li>
</ol>
<blockquote>
<p>$L(G, F, D_X, D_Y) = L_{GAN}(G, D_Y, X, Y) + L_{GAN}(F, D_X, Y, X) + \lambda L_{cyc}(G, F)$</p>
</blockquote>
<ul>
<li>$L_{GAN}(G, D_Y, X, Y)$ : unpaired image X를 조건으로 Y 생성, Y가 얼마나 실제 데이터와 흡사한지에 대한 loss</li>
<li>$L_{GAN}(F, D_X, Y, X)$ : unpaired image Y를 조건으로 X 생성, X가 얼마나 실제 데이터와 흡사한지에 대한 loss</li>
<li>$L_{cyc}(G, F)$ : X -&gt; Y -&gt; X&#39; 과정을 거친 뒤 X&#39;와 X가 얼마나 유사한지에 대한 cycle consistency loss</li>
</ul>
<h2 id="stargan">StarGAN</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/a9a3842b-7780-4939-a4db-b1190b92b14f/image.png" alt=""></p>
<p>CycleGAN을 통해 도메인 간의 변환을 효율적으로 학습시킬 수 있게 되었지만, 도메인 별로 별도의 생성 모델을 만들어야 한다는 단점이 존재한다.</p>
<p>이를 해소하기 위한 모델이 StarGAN으로, 단일 생성 모델만으로 여러 도메인을 모두 반영할 수 있는 구조를 제시한다. 기존의 CycleGAN 구조에 변환할 도메인을 나타내는 도메인 레이블을 추가함으로써 이를 제어한다.</p>
<p>StarGAN은 모델 학습을 위해 세 가지 목적함수를 사용한다.</p>
<ol>
<li>기존의 GAN에서 사용하는 $L_{GAN}$</li>
<li>도메인을 판단하기 위한 $L_{cls}$</li>
<li>Cycle Consistency Loss인 $L_{rec}$</li>
</ol>
<blockquote>
<p>$L_D = -L_{GAN} + \lambda_{cls} L^{\lambda}<em>{cls}$
$L_G = L</em>{GAN} + \lambda_{cls} L^f_{cls} + \lambda_{rec}L_{rec}$</p>
</blockquote>
<h2 id="progressivegan">ProgressiveGAN</h2>
<p>처음부터 고해상도 이미지를 생성하기 위해서는 많은 비용이 필요한데, 이를 해결하기 위해 저해상도 이미지 생성 구조에서 시작해서 단계적으로 증강하여 적은 비용으로 고해상도 이미지를 생성할 수 있는 모델이다.</p>
<p>저해상도 이미지를 먼저 생성한 뒤에 이를 고해상도로 변환하는 과정을 여러 번 거친다. 이미지 해상도를 키우는 과정에서 저해상도 이미지의 결과를 고해상도 이미지의 결과와 weighted sum함으로써 활용한다.</p>
<p>점진적으로 해상도를 키우는 과정에서 각 단계 별로 새로운 레이어가 추가되고, 점차 복잡한 세부 정보를 학습하게 된다. </p>
<h2 id="stylegan">StyleGAN</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/2cfba219-b471-420b-816f-30611083784f/image.png" alt=""></p>
<p>ProgressiveGAN의 구조에서 각 레이어의 생성 단계에 스타일 벡터를 주입함으로써 고해상도의 다양한 스타일 정보를 가진 이미지를 생성할 수 있다.</p>
<p>기존의 GAN에서는 잠재 벡터(z)가 바로 생성 모델의 입력으로 전달되지만, StyleGAN에서는 이를 별도의 스타일 네트워크 f를 통해 스타일 벡터(w)로 변환하여 입력으로 사용한다.</p>
<p>기존의 잠재 벡터(z)는 가우시안 분포를 가정하기 때문에 데이터가 복잡하게 얽혀 있는데, 이 얽힘을 데이터 분포에 맞춰 적절하게 풀리도록 하기 위해 스타일 네트워크 f를 사용한다.</p>
<p>이렇게 얻은 스타일 벡터(w)를 affine transform을 통해 변환하고, 이를 AdaIN(Adaptive Instance Normalization)을 통해 반영한다. </p>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://arxiv.org/abs/1812.04948">Karras, T., Laine, S., &amp; Aila, T. (2018). A Style-Based Generator Architecture for Generative Adversarial Networks.</a></li>
<li><a href="https://arxiv.org/abs/1711.09020">Choi, Y., Choi, M., Kim, M., Ha, J. W., Kim, S., &amp; Choo, J. (2018). StarGAN: Unified Generative Adversarial Networks for Multi-Domain Image-to-Image Translation.</a></li>
<li><a href="https://arxiv.org/abs/1703.10593">Zhu, J. Y., Park, T., Isola, P., &amp; Efros, A. A. (2017). Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks.</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 16]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-16</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-16</guid>
            <pubDate>Fri, 06 Dec 2024 05:32:08 GMT</pubDate>
            <description><![CDATA[<h2 id="16주차-회고">16주차 회고</h2>
<p>오랜만에 프로젝트가 없는 주간이었다.
몇달간 연이어 진행되던 프로젝트가 없으니 약간 허전하기도 했지만 쉬어가는 듯한 이 기간을 잘 활용해봐야겠다는 생각이 들었다.</p>
<h4 id="generative-ai">Generative AI</h4>
<p>이번주는 Generative AI에 관한 강의를 진행했다. 
요즘 텍스트 생성, 이미지 생성, 또는 멀티모달 생성과 관련한 기술들이 많이 이용되고 있다보니, 추천 시스템 도메인에도 LLM 기술이 활용되는 등 활용 분야가 다양해서 중요한 부분이었던 것 같다.</p>
<p>그 중에서도 멘토님이 요즘 추천 시스템 관련해서 LLM을 활용한 추천 등이 많이 연구되고 또 적용되고 있다고 말씀해주시면서 참고할 만한 발표 등에 대해 소개해주셨다.</p>
<p>🔍<a href="https://dan.naver.com/24/sessions/606">사용자 경험을 극대화하는 AI 기반 장소 추천 시스템 : LLM과 유저 데이터의 융합</a>
🔍<a href="https://dan.naver.com/24/sessions/585">검색과 피드의 만남: LLM으로 완성하는 초개인화 서비스</a>
🔍<a href="https://dan.naver.com/24/sessions/586">LLM 기반 추천/광고 파운데이션 모델</a></p>
<p>네이버 개최 컨퍼런스인 DAN 24에서 발표된 내용들인데 확실히 LLM 모델이 여러 방면에서 많이 활용되고 있다는 것이 느껴졌다.</p>
<h4 id="알고리즘-스터디">알고리즘 스터디</h4>
<p>프로젝트를 진행하지 않는 주간이다보니, 피어세션 때 프로젝트 관련 논의를 진행하지 않는 만큼 피어세션 시간에 나눌 이야기들이 적어져서, 이 시간을 활용해 다시 이전에 했던 것처럼 알고리즘 스터디를 활발하게 진행하기로 했다.</p>
<p>이번주는 최단 경로, 다익스트라 알고리즘에 관한 코딩 테스트 문제들을 풀었다.</p>
<h4 id="이력서-작성-및-피드백">이력서 작성 및 피드백</h4>
<p>그간 미루고 미뤄왔던 이력서 작성을 시작했다. 
시작하기 전에는 어떤 내용을 작성해야 할 지, 어떤 식으로 작성해야 할 지 막막하기만 했는데 막상 작성하고 보니 지나치게 겁을 먹었던 것 같기도 하다.</p>
<p>물론 단순히 초안을 작성했을 뿐이라 부족한 부분이 많아서 앞으로 많은 부분을 수정해 나가야 겠지만, 아무것도 없는 상태로 걱정만 하는 것보다는 든든해진 기분이 든다.</p>
<p>다른 팀원들에게 이력서를 공유하고 서로 피드백을 주고 받았는데, 다른 팀원들의 이력서를 보니 앞으로 수정해 나가야할 내용이 많을 것 같다.</p>
<h2 id="주간학습정리">주간학습정리</h2>
<p>📍LLM
📍<a href="https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-GANs-Generative-Adversarial-Networks">GANs</a>
📍<a href="https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-AEs-Autoencoders">AEs</a>
📍<a href="">Diffusion Models</a>
📍<a href="">Stable Diffusion</a></p>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩이력서 피드백 받은 내용 반영하여 수정하기
🚩프로젝트 코드 피드백 받은 내용 반영하여 리팩토링</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 15]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-15</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-15</guid>
            <pubDate>Mon, 02 Dec 2024 08:09:19 GMT</pubDate>
            <description><![CDATA[<h2 id="15주차-회고">15주차 회고</h2>
<p>이번 프로젝트는 여러가지 의미로 참 우여곡절이 많았다.🫠
일단 처음 시작할 때 깊게 생각하지 않고 구현 모델을 정했던 것이 가장 큰 문제였던 것 같다.
이번 프로젝트 주제에서는 큰 성능을 기대할 수 없는 모델이었는데, 한 번 구현해봐야겠다는 가벼운 마음으로 시작했다가 3주동안 그 모델만 붙잡고 있게 될 줄은 몰랐다.</p>
<p>내가 3주 동안 붙잡고 있던 모델은 DeepFM으로, 메타 데이터를 활용하는데 특히 강점이 있는 모델이었다. 이번 프로젝트 주제가 Movie Recommendation으로 Top-k 추천이 목표인 프로젝트였기 때문에 General Model, Sequential Model 등 메타 데이터를 활용하지 않는 모델이 많아서, 메타 데이터를 활용하는 모델을 구현하여 앙상블이나 하이브리드 모델을 통해 두 결과값을 조합하여 성능 개선을 시도해보고 싶었다.</p>
<p>내가 간과했던 부분은 DeepFM은 CTR 예측과 관련된 모델이기 때문에 Top-K 문제 해결에 그다지 좋은 성능을 보이는 모델은 아니라는 점이었다.😭
결국 3주 동안 붙잡고 있었지만 눈에 띄는 개선을 달성하지 못하고, 남은 시간이 짧아서 MF 모델 구현을 진행했다.</p>
<p>MF 모델은 비교적 구조가 단순한 모델이기 때문에 MF 모델의 ALS 연산 과정에서 아이템 연산을 진행할 때 영화 제목과 장르를 텍스트로 만들어 이를 TF-IDF 벡터로 만든 후 이를 반영하는 방식으로 메타 데이터를 반영해보고자 했고, 눈에 띄는 개선은 아니었지만 미약하게 성능이 오른 것을 확인할 수 있었다.</p>
<p>이번 프로젝트를 진행하며 확실하게 느낀 것은, <strong>프로젝트 진행 내용과 방향성을 정할 때 해결하고자 하는 목표와 가설을 명확하게 정하고, 이를 달성하기 위한 방향으로 모든 것을 진행해야 한다</strong>는 것이다.</p>
<p>우여곡절이 많았던 만큼 힘든 부분도 많았지만, 가장 큰 교훈을 얻어가는 프로젝트였던 것 같다.</p>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩프로젝트, 강의를 통해 학습한 내용 정리</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 13]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-13</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-13</guid>
            <pubDate>Fri, 15 Nov 2024 09:42:53 GMT</pubDate>
            <description><![CDATA[<h2 id="13주차-회고">13주차 회고</h2>
<p>이번주부터 새로운 팀원이 합류하게 되면서, 새로운 마음으로 새로운 프로젝트를 시작하게 되었다.🤗
그동안 할 일이 많아지고, 바빠지고 하며 시간이 지날 때마다 흐지부지되던 협업 관리를 새로운 마음으로 다시 체계를 잡았다.</p>
<h4 id="github-notion을-통한-협업-관리">Github, Notion을 통한 협업 관리</h4>
<p>잘 활용되지 않고 있던 깃허브를 최대한 활용하기 위해 issue, pull request, discussion을 잘 작성해보기로 했고, 전반적인 프로세스와 기록을 관리할 수 있는 팀 노션 페이지도 만들었다.
issue를 통해 현재 진행하고 있는 task에 대한 코드 관리를 하고, pull request를 통해 완료한 task를 정리하고 팀원들과 공유하기로 했다. discussion은 단순히 게시판 용도로만 활용해봤었는데, 이번에는 프로젝트와 관련된 아이디어 공유나 문서 관리를 해보기로 했다. </p>
<h4 id="베이스라인-모듈화">베이스라인 모듈화</h4>
<p>그간 프로젝트를 진행할 때마다, 각자 브랜치를 파서 각자 브랜치에서 모든 내용을 별개 라인으로 진행한 뒤에 마지막에 main으로 합치는 방식으로 작업을 했었다.
그렇다보니 마지막에 합치는 과정에서 서로 맞지 않는 방식이 많아 꼬이거나 코드가 잘 작동하지 않아 수정해야 하는 경우가 잦았다.
그래서 이번 프로젝트는 담당자를 정해 베이스라인에 해당하는 코드를 미리 모듈화하여 먼저 체계를 잡고 작업을 진행하기로 했다.</p>
<h4 id="movie-recommendation">Movie Recommendation</h4>
<p>이번에 새로 시작하게 된 프로젝트는 Top-K Recommendation이 주요 과제이고, implicit dataset을 사용해야 하는 프로젝트였다.
강의를 듣다보니 새로운 모델들이 많아서 데이터 EDA를 통해 데이터 구조를 명확히 파악한 뒤 여러 모델에 사용할 데이터셋을 통합하는 것이 중요하겠다는 생각이 들었다.</p>
<h3 id="주간-학습-내용">주간 학습 내용</h3>
<p>📍Multi-VAE
📍<a href="https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-DeepFM">DeepFM</a>
📍RNN, Transformer based Sequential Model</p>
<h3 id="다음주-목표">다음주 목표</h3>
<p>🚩implicit dataset을 사용할 때는 어떤 방식으로 EDA를 진행해야 할 지 고민해서 적절한 방식으로 EDA 진행해보기
🚩데이터 구조를 파악하여 범용적으로 사용할 수 있는 통합 데이터셋 구성해보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] DeepFM]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-DeepFM</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-DeepFM</guid>
            <pubDate>Thu, 14 Nov 2024 07:57:38 GMT</pubDate>
            <description><![CDATA[<h2 id="필요성">필요성</h2>
<p>CTR(클릭률)을 예측 및 극대화하기 위해 사용자의 행동 패턴에 숨겨져 있는 복잡한 피처 간의 상호작용을 파악하는 것이 중요하다. 하지만 기존 모델은 고차원 또는 저차원의 상호작용을 파악하는 것에 치우쳐 있거나 복잡한 피처 엔지니어링을 필요로 한다.</p>
<p>DeepFM 모델은 고차원과 저차원의 상호작용을 모두 고려할 수 있고, end-to-end 방식의 모델이기 때문에 별도의 피처 엔지니어링이 필요하지 않다.</p>
<blockquote>
<ul>
<li>저차원 상호작용 : 주로 두 가지의 특성 간의 단순한 조합과 상호작용</li>
</ul>
</blockquote>
<ul>
<li>고차원 상호작용 : 3개 이상의 여러 특성 간의 복잡한 상호작용</li>
<li>end-to-end : 입력부터 출력까지의 모든 단계를 한 모델에서 처리하는 것을 말한다. 입력값의 전처리나 피처 엔지니어링 없이 raw data를 입력값으로 받아 처리한다.</li>
</ul>
<h2 id="구조">구조</h2>
<p>DeepFM 모델은 저차원의 피처 간 상호작용을 잘 학습하는 FM component와 고차원의 상호작용을 학습하는 데 적합한 Deep component로 이루어져 있다. </p>
<p>wide 부분의 입력과 deep 부분의 입력을 별도로 구성해야 하는 Wide &amp; Deep 모델과 다르게 DeepFM은 동일한 입력을 사용한다. </p>
<h3 id="fm-component">FM component</h3>
<p>FM(Factorization Machine)은 pairwise product를 통해 latent vector를 이용하여 특성 간의 상호작용을 효과적으로 표현할 수 있다. 고차원의 상호작용을 표현하는 것도 가능하지만, 계산의 효율성과 성능 사이의 균형을 맞추기 위해 보통은 2차 상호작용까지만 고려한다.</p>
<h3 id="deep-component">Deep component</h3>
<p>심층 신경망을 활용하여 고차원의 상호작용을 학습할 수 있다. 심층 신경망 구조로는 CNN, RNN을 사용하기도 하지만 주로 DNN을 기반으로 사용한다. 기본적으로 Feed Forward Network 구조이다.</p>
<blockquote>
<p>CNN은 인접한 feature 간의 상호작용에 편향되어 있는 경우, RNN은 순차적인 의존성이 있는 경우에 더 적합하다.</p>
</blockquote>
<h2 id="입력-데이터셋-구성">입력 데이터셋 구성</h2>
<p>n개의 인스턴스로 구성되며, 각각의 인스턴스 (x, y)는 일반적으로 사용자와 아이템의 쌍으로 이루어진 m개의 필드를 포함한다. y는 0 또는 1로 클릭 여부를 나타내고, x는 연속형 또는 범주형 피처를 포함할 수 있다.</p>
<p>범주형 피처는 one-hot encoding으로 표현하고, 연속형 피처는 그대로 사용하거나, 이산화하여 one-hot encoding으로 표현한다. </p>
<h2 id="구현">구현</h2>
<blockquote>
<p>$\hat y = \mathit{sigmoid}(y_{FM} + y_{DNN})$</p>
</blockquote>
<ul>
<li>$y_{FM}$ : FM component의 출력</li>
<li>$y_{DNN}$ : Deep component의 출력</li>
</ul>
<h3 id="fm">FM</h3>
<blockquote>
<p>$y_{FM} = &lt;w, x&gt; + \sum^d_{j_1 = 1} \sum^d_{j_2=j_1+1} &lt;V_i, V_j&gt;x_{j_1}\cdot x_{j_2}$</p>
</blockquote>
<blockquote>
<p>$\sum^d_{j_1 = 1} \sum^d_{j_2=j_1+1} &lt;V_i, V_j&gt;x_{j_1}\cdot x_{j_2} = \frac{1}{2}\sum_{f=1}^{k}((\sum_{i=1}^{n}v_{i,f}x_i)^2-\sum_{i=1}^{n}v_{i,f}^2x_i^2)$</p>
</blockquote>
<ul>
<li>$w$ : 각 피처들이 결과값에 미치는 영향에 대한 가중치</li>
<li>$x$ : 입력 피처 벡터</li>
<li>$&lt;w, x&gt;$ : $w$와 $x$의 내적</li>
<li>$V_i, V_j$ : 피처 i와 j의 잠재벡터</li>
<li>$&lt;V_i, V_j&gt;$ : 피처 간의 2차 상호작용 표현</li>
<li>$x_{j_1}\cdot x_{j_2}$ : 피처의 실제 값들 간의 곱으로, 해당 상호작용의 강도를 나타내는 가중치 역할을 한다.</li>
</ul>
<h3 id="dnn">DNN</h3>
<blockquote>
<p>$y_{DNN} = \sigma(W^{|H|+1} \cdot a^H + b^{|H|+1})$</p>
</blockquote>
<ul>
<li>MLP layers로 구성</li>
<li>$|H|$ : hidden layer(은닉층)의 수</li>
</ul>
<ol>
<li><p>embedding layer</p>
<ul>
<li>sparse vector 입력을 dense vector로 변환</li>
<li>FM component에서 사용되는 latent vector $V$를 가중치로 사용하여 임베딩된 피처 벡터들을 압축</li>
</ul>
</li>
<li><p>hidden layers</p>
<ul>
<li>embedding layer에서 처리된 dense vector를 순전파 과정을 통해 처리</li>
<li>각 레이어에서 출력값($a^{(l)}$)에 가중치($W^{(l)}$)와 편향($b^{(l)}$)을 적용한 뒤 활성화 함수($\sigma$)를 통과하여 다음 레이어로 전달</li>
</ul>
</li>
</ol>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://arxiv.org/pdf/1703.04247">Rendle, S., &amp; Zhang, L. (2017). DeepFM: A Factorization-Machine based Neural Network for CTR Prediction.</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 12]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-12</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-12</guid>
            <pubDate>Fri, 08 Nov 2024 05:52:20 GMT</pubDate>
            <description><![CDATA[<h2 id="12주차-회고">12주차 회고</h2>
<p>저번주부터 강의와 구인구팀데이, 프로젝트와 과제가 겹쳐서 시간적으로 쫓기듯 당장 급한 일만 처리하는 것에 급급했었는데 이번주에 그 스노우볼의 여파를 제대로 받았다.
강의 내용과 실습, 과제 코드는 완벽하게 숙지되지 않았고, 그러다보니 이와 연관되어 있는 프로젝트의 베이스 라인 코드도 숙지하는데 시간이 소요되었다.
그렇다보니 해야할 일과 해보고 싶은 일은 많은데, 시간이 부족하여 제대로 처리하지 못하는 일이 발생하면서 시간 관리를 철저히 해야겠다고 다짐하게 되는 계기가 되었다.</p>
<h4 id="book-rating-prediction">Book Rating Prediction</h4>
<p>이번 프로젝트는 책 평점 예측 프로젝트였는데, 주제가 흥미롭기도 했고 전부터 논문을 레퍼런스로 바닥부터 모델 구현을 해보고 싶은 마음이 있어서 이번 기회에 도전해보았다.
내가 참고한 논문은 <a href="https://arxiv.org/abs/2007.00847">Collaborative Variational Autoencoder for Recommender Systems</a>라는 논문으로, 추천 시스템에서 사용자, 아이템의 잠재요인을 학습하여 모델에 활용하고 싶을 때 주로 사용되는 Collaborative Filtering을 VAE에 적용하여 추천 시스템에 적합하도록 구현한 모델이었다. 이 논문을 참고한 이유는 Collaborative VAE를 유저, 아이템, 평점뿐만 아니라 다른 메타 데이터들을 활용해보고 싶었고 또한 최종적으로 예측에 활용하는 데이터가 implicit feedback보다는 explicit feedback에 가까웠기 때문에 해당 모델을 선택했었다.</p>
<blockquote>
<p><a href="https://github.com/boostcampaitech7/level2-bookratingprediction-recsys-01/blob/main/code/src/models/CVAE.py">해당 모델 구현 코드 참고</a></p>
</blockquote>
<p>저번 프로젝트를 마무리하면서 wandb와 log 모듈을 활용해 실험 기록과 결과를 잘 관리해보고 싶다고 했었는데, 이번 프로젝트 베이스 라인에서 wandb 설정이 잘 되어 있어 큰 참고가 되었다. wandb sweep도 진행해보았는데 이전의 수업에서 wandb sweep을 배웠을 때는 어떻게 활용해야 할 지 몰라 잘 와닿지 않았는데 직접 모델을 구현하여 sweep을 활용해보니 어떤 방식으로 활용해야 할 지 조금 느낌이 왔다.</p>
<p>이번 프로젝트는 베이스 라인의 구조도 굉장히 모듈화가 잘 되어 있었고, 쉘 스크립트와 arguments 설정 등도 굉장히 잘 정리되어 있어 앞으로의 프로젝트를 진행하는 데에 큰 참고가 될 것 같았다.
다음 프로젝트도 이번 프로젝트 내용을 바탕으로 모듈화와 arguments 설정 등을 잘 정리하여 진행해보고 싶다.</p>
<h3 id="주간-학습-내용">주간 학습 내용</h3>
<p>📍Collaborative Variational Autoencoder for Recommender Systems</p>
<h3 id="다음주-목표">다음주 목표</h3>
<p>🚩시간, task 관리 잘하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 11]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-11</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-11</guid>
            <pubDate>Fri, 01 Nov 2024 10:04:52 GMT</pubDate>
            <description><![CDATA[<h2 id="11주차-회고">11주차 회고</h2>
<p>이번주부터는 RecSys 기초 프로젝트가 시작되었다.
그동안 배웠던 내용도 기본적인 AI 지식을 쌓기 위해 꼭 필요한 내용이었다고 생각하지만, 드디어 본격적으로 RecSys 도메인 분야의 내용을 배우기 시작하니 확실히 전보다 설레는 것 같다.
다만 내용은 정말 재밌고 흥미로운 것들이 많았지만 일정이 너무 촉박해서 과제 제출을 위해 강의를 서둘러 듣다보니 내용을 잘 파악하지 못했다. 
주말동안 강의 내용과 실습, 과제 코드들을 잘 숙지해봐야겠다.</p>
<h3 id="주간-학습-내용">주간 학습 내용</h3>
<p>📍<a href="https://velog.io/@slow_runner/AI-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-7%EA%B8%B0-Recommender-System">Recommender System과 평가 지표</a>
📍연관 분석과 연관 규칙
📍콘텐츠 기반 추천 (TF-IDF, Cosine Similarity)
📍MBCF (SVD, MF)
📍Word2Vec과 Item2Vec, ANN
📍Recommender System with DL, MLP, AE
📍GNN, RNN
📍FM, FFM, GBM</p>
<h3 id="다음주-목표">다음주 목표</h3>
<p>🚩WandB 사용, Log 관리 프로젝트에 적용해보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Recommender System과 평가 지표]]></title>
            <link>https://velog.io/@slow_runner/AI-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-7%EA%B8%B0-Recommender-System</link>
            <guid>https://velog.io/@slow_runner/AI-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-7%EA%B8%B0-Recommender-System</guid>
            <pubDate>Mon, 28 Oct 2024 07:29:54 GMT</pubDate>
            <description><![CDATA[<h1 id="추천시스템">추천시스템</h1>
<h2 id="등장배경">등장배경</h2>
<p>옛날과 다르게 유저가 상호작용할 수 있는 아이템의 수가 기하급수적으로 늘어나면서, 극소수의 인기있는 아이템에 소비가 집중되는 Long-tail Phenomenon이 발생하고 있다.
이러한 소비 추세에서 Long-tail에 해당되는 아이템에 대해 사용자가 원하는 아이템을 추천해줌으로써 아이템 소비를 분산시키고, 사용자에게는 개인화된 추천을 제공하기 위해 추천 시스템이 사용되고 있다.</p>
<h2 id="사용-데이터">사용 데이터</h2>
<p>추천 시스템을 구현하기 위해 사용되는 데이터는 크게 세가지이다.</p>
<blockquote>
<ol>
<li>사용자 메타 데이터</li>
<li>아이템 메타 데이터</li>
<li>사용자-아이템 상호작용 데이터</li>
</ol>
</blockquote>
<h2 id="평가지표">평가지표</h2>
<p>추천 시스템의 추천 목표는 크게 두가지로, 사용자가 선호할 만한 아이템에 대해 <strong>랭킹</strong>을 매기거나 혹은 사용자의 선호 확률을 <strong>예측</strong>하는 것이다.</p>
<p>어느 쪽이든 추천 시스템을 구현한 뒤에 추천 시스템을 평가하는 과정이 필요하다. 
최종적으로 추천 시스템 모델을 평가하는 것은 CTR, 매출 등과 같은 비즈니스/서비스적 관점에서의 기준이지만 개발 과정에서 모델 자체의 성능을 평가하는 것도 필요하다.</p>
<p>개발 과정에서 모델의 성능을 평가하는 것은 크게 오프라인 테스트와 온라인 테스트로 나뉘는데, 오프라인 테스트는 특정 지표의 스코어를 통해 모델의 성능을 정량화하는 것이고, 온라인 테스트는 실제 배포 환경에서 Traffic을 나누어 대조군 A와 실험군 B를 동시에 진행하여 테스트하는 것으로 A/B 테스트라고도 한다.</p>
<p>여기서는 오프라인 테스트 시에 사용되는 정량화된 지표들에 대해 자세히 알아보고자 한다.</p>
<p>랭킹 부분에서 추천 시스템의 성능을 평가하기 위해 사용되는 지표로는 Precision@K, Recall@K, MAP@K, NDCG@K 등이 있다.</p>
<p>예측 부분에서는 회귀 문제에서와 같이 RMSE, MAE 등의 지표를 사용한다.</p>
<h3 id="precisionk">Precision@K</h3>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/07d1c3d1-b699-44ab-a036-339e856e14ad/image.png" alt="">
Precision은 $\frac{TP}{TP + FP}$으로 추천 시스템이 추천해준 아이템 중에 실제로 사용자가 관심을 보인 아이템의 비율을 의미한다.</p>
<h3 id="recallk">Recall@K</h3>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/b2e18fe9-98ed-4af1-b231-10c8bc18b483/image.png" alt="">
Recall은 $\frac{TP}{TP + FN}$으로 사용자가 관심을 보인 아이템 중에 추천 시스템이 추천해 준 아이템이 얼마나 포함되는지의 비율을 의미한다.</p>
<h3 id="mapk">MAP@K</h3>
<p>MAP(Mean Average Precision)은 성능 평가에 순서 개념을 도입한 것이다. 즉, Precision과 Recall처럼 단순히 개수가 아닌 얼마나 선호하는지에 대한 순서 또한 평가 지표에 반영한 것이다.</p>
<p>Precision@i는 총 K개의 인덱스 중 i까지의 인덱스만 반영하여 계산했을 때의 스코어를 의미한다. AP@K는 Precision@1부터 Precision@K까지의 평균값을 뜻한다. 순서가 반영되기 때문에, 관련 아이템을 상위 순위로 추천할 수록 점수가 상승한다.</p>
<p>MAP@K는 이렇게 구한 AP@K의 모든 유저에 대한 평균값을 의미한다.</p>
<h3 id="ndcg">NDCG</h3>
<p>마찬가지로 순서를 반영한 평가 지표로, 추천 시스템에서 가장 많이 사용되는 지표 중에 하나이다.</p>
<blockquote>
<ul>
<li>Cumulative Gain : 순서에 따른 차등없이, 추천된 상위 K개의 아이템의 관련도를 합한 것</li>
</ul>
</blockquote>
<ul>
<li>Discounted Cumulative Gain : 순서를 반영하여 순서에 따라 CG를 Discount하여 계산<ul>
<li>관련도를 $log_2(i + 1)$로 나누어 순서에 따른 차등 부여</li>
</ul>
</li>
<li>Ideal DCG : 이상적인 추천이 일어났을 때의 DCG 값으로 사용자가 선호하는 순서에 따라 계산된 값을 의미</li>
<li><strong>Normalized DCG</strong> : $\frac{DCG}{IDCG}$, 추천 결과에 따라 구해진 DCG 값을 IDCG로 나눈 것으로 최대값은 1</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 10]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-10</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-10</guid>
            <pubDate>Fri, 25 Oct 2024 10:04:54 GMT</pubDate>
            <description><![CDATA[<h1 id="10주차-회고">10주차 회고</h1>
<p>이번 프로젝트는 저번 프로젝트에서 아쉬웠던 점이 많았고, 아쉬웠던 부분을 개선하기 위해 많은 부분을 노력했던 프로젝트였다. 노력한만큼 결과도 만족할만한 성과가 나와서 뿌듯했지만, 다음 프로젝트에서 좀 더 개선해야 할 부분도 생각해봐야 할 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/7f3f4c9a-60b9-45fe-b29a-d6817ca8dc7a/image.png" alt=""></p>
<h2 id="이번-프로젝트에서의-목표와-달성하기-위한-변화">이번 프로젝트에서의 목표와 달성하기 위한 변화</h2>
<p>이번 프로젝트에서 내 학습 목표는 <strong>실험 내용과 결과를 명확하게 기록</strong>하여 관리하고, <strong>재현성을 확보</strong>하는 것이었다. 또한 <strong>data leakage에 유의함과 동시에 valid score에 대한 신뢰성을 확보</strong>하고자 했다.</p>
<blockquote>
<ul>
<li>모델 실험 시마다, 사용한 피처와 모델, 하이퍼파라미터와 사용한 코드들을 포함해 해당 실험의 결과인 valid score와 public score를 모두 기록했다.</li>
</ul>
</blockquote>
<ul>
<li>실험 재현성을 확보하기 위해 모두 동일한 랜덤 시드를 사용하고, 사용한 시드 번호를 저장해두었다.</li>
<li>valid, test dataset의 내용이 학습 과정에 포함되지 않도록 clustering 과정과 같은 leakage가 발생할 수 있는 부분에서 train, valid, test dataset을 철저하게 분리하여 사용했다. </li>
</ul>
<h2 id="내가-모델을-개선한-방식">내가 모델을 개선한 방식</h2>
<h3 id="feature-engineering">Feature Engineering</h3>
<p>feature engineering 부분에서는 두 가지 관점에서 새로운 피처를 추가하여 모델을 개선했다. </p>
<ul>
<li>첫번째는 타겟 자체의 위도, 경도에 대해 클러스터링을 하여 해당 군집에 대한 피처를 추가했다. 클러스터링 결과에 따른 군집과, 포함되는 군집의 중심(centroid)과 데이터 샘플 간의 거리를 최종 피처로 추가했다. </li>
<li>두번째는 타겟의 주변 환경에 대한 피처를 추가했다. 타겟의 위치를 중심으로 반경 2km 이내의 공원 면적 총합, 학교 개수, 1km 반경 지하철역의 개수, 가장 가까운 지하철역까지의 거리 등을 추가했다.</li>
</ul>
<h3 id="modeling">Modeling</h3>
<p>modeling 부분에서는, 내가 개선을 시도할 때는 XGBoost에 중요도가 높은 상위 20개의 피처를 사용했을 때 성능이 괜찮다는 것이 확인된 상태였다. 그래서 해당 모델을 그대로 사용하되, 성능을 좀 더 끌어올리기 위해 데이터 샘플에 공간적 자기 상관이 존재할 것이라는 가설을 바탕으로 이를 반영해보고자 했다.</p>
<ul>
<li>공간적 자기 상관을 반영하기 위해 공간적 가중치 행렬을 생성했다. 데이터셋의 크기가 너무 커서 이를 모두 메모리에 올릴 수 없기 때문에 데이터셋을 청크 단위로 분할하여 데이터 샘플과 나머지 데이터 샘플 간의 공간적 가중치를 계산하여 희소 행렬로 저장하는 방식으로 메모리를 관리하며 가중치 행렬을 생성했다. 가중치는 거리 기반으로 생성하되, 1/거리의 가중치를 모두 합했을 때 1이 되도록 설정했다.</li>
<li>공간적 가중치를 위도, 경도를 기준으로 생성했기 때문에 면적에 따른 전세가 차이를 고려하지 못한다는 부분을 생각해서 면적 당 전세가를 구한 뒤, 이를 가중치 행렬과의 행렬곱을 통해 가중 평균을 구하여 피처로 추가했다.</li>
</ul>
<p>공간적 가중치 행렬을 생성하는 과정에서 있었던 고민과 구현 과정은 따로 링크로 첨부하겠다.</p>
<blockquote>
<p><a href="https://velog.io/@slow_runner/Spatial-Weight-Matrix">공간적 가중치 행렬(Spatial Weight Matrix) 구현 과정</a></p>
</blockquote>
<h2 id="개선한-모델의-성능과-평가">개선한 모델의 성능과 평가</h2>
<p>기존의 모델에 공간적 가중치를 부여했을 때 성능이 좋아지는 부분에서 데이터에 종속변수에 대한 공간적 자기 상관이 존재할 가능성이 높다는 가설을 뒷받침할 수 있었다.</p>
<h2 id="이전-프로젝트에서-아쉬웠던-점에-대해-이번-프로젝트에서-개선한-점">이전 프로젝트에서 아쉬웠던 점에 대해 이번 프로젝트에서 개선한 점</h2>
<p>이번 프로젝트에서는 저번 프로젝트에서 아쉬웠던 부분인 실험 결과 기록 및 관리에 대한 부분을 개선해보기 위해 다양한 시도를 해보았다. 노션에 우리 팀의 컨벤션을 명확히 지정하고, 실험 내용과 결과에 대해 기록하여 팀원들과 공유했다. 그리고 깃허브 브랜치를 컨벤션에 따라 나누고, 코드를 모듈화하여 나중에 정리하기 쉽도록 했다. 그 결과 실험한 모델과 그 내용에 따라 결과를 바로 확인할 수 있어서 실험 결과들을 분석하고 최종적으로 좋은 성능을 내는 모델을 결정하기 용이했고, 서로 진행하고 있는 내용을 노션에 기록해두어서 비효율적으로 동일한 작업을 겹쳐서 진행하는 일이 없도록 할 수 있었다.</p>
<h2 id="한계와-아쉬웠던-점">한계와 아쉬웠던 점</h2>
<p>공간적 행렬을 생성하는 과정에서 공간적 가중치 행렬이라고 해서 위도, 경도에 따른 거리만 고려하기보다는 built year 등과 같은 피처를 포함해서 계산했다면 좀 더 정확한 가중치 행렬이 만들어지지 않을까하는 아이디어를 떠올렸었다. 하지만 가중치 행렬을 생성하는 과정이 너무 오래걸렸기 때문에 시간적인 문제로 아이디어를 테스트해보거나 반영해보지 못했다. 
데이터셋의 크기나 그에 따른 소요시간 등을 고려하지 못해서 모델이 돌아가는 동안 비는 시간을 잘 활용하지 못한 것이 아쉽다.</p>
<h2 id="다음-목표와-개선-사항">다음 목표와 개선 사항</h2>
<p>데이터셋의 크기가 클 때는, 새로운 아이디어나 개선점을 적용할 때 처음부터 전체 데이터에 적용하기 보다는 적당한 크기의 샘플을 뽑아 테스트해본 뒤 전체 데이터에 적용해야 할 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Spatial Weight Matrix]]></title>
            <link>https://velog.io/@slow_runner/Spatial-Weight-Matrix</link>
            <guid>https://velog.io/@slow_runner/Spatial-Weight-Matrix</guid>
            <pubDate>Fri, 25 Oct 2024 09:44:24 GMT</pubDate>
            <description><![CDATA[<p>AI 부스트캠프 7기 과정 중 두번째 프로젝트를 진행하는 과정에서 모델링 관련하여 고민했던 내용을 정리해보고자 한다.</p>
<p>이번 수도권 아파트 전세가 예측 프로젝트를 진행하면서, <strong>데이터 샘플의 위도, 경도에 대한 공간적인 관계에 대해 종속변수인 전세가에 대한 공간적 자기 상관이 존재할 것이라는 가설</strong>을 세웠다.</p>
<p>즉, 거리가 가까운 이웃끼리는 전세가가 비슷할 것이라고 예상하고 이를 모델에 반영해보기 위해 공간적 가중치 행렬(Spatial Weight Matrix)을 사용해보기로 했다.</p>
<h1 id="spatial-weight-matrix">Spatial Weight Matrix</h1>
<h2 id="공간적-자기-상관">공간적 자기 상관</h2>
<p><strong>상관(Correlation)</strong>은 서로 다른 두 변수가 상관 관계를 갖고 동일한 규칙으로 변화하는 것을 말한다. <strong>자기 상관</strong>은 시간 또는 공간적으로 연속된 일련의 관측치들 간의 상관관계를 의미한다. 따라서 <strong>공간적 자기 상관</strong>은 공간적인 근접성에 의해 관측치, 즉 종속변수들 간의 상관관계를 갖는 것을 말한다.</p>
<h2 id="거리-기반-가중치-측정">거리 기반 가중치 측정</h2>
<p>공간적 자기 상관은 공간적인 근접성을 전제로 하기 때문에, 공간적인 근접성을 판단할 기준이 필요하다. 나는 거리를 기준으로 공간적인 근접성을 판단하기로 했다. 여기서 문제가 된 것은, 그렇다면 모든 샘플 간의 거리를 측정해야 한다는 것이다. 그런데 데이터셋의 크기가 상당히 큰 편이기 때문에, 모든 데이터 샘플 간의 거리를 측정하는 것은 자원의 한계가 있었다.</p>
<p>그래서 공간 분할 알고리즘을 이용해 계산해야 하는 범주를 좁혀보기로 했다. 공간 분할 알고리즘에는 대표적으로 KD Tree와 Ball Tree가 있는데, 나는 그 중에 Ball Tree 알고리즘을 선택했다.</p>
<h3 id="ball-tree--kd-tree">Ball Tree &amp; KD Tree</h3>
<p>Ball Tree와 KD Tree는 모두 고차원 데이터에서 효율적으로 근접 이웃을 탐색하거나 거리 계산을 하기 위해 사용되는 알고리즘이다. 다만 사용 목적과 차원의 크기에 따라 적합한 알고리즘이 달라질 수 있어서 이를 비교해보기로 했다.</p>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>Ball Tree</th>
<th>KD Tree</th>
</tr>
</thead>
<tbody><tr>
<td>작동 방식</td>
<td>중심점과 반경을 기준으로 분할</td>
<td>각 차원의 중간값을 기준으로 분할</td>
</tr>
<tr>
<td>거리 함수</td>
<td>유클리드, 하버사인 거리와 같은 다양한 거리 함수 사용 가능</td>
<td>유클리드 거리 계산에 최적화</td>
</tr>
<tr>
<td>차원의 크기에 따른 효율</td>
<td>고차원에 효과적이고, 저차원에서는 KD Tree보다 느릴 수 있음</td>
<td>저차원(2, 3차원)에 특히 효율적, 고차원일수록 차원의 저주로 인해 성능이 저하될 수 있음</td>
</tr>
<tr>
<td>나는 위도, 경도를 기준으로 근접 이웃을 탐색할 예정이기 때문에 고차원의 데이터는 아니지만, 위도, 경도에 따른 거리 계산에는 하버사인 거리 함수가 보다 정확하고 Ball Tree는 저차원에서도 KD Tree와 많은 성능 차이를 보이지 않기 때문에 최종적으로 Ball Tree 알고리즘을 이용하게 되었다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h4 id="ball-tree-알고리즘-사용-예시">Ball Tree 알고리즘 사용 예시</h4>
<pre><code class="language-python">from sklearn.neighbors import BallTree

data = ... # 분할할 데이터
tree = BallTree(data, metric=&#39;haversine&#39;) # metric에 사용할 거리 함수 지정

distance, indices = tree.query(data, k=n) # k : 찾고자 하는 근접 이웃 수

&#39;&#39;&#39;
distance : k개의 근접 이웃을 거리가 가까운 순으로 data 샘플 포인트와의 거리 반환
indices : 거리가 가까운 k개의 근접 이웃의 인덱스
&#39;&#39;&#39;</code></pre>
<h2 id="청크-분할과-희소-행렬">청크 분할과 희소 행렬</h2>
<p>공간적 가중치 행렬을 전체 크기로 생성하여 사용하기에는 데이터셋의 크기가 너무 컸다. out of memory로 인해 공간적 가중치 행렬을 메모리에 올릴 수 있는 크기로 분할해서 사용할 필요가 있었고, 이를 다시 희소 행렬로 변환하여 더욱 메모리 사용량을 줄였다. 최종적으로 청크 단위로 분할한 뒤, 이를 csr_matrix를 이용해 희소 행렬로 변환하고, 이를 pkl 파일로 저장하여 필요할 때 해당하는 부분만 로드하여 사용하는 것으로 공간적 가중치 행렬을 생성했다.</p>
<p><a href="https://github.com/boostcampaitech7/level2-competitiveds-recsys-01/blob/main/code/models/SpatialWeightMatrix.py">공간적 가중치 행렬 생성 클래스 코드</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 9]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-9</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-9</guid>
            <pubDate>Fri, 18 Oct 2024 07:27:38 GMT</pubDate>
            <description><![CDATA[<h1 id="9주차-회고">9주차 회고</h1>
<p>이번주는 프로젝트를 진행하면서 데이터셋에 대한 파악을 거의 완료하고 새로운 피처(파생 지표)를 추가하는 것에 집중했다. 모델을 고정해두고 새로운 피처들을 추가하다보니 어떤 피처가 모델에 어느정도의 영향을 미치는지, 그 피처가 중요한지 아닌지에 대해 파악하기가 수월했다.</p>
<p>또한 팀원들이 그간 각자 작업한 내용을 모두 합쳐서 정리하는 시간을 가졌다. 그리고 다들 각자의 어떤 방향성으로 진행하고 있는지를 계속해서 공유해서, 비효율적으로 같은 작업을 진행하고 있는 일이 없도록 했다.</p>
<p>다만 프로젝트에 너무 몰두하다보니, 기본적으로 해야하는 강의, 과제 등에 너무 소홀했던 것 같다. </p>
<h2 id="학습한-내용">학습한 내용</h2>
<p>📍KNN(K-nearest-neighbors)
📍SAR(Spatial Autoregressive Regression)</p>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩SAR 모델 구현 완료하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] Week 8]]></title>
            <link>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-8</link>
            <guid>https://velog.io/@slow_runner/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-AI-Tech-7%EA%B8%B0-Week-8</guid>
            <pubDate>Fri, 11 Oct 2024 06:43:29 GMT</pubDate>
            <description><![CDATA[<h1 id="8주차-회고">8주차 회고</h1>
<p>8주차부터는 새로운 두번째 프로젝트가 시작되었다. 
중간에 연휴가 길었어서 다시 마음을 다잡기가 쉽지 않다.
또 프로젝트에 몰두하다보니 자꾸만 강의를 듣는 것에 소홀해진다.
프로젝트도 중요하지만, 강의 내용도 확실하게 정리하고 넘어가야할 것 같다.</p>
<h4 id="프로젝트">프로젝트</h4>
<p>저번 프로젝트를 진행하면서 아쉬운 점이 많았다보니, 이번에는 그런 부분에 있어서 확실하게 개선해보고 싶다. 특히 실험 관리 부분에서 아쉬운 점이 많았다보니 이번 프로젝트에서는 실험 관리, 기록, 공유를 특히 신경쓰려고 노력하고 있다.
하지만 아직 익숙하지 않아서 오히려 정리하려고 할 때 혼란스러워지는 경우가 있어서, 좀 더 스스로 기준을 잡고 정리를 해봐야할 것 같다.</p>
<h2 id="학습한-내용">학습한 내용</h2>
<p>📍<a href="https://velog.io/@slow_runner/%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8Pipeline">모델 학습 파이프라인</a>
📍클러스터링 - K-means 클러스터링
📍Retrieval</p>
<h2 id="다음주-목표">다음주 목표</h2>
<p>🚩프로젝트 진행 내용 좀 더 체계적으로 기록하고 결과 정리하기
🚩PyTorch를 이용해서 딥러닝 모델(MLP) 만들어보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부스트캠프 AI Tech 7기] 모델 학습 파이프라인(Pipeline)]]></title>
            <link>https://velog.io/@slow_runner/%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8Pipeline</link>
            <guid>https://velog.io/@slow_runner/%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8Pipeline</guid>
            <pubDate>Tue, 01 Oct 2024 10:50:35 GMT</pubDate>
            <description><![CDATA[<p>새로운 프로젝트 시작에 앞서, 프로젝트를 시작할 때마다 고민하는 &#39;어디서부터 시작해야하는가&#39;에 대한 문제를 해결하기 위해, 모델 학습을 진행하는 파이프라인(Pipeline)에 대해 정리해보고자 한다.</p>
<h2 id="전반적인-진행-과정">전반적인 진행 과정</h2>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/31959afe-ccf8-4388-a279-7e16383c6396/image.png" alt="">
전반적인 과정은 위와 같이 진행된다.</p>
<p>먼저 프로젝트에 사용할 데이터를 확보하고, 확보한 데이터를 EDA하며 데이터를 확인한 뒤, 데이터를 전처리하고 추가적인 파생 지표를 생성하거나, 모델 학습에 사용할 피처를 선택한다. 그리고 모델을 선택하여 학습시킨 뒤, 해당 모델의 성능을 평가한다.</p>
<p>이제 전반적인 진행 과정을 파악했으나 각 단계별로 세부적으로 해야할 일을 짚어보자.</p>
<h3 id="eda">EDA</h3>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/2051df36-11f0-4a19-882e-c33eed81143d/image.png" alt=""></p>
<p>EDA는 Exploratory Data Analysis의 줄임말로, 탐색적으로 데이터를 분석하는 것을 말한다. 보통 이 단계에서 데이터를 시각화해보며 데이터의 특성을 파악한다.</p>
<ul>
<li><strong>변수 별 데이터 분포를 파악</strong>할 때는 주로 히스토그램을 사용하여 분포를 시각화한다. hist() 함수를 사용할 때 kde 매개변수를 True로 전달하면 곡선 플롯도 확인할 수 있다.</li>
<li><strong>결측치</strong>는 pandas의 info() 메소드를 이용하면 데이터 프레임에 대한 정보를 확인할 수 있는데, 그 중 결측치가 아닌 데이터 개수를 통해 확인할 수 있다. 또는 seaborn 라이브러리의 heatmap()을 통해 결측값을 시각화해서 확인할 수도 있다.</li>
<li><strong>이상치</strong>는 boxplot()을 통해 사분위수를 시각화하여 box 끝의 가로선을 벗어나는 위치에 있는 데이터들을 이상치라고 판단할 수 있다.</li>
<li><strong>변수 간 상관관계</strong>는 corr() 메소드를 통해 상관계수를 계산할 수 있다. 이를 heatmap()을 통해 시각화하여 표현할 수도 있다. 상관계수를 계산하지 않고 산점도를 통해 직접 변수 간 선형적인 관계가 존재하는지 확인하는 것도 가능하다.</li>
</ul>
<h3 id="data-preprocessing">Data Preprocessing</h3>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/690464b5-6e1a-4fb1-8e04-2a4077bad1c7/image.png" alt=""></p>
<p>데이터 전처리 과정에서는 모델 학습에 왜곡이 생기지 않도록 결측치, 이상치를 적절하게 처리하고 데이터 분포를 스케일링하여 변수별 범위를 맞춰준다.</p>
<ul>
<li><strong>결측치 처리</strong> 방법은 결측값을 삭제하거나, 대체하는 방식이 있다. 삭제는 결측값을 포함하는 행 또는 열을 삭제하는 것이고, 대체는 통계값, 회귀 모델을 사용한 예측값 등으로 대체하는 것이다. </li>
<li><strong>이상치 처리</strong>는 데이터 특성, 패턴에서 벗어난 값을 적절한 값으로 대체하거나 제거하는 것이다. 이 때 이상치를 판단하는 기준에는 Z-Score, IQR, DBSCAN 등을 사용할 수 있다. 이상치를 제거할 때는 데이터의 패턴을 해치치 않도록 신중한 판단 기준이 필요하고, 대체할 때는 평균, 중앙값 등을 사용하여 대체한다.</li>
<li><strong>스케일링</strong>은 변수의 크기를 일정하게 맞춰서 모델일 특정 변수에 지나치게 의존하거나 변수 별 관계를 왜곡하지 않도록 한다. 트리 기반 모델을 제외하고는 스케일링을 적용하는 것이 모델 성능에 도움이 된다. 스케일링 시에는 데이터의 분포나 특성에 따라 Standard Scaling, Min-Max Scaling, Robust Scaling, MaxAbs Scaling 등을 사용할 수 있다.</li>
</ul>
<h3 id="feature-engineering">Feature Engineering</h3>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/febe0b80-e9ed-4ed1-8f9f-dd2cb34e7236/image.png" alt=""></p>
<p>Feature Engineering 과정에서는 모델 학습에 도움이 될 만한, 데이터를 더 잘 표현할 수 있는 파생 변수를 생성하거나 노이즈가 될 수 있는 변수를 제외하여 모델 학습 시 사용할 변수들을 선택한다.</p>
<ul>
<li><strong>파생 변수 생성</strong> 시에는 변수 간의 상호작용을 고려하여 결합 가능한 두 개 이상의 기존 변수들로 파생 변수를 생성한다. 변수 간의 곱이나 차, 합 등으로 나타낼 수 있다.</li>
</ul>
<h3 id="model-training">Model Training</h3>
<p><img src="https://velog.velcdn.com/images/slow_runner/post/ed2127e2-83b1-471f-92c0-cb340263e081/image.png" alt=""></p>
<p>이제 준비된 데이터를 이용하여 모델 학습을 진행할 수 있다. 목표에 맞게 적절한 모델을 선택하고, 모델 학습을 진행한 뒤 하이퍼 파라미터 튜닝을 통해 적절한 하이퍼 파라미터 튜닝을 선택한다.</p>
<h3 id="model-evaluation">Model Evaluation</h3>
<p>모델 학습이 끝났다면 모델의 성능을 평가해야 한다. 모델 평가 시에는 데이터의 특성이나 목적에 따라 적절한 평가 방식을 선택해야 한다. 모델을 평가하는 지표는 다양하지만 회귀 모델에는 MSE, MAE 등의 평가 지표를 주로 사용하고 분류 모델에는 Accuracy, F1 score, 정밀도, 재현율 등의 지표를 사용한다.</p>
<p>평가 결과를 통해 다시 Feature Engineering 과정으로 돌아가 변수들을 변경하거나, 스케일링 방식을 변경하기도 하고, 모델을 바꾸는 등 조정을 통해 원하는 성능이 나올 때까지 파이프라인을 반복한다.</p>
]]></description>
        </item>
    </channel>
</rss>