<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>linear_.log</title>
        <link>https://velog.io/</link>
        <description>선형의 비선형적 기록 🐜</description>
        <lastBuildDate>Sat, 20 Sep 2025 16:38:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>linear_.log</title>
            <url>https://velog.velcdn.com/images/linear_/profile/a19296a6-3791-4508-998e-52dae3830f2d/image.svg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. linear_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/linear_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[NLP] Seq2Seq]]></title>
            <link>https://velog.io/@linear_/NLP-Seq2Seq</link>
            <guid>https://velog.io/@linear_/NLP-Seq2Seq</guid>
            <pubDate>Sat, 20 Sep 2025 16:38:19 GMT</pubDate>
            <description><![CDATA[<h1 id="encoder-decoder-model의-등장-배경">Encoder-Decoder Model의 등장 배경</h1>
<p>Machine Translation, QA, Speech Recognition, Image Captioning 같은 문제에서는 입력과 출력 길이가 다를 수 있다. 일반 RNN은 이러한 입력과 출력의 길이가 서로 다르거나 aligned되지 않은 경우를 처리할 수 없다.</p>
<h1 id="seq2seq">Seq2Seq</h1>
<p><img src="https://velog.velcdn.com/images/linear_/post/1e9b5978-167e-459e-a8bf-9696ca6ac936/image.png" alt="Seq2Seq">
하나의 RNN (Encoder)를 사용하여 입력 시퀀스를 한 단어씩 읽고, 고정된 차원의 벡터 표현인 Context Vector를 얻는다. 그리고 다른 RNN (Decoder)를 사용하여, 그 벡터로부터 출력 시퀀스를 생성한다.
인코더는 각 입력 시퀀스를 대응되는 Context Vector로 변환하고, 디코더는 이 과정을 역으로 수행하여 이전 출력을 현재 입력으로 사용하고, 매 시점마다 벡터를 출력 단어로 변환한다. 즉, 이전 단계 출력에 기반하여 다음 단어를 예측하는 Conditioned LM이다.</p>
<blockquote>
<p>✏️ <strong>Conditional LM?</strong>
일반적인 LM과 유사하지만, 추가적인 맥락 $c$에 조건화된다.
$$
P\left(y|c\right)=\prod_i P\left(y_i|y_1,\dots,y_{i-1},c\right)
$$
예를 들어, 텍스트 요약에서 $c$는 긴 문서, $y$는 요약본을 의미하고, 기계 번역에서 $c$는 영어 문장을 $y$는 한국어 문장을 의미한다. 이는 디코더가 타겟 시퀀스 $y$의 다음 단어를 예측하기 때문이다. 다음 단어는 지금까지의 타겟 시퀀스의 원본 시퀀스 $x$에 기반해 예측된다.</p>
</blockquote>
<blockquote>
<p>✏️ 일반적으로 <code>&lt;SOS&gt;</code> (Start of Sequence)와 <code>&lt;EOS&gt;</code> (End of Sequence) 토큰을 포함한다.</p>
</blockquote>
<p>Machine Translation, Text summarization, Conversational Modeling, QA, Image Captioning에 사용된다.</p>
<h2 id="한계">한계</h2>
<ul>
<li>인코더와 디코더 모두 시점이 멀어질수록 초반 정보가 잊혀진다. 즉, Context Vector에 첫 단어보다 마지막 단어의 정보가 많이 담겨 있다.</li>
<li>입력 시퀀스가 길어지면 너무 많은 단어가 하나의 Context Vector로 압축되어 정보가 손실된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NLP] GRU와 LSTM]]></title>
            <link>https://velog.io/@linear_/NLP-GRU%EC%99%80-LSTM</link>
            <guid>https://velog.io/@linear_/NLP-GRU%EC%99%80-LSTM</guid>
            <pubDate>Sat, 20 Sep 2025 15:21:29 GMT</pubDate>
            <description><![CDATA[<h1 id="gating">Gating</h1>
<p>Sigmoid와 요소 단위 곱을 게이트로 사용하여 얼마나 많은 정보가 통과할지 제어한다.
$$
f=\sigma\left(\mathbf{w}<em>{hh}h</em>{t-1}+\mathbf{w}_{xh}x_t\right)
$$</p>
<h1 id="gru-gated-recurrent-unit">GRU (Gated Recurrent Unit)</h1>
<p>Reset Gate $r_t$와 Update Gate $z_t$를 사용한다. 두 Gate는 공식이 같지만 가중치와 사용 용도가 다르다.
Reset Gate는 과거 메모리를 얼마나 잊을지 결정하며, 새로운 hidden state $\tilde{h}_t$를 계산할 때 사용한다.
Update Gate는 과거 메모리를 얼마나 미래로 유지할지 결정하며, 최종 hidden state $h_t$를 계산할 때 사용한다.</p>
<h1 id="lstm-long-short-term-memory">LSTM (Long Short-Term Memory)</h1>
<p>LSTM은 Gradient Vanishing을 해결하고 Long Term Dependency를 포착한다.
Gated RNN은 신경망이 오래된 상태를 잊도록 하면서 동시에 새로운 정보를 축적할 수 있게 한다. Gate는 어떤 정보를 읽을지/지울지/쓸지를 제어하고, 열림 (1), 닫힘 (0), 또는 그 사이 값이 될 수 있다.
입력 데이터는 Input Gate, Forget Gate, Output Gate를 거친다. 여기에 Memory Cell $c$가 추가된다. 모든 게이트는 시그모이드 함수이며, Candidate Cell $\tilde{c}$는 tanh 함수를 거친다. 모든 게이트와 셀은 각각 고유한 가중치를 가지며, 연산은 요소 단위로 이루어진다.</p>
<h2 id="gate의-역할">Gate의 역할</h2>
<ul>
<li>Input Gate: 언제 입력을 받아들일지 결정한다.</li>
<li>Forget Gate: 언제 과거 메모리를 지울지 결정한다. $f=0$이면 메모리를 삭제한다. 즉, 기억할 Long-Term Memory의 비율을 결정한다.</li>
<li>Output Gate: 얼마나 많은 메모리를 다음 단계로 보낼지 결정한다. 즉, 새로운 Short-Term Memory의 비율을 결정한다.<h2 id="동작-흐름">동작 흐름</h2>
Forget Gate는 과거 상태 중 불필요한 history를 지우고, Input Gate는 새로운 정보를 저장한다. Cell은 이 두 가지를 결합하여 셀 상태 값을 업데이트한다. 이때 더하기 연산을 사용해 정보 추가가 용이하다. Output Gate는 셀 상태의 필터링된 버전을 생성해 다음 시점으로 전달한다. Sigmoid Layer는 어떤 값을 업데이트할지, Tanh Layer는 어떻게 업데이트할지를 결정한다. Forget Gate가 1로, Input Gate가 0으로 초기화되면, 셀 정보는 무한히 유지된다.</li>
</ul>
<h1 id="gru-vs-lstm">GRU vs LSTM</h1>
<p><img src="https://velog.velcdn.com/images/linear_/post/5fbcdd7f-f6d0-4f04-9120-0191e1d8fc5a/image.png" alt="RNN LSTM GRU">
두 모델 모두 게이트를 사용한다.
그러나 GRU는 Hidden State를 사용하여 Long/Short Term Dependency를 모두 표현하고, LSTM은 Long-Term Memory를 담당하는 Cell State와 Short-Term Memory를 담당하는 Hidden State를 구분한다.
GRU는 파라미터 수가 적고 학습이 빠르다.
어떤 모델이 항상 낫다는 증거는 없으나, 이론적으로 LSTM이 더 긴 시퀀스를 기억할 수 있어 Long-Range Correlation 모델링에 더 유리하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NLP] RNN]]></title>
            <link>https://velog.io/@linear_/NLP-RNN</link>
            <guid>https://velog.io/@linear_/NLP-RNN</guid>
            <pubDate>Sat, 20 Sep 2025 10:30:44 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>RNN (Recurrent Neural Network)은 임의 길이의 순차 입력을 받아 각 단계에서 동일한 가중치를 적용하고 출력을 생성한다.</p>
<h2 id="sequence-modeling-with-rnn">Sequence Modeling with RNN</h2>
<p><img src="https://velog.velcdn.com/images/linear_/post/856e45a5-2775-404a-9b40-e2ef7273b83c/image.webp" alt="RNN"></p>
<ul>
<li>one to one: Vanilla NN (ex. image classification)</li>
<li>one to many: Image Captioning (이미지 → 단어 시퀀스)</li>
<li>many to one: Sentiment Analysis (단어 시퀀스 → 감정 클래스)</li>
<li>many to many (left): Machine Translation (단어 시퀀스 → 단어 시퀀스)</li>
<li>many to many (right): PoS Tagging, NER, Video Classification on Frame Level</li>
</ul>
<h1 id="vanilla-rnn">Vanilla RNN</h1>
<p><img src="https://velog.velcdn.com/images/linear_/post/4ca83c1c-fa58-4825-8b98-8e16068fc7b4/image.png" alt="Vanilla RNN">
Feed Forward NN은 입력과 출력이 단방향으로만 연결되어, 순차적 맥락을 담아낼 수 없다. 시퀀스를 다루기 위해서는 정보가 시간에 걸쳐 유지될 수 있는 Loop 구조가 필요하다.</p>
<h2 id="동작-원리">동작 원리</h2>
<p>Input vector: $\mathbf{x}$, Output vector: $\mathbf{y}$
$\mathbf{y}$는 방금 입력된 $\mathbf{x}$뿐만 아니라 과거의 모든 입력에 영향을 받는다.
각 시점 $t$에서, output $\hat{y}<em>t$는 current input $\mathbf{x}_t$와 내부 hidden state $h_t$로부터 계산된다. hidden state $h_t$는 다음 시점 $h</em>{t+1}$로 전달된다. 매 시점마다 Input을 읽고, hidden state를 갱신하며, output을 생성한다.
정보는 내부적으로 한 시점에서 다음 시점으로 전달된다. 결과적으로 $\mathbf{x}$는 네트워크 전체로 전파된다.</p>
<h2 id="h의-계산">$h$의 계산</h2>
<p>매 시점마다 내부 hidden state $h_t$를 계산한다. 가중치 $\mathbf{w}$로 파라미터화된 Activation Function $f$가 이전 hidden state $h_{t-1}$과 input vector $\mathbf{x}$에 적용된다. 매 시점마다 동일한 함수와 가중치가 사용되어 가변 길이 입력을 처리할 수 있다.
RNN은 매 시점마다 동일한 가중치를 반복 사용하기 때문에, 한 시점에서 Gradient가 감소하면 매 시점마다 반복해서 감소한다. 따라서 Activation Function으로 Gradient vanishing 문제가 덜한 $\tanh$를 사용한다.</p>
<h2 id="input과-output">Input과 Output</h2>
<p>매 시점마다 두 Input $\left(x_t, h_{t-1}\right)$을 사용하여 hidden state $h_t$를 계산한다. 일반적으로 초기 hidden state $h_0$는 0으로 설정한다.
output $\hat{\mathbf{y}}<em>t$는 hidden state $h_t$에 가중치를 곱해 계산하며, 분류 문제에서는 softmax를 사용하여 $\hat{y}_t$를 결정한다.
동일한 세 가지 가중치 행렬 ($\mathbf{w}</em>{hh}$, $\mathbf{w}<em>{xh}$, $\mathbf{w}</em>{hy}$)이 네트워크 전체에서 반복적으로 사용된다.
$$
\begin{aligned}
h_t&amp;=f_\mathbf{w}\left(h_{t-1},x_t\right)\
&amp;=\tanh\left(\mathbf{w}<em>{hh}^T h</em>{t-1}+\mathbf{w}<em>{xh}^T x_t\right)\
\hat{y}_t&amp;=\mathbf{w}</em>{hy}^T h_t
\end{aligned}
$$</p>
<h2 id="이점과-한계">이점과 한계</h2>
<h3 id="이점">이점</h3>
<ul>
<li>가변 길이 입력을 처리할 수 있다.</li>
<li>입력 텍스트가 길어져도 모델의 크기가 증가하지 않는다.</li>
<li>매 시점마다 동일한 가중치가 적용된다.<h3 id="한게">한게</h3>
</li>
<li>Long-Term Dependency Problem</li>
<li>Gradient Vanishing/Exploding</li>
</ul>
<h1 id="rnn의-종류">RNN의 종류</h1>
<h2 id="simple-rnn">Simple RNN</h2>
<p>하나의 hidden RNN Layer로 구성된다. 모델의 메모리 부분을 유지하고, Feed Forward Dense Layer가 이어진다.</p>
<h2 id="stacked-rnn">Stacked RNN</h2>
<p><img src="https://velog.velcdn.com/images/linear_/post/a4958acc-5cbb-4b8a-a888-361a17660b10/image.png" alt="Stacked RNN">
여러 RNN Layer가 차례대로 쌓여 있고, 각 레이어는 고유의 Memory unit을 가진다. 한 RNN Layer의 출력 시퀀스가 다음 RNN Layer의 입력으로 들어간다.
더 깊은 네트워크일수록 문맥이나 시계열 정보를 더 잘 포착할 수 있다.</p>
<h2 id="bidirectional-rnn">Bidirectional RNN</h2>
<p><img src="https://velog.velcdn.com/images/linear_/post/4f108df0-4d5b-43ad-bebd-804043168063/image.png" alt="Bidirectional RNN"></p>
<p>두 RNN을 결합한 구조로, 하나는 시간 순서대로 입력을 처리하고 다른 하나는 시간 역순으로 입력을 처리한다. 이 구조는 네트워크가 시퀀스에 대한 앞뒤 정보를 동시에 활용할 수 있게 한다. 즉, 현재 라벨을 예측할 때 미래와 과거의 정보를 모두 사용할 수 있다. 두 RNN의 출력이 결합되어 최종 출력으로 사용된다.</p>
<h1 id="예시-rnn-lm을-사용한-텍스트-생성">예시: RNN LM을 사용한 텍스트 생성</h1>
<p>학습된 RNN LM을 사용하여 시작 단어로부터 텍스트를 생성한다. 이 단어를 사용하여 출력 확률 분포를 샘플링하여 다음 단어를 예측한다. 샘플링된 output은 다음 단계의 입력이 된다. 원하는 시점까지 각 시점에서 다음 단어를 반복적으로 생성한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NLP] Language Model]]></title>
            <link>https://velog.io/@linear_/NLP-Language-Model</link>
            <guid>https://velog.io/@linear_/NLP-Language-Model</guid>
            <pubDate>Sat, 20 Sep 2025 09:21:15 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>Language Model (LM)은 이전 단어(/문자/subword)를 기반으로 문장에서 다음에 올 단어(/문자/subword)를 예측하는 모델이다.
단어 시퀀스 $w_1, w_2, \dots, w_{i-1}$이 주어졌을 때, 다음 단어 $w_i$의 확률 분포 $P\left(w_i|w_{i-1},\dots,w_1\right)$을 계산한다.
텍스트 자동완성, 검색어 입력 시 추천 등에서 사용한다.</p>
<h1 id="n-grams">n-grams</h1>
<p>n-gram은 연속된 $n$개의 단어 조각을 의미한다. 예를 들어 &#39;Students opened their notebooks&#39;라는 문장이 있을 때, unigrams, bigrams, trigrams는 각각 다음과 같다.</p>
<ul>
<li>unigrams: Students, opened, their, notebooks</li>
<li>bigrams: Students opened, opened their, their notebooks</li>
<li>trigrams: Students opened their, opened their notebooks
일반적으로 $n$은 5 이하의 값을 사용한다.</li>
</ul>
<h2 id="n-gram-language-modeling">n-gram Language Modeling</h2>
<p>전체 시퀀스가 아닌 마지막 몇 개의 단어만 사용해 다음 단어를 예측한다. n-gram LM은 마지막 $n-1$개의 단어로 다음 단어를 예측한다. 예를 들어, Trigram LM은 아래와 같이 확률 분포를 계산한다.
$$
P\left(w_i|w_1,\dots,w_{i-1}\right)=P\left(w_i|w_{i-1},w_{i-2}\right)
$$</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NLP] Tokenization과 Embedding]]></title>
            <link>https://velog.io/@linear_/NLP-Tokenization%EA%B3%BC-Embedding</link>
            <guid>https://velog.io/@linear_/NLP-Tokenization%EA%B3%BC-Embedding</guid>
            <pubDate>Sat, 20 Sep 2025 09:06:09 GMT</pubDate>
            <description><![CDATA[<h1 id="tokenization">Tokenization</h1>
<p>Token은 특정 문서에서 연속된 문자들 중 의미 있는 단위다. Tokenization은 언어 입력을 토큰과 같은 작은 단위로 나누는 과정이다.</p>
<h2 id="tokenization-level">Tokenization Level</h2>
<h3 id="단어-수준">단어 수준</h3>
<p>일반적으로 공백, 구두점을 기준으로 분리하며, 각 단어는 고유 ID로 매핑된다. &#39;dog&#39;, &#39;dogs&#39;와 같이 매우 유사한 단어도 서로 다른 ID를 가지며, Vocabulary 크기가 지나치게 커질 수 있다. Vocabulary 크기를 제한하였을 때 Vocabulary에 포함되지 못한 단어 (Out of Vocab)은 &quot;unknown&quot; 토큰 ID로 매핑되며, 이는 정보 손실을 초래할 수 있다.</p>
<h3 id="문자-수준">문자 수준</h3>
<p>토큰 수가 적으며 Out of Vocab 문제가 줄어든다. 그러나 각 문자는 많은 정보를 담지 못하며, 각 단어가 매우 긴 시퀀스로 표현될 수 있다.</p>
<h3 id="subword-수준">Subword 수준</h3>
<p>단어 수준과 문자 수준 토큰화의 중간 지점이다.</p>
<h4 id="원칙">원칙</h4>
<ul>
<li>자주 사용되는 단어는 분할하지 않는다. (ex. cat → cat)</li>
<li>드물게 사용되는 단어는 의미 있는 subword로 분할한다. (ex. cats → cat/s, playing → play/ing)</li>
</ul>
<h1 id="embedding">Embedding</h1>
<h2 id="one-hot-embedding">One-Hot Embedding</h2>
<p>0과 1로 이루어진 $N \times 1$ 크기 ($N$: Vocabulary 크기)의 One-Hot Vector로 표현한다. One-Hot vector는 sparse하고 크기가 크며, 서로 다른 두 One-Hot vector의 Inner Product는 0이다.
단어의 특성에 대한 정보나 단어 간 유사성을 제공하지 못한다.</p>
<h2 id="word-embedding">Word Embedding</h2>
<h3 id="context의-이해-분포-가설-distributional-hypothesis">Context의 이해: 분포 가설 (Distributional Hypothesis)</h3>
<p>단어의 의미는 그 단어가 나타나는 문맥에 의해 정의된다. 즉, 비슷한 문맥에서 쓰이는 단어들은 의미도 비슷하다. 언어학적으로 단어의 의미를 그 분포, 즉 주변 문맥을 기반으로 분석할 수 있음을 시사한다.</p>
<h3 id="word2vec">Word2Vec</h3>
<p>함께 출현한 단어로 이루어진 Train data를 구성하고, 주변 단어와 타겟 단어 간의 분류 문제로 정의한 후 학습한다. 신경망을 기반으로 주변 단어들과의 관계를 임베딩 공간에 투영한다.</p>
<h4 id="학습-방식">학습 방식</h4>
<ul>
<li>Skip-gram: 중심 단어로부터 주변 단어를 예측한다.</li>
<li>CBOW (Continuous Bag of Words): 주변 단어들로부터 중심 단어를 예측한다.<h3 id="glove">GloVe</h3>
행렬 분해를 기반으로 단어 임베딩을 학습한다.<h3 id="fasttext">FastText</h3>
Word2Vec의 확장 버전으로, 단어를 subword 단위로 분해해 임베딩한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Gradient Descent]]></title>
            <link>https://velog.io/@linear_/DL-Gradient-Descent</link>
            <guid>https://velog.io/@linear_/DL-Gradient-Descent</guid>
            <pubDate>Sat, 20 Sep 2025 08:13:22 GMT</pubDate>
            <description><![CDATA[<h1 id="gradient-descent">Gradient Descent</h1>
<p>Loss Function의 Gradient를 계산하고, Gradient가 0에 가까워질 때까지 parameter $\theta$를 반복적으로 조정한다.
Chain Rule을 통해 Gradient를 계산하고 기울기의 반대 방향으로 small step 이동하며 parameter를 업데이트한다. 이때 learning rate ($\alpha$)를 곱하여 Step size를 조정한다. 시간이 지남에 따라 Loss Function $J\left(\theta\right)$가 줄어들어, 결국 Loss를 최소화하는 방향으로 이동한다.
$$
\theta^{new}=\theta^{old}-\alpha\nabla_\theta J\left(\theta\right)
$$
매 반복 시행마다 가중치를 업데이트하기 위해 학습 데이터 전체를 사용하기 때문에, 학습 데이터가 커지면 학습에 소요되는 비용이 매우 커진다.</p>
<h1 id="sgd-stochastic-gradient-descent">SGD (Stochastic Gradient Descent)</h1>
<p>Stochastic Gradient Descent는 가중치 업데이트 과정에서 학습 데이터 전체를 사용하지 않고, 샘플 데이터 하나만 사용한다. 즉, 학습 샘플 데이터 한 개로 계산한 에러를 바탕으로 가중치를 업데이트하고 다음으로 넘어간다.</p>
<h1 id="mini-batch-gradient-descent">Mini-Batch Gradient Descent</h1>
<p>Mini-Batch Gradient Descent는 가중치 업데이트 과정에서 샘플 데이터를 여러 개 사용한다. 예를 들어 배치 사이즈가 32이면 32개의 샘플을 사용하여 Gradient의 평균을 구하고, 이 값을 사용하여 가중치를 업데이트한다. 배치 사이즈가 1인 경우 SGD와 동일하다.</p>
<h1 id="learning-rate">Learning Rate</h1>
<p>학습 중 weight를 업데이트할 때 step size를 결정한다.
$$
\mathbf{w}\leftarrow\mathbf{w}-\alpha\frac{\partial J(\theta)}{\partial\mathbf{w}}
$$
learning rate가 너무 작으면 local minimum에 갇히거나 학습 속도가 너무 느려질 수 있다. 반대로 learning rate가 너무 크면 global optimum을 지나칠 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Optimizer Function]]></title>
            <link>https://velog.io/@linear_/DL-Optimizer-Function</link>
            <guid>https://velog.io/@linear_/DL-Optimizer-Function</guid>
            <pubDate>Fri, 19 Sep 2025 12:52:41 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<h2 id="optimizer">Optimizer</h2>
<p>신경망이 Loss를 기반으로 어떻게 학습할지 결정한다.</p>
<h2 id="adaptive-optimizer">Adaptive Optimizer</h2>
<p>손실 지형이나 학습 단계에 따라 학습률을 조정한다. 학습 과정에서 파라미터를 다른 속도로 조정하기 때문에 더 효과적이며, 더 신뢰할 만한 성능을 보인다.</p>
<h1 id="argument">Argument</h1>
<h3 id="learning-rate">Learning rate</h3>
<p>Optimizer의 학습률을 결정한다.</p>
<h3 id="momentum">Momentum</h3>
<p>Gradient Descent에서 이미 계산된 기울기 정보를 이용해, Step Size를 조절한다. 직관적으로 기울기 방향이 바뀌면 이동 폭을 줄이고, 기울기 방향이 유지되면 이동 폭을 키운다. 이를 통해 학습 속도가 빨라지고 Local Optimum에 갇히는 문제가 감소한다.</p>
<h3 id="weight-decay">Weight decay</h3>
<p>Weight regularization을 통해 Overfitting을 방지한다.</p>
<h1 id="optimizer-function">Optimizer Function</h1>
<h2 id="gradient-descent">Gradient Descent</h2>
<p>$$
\theta=\theta-\eta\nabla_\theta J\left(\theta\right)
$$</p>
<h2 id="stochastic-gradient-descent">Stochastic Gradient Descent</h2>
<p>$$
\theta=\theta-\eta\nabla_\theta J\left(\theta;sample\right)
$$</p>
<h2 id="mini-batch-gradient-descent">Mini-Batch Gradient Descent</h2>
<p>$$
\theta=\theta-\eta\nabla_\theta J\left(\theta;N sample\right)
$$</p>
<h2 id="sgdmomentum">SGD+Momentum</h2>
<p>$$
v=\gamma v+\nabla_\theta J\left(\theta\right) \
\theta=\theta-\eta v
$$</p>
<h2 id="sgd-with-nesterov-accelerated-gradient">SGD with Nesterov Accelerated Gradient</h2>
<p>$$
v=\gamma v+\eta\nabla_\theta J\left(\theta-\gamma v\right) \
\theta=\theta-v
$$</p>
<blockquote>
<p>✏️ 이외에도 Adagrad, Adadelta, Adam 등의 Optimizer Function이 존재한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Activation Function과 Loss Function]]></title>
            <link>https://velog.io/@linear_/DL-Activation-Function%EA%B3%BC-Loss-Function</link>
            <guid>https://velog.io/@linear_/DL-Activation-Function%EA%B3%BC-Loss-Function</guid>
            <pubDate>Fri, 19 Sep 2025 12:33:03 GMT</pubDate>
            <description><![CDATA[<h1 id="activation-function">Activation Function</h1>
<p>각 Layer에서 비선형 변환을 적용해, 신경망이 더 잘 학습하고 복잡한 작업을 수행할 수 있도록 한다.</p>
<h2 id="sigmoid-function">Sigmoid Function</h2>
<p>$$
\phi\left(z\right)=\frac{1}{1+e^{-z}}
$$
Sigmoid 함수는 입력값을 0과 1 사이의 값으로 압축한다. Binary Classification Problem에 적합하며, 신경망의 Output Layer에서 주로 사용된다.
입력값이 극단적인 경우에도 출력값이 0 또는 1에 가까워지며, 이로 인해 Gradient Vanishing이 발생할 수 있다.</p>
<h2 id="tanh">Tanh</h2>
<p>$$
\tanh\left(z\right)=2\sigma\left(2z\right)-1
$$
Hyperbolic Tangent는 Sigmoid Function을 단순히 rescale 및 shift한 것이다. RNN, LSTM에서 사용한다.
Sigmoid와 유사하지만 출력값이 0 중심으로 분포되어 Gradient Vanishing 문제가 덜 발생한다.</p>
<h2 id="softmax-function">Softmax Function</h2>
<p>$$
\sigma(z_i)=\frac{e^{z_i}}{\sum_{j=1}^K{e^{z_j}}}
$$
Softmax 함수는 Output을 0과 1 사이로 압축하고, 각 클래스 값이 전체 값에서 차지하는 확률을 계산한다. 여러 출력 클래스 값에 대해, 출력 확률의 합은 항상 1이 된다. 일반적으로 Multi Class Classification의 마지막 Layer에서 사용한다.</p>
<h2 id="relu-rectified-linear-unit">ReLU (Rectified Linear Unit)</h2>
<p>$$
f(x)=\max(0,x)
$$
ReLU는 단순한 비선형 함수로, 입력이 양수이면 그대로 반환하고, 음수이면 0으로 반환한다. 신경망의 Hidden Layer에서 가장 널리 사용된다.
계산이 간단하고 Gradient Vanishing 문제가 적다.</p>
<h2 id="leaky-relu">Leaky ReLU</h2>
<p>$$
f(x) =
\begin{cases}
x &amp; \text{if } x \geq 0 \
a \cdot x &amp; \text{otherwise}
\end{cases}
$$
ReLU의 개선된 버전으로, Gradient Vanishing 문제를 해결하기 위해 등장했다. 음수 입력에 대해 작은 기울기를 유지하여 Gradient가 0이 되는 것을 피할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/linear_/post/9972f0f2-7858-47df-a5c8-7c89e4c02cd0/image.png" alt="Activation Function"></p>
<h1 id="loss-function">Loss Function</h1>
<p>예측값과 실제값의 차이를 어떻게 계산할지 정의하여, 신경망 모델이 정답으로부터 얼마나 멀리 벗어나 있는지 알려준다.</p>
<h2 id="mse-mean-squared-error">MSE (Mean Squared Error)</h2>
<p>$$
\frac{1}{N}\sum_{i=1}^N {\left(\hat{y}-y\right)^2}
$$</p>
<h2 id="mae-mean-absolute-error">MAE (Mean Absolute Error)</h2>
<p>$$
\frac{1}{N}\sum_{i=1}^N {\lvert \hat{y}-y\rvert}
$$</p>
<h2 id="binary-cross-entropy">Binary Cross Entropy</h2>
<p>$$
\text{BCE Loss}=\sum_i{-y_i\log\left(\hat{y}_i\right)-\left(1-y_i\right)\log\left(1-\hat{y}_i\right)}
$$
Binary Classification에서 사용하며, 일반적으로 마지막 Layer에 Sigmoid Activation을 추가한다.</p>
<h2 id="cross-entropy-loss">Cross Entropy Loss</h2>
<p>$$
\text{CE Loss} \left(y_i, \hat{y}<em>i\right) = - \sum</em>{i=1}^{C} y_i \log\left(\hat{y}_i\right)
$$
Multi-Class Problem에서 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Neural Network]]></title>
            <link>https://velog.io/@linear_/DL-Neural-Network</link>
            <guid>https://velog.io/@linear_/DL-Neural-Network</guid>
            <pubDate>Fri, 19 Sep 2025 12:14:04 GMT</pubDate>
            <description><![CDATA[<h1 id="neural-network와-perceptron">Neural Network와 Perceptron</h1>
<p>Neural Network는 인간의 신경망 구조에서 영감을 받은 모델이다. Perceptron은 Neural Network의 기본 요소로, 각 퍼셉트론은 신경 세포 하나를 나타낸다.
퍼셉트론은 입력 벡터의 Weighted sum에 bias를 더한 뒤, Activation Function을 적용한다. 단일 퍼셉트론은 사실상 Logistic Regression과 동일하다.</p>
<p><img src="https://velog.velcdn.com/images/linear_/post/98c1a2c8-d76e-4174-9251-c0fcc216f145/image.webp" width=350px></p>


<blockquote>
<p>✏️ <strong>Activation Function의 역할</strong></p>
</blockquote>
<ol>
<li>출력을 특정 범위로 제한한다.</li>
<li>데이터에 비선형성을 추가한다.</li>
</ol>
<blockquote>
<p>✏️ Neural Network는 여러 퍼셉트론으로 Layer를 구성해 쌓아가는 구조이기 때문에 Multi Layer Perceptron이라고도 불린다.</p>
</blockquote>
<ul>
<li>Input Layer: Feature 값을 그대로 입력으로 받는 첫 레이어</li>
<li>Hidden Layer: Input Layer와 Output Layer 사이의 레이어</li>
<li>Output Layer: 최종 출력값 (예측값)을 반환하는 레이어<blockquote>
</blockquote>
Input Layer와 Output Layer는 오직 하나이지만, Hidden Layer는 여러 개일 수 있다.<blockquote>
<p><img src="https://velog.velcdn.com/images/linear_/post/7380a721-6024-472f-9910-5acad3d752e4/image.png" alt="Hidden Layer"></p>
</blockquote>
</li>
</ul>
<h1 id="feed-forward와-backpropagation">Feed Forward와 Backpropagation</h1>
<p>Feed Forward (순전파)는 Input Feature를 받아서 출력 결과를 받는 것을 말한다. 즉, Input Layer에서 Output Layer 방향으로 값이 흐른다. Input Vector와 Weight의 Dot Product를 계산하여 Bias 더한 뒤, Activation Function을 적용한다.
Backpropagation (역전파)은 반대로 Output Layer에서 Input Layer 방향으로 오차 신호가 흐른다.</p>
<h1 id="neural-network의-학습">Neural Network의 학습</h1>
<ol>
<li>주어진 Input Feature에 대해 Feed Forward 과정을 거쳐 예측값을 계산한다. 맨 처음에는 가중치에 임의의 초기값을 할당한다.</li>
<li>예측값과 타겟값 사이의 차이, 즉 Loss Function을 계산한다.</li>
<li>Loss Function 값에 대한 각 퍼셉트론의 가중치 벡터의 Gradient 값을 활용하여 가중치를 업데이트한다. 해당 과정은 역전파를 통해 수행된다.</li>
<li>1~3이 끝나면 다음 샘플 데이터에 대해 해당 과정을 반복한다. 모든 샘플 데이터에 대해 업데이트가 끝나면 Iteration이 한 번 끝난 것으로 간주한다. 이때 한 번의 Iteration을 1 Epoch라고 한다.</li>
<li>이러한 Iteration을 Gradient가 수렴할 때까지 반복 수행한다.</li>
</ol>
<blockquote>
<p>✏️ 1 Epoch = 모든 학습 샘플에 대해 1번의 Forward Pass + Backward Pass</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/linear_/post/dae51fc0-f40f-4e6c-8f67-dc3616449264/image.gif" alt="NN Learning"></p>
<h2 id="수식">수식</h2>
<h3 id="feed-forward">Feed Forward</h3>
<p>Input $a^{[0]}=\mathbf{x}$과 Activation Function $f$에 대해, 각 Layer ($l=1,\dots,L$)에서
$$
z^{[l]}=\mathbf{W}^{[l]}a^{[l-1]}+b^{[l]}\
a^{[l]}=f^{[l]}\left(z^{[l]}\right)
$$
Output Layer에서는 $a^{[L]}$이 최종 출력이다.</p>
<h3 id="backpropagation">Backpropagation</h3>
<p>Loss Function $L$에 대해,
Output Layer($l=L$) Loss:
$$
\delta^{[L]}=\frac{\partial L}{\partial z^{[L]}}=a^{[L]}-y
$$
Hidden Layer ($l=L-1,\dots,1$) Loss:
$$
\delta^{[l]}=\left(\left(\mathbf{W}^{[l+1]}\right)^T\delta^{[l+1]}\right)\odot f&#39;^{[l]}\left(z^{[l]}\right)
$$
where $\odot$: 원소별 곱
Gradients:
$$
\frac{\partial L}{\partial \mathbf{W}^{[l]}}=\delta^{[l]}\left(a^{[l-1]}\right)^T\
\frac{\partial L}{\partial b^{[l]}}=\delta^{[l]}
$$</p>
<h3 id="update">Update</h3>
<p>$$
\mathbf{W}^{[l]}\leftarrow\mathbf{W}^{[l]}-\eta\frac{\partial L}{\partial \mathbf{W}^{[l]}}\
b^{[l]}\leftarrow b^{[l]}-\eta\frac{\partial L}{\partial b^{[l]}}
$$</p>
<h2 id="예시">예시</h2>
<p><img src="https://velog.velcdn.com/images/linear_/post/c3b42db0-8a06-47f1-9c5b-3ad3f6ab09ee/image.png" alt="Feed Forward">
<img src="https://velog.velcdn.com/images/linear_/post/784407c7-3f54-486c-aa9d-fd90d65dbff0/image.png" alt="Backpropagation"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ML] Regularization]]></title>
            <link>https://velog.io/@linear_/ML-Regularization</link>
            <guid>https://velog.io/@linear_/ML-Regularization</guid>
            <pubDate>Fri, 19 Sep 2025 11:03:53 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>모델이 과도하게 복잡해져서 일반화 성능이 떨어지는 문제를 완화하기 위하여, 모델의 파라미터나 구조에 인위적으로 패널티를 부과하는 기법이다. Overfitting을 방지하고, 모델 복잡도를 제어한다.</p>
<h1 id="lasso-least-absolute-shrinkage-and-selection-operator">LASSO (Least Absolute Shrinkage and Selection Operator)</h1>
<p>L1 Regularization을 적용한다. 낮은 회귀 계수는 강제로 0으로 조절하여 변수 선택 효과가 발생한다. <code>alpha</code> 값을 조정하여 사용자 정의 가능하며, 일반적으로 작은 값 (ex. 0.1)에서 시작하여 조정한다. 모델이 단순해지면, 중요한 변수만 남는다.
$$
\lVert\mathbf{w}\rVert^1=\sum_{i=1}^k\lvert w_i \rvert
$$</p>
<h1 id="ridge">Ridge</h1>
<p>L2 Regularization을 적용한다. 회귀 계수의 크기를 줄이고, 낮은 회귀 계수는 0에 가깝게 처리하여 다중공선성 문제를 완화한다. 변수 선별/제거 없이 처리된 변수를 모두 반영한다.
$$
\lVert\mathbf{w}\rVert^2=\sum_{i=1}^k w_i^2
$$</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ML] Linear Regression]]></title>
            <link>https://velog.io/@linear_/ML-Linear-Regression</link>
            <guid>https://velog.io/@linear_/ML-Linear-Regression</guid>
            <pubDate>Fri, 19 Sep 2025 10:59:17 GMT</pubDate>
            <description><![CDATA[<h1 id="linear-regression">Linear Regression</h1>
<p>Input Feature와 연속형 출력값 사이의 관계를 설명하는 선형 방정식을 찾는 알고리즘
Input Feature Vector $\mathbf{x}=\left(x_1,x_2,\dots,x_n\right)$에 대응되는 결과값 $y$가 있을 때, $y$를 가능한 한 잘 맞출 수 있는 선형 방정식을 찾는다.
$$
\hat{y}=w_1x_1+w_2x_2+\cdots+w_nx_n=\mathbf{w}^T\mathbf{x}
$$
$\hat{y}$이 $y$와 비슷할수록 좋은 선형 회귀 모델이다.
주어진 데이터를 선형 관계로 완벽하게 표현하는 것이 어렵기 때문에, 일반적으로 bias를 식에 추가해 사용한다.
$$
\hat{y}=w_0+w_1x_1+w_2x_2+\cdots+w_nx_n=\mathbf{w}^T\mathbf{x}
$$</p>
<h2 id="cost-function">Cost Function</h2>
<p>$$
J\left(\mathbf{w}\right)=\text{MSE}\left(\mathbf{w}\right)=\frac{1}{n} \sum_{i=1
}^n {\frac{1}{2}\left(\hat{y}^{(i)}-y^{(i)}\right)^2}
$$</p>
<h2 id="학습">학습</h2>
<p>Gradient Descent를 통해 $J\left(\mathbf{w}\right)$의 값이 최소가 되도록 하는 $\mathbf{w}$의 최적치를 구한다.
$$
\begin{aligned}
J\left(\mathbf{w}\right)=\frac{1}{n} \sum_{i=1
}^n {\frac{1}{2}\left(\hat{y}^{(i)}-y^{(i)}\right)^2} &amp;\
&amp;\Rightarrow \Delta \mathbf{w}=\frac{1}{n} \sum_{i=1
}^n {\left(-y^{(i)}+\hat{y}^{(i)}\right)\mathbf{x}^{(i)}} \
&amp;\Rightarrow \mathbf{w} := \mathbf{w}+\eta \frac{1}{n} \sum_{i=1
}^n {\left(y^{(i)}-\hat{y}^{(i)}\right)\mathbf{x}^{(i)}}
\end{aligned}
$$</p>
<h2 id="예측">예측</h2>
<p>학습을 통해 가중치 벡터 $\mathbf{w}$를 찾게 되면, 새로운 입력 벡터 $\mathbf{x}&#39;$에 대한 결과 예측값 $y&#39;$은 다음과 같다.
$$
y&#39;=\mathbf{w}^T\mathbf{x}&#39;
$$</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ML] Logistic Regression]]></title>
            <link>https://velog.io/@linear_/ML-Logistic-Regression</link>
            <guid>https://velog.io/@linear_/ML-Logistic-Regression</guid>
            <pubDate>Fri, 19 Sep 2025 10:48:46 GMT</pubDate>
            <description><![CDATA[<h1 id="logistic-function-sigmoid-function">Logistic Function (=Sigmoid Function)</h1>
<p>입력값을 0과 1 사이의 확률로 변환한다.
$$
y(z)=\frac{1}{1+\exp(-z)}
$$</p>
<h1 id="logistic-regression">Logistic Regression</h1>
<h2 id="feature-변환">Feature 변환</h2>
<p>Logistic Regression에서 Logistic Function의 입력인 $z$는 feature $\mathbf{x}$의 가중합이다.
$$
\mathbf{z}=w_1x_1+w_2x_2+\cdots+w_nx_n=\mathbf{w}^T\mathbf{x}
$$
주어진 데이터를 선형 변환만으로 완벽하게 목적하는 공간으로 매핑하는 것은 어렵기 때문에, 일반적으로 bias를 식에 추가해 사용한다.
$$
\mathbf{z}=w_0+w_1x_1+w_2x_2+\cdots+w_nx_n=\mathbf{w}^T\mathbf{x}
$$</p>
<h2 id="분류">분류</h2>
<p>Logistic Function을 통해 Feature Vector $\mathbf{x}$의 클래스를 판별한다.
$$
\hat{y}=\frac{1}{1+\exp\left(-\mathbf{w}^T\mathbf{x}\right)}
$$
Logistic Function의 출력이 0과 1 사이의 값을 가지기 때문에 $\hat{y}$를 $P\left(y=1|\mathbf{x}\right)$로 생각할 수 있다.</p>
<h2 id="학습">학습</h2>
<p>Logistic Regression에서 학습은 Feature를 잘 변환해줄 수 있는 가중치 $\mathbf{w}$를 찾는 것이다. 좋은 가중치일수록 음성 클래스의 입력은 0에, 양성 클래스의 입력은 1에 가깝게 예측한다.</p>
<h1 id="multinomial-logistic-regression-softmax-regression">Multinomial Logistic Regression (Softmax Regression)</h1>
<p>클래스가 3개 이상일 때 사용하는 Logistic Regression의 확장형이다.
Binary Classification에서 Logistic Function을 사용하는 것과 달리, Softmax Function을 사용한다.
$K$개의 클래스가 존재할 때 Feature Vector $\mathbf{x}$에 대해 클래스 $k$일 확률은 아래와 같다.
$$
\hat{y}<em>k=\frac{\exp{\left(\mathbf{w}_k^T\mathbf{x}\right)}}{\sum</em>{j=1}^K{\exp{\left(\mathbf{w}_j^T\mathbf{x}\right)}}}
$$</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ML] Decision Tree]]></title>
            <link>https://velog.io/@linear_/ML-Decision-Tree</link>
            <guid>https://velog.io/@linear_/ML-Decision-Tree</guid>
            <pubDate>Fri, 19 Sep 2025 10:42:45 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>Decision Tree Classifier는 트리 모양의 순차형 다이어그램을 통해 주어진 데이터를 분류한다. 데이터를 기반으로 조건을 자동 생성 및 판단하고, 이를 통해 최종 결과에 이른다. 대표적인 트리 생성 알고리즘에는 CART가 있다.</p>
<h1 id="트리-분할">트리 분할</h1>
<p>기본적으로 클래스별로 데이터를 잘 분류할 수 있으면 좋은 분할이라 한다. 그러나 하나의 Feature와 그 Feature 값으로 전체 데이터를 클래스 별로 완벽하게 분류할 수 없으므로, 단계적으로 분할 조건을 탐색한다.</p>
<h2 id="트리-분할-측정-척도">트리 분할 측정 척도</h2>
<p>각 Feature와 그 값들이 가장 잘 조합된 결과를 선택하기 위해 분할이 얼마나 잘 이루어졌는지 측정한다.</p>
<h3 id="지니-계수-gini-impurity">지니 계수 (Gini Impurity)</h3>
<p>$$
\text{Gini}=1-\sum_{k=1}^K{f_k^2}
$$
where $f_k$: $k$번째 클래스에 해당하는 데이터의 비율</p>
<p>데이터가 클래스별로 고르게 분포되어 있는지, 아니면 특정 클래스에 편중되어 분포되어 있는지 나타낸다. 특정 클래스에 치우칠수록 값이 작고, 클래스별 분포가 균등할수록 값이 크다.
분할한 결과로 만들어진 각 자식 노드에 대해서 지니 계수를 구하고 가중 평균하여 분할 결과를 측정한다.
$$
\sum_{c=1}^C\frac{n_c}{n_p}{\text{Gini}_c}
$$
where $C$: 자식 노드 집합, $\text{Gini}_c$: 자식 노드 $c$에 있는 데이터의 지니 계수, $n_p$: 부모 노드 $p$에 속하는 데이터 수, $n_c$: 자식 노드 $c$에 속하는 데이터 수</p>
<p>해당 값이 작을수록 분할을 잘 수행했음을 나타낸다.</p>
<h3 id="정보-이득">정보 이득</h3>
<p>트리 분할 후 얼마나 순도가 더 좋아졌는지 평가한다. 즉, 트리 분할을 통해 불확실성이 얼마나 낮아졌는지 측정한다.</p>
<blockquote>
<p>✏️ 불확실성이 높다는 것은 클래스 별로 고르게 분포되어 있음을 의미한다. 모든 클래스에 대해 동일한 비율로 데이터가 존재하면, 임의로 데이터를 뽑았을 때 해당 데이터가 어떤 클래스에 속할지 불확실하다.</p>
</blockquote>
<p>정보 이득은 트리 분할 전후의 엔트로피 값 차이로 계산한다.
$$
\text{Entropy}=-\sum_{k=1}^{K}{f_k\cdot \log_2{f_k}}
$$
where $f_k$: $k$번째 클래스에 해당하는 데이터의 비율</p>
<p>$$
\text{Information Gain}=\text{Entropy}(p)-\sum_{c}^C\frac{n_c}{n_p}{\text{Entropy}(c)}
$$</p>
<p>정보 이득이 클수록, 즉 트리 분할 전후의 엔트로피의 차가 클수록 분할을 잘 수행했음을 나타낸다.</p>
<blockquote>
<p>✏️ <strong>지니 계수&amp;엔트로피 계산</strong>
<img src="https://velog.velcdn.com/images/linear_/post/bfeddbc4-184c-4938-86e6-0e4ce3b3c4fb/image.png" alt="Gini/Entropy Calculation">
$\text{Gini}=1-\left(\frac{5}{14}\right)^2-\left(\frac{9}{14}\right)^2 \simeq 0.46$<br>$\text{Entropy}=-\left(\frac{5}{14}\log_2\frac{5}{14}+\frac{9}{14}\log_2\frac{9}{14}\right)\simeq0.94$</p>
</blockquote>
<h1 id="decision-tree-cart-알고리즘-기반">Decision Tree (CART 알고리즘 기반)</h1>
<p>학습 데이터를 사용하여 각 노드를 왼쪽 자식 노드와 오른쪽 자식 노드로 분할해 가며 트리를 생성한다.</p>
<p>각 노드에서 분할을 위한 Feature와 해당 Feature의 값 조합을 Greedy 방식으로 탐색하며, 분할 후 만들어질 노드의 샘플 수가 일정 수준 이하이거나 트리의 깊이가 일정 수준 이상일 때 분할을 정지한다. 오분류율이 높거나 부적절한 추론 규칙을 가지고 있는 가지는 제거 (Pruning)한다. 분할되지 않은 마지막 노드에 대해서는 각 노드에 있는 데이터의 클래스 중 가장 다수의 클래스를 해당 노드의 클래스 라벨로 할당한다.</p>
<p>고객 신용 점수, 고객 세분화, 캠페인 반응 분석, 광고 효과 측정 등에 활용한다.</p>
<h2 id="장단점">장단점</h2>
<h3 id="장점">장점</h3>
<h4 id="직관적이고-이해하기-쉽다">직관적이고 이해하기 쉽다.</h4>
<p>규칙 기반으로 분류/예측을 수행하고 결과 해석이 간단하다.
모델 시각화가 가능하고, 비전문가도 이해하기 쉽다.</p>
<h4 id="데이터-전처리-부담이-적다">데이터 전처리 부담이 적다.</h4>
<p>범주형 데이터와 연속형 데이터를 모두 처리할 수 있다.
표준화나 정규화 등의 작업이 필요하지 않다.
이상치, 결측치에 덜 민감하다.</p>
<h4 id="변수-중요도를-파악할-수-있다">변수 중요도를 파악할 수 있다.</h4>
<p>분할 기준으로 사용된 Feature를 기반으로 데이터에서 가장 중요한 변수를 쉽게 파악할 수 있다.</p>
<h4 id="작은-데이터셋에-적합하다">작은 데이터셋에 적합하다.</h4>
<p>비교적 작은 데이터셋에서도 잘 동작하며, 빠르게 학습이 가능하다.</p>
<h3 id="단점">단점</h3>
<h4 id="과적합-가능성">과적합 가능성</h4>
<p>나무가 깊게 성장할수록 학습 데이터에 과적합될 가능성이 높다. 이를 방지하기 위해 가지 치기, 최대 깊이 제한이 필요하다.</p>
<h4 id="불안정성">불안정성</h4>
<p>데이터에 민감하여 작은 변화를 반영한 별도 트리를 생성할 수 있다. 이를 극복하기 위해 앙상블 기법이 자주 사용된다.</p>
<h4 id="모델의-일반화-성능이-낮을-수-있다">모델의 일반화 성능이 낮을 수 있다.</h4>
<p>나무의 깊이에 따라 일반화 성능이 낮아질 수 있다.
단일 트리는 복잡한 데이터 특징을 반영하는 데 한계가 있다.</p>
<h4 id="데이터셋-영향">데이터셋 영향</h4>
<p>큰 데이터셋에서는 분할 기준을 찾는 과정에서 계산량이 많아질 수 있다.
클래스 불균형 데이터에서는 잘못된 분류 결과를 계산할 가능성이 있다.</p>
<h1 id="random-forest">Random Forest</h1>
<p><img src="https://velog.velcdn.com/images/linear_/post/7d2eeccd-f947-4d64-b445-00c5e17a513a/image.png" width=350px></p>

<p>여러 개의 Decision Tree 모델을 조합하여 사용하는 Bagging 방식이다.
원본 데이터에서 무작위로 데이터를 추출하여 여러 개의 학습 데이터셋을 구축하고, 각 데이터셋에 대해 모델을 학습시켜 여러 개의 학습 모델을 생성한다. 이후 새로운 데이터가 들어오면 개별 학습 모델이 판단한 결과를 통합하여 다수 모델이 선택한 결과를 최종 결과로 선택한다.</p>
<blockquote>
<p>✏️ 모든 Feature를 똑같이 모두 사용하면, Random Forest에서 사용하는 개별 모델이 비슷해지기 쉬워 큰 차이를 만들기 어렵다. 따라서 서로 다른 Feature Set을 임의로 구성해 개별 모델을 학습한다.</p>
</blockquote>
<h1 id="decision-tree-기반-boosting">Decision Tree 기반 Boosting</h1>
<p>같은 가중치를 가지는 여러 개의 성능이 낮은 모델을 생성하고, 오분류 데이터를 보완하는 방향으로 반복 학습하여 점진적으로 오차를 최소화하는 모델을 생성한다.</p>
<h2 id="adaboost-adaptive-boosting">AdaBoost (Adaptive Boosting)</h2>
<p>정확도가 높지 않은 약한 학습 모델을 연속적으로 이어붙여서 결과를 점진적으로 개선한다. 한 모델의 예측 결과를 다음 모델 학습에 반영하고, 앞에서 잘못 맞춘 데이터 샘플에 더 가중치를 주어서 다음 모델에서 더 집중적으로 학습할 수 있도록 한다.</p>
<h2 id="xgboost-extreme-gradient-boosting">XGBoost (Extreme Gradient Boosting)</h2>
<p>Depth-Wise 방식을 기반으로 순차적으로 이전 트리보다 더 나은 트리를 만들어내는 알고리즘으로, 예측 성능이 좋은 편이다. 변수 종류가 많고 데이터가 클수록 상대적으로 뛰어난 성능을 보인다. 복잡한 모델인 만큼 모델 해석에 어려움이 있다.</p>
<h2 id="lightgbm-light-gradient-boosting-method">LightGBM (Light Gradient Boosting Method)</h2>
<p>Leaf-Wise 방식을 채택하여 트리를 분할한다. XGBoost보다 빠르고 높은 정확도를 보여주는 경우가 많다. 변수 종류가 많고 데이터가 클수록 뛰어난 성능을 보인다.</p>
<p><img src="https://velog.velcdn.com/images/linear_/post/d685b068-10cd-4979-beba-6d4259ed94ab/image.png" alt="Boosting"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ML] SVM]]></title>
            <link>https://velog.io/@linear_/ML-SVM</link>
            <guid>https://velog.io/@linear_/ML-SVM</guid>
            <pubDate>Fri, 19 Sep 2025 10:36:32 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>SVM (Support Vector Machine)은 클래스별 데이터를 가장 잘 구분하는 최적의 Hyperplane을 찾는 알고리즘이다. 최적의 하이퍼플레인은 하이퍼플레인에 의해 양분된 두 공간 내에 있는 가장 가까운 데이터 포인트 간의 거리 (Margin)이 최대가 되도록다.</p>
<blockquote>
<p>✏️ <strong>Hyperplane (하이퍼플레인)?</strong>
데이터가 분포되어 있는 $n$차원 feature space를 두 공간으로 나누는 $n-1$차원의 평면</p>
</blockquote>
<blockquote>
<p>✏️ <strong>Support Vector (서포트 벡터)?</strong>
하이퍼플레인에 의해 양분된 두 공간 내에 있는 가장 가까운 데이터 포인트</p>
</blockquote>
<h1 id="동작-방식">동작 방식</h1>
<p>하이퍼플레인도 하나의 부분 공간이므로, 하이퍼플레인은 같이 정의할 수 있다.
$$
\mathbf{w}\cdot\mathbf{x}+b=0
$$
최적의 하이퍼플레인은 아래 조건을 만족해야 한다.</p>
<ul>
<li>2개 클래스 중 한 쪽에 속한 모든 데이터 $\mathbf{x}$에 대해 $\mathbf{w}\cdot\mathbf{x}+b&gt;0$</li>
<li>2개 클래스 중 다른 쪽에 속한 모든 데이터 $\mathbf{x}$에 대해 $\mathbf{w}\cdot\mathbf{x}+b&lt;0$</li>
<li>서포트 벡터 사이의 거리인 Margin이 최대여야 한다.</li>
</ul>
<h2 id="decisionpositivenegative-hyperplane">Decision/Positive/Negative Hyperplane</h2>
<p align><img src="https://velog.velcdn.com/images/linear_/post/15bb186a-36f8-4ae6-ac3d-e1eaf5a699f3/image.png" width=350px></p>

<h3 id="decision-hyperplane">Decision Hyperplane</h3>
<p>두 공간을 양분하는 하이퍼플레인
$$
\mathbf{w}\cdot\mathbf{x}+b=0
$$</p>
<h3 id="positive-hyperplane">Positive Hyperplane</h3>
<p>Desicion Hyperplane 위쪽에 평행한 하이퍼플레인
$$
\mathbf{w}\cdot\mathbf{x}^{\left(p \right)}+b=1
$$</p>
<h3 id="negative-hyperplane">Negative Hyperplane</h3>
<p>Desicion Hyperplane 아래쪽에 평행한 하이퍼플레인
$$
\mathbf{w}\cdot\mathbf{x}^{\left(n \right)}+b=-1
$$</p>
<p>Positive/Negative Hyperplane은 위치한 공간에서 Desicion Hyperplane과 가장 가까운 데이터를 포함하며, 해당 데이터 포인트가 서포트 벡터이다.</p>
<h2 id="데이터-양분">데이터 양분</h2>
<ul>
<li>2개 클래스 중 한 쪽에 속한 모든 데이터 $\mathbf{x}$에 대해 $\mathbf{w}\cdot\mathbf{x}+b&gt;0$</li>
<li>2개 클래스 중 다른 쪽에 속한 모든 데이터 $\mathbf{x}$에 대해 $\mathbf{w}\cdot\mathbf{x}+b&lt;0$</li>
</ul>
<p>위 조건을 양성/음성 하이퍼플레인에 대한 표현으로 바꾸면,
$$
\begin{aligned}
&amp;\mathbf{w}\cdot\mathbf{x}^{\left(i \right)}+b \geq 1 \text{ if } y^{\left(i \right)}=1 \
&amp;\mathbf{w}\cdot\mathbf{x}^{\left(i \right)}+b \leq -1 \text{ if } y^{\left(i \right)}=-1
\end{aligned}
$$
where $\left(\mathbf{x}^{\left(i \right)}, y^{\left(i \right)}\right)$: $i$번째 데이터의 feature와 class label</p>
<p>위 식을 하나로 정리하면,
$$
y^{\left(i \right)}\left(\mathbf{w}\cdot\mathbf{x}^{\left(i \right)}+b\right) \geq 1
$$</p>
<h2 id="margin의-표현">Margin의 표현</h2>
<p>양성 하이퍼플레인의 점 $\mathbf{x}^{\left(p \right)}$에서 직선 $\mathbf{w}\cdot\mathbf{x}+b=0$ 사이의 거리:
$$
d^{\left(p \right)}=\frac{\lvert \mathbf{w}\cdot\mathbf{x}^{\left(p \right)}+b \rvert}{\lVert\mathbf{w}\rVert}=\frac{1}{\lVert\mathbf{w}\rVert}
$$</p>
<p>음성 하이퍼플레인의 점 $\mathbf{x}^{\left(n \right)}$에서 직선 $\mathbf{w}\cdot\mathbf{x}+b=0$ 사이의 거리:
$$
d^{\left(n \right)}=\frac{\lvert \mathbf{w}\cdot\mathbf{x}^{\left(n \right)}+b \rvert}{\lVert\mathbf{w}\rVert}=\frac{1}{\lVert\mathbf{w}\rVert}
$$</p>
<p>따라서 Margin은 아래와 같이 표현할 수 있다.
$$
d^{\left(p \right)}+d^{\left(n \right)}=\frac{2}{\lVert\mathbf{w}\rVert}
$$</p>
<p>결과적으로 Margin이 최대가 되게 하기 위해서는 $\lVert \mathbf{w} \rVert$를 최소화해야 한다.</p>
<h2 id="최적의-하이퍼플레인">최적의 하이퍼플레인</h2>
<p>결론적으로, 아래 조건을 만족하는 하이퍼플레인을 찾는 것이 SVM의 학습 과정이다.</p>
<h4 id="조건1-데이터-양분">조건1: 데이터 양분</h4>
<p>모든 학습 데이터 $\left(\mathbf{x}^{\left(i \right)}, y^{\left(i \right)}\right)$에 대하여
$$
y^{\left(i \right)}\left(\mathbf{w}\cdot\mathbf{x}^{\left(i \right)}+b\right) \geq 1
$$</p>
<h4 id="조건2-최대-margin">조건2: 최대 Margin</h4>
<p>$\lVert \mathbf{w} \rVert$ 최소화</p>
<blockquote>
<p>✏️ <strong>Soft-margin SVM?</strong>
현실적으로 모든 학습 데이터를 정확하게 양분하는 선형식을 찾기는 어렵다. 따라서 학습 단계에서 margin 안에 일정 수준의 noise/outlier를 포함하도록 허용하는 완화된 기준을 적용한다. 이에 따라 현재 선형식으로 학습 데이터를 분류하였을 때 올바르게 양분되지 못한 경우를 수치화한 Hinge Loss를 정의하고, 이를 최소가 되게 하는 조건을 추가한다.</p>
</blockquote>
<h1 id="multi-class-svm">Multi-Class SVM</h1>
<h2 id="ovr-one-vs-rest">OvR (One vs Rest)</h2>
<p>$K$-Class 문제에 대해 $K$개의 서로 다른 Binary SVM Classifier를 생성한다. $k$번째 분류기는 $k$번째 클래스를 양성 케이스로 처리하고, 나머지 $k-1$개의 클래스는 음성 케이스로 처리한다. 이를 학습하여 하이퍼플레인 $\mathbf{w}_k\cdot\mathbf{x}+b_k=0$를 찾는다. 학습 후 $\mathbf{x}&#39;$의 클래스 $y&#39;$은 Binary Classifier $\mathbf{w}_k\cdot\mathbf{x}+b_k$의 값이 가장 큰 값을 가지는 클래스 $k$로 결정한다.</p>
<h2 id="ovo-one-vs-one">OvO (One vs One)</h2>
<p>클래스의 각 쌍을 구분하는 Binary SVM Classifier를 생성한다. $K$개의 클래스가 있으면, $\frac{K\left(K-1\right)}{2}$개 분류기를 생성한다. 학습 후 새로운 데이터 $\mathbf{x}&#39;$를 분류할 때는 모든 Binary Classifier에 데이터를 넣고 예측을 수행한 뒤, 가장 많이 예측된 클래스를 $\mathbf{x}&#39;$의 최종 예측 클래스 $\mathbf{y}&#39;$으로 결정한다.</p>
<h1 id="kernel">Kernel</h1>
<p>기본적으로 SVM은 선형식으로 표현되는 하이퍼플레인에 기반하기 때문에 비선형 문제에 적합하지 않다. 그러나 선형으로 양분할 수 있도록 Feature Space를 변환한 뒤에 SVM을 적용하는 것이 가능하다.</p>
<h2 id="대표-커널-함수">대표 커널 함수</h2>
<h3 id="polynomial-kernel">Polynomial Kernel</h3>
<p>$$
K\left(\mathbf{x},\mathbf{z}\right)=\left(\mathbf{x}\cdot\mathbf{z}+\mathbf{1}\right)^p
$$</p>
<h3 id="rbf-radial-basis-function-kernel">RBF (Radial Basis Function) Kernel</h3>
<p>$$
K\left(\mathbf{x},\mathbf{z}\right)=\exp{\left(-\frac{\lVert \mathbf{x}-\mathbf{z} \rVert^2}{2\sigma^2}\right)}
$$</p>
<h3 id="hyperbolic-tangent-kernel">Hyperbolic Tangent Kernel</h3>
<p>$$
K\left(\mathbf{x},\mathbf{z}\right)=\tanh\left(\alpha\mathbf{x}\cdot\mathbf{z}+\beta\right)
$$</p>
<h1 id="하이퍼파라미터">하이퍼파라미터</h1>
<h2 id="gamma"><code>gamma</code></h2>
<p>두 샘플 사이의 거리에 얼마나 민감하게 반응할지 결정한다. 
값이 커질수록 거리 변화에 민감하게 반응하고, Overfitting 가능성이 높아진다.</p>
<p><img src="https://velog.velcdn.com/images/linear_/post/0aff2a3a-6696-415c-b52c-691c80a26790/image.webp" alt="Hyperparameter Tuning"></p>
<h2 id="c"><code>C</code></h2>
<p>Margin과 Hinge Loss 중 어떤 것을 더 중요하게 볼 것인지 결정한다.
값이 크면 Hinge Loss를 더 고려하며, 학습 데이터에 Overfitting될 수 있다. 값이 작으면 Margin을 더 고려하며, Underfitting 가능성이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ML] Naïve Bayes]]></title>
            <link>https://velog.io/@linear_/ML-Nave-Bayes</link>
            <guid>https://velog.io/@linear_/ML-Nave-Bayes</guid>
            <pubDate>Fri, 19 Sep 2025 10:32:33 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>Naïve Bayes는 $n$개의 특성을 지닌 샘플 데이터 $\mathbf{x}$가 주어졌을 때, 이 샘플 데이터가 $K$개의 클래스 $y_1, y_2, \dots y_k$ 중 하나에 속할 확률을 결정한다. 즉, 각 클래스에 대해
$$
P\left(y_k|\mathbf{x}\right)=P\left(y_k|x_1,x_2,\dots,x_n\right)
$$
을 계산하고, 위 확률값이 최대인 클래스를 샘플의 최종 클래스로 할당한다.
$$
\begin{aligned}
\argmax\limits_{y_k} P\left(y_k|\mathbf{x}\right)&amp;=\argmax\limits_{y_k} \frac{P\left(\mathbf{x}|y_k\right)P\left(y_k\right)}{P\left(\mathbf{x}\right)} \
&amp;\text{where } P\left(\mathbf{x}|y_k\right)=P\left(x_1|y_k\right)\times P\left(x_2|y_k\right)\times \cdots \times P\left(x_n|y_k\right)
\end{aligned}
$$</p>
<h1 id="예시-spamham-이메일-예측">예시: Spam/Ham 이메일 예측</h1>
<h2 id="클래스별-확률-계산">클래스별 확률 계산</h2>
<p>스팸 메일을 S (Spam), 햄 메일을 NS (Not Spam)로 정의하고, 출현한 단어를 $x_1, \dots, x_n$이라 하면, 테스트 데이터의 이메일이 각 클래스에 속할 확률은 아래와 같다.
$$
\begin{aligned}
&amp; P\left(S|x_1,x_2,\dots,x_n\right)\
&amp; P\left(NS|x_1,x_2,\dots,x_n\right)
\end{aligned}
$$</p>
<p>$$
\begin{aligned}
\argmax\limits_{y \in \left\lbrace S, NS\right\rbrace} &amp;P\left(y|x_1,x_2,\dots,x_n\right)
=\argmax\limits_{y \in \left\lbrace S, NS\right\rbrace}  \frac{P\left(x_1, x_2, \dots, x_n|y\right)P\left(y\right)}{P\left(x_1, x_2, \dots, x_n\right)}\
&amp;\text{where } P\left(x_1,x_2,\dots,x_n|y_k\right)=P\left(x_1|y_k\right)\times P\left(x_2|y_k\right)\times \cdots \times P\left(x_n|y_k\right)
\end{aligned}
$$</p>
<h2 id="smoothing">Smoothing</h2>
<p>만약 $$P\left(x_i|y_k\right)=0$$이라면, 즉 단어 하나가 해당 클래스에서 관측되지 않은 경우, 다른 단어의 Likelihood 값과 무관하게 $P\left(\mathbf{x}|y_k\right)$가 0이 된다. 따라서 Smoothing 기법을 적용하여 이를 방지한다.</p>
<h3 id="laplace-smoothing">Laplace Smoothing</h3>
<p>모든 클래스에서 단어들이 한 번씩 출현했다고 가정한다. 단어의 종류가 $n$개일 때, 각 단어가 해당 클래스에서 한 번씩은 등장했다는 가정에 따라, 단어 당 한 번씩 분모에 단어의 개수 $n$을 더해준다. </p>
<blockquote>
<p>✏️ 분모에도 1을 더하거나, 분모는 모든 단어에 대해 동일하므로 아무것도 더하지 않는 방법도 가능하다.</p>
</blockquote>
<h2 id="log-사용">Log() 사용</h2>
<p>$$
P\left(\mathbf{x}|y_k\right)=P\left(x_1|y_k\right)\times P\left(x_2|y_k\right)\times \cdots \times P\left(x_n|y_k\right)
$$
단어들이 매우 많은 경우, 0~1의 값을 가지는 Likelihood 식을 곱하다 보면 매우 작은 값을 가지게 된다. 일정 수준 이상 작아지면 Underflow되어 모두 같은 값으로 치환되므로, log를 취해 덧셈 연산으로 바꾸어 이를 방지한다.
$$
P\left(\mathbf{x}|y_k\right)=\exp{\left(\log{\left(P\left(x_1|y_k\right)\right)}+ \log{\left(P\left(x_2|y_k\right)\right)}+\cdots+ \log{\left(P\left(x_n|y_k\right)\right)}\right)}
$$</p>
<p>$$
\begin{aligned}
\argmax\limits_{y_k} P\left(y_k|\mathbf{x}\right)&amp;=\argmax\limits_{y_k} \frac{P\left(\mathbf{x}|y_k\right)P\left(y_k\right)}{P\left(\mathbf{x}\right)} \
&amp; =\argmax\limits_{y_k}\left(\log P\left(y_k\right)+\log P\left(\mathbf{x}|y_k\right)\right) \
&amp; =\argmax\limits_{y_k}\left(\log P\left(y_k\right)+\sum_i\log P\left(x_i|y_k\right)\right)
\end{aligned}
$$</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] Docker 명령어 실습]]></title>
            <link>https://velog.io/@linear_/Docker-Docker-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@linear_/Docker-Docker-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Mon, 15 Sep 2025 09:49:14 GMT</pubDate>
            <description><![CDATA[<h1 id="이미지-조회">이미지 조회</h1>
<pre><code class="language-shell">docker search [imagename]</code></pre>
<p><img src="https://velog.velcdn.com/images/linear_/post/c5e32d9a-751d-4c0f-88a3-1b7312a10021/image.png" alt="docker search"></p>
<p>레지스트리 (Docker Hub)에서 이미지 (ubuntu)를 검색한다.</p>
<h1 id="이미지-다운로드">이미지 다운로드</h1>
<pre><code class="language-shell">docker pull [imagename]</code></pre>
<p><img src="https://velog.velcdn.com/images/linear_/post/8ae94e8a-22e2-4b7c-815d-d67a1813241a/image.png" alt="docker pull"></p>
<p>레지스트리 (Docker Hub)에서 이미지 (ubuntu)를 다운로드한다. 태그를 지정하지 않으면 최신 버전 (latest)를 다운받는다.</p>
<h1 id="이미지-조회-1">이미지 조회</h1>
<pre><code class="language-shell">docker image ls</code></pre>
<p><img src="https://velog.velcdn.com/images/linear_/post/f9311cc8-c24c-46e7-a722-12545018657a/image.png" alt="docker image ls"></p>
<p>다운로드받은 이미지를 조회한다. 방금 다운로드받은 ubuntu 이미지가 존재하는 것을 확인할 수 있다. </p>
<h1 id="컨테이너-실행">컨테이너 실행</h1>
<pre><code class="language-shell">docker run -it --name [containername] [imagename]</code></pre>
<p><img src="https://velog.velcdn.com/images/linear_/post/6cf3485b-d92d-4905-b231-effadbdebbcf/image.png" alt="docker run"></p>
<p>ubuntu 이미지를 컨테이너로 생성한다. 이때 <code>--name</code> 옵션을 사용하여 컨테이너 이름을 &#39;hello&#39;로 지정하였다. <code>-it</code> 옵션을 사용하여 실행된 bash shell에서 명령어를 실행할 수 있다.</p>
<h1 id="컨테이너-접속">컨테이너 접속</h1>
<pre><code class="language-shell">docker ps -a
docker start [containername]
docker ps
docker exec [containername] [command]
docker ps
docker stop [containername]
docker ps</code></pre>
<p><img src="https://velog.velcdn.com/images/linear_/post/8ffb3ef3-7b26-4ad8-bda2-3c44a195f1be/image.png" alt="docker start/exec/stop"></p>
<p><code>docker ps -a</code>를 통해 모든 컨테이너 목록을 출력한다. 현재 컨테이너는 정지된 상태이다.
<code>docker start</code>를 통해 컨테이너를 다시 시작한다. <code>docker ps</code>를 통해 해당 컨테이너가 시작되었음을 확인할 수 있다.
현재 컨테이너는 <code>/bin/bash</code>로 실행된 상태이다. <code>docker exec</code>를 통해 외부에서 컨테이너 안의 명령어를 실행한다. 그 결과 화면에 &quot;Hello World&quot;가 출력된다.
<code>docker stop</code>을 통해 실행된 컨테이너를 정지한다. <code>docker ps</code>를 통해 해당 컨테이너가 정지된 것을 확인할 수 있다.</p>
<h1 id="컨테이너이미지-삭제">컨테이너/이미지 삭제</h1>
<pre><code class="language-shell">docker rm [containername]
docker rmi [imagename]</code></pre>
<p><img src="https://velog.velcdn.com/images/linear_/post/beaead96-94f1-4f27-82b7-e6c2e62c4c35/image.png" alt="docker rm"></p>
<p><code>docker rm</code>, <code>docker rmi</code>를 통해 각각 컨테이너와 이미지를 삭제할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] JWT (JSON Web Token)]]></title>
            <link>https://velog.io/@linear_/SpringBoot-JWT-JSON-Web-Token</link>
            <guid>https://velog.io/@linear_/SpringBoot-JWT-JSON-Web-Token</guid>
            <pubDate>Thu, 11 Sep 2025 08:56:54 GMT</pubDate>
            <description><![CDATA[<h1 id="jwt-json-web-token">JWT (JSON Web Token)</h1>
<p>웹 어플리케이션에서 인증 (Authentication) 및 정보 전달을 위해 널리 사용되는 토큰 기반 인증 방식이다. 서버와 클라이언트 간에 인증 정보를 Base64Url 방식으로 인코딩하여 전달한다.</p>
<h2 id="구조">구조</h2>
<pre><code class="language-text">&lt;base64url(header)&gt;.&lt;base64url(payload)&gt;.&lt;base64url(signature)&gt;</code></pre>
<h3 id="header">Header</h3>
<p>토큰의 타입 (JWT)과 서명 알고리즘 (HS256, RS256, etc.)을 명시한다.</p>
<h4 id="필드">필드</h4>
<ul>
<li><code>alg</code>: Algorithm. 서명에 사용된 알고리즘</li>
<li><code>typ</code>: Type. 토큰의 타입 (일반적으로 &quot;JWT&quot;로 고정)</li>
<li><code>kid</code> (Option): Key ID. 여러 키 중 어떤 키로 서명되었는지 식별할 때 사용<h3 id="payload">Payload</h3>
사용자 ID, 권한, 만료시간 등 사용자 정보 (Claims)가 들어가는 부분이다.<h4 id="필드-1">필드</h4>
</li>
<li><code>iss</code>: Issuer. 토큰 발급자</li>
<li><code>sub</code>: Subject. 토큰 주제</li>
<li><code>aud</code>: Audience. 토큰 대상자</li>
<li><code>exp</code>: Expiration. 만료 시간</li>
<li><code>nbf</code>: Not Before. 해당 시간 이전에는 유효하지 않음</li>
<li><code>iat</code>: Issued At. 토큰 발급 시간</li>
<li><code>jti</code>: JWT ID. 토큰 고유 식별자 (재사용 방지에 유용)</li>
</ul>
<blockquote>
<p>✏️ 사용자 정의 필드는 임의로 추가할 수 있다.</p>
</blockquote>
<blockquote>
<p>✏️ JWT Payload는 인코딩일 뿐 암호화되지 않으므로, 민감한 정보는 포함하지 않는다.</p>
</blockquote>
<h3 id="signature">Signature</h3>
<p>Header와 Payload를 인코딩한 후 비밀 키로 서명한 값으로, 무결성 검증에 사용된다.</p>
<h1 id="spring-security">Spring Security</h1>
<p>Spring 기반 어플리케이션에서 인증/권한을 관리하는 프레임워크이다.</p>
<h2 id="설정-범위">설정 범위</h2>
<ul>
<li>인증 (Authentication): 사용자가 누구인지 확인한다.</li>
<li>인가 (Authorization): 특정 리소스에 접근 가능한지 판단한다.</li>
<li>세션/토큰 관리: JWT, 세션 쿠키 등 인증 상태 유지 방식을 설정한다.</li>
<li>CORS 정책: 다른 도메인에서의 요청 허용 여부를 설정한다.</li>
<li>CSRF 보호: 주로 웹 브라우저 환경에서 사용된다.</li>
<li>예외 처리: 인증/인가 실패 시 응답 형식을 지정한다.</li>
<li>HTTP 요청 보안 설정: <code>http.authorizeHttpRequests()</code> 등으로 경로별 권한을 제어한다.</li>
</ul>
<blockquote>
<p>✏️ <strong>CORS (Cross-Origin Resource Sharing)?</strong>
웹 브라우저는 보안 상의 이유로 다른 출처의 리소스 요청을 기본적으로 차단한다. CORS는 서버가 &quot;내 리소스를 특정 외부 도메인에서 접근해도 된다&quot;고 명시해주는 방식이다. 클라이언트 측의 보안 제약이며, 서버 보안 정책은 아니다.</p>
<pre><code class="language-java">@Configuration
public class WebConfig implements WebMvcConfigurer {
</code></pre>
</blockquote>
<pre><code>@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping(&quot;/**&quot;)     /* 모든 경로 허용 */
            .allowedOrigins(&quot;*&quot;)   /* 모든 도메인 허용 */
            .allowedMethods(&quot;*&quot;)   /* 모든 HTTP 메서드 허용 */
            .allowedHeaders(&quot;*&quot;)   /* 모든 헤더 허용 */
            .allowCredentials(true); /* 인증 정보 허용 */
}</code></pre><p>}</p>
<blockquote>
<p>```</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] JPA Transaction]]></title>
            <link>https://velog.io/@linear_/SpringBoot-JPA-Transaction</link>
            <guid>https://velog.io/@linear_/SpringBoot-JPA-Transaction</guid>
            <pubDate>Thu, 11 Sep 2025 07:01:23 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>JPA의 변경 내용 (저장/수정/삭제)은 트랜잭션이 있을 때만 데이터베이스에 안전하게 반영된다.
JPA에서는 서비스 계층에서 트랜잭션을 시작하며, Spring은 <code>@Transactional</code> 어노테이션을 사용해 트랜잭션을 자동으로 관리한다.</p>
<h1 id="jpa-트랜잭션-commit-시점">JPA 트랜잭션 Commit 시점</h1>
<p>기본적으로 트랜잭션을 시작한 메서드가 성공적으로 종료될 때 Commit된다. Spring 환경에서는 <code>@Transactional</code> 어노테이션이 붙은 메서드가 예외 없이 실행을 마치는 시점에 Commit이 발생한다.</p>
<h1 id="transactional"><code>@Transactional</code></h1>
<p>트랜잭션 관리를 지원하는 선언적 어노테이션이다. 메서드/클래스에 붙이면, 해당 범위 내의 모든 데이터베이스 작업을 트랜잭션 단위로 처리한다. 모든 작업이 성공하면 Commit, 중간에 예외가 발생하면 자동으로 Rollback 처리된다.</p>
<table>
<thead>
<tr>
<th>속성</th>
<th>기본값</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>propagation</code></td>
<td><code>REQUIRED</code></td>
<td>트랜잭션 전파 방식 (<code>REQUIRED</code>, <code>REQUIRES_NEW</code>, etc.)</td>
</tr>
<tr>
<td><code>isolation</code></td>
<td><code>DEFAULT</code></td>
<td>트랜잭션 격리 수준 (<code>READ_COMMITTED</code>, <code>REPEATABLE_READ</code>, etc.)</td>
</tr>
<tr>
<td><code>readOnly</code></td>
<td><code>false</code></td>
<td>읽기 전용 트랜잭션으로 설정 (삽입/수정/삭제 없이 조회만 하는 경우 성능 향상)</td>
</tr>
<tr>
<td><code>timeout</code></td>
<td>-1</td>
<td>트랜잭션 제한 시간 (초), 초과 시 Rollback</td>
</tr>
<tr>
<td><code>rollbackFor</code></td>
<td><code>{}</code></td>
<td>지정한 예외 발생 시에만 Rollback <br>(Default는 <code>RuntimeException</code>, Error 발생 시 Rollback)</td>
</tr>
<tr>
<td><code>noRollbackFor</code></td>
<td><code>{}</code></td>
<td>지정한 예외 발생 시에는 Rollback하지 않음</td>
</tr>
</tbody></table>
<h2 id="설정-기준">설정 기준</h2>
<p>DB 변경이 있는 오퍼레이션을 수행하는 서비스 메서드에는 <code>@Transactional</code>을 사용하는 것을 권장한다. 그러나 남용하는 경우, 트랜잭션 범위가 넓어져 DB lock, 동시성 저하 등 성능 문제가 생길 수 있다.</p>
<h3 id="예시">예시</h3>
<ul>
<li>데이터 변경 (INSERT/UPDATE/DELETE): 반드시 사용한다.</li>
<li>단순 조회 (SELECT): <code>@Transactional(readOnly = true)</code>를 사용하거나 사용하지 않는다.</li>
<li>외부 API/파일 등 장시간 IO 포함: 트랜잭션 범위를 DB 오퍼레이션만 포함하도록 최대한 좁게 유지한다.</li>
</ul>
<h1 id="트랜잭션-전파-propagation">트랜잭션 전파 (Propagation)</h1>
<p>트랜잭션이 이미 존재할 때, 현재 실행 중인 메서드에서 트랜잭션을 어떻게 처리할지 결정한다.</p>
<table>
<thead>
<tr>
<th>옵션명</th>
<th>설명</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>REQUIRED</code> (default)</td>
<td>기존 트랜잭션이 있으면 참여하고, 없으면 새로 생성한다.</td>
<td>대부분의 서비스 로직</td>
</tr>
<tr>
<td><code>REQUIRES_NEW</code></td>
<td>항상 새로운 트랜잭션을 생성하며, 기존 트랜잭션은 일시중단한다.</td>
<td>로그 기록, 감사</td>
</tr>
<tr>
<td><code>SUPPORTS</code></td>
<td>트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행한다.</td>
<td>조회성 로직</td>
</tr>
<tr>
<td><code>MANDATORY</code></td>
<td>반드시 트랜잭션 내에서만 실행하고, 없으면 예외가 발생한다.</td>
<td>보안 로직</td>
</tr>
<tr>
<td><code>NOT_SUPPORTED</code></td>
<td>트랜잭션이 있으면 일시 중단하고, 트랜잭션 없이 실행한다.</td>
<td>외부 시스템 연동, 대용량 데이터 연동</td>
</tr>
<tr>
<td><code>NEVER</code></td>
<td>트랜잭션 없이만 실행하고, 트랜잭션이 있으면 예외가 발생한다.</td>
<td>트랜잭션과 같이 동작하면 안 되는 로직</td>
</tr>
<tr>
<td><code>NESTED</code></td>
<td>기존 트랜잭션이 있으면 중첩 (새 savepoint)하고, <br>없으면 새 트랜잭션을 생성한다.</td>
<td>부분 롤백 지원이 필요한 경우</td>
</tr>
</tbody></table>
<h2 id="트랜잭션-전파-오류-self-invocation">트랜잭션 전파 오류: Self Invocation</h2>
<p>하나의 클래스 내에서 <code>@Transactional</code>이 붙은 다른 메서드를 호출하면, 프록시를 거치지 않고 실제 객체의 내부 메서드를 직접 호출하기 때문에 트랜잭션 전파 설정이 적용되지 않는다.</p>
<pre><code class="language-java">@Service
public class UserService {

    @Transactional  // (propagation = Propagation.REQUIRED) Default
    public void outerMethod() {
        // DB 작업 1
        innerMethod(); // 내부 호출 (프록시 X)
    }

    @Transactional (propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        // DB 작업 2
    }
}</code></pre>
<p><code>outerMethod()</code>가 Transaction A를 시작하고, <code>innerMethod()</code>는 프록시를 거치지 않아 <code>REQUIRES_NEW</code>가 적용되지 않아 Transaction A 안에서 그대로 실행된다. 결국, 두 작업이 하나의 트랜잭션에서 Commit/Rollback된다.
이를 해결하기 위해서, 트랜잭션 단위가 다른 메서드는 별도의 클래스로 분리하여 주입받아 사용한다.</p>
<pre><code class="language-java">@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional  // (propagation = Propagation.REQUIRED)
    public void outerMethod() {
        // DB 작업 1
        innerService.innerMethod();
        // DB 작업 3
    }
}</code></pre>
<pre><code class="language-java">@Service
public class InnerService {
    @Transactional  // (propagation = Propagation.REQUIRED)
    public void innerMethod() {
        // DB 작업 2
        // 만약 여기서 예외가 발생하여 롤백 되면,
        // outerMethod의 &#39;DB 작업 1&#39;까지 모두 롤백 된다.
    }
}</code></pre>
<h2 id="트랜잭션-예외와-rollback-규칙">트랜잭션 예외와 Rollback 규칙</h2>
<p><code>@Transactional</code>은 기본적으로 <code>RuntimeException</code> (Unchecked Exception)과 Error가 발생했을 때만 Rollback한다. 따라서 Checked Exception이 발생하면 기본 정책 상 Rollback이 아니라 Commit이 된다. 또한<code>try-catch</code>로 해당 예외를 삼켜도 정상 리턴으로 간주되므로, Commit되어 데이터 정합성 문제가 발생할 수 있다. 따라서 모든 예외에 대해 Rollback하고 싶다면 <code>rollbackFor</code> 속성을 사용한다.</p>
<h1 id="트랜잭션-동시성-제어-consistency-control">트랜잭션 동시성 제어 (Consistency Control)</h1>
<p>여러 사용자가 동시에 데이터에 접근하고 수정할 때 발생할 수 있는 데이터 불일치 문제를 방지하기 위한 제어로, JPA는 낙관적 Lock과 비관적 Lock을 제공한다.</p>
<h2 id="낙관적-lock">낙관적 Lock</h2>
<p>트랜잭션 충돌이 자주 발생하지 않을 것이라고 가정하는 방식이다. DB 레코드 자체에 lock을 걸지 않고, <code>@Version</code>을 사용하여 데이터의 변경 여부를 확인한다.</p>
<pre><code class="language-java">@Entity
public class Article {
    @Id
    @GeneratedValue
    private Long id;

    private String content;

    @Version
    private Long version;
}</code></pre>
<p>엔터티에 <code>@Version</code> 어노테이션이 붙은 필드 (주로 숫자 타입)을 추가하여, 데이터를 조회할 때 함께 가져온다. 데이터를 수정하고 트랜잭션을 커밋할 때, 현재 데이터베이스의 버전과 조회했던 시점의 버전을 비교한다. 버전이 일치하면 데이터를 수정하고 버전을 1 증가시키고, 일치하지 않으면 (다른 트랜잭션이 먼저 수정한 경우), <code>ObjectOptimisticLockingFailureException</code>을 발생시키고 수정을 실패 처리한다.</p>
<h3 id="장단점">장단점</h3>
<ul>
<li>장점: DB에 직접적인 lock을 걸지 않아 부하가 적고 성능이 좋다.</li>
<li>단점: 충돌이 발생하면 개발자가 직접 예외를 처리하고 재시도 로직 등을 구현해야 한다.<h2 id="비관적-lock">비관적 Lock</h2>
트랜잭션 충돌이 자주 발생할 것이라고 가정한다. 데이터에 접근 시점부터 데이터베이스 레코드에 직접 lock을 걸고, 다른 트랜잭션은 해당 lock이 해제될 때까지 접근할 수 없다.<h3 id="lock-모드">Lock 모드</h3>
</li>
<li><code>PESSIMISTIC_WRITE</code>: 데이터를 수정하기 위해 설정한다. 다른 트랜잭션은 해당 데이터를 읽거나 쓸 수 없다.</li>
<li><code>PESSIMISTIC_READ</code>: 다른 트랜잭션이 해당 데이터를 수정하는 것을 방지한다. DB에 따라 읽기는 허용될 수 있다.<h3 id="장단점-1">장단점</h3>
</li>
<li>장점: 데이터 정합성을 확실하게 보장하여, 충돌이 잦은 환경에서 데이터 무결성을 유지하는 데 효과적이다.</li>
<li>단점: 데이터베이스에 직접 lock을 걸기 때문에 성능 저하가 발생할 수 있다. 다른 트랜잭션의 대기 시간이 길어져 전체적인 시스템 성능에 영향을 주고, 데드락이 발생할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] JPA Repository]]></title>
            <link>https://velog.io/@linear_/SpringBoot-JPA-Repository</link>
            <guid>https://velog.io/@linear_/SpringBoot-JPA-Repository</guid>
            <pubDate>Thu, 11 Sep 2025 05:19:09 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>Spring Data JPA가 제공하는 인터페이스로, JPA를 더 쉽게 다루기 위한 추상화 계층이다. 개발자가 데이터베이스에 접근하는 코드 DAO를 직접 구현하지 않고도 데이터베이스를 조작할 수 있도록 하는 인터페이스 기반 프레임워크이다.</p>
<h1 id="주요-메서드">주요 메서드</h1>
<h2 id="조회">조회</h2>
<table>
<thead>
<tr>
<th>메서드 시그니처</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Optional&lt;T&gt; findById(ID id)</code></td>
<td>PK로 엔티티 단건 조회</td>
</tr>
<tr>
<td><code>List&lt;T&gt; findAll()</code></td>
<td>모든 엔티티 조회</td>
</tr>
<tr>
<td><code>List&lt;T&gt; findAllById(Iterable&lt;ID&gt; ids)</code></td>
<td>여러 PK로 엔티티 조회</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; List&lt;S&gt; findAll(Example&lt;S&gt; example)</code></td>
<td>Example 조건에 맞는 전체 엔티티 조회</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; Optional&lt;S&gt; findOne(Example&lt;S&gt; example)</code></td>
<td>Example 조건에 맞는 엔티티 1개 조회</td>
</tr>
<tr>
<td><code>long count()</code></td>
<td>전체 엔티티 개수 반환</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; long count(Example&lt;S&gt; example)</code></td>
<td>Example 조건에 맞는 엔티티 개수 반환</td>
</tr>
<tr>
<td><code>boolean existsById(ID id)</code></td>
<td>해당 PK 엔티티 존재 여부</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; boolean exists(Example&lt;S&gt; example)</code></td>
<td>Example 조건에 맞는 엔티티 존재 여부</td>
</tr>
<tr>
<td><code>T getReferenceById(ID id)</code></td>
<td>프록시 엔티티 반환 (JPA Lazy Loading)</td>
</tr>
<tr>
<td>## 저장 및 수정</td>
<td></td>
</tr>
<tr>
<td>메서드 시그니처</td>
<td>설명</td>
</tr>
<tr>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; S save(S entity)</code></td>
<td>엔티티 저장 또는 병합 (수정)</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; List&lt;S&gt; saveAll(Iterable&lt;S&gt; entities)</code></td>
<td>여러 엔티티 저장/수정</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; S saveAndFlush(S entity)</code></td>
<td>엔티티 저장/수정 후 즉시 flush</td>
</tr>
<tr>
<td><code>void flush()</code></td>
<td>영속성 컨텍스트 변경 내용을 DB에 즉시 반영</td>
</tr>
</tbody></table>
<blockquote>
<p>✏️ <strong><code>flush()</code>?</strong>
JPA에서 영속성 컨텍스트의 변경 내용을 즉시 DB에 반영하도록 하는 메서드다. JPA는 기본적으로 트랜잭션이 commit되는 시점에 자동으로 <code>flush</code>를 호출하여 DB에 쿼리를 실행한다.
대표적으로, DB에 즉시 쿼리를 반영해야 할때, 트랜잭션 내에서 쿼리 동작 순서를 명확히 해야 할 때, JPA 이벤트 리스너나 복잡한 이벤트 처리 로직을 실행할 때 <code>flush()</code>를 직접 호출한다.</p>
</blockquote>
<h2 id="삭제">삭제</h2>
<table>
<thead>
<tr>
<th>메서드 시그니처</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>void deleteById(ID id)</code></td>
<td>PK 기준 엔티티 삭제</td>
</tr>
<tr>
<td><code>void delete(T entity)</code></td>
<td>엔티티 단건 삭제</td>
</tr>
<tr>
<td><code>void deleteAllById(Iterable&lt;? extends ID&gt; ids)</code></td>
<td>여러 PK 엔티티 삭제</td>
</tr>
<tr>
<td><code>void deleteAll(Iterable&lt;? extends T&gt; entities)</code></td>
<td>여러 엔티티 삭제</td>
</tr>
<tr>
<td><code>void deleteAll()</code></td>
<td>모든 엔티티 삭제</td>
</tr>
<tr>
<td><code>void deleteAllInBatch()</code></td>
<td>모든 엔티티 일괄 (벌크) 삭제</td>
</tr>
<tr>
<td><code>void deleteAllInBatch(Iterable&lt;T&gt; entities)</code></td>
<td>전달받은 엔티티만 일괄 (벌크) 삭제</td>
</tr>
<tr>
<td><code>void deleteAllByIdInBatch(Iterable&lt;ID&gt; ids)</code></td>
<td>여러 PK 엔티티만 일괄 (벌크) 삭제</td>
</tr>
<tr>
<td>## 페이징 및 정렬</td>
<td></td>
</tr>
<tr>
<td>메서드 시그니처</td>
<td>설명</td>
</tr>
<tr>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td><code>List&lt;T&gt; findAll(Sort sort)</code></td>
<td>정렬 조건에 맞게 전체 엔티티 조회</td>
</tr>
<tr>
<td><code>Page&lt;T&gt; findAll(Pageable pageable)</code></td>
<td>페이징 조건에 맞는 엔티티 페이지 조회</td>
</tr>
<tr>
<td><code>&lt;S extends T&gt; List&lt;S&gt; findAll(Example&lt;S&gt; example, Sort sort)</code></td>
<td>Example + Sort 조건으로 엔티티 조회</td>
</tr>
</tbody></table>
<h1 id="사용자-정의-메서드">사용자 정의 메서드</h1>
<p>JPA Repository 기본 제공 메서드나 간단한 쿼리 메서드만으로 해결하기 어려운 복잡한 조회 조건 또는 특정 데이터 형태의 조회가 필요할 때 작성한다.
여러 테이블을 조인할 때, 특정 조건에 따라 동적으로 쿼리를 만들어야 할 때, 전체 엔터티가 아닌 일부 데이터만 DTO 형태로 가져와 성능을 개선하고 싶을 때 필요하다.</p>
<h2 id="생성-방법">생성 방법</h2>
<h3 id="쿼리-메서드">쿼리 메서드</h3>
<p>메서드 명 규칙만으로 자동 쿼리를 생성한다.
쉽고 빠르며, 실수를 방지할 수 있다. 단순 조건, 검색, 정렬에 매우 효율적이다. 반면 조인, 복잡 논리, 그룹/집계, 복잡 동적 쿼리에는 적합하지 않다.</p>
<h4 id="쿼리-메서드-키워드">쿼리 메서드 키워드</h4>
<table>
<thead>
<tr>
<th>구분</th>
<th>키워드(메서드 내 단어)</th>
<th>의미 / 동작</th>
<th>예시 메서드명</th>
</tr>
</thead>
<tbody><tr>
<td>조회</td>
<td><code>find</code>, <code>read</code>, <code>get</code>, <code>query</code>, <code>search</code>, <br><code>stream</code>, <code>count</code>, <code>exists</code>, <code>delete</code>, <br><code>remove</code></td>
<td>조회, 개수, 존재 여부, 삭제, etc.</td>
<td><code>findByName</code>, <code>countByAge</code>, <br><code>existsByEmail</code></td>
</tr>
<tr>
<td>조건</td>
<td><code>By</code></td>
<td>뒤에 오는 조건 필드 지정</td>
<td><code>findByName</code>, <code>findByAge</code></td>
</tr>
<tr>
<td></td>
<td><code>And</code>, <code>Or</code></td>
<td>조건 연결 (AND/OR)</td>
<td><code>findByNameAndAge</code>, <br><code>findByNameOrEmail</code></td>
</tr>
<tr>
<td></td>
<td><code>Is</code>, <code>Equals</code></td>
<td>같음 (=)</td>
<td><code>findByNameIs</code>, <br><code>findByNameEquals</code></td>
</tr>
<tr>
<td></td>
<td><code>Between</code></td>
<td>범위 (사이)</td>
<td><code>findByAgeBetween</code></td>
</tr>
<tr>
<td></td>
<td><code>LessThan</code>, <code>Before</code></td>
<td>미만 (&lt;)</td>
<td><code>findByAgeLessThan</code></td>
</tr>
<tr>
<td></td>
<td><code>GreaterThan</code>, <code>After</code></td>
<td>초과 (&gt;)</td>
<td><code>findByAgeGreaterThan</code></td>
</tr>
<tr>
<td></td>
<td><code>LessThanEqual</code></td>
<td>이하 (&lt;=)</td>
<td><code>findByAgeLessThanEqual</code></td>
</tr>
<tr>
<td></td>
<td><code>GreaterThanEqual</code></td>
<td>이상 (&gt;=)</td>
<td><code>findByAgeGreaterThanEqual</code></td>
</tr>
<tr>
<td></td>
<td><code>IsNull</code>, <code>IsNotNull</code></td>
<td>Null/Null이 아님</td>
<td><code>findByEmailIsNull</code>, <br><code>findByEmailIsNotNull</code></td>
</tr>
<tr>
<td></td>
<td><code>In</code>, <code>NotIn</code></td>
<td>컬렉션 내 포함/불포함</td>
<td><code>findByStatusIn</code>, <br><code>findByAgeNotIn</code></td>
</tr>
<tr>
<td></td>
<td><code>StartingWith</code></td>
<td>문자열로 시작하는 <br>(<code>Like &#39;xxx%&#39;</code>)</td>
<td><code>findByNameStartingWith</code></td>
</tr>
<tr>
<td></td>
<td><code>EndingWith</code></td>
<td>문자열로 끝나는 <br>(<code>Like &#39;%xxx&#39;</code>)</td>
<td><code>findByNameEndingWith</code></td>
</tr>
<tr>
<td></td>
<td><code>Containing</code>, <code>Contains</code></td>
<td>포함 (<code>Like &#39;%xxx%&#39;</code>)</td>
<td><code>findByNameContaining</code></td>
</tr>
<tr>
<td></td>
<td><code>Like</code>, <code>NotLike</code></td>
<td>Like, Not Like</td>
<td><code>findByNameLike</code>, <br><code>findByNameNotLike</code></td>
</tr>
<tr>
<td></td>
<td><code>True</code>, <code>False</code></td>
<td>불리언 값</td>
<td><code>findByActiveTrue</code></td>
</tr>
<tr>
<td>정렬</td>
<td><code>OrderBy</code></td>
<td>정렬 조건</td>
<td><code>findByNameOrderByAgeDesc</code></td>
</tr>
<tr>
<td>페이징</td>
<td><code>Pageable</code>, <code>Slice</code></td>
<td>페이징 파라미터 사용</td>
<td><code>findByName(String name,</code><br><code>Pageable pageable)</code></td>
</tr>
</tbody></table>
<h3 id="query-jpql"><code>@Query</code> (JPQL)</h3>
<p>JPQL (엔터티 기반)로 커스텀 쿼리를 작성한다.
복잡 조건, 조인, 집계, 부분 컬럼 추출 등에 적합하나, 동적 조건이 많으면 코드가 지저분해지고 컴파일러 타입 체크가 어렵다.</p>
<pre><code class="language-java">@Query(&quot;select u from User u where u.status = :status and u.age &gt;= :minAge&quot;)
List&lt;User&gt; findActiveUsers(@Param(&quot;status&quot;) UserStatus status, @Param(&quot;minAge&quot;) int minAge);</code></pre>
<h3 id="query-native"><code>@Query</code> (Native)</h3>
<p>실제 DB SQL로 쿼리를 작성한다.
DB에 최적화되어 있으며, 고급 쿼리 작성이 가능하다. 그러나 DB 종속적이고 유지보수가 어렵다.</p>
<pre><code class="language-java">@Query(&quot;SELECT * FROM users WHERE status = ?1 AND created_at &gt; ?2&quot;, nativeQuery = true)
List&lt;User&gt; findActiveUsersNative(String status, LocalDateTime createdAt);</code></pre>
<h3 id="querydsl">QueryDSL</h3>
<p>Java 코드를 기반으로 타입 안전, 동적 쿼리, 복잡 로직을 작성한다.
유지보수나 재사용성이 탁월하다. 반복/변경 가능한 동적 쿼리는 반드시 QueryDSL을 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] JPA (Java Persistence API)]]></title>
            <link>https://velog.io/@linear_/Java-JPA-Java-Persistence-API</link>
            <guid>https://velog.io/@linear_/Java-JPA-Java-Persistence-API</guid>
            <pubDate>Thu, 11 Sep 2025 01:55:44 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>RDBMS에 데이터를 CRUD (저장/조회/수정/삭제)할 수 있도록 표준화된 Java의 ORM 프레임워크</p>
<blockquote>
<p>✏️ <strong>ORM (Object-Relational Mapping)?</strong>
객체 지향 프로그래밍 언어와 데이터베이스 간에 데이터를 매핑하는 기술로, 개발자는 SQL이 아닌 객체를 사용하여 데이터베이스에 접근할 수 있다.</p>
</blockquote>
<blockquote>
<p>✏️ <strong>JDBC (Java Database Connectivity)?</strong>
Java에서 데이터베이스에 접근하기 위한 표준 API로, Java에서 SQL을 직접 실행하여 DB와 연동하고 SQL을 직접 작성한다.</p>
</blockquote>
<h1 id="jpa를-위한-db-정보-구성">JPA를 위한 DB 정보 구성</h1>
<p>Spring Boot에서 JPA 및 데이터베이스 연결 정보는 <code>application.yml</code>에 설정한다.</p>
<pre><code class="language-yaml">spring:
  datasource:
    url: jdbc:h2:mem:skala-stock
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  h2:
    console:
      enabled: true</code></pre>
<blockquote>
<p>✏️ Spring Boot는 2.0 ver.부터 기본 데이터베이스 커넥션 풀로 HikariCP를 사용한다. 세밀한 커넥션 풀 설정 필요 시 hikari 속성으로 조정 가능하다.</p>
<pre><code class="language-yaml">spring:
  datasource:
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2
      connection-timeout: 30000
      idle-timeout: 600000</code></pre>
</blockquote>
<h1 id="주요-어노테이션">주요 어노테이션</h1>
<h2 id="entity"><code>@Entity</code></h2>
<p>해당 클래스를 JPA가 관리하는 엔터티 (테이블)로 지정한다. Java 클래스와 데이터베이스의 테이블을 1:1로 매핑한다.
<code>@Entity</code>가 선언된 클래스는 JPA의 CRUD 대상이 되며, 반드시 기본 생성자 (파라미터가 없는 생성자)가 필요하다. <code>final</code>, <code>abstract</code>, <code>interface</code>, <code>inner class</code>에는 선언할 수 없다.
<code>@entity</code>만 선언하면 클래스 이름이 그대로 테이블명으로 매핑되나, 실제 운영에서는 테이블명과 클래스명이 다를 수 있으므로 일반적으로 <code>@Table</code>을 함께 사용해서 명시적으로 테이블명을 지정하는 것이 안전하다.</p>
<pre><code class="language-java">import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = &quot;stocks&quot;)
public class Stock {
    @Id
    private String ticker;

    private String name;

    private String market;
}</code></pre>
<h2 id="table"><code>@Table</code></h2>
<p><code>@Entity</code>로 지정한 클래스가 어느 DB 테이블과 매핑될지 명시한다. 주로 테이블 이름이 클래스명과 다를 때 사용하거나, 스키마 지정 등 추가 설정이 필요할 때 사용한다.</p>
<h3 id="속성">속성</h3>
<ul>
<li><code>name</code>: 매핑할 DB 테이블 이름을 지정한다.</li>
<li><code>schema</code>: 사용할 DB 스키마를 지정한다. (DBMS가 지원하는 경우에만 해당)</li>
<li><code>catalog</code>: DB 카탈로그를 지정한다. (거의 사용하지 않는다.)</li>
<li><code>uniqueConstraints</code>: Unique 제약 조건을 지정한다.<pre><code class="language-java">@Entity
@Table(
  name = &quot;stocks&quot;,
  uniqueConstraints = {
      @UniqueConstraint(columnNames = {&quot;ticker&quot;, &quot;market&quot;})
  }
)
public class Stock {
  @Id
  private String ticker;
  private String name;
  private String market;
}</code></pre>
</li>
</ul>
<blockquote>
<p>✏️ <strong>Unique 제약 조건: 단일 컬럼 vs 테이블 단위 복합 컬럼</strong>
<code>@Column(unique = true)</code>: 한 컬럼이 중복되지 않아야 할 때 사용한다.
<code>@Table(uniqueConstraints = ...)</code>: 두 개 이상 컬럼 조합이 중복되지 않도록 보장하고 싶을 때 사용한다.</p>
</blockquote>
<h2 id="id"><code>@Id</code></h2>
<p>엔터티의 Primary Key 필드를 지정한다. JPA의 각 엔터티는 반드시 하나 이상의 <code>@Id</code>가 필요하다.
<code>@GeneratedValue(strategy = GenerationType.IDENTITY)</code>를 사용하여 DBMS에서 <code>auto_increment</code>로 PK를 자동 할당하도록 할 수 있다.</p>
<pre><code class="language-java">// 기본키가 하나인 경우
@Entity
@Table(name = &quot;stocks&quot;)
public class Stock {
    @Id
    private String ticker;

    private String name;
    private String market;
}</code></pre>
<pre><code class="language-java">// 자동 생성되는 숫자 기본키
@Entity
@Table(name = &quot;transactions&quot;)
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String ticker;
    private int quantity;
    private double price;
    private LocalDateTime tradedAt;
}</code></pre>
<p>주로 두 개 이상의 컬럼 조합이 고유해야 하는 경우 복합키(Composite Key)로 구성한다.
<code>@Embeddable</code> 클래스로 복합키 타입을 정의하고, <code>@EmbeddedId</code>로 엔터티에 복합키를 사용한다.</p>
<pre><code class="language-java">@Embeddable
public class UserStockId implements Serializable {
    private Long userId;
    private String ticker;
}

@Entity
@Table(name= &quot;user_stocks&quot;)
public class UserStock {
    @EmbeddedId
    private UserStockId id;

    private int quantity;
}</code></pre>
<h2 id="generatedvalue"><code>@GeneratedValue</code></h2>
<p>Primary Key 값을 자동으로 생성하는 전략을 지정할 때 사용한다. 반드시 <code>@Id</code>와 함께 사용한다. 데이터베이스의 auto_increment, 시퀀스, 별도 테이블 등 다양한 자동 생성 방식을 지원한다.</p>
<h3 id="속성-1">속성</h3>
<ul>
<li><code>strategy</code>: PK 자동 생성 방식 지정 (<code>AUTO</code>, <code>IDENTITY</code>, <code>SEQUENCE</code>)</li>
<li><code>generator</code>: 시퀀스/테이블 방식일 때 커스텀 생성기 이름 지정<h2 id="column"><code>@Column</code></h2>
엔터티 필드를 데이터베이스의 컬럼과 매핑할 때 사용한다.<h3 id="속성-2">속성</h3>
</li>
<li><code>name</code>: 컬럼명 지정</li>
<li><code>nullable</code>: null 허용 여부 (default: <code>true</code>)</li>
<li><code>length</code>: 문자 길이 (문자열 필드에만 사용)</li>
<li><code>unique</code>: Unique 제약 조건 부여 (단일 컬럼인 경우)</li>
<li><code>updatable</code>: UPDATE 쿼리에서 수정 가능 여부</li>
<li><code>insertable</code>: INSERT 쿼리에서 포함 여부</li>
<li><code>precision</code>, <code>scale</code>: 소수점 숫자 자리수 지정 (숫자형 필드에만 사용)</li>
</ul>
<pre><code class="language-java">@Entity
@Table(name = &quot;users&quot;)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long Id;

    @Column(name = &quot;user_name&quot;, nullable = false, length = 30)
    private String name; // DB 컬럼: user_name, NOT NULL, 최대 30자

    @Column(unique = true, length = 50)
    private String email; // DB 컬럼: email, UNIQUE, 최대 50자

    @Column(length = 100)
    private String password; // 길이 100자 제한

    @Column(updatable = false)
    private LocalDateTime createdAt; //수정 불가
}</code></pre>
<pre><code class="language-java">@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long Id;

    @Column(precision = 12, scale=4)
    private BigDecimal price; // 최대 99,999,999.9999
}</code></pre>
<h2 id="enumerated"><code>@Enumerated</code></h2>
<p><code>ENUM</code> 타입 필드를 데이터베이스에 어떻게 저장할지 지정할 때 사용한다. Java의 <code>enum</code> 타입은 RDB에 직접 저장할 수 없으므로, <code>ORDINAL</code> (숫자)나 <code>STRING</code> (문자열)로 변환하여 저장한다.</p>
<blockquote>
<p>✏️ 실무에서는 <code>STRING</code> 방식을 권장한다.</p>
</blockquote>
<pre><code class="language-java">public enum StockStatus {
    ACTIVE,
    INACTIVE
}

@Entity
public class Stock {
    @Id
    private String ticker;

    @Enumerated(EnumType.STRING)
    @Column(length = 10)
    private StockStatus status;
}</code></pre>
<h2 id="lob"><code>@Lob</code></h2>
<p>대용량 데이터를 데이터베이스에 저장할 때 사용한다. 필드 타입에 따라 자동으로 <code>CLOB</code> (텍스트) 또는 <code>BLOB</code> (바이너리)으로 매핑된다.</p>
<pre><code class="language-java">@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Lob
    private String description; // CLOB로 매핑

    @Lob
    private byte[] profileImage; // BLOB로 매핑
}</code></pre>
<blockquote>
<p>✏️ <strong><code>@Lob</code> vs <code>Column(columnDefinition = &quot;TEXT&quot;)</code></strong>
<code>@Lob</code>은 JPA 표준 방식으로 DB 독립적이다. 유지보수 및 확장성에 유리하다.
<code>columnDefinition</code>은 DB 종속정이며, 유연성이 적다.</p>
</blockquote>
<h2 id="transient"><code>@Transient</code></h2>
<p>엔터티 필드 중에서 DB 컬럼과 매핑하지 않을 필드에 사용한다. 해당 필드는 DB 테이블에 컬럼으로 생성되지 않으며, JPA의 영속화 (저장/조회) 대상이 아님을 명시한다. 계산값, 임시 데이터, 화면 출력 전용 속성 등에 활용한다.</p>
<pre><code class="language-java">@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String password;

    @Transient
    private String passwordConfirm;
}</code></pre>
<h2 id="onemanytoonemanyjoincolumnjointable"><code>@{One/Many}To{One/Many}</code>/<code>@JoinColumn</code>/<code>@JoinTable</code></h2>
<p>객체 간의 관계를 데이터베이스 테이블의 Foreign Key와 연결하는 방법을 정의한다. 다중성 (1:1, 1:N, N:1, N:M)과 방향성 (단방향, 양방향)을 조합하여 설정한다.</p>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>관계</th>
</tr>
</thead>
<tbody><tr>
<td><code>@ManyToOne</code></td>
<td>N:1</td>
</tr>
<tr>
<td><code>@OneToMany</code></td>
<td>1:N</td>
</tr>
<tr>
<td><code>@OneToOne</code></td>
<td>1:1</td>
</tr>
<tr>
<td><code>@ManyToMany</code></td>
<td>N:M</td>
</tr>
<tr>
<td><code>@JoinColumn</code></td>
<td>외래키 지정</td>
</tr>
<tr>
<td><code>@JoinTable</code></td>
<td>중간테이블 (N:M)</td>
</tr>
</tbody></table>
<pre><code class="language-java">// Users.java
@Entity
@Table(name = &quot;users&quot;)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;user&quot;) // mappedBy는 상대 엔터티에서 어떤 필드로 매핑되어 있는지 지정한다.
    private List&lt;Transaction&gt; transactions = new ArrayList&lt;&gt;();

    @OneToOne(mappedBy = &quot;user&quot;)
    private UserProfile profile;

    @ManyToMany
    @JoinTable(name = &quot;user_watchlist&quot;, joinColumns = @JoinColumn(name = &quot;user_id&quot;),
            inverseJoinColumns = @JoinColumn(name = &quot;ticker&quot;))
    private List&lt;Stock&gt; watchList = new ArrayList&lt;&gt;();
}</code></pre>
<pre><code class="language-java">// Stocks.java
@Entity
@Table(name = &quot;stocks&quot;)
public class Stock {
    @Id
    private String ticker;

    private String name;

    @Lob
    private String description;

    @OneToMany(mappedBy = &quot;stock&quot;)
    private List&lt;Transaction&gt; transactions = new ArrayList&lt;&gt;();

    @ManyToMany(mappedBy = &quot;watchList&quot;)
    private List&lt;User&gt; fans = new ArrayList&lt;&gt;();
}</code></pre>
<pre><code class="language-java">// Transactions.java
@Entity
@Table(name = &quot;transactions&quot;)
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = &quot;user_id&quot;)
    private User user; // 거래한 유저

    @ManyToOne
    @JoinColumn(name = &quot;ticker&quot;)
    private Stock stock; // 거래한 주식

    private int quantity;

    @Column(precision = 15, scale = 4)
    private BigDecimal price;

    private LocalDateTime tradedAt;

    @Enumerated(EnumType.STRING)
    @Column(length = 10)
    private TransactionType type;
}</code></pre>
<pre><code class="language-java">// UserProfiles.java
@Entity
@Table(name = &quot;user_profiles&quot;)
public class UserProfile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = &quot;user_id&quot;, unique = true) // FK 컬럼 이름을 지정한다. 해당 필드가 다른 테이블의 PK를 참조함을 명시한다.
    private User user;

    @Lob
    private byte[] profileImage;
}</code></pre>
<table>
<thead>
<tr>
<th>관계</th>
<th>DBML</th>
<th>JPA 매핑</th>
</tr>
</thead>
<tbody><tr>
<td>User-Transaction</td>
<td>users.id → transactions.user_id (1:N)</td>
<td>User: <code>@OneToMany(mappedBy=&quot;user&quot;)</code> <br> Transaction: <code>@ManyToOne(user_id)</code></td>
</tr>
<tr>
<td>Stock-Transaction</td>
<td>stocks.ticker → transactions.ticker (N:1)</td>
<td>Stock: <code>@OneToMany(mappedBy=&quot;stock&quot;)</code> <br> Transaction: <code>@ManyToOne(ticker)</code></td>
</tr>
<tr>
<td>User-Profile</td>
<td>users.id → user_profiles.user_id (1:1)</td>
<td>User: <code>@OneToOne(mappedBy=&quot;user&quot;)</code> <br> UserProfile: <code>@OneToOne</code> + <code>@JoinColumn</code></td>
</tr>
<tr>
<td>User-Stock <br>(관심종목)</td>
<td>user_watchlist (user_id, ticker) (N:M)</td>
<td>User: <code>@ManyToMany</code> + <code>@JoinTable</code> <br> Stock: <code>@ManyToMany(mappedBy=&quot;watchList&quot;)</code></td>
</tr>
</tbody></table>
<h2 id="embeddedembeddable"><code>@Embedded</code>/<code>@Embeddable</code></h2>
<p><code>@Embeddable</code>는 값 타입 객체를 정의할 때 사용한다. 독립적인 엔터티가 아니라, 다른 엔터티에 포함되어 함께 저장되는 복합 값 객체임을 표시한다. DB에 별도 테이블이 생성되지 않고, 엔터티 테이블의 컬럼으로 들어간다.
<code>@Embedded</code>는 엔터티에서 값 타입, 즉 <code>@Embeddable</code>로 선언된 클래스를 필드로 사용할 때 설정한다. 실제로는 엔터티 테이블에 이 값 타입의 각 필드가 컬럼으로 생성된다.</p>
<pre><code class="language-java">@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    // getter/setter 등
}</code></pre>
<pre><code class="language-java">@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    private Address address; // User 테이블에 city, street, zipcode 생성
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>