<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>lumiere-on.log</title>
        <link>https://velog.io/</link>
        <description>얼렁뚱땅 요리조리</description>
        <lastBuildDate>Tue, 15 Jul 2025 08:58:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>lumiere-on.log</title>
            <url>https://velog.velcdn.com/images/iumiere-on/profile/05d43ed8-ea3a-4a59-9b13-04ca7c5a3f41/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. lumiere-on.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/iumiere-on" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[논문 리뷰(8) TTRL: Test-Time Reinforcement Learning]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B08-TTRL-Test-Time-Reinforcement-Learning</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B08-TTRL-Test-Time-Reinforcement-Learning</guid>
            <pubDate>Tue, 15 Jul 2025 08:58:03 GMT</pubDate>
            <description><![CDATA[<p>이번에는 <a href="https://arxiv.org/pdf/2504.16084">TTRL: Test-Time Reinforcement Learning</a>에 대해 리뷰하겠습니다. 
(<a href="https://github.com/PRIME-RL/TTRL">깃허브</a>)
<a href="https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B07-Self-Evolved-Reward-learning-for-LLMs">논문 리뷰(7)</a>과 동일하게 self-evolution of LLM을 탐구했으며 groud truth 없이 inference를 진행한다는 게 특징적인 연구입니다. </p>
<p>아직 읽어보진 않았지만, majority voting reward function을 사용한다는 점에서 흥미로워보입니다(՞•֊•՞)</p>
<p><em>이건 사담인데요! AI쪽 논문을 읽다보면 칭화대에서 publish한 논문이 꽤나 많이 보입니다. 중국의 AI 경쟁력이 어마무시하다는 걸 종종 깨닫습니다..</em>(* •︠ ̯ •︡)</p>
<h1 id="abstract">Abstract</h1>
<p>본 논문에서는 LLM의 reasoning task에서 explicit label 없이 RL을 이용해 training 함.</p>
<ul>
<li><p>이때 challenge: <em>ground truth 없이</em> 추론 과정에서 reward estimation 하는 것.
→ majority voting(과반수 투표제)와 같은 Test-Time Scaling(TTS)에서 사용하는 기법을 통해 효과적으로 reward 산출 
→ 본 논문: Test-Time Reinforcement Learning(TTRL) 제안</p>
</li>
<li><p>TTRL의 정의 및 특징</p>
<ul>
<li>unlabeled data에 RL 이용해 LLM training</li>
<li>pre-trained LLM 활용해 self-evolution 가능케 함.</li>
</ul>
</li>
<li><p>TTRL 실험 요약</p>
<ul>
<li>Qwen모델의 <strong>pass@1</strong> 성능 향상</li>
<li>TTRL이 maj@n metric만을 보상으로 삼아 학습함. <ul>
<li><strong>maj@n</strong>: 모델이 한 입력(x)에 대해 n개의 후보 답변을 만들고, 그중 다수결로 답을 뽑는 방식</li>
</ul>
</li>
</ul>
<p>→ 초기 모델의 maj@n 성능(모델이 다수결만으로 낼 수 있는 최대치)를 뛰어넘음
&amp; ground-truth label로 직접 supervised training한 모델의 성능에 근접</p>
</li>
</ul>
<h1 id="6-conclusion">6. Conclusion</h1>
<ul>
<li><p>TTRL</p>
<blockquote>
<p>a framework for training LLMs with RL on test data without access to ground-truth labels</p>
</blockquote>
<ul>
<li><p>key component</p>
<ul>
<li><strong>majority voting reward function</strong>: generate rule-based rewards based on consensus among model predictions(모델 예측 중 가장 많이 나온 y를 reward signal로 한다는 합의에 따라 규칙 기반한 보상을 생성)</li>
</ul>
</li>
<li><p>TTRL이 기여한 바</p>
<ul>
<li>다양한 모델과 태스크에 TTRL을 적용 가능</li>
<li><strong>RL에 self-labeled reward를 적용</strong>하는 방법의 초석을 다짐. </li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="7-limitations-and-future-works">7. Limitations and Future works</h1>
<h2 id="limitations">Limitations</h2>
<ul>
<li>사전 지식과 하이퍼파라미터 구성의 영향에 대한 심도있는 분석이 부족</li>
</ul>
<h2 id="future-works">Future works</h2>
<ul>
<li>Theoretical Analysis</li>
<li>Online Learning with Streaming data<ul>
<li>extend TTRL to real-time learning  → Test-time adaptation</li>
</ul>
</li>
<li>Lare-Scale Self-supervised RL training</li>
<li>Agentic tasks and scientific discovery 
<br><br></li>
</ul>
<hr>
<h1 id="1-introduction">1. Introduction</h1>
<h3 id="최근-lrm의-발전-및-한계">최근 LRM의 발전 및 한계</h3>
<ul>
<li>최근 <strong>Large Reasoning Models</strong> such as DeepSeek-R1 and OpenAI&#39;s o1 등이 많이 발전함.
그런데 이렇게 발저한 모델들도 복잡하고 라벨링되지 않은 질문에는 답변을 잘 못함. 
→ 이런 문제를 해결하기 위한 scaling up training with more data &amp; computational resources 와 같은 해결책이 있으나 이 방법도 높은 성능을 이끌어내지는 못함. 
→ 이후 대안으로 &quot;era of experience&quot;로의 전환이 제시됨.(경험을 통해 self-evolve하는 것)</li>
</ul>
<h3 id="lrm-분야에서의-진보">LRM 분야에서의 진보</h3>
<ul>
<li><strong>self-experience &amp; learning</strong>: AI 시스템이 unlabeled data에 RL을 이용해 자율적으로 발전</li>
<li>self-evolvement의 두 범주<ol>
<li>adaptation to test-time data: model이 어려운 벤치마크를 다룰 수 있게 함</li>
<li>training on external unlabeled data: labeled corpora 이외에 더 많은 데이터 확보 가능
→ 본 논문에서는 1번 범주를 다룸.</li>
</ol>
</li>
</ul>
<h3 id="ttrl의-도입">TTRL의 도입</h3>
<ul>
<li>본 논문에서는 RL을 이용해 test time에 모델을 업데이트하고자 한다. 그런데 <em>test time에 RL을 위한 reward를 어떻게 얻을까?</em></li>
<li>이 질문은 현재 RL의 한계와도 맞물림.<ul>
<li>RL은 labeled data에 의존하기 때문에 이 점이 RL 발전의 장벽이 되고 있음. </li>
</ul>
</li>
</ul>
<p>➡️ 위와 같은 한계를 해결하기 위해 TTRL 도입</p>
<ul>
<li><h4 id="ttrl">TTRL</h4>
<ul>
<li><strong>test-time training through RL</strong></li>
<li><strong>repeated sampling</strong> 전략 사용 ← label을 더 정확히 예측하고 보상을 계산하기 위해서</li>
<li><strong>majority voting rewards</strong>를 사용해 ground truth label 없이도 안정적으로 RL 적용</li>
<li>한 마디로 정리하면 <em>generate experience, estimate rewards, improve performance</em> <br> 
</li>
</ul>
</li>
<li><p>TTRL의 주요내용</p>
<blockquote>
<p>1.Majority voting은 효과적인 보상 예측을 제공한다. </p>
<ol start="2">
<li>TTRL can exceed it training signal and upper limit maj@n, and closely mirrors the performance of direct training on the test data with ground-truth.</li>
<li>unsupervised 방식을 통해서 효과적이고 안정적인 RL을 이끌어낼 수 있다. </li>
</ol>
</blockquote>
</li>
</ul>
<h1 id="2-test-time-reinforcemet-learningttrl">2. Test-Time Reinforcemet Learning(TTRL)</h1>
<ul>
<li>본 논문에서 정의한 task<blockquote>
<p>우리는 ground-truth label 없이 RL을 사용해 test time동안 pre-trained된 모델을 훈련하는 문제를 해결한다. 그리고 이 세팅을 TTRL이라 부른다. </p>
</blockquote>
</li>
</ul>
<h2 id="21-methodology">2.1 Methodology</h2>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/7a3a73e4-68c7-4f96-b8ed-ebc73f140e4a/image.png" alt=""></p>
<h3 id="위-구조에-기반한-동작-순서">위 구조에 기반한 동작 순서</h3>
<ol>
<li><p>prompt x에 의해 상태가 주어지면, 모델은 정책 $π_θ (y | x)$로부터 샘플된 output $y$를 산출한다. </p>
<ul>
<li>repeated sampling에 의해 모델이 여러 개의 후보 output인 ${y_1, y_2, ..., y_N}$을 생성</li>
</ul>
</li>
<li><p>majority voting에 의해 합의된 output인 $y^*$가 도출</p>
</li>
<li><p>환경은 reward $r(y, y*)$를 제공</p>
</li>
<li><p>RL objective를 기반으로 policy optimization 수행</p>
<ul>
<li><p>RL objective의 목표: reward 기댓값 최대화
$$
\max_{\theta};
\mathbb{E}<em>{y\sim\pi</em>{\theta}(,\cdot\mid x)}
\bigl[r(y, y^{*})\bigr]
$$</p>
</li>
<li><p>parameter $\theta$ update
$$
θ ← θ + η∇<em>θE</em>{y∼π_θ(·|x)}[r(y, y∗)]
$$</p>
<ul>
<li>$\eta$: learning rate</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="22-majority-voting-reward-function">2.2 Majority Voting Reward function</h2>
<p>majority voting이 동작하는 순서</p>
<ol>
<li><p><strong>LLM</strong>에 질문 x를 입력  </p>
</li>
<li><p><strong>LLM</strong>이 여러 개의 candidate output을 생성  </p>
</li>
<li><p><strong>Answer Extractor</strong>가 output에서 predicted answers를 추출  </p>
<ul>
<li><p>predicted answers는 집합 P에 저장  </p>
<p>$$
P = {\hat{y}<em>{i}}</em>{i=1}^{N}
$$  </p>
</li>
</ul>
</li>
<li><p>scoring function s를 사용해 최종 답변 $y^{*}$를 선정  </p>
<p>  $$
  y^{*} = \underset{y_i}{\arg\max}; s(y_i, x)
  $$  </p>
</li>
</ol>
<ol start="5">
<li>y($y^*$)를 이용해 rule-based rewards 계산
$$
R(\hat{y}<em>{i},, y) =
\begin{cases}
1, &amp; \text{if } \hat{y}</em>{i} = y, \[4pt]
0, &amp; \text{otherwise.}
\end{cases}
$$</li>
</ol>
<p>위 1~5까지의 과정을 pseudo-code로 정리하면 아래와 같음.
<img src="https://velog.velcdn.com/images/iumiere-on/post/8e7db5d8-29eb-49a8-844a-47d12487c49f/image.png" alt=""></p>
<h1 id="3-experiments">3. Experiments</h1>
<h2 id="31-experimental-setup">3.1 Experimental Setup</h2>
<p>model, benchmarks, evaluation setup, baselines로 나뉨.</p>
<h3 id="model">Model</h3>
<ul>
<li>Qwen family</li>
<li>LLaMA family</li>
<li>Mistral Family</li>
<li>DeepSeek family</li>
<li>others: Skywork-OR1-Math-7B</li>
</ul>
<h3 id="benchmarks">Benchmarks</h3>
<ul>
<li>GPQA-Diamond</li>
<li>AIME 2024</li>
<li>AMC</li>
<li>MATH-500</li>
</ul>
<h3 id="evaluation-setup">Evaluation Setup</h3>
<ul>
<li>main experimens<ul>
<li>pass@k</li>
<li>pass@1:$pass@1 = \frac{1}{k}\Sigma^{k}_{i=1}p_i$<ul>
<li>$p_i$: i번째 응답이 올바른지 나타냄</li>
</ul>
</li>
</ul>
</li>
<li>analysis and additional experiments on Qwen2.5-MATH<ul>
<li>greedy decoding to report pass@1</li>
</ul>
</li>
</ul>
<h3 id="baselines">Baselines</h3>
<ul>
<li>TTRL이 성취를 이뤘는지 판단하기 위해 backbone model과 비교(Test-Time Training에 대한 선행연구가 부족해서)</li>
</ul>
<h2 id="32-main-results">3.2 Main results</h2>
<ul>
<li>TTRL performs well on most tasks and models
<img src="https://velog.velcdn.com/images/iumiere-on/post/5c5cf706-1fae-4d30-b1e4-9a6e93de01d1/image.png" alt="">
<img src="https://velog.velcdn.com/images/iumiere-on/post/dc371237-5e58-4632-a73c-62ebb8f2ff63/image.png" alt="">
위 2개의 표에서 알 수 있듯이 TTRL을 적용한 모든 모델에서 유의미한 성능 향상을 보임.</li>
</ul>
<ul>
<li><p>TTRL performs well on LRMs
<img src="https://velog.velcdn.com/images/iumiere-on/post/e97e7a32-1233-49c2-acca-564dd34aa3b5/image.png" alt="">
TTRL을 적용한 모델이 시간과 비용 측면에서 소모적인 post-training을 하는 기존 모델들보다도 좋은 성과를 냄.</p>
</li>
<li><p>TTRL naturally scales
모델이 클수록 성능이 더 향상됨. </p>
</li>
<li><p>TTRL generalizes beyond the target task
OOD(Out-Of-Distrubution: 범위 밖 → 타겟 데이터셋 이외 데이터)에도 잘 적용됨 → TTRL이 self-improvement를 잘 수행하고 있다는 증거</p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/634bb15c-1bcb-4b3d-84d6-2932cf606f8c/image.png" alt=""></p>
<ul>
<li>위 표에서는 AIME/AMC/MATH-500에 TTRL을 적용한 뒤 다른 데이터셋을 기반으로 평가한 결과를 보여줌. </li>
<li>벤치마크 데이터셋이 아닌 데이터셋에 평가를 진행했음에도 불구하고 성능 향상이 이루어졌음. </li>
</ul>
</li>
<li><p>TTRL is compatible with different RL algorithms</p>
<ul>
<li>본 논문에서 사용한 GRPO 외에 PPO, PRIME을 사용했을 때에도 유사한 결과 도출</li>
</ul>
</li>
<li><p>TTRL achieves sustainable self-evolution through &quot;online&quot; &amp; &quot;RL&quot;</p>
<ol>
<li>모델의 training dynamics를 연구하기 위해 average(pass@1, avg@16) &amp; majority(maj@16)을 추적</li>
<li>TTRL이 maj@16의 비용을 줄인 상황에서 pass@1을 향상시키는지 추적</li>
</ol>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/e5ebeb9f-ad1e-4837-b73e-a33b996e3924/image.png" alt=""></p>
<ul>
<li>위 표에서 알 수 있듯이 training이 진행될수록 1의 지표가 모두 우상향을 그림. </li>
<li>또한 단순히 majority voting에 기반해 뽑았던 label y를 보상으로 활용해 RL을 진행하면서 답변의 질을 높임! </li>
</ul>
</li>
</ul>
<h1 id="4-analysis--discussions">4. Analysis &amp; Discussions</h1>
<p>이 파트에서는 3개의 질문을 던집니다. </p>
<h2 id="41-q1-how-well-can-ttrl-perform">4.1 Q1: How well can TTRL perform?</h2>
<p>분석을 진행하기에 앞서 2가지의 상한선을 정의합니다. </p>
<blockquote>
<ol>
<li>maj@n of the initial model</li>
<li>direct training on benchmark(ground-truth label 있음)</li>
</ol>
</blockquote>
<p>1은 기존 모델이 가장 많이 선택한 답을 의미, 2는 ground-truth label이 있는 벤치마크로 직접 학습한 경우를 말합니다. </p>
<ul>
<li>TTRL is supervised by maj@n yet surpasses it. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰(7) Self-Evolved Reward learning for LLMs]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B07-Self-Evolved-Reward-learning-for-LLMs</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B07-Self-Evolved-Reward-learning-for-LLMs</guid>
            <pubDate>Tue, 15 Jul 2025 05:43:35 GMT</pubDate>
            <description><![CDATA[<p>요즘 RLHF의 분야에서 human preference 데이터의 한계를 뛰어넘고자 하는 self-evolve 아이디어가 많이 나오는 것 같습니다. </p>
<p>오늘 읽어볼 <a href="https://arxiv.org/pdf/2411.00418">Self-Evolved Reward learning for LLms</a>도 유사한 맥락입니다. 
(참고로 이 논문은 제 벨로그의 <a href="https://velog.io/@iumiere-on/%EC%97%B0%EA%B5%AC%EC%8B%A4%EC%97%B0%EA%B5%AC-%EC%A3%BC%EC%A0%9C-%EC%B0%BE%EA%B8%B01">&#39;연구 주제 찾기(1)&#39;의 To-do</a>에서 언급한 논문 중 하나입니다. )</p>
<p>그럼 리뷰를 시작하겠습니다!</p>
<h1 id="abstract">Abstract</h1>
<ul>
<li>RLHF의 핵심은 RM을 훈련시키는 것 ← human experts/AI system가 제공한 label에 의존</li>
<li>language model이 발전하면서 human input이 LM의 성능을 높이는데 덜 효과적이게 됨. </li>
<li>본 논문에서는 RM이 training data를 생성해 학습하는 <strong>Self-Evolved Reward Learning(SER)</strong> 제시.</li>
<li>실험의 데이터셋: HH-RLHF, UltraFeedback</li>
<li>실험에서 사용한 모델: Mistal, Llama3</li>
<li>Resources of paper: <a href="https://microsoft.github.io/DKI_LLM/ser/ser_index.html">https://microsoft.github.io/DKI_LLM/ser/ser_index.html</a></li>
</ul>
<h1 id="6-conclusion">6. Conclusion</h1>
<ul>
<li><strong>SER</strong>을 통해 <ul>
<li>모델이 labeled data를 생성 </li>
<li>적절한 데이터를 선택하도록 모델의 학습 상태 조절
➡️ 뛰어난 성능을 내는 <strong>iterative evolution</strong> 이뤄냄
➡️ self-improvement of LLMs의 insight를 제공</li>
</ul>
</li>
</ul>
<h1 id="1-introduction">1. Introduction</h1>
<h3 id="기존의-rlhf">기존의 RLHF</h3>
<ol>
<li>human preference로부터 RM 학습 → learned RM is frozen to tain LLM via RL such as <em>PPO</em><ol start="2">
<li>RM 학습 없이 human preference로부터 train LLM such as <em>DPO</em></li>
</ol>
</li>
</ol>
<ul>
<li>위 방법들의 한계<ul>
<li>human-annotated preference data에 의존 
→ 이 데이터들은 제한적이고 비쌈
→ RL 발전에 제약 &amp; LLM의 scalability를 발휘할 수 없게 함.(여기서 scalability: 많은 데이터로 높은 성능을 내는 것)</li>
</ul>
</li>
<li>한계를 극복하기 위한 해결책<ul>
<li>RLAIF: Reinforcement Learning from AI Feedback</li>
<li>RLAIF의 한계: <ul>
<li>RLAIF도 _LLM이 높은 질의 feedback을 낼 수 있다는 가정_을 함</li>
<li>feedback을 위해선 _stronger LLM_이 필요함</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="최근-발전-및-본-논문의-제안">최근 발전 및 본 논문의 제안</h3>
<ul>
<li><p>LLM이 인간의 입력 없이 세계 지식과 복잡한 패턴을 이해할 수 있다는 _word model_로서의 능력이 있다고 제안. 
→ 이 능력을 RLHF &amp; RLAIF까지 확장; RM에서의 역할을 확장</p>
<p>→ 본 논문에서는 더 적은 human-annoated data를 기반으로 feedback loop을 통해 RM이 self-evolve하는 방법 제안</p>
<ul>
<li>LLM이 RM으로 동작하며 dataset에 기반한 <em>feedback</em> 산출</li>
<li><strong>feedback-then-train</strong> 루프를 통해 RM이 self-evolve 가능해짐.</li>
<li>iteration 과정에서 유사한 데이터가 성능 저하를 불러올 수 있기에 <em>RM의 학습 상태를 구별</em> → <em>데이터 선별 전략을 사용 for robost RM training</em></li>
</ul>
</li>
</ul>
<h3 id="본-논문의-contributions">본 논문의 contributions</h3>
<ol>
<li>15%의 human-annotated seed data를 가지고 full human-labeled dataset으로 학습시킨 모델과 견줄만한 성과 이룸.</li>
<li>self-learning paradigms in LLM에 insight 제공
특히 RM 향상에서</li>
<li>다양한 실험을 통해 various LLM, model sizes dataset 에 걸친 성능 향상을 증명</li>
</ol>
<h1 id="2-related-work">2. Related work</h1>
<h2 id="21-reinforcement-learning-from-external-feedback">2.1 Reinforcement Learning from external feedback</h2>
<p>기존 RL의 한계들이 제시됩니다. 위에서 말했던 걸 다시 자세하게 언급합니다. </p>
<ul>
<li>RLHF의 방법<ul>
<li>방법 1</li>
</ul>
<ol>
<li>human preference data로부터 fixed RM 훈련시킴</li>
<li>trained RM은 PPO 같은 RL을 통해 LLM을 훈련시키는 데 사용<br></li>
</ol>
<ul>
<li>방법 2</li>
</ul>
<ol>
<li>DPO가 human preference data를 이용해 RM training 없이 바로 LLM 훈련시킴. <br></li>
</ol>
<ul>
<li>방법 3</li>
</ul>
<ol>
<li>preference training schemes가 성능과 안정성을 향상시키도록 조정<br></li>
</ol>
</li>
<li>데이터셋으로 인한 기존 RLHF의 한계<ul>
<li>human preference data는 비싸고 시간도 많이 소요됨. 
→ data quality &amp; size는 LL의 성능의 bottleneck이였음. </li>
</ul>
</li>
<li>RLAIF 도입<ul>
<li>RLHF의 한계를 극복하고자 RLAIF를 도입했음</li>
<li>한계: <ul>
<li>LLM이 높은 질의 피드백으 제공한다는 가정에 의존</li>
<li>피드백을 제공하기 위해서는 stronger LLM이 필요</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="22-self-learning-in-llms">2.2 Self-learning in LLMs</h2>
<ul>
<li><p>self-learning의 발전 </p>
<blockquote>
<p>self-learning in LLMs focuses on enhancing capabilities without external supervision. </p>
</blockquote>
<ul>
<li>self-alignment through principle-driven reasoning</li>
<li>self-training to enhance problem-solving abilities</li>
<li>self-correction &amp; improvement using self-generated data</li>
<li>LLMs refine reasoning through self-generated augmented answers</li>
<li><a href="https://arxiv.org/pdf/2401.10020">self-rewarding</a></li>
</ul>
</li>
</ul>
<h1 id="3-self-evolved-reward-learning-for-llms">3. Self-Evolved Reward learning for LLMs</h1>
<ul>
<li><p>SER을 통해 RM이 스스로의 예측만으로 학습함으로써 iteratively improve itself하게 됨.</p>
</li>
<li><p>간략한 순서는 아래와 같음</p>
<ol>
<li>RM을 작은 human-annotated dataset으로 훈련시킴</li>
<li>RM이 self-labeling &amp; iterative retraining를 통해 evolve됨.</li>
<li>enhanced RM이 PPO를 통해 LLM 훈련시키는 데 사용</li>
</ol>
<p>보다 자세한 과정과 구성요소는 아래에서 설명하겠습니다. </p>
<h2 id="31-overview">3.1 Overview</h2>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/709fc1e5-e09e-424f-b13b-45a6bcd839b2/image.png" alt=""></p>
<ul>
<li>RM training은 아래의 iterative steps로 이루어짐. </li>
</ul>
<ol>
<li><p>self-label with Reward Model
(1) RM이 small set of human-annotated data로 훈련 
(2) RM이 unlabeled data에 self-labeling</p>
</li>
<li><p>Identify the learning status of RM &amp; select high-confidence data
(1) RM이 <em>good/bad answer을 구별_하거나 _유사한 답변 사이 차이점을 증폭시키는</em> 능력을 평가.
(2)평가를 기반으로 데이터 선정</p>
</li>
<li><p>Retrain the RM with pairwise loss
필터링 후, 선정된 데이터들을 pairwise loss와 함께 RM을 재훈련시키는 데 사용됨.
→ 답변의 질을 이해하는 능력 향상</p>
</li>
</ol>
<p>위와 같이 RM training 이후 RM이 PPO를 통해 LLM을 훈련시키는 데 사용됨.</p>
<ul>
<li>2(1)에서 RM의 learning state를 구별하는 이유 3가지<ol>
<li>Targeted skill development<ul>
<li>다른 learning statuses를 인식 → RM이 특정 skill set에 집중</li>
<li>초반: good vs. bad answers 구별</li>
<li>중후반: refine with 미묘한 차이</li>
</ul>
</li>
<li>Adaptive data filtering<ul>
<li>현재 learning status에 의해 data filtering → 모델이 더 적절한 데이터를 통해 훈련</li>
</ul>
</li>
<li>Improved self-evaluation<ul>
<li>learning status를 모니터링하며 RM이 learning focus를 결정 </li>
</ul>
</li>
</ol>
</li>
</ul>
<h3 id="step-1-self-label-with-reward-model">Step 1. Self-label with reward model</h3>
<ul>
<li>human-annotated data로 학습시켜 얻은 seed RM 기반</li>
<li>unlabeled data(question, answer)에 대해 reward score 예측
$r_i = RM(Q_i, A_i)$</li>
</ul>
<h3 id="step-2-identify-the-learning-status-of-the-reward-model-and-select-high-confidence-data">Step 2. Identify the learning status of the reward model and select high-confidence data</h3>
<ul>
<li><p>self-labeled dataset은 이렇게 구성됨
$$
D_{\text{train}} = {, (Q_i, A_i^{1}, A_i^{2}) }_{i=1}^{N}
$$</p>
</li>
<li><p>RM이 $A_i^{1}, A_i^{2}$이 얼마나 좋은지에 해당하는 가능성을 제시
➡️ $p_i^{1}, p_i^{2}$</p>
</li>
<li><p>learnig status S는 $p_i^{1}, p_i^{2}$ 의 차이를 이용해 결정됨. </p>
</li>
</ul>
<p>$$
∆_i = |p_i^{1}- p_i^{2}|
$$
<br></p>
<p>$$
S = \begin{cases}
\text{Status}<em>{1}, &amp;
  \text{if }\bigl(p_i^{1} &gt; \tau</em>{\text{high}} ,\text{ and }, p_i^{2} &lt; \tau_{\text{low}}\bigr)
  \text{ or }
  \bigl(p_i^{1} &lt; \tau_{\text{low}} ,\text{ and }, p_i^{2} &gt; \tau_{\text{high}}\bigr), \[4pt]
\text{Status}<em>{2}, &amp;
  \text{else if } \Delta_i \ge \tau</em>{\Delta}, \[4pt]
\text{Stop}, &amp;
  \text{otherwise.}
\end{cases}
$$</p>
<br>
- Status 1 vs. Status 2

<table>
<thead>
<tr>
<th></th>
<th align="left">status 1</th>
<th align="right">status 2</th>
</tr>
</thead>
<tbody><tr>
<td>task</td>
<td align="left">easier task</td>
<td align="right">harder task</td>
</tr>
<tr>
<td>구별하는 것</td>
<td align="left">good vs. bad samples</td>
<td align="right">비슷한 질의 답변 사이 미묘한 변화</td>
</tr>
</tbody></table>
<br>

<h3 id="step-3-retrain-the-reward-model-with-filtered-data-using-pairwise-loss">Step 3. Retrain the reward model with filtered data using pairwise loss</h3>
<ul>
<li>data filtering strategy는 아래와 같음. 각 status 별 어떤 데이터를 고를지가 제시됨. </li>
</ul>
<p>$$
\mathcal{F}\bigl(D_{\text{unlabeled}},, S\bigr) =
\begin{cases}
\displaystyle
\Bigl{,\bigl(Q_j, A_j^{1}, A_j^{2}\bigr)\ \big|\ 
  \bigl(RM(Q_j, A_j^{1}) &gt; \tau_{\text{high}} ,\land, RM(Q_j, A_j^{2}) &lt; \tau_{\text{low}}\bigr)\
\qquad\qquad\qquad\quad
\lor\ 
  \bigl(RM(Q_j, A_j^{1}) &lt; \tau_{\text{low}} ,\land, RM(Q_j, A_j^{2}) &gt; \tau_{\text{high}}\bigr)\Bigr},
&amp;\text{if } S = \text{Status}_{1},\[6pt]</p>
<p>\displaystyle
\Bigl{,\bigl(Q_j, A_j^{1}, A_j^{2}\bigr)\ \big|\ 
  \lvert RM(Q_j, A_j^{1}) - RM(Q_j, A_j^{2}) \rvert &gt; \delta \Bigr},
&amp;\text{if } S = \text{Status}_{2},\[6pt]</p>
<p>\varnothing,
&amp;\text{if } S = \text{Stop}.
\end{cases}
$$</p>
<br>

<ul>
<li><p>위 strategy를 통해 filtering을 한 뒤 <strong>pairwise loss</strong>를 이용해 모델을 재훈련<br>$$
\mathcal{L}<em>{\text{pair}} = 
  \frac{1}{\lvert D</em>{\text{filtered}}\rvert}
  \sum_{(Q_j,,A_j^{(1)},,A_j^{(2)}) \in D_{\text{filtered}}}
  \max!\Bigl(0,; \Delta - \bigl(RM(Q_j, A_j^{(1)}) - RM(Q_j, A_j^{(2)})\bigr)\Bigr)
$$</p>
<ul>
<li><p><strong>$\Delta$</strong>: 희망하는 reward score 간 차  </p>
</li>
<li><p>$D_{filtered}=D_{filtered}^n + D_{filtered}^{n-1}$</p>
</li>
<li><p>위 step 1-step 3가 RM이 수렴할 때까지 iterative loop로 반복됨. </p>
</li>
</ul>
</li>
</ul>
<h3 id="step-4-train-the-llm-via-rl-with-self-evolved-reward-model">Step 4. Train the LLM via RL with self-evolved reward model</h3>
<p>앞선 step 1~step 3를 통해 RM을 self-evolving 시켰으니 이제 RL을 이용해 LLM을 훈련시키자!</p>
<ul>
<li>policy가 input Q에 대해 response A 생성.</li>
<li>objective는 RM에 의해 생성되 reward를 극대화하는 방향으로 policy를 최적화</li>
<li>self-evolved RM이 산출한 기대 reward를 극대화</li>
<li>policy는 clipped surrogate objective를 최대화하면서 update됨. 
$$
\mathcal{L}_{\text{PPO}}
= \mathbb{E}\Bigl[<pre><code>\min\Bigl(
  \frac{\pi_{\phi}(A \mid Q)}{\pi_{\phi_{\text{old}}}(A \mid Q)}\,A^{R},
  \;
  \operatorname{clip}\!\Bigl(
    \frac{\pi_{\phi}(A \mid Q)}{\pi_{\phi_{\text{old}}}(A \mid Q)},
    1 - \epsilon,\,
    1 + \epsilon
  \Bigr)\,A^{R}
\Bigr)</code></pre>  \Bigr]
$$<ul>
<li>$A^R$: advantage function based on reward from RM<h1 id="4-experiments">4. Experiments</h1>
<h2 id="41-reward-modeling-results">4.1 Reward modeling results</h2>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/485348b9-9811-44d7-97d5-4fa8762102b7/image.png" alt=""></p>
<h3 id="main-findings">Main findings</h3>
<blockquote>
<ol>
<li>SER consistently and effectively enhances model performance</li>
<li>SER can approach or even exceed the performance of full-scale human-labeled data</li>
</ol>
</blockquote>
<h3 id="fine-grained-analysis">Fine-grained analysis</h3>
<blockquote>
<ol>
<li>The model can iteratively enhances its performance on self-labeled data, or even if the self labeled data contains noise.</li>
<li>Similar data becomes marginally helpful after multiple iterations and may even harm the model&#39;s performance.</li>
<li>By adjusting the error reduction strategy, more diverse self-labeled data can be obtained, further enhancing the effectiveness of self-learning.</li>
<li>SER is more data and human-labor efficient than full fine-tuning.</li>
</ol>
</blockquote>
<h2 id="42-ppo-results">4.2 PPO results</h2>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/f4896c8a-5963-4f0e-b32b-8dcec73585c6/image.png" alt=""></p>
<blockquote>
<p>SER enhances the capabilities of LLMs, and the degree of enhancement is positively correlated with the performances of RMs.</p>
</blockquote>
<h1 id="5-discussion">5. Discussion</h1>
<ul>
<li><p>미래에는 more robust and autonomous method to identify learning status &amp; filter self-labeled data.</p>
</li>
<li><p>generate more diverse responses through LLMs</p>
</li>
<li><p>integrate LLMs into the entire self-evolved reward learning loop ← step 4를 loop에 합쳐서 LLM이 RM에 대한 response 생성하도록!</p>
</li>
</ul>
<hr>
<p>SER에서는 data를 filtering 하는 과정이 굉장히 중요한 것 같다.
filtering 된 data(high confidence sample)를 통해 RM을 다시 훈련시키는 과정이 반복된다. </p>
<p>그리고 최종적으로 self-evolved RM을 기반으로 PPO를 사용해 LLM을 훈련시킨다. 그래서 LLM이 high quality answer을 산출하도록 만든다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰 (6) Self-Rewarding Language Models]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-6-Self-Rewarding-Language-Models</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-6-Self-Rewarding-Language-Models</guid>
            <pubDate>Tue, 15 Jul 2025 04:28:09 GMT</pubDate>
            <description><![CDATA[<p>논문을 파도타며 리서치하다보니 찾은 논문입니다. 제가 생각하는 연구 주제의 방향성과는 약간 다르지만 인사이트를 얻을 수 있을 것 같아 선정했습니다. </p>
<p>나는 언제쯤 연구 주제를 찾을 수 있을까! (●´⌓`●)</p>
<p>지금까지 읽었던 논문들과는 달리
reward를 self로 준다는 점이 특징적입니다.</p>
<p>지금은 구조에 대해서만 간단히 설명하고 추후에 논문 전체에 대해 리뷰하겠습니다. </p>
<p>방법론의 구조는 아래와 같습니다. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/a89a6abe-e7d9-463b-a5e5-ef5bd0f71356/image.png" alt=""></p>
<p>위 구조에서 알 수 있듯이 prompt, response, reward를 생성하는 self-instruction creation과 생성된 pair를 기반으로 DPO를 진행하는 instruction following training 단계로 나누어져 있습니다. </p>
<p>각 단계는 아래와 같은 흐름으로 구성됩니다. </p>
<p><strong>Self-Instruction creation</strong></p>
<ol>
<li>Generate a new prompt</li>
<li>Generate a candidate responses</li>
<li>Evaluate candidate responses (Generate rewards)</li>
</ol>
<p><strong>Instruction following training</strong></p>
<ol>
<li><p>Preference building</p>
<ul>
<li>높은 score -&gt; chosed</li>
<li>낮은 score -&gt; rejected
➡️ (x, chosen, rejected) pair 만듦</li>
</ul>
</li>
<li><p>위 pair로 DPO 진행</p>
<ul>
<li>DPO loss를 최소화하도록 fine-tuning</li>
</ul>
</li>
<li><p>Evaluate</p>
</li>
</ol>
<p>➡️ 2-3 반복</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[연구실]연구 주제 찾기(1)]]></title>
            <link>https://velog.io/@iumiere-on/%EC%97%B0%EA%B5%AC%EC%8B%A4%EC%97%B0%EA%B5%AC-%EC%A3%BC%EC%A0%9C-%EC%B0%BE%EA%B8%B01</link>
            <guid>https://velog.io/@iumiere-on/%EC%97%B0%EA%B5%AC%EC%8B%A4%EC%97%B0%EA%B5%AC-%EC%A3%BC%EC%A0%9C-%EC%B0%BE%EA%B8%B01</guid>
            <pubDate>Tue, 15 Jul 2025 04:18:40 GMT</pubDate>
            <description><![CDATA[<p>저는 현재 AI랩실에서 학부인턴을 하고 있습니다.</p>
<p>6월 중순에 종강한 뒤부터 연구 주제를 줄곧 찾고 있었고 
아직도 찾는 중입니다...</p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/761bc65b-d58f-4b86-88fd-66686259e69d/image.png" alt=""></p>
<p>저는 어마어마한 감자지만.. </p>
<p>요즘 들어 꽤나 지쳐서...
넉두리 겸 정리를 하며 저와 비슷한 처지에 있는 분들께 조금이나마 도움이 되고자(?) </p>
<p>&#39;연구 주제 찾기&#39; 시리즈를 시작합니다.</p>
<p>연구 주제를 찾아서 연구를 시작하면 이 시리즈는 끝낼 계획입니다!</p>
<p><br><br></p>
<hr>
<br>
우선 현재 저는 RLHF분야에 관심이 있고, 이 중에서도
**Reward model에 관심이 있어서 RM의 reward와 critique** 쪽을 연구하고 싶습니다. 

<p>이와 관련해 매주 랩미팅을 하는데.. 교수님들께서(공동랩이라 교수님이 두 분 계십니다) 
 피드백을 주시고 → 다시 리서치 → 랩미팅→ 피드백......</p>
<p>이 과정을 한달 째 반복하다보니 조금 지치고 헤이해져서 벨로그에 기록하면서 주제를 찾아보려고 합니다. 
<br></p>
<h4 id="랩미팅-내용">랩미팅 내용</h4>
<p>이번주 랩미팅에서는 &#39;self-reward + self-critic&#39; 을 연구하고 싶다고 말씀드렸고</p>
<h4 id="피드백">피드백</h4>
<ul>
<li>유사한 논문이 무지 많다. </li>
<li>요즘 많이 진행되고 있는 연구이니 Related work를 더 조사해봐라 </li>
<li>논문을 보내줄테니 더 읽어봐라<br>

</li>
</ul>
<h4 id="📌-이번주-to-do"><strong>📌 이번주 TO-DO</strong></h4>
<ul>
<li><input disabled="" type="checkbox"> ✨ 논문 3편 읽기</li>
<li><input disabled="" type="checkbox"> 교수님께서 보내주신 RL 자료 읽기</li>
<li><input disabled="" type="checkbox"> Related work 조사 후 논문 읽기</li>
<li><input disabled="" type="checkbox"> 랩미팅 준비</li>
<li><input disabled="" type="checkbox"> 랩 세미나 논문 선정 및 준비</li>
</ul>
<p>그럼 이번주도 열심히 해보고 다음주에 다시 돌아오겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰 (4) Self-Generated Critiques Boost Reward Modeling
for Language Models]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-4-Self-Generated-Critiques-Boost-Reward-Modelingfor-Language-Models</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-4-Self-Generated-Critiques-Boost-Reward-Modelingfor-Language-Models</guid>
            <pubDate>Mon, 07 Jul 2025 15:43:35 GMT</pubDate>
            <description><![CDATA[<p>오늘 다룰 논문은 <a href="https://arxiv.org/pdf/2411.16646">Self-Generated Critiques Boost Reward Modeling
for Language Models</a>입니다. </p>
<p>2025년 4월에 NAACL에 publish된 논문입니다. 앞선 논문 리뷰와 비슷하게 제가 연구하려는 방향성과 유사해서 정리하게 되었습니다. </p>
<h1 id="abstract">Abstract</h1>
<ul>
<li>Reward modeling은 RLHF에서 필수적임.  </li>
<li>critiques와 scalar rewards 산출이 reward model의 성능을 높이며, 여기서 이 논문이 시작됨. </li>
<li>해당 논문에서는 <strong>Critic-RM</strong>을 제시  <ul>
<li>scalar reward에 기반한 선호 예측을 위해 reward model을 train하는데 critique 사용.  </li>
<li>2 stages로 구성: 1. generating critiques 2. filtering critiques<br>  

</li>
</ul>
</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<ul>
<li>LLM이 ciritique을 generate &amp; refine하는 능력 활용해 Critic-RM 제시. </li>
<li>Critic-RM은 critique quality &amp; reward prediction accuracy를 모두 높였다는 점에서 의의.<br>

</li>
</ul>
<h1 id="1-introduction">1. Introduction</h1>
<h3 id="기존-rlhf에서-reward-model의-한계-및-해결방안">기존 RLHF에서 reward model의 한계 및 해결방안</h3>
<ul>
<li><p>RLHF의 메인은 reward model(RM)</p>
<ul>
<li>RM은 policy LLM을 이용해 training동안 최적화 방향 정의. </li>
</ul>
</li>
<li><p>기존 Rewarding 과정의 한계</p>
<ul>
<li><p>표준 RM은 preference pairs을 이용해 훈련 -&gt; 각 응답에 대한 single scalar score 산출</p>
</li>
<li><p>이 과정에서 산출된 <em>scalar score은 해석하기 어려움 + LLM의 language modeling 능력도 활용X</em>
=&gt; 문제점: less data-efficient &amp; robustness issues 발생</p>
</li>
<li><p>이러한 RM의 한계를 극복하고자 <strong>critique</strong> 도입!</p>
</li>
<li><p>critique 도입의 장점:</p>
<ul>
<li>해석 가능성 높임 &amp;  구조화 가능</li>
</ul>
</li>
<li><p>critique 도입의 challenge:</p>
</li>
<li><p><em>1. Conflicting objectives*</em>
reward model이 산출하는 scalar score과 critique을 합산하는 과정에서 발생하는 충돌 문제 </p>
</li>
<li><p><em>2. Evaluator limitations*</em>
기존이 LM은 제한적인데 이걸 평가에 사용하는 건 효과적이지 않음.</p>
</li>
</ul>
</li>
</ul>
<h3 id="introducing-critic-rm">Introducing Critic-RM</h3>
<ul>
<li><p>위 challenge를 극복하기 위해 <strong>Critic-RM</strong> 제시</p>
</li>
<li><p>Critic-RM은 instruction-finetuned LLM을 backbone으로 활용해 score을 가진 candidate critiques를 여러 개 산출. </p>
</li>
<li><p>good quality를 유지하기 위해 아래 라벨 및 방법 사용*</p>
<ol>
<li>human-annotated preference labels 이용</li>
<li>summarization &amp; ranking*</li>
</ol>
</li>
<li><p>앞선 conflicting objectives 문제를 해결하기 위한 방법</p>
<ul>
<li>모델이 초반에는 <em>critique modeling loss</em>에만 집중 -&gt; 점차 <em>response와 critique을 기반으로 reward를 예측하는 것</em>으로 초점 옮김. </li>
</ul>
<p>=&gt; RM이 <strong>high-quality critique generation &amp; accurate reward prediction</strong> 모두 잘하게 됨. </p>
</li>
</ul>
<Br>

<h1 id="2-related-work">2. Related work</h1>
<pre><code>-  (사담)연구 주제를 계속 찾는 중이다보니.. 해당 논문 자체도 중요한데, 해당 논문에 명시된 related work 역시 상당히 중요하다는 것을 알았습니다. 개인적으로는 배경지식을 가져갈 뿐만 아니라 해당 논문이 인사이트를 얻은 부분, 기존의 한계가 적힌 부분이라 중요하다고 생각합니다. </code></pre><h2 id="reward-models">Reward Models</h2>
<ul>
<li><p>최근의 generative reward modeling의 경우, RM을 critique generation하도록 훈련시키지 않음. teacher model이 critique을 산출할 뿐.</p>
<ul>
<li>본 논문과 유사한 연구의 경우(Zhang et al. (2024); Ankner et al. (2024); Mahan et al. (2024))</li>
<li>training을 위해 teacher model이 high-quality critique을 생성하는데만 의존 </li>
</ul>
</li>
<li><blockquote>
<p>teacher model이 없는 경우 solution을 제공하지x.</p>
</blockquote>
</li>
<li><blockquote>
<p>critique의 quality를 향상시키는 데에도 제한적. </p>
</blockquote>
</li>
</ul>
<h2 id="llm-as-a-judge-and-critique-models">LLM-as-a-judge and Critique Models</h2>
<ul>
<li>LLM이 human evaluation의 대안으로 사용됨.</li>
<li>off-the-shelf LLMS을 평가에 사용하면 risks of bias + being misled의 위험이 있음. </li>
<li><blockquote>
<p>최근 연구에서는 high-quality response pairs를 통해 이러한 위험을 해결하고자 함. (LLM 자체에 의존하지 않겠다는 의미인 듯)</p>
</blockquote>
</li>
</ul>
<h2 id="self-alignment-techniques">Self-alignment Techniques</h2>
<ul>
<li>Aligning LLMs with human preference 과정에서 human annotation 대신 LLM을 활용. </li>
<li>본 논문에서는  LLM이 아닌 human-annotated preference pairs를 reward modeling 훈련에 사용. </li>
</ul>
<h1 id="3-methodology">3. Methodology</h1>
<h2 id="31-preliminaries">3.1 Preliminaries</h2>
<ul>
<li><p>pairwise preferences를 모델링하는 learning objective로, Bradley-Terry model 이용</p>
<p>$$
p(y^+ ≻  y^− |  x)=\frac{exp (r (x, y^+))}
{exp (r (x, y^+)) + exp (r (x, y^−))}
$$</p>
<br>
- x: prompt, y: response, r: reward
</li>
<li><p>reward model  $r_ψ$ 의 loss function
$$ 
ℓ<em>{rm}(ψ) = −E</em>{(x,y^+,y^−)∼D} log(σ(r_ψ(x, y^+)− r_ψ(x, y^-))
$$</p>
<ul>
<li>일반적인 reward model은 prompt와 response만을 보고 학습함.</li>
</ul>
</li>
<li><p>overview of Critic-RM
   <img src="https://velog.velcdn.com/images/iumiere-on/post/e5284cdf-cd4e-44f0-8a22-148db76797e3/image.png" alt=""></p>
<p>Critic-RM은 3단계를 거칩니다. </p>
<blockquote>
<ol>
<li>각 prompt-response pair에 대해 후보 critique 생성</li>
<li>Critique filtering - noisy한 critique을 줄이고, 더 정확한 critique을 증가시킴.</li>
<li>joint training - model에게 critique generation &amp; reward modeling 동시에 훈련시킴.</li>
</ol>
</blockquote>
<p>위 단계에 대해서는 뒤에서 더 자세히 다뤄보겠습니다!</p>
</li>
</ul>
<h2 id="32-critique-augmented-reward-model-training">3.2 Critique-augmented Reward Model training</h2>
<p>$z^+, z^-$를 각각 chosen/rejected response에 대한 critique이라고 할 때 아래와 같은 수식이 성립한다. </p>
<p>  $$
  p(y^+ ≻ y^− | x) = \Sigma_{z^+,z^−}p(y^+ ≻ y^−, z^+, z^− | x)=\Sigma_{z^+,z^−}p(y^+ ≻ y^− | z^+, z^−, x) · p^∗(z^+ | y^+, x) · p^∗(z^− | y^−, x)
  $$
  <br></p>
<p>  <strong>$p^{\ast}(\cdot \mid y, x)$가 critique의 기존 분포라고 할 때,  본 논문에서는 critic generation model인 $g_{\phi}$가 근사 분포인 $q_{\phi}$를 생성하도록 훈련시킵니다.</strong></p>
<p> 이때 Jensen&#39;s Inequality를 적용하면 아래와 같은 수식이 성립합니다. </p>
<p>$$
\begin{aligned}
\log p\bigl(y^{+} \succ y^{-} \mid x\bigr)
&amp;= \log
   \mathbb{E}<em>{q</em>{\phi}(z^{+}!\mid y^{+},x),,,q_{\phi}(z^{-}!\mid y^{-},x)}
   \Bigl[
      \tfrac{
        p\bigl(y^{+} \succ y^{-}, z^{+}, z^{-} \mid x\bigr)
      }{
        q_{\phi}(z^{+}!\mid y^{+},x),
        q_{\phi}(z^{-}!\mid y^{-},x)
      }
   \Bigr] \[6pt]
&amp;\ge
   \mathbb{E}<em>{q</em>{\phi}(z^{+}!\mid y^{+},x),,,q_{\phi}(z^{-}!\mid y^{-},x)}
   \Bigl[
      \log
      \tfrac{
        p\bigl(y^{+} \succ y^{-}, z^{+}, z^{-} \mid x\bigr)
      }{
        q_{\phi}(z^{+}!\mid y^{+},x),
        q_{\phi}(z^{-}!\mid y^{-},x)
      }
   \Bigr].
\end{aligned}
$$</p>
<h3 id="321-critique-augmented-reward-prediction">3.2.1 Critique-augmented Reward Prediction</h3>
<ul>
<li>reward model $r_\psi$가 critique와 함께 preference(선호)를 학습하기 위해서
➡️ <strong>response 뒤에 critique z를 붙여서(augment) [y;z] 형태를 reward model에 입력으로 줌.</strong> </li>
</ul>
<p>$$
ℓ_r(x, y^+, y^−, z^+, z^−) = − log p(y^+ ≻ y^−, z^+, z^− | x)= − log  p(r_ψ(x, [y^+; z^+]) &gt; r_ψ(x, [y^−; z^−]))
$$</p>
<ul>
<li>위의 reward loss function을 사용하면 reward model이 response와 critique에 기반해 reward를 생성하도록 학습시킬 수 있음. </li>
</ul>
<h3 id="322-critique-generation--filtering">3.2.2 Critique generation &amp; Filtering</h3>
<p>1~4단계로 이루어집니다!</p>
<h4 id="1-critique-generation">1. critique generation</h4>
<ul>
<li>LLM $M_\theta$를 프롬프팅 → N개의 후보 critique 산출</li>
<li>이때 아래 수식에 기반해 LLM-as-a-judge 파이프라인 사용
$$(\tilde{z}<em>i, s_i)</em>{i=1}^N \sim g_{\phi}(x,y)$$<ul>
<li>z: 생성된 critique, s: 1~10점까지 매긴 점수</li>
</ul>
</li>
</ul>
<h4 id="2-insance-level-critique-filtering">2. Insance-level Critique filtering</h4>
<ul>
<li>아래 영역에 해당하는 인스턴스만 남김
$$
D_{sub} = {(x, y^+, y^-)|\tilde{s}(x,y^+)&gt;\tilde{s}(x, y^-)}
$$<ul>
<li>$\tilde{s}(x,y^+) = \Sigma_{i=1}^Ns_i^+/N$</li>
<li>$\tilde{s}(x,y^-) = \Sigma_{i=1}^Ns_i^-/N$</li>
</ul>
</li>
</ul>
<h4 id="3-quality-aware-critique-refinement">3. Quality-aware Critique Refinement</h4>
<ul>
<li><p>2번 단계에서는 prompt와 response 위주로 filtering 했으니 이제는 critique의 질을 개선해보겠습니다.</p>
</li>
<li><p>refinement는 Summarization-based refinement와 Ranking-based refinement로 나뉩니다. </p>
</li>
<li><p><strong>Summarization-based refinement</strong>
$$
Z_{\mathrm{summ}}
= {z_i}<em>{i=1}^{K}
\sim
g</em>{\phi}\bigl(x,y,\prod_{j=1}^{N}\tilde{z}_j\bigr)
$$</p>
<ul>
<li><strong>Ranking-based refinement</strong>
$$
Z_{\mathrm{rank}}
= {,z_i}<em>{i=1}^{K}
= \mathrm{Top}\text{-}K!\bigl({\tilde{z}_i}</em>{i=1}^{N}\bigr)
$$</li>
</ul>
</li>
</ul>
<h4 id="4-final-loss-for-critique-generation">4. Final loss for critique generation</h4>
<ul>
<li><p>지금까지 critique을 개선시켜 training set인 $D_{sub}$을 augment 했습니다. 이제 z를 이용해 <strong>oracle distribution $p^*$을 근사하는 $q_\phi$</strong>를 구해보겠습니다!
$$
\mathcal{D}_{\mathrm{sub}}
= \bigl{,\bigl(x,,y^{+},,y^{-},,Z^{+},,Z^{-}\bigr)\bigr}.
$$</p>
</li>
<li><p>KL divergence를 이용한 learning objective
$$
\begin{aligned}
\ell_{c}(Z; x, y)
&amp;= D_{\mathrm{KL}}!\bigl(p^{<em>}(z \mid y_i, x_i),\big|,q_{\phi}(z \mid y_i, x_i)\bigr) \[4pt]
&amp;= \mathbb{E}_{z\sim p^{</em>}(z\mid y_i,x_i)}\bigl[\log p^{*}(z\mid y_i,x_i)-\log q_{\phi}(z\mid y_i,x_i)\bigr] \[4pt]
&amp;= -\tfrac{1}{K}\sum_{z\in Z}\log q_{\phi}(z\mid y,x);+;\mathrm{const.}
\end{aligned}
$$</p>
<h3 id="323-joint-learning-of-critique-generation-and-reward-modeling">3.2.3 Joint Learning of Critique generation and reward modeling</h3>
</li>
<li><p><strong>reward modeling loss와 critique generation loss</strong>를 한 데 합치려고 합니다.</p>
</li>
<li><p>이때 발생하는 문제!! <br></p>
<p>critique generation을 위해서는 $g_\phi$기 다양한 z로부터 finetuning되면 좋음. 
↔ reward model $r_\psi$는 1라운드 이상 돌면 오버피팅 이슈. </p>
<p>➡️ 두 loss 간 balancing이 필요. </p>
<p>$$
\begin{aligned}
\mathcal{L}(\phi,\psi)
&amp;= \mathbb{E}<em>{(x,y^{+},y^{-},z^{+},z^{-})\in \mathcal{D}</em>{\mathrm{sub}}}
 \Bigl[
   \lambda(t),\ell_{c}(\phi)</p>
<ul>
<li><p>\bigl(1-\lambda(t)\bigr),\ell_{r}(\psi)
\Bigr], \[6pt]
\lambda(t)
&amp;=
\begin{cases}
1, &amp; 0 &lt; t &lt; (K-1)T, \
1 - \beta,\dfrac{t - (K-1)T}{T}, &amp; (K-1)T &lt; t &lt; K T.
\end{cases}
\end{aligned}
$$</p>
<ul>
<li>T: 1 epoch에서 전체 training steps의 수를 의미.</li>
<li>위와 같은 learning objective를 사용함으로써 training 초기에는 critique generation에, 그 이후에는 reward model의 overfitting 완화에 집중할 수 있음.</li>
</ul>
</li>
</ul>
<p><br><br></p>
</li>
</ul>
<hr>
<p>  experiment 부분 역시 중요하지만 지금은 논문 양치기를 하고 있어서,, 빠른 시일 내로 experiments까지 다뤄보도록 하겠습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰(5) U-Net: Convolutional Networks for Biomedical
Image Segmentation]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B05-U-Net-Convolutional-Networks-for-BiomedicalImage-Segmentation</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B05-U-Net-Convolutional-Networks-for-BiomedicalImage-Segmentation</guid>
            <pubDate>Mon, 07 Jul 2025 15:41:12 GMT</pubDate>
            <description><![CDATA[<p>이번주 CV스터디 논문은 U-Net입니다. Segment의 기초 논문 중 하나이기에 꼼꼼하게 다뤄보도록 하겠습니다!</p>
<h1 id="abstract">Abstract</h1>
<ul>
<li>본 논문에서는 data augmentation에 기반한 네트워크와 training strategy를 제시함</li>
<li>구조는 contracting path &amp; symmetric expanding path로 구성됨.<ul>
<li>contracting path: context를 캐치</li>
<li>symmetric expanding path: 정확한 localization 가능케 함</li>
</ul>
</li>
<li>해당 모델로 ISBI challenge 우승하는 등 좋은 성과</li>
<li>해당 모델은 빠름</li>
</ul>
<h1 id="5-conclusion">5. Conclusion</h1>
<ul>
<li>u-net 구조는 biomedical segmentation 분야에서 좋은 성과 얻음.</li>
<li>유연한 분해(deformation) 로 data augmentation 한 덕분에 적은 이미지 &amp; 적은 시간으로 training함. </li>
<li>u-net은 더 많은 태스크에도 쉽게 적용 가능. </li>
</ul>
<br>

<h1 id="1-introduction">1. Introduction</h1>
<ul>
<li>large network를 1M 크기의 ImageNet dataset에 학습시킨 Krizhevsky et al.부터 CNN이 인식 태스크에서 두각을 나타냄. 이때부터 크고 깊은 네트워크가 훈련됨. </li>
<li>전형적으로 CNN은 분류 태스크에서 사용됐음. 특히 biomedical image의 경우는 localization을 포함해야 해서 pixel 단위의 label이 필요함. </li>
<li><blockquote>
<p> Ciresan et al. -  &lt; DNN &gt; 등장: </p>
</blockquote>
<ul>
<li>동작 매커니즘: <ul>
<li>network를 sliding-window로 학습시킴. </li>
<li>input: pixel 근처 local region(patch)</li>
<li>output: each pixel의 class label</li>
</ul>
</li>
<li>단점:<ul>
<li>중복되는 patches 때문에 학습에 오랜 시간 소요</li>
<li>localization accuracy와 context 둘 중 하나는 포기해야 함.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="u-net">U-Net</h3>
<ul>
<li><p>main idea:</p>
<ul>
<li>contract network by successive layers 
= pooling operators -&gt; upsampling operator로 교체</li>
</ul>
<p>➡️ ouput의 해상도 높임</p>
<ul>
<li>localize하기 위해, contracting path로부터의 고해상도 features들이 upsampled output과 결합됨. 
➡️ successive convolution layer가 이 정보에 기반해 더 정확한 output 도출</li>
</ul>
</li>
<li><p>U-Net에서의 주요한 변화: upsampling part에 있는 많은 feature channels 을 통해 context 정보가 고해상도 layer로 전달됨.
➡️ expansive path가 contracting path에 비대칭적이게 됨 &amp; u-shaped 구조 만듦.</p>
</li>
</ul>
<h3 id="data-augmentation">data augmentation</h3>
<ul>
<li>태스크 특성 상 가용가능한 데이터가 적음 -&gt; image를 유연하게 분해해 data augmentation</li>
</ul>
<h3 id="challenge와-application">challenge와 application</h3>
<ul>
<li>challenge: 서로 겹쳐있는 세포들의 경계는 분류하기 어려움😢
➡️ 가중치를 둬서 세포 간 경계를 정확히 찾도록 학습</li>
<li>application: u-net을 다양한 biomedical segmentation 문제에 적용</li>
</ul>
<h1 id="2-network-architecture">2. Network Architecture</h1>
<p>   <img src="https://velog.velcdn.com/images/iumiere-on/post/d5071270-4ef5-4b85-9b93-2c55f906e81a/image.png" alt=""></p>
<ul>
<li><p>왼쪽: contracting path, 오른쪽: expansive path</p>
</li>
<li><p>contracting path: 전형적인 CNN 구조.</p>
<ul>
<li>2개의 3x3 convolutions -&gt; ReLU<ul>
<li>2x2 max pooling opeation with stride 2</li>
<li>downsampling할 때마다 feature channel 수는 2배가 됨.</li>
</ul>
</li>
</ul>
</li>
<li><p>expansive path: feature map의 upsampling으로 구성</p>
<ul>
<li>2x2 convolution </li>
<li>2개의 3x3 convolution -&gt; ReLU</li>
<li>1x1 convolution: class에 대한 64개의 구성 feature vector</li>
</ul>
</li>
</ul>
<h1 id="3-training">3. Training</h1>
<ul>
<li><p>overhead를 줄이고 GPU를 최대한 사용하기 위해 large input tiles over large batch size 사용 &lt;- 이렇게 하면 이미지 한장 당 batch를 줄일 수 있음!</p>
</li>
<li><p>energy function: feature map 위에서 pixel 단위 softmax로 계산됨. </p>
</li>
<li><p>softmax:
$$
p_k(x) = exp(a_k(x))/\Sigma_{k&#39;=1}^K exp(a_{k&#39;} (x))
$$</p>
<ul>
<li>$a_k(x)$: activation in feature channel k at pixel position x</li>
<li>K: 클래스의 수</li>
</ul>
</li>
<li><p>CE penalizes at each position
$$
E = \Sigma_{x∈Ω}w(x) log(p_{l(x)}(x))
$$</p>
<ul>
<li>l: 각 픽셀 당 라벨</li>
<li>w: a weighted map</li>
</ul>
</li>
<li><p>separation border에서 사용되는 weight map
$$
w(x) = w_c(x) + w_0 · \exp!\left(-\frac{(d_{1}(\mathbf{x}) + d_{2}(\mathbf{x}))^{2}}{2\sigma^{2}}\right)
$$</p>
<ul>
<li>$d_1$: 가장 가까운 세포의 경계까지의 거리</li>
<li>$d_2$: 두번째로 가까운 세포의 경계까지의 거리</li>
</ul>
</li>
<li><p>U-Net의 initialization: 가우시안 분포로부터 get</p>
</li>
</ul>
<h2 id="31-data-augmentation">3.1 Data augmentation</h2>
<ul>
<li>매우 작은 이미지의 경우: shift &amp; rotation invariance 필요</li>
<li>random elastic deformation 이 augmentation의 핵심!<ul>
<li>3x3 그리드에 위치한 백터에 랜덤으로 smooth deformation 적용</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰(3) You Only Look Once: Unified, Real-Time Object Detection]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B03-You-Only-Look-Once-Unified-Real-Time-Object-Detection</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B03-You-Only-Look-Once-Unified-Real-Time-Object-Detection</guid>
            <pubDate>Tue, 01 Jul 2025 02:13:52 GMT</pubDate>
            <description><![CDATA[<p>오늘은 아주 유우명한 객체 탐지(object detection) 분야의 논문을 읽어보겠습니다. 바로 <a href="https://arxiv.org/pdf/1506.02640">YOLO</a>입니다!</p>
<p>오늘도 Abstract부터 읽어보겠습니다.</p>
<h1 id="abstract">Abstract</h1>
<ul>
<li>논문에서는 &quot;<strong>한 개의 신경망이 한 평가에서 전체 이미지를 기반으로</strong> bounding boxes를 예측 ➡️ 클래스 확률 예측&quot;하는 YOLO를 제시.</li>
<li>YOLO의 속도는 매우 빠름</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<ul>
<li><p>YOLO: object detecion을 위해 통합된 모델</p>
</li>
<li><p>장점:   </p>
<ul>
<li>구성이 간단</li>
<li>전체 이미지에 바로 훈련시킬 수 있음.</li>
<li>행동을 감지에 상응하는 손실 함수에 바로 훈련시킬 수 있음</li>
</ul>
</li>
<li><p><strong>Fast YOLO</strong>: </p>
<ul>
<li>문학 분야에서 범용 목적의 가장 빠른 객체 탐지 모델. </li>
</ul>
</li>
<li><p><strong>YOLO</strong>: </p>
<ul>
<li>논문 나올 당시 실시간 객체 탐지에서 SOTA 달성</li>
<li>새로운 도메인에 일반화 잘함. </li>
</ul>
</li>
</ul>
<h1 id="introduction">Introduction</h1>
<ul>
<li><p>current detection system: </p>
<ul>
<li><strong>classifier</strong> 사용</li>
<li><strong>DPM</strong> 사용: DPM은 classifier가 전체 이미지에서 고르게 동작하는 <em>sliding window 접근법</em> 사용</li>
<li>R-CNN </li>
</ul>
</li>
<li><p>YOLO: &quot;우리는 <em>하나의 회귀 문제를 이용해</em> 기존 모델과 달리 objects가 현재 존재하는 곳만 예상한다.&quot;
<img src="https://velog.velcdn.com/images/iumiere-on/post/cab3a5ff-41fd-4ea8-8aab-1fda41415879/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>YOLO의 장점<ol>
<li>YOLO는 매우 빠르다. <ul>
<li>심지어 실시간 영상에서도 처리할 수 있다. </li>
<li>다른 실시간 영상 시스템보다 평균 정확도가 두 배 이상 높은 성과까지!</li>
</ul>
</li>
<li>YOLO는 이미지 전체를 전역적으로 예측한다. </li>
<li>YOLO는 객체의 일반적인 특성을 학습한다. </li>
</ol>
</li>
</ul>
<h1 id="unified-detection">Unified Detection</h1>
<p>  분리된 객체 탐지 요소를 하나의 신경망으로 통합시켰다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰(2) Critique-out-LOUD Reward Models]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B02-Critique-out-LOUD-Reward-Models</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B02-Critique-out-LOUD-Reward-Models</guid>
            <pubDate>Tue, 24 Jun 2025 07:40:27 GMT</pubDate>
            <description><![CDATA[<p>오늘 리뷰할 논문은 <a href="https://arxiv.org/pdf/2408.11791">Critique-out-LOUD Reward Models</a>입니다! </p>
<p>저번에 읽었던 논문 <a href="https://arxiv.org/pdf/2502.10391">MM-RLHF: The Next Step Forward in Multimodal LLM Alignment</a>과 Reward Modeling 측면에서 유사합니다. 따라서 오늘은 논문을 리뷰하면서 MM-RLHF-Reward-Model과의 차이점도 함께 분석해보겠습니다. </p>
<p><em>한국어로 해석하는 것보다 영어가 더 직관적일 경우에는 논문을 그대로 차용/약간 수정한 형태로 적어두었습니다:)</em></p>
<h1 id="abstract">Abstract</h1>
<p>기존 RLHF에서 사용하던 Reward models는 LLM의 생성능력을 활용하지 않아서 추론 과정이 빈약
→ RM이 응답의 질에 대해 명시적으로 추론하게 만들기 위해 <em>Critique-out-Loud reward model</em> 도입</p>
<ul>
<li>간단한 흐름: </li>
</ul>
<ol>
<li>CLoud RM generates a <strong>natural language critiques</strong> of the response </li>
<li><strong>Critique</strong> is used to predict a <strong>scalar reward</strong> for the quality of the response. </li>
</ol>
<ul>
<li>demonstration<ul>
<li>Llama-3-8B&amp;Llama-3-70B로 진행. </li>
<li>RewardBench에서 이전 RM보다 높은 정확도 얻음.</li>
<li>Pareto improvement</li>
</ul>
</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<ul>
<li>Through <strong>generating critiques</strong>, CLoud reward models can explicitly reason about the quality of a response</li>
<li>improve average pairwise preference modeling accuracy</li>
<li>performing Best-of-N decoding with CLoud reward models is a Pareto improvement over classic reward models for ArenaHard win-rate.</li>
<li>CLoud reward models only benefit from self-consistency on reasoning problems and demonstrate that self-consistency is predominantly useful when assigning rewards to responses with short reasoning horizons</li>
</ul>
<h1 id="introduction">Introduction</h1>
<p>기존 reward model은 LLM 기반의 prompt와 response의 분류기였음. 
→ Reward modeling 과정서 LLM의 LM head가 사용되지 않음
→ RM can&#39;t explicitly reason about the quality of the response in CoT.</p>
<p>➡️ Reward modeling 과정서 비판적인 추론 능력 부족</p>
<p>So, 이 논문에서는 CLould reward model을 제시함으로서
→ LLM의 language generation 으로 critique을 산출
→ reward model의 성능을 높이고자 함. </p>
<h1 id="methods">Methods</h1>
<p>기존 RM 방법론 설명 → Cloud RM 설명 순으로 다루겠습니다. </p>
<h2 id="21-classical-reward-models">2.1 Classical Reward models</h2>
<p>$L_{RM}(θ<em>B, θ_R, D) = −E(x,y^−,y^+)</em>{∼D} [log(σ(r_{θ<em>B,θ_R} (x, y^+) − r</em>{θ_B,θ_R} (x, y^−)))]$
       </p>
<ul>
<li>user prompt x, assistant response y로 이루어진 데이터셋 D를 기반으로 training</li>
<li>base model과 Reward head의 파라미터로 이루어진 Reward model에서 preferred response에는 rejected response보다 높은 reward를 주도록 train시킴. </li>
</ul>
<h2 id="22-cloud-reward-models">2.2 CLoud reward models</h2>
<h3 id="reward-model-구성">reward model 구성</h3>
<ul>
<li><p>parameters:  $θ = (θ<em>B, θ</em>{LM}, θ_R)$</p>
<ul>
<li>기존 RM에 LLM의 language modeling head의 파라미터 $θ_{LM}$가 추가됨.</li>
</ul>
</li>
<li><p>critique: $\hat{c}\ ∼ p(∗|x, y; θ<em>B; θ</em>{LM})$</p>
<ul>
<li>prompt x와 assistant response y를 기반으로 산출한critique</li>
</ul>
</li>
<li><p>reward: $\hat{R};= r_{θ_B;θ_R} (x, y, \hat{c};)$</p>
<br>
### Training CLoud reward models
![](https://velog.velcdn.com/images/iumiere-on/post/0b288c2a-ad9d-4ba6-b7fd-89a2ff82e4bb/image.png)
</li>
<li><p>train과정의 목표: oracle critique(human critique on response)을 self-generated critique으로 바꾸기. </p>
</li>
</ul>
<blockquote>
<h4 id="훈련-단계">훈련 단계</h4>
</blockquote>
<ol>
<li>base model&amp;LM head를 oracle critiques를 기반으로 supervised finetuning 진행 → critique 생성</li>
<li>데이터셋 내 oracle critique을 finetuned model이 생성한 critique으로 교체</li>
<li>2에서 교체한 critique으로 reward model을 train</li>
</ol>
<ul>
<li>objectives 정리</li>
<li><em>1. $L_{rm}$*</em>
$$
   L_{RM}(θ<em>B, θ_R, D) = −E(x,y^−,y^+,c^−,c^+)</em>{∼D}    [log(σ(r_{θ<em>B,θ_R} (x, y^+, c^+) − r</em>{θ_B,θ_R}(x, y^−, c^−)))] 
   $$<ul>
<li>preferred data(preferred prompt, response, human critique) - rejected data<br>
+ Cross Entropy 기반의 수식이고 preferred data와 rejected data의 차가 클수록 → loss값이 작아짐. 
</li>
</ul>
</li>
</ul>
<ol start="2">
<li><p>$L_{SFT}$</p>
<p>$$
L_{SFT}(\theta_B,\theta_{LM},D)
= -\mathbb{E}<em>{(x,y^{-},y^{+},c^{-},c^{+})\sim D}
  \Bigl[\sum</em>{c_t^{-}\in c^{-}}\log p(c_t^{-}\mid\cdots)\Bigr]
$$</p>
<ul>
<li>supervised finetuning에서는 preferred / rejected data 기반<br>loss의 <strong>평균</strong>을 loss로 가져감</li>
<li>$L_{SFT}$를 최소화하는 critique 생성</li>
<li>$L_{SFT}$를 최소화하는 base model &amp; LM head 파라미터($$) 없음</li>
</ul>
</li>
<li><p>$L_{CLoud}$
$$
L_{CLoud}(θ<em>B, θ</em>{LM}, θ<em>R, D) = L</em>{RM}(θ<em>B, θ_R,    D) + λ · L</em>{SFT} (θ<em>B, θ</em>{LM}, D)
$$  </p>
<ul>
<li>RM와 SFT의 loss를 합한 게 최종 objective.</li>
<li>하이퍼파라미터 $\lambda$를 통해 supervised finetuning model의 강도 조정. </li>
</ul>
</li>
</ol>
<h1 id="3-results">3. Results</h1>
<h2 id="31-setup">3.1 Setup</h2>
<h3 id="data">Data</h3>
<p>training에 사용하기 위해 가공 단계를 거치는데, 흐름은 아래와 같습니다. </p>
<ol>
<li><p><strong>&lt; merge &gt;</strong></p>
<ul>
<li>UltraFeedback + UltraInteract → <strong>Ultra dataset</strong>  <ul>
<li>종류가 다양한 데이터셋 2개를 하나로 합침</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>&lt; replace &gt;</strong></p>
<ul>
<li>chosen &amp; rejected response in Ultra dataset → _Llama-3-8B-Instruct_가 생성한 responses로 교체 → <strong>UltraLlama</strong></li>
</ul>
</li>
<li><p><strong>&lt; label &gt;</strong></p>
<ul>
<li>_Llama-3.1-405B-Instruct_가 chosen &amp; rejected response에 대해 labeling  <ul>
<li>prompt: ArenaHard의 pairwise judgement prompt 사용</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>&lt; generate oracle critique &gt;</strong></p>
<ul>
<li>_Llama-3.1-405B-Instruct_가 chosen &amp; rejected responses에 대해 <strong>oracle critique</strong> 생성</li>
<li>critique: response가 query에 대해 얼마나 잘 응답했는지를 평가 </li>
<li>여기서 질문! 왜 기존 데이터셋에 있는 critique를 안 쓰고 새로 생성할까?
 <del>글쎄</del> 왜일까<del>요</del>?</li>
</ul>
</li>
</ol>
<h3 id="evaluation">Evaluation</h3>
<p>평가요소는 2가지. </p>
<ol>
<li>pairwise preference classification accuracy</li>
<li>Best-of-N(BoN) win rate</li>
</ol>
<h2 id="33-self-consistency-for-cloud-reward-models">3.3 Self-consistency for cloud reward models</h2>
<h3 id="rq3-when-is-self-consistency-useful">RQ3: When is self-consistency useful?</h3>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/5e48294e-9686-4f52-a636-2517b2dd79c9/image.png" alt=""></p>
<p>critiques의 수가 증가할 때, 1-2 reasoning steps의 성능이 가장 좋음. 
➡️ 왜 critique 수를 늘릴수록 1-2 reasoning steps에서 self-consistency가 좋아질까?</p>
<p>이 질문에 답하기에 앞서 self-consistency가 뭔지 알아보겠습니다. </p>
<blockquote>
<p><strong>self-consistency</strong>
같은 response에 대해 여러 번 critique을 생성했을 때 나온 점수의 평균을 최종 reward로 산출하는 방법<Br>
 ➡️ 답변의 일관성 의미. </p>
</blockquote>
<p>이제 질문에 답해보겠습니다. 흐름을 단계적으로 살펴보겠습니다. </p>
<p>reasoning step이 짧을수록(짧은 추론일수록)
→ 결론 역시 짧아짐
→ 짧은 결론일수록 관점이 한두가지로 수렴
→ 이 상태에서 표본을 늘릴수록
→ 유사한 결론이 반복 언급
→ 결론의 분산이 작아짐(결론의 분포가 좁아짐) &amp; 일관된 예측
→  pairwise acc↑</p>
<p>위 단계적 사고에 따라 reasoning step이 짧을수록 self-consistency가 좋아진다는 점을 알 수 있습니다. </p>
<ul>
<li>흥미로운 점<ul>
<li>수학 문제에 대해서도 critique을 적용할 수 있습니다. </li>
<li>일반적으로 수학 문제는 답이 정해져 있으니 critique의 효과가 적을 것이라 생각했는데요, critique 사용이 잘못된 답을 교정하는 것뿐만이 아니라 답안을 더 풍부하게 만드는 효과도 있습니다! 
<img src="https://velog.velcdn.com/images/iumiere-on/post/ee6cf4f5-6427-48bb-9af9-7aeca71acc94/image.png" alt="">
<img src="https://velog.velcdn.com/images/iumiere-on/post/1bc2882d-ab55-4893-8354-45fd95e8fe86/image.png" alt=""></li>
</ul>
</li>
</ul>
<h1 id="4-related-work">4. Related work</h1>
<h2 id="critique-based-feedback">Critique-based feedback</h2>
<ul>
<li>기존에도 Reward model에서 critique을 도입했으나 기존 모델에서는 critique을 단순히 &quot;사용&quot;만 함. </li>
<li>CLoud에서는 human critique을 기반으로 critique을 &quot;생성&quot;까지 함. </li>
</ul>
<h2 id="llm-as-a-judge">LLM-as-a-Judge</h2>
<ul>
<li><strong>기존 LLM-as-a-Judge model</strong>  <ul>
<li>사람이 만든 채점기준표(rubric)를 기반으로 response를 scoring(평가)  </li>
<li>response 평가(O), response 교정(X)  </li>
<li><strong>CLoud와 유사점</strong>: scoring 전 response에 채점기준표를 적용하는 과정에 chain-of-thought 추론 적용  </li>
<li><strong>CLoud와 차이점</strong>: scalar reward head가 없음</li>
</ul>
</li>
</ul>
<p>   ➡️ 후속 연구에서는 사람이 만든 채점기준표(rubric)이 <strong>critique generation 과정 &amp; scoring 과정</strong>에서 모두 쓰일 수 있는 방향을 탐구</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 리뷰(1) MM-RLHF: The Next Step Forward in Multimodal LLM Alignment]]></title>
            <link>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B01-MM-RLHF-The-Next-Step-Forward-in-Multimodal-LLM-Alignment</link>
            <guid>https://velog.io/@iumiere-on/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B01-MM-RLHF-The-Next-Step-Forward-in-Multimodal-LLM-Alignment</guid>
            <pubDate>Mon, 23 Jun 2025 06:16:20 GMT</pubDate>
            <description><![CDATA[<p>Multimodal Large Language Model에 RLHF를 적용해 기존의 한계점들을 보완한 MM-RLHF에 대해 정리해보겠다. </p>
<p>개인적으로 공부하면서+발표하면서 힘들게 공부한 논문이라 보다 꼼꼼하게 리뷰하고자 한다. 
<em>정보량이 많은 논문이니 차근차근 따라오시는 걸 추천드립니다</em></p>
<p>목차는 다음과 같다. 나는 논문을 읽을 때 Abstract -&gt; Conclustion -&gt; Introduction -&gt; Dataset -&gt; Method... 순으로 읽는 편이라 이 순서대로 리뷰를 전개해보고자 한다. </p>
<blockquote>
<p>Abstract
Introduction
MM-RLHF-Dataset
MM-RLHF-Reward Model
MM-DPO
Experiments
Conclusion</p>
</blockquote>
<h1 id="abstract">Abstract</h1>
<ul>
<li><p>Most MLLMs은 alignment with human preferences에 기반한 발전에 어려움을 겪는다.</p>
</li>
<li><p>이를 해결하기 위해 이 논문에서는 <strong>MM-RLHF, Critique-based reward model, Dynamic reward Scaling</strong>을 제안</p>
</li>
</ul>
<pre><code>* **MM-RLHF**: a dataset containing 120k fine-grained, human-annotated preference comparison pairs
* **Critique-based reward model**: generates ciritques before assigning scores
* **Dynamic Reward Scaling**: Adjust the loss weight according to the reward signal

&lt;br&gt;</code></pre><h1 id="conclusion">Conclusion</h1>
<ul>
<li>MM-RLHF: dataset across diverse dimensions</li>
<li>Improvements to reward modeling &amp; optimization algorithms </li>
<li>Future work: <ul>
<li>leveraging annotation granularity with advanced
optimization techniques<ul>
<li>integrating high-resolution data to address limitations in specific benchmark     - scaling the dataset </li>
</ul>
</li>
</ul>
</li>
</ul>
<p>=&gt; 논문의 3개 포인트 정리 및 후속 연구 방향성 제시(MM-RLHF 데이터셋 활용)</p>
<br>

<blockquote>
<p>논문을 다 정리해야하지만 연구 주제를 빨리 잡기 위해서 우선 RM의 방법론만 정리하고 가겠다.. 빠른 시일 이내로 논문 전체를 정리하는걸로!</p>
</blockquote>
<h1 id="mm-rlhf-reward-model">MM-RLHF-Reward Model</h1>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/61ad33f4-be99-4628-a08f-b4c59b685a62/image.png" alt=""></p>
<h2 id="31-standard-reward-models">3.1 Standard Reward Models</h2>
<p>$$ 
ℓ<em>{Reward}(θ) = E</em>{x,y_w,y_l}[− log σ
(r(y_w|x) − r(y_l|x))]
$$</p>
<p>query x, 선호하는 응답과 덜 선호하는 응답인 $y_w,y_l$가 주어지면, RM이 더 선호하는 응답에 높은 보상을 부여하도록 최적화한다. 수식은 Cross Entropy 기반이다. </p>
<blockquote>
<h3 id="cross-entropy">Cross Entropy</h3>
<p>$H(p,q) = -logq(y)$<br>
     - $q(y)$가 1에 수렴 -&gt; H값이 0에 수렴 -&gt; loss값 작음
     - $q(y)$가 0에 수렴 -&gt; H값이 무한대로 발산  -&gt; loss값 큼</p>
</blockquote>
<p>위는 아래는 CE의 간단한 개념인데 이걸 기반으로 보면 이 선호하는 응답 $y_w$에 대한 보상과 덜 선호하는 응답 $y_l$에 대한 보상의 차가 클수록 loss값이 작다는 걸 알 수 있다! </p>
<p>이런 stadard reward model의 경우에는 아래와 같은 단점이 있다. </p>
<ol>
<li>They fail to utilize the detailed feedback provided by high-quality human annotations</li>
</ol>
<p><em>사람이 기껏 작성한 주석 피드백을 활용하지 못함</em>
2. Understanding how the reward generated is difficult. 
<em>스칼라 보상이 산출되는 과정을 명확히 이해하기 어려움</em></p>
<p>=&gt; RM의 reasoning 과정에 <strong>critique</strong>을 넣자!</p>
<h2 id="32-critique-based-reward-model-training">3.2 Critique-Based Reward Model Training</h2>
<p>Critique-based reward model은 두 가지 구성요소가 있음. </p>
<ul>
<li>** Critique head($h_l$)** : critique 산출</li>
<li>** Scoring head($h_r$)** : scalar rewards based on critiques 산출 </li>
</ul>
<p><strong>1. Critique head</strong>
$$
ℓ<em>{Critique}(θ) = E{x,y,c}[− \Sigma</em>{
t=1}^{|c|}log π<em>θ(c_t|c</em>{&lt;t}, x, y)]
$$</p>
<p>수식은 위와 같으며 앞선 CE 기반의 수식이고 산출한 critique이 human annotation에 부합하도록 학습된다. </p>
<ul>
<li><p>human annotation이 너무 간결해서 오히려 학습이 저하되므로 GPT-4o를 이용해 annotation을 증강한다. </p>
<br>
** 2. Scoring head **
$$
ℓ_{Score}(θ) = E{x,y_w,y_l}[− log σ(r(x, y_w, c_w) − r(x, y_l, c_l))]
$$
</li>
<li><p>training 과정에서는 학습의 정확도를 위해 위에서 생성한 critique 대신 ground truth critique를 사용한다.
<em>여기서 말하는 ground truth critique == human annotation</em></p>
</li>
</ul>
<blockquote>
<p><strong>Overall training objective</strong>
$ℓ<em>{total} = ℓ</em>{Critique}(θ) + ℓ_{Score}(θ)$</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI를 기반으로 한 Docker- build부터 running까지]]></title>
            <link>https://velog.io/@iumiere-on/FastAPI%EB%A5%BC-%EA%B8%B0%EB%B0%98%EC%9C%BC%EB%A1%9C-%ED%95%9C-Docker-build%EB%B6%80%ED%84%B0-running%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@iumiere-on/FastAPI%EB%A5%BC-%EA%B8%B0%EB%B0%98%EC%9C%BC%EB%A1%9C-%ED%95%9C-Docker-build%EB%B6%80%ED%84%B0-running%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Mon, 19 May 2025 08:26:12 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! </p>
<p>저는 이대 컴퓨터공학과에 재학 중이고 캡스톤 디자인과 창업프로젝트-그로쓰에서 AI파트로 참여하고 있습니다.
오늘은 개발 과정에 대해 말씀드리면서 저와 같은 어려움을 겪으셨던 분들에게 조금이나마 도움이 되보고자 합니다.</p>
<p>우선 전체 <strong>파이프라인</strong>에 대해 언급한 다음, <strong>FastAPI</strong>의 전체 구조와 <strong>도커 빌드</strong>에 대해 말씀드리고자 합니다. 말씀드리면서 제가 유용하게 썼던 명령어들에 대해 정리해볼게요!</p>
<h2 id="1-파이프라인-구조">1. 파이프라인 구조</h2>
<p>프로젝트의 목표는 &#39;뉴스 기사를 숏폼으로 생성해보자!&#39;입니다.</p>
<p>전체 파이프라인은 아래와 같습니다!</p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/f5b2e00e-a622-4040-b0b2-df5257314324/image.png" alt=""></p>
<blockquote>
<ol>
<li>뉴스 기사 크롤링</li>
<li>gpt-4o-mini를 이용해 TTV와 TTS에 입력할 프롬프트와 기사의 태그 산출</li>
<li>영상과 음성 산출
3-1. 프롬프트를 바탕으로 TTV모델이 영상 산출
3-2. 프롬프트를 바탕으로 TTS API가 음성 산출</li>
<li>MoviePyf를 이용해 영상과 음성, 자막을 합산하고 썸네일 산출</li>
<li>데이터 업로드
5-1. S3에 영상과 썸네일 푸시
5-2. BE 서버에 메타데이터 post </li>
</ol>
</blockquote>
<p>스타트 때는(3-2) 코랩으로 각 단계별 코드를 짜서 돌렸습니다. 전체 파이프라인이 연결되지 않은 상태였죠! </p>
<p>이때 각 단계별로는 코드가 성공적으로 돌아갔지만, 산출물을 다음 단계에 입력으로 쓰는 게 꽤나 불편했습니다.. csv 파일과 영상, 음성의 디렉토리를 정확히 지정해줘야 됐거등요..</p>
<p>그리고 최종적으로 배포하기 위해서는 도커에 빌드를 해야했기에! </p>
<p>전체 파이프라인 개발은 <strong>FastAPI</strong> 프레임워크를 사용하기로 결정했습니다.</p>
<p><br><br></p>
<h2 id="2-fastapi-구조-및-기능">2. FastAPI 구조 및 기능</h2>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/68261a27-1953-499e-adc6-e93ca88152ae/image.png" alt=""></p>
<p>최종적인 AI 서버의 구조는 위와 같습니다. 오늘은 이중 <strong>FastAPI</strong>부터 <strong>도커 빌드</strong>까지 다뤄볼거에요!</p>
<h3 id="1-fastapi의-폴더-구조">1) FastAPI의 폴더 구조</h3>
<p>우선 <strong>FastAPI</strong> 구조는 다음과 같습니다. </p>
<p>구조와 기능에 대해 자세히 써놨으니 이걸 토대로 FastAPI 틀 잡으시는 걸 추천드립니다!</p>
<pre><code>📂 dev/
│
└── app/                           # 🧠 애플리케이션 패키지
    ├── __init__.py
    │
    ├── main.py                    # 🌐 FastAPI 진입점
    │
    ├── core/                      # ⚙️ 공통 설정 &amp; 유틸
    │   ├── config.py              #  환경변수/세팅
    │   └── logging.py             #  로깅 포맷, 레벨
    │
    ├── api/                       # 🚏 라우팅 계층
    │   ├── __init__.py
    │   └── endpoints/
    │           ├── crwal.py         # 뉴스 기사 크롤링
    │           ├── summarize.py  # GPT가 프롬프트&amp;태그 산출
    │           ├── tts.py         # TTS API가 음성 산출
    │           ├── ttv.py         # TTV 모델이 영상 산출
    │           ├── moviepy.py    # 영상+음성+자막 합산, 썸네일 산출
    │           └── pipeline.py   # 전체 파이프라인 동작
    │
    ├── services/                  # 🏷️ 전체 동작 코드
    │   ├── logger.py             # 모듈 동작 시 로그
    │   ├── crawler.py            # 셀레니움을 이용해 크롤링
    │   ├── gpt.py                # 기사 요약 -&gt; 정제 -&gt; 프롬프트 산출 
    │   ├── tags.py               # 태그 산출
    │   ├── tts.py                # 음성 산출
    │   ├── ttv.py                # 영상 산출
    │   ├── moviepy.py            # 영상+음성+자막 합산, 썸네일 산출
    │   ├── pipeline.py           # 전체 파이프라인 동작
    │   ├── storage.py            # s3에 푸시
    │   └── to_request.py         # BE 서버에 푸시
    │
    └── data/                        # google-credentials
        └── keys
            └── tts.json

</code></pre><p>꽤나 복잡하죠?!</p>
<p>사실 FastAPI는 파이썬으로 동작하기 때문에 코드를 짜는 것 자체는 어렵지 않습니다. 전체 로직을 짜고 endpoint를 명확히 설정하는 게 훨씬 중요하고 어렵죠! </p>
<p>이제 각 폴더별 기능을 살펴보겠습니다. </p>
<h4 id="1-core-폴더">(1) core 폴더</h4>
<ul>
<li><p><strong>설정·환경변수(config.py)</strong>, 공통 유틸, 로깅 등 앱 전반에서 필요한 기반 기능 </p>
</li>
<li><p>아래는 config 코드 예시,
사용할 키값들을 설정하는 역할을 합니다~</p>
</li>
</ul>
<pre><code class="language-python">from functools import lru_cache

ROOT_DIR = Path(__file__).resolve().parents[2]   # app/../../

class Settings(BaseSettings):
    # === OpenAI ===
    OPENAI_API_KEY: str = Field(..., env=&quot;OPENAI_API_KEY&quot;)

    class Config:
        env_file = ROOT_DIR / &quot;.env&quot;
        case_sensitive = True

@lru_cache
def get_settings() -&gt; Settings:
    return Settings()

</code></pre>
<p>config외에 전체 코드에서 공통적으로 사용할 로깅 파일 등도 담습니다. </p>
<h4 id="2-api폴더">(2) api폴더</h4>
<ul>
<li>FastAPI의 킥인 <strong>FastAPI 라우터·엔드포인트</strong>들을 모아둠.</li>
<li>아래는 예시 파일로, 전체 <code>/crawl</code>로 끝나는 endpoint의 경우, <code>crawler</code> 모듈이 동작하도록 합니다. </li>
<li>services폴더에 담겨있는 모듈들이 동작하는 위치라고 보시면 될 것 같습니다!</li>
</ul>
<pre><code class="language-python">from fastapi import APIRouter, BackgroundTasks
from app.core.logger import logger
from app.services import crawler

router = APIRouter(prefix=&quot;/crawl&quot;, tags=[&quot;Crawler&quot;])


@router.get(&quot;/&quot;, summary=&quot;네이버 섹션 헤드라인 크롤링&quot;)
async def crawl(bg: BackgroundTasks):
    def _job():
        data = crawler.crawl_headlines()
        logger.info(f&quot;[CRAWL] headlines={len(data)}&quot;)
    bg.add_task(_job)
    return {&quot;msg&quot;: &quot;크롤링 시작! 로그에서 진행 상황을 확인하세요.&quot;}
</code></pre>
<h4 id="3-services-폴더">(3) services 폴더</h4>
<ul>
<li>실제 모듈들의 동작 코드가 담겨있는 폴더</li>
<li>아래는 예시 파일<pre><code class="language-python">from selenium import webdriver
from app.core.logger import logger
</code></pre>
</li>
</ul>
<p>def <em>get_driver(headless: bool = True) -&gt; webdriver.Chrome:
...
def crawl_headlines(sections: List[str]|None=None) -&gt; List[Dict]:
...
def enrich_articles(news_list: List[Dict]) -&gt; List[Dict]:
...
if <em>_name</em></em> == &quot;<strong>main</strong>&quot;:
...</p>
<pre><code>

#### (4) data 폴더
- 예시 데이터, 모델 가중치, 리소스 등 애플리케이션이 읽고 쓰는 파일을 보관
- 제 data폴더는 위 경우와 달리 key를 담은 파일이 루트 디렉토리에 있지 않도록 보호하는 역할을 합니다.
    - .env파일과 다릅니다!!!!
    - 제 경우, 
    Google-credential은 json파일로 주어지는데 이게 root 디렉토리 바로 밑에 있지 않도록 보호하는 역할을 합니다.


#### (5) init.py, main.py
- init.py: 
        - 폴더를 파이썬 패키지로 인식하게 함. 
    - FastAPI가 구동되기 위한 기본 조건. 
    - 아무것도 안 써있어도 됨. 다만 무조건 존재해야 함. 
- main.py: 
    - 사령탑. 
    - FastAPI 객체 생성, 미들웨어(CORS 등) 등록, api 라우터 합체 등
    ```python
    app = FastAPI(
        title=&quot;📰Shortform Generating by New-valance&quot;,
        description=&quot;네이버 뉴스 → GPT요약 → TTS/TTV → 병합 → S3 업로드&quot;,
        version=&quot;1.0.0&quot;,
        docs_url=&quot;/swagger&quot;,
        redoc_url=&quot;/redoc&quot;,
        openapi_url=&quot;/openapi.json&quot;)


    ```

### 2) FastAPI 구동
자~! 이제 FastAPI를 동작시켜보겠습니다!

동작시키기 전 기본적인 uvicorn 명령어 대해 알아보겠습니다.. 

- 기본 설치 명령어:
`pip install fastapi &quot;uvicorn[standard]&quot;`
- 로컬 개발 서버에서 실행
```bash
# 기본 실행
uvicorn app.main:app

# 리로드
uvicorn app.main:app --reload

# .env 로드와 함께 실행
uvicorn app.main:app --env-file .env

# 포트, 호스트 변경
uvicorn app.main:app --host 0.0.0.0 --port 8000</code></pre><br>
이제 로컬 서버를 실행해보겠습니다! 

<p>저는 .env로 환경변수를 주입해야 해서 
<code>uvicorn app.main:app --env-file .env</code> 명령어를 사용했어요!</p>
<p>그리고 app의 루트폴더(app폴더의 한 단계 상위폴더)에서 실행해야 main.py와 .env파일이 모두 문제 없이 돌아갑니다~! </p>
<ul>
<li><p>아래는 명령어를 입력한 뒤 출력된 터미널입니다.
<img src="https://velog.velcdn.com/images/iumiere-on/post/fe952c22-d253-48dc-a734-dd1fedbbebea/image.png" alt=""></p>
</li>
<li><p>FastAPI로 로컬 서버가 돌아가는 화면입니다.
<img src="https://velog.velcdn.com/images/iumiere-on/post/e1aa169c-af18-4c60-83e9-ab3e36a6dc4b/image.png" alt=""></p>
</li>
</ul>
<p>위 사진보면 아시겠지만 <code>http://127.0.0.1:8000/swagger</code> 에서  돌아가고 있습니다! </p>
<p>위 main.py에 적어둔 <code>app = FastAPI()</code> 부분에 적어둔대로 로컬호스트의 8000번 포트에서 실행되고 있는겁니다!
<br>
여기서 크롤링을 해볼게요🐵</p>
<p>입력할 endpoint는 <code>http://127.0.0.1:8000/swagger#/Crawler/crawl_api_crawl__get</code>
위와 같고, 이 endpoint를 직접 입력하거나 swagger 화면에서 execute해도 됩니다. </p>
<p>실행 뒤 아래처럼 response body에 잘 담겨온 걸 확인할 수 있습니다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/2c9e738b-02b2-4c10-b388-ab9bfa0993be/image.png" alt=""></p>
<p>로컬 터미널에서는 아래처럼 로그와 출력이 찍힙니다.
<img src="https://velog.velcdn.com/images/iumiere-on/post/b93459c0-03d2-4252-b69c-30f819daa162/image.png" alt=""></p>
<p>지금까지 FastAPI가 잘 동작하는 걸 보았으니 이제 도커로 넘어가도록 하겠습니다. 
<br><br></p>
<hr>
<h2 id="3-docker-세팅-및-실행">3. Docker 세팅 및 실행</h2>
<p>저도 이번 프로젝트를 하면서 도커를 처음으로 사용해봤습니다. 어렵다는 말을 많이 들어서 지레 겁먹었지만 막상 해보니 별 거 없더라구요! </p>
<p>여러분도 차근차근 따라오시면 됩니다🍀</p>
<p>도커를 사용하려면 기본적으로 Dockerfile을 작성해야 합니다. Docker의 사용흐름은 간단히 정리하면 아래와 같습니다.</p>
<blockquote>
<p>1.Dockerfile 작성
2. 이미지 빌드
3. 컨테이너 실행
4. 수정 -&gt; 재빌드  #캐시 활용
5. docker hub에 푸시
6. 배포
7. 정리 &amp; 모니터링</p>
</blockquote>
<p>이제 각 단계별로 차근차근 알려드리겠습니다. </p>
<h3 id="1-docker-세팅">1) Docker 세팅</h3>
<p>Docker를 실행하려면 _<strong>Dockerfile</strong>은 필수, <strong>docker-compose.yml과 .dockerignore</strong>은 선택_입니다. </p>
<p>다만 저는 추후 EC2로 배포를 해야하고 이미지 용량이 크기 때문에 나머지 두 파일을 작성했습니다. </p>
<p>이제 파일 형식에 대해 보여드릴게요!</p>
<ul>
<li><strong>Dockerfile</strong><pre><code class="language-docker"># ---------- 1단계: 빌드 ----------
FROM --platform=linux/amd64 python:3.11-slim AS builder
</code></pre>
</li>
</ul>
<h1 id="시스템-패키지--가상환경">시스템 패키지 &amp; 가상환경</h1>
<p>RUN apt-get update &amp;&amp; apt-get install -y gcc curl &amp;&amp; <br>    python -m venv /opt/venv &amp;&amp; <br>    . /opt/venv/bin/activate &amp;&amp; pip install --upgrade pip</p>
<h1 id="종속성-복사·설치-캐시-최적화">종속성 복사·설치 (캐시 최적화!)</h1>
<p>COPY requirements.txt .
RUN . /opt/venv/bin/activate &amp;&amp; pip install --no-cache-dir -r requirements.txt</p>
<h1 id="-----------2단계-런타임-----------">---------- 2단계: 런타임 ----------</h1>
<p>FROM --platform=linux/amd64 python:3.11-slim</p>
<p>ENV PYTHONUNBUFFERED=1 <br>    VIRTUAL_ENV=/opt/venv <br>    PATH=&quot;/opt/venv/bin:$PATH&quot;</p>
<h1 id="빌더-단계에서-설치한-가상환경-가져오기">빌더 단계에서 설치한 가상환경 가져오기</h1>
<p>COPY --from=builder /opt/venv /opt/venv</p>
<h1 id="애플리케이션-소스">애플리케이션 소스</h1>
<p>COPY app /app
WORKDIR /app</p>
<h1 id="기본-실행-명령-필요하면---workers-등-추가">기본 실행 명령 (필요하면 --workers 등 추가)</h1>
<p>CMD [&quot;uvicorn&quot;, &quot;app.main:app&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;, &quot;--port&quot;, &quot;8000&quot;]</p>
<pre><code>앞서 말씀드렸듯이 제 도커이미지는 용량이 큽니다. 이때 사용하는 게 `멀티 스테이지`인데요! 바로 빌드와 런타임 두 단계로 나눠서 이미지 용량을 최소화하는 방법입니다. 

위 파일에서도 빌드 단계에서 설치한 걸 런타임에서는 복붙해서 사용하는 구조입니다. 
&lt;BR&gt;

프로젝트 규모가 작거나 CPU를 사용하면 이렇게 멀티스테이지로 나눌 필요는 없을 것 같습니다! 
다만 저처럼... _GPU를 쓰거나 규모가 크면_ 나누시는 걸 추천드립니다(ง •_•)ง



- **docker-compose.yml**

```docker
  services:
  api:
    build: 
      context: .
      dockerfile: Dockerfile

    restart: unless-stopped

    gpus: all    

    ports:
      - &quot;8000:8000&quot;
    env_file:
       - .env
    volumes:
       - .:/app</code></pre><ul>
<li><p><strong>.dockerignore</strong></p>
<ul>
<li>.gitignore처럼 빌드 시 파일이 포함되지 않게 해줍니다. 다만 runtime에는 포함된다는 점 유의해주세요! </li>
<li>가벼운 이미지를 만들거나 보안상 이유로 사용합니다~!</li>
</ul>
</li>
</ul>
<pre><code>  # 파이썬 캐시
      __pycache__/
      *.pyc

  # 가상환경/IDE
    .venv/
    env/
</code></pre><br>


<h3 id="2-docker-실행">2) Docker 실행</h3>
<p>  이제 세팅을 했으니 실행해보겠습니다. 앞서 말씀드렸듯이</p>
<p> <code>build</code> -&gt; <code>run</code> 순서도 진행해볼게요!</p>
<h4 id="1-docker-desktop-실행">(1) docker desktop 실행</h4>
<p>  빌드하기 전에 docker desktop을 먼저 키셔야합니다. 그럼 아래처럼 초록불이 들어오면서 engine이 켜지는 걸 확인할 수 있어요! 
 <img src="https://velog.velcdn.com/images/iumiere-on/post/7c8851fa-ee6d-44c0-8198-9d3308e82b33/image.png" alt=""></p>
<h4 id="2-docker-build">(2) docker build</h4>
<p>  <code>docker build -t image_name:latest .</code></p>
<ul>
<li><p>image_name에는 이미지의 이름을, 그 뒤에는 &quot;:&quot;를 붙여 태그를 붙여주시면 됩니다. 태그는 선택!!!</p>
</li>
<li><p>아래는 해당 명령어에 대한 터미널 출력 화면입니다（*＾-＾*）
<img src="https://velog.velcdn.com/images/iumiere-on/post/1c625ce9-05e0-418a-a3ee-fe44c0dcb6c1/image.png" alt=""></p>
<p>명령어 입력하고 위 화면처럼 뜨면 성공!! </p>
<p>제 경우는 이미지 크기가 10GB 이상이라 한 번 빌드할 때 10분~30분은 걸렸던 것 같아요. 여러분도 빌드 시에 너무 오래 걸린다고 걱정 마시고  고냥 냅둬주세요!!</p>
<h4 id="3-docker-run">(3) docker run</h4>
</li>
</ul>
<p> <code>docker run --rm -p 8000:8000 myapp:latest</code></p>
<p>  이게 기본 명령어고 저는 변수들을 주입해줘야해서 아래처럼 입력했습니다. 
  저렇게 알 수 없는 숫자가 뜨면? 잘 돌아가고 있는겁니다. </p>
<p>  <img src="https://velog.velcdn.com/images/iumiere-on/post/11428970-062e-42d1-b22b-8366ab1fb4f0/image.png" alt="">
  이제 로컬 호스트에 접속하시면 제대로 돌아가는 걸 확인할 수 있습니다!</p>
<h4 id="4-docker-push">(4) docker push</h4>
<pre><code class="language-bash">
          docker login

          #이미지 태그
        docker tag image_name:latest repo_id/myapp:1.0

          #docker hub에 push
        docker push repo_id/myapp:1.0
</code></pre>
<pre><code>      이제 푸시를 해보겠습니다. 
      ![](https://velog.velcdn.com/images/iumiere-on/post/bcc1470e-e64c-4641-a4bd-9347ceb0e824/image.png)</code></pre><p>  push 명령어를 입력하면 이렇게 로그가 쭈욱 찍히다가 푸시가 완료됩니다! 
  푸시된 이미지는 docker hub에서 확인할 수 있습니다.
  <img src="https://velog.velcdn.com/images/iumiere-on/post/a52e960f-07e7-4038-8681-d6e9656d6ace/image.png" alt=""></p>
<h4 id="5-배포deploy">(5) 배포(deploy)</h4>
<p>  배포 시에는 </p>
<ol>
<li><p>docker 단일 서버: <code>docker pull myrepo/myapp:1.0</code></p>
</li>
<li><p>compose: <code>docker-compose up -d</code> </p>
</li>
<li><p>kubernetes: <code>kubectl apply -f deployment.yaml</code></p>
<p>위 세 가지 방법이 있습니다. 오늘은 docker run까지만 알아보기로 했으니 배포부터는 다음에 설명하겠습니다!</p>
<h4 id="6-🧹-정리--모니터링">(6) 🧹 정리 &amp; 모니터링</h4>
<pre><code class="language-bash"># 불필요한 이미지/컨테이너 삭제
docker system prune -a

# 로그 확인 
docker logs &lt;container&gt;

# 실시간 상태 확인
docker ps

# 빌드된 도커 이미지 확인
docker images
</code></pre>
<p>틈틈히 캐시를 정리해주면 build/run/push 시에 속도가 올라가거나 이미지 크기가 작아질 수 있습니다~!
<br><br></p>
</li>
</ol>
<hr>
<p>  지금까지 FastAPI 설계부터 docker run/push까지 알아봤습니다. 저처럼 docker가 낯선 분들에게 좋은 길잡이가 되길 바라면서 오늘의 글 마무리하겠습니다. </p>
  <br>



<p>  다들 즐거운 개발하시길 바라요~🍀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Selenium을 활용한 뉴스 크롤링]]></title>
            <link>https://velog.io/@iumiere-on/Selenium%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%89%B4%EC%8A%A4-%ED%81%AC%EB%A1%A4%EB%A7%81</link>
            <guid>https://velog.io/@iumiere-on/Selenium%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%89%B4%EC%8A%A4-%ED%81%AC%EB%A1%A4%EB%A7%81</guid>
            <pubDate>Tue, 26 Nov 2024 13:39:19 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요!
오늘 <strong>selenium</strong>에 관해 알아보기에 앞서 왜 이걸 사용하게 됐는지 간략하게 말씀드리겠습니다. </p>
<p>편한 말투로 진행해보겠습니다~~</p>
<h2 id="프로젝트-개요-및-selenium이-필요하게-된-계기">프로젝트 개요 및 selenium이 필요하게 된 계기</h2>
<blockquote>
<p>나는 현재 이화여대에서 컴퓨터공학과의 캡스톤 디자인 졸업 프로젝트로 &#39;자동화된 뉴스 요약 숏폼 생성 시스템&#39;을 진행하고 있으며, AI파트를 담당하고 있다. </p>
</blockquote>
<p>우리 프로젝트를 진행하기 위해서 가장 먼저 준비해야할 것이 바로 기사를 크롤링하는 것이다. </p>
<p>처음에는 beautifulsoup을 이용해서 다음 기사를 크롤링하는 코드를 작성했는데, 몇 주 뒤에 확인해보니 URL이 바뀌었다... </p>
<p> 그래서 급하게 <strong>Selenium</strong>을 이용해서 크롤링을 하기로 결정했다. </p>
<p> 우선 크롤링할 때는 대부분 <code>beautifulsoup</code> 이 라이브러리를 가장 많이 사용하는 것 같고, 뉴스 기사 크롤링의 경우는 <code>newspaper3k</code> 이 라이브러리도 종종 사용하는 것 같다. </p>
<p>오늘은 이 두 개의 라이브러리가 아닌 <code>selenium</code> 이라는 라이브러리를 사용해보고자 한다. 그렇다면 왜 이걸 사용할까?</p>
<p>바로 <strong>동적 웹 크롤링</strong> 때문이다. 앞서 말한 라이브러리와 selenium의 차이점은 url에 있다. </p>
<ul>
<li><p>beautifulsoup와 newspaper3k는 크롤링하고자 하는 URL에 기반하기 때문에 _URL이 정확히 일치_하는 경우에만 크롤링을 시작할 수 있다. </p>
</li>
<li><p>selenium의 주요 기능과 특징은 다음과 같다. </p>
</li>
</ul>
<h2 id="selenium의-주요-기능과-특징">selenium의 주요 기능과 특징</h2>
<h3 id="주요-기능">주요 기능</h3>
<ul>
<li><strong>브라우저 자동화</strong>: <ul>
<li>Selenium은 Chrome, Firefox, Safari, Edge 등 다양한 웹 브라우저를 자동화 가능. </li>
</ul>
</li>
<li><blockquote>
<p>각 브라우저에 맞는 드라이버를 사용하여 자동화 작업을 수행합니다</p>
</blockquote>
</li>
<li><strong>실시간 사용자 상호작용 에뮬레이션</strong>: <ul>
<li>사용자의 웹 브라우징 활동을 모방하여 클릭, 스크롤, 텍스트 입력, 파일 업로드 등의 동작을 자동으로 수행</li>
</ul>
</li>
<li><strong>동적 콘텐츠 처리</strong>: AJAX와 같은 기술로 생성된 동적 콘텐츠를 다룰 때 유용하며, 요소가 로드될 때까지 대기하고 이벤트를 트리거하는 기능을 제공</li>
<li><strong>크로스 브라우저 테스팅</strong>: 다양한 브라우저 및 운영 체제 환경에서 웹 애플리케이션의 호환성을 테스트할 수 있음. </li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li><strong>언어 지원</strong>: Python, Java, C#, Ruby 등 여러 프로그래밍 언어를 지원</li>
<li><strong>요소 위치 지정</strong>: 웹 페이지 내의 요소를 찾기 위해 XPath, CSS 선택자 등을 사용</li>
<li><strong>Selenium WebDriver</strong>: 실제 브라우저를 제어하며, 각 브라우저에 맞는 드라이버(예: ChromeDriver)를 통해 브라우저와 직접적으로 상호작용</li>
<li><strong>Selenium Grid</strong>: 다양한 브라우저 환경에서 테스트를 병렬로 실행할 수 있도록 지원</li>
</ul>
<h2 id="코드">코드</h2>
<p>주요기능과 특징을 알아봤으니, 이제 코드에 대해 알아보자. </p>
<p>우선 전체 코드는 아래 링크에 담겨있다. 
<a href="https://colab.research.google.com/drive/1Lzr7XmWEfzHS7ILnpkg6V-MDujWo3Y9r#scrollTo=dRwWUQkA-y-1">https://colab.research.google.com/drive/1Lzr7XmWEfzHS7ILnpkg6V-MDujWo3Y9r#scrollTo=dRwWUQkA-y-1</a></p>
<p>이제 colab에서 크롬 드라이브를 설치하는 과정부터 실제 크롤링 코드를 작성하는 과정까지 알아보자. 
참고로, colab에서 크롬 드라이브를 설치하는 과정은 로컬에서 설치하는 과정과 다른 점이 있으니 참고바란다. </p>
<h3 id="1-selenium--chrome-driver-설치">1. selenium &amp; chrome driver 설치</h3>
<pre><code>!pip install selenium
!apt-get update
!apt install chromium-chromedriver
# !cp /usr/lib/chromium-browser/chromedriver &#39;/content/drive/MyDrive/Colab Notebooks&#39; # (최초 1회)
!pip install chromedriver-autoinstaller</code></pre><p>위 코드를 통해 selenium을 설치하고 chrome driver을 설치하게 된다. </p>
<p>이후 아래 코드를 통해 파이썬과 selenium의 버전을 확인을 통해 올바르게 다운되었는지 확인할 수 있다. (나중에 버전 관련 오류가 생길 때도 버전 확인이 필요할 수 있음!)</p>
<pre><code># selenium 설치 확인
!python --version


#selenium 버전 확인
import selenium
print(selenium.__version__)
###</code></pre><h3 id="2-크롤링을-위한-준비">2. 크롤링을 위한 준비</h3>
<p>자 크롬 드라이버를 다운받았다면 이제 본격적으로 크롤링을 위한 준비를 해보자!</p>
<p><strong>2-1. 라이브러리 import해주기</strong></p>
<pre><code>from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import sys
from selenium.webdriver.common.keys import Keys
import urllib.request
import os
from urllib.request import urlretrieve

import time
import pandas as pd
import chromedriver_autoinstaller  # setup chrome options</code></pre><p><strong>2-2. driver에 사용할 chrome path와 options 설정해주기</strong></p>
<pre><code>chrome_path = &quot;/content/drive/MyDrive/Colab Notebooks/chromedriver&quot;

sys.path.insert(0,chrome_path)
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(&#39;--headless&#39;) # ensure GUI is off : cloab은 새창을 지원하지않기 때문에 창 없는 모드
chrome_options.add_argument(&#39;--no-sandbox&#39;)
chrome_options.add_argument(&#39;--disable-dev-shm-usage&#39;)  # set path to chromedriver as per your configuration
chrome_options.add_argument(&#39;lang=ko_KR&#39;) # 한국어

chromedriver_autoinstaller.install()  # set the target URL
</code></pre><ul>
<li>여기서 설정하는 chrome_path는 다른분들이 사용하실 때도 변경하지 않고 그대로 사용하시면 됩니다. </li>
</ul>
<p>*<em>2-3. chrome driver 세팅해주기 *</em></p>
<pre><code>driver = webdriver.Chrome(options=chrome_options)

url = &quot;https://google.com&quot;
driver.get(url)
driver.implicitly_wait(2) 

print(driver)
</code></pre><p><strong>2-4. 현재 날짜를 변수에 담아두기</strong></p>
<p>이 코드가 필요없으신 분들은 건너뛰시면 됩니다!</p>
<pre><code>from datetime import datetime

# 현재 날짜 가져오기
date = datetime.now().date()

date = date.strftime(&quot;%Y%m%d&quot;)
print(date)</code></pre><h3 id="3-크롤링-시작하기">3. 크롤링 시작하기</h3>
<p><strong>url 분석</strong></p>
<p>나는 네이버 뉴스기사를 크롤링했기 때문에 네이버 뉴스의 url을 기반으로 했다. 
네이버 뉴스는 section별로 각 뉴스 부문이 나눠져 있다. 
아래 사진에는 정치면이 나와있고, url을 보면 100번 섹션이 정치를 나타냄을 알 수 있다. (<a href="https://news.naver.com/section/100">https://news.naver.com/section/100</a> )</p>
<p><strong>크롬 브라우저에서 &#39;개발자 도구&#39;를 사용해 html 파일 보기</strong></p>
<p>다음은 개발자 도구를 사용해서 크롤링하고자 하는 요소를 알아보자!</p>
<ol>
<li>크롬에서 개발자 도구를 찾으려면 아래 사진처럼 오른쪽 위 점 세 개를 누른다.</li>
<li>&#39;도구 더보기&#39;를 누른다. </li>
<li>&#39;개발자 도구&#39;를 누르면 개발자 도구가 뜬다.<br><img src="https://velog.velcdn.com/images/iumiere-on/post/8afdc022-990f-43dc-a80f-6175f9aaad96/image.png" alt=""></li>
</ol>
<p>그 다음 </p>
<ol>
<li>아래 사진에 하이라이트된 아이콘을 누른다.</li>
<li>웹 페이지 내에서 커서를 확인하고 싶은 html 요소 위에 옮긴다.</li>
<li>해당 요소의 구성이 개발자 도구에 뜬다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/91ffa0ab-893f-45fc-a2bb-ed079889863e/image.png" alt=""></li>
</ol>
<p>위 방법을 이용해서 페이지에서 크롤링하고 싶은 부분의 요소를 확인할 수 있다.</p>
<h3 id="3-1-섹션을-기반으로-url와-헤드라인-크롤링하기">3-1. 섹션을 기반으로 url와 헤드라인 크롤링하기</h3>
<p>나는  위 방법을 통해 각 섹션에 있는 기사들의 url과 헤드라인의 요소를 알아본다음, <strong><em>url과 헤드라인</em></strong>을 추출하는 코드를 짰다. 
크롤링한 속성들은 모두 <strong>news_list</strong>라는 변수에 담았으며, 추후 csv파일 저장을 위해 사용될 예정이다. </p>
<p>*코드에 주석을 달아놓았으나 이해되지 않는 부분이 있다면 답글 남겨주세요! 설명해드리겠습니다. </p>
<pre><code># section의 숫자가 나타내는 의미
# 100:정치
# 101: 경제
# 102: 사회
# 103: 생활/문화
# 104: 세계
# 105: IT/과학

#크롤링할 base url
base_url = &quot;https://news.naver.com/section/&quot;


#news_list의 속성 = (날짜, 섹션, url, 기사 제목, 기사 내용, 이미지, 감정)
news_list = []

# 섹션 리스트 (예: 정치, 경제, 사회 등)
sections = [&quot;100&quot;, &quot;101&quot;, &quot;102&quot;,&quot;103&quot;,&quot;104&quot;,&quot;105&quot;]


# 각 섹션에서 뉴스 크롤링
for section in sections:

    section_url = f&quot;{base_url}{section}&quot;  # 섹션 URL 생성
    driver.get(section_url)
    time.sleep(2)  # 페이지 로드 대기

    # 기사 제목과 URL 크롤링
    articles = driver.find_elements(By.XPATH, &#39;//a[contains(@class, &quot;sa_text_title&quot;)]&#39;)
    for article in articles:
        url = article.get_attribute(&#39;href&#39;)  # href 속성 (기사 URL)
        title_element = article.find_element(By.XPATH, &#39;./strong&#39;)  # strong 태그 안의 제목
        title = title_element.text.strip()  # 제목 텍스트


        if section == &quot;100&quot;:
          section_name = &quot;정치&quot;
        elif section == &quot;101&quot;:
          section_name = &quot;경제&quot;
        elif section == &quot;102&quot;:
          section_name = &quot;사회&quot;
        elif section == &quot;103&quot;:
          section_name = &quot;생활/문화&quot;
        elif section == &quot;104&quot;:
          section_name = &quot;세계&quot;
        elif section == &quot;105&quot;:
          section_name = &quot;IT/과학&quot;
        else: section_name = &quot;none&quot;

        # 뉴스 데이터 저장
        news_list.append({
            &quot;date&quot;: date,
            &quot;section&quot;: section_name,
            &quot;url&quot;: url,
            &quot;title&quot;: title
        })
        print(f&quot;[{section}] {title}: {url}&quot;)  # 크롤링 결과 출력

# WebDriver 종료
driver.quit()

# 결과 출력 (선택적으로 DataFrame으로 저장 가능)
import pandas as pd
news_df = pd.DataFrame(news_list)
news_df.to_csv(&quot;naver_news.csv&quot;, index=False, encoding=&quot;utf-8-sig&quot;)
print(&quot;크롤링 완료. CSV 파일로 저장되었습니다.&quot;)
</code></pre><h3 id="3-2-기사-내용-이미지-리뷰-크롤링하기">3-2. 기사 내용, 이미지, 리뷰 크롤링하기</h3>
<p>위 코드를 통해 추출한 각 기사의 url을 기반으로 기사 본문에 들어가면 다음과 같은 화면이 뜬다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/6e7da79b-364d-430a-bb1d-e16332f9340c/image.png" alt=""></p>
<p>나는 이 화면에서 하이라이트된 부분을 크롤링하고자 한다. 
<strong><em>기사 본문(내용)과 기사 사진, 사용자 리뷰(기자 이름 밑에 있는 사용자 리뷰들)</em></strong>를 크롤링하고자 한다. </p>
<p><strong>기존에 사용하던 driver kill</strong></p>
<pre><code>!pkill -f chromedriver</code></pre><p><strong>드라이버 설정하기</strong>
그 다음 드라이버 설정을 다시 해준다. 위에어 설정한 드라이브와 다른 점은 &#39;chromedriver_autoinstaller.install()&#39; 이 코드를 돌리지 않았다는 점이다. </p>
<pre><code>from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

chromedriver_path = &quot;/content/drive/MyDrive/Colab Notebooks/chromedriver&quot;

sys.path.insert(0,chrome_path)
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(&#39;--headless&#39;) # ensure GUI is off : cloab은 새창을 지원하지않기 때문에 창 없는 모드
chrome_options.add_argument(&#39;--no-sandbox&#39;)
chrome_options.add_argument(&#39;--disable-dev-shm-usage&#39;)  # set path to chromedriver as per your configuration
chrome_options.add_argument(&#39;lang=ko_KR&#39;) # 한국어


driver = webdriver.Chrome(options=chrome_options)
</code></pre><p><strong>기사 내용, 기사 이미지, 리뷰 크롤링하기</strong></p>
<pre><code># URL 리스트에서 뉴스 데이터 처리
url_list = [news[&#39;url&#39;] for news in news_list]



for i, url in enumerate(url_list):
    driver.get(url)
    time.sleep(2)  # 페이지 로드 대기

    # 기존 뉴스 항목 가져오기
    news_item = news_list[i]

    # 기사 내용 추출
    try:
        article_element = driver.find_element(By.ID, &quot;dic_area&quot;)
        paragraphs = article_element.text.split(&quot;\n&quot;)
        cleaned_paragraphs = [p.strip() for p in paragraphs if p.strip()]
        news_content = &quot; &quot;.join(cleaned_paragraphs)

        if not news_content.strip():
            print(&quot;기사 내용 추출 실패&quot;)
        else:
            print(&quot;내용 추출 성공&quot;)
            news_item[&quot;content&quot;] = news_content
    except Exception as e:
        print(f&quot;기사 내용을 찾지 못했습니다: {e}&quot;)

    # 이미지 추출
    try:
        img_tag = driver.find_element(By.ID, &quot;img1&quot;)
        news_img = img_tag.get_attribute(&#39;src&#39;)

        if news_img is None:
            print(&quot;이미지 추출 실패&quot;)
        else:
            print(&quot;이미지 추출 성공&quot;)
            news_item[&quot;img&quot;] = news_img
    except Exception as e:
        print(f&quot;이미지를 추출하지 못했습니다: {e}&quot;)

    # 감정 데이터 추출
    try:
        labels = driver.find_elements(By.CLASS_NAME, &quot;u_likeit_list_name&quot;)
        counts = driver.find_elements(By.CLASS_NAME, &quot;u_likeit_list_count&quot;)

        if not labels or not counts:
            print(&quot;리뷰 추출 실패&quot;)
        else:
            print(&quot;리뷰 추출 성공&quot;)
            results = {label.text.strip(): count.text.strip() for label, count in zip(labels, counts)}
            news_item[&quot;review&quot;] = results
    except Exception as e:
        print(&quot;감정을 추출할 수 없습니다:&quot;, e)

# WebDriver 종료
driver.quit()
</code></pre><p>지금까지 크롤링한 변수를 담은 news_list의 자료형을 데이터프레임으로 바꾼 다음, csv파일로 저장한다. </p>
<pre><code>import pandas as pd
news_df = pd.DataFrame(news_list)
news_df.to_csv(&quot;total_naver_news.csv&quot;, index=False, encoding=&quot;utf-8-sig&quot;)
print(&quot;크롤링 완료. CSV 파일로 저장되었습니다.&quot;)
</code></pre><p>완료된 csv파일은 다음과 같다. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/83a89adc-8a79-4933-b899-5c2c5fc3e002/image.png" alt=""></p>
<p>크롤링한 코드에서 사용했던 주요 변수인 <strong><em>news_list의 속성인 date, section, url, title, content, img, review</em></strong>가 크롤링된 것을 볼 수 있다. </p>
<p>자 그럼 오늘의 코드를 이만 마무리해보자. </p>
<p>오늘 코드를 작성하면서 selenium에 대해 새롭게 알아보고 적용해보는 시간을 가졌는데, 동적 크롤링이 되기에 앞으로 효율적으로 사용할 수 있을 것 같다. </p>
<p>긴 글 읽어주셔서 감사합니다. 
다들 행복하고 오류없는 하루 보내시길 바랍니다!
<img src="https://velog.velcdn.com/images/iumiere-on/post/09d98bc1-30d3-4fe1-af72-efa69d8106ff/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성 AI - Ch6. 노멀라이징 플로 모델(Normalizing flow model)]]></title>
            <link>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch6.-%EB%85%B8%EB%A9%80%EB%9D%BC%EC%9D%B4%EC%A7%95-%ED%94%8C%EB%A1%9C-%EB%AA%A8%EB%8D%B8Normalizing-flow-model</link>
            <guid>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch6.-%EB%85%B8%EB%A9%80%EB%9D%BC%EC%9D%B4%EC%A7%95-%ED%94%8C%EB%A1%9C-%EB%AA%A8%EB%8D%B8Normalizing-flow-model</guid>
            <pubDate>Wed, 29 May 2024 08:49:26 GMT</pubDate>
            <description><![CDATA[<p>생각보다 성실한 나날들의 연속이다.
벌써 생성AI 6장을 배우고 이에 관해 정리하고 있다.</p>
<p>자! 그럼 오늘도 힘내면서! 6장인 Normalizing flow 모델에 대해 배워보자. </p>
<p>공부를 본격적으로 시작하기에 앞서 normalizing flow 모델이 우리가 전에 배웠던 autoregressive 모델, VAE와의 공통점과 차이점을 간단히 정리해보겠다.</p>
<p><strong>Autogressive 모델</strong>과의 공통점</p>
<ul>
<li>다루기 쉽고 명시적인 데이터 생성 분포 p(x)를 모델링 가능</li>
</ul>
<p><strong>VAE 모델</strong>과의 공통점</p>
<ul>
<li>데이터를 가우스분포와 같은 간단한 분포에 매핑</li>
</ul>
<p>차이점</p>
<ul>
<li>_매핑함수는 역함수가 가능해야함. _</li>
</ul>
<p>오늘의 목차는</p>
<h3 id="62-normalizing-flow">6.2 Normalizing Flow</h3>
<h3 id="63-realnvp">6.3 RealNVP</h3>
<h3 id="64-다른-normalizing-flow-모델">6.4 다른 Normalizing flow 모델</h3>
<p>과 같다. </p>
<h2 id="62-normalizing-flow-1">6.2 Normalizing flow</h2>
<p>노멀라이징 플로 모델은 VAE와 유사한 형태로 구성된다. 즉 인코더와 디코더로 구성된다. 다만 디코더가 인코더의 역함수라는 점이 다르다. 
그렇다면 인코더의 역함수로 사용하려면 어떻게 해야할까?</p>
<p> 이 질문에 대한 답을 얻기 위해 변수 변환(change of variables)에 대해 배워보자. </p>
<h3 id="621-변수-변환change-of-variables">6.2.1 변수 변환(change of variables)</h3>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/8641a095-d333-408d-b2ec-f2fac9e4b392/image.jpg" alt=""></p>
<p>변수 변환은 x를 z로, 다시 z를 x로 변환하는 과정을 말한다. </p>
<p>우리의 목적은 원본 데이터 x를 latent space인 Z의 한 포인트로 매핑하는 것이다. (이때 Z는 X에서 1. 분포 이동 2. 스케일 조정을 거쳐 매핑되는 곳이다.)</p>
<p>이를 위해 함수 f를 정의해보자! 
f는 </p>
<ul>
<li>z=f(x)</li>
<li>변수 z=(z1, z2)와 X의 각 포인트를 정확히 Z의 한 포인트에 매핑하는 함수이다.<br>*이때 z와 함수 f(x)는 그림 속 분홍색 박스에 정의되어있다.</li>
<li>함수 f를 가역함수(invertible function)라 부른다!</li>
</ul>
<p>z를 x로 매핑하는 함수 g는 </p>
<ul>
<li>x=g(z)</li>
</ul>
<p>자 그럼 이제 X에서 Z로의 변수 변환이 확률변수 Px(x)에 미치는 영향을 알기 위해 확률분포 Pz(z)에 대해 알아보자. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/3d0033b4-cb31-4a9c-b135-754d65f0b8a9/image.jpg" alt=""></p>
<p>위 그림에서 Pz(z)를 적분해 1/6이란 값을 얻었다. 그런데 확률분포를 샘플링이 가능한 분포로 변환하기 위해서는 적분 결과가 1이 되어야하기 때문에 Pz(z)가 더 이상 유효하지 않다는 사실을 알 수 있다.</p>
<p>그렇다면 Pz(z)가 유효한 확률분포가 되려면 어떻게 해야할까?
답은 <strong>정규화 계수를 곱하는 것</strong>이다. 
즉 정규화 계수를 곱해 원본 데이터의 면적과 같은 데이터로 만들어줘야한다!</p>
<p>다음 절에서는 정규화 계수를 쉽게 구할 수 있는 야코비안 행렬에 대해 배워보자.</p>
<h3 id="622-야코비-행렬식jacobian-matrix">6.2.2 야코비 행렬식(Jacobian matrix)</h3>
<p>Jacobian matrix에 대한 설명은 말보다 수식이 더 간단할 것 같아 내가 정리한 수식으로 대체하겠다. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/b17e5b25-9822-4a36-a6ce-9982f4f03d26/image.jpg" alt=""></p>
<h3 id="623-변수-변환-방정식change-of-variable-equation">6.2.3 변수 변환 방정식(change of variable equation)</h3>
<p>변수 변환 방정식이란?</p>
<blockquote>
<p>X와 Z 사이의 변수 변환 과정을 설명하는 하나의 방정식</p>
</blockquote>
<p>이다.
이 역시 수식으로 설명하는 게 편할 것 같아 관련 설명은 내가 정리한 수식으로 대체하겠다.
<img src="https://velog.velcdn.com/images/iumiere-on/post/d0c00a9f-b062-41a2-80fe-ee4a7e7b4dd9/image.jpg" alt=""></p>
<p>그런데 이렇게 변수 변환 방정식을 적용할 때 문제점 2가지가 발생한다. </p>
<ol>
<li>고차원 행렬의 행렬식을 계산할 때 매우 많은 비용이 발생</li>
<li>f(x)의 역함수를 계산하는 방법이 불명확</li>
</ol>
<p>이 2가지 문제점을 해결하기 위해 RealNVP가 등장했다.</p>
<h2 id="63-realnvp-1">6.3 RealNVP</h2>
<p>이 RealNVP의 신경망에는 2가지 목표가 있다.</p>
<ol>
<li>역변환 가능(역함수 성립 가능)</li>
<li>야코비 행렬 쉽게 계산 가능</li>
</ol>
<h3 id="631-초승달-데이터셋">6.3.1 초승달 데이터셋</h3>
<pre><code># 데이터 로드
#잡음이 있고 3000개의 포인트를 가진 초승달 데이터셋
data = datasets.make_moons(30000, noise=0.05)[0].astype(&quot;float32&quot;) 
norm = layers.Normalization()
norm.adapt(data)
#표준정규분포로 정규화. 
normalized_data = norm(data) 
plt.scatter(
    normalized_data.numpy()[:, 0], normalized_data.numpy()[:, 1], c=&quot;green&quot;
)
plt.show()</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/348818a0-9c3f-4348-a0ea-26dcc4f7a27c/image.png" alt=""></p>
<h3 id="632-커플링-층">6.3.2 커플링 층</h3>
<p>커플링층은 입력의 각 원소(포인트)에 대해 scale factor와 translation factor을 만든다.
=&gt;입력과 동일한 크기의 텐서 2개(scale factor, translation factor)를 만든다. </p>
<p>*이 모델은 이미지가 아니기 때문에 입력의 크기(shape)가 2차원이다. </p>
<p>코드에 주석을 달아놓았으니 천천히 보면서 이해해보자. </p>
<pre><code>def Coupling(input_dim, coupling_dim, reg):
      #입력의 크기가 2차원. 
    input_layer = layers.Input(shape=2)

    #scale factor 산출하기. 
    #scale factor를 위해 크기가 256인 dense층 쌓기
    s_layer_1 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(input_layer)
    s_layer_2 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(s_layer_1)
    s_layer_3 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(s_layer_2)
    s_layer_4 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(s_layer_3)

    #마지막 층은 크기가 2이고 tanh활성화 함수 사용 
    s_layer_5 = layers.Dense(
        input_dim, activation=&quot;tanh&quot;, kernel_regularizer=regularizers.l2(reg)
    )(s_layer_4)

    #translation factor 산출하기. 
    #translation factor을 위해 크기가 256인 dense층 쌓기
    t_layer_1 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(input_layer)
    t_layer_2 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(t_layer_1)
    t_layer_3 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(t_layer_2)
    t_layer_4 = layers.Dense(
        coupling_dim, activation=&quot;relu&quot;, kernel_regularizer=regularizers.l2(reg)
    )(t_layer_3)

    #마지막 층은 크기가 2이고 linear 활성화 함수 사용. 
    t_layer_5 = layers.Dense(
        2, activation=&quot;linear&quot;, kernel_regularizer=regularizers.l2(reg)
    )(t_layer_4)


    #입력으로 input_layer을 넣으면서 scale factor와 translation factor 을 결과로 출력
    return models.Model(inputs=input_layer, outputs=[s_layer_5, t_layer_5])</code></pre><h4 id="커플링-층으로-데이터-전달하기">커플링 층으로 데이터 전달하기.</h4>
<p>사실 커플링 층의 구조는 특이하지 않지만 이 속에서 마스킹을 하고 변환되는 과정이 독특하기 때문에 이 과정에 대해 자세히 알아보자. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/694d5bf4-8c84-43c4-b170-2dad359c0afd/image.jpg" alt="">
이 그림은 forward 과정을 보여준다. </p>
<ul>
<li><p>x의 d차원은 커플링 층에 주입 &amp; 다음 단계로 이전</p>
</li>
<li><p>x의 D-d차원은 완전히 마스킹</p>
</li>
<li><p>출력으로 scale factor와 translation factor가 나옴</p>
</li>
<li><blockquote>
<p>역 마스킹을 거친 뒤 (0,s2)와 (0, t2)가 출력됨.</p>
</blockquote>
<ul>
<li>출력된 계수를 x의 D-d차원에 원소별로 적용. </li>
</ul>
<p>여기서 마스킹을 왜 사용하는지는 그림의 오른쪽에 나와있고 이를 한 마디로 정리하면 계산의 편리성을 위해서이다!</p>
<p>즉 jacobian 행렬의 결과가 하삼각 행렬이 되기 때문에 결과가 대각 원소의 곱으로 매우 간단해진다. 
이는 RealNVP의 두번째 목표를 충족시킨다.</p>
<p>자 그럼 다음은 첫번째 목표인 역함수를 만들어보자. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/ecbdd03b-d554-4251-a0ea-9c1f214a6fdb/image.jpg" alt=""></p>
</li>
</ul>
<p>그림 왼쪽에 정방향 계산을 재정렬한 역함수가 정리되어있다. </p>
<p>자 그럼 이제 <em>이 forward 식과 inverse식을 결합해 커플링 층을 쌓아보자</em>!
이때 핵심은 그대로 유지되던 x의 d차원을 업데이트해야한다는 점이다.</p>
<p>업데이트를 위해 마스킹을 매번 뒤집는다. </p>
<p>즉, 
커플링 층을 쌓고 매번 마스킹을 뒤집으면([0,1]&lt;-&gt;[1,0])
이전 층에서 변경되지 않았던 부분이 다음 층에서 업데이트된다. </p>
<p>이 과정을 반복하면 입력이었던 x가 전체 입력 텐서로 변환되는 과정울 만들 수 있다. </p>
<h3 id="633-realnvp-모델-훈련">6.3.3 RealNVP 모델 훈련</h3>
<p>코드를 살펴보자. </p>
<pre><code>class RealNVP(models.Model):
    def __init__(
        self, input_dim, coupling_layers, coupling_dim, regularization
    ):
        super(RealNVP, self).__init__()
        self.coupling_layers = coupling_layers

         #타깃 분포는 표준 2D 가우스 분포이다.
        self.distribution = tfp.distributions.MultivariateNormalDiag(
            loc=[0.0, 0.0], scale_diag=[1.0, 1.0]
        )

        #번갈아 바뀌는 마스크 패턴을 만든다.
        #[0,1]=&gt;(x1, 0), [1,0]=&gt;(0, x2)
        self.masks = np.array(
            [[0, 1], [1, 0]] * (coupling_layers // 2), dtype=&quot;float32&quot;
        )
        self.loss_tracker = metrics.Mean(name=&quot;loss&quot;)

        #coupling 층 리스트로 RealNVP 신경망 정의. 
        self.layers_list = [
            Coupling(input_dim, coupling_dim, regularization)
            for i in range(coupling_layers)
        ]

    @property
    def metrics(self):
        return [self.loss_tracker]


    #coupling 층 순회하는 함수
    #training=True -&gt; 정방향으로 층 통과(x-&gt;z)
    #training=False -&gt;역방향으로 층 통과(z-&gt;x)
    def call(self, x, training=True):
        log_det_inv = 0
        direction = 1
        if training:
            direction = -1
        for i in range(self.coupling_layers)[::direction]:
            x_masked = x * self.masks[i]
            reversed_mask = 1 - self.masks[i]
            s, t = self.layers_list[i](x_masked)
            s *= reversed_mask
            t *= reversed_mask
            gate = (direction - 1) / 2

            #direction에 따라 정방향/역방향 식 구별
            x = (
                reversed_mask
                * (x * tf.exp(direction * s) + direction * t * tf.exp(gate * s))
                + x_masked
            )
            #손실함수는 스케일링 계수의 합
            log_det_inv += gate * tf.reduce_sum(s, axis=1)
        return x, log_det_inv

    def log_loss(self, x):
        y, logdet = self(x)

        #손실함수는 타깃 가우스 분포와 야코비 행렬식의 로그 값으로 결정되는 변환된 데이터의 음의 로그 확률의 합. 
        log_likelihood = self.distribution.log_prob(y) + logdet
        return -tf.reduce_mean(log_likelihood)

    def train_step(self, data):
        with tf.GradientTape() as tape:
            loss = self.log_loss(data)
        g = tape.gradient(loss, self.trainable_variables)
        self.optimizer.apply_gradients(zip(g, self.trainable_variables))
        self.loss_tracker.update_state(loss)
        return {&quot;loss&quot;: self.loss_tracker.result()}

    def test_step(self, data):
        loss = self.log_loss(data)
        self.loss_tracker.update_state(loss)
        return {&quot;loss&quot;: self.loss_tracker.result()}


model = RealNVP(
    input_dim=INPUT_DIM,
    coupling_layers=COUPLING_LAYERS,
    coupling_dim=COUPLING_DIM,
    regularization=REGULARIZATION,
)</code></pre><h4 id="출력-결과">출력 결과</h4>
<p>&lt;훈련하기 전&gt;
<img src="https://velog.velcdn.com/images/iumiere-on/post/2c267931-fe5b-4cb4-9ecb-4aff6bc04d08/image.png" alt=""></p>
<p>&lt;훈련한 후&gt;
<img src="https://velog.velcdn.com/images/iumiere-on/post/4a0d31bb-08b6-4662-aa29-a104ee096dc5/image.png" alt="">
가우스 분포에서 샘플링한 포인트가 원본 데이터인 x와 매우 유사함을 알 수 있다!</p>
<h2 id="64-다른-노멀라이징-플로-모델">6.4 다른 노멀라이징 플로 모델</h2>
<p>다른 모델들은 이것에서 조금더 발전시킨 것이고 다른 모델들까지 설명하기에는 너무 복잡해서 차이점만 간단히 정리하고 넘어가겠다. </p>
<h3 id="641-glow-모델">6.4.1 GLOW 모델</h3>
<p>기존 RealNVP에 이미지를 적용하면 단계마다 채널의 순서가 바뀌면서 신경망이 모든 입력을 변환할 가능성이 있다는 단점이 있다.
이를 개선하기 위해 
GLOW에서는 </p>
<blockquote>
<p>1X1 합성곱을 적용해서 모델이 원하는 채널 순서대로 조합을 생성한다. </p>
</blockquote>
<h3 id="642-ffjord">6.4.2 FFJORD</h3>
<p>FFJORD는 RealNVP와 GLOW의 한계였던 discrete time을 continuous time으로 발절시켰다. 
즉, </p>
<blockquote>
<p>연속적인 시간 과정으로 변환을 모델링하는 과정을 담았다. 이때
p(z(t))함수를 사용해 데이터 분포를 표준 가우스 분포로 변환한다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성 AI - Ch5. autoregressive model(자기회귀 모델)]]></title>
            <link>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch5.-autoregressive-model%EC%9E%90%EA%B8%B0%ED%9A%8C%EA%B7%80-%EB%AA%A8%EB%8D%B8</link>
            <guid>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch5.-autoregressive-model%EC%9E%90%EA%B8%B0%ED%9A%8C%EA%B7%80-%EB%AA%A8%EB%8D%B8</guid>
            <pubDate>Wed, 22 May 2024 08:02:11 GMT</pubDate>
            <description><![CDATA[<p>오늘은 autoregressive model을 다루려고 한다. 
5.1은 자기회귀모델에 관한 비유이므로 넘어가겠다 !</p>
<p>목차는 다음과 같다. </p>
<blockquote>
<p>5.2 LSTM 네트워크
5.3 RNN 확장
5.4 PixelCNN</p>
</blockquote>
<p>오늘은 4장에서 배웠던 GAN에 비해 비교적 간단하니 집중해서 빠르게 정리해보자!</p>
<h1 id="52-lstm-네트워크">5.2 LSTM 네트워크</h1>
<p>LSTM은 RNN(Recurrent neural network, 순환신경망)의 한 종류이다. 
LSTM에 대해 설명하기에 앞서 RNN에 대한 간단한 설명을 해보자면</p>
<blockquote>
<p>RNN은 </p>
</blockquote>
<ul>
<li><strong>순차 데이터를 처리하는 순환 층(RNN에서는 cell이라고 부름)</strong>을 이용해 특정 time step에서 출력한 셀의 출력을 다음 time step 입력에 사용한다. 
(tanh 함수 이용 -&gt; 결과가 [-1,1])<pre><code>- gradient vanishing(그레디언트 감소) 문제 발생 -&gt;sequence가 긴 데이터에는 잘 맞지 않음  </code></pre></li>
</ul>
<p>기존 RNN에서 gradient vanishing 문제를 보완하기 위해 LSTM 을 개발했다. </p>
<h2 id="522-텍스트-데이터-다루기">5.2.2 텍스트 데이터 다루기</h2>
<p> 기존에 우리가 다루던 이미지 데이터셋과 비교했을 때 텍스트 데이터셋의 특징을 살펴보자. </p>
<ol>
<li>텍스트 데이터는 이산적 데이터이므로 일반적으로 <strong>역전파</strong>를 적용할 수 없다. </li>
</ol>
<ul>
<li>이미지 데이터셋: 이미지의 픽셀은 연속적인 스펙트럼 -&gt; 역전파(back propagation) 적용 쉬움</li>
<li>텍스트 데이터셋: 개별적인 데이터 조각(문자/단어)로 구성 -&gt; 역전파 적용 어려움. </li>
</ul>
<ol start="2">
<li><p>텍스트 데이터에는 <strong>시간 차원</strong>이 존재한다.</p>
<ul>
<li>이미지: 2개의 공간 차원 O, 시간 차원 X -&gt; 동시에 처리 가능</li>
<li>텍스트: 시간 차원 O, 공간 차원 X -&gt; 순서에 의존성 O</li>
</ul>
<ol start="3">
<li><p>텍스트 데이터는 개별 단위(단어/문자)의 변화에 민감</p>
</li>
<li><p>텍스트 데이터는 규칙 기반의 문법 구조 존재. </p>
</li>
</ol>
</li>
</ol>
<h2 id="523-토큰화">5.2.3 토큰화</h2>
<ul>
<li>토큰화: 텍스트를 개별 단위(단어/문자)로 나누는 작업<ul>
<li>문자 토큰</li>
<li>단어 토큰</li>
</ul>
</li>
</ul>
<p>다음은 토큰화 코드이다.</p>
<pre><code># 구두점을 분리하여 별도의 &#39;단어&#39;로 취급합니다.
def pad_punctuation(s):
    #-&gt;이 표현식은 string.punctuation에 있는 모든 구두점 문자 주위에 앞뒤로 공백을 추가합니다.
    # ([{string.punctuation}]): string.punctuation 내의 모든 문자를 대괄호 [ ] 안에 넣어 문자 집합을 생성하고, 이를 캡처 그룹 ( )으로 감싸 캡처합니다.
    # r&quot; \1 &quot;: \1는 첫 번째 캡처 그룹에 해당하는 문자를 참조합니다. 즉, 각 구두점 문자 양쪽에 공백을 추가하도록 합니다.
    s = re.sub(f&quot;([{string.punctuation}])&quot;, r&quot; \1 &quot;, s)

    # re.sub(&quot; +&quot;, &quot; &quot;, s): 이 표현식은 연속된 하나 이상의 공백을 하나의 공백으로 대체합니다.
    #  여기서 +는 &quot;하나 이상&quot;을 의미하는 정규 표현식 퀀티파이어입니다.
    #-&gt;중복 공백을 정리. 
    s = re.sub(&quot; +&quot;, &quot; &quot;, s)
    return s


text_data = [pad_punctuation(x) for x in filtered_data]


# 텐서플로 데이터셋으로 변환하기
text_ds = (
    tf.data.Dataset.from_tensor_slices(text_data)
    .batch(32)
    .shuffle(1000)
)

# 벡터화 층 만들기
vectorize_layer = layers.TextVectorization(
    standardize=&quot;lower&quot;, #텍스트 소문자로 변환 
    max_tokens=10000, output_mode=&quot;int&quot;, #가장 자주 등장하는 10,000개에 단어에 정수 부여 
    output_sequence_length=200 + 1, #시퀀스 길이가 201개의 토큰이 되도록 1. 자르기 2. 패딩 진행
)

# 훈련 세트에 층 적용
vectorize_layer.adapt(text_ds)
vocab = vectorize_layer.get_vocabulary()</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/41e70487-2fb8-4530-8955-a4c921752898/image.png" alt="">
윗쪽 출력은 TextVectorization을 적용한 결과이다.
아랫쪽 출력은 해당 문장을 토큰화한 결과이다. </p>
<h2 id="524-데이터셋-만들기">5.2.4 데이터셋 만들기</h2>
<p>단어의 시퀀스가 주어지면 다음 단어를 예측하도록 전체 시퀀스 중 한 토큰을 이동시킴. </p>
<pre><code># 레시피와 한 단어 이동한 동일 텍스트로 훈련 세트를 만듭니다.
def prepare_inputs(text):
    text = tf.expand_dims(text, -1)
    tokenized_sentences = vectorize_layer(text)

    x = tokenized_sentences[:, :-1]
    # 이 코드는 변환된 토큰화된 문장들에서 마지막 토큰을 제외한 모든 토큰을 포함하는 새로운 배열 x를 생성합니다. 
    # x는 모델이 입력으로 사용할 데이터로, 모델은 이 데이터를 바탕으로 다음 토큰을 예측하는 학습을 수행합니다.

    y = tokenized_sentences[:, 1:]
    # y는 x의 각 시퀀스보다 한 위치씩 뒤로 옮겨진 토큰을 포함합니다. 
    # 이는 x에 주어진 시퀀스 다음에 등장할 토큰, 즉 라벨 데이터를 나타냅니다. 
    # 모델은 이 y 데이터를 정답으로 사용하여 예측의 정확성을 학습합니다.


    return x, y


train_ds = text_ds.map(prepare_inputs) #입력 토큰과 동일하지만 한 토큰 이동된 벡터. </code></pre><h2 id="526-임베딩-층">5.2.6 임베딩 층</h2>
<p>임베딩 층은 각 정수 토큰을 embedding size길이의 벡터로 변환하는 lookup table 이다. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/e1bf85ab-c051-409d-bc1d-0df514aaed4f/image.png" alt="">
(출처: <a href="https://www.google.com/url?sa=i&amp;url=https%3A%2F%2Fwikidocs.net%2F33793&amp;psig=AOvVaw1zopwe5ldZMgizoRe7Dlxk&amp;ust=1716446973264000&amp;source=images&amp;cd=vfe&amp;opi=89978449&amp;ved=0CBIQjRxqFwoTCOi97rPVoIYDFQAAAAAdAAAAABAJ">https://www.google.com/url?sa=i&amp;url=https%3A%2F%2Fwikidocs.net%2F33793&amp;psig=AOvVaw1zopwe5ldZMgizoRe7Dlxk&amp;ust=1716446973264000&amp;source=images&amp;cd=vfe&amp;opi=89978449&amp;ved=0CBIQjRxqFwoTCOi97rPVoIYDFQAAAAAdAAAAABAJ</a>)</p>
<ul>
<li>integer: 토큰의 번호를 의미</li>
<li>lookup table의 가로의 길이: 임베딩 크기(=embedding size)</li>
<li>lookup table의 새로의 길이: 토큰의 수</li>
<li>lookup 벡터: lookup table의 구성요소로, 모델에 의해 학습되는 가중치이다. 벡터의 총 개수는 가로x세로=(임베딩 크기)x(토큰의 수)</li>
</ul>
<h2 id="527-lstm-층">5.2.7 LSTM 층</h2>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/13b070ac-3491-45d9-bf55-5d34b5119ed2/image.png" alt="">
(출처: <a href="https://blog.kakaocdn.net/dn/bt6eUK/btrq58ZGMlu/5z9Tu63GScTwhPVXBDxJmK/img.png">https://blog.kakaocdn.net/dn/bt6eUK/btrq58ZGMlu/5z9Tu63GScTwhPVXBDxJmK/img.png</a>)
LSTM 층의 핵심은 <strong>은닉층(hidden state)</strong>이다. 
은닉층의 흐름은 시퀀스가 순환층에 주입된 과정인데 이에 대해서 자세히 이해해보자.</p>
<blockquote>
<p><strong>h_{t-1}</strong>(이전 은닉 상태) + <strong>x_t</strong>(현재 time step에서의 데이터) = <strong>h_{t}</strong>(현재 time step에서의 은닉 상태)
=&gt;이전 층의 은닉 상태와 현재 time step에서의 데이터가 셀의 입력으로 들어가 현재 은닉 상태를 출력한다. </p>
</blockquote>
<p>*이 그림에서 LSTM이 써진 박스가 cell이다. </p>
<h2 id="528-lstm-셀">5.2.8 LSTM 셀</h2>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/c4a3d692-282b-43bb-9103-810f77c6976e/image.png" alt="">
(출처: <a href="https://wikidocs.net/images/page/152773/2.JPG">https://wikidocs.net/images/page/152773/2.JPG</a>)</p>
<h4 id="각-gate의-역할">각 gate의 역할</h4>
<ul>
<li>forget gate: 얼마나 잊어버릴지 결정. 즉 이전 셀 상태 C_{t-1}을 얼마나 유지할지 결정</li>
<li>input gate: 이전 셀 상태 C_{t-1}에 얼마나 새로운 정보를 추가할지 결정</li>
<li>output gate: 업데이트된 셀 상태 C_t를 셀의 출력으로 얼마나 보낼지 결정</li>
</ul>
<h4 id="단계별-흐름">단계별 흐름</h4>
<p> <a href="https://wikidocs.net/152773">https://wikidocs.net/152773</a></p>
<h2 id="529-lstm-훈련">5.2.9 LSTM 훈련</h2>
<pre><code>inputs = layers.Input(shape=(None,), dtype=&quot;int32&quot;)

#토큰의 수와 임베딩 벡터의 차원을 임베딩 층에 input으로 줌.
x = layers.Embedding(10000, 100)(inputs)

#LSTM의 은닉층에는 은닉 벡터의 차원을 128개로 주고, 전체 타임 스텝의 은닉 상태 반환하도록 설정. 
x = layers.LSTM(128, return_sequences=True)(x)

#각 타임 스텝의 은닉 상태를 토큰에 대한 확률 벡터로 지정. 
outputs = layers.Dense(100000, activation=&quot;softmax&quot;)(x)
lstm = models.Model(inputs, outputs)


#SparseCategoricalCrossentropy는 다중 클래스 분류 문제에 자주 사용되는 손실 함수로, 
#실제 레이블이 정수 형태로 제공될 때 적합
loss_fn = losses.SparseCategoricalCrossentropy()

lstm.compile(&quot;adam&quot;, loss_fn)
lstm.fit(train_ds, epochs=25)
</code></pre><p> 여기서 토큰의 수와 시퀀스 길이에 대한 개념을 잠깐 정리해보자.</p>
<blockquote>
<ul>
<li><strong>토큰의 수</strong>: 이는 주어진 데이터의 구성 단위인 토큰들의 총 개수를 의미합니다. 토큰은 단어, 문자 등입니다. </li>
</ul>
</blockquote>
<ul>
<li><p><strong>시퀀스 길이</strong>: 이는 LSTM 모델에 입력되는 시퀀스(데이터의 연속적인 열)의 길이를 의미합니다. 시퀀스 길이는 모델이 한 번에 처리할 수 있는 토큰의 수를 지정합니다. 자연어 처리에서 하나의 문장이나 문단을 모델에 입력할 때, 그 문장 또는 문단의 토큰 수가 시퀀스 길이가 됩니다.</p>
<p>예를 들어 토큰의 수가 10,000개일 때 시퀀스 길이가 200이라면 전체 토큰의 수는 10,000이고 한 번에 200개의 토큰을 처리한다는 의미이다. </p>
</li>
</ul>
<h2 id="5210-lstm-분석">5.2.10 LSTM 분석</h2>
<p>아래 코드는 훈련 epoch이 끝날 때 텍스트를 생성하는 콜백함수이다.</p>
<pre><code># TextGenerator 체크포인트 만들기
class TextGenerator(callbacks.Callback):
    def __init__(self, index_to_word, top_k=10):
        self.index_to_word = index_to_word
        self.word_to_index = { #1. 단어-&gt; 토큰 역매핑
            word: index for index, word in enumerate(index_to_word)
        }

    def sample_from(self, probs, temperature):  #2. temeperature 매개변수 사용해 확률 업데이트 
        probs = probs ** (1 / temperature)
        probs = probs / np.sum(probs)
        return np.random.choice(len(probs), p=probs), probs

    def generate(self, start_prompt, max_tokens, temperature):
        start_tokens = [ #3. start_prompt는 생성과정을 시작하기 위해 모델에 제공하는 단어의 문자열. 단어는 토큰의 리스트로 변환된다.  
            self.word_to_index.get(x, 1) for x in start_prompt.split()
        ]
        sample_token = None
        info = []
        while len(start_tokens) &lt; max_tokens and sample_token != 0:
            x = np.array([start_tokens])
            y = self.model.predict(x, verbose=0) #4. 모델은 시퀀스의 다음에 나올 단어의 확률을 출력한다. 
            sample_token, probs = self.sample_from(y[0][-1], temperature) #5. 윗줄 코드서의 확률은 sample_from 메서드로 전달돼 temperature 기반으로 단어 선택. 
            info.append({&quot;prompt&quot;: start_prompt, &quot;word_probs&quot;: probs})
            start_tokens.append(sample_token) #6. 생성과정 반복 위해 새로운 단어를 프롬프트 텍스트에 추가. 
            start_prompt = start_prompt + &quot; &quot; + self.index_to_word[sample_token]
        print(f&quot;\n생성된 텍스트:\n{start_prompt}\n&quot;)
        return info

    def on_epoch_end(self, epoch, logs=None):
        self.generate(&quot;recipe for&quot;, max_tokens=100, temperature=1.0)</code></pre><p>temperature 매개변수 값에 따른 학습의 차이를 보자. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/335b25e4-b769-4855-8351-cf8611e01c46/image.png" alt=""></p>
<ul>
<li>temperature=1.0인 경우<ul>
<li>더 모험적/ 다양함 -&gt; 정확도 낮</li>
</ul>
</li>
<li>temperature=0.2인 경우<ul>
<li>정확도 높다.<ul>
<li>첫 번째 선택 초큰에 훨씬 많은 가중치 부여 -&gt; 다양성 적음.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p> 자! 이제 LSTM을 마무리하며 의의와 단점을 살펴보자. </p>
<blockquote>
<p> LSTM 모델은 여러 맥락에 맞게 가장 가능성 있는 다음 단어의 분포를 생성할 수 있지만 단어의 의미를 모른다는 단점이 있다. </p>
</blockquote>
<h1 id="53-rnn의-확장">5.3. RNN의 확장</h1>
<h2 id="531-적층-순환-네트워크">5.3.1 적층 순환 네트워크</h2>
<p>기존 LSTM의 층의 개수는 1개 -&gt; 이 층을 여러 개 쌓으면 &#39;적층 순환 네트워크&#39;가 됨.  </p>
<h2 id="532-gru-층">5.3.2 GRU 층</h2>
<p>GRU(Gated Recurrent Unit)은 LSTM 셀과 2가지 차이점을 보인다. 
    1. 삭제 게이트 -&gt; 리셋 게이트, 입력 게이트 -&gt; 업데이트 게이트
    2. 셀 상태와 출력 게이트 제거. 셀은 은닉 상태만 출력. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/40acc1ce-e62d-4c1d-a029-890c9c44b1a1/image.png" alt=""></p>
<h2 id="533-양방향-셀">5.3.3 양방향 셀</h2>
<p>Bidirectional 층은 전체 텍스트를 바탕으로 추론하는 문제에서 사용하기 위해 만들어짐. 
기존에 사용하던 forward 방향 &amp; back 방향을 둘 다 사용 
-&gt; RNN이 time step의 앞뒤에서 모두 학습할 수 있다.</p>
<p>*GRU층의 은닉 상태에서의 셀 유닛의 개수는 한 방향 셀(기존에 사용하던) 유닛개수의 두 배이다.</p>
<h1 id="54-pixelcnn">5.4 PixelCNN</h1>
<p>다음은 PixelCNN에 대해 배워보겠다! 안뇽!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성 AI - Ch4. GAN(생성적 적대 신경망)]]></title>
            <link>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch4.-GAN%EC%83%9D%EC%84%B1%EC%A0%81-%EC%A0%81%EB%8C%80-%EC%8B%A0%EA%B2%BD%EB%A7%9D</link>
            <guid>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch4.-GAN%EC%83%9D%EC%84%B1%EC%A0%81-%EC%A0%81%EB%8C%80-%EC%8B%A0%EA%B2%BD%EB%A7%9D</guid>
            <pubDate>Wed, 15 May 2024 12:01:51 GMT</pubDate>
            <description><![CDATA[<p>오늘은 GAN에 대해 배워보자!</p>
<p>4장의 목차는 </p>
<blockquote>
</blockquote>
<p>4.1 소개
4.2 심층 합성곱 GAN(DCGAN)
4.3 와서스테인 GAN-그레디언트 페널티(WGAN-GP)
4.4 조건부 GAN(CGAN)</p>
<p>으로 구성되어 있다. </p>
<h3 id="41-소개">4.1 소개</h3>
<p>우선 GAN은 Generative Adversarial Neworks의 약자로, &#39;생성적 적대 신경망&#39;이라고도 한다. </p>
<p>GAN을 소개할 때 가장 많이 드는 예시는 바로 경찰과 위조범인데, 이 책에서도 유사하게 회사의 제품을 위조하는 위조범 vs. 위조 제품을 탐지하는 회사 간 경쟁 구도를 들어 GAN을 설명하고 있다. </p>
<p><strong>GAN</strong>은 <strong>생성자(generator)</strong>과 <strong>판별자(discriminator)</strong> 간의 싸움인데, 앞서 예시로 들었던 생성자는 회사의 제품을 위조하는 _위조범_이고 판별자는 위조범이 생상한 제품과 자회사의 제품을 판별해내는 _회사_이다. </p>
<p>둘의 역할을 간단하게 말하자면!</p>
<ul>
<li><strong>생성자</strong>는 잡읍에서 원래 데이터셋에서 샘플링한 것처럼 보이는 샘플을 반환</li>
<li><strong>판별자</strong>는 샘플이 샘플의 진위를 식별 </li>
</ul>
<h3 id="42-심층-합성곱-gandcgan">4.2 심층 합성곱 GAN(DCGAN)</h3>
<h4 id="421-레고-블록-데이터셋">4.2.1 레고 블록 데이터셋</h4>
<p> 자 이제 초기 GAN 논문에서 다뤘던 DCGAN으로 벽돌 사진을 생성해보자! </p>
<p>우선 우리는 캐글의 레고 블록 이미지 데이터셋을 이용할 것이다. 
코드를 그대로 복사해서 사용하면 내 출력과 같은 결과를 얻을 수 있다. </p>
<pre><code>#필요한 라이브러리를 import
import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf
from tensorflow.keras import (
    layers,
    models,
    callbacks,
    losses,
    utils,
    metrics,
    optimizers,
)

from notebooks.utils import display, sample_batch

#캐글에서 레고 데이터셋 다운로드
 from google.colab import files
 files.upload()

 !mkdir ~/.kaggle
 !cp kaggle.json ~/.kaggle/
 !chmod 600 ~/.kaggle/kaggle.json

!kaggle datasets download -d joosthazelzet/lego-brick-images
!unzip -q lego-brick-images.zip

!mkdir output

#다운받은 이미지 파일을 텐서플로 데이터로 만들기

train_data = utils.image_dataset_from_directory(
    &quot;./dataset/.&quot;,  //파일이 dataset 폴더 아래에 있다는 의미!
    labels=None,
    color_mode=&quot;grayscale&quot;,
    image_size=(64, 64),
    batch_size=128,
    shuffle=True,
    seed=42,
    interpolation=&quot;bilinear&quot;,
)

#이미지 전처리 -  이미지 정규화 및 크기 변경

def preprocess(img):

    img = (tf.cast(img, &quot;float32&quot;) - 127.5) / 127.5
    return img

train = train_data.map(lambda x: preprocess(x))

</code></pre><p>이미지 전처리 단계에 대한 설명을 해보자!</p>
<p>우선 우리의 목표는 생성자 마지막 층(layer)에서 tanh 활성화 함수를 사용하는 것이 목표이다. 이 _tanh 활성화 함수_를 사용하기 위해 이미지 스케일을 [-1, 1]범위로 조정하는 것이다!</p>
<p>아래는 tanh 활성화 함수에 대한 간단한 설명이다.</p>
<blockquote>
<p>*<em>tanh활성화 함수란? *</em>
<img src="https://velog.velcdn.com/images/iumiere-on/post/fe82d703-ea39-450b-a7c0-000b83f98d45/image.png" alt="">
딥러닝에서 주로 쓰는 활성화함수의 모음이다. 이 중 tanh 함수는 출력이 [-1,1] 사이이며, 시그모이드 함수보다 더 강한 그레디언트(gradient)를 제공한다.
그렇다면 tanh함수를 사용하기 위해 데이터셋의 범위를 [-1,1]로 조정하는 이유가 무엇일까?
다양한 이유가 있지만 가장 큰 이유는 <em>활성화 함수의 일관성을 위해서이다!</em> 
_입력데이터와 출력 데이터가 같은 범위_를 가지면 모델이 더 일관되게 학습할 수 있기 때문!</p>
</blockquote>
<h4 id="422-판별자">4.2.2 판별자</h4>
<p>이제 진위를 가려내는 판별자에 대해 배워보자. </p>
<pre><code>discriminator_input = layers.Input(shape=(IMAGE_SIZE, IMAGE_SIZE, CHANNELS))
x = layers.Conv2D(64, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False)(
    discriminator_input
)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
    128, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
    256, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
    512, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
    1,
    kernel_size=4,
    strides=1,
    padding=&quot;valid&quot;,
    use_bias=False,
    activation=&quot;sigmoid&quot;,
)(x)
discriminator_output = layers.Flatten()(x)

discriminator = models.Model(discriminator_input, discriminator_output)
discriminator.summary()</code></pre><p>판별자에서는 </p>
<ul>
<li><p>convolutional layer: 
합성곱층 5개를 사용하고 각 합성곱층의 strides=2이기 때문에 각 합성곱층을 지날 때마다 텐서의 크기가 64-&gt; 32 -&gt; 16 -&gt; 8 -&gt; 4 -&gt; 1 로 줄어든다. </p>
<ul>
<li>마지막 conv층에서 텐서의 크기가 1x1x1로 줄어드는 이유는
<code>Conv2D(1, kernel_size=4, strides=1, padding=&#39;valid&#39;)</code> 코드에서 공간 크기가 (4-4+1)x(4-4+1)=1x1이 되기 때문!</li>
</ul>
</li>
<li><p>sigmoid 활성화 함수:
결과가 [0,1] 사이로 출력된다. (참 or 거짓 판별위해 단일 확률 값을 출력!)</p>
</li>
<li><p>마지막에 Dense층이 없는 이유:
마지막 conv층을 거쳤을 때 텐서의 크기가 1x1x1이기 때문. </p>
</li>
</ul>
<h4 id="423-생성자">4.2.3 생성자</h4>
<p>생성자 코드를 설명하기에 앞서 생성자의 입력에 대해 알아보자. </p>
<blockquote>
<p>DCGAN의 Generator에서 입력으로 사용되는 벡터(길이가 100)은 latent space에서 샘플링된 벡터이다. 
이 벡터는 랜덤 노이즈 벡터로, 생성기가 이 벡터를 입력으로 받아들인 뒤 가짜 이미지를 생성한다. </p>
</blockquote>
<p>아래는 latent space vector 생성 예제에 대해 알아보겠다. </p>
<pre><code>import numpy as np

# 잠재 공간 벡터의 차원
latent_dim = 100

# 잠재 공간 벡터 생성 (예: 배치 크기 16)
batch_size = 16
random_latent_vectors = np.random.normal(0, 1, (batch_size, latent_dim))
</code></pre><p>자 그럼 이제 본격적인 생성자 코드에 대해 알아보겠다. </p>
<pre><code>#latent space vector의 입력
generator_input = layers.Input(shape=(100,))

# 잠재 공간 벡터를 1x1x100 크기로 변환 //Z_DIM=100
x = layers.Reshape((1, 1, Z_DIM))(generator_input)

#첫번째 합성곱층: 1X1 -&gt;4X4
x = layers.Conv2DTranspose(
    512, kernel_size=4, strides=1, padding=&quot;valid&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)

#2번째 합성곱층: 4X4 -&gt; 8X8
x = layers.Conv2DTranspose(
    256, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)

#3번째 합성곱층: 8X8 -&gt; 16X16
x = layers.Conv2DTranspose(
    128, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)

#4번째 합성곱층: 16X16 -&gt; 32X32
x = layers.Conv2DTranspose(
    64, kernel_size=4, strides=2, padding=&quot;same&quot;, use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)

#마지막 합성곱층: 32X32 -&gt; 64X64
generator_output = layers.Conv2DTranspose(
    1,
    kernel_size=4,
    strides=2,
    padding=&quot;same&quot;,
    use_bias=False,
    activation=&quot;tanh&quot;,
)(x)
generator = models.Model(generator_input, generator_output)
generator.summary()</code></pre><h4 id="424-dcgan-훈련">4.2.4 DCGAN 훈련</h4>
<p>판별자와 생성자를 공부했으니 이제는 모델의 핵심인 훈련과정에 대해 알아보자!</p>
<p>훈련과정은 훈련 세트의 진짜 샘플과 생성자의 출력을 합쳐서 훈련 세트를 만든 뒤 지도학습을 하는 것으로 이루어져있다. </p>
<p>이 지도학습에서 진짜 이미지 레이블=1, 가짜 이미지 레이블=0으로 학습을 진행하며 손실함수로는 이진 크로스 엔트로피를 사용한다. </p>
<p>다음은 생성함수의 훈련과정에 대해 알아보자! </p>
<blockquote>
<p>우선 생성된 이미지에 점수를 부여한 뒤 높은 점수를 낸 이미지를 최적화한다는 것이 핵심이다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/88ddc93e-2eb2-49fb-b25b-5285057c32c1/image.png" alt="">
[출처: <a href="https://www.researchgate.net/figure/The-architecture-of-vanilla-GANs_fig1_340458845%5D">https://www.researchgate.net/figure/The-architecture-of-vanilla-GANs_fig1_340458845]</a></p>
<p>이 그림을 통해 알 수 있듯이 <strong>생성자</strong>가 noise에서 생성한 이미지를 <strong>판별자</strong>가 진위(real or fake)인지 가려내는 방식으로 훈련이 진행되며 이 과정에서 <strong>생성자와 판별자의 가중치를 업데이트</strong>하는 것이 훈련의 핵심이다. </p>
<pre><code>class DCGAN(models.Model):
    def __init__(self, discriminator, generator, latent_dim):
        super(DCGAN, self).__init__()
        self.discriminator = discriminator
        self.generator = generator
        self.latent_dim = latent_dim

    def compile(self, d_optimizer, g_optimizer):
        super(DCGAN, self).compile()
        self.loss_fn = losses.BinaryCrossentropy()
        self.d_optimizer = d_optimizer
        self.g_optimizer = g_optimizer
        self.d_loss_metric = metrics.Mean(name=&quot;d_loss&quot;)
        self.d_real_acc_metric = metrics.BinaryAccuracy(name=&quot;d_real_acc&quot;)
        self.d_fake_acc_metric = metrics.BinaryAccuracy(name=&quot;d_fake_acc&quot;)
        self.d_acc_metric = metrics.BinaryAccuracy(name=&quot;d_acc&quot;)
        self.g_loss_metric = metrics.Mean(name=&quot;g_loss&quot;)
        self.g_acc_metric = metrics.BinaryAccuracy(name=&quot;g_acc&quot;)

    @property
    def metrics(self):
        return [
            self.d_loss_metric,
            self.d_real_acc_metric,
            self.d_fake_acc_metric,
            self.d_acc_metric,
            self.g_loss_metric,
            self.g_acc_metric,
        ]

    def train_step(self, real_images):
        # 잠재 공간에서 랜덤 포인트 샘플링
        batch_size = tf.shape(real_images)[0]
        random_latent_vectors = tf.random.normal(
            shape=(batch_size, self.latent_dim)
        )

        # 가짜 이미지로 판별자 훈련하기
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
            generated_images = self.generator(
                random_latent_vectors, training=True
            )
            real_predictions = self.discriminator(real_images, training=True)
            fake_predictions = self.discriminator(
                generated_images, training=True
            )

            real_labels = tf.ones_like(real_predictions)
            real_noisy_labels = real_labels + NOISE_PARAM * tf.random.uniform(
                tf.shape(real_predictions)
            )
            fake_labels = tf.zeros_like(fake_predictions)
            fake_noisy_labels = fake_labels - NOISE_PARAM * tf.random.uniform(
                tf.shape(fake_predictions)
            )

            d_real_loss = self.loss_fn(real_noisy_labels, real_predictions)
            d_fake_loss = self.loss_fn(fake_noisy_labels, fake_predictions)
            d_loss = (d_real_loss + d_fake_loss) / 2.0

            g_loss = self.loss_fn(real_labels, fake_predictions)

        gradients_of_discriminator = disc_tape.gradient(
            d_loss, self.discriminator.trainable_variables
        )
        gradients_of_generator = gen_tape.gradient(
            g_loss, self.generator.trainable_variables
        )

        self.d_optimizer.apply_gradients(
            zip(gradients_of_discriminator, discriminator.trainable_variables)
        )
        self.g_optimizer.apply_gradients(
            zip(gradients_of_generator, generator.trainable_variables)
        )

        # 메트릭 업데이트
        self.d_loss_metric.update_state(d_loss)
        self.d_real_acc_metric.update_state(real_labels, real_predictions)
        self.d_fake_acc_metric.update_state(fake_labels, fake_predictions)
        self.d_acc_metric.update_state(
            [real_labels, fake_labels], [real_predictions, fake_predictions]
        )
        self.g_loss_metric.update_state(g_loss)
        self.g_acc_metric.update_state(real_labels, fake_predictions)

        return {m.name: m.result() for m in self.metrics}
</code></pre><h4 id="425-분석">4.2.5 분석</h4>
<p>  위 그림에서 epoch가 진행됨에 따라 생성자가 이미지를 더 정교하게 추출한다는 것을 알 수 있다. </p>
<p>  GAN의 훈련과정에서 중요한 것은 단순히 생성자가 이미지를 추출하는 것이 아니라 원본 데이터에 있는 것 같은 이미지를 추출하는 것인데, 이를 위해서 진짜 이미지와 생성자가 만든 가짜 이미지 사이 거리를 비교해보자. </p>
<h4 id="426-gan-훈련의-팁과-트릭">4.2.6 GAN 훈련의 팁과 트릭</h4>
<p>GAN에서 훈련을 진행할 때 문제가 생기는 경우는 다음과 같다. </p>
<ul>
<li>핀별자가 생성자보다 훨씬 뛰어난 경우</li>
<li>생성자가 판별자보다 훨씬 뛰어난 경우</li>
<li>생성자의 손실함수의 무쓸모</li>
<li>많은 하이퍼파라미터</li>
</ul>
<h3 id="43-wgan-gp와서-스테인-gan-그레디언트-패널티">4.3 WGAN-GP(와서 스테인 GAN-그레디언트 패널티)</h3>
<p>2017년에 공개된 WGAN-GP를 다룬 논문은 기존의 손실함수인 이진 크로스 엔트로피가 아닌 와서스테인 손실함수(Wasserstein Loss Function)을 사용해 GAN의 안정도를 높였다. </p>
<h4 id="431-wasserstein-loss-function">4.3.1 Wasserstein Loss Function</h4>
<p>손실함수 관련 내용이 꽤 복잡해 직접 필기하며 정리해보았다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/a904d4ae-3593-4343-935f-1bba30276e28/image.jpg" alt=""></p>
<p>필기에 있는 것처럼 와서스테인 손실함수는 기존의 손실함수와 다음과 같은 차이점이 있다. 
1.타켓값 변화
2.시그모이드 활성화 함수를 제거 -&gt; log 사용 x -&gt; 결과값의 범위가 무한대!</p>
<h4 id="432-립시츠-제약">4.3.2 립시츠 제약</h4>
<p>앞에서 와서스테인 손실함수에 배웠다. 하지만 와서스테인 손실함수의 값은 범위가 너무 커서 비평자(critic; discriminator와 똑같음)에 제약을 걸어야 한다. 이를 위해 비평자는 1-립시츠 연속 함수(1-Lipschitz continuous func)이여야 한다.</p>
<blockquote>
<p>립시츠 함수는 임의의 두 지점의 기울기가 어떤 상숫값 이상으로 증가하지 않는 함수이다.
상수가 1일 때 &quot;1-립시츠 함수&quot;라고 부른다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/609ad9ac-7369-4513-b167-eb882a2ddfa9/image.png" alt="">
위 함수는 1-립시츠 함수이며 더 설명을 해보자면</p>
<ul>
<li><p>D: 비평자를 의미</p>
</li>
<li><p>|D(x1)-D(x2)|: 비평자 예측 간의 절댓값 차이</p>
</li>
<li><p>|x1-x2|: 두 이미지 픽셀의 평균적인 절댓값 차이</p>
</li>
<li><blockquote>
<p>&#39;위 수식을 아래 수식으로 나눈 값&lt;=1&#39;이라는 것은 비평자의 예측을 제한한다는 의미로, </p>
</blockquote>
<p>결과적으로 <img src="https://velog.velcdn.com/images/iumiere-on/post/97b345c6-f2f0-4bc2-aaba-52e4c6dfdfc0/image.png" alt="">
위 그림은 직선이 초록색 부분에만 속한다는 것을 보여주며, 기울기가 [-1,1]에 속한다는 것을 알 수 있다. </p>
</li>
</ul>
<h4 id="433-립시츠-제약-부과하기">4.3.3 립시츠 제약 부과하기</h4>
<p>WGAN 논문에서는 비평자의 가중치를 [-0.01, 0.01] 안에 놓이도록 훈련 배치를 한 다음-&gt;<strong>가중치 클리핑(weight clipping)</strong>을 사용해 립시츠 제약을 부과했다.</p>
<p>근데 이렇게 립시츠 제약을 부과하면 비평자의 성능이 약해진다. 
그 이유는 립시츠 제약을 통해 비평자의 함수가 너무 급격하게 변동하는 것을 제한하면, 비평자가 매우 세밀하거나 극단적인 평가를 내리는 능력이 줄어들기 때문이다.
결과적으로 생성자가 실제 데이터 분포를 정확하게 학습하는 데 필요한 충분하고 구체적인 피드백을 받지 못할 수도 있다. </p>
<p>이를 보완하기 위해 <strong>WGAN-gradient penalty</strong>을 비판자에 손실함수에 포함시켰다.</p>
<blockquote>
<p><em>WGAN-gradient penalty</em>란 gradient norm이 1에서 벗어날 경우 모델에 불이익을 주는 것</p>
</blockquote>
<h4 id="434-gradient-penalty-loss">4.3.4 gradient penalty loss</h4>
<p>gradient penalty loss는 (입력 이미지에 대한 그레디언트 노름-1)^2로 학습과정에서 모델이 이 페널티를 최소화하는 가중치를 찾기 때문에 립시츠 제약을 따른다는 점을 알 수 있다. </p>
<p>대신 모든 이미지에 적용하는 것이 아니라 일부 이미지에 선택적으로 적용하며, 이때 진짜 이미지와 가짜 이미지 쌍 사이를 interpolation한 이미지를 이용한다. </p>
<p>아래 코드는 gradient penalty loss 코드이다.</p>
<pre><code>
    def gradient_penalty(self, batch_size, real_images, fake_images):
        alpha = tf.random.normal([batch_size, 1, 1, 1], 0.0, 1.0) //1
        diff = fake_images - real_images
        interpolated = real_images + alpha * diff //2

        with tf.GradientTape() as gp_tape:
            gp_tape.watch(interpolated)
            pred = self.critic(interpolated, training=True) //3

        grads = gp_tape.gradient(pred, [interpolated])[0] //4
        norm = tf.sqrt(tf.reduce_sum(tf.square(grads), axis=[1, 2, 3])) //5
        gp = tf.reduce_mean((norm - 1.0) ** 2) //6
        return gp
</code></pre><ol>
<li>배치에 있는 이미지마다 0~1 사이 랜덤한 숫자 생성해 벡터 alpha에 저장</li>
<li>interpolation 이미지 계산</li>
<li>비평자가 interpolation 이미지에서 예측값 계산</li>
<li>interpolation 이미지에 대해 예측의 그레디언트 계산</li>
<li>벡터의 L2 norm 계산</li>
<li>L2 norm 과 1 사이의 평균 제곱 거리를 반환. </li>
</ol>
<h4 id="435-wgan-gp-훈련">4.3.5 WGAN-GP 훈련</h4>
<pre><code>    def train_step(self, real_images):
        batch_size = tf.shape(real_images)[0]

        for i in range(self.critic_steps):
            random_latent_vectors = tf.random.normal(
                shape=(batch_size, self.latent_dim)
            )

            with tf.GradientTape() as tape:
                fake_images = self.generator(
                    random_latent_vectors, training=True
                )
                fake_predictions = self.critic(fake_images, training=True)
                real_predictions = self.critic(real_images, training=True)

                c_wass_loss = tf.reduce_mean(fake_predictions) - tf.reduce_mean(
                    real_predictions
                )
                c_gp = self.gradient_penalty(
                    batch_size, real_images, fake_images
                )
                c_loss = c_wass_loss + c_gp * self.gp_weight

            c_gradient = tape.gradient(c_loss, self.critic.trainable_variables)
            self.c_optimizer.apply_gradients(
                zip(c_gradient, self.critic.trainable_variables)
            )

        random_latent_vectors = tf.random.normal(
            shape=(batch_size, self.latent_dim)
        )
        with tf.GradientTape() as tape:
            fake_images = self.generator(random_latent_vectors, training=True)
            fake_predictions = self.critic(fake_images, training=True)
            g_loss = -tf.reduce_mean(fake_predictions)

        gen_gradient = tape.gradient(g_loss, self.generator.trainable_variables)
        self.g_optimizer.apply_gradients(
            zip(gen_gradient, self.generator.trainable_variables)
        )

        self.c_loss_metric.update_state(c_loss)
        self.c_wass_loss_metric.update_state(c_wass_loss)
        self.c_gp_metric.update_state(c_gp)
        self.g_loss_metric.update_state(g_loss)

        return {m.name: m.result() for m in self.metrics}
</code></pre><p>&lt;출력 결과&gt;
<img src="https://velog.velcdn.com/images/iumiere-on/post/4f86a1d2-d439-448e-a5f0-cbb29f9895fa/image.png" alt=""></p>
<h3 id="44-cganconditional-gan">4.4 CGAN(Conditional GAN)</h3>
<p>CGAN에서는 생성하려는 이미지의 유형(남성/여성의 얼굴, 금발 등)을 제어할 수 있다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/d0671720-aad6-4c3a-84d2-47d1c6635ed3/image.png" alt="">
(출처:<a href="https://kr.mathworks.com/help/deeplearning/ug/train-conditional-generative-adversarial-network.html">https://kr.mathworks.com/help/deeplearning/ug/train-conditional-generative-adversarial-network.html</a>)</p>
<p>이 그림은 CGAN의 구조를 보여준다.</p>
<ul>
<li>생성자: noise + 원핫 인코딩된 레이블 </li>
<li>판별자: real/generated image + 원핫 인코딩된 레이블 채널</li>
</ul>
<p> 생성자에서는 원핫인코딩된 벡터를 latent space sample에 추가하고 
 판별자에서는 레이블 정보를 기존의 RGB이미지에 추가 채널로 추가한다. </p>
<p> 다음은 CGAN 입력층 코드이다.</p>
<pre><code>critic_input = layers.Input(shape=(32, 32, 3))
label_input = layers.Input(shape=(32, 32, 2)) #레이블이 2개라서 channel 수가 2개!
x = layers.Concatenate(axis=-1)([critic_input, label_input])

...

generator_input = layers.Input(shape=(32,))
label_input = layers.Input(shape=(2,))
x = layers.Concatenate(axis=-1)([generator_input, label_input])
x = layers.Reshape((1, 1, 34))(x) #원한 인코딩 벡터를 기존 input에 추가</code></pre><h4 id="442-cgan-훈련">4.4.2 CGAN 훈련</h4>
<p>자 그럼 본격적으로 CGAN을 훈련해보자.</p>
<pre><code>   def train_step(self, data):
        real_images, one_hot_labels = data #1. 입력데이터에서 이미지와 레이블 분류

        image_one_hot_labels = one_hot_labels[:, None, None, :] #2. 원핫 인코딩된 벡터를 입력이미지의 크기와 같은 원핫 인코딩된 이미지로 확장
        image_one_hot_labels = tf.repeat(
            image_one_hot_labels, repeats=IMAGE_SIZE, axis=1
        )
        image_one_hot_labels = tf.repeat(
            image_one_hot_labels, repeats=IMAGE_SIZE, axis=2
        )

        batch_size = tf.shape(real_images)[0]

        for i in range(self.critic_steps):
            random_latent_vectors = tf.random.normal(
                shape=(batch_size, self.latent_dim)
            )

            with tf.GradientTape() as tape:
                fake_images = self.generator(   #3. 생성자에게 두개의 입력으로 구성된 리스트 주입
                    [random_latent_vectors, one_hot_labels], training=True
                )

                fake_predictions = self.critic( #4. 비평자에게  개의 입력으로 구성된 리스트 주입
                    [fake_images, image_one_hot_labels], training=True
                )
                real_predictions = self.critic(
                    [real_images, image_one_hot_labels], training=True
                )

                c_wass_loss = tf.reduce_mean(fake_predictions) - tf.reduce_mean(
                    real_predictions
                )
                c_gp = self.gradient_penalty( #5. gradient penalty 함수도 비평자를 호출할 때 원핫 인코딩된 레이블 채널이 필요
                    batch_size, real_images, fake_images, image_one_hot_labels
                )
                c_loss = c_wass_loss + c_gp * self.gp_weight

            c_gradient = tape.gradient(c_loss, self.critic.trainable_variables)
            self.c_optimizer.apply_gradients(
                zip(c_gradient, self.critic.trainable_variables)
            )

        random_latent_vectors = tf.random.normal(
            shape=(batch_size, self.latent_dim)
        )

        with tf.GradientTape() as tape:   #6. 생성자의 gradient  계산
            fake_images = self.generator(   #7. 비평자에서 추가로 대입했던 원핫인코딩 레이블은 생성자에도 적용
                [random_latent_vectors, one_hot_labels], training=True
            )
            fake_predictions = self.critic(
                [fake_images, image_one_hot_labels], training=True
            )
            g_loss = -tf.reduce_mean(fake_predictions)

        gen_gradient = tape.gradient(g_loss, self.generator.trainable_variables)
        self.g_optimizer.apply_gradients(
            zip(gen_gradient, self.generator.trainable_variables)
        )

        self.c_loss_metric.update_state(c_loss)
        self.c_wass_loss_metric.update_state(c_wass_loss)
        self.c_gp_metric.update_state(c_gp)
        self.g_loss_metric.update_state(g_loss)

        return {m.name: m.result() for m in self.metrics}</code></pre><h4 id="443-cgan-분석">4.4.3 CGAN 분석</h4>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/bd93e18d-4c27-4805-874a-26a1a430a454/image.png" alt="">
윗쪽 출력: 레이블이 0일 때로, 금발이 아닌 특성을 의미
아랫쪽 출력: 레이블이 1일 때로, 금발인 특성을 의미</p>
<p>위 출력 결과를 통해 GAN에 <em>기존의 랜덤한 latent space vector 에 레이블 벡터를 추가한</em> <strong>CGAN</strong>을 통해 특성이 분리되도록 잠재공간의 포인트를 구성할 수 있다는 것을 알 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성 AI - Ch3. VAE(변이형 오토인코더)]]></title>
            <link>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch3.-VAE%EB%B3%80%EC%9D%B4%ED%98%95-%EC%98%A4%ED%86%A0%EC%9D%B8%EC%BD%94%EB%8D%94</link>
            <guid>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch3.-VAE%EB%B3%80%EC%9D%B4%ED%98%95-%EC%98%A4%ED%86%A0%EC%9D%B8%EC%BD%94%EB%8D%94</guid>
            <pubDate>Mon, 13 May 2024 15:13:20 GMT</pubDate>
            <description><![CDATA[<p>파트2부터는 본격적인 생성 모델링 방식에 대해 배운다.</p>
<p>그럼 오늘은 파트 2(ch.3~ch.8)의 시작인 3장 VAE에 대해 배워보도록 하자!</p>
<p>3장의 구성은 아래와 같다.</p>
<p>3.1 소개
3.2 오토인코더
3.3 VAE(변이형 오토인코더)
3.4 잠재 공간 탐색하기</p>
<p>목차를 보면 알 수 있듯이 오늘은 VAE에 대해 살펴보기 전 오토인코더에 관해 자세히 배우고 이 개념을 VAE에 녹여보도록 하겠다.</p>
<h3 id="31-소개">3.1 소개</h3>
<p>소개에서는 우리가 스타일리스트인 브라이언에게 옷장 속 해당 옷의 위치를 알려주기만 하면 브라이언이 위치를 바탕으로 새로운 옷을 만든다는 가정을 한다. 이렇게 하면 브라이언에게 빈 곳의 위치를 알려주기만 하면 완전히 새로운 옷을 만들 수 있게 된다.</p>
<h3 id="32-오토인코더">3.2 오토인코더</h3>
<p><strong>인코더(Encoder):</strong> 입력 데이터를 받아 잠재 공간의 낮은 차원으로 압축
<strong>디코더(Decoder):</strong> 압축된 잠재 공간의 데이터를 다시 원래의 데이터 공간으로 복원
<strong>임베딩(embedding)</strong>:인코더가 디코더가 정확하게 재구성할 수 있도록 가능한 많은 정보를 내포시키는 과정 </p>
<blockquote>
<p>오토인코더(Autoencoder)는 입력 데이터를 효과적으로 압축(인코딩)하고 다시 복원(디코딩)하기 위해 설계된 인공 신경망입니다. 기본적으로 오토인코더는 비지도 학습 방식으로, 입력 데이터의 효율적인 표현(잠재 공간)을 학습하는 데 사용됩니다. </p>
</blockquote>
<p>이러한 설명을 통해 알 수 있듯이 오토인코더의 목적은 출력이 원본 아이템에 가까워지도록 하는 것이다. 
즉, 잠재공간(latent space)의 모든 포인트를 디코딩해 새로운 데이터를 생성하는 것이 목적이다!</p>
<p>그럼 본격적으로 실습을 시작하겠다. 실습은 패션 MNIST 데이터셋으로 진행된다. </p>
<h4 id="321-데이터-로드--전처리">3.2.1 데이터 로드 + 전처리</h4>
<pre><code># 데이터를 로드합니다.
(x_train, y_train), (x_test, y_test) = datasets.fashion_mnist.load_data()

# 데이터 전처리
def preprocess(imgs):

    imgs = imgs.astype(&quot;float32&quot;) / 255.0 #기존 출력인 0~255 -&gt; 0~1 로 바꿔줌
    imgs = np.pad(imgs, ((0, 0), (2, 2), (2, 2)), constant_values=0.0) ## 이미지에 패딩을 추가해 32x32로 만들어줌. 
    imgs = np.expand_dims(imgs, -1) #이미지를 1차원으로 만들어줌.
    return imgs


x_train = preprocess(x_train)
x_test = preprocess(x_test)</code></pre><h4 id="322-오토인코더-구조">3.2.2 오토인코더 구조</h4>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/83925013-dc2b-482b-b693-09b48e845d84/image.png" alt=""></p>
<ul>
<li>인코더: 이미지 같은 고차원 입력 데이터를 저차원 임베딩 벡터로 압축</li>
<li>디코더: 임베딩 벡터를 원본 도메인으로 압축 해제</li>
</ul>
<p>위 그림을 통해 설명하자면, 
입력이미지가 잠재 임베딩 벡터 z로 인코딩 -&gt; 원본 픽셀 공간으로 디코딩 하는 과정을 거친다.</p>
<p>자 그렇다면 임베딩이란 무엇일까?</p>
<blockquote>
<p>임베딩(z)는 원본 이미지를 저차원 잠재 공간으로 압축하는 것이고, 
이 잠재 공간에서 포인트를 선택해 디코더에 통과시키면 새로운 이미지를 생성한다. </p>
</blockquote>
<p>여기서 잠재공간(latent space)와 잠재 임베딩 벡터(latent embedding vector)의 차이점이 궁금해서 찾아보았다. </p>
<ul>
<li><strong>잠재 공간</strong>은 데이터가 표현되는 추상적인 공간 -&gt;전체적인 차원 축소의 결과</li>
<li><strong>잠재 임베딩 벡터</strong>는 잠재 공간 안에서 개별 데이터 포인트가 위치하는 구체적인 위치나 벡터 -&gt; 특정 데이터 포인트의 위치나 상태를 설명 </li>
</ul>
<h4 id="323-인코더">3.2.3 인코더</h4>
<p>자! 그럼 인코더 코드를 살펴보자. </p>
<pre><code>encoder_input = layers.Input(
    shape=(IMAGE_SIZE, IMAGE_SIZE, CHANNELS), name=&quot;encoder_input&quot;
)
x = layers.Conv2D(32, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;)(
    encoder_input
)
x = layers.Conv2D(64, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;)(x)
x = layers.Conv2D(128, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;)(x)
shape_before_flattening = K.int_shape(x)[1:]  # 디코더에 필요합니다!

x = layers.Flatten()(x)
encoder_output = layers.Dense(EMBEDDING_DIM, name=&quot;encoder_output&quot;)(x)

encoder = models.Model(encoder_input, encoder_output)
encoder.summary()</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/3ae9d9e5-d40f-4997-a42e-410b4dd7128d/image.png" alt=""></p>
<p>이 출력은 위에서부터 순서대로 input층과 Con2D(합성곱층) 3개, 합성곱 출력을 1차원 벡터로 펼치는 층, 벡터를 2D임베딩에 해당하는 dense층에 연결하는 층이다. </p>
<h4 id="324-디코더">3.2.4 디코더</h4>
<pre><code>decoder_input = layers.Input(shape=(EMBEDDING_DIM,), name=&quot;decoder_input&quot;)
x = layers.Dense(np.prod(shape_before_flattening))(decoder_input) #입력을 Dense층에 연결. 
x = layers.Reshape(shape_before_flattening)(x)
x = layers.Conv2DTranspose(
    128, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;
)(x)
x = layers.Conv2DTranspose(
    64, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;
)(x)
x = layers.Conv2DTranspose(
    32, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;
)(x)
decoder_output = layers.Conv2D(
    CHANNELS,
    (3, 3),
    strides=1,
    activation=&quot;sigmoid&quot;,
    padding=&quot;same&quot;,
    name=&quot;decoder_output&quot;,
)(x)

decoder = models.Model(decoder_input, decoder_output)
decoder.summary()</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/95acc2d9-be02-44e8-ae9d-2e48cd1308ce/image.png" alt=""></p>
<p>디코더 코드에서는 생소한 reshape층과 conv2d_transpose층만 다루도록 하겠다. </p>
<ul>
<li><p><strong>reshape</strong>: dense층에 연결된 입력이 conv2d_transpose층에 입력으로 쓰일 수 있도록 reshape층으로 벡터의 크기를 바꿈(=크기가 2차원인 dense층에서 conv2d_transpose에 맞는 벡터 크기로 바꿈)</p>
</li>
<li><p><strong>conv2d_transpose</strong>: &quot;전치 합성곱 층&quot;으로, conv2D에서 strides를 이용해 이미지 크기를 줄였던 것과 반대로 strides를 통해 크기를 증가시키는 것이다. 궁극적인 목표는 32x32x1로 만드는 것!</p>
<p>ex)3x3x1 ----&#39;strides=2&#39;----&gt; 6x6x1</p>
</li>
</ul>
<h4 id="325-인코더와-디코더-연결">3.2.5 인코더와 디코더 연결</h4>
<pre><code># 오토인코더
autoencoder = models.Model(
    encoder_input, decoder(encoder_output)
)
autoencoder.summary()

# 오토인코더 컴파일
autoencoder.compile(optimizer=&quot;adam&quot;, loss=&quot;binary_crossentropy&quot;)

#오토인코더 훈련
autoencoder.fit(
    x_train,
    x_train,
    epochs=5,
    batch_size=100,
    shuffle=True,
    validation_data=(x_test, x_test),
    callbacks=[model_checkpoint_callback, tensorboard_callback],
)</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/a741dccb-9ff7-441c-89f5-f0ea79a33293/image.png" alt="">
손실이 꽤 낮은 편이라는 것을 알 수 있다 :)</p>
<h4 id="326-이미지-재구성">3.2.6 이미지 재구성</h4>
<p>다음으로 인코딩을 거쳐 디코딩 된 결과를 보자!</p>
<pre><code>predictions = autoencoder.predict(example_images)

print(&quot;실제 의류 아이템&quot;)
display(example_images)
print(&quot;재구성 이미지&quot;)
display(predictions)</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/77002cb8-edaa-4bf1-9039-adb0c0a00d53/image.png" alt="">
디코딩 된 결과가 원본 이미지에 비해 흐릿하고, 로고도 잡아내지 못한 것을 알 수 있다. 
그 이유는..!  2D로 임베딩하면서 정보가 손실됐기 때문이다ㅠㅅㅠ</p>
<h4 id="327-잠재-공간-시각화">3.2.7 잠재 공간 시각화</h4>
<pre><code># 샘플 이미지를 인코딩합니다.(임베딩 생성)
embeddings = encoder.predict(example_images)

# 몇 개의 임베딩을 출력합니다.
print(embeddings[:10])

# 레이블(의류 종류)에 따라 임베딩에 색을 입힙니다.
example_labels = y_test[:5000]

figsize = 8
plt.figure(figsize=(figsize, figsize)) #그래프 크기 설정
plt.scatter( #임베딩들을 2D 공간에 점으로 표현 
    embeddings[:, 0],   #임베딩 벡터의 첫 번째 차원을 x축 좌표로 사용
    embeddings[:, 1],    #임베딩 벡터의 두 번째 차원을 y축 좌표로 사용
    cmap=&quot;rainbow&quot;,        #점들에 색상을 입히는 컬러맵을 무지개색으로 설정. 
    c=example_labels,   #각 점의 색상을 example_labels 배열의 값에 따라 다르게 표시하도록 지정합니다. 이 배열에는 각 이미지가 어떤 카테고리(의류 종류)에 속하는지 나타내는 레이블이 들어 있습니다.
    alpha=0.8,        #점들의 투명도
    s=3,        #점들의 크기
) 
plt.colorbar()    #컬러바 추가해 레이블 별 색상 보여줌
plt.show()    #그래프 화면에 표시</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/0d5b8fc3-644c-41ab-a417-5196813d23db/image.png" alt="">
각 코드별 주석을 보며 그래프를 보면 혼자서도 해석할 수 있다. </p>
<h4 id="328-디코딩을-통한-이미지-생성">3.2.8 디코딩을 통한 이미지 생성</h4>
<pre><code># 기존의 임베딩 범위 구하기
mins, maxs = np.min(embeddings, axis=0), np.max(embeddings, axis=0)

# 잠재 공간에서 포인트를 샘플링합니다.
grid_width, grid_height = (6, 3)
sample = np.random.uniform(
    mins, maxs, size=(grid_width * grid_height, EMBEDDING_DIM)
)

# 샘플링된 포인트를 디코딩합니다.
reconstructions = decoder.predict(sample)

# 그래프로 그립니다.
figsize = 8
plt.figure(figsize=(figsize, figsize))

# ... 원본 임베딩 ...
plt.scatter(embeddings[:, 0], embeddings[:, 1], c=&quot;black&quot;, alpha=0.5, s=2)

# ... 잠재 공간에서 새로 생성된 포인트
plt.scatter(sample[:, 0], sample[:, 1], c=&quot;#00B0F0&quot;, alpha=1, s=40)
plt.show()

# 디코딩된 이미지 그리드 추가
fig = plt.figure(figsize=(figsize, grid_height * 2))
fig.subplots_adjust(hspace=0.4, wspace=0.4) ##서브플롯 간의 간격을 조정. hspace는 수평 간격, wspace는 수직 간격을 설정.

for i in range(grid_width * grid_height): 
    ax = fig.add_subplot(grid_height, grid_width, i + 1) #그리드의 각 위치에 서브플롯을 추가
    ax.axis(&quot;off&quot;)  #서브플롯의 축을 숨김. 
    ax.text( #각 이미지 아래에 텍스트 추가. 
        0.5,
        -0.35,
        str(np.round(sample[i, :], 1)),
        fontsize=10,
        ha=&quot;center&quot;,
        transform=ax.transAxes,
    )
    ax.imshow(reconstructions[i, :, :], cmap=&quot;Greys&quot;)</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/482f95bd-1bd9-414a-8a1d-2c1469d2b3fd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/783fae66-5a80-4f6b-ad33-e41671d84e4d/image.png" alt=""></p>
<p>이 결과에서 일부 포인트의 이미지가 무척 선명하다는 점을 알 수 있다.</p>
<p> 왜일까? </p>
<p> 답은 잠재공간 내 포인트 분포와 관련있다. 
 잠재 공간 내 벡터가 많이 분포하는 곳에서는 포인트를 샘플링하는 것이 쉽지만 벡터가 적은 곳에서는 샘플링이 어렵기 때문이다. </p>
<h3 id="33-vae">3.3 VAE</h3>
<p>드디어! VAE로 넘어왔다. 앞서 배웠던 오토인코더의 개념을 상기시키면서 VAE에 대해 배워보자. </p>
<h4 id="331-인코더">3.3.1 인코더</h4>
<p>오토인코더와 VAE는 잠재 공간 내 매핑 방식에 차이가 있다.</p>
<ul>
<li><strong>오토인코더</strong>: 각 이미지가 잠재 공간의 한 포인트에 직접 매핑</li>
<li><strong>VAE:</strong> 이미지가 잠재 공간에 있는 포인트 줒변의 다변량 정규 분포(multivariate normal distribution)에 매핑</li>
</ul>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/be02083b-9a34-43fd-bfa6-f03f8abd37b8/image.png" alt="">
[출처: <a href="https://vitalflux.com/autoencoder-vs-variational-autoencoder-vae-difference/%5D">https://vitalflux.com/autoencoder-vs-variational-autoencoder-vae-difference/]</a></p>
<blockquote>
<ul>
<li>정규 분포(normal distribution or gaussian distribution):
평균과 분산으로 정의되는 확률 분포</li>
</ul>
</blockquote>
<ul>
<li><p>표준 정규 분포(standard normal distribution):
평균이 0이고 분산이 1인 정규 분포</p>
</li>
<li><p>다변량 표준 정규 분포(multivariate standard normal distribution): 
N(0,I)는 평균 벡터가 0이고 공분산 행렬이 단위 벡터인 다변량 분포</p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/6540ca40-cd3a-4ae6-8de5-689ef082346f/image.PNG" alt=""></p>
</li>
</ul>
<p>위 사진에서 z에 관한 식을 통해 포인트 z를 샘플링할 수 있으며
이는 <strong>입력 이미지가 latent space의 다변량 정규 분포를 정의하는 μ와 σ로 인코딩된다</strong>는 의미다. </p>
<p>이를 그림으로 나타내면 아래와 같다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/22b4250c-567f-41b4-b3d0-28407965b040/image.png" alt="">
*ε은 다변량 표준 정규 분포서 샘플링!  </p>
<p>샘플링에 대한 개념을 설명했으니 코드를 살펴보자. </p>
<pre><code>#샘플링
class Sampling(layers.Layer):
    def call(self, inputs):
        z_mean, z_log_var = inputs
        batch = tf.shape(z_mean)[0]
        dim = tf.shape(z_mean)[1]
        epsilon = K.random_normal(shape=(batch, dim))
        return z_mean + tf.exp(0.5 * z_log_var) * epsilon


# 인코더
encoder_input = layers.Input(
    shape=(IMAGE_SIZE, IMAGE_SIZE, 1), name=&quot;encoder_input&quot;
)
x = layers.Conv2D(32, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;)(
    encoder_input
)
x = layers.Conv2D(64, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;)(x)
x = layers.Conv2D(128, (3, 3), strides=2, activation=&quot;relu&quot;, padding=&quot;same&quot;)(x)
shape_before_flattening = K.int_shape(x)[1:]  # 디코더에 필요합니다!

x = layers.Flatten()(x)
z_mean = layers.Dense(EMBEDDING_DIM, name=&quot;z_mean&quot;)(x)
z_log_var = layers.Dense(EMBEDDING_DIM, name=&quot;z_log_var&quot;)(x)
z = Sampling()([z_mean, z_log_var])

encoder = models.Model(encoder_input, [z_mean, z_log_var, z], name=&quot;encoder&quot;) #입력 이미지를 받고, z_mean, z_log_var와 이런 파라미터로 정의된 정규분포에서 샘플링된 포인트 z를 출력. 
encoder.summary()</code></pre><h4 id="332-손실-함수">3.3.2 손실 함수</h4>
<p>VAE는 기존에 오토인코더에서 사용하던 손실 함수에 추가적으로 KL-divergence를 사용한다. </p>
<blockquote>
<p>KL-divergence는 한 확률분포가 다른 분포와 얼마나 다른지 측정하는 도구이다.</p>
</blockquote>
<ul>
<li><p>VAE에서는 평균이 z_mean, 분산이 z_log_var인 정규 분포가 표준 정규 분포와 얼마나 다른지 측정!</p>
<p>코드로 나타내면 아래와 같다. </p>
<pre><code>kl_loss= -0.5 * sum(1+z_log_var - z_mean ^ 2 - exp(z_log_var))</code></pre></li>
</ul>
<p>그렇다면 KL-divergence를 사용하는 게 왜 좋을까?</p>
<ol>
<li><em>latent space에서 포인트를 선택할 때</em> 사용할 수 있는 <strong>표준 정규 분포</strong>를 가지게 됨</li>
<li>이 항이 모든 _인코딩된 분포_를 <strong>표준 정규 분포</strong>에 가깝도록 강제한다. </li>
</ol>
<p>-&gt;결론적으로 포인터들의 분포가 표준 정규 분포를 따르게 되므로 디코딩 시 보다 선명한 결과물을 산출할 것임을 예측할 수 있다!</p>
<h4 id="334-vae-분석">3.3.4 VAE 분석</h4>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/44ef16c5-9a12-4247-877e-7216585ac2d8/image.png" alt=""></p>
<ul>
<li>검은색 점: 인코딩된 각 이미지 z_mean 값 나타냄</li>
<li>파란색 점: latent space에서 샘플링된 일부 포인트 나타냄. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/812facd4-1ca1-4685-bc3d-bb77487411d3/image.png" alt=""></li>
<li>디코딩된 이미지</li>
</ul>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/f956af79-23e5-4666-a91a-cc91d440d844/image.png" alt=""></p>
<ul>
<li>왼쪽:  잠재 공간의 포인트로 의류 종류 나타내기</li>
<li>오른쪽: 잠재 공간을 p-값으로 변환  &lt;- 레이블별로 고르게 분포한다는 것을 알 수 있다. 
cf)p-값: 어떤 사건이 우연히 발생할 확률(확률과 통계에 나오는 개념이다)</li>
</ul>
<p>  3.4 에서는 CelebA데이터셋을 통해 잠재 공간을 탐색하는 부분이 나온다. </p>
<p>  이 부분은 지금까지 배웠던 VAE를 응용하는 단계라 실습이 더 중요한 것 같다고 판단해 넘어가겠다..! </p>
<p>  마지막으로 오늘 배웠던 오토인코더, VAE의 개념을 마무리해보자. </p>
<blockquote>
<ul>
<li><strong>오토인코더</strong>는 입력 데이터를 효과적으로 압축(인코딩)하고 다시 복원(디코딩)하기 위해 설계된 인공 신경망이다. </li>
</ul>
</blockquote>
<ul>
<li><strong>VAE</strong>는 multivariate normal distribution을 이용해 입력 데이터의 확률적 표현을 학습하는 생성적 신경망 모델이다. <ul>
<li>이미지가 하나의 포인트로 매핑되는 게(오토인코더) 아니라 잠재 공간의 다변량 정규 분포를 의미하는 μ와 σ 벡터로 인코딩된다는 점(VAE)이둘의 차이점! </li>
</ul>
</li>
</ul>
<p> 그럼 다음 4장에서 배울 GAN을 기약하며, 안녕! :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC Spring - Ch.4 DATABASE 설계 & AWS RDS 설정 (1)]]></title>
            <link>https://velog.io/@iumiere-on/UMC-Spring-Ch.4-DATABASE-%EC%84%A4%EA%B3%84-AWS-RDS-%EC%84%A4%EC%A0%95-1</link>
            <guid>https://velog.io/@iumiere-on/UMC-Spring-Ch.4-DATABASE-%EC%84%A4%EA%B3%84-AWS-RDS-%EC%84%A4%EC%A0%95-1</guid>
            <pubDate>Wed, 08 May 2024 09:04:37 GMT</pubDate>
            <description><![CDATA[<p>4장에서는 DB 설계를 다룹니다. </p>
<p>이제 데이터베이스를 상황에 따라 설계하는 방법을 배운 뒤 직접 설계해보도록 하겠습니다.</p>
<p>데이터베이스를 설계해야하기 때문에 MySQL과 ERD에 관한 개념을 간단히 정리는 아래 링크를 참고해주세요!</p>
<p>그럼 본문으로 들어가도록 하겠습니다.</p>
<p>ERD는 간단히 말해 데이터베이스의 설계도입니다. 따라서 프로젝트 진행 시 초기에 ERD를 보다 원활한 프로젝트를 진행할 수 있습니다.</p>
<p>4장에서는 데이터베이스의 설계에 관해 배운다고 했는데요, 보다 구체적인 상황은 다음과 같습니다.</p>
<ul>
<li><strong>유저 테이블을 어떻게 설계</strong>하는 것이 좋은지</li>
<li><strong>N : M(다대다) 관계는 어떻게 하는 것</strong>이 좋은지</li>
<li><strong>알림을 보내야 하는 경우</strong>는 어떻게 하는 것이 좋은지</li>
</ul>
<p>4장에서는 도서 대여 관리 app의 DB를 예시로 DB를 설계하고 있습니다.</p>
<p>우선 관련 요구사항을 살펴보겠습니다.</p>
<hr>
<h3 id="사용자-관련-요구-사항">사용자 관련 요구 사항</h3>
<ol>
<li><strong>카카오 소셜 로그인</strong>을 구현 할 예정이다.</li>
<li><strong>회원 탈퇴 기능</strong>이 필요하다.</li>
<li><strong>이름, 닉네임, 전화번호, 성별</strong>이 필요하다.</li>
</ol>
<h3 id="책-관련-요구-사항">책 관련 요구 사항</h3>
<ol>
<li><strong>사용자가 책 여러 권을 대여</strong>할 수 있다.</li>
<li>책은 <strong>하나의 카테고리</strong>가 있다.</li>
<li>책은 <strong>제목, 설명에 대한 정보</strong>가 필요하다.</li>
<li>책 소개 페이지에 <strong>해시태그</strong>가 붙을 수 있고,</li>
</ol>
<p><strong>책 한 권에 해시태그 여러 개</strong>가, <strong>해시태그 하나가 여러 책</strong>에 붙을 수 있다.
5. 사용자가 책 설명 페이지에서 책에 <strong>좋아요</strong>를 누를 수 있다.
6. 책 <strong>카테고리 별로 현재 몇 개의 책이 있는지 집계</strong>가 필요하다.</p>
<h3 id="알림-관련-요구-사항">알림 관련 요구 사항</h3>
<ol>
<li>알림은 <strong>공지 관련 알림, 책 반납 시간 임박 알림, 마케팅 알림이 있을 수 있다.</strong></li>
</ol>
<hr>
<p>위 요구사항을 바탕으로 ERD를 설계할 예정인데 단계별로 추가될 때 마다 n차 ERD로 나누어 설명하였다.</p>
<h4 id="1차-erd">1차 ERD</h4>
<p>이 요구사항을 보면 단순히 테이블이 사용자, 책, 알림으로만 구성된다고 생각할 수 있다. 그러나 카테고리에 관한 설명을 보면 단순히 책 테이블 안에서 해결하기에는 복잡하다는 것을 알 수 있다. 
-&gt; 그래서 <em>bookCategory 테이블을 추가했다.</em></p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/fbfce0ab-de4f-4ad8-9768-e4e4ac1ee7ea/image.png" alt=""></p>
<p>위의 ERD처럼 설계하기 위해서 기본적으로 3가지 조건을 지켜야한다.</p>
<ol>
<li>테이블 이름&amp; 칼럼 이름은 소문자! 
단어 구분은 밑줄로!(대문자xx)</li>
</ol>
<p>2.기본키로 엔티티의 유일한 값을 채택하기(X)
index를 따로 만들어 기본키로 사용(O)</p>
<ol start="3">
<li>기본키 타입은 bigint로! (추후 확장 고려)</li>
</ol>
<h4 id="2차-erd">2차 ERD</h4>
<p>이 ERD에서 book테이블의 description, member테이블의 gender, created_at, updated_at을 수정/추가해 아래와 같은 ERD가 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/75de3190-12fe-4034-979a-1947107961f3/image.png" alt=""></p>
<ol>
<li><p>book테이블의 description
사실 1차 ERD와 차이는 없지만 설명을 하기 위해서 데려왔다.
description은 text(string과 같은 역할) vs. varchar(50) 이 둘 중에 결정을 해야한다. 
이 경우 description에 지정한 글자수 제한이 없으므로 길이 제한이 없는 text를 자료형으로 선택했다. </p>
</li>
<li><p>member테이블의 gender
gender는 int(0이면 남자, 1이면 여자) vs. varchar(10) 이 둘 중에 결정해야한다.
이때 varchar로 설정해 enum으로 관리할 수 있기에 이번에는 varchar(10)을 선택했다. 
각자 편한 걸로 고르면 된다!</p>
</li>
<li><p>created_at, updated_at
최신순 정렬 기능을 위한 속성이다. 
이때 자료형인 datetime(6)은 밀리초 소수점 6자리까지 구분한다는 의미이다. 이렇게 밀리초 단위로 아주 작은 부분으로 나누면 케이스별로 같은 시간을 사용해 겹치는 걸 방지할 수 있다!</p>
</li>
</ol>
<h4 id="3차-erd">3차 ERD</h4>
<p>마지막으로 member테이블에 status와 inactive_date 속성을 두어 추후에 삭제 기능을 구현할 때 사용한다. 
<img src="https://velog.velcdn.com/images/iumiere-on/post/f1609c8b-de27-4d86-9372-da4e85a02ddb/image.png" alt=""></p>
<p>이렇게 status, inactive_date 속성을 두는 이유는 soft delete와 연관이 있다.</p>
<p>우선 Hard Delete vs. Soft Delete 로 나누어 살펴보자. </p>
<ul>
<li><p>Hard Delete
:HTTP Method 중 Delete로 바로 삭제!</p>
<p>hard delete의 단점은 다음과 같은 경우에서 알 수 있다.</p>
<ul>
<li><ol>
<li>회원 탈퇴를 철회하는 경우
-&gt;이미 삭제해버려서 복구할 수 없다악!!</li>
</ol>
</li>
<li><ol start="2">
<li>join 연산을 이용해 매일 인기 상위 사용자 5명을 집계해 보여주는 경우</li>
</ol>
</li>
</ul>
</li>
<li><blockquote>
<p>1등이 탈퇴를 하면 2, 3, 4, 5등만 남아있네???</p>
</blockquote>
</li>
</ul>
<p>  이렇게 hard delete의 경우 문제를 불러 일으킬 수 있으니 지양하는 게 좋다!</p>
<ul>
<li><p>Soft Delete
: HTTP Method 중 Patchfh, 일단 비활성 상태로 두고, 일정 기간동안 비활성인 경우 자동 삭제가 되도록!</p>
<p>사용하는 변수:</p>
<ul>
<li><p>status: 비활성 여부</p>
</li>
<li><p>inactive_date: 비활성한 기간</p>
<p>Q. 어떻게 자동으로 지우죠??
A. 정해진 시간에 자동으로 실행되는 프로세스인 batch를 이용해서 검사한 뒤, inactive 된지 일정기간만큼 지나면 삭제합니다!</p>
</li>
</ul>
</li>
</ul>
<h4 id="연관-관계">연관 관계</h4>
<p>다음은 연관관계에 대해 알아보겠습니다. 
이번에 다룰 연관관계는 총 5가지로, 사용자:책, 책:책 카테코리, 책:해시태크, 사용자: 좋아요, 알림입니다.</p>
<p>왁! 너무 배고파서 닭강정 먹고 오겠습니다. 호다다다닥</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성 AI -  Ch1. 생성  모델링]]></title>
            <link>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch1.-%EC%83%9D%EC%84%B1-%EB%AA%A8%EB%8D%B8%EB%A7%81</link>
            <guid>https://velog.io/@iumiere-on/%EC%83%9D%EC%84%B1-AI-Ch1.-%EC%83%9D%EC%84%B1-%EB%AA%A8%EB%8D%B8%EB%A7%81</guid>
            <pubDate>Wed, 08 May 2024 05:43:39 GMT</pubDate>
            <description><![CDATA[<p>이번 시리즈는 생성 AI 이다. 
ChatGPT처럼 산출물을 내는 생성형 AI에 관심은 많았는데, 이번 스터디를 통해 처음으로 제대로 공부하게 되었다. </p>
<p>스터디를 통해 이 책을 무사히 끝내길 기원하며 첫 장을 시작해보겠다. </p>
<p>우선 1장은 </p>
<p>1.1 생성 모델링이란?
1.2 첫 번재 생성 모델
1.3 핵심 확률 이론 
1.4 생성 모델 분류
1.5 생성 딥러닝 예제 코드 </p>
<p>로 구성되어있다. </p>
<p>그럼 키워드를 통해 생성 모델링에 대한 개념을 정리하도록 하겠다. </p>
<h3 id="11-생성-모델링이란">1.1 생성 모델링이란?</h3>
<blockquote>
<p>생성 모델링은 주어진 데이터셋과 유사한 새로운 데이터를 생성하도록 모델을 훈련하는 머신러닝의 한 분야입니다. </p>
</blockquote>
<p>생성 모델링의 큰 흐름은 두 가지로, <strong>훈련과 샘플링</strong>이다. 
즉 1. 훈련데이터셋에서 <strong>생성모델을 훈련</strong>해 특성(feature)을 포착
 2. 모델에서 <strong>샘플링</strong>해 새로운 데이터 생성</p>
<p>Q. 생성 모델은 결정적(deterministic)일까, 아니면 확률적(probabilistic)일까?
A. 확률적; 다양한 출력 결과를 샘플링할 수 있어야 하기 때문. </p>
<h4 id="111-생성-모델링-vs-판별-모델링">1.1.1 생성 모델링 vs. 판별 모델링</h4>
<p>판별 모델링은 훈련 데이터의 각 샘플에 대한 레이블을 토대로 학습 진행 -&gt; 데이터의 분류/판별 가능해짐. </p>
<p><em>수학적 정의
판별 모델링: *</em>p(y|x)를 추정**
-&gt;샘플 x가 주어졌을 때 레이블 y의 확률을 모델링</p>
<p>생성 모델링: <strong>p(x)를 추정</strong>
-&gt;샘플 x를 관측할 확률을 모델링</p>
<h3 id="12-첫-번째-생성-모델">1.2 첫 번째 생성 모델</h3>
<h4 id="122-생성-모델링-프레임워크">1.2.2 생성 모델링 프레임워크</h4>
<ul>
<li>샘플 데이터셋 <strong>x</strong></li>
<li><strong>샘플</strong>을 알려지지 않은 분포 <strong>Pdata 분포</strong>로부터 생성</li>
<li>Pdata를 흉내내는 생성모델인 <strong>Pmodel</strong></li>
<li>Pmodel의 속성 3가지
1)정확도 - Pmodel이 높다는 것은 Pdata에서 뽑은 것 같다는 의미. 
2)생성 - Pmodel 에서 샘플링 쉽게 할 수 있어야!
3)표현 - 데이터의 다양한 고수준 특성이 Pmodel로 어떻게 표현되는지 알아야!</li>
</ul>
<h4 id="123-표현-학습">1.2.3 표현 학습</h4>
<p>표현 학습의 작동 원리:</p>
<ul>
<li>고차원의 표본 공간을 직접 모델링 ex)픽셀 하나하나 모델링(X) </li>
<li><strong>저차원의 잠재공간(latent space)</strong>를 사용해 훈련 데이터셋의 각 샘플을 표현하고 이를 원본 공간의 포인트에 매핑(O)</li>
<li><blockquote>
<p>보다 쉽게 말하자면 데이터셋에서 특징적인 feature만  추출한 뒤 원본 공간의 포인트에 연결한다는 의미!</p>
</blockquote>
</li>
</ul>
<p>이런 특징을 가진 표현 학습은 두 단계로 작동한다. </p>
<p><strong>step 1</strong>. 데이터셋을 잘 설명하는 _특성(feature)_이 두 개의 <strong>latent space</strong>임을 인지. 
<strong>step 2.</strong> 공간의 한 포인트를 이미지에 매핑하는 <strong>매핑함수 f</strong> 학습. </p>
<p>앞서 말한 표현학습의 특징과 학습 순서를 정리하면</p>
<blockquote>
<p>표현 학습은 데이터로부터 특성을 추출하고, 그것을 기계 스스로 배워내는 과정</p>
</blockquote>
<p>이라고 정의할 수 있다. </p>
<p>이렇게 개념적으로 배운 표현학습이 인코더-디코더에 적용되면 다음과 같은 순서로 진행된다. </p>
<ol>
<li>훈련 데이터셋을 latent space로 인코딩</li>
<li>샘플링</li>
<li>디코딩해 원래 도메인으로 복귀</li>
</ol>
<h3 id="13-핵심-확률-이론">1.3 핵심 확률 이론</h3>
<p>여기서는 <strong>표본 공간, 확률 밀도 함수, 모수 모델링, 가능도, 최대 가능도 추정</strong>에 대해 정리할 예정이다. </p>
<h4 id="표본-공간sample-space">표본 공간(sample space)</h4>
<p>샘플 x가 가질 수 있는 모든 값의 집합</p>
<h4 id="확률-밀도-함수probability-density-function">확률 밀도 함수(probability density function)</h4>
<p>포인트 x를 0과 1 사이의 수자로 매핑하는 함수 p(x)로, 표본 공간에 있는 모든 포인트에 대해 밀도 함수를 적분했을 때 1이 되어야 잘 정의됐다고 할 수 있다.</p>
<h4 id="모수-모델링parametric-modeling">모수 모델링(parametric modeling)</h4>
<p>안정적인 Pmodel(x)를 찾는 데 사용할 수 있는 기법으로, 
모수 모델(parametric model)은 유한한 개수의 파라미터 Θ를 사용해 기술할 수 있는 P{Θ}(x)의 한 종류이다. </p>
<h4 id="가능도likelihood">가능도(likelihood)</h4>
<p>파라미터 집합 Θ의 가능도(가능도.. 입에 너무 안 붙는다. 왜 가능성이라고 안하고 가능도라고 번역했을까,,,,) 
L(Θ|x)는 관측된 포인트 x가 주어졌을 때 Θ의 타당성을 측정하는 함수. </p>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/cf273062-b614-4f10-aa12-a2f936493827/image.png" alt="">
<img src="https://velog.velcdn.com/images/iumiere-on/post/a7b27b4e-3b79-481b-af39-50d8545ac1cf/image.png" alt="">
(이산분포일 경우)</p>
<p>실제로는 계산의 편의를 위해 log likelihood를 더 많이 사용한다.
(파라미터 집합 Θ의 likelihood==데이터가 발견될 확률)
<img src="https://velog.velcdn.com/images/iumiere-on/post/a156b5dc-0cf4-4af3-98eb-693366f7e170/image.png" alt=""></p>
<h4 id="최대-가능도-추정maximum-likelihood-estimation-">최대 가능도 추정(maximum likelihood estimation )</h4>
<p>MLE는 파라미터 집합 Θ를 추정하는 기법. </p>
<ul>
<li>손실함수를 최소화하는 게 딥러닝의 목적이기 때문에, 
negative log-likelihood를 최소화하는 파라미터 집합을 찾는 것과 같은 의미. </li>
</ul>
<h3 id="14-생성-모델-분류">1.4 생성 모델 분류</h3>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/25c1c679-1b01-4f0a-b0b2-b70c03993790/image.png" alt=""></p>
<p>1.Explicit density vs. Implicit density</p>
<ul>
<li>Explcit density(명시적 밀도)는 밀도 함수를 직접 최적화하는 모델(tractable density)과 밀도 함수의 근사치를 최적화하는 모델(approximate density)로 나뉨</li>
<li>Implicit density는 데이터를 직접 생성하는 확률적 과정을 만드는 것이 목표 
ex) GAN</li>
</ul>
<ol start="2">
<li>Tractable model </li>
</ol>
<ul>
<li>모델 구조에 제약을 가해 밀도 함수의 계산을 쉽게 만듦. </li>
<li>autoregressive model: 입력 특성에 순서 부여-&gt; 출력이 순차적</li>
<li>normalizing flow model: 단순한 분포를 생성, 역함수를 연속적으로 사용</li>
</ul>
<ol start="3">
<li>Approximate density model</li>
</ol>
<ul>
<li>VAE: 잠재변수를 도입, 결합 밀도 함수(joint density function)의 근사치를 최적화</li>
<li>energy-based model: Markov chain 샘플링 사용</li>
<li>diffusion: 오염된 이미지에서 noise 제거하는 모델을 계속 훈련 -&gt; 밀도 함수 근사. </li>
</ul>
<p>오늘은 생성 모델링에 대한 전반적인 개념과 작동방식, 종류에 대해 알아보았다. 공부하면서 핵심 확률 이론의 기본을 통해 기초를 튼튼히 쌓고 생성모델의 종류를 대략적으로 알아봄으로써 밀도 함수가 생성 모델을 나누는 구분점이 된다는 것을 알았다. </p>
<p>다음 2장에서는 딥러닝의 전반적인 개념을 공부해보도록 하겠다. 안녕!
<img src="https://velog.velcdn.com/images/iumiere-on/post/69e313f8-810b-475d-b336-e3f2613c16e9/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC Spring - Ch.1 서버란 무엇인가]]></title>
            <link>https://velog.io/@iumiere-on/UMC-Spring-Ch.1-%EC%84%9C%EB%B2%84%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@iumiere-on/UMC-Spring-Ch.1-%EC%84%9C%EB%B2%84%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Fri, 03 May 2024 07:13:06 GMT</pubDate>
            <description><![CDATA[<h4 id="ch1의-목표">ch.1의 목표</h4>
<ol>
<li>서버의 정의와 역할에 대한 이해</li>
<li>서버가 구축되는 과정에 대한 이해</li>
</ol>
<p>오늘은 서버에 대한 정확히 이해하는 시간을 가져보겠습니다!</p>
<h4 id="cf-내용에-들어가기에-앞서-알아야할-기본-개념-4가지">cf) 내용에 들어가기에 앞서 알아야할 기본 개념 4가지</h4>
<blockquote>
<p><strong>1. 시스템콜</strong>
-정의: OS의 커널이 제공하는 서비스에 대해, 응용 프로그램(ex)사용자 프로그램)의 요청에 대해 커널에 접근하기 위한 인터페이스. 
-시스템콜을 사용하는 이유: 유저모드(일반 명령)만으로는 구현하기 힘든 기능을 커널에서 커널모드(특권 명령)로 해결할 수 있기 때문.
-시스템콜의 유형: process control/file manangement/device management/정보 관리/통신/보안</p>
</blockquote>
<blockquote>
<p><strong>2. 프로세스 &amp; 스레드</strong>
-프로세스: 운영체제로부터 자원을 할당받은 자원의 단위 
&quot;Process is a program in execution&quot;
-스레드: 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위
-스레드끼리 공유하는 부분: PC, register, stack space (stack 부분을 공유한다고 보면 될 듯 싶다)
-스레드끼리 공유하지 않는 부분: code, data, OS resources</p>
</blockquote>
<blockquote>
<p><strong>3. TCP/IP 계층</strong>
-NW Interface, Internet, Transport, Application 계층으로 구성
-Internent Layer(L2): 네트워크 계층으로, 컴퓨터 간 라우팅 담당 ex)IP
-Transport Layer(L3): 전송 계층으로, IP에 의해 전달되는 패킷의 오류를 검사하고 재전송 요구 등의 제어 담당 ex) TCP </p>
</blockquote>
<blockquote>
<p><strong>4. TCP vs. UDP</strong>
-공통점: 
TCP/IP 전송계층에서 사용되는 프로토콜로, IP프로토콜 기반으로 구현됨. 체크섬(for 데이터 오류 검사) 존재 
-TCP: 응답을 받기 때문에 신뢰성이 요구되는 앱에서 사용
-UDP: 간단한 데이터를 빠른 속도로 전송하는 앱에서 사용</p>
</blockquote>
<h2 id="1-서버의-정의와-역할">1. 서버의 정의와 역할</h2>
<p>서버는 OS에 의해 동작하는 프로세스인 동시에 클라이언트의 역할을 하는 프로세스와 소켓을 통해 IPC를 수행하는 것입니다.  </p>
<p>cf)IPC(Inter Process Communication)는 shared memory, message passing로 구성. </p>
<h3 id="ip주소--port-번호">IP주소 &amp; port 번호</h3>
<p>이제 프로세스 식별 과정을 알아보겠습니다! </p>
<h4 id="ip주소">Ip주소</h4>
<p>컴퓨터 네트워크 상에서 수많은 컴퓨터들을 식별하기 위한 수단
-&gt; <strong>IP주소</strong>
IP주소는 식별을 목적으로 사용하기 때문에 유일해야 함! 이를 위해 NAT, 서브넷팅, IPv6 등이 사용됨. </p>
<h4 id="port-번호">port 번호</h4>
<p>컴퓨터가 직접 네트워크에서 통신하는 게 아니라 컴퓨터 내 프로세스가 다른 컴퓨터의 프로세스와 통신하는 것 
=&gt;각자의 컴퓨터의 프로세스에서 IPC수행</p>
<p>위에서 설명한 IP주소를 통해 컴퓨터를 식별한 이후 해당 컴퓨터의 어떤 프로세스로 데이터를 보낼지 결정할 때 사용하는 수단
-&gt;port 번호</p>
<p>이러한 IP주소와 port번호를 합쳐서 클라이언트가 서버에게 데이터를 보내는 형태는 </p>
<blockquote>
<p>[서버 프로세스가 동작 중인 컴퓨터의 아이피 주소]:[서버 프로세스가 부여받은 포트번호] 
_ex)[203.201.7.3:80]는 203.201.7.3의 ip주소를 가진 컴퓨터의 80번 포트의 프로세스 의미. _</p>
</blockquote>
<h3 id="데이터-송수신-과정">데이터 송수신 과정</h3>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/775b8605-ddf9-4369-88b2-f4cea4cc0a68/image.png" alt=""></p>
<p>위의 계층 그림에서 </p>
<ol>
<li>Application</li>
<li>Sockets</li>
<li>NW 스택</li>
<li>NIC</li>
</ol>
<p>1-&gt;2-&gt;3-&gt;4 순서로 송신이 이루어집니다. 그림에서 왼쪽은 계층, 오른쪽은 송신 시 보내는 패킷(header+payload)을 나타냅니다. </p>
<h4 id="데이터-송신">데이터 송신</h4>
<ol>
<li>application: 서버 프로세스가 write 시스템 콜 통해 소켓에 데이터 보냄
2,3: 이후 TCP/UDP 계층(L4) &amp; IP계층(L3), Ethernet(L2) 거쳐 흐름제어, 라우팅 등을 하게 됨.
4: NIC 통해 외부로 데이터 보냄</li>
</ol>
<h4 id="데이터-수신">데이터 수신</h4>
<p>4-&gt;3-&gt;2-&gt;1 순서로 진행됨.
4: NIC에서 데이터 수신하고 인터럽트를 통해 driver로 데이터 옮김
3,2: NW 스택에서 데이터 이동하며 소켓에 데이터가 담김
1: 프로세스에 데이터 도착!</p>
<h3 id="소켓">소켓</h3>
<p>소켓은 종류에 따라 TCP와 UDP에서 사용하는 걸로 나뉩니다. </p>
<h4 id="tcp-전용-소켓stream-소켓">TCP 전용 소켓(=stream 소켓)</h4>
<p>연결지향(connected-oriented)</p>
<h4 id="udp-전용-소켓datagram-소켓">UDP 전용 소켓(=datagram 소켓)</h4>
<p>비연결지향
<img src="https://velog.velcdn.com/images/iumiere-on/post/c7d75a69-e236-469c-a2fd-bf016e4f2979/image.png" alt=""></p>
<p>TCP 소켓의 시스템콜을 다뤄보겠습니다. </p>
<p>*<em>1. socket() 시스템 콜
*</em>
통신을 위한 틀을 만드는 과정으로, 아래와 같은 형태이다. </p>
<blockquote>
<p>socket(domain, type, protocol);
domain:IPv4 vs. IPv6 중 뭘 사용할지 결정
type: stream vs. datagram 
protocol: 0/6/7 //0: 프로토콜 선택, 6: TCP, 17: UDP
리턴값: 파일 디스크립터</p>
</blockquote>
<pre><code>int socket_descriptor;
socket_descriptor=socket(AF_INET, SOCK_STREAM, 0);</code></pre><p>리눅스에서는 모든 것을 파일로 취급하기에 소켓 역시 파일로 취급한다. 
따라서 서버 프로세스가 write(), read() 시스템 콜을 할 때 파일 디스크리터를 파라미터로 사용해야한다. </p>
<p><strong>2. bind() 시스템 콜</strong></p>
<p>생성한 소켓에 실제 아이피 주소와 포트번호를 부여하는 시스템 콜로, 서버에서만 사용한다. (클라이언트는 통신 시 포트번호가 자동으로 부여되기 때문.)</p>
<blockquote>
<p>bind(sockfd, sockaddr, socklen_t);
sockfd: 바인딩을 할 소켓의 파일 디스크립터
sockaddr: 소켓에 바인딩 할 아이피 주소, 포트번호를 담은 구조체 
socklen_t: 위 구조체의 메모리 크기 </p>
</blockquote>
<pre><code>#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;

int main(){
    int sockfd=socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd==-1){
        perror(&quot;Socket creation failed&quot;);
        return 1;
    }

    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET; //IPv4 주소 체계
    server_address.sin_addr.s_addr = INADDR_ANY; //모든 가능한 IP주소
    server_address.sin_port = htons(80); 포트번호 80번

    if(bind(sockfd, (struct sockaddr *)&amp;server_address, sizeof(server_address))==-1){
        perror(&quot;Bind failed&quot;);
        return 1;
     }

     //바인딩 성공 처리~~

     return 0;
}</code></pre><p>*<em>3. listen() 시스템 콜 *</em> 
<em>TCP 에서만 사용되기 때문에 무척이나 중요하다!</em></p>
<p>파일 디스크립터에 해당하는 소켓이 클라이언트의 요청을 받아들이도록 하며, 최대로 받아주는 크기를 backlog로 설정한다. </p>
<p>*backlog == TCP에서의 backlog queue의 크기</p>
<p>-&gt;listen() 시스템 콜은 파라미터로 받은 backlog 크기만큼 backlog queue를 만드는 시스템 콜. </p>
<blockquote>
<p>listen(sofkfd, backlog);
sockfd: 소켓의 파일 디스크립터
backlog: 연결요청을 받아줄 크기=TCP 백로그 큐의 크기</p>
</blockquote>
<pre><code>#include &lt;sys/socket.h&gt;

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); //소켓 생성. 
    if (sockfd == -1) {
        perror(&quot;Socket creation failed&quot;);
        return 1;
    }

    // ... 서버 소켓의 주소와 바인딩 설정 ...

    int backlog = 10; // 최대 대기열 크기
    if (listen(sockfd, backlog) == -1) {
        perror(&quot;Listen failed&quot;);
        return 1;
    }

    // 리스닝 성공 처리 및 연결 요청 처리

    return 0;
}&#39;&#39;&#39;

</code></pre><p><img src="https://velog.velcdn.com/images/iumiere-on/post/feb27911-1d4d-4050-8115-befafc76ffc0/image.png" alt=""></p>
<p>클라이언트가 클라이언트 소켓을 통해 처음으로 서버에 요청해 backlog queue에 들어갈 때 syn 요청을 보냄. </p>
<pre><code></code></pre><p><strong>4.accept() 시스템 콜</strong></p>
<p>accept() 시스템 콜은 backlog queue에서 syn을 보내서 대기 중인 요청을 FIFO 방식으로 하나씩 연결에 대해 수립시킨다. </p>
<p>이제 코드가 슬슬 길어질텐데 중요한 부분이니 꼬옥 집중해서 봐주세요!</p>
<blockquote>
<p>int accept(sockfd, sockaddr, socklen_t);
sockfd: 백로그 큐의 요청을 받아들이기 위한 소켓의 파일 디스크립터 
sockaddr: 선입선출로 빼온 연결 요청에서 알아낸 클라이언트의 주소 정보. 
socklen_t: 위 ㅣ구조체의 메모리 크기. </p>
</blockquote>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;

int main() {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);

        //서버 프로세스
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET; //소켓의 도메인(IPv4 or IPv6)
    server_address.sin_addr.s_addr = INADDR_ANY; //소켓의 IP주소
    server_address.sin_port = htons(80);//소켓의 포트번호

    bind(server_socket, (struct sockaddr *)&amp;server_address, sizeof(server_address));

    listen(server_socket, 5); //소켓에 백로크 큐 크기 할당. 

    printf(&quot;Server: Waiting for client&#39;s connection...\n&quot;);

        //클라이언트 프로세스
    struct sockaddr_in client_address;
    socklen_t client_addrlen = sizeof(client_address);

        //새로운 파일 디스크립터 값을 리턴함.
    int client_socket = accept(server_socket, (struct sockaddr *)&amp;client_address, &amp;client_addrlen);

    printf(&quot;Server: Accepted connection from %s:%d\n&quot;,
           inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  //이게 뭐지

    // 3-way handshake의 나머지 두 단계 수행
    char buffer[1024];
    ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); // 클라이언트의 ACK 받기
    if (bytes_received &gt; 0) {
        printf(&quot;Server: Received ACK from client.\n&quot;);
    }</code></pre><p><strong>4.1  TCP 3-way handshake</strong></p>
<p>신뢰성을 위해 클라이언트와 서버가 서로 준비되었다는 걸 확인하는 과정!
<img src="https://velog.velcdn.com/images/iumiere-on/post/58598def-5ee0-4081-9183-82543d4fa146/image.png" alt=""></p>
<ol>
<li>client -&gt; server 사이 SYN: listen 상태인 서버의 소켓에 연결 요청
2, 3: accept() 시스템 콜 이후 진행하며, Established 상태(데이터 송/수신을 위한 준비 끝)를 만드는 것이 목표. </li>
</ol>
<p>4.2 accept() 시스템 콜 이후 멀티 프로세스/멀티 스레드
서버는 병목 현상을 방지하기 위해 연결요청과 응답요청을 분리!</p>
<p>아래는 전체적인 코드이며, 이후 위의 fork()문 이후 부모 프로세스 코드와 자식 프로세스 코드가 나옵니다. </p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;

int main() {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(80); // 웹 서버 포트인 80

    bind(server_socket, (struct sockaddr *)&amp;server_address, sizeof(server_address));

    listen(server_socket, 5);

    printf(&quot;Server: Listening on port 80...\n&quot;);

    while (1) { //while문을 돌며 백로그 큐에 있는 클라이언트 프로세스들 처리.
        struct sockaddr_in client_address;
        socklen_t client_addrlen = sizeof(client_address);

        int client_socket = accept(server_socket, (struct sockaddr *)&amp;client_address, &amp;client_addrlen);

        if (fork() == 0) { // 자식 프로세스 &lt;- 이 부분에 집중!

            printf(&quot;Server: Accepted connection from %s:%d\n&quot;,
                   inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));

            // 3-way handshake의 나머지 두 단계 수행
            // 여기서는 ACK를 보내는 과정만 간단히 보여줍니다.
            sleep(1); // 실제로는 필요한 로직 수행

            // 서버의 응답 전송
            char response[] = &quot;HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!&quot;;
            send(client_socket, response, strlen(response), 0);
            printf(&quot;Server: Sent response to client.\n&quot;);

            close(client_socket);
            exit(0);
        }

        close(client_socket);
    }

    close(server_socket);

    return 0;
}</code></pre><p>&lt;부모 프로세스&gt; </p>
<pre><code>#include &lt;stdio.h#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;

int main() {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(80); // 웹 서버 포트인 80

    bind(server_socket, (struct sockaddr *)&amp;server_address, sizeof(server_address));

    listen(server_socket, 5);

    printf(&quot;Server: Listening on port 80...\n&quot;);

    while (1) {
        struct sockaddr_in client_address;
        socklen_t client_addrlen = sizeof(client_address);

        int client_socket = accept(server_socket, (struct sockaddr *)&amp;client_address, &amp;client_addrlen);

        if (fork() == 0 -&gt; false ) { 
           실행안함
        }


    }

    close(server_socket);

    return 0;
    }</code></pre><p>fork()를 통해 생성된 자식 프로세스의 코드입니다.
자식 프로세스는 부모 프로세스가 가진 데이터들을 똑같지만 새로운 프로세스입니다. 그래서 부모 프로세스의 기억을 가지고 자신의 프로세스를 진행할 수 있다는 장점이 있습니다. </p>
<p>&lt;자식 프로세스&gt;</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;

int main() {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(80); // 웹 서버 포트인 80

    bind(server_socket, (struct sockaddr *)&amp;server_address, sizeof(server_address));

    listen(server_socket, 5);

    printf(&quot;Server: Listening on port 80...\n&quot;);

    while (1) {
        struct sockaddr_in client_address;
        socklen_t client_addrlen = sizeof(client_address);

        int client_socket = accept(server_socket, (struct sockaddr *)&amp;client_address, &amp;client_addrlen);

        if (fork() == 0 -&gt; true) { // 자식 프로세스


            printf(&quot;Server: Accepted connection from %s:%d\n&quot;,
                   inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));

            // 3-way handshake의 나머지 두 단계 수행
            // 여기서는 ACK를 보내는 과정만 간단히 보여줍니다.
            sleep(1); // 실제로는 필요한 로직 수행

            // 서버의 응답 전송
            char response[] = &quot;HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!&quot;;
            send(client_socket, response, strlen(response), 0);
            printf(&quot;Server: Sent response to client.\n&quot;);

            close(client_socket);
            exit(0); &lt;-여기서 자식 프로세스가 종료됨
        }

        close(client_socket);
    }

    close(server_socket);

    return 0;
}</code></pre><p>마지막으로 위에서 설명한 시스템 콜들을 합친 아파치(HTTP 웹 서버)의 전체 코드입니다. </p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;arpa/inet.h&gt;

int main() {
    const char* server_ip = &quot;127.0.0.1&quot;;
    int server_port = 8080;

    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror(&quot;Socket creation failed&quot;);
        return 1;
    }

    struct sockaddr_in server_addr, client_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip);

    if (bind(server_socket, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) == -1) {
        perror(&quot;Binding failed&quot;);
        return 1;
    }

    if (listen(server_socket, 5) == -1) {
        perror(&quot;Listening failed&quot;);
        return 1;
    }

    printf(&quot;Server listening on %s:%d\n&quot;, server_ip, server_port);

        //클라이언트 프로세스
    while (1) {
        socklen_t client_addr_len = sizeof(client_addr);
        int client_socket = accept(server_socket, (struct sockaddr*)&amp;client_addr, &amp;client_addr_len);
        if (client_socket == -1) {
            perror(&quot;Accepting client failed&quot;);
            continue;
        }

        printf(&quot;Accepted connection from %s:%d\n&quot;, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        char request[1024];
        //receive
        recv(client_socket, request, sizeof(request), 0);
        printf(&quot;Received request:\n%s\n&quot;, request);

                //send
        char response[] = &quot;HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nHello, World!&quot;;
        send(client_socket, response, sizeof(response), 0);

        close(client_socket);
    }

    close(server_socket);
    return 0;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[비동기 처리 async & await in Node.js]]></title>
            <link>https://velog.io/@iumiere-on/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC-async-await-in-Node.js</link>
            <guid>https://velog.io/@iumiere-on/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC-async-await-in-Node.js</guid>
            <pubDate>Wed, 31 Jan 2024 15:46:14 GMT</pubDate>
            <description><![CDATA[<p>오늘은 Node.js를 공부하려면 꼭 알아야하는 기능인 비동기처리에 대해서 알아보고자 한다. 
우선 간략한 목차는 다음과 같다.</p>
<h4 id="목차">목차</h4>
<ol>
<li>동기 vs. 비동기</li>
<li>Node.js에서 비동기처리를 사용해야하는 이유</li>
<li>비동기처리의 발전과정</li>
<li>async &amp; await</li>
<li>비동기처리의 효과</li>
</ol>
<h2 id="1-동기-vs-비동기">1. 동기 vs. 비동기</h2>
<h3 id="동기synchronous">동기(synchronous):</h3>
<p>동시에 일어난다는 의미로 하나의 요청이 들어오면 결과가 나온 후 다음 요청이 들어오고 결과가 나온다. 이렇게 요청이 순차적으로 처리된다.</p>
<h3 id="비동기asynchronous">비동기(asynchronous):</h3>
<p>병렬적으로 처리된다는 의미로 여러 요청을 동시에 처리한다. </p>
<h2 id="2-nodejs에서-비동기처리를-사용해야하는-이유">2. Node.js에서 비동기처리를 사용해야하는 이유</h2>
<p>Node.js는 요청을 멀티 스레드로 처리하는 것이 아니라 병렬로 처리한다. 즉 Node.js는 비동기 IO(Input&amp;Output)을 지원하고 싱글 스레드를 기반으로 동작한다.(*싱글 스레드는 처리시간이 긴 작업의 경우 시간이 많이 멀티 스레드에 비해 늘어날 수 있다는 단점이 있다.)</p>
<p>그렇다면 Node.js가 병렬로 처리하기 위해서 어떤 방법을 사용할까?</p>
<h4 id="_이벤트-방식_으로-처리한다">_이벤트 방식_으로 처리한다.</h4>
<p><img src="https://velog.velcdn.com/images/iumiere-on/post/cc94168b-b65a-4e0f-9d42-f7e28d2136d5/image.png" alt=""></p>
<h4 id="nodejs를-공부하다보면-한-번쯤은-접했을-개념인-이벤트-방식에서는">Node.js를 공부하다보면 한 번쯤은 접했을 개념인 이벤트 방식에서는</h4>
<p>1) 요청을 비동기로 처리하기 위해 이벤트를 발생시키고 
2) 서버 내부에 메세지 형태로 전달한다.</p>
<p>이후 서버 내부에서 이벤트 루프가 메세지를 처리한다. 
3) 이렇게 내부에서 메세지를 처리하는 동안 제어권이 다음 요청으로 넘어간다.
4) 처리가 완료되면 Callback을 호출해 처리완료를 호출 측에 알려준다. </p>
<p>이처럼 Node.js의 기본적인 작업 방식이 병렬적으로 처리하는 것이기 때문에 해당 서버에서는 비동기처리를 사용하는 것이 보다 효과적이다. </p>
<h2 id="3-비동기처리의-발전과정">3. 비동기처리의 발전과정</h2>
<p>비동기처리는 ES가 발전함에 따라 같이 발전해왔다. 나는 크게 4가지 형태를 살펴볼 예정이다.</p>
<h4 id="1-callback">1) Callback</h4>
<h4 id="2-promise--then--catch">2) Promise &amp; then &amp; catch</h4>
<h4 id="3-async--await">3) async &amp; await</h4>
<h4 id="1-callback-함수는-비동기-작업이-끝나고-난-뒤-실행되는-함수이다">1) Callback 함수는 비동기 작업이 끝나고 난 뒤 실행되는 함수이다.</h4>
<p>내가 느끼기에 &#39;지옥의 연속성&#39;이라는 단점이 있다. 말 그대로 callback을 사용하게 되면 덧붙여서 계속 사용하는 경우가 많은데 이는 코드의 가독성을 떨어뜨린다는 단점이 있다. </p>
<p>아래는 콜백 함수의 예제이다.</p>
<pre><code>const fs = require(&#39;fs&#39;);

// 파일 읽기 함수
function readFileContent(filePath, callback) {
    fs.readFile(filePath, &#39;utf8&#39;, (err, data) =&gt; {
        if (err) {
            // 에러가 발생한 경우 콜백 함수에 에러를 전달
            callback(err, null);
            return;
        }

        // 정상적으로 파일을 읽은 경우 콜백 함수에 데이터를 전달
        callback(null, data);
    });
}

// 예제 파일 경로
const filePath = &#39;example.txt&#39;;

// 파일 읽기 함수 호출
readFileContent(filePath, (err, data) =&gt; {
    if (err) {
        console.error(&#39;파일 읽기 에러:&#39;, err);
        return;
    }

    console.log(&#39;파일 내용:&#39;, data);
});

console.log(&#39;파일 읽기 요청을 기다립니다...&#39;);
</code></pre><h4 id="2-promise--then--catch-1">2) Promise &amp; then &amp; catch</h4>
<p>promise 객체는 콜백함수의 단점을 보완하기 위해 등작했으며 기본 형태는
<strong>Promise(resolve, reject)</strong>이다. 
아래 예제는 파일 읽기를 promise를 이용해 실행한 것이다.  </p>
<pre><code>const fs = require(&#39;fs/promises&#39;);

// Promise를 사용한 파일 읽기 함수
function readFileContent(filePath) {
    return new Promise((resolve, reject) =&gt; {
        fs.readFile(filePath, &#39;utf8&#39;)
            .then((data) =&gt; {
                resolve(data); // 파일을 성공적으로 읽었을 때 resolve 호출
            })
            .catch((err) =&gt; {
                reject(err); // 에러가 발생했을 때 reject 호출
            });
    });
}

// 예제 파일 경로
const filePath = &#39;example.txt&#39;;

// Promise를 사용한 파일 읽기 함수 호출
readFileContent(filePath)
    .then((data) =&gt; {
        console.log(&#39;파일 내용:&#39;, data);
    })
    .catch((err) =&gt; {
        console.error(&#39;파일 읽기 에러:&#39;, err);
    });

console.log(&#39;파일 읽기 요청을 기다립니다...&#39;);
</code></pre><p>이때 resolve는 주어진 일을 성공했을 때, reject는 실패했을 때 호출된다. 
또한 promise를 반환하는 함수는 then과 catch를 통해 결과와 에러를 처리할 수 있다. </p>
<p>하지만 promise then을 연속해서 사용할 경우 가독성이 떨어진다는 단점이 있다. </p>
<h4 id="3-async--await-1">3) async &amp; await</h4>
<p>async와 await은 앞선 promise를 보완하기 위해 등장했다. then과 catch를 사용하지 않는 대신 async와 await을 사용해 코드의 가독성을 높히고 코드를 보다 간결하게 만들어준다. </p>
<h2 id="4-async--await">4. async &amp; await</h2>
<p><strong>async</strong>
async는 function 앞에 덧붙여 비동기로 처리할 것이라고 알려준다. </p>
<p><strong>await</strong>
await은 비동기로 사용할 함수의 왼쪽에 명시한다.
그리고 Promise 비동기 처리가 완료될 때 까지 코드 실행을 일시 중지하고 wait 하는 역할을 한다. </p>
<p>아래 예제는 관련 코드이다. </p>
<pre><code>const fs = require(&#39;fs/promises&#39;);

// async 함수를 사용한 파일 읽기 함수
async function readFileContent(filePath) {
    try {
        const data = await fs.readFile(filePath, &#39;utf8&#39;);
        return data;
    } catch (err) {
        throw err;
    }
}

// 예제 파일 경로
const filePath = &#39;example.txt&#39;;

// async 함수를 사용한 파일 읽기 함수 호출
async function main() {
    try {
        const data = await readFileContent(filePath);
        console.log(&#39;파일 내용:&#39;, data);
    } catch (err) {
        console.error(&#39;파일 읽기 에러:&#39;, err);
    }
}

// main 함수 호출
main();

console.log(&#39;파일 읽기 요청을 기다립니다...&#39;);
</code></pre><p>  해당 코드는 파일 읽기를 async와 await을 사용해 비동기처리하는 과정으로 앞선 예제들과 같은 작업을 수행한다. 그러나 앞선 예제들과 비교했을 때 async와  await을 사용한 비동기처리 코드가 훨씬 간결함을 알 수 있다. </p>
<p>또한 이 코드가 비동기처리로 수행된다는 점을 고려했을 때 main함수에서 출력하는 <code>파일 내용: &#39;data에 해당하는 부분&#39;</code>  보다  &#39;파일 읽기 요청을 기다립니다...&#39;가 먼저 출력된다는 점을 짐작할 수 있다. </p>
<h2 id="5비동기처리의-효과">5.비동기처리의 효과</h2>
<p>그렇다면 Node.js는 왜 비동기처리 방식을 택했으며 비동기로 처리하기 위해 콜백함수부터 async&amp;await에 이르기까지 다양한 발전을 거듭해왔을까?</p>
<p>그 이유는 첫번째 목차에서 설명한 것처럼 메인스레드의 단점을 보완하기 위해서이다. 메인스레드에서는 요청을 여러 스레드에서 병렬적으로 처리하므로 동시 접속자가 많을 경우 그만큼 스레드의 사용이 많아지며 이에 따라 메모리 자원도 많이 사용된다. 그럼 메인 스레드의 기능이 저하된다. </p>
<p>이와 달리 Node.js는 병렬처리를 thread가 아닌 이벤트 루프에서 하기 때문에 멀티 스레드의 단점을 보완할 수 있다. 즉 요청을 받는 곳과 요청을 처리하는 곳이 달라 효과적으로 동작할 수 있다. </p>
<p>Node.js의 비동기처리는 현재까지 나온 async&amp;await 을 비롯해 발전을 거듭할 것이다. Node.js을 사용하는 개발자라면 이러한 비동기처리 방식의 발전에 지속적인 관심을 가지면 좋을 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Room을 이용한 데이터베이스 ]]></title>
            <link>https://velog.io/@iumiere-on/Room%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@iumiere-on/Room%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Wed, 31 Jan 2024 15:06:38 GMT</pubDate>
            <description><![CDATA[<p>나는 현재 플러터를 이용한 안드로이드 앱 개발을 진행 중이다. 앱은 프론트엔드와 백엔드가 명확히 나뉘어져 있지는 않지만 팀원 간 역할을 분배하는 과정에서 백엔드를 맡게 되었다. </p>
<p>프로젝트 초기에 데이터베이스는 로그인, 회원가입, 게시판 등의 기능은 구현하지 않기로 팀원들 간 합의를 봐서 플러터와 파이어베이스에 관한 공부를 진행했다. </p>
<p>나는 그저 대중적이고 편한 프레임워크라는 이유로 파이어베이스를 선택한 것이였는데 파이어베이스에 관한 공부를 하면 할수록 우리가 만드는 간단한 앱에서는 서버가 아예 필요없고 오직 데이터베이스만 필요하다는 사실을 느꼈다. room으로 정착하기까지 2번의 수정을 거쳤다. </p>
<p>우선 1차는 firebase -&gt; SQLite 였다. 팀원이 타 프로젝트에서 SQLite를 사용했었는데 엄청 편리했다면서 추천해줬다. 그래서 열심히 구글링하고 안드로이드 스튜디오 사이트(<a href="https://developer.android.com/?hl=ko)%EC%97%90%EC%84%9C%EB%8F%84">https://developer.android.com/?hl=ko)에서도</a> 찾아본 결과 안드로이드 스튜디오와 구글은 최근 들어 room을 지향하고 있다는 사실을 알게 되었다. </p>
<p>그래서 2차는 SQLite -&gt; room 이 되었다. room에 관한 정리는 TIL 시리즈에 정리해놓았다. </p>
<p>사실 플러터에는 다양한 데이터베이스의 종류가 있기 때문에 뭐가 정답이라고 할 수 없다. 현재 본인이 필요한 방향성에 따라 선택하면 된다. 파이어베이스가 간편하긴 하지만 파이어베이스의 많은 기능이 필요가 없다면 SQFlite(플러터 버전의 SQLite)를 사용하면 되는 것이고 그 외에도 room과 같은 다양한 선택지가 있다. </p>
<p>혹시나 데이터베이스에 관해 고민하다가 이 글을 봤다면 자신의 방향성에 맞게 편리하다고 생각되는 데이터베이스를 택하길 바란다.</p>
]]></description>
        </item>
    </channel>
</rss>