<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>juhee_ai.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 23 Jul 2025 12:39:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>juhee_ai.log</title>
            <url>https://velog.velcdn.com/images/juhee_ai/profile/91a99e01-e043-41d7-aacf-c3ea541c5d40/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. juhee_ai.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/juhee_ai" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[코딩테스트 : 10815 숫자카드]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-10815-%EC%88%AB%EC%9E%90%EC%B9%B4%EB%93%9C</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-10815-%EC%88%AB%EC%9E%90%EC%B9%B4%EB%93%9C</guid>
            <pubDate>Wed, 23 Jul 2025 12:39:57 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>숫자 카드는 정수 하나가 적혀져 있는 카드이다. 상근이는 숫자 카드 N개를 가지고 있다. 정수 M개가 주어졌을 때, 이 수가 적혀있는 숫자 카드를 상근이가 가지고 있는지 아닌지를 구하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다. 두 숫자 카드에 같은 수가 적혀있는 경우는 없다.</p>
<p>셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 가지고 있는 숫자 카드인지 아닌지를 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이 수도 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.</p>
<p><strong>출력</strong></p>
<p>첫째 줄에 입력으로 주어진 M개의 수에 대해서, 각 수가 적힌 숫자 카드를 상근이가 가지고 있으면 1을, 아니면 0을 공백으로 구분해 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>시간 제한: 2초</li>
<li>메모리 제한: 256MB</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">5
6 3 2 10 -10
8
10 9 -5 2 3 4 5 -10</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">1 0 0 1 1 0 0 1</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>자료구조</li>
<li>탐색 (Search)</li>
<li>해시셋 / 이진 탐색</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li>N, M ≤ 500,000 → O(N x M) 불가능</li>
<li>효율적인 탐색 필요</li>
</ul>
<p><strong>해시셋(set) 사용 시</strong></p>
<ul>
<li>입력 저장: O(N)</li>
<li>탐색 M번: O(1) 평균 → 총 O(N + M)</li>
<li>공간복잡도: O(N) (카드 저장용)</li>
</ul>
<p><strong>이진 탐색 사용 시 (정렬 필요)</strong></p>
<ul>
<li>정렬: O(N log N)</li>
<li>탐색 M번: O(M log N)</li>
</ul>
<p>총 O(N log N + M log N)</p>
<p>둘 다 충분히 시간 내 가능 (2초 제한)</p>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li><p>해시셋 (set) 또는 이진 탐색 (Binary Search)</p>
<p>  → 둘 중 set이 가장 간단하고 빠름</p>
<p>  (문제에서 중복 없는 카드라고 했기 때문에 set에 적합)</p>
</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>sys.stdin.readline (입력 빠르게 받기 위해)</li>
<li>또는 bisect (이진 탐색 방식 선택 시)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li>N = M = 500,000일 때<ul>
<li>set 사용 시: 약 100만 연산 (충분히 처리 가능)</li>
<li>list + in: O(N x M) → 시간 초과 발생</li>
</ul>
</li>
</ul>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>숫자 카드를 set에 저장</li>
<li>M개의 숫자 각각에 대해 in 연산</li>
<li>결과를 리스트로 저장하고 출력</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">import sys

# 입력 빠르게
input = sys.stdin.readline

# 입력 처리
N = int(input())
cards = set(map(int, input().split()))
M = int(input())
queries = list(map(int, input().split()))

# 탐색
result = []
for num in queries:
    if num in cards:
        result.append(&#39;1&#39;)
    else:
        result.append(&#39;0&#39;)

# 출력
print(&#39; &#39;.join(result))</code></pre>
<h3 id="추가-팁--주의사항--많이-실수하는-점">추가 팁 / 주의사항 / 많이 실수하는 점</h3>
<ul>
<li>input() 대신 sys.stdin.readline() 사용해야 시간 초과 방지 가능</li>
<li>출력은 join으로 처리해야 시간 절약됨 (print(…, end=’ ‘)보다 빠름)</li>
<li>list로 탐색하면 시간 초과 (in이 O(N)임)</li>
<li>Python3는 set 탐색이 평균 O(1)이라 매우 적합</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 구간 합 구하기 5]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0-5</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0-5</guid>
            <pubDate>Wed, 23 Jul 2025 12:39:03 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>N x N개의 수가 N x N 크기의 표에 채워져 있다. (x1, y1)부터 (x2, y2)까지 합을 구하는 프로그램을 작성하시오. (x, y)는 x행 y열을 의미한다.</p>
<p>예를 들어, N = 4이고, 표가 아래와 같이 채워져 있는 경우를 살펴보자.</p>
<table>
<thead>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
</tr>
</tbody></table>
<p>여기서 (2, 2)부터 (3, 4)까지 합을 구하면 3+4+5+4+5+6 = 27이고, (4, 4)부터 (4, 4)까지 합을 구하면 7이다.</p>
<p>표에 채워져 있는 수와 합을 구하는 연산이 주어졌을 때, 이를 처리하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 표의 크기 N과 합을 구해야하는 횟수 M이 주어진다. (1 ≤ N ≤ 1024, 1 ≤ M ≤ 100,000) 둘째 줄부터 N개의 줄에는 표에 채워져 있는 수가 1행부터 차례대로 주어진다. 다음 M개의 줄에는 네 개의 정수 x1, y1, x2, y2가 주어지며, (x1, y1)부터 (x2, y2)의 합을 구해 출력해야 한다. 표에 채워져 있는 수는 1,000보다 작거나 같은 자연수이다. (x1 ≤ x2, y1 ≤ y2)</p>
<p><strong>출력</strong></p>
<p>총 M줄에 걸쳐 (x1, y1)부터 (x2, y2)까지 합을 구해 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<p>시간 제한: 1초</p>
<p>메모리 제한: 256MB</p>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">4 3
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
2 2 3 4
3 4 3 4 
1 1 4 4</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">27
6
64</code></pre>
<p><strong>예제 입력 2</strong></p>
<pre><code class="language-python">2 4
1 2
3 4
1 1 1 1
1 2 1 2
2 1 2 1
2 2 2 2</code></pre>
<p><strong>예제 출력 2</strong></p>
<pre><code class="language-python">1
2
3
4</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>누적합 (2차원 Prefix Sum)</li>
<li>구간 합 처리</li>
<li>구현</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li>표 생성: O(N$^2$)</li>
<li>2차원 누적합 배열 생성: O(N$^2$)</li>
<li>각 쿼리 처리: O(1) (누적합 이용 시)</li>
<li>총 쿼리 수: 최대 100,000 → 브루트포스로 하면 시간 초과 발생</li>
</ul>
<p><strong>총 시간 복잡도 : O(N$^2$ + M)</strong></p>
<p><strong>공간 복잡도: O(N$^2$)</strong></p>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>2차원 누적합(Prefix Sum)</li>
<li>일반 리스트 arr와 누적합 배열 prefix_sum 2개 사용</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline</code></pre>
<p>시간 초과 방지를 위해 빠른 입력 함수 사용</p>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li>N = 1024, M = 100000일 때<ul>
<li>단순하게 브루트포스로 각 영역의 합을 직접 더하면 O(N$^2$ * M) → 최악 1000억 연산 → 시간 초과</li>
<li>2차원 누적합으로 변환 시 → O(M) 쿼리 처리 가능</li>
</ul>
</li>
</ul>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>입력을 받고 arr이라는 2차원 배열을 만든다.</li>
<li>2차원 누적합 배열 prefix_sum을 만든다.<ul>
<li>prefix_sum[i][j] = arr[1][1]부터 arr[i][j]까지의 합</li>
<li>prefix_sum[i][j] = prefix_sum[i-1][j] + prefix_sum[i][j-1] - prefix_sum[i-1][j-1] + arr[i][j]</li>
</ul>
</li>
<li>쿼리 (x1, y1, x2, y2)에 대해 합을 구하는 공식 사용</li>
</ol>
<pre><code class="language-python">S = prefix[x2][y2]
    - prefix[x1-1][y2]
    - prefix[x2][y1-1]
    + prefix[x1-1][y1-1]</code></pre>
<ol>
<li>쿼리마다 위 공식을 써서 답을 출력한다.</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

n, m = map(int, input().split())
arr = [[0] * (n+1)] # 1-based index를 위해 padding

for _ in range(n):
    arr.append([0] + list(map(int, input().split())))

# 누적합 배열 생성
prefix = [ [0] * (n + 1) for _ in range(n + 1) ]

for i in range(1, n + 1):
    for j in range(1, n + 1):
        prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1] + arr[i][j]

# 쿼리 처리
for _ in range(m):
    x1, y1, x2, y2 = map(int, input().split())
    res = prefix[x2][y2] - prefix[x1-1][y2] - prefix[x2][y1-1] + prefix[x1-1][y1-1]
    print(res)</code></pre>
<pre><code class="language-python">import sys
input = sys.stdin.readline

n, m = map(int, input().splut())
arr = [[0] * (n+1)]
&#39;&#39;&#39;
첫 번째 줄이 4 3일 경우

arr = [[0] * (n+1)] 해석
n = 4이니까 n + 1 = 5
즉, arr = [[0, 0, 0, 0, 0]]
&quot;0번째 행을 dummy로 채워넣기&quot;
&#39;&#39;&#39;

for _ in range(n):
    arr.append([0] + list(map(int, input().split())))
&#39;&#39;&#39;
다음 입력이 주어진다면
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7

arr = 
[[0, 0, 0, 0, 0],
 [0, 1, 2, 3, 4],
 [0, 2, 3, 4, 5],
 [0, 3, 4, 5, 6],
 [0, 4, 5, 6, 7]]
&#39;&#39;&#39;

for i in range(1, n + 1):
    for j in range(1, n + 1):
        prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1] + arr[i][j]
&#39;&#39;&#39;
arr =
  j→   1   2   3   4
i↓  -----------------
1  |  1   2   3   4
2  |  2   3   4   5
3  |  3   4   5   6
4  |  4   5   6   7

prefix[2][3]을 계산한다고 해보자.
우리가 원하는 영역
(1, 1) (1, 2) (1, 3)
(2, 1) (2, 2) (2, 3)
6칸의 합을 구하고 싶음

prefix[2][3] = 
        prefix[1][3] &lt;- 위쪽 누적합
    + prefix[2][2] &lt;- 왼쪽 누적합
    - prefix[1][2] &lt;- 겹치는 영역 제거
    + arr[2][3]    &lt;- 현재 셀 값 더함

prefix[1][3]: (1, 1) ~ (1, 3) -&gt; 1 + 2 + 3 = 6
prefix[2][2]: (1, 1) ~ (2, 2) -&gt; 1 + 2 + 2 + 3 = 8
prefix[1][2]: (1, 1) ~ (1, 2) -&gt; 1 + 2 = 3
arr[2][3]:                    -&gt; 4

prefix[2][3] = 6 + 8 - 3 + 4 = 15
&#39;&#39;&#39;</code></pre>
<h3 id="추가-팁--주의사항--많이-실수하는-점">추가 팁 / 주의사항 / 많이 실수하는 점</h3>
<table>
<thead>
<tr>
<th>1-based index 사용</th>
<th>누적합 공식을 간단하게 만들기 위함. arr[0][<em>], arr[</em>][0]은 0으로 padding</th>
</tr>
</thead>
<tbody><tr>
<td>빠른 입력</td>
<td>입력 수가 많기 때문에 input() 대신 sys.stdin.readline() 사용</td>
</tr>
<tr>
<td>음수 값 없음</td>
<td>배열 값이 0 이상이므로 누적합 계산에서 음수로 인한 오류 걱정 X</td>
</tr>
<tr>
<td>(x1, y1) &gt; (x2, y2) 없음</td>
<td>문제에서 항상 x1 ≤ x2, y1 ≤ y2 보장</td>
</tr>
</tbody></table>
<p><strong>왜 0-based가 아닌 1-based로 arr과 prefix_sum 배열을 만드는가?</strong></p>
<p>2차원 누적합(Prefix Sum) 공식을 단순화하기 위해 사용</p>
<p>0-based index를 쓴다면 (0, 0)좌표도 실제 데이터로 사용해야 하지만 prefix[x1-1][y2] 또는 prefix[x1-1][y1-1]에서 -1이 나오면 인덱스 에러가 남 → if x1 == 0일 때는 별도 처리, 예외처리 코드를 매번 넣어줘야 함</p>
<p><strong>누적합 공식은 어떻게 유도되었는가?</strong></p>
<ol>
<li><p>한 번에 직접 더한다면?</p>
<pre><code class="language-python"> total = 0
 for i in range(x1, x2+1):
     for j in range(y1, y2+1):
         total += arr[i][j]</code></pre>
<p> 시간복잡도: O((x2 - x1 + 1) * (y2 - y1 + 1))</p>
<p> 최악의 경우 1024 x 1024 = 1,048,576번 덧셈</p>
<p> 쿼리 100,000개면 1000억번 연산 = 터짐</p>
</li>
<li><p>미리 계산해두는 방식 필요</p>
<p> 한 번만 전체를 훑고, 쿼리마다 바로 결과를 꺼내자</p>
<p> 이걸 가능하게 하려면 직사각형을 부분 합들로 나누는 방식이 수학적으로 가장 효율적</p>
<pre><code class="language-python"> prefix[i][j] = (1,1) ~ (i,j)의 합</code></pre>
<p> 이걸 만들 때 전체를 매번 더하면 O(N²),</p>
<p> 하지만 부분을 누적하면서 만들면 O(1)씩 누적 가능</p>
</li>
<li><p>왜 “가로 + 세로 - 겹침 + 자기” 구조인가?</p>
<pre><code class="language-python">     j
 i  ┌─────────────┐
    │             │
    │             │  ← prefix[i][j] (전체)
    │             │
    └─────────────┘</code></pre>
<ul>
<li><p>prefix[i-1][j]: 위쪽 전체</p>
</li>
<li><p>prefix[i][j-1]: 왼쪽 전체</p>
</li>
<li><p>prefix[i-1][j-1]: 위+왼쪽 겹침 (빼줘야 함)</p>
</li>
<li><p>arr[i][j]: 현재 셀</p>
<table>
<thead>
<tr>
<th><strong>방식</strong></th>
<th><strong>시간 복잡도</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td>직접 더하기</td>
<td>O(N²)</td>
<td>쿼리마다 전체 계산</td>
</tr>
<tr>
<td>누적합 사용</td>
<td>O(1)</td>
<td>덧셈 4번이면 끝</td>
</tr>
</tbody></table>
<p>덧셈 연산의 중복을 없애고 사전 계산한 값으로 한 번에 즉시 꺼내기 위해</p>
</li>
<li><p>이 계산을 하면 최대 100,000개 쿼리도 즉시 처리 가능</p>
</li>
<li><p>공식이 덧셈/뺄셈만으로 계산 가능해서 CPU에 부담 없음</p>
</li>
<li><p>겹치는 영역만 잘 제거해주면 여러 방향으로 응용 가능 (3차원도 가능)</p>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 구간 합 구하기 4]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0-4</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0-4</guid>
            <pubDate>Thu, 17 Jul 2025 12:39:18 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>수 N개가 주어졌을 때, i번째 수부터 j번째 수까지 합을 구하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 수의 개수 N과 합을 구해야 하는 횟수 M이 주어진다. 둘째 줄에는 N개의 수가 주어진다. 수는 1,000보다 작거나 같은 자연수이다. 셋째 줄부터 M개의 줄에는 합을 구해야 하는 구간 i와 j가 주어진다.</p>
<p><strong>출력</strong></p>
<p>총 M개의 줄에 입력으로 주어진 i번째 수부터 j번째 수까지 합을 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>1 ≤ N ≤ 100,000</li>
<li>1 ≤ M ≤ 100,000</li>
<li>1 ≤ i ≤ j ≤ N</li>
<li>시간 제한: 1초</li>
<li>메모리 제한 256MB</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">5 3
5 4 3 2 1
1 3
2 4
5 5</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">12
9
1</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>누적합(Prefix Sum)</li>
<li>구간 합(Query)</li>
<li>자료구조 기초</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li>수의 개수 N, 질의 수 M: 최대 100,000 → O(N + M) 알고리즘 필요</li>
<li>브루트포스로 구간합을 직접 계산하면 → O(M x N) → 시간 초과</li>
<li>누적합(Prefix Sum) 활용 시<ul>
<li>전처리: O(N)</li>
<li>쿼리 응답: O(1)</li>
</ul>
</li>
<li>총 시간복잡도 = O(N + M)</li>
<li>공간 복잡도: O(N) (누적합 저장용 배열)</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>누적합 배열</li>
<li>배열 인덱스 주의 (1-based / 0-based)</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>sys.stdin.readline → 빠른 입력 처리</li>
<li>itertools.accumulate (선택)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<pre><code class="language-python">N = 100_000
M = 100_000
누적합 → O(N) = 1e5
M번 쿼리 → O(M) = 1e5
→ 총 연산량 약 200,000 → 1초 내 가능 (충분)</code></pre>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li><strong>누적합 배열 생성</strong><ul>
<li>prefix_sum[i] = A[1] + A[2] + ... + A[i]</li>
</ul>
</li>
<li><strong>i부터 j까지의 구간합 = prefix_sum[j] - prefix_sum[i-1]</strong></li>
<li>입력은 <strong>1-based</strong>이므로 prefix_sum[0] = 0으로 설정</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

# 입력 처리
n, m = map(int, input().split())
nums = list(map(int, input().split()))

# 누적합 배열 생성 (prefix_sum[0] = 0)
prefix_sum = [0]
for num in nums:
    prefix_sum.append(prefix_sum[-1] + num)

# 질의 처리
for _ in range(m):
    i, j = map(int, input().split())
    print(prefix_sum[j] - prefix_sum[i - 1])</code></pre>
<h3 id="주의사항">주의사항</h3>
<ul>
<li>인덱스 착각 (1-based인데 0-based로 처리함)</li>
<li>입력이 너무 많을 때 input() 대신 sys.stdin.readline() 안 씀 → 시간 초과</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[YOLOv8 기반 경구 약체 객체 탐지 모델 개발기]]></title>
            <link>https://velog.io/@juhee_ai/YOLOv8-%EA%B8%B0%EB%B0%98-%EA%B2%BD%EA%B5%AC-%EC%95%BD%EC%B2%B4-%EA%B0%9D%EC%B2%B4-%ED%83%90%EC%A7%80-%EB%AA%A8%EB%8D%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@juhee_ai/YOLOv8-%EA%B8%B0%EB%B0%98-%EA%B2%BD%EA%B5%AC-%EC%95%BD%EC%B2%B4-%EA%B0%9D%EC%B2%B4-%ED%83%90%EC%A7%80-%EB%AA%A8%EB%8D%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0</guid>
            <pubDate>Wed, 16 Jul 2025 02:52:49 GMT</pubDate>
            <description><![CDATA[<h3 id="막막함-속에서-시작된-첫걸음">막막함 속에서 시작된 첫걸음</h3>
<p>딥러닝 기반 이미지 객체 탐지 분야로 첫 프로젝트를 시작하게 됐습니다. </p>
<p>처음엔 막막함 그 자체였습니다. 어디서부터 시작해야 할지, 어떤 모델을 써야 할지, 하나부터 열까지 전부 낯설고 두려웠습니다. 이 글에는 그 막막함을 시작으로 얽힌 매듭을 하나씩 풀어가며 문제를 정리한 과정이 담겨있습니다.</p>
<p>혹시나 저처럼 어떻게 시작해야할지 몰라서 막막한 분들이 있다면 도움이 되는 글이었으면 좋겠습니다. 진지하게 읽어주셔도 좋고, 가볍게 재미로 흘려보셔도 괜찮습니다.</p>
<h3 id="데이터-이전에-맥락부터">데이터 이전에, 맥락부터</h3>
<p>가장 먼저 마주한 건 이 프로젝트가 다루는 “경구 약제”라는 특수한 도메인이었습니다. 단순히 모델을 돌리기보다는 이 도메인을 제대로 이해하고, 데이터가 담고 있는 특성을 먼저 파악하는 것이 우선이라는 생각이 들었습니다.</p>
<p>이미지 처리나 객체 탐지에 대한 전문적인 딥러닝 지식이 부족했고, 어떤 모델이 적합할지조차 명확하지 않은 상태였습니다. 그렇기 때문에 초기 전략을 비슷한 도메인과 유사한 태스크를 어떻게 접근했는지를 먼저 탐색하고 이해하는 데에 집중하는 것으로 잡았습니다.</p>
<p>첫째로, 다양한 객체 탐지 모델을 리서치하고, 각각의 모델이 어떤 구조적 특성과 장단점을 갖는지 비교 분석 했습니다. 특히 이번 프로젝트에 사용할 Google Colab T4 환경에서 실험 가능한 자원과 시간 제약을 고려해 현실적으로 여러 번 실험할 수 있는 가벼운 구조인지, 성능을 위해 어느 정도 리소스를 투자해야 하는 모델인지 등을 검토해봤습니다.</p>
<p>또한, 실제로 경구 약제와 유사한 도메인에서 객체 탐지를 진행한 사례들을 찾아보고자 했습니다. 어떤 기준으로 모델을 선택했는지, 중요하게 본 요소는 무엇이고 어떤 이슈가 있었는지, 정확도를 위해 어떤 전략을 사용했는지를 중심으로 봤습니다.</p>
<h3 id="최고의-레시피를-찾아">최고의 레시피를 찾아</h3>
<p>모델 리서치하는 과정에서 다양한 논문을 참고했습니다. 가장 먼저 대표적인 one-stage detection 방식인 YOLO 시리즈[10][11]와, two-stage detection 방식의 Faster R-CNN[12] 같은 object detection 모델들을 중심으로 살펴보며 구조적 차이와 성능 특성을 비교했습니다. 그리고 약제와 같은 특수 도메인에서 활용된 fine-grained classification, OCR 기반 인식, prompt tuning, 멀티모달 융합 등 다양한 방식들을 제안한 논문들까지 추가로 참고했습니다.</p>
<p>One-state detection 모델은 이미지를 한 번에 처리해 객체의 위치와 클래스를 동시에 예측하는 방식으로 YOLO 시리즈와 SSD가 있습니다. 빠르고 가벼워서 실시간 처리에 적합하다는 장점이 있습니다.</p>
<p>Two-stage detection 모델은 이미지 내에서 Region Proposal을 뽑고 그 영역에 대해 정밀한 분류와 박스 조정을 수행하는 방식입니다. Faster R-CNN이 그 예이며 정확도는 높지만 연산량이 많고 속도가 느려 실시간 처리에는 적합하지 않습니다.</p>
<p>추가로 본 논문 중 가장 유의 깊게 봤던 건 OCR 기반 접근 [7]이었습니다. 약제에 새겨진 각인을 탐지하고 인식하는 구조가 생각보다 정교하게 발전했다는 걸 알았고 유사한 약제에 대해 각인 정보를 추가로 조합하면 좋은 예측 성능을 낼 수 있을 거라고 생각했습니다.</p>
<p>ViT 기반의 Visual Prompt Tuning 논문 [8]에서는 데이터가 부족한 클래스에 대해 few-shot 방식으로 대처합니다. 클래스가 많고 데이터가 한정적일수록 클래스 불균형이 더 심할거라고 생각해서 불균형 해소 방법을 염두해두고 있었지만 oversampling이나 class-weight 외에 few-shot 방식으로 해결할 수도 있다는 걸 처음 알았습니다.</p>
<p>Multimodal Pill ID 리뷰 [9]에서는 단순히 이미지를 넣는 방식이 아니라 색상·형태·각인 정보를 각각 따로 처리하고 최종적으로 종합 판단하는 방식이 소개됐습니다. 이미 데이터셋을 다 본 지금 관점에서 보자면, JSON 파일에 이러한 정보들이 세분화되어 담겨있었기 때문에 좋은 실험 방법이었다고 생각됩니다.</p>
<p>그 외에도 Pill-ID, Fine-grained Attenion 기반 모델 [5] 등을 참고했지만 처음 접하는 내용들이 너무나도 많았기 때문에(Transformer 등) ‘경구 약제 객체 탐지 task를 실험할 수 있는 방법이 정말 무궁무진하구나’라고만 생각하고 가볍게 봤습니다. 몇 없는 지식으로 여러 정보를 머릿속에 담자니 더 보다간 과부화만 올 것 같았습니다.</p>
<p>이 외로 비슷한 테스크의 자료들을 참고했습니다. 여러 참고 자료들이 있었고 YOLO 기반 객체 탐지에 OCR과 분류 모델을 결합한 파이프라인 [1], AI Hub에서 제공하는 ResNet 기반 Baseline [2]등 저희 테스크에 맞는 자료들도 존재했습니다. 하지만 세부적으로 어떤 실험을 거쳤고 왜 최종 모델로 선정했는지 등은 알 수 없어 참고용으로만 확인했습니다. [3][4]</p>
<p>최종적으로 저희 팀은 YOLOv8n를 실험의 baseline으로 선택했습니다. 처음에는 구조가 가볍고 실험 반복하기 적합하다는 점에서 YOLOv8n를 기준으로 삼았고 이후에는 성능 향상과 최신 구조 반영을 위해 YOLOv11을 실험했습니다. 또한 비교군으로 Faster R-CNN, YOLO + OCR 조합, ResNet + YOLOv8 연계 구조도 함께 테스트했습니다.</p>
<h3 id="본격적으로">본격적으로</h3>
<p>저희 팀의 프로젝트 목표는 두 가지였습니다. 첫째, 이 테스크 전용 Kaggle 리더보드에서 가능한 한 높은 점수를 기록하는 것. 둘째, 실제 경구 약제를 정확하게 탐지하고 분류할 수 있는 모델을 만드는 것이었습니다.</p>
<p>이 두 가지를 위해 실험 전략을 짰습니다. 먼저, 다양한 객체 탐지 모델을 폭넓게 실험해본 뒤, 가장 성능이 우수한 모델을 선정합니다. 그리고 해당 모델의 구조를 분석해 성능을 더 끌어올릴 수 있는 모든 요소(하이퍼파라미터, 증강, 학습 기법, 후처리 방식 등)을 적극적으로 조정하고 개선했습니다.</p>
<h3 id="시행착오를-겪으며">시행착오를 겪으며</h3>
<p>하지만 실험은 항상 뜻대로 흘러가지 않았습니다. 프로젝트는 2주 간 진행됐고, 많은 시행착오가 발생했습니다.</p>
<ol>
<li><p><strong>데이터 정제에 소요된 과도한 시간</strong>
초기 데이터셋에는 이미지 누락, 바운딩 박스 오류, 라벨 누락 등 여러 문제가 있었고, 수작업으로 보완하며 JSON 파일을 새로 생성하는 데만 약 2일이 소요됐습니다. 특히 약제의 앞면과 뒷면이 동일한 클래스로 처리되어 있어 시각적으로는 완전히 다른 두 이미지를 같은 클래스로 다뤄야 한다는 문제는 끝내 해결하지 못했습니다. 프로젝트가 끝날 무렵 AI Hub에서 누락된 이미지를 포함한 데이터셋을 다시 받을 수 있다는 걸 알아 이 또한 진행했지만 라벨 오류를 정제하는 데 여전히 많은 시간을 쏟아야 했습니다.</p>
<img src="https://velog.velcdn.com/images/juhee_ai/post/fd9e483e-bae8-421e-9bbb-f3b4f8215635/image.png" width="100%" height="100%">
바운딩 박스 오류 예시 ↑ 
<img src="https://velog.velcdn.com/images/juhee_ai/post/b2d7490c-c2de-4ff8-91d7-154dc3ffe597/image.png" width="100%" height="60%">
클래스 오분류 예시 ↑
</li>
<li><p><strong>다양한 실험에도 불변하는 성능</strong>
데이터셋을 정제한 후 여러 모델(YOLOv8/v11, Faster R-CNN)을 실험한 결과, Kaggle 리더보드에는 거의 동일한 점수(0.99532)가 반복됐습니다. 이를 해결하기 위해 다양한 방식의 augmentation을 시도했습니다. 모듈을 통한 강한 증강/약한 증강, 알약 누끼를 따서 검은 배경 위에 유사 알약을 배치한 synthetic 이미지 생성, 오분류 사례 중심의 배경 포토샵 자동화, crop &amp; collage 방식 등. 학습 구조도 크기(s/m/l)와 loss function(focal loss), 하이퍼파라미터까지 다양하게 조정했지만 점수는 변하지 않았습니다.</p>
<img src="https://velog.velcdn.com/images/juhee_ai/post/c12be2a8-ad7d-4343-8eb1-9c7882cd7410/image.png" width="70%" height="60%">
synthetic 이미지 생성 예시 ↑
<img src="https://velog.velcdn.com/images/juhee_ai/post/ec2c395f-5488-4aeb-83b6-fdc4c8a8dae5/image.png" width="70%" height="60%">
강한 증강 예시 ↑ 
.
원인을 찾기 위해 여러 방법을 시도해보다가 훈련 이미지와 테스트 이미지 간의 유사도 분석을 해봤습니다. SSIM을 사용해 두 이미지 간의 시각적 유사도를 비교했고 그 결과 전체 테스트셋의 80% 이상이 SSIM 1.0으로 훈련 이미지와 일치한다는 사실을 발견했습니다. 결국 나중에서야 데이터 누수 문제가 근본 원인이라는 것을 알게 됐습니다. 또한 테스트셋 내 일부 이미지에는 라벨 오류까지 포함돼 있었기 때문에, 점수 자체가 실제 모델의 일반화 성능을 반영하지 못한다는 문제도 있었습니다.
</li>
<li><p><strong>증강이 성능을 떨어뜨렸던 이유</strong>
augmentation 강도를 높일수록 오히려 성능이 저하되는 현상도 겪었습니다. 처음에는 일반화 실패라고 생각했지만, 실제로는 데이터 누수와 테스트셋 특성이 원인이었습니다. 훈련 데이터와 테스트 데이터가 대부분 동일한 환경(조명, 배경, 각도 등)에서 촬영된 유사한 이미지들이었기 때문에 훈련 이미지를 과하게 변형하면 오히려 테스트셋과 괴리감이 생겨 성능이 떨어졌습니다. 결국 이 프로젝트에서는 일반화보다 ‘훈련셋과 최대한 유사한 이미지’를 그대로 보여주는 쪽이 더 높은 점수를 내는 구조였고, 증강이 실제로 도움이 되지 않았던 셈입니다.</p>
</li>
<li><p><strong>사소한 실수가 성능 저하로 이어짐</strong>
모델 학습 시 원본 이미지의 입력 해상도를 설정할 때 (height, width) 대신 (width, height)로 잘못 기입하여 성능이 저하되는 실수도 있었습니다. </p>
</li>
<li><p><strong>클래스 불균형 문제의 한계</strong>
전체 클래스 수는 많은 반면 클래스별 이미지 수가 매우 적어 불균형 문제가 심각했습니다. 한 이미지에 여러 알약이 담겨 있는 데이터셋 특성상 클래스 균형을 고려한 학습이 구조적으로 어려웠고, train/val 분리도 제대로 수행하지 못했습니다. 임의로 증강한 데이터를 validation set으로 사용했습니다. 
클래스 불균형에 대해서는, 오버샘플링이나 focal loss를 적용하는 것, 증강 외에도 rare class에 대해 few-shot 학습 구조나 prototype 기반 분류기를 도입해보거나, class-aware sampling 전략으로 학습 배치를 조정하는 등 여러 시도를 해보면 좋았겠다는 아쉬움이 남는 것 같습니다.</p>
</li>
<li><p><strong>실험 코드 관리의 어려움</strong>
첫 프로젝트였기에 실험을 반복할수록 코드가 복잡해지고 비효율적으로 정리되지 않는 문제가 발생했습니다. 실험별 버전을 체계적으로 기록하지 못했고, git을 적극적으로 활용하지 않아 비슷한 코드를 계속 반복해서 작성해야 했던 비효율성이 컸습니다. 다음 프로젝트에서는 실험 단위별 버전 관리와 코드 구조화에 더 신경써보려고 합니다.</p>
</li>
</ol>
<h3 id="성능-평가-결과">성능 평가 결과</h3>
<table>
<thead>
<tr>
<th><strong>모델명</strong></th>
<th><strong>mAP@50</strong></th>
<th><strong>mAP@50-95</strong></th>
<th><strong>Precision</strong></th>
<th><strong>Recall</strong></th>
<th><strong>FPS (추론 속도)</strong></th>
<th><strong>파라미터 수</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>YOLOv8n</strong></td>
<td>0.993</td>
<td>0.983</td>
<td>0.994</td>
<td>0.999</td>
<td><strong>98 FPS</strong></td>
<td>3.2M</td>
</tr>
<tr>
<td><strong>YOLOv11l</strong></td>
<td>0.993</td>
<td>0.980</td>
<td>0.993</td>
<td>0.998</td>
<td>약 45 FPS</td>
<td>약 25M (추정)</td>
</tr>
<tr>
<td><strong>Faster R-CNN</strong></td>
<td>0.987</td>
<td>0.807</td>
<td>0.750</td>
<td>1.000</td>
<td>약 12 FPS</td>
<td>42M</td>
</tr>
</tbody></table>
<p>YOLOv8n 모델은 mAP@50 기준 0.993, 추론 속도 98FPS라는 우수한 성능을 보였습니다.</p>
<p>Faster R-CNN은 높은 정확도를 가질 것이라는 기대와는 다르게 mAP@50-95가 0.807로 다소 낮은 성능을 보였습니다. NMS 튜닝 부족, rare class에 대한 적응 실패, 연산량이 많아 충분히 학습을 돌리지 못한 점 등이 복합적으로 작용한 결과라고 판단했습니다. 또한 mAP@50 대비 mAP@50-95가 급격히 낮아지는 건 과적합의 명확한 신호였습니다.</p>
<p>실제로 다양한 실험에서도 과적합 문제가 계속 나타났습니다. focal loss, EMA, label smoothing 등의 학습 기법을 적용해봤고, 다양한 증강 기법도 시도했지만 성능에는 거의 영향을 주지 못했습니다. </p>
<table>
<thead>
<tr>
<th>OCR 엔진</th>
<th>한글 인식률</th>
<th>영문 인식률</th>
<th>평균 속도 (초)</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong>EasyOCR</strong></td>
<td><strong>98.3%</strong></td>
<td><strong>97.9%</strong></td>
<td><strong>1.2</strong></td>
<td>GPU 지원, 한글/영문 인식 강점</td>
</tr>
<tr>
<td>Tesseract</td>
<td>94.7%</td>
<td>92.1%</td>
<td>2.7</td>
<td>CPU 전용, 속도 느림</td>
</tr>
</tbody></table>
<p>OCR도 실험의 주요 요소였습니다. EasyOCR과 Tesserack 두 가지 엔진을 활용해 한글과 영문 인식 성능을 비교해봤고, EasyOCR이 평균 인식률 98% 이상으로 우수한 결과를 보였습니다. 그러나 실제 약제명과의 일치율은 기대에 미치지 못했습니다. 텍스트 인식 자체는 잘 되지만 회전된 이미지, 배경 노이즈, 저조도 환경 등에서 인식률이 급격히 떨어졌고, 약제명은 단순 문자열이 아니라 정제된 형태소 기반 이름이라 후처리 없이 활용하기엔 어려움이 컸습니다.</p>
<img src="https://velog.velcdn.com/images/juhee_ai/post/038d8a37-3a33-4d58-9e8c-b85763b7102b/image.png" width="40%" height="60%">

<p>잘 예측한 예 (BSP)</p>
<img src="https://velog.velcdn.com/images/juhee_ai/post/77466f92-ec84-4aee-aee6-fcb5cb52644e/image.png" width="40%" height="60%">

<p>예측하지 못한 예 (No Text)</p>
<img src="https://velog.velcdn.com/images/juhee_ai/post/8baa2a79-dcc7-41d2-a3ff-e1ace8749eb7/image.png" width="40%" height="">
잘못 예측한 예 (Noltec |.|)

<p>YOLO와 ResNet을 결합한 two-stage 실험도 진행했습니다. YOLO가 객체를 탐지하고, cropped 이미지를 ResNet18이 분류하는 구조였는데, YOLOv8n 대비 약 0.0065의 성능 상승이 있었고, cascade 방식에 비해 연산량이 적고 효율적이었습니다. 하지만 성능 변화가 미미했고, 이후 소수 클래스에 대한 보완을 임베딩 기반이나 트랜스포머 계열 분류기로 확장해보았다면 더 좋은 실험 방법이 됐을 것 같습니다.</p>
<p>하지만 주어진 테스트셋의 심각한 누수 문제로 인해 모든 수치 성능은 일반화 적용에 신뢰하기 어려운 면이 있었습니다. 그래서 라벨이 없는 외부 이미지 12장을 대상으로 정성 평가를 진행했습니다. 그 결과, 강한 증강으로 학습한 대형 모델인 YOLOv11이 더 많은 객체를 정확히 탐지했고, 약한 증강만 한 작은 모델보다 일반화 성능이 좋다는 걸 있었습니다.</p>
<img src="https://velog.velcdn.com/images/juhee_ai/post/75771a2f-6ffa-45e4-a174-5c5dc1afbdd7/image.png" width="60%" height="60%">
YOLOv11의 예측 잘된 예 (출처: 약학정보원)

<h3 id="마치며">마치며</h3>
<p>첫 프로젝트를 진행하면서는 ‘아 이번 프로젝트 망했네. 어떻게 수습할까?’라는 생각 뿐이었습니다. 하나도 내 뜻대로 되는 게 없었고, 순조롭게 흘러가지 않았고, 결과도 엉망인 것처럼 느껴졌습니다. 하지만 되돌아보니 그만큼 많이 배우고 성장한 시간이었습니다.</p>
<p>무엇보다 가장 어려웠던 점은 실험을 반복하는 과정에서 Git이나 폴더 구조를 체계적으로 정리하지 못했던 점입니다. 실험을 거듭할수록 코드는 점점 복잡해지는데, 나중에 어떤 코드가 어떤 실험을 위한 것인지 혼란스러워졌습니다. 구조화된 실험 관리의 중요성을 절실히 느꼈습니다.</p>
<p>그리고 두 번째로 어려웠던 건, 어떤 시도가 성능에 어떤 영향을 주는지에 대한 판단이 부족했다는 점입니다. 다양한 요소를 건드려보긴 했지만, 무엇이 진짜 중요한지 감을 잡지 못하고 시간만 빠르게 흘러갔던 순간들이 많았습니다. 특히 점수가 높게 나올 때도 이유를 명확히 파악하지 못한 채 마냥 좋아했던 건 아쉬움으로 남습니다.</p>
<p>세 번째로, 실엄의 정량적 결과를 체계적으로 기록하지 못했던 점입니다. 실험마다 어떤 학습기법을 사용했고 어떤 후처리를 적용했는지에 대한 로그가 누락되거나 불완전한 경우가 많았습니다. 나중에 어떤 방식이 효과적이었는지 되짚어보기 쉽지 않았습니다.</p>
<p>마지막으로, 프로젝트 초반에 설정한 리더보드 점수 높이는 것과 탐지 정확도 극대화하는 두 가지 목표에만 집착하다 보니 모델의 일반화 성능을 고려한 폭넓은 실험은 오히려 놓치게 됐습니다. 다양한 증강 기법이나 모델 조합은 실험했지만, 결과적으로는 리더보드 점수에 영향을 주지 않는 실험은 생략하게 됐고 이게 오히려 실험의 깊이를 얕게 만든 원인이기도 했습니다.</p>
<p>다음 프로젝트에서는 전처리, 학습, 후처리 각 단계마다 중간 점검과 검증 단계를 명확히 두고 Git을 통해 실험 버전을 정리하며 각 실험의 전략과 결과, 적용한 학습기법과 후처리 내용 등을 자세히 기록하고 사전 조사 내용을 실험 설계에 적극적으로 반영하면 좋을 것 같다는 생각을 했습니다.</p>
<p>이번 프로젝트는 성능 좋은 모델을 만드는 경험 뿐만 아니라 하나의 프로젝트를 처음부터 끝까지 어떻게 이끌고 기록하며 마무리할 수 있을지 연습하는 시간이었다고 생각합니다. 매듭 묶인 실을 처음 풀어보니 이렇게도 건들여보고 저렇게도 건들여봐서 실이 엉망진창이 되고 엉성하게 풀렸다는 느낌을 지울 수 없는 다사다난한 첫 프로젝트였지만 ‘뭐라도 하면 풀리긴 풀리는구나..’ 싶었습니다. 이 글을 읽는 분들께 조금이나마 도움이 되었기를 바랍니다. 읽어주셔서 감사합니다.</p>
<p>최종 코드는 아래 GitHub에서 확인하실 수 있습니다.
<a href="https://github.com/Team-Epoch-4/Project">https://github.com/Team-Epoch-4/Project</a></p>
<aside>

<h3 id="참고자료">참고자료</h3>
<p>[1] <a href="https://velog.io/@rkdghwjd1999/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EA%B5%AC-%EC%95%BD%EC%A0%9C-%EB%B6%84%EB%A5%98-%EC%8B%9C%EC%8A%A4%ED%85%9C">“뭔약이유?” 경구약 분류 시스템</a></p>
<p>[2] <a href="https://www.aihub.or.kr/aihubdata/data/view.do?dataSetSn=576">AI Hub – 의약품 이미지 데이터셋 (경구약제)</a></p>
<p>[3] <a href="https://www.rne.or.kr/gnuboard5/bbs/download.php?bo_table=rs_year&amp;wr_id=1923&amp;no=0">경구약제 분류 및 효능 안내 시스템 (한국로봇학회 논문, 2023)</a></p>
<p>[4] <a href="https://www.sci-gifted-festival.kr/bbs/board.php?bo_table=pro2&amp;wr_id=105">YOLO v8을 이용한 경구약제 분류 및 효능, 부작용 안내 프로그램 개발</a></p>
<p>[5] <a href="https://arxiv.org/abs/2103.00295">Pill-ID: Towards Transparent and Transferable Pill Identification from Images</a></p>
<p>[6] <a href="https://arxiv.org/abs/2011.12546">Fine-grained Visual Classification via Multi-Feature Embedding and Attention</a></p>
<p>[7] <a href="https://arxiv.org/abs/2001.05086">OCR + Detection 기반 약제 인식 구조 관련 리뷰: Scene Text Detection and Recognition: The Deep Learning Era</a></p>
<p>[8] <a href="https://arxiv.org/abs/2203.12119">Visual Prompt Tuning for Pill Recognition in Few-shot Settings</a></p>
<p>[9] <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5962805/">Multimodal Approaches for Pill Identification using Shape, Color, Imprint and OCR</a></p>
<p>[10] <a href="https://docs.ultralytics.com/ko/">YOLOv8 - Ultralytics 공식 문서</a></p>
<p>[11] [YOLOv11]</p>
<p>[12] <a href="https://arxiv.org/abs/1506.01497">Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks</a></p>
</aside>]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 수열]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%88%98%EC%97%B4</guid>
            <pubDate>Thu, 10 Jul 2025 01:17:38 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>매일 아침 9시에 학교에서 측정한 온도가 어떤 정수의 수열로 주어졌을 때, 연속적인 며칠 동안의 온도의 합이 가장 큰 값을 알아보고자 한다.</p>
<p>예를 들어, 아래와 같이 10일 간의 온도가 주어졌을 떄,</p>
<p>3 -2 -4 -9 0 3 7 13 8 -3</p>
<p>모든 연속적인 이틀간의 온도의 합은 아래와 같다.</p>
<p><img src="attachment:294c5cdb-9300-4e7f-95c1-f02e466d9ee6:image.png" alt="image.png"></p>
<p>이때, 온도의 합이 가장 큰 값은 21이다.</p>
<p>또 다른 예로 위와 같은 온도가 주어졌을 때, 모든 연속적인 5일 간의 온도의 합은 아래와 같으며,</p>
<p><img src="attachment:1653abda-4353-4060-8dea-184654c3a9de:image.png" alt="image.png"></p>
<p>이때, 온도의 합이 가장 큰 값은 31이다.</p>
<p>매일 측정한 온도가 정수의 수열로 주어졌을 때, 연속적인 며칠 동안의 온도의 합이 가장 큰 값을 계산하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에는 두 개의 정수 N과 K가 한 개의 공백을 사이에 두고 순서대로 주어진다. 첫 번째 정수 N은 온도를 측정한 전체 날짜의 수이다. N은 2 이상 100,000 이하이다. 두 번째 정수 K는 합을 구하기 위한 연속적인 날짜의 수이다. K는 1과 N 사이의 정수이다. 둘째 줄에는 매일 측정한 온도를 나타내는 N개의 정수가 빈칸을 사이에 두고 주어진다. 이 수들은 모두 -100 이상 100 이하이다.</p>
<p><strong>출력</strong></p>
<p>첫째 줄에는 입력되는 온도의 수열에서 연속적인 K일의 온도의 합이 최대가 되는 값을 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>시간 제한: 1초</li>
<li>메모리 제한: 128MB</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">10 2
3 -2 -4 -9 0 3 7 13 8 -3</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">21</code></pre>
<p><strong>예제 입력 2</strong></p>
<pre><code class="language-python">10 5
3 -2 -4 -9 0 3 7 13 8 -3</code></pre>
<p><strong>예제 출력 2</strong></p>
<pre><code class="language-python">31</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>슬라이딩 윈도우</li>
<li>누적합 (prefix sum) (보조 방식)</li>
<li>최댓값 계산</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><strong>시간복잡도</strong>: O(N)<ul>
<li>슬라이딩 윈도우로 처음에 K개 더한 뒤, 한 칸씩 이동하면서 O(1)씩 갱신</li>
</ul>
</li>
<li><strong>공간복잡도</strong>: O(N) (입력 저장 배열만 사용)</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>슬라이딩 윈도우</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>없음 (기본 함수로 충분)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>N, K, 온도 수열 입력받기</li>
<li>초기 구간 합 (sum(temp[0:K])) 계산</li>
<li>인덱스 i=K부터 N-1까지 슬라이딩 윈도우로 갱신<ul>
<li>current_sum = current_sum + temp[i] - temp[i-K]</li>
<li>max_sum = max(max_sum, current_sum)</li>
</ul>
</li>
<li>최댓값 출력</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python"># 입력
N, K = map(int, input().split())
temps = list(map(int, input().split()))

# 초기 합 (0번째 ~ K-1번째)
current_sum = sum(temps[:K])
max_sum = current_sum

# 슬라이딩 윈도우: 한 칸씩 옮기면서 최대합 찾기
for i in range(K, N):
    current_sum = current_sum + temps[i] - temps[i - K]
    max_sum = max(max_sum, current_sum)

# 출력
print(max_sum)</code></pre>
<h3 id="주의사항">주의사항</h3>
<ul>
<li>sum() 매번 쓰면 시간복잡도 O(N·K) → 시간 초과</li>
<li>인덱스 범위 초과 주의 (e.g., temps[i-K])</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 바구니 뒤집기]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%94%EA%B5%AC%EB%8B%88-%EB%92%A4%EC%A7%91%EA%B8%B0</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%94%EA%B5%AC%EB%8B%88-%EB%92%A4%EC%A7%91%EA%B8%B0</guid>
            <pubDate>Thu, 10 Jul 2025 00:38:46 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>도현이는 바구니를 총 N개 가지고 있고, 각각의 바구니에는 1번부터 N번까지 번호가 순서대로 적혀져 있다. 바구니는 일렬로 놓여져 있고, 가장 왼쪽 바구니를 1번째 바구니, 그 다음 바구니를 2번째 바구니, …, 가장 오른쪽 바구니를 N번째 바구니라고 부른다.</p>
<p>도현이는 앞으로 M번 바구니의 순서를 역순으로 만들려고 한다. 도현이는 한 번 순서를 역순으로 바꿀 때, 순서를 역순으로 만들 범위를 정하고, 그 범위에 들어있는 바구니의 순서를 역순으로 만든다.</p>
<p>바구니의 순서를 어떻게 바꿀지 주어졌을 때, M번 바구니의 순서를 역순으로 만든 다음, 바구니에 적혀있는 번호를 가장 왼쪽 바구니부터 출력하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 N(1 ≤ N ≤ 100)과 M(1 ≤ M ≤ 100)이 주어진다.</p>
<p>둘째 줄부터 M개의 줄에는 바구니의 순서를 역순으로 만드는 방법이 주어진다. 방법은 i j로 나타내고, 왼쪽으로부터 i번째 바구니부터 j번째 바구니의 순서를 역순으로 만든다는 뜻이다. (1 ≤ i ≤ j ≤ N)</p>
<p>도현이는 입력으로 주어진 순서대로 바구니의 순서를 바꾼다.</p>
<p><strong>출력</strong></p>
<p>모든 순서를 바꾼 다음에, 가장 왼쪽에 있는 바구니부터 바구니에 적혀있는 순서를 공백으로 구분해 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">5 4
1 2
3 4
1 4
2 2</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>리스트 인덱싱</li>
<li>슬라이싱 + reverse</li>
<li>시뮬레이션</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><strong>시간복잡도</strong><ul>
<li>각 연산 O(k), 최악 M=100, K=100 → O(M·K) = O(10,000) 수준 → 매우 작음</li>
</ul>
</li>
<li><strong>공간복잡도</strong>: O(N) → 바구니 상태 저장 리스트</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>리스트(list)</li>
<li>슬라이싱(list[i:j])</li>
<li>reversed() or [::-1]</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>없음 (기본 input(), 리스트 연산만 사용)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>바구니 초기 상태 = [1, 2, …, N] 생성</li>
<li>M개의 연산에 대해<ul>
<li>입력 i, j를 받아 → 리스트는 0-based 인덱스이므로 i-1 ~ j</li>
<li>baskets[i-1:j] = reversed(baskets[i-1:j])</li>
</ul>
</li>
<li>마지막 바구니 상태 출력</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python"># 입력
N, M = map(int, input().split())

# 초기 바구니 상태: [1, 2, ..., N]
baskets = list(range(1, N + 1))

# M번의 역순 명령 처리
for _ in range(M):
    i, j = map(int, input().split())
    baskets[i-1:j] = reversed(baskets[i-1:j])  # 슬라이싱은 [start:end+1]

# 출력
print(*baskets)</code></pre>
<h3 id="주의할-점">주의할 점</h3>
<ul>
<li>리스트 인덱스는 0-based, 입력은 1-based → 꼭 i-1, j로 처리</li>
<li>reversed()는 iterator → 다시 리스트에 할당 시 list(…) 또는 슬라이싱으로 덮어써야 함</li>
<li>print(*baskets)는 요소를 공백 구분해 출력</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : OX퀴즈]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-OX%ED%80%B4%EC%A6%88</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-OX%ED%80%B4%EC%A6%88</guid>
            <pubDate>Thu, 10 Jul 2025 00:20:25 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>&quot;OOXXOXXOOO&quot;와 같은 OX퀴즈의 결과가 있다. O는 문제를 맞은 것이고, X는 문제를 틀린 것이다. 문제를 맞은 경우 그 문제의 점수는 그 문제까지 연속된 O의 개수가 된다. 예를 들어, 10번 문제의 점수는 3이 된다.</p>
<p>&quot;OOXXOXXOOO&quot;의 점수는 1+2+0+0+1+0+0+1+2+3 = 10점이다.</p>
<p>OX퀴즈의 결과가 주어졌을 때, 점수를 구하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 테스트 케이스의 개수가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 길이가 0보다 크고 80보다 작은 문자열이 주어진다. 문자열은 O와 X만으로 이루어져 있다.</p>
<p><strong>출력</strong></p>
<p>각 테스트 케이스마다 점수를 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">5
OOXXOXXOOO
OOXXOOXXOO
OXOXOXOXOXOXOX
OOOOOOOOOO
OOOOXOOOOXOOOOX</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">10
9
7
55
30</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>문자열 처리</li>
<li>시뮬레이션</li>
<li>누적 합 (accumulation)</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><strong>시간복잡도: O(N)</strong> (문자열 길이 N ≤ 80, 최대 T=100이면 총 8000 → 매우 작음)</li>
<li><strong>공간복잡도</strong>: <strong>O(1)</strong> (문자열 하나씩 처리하며 누적)</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>for 루프 + 카운터 변수</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>불필요 (표준 input(), print() 만으로 충분)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>테스트 케이스 개수 T 입력</li>
<li>각 문자열 s에 대해<ul>
<li>score = 0, streak = 0</li>
<li>각 문자 c가 0이면 streak += 1, score += streak</li>
<li>c가 X면 streak = 0</li>
</ul>
</li>
<li>점수 출력</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">T = int(input())

for _ in range(T):
    s = input()
    score = 0
    streak = 0
    for c in s:
        if c == &#39;O&#39;:
            streak += 1
            score += streak
        else:
            streak = 0
    print(score)</code></pre>
<p><strong>빠른 입력 처리 버전 (sys.stdin, map() 사용)</strong></p>
<pre><code class="language-python">import sys

T = int(sys.stdin.readline())

for _ in range(T):
    line = sys.stdin.readline().strip()
    score = 0
    streak = 0
    for c in line:
        if c == &#39;O&#39;:
            streak += 1
            score += streak
        else:
            streak = 0
    print(score)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 평균]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8F%89%EA%B7%A0</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8F%89%EA%B7%A0</guid>
            <pubDate>Wed, 09 Jul 2025 23:29:20 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>세준이는 기말고사를 망쳤다. 세준이는 점수를 조작해서 집에 가져가기로 했다. 일단 세준이는 자기 점수 중에서 최댓값을 골랐다. 이 값을 M이라고 한다. 그리고 나서 모든 점수를 점수/M*100으로 고쳤다.</p>
<p>예를 들어, 세준이의 최고점이 70이고, 수학점수가 50이었으면 수학점수는 50/70*100이 되어 71.43점이 된다.</p>
<p>세준이의 성정을 위의 방법대로 새로 계산했을 때, 새로운 평균을 구하는 프로그램을 작성하시오.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 시험 본 과목의 개수 N이 주어진다. 이 값은 1000보다 작거나 같다. 둘째 줄에 세준이의 현재 성적이 주어진다. 이 값은 100보다 작거나 같은 음이 아닌 정수이고, 적어도 하나의 값은 0보다 크다.</p>
<p><strong>출력</strong></p>
<p>첫째 줄에 새로운 평균을 출력한다. 실제 정답과 출력값의 절대오차 또는 상대오차가 $10^{-2}$이하이면 정답이다.</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">3
40 80 60</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">75.0</code></pre>
<p><strong>예제 입력 2</strong></p>
<pre><code class="language-python">3
10 20 30</code></pre>
<p><strong>예제 출력 2</strong></p>
<pre><code class="language-python">66.666667</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>리스트 계산</li>
<li>정규화 (Normalization)</li>
<li>평균 계산 (평균 = 합계 / 개수)</li>
<li>실수 오차 처리 (출력 오차 제한 조건 포함)</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><p><strong>시간복잡도</strong></p>
<ul>
<li><p>max(scores) → O(N)</p>
</li>
<li><p>조정된 점수 리스트 생성 → O(N)</p>
</li>
<li><p>sum() → O(N)</p>
<p>총 O(N)</p>
</li>
</ul>
</li>
<li><p><strong>공간복잡도</strong>: O(N) → 점수 리스트 저장</p>
</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>max(), sum(), 리스트 컴프리헨션</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>기본 input(), map(), max(), sum() 사용</li>
<li>round() 또는 format() 함수로 소수점 제어 가능</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>점수 개수 N을 입력받음</li>
<li>점수 리스트 scores 입력받음</li>
<li>max_score = max(scores) 계산</li>
<li>new_scores = [(score / max_score) * 100 for score in scores]</li>
<li>평균 = sum(new_scores) / N</li>
<li>출력 (소수점 6자리 이상 보여주기)</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python"># 입력
N = int(input())
scores = list(map(int, input().split()))

# 최댓값
max_score = max(scores)

# 점수 조정
new_scores = [(score / max_score) * 100 for score in scores]

# 평균 계산
average = sum(new_scores) / N

# 출력
print(round(average, 6))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 최댓값]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%88%AB%EC%9E%90%EC%9D%98-%EA%B0%9C%EC%88%98</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%88%AB%EC%9E%90%EC%9D%98-%EA%B0%9C%EC%88%98</guid>
            <pubDate>Wed, 09 Jul 2025 23:10:05 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>9개의 서로 다른 자연수가 주어질 때, 이들 중 최댓값을 찾고 그 최댓값이 몇 번째 수인지를 구하는 프로그램을 작성하시오.</p>
<p>예를 들어, 서로 다른 9개의 자연수</p>
<p>3, 29, 38, 12, 57, 74, 40, 85, 61</p>
<p>이 주어지면, 이들 중 최댓값은 85이고, 이 값은 8번째 수이다.</p>
<p><strong>입력</strong></p>
<p>첫째 줄부터 아홉 번째 줄까지 한 줄에 하나의 자연수가 주어진다. 주어지는 자연수는 100보다 작다.</p>
<p><strong>출력</strong></p>
<p>첫째 줄에 최댓값을 출력하고, 둘째 줄에 최댓값이 몇 번째 수인지를 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">3
29
38
12
57
74
40
85
61</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">85
8</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>배열/리스트 처리</li>
<li>순차 탐색 (시퀀스 순회)</li>
<li>최댓값 및 인덱스 추출</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><strong>시간복잡도</strong>: O(9) → 입력된 9개 숫자만 순회</li>
<li><strong>공간복잡도</strong>: O(9) → 리스트에 저장</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>리스트(list)</li>
<li>max() 함수, index() 함수</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>없음 (input(), max(), index()만 사용)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li>입력이 무작위로 주어져도 최댓값을 찾는 데는 한 번의 순회면 충분함</li>
</ul>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>9개의 수를 리스트에 저장</li>
<li>max()로 최댓값 찾기</li>
<li>list.index(max_value) + 1로 위치 찾기 (1-based index)</li>
<li>출력</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python"># 입력 받기
numbers = [int(input()) for _ in range(9)]

# 최댓값 찾기
max_value = max(numbers)

# 위치 찾기 (1부터 시작)
position = numbers.index(max_value) + 1

# 출력
print(max_value)
print(position)</code></pre>
<p><strong>enumerate 사용</strong></p>
<pre><code class="language-python"># 입력 받기
numbers = [int(input()) for _ in range(9)]

# 초기값 설정
max_value = -1
max_index = -1

# enumerate로 인덱스와 값 동시에 순회
for idx, num in enumerate(numbers):
    if num &gt; max_value:
        max_value = num
        max_index = idx  # 0-based index

# 출력 (문제는 1-based index 요구)
print(max_value)
print(max_index + 1)</code></pre>
<h3 id="조건-검증">조건 검증</h3>
<ul>
<li>“서로 다른” 자연수라는 조건이 있지만, 로직상 크게 영향을 미치지 않음 (단, 동일한 최대값이 여러 개일 경우엔 index()가 가장 먼저 나오는 인덱스 반환)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 숫자의 개수]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-2557%EB%B2%88-%EC%88%AB%EC%9E%90%EC%9D%98-%EA%B0%9C%EC%88%98</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-2557%EB%B2%88-%EC%88%AB%EC%9E%90%EC%9D%98-%EA%B0%9C%EC%88%98</guid>
            <pubDate>Wed, 09 Jul 2025 14:07:06 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>세 개의 자연수 A, B, C가 주어질 때 A x B x C를 계산한 결과에 0부터 9까지 각각의 숫자가 몇 번씩 쓰였는지를 구하는 프로그램을 작성하시오.</p>
<p>예를 들어 A = 150, B = 266, C = 427이라면 A x B x C = 150 x 266 x 427 = 17037300이 되고, 계산한 결과 17037300에는 0이 3번, 1이 1번, 3이 2번, 7이 2번 쓰였다.</p>
<p><strong>입력</strong></p>
<p>첫째 줄에 A, 둘째 줄에 B, 셋째 줄에 C가 주어진다. A, B, C는 모두 100보다 크거나 같고, 1,000보다 작은 자연수이다.</p>
<p><strong>출력</strong></p>
<p>첫째줄에는 A x B x C의 결과에 0이 몇 번 쓰였는지 출력한다. 마찬가지로 둘째 줄부터 열 번째 줄까지 A x B x C의 결과에 1부터 9까지의 숫자가 각각 몇 번 쓰였는지 차례로 한 줄에 하나씩 출력한다.</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code class="language-python">150
266
427</code></pre>
<p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">3
1
0
2
0
0
0
2
0
0</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>문자열 처리</li>
<li>시뮬레이션</li>
<li>빈도수 세기 (카운팅)</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><strong>시간복잡도</strong><ul>
<li>곱셈은 O(1), 문자열 변환은 O(N) (N은 숫자 자릿수, 최대 10자리 이하)</li>
<li>전체적으로 O(N)</li>
</ul>
</li>
<li><strong>공간복잡도</strong><ul>
<li>O(10) → 0~9까지 개수를 저장하는 리스트</li>
</ul>
</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>문자열 반환(str())</li>
<li>리스트 인덱싱</li>
<li>count() 함수 or for문으로 직접 카운팅</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>표준 입력 함수 (input()), 별도 외부 라이브러리 불필요</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li>A = B = C = 999 → 결과는 약 9자리 숫자</li>
<li>충분히 문자열로 변환해 처리 가능</li>
</ul>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>A, B, C 입력 받기</li>
<li>A × B × C 계산</li>
<li>결과를 문자열로 변환</li>
<li>0부터 9까지 순회하면서 .count(str(i)) 또는 직접 카운팅</li>
<li>각 숫자별 등장 횟수 출력</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python"># 입력
A = int(input())
B = int(input())
C = int(input())

# 곱셈 결과
result = A * B * C

# 문자열로 변환
result_str = str(result)

# 0~9 숫자 카운트
for i in range(10):
    print(result_str.count(str(i)))</code></pre>
<h3 id="추가-팁">추가 팁</h3>
<ul>
<li>collections.Counter로도 가능하지만 이 문제에서는 단순한 리스트와 문자열 메서드로 충분함</li>
<li>출력 순서에 주의 (0부터 9까지 순서대로)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 제로 (Stack)]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%9C%EB%A1%9C-%EB%B0%B1%EC%A4%80-10773</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%9C%EB%A1%9C-%EB%B0%B1%EC%A4%80-10773</guid>
            <pubDate>Sun, 06 Jul 2025 05:17:29 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>나코더 기장 재민이는 동아리 회식을 준비하기 위해서 장부를 관리하는 중이다.</p>
<p>재현이는 재민이를 도와서 돈을 관리하는 중인데, 애석하게도 항상 정신없는 재현이는 돈을 실수로 잘못 부르는 사고를 치기 일쑤였다.</p>
<p>재현이는 잘못된 수를 부를 때마다 0을 외쳐서, 가장 최근에 재민이가 쓴 수를 지우게 시킨다.</p>
<p>재민이는 이렇게 모든 수를 받아 적은 후 그 수의 합을 알고 싶어 한다. 재민이를 도와주자!</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>입력</strong></p>
<p>첫 번째 줄에 정수 K가 주어진다. (1 ≤ K ≤ 100,000)</p>
<p>이후 K개의 줄에 정수가 1개씩 주어진다. 정수는 0에서 1,000,000 사이의 값을 가지며, 정수가 “0”일 경우에는 가장 최근에 쓴 수를 지우고, 아닐 경우 해당 수를 쓴다.</p>
<p>정수가 “0”일 경우에 지울 수 있는 수가 있음을 보장할 수 있다.</p>
<p><strong>출력</strong></p>
<p>재민이가 최종적으로 적어 낸 수의 합을 출력한다. 최종적으로 적어낸 수의 합은 $2^{31}-1$보다 작거나 같은 정수이다.</p>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>자료구조</li>
<li>스택 (stack)</li>
<li>시뮬레이션</li>
</ul>
<h3 id="시간-복잡도--공간-복잡도-추정">시간 복잡도 + 공간 복잡도 추정</h3>
<ul>
<li>시간 복잡도: O(K)<ul>
<li>각 입력마다 push 또는 pop 연산을 한 번씩만 수행</li>
</ul>
</li>
<li>공간 복잡도: O(K)<ul>
<li>최악의 경우 모든 숫자를 저장해야 하므로 K개 저장</li>
</ul>
</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>스택 사용<ul>
<li>0이 나오면 가장 최근 값을 지워야 하므로 <strong>후입선출 구조(LIFO)인 스택이 적절하다</strong></li>
</ul>
</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>별도의 외부 라이브러리 불필요</li>
<li>list를 스택처럼 사용 (append, pop)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li>K = 100,000, 모든 수가 1이거나 1,000,000이라면 총합 최대는 1,000,000 * 100,000 = 100,000,000,000이지만 문제 조건에서는 결과 $2^{31} - 1$ 이하로 제한되어 안전한 입력만 주어짐</li>
</ul>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>입력 받은 숫자를 순서대로 처리</li>
<li>0이면 스택에서 pop</li>
<li>0이 아니면 스택에 push</li>
<li>반복 종료 후 스택의 모든 숫자 합산</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">K = int(input())
stack = []

for _ in range(K):
    num = int(input())
    if num == 0:
        stack.pop()
    else:
        stack.append(num)

print(sum(stack))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 괄호 (Stack)]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 03 Jul 2025 09:18:12 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>괄호 문자열(Parenthesis String, PS)은 두 개의 괄호 기호인 ‘(’ 와 ‘)’ 만으로 구성되어 있는 문자열이다. </p>
<p>그 중에서 괄호의 모양이 바르게 구성된 문자열을 올바른 괄호 문자열(Valid PS, VPS)이라고 부른다. 한 쌍의 괄호 기호로 된 “( )” 문자열은 기본 VPS 이라고 부른다. </p>
<p>만일 x 가 VPS 라면 이것을 하나의 괄호에 넣은 새로운 문자열 “(x)”도 VPS 가 된다. 그리고 두 VPS x 와 y를 접합(concatenation)시킨 새로운 문자열 xy도 VPS 가 된다. 예를 들어 “(())()”와 “((()))” 는 VPS 이지만 “(()(”, “(())()))” , 그리고 “(()” 는 모두 VPS 가 아닌 문자열이다.</p>
<p>여러분은 입력으로 주어진 괄호 문자열이 VPS 인지 아닌지를 판단해서 그 결과를 YES 와 NO 로 나타내어야 한다.</p>
<h3 id="제한사항">제한사항</h3>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>입력</strong></p>
<p>입력 데이터는 표준 입력을 사용한다. 입력은 T개의 테스트 데이터로 주어진다. 입력의 첫 번째 줄에는 입력 데이터의 수를 나타내는 정수 T가 주어진다. 각 테스트 데이터의 첫째 줄에는 괄호 문자열이 한 줄에 주어진다. 하나의 괄호 문자열의 길이는 2 이상 50 이하이다. </p>
<pre><code class="language-python">6
(())())
(((()())()
(()())((()))
((()()(()))(((())))()
()()()()(()()())()
(()((())()(</code></pre>
<p><strong>출력</strong></p>
<p>출력은 표준 출력을 사용한다. 만일 입력 괄호 문자열이 올바른 괄호 문자열(VPS)이면 “YES”, 아니면 “NO”를 한 줄에 하나씩 차례대로 출력해야 한다. </p>
<pre><code class="language-python">NO
NO
YES
NO
YES
NO</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li><strong>자료구조</strong>: 스택(Stack)</li>
<li><strong>문자열 처리</strong></li>
<li><strong>시뮬레이션 / 괄호 검증</strong></li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><strong>시간 복잡도</strong> : O(N) (N = 괄호 문자열 길이, 최대 50)</li>
<li><strong>공간 복잡도</strong> : O(N) (스택 사용 시 최대 길이만큼 쌓일 수 있음)</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li><strong>스택</strong> : 여는 괄호 (가 나오면 push, 닫는 괄호 )가 나오면 pop</li>
<li><strong>대안</strong> : 스택 없이 count(+)를 쓰는 방식도 가능 ((이면 +1, )이면 -1)</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<ul>
<li>별도 라이브러리 필요 없음 (기본 list로 스택 구현 가능)</li>
</ul>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li>모든 문자가 닫는 괄호 )로 시작하거나 여는 괄호로 끝날 경우, 스택이 계속 쌓이거나 비어있지 않음</li>
<li>스택이 <strong>중간에 비었는데 )가 나오면 → 잘못된 VPS</strong></li>
<li><strong>마지막에 스택이 남아있으면 → 잘못된 VPS</strong></li>
<li><code>() ) () () ( ()</code></li>
</ul>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li>문자열을 왼쪽부터 한 글자씩 순화</li>
<li>여는 괄호 (이면 스택에 push</li>
<li><strong>닫는 괄호 )이면</strong> <ul>
<li><strong>스택이 비었으면 VPS 아님 → NO</strong></li>
<li>아니면 스택에서 pop</li>
</ul>
</li>
<li>순회가 끝난 후<ul>
<li>스택이 비어 있으면 VPS → YES</li>
<li>비어 있지 않으면 VPS 아님 → NO</li>
</ul>
</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">T = int(input())
for _ in range(T):
    s = input()
    stack = []
    is_vps = True

    for char in s:
        if char == &#39;(&#39;:
            stack.append(char)
        else:  # char == &#39;)&#39;
            if not stack:
                is_vps = False
                break
            stack.pop()

    if stack:
        is_vps = False

    print(&quot;YES&quot; if is_vps else &quot;NO&quot;)</code></pre>
<p><strong>대안 코드 (카운터 방식, 스택 없이)</strong></p>
<pre><code class="language-python">T = int(input())
for _ in range(T):
    s = input()
    count = 0
    is_vps = True

    for char in s:
        if char == &#39;(&#39;:
            count += 1
        else:
            count -= 1
        if count &lt; 0:
            is_vps = False
            break

    if count != 0:
        is_vps = False

    print(&quot;YES&quot; if is_vps else &quot;NO&quot;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 동전 1]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8F%99%EC%A0%84-1</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8F%99%EC%A0%84-1</guid>
            <pubDate>Tue, 01 Jul 2025 16:16:32 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><strong>동전 1</strong></p>
<p>n가지 종류의 동전이 있습니다. 각각의 동전이 나타내는 가치는 다르며, 이 동전들을 적당히 사용해서 그 가치의 합이 k원이 되도록 하는 경우의 수를 구하는 프로그램을 작성하세요.</p>
<p>각각의 동전은 몇 개라도 사용할 수 있으며, 사용한 동전의 구성이 같은데 순서만 다른 것은 같은 경우로 취급합니다.</p>
<p><strong>입력 형식</strong></p>
<ol>
<li>첫째 줄에 두 개의 정수 n과 k가 주어집니다.<ul>
<li>n: 동전의 종류 개수 (1 ≤ n ≤ 100)</li>
<li>k: 만들어야 하는 금액 (1 ≤ k ≤ 10,000)</li>
</ul>
</li>
<li>다음 n개의 줄에는 각각의 동전의 가치가 주어집니다.<ul>
<li>각 동전의 가치는 100,000보다 작거나 같은 자연수입니다.</li>
</ul>
</li>
</ol>
<p><strong>출력 형식</strong></p>
<ol>
<li>첫째 줄에 경우의 수를 출력합니다.<ul>
<li>경우의 수는 2^31보다 작습니다.</li>
</ul>
</li>
</ol>
<h3 id="입출력-예">입출력 예</h3>
<p><strong>예제 입력 1</strong></p>
<pre><code>3 10
1
2
5</code></pre><p><strong>예제 출력 1</strong></p>
<pre><code class="language-python">10</code></pre>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<ul>
<li>전형적인 동전 조합의 수를 구하는 <strong>동적 계획법(DP)</strong> 문제</li>
<li>중복 순서는 허용하지 않으므로 <strong>조합(combination)</strong>을 구하는 문제</li>
<li>각 동전은 무한히 사용할 수 있으므로 <strong>완전 탐색</strong>보다는 <strong>DP로 누적</strong>해서 처리</li>
</ul>
<h3 id="시간-복잡도--공간복잡도-추정">시간 복잡도 + 공간복잡도 추정</h3>
<ul>
<li><p><strong>시간복잡도</strong>: $O(n * k)$</p>
<p>  (각 동전에 대해 1원부터 k원까지 반복)</p>
</li>
<li><p><strong>공간복잡도</strong>: $O(k)$</p>
<p>  (1차원 dp 배열 사용)</p>
</li>
</ul>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<ul>
<li>DP (Dynamic Programming)</li>
<li>dp[i]: i원을 만드는 경우의 수</li>
</ul>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<h3 id="접근-방법">접근 방법</h3>
<ol>
<li><p>dp[0] = 1로 초기화</p>
<ul>
<li>0원을 만드는 경우는 “동전을 하나도 사용하지 않는 방법” 1가지</li>
</ul>
</li>
<li><p>각 동전에 대해</p>
<pre><code class="language-python"> for coin in coins:
     for i in range(coin, k + 1):
         dp[i] += dp[i - coin]</code></pre>
<ul>
<li><p>현재 금액 i원을 만들기 위해 coin을 추가했을 때, i - coin원을 만들 수 있는 경우의 수를 더함.</p>
</li>
<li><p>dp[i] += dp[i - coin]</p>
<p>  → i - coin원을 만들 수 있다면 거기에 coin을 하나 더해서 i원을 만들 수 있다는 의미</p>
</li>
<li><p>동전 종류를 바깥 루프로 돌면 순서가 다른 구성은 중복으로 세지 않게 됩니다.</p>
</li>
</ul>
</li>
</ol>
<h3 id="최종-코드">최종 코드</h3>
<p><strong>Ver 1</strong></p>
<pre><code class="language-python">n, k = map(int, input().split())
coins = [int(input()) for _ in range(n)]

dp = [0] * (k + 1)
dp[0] = 1  # 0원을 만드는 경우는 1가지

for coin in coins:
    for i in range(coin, k + 1):
        dp[i] += dp[i - coin]

print(dp[k])</code></pre>
<p><strong>Ver 2</strong></p>
<pre><code class="language-python">def count_coin_cases(n, k, coins):
    dp = [0] * (k + 1)
    dp[0] = 1  # 0원을 만드는 경우는 1가지 (아무 동전도 사용하지 않음)

    for coin in coins:
        for j in range(coin, k + 1):
            dp[j] += dp[j - coin]  # 현재 동전을 사용하는 경우의 수 추가

    return dp[k]  # k원을 만드는 모든 경우의 수 반환

# 입력 처리
n, k = map(int, input().split())
coins = [int(input()) for _ in range(n)]

print(count_coin_cases(n, k, coins))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 가장 많이 받은 선물]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9E%A5-%EB%A7%8E%EC%9D%B4-%EB%B0%9B%EC%9D%80-%EC%84%A0%EB%AC%BC</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9E%A5-%EB%A7%8E%EC%9D%B4-%EB%B0%9B%EC%9D%80-%EC%84%A0%EB%AC%BC</guid>
            <pubDate>Mon, 30 Jun 2025 21:57:03 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>선물을 직접 전하기 힘들 떄 카카오톡 선물하기 기능을 이용해 축하 선물을 보낼 수 있습니다. 당신의 친구들이 이번 달까지 선물을 주고받은 기록을 바탕으로 다음 달에 누가 선물을 많이 받을지 예측하려고 합니다.</p>
<ul>
<li><p>두 사람이 선물을 주고받은 기록이 있다면, 이번 달까지 두 사람 사이에 더 많은 선물을 준 사람이 다음 달에 선물을 하나 받습니다.</p>
<ul>
<li>예를 들어 <code>A</code>가 <code>B</code>에게 선물을 5번 줬고, <code>B</code>가 <code>A</code>에게 선물을 3번 줬다면 다음 달엔 <code>A</code>가 <code>B</code>에게 선물을 하나 받습니다.</li>
</ul>
</li>
<li><p>두 사람이 선물을 주고받은 기록이 하나도 없거나 주고받은 수가 같다면, 선물 지수가 더 큰 사람이 선물 지수가 더 작은 사람에게 선물을 하나 받습니다.</p>
<ul>
<li>선물 지수는 이번 달까지 자신이 친구들에게 준 선물의 수에서 받은 선물의 수를 뺀 값입니다.</li>
<li>예를 들어 <code>A</code>가 친구들에게 준 선물이 3개고 받은 선물이 10개라면 <code>A</code>의 선물 지수는 -7입니다. <code>B</code>가 친구들에게 준 선물이 3개고 받은 선물이 2개라면 <code>B</code>의 선물 지수는 1입니다. 만약 <code>A</code>와 <code>B</code>가 선물을 주고받은 적이 없거나 정확히 같은 수로 선물을 주고받았다면, 다음 달엔 <code>B</code>가 <code>A</code>에게 선물을 하나 받습니다.</li>
<li>만약 두 사람의 선물 지수도 같다면 다음 달에 선물을 주고받지 않습니다.</li>
</ul>
</li>
</ul>
<p>위에서 설명한 규칙대로 다음 달에 선물을 주고받을 때, 당신은 선물을 가장 많이 받을 친구가 받을 선물의 수를 알고 싶습니다.</p>
<p>친구들의 이름을 담은 1차원 문자열 배열 <code>friends</code> 이번 달까지 친구들이 주고받은 선물 기록을 담은 1차원 문자열 배열 <code>gifts</code>가 매개변수로 주어집니다. 이때, 다음달에 가장 많은 선물을 받는 친구가 받을 선물의 수를 return 하도록 soluttion 함수를 완성해 주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>2 &lt;= <code>friends</code>의 길이 = 친구들의 수 &lt;= 50<ul>
<li><code>friends</code>의 원소는 친구의 이름을 의미하는 알파벳 소문자로 이루어진 길이가 10 이하인 문자열입니다.</li>
<li>이름이 같은 친구는 없습니다.</li>
</ul>
</li>
<li>1 &lt;= <code>gifts</code>의 길이 &lt;= 10,000<ul>
<li><code>gifts</code>의 원소는 <code>A B</code> 형태의 문자열입니다. <code>A</code>는 선물을 준 친구의 이름을 <code>B</code>는 선물을 받은 친구의 이름을 의미하며 공백 하나로 구분됩니다.</li>
<li><code>A</code>와 <code>B</code>는 <code>friends</code>의 원소이며 <code>A</code>와 <code>B</code>가 같은 이름인 경우는 존재하지 않습니다.</li>
</ul>
</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>friends</th>
<th>gifts</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[&quot;muzi&quot;, &quot;ryan&quot;, &quot;frodo&quot;, &quot;neo&quot;]</td>
<td>[&quot;muzi frodo&quot;, &quot;muzi frodo&quot;, &quot;ryan muzi&quot;, &quot;ryan muzi&quot;, &quot;ryan muzi&quot;, &quot;frodo muzi&quot;, &quot;frodo ryan&quot;, &quot;neo muzi&quot;]</td>
<td>2</td>
</tr>
<tr>
<td>[&quot;joy&quot;, &quot;brad&quot;, &quot;alessandro&quot;, &quot;conan&quot;, &quot;david&quot;]</td>
<td>[&quot;alessandro brad&quot;, &quot;alessandro joy&quot;, &quot;alessandro conan&quot;, &quot;david alessandro&quot;, &quot;alessandro david&quot;]</td>
<td>4</td>
</tr>
<tr>
<td>[&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</td>
<td>[&quot;a b&quot;, &quot;b a&quot;, &quot;c a&quot;, &quot;a c&quot;, &quot;a c&quot;, &quot;c a&quot;]</td>
<td>0</td>
</tr>
</tbody></table>
<p><strong>입출력 예 # 1</strong></p>
<p>주고받은 선물과 선물 지수를 표로 나타내면 다음과 같습니다.</p>
<table>
<thead>
<tr>
<th>↓준 사람 \ 받은 사람→</th>
<th>muzi</th>
<th>ryan</th>
<th>frodo</th>
<th>neo</th>
</tr>
</thead>
<tbody><tr>
<td><strong>muzi</strong></td>
<td>-</td>
<td>0</td>
<td>2</td>
<td>0</td>
</tr>
<tr>
<td><strong>ryan</strong></td>
<td>3</td>
<td>-</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td><strong>frodo</strong></td>
<td>1</td>
<td>1</td>
<td>-</td>
<td>0</td>
</tr>
<tr>
<td><strong>neo</strong></td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>-</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>이름</th>
<th>준 선물</th>
<th>받은 선물</th>
<th>선물 지수</th>
</tr>
</thead>
<tbody><tr>
<td>muzi</td>
<td>2</td>
<td>5</td>
<td>-3</td>
</tr>
<tr>
<td>ryan</td>
<td>3</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>frodo</td>
<td>2</td>
<td>2</td>
<td>0</td>
</tr>
<tr>
<td>neo</td>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
</tbody></table>
<p><code>muzi</code>는 선물을 더 많이 줬던 <code>frodo</code>에게서 선물을 하나 받습니다.
<code>ryan</code>은 선물을 더 많이 줬던 <code>muzi</code>에게서 선물을 하나 받고, 선물을 주고받지 않았던 <code>neo</code>보다 선물 지수가 커 선물을 하나 받습니다.
<code>frodo</code>는 선물을 더 많이 줬던 <code>ryan</code>에게 선물을 하나 받습니다.
<code>neo</code>는 선물을 더 많이 줬던 <code>muzi</code>에게서 선물을 하나 받고, 선물을 주고받지 않았던 <code>frodo</code>보다 선물 지수가 커 선물을 하나 받습니다.</p>
<p>다음달에 가장 선물을 많이 받는 사람은 <code>ryan</code>과 <code>neo</code>이고 2개의 선물을 받습니다. 따라서 2를 return 해야 합니다.</p>
<p><strong>입출력 예 # 2</strong></p>
<p>주고받은 선물과 선물 지수를 표로 나타내면 다음과 같습니다.</p>
<table>
<thead>
<tr>
<th>↓준 사람 \ 받은 사람→</th>
<th>joy</th>
<th>brad</th>
<th>alessandro</th>
<th>conan</th>
<th>david</th>
</tr>
</thead>
<tbody><tr>
<td><strong>joy</strong></td>
<td>-</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td><strong>brad</strong></td>
<td>0</td>
<td>-</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td><strong>alessandro</strong></td>
<td>1</td>
<td>1</td>
<td>-</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td><strong>conan</strong></td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-</td>
<td>0</td>
</tr>
<tr>
<td><strong>david</strong></td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>-</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>이름</th>
<th>준 선물</th>
<th>받은 선물</th>
<th>선물 지수</th>
</tr>
</thead>
<tbody><tr>
<td>joy</td>
<td>0</td>
<td>1</td>
<td>-1</td>
</tr>
<tr>
<td>brad</td>
<td>0</td>
<td>1</td>
<td>-1</td>
</tr>
<tr>
<td>alessandro</td>
<td>4</td>
<td>1</td>
<td>3</td>
</tr>
<tr>
<td>conan</td>
<td>0</td>
<td>1</td>
<td>-1</td>
</tr>
<tr>
<td>david</td>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody></table>
<p><code>alessandro</code>가 선물을 더 많이 줬던 <code>joy</code>, <code>brad</code>, <code>conan</code>에게서 선물을 3개 받습니다. 선물을 하나씩 주고받은 <code>david</code>보다 선물 지수가 커 선물을 하나 받습니다.
<code>david</code>는 선물을 주고받지 않았던 <code>joy</code>, <code>brad</code>, <code>conan</code>보다 선물 지수가 커 다음 달에 선물을 3개 받습니다.
<code>joy</code>, <code>brad</code>, <code>conan</code>은 선물을 받지 못합니다.</p>
<p>다음달에 가장 선물을 많이 받는 사람은 <code>alessandro</code>이고 4개의 선물을 받습니다. 따라서 4를 return 해야 합니다.</p>
<p>입출력 예 #3</p>
<p><code>a</code>와 <code>b</code>, <code>a</code>와 <code>c</code>, <code>b</code>와 <code>c</code> 사이에 서로 선물을 주고받은 수도 같고 세 사람의 선물 지수도 0으로 같아 다음 달엔 아무도 선물을 받지 못합니다. 따라서 0을 return 해야 합니다.</p>
<h3 id="문제-유형-분류">문제 유형 분류</h3>
<p><strong>시뮬레이션</strong> 문제
규칙에 따라 상태를 계산하고 그 결과를 판단하는 <strong>조건 기반 시뮬레이션 유형</strong></p>
<h3 id="시간복잡도-생각">시간복잡도 생각</h3>
<p><code>friends</code>: 최대 50명
<code>gifts</code>: 최대 10,000건</p>
<p>모든 친구 쌍을 비교해도 50 x 50 = 2500회 → 충분히 가능</p>
<p><strong>$O(N^2)$ 시뮬레이션 구조로 구현해도 무방함</strong></p>
<h3 id="적합한-알고리즘--자료구조">적합한 알고리즘 / 자료구조</h3>
<p><strong>[1] 필요한 구조</strong></p>
<ul>
<li>이름 → 인덱스 매핑: 친구 이름을 리스트 index로 매핑 (dict)</li>
<li>선물 주고받은 기록: <strong>2차원 배열 (gifts[i][j] = i가 j에게 준 수)</strong></li>
<li>선물 지수 계산: 주고받은 총합 저장 (준 선물 수, 받은 선물 수)</li>
<li>다음달 받을 선물 개수 계산: <strong>2중 루프</strong></li>
</ul>
<p><strong>[2] 핵심 자료구조</strong></p>
<ul>
<li>gift_table[i][j]: i가 j에게 준 선물 수</li>
<li>gift_score[i]: 선물 지수 = 준 선물 - 받은 선물</li>
<li><strong>next_received[i]</strong>: 다음 달 받을 선물 수</li>
<li><strong>경계값 / 예외 케이스 고민</strong><ul>
<li><strong>선물 주고받은 적 없는 친구 쌍</strong></li>
<li><strong>선물 횟수가 같음</strong></li>
<li><strong>선물 지수가 같음</strong></li>
<li><strong>받은 선물 수가 0일 수도 있음</strong></li>
<li><strong>선물 지수가 음수일 수도 있음</strong>
→ 조건문에 이런 케이스를 반드시 명시해줘야 함</li>
</ul>
</li>
</ul>
<h3 id="시간복잡도--공간복잡도-추정">시간복잡도 + 공간복잡도 추정</h3>
<table>
<thead>
<tr>
<th><strong>항목</strong></th>
<th><strong>추정</strong></th>
</tr>
</thead>
<tbody><tr>
<td>시간복잡도</td>
<td>O(N² + M) = 50² + 10,000 → 실질적으로 무난</td>
</tr>
<tr>
<td><strong>공간복잡도</strong></td>
<td>O(N² + N) = 2차원 테이블 + 점수 리스트</td>
</tr>
</tbody></table>
<h3 id="필요한-라이브러리">필요한 라이브러리</h3>
<p>기본 내장 라이브러리로 충분 (딕셔너리, 리스트만 사용)</p>
<h3 id="최악의-경우-시뮬레이션">최악의 경우 시뮬레이션</h3>
<ul>
<li><strong>50명의 친구가 모두 서로 선물을 주고받는 경우</strong></li>
<li><strong>gift 배열이 10,000개인 경우</strong></li>
<li><strong>선물 지수/선물 기록이 모두 동일한 경우</strong></li>
</ul>
<p>→ 이 모든 경우에서도 로직이 잘 작동해야 함</p>
<h3 id="정리된-접근-방법">정리된 접근 방법</h3>
<ul>
<li>이름 → 인덱스 매핑</li>
</ul>
<pre><code class="language-python">name_to_idx = {name: i for i, name in enumerate(friends)}</code></pre>
<ul>
<li><strong>선물 주고받기 기록 저장 (2차원 배열)</strong></li>
</ul>
<pre><code class="language-python">gift_table = [[0]*n for _ in range(n)]</code></pre>
<pre><code class="language-python"># 부연 설명
gift_table = [
   [0, 0, 0, 0],  # 0번째 친구가 다른 친구들에게 준 선물 수
   [0, 0, 0, 0],  # 1번째 친구가 ...
   [0, 0, 0, 0],
   [0, 0, 0, 0]
]</code></pre>
<ul>
<li>선물 지수 계산 (준 개수 - 받은 개수)</li>
</ul>
<pre><code class="language-python">send = [0]*n
receive = [0]*n</code></pre>
<ul>
<li>모든 친구 쌍을 비교해서 다음 달 받을 선물 계산</li>
</ul>
<pre><code class="language-python">for i in range(n):
    for j in range(n):
        if i == j: continue
            ...</code></pre>
<ul>
<li>최종적으로 next_receive[i] 중 max 값 반환</li>
</ul>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-python">def solution(friends, gifts):
    n = len(friends)
    name_to_idx = {name: i for i, name in enumerate(friends)}

    # 선물 기록용 2차원 배열: gift_table[i][j] = i가 j에게 준 선물 수
    gift_table = [[0]*n for _ in range(n)]

    # 준 선물 수, 받은 선물 수
    send = [0]*n
    receive = [0]*n

    # 1. 선물 주고받기 기록 저장
    for g in gifts:
        giver, receiver = g.split()
        i = name_to_idx[giver]
        j = name_to_idx[receiver]

        gift_table[i][j] += 1
        send[i] += 1
        receive[j] += 1

    # 2. 선물 지수 계산
    gift_score = [send[i] - receive[i] for i in range(n)]

    # 3. 다음 달 받을 선물 수 계산
    next_received = [0]*n

    for i in range(n):
        for j in range(n):
            if i == j:
                continue
            # 선물을 더 많이 준 사람이 있으면
            if gift_table[i][j] &gt; gift_table[j][i]:
                next_received[i] += 1
            # 주고받은 횟수가 같을 경우 → 선물 지수 비교
            elif gift_table[i][j] == gift_table[j][i]:
                if gift_score[i] &gt; gift_score[j]:
                    next_received[i] += 1

    return max(next_received)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 옹알이 (1)]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%98%B9%EC%95%8C%EC%9D%B4-1</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%98%B9%EC%95%8C%EC%9D%B4-1</guid>
            <pubDate>Thu, 26 Jun 2025 02:28:21 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>머쓱이는 태어난 지 6개월 된 조카를 돌보고 있습니다. 조카는 아직 &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot; 네 가지 발음을 최대 한 번씩 사용해 조합한(이어붙인) 발음밖에 하지 못합니다. 문자열 배열 <code>babbling</code>이 매개변수로 주어질 때, 머쓱이의 조카가 발음할 수 있는 단어의 개수를 return하도록 solution 함수를 완성해 주세요</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>1 &lt;= <code>babbling</code>의 길이 &lt;= 100</li>
<li>1 &lt;= <code>babbling[i]</code>의 길이 &lt;= 15</li>
<li><code>babbling</code>의 각 문자열에서 &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;가 한 번씩만 등장합니다.<ul>
<li>즉, 각 문자열의 가능한 모든 부분 문자열 중에서 &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;가 한 번씩만 등장합니다.</li>
</ul>
</li>
<li>문자열은 알파벳 소문자로만 이루어져 있습니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>babbling</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[&quot;aya&quot;, &quot;yee&quot;, &quot;u&quot;, &quot;maa&quot;, &quot;wyeoo&quot;]</td>
<td>1</td>
</tr>
<tr>
<td>[&quot;ayaye&quot;, &quot;uuuma&quot;, &quot;ye&quot;, &quot;yemawoo&quot;, &quot;ayaa&quot;]</td>
<td>3</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예 # 1</p>
<ul>
<li>[&quot;aya&quot;, &quot;yee&quot;, &quot;u&quot;, &quot;maa&quot;, &quot;wyeoo&quot;]에서 발음할 수 있는 것은 &quot;aya&quot;뿐입니다. 따라서 1을 return합니다.</li>
</ul>
<p>입출력 예 #2</p>
<ul>
<li>[&quot;ayaye&quot;, &quot;uuuma&quot;, &quot;ye&quot;, &quot;yemawoo&quot;, &quot;ayaa&quot;]에서 발음할 수 있는 것은 &quot;aya&quot; + &quot;ye&quot; = &quot;ayaye&quot;, &quot;ye&quot;, &quot;ye&quot; + &quot;ma&quot; + &quot;woo&quot; = &quot;yemawoo&quot;로 3개입니다. 따라서 3을 return합니다.</li>
</ul>
<h3 id="유의사항">유의사항</h3>
<ul>
<li>네 가지를 붙여 만들 수 있는 발음 이외에는 어떤 발음도 할 수 없는 것으로 규정합니다. 예를 들어 &quot;woowo&quot;는 &quot;woo&quot;는 발음할 수 있지만 &quot;wo&quot;를 발음할 수 없기 때문에 할 수 없는 발음입니다.</li>
</ul>
<h3 id="문제-특징">문제 특징</h3>
<ul>
<li>각각 한 번씩만 사용 가능하므로 네 단어들로만 이루어져있으며 중복 없이 구성되어야 함</li>
</ul>
<h3 id="사고-흐름">사고 흐름</h3>
<ul>
<li>문자열 안에 제한된 단어만 허용 : 부분 문자열 검사 (in) or replace</li>
<li>중복 사용 금지 : 사용 횟수 추적 또는 replace 후 검증</li>
<li>이어붙인 형태만 허용 : 완전히 치환 후 빈 문자열인지 확인</li>
</ul>
<h3 id="접근-방식">접근 방식</h3>
<ul>
<li>방법 1 : 문자열 치환 + 검증<ol>
<li>&quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;를 하나씩 replace()로 제거</li>
<li>모든 단어를 제거한 뒤 남은 문자열이 &quot;&quot;이면 → 유효한 단어</li>
<li>단, &quot;ayaaya&quot;처럼 같은 단어 두 번 쓰면 제거되긴 해도 유효하지 않음 → 이를 검출해야 함</li>
</ol>
</li>
<li>방법 2 : 정규 표현식 (정규식) 사용
  •    &#39;^(aya|ye|woo|ma){1,4}$&#39; 패턴과 일치하는 문자열만 허용
  •    단, 중복 단어 방지는 추가 로직 필요함<h3 id="알고리즘">알고리즘</h3>
</li>
<li>문자열 처리</li>
<li>Set 활용</li>
<li>정규 표현식</li>
</ul>
<h3 id="시간-복잡도">시간 복잡도</h3>
<p>babbling의 길이를 n, 각 문자열의 최대 길이를 l이라고 할 때, 전체 시간복잡도는 O(n × l)</p>
<p>(방법 3 : O(k)(k &lt; l, k는 실제로 검사하게 되는 평균적인 문자 수))</p>
<h3 id="코드">코드</h3>
<p>방법 1: 문자열 치환(replace) + 중복 방지</p>
<pre><code class="language-python">def solution(babbling):
    valid = [&quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;]
    count = 0

    for word in babbling:
        temp = word
        for v in valid:
            temp = temp.replace(v, &quot; &quot;)
        # 중복 방지를 위해 한 단어가 두 번 연속 사용된 경우 제거
        if all(word.count(v*2) == 0 for v in valid) and temp.strip() == &quot;&quot;:
            count += 1

    return count</code></pre>
<p>방법 2: 정규 표현식 (re 모듈) + 중복 방지</p>
<pre><code class="language-python">import re

def solution(babbling):
    valid = [&quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;]
    count = 0

    for word in babbling:
        # 정규식으로 전체가 valid 단어들로만 구성되어 있는지 확인
        if re.fullmatch(r&quot;(aya|ye|woo|ma)+&quot;, word):
            # 중복 단어 사용 방지
            is_duplicate = any(v * 2 in word for v in valid)
            if not is_duplicate:
                count += 1

    return count</code></pre>
<p>방법 3: 시간복잡도 개선 (replace 방식 기반)</p>
<pre><code class="language-python">def solution(babbling):
    valid = [&quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;]
    count = 0

    for word in babbling:
        # 1. 길이 초과 시 즉시 탈락
        if len(word) &gt; 10:
            continue

        # 2. 중복된 단어가 연속된 경우 탈락
        if any(v*2 in word for v in valid):
            continue

        # 3. 유효 단어만 replace하고 나머지 문자가 남으면 탈락
        temp = word
        for v in valid:
            temp = temp.replace(v, &quot; &quot;)
        if temp.strip() == &quot;&quot;:
            count += 1

    return count</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트 : 종이 자르기]]></title>
            <link>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A2%85%EC%9D%B4-%EC%9E%90%EB%A5%B4%EA%B8%B0</link>
            <guid>https://velog.io/@juhee_ai/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A2%85%EC%9D%B4-%EC%9E%90%EB%A5%B4%EA%B8%B0</guid>
            <pubDate>Wed, 25 Jun 2025 22:06:53 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>큰 종이를 1 x 1 크기로 자르려고 합니다. 예를 들어 2 x 2 크기의 종이를 1 x 1 크기로 자르려면 최소 가위질 세 번이 필요합니다.</p>
<p>정수 <code>M</code>, <code>N</code>이 매개변수로 주어질 때, <code>M</code> x <code>N</code> 크기의 종이를 최소로 가위질 해야하는 횟수를 return 하도록 solution 함수를 완성해보세요.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/74bfb88b-5fda-4b15-bac6-e601b7fbba8b/image.png" alt=""></p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>0 &lt; <code>M</code>, <code>N</code> &lt; 100</li>
<li>종이를 겹쳐서 자를 수 없습니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>M</th>
<th>N</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>2</td>
<td>5</td>
<td>9</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody></table>
<h3 id="문제-특징">문제 특징</h3>
<ul>
<li>겹쳐서 자를 수 없음</li>
<li>가위질 횟수 최소</li>
<li>M x N 크기의 종이를 1 x 1로 자름</li>
</ul>
<p>-&gt; 최소한의 연산으로 M x N 개의 조각을 만들자</p>
<h3 id="접근-방식">접근 방식</h3>
<ul>
<li>가위질을 한 번 할 때마다 조각 수가 1개 늘어남</li>
<li>처음 1개에서 시작해서 M x N개가 되려면 몇 번 늘려야 하나? </li>
</ul>
<p>-&gt; M x N - 1</p>
<h3 id="문제-유형">문제 유형</h3>
<p>수학적 사고 / 그리디 / 패턴 인식</p>
<h3 id="시간-복잡도">시간 복잡도</h3>
<p>O(1)</p>
<h3 id="코드">코드</h3>
<pre><code>def solution(M, N):
    return M * N - 1</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[VAE]]></title>
            <link>https://velog.io/@juhee_ai/VAE</link>
            <guid>https://velog.io/@juhee_ai/VAE</guid>
            <pubDate>Mon, 26 May 2025 10:59:21 GMT</pubDate>
            <description><![CDATA[<h2 id="💡-vae란-무엇인가">💡 VAE란 무엇인가?</h2>
<p>Variational Autoencoder(VAE)는 입력 데이터의 변형을 생성하는 데 사용되는 <strong>생성 모델</strong>로 일반적인 오토인코더와 마찬가지로 인코더와 디코더로 구성되어 있습니다.</p>
<p>하지만 VAE는 잠재 공간(latent space)을 <strong>연속적이고 확률적인 방식</strong>으로 모델링하여 원본 입력을 <strong>정확히 재구성</strong>할 뿐만 아니라 원본과 유사한 새로운 데이터를 생성할 수 있습니다.</p>
<h2 id="💡-잠재-공간latent-space">💡 잠재 공간(Latent Space)</h2>
<p>잠재 공간은 입력 데이터의 중요한 특성을 압축하여 표현하는 저차원 공간입니다. </p>
<p>예를 들어, 28x28 픽셀의 흑백 이미지 (MNIST 데이터셋)는 784차원의 벡터로 표현되지만 실제로 의미 있는 정보는 그보다 훨씩 적은 차원에 존재합니다.</p>
<p>VAE는 이러한 유의미한 정보를 잠재 공간에 <strong>효과적으로 압축</strong>하여 표현합니다.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/09bdc6be-4a59-47d2-9dce-1a26c691bf61/image.png" alt=""></p>
<h2 id="💡-vae와-일반-오토인코더의-차이점">💡 VAE와 일반 오토인코더의 차이점</h2>
<p>일반 오토인코더는 입력 데이터를 압축하고 재구성하는 데 중점을 두며 잠재 공간을 <strong>고정된 벡터</strong>로 표현합니다.</p>
<p>반면 VAE는 잠재 공간을 <strong>확률 분포로 모델링</strong>하여 새로운 데이터를 생성할 수 있는 능력을 갖추고 있습니다.</p>
<p>또한 VAE는 재구성 손실 외에도 <strong>Kullback-Leibler(KL)</strong> 발산을 손실 함수에 포함시켜 잠재 공간의 분포가 사전에 정의된 분포와 유사하도록 학습합니다.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/c21d4590-dfa9-466c-ba13-15bd8a9b93a0/image.png" alt=""></p>
<h2 id="💡-reparameterization-trick">💡 Reparameterization Trick</h2>
<p>VAE는 잠재 공간에서 샘플링을 통해 데이터를 생성하지만 이 과정은 <strong>미분 가능하지 않음</strong> 문제가 발생합니다. </p>
<p>이를 해결하기 위해 <strong>Reparameterization Trick</strong>을 사용해 샘플링 과정을 미분 가능하게 만들어 모델의 학습이 가능하도록 합니다.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/bd181754-1078-43f1-a3db-ae1cb16d3e98/image.png" alt=""></p>
<h2 id="💡-수식-분석">💡 수식 분석</h2>
<h3 id="1️⃣-잠재-변수-모델에서의-추론">1️⃣ 잠재 변수 모델에서의 추론</h3>
<p>관측된 데이터 $x$와 잠재 변수 $z$를 가지는 확률 모델 $p(x, z)$를 고려합니다. </p>
<p><strong>우리의 목표</strong>
주어진 $x$에 대한 잠재 변수의 사후 분포 $p(z|x)$를 추정하는 것</p>
<p><strong>발생하는 문제</strong> </p>
<p>$p(z|x) = \frac{p(x, z)}{p(x)} = \frac{p(x, z)}{\int p(x, z) , dz}$
이 사후 분포는 분모의 적분 계산이 어려워 직접 계산이 불가능함</p>
<h3 id="2️⃣-변분-추론--근사-분포-qz-도입">2️⃣ 변분 추론 : 근사 분포 $q(z)$ 도입</h3>
<p>사후 분포 $p(z|x)$를 직접 계산하는 대신 이를 근사하기 위해 $q(z)$라는 변분 분포를 도입함</p>
<p>이때 $q(z)$는 $p(z|x)$와 유사하도록 선택돼야 하며 두 분포간의 유사성은 <strong>Kullback-Leibler(KL) 발산</strong>으로 측정됨</p>
<p>$D_{KL}(q(z) | p(z|x)) = \int q(z) \log \frac{q(z)}{p(z|x)} , dz$</p>
<p>KL 발산을 최소화하는 것은 $q(z)$가 $p(z|x)$에 가까워지도록 함</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/d519d038-e9e3-4c12-89fa-52ad92bdfff1/image.png" alt=""></p>
<h3 id="3️⃣-elbo-유도--jensen의-부등식-활용">3️⃣ ELBO 유도 : Jensen의 부등식 활용</h3>
<p>$\log p(x) = \log \int p(x, z) , dz = \log \int q(z) \frac{p(x, z)}{q(z)} , dz$</p>
<p>여기서 Jensen의 부등식을 적용하면</p>
<p>$\log p(x) \geq \int q(z) \log \frac{p(x, z)}{q(z)} , dz = \mathbb{E}_{q(z)}\left[\log \frac{p(x, z)}{q(z)}\right]$</p>
<p><strong>이 우변이 바로 ELBO(Evidence Lower Bound)</strong>입니다.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/dc4ca291-39e8-42c3-9aff-d84d2bc9c5a5/image.png" alt=""></p>
<h3 id="4️⃣-elbo의-대안적-표현">4️⃣ ELBO의 대안적 표현</h3>
<p>ELBO는 다음과 같이 재구성 손실과 정규화 항으로 분해될 수 있습니다.</p>
<p>$\mathbb{E}{q(z)}[\log p(x|z)] - D{KL}(q(z) | p(z))
$</p>
<p><strong>첫 번째 항</strong>: 재구성 손실, <strong>잠재 변수 $z$로부터 원래 데이터 $x$를 얼마나 잘 복원하는지</strong></p>
<p><strong>두 번째 항</strong>: 정규화 항, <strong>근사 분포 $q(z)$가 사전 분포 $p(z)$와 얼마나 유사한지 측정</strong></p>
<h3 id="5️⃣-elbo와-증거-log-px의-관계">5️⃣ ELBO와 증거 $log\ p(x)$의 관계</h3>
<p><strong>ELBO</strong>는 $log\ p(x)$의 하한이며 그 차이는 KL 발산으로 표현됨</p>
<p>$\log p(x) = \text{ELBO} + D_{KL}(q(z) | p(z|x))$</p>
<p>따라서 ELBO를 최대화하는 것은 KL 발산을 최소화하는 것과 동등하며 이는 $q(z)$가 $p(z|x)$에 가까워지도록 함</p>
<h2 id="💡-vae의-활용-분야">💡 VAE의 활용 분야</h2>
<p>VAE는 이미지 생성 외에도 다양한 인공지능 분야에서 활용됩니다.</p>
<p>예를 들어 <strong>이상 탐지, 노이즈 제거, 데이터 압축, 신약 개발</strong> 등에서 사용되며 복잡한 데이터의 잠재 구조를 학습하고 새로운 데이터를 생성하는 데 강력한 도구로 자리 잡고 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[YOLOv8]]></title>
            <link>https://velog.io/@juhee_ai/YOLOv8</link>
            <guid>https://velog.io/@juhee_ai/YOLOv8</guid>
            <pubDate>Fri, 23 May 2025 17:15:00 GMT</pubDate>
            <description><![CDATA[<h2 id="💡-참고-문서">💡 참고 문서</h2>
<ul>
<li><a href="https://docs.ultralytics.com">Ultralytics YOLOv8 공식 문서</a></li>
<li><a href="https://ultralytics.com/blog/yolov8/">YOLOv8 모델 발표 블로그</a></li>
<li><a href="https://arxiv.org/abs/2304.00501">YOLO 리뷰 논문 (arXiv:2304.00501)</a></li>
<li><a href="https://arxiv.org/abs/2408.15857">What is YOLOv8 논문 (arXiv:2408.15857)</a></li>
</ul>
<h2 id="💡-yolov8-개요-및-역사">💡 YOLOv8 개요 및 역사</h2>
<h3 id="1️⃣-yolo-시리즈-개요">1️⃣ YOLO 시리즈 개요</h3>
<table>
<thead>
<tr>
<th>버전</th>
<th>발표 시기</th>
<th>주요 변화</th>
</tr>
</thead>
<tbody><tr>
<td>YOLOv1</td>
<td>2016</td>
<td>최초의 YOLO, 단일 CNN으로 직접 bbox + class 예측</td>
</tr>
<tr>
<td>YOLOv3</td>
<td>2018</td>
<td>multi-scale detection 도입 (3단계 예측), 성능 향상</td>
</tr>
<tr>
<td>YOLOv4</td>
<td>2020</td>
<td>다양한 기법 통합 (CSPNet, Mish, Mosaic 등)</td>
</tr>
<tr>
<td>YOLOv5</td>
<td>2020</td>
<td>PyTorch 기반 비공식 모델, 경량화 + 실용성 강화</td>
</tr>
<tr>
<td>YOLOv7</td>
<td>2022</td>
<td>task-specific optimization, E-ELAN 구조</td>
</tr>
<tr>
<td><strong>YOLOv8</strong></td>
<td><strong>2023 (Ultralytics)</strong></td>
<td><strong>anchor-free, NMS 개선, segmentation + classification 통합</strong></td>
</tr>
</tbody></table>
<blockquote>
<p>YOLOv5부터는 공식 논문이 없고 Ultralytics에서 개발 및 유지보수중</p>
</blockquote>
<h3 id="2️⃣-yolov8-주요-특징-요약">2️⃣ YOLOv8 주요 특징 요약</h3>
<p><strong>Anchor-free 방식</strong> 채택
기존 YOLOv5는 anchor 기반이었지만 YOLOv8은 anchor 없이 중심점 예측을 사용해 더 단순하고 일반화된 구조 사용</p>
<p><strong>모듈 개선:C2f 구조 도입</strong>
기존 C3 모듈보다 더 경량화되고 연산 효율이 좋은 <strong>C2f(Concat-to-fuse)</strong> 모듈을 도입해 파라미터 수 감소 + 성능 향상</p>
<p><strong>모델 통합 지원</strong>
단일 프레임워크에서 <strong>Object Detection</strong>, <strong>Instance Segmentation</strong>, <strong>Classification</strong>까지 모두 지원</p>
<p><strong>기존 YOLOv5 대비 성능 향상</strong>
속도와 정확도 모두 향상됐으며 특히 작은 모델(YOLOv8n)에서도 높은 mAP 유지</p>
<h3 id="3️⃣-모델-라인업-크기별">3️⃣ 모델 라인업 (크기별)</h3>
<table>
<thead>
<tr>
<th>모델</th>
<th>파라미터 수</th>
<th>성능 (COCO val2017, mAP)</th>
<th>목적</th>
</tr>
</thead>
<tbody><tr>
<td>YOLOv8n</td>
<td>~3.2M</td>
<td>중간 성능, 빠른 속도</td>
<td>Mobile / 실시간</td>
</tr>
<tr>
<td>YOLOv8s</td>
<td>~11.2M</td>
<td>속도-성능 균형</td>
<td>중간</td>
</tr>
<tr>
<td>YOLOv8m</td>
<td>~25.9M</td>
<td>높은 정확도</td>
<td>일반 목적</td>
</tr>
<tr>
<td>YOLOv8l</td>
<td>~43.7M</td>
<td>더 높은 정확도</td>
<td>고정밀 탐지</td>
</tr>
<tr>
<td>YOLOv8x</td>
<td>~68.2M</td>
<td>최고 정확도</td>
<td>오프라인 탐지</td>
</tr>
</tbody></table>
<h2 id="💡-yolov8-구조-및-동작-원리">💡 YOLOv8 구조 및 동작 원리</h2>
<p>&#39;Backbone -&gt; Neck -&gt; Head&#39; 형태를 따르되 각 구성 요소에 다양한 개선이 이루어짐</p>
<h3 id="1️⃣-전체-구조">1️⃣ 전체 구조</h3>
<pre><code>[입력 이미지]
↓
[Backbone: CSPDarknet with C2f]
↓
[Neck: FPN-like 구조 (PANeck)]
↓
[Head: Anchor-Free Detection Head]
↓
[출력: class, bbox(x, y, w, h), objectness]</code></pre><h3 id="2️⃣-backbone-c2f-모듈-기반-경량-구조">2️⃣ Backbone: C2f 모듈 기반 경량 구조</h3>
<ul>
<li>기존 YOLOv5의 C3 모듈 -&gt; <strong>C2f(Concat-to-fuse)</strong>로 대체</li>
<li>목적 : feature reuse + 연산 감소</li>
<li>구조 : <ul>
<li>병렬 conv -&gt; concat -&gt; bottleneck (CSP 스타일)</li>
<li>파라미터 수 감소 + 정확도 유지/향상</li>
</ul>
</li>
</ul>
<pre><code class="language-python">Input → Conv → Bottleneck Blocks (split path) → Concat → Conv → Output</code></pre>
<h3 id="3️⃣-neck--fpn--pan-구조-multi-scale-feature">3️⃣ Neck : FPN + PAN 구조 (Multi-Scale Feature)</h3>
<ul>
<li>고해상도(low-level) + 저해상도(high-level) feature를 통합</li>
<li>upsample + concat -&gt; detection 성능 향상</li>
<li>이름은 따로 명시되지 않았지만 PANet 유사 구조</li>
</ul>
<h3 id="4️⃣-detection-head-예측부">4️⃣ Detection Head (예측부)</h3>
<p>Anchor-Free 방식</p>
<ul>
<li><p>중심점 기반 예측 (center-based point regression)</p>
</li>
<li><p>Anchor 설정 및 prior box가 필요 없음</p>
</li>
<li><p>더욱 단순화된 출력 구조</p>
<pre><code>  [batch, grid_h, grid_w, (num_classes + 4 + 1)]
   → 4: [x_center, y_center, width, height]
   → 1: objectness
   → num_classes: softmax 또는 sigmoid</code></pre></li>
</ul>
<h3 id="5️⃣-nms-방식">5️⃣ NMS 방식</h3>
<ul>
<li>기존 : class-agnostic NMS</li>
<li>YOLOv8 : class-aware NMS로 개선<ul>
<li>서로 다른 클래스 간에는 겹쳐도 제거되지 않음</li>
</ul>
</li>
<li>옵션으로 Dious-NMS, Soft-NMS 도입 가능</li>
</ul>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/e8c63269-cbcf-407a-a74e-5185a923e99a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/2d563a99-5b83-4a1a-aae5-3b8232171157/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/04a9e239-99df-4ea5-a5b0-f322f4ad5462/image.png" alt=""></p>
<p>(파라미터 개수)</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/fae6b4d3-8bac-4998-80c6-db4abfd82be3/image.png" alt=""></p>
<h2 id="💡-yolov8-학습-및-추론-과정">💡 YOLOv8 학습 및 추론 과정</h2>
<p>다양한 태스크(Object Detection, Classification, Segmentation)를 하나의 프레임워크에서 지원하며 학습 및 추론 과정에서도 직관적이고 유연하게 설계됨</p>
<h3 id="1️⃣-데이터-전처리-및-입력">1️⃣ 데이터 전처리 및 입력</h3>
<pre><code>    기본 입력 해상도 : 640 x 640
    입력 형식 : [batch_size, 3, H, W]
    전처리 내용 :
        이미지 리사이징 (비율 유지)
        Padding (Letterbox 방식)
        정규화 (0~1 스케일)
        채널 순서 변환 (HWC -&gt; CHW)</code></pre><h3 id="2️⃣-데이터-증강augmentation">2️⃣ 데이터 증강(Augmentation)</h3>
<p>기본 제공되는 증강 기법</p>
<table>
<thead>
<tr>
<th>기법명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Mosaic</td>
<td>4장의 이미지를 하나로 결합해 다양한 객체 수를 표현</td>
</tr>
<tr>
<td>HSV 변화</td>
<td>색조, 채도, 명도 변화</td>
</tr>
<tr>
<td>Flip</td>
<td>좌우 반전</td>
</tr>
<tr>
<td>Scale</td>
<td>이미지 확대/축소</td>
</tr>
<tr>
<td>Random crop</td>
<td>일부 영역만 자르기 (선택)</td>
</tr>
</tbody></table>
<p>-&gt; <code>Albumentations</code> 또는 <code>cv2</code> 기반으로 추가 커스터마이징 가능</p>
<h3 id="3️⃣-손실-함수loss">3️⃣ 손실 함수(Loss)</h3>
<p>다양한 손실 항 조합</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>손실 종류</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>bbox 위치</td>
<td>CIoU Loss</td>
<td>중심점과 크기 차이 모두 반영</td>
</tr>
<tr>
<td>class 예측</td>
<td>BCEWithLogitsLoss</td>
<td>멀티 클래스 분류용 binary cross entropy</td>
</tr>
<tr>
<td>objectness</td>
<td>BCEWithLogitsLoss</td>
<td>객체 여부 판단용 확률 출력</td>
</tr>
</tbody></table>
<p>-&gt; Loss는 각 항의 가중치를 조절해 전체 Loss로 통합</p>
<h3 id="4️⃣-optimizer-및-learning-rate-scheduler">4️⃣ Optimizer 및 Learning Rate Scheduler</h3>
<pre><code>    기본 설정
    Optimizer : SGD or AdamW
    초기 Learning : 0.01(SGD), 0.001(AdamW)
    Scheduler : Cosine Annealing, Linear Decay 또는 사용자 정의</code></pre><h3 id="5️⃣-추론inference-파이프라인">5️⃣ 추론(Inference) 파이프라인</h3>
<pre><code>입력 이미지
  ↓
전처리 및 크기 조정 (letterbox)
  ↓
모델 추론 (feature 추출 + head 예측)
  ↓
NMS 후 최종 bbox/class 반환
  ↓
원본 이미지 좌표로 bbox 재조정</code></pre><p>추론 속도 : YOLOv8n 기준 30~100 FPS 이상 가능 (GPU 기준)
출력 형식 : bbox 좌표 [x1, y1, x2, y2], class index + confidence</p>
<h2 id="💡-yolov8-성능-및-벤치마크">💡 YOLOv8 성능 및 벤치마크</h2>
<p>다양한 크기(n, s, m, l, x)의 모델 제공, 정확도와 추론 속도에서 균형 잡힌 성능을 보여줌 
anchor-free 방식 도입 이후 YOLOv5 대비 성능 향상, 실시간 및 고정밀 작업 모두에서 우수한 효율을 보임</p>
<h3 id="1️⃣-성능-지표-coco-val2017-기준">1️⃣ 성능 지표 (COCO val2017 기준)</h3>
<table>
<thead>
<tr>
<th>모델</th>
<th>파라미터 수</th>
<th>FPS (Tesla T4)</th>
<th>mAP50</th>
<th>mAP50-95</th>
</tr>
</thead>
<tbody><tr>
<td>YOLOv8n</td>
<td>~3.2M</td>
<td>~150 FPS</td>
<td>70.3</td>
<td>37.3</td>
</tr>
<tr>
<td>YOLOv8s</td>
<td>~11.2M</td>
<td>~100 FPS</td>
<td>78.4</td>
<td>44.9</td>
</tr>
<tr>
<td>YOLOv8m</td>
<td>~25.9M</td>
<td>~70 FPS</td>
<td>82.3</td>
<td>50.2</td>
</tr>
<tr>
<td>YOLOv8l</td>
<td>~43.7M</td>
<td>~45 FPS</td>
<td>84.5</td>
<td>52.9</td>
</tr>
<tr>
<td>YOLOv8x</td>
<td>~68.2M</td>
<td>~30 FPS</td>
<td>85.9</td>
<td>53.9</td>
</tr>
</tbody></table>
<h3 id="2️⃣-다른-모델과-비교-yolov5-yolov7-faster-r-cnn-등">2️⃣ 다른 모델과 비교 (YOLOv5, YOLOv7, Faster R-CNN 등)</h3>
<table>
<thead>
<tr>
<th>모델</th>
<th>Params</th>
<th>mAP50-95</th>
<th>FPS</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>YOLOv5s</td>
<td>7.2M</td>
<td>36.7</td>
<td>~110</td>
<td>anchor-based</td>
</tr>
<tr>
<td>YOLOv7</td>
<td>37.2M</td>
<td>51.2</td>
<td>~50</td>
<td>task-optimized</td>
</tr>
<tr>
<td><strong>YOLOv8m</strong></td>
<td>25.9M</td>
<td><strong>50.2</strong></td>
<td>~70</td>
<td>경량화 + anchor-free</td>
</tr>
<tr>
<td>Faster R-CNN (R50)</td>
<td>41.5M</td>
<td>42.1</td>
<td>~10</td>
<td>2-stage, 고정밀 but 느림</td>
</tr>
</tbody></table>
<p>→ YOLOv8은 속도와 정확도 양면에서 매우 강력한 <strong>실시간 탐지 최적화 모델</strong></p>
<h3 id="3️⃣-특징별-장단점-요약">3️⃣ 특징별 장단점 요약</h3>
<table>
<thead>
<tr>
<th>특징</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>Anchor-Free</td>
<td>단순 구조, 적은 prior 설정</td>
<td>작은 객체 탐지 성능 민감도 ↑</td>
</tr>
<tr>
<td>C2f 모듈</td>
<td>경량화 + 정확도 유지</td>
<td>구조가 덜 직관적일 수 있음</td>
</tr>
<tr>
<td>NMS 개선</td>
<td>클래스 간 억제 방지</td>
<td>여전히 높은 중복 제거 어려움 존재</td>
</tr>
<tr>
<td>다양한 사이즈 제공</td>
<td>디바이스 성능에 맞는 선택 가능</td>
<td>초경량 모델은 성능 제한 존재</td>
</tr>
</tbody></table>
<h3 id="4️⃣-실제-활용-환경에서-성능">4️⃣ 실제 활용 환경에서 성능</h3>
<table>
<thead>
<tr>
<th>환경</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>Raspberry Pi</td>
<td>YOLOv8n 기준 10~15 FPS (TensorRT 최적화 시)</td>
</tr>
<tr>
<td>Jetson Nano</td>
<td>YOLOv8n 기준 8~12 FPS (fp16 최적화 시)</td>
</tr>
<tr>
<td>Google Colab T4</td>
<td>YOLOv8s 기준 100+ FPS (실시간 가능)</td>
</tr>
<tr>
<td>RTX 3090</td>
<td>YOLOv8x도 40+ FPS 처리 가능</td>
</tr>
</tbody></table>
<h2 id="💡-실제-활용-사례-및-확장성">💡 실제 활용 사례 및 확장성</h2>
<p>단순한 객체 탐지를 넘어 다양한 컴퓨터 비전 작업에 활용될 수 있도록 설계됨
특히 Ultralytics 팀은 Detection 외에도 Classification, Instance Segmentation, Object Tracking까지 통합 지원함</p>
<h3 id="1️⃣-지원-태스크">1️⃣ 지원 태스크</h3>
<table>
<thead>
<tr>
<th>태스크 유형</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Object Detection</td>
<td>일반적인 bounding box 기반 탐지</td>
</tr>
<tr>
<td>Instance Segmentation</td>
<td>픽셀 단위 객체 분할 (mask 예측 포함)</td>
</tr>
<tr>
<td>Classification</td>
<td>이미지 전체의 클래스 분류</td>
</tr>
<tr>
<td>Object Tracking (beta)</td>
<td>동영상에서 객체 추적 (SORT 등과 통합)</td>
</tr>
</tbody></table>
<h3 id="2️⃣-주요-실무-활용-사례">2️⃣ 주요 실무 활용 사례</h3>
<table>
<thead>
<tr>
<th>분야</th>
<th>활용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>의약/헬스케어</td>
<td>약물 식별, 병변 탐지 (의료 영상)</td>
</tr>
<tr>
<td>제조/산업</td>
<td>결함 탐지, 로봇 비전</td>
</tr>
<tr>
<td>리테일</td>
<td>POS 모니터링, 재고 자동 인식</td>
</tr>
<tr>
<td>교통</td>
<td>번호판 인식, 차량/사람 추적</td>
</tr>
<tr>
<td>농업</td>
<td>작물 분류, 해충 탐지</td>
</tr>
<tr>
<td>보안</td>
<td>침입자 탐지, CCTV 분석</td>
</tr>
</tbody></table>
<p>다양한 edge-device에서도 실행 가능해 <strong>실시간 응용 분야에 최적화</strong>됨</p>
<h3 id="3️⃣-확장-및-배포-방식">3️⃣ 확장 및 배포 방식</h3>
<p><strong>Ultralytics API</strong></p>
<ul>
<li>from ultralytics import YOLO 만으로 모든 태스크 실행</li>
<li>추론 결과 : .boxes, .masks, .probs 등 다양한 출력 제공</li>
</ul>
<p><strong>ONNX / TensorRT / OpenVINO 내보내기</strong></p>
<pre><code class="language-bash">yolo export model=yolov8n.pt format=onnx</code></pre>
<p><strong>Edge 환경 최적화</strong></p>
<ul>
<li>fp16, int8로 양자화</li>
<li>ncnn, tflite, coreml 등 모바일 변환 가능</li>
</ul>
<h3 id="4️⃣-커스텀-데이터셋-적용">4️⃣ 커스텀 데이터셋 적용</h3>
<p>Ultralytics에서는 다양한 형식의 데이터셋을 지원</p>
<table>
<thead>
<tr>
<th>형식</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>YOLO</td>
<td>기본 .txt 포맷</td>
</tr>
<tr>
<td>COCO</td>
<td>.json 어노테이션</td>
</tr>
<tr>
<td>VOC</td>
<td>.xml</td>
</tr>
<tr>
<td>Custom</td>
<td>.yaml 파일로 경로만 설정하면 OK</td>
</tr>
</tbody></table>
<pre><code class="language-yaml"># data.yaml 예시
train: ../images/train
val: ../images/val

nc: 3
names: [&#39;pill&#39;, &#39;capsule&#39;, &#39;tablet&#39;]</code></pre>
<p>참고 링크
[Ultralytics 공식 튜토리얼] (<a href="https://docs.ultralytics.com/ko/">https://docs.ultralytics.com/ko/</a>)
[YOLOv8 Python API] (<a href="https://docs.ultralytics.com/ko/modes/predict/#inference-sources">https://docs.ultralytics.com/ko/modes/predict/#inference-sources</a>)</p>
<h2 id="💡-분석-논문-요약-및-비판적-고찰">💡 분석 논문 요약 및 비판적 고찰</h2>
<h3 id="1️⃣-분석-논문-요약">1️⃣ 분석 논문 요약</h3>
<p><strong>논문 1 : &quot;What is YOLOv8?&quot;</strong></p>
<ul>
<li>YOLOv8의 구조 (C2f, anchor-free head 등) 분석</li>
<li>YOLOv5 대비 정확도/속도 향상 정량 비교</li>
<li>Instance Segmentation 확장 구조 분석</li>
<li>다양한 태스크에서의 성능 벤치마크</li>
</ul>
<blockquote>
<p>YOLOv8은 구조적으로 YOLO 시리즈의 단순성과 실용성을 유지하면서도 성능 향상을 달성한 &quot;engineering-optimized model&quot;
<a href="https://arxiv.org/abs/2408.15857">논문 링크</a></p>
</blockquote>
<p><strong>논문 2 : &quot;A Comprehensive Review of YOLO Architectures in Computer Vision&quot;</strong></p>
<ul>
<li>YOLOv1부터 YOLOv8까지의 진화 과정 서술</li>
<li>각 버전별 핵심 기술 변화 정리</li>
<li>YOLOv8이 anchor-free로 전환된 이유에 대한 설명</li>
<li>기타 YOLO 기반 파생 모델(NAS, RT-DETR 등)과 비교</li>
</ul>
<blockquote>
<p>사용자 중심으로 진화한 최신 YOLO
<a href="https://arxiv.org/abs/2304.00501">논문 링크</a></p>
</blockquote>
<h3 id="2️⃣-yolov8의-한계점">2️⃣ YOLOv8의 한계점</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>공식 논문 부재</td>
<td>기술적 정합성 및 peer-review 통과된 근거 부족</td>
</tr>
<tr>
<td>small object 대응 미흡</td>
<td>anchor-free 구조에서 작은 객체 탐지 민감도 낮을 수 있음</td>
</tr>
<tr>
<td>custom training 제어 한계</td>
<td>하이레벨 API 위주 → 세부 튜닝에 제약 가능성 있음</td>
</tr>
<tr>
<td>복잡한 구조 해석 어려움</td>
<td>C2f 구조나 custom NMS 등은 직관적이지 않음</td>
</tr>
</tbody></table>
<h3 id="3️⃣-비판적-고찰">3️⃣ 비판적 고찰</h3>
<ul>
<li><strong>편리함 vs. 제어력</strong>  <ul>
<li>Ultralytics YOLO는 CLI, Python API로 매우 쉽고 빠르지만</li>
<li>세밀한 연구 목적이나 구조 실험에는 한계가 있을 수 있음</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>anchor-free의 과장된 장점?</strong>  </p>
<ul>
<li>anchor-free 방식이 항상 모든 상황에서 좋다고 단정할 수 없음</li>
<li>작은 객체나 밀집된 환경에서 성능이 불안정할 수 있음</li>
</ul>
</li>
<li><p><strong>결론:</strong><br>YOLOv8은 실용성과 속도를 최적화한 최신 모델이며 실무에 적합한 반면 <strong>연구 실험에는 구조 분석과 튜닝 측면에서 다소 제한</strong>이 있을 수 있음</p>
</li>
</ul>
<h3 id="4️⃣-실무-적용-시-요약-팁">4️⃣ 실무 적용 시 요약 팁</h3>
<ul>
<li>빠른 추론: YOLOv8n/s</li>
<li>정확도 중시: YOLOv8m/l/x</li>
<li>segmentation: yolov8s-seg.pt 등 별도 모델 사용</li>
<li>작은 객체 위주 작업: 입력 해상도 증가 or hybrid 모델 검토</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RetinaNet]]></title>
            <link>https://velog.io/@juhee_ai/RetinaNet</link>
            <guid>https://velog.io/@juhee_ai/RetinaNet</guid>
            <pubDate>Fri, 23 May 2025 04:19:54 GMT</pubDate>
            <description><![CDATA[<h2 id="💡-논문-정보">💡 논문 정보</h2>
<blockquote>
<p>논문 : Focal Loss for Dense Object Detection
저자 : Tsung-Yi Lin, Priya Goyal, Ross Girshick, Kaiming He, Piotr Dollár<br>출처 : ICCV 2017, Facebook AI Research (FAIR)
논문 링크 : <a href="https://arxiv.org/pdf/1708.02002">https://arxiv.org/pdf/1708.02002</a></p>
</blockquote>
<h2 id="💡-문제-정의-및-연구-동기">💡 문제 정의 및 연구 동기</h2>
<h3 id="1️⃣-객체-탐지object-detection의-두-가지-접근-방식">1️⃣ 객체 탐지(Object Detection)의 두 가지 접근 방식</h3>
<p>객체 탐지기는 입력 이미지에서 객체의 위치(Bounding Box)와 클래스(Label)를 예측하는 모델</p>
<p><strong>Two-stage Detector</strong></p>
<pre><code>    대표 모델 : R-CNN, Fast R-CNN, Faster R-CNN
    1단계 : 후보 영역(Region Proposal) 생성
    2단계 : 각 영역에 대해 분류 및 박스 회귀 수행
    장점 : 높은 정확도
    단점 : 속도가 느림</code></pre><p><strong>One-stage Detector</strong></p>
<pre><code>    대표 모델 : YOLO, SSD
    전체 이미지를 grid처럼 나누어 **한 번에 객체 탐지**
    장점 : 매우 빠름
    단점 : 정확도는 two-stage에 비해 낮음</code></pre><h3 id="2️⃣-one-stage-detector의-한계--클래스-불균형">2️⃣ One-stage Detector의 한계 : 클래스 불균형</h3>
<p>수많은 앵커(anchor)에 대해 <strong>대부분이 배경(background)</strong> 이기 때문에 문제 발생</p>
<pre><code>    양성 샘플(positive) : 실제 객체를 포함한 anchor (매우 적음)
    음성 샘플(negative) : 객체가 없는 anchor (매우 많음)</code></pre><p>발생하는 문제점
<strong>훈련 손실(loss)의 대부분이 easy negative에 의해 지배됨</strong>
모델이 모든 영역을 배경으로 분류하는 <strong>보수적 학습</strong>을 하게 됨
<strong>정확한 객체 탐지가 어려워짐</strong></p>
<h3 id="3️⃣-연구-목표">3️⃣ 연구 목표</h3>
<blockquote>
<p>❓ “왜 one-stage detector는 two-stage보다 정확도가 낮은가?”</p>
</blockquote>
<p>결론</p>
<pre><code>    원인은 모델 구조가 아니라 훈련 과정에서의 클래스 불균형임
    해결책은 손실 함수 자체에 존재</code></pre><p>따라서 기존 <strong>cross-entropy 손실의 한계</strong>를 분석하고 <strong>새로운 손실 함수인 Focal Loss</strong>를 제안하여 One-stage 모델인 RetinaNet을 통해 <strong>정확도와 속도의 균형</strong>을 달성하고자 함</p>
<h2 id="💡-focal-loss-정의-및-직관">💡 Focal Loss 정의 및 직관</h2>
<h3 id="1️⃣-클래스-불균형-문제--cross-entropy">1️⃣ 클래스 불균형 문제 : Cross Entropy</h3>
<p>객체 탐지에서 <strong>수많은 negative anchor</strong>는 모델 학습에 방해가 됨</p>
<p>기존 Cross Entropy 손실 수식 : $CE(p_t) = -log(p_t)$</p>
<ul>
<li>$p_t$ : 예측 확률이 정답 클래스일 경우의 확률(정답 클래스가 1이고 모델이 0.95확률로 예측했다면 0.95)</li>
</ul>
<blockquote>
<p><strong>문제점</strong>
이미 <strong>정확하게 분류된 쉬운 예제</strong>도 높은 loss를 유발
negative anchor가 많으므로 전체 loss에서 지배적</p>
</blockquote>
<h3 id="2️⃣-focal-수식">2️⃣ Focal 수식</h3>
<p>Focal Loss는 Cross Entropy에 <strong>modulating factor $(1 - p_t)^\gamma$</strong> 를 곱하여 easy sample의 loss를 줄임</p>
<p>$FL(p_t) = -α_t * (1 - p_t)^γ * log(p_t)$</p>
<ul>
<li>$p_t$ : 정답 클래스일 확률</li>
<li>$\gamma$ : 조절 계수 (focusing parameter), 일반적으로 2 사용</li>
<li>$α_t$ : 클래스 불균형을 보정하는 weight factor (optional)</li>
</ul>
<h3 id="3️⃣-직관-요약">3️⃣ 직관 요약</h3>
<table>
<thead>
<tr>
<th>예시</th>
<th>$p_t$</th>
<th>$(1 - p_t)^\gamma$</th>
<th>영향</th>
</tr>
</thead>
<tbody><tr>
<td>Easy positive</td>
<td>0.95</td>
<td>매우 작음 → loss 거의 0</td>
<td>무시됨</td>
</tr>
<tr>
<td>Hard positive</td>
<td>0.1</td>
<td>큼 → loss 유지됨</td>
<td>집중됨</td>
</tr>
<tr>
<td>Easy negative</td>
<td>0.99</td>
<td>매우 작음</td>
<td>억제됨</td>
</tr>
<tr>
<td>Hard negative</td>
<td>0.3</td>
<td>중간</td>
<td>집중됨</td>
</tr>
</tbody></table>
<p><strong>쉬운 샘플은 무시하고 어려운 샘플에 집중</strong>하도록 유도</p>
<h3 id="4️⃣-γ-gamma의-영향-실험">4️⃣ γ (gamma)의 영향 실험</h3>
<p>γ가 0일 때 -&gt; 일반 cross-entropy
γ가 높을 때 -&gt; easy 예제 무시 비율 증가</p>
<p>일반적으로 <strong>γ = 2</strong>가 best 결과를 보임</p>
<h3 id="5️⃣-α-alpha-balancin">5️⃣ α (alpha) balancin</h3>
<p>클래스 불균형을 보정하는 weight
positive class에 α = 0.25, negative class에 1 - α = 0.75 사용
(기존 <strong>class-balanced CE</strong> 손실과 유사한 개념)</p>
<h3 id="6️⃣-focal-loss의-핵심-효과">6️⃣ Focal Loss의 핵심 효과</h3>
<pre><code>    easy negative가 loss에서 차지하는 비중을 줄임
    학습 효율을 높이고 모델이 진짜 객체(positive anchor)에 집중하게 만듦
    따라서 one-stage detector에서도 높은 정확도 가능하게 만듦</code></pre><h2 id="💡-retinanet-구조-분석">💡 RetinaNet 구조 분석</h2>
<h3 id="1️⃣-one-stage-detector의-재정의">1️⃣ One-stage Detector의 재정의</h3>
<pre><code>    Backbone : ResNet-50 또는 ResNet-101
    Feature Extractor : FPN (Feature Pyramid Network)
    Head Subnet : Classification Subnet(anchor 클래스 예측), Regression Subnet(bbox 예측)</code></pre><h3 id="2️⃣-feature-pyramid-network-fpn">2️⃣ Feature Pyramid Network (FPN)</h3>
<p>객체 탐지는 다양한 크기의 객체에 대응해야 하므로 <strong>멀티스케일 feature map</strong> 필요</p>
<blockquote>
<p><strong>FPN 구조</strong> </p>
</blockquote>
<p>ResNet의 중간 feature (C3, C4, C5 등)를 상향 전파해 고해상도 정보 보존
상향(feature upsampling) + 측면 연결(lateral connection)을 통해 P3 ~ P7 pyramid 생성</p>
<table>
<thead>
<tr>
<th>Pyramid Level</th>
<th>해상도 (input 기준)</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>P3</td>
<td>1/8</td>
<td>소형 객체 탐지</td>
</tr>
<tr>
<td>P4</td>
<td>1/16</td>
<td>중형 객체 탐지</td>
</tr>
<tr>
<td>P5</td>
<td>1/32</td>
<td>대형 객체 탐지</td>
</tr>
<tr>
<td>P6, P7</td>
<td>1/64, 1/128</td>
<td>매우 큰 객체 / 앵커 다양화</td>
</tr>
</tbody></table>
<h3 id="3️⃣-subnet-구조-head">3️⃣ Subnet 구조 (Head)</h3>
<p>각 FPN 레벨(P3~P7)에 대해 동일한 구조의 Subnet 2개를 적용</p>
<p><strong>🔧 Classification Subnet</strong></p>
<ul>
<li>4개의 3x3 Conv + ReLU</li>
<li>1개의 3x3 Conv -&gt; $A \times C$ 출력 (A : anchor 수, C : 클래스 수)</li>
</ul>
<p><strong>🔧 Regression Subnet</strong></p>
<ul>
<li>4개의 3x3 Conv + ReLU</li>
<li>1개의 3x3 Conv -&gt; $A \times 4$ 출력 (x, y, w, h)</li>
</ul>
<h3 id="4️⃣-anchor-설정">4️⃣ Anchor 설정</h3>
<p>각 FPN level마다 <strong>anchor box</strong>를 미리 정의해 다중 객체 탐지 수행</p>
<table>
<thead>
<tr>
<th>설정 항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>Anchor scale</td>
<td>${32, 64, 128, 256, 512}$</td>
</tr>
<tr>
<td>Aspect ratio</td>
<td>${1:1, 1:2, 2:1}$</td>
</tr>
<tr>
<td>각 레벨당 anchor 수</td>
<td>9 (3 ratio × 3 scale)</td>
</tr>
</tbody></table>
<h3 id="5️⃣-장점">5️⃣ 장점</h3>
<p>FPN으로 멀티 스케일 탐지 강화
Head subnet은 매우 얕고 공유 구조를 사용 -&gt; <strong>속도 향상</strong>
Focal Loss로 인해 <strong>정확도 문제까지 해결</strong></p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/9eb966e4-906a-4e06-8374-78ec042e0f83/image.png" alt=""></p>
<h2 id="💡-실험-설정-및-성능-비교-결과">💡 실험 설정 및 성능 비교 결과</h2>
<h3 id="1️⃣-실험-환경-및-세부-설정">1️⃣ 실험 환경 및 세부 설정</h3>
<p><strong>🔧 모델 학습 설정</strong></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>Optimizer</td>
<td>SGD</td>
</tr>
<tr>
<td>Batch size</td>
<td>16</td>
</tr>
<tr>
<td>Learning rate</td>
<td>0.01</td>
</tr>
<tr>
<td>Weight decay</td>
<td>0.0001</td>
</tr>
<tr>
<td>Step schedule</td>
<td>[60k, 80k]</td>
</tr>
<tr>
<td>Total iterations</td>
<td>90k</td>
</tr>
</tbody></table>
<p><strong>🔧 하이퍼파라미터</strong></p>
<ul>
<li>Focal Loss의 γ (gamma): 2</li>
<li>α (alpha): 0.25 (positive anchor에 적용)</li>
</ul>
<h3 id="2️⃣-focal-loss-효과-실험">2️⃣ Focal Loss 효과 실험</h3>
<p>Focal Loss의 γ 값을 변경하면서 <strong>어떻게 정확도(AP)</strong> 가 달라지는지 실험</p>
<table>
<thead>
<tr>
<th>γ (gamma) 값</th>
<th>AP (전체)</th>
<th>AP50</th>
<th>AP75</th>
</tr>
</thead>
<tbody><tr>
<td>0 (CE Loss)</td>
<td>낮음</td>
<td>낮음</td>
<td>낮음</td>
</tr>
<tr>
<td>1.0</td>
<td>증가함</td>
<td>↑</td>
<td>↑</td>
</tr>
<tr>
<td>2.0</td>
<td><strong>최고 성능</strong></td>
<td>최고</td>
<td>최고</td>
</tr>
<tr>
<td>5.0 이상</td>
<td>과도하게 easy sample 무시 → 성능 저하</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="3️⃣-retinanet-vs-faster-r-cnn-vs-ssd">3️⃣ RetinaNet vs Faster R-CNN vs SSD</h3>
<table>
<thead>
<tr>
<th>Model</th>
<th>Backbone</th>
<th>AP</th>
<th>AP50</th>
<th>FPS</th>
</tr>
</thead>
<tbody><tr>
<td>RetinaNet</td>
<td>ResNet-101-FPN</td>
<td><strong>39.1</strong></td>
<td>59.1</td>
<td>5.0</td>
</tr>
<tr>
<td>Faster R-CNN</td>
<td>ResNet-101</td>
<td>36.2</td>
<td>59.1</td>
<td>2.0</td>
</tr>
<tr>
<td>SSD513</td>
<td>ResNet-101</td>
<td>31.2</td>
<td>51.2</td>
<td>6.6</td>
</tr>
</tbody></table>
<p>RetinaNet은 <strong>Faster R-CNN보다 정확도 높고</strong> SSD보다 <strong>성능-속도 균형이 우수</strong>
특히 AP50, AP75뿐 아니라 **소형 객체(AP_S), 중형 객체(AP_M), 대형 객체(AP_L) 모두에서 강함</p>
<h3 id="4️⃣-retinanet의-학습-안정성">4️⃣ RetinaNet의 학습 안정성</h3>
<p>Focal Loss를 사용한 RetinaNet은 <strong>overfitting 없이 학습이 안정적</strong>
CE Loss만 썼을 때는 easy negative에 빠르게 overfit</p>
<p>Focal Loss + FPN + 간결한 서브넷 구조 = 속도·정확도 균형</p>
<h2 id="💡-결론-및-영향력-요약">💡 결론 및 영향력 요약</h2>
<h3 id="1️⃣-실제-영향력">1️⃣ 실제 영향력</h3>
<table>
<thead>
<tr>
<th>분야</th>
<th>영향</th>
</tr>
</thead>
<tbody><tr>
<td>논문 인용 수</td>
<td>10,000+ 회 이상 (2024년 기준)</td>
</tr>
<tr>
<td>후속 연구</td>
<td>EfficientDet, CenterNet, FCOS 등 다수의 모델이 Focal Loss 또는 RetinaNet 구조를 기반으로 발전</td>
</tr>
<tr>
<td>산업 적용</td>
<td>Edge 디바이스, 모바일, 실시간 객체 탐지 분야에서 폭넓게 활용됨</td>
</tr>
</tbody></table>
<h3 id="2️⃣-한계-및-향후-과제">2️⃣ 한계 및 향후 과제</h3>
<table>
<thead>
<tr>
<th>한계</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>높은 연산량</td>
<td>FPN + 다수의 anchor 사용으로 여전히 <strong>연산 부담 존재</strong></td>
</tr>
<tr>
<td>anchor 기반 구조</td>
<td>anchor-free 방식(FoveaBox, FCOS 등)에 비해 <strong>설정 복잡성</strong> 있음</td>
</tr>
</tbody></table>
<blockquote>
<p>이 논문은 이후 <strong>anchor-free 객체 탐지기의 출현</strong>에도 영향을 주었으며 Dense prediction 문제 해결을 위한 출발점 역할을 함</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[FCN]]></title>
            <link>https://velog.io/@juhee_ai/FCN</link>
            <guid>https://velog.io/@juhee_ai/FCN</guid>
            <pubDate>Fri, 16 May 2025 15:18:58 GMT</pubDate>
            <description><![CDATA[<p><strong>[FCN 논문]</strong>
“Fully Convolutional Networks for Semantic Segmentation” Jonathan Long, Evan Shelhamer, Trevor Darrell (CVPR 2015)</p>
<p><a href="https://arxiv.org/abs/1411.4038">https://arxiv.org/abs/1411.4038</a></p>
<h2 id="논문-요약">논문 요약</h2>
<p>기존 이미지 분류용 CNN을 픽셀 단위 의미론적 분할(Semantic Segmentation)에 맞게 완전히 컨볼루션 층으로만 재설계한 구조입니다.</p>
<h3 id="핵심-아이디어">핵심 아이디어</h3>
<p>1️⃣ 기존 CNN의 Fully Connected 층을 1x1 Convolution으로 대체해 입력 크기에 관계없이 공간적 특성을 유지하며 dense한 픽셀별 예측이 가능하도록 합니다.</p>
<p>2️⃣ 깊은 네트워크를 통해 축소된 저해상도 feature map을 Transposed Convolution으로 원래 해상도로 업샘플링하여 픽셀 단위의 class score map을 생성합니다.</p>
<h3 id="특징">특징</h3>
<p>중간 해상도의 feature map을 활용하는 skip connection을 도입해 coarse한 예측의 공간적 세부 정보를 보완함으로써 더 정밀한 분할 결과를 얻습니다.</p>
<p>sliding window 방식의 비효율성을 극복하며 입력의 크기에 구애받지 않고 end-to-end로 학습 가능한 semantic segmentation으로 평가받습니다.</p>
<h2 id="개념-설명">개념 설명</h2>
<h3 id="1️⃣-semantic-segmentation">1️⃣ Semantic Segmentation</h3>
<p>이미지의 모든 픽셀에 대해 클래스(label)를 예측하는 문제
기존의 CNN의 한계를 넘어 픽셀 단위로 의미를 해석하는 최초의 완전한 컨볼루션 구조를 제공한 모델</p>
<h3 id="2️⃣-sliding-window-vs-fcn">2️⃣ Sliding Window vs FCN</h3>
<p><strong>Sliding Window</strong>
이미지 내의 <strong>작은 고정 크기 영역(patch)</strong>을 하나씩 잘라내어 각 패치마다 CNN을 따로 적용해 분류하는 전통적 방법</p>
<p>고정 크기의 윈도우를 이미지 위에서 일정한 간격으로 이동시키며 각 위치에서 모델을 적용하여 객체나 패턴을 탐지하는 방식입니다.
중복 계산이 많고 비효율적이며 고정된 입력 크기를 요구하고 객체 경계가 부정확할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/9316e39c-866b-414d-9e4a-ed41697769ce/image.png" alt=""></p>
<p><strong>FCN</strong>
입력 이미지의 각 픽셀에 대해 예측을 수행하는 방식</p>
<p>전체 이미지를 한 번에 처리하며 중복 계산을 줄이고 다양한 크기의 입력을 처리할 수 있으며 픽셀 단위의 정밀한 예측이 가능합니다.</p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/099fc9c7-a830-44b4-b8f5-cab6603118fe/image.png" alt=""></p>
<p>출처: <a href="https://medium.com/ai-quest/convolutional-implementation-of-the-sliding-window-algorithm-db93a49f99a0">https://medium.com/ai-quest/convolutional-implementation-of-the-sliding-window-algorithm-db93a49f99a0</a></p>
<h3 id="3️⃣-fc-layer---1x1-conv">3️⃣ FC Layer -&gt; 1x1 Conv</h3>
<p><strong>FC Layer의 한계</strong></p>
<p>기존 CNN에서는 마지막에 flatten -&gt; FC layer로 클래스 벡터를 출력하게 됩니다.
이렇게 되면 위치 정보는 다 사라지고 &quot;무엇이 있는가&quot;만 예측할 수 있게 됩니다.</p>
<p><strong>1x1 Convolution으로 대체</strong></p>
<p>FC layer는 사실상 전 채널에 가중치를 곱해 하나의 벡터로 만드는 연산입니다.
이걸 공간을 유지한 채로 적용하기 위해 1x1 convolution으로 대체했습니다.</p>
<p>이렇게 변경한 덕분에 모든 위치에 대해 동시에 예측(dense prediction) 가능하고 각 픽셀에 대해 어떤 클래스인지 판단할 수 있게 됐습니다.</p>
<blockquote>
<p><strong>Dense prediction이란?</strong>
입력 이미지의 각 픽셀 위치마다 출력(예측)을 생성하는 문제 구조
출력이 이미지와 같은 해상도를 갖고 공간적으로 조밀하게 구성되는 예측 방식</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/8074ef86-278c-4a67-8651-291c9ae4122d/image.png" alt=""></p>
<h3 id="4️⃣-transposed-convolution">4️⃣ Transposed Convolution</h3>
<p>VGG-16은 conv+pool을 반복하여 해상도를 축소합니다.
(예: 224x224 -&gt; 112 -&gt; 56 - 28 -&gt; 14 -&gt; 7) -&gt; 최대 32배 감소 (stride=32)</p>
<p>이때 축소된 해상도를 복원하기 위해 Transposed Convolution을 사용하게 됩니다.
이는 축소된 feature map을 다시 upsampling해서 원래 해상도로 복원하는 역할입니다.
학습 가능한 가중치를 갖는 업샘플링이므로 단순 보간(Bilinear)보다 표현력이 높습니다.</p>
<blockquote>
<p><strong>Bilinear Interpolation이란?</strong>
비학습 업샘플링 방식입니다. 계산 속도가 빠르다는 장점이 있지만 디테일 복원이 불가하고 경계가 흐릿하게 표현되고 표현력이 제한된다는 치명적인 단점이 존재합니다. 
<img src="https://velog.velcdn.com/images/juhee_ai/post/23d1b414-4204-4c3e-aa19-200212da216c/image.png" alt=""></p>
</blockquote>
<p><strong>Transposed Convolution 방식</strong>
작은 feature map을 입력받아 커널을 &quot;거꾸로 적용&quot;해 더 큰 출력(feature map)을 생성하는 연산입니다.</p>
<p><strong>출력 크기</strong>
$$\boxed{
O = S \cdot (I - 1) + K - 2P
}$$</p>
<p>출력 해상도 = Stride(입력 해상도 - 1) + 커널 - 2 x 패딩</p>
<p><strong>작동 방식</strong>
1️⃣ Zero Insertion (Zero Padding between pixels)
입력 feature map의 픽셀 사이에 0을 삽입해 크기를 확장합니다.
(예: stride=2이면 픽셀 사이에 1칸씩 0을 삽입)</p>
<p>2️⃣ Normal Convolution 적용
확장된 입력에 일반 convolution처럼 커널을 sliding합니다.</p>
<p><strong>padding = 1, stride = 2를 준 Transposed Convolution 작동 방식</strong>
<img src="https://velog.velcdn.com/images/juhee_ai/post/6045de65-d6d5-414d-9c86-d474c745e5ee/image.gif" alt=""></p>
<p>출처: <a href="https://github.com/vdumoulin/conv_arithmetic?tab=readme-ov-file">https://github.com/vdumoulin/conv_arithmetic?tab=readme-ov-file</a></p>
<blockquote>
<p><strong>Transposed인 이유</strong></p>
</blockquote>
<pre><code class="language-python">nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding, output_padding)</code></pre>
<p>이때 내부 weight의 shape은</p>
<pre><code class="language-python">(out_channels, in_channels, kernel_size, kernel_size)</code></pre>
<p>-&gt; 일반 conv와 in/out_channels 순서가 바뀌어 있습니다.</p>
<blockquote>
</blockquote>
<p><strong>커널 내부 값들의 형태나 패턴이 정해져있는가? X</strong>
모든 커널 값들은 일반 Convolution처럼 랜덤 초기화되고 학습 과정에서 최적화됩니다.</p>
<blockquote>
</blockquote>
<p>1) 보통 Xavier, He, Kaiming 초기화로 랜덤하게 초기화됨
2) 학습 초기에는 불규칙한 값들로 채워져 있음
3) 학습이 진행되면서 <strong>업샘플링에 적합한</strong> 방향성 있는 패턴을 자동으로 학습</p>
<h3 id="5️⃣-fcn-32s-fcn-16s-fcn-8s-구조와-skip-connection">5️⃣ FCN-32s, FCN-16s, FCN-8s 구조와 Skip Connection</h3>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/4b4b57c7-2284-40de-bad1-158d558b3cd2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/64f694a8-cdbe-4b69-a251-3e171303bb4c/image.png" alt=""></p>
<p><strong>FCN-32s</strong></p>
<pre><code>Input (e.g. 224×224 RGB)
 ↓
VGG-16 conv layers → 마지막 feature map: 7×7×512
 ↓
1×1 conv (num_classes) → score map: 7×7×21
 ↓
Transposed Conv (stride=32, kernel=64) → 224×224 복원
 ↓
Pixel-wise Softmax</code></pre><p>특징</p>
<ul>
<li>Skip connection 없음</li>
<li>한 번에 coarse한 feature를 32배 upsampling</li>
<li>가장 간단한 구조지만 출력이 흐릿함</li>
</ul>
<p><strong>FCN-16s</strong></p>
<pre><code>Input
 ↓
VGG-16 conv layers
 ↓
pool5 → 1×1 conv → score map (7×7)
 ↓
Transposed Conv (stride=2) → 14×14
 + element-wise sum with pool4 (1/16 해상도)
 ↓
Transposed Conv (stride=16) → 224×224</code></pre><p>특징</p>
<ul>
<li>skip connection 1개: pool4 사용</li>
<li>pool5의 coarse 정보 + pool4의 finer 정보 결합</li>
<li>출력 경계가 더 정밀해짐</li>
</ul>
<p><strong>FCN-8s</strong></p>
<pre><code>Input
 ↓
VGG-16 conv layers
 ↓
pool5 → 1×1 conv → score map (7×7)
 ↓
Transposed Conv (stride=2) → 14×14
 + element-wise sum with pool4
 ↓
Transposed Conv (stride=2) → 28×28
 + element-wise sum with pool3
 ↓
Transposed Conv (stride=8) → 224×224</code></pre><p>특징</p>
<ul>
<li>skip connection 2개: pool4, pool3 사용</li>
<li>shallow feature 활용해 위치 정보/경계 보정</li>
<li>가장 정밀한 FCN 구조</li>
</ul>
<h3 id="6️⃣-평가-지표iou">6️⃣ 평가 지표(IoU)</h3>
<p>$$\text{IoU} = \frac{\text{TP}}{\text{TP} + \text{FP} + \text{FN}}$$
<img src="https://velog.velcdn.com/images/juhee_ai/post/be160fae-a81f-445b-9d19-4150dbe25088/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee_ai/post/c43d749c-50f5-4d45-a051-6bb5d853f2f7/image.png" alt=""></p>
<p>여러 클래스가 있을 경우 클래스별 IoU를 구한 뒤 평균을 냅니다.
$$\text{mIoU} = \frac{1}{C} \sum_{i=1}^{C} \text{IoU}_i$$</p>
<ul>
<li>객체가 많은 클래스와 적은 클래스 모두 동일하게 반영되므로 <strong>클래스 불균형에 덜 민감</strong>합니다.</li>
</ul>
<h3 id="7️⃣-fcn의-한계">7️⃣ FCN의 한계</h3>
<p><strong>1️⃣ 출력 해상도가 낮음 (coarse prediction)</strong>
FCN의 기본 구조는 VGG와 같은 classification backbone을 기반으로 합니다.
따라서 <strong>Conv + Pooling이 반복</strong>되어 입력 대비 32배나 축소된 coarse feature map이 생성됩니다.
이 작은 feature map을 Transposed Convolution으로 upsampling하지만 결과적으로 픽셀 단위의 예측이 부정확하거나 경계가 흐릿하게 보입니다.</p>
<p><strong>2️⃣ 객체 경계 불명확 (blurred boundaries)</strong>
깊은 레이어일수록 의미는 강하지만 위치 정보는 약해집니다. (<strong>semantic-rich, spatial-poor</strong>)
FCN은 low-level feature의 위치 정보가 부족하므로 객체 경계(sharp edge) 표현이 부족합니다.
특히 얇은 객체나 경계가 중요한 경우(예: 의료영상, 도로 차선)에 치명적입니다.</p>
<p><strong>3️⃣ skip connection의 단순 결합</strong>
단순한 Element-wise sum으로 결합하므로 shallow와 deep feature 간의 의미 불일치 또는 정보 손실이 발생합니다. 
이에 대해서는 이후 U-Net처럼 concat 후 conv로 보완하는 방식으로 발전됐습니다.</p>
<p><strong>4️⃣ 모양 다양성(object shape variation)에 취약</strong>
FCN은 고정된 필터와 고정 receptive field를 기반으로 하기 때문에 다양한 크기/형태의 객체에 적응력이 부족하다는 단점이 있습니다.
이에 대해서는 이후 DeepLab의 ASPP(Atrous Spatial Pyramid Pooling) 등 멀티스케일 대응 방식으로 발전됐습니다.</p>
<p><strong>5️⃣ 인스턴스 구분 불가(instance-unaware)</strong>
픽셀에 class는 부여하지만 동일 클래스 내 다른 객체 구분은 할 수 없습니다.
이에 대해서는 Mask R-CNN, Panoptic Segmentation을 통해 객체까지 구분할 수 있게끔 발전됐습니다.</p>
<h3 id="8️⃣-fcn-코드-구현">8️⃣ FCN 코드 구현</h3>
<p><strong>FCN 공통 기반 (VGG-16 Feature Extractor)</strong></p>
<pre><code class="language-python">import torch
import torch.nn as nn
import torchvision.models as models

class VGGBackbone(nn.Module):
    def __init__(self, pretrained=True):
        super().__init__()
        vgg = models.vgg16(pretrained=pretrained)
        features = list(vgg.features.children())
        self.stage1 = nn.Sequential(*features[:5])     # relu1_2
        self.stage2 = nn.Sequential(*features[5:10])   # relu2_2
        self.stage3 = nn.Sequential(*features[10:17])  # relu3_3
        self.stage4 = nn.Sequential(*features[17:24])  # relu4_3
        self.stage5 = nn.Sequential(*features[24:31])  # relu5_3
    def forward(self, x):
        x1 = self.stage1(x)
        x2 = self.stage2(x1)
        x3 = self.stage3(x2)
        x4 = self.stage4(x3)
        x5 = self.stage5(x4)

return x3, x4, x5  # skip용으로 3, 4, 5단계 반환</code></pre>
<p><strong>FCN-32s</strong></p>
<pre><code class="language-python">class FCN32s(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.backbone = VGGBackbone()
        self.classifier = nn.Conv2d(512, num_classes, kernel_size=1)
        self.upsample = nn.ConvTranspose2d(
        num_classes, num_classes, kernel_size=64, stride=32,
        padding=16, bias=False
        )

    def forward(self, x):
        _, _, x5 = self.backbone(x)
        score = self.classifier(x5)
        upsampled = self.upsample(score)
        return upsampled</code></pre>
<p><strong>FCN-16s (skip from pool4)</strong></p>
<pre><code class="language-python">class FCN16s(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.backbone = VGGBackbone()
        self.score_pool4 = nn.Conv2d(512, num_classes, kernel_size=1)
        self.score_final = nn.Conv2d(512, num_classes, kernel_size=1)
        self.upsample2x = nn.ConvTranspose2d(num_classes, num_classes, 4, stride=2, padding=1, bias=False)
        self.upsample32x = nn.ConvTranspose2d(num_classes, num_classes, 32, stride=16, padding=8, bias=False)

    def forward(self, x):
        _, x4, x5 = self.backbone(x)
        score = self.score_final(x5)
        upscore2 = self.upsample2x(score)
        score_pool4 = self.score_pool4(x4)
        fuse = upscore2 + score_pool4
        out = self.upsample32x(fuse)
        return out</code></pre>
<p><strong>FCN-8s (skip from pool3)</strong></p>
<pre><code class="language-python">class FCN8s(nn.Module):
def __init__(self, num_classes):
    super().__init__()
    self.backbone = VGGBackbone()
    self.score_pool3 = nn.Conv2d(256, num_classes, kernel_size=1)
    self.score_pool4 = nn.Conv2d(512, num_classes, kernel_size=1)
    self.score_final = nn.Conv2d(512, num_classes, kernel_size=1)
    self.upsample2x = nn.ConvTranspose2d(num_classes, num_classes, 4, stride=2, padding=1, bias=False)
    self.upsample4x = nn.ConvTranspose2d(num_classes, num_classes, 4, stride=2, padding=1, bias=False)
    self.upsample8x = nn.ConvTranspose2d(num_classes, num_classes, 16, stride=8, padding=4, bias=False)

def forward(self, x):
    x3, x4, x5 = self.backbone(x)
    score = self.score_final(x5)
    score = self.upsample2x(score)
    score += self.score_pool4(x4)
    score = self.upsample4x(score)
    score += self.score_pool3(x3)
    out = self.upsample8x(score)

    return out</code></pre>
]]></description>
        </item>
    </channel>
</rss>