<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jiyun_kang.log</title>
        <link>https://velog.io/</link>
        <description>Proverbs 2:20</description>
        <lastBuildDate>Thu, 20 Nov 2025 09:26:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jiyun_kang.log</title>
            <url>https://velog.velcdn.com/images/jiyun_kang/profile/cc602378-2e5a-400d-bdc9-d7b28a1be2ca/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jiyun_kang.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jiyun_kang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[최신 safeguard 모델 비교] gpt-oss-safeguard vs kanana safeguard]]></title>
            <link>https://velog.io/@jiyun_kang/%EC%B5%9C%EC%8B%A0-safeguard-%EB%AA%A8%EB%8D%B8-%EB%B9%84%EA%B5%90-gpt-oss-safeguard-vs-kanana-safeguard</link>
            <guid>https://velog.io/@jiyun_kang/%EC%B5%9C%EC%8B%A0-safeguard-%EB%AA%A8%EB%8D%B8-%EB%B9%84%EA%B5%90-gpt-oss-safeguard-vs-kanana-safeguard</guid>
            <pubDate>Thu, 20 Nov 2025 09:26:09 GMT</pubDate>
            <description><![CDATA[<h2 id="introduction">Introduction</h2>
<p>지난 4월, 10대 소년이 Chatgpt가 가르쳐준 방법으로 스스롤 목숨을 끊은 사건이 전세계적으로 알려져, 충격을 주고 있다. 이전부터 AI 윤리적 이슈들에 대한 논의는 끊임없이 이어지고 있었다. 이러한 사건들이 반복되지 않기 위해서 AI 산업에서는 기술적인 개선과 해결방안이 필요할 것으로 보인다. </p>
<p><a href="https://www.mt.co.kr/tech/2025/09/07/2025090409390424779">챗GPT, 10대 소년에 &quot;이렇게 자살해&quot;…충격에 안전 강화하는 AI업계 - 머니투데이</a></p>
<h3 id="1-open-ai의-첫번째-안전-모델-gpt-oss-safeguard">1. Open AI의 첫번째 안전 모델: gpt-oss-safeguard</h3>
<p>Citation : OpenAI devday 2025</p>
<p><strong>안전 분류 작업을 위한 개방형 가중치 추론 모델</strong></p>
<p>지난 10월에 맞춤형 안정 정책을 지원하는 안전 추론 모델로 gpt-oss-safeguard-120b 와 20b모델을 출시하였다. 본 모델의 차별점은 reasoning(추론) 기반으로 라벨학습을 하지 않아도 적용이 가능하고 정책을 바꿀 수 있는 유연성이 높으며, chain-of-thought 방식을 통해 투명성이 있다는 점이다.</p>
<p>이전 안전 분류기 모델은 안전한 콘텐츠와 안전하지 않은 콘텐츠의 수천개의 예시를 사전 정의하고 안전 정책에 따라 수동적으로 분류하도록 개발하였습니다. 그러나, 과거 방식은 실제 안전 정책 문서를 직접 입력으로 받지 않고, 라벨링된 예시만으로 “어떤 기준으로 위험으로 판단 되었는가’를 암묵적으로 학습하였다. 즉, 안전하지 않다고 레이블이 지정된 콘텐츠에서는 유사성을 찾고, 안전한 콘텐츠와의 차이점을 찾음으로써 학습을 진행하였다. 이 방식의 장점은 계산량이 적어 레이턴시가 짧고 비용이 낮다는 점이었다. 하지만, 단점은 훈련 예시를 충분히 수집해야하기 때문에 비용이 오히려 더 들 수 있고 정책을 업데이트를 하려면 재훈련을 시켜야 했다.</p>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/49e1e8d3-cc3a-4a4c-9935-fdb5782f74be/image.png" alt=""></p>
<p>반면, 이번 모델은 추론 능력을 통해 새롭게 작성된 정책을 일반화할 수 있다 있다는 장점을 가지고 있다. 개발자가 제시한 정책 텍스트(사용자 메시지, 챗 완료, 전체 대화 등에 적용할 정책)를 추론 시점(inference time)에 직접 받아들여 그 정책에 따라 콘텐츠를 분류한다. 즉, 정책이 모델 내부에 미리 “학습”되어 있는 거이 아니라, 실행 시점에 투입되므로 정책을 빠르게 바꾸거나 커스터마이즈 할 수 있다.</p>
<p>또한, chain-of-thought으로 추론 과정을 거쳐 어떤 결정을 어떻게 내렸는지 내부과정을 볼 수 있다. 이를 통해, 결정 근거를 검토할 수 있고, 정책 개선이나 오류 분석 시 유리하다.  따라서, 단순히 피쳐(feature)륿 보고 스코어만 내는 것이 아니라, 정책을 해석하고 콘텐츠가 그 정책을 어떻게 위반하는지 논리를 생성한다.</p>
<p>마지막으로, oss-safeguard 는 “정책을 직접 입력 받는다”는 점 때문에, 학습으로 인해 고정된 것이 아닌 외부에서 입력된 정책 문서에 의해 판단 기준이 동적으로 주어질 수 있다. 따라서,플랫폼 혹은 도메인 별로 고유 정책을 적용할 수 있다는 장점을 가지고 있다.</p>
<p><strong>제약사항</strong></p>
<ol>
<li>정책에서 직접 추론할 때 레이블이 지정된 수만개의 고품질샘플에 대해 학습한 분류기가 여전히 성능이 높다.</li>
<li>시간과 컴퓨팅이 많이 들어갈 수 있기 때문에 모든 플랫폼 콘텐츠로 확장하기 어렵다.</li>
</ol>
<p>→ 경우에 따라 비동기식으로 기존 안전 추론기를 사용하여 레이턴시가 짧은 사용자 경험을 제공하도록 내부적으로 처리해둠.</p>
<h2 id="2-kakao의-첫번째-안전-모델-kakao-safeguard">2. Kakao의 첫번째 안전 모델: Kakao-safeguard</h2>
<p>본 모델은 카카오의 자체 언어모델인 8B를 기반으로 한 유해 콘텐츠를 탐지하는 모델이다. 리스크 여부에 따라 분류하도록 학습되어 있으며, 분류 결과는 <SAFE> 또는 <UNSAFE-S4> 형식의 단일 토큰으로 출력된다. 리스트 분류 체계는 ML Commons 분류 체계를 기반으로 하며, 한국 로컬 특성에 따라 총 7개의 카테고리로 리스크 분류 체계를 수립하였다.</p>
<p>학습 데이터는 수기 데이터와 합성 데이터로 구성되어 있으며, 한국어로만 되어있다. 수기 데이터는 내부 정책에 부합하도록 전문 라벨러가 직접 생성하고 라벨링한 데이터다. 합성 데이터는 LLM 기반 표현 변환과 노이즈 삽입 등 다양한 데이터 증강기법을 사용하여 만들었다.</p>
<p>Kakao-safeguard는 시리즈가 크게 3가지로 구성되어 있는데, Kanana Safeguard, Kanana Safeguard-Siren, Kanana Safeguard-Prompt로 각 시리즈 별로  발생 맥락과 정책 판단 기준이 다르기 때문에 모델을 분리하였다.</p>
<table>
<thead>
<tr>
<th>모델명(파라미터 수)</th>
<th>탐지  대상</th>
<th>분류 체계</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Kanana Safeguard (8B)</strong></td>
<td>사용자 발화 및 AI 응답</td>
<td><strong>유해 콘텐츠가 출력될 리스크</strong></td>
</tr>
<tr>
<td>증오(S1), 괴롭힘(S2), 성적 콘텐츠(S3), 범죄(S4), 아동 성착취(S5), 자살 및 자해(S6), 잘못된 정보(S7)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><strong>Kanana Safeguard-Siren (8B)</strong></td>
<td>사용자 발화</td>
<td><strong>법적·정책적 리스크</strong></td>
</tr>
<tr>
<td>성인인증(I1), 전문 조언(I2), 개인정보(I3), 지식재산권(I4)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><strong>Kanana Safeguard-Prompt (2.1B)</strong></td>
<td>사용자 발화</td>
<td><strong>프롬프트 공격 리스크</strong></td>
</tr>
<tr>
<td>Prompt Injection(A1), Prompt Leaking(A2)</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/7c48a448-01f0-4c1d-b6b0-4bb540907171/image.png" alt=""></p>
<p>본 모델은 추론 비용을 최소화하기 위해, 추론에 소요되는 리소스를 과도하게 사용하지 않도록 출력 결과를 &lt;단일 토큰&gt;으로 반환하도록 설계하였다.</p>
<h2 id="3-비교-결과">3. 비교 결과</h2>
<p>두 모델은 추론모델과 분류모델이라는 점에서 큰 차이가 있따. 즉, Kanana-safeguard는 고정된 리스크 카테고리 라벨형식으로 미리 정의된 위험 유형을 탐지하는 방식인 반면, gpt-oss-safeguard는 사용자가 제공하는 정책을 실행(inference) 시점에 입력으로 받아서 그 정책에 따라 콘텐츠를 평가하는 구조다. 두 모델의 장단점은 명확하다. 전자의 모델은 고정된 정책이외에는 분류를 하지 못하지만, 그만큼 시간이 빠르고, 정의되어 있는 정책 내에서도 정확도가 꽤나 높다. 그러나, 도메인에 따라 유연하게 사용하지 못하지만 후자 모델은 “맞춤형 적용성”을 가지고 있어서 유연하다는 장점이 있다. 그러나, 연산량이 많아서 지연이 크다는  것을 단점으로 가지고 있다. 이는 두 모델을 통합할 수 있는 방안을 모색하여, 정확도는 높으면서 레이턴시를 최소화할 수 있는 모델을 개발해야 한다는 시사점을 주고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문 리뷰] HALO: Hierarchical Autonomous Logic-Oriented Orchestration for Multi-Agent LLM Systems]]></title>
            <link>https://velog.io/@jiyun_kang/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-HALO-Hierarchical-Autonomous-Logic-Oriented-Orchestration-for-Multi-Agent-LLM-Systems</link>
            <guid>https://velog.io/@jiyun_kang/%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-HALO-Hierarchical-Autonomous-Logic-Oriented-Orchestration-for-Multi-Agent-LLM-Systems</guid>
            <pubDate>Thu, 16 Oct 2025 05:04:46 GMT</pubDate>
            <description><![CDATA[<h2 id="overview">Overview</h2>
<ul>
<li><p><strong>HALO Framework</strong>
High-level : Planning agent
Mid-level : role-design agents
Low-level : inference agent → Workflow search engine (subtask execution with Monto Carlo Tree Search(MCTS))</p>
</li>
<li><p><strong>Adaptive Prompt Refinement module</strong>
: raw query → task-specific prompt</p>
</li>
</ul>
<h2 id="challenges">Challenges</h2>
<blockquote>
<p>*<em>How to maintain robust performance in complex interaction environments and expert-level task?
*</em></p>
</blockquote>
<ul>
<li><strong>limitation</strong> : 
(1) heavily depend on expert insight and manually-designed policies (rigid and predefined role space)
(2) users’ lack expertise in prompt engineering<br></li>
<li><strong>main problems</strong> :
  (1) how can agentic systems <strong>self-organize and coordinate in unfamiliar environments</strong> with <strong>minimal manual intervention ?</strong>
  (2) how can <strong>user queries be refined</strong> to improve the overall efficiency and effectiveness of multi-agent collaboration?</li>
<li><strong>HALO solution:</strong>
(1) an extensible agent-role instantiation mechanism and a dynamic communication architecture
  (2)prompt engineering module</li>
</ul>
<h2 id="halo-framework">HALO Framework</h2>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/d6215d51-3a51-459f-a15b-4a05533fdda8/image.png" alt=""></p>
<h3 id="1-adaptive-prompt-refinement">(1) Adaptive prompt Refinement</h3>
<ul>
<li><p><strong>four collaborative</strong> agents 
→ different phase of prompt refinement (query parsing ~ final synthesis)</p>
</li>
<li><p>detailed in Section 3.2</p>
<ul>
<li><strong>Agent P1</strong><image src="https://velog.velcdn.com/images/jiyun_kang/post/fa3dead6-d053-4509-bfee-6e968e3e3ff5/image.png" width= 400>
 - Task Parser Agent
 - analyze the original query Q  
  → three components : 
  1) task type T 
  2) core intent 
  3) key details D
 - F → a Global semantic context throughout the entire reasoning flow

</li>
</ul>
<ul>
<li><p><strong>Agent P2</strong></p>
<image src="https://velog.velcdn.com/images/jiyun_kang/post/c11691a8-b7b8-4f05-b2b6-51e9fc3845c5/image.png" width= 300>
 - Prompt Template Agent
 - Qo : task description 재구성, clear reasoning objectives, bounded input conditions. explicit output format

<ul>
<li><strong>Agent P3</strong><ul>
<li>Prompt Optimization Agent</li>
<li>slow thinking prompting strategies, tool calling instructions, optimized prompt</li>
</ul>
</li>
<li><strong>Agent P4</strong><ul>
<li>Prompt Generator Agent</li>
<li>Sythesizes the optimized structure</li>
<li>the entry point for downsream multi-agent reasoning</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="2-hierarchical-reasoning-stack-section-33">(2) Hierarchical Reasoning Stack (Section 3.3)</h3>
<ul>
<li><p>three-tier multi-agent collaboration architecture</p>
<ol>
<li><p><strong>High-level : Planning agent</strong> [과업을 한번에 세분화하지 않고 점진적으로 하나의 세부 과업을 만들면, 그걸 기반으로 다음 과업을 갱신하는 형태로 계획을 세움]</p>
<p>refined prompt Q*, Global structure task representation F,  subtask execution history H k-1 (Tk 이전에 수행된 하위 과업들의 실행 이력을 나타냄)</p>
</li>
</ol>
</li>
</ul>
<image src= https://velog.velcdn.com/images/jiyun_kang/post/a23b6035-2e67-466b-a73e-07814a72a404/image.png width=400>


<blockquote>
<p>Aplan은 정제된 프롬프트 Q∗와 전역 구조화된 과업 표현 F을 입력받는다.
     이 정보를 바탕으로 전체 작업을 하위 과업들의 순서적 집합{T1,T2,...,TK}으로 분해한다.
전체 과업을 한 번에 모두 세분화하는 대신,
    Aplan은 <strong>단계적(step-wise) 전략</strong>을 사용하여 <strong>한 번에 하나의 하위 과업만 생성</strong>하고, 이전 하위 과업들의 수행 이력 Hk−1을 바탕으로 자신의 분해 정책(decomposition policy)을 점진적으로 갱신한다.</p>
</blockquote>
<ol>
<li><p><strong>Mid-level : role-design agents [LLM 기반 하위 에이전트를 생성하는 역할+ 시스템 프롬프트 할당]</strong></p>
<p><strong>2-1. 하위 에이전트 생성</strong></p>
<p>Tk가 Arole에게 과업을 이행하면 아래와 같이 LLM 기반 하위 에이전트 ak를 생성한다.<image src="https://velog.velcdn.com/images/jiyun_kang/post/0d2e48eb-67d3-474e-be2f-915ade79f9dc/image.png" width= 200></p>
<blockquote>
<p>하위 에이전트를 생성하는 과정은 다음과 같다</p>
<ul>
<li><p>하위 과업 TkT_kTk의 의미적 정보(semantics),</p>
</li>
<li><p>정제된 프롬프트 Q∗,</p>
</li>
<li><p>전역 구조화된 과업 표현 F</p>
<p>  에 의해 <strong>공동으로 가이드 →</strong> </p>
<p>  이를 통해 각 생성된 에이전트 ak(i)가 해당 하위 과업 Tk의 요구 사항에 잘 정렬(aligned)되도록 보장</p>
</li>
</ul>
</blockquote>
<p><strong>2-2. 역할별 시스템 프롬프트 생성</strong> </p>
<p>각 역할에 맞는 시스템 프롬프트를 할당 받으며 하위 과업 Tk에 의해서 행동을 제어한다. 즉, 역할 설계 에이전트는 (Tk,Q∗,F)을 입력받아 특화된 역할 프롬프트ρk(i)를 생성</p>
<image src =https://velog.velcdn.com/images/jiyun_kang/post/79bf6452-3255-4f67-a887-ff26559f302c/image.png width=300>


</li>
</ol>
<pre><code>1. **Low-level : inference agent**

**3-1. 협력적 추론 수행** 

Tk,Q*, F를 받은 Inference agent는 협력적 추론을 수행하고 이에 맞는 집합 Yk라는 중간 산출물을 생성
&lt;image src= https://velog.velcdn.com/images/jiyun_kang/post/cf6710f6-3215-4c12-983e-6b04a1a10ba4/image.png width=400&gt;      </code></pre><ol start="4">
<li><strong>조기종료( early-stopping)</strong></li>
</ol>
<ul>
<li>HALO는 고수준 계획 에이전트 Aplan 내부에 조기 종료 메커니즘(early-stopping mechanism)을 통합 (비잔틴 합의 이론에서 영감→ 이 이론에 따르면 하나의 통신 라운드에서 p개의 오류(faulty) 에이전트를 견디기 위해서는 최소 3p + 1개의 에이전트가 필요하다.)</li>
<li>HALO는 수행 이력 Hk에 포함된 하위 과업 중 66% 이상이 동일한 일관된 답변 Y^에 도달했을 경우, 추론 과정을 조기에 종료</li>
</ul>
<h3 id="3-workflow-search-engine-section-34">(3) Workflow Search Engine (Section 3.4)</h3>
<ul>
<li>At the low-level with inference agents</li>
<li>reformulate subtask execution as a workflow search process (Monte Carlo Tree Search, MCTS)</li>
<li>Node : corresponds to an agent-generated response /  intermediate reasoning step</li>
<li>Edge: denote possible transitions between reasoning states</li>
<li>search trees with Node &amp; Edge : represent a candidate reasoning trajectory for each substask</li>
<li>Section 3.4<ul>
<li>HALO는 여러 에이전트가 서로 다른 역할로 협력하는 추론 과정을 <strong>트리 형태의 탐색 공간</strong>으로 모델링</li>
<li>Node: 트리의 한 점(node)은 하나의 <strong>“역할을 수행 중인 에이전트”</strong></li>
<li>Edge: 엣지(edge)는 에이전트 간의 <strong>의사소통 혹은 정보 전달(communication transition)</strong></li>
<li>Node + Edge: 하나의 하위 과업에 대한 후보 추론 경로(candidate reasoning trajectory)를 형성</li>
<li>추론하기 위한 MCTS paradigm (Selection, Expansion, Simulation, Backpropagation)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/a10be085-5211-4c9f-b350-ee4660c538a5/image.png" alt=""></p>
<ul>
<li><strong>Selection</strong>  → best agent를 선택 with <strong>UCT algorithm</strong><image src=https://velog.velcdn.com/images/jiyun_kang/post/281c008d-4ff1-432e-8b0d-9c90121f6002/image.png width=400>

</li>
</ul>
<p>UCT는 “성과가 좋거나, 아직 많이 시도하지 않은 노드”를 우선적으로 선택하는 알고리즘 ! 
          -  첫번째 항 : 평균 보상값(Vk : the score value of agent) ; 지금까지의 보상이 크면 그 노드를 더 자주 선택 → <strong>활용(exploitation)</strong> 
      - 두번째 항 : 현재 노드의 방문 횟수가 적을수록 값이 커짐 → <strong>탐험(exploration)</strong></p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>N</td>
<td><strong>전체 탐색 횟수</strong> (부모 노드가 몇 번 방문되었는지)</td>
</tr>
<tr>
<td>nk</td>
<td><strong>현재 노드 k의 방문 횟수</strong></td>
</tr>
<tr>
<td>α</td>
<td><strong>탐험 정도를 조절하는 상수(Exploration constant)</strong>, 보통 √2 사용</td>
</tr>
</tbody></table>
<ul>
<li>Expansion → role-specific agent들을 만드는 역할<ul>
<li>Simulation → 시뮬레이션을 위해 expansion한 agent 각각의 실제가 아닌 가상 agent를 만들어서  <strong>중간 산출물(intermediate output)</strong> yk를 만든 다음, 두개의 보조 에이전트에 의해 평가됨 (judging agent &amp; scoring agent)<ol>
<li>에이전트 ak(i+1)은 하위 과업 Tk에 대해 시뮬레이션된 추론 경로(simulated reasoning trajectory)를 시작한다.          <blockquote>
<p>💡 즉, 실제 실행 전에 “가상의 추론 경로”를 만들어 앞으로의 reasoning 단계를 예측·시험하는 것</p>
</blockquote>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<ol start="2">
<li><p>“˜(틸드)”가 붙은 에이전트들은 실제가 아닌 <strong>가상(simulated)</strong> 에이전트이며, HALO가 “만약 이 방향으로 진행한다면 어떤 결과가 나올까?”를 탐색하기 위해 사용하는 것
<img src="https://velog.velcdn.com/images/jiyun_kang/post/0ea25074-43a3-46dc-9c92-e0fd421d8633/image.png" alt=""></p>
</li>
<li><p>각 시뮬레이션 에이전트 a<del>k(j)는 하위 과업 Tk, 정제된 프롬프트 Q∗, 전역 구조화된 과업 표현 F을 입력으로 받아 <strong>중간 산출물(intermediate output)</strong> y</del>k(j)를 생성한다.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/792f7bb3-738a-4d05-aaa3-31a6ff1e7a21/image.png" alt=""></p>
<blockquote>
<p>📘 즉, HALO는 실제 수행 없이 가상의 reasoning output을 여러 단계로 예측 생성하는 과정</p>
</blockquote>
</li>
<li><p>결과 평가: 두 보조 에이전트(auxiliary agents) -appendix B 참고</p>
</li>
</ol>
<p>가상 산출물y~k(j)은 <strong>두 개의 보조 에이전트(auxiliary agents)</strong>에 의해 평가</p>
<p>1) judge agent</p>
<p>판단 에이전트(judging agent)는 현재 하위 과업이 완료되었는지를 나타내기 위해 <strong>상태 레이블(status label)</strong>ℓ~k(j)을 부여한다.
이 값은 <strong>성공(success)</strong>, <strong>실패(fail)</strong>, <strong>계속 진행(continue)</strong> 중 하나</p>
<p> 2) scoring agent
 <em>평가 에이전트(scoring agent)*</em>는 생성된 산출물의 <strong>효과성(effectiveness)</strong>을 수치적으로 평가하기 위해 품질 점수(quality score) v~k(j)∈[0,1]을 계산</p>
<ul>
<li>*<em>Backpropagation *</em>→ reward signal adjustment mechanism based on the judgement outcome
<img src="https://velog.velcdn.com/images/jiyun_kang/post/f341ae60-7379-417a-a3e3-76e4cb1ab39a/image.png" alt=""></li>
</ul>
<blockquote>
<p>보상 계수 λ 정의: 
λ(ℓ<del>k(j))는 상태 레이블 ℓ</del>k(j)에 대응하는 <strong>영향 계수(impact factor)</strong>로, 시뮬레이션 결과에 대한 <strong>보상(reward)</strong> 또는 <strong>페널티(penalty)</strong>를 반영한다.</p>
</blockquote>
<p>부모 노드의 새로운 값은 “현재 노드의 기존 점수 × 노드 방문횟수 + 시뮬레이션 보상값 + (시뮬레이션)리프 노드의 품질 점수의 합”을 자식 수 + 기존 방문 수로 나눈 <strong>가중 평균(weighted average)</strong> 형태로 업데이트를 함께 반영하여 계산된다. 이를 통해 HALO는 과업 완료 상태에 대한 민감도를 높이고, reasoning trajectory 전반의 평가 정확도를 향상</p>
<blockquote>
<p><strong>HALO framework 알고리즘 정리</strong>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/01559e8c-496e-4031-a8e6-71c7d61635b7/image.png" alt=""></p>
</blockquote>
<h2 id="related-work">Related work</h2>
<h3 id="1-prompt-optimization-프롬프트-최적화--manual-intervention-없이도-modularity-⬆️"><strong>(1) Prompt optimization 프롬프트 최적화 ;</strong> manual intervention 없이도 modularity ⬆️</h3>
<ul>
<li>CAMEL : integrates a task-planner agent
→ CAMEL은 여러 에이전트(agent)가 역할(role)을 배정받아 <strong>역할 놀이(role-playing)</strong> 방식으로 상호작용하면서 과제를 해결하도록 설계됨, → 각 에이전트가 특정 역할을 맡고, 역할에 맞게 대화를 나누면서 협력 또는 상호작용 
<img src="https://velog.velcdn.com/images/jiyun_kang/post/a7d212c6-6208-43d3-be9c-45db5e9f39a9/image.png" alt=""></li>
</ul>
<ul>
<li><p>ComfyAgent: incorporate prompt generation as part of evolving workflows
→ ComfyAgent 내부에 여러 역할의 에이전트를 두고 협업하면서 워크플로우를 구축. 주로 Planner, Memory, Action 모듈 등이 있음.</p>
</li>
<li><p>challenge : prompts that runtime context changes  largely open in scenarios where agent roles or task objectives are not predefined</p>
</li>
</ul>
<h3 id="2-role-design-in-llm-based-architecture"><strong>(2) Role design in LLM-based architecture</strong></h3>
<ul>
<li><p>common strategy(ex. MetaGPT) : static role configuration ; governs their behavior through standard operating procedures → 문제: fixed roles ; open-ended or dynamic settings agent responsibilities must evolve in response to task changes</p>
</li>
<li><p>MetaGPT: 인간이 사용하는 <strong>표준 운영 절차 (SOP: Standard Operating Procedure)</strong>를 에이전트 워크플로우에 “프롬프트 시퀀스” 형태로 내재화</p>
</li>
<li><p>newer framework(ex. TPTU, DyLAN) : role generation mechanisms (agents to create, adapt, or inherit roles dynamically.)</p>
</li>
<li><p>TPTU :<strong>Task Planning</strong> + <strong>Tool Usage</strong> — 를 LLM 기반 에이전트에게 요구되는 핵심 역량으로 보고, 이를 구조화하여 평가하고 개선하기 위한 프레임워크 즉, 단순히 “텍스트를 생성하는 것”에서 벗어나, “임무를 분해하고, 어떤 도구를 쓸지 결정하고, 도구를 호출하고, 중간 결과를 관리하면서 완성해 나가는 에이전트”</p>
</li>
<li><p>DyLAN(현 논문과 가장 비슷한듯) : LLM 기반 에이전트를 동적으로 선택하고, 그들 사이 통신 구조를 동적으로 조정하면서 협업하도록 설계된 프레임워크<br></p>
<p>   → 자세히 설명</p>
<ul>
<li><p><strong>2단계 방식 (Two-stage paradigm)</strong></p>
<ul>
<li><p><strong>팀 최적화 (Team Optimization)</strong> 단계: 후보 에이전트 집합 중에서 주어진 작업에 가장 기여할 가능성이 높은 에이전트를 선정</p>
</li>
<li><p><strong>작업 해결 (Task Solving)</strong> 단계: 선정된 에이전트들이 실제로 협업하면서 최종 답을 도출</p>
</li>
</ul>
</li>
<li><p><strong>Agent Importance Score</strong></p>
<ul>
<li><p>팀 최적화 단계에서, 각 후보 에이전트가 초기 협업 시도(trial)에서 얼마나 기여했는지를 판단하는 비지도 지표</p>
</li>
<li><p>이 점수를 기반으로 에이전트를 선택하거나 제외하는 방식→ 필요없는 에이전트 비활성화 (중간에 LLM 기반의 랭커(rank­er) 역할을 두어, 어느 에이전트가 계속 참여할지 판단하게 함)</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="3-cooperation-optimization-strategies"><strong>(3) Cooperation optimization strategies</strong></h3>
<ul>
<li><p><strong>Centralized framework</strong>(ex. AgentLaboratory, ScoreFlow, WORKFLOW-LLM) : controller agent</p>
</li>
<li><p><strong>Decentralized approaches</strong>: control and decision-making across agents / peer-to-peer communication, identity-based protocols</p>
</li>
<li><p>Game-theoretic negotiation : greater autonomy and robustness in unstructured environments</p>
</li>
<li><p>Game-theoretic Workflow : LLM에게 단순한 “무엇을 해라” 지시를 넘어서, “이런 사고 과정을 거쳐야 한다”라는 구조적 지침을 제공 (규칙 설명 → 전략 탐색 → 균형 분석 →….)<br><img src="https://velog.velcdn.com/images/jiyun_kang/post/480f1825-80b7-4599-bac5-197b663ccf7a/image.png" alt=""></p>
</li>
<li><p>ex. AFlow : workflows through dynamic graph traversal</p>
<pre><code>  - **Workflow Optimization을 코드 수준의 탐색(search) 문제**로 재정의하고, 자동화된 방식으로 워크플로우를 생성하고 개선하는 시스템
  - 워크플로우를 코드(code)로 표현. 노드(node)는 LLM 호출을 대표하고, 엣지(edge)는 노드 간의 의존성 및 흐름을 나타냄 → 워크플로우 구조를 탐색하는 데 **Monte Carlo Tree Search (MCTS)** 기법을 활용. 노드를 추가하거나 수정하고, 이 변경을 평가하면서 탐색 트리를 확장 (본 논문과 동일) + 트리 상위에 피드백을 되돌려 주어 탐색에 반영. 즉, 좋은 워크플로우 구조는 더 자주 탐색되게 유도</code></pre><ul>
<li>building more context-sensitive agent teams</li>
</ul>
</li>
</ul>
<h2 id="experiments">Experiments</h2>
<h3 id="1-experimental-setup">1. Experimental setup</h3>
<p>   1) <strong>Code generation</strong> : HumanEval dataset (164 Python programming problems and units test), 평가지표- pass@1 metric (code accuracy)
   2) <strong>General reasoning *<em>: MMLU dataset (57 subjects with 15,908 questions in multiple-choice format with four options), 평가지표 - accuracy, 13% sampling
   3) *</em>Arithmetic reasoning</strong>: : MATH dataset (12,500 math problems- 7 subareas with 5 diffculty levels) 500 math problems sampling, 평가 - accuracy</p>
<h3 id="2-halo-setup">2. HALO setup</h3>
<p>   1) LLM: GPT -4o
    2) random seed : 10 
   3) temperature : 0.8
    4)  max tokens : 2048
    5) same number of few-shot examples 
    6) code interpreters as tools in the code generation task</p>
<h3 id="3-main-results">3. Main results</h3>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/355b636e-3313-4650-8243-4f8ce47dfb67/image.png" alt=""></p>
<ul>
<li><strong>인지적 과부하(cognitive overload) 해결</strong></li>
</ul>
<p>  HALO는 <strong>계층적 추론 아키텍처(hierarchical reasoning architecture)</strong>를 통해 기존 단일 에이전트 프레임워크(ReAct 등)의 한계인 인지적 과부하 문제를 극복하였다.</p>
<p>ReAct는 하나의 에이전트가 계획 수립, 추론, 반성(reflection)을 동시에 수행해야 하지만, HALO는 이 과정을 <strong>과업 분해(task decomposition)</strong>, <strong>역할 할당(role instantiation)</strong>, <strong>하위 과업 추론(subtask inference)</strong>으로 분리하여 <strong>에이전트 간 역할을 분담</strong></p>
<p>HALO는 ReAct 대비 다음과 같은 개선을 보였다 (Table 2 기준):</p>
<ul>
<li><strong>HumanEval pass@1</strong>: 69.1% → <strong>95.2%</strong> (+26.1%)</li>
<li><strong>MMLU 정확도</strong>: 57.6% → <strong>81.6%</strong> (+24.0%)</li>
<li><strong>MATH 정확도</strong>: 29.2% → <strong>58.9%</strong> (+29.7%)</li>
</ul>
<p>평균적으로 <strong>26.6% 성능 향상(78.6% vs. 52.0%)</strong>을 달성하였다. → 이는 HALO의 <strong>분산형 계층 추론 구조가 단일형(monolithic) 구조보다 우월함</strong></p>
<ul>
<li><strong>적응형 에이전트 생성과 탐색 기반 워크플로우</strong>
기존의 정적 MAS(예: CAMEL, LLM-Debate)는 고정된 역할과 수동 워크플로에 의존하고, 동적 MAS(예: DyLAN, AgentVerse, ADAS)는 세밀한 역할–과업 정렬(fine-grained alignment)이 부족하다.</li>
</ul>
<p>반면 HALO는 <strong>MCTS 기반 탐색(Monte Carlo Tree Search)</strong>을 통해 실시간 피드백에 따라 적절한 역할을 동적으로 생성하고, 실행 경로를 반복적으로 개선한다.</p>
<p>그 결과(Table 2):</p>
<ul>
<li>평균 성능: HALO 78.6% vs. ADAS 64.0% ( +14.6% )</li>
<li>HumanEval: 82.4% → <strong>95.2% (+12.8%)</strong></li>
<li>MMLU: 72.8% → <strong>81.6% (+8.8%)</strong></li>
<li>MATH: 36.9% → <strong>58.9% (+22.0%)</strong></li>
</ul>
<p>→ HALO는 <strong>대규모 다중 에이전트 추론(multi-agent reasoning at scale)</strong>을 정교하게 조율하는 데 탁월함을 입증</p>
<ul>
<li><strong>전문적·고난도 추론 능력</strong></li>
</ul>
<p>HALO는 <strong>복잡하고 전문적인(reasoning-intensive)</strong> 과업에서도 높은 성능을 보였다.</p>
<p>MMLU의 <strong>추상적 과목(abstract subjects)</strong>과 MATH의 <strong>고난도 세부 영역(subareas)</strong>을 비교한 결과:</p>
<ul>
<li>MMLU(추상 과목): HALO 70.8% vs. ADAS 56.4% → <strong>+14.4% 향상</strong></li>
<li>MATH(고난도 영역): HALO 43.9% vs. ADAS 24.4% → <strong>+19.5% 향상</strong></li>
</ul>
<h3 id="4-ablation-study">4. Ablation study</h3>
<image src =https://velog.velcdn.com/images/jiyun_kang/post/60b8f4c9-7ef9-4a5a-ab39-e3c00ee112fc/image.png width=400>

<p>*<em>1) Adaptive Prompt Refinement 
*</em>performance drops by 5.3% on average, with MMLU suffering the most (81.6% →75.4%). This result highlights the importance of structured prompt construction in enhancing task understanding and aligning reasoning trajectories with user intent.
*<em>2) High-level planning agent 
*</em>this leads to an average performance drop of 11.3% across all benchmarks. Notable drops on HumanEval (95.2% →83.8%) and MATH (58.9% →44.7%) demonstrate that removing task decomposition impairs reasoning coherence, highlighting its critical role in HALO.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025 AI Festa 다녀온 후기]]></title>
            <link>https://velog.io/@jiyun_kang/AI-Festa-%EB%8B%A4%EB%85%80%EC%98%A8-%ED%9B%84%EA%B8%B0%EA%B8%80</link>
            <guid>https://velog.io/@jiyun_kang/AI-Festa-%EB%8B%A4%EB%85%80%EC%98%A8-%ED%9B%84%EA%B8%B0%EA%B8%80</guid>
            <pubDate>Sat, 04 Oct 2025 08:39:30 GMT</pubDate>
            <description><![CDATA[<p>오늘은 같이 프로젝트를 했던 팀들과 AI Festa를 다녀왔다. AI를 다루는 대부분의 기업들이 모두 참석한 행사인만큼 볼거리가 정말 풍성했다. 팀원들과 AI 현재 동향도 살펴보고, 나름 우리가 공부했던 눈(?)을 바탕으로 비교도 해보고 재미난 시간을 보냈다 ☺️ 지금부터 흥미롭다고 생각했던 회사들을 골라서 아주 살짝 공부 한스푼과 느낀점을 곁들인 후기를 써보려고 한다.</p>
<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/2651e229-1640-45a6-b2c8-d50a768325e7/image.png" width="300"/></th>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/283ed585-9118-4dff-bc04-1b37a4235417/image.png" width="500"/></th>
</tr>
</thead>
</table>
<h2 id="💡-kakao---kanana">💡 Kakao - Kanana</h2>
<p>우선 들어가자마자 보이는 곳은 Kakao였다. 역시나 사람도 엄청많아서 줄을 서서 들어가야 했지만, 들어가서 4개의 세션으로 나눠져 있어서 사람이 최대한 적은 쪽부터 돌기 시작했다.</p>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/6d39fae7-db5e-407c-9bb5-203efeb8ca72/image.png" alt=""></p>
<h3 id="1-kanana-llm">1. Kanana LLM</h3>
<p>가장 먼저 본 곳은 새로 출시한 Kanana 모델을 소개하는 공간이었는데, 우리도 프로젝트때 파인튜닝 과정에서 Kanana를 사용했다보니, 엄청 반갑게 다가왔다. 우리는 Kanana-1.5-8B-instruct모델을 사용했는데 1.5버전에서 Kanana-1.5-15.7B-A3B를 새롭게 출시했다. 이 모델은 꽤 큰 파라미터를 가지고 있지만 추론 시에는 약 3B 파라미터만 활성화되는 MoE구조의 효율적인 모델이었다. </p>
<blockquote>
<p>*<em>MoE(Mixture of Experts) 구조란? *</em>
“MoE = 전문가 혼합” 이라는 이름에서 알 수 있듯이, 전체 모델을 여러 개의 전문가 네트워크(experts) 로 쪼개고, 입력(input)에 따라 어느 전문가를 쓰거나 가중합을 하여 출력을 만드는 방식이다.
MoE 레이어에서 입력을 받으면, 가장 먼저 router에서 어떤 expert가 적절한지 expert들의 점수를 매기고, top-K로 상위 몇개의 expert들로 입력 값을 흘려보내 처리하도록 한다. 그럼 활성화된 expert들로부터의 연산 출력값과 router에서 매긴 점수로 가중치를 두어서 합하여 이후 연산 과정에서 사용하게 된다. 
이는 모든 전문가를 매번 활성화하지 않고, 일부만 골라 쓰면 → 계산량(flops)을 줄이면서도 거대한 모델을 유지할 수 있다 (“sparse activation”) </p>
</blockquote>
<p>이렇듯, 카카오는 기존 dense구조였던 Kanana-1.5-8B 수준 성능과 비슷한 수준을 목표로 하면서 연산 비용을 낮추고자 하였다. 우리 프로젝트에서도 8B모델을 썼었는데, 모델을 돌리는데 최소 A100은 필요했다.. 이걸 쓰면 더 작은 GPU에서 돌릴 수 있을까...근데 또 15.7B라서 안 돌아갈거 같긴 하다..나중에 써볼 수 있는 기회가 있으면 좋겠다! 
더 자세한 내용은 Kakaotech 홈페이지에 들어가면 매우 자세하게 설명되어 있다!
<a href="https://tech.kakao.com/posts/716">Kanana-MoE 개발기 블로그 참고</a></p>
<h3 id="2-kanana-safeguard">2. Kanana-Safeguard</h3>
<p>그 다음 존에는 Kanana Safeguard 모델을 소개하고 있었는데, 한국어 특화 가드레일 모델이라서 우리나라 언어와 문화들을 잘 이해하고 있었다. 거기서 직접 모델을 사용도 해봤는데 어떤 질문을 돌려서해도 공격적인 내용이거나 잘못된 발언을 할 경우, 답변을 해주지 않았다. huggingface에서도 몇번 본적이 있는데 그때는 이러한 기능을 하고 있는 모델인줄 몰랐다. 직접 사용도 해보고 설명을 들으니깐 이 모델을 더 관심을 가지고 볼 수 있게 되었다. kanana-safeguard, Kanana-safeguard-siren, Kanan-safeguard-promt 이렇게 3가지 모델로 나눠져있다. 자세한 내용은 아래 huggingface 페이지을 참고하면 좋을거 같다! 
<a href="https://huggingface.co/kakaocorp/kanana-safeguard-8b">kakao safeguard huggingface 페이지</a>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/3a8a3668-e074-4a5b-a0a1-b61c4afd4fe1/image.png" alt=""></p>
<h3 id="3-play-mcp">3. Play MCP</h3>
<p>PlayMCP는 카카오에서 개발한 MCP서버 마켓으로, 다양한 MCP들을 툴로 불러와서 사용할 수 있었다.거기서 직접 체험도 해보니깐 내 프롬프트를 llm이 판단해서 필요한 MCP 툴을 불러와서 답변했다. 이렇게 툴로서 사용하니깐 확장성도 있고 좋았지만, 담당자 분께도 질문했을 때, 정확도나 직접 컨트롤 하는데는 한계가 있다는 점도 말씀해주셨다. 그래도 꽤나 다양한 MCP를 사용해서 툴로 사용할 수 있다는 것은 정말 큰 장점이었다.</p>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/457a3c44-dbc2-424c-a3db-d7fa29134321/image.png" alt=""></p>
<h3 id="4-kanana-ondevice-ai">4. Kanana Ondevice AI</h3>
<p>10월에 출시 예정인 카카오톡에 온디바이스AI가 내장되는데,거기서 사전 등록도 할 수 있었다. 온디바이스에 들어가는 모델은 Kanana Nano 모델인데, 온디바이스라서 따로 채팅 내역을 기억하지 않고 기기 안에서 사용자 요청시에 서버를 통해 답변을 제공한다. 출시가 되어야 어떻게 되는지 알 수 있겠지만, 이제는 휴대폰에도 AI가 온디바이스로 들어간다니 너무 신기했다.
<a href="https://tech.kakao.com/posts/682">Kanana Nano 개발하기 블로그 참고</a></p>
<h2 id="💡-nc-ai---varco-3d">💡 NC AI - Varco 3D</h2>
<p>다음으로 흥미로웠던 기업은 바로 NC에서 개발한 3D생성 모델인 Varco 3D 모델이었다. NC는 원래도 게임 캐릭터들을 개발하는 곳이다보니깐, 모델이 생성하는 캐릭터 퀄리티도 엄청 높았다. 처음에 원하는 모양을 프롬프트를 작성하면, 3가지의 후보 중에 마음에 드는 이미지를 먼저 선택하고, 마음에 들지 않을땐 프롬프트를 재작성할 수도 있다. 그리고 최종적으로 선택하고 나면 생성된 3D 모델을 확인할 수 있는데, 퀄리티가 너무 좋아서 깜짝 놀랐다. 프로젝트를 하면서 3D모델을 몇가지 사용해봤지만, 가장 퀄리티가 높았던 것 같다. 이외에도 캐릭터에 모션들도 추가할 수 있어서 사람의 골격에 따라 다양한 모션을 입힐 수 있었다. 아직은 표정까지는 움직이지 못하지만, 표정도 움직일 수 있으면 더 재밌을 것 같다.
이 링크로 베타버전을 사용해볼 수 있다 ! <a href="https://3d.varco.ai/">Verco 3D Beta버전</a>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/c618f0d8-8506-496f-b2fa-c1087b982e77/image.png" alt=""></p>
<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/7738b2f6-a3ac-463c-9e8d-82ef3e4a971e/image.png" alt="img1"></th>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/520a5bf6-7d9a-4736-90b5-4a8ea37de716/image.png" alt="img2"></th>
</tr>
</thead>
</table>
<h2 id="💡-naver-cloud---팟캐스트-ai">💡 Naver Cloud - 팟캐스트 AI</h2>
<p>  어렸을 때 종종 팟캐스트를 듣고 했었는데, 이번 Naver cloud에서 AI 음성으로 팟캐스트를 만들 수 있다는 것이 아주 흥미로웠다. 실제로 들어봤을 때 진짜 AI로 생각하지 못할 만큼 사람이 방송하는 듯한 목소리였다. 게다가 30분동안 실제 사람의 목소리를 학습시키면 그 목소리를 바탕으로 AI가 팟캐스트를 생성해준다는 점이 가장 좋았다. 이질적인 AI 목소리가 아니라 실제 목소리를 기반으로 하다보니 아주 자연스러웠다. 이제는 대본만 있으면, 실제로 녹음하지 않아도 손쉽게 팟캐스트를 만들 수 있을 것 같다. </p>
<h2 id="💡-samsung-sds---브리티-코파일럿">💡 Samsung SDS - 브리티 코파일럿</h2>
<p>이제는 글로벌 시대이다보니, 해외와도 협업하는 순간들이 정말 많은데 그렇게 하다보면 모든 대화를 이해하기란 쉽지 않다. 브리티 코파일럿이라는 툴을 현장에서 체험해볼 수 있었는데, 어떤 러시아 분이 실제로 회의에서 러시아어로 말씀하시면 실시간 자막과 번역이 되어 다시 음성으로 들려준다. 꽤나 정확학게 번역을 할 뿐만 아니라 STT 자체의 기능도 너무 뛰어나서 단어를 거의 틀리지 않고 텍스트로 생성되는 것을 확인할 수 있었다. 내가 만약 해외기업에서 계속 일해야하는 사람이라면 꽤나 유용한 툴이 되지 않을까 싶다.</p>
<h2 id="💡-eq4all---아바타-수어-번역-플랫폼">💡 EQ4all - 아바타 수어 번역 플랫폼</h2>
<p>우리는 흔히 뉴스를 볼때면, 오른쪽 아래에 수어로 뉴스 내용을 통역해주는 분을 자주 볼 수 있다. 수어는 청각장애를 가지신 분들의 언어로, 뉴스 이외에 더 다양한 콘텐츠에서 지원이 필요하다. 이 기업은 청각 장애인분들이 생활 속에 겪는 여러 불편함들을 해소하기 위해서, 주로는 아바타 수어 번역 기술로 해결해나가고 있는 기업이다. 수많은 다른 기업들은 오로지 기술에 집중하여 트랜드를 따라가기 바빴고, 누구를 위한 서비스보다는 가장 최신의 서비스를 만들어내는 것에 집중하고 있는 듯 했다. 그러나, 이 기업은 하나의 기술이라도 선한 곳에 우선적으로 사용되는 것에 더 집중하고 있었다. 이런 기업들이 더 많이 성장하고, 또 다양한 기술들을 접목해서 세상에 있느 수많은 어두운 곳을 밝혀주는 기업이 되었으면 좋겠다. 무엇보다 설명해주는 직원분께서 열정을 가지고 설명을 해주셔서 마음이 오히려 너무 따뜻해졌다.
<img src = https://velog.velcdn.com/images/jiyun_kang/post/f0f4dc3a-739a-4c01-82f6-003bf630c11d/image.png width=500></p>
<h2 id="마무리-글">마무리 글</h2>
<p>이외에도 정말 많은 회사들이 있었지만, Top 5으로 가장 마음에 드는 기업들을 선정해서 블로그를 작성해보았다. 요즘엔 AI를 사용하지 않는 기업이 있을까 싶을 정도로, 수많은 기업들이 더 성능 좋은 AI 서비스를 만들어내고 있다는 것을 몸소 느낄 수 있는 현장이었다. AI를 통해 얼마나 큰 발전이 이루어질지 기대가 되면서도 그 기술이 올바른 방향을 향해 갈 수 있도록 한명의 사회 구성원으로서 더욱 열정적으로 AI 기술을 공부하고 만들어가는 사람이 되고 싶다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SK네트웍스 Family AI 캠프 마지막 회고 -AI 부트캠프 최종 프로젝트편]]></title>
            <link>https://velog.io/@jiyun_kang/SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI-%EC%BA%A0%ED%94%84-%EB%A7%88%EC%A7%80%EB%A7%89-%ED%9A%8C%EA%B3%A0-%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%8E%B8</link>
            <guid>https://velog.io/@jiyun_kang/SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI-%EC%BA%A0%ED%94%84-%EB%A7%88%EC%A7%80%EB%A7%89-%ED%9A%8C%EA%B3%A0-%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%8E%B8</guid>
            <pubDate>Mon, 22 Sep 2025 08:57:13 GMT</pubDate>
            <description><![CDATA[<p>드디어 최종 프로젝트를 끝으로, 6개월 간의 AI 캠프의 여정이 끝났다. 6개월이라는 길고도 짧은 시간에 정말 많은 부분을 배우고, 개발자로서 한층 성장된 나 자신을 마주할 수 있었다. 이번 마지막 회고는 2개월 간의 최종 프로젝트 기간 동안 기획부터 발표 준비까지 모든 과정을 담아보고자 한다.</p>
<figure style="text-align: center;">
  <img src="https://velog.velcdn.com/images/jiyun_kang/post/db9c849f-72e7-47d1-857d-86fe9ab75bc3/image.png" style="display:block; margin:auto;" width="800"/>
  <figcaption>📅 실제 프로젝트 수행 절차</figcaption>
</figure>

<h3 id="1-주제-선정">1. 주제 선정</h3>
<p><strong>📋 대주제 선정</strong>
최종 프로젝트는 주어진 6개의 프로젝트 주제 중 하나를 선택하고, 선택한 주제를 바탕으로 세부 주제를 정하는 방식이다. 우리팀은 결성이 되자마자, 다같이 모여서 회의를 했다. 우리가 가장 하고 싶었던 주제는 &quot;자체 sLLM 개발을 통한 기업 업무 활용 생성형 AI 플랫폼&quot;이었다. 이 주제는 모든 팀에게 가장 인기가 있다보니, 우리팀이 이 주제를 할 수 있을지 반신반의 했지만, 그래도 가장 빨리 구글폼을 작성하면 1지망이 되지 않을까 전략을 세워보았다. 
그리고 대망의 팀 주제선정 발표날,,,정말 감사하게도 우리가 가장 하고 싶었던 주제인 sLLM 개발로 정해졌다. 우리팀은 스타트부터 정말 운이 좋은 팀이라고 생각했다.</p>
<p><strong>📋 세부주제 선정</strong>
우리팀은 세부 주제를 제대로 정해야 한다는 한마음을 가졌다보니, 주제를 선정하고 서로 설득하는데 정말 많은 시간이 썼다. 처음에 회의를 하고 나면 진이 다 빠질 정도로 서로 열정을 다해서 토론하는 시간을 가졌다. 처음에는 각자 원하는 세부주제들을 들고 와서 왜 하고 싶은지, 어떤 데이터를 사용하고 싶은지 등을 이야기하는 시간을 가졌다.
결론적으로 우리가 주제를 선정하는 기준은 다음과 같았다.</p>
<blockquote>
<p><strong>주제 선정 기준</strong></p>
<ol>
<li>2달이라는 긴 기간동안 &quot;재밌게&quot; 할 수 있는 주제인가!</li>
<li>&quot;다양한 기술&quot;을 활용해볼 수 있는 주제인가!</li>
<li>&quot;sLLM&quot;이어야 하는 이유를 잘 납득할 수 있는 주제인가!</li>
</ol>
</blockquote>
<p>그래서 결론적으로 우리가 정한 주제는 &quot;자동차 디자이너를 위한 프로토타입 이미지 생성 플랫폼&quot;이다. 처음엔 이 주제가 팀원들을 설득하지 못했다. 디자이너들이 제품을 만드는 과정에서 User Experience를 향상시키기 위해서는 VoC(Voice of Customer), 디자인 아이덴티티 등 고려해야할 사항들이 많지만, 이를 다 조사해서 반영하고 또 수정하는 과정은 꽤나 복잡한 노드들을 거쳐야 한다는 생각이 들었다. 이를 AI가 해결할 수 있는 방법이 있지 않을까 하는 생각에 낸 주제였지만, 아무래도 처음엔 잘 그려지지 않아서 어려웠던 것 같다. 그래도 계속해서 주제에 대해 생각해보고 이해하는 한 팀원이 있어서 팀원들도 점차 이 주제로 관심을 가지고 다시 토론해볼 수 있었다. 그래도, 생각했던 주제가 세부 주제로 선정되어서 너무 뿌듯했다. 
(우리팀은 회의를 할 때 화이트보드를 활용해서 토론하는 방식을 선택했다 ! 화이트보드는 사랑...)</p>
<div align="center">

<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/f94df92f-4ba7-4ea5-acad-9003f4f63a6f/image.JPG" width="400"/></th>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/57a88e5a-2d8c-4448-a6e1-c451a0084c6b/image.png" width="400" /></th>
</tr>
</thead>
</table>
</div>


<h3 id="2-기획-및-설계">2. 기획 및 설계</h3>
<p>세부 주제를 정하고 난뒤에 기획서를 작성하고 설계하는 것에도 많은 시간을 투자헀다. 이 프로젝트를 왜 하는것인가, 어떤 문제점을 발견하고, 이 부분을 어떻게 해결할 것인가에 대해 팀원들 간의 “개발 목표”를 공유하는 것은 매우 중요했다. 그래서, 초반에는 이 부분에서 의견을 합하는 과정에서 시간과 노력들을 투자했다. 따라서, 관련 시장 조사도 많이하고, 경쟁사들과의 차별성을 두기 위한 노력들도 기획에 녹여냈다. 그리하여...플랫폼 이름은 바로.. <em>JJACKLETTE !</em> 그리고 개발목표를 한문장으로 정리하면 아래와 같다.</p>
<blockquote>
<p><strong>프로젝트 목표</strong>
본 프로젝트는 자동차 제품 산업 분야에서 기업에 특화된 sLLM을 개발함으로써 사용자 경험 중심 접근을 기반으로, 자동차 디자이너의 업무 프로세스의 효율성과 창의성을 극대화 하는 이미지 프로토타입 플랫폼을 개발하는 것을 목표로 한다.</p>
</blockquote>
<blockquote>
<p>** JJACKLETTE의 Motto (한번 맛깔나게...모토도 정해보아따 ㅎㅎㅎㅎ)
“Automotive design is no longer just about aesthetics — it&#39;s about understanding people, trends, and technology.” **</p>
</blockquote>
<blockquote>
<p><strong>JJACKLETTE의 전반적인 모델링 과정 설계</strong></p>
</blockquote>
<ol>
<li>데이터 수집 및 전처리: 각 모델에 필요한 데이터를 수집하고 학습에 적합한 형태로 가공</li>
<li>모델 파인튜닝: 각 베이스 모델(kanana, InternVL3, FLUX.1, Hunyuan3D-2, LTX-Video)을 수집된 기업 특화 데이터로 파인튜닝</li>
<li>모델 파이프라인 구축: 파인튜닝된 데이터 이외에 추가적으로 RAG 기반의 내부 문서 검색 혹은 모델 활용 경로 설정 </li>
<li>모델 통합 및 배포: 파인튜닝된 모델들을 JJACKLETTE 플랫폼에 통합하고 배포하여 실제 서비스에 활용<br></li>
</ol>
<p><strong>🛠️ 개발 목표</strong></p>
<ul>
<li>현대자동차 디자인 실무에 맞는, 통합형 AI 프로토타입 생성 플랫폼 개발</li>
<li>내부 규정/트렌드/피드백 등 기업 맥락을 반영한 텍스트·이미지 생성</li>
<li>프롬프트만으로 다양한 디자인 시안/시각화/3D·4D 모델 생성 지원</li>
<li>유연한 수정, 아카이빙, 프로젝트 관리 등 협업 효율화 기능 제공</li>
<li>모든 백엔드, 프론트엔드, 데이터베이스, 모델 서버를 Docker 기반 컨테이너로 운영</li>
<li>docker-compose로 전체 서비스 통합 구동 및 재현 가능성 확보</li>
</ul>
<h3 id="3-데이터-수집">3. 데이터 수집</h3>
<p>기획 및 설계를 명확하게 하고 나니, 어떤 데이터를 수집해야 하는지 명확하게 그려졌다. 우리는 크게 RAG시스템과 파인튜닝용 데이터를 따로 구분하여 데이터를 수집하였다. 그리고 이를 제대로 관리하기 위한 스프레드 시트에 한눈에 볼 수 있도록 정리하면서 데이터를 수집했다.</p>
<figure style="text-align: center;">
  <img src="https://velog.velcdn.com/images/jiyun_kang/post/c466efb7-0271-4a01-9879-1eb2e022a71f/image.png" style="display:block; margin:auto;" width="600"/>
  <figcaption>데이터 수집 관리 스프레드 시트 화면</figcaption>
</figure>

<figure style="text-align: center;">
  <img src="https://velog.velcdn.com/images/jiyun_kang/post/5261ffdd-da56-4a8f-be57-fb405a4c09dd/image.png" style="display:block; margin:auto;" width="600"/>
  <figcaption>데이터 ERD</figcaption>
</figure>


<h3 id="4-데이터-전처리">4. 데이터 전처리</h3>
<ul>
<li>파인튜닝을 위한 데이터 전처리
: <strong>Reranker 학습용 컨텍스트 선택 데이터셋</strong>을 생성</li>
</ul>
<blockquote>
<ol>
<li>원문 문서를 <strong>청크 단위로 분할(chunking)</strong></li>
<li>각 청크를 바탕으로 LLM이 <strong>질문–답변(Question–Answer) 3쌍</strong>을 생성</li>
<li>생성된 <code>_chunks_qa.jsonl</code> 입력을 읽어, 각 레코드별로<ul>
<li>해당 레코드의 청크를 <strong>긍정(positive) 컨텍스트</strong>로 설정</li>
<li>다른 청크들에서 <strong>부정(negative) 컨텍스트 2개</strong> 샘플링</li>
<li>positive/negative를 섞어 순서 랜덤화 및 <strong>정답 인덱스(positive_index)</strong> 부여</li>
</ul>
</li>
<li>최종적으로 <strong>Reranker 학습 포맷(JSONL)</strong>으로 저장</li>
</ol>
</blockquote>
<blockquote>
<p>목적: 주어진 질의/문맥에서 올바른 컨텍스트(positive)를 여러 후보(contexts) 중 골라내도록 Reranker를 학습시키는 것.</p>
</blockquote>
<ul>
<li><p><strong>1) 1차 Chunking:</strong>  큰 단위로 분할  (논문의 경우, 소제목별로 / Article 은 기사 내용별로)</p>
<ul>
<li>TXT (Article) - Hyphen 으로 분리<pre><code class="language-jsx">def split_by_hyphen(text, delim=&quot;----------------------------------------&quot;):
  return [c.strip() for c in text.split(delim) if c.strip()]</code></pre>
</li>
<li>PDF (논문) - 소제목으로 분류<pre><code class="language-jsx">def split_by_topic(text):
  splitters = [
      &quot;요 약&quot;,&quot;서 론&quot;,&quot;실험 설정&quot;,&quot;실험 및 결과&quot;,
      &quot;1. 차체 측면 형태에 따른 공력 성능 비교&quot;,
      &quot;2. 차체 측면 유리창 각도에 따른 공력 성능 비교&quot;,
      &quot;3. 엔진 후드의 각도 변화에 따른 공력 성능 비교&quot;,
      &quot;4. 차체의 루프(roof) 각도에 따른 공력 성능 비교&quot;,
      &quot;4. 후방 디퓨저 적용에 따른 공력 성능 변화&quot;,
      &quot;결 론&quot;,
      &quot;2.1 플루이딕 스컬프쳐와 스톰 엣지&quot;, &quot;2.2 센슈어스 스포트니스&quot;,
      &quot;3.1 플루이득 스컬프쳐와 스톰엣지 미의식&quot;, &quot;3.2 센슈어스 스포트니스 미의식&quot;,
      &quot;4.1 인지된 미의식과 인식적 환원의 차이&quot;,
      &quot;4.2 플루이딕 스컬프쳐와 스톰 엣지의 신경학적 해석&quot;,
      &quot;4.3 센슈어스 스포트니스의 신경학적 해석&quot;,
      &quot;4.3.1 파라메트릭 다이나믹스&quot;,&quot;4.3.2 파라메트릭 주얼&quot;,&quot;4.3.3. 히든라이팅&quot;,
      &quot;4.3.4 현대자동차 디자인 철학의 신경학적 해석&quot;,
      &quot;REFLECTIONS IN MOTION&quot;,&quot;HERITAGE SERIES&quot;,&quot;PONY&quot;,
      &quot;COLOR &amp; LIGHT&quot;,&quot;MATERIAL&quot;,&quot;A JOURNEY&quot;
  ]</code></pre>
</li>
</ul>
</li>
<li><p><strong>2) 2차 Chunking</strong> → 문서의 성격에 따라 다르게 분할함 ! (각 문서마다 가진 특성이 다르기 때문에 이에 맞게 상이하게 분할하는 것이 맞다고 판단함)</p>
</li>
</ul>
<blockquote>
<p>🔽 Chunking 종류
    - Paragraph
    - Sentence
    - Q_A쌍
    - length</p>
</blockquote>
<ul>
<li><p>*<em>3) 메타데이터/잡음 정리: *</em><code>clean_metadata(text: str) -&gt; str</code></p>
<ul>
<li>역할: 괄호/대괄호, 특정 저널/도표/구분선 등 <strong>모델 학습에 불필요한 메타 텍스트 제거</strong> 및 공백 정규화</li>
<li>주요 정리 규칙:<ul>
<li><code>(...)</code>, <code>[...]</code> 내부 제거</li>
<li>저널/도표 패턴 제거</li>
<li>구분선(---------) 제거</li>
<li>문두 <code>Q.</code> / <code>A.</code> 제거</li>
<li>다중 공백 → 단일 공백, 앞뒤 공백 제거</li>
</ul>
</li>
<li>결과: 비교/샘플링에 사용할 <strong>정제된 컨텍스트 문자열</strong> 반환</li>
</ul>
</li>
</ul>
<ul>
<li><strong>4) 스키마 정의 (학습에 중요한 필드)</strong></li>
</ul>
<table>
<thead>
<tr>
<th>필드</th>
<th>타입</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>question</code></td>
<td>string</td>
<td>(선행 단계) LLM이 생성한 질문</td>
</tr>
<tr>
<td><code>answer</code></td>
<td>string</td>
<td>(선행 단계) LLM이 생성한 답변</td>
</tr>
<tr>
<td><code>chunk_text</code></td>
<td>string</td>
<td>이 레코드의 원본 청크 텍스트</td>
</tr>
<tr>
<td><code>contexts</code></td>
<td>string[]</td>
<td><code>[1] …</code>, <code>[2] …</code>, <code>[3] …</code> 형식의 후보 컨텍스트 리스트 (positive 1개 + negative 2개)</td>
</tr>
<tr>
<td><code>positive_index</code></td>
<td>int</td>
<td><code>contexts</code> 내 정답(positive) 위치</td>
</tr>
<tr>
<td>그 외</td>
<td>any</td>
<td>원본에서 보존되는 메타데이터(문서 ID, 청크 ID 등)</td>
</tr>
</tbody></table>
<blockquote>
<p>Reranker 입력 시, <code>question</code>(+ <code>answer</code> 옵션)와 <code>contexts</code>를 함께 넣고, 모델로 하여금 <code>positive_index</code>를 맞히도록 학습하는 방식</p>
</blockquote>
<pre><code class="language-jsx">{
  &quot;... 원본 필드 ...&quot;: &quot;...&quot;,
  &quot;contexts&quot;: [
    &quot;[1] 컨텍스트_문자열&quot;,
    &quot;[2] 컨텍스트_문자열&quot;,
    &quot;[3] 컨텍스트_문자열&quot;
  ],
  &quot;positive_index&quot;: [1]  // 1-based index, contexts 중 정답(positive) 위치
}
</code></pre>
<ul>
<li>RAG Vector DB를 위한 Parsing 과정<pre><code class="language-py"># 프롬프트 템플릿
prompt = ChatPromptTemplate.from_template(
  dedent(&quot;&quot;&quot;### Instruction
당신은 텍스트 분석 전문가입니다.
아래 자동차 기사를 &quot;title&quot;, &quot;car_name&quot;, &quot;category&quot;, &quot;tags&quot;, &quot;brand&quot;로 나눠서 json 형태로 만들어주세요.
</code></pre>
</li>
</ul>
<h3 id="주의사항">주의사항</h3>
<ol>
<li>title 은 제목입니다.</li>
<li>car_name 은 본문에서 차의 세부 이름을 추출하여 기입하세요. 단, 추출이 불가능한 경우 빈 문자열로 대체합니다.</li>
<li>category 는 [&quot;review&quot;, &quot;news&quot;, &quot;article&quot;] 중 하나로 판단하여 기입하세요.</li>
<li>&quot;review&quot;는 고객 리뷰, &quot;news&quot; 는 최신 뉴스 및 동향 등, &quot;article&quot; 은 논문 내용입니다.</li>
<li>tags 는 키워드를 리스트 형태로 담습니다. 가능한 많이 추출하세요.</li>
<li>brand 는 &quot;현대&quot;, &quot;기아&quot;, &quot;쌍용&quot; 과 같은 꼴입니다. 단, 추출이 불가능한 경우 빈 문자열로 대체합니다.</li>
<li>page_content 는 본문의 내용을 기입합니다.</li>
<li>json, ``` 등과 같은 불필요한 문자는 절대 입력하지 않습니다.</li>
</ol>
<h3 id="few-shot">Few Shot</h3>
<p>{{
  &quot;metadata&quot;: {{
    &quot;title&quot;: &quot;현대차, 신형 그랜저 공개…럭셔리 대형세단 시장 공략&quot;,
    &quot;car_name&quot;: &quot;그랜저&quot;,
    &quot;category&quot;: &quot;news&quot;,
    &quot;tags&quot;: [&quot;그랜저&quot;, &quot;현대자동차&quot;, &quot;신차발표&quot;],
    &quot;brand&quot;: &quot;현대&quot;
  }},
  &quot;page_content&quot;: &quot;현대차, 신형 그랜저 공개…럭셔리 대형세단 시장 공략\n신형 그랜저가 드디어 공개됐다. 이번 모델은 디자인 혁신과 첨단 기술로 럭셔리 대형세단 시장을 정조준한다.\n태그: 그랜저, 현대자동차, 신차발표&quot;
}},
{{
  &quot;metadata&quot;: {{
    &quot;title&quot;: &quot;기아 EV6 롱텀 시승기 – 전기차의 새로운 기준&quot;,
    &quot;car_name&quot;: &quot;EV6&quot;,
    &quot;category&quot;: &quot;review&quot;,
    &quot;tags&quot;: [&quot;EV6&quot;, &quot;전기차&quot;, &quot;시승기&quot;, &quot;기아&quot;],
    &quot;brand&quot;: &quot;기아&quot;
  }},
  &quot;page_content&quot;: &quot;기아 EV6 롱텀 시승기 – 전기차의 새로운 기준\n지난 2주간 기아 EV6를 직접 타보았다. 주행감과 충전 인프라 모두 만족스럽다.\n태그: EV6, 전기차, 시승기, 기아&quot;
}}</p>
<h3 id="input-data">Input Data</h3>
<p>{article}&quot;&quot;&quot;)
)</p>
<pre><code>
### 5. 화면 설계 (Figma)
다음으로 피그마를 활용해서 우리의 기능이 들어갈 수 있는 웹 화면을 설계하였다. 예전에도 피그마 툴을 많이 써봤다보니, 익숙하게 잘 만들 수 있었다. 캠프에 들어와서, 예전부터 묵혀두었던 디자인 역량을 최대치로 발휘해야 하는 순간이 많아 예전에 학부와 대학원 때 열심히 하길 잘했다는 생각을 했다. 

&lt;div align=&quot;center&quot;&gt;

| &lt;img src=&quot;https://velog.velcdn.com/images/jiyun_kang/post/6abc96fc-6d04-4250-adbe-f781b85b0e0b/image.png&quot; width=&quot;500&quot;/&gt; | &lt;img src=&quot;https://velog.velcdn.com/images/jiyun_kang/post/0871d45d-c163-4386-a3f1-9ee6f3a72baa/image.png&quot; width=&quot;500&quot; /&gt; |
|:---:|:---:|

&lt;/div&gt;

### 6. 모델 선정 (바꾸고 바꾸고…또 바꾸는 과정)

우리는 모델을 정하고 학습시키는 과정을 반복하면서, 성능이 제대로 나올 때까지 고군분투의 과정을 거쳤다. 

**텍스트 모델(LGAI-EXAONE/EXAONE-4.0-1.2B -&gt; kakaocorp/kanana-1.5-8b-instruct-2505)**
텍스트 모델의 경우, 초기에는 Exaone모델(LGAI-EXAONE/EXAONE-4.0-1.2B)을 사용했었는데, 이 모델의 경우는 ondevice를 위한 모델이다보니, 1.2B밖에 되지 않아서 성능을 아무리 높여도 크게 향상되지 않았다. 아니면, 32B모델을 사용해야 하는데, 이건 또 너무 커서 제한된 리소스를 가지고 있는 우리에겐 너무 버거운 모델이었다. 그래서 바꾸게 된 모델이 지금의 Kanana 모델(kakaocorp/kanana-1.5-8b-instruct-2505)인데, 이 모델은 파라미터도 Exaone보다 크고 instruct모델이다 보니, 한국어와 영어를 모두 지원하여 이미 base 모델만으로도 사용자의 지시를 잘 따랐다. 

**2D 모델(Stable Diffusion → FLUX)**
우리 플랫폼에서 가장 중요한 기능을 해야 하는 2D모델은 단순히 정량적으로 이미지가 잘 나온다고 평가할 수 없었다. 얼만큼 기업의 아이덴티티를 잘 반영하고 있는지 혹은 자동차 디자인을 제대로 뽑아내고 있는가 또한 중요하기 때문에, 사실상 정성적인 평가가 훨씬 중요하다. 그래서, 어떤 기준으로 평가를 할 것인지를 프로젝트를 제대로 이해하고 설정하는가는 몹시 중요하다는 생각이 들었다. 초반에 사용했던 Stable Diffusion 말고, FLUX를 선택한 이유는 크게 두가지다. 첫번째, FLUX는 inpaint 기능까지도 지원한다는 점이다. 보통 이미지 생성 모델을 사용해보면 알겠지만, 이미지를 주고 수정을 하려고 해도, 부분 수정을 정확히 하는 모델들은 사실상 굉장히 드물다. 그러다보니 FLUX모델은 부분 수정에 특화되어 있는 모델이라서 이전 이미지 파일을 PIL image 형태로만 주면, 프롬프트에 따라 수정사항을 잘 반영하고 했다. 그리고 두번째로는 자동차 모델을 너무나 자연스럽게 잘 만들어준다는 점이었다. 이전 모델에 비해 월등히 잘 만들고, 이미 어느정도 현대자동차에 대한 이해도 있는 모델이었다. 

**3D 모델(Trellis -&gt; shapE -&gt; Hunyuan3D-2)과 Video 모델(Stable Diffusion video-&gt; LTX video)**
두 모델은 앞서 텍스트와 2D모델에 비해 많은 시간을 투자하지 않았지만 막판에 모델도 꽤나 많이 바꿨다. 나의 담당은 이쪽은 아니였지만, 이 부분을 comfyUI에 올려서 배포하는 과정도 꽤나 복잡하고 어려웠다....고생 많았다 ㅜㅜ

### 7.LLM 파인튜닝

Finetuning을 할 때 어떻게 학습을 시킬지에 대한 방법은 정말 다양하다. 우리가 활용한 방식은`Positive/Negative context 기반의 supervised reranker fine-tuning`이다. 앞서 데이터셋을 구성하는 것에서 볼 수 있듯이 이 방식은 reranker로 질문–문맥 쌍을 깊게 cross-encoding해서, 진짜 정답(positive)을 negative보다 높은 점수로 올려줌으로써 모델이 정확한 문서를 상위에 노출하게 된다. 또한, Ranking Robustness (순위 안정성)을 보장해서 여러 후보 문맥이 유사해도, 학습 과정에서 positive와 negative를 구분하는 기준을 배우게 되고, Noise가 많은 상황에서도 더 안정적인 문맥 선택이 가능하다.

&lt;figure style=&quot;text-align: center;&quot;&gt;
  &lt;img src=&quot;https://velog.velcdn.com/images/jiyun_kang/post/ae88f324-8cfa-488d-bbc9-98566a9ee768/image.png&quot; style=&quot;display:block; margin:auto;&quot; width=&quot;800&quot;/&gt;
  &lt;figcaption&gt;LLM 파인튜닝 결과(BERT score, Cosine Similarity&lt;/figcaption&gt;
&lt;/figure&gt;






### 8. 전체 파이프라인 (사용자 시나리오에 따른 node 짜기)

**1. 초기 의도 분류**

우선 처음 사용자가 챗봇 화면을 켰을 때, 어떤 질문과 기능을 사용할 것인지 첫번째 intent classifier 를 정하는 것부터 시작했다.
사용자 시나리오를 그려봤을때, 크게 이미지 생성 노드와 지식 질문 노드로 나눠볼 수 있었다. 그리고, 이미지 생성 안에서도 체크리스트를 기반으로 생성하는지 혹은 바로 이미지 쿼리를 보내서 생성하는지, 이미지 수정 요청으로 바로 갈것인지 등을 나누었다. 또한, 사용자가 이미지를 생성하지 않고, 단순히 디자인 철학이나 지식 등을 물어볼 수 있다고 생각하면 아래와 같이 시나리오를 4가지로 나눴다.

&gt;**🎯 4가지 사용자 시나리오**&lt;br&gt;
**시나리오 1: 체크리스트 기반 이미지 생성**
1. 페이지 접속 → 자동 환영 메시지
2. 사용자: &quot;이미지 생성&quot;
3. AI: &quot;단계별로 만들어줘&quot; vs &quot;바로 만들어줘&quot; 구분
4. 사용자: &quot;단계별로 만들어줘&quot;
5. AI: 체크리스트 안내 및 단계별 가이드&lt;br&gt;
**시나리오 2: 직접 이미지 생성**
1. 페이지 접속 → 자동 환영 메시지
2. 사용자: &quot;빨간색 SUV 이미지 만들어줘&quot;
3. AI: 바로 이미지 쿼리 생성&lt;br&gt;
**시나리오 3: 이미지 수정**
1. 페이지 접속 → 자동 환영 메시지
2. 사용자: &quot;이미지 수정&quot;
3. AI: 이미지 업로드 및 수정 요청 안내&lt;br&gt;
**시나리오 4: 지식 질문**
1. 페이지 접속 → 자동 환영 메시지
2. 사용자: &quot;현대자동차의 디자인 철학이 뭐야?&quot;
3. AI: RAG 기반 전문 답변

1) 시나리오를 반영하기 위한 Intent classifier AI 프롬프트
```py
    INITIAL_INTENT_CLASSIFICATION_PROMPT = &quot;&quot;&quot;다음 사용자 질문의 의도를 분류해주세요:

사용자 질문: {user_query}

의도 분류 옵션:
1. rag: 현대자동차나 자동차에 대한 구체적인 지식 질문 (기술, 디자인, 철학, 역사 등)
2. image_generation: 새로운 자동차 이미지 생성 요청 (예: &quot;자동차 이미지 만들어줘&quot;, &quot;새로운 디자인 생성해줘&quot;, &quot;이미지 생성&quot;)
3. image_modification: 이미지 수정 요청 (예: &quot;이미지 수정해줘&quot;, &quot;이 차 색깔 바꿔줘&quot;, &quot;이미지 업로드해서 수정&quot;, &quot;이미지 수정&quot;)
4. general_conversation: 일반적인 일상 대화 (인사, 날씨, 개인적인 질문, 자동차와 무관한 일반적인 대화 등)

반드시 다음 중 하나의 키워드만 답변하세요: rag, image_generation, image_modification, general_conversation&quot;&quot;&quot;</code></pre><p>2) 초기 분류를 위한 노드 함수 정의</p>
<pre><code class="language-py">def classify_and_apply_intent(state: PipelineState) -&gt; Dict[str, Any]:
    print(f&quot;[분기] classify_and_apply_intent 노드 접근&quot;)
    user_query = state.get(&quot;user_query&quot;, &quot;&quot;)
    final_intent = _classifier.classify_initial_intent(user_query)
    print(f&quot;[처리] 의도 분류 완료: &#39;{user_query[:30]}...&#39; -&gt; &#39;{final_intent}&#39;&quot;)

    # image_generation 경로로 갈 때 image_mode에서 interrupt 발생
    if final_intent == &quot;image_generation&quot;:
        state[&quot;waiting_node&quot;] = &quot;image_mode&quot;

    return {&quot;initial_intent&quot;: final_intent}


def route_top(state: PipelineState) -&gt; str:
    it = _norm(state.get(&quot;initial_intent&quot;))
    print(f&quot;[라우팅] route_top - 의도: &#39;{it}&#39;&quot;)
    if it == &quot;image_generation&quot;:
        print(f&quot;[라우팅] 이미지 생성 경로로 라우팅&quot;)
        return &quot;image_generation&quot;
    elif it == &quot;image_modification&quot;:
        print(f&quot;[라우팅] 이미지 수정 경로로 라우팅&quot;)
        return &quot;image_modification&quot;
    elif it == &quot;rag&quot;:
        print(f&quot;[라우팅] RAG 질문 경로로 라우팅&quot;)
        return &quot;rag&quot;
    else:
        # Interrupt 걸리기 전, 첫 방문 시
        if state[&quot;waiting_node&quot;] != &quot;route_top&quot;:
            interrupt({&quot;is_loading&quot;: True, &quot;generation_type&quot;: &quot;text&quot;})
        else:
            print(f&quot;[라우팅] 일반 대화 경로로 라우팅&quot;)
            return &quot;general_conversation&quot;</code></pre>
<p>3) Langgraph conditional edge 정의</p>
<pre><code class="language-py"> g.add_edge(&quot;process_welcome_input&quot;, &quot;classify_and_apply_intent&quot;)

    g.add_conditional_edges(
        &quot;classify_and_apply_intent&quot;,
        route_top,
        {
            &quot;image_generation&quot;: &quot;image_mode&quot;,
            &quot;image_modification&quot;: &quot;mod_intro&quot;,
            &quot;rag&quot;: &quot;rewrite_query&quot;,  # RAG는 rewrite_query부터 시작
            &quot;general_conversation&quot;: &quot;handle_general_conversation&quot;,
        },
    )</code></pre>
<p><strong>2. RAG 시스템</strong></p>
<img src = https://velog.velcdn.com/images/jiyun_kang/post/fe26d594-a4be-4ebe-8056-aad955303895/image.png style="display:block; margin:auto;" width=800/>

<p>위 의도분류에서 자동차나 디자인 관련 지식을 물어봤을 때는 RAG시스템으로 가도록 설계되어 있는데, RAG 안에서도 사용자 쿼리를 기반으로 정확도를 높이기 위해 &quot;쿼리 재작성&quot;, &quot;품질 라우팅&quot;, &quot;LLM Fallback&quot; 처리를 해두었다.
쿼리 재작성은 Hyde방식을 통해, 검색 쿼리르 재구성하여 보다 명확한 답변을 찾아올 수 있도록 했다. 또한, 품질 라우팅을 통해 관련성과 충분성이 적절하게 있는지 스스로 판단할 수 있는 Agent RAG를 활용하였다.</p>
<p>1) Rewrite the Query</p>
<ol>
<li>기능: RAG가 사용자의 쿼리 의도를 정확하게 파악 (보통은 친절하게 쿼리를 안쓰기 때문에)</li>
<li>방식: HyDE + 키워드 압축 + 검색 쿼리 생성</li>
</ol>
<ul>
<li><strong>pseudo</strong> = LLM이 만들어낸 <strong>가상의 답변 (HyDE용)</strong></li>
<li><strong>keys</strong> = 그 답변에서 추출된 <strong>핵심 키워드 목록</strong></li>
<li><strong>q</strong> = 최종적으로 벡터 검색에 던질 <strong>검색 쿼리</strong></li>
</ul>
<pre><code class="language-py">out = kanana_llm_model.generate_response(prompt, max_length=360).strip()
pseudo = _pick(out, r&quot;가상답변\s*:\s*(.+)&quot;)
keys   = _pick(out, r&quot;키워드\s*:\s*(.+)&quot;)
q      = _pick(out, r&quot;검색쿼리\s*:\s*(.+)&quot;) or re.sub(r&quot;\s+&quot;, &quot; &quot;, keys.replace(&quot;,&quot;, &quot; &quot;))
return q[:512], pseudo</code></pre>
<ul>
<li><code>pseudo</code> → 모델 prompt에 context로 넣거나 log로 기록.</li>
</ul>
<p>2) Qdrant vector DB 검색 입력(embedding 모델은 vectorDB에 넣을 때 사용했던 동일한 모델인 BAAI/bge-m3 사용).</p>
<pre><code class="language-py">lass BabsimRAGAdapter:
    &quot;&quot;&quot;Babsim Vector DB를 현재 파이프라인 RAG와 연결하는 어댑터&quot;&quot;&quot;

    def __init__(self):
        self.qdrant_client = QdrantClient(host=&quot;localhost&quot;, port=6333)
        self.collection_name = &quot;babsim_rag_db&quot;
        self.embedding_model = None  # 지연 로딩

    def search_relevant_documents(self, query: str, k: int = 5) -&gt; List[Dict[str, Any]]:
        &quot;&quot;&quot;babsim Vector DB에서 관련 문서 검색&quot;&quot;&quot;
        try:
            # 지연 로딩
            if self.embedding_model is None:
                self.embedding_model = SentenceTransformer(&#39;BAAI/bge-m3&#39;)
            # 쿼리 임베딩 생성
            query_vector = self.embedding_model.encode(query).tolist()

            # 검색 실행
            search_result = self.qdrant_client.search(
                collection_name=self.collection_name,
                query_vector=query_vector,
                limit=k,
                with_payload=True
            )

            # 결과 변환
            results = []
            for result in search_result:
                results.append({
                    &#39;content&#39;: result.payload.get(&#39;page_content&#39;, &#39;&#39;),
                    &#39;metadata&#39;: {k: v for k, v in result.payload.items() if k != &#39;page_content&#39;},
                    &#39;score&#39;: result.score
                })

            logger.info(f&quot;babsim Vector DB에서 {len(results)}개 문서 검색 완료&quot;)
            return results</code></pre>
<p>3) Langgraph - RAG 처리 플로우 코드</p>
<pre><code class="language-py">    g.add_edge(&quot;rewrite_query&quot;, &quot;generate_rag_response&quot;)
    g.add_edge(&quot;generate_rag_response&quot;, &quot;evaluate_answer&quot;)
    g.add_edge(&quot;evaluate_answer&quot;, &quot;retry_or_accept&quot;)
    g.add_conditional_edges(
        &quot;retry_or_accept&quot;,
        route_retry_ok,
        {
            &quot;handle_llm_fallback&quot;: &quot;handle_llm_fallback&quot;,
            &quot;finalize&quot;: &quot;finalize&quot;,
        },
    )
    g.add_edge(&quot;handle_llm_fallback&quot;, &quot;finalize&quot;)</code></pre>
<p><strong>3. 텍스트 파이프라인 for 이미지 쿼리 생성 (여기서부터 꽤나 복잡한...파이프라인)</strong></p>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/ea797617-b4e2-40b4-b802-10be79e322c5/image.png" style="display:block; margin:auto;" width="800"/>

<p>이미지를 생성하기 위해서는 어떻게 이미지를 생성할 것인지 쿼리를 보내야 한다. 사용자가 직접 바로 생성해서 쿼리를 보낼 수 있지만, 자동차를 생성하는데는 고려해야 할 요소들이 매우 많기 때문에 이를 한번에 쿼리로 만들어서 보내는 것은 쉽지 않다. 그래서,아래와 같이 디자인 체크리스트를 받아서 체크리스트 답변들을 바탕으로 이미지 쿼리를 생성해서 이미지 모델로 보내는 방식으로 파이프라인을 구성하였다. 그리고 체크리스트를 채우지 않고 direct로 보낼 수 있는 노드도 있어서 바로 이미지 생성으로 진행될 수 있게끔 만들었다.</p>
<figure style="text-align: center;">
  <img src="https://velog.velcdn.com/images/jiyun_kang/post/c8c6f24f-493b-4252-85ba-f8b41eb2ac78/image.png" style="display:block; margin:auto;" width="800"/>
  <figcaption>이미지 쿼리 생성을 위한 체크리스트 화면</figcaption>
</figure>

<p>체크리스트를 받는 과정에서 Langgraph의 주요한 기능인 Human in the Loop 방식을 사용했는데,</p>
<blockquote>
<p><strong>Human in the Loop란?</strong>
AI가 모든 과정을 자동으로 처리하는 것이 아니라, 중요한 의사결정 지점에서 사용자가 개입할 수 있도록 설계된 구조를 의미한다. 사람의 정보 입력과 전문 지식을 머신러닝(ML) 및 인공지능 시스템의 수명 주기에 통합하는 공동작업 접근 방식이다. 사람은 ML 모델의 학습, 평가, 운영에 적극적으로 참여하여 귀중한 가이드, 피드백, 주석을 제공합니다. HITL은 이러한 협업을 통해 사람과 머신의 고유한 기능을 활용하여 ML 시스템의 정확성, 신뢰성, 적응성을 향상하는 것을 목표로 한다.<br>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/05e7e729-7210-4e9a-857e-91d7fe64a572/image.png" style="display:block; margin:auto;" width="400"/></p>
</blockquote>
<p>그래서 처음 invoke를 하고 난 뒤에, interrupt를 걸면 그때 사용자로부터 받은 답변을 그 다음 노드로 넘겨주는 방식이다. 우리 파이프라인 코드들을 보면 다음과 같다. 아래 코드는 이미지 생성을 하고자 할때, 체크리스트를 받아서 할 것인지 혹은 직접 프롬프트를 작성해서 자유형식으로 할 것인지 선택할 수 있도록 사용자의 개입이 이루어진다.</p>
<pre><code class="language-py">def image_mode(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;이미지 생성 방식을 선택하도록 요청&quot;&quot;&quot;
    print(f&quot;[분기] image_mode 노드 접근&quot;)
    msg = (
        &quot;&quot;&quot;이미지 생성을 시작하겠습니다! 🎨

어떤 방식으로 진행하시겠습니까?

1️⃣ **체크리스트 기반 단계별 가이드**
   - 11가지 카테고리를 차근차근 채워가며 상세한 디자인 생성

2️⃣ **직접 이미지 생성**
   - 원하는 디자인을 자유롭게 설명하면 바로 이미지 생성

- 체크리스트 기반: &quot;체크리스트&quot; 또는 &quot;단계별&quot;
- 바로 생성: &quot;바로&quot; 또는 &quot;직접&quot;
- 건너뛰기: &quot;건너뛰기&quot;, &quot;skip&quot;, &quot;다음&quot;, &quot;next&quot; 를 통해 바로 이미지 생성&quot;&quot;&quot;
    )
    user_query = state.get(&quot;user_query&quot;, &quot;&quot;)
    # image_mode 노드 첫 방문 시 
    if state.get(&quot;waiting_node&quot;, &quot;&quot;) != &quot;image_mode&quot;:
        interrupt({&quot;query&quot;: msg})
    else:
        print(f&quot;[처리] 이미지 생성 방식 선택 요청 메시지 전송&quot;)
        return {&quot;user_query&quot;: user_query, &quot;response&quot;: msg}
</code></pre>
<p>그리고 interrupt를 통해 받은 사용자 답변은 checkpointer 를 통해 상태를 저장하고 복원하는 과정을 반복한다.
여기서 checkpoint는 다음과 같은 역할을 한다.</p>
<blockquote>
<p><strong>checkpointer의 핵심 역할</strong></p>
</blockquote>
<p>1) 상태 저장 (State Persistence)
사용자가 특정 지점에서 개입(HITL)을 하면, 그 시점의 대화 상태(state)를 checkpointer에 기록
예: 사용자가 &quot;체크리스트&quot; 라고 답변하고 멈춘 지점 → 그 시점의 query, context, intermediate outputs 등을 저장.<br>
2) 상태 복원 (State Resume)
사용자가 입력을 마치고 다시 실행하면, checkpointer가 저장된 상태를 불러와서 중단된 시점 이후부터 파이프라인을 이어서 실행 (덕분에 처음부터 다시 돌릴 필요 없이, 멈췄던 부분부터 재개가 가능)<br>
3) 세션 관리 (Thread / Session Continuity)
여러 명이 동시에 쓰거나, 한 사용자가 여러 세션을 진행할 때도 checkpointer가 각각의 상태를 기억
예: MemorySaver(thread_id=thread_id)처럼 설정하면 세션별로 상태가 분리 관리됨.<br>
4) HITL 루프 제어 (Human Feedback Loop)
사람이 개입하는 노드(예: ask_modify, guided_llm_chat)에서 입력을 기다릴 때, 그 지점을 정확히 기록.
이후 사람이 응답을 주면, 기록된 지점에서 Command(resume=state) 형태로 재실행이 가능</p>
<p>그래서 간단하게 실제 동작 흐름은 다음과 같다. 첫 실행 때 checkpointer가 상태를 저장하고, Human input 후 Command(resume=…)로 다시 불러오면서 이어가는 형태다.</p>
<pre><code class="language-py">checkpointer = MemorySaver()

# 최초 실행 → HITL 지점 도달 → 멈춤
result = pipeline.invoke(initial_state, config=config, checkpointer=checkpointer)

# 사용자가 답변을 준 후 → 저장된 상태 불러와서 재개
pipeline_result = pipeline.invoke(
    Command(resume=initial_state),
    config=config,
    checkpointer=checkpointer
)</code></pre>
<p><strong>4. 이미지 파이프라인</strong></p>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/8f48652f-d095-437a-9b6d-b12197c09058/image.png" style="display:block; margin:auto;" width="800"/>
이미지 파이프라인의 경우 수정여부나 어떤 2D, 3D 혹은 video인지에 따라 HITL 방식을 똑같이 적용해서 사용자가 원하는 방식대로 할 수 있게끔 노드를 짰다. 그리고 중요한 것은 모든 과정에서 수정이 필요할 때 마다 수정을 할 수 있게끔 노드를 만들었다는 점이다.

<p>2) 3D, 4D 파이프라인</p>
<pre><code class="language-py">def run_3d_generation(state: PipelineState) -&gt; Dict[str, Any]:
    &quot;&quot;&quot;3D 생성 노드&quot;&quot;&quot;
    print(f&quot;[분기] run_3d_generation 노드 접근&quot;)

    s3_url = state.get(&quot;s3_url&quot;, &quot;&quot;)
    if not s3_url:
        return {&quot;error&quot;: &quot;이미지 URL이 없습니다.&quot;}

    # 3D 생성기 사용
    result = generator_3d.generate_3d_model(s3_url)

    if &quot;error&quot; in result:
        return {&quot;response&quot;: f&quot;3D 생성 실패: {result[&#39;error&#39;]}&quot;}

    # 결과를 상태에 저장
    state[&quot;s3_url_3d&quot;] = result[&quot;s3_url_3d&quot;]
    state[&quot;generation_type&quot;] = result[&quot;generation_type&quot;]

    return {
        &quot;s3_url_3d&quot;: result[&quot;s3_url_3d&quot;],
        &quot;generation_type&quot;: result[&quot;generation_type&quot;],
        &quot;response&quot;: f&quot;3D 모델이 생성되었습니다! 🎉\n\n3D 모델: {result[&#39;s3_url_3d&#39;]}&quot;,
        &quot;waiting_node&quot;: &quot;show_3d_4d_result&quot;
    }

def run_4d_generation(state: PipelineState) -&gt; Dict[str, Any]:
    &quot;&quot;&quot;4D 생성 노드&quot;&quot;&quot;
    print(f&quot;[분기] run_4d_generation 노드 접근&quot;)

    s3_url = state.get(&quot;s3_url&quot;, &quot;&quot;)
    if not s3_url:
        return {&quot;error&quot;: &quot;이미지 URL이 없습니다.&quot;}

    # 4D 생성기 사용
    result = generator_4d.generate_4d_model(s3_url)

    if &quot;error&quot; in result:
        return {&quot;response&quot;: f&quot;4D 생성 실패: {result[&#39;error&#39;]}&quot;}

    # 결과를 상태에 저장
    state[&quot;s3_url_4d&quot;] = result[&quot;s3_url_4d&quot;]
    state[&quot;generation_type&quot;] = result[&quot;generation_type&quot;]

    return {
        &quot;s3_url_4d&quot;: result[&quot;s3_url_4d&quot;],
        &quot;generation_type&quot;: result[&quot;generation_type&quot;],
        &quot;response&quot;: f&quot;4D 모델이 생성되었습니다! 🎉\n\n4D 모델: {result[&#39;s3_url_4d&#39;]}&quot;,
        &quot;waiting_node&quot;: &quot;show_3d_4d_result&quot;
    }

def show_3d_4d_result(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;3D/4D 결과 표시 및 후속 액션 노드&quot;&quot;&quot;
    generation_type = state.get(&quot;generation_type&quot;, &quot;&quot;)
    s3_url_3d = state.get(&quot;s3_url_3d&quot;, &quot;&quot;)
    s3_url_4d = state.get(&quot;s3_url_4d&quot;, &quot;&quot;)

    if generation_type == &quot;3d&quot; and s3_url_3d:
        model_url = s3_url_3d
        model_type = &quot;3D&quot;
    elif generation_type == &quot;4d&quot; and s3_url_4d:
        model_url = s3_url_4d
        model_type = &quot;4D&quot;
    else:
        state[&quot;response&quot;] = &quot;생성된 모델이 없습니다.&quot;
        return state

    message = (
        f&quot;🎉 {model_type} 모델 생성 완료!\n\n&quot;
        f&quot;![{model_type} 모델]({model_url})\n\n&quot;
        f&quot;다음 중 하나를 선택해주세요:\n\n&quot;
        f&quot;1️⃣ **{model_type} 재생성** - 다른 설정으로 다시 생성\n&quot;
        f&quot;2️⃣ **4D 생성** - 애니메이션 모델로 변환 (3D인 경우)\n&quot;
        f&quot;3️⃣ **다른 작업** - 이미지 수정이나 새로운 이미지 생성\n&quot;
        f&quot;4️⃣ **완료** - 작업 종료\n\n&quot;
        f&quot;원하는 옵션을 선택해주세요.&quot;
    )

    user_query = interrupt({&quot;query&quot;: message})
    return safe_merge_user_query(user_query, response=message)</code></pre>
<pre><code class="language-py"> # 3D/4D 생성 후 결과 표시
    g.add_edge(&quot;run_3d_generation&quot;, &quot;show_3d_4d_result&quot;)
    g.add_edge(&quot;run_4d_generation&quot;, &quot;show_3d_4d_result&quot;)

    g.add_conditional_edges(
        &quot;show_3d_4d_result&quot;,
        route_3d_4d_result_choice,
        {
            &quot;run_3d_generation&quot;: &quot;run_3d_generation&quot;,
            &quot;run_4d_generation&quot;: &quot;run_4d_generation&quot;,
            &quot;modify_image&quot;: &quot;mod_intro&quot;,
            &quot;image_generation&quot;: &quot;image_mode&quot;,
            &quot;finish&quot;: &quot;finalize&quot;
        },
    )</code></pre>
<p>파이프라인 전체를 다 설명하고 싶지만 주요 파이프라인 코드만 1487줄에다가.. 이외에 components로 만들어둔 파일만 14개 정도 되다보니 다시 봐도 너무 복잡하다.. chatGPT같은 거대한 챗봇 플랫폼들은 이 모든 노드들을 어떻게 처리하는걸까??... 갑자기 너무 대단하게 느껴진다.</p>
<figure style="text-align: center;">
  <img src="https://velog.velcdn.com/images/jiyun_kang/post/85c24081-fbb8-4af9-98aa-3cb0d3e4f8e9/image.png" style="display:block; margin:auto;" width="800"/>
  <figcaption>전체 파이프라인 1487줄..</figcaption>
</figure>

<h3 id="9-시스템-아키텍처">9. 시스템 아키텍처</h3>
<p>우리의 전체 시스템 아키텍처는 아래와 같다. EC2 서버에 올려서 서비스를 배포하게 되는데, 특히 파인튜닝된 모델이나 이외에 다른 모델들을 활용할때는 vLLM endpoint를 활용해서 Runpod에서 모델을 서빙하면 그 모델들을 url 형태로 가져와서 사용할 수 있다.파인튜닝된 Kananaa모델과 Flux 모델은 vLLM 엔드포인트로 연결하고 나머지 3D와 비디오 모델은 백엔드가 ComfyUI (Runpod Serverless Endpoint)를 호출하도록 설계했다. 그리고, 작업 안료 후 산출물(mp4/glb/이미지)을 S3로 업로드하여 저장하도록 하였다.</p>
<figure style="text-align: center;">
  <img src="https://velog.velcdn.com/images/jiyun_kang/post/45476d91-433b-4a5c-b702-f815095579d1/image.png" style="display:block; margin:auto;" width="900"/>
  <figcaption>시스템 아키텍처(React UI → EC2/Backend → 데이터 계층 → 모델 엔드포인트 → Runpod GPU → S3 아웃풋)</figcaption>
</figure>

<h3 id="10-아쉬운-점">10. 아쉬운 점</h3>
<p><strong>1) Human-in-the-Loop 평가 방식의 한계</strong></p>
<p>이번 프로젝트를 돌아보면서 가장 먼저 아쉬움이 남는 부분은 평가 방식이었다. 우리는 Human in the Loop가 잘 적용되고 있는지를 사용자 시나리오를 기반으로 검증했지만, 심사 과정에서 “다른 평가 방식도 가능하지 않았는가?”라는 질문을 받았을 때 충분히 답변하지 못했다. 사용자 경험에 집중한 평가는 의미 있었지만, 정량적인 지표나 비교 실험과 같은 다른 방법을 병행하지 못한 것이 아쉬움으로 남았다. 향후에는 사용자 시나리오뿐만 아니라 nDCG, Top-K accuracy 같은 지표를 함께 적용하고, Human in the Loop 적용 여부에 따라 성능을 비교하는 A/B 테스트도 병행해야겠다고 느꼈다.</p>
<p><strong>2) 창의적 자동차 디자인 생성의 한계</strong></p>
<p>이미지 쿼리를 만드는 과정에서 주로 현대자동차의 기존 모델을 반영하다 보니, 전혀 새로운 디자인을 만들어내기에는 한계가 분명했다. 심사위원분께서 말씀하신 것처럼, “창의적이고 새로운 디자인을 어떻게 이끌어낼 수 있을까?”라는 질문에 대해 더 깊이 고민하지 못한 것이 아쉬웠다. 단순히 기존 모델을 재현하는 데 그치지 않고, Multi-Concept Prompting을 활용해 다른 산업 디자인과 결합하는 방식, 혹은 LoRA와 Prompt Mixing을 통해 참신한 결과물을 만들어낼 수 있었을 것이다. 이 부분은 앞으로 반드시 보완하고 싶은 과제다.</p>
<p><strong>3) 파이프라인 관리·운영의 어려움</strong></p>
<p>마지막으로, 파이프라인 관리 방식에서도 한계를 느꼈다. 현재는 코드를 일렬로 작성해둔 상태라 유지보수와 확장이 쉽지 않았다. 파이프라인을 독립적인 모듈처럼 구성했더라면, 훨씬 더 효율적이고 안정적인 운영이 가능했을 것이다. 예를 들어 vLLM을 엔드포인트로 띄워놓고 API 호출 방식으로 사용하거나, LangGraph나 Docker를 통해 노드 단위로 관리했더라면 지금보다 훨씬 깔끔하게 파이프라인을 운영할 수 있었을 것이다. 결국 이 경험은 앞으로 시스템을 설계할 때는 코드 그 자체보다는 재사용성과 확장성을 고려해 툴처럼 사용할 수 있도록 설계하는 것이 중요하다는 점을 일깨워주었다.</p>
<h3 id="11-내가-느낀-경험들">11. 내가 느낀 경험들</h3>
<p><strong>1) 팀워크</strong></p>
<p>우리팀은 그 어떤 누구도 대충하는 사람이 없었다. 다들 욕심도 있고, 그리고 기준도 높다보니깐 어떻게 해서든 계획한바를 제대로 그리고 잘하고 싶어하는 사람들이 모였다. 심지어 나이도 다 비슷하다보니, 편안한 분위기에서 토론도 하고 서로 격려도 해주는 분위기였다. 특히, 우리 PM님은 묵묵히 본인의 일을 해내고 도움이 필요하면 붙어서 바로 도와주곤 했다. 그리고 각자 전담해서 하는 역할들이 명확했던 우리 팀원들 ...! 이미지모델, 3D 모델, Video 모델, 그리고 전체 파이프라인까지 정말 복잡하고도 어려운 과정이었지만 끝까지 놓지 않고 해낸 팀원들이 모두 대단하다. 그래서 우수상이라는 좋은 결과도 있었던 것 같다..!! 밥심 취뽀하자~!</p>
<div align="center">

<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/6eb69e82-d92d-48b3-a8e8-6e6cad28b68c/image.png" width="400"/></th>
<th align="center"><img src="https://velog.velcdn.com/images/jiyun_kang/post/60df2f44-acbd-41be-a612-3b9bc524b33d/image.png" width="300" style="transform: rotate(90deg)"/></th>
</tr>
</thead>
</table>
</div>


<p><strong>2) 배우고자 하는 자세!</strong></p>
<p>프로젝트를 하면서 수업 시간에 배웠던 내용들을 최대치로 활용하려고 노력했다. Family AI 캠프 커리큘럼에서 프로젝트가 무려 5번이나 있는건, 배운 내용을 제대로 적용해볼 수 있는 경험의 장을 만들어주신거라고 생각했다. 그래서, 수업을 들으면서 정리해놨던 내용들을 다시 꺼내보고, 코드를 짜기를 반복했던 것 같다. 만약 수업 때 배웠던 것 이외에 문제에 직면하면 많은 부분을 강사님께 여쭤보았다. 우리 강사님께서 정말 너무 친절하시고,,, 심지어 이해하기 쉽게 알려주셔서 프로젝트 내내 너무 많은 도움을 받았다. .드를 짜는 과정에서 어려움도 많고 오류를 해결하기에 시간이 부족할 때마다 강사님께서는 해답에 가깝게 갈 수 있게끔 퍼실리테이터로서의 역할을 끊임없이 해주셨다. (우리 기수는 강사님 운이 정말 좋은것 같다 !) 
결론적으로, 이번 프로젝트를 통해, 개발하는 과정에서 배우고자 하는 자세와 끈기만 있다면 주변 자원들을 모두 활용해서 어려운 문제들도 무조건 해결할 수 있다는 믿음이 생겼다. 오류가 해결되지 않는다면.. 너무 답답하겠지만 언젠간 해결이 될거라는 믿음을 가져보자!</p>
<p>*<em>3) 바이브코딩 시대! 우리가 갖추어야할 역량 *</em></p>
<p>이번 프로젝트를 통해 깨달은 또 하나의 중요한 점은, 우리가 무엇을 개발하는지 명확히 이해하고 그에 맞는 기술을 선택할 수 있는 판단력이 필요하다는 것이었다. 새로운 기술을 빠르게 적용하는 것도 중요하지만, 그것이 실제로 우리의 목표와 맞아떨어지는지, 문제 해결에 기여하는지 끊임없이 되묻는 태도가 더 중요하다는 사실을 경험을 통해 배웠다.</p>
<p>특히 요즘처럼 AI가 코드 작성과 구현을 빠르게 대신해주는 바이브 코딩 시대에는 단순히 코드를 작성하는 능력보다, 전체 그림을 짜는 능력이 더욱 큰 가치를 지닌다. 어떤 데이터를 활용하고, 어떤 모델을 적용하며, 어떻게 아키텍처를 구성할 것인지 큰 틀을 설계하는 일은 여전히 인간의 손길이 필요한 부분이다. AI가 제안한 코드를 그대로 쓰는 것이 아니라, 그 코드가 전체 시스템 속에서 어떤 의미를 갖는지 파악하고 맥락에 맞게 조정하는 능력이 필요하다. </p>
<p>또한 모델이 낸 결과가 정말 유의미한지, 사람에게 어떤 가치를 주는지 판단하는 것은 결국 사람이 맡아야 하는 역할이었다. 정량적인 지표와 정성적인 사용자 평가를 함께 바라보며, 더 나은 개선 방향을 찾아내는 능력이 중요하다는 것을 이번 경험으로 확인할 수 있었다.</p>
<p>결국 바이브 코딩 시대에 우리가 키워야 할 역량은 단순한 코딩 기술이 아니라, 무엇을 만들 것인지 정의하고, 적절한 기술을 선택하며, 전체 그림을 조율하고, 납득할 수 있는 유의미한 결과를 내는 능력이다. 이러한 역할은 앞으로도 인간이 반드시 맡아야 할 부분이며, 이번 프로젝트는 그 점을 더욱 분명하게 일깨워주는 계기가 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CV] CNN(Convolutional Neural Network) ]]></title>
            <link>https://velog.io/@jiyun_kang/CV-CNNConvolutional-Neural-Network</link>
            <guid>https://velog.io/@jiyun_kang/CV-CNNConvolutional-Neural-Network</guid>
            <pubDate>Thu, 03 Jul 2025 10:35:53 GMT</pubDate>
            <description><![CDATA[<h2 id="introduction">Introduction</h2>
<p>저번주에 CNN에 대해 처음 접하게 되었다. 그 전까지는 NLP(자연어 처리)모델들을 배우다보니 이번 이론들은 꽤나 새롭고 흥미로웠다. 더 깊게 들어가면 분명 어려운 부분도 있지만, AI가 이미지를 파악하는 것이 얼마나 어려운지를 알게 되었다. 생각해보면, 사람의 직관과 감각을 기계가 따라잡기 위해서 흉내낼 수는 있겠지만, 이를 완벽히 구현해내는 것은 꽤나 오랜 기간이 걸릴 수 있을 것 같다. </p>
<h3 id="cnn의-기본-구조feature-extractorcovolutions--subsampling---fully-connection">CNN의 기본 구조(feature extractor[covolutions- subsampling] - fully connection)</h3>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/a64dc432-a533-4fc2-a45c-0510beccb1de/image.png" alt="">
CNN의 feature extractor : Fully connected가 아닌 convolution- subsampling 구조
왜 fully connected layer를 사용하지 않을까? 
fully connected layer는 이미지의 공간적(spatial)구조를 학습하는 것이 어려움.
그니깐, 만약에 강아지 이미지가 있다고 해보자. 강아지가 왼쪽에서 달려오는 것과 오른쪽에서  달려오는 것이 이미지에서 위치만 다를 뿐이지 우리 눈엔 같은 강아지지만, 기계는 그걸 아예 다른 물체로 인식한다는 의미임.</p>
<p>그래서, 이미지의 특징을 추출하기 위해서 Convolution(합성곱) 연산을 사용!
그게 몬데!!! </p>
<p>합성곱 연산은 input data와 weight 간의 가중합을 구할 떄 한번에 구하지 않고, 옆으로 이동하져면서 작은 크기의 filter를 만들어서 가중합을 구하는 방식임. 이때 필터에 표현된 값과 이미지의 합성곱 결과가 값이 나오면, 그 부분은 이미지에 필터가 표현하는 이미지 특성이 존재한다는 뜻임.</p>
<p><strong>Deep Learning(CNN)에서의 Filter:</strong> 
Filter를 구성하는 원소(element) = parameter(weight)
convolution layer도 여러 층으로 쌓임. (저수준 &gt; 고수준의 특성을 찾음)</p>
<p>1) 저수준- 일반화하기 쉬운 기초적인 특성
2) 고수준- 각 사물만이 가지는 특성</p>
<h3 id="convolutional-layer-작동-방식">Convolutional Layer 작동 방식</h3>
<h4 id="torchnnconv2d">torch.nn.Conv2d</h4>
<p>입력 변수: N,Channel, Height, Width shape의 tensor를 받음</p>
<ul>
<li><strong>in_channels</strong> : 입력 데이터의 channel size</li>
<li><strong>out_channels</strong> : 출력 데이터의 channel size(kernal의 개수: Feature map의 depth, channel의 크기)</li>
<li><strong>kernel_size</strong>: Filter의 크기(height, width)
보통 홀수 크기로 잡음(3 * 3, 5 * 5) </li>
<li><strong>padding=0</strong>: input tensor의 추가할 여백의 크기(default는 0으로 padding을 추가함)-&gt; 문자열로 줄 경우 1) &quot;same&quot;: input의 height와 width와 동일한 output이 나오도록 padding을 추가함 2) &quot;valid&quot;: padding을 사용하지 않음</li>
<li><strong>stride=1</strong>: 연산시 Filter의 이동 size</li>
</ul>
<pre><code class="language-py"># Conv2d 생성
layer = nn.Conv2d(
    in_channels=3,  # 입력 데이터의 channel 개수. 입력 tensor의 shape: (batch_size, channel, height, width) 
    out_channels=5, # 필터의 개수 (output feature map의 개수)
    kernel_size=3,  # 필터의 크기 (3, 3)
    stride=1,       # 계산을 위하 이동 크기. 좌-&gt;우: 1칸씩, 상-&gt;하: 1칸 (default: 1)
    padding=1,      # 패팅 크기 (정수: 상하/좌우 동일할 패팅크기를 명시 - 0(default): 패딩추가 안함.)
                    # &quot;same&quot;: 입력 size와 동일한 size의 출력이 나오도록 알아서 패딩을 추가.
)</code></pre>
<blockquote>
<p>Zeropadding을 붙이는 이유
가장 큰 이유는 자리를 옮기면서 feature를 추출하면, 가장 자리에 있는 값들은 한번씩 밖에 추출이 안되는 현상이 발생함. 그래서, 패딩을 붙여줌으로써 가장자리까지 똑같이 학습할 수 있도록 만들어줌.
또 다른 이유는 입력값과 출력값의 사이즈를 맞춰주기 위함. 보통 filter를 하면, 출력값이 줄어들기 때문에, 결국 입력값과 출력값의 사이즈를 같게 만들 수 있음.</p>
</blockquote>
<blockquote>
<p>input &amp; output을 똑같게 하기 위해서, 3*3 filter에 padding을 1로 주면 됨! </p>
</blockquote>
<h4 id="input-shape데이터-개수batch-size-channel-height-width">Input Shape(데이터 개수(batch size), channel, height, width)</h4>
<p>Channel은 하나의 data를 구성하는 행렬의 개수를 의미하는데, channel은 
흑백과 컬러에 따른 이미지의 행렬 개수와 feature map(특성 개수- height x width)로 이루어져 있다. 이때, filter의 channel 수는 입력 데이터의 channel 수와 동일해야 함 !!</p>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/295e70a5-5b2b-4400-879a-6bc3e1f4d30c/image.png" alt="">
그림에서 본 것처럼, 만약 input data가 3x6x6이면, 3x3x3의 filter가 2개가 있고, 그럼 각각 4x4의 output 값이 나오고,그게 2개니깐 그럼 2x4x4의 output이 만들어짐</p>
<pre><code class="language-py">input_data = torch.ones(1, 3, 10, 10) # batch크기, channel수, heigth, width
output = layer(input_data)
output.shape</code></pre>
<h4 id="max-pooling-layer-for-subsampling">Max Pooling Layer for subsampling</h4>
<p><strong>Pooling Layer란?</strong>
Feature map의 특성 영역의 값들 중 그 영역을 대표할 수 있는 한개의 값을 추출하여 output을 만드는 것. 대표적으로 pooling layer에는  Max pooling과 Average pooling이 있음.
그렇다면, 왜 대표값을 추출해서 output값을 줄여주려고 하는 걸까? 
당연히 feature map의 size가 너무 크면, 너무 오랜 시간과 비용이 필요하기 때문! 
그래서 feature map을 추출하면, 그걸 downsampling 해줌.</p>
<p><strong>torch.nn.MaxPool2d</strong></p>
<ul>
<li>해당 영역의 input 중에 가장 큰 값 출력</li>
<li>영역의 size와 stride를 동일하게 주어서 값을 추출하는 영역이 안겹치도록 해야 함
ex. 2x2의 크기에 stride는 2를 사용하면, height와 width가 각각 절반의 크기로 줄어듦. 
<img src="https://velog.velcdn.com/images/jiyun_kang/post/b48cacb8-8590-45eb-a79e-95a17adc5415/image.png" alt=""></li>
</ul>
<pre><code class="language-py">pool_layer = nn.MaxPool2d(
    kernel_size=2, # 값을 추출하는 영역 크기(2, 2) - default: 2
    stride=2,      # 다음 값을 추출하기위해서 몇칸을 이동할지.(default: kernel_size)
    padding=0
    # 값을 추출할 영역이 kernel_size보다 작을 경우 추출할지 여부.
    # 0-추출을 하지 않겠다.
)</code></pre>
<h4 id="pretrained-모델을-활용한-이미지-분류">Pretrained 모델을 활용한 이미지 분류</h4>
<p><strong>pretrained model 사용하는 방식</strong></p>
<ul>
<li><p>Zero-shot transfer learning : 추가학습 없이 pretrained 모델을 사용</p>
</li>
<li><p>Transfer learning(전이 학습): pretrained 모델의 일부분을 재학습 시킴(주로 출력 Layer)</p>
</li>
<li><p>Fine-tuning : Pretrained 모델의 파라미터를 초기 파라미터로 사용 -&gt; Custom dataset으로 학습을 진행해서 모든 파라미터 업데이트</p>
<p>이제 Pytorch에서 제공하는 Pretrained Model(<a href="https://pytorch.org/hub/)%EB%A1%9C">https://pytorch.org/hub/)로</a> 이미지 분류를 해보려고 하는데, 다양한 모델들이 많음.</p>
</li>
</ul>
<h4 id="1-vggnet-모델-imagenet-dataset으로-사전-학습시킨-모델로-학습된-parameter를-제공-120만장의-trainset-1000개의-class로-구성">1. VGGNet 모델: ImageNet dataset으로 사전 학습시킨 모델로 학습된 parameter를 제공 (120만장의 trainset, 1000개의 class로 구성)</h4>
<p>1-1. 일단 정답이 뭔지 class 목록을 다운받아보자.</p>
<pre><code class="language-py"># ImageNet 1000개의 class 목록
%pip install wget
import wget
url = &#39;https://gist.github.com/yrevar/942d3a0ac09ec9e5eb3a/raw/238f720ff059c1f82f368259d1ca4ffa5dd8f9f5/imagenet1000_clsidx_to_labels.txt&#39;
imagenet_filepath = wget.download(url) # url의 파일을 다운로드.

# string 형태를 dictionary로 바꿈
import ast
with open(&quot;imagenet1000_clsidx_to_labels.txt&quot;, &quot;rt&quot;) as fr:
    index_to_class = ast.literal_eval(fr.read())  # dictionary로 변환. #literal_eval은 eval과 다르게 실행하지 않고 그 값만 리턴해줌.
print(type(index_to_class), len(index_to_class))
</code></pre>
<p> 1-2. Pretrained 모델 load</p>
<pre><code class="language-py"> import torch
from torchvision import models, transforms 
# torchvision.models: Pretrained 모델들을 제공.
from torchinfo import summary

## Pretrained 모델 Loading
load_model = models.vgg16(
    weights=models.VGG16_Weights.DEFAULT # 학습된 weight(parameter) 도 같이 load #pretrained-model에서 이미지를 학습할 떄 써던 weight를 가져옴. #DEFAULT는 최신으로 학습한 파라미터 
)</code></pre>
<p> 이 모델은 아래 summary를 보면 covolution&amp;Relu를 두번씩 하고 나면, Max-pooling을 해서 size를 줄이는 구조를 5번 반복한 뒤에 adaptive_average pooling을 하는데 이건 output_size를 (7,7)로 맞춤. 여기서는 Global Average Pooling을 쓰는 대신 adaptive average pooling으로 장치를 하나 만들어주었는데, 그건 바로 classifier에 넣기 전에 output size를 무조건 7x7로 맞춰주기 위해 넣어둠. 즉, 각 데이터의 크기가 다를 수 있기 때문에 무조건 그걸 분류기랑 맞춰주기 위해서 장치를 하나 넣어둠
<img src="https://velog.velcdn.com/images/jiyun_kang/post/cfbd722f-4229-495b-a71b-76f0ea44df9b/image.png" alt=""></p>
<blockquote>
<p>번외!) Adaptive_average_pooling - &gt; Global Pooling에 대해 이야기 해보고자 한다! (output_size = (1,1))
추론 전에 flatten을 대신해서 Global pooling 해줄 수 있다고 했는데, 이렇게 pooling을 거치면 그냥 flatten을 할 때보다 feature map의 정보 손실을 최소화하면서 연산된 feature map의 사이즈를 크게 줄여줘서 연산 속도를 가속시키면서 globalization 효과로 overfitting을 방지해줄 수 있다.
그러나, 본 데이터셋은 값이 작아서 average_pooling을 해도 되지만, input data의 이미지가 커질수록 이미지 합성곱 연산층이 깊어져서 pooling만으로는 충분한 결과를 얻지 못할 수 있다 왜냐면, 대표값으로 해버리면, 원래 feature map이 가지고 있던 위치 정보를 모두 잃어버릴 수 있기 때문!</p>
</blockquote>
<table>
  <tr>
    <td><img src="https://velog.velcdn.com/images/jiyun_kang/post/f4a2427e-49f4-442f-b746-8ea805ec41d7/image.png" width="400">일반적으로 classfier 전달하는 방식</td>
    <td><img src="https://velog.velcdn.com/images/jiyun_kang/post/33d581fc-f2a9-4e20-b8a2-405220901a2c/image.png" width="400">Global Average Pooling을 통해 </td>
  </tr>
</table>

<p> 1-3. 추론</p>
<pre><code class="language-py"> # 추론할 이미지 다운로드
import requests
from io import BytesIO
from PIL import Image

# img_url = &#39;https://cdn.download.ams.birds.cornell.edu/api/v1/asset/169231441/1800&#39;
img_url = &#39;https://blogs.ifas.ufl.edu/news/files/2021/10/anole-FB.jpg&#39;
# img_url = &#39;https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/YellowLabradorLooking_new.jpg/640px-YellowLabradorLooking_new.jpg&#39;

res = requests.get(img_url)

# res.content # binary data
# BytesIO() -&gt; binary data(file)을 bytes 타입으로 변환.
test_img = Image.open(BytesIO(res.content)) # res: http 응답정보. res.content: 다운받은 binary 파일
test_img</code></pre>
<p>다음에는 데이터에 맞게 사이즈를 조정하는 과정이 필요함~!</p>
<pre><code class="language-py"> transform = transforms.Compose([
    transforms.Resize((224,224)), #데이터 사이즈에 맞게 조정
    transforms.ToTensor() # tensor로 조정 
])
input_tensor = transform(test_img).unsqueeze(0) # 배치 축 (batch: 1 추가)
input_tensor.shape</code></pre>
<p>추론 시작!</p>
<pre><code class="language-py">load_model = load_model.to(device)
input_tensor = input_tensor.to(device)

load_model.eval()
with torch.no_grad():
    pred = load_model(input_tensor)


pred = torch.nn.Softmax(dim=-1)(pred)   # softmax 처리
pred_cls = pred.max(dim=-1).indices[0]  # 클래스  맥스값의 index 값
pred_proba = pred.max(dim=-1).values[0] #  맥스의 실제 값
print(pred_cls)
print(pred)

print(pred_proba)  # softmax처리한 확률값
print(pred_cls, index_to_class[pred_cls.item()])

-------------------------------------------------------------
tensor(0.6342)   #class중에 가장 높은 확률 값
tensor(47) African chameleon, Chamaeleo chamaeleon ##정답임!!</code></pre>
<h4 id="2-transfer-learning전이학습">2. transfer learning(전이학습)</h4>
<ul>
<li>미리 학습된(pre-trained) Model을 이용하여 모델을 구성한 뒤 현재 하려는 예측 문제를 해결함. 그래서 보통은 보통 Pretrained Model에서 Feature Extraction 부분을 사용함</li>
<li>Computer Vision 문제의 경우 Bottom 쪽의 Convolution Layer(Feature Extractor)들은 이미지에 나타나는 일반적인 특성을 추출하므로 <strong>다른 대상을 가지고 학습했다고 하더라도 재사용할 수 있음.</strong></li>
<li>Top 부분 Layer 부분은 특히 출력 Layer의 경우 대상 데이터셋의 목적에 맞게 변경 해야 하므로 재사용할 수 없음.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/76fd79ab-8493-4714-8935-aa86cbda4c53/image.png" alt=""></li>
</ul>
<p>들어가기 전에 ! transfer learning의 종류에 대해서 좀 공부해보았다!
<img src="https://velog.velcdn.com/images/jiyun_kang/post/c8e8bf92-875b-4dd7-bb95-29751b13e114/image.png" alt="">아래의 표에 자세히 설명되어 있지만, 귀납과 변형의 가장 큰 차이는 target domain에 label이 있는가 없는가의 차이이다. 귀납의 경우는 정답이 있기 때문에 그 정답에 대한 추론을 진행하지만, 변형 전이학습은 정답이 없어서, source의 label에만 국한되지 않고, target의 label을 도메인에 맞게 변형할 수 있다!</p>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="left">알고리즘</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>Inductive Transfer Learning<br>(귀납적 전이 학습)</strong></td>
<td align="left">✅ <strong>특징</strong>: Target Domain에 Labeled Data 존재<br>✅ <strong>목적</strong>: 학습된 지식을 활용해 다른(또는 유사한) 태스크에 적용</td>
</tr>
<tr>
<td align="center">┗ Multi-task 학습</td>
<td align="left">하나의 학습 데이터셋으로 여러 태스크(분류기)를 동시에 학습<br>예시: 감정 분석 + 요약 작업을 동시에 학습하여 서로의 정보를 공유</td>
</tr>
<tr>
<td align="center">┗ Self-taught 학습</td>
<td align="left">Labeled Data에서 feature를 생성하고, 이를 기반으로 최종 분류기에 전이<br>예시: Autoencoder로 학습된 feature를 분류기에 전달</td>
</tr>
<tr>
<td align="center">*<em>Transductive Transfer Learning<br>(변형 전이 학습) *</em></td>
<td align="left">✅ <strong>특징</strong>: Target Domain에는 Labeled Data 없음<br>✅ <strong>목적</strong>: Source 도메인에서 학습된 지식을 Target 도메인에 맞게 변형</td>
</tr>
<tr>
<td align="center">┗ Domain Adaptation</td>
<td align="left">Feature를 생성한 후 target domain과 source domain 간 분포 차이를 극복<br>예시: 영어 뉴스로 학습한 모델을 한국어 뉴스에 적용할 때 언어 차이를 줄이는 방식</td>
</tr>
<tr>
<td align="center">┗ Sample Selection Bias</td>
<td align="left">학습 시 특정 샘플만 선택하거나, 학습치가 제한된 상태에서 전이<br>예시: 특정 환경(도시)에서 수집된 데이터만으로 학습한 뒤 다른 지역에 적용</td>
</tr>
</tbody></table>
<p>이제 본격적으로 코드를 하나씩 뜯어보자!</p>
<p>2-1. dataset, DataLoader </p>
<ul>
<li><p>Image Augmentation 
Augmentation은 기존의 이미지를 변형해주는 것인데, 데이터셋의 종류가 많지 않은 경우에 Cropping, Reverse, Rotation 등 여러 변형을 주어 모델이 받아들이기에 새로운 이미지처럼 만드는 것. (**이건 trainset에만 적용, validationset은 실제 데이터만 써야하니깐 augmentation 안함)</p>
</li>
<li><p>전처리 과정
resize =&gt; ToTensor(channel first) =&gt; Normalization(정규화)</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/1191a64e-0c5e-4874-957f-211af280ad41/image.png" alt="">
argumentation과 전처리를 하면 요렇게 된답니다~색깔은 normalize 하니까 이런 색깔이 됨</p>
<pre><code class="language-py">train_transform = transforms.Compose([
    # 1) 전처리
    transforms.Resize((224, 224)), # resize
    transforms.ToTensor(),         # ndarray, Image -&gt; Tensor, 0 ~ 1 정규화, channel first
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),  # (Red, Green, Blue) -&gt; 저 수치는 이렇게 했을 때 가장 성능이 좋다고 해서 가장 많이 씀. 
    # 채널별 평균, 표준편차설정. -&gt; Standard Scaling 처리.(픽셀값-평균)/표준편차
    # 2) Augmentation
    transforms.RandomHorizontalFlip(),  # 좌우 반전, 랜덤
    transforms.RandomVerticalFlip(),  # 상하 반전
    transforms.RandomRotation(degrees=180)  # 0-180도 사이 렌덤하게 회전
])

# validation에는 실제 데이터만 써야 하니까, augmentation 안함.
test_transform = transforms.Compose([
    transforms.Resize((224, 224)), #  resize
    transforms.ToTensor(),          # ndarray, Image -&gt; Tensor, 0 ~ 1 정규화, channel first
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])</code></pre>
<ul>
<li>dataset 정의<pre><code class="language-py"># cat/dogs 디렉토리 이름이 class, 그 안에 데이터가 input data 
train_set = datasets.ImageFolder(
  os.path.join(target_path, &quot;train&quot;), # Data들을 저장한 디렉토리.
  transform=train_transform
)
valid_set = datasets.ImageFolder(
  os.path.join(target_path, &quot;validation&quot;),
  transform=test_transform
)
test_set = datasets.ImageFolder(
  os.path.join(target_path, &quot;test&quot;),
  transform=test_transform
)</code></pre>
</li>
<li>DataLoader<pre><code class="language-py">train_loader = DataLoader(train_set, batch_size=BATCH_SIZE, drop_last=True,  
                      num_workers=os.cpu_count()) # 데이터 불러오는 것 병렬처리.
valid_loader = DataLoader(valid_set, batch_size=BATCH_SIZE, num_workers=os.cpu_count())
test_loader = DataLoader(test_set, batch_size=BATCH_SIZE, num_workers=os.cpu_count())</code></pre>
2-2. pretrained 모델(Backbone) 가져오기 (vgg16 활용)<pre><code class="language-py"># Transfer Learning - Backbone 모델: VGG16 + classifier(내것)
model = models.vgg16(models.VGG16_Weights.DEFAULT)
model</code></pre>
</li>
</ul>
<p>2-3. Backbone 모델 frozen 시키기 -&gt; 파라미터들이 학습시 update 되지 않도록 변경.</p>
<pre><code class="language-py">for p in model.parameters():
    p.requires_grad = False</code></pre>
<p>2-4. classifier(분류기) 내것으로 변경</p>
<pre><code class="language-py">model.classifier = nn.Linear(in_features=25088, out_features=2)</code></pre>
<p> Linear(in_features=4096, out_features=1000, bias=True) -&gt;
 Linear(in_features = 25088, out_features =2) 
 이렇게 out_features가 바뀐걸 볼 수 있음 !</p>
<p> 2-5. 학습
 여기서 mode=&quot;multi&quot;는 다중모델임을 설정해주는 값임.</p>
<pre><code class="language-py">  os.makedirs(&quot;saved_models&quot;, exist_ok=True)
save_model_path = &quot;saved_models/cat_dog_model.pt&quot;

model = model.to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)

result = fit(train_loader, valid_loader, model, loss_fn, optimizer, EPOCH,
            save_best_model=True, save_model_path=save_model_path, 
            device=device, mode=&quot;multi&quot;)</code></pre>
<p> 2-6. 최종 평가
 input_data 의 값을 사이즈에 맞춰서 넣어준 다음에 추론을 진행</p>
<pre><code class="language-py"> load_model = torch.load(save_model_path)

 def predict(image_path, model, transform, device):
    # &quot;model로 image_path의 이미지를 추론한 결과를 반환.&quot;
    img = Image.open(image_path)  # 추론대상 이미지 loading
    input_data = transform(img)  # shape: (C, H, W)
    input_data = input_data.unsqueeze(dim=0) # (C, H, W) -&gt; (1, C, H, W) 
    input_data = input_data.to(device)

    # 추론
    model = model.to(device)
    model.eval()
    with torch.no_grad():
        pred = model(input_data)
        pred_proba = pred.softmax(dim=-1) # 확률값으로 변경.
        pred_label = pred_proba.argmax(dim=-1).item()  # Tensor([3]) -&gt; 3
        pred_proba_max = pred_proba.max(dim=-1).values.item()
        class_name = &quot;cat&quot; if pred_label == 0 else &quot;dog&quot;
        return pred_label, class_name, pred_proba_max

 predict(&quot;test_img/dog.jpg&quot;, load_model, test_transform, device)</code></pre>
<h2 id="나가며">나가며..</h2>
<p> 이번 CV 의 여정은 꽤나 길었다... 하지만,,, 아직 2편이 남아있다. CV 너무 재밌자나~
 2편에는 GAN 모델과 Stable Diffusion이 남아있으니, 많관부~~😁</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PEFT] LoRA(Low-Rank Adaption)]]></title>
            <link>https://velog.io/@jiyun_kang/PEFT-LoRALow-Rank-Adaption</link>
            <guid>https://velog.io/@jiyun_kang/PEFT-LoRALow-Rank-Adaption</guid>
            <pubDate>Tue, 01 Jul 2025 09:37:44 GMT</pubDate>
            <description><![CDATA[<h2 id="peftparameter-efficient-fine-tuning">PEFT(Parameter Efficient Fine-Tuning)</h2>
<p>배경: 1,750억 개의 학습 가능한 파라미터가 있는 GPT-3에서는 full-finetuning 방식을 활용하기엔 시간적 혹은 비용적 측면에서 힘들어지고 있다. </p>
<p>이에 많은 사람들이 일부 파라미터만 조정하거나 새로운 task를 위한 외부 모듈을 학습하여 이를 완화하려고 했다. 이렇게 하면 각 task에 대해 사전 학습된 모델 외에 소수의 task별 파라미터만 저장하고 로드하면 되므로 배포 시 운영 효율성이 크게 향상된다. </p>
<p>PEFT는 다운스트림 작업의 성능을 유지하거나 향상시키면서 계산량과 모델 크기를 줄이는 것을 목표로 한다.</p>
<h3 id="adapter-tuning-lora">Adapter tuning: LoRA</h3>
<p>-&gt; 기존 학습 모델에서 어댑터 레이어 추가 ; 모델 파라미터 재구성, 더 적은 파라미터 학습
대표적인 모델: LoRA(Low-RANK Adaption)</p>
<p><strong>기본 컨셉</strong>
LoRA 파라미터 재구성 -&gt; 모델의 Parameter 행렬을 더 작은 2개의 행렬 곱으로 표현. 기존 파라미터에다가 더 작은 2개의 행렬 수정</p>
<p>이때 LoRA는 내재적 차원을 사용해서 튜닝을 하는거라고 할 수 있음!</p>
<p>표면적 차원 vs 내재적 차원
표면적 차원(surface dimension): 모델 파라미터의 전체 수. 예를 들어, 어떤 레이어의 가중치가 W∈R 1000×1000이라면, 총 1백만 개의 파라미터를 학습하는 것이므로 차원은 1,000,000</p>
<p>내재적 차원(intrinsic dimension): <strong>실제로 학습 문제를 잘 해결하는 데 필요한 최소한의 유효한 파라미터 공간</strong>의 차원입니다. 즉, 모델이 매우 높은 차원의 공간에서 정의되어 있더라도, 학습 중 유의미하게 변화하는 방향은 사실상 훨씬 적다는 뜻</p>
<blockquote>
<p> 이 특성을 이용해서 파라미터를 다 바꾸지 않고도, 몇천 차원의 subspace만을 가지고 학습해도 성능이 잘나옴</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/77bcc5e8-db66-4787-9222-b69796aa106f/image.png" alt=""></p>
<p>여기서 r은 low_rank의 차원인데, 그림에서 보면 일단 A에서는 랜덤 가우시안을 초기화를 한다. 랜덤 가우시안 분포는 정규분포를 따라 랜덤하게 설정하는 방식인데, 이걸 왜 써서 초기화를 하냐면!</p>
<p>신경망의 학습 성능은 초기화 방법에 민감함. 그래서 이때 정규분포를 쓰면 모든 뉴런이 동일한 값을 가지면 학습이 진행되지 않을 뿐더러, 적절할 분산을 통해서 backward 시 gradient vanishing을 막을 수 있음.</p>
<p>반면 B에서는 처음에 영향이 없게 0으로 시작해서, 처음에는 BA=0으로 시작할 수 있게 함. 그리고 학습이 진행되면 점차 영향을 받을 수 있게 함.</p>
<p>그런 다음에, LoRA scaling factor를 해줘야 하는데, 
<img src="https://velog.velcdn.com/images/jiyun_kang/post/4ac76799-884e-4691-b54a-e2039d3ab132/image.png" alt="">
여기서 𝛼는 LoRA scaling factor이며, rank 𝑟이 커질수록 스케일을 줄여서 학습 안정성 유지해주는 역할을 함. 이 스케일링은  𝑟을 변경할 때 hyperparameter를 다시 튜닝할 필요성을 줄이는데 도움이 됨.</p>
<p>그럼 이제 어떻게 기존 모델의 특성값과 도메인 특성 값에 대한 차원을 구해서 업데이트를 하는지 알아보자.</p>
<ol>
<li>기존 모델의 특성값 계산
(1,100) @ (100,100) = (1,100)차원의 특성값을 뽑아냄.</li>
</ol>
<ul>
<li><p>기존 가중치 행렬 $W_0 \in \mathbb{R}^{100 \times 100}$:</p>
<p>예시 (일부):
$$
W_0 =
\begin{bmatrix}
1 &amp; 2 &amp; 3 &amp; \cdots \
0 &amp; 1 &amp; 0 &amp; \cdots \
\vdots &amp; \vdots &amp; \vdots &amp; \ddots
\end{bmatrix}
$$</p>
</li>
<li><p>계산 결과:
$
h = x @ W_0 = [1, 2, 3, ..., 0] \in \mathbb{R}^{1 \times 100}
$</p>
</li>
</ul>
<ol start="2">
<li>LoRA를 통한 도메인 특성값 계산
그럼 (1,100)@(100, 8(r)) = (1,8) = A
(8(r), 100) = B 
A @ B = (1,8) @(8,100)  = (1,100) </li>
</ol>
<ul>
<li><p>LoRA 구성: </p>
<ul>
<li>$A \in \mathbb{R}^{100 \times 8}$</li>
<li>$B \in \mathbb{R}^{8 \times 100}$ (처음에는 0으로 초기화)</li>
</ul>
</li>
<li><p>예시:</p>
<ul>
<li>$A$: 랜덤 가우시안 초기화<br>$$
A =
\begin{bmatrix}
0.1 &amp; 0.2 &amp; ... &amp; 0.8 \
\vdots &amp; &amp; &amp; \vdots \
\end{bmatrix}
$$</li>
<li>$B$:<br>$$
B =
\begin{bmatrix}
0 &amp; 0 &amp; ... &amp; 0 \
\vdots &amp; &amp; &amp; \vdots \
\end{bmatrix}
$$</li>
</ul>
</li>
<li><p>연산:</p>
<ol>
<li><p>도메인 투영:
$
A&#39; = x @ A \Rightarrow (1 \times 100) @ (100 \times 8) = (1 \times 8)
$
예:  $A&#39; = [0.1, 0.2, 0.3, ..., 0.8]$</p>
</li>
<li><p>원래 차원 복원:
$\Delta h = A&#39; @ B = (1 \times 8) @ (8 \times 100) = (1 \times 100)$</p>
</li>
</ol>
<p>→ 현재는 $B = 0$이므로 $\Delta h = 0$</p>
</li>
</ul>
<ol start="2">
<li>최종 출력 계산
그럼 이제 기존 모델 특성값과 도메인 특성값의 shape가 똑같으니깐, 서로 더할 수 있음! </li>
</ol>
<p>$$
h_{\text{new}} = h + \Delta h = [1, 2, 3, ..., 0] + [0, 0, 0, ..., 0] = [1, 2, 3, ..., 0]
$$</p>
<p>→ 학습 전에는 LoRA의 영향이 없으며, 학습이 진행될수록 $B$가 업데이트되면서 보정값이 추가</p>
<p>그리고 본래 모델의 파라미터는 freeze을 시키고,  LoRA adapeter를 구성하는  d<em>r 과 r</em>d의 행렬이니깐 본래 모델의 d*d의 행렬보다 훨씬 더 적은 파라미터를 학습시킬 수 있음.</p>
<p><strong>추가적인 inference latency 없음</strong>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/82881b3a-db8d-44b9-af5a-cec84f5f1bbf/image.png" alt=""></p>
<p><strong>downstream task &amp; upstream task</strong></p>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Upstream task</strong></td>
<td>대규모 데이터로 모델을 사전 학습시키는 작업 (예: 언어 모델 훈련)</td>
</tr>
<tr>
<td><strong>Downstream task</strong></td>
<td>사전 학습된 모델을 실제 문제에 적용하는 작업 (예: 감정 분류)</td>
</tr>
<tr>
<td>즉, Downstream task는 pretrained 모델을 특정 도메인 문제에 fine-tuning하거나 적용하는 것을 의미함.</td>
<td></td>
</tr>
</tbody></table>
<p><strong>LoRA의 설정 값</strong></p>
<ol>
<li>r: AB를 만들때 r을 몇으로 할지 설정
그렇다면, 최적의 r은 몇일까???
LoRA 원논문에 따르면,
BERT-base, RoBERTa 등: r=4로도 기존 full fine-tuning과 거의 동일한 성능을 달성
GPT-3 r= 8 또는 r = 16으로 안정적 성능을 확보할 수 있다고 함.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/12cdfc51-f970-4f44-b268-57c757155756/image.png" alt=""></li>
</ol>
<ol start="2">
<li>alpha: adaptor 파라미터의 결과를 모델의 파라미터에 얼마나 반영할지를 결정(alpha가 커질수록 adpator쪽에 더 비중을 둠)</li>
</ol>
<p><strong>transformer에서의 LoRA</strong></p>
<p>모델의 파라미터 중 어디에  adaptor를 적용할 지도 결정할 수 있음!
예를 들어서, transformer에 self-attention layer 는 key, query, value, feed-forward의 linear layer로 구성. 이중 특정 파라미터에만 LoRA를 적용할 수 있음.
논문에서도 GPT-3의 가중치 행렬 종류에 대한 정확도가 나오는데,key, query, value, output projection 모두에 LoRA를 적용하는것이 가장 정확도가 높다.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/d63644ec-2101-43f1-b179-025f8fe5779a/image.png" alt=""></p>
<p>갑분 논문이지만..WikiSQL 이랑 MultiNLI는 둘다 자연어처리(NLP)분야에서 자주 사용되는 다운스트림 테스크용 데이터셋인데, 두개 차이의 용도는 완전히 다름.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>WikiSQL</th>
<th>MultiNLI</th>
</tr>
</thead>
<tbody><tr>
<td>태스크</td>
<td>자연어 → SQL 변환 (Semantic Parsing)</td>
<td>문장 간 논리 관계 분류 (Natural Language Inference)</td>
</tr>
<tr>
<td>입력</td>
<td>질문 + 테이블</td>
<td>두 문장 (premise, hypothesis)</td>
</tr>
<tr>
<td>출력</td>
<td>SQL 쿼리 (문자열)</td>
<td>범주(Label)</td>
</tr>
<tr>
<td>학습 유형</td>
<td>생성(Generation)</td>
<td>분류(Classification)</td>
</tr>
<tr>
<td>사용 분야</td>
<td>DB 질의, 데이터 접근</td>
<td>논리 추론, 질의 응답, 요약</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Runnable 하위 클래스 종류]]></title>
            <link>https://velog.io/@jiyun_kang/Runnable-%ED%95%98%EC%9C%84-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A2%85%EB%A5%98</link>
            <guid>https://velog.io/@jiyun_kang/Runnable-%ED%95%98%EC%9C%84-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A2%85%EB%A5%98</guid>
            <pubDate>Wed, 11 Jun 2025 01:28:44 GMT</pubDate>
            <description><![CDATA[<p><code>Runnable Sequence</code></p>
<h2 id="1-runnablelambda함수">1. RunnableLambda함수</h2>
<pre><code class="language-py">from langchain_core.runnables import RunnableLambda
# RunnableLambda(함수) -&gt; 함수를 실행하는 Runnable을 생성.
my_runnable2 = RunnableLambda(lambda input_data : f&quot;{input_data}를 한문장으로 설명해줘.&quot;)  # lambda 말고 함수 정의한거 넣을 수 있음

my_runnable2.invoke(&quot;LLM&quot;)</code></pre>
<h2 id="2-runnablepassthrough">2. RunnablePassthrough</h2>
<h4 id="1-앞-runnable이-처리한-결과를-다음-runnable에-그대로-전달">1) 앞 Runnable이 처리한 결과를 다음 Runnable에 그대로 전달</h4>
<pre><code>from langchain_core.runnables import RunnablePassthrough

RunnablePassthrough().invoke(&quot;안녕하세요&quot;)   # 받은 값을 그대로 전달
RunnablePassthrough().invoke({&quot;Key&quot;:&quot;value&quot;})</code></pre><pre><code>--&gt; {&#39;Key&#39;: &#39;value&#39;}
</code></pre><h4 id="2-runnablepassthrough에서-그대로-전달하는-게-아닌-값을-추가해서-전달하고-싶은-경우">2) <code>RunnablePassthrough</code>에서 그대로 전달하는 게 아닌 값을 추가해서 전달하고 싶은 경우</h4>
<pre><code class="language-py"># 2) 앞 Runnable이 처리한 결과에 Item을 추가해서 다음 Runnable에 그대로 전달
# -&gt;입력으로 dictionary 받아서 거기에 item을 추가. 
#RunnablePassthrough.assign(key1=Runnable, key2=Runnable, ...)
# - 받은 dictionary에 &quot;key1&quot;: Runnable 반환값, &quot;key2&quot;: Runnable 반환값 ..추가해서 다음으로 전달.

address_runnable = RunnableLambda(lambda x: &quot;서울시 금천구&quot;)  # &quot;서울시 금천구&quot;를 반환.
phone_runnable = RunnableLambda(lambda x: &quot;010-1111-2222&quot;)

RunnablePassthrough().assign(address=address_runnable, phone=phone_runnable).invoke({&quot;name&quot;:&quot;홍길동&quot;})
</code></pre>
<pre><code class="language-py">--&gt; {&#39;name&#39;: &#39;홍길동&#39;, &#39;address&#39;: &#39;서울시 금천구&#39;, &#39;phone&#39;: &#39;010-1111-2222&#39;}
</code></pre>
<h2 id="3-runnableparallel---dictionary로-묶어서-반환">3. RunnableParallel -&gt; Dictionary로 묶어서 반환</h2>
<h4 id="1-일반적인-runnableparrallel-표현">1) 일반적인 RunnableParrallel 표현</h4>
<pre><code class="language-py">from langchain_core.runnables import RunnableParallel, RunnableLambda

run1 = RunnableLambda(lambda x: x+1)
run2  =RunnableLambda(lambda x: x*2)
run3 = RunnableLambda(lambda x: x//3)

runnable = RunnableParallel(
    {
        &quot;result1&quot;:run1,
        &quot;result2&quot;:run2,
        &quot;result3&quot;:run3,
        &quot;result4&quot;: RunnablePassthrough()  #앞에서 받은 값을 그대로 다음에 전달.
    }
)
# Runnable들을 각각 실행하고 그 결과를 key에 할당한 Dictionary를 반환
runnable.invoke(20)
</code></pre>
<pre><code class="language-py">--&gt; {&#39;result1&#39;: 21, &#39;result2&#39;: 40, &#39;result3&#39;: 6, &#39;result4&#39;: 20}</code></pre>
<h4 id="2-lcel-형태-안에-runnableparallel을-넣을-때는-딕셔너리-형태로-표현할-수-있다">2) LCEL 형태 안에 Runnableparallel을 넣을 때는 딕셔너리 형태로 표현할 수 있다!</h4>
<pre><code class="language-py">run0 = RunnableLambda(lambda x: x+100)
# run0 -&gt; {run1, run2, run3} -&gt; 
chain = run0 |{&quot;result1&quot;:run1,
                &quot;result2&quot;:run2,
                &quot;result3&quot;:run3,}
# lcel 안에서만 runnableparallel을 딕셔너리 형태로 표현할 수 있다, 
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Attention is All You Need: Transformer]]></title>
            <link>https://velog.io/@jiyun_kang/Attention-is-All-You-Need-Transformer</link>
            <guid>https://velog.io/@jiyun_kang/Attention-is-All-You-Need-Transformer</guid>
            <pubDate>Tue, 03 Jun 2025 15:54:35 GMT</pubDate>
            <description><![CDATA[<h1 id="🧠-transformer-구조-정리-encoder-decoder-기반">🧠 Transformer 구조 정리 (Encoder-Decoder 기반)</h1>
<p>Transformer는 Attention 메커니즘을 활용한 모델로, 병렬처리가 가능하고 긴 문맥을 잘 반영할 수 있다는 점에서 RNN/LSTM을 대체하는 대표적인 모델입니다. 본 포스트에서는 Encoder-Decoder 구조를 중심으로 Transformer의 핵심 구성 요소를 설명합니다.</p>
<hr>
<h2 id="🔹-transformer-전체-구조">🔹 Transformer 전체 구조</h2>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/4b20bab1-4d8c-4d42-a7b1-2d4955028215/image.png" alt=""></p>
<p>Transformer는 크게 두 부분으로 구성됩니다.</p>
<ul>
<li><strong>Encoder</strong>: 입력 시퀀스를 인코딩 (예: 문장을 의미 벡터로 변환)</li>
<li><strong>Decoder</strong>: 인코딩된 벡터를 바탕으로 출력 시퀀스 생성</li>
</ul>
<hr>
<h2 id="🔸-encoder-구성">🔸 Encoder 구성</h2>
<h3 id="1-input-embedding--positional-encoding">1. Input Embedding + Positional Encoding</h3>
<p>Transformer는 순서를 모르는 구조이기 때문에, <strong>단어 임베딩 + 위치 정보(Positional Encoding)</strong>를 더해줍니다.</p>
<h3 id="2-multi-head-self-attention">2. Multi-Head Self-Attention</h3>
<ul>
<li><strong>Self-Attention</strong>: 입력 시퀀스 내 단어들끼리 서로 얼마나 중요한지 계산합니다.</li>
<li>각 단어는 Query, Key, Value로 변환되고 아래 수식을 따라 Attention Score가 계산됩니다.</li>
</ul>
<p>$$
\text{Attention}(Q, K, V) = \text{softmax}\left( \frac{QK^T}{\sqrt{d_k}} \right)V
$$</p>
<h3 id="3-feed-forward-network-ffn">3. Feed Forward Network (FFN)</h3>
<p>각 위치에서 독립적으로 작동하는 작은 신경망입니다.</p>
<h3 id="4-add--norm-residual--layernorm">4. Add &amp; Norm (Residual + LayerNorm)</h3>
<p>Residual connection을 통해 정보 손실을 막고, Layer Normalization을 통해 학습 안정성을 높입니다.</p>
<hr>
<h2 id="🔸-self-attention-더-확대해서-보면-다음과-같다">🔸 Self Attention 더 확대해서 보면 다음과 같다!</h2>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/08d6c7e6-609f-4bf3-9110-e52a82baa73e/image.png" alt=""></p>
<h3 id="▶-query-key-value-생성">▶ Query, Key, Value 생성</h3>
<table>
<thead>
<tr>
<th>입력</th>
<th>Q, K, V 생성 방식</th>
</tr>
</thead>
<tbody><tr>
<td>임베딩 벡터</td>
<td>각기 다른 가중치를 가진 선형변환으로 Q, K, V 생성</td>
</tr>
</tbody></table>
<ul>
<li><strong>Query</strong>: Embedding vector를 구할 대상 (ex: it)<ul>
<li><strong>Key</strong>: Query와 연관성있는 단어들을 계산할 대상 (ex: 위 그림의 왼쪽 문서 토큰들)</li>
<li><strong>Value</strong>: Query와 Key를 이용해 찾은 attention weight를 적용해 Attention value를 찾을 대상. </li>
<li>Self attention은 Query, Key, Value 모두 입력 Sequence(X)로 부터 생성한다.<ul>
<li>X에 각각 다른 Weight를 내적하여 만든다.<ul>
<li>$Query=X\cdot W_q$ </li>
<li>$Key=X\cdot W_k$ </li>
<li>$Value=X\cdot W_v$
<img src="https://velog.velcdn.com/images/jiyun_kang/post/0b1b6b03-ddd9-4b5a-b752-a060a31a272e/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="▶-attention-score-계산">▶ Attention Score 계산</h3>
<ul>
<li>Q와 K 간의 내적 (유사도)</li>
<li>$\frac{Q \cdot K^T}{\sqrt{d_k}}$ 계산 → 유사도가 높을수록 더 집중할 수 있음 
<img src="https://velog.velcdn.com/images/jiyun_kang/post/d797ee89-b0d1-4c4e-aa1c-b4a75c0e8290/image.png" alt=""></li>
</ul>
<h3 id="▶-attention-weight">▶ Attention Weight</h3>
<ul>
<li>Score에 Softmax 적용</li>
<li>총합이 1인 확률 분포 생성</li>
</ul>
<h3 id="▶-attention-value">▶ Attention Value</h3>
<ul>
<li><p>Attention Weight와 Value를 곱함 → 문맥(Context) 벡터 생성
 $$
 \text{Attention Value} = \text{Attention Weight}\cdot\text{Value}
 $$ </p>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/dc74998c-234c-4028-b110-1615f182062c/image.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="🔸-decoder-구성">🔸 Decoder 구성</h2>
<p>Decoder는 두 가지 종류의 Attention을 사용합니다:</p>
<h3 id="1-masked-multi-head-self-attention">1. Masked Multi-Head Self-Attention</h3>
<ul>
<li>왼쪽(과거 단어)만 참고해서 다음 단어 예측</li>
<li>미래 단어 정보가 보이지 않도록 마스킹</li>
</ul>
<h3 id="2-encoder-decoder-attention">2. Encoder-Decoder Attention</h3>
<ul>
<li>Encoder에서 생성된 Key/Value를 사용</li>
<li>현재 디코딩 위치의 Query와 연산</li>
</ul>
<h3 id="3-feed-forward--add--norm">3. Feed Forward + Add &amp; Norm</h3>
<p>Encoder와 동일한 방식으로 처리됩니다.</p>
<hr>
<h2 id="🧩-시각적-이해-보충">🧩 시각적 이해 보충</h2>
<h3 id="▶-masked-attention-matrix">▶ Masked Attention Matrix</h3>
<table>
<thead>
<tr>
<th>Query →</th>
<th>I</th>
<th>am</th>
<th>a</th>
<th>boy</th>
</tr>
</thead>
<tbody><tr>
<td>I</td>
<td>✔</td>
<td>✖</td>
<td>✖</td>
<td>✖</td>
</tr>
<tr>
<td>am</td>
<td>✔</td>
<td>✔</td>
<td>✖</td>
<td>✖</td>
</tr>
<tr>
<td>a</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td>✖</td>
</tr>
<tr>
<td>boy</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
</tr>
</tbody></table>
<p>✔: 사용 가능, ✖: 마스킹됨 (미래 정보 차단)
<img src="https://velog.velcdn.com/images/jiyun_kang/post/a8b6421e-9852-4484-8297-8abd230af3f2/image.png" alt=""></p>
<hr>
<h2 id="✅-핵심-요약">✅ 핵심 요약</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Positional Encoding</td>
<td>위치 정보 추가</td>
</tr>
<tr>
<td>Multi-Head Attention</td>
<td>다양한 관점에서의 정보 결합</td>
</tr>
<tr>
<td>FFN</td>
<td>위치별로 독립적인 피처 추출</td>
</tr>
<tr>
<td>Add &amp; Norm</td>
<td>학습 안정화, gradient 흐름 유지</td>
</tr>
<tr>
<td>Masking</td>
<td>Decoder가 미래 단어를 참고하지 않게 만듦</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[GRU(Gate Recurrent Units)]]></title>
            <link>https://velog.io/@jiyun_kang/GRUGate-Recurrent-Units</link>
            <guid>https://velog.io/@jiyun_kang/GRUGate-Recurrent-Units</guid>
            <pubDate>Mon, 02 Jun 2025 17:24:38 GMT</pubDate>
            <description><![CDATA[<h2 id="🔁-grugated-recurrent-unit-구조와-동작-원리">🔁 GRU(Gated Recurrent Unit) 구조와 동작 원리</h2>
<h3 id="✅-lstm의-장점을-가져온-gru">✅ LSTM의 장점을 가져온 GRU</h3>
<p>GRU(Gated Recurrent Unit)는 <strong>LSTM의 장점을 유지하면서 더 단순한 구조</strong>로 만든 순환 신경망(RNN) 
LSTM의 핵심 기능인 <strong>장기 기억 유지</strong>는 그대로 가져오되, 내부 구조를 더 간소화해서 <strong>연산량을 줄이고 학습 속도를 개선</strong></p>
<hr>
<h3 id="🔍-gru의-핵심-특징">🔍 GRU의 핵심 특징</h3>
<ul>
<li>LSTM에서 사용되던 <strong>3개의 게이트 (Forget, Input, Output)</strong>를  
→ <strong>2개의 게이트 (Update, Reset)</strong>로 축소</li>
<li>LSTM의 <strong>Cell State $C_t$</strong>를 제거하고,<br>→ 하나의 <strong>Hidden State $h_t$</strong>가 모든 기억을 담당</li>
<li>구조는 단순하지만, 성능은 LSTM에 근접하거나 유사</li>
</ul>
<hr>
<h3 id="📦-gru의-구조-개요">📦 GRU의 구조 개요</h3>
<ul>
<li>GRU는 은닉 상태 $h_{t-1}$에서 다음 상태 $h_t$로 연결될 때<br><strong>두 갈래로 분기되었다가 다시 합쳐지는 구조</strong>를 가짐! (LSTM이랑 비슷)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/8759c333-22da-44dc-8f9a-f1c91a39ec98/image.png" alt=""></p>
<blockquote>
<p>위 그림처럼, Reset Gate와 Update Gate가 각각 정보를 조절하는 역할을 하며,<br><strong>정보 선택과 업데이트가 이루어지는 흐름</strong>을 만들어줍니다.</p>
</blockquote>
<hr>
<h3 id="✨-gru에서-새롭게-생긴-두-개의-gate">✨ GRU에서 새롭게 생긴 두 개의 Gate</h3>
<h4 id="1️⃣-reset-gate-r_t">1️⃣ Reset Gate $r_t$</h4>
<ul>
<li>새로운 사건이나 입력이 들어왔을 때, <strong>기존의 기억을 얼마나 참고할지</strong> 결정하는 게이트</li>
<li><strong>단기 기억 중심의 처리를 가능하게 함</strong></li>
</ul>
<p>$$
r_t = \sigma(W_r \cdot x_t + U_r \cdot h_{t-1})
$$</p>
<blockquote>
<p>예: 이전 정보가 무의미할 경우(질문이 바뀜 등) 기억을 &#39;초기화&#39;하듯 리셋</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/b72c54a5-8cd9-4aba-a28f-4246865028fe/image.png" alt=""></p>
<hr>
<h3 id="2️⃣-update-gate-z_t">2️⃣ Update Gate $z_t$</h3>
<ul>
<li>현재 기억 ( \tilde{h}<em>t )와 이전 기억 ( h</em>{t-1} )을 <strong>어떻게 조합할지 가중 평균을 결정</strong>  </li>
<li><strong>기억의 유지 vs 갱신 정도</strong>를 제어</li>
</ul>
<p>$$
z_t = \sigma(W_z \cdot x_t + U_z \cdot h_{t-1})
$$</p>
<blockquote>
<p>이전 기억을 유지할지, 새로 덮어쓸지를 결정하는 <strong>가중치 평균 게이트</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/1971a330-f56d-45d0-9a13-22d9e4463744/image.png" alt=""></p>
<hr>
<h4 id="🔁-gru의-전체-계산-과정">🔁 GRU의 전체 계산 과정</h4>
<ol>
<li><p><strong>Reset Gate 계산</strong>: 과거 정보를 얼마나 반영할지 결정
$$
r_t = \sigma(W_r \cdot x_t + U_r \cdot h_{t-1})
$$</p>
</li>
<li><p><strong>Update Gate 계산</strong>: 이전 상태를 얼마나 유지할지 결정
$$
z_t = \sigma(W_z \cdot x_t + U_z \cdot h_{t-1})
$$</p>
</li>
<li><p><strong>후보 은닉 상태 계산</strong>: 현재 입력 + 리셋된 과거 정보 기반
$$
\tilde{h}<em>t = \tanh(W \cdot x_t + U \cdot (r_t \odot h</em>{t-1}))
$$</p>
</li>
<li><p><strong>최종 은닉 상태 업데이트</strong>
$$
h_t = z_t \odot h_{t-1} + (1 - z_t) \odot \tilde{h}_t
$$</p>
</li>
</ol>
<ul>
<li>$\odot$: element-wise 곱</li>
<li>$h_t$: GRU의 최종 출력이자 다음 시점으로 전달되는 hidden state</li>
</ul>
<hr>
<h2 id="🧠-gru의-직관적-해석">🧠 GRU의 직관적 해석</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td>Reset Gate $r_t$</td>
<td>과거 정보를 얼마나 <strong>초기화할지</strong> 결정</td>
</tr>
<tr>
<td>Update Gate $z_t$</td>
<td>과거 정보를 얼마나 <strong>유지할지</strong> 결정</td>
</tr>
<tr>
<td>Hidden State $h_t$</td>
<td><strong>기억과 출력</strong>을 모두 담당하는 상태</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[LSTM(Long Short-term memory)]]></title>
            <link>https://velog.io/@jiyun_kang/LSTMLong-Short-term-memory</link>
            <guid>https://velog.io/@jiyun_kang/LSTMLong-Short-term-memory</guid>
            <pubDate>Mon, 02 Jun 2025 16:48:08 GMT</pubDate>
            <description><![CDATA[<h2 id="🔍-rnnsimple-rnn의-한계와-lstm의-등장-배경">🔍 RNN(Simple RNN)의 한계와 LSTM의 등장 배경</h2>
<h3 id="✅-rnn의-핵심-구조-복습">✅ RNN의 핵심 구조 복습</h3>
<p>RNN은 이전 시점의 정보를 현재 시점으로 전달하며 시퀀스 정보를 처리하는 모델이에요! 
하지만 아래와 같은 <strong>심각한 한계점</strong>이 존재합니다.</p>
<hr>
<h3 id="❗-문제점-gradient-vanishing--기억-소실">❗ 문제점: Gradient Vanishing &amp; 기억 소실</h3>
<h4 id="🔹-1-gradient-vanishing-problem">🔹 (1) Gradient Vanishing Problem</h4>
<ul>
<li>RNN은 시퀀스가 길어질수록 <strong>역전파 과정에서 기울기(gradient)가 점점 작아지는 문제</strong>가 생깁니다.</li>
<li>특히 RNN에서 사용하는 활성 함수 ( \tanh )의 도함수는 항상 ( 0 \sim 1 ) 범위의 실수이므로, 곱해질수록 값이 작아집니다.</li>
<li>결과적으로 <strong>초기 time step의 정보에 대해 학습이 거의 되지 않는 현상</strong>이 발생합니다.</li>
</ul>
<blockquote>
<p>👉 이로 인해 긴 문장을 처리할 때, 문장의 앞부분을 제대로 기억하지 못함</p>
</blockquote>
<h4 id="🔹-2-기억력-소실-memory-decay">🔹 (2) 기억력 소실 (Memory Decay)</h4>
<ul>
<li>hidden state만을 사용하여 정보를 전달하기 때문에, <strong>오랜 시간이 지나면 앞 정보는 잊히게 됩니다.</strong></li>
<li>특히 긴 문장에서 초반에 등장한 정보는 끝까지 유지되지 못하고 소실됩니다.</li>
</ul>
<hr>
<p><strong>📉 BPTT(Backpropagation Through Time)</strong>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/b74aaaba-f2ad-4c92-ad71-017c8fcba487/image.png" alt=""></p>
<ul>
<li>RNN은 시간 축을 따라 펼쳐지고, 각 시점의 가중치는 공유됩니다.</li>
<li>역전파가 긴 시퀀스를 따라 갈수록, <strong>기울기 값이 0에 수렴</strong>하면서 학습이 멈춥니다.</li>
</ul>
<hr>
<h3 id="💡해결책-lstm의-등장">💡해결책: LSTM의 등장</h3>
<blockquote>
<p>&quot;기억을 오래 유지할 수 있는 메커니즘이 필요하다!&quot;</p>
</blockquote>
<h4 id="🔑-lstm-long-short-term-memory">🔑 LSTM (Long Short-Term Memory)</h4>
<ul>
<li>LSTM은 RNN의 구조를 확장하여, <strong>장기 기억(long-term dependency)</strong>을 유지할 수 있도록 설계된 모델입니다.</li>
<li>핵심은 <strong>셀 상태(cell state)</strong>와 <strong>게이트(gate)</strong> 구조를 도입한 것!
<img src="https://velog.velcdn.com/images/jiyun_kang/post/cd289bc6-de21-4138-8337-607492b90715/image.png" alt=""></li>
</ul>
<h5 id="🧠-주요-아이디어">🧠 주요 아이디어:</h5>
<ul>
<li>정보를 얼마나 기억할지, 얼마나 잊을지 <strong>스스로 판단할 수 있는 구조</strong>를 만듦</li>
<li>셀 상태는 각 time step을 거치며 정보가 <strong>선형적으로 흐르게 되어</strong>, 정보가 소실되지 않음</li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th>Simple RNN</th>
<th>LSTM</th>
</tr>
</thead>
<tbody><tr>
<td>기울기 소실</td>
<td>심각하게 발생</td>
<td>거의 발생하지 않음</td>
</tr>
<tr>
<td>기억 유지</td>
<td>짧은 구간만 가능</td>
<td>긴 구간까지 가능</td>
</tr>
<tr>
<td>구조</td>
<td>단순 (1개의 hidden state)</td>
<td>게이트 + 셀 상태 도입</td>
</tr>
<tr>
<td>주 사용처</td>
<td>짧은 시퀀스, 기초 실습</td>
<td>실무 모델 대부분 사용</td>
</tr>
</tbody></table>
<hr>
<h2 id="lstm의-구조">LSTM의 구조</h2>
<ul>
<li>Simple RNN과 다르게 두개의 이전 time step까지의 처리결과를 사용한다.<ul>
<li><strong>Cell State</strong><ul>
<li>Long term memory 로 전체 step에 대한 누적 기억값</li>
</ul>
</li>
<li><strong>Hidden State</strong><ul>
<li>Short term memory 로 이전 sequence 에 대한 기억값
<img src="https://velog.velcdn.com/images/jiyun_kang/post/b43919f8-55d2-497e-9cc9-ae70faac6709/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="🧠-기억을-선택하는-게이트-구조">🧠 기억을 선택하는 게이트 구조</h3>
<p>LSTM은 내부에 다음 4가지 요소가 있습니다:</p>
<ul>
<li><strong>Forget Gate</strong>: 이전 정보를 얼마나 지울지 결정</li>
<li><strong>Input Gate</strong>: 새로운 정보를 얼마나 저장할지 결정</li>
<li><strong>Cell State</strong>: 장기 기억(기억 흐름)</li>
<li><strong>Output Gate</strong>: 현재 출력을 얼마나 보낼지 결정</li>
</ul>
<hr>
<h3 id="🧩-gate-연산-하나씩-살펴보기">🧩 Gate 연산 하나씩 살펴보기</h3>
<h4 id="1️⃣-forget-gate--잊을-것-결정">1️⃣ Forget Gate – <strong>잊을 것 결정</strong></h4>
<p>$$
f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + b_f)
$$
<img src="https://velog.velcdn.com/images/jiyun_kang/post/0d37f378-dd41-450c-a508-796344698776/image.jpg" alt=""></p>
<ul>
<li>$f_t$: Forget 게이트 출력 (0~1)</li>
<li>0에 가까우면 삭제, 1에 가까우면 유지</li>
<li>이전 셀 상태와 곱하여 <strong>기억할 정보만 유지</strong></li>
</ul>
<hr>
<h4 id="2️⃣-input-gate--새로-기억할-것-결정">2️⃣ Input Gate – <strong>새로 기억할 것 결정</strong></h4>
<p>$$
i_t = \sigma(W_i \cdot [h_{t-1}, x_t] + b_i)
$$</p>
<p>$$
\tilde{C}<em>t = \tanh(W_C \cdot [h</em>{t-1}, x_t] + b_C)
$$
<img src="https://velog.velcdn.com/images/jiyun_kang/post/e7aff842-06ba-4f71-8122-5cc9fbe76ce2/image.jpg" alt=""></p>
<ul>
<li>$i_t$: 저장 비율</li>
<li>$\tilde{C}_t$: 새로 기억하고 싶은 정보</li>
<li>$i_t \times \tilde{C}_t$: 셀 상태에 추가할 정보</li>
</ul>
<hr>
<h4 id="3️⃣-cell-state-업데이트--장기-기억-유지">3️⃣ Cell State 업데이트 – <strong>장기 기억 유지</strong></h4>
<p>$$
C_t = f_t \cdot C_{t-1} + i_t \cdot \tilde{C}_t
$$
<img src="https://velog.velcdn.com/images/jiyun_kang/post/51f6df28-116c-424b-aabf-b9001d4ede6f/image.jpg" alt=""></p>
<ul>
<li>이전 셀 상태와 새로운 기억을 <strong>덧셈과 곱셈만으로</strong> 업데이트</li>
<li><strong>가중치 곱 없이 정보가 전달되므로</strong>, 장기 기억 유지에 적합</li>
</ul>
<hr>
<h3 id="4️⃣-output-gate--현재-출력-결정">4️⃣ Output Gate – <strong>현재 출력 결정</strong></h3>
<p>$$
o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + b_o)
$$</p>
<p>$$
h_t = o_t \cdot \tanh(C_t)
$$
<img src="https://velog.velcdn.com/images/jiyun_kang/post/5e574b50-815a-46bb-aba8-d69906e88711/image.jpg" alt=""></p>
<ul>
<li>$o_t$: 출력 게이트</li>
<li>$h_t$: 현재 시점의 hidden state (= 출력값)</li>
</ul>
<hr>
<h2 id="🔁-lstm-전체-흐름-요약">🔁 LSTM 전체 흐름 요약</h2>
<table>
<thead>
<tr>
<th>게이트</th>
<th>계산식</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Forget Gate</td>
<td>$$f_t = \sigma(W_f[h_{t-1}, x_t] + b_f)$$</td>
<td>무엇을 잊을지</td>
</tr>
<tr>
<td>Input Gate</td>
<td>$$i_t = \sigma(W_i[h_{t-1}, x_t] + b_i)$$<br> $$\tilde{C}<em>t = \tanh(W_C[h</em>{t-1}, x_t] + b_C)$$</td>
<td>무엇을 기억할지</td>
</tr>
<tr>
<td>Cell State</td>
<td>$$C_t = f_t \cdot C_{t-1} + i_t \cdot \tilde{C}_t$$</td>
<td>장기 기억 업데이트</td>
</tr>
<tr>
<td>Output Gate</td>
<td>$$o_t = \sigma(W_o[h_{t-1}, x_t] + b_o)$$ <br> $$h_t = o_t \cdot \tanh(C_t)$$</td>
<td>현재 출력 결정</td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-gate-정리">✅ Gate 정리</h2>
<ul>
<li>모든 Gate는 <strong>입력 ( x_t )</strong>와 <strong>이전 hidden state ( h_{t-1} )</strong>를 입력으로 받음</li>
<li>각 Gate에는 고유한 <strong>가중치 ( W )</strong>와 <strong>바이어스 ( b )</strong>가 존재</li>
<li><strong>Sigmoid</strong>는 0~1 사이의 값을 출력하여 정보 통과 여부 결정</li>
<li><strong>tanh</strong>는 -1~1 사이의 값을 출력하여 정보를 조절함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[기억을 갖는 신경망 모델 RNN]]></title>
            <link>https://velog.io/@jiyun_kang/%EA%B8%B0%EC%96%B5%EC%9D%84-%EA%B0%96%EB%8A%94-%EC%8B%A0%EA%B2%BD%EB%A7%9D-%EB%AA%A8%EB%8D%B8-RNN</link>
            <guid>https://velog.io/@jiyun_kang/%EA%B8%B0%EC%96%B5%EC%9D%84-%EA%B0%96%EB%8A%94-%EC%8B%A0%EA%B2%BD%EB%A7%9D-%EB%AA%A8%EB%8D%B8-RNN</guid>
            <pubDate>Mon, 02 Jun 2025 15:55:45 GMT</pubDate>
            <description><![CDATA[<h2 id="rnn-recurrent-neural-network">RNN (Recurrent Neural Network)</h2>
<p>:  Sequential data(순서가 있는 데이터) 의 특성을 추출하는데 좋은 성능을 보이는 Recurrent Layer를 Feature Extractor로 사용하는 딥러닝 모델.</p>
<h3 id="🧱-recurrent-layer-구조">🧱 Recurrent Layer 구조</h3>
<p> RNN은 순서대로 입력되는 데이터를 반복 처리하는  <strong>Recurrent Layer</strong>를 이용해 <strong>순서를 감안해서 Feature vector를 추출하고</strong> 그 Feature vector를 Estimator Layer에 전달해 추론한다. (이건 뒤에서 더 자세하게 다룰 예정)</p>
<p>💡  즉, 순서를 감안한다는 것은 이전 순서의 내용을 기억한다는 것, 즉 memory system이 존재한다!
<img src="https://velog.velcdn.com/images/jiyun_kang/post/f0dc3d55-8928-40a0-9004-e60f0cdc3803/image.png" alt=""></p>
<p>🧱 1. RNN은 어떤 구조인가요?
<strong>기본적으로는 선형 레이어(Linear Layer)</strong>처럼 생겼지만, 거기에 “자기 자신을 반복해서 참조하는 구조”가 들어간 것이 RNN입니다.</p>
<p>이걸 우리는 “재귀 순환 구조”라고 부릅니다.</p>
<p>아래 그림으로 함께 설명해보겠습니닷 ! </p>
<blockquote>
<p><strong>구조 1. 순차 구조를 인식하면 콘텍스트를 기억하는 순환 신경망</strong></p>
</blockquote>
<p>순환 신경망은 데이터의 순차 구조를 인식하기 위해 데이터를 시간 순서대로 하나씩 입력받는다. 그리고 순서대로 입력받은 데이터의 콘텍스트를 만들기 위해 은닉 계층에 피드백 연결을 가진다. 
왼쪽 그림은 은닉 계층에 피드백 연결을 갖는 모델 구조이고, 오른쪽 그림은 모델이 데이터를 처리하는 과정을 시간 순서에 따라 펼쳐서 보여준다.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/057e835d-8b09-40ce-a14a-ba1010f84565/image.png" alt=""></p>
<blockquote>
<p>그럼 오른쪽 그림을 더 확대해서 보자!
일단 시간 순서에 따라 하나씩 입력을 받으니까, 아래 네모칸 하나를 하나의 time step이라고 보면 된다. <br>
그럼 하나의 네모 칸 안에서는 무슨일이 일어나냐면!
<img src="https://velog.velcdn.com/images/jiyun_kang/post/166a67ea-ba96-4b8b-849f-76cb8154b5ad/image.png" alt="">
일단, 각 시점(time step)에서 다음 두 가지를 입력으로 받아요:</p>
</blockquote>
<ol>
<li><strong>현재 시점의 입력 𝑋𝑡</strong> 예: 지금 단어, 지금 시점의 주가, 현재 시계열 값 등</li>
<li><strong>이전 시점까지의 결과 ℎ𝑡−1</strong> 예: 그 전에 들었던 단어들의 의미를 담고 있는 기억 (ℎ𝑡−1는 hidden state, 즉 “이전 기억” 이라고 불러요.)</li>
</ol>
<p>*<em>🧠 Hidden State란? =기억 *</em> 
지금까지 들어온 입력들의 내용을 종합해서 요약한 벡터!</p>
<blockquote>
<p>⚙️ 계산식은 다음과 같다.
$$
h_t = \tanh(W_{xh} \cdot X_t + W_{hh} \cdot h_{t-1} + b_t)
$$</p>
</blockquote>
<ul>
<li>$W_{xh}$: <strong>현재 입력</strong> $X_t$에 곱해지는 가중치 행렬  </li>
<li>$W_{hh}$: <strong>이전 hidden state</strong> $h_{t-1}$에 곱해지는 가중치 행렬  </li>
<li>$b_t$: <strong>바이어스(bias)</strong>  </li>
<li>$tanh$: <strong>비선형 활성화 함수</strong>, 결과를 -1에서 1 사이로 압축함</li>
</ul>
<hr>
<p>즉, <strong>현재 입력과 이전의 기억을 합쳐서</strong>, 새로운 출력을 만들어내는 구조입니다.
이렇게 계산된 $h_t$는 <strong>현재 시점의 출력값이자 다음 시점의 입력값</strong>으로 사용됩니다.</p>
<p>그럼 여기서 궁금한 게 생길 수 있는데,,,
💡 $W_{xh}$랑 $W_{hh}$의 가중치는 어떤 차이가 있는걸까?</p>
<p>RNN은 모든 time step에서 $W_{xh}$랑 $W_{hh}$ 같은 가중치를 사용한다. 
따라서, 시간 순서가 길어져도 모델의 복잡도는 늘어나지 않음 (공유된 파라미터 덕분에)</p>
<blockquote>
<p><strong>2. 🔄 Bidirectional RNN (양방향 순환 신경망)</strong></p>
</blockquote>
<p>✅ 기본 아이디어
&quot;문장의 앞도 중요하지만, 뒤의 문맥도 함께 고려하면 더 정확한 이해가 가능하다!&quot;</p>
<p>일반 RNN은 앞에서 뒤로 한 방향으로만 데이터를 처리합니다. 하지만 어떤 단어의 의미는 앞뒤 문맥을 모두 봐야 정확히 알 수 있죠?</p>
<p>예를 들어:</p>
<p>“나는 배를 탔다.” → 여기서 &#39;배&#39;는 타는 배</p>
<p>“나는 배를 먹었다.” → 여기선 과일 배</p>
<p>이럴 때 뒤에 오는 단어(&#39;탔다&#39; or &#39;먹었다&#39;)가 &#39;배&#39;의 의미를 결정합니다.
👉 그래서 뒤 문맥도 파악할 수 있도록 만든 구조가 바로 Bidirectional RNN입니다.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/702fac84-a72f-449e-946c-3fee11a5c8d0/image.png" alt="">
🔹 아래쪽 (진한 파랑) :일반적인 정방향 RNN
왼쪽에서 오른쪽으로 순서대로 진행 (과거 → 현재 → 미래)
🔹 위쪽 (연한 파랑) 역방향 RNN
오른쪽에서 왼쪽으로 진행 (미래 → 현재 → 과거)
🔹 위의 동그라미 두 방향에서 계산된 정보를 <strong>합쳐서 예측 결과</strong>를 만듭니다. 예: 텍스트 분류 결과, 번역 결과, 품사 태깅 등</p>
<p>✅ 언제 Bidirectional RNN을 쓰나?
문맥 전체를 알고 있는 경우, 즉 입력 전체가 고정되어 있을 때 사용하면 좋습니다. 예: 감정 분석, 품사 태깅, 번역 등
특히 <strong>입력과 출력 길이가 같은 구조(Many-to-Many)</strong>에서 매우 효과적이에요.</p>
<p>⚠️ 사용 시 주의: Auto Regressive 모델에는 ❌
❓ Auto Regressive란? 이전 출력값을 다음 입력으로 사용하는 구조 (예: GPT, 텍스트 생성 모델)</p>
<p>❌ 왜 Bidirectional 안 되는가?
미래 정보를 보면서 현재를 예측하면, 텍스트 &quot;생성&quot;이 아니라 &quot;복귀&quot;가 됨
GPT처럼 단어를 하나씩 이어서 생성하는 경우는 단방향만 가능</p>
<blockquote>
<ol start="3">
<li><strong>🏗️ Stacking (Multi-Layer) RNN 구조</strong>
: &quot;하나의 RNN 층으로 부족할 때, 여러 개의 RNN 층을 층층이 쌓아서 더 복잡한 표현을 학습하는 구조&quot;</li>
</ol>
</blockquote>
<p>✅ <strong>1. 왜 여러 층을 쌓을까?</strong>
단일 RNN Layer는 입력 시퀀스를 시간적으로 요약하는 데는 유용하지만, 복잡한 문맥이나 고차원 패턴을 파악하는 데는 한계가 있어요.</p>
<p>그래서 여러 층을 쌓아서 표현 능력(representational capacity)을 강화합니다!</p>
<p>🧱 <strong>2. 구조는 어떻게 생겼을까?</strong>
첫 번째 RNN Layer는 <strong>입력 시퀀스</strong>를 받아서 각 time step마다 hidden state들을 출력합니다.
👉 예: [30, 100, 4] → [30, 100, 256] (batch, sequence, hidden size)</p>
<p>그 다음 Layer는 이 hidden state 시퀀스를 새로운 입력으로 받아 다시 RNN 연산을 수행합니다.</p>
<p>즉, 앞 층의 모든 time step별 출력값을 다음 층이 입력으로 받는다.</p>
<h3 id="🧠-pytorch-rnn-layer-구조">🧠 PyTorch RNN Layer 구조</h3>
<p>PyTorch의 nn.RNN은 순차 데이터(예: 텍스트, 시계열 등)를 처리할 수 있는 기본 RNN 레이어
| 파라미터 이름         | 설명                                                                      |
| --------------- | --------------------------------------------------------------------------------------------------------------------- |
| <code>input_size</code>    | 각 time step에서 입력되는 feature 수                                                                                          |
| <code>hidden_size</code>   | RNN 내부에서 계산되는 hidden state의 차원                                                                                        |
| <code>num_layers</code>    | RNN 레이어를 몇 층 쌓을지                                                                                                      |
| <code>nonlinearity</code>  | 활성 함수 (기본: <code>&#39;tanh&#39;</code>, 선택: <code>&#39;relu&#39;</code>)                                                                                    |
| <code>batch_first</code>   | 입력 텐서의 shape 순서 지정<br>(기본: <code>(seq_len, batch_size, input_size)</code> → <code>True</code>로 설정하면 <code>(batch_size, seq_len, input_size)</code>가 됨) |
| <code>dropout</code>       | 레이어 간 dropout 비율                                                                                                      |
| <code>bidirectional</code> | 양방향 RNN 여부                                                                                                            |</p>
<p><strong>📥 Input Tensor Shape</strong></p>
<ul>
<li>입력값 두개 : Input_Data, Hidden_state</li>
</ul>
<p><strong>✅ 기본 구조</strong>
<img src="https://velog.velcdn.com/images/jiyun_kang/post/9fec1aa7-62a4-440e-aee4-d8fb095b318a/image.png" alt=""></p>
<p><code>input_data</code>의 shape:</p>
<p>$$
(\text{sequence length},\ \text{batch size},\ \text{feature size})
$$</p>
<p>*<em>✨ 예시
*</em></p>
<ul>
<li><strong>feature</strong>: 4 (시가, 종가, 최고가, 최저가)</li>
<li><strong>sequence length</strong>: 100일치</li>
<li><strong>batch size</strong>: 30개</li>
</ul>
<p>따라서 입력 텐서의 shape은:</p>
<p>$$
(100,\ 30,\ 4)
$$</p>
<blockquote>
<p>단, <code>batch_first=True</code>로 설정하면:
$$
(100,\ 30,\ 4)
$$</p>
</blockquote>
<hr>
<p><strong>💾 3. Hidden State 입력</strong></p>
<p>초기 hidden state는 생략하면 자동으로 0으로 초기화됩니다.</p>
<h4 id="✅-shape">✅ shape</h4>
<p>$$
(D \times \text{num_layers},\ \text{batch size},\ \text{hidden size})
$$</p>
<ul>
<li>( D = 2 ) (양방향), ( D = 1 ) (단방향)</li>
<li><code>num_layers</code>: RNN이 몇 층인지</li>
</ul>
<hr>
<p><strong>📤Output: RNN의 반환값</strong></p>
<pre><code class="language-python">output, hidden_state = rnn(input, h_0)</code></pre>
<p><strong>✅ <code>output</code>의 shape</strong></p>
<ul>
<li><p>전체 time step의 출력값을 담고 있음<br><img src="https://velog.velcdn.com/images/jiyun_kang/post/8f7a714e-c2e1-4e54-aeae-c81a5336ac91/image.png" alt=""></p>
</li>
<li><p>Shape:</p>
</li>
</ul>
<p>$$
(\text{sequence length},\ \text{batch size},\ \text{hidden size} \times D)
$$</p>
<ul>
<li>$D = 2$: <strong>양방향 RNN</strong> (<code>bidirectional=True</code>)  </li>
<li>$D = 1$: <strong>단방향 RNN</strong> (기본값)</li>
</ul>
<blockquote>
<p>📌 Many-to-Many 구조에서 사용됨 (모든 시점의 출력이 필요할 때)</p>
</blockquote>
<hr>
<p><strong>✅ <code>hidden_state</code>의 shape</strong></p>
<ul>
<li>마지막 time step에서 <strong>각 레이어의 출력값</strong>이 모여 있는 텐서  </li>
<li>Shape:</li>
</ul>
<p>$$
(D \times \text{num_layers},\ \text{batch size},\ \text{hidden size})
$$</p>
<ul>
<li>$D = 2$: 양방향  </li>
<li>$D = 1$: 단방향  </li>
<li><code>num_layers</code>: RNN이 몇 층으로 구성되어 있는지</li>
</ul>
<blockquote>
<p>📌 Many-to-One 구조에서 사용됨 (마지막 출력만 사용할 때)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Seq2Seq(1) - Encoder&Decoder]]></title>
            <link>https://velog.io/@jiyun_kang/Seq2Seq1-EncoderDecoder</link>
            <guid>https://velog.io/@jiyun_kang/Seq2Seq1-EncoderDecoder</guid>
            <pubDate>Thu, 29 May 2025 00:39:12 GMT</pubDate>
            <description><![CDATA[<h2 id="seq2seq">Seq2Seq</h2>
<ul>
<li>Encoder-Decoder 구조를 RNN 계열에 적용한 모델.</li>
<li>Encoder: 입력 Sequence의 전체 의미(특징)을 표현하는 <strong>context vector</strong>를 출력</li>
<li>Decoder: Encoder가 출력한 Context Vector를 입력받아 결과 sequence를 생성</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/436cb3e9-8566-408a-87ba-d37127cd4c23/image.png" alt=""></p>
<h3 id="seq2seq-모델-정의">Seq2Seq 모델 정의</h3>
<ul>
<li><p>Seq2Seq 모델은 Encoder와 Decoder의 입력 Sequence의 <strong>길이</strong>와 <strong>순서</strong>가 자유롭기 때문에 챗봇이나 번역에 이상적인 구조다.</p>
<ul>
<li>단일 RNN은 각 timestep 마다 입력과 출력이 있기 때문에 입/출력 sequence의 개수가 같아야 한다.(ex. many to many)</li>
<li>Seq2Seq는 <strong>입력처리(질문,번역대상)처리 RNN과 출력 처리(답변, 번역결과) RNN 을 각각 만들고 그 둘을 연결한 형태이기 때문에, 
길이가 다르더라도 상관없다.</strong></li>
</ul>
<h4 id="encoder-bidirectional-context-vector">Encoder (bidirectional, context vector)</h4>
<ul>
<li>Encoder class 구현하기<ul>
<li>nn.Module을 상속받아서 <strong>init</strong>() 메소드로 초기화 작업을 진행<ul>
<li>forward(self, X)를 사용해서 계산식을 정의.
<img src="https://velog.velcdn.com/images/jiyun_kang/post/489cff0a-9103-4c1e-a590-3fa04f459421/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>class Encoder(nn.Module):

  def __init__(self, vocab_size, embedding_dim, 
              hidden_size, bidirectional=True, num_layers=1, dropout_rate=0.0):  #bidirectional : 양방향성.  문장 token에 대해서 왼쪽에서 뽑아낸거랑 오른른쪽에서 시작해서 뽑아낸거 둘다 고려.
      super().__init__()
      # Encoder는 context vector(문장의 feature)를 생성하는 것이 목적 (분류기는 생성안함.) 여기서는 그냥 특징을 decoder에 주는 역할
      # Embedding Layer, GRU Layer를 생성.
      self.vocab_size = vocab_size # 어휘사전의 총 어휘수(토큰수)
      # 임베딩레이어
      self.embedding = nn.Embedding(
          vocab_size,    # 총 어휘개수 (weight 행렬의 행)
          embedding_dim,   # embedding vector 차원수. (Weight 행렬의 열 수) weight 행렬의 shape: [vocab_size, embedding_dim]
          padding_idx=0   # [PAD]  (패딩 토큰의 ID) - padding의 embedding vector는 학습이 안되도록 한다.(vector값이 0으로 구성)
      )
      # GRU
      self.gru = nn.GRU(
          embedding_dim, # 개별 토큰(time step)의 크기(feature 수).
          hidden_size=hidden_size,   # hidden state의 크기- 개별 토큰 별로 몇개의 feature를 추출할지. 
          num_layers=num_layers,
          bidirectional=bidirectional,
          dropout=dropout_rate if num_layers &gt; 1 else 0.0  # stacked rnn일 경우(layer가 여러개일 경우), dropout 적용.  
      )

  def forward(self, X):   # 계산
      # X shape: (batch, seq_len) 토큰값 하나씩
      X = self.embedding(X) # (batch, seq_len, embedding_dim)
      X = X.transpose(1, 0) # (seq_len, batch, embedding_dim)
      out, hidden = self.gru(X)
      return out, hidden</code></pre><h4 id="decoder-bidirectional-x-다중-분류">Decoder (bidirectional X, 다중 분류)</h4>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/1eddc33d-09f4-4c01-8045-4e8f2537e694/image.png" alt=""></p>
<pre><code>class Decoder(nn.Module):
  # auto regressive RNN 모델은 단방향만 가능
  def __init__(self, vocab_size, embedding_dim, 
               hidden_size, num_layers=1, bidirectional=False, dropout_rate=0.0):  #여기서는 역방향을 할 수 없음. 앞에서 생성이 되어야 하는데, 뒤로 하면 생성 안됨. 
      super().__init__()
      self.vocab_size = vocab_size # 총 어휘사전 토큰 개수.
      # embedding layer
      self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)
      # GRU 
      ## Auto Regressive RNN은 단방향만 가능. 
      self.gru = nn.GRU(embedding_dim, hidden_size, 
                        num_layers=num_layers, dropout=dropout_rate if num_layers &gt; 1 else 0.0)
      # Dropout layer (feature랑 분류기 사이에 dropout layer 넣기)
      self.dropout = nn.Dropout(dropout_rate)
      # 분류기 (다음 단어(토큰)를 추론)
         # - 다중분류(단어사전의 단어들의 다음 단어일 확를)
      self.lr = nn.Linear(
          hidden_size,  # GRU 출력 값 중 마지막 hidden state값을 입력으로 받음.  # ev -&gt; --- -&gt; hidden state -&gt; linnear에 넣기
          vocab_size)    # 출력: 다음 단어일 확률

  def forward(self, X, hidden):
      # X: torch.LongTensor: shape - [batch] : 한 단어씩 입력을 받음.
      # hidden: torch.FloatTensor: shape - [1, batch, hidden_size] (이전까지의 특성)   # sequence_length는 1이 됨 (단어가 한개)

      X = X.unsqueeze(1) # seq_len 축을 추가. [batch] -&gt; [batch, 1] (Embedding Layer의 input shape)  1: sequance_length
      X = self.embedding(X) # [batch, 1, embedding 차원]
      X = X.transpose(1, 0) # [1, batch, embedding 차원]  #seq_len이랑 batch 축 바꿈. 

      out, hidden = self.gru(X, hidden)
      last_out = out[-1] # out: 전체 hidden state값-&gt; 마지막 hidden state을 추출  # 근데 어차피 seq_len이 1이니까 한개임.
      self.dropout(last_out)   # 과적합을 막아주기 위해서 dropout 진행
      last_out = self.lr(last_out)

      #last_out : 어휘 사전의 단어들에 대해 다음 단어일 확률. 
      return last_out, hidden # (hidden: 다음 timestep에 전달.)  # hidden도 같이 다음 꺼에 넣어야 함,</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[플레이데이터 SK네트웍스 Family AI캠프 13기] 5주차 회고]]></title>
            <link>https://velog.io/@jiyun_kang/%ED%94%8C%EB%A0%88%EC%9D%B4%EB%8D%B0%EC%9D%B4%ED%84%B0-SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI%EC%BA%A0%ED%94%84-13%EA%B8%B0-5%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jiyun_kang/%ED%94%8C%EB%A0%88%EC%9D%B4%EB%8D%B0%EC%9D%B4%ED%84%B0-SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI%EC%BA%A0%ED%94%84-13%EA%B8%B0-5%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 08 May 2025 02:15:37 GMT</pubDate>
            <description><![CDATA[<h3 id="👣-딥러닝-pytorch">👣 딥러닝 Pytorch</h3>
<p>순식간에 머신러닝을 다 배우고, 지난 금요일에 딥러닝을 나가기 시작했다. 그래도 머신러닝에서 배웠던 선형회귀 모델과 loss 함수 등을 배워서 이해하는데 큰 어려움이 있진 않았다. 예측 모델을 만드는 과정은 생각보다 정해져 있다보니, 어떻게 오차를 줄일 것인가, 혹은 과적합이 피할 수 있는가에 대해 초점을 맞춘다면 좋은 성능의 모델을 만들어갈 수 있다.</p>
<p>📚 <strong>회고 구성</strong></p>
<p>1️⃣ LEARN </p>
<p><em>1)  지난 일주일 동안 가장 인상 깊었던 배움</em></p>
<p><em>2) 그 배움까지 다가가는데 어떤 어려움이 있었는지</em></p>
<p>2️⃣FINDINGS &amp; FEELINGS</p>
<p><em>:  그 과정에서 나는 무엇을 깨달았고, 어떤 감정/생각이 들었는지?</em> </p>
<p>3️⃣ RESULT</p>
<p><em>: 결과적으로, 현재 나의 상태는?</em></p>
<p>4️⃣ HOW  TO SOLVE</p>
<p><em>: 이 상태에서 다음 일주일을 더 잘 보내려면 어떻게 해야 할까?</em></p>
<h2 id="1️⃣-learn">1️⃣ LEARN</h2>
<blockquote>
<p><em>지난 일주일동안 가장 인상 깊었던 배움</em></p>
</blockquote>
<p>이번 주는 사실 저번주보다 훨씬 더 많은 진도를 나갔다보니 과부하도 있었지만, 최대한 머리 속에 넣으려고 노력했다.</p>
<p>💡<strong>그 중에서도 가장 인상적인 부분을 정리하자면…</strong> </p>
<ol>
<li>자동미분(requires_grad = True) - weight &amp; bias</li>
<li>다중입력 - 다중출력</li>
<li>Optimizer &amp; loss함수 </li>
</ol>
<h4 id="1-자동미분requires_grad--true">1. 자동미분(requires_grad = True)</h4>
<ul>
<li>자동 미분을 이용해 gradient(미분계수)를 계산하는 pytorch system.</li>
<li>딥러닝 모델에서 weight와 bias tensor들(Parameter)은 backpropagation(역전파)를 이용해 gradient를 구해서 loss가 줄어드는 방향으로 update를 하게된다.</li>
<li>pytorch는 이런 미분 수행을 자동으로 처리해 준다.<ul>
<li>gradient(기울기)를 구한다는 것은 미분을 한다는 것을 말한다.</li>
</ul>
</li>
<li>tensor가 미분 가능하려면(gradient 계산 대상 변수) <code>requires_grad=True</code> 로 설정되어 있어야 한다. (default: False)</li>
</ul>
<p>이걸 이용해서 간단한 경사하강법을 구현해보면,,,</p>
<pre><code># 간단한 경사하강법 구현
input_data = torch.tensor([30.2])
output_data = torch.tensor([10.5])
weight = torch.tensor([0.2], requires_grad=True)
y_pred = weight * input_data
loss = (output_data - y_pred)**2

loss.backward()  #loss를 백워드하면, y_pred의 도함수를 먼저 계산한 다음에 loss의 도함수를 계산해서 합성함수(즉, 두 기울기를 곱한 값)로 계산
# loss = (output_data - (weight*input_data))**2
weight.grad

new_weight = weight.data - weight.grad * 0.0001  
new_weight</code></pre><h4 id="2-다중입력---다중출력">2. 다중입력 - 다중출력</h4>
<ul>
<li>다중입력: Feature가 여러개인 경우</li>
<li>다중출력: Output 결과가 여러개인 경우</li>
</ul>
<table>
<thead>
<tr>
<th>온도(F)</th>
<th>강수량(mm)</th>
<th>습도(%)</th>
<th align="right">사과생산량(ton)</th>
<th align="right">오렌지생산량</th>
</tr>
</thead>
<tbody><tr>
<td>73</td>
<td>67</td>
<td>43</td>
<td align="right">56</td>
<td align="right">70</td>
</tr>
<tr>
<td>91</td>
<td>88</td>
<td>64</td>
<td align="right">81</td>
<td align="right">101</td>
</tr>
<tr>
<td>87</td>
<td>134</td>
<td>58</td>
<td align="right">119</td>
<td align="right">133</td>
</tr>
<tr>
<td>102</td>
<td>43</td>
<td>37</td>
<td align="right">22</td>
<td align="right">37</td>
</tr>
<tr>
<td>69</td>
<td>96</td>
<td>70</td>
<td align="right">103</td>
<td align="right">119</td>
</tr>
</tbody></table>
<pre><code>사과수확량  = w11 * 온도 + w12 * 강수량 + w13 * 습도 + b1
오렌지수확량 = w21 * 온도 + w22 * 강수량 + w23 *습도 + b2</code></pre><center>
weight와 bias를 구성하는 shape는 몇일까?
 <br> 

<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/0b9ad24d-b0e3-4e1c-95ad-fa21e7a8e881/image.png" alt=""></p>
<p>우선 weight를 보면 feature의 개수인 &quot;온도, 강수량, 습도&quot;와 이에 해당되는 예측할 값의 개수인 &quot;사과수확량과 오렌지 수확량&quot;니까 (3,2)가 된다.
다음으로 bias는 각 예측할 값에 대한 bias만 필요하니깐 (2, )의 shape가 필요하다. </center></p>
<h4 id="3-optimizer--loss-함수">3. Optimizer &amp; Loss 함수</h4>
<ul>
<li><strong>Optimizer</strong>: 계산된 gradient값을 이용해 파라미터들을 업데이트 하는 함수</li>
<li><strong>Loss 함수</strong>: 정답과 모델이 예측한 값사이의 차이(오차)를 계산하는 함수.<ul>
<li>모델을 최적화하는 것은 이 함수의 값을 최소화하는 것을 말한다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiyun_kang/post/2ad4703d-99be-44b3-8a87-8684265c0c45/image.png" alt=""></p>
<h2 id="2️⃣findings--feelings">2️⃣FINDINGS &amp; FEELINGS</h2>
<p>:  그 과정에서 나는 무엇을 깨달았고, 어떤 감정/생각이 들었는지? </p>
<p>저번주 금요일부터 딥러닝 파트로 넘어가면서, 난이도가 꽤나 올라가게 되었다. 그래도 가장 인상적인 부분이라고 한다면, 머신러닝과 다르게 딥러닝에서는 feature 자체의 특성을 학습해서 최종적인 feature를 줄여나가면서 주요 특성을 뽑아낸다는 점이다.</p>
<h2 id="3️⃣-result">3️⃣ RESULT</h2>
<blockquote>
<p><em>: 결과적으로, 현재 나의 상태는?</em></p>
</blockquote>
<p>머신러닝을 다루면서 꽤나 방대한 양을 빠르게 나가다보니, 놓치는 부분이 생길 때도 있었던 것 같다.그래도 딥러닝 부분과 머신러닝 부분이 어느정도 겹쳐있는 내용이 있어서 이해하는데 큰 어려움은 없었던 것 같다. </p>
<h2 id="4️⃣-how--to-solve">4️⃣ HOW  TO SOLVE</h2>
<blockquote>
<p><em>: 이 상태에서 다음 일주일을 더 잘 보내려면 어떻게 해야 할까?</em></p>
</blockquote>
<p>딥러닝에는 다양한 개념들이 많이 나와서, 개념별로 따로 정리해둘 필요가 있다.
이번주에는 딥러닝 모델 별로 정리한 내용을 올려볼까 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[플레이데이터 SK네트웍스 Family AI캠프 13기] 3주차 회고]]></title>
            <link>https://velog.io/@jiyun_kang/%ED%94%8C%EB%A0%88%EC%9D%B4%EB%8D%B0%EC%9D%B4%ED%84%B0-SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI%EC%BA%A0%ED%94%84-13%EA%B8%B0-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jiyun_kang/%ED%94%8C%EB%A0%88%EC%9D%B4%EB%8D%B0%EC%9D%B4%ED%84%B0-SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI%EC%BA%A0%ED%94%84-13%EA%B8%B0-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 15 Apr 2025 02:10:40 GMT</pubDate>
            <description><![CDATA[<h3 id="👣-봄과-함께-맞이한-첫-프로젝트-주간">👣 봄과 함께 맞이한 첫 프로젝트 주간</h3>
<p>이번 주간은 드디어 벚꽃이 만개함과 동시에 첫 프로젝트를 시작하게 되었다. 비전공자로서 팀에서 최소한 1만큼은 하고 싶다는 마인드로 최선을 다하고 싶었다. 아무래도 이제 막 파이썬과 SQL을 공부했던 터라 지금까지 배운 것들을 한번에 적용해서 프로젝트를 만들어낼 수 있을까하는 걱정이 먼저 앞섰다. 그래도 가장 감사했던 것은 혼자서는 해결할 수 없었던 부분들은 주변 팀원분들이 문제해결사처럼 도와주셔서 문제의 늪에서 빠져나올 수 있도록 도와주셨다. 물론 혼자서 고군분투했던 시간들도 있었지만, 그 시간들마저 하나도 아깝지 않고 값진 시간이었다. </p>
<p>📚회고 구성</p>
<p>1️⃣ LEARN </p>
<p><em>1)  지난 일주일 동안 가장 인상 깊었던 배움</em></p>
<p><em>2) 그 배움까지 다가가는데 어떤 어려움이 있었는지</em></p>
<p>2️⃣FINDINGS &amp; FEELINGS</p>
<p><em>:  그 과정에서 나는 무엇을 깨달았고, 어떤 감정/생각이 들었는지?</em> </p>
<p>3️⃣ RESULT</p>
<p><em>: 결과적으로, 현재 나의 상태는?</em></p>
<p>4️⃣ HOW  TO SOLVE</p>
<p><em>: 이 상태에서 다음 일주일을 더 잘 보내려면 어떻게 해야 할까?</em></p>
<h2 id="1️⃣-learn">1️⃣ LEARN</h2>
<blockquote>
<p><em>지난 일주일동안 가장 인상 깊었던 배움</em></p>
</blockquote>
<p>💡그 중에서도 가장 인상적인 부분을 정리하자면… <br></p>
<ol>
<li>Web crawling<br></li>
<li>streamlit<br></li>
</ol>
<ol>
<li>Web crawling</li>
</ol>
<p>이번 프로젝트의 주제는  웹크롤링을 통해 자동차나 자동차 회사와 관련된 데이터를 수집해서 Database에 저장한 후에 그 기반으로 시스템을 구현하는 것이다. 우리 팀은 다양한 주제 후보들이 나왔지만, 그중에 한 팀원이 낸 아이디어 중에 신박하고 새로운 아이디어가 채택되었다. 그 아이디어는 바로 다양한 자동차 회사의 자동차 정보들을 크롤링을 해서 자동차 월드컵 게임을 만들자는 것이다. 그냥 자동차 정보를 보여주면, 식상하기도 하고 단순한 시스템 구현이다보니 보다 사용자들이 재밌게 즐길 수 있는 콘텐츠를 선정하게 되었다.</p>
<p>우리는 그 프로젝트를 실현하기 위해 가장 먼저 10년치 신차 데이터 정보를 사이트에서 크롤링 해와야 했다. 우리가 배웠던 크롤링 방법은 두가지가 있는데 정적 사이트와 동적 사이트에 따라 방법이 달라진다. 그래도 우리가 활용할 사이트는 정적 사이트이다보니, Beautiful soup을 활용했다.  </p>
<p>여기서 인상적인 부분이 있다면, 우리는 거의 3897개의 데이터를 끌어와야해서 데이터의 양이 상당했다. 보통, 그 웹사이트가 랜더링 되는 시간을 기다려야 하기 때문에  <code>time.sleep()</code> 을 걸어주는데 처음엔 3초 정도 걸어주어도 데이터를 크롤링해서 csv에 저장하는데까지 시간이 걸렸다. 그래서 다른 팀원분이 0.1초로 해보라고 말씀해주셔서 바로 해보니 빠르게 데이터를 가져올 수 있었다. </p>
<pre><code class="language-python">for i in range(7576, 4000, -1):
        try:
            url = f&#39;https://www.carisyou.com/car/{i}/Spec&#39;
            response = requests.get(url)
            time.sleep(0.1)
            response.raise_for_status()
            parser = BeautifulSoup(response.text, &#39;lxml&#39;)</code></pre>
<ol start="2">
<li>Streamlit - 자동차 월드컵 게임 구현  </li>
</ol>
<p>다음으로, 이제 가져온 데이터를 자동차 월드컵 게임으로 구현하는 과정이 필요했는데, python 패키지 중 하나인 Streamlit을 활용했다. streamlit은 수업 시간에 배운 내용이 아니다보니, 어떻게 사용해야 하는지 모르는 부분이 많았다. 그치만, 경험이 많으신 팀원분께서 하나하나씩 알려주셔서 도움을 많이 받았다. 그 중에서도 내가 담당했던 파트는 자동차 월드컵 게임 후에 최종으로 선택받은 자동차인 <code>winner_info</code>를 데이터에 저장하고 시각화해서 보여주는 부분까지를 연결하는 코드로 구현해야 했다. 그리고, 그 <code>winner_info</code> 중에 <code>win_log</code> 를 하나씩 추가해서 <code>win_log</code> 에 따라 랭킹을 나타내는 것이 통계 시각화의 주요 목적이었다. 이때 아래와 같이 데이터를 연결할 때, if구문을 활용해서 <code>win_log</code>  를 update함과 동시에 <code>winner_info</code> 를 insert할 수 있었다.</p>
<pre><code class="language-python">with conn.cursor() as cursor:
                    # 1. 기존에 해당 car_id가 있는지 확인
                    select_sql = &quot;SELECT win_log FROM winner_info WHERE car_id = %s&quot;
                    cursor.execute(select_sql, (car.car_id,))
                    existing = cursor.fetchone()

                    if existing:
                        # 2. 이미 있다면 win_log 1 증가시켜 업데이트
                        new_win_log = existing[0] + 1
                        update_sql = &quot;&quot;&quot;
                        UPDATE winner_info
                        SET win_log = %s
                        WHERE car_id = %s
                        &quot;&quot;&quot;
                        cursor.execute(update_sql, (new_win_log, car.car_id))
                    else:
                        # 3. 없다면 새로 insert (win_log = 1)
                        insert_sql = &quot;&quot;&quot;
                        INSERT INTO winner_info (
                            car_id, model, img_url, price, outfit, win_log
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s
                        )
                        &quot;&quot;&quot;</code></pre>
<blockquote>
<p><em>그 배움까지 다가가는데 어떤 어려움이 있었는지</em></p>
</blockquote>
<ol>
<li>web_crawling</li>
</ol>
<p>우리가 수집해야 하는 자동차 정보 중에 가격은 특정 가격이 아니라 range가 있는 가격 정보였다보니,  가격을 몇으로 가져올지도 정해올지도 정해야 했고, 그걸 코드로 어떻게 구현해서 가져올지도 어려움이 있었다. 이때, <code>len()</code> 을 활용해서, 각 가격에 따라 다른  값들을 가져오도록 설정하였다.</p>
<pre><code class="language-python">if len(elements2) == 3 or len(elements2) == 4:
                price_real = elements2[-2].text.strip().replace(&#39;,&#39;, &#39;&#39;) + elements2[-1].text.strip().replace(&#39;,&#39;, &#39;&#39;)
            elif len(elements2) == 2:
                if len(elements2[0].text) &lt;= 2:
                    price_real = elements2[0].text.strip().replace(&#39;,&#39;, &#39;&#39;) + elements2[1].text.strip().replace(&#39;,&#39;, &#39;&#39;)
                else:
                    price_real = elements2[1].text.strip().replace(&#39;,&#39;, &#39;&#39;)
            else:
                price_real = elements2[0].text.strip().replace(&#39;,&#39;, &#39;&#39;)

            가격.append(price_real)</code></pre>
<h2 id="2️⃣findings--feelings">2️⃣FINDINGS &amp; FEELINGS</h2>
<p>:  그 과정에서 나는 무엇을 깨달았고, 어떤 감정/생각이 들었는지? </p>
<p>이번 프로젝트를 하면서, 아직 나는 갈 길이 멀었다는 것을 다시금 깨닫는 시간이 되었다. 어쨌든, 이 교육이 끝나면 실무에 투입되어서 나의 역할을 잘 수행해야 하지만, 지금 상태로는 절대로 불가능하다는 것을 직면했다. 여러 방면에서 잘할 수 있는 사람이 되고 싶지만, 이번 프로젝트에서 나의 역할이라고 한다면 <code>READ.me</code> 파일과 문서를 정리하는 역할이었다. 정리를 하면서 프로젝트 전반적인 부분들을 이해할 수 있엇지만, 아직까지 세밀하게 코드를 구성하는 부분이 많이 약하다는 것을 느끼게 되었다. 이번 프로젝트를 통해, 나의 부족한 부분들을 다시금 직면할 수 있는 계기가 되었다. </p>
<h2 id="3️⃣-result">3️⃣ RESULT</h2>
<blockquote>
<p><em>: 결과적으로, 현재 나의 상태는?</em></p>
</blockquote>
<p>프로젝트 부분 중에서 몇 퍼센트를 이해했는지 묻는다면..한 40%를 이해했던 것 같다. 프로젝트의 전반적인 부분들은 모두 이해하고 있지만 사실상 하나씩 뜯어보면 그 내부적인 코딩 방식이나 DB를 연결하는 부분들은 완벽히 이해하기엔 나의 역량이 너무 부족하다. 2주동안 배운 내용을 단번에 다 적용하면..아마 난 천재가 아닐까... 근데 난 천재가 아니다^^</p>
<h2 id="4️⃣-how--to-solve">4️⃣ HOW  TO SOLVE</h2>
<blockquote>
<p><em>: 이 상태에서 다음 일주일을 더 잘 보내려면 어떻게 해야 할까?</em></p>
</blockquote>
<p>코딩 실력에 심각성을 느끼고, 이번주에는 남아서 공부하는 시간을 최대한으로 늘려보려고 한다. 그래서 실질적 실천 방안을 작성해보자면!</p>
<p>실질적 실천방안</p>
<p>1️⃣ 첫 번째, 지금까지 배운 내용들을 꾸준히 복습하기!</p>
<p>→ 1-2주차 때 배웠던 Python 내용들을 꾸준히 복습하고 이에 맞는 예제들을 찾아서 연습해보는 것이다. </p>
<p>2️⃣ 두 번째, 예복습 스터디에 진심으로 임하기!</p>
<p>→ 우리 예복습 스터디 멤버들도 다 너무 진심으로 임하다보니 공부가 정말 많이 된다. 이번주엔 내가 lead를 하는 차례가 되어서 더 열심히 수업을 듣고 준비해보려고 한다.</p>
<p>3️⃣세 번째, 프로그래머스 코테 꾸준히 풀기!</p>
<p>→ 코테의 장점은 python을 계속해서 복습시켜준다는 것이다. 그래서 코테 스터디전까지 모든 문제를 다양한 풀이 방안으로 풀어가보는 것이 목표다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[플레이데이터 SK네트웍스 Family AI캠프 13기] 1주차 회고]]></title>
            <link>https://velog.io/@jiyun_kang/%ED%94%8C%EB%A0%88%EC%9D%B4%EB%8D%B0%EC%9D%B4%ED%84%B0-SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI%EC%BA%A0%ED%94%84-13%EA%B8%B0-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jiyun_kang/%ED%94%8C%EB%A0%88%EC%9D%B4%EB%8D%B0%EC%9D%B4%ED%84%B0-SK%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4-Family-AI%EC%BA%A0%ED%94%84-13%EA%B8%B0-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 31 Mar 2025 11:11:39 GMT</pubDate>
            <description><![CDATA[<h3 id="👣-ai부트캠프를-시작하게-된-이유">👣 AI부트캠프를 시작하게 된 이유</h3>
<p>올해 2월에 대학원을 졸업하고, 앞으로의 진로에 대한 고민이 많아지는 시기를 보내고 있었다. 졸업 후에 바로 취업을 해야할까, 혹은 어떤 분야로 가는 것이 나에게 맞을까 등등 다양한 고민이 있었다. 사실 긴 기간을 공부하다보니, 이제는 회사에 들어가서 실무경험을 쌓는 것도 나쁘지 않겠다는 생각도 있었다. </p>
<p>그렇지만 결론적으로 AI부트캠프를 선택하게 된 것은 대학원 논문이 가장 큰 계기가 되었다. 논문의 내용을 짧게 이야기 하면, 생성형 AI를 활용해서 학습자 데이터 기반 맞춤형 수업 설계를 지원하는 내용인데, chatGPT가 세상에 나오지 않았더라면 나의 논문도 존재할 수 없을 정도로 생성형 AI가 너무나 중요한 화두 속에 있었다.</p>
<p>이처럼, 현 시대를 살아가면서 어느 분야에 속해있든지 기술적인 발전에 따라 본질은 변하지 않더라도, 그 속성과 방식은 계속해서 변해간다. 특히 오늘날에는 그 발전의 속도가 모든 사람이 체감할 수 있을 정도로 빠르다. 그 중심에는 생성형 인공지능이 자리매김하고 있으며, 각자가 속한 분야에서 더욱 성장하기 위해서는 그 발전의 흐름을 읽을 수 있는 사람이 되어야 한다. </p>
<p><em><em>📚회고 구성 목차
*</em><br>1️⃣ LEARN 
*1)  지난 일주일 동안 가장 인상 깊었던 배움</em>
<em>2) 그 배움까지 다가가는데 어떤 어려움이 있었는지</em></p>
<p>2️⃣FINDINGS &amp; FEELINGS
<em>:그 과정에서 나는 무엇을 깨달았고, 어떤 감정/생각이 들었는지?</em> </p>
<p>3️⃣ RESULT
<em>: 결과적으로, 현재 나의 상태는?</em></p>
<p>4️⃣ HOW  TO SOLVE
<em>: 이 상태에서 다음 일주일을 더 잘 보내려면 어떻게 해야 할까?</em></p>
<h2 id="1️⃣-learn">1️⃣ LEARN</h2>
<blockquote>
<ol>
<li>지난 일주일동안 가장 인상 깊었던 배움</li>
</ol>
</blockquote>
<p>첫 주는 파이썬에 대한 강의가 진행되었다. 파이썬을 아주 잠깐 대학원 수업 때 사용한 적이 있지만, 사실상 이렇게 하나하나 문법을 배운 것은 처음이었다. 그래서, 사실 인상 깊었던 부분이라고 하면…전체 다라고 해도 될 것 같다..</p>
<p><strong>💡 그 중에서도 가장 인상적인 부분을 정리하자면… (아주 사소할 수 있음)
**
*<em>1. Dictionary 생성 함수, 조회 및 변경
*</em>2. 난수 (random.randint)
**3. 컴프리헨션 문법</strong></p>
<p>*<em>1. Dictionary 생성 함수, 조회 및 변경
*</em>
1-1. 함수를 쓰면 보다 쉽고 빠르게 생성할 수 있는 것</p>
<pre><code class="language-python">d3= dict(이름=&quot;홍길동&quot;, 나이=30, 주소=&quot;서울시 금천구&quot;, 취미=[&quot;게임&quot;, &quot;야구&quot;])
d3

{&#39;이름&#39;: &#39;홍길동&#39;, &#39;나이&#39;: 30, &#39;주소&#39;: &#39;서울시 금천구&#39;, &#39;취미&#39;: [&#39;게임&#39;, &#39;야구&#39;]}
</code></pre>
<p>*<em>1-2. 변경하거나 추가하고 싶을 때 똑같이 쓰면 되는 것
*</em></p>
<pre><code class="language-python">#조회
d3[&quot;나이&quot;]
#변경(그냥 추가하면 됨)
d3[&#39;전화번호&#39;]= &quot;01&quot;

{&#39;이름&#39;: &#39;홍길동&#39;,
 &#39;나이&#39;: 30,
 &#39;주소&#39;: {&#39;시&#39;: &#39;서울시&#39;, &#39;구&#39;: &#39;금천구&#39;},
 &#39;취미&#39;: [&#39;게임&#39;, &#39;야구&#39;],
 &#39;전화번호&#39;: &#39;01&#39;}</code></pre>
<p>*<em>2. 난수(random.randint) : 그냥 랜덤하게 나와서 신기하다.. 
*</em></p>
<pre><code class="language-python">import random
random.randint(10, 20)  # 시작(10) ~ 끝(20) 범위 안에 있는 임의의 정수(난수-random값)를 반환.</code></pre>
<p>*<em>3. 컴프리헨션
*</em></p>
<p>코드를 간편하게 작성할 수 있도록 만들어서 유용하게 쓸 수 있을 것 같다. 처음에는  v  안에 또 v 가 있고, 그 뒤에 if 구문을 바로 붙인다고 생각하면 뭔가 복잡해 보이지만 시간이 지날수록 익숙해졌다. </p>
<pre><code class="language-python">#컴프리헨션 사용 안했을 때
result = []
for v in l:
    if v % 2 == 0:
        result.append(v)
result # mapping 을 컴프리헨션 문법으로 구현
l = [1, 2, 3, 4, 5, 6]

# filtering을 컴프리헨션 문법으로 구현
result4 = [v*10 for v in l if v % 2 == 0]
result4
</code></pre>
<p>*<em>4.  local 변수 &amp; global 변수
*</em></p>
<p>처음에는 global 변수라고 해서, 엄청나게 대단한 특성을 가진 변수라고 생각했는데, 코드 예시를 보니 왜 그렇게 표현했는지 단번에 알 수 있었다. 말 그대로 global 변수는 함수 밖에서 선언한 것이고, local 변수는 함수 안에서 선언된 것이라, 함부로 global 변수는 변경하지 않게 된다.</p>
<pre><code class="language-python">g_var =10 #global 변수 : 함수 밖에서 선언
def func():
    local_var = 100 #local 변수 :함수 안에서 선언
    g_var = &quot;안녕하세요.&quot; #local 변수로 선언 

    print(local_var)
    print(g_var)</code></pre>
<p>만약 변경하고 싶다면!</p>
<pre><code class="language-python">g_var =10 #global 변수 : 함수 밖에서 선언
def func():
    local_var = 100 #local 변수 :함수 안에서 선언
    global g_var #gloabl 변수 자체를 변경하고 싶다면, 앞에 먼저 global변수를 바꿀 것임을 명시해야함. 
    g_var = &quot;안녕하세요.&quot; #local 변수로 선언 

    print(local_var)
    print(g_var)</code></pre>
<p><em>*&gt; * 2) 그 배움까지 다가가는데 어떤 어려움이 있었는지</em>
**&gt; </p>
<p>자료 구조와 제어문까지는 강사님께서도 차근차근 자세하게 설명해주시기도 하고, 이해하는데 큰 무리는 없었는데 벌써 목요일에 난관에 봉착했다..</p>
<p>*<em>어려움 1. 함수에는 헷갈리는 용어가 너무 많다!
*</em></p>
<p>예를 들면, Parameter와 Argument는 어떤 차이가 있는가! 그래서, 한번 구글링을 해보았다. </p>
<p>*<em>💡Parameter(매개변수) vs. Argument(전달인자)
*</em></p>
<ul>
<li>파라미터는 함수 정의에서 정의된 <strong>변수</strong>다; 파이썬 오피셜 문서에서는 &#39;함수 정의에서 함수가 받을 수 있는 인자를 지정하는 이름 붙은 엔티티&#39;라고 한다.</li>
<li>아규먼트는 함수를 호출할 때 실제로 함수에 전달되는 <strong>값</strong>이다.</li>
<li>그러니까 아규먼트를 넣어서 함수를 호출하면 아규먼트의 값을 파라미터(매개변수)에 할당한다라고 생각해도 좋겠다.</li>
</ul>
<p>출처 : [파이썬] Parameter 와 Argument 의 차이가 뭐예요?]</p>
<p>또 다른 예시로는,,</p>
<p>*<em>💡함수(function) vs. 메소드(Method)
*</em></p>
<ul>
<li><p>함수의 기본 구조: 함수명()</p>
</li>
<li><p>메소드의 기본 구조: object.method_name()</p>
</li>
<li><p>함수는 함수 이름을 통해 사용할 수 있다.</p>
</li>
<li><p>함수 예) print(), type(), str(), int(), bool(),</p>
</li>
<li><p>함수의 값을 변수에 대입할 수 있다.</p>
<p>  👉🏻 output = function_name(input)</p>
</li>
<li><p>메소드는 object(객체)와 연관되어 사용된다. → 사용하고자 하는 대상이 . 으로 연결되어있어야함</p>
</li>
<li><p>str,float,list와 같은 자료형은 모두 객체이므로 이러한 자료형과 연관되어 사용되는 것은 메소드로 볼 수 있다.</p>
</li>
<li><p>메소드 예) .split() , .append() 등</p>
</li>
</ul>
<p>출처: [[Python] 함수와 메소드의 차이점]</p>
<p>*<em>어려움 2. 객체지향..그 자체..
*</em></p>
<p>우리나라 말은 왜 이렇게 사람을 주눅들게 어려운 단어를 쓸까.. 객체지향이라고 하니까 마치 엄청 대단한 말 같지만 사실상 파이썬이라는 프로그램이 객체지향 그 자체를 말해준다.</p>
<blockquote>
<p>&quot;Python에서 모든 것은 객체(Object)이다. 그리고 대부분 객체는 속성(attributes)과 메서드(methods)를 갖는다.&quot;</p>
</blockquote>
<p>파이썬에서 bool, 정수, 실수, 문자열, 배열, 딕셔너리, 함수, 모듈, 프로그램 등 모든 것은 객체이다. </p>
<p>참조  [Everything is an Object]</p>
<p>그러니까 이제 객체가 나오면,,,쫄지 않고 그냥 내가 보고 있는 것이 객체인가보다 생각하기로 했다.</p>
<p>즉, 객체지향 프로그래밍이란</p>
<p> Data와 Data를 처리하는 함수를 분리하지 않고 하나로 묶어 모듈로 개발하는 방식이다. </p>
<p>그래서 어떤 값들과 어떤 함수를 묶을 것인지를 class로 정의한다. 그리고 그 class로부터 <strong>객체(instance)</strong> 를 생성(instantiate)해서 사용한다는 점!</p>
<h2 id="2️⃣findings--feelings">2️⃣FINDINGS &amp; FEELINGS</h2>
<blockquote>
<p>:  그 과정에서 나는 무엇을 깨달았고, 어떤 감정/생각이 들었는지? </p>
</blockquote>
<p><strong>깨달은 점 1.  복습을 해야한다!</strong>  </p>
<p>우선, 깨달은 점은 복습을 정말 열심히 해야겠다는 생각이 들었다. 수업시간에 설명해주신 부분이 이해가 되더라도 사실상 내 머릿 속에 남아있는게 없다면, 적용하는게 불가능하다. 주말이 지났다고, 바로 휘발 되어버리는 자랑스러운(?) 나의 뇌를 다시금 인지하게 되면서 결국엔 계속해서 복습하고 문제도 여러번 풀어봐야 할 것 같다. </p>
<p><strong>깨달은 점 2. 아직까지 이 코드가 왜 필요한지 이해하지 못한다.</strong></p>
<p>아무래도 코딩을 직접해보면서 이 코드를 쓰면 유용하겠다는 경험이 필요한데, 아직 그런 경험이 부족하다보니 사실상 이런 코드가 있구나하고 잘 기억해두는 것을 넘어서 어떻게 응용할 수 있을까하는 생각도 필요하다. 이제 점점 더 어려운 코드와 개념들을 배워나갈텐데, 지금까지 배운 코드들을 잘 써먹기 위해서는 “응용력”이 필요하다. </p>
<h2 id="3️⃣-result">3️⃣ RESULT</h2>
<blockquote>
<p>: 결과적으로, 현재 나의 상태는?</p>
</blockquote>
<p>그래서 나의 상태를 한마디로 표현하자면.. 큰일났다!! </p>
<p>더 열심히 공부해야겠다는 생각만 든다.. 사실 회고록을 쓰기 전까지는 약간의 회피를 하고 있었던 것 같은데, 회고록을 쓰면서 정리해둔 노션을 보면서 아직도 갈길이 너무나 멀다는 것을 직감하게 되었다. </p>
<h2 id="4️⃣-how--to-solve">4️⃣ HOW  TO SOLVE</h2>
<blockquote>
<p>: 이 상태에서 다음 일주일을 더 잘 보내려면 어떻게 해야 할까?</p>
</blockquote>
<p>하지만, 여기에 들어오면서 다짐한 것이 있다!</p>
<p>바로 “이 배움을 즐기자&quot;는 것이다. </p>
<p>코딩은 나에게 원래도 어렵고, 계속해서 어려울 예정인데 이걸 너무 심각하게 생각했다가는 스트레스를 너무나 받을 것 같았다. 그래서, 오히려 이 어려움을 즐기기로 마음 먹었다. </p>
<p>다음으로는 실질적 실천방안이라고 한다면, </p>
<p><strong>📚실질적 실천방안</strong></p>
<p>*<em>1️⃣ 첫 번째, 수업 시간에 매우 집중하기!
*</em>
→너무 당연한 이야기일 수도 있지만, 비전공자인 나에게 수업은 너무나 귀하고 새로움 그 자체이기 때문에 하나도 놓칠 수 없다.</p>
<p>*<em>2️⃣ 두 번째, 어려워하는 부분이 있다면, 꼭 표시해두기!
*</em>
→ 복습할 때 참고하는 용도 (어디가 어려웠는지 까먹을 때가 많다!)</p>
<p>*<em>3️⃣세 번째, 키워드 기반으로 정리하기!
*</em>
→ 개념이 방대할 때는 그 개념이 어떻게 파생되었는지 마인드맵이나 키워드로 정리해보는 시간이 필요하다. 이건 어떤 공부를 할때나 다 유용하게 썼던 방법인데 이번주엔 각 개념들 사이에 어떤 연결점이 있는지, 혹은 코드 구성이 어떻게 되는지 한눈에 볼 수 있는 정리해보고자 한다.</p>
]]></description>
        </item>
    </channel>
</rss>