<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jaehoon</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 10 Nov 2024 12:57:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Jaehoon</title>
            <url>https://velog.velcdn.com/images/jaehoon_go/profile/6a5f247e-7b10-4f30-9e78-d42c5f692e20/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Jaehoon. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jaehoon_go" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OpenCLIP 학습 코드 정리]]></title>
            <link>https://velog.io/@jaehoon_go/OpenCLIP-%ED%95%99%EC%8A%B5-%EC%BD%94%EB%93%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jaehoon_go/OpenCLIP-%ED%95%99%EC%8A%B5-%EC%BD%94%EB%93%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 10 Nov 2024 12:57:11 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/mlfoundations/open_clip">https://github.com/mlfoundations/open_clip</a></p>
<h1 id="모델-불러오기">모델 불러오기</h1>
<blockquote>
<p><strong>주의!</strong>
OpenCLIP은 기존 CLIP과 다른 라이브러리를 사용한다.
( pip install open_clip_torch )</p>
</blockquote>
<pre><code class="language-python">import torch
import open_clip

device = &quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;
print(device)

model, _, preprocess = open_clip.create_model_and_transforms(&#39;ViT-B-32&#39;)
state_dict = torch.load(&#39;path_to_pretrained_weight&#39;, map_location=device)
model.load_state_dict(state_dict[&#39;CLIP&#39;])
model.to(device)
tokenizer = open_clip.get_tokenizer(&#39;ViT-B-32&#39;)</code></pre>
<p><strong>state_dict</strong>: 모델의 가중치 및 파라미터를 담고 있는 dict타입 데이터. 학습된 모델을 저장하거나 로드할 때 사용된다. 여기서는 미리 저장된 가중치를 불러왔다.</p>
<p><strong>tokenizer</strong>: 텍스트를 모델 입력에 맞게 토큰화하여 벡터로 변환하는 도구. 각 모델에 맞는 토크나이저를 사용해야한다.</p>
<p><strong>preprocess</strong>: 이미지 데이터를 전처리하는 함수로 마찬가지로 모델이 요구하는 방식의 전처리기를 사용해야한다.</p>
<h1 id="데이터-준비">데이터 준비</h1>
<h2 id="커스텀-데이터셋-정의">커스텀 데이터셋 정의</h2>
<pre><code class="language-python">from PIL import Image
from datasets import load_dataset
from torch.utils.data import Dataset

ds = load_dataset(&quot;tomytjandra/h-and-m-fashion-caption&quot;)

class HMFashionDataset(Dataset):
    def __init__(self, dataset_split, preprocess):
        self.dataset = dataset_split
        self.preprocess = preprocess

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

    def __getitem__(self, idx):
        item = self.dataset[idx]

        # 이미지 데이터 가져오기
        image = item[&#39;image&#39;]
        if isinstance(image, Image.Image):
            image = image.convert(&#39;RGB&#39;)
        else:
            image = Image.open(image).convert(&#39;RGB&#39;)

        image = self.preprocess(image)
        caption = item[&#39;text&#39;]
        return image, caption  # 텍스트를 문자열로 반환</code></pre>
<p><strong>load_dataset</strong>: datasets 라이브러리의 함수로, 다양한 공개 데이터를 쉽게 불러올 수 있다. 여기서는 H&amp;M 패션 이미지 - 캡션 데이터를 활용하였다.</p>
<p><a href="https://huggingface.co/datasets/tomytjandra/h-and-m-fashion-caption">허깅페이스</a></p>
<p><strong>Dataset 클래스</strong>: PyTorch에서 사용자 정의 데이터셋을 만들기 위해 상속하는 클래스로, <strong>len</strong>과 <strong>getitem</strong> 메서드를 통해 데이터셋을 정의하게 된다.</p>
<h2 id="데이터-로더-설정">데이터 로더 설정</h2>
<pre><code class="language-python">import torch
from torch.utils.data import DataLoader
import datasets
import random

prompts = [
    &#39;a photo of a {}&#39;,
    &#39;a fashion photo of a {}&#39;,
]

def collate_fn(batch):
    images, captions = zip(*batch)
    images = torch.stack(images)
    prompted_captions = []
    for caption in captions:
        prompt = random.choice(prompts)
        prompted_captions.append(prompt.format(caption))
    texts = tokenizer(prompted_captions)
    return images, texts

print(ds)

if isinstance(ds, datasets.DatasetDict):
    if &#39;train&#39; in ds:
        # ds[&#39;train&#39;]을 훈련용과 테스트용으로 분할
        split_ds = ds[&#39;train&#39;].train_test_split(test_size=0.2, seed=42)
    else:
        raise KeyError(&quot;The dataset does not contain a &#39;train&#39; split.&quot;)
else:
    # ds 자체가 Dataset인 경우
    split_ds = ds.train_test_split(test_size=0.2, seed=42)

# 데이터셋 스플릿
train_dataset = HMFashionDataset(split_ds[&#39;train&#39;], preprocess)
test_dataset = HMFashionDataset(split_ds[&#39;test&#39;], preprocess)

train_dataloader = DataLoader(
    train_dataset,
    batch_size=512,
    shuffle=True,
    num_workers=0,
    collate_fn=collate_fn
)

test_dataloader = DataLoader(
    test_dataset,
    batch_size=512,
    shuffle=False,
    num_workers=0,
    collate_fn=collate_fn
)</code></pre>
<p><strong>prompts</strong>: 텍스트 입력을 생성하기 위해 사용되는 미리 정의된 프롬프트로, 주로 ‘a photo of {}’를 사용하지만 데이터셋의 특징에 따라서 다르게 설정할 수도 있다.</p>
<p><strong>collate_fn</strong>: 데이터로더의 각 배치에서 호출되어 데이터를 배치에 맞게 정리하는 함수로 앞서 정의한 프롬프트를 적용하거나, torch.stack를 사용하여 텐서들을 하나의 배치로 결합할 수 있다.</p>
<p><strong>DataLoader</strong>: 데이터셋을 반복 가능한(iterable) 형태로 만들어주는 파이토치 유틸리티</p>
<p><strong>num_workers</strong>: 병렬 데이터 로딩을 위한 프로세스 수를 설정하는 옵션</p>
<h1 id="모델-학습">모델 학습</h1>
<h2 id="하이퍼-파라미터-설정">하이퍼 파라미터 설정</h2>
<pre><code class="language-python">import torch
import torch.nn.functional as F
from torch.amp import GradScaler
from tqdm import tqdm

# 손실 함수 정의 (CLIP의 대조적 손실 함수)
def clip_loss(logits_per_image, logits_per_text):
    batch_size = logits_per_image.size(0)
    labels = torch.arange(batch_size, dtype=torch.long, device=logits_per_image.device)

    loss_i = F.cross_entropy(logits_per_image, labels)
    loss_t = F.cross_entropy(logits_per_text, labels)

    return (loss_i + loss_t) / 2

# visual encoder 파라미터만 학습 가능하도록 설정
for param in model.parameters():
    param.requires_grad = False  # 모든 파라미터를 고정
for param in model.image_encoder.parameters():  # visual encoder 파라미터만 학습 가능
    param.requires_grad = True

# 학습 가능한 파라미터만 필터링하여 전달
optimizer = torch.optim.AdamW(
    filter(lambda p: p.requires_grad, model.parameters()),  
    lr=5e-6,
    betas=(0.9, 0.98),
    eps=1e-6,
    weight_decay=0.2
)

# 스케일러 정의
scaler = GradScaler()

# 에포크 수 및 total_steps 계산
epochs = 10
total_steps = epochs * len(train_dataloader)

# 스케줄러 정의
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=total_steps)</code></pre>
<p><strong>torch.arange</strong>: 일정 범위의 정수 배열을 생성하는 함수로, 라벨 생성에 사용된다.</p>
<p><strong>AdamW</strong>: AdamW는 학습률을 조정하며 가중치 감쇠를 적용하는 Adam의 변형 옵티마이저를 말한다. 대안으로 SGD, Adam, RMSprop 등이 있으며, 모델의 특성과 학습 속도에 따라 다른 옵티마이저를 사용할 수 있다.
<a href="https://rla020.tistory.com/41">optimizer 정리글</a></p>
<p><strong>lr, betas, eps, weight_decay</strong>: AdamW 옵티마이저의 하이퍼파라미터. lr은 학습률, betas는 모멘텀, eps는 수치적 안정성을 위한 작은 값, weight_decay는 과적합 방지를 위한 L2 정규화를 나타낸다.</p>
<p><strong>GradScaler</strong>: Mixed Precision 학습에서 손실 스케일링 적용
<a href="https://computing-jhson.tistory.com/37">Auto Mixed Precision</a></p>
<p><strong>scheduler</strong>: 학습률을 점진적으로 감소시키는 역할</p>
<h2 id="학습">학습</h2>
<pre><code class="language-python">from tqdm import tqdm

model.train()

for epoch in range(epochs):
    progress_bar = tqdm(train_dataloader, desc=f&quot;Epoch {epoch+1}/{epochs}&quot;)
    for batch in progress_bar:
        images, texts = batch
        images = images.to(device)
        texts = texts.to(device)

        optimizer.zero_grad()

        with torch.autocast(device_type=&#39;cuda&#39;, dtype=torch.float16):
            # 이미지와 텍스트 임베딩 추출
            image_features = model.encode_image(images)
            text_features = model.encode_text(texts)

            # 임베딩 정규화
            image_features = image_features / image_features.norm(dim=1, keepdim=True)
            text_features = text_features / text_features.norm(dim=1, keepdim=True)

            # 유사도 계산
            logit_scale = model.logit_scale.exp()
            logits_per_image = logit_scale * image_features @ text_features.t()
            logits_per_text = logits_per_image.t()

            # 손실 함수 계산
            loss = clip_loss(logits_per_image, logits_per_text)

        scaler.scale(loss).backward()
        scaler.step(optimizer)
        scaler.update()
        scheduler.step()  # 각 배치마다 호출

        progress_bar.set_postfix(loss=loss.item())</code></pre>
<p><strong>임베딩 정규화</strong>:  정규화는 벡터의 크기가 1이 되도록 벡터를 스케일링하여 임베딩 간에 비교할 때 크기가 아닌 방향만을 고려하게 하는 역할을 한다.</p>
<p><strong>logit_scale</strong>: 모델에 포함된 logit_scale 파라미터를 지수 함수로 변환하여 이미지와 텍스트 임베딩 사이의 유사도 분포를 조절하여 학습의 안정성을 높이고 성능을 향상 시키는 역할을 한다.</p>
<p><strong>tqdm</strong>: 진행 상황 표시바를 보여주는 라이브러리로, 반복문이 실행될 때 진행률을 아래처럼 확인할 수 잇게 해준다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/70613492-6891-489f-856a-7ace03e13b62/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Discovering and Mitigating Visual Biases through Keyword Explanation (CVPR 2024)]]></title>
            <link>https://velog.io/@jaehoon_go/Discovering-and-Mitigating-Visual-Biases-through-Keyword-Explanation</link>
            <guid>https://velog.io/@jaehoon_go/Discovering-and-Mitigating-Visual-Biases-through-Keyword-Explanation</guid>
            <pubDate>Sun, 13 Oct 2024 12:48:34 GMT</pubDate>
            <description><![CDATA[<h2 id="contribution">Contribution</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/782157dd-45bb-4c1f-88ac-22280a4874e0/image.png" alt=""></p>
<blockquote>
<p>Bias-to-Text(B2T): 시각적 편향을 키워드로 추출 (잠재적 편향 검출) 
⇒ 키워드 검증(CLIP score)을 통해 해당 키워드의 임베딩이 정답 캡션보다 이미지와 가까운지 여부를 판단</p>
</blockquote>
<ul>
<li>이미 잘 알려진 bias (CelebA, Waterbirds 등) 뿐만 아니라
새로운 bias도 찾아냄 ⇒  꽃(flower)이 포함된 image에서 개미(ant)를 벌(bee)로 오인</li>
<li>이렇게 찾아낸 bias keywords를 가지고 DRO와 같은 편향 제거 훈련에 이용하거나, CLIP prompting에 적용하거나, 다른 모델과 비교할 수 있음. 또한 잘못된 레이블을 검출할 수도 있음</li>
</ul>
<h1 id="bias-to-text-b2t-framework">Bias-to-Text (B2T) Framework</h1>
<h2 id="problem-formulation">Problem formulation</h2>
<p>image $x \in \mathcal X$ 에 대해서 클래스 $y \in \mathcal Y$ 를 예측하는 classifier가 있을 때, </p>
<p>자주 틀리는 속성 $a$가 있을 경우 $y$에 대한 bias으로 정의 ⇒ Keyword 설명 형식</p>
<p>*검출되는 bias에는 spurious correlation이나 distribution shifts가 있음</p>
<h2 id="bias-keywords">Bias Keywords</h2>
<p>잘못 예측된 클래스의 이미지들의 caption에서 공통된 Keywords를 추출 
⇒ Minority subgroups가 이 과정에서 자주 등장 (ex: Man - Blonde hair)</p>
<p>*Captioning Model로 <code>ClipCap</code>, Keywords Extraction Algorithm으로 <code>YAKE</code> 적용</p>
<h2 id="clip-score">CLIP score</h2>
<p>추출한 kewords가 실제로 bias를 나타내는지 검증하기 위해 CLIP과 같은 vision-language scoring model 사용 ⇒ 편향된 컨셉과 관련된 keyword에서 높은 점수가 나타남</p>
<p>$s_{CLIP}(a; \mathcal D) := \text{sim}(a,\mathcal D_{\text{wrong}}) - \text{sim}(a,\mathcal D_{\text{correct}})$</p>
<p>$\text{sim}(a,\mathcal D) := \frac{1}{|\mathcal D|} \sum_{x \in \mathcal D}f_\text {image}(x)f_\text{text}(a)$</p>
<blockquote>
<p>$\mathcal D_{\text{wrong}}$, $\mathcal D_{\text{correct}}$는 class-wise validation set $\mathcal D$의 subset
keyword $a$와 dataset $\mathcal D$ 사이의 similarity</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/b7db7f48-c285-402a-939a-8a14ce9137ac/image.png" alt=""></p>
<p><strong>(a)</strong> ‘species’와 ‘bird’는 예측에 성공/실패한 이미지에서 공통적으로 나타나기 때문에 non-bias이고, 따라서 CLIP score도 낮게 나타남. ↔ ‘bamboo’, ‘forest’, ‘woods’는 잘못된 예측에서 더 높은 유사성을 보이므로 CLIP score도 높음.
<strong>(b)</strong> subgroup accuracy(AUROC) for keywords <strong>(c)</strong> CLIP score와 AUROC 간의 상관관계(-0.95)</p>
<h1 id="discovering-biases-in-image-classifiers">Discovering Biases in Image Classifiers</h1>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/a17d7f2b-d9a7-4ba2-b263-f5ef63358b03/image.png" alt=""></p>
<p><strong>Known Biases (a)</strong> gender bias in CelebA blond <strong>(b)</strong> background bias in Waterbirds <strong>(c)</strong> distribution shifts in ImageNet-R with different styles <strong>(d)</strong> ImageNet-C with natural corruptions
<strong>Novel Biases  (e)</strong> spurious correlations between the keyword “cave” and wardrobe class indicating geographical bias (f) the keyword “flower” and ant class indicating contextual bias</p>
<h2 id="known-biases">known biases</h2>
<h3 id="spurious-correlation">Spurious correlation</h3>
<p>(ERM) CelebA의 Blond 클래스에 대해 B2T가 <strong>“man”</strong> 키워드를, Waterbirds에 대해서 <strong>“forest”</strong>, <strong>“ocean”</strong>을 포착하여 성별, 배경 편향을 찾아냈을 뿐만 아니라 기존의 background annotation인 <strong>“land”</strong>에 비해 더 정확한 keyword인 <strong>“bamboo”</strong>를 찾아냄</p>
<h3 id="distribution-shifts">Distribution shifts</h3>
<p>(ResNet-50) B2T는 ImageNet-R에서 키워드 <strong>“illustration”</strong>, <strong>“drawing”</strong>, 좀 더 자세하게는 
<strong>“hand-drawn”</strong>, <strong>“vector-art”</strong>를 찾아내었고, ImageNet-C 에서는 <strong>“snow”</strong>(snow corruption), <strong>“window”</strong>(frost-corruption)를 찾아냄.</p>
<h2 id="sample-wise-bias-labeling">Sample-wise bias labeling</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/a12d75a2-247c-41b4-b922-39133c79ac68/image.png" alt=""></p>
<p>이렇게 찾아낸 keywords를 CLIP zero-shot classifier에 적용하여 샘플 단위로 편항을 라벨링 할 수 있음</p>
<p><code>“a photo of [group]”</code>과 같이 bias keyword를 입력한 프롬프트를 통해 group labeling을 진행하고</p>
<p>ground-truth bias가 존재하는 CelebA, Waterbirds에 대해 기존 방법들과 비교 분석</p>
<p>⇒ 거의 최적에 가까운 결과를 보임</p>
<h2 id="novel-biases">novel biases</h2>
<p><strong>Dollar Street</strong>(Figure4. e)은 다양한 소득 수준을 가진 국가들의 객체 이미지들을 포함. 이전의 연구들은 이미지 분류기가 저소득 국가에서 낮은 성능을 보인다는 것을 보임.</p>
<p>⇒ B2T를 Dollar Street Validation Set에서 ImageNet을 사용하여 이러한 편향을 분석하였음</p>
<p>몇가지 예시로</p>
<p><strong>“cave”</strong> (동굴): “wardrobe” (옷장) 클래스에서 저소득 국가의 옷장이 어두운 곳에 있는 경우가 많아 동굴처럼 보이는 경향</p>
<p><strong>“fire”</strong> (불): “stove” (난로) 클래스에서, 저소득 국가의 전통적인 디자인의 난로는 종종 불을 사용하는 방식</p>
<p>이러한 객체의 차이는 국가 간의 <strong>지리적 편향</strong>을 나타내며, 분류기가 고소득 국가의 객체는 잘 예측하지만 저소득 국가의 객체는 잘못 예측하는 원인을 설명</p>
<p>ImageNet(Figure4. f)에서는 여러 객체가 동시에 존재함으로 인해서 발생하는 contextual biases를 발견할 수 있었는데, </p>
<p>“flower”(꽃)과 함께 존재하는 “ant”(개미)를 “bee”(벌)로 예측하였다. 이는 벌이 개미보다 꽃과 더 강한 연관성을 가지고 있음을 시사한다.</p>
<ul>
<li>“playground”(놀이터)에서 “horizontal bar”(철봉)을 “swing”(그네)로 오인한다.</li>
</ul>
<h1 id="applications-of-the-b2t-keywords">Applications of the B2T Keywords</h1>
<p>B2T를 사용해서 얻어낸 키워드들은 학습, 프롬프팅, 모델 비교, 레이블 진단 등에 사용할 수 있다.</p>
<h2 id="debiased-dro-training"><strong>Debiased DRO training</strong></h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/8068a36d-8c7e-4659-8fb4-01429133b83d/image.png" alt=""></p>
<p>앞서 구한 Sample-wise bias label을 이용해서 DRO(distributionally robust optimization)의 group label로 적용한 DRO-B2T의 성능을 측정하였다.</p>
<p>가장 성능이 나쁜 그룹의 정확도를 나타내는 WGA(worst-group accuracy)가 오히려 기존 ground truth를 사용한 DRO보다 능가하였다.</p>
<h2 id="clip-zero-shot-prompting">CLIP zero-shot prompting</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/6b7ba5c9-5b56-495c-872b-266a23211b50/image.png" alt=""></p>
<p>CLIP이 기본적으로 사용하는 “a photo of a [class]“ 프롬프트에 키워드를 추가하여  “a photo of a [class] in the [group]“와 같은 형식을 사용한다.</p>
<p>B2T 키워드 중에 앞서 구한 CLIP score를 가지고 B2T-pos(ex: “ocean”), B2T-neg(ex: “bird)를 [group]에 대입하여 실험한 결과 positive 키워드를 사용했을 때 worst-group accuracy, average accuracy 모두 향상되었고, 반대로 negative 키워드를 사용했을 때는 오히려 성능이 나빠진 것을 알 수 있다.</p>
<h2 id="model-comparison"><strong>Model comparison</strong></h2>
<p><strong>ResNet vs. ViT</strong></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/2ebd3a39-a036-40dd-b10f-96d83aefab90/image.png" alt=""></p>
<p>ViT는 ResNet보다 더 전반적인 문맥 이해와와 세밀한 클래스 분류에서 더 우수한 성능을 보임.</p>
<p>가령 ViT는 “work out”과 같은 추상적인 편향 키워드도 성공적으로 예측하였음</p>
<p>반면 ResNet은 “horizontal bar”를 “dumbbell”로, “shopping basket”을 “grocery store”로 잘못 예측하는 등 복잡한 이미지에서 어려움을 보임</p>
<p><strong>ERM vs. DRO</strong></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/bc45c9be-430a-4a7a-9d3a-96392c225666/image.png" alt=""></p>
<p>CelebA와 Waterbirds 데이터셋에서 ERM과 DRO를 비교하였을 때, </p>
<p>DRO는 편향 키워드를 줄이거나 거의 완전히 제거에 성공하였음, CelebA blond에서 “man” 키워드가 사라졌고 Waterbirds에서 “seagull”의 CLIP 점수가 3.10에서 1.85로 감소하였음.</p>
<h2 id="label-diagnosis"><strong>Label diagnosis</strong></h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/b5da8916-9c2a-44f3-b983-067d005037a7/image.png" alt=""></p>
<p>B2T는 잘못된 레이블 및 레이블 모호성을 진단하는 데 사용할 수 있는데, ImageNet에 존재하는 레이블 오류를 발견하였음.</p>
<p>B2T를 통해 “bee”가 “fly”로, “boar”가 “pig”로 잘못 레이블링된 이미지를 발견하였고, 또한 “desk”, “market”과 같이 대체적으로 여러 객체가 한번에 포함되어 모호한 레이블도 판별 할 수 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Audio-Visual Segmentation]]></title>
            <link>https://velog.io/@jaehoon_go/Audio-Visual-Segmentation</link>
            <guid>https://velog.io/@jaehoon_go/Audio-Visual-Segmentation</guid>
            <pubDate>Sun, 04 Aug 2024 09:05:23 GMT</pubDate>
            <description><![CDATA[<p><a href="https://arxiv.org/abs/2207.05042">Audio-Visual Segmentation (ECCV 2022)</a>
<a href="https://github.com/OpenNLPLab/AVSBench">Source Code - GitHub</a></p>
<h1 id="introduction">Introduction</h1>
<p>본 논문에서는 이미지 프레임에서 소리를 내는 물체를 픽셀 단위로 구분하는 Task인 Audio-Visual Segmentation (AVS)를 제시한다. 기존에 존재하던 Task와의 차이점, 새로운 Dataset과 Baseline model 그리고 실험 결과에 대해 알아보자.</p>
<h1 id="related-field">Related Field</h1>
<p><strong>AVC(Audio-Visual Correspondence)</strong>는 오디오와 이미지가 같은 scene에 해당하는지 판단하며, <strong>AVEL(Audio-Visual Event Localization)</strong>는 사전에 학습된 event label로 video segment를 분류한다. <strong>AVVP(Audio-Visual Video Parsing)</strong>는 비디오를 몇가지 event로 나누고 소리, 프레임, 또는 모두를 label에 따라 분류한다.</p>
<p>이러한 작업들은 프레임/시간 수준으로 제한되므로 새로운 Task는 소리가 나는 물체를 분류하는 것으로 범위를 줄인다.</p>
<p><strong>SSL (Sound Source Localization)</strong>은 이중 가장 AVS와 가까운 작업으로, 프레임 내부에서 주어진 소리와 일치하는 영역을 찾아낸다.
그러나 SSL은 patch 단위로 이루어져있고, heat map으로 영역을 표시하므로 소리를 내는 객체의 모양을 정확히 표시하지는 않는다.</p>
<h1 id="audio-visual-segmentation">Audio-Visual Segmentation</h1>
<p><strong>AVS (Audio-Visual Segmentation)</strong>는 각 pixel이 해당 audio와 일치하는지 파악하여 sounding object와 겹치도록 mask를 생성한다.
<img src="https://velog.velcdn.com/images/jaehoon_go/post/9b9cccb3-e36d-4d08-aea7-7d560e9e7a5c/image.png" alt=""></p>
<p>위 비디오 프레임 중 두 번째 행이 SSL, 세 번째 행이 AVS를 나타낸다. SSL은 patch 단위의 히트맵으로 표시된다. AVS는 pixel 단위로 물체를 표시하며, 이는 sounding object가 복수인 경우에도 마찬가지이다.</p>
<h1 id="avsbench">AVSBench</h1>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/265f0679-abd3-4e56-b5d3-4592dd6aac1f/image.png" alt=""></p>
<p>기존 데이터셋 중에서는 pixel 단위의 label를 제공하는 것이 없었다. frame에 대한 event만 분류하거나(AVE, LLP), target sound source의 outline이 되는 bounding box만 제공한다(Flickr-SoundNet, VGG-SS). 따라서 저자는 새로운 Task 학습에 적합한 Dataset인 AVSBench를 제시한다.</p>
<p>AVSBench는 sounding object의 수에 따라 Single-source와 Multi-sources로 구분한다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/28ac06ff-c57d-4e61-b11a-4bed78d38b30/image.png" alt=""></p>
<p>각 데이터 셋은 5초 분량의 비디오가 1초 클립 5개로 나누어진 형태이며 label은 각 클립의 마지막 프레임에 제시된다. label은 binary mask 형태로 되어있으며, sounding object를 pixel-level로 표시하는 역할을 한다.</p>
<p>이 때 source 수에 따라 labeling 방식이 조금 다르다.</p>
<h3 id="semi-supervised-single-sound-source-segmentation-s4">semi-supervised Single Sound Source Segmentation (S4)</h3>
<p>Single-source의 학습 데이터 부분의 경우는 각 비디오의 5개의 클립 중 첫번째 클립에서만 label이 제공된다. 이는 single-source의 경우에는 one-shot annotation으로 충분하다는 가정에 의한 것이다.</p>
<h3 id="fully-supervised-multiple-sound-source-segmentation-ms3">fully-supervised Multiple Sound Source SEgmentation (MS3)</h3>
<p>Multi-sources의 경우에는 좀 더 어려운 Task이기 때문에. 모든 학습 데이터의 클립에 label이 존재한다. 실제 데이터셋을 살펴보면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/16565ca0-cc97-4109-bfa4-08182132dc65/image.png" alt=""></p>
<h1 id="baseline">Baseline</h1>
<p>논문에서는 AVS를 위한 End-to-End framework를 제시하는데, temporal pixel-wise audio-visual 상관관계를 인코딩하기 위한 TPAVI 모듈과 audio-visual correlation을 활용하기 위한 regularization loss가 포함되어있다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/098fd58a-f0f6-40cf-b68c-777d42fae451/image.png" alt=""></p>
<h3 id="encoder">Encoder</h3>
<p>Audio와 Video frame의 Encoding은 독립적으로 진행된다. 우선 auido clip은 short-time Fourier transform을 거친 후 VGGish를 통해 Txd 차원(d=128)의 audio feature가 추출된다. Visual feature의 경우는 convolution 또는 transformer 기반의 백본에 의해 처리된다.</p>
<h3 id="cross-modal-fusion">Cross-Modal Fusion</h3>
<p>앞서 추출된 visual feature를 후처리하기 위해서 Atrous Spatial Pyramid Pooling (ASPP)이 사용된다. 이러한 후처리는 병렬적으로 수행되는데, 이로 인해 서로 다른 크기의 receptive field를 갖는 객체를 인식할 수 있게 된다.</p>
<h3 id="tpavi">TPAVI</h3>
<p>ASPP까지 거친 visual feature를 이제 audio의 feature와 mapping하는 작업이 필요하다. 이를 통해 어떤 물체가 소리를 내고 있는지 파악할 수 있기 때문이다. 이를 위해 Temporal Pixel-wise Audio-visual Interaction (TPVAI)을 인코딩하는 데, sound source의 소리와 모습이 항상 동시에 나타나지는 않기 때문에(예: 화면 밖에서 등장하는 경우) 한 video frame에 대해서 모든 audio signal을 고려하는 non-local neural networks 방식을 차용했다.
<img src="https://velog.velcdn.com/images/jaehoon_go/post/79c5d0b3-0db2-487d-8b78-b84ff6cfc7c1/image.png" alt="">
이때 audio feature는 visual feature와 같은 차원으로 변환된 후 hi * wi 만큼 복제후 재배열되는 방식으로 처리되어 TPAVI에 입력된다.</p>
<p>소리와 프레임 간의 관계를 나타내는 audio-visual interaction은 내적 연산에 의해 측정될 수 있다. 아래 식을 보자</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/05c1d943-f2f2-498b-8d16-3d78f8f5dc02/image.png" alt="">
이때 θ, φ, g and μ는 1×1×1 convolution 연산이며, N은 T×hi×wi 크기의 Normalization factor, αi는 the audio-visual similarity이며 Zi는 RT×hi×wi×C의 크기를 갖는다. TPAVI 내에서 각각의 픽셀들은 전체 audio와 상호작용한다.</p>
<p>아래 사진은 실제로 audio와 pixel의 유사도를 나타낸 것으로 밝을 수록 유사도가 높은 구역이다.
<img src="https://velog.velcdn.com/images/jaehoon_go/post/bf6df5d0-4c0f-4cdb-951f-ee757726375a/image.png" alt=""></p>
<h1 id="experiments">Experiments</h1>
<h3 id="comparison-with-methods-from-related-tasks">Comparison with methods from related tasks</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/fb1daf55-15ea-4faa-acd1-f6c0ab1786cf/image.png" alt="">
SOD method인 LGVT가 ResNet50 기반 AVS 모델을 Single-Source Set에 관한 지표에서 조금 앞섰지만 Multi-Source 지표에서는 훨씬 밀린다. </p>
<p>⇒ 이것은 SOD가 소리내는 물체는 바뀌지만 화면은 그대로인 경우를 감지하지 못하기 때문인 것으로 보인다.</p>
<p>⇒ 반면 AVS는 Audio 전체를 참고하기 때문에 Visual Frame에서 어떤 객체를 포착할지 알아챈다.</p>
<p>Single Source인 경우 조금 앞서는 것도 LGVT가 Swin-Transformer 기반이기 때문에 Backbone 자체의 성능이 좋아서 그런 것 같고, Transformer 기반의 PVT를 쓸 경우에는 두 지표에서 LGVT를 모두 앞선다.</p>
<h3 id="qualitative-examples-of-the-ssl-methods-and-avs">Qualitative examples of the SSL methods and AVS</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/2917a207-cb5a-4486-8277-84a158da3979/image.png" alt="">
fully-supervised MS3 환경에서, SSL 메소드들(LVS, MSSL)은 대략적인 위치만 찾아냈지만, AVS는 객체의 더 정확한 pixel 단위의 모양을 구분해낼 수 있었다. </p>
<h3 id="qualitative-examples-of-the-vos-sod-and-avs">Qualitative examples of the VOS, SOD, and AVS</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/10750005-f6ae-46c6-947c-5b5fe683b887/image.png" alt="">
MS3 환경에서 VOS(Video Object Segmentation)의 SOTA mothod인 SST와 SOD(Sounding object Detection)의 method LGVT와 비교해보았을 때, AVS는 다른 두 방법과 달리 sounding object의 변화를 정확하게 잘 잡아내는 것을 알 수 있다. baby, dog 예시와 같이 중간에 소리를 내는 물체가 변화할 경우 SST와 LGVT는 계속 하나만 포착하거나, 두개를 포착하는 결과를 보여준다.</p>
<h3 id="comparison-with-a-two-stage-baseline-method">Comparison with a two-stage baseline method</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/d20ae961-8cc8-4620-aae8-ac4967fa77eb/image.png" alt="">
Two-Stage 구조에서 first-stage에 Mask R-CNN을 사용하여 segmentation quality를 증가시키더라도 AVS task 자체의 성능에 큰 영향을 미치지 않는다. 오히려 audio signal의 영향을 훨씬 많이 받는다.</p>
<h3 id="impact-of-audio-signal-and-tpavi">Impact of audio signal and TPAVI</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/84569b85-8539-4a32-8cbc-a7c01b740615/image.png" alt="">
중간에 있는 row는 단순히 audio와 visual feature를 더한 것인데 이것만으로도 어느 정도 성능이 향상되었다.</p>
<h3 id="qualitative-results-under-the-semi-supervised-s4-setting">Qualitative results under the semi-supervised S4 setting</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/a03b0f56-f253-4b28-b81d-b7d74c9c6b6a/image.png" alt="">
TPAVI를 통해서 비디오에 존재하는 sounding object의 형태와 올바른 sound source를 학습하게 된다.</p>
<h3 id="qualitative-results-under-the-fully-supervised-ms3-setting">Qualitative results under the fully-supervised MS3 setting</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/2f85b4a3-bc41-438d-9847-7a339645f1c0/image.png" alt=""></p>
<p>TPAVI를 적용한 모델이 사람의 손과 같이 소리와 직접적으로 관련이 없는 객체를 필터링하거나 노래 부르는 사람과 같이 더 정확한 객체를 포착하는등 더 뛰어난 성능을 보였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Residual Learning의 이해와 ResNet-18 구현]]></title>
            <link>https://velog.io/@jaehoon_go/Residual-Learning%EC%9D%98-%EC%9D%B4%ED%95%B4%EC%99%80-ResNet-18-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jaehoon_go/Residual-Learning%EC%9D%98-%EC%9D%B4%ED%95%B4%EC%99%80-ResNet-18-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sun, 21 Jul 2024 13:52:07 GMT</pubDate>
            <description><![CDATA[<h1 id="paper-review">Paper Review</h1>
<h2 id="요약">요약</h2>
<p>Residual Learning(잔차 학습)은 레이어의 입력을 Reference로 하는 Deep Learning 기법이다.</p>
<p>ResNet은 기존 모델에 비해 상당한 깊이에서도 높은 정확도를 유지할 뿐 아니라, 빠른 학습 시간을 보여준다. ILSVRC 2015에서 우승하였으며 CIFAR 10, 100 1000 image classification과 Detection, Localization, Segmentation에서도 뛰어난 성능을 보인다.</p>
<h2 id="introduction">Introduction</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/ce6f695a-a21d-4b13-b2e9-39943ec6b4ad/image.png" alt=""></p>
<p>Deep Network는 입력에 가까울수록 지역적인 low feature가, 출력에 가까울수록 전역적이고 추상적인 high feature가 나타난다. 깊이에 따라 이러한 feature level이 다양해지므로 network 깊이는 중요한 요소라고 할 수 있다.</p>
<blockquote>
<p>그렇다면 layer가 많으면 많을 수록 좋은 network일까?</p>
</blockquote>
<p>기존에 존재하던 기울기 소실/폭발 문제는 초기 정규화(normalized initialization)와 중간 정규화 층(intermediate normalization layer)을 통해 어느정도 해결됐다.
<img src="https://velog.velcdn.com/images/jaehoon_go/post/7a99c78a-4df0-481b-94a3-46538206ee33/image.png" alt=""></p>
<p>하지만 network가 점점 깊어지면서 <strong>degradation</strong>이라는 새로운 문제가 발생한다. degradation은 deeper network의 정확도가 포화(saturated)되었다가 급격히 저하(degraded)되는 것을 뜻한다.</p>
<p>degradation은 높은 traning error를 보인다는 점에서, 높은 training accuracy를 보이는 과적합(overfitting)과는 별개의 문제라고 할 수 있다.
<img src="https://velog.velcdn.com/images/jaehoon_go/post/9f76d0c5-99ff-4f35-b516-c98c9fce4a86/image.png" alt=""></p>
<p>저자는 이러한 문제를 해결하기 위해 Deep Residual Learning 구조를 제안한다. 입력이 x, relu를 거치기 이전의 출력이 H(x)라면, H(x) = F(x) + x 로 나타낼 수 있다. 이와 같이 입력이 층을 건너뛰는 것을 skip connection 이라고 한다. </p>
<p>본 논문에서는 skip connection을 identity mapping 즉, 다른 추가적인 파라미터나 연산없이 구현하였다. 이로써 전체 network는 여전히 확률적 경사하강법(Stochastic Gradient Descent)을 통해 E2E 학습을 진행할 수 있다.</p>
<h2 id="deep-residual-learning">Deep Residual Learning</h2>
<h3 id="residual-learning-잔차-학습">Residual Learning (잔차 학습)</h3>
<p>그렇다면 H(x) = F(x) + x 와 같은 새로운 구조를 제시한 이유는 무엇일까? x는 우리가 이미 알고 있는 값이므로 관점을 바꾸어 F(x) = H(x) - x 를 학습시킨다고 생각해보자. </p>
<p>만약 정답의 형태가 H(x) = x 와 같은 identity mapping이라면 Residual Learning에서는 F(x) = 0 이 되야 하므로 이러한 결과가 나오도록 학습시키는 것이 (Residual Learning이 아닌) plain network가 H(x) = x가 되도록 학습시키는 것보다 훨씬 쉽기 때문이다.</p>
<p>물론 항상 정답이 H(x) = x 가 되는 것은 아니지만 아무것도 없는 상태에서 multiple layers를 어떤 함수에 근사 시키는 것보다는 x라는 reference를 제공하는 identity mapping을 바탕으로 정답을 찾아나가는 것이 학습에 유리하다.</p>
<h3 id="identity-mapping-by-shortcuts">Identity Mapping by Shortcuts</h3>
<p>identity mapping은 아래와 같이 나타낼 수 있다
<img src="https://velog.velcdn.com/images/jaehoon_go/post/472d99c7-19ee-40c6-ae10-20ed4854f24b/image.png" alt=""></p>
<p>Figure 2를 예시로 들면 F = W2σ(W1x)인데, σ는 ReLU 함수이고 bias는 표기상의 편의를 위해 빠졌다. F + x 연산은 shortcut connection과 element-wise addition으로 수행된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/0f41dbae-b7b8-432b-90ed-2a299a892957/image.png" alt=""></p>
<p>행렬 덧셈의 특성상 F와 x의 차원이 같아야 하는데, 만약 그렇지 않을 경우에는 차원을 맞추기 위해 Ws를 x에 곱한 후 더해준다.</p>
<h3 id="network-architecturesfor-imagenet">Network Architectures(for ImageNet)</h3>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/4a7ebbd4-d13d-4f30-ab29-520be4ef010e/image.png" alt=""></p>
<h4 id="plain-network">Plain Network</h4>
<p>VGG Net(Fig.3 왼쪽)에서와 같이 convolutional layer는 거의 3x3 필터를 사용하고 두가지 디자인 규칙을 적용한다.</p>
<ol>
<li>동일한 feature map size에 대해서 각 layer는 똑같은 수의 filter를 가진다.</li>
<li>만약 feature map size가 절반이 되면, filter의 수는 두배로 늘린다.</li>
</ol>
<p>이는 매 layer의 시간 복잡성을 유지하기 위함이다.</p>
<p>여기에 convolutional layer(stride=2)로 직접 downsampling을 수행한다. 마지막에는 global average pooling과 함께 1000-way fully-connected Layer 및 softmax를 통해 분류를 진행하게 된다.(Fig.3 가운데)</p>
<h4 id="residual-network">Residual Network</h4>
<p>Plain Network에서 identity mapping이 추가된다. 차원이 증가할 때는 점선으로 표기하였는데  두 가지 선택지를 고려할 수 있다.</p>
<p>(A) identity mapping을 수행하고 증가한 차원 부분에 대해서는 zero padding을 적용한다.
(B) projection shortcut을 사용한다(Ws, 1x1 conv). </p>
<p>두 경우 모두 size가 반으로 줄어드므로 stride를 2로 설정하였다.</p>
<h3 id="implemetation">Implemetation</h3>
<p>실제 구현은 아래와 같이 진행한다. </p>
<ol>
<li>이미지의 짧은 쪽이 256~480 사이가 되도록 리사이즈를 진행한다.</li>
<li>224x224 사이즈만큼 추출한다(기존 or 좌우 반전) + per-pixel 평균을 뺸다.</li>
<li>standard color augmentation 적용</li>
<li>Batch Normalization을 convolution ~ activation 사이에 적용한다.</li>
<li>He 초기화 방법으로 가중치 초기화</li>
<li>SGD 적용 (mini-batch size : 256)</li>
<li>Learning rate 0.1에서 시작하여 10씩 나눠주며 진행</li>
<li>iteration: 60e4, Weight decay: 0.0001, Momentum: 0.9, dropout X</li>
</ol>
<p>테스트 시에는 10-crop testing을 적용하였고, muliple scale을 사용해 짧은 쪽의 길이가 {224, 256, 384, 480, 640} 중 하나가 되게 한다.</p>
<h2 id="experiments">Experiments</h2>
<h3 id="imagenet-classification">ImageNet Classification</h3>
<p>ImageNet 2012 데이터셋이 사용되었는데 128만개의 training images, 5만개의 validation images, 10만개의 test images가 사용 되었고, top-1 &amp; top-5 error rates를 측정하였다.</p>
<h4 id="plain-networks">Plain Networks</h4>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/7212bbba-cb83-49a6-8ad2-dae04dcedb1a/image.png" alt=""></p>
<p>처음에는 18-layer와 34-layer에 대한 평가를 진행하였다. 18-layer는 아까 위에서 보았던 34-layer와 유사한 형태이다. 아래 그래프를 통해 알 수 있듯이 더 깊은 34-layer가 18-layer보다 높은 validation error(굵은 선)를 보인다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/1a360f1e-e953-4ad9-acee-9d32040f9f2d/image.png" alt=""></p>
<p>또한 초반부에 설명했던 <strong>degradation</strong> 문제 또한 발생하였다. 즉, <strong>34-layer plain network의 training error(가는 선)가 전체 학습 과정에서 가장 높게 나타났다.</strong></p>
<blockquote>
<p>plain network에서는 34-layer에서 validation error가 커지며 degradation 문제가 발생하였다.</p>
</blockquote>
<p>이러한 문제는 배치 정규화가 적용되어 순전파/역전파 기울기에는 문제가 없었기 때문에 기울기 소실에 의해 일어나는 것으로 판단되지는 않는다.</p>
<h4 id="residual-networks">Residual Networks</h4>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/96c37ad1-03da-4d55-ac96-ee46a9bc159b/image.png" alt=""></p>
<p>다음으로는 plain network에 residual learning이 적용된 ResNet-18과 ResNet-34의 성능을 측정하였다. 이때 모든 shortcut에 대해서 차원이 증가될 때 zero padding(A 옵션)을 적용하였다. zero padding은 추가적인 파라미터가 필요하지 않으므로 plain 모델과 파라미터 수의 차이는 없다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/2388e01a-125a-45bf-a1e7-d47d122933dc/image.png" alt=""></p>
<p>위 결과들을 토대로 몇가지 사실을 알 수 있다.</p>
<ol>
<li>ResNet-34는 ResNet-18 보다 뛰어난 성능을 보이며(약 2.8%) 낮은 traning error가 눈에 띈다. =&gt; 이를 통해 degradation 문제가 해소되었음을 알 수 있다.</li>
<li>plain network와 비교하였을 때, 34-layer ResNet은 top-1 error rate를 약 3.5% 개선시켰다.(28.54% -&gt; 25.03%)</li>
<li>18-layer plain/residual net 모두 상당한 정확도를 보였지만, 18-layer ResNet이 조금 더 빨리 수렴하였다.</li>
</ol>
<blockquote>
<p> network가 엄청나게 깊지 않다면(18-layer 포함), SGD solver는 여전히 plain net에 훌륭한 solution을 찾아줄 수 있다.</p>
<p>물론 ResNet은 여기에 빠른 수렴을 통해 최적화를 쉽게 만들어준다.</p>
</blockquote>
<h4 id="identity-vs-projection-shortcuts">Identity vs. Projection Shortcuts</h4>
<p>앞에서 파라미터가 필요하지 않은 identity shortcut이 학습에 도움이 되는 것을 알 수 있었는데, 이번에는 zero padding과 projection shortcut을 비교하여 살펴본다.</p>
<p>세 가지 옵션이 있다.</p>
<p>A) increasing dimension에 zero padding shortcut이 사용된 경우. 모든 shortcut은 parameter-free이다.</p>
<p>B) increasing dimension에 projection shortcut을 적용하고 나머지는 identity shortcut인 경우.</p>
<p>C) projection shorcut만 사용한 경우</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/6e49651b-b3f0-4005-84ce-01298735f6fa/image.png" alt=""></p>
<p>Error rates (%, 10-crop testing) on ImageNet validation. VGG-16 is based on our test. ResNet-50/101/152 are of option B that only uses projections for increasing dimensions.</p>
</blockquote>
<p>위 Table에서 알 수 있는 것은 우선 모두 plain보다는 상당히 좋은 성능을 보였다는 것이다.</p>
<p>ABC 끼리 차이는 있지만(A&lt;B&lt;C) degradation 문제를 해결하는데에 필수적이지는 않으므로 편의를 위해 C는 사용하지 않았다.</p>
<p>bottleneck architecture의 복잡성을 높이지 않는 데에 Identical shortcuts가 중요함.</p>
<h4 id="deeper-bottleneck-architectures">Deeper Bottleneck Architectures</h4>
<p>ResNet의 깊이가 깊어짐에 따라 training time이 너무 커지는 것을 막기 위해 파라미터 수가 적은 bottleneck architecture를 도입하였다. </p>
<p>위 아래에 있는 1x1 conv layer는 각각 차원을 줄였다가 다시 늘리는 데에 사용된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/34182be1-3763-4de3-9bcd-887238369b6e/image.png" alt=""></p>
<p>이렇게 다시 차원을 원래대로 돌아오도록 하는 이유는 parameter-free인 identity shortcut이 bottleneck architecture에 매우 중요하기 때문이다. </p>
<p>만약 identity shortcut이 projection으로 교체된다면 2개의 high-dimentional 출력이 연결되어 시간 복잡도와 모델 크기가 두배가 된다.</p>
<p>이러한 결과를 막아주기 때문에 identity shortcut은 bottleneck design이 효과적인 모델이 되는데 중요한 역할을 한다.</p>
<h4 id="50-layer-resnet">50-layer ResNet</h4>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/7212bbba-cb83-49a6-8ad2-dae04dcedb1a/image.png" alt=""></p>
<p>50-layer부터는 기존의 2-layer block을 3-layer bottleneck block으로 교체한다. 이 때 B 옵션을 사용하였다.</p>
<h4 id="101-layer-and-152-layer-resnets">101-layer and 152-layer ResNets</h4>
<blockquote>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/b42e6907-f805-47e6-a075-d2ef0a3a65ed/image.png" alt=""></p>
<p>Error rates (%) of single-model results on the ImageNet validation set (except † reported on the test set).</p>
</blockquote>
<p>더 많은 3-layer block들을 사용하여 101-layer, 152-layer ResNet을 구성하였는데, 놀랍게도 깊이가 상당히 늘어났음에도 불구하고 152-layer ResNet(11.3 billion FLOPs)은 여전히 VGG-16/19 nets(15.3/19.6 billion FLOPs)보다 더 낮은 복잡도를 가졌다.</p>
<p>위 single-model 테스트에서 34-layer ResNets이 매우 경쟁력 있는 정확도를 보이지만, 50/101/152-layer ResNet은 더 높은 정확도를 보인다. 또한 여전히 degradation 문제를 찾아볼 수 없었다.</p>
<p>152-layer ResNet은 single-model 테스트에서 4.49%의 top-5 validation error를 보여주었는데, 이것은 심지어 single-model 임에도 이전의 모든 앙상블 모델을 제친 것이다.</p>
<h4 id="comparisons-with-state-of-the-art-methods">Comparisons with State-of-the-art Methods</h4>
<blockquote>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/472a9cff-0a55-4201-b2be-72a84827cafd/image.png" alt=""></p>
<p>Error rates (%) of ensembles. The top-5 error is on the test set of ImageNet and reported by the test server.</p>
</blockquote>
<p>저자는 서로 다른 깊이의 6개 모델을 앙상블한 모델을 만들었고, 이는 3.57% top-5 error를 보여주었다. 이 모델은 ILSVRC 2015에서 우승을 차지하였다.</p>
<h1 id="pytorch-implementation-18-layer">Pytorch Implementation (18-layer)</h1>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/7212bbba-cb83-49a6-8ad2-dae04dcedb1a/image.png" alt=""></p>
<p>ResNet은 위 표처럼 Block이 반복되는 구조이므로 Block 코드를 먼저 작성한 후, 이를 바탕으로 ResNet을 구성한다. </p>
<p>Block을 쌓는 구조이기 때문에 ResNet-18을 만들 수 있다면 34, 50 등도 만들 수 있다.</p>
<h2 id="import">Import</h2>
<pre><code class="language-py">import torch
from torch import nn
from torch import Tensor
from typing import Optional, Callable, Union, Type, List</code></pre>
<p>구조만 파악하기 때문에 학습 관련 라이브러리는 배제하였다.</p>
<h2 id="convolutional-layer">convolutional layer</h2>
<pre><code class="language-py">def conv3x3(in_planes: int, out_planes: int, stride: int = 1, groups: int = 1, dilation: int = 1) -&gt; nn.Conv2d:
    &quot;&quot;&quot;3x3 convolution with padding&quot;&quot;&quot;
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=dilation, groups=groups, bias=False, dilation=dilation)

def conv1x1(in_planes: int, out_planes: int, stride: int = 1) -&gt; nn.Conv2d:
    &quot;&quot;&quot;1x1 convolution&quot;&quot;&quot;
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)</code></pre>
<p>BasicBlock과 Bottleneck에서 사용될 3x3, 1x1 convolutional layer를 정의하는 부분.</p>
<h2 id="basicblock">BasicBlock</h2>
<pre><code class="language-py">class BasicBlock(nn.Module):
    def __init__(
        self,
        inplanes: int,
        planes: int,
        stride: int = 1,
        downsample: Optional[nn.Module] = None,
        groups: int = 1,
        dilation: int = 1,
        norm_layer: Optional[Callable[..., nn.Module]] = None
    )-&gt; None:
        super(BasicBlock, self).__init__()

        # Normalization Layer
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d

        self.conv1 = conv3x3(inplanes, planes, stride) # padding, dilation = 1
        self.bn1 = norm_layer(planes)
        self.relu = nn.ReLU(inplace=True) # inplace : 원본 직접 수정 여부
        self.conv2 = conv3x3(planes, planes) # stride = 1
        self.bn2 = norm_layer(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x: Tensor) -&gt; Tensor:
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity  #  residual connection
        out = self.relu(out)
        return out</code></pre>
<p>정규화는 기본값으로 2d Batch Normalization이 사용된다. </p>
<blockquote>
<p>Block 구성)
conv3x3 - BN - ReLU - conv3x3 - BN - residual connection - ReLU</p>
</blockquote>
<h2 id="bottleneck">Bottleneck</h2>
<pre><code class="language-py">class Bottleneck(nn.Module):
    # Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
    # while original implementation places the stride at the first 1x1 convolution(self.conv1)
    # according to &quot;Deep residual learning for image recognition&quot;https://arxiv.org/abs/1512.03385.
    # This variant is also known as ResNet V1.5 and improves accuracy according to
    # https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.

    expansion: int = 4

    def __init__(
        self,
        inplanes: int,
        planes: int,
        stride: int = 1,
        downsample: Optional[nn.Module] = None,
        groups: int = 1,
        base_width: int = 64,
        dilation: int = 1,
        norm_layer: Optional[Callable[..., nn.Module]] = None,
    ) -&gt; None:
        super().__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        width = int(planes * (base_width / 64.0)) * groups
        # Both self.conv2 and self.downsample layers downsample the input when stride != 1
        self.conv1 = conv1x1(inplanes, width)
        self.bn1 = norm_layer(width)
        self.conv2 = conv3x3(width, width, stride, groups, dilation)
        self.bn2 = norm_layer(width)
        self.conv3 = conv1x1(width, planes * self.expansion)
        self.bn3 = norm_layer(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x: Tensor) -&gt; Tensor:
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out</code></pre>
<p>Bottleneck은 ResNet-18에서 사용되지 않는다. 자세한 내용은 레퍼런스에서 참고할 수 있다.</p>
<h2 id="resnet-18">ResNet-18</h2>
<pre><code class="language-py">class ResNet(nn.Module):
    def __init__(
        self,
        block: Type[Union[BasicBlock, Bottleneck]],
        layers: List[int],
        num_classes: int = 1000,
        zero_init_residual: bool = False,
        norm_layer: Optional[Callable[..., nn.Module]] = None
    )-&gt; None:
        super(ResNet, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self._norm_layer = norm_layer # batch norm layer

        self.inplanes = 64 # input shape
        self.dilation = 1 # dilation fixed
        self.groups = 1 # groups fixed

        # input block
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = norm_layer(self.inplanes)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

        # residual blocks
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2, dilate=False)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2, dilate=False)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2, dilate=False)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512, num_classes)

        # weight initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode=&#39;fan_out&#39;, nonlinearity=&#39;relu&#39;)
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

        # Zero-initialize the last BN in each residual branch,
        # so that the residual branch starts with zeros, and each residual block behaves like an identity.
        # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)  # type: ignore[arg-type]
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)  # type: ignore[arg-type]

    def _make_layer(self, block: Type[Union[BasicBlock, Bottleneck]], planes: int, blocks: int, stride: int=1, dilate: bool = False) -&gt; nn.Sequential:
        norm_layer = self._norm_layer
        downsample = None

        # downsampling 필요할 경우 downsample layer 생성
        if stride != 1 or self.inplanes != planes:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes, stride),
                norm_layer(planes)
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, self.groups, self.dilation, norm_layer))
        self.inplanes = planes
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups, dilation=self.dilation, norm_layer=norm_layer))

        return nn.Sequential(*layers)

    def forward(self, x: Tensor) -&gt; Tensor:
        print(&#39;input shape:&#39;, x.shape)
        x = self.conv1(x)
        print(&#39;conv1 shape:&#39;, x.shape)
        x = self.bn1(x)
        print(&#39;bn1 shape:&#39;, x.shape)
        x = self.relu(x)
        print(&#39;relu shape:&#39;, x.shape)
        x = self.maxpool(x)
        print(&#39;maxpool shape:&#39;, x.shape)

        x = self.layer1(x)
        print(&#39;layer1 shape:&#39;, x.shape)
        x = self.layer2(x)
        print(&#39;layer2 shape:&#39;, x.shape)
        x = self.layer3(x)
        print(&#39;layer3 shape:&#39;, x.shape)
        x = self.layer4(x)
        print(&#39;layer4 shape:&#39;, x.shape)

        x = self.avgpool(x)
        print(&#39;avgpool shape:&#39;, x.shape)
        x = torch.flatten(x, 1)
        print(&#39;flatten shape:&#39;, x.shape)
        x = self.fc(x)
        print(&#39;fc shape:&#39;, x.shape)

        return x</code></pre>
<p>ResNet 코드에서는 BasicBlock을 이용하여 Residual Blocks를 구성하는 _make_layer 함수를 구현하고 이를 통해서 Block을 쌓는다. </p>
<p>또한 입력 맨 처음 단의 Input Block 또한 별도로 구성되어 있다.</p>
<h2 id="result">Result</h2>
<pre><code class="language-py">model = ResNet(BasicBlock, [2, 2, 2, 2])
x = torch.randn(1, 3, 112, 112)
print(&#39;\noutput shpae: &#39;, model(x).shape)</code></pre>
<p>ResNet-18은 각 블럭이 2개씩 구성되어 있으므로 [2, 2, 2, 2]를 입력한다.</p>
<blockquote>
<p>input shape: torch.Size([1, 3, 112, 112])
conv1 shape: torch.Size([1, 64, 56, 56])
bn1 shape: torch.Size([1, 64, 56, 56])
relu shape: torch.Size([1, 64, 56, 56])
maxpool shape: torch.Size([1, 64, 28, 28])
layer1 shape: torch.Size([1, 64, 28, 28])
layer2 shape: torch.Size([1, 128, 14, 14])
layer3 shape: torch.Size([1, 256, 7, 7])
layer4 shape: torch.Size([1, 512, 4, 4])
avgpool shape: torch.Size([1, 512, 1, 1])
flatten shape: torch.Size([1, 512])
fc shape: torch.Size([1, 1000])</p>
<p>output shpae:  torch.Size([1, 1000])</p>
</blockquote>
<h1 id="레퍼런스">레퍼런스</h1>
<p><a href="https://arxiv.org/abs/1512.03385">Deep Residual Learning for Image Recognition(2015) - 논문</a>
<a href="https://www.youtube.com/watch?v=671BsKl8d0E">ResNet 논문 리뷰 - Youtube</a>
<a href="https://lv99.tistory.com/25">Short Connection과 Identity Mapping - 블로그</a>
<a href="https://phil-baek.tistory.com/entry/ResNet-Deep-Residual-Learning-for-Image-Recognition-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0">ResNet 논문 리뷰 - 블로그</a>
<a href="https://yhkim4504.tistory.com/3">Pytorch 구현 - 블로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transformer에 대해 조금만 알아보자]]></title>
            <link>https://velog.io/@jaehoon_go/Transformer%EC%97%90-%EB%8C%80%ED%95%B4-%EC%A1%B0%EA%B8%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@jaehoon_go/Transformer%EC%97%90-%EB%8C%80%ED%95%B4-%EC%A1%B0%EA%B8%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 07 Jul 2024 11:45:50 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p>Transformer는 Attention Mechanism 만을 적용하여 Recurrent Network의 한계를 극복한 신경망 아키텍처이다. 2017년에 구글이 발표한 논문인 “Attention is all you need”을 통해 소개되었고 GPT, BERT의 기반이 되는 등 현재까지도 자연어 처리 분야에 큰 영향력을 끼치고 있다.</p>
<h1 id="recurrent-model의-문제점">Recurrent Model의 문제점</h1>
<p>Transformer 이전의 언어 모델은 순환 신경망(RNN) 기반으로 만들어졌다. RNN은 연속적인 데이터를 입력으로 받고 현재 시점의 hidden state가 다음 hidden state에 영향을 주는 구조이다. 이로써 자연어 문장과 같은 순차적 입력에 대해 순서를 고려한 출력을 얻을 수 있는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/f27151d4-753f-4990-864e-ab48882c24c2/image.png" alt=""></p>
<p>하지만 이러한 순차적인 구조는 몇 가지 문제점들이 있다. 우선, 길이가 긴 입력에 대해서 뒤로 갈수록 앞선 입력에 대한 정보를 모두 기억하기 어렵다. 또한 순서대로 입력을 처리해야하므로 (병렬 처리가 불가능하여) 연산 시간 단축에 한계가 있다.</p>
<h1 id="transformer-기본-구조">Transformer 기본 구조</h1>
<p>예시) 영어-독일어 번역 모델</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/64a2924f-6def-418a-9fd6-89fabd02bb1e/image.png" alt=""></p>
<p>Transformer는 인코더와 디코더를 N개씩 쌓은 구조로 되어있으며, 마지막 인코더의 출력이 각 디코더에 영향을 미친다. 논문에서는 N=6을 사용하였다.</p>
<p>Decoder에 있는 &lt;sos&gt;는 문장의 시작(start of string)을 나타내는 토큰이다. 문장의 끝(end of string)은 &lt;eos&gt;를 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/1f046f5e-804c-45a6-9ed9-9e40dfd3af19/image.png" alt=""></p>
<blockquote>
<p>Transformer는 위 그림과 같이 문장 내 단어들을 순서대로 입력하는 것이 아니라 병렬적으로 동시에 입력하게 된다.</p>
</blockquote>
<h2 id="positional-encoding">Positional Encoding</h2>
<p>Transformer는 입력을 순차적으로 받지 않는다면 단어의 위치 정보를 어떻게 반영할까?
각 단어의 임베딩 벡터에 위치 정보를 더하는 것을 <strong>Positional Encoding</strong>이라고 한다.</p>
<p>  <img src="https://velog.velcdn.com/images/jaehoon_go/post/9d9dbc33-376a-49ae-99c4-db356bce9cd2/image.png" alt=""></p>
<p>Positional Encoding 함수에서 pos는 입력 문장에서의 임베딩 벡터의 위치를, i는 임베딩 벡터 내의 차원 인덱스를 의미한다. 각 차원 인덱스가 짝수일 경우에는 sin 함수를, 홀수 일 때는 cos 함수를 사용한다.</p>
  <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <mi>P</mi>
  <msub>
    <mi>E</mi>
    <mrow data-mjx-texclass="ORD">
      <mo stretchy="false">(</mo>
      <mi>p</mi>
      <mi>o</mi>
      <mi>s</mi>
      <mo>,</mo>
      <mtext>&#xA0;</mtext>
      <mn>2</mn>
      <mi>i</mi>
      <mo stretchy="false">)</mo>
    </mrow>
  </msub>
  <mo>=</mo>
  <mi>s</mi>
  <mi>i</mi>
  <mi>n</mi>
  <mo stretchy="false">(</mo>
  <mi>p</mi>
  <mi>o</mi>
  <mi>s</mi>
  <mrow data-mjx-texclass="ORD">
    <mo>/</mo>
  </mrow>
  <msup>
    <mn>10000</mn>
    <mrow data-mjx-texclass="ORD">
      <mn>2</mn>
      <mi>i</mi>
      <mrow data-mjx-texclass="ORD">
        <mo>/</mo>
      </mrow>
      <msub>
        <mi>d</mi>
        <mrow data-mjx-texclass="ORD">
          <mi>m</mi>
          <mi>o</mi>
          <mi>d</mi>
          <mi>e</mi>
          <mi>l</mi>
        </mrow>
      </msub>
    </mrow>
  </msup>
  <mo stretchy="false">)</mo>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <mi>P</mi>
  <msub>
    <mi>E</mi>
    <mrow data-mjx-texclass="ORD">
      <mo stretchy="false">(</mo>
      <mi>p</mi>
      <mi>o</mi>
      <mi>s</mi>
      <mo>,</mo>
      <mtext>&#xA0;</mtext>
      <mn>2</mn>
      <mi>i</mi>
      <mo>+</mo>
      <mn>1</mn>
      <mo stretchy="false">)</mo>
    </mrow>
  </msub>
  <mo>=</mo>
  <mi>c</mi>
  <mi>o</mi>
  <mi>s</mi>
  <mo stretchy="false">(</mo>
  <mi>p</mi>
  <mi>o</mi>
  <mi>s</mi>
  <mrow data-mjx-texclass="ORD">
    <mo>/</mo>
  </mrow>
  <msup>
    <mn>10000</mn>
    <mrow data-mjx-texclass="ORD">
      <mn>2</mn>
      <mi>i</mi>
      <mrow data-mjx-texclass="ORD">
        <mo>/</mo>
      </mrow>
      <msub>
        <mi>d</mi>
        <mrow data-mjx-texclass="ORD">
          <mi>m</mi>
          <mi>o</mi>
          <mi>d</mi>
          <mi>e</mi>
          <mi>l</mi>
        </mrow>
      </msub>
    </mrow>
  </msup>
  <mo stretchy="false">)</mo>
</math>

<p>  <img src="https://velog.velcdn.com/images/jaehoon_go/post/31b4ce53-9ca2-4212-8577-5c74ded5224d/image.png" alt=""></p>
<p>𝑑𝑚𝑜𝑑𝑒𝑙은 각 출력층의 차원 정보를 나타내는 Transformer의 하이퍼파라미터 값이다. 여기서 임베딩 벡터의 차원이기도 하며 앞으로 계속 등장하는 개념이다. 예시에서는 간단하게 𝑑𝑚𝑜𝑑𝑒𝑙 = 4로 표현하였지만, 논문에서는 512를 사용하였다.</p>
<h1 id="attention">Attention</h1>
<p>Attention은 단어 사이의 상관관계를 게산하여 중요한 정보에 집중하는 Mechanism이다. Transformer에서 사용되는 어텐션들은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/063c0116-f7cc-48fc-81b9-62c82aa6a17a/image.png" alt=""></p>
<p><strong>Encoder Self-Attention</strong>은 인코더 자체의 Query, Key, Value 벡터만을 가지고 모든 정보를 고려하여 Attention을 적용한다. <strong>Maked Decoder Self-Attention</strong>과 <strong>Encoder-Decoder Attention</strong>은 디코더에서 이루어지며 적용방식이 조금 다르다. 자세한 내용은 아래서 설명한다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/f3044a2c-01c7-4544-b35c-476149e184fd/image.png" alt=""></p>
<p>앞서 소개한 세 종류의 Attention이 어느 위치에 적용되는지 나타낸 그림이다. Mutli-head라는 것은 Attention을 병렬적으로 처리함을 나타낸다.</p>
<h1 id="encoder">Encoder</h1>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/e8954305-07b7-4097-97a6-49448bdb199f/image.png" alt=""></p>
<p>Position Encoding을 거친 문장은 인코더에 입력되어 num_layers 만큼의(논문에서는 6) 인코더를 통과하게 되며, 각 인코더마다 2개의 Sublayer로 구성되어 있다. Multi-head Self-Attention은 Self-Attention을 병렬적으로 처리하는 구조이며, FFNN은 Feed Foward Neural Network를 말한다. 핵심적인 구조인 Self-Attention를 먼저 이해해보자.</p>
<h2 id="self-attention">Self-Attention</h2>
<p>Attention Function은 쿼리(Query)와 키(Key)의 유사도를 구한 후, 이를 가중치로 하여 값(Value)의 Weight Sum을 최종적으로 Return 한다. 여기서 Query는 유사도를 구하고 싶은(또는 궁금한) 단어를 가리킨다고 생각하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/d931c116-11e1-4666-b35d-28b71e1bb640/image.png" alt=""></p>
<p>Self-Attention은 Attention의 한 종류로, 입력 문장의 모든 단어 벡터들에 존재하는 Q, K, V를 가지고 Attention을 수행한다. 다른 문장을 이용하는 것이 아닌 한 문장 내부에서 Attention이 이루어진다고 해서 Self-Attention라 불린다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/006d532c-090c-44d1-ab2a-e33dd786d0e5/image.png" alt=""></p>
<p>유사도 계산을 통해서 입력 문장의 it은 ‘street’이 아닌 ‘animal’을 나타낼 확률이 높다는 것을 확률적으로 나타낸다. 위 예시에서는 it를 Query로 하여 Attention을 수행하였는데, 이런 방식으로 모든 단어에 대해서 Attention을 수행하는 것이 Self-Attention이다.</p>
<h2 id="q-k-v-벡터-산출">Q, K, V 벡터 산출</h2>
<p>예시) “I am a student” 문장</p>
<p>Positional Encoding을 거친 입력 문장(𝑑𝑚𝑜𝑑𝑒𝑙)에서 Q, K, V 벡터를 구하기 위해서는 각각의 가중치 행렬을 곱해야 한다. 최종적으로 얻어지는 Q, K, V 벡터는 𝑑𝑚𝑜𝑑𝑒𝑙 / num_heads의 크기를 갖는다. 논문에서는 𝑑𝑚𝑜𝑑𝑒𝑙 = 512,  num_head = 8을 사용하였다.</p>
<p>예시에서는 간단하게 𝑑𝑚𝑜𝑑𝑒𝑙 = 4, num_head = 2로 표현하였다. </p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/5ce5d100-e3eb-428a-87d7-45083108d719/image.png" alt=""></p>
<h2 id="scaled-dot-product-attention">Scaled dot-product Attention</h2>
<p>Q벡터는 전체 문장 중에서 유사도를 구하고 싶은 주체가 되는 단어를 나타낸다. K벡터는 유사도를 구할 대상을 나타내며 벡터 내적을 통해 Attention Score를 구하게 된다.</p>
<p>아래 예시에서는 “I am a student”라는 문장에서 단어 “I’와 다른 단어들 사이의 Attention Score를 구한 것이다.</p>
<p>Attention Score를 구하는 과정에서 dk^1/2로 나누게 되는데 이는 내적 결과를 보정하기 위한 값이며 이 때문에 “Scaled”라는 말이 붙는다. dk는 앞서 구했던 𝑑𝑚𝑜𝑑𝑒𝑙 / num_heads 를 나타낸다. (논문에서는 8)</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/872d027a-769d-442a-8e9f-17dfa023a571/image.png" alt=""></p>
<p>이렇게 구한 Attention Score에 Softmax Function을 적용하여 Attention Distribution을 구할 수 있다. 이를 가중치로 사용하여 V벡터의 Weight Sum을 구한 결과가 최종적인 Attention Value가 된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/ad444b4d-608c-438a-9078-a916c396dffb/image.png" alt=""></p>
<p>여태까지는 이해를 돕기 위해서 한 단어에 대해 연산을 구하는 과정을 설명하였다. 실제로는 전체 단어들에 대해 행렬 연산을 통해 Self-Attention을 수행하게 된다.</p>
<h2 id="행렬-연산">행렬 연산</h2>
<p>행렬 연산을 이용하면 전체 단어들에 대한 Q, K, V 벡터를 한번에 구할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/d84e8a82-588e-4e64-b4e7-02d882466c94/image.png" alt=""></p>
<p>벡터 내적도 마찬가지로 전체 내적 결과를 담은 Attention Score를 얻을 수 있는데 이후에 Attention Value 또한 모든 단어에 대해 얻을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/3b3ee116-3c17-4786-b0f7-b33ce68cb6fb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/a352a1ed-3af4-40a4-9f37-c6085bf497f5/image.png" alt=""></p>
<p>전체 계산과정을 수식으로 나타내면 아래와 같다. 여기서의 Attention은 Attention Value Matrix를 말한다.</p>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <mi>A</mi>
  <mi>t</mi>
  <mi>t</mi>
  <mi>e</mi>
  <mi>n</mi>
  <mi>t</mi>
  <mi>i</mi>
  <mi>o</mi>
  <mi>n</mi>
  <mo stretchy="false">(</mo>
  <mi>Q</mi>
  <mo>,</mo>
  <mi>K</mi>
  <mo>,</mo>
  <mi>V</mi>
  <mo stretchy="false">)</mo>
  <mo>=</mo>
  <mi>s</mi>
  <mi>o</mi>
  <mi>f</mi>
  <mi>t</mi>
  <mi>m</mi>
  <mi>a</mi>
  <mi>x</mi>
  <mo stretchy="false">(</mo>
  <mrow data-mjx-texclass="ORD">
    <mfrac>
      <mrow>
        <mi>Q</mi>
        <msup>
          <mi>K</mi>
          <mi>T</mi>
        </msup>
      </mrow>
      <mrow data-mjx-texclass="ORD">
        <msqrt>
          <msub>
            <mi>d</mi>
            <mi>k</mi>
          </msub>
        </msqrt>
      </mrow>
    </mfrac>
  </mrow>
  <mo stretchy="false">)</mo>
  <mi>V</mi>
</math>

<h2 id="multi-head-attention">Multi-Head Attention</h2>
<p>앞에서 입력 벡터의 𝑑𝑚𝑜𝑑𝑒𝑙 차원을 그대로 사용하지 않고 𝑑𝑚𝑜𝑑𝑒𝑙 / num_heads 차원을 갖는 Q, K, V 벡터로 바꾼 것은 Multi-Head 연산을 위함이다.</p>
<p>논문 저자는 한 번의 Attention보다 여러번의 Attention을 병렬로 사용하는 것이 다양한 관점을 학습하는 데에 더 효과적이라고 판단하였다. </p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/ba81e4b3-5e1b-411f-9af3-d7b1044776a7/image.png" alt=""></p>
<p>각각의 head에서 Self-Attention을 수행하고 얻은 결과를 임베딩 차원 축으로 병합(Concatenation)을 수행한다. 병합을 통해서 얻은 Attention Value Matrix는 다시 𝑑𝑚𝑜𝑑𝑒𝑙 차원을 갖게 된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/cd2e451a-b63e-4ce9-ae8c-792328c534d8/image.png" alt=""></p>
<p>병합된 행렬은 또 한번의 행렬 곱을 통해 최종적으로 입력과 같은 (Seq_len, dmodel) 크기의 행렬이 되며, Multi-head Self-Attention의 최종 출력이 된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/cb74415a-6db9-461b-a200-0582d48a79ef/image.png" alt=""></p>
<p>이렇게 입력과 출력을 같은 크기게 되도록 하는 것은 동일한 구조의 인코더에 다시 입력하기 위함과 Residual Connection 때문이다.</p>
<h1 id="position-wise-ffnn">Position-wise FFNN</h1>
<p>FFNN은 인코더와 디코더에서 공통적으로 가지고 있는 sublayer이다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/26935c8c-8a12-4128-9eac-86bf81dd351e/image.png" alt=""></p>
<p>이를 식으로 나타내면 아래와 같다.</p>
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mi>F</mi>
  <mi>F</mi>
  <mi>N</mi>
  <mi>N</mi>
  <mo stretchy="false">(</mo>
  <mi>x</mi>
  <mo stretchy="false">)</mo>
  <mo>=</mo>
  <mi>M</mi>
  <mi>A</mi>
  <mi>X</mi>
  <mo stretchy="false">(</mo>
  <mn>0</mn>
  <mo>,</mo>
  <mi>x</mi>
  <mrow data-mjx-texclass="ORD">
    <msub>
      <mi>W</mi>
      <mrow data-mjx-texclass="ORD">
        <mn>1</mn>
      </mrow>
    </msub>
  </mrow>
  <mo>+</mo>
  <msub>
    <mi>b</mi>
    <mrow data-mjx-texclass="ORD">
      <mn>1</mn>
    </mrow>
  </msub>
  <mo stretchy="false">)</mo>
  <mrow data-mjx-texclass="ORD">
    <msub>
      <mi>W</mi>
      <mn>2</mn>
    </msub>
  </mrow>
  <mo>+</mo>
  <msub>
    <mi>b</mi>
    <mn>2</mn>
  </msub>
</math>

<p>x는 앞서 구한 Multi-Head Attention의 출력인 (seq_len, 𝑑𝑚𝑜𝑑𝑒𝑙)의 크기를 가지는 행렬이다. 이 때 가중치 W1은 (𝑑𝑚𝑜𝑑𝑒𝑙, 𝑑ff), W2는 (𝑑ff, 𝑑𝑚𝑜𝑑𝑒𝑙)의 크기를 가진다. 논문에서는 𝑑ff = 2048을 사용한다. 가중치는 인코더마다 하나의 값이 사용된다.</p>
<p>FFNN을 통과한 결과도 Self-Attention과 마찬가지로 (seq_len, 𝑑𝑚𝑜𝑑𝑒𝑙)의 크기가 보존된다. 이를 통해 출력을 다음 인코더의 입력으로 사용할 수 있다.</p>
<h1 id="residual-connection--layer-normalization">Residual Connection &amp; Layer Normalization</h1>
<p>지금까지 sublayer에 대해 설명하면서 지나친 부분이 있다. 각 sublayer 출력에 연결된 Add &amp; Norm이라고 써있는 부분인데, Add는 Residual Connection을 Norm은 Layer Normalization을 뜻한다.</p>
<h2 id="residual-connection">Residual Connection</h2>
<p>앞서 각 sublayer의 최종 출력이 입력과 같은 크기여야 한다고 설명했는데, 이는 입력과 출력을 더하는 Residual Connection의 구조 때문이다. Residual Connection은 깊은 신경망 구조에서 학습 난이도를 낮추기 위해 사용된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/3b70a36c-452c-40c5-8e27-3572b867e0b4/image.png" alt=""></p>
<p>가령 Multi-head Attention에서는 다음과 같이 표현할 수 있다.</p>
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mi>H</mi>
  <mo stretchy="false">(</mo>
  <mi>x</mi>
  <mo stretchy="false">)</mo>
  <mo>=</mo>
  <mi>x</mi>
  <mo>+</mo>
  <mi>M</mi>
  <mi>u</mi>
  <mi>l</mi>
  <mi>t</mi>
  <mi>i</mi>
  <mo>&#x2212;</mo>
  <mi>h</mi>
  <mi>e</mi>
  <mi>a</mi>
  <mi>d</mi>
  <mtext>&#xA0;</mtext>
  <mi>A</mi>
  <mi>t</mi>
  <mi>t</mi>
  <mi>e</mi>
  <mi>n</mi>
  <mi>t</mi>
  <mi>i</mi>
  <mi>o</mi>
  <mi>n</mi>
  <mo stretchy="false">(</mo>
  <mi>x</mi>
  <mo stretchy="false">)</mo>
</math>

<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/06c84759-74bc-4977-8534-bc261db3e9e8/image.png" alt=""></p>
<h2 id="layer-normalization">Layer Normalization</h2>
<p>Sublayer, Residual Connection을 거쳐 Layer Normalization을 진행한 결과를 다음과 같이 나타낼 수 있다.</p>
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mi>L</mi>
  <mi>N</mi>
  <mo>=</mo>
  <mi>L</mi>
  <mi>a</mi>
  <mi>y</mi>
  <mi>e</mi>
  <mi>r</mi>
  <mi>N</mi>
  <mi>o</mi>
  <mi>r</mi>
  <mi>m</mi>
  <mo stretchy="false">(</mo>
  <mi>x</mi>
  <mo>+</mo>
  <mi>S</mi>
  <mi>u</mi>
  <mi>b</mi>
  <mi>l</mi>
  <mi>a</mi>
  <mi>y</mi>
  <mi>e</mi>
  <mi>r</mi>
  <mo stretchy="false">(</mo>
  <mi>x</mi>
  <mo stretchy="false">)</mo>
  <mo stretchy="false">)</mo>
</math>

<p>층 정규화(Layer Normalization)는 텐서의 마지막 차원의 평균과 분산을 구하고, 정규화를 진행하여 학습을 돕는다. 여기서는 𝑑𝑚𝑜𝑑𝑒𝑙이 마지막 차원에 해당된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/31f81577-438a-4296-9eed-5b4d4cd3d5af/image.png" alt=""></p>
<p>𝑑𝑚𝑜𝑑𝑒𝑙 차원 방향을 기준으로 평균과 분산을 구한 후, 그 값들로 각 화살표 방향의 벡터들에 대하여 정규화를 진행한다. 정규화된 벡터는 아래처럼 표기한다.</p>
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mi>l</mi>
  <msub>
    <mi>n</mi>
    <mrow data-mjx-texclass="ORD">
      <mi>i</mi>
    </mrow>
  </msub>
  <mo>=</mo>
  <mi>L</mi>
  <mi>a</mi>
  <mi>y</mi>
  <mi>e</mi>
  <mi>r</mi>
  <mi>N</mi>
  <mi>o</mi>
  <mi>r</mi>
  <mi>m</mi>
  <mo stretchy="false">(</mo>
  <msub>
    <mi>x</mi>
    <mrow data-mjx-texclass="ORD">
      <mi>i</mi>
    </mrow>
  </msub>
  <mo stretchy="false">)</mo>
</math>

<p>실제 계산을 위해서 우선, 기존 벡터의 원소(스칼라)마다 다음과 같이 정규화를 진행한다.</p>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <msub>
    <mrow data-mjx-texclass="ORD">
      <mover>
        <mi>x</mi>
        <mo stretchy="false">^</mo>
      </mover>
    </mrow>
    <mrow data-mjx-texclass="ORD">
      <mi>i</mi>
      <mo>,</mo>
      <mi>k</mi>
    </mrow>
  </msub>
  <mo>=</mo>
  <mfrac>
    <mrow>
      <msub>
        <mi>x</mi>
        <mrow data-mjx-texclass="ORD">
          <mi>i</mi>
          <mo>,</mo>
          <mi>k</mi>
        </mrow>
      </msub>
      <mo>&#x2212;</mo>
      <msub>
        <mi>&#x3BC;</mi>
        <mrow data-mjx-texclass="ORD">
          <mi>i</mi>
        </mrow>
      </msub>
    </mrow>
    <msqrt>
      <msubsup>
        <mi>&#x3C3;</mi>
        <mrow data-mjx-texclass="ORD">
          <mi>i</mi>
        </mrow>
        <mrow data-mjx-texclass="ORD">
          <mn>2</mn>
        </mrow>
      </msubsup>
      <mo>+</mo>
      <mi>&#x3F5;</mi>
    </msqrt>
  </mfrac>
</math>

<p>여기서 epsilon은 분모가 0이 되는 것을 방지하는 값이다.</p>
<p>이제 학습 가능한 파라미터인 두 벡터를 아래처럼 초기값을 설정하고 최종 수식을 완성한다.
<img src="https://velog.velcdn.com/images/jaehoon_go/post/50323c3a-3506-4618-90fc-b866c479c998/image.png" alt=""></p>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <mi>l</mi>
  <msub>
    <mi>n</mi>
    <mrow data-mjx-texclass="ORD">
      <mi>i</mi>
    </mrow>
  </msub>
  <mo>=</mo>
  <mi>&#x3B3;</mi>
  <msub>
    <mrow data-mjx-texclass="ORD">
      <mover>
        <mi>x</mi>
        <mo stretchy="false">^</mo>
      </mover>
    </mrow>
    <mrow data-mjx-texclass="ORD">
      <mi>i</mi>
    </mrow>
  </msub>
  <mo>+</mo>
  <mi>&#x3B2;</mi>
  <mo>=</mo>
  <mi>L</mi>
  <mi>a</mi>
  <mi>y</mi>
  <mi>e</mi>
  <mi>r</mi>
  <mi>N</mi>
  <mi>o</mi>
  <mi>r</mi>
  <mi>m</mi>
  <mo stretchy="false">(</mo>
  <msub>
    <mi>x</mi>
    <mrow data-mjx-texclass="ORD">
      <mi>i</mi>
    </mrow>
  </msub>
  <mo stretchy="false">)</mo>
</math>

<h1 id="decoder">Decoder</h1>
<p>지금까지 Encoder Self-Attention과 Position-wise FFNN, Residual Connection, Layer Normalization에 대해 알아보았다. 디코더도 앞서 설명한 구조와 비슷하지만 2개의 Attention Layer가 인코더와 다소 다른 형태를 가지고 있다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/ec72e87e-e8e0-46c0-bad8-d6a45adee37c/image.png" alt=""></p>
<p>디코더는 Masked Self-Attention, Encoder-Decoder Attention, Position-wise FFNN의 3 sublayer로 구성되어있다. FFNN과 Add &amp; Norm 구조는 동일하므로 2개의 Attention Layer에 대해서만 알아보려한다.</p>
<h2 id="masked-self-attention">Masked Self-Attention</h2>
<p>첫번째 sublayer에 주목해보자. 디코더도 인코더와 마찬가지로 Positional Encoding된 문장 행렬이 입력된다. 인코더와 차이점은 인코더는 번역하고 싶은 문장인 “I am a student”를 입력했다면, 디코더는 그의 번역 결과인 “&lt;sos&gt; je suis étudiant” 행렬을 입력한다. 이는 번역을 진행할 때 앞서 번역한 결과 단어들을 참고하기 위함이다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/814870d2-56de-49c3-a165-ad948e1b9f44/image.png" alt=""></p>
<p>이 때 한가지 문제점이 있다. 예를 들어 suis를 예측하는 시점에는 &lt;sos&gt;와 je만을 참고하여 번역을 수행하여야 하고, 예측 대상인 suis나 그 뒷 단어를 미리 참고해서는 안된다. RNN 계열의 모델과 달리 Transformer는 순차적으로 입력하지 않으므로 위와 같은 문제를 방지하기 위해 look-ahead mask를 도입했다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/f958360b-0816-4a29-b87a-24953ef7de96/image.png" alt=""></p>
<p>앞서 설명한 것과 같이 Self-Attention을 수행한 후, 예측하는 시점과 같거나 후순위에 있는 단어는 참고하지 못하도록 미리보기(look-ahead)를 막는다(mask).</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/59741e3d-5412-4b6f-83be-6e5aea076dd5/image.png" alt=""></p>
<p>검은색 부분이 maked 부분이며, 실제로는 해당 부분을 제외한 행렬을 만들 수 없으므로 masking할 부분을 Softmax Fucntion을 거친 결과가 0에 가깝게 나오도록 절댓값이 매우 큰 음수로 설정한다.</p>
<h2 id="encoder-decoder-attention">Encoder-Decoder Attention</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/c9e21358-f665-4871-95d7-9cafad12d9b5/image.png" alt=""></p>
<p>이번에는 두번째 sublayer를 살펴보자. Encoder-Decoder Attention은 Query 벡터는 디코더로부터 가져오며, Key, Value 벡터는 인코더로부터 가져온다. 서로 다른 곳에서 벡터들을 가져오므로 Self-Attention이 아니다. 이를 통해 디코더는 인코더의 출력을 참고하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/ed0186d9-339b-402a-a022-d13f63bd05d7/image.png" alt=""></p>
<p>이후 다른 연산들은 앞서 설명한 것과 같다.</p>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need - 논문</a>
<a href="https://www.youtube.com/watch?v=AA621UofTUA">나동빈님 논문 리뷰 - Youtube</a>
<a href="https://wikidocs.net/31379">딥 러닝을 이용한 자연어 처리 입문 - 위키독스</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA와 N+1 문제]]></title>
            <link>https://velog.io/@jaehoon_go/JPA%EC%99%80-N1-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@jaehoon_go/JPA%EC%99%80-N1-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 30 May 2024 09:58:46 GMT</pubDate>
            <description><![CDATA[<h1 id="1-n1-문제란">1. N+1 문제란</h1>
<p>엔티티를 조회할 때 연관된 엔티티를 조회하기 위한 N번의 쿼리가 추가적으로 발생하는 문제 ⇒ DB 부담 증가</p>
<h2 id="예시1--n-관계">예시(1 : N 관계)</h2>
<blockquote>
<p>1:N 뿐만 아니라 1:1, N:1 관계에서 모두 발생할 수 있음!</p>
</blockquote>
<h3 id="entity">Entity</h3>
<pre><code class="language-java">    @Entity
    @Getter
    @Builder
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @AllArgsConstructor
    public class Post {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String title;
        private String content;

        // ...

        @Builder.Default
        @OneToMany(mappedBy = &quot;post&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
        private List&lt;Image&gt; images = new ArrayList&lt;&gt;(); // image는 id와 url을 갖고 있음

        //... 
    }</code></pre>
<h3 id="service">Service</h3>
<h4 id="5개의-게시글을-불러온다고-가정">5개의 게시글을 불러온다고 가정</h4>
<pre><code class="language-java">    @Transactional(readOnly = true)
    public PostResponse getPostList(Long postId) {
        List&lt;Post&gt; posts = postRepository.findAll();

        return posts.stream()
                    .map(PostResponse::toResponse)
                    .collect(Collectors.toList());
    }</code></pre>
<h3 id="query">Query</h3>
<h4 id="각각의-게시글의-image에서-url을-불러올-때-쿼리문-추가-발생n5">각각의 게시글의 Image에서 url을 불러올 때 쿼리문 추가 발생(N=5)</h4>
<pre><code class="language-sql">    Hibernate: select p1_0.id, p1_0.content, p1_0.title from post p1_0
    Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
    Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
    Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
    Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?
    Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.id=?</code></pre>
<ol>
<li>postRepository의 findAll() 메서드를 사용하여 1개의 Select 쿼리로 Post 목록 조회</li>
<li>@OneToMany는 기본적으로 Fetch Type이 Lazy(지연) ⇒ 이미지 리스트 자리에 프록시 객체 생성</li>
<li>Image에 있는 데이터를 조회할 때 엔티티 객체를 불러오기 위한 N개의 Select 쿼리가 추가적으로 발생</li>
</ol>
<h1 id="2-발생하는-이유">2. 발생하는 이유</h1>
<h2 id="객체와-rdb간-패러다임-차이">객체와 RDB간 패러다임 차이</h2>
<p>객체는 레퍼런스를 가지고 언제든지 연관된 객체에 접근할 수 있지만, RDB의 경우 SELECT 쿼리를 통해서만 조회할 수 있기 때문에 연관된 엔티티를 조회하려고 할 때 추가적으로 쿼리가 발생하게 된다.</p>
<h2 id="fetch-type">fetch type</h2>
<blockquote>
<p>Q. 지연 로딩이 아닌 즉시 로딩을 사용하면 되는 것 아닌가요?</p>
</blockquote>
<p>@OneToMany, @ManyToMany는 <code>지연(Lazy) 로딩</code> 이 기본</p>
<p>@ManyToOne, @OneToOne은 <code>즉시(Eager) 로딩</code> 이 기본</p>
<p>⇒ JPQL을 사용하는 시점에 N+1 문제 발생
    + 예상치 못한 쿼리 발생 우려가 있어서 실무에서 사용하지 않음, </p>
<h1 id="3-해결-방법">3. 해결 방법</h1>
<h2 id="1-fetch-join">1) fetch join</h2>
<h3 id="특징">특징</h3>
<ul>
<li>fetch join은 객체 그래프를 SQL 한번에 조회하는 개념</li>
<li>fetch join을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩) ⇒ 글로벌 로딩 전략 무시</li>
</ul>
<h3 id="한계점">한계점</h3>
<ul>
<li>둘 이상의 컬렉션은 fetch join 할 수 없다.</li>
<li>컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다. 
⇒ 일대다 데이터를 조회하고 중복을 제거하는 과정에서 데이터의 수가 변하기 때문</li>
</ul>
<pre><code class="language-java">public interface PostRepository extends JpaRepository&lt;Post, Long&gt; {

    @Override
    @Query(&quot;select p from Post p join fetch p.images&quot;)
    List&lt;Post&gt; findAll();
}</code></pre>
<pre><code class="language-java">Hibernate: 
    select
        p1_0.id,
        p1_0.content,
        i1_0.post_id,
        i1_0.id,
        i1_0.url,
        p1_0.title
    from
        post p1_0 
    join
        image i1_0 
            on p1_0.post_id=i1_0.post_post_id </code></pre>
<p>cf) 하이버네이트 6 이후 부터는 자동으로 중복 제거(distinct)</p>
<h3 id="일반-join과-차이점">일반 join과 차이점</h3>
<pre><code class="language-java">select p from Post p join p.images</code></pre>
<ul>
<li>JPQL은 결과를 반환할 때 연관관계 고려X ⇒ SELECT 절에 지정한 엔티티만 조회</li>
<li>여기서는 Post 엔티티만 조회하고, Image 엔티티는 조회X</li>
</ul>
<h2 id="2-entitygraph">2) EntityGraph</h2>
<blockquote>
<p>fetch join을 편하게 사용하도록 도와주는 기능</p>
</blockquote>
<pre><code class="language-java">public interface PostRepository extends JpaRepository&lt;Post, Long&gt; {

    @Override
    @EntityGraph(attributePaths = {&quot;images&quot;})
    List&lt;Post&gt; findAll();
}</code></pre>
<p>기본적으로 inner join을 사용하는 fetch join과 다르게 EntityGraphs는 outer join을 사용하기 때문에 성능 이슈가 있을 수 있음</p>
<h2 id="3-batchsize">3) BatchSize</h2>
<ul>
<li>연관된 엔티티 조회시 지정한 size 만큼 SQL의 in 절을 사용</li>
<li>즉시 로딩을 사용하면 최초에 JPQL 쿼리를 사용할 때, 지연 로딩으로 실행하면 지연 로딩된 엔티티를 최초 접근하는 시점에 size에 설정된 값만큼 in 절을 사용해서 조회한다.</li>
<li>10개를 조회하는데, @BatchSize(5)이라면 JPA는 쿼리를 2번 (10/5 = 2개) 날린다.</li>
</ul>
<pre><code class="language-java">@BatchSize(size = 5)
@Entity
public class Image {
    ...
}

@Entity
public class Post {

    @BatchSize(size = 5)
    @OneToMany(mappedBy = &quot;post&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    private List&lt;Image&gt; images = new ArrayList&lt;&gt;();
}</code></pre>
<pre><code class="language-sql">Hibernate: select p1_0.id, p1_0.content, p1_0.title from post p1_0
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.post_id in (?, ?, ?, ?, ?)
Hibernate: select i1_0.id, i1_0.post_id, i1_0.url from image i1_0 where i1_0.post_id in (?, ?, ?, ?, ?)</code></pre>
<p>전역적으로 Batch Size 설정하는 방법</p>
<pre><code class="language-sql">// application.yml

jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100</code></pre>
<h1 id="레퍼런스">레퍼런스</h1>
<p><a href="https://www.inflearn.com/course/ORM-JPA-Basic">https://www.inflearn.com/course/ORM-JPA-Basic</a>
<a href="https://inma.tistory.com/165">https://inma.tistory.com/165</a>
<a href="https://ttl-blog.tistory.com/1135">https://ttl-blog.tistory.com/1135</a>
<a href="https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC">https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링에서 Custom Exception 깔끔하게 정리하기 (feat. API 응답 포맷)]]></title>
            <link>https://velog.io/@jaehoon_go/%EC%8A%A4%ED%94%84%EB%A7%81%EC%97%90%EC%84%9C-Custom-Exception-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaehoon_go/%EC%8A%A4%ED%94%84%EB%A7%81%EC%97%90%EC%84%9C-Custom-Exception-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 Apr 2024 09:15:47 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>예외 처리는 서비스가 안정적으로 동작하게 하는데 필수적인 요소이다. 스프링 어플리케이션의 기능이 늘어남에 따라 도메인 별로 발생할 수 있는 예외들을 추가로 정의하고 예외 처리를 적용하는 과정에서 발생한 문제와 해결 과정을 정리하였다.</p>
<h1 id="문제점기존-방식">문제점(기존 방식)</h1>
<h2 id="사용자-정의-예외마다-클래스를-추가하는-방식">사용자 정의 예외마다 클래스를 추가하는 방식</h2>
<p>예외 하나마다 클래스를 정의하다보니 각 Custom Exception마다 ExceptionHandler 코드를 작성해야 했다.</p>
<p><strong>⇒ 불필요하게 코드 길이 증가, 유지 보수의 어려움</strong></p>
<h4 id="postnotfoundexception">PostNotFoundException</h4>
<pre><code class="language-java">public class PostNotFoundException extends IllegalArgumentException {

    public PostNotFoundException(Long postId) {
        super(&quot;해당 게시글이 없습니다.(게시글 ID: &quot; + postId +&quot;)&quot;);
    }

}</code></pre>
<h2 id="복수의-restcontrolleradvice-사용">복수의 @RestControllerAdvice 사용</h2>
<p>아래와 같은 방법으로 도메인마다 @RestControllerAdvice를 붙여 ExceptionHandler를 정의하였다가 일부 ExceptionHandler만 작동하는 문제가 발생하였다.</p>
<p><strong>⇒ @RestControllerAdvice가 여러군데 선언되어 있어 발생하는 오류 **
**⇒ 하나만 사용하거나 @Order를 사용해서 순서를 지정해줘야한다!</strong></p>
<h4 id="postexceptionhandler">PostExceptionHandler</h4>
<pre><code class="language-java">@RequiredArgsConstructor
@RestControllerAdvice
public class PostExceptionHandler {

    private final ApiResponse apiResponse;

    // 게시글을 찾을 수 없는 경우 예외 처리
    @ExceptionHandler(PostNotFoundException.class)
    public ResponseEntity&lt;?&gt; handlePostNotFoundException(PostNotFoundException e) {
        return apiResponse.error(e.getMessage(), HttpStatus.NOT_FOUND);
    }

    // 게시글 수정, 삭제 권한이 없는 경우 예외처리
    @ExceptionHandler(PostOwnershipException.class)
    public ResponseEntity&lt;?&gt; handlePostOwnershipException(PostOwnershipException e) {
        return apiResponse.error(e.getMessage(), HttpStatus.UNAUTHORIZED);
    }

}</code></pre>
<h1 id="exceptionhandler를-이용한-예외-처리">ExceptionHandler를 이용한 예외 처리</h1>
<h2 id="exceptionhandler">@ExceptionHandler</h2>
<p>@(Rest)Controller에서 발생하는 예외를 처리하는 기능. Exception 클래스들을 값으로 받아 처리할 예외를 지정할 수 있다.</p>
<p> <strong>⇒ 각 Controller 마다 발생하는 예외를 ExceptionHandler로 처리할 수 있지만, Controller의 코드가 길어지는 문제가 발생한다.</strong></p>
<pre><code class="language-java">@RestController
public class PostController {

    // ...

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity handleRuntimeException(RuntimeException ex) {
    // ...
    }

}</code></pre>
<h2 id="restcontrolleradvice">@RestControllerAdvice</h2>
<p>여러 컨트롤러에서 발생하는 예외를 전역적으로 처리하는 역할을 한다.</p>
<p><strong>⇒ 컨트롤러 내부에 ExceptionHandler를 정의할 필요가 없이, 하나의 클래스에서 예외 처리를 담당할 수 있다.</strong></p>
<blockquote>
<p>@ResponseBody가 들어 있어서 Json 형식의 응답을 반환한다. 
@Component도 포함되어 있어 스프링 빈에 등록된다.</p>
</blockquote>
<h1 id="문제-해결-과정">문제 해결 과정</h1>
<h2 id="enum-타입으로-상태-코드-정의">enum 타입으로 상태 코드 정의</h2>
<h4 id="errorcode">ErrorCode</h4>
<pre><code class="language-java">public interface ErrorCode {

    String name();
    HttpStatus getHttpStatus();
    String getMessage();
}</code></pre>
<p>도메인 별로 상태코드를 관리하여 유지/보수가 용이하도록 하였다.</p>
<h4 id="posterrorcode">PostErrorCode</h4>
<pre><code class="language-java">@Getter
@RequiredArgsConstructor
public enum PostErrorCode implements ErrorCode {

    NO_PERMISSION(HttpStatus.UNAUTHORIZED, &quot;User not have permission to post&quot;),
    POST_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;Post not found&quot;),
    ;

    private final HttpStatus httpStatus;
    private final String message;
}</code></pre>
<h2 id="단일-customexception-정의-handler-적용">단일 CustomException 정의 Handler 적용</h2>
<p>기존에 사용했던 방식(예외마다 클래스를 생성)과 다르게 <strong>하나의 CustomException을 정의하고 앞서 정의한 에러코드만 바꿔서 사용하는 방식</strong>을 적용하였다.</p>
<h4 id="customexception">CustomException</h4>
<pre><code class="language-java">@Getter
@RequiredArgsConstructor
public class CustomException extends RuntimeException {

    private final ErrorCode errorCode;
}</code></pre>
<h4 id="사용-예시">사용 예시</h4>
<pre><code class="language-java">public Post validatePostExists(Long postId) {
            Post post = postRepository.findById(postId)
                    .orElseThrow(() -&gt; new CustomException(PostErrorCode.POST_NOT_FOUND));
            return post;
        }</code></pre>
<blockquote>
<p>CustomException을 처리하는 ExceptionHandler가 하나만 필요하므로 @RestControllerAdvice도 기존처럼 여러 개를 사용할 필요가 없다!</p>
</blockquote>
<h4 id="globalexceptionhandler">GlobalExceptionHandler</h4>
<pre><code class="language-java">@RequiredArgsConstructor
@RestControllerAdvice
public class GlobalExceptionHandler {

    private final ApiResponse apiResponse;

    @ExceptionHandler(CustomException.class)
    public ResponseEntity&lt;?&gt; handleCustomException(RestApiException e) {
        ErrorCode errorCode = e.getErrorCode();
        return apiResponse.error(errorCode.getMessage(), errorCode.getHttpStatus());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity&lt;Object&gt; handleIllegalArgument(IllegalArgumentException e) {
        ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER;
        return apiResponse.error(errorCode.getMessage(), errorCode.getHttpStatus());
    }

    // ...

    @ExceptionHandler(Exception.class)
    public ResponseEntity&lt;Object&gt; handleAllException(Exception e) {
        ErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR;
        return apiResponse.error(e.getMessage(), errorCode.getHttpStatus());
    }
}</code></pre>
<blockquote>
<p>미리 정의되어 있는 예외는 handleCustomException에서 처리하고, CustomException에 속하지 않는 예외는 다른 ExceptionHandler에 의해 처리된다.</p>
</blockquote>
<h3 id="응답-예시">응답 예시</h3>
<p>미리 정의한 POST_NOT_FOUND 예외가 정상적으로 적용된 것을 알 수 있다.</p>
<pre><code class="language-json">// 404 Not Found
{
  &quot;status&quot;: &quot;error&quot;,
  &quot;message&quot;: &quot;Post not found&quot;
}</code></pre>
<h1 id="정리">정리</h1>
<p>지금까지 <strong>ExceptionHandler를 사용한 스프링에서의 예외 처리 방법</strong>에 대해 알아보았다.
기존에 사용자 정의 예외를 모두 클래스로 선언하고 여러 개의 RestControllerAdvice에 적용하는 방식 대신에 하나의 사용자 정의 예외 클래스를 선언하고 enum 타입의 상태코드를 적용하여 코드의 <strong>유지/보수 및 확장성</strong>을 향상시킬 수 있었다.</p>
<h1 id="레퍼런스">레퍼런스</h1>
<p><a href="https://mangkyu.tistory.com/204">[Spring] 스프링의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)</a></p>
<p><a href="https://mangkyu.tistory.com/205">[Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2)</a></p>
<p><a href="https://yulee.tistory.com/113">[공부/Spring] @ExceptionHandler 예외처리</a></p>
<h1 id="부록-api-응답-포맷">부록) API 응답 포맷</h1>
<p><a href="https://wildeveloperetrain.tistory.com/240">REST API Response Format, 응답 객체는 어떤 형식이 좋을까?</a></p>
<p>위 예제에서 사용된 ApiResponse는 아래와 같이 JSend 형식의 응답 포맷을 정의해두고 사용한 것이다. JSON 타입으로 API 요청 성공/예외 여부와 메시지(+데이터)를 반환한다. </p>
<pre><code class="language-java">@Component
public class ApiResponse {

    private static final String STATUS_SUCCESS = &quot;success&quot;;
    private static final String STATUS_ERROR = &quot;error&quot;;

    private  &lt;T, E&gt; ResponseEntity&lt;Object&gt; get(String status, @Nullable String message, @Nullable T data, @Nullable E errors, HttpStatus httpStatus) {

        if (status.equals(STATUS_SUCCESS)) {
            return new ResponseEntity&lt;&gt;(SucceededBody.builder()
                    .status(status)
                    .message(message)
                    .data(data)
                    .build(),
                    httpStatus);
        } else if (status.equals(STATUS_ERROR)) {
            return new ResponseEntity&lt;&gt;(ErroredBody.builder()
                    .status(status)
                    .message(message)
                    .build(),
                    httpStatus);
        } else {
            throw new RuntimeException(&quot;Api Response Error&quot;);
        }
    }

//     성공 응답 반환 (상태, 메시지, 데이터)
//     {
//          &quot;status&quot; : &quot;success&quot;,
//          &quot;message&quot; : &quot;success message&quot;,
//          &quot;data&quot; : &quot;배열 또는 단일 데이터&quot;
//     }
    public &lt;T&gt; ResponseEntity&lt;Object&gt; success(String message, T data, HttpStatus httpStatus) {
        return get(STATUS_SUCCESS, message, data, null, httpStatus);
    }

//     성공 응답 반환 (상태, 데이터)
//     {
//          &quot;status&quot; : &quot;success&quot;,
//          &quot;message&quot; : null,
//          &quot;data&quot; : &quot;배열 또는 단일 데이터&quot;
//     }
    public &lt;T&gt; ResponseEntity&lt;Object&gt; success(T data, HttpStatus httpStatus) {
        return get(STATUS_SUCCESS, null, data, null, httpStatus);
    }

//     성공 응답 반환 (메시지, 데이터)
//     {
//          &quot;status&quot; : &quot;success&quot;,
//          &quot;message&quot; : &quot;success message&quot;,
//          &quot;data&quot; : null
//     }

    public &lt;T&gt; ResponseEntity&lt;Object&gt; success(String message, HttpStatus httpStatus) {
        return get(STATUS_SUCCESS, message, null, null, httpStatus);
    }

//     성공 응답 반환 (상태)
//     {
//          &quot;status&quot; : &quot;success&quot;,
//          &quot;message&quot; : null,
//          &quot;data&quot; : null
//     }
    public ResponseEntity&lt;Object&gt; success(HttpStatus httpStatus) {
        return get(STATUS_SUCCESS, null, null, null, httpStatus);
    }

//     예외 발생 시 에러 응답 반환
//     {
//          &quot;status&quot; : &quot;error&quot;,
//          &quot;message&quot; : &quot;custom error message&quot;
//     }
    public ResponseEntity&lt;Object&gt; error(String message, HttpStatus httpStatus) {
        return get(STATUS_ERROR, message, null, null, httpStatus);
    }

    // 성공 응답 객체 바디
    @Builder
    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class SucceededBody&lt;T&gt; {

        private String status;
        private String message;
        private T data;
    }

    // 오류 응답 객체 바디
    @Builder
    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ErroredBody {

        private String status;
        private String message;
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 다형성(polymorphisom)]]></title>
            <link>https://velog.io/@jaehoon_go/Java-%EB%8B%A4%ED%98%95%EC%84%B1polymorphisom</link>
            <guid>https://velog.io/@jaehoon_go/Java-%EB%8B%A4%ED%98%95%EC%84%B1polymorphisom</guid>
            <pubDate>Fri, 15 Mar 2024 15:10:51 GMT</pubDate>
            <description><![CDATA[<h2 id="1-다형성이란">1. 다형성이란?</h2>
<blockquote>
<p>객체지향에서의 다형성이란 &#39;여러 가지 형태를 가질 수 있는 능력&#39;을 의미한다. 구체적으로는 <strong>조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있는 성질</strong>이다.</p>
</blockquote>
<pre><code class="language-java">class Tv {
    boolean power;        // 전원상태(on/off)
    int channel;        // 채널

    void power()     {    power = !power    }
    void channelUp() {    power = !power     }
    void channelUp() {    power = !power     }
}</code></pre>
<pre><code class="language-java">class CaptionTv extends Tv {
    String text;    // 캡션을 보여 주기 위한 문자열
    void caption() { /* 내용생략 */ }</code></pre>
<p>위와 같은 클래스들이 있을 때 Tv와 CaptionTv는 <strong>상속 관계</strong>에 있다. 두 클래스의 인스턴스를 생성하기 위해서는 아래와 같은 방법을 사용할 수 있다.</p>
<pre><code class="language-java">Tv t = new Tv();
CaptionTv c = new CationTv();</code></pre>
<p>만약 인스턴스를 같은 타입의 참조변수가 아닌 <strong>조상타입의 참조변수</strong>나 <strong>자손타입의 참조변수</strong>를 사용하여 참조하는 것이 가능할까?</p>
<h3 id="조상타입의-참조변수로-참조하는-경우">조상타입의 참조변수로 참조하는 경우</h3>
<pre><code class="language-java">Tv t = new CaptionTv();</code></pre>
<p>CaptionTv 인스턴스를 Tv타입의 참조변수 t를 사용하여 참조할 수 있지만, Tv타입의 참조변수로는 <strong>Tv 클래스에 정의되어 있지 않은 CaptionTv 인스턴스의 멤버를 사용할 수 없다.</strong> 
예를 들어 t.power()는 사용할 수 있지만, t.caption()은 사용할 수 없다.</p>
<blockquote>
<p>인스턴스가 가지고 있는 멤버는 참조 변수 타입에 정의된 범위 내에서만 사용 가능하다.</p>
</blockquote>
<h3 id="자손타입의-참조변수로-참조하는-경우">자손타입의 참조변수로 참조하는 경우</h3>
<pre><code class="language-java">CaptionTv c = new Tv();</code></pre>
<p>위 코드는 컴파일하면 에러가 발생한다. 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. <strong>즉, 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야한다.</strong></p>
<blockquote>
<p>조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없다.</p>
</blockquote>
<h2 id="2-참조변수의-형변환">2. 참조변수의 형변환</h2>
<p>기본형 변수와 같이 참조형 변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하다(조상타입&lt;--&gt;자손타입). </p>
<p>기본형 변수에서 작은 자료형에서 큰 자료형의 형변환이 생략 가능한 것처럼, 자손타입의 참조변수를 조상타입으로 형변환하는 경우에는 형변환을 생략할 수 있다.</p>
<pre><code class="language-java">Tv t = new Tv();
Caption c = (CaptionTv)t; // (CaptionTv) 생략 불가</code></pre>
<pre><code class="language-java">CaptionTv c = new CaptionTv();
Tv t = (Tv)c; // (Tv) 생략 가능</code></pre>
<blockquote>
<p>자손타입 -&gt; 조상타입(Up-Casting) :형변환 생략가능
조상타입 -&gt; 자손타입(Down-Casting) :형변환 생략불가</p>
</blockquote>
<pre><code class="language-java">Tv t = null;
CaptionTv c = new CaptionTv();
CaptionTv c2 = null

c.caption();
t = c; // t=(Tv)c;에서 형변환 생략됨
t.caption(); // 컴파일 에러 발생! Tv타입의 참조변수로는 caption() 호출 불가

c2 = (CaptionTv)t; // Down-Cating이므로 형변환 생략불가
c2.caption();
</code></pre>
<blockquote>
<p>형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.</p>
</blockquote>
<blockquote>
<p>위 예시에서 생성된 인스턴스는 CaptionTv 타입이므로 해당 인스턴스를 참조하는 참조변수의 타입에 따라 호출할 수 있는 멤버의 범위만 달라질 뿐이다. 따라서 같은 인스턴스라도 Tv 타입의 참조변수로는 Caption()을 호출할 수 없지만, CaptionTv 타입의 참조변수로는 Caption()을 호출할 수 있다.</p>
</blockquote>
<h2 id="3-instanceof-연산자">3. instanceof 연산자</h2>
<p>참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자가 사용된다(주로 조건문). 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치하고, 결과로는 boolean 값이 반환된다.</p>
<pre><code class="language-java">class InstanceofTest {
    public static void main(String args[]) {
        CaptionTv c = new CaptionTv();

        if(c instanceof CaptionTv) {
             System.out,println(&quot;This is a CaptionTv instance.&quot;);
        }
        if(c instanceof Tv) {
             System.out,println(&quot;This is a CaptionTv instance.&quot;);
        }
        if(c instanceof Object) {
             System.out,println(&quot;This is an CaptionTv instance.&quot;);
        }
        System.out.println(c.getClass().getName()); // 클래스 이름 출력
    }
}

This is a CaptionTv instance.
This is a Tv instance.
This is an Object instance.</code></pre>
<blockquote>
<p>어떤 타입의 대한 instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.</p>
</blockquote>
<h2 id="4-참조변수와-인스턴스의-연결">4. 참조변수와 인스턴스의 연결</h2>
<p>자손 클래스에서 조상 클래스의 멤버변수를 중복으로 정의하면, <strong>호출되는 인스턴스 변수는 참조변수의 타입에 따라 달라진다.</strong></p>
<pre><code class="language-java">class BindingTest{
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        System.out.println(&quot;p.x = &quot;, p.x);
        p.method();

        System.out.println(&quot;c.x = &quot;, c.x);
        c.method();
    }
}

class Parent {
    int x = 100;

    void method() {
        System.out.println(&quot;Parent Method&quot;);
    }
}

class Child extends Parent {
    int x = 200;

    void method() {
        System.out.println(&quot;Child Method&quot;);
    }
}</code></pre>
<p>출력</p>
<pre><code>p.x = 100
Child Method
c.x = 200
Child Method</code></pre><blockquote>
<p>메서드와 달리 멤버변수가 중복될 경우, 인스턴스의 타입과 상관없이 참조변수의 타입에 따라 사용되는 멤버변수가 달라진다.</p>
</blockquote>
<blockquote>
<p>따라서 멤버변수는 주로 private로 접근을 제한하고, 외부에서 메서드를 통해서만 멤버변수에 접근할 수 있도록 하는 것이 바람직하다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] 스프링 부트 기본 동작]]></title>
            <link>https://velog.io/@jaehoon_go/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EA%B8%B0%EB%B3%B8-%EB%8F%99%EC%9E%91</link>
            <guid>https://velog.io/@jaehoon_go/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EA%B8%B0%EB%B3%B8-%EB%8F%99%EC%9E%91</guid>
            <pubDate>Sun, 03 Mar 2024 11:13:38 GMT</pubDate>
            <description><![CDATA[<h2 id="1-welcome-page">1. Welcome Page</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/5326c573-55a4-4a9c-b2c7-bea1441d5cba/image.png" alt=""></p>
<blockquote>
<p>스프링 부트는 <code>static/index.html</code> 을 올려두면 Welcome page 기능을 제공한다.</p>
</blockquote>
<p><strong>resources/static/index.html</strong></p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Hello&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
Hello
&lt;a href=&quot;/hello&quot;&gt;hello&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><br>

<h2 id="2-controller와-viewresolver">2. Controller와 viewResolver</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/a53d4f1c-4030-4d4b-b5f1-3ec19ba5a634/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/67ea5a0a-414e-4efc-b2c5-2b298d7ddae4/image.png" alt=""></p>
<blockquote>
<p>/hello로 접속 =&gt; @GetMapping(&quot;hello&quot;)를 통해 hello 메소드가 호출됨 =&gt; model에 &quot;hello!!&quot;가 저장된 data attribute를 추가 =&gt; &quot;hello&quot;라는 ViewName을 반환 =&gt; viewResolver가 hello.html 반환</p>
</blockquote>
<blockquote>
<p>컨트롤러에서 리턴 값으로 문자를 반환하면 뷰 리졸버( <code>viewResolver</code> )가 화면을 찾아서 처리한다. 
스프링 부트 템플릿엔진 기본 viewName 매핑: <code>resources:templates/</code> +{ViewName}+ <code>.html</code></p>
</blockquote>
<pre><code class="language-java">@Controller
public class HelloController {

    @GetMapping(&quot;hello&quot;)
    public String hello(Model model){
        model.addAttribute(&quot;data&quot;, &quot;hello!!&quot;);
        return &quot;hello&quot;;
    }
}</code></pre>
<p><strong>resources/templates/hello.html</strong>  (thymeleaf)</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Hello&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;안녕하세요. &#39; + ${data}&quot; &gt;안녕하세요. 손님&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><br>

<h2 id="3-정적-컨텐츠">3. 정적 컨텐츠</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/d6784265-594a-427f-8c84-f053e3fe7a32/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/280b0541-ad80-473e-bf3a-bd1623e6deed/image.png" alt=""></p>
<blockquote>
<p>HelloController, viewResolver 거치지 않고 바로 static/hello-static.html 정적 컨텐츠 반환</p>
</blockquote>
<p><strong>resources/static/hello-static.html</strong></p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;static content&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
정적 컨텐츠 입니다.
&lt;/body&gt;
&lt;/html&gt;</code></pre><br>

<h2 id="4-requestparam">4. @RequestParam()</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/6cb63825-5ed5-47c4-84fc-73287969ac8c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/b47c8491-492a-4e83-8122-87b9b5f31382/image.png" alt=""></p>
<blockquote>
<p>@RequestParam(&quot;name&quot;) =&gt; 쿼리 파라미터를 통해 name Atrribute 추가 =&gt; hello-template.html 반환</p>
</blockquote>
<p><strong>Controller</strong></p>
<pre><code class="language-java"> @Controller
 public class HelloController {
     @GetMapping(&quot;hello-mvc&quot;)
     public String helloMvc(@RequestParam(&quot;name&quot;) String name, Model model) {
         model.addAttribute(&quot;name&quot;, name);
         return &quot;hello-template&quot;;
     }
}</code></pre>
<p><strong>View</strong></p>
<pre><code>&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
 &lt;body&gt;
 &lt;p th:text=&quot;&#39;hello &#39; + ${name}&quot;&gt;hello! empty&lt;/p&gt;
 &lt;/body&gt;
&lt;/html&gt;</code></pre><br>

<h2 id="5-responsebody">5. @ResponseBody</h2>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/28adc5e4-2532-406e-b51b-c1239dffbbc8/image.png" alt=""></p>
<h3 id="1-문자-반환">1) 문자 반환</h3>
<blockquote>
<p>viewResolver 없이 HTTP Body에 문자 내용을 &quot;직접&quot; 반환</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/46cde3c7-5e2a-427c-9dbd-51f167fcbe05/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/4a609524-7d92-4cc3-b93b-35908236f099/image.png" alt=""></p>
<pre><code class="language-java"> @Controller
 public class HelloController {
     @GetMapping(&quot;hello-string&quot;)
     @ResponseBody
     public String helloString(@RequestParam(&quot;name&quot;) String name) {
         return &quot;hello &quot; + name;
     }
}</code></pre>
<h3 id="2-객체-반환">2) 객체 반환</h3>
<blockquote>
<p>@ResponseBody와 함께 객체 반환시 객체가 JSON으로 변환됨</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/70c55b7f-214f-46c3-99ba-8992dd6e886b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehoon_go/post/8bd3ebf2-6015-41a7-a5fa-47d85e1257aa/image.png" alt=""></p>
<pre><code class="language-java"> @Controller
 public class HelloController {
     @GetMapping(&quot;hello-api&quot;)
     @ResponseBody
     public Hello helloApi(@RequestParam(&quot;name&quot;) String name) {
         Hello hello = new Hello();
         hello.setName(name);
         return hello;
     }
     static class Hello {
         private String name;
         public String getName() {
             return name;
}
         public void setName(String name) {
             this.name = name;
} }
}
</code></pre>
<p><strong>빌드하고 실행하기</strong>
콘솔로 이동</p>
<ol>
<li><code>./gradlew build</code></li>
<li><code>cd build/libs</code></li>
<li><code>java -jar hello-spring-0.0.1-SNAPSHOT.jar</code></li>
<li>실행확인</li>
</ol>
<h2 id="참고-자료">참고 자료</h2>
<p><a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8">스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술</a></p>
]]></description>
        </item>
    </channel>
</rss>