<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>pink-frog.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 06 Oct 2026 06:13:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>pink-frog.log</title>
            <url>https://velog.velcdn.com/images/pink-frog/profile/fac09ffc-9e4a-424c-a6fd-f41b5fee97e7/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. pink-frog.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pink-frog" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[PyTorch 실습1]]></title>
            <link>https://velog.io/@pink-frog/PyTorch-%EC%8B%A4%EC%8A%B51</link>
            <guid>https://velog.io/@pink-frog/PyTorch-%EC%8B%A4%EC%8A%B51</guid>
            <pubDate>Tue, 06 Oct 2026 06:13:36 GMT</pubDate>
            <description><![CDATA[<h1 id="pytorch-실습-텍스트-전처리부터-모델-학습과-평가까지">[PyTorch 실습] 텍스트 전처리부터 모델 학습과 평가까지</h1>
<p>오늘은 NLP 태스크의 입력과 출력을 살펴보고, PyTorch로 데이터를 준비하고 모델을 학습하는 기본 흐름을 실습했다.</p>
<p>전체 과정은 다음과 같다.</p>
<pre><code class="language-text">데이터 준비
→ 전처리
→ Dataset과 DataLoader 구성
→ 모델 예측
→ 손실 계산
→ 역전파
→ 가중치 업데이트
→ 성능 평가</code></pre>
<h2 id="1-태스크에-따른-입력-x와-정답-y">1. 태스크에 따른 입력 X와 정답 Y</h2>
<p>모델을 구현하기 전에 <strong>입력 X와 정답 Y가 어떤 형태인지</strong> 먼저 생각해야 한다.</p>
<table>
<thead>
<tr>
<th>태스크</th>
<th>입력 X</th>
<th>정답 Y</th>
</tr>
</thead>
<tbody><tr>
<td>이미지 캡셔닝</td>
<td>이미지</td>
<td>이미지를 설명하는 문장</td>
</tr>
<tr>
<td>추출형 QA</td>
<td>문맥과 질문</td>
<td>문맥 안의 정답 구간</td>
</tr>
<tr>
<td>생성형 QA</td>
<td>문맥과 질문</td>
<td>문맥을 근거로 생성한 답변</td>
</tr>
</tbody></table>
<h3 id="이미지-캡셔닝">이미지 캡셔닝</h3>
<p>이미지를 보고 내용을 설명하는 문장을 생성하는 작업이다.</p>
<p>Y에는 다음과 같은 정보가 담길 수 있다.</p>
<ul>
<li>사람의 수와 배치</li>
<li>옷차림</li>
<li>포즈와 행동</li>
<li>배경과 주변 사물</li>
<li>사람과 사물 사이의 관계</li>
</ul>
<p>모든 정보를 나열하기보다는 <strong>이미지에서 중요한 내용을 정확하고 자연스럽게 설명</strong>해야 한다.</p>
<pre><code class="language-text">이미지 입력
→ 시각적 특징 추출
→ 대상·속성·행동·관계에 관한 정보 활용
→ 설명 문장 생성</code></pre>
<p>이미지 한 장에 정답 문장 5개가 연결되어 있어도, 모델이 반드시 5개를 출력해야 하는 것은 아니다. 여러 문장은 같은 장면을 설명하는 다양한 정답 표현이다.</p>
<h3 id="추출형-qa">추출형 QA</h3>
<p>주어진 문맥에서 질문의 답을 그대로 추출하는 작업이다.</p>
<pre><code class="language-text">문맥: 민수는 2023년 부산에서 열린 대회에 참가했다.
질문: 대회가 열린 장소는 어디인가?
정답: 부산</code></pre>
<p>일반적인 추출형 QA에서 모델의 Y는 <strong>정답의 시작 토큰 위치와 끝 토큰 위치</strong>다.</p>
<pre><code class="language-text">문맥과 질문 입력
→ 질문과 관련된 내용 파악
→ 정답 시작·끝 위치 예측
→ 해당 구간의 텍스트 추출</code></pre>
<h3 id="생성형-qa">생성형 QA</h3>
<p>문맥을 근거로 답변을 새롭게 생성하는 작업이다.</p>
<pre><code class="language-text">문맥: 오늘은 버스 운행이 중단되어 지수가 지하철을 이용했다.
질문: 지수는 왜 지하철을 이용했는가?
정답: 버스 운행이 중단되었기 때문이다.</code></pre>
<p>문맥의 표현을 바꿔 쓰거나 여러 정보를 종합할 수 있지만, <strong>문맥에서 뒷받침되지 않는 내용을 임의로 추가해서는 안 된다.</strong></p>
<pre><code class="language-text">문맥과 질문 입력
→ 관련 근거 파악
→ 필요한 정보 종합
→ 답변을 토큰 단위로 생성</code></pre>
<h2 id="2-텍스트-전처리-토큰화와-패딩">2. 텍스트 전처리: 토큰화와 패딩</h2>
<p>모델이 텍스트를 처리하려면 문자열을 숫자로 변환해야 한다.</p>
<pre><code class="language-python">import torch
from torch.nn.utils.rnn import pad_sequence

sentences = [
    &quot;I love banana.&quot;,
    &quot;I also love NLP&quot;,
    &quot;NLP is interesting&quot;,
    &quot;I am studying NLP&quot;,
    &quot;NLP is fun&quot;
]

# 공백을 기준으로 문장을 나누기
tokenized_sentences = [
    sentence.split() for sentence in sentences
]

# 단어별 ID 부여: 0은 패딩용으로 예약
vocab = {}

for sentence in tokenized_sentences:
    for word in sentence:
        if word not in vocab:
            vocab[word] = len(vocab) + 1

# 단어를 ID로 변환
indexed_sentences = [
    [vocab[word] for word in sentence]
    for sentence in tokenized_sentences
]

# 정수 텐서로 변환
tensor_sentences = [
    torch.tensor(sentence, dtype=torch.long)
    for sentence in indexed_sentences
]

# 가장 긴 문장에 맞춰 패딩
padded_sentences = pad_sequence(
    tensor_sentences,
    batch_first=True,
    padding_value=0
)

print(padded_sentences)</code></pre>
<p>출력은 다음과 같다.</p>
<pre><code class="language-text">tensor([[ 1,  2,  3,  0],
        [ 1,  4,  2,  5],
        [ 5,  6,  7,  0],
        [ 1,  8,  9,  5],
        [ 5,  6, 10,  0]])</code></pre>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li><strong>토큰화:</strong> 문장을 작은 단위로 나누는 과정</li>
<li><strong>단어 사전:</strong> 단어와 숫자 ID의 대응 관계</li>
<li><strong>패딩:</strong> 길이가 다른 문장들을 동일한 길이로 맞추는 작업</li>
<li><strong><code>batch_first=True</code>:</strong> 배치 차원을 맨 앞에 배치</li>
</ul>
<p>결과 텐서의 크기는 다음과 같다.</p>
<pre><code class="language-text">(문장 수, 최대 토큰 수) = (5, 4)</code></pre>
<p>단어 ID는 식별자이므로 숫자의 크기에 의미나 중요도가 담겨 있지는 않다.</p>
<p>또한 <code>split()</code>은 공백만 기준으로 나누므로 <code>&quot;banana.&quot;</code>처럼 문장부호가 붙은 표현도 하나의 토큰으로 처리된다.</p>
<h2 id="3-임베딩-단어-id를-벡터로-변환하기">3. 임베딩: 단어 ID를 벡터로 변환하기</h2>
<p>임베딩은 단어 ID를 <strong>학습 가능한 실수 벡터</strong>로 바꾼다.</p>
<pre><code class="language-python">import torch.nn as nn

embedding_dim = 8
vocab_size = len(vocab) + 1

embedding_layer = nn.Embedding(
    vocab_size,
    embedding_dim,
    padding_idx=0
)

embedded_sentences = embedding_layer(padded_sentences)

print(embedded_sentences.shape)</code></pre>
<p>출력:</p>
<pre><code class="language-text">torch.Size([5, 4, 8])</code></pre>
<table>
<thead>
<tr>
<th>차원</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>5</td>
<td>문장 수</td>
</tr>
<tr>
<td>4</td>
<td>패딩을 포함한 문장당 토큰 수</td>
</tr>
<tr>
<td>8</td>
<td>토큰 하나의 임베딩 차원</td>
</tr>
</tbody></table>
<pre><code class="language-text">단어 ID: (5, 4)
→ 임베딩
단어 벡터: (5, 4, 8)</code></pre>
<p><code>nn.Embedding</code>은 단어 ID에 해당하는 행을 찾아 벡터를 꺼내는 표처럼 동작한다. 같은 ID는 같은 벡터를 받는다.</p>
<p><code>padding_idx=0</code>을 지정하면 기본 초기화에서 패딩 벡터가 0으로 채워지고, 임베딩의 역전파에서 해당 행의 기울기가 발생하지 않는다.</p>
<p>다만 이것만으로 이후 모델이 패딩을 자동으로 무시하는 것은 아니다. 모델에 따라 별도의 마스크가 필요할 수 있다.</p>
<blockquote>
<p>처음 생성한 임베딩은 무작위 값이다. 학습 과정에서 손실을 줄이는 방향으로 조정된다.</p>
</blockquote>
<h2 id="4-경사하강법-손실을-줄이는-방향으로-이동하기">4. 경사하강법: 손실을 줄이는 방향으로 이동하기</h2>
<p>경사하강법은 <strong>기울기의 반대 방향으로 이동하면서 함수값을 줄이는 방법</strong>이다.</p>
<p>실습에서 사용한 함수는 다음과 같다.</p>
<pre><code class="language-python">def f(x):
    return x**4 - 3 * x**3

def f_prime(x):
    return 4 * x**3 - 9 * x**2</code></pre>
<p>업데이트는 다음 코드로 이루어진다.</p>
<pre><code class="language-python">x_new = x_old - learning_rate * f_prime(x_old)</code></pre>
<ul>
<li>기울기가 양수이면 왼쪽으로 이동한다.</li>
<li>기울기가 음수이면 오른쪽으로 이동한다.</li>
<li>학습률은 기울기를 이동량으로 바꾸는 비율이다.</li>
</ul>
<p>시작점이 <code>6</code>, 학습률이 <code>0.01</code>이면 다음과 같다.</p>
<pre><code class="language-text">f_prime(6) = 540
x_new = 6 - 0.01 × 540 = 0.6</code></pre>
<p>첫 이동은 <code>6 → 0.6</code>이며, 이후 업데이트를 반복하면 최솟값을 갖는 위치인 <code>x=2.25</code> 근처로 이동한다.</p>
<p>신경망에서도 같은 원리를 사용한다. 숫자 하나 대신 여러 가중치를 업데이트하고, 함수값 대신 손실을 줄인다.</p>
<blockquote>
<p>이동량이 작아졌다는 것이 항상 최솟값에 도달했다는 뜻은 아니다. 실제 최적화에서는 최대 반복 횟수 등의 종료 조건도 함께 고려한다.</p>
</blockquote>
<h2 id="5-손실-함수">5. 손실 함수</h2>
<p>손실 함수는 <strong>모델의 예측과 정답 사이의 차이를 수치로 나타내는 함수</strong>다.</p>
<h3 id="mseloss">MSELoss</h3>
<p>평균제곱오차는 예측과 정답의 차이를 제곱한 뒤 평균을 구한다.</p>
<pre><code class="language-python">prediction = torch.tensor([[2.0], [3.0], [4.0]])
ground_truth = torch.tensor([[2.5], [3.5], [5.0]])

criterion = nn.MSELoss()
loss = criterion(prediction, ground_truth)

print(f&quot;손실 값: {loss.item()}&quot;)</code></pre>
<p>출력:</p>
<pre><code class="language-text">손실 값: 0.5</code></pre>
<p>계산 과정은 다음과 같다.</p>
<pre><code class="language-text">예측과 정답의 차이: -0.5, -0.5, -1.0
차이의 제곱:         0.25, 0.25, 1.00
평균:               (0.25 + 0.25 + 1.00) / 3 = 0.5</code></pre>
<p>MSE는 수치 값을 예측하는 회귀 문제에서 사용하는 대표적인 손실 함수다.</p>
<h3 id="crossentropyloss">CrossEntropyLoss</h3>
<p>교차 엔트로피는 정답 클래스에 얼마나 높은 확률을 부여하는지 평가한다.</p>
<pre><code class="language-python">logits = torch.tensor([
    [0.8, 0.2],
    [0.4, 0.6],
    [0.9, 0.1]
])

# 강아지: 0, 고양이: 1
labels = torch.tensor([0, 1, 0])

criterion = nn.CrossEntropyLoss()
loss = criterion(logits, labels)

print(f&quot;손실 값: {loss.item():.4f}&quot;)</code></pre>
<p>출력:</p>
<pre><code class="language-text">손실 값: 0.4689</code></pre>
<p><strong><code>CrossEntropyLoss</code>에는 Softmax를 적용하기 전의 점수인 로짓을 입력해야 한다.</strong></p>
<p>위 코드의 <code>[0.8, 0.2]</code>도 80%, 20%의 확률이 아니라 두 클래스의 점수로 해석된다. 내부적으로 <code>log_softmax</code>를 적용해 손실을 계산한다.</p>
<p>정답은 <code>0</code>, <code>1</code> 같은 클래스 번호로 전달한다.</p>
<blockquote>
<p>가장 높은 점수의 클래스를 기준으로 모두 맞혔더라도 손실은 0이 아닐 수 있다. 교차 엔트로피는 정답 여부뿐 아니라 정답에 부여한 확률도 반영한다.</p>
</blockquote>
<h2 id="6-dataset-데이터를-제공하는-규칙">6. Dataset: 데이터를 제공하는 규칙</h2>
<p><code>Dataset</code>은 데이터의 개수와 특정 데이터를 가져오는 방법을 정의한다.</p>
<pre><code class="language-python">from torch.utils.data import Dataset

class CustomDataset(Dataset):
    def __init__(self, data):
        self.data = data

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

dataset = CustomDataset([1, 2, 3, 4, 5])

print(dataset[0])
print(len(dataset))</code></pre>
<p>출력:</p>
<pre><code class="language-text">1
5</code></pre>
<table>
<thead>
<tr>
<th>메서드</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>__init__</code></td>
<td>데이터와 설정 저장</td>
</tr>
<tr>
<td><code>__len__</code></td>
<td>데이터 개수 반환</td>
</tr>
<tr>
<td><code>__getitem__</code></td>
<td>해당 인덱스의 데이터 반환</td>
</tr>
</tbody></table>
<p>실제 지도학습에서는 보통 <code>__getitem__</code>이 입력과 정답을 함께 반환한다.</p>
<pre><code class="language-python">return inputs, labels</code></pre>
<h2 id="7-iterabledataset-데이터를-순서대로-제공하기">7. IterableDataset: 데이터를 순서대로 제공하기</h2>
<p><code>IterableDataset</code>은 인덱스로 데이터를 선택하기보다 순회하면서 데이터를 제공하는 방식이다.</p>
<pre><code class="language-python">from torch.utils.data import IterableDataset

class CustomIterableDataset(IterableDataset):
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __iter__(self):
        return iter(range(self.start, self.end))

dataset = CustomIterableDataset(start=1, end=6)

for data in dataset:
    print(data)</code></pre>
<p>출력:</p>
<pre><code class="language-text">1
2
3
4
5</code></pre>
<p><code>range(1, 6)</code>에서 끝값 <code>6</code>은 제외된다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>일반적인 Dataset</th>
<th>IterableDataset</th>
</tr>
</thead>
<tbody><tr>
<td>핵심 메서드</td>
<td><code>__getitem__</code>, <code>__len__</code></td>
<td><code>__iter__</code></td>
</tr>
<tr>
<td>접근 방식</td>
<td>인덱스로 선택</td>
<td>순회하며 다음 데이터 받기</td>
</tr>
<tr>
<td>활용 예</td>
<td>저장된 이미지·문장</td>
<td>스트리밍·큰 파일의 순차 읽기</td>
</tr>
</tbody></table>
<p>이 예시에서는 <code>__getitem__</code>과 <code>__len__</code>을 구현하지 않았으므로 <code>dataset[0]</code>과 <code>len(dataset)</code>을 사용할 수 없다.</p>
<h2 id="8-dataloader-데이터를-배치로-묶기">8. DataLoader: 데이터를 배치로 묶기</h2>
<p><code>DataLoader</code>는 데이터셋의 샘플들을 여러 개씩 묶어 제공한다. 이 묶음을 <strong>배치(batch)</strong>라고 한다.</p>
<p>앞서 만든 <code>IterableDataset</code>에는 다음처럼 사용할 수 있다.</p>
<pre><code class="language-python">from torch.utils.data import DataLoader

train_loader = DataLoader(
    dataset,
    batch_size=2
)

for batch_idx, data in enumerate(train_loader):
    print(f&quot;Batch {batch_idx + 1}: {data}&quot;)</code></pre>
<p>출력:</p>
<pre><code class="language-text">Batch 1: tensor([1, 2])
Batch 2: tensor([3, 4])
Batch 3: tensor([5])</code></pre>
<p>데이터가 5개이므로 <code>2개 → 2개 → 1개</code>로 나뉜다.</p>
<p>기본 설정에서는 마지막의 작은 배치도 반환한다.</p>
<h3 id="주의할-점">주의할 점</h3>
<ul>
<li>일반적인 인덱스 기반 Dataset에서는 <code>shuffle=True</code>로 순서를 섞을 수 있다.</li>
<li><code>IterableDataset</code>에서는 <code>shuffle=True</code>를 지원하지 않는다.</li>
<li><code>for inputs, labels in train_loader</code>를 사용하려면 데이터셋이 <code>(입력, 정답)</code> 쌍을 반환해야 한다.</li>
</ul>
<blockquote>
<p>Dataset은 데이터를 제공하는 방법을 정의하고, DataLoader는 데이터를 배치로 묶어 가져온다.</p>
</blockquote>
<h2 id="9-vgg16-이미지를-분류하는-모델">9. VGG16: 이미지를 분류하는 모델</h2>
<p>VGG16은 이미지에서 특징을 추출해 어떤 클래스에 속하는지 예측하는 CNN 모델이다.</p>
<pre><code class="language-text">이미지 입력
→ 합성곱으로 특징 추출
→ ReLU
→ 풀링으로 공간 크기 축소
→ 위 과정을 반복
→ 완전연결층
→ 클래스 점수 출력</code></pre>
<p>모델을 통과하면서 가로·세로 크기는 줄어들고 채널 수는 증가한다.</p>
<p>아래 크기는 각 블록의 풀링을 거친 결과다.</p>
<table>
<thead>
<tr>
<th>단계</th>
<th>높이 × 너비 × 채널</th>
</tr>
</thead>
<tbody><tr>
<td>입력</td>
<td>224 × 224 × 3</td>
</tr>
<tr>
<td>블록 1 이후</td>
<td>112 × 112 × 64</td>
</tr>
<tr>
<td>블록 2 이후</td>
<td>56 × 56 × 128</td>
</tr>
<tr>
<td>블록 3 이후</td>
<td>28 × 28 × 256</td>
</tr>
<tr>
<td>블록 4 이후</td>
<td>14 × 14 × 512</td>
</tr>
<tr>
<td>블록 5 이후</td>
<td>7 × 7 × 512</td>
</tr>
</tbody></table>
<p>PyTorch 텐서는 보통 다음 순서를 사용한다.</p>
<pre><code class="language-text">(배치 크기, 채널 수, 높이, 너비)</code></pre>
<p>따라서 마지막 특징 텐서는 <code>(B, 512, 7, 7)</code> 형태다.</p>
<p>이를 펼치면 샘플당 <code>25,088</code>개의 값이 된다.</p>
<pre><code class="language-text">7 × 7 × 512 = 25,088</code></pre>
<p>완전연결층은 다음 크기로 이어진다.</p>
<pre><code class="language-text">25,088 → 4,096 → 4,096 → 1,000</code></pre>
<p>마지막 <code>1,000</code>은 원래 VGG16이 분류하는 ImageNet 클래스 수다. 강아지·고양이 분류처럼 다른 작업에서는 마지막 출력 크기를 바꿀 수 있다.</p>
<p>VGG16의 <code>16</code>은 <strong>합성곱 층 13개와 완전연결층 3개</strong>를 합한 개수다.</p>
<p>이미지 캡셔닝에서는 VGG16의 특징 추출 부분을 이미지 인코더로 활용하고, 추출한 특징을 별도의 문장 생성 모델에 전달할 수 있다.</p>
<h2 id="10-gpu에서-학습하기">10. GPU에서 학습하기</h2>
<p>모델과 입력, 정답은 같은 장치에 있어야 한다.</p>
<pre><code class="language-python">device = torch.device(
    &quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;
)

model = model.to(device)

optimizer = torch.optim.Adam(
    model.parameters(),
    lr=0.001
)

criterion = nn.CrossEntropyLoss()</code></pre>
<p>CUDA GPU를 사용할 수 있으면 GPU를 선택하고, 그렇지 않으면 CPU를 선택한다.</p>
<p>모델을 장치로 옮긴 다음 옵티마이저를 생성하는 것이 좋다.</p>
<pre><code class="language-python">inputs = inputs.to(device)
labels = labels.to(device)</code></pre>
<p>입력과 정답도 같은 장치로 옮겨야 한다.</p>
<pre><code class="language-text">모델: GPU
입력: GPU
정답: GPU</code></pre>
<p>모델과 입력의 장치가 다르면 일반적으로 장치 불일치 오류가 발생한다.</p>
<h2 id="11-모델-학습과-검증">11. 모델 학습과 검증</h2>
<p>아래 코드는 <strong>샘플마다 정답 클래스가 하나인 분류 작업</strong>을 위한 예시다.</p>
<p>기본 평균 방식의 <code>CrossEntropyLoss</code>와 비어 있지 않은 DataLoader를 전제로 한다.</p>
<pre><code class="language-python">def train_model(
    model,
    train_loader,
    val_loader,
    optimizer,
    criterion,
    device,
    num_epochs=10
):
    for epoch in range(num_epochs):
        # 학습
        model.train()
        total_loss = 0.0
        total_samples = 0

        for inputs, labels in train_loader:
            inputs = inputs.to(device)
            labels = labels.to(device)

            optimizer.zero_grad()

            outputs = model(inputs)
            loss = criterion(outputs, labels)

            loss.backward()
            optimizer.step()

            batch_size = labels.size(0)
            total_loss += loss.item() * batch_size
            total_samples += batch_size

        train_loss = total_loss / total_samples

        # 검증
        model.eval()
        val_loss = 0.0
        correct = 0
        total = 0

        with torch.no_grad():
            for inputs, labels in val_loader:
                inputs = inputs.to(device)
                labels = labels.to(device)

                outputs = model(inputs)
                loss = criterion(outputs, labels)

                batch_size = labels.size(0)
                val_loss += loss.item() * batch_size

                predicted = outputs.argmax(dim=1)

                correct += (
                    predicted == labels
                ).sum().item()

                total += batch_size

        print(
            f&quot;Epoch [{epoch + 1}/{num_epochs}] &quot;
            f&quot;Train Loss: {train_loss:.4f}, &quot;
            f&quot;Validation Loss: {val_loss / total:.4f}, &quot;
            f&quot;Accuracy: {100 * correct / total:.2f}%&quot;
        )</code></pre>
<p>이 함수에는 미리 준비한 모델과 <code>(입력, 정답)</code>을 반환하는 학습·검증 DataLoader를 전달해야 한다.</p>
<h3 id="에포크란">에포크란?</h3>
<p><strong>에포크(epoch)</strong>는 학습 데이터를 한 번 모두 순회하는 단위다.</p>
<p><code>num_epochs=10</code>이면 전체 학습 데이터를 10번 반복해서 사용한다.</p>
<h3 id="학습의-핵심-다섯-단계">학습의 핵심 다섯 단계</h3>
<pre><code class="language-python">optimizer.zero_grad()             # 이전 기울기 초기화
outputs = model(inputs)           # 예측
loss = criterion(outputs, labels) # 손실 계산
loss.backward()                  # 기울기 계산
optimizer.step()                 # 가중치 업데이트</code></pre>
<p>특히 다음 차이를 기억해야 한다.</p>
<ul>
<li><code>loss.backward()</code>: 각 파라미터의 기울기를 계산한다.</li>
<li><code>optimizer.step()</code>: 계산한 기울기로 파라미터를 수정한다.</li>
</ul>
<h3 id="평균-손실-계산">평균 손실 계산</h3>
<pre><code class="language-python">total_loss += loss.item() * batch_size
train_loss = total_loss / total_samples</code></pre>
<p>배치 크기를 반영해 누적하면 마지막 배치가 작아도 샘플 수를 반영한 평균 손실을 구할 수 있다.</p>
<h2 id="12-학습-모드와-평가-모드">12. 학습 모드와 평가 모드</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>학습</th>
<th>평가</th>
</tr>
</thead>
<tbody><tr>
<td>모델 모드</td>
<td><code>model.train()</code></td>
<td><code>model.eval()</code></td>
</tr>
<tr>
<td>기울기 계산</td>
<td>필요</td>
<td>일반적으로 불필요</td>
</tr>
<tr>
<td>역전파</td>
<td>수행</td>
<td>수행하지 않음</td>
</tr>
<tr>
<td>가중치 업데이트</td>
<td>수행</td>
<td>수행하지 않음</td>
</tr>
</tbody></table>
<p><code>model.eval()</code>은 Dropout과 BatchNorm 같은 층의 동작을 평가용으로 바꾼다.</p>
<p>하지만 기울기 계산 자체를 끄지는 않는다. 따라서 평가에서는 다음을 함께 사용한다.</p>
<pre><code class="language-python">model.eval()

with torch.no_grad():
    outputs = model(inputs)</code></pre>
<h2 id="13-정확도-계산하기">13. 정확도 계산하기</h2>
<p>분류 정확도는 가장 높은 점수를 가진 클래스와 실제 정답을 비교해서 구한다.</p>
<pre><code class="language-python">predicted = outputs.argmax(dim=1)

correct = (predicted == labels).sum().item()
accuracy = correct / labels.size(0)</code></pre>
<p>예를 들어 다음과 같다면:</p>
<pre><code class="language-text">예측: [0, 1, 1]
정답: [0, 1, 0]</code></pre>
<p>3개 중 2개를 맞혔으므로 정확도는 약 <code>0.6667</code>, 즉 <code>66.67%</code>다.</p>
<pre><code class="language-python">print(f&quot;Accuracy: {accuracy * 100:.2f}%&quot;)</code></pre>
<p>가장 높은 점수의 클래스를 선택하는 데는 Softmax를 적용할 필요가 없다.</p>
<h2 id="14-오늘-실습에서-배운-점">14. 오늘 실습에서 배운 점</h2>
<ul>
<li>태스크마다 입력 X와 정답 Y의 형태가 다르다.</li>
<li>텍스트는 토큰화, ID 변환, 패딩, 임베딩을 거쳐 모델 입력이 된다.</li>
<li>손실 함수는 예측과 정답 사이의 오차를 수치로 나타낸다.</li>
<li>Dataset은 데이터를 제공하고, DataLoader는 데이터를 배치로 묶는다.</li>
<li>학습은 예측, 손실 계산, 역전파, 가중치 업데이트를 반복하는 과정이다.</li>
<li>평가에서는 가중치를 수정하지 않고 성능을 확인한다.</li>
<li>GPU를 사용할 때는 모델과 입력, 정답의 장치를 맞춰야 한다.</li>
</ul>
<p>이번 실습을 통해 개별 코드가 <strong>데이터 준비에서 학습과 평가까지 이어지는 하나의 흐름</strong>이라는 점을 이해했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day4]]></title>
            <link>https://velog.io/@pink-frog/Day4</link>
            <guid>https://velog.io/@pink-frog/Day4</guid>
            <pubDate>Tue, 06 Oct 2026 05:32:50 GMT</pubDate>
            <description><![CDATA[<h1 id="til-nlp-task-설계와-llm의-학습·생성-흐름">[TIL] NLP Task 설계와 LLM의 학습·생성 흐름</h1>
<p>📅 2026.10.06</p>
<p>오늘은 <strong>NLP Task에 맞게 입력과 출력을 설계하는 방법</strong>, 그리고 <strong>언어 모델이 텍스트를 처리하고 생성하는 과정</strong>을 배웠다. 사전학습 모델을 활용하고 미세조정하는 이유도 함께 정리했다.</p>
<hr>
<h2 id="1-자연어와-nlp-task">1. 자연어와 NLP Task</h2>
<p>자연어는 한국어·영어처럼 사람들이 의사소통하며 자연스럽게 형성한 언어다. <strong>중의성, 문맥 의존성, 표현의 다양성, 생략</strong> 등의 특징이 있다.</p>
<p>“배가 크다”는 여러 의미로 해석할 수 있고, “여기 춥네”는 상황에 따라 창문을 닫아 달라는 요청일 수도 있다.</p>
<p>NLP는 이런 언어의 <strong>구조와 의미를 분석·변환·생성하는 분야</strong>다. 컴퓨터 맥락에서는 명시적인 기호와 문법 규칙으로 정의한 형식 언어와 대비한다.</p>
<h3 id="task를-이해해야-하는-이유">Task를 이해해야 하는 이유</h3>
<p>같은 텍스트라도 목표에 따라 필요한 결과가 달라진다.</p>
<ul>
<li><strong>감정 분석:</strong> 리뷰 → 긍정·중립·부정</li>
<li><strong>개체명 인식:</strong> 문장 → 사람·장소·기관 등의 이름과 종류</li>
<li><strong>번역:</strong> 원문 → 다른 언어의 문장</li>
<li><strong>요약:</strong> 긴 문서 → 핵심 내용을 담은 짧은 글</li>
<li><strong>질문 답변:</strong> 질문과 관련 문서 → 답변</li>
<li><strong>검색:</strong> 검색어 → 관련 문서 목록</li>
</ul>
<p>Task를 알아야 <strong>데이터, 출력 형식, 학습 목표, 평가 기준</strong>을 정할 수 있다. Transformer나 LLM은 도구이고, Task는 그 도구가 해결해야 할 문제다.</p>
<h3 id="nlu와-nlg">NLU와 NLG</h3>
<ul>
<li><strong>NLU:</strong> 언어의 의미와 의도를 파악하는 것</li>
<li><strong>NLG:</strong> 전달할 내용을 자연어로 표현하는 것</li>
</ul>
<p>“NLG는 NLU가 전제된다”는 말은 적절한 생성에 내용과 맥락의 파악이 중요하다는 뜻이다. 별도의 이해 모듈이 반드시 필요한 것은 아니며, 숫자 데이터를 문장으로 바꾸는 생성도 가능하다.</p>
<p>여러 NLP Task를 <strong>입력 → 출력 텍스트</strong>라는 생성 문제로 표현할 수 있다. 하지만 유창한 출력이 사실성이나 정확한 이해를 보장하지는 않는다. 원문 왜곡, 형식 위반, 오류 누적과 평가의 어려움도 고려해야 한다.</p>
<hr>
<h2 id="2-텍스트-처리-파이프라인">2. 텍스트 처리 파이프라인</h2>
<p>모델에 텍스트를 넣을 때는 다음 흐름을 거친다.</p>
<p><strong>텍스트 → 토큰화 → 토큰 ID → 임베딩 벡터 → 문맥 처리 → 출력</strong></p>
<ul>
<li><strong>Tokenizing:</strong> 텍스트를 토큰으로 나눈다. 토큰은 단어뿐 아니라 서브워드·문자·바이트일 수도 있다.</li>
<li><strong>Encoding:</strong> 모델이 사용할 표현으로 바꾼다. 토크나이저에서는 주로 토큰을 ID로 변환하는 것을 뜻한다.</li>
<li><strong>Vectorization:</strong> 텍스트를 숫자 배열로 표현한다. TF-IDF와 학습된 임베딩 등이 있다.</li>
</ul>
<p><strong>토큰 ID는 식별 번호이고, 임베딩은 계산에 사용하는 벡터다.</strong> 번호가 가깝다고 의미가 가까운 것은 아니다. Transformer에서는 기본 임베딩을 이후 층들이 문맥에 맞게 갱신한다.</p>
<h3 id="inference와-decoding">Inference와 Decoding</h3>
<p><strong>Inference는 학습된 모델에 새 입력을 넣어 결과를 얻는 과정</strong>이다. 일반적으로 파라미터를 바꾸지 않는다.</p>
<p>Decoding은 두 의미로 사용된다.</p>
<ol>
<li>확률을 바탕으로 다음 토큰을 선택하는 과정</li>
<li>토큰 ID를 사람이 읽는 텍스트로 복원하는 과정</li>
</ol>
<p>Greedy는 가장 높은 확률의 토큰을 선택하고, Sampling은 확률 분포에 따라 토큰을 뽑는다. <code>tokenizer.decode()</code>는 보통 두 번째 의미다.</p>
<hr>
<h2 id="3-정답과-모델-출력-설계">3. 정답과 모델 출력 설계</h2>
<p>모델을 설계할 때는 다음 순서로 생각할 수 있다.</p>
<p><strong>Task 정의 → 정답 Y 설계 → 모델 출력 설계 → 손실 함수 설계 → 모델 설계</strong></p>
<h3 id="classification과-regression">Classification과 Regression</h3>
<ul>
<li><strong>Classification:</strong> 정해진 범주를 선택한다. 감정 분석은 부정·중립·긍정 중 하나를 예측한다.</li>
<li><strong>Regression:</strong> 숫자의 크기와 차이가 의미 있는 값을 예측한다. 만족도 4.2점이나 예상 기온이 예시다.</li>
</ul>
<p>분류의 정답이 <code>긍정=2</code>라고 해서 모델이 숫자 2를 직접 출력해야 하는 것은 아니다. <strong>범주별 점수 3개를 출력한 뒤 확률로 변환하고, 가장 높은 범주를 선택</strong>할 수 있다.</p>
<p>즉, 정답 Y의 형태와 모델의 직접 출력 형태는 다를 수 있다.</p>
<h3 id="summarization이라면">Summarization이라면?</h3>
<p><strong>생성형 요약</strong>의 정답은 요약문의 토큰 ID 시퀀스다. 모델은 각 생성 위치에서 <strong>다음 토큰 후보 전체에 대한 점수</strong>를 출력한다.</p>
<p><strong>원문 + 지금까지의 요약 → 다음 토큰 확률 → 토큰 선택 → 반복</strong></p>
<p>각 단계는 토큰 분류처럼 볼 수 있지만, 전체 Task는 <strong>가변 길이 시퀀스 생성</strong>이다. 학습에는 보통 정답 토큰에 대한 교차 엔트로피를 사용한다.</p>
<p>반면 <strong>추출형 요약</strong>은 원문에서 중요한 문장을 고르므로, 문장별 선택 여부나 중요도 점수를 출력하도록 설계할 수 있다.</p>
<hr>
<h2 id="4-next-token-prediction">4. Next Token Prediction</h2>
<p>GPT 같은 자기회귀 모델은 다음 토큰을 예측하는 과정을 반복해 문장을 생성한다.</p>
<p><strong>문맥 벡터 → Logits → Softmax → 확률 분포 → 토큰 선택</strong></p>
<ul>
<li><strong>Logits:</strong> 확률로 변환하기 전의 후보별 점수</li>
<li><strong>Softmax:</strong> 점수들을 합이 1인 확률로 변환</li>
<li><strong>확률 분포:</strong> 어휘 사전의 각 토큰이 다음에 나올 확률</li>
</ul>
<p>출력층의 계산은 다음처럼 표현할 수 있다.</p>
<p><strong>p = softmax(Wh + b)</strong></p>
<p>여기서 h는 모델이 계산한 문맥 표현이고, W와 b는 학습된 파라미터다. 어휘 크기가 V라면 한 단계의 출력 점수는 V개다.</p>
<p>학습에서는 <strong>정답 토큰의 확률을 높이도록 파라미터를 조정</strong>하고, 추론에서는 <strong>확률을 바탕으로 토큰을 선택</strong>한다.</p>
<p><strong>Softmax는 확률을 계산하고, 디코딩은 출력 토큰을 선택한다.</strong></p>
<hr>
<h2 id="5-lstm과-transformer">5. LSTM과 Transformer</h2>
<h3 id="lstm">LSTM</h3>
<p>LSTM은 <strong>Cell State와 Hidden State</strong>, 그리고 게이트를 사용해 이전 정보를 전달하는 RNN 구조다.</p>
<ul>
<li><strong>Forget Gate:</strong> 이전 기억을 얼마나 유지할지 결정</li>
<li><strong>Input Gate:</strong> 새 정보 후보를 얼마나 반영할지 결정</li>
<li><strong>Output Gate:</strong> 기억을 현재 출력 표현에 얼마나 드러낼지 결정</li>
</ul>
<p>핵심은 다음 관계다.</p>
<p><strong>새 기억 = 유지한 기존 기억 + 선택한 새 정보</strong></p>
<p>게이트는 현재 입력과 이전 은닉 상태를 바탕으로 학습된다. 장기 의존성 학습을 돕지만 모든 정보를 무한히 기억하는 것은 아니다. 은닉 상태 자체가 토큰 확률은 아니며, 별도의 출력층에서 확률을 계산한다.</p>
<h3 id="transformer">Transformer</h3>
<p>Transformer는 <strong>Self-Attention으로 토큰 간 관계를 계산해 문맥 표현을 만드는 구조</strong>다.</p>
<p>순환 계산 없이 토큰 위치의 계산을 병렬화하기 유리해 대규모 학습에 도움이 됐다. 이후 BERT와 GPT 계열을 통해 <strong>사전학습 모델의 재사용, 미세조정, 프롬프트 기반 Task 수행</strong>이 확산됐다.</p>
<p>다만 사전학습과 문맥 기반 표현이 Transformer에서 처음 시작된 것은 아니다. 또한 학습을 병렬화할 수 있어도, 자기회귀 생성은 보통 토큰을 하나씩 만든다.</p>
<hr>
<h2 id="6-pretrained-model과-fine-tuning">6. Pretrained Model과 Fine-tuning</h2>
<p><strong>Pretrained Model은 대량의 데이터로 미리 학습된 모델</strong>이다. 이미 학습한 언어 표현과 패턴을 새로운 Task에 활용한다.</p>
<ul>
<li><strong>BERT:</strong> 가려진 토큰 예측</li>
<li><strong>GPT 계열:</strong> 다음 토큰 예측</li>
</ul>
<p>이를 사용하면 처음부터 학습하는 것보다 <strong>Task 데이터, 시간, 연산 비용을 줄일 수 있다.</strong></p>
<p>LLM에서는 사전학습을 마친 기본 모델을 Base Model이라고 부른다. 여기에 지시·대화 데이터로 추가 학습해 요청을 따르도록 조정할 수 있다.</p>
<h3 id="fine-tuning">Fine-tuning</h3>
<p>사전학습 모델을 특정 Task나 분야의 데이터로 추가 학습해 <strong>전체 또는 일부 파라미터를 조정</strong>하는 과정이다.</p>
<p>예: 사전학습 모델 → 원문·정답 요약문으로 추가 학습 → 요약에 맞게 조정</p>
<p>특정 Task 성능, 전문 용어 적응, 출력 형식과 문체의 일관성을 개선할 수 있다. 다만 효과를 확인하려면 별도의 평가 데이터가 필요하다.</p>
<p><strong>프롬프트는 입력 문맥을 바꾸고, 미세조정은 학습으로 파라미터를 바꾼다.</strong></p>
<hr>
<h2 id="오늘의-정리">오늘의 정리</h2>
<p>NLP 모델을 이해하려면 구조뿐 아니라 <strong>어떤 문제를 풀고, 무엇을 출력하며, 어떤 목표로 학습하는지</strong> 함께 봐야 한다.</p>
<p><strong>Task 정의 → 데이터·정답 설계 → 표현 변환 → 모델 계산 → 출력·평가</strong></p>
<p>생성 모델도 이 흐름 안에 있다. 다음 토큰 예측을 반복해 문장을 만들지만, 자연스러운 문장과 올바른 Task 수행은 구분해야 한다. 사전학습과 미세조정을 연결해 보니 모델이 무엇을 배우고 어떻게 활용되는지 더 명확해졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pytorch를 사용하여 모델 학습 파이프라인 구축하기]]></title>
            <link>https://velog.io/@pink-frog/Pytorch%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pink-frog/Pytorch%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Oct 2026 08:28:21 GMT</pubDate>
            <description><![CDATA[<h2 id="타이타닉-데이터로-pytorch-모델-만들기">타이타닉 데이터로 PyTorch 모델 만들기</h2>
<p>오늘은 타이타닉 승객 데이터를 이용해 <strong>생존 여부와 성별을 예측하는 모델</strong>을 만들었다. 아직 세부 코드를 모두 이해한 것은 아니지만, 데이터 준비부터 학습과 검증까지의 전체 흐름을 익혔다.</p>
<h3 id="1-데이터-확인과-feature-선택">1. 데이터 확인과 feature 선택</h3>
<p>각 열의 의미를 확인하고, 모델에 넣을 입력 정보와 예측할 정답을 구분했다.</p>
<ul>
<li><strong>입력(X)</strong>: 승객의 나이, 객실 등급, 운임, 동반 가족 수 등의 정보</li>
<li><strong>정답(y)</strong>: <code>survived</code>와 <code>sex</code></li>
</ul>
<p>정답으로 사용하는 열은 입력에서 제외해야 한다는 점도 배웠다.</p>
<h3 id="2-데이터-분리와-전처리">2. 데이터 분리와 전처리</h3>
<p>데이터를 <strong>train, validation, test</strong>로 나눴다. 각각 학습, 모델 선택, 최종 평가에 사용하는 데이터다.</p>
<p>모델이 계산할 수 있도록 boolean과 문자 데이터를 숫자로 변환하고, 나이의 결측값은 <strong>train에서 구한 중앙값</strong>으로 채웠다. 결측값이 많은 <code>deck</code>은 제외하기로 했다.</p>
<h3 id="3-dataset과-dataloader-생성">3. Dataset과 DataLoader 생성</h3>
<p>전처리한 데이터를 PyTorch의 <strong>Tensor</strong>로 변환했다.</p>
<p><code>TensorDataset</code>으로 입력과 정답을 묶고, <code>DataLoader</code>로 한 번에 32명씩 학습하도록 구성했다. 학습 데이터는 순서를 섞어서 사용했다.</p>
<h3 id="4-모델-구축과-학습">4. 모델 구축과 학습</h3>
<p>여러 <code>Linear</code> 층과 <code>ReLU</code>를 연결한 신경망을 만들었다. 생존 여부와 성별을 함께 예측하므로 마지막 출력은 2개로 설정했다.</p>
<p>학습은 다음 과정을 반복한다.</p>
<p><strong>예측 → 정답과 비교해 오차 계산 → 기울기 계산 → 가중치 수정</strong></p>
<p>손실함수는 <code>BCEWithLogitsLoss</code>, 최적화 도구는 <code>Adam</code>을 사용했다.</p>
<h3 id="5-결과-확인과-기록">5. 결과 확인과 기록</h3>
<p>마지막 학습 기록에서 두 예측을 합친 정확도는 약 <strong>73.7%</strong>였다. 검증 데이터에서는 다음 결과가 나왔다.</p>
<ul>
<li><strong>생존 예측 정확도: 65.17%</strong></li>
<li><strong>성별 예측 정확도: 71.91%</strong></li>
</ul>
<p>학습 정확도만으로 성능을 판단할 수 없고, 별도 데이터에서 검증해야 한다는 점을 배웠다. 또한 epoch마다 Loss와 Accuracy를 저장하고 그래프로 확인하는 방법도 살펴봤다.</p>
<p>오늘 이해한 핵심 흐름은 <strong>데이터 확인 → 전처리·분리 → Dataset·DataLoader 생성 → 모델 구축 → 학습 → 검증</strong>이다. 앞으로는 각 코드의 역할을 더 자세히 이해하고, 전처리와 모델 설정에 따라 성능이 어떻게 달라지는지 확인해 보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day3]]></title>
            <link>https://velog.io/@pink-frog/Day3-ppjvgsgf</link>
            <guid>https://velog.io/@pink-frog/Day3-ppjvgsgf</guid>
            <pubDate>Fri, 02 Oct 2026 05:00:05 GMT</pubDate>
            <description><![CDATA[<h1 id="til-nlp-언어-모델과-pytorch-학습-흐름">[TIL] NLP 언어 모델과 PyTorch 학습 흐름</h1>
<p>📅 2026.10.02</p>
<p>오늘은 <strong>언어 모델이 단어와 문맥을 처리하는 방법</strong>, 그리고 <strong>PyTorch로 모델을 학습시키는 과정</strong>을 배웠다.</p>
<hr>
<h2 id="1-확률적-언어-모델과-n-gram">1. 확률적 언어 모델과 N-gram</h2>
<p>확률적 언어 모델은 앞 문맥을 바탕으로 다음 단어의 확률을 계산한다. 전체 문장 확률은 각 단어의 조건부 확률을 곱해 구할 수 있다.</p>
<p>“나는 밥을 먹었다”의 확률:</p>
<p><strong>P(나는) × P(밥을 | 나는) × P(먹었다 | 나는, 밥을)</strong></p>
<p>N-gram은 전체 문맥 대신 최근 <strong>N−1개 토큰</strong>만 참고한다. 이전 2개로 다음 단어를 예측하면 Trigram이다.</p>
<h3 id="n-gram의-한계">N-gram의 한계</h3>
<ul>
<li><strong>데이터 희소성:</strong> 긴 조합일수록 관측 데이터가 부족하다.</li>
<li><strong>문맥 제한:</strong> 멀리 떨어진 정보를 참고하기 어렵다.</li>
<li><strong>의미적 정보 공유의 어려움:</strong> 비슷한 단어의 조합도 별도로 센다.</li>
</ul>
<hr>
<h2 id="2-임베딩과-신경망-언어-모델">2. 임베딩과 신경망 언어 모델</h2>
<p>토큰 ID는 식별 번호일 뿐 의미를 나타내지 않는다. 임베딩은 토큰을 <strong>학습 가능한 실수 벡터</strong>로 표현한다.</p>
<p>임베딩 행렬은 어휘 크기가 V, 벡터 차원이 d일 때 <strong>(V, d)</strong> 형태다. 토큰 ID에 해당하는 행에서 벡터를 가져온다.</p>
<h3 id="분포-가설">분포 가설</h3>
<blockquote>
<p>비슷한 문맥에 등장하는 단어들은 의미적으로 관련될 가능성이 높다.</p>
</blockquote>
<p>“차를 마시다”, “커피를 마시다”, “우유를 마시다”처럼 주변 표현이 비슷하면 단어 사이의 관계를 학습할 수 있다.</p>
<p>다만 반대말도 비슷한 문맥에서 쓰이므로, <strong>가까운 벡터가 반드시 같은 뜻은 아니다.</strong></p>
<h3 id="초기-신경망-언어-모델">초기 신경망 언어 모델</h3>
<p><strong>최근 N−1개 단어 → 임베딩 → MLP → Softmax → 다음 단어 확률</strong></p>
<p>이 과정에서 <strong>임베딩과 MLP의 가중치를 함께 학습</strong>한다. 다만 입력 문맥의 길이는 여전히 제한된다.</p>
<hr>
<h2 id="3-rnn과-lstm">3. RNN과 LSTM</h2>
<h3 id="rnn">RNN</h3>
<p>입력을 순서대로 처리하며 <strong>은닉 상태</strong>로 이전 정보를 전달한다.</p>
<ul>
<li>모든 시점에서 같은 가중치를 사용한다.</li>
<li>가변 길이 입력을 처리할 수 있다.</li>
<li>긴 문맥에서는 정보가 약해질 수 있다.</li>
<li>역전파 과정에서 <strong>기울기 소실·폭발</strong>이 발생할 수 있다.</li>
</ul>
<h3 id="lstm">LSTM</h3>
<p><strong>셀 상태와 망각·입력·출력 게이트</strong>로 기억을 조절해 장기 의존성 학습을 돕는다.</p>
<p>이전 기억을 얼마나 유지하고, 새 정보를 얼마나 저장하며, 무엇을 출력할지 학습한다.</p>
<p>LSTM도 모든 정보를 무한히 기억하는 것은 아니다.</p>
<hr>
<h2 id="4-pytorch의-역할">4. PyTorch의 역할</h2>
<p>PyTorch는 텐서 연산, 자동 미분, 모델 구성, 파라미터 업데이트를 위한 기능을 제공한다.</p>
<ul>
<li><strong>Tensor:</strong> N차원 배열. 차원은 원소 수가 아니라 축의 개수다.</li>
<li><strong>Dataset:</strong> 샘플을 읽고 전처리해 반환하는 방법을 정의한다.</li>
<li><strong>DataLoader:</strong> 샘플을 배치로 묶고 모델에 공급한다.</li>
<li><strong>nn.Module:</strong> 레이어와 순전파 구조를 정의한다.</li>
<li><strong>Autograd:</strong> 기울기를 계산한다.</li>
<li><strong>Optimizer:</strong> 기울기를 이용해 파라미터를 갱신한다.</li>
</ul>
<p>Hugging Face는 모델·데이터셋 공유와 사전 학습 모델 활용을 돕는 생태계이며, PyTorch와 함께 사용할 수 있다.</p>
<hr>
<h2 id="5-학습-루프와-병목">5. 학습 루프와 병목</h2>
<p>학습은 다음 과정을 반복한다.</p>
<p><strong>배치 가져오기 → 기울기 초기화 → 순전파 → 손실 계산 → 역전파 → 파라미터 업데이트</strong></p>
<ul>
<li><code>optimizer.zero_grad()</code>: 이전 기울기 초기화</li>
<li><code>model(x)</code>: 순전파</li>
<li><code>loss.backward()</code>: 기울기 계산</li>
<li><code>optimizer.step()</code>: 파라미터 업데이트</li>
</ul>
<p><strong>역전파는 기울기를 계산하고, 옵티마이저는 실제 파라미터를 수정한다.</strong></p>
<h3 id="batch와-epoch">Batch와 Epoch</h3>
<ul>
<li><strong>Batch:</strong> 한 번에 처리하는 데이터 묶음</li>
<li><strong>Epoch:</strong> 학습 데이터 전체를 한 바퀴 처리</li>
</ul>
<h3 id="데이터-병목">데이터 병목</h3>
<p>GPU가 빨라도 데이터 준비가 느리면 전체 학습이 느려진다. DataLoader의 병렬 로딩은 이런 병목을 줄이는 데 도움이 된다.</p>
<p><strong>num_workers는 GPU 수가 아니라 로딩 작업자 수다.</strong></p>
<hr>
<h2 id="오늘의-정리">오늘의 정리</h2>
<p>임베딩은 단어를 표현하고, RNN·LSTM은 문맥을 전달하며, 학습 루프는 예측의 오차를 이용해 모델을 조정한다.</p>
<p><strong>표현 → 문맥 처리 → 예측 → 손실 → 업데이트</strong></p>
<p>이 흐름으로 연결하니 각 개념의 역할이 더 명확해졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day2]]></title>
            <link>https://velog.io/@pink-frog/Day2-vt986fdg</link>
            <guid>https://velog.io/@pink-frog/Day2-vt986fdg</guid>
            <pubDate>Thu, 01 Oct 2026 02:41:25 GMT</pubDate>
            <description><![CDATA[<h1 id="📝-신경망에서-텍스트-벡터화까지-nlp-기초-정리">📝 신경망에서 텍스트 벡터화까지: NLP 기초 정리</h1>
<blockquote>
<p>📅 2026.10.01 · Today I Learned<br><strong>MLP · 전처리 · 토큰화 · BPE · WordPiece · Unigram · 임베딩</strong></p>
</blockquote>
<p>오늘은 신경망의 학습 과정을 살펴보고, 자연어가 모델의 입력이 되기까지의 과정을 배웠다.</p>
<p>핵심은 두 가지였다. <strong>텍스트를 숫자로 바꾸는 것</strong>, 그리고 <strong>그 과정에서 필요한 의미를 보존하는 것</strong>이다.</p>
<hr>
<h2 id="🧠-1-인공지능과-학습-방식">🧠 1. 인공지능과 학습 방식</h2>
<p>세 개념은 포함 관계를 가진다.</p>
<pre><code class="language-text">인공지능(AI)
└─ 머신러닝(ML)
   └─ 딥러닝(DL)</code></pre>
<ul>
<li><strong>인공지능:</strong> 지능과 관련된 기능을 컴퓨터로 구현하는 넓은 분야</li>
<li><strong>머신러닝:</strong> 데이터에서 패턴을 학습하는 방법</li>
<li><strong>딥러닝:</strong> 여러 층의 인공신경망을 사용하는 머신러닝 방법</li>
</ul>
<h3 id="모델은-무엇을-기준으로-배울까">모델은 무엇을 기준으로 배울까?</h3>
<table>
<thead>
<tr>
<th align="left">방식</th>
<th align="left">학습 기준</th>
<th align="left">예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>지도학습</strong></td>
<td align="left">입력과 정답</td>
<td align="left">감정 분석, 집값 예측</td>
</tr>
<tr>
<td align="left"><strong>비지도학습</strong></td>
<td align="left">데이터의 구조와 패턴</td>
<td align="left">군집화, 오토인코더</td>
</tr>
<tr>
<td align="left"><strong>강화학습</strong></td>
<td align="left">행동에 따른 보상</td>
<td align="left">게임, 로봇 제어</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>모델 구조와 학습 방식은 다른 구분이다.</strong><br>MLP는 모델 구조이며, 지도학습과 비지도학습 모두에 사용할 수 있다.</p>
</blockquote>
<p>지도학습은 예측과 정답의 차이를 줄인다. 비지도학습도 복원 오차나 군집 내부 거리처럼 학습 목표에 맞는 목적함수를 줄일 수 있다.</p>
<p>다만 K-means처럼 역전파를 사용하지 않는 알고리즘도 있다.</p>
<hr>
<h2 id="⚙️-2-인공신경망과-mlp">⚙️ 2. 인공신경망과 MLP</h2>
<p>인공신경망은 생물학적 신경망에서 아이디어를 얻은 계산 모델이다. 뇌를 그대로 복제한 것은 아니다.</p>
<p><strong>MLP(Multi-Layer Perceptron)</strong>는 입력층·은닉층·출력층으로 구성되며, 기본적으로 인접한 층 사이를 완전 연결한다.</p>
<pre><code class="language-text">입력층 → 은닉층 → 출력층
데이터 → 특징과 관계 계산 → 예측</code></pre>
<p>각 뉴런의 계산은 다음과 같다.</p>
<p>$$
z = \sum_i w_i x_i + b,\qquad a=f(z)
$$</p>
<table>
<thead>
<tr>
<th align="left">요소</th>
<th align="left">역할</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>가중치 (w)</strong></td>
<td align="left">입력이 반영되는 강도와 방향 조절</td>
</tr>
<tr>
<td align="left"><strong>편향 (b)</strong></td>
<td align="left">가중합의 기준을 이동</td>
</tr>
<tr>
<td align="left"><strong>활성화 함수 (f)</strong></td>
<td align="left">비선형성을 더해 복잡한 관계 표현</td>
</tr>
</tbody></table>
<h3 id="활성화-함수">활성화 함수</h3>
<table>
<thead>
<tr>
<th align="left">함수</th>
<th align="left">특징</th>
<th align="left">대표 용도</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>ReLU</strong></td>
<td align="left">음수는 0, 양수는 그대로</td>
<td align="left">은닉층</td>
</tr>
<tr>
<td align="left"><strong>Sigmoid</strong></td>
<td align="left">0~1 사이 출력</td>
<td align="left">이진 분류 출력층</td>
</tr>
<tr>
<td align="left"><strong>Tanh</strong></td>
<td align="left">−1~1 사이 출력</td>
<td align="left">일부 은닉층</td>
</tr>
<tr>
<td align="left"><strong>Softmax</strong></td>
<td align="left">여러 점수를 합이 1인 확률 분포로 변환</td>
<td align="left">다중 분류 출력층</td>
</tr>
</tbody></table>
<p>비선형 활성화 함수 없이 선형 계산만 여러 층 쌓으면, 전체도 하나의 선형 계산과 같다.</p>
<p>편향은 뉴런이 반응하는 기준을 조절할 수 있다. 예를 들어 <code>ReLU(x - 3)</code>은 입력이 3보다 클 때 양수를 출력한다.</p>
<h3 id="순전파와-역전파">순전파와 역전파</h3>
<pre><code class="language-text">순전파 → Loss 계산 → 역전파 → 가중치·편향 업데이트
                     반복</code></pre>
<ul>
<li><strong>순전파:</strong> 입력으로 예측을 만든다.</li>
<li><strong>Loss:</strong> 예측이 학습 목표에서 얼마나 벗어났는지 측정한다.</li>
<li><strong>역전파:</strong> 가중치·편향에 대한 Loss의 기울기를 계산한다.</li>
<li><strong>옵티마이저:</strong> 기울기를 이용해 파라미터를 수정한다.</li>
</ul>
<blockquote>
<p>📌 <strong>역전파는 기울기를 계산하는 과정이다.</strong><br>실제 파라미터 업데이트는 옵티마이저가 수행한다.</p>
</blockquote>
<hr>
<h2 id="📥-3-데이터-수집은-의미-보존까지">📥 3. 데이터 수집은 의미 보존까지</h2>
<p>NLP 데이터는 공개 데이터셋, API, 웹페이지, 내부 문서, 직접 수집 등의 방법으로 확보할 수 있다.</p>
<table>
<thead>
<tr>
<th align="left">목적</th>
<th align="left">수집할 데이터</th>
</tr>
</thead>
<tbody><tr>
<td align="left">감정 분석</td>
<td align="left">리뷰와 감정 라벨</td>
</tr>
<tr>
<td align="left">기계 번역</td>
<td align="left">원문과 대응하는 번역문</td>
</tr>
<tr>
<td align="left">문서 군집화</td>
<td align="left">문서 모음</td>
</tr>
</tbody></table>
<p>이번 강의에서는 수집을 단순히 자료를 가져오는 것보다 넓게 설명했다. <strong>원문의 의미와 구조를 보존하며 사용할 수 있는 형태로 변환하는 과정도 포함한다.</strong></p>
<p>예를 들어 수식 이미지에서 문자만 추출하면 분수선·위첨자·아래첨자가 사라질 수 있다.</p>
<pre><code class="language-latex">\mathrm{Attention}(Q,K,V)
= \mathrm{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V</code></pre>
<p>LaTeX로 옮기면 분자·분모, 전치, 제곱근 등의 관계를 명시할 수 있다.</p>
<p>텍스트에서도 마찬가지다. <strong>부정 표현, 표의 행·열 관계, 제목과 본문 관계</strong>를 잃으면 원래 정보를 제대로 옮겼다고 보기 어렵다.</p>
<hr>
<h2 id="🧹-4-데이터-정제와-전처리">🧹 4. 데이터 정제와 전처리</h2>
<p><strong>전처리</strong>는 원문을 분석과 모델 입력에 적합하게 준비하는 과정이다.</p>
<p>이번 강의에서는 <strong>정제를 좁은 의미의 전처리</strong>로 설명하며, 불필요한 요소 제거·정규화·토큰화를 포함한다. 용어의 범위는 자료마다 다를 수 있다.</p>
<table>
<thead>
<tr>
<th align="left">작업</th>
<th align="left">예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>불필요한 요소 제거</strong></td>
<td align="left">HTML 태그, 광고 문구 제거</td>
</tr>
<tr>
<td align="left"><strong>정규화</strong></td>
<td align="left">중복 공백 정리, 소문자화</td>
</tr>
<tr>
<td align="left"><strong>토큰화</strong></td>
<td align="left">단어·형태소·서브워드로 분리</td>
</tr>
<tr>
<td align="left"><strong>품질 확인</strong></td>
<td align="left">중복·누락·OCR 오류 처리</td>
</tr>
</tbody></table>
<p>정제된 텍스트 모음을 <strong>Clean corpus</strong>라고 한다.</p>
<pre><code class="language-text">원문: &quot;&lt;p&gt;이 영화는   안 좋았어요!!&lt;/p&gt;&quot;
정제: &quot;이 영화는 안 좋았어요!!&quot;</code></pre>
<h3 id="불용어--stopwords">불용어 — Stopwords</h3>
<p>작업 목적에 따라 중요도가 낮다고 판단해 제외하는 단어다. 일부 관사나 조사가 후보가 될 수 있다.</p>
<p>하지만 <strong>자주 등장한다고 무조건 제거해서는 안 된다.</strong> “안 좋다”에서 ‘안’을 제거하면 의미가 바뀐다. 감정 분석에서는 느낌표나 이모지도 정보가 될 수 있다.</p>
<hr>
<h2 id="🔤-5-토큰-→-인덱스-→-임베딩">🔤 5. 토큰 → 인덱스 → 임베딩</h2>
<p>모델은 자연어를 다음 흐름으로 처리한다.</p>
<pre><code class="language-text">텍스트 → 토큰 → 토큰 ID → 임베딩 → 문맥 처리</code></pre>
<table>
<thead>
<tr>
<th align="left">개념</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>토큰</strong></td>
<td align="left">텍스트를 처리하는 단위</td>
</tr>
<tr>
<td align="left"><strong>단어 사전</strong></td>
<td align="left">토큰과 고유 ID의 대응표</td>
</tr>
<tr>
<td align="left"><strong>인덱스 / 토큰 ID</strong></td>
<td align="left">토큰을 식별하는 번호</td>
</tr>
<tr>
<td align="left"><strong>임베딩</strong></td>
<td align="left">토큰 등을 나타내는 학습된 숫자 벡터</td>
</tr>
</tbody></table>
<pre><code class="language-text">“고양이” → ID 82 → 임베딩 행렬의 해당 행 → 숫자 벡터</code></pre>
<p>ID는 식별 번호다. 숫자가 크다고 더 중요한 단어라는 뜻은 아니다.</p>
<p>같은 토큰의 초기 임베딩은 같아도, 문맥을 처리한 표현은 달라질 수 있다. “배를 먹었다”와 “배를 탔다”의 ‘배’가 그 예다.</p>
<h3 id="토큰을-나누는-단위">토큰을 나누는 단위</h3>
<table>
<thead>
<tr>
<th align="left">단위</th>
<th align="left">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>단어·공백</strong></td>
<td align="left">간단하지만 한국어의 조사·어미 처리에 한계</td>
</tr>
<tr>
<td align="left"><strong>형태소</strong></td>
<td align="left">의미·문법 기능을 기준으로 분리</td>
</tr>
<tr>
<td align="left"><strong>문자</strong></td>
<td align="left">사전은 작지만 입력이 길어짐</td>
</tr>
<tr>
<td align="left"><strong>서브워드</strong></td>
<td align="left">단어의 일부를 활용해 사전 크기와 입력 길이를 절충</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧩-6-bpe·wordpiece·unigram">🧩 6. BPE·WordPiece·Unigram</h2>
<p>세 방법 모두 서브워드 사전을 학습하지만 기준이 다르다.</p>
<table>
<thead>
<tr>
<th align="left">방법</th>
<th align="left">핵심 원리</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>BPE</strong></td>
<td align="left">빈도가 높은 인접 토큰 쌍을 반복 병합</td>
</tr>
<tr>
<td align="left"><strong>WordPiece</strong></td>
<td align="left">데이터의 우도를 높이는 병합을 추구</td>
</tr>
<tr>
<td align="left"><strong>Unigram</strong></td>
<td align="left">큰 후보 사전에서 제거 영향이 작은 토큰을 삭제</td>
</tr>
</tbody></table>
<h3 id="bpe">BPE</h3>
<p>작은 단위에서 시작해 자주 붙어 나오는 쌍을 합친다.</p>
<pre><code class="language-text">[h, u, g] → [h, ug] → [hug]</code></pre>
<p>새 텍스트에는 학습해 둔 병합 순서를 적용한다.</p>
<h3 id="wordpiece">WordPiece</h3>
<p>단순한 쌍의 빈도뿐 아니라 각 조각의 빈도도 고려한다. 병합 원리를 설명하는 교육용 점수는 다음과 같다.</p>
<p>$$
\mathrm{score}(a,b)
=\frac{\mathrm{freq}(ab)}
{\mathrm{freq}(a)\mathrm{freq}(b)}
$$</p>
<p>개별 빈도에 비해 함께 등장하는 경향이 강한 쌍에 높은 점수를 준다. 실제 학습 구현은 달라질 수 있다.</p>
<p>새 텍스트는 왼쪽부터 사전에 있는 가장 긴 조각으로 분할한다. BERT 방식의 <code>##</code>는 단어 중간에 이어지는 조각이라는 표시다.</p>
<pre><code class="language-text">playing → [play, ##ing]</code></pre>
<h3 id="unigram">Unigram</h3>
<p>토큰별 확률을 학습하고, 각 토큰을 독립적으로 가정해 분할 확률을 계산한다.</p>
<p>$$
P(t_1,\ldots,t_n)=\prod_i P(t_i)
$$</p>
<p>학습할 때는 제거해도 Loss 증가가 작은 토큰을 삭제한다. 일반적인 분할에서는 전체 확률이 가장 높은 후보를 선택한다.</p>
<blockquote>
<p>💡 <strong>세 방법 모두 의미를 직접 분석하는 형태소 분석과는 다르다.</strong><br>SentencePiece는 BPE와 Unigram 등을 지원하는 도구다.</p>
</blockquote>
<hr>
<h2 id="🔢-7-자연어를-벡터로-표현하는-방법">🔢 7. 자연어를 벡터로 표현하는 방법</h2>
<table>
<thead>
<tr>
<th align="left">방법</th>
<th align="left">표현 기준</th>
<th align="left">주요 특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>BoW</strong></td>
<td align="left">단어 등장 횟수</td>
<td align="left">간단한 희소 벡터</td>
</tr>
<tr>
<td align="left"><strong>TF-IDF</strong></td>
<td align="left">단어 빈도와 희소성</td>
<td align="left">문서를 구별하는 단어에 가중치</td>
</tr>
<tr>
<td align="left"><strong>Word2Vec</strong></td>
<td align="left">주변 단어 예측 학습</td>
<td align="left">단어별 고정 밀집 벡터</td>
</tr>
<tr>
<td align="left"><strong>Embedding</strong></td>
<td align="left">학습된 벡터 표현</td>
<td align="left">토큰·문장·문서 등에 적용</td>
</tr>
</tbody></table>
<h3 id="bow--bag-of-words">BoW — Bag of Words</h3>
<pre><code class="language-text">사전: [고양이, 강아지, 좋아]

“고양이 좋아”      → [1, 0, 1]
“강아지 좋아 좋아” → [0, 1, 2]</code></pre>
<p>해석하기 쉽지만 단어 순서와 문맥을 잃는다.</p>
<h3 id="tf-idf">TF-IDF</h3>
<p>$$
\mathrm{TF!-!IDF}(t,d)=\mathrm{TF}(t,d)\times\mathrm{IDF}(t)
$$</p>
<ul>
<li><strong>TF:</strong> 해당 문서에서의 단어 빈도</li>
<li><strong>IDF:</strong> 전체 문서에서 해당 단어가 얼마나 드문지 나타내는 값</li>
</ul>
<p>여러 문서에 흔한 단어의 비중은 낮추고, 특정 문서를 구별하는 단어의 비중은 높일 수 있다.</p>
<h3 id="word2vec">Word2Vec</h3>
<p>주변 단어를 이용해 단어 임베딩을 학습한다.</p>
<ul>
<li><strong>CBOW:</strong> 주변 단어로 가운데 단어 예측</li>
<li><strong>Skip-gram:</strong> 가운데 단어로 주변 단어 예측</li>
</ul>
<p>비슷한 문맥에서 쓰이는 단어의 관계를 표현할 수 있지만, 기본적으로 단어마다 하나의 벡터를 사용해 다의어를 문맥별로 구분하지 못한다.</p>
<h3 id="embedding">Embedding</h3>
<p>임베딩은 학습된 벡터 표현을 가리키는 넓은 개념이다. <strong>Word2Vec도 임베딩을 만드는 방법 중 하나</strong>다.</p>
<p>문맥 기반 모델에서는 주변 토큰을 함께 처리해 문맥에 따라 다른 표현을 만든다.</p>
<hr>
<h2 id="📦-8-batch·padding·truncation">📦 8. Batch·Padding·Truncation</h2>
<p><strong>Batch</strong>는 한 번에 함께 처리하는 데이터 묶음이다. 일반적인 모델에서는 묶음 안의 입력을 배열로 만들기 위해 길이를 맞춘다.</p>
<table>
<thead>
<tr>
<th align="left">작업</th>
<th align="left">역할</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Padding</strong></td>
<td align="left">짧은 입력에 패딩 토큰 추가</td>
</tr>
<tr>
<td align="left"><strong>Truncation</strong></td>
<td align="left">최대 길이를 넘는 입력 절단</td>
</tr>
<tr>
<td align="left"><strong>Attention mask</strong></td>
<td align="left">실제 토큰과 패딩 위치 구분</td>
</tr>
</tbody></table>
<p>최대 길이가 5인 예시:</p>
<pre><code class="language-text">[11, 12, 13]         → [11, 12, 13, PAD, PAD]
[21, 22, 23, 24, 25] → [21, 22, 23, 24, 25]
[31, 32, 33, 34, 35, 36] → [31, 32, 33, 34, 35]

첫 입력의 attention mask: [1, 1, 1, 0, 0]</code></pre>
<ul>
<li><strong>고정 패딩:</strong> 지정한 길이에 맞춘다.</li>
<li><strong>동적 패딩:</strong> 각 batch에서 가장 긴 입력에 맞춘다.</li>
</ul>
<p>패딩은 빈자리를 채우지만, 절단은 실제 내용을 버린다. 중요한 내용이 잘리지 않는지 확인해야 한다.</p>
<hr>
<h2 id="✅-9-전체-흐름에서-기억할-점">✅ 9. 전체 흐름에서 기억할 점</h2>
<pre><code class="language-text">데이터 수집·의미 보존
→ 품질 확인·데이터 분리
→ 정제·토큰화
→ 벡터화 또는 토큰 ID 변환
→ 필요에 따라 패딩·절단
→ 모델 학습·평가</code></pre>
<ul>
<li><input disabled="" type="checkbox"> 작업에 필요한 의미를 전처리 과정에서 보존하기</li>
<li><input disabled="" type="checkbox"> 사전·TF-IDF는 학습 데이터로만 구축하기</li>
<li><input disabled="" type="checkbox"> 학습과 실제 예측에 같은 전처리 적용하기</li>
<li><input disabled="" type="checkbox"> 사전 학습된 모델의 토크나이저 사용하기</li>
<li><input disabled="" type="checkbox"> 중복 데이터가 학습·테스트에 함께 들어가지 않도록 확인하기</li>
</ul>
<hr>
<h2 id="✍️-오늘의-학습-메모">✍️ 오늘의 학습 메모</h2>
<p>자연어를 모델에 넣으려면 숫자로 바꿔야 한다. 하지만 숫자로 변환했다는 사실만으로 의미를 잘 표현했다고 볼 수는 없다.</p>
<p>토큰 ID는 식별 번호이고, 임베딩은 학습된 표현이다. 또한 같은 단어도 문맥에 따라 해석이 달라진다.</p>
<p><strong>어떤 정보를 남기고, 어떤 단위로 나누고, 어떤 표현으로 바꿀지 결정하는 과정이 NLP의 중요한 부분이라는 점을 배웠다.</strong></p>
<hr>
<h2 id="📚-참고-자료">📚 참고 자료</h2>
<ul>
<li><a href="https://huggingface.co/docs/transformers/en/tokenizer_summary">Hugging Face — 토큰화 알고리즘</a></li>
<li><a href="https://huggingface.co/learn/nlp-course/chapter6/6?fw=tf">Hugging Face — WordPiece</a></li>
<li><a href="https://huggingface.co/docs/course/chapter6/7">Hugging Face — Unigram</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day1]]></title>
            <link>https://velog.io/@pink-frog/Day1</link>
            <guid>https://velog.io/@pink-frog/Day1</guid>
            <pubDate>Wed, 30 Sep 2026 08:30:57 GMT</pubDate>
            <description><![CDATA[<h1 id="🐈-ai의-발전부터-바이브-코딩까지-데스크톱-고양이-만들기">🐈 AI의 발전부터 바이브 코딩까지: 데스크톱 고양이 만들기</h1>
<blockquote>
<p>📅 2026.09.30 · Today I Learned<br><strong>AI · 머신러닝 · 딥러닝 · 자연어 프로그래밍 · Vibe Coding</strong></p>
</blockquote>
<p>오늘은 AI의 기본 개념과 발전 과정을 배우고, Claude로 데스크톱 고양이 프로그램을 바이브 코딩으로 제작했다.</p>
<p>아이디어가 프로그램으로 바뀌는 과정은 빨랐다. 하지만 <strong>코드가 작동하는 것과 내가 그 구조를 이해하는 것은 별개의 문제</strong>였다.</p>
<hr>
<h2 id="🧠-1-인공지능·머신러닝·딥러닝">🧠 1. 인공지능·머신러닝·딥러닝</h2>
<p>세 개념의 포함 관계부터 정리했다.</p>
<pre><code>인공지능(AI)
└─ 머신러닝(ML)
   └─ 딥러닝(DL)</code></pre><table>
<thead>
<tr>
<th align="left">개념</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>인공지능</strong></td>
<td align="left">인식·언어 처리·추론·의사결정 등 지능과 관련된 작업을 수행하는 기술과 시스템</td>
</tr>
<tr>
<td align="left"><strong>머신러닝</strong></td>
<td align="left">데이터와 경험으로 패턴을 학습하는 방법</td>
</tr>
<tr>
<td align="left"><strong>딥러닝</strong></td>
<td align="left">여러 층의 인공신경망을 사용하는 머신러닝 방법</td>
</tr>
</tbody></table>
<p>전문가 시스템처럼 사람이 작성한 규칙으로 작동하는 AI도 있다. <strong>모든 AI가 머신러닝인 것은 아니다.</strong></p>
<h3 id="머신러닝은-어떻게-학습할까">머신러닝은 어떻게 학습할까?</h3>
<table>
<thead>
<tr>
<th align="left">학습 방식</th>
<th align="left">사용하는 정보</th>
<th align="left">예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left">지도학습</td>
<td align="left">입력과 정답</td>
<td align="left">이미지 분류, 집값 예측</td>
</tr>
<tr>
<td align="left">비지도학습</td>
<td align="left">정답 라벨 없는 데이터</td>
<td align="left">고객 군집화</td>
</tr>
<tr>
<td align="left">강화학습</td>
<td align="left">행동 결과와 보상</td>
<td align="left">바둑 전략, 로봇 제어</td>
</tr>
<tr>
<td align="left">자기지도학습</td>
<td align="left">데이터 자체에서 만든 학습 목표</td>
<td align="left">다음 토큰 예측</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>모델과 학습 방식은 구분해야 한다.</strong><br>딥러닝은 어떤 모델을 사용하는지에 대한 구분이고, 지도·비지도·강화학습은 어떻게 학습하는지에 대한 구분이다.</p>
</blockquote>
<hr>
<h2 id="⚙️-2-신경망의-기본-구조">⚙️ 2. 신경망의 기본 구조</h2>
<h3 id="mlp--다층-퍼셉트론">MLP — 다층 퍼셉트론</h3>
<p>MLP는 입력층·은닉층·출력층으로 구성된 신경망이다.</p>
<pre><code>입력층 → 은닉층 → 출력층
데이터 → 특징과 관계 계산 → 예측</code></pre><ul>
<li><strong>입력층:</strong> 데이터를 전달한다.</li>
<li><strong>은닉층:</strong> 입력을 조합하고 변환한다.</li>
<li><strong>출력층:</strong> 최종 예측을 만든다.</li>
<li><strong>가중치:</strong> 입력이 반영되는 강도와 방향을 결정한다.</li>
<li><strong>편향:</strong> 계산 결과를 이동시킨다.</li>
<li><strong>활성화 함수:</strong> 비선형성을 더해 복잡한 관계를 표현한다.</li>
</ul>
<h3 id="역전파--오차를-줄이기-위한-기울기-계산">역전파 — 오차를 줄이기 위한 기울기 계산</h3>
<pre><code>예측 → 손실 계산 → 역전파 → 가중치·편향 수정</code></pre><p>역전파가 기울기를 계산하고, 최적화 알고리즘이 그 기울기로 매개변수를 수정한다.</p>
<table>
<thead>
<tr>
<th align="left">모델</th>
<th align="left">특징</th>
<th align="left">활용</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>RNN</strong></td>
<td align="left">이전 정보를 은닉 상태로 전달</td>
<td align="left">문장, 음성, 시계열</td>
</tr>
<tr>
<td align="left"><strong>CNN</strong></td>
<td align="left">필터로 공간적 패턴 학습</td>
<td align="left">이미지 인식</td>
</tr>
</tbody></table>
<hr>
<h2 id="🕰️-3-ai-발전의-주요-사건">🕰️ 3. AI 발전의 주요 사건</h2>
<table>
<thead>
<tr>
<th align="left">시기</th>
<th align="left">대표 사례</th>
<th align="left">핵심 의미</th>
</tr>
</thead>
<tbody><tr>
<td align="left">1980년대 주요 흐름</td>
<td align="left">전문가 시스템, 신경망·역전파 연구</td>
<td align="left">규칙과 학습 기반 접근 발전</td>
</tr>
<tr>
<td align="left">1990년대</td>
<td align="left">머신러닝, Deep Blue, LeNet</td>
<td align="left">통계적 학습과 실용적 성과</td>
</tr>
<tr>
<td align="left"><strong>2012</strong></td>
<td align="left">AlexNet</td>
<td align="left">깊은 CNN·대규모 데이터·GPU의 효과</td>
</tr>
<tr>
<td align="left"><strong>2016</strong></td>
<td align="left">AlphaGo</td>
<td align="left">신경망·강화학습·탐색의 결합</td>
</tr>
<tr>
<td align="left"><strong>2017</strong></td>
<td align="left">Transformer</td>
<td align="left">Attention 중심의 언어 처리 구조</td>
</tr>
<tr>
<td align="left"><strong>2022~2023</strong></td>
<td align="left">ChatGPT</td>
<td align="left">대화형 생성 AI의 대중화</td>
</tr>
<tr>
<td align="left"><strong>2022~2024</strong></td>
<td align="left">Stable Diffusion 계열</td>
<td align="left">텍스트 기반 이미지 생성</td>
</tr>
<tr>
<td align="left"><strong>2025 주요 흐름</strong></td>
<td align="left">AI 에이전트</td>
<td align="left">도구를 사용한 여러 단계의 작업 수행</td>
</tr>
</tbody></table>
<h3 id="흐름을-연결하면">흐름을 연결하면</h3>
<ul>
<li><strong>AlexNet:</strong> 사진에서 특징을 학습해 대상을 분류했다.</li>
<li><strong>AlphaGo:</strong> 학습한 신경망으로 탐색을 안내하고 바둑 국면을 평가했다.</li>
<li><strong>Transformer:</strong> Self-Attention으로 위치 간 관계를 직접 반영하고 학습의 병렬화를 쉽게 했다.</li>
<li><strong>ChatGPT:</strong> 언어 모델을 대화 형식으로 사용할 수 있게 했다.</li>
<li><strong>Stable Diffusion:</strong> 텍스트 설명에 맞는 이미지를 생성했다.</li>
<li><strong>AI 에이전트:</strong> 목표를 받아 도구를 실행하고 결과에 따라 다음 행동을 선택한다.</li>
</ul>
<blockquote>
<p>📌 <strong>최초 공개와 대중화 시점은 다르다.</strong><br>ChatGPT는 <strong>2022년 11월</strong>, Stable Diffusion은 <strong>2022년</strong>에 처음 공개됐다.<br>AI 에이전트 역시 2025년에 처음 등장한 개념은 아니다.</p>
</blockquote>
<hr>
<h2 id="📚-4-함께-배운-개념">📚 4. 함께 배운 개념</h2>
<table>
<thead>
<tr>
<th align="left">개념</th>
<th align="left">정리</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Hugging Face Spaces</strong></td>
<td align="left">AI 앱을 배포하고 브라우저에서 체험하게 하는 플랫폼</td>
</tr>
<tr>
<td align="left"><strong>PoC</strong></td>
<td align="left">아이디어나 기술이 실제로 가능한지 작은 규모로 검증하는 작업</td>
</tr>
<tr>
<td align="left"><strong>온톨로지</strong></td>
<td align="left">특정 분야의 개념·관계·제약을 명확하게 정의한 지식 체계</td>
</tr>
</tbody></table>
<p>PoC가 성공했다고 제품의 완성도나 사업성까지 검증한 것은 아니다.</p>
<hr>
<h2 id="💬-5-자연어-프로그래밍과-vibe-coding">💬 5. 자연어 프로그래밍과 Vibe Coding</h2>
<p><strong>자연어 프로그래밍</strong>은 일상 언어로 프로그램의 동작을 지정하는 넓은 개념이다.</p>
<p><strong>Vibe Coding</strong>은 AI와 대화하며 결과를 만들고 반복 수정하는 개발 방식이다. 좁은 의미에서는 코드를 자세히 읽지 않고 결과 중심으로 진행하는 방식을 강조한다.</p>
<h3 id="ai-개발-도구의-변화">AI 개발 도구의 변화</h3>
<pre><code>코드 자동완성 → 대화형 코드 생성 → IDE 통합 → 에이전트 작업 수행</code></pre><table>
<thead>
<tr>
<th align="left">도구</th>
<th align="left">등장 시점</th>
<th align="left">대표한 변화</th>
</tr>
</thead>
<tbody><tr>
<td align="left">GitHub Copilot</td>
<td align="left">2021</td>
<td align="left">문맥 기반 코드 추천</td>
</tr>
<tr>
<td align="left">ChatGPT</td>
<td align="left">2022</td>
<td align="left">자연어로 코드 생성·설명·수정</td>
</tr>
<tr>
<td align="left">Cursor</td>
<td align="left">2023</td>
<td align="left">AI를 코드 에디터에 통합</td>
</tr>
<tr>
<td align="left">Claude Code</td>
<td align="left">2025</td>
<td align="left">탐색·수정·실행·검증을 이어가는 에이전트</td>
</tr>
</tbody></table>
<h3 id="자연어만으로-충분할까">자연어만으로 충분할까?</h3>
<p>자연어에는 모호성과 생략된 조건이 있다. AI가 그 부분을 추정해서 구현하면 사용자의 의도와 어긋날 수 있다.</p>
<blockquote>
<p><strong>코드가 실행된다 ≠ 요구사항을 만족한다</strong></p>
</blockquote>
<p>자연어로 프로그램을 만드는 일은 가능해졌지만, 검증과 유지보수의 필요성까지 사라진 것은 아니다.</p>
<hr>
<h2 id="🐈-6-실습-데스크톱-고양이-프로그램">🐈 6. 실습: 데스크톱 고양이 프로그램</h2>
<h3 id="무엇을-만들었나">무엇을 만들었나?</h3>
<p>Desktop Goose v0.31 폴더를 Claude에 제공하고 비슷한 프로그램을 만들도록 요청했다.</p>
<p>결과물은 <strong><code>cat_pet.py</code></strong>이며, Python·Tkinter·Windows API를 사용한다.</p>
<h3 id="구현된-기능">구현된 기능</h3>
<ul>
<li>마우스 주변을 따라다니는 고양이</li>
<li>걷기·달리기·앉기·눕기·수면 애니메이션</li>
<li>최대 6마리 소환과 겹침 완화</li>
<li>투명하고 항상 위에 표시되는 창</li>
<li>소환·종료 단축키</li>
</ul>
<h3 id="프로그램의-동작-흐름">프로그램의 동작 흐름</h3>
<pre><code>이미지 로딩 → 고양이 생성 → 입력 확인
→ 목표 위치 계산 → 이동·행동 결정 → 화면 갱신</code></pre><p>프로그램 안에 AI 모델이 들어 있는 것은 아니다. <strong>Claude가 작성한 규칙 기반 프로그램</strong>이다.</p>
<h3 id="원본을-참고한다는-것의-의미">원본을 참고한다는 것의 의미</h3>
<p>Desktop Goose 폴더에는 실행 파일·자산·설정·모드 API와 예제가 있었지만, 본체 전체 소스는 없었다.</p>
<p>고양이 프로그램은 데스크톱 캐릭터라는 경험을 구현했지만, 원본의 가속도 이동·행동별 Task·모드 구조까지 동일하게 재현한 것은 아니다.</p>
<p>또한 Claude가 당시 어떤 자료를 실제로 참고했는지는 결과물만으로 확인할 수 없다.</p>
<h3 id="코드-분석에서-발견한-점">코드 분석에서 발견한 점</h3>
<table>
<thead>
<tr>
<th align="left">잘 구현된 부분</th>
<th align="left">개선할 부분</th>
</tr>
</thead>
<tbody><tr>
<td align="left">이동과 그리기 분리</td>
<td align="left">겹침 해소 후 화면 밖으로 밀릴 가능성</td>
</tr>
<tr>
<td align="left">이미지 캐시</td>
<td align="left">행동 전환 시 첫 프레임 생략 가능성</td>
</tr>
<tr>
<td align="left">소환 키 연속 입력 방지</td>
<td align="left">실행 옵션의 예외 처리 부족</td>
</tr>
<tr>
<td align="left">일부 누락 이미지 대응</td>
<td align="left">이동 속도가 갱신 주기에 의존</td>
</tr>
</tbody></table>
<p>분석은 소스·자산 확인과 일부 계산 재현으로 진행했다. 실제 GUI 실행 검증은 하지 않았다.</p>
<hr>
<h2 id="🔍-7-작동하는데-왜-블랙박스처럼-느껴질까">🔍 7. 작동하는데, 왜 블랙박스처럼 느껴질까?</h2>
<p>프로그램은 움직였지만 다음 질문에 답하기 어려웠다.</p>
<ul>
<li>왜 이런 구조로 작성했을까?</li>
<li>특정 수치는 왜 선택했을까?</li>
<li>한 부분을 바꾸면 어디에 영향을 줄까?</li>
<li>원본의 어떤 특징을 가져왔을까?</li>
</ul>
<p><strong>“비슷하게 만들어줘”라는 요청부터 구체화할 필요가 있었다.</strong></p>
<p>외형을 비슷하게 할 것인지, 행동을 재현할 것인지, 내부 구조까지 참고할 것인지 먼저 정해야 했다.</p>
<hr>
<h2 id="✅-8-다음-작업에-적용할-방법">✅ 8. 다음 작업에 적용할 방법</h2>
<pre><code>작업 지시 → 계획 작성·수정 → 구현·테스트
→ PR 작성 → 검토·승인 → 병합</code></pre><p>PR에는 <strong>변경 이유·구현 방식·테스트 결과·확인하지 못한 부분</strong>을 기록한다.</p>
<ul>
<li><input disabled="" type="checkbox"> 구현 전에 요구사항과 제외 범위 정하기</li>
<li><input disabled="" type="checkbox"> 주요 기능의 데이터 흐름 이해하기</li>
<li><input disabled="" type="checkbox"> 중요한 설계 선택의 이유 기록하기</li>
<li><input disabled="" type="checkbox"> 작은 단위로 수정하고 Git에 체크포인트 남기기</li>
<li><input disabled="" type="checkbox"> 기대하는 동작을 테스트 기준으로 만들기</li>
<li><input disabled="" type="checkbox"> PR 설명과 실제 변경을 비교하기</li>
</ul>
<hr>
<h2 id="✍️-오늘의-회고">✍️ 오늘의 회고</h2>
<p>AI 덕분에 아이디어를 빠르게 구현할 수 있었다. 하지만 생성 속도에 비해 구조와 설계 의도를 이해하는 속도가 따라가지 못했다.</p>
<p>앞으로는 기능을 추가하는 것뿐 아니라, 변경 이유와 영향을 설명하고 검증하는 데도 시간을 쓰려 한다.</p>
<p><strong>작동하는 코드에서, 내가 이해하고 관리할 수 있는 코드로 나아가는 것이 다음 목표다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[최종 프로젝트]]></title>
            <link>https://velog.io/@pink-frog/%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@pink-frog/%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Thu, 21 Dec 2023 01:47:20 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-관리-서비스-am_pm">프로젝트 관리 서비스 AM_PM</h2>
<p><img src="https://velog.velcdn.com/images/pink-frog/post/43194f9e-870c-4445-8b0d-479b483f7064/image.png" alt="AM_PM"></p>
<p>프로젝트의 설계부터 산출물 관리까지 도와주는 서비스
<a href="https://github.com/Phoenix-kosa/AM_PM">https://github.com/Phoenix-kosa/AM_PM</a></p>
<h3 id="제공-기능">제공 기능</h3>
<p>팀원 관리
회원가입/로그인
프로젝트 산출물 업로드
팀채팅
간트차트
Q&amp;A게시판
관리자 페이지</p>
<h3 id="목표">목표</h3>
<p>프로젝트를 기획 단계부터 개발 단계까지 관리해주는 서비스</p>
<h3 id="맡은-역할">맡은 역할</h3>
<p>웹소켓을 이용한 실시간 채팅 및 프로젝트 생성과 멤버 관리 구현</p>
<h3 id="erd">ERD</h3>
<p><img src="https://velog.velcdn.com/images/pink-frog/post/1aa86761-c942-489b-8ce9-c22da0ca570b/image.png" alt="ERD"></p>
<h3 id="화면-구성">화면 구성</h3>
<p><img src="https://velog.velcdn.com/images/pink-frog/post/24072e81-3c76-45dd-bccc-85a1c7c0a28d/image.png" alt="화면 구성"></p>
<h3 id="프로젝트-아키텍처">프로젝트 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/pink-frog/post/48f161ba-f580-4fb7-801b-7a040d94d53e/image.png" alt="프로젝트 아키텍처"></p>
<h3 id="구현한-주요-내용">구현한 주요 내용</h3>
<h4 id="팀채팅">팀채팅</h4>
<p>웹소켓을 이용하여 프로젝트 팀원 간의 실시간 채팅을 구현하였다. 팀원이 추가/제거됨에 따라 실시간 채팅 참여 인원이 달라지고 이에 따라 메시지를 읽은 사람의 수가 변화되도록 구현하였다.</p>
<h4 id="프로젝트-생성-및-팀원-관리">프로젝트 생성 및 팀원 관리</h4>
<p>사용자가 프로젝트를 생성하면 해당 프로젝트의 대표로 설정되며, 추후 팀원을 추가/제거하고 대표 역할을 위임할 수 있도록 구현하였다. 프로젝트 팀원 추가 혹은 프로젝트의 삭제는 대표의 권한으로 구현하였으며, 프로젝트 삭제시 팀원을 모두 제거하여야 하는 조건을 설정하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[잃어버린 괄호]]></title>
            <link>https://velog.io/@pink-frog/%EC%9E%83%EC%96%B4%EB%B2%84%EB%A6%B0-%EA%B4%84%ED%98%B8</link>
            <guid>https://velog.io/@pink-frog/%EC%9E%83%EC%96%B4%EB%B2%84%EB%A6%B0-%EA%B4%84%ED%98%B8</guid>
            <pubDate>Tue, 31 Oct 2023 06:17:40 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1541번-잃어버린-괄호">백준 1541번 잃어버린 괄호</h3>
<p>세준이는 양수와 +, -, 그리고 괄호를 가지고 식을 만들었다. 그리고 나서 세준이는 괄호를 모두 지웠다.</p>
<p>그리고 나서 세준이는 괄호를 적절히 쳐서 이 식의 값을 최소로 만들려고 한다.</p>
<p>괄호를 적절히 쳐서 이 식의 값을 최소로 만드는 프로그램을 작성하시오.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 식이 주어진다. 식은 ‘0’~‘9’, ‘+’, 그리고 ‘-’만으로 이루어져 있고, 가장 처음과 마지막 문자는 숫자이다. 그리고 연속해서 두 개 이상의 연산자가 나타나지 않고, 5자리보다 많이 연속되는 숫자는 없다. 수는 0으로 시작할 수 있다. 입력으로 주어지는 식의 길이는 50보다 작거나 같다.</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 정답을 출력한다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.next();
        StringBuilder sb = new StringBuilder();
        String word;
        Queue&lt;String&gt; queue = new LinkedList&lt;&gt;();
        for(int i = 0; i &lt; input.length(); i++) {
            word = input.substring(i, i+1);
            if(word.equals(&quot;+&quot;) || word.equals(&quot;-&quot;)) {
                queue.add(String.valueOf(sb));
                sb.setLength(0);
                queue.add(word);
            }
            else
                sb.append(word);
        }
        queue.add(String.valueOf(sb));

        int sum = 0;
        boolean minus = false;
        while(!queue.isEmpty()) {
            if(minus)
                sum -= Integer.parseInt(queue.poll());
            else
                sum += Integer.parseInt(queue.poll());
            if(!queue.isEmpty() &amp;&amp; queue.poll().equals(&quot;-&quot;)) {
                minus = true;
            }
        }
        System.out.println(sum);
    }
}
</code></pre>
<p>&quot;-&quot;기호가 한 번이라도 등장하면 그 뒤에 나오는 수식은 모두 뺄셈으로 계산할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[회의실 배정]]></title>
            <link>https://velog.io/@pink-frog/%ED%9A%8C%EC%9D%98%EC%8B%A4-%EB%B0%B0%EC%A0%95-vanddvn7</link>
            <guid>https://velog.io/@pink-frog/%ED%9A%8C%EC%9D%98%EC%8B%A4-%EB%B0%B0%EC%A0%95-vanddvn7</guid>
            <pubDate>Tue, 31 Oct 2023 00:52:30 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1931번-회의실-배정">백준 1931번 회의실 배정</h3>
<p>한 개의 회의실이 있는데 이를 사용하고자 하는 N개의 회의에 대하여 회의실 사용표를 만들려고 한다. 각 회의 I에 대해 시작시간과 끝나는 시간이 주어져 있고, 각 회의가 겹치지 않게 하면서 회의실을 사용할 수 있는 회의의 최대 개수를 찾아보자. 단, 회의는 한번 시작하면 중간에 중단될 수 없으며 한 회의가 끝나는 것과 동시에 다음 회의가 시작될 수 있다. 회의의 시작시간과 끝나는 시간이 같을 수도 있다. 이 경우에는 시작하자마자 끝나는 것으로 생각하면 된다.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 회의의 수 N(1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N+1 줄까지 각 회의의 정보가 주어지는데 이것은 공백을 사이에 두고 회의의 시작시간과 끝나는 시간이 주어진다. 시작 시간과 끝나는 시간은 231-1보다 작거나 같은 자연수 또는 0이다.</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 최대 사용할 수 있는 회의의 최대 개수를 출력한다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        final int N = Integer.parseInt(br.readLine()); // 회의의 개수
        List&lt;Meeting&gt; meetingList = new ArrayList&lt;&gt;();
        Meeting meeting;
        for(int i = 0; i &lt; N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            meeting = new Meeting(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()));
            meetingList.add(meeting);
        }
        int count = 0;
        int endTime;
        meetingList.sort(new Comparator&lt;Meeting&gt;() {
            @Override
            public int compare(Meeting o1, Meeting o2) {
                if(o1.end == o2.end)
                    return o1.start - o2.start;
                return o1.end - o2.end;
            }
        });
        endTime = meetingList.get(0).end;
        count++;
        for(int i = 1; i &lt; meetingList.size(); i++) {
            if(meetingList.get(i).start &gt;= endTime) {
                endTime = meetingList.get(i).end;
                count++;
            }
        }
        System.out.println(count);
    }
}
class Meeting {
    int start;
    int end;
    Meeting(int start, int end) {
        this.start = start;
        this.end = end;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[수 묶기]]></title>
            <link>https://velog.io/@pink-frog/%EC%88%98-%EB%AC%B6%EA%B8%B0</link>
            <guid>https://velog.io/@pink-frog/%EC%88%98-%EB%AC%B6%EA%B8%B0</guid>
            <pubDate>Mon, 30 Oct 2023 08:55:32 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1744번-수-묶기">백준 1744번 수 묶기</h3>
<p>길이가 N인 수열이 주어졌을 때, 그 수열의 합을 구하려고 한다. 하지만, 그냥 그 수열의 합을 모두 더해서 구하는 것이 아니라, 수열의 두 수를 묶으려고 한다. 어떤 수를 묶으려고 할 때, 위치에 상관없이 묶을 수 있다. 하지만, 같은 위치에 있는 수(자기 자신)를 묶는 것은 불가능하다. 그리고 어떤 수를 묶게 되면, 수열의 합을 구할 때 묶은 수는 서로 곱한 후에 더한다.</p>
<p>예를 들면, 어떤 수열이 {0, 1, 2, 4, 3, 5}일 때, 그냥 이 수열의 합을 구하면 0+1+2+4+3+5 = 15이다. 하지만, 2와 3을 묶고, 4와 5를 묶게 되면, 0+1+(2*3)+(4*5) = 27이 되어 최대가 된다.</p>
<p>수열의 모든 수는 단 한번만 묶거나, 아니면 묶지 않아야한다.</p>
<p>수열이 주어졌을 때, 수열의 각 수를 적절히 묶었을 때, 그 합이 최대가 되게 하는 프로그램을 작성하시오.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 수열의 크기 N이 주어진다. N은 50보다 작은 자연수이다. 둘째 줄부터 N개의 줄에 수열의 각 수가 주어진다. 수열의 수는 -1,000보다 크거나 같고, 1,000보다 작거나 같은 정수이다.</p>
<h4 id="출력">출력</h4>
<p>수를 합이 최대가 나오게 묶었을 때 합을 출력한다. 정답은 항상 231보다 작다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        final int N = Integer.parseInt(br.readLine()); // 수열의 크기
        PriorityQueue&lt;Integer&gt; positive = new PriorityQueue&lt;&gt;(Collections.reverseOrder());
        PriorityQueue&lt;Integer&gt; negative = new PriorityQueue&lt;&gt;();
        int input;
        for(int i = 0; i &lt; N; i++) {
            input = Integer.parseInt(br.readLine());
            if(input &gt; 0)
                positive.add(input);
            else
                negative.add(input);
        }
        int sum = 0;
        int num1;
        int num2;
        while(positive.size() &gt; 1) {
            num1 = positive.poll();
            num2 = positive.poll();
            sum += Math.max(num1 + num2, num1 * num2);
        }
        while(!positive.isEmpty())
            sum += positive.poll();
        while(negative.size() &gt; 1) {
            num1 = negative.poll();
            num2 = negative.poll();
            sum += Math.max(num1 + num2, num1 * num2);
        }
        while(!negative.isEmpty())
            sum += negative.poll();
        System.out.println(sum);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[카드 정렬하기]]></title>
            <link>https://velog.io/@pink-frog/%EC%B9%B4%EB%93%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pink-frog/%EC%B9%B4%EB%93%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 30 Oct 2023 06:08:57 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1715번-카드-정렬하기">백준 1715번 카드 정렬하기</h3>
<p>정렬된 두 묶음의 숫자 카드가 있다고 하자. 각 묶음의 카드의 수를 A, B라 하면 보통 두 묶음을 합쳐서 하나로 만드는 데에는 A+B 번의 비교를 해야 한다. 이를테면, 20장의 숫자 카드 묶음과 30장의 숫자 카드 묶음을 합치려면 50번의 비교가 필요하다.</p>
<p>매우 많은 숫자 카드 묶음이 책상 위에 놓여 있다. 이들을 두 묶음씩 골라 서로 합쳐나간다면, 고르는 순서에 따라서 비교 횟수가 매우 달라진다. 예를 들어 10장, 20장, 40장의 묶음이 있다면 10장과 20장을 합친 뒤, 합친 30장 묶음과 40장을 합친다면 (10 + 20) + (30 + 40) = 100번의 비교가 필요하다. 그러나 10장과 40장을 합친 뒤, 합친 50장 묶음과 20장을 합친다면 (10 + 40) + (50 + 20) = 120 번의 비교가 필요하므로 덜 효율적인 방법이다.</p>
<p>N개의 숫자 카드 묶음의 각각의 크기가 주어질 때, 최소한 몇 번의 비교가 필요한지를 구하는 프로그램을 작성하시오.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 N이 주어진다. (1 ≤ N ≤ 100,000) 이어서 N개의 줄에 걸쳐 숫자 카드 묶음의 각각의 크기가 주어진다. 숫자 카드 묶음의 크기는 1,000보다 작거나 같은 양의 정수이다.</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 최소 비교 횟수를 출력한다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        final int N = Integer.parseInt(br.readLine()); // 카드 묶음의 개수
        PriorityQueue&lt;Integer&gt; queue = new PriorityQueue&lt;&gt;();
        for(int i = 0; i &lt; N; i++)
            queue.add(Integer.parseInt(br.readLine()));
        int result = 0;
        int num1;
        int num2;
        while(queue.size() &gt; 1) {
            num1 = queue.poll();
            num2 = queue.poll();
            result += num1 + num2;
            queue.add(num1 + num2);
        }
        System.out.println(result);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[동전 0]]></title>
            <link>https://velog.io/@pink-frog/%EB%8F%99%EC%A0%84-0</link>
            <guid>https://velog.io/@pink-frog/%EB%8F%99%EC%A0%84-0</guid>
            <pubDate>Fri, 27 Oct 2023 05:54:56 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-11047번-동전-0">백준 11047번 동전 0</h3>
<p>준규가 가지고 있는 동전은 총 N종류이고, 각각의 동전을 매우 많이 가지고 있다.</p>
<p>동전을 적절히 사용해서 그 가치의 합을 K로 만들려고 한다. 이때 필요한 동전 개수의 최솟값을 구하는 프로그램을 작성하시오.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 N과 K가 주어진다. (1 ≤ N ≤ 10, 1 ≤ K ≤ 100,000,000)</p>
<p>둘째 줄부터 N개의 줄에 동전의 가치 Ai가 오름차순으로 주어진다. (1 ≤ Ai ≤ 1,000,000, A1 = 1, i ≥ 2인 경우에 Ai는 Ai-1의 배수)</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 K원을 만드는데 필요한 동전 개수의 최솟값을 출력한다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        final int N = Integer.parseInt(st.nextToken()); // 동전의 종류
        int K = Integer.parseInt(st.nextToken()); // 목표 가격
        int[] coins = new int[N];
        for(int i = 0; i &lt; N; i++) {
            coins[i] = Integer.parseInt(br.readLine());
        }
        int cnt = 0;
        while(K &gt; 0) {
            for(int i = N - 1; i &gt;= 0; i--) {
                if(K &lt; coins[i])
                    continue;
                cnt += K / coins[i];
                K %= coins[i];
            }
        }
        System.out.println(cnt);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[그리디(Greedy) 알고리즘]]></title>
            <link>https://velog.io/@pink-frog/%EA%B7%B8%EB%A6%AC%EB%94%94Greedy-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@pink-frog/%EA%B7%B8%EB%A6%AC%EB%94%94Greedy-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Fri, 27 Oct 2023 05:28:35 GMT</pubDate>
            <description><![CDATA[<h2 id="그리디greedy-알고리즘">그리디(Greedy) 알고리즘</h2>
<p>그리디 알고리즘은 현재 상태에서 볼 수 있는 선택지 중에 최선의 선택을 하는 알고리즘이다. 그리디 알고리즘은 동적 계획법보다 구현하기 쉽고 시간 복잡도가 우수하다. 하지만 항상 최적의 해를 보장하지 못한다는 단점도 있다. 따라서 그리디 알고리즘을 적용할 때의 논리 유무를 충분히 살펴야 한다.</p>
<h3 id="그리디-알고리즘의-핵심-이론">그리디 알고리즘의 핵심 이론</h3>
<p>① 해 선택 : 현재 상태에서 가장 최선이라고 생각되는 해를 선택한다.
② 적절성 검사 : 현재 선택한 해가 전체 문제의 제약 조건에 벗어나지 않는지 검사한다.
③ 해 검사 : 현재까지 선택한 해 집합이 전체 문제를 해결할 수 있는지 검사한다. 전체 문제를 해결하지 못한다면 ①로 돌아가 같은 과정을 반복한다.</p>
<h4 id="예제">예제</h4>
<p><a href="https://velog.io/@pink-frog/%EB%8F%99%EC%A0%84-0">동전 0</a>
<a href="https://velog.io/@pink-frog/%EC%B9%B4%EB%93%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0">카드 정렬하기</a>
<a href="https://velog.io/@pink-frog/%ED%9A%8C%EC%9D%98%EC%8B%A4-%EB%B0%B0%EC%A0%95-vanddvn7">회의실 배정</a>
<a href="https://velog.io/@pink-frog/%EC%88%98-%EB%AC%B6%EA%B8%B0">수 묶기</a>
<a href="https://velog.io/@pink-frog/%EC%9E%83%EC%96%B4%EB%B2%84%EB%A6%B0-%EA%B4%84%ED%98%B8">잃어버린 괄호</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[K번째 수]]></title>
            <link>https://velog.io/@pink-frog/K%EB%B2%88%EC%A7%B8-%EC%88%98-hlbodttf</link>
            <guid>https://velog.io/@pink-frog/K%EB%B2%88%EC%A7%B8-%EC%88%98-hlbodttf</guid>
            <pubDate>Fri, 27 Oct 2023 05:23:11 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1300번-k번째-수">백준 1300번 K번째 수</h3>
<p>세준이는 크기가 N×N인 배열 A를 만들었다. 배열에 들어있는 수 A[i][j] = i×j 이다. 이 수를 일차원 배열 B에 넣으면 B의 크기는 N×N이 된다. B를 오름차순 정렬했을 때, B[k]를 구해보자.</p>
<p>배열 A와 B의 인덱스는 1부터 시작한다.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 배열의 크기 N이 주어진다. N은 105보다 작거나 같은 자연수이다. 둘째 줄에 k가 주어진다. k는 min(109, N2)보다 작거나 같은 자연수이다.</p>
<h4 id="출력">출력</h4>
<p>B[k]를 출력한다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        final int N = Integer.parseInt(br.readLine()); // 배열의 크기
        final int k = Integer.parseInt(br.readLine()); // k번째 수 찾기
        int startIndex = 1;
        int endIndex = k;
        int midIndex;
        int sum;
        int result = 0;
        while(startIndex &lt;= endIndex) {
            midIndex = (startIndex + endIndex) / 2;
            sum = 0;
            for(int i = 0; i &lt; N; i++) {
                sum += Math.min(midIndex / (i + 1), N);
            }
            if(sum &lt; k)
                startIndex = midIndex + 1;
            else {
                endIndex = midIndex - 1;
                result = midIndex;
            }
        }
        System.out.println(result);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[기타 레슨]]></title>
            <link>https://velog.io/@pink-frog/%EA%B8%B0%ED%83%80-%EB%A0%88%EC%8A%A8</link>
            <guid>https://velog.io/@pink-frog/%EA%B8%B0%ED%83%80-%EB%A0%88%EC%8A%A8</guid>
            <pubDate>Fri, 27 Oct 2023 00:46:30 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-2343번-기타-레슨">백준 2343번 기타 레슨</h3>
<p>강토는 자신의 기타 강의 동영상을 블루레이로 만들어 판매하려고 한다. 블루레이에는 총 N개의 강의가 들어가는데, 블루레이를 녹화할 때, 강의의 순서가 바뀌면 안 된다. 순서가 뒤바뀌는 경우에는 강의의 흐름이 끊겨, 학생들이 대혼란에 빠질 수 있기 때문이다. 즉, i번 강의와 j번 강의를 같은 블루레이에 녹화하려면 i와 j 사이의 모든 강의도 같은 블루레이에 녹화해야 한다.</p>
<p>강토는 이 블루레이가 얼마나 팔릴지 아직 알 수 없기 때문에, 블루레이의 개수를 가급적 줄이려고 한다. 오랜 고민 끝에 강토는 M개의 블루레이에 모든 기타 강의 동영상을 녹화하기로 했다. 이때, 블루레이의 크기(녹화 가능한 길이)를 최소로 하려고 한다. 단, M개의 블루레이는 모두 같은 크기이어야 한다.</p>
<p>강토의 각 강의의 길이가 분 단위(자연수)로 주어진다. 이때, 가능한 블루레이의 크기 중 최소를 구하는 프로그램을 작성하시오.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 강의의 수 N (1 ≤ N ≤ 100,000)과 M (1 ≤ M ≤ N)이 주어진다. 다음 줄에는 강토의 기타 강의의 길이가 강의 순서대로 분 단위로(자연수)로 주어진다. 각 강의의 길이는 10,000분을 넘지 않는다.</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 가능한 블루레이 크기중 최소를 출력한다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken()); // 강의의 수
        int M = Integer.parseInt(st.nextToken()); // 블루레이의 수
        st = new StringTokenizer(br.readLine());
        int[] input = new int[N]; // 강의 길이 배열
        int max = 0; // 길이 최대값
        int total = 0; // 길이 합
        for(int i = 0; i &lt; N; i++) {
            input[i] = Integer.parseInt(st.nextToken());
            max = Math.max(input[i], max);
            total += input[i];
        }
        int startIndex = max;
        int endIndex = total;
        int midIndex;
        int count;
        int sum;
        while(startIndex &lt;= endIndex) {
            midIndex = (startIndex + endIndex) / 2;
            count = 1;
            sum = 0;
            for(int i : input) {
                sum += i;
                if(sum &gt; midIndex) {
                    count++;
                    sum = i;
                }
            }
            if(count &lt;= M)
                endIndex = midIndex - 1;
            else
                startIndex = midIndex + 1;
        }
        System.out.println(startIndex);
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[수 찾기]]></title>
            <link>https://velog.io/@pink-frog/%EC%88%98-%EC%B0%BE%EA%B8%B0</link>
            <guid>https://velog.io/@pink-frog/%EC%88%98-%EC%B0%BE%EA%B8%B0</guid>
            <pubDate>Thu, 26 Oct 2023 09:03:48 GMT</pubDate>
            <description><![CDATA[<h2 id="백준-1920번-수-찾기">백준 1920번 수 찾기</h2>
<p>N개의 정수 A[1], A[2], …, A[N]이 주어져 있을 때, 이 안에 X라는 정수가 존재하는지 알아내는 프로그램을 작성하시오.</p>
<h3 id="입력">입력</h3>
<p>첫째 줄에 자연수 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 줄에는 N개의 정수 A[1], A[2], …, A[N]이 주어진다. 다음 줄에는 M(1 ≤ M ≤ 100,000)이 주어진다. 다음 줄에는 M개의 수들이 주어지는데, 이 수들이 A안에 존재하는지 알아내면 된다. 모든 정수의 범위는 -231 보다 크거나 같고 231보다 작다.</p>
<h3 id="출력">출력</h3>
<p>M개의 줄에 답을 출력한다. 존재하면 1을, 존재하지 않으면 0을 출력한다.</p>
<h3 id="소스">소스</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine()); // 정렬할 수 개수
        int[] input = new int[N]; // 정수 배열
        StringTokenizer st = new StringTokenizer(br.readLine());
        for(int i = 0; i &lt; N; i++) {
            input[i] = Integer.parseInt(st.nextToken());
        }
        Arrays.sort(input);
        int M = Integer.parseInt(br.readLine()); // 탐색할 숫자의 개수
        int target;
        int startIndex;
        int endIndex;
        int midIndex;
        boolean exist;
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i &lt; M; i++) {
            target = Integer.parseInt(st.nextToken()); // 탐색할 숫자
            startIndex = 0;
            endIndex = N - 1;
            midIndex = N / 2;
            exist = false;
            while(startIndex &lt;= endIndex) {
                if(input[midIndex] &gt; target)
                    endIndex = midIndex - 1;
                else if(input[midIndex] &lt; target)
                    startIndex = midIndex + 1;
                else {
                    exist = true;
                    break;
                }
                midIndex = (startIndex + endIndex) / 2;
            }
            if(exist)
                System.out.println(1);
            else
                System.out.println(0);
        }
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[트리의 지름]]></title>
            <link>https://velog.io/@pink-frog/%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84</link>
            <guid>https://velog.io/@pink-frog/%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84</guid>
            <pubDate>Wed, 25 Oct 2023 05:02:50 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1167번-트리의-지름">백준 1167번 트리의 지름</h3>
<p>트리의 지름이란, 트리에서 임의의 두 점 사이의 거리 중 가장 긴 것을 말한다. 트리의 지름을 구하는 프로그램을 작성하시오.</p>
<h4 id="입력">입력</h4>
<p>트리가 입력으로 주어진다. 먼저 첫 번째 줄에서는 트리의 정점의 개수 V가 주어지고 (2 ≤ V ≤ 100,000)둘째 줄부터 V개의 줄에 걸쳐 간선의 정보가 다음과 같이 주어진다. 정점 번호는 1부터 V까지 매겨져 있다.</p>
<p>먼저 정점 번호가 주어지고, 이어서 연결된 간선의 정보를 의미하는 정수가 두 개씩 주어지는데, 하나는 정점번호, 다른 하나는 그 정점까지의 거리이다. 예를 들어 네 번째 줄의 경우 정점 3은 정점 1과 거리가 2인 간선으로 연결되어 있고, 정점 4와는 거리가 3인 간선으로 연결되어 있는 것을 보여준다. 각 줄의 마지막에는 -1이 입력으로 주어진다. 주어지는 거리는 모두 10,000 이하의 자연수이다.</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 트리의 지름을 출력한다.</p>
<h4 id="풀이">풀이</h4>
<p>트리의 지름을 찾는 방법 :
임의의 한 정점에서 가장 먼 정점을 찾고
그 정점에서 가장 먼 정점과의 거리가 트리의 지름이다.
<a href="https://velog.io/@zioo/%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84-%EA%B5%AC%ED%95%98%EA%B8%B0">증명 링크</a></p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    static int V;
    static ArrayList[] graph;
    static boolean[] visited;
    static int[] distance;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        V = Integer.parseInt(br.readLine());
        StringTokenizer st = null;
        graph = new ArrayList[V + 1];
        for(int i = 0; i &lt; V; i++) {
            st = new StringTokenizer(br.readLine());
            int order = Integer.parseInt(st.nextToken());
            graph[order] =  new ArrayList&lt;&gt;();
            int input = Integer.parseInt(st.nextToken());
            while(input != -1) {
                Edge edge = new Edge(input, Integer.parseInt(st.nextToken()));
                graph[order].add(edge);
                input = Integer.parseInt(st.nextToken());
            }
        }
        visited = new boolean[V + 1];
        distance = new int[V + 1];
        BFS(1);

        int farFromFirstValue = 1;
        for(int i = 1; i &lt; V + 1; i++) {
            if(distance[farFromFirstValue] &lt; distance[i])
                farFromFirstValue = i;
        }

        Arrays.fill(visited, false);
        Arrays.fill(distance, 0);
        BFS(farFromFirstValue);

        int maxValue = 1;
        for(int i = 1; i &lt; V + 1; i++) {
            if(distance[maxValue] &lt; distance[i])
                maxValue = i;
        }

        System.out.println(distance[maxValue]);
    }
    public static void BFS(int num) {
        Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();
        queue.add(num);
        visited[num] = true;
        while(!queue.isEmpty()) {
            int now = queue.poll();
            ArrayList&lt;Edge&gt; edgeList = graph[now];
            for(Edge edge : edgeList) {
                if(!visited[edge.node]) {
                    visited[edge.node] = true;
                    queue.add(edge.node);
                    distance[edge.node] = distance[now] + edge.cost;
                }
            }
        }
    }
}
class Edge{
    int node;
    int cost;
    public Edge(int node, int cost) {
        this.node = node;
        this.cost = cost;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[미로 탐색]]></title>
            <link>https://velog.io/@pink-frog/%EB%AF%B8%EB%A1%9C-%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@pink-frog/%EB%AF%B8%EB%A1%9C-%ED%83%90%EC%83%89</guid>
            <pubDate>Tue, 24 Oct 2023 08:44:20 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-2178번-미로-탐색">백준 2178번 미로 탐색</h3>
<p>N×M크기의 배열로 표현되는 미로가 있다.
미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 최소의 칸 수를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.</p>
<p>위의 예에서는 15칸을 지나야 (N, M)의 위치로 이동할 수 있다. 칸을 셀 때에는 시작 위치와 도착 위치도 포함한다.</p>
<h4 id="입력">입력</h4>
<p>첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.</p>
<h4 id="출력">출력</h4>
<p>첫째 줄에 지나야 하는 최소의 칸 수를 출력한다. 항상 도착위치로 이동할 수 있는 경우만 입력으로 주어진다.</p>
<h4 id="소스">소스</h4>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;


public class Main {
    static int N;
    static int M;
    static int[][] input;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        input = new int[N][M];
        for(int i = 0; i &lt; N; i++) {
            String line = br.readLine();
            String[] lineArry = line.split(&quot;&quot;);
            for(int j = 0; j &lt; M; j++) {
                input[i][j] = Integer.parseInt(lineArry[j]);
            }
        }

        BFS(0, 0);
        System.out.println(input[N-1][M-1]);
    }
    public static void BFS(int row, int column) {
        Queue&lt;int[]&gt; queue = new LinkedList&lt;&gt;();
        queue.add(new int[]{row, column});

        boolean[][] visited = new boolean[N][M];
        visited[row][column] = true;

        int[][] move = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        while(!queue.isEmpty()) {
            int[] now = queue.poll();
            for (int i = 0; i &lt; 4; i++) {
                int[] search = {now[0] + move[i][0], now[1] + move[i][1]};
                if(search[0] &gt; -1 &amp;&amp; search[0] &lt; N &amp;&amp; search[1] &gt; -1 &amp;&amp; search[1] &lt; M) {
                    if (input[search[0]][search[1]] != 0 &amp;&amp; !visited[search[0]][search[1]]) {
                        visited[search[0]][search[1]] = true;
                        input[search[0]][search[1]] = input[now[0]][now[1]] + 1;
                        queue.add(search);
                    }
                }
            }
        }
    }
}
</code></pre>
<p>BFS를 사용해 최초로 도달했을 때 깊이 =&gt; 최단 거리</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[페이지네이션(Pagination)]]></title>
            <link>https://velog.io/@pink-frog/%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98Pagination</link>
            <guid>https://velog.io/@pink-frog/%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98Pagination</guid>
            <pubDate>Fri, 20 Oct 2023 00:42:20 GMT</pubDate>
            <description><![CDATA[<h2 id="페이지네이션pagination">페이지네이션(Pagination)</h2>
<p>여러개의 컨텐츠를 여러 페이지로 나누어 보여주고 페이지 번호 버튼, 이전 버튼, 다음 버튼을 눌러서 페이지를 이동하는 기능</p>
<h3 id="필요한-내용">필요한 내용</h3>
<p>한 페이지에서 보여줄 페이지 버튼의 개수
한 페이지에서 보여줄 컨텐츠 개수
이전 버튼, 다음 버튼</p>
<h3 id="계산해야할-내용">계산해야할 내용</h3>
<p>총 페이지 개수
화면에 보여질 페이지 그룹
화면에 보여질 첫번째 페이지 번호
화면에 보여질 마지막 페이지 번호</p>
<h4 id="총-페이지-개수">총 페이지 개수</h4>
<p><code>Math.ceil(전체 컨텐츠 개수/한 페이지에서 보여줄 컨텐츠 개수)</code>
ex) 총 페이지 개수 = Math.ceil(111/10) = 12페이지</p>
<h4 id="화면에-보여질-페이지-그룹">화면에 보여질 페이지 그룹</h4>
<p><code>Math.ceil(현재 페이지 번호/한 페이지에서 보여줄 페이지 버튼의 개수)</code>
ex) 한 화면에 보여줄 페이지 그룹 = Math.ceil(7/5) = 2번째 페이지 그룹</p>
<h4 id="화면에-보여질-첫번째-페이지-번호">화면에 보여질 첫번째 페이지 번호</h4>
<p><code>((페이지 그룹 - 1) * 한 페이지에서 보여줄 페이지 버튼의 개수) + 1</code>
ex) 화면에 보여질 첫번째 페이지 번호 = ((2-1) * 5) + 1 = 6</p>
<h4 id="화면에-보여질-마지막-페이지-번호">화면에 보여질 마지막 페이지 번호</h4>
<p><code>페이지 그룹 * 한 페이지에서 보여줄 페이지 버튼의 개수</code>
ex) 화면에 보여질 마지막 페이지 번호 = 2 * 5 = 10
<code>하지만 전체 페이지보다 크다면 마지막 페이지 번호 = 총 페이지 개수</code>
ex)현재 페이지가 12 라면
페이지 그룹 : 3
첫번째 페이지 번호 : 11
마지막 페이지 번호 : 15 &gt; 총 페이지 개수 12 이므로 마지막 페이지 번호는 12가 된다.</p>
<h2 id="offset-페이징">Offset 페이징</h2>
<pre><code class="language-sql">SELECT * FROM 테이블
ORDER BY 컬럼
OFFSET 10 LIMIT 5</code></pre>
<p>offset에서 시작하여 limit만큼 가져온다.
JPA &gt; Pageable 객체 사용</p>
<h2 id="cursor-페이징">Cursor 페이징</h2>
<p>offset은 이전의 데이터를 조회하고 건너 뛰는 것이므로 데이터가 많을 수록 시간복잡도가 증가한다. 또한 삽입 삭제가 이루어질 경우 다음페이지에서 이전 페이지와 중복되는 데이터가 보일 수 있다.</p>
<p>cursor는 특정 조건 이후에 있는 데이터만 선별해서 가져온다.</p>
<pre><code class="language-sql">SELECT * FROM 테이블
WHERE cursor &gt; timestamp
ORDER BY timestamp
LIMIT 5</code></pre>
<p>커서 페이징을 사용할 때 주의 사항
기준 컬럼이 중복된 값을 갖지 않고 순차적이어야 한다. 따라서 대부분 timestamp 컬럼을 기준으로 사용한다.
중복이 발생하는 컬럼을 커서로 사용하면 데이터 손실이 발생할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Join과 Subquery]]></title>
            <link>https://velog.io/@pink-frog/Join%EA%B3%BC-Subquery</link>
            <guid>https://velog.io/@pink-frog/Join%EA%B3%BC-Subquery</guid>
            <pubDate>Wed, 18 Oct 2023 01:47:36 GMT</pubDate>
            <description><![CDATA[<h3 id="join">Join</h3>
<p>2개 이상의 테이블을 연결</p>
<h3 id="subquery">Subquery</h3>
<p>외부 쿼리 내에 내부 쿼리가 있는 구조</p>
<h3 id="subquery를-join으로-대체할-수-있는-경우">Subquery를 Join으로 대체할 수 있는 경우</h3>
<ol>
<li>내부 쿼리가 단일 값을 반환하거나 1개의 열과 1개의 행을 반환하는 경우</li>
<li>IN( ) 연산자 안에 서브쿼리가 있는 경우</li>
<li>NOT IN( ) 연산자 안에 서브쿼리가 있는 경우</li>
<li>EXISTS, NOT EXISTS 연산자 안에 서브쿼리가 있는 경우<h3 id="subquery를-join으로-대체할-수-없는-경우">Subquery를 Join으로 대체할 수 없는 경우</h3>
</li>
<li>GROUP BY를 사용한 서브쿼리가 FROM절에 있는 경우</li>
<li>집계된 값을 반환하는 서브쿼리가 WHERE절에 있는 경우</li>
<li>ALL( ) 연산자 안에 서브쿼리가 있는 경우</li>
</ol>
<h3 id="join과-subquery-성능-비교">Join과 Subquery 성능 비교</h3>
<p><a href="https://jojoldu.tistory.com/520">참고 링크</a></p>
]]></description>
        </item>
    </channel>
</rss>