<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jul-ee.log</title>
        <link>https://velog.io/</link>
        <description>AI에 관심을 가지고, 데이터로 가치를 만들어 나가는 과정을 기록합니다.</description>
        <lastBuildDate>Tue, 12 Aug 2025 01:25:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jul-ee.log</title>
            <url>https://velog.velcdn.com/images/jul-ee/profile/a9e3a0ae-835e-4b15-90c1-d07286375248/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jul-ee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jul-ee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[아이펠톤] LLM 토론 시스템 도입: DEEVO, DReaMAD 연구 분석]]></title>
            <link>https://velog.io/@jul-ee/DS-%EC%95%84%EC%9D%B4%ED%8E%A0%ED%86%A4-LLM-%ED%86%A0%EB%A1%A0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%8F%84%EC%9E%85-DEEVO-DReaMAD-%EC%97%B0%EA%B5%AC-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@jul-ee/DS-%EC%95%84%EC%9D%B4%ED%8E%A0%ED%86%A4-LLM-%ED%86%A0%EB%A1%A0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%8F%84%EC%9E%85-DEEVO-DReaMAD-%EC%97%B0%EA%B5%AC-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Tue, 12 Aug 2025 01:25:04 GMT</pubDate>
            <description><![CDATA[<p>이 프로젝트에서 개발 중인 SAR 분석 시스템은 Activity Cliff 현상을 분석하여 화학자들에게 신뢰할 수 있는 가설을 제공해야 한다.</p>
<p>단일 LLM을 사용할 때의 결과가 우려스러웠다. 같은 데이터를 여러 번 분석해도 일관성이 부족하고, 때로는 명백히 잘못된 화학 지식을 근거로 제시하는 할루시네이션 현상이 빈번할 것으로 보였다.</p>
<p>이런 상황에서 LLM 토론 시스템에 대한 최신 연구들을 접하게 되었다. 여러 LLM이 서로 토론하며 분석 품질을 향상시킨다는 아이디어는 매력적이었지만, 실제로 효과가 있는지, 그리고 우리 프로젝트에 어떻게 적용할 수 있는지 명확하지 않았다.</p>
<p>두 가지 핵심 연구를 살펴보았고, 이를 통해 LLM 토론 시스템 도입의 과학적 근거와 구체적 방법론을 찾을 수 있었다.</p>
<br>
<br>
<br>

<h3 id="deevo-연구-토론을-통한-프롬프트-자동-진화">DEEVO 연구: 토론을 통한 프롬프트 자동 진화</h3>
<blockquote>
<p><a href="https://arxiv.org/abs/2506.00178">📄 Tournament of Prompts: Evolving LLM Instructions Through Structured Debates and Elo Ratings, 2025</a></p>
</blockquote>
<p>우리 SAR 분석 시스템에서 가장 큰 고민은 <span style="background-color: rgba(242,179,188,0.5)">&quot;어떤 프롬프트가 가장 좋은 분석을 만들어내는가&quot;</span>였다. 화학 분야는 정답이 명확하지 않은 영역이다. Activity Cliff 분석에서 &quot;이 구조 변화가 활성 감소의 원인인가&quot;라는 질문에 대해 절대적 정답은 존재하지 않는다. 여러 가설이 경쟁하는 상황에서 어떤 프롬프트가 더 나은 가설을 생성하는지 평가하기가 어렵다.</p>
<p>DEEVO 연구진들도 정확히 같은 문제를 지적했다. 기존 프롬프트 최적화 방법들의 한계는 다음과 같았다.</p>
<ul>
<li><strong>수치형 적합도 함수 의존성</strong>: 명확한 정답이 있는 문제에만 적용 가능</li>
<li><strong>템플릿 기반 접근</strong>: 복잡하고 주관적인 작업의 요구사항을 포착하지 못함</li>
<li><strong>정적 최적화</strong>: 작업 특성 변화에 적응하지 못함</li>
</ul>
<p>그들이 제시한 해결책은 진화 알고리즘과 다중 에이전트 토론을 결합하는 것이었다.</p>
<blockquote>
<p>💡 핵심 아이디어는 이렇다.</p>
<p>두 프롬프트가 같은 문제를 해결하게 한 다음, LLM 심판들이 토론을 통해 어떤 답변이 더 나은지 결정하게 하는 것이다. 미리 정의된 평가 기준 없이 LLM 자체의 추론 능력만으로 품질을 판단한다는 점이 혁신적이었다.</p>
</blockquote>
<p>이 방법이 효과적인 이유는 여러 라운드의 토론을 거치면서 각 답변의 강점과 약점이 명확히 드러나기 때문이다. 한 LLM이 놓친 부분을 다른 LLM이 지적하고, 이런 과정을 통해 더 객관적인 평가가 가능해진다.</p>
<p>실험 결과도 놀라웠다. 폐쇄형 작업에서는 기존 최고 방법 대비 상당한 정확도 향상을 보였고, 개방형 작업에서도 MT-Bench의 쓰기, 역할극, 인문학 부문에서 평균 1점 이상의 개선이 있었다.</p>
<p>특히 Elo 점수와 실제 성능 간의 상관관계가 0.87로 매우 높았는데, 이는 토론 기반 평가가 실제 품질을 정확히 반영한다는 의미다.</p>
<blockquote>
<p>우리 프로젝트에 이를 적용하면,</p>
<p>여러 버전의 SAR 분석 프롬프트가 서로 경쟁하면서 점점 더 나은 프롬프트로 진화할 수 있다. 화학자가 수동으로 프롬프트를 개선하는 대신, <span style="background-color: rgba(242,179,188,0.5)">시스템이 자동으로 최적의 프롬프트</span>를 찾아낼 수 있는 것이다.</p>
</blockquote>
<hr>
<br>
<br>
<br>

<h3 id="dreamad-연구-토론-시스템의-치명적-결함-발견">DReaMAD 연구: 토론 시스템의 치명적 결함 발견</h3>
<blockquote>
<p><a href="https://arxiv.org/abs/2503.16814">📄 Understanding Bias Reinforcement in LLM Agents Debate, 2025</a></p>
</blockquote>
<p>DEEVO 연구를 분석하면서</p>
<p><em>토론 시스템이 과연 항상 좋은 결과만 가져올까?</em>
<em>여러 LLM이 토론한다고 해서 반드시 더 나은 결과가 나온다는 보장이 있을까?</em>
<em>혹시 우리가 예상치 못한 함정이 숨어있는 것은 아닐까?</em></p>
<p>하는 근본적인 의문이 들었다.</p>
<p>이런 의문은 우리 프로젝트에 토론 시스템을 도입하기 전에 반드시 확인해야 할 중요한 사항이었다. 만약 토론 시스템에 치명적 결함이 있다면, 단일 LLM보다도 못한 결과를 얻을 수도 있기 때문이다.
<br></p>
<p>이 논문은 정확히 그런 의문에 답을 제공했다. 연구진들은 기존 다중 에이전트 토론 시스템에서 놀라운 현상을 발견했다. <strong>토론이 오히려 편향을 강화하는 경우가 있다</strong>는 것이다.</p>
<p>이런 현상이 발생하는 이유를 분석해보니 논리적으로 명확했다.</p>
<blockquote>
<p>대부분의 토론 시스템은 동일한 <strong>기본 모델을 복제</strong>해서 사용한다. 겉으로는 여러 에이전트가 토론하는 것처럼 보이지만, 실제로는 같은 추론 패턴을 가진 모델들이 서로의 편향을 확인하고 강화하는 상황이 벌어진다. 마치 비슷한 생각을 가진 사람들끼리 모여서 토론할 때 극단적 견해가 더욱 강화되는 인간의 &quot;확증 편향&quot;과 유사한 현상이다.</p>
</blockquote>
<p>연구진들은 이 문제를 정량적으로 측정하기 위해 MetaNIM Arena라는 독창적인 벤치마크를 개발했다. 이 벤치마크가 우리에게 특별히 중요한 이유는 화학 분야와 유사한 특성을 가지고 있기 때문이다. 수학적으로 완벽한 답이 정해져 있으면서도 복잡한 전략적 추론이 필요한 조합 게임들을 사용했다. </p>
<p>Activity Cliff 분석도 마찬가지다. <span style="background-color: rgba(242,179,188,0.5)">이론적으로는 정확한 메커니즘이 존재</span>하지만, 그것을 찾아내려면 <span style="background-color: rgba(242,179,188,0.5)">화학적 지식과 논리적 추론을 결합한 복잡한 사고 과정이 필요</span>하다. 따라서 MetaNIM Arena에서의 결과는 우리 화학 분야 적용에도 직접적인 시사점을 제공한다.</p>
<br>
기존 다중 에이전트 토론 시스템의 실험 결과는 충격적이었다.

<ul>
<li>최적의 정보가 주어져도 <strong>편향된 패턴에 수렴</strong></li>
<li>토론 라운드가 증가할수록 <strong>정확도 오히려 감소</strong></li>
<li>추론의 다양성 <strong>30% 감소</strong></li>
</ul>
<p>이는 우리가 토론 시스템을 잘못 설계하면 단일 LLM보다도 못한 결과를 얻을 수 있다는 의미였다.</p>
<br>

<p>하지만 연구진들은 문제를 발견하는 데 그치지 않고 해결책도 제시했다. <span style="background-color: rgba(242,179,188,0.5);color: #d85999;font-weight:bold">DReaMAD</span> 라는 새로운 프레임워크의 핵심은 두 가지 전략이었다.</p>
<p><strong>1. 전략적 사전 지식 추출</strong></p>
<ul>
<li>문제를 바로 풀기 시작하는 대신, 각 에이전트가 먼저 고수준 전략을 수립</li>
<li>&quot;이 문제의 핵심은 무엇인가&quot;, &quot;어떤 전략이 유효할 것인가&quot;를 스스로 정리</li>
<li>표면적인 분석을 넘어선 깊이 있는 추론 유도</li>
</ul>
<p><strong>2. 관점 다양화</strong></p>
<ul>
<li>동일한 모델이라도 서로 다른 관점을 강제로 부여</li>
<li>진정한 다양성 확보 (예: &quot;공격적 전략&quot; vs &quot;수비적 전략&quot;)</li>
<li>편향 방지와 창의적 사고 촉진</li>
</ul>
<p>DReaMAD의 성과는 명확했다. MetaNIM Arena에서 기존 표준 프롬프트 대비 12.0% 정확도 향상, 기존 토론 시스템 대비 20.8% 승률 향상을 달성했다. 더 중요한 것은 편향이 25% 감소했다는 점이다. 또한 일반적인 수학 문제나 상식 추론 문제에서도 높은 성능을 보여 다양한 영역에 적용 가능함을 증명했다.</p>
<hr>
<br>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>두 연구를 분석한 결과, SAR 분석 시스템에 LLM 토론을 도입해야 하는 근거와 방향을 잡을 수 있었다.</p>
<blockquote>
<p>💡 DEEVO 연구는 꾸준히 고민하고 있는 &quot;정답이 없는 문제의 품질 평가&quot; 방안을 제시했다.</p>
<p>Activity Cliff 분석에서 어떤 가설이 더 타당한지 판단하기 어려웠는데, 토론을 통한 상대 평가로 이 문제를 해결할 수 있다는 것을 확인했다. 또한 프롬프트가 자동으로 진화하는 시스템을 구축하면 화학자가 수동으로 개선하지 않아도 점점 더 나은 분석이 가능해진다.</p>
</blockquote>
<blockquote>
<p>💡 DReaMAD 연구는 더욱 중요한 경고와 해결책을 동시에 제공했다.</p>
<p>토론 시스템을 잘못 설계하면 오히려 역효과가 날 수 있다는 경고와 함께, 관점 다양화와 사전 전략 수립이라는 구체적 해결책도 제시했다. 우리 시스템에서는 구조화학 전문가, 생체분자 상호작용 전문가, SAR 통합 전문가로 역할을 분화하고, 각각 다른 관점으로 분석하게 만들어야 한다는 설계 방향을 생각해 볼 수 있었다.</p>
</blockquote>
<p>이 두 연구를 통해 가장 중요한 확신을 얻은 것은 실험적 검증의 엄밀성이었다.</p>
<ul>
<li>DEEVO는 여러 벤치마크에서 일관된 성능 향상을 보였고, 특히 Elo 점수와 실제 성능의 강한 상관관계(0.87)를 통해 평가 방법의 신뢰성을 입증했다.</li>
<li>DReaMAD는 수학적으로 정답이 정해진 MetaNIM Arena에서 편향 감소와 정확도 향상을 동시에 달성했다.</li>
</ul>
<p>이를 통해 과학적으로 검증된 방법론이라는 신뢰를 기반으로 우리 프로젝트에 적용할 방안을 고민할 수 있었다.</p>
<p>특히 화학 분야처럼 복잡하고 때로는 주관적 판단이 필요한 영역에서는 이런 방법론의 가치가 더욱 크다고 본다. 단일 LLM의 편향과 할루시네이션을 극복하고, 전문가 수준의 신뢰할 수 있는 가설을 생성할 수 있는 실현 가능한 방향을 잡아가는 중이다. 우리 프로젝트에 LLM 토론 시스템을 도입하는 것이 선택이 아닌 필수일 수 있겠다는 생각도 든다. 두 연구를 통해 그 구체적인 설계 방향을 명확하게 정리해 보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[아이펠톤] 프롬프트 엔지니어링: 도메인 지식 내장형 CoT로 과학 추론 강화하기]]></title>
            <link>https://velog.io/@jul-ee/DS-%EC%95%84%EC%9D%B4%ED%8E%A0%ED%86%A4-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EC%84%A4%EA%B3%84-%EA%B7%BC%EA%B1%B0-1</link>
            <guid>https://velog.io/@jul-ee/DS-%EC%95%84%EC%9D%B4%ED%8E%A0%ED%86%A4-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EC%84%A4%EA%B3%84-%EA%B7%BC%EA%B1%B0-1</guid>
            <pubDate>Tue, 29 Jul 2025 01:43:12 GMT</pubDate>
            <description><![CDATA[<p>자동화된 지능형 SAR 분석 및 근거 중심 가설 생성 시스템을 설계하는 과정에서 거대 언어 모델(LLM)의 추론 능력을 최대한 활용할 방법을 고민했다.</p>
<p>특히 “이 구조가 왜 활성이 달라졌는지”를 논리적으로 설명하고, 근거 기반의 가설을 생성하려면 단순한 질문–답변 방식으로는 화학·재료 과학 분야의 복잡한 인과 관계를 온전히 포착하기 어려울 것으로 보였다.</p>
<p>이에 따라 화학 분야 프롬프트 엔지니어링의 최신 연구 성과를 참고할 필요를 느꼈고, 도메인 특화 프롬프트 엔지니어링 연구 중에서도 LLM에 전문 지식을 내장하여 정확도·재현성·할루시네이션 감소를 동시에 이룬 Liu et al. (2024)의 “Integrating Chemistry Knowledge in LLMs via Prompt Engineering”을 첫 번째 참조 논문으로 선정하였다.</p>
<blockquote>
<p>📄 <a href="https://www.sciencedirect.com/science/article/pii/S2405805X24001029">Integrating Chemistry Knowledge in Large Language Models via Prompt Engineering (Liu et al., 2024)</a></p>
</blockquote>
<br>
<br>

<h3 id="🖇-논문-선택-이유">🖇 &nbsp;논문 선택 이유</h3>
<p>우리가 다루려는 특허 기반 SAR(Structure–Activity Relationship) 테이블 분석은 전문 지식이 필요하고 실험 데이터가 복잡하게 얽힌 도메인으로 분류된다. 일반적인 LLM 프롬프트 방식은 이러한 분야에서 종종 부정확하거나 할루시네이션을 일으키기 쉽다.</p>
<p>Liu et al. 논문은 도메인 지식 내장형 프롬프트라는 새로운 방법론을 제안한다.</p>
<ol>
<li><p><strong>도메인 특화 평가 세트 구축</strong></p>
<p> Liu et al.은 기존의 일반 평가셋 대신, 약물·효소·결정 재료 관련 1,280개의 문제를 자체 제작해 체계적으로 LLM 성능을 측정했다. 이는 일반 평가셋에 비해 화학 분야 난이도를 현실에 가깝게 반영한다는 점에서 의미가 크다. </p>
</li>
<li><p><strong>포괄적 성능 지표</strong></p>
<p> Capability(문제 해결 능력), Accuracy(정확도), F1 score(정밀도·재현율 통합), Hallucination drop(할루시네이션 감소) 등 네 가지 지표로 다양한 질문 유형(객관식·서술형·숫자형)을 평가했다.</p>
</li>
<li><p><strong>실물 사례 연구</strong></p>
<p> MacMillan 촉매, 파클리탁셀, 리튬 코발트 산화물 등 복잡한 화합물에 적용하여 실제 연구 현장과 유사한 조건에서 유의미한 성능 향상을 입증했다.</p>
</li>
<li><p><strong>할루시네이션 최소화</strong></p>
<p> 풍부한 문맥 정보(in‑context information)를 제공할 때 할루시네이션이 크게 줄어든다는 사실을 데이터로 보여주었다.</p>
</li>
</ol>
<p>위와 같은 이유로, 화학 분야 LLM 활용 관점이 우리 SAR 분석 프로젝트에도 적용 가능한 가이드라인이 될 수 있을 것이라 판단된다.</p>
<hr>
<br>
<br>
<br>


<h3 id="🖇-연구-핵심-내용-요약">🖇 &nbsp;연구 핵심 내용 요약</h3>
<p>Liu et al. 논문은 크게 네 단계로 연구를 전개한다.</p>
<blockquote>
<p>첫째, 화학·재료 과학 도메인 특화 평가를 위해 작은 분자·효소·결정 재료 관련 1280문제를 자체 생성했다.</p>
<p>둘째, 일반 프롬프트(Zero-shot, Few-shot, Chain‑of‑Thought(CoT))와 도메인 지식 내장형 프롬프트를 비교 실험했다.</p>
<p>셋째, GPT-3.5-turbo-1106 모델을 대상으로 Capability, Accuracy, F1, Hallucination drop 등을 측정하여, 제안 방식이 객관식·주관식·서술형 등 여러 유형에서 유의미한 성능 향상을 보임을 확인했다.</p>
</blockquote>
<p>넷째, 복잡한 물질(고분자, 복합 결정 등)이나 연구량이 적은 효소 분야에서 성능 저하가 관찰되었지만, 이는 LLM이 외부 지식에 의존하는 특성 때문이라는 분석을 제시했다.</p>
<p>정리하자면,</p>
<p>일반적인 프롬프트 방식과 도메인 지식 내장형 프롬프트를 직접 비교하며 그 차이를 네 가지 주요 관점에서 심층적으로 살펴본다.
<br></p>
<p><span style="background-color:#E0D3ED"><strong>1. 도메인 지식 내장형 프롬프트(Method)</strong></span></p>
<p>&quot; 일반 프롬프트에 <u>전문가 사고 과정</u>과 <u>핵심 배경 지식</u>을 명시적으로 추가 &quot;</p>
<ul>
<li>비교 실험: Zero‑Shot, Few‑Shot, Zero‑Shot CoT(“Let’s think step by step”), Few‑Shot CoT, Domain‑Knowledge Embedded Prompting</li>
<li>결과: 모든 지표에서 도메인 지식 내장 프롬프트가 최고 성능<ul>
<li>Capability: 평균 +12%</li>
<li>Accuracy: 평균 +15%</li>
<li>F1 score: 평균 +18%</li>
<li>Hallucination drop: –25%</li>
</ul>
</li>
</ul>
<p><strong>무엇을 바꿨나?</strong></p>
<ul>
<li>기존 방식은 질문만 던지거나(Zero‑Shot), 몇 가지 예시를 주는 수준(Few‑Shot), “단계별로 생각해보자”라고만 지시(CoT)하는 데 그쳤다.</li>
<li>도메인 지식 내장형 방식은 여기에 더해 “전문가가 실제로 갖고 있는 배경 지식”과 “구체적인 사고 흐름”을 프롬프트에 명시적으로 추가한다.</li>
</ul>
<p><strong>왜 중요한가?</strong></p>
<ul>
<li>일반 CoT 프롬프트는 LLM이 스스로 논리를 만들어내려 하지만, 실제 전문가가 쓰는 세부 배경 지식 없이 단순히 “생각해보라”고만 하면 근거 없는 추측이나 엉뚱한 답변이 나올 수 있다.</li>
<li>반면 도메인 지식 내장형은 “화합물 구조의 어떤 부분이 중요한지”, “어떤 물리화학 특성이 활성에 영향을 주는지” 같은 핵심 정보를 미리 제시한 뒤, 그 정보를 바탕으로 논리를 전개하도록 유도한다.</li>
</ul>
<p><strong>어떤 효과가 나타났나?</strong></p>
<ul>
<li>논문에서는 이 방식이 모든 평가 지표(Capability, Accuracy, F1, Hallucination drop)에서 10~20% 이상 일관된 성능 향상을 보였다고 보고한다.</li>
<li>특히 할루시네이션(근거 없는 허구 생성) 수치는 평균 25% 이상 감소했다. 이는 LLM이 “제공된 정보만 갖고 추론하도록” 제약을 주었기 때문이다.</li>
</ul>
<br>
<br>

<p><span style="background-color:#E0D3ED"><strong>2. 전문가 역할 부여(Expert Prompting)와 단계별 사고 흐름(CoT)</strong></span></p>
<p><strong>Expert Prompting이란?</strong></p>
<ul>
<li>“You are a senior medicinal chemist…” 처럼 LLM에게 특정 전문가 페르소나를 부여하는 것이 효과적이라는 것이다.</li>
<li>사람도 “의사처럼 설명해달라”, “변호사처럼 분석해달라”고 하면 말투나 접근 방식이 달라지듯, LLM도 내부 가중치가 그 분야 문서들과 더 잘 매칭되도록 유도된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/27c06a24-d989-4033-b8e6-e1861cb8f16e/image.png" alt=""></p>
<h6 id="integrating-chemistry-knowledge-in-large-language-models-via-prompt-engineering-liu-et-al-2024">Integrating Chemistry Knowledge in Large Language Models via Prompt Engineering (Liu et al., 2024)</h6>
<ul>
<li>Fig. 7(a-c)에서 보듯이 도메인 지식 내장형 CoT가 특히 논리적 추론 과제에서 +20% 이상 &#39;상당한(significant)&#39; 성능 향상을 보였다.</li>
<li>도메인 지식 내장형 프롬프트 엔지니어링 방법이 일반 프롬프트 전략보다 모든 질문 유형에서 일관되게 우수하며, 특히 능력(Capability) 및 정확도(Accuracy) 지표에서 100%를 초과하는 가장 주목할 만한 향상을 보였다고 전체적인 성능 향상을 언급한다.</li>
<li>LLM이 실험 데이터 기반 답변보다 논리적 추론 기반 답변에 더 잘 수행한다는 점을 강조하며, 이러한 경향이 &#39;도메인 특화 프롬프트 엔지니어링 방법&#39;에서 더욱 증폭된다는 것을 볼 수 있었다.<br>

</li>
</ul>
<p><strong>단계별 CoT 구성 방법</strong></p>
<ol>
<li><p>구조 차이 식별</p>
<ul>
<li>두 화합물을 나란히 놓고 “어떤 치환기나 고리 구조가 달라졌는지” 정확히 찾아내게 한다.</li>
</ul>
</li>
<li><p>물리화학 특성 영향 추론</p>
<ul>
<li>식별된 구조 차이가 LogP(지용성), TPSA(극성 표면적), 수소 결합 능력 등 어떤 특성에 어떤 변화를 주었을지 설명하도록 한다.</li>
</ul>
</li>
<li><p>생체 상호작용 가설 수립</p>
<ul>
<li>그 변화가 표적 단백질 결합 친화도, 대사 안정성, 세포 투과성 등에 어떤 영향을 미칠지 단계별로 가설을 세우게 한다.</li>
</ul>
</li>
<li><p>활성 변화 연결</p>
<ul>
<li>관찰된 활성 값(Activity Cliff)과 앞서 세운 가설을 논리적으로 연결해 “왜” 그 값이 나왔는지 설명하도록 한다.</li>
</ul>
</li>
<li><p>추가 실험 제안</p>
<ul>
<li>도킹 시뮬레이션, ADMET 예측, 합성 계획 등 가설 검증을 위한 구체적인 후속 실험을 제시하도록 한다.</li>
</ul>
</li>
</ol>
<p>이처럼 CoT를 구성하면 LLM이 단순히 답을 맞히는 것 이상으로, 실제 전문가가 논문에 쓸 법한 <strong>체계적인 분석 보고서</strong>를 생성하는 근거가 될 것으로 판단된다.</p>
<br>
<br>

<p><span style="background-color:#E0D3ED"><strong>3. In‑Context Information으로 할루시네이션 억제</strong></span></p>
<blockquote>
<p>“In-Context Information Could Effectively Reduce Hallucination Level.”</p>
</blockquote>
<p><strong>할루시네이션이란?</strong></p>
<ul>
<li>모델이 실제 근거 없는 정보를 생성하는 현상이다. 예를 들어 “해당 화합물은 세포 내에서 X 기전을 사용한다”라는 완전히 허구의 내용을 만들어낼 수 있다.</li>
</ul>
<p><strong>어떻게 줄였나?</strong></p>
<ul>
<li>논문 실험에서 물리화학적 특성, 타겟 단백질 정보, assay 조건 등 가능한 모든 관련 데이터를 프롬프트에 포함했다.</li>
<li>그 결과, LLM은 “주어진 정보만 사용해 답하라”는 일종의 제약을 받게 되고, 불필요한 외부 지식 추측을 최소화했다.</li>
</ul>
<p><strong>정량적 개선</strong></p>
<ul>
<li>In‑Context 정보가 가장 풍부한 설정에서 할루시네이션 비율이 평균 30% 이상 감소했다.</li>
<li>이는 특히 서술형 질문, 복합 메커니즘 설명 과제에서 두드러졌다.</li>
</ul>
<br>
<br>

<p><span style="background-color:#E0D3ED"><strong>4. 서술형(Verbal) 메커니즘 설명의 강점</strong></span></p>
<p><strong>숫자 선택 vs. 서술형</strong></p>
<ul>
<li>논문에서는 객관식 숫자 선택 문제(예: “A, B, C 중 정답은?”)보다 서술형 메커니즘 설명 과제에서 도메인 지식 내장형 프롬프트의 성능 향상이 더 크다고 밝혔다.</li>
<li>이는 LLM이 대규모 텍스트 학습을 통해 축적한 <strong>언어적·논리적 연결 능력</strong>을 활용할 때 진가를 발휘한다는 의미로 볼 수 있다.</li>
</ul>
<p><strong>프로젝트에 주는 시사점</strong></p>
<ul>
<li>“가장 설득력 있는 가설을 기술하라”, “어떤 분자 상호작용 메커니즘이 활성 변화를 초래했는지 상세히 설명하라” 등 <strong>서술형 지시어</strong>를 적극적으로 활용해야 한다.</li>
<li>단순히 “활성 값이 증가한 이유는?”이 아니라, “활성 변화의 분자 수준 메커니즘을 단계별로 기술하라”처럼 문장 구조를 구체화하면 더 좋은 결과를 얻을 수 있다.</li>
</ul>
<br>
<br>

<p><span style="background-color:#E0D3ED"><strong>5. Few‑Shot Example로 Thought‑Chain 학습</strong></span></p>
<p><strong>Few‑Shot CoT</strong></p>
<ul>
<li>단순 예시 제공(Few‑Shot)과는 다르다. 여기서는 예시 하나하나에 전문가의 실제 사고 흐름(Thought‑Chain)이 포함된다.</li>
<li>LLM은 이 과정을 보고 “이런 방식으로 답을 쓰면 되겠구나”를 학습한다.</li>
</ul>
<p><strong>Few‑Shot CoT Prompting 실험 결과</strong></p>
<ul>
<li>구성 요소<ul>
<li>문제 제시: A vs. B 구조·활성 데이터</li>
<li>전문가 CoT: 단계별 분석</li>
<li>최종 해석 및 검증 제안</li>
</ul>
</li>
<li>실제 전문가 예시 2~3개를 제공했을 때, 모델이 초기에 제시된 Thought‑Chain을 흉내 내어 일관성 높은 가설을 생성했다.</li>
<li>예시가 복잡해질수록, 즉 실제 전문가 레퍼런스에 가까울수록 효과가 커졌다.</li>
</ul>
<br>

<p>위 다섯 가지 관점을 통해 논문이 제시한 모든 요소가 서로 유기적으로 결합될 때 도메인 특화 프롬프트가 완성된다. 이 인사이트를 바탕으로 우리 시스템에 실제 적용할 템플릿과 실험 설계 방향을 생각해 본다.</p>
<hr>
<br>
<br>
<br>


<h3 id="🖇-프로젝트-적용-방안">🖇 &nbsp;프로젝트 적용 방안</h3>
<p>우리 시스템은 SAR 테이블에서 Activity Cliff를 감지한 뒤, “왜 활성이 달라졌는가”에 대한 가설 생성 모듈을 LLM으로 구현한다.</p>
<p>이때 위 논문에서 얻은 인사이트를 다음과 같이 구체화해 볼 수 있다.</p>
<ol>
<li><p><strong>전문가 페르소나(Role‑Playing)</strong></p>
<ul>
<li>프롬프트 첫 부분에 “당신은 선임 약화학자입니다” 또는 “당신은 SAR, Activity Cliff 분석 전문가입니다”라고 명시한다.</li>
<li>이를 통해 LLM이 단순 정보 검색 대신, 특정 관점에서 심층 분석하도록 유도한다.</li>
</ul>
</li>
<li><p><strong>단계별 Chain‑of‑Thought(CoT) 구성</strong></p>
<ul>
<li>실제 약화학자가 사용하는 분석 절차를 4~5단계로 스크립트화한다.<ol>
<li>구조 비교: “두 구조 A와 B의 차이점을 정확히 식별하세요.”</li>
<li>물리화학적 영향: “식별된 변경이 소수성, 수소 결합, 전자 분포에 미치는 영향을 추론하세요.”</li>
<li>생체 상호작용 가설: “이 변경이 표적 단백질 결합이나 대사 안정성에 어떻게 작용할지 가설을 제시하세요.”</li>
<li>활성 변화 연결: “이 가설이 관찰된 Activity Cliff를 어떻게 설명하는지 연결하세요.”</li>
<li>추가 실험 제안: “검증을 위한 분자 도킹, ADMET 예측 등 후속 실험을 제안하세요.”</li>
</ol>
</li>
</ul>
</li>
<li><p><strong>풍부한 In‑Context 정보 제공</strong></p>
<ul>
<li>SAR 테이블의 구조-활성 데이터는 물론, LogP, 분자량, TPSA, 수소 결합 수용/공여자 수 등의 물리화학적 특성</li>
<li>타겟 단백질의 명칭·기능·결합 메커니즘</li>
<li>활성 측정 assay 종류·조건 및 관련 선행 연구 결과</li>
<li>이렇게 구체적인 문맥을 함께 주면 LLM이 확실한 앵커를 갖고 답변을 생성한다.</li>
</ul>
</li>
<li><p><strong>서술형 메커니즘 설명 유도</strong></p>
<ul>
<li>질문 의도를 “메커니즘을 상세히 설명하라”, “가장 설득력 있는 약화학적 가설을 제시하라”처럼 서술형 동사로 명확히 규정한다.</li>
<li>단순 나열이나 숫자 선택 대신, 인과관계를 풀어내는 글쓰기를 유도한다.</li>
</ul>
</li>
<li><p><strong>Few‑Shot 예시로 Thought‑Chain 시연</strong></p>
<ul>
<li>프롬프트 앞부분에 2~3개의 실제 SAR 사례를 제시한다.</li>
<li>각 예시는 문제(구조 쌍·활성값), 전문가의 단계별 CoT, 최종 해석 및 검증 제안으로 구성한다.</li>
<li>이를 통해 LLM이 우리가 원하는 답변 흐름과 깊이를 모방하도록 돕는다.</li>
</ul>
</li>
</ol>
<br>

<h4 id="프롬프트-템플릿-예시">프롬프트 템플릿 예시</h4>
<p>위 논문을 통해 얻은 핵심 인사이트를 반영한 간단한 예시 템플릿을 생성해 보았다.</p>
<p>이 모든 요소를 결합한 프롬프트 템플릿을 고도화한다면 이론적으로 강력해 보이지만, 제시된 연구 결과를 그대로 받아들이기보다는 실험을 통해 그 유효성을 직접 검증할 것이다.</p>
<pre><code>당신은 SAR 분석에 능숙한 선임 약화학자입니다.
다음 예시를 참고해 A와 B 구조의 활성 차이를 단계별로 분석하고, 그 원인을 근거 중심으로 설명한 뒤 추가 실험을 제안하세요.

[Example 1]
구조 A: SMILES_1, 활성 10 μM
구조 B: SMILES_2, 활성 0.5 μM
1. 구조 비교
2. 물리화학적 특성 영향
3. 생체 상호작용 가설
4. 활성 변화 연결
5. 추가 실험 제안

— 여기에 실제 예시 CoT와 해석을 삽입 —

[Your Task]
구조 C: SMILES_3, 활성 5 μM
구조 D: SMILES_4, 활성 50 μM

1. 구조 비교
2. 물리화학적 특성 영향
3. 생체 상호작용 가설
4. 활성 변화 연결
5. 추가 실험 제안
</code></pre><p>이처럼 전문가 역할, 단계적 CoT, 풍부한 정보, 서술형 유도, 예시 학습을 결합하면 LLM이 근거 중심의 고품질 가설을 생성할 수 있을 것으로 기대한다.</p>
<hr>
<br>
<br>

<h3 id="🖇-인사이트및-회고">🖇 &nbsp;인사이트및 회고</h3>
<p>지금까지 Liu et al. (2024)의 <strong>도메인 지식 내장형 CoT</strong> 프롬프트 엔지니어링 연구를 분석하고, 우리 SAR 프로젝트에 곧바로 적용할 수 있는 구체적인 전략을 고민해 보았다.</p>
<ul>
<li>전문가 역할(Role‑Playing)로 LLM을 특정 관점에 고정시키고</li>
<li>단계별 CoT로 구조‑활성 관계를 체계적으로 추론하게 하며</li>
<li>In‑Context 정보로 할루시네이션을 억제하고</li>
<li>서술형 설명으로 심층 메커니즘 분석을 유도하며</li>
<li>Few‑Shot 예시로 Thought‑Chain 패턴을 학습시킬 수 있다.<br>

</li>
</ul>
<p>이 글에서 제시한 CoT 기반 설계와 실험 프레임워크와 함께, 프롬프트 설계의 폭과 깊이를 더 확장하기 위해 총 네 건의 연구를 참고할 계획이다.</p>
<p>마찬가지로 연구 결과를 맹신하는 것이 아니라, 오히려 이 지침을 바탕으로 직접 <strong>프롬프트 엔지니어링 실험</strong>을 설계하고, 생성된 가설을 단계별로 기록/비교하면서 최적화해 나갈 것이다. 이 과정을 통해 “이 프롬프트가 정말로 더 나은 가설을 만들어내는가?” 라는 질문에 스스로 답을 내리고, 필요 시에는 템플릿과 파라미터를 재조정해야 한다.</p>
<p>최종적으로 신뢰도·확장성·효율성을 모두 만족하는 SAR 가설 생성 파이프라인을 완성하는 것을 목표로 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[추천시스템] 딥러닝과 추천 시스템]]></title>
            <link>https://velog.io/@jul-ee/DS-%EB%94%A5%EB%9F%AC%EB%8B%9D%EA%B3%BC-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@jul-ee/DS-%EB%94%A5%EB%9F%AC%EB%8B%9D%EA%B3%BC-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Thu, 10 Jul 2025 05:23:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;딥러닝이 가져온 이점
🖇 &nbsp;딥러닝 기반 추천 시스템: 실제 사례
🖇 &nbsp;추천 시스템 적용 시 고려사항</p>
</blockquote>
<br>


<p>추천 시스템의 중요성과 딥러닝을 접목함으로써 생기는 이점을 시작으로, 딥러닝 기반 추천 시스템의 실제 사례와 평가 지표를 통해 좋은 추천 시스템이란 무엇일지 이해하고 정리해 보았다.</p>
<p>실제로 서비스에 추천 시스템을 적용하기 위해 무엇을 고려해야 할지 생각해 보자.</p>
<br>
<br>
<br>

<h3 id="🖇-추천-시스템과-딥러닝">🖇 &nbsp;추천 시스템과 딥러닝</h3>
<h4 id="추천-시스템의-중요성">추천 시스템의 중요성</h4>
<p>사용자에게 적절한 콘텐츠나 상품을 추천하는 시스템은 기업의 성과와 사용자의 경험을 결정할 수 있는 핵심 요소로 자리잡았다. 최근에는 이러한 추천 시스템에 딥러닝 기술이 접목되며 더 높은 정밀도와 개인화를 가능하게 하고 있다.</p>
<p>추천 시스템은 상품이나 콘텐츠를 제공하고 끝나는 게 아니다.</p>
<p>클릭률 상승, 콘텐츠 소비 증가, 사용자 이탈률 감소 등 이어지는 효과를 유도하고 기업의 수익 구조에 실질적인 영향을 끼친다. 사용자 입장에서도 추천의 질이 향상되면 원하는 정보를 더 빠르게 얻을 수 있고, 결과적으로 만족도와 경험 전반이 좋아진다.</p>
<p>이처럼 중요한 추천 시스템은 우리가 매일 접하는 플랫폼 유튜브, 넷플릭스, 쇼핑몰 등에 이미 깊숙하게 자리잡고 있다. 그런데 이제는 단순한 알고리즘만으로는 한계에 부딪히는 시대가 왔다. 복잡한 사용자 행동과 데이터가 쏟아지는 환경에서 기존 방식만으로는 부족해졌고, 그 지점에서 딥러닝이 등장한다.</p>
<hr>
<br>
<br>
<br>

<h3 id="🖇-딥러닝이-가져온-이점">🖇 &nbsp;딥러닝이 가져온 이점</h3>
<h4 id="1-복잡한-데이터의-효율적-처리">1. 복잡한 데이터의 효율적 처리</h4>
<p>딥러닝은 구조적으로 다차원적이고 비정형적인 데이터를 효과적으로 처리할 수 있다.</p>
<p>예를 들어, 사용자의 구매 패턴, 클릭 로그, 선호 장르 등 서로 다른 유형의 정보를 동시에 고려하고, 이를 기반으로 정확한 추천을 제공할 수 있다. 이로 인해 추천 결과의 품질이 한층 높아진다.</p>
<h4 id="2-고차원-특징-학습">2. 고차원 특징 학습</h4>
<p>추천에 사용되는 데이터는 일반적으로 고차원적이다.</p>
<p>수백 명의 사용자와 수만 개의 아이템이 상호작용한 데이터는 수학적으로 수천만 개의 차원을 갖게 된다. 딥러닝은 이런 고차원 공간에서 의미 있는 패턴을 찾아내고, 각 feature 간의 상관관계를 스스로 학습한다.</p>
<h4 id="3-희소-데이터-보완">3. 희소 데이터 보완</h4>
<p>대다수의 사용자는 전체 아이템 중 일부만을 경험하게 된다.</p>
<p>10,000개의 상품 중 10개 정도만 클릭하는 것처럼 말이다. 이렇게 상호작용이 적은 데이터는 &#39;희소하다(sparse)&#39;고 표현되는데, 전통적인 방식에서는 이를 처리하는 데 어려움이 많았다. 딥러닝은 이런 희소 데이터 속에서도 유의미한 연관 관계를 학습할 수 있다는 강점을 지닌다.</p>
<h4 id="4-확장성과-유연성">4. 확장성과 유연성</h4>
<p>사용자 수나 아이템 수가 늘어날수록 추천 시스템의 복잡도는 기하급수적으로 증가한다. 하지만 딥러닝 모델은 대규모 데이터를 학습하는 데 최적화되어 있으며, GPU나 분산처리 시스템과 함께 사용하면 시스템을 안정적으로 확장할 수 있다.</p>
<hr>
<br>
<br>
<br>

<h3 id="🖇-딥러닝-기반-추천-시스템-실제-사례">🖇 &nbsp;딥러닝 기반 추천 시스템: 실제 사례</h3>
<h4 id="youtube-대규모-사용자-추천">YouTube: 대규모 사용자 추천</h4>
<p>YouTube는 딥러닝 기반 추천 시스템을 가장 체계적으로 도입한 사례 중 하나다. 추천 과정은 크게 두 단계로 나뉜다.</p>
<ol>
<li><p><strong>Candidate Generation (후보 생성)</strong></p>
<p> 수많은 동영상 중에서 특정 사용자에게 맞을 법한 수천 개의 후보를 먼저 추려낸다. 이 단계에서는 사용자의 최근 시청 기록, 구독 정보, 검색 쿼리 등 다양한 feature가 활용된다.</p>
</li>
<li><p><strong>Ranking (랭킹 선정)</strong></p>
<p> 추려낸 후보들에 대해 점수를 매기고 최종적으로 보여줄 순서를 정한다. 이때는 콘텐츠의 품질, 시청 시간 예측, 클릭 확률 등이 고려된다.</p>
</li>
</ol>
<p>YouTube는 이처럼 멀티태스크 딥러닝 구조를 통해 정교한 사용자 맞춤 추천을 구현하고 있으며, 관련 내용을 자세히 다룬 논문과 아티클은 다음에서 확인할 수 있다.</p>
<blockquote>
<ul>
<li><a href="https://static.googleusercontent.com/media/research.google.com/ko//pubs/archive/45530.pdf">Deep Neural Networks for YouTube Recommendations (Google Research)</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://daiwk.github.io/assets/youtube-multitask.pdf">Recommending What Video to Watch Next: A Multitask Ranking System</a></li>
<li><a href="https://blog.youtube/inside-youtube/on-youtubes-recommendation-system/">On YouTube’s Recommendation System (YouTube 공식 블로그)</a></li>
</ul>
<br>

<h4 id="netflix-다양한-모델-혼용">Netflix: 다양한 모델 혼용</h4>
<p>Netflix는 초기에 딥러닝 도입을 망설였던 기업 중 하나다. 이유는 전통적인 협업 필터링 방식이 이미 충분히 높은 성능을 보였기 때문이다. 하지만 이후 다양한 feature(사용자, 콘텐츠, 상황 정보 등)를 통합하면서 딥러닝의 장점이 극명하게 드러나기 시작했다.</p>
<p>특히 넷플릭스는 추천 대상이 되는 영역인 인기 콘텐츠 섹션, 장르별 리스트 등에 따라 서로 다른 딥러닝 모델을 적용한다. 하나의 만능 모델이 아니라 각 상황에 최적화된 다양한 모델을 병렬적으로 운영하며 사용자 경험을 극대화하는 방식이다. 관련된 넷플릭스 기술 블로그와 연구 자료는 아래에서 확인할 수 있다.</p>
<blockquote>
<ul>
<li><a href="https://ojs.aaai.org/aimagazine/index.php/aimagazine/article/view/18140">Deep Learning for Recommender Systems: A Netflix Case Study</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://netflixtechblog.com/search?q=recommendation">Netflix Tech Blog – 추천 시스템 관련 글 모음</a></li>
<li><a href="https://research.netflix.com/research-area/recommendations">Netflix Research Areas: Recommendations</a></li>
</ul>
<br>

<h4 id="국내-사례-쿠팡-네이버-당근마켓-등">국내 사례: 쿠팡, 네이버, 당근마켓 등</h4>
<p>국내에서도 딥러닝 기반 추천 시스템은 다양한 산업에서 활발히 활용되고 있다. 아래는 대표적인 발표 아티클이다.</p>
<ul>
<li><p>쿠팡의 추천 시스템 2년간의 진화
  : 실시간 개인화 전환 과정, 모델 서빙 전략, 인덱싱 처리 방식 등을 설명하고 있다.</p>
<blockquote>
<p><a href="https://tv.naver.com/v/11212875">발표 영상</a> / <a href="https://deview.kr/data/deview/2019/presentation/%5B215%5D%20%E1%84%8F%E1%85%AE%E1%84%91%E1%85%A1%E1%86%BC%E1%84%8E%E1%85%AE%E1%84%8E%E1%85%A5%E1%86%AB%E1%84%89%E1%85%B5%E1%84%89%E1%85%B3%E1%84%90%E1%85%A6%E1%86%B7%20%E1%84%87%E1%85%A7%E1%86%AB%E1%84%8E%E1%85%A5%E1%86%AB%E1%84%89%E1%85%A1%20(%E1%84%87%E1%85%A2%E1%84%91%E1%85%A9%E1%84%8B%E1%85%AD%E1%86%BC).pdf">발표 자료 PDF</a></p>
</blockquote>
</li>
<li><p>네이버 AiRS, 라인, 당근마켓, 씽크빅 등 다양한 국내 사례
  : 추천 편향, 인과관계, 사용자 탐색 경험, 개인화 썸네일 등에 대한 고민이 담겨 있다.</p>
<blockquote>
<ul>
<li><a href="https://tv.naver.com/v/16970750">딥러닝 후기시대에서 추천 시스템의 진화</a> / <a href="https://deview.kr/data/deview/session/attach/1400_T1_%EA%B9%80%EA%B2%BD%EB%AF%BC_%EC%B6%94%EC%B2%9C%EC%8B%9C%EC%8A%A4%ED%85%9C%203.0_%EB%94%A5%EB%9F%AC%EB%8B%9D%20%ED%9B%84%EA%B8%B0%EC%8B%9C%EB%8C%80%EC%97%90%EC%84%9C%20%EB%B0%94%EC%9D%B4%EC%96%B4%EC%8A%A4_%EA%B7%B8%EB%9E%98%ED%94%84_%EA%B7%B8%EB%A6%AC%EA%B3%A0%20%EC%9D%B8%EA%B3%BC%EA%B4%80%EA%B3%84%EC%9D%98%20%EC%A4%91%EC%9A%94%EC%84%B1.pdf">발표 자료 PDF</a></li>
<li><a href="https://blog.naver.com/naver_search/222439351406">네이버 뉴스 추천 알고리즘 소개 블로그</a></li>
<li><a href="https://engineering.linecorp.com/ko/blog/a-new-challenge-for-line-timeline-1">LINE 추천 모델 개편기</a></li>
</ul>
</blockquote>
</li>
</ul>
<hr>
<br>
<br>
<br>

<h3 id="🖇-추천-시스템-적용-시-고려사항">🖇 &nbsp;추천 시스템 적용 시 고려사항</h3>
<p>추천 시스템은 알고리즘만으로 완성되는 것이 아니다.</p>
<p>딥러닝 모델의 구조나 성능도 물론 중요하지만, 실제 서비스를 위해서는 다양한 관점을 동시에 고려해야 한다.</p>
<h4 id="1-정량적-평가-지표">1. 정량적 평가 지표</h4>
<p>추천 성능을 평가하는 데는 여러 지표가 존재하며, 이들은 추천의 질뿐 아니라 사용자와의 인터랙션을 정량화하는 데도 도움이 된다.</p>
<ul>
<li><p><strong>Precision@K / Recall@K / Hit Rate@K</strong></p>
<p>  상위 K개의 추천 항목 중에서 얼마나 정확하고 적절한 결과를 제시했는지를 확인하는 지표다.</p>
</li>
<li><p><strong>MAP / NDCG</strong></p>
<p>  추천된 항목의 순서를 고려한 정밀도 측정이다. 사용자가 가장 선호할 법한 아이템이 상위에 위치하는지를 평가한다.</p>
</li>
<li><p><strong>AUC / Log Loss</strong></p>
<p>  전반적인 예측 정확도 및 손실을 수치로 나타낸다. 일반적인 분류 문제와 유사하게 모델의 전체적인 품질을 확인할 수 있다.</p>
</li>
</ul>
<p>이러한 지표들은 <a href="https://netflixtechblog.com/learning-a-personalized-homepage-aa8ec670359a">넷플릭스의 추천 평가 사례</a>나, <a href="https://deview.kr/data/deview/session/attach/1400_T1_%EA%B9%80%EA%B2%BD%EB%AF%BC_%EC%B6%94%EC%B2%9C%EC%8B%9C%EC%8A%A4%ED%85%9C%203.0_%EB%94%A5%EB%9F%AC%EB%8B%9D%20%ED%9B%84%EA%B8%B0%EC%8B%9C%EB%8C%80%EC%97%90%EC%84%9C%20%EB%B0%94%EC%9D%B4%EC%96%B4%EC%8A%A4_%EA%B7%B8%EB%9E%98%ED%94%84_%EA%B7%B8%EB%A6%AC%EA%B3%A0%20%EC%9D%B8%EA%B3%BC%EA%B4%80%EA%B3%84%EC%9D%98%20%EC%A4%91%EC%9A%94%EC%84%B1.pdf">Deview 발표 자료</a>에서 다양한 예시와 함께 설명되고 있으니 참고해도 좋겠다.</p>
<h4 id="2-시스템-구조-및-uiux">2. 시스템 구조 및 UI/UX</h4>
<p>딥러닝 모델을 효율적으로 서빙하고, 사용자에게 의미 있게 전달하는 아키텍처 역시 중요하다. 추천의 정확도만 높다고 좋은 시스템은 아니다. 추천된 결과가 직관적으로 전달되고, 탐색 경험이 좋아야 실제 전환율로 이어진다.</p>
<h4 id="3-운영-및-모니터링">3. 운영 및 모니터링</h4>
<p>실서비스 환경에서는 추천 시스템이 꾸준히 학습하고, 편향을 교정하여 성능을 유지하는 것이 중요하다. Netflix, 쿠팡, 네이버 등의 기업들은 추천 시스템을 지속적으로 모니터링하고 개선하는 체계를 갖추고 있다.</p>
<hr>
<br>
<br>
<br>

<h3 id="🖇-인사이트-및-회고">🖇 &nbsp;인사이트 및 회고</h3>
<p>추천 시스템은 기술적으로도, 비즈니스적으로도 이미 필수적인 요소가 되었다고 볼 수 있다. 특히 딥러닝의 도입은 정교한 개인화, 대규모 데이터 처리, 희소 데이터 대응 등 기존 방식의 한계를 극복하는 데 큰 기여를 하고 있다.</p>
<p>이에 따라 좋은 추천 시스템이란 무엇인지 고민하는 게 필요하다고 생각한다. 높은 정확도를 자랑하는 모델을 만드는 게 아니라, 사용자 경험이나 서비스 전략, UI 구성, 시스템 성능, 모니터링까지 종합적으로 설계되어야 진정한 의미에서 효과적인 시스템일 것이다.</p>
<p>앞으로 추천 시스템은 더 많은 데이터를 다루게 될 것이고, 사용자의 맥락을 더욱 정밀하게 반영하는 방향으로 진화할 것이다. 그 과정에서 무엇을 어떻게 추천할 것인가, 왜 이 추천이 필요한가에 대한 질문을 지속적으로 던질 필요가 있겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[💊 영양제 Check! 프로젝트 회고]]></title>
            <link>https://velog.io/@jul-ee/%EC%98%81%EC%96%91%EC%A0%9C-Check-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jul-ee/%EC%98%81%EC%96%91%EC%A0%9C-Check-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 05 Jul 2025 01:50:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🔗 &nbsp;이전 글</p>
<p><a href="https://velog.io/@jul-ee/DATAthon-1-%ED%8C%80-%EB%B9%8C%EB%94%A9-%EB%B0%8F-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%84%A0%EC%A0%95">💊 영양제 Check! 개발기</a></p>
</blockquote>
<br>

<p>이번 프로젝트는 &quot;영양제 추천 서비스 만들기&quot; 가 아니라, LLM이 어떻게 더 신뢰성 있게 정보를 전달할 수 있을까에 대한 고민과 실험이었다고 할 수 있다.</p>
<p>&#39;누가 먹어도 괜찮은 영양제&#39; 같은 모호한 추천이 아니라, “이 사용자에게, 이 질환에 맞는, 이 성분이 왜 필요한가” 를 설명할 수 있는 시스템이 목표였다.</p>
<p>개인적으로는 도메인 이해, 구조 설계, 팀 협업, 프롬프트 튜닝, 실패 사례 복기까지 다방면에서 얻은 게 많은 프로젝트였다.
<br></p>
<p>이 글에서는 그 과정에서 배우고 느낀 점들을 정리해 보려고 한다.</p>
<br>
<br>
<br>


<h3 id="🖇-도메인-지식의-중요성과-전문성-확보의-필요성">🖇 &nbsp;도메인 지식의 중요성과 전문성 확보의 필요성</h3>
<p>기술만 잘 써서 좋은 결과가 나오는 건 아니라는 건 당연하다.</p>
<p>건강기능식품처럼 민감한 도메인에서는 단어 하나가 의학적으로 다른 의미로 해석되기 때문에, GPT가 아무 말이나 하는 경우에 이를 그대로 신뢰하면 안 된다.</p>
<p>예를 들어 “눈에 좋은 영양제 뭐 있어?” 라고 했을 때 나같은 경우에는 루테인 정도를 떠올릴 수 있었다. 하지만 도메인 조사와 시나리오별 신뢰할 수 있는 관련 학술 문헌들을 찾아보면서 실제로는 루테인, 지아잔틴, 비타민A, 아스타잔틴 등 다양한 성분이 있고, 각각의 작용 기전이 다르다는 것을 알 수 있었다.</p>
<p>게다가 당뇨 환자가 섭취하면 안 되는 성분도 있을 수 있고, 특정 약물과 상호작용이 발생하는 경우도 있어서 성분-질환-약물 간 관계를 모르면 그냥 위험한 추천이 될 수도 있다. 이런 부분은 결국 논문을 직접 보고, 근거를 확인하고, 정리하는 과정이 필요했다.</p>
<p>물론 RAG의 지식베이스가 되는 학술 문헌들을 찾는 과정이 쉽지 않았다. 해당 논문이 어떤 전제를 하고, 어떤 근거로, 어떤 결과를 냈는지 확인하고 실제 조사한 내용들과 비교하여 선정하는 과정에서 신중해야 했고 시간도 오래 걸렸다. 이 작업을 통해 성분 정보의 신뢰도가 완전히 달라질 수 있기 때문에 소홀히 할 수 없었다.</p>
<p>그냥 “GPT가 추천해줬대” 가 아니라, “이 논문에서 실제로 A 성분이 B 질환에 효과 있다고 보고했어” 라고 근거를 제시하는 시스템을 목표로 했기 때문이다.</p>
<p>이 프로젝트를 정말 고도화하고 싶다면 아예 당뇨, 치매, 고혈압 등 특정 질환에 중점을 두면 지금보다 더 신뢰할 수 있는 정보를 제공할 수 있을 것 같다는 생각이 들었다. 확실히 어떤 서비스든 관련 도메인을 깊게 알아야 경쟁력이 생긴다는 점을 다시 한 번 이해할 수 있었다.</p>
<br>
<br>

<h3 id="🖇-학술-논문-기반-rag에서의-프롬프트-엔지니어링-어려움">🖇 &nbsp;학술 논문 기반 RAG에서의 프롬프트 엔지니어링 어려움</h3>
<p>잎서 언급한 것처럼 RAG의 지식베이스로 사용할 학술 논문을 선정하는 것에도 어려움이 있었지만, 진짜 힘들었던 건 여기부터였다.</p>
<p>논문 PDF를 벡터화하는 건 어렵지 않은데, 문제는 LLM이 거기서 제대로 된 답을 끌어오게 만드는 과정이었다.</p>
<p>단순히 질문하면 원하는 성분이 바로 딱 나오는 게 아니었던 터라 아래와 같은 고민이 계속 반복됐다.</p>
<ul>
<li>문서 chunk를 어떻게 나누면 정보 손실 없이 분할될까?</li>
<li>각 chunk에 어떤 메타 정보를 넣어야 필터링과 응답이 정확해질까?</li>
<li>프롬프트 안에 어떤 조건을 넣어야 환각 없이 응답하게 만들 수 있을까?</li>
</ul>
<p>특히 JSON 형태의 응답을 유도하는 게 쉽지 않았다.</p>
<p>&quot;허용된 성분 외에는 말하지 마라&quot;, &quot;형식은 JSON만 허용&quot;, &quot;마크다운 금지&quot; 같은 조건을 쭉 나열해도 종종 형식이 틀어졌다.</p>
<p>이런 경우는 파싱이 깨져버리기 때문에 응답을 어떻게 안정적으로 받을지에 대한 예외처리도 필요했다.</p>
<p>수동으로 확인해서 수정하기도 하고, 많은 시행착오가 있었지만 그 과정에서 놓치고 있던 부분을 다잡을 수 있었다.</p>
<blockquote>
<p>프롬프트 엔지니어링은 내가 원하는 걸 요구하는 과정이 아니라, 문서 구조와 LLM 동작 방식을 함께 고려한 설계라는 것 !</p>
</blockquote>
<br>
<br>

<h3 id="🖇-tavily-ai-적용-과정에서의-시행착오">🖇 &nbsp;TAVILY AI 적용 과정에서의 시행착오</h3>
<p>초반에는 TAVILY AI를 사용해서 RAG를 구성해 보려고 했다.</p>
<p>실시간 검색도 되고, 웹 결과를 요약해서 보여주니까 뭔가 요긴할 것 같았는데 결론부터 말하면 이 프로젝트에는 맞지 않았다.</p>
<p>가장 큰 문제는 “출처가 신뢰할 만한가?” 였다.</p>
<p>논문이 아니라 블로그, 뉴스 기사 위주로 결과가 제공될 수 있는데 이런 정보는 챗봇이 답변할 근거로 삼기엔 너무 불안정하다. <code>TavilyClient</code> 는 원하는 도메인(PubMed, 식약처 등)을 지정하더라도 정확히 그 문서만 가져오진 못하고,  결국 AI가 요약한 결과를 출력하는 구조라 LLM 자체의 창작이 섞이게 된다.</p>
<p>우리 팀이 만들고자 한 건 논문 기반의 응답이었고, &quot;왜 이 성분이 추천되는지&quot; 를 출처까지 정리해서 설명할 수 있는 구조여야 했다. 결국 TAVILY는 실험용으로 써보는 데 그쳤고, 본 구현에서는 완전히 제외하게 되었다.</p>
<p>그래도 덕분에 프로젝트에서 설정한 방향과 목표에 적절한 구조는 어떤 것인지를 계속해서 상기하고 고민해 나갈 수 있었다.</p>
<br>
<br>

<h3 id="🖇-시스템-설계와-사용자-경험-관점">🖇 &nbsp;시스템 설계와 사용자 경험 관점</h3>
<p>이번 프로젝트를 기술 과시형 챗봇이 아니라 &quot;진짜 쓸모 있는 서비스&quot; 처럼 느껴지도록 하고 싶었다. 그러기 위해서는 사용자 경험(UX)을 고려한 설계가 필요했다.</p>
<p>성분을 뽑아주는 데서 끝나는 게 아니라,</p>
<ul>
<li>어떤 성분을 추천하는지</li>
<li>왜 추천하는지</li>
<li>어떤 성분은 피해야 하는지</li>
<li>출처는 무엇인지</li>
<li>그 성분이 실제로 들어간 제품은 무엇인지까지</li>
</ul>
<p>하나의 흐름으로 연결되어 있다는 점이 중요했다.</p>
<p>게다가 모든 결과는 출처가 있는 문서 기반이고, 최종 추천도 단순 응답이 아니라 실제 공공데이터에 있는 제품 목록에서 골라주기 때문에 &quot;이걸 먹어볼까?&quot; 라는 사용자 행동까지 자연스럽게 이어지는 것을 기대할 수 있었다.</p>
<p>Streamlit 기반으로 UI를 구성했는데, 프론트엔드가 과하지 않아서 오히려 더 깔끔했다. 전체 구조를 사용자 입장에서 체계적으로 연결할 수 있었던 점이 인상 깊었다.</p>
<p>다만, 시간의 한계로 구현하지 못했던 성분에 대한 출처 명시 부분에서는 고민이 좀 더 필요하다. 피드백 주신 것처럼 사용자에게 &quot;추천 성분&quot; 또는 &quot;피해야 할 성분&quot;을 제시할 때, <strong>사용자 경험을 해치지 않도록</strong> 해당 내용을 뒷받침하는 연구 결과나 임상 근거를 함께 제시하는 것이 중요할 것 같다.</p>
<br>
<br>


<h3 id="🖇-좋은-팀원과의-협업-명확한-목표-설정">🖇 &nbsp;좋은 팀원과의 협업, 명확한 목표 설정</h3>
<p>원활한 상호 소통으로 유능한! 팀원들과 함께하는 과정에서 많은 것들을 배울 수 있었다. 덕분에 여러 시행착오가 있었음에도 매끄럽게 진행될 수 있었다고 생각한다.</p>
<p>데이터톤에서는 놓쳤던, 프로젝트를 시작할 때부터 “무엇을 만들 것인가” 가 아니라 <strong>“왜 만들 것인가”</strong> 에 대한 합의가 있었다. 이 논의를 하는 동안, 어떤 프로젝트든 솔직하게 되돌아보고 회고하는 과정이 반드시 필요하다는 소소한 생각이 들었었다.</p>
<p>LLM 프로젝트는 &quot;뭔가 똑똑해 보이는 챗봇을 만들자&quot; 같은 흐름으로 갈 수 있는데, &quot;건강 정보는 신뢰가 생명이다&quot; 라는 명확한 문제의식을 바탕으로 출발했기 때문에 프롬프트 구조를 짤 때도, 데이터를 고를 때도, 기술 선택에 있어서도 모두 사용자 중심의 일관된 기준이 있었다.</p>
<p>아이디어 기획, 기술 조사, 논문 기반 문서 정제, 프롬프트 엔지니어링, 공공데이터 수집, RAG 구조 설계, UI 구성 등 서로가 맡은 파트를 책임감 있게 가져갔고, 필요한 순간엔 빠르게 공유하고 피드백을 주고받았다.</p>
<p>프로젝트 중 기능을 구현하다 보면 가끔 &quot;내가 지금 이걸 왜 하고 있지?&quot; 라는 의문이 들 때도 종종 있었는데, 이번에는 처음부터 끝까지 목적과 방향이 분명했기 때문에 동기부여가 계속 유지될 수 있었던 것 같다.</p>
<blockquote>
<p>앞으로도 어떠한 프로젝트에서 뿐만 아니라
무엇을 하는지 보다, 왜 하는지를 끊임없이 생각하는 자세를 지니자.</p>
</blockquote>
<br>
<br>
<br>


<h3 id="🖇-배운-점과-앞으로의-각오">🖇 &nbsp;배운 점과 앞으로의 각오</h3>
<p>이제는 궁금한 게 생기면 일단 GPT나 Gemini 같은 LLM에게 먼저 묻는 게 자연스러울 정도로, 누구나 쉽게 정보에 접근할 수 있는 시대가 되었다.</p>
<p>이번 프로젝트를 하면서 이 지점에 대해 깊이 고민할 수 있었다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&quot;LLM이 답변해준 정보가 정확한가?&quot;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;그 근거는 무엇인가?&quot;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;사용자가 스스로 검토할 수 있는 구조인가?&quot;</p>
<p>이런 질문에 대해 기술적으로만 접근하는 게 아니라, 전체 구조와 설계의 문제로 확장할 수 있었다.</p>
<p>물론 RAG 구조를 처음부터 직접 설계하고, 프롬프트를 반복해서 튜닝하고, 논문 중심의 도메인을 설계하고, Streamlit으로 UI를 구성하고, 식약처 공공 API까지 연동하는 과정에서 기술적으로도 좋은 경험이 되었다.</p>
<p>구현하고 테스트하는 과정에서 계속 나를 따라다녔던 의문이 있었다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;“이 시스템을 과연 사용자가 믿고 쓸 수 있을까?”</p>
<p>겉보기엔 그럴듯하게 작동하는 챗봇이지만</p>
<p>정작 그 안에서 사용자가 출처를 확인할 수 없다거나, 논문 기반이 아닌 내용을 생성해버린다거나, 같은 질문에 따라 답이 바뀐다면 그건 신뢰할 수 있는 시스템이 아닐 것이다.</p>
<p>그래서 이번 프로젝트에서는 그런 허점을 막기 위해 하나하나 설계를 조이고, 근거를 명시하고, 정보가 ‘떠도는 말’이 아니라 ‘출처 있는 지식’이 되도록 만드는 데 초점을 맞추게 되었다. 프로젝트를 되돌아보면 기술보다 설계가 중요했고, 설계보다 문제 정의가 더 중요했다고 정리할 수 있겠다.</p>
<p>앞으로 다가올 해커톤이나 프로젝트에서도 그럴듯한 AI가 아니라
진짜 도움을 주는, 그리고 검증 가능한 구조로 신뢰를 줄 수 있는 AI 서비스를 만들고 싶다.</p>
<br>

<blockquote>
<p>약 3일 간의 짧은 프로젝트였지만 유익한 경험이 되었다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[💊 영양제 Check! 개발기]]></title>
            <link>https://velog.io/@jul-ee/%EC%98%81%EC%96%91%EC%A0%9C-Check-%EA%B0%9C%EB%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@jul-ee/%EC%98%81%EC%96%91%EC%A0%9C-Check-%EA%B0%9C%EB%B0%9C%EA%B8%B0</guid>
            <pubDate>Thu, 03 Jul 2025 10:54:33 GMT</pubDate>
            <description><![CDATA[<h4 id="개인-맞춤과-신뢰-기반을-모두-갖춘-건강기능식품-추천-시스템의-설계와-구현">개인 맞춤과 신뢰 기반을 모두 갖춘 건강기능식품 추천 시스템의 설계와 구현</h4>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/dc372e21-e3f2-471a-b4b8-567ce2092b65/image.gif" alt=""></p>
<blockquote>
<p>🔗 <a href="https://github.com/jul-ee/langchainthon-nutri-check">GitHub repository</a></p>
<p>프로젝트 기간: &nbsp;2025.06.30 - 2025.07.03 &nbsp;(4인 프로젝트)</p>
</blockquote>
<br>
<br>

<h2 id="0-목차">0. 목차</h2>
<ol>
<li><a href="#1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8F%84%EC%9E%85-%EC%99%9C-%EC%98%81%EC%96%91%EC%A0%9C-%EC%B6%94%EC%B2%9C%EC%9D%B8%EA%B0%80">프로젝트 도입: 왜 &#39;영양제 추천&#39;인가?</a></li>
<li><a href="#2-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A9%ED%91%9C">프로젝트 목표</a></li>
<li><a href="#3-%EC%96%B4%EB%96%A4-%EA%B8%B0%EC%88%A0%EC%9D%84-%EC%99%9C-%EC%84%A0%ED%83%9D%ED%96%88%EB%8A%94%EA%B0%80">어떤 기술을 왜 선택했는가?</a></li>
<li><a href="#4-%EB%AC%B8%ED%97%8C-%EC%88%98%EC%A7%91-%EB%B0%8F-%EC%A0%84%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%8B%9D">문헌 수집 및 전처리 방식</a></li>
<li><a href="#5-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%84%A4%EA%B3%84-%ED%8F%AC%EC%9D%B8%ED%8A%B8">기술적 설계 포인트</a></li>
<li><a href="#6-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9E%85%EB%A0%A5-%EC%B2%98%EB%A6%AC-%EB%B0%8F-case-%EB%B6%84%EB%A5%98-%EB%A1%9C%EC%A7%81">사용자 입력 처리 및 Case 분류 로직</a></li>
<li><a href="#7-%EA%B3%B5%EA%B3%B5%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B8%B0%EB%B0%98-%EC%A0%9C%ED%92%88-%EC%B6%94%EC%B2%9C-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8">공공데이터 기반 제품 추천 파이프라인</a></li>
<li><a href="#8-streamlit-%EA%B8%B0%EB%B0%98-ui-%EA%B5%AC%EC%84%B1-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B2%BD%ED%97%98-%EC%84%A4%EA%B3%84">Streamlit 기반 UI 구성 및 사용자 경험 설계</a></li>
<li><a href="#9-%EC%8B%A4%ED%96%89-%EA%B2%B0%EA%B3%BC">실행 결과</a></li>
<li><a href="#10-%EA%B0%9C%EC%84%A0-%EB%B0%A9%ED%96%A5">개선 방향</a></li>
<li><a href="#11-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0">프로젝트 회고</a></li>
</ol>
<br>
<br>
<br>


<h2 id="1-프로젝트-도입-왜-영양제-추천인가">1. 프로젝트 도입: 왜 &#39;영양제 추천&#39;인가?</h2>
<p>최근 몇 년 사이 건강기능식품 시장은 트렌드를 넘어 일상 소비로 정착되었다. 그러나 수백 개의 브랜드와 성분, 제품군이 동시에 쏟아지는 상황에서 소비자는 여전히 어떤 영양제를 어떻게 선택해야 할지 명확한 기준을 갖기 어렵다.</p>
<p>게다가 최근에는 검색보다 ChatGPT, Gemini와 같은 대형 언어모델(Large Language Model, LLM) 기반 챗봇에게 직접 질문하여 정보를 얻는 게 자연스러운 시대가 되었다.</p>
<p>“무슨 영양제를 먹는 게 좋을까?”, “루테인은 눈에 진짜 효과 있어?”, “이 제품은 당뇨 환자도 먹을 수 있어?” 같은 질문도 전문가가 아니라 챗봇에게 먼저 묻고는 한다.</p>
<p>그런데 &quot;그 정보, 그대로 믿어도 될까?&quot; 하는 의문도 가지고 있을 것이다.</p>
<p>실제로 건강 정보나 성분 추천에 대해 이들 챗봇이 제공하는 응답은 과거에 비해 상당히 정확하고 자연스럽게 보이지만, 여전히 다음과 같은 구조적인 문제가 존재한다.</p>
<ul>
<li><p>모델마다 답변이 다르며 사용자는 어떤 응답이 진짜인지 판단할 기준이 없다.</p>
</li>
<li><p>출처가 명시되지 않거나, 논리의 근거가 불분명하여 검증이 어렵다.</p>
</li>
<li><p>특히 건강 도메인에서는 존재하지 않는 성분을 제시하거나,</p>
<p>  의학적으로 권장되지 않는 정보를 <strong>사실처럼 응답하는 &#39;환각(Hallucination)&#39;</strong> 현상이 여전히 발생한다.</p>
</li>
</ul>
<p>이러한 배경에서 본 프로젝트는 &#39;영양제 추천 시스템&#39;을 만드는 것이 아니라, LLM의 불확실한 응답 구조 자체를 신뢰 가능한 구조를 제공하기 위한 기술적 실험으로 시작되었다.</p>
<blockquote>
<p>&quot; GPT보다 믿을 수 있는, <em>실제로 도움이 되는</em> &nbsp;영양제 추천 시스템 ”</p>
</blockquote>
<br>
<br>
<br>


<h2 id="2-프로젝트-목표">2. 프로젝트 목표</h2>
<p>우리 팀은 하나의 방향 속에서 세 가지의 목표를 정확하게 잡았다.</p>
<h4 id="21-신뢰-가능한-정보-제공">2.1 &nbsp;신뢰 가능한 정보 제공</h4>
<ul>
<li><p>GPT-4 기반 LLM이 답변을 생성할 때 임의의 성분을 &#39;창작&#39;하지 않도록 하기 위해</p>
<p>  오직 문서 기반의 RAG 구조를 채택하였다.</p>
</li>
<li><p>사용된 문서는 학술 논문, 정부 가이드라인, 식약처 자료 등 46편의 PDF 문서이며,</p>
<p>  LLM은 반드시 이 문서 내 정보에 기반하여 답변을 생성해야 한다.</p>
</li>
</ul>
<h4 id="22-사용자-맞춤형-추천">2.2 &nbsp;사용자 맞춤형 추천</h4>
<ul>
<li><p>사용자로부터 나이, 성별, 질환, 복용 중 약물, 선호 성분 등의 정보를 입력받고,</p>
<p>  키워드 기반으로 사전 정의된 <em>Case A~D 시나리오</em> 로 분류하여 각기 다른 처방을 반환하도록 설계하였다.</p>
</li>
<li><p>동일한 사용자 질문이라도 복용 중 약물 또는 질환에 따라 금기 성분이 달라질 수 있으며,</p>
<p>  이 분류를 통해 상호배타적인 조건 처리 및 다중 처방 적용이 가능하도록 구성하였다.</p>
</li>
</ul>
<h4 id="23-실제-제품-추천-및-실행-가능성-확보">2.3 &nbsp;실제 제품 추천 및 실행 가능성 확보</h4>
<ul>
<li><p>성분 정보를 알려주는 데 그치지 않고,</p>
<p>  식품의약품안전처 공공데이터(API)를 통해 추천 성분이 포함된, 피해야 할 성분은 제외된 <strong>실제 제품</strong>을 추천한다.</p>
</li>
<li><p>유사도 기반 벡터 검색을 통해 추천 제품을 찾아주며,</p>
<p>  금기 성분이 포함된 제품은 실시간으로 필터링하여 제외한다.</p>
</li>
<li><p>실제 제품을 사용자에게 연결하여 정보 소비에 그치지 않고 실행 가능한 사용자 행동으로 이어지는 것을 기대하였다.</p>
</li>
</ul>
<blockquote>
<p>프로젝트의 방향 설정 후, 아래와 같이 역할을 분배하였다.</p>
</blockquote>
<div align="center">

<table>
<thead>
<tr>
<th align="center">팀장 A</th>
<th align="center">팀원 B (본인)</th>
<th align="center">팀원 C</th>
<th align="center">팀원 D</th>
</tr>
</thead>
<tbody><tr>
<td align="center">⸰ PM<br>⸰ 아이디어 기획<br>⸰ 기술 조사</td>
<td align="center">⸰ 프롬프트 엔지니어링<br>⸰ 1차 RAG 설계<br>⸰ 응답 정밀도 개선</td>
<td align="center">⸰ 학술 논문 조사<br>⸰ 문서 정제<br>⸰ 시나리오 기반 테스트</td>
<td align="center">⸰ 공공데이터 파이프라인 구축<br>⸰ 2차 RAG 설계<br>⸰ 벡터 DB 구축</td>
</tr>
</tbody></table>
</div>

<br>
<br>
<br>
<br>

<h2 id="3-어떤-기술을-왜-선택했는가">3. 어떤 기술을 왜 선택했는가?</h2>
<h4 id="31-llm-환각-방지를-위한-rag-구조-도입">3.1 &nbsp;LLM 환각 방지를 위한 RAG 구조 도입</h4>
<p>GPT-4는 강력한 문장 생성 능력을 갖추었지만, 정해진 지식 범위 내에서만 작동하지 않는다. 특히 건강 도메인에서는 존재하지 않는 성분이나 제품명을 제시하는 &#39;환각(Hallucination)&#39; 문제가 치명적이다.</p>
<p>이 문제를 해결하기 위해, 본 프로젝트에서 <code>RAG (Retrieval-Augmented Generation)</code> 구조를 채택하는 것이 적절하다고 판단하였다.</p>
<p>RAG는 외부 문서에서 정보를 검색하고 이를 기반으로 LLM이 응답을 생성하는 방식으로,&quot;문서 기반 지식의 통제&quot; 와 &quot;LLM 응답의 정확성&quot; 을 동시에 확보할 수 있는 구조이다.</p>
<blockquote>
<ul>
<li>Hallucination을 원천적으로 차단 가능 (문서 외 정보 사용 제한)</li>
</ul>
</blockquote>
<ul>
<li>응답 신뢰도 향상 및 출처 명시 가능</li>
<li>LangChain 기반으로 RAG 체인을 구성하기 용이함</li>
</ul>
<br>

<h4 id="32-문서-임베딩-및-검색을-위한-faiss-사용">3.2 &nbsp;문서 임베딩 및 검색을 위한 FAISS 사용</h4>
<p>문서 검색 과정에서 핵심이 되는 것은 사용자의 질문과 가장 유사한 문단을 빠르게 찾아내는 것이다. 이를 위해</p>
<p><code>OpenAI Embedding + FAISS 인덱싱</code> 구조를 선택하였다.</p>
<p>FAISS(Facebook AI Similarity Search)는 고차원 벡터 간 유사도 계산에 특화된 라이브러리로, 수천~수만 개의 벡터에서 유사한 결과를 빠르게 찾아내는 데 적합하다.</p>
<p>Document 객체에 메타 정보를 함께 저장하므로 검색 후 원문 출처, 문서명, 출판연도 등도 함께 추출 가능하다는 점에서 프로젝트의 방향성에 부합한다.</p>
<pre><code class="language-python">from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings

embedding = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(documents, embedding)
vectorstore.save_local(&quot;faiss_db/&quot;)
</code></pre>
<blockquote>
<ul>
<li>1만 건 이상의 문서를 벡터화하여도 검색 속도가 빠름</li>
</ul>
</blockquote>
<ul>
<li>Local 환경에서 저장, 로드, 추가 삽입(add_documents) 가능</li>
<li>LangChain과 통합 가능 (FAISS.from_documents(), load_local() 등)</li>
</ul>
<br>

<h4 id="33-실제-제품-추천을-위한-공공데이터-api-활용">3.3 &nbsp;실제 제품 추천을 위한 공공데이터 API 활용</h4>
<p>문서 기반 성분 추천만으로는 실제 사용자 액션으로 이어지기 어렵다.</p>
<p>이를 해결하기 위해 <code>식품의약품안전처 건강기능식품정보 Open API</code> 를 사용하였다.</p>
<p>이 API는 약 10,000건의 영양제 제품 정보를 제공하며 기능성, 성분, 주의사항, 섭취방법 등의 정보를 포함하고 있다.</p>
<blockquote>
</blockquote>
<ul>
<li>공공기관이 제공하는 신뢰 가능한 제품 정보</li>
<li>일일 10,000건 호출 가능</li>
<li>제품명, 기능성, 주의사항, 섭취량 등의 필드가 존재하여 추천 후 연계 가능</li>
</ul>
<br>
<br>
<br>
<br>

<h2 id="4-문헌-수집-및-전처리-방식">4. 문헌 수집 및 전처리 방식</h2>
<p>사용된 문헌은 총 46편의 학술 논문, 건강기능식품 가이드라인, 식약처 자료로 구성되어 있다. 이 문헌들은 PDF 형식으로 수집되었고, 문단 단위로 분할하고 벡터화하기 위한 사전 정제 작업이 필요하였다.</p>
<p>아래의 과정으로 전처리를 수행하였다.</p>
<ol>
<li>텍스트 추출: &nbsp;<code>PyMuPDF</code> 기반으로 PDF에서 본문 텍스트 추출</li>
<li>의미 단위 분리: &nbsp;불필요한 공백/머리말 제거 후, 문단 또는 항목 기반으로 의미 단위 분리</li>
<li>LangChain 문서화: &nbsp;<code>Document</code> 객체로 변환 (메타 정보 포함: 출처, 제목, 문서 유형 등)</li>
</ol>
<p>문서 분할은 단순 길이 기준이 아닌 문맥 보존을 위한 chunk overlap 기법을 적용하였으며 이후 Embedding 과정으로 연결된다.</p>
<pre><code class="language-python">from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

loader = PyPDFLoader(&quot;간건강_가이드라인.pdf&quot;)
pages = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=700, chunk_overlap=100)
documents = splitter.split_documents(pages)
</code></pre>
<br>
<br>
<br>
<br>

<h2 id="5-기술적-설계-포인트">5. 기술적 설계 포인트</h2>
<p>RAG(Retrieval-Augmented Generation)를 두 번 활용해 신뢰성과 정밀도를 동시에 확보합니다.</p>
<ol>
<li>첫 번째 RAG는 사용자의 건강 상태와 선호에 따라 논문 기반 성분 정보를 추출하고,</li>
<li>두 번째 RAG는 해당 성분을 기준으로 공공데이터에서 실제 제품을 추천한다.</li>
</ol>
<p>이렇게 2단계로 분리된 구조는 &quot;왜 이 성분을 추천하는가&quot; 와 &quot;그 성분이 포함된 제품은 무엇인가&quot; 를 연결할 수 있다.</p>
<h4 id="50-rag-구조">5.0 &nbsp;RAG 구조</h4>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/29b2e5cf-ee28-4f04-908b-3a046c0b462d/image.png" alt=""></p>
<h4 id="51-카테고리별-문서-벡터-db-분리-및-앙상블-리트리버-구성">5.1 &nbsp;카테고리별 문서 벡터 DB 분리 및 앙상블 리트리버 구성</h4>
<p>사용자 입력은 단일 증상이 아닌 여러 건강 영역을 포함할 수 있다.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ex. 눈 건강 + 피로 + 커피 과다 섭취</p>
<p>이를 고려하여 문서 벡터 DB를 질환/이슈별 시나리오 케이스를 분리 저장하였으며, LangChain의 <code>EnsembleRetriever</code>를 통해 다중 검색이 가능하도록 구성하였다.</p>
<pre><code class="language-python">retriever1 = FAISS.load_local(&quot;faiss_db/case_A&quot;, embedding)
retriever2 = FAISS.load_local(&quot;faiss_db/case_b&quot;, embedding)

ensemble_retriever = EnsembleRetriever(
    retrievers=[retriever1, retriever2],
    weights=[0.5, 0.5]
)
</code></pre>
<br>

<h4 id="52-사용자-입력-처리-및-case-분류-로직">5.2 &nbsp;사용자 입력 처리 및 Case 분류 로직</h4>
<p>사용자 입력 키워드 추출 후, 시나리오별 Case를 분류하여 화이트리스트 기반 프롬프트를 설계하였다.</p>
<ul>
<li>RAG 응답 생성 시, LLM이 사용할 수 있는 성분 리스트를 사전에 명시하여 전달</li>
<li>프롬프트 내에 &quot;JSON only&quot;, &quot;마크다운 금지&quot;, &quot;허용된 성분 외 사용 금지&quot; 명시</li>
<li>결과적으로 환각 방지 + 응답 일관성 + 파싱 안정성 확보</li>
</ul>
<p>&gt;&gt; &nbsp;<a href="#6-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9E%85%EB%A0%A5-%EC%B2%98%EB%A6%AC-%EB%B0%8F-case-%EB%B6%84%EB%A5%98-%EB%A1%9C%EC%A7%81">6. 사용자 입력 처리 및 Case 분류 로직</a> 참고</p>
<br>

<h4 id="53-공공데이터-기반-제품-추천-파이프라인">5.3 &nbsp;공공데이터 기반 제품 추천 파이프라인</h4>
<p>&gt;&gt; &nbsp;<a href="#7-%EA%B3%B5%EA%B3%B5%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B8%B0%EB%B0%98-%EC%A0%9C%ED%92%88-%EC%B6%94%EC%B2%9C-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8">7. 공공데이터 기반 제품 추천 파이프라인</a> 참고</p>
<br>
<br>
<br>
<br>


<h2 id="6-사용자-입력-처리-및-case-분류-로직">6. 사용자 입력 처리 및 Case 분류 로직</h2>
<p>의료·영양 도메인은 &quot;창의성&quot; 보다 안전성·근거 기반이 최우선이다.</p>
<p>실제 논문마다 결과가 엇갈리고, 전문가들조차 단일 정답을 내리기 어려운 영역이므로, &quot;모든 상황에 맞는 답변&quot; 대신 시나리오(case) 단위로 검증된 성분만 제공하도록 구현하였다. 이 과정에서 논문/연구 자료 등 신빙성 있는 자료 조사를 통해 도메인에 대한 정확한 이해가 필요했다.
<br></p>
<p>본 프로젝트는 사용자에게 다음 항목의 입력을 요구한다.</p>
<ul>
<li>나이, 성별</li>
<li>현재 가지고 있는 질환 혹은 증상</li>
<li>복용 중인 약품 및 영양제</li>
<li>원하는 영양제의 특징 또는 조건 (ex. 눈 건강, 근육 회복, 뇌 건강 개선 등)</li>
</ul>
<p>이 입력은 단순히 문자열로 구성되어 있으나, 이후 전체 파이프라인에서 핵심적인 역할을 하게 되며</p>
<p>특히 <code>&quot;사용자 상태 분류 → case별 처방 분기&quot;</code> 의 기준이 된다.</p>
<h4 id="61-case-ad-분류-체계">6.1 &nbsp;Case A~D 분류 체계</h4>
<p>초기 설계 단계에서 구체적인 페르소나를 지정하여 사용자 요구를 몇 가지 유형으로 분류하였다.</p>
<p> 이는 질문의 형태를 제한하기 위함이 아니라, LLM의 프롬프트 안정성과 응답 품질을 확보하기 위한 구조적 장치이다. 앞서 언급한 것처럼 도메인 특성 상 &quot;모든 상황에 맞는 답변&quot; 보다는 한정된 대상이라도 검증된 성분 정보를 제공하는 것이 중요하기 때문이다.</p>
<ol>
<li>Case_A, Case_B 에서는 동일한 &quot;음주/흡연&quot; 이라는 변수를 주어 비교 실험을 설계하였다.</li>
<li>Case_C, Case_D 에서는 특정 질환에 중점을 두고 정확한 결과를 도출해 내는 것을 목표로 하였다.</li>
</ol>
<ul>
<li>Case_A: &nbsp;눈 건강 + 간 건강 관련</li>
<li>Case_B: &nbsp;커피/카페인 과다 섭취</li>
<li>Case_C: &nbsp;심혈관 및 혈압 관리</li>
<li>Case_D: &nbsp;복용 중인 약품과 충돌 가능성 (예: 철분 + 항생제)</li>
</ul>
<br>

<h4 id="62-사용자-입력-분석-함수">6.2 &nbsp;사용자 입력 분석 함수</h4>
<p>사용자의 자유 입력을 사전 정의된 Case에 매핑하기 위한 함수를 구성하였다.</p>
<pre><code class="language-python">def categorize_user_query(user_input: str) -&gt; list:
    case_list = []
    if &quot;간&quot; in user_input or &quot;음주&quot; in user_input or &quot;회식&quot; in user_input:
        case_list.append(&quot;A&quot;)
    if &quot;커피&quot; in user_input or &quot;카페인&quot; in user_input:
        case_list.append(&quot;B&quot;)
    if &quot;눈&quot; in user_input or &quot;루테인&quot; in user_input:
        case_list.append(&quot;C&quot;)
    if &quot;약&quot; in user_input or &quot;복용&quot; in user_input:
        case_list.append(&quot;D&quot;)
    return case_list
</code></pre>
<p>해당 함수는 자유 입력을 분석하여 중첩된 case를 다중 반환한다.</p>
<p>즉, 한 사용자가 동시에 간 건강 + 눈 건강 + 약물 복용 정보를 입력한 경우, 세 가지 처방 논리를 모두 프롬프트에 반영할 수 있도록 설계하였다.</p>
<br>

<h4 id="63-프롬프트-템플릿과-연결">6.3 &nbsp;프롬프트 템플릿과 연결</h4>
<p>각 case에는 다음과 같은 구조의 JSON 응답 템플릿이 사전에 정의되어 있다.</p>
<pre><code class="language-json">{
  &quot;recommended&quot;: [...],
  &quot;avoid&quot;: [...],
  &quot;caution&quot;: [...],
  &quot;reference&quot;: [...]
}
</code></pre>
<p>이 템플릿은 각 case별로 다르게 정의되고, 사용자의 입력이 여러 case에 해당될 경우 해당 템플릿을 병합하여 LLM 프롬프트로 전달한다.</p>
<p>프로젝트 진행 과정에서</p>
<p>프롬프트는 <code>&quot;허용된 성분만 사용하라&quot;</code> 는 whitelist 구조를 갖추도록 설계 방향을 잡게 되었다. 이 구조 덕분에 <strong>구조화된 응답, 일관된 필드, 안정적인 파싱</strong>이 가능해졌다.</p>
<br>
<br>
<br>
<br>

<h2 id="7-공공데이터-기반-제품-추천-파이프라인">7. 공공데이터 기반 제품 추천 파이프라인</h2>
<h4 id="71-데이터-출처-및-형식">7.1 &nbsp;데이터 출처 및 형식</h4>
<p>사용된 데이터는 식약처 건강기능식품정보 공공 API로, 총 10,000건의 영양제 제품 정보를 사용하였다.
<img src="https://velog.velcdn.com/images/jul-ee/post/45525a62-b21d-40e0-b504-f80bc6195ead/image.png" alt=""></p>
<p>본 프로젝트에서는 다음 5가지 필드만 유지하여 벡터화 대상 문서를 구성하였다.</p>
<ul>
<li>제품명</li>
<li>기능성</li>
<li>섭취 방법</li>
<li>주의사항</li>
<li>주성분</li>
</ul>
<blockquote>
<p>[참고] <a href="https://www.data.go.kr/data/15056760/openapi.do">식품의약품안전처_건강기능식품정보 Open API</a></p>
</blockquote>
<br>

<h4 id="72-데이터-수집-및-저장-전략">7.2 &nbsp;데이터 수집 및 저장 전략</h4>
<p>사용한 식약처 API에 아래과 같은 제약이 존재하는 것을 확인하였다.</p>
<ul>
<li><code>numOfRows</code> 최대값: 100</li>
<li>호출 단위: 페이지당 100개</li>
<li>일일 최대 호출 건수: 10,000회</li>
</ul>
<p>이를 고려하여 다음 전략으로 수집을 진행하였다.</p>
<table>
<thead>
<tr>
<th>단계</th>
<th>처리 방식</th>
<th>목적</th>
</tr>
</thead>
<tbody><tr>
<td>① 1,000건 프로토타입</td>
<td>500 + 500건을 호출하여 테스트</td>
<td>응답 형식 분석, 파싱 테스트</td>
</tr>
<tr>
<td>② 임베딩 토큰 초과 대응</td>
<td>500건씩 수집 → 다시 100개씩 나누어 임베딩</td>
<td>OpenAI 임베딩 토큰 제한 회피</td>
</tr>
<tr>
<td>③ 전체 수집</td>
<td>100페이지 × 100개 = 10,000건 수집 완료</td>
<td>전체 데이터 확보</td>
</tr>
<tr>
<td>④ 문서화</td>
<td>필요한 필드만 LangChain <code>Document</code>로 저장</td>
<td>임베딩 부하 최소화, 검색 정확도 향상</td>
</tr>
</tbody></table>
<br>

<h4 id="73-제품-추천-로직">7.3 &nbsp;제품 추천 로직</h4>
<p>제품 추천은 <code>recommended</code> 성분 리스트를 기준으로 FAISS 검색을 수행하고,</p>
<p>동시에 <code>avoid</code> 성분 리스트에 해당하는 제품은 필터링하여 제외한다.</p>
<pre><code class="language-python">def filter_avoid_products(results, avoid_list):
    return [
        r for r in results
        if not any(avoid in r.page_content for avoid in avoid_list)
    ]
</code></pre>
<p>최종적으로 5개 제품만 추려서 사용자에게 카드 형태로 제시하고, 제품 정보는 UI 상에서 확장 가능(expandable)하게 구성하였다.</p>
<br>
<br>
<br>
<br>

<h2 id="8-streamlit-기반-ui-구성-및-사용자-경험-설계">8. Streamlit 기반 UI 구성 및 사용자 경험 설계</h2>
<h4 id="81-전체-ui-흐름">8.1 &nbsp;전체 UI 흐름</h4>
<p>UI는 Streamlit 기반으로 구성하였으며, 사용자는 한 화면 내에서 다음과 같은 흐름을 경험하게 된다.</p>
<ol>
<li>정보 입력 (폼 기반)</li>
<li>RAG 기반 챗 응답 (성분 추천 및 주의사항 포함)</li>
<li>해당 성분 기반의 제품 카드 추천</li>
<li>모든 응답의 출처 명시 및 JSON 기반 시각화</li>
</ol>
<p>이러한 구성은 사용자가 응답 신뢰도를 기반으로 실시간 제품 비교 행동으로 이어질 수 있도록 UX 흐름을 설계하였다.</p>
<h4 id="82-파싱-오류-및-예외-처리">8.2 &nbsp;파싱 오류 및 예외 처리</h4>
<p>LLM의 응답은 JSON으로 제한되어 있지만,</p>
<p>응답이 누락되거나 포맷 오류가 발생할 가능성을 대비하여 다음과 같은 안전 장치를 마련하였다.</p>
<ul>
<li><code>json.loads()</code> 실패 시 즉시 오류 메시지 출력</li>
<li>Streamlit 상에서 응답 중단 없이 이전 결과 유지</li>
<li>잘못된 응답이 반복되는 경우 로그 저장 후 LLM 호출 재시도</li>
</ul>
<br>
<br>
<br>
<br>

<h2 id="9-실행-결과">9. 실행 결과</h2>
<h4 id="💬-영양제-check-실행-결과">💬 &nbsp;영양제 Check! 실행 결과</h4>
<p>아래는 최종적으로 완성된 &quot;영양제 Check!&quot; 서비스를 실행한 화면이다.</p>
<p>사용자가 정보를 입력하면, 추천 성분과 피해야 할 성분, 주의사항, 실제 제품까지 순차적으로 출력되고, 각 결과는 모두 논문 또는 공공데이터에 기반한 검증된 정보만을 사용한다.</p>
<p>사용자는 자신의 상태에 따라 다른 결과를 받게 되며, 그 차이를 명확히 체감할 수 있다.</p>
<ul>
<li>Case_A, Case_B</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/636326f2-54a6-49c3-88c7-70d0447aed4e/image.png" alt=""></p>
<ul>
<li>Case_C, Case_D</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/8ada7987-bfa9-4a74-b094-6d0fbae3de6f/image.png" alt=""></p>
<p>실행 결과를 해석할 때 가장 중요했던 건, 제시된 성분이 실제로 해당 사용자에게 적절한가를 도메인 기준에서 검증하는 것이었다.</p>
<p>공부하고 이해한 도메인 지식을 바탕으로 나와야 할 성분이 빠지거나 나오면 안 되는 성분이 포함된 경우를 구분할 수 있었고, 이 과정을 통해 현재 로직에 어떤 문제가 있는지 짚어내는 것도 가능했다.</p>
<p>궁극적으로는 “그럴듯한 답변” 이 아니라 정확하고 신뢰 가능한 결과만을 사용자에게 제공하는 것에 가까워질 수 있었다.</p>
<br>
<br>
<br>
<br>

<h2 id="10-개선-방향">10. 개선 방향</h2>
<blockquote>
<p><em>응답 정밀도 개선 전략 및 후속 개선 방향</em></p>
<p>랭체인톤 발표 후,
퍼실님의 피드백을 통해 얻은 인사이트로 개선 방향을 생각해 볼 수 있었다.</p>
</blockquote>
<h4 id="101-문제점-성분-간-유사도와-우선순위-불일치">10.1 &nbsp;문제점: 성분 간 유사도와 우선순위 불일치</h4>
<p>본 프로젝트는 RAG를 두 단계로 구성하여 사용자 질문에 대한 질의응답과, 이후 조건 기반 영양제 추천까지 모두 언어 모델 기반 유사도 검색을 활용하였다. 그러나 성분 간 유사성이 높은 상황에서는 벡터 유사도 기반의 정렬이 기대만큼 명확한 우선순위를 제공하지 못할 가능성도 있다.</p>
<p>유사한 효능을 지닌 성분들이 다수 존재할 때, 유사도 기반 검색이 실제 추천의 우선순위와 다르게 작동할 수 있으며 이 경우 정형화된 구조의 RDB(Relational Database)를 활용한 정렬 방식이 더 효과적일 수 있다. 향후에는 RAG 기반 추천과 RDB 기반 정렬의 성능을 비교하여 추천 정확도 및 사용자 만족도를 기준으로 하이브리드 구조 도입도 고려할 수 있다.</p>
<h4 id="102-연구-근거-인용-기능-강화">10.2 &nbsp;연구 근거 인용 기능 강화</h4>
<p>사용자에게 &quot;추천 성분&quot; 또는 &quot;피해야 할 성분&quot;을 제시할 때, 해당 내용을 뒷받침하는 연구 결과나 임상 근거(논문, 가이드라인 등)를 함께 제시하면 정보의 신뢰도와 전문성이 한층 높아질 수 있다. RAG의 응답 결과에 논문 출처 또는 핵심 문장을 인용하는 기능을 추가하거나, 근거 기반 요약 기능을 보완하는 방향으로도 개선이 가능하다.</p>
<br>
<br>
<br>
<br>




<h2 id="11-프로젝트-회고">11. 프로젝트 회고</h2>
<blockquote>
<p>🔗 &nbsp;다음 글</p>
<p><a href="https://velog.io/@jul-ee/%EC%98%81%EC%96%91%EC%A0%9C-Check-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0">💊 영양제 Check! 프로젝트 회고</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그 데이터 수집 자동화 환경 구축하기]]></title>
            <link>https://velog.io/@jul-ee/%EB%A1%9C%EA%B7%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91-%EC%9E%90%EB%8F%99%ED%99%94-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jul-ee/%EB%A1%9C%EA%B7%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91-%EC%9E%90%EB%8F%99%ED%99%94-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 24 Jun 2025 04:32:30 GMT</pubDate>
            <description><![CDATA[<p>날씨앱을 개발하고 서비스하면서
사용자 경험을 더 정교하게 만들고 싶다는 욕심이 생겼다 🌤️</p>
<p>그러기 위해서, 의사결정 근거로써 사용자 로그 데이터를 수집하고 싶었다.</p>
<p>복잡한 서버 연동 없이 앱 단에서 수집할 수 있는 구조를 만들고자 했고, 그 결과 <strong>Google Sheets</strong> 를 활용하여 경량-무서버 로그 수집 시스템을 구성하게 되었다.
<br></p>
<p>이 글에서는 시스템의 환경 구축과 구현 과정을 중심으로 기록해 보았다.</p>
<br>
<br>
<br>

<h3 id="google-sheets를-선택한-이유">Google Sheets를 선택한 이유</h3>
<p>Google Sheets는 별도의 서버나 DB 구축 없이도 데이터를 실시간으로 저장하고 확인할 수 있어 초기 구성 부담이 적다. Google Apps Script를 통해 HTTP 요청을 처리할 수 있어 간단한 로깅 서버 역할도 가능하다.</p>
<p>React Native 환경에서도 fetch 기반으로 쉽게 연동할 수 있고, 개발·운영 비용 없이 빠르게 테스트 가능한 점이 실용적이었다 무거운 인프라 없이 가볍게 로그 수집을 시작하기에 합리적이라 판단하였다.</p>
<blockquote>
<p>결과를 먼저 소개한 뒤,
이를 가능하게 만든 환경 구축 과정을 차례대로 정리해 보았다.</p>
</blockquote>
<br>
<br>
<br>

<h3 id="로그-데이터-수집-결과">로그 데이터 수집 결과</h3>
<p>사용자 로그 데이터 수집 자동화 환경을 구축하면
앱을 사용함에 따라 <strong>구글 스프레드시트에 그 로그 데이터가 적재</strong>된다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/0ef4cbb8-c5c8-4681-90ae-e26686f66a52/image.JPG" alt=""></p>
<p>시트별로 action에 따라 필요한 데이터를 수집할 수 있는데,</p>
<p>해당 이미지에서는 앱 실행(app_open) 시 수집 가능한 가장 기본적인 데이터들이 쌓인 것을 볼 수 있다.</p>
<br>
<br>
<br>

<h3 id="아키텍처">아키텍처</h3>
<p>로그 데이터 수집한 아키텍처는 아래처럼 설계하였다.</p>
<pre><code>React Native App ──&gt; fetch() ──&gt; Google Sheets API
     │                                 ▲
     ├─ AsyncStorage: Access / Refresh ┘
     └─ logEvent() util
</code></pre><ul>
<li>앱은 <strong>OAuth 2.0 토큰</strong> 을 직접 들고 Google Sheets에 append 요청을 날린다.</li>
<li>스프레드시트는 테스트(Dev)·운영(Prod) 두 개로 분리해 데이터 오염을 막는다.</li>
<li>추후 분석은 Sheet → CSV export → Python / BigQuery 로 이어진다.</li>
</ul>
<br>
<br>
<br>

<h2 id="구현-과정">구현 과정</h2>
<p>2025.05.20 기준으로 작성되었다.</p>
<h3 id="1-구글-스프레드시트-api-활성화-및-access-token-발급">1. 구글 스프레드시트 API 활성화 및 Access Token 발급</h3>
<h4 id="1-0-google-sheets-api-요금-구조">1-0. Google Sheets API 요금 구조</h4>
<p>장기적인 로그 수집을 위해서 <code>일반 계정 활성화</code> 를 통해 결제 계정 업그레이드하여 사용하고 있다.</p>
<p><a href="https://developers.google.com/workspace/sheets/api/limits?utm_source=chatgpt.com">공식 문서</a>에 따르면, Google Sheets API 자체는 추가 요금이 없다. 과금 SKU 자체가 없어서 호출이 많아도 비용이 0 원으로 찍힌다. 다만 초당·분당 쿼터를 넘기면 429(Too Many Requests)를 받게 되기는 한다. 과금 계정은 ‘신용카드 보증’일 뿐, Sheets API에는 실제 요금이 발생하지 않는 것이다.</p>
<p>다만, 무료 평가판이 끝나면 프로젝트 결제가 막혀 API도 동작하지 않기 때문에 일반 계정 활성화를 통해 업그레이드하여 사용해야 한다.</p>
<p>예측 불가한 트래픽 대비하여 업그레이드 후에는 Budget 알림과 쿼터 모니터링으로 예기치 않은 과금을 방지하는 것이 필요하겠다.</p>
<br>

<h4 id="1-1-google-sheets-api-활성화">1-1. Google Sheets API 활성화</h4>
<ol>
<li><p><a href="https://console.cloud.google.com/welcome?inv=1&amp;invt=Ab08rA&amp;project=weather-app-analytics-459400">Google Cloud Console</a>에 접속하여 구글 계정으로 로그인</p>
</li>
<li><p>상단에서 <code>프로젝트 선택</code> &gt; <code>새 프로젝트</code> 클릭 <img src="https://velog.velcdn.com/images/jul-ee/post/ad54a1b0-07ab-49b9-b501-074e57a242c5/image.png" alt=""></p>
</li>
<li><p>Google Sheets API 활성화</p>
</li>
</ol>
<ul>
<li>새로 만든 프로젝트로 이동한 다음, 왼쪽 메뉴에서 <code>API 및 서비스</code> &gt; <code>라이브러리</code> 로 이동</li>
<li>검색창에 Google Sheets API 검색 → 클릭 → <code>사용</code> 버튼 클릭해서 활성화
  <img src="https://velog.velcdn.com/images/jul-ee/post/f90e46ab-ffaf-49c2-830d-f1be778d01d2/image.png" alt=""><img src="https://velog.velcdn.com/images/jul-ee/post/139f8c60-5bf6-4ec4-8bf6-29d3fe748eaa/image.png" alt=""><img src="https://velog.velcdn.com/images/jul-ee/post/e2ef2b52-1605-449a-ba18-f8a5a9256fb1/image.png" alt=""></li>
</ul>
<br>
<br>

<h4 id="1-2-oauth-20-클라이언트-id-발급">1-2. OAuth 2.0 클라이언트 ID 발급</h4>
<p>사용자가 인증된 상태로 API에 접근하도록 Access Token을 발급받기 위한 과정이다.</p>
<ol>
<li><p>왼쪽 메뉴에서 <code>API 및 서비스</code> &gt; <code>사용자 인증 정보</code> 로 이동
 <img src="https://velog.velcdn.com/images/jul-ee/post/af060fff-972c-477d-a3b0-c41063d16103/image.png" alt=""></p>
</li>
<li><p>OAuth 동의 화면 구성 -&gt; 이때 대상: <strong>외부(External)</strong></p>
<ul>
<li>외부로 설정하지 않으면 테스트 사용자를 등록할 수 없게 된다.
<img src="https://velog.velcdn.com/images/jul-ee/post/02a16ebc-4c94-4b3b-8f2e-3b5b172c9a01/image.png" alt=""><img src="https://velog.velcdn.com/images/jul-ee/post/7700391c-7bcd-4713-a81e-90d015de6adf/image.png" alt=""></li>
</ul>
</li>
<li><p>상단에서 <code>+ 사용자 인증 정보 만들기</code> → <code>OAuth 클라이언트 ID</code> 선택
 <img src="https://velog.velcdn.com/images/jul-ee/post/192a533f-62cf-4afd-959d-485b880f340a/image.png" alt=""></p>
</li>
<li><p>OAuth 클라이언트 ID 만들기
 <img src="https://velog.velcdn.com/images/jul-ee/post/22f4791d-eb3c-46b5-a419-b20bd5e9b9e4/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>🌟 애플리케이션 유형: <strong>웹 애플리케이션</strong></li>
<li>🌟 승인된 리다이렉션 URI
  <a href="https://developers.google.com/oauthplayground">https://developers.google.com/oauthplayground</a> 입력</li>
</ul>
<blockquote>
<p>[참고]
💡 애플리케이션 유형을 웹 애플리케이션으로 지정하지 않으면, redirect URI를 입력할 수 없다.
💡 승인된 리디렉션 URI (Authorized redirect URIs) 항목에 해당 주소를 추가하지 않으면, 이후 구글 계정 인증 단계에서 <strong>엑세스 요청이 차단</strong>되는 400: redirect_uri_mismatch 에러가 발생하게 된다.</p>
</blockquote>
<p>여기까지 진행하면 Client ID와 Client Secret이 발급된다.</p>
<p>이후, 아래의 화면에서 확인되는 <strong>클라이언트 ID</strong>와 <strong>클라이언트 보안 비밀번호</strong>를 사용하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/e5262b95-e393-4a32-8f8f-3f2e919d7fb5/image.png" alt=""></p>
<br>
<br>

<h4 id="1-3-테스트-사용자-계정-등록">1-3. 테스트 사용자 계정 등록</h4>
<p>3-2 까지 수행 후, 곧바로 3-4로 넘어갔을 때 아래와 같은 에러를 만났다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/a4f8e42e-586b-4057-98a9-25ddbc145db8/image.png" alt=""></p>
<p>에러의 원인은 OAuth 동의 화면을 외부 사용자에게 공개하지 않았기 때문인 것으로 확인되었다.</p>
<p>현재 Google Cloud Console에서 만든 OAuth 클라이언트의 동의 화면은 &quot;테스트 중&quot; 상태이며, Google 계정 중 등록된 테스트 사용자만 접근할 수 있도록 제한되어 있는 것이다.</p>
<p>[ 해결 방법 ]</p>
<ol>
<li><code>API 및 서비스</code> &gt; <code>OAuth 동의 화면</code> &gt; <code>대상</code> 으로 이동</li>
<li>테스트 사용자로 Gmail 계정을 등록하여 저장
<img src="https://velog.velcdn.com/images/jul-ee/post/18fe8b8d-541a-4957-8ed4-b72c7fdd878f/image.png" alt=""></li>
</ol>
<br>
<br>

<h4 id="1-4-oauth-20-playground에서-토큰-발급">1-4. OAuth 2.0 Playground에서 토큰 발급</h4>
<p>OAuth 클라이언트가 만들어졌으면 이제 Access Token을 발급받아야 한다.
가장 간단한 방법은 OAuth 2.0 Playground를 이용하는 것이다.</p>
<ol>
<li><p><a href="https://developers.google.com/oauthplayground/">OAuth 2.0 Playground</a> 접속</p>
</li>
<li><p>오른쪽 상단 톱니바퀴(⚙️)
ⅰ. &nbsp;&quot;Use your own OAuth credentials&quot; 체크
ⅱ. &nbsp;발급받은 Client ID, Client Secret 입력
 <img src="https://velog.velcdn.com/images/jul-ee/post/73d855a7-8092-49be-a901-e9bf4cc98a7c/image.png" alt=""></p>
</li>
<li><p>[Setp 1] API 스코프 선택
<code>https://www.googleapis.com/auth/spreadsheets</code> 스코프 선택 (직접 입력도 가능)
 <img src="https://velog.velcdn.com/images/jul-ee/post/19cdc4ce-a0a5-46a7-a518-9c4c24807104/image.png" alt=""></p>
</li>
<li><p>활성화된 <code>Authorize APIs</code> 클릭 → Google 계정 로그인</p>
</li>
<li><p>[Step 2] Authorization Code 발급</p>
<ul>
<li><p><code>Exchange authorization code for tokens</code> 클릭
➱ &nbsp;<strong>Refresh token 및 Access token 발급 완료</strong></p>
</li>
<li><p>Auto-refresh the token before it expires.
→ &nbsp;OAuth Playground는 테스트 용이기 때문에 체크할 필요는 없음
<img src="https://velog.velcdn.com/images/jul-ee/post/0677e596-c522-4248-9b55-0cc1ec17834e/image.png" alt=""></p>
</li>
</ul>
</li>
</ol>
<p>이렇게 발급받은 토큰을 사용하여 Google Sheets에 접근할 수 있게 된다.</p>
<br>
<br>
<br>

<h3 id="2-access-token을-사용하여-google-sheets에-데이터-수집">2. Access Token을 사용하여 Google Sheets에 데이터 수집</h3>
<p>발급된 Access Token은 1시간(3600초) 후 만료된다. 따라서 Playground에서 함께 발급받은 Refresh Token을 이용해 자동 갱신 로직으로 구현하였다.</p>
<p>API 활성화와 토큰 발급 과정이 핵심이지만, React Native 기반 기술적인 구현 과정도 간략하게 정리해 보았다.</p>
<h4 id="2-1-필요한-사전-정보">2-1. 필요한 사전 정보</h4>
<table>
<thead>
<tr>
<th align="left">항목</th>
<th align="left">설명</th>
<th align="left">예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left">스프레드시트 ID</td>
<td align="left">Google Sheets 문서의 ID</td>
<td align="left"><code>1AbCDeFgHijKlmNoPqRstUvWxYz1234567890</code></td>
</tr>
<tr>
<td align="left">시트 이름</td>
<td align="left">보통은 <code>Sheet1</code></td>
<td align="left"><code>Sheet1</code></td>
</tr>
<tr>
<td align="left">Access Token</td>
<td align="left">방금 발급받은 토큰</td>
<td align="left"><code>ya29.a0AZYkNZJl...</code></td>
</tr>
</tbody></table>
<ol>
<li><p>로그 데이터 수집을 위해 <a href="https://docs.google.com/spreadsheets/u/0/">구글 스프레드시트</a>를 생성해야 한다.
 스프레드시트 ID는 URL에서 확인 가능
 ➱ &nbsp;<code>https://docs.google.com/spreadsheets/d/[여기가 ID]/edit</code></p>
</li>
<li><p>API가 접근하려면 스프레드시트에 사용자 계정(서비스 계정이거나 OAuth 로그인한 계정)을 편집자로 초대해야 한다.</p>
</li>
<li><p>Sheet 이름은 대소문자가 구분된다.
 → &nbsp;Sheet1, sheet1 은 다름</p>
</li>
</ol>
<br>

<h4 id="2-2-토큰-암호화">2-2. 토큰 암호화</h4>
<p>Playground에서 받아온 토큰들을 바로 사용할 수 없다. </p>
<p><a href="https://velog.io/@jul-ee/GitHub-Push-Protection-%EB%B3%B4%EC%95%88%EC%9D%98-%EB%A7%88%EC%A7%80%EB%A7%89-%EA%B2%BD%EA%B3%A0">GitHub Push Protection, 보안의 마지막 경고</a>에서 언급된 사례가 바로 이 상황이었다.</p>
<p>민감한 CLIENT_ID, CLIENT_SECRET 값은 <code>.env</code> 에 두고, 앱 빌드 시 Babel 플러그인이나 EAS config로 주입한다.</p>
<p>현재 단계에서는 Refresh Token을 기기에 보관하긴 하지만, 실서비스에서는 Cloud Functions나 프록시 서버로 옮기면 더 안전하다.</p>
<pre><code class="language-javascript">// .env

GOOGLE_ACCESS_TOKEN=ya29...
GOOGLE_REFRESH_TOKEN=1//..
GOOGLE_CLIENT_ID=...apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=...</code></pre>
<br>

<h4 id="2-3-google-sheets-로그-기록을-위한-초기-토큰-설정">2-3. Google Sheets 로그 기록을 위한 초기 토큰 설정</h4>
<p>앱이 처음 실행될 때, Google Sheets API를 사용하기 위한 토큰이 없다면 .env에서 불러온 토큰을 AsyncStorage에 저장한다. 이미 저장되어 있다면 초기화를 생략하여 불필요한 덮어쓰기를 방지한다.</p>
<pre><code class="language-javascript">// App.js

// Google Sheets API 사용을 위한 Access Token 및 Refresh Token 초기 저장
useEffect(() =&gt; {
  const ensureSheetsTokens = async () =&gt; {
    const [[, access], [, refresh]] = await AsyncStorage.multiGet([
      &#39;accessTokenForSheets&#39;,
      &#39;googleRefreshToken&#39;,
    ]);

    if (!access || !refresh) {
      // 토큰 저장 (.env 파일에 별도 관리)
      await AsyncStorage.multiSet([
        [&#39;accessTokenForSheets&#39;, process.env.GOOGLE_ACCESS_TOKEN],
        [&#39;googleRefreshToken&#39;, process.env.GOOGLE_REFRESH_TOKEN],
      ]);
     ...
  };

  ensureSheetsTokens();
}, []);
</code></pre>
<br>

<h4 id="2-4-google-sheets를-원격-로그-저장소로-사용-자동화">2-4. Google Sheets를 원격 로그 저장소로 사용 자동화</h4>
<p>React Native 앱에서 Google Sheets를 원격 로그 저장소로 쓰기 위해 필요한 전 과정을 자동화하기 위해 다음과 같이 핵심 로직을 구상하였다.</p>
<p>목표는 &quot;Access Token은 자동으로 갱신되어 로그 유실이 최소화되고, 시트에는 한국 시간 기준의 깔끔한 로그 테이블을 쌓는 것&quot; 이다.</p>
<ol>
<li><p>KST 타임스탬프 생성</p>
<ul>
<li>클라이언트 시간(UTC)을 한국 표준시(+9 h)로 보정하여 <code>&quot;YYYY-MM-DD hh:mm:ss&quot;</code> 형식 문자열 반환</li>
<li>이렇게 만든 값을 모든 로그의 첫 컬럼으로 넣어 시간대를 일관되게 관리</li>
</ul>
</li>
<li><p>OAuth 2.0 토큰 관리</p>
<ul>
<li>AsyncStorage에 저장해 둔 Refresh Token을 이용해 Google OAuth 서버에 POST 요청을 보내고,<ul>
<li>새로 발급받은 Access Token을 다시 AsyncStorage에 캐싱</li>
<li>토큰이 갱신될 때마다 그 사실도 별도 시트(Sheet2)에 기록해 추후 모니터링할 수 있게 함</li>
</ul>
</li>
</ul>
</li>
<li><p>Google Sheets 한 줄 추가(append)</p>
<ul>
<li>전달받은 배열을 지정 시트의 맨 아래 행에 추가<ul>
<li>요청에 401(만료) 응답이 오면 위의 토큰을 자동 재발급한 뒤 한 번 더 시도하므로, 호출부는 토큰 만료 여부를 신경 쓰지 않아도 됨</li>
</ul>
</li>
</ul>
</li>
<li><p>실제 로그 이벤트 함수</p>
<ul>
<li>앱 실행을 감지하여 추출 필드와 함께 actionName 기록</li>
<li>수집 과정에서 사용자 식별을 위해 사용되는 이메일 암호화 필요</li>
<li>토큰이 갱신될 때마다 시스템 이벤트로 기록</li>
</ul>
</li>
</ol>
<blockquote>
<p>추우 출석 전용 이벤트, 게시글 작성 전용 이벤트 등 유의미한 인사이트를 도출해 낼 수 있는 데이터 수집을 고려하고 있다.</p>
</blockquote>
<pre><code class="language-javascript">// api &gt; googleSheetLogger.js

import {Platform} from &#39;react-native&#39;;
import AsyncStorage from &#39;@react-native-async-storage/async-storage&#39;;
import {GOOGLE_CLIENT_ID as CLIENT_ID, GOOGLE_CLIENT_SECRET as CLIENT_SECRET} from &#39;@env&#39;;

const SPREADSHEET_ID = &#39;...&#39;;
const SHEET_NAME    = &#39;...&#39;;

// 1) KST 타임스탬프
const getKSTTimestamp = () =&gt;
  new Date(Date.now() + 9 * 60 * 60 * 1000) 
    .toISOString()                      
    .replace(&#39;T&#39;, &#39; &#39;)       
    .substring(0, 19);         

// 2) RefreshToken으로 AccessToken 재발급
const refreshAccessToken = async () =&gt; {
  const refreshToken = await AsyncStorage.getItem(&#39;googleRefreshToken&#39;);
  if (!refreshToken) throw new Error(&#39;...&#39;);

  const res = await fetch(&#39;https://oauth2.googleapis.com/token&#39;, {
    method : &#39;POST&#39;,
    headers: {&#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;},
    body   : new URLSearchParams({
      client_id    : CLIENT_ID,
      client_secret: CLIENT_SECRET,
      refresh_token: refreshToken,
      grant_type   : &#39;refresh_token&#39;,
    }).toString(),
  });
  if (!res.ok) throw new Error(&#39;...&#39;);

  const {access_token} = await res.json();
  await AsyncStorage.setItem(&#39;accessTokenForSheets&#39;, access_token);
  await logRefreshTokenUsage();                     
  return access_token;
};

// 3) Google Sheets append 유틸
const appendToGoogleSheet = async (values, sheetName = SHEET_NAME) =&gt; {
  const request = async token =&gt;
    fetch(
      `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${sheetName}!A1:append?valueInputOption=USER_ENTERED`,
      {
        method : &#39;POST&#39;,
        headers: {Authorization: `Bearer ${token}`, &#39;Content-Type&#39;: &#39;application/json&#39;},
        body   : JSON.stringify({values: [values]}),
      },
    );

  let token = await AsyncStorage.getItem(&#39;accessTokenForSheets&#39;);
  let res   = await request(token);

  // 만료(401) → 자동 재발급 후 재시도
  if (res.status === 401) {
    token = await refreshAccessToken();
    res   = await request(token);
  }
  if (!res.ok) throw new Error(&#39;...&#39;);
};

// 4) RefreshToken 사용 기록
const logRefreshTokenUsage = async () =&gt;
  appendToGoogleSheet(
    [getKSTTimestamp(), &#39;system&#39;, &#39;refresh_used&#39;, &#39;-&#39;, &#39;-&#39;, &#39;-&#39;, &#39;-&#39;, &#39;-&#39;, Platform.OS],
    &#39;Sheet2&#39;,
  );

// 5) 실제 사용자 액션 로그
export const logUserAction = async ({...}, actionName) =&gt;
  appendToGoogleSheet(
    [
      ...
    ],
  );</code></pre>
<hr>
<br>
<br>
<br>


<h3 id="3-데이터-기반-운영을-위한-구조-확장">3. 데이터 기반 운영을 위한 구조 확장</h3>
<h4 id="법적·윤리적-고려-사항">법적·윤리적 고려 사항</h4>
<p>데이터 수집 구조를 설계할 때 가장 우선되어야 할 요소는 법적·윤리적 기준을 준수하는 것이다.</p>
<p>사용자 식별을 위한 민감 정보 수집을 배제하고, UUID(Universally Unique Identifier) 기반으로 로그를 관리함으로써 최소 수집 원칙을 따르고 있다.</p>
<p>또한 첫 실행 시 개인정보 처리방침과 로그 수집 목적을 사용자에게 명확히 고지하고, 동의를 받는 절차를 포함하였다. 수집된 데이터는 내부 분석 용도로만 활용되며, 광고 등 다른 목적으로의 사용은 금지된다.</p>
<p>보관 기간 역시 1년으로 한정하고 있으며 이후 자동 파기될 수 있도록 설계하였다. 국외 서버(Google Sheets)를 사용하는 구조 특성상 개인정보 국외 이전에 대한 안내와 동의 항목도 정책 내에 포함해야 한다.</p>
<p>향후에도 개인정보 보호법(PIPA) 및 관련 가이드라인에 따라 체계를 지속적으로 점검하고 개선할 예정이다.</p>
<br>


<h4 id="운영prod-확장-계획">운영(Prod) 확장 계획</h4>
<p>현재는 테스트 환경에서 Google Sheets에 로그 데이터를 수집하고 있지만, 운영 단계에서는 보다 안정적이고 분석 친화적인 구조로 확장할 계획이다. 앱에서 민감한 인증 정보를 직접 다루지 않도록 운영용 시트는 테스트 환경과 분리하여 데이터 정확성과 보안을 강화할 예정이다.</p>
<p>수집된 로그는 Google Sheets에서 일정 주기로 CSV 형태로 추출하거나 BigQuery로 연동하여 대용량 데이터도 유연하게 다룰 수 있는 기반을 마련할 계획이다. 이후 시계열 흐름, 기능별 사용 패턴, 앱 버전별 변화 등을 Tableau 기반 대시보드로 시각화하여 사용자 행동을 종합적으로 해석할 수 있도록 구성할 예정이다.</p>
<p>또한, 쌓인 데이터를 분석 가능한 구조로 정제하여 다양한 방식의 탐색적 분석과 지표 계산이 가능하도록 관리할 예정이다. 이를 통해 앱 운영의 방향성을 도출하고, 데이터 기반의 의사결정을 체계화할 수 있는 환경을 갖춰나갈 계획이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 토큰에 의미 부여하기]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%ED%86%A0%ED%81%B0%EC%97%90-%EC%9D%98%EB%AF%B8-%EB%B6%80%EC%97%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%ED%86%A0%ED%81%B0%EC%97%90-%EC%9D%98%EB%AF%B8-%EB%B6%80%EC%97%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 12 Jun 2025 06:09:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;왜 Word Embedding이 필요한가?
🖇 &nbsp;1. Word2Vec
🖇 &nbsp;2. FastText
🖇 &nbsp;3. ELMo</p>
</blockquote>
<br>

<p>앞선 글에서는 자연어처리에서 가장 기초가 되는 작업인 토큰화(Tokenization)에 대해 알아보았다. 이제는 이 토큰들이 기계에게 어떤 방식으로 의미를 가질 수 있도록 만드는지를 이해해야 한다.</p>
<p>기계는 숫자 연산을 통해 데이터를 인지한다. 그렇다면 텍스트로 이루어진 문장을 어떻게 숫자로 바꾸고, 또 의미를 담게 할 수 있을까?
<br></p>
<p>단어 하나하나를 숫자로 변환하는 가장 일반적인 방법은 바로 워드 임베딩(Word Embedding)이다. 이 글에서는 워드 임베딩의 개념과 대표적인 세 가지 임베딩 방식인 <strong>Word2Vec</strong>, <strong>FastText</strong>, <strong>ELMo</strong>를 소개한다. 전체 흐름을 이해하는 데 중점을 두고 작성하였다.</p>
<br>
<br>
<br>

<h3 id="🖇-왜-word-embedding이-필요한가">🖇 &nbsp;왜 Word Embedding이 필요한가?</h3>
<p>기계는 토큰 자체를 이해하지 못한다.</p>
<p>우리가 &quot;고양이&quot;라는 단어를 보면 귀엽고 털복숭복숭..인 동물을 떠올리지만, 컴퓨터는 &quot;고양이&quot;라는 단어가 어떤 의미를 가지는지 전혀 알지 못한다. 따라서 기계가 이해할 수 있도록 각 단어에 <strong>숫자 벡터를 부여</strong>해야 한다. 이 벡터가 단어의 의미를 표현하는 수단이 된다.</p>
<p>처음에는 각 단어에 랜덤한 실수값이 할당된다. 이렇게 초기화된 벡터는 의미를 담고 있지 않기 때문에 학습을 통해 단어들 사이의 <strong>의미적 유사도</strong>나 <strong>관계성</strong>이 반영된 방향으로 벡터값을 조정해줘야 한다.</p>
<p>이를 위해 고안된 여러 알고리즘들이 Word Embedding 방식들이다.</p>
<hr>
<br>
<br>

<h3 id="🖇-1-word2vec">🖇 &nbsp;1. Word2Vec</h3>
<p>Word2Vec은 &quot;단어를 벡터로 만든다&quot;는 이름 그대로, 단어 하나하나를 벡터 공간에 위치시키는 방법이다.</p>
<p>핵심 아이디어는 간단하다. 어떤 단어가 <strong>어떤 단어들과 함께 자주 등장하는지</strong>를 보면 그 단어의 의미를 유추할 수 있다는 것이다. 이를 Distributional Hypothesis (분포 가설)이라고 부른다.</p>
<p>예를 들어, <code>&quot;난 오늘 술을 한 잔 마셨어&quot;</code> 라는 문장이 있다고 하자. 이 문장에서 &#39;술&#39;과 &#39;마셨어&#39;는 의미적으로 밀접하게 연결되어 있다. Word2Vec은 이러한 <strong>공동 등장(co-occurrence)</strong> 패턴을 학습해 단어 간의 관계를 벡터에 반영한다.</p>
<h4 id="학습-방식">학습 방식</h4>
<p>Word2Vec에는 두 가지 주요 학습 방식이 존재한다.</p>
<p>CBOW (Continuous Bag of Words)</p>
<ul>
<li>주변 단어들을 보고 중심 단어를 예측하는 방식</li>
</ul>
<p>Skip-gram</p>
<ul>
<li>중심 단어를 보고 주변 단어를 예측하는 방식</li>
</ul>
<p>CBOW는 학습 속도가 빠르고 일반적인 경우에 잘 작동하지만, Skip-gram은 희귀 단어에 더 강하고, 실제 성능 면에서 조금 더 우세한 것으로 알려져 있다.</p>
<p>Word2Vec은 은닉층 없이 단순한 구조를 사용하고, Softmax나 Negative Sampling을 통해 단어 벡터를 학습하는 방식이기 때문에 일반적으로 Shallow Neural Network로 분류된다.</p>
<h5 id="참고-위키독스-word2vec">[참고] <a href="https://wikidocs.net/22660">위키독스 Word2Vec</a></h5>
<blockquote>
<p>💡 Word2Vec: 단어의 주변을 보면 의미가 보인다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-2-fasttext">🖇 &nbsp;2. FastText</h3>
<p>Word2Vec은 당연히 약점도 존재한다. 특히 자주 등장하지 않는 희귀 단어는 학습 기회가 적기 때문에 그 벡터는 사실상 거의 초기 상태에서 멈춰버리는 경우도 생긴다.</p>
<p>이를 해결하기 위해 등장한 것이 FastText다.</p>
<p>FastText는 단어를 하위 단위(Subword)로 나누어 처리한다.</p>
<p>예를 들어, &#39;playing&#39;이라는 단어를 3-gram으로 분해하면 <code>pla</code>, <code>lay</code>, <code>ayi</code>, <code>yin</code>, <code>ing</code> 같은 문자 단위 subword들이 생성되고, FastText는 이들의 벡터 평균으로 단어 임베딩을 구성한다. 이 방식 덕분에 처음 등장하는 단어도 그 하위 정보를 기반으로 의미를 추론할 수 있게 된다.</p>
<h4 id="주요-특징">주요 특징</h4>
<ul>
<li>단어를 일정 길이의 문자 단위로 나눈다 (n-gram)</li>
<li>OOV(사전에 없는 단어) 문제에 매우 강함</li>
<li>형태가 유사한 단어는 벡터도 유사하게 생성됨</li>
<li><blockquote>
<p>기존 Word2Vec이 희귀 단어에 약하고, 단어의 부분 정보를 활용하지 못한다는 점에서 유용하게 작용함</p>
</blockquote>
</li>
</ul>
<h5 id="참고-fasttext-설명글">[참고] <a href="https://brunch.co.kr/@learning/7">FastText 설명글</a></h5>
<blockquote>
<p>💡 FastText: 단어를 잘게 나누면 의미가 보인다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-3-elmo">🖇 &nbsp;3. ELMo</h3>
<p>Word2Vec이나 FastText는 아무리 뛰어나도 고정된 단어 벡터를 사용한다는 점에서 한계를 가진다. 예를 들어, 다음 두 문장을 보자.</p>
<ul>
<li>탐스럽고 먹음직스러웠던 <strong>사과</strong>가 이렇게 썩어버리다니</li>
<li>당신이 저지른 실수는 <strong>사과</strong>한다고 용서될 수 없다</li>
</ul>
<p>두 문장의 &#39;사과&#39;는 전혀 다른 의미지만 Word2Vec은 이 두 단어를 동일한 벡터로 처리한다. 이를 해결하기 위해 등장한 것이 Contextualized Word Embedding, <strong>문맥 기반 임베딩</strong>이다.</p>
<h4 id="elmo의-등장">ELMo의 등장</h4>
<p>2018년에 발표된 ELMo(Embeddings from Language Models)는 문장 속에서 단어가 사용된 <strong>문맥 전체</strong>를 고려해 단어의 임베딩을 생성한다. 즉, 문장이 달라지면 같은 단어라도 벡터가 달라진다. 이 덕분에 동음이의어 문제나 문맥에 따른 의미 변화를 효과적으로 처리할 수 있다.</p>
<h4 id="elmo의-작동">ELMo의 작동</h4>
<p>ELMo는 양방향 LSTM (Bidirectional LSTM)을 사용해 문장의 앞과 뒤 모두를 고려한다. 최종적으로 다음 세 벡터를 합쳐 단어의 의미를 결정한다.</p>
<ul>
<li>ELMo는각 단어에 대해 embedding 레이어와 양방향 LSTM의 여러 층에서 나온 hidden state들을 조합하여 문맥에 맞는 벡터를 생성한다. 최종 벡터는 이 hidden state들을 학습 가능한 가중치로 결합하여 구성된다.</li>
</ul>
<p>이렇게 만들어진 벡터는 단어마다 사용된 위치와 문맥에 따라 달라지고, 보다 정교하고 정확한 자연어 처리를 가능하게 해준다.</p>
<h5 id="참고-모두의-연구소-elmo-리뷰">[참고] <a href="https://modulabs.co.kr/blog/reviewing-elmo">모두의 연구소: ELMo 리뷰</a></h5>
<blockquote>
<p>💡 ELMo: 문맥을 반영한 의미 임베딩</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>지금까지 텍스트를 숫자로 바꾸는 가장 기초적인 방식인 Word Embedding의 기본 개념들을 정리해 보았다.</p>
<ul>
<li>Word2Vec은 단어 주변의 단어들을 통해 의미를 학습한다.</li>
<li>FastText는 단어를 잘게 쪼개어 희귀 단어까지도 유의미하게 표현한다.</li>
<li>ELMo는 문맥을 반영해 동음이의어조차 정확히 구분할 수 있다.</li>
</ul>
<p>이렇게 되면 토큰은 단순 기호가 아니라 <strong>의미를 담은 벡터</strong>로 표현된다. 이런 벡터 덕분에 기계는 단어 간의 유사성, 문장 내의 관계성, 감정의 흐름까지도 이해할 수 있게 된다.</p>
<p>이 글에서는 워드 임베딩이 무엇이고, 어떤 문제를 해결하며, 왜 발전하게 되었는지를 개념적으로 이해하였다. 각 방식의 내부 구조나 수학적 원리에 대한 이해는 실제로 사용해 보면서 정리해 볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Tokenization: 기계가 텍스트를 다루려면]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-Tokenization-%EA%B8%B0%EA%B3%84%EA%B0%80-%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EB%8B%A4%EB%A3%A8%EB%A0%A4%EB%A9%B4</link>
            <guid>https://velog.io/@jul-ee/DS-DL-Tokenization-%EA%B8%B0%EA%B3%84%EA%B0%80-%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EB%8B%A4%EB%A3%A8%EB%A0%A4%EB%A9%B4</guid>
            <pubDate>Thu, 12 Jun 2025 06:05:46 GMT</pubDate>
            <description><![CDATA[<p>자연어 처리를 위해서는 토큰화(Tokenization)를 이해해야 한다. 문장을 기계가 이해할 수 있는 형태로 바꾸기 위해서는 먼저 문장을 잘게 쪼개는 작업이 필요하다.</p>
<p>이 글에서는 토큰화의 개념, 목적, 다양한 기법들을 정리해 보았다. 실질적으로 어떤 방식으로 기계가 텍스트를 다루는지 이해하기 위한 흐름에 중점을 두고 작성하였다.</p>
<br>
<br>
<br>

<h3 id="🖇-토큰화란">🖇 &nbsp;토큰화란?</h3>
<p>토큰화는 텍스트를 의미 있는 단위로 나누는 작업이다.</p>
<p>이 단위를 &#39;토큰(token)&#39;이라고 한다. 일반적으로는 단어 수준으로 나누지만 때로는 형태소, 음절, 심지어 문자 수준까지 세분화하기도 한다. 기계 학습 모델이 문장을 처리할 수 있게 만드는 첫 번째 전처리 단계로서, 이후 임베딩, 분류, 번역, 생성 등의 작업을 위해 필수적으로 수행된다.</p>
<h4 id="그녀는-나와-밥을-먹는다-는-몇-개의-단어로-이뤄졌을까">&quot;그녀는 나와 밥을 먹는다&quot; 는 몇 개의 단어로 이뤄졌을까?</h4>
<p>예를 들어, <code>&quot;그녀는 나와 밥을 먹는다&quot;</code> 라는 문장이 주어졌을 때</p>
<ul>
<li>공백 기준으로 보면: &nbsp;[&quot;그녀는&quot;, &quot;나와&quot;, &quot;밥을&quot;, &quot;먹는다&quot;] &nbsp;→ 4개의 토큰</li>
<li>형태소 기준으로 보면: &nbsp;[&quot;그녀&quot;, &quot;는&quot;, &quot;나&quot;, &quot;와&quot;, &quot;밥&quot;, &quot;을&quot;, &quot;먹는다&quot;] &nbsp;→ 7개의 토큰</li>
</ul>
<p>같은 문장이라도 어떤 기준으로 나누느냐에 따라 단어 수는 달라진다.
즉, 토큰의 정의는 토큰화 방식에 따라 결정된다.</p>
<p>이처럼 토큰화는 <strong>텍스트를 해석하고 처리하는 방식</strong>을 좌우하고, 문장 속 의미 단위의 경계를 설정하는 중요한 작업이다.</p>
<hr>
<br>
<br>

<h3 id="🖇-왜-토큰화가-필요한가">🖇 &nbsp;왜 토큰화가 필요한가?</h3>
<p>자연어는 비정형적이며 복잡한 구조를 가진다. 사람이 사용하는 언어는 문법적 오류, 은유, 생략, 반복, 신조어 등 다양한 변형이 가능하고, 의미도 문맥에 따라 달라진다. 따라서 기계가 이를 이해하려면 먼저 문장을 일정한 규칙에 따라 잘게 나누어 구조화된 형태로 바꾸는 과정이 필요하다.</p>
<h4 id="주요-목적">주요 목적</h4>
<ul>
<li>텍스트 전처리 및 정규화: 불필요한 공백, 특수기호 제거 등</li>
<li>문장의 의미 파악을 위한 구조 분석</li>
<li>단어, 구, 절 단위의 의미 정보 분리</li>
<li>OOV(Out-Of-Vocabulary) 문제 최소화</li>
<li>모델 학습의 효율성과 성능 향상</li>
</ul>
<p>토큰화는 단어의 경계, 문법적 구조, 의미적 관계 등을 파악하는 기반이 된다. 특히, 통계적 기법이나 딥러닝 모델에서는 토큰화된 단위가 임베딩의 기본 단위가 되기 때문에, 성능에 직접적인 영향을 미친다.</p>
<p>OOV 문제에 대해서는 뒤에서 간단하게 다뤄보았다.</p>
<hr>
<br>
<br>


<h3 id="🖇-다양한-토큰화-기법들">🖇 &nbsp;다양한 토큰화 기법들</h3>
<h4 id="공백-기반-토큰화">공백 기반 토큰화</h4>
<p>가장 기본적인 방식으로, <code>.split()</code> 함수를 이용해 단어를 공백 기준으로 분리한다. 영어처럼 띄어쓰기가 명확한 언어에서는 비교적 잘 작동하지만, 영어조차도 구두점이나 복합어 처리에 한계가 있다.</p>
<pre><code class="language-python">corpus = &quot;in the days that followed i learned to spell ...&quot;
tokens = corpus.split()
print(&quot;Tokens:&quot;, tokens)</code></pre>
<p>간단하게 토큰화를 수행할 수 있다는 장점이 있지만,
형태 변화(e.g., <code>day</code> vs <code>days</code>), 구두점 처리, 복합 단어 인식 실패, 의미 단위 단절 등의 문제가 존재한다.</p>
<br>

<h4 id="형태소-기반-토큰화">형태소 기반 토큰화</h4>
<p>한국어는 교착어이자 형태소 변화가 많은 언어다. 그렇기 때문에 공백 기준 토큰화로는 조사나 어미 분리가 어려워 엉뚱한 결과가 나온다.</p>
<p>형태소는 &#39;의미를 가지는 최소 단위&#39;이며 이를 단위로 쪼개는 것이 형태소 기반 토큰화이다.</p>
<p>e.g., &nbsp;<code>오늘도 공부만 한다</code> → [<code>오늘</code>, <code>도</code>, <code>공부</code>, <code>만</code>, <code>한다</code>]</p>
<p>주요 도구: <a href="https://konlpy.org/ko/v0.5.2/">KoNLPy</a></p>
<ul>
<li>분석기: Hannanum, Kkma, Komoran, Mecab, Okt</li>
</ul>
<pre><code class="language-python">from konlpy.tag import Mecab
mecab = Mecab()
print(mecab.pos(&quot;자연어처리는 매우 흥미로운 분야입니다.&quot;))</code></pre>
<p>분석기 선택 기준</p>
<ul>
<li>속도 중시 (대용량 문서 처리): Mecab</li>
<li>오탈자, 띄어쓰기 오류 대응: KOMORAN + Okt</li>
<li>정확도 중시: Kkma</li>
</ul>
<h5 id="참고-konlpy-파이썬-한국어-nlp---konlpy-052-documentationbr참고-한국어-형태소-분석기-성능-비교">[참고] <a href="https://konlpy.org/ko/v0.5.2/">KoNLPy: 파이썬 한국어 NLP - KoNLPy 0.5.2 documentation</a><br>[참고] <a href="https://iostream.tistory.com/144">한국어 형태소 분석기 성능 비교</a></h5>
<hr>
<br>
<br>

<h3 id="🖇-사전에-없는-단어-oov-문제">🖇 &nbsp;사전에 없는 단어, OOV 문제</h3>
<p>공백 기반 또는 형태소 기반 방식은 미리 정의된 단어 사전에 기반하여 토큰을 생성한다. 하지만 현실의 데이터는 새로운 단어, 신조어, 외래어가 끊임없이 등장하므로 사전에 없는 단어는 <code>&lt;unk&gt;</code>(unknown token)으로 치환하게 된다.</p>
<pre><code class="language-text">&quot;코로나바이러스는 2019년 12월 중국 우한에서 처음 발생한 뒤...&quot;
→ &quot;&lt;unk&gt;는 2019년 12월 중국 &lt;unk&gt;에서...&quot;</code></pre>
<p><code>&lt;unk&gt;</code> 토큰으로 치환되는 상황은 모델이 단어를 인식하지 못하게 되어, 의미 손실이나 예측 오류를 일으킬 수 있다. 이를 OOV(Out-Of-Vocabulary) 문제라고 한다.</p>
<p>OOV 문제는 모델이 학습할 때는 본 적이 없는 단어나 문장이 테스트 데이터에 등장하여 발생하는 문제로, 핵심 단어가 치환되면 문장의 의미 전달은 실패하게 된다.</p>
<p>이러한 OOV 문제를 완화하기 위해 단어를 더 작은 의미 단위로 분해하는 Subword 기반 접근법이 제안되었다. 대표적인 Subword 기반 접근에는 WordPiece Model과 SentencePiece가 있다.</p>
<hr>
<br>
<br>

<h3 id="🖇-subword란">🖇 &nbsp;Subword란?</h3>
<p>Subword는 단어보다 작은 단위로 구성된 의미 단위를 뜻한다.</p>
<p>Subword는 주로 의미 있는 접두사, 어근, 접미사 등의 조합으로 구성되며, 희귀 단어도 하위 단위로 분해해 처리함으로써 OOV 문제를 효과적으로 완화할 수 있다.</p>
<p>예를 들어, <code>unhappiness</code>라는 단어를 <code>un</code>, <code>happi</code>, <code>ness</code>와 같이 더 작은 단위로 분해할 수 있다. 이처럼 전체 단어가 아니라 단어의 일부분(sub)만으로도 의미를 구성할 수 있기 때문에 Subword 단위로 텍스트를 다루면 희귀 단어를 효과적으로 처리할 수 있다.</p>
<p>Subword 기반 토큰화는 사전에 없던 단어도 이미 학습된 하위 단위의 조합으로 표현할 수 있게 해 주어 OOV 문제 해결에 매우 효과적이다.</p>
<hr>
<br>
<br>

<h3 id="🖇-byte-pair-encoding-bpe">🖇 &nbsp;Byte Pair Encoding (BPE)</h3>
<p>BPE는 원래는 데이터 압축을 위해 만들어진 방식이다. 자주 등장하는 문자 쌍을 하나의 단위로 묶어 사전을 구성하는 방식인데 이를 자연어처리에 적용하면 단어를 더 작은 의미 단위로 나눠 처리할 수 있고, OOV 문제를 줄일 수 있다.</p>
<h4 id="bpe-작동-원리">BPE 작동 원리</h4>
<ol>
<li>모든 단어를 문자 단위로 나눈다.</li>
<li>가장 자주 등장하는 문자 쌍을 병합한다.</li>
<li>반복적으로 병합하면서 새로운 토큰을 만든다.</li>
</ol>
<p>아래의 간단한 예시로 BPE 알고리즘을 이해할 수 있다.</p>
<pre><code>aaabdaaabac
→ ZabdZabac (Z=aa)
→ ZYdZYac   (Y=ab)
→ XdXac     (X=ZY)</code></pre><p>BPE를 통해 희귀 단어를 문자 단위로 분해하여 처리할 수 있고, 토큰 조합만으로 대부분의 단어 표현 가능하다는 점도 큰 이점으로 작용한다. 또한, 병합 횟수에 따라 vocabulary 크기를 조절할 수 있어 Subword 기반 모델이 사용하는 사전을 효율적으로 설계하는 데 활용된다.</p>
<p>(레포 링크)
아래 논문에서 제공해 주는 예제로 동작 방식을 구현해 보았다.</p>
<h5 id="참고-논문-neural-machine-translation-of-rare-words-with-subword-units">[참고 논문] <a href="https://arxiv.org/pdf/1508.07909">Neural Machine Translation of Rare Words with Subword Units</a></h5>
<hr>
<br>
<br>

<h3 id="🖇-wordpiece-model-wpm">🖇 &nbsp;WordPiece Model (WPM)</h3>
<p>Google에서 제안한 WordPiece는 BPE의 변형으로 BERT, RoBERTa 등에서 사용된다. 단어 조합의 가능도(likelihood)를 기반으로 가장 자연스러운 형태의 토큰을 만든다는 점에서 더 향상된 접근이다.</p>
<h4 id="wpm-특징">WPM 특징</h4>
<ol>
<li>단어 시작을 <code>_</code> 기호로 표시하여 토큰 경계를 명시함
e.g., &nbsp;<code>_you</code>, <code>_are</code>, <code>_teacher</code></li>
<li>빈도수 기반이 아닌 가능도 기반 병합
→ &nbsp;더 자연스러운 분해와 문장 복원이 가능함</li>
</ol>
<p>WordPiece는 특히 조사, 어미 변화가 심한 한국어나 일본어에서 매우 효과적이다. 형태소 분석기 없이도 높은 정확도를 보이고, 언어 독립적인 접근이라는 점에서 장점이 크다.</p>
<h5 id="참고-논문-japanese-and-korean-voice-search">[참고 논문] <a href="https://static.googleusercontent.com/media/research.google.com/ko//pubs/archive/37842.pdf">JAPANESE AND KOREAN VOICE SEARCH</a></h5>
<hr>
<br>
<br>

<h3 id="🖇-sentencepiece-통합형-subword-토크나이저">🖇 &nbsp;SentencePiece: 통합형 Subword 토크나이저</h3>
<p>SentencePiece는 전처리 없이 원시 문장을 그대로 학습하여 subword 토큰을 생성하는 방식이다.</p>
<p>SentencePiece는 띄어쓰기를 유지하지 않고, 공백을 특수 문자 <code>▁</code>로 치환해 문장 구조를 보존하며 다국어와 비정형 텍스트에 강하다. 특히 한국어, 일본어 등에서도 별도의 형태소 분석기 없이 사용할 수 있다.</p>
<h4 id="sentencepiece-특징">SentencePiece 특징</h4>
<ul>
<li>공백을 특별한 문자로 처리</li>
<li>복잡한 전처리 과정 없이 바로 학습 가능</li>
<li>다양한 언어에 범용적으로 적용 가능</li>
</ul>
<h5 id="참고-github-googlesentencepiece">[참고] GitHub: <a href="https://github.com/google/sentencepiece">google/sentencepiece</a></h5>
<hr>
<br>
<br>

<h3 id="🖇-soynlp-한국어-전용-통계-기반-토크나이저">🖇 &nbsp;soynlp: 한국어 전용 통계 기반 토크나이저</h3>
<p>soynlp는 비지도 학습 기반으로 단어 경계를 인식하는 한국어 특화 토크나이저다. 단어의 다음 글자가 등장할 확률을 계산해 단어 경계를 추정하고, 사전이 필요 없다.</p>
<p>예를 들어 보면
<code>트</code>, <code>트와</code>, <code>트와이</code>, <code>트와이스</code></p>
<p>각각 다음 글자의 등장 확률을 비교하여 최적 경계를 결정하게 된다.</p>
<h4 id="soynlp-특징">soynlp 특징</h4>
<ul>
<li>비지도학습 기반으로 미등록 단어 처리 가능</li>
<li>형태소 분석의 대안으로 사용할 수 있으며, 신조어나 비표준어가 많은 데이터에 적합</li>
</ul>
<h5 id="참고-github-soynlp">[참고] GitHub: <a href="https://github.com/lovit/soynlp">soynlp</a></h5>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>지금까지 토큰화가 무엇인지와 그 필요성으로 시작하여 토큰화의 다양한 방식과 한국어에 특화된 접근까지 정리해 보았다.</p>
<p>토큰화는 문장을 기계가 이해할 수 있는 단위로 나누는 과정이고, 방식에 따라 분석 결과가 완전히 달라질 수 있다는 것을 알 수 있었다.</p>
<p>자연어처리는 사람의 언어를 기계가 이해할 수 있도록 바꾸는 작업이인데, 토큰화를 어떻게 처리하느냐에 따라 이후 모든 모델의 성능과 방향성이 달라질 수 있게 된다.</p>
<p>결국 완벽한 토크나이저는 없고, 목적과 언어에 맞는 선택이 중요하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 자연어 처리를 시작하기 전에]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EC%9E%90%EC%97%B0%EC%96%B4-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%A0%84%EC%97%90</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EC%9E%90%EC%97%B0%EC%96%B4-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%A0%84%EC%97%90</guid>
            <pubDate>Thu, 12 Jun 2025 05:56:37 GMT</pubDate>
            <description><![CDATA[<p>우리는 매일 수많은 문장을 읽고, 쓰고, 듣고, 말하며 살아간다.</p>
<p>아침에 스마트폰으로 보는 뉴스 기사, 친구에게 받은 메시지, 지금 이 블로그 글까지 모두 <strong>자연어(Natural Language)</strong>다. 자연어는 사람이 일상에서 자연스럽게 사용하는 언어를 뜻한다.</p>
<p>이처럼 자연어는 우리가 숨 쉬듯 사용하는 언어지만 정작 그 복잡성과 정체를 잘 인지하지 못한 채 살아간다. 대부분의 사람은 모국어를 &#39;학습&#39;했다기보다 &#39;노출&#39;을 통해 습득한다. 게다가 문법을 정확히 몰라도 말하고, 듣고, 쓸 수 있다.
<br></p>
<p>그렇다면 &nbsp;<em>&quot;기계는 이 언어를 제대로 이해할 수 있을까?&quot;</em></p>
<p>이 언어를 기계에게 가르치려고 할 때 인간의 언어는 생각보다 훨씬 더 복잡하다 것을 인지할 수 있다.</p>
<br>
<br>
<br>

<h3 id="🖇-자연어-vs-인공어">🖇 &nbsp;자연어 vs 인공어</h3>
<p>기계에게 언어를 가르친다고 하면 프로그래밍 언어를 떠올릴 수 있다. 파이썬, 자바스크립트, C 같은 언어들은 사람이 만든 <strong>인공어(Artificial Language)</strong>다. 문법이 명확하고 애매함이 없어, 기계는 언제나 같은 방식으로 이해하고 처리할 수 있다.</p>
<p>이러한 인공어는 <strong>문맥자유 문법(Context-free Grammar)</strong>을 따른다. 이는 한 문장을 해석할 때 앞뒤 문맥에 의존하지 않아도 되는 구조다. 컴파일러는 이러한 규칙 덕분에 에러를 빠르게 감지하고, 코드 실행 흐름을 정확하게 이해할 수 있다.</p>
<p>반면 자연어는 <strong>문맥의존 문법(Context-sensitive Grammar)</strong>을 따른다. 단어의 의미나 문장의 구조가 앞뒤 맥락에 따라 달라지고, 문법 규칙도 예외가 많다. 인간에게는 당연한 이 특성은 기계에게는 큰 장벽이 된다.</p>
<hr>
<br>
<br>

<h3 id="🖇-자연어의-복잡성">🖇 &nbsp;자연어의 복잡성</h3>
<p>예를 들어 보자.</p>
<blockquote>
<p>Alice drove down the street in her car.</p>
</blockquote>
<p>이 문장은 두 가지로 해석될 수 있다.</p>
<ol>
<li>앨리스가 자동차를 운전해서 거리를 달렸다.</li>
<li>앨리스가 차 안에 있는 &#39;거리&#39;를 운전해 달렸다.</li>
</ol>
<p>두 번째 해석은 현실적으로 말이 안 되지만, 문법적으로는 가능하다.</p>
<p>이럴 때 사람은 &#39;차 안에 거리 같은 게 있을 리 없다&#39;는 상식을 바탕으로 해석을 하나로 고정할 수 있다. 하지만 기계는 그렇지 않다. 기계에게 상식은 학습된 데이터로만 주어지며 그조차 부족하거나 왜곡되기 쉽다.</p>
<p>이처럼 자연어는 단어의 뜻, 문법, 문맥, 상황, 배경지식, 상식까지 모두 고려해야 정확히 이해할 수 있기 때문에 복잡한 것이다.</p>
<hr>
<br>
<br>

<h3 id="🖇-자연어의-어려움">🖇 &nbsp;자연어의 어려움</h3>
<p>자연어는 다음 네 가지 측면에서 기계 처리에 특히 어려움을 준다.</p>
<h4 id="1-중의성ambiguity">1. 중의성(Ambiguity)</h4>
<ul>
<li>하나의 문장이 둘 이상의 의미로 해석될 수 있음
<em>&quot;나는 그녀를 보고 웃었다&quot;</em> &nbsp;→ &nbsp;내가 웃었는지, 그녀가 웃었는지 모호</li>
</ul>
<h4 id="2-문맥의존성context-dependence">2. 문맥의존성(Context-dependence)</h4>
<ul>
<li>단어의 의미나 문장의 구조가 앞뒤 문맥에 따라 달라짐
<em>&quot;배가 아파&quot;</em> &nbsp;→ &nbsp;배(복부)? 배(선박)?</li>
</ul>
<h4 id="3-탈문법성non-grammaticality">3. 탈문법성(Non-grammaticality)</h4>
<ul>
<li>자연어는 종종 문법 규칙을 벗어난 표현을 포함함
<em>&quot;그니까 그거 있잖아, 어... 그거 뭐더라?&quot;</em></li>
</ul>
<h4 id="4-비정형성unstructured">4. 비정형성(Unstructured)</h4>
<ul>
<li>정해진 틀 없이 자유롭게 쓰이는 말과 표현</li>
<li>메신저 대화, 댓글, 속어 등</li>
</ul>
<p>이러한 특성 때문에 자연어처리는 문법 분석 수준에서 멈출 수 없고, 의미 분석, 문백 구조 파악, 도메인 지식 기반 추론까지 필요하다.</p>
<hr>
<br>
<br>

<h3 id="🖇-자연어-처리란-결국">🖇 &nbsp;자연어 처리란 결국</h3>
<p>여기서 자연어 처리를 왜 배우는가를 생각해 보게 되었다.</p>
<p>자연어를 처리하는 것은 굉장히 복잡하고 어려운 과정이라는 것을 알 수 있었다. 그렇다면 그저 문장을 처리하거나 번역하거나 챗봇을 만들기 위해서 사람들은 자연어 처리에 비용을 투자하고 있는 걸까? 하는 궁금증이 생겼다.</p>
<p>좀 더 넓게 보면 자연어 처리는 언어를 이해하는 시스템을 만드는 작업이고, 이는 인간의 사고방식을 기계에 담아내려는 시도라고 해석할 수 있을 것 같다.</p>
<p>이 관점에서 생각해 보면 결국 자연어 처리를 배우는 것은,</p>
<blockquote>
<p>&quot;기계가 어떻게 사람처럼 말하고, 듣고, 이해할 수 있을까?&quot;
를 진지하게 고민해보는 일이지 않을까?</p>
</blockquote>
<p>앞으로 공부해 나가다 보면 어딘가 결론에 도달할 수 있을 거라 기대한다.</p>
<hr>
<br>
<br>

<h3 id="🖇-기계는-어떻게-시작할까">🖇 &nbsp;기계는 어떻게 시작할까?</h3>
<p>기계는 언어를 해석하려면 가장 먼저 문장을 잘게 나눠야 한다. 이때 <strong>토큰화(Tokenization)</strong>라는 개념이 등장한다.</p>
<p>토큰화란 문장을 의미 단위(보통 단어 또는 그 이하)로 쪼개는 작업이다. 이 단계가 정확하지 않으면 이후에 아무리 좋은 모델을 써도 원하는 결과를 얻기 어렵다.</p>
<p>예시로 살펴보자.</p>
<p>&quot;나는 밥을 먹었다&quot;
→ &nbsp;[&quot;나는&quot;, &quot;밥을&quot;, &quot;먹었다&quot;]
혹은 → &nbsp;[&quot;나&quot;, &quot;는&quot;, &quot;밥&quot;, &quot;을&quot;, &quot;먹&quot;, &quot;었&quot;, &quot;다&quot;]</p>
<p>이처럼 문장을 어디서 자르고, 어떤 단위를 의미로 볼지 정하는 기준은 자연어 처리의 성능에 큰 영향을 미친다. 이 내용은 다음 글에서 자세히 다뤄 볼 예정이다.</p>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>이 글을 작성하면서 사람이 무의식적으로 사용하는 자연어가 기계에게 어떤 이유로 까다롭게 작용하는지 이해할 수 있었다.</p>
<p>당연한 사실이지만 기계 입장에서는 구조가 불명확하고 예외가 많은 복잡한 데이터라는 점을 생각하면서 자연어 처리에 어떤 어려움이 있을 수 있을지 생각해 보는 시간이 되었다.</p>
<p>동일한 문장도 상식이나 배경지식이 있어야 제대로 해석되는 것처럼  규칙 기반 처리만으로는 자연어를 이해할 수 없는 어려움을 어떻게 해결해 나갈 수 있을지 앞으로의 학습을 통해 확인해 보아야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Adam vs. SGD로 보는 EarlyStopping 작동 원리]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-Adam-vs-SGD%EB%A1%9C-%EB%B3%B4%EB%8A%94-EarlyStopping-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jul-ee/DS-DL-Adam-vs-SGD%EB%A1%9C-%EB%B3%B4%EB%8A%94-EarlyStopping-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Wed, 11 Jun 2025 08:44:53 GMT</pubDate>
            <description><![CDATA[<p>딥러닝 학습 중 <code>EarlyStopping</code> 콜백 동작에서 의문이 하나 생겼다.</p>
<blockquote>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&quot; 같은 모델, 같은 학습 조건인데 ..</p>
</blockquote>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Adam을 쓰면 중간에 멈추고,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SGD를 쓰면 끝까지 100 epoch 다 돌아버리네? &quot;</p>
<p>이 글에서는 왜 이런 차이가 발생하는지, 왜 SGD에서는 EarlyStopping이 작동하지 않는 것처럼 보이는 건지 (아니면 혹시 정말 작동하지 않는 건지)</p>
<p>이를 확인하고 이해하는 과정을 기록해 보았다.</p>
<br>
<br>
<br>

<h3 id="earlystopping이란">EarlyStopping이란?</h3>
<p><code>EarlyStopping</code>은 모델이 더 이상 성능이 좋아지지 않는 시점에서 학습을 멈추는 전략이다.</p>
<p>과적합을 막고, 학습 시간을 단축하는 데 유용하게 작용한다.</p>
<pre><code class="language-python">from tensorflow.keras.callbacks import EarlyStopping

&quot;&quot;&quot;
- monitor: 모니터링 대상 (보통 &#39;val_loss&#39; 또는 &#39;val_accuracy&#39;)
- patience: 개선되지 않아도 기다릴 epoch 수
- restore_best_weights: 가장 성능 좋았던 시점의 가중치 복원
&quot;&quot;&quot;
early_stopping_cb = EarlyStopping(
    monitor=&#39;val_loss&#39;,
    patience=10,
    restore_best_weights=True
)</code></pre>
<hr>
<br>
<br>



<h3 id="문제-상황-요약">문제 상황 요약</h3>
<p>같은 모델, 같은 데이터, 동일한 EarlyStopping 설정을 사용했는데도 Adam은 중간에 학습이 멈추고, SGD는 유독 끝까지 학습이 진행되는 것을 볼 수 있었다. 겉으로 보기엔 Adam에서는 EarlyStopping이 작동하고, SGD는 무시하는 것처럼 보일 수 있지만 <em>실제로 그런 건 아닐 것 같아</em> &nbsp;확실하게 짚고 넘어가고자 했다.</p>
<p>결론부터 이야기하자면,</p>
<p>EarlyStopping은 Adam과 SGD 모두 동일한 기준으로 동작한다. 다만, 그 조건을 <strong>누가 더 빨리 만족시키느냐</strong>의 차이라는 것을 알 수 있었다.</p>
<ul>
<li>Adam은 파라미터별로 학습률을 자동 조절(adaptive learning rate)하며, 모멘텀까지 활용해 빠르게 수렴한다. validation loss가 빠르게 줄고 plateau를 형성하면서 EarlyStopping이 쉽게 트리거된다.</li>
<li>반면 SGD는 학습률이 고정되어 있고 초기 수렴이 느리다. validation loss는 천천히 감소하거나 출렁거리면서, EarlyStopping이 감지할 수 있을 만큼 충분히 안정적인 개선 패턴을 보이지 않는다.</li>
</ul>
<p>EarlyStopping이 작동하지 않는 게 아니라 <strong>멈출 만한 조건이 만들어지지 않았을 뿐</strong>이다.</p>
<p>아래에서 실제 로그와 그래프를 분석하여 이를 확인해 보았다.</p>
<hr>
<br>
<br>

<h3 id="adam-vs-sgd">Adam vs SGD</h3>
<p>Adam과 SGD는 자주 사용되는 대표적인 옵티마이저다. 이 둘의 학습 특성은 EarlyStopping이 작동하는 방식에 큰 영향을 준다.</p>
<ul>
<li><p>학습률</p>
<ul>
<li>Adam: &nbsp;학습률을 파라미터별로 자동 조절. 초반부터 빠르게 손실 감소.</li>
<li>SGD: &nbsp;학습률 고정. 일반적으로 낮게 설정됨. 한 번의 업데이트 크기가 작고 수렴 속도도 느림.</li>
</ul>
</li>
<li><p>초기 수렴 속도</p>
<ul>
<li>Adam: &nbsp;몇 epoch만에 빠르게 손실 감소, plateau 상태 도달이 빠름.</li>
<li>SGD: &nbsp;느리게 손실 감소. plateau에 도달하려면 더 많은 epoch 필요.</li>
</ul>
</li>
<li><p>Validation Loss 패턴</p>
<ul>
<li>Adam: &nbsp;손실이 급격히 줄고, 이후 plateau 상태 또는 진동 발생. EarlyStopping이 감지하기 쉬움.</li>
<li>SGD: &nbsp;손실이 서서히 줄고, 진동도 적음. 개선 폭이 작아 변화가 없는 것처럼 보이기도 함.</li>
</ul>
</li>
<li><p>EarlyStopping 반응</p>
<ul>
<li>Adam: plateau가 뚜렷해서 EarlyStopping 기준을 쉽게 만족함.</li>
<li>SGD: 손실이 계속 조금씩 줄거나 출렁여서, 기준을 충족하지 못할 수 있음.</li>
</ul>
</li>
</ul>
<hr>
<br>
<br>

<h3 id="왜-sgd는-earlystopping이-잘-안-되는-것처럼-보일까">왜 SGD는 EarlyStopping이 잘 안 되는 것처럼 보일까?</h3>
<h4 id="1-수렴이-느림">1. 수렴이 느림</h4>
<p>SGD는 고정된 학습률로 매 epoch 이동하므로 수렴이 느리다. <code>val_loss</code>가 감소해도 그 속도가 느려서 patience 조건을 충족하지 못할 수 있다.</p>
<h4 id="2-진동이-적음">2. 진동이 적음</h4>
<p>SGD는 모멘텀이 없으면 최솟값 근처에서 진동이 크지 않다. 오히려 줄어드는 듯한 패턴을 유지해서 EarlyStopping이 작동하지 않을 수 있다.</p>
<h4 id="3-작동은-하지만-티가-안-남">3. 작동은 하지만 티가 안 남</h4>
<p>기술적으로는 EarlyStopping이 동작하고 있다. 단지, <code>val_loss</code>가 개선되지 않은 상태가 계속되지 않아서 트리거가 안 되는 것뿐이다.</p>
<hr>
<br>
<br>

<h3 id="실험-val_loss-시각화">실험: val_loss 시각화</h3>
<p>이 그래프는 동일한 모델 구조와 학습 조건에서 Adam과 SGD만 바꿔 학습시킨 결과를 시각화한 것이다. validation loss의 변동을 시각화하여 EarlyStopping의 작동을 확인하고자 하였다.</p>
<pre><code class="language-python">import matplotlib.pyplot as plt

def plot_val_loss(history_adam, history_sgd):
    plt.figure(figsize=(10, 5))
    plt.plot(history_adam.history[&#39;val_loss&#39;], label=&#39;Adam - val_loss&#39;, color=&#39;blue&#39;)
    plt.plot(history_sgd.history[&#39;val_loss&#39;], label=&#39;SGD - val_loss&#39;, color=&#39;orange&#39;)
    plt.xlabel(&#39;Epochs&#39;)
    plt.ylabel(&#39;Validation Loss&#39;)
    plt.title(&#39;EarlyStopping Behavior: Adam vs SGD&#39;)
    plt.legend()
    plt.grid(True)
    plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/68df95c1-0417-4c01-9618-aa97e4dadfd2/image.png" alt=""></p>
<h4 id="그래프-해석">그래프 해석</h4>
<p>실제 학습 로그와 (길어서 따로 첨부하진 않음)
그래프를 분석하여 <code>val_loss</code>와 EarlyStopping 작동 관계를 해석해 보았다.</p>
<p><strong>1. Adam 해석</strong></p>
<ul>
<li>Epoch 1~7: val_loss가 1.81 → 1.52까지 빠르게 감소</li>
<li>Epoch 8~12: 일시적으로 증가하거나 진동하는 구간</li>
<li>Epoch 13~29: 다시 감소해 최저점 1.4175 도달</li>
<li>Epoch 30~39: val_loss가 다시 증가 또는 정체 → <code>patience=10</code> 조건 만족 → EarlyStopping 작동</li>
</ul>
<p>→ Adam은 빠르게 수렴하고 이후 일정 구간에서 개선이 없는 상태가 이어져 조기 종료됨</p>
<p><strong>2. SGD 해석</strong></p>
<ul>
<li>Epoch 1~20: val_loss가 1.98 → 1.50 수준까지 점진적으로 감소</li>
<li>Epoch 21~40: 감소세가 유지되며 1.43대까지 도달함</li>
<li>Epoch 41<del>60: 미세한 진동과 함께 1.39</del>1.41 수준 유지</li>
<li>Epoch 61~100: 최저점 1.33 수준까지 <strong>계속 개선됨</strong> → EarlyStopping 작동 조건 미충족</li>
</ul>
<p>→ SGD는 전체 학습 내내 <code>val_loss</code>가 조금씩 줄어들며 plateau 없이 미세 개선이 지속됨
→ EarlyStopping은 조건이 충족되지 않아 작동하지 않았음</p>
<hr>
<br>
<br>

<h3 id="earlystopping-로그로-확인하기">EarlyStopping 로그로 확인하기</h3>
<p>EarlyStopping로 작동 여부를 확인할 수 있다.</p>
<p>이 글에서는 SGD를 적용했을 때 작동하지 않는 것처럼 보이는 원인을 알고 싶었던 것이기 때문에 간단하게 설명하고 넘어가겠다.</p>
<pre><code class="language-python">early_stopping_cb = EarlyStopping(
    monitor=&#39;val_loss&#39;,
    patience=10,
    verbose=1,  # 학습 중 중간 로그 출력
    restore_best_weights=True  # 성능이 가장 좋았던 에폭의 가중치로 자동 복원 + 로그 출력
)</code></pre>
<p>학습 중 다음 메시지가 보이면 EarlyStopping이 실제로 작동한 것이다.</p>
<pre><code>Epoch 30: early stopping
Restoring model weights from the end of the best epoch: 20.</code></pre><p>이 로그가 없다면 조건이 충족되지 않았다는 뜻이다.
즉, EarlyStopping은 항상 작동 중이며 조건을 기다리고 있는 중이다.</p>
<hr>
<br>
<br>

<h3 id="다른-옵티마이저는-어떨까">다른 옵티마이저는 어떨까?</h3>
<p>지금까지 대표적인 두 옵티마이저인 Adam과 SGD의 EarlyStopping 작동 차이에 초점을 맞추어 비교해 보았다.</p>
<p>추가적으로 RMSprop, Adagrad, Nadam 등의 옵티마이저는 Adam처럼 빠른 수렴을 유도하기 때문에 대부분 EarlyStopping이 잘 작동하는 편이다. 고전적인 SGD는 세밀한 튜닝 없이는 조건을 만족시키기 어려워 조기 종료가 잘 되지 않는 것처럼 보일 수 있는 것이다.</p>
<p>그 사이의 성격을 지닌 옵티마이저와 특징을 간단하게 정리해 보았다.</p>
<table>
<thead>
<tr>
<th>옵티마이저</th>
<th>특징</th>
<th>EarlyStopping 반응 가능성</th>
</tr>
</thead>
<tbody><tr>
<td>RMSprop</td>
<td>Adam의 전신, 학습률 자동 조정</td>
<td>빠르게 수렴 → 잘 작동</td>
</tr>
<tr>
<td>Adagrad</td>
<td>희소 데이터에 유리, 학습률 빠르게 감소</td>
<td>plateau 빨리 오면 작동함</td>
</tr>
<tr>
<td>Nadam</td>
<td>Adam + Nesterov 모멘텀</td>
<td>빠르고 진동 적음 → 잘 작동</td>
</tr>
<tr>
<td>SGD + momentum</td>
<td>진동 완화, 일반화 성능 높임</td>
<td>튜닝하면 잘 작동 가능</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 EarlyStopping이 작동하는지는 옵티마이저 자체보다는 수렴 특성과 손실 곡선 형태에 더 큰 영향을 받는다.</p>
</blockquote>
<blockquote>
<p>💡 옵티마이저를 선택할 때는 정확도 외에도,
학습 곡선의 형태와 EarlyStopping이 정상 작동할 수 있는지까지 고려하는 것이 중요하다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>EarlyStopping은 Adam, SGD 구분 없이 항상 작동하는데, 옵티마이저의 차이는 작동 조건을 얼마나 빨리 만족시키느냐에 있다는 것을 알 수 있었다.</p>
<p>Adam은 수렴이 빠르고 손실의 진동이 커서 조건을 조기에 만족시키는 경우가 많고, SGD는 손실이 완만하게 감소하기 때문에 조건을 만족시키지 못한 채 계속 학습되는 것처럼 보일 수 있는 것이다.</p>
<p>EarlyStopping이 작동하지 않는다고 무조건 문제라고 단정 짓지 말고, 그 작동 메커니즘을 이해하고 해석하는 시각을 갖추는 것이 중요하다는 생각이 들었다.</p>
<p>결국 모델 학습의 효율성을 높이려면 EarlyStopping을 비롯한 설정을 학습 과정의 피드백으로 활용하는 능력이 필요하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 딥러닝 모델 학습 기술: 이론]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%EA%B8%B0%EC%88%A0-%EC%9D%B4%EB%A1%A0</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%EA%B8%B0%EC%88%A0-%EC%9D%B4%EB%A1%A0</guid>
            <pubDate>Wed, 11 Jun 2025 07:16:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 콜백 (Callbacks)
🖇 &nbsp;2. 학습 단위 (Batch)
🖇 &nbsp;3. 데이터 스케일링 (Data Scaling)
🖇 &nbsp;4. 학습률과 에폭 (Learning Rate &amp; Epochs)
🖇 &nbsp;5. 은닉층과 뉴런 수 (Hidden Layers &amp; Neurons)
🖇 &nbsp;6. 활성화 함수 (Activation Function)
🖇 &nbsp;7. 가중치 초기화 (Weight Initialization)
🖇 &nbsp;8. 옵티마이저 (Optimizer)</p>
</blockquote>
<br>

<p>딥러닝 모델의 성능에 있어서 &#39;학습 기술&#39;이 중요하게 작용한다. 어떤 활성화 함수를 사용할지, 가중치는 어떻게 초기화할지, 학습률은 얼마나 줄지, 배치 단위는 어떻게 설정할지 등 세부적인 설정이 전체 결과를 결정짓게 된다.</p>
<p>이 글에서는 이러한 딥러닝 학습 기술들의 특징을 기반으로 반드시 알아야 할 요소를 주제별로 정리해 보았다. 그 개념과 흐름을 이해하는 데 중점을 두었다.</p>
<br>
<br>

<h3 id="🖇-1-콜백-callbacks">🖇 &nbsp;1. 콜백 (Callbacks)</h3>
<p>콜백은 모델 학습 중간에 특정 조건을 만족하면 실행되는 기능이다.
Keras에서는 <code>fit()</code> 함수의 인자로 <code>callbacks</code> 리스트를 통해 지정할 수 있다.</p>
<h4 id="modelcheckpoint">ModelCheckpoint</h4>
<ul>
<li>일정 주기로 모델의 가중치를 저장해두는 기능</li>
<li>학습 도중 중단되거나 성능이 나빠진 경우, 가장 좋은 성능의 가중치를 복구할 수 있음</li>
</ul>
<h4 id="earlystopping">EarlyStopping</h4>
<ul>
<li>검증 성능이 개선되지 않으면 학습을 자동 중단시킴</li>
<li><code>patience</code> 만큼의 여유 기간을 두고 개선 여부를 판단</li>
<li>과적합을 방지하고, 불필요한 에폭을 줄이는 데 유리함</li>
<li>기본적으로는 <code>monitor=&#39;val_loss&#39;</code>를 기준으로 조기 종료를 판단하며, 필요에 따라 <code>val_accuracy</code>나 다른 지표로 변경 가능함</li>
<li><code>restore_best_weights=True</code>로 설정하면 별도 저장 없이도 최적 가중치를 자동 복원</li>
</ul>
<h4 id="learningratescheduler">LearningRateScheduler</h4>
<ul>
<li>학습이 진행됨에 따라 학습률을 점차 줄이거나 일정한 전략으로 조절</li>
<li>초기에는 빠르게 학습하다가 후반에 더 정밀한 수정을 가능하게 함</li>
</ul>
<h4 id="tensorboard">TensorBoard</h4>
<ul>
<li>학습 과정의 손실, 정확도, 학습률 등의 변화를 시각화하여 확인 가능</li>
<li>로그 파일을 저장할 디렉토리를 지정하면 웹 인터페이스로 간편하게 모니터링 가능</li>
</ul>
<blockquote>
<p>💡 이처럼 콜백은 학습 도중 모델이 과적합되지 않도록 막아주고, 가장 성능이 좋았던 상태를 자동으로 저장하거나 복원해서 실수 없이 안정적으로 학습을 마칠 수 있게 해준다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-2-학습-단위-batch">🖇 &nbsp;2. 학습 단위 (Batch)</h3>
<p>전체 데이터를 한 번에 학습시키는 Full Batch 방식은 이론상 정확하지만, 계산 효율이 매우 떨어진다. 대신 다음의 방식들이 자주 사용된다.</p>
<h4 id="stochastic-gradient-descent-sgd">Stochastic Gradient Descent (SGD)</h4>
<ul>
<li>데이터 샘플을 하나씩 학습에 사용</li>
<li>진동이 심하고 수렴 경로가 불안정하지만 메모리 효율이 높음</li>
</ul>
<h4 id="mini-batch">Mini-Batch</h4>
<ul>
<li>전체 데이터를 일정한 크기(n)로 분할하여 학습</li>
<li>SGD의 장점(속도)과 Batch의 장점(안정성)을 적절히 결합한 형태</li>
<li>학습 속도, 수렴 안정성, 메모리 사용 사이의 균형을 잡기 위함</li>
</ul>
<p><strong>Batch Size</strong>가 작을수록 진동이 커지고 시간이 오래 걸리지만, 일반화에 유리한 경우도 있다.</p>
<blockquote>
<p>💡 실제로 대부분 Mini-Batch 방식을 사용한다. 적절한 배치 크기 선택은 하드웨어, 메모리, 데이터 특성에 따라 달라진다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-3-데이터-스케일링-data-scaling">🖇 &nbsp;3. 데이터 스케일링 (Data Scaling)</h3>
<p>입력 데이터의 스케일이 너무 다르면 특정 특성이 가중치 학습에 불균형한 영향을 미친다. 이로 인한 왜곡을 방지하기 위해 전처리가 필요하다.</p>
<h4 id="표준화-standardization">표준화 (Standardization)</h4>
<ul>
<li>평균을 0, 분산을 1로 조정 (z-score normalization)
  → &nbsp;정규분포 형태로 스케일 조정</li>
<li>선형 회귀, PCA, 신경망 등 대부분의 기법에서 안정적인 학습을 유도함</li>
<li>딥러닝에서는 표준화가 더 일반적이며, 배치 정규화(Batch Normalization)나 층 정규화(Layer Normalization)와도 관련 깊음</li>
</ul>
<h4 id="정규화-normalization">정규화 (Normalization)</h4>
<ul>
<li>모든 값을 0~1 범위로 조정 (min-max scaling)</li>
<li>이미지 픽셀과 같이 고정 범위가 있는 경우에 적합</li>
</ul>
<blockquote>
<p>💡 표준화는 모델이 정규분포 전제를 포함할 때 유리하고, 정규화는 범위 제한이 필요한 상황에 자주 사용된다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-4-학습률과-에폭-learning-rate--epochs">🖇 &nbsp;4. 학습률과 에폭 (Learning Rate &amp; Epochs)</h3>
<h4 id="학습률-learning-rate">학습률 (learning rate)</h4>
<p>→ &nbsp;손실 함수의 기울기(gradient)에 곱해지는 값</p>
<ul>
<li>가중치를 얼마나 크게 변화시킬지를 결정함</li>
<li>너무 크면 최솟값을 지나쳐 버릴 수 있고(overshoot) → 발산</li>
<li>너무 작으면 수렴 속도가 느려짐</li>
<li>보통 초기에는 큰 값을 주고 점차 줄이는 방식(step decay, cosine decay 등)을 사용함<ul>
<li>Step Decay: &nbsp;일정 에폭마다 학습률을 줄임</li>
<li>Cosine Annealing: &nbsp;학습률을 코사인 함수 곡선처럼 점차 감소</li>
<li>Warm-up: &nbsp;초반 몇 에폭 동안 학습률을 천천히 증가시킴</li>
</ul>
</li>
</ul>
<h4 id="에폭-epochs">에폭 (epochs)</h4>
<p>→ &nbsp;전체 데이터를 몇 번 반복 학습할지를 의미</p>
<ul>
<li>학습률이 작아질수록 충분한 학습을 위해 더 많은 에폭이 필요해짐</li>
<li>적절한 에폭은 실험을 통해 찾아야 함 (EarlyStopping을 통해 자동 조절 가능)</li>
</ul>
<blockquote>
<p>💡 학습률과 에폭은 서로 보완 관계이고, 일정 학습률 이하로 줄어들면 더 이상 손실 감소 효과가 없기 때문에 EarlyStopping과 연동하는 것이 일반적이다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-5-은닉층과-뉴런-수-hidden-layers--neurons">🖇 &nbsp;5. 은닉층과 뉴런 수 (Hidden Layers &amp; Neurons)</h3>
<p>딥러닝 모델의 표현력은 은닉층(hidden layer)의 수와 각 층의 뉴런(neuron) 수에 따라 달라진다.</p>
<ul>
<li>층이 너무 적으면 패턴을 학습하지 못함 → 과소적합</li>
<li>너무 많으면 학습 데이터에 과도하게 적합함 → 과대적합</li>
<li>일반적으로 1~3개의 은닉층으로도 충분한 성능을 낼 수 있으며, 층 수보다 뉴런 수 조절이 더 민감하게 작용하는 경우도 많음</li>
</ul>
<blockquote>
<p>💡 적절한 구조는 데이터의 복잡도에 따라 달라지고, Dropout이나 정규화를 통해 복잡도 제어가 가능하다.</p>
<p>💡 중요한 건 층의 수가 아니라 필요한 만큼의 표현력만 확보하는 것이다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-6-활성화-함수-activation-function">🖇 &nbsp;6. 활성화 함수 (Activation Function)</h3>
<p>딥러닝은 선형 모델로는 표현할 수 없는 비선형 패턴을 학습해야 하므로 활성화 함수가 필요하다.
→ &nbsp;딥러닝이 단순 선형 회귀보다 뛰어난 이유는 비선형 활성화 함수 덕분이다.</p>
<h4 id="sigmoid-계열">Sigmoid 계열</h4>
<ul>
<li>Sigmoid, Tanh, Softsign 등</li>
<li>출력이 제한적(0<del>1 또는 -1</del>1)</li>
<li>출력값이 0 또는 1 근처에서 평탄해져서 기울기 소실(vanishing gradient)이 발생할 수 있음</li>
<li>RNN의 출력층 등 특수한 경우에만 사용됨</li>
</ul>
<h4 id="relu-계열">ReLU 계열</h4>
<ul>
<li>ReLU, Leaky ReLU, ELU, SELU 등</li>
<li>기울기 소실 문제가 적고 연산 속도가 빠름</li>
<li>단점: 음수 입력에서 출력이 0이 되어 뉴런이 죽는 문제(dead neuron)가 생길 수 있음 → &nbsp;ReLU</li>
<li>대부분의 CNN, DNN에서 기본 활성화 함수로 채택</li>
</ul>
<blockquote>
<p>💡 ReLU가 기본값으로 사용되고, 음수 대응이 필요한 경우 Leaky ReLU 등을 고려한다. </p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-7-가중치-초기화-weight-initialization">🖇 &nbsp;7. 가중치 초기화 (Weight Initialization)</h3>
<p>신경망 학습은 적절한 초기 가중치 없이는 불가능하다.</p>
<p>모든 가중치를 0으로 초기화하면 모든 뉴런이 같은 출력을 내고, 역전파가 동작하지 않는다. 무작위로 초기화하되, 분포를 조절하여 효율적인 학습이 가능하게 하게 해야 한다.</p>
<p>다음은 대표적인 초기화 방법이다.</p>
<h4 id="xavier-초기화">Xavier 초기화</h4>
<ul>
<li>입력과 출력의 노드 수에 기반하여 분포 조정</li>
<li>Sigmoid, Tanh 등 대칭 함수에 적합</li>
</ul>
<h4 id="he-초기화">He 초기화</h4>
<ul>
<li>ReLU 계열 함수에 최적화</li>
<li>평균 0, 표준편차 √(2/n) 분포</li>
</ul>
<blockquote>
<p>💡 올바른 초기화는 학습이 안정적으로 수렴하도록 해주고, 빠른 속도로 최적점에 도달할 수 있도록 만든다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-8-옵티마이저-optimizer">🖇 &nbsp;8. 옵티마이저 (Optimizer)</h3>
<p>옵티마이저는 손실 함수가 최소가 되도록 파라미터를 조정하는 역할을 한다.</p>
<ul>
<li><p><strong>SGD</strong>: 기본 경사 하강법. 단순하지만 느림</p>
</li>
<li><p><strong>Momentum, NAG</strong>: 경사 방향에 관성을 부여하여 진동 방지</p>
</li>
<li><p><strong>Adagrad</strong>: 파라미터별 학습률 조절. 자주 업데이트되는 파라미터에 작은 학습률 부여</p>
</li>
<li><p><strong>RMSprop</strong>: 최근 그래디언트만 반영하여 학습률 조절. RNN 등에서 효과적</p>
</li>
<li><p><strong>Adam</strong>: 모멘텀 + RMSprop 결합. 대부분의 모델에서 기본값으로 채택</p>
</li>
</ul>
<blockquote>
<p>💡 옵티마이저는 모델의 수렴 속도, 안정성, 일반화 성능을 결정하는 중요한 요소다.</p>
</blockquote>
<blockquote>
<p>💡 딥러닝에서는 대부분 Adam을 시작점으로 사용하고, 성능 향상 필요시 Lookahead, LAMB, Ranger 등의 최신 기법을 탐색한다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="9-과소적합과-과대적합">9. 과소적합과 과대적합</h3>
<p>모델이 너무 단순하거나 너무 복잡하면 학습 성능에 문제가 발생한다.</p>
<h4 id="과소적합-underfitting">과소적합 (Underfitting)</h4>
<ul>
<li>학습 데이터를 충분히 반영하지 못한 상태</li>
<li>모델이 지나치게 단순하거나, 에폭이 부족할 때 발생</li>
<li>해결: 더 복잡한 모델, 더 많은 에폭, 더 좋은 특징 추출</li>
</ul>
<h4 id="과대적합-overfitting">과대적합 (Overfitting)</h4>
<ul>
<li>학습 데이터에 과하게 맞춰져서 새로운 데이터에 일반화되지 않는 상태</li>
<li>너무 많은 파라미터, 너무 많은 학습 횟수, 너무 적은 데이터에서 발생</li>
<li>해결: L1/L2 정규화, Dropout, 데이터 증강, 학습 조기 종료, 모델 단순화</li>
</ul>
<blockquote>
<p>💡 결국 좋은 모델은 학습 데이터에 너무 덜 맞춰서도, 너무 과하게 맞춰서도 안 된다. 일반화 성능을 중심으로 모델을 평가하고 조정해야 한다.</p>
</blockquote>
<hr>
<br>
<br>

<h2 id="인사이트-및-회고">인사이트 및 회고</h2>
<p>딥러닝은 모델을 많이 쌓는 것보다 학습을 잘 되게 만드는 설계가 훨씬 중요하다는 것을 알 수 있었다. 에폭을 늘리면 일단 성능이 나아지는 줄 알았는데 학습률이나 가중치 초기화가 잘못되면 아예 시작부터 학습이 안 된다는 걸 이해하였다.</p>
<p>EarlyStopping 같은 콜백이 왜 필요한지, 옵티마이저마다 어떤 차이가 있는지를 이해하는 것도 학습의 안정성에 있어 중요하다는 것을 알았다.</p>
<p>앞으로 학습이 안 되는 상황이 오면 무작정 구조를 바꾸기보다 먼저 학습 환경을 점검해 봐야겠다는 기준이 생겼다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 옵티마이저와 지표]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EC%98%B5%ED%8B%B0%EB%A7%88%EC%9D%B4%EC%A0%80%EC%99%80-%EC%A7%80%ED%91%9C</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EC%98%B5%ED%8B%B0%EB%A7%88%EC%9D%B4%EC%A0%80%EC%99%80-%EC%A7%80%ED%91%9C</guid>
            <pubDate>Mon, 09 Jun 2025 04:01:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;0. 딥러닝 학습 흐름
🖇 &nbsp;1. 옵티마이저란?
🖇 &nbsp;2. 경사하강법 (Gradient Descent)
🖇 &nbsp;3. 볼록 함수 vs 비볼록 함수
🖇 &nbsp;4. 학습률 (Learning Rate)
🖇 &nbsp;5. 지표 (Metrics)</p>
</blockquote>
<br>

<p>모델 학습에서 손실 함수가 예측 성능을 수치화해준다면, 옵티마이저는 그 손실 값을 최소화하기 위해 파라미터를 어떻게 업데이트할지를 결정하는 역할을 한다. 학습 도중 혹은 완료 후 모델 성능을 평가할 때는 지표(metrics)를 사용한다.</p>
<p>이 글에서는 딥러닝의 학습 흐름을 이해하고 옵티마이저의 개념과 경사하강법, 학습률, 지표의 종류 순서로 정리해 보았다.</p>
<br>
<br>


<h3 id="🖇-0-딥러닝-학습-흐름">🖇 &nbsp;0. 딥러닝 학습 흐름</h3>
<p>사람은 실수하거나 경험을 통해 피드백을 받고 점점 더 나아지는 방식으로 학습한다. 반면 딥러닝은 손실 함수를 통해 예측과 정답 간의 오차를 계산하고, 이를 기반으로 모델의 가중치를 반복적으로 업데이트해 나가는 방식으로 학습이 이루어진다. 오차가 작아지도록 모델 내부의 수많은 가중치를 미세하게 조정해가는 과정이 딥러닝의 학습이다.</p>
<p>딥러닝 모델의 학습 과정은 다음과 같은 흐름으로 이루어진다.</p>
<ol>
<li>입력 X가 모델에 들어가고</li>
<li>레이어를 거쳐 예측 Y&#39;를 만든다.</li>
<li>예측값 Y&#39;와 정답 Y를 비교하여 손실 함수로 손실값을 계산한다.</li>
<li>손실값을 기준으로 옵티마이저가 가중치를 업데이트한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/f886cc17-2688-46a5-8e04-ca3ed2afc65c/image.png" alt=""></p>
<blockquote>
<p>💡 딥러닝 모델의 학습에 필요한 구성요소는 입력 X, 정답 Y, 손실 함수, 옵티마이저, 그리고 예측값 Y&#39;로 이루어진다. 먼저 데이터셋을 입력 X와 실제 정답(레이블)인 Y로 구분하고, 입력 데이터는 연속된 레이어를 통해 예측값 Y&#39;로 출력된다. 이때 손실 함수는 모델의 예측값 Y&#39;과 실제 정답 Y의 차이를 손실값으로 계산하고, 이 손실값을 기준으로 옵티마이저가 가중치를 업데이트한다. 이 과정은 예측 → 오차 측정 → 가중치 업데이트의 순서로 반복되며 계산한 손실값을 점점 줄여나가는 방향으로 진행된다. 결국 계산한 손실값을 최소화하도록 옵티마이저가 동작하는 것이 딥러닝 모델 학습이다.</p>
</blockquote>
<p>이처럼 딥러닝 학습은 예측-오차 계산-오차 최소화를 반복하는 과정이고, 손실 함수와 옵티마이저는 각각 오차 계산과 학습 방향 결정을 담당한다.</p>
<hr>
<br>
<br>

<h3 id="🖇-1-옵티마이저란">🖇 &nbsp;1. 옵티마이저란?</h3>
<p>옵티마이저는 손실 함수 값을 기준으로 모델 파라미터(가중치 등)를 어떤 방식으로 조정할지 결정하는 알고리즘이다. 딥러닝 모델이 학습할 때 사용하는 <strong>최적화 기법</strong>이다.</p>
<ul>
<li>손실 함수는 오차를 수치화하고,</li>
<li>옵티마이저는 그 값을 줄이기 위해 모델을 업데이트한다.</li>
</ul>
<p>Keras에서는 여러 종류의 옵티마이저를 제공하는데 모델에 맞게 선택하여 사용할 수 있다.</p>
<h4 id="자주-쓰이는-옵티마이저-종류">자주 쓰이는 옵티마이저 종류</h4>
<p><code>keras.optimizer.SGD()</code></p>
<ul>
<li>가장 기본적인 옵티마이저</li>
<li>확률적 경사하강법(Stochastic Gradient Descent)</li>
</ul>
<p><code>keras.optimizer.Adam()</code></p>
<ul>
<li>학습률을 자동 조정해주는 대표적인 옵티마이저</li>
</ul>
<p>SGD는 고정된 학습률과 단순한 기울기 업데이트 방식을 사용하지만, Adam은 모멘텀과 적응형 학습률을 활용해 더 안정적이고 빠른 수렴을 기대할 수 있다. 특히 안장점이나 평평한 지역에서 잘 빠져나오는 특징이 있어 많이 쓰인다.</p>
<p>옵티마이저는 <code>model.compile()</code> 단계에서 설정하고, 필요하면 별도 객체로 생성해서 하이퍼파라미터를 조정할 수도 있다.</p>
<hr>
<br>
<br>

<h3 id="🖇-2-경사하강법-gradient-descent">🖇 &nbsp;2. 경사하강법 (Gradient Descent)</h3>
<p>옵티마이저는 손실 함수를 기반으로 모델이 어떻게 업데이트되어야 하는지 결정한다. Keras에서 여러 옵티마이저를 제공하고, 사용자는 특정 종류의 확률적 경사 하강법을 지정할 수 있다.</p>
<p>경사하강법은 대부분의 옵티마이저가 기반으로 삼는 핵심 원리다.</p>
<p>손실 함수가 정의된 공간에서 그 함수의 기울기를 따라 <strong>손실 값이 줄어드는 방향으로 조금씩 이동</strong>하면서 최적의 파라미터를 찾아가는 방식이다.</p>
<p>수학적으로는 미분을 통해 기울기를 계산하고, 그 기울기의 반대 방향으로 파라미터를 업데이트한다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/b45d6c22-1c3a-48c3-afec-4000b0256e90/image.png" alt=""></p>
<p>$$$
x_{n} = x_{n-1} - \eta \cdot \nabla f(x)
$$$</p>
<ul>
<li>$x$: &nbsp;현재 파라미터</li>
<li>$\eta$: &nbsp;학습률 (learning rate)</li>
<li>$\nabla f(x)$: &nbsp;손실 함수의 기울기</li>
</ul>
<blockquote>
<p>$f(x+\Delta x) - f(x)$ 형태의 미분 정의를 통해 변화량이 큰 곳에서는 빠르게 이동하고, 평평한 곳에서는 천천히 움직인다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/06965bc0-1f92-4b13-b63b-462ec8b3ff55/image.png" alt=""></p>
<h4 id="경사하강법의-한계-안장점-문제">경사하강법의 한계: 안장점 문제</h4>
<p>경사하강법은 기울기를 따라 이동하기 때문에 <strong>기울기가 0인 지점에서는 더 이상 나아가지 못한다.</strong> 이때 해당 지점이 실제 최소값이 아니라면 문제가 된다.</p>
<ul>
<li>이런 지점을 안장점(Saddle Point)이라 부른다.</li>
<li>안장점은 한 축에서는 최솟값처럼 보이지만, 다른 축에서는 최댓값처럼 보이므로 기울기가 0이 되어 멈추게 된다.</li>
<li>Adam, RMSProp 등의 옵티마이저는 이 문제를 피하기 위해 이전 변화량을 누적하거나, 축마다 학습률을 조정하는 전략을 사용한다.</li>
</ul>
<hr>
<br>
<br>

<h3 id="🖇-3-볼록-함수-vs-비볼록-함수">🖇 &nbsp;3. 볼록 함수 vs 비볼록 함수</h3>
<p>최적화 과정에서 손실 함수의 형태에 따라 결과가 달라질 수 있다.</p>
<ul>
<li><strong>볼록 함수(Convex Function)</strong>: 어디서 시작하든 전역 최적점(최솟값)에 도달함</li>
<li><strong>비볼록 함수(Non-Convex Function)</strong>: 시작 위치에 따라 다른 국소 최소점에 빠질 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/59958637-ffca-4b52-bb60-58bde34616ef/image.png" alt=""></p>
<p>대부분의 딥러닝 모델은 비선형 활성 함수 등을 포함하고 있어 손실 함수가 비볼록 함수가 되는 경우가 많다. 이 때문에 경사하강법 기반의 옵티마이저가 안정적으로 작동하려면 하이퍼파라미터 조정이나 초기값 설정이 중요하다.</p>
<hr>
<br>
<br>

<h3 id="🖇-4-학습률-learning-rate">🖇 &nbsp;4. 학습률 (Learning Rate)</h3>
<p>학습률은 옵티마이저가 파라미터를 얼마만큼 변경할지를 결정하는 값이다.</p>
<ul>
<li>너무 크면 최솟값을 지나쳐 발산할 수 있고,</li>
<li>너무 작으면 학습이 매우 느리거나 지역 최솟값에서 멈춰버릴 수 있다.</li>
</ul>
<p>경사하강법을 통해 손실함수의 반대 기울기 방향으로 업데이트 할 때 업데이트 되는 크기에 관여하며 잘못 설정한 경우 손실함수의 최저점에 도달하지 못할 수 있다.
<img src="https://velog.velcdn.com/images/jul-ee/post/b579b66f-7fec-4343-ba3f-4132dfb6e1e7/image.png" alt=""></p>
<p>위에서 볼 수 있듯이</p>
<ul>
<li>큰 학습률: 수렴하지 못하고 진동하거나 발산</li>
<li>작은 학습률: 느리게 수렴하거나 정체</li>
<li>적절한 학습률: 빠르게 최적점에 수렴</li>
</ul>
<p>일반적으로는 작은 학습률로 시작하고 일정 횟수마다 감소시키는 스케줄링을 쓰거나, Adam과 같은 적응형 옵티마이저를 통해 자동 조절하는 방식이 활용된다.</p>
<p>대표적인 스케줄링 기법으로는 <code>StepDecay</code>, <code>ExponentialDecay</code>, <code>ReduceLROnPlateau</code> 등이 있다.</p>
<hr>
<br>
<br>

<h3 id="🖇-5-지표-metrics">🖇 &nbsp;5. 지표 (Metrics)</h3>
<p>손실 함수가 학습 과정의 최적화를 위한 기준이라면 지표는 모델이 얼마나 잘 작동하는지를 평가하기 위한 기준이다. 손실 함수는 역전파와 파라미터 업데이트에 영향을 주는 반면, 지표는 오직 평가 목적으로만 사용된다.</p>
<p>모델을 평가할 때는 손실 함수 외에도 다양한 지표(metrics)를 함께 설정한다.</p>
<h4 id="keras에서-자주-쓰는-지표">Keras에서 자주 쓰는 지표</h4>
<ul>
<li><code>mae</code> (Mean Absolute Error): &nbsp;회귀 문제에서 자주 사용</li>
<li><code>accuracy</code>: &nbsp;분류 문제에서 정확도를 측정</li>
<li><code>acc</code>: &nbsp;<code>accuracy</code>의 줄임말로도 사용 가능</li>
</ul>
<p>학습 중에는 지표의 변화 추이를 통해 과적합 여부나 학습 상태를 확인할 수 있다. Keras에서는 metrics 리스트를 통해 여러 지표를 동시에 모니터링할 수 있다.</p>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>지금까지 딥러닝 학습 흐름부터 옵티마이저, 경사하강법, 학습률, 지표의 개념과 실제 적용 시 주의할 점까지 살펴보았다. 마무리하면서 각 개념에서 이해해야 할 주요 이슈들을 정리해 보았다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>역할</th>
<th>이슈</th>
</tr>
</thead>
<tbody><tr>
<td>옵티마이저</td>
<td>손실 값을 줄이기 위한 파라미터 업데이트 방식</td>
<td>Adam, SGD 등 다양한 종류, 학습률 필요</td>
</tr>
<tr>
<td>경사하강법</td>
<td>기울기를 따라 손실 값을 줄이는 원리</td>
<td>안장점에서 정체 가능성 있음</td>
</tr>
<tr>
<td>학습률</td>
<td>파라미터를 얼마나 빠르게 조정할지 결정</td>
<td>크면 발산, 작으면 수렴 속도 느림</td>
</tr>
<tr>
<td>지표</td>
<td>모델 성능 평가용 기준</td>
<td><code>mae</code>, <code>accuracy</code> 등 문제 유형별 선택</td>
</tr>
</tbody></table>
<p>손실 함수가 예측과 정답의 차이를 수치로 표현한다면 옵티마이저는 그 차이를 줄이기 위한 방법이고, 지표는 그 결과를 평가하는 기준이라는 것을 알 수 있었다.</p>
<p>학습률이나 함수의 형태에 따라 학습 성능이 크게 달라질 수 있으므로 이론과 함께 실험을 병행하며 설정값을 조정하는 과정이 중요하다고 생각된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 손실 함수 (Loss Function)]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EC%86%90%EC%8B%A4-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EC%86%90%EC%8B%A4-%ED%95%A8%EC%88%98</guid>
            <pubDate>Mon, 09 Jun 2025 03:47:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;손실 함수란?
🖇 &nbsp;Keras의 손실 함수
🖇 &nbsp;평균절대오차 (MAE)
🖇 &nbsp;평균제곱오차 (MSE)
🖇 &nbsp;MAE vs MSE
🖇 &nbsp;교차 엔트로피 오차 (CEE)
🖇 &nbsp;손실 함수 값 계산</p>
</blockquote>
<br>

<p>&quot;손실 함수(Loss Function)&quot;는 딥러닝에서 매우 중요한 개념이다. 문맥에 따라 목적 함수(Objective Function) 등으로 혼용되는 경우도 많다. 어떤 경우에는 손실 함수가 목적 함수 그 자체로 사용되고, 어떤 경우에는 손실 함수에 정규화 항까지 포함된 전체를 목적 함수로 간주하기도 한다. 이 글에서는 이해를 위해 손실 함수로 통일하여 사용하겠다.</p>
<p>이 글에서는 손실 함수의 이론적 개념과 실제 코드에서 자주 사용되는 함수명과 적용 맥락의 순서로 정리해 보았다.</p>
<br>
<br>


<h3 id="🖇-손실-함수란">🖇 &nbsp;손실 함수란?</h3>
<p>모델이 <strong>학습을 잘하고 있는지를 판단</strong>하려면 그 과정에 대한 지표가 필요하다. 그 역할을 해주는 것이 손실 함수(Loss Function)다.</p>
<p>손실 함수는 모델이 예측한 값과 실제 정답 사이의 오차를 수치로 나타내고, 이 값이 작을수록 예측이 정확하다는 뜻이다. 학습은 이 <strong>손실 함수를 최소화</strong>하는 방향으로 진행된다. 딥러닝에서 손실 함수는 최적화 이론에 따라 미분 가능한 함수여야 하며 그 결과값을 기반으로 파라미터를 업데이트한다.</p>
<h4 id="손실함수의-역할">손실함수의 역할</h4>
<ul>
<li>학습 중 모델이 얼마나 잘 예측하고 있는지 수치적으로 보여줌</li>
<li>정답과 예측값의 차이를 바탕으로 파라미터를 어떻게 조정할지 결정하게 함</li>
<li>모델이 수렴할 수 있도록 손실 값을 최소화하는 방향으로 학습을 유도</li>
<li>최적화 과정에서 경사 하강법 등 미분 기반 기법을 사용할 수 있도록 대부분의 손실 함수는 미분 가능하게 설계됨</li>
</ul>
<p>이처럼 손실함수는 학습이 진행되면서 해당 과정이 얼마나 잘 되고 있는지 나타내는 지표로서 모델이 훈련되는 동안 최소화될 값이다. 주어진 문제에 대한 성공 지표가 되는 것이다.</p>
<hr>
<br>
<br>


<h3 id="🖇-keras의-손실-함수">🖇 &nbsp;Keras의 손실 함수</h3>
<p>Keras에서 자주 사용하는 손실 함수 세 가지와 그 특징이다.</p>
<h4 id="1-sparse_categorical_crossentropy">1) <code>sparse_categorical_crossentropy</code></h4>
<ul>
<li>클래스 정답이 정수 레이블(예: 0, 1, 2, ...)로 표현될 때 사용
  e.g., &nbsp;정답 벡터가 <code>[2]</code>인 경우</li>
</ul>
<h4 id="2-categorical_crossentropy">2) <code>categorical_crossentropy</code></h4>
<ul>
<li>클래스 정답이 원-핫 인코딩(예: <code>[0, 0, 1, 0]</code>) 형태로 제공될 때 사용</li>
<li>정답 클래스가 [0, 0, 1, 0]처럼 원-핫 형태로 주어진다면 이 함수를 사용</li>
</ul>
<h4 id="3-binary_crossentropy">3) <code>binary_crossentropy</code></h4>
<ul>
<li>이진 분류 문제에서 사용
  e.g., &nbsp;정답이 0 또는 1인 경우 (<code>[1]</code> 혹은 <code>[0]</code>)</li>
</ul>
<hr>
<br>
<br>

<h3 id="🖇-평균절대오차-mae">🖇 &nbsp;평균절대오차 (MAE)</h3>
<p>MAE(Mean Absolute Error)는 예측값과 실제값의 차이의 절댓값을 평균내는 방식으로 계산된다.</p>
<p>$$$
E = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|
$$$</p>
<h4 id="특징">특징</h4>
<ul>
<li>오차가 커지더라도 손실 함수 값이 일정하게 증가함 (선형)</li>
<li>이상치(outlier)에 비교적 강건(Robust)함</li>
<li>이상치에 해당하는 지점에서 손실 함수 값이 크긴 하지만, 그것이 전체 평균값을 왜곡할 정도로 영향력이 크지 않음</li>
<li>회귀 문제(Regression)에서 자주 사용됨</li>
</ul>
<p>좋은 예측을 했을 때 손실은 작고, 틀린 정도에 비례해서 손실이 선형적으로 커진다. 데이터에 노이즈가 있을 가능성이 높을 때 유용하다.</p>
<p>시각적으로 보면 MAE는 예측값이 정답에서 멀어질수록 <strong>기울기 1의 직선 형태로 손실이 증가</strong>한다. 즉, 예측이 잘못되었을수록 일정하게 페널티를 준다.</p>
<hr>
<br>
<br>

<h3 id="🖇-평균제곱오차-mse">🖇 &nbsp;평균제곱오차 (MSE)</h3>
<p>MSE(Mean Squared Error)는 오차를 제곱한 뒤 평균을 구하는 방식이다.</p>
<p>$$$
E = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
$$$</p>
<h4 id="특징-1">특징</h4>
<ul>
<li>오차가 커질수록 손실 함수 값이 <strong>비선형적으로 급격히 증가</strong> (제곱 함수)</li>
<li>큰 오차에 대해 더 많은 페널티를 부여함</li>
<li>이상치의 영향을 크게 받음</li>
<li>회귀 문제(Regression)에서 자주 사용되고, 특히 이상치가 적다고 판단될 때 선호됨</li>
</ul>
<p>시각적으로 보면 MSE는 예측값이 정답에서 멀어질수록 <strong>곡선 형태로 손실이 급격히 증가</strong>한다. 이는 오차가 작을 때는 비교적 완만하지만 큰 오차에 대해서는 매우 민감하게 반응하도록 한다.</p>
<hr>
<br>
<br>

<h3 id="🖇-mae-vs-mse">🖇 &nbsp;MAE vs MSE</h3>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/23a72917-d82d-4bc7-bf43-e09b66c6f1f0/image.png" alt=""></p>
<blockquote>
<p>공통점</p>
</blockquote>
<ul>
<li>둘 다 <strong>회귀 모델</strong>에서 예측값과 실제값 사이의 오차를 측정하는 데 사용되는 손실 함수</li>
<li>손실 값이 작을수록 예측이 정답과 가까운 것을 의미</li>
</ul>
<blockquote>
<p>차이점</p>
</blockquote>
<table>
<thead>
<tr>
<th>항목</th>
<th>MAE</th>
<th>MSE</th>
</tr>
</thead>
<tbody><tr>
<td>오차 처리 방식</td>
<td>절댓값</td>
<td>제곱</td>
</tr>
<tr>
<td>이상치에 대한 민감도</td>
<td>낮음 (강건함)</td>
<td>높음 (민감함)</td>
</tr>
<tr>
<td>페널티 증가 속도</td>
<td>선형</td>
<td>제곱에 비례 (비선형)</td>
</tr>
</tbody></table>
<p>MAE는 오차가 매우 크더라도(outlier) 제곱항으로 영향을 미치는 것이 아니라 차이의 절대값만큼만 영향을 미치기 때문에 MSE에 비해 상대적으로 이상치에 더 강건한 것이다.</p>
<p>즉, 둘 다 오차를 기준으로 총합을 해 평균을 구한다는 것은 동일하지만 각각의 오차를 절대 값으로 다룰지 혹은 제곱 값으로 처리하는지가 다르다.</p>
<hr>
<br>
<br>

<h3 id="🖇-교차-엔트로피-오차-cee-cross-entropy-error">🖇 &nbsp;교차 엔트로피 오차 (CEE, Cross Entropy Error)</h3>
<p>교차 엔트로피는 분류 문제에서 가장 널리 쓰이는 손실 함수다. 이진 분류(binary classification) 또는 다중 클래스 분류(multi-class classification)에 사용된다.</p>
<p>결과적으로 소프트맥스(softmax) 함수의 출력과 실제 정답(원-핫 인코딩) 사이의 차이를 측정한다.</p>
<p>$$$
E = - \sum_{i=1}^{n} y_i \log(\hat{y}_i)
$$$</p>
<p>정답을 맞추면 손실이 0에 가까워지고 틀릴수록 로그값 특성에 의해 손실이 급격하게 커진다. 이 함수는 예측 확률이 정답일수록 낮은 손실을, 틀릴수록 매우 큰 손실을 부여한다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/1ee03fb2-591b-49cd-9b1d-0e1b1f80745f/image.png" alt=""></p>
<h4 id="이진-분류-문제의-교차-엔트로피-binary-cross-entropy-bce">이진 분류 문제의 교차 엔트로피 (Binary Cross Entropy, BCE)</h4>
<p>$$$
E = - \sum_{i=1}^{2} y_i \log(\hat{y}_i)
$$$</p>
<p>이진 분류에서는 보통 <code>binary_crossentropy</code>를 사용한다. 확률적으로 예측한 값이 실제 정답과 얼마나 가까운지를 평가한다.</p>
<hr>
<br>
<br>

<h3 id="🖇-손실-함수-값-계산">🖇 &nbsp;손실 함수 값 계산</h3>
<p>이번에는 교차 엔트로피 손실 함수가 실제로 어떻게 계산되는지, 예측이 얼마나 정답에 가까웠는지를 수치로 어떻게 표현하는지 계산해 보자.</p>
<h4 id="예시-1-다중-분류-문제에서-softmax-출력이-06-01-03이고-정답이-첫-번째-클래스일-때-즉-y--1-0-0">예시 1) 다중 분류 문제에서 softmax 출력이 [0.6, 0.1, 0.3]이고 정답이 첫 번째 클래스일 때 (즉, y = [1, 0, 0])</h4>
<p>정답 레이블은 원-핫 인코딩 방식으로 표현된다. 정답인 클래스 인덱스에만 1이 있고 나머지는 모두 0인 벡터다.</p>
<p>이때 교차 엔트로피 손실 함수는 아래와 같이 계산된다.</p>
<p>$$
\begin{aligned}
E &amp;= 1 \times -\log(0.6) + 0 \times -\log(0.1) + 0 \times -\log(0.3) \
  &amp;= -\log(0.6) \
  &amp;\approx 0.511
\end{aligned}
$$</p>
<p>예측 확률이 0.6인 첫 번째 클래스를 정답으로 예측했을 때 손실 값은 약 0.511이다. 이 값은 모델이 정답을 비교적 잘 예측했다는 뜻이다. 예측이 더 정확해질수록 softmax 확률이 1에 가까워지고, 이 손실 값은 0에 가까워진다.</p>
<h4 id="예시-2-이진-분류-문제에서-예측값이-08일-때">예시 2) 이진 분류 문제에서 예측값이 0.8일 때</h4>
<p>이진 분류에서는 출력값이 1일 확률로 해석된다. 따라서 상황에 따라 손실 함수의 값이 달라진다.</p>
<p>실제 정답이 1일 경우</p>
<ul>
<li><p>$E = -\log(0.8) \approx 0.223$</p>
</li>
<li><p>예측값이 0.8이면 정답인 1에 가까운 확률을 준 셈이므로, 손실이 작게 나온다. 예측이 맞을수록 손실이 작아진다는 원리를 잘 보여주는 수치다.</p>
</li>
</ul>
<p>실제 정답이 0일 경우</p>
<ul>
<li><p>$E = -\log(1 - 0.8) = -\log(0.2) \approx 1.609$</p>
</li>
<li><p>이 경우는 정답이 0인데 모델은 0.8이라는 높은 확률로 1이라고 예측했다. 틀린 예측을 매우 자신 있게 한 상황이기 때문에 손실 값이 크게 나온다. 이처럼 교차 엔트로피 손실 함수는 틀린 예측을 더 강하게 페널티 주는 구조라는 것을 알 수 있다.</p>
</li>
</ul>
<blockquote>
<p>요약하자면,</p>
<p>교차 엔트로피 손실 함수는 &quot;정답 클래스에 얼마나 높은 확률을 줬는지&quot;를 기준으로 손실을 계산한다. 높은 확률로 정답을 예측하면 손실이 작아지고, 틀린 예측을 자신 있게 할수록 손실이 커진다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>손실 함수는 딥러닝 모델이 학습하는 데 있어 중요하다는 것을 알 수 있었다. 어떤 손실 함수를 선택하느냐에 따라 모델의 학습 방향과 특성이 결정된다. 회귀 문제에서는 MAE와 MSE 중 데이터 특성에 맞는 것을 고르고, 분류 문제에서는 출력 형식(정수인지, 원-핫 인코딩인지)에 따라 적절한 교차 엔트로피 함수를 선택해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Keras 딥러닝 모델]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-Keras-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%AA%A8%EB%8D%B8</link>
            <guid>https://velog.io/@jul-ee/DS-DL-Keras-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%AA%A8%EB%8D%B8</guid>
            <pubDate>Mon, 09 Jun 2025 03:23:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;기본 임포트
🖇 &nbsp;1. Sequential API
🖇 &nbsp;2. Functional API
🖇 &nbsp;3. Subclassing API
🖇 &nbsp;어떤 방식을 언제 선택해야 할까?</p>
</blockquote>
<br>

<p>딥러닝 모델을 구성하는 방법에는 여러 가지가 있다. 그중 Keras를 사용할 때는 대부분 3가지 방법 중 하나를 선택하게 된다.</p>
<ul>
<li>Sequential API</li>
<li>Functional API</li>
<li>Subclassing API</li>
</ul>
<p>각각의 방식은 구조, 표현력, 유연성에서 차이가 있다.</p>
<p>이 글에서는 세 가지 방법의 개념과 구조, 예제를 비교하면서 어떤 상황에서 어떤 방식을 선택하면 좋을지 정리해 보았다. 각각의 방식이 어떤 구조와 개념적 배경을 갖고 있는지, 왜 그렇게 쓰이는지에 대해 이해하는 과정도 함께 담아보았다.</p>
<hr>
<br>
<br>

<h3 id="🖇-기본-임포트">🖇 &nbsp;기본 임포트</h3>
<p>딥러닝 모델을 구현하기 위해서는 TensorFlow와 Keras의 모듈을 임포트해야 한다.</p>
<pre><code class="language-python">from tensorflow.keras import models, layers, utils
import tensorflow as tf</code></pre>
<br>

<blockquote>
<h3 id="🖇-1-sequential-api">🖇 &nbsp;1. Sequential API</h3>
<p>가장 단순하고 직관적인 방식으로, 순차적인 구조에 적합하다.</p>
</blockquote>
<p>Sequential API는 위에서 아래로, <strong>순차적으로 레이어를 쌓아</strong>가는 방식이다. 마치 블록을 위에서부터 아래로 쌓듯이 하나씩 레이어를 추가하는 구조다. 입력에서 출력까지 흐름이 단방향이고 직선적이며 분기나 병합이 없다.</p>
<p>MLP 구조나 단순한 CNN 모델에서 주로 사용된다.</p>
<ul>
<li><p>구현이 간단하고 읽기 쉽지만, 입력이 여러 개이거나 출력이 여러 개인 모델 또는 레이어 간 연결이 복잡한 모델은 표현할 수 없다.</p>
</li>
<li><p>Sequential API는 내부적으로 자동으로 입력 형태를 추론하고 레이어를 쌓는 방식이기 때문에 디버깅이나 복잡한 흐름 제어가 어렵다.</p>
</li>
</ul>
<h4 id="예제-1--add-메서드로-레이어-추가">예제 1: &nbsp; <code>add()</code> 메서드로 레이어 추가</h4>
<pre><code class="language-python">model = models.Sequential()
model.add(layers.Input(shape=(28, 28)))
model.add(layers.Dense(300, activation=&#39;relu&#39;))
model.add(layers.Dense(100, activation=&#39;relu&#39;))
model.add(layers.Dense(10, activation=&#39;softmax&#39;))
model.summary()
utils.plot_model(model, show_shapes=True)</code></pre>
<h4 id="예제-2--리스트-형태로-레이어-정의">예제 2: &nbsp; 리스트 형태로 레이어 정의</h4>
<pre><code class="language-python">model = models.Sequential([
    layers.Input(shape=(28, 28), name=&#39;Input&#39;),
    layers.Dense(300, activation=&#39;relu&#39;, name=&#39;Dense1&#39;),
    layers.Dense(100, activation=&#39;relu&#39;, name=&#39;Dense2&#39;),
    layers.Dense(10, activation=&#39;softmax&#39;, name=&#39;Output&#39;)
])
model.summary()
utils.plot_model(model, show_shapes=True)</code></pre>
<hr>
<br>
<br>

<blockquote>
<h3 id="🖇-2-functional-api">🖇 &nbsp;2. Functional API</h3>
</blockquote>
<p>모듈처럼 조립하는 방식으로, 유연하고 복잡한 모델을 표현할 수 있다.</p>
<p>Functional API는 <strong>각 레이어를 함수처럼 조립하듯 연결</strong>하는 방식이다. Sequential 방식보다 훨씬 유연해서 다음과 같은 구조도 표현할 수 있다.</p>
<ul>
<li>다중 입력/출력</li>
<li>병렬 네트워크</li>
<li>중간 결과 재활용</li>
<li>스킵 연결</li>
</ul>
<p>입출력 텐서를 변수처럼 다루고, 그 사이를 함수 형태로 연결하는 개념이다. 모델 흐름이 눈에 잘 보이고, 재사용과 디버깅도 편해서 많이 사용된다. 또한 모델을 시각화하거나 구조적으로 설명할 때도 명확하다.</p>
<h4 id="예제-1--단일-입력출력-기본-구조">예제 1: &nbsp; 단일 입력/출력 기본 구조</h4>
<pre><code class="language-python">inputs = layers.Input(shape=(28, 28, 1))
x = layers.Flatten()(inputs)
x = layers.Dense(300, activation=&#39;relu&#39;)(x)
x = layers.Dense(100, activation=&#39;relu&#39;)(x)
outputs = layers.Dense(10, activation=&#39;softmax&#39;)(x)

model = models.Model(inputs=inputs, outputs=outputs)
model.summary()
utils.plot_model(model, show_shapes=True)</code></pre>
<h4 id="예제-2--입력과-은닉층-결과를-연결-concatenate">예제 2: &nbsp; 입력과 은닉층 결과를 연결 (Concatenate)</h4>
<pre><code class="language-python">inputs = layers.Input(shape=(28, 28))
hidden1 = layers.Dense(100, activation=&#39;relu&#39;)(inputs)
hidden2 = layers.Dense(30, activation=&#39;relu&#39;)(hidden1)
concat = layers.Concatenate()([inputs, hidden2])
output = layers.Dense(1)(concat)

model = models.Model(inputs=inputs, outputs=output)
model.summary()
utils.plot_model(model, show_shapes=True)</code></pre>
<h4 id="예제-3--다중-입력--다중-출력">예제 3: &nbsp; 다중 입력 + 다중 출력</h4>
<p>입력이 두 개, 출력이 두 방향으로 나뉘는 복잡한 구조를 구현할 수 있다. 예측 문제를 분기하거나 멀티태스크 학습 등에서 유용하게 사용된다.</p>
<pre><code class="language-python">input_1 = layers.Input(shape=(40,), name=&quot;Input_1&quot;)
input_2 = layers.Input(shape=(10,), name=&quot;Input_2&quot;)

x1 = layers.Dense(100, activation=&#39;relu&#39;)(input_1)
x2 = layers.Dense(100, activation=&#39;relu&#39;)(input_2)

concat = layers.Concatenate()([x1, x2])
output_1 = layers.Dense(1, name=&quot;dense_2&quot;)(concat)

x = layers.Dense(40, activation=&#39;relu&#39;, name=&quot;dense_3&quot;)(concat)
output_2 = layers.Dense(10, name=&quot;dense_4&quot;)(x)

model = models.Model(inputs=[input_1, input_2], outputs=[output_1, output_2])
model.summary()
utils.plot_model(model, show_shapes=True)</code></pre>
<hr>
<br>
<br>

<blockquote>
<h3 id="🖇-3-subclassing-api">🖇 &nbsp;3. Subclassing API</h3>
<p>클래스로 모델을 정의하는 방식으로, 완전한 자유도를 제공한다.</p>
</blockquote>
<p>Subclassing 방식은 <code>Model</code> 클래스를 상속받아 모델을 직접 정의하는 방식이다. 모델 구성뿐 아니라 <code>call()</code> 함수에서 레이어 흐름도 직접 제어할 수 있어서 가장 자유롭다.</p>
<ul>
<li>조건문, 반복문 등 파이썬 코드와 결합해 동적 구조를 구현할 수 있다.</li>
<li>강화학습, RNN, 조건부 분기 네트워크 같은 복잡한 구조에서 특히 유용하다.</li>
</ul>
<p>Functional API조차 표현할 수 없는 커스텀 로직을 필요로 할 때 사용하는 방식이다. 다만 자유도가 높은 만큼 코드가 복잡하고 디버깅이 어려울 수 있다.</p>
<h4 id="예제--functional에서-만든-모델을-subclassing으로-구현">예제: &nbsp; Functional에서 만든 모델을 Subclassing으로 구현</h4>
<pre><code class="language-python">class CustomModel(models.Model):
    def __init__(self):
        super().__init__()
        self.dense1 = layers.Dense(100, activation=&#39;relu&#39;)
        self.dense2 = layers.Dense(100, activation=&#39;relu&#39;)
        self.concat = layers.Concatenate()
        self.output1 = layers.Dense(1, name=&quot;dense_2&quot;)
        self.shared = layers.Dense(40, activation=&#39;relu&#39;, name=&quot;dense_3&quot;)
        self.output2 = layers.Dense(10, name=&quot;dense_4&quot;)

    def call(self, inputs):
        input1, input2 = inputs
        x1 = self.dense1(input1)
        x2 = self.dense2(input2)
        merged = self.concat([x1, x2])
        out1 = self.output1(merged)
        x = self.shared(merged)
        out2 = self.output2(x)
        return [out1, out2]

# 입력 테스트
input1 = tf.random.normal([32, 40])
input2 = tf.random.normal([32, 10])
model = CustomModel()
output = model([input1, input2])
model.summary()</code></pre>
<hr>
<br>
<br>

<h3 id="🖇-어떤-방식을-언제-선택해야-할까">🖇 &nbsp;어떤 방식을 언제 선택해야 할까?</h3>
<p>Keras의 세 가지 딥러닝 모델 구현 방식 중 어떤 방식을 어떨 때 사용하면 좋을지 정리해 보았다.</p>
<ul>
<li>구조가 단순하고 빠르게 테스트하고 싶다면 → Sequential</li>
<li>모델 구조가 조금이라도 복잡하거나 시각화가 필요하다면 → Functional</li>
<li>학습 중 동적인 조건 분기나 반복이 필요하다면 → Subclassing</li>
</ul>
<table>
<thead>
<tr>
<th>방식</th>
<th>특징</th>
<th>추천 상황</th>
</tr>
</thead>
<tbody><tr>
<td>Sequential API</td>
<td>가장 단순<br>순차적인 레이어만 구성 가능</td>
<td>기본 MLP, 간단한 CNN, 빠르게 테스트할 때</td>
</tr>
<tr>
<td>Functional API</td>
<td>유연하고 직관적<br>대부분의 모델 구현 가능</td>
<td>대부분의 모델 (CNN, RNN, Autoencoder 등)</td>
</tr>
<tr>
<td>Subclassing API</td>
<td>완전한 사용자 정의<br>제어 흐름까지 직접 작성 가능</td>
<td>조건문/반복문/동적 구조가 필요한 고급 모델</td>
</tr>
</tbody></table>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>딥러닝 모델을 구성할 수 있는 방법은 다양하지만, 가장 많이 사용되는 세 가지 방식과 그 특징을 함께 살펴보았다.</p>
<p>Sequential API는 빠르게 실습할 때 편하고, Functional API는 대부분의 실무 모델을 다룰 수 있으며, Subclassing은 복잡한 사용자 정의 구조를 구현할 때 사용할 수 있겠다는 것을 알 수 있었다.</p>
<p>더 다양한 예제로 세 가지 방법의 차이와 쓰임새를 체득할 수 있도록 비교해 보아야겠다. 구조를 잘 이해하면 어떤 방식으로든 딥러닝 모델을 자유롭게 설계할 수 있을 거라 기대한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 딥러닝 구조와 레이어]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%A0%88%EC%9D%B4%EC%96%B4</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%A0%88%EC%9D%B4%EC%96%B4</guid>
            <pubDate>Mon, 09 Jun 2025 03:01:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 딥러닝 모델의 구조
🖇 &nbsp;2. 레이어란?
🖇 &nbsp;3. 주요 레이어의 종류와 예시
🖇 &nbsp;4. 활성화 함수 종류와 특징
🖇 &nbsp;5. 레이어 예제: Dense Layer와 랜덤 입력</p>
</blockquote>
<br>

<p>딥러닝 모델을 만들기 위해서는 모델의 구성 요소들이 어떤 역할을 하고, 어떻게 연결되는지를 정확히 이해해야 한다.</p>
<p>이 글에서는 Keras에서 딥러닝 모델을 구성할 때 활용하는 주요 API 구조와 레이어(Layer)의 개념, 종류, 특징, 실제 코드 사용법의 순서로 정리해 보았다. </p>
<p>입력 → 레이어 → 출력 → 손실 함수로 이어지는 기본 흐름을 바탕으로 딥러닝 모델이 내부적으로 어떻게 작동하는지 감을 잡는 데 도움이 될 것이라 생각한다.</p>
<br>
<br>

<h3 id="🖇-1-딥러닝-모델의-구조">🖇 &nbsp;1. 딥러닝 모델의 구조</h3>
<p>딥러닝 모델은 여러 구성 요소로 나뉘고, Keras에서는 크게 세 가지 API 계층으로 볼 수 있다.
<img src="https://velog.velcdn.com/images/jul-ee/post/b94e29f5-9872-4363-adba-33d377f2622c/image.png" alt=""></p>
<ul>
<li><p><strong>Core Modules API</strong>: 딥러닝의 기본을 이루는 요소들이 포함된다. 모델을 학습하고 평가할 때 꼭 필요한 기능들이다.</p>
</li>
<li><p><strong>Model API</strong>: 모델 구조를 정의하는 방식이다. Sequential API는 레이어를 순서대로 쌓는 방식이고, Functional API는 더 복잡한 모델을 만들 수 있는 함수형 연결 방식이다. 이와 관련해서는 다음 글에서 다뤄 볼 예정이다.</p>
</li>
<li><p><strong>Layer API</strong>: 실제로 모델을 구성하는 레이어들을 정의한다.문제 유형과 데이터 형태에 따라 적절하게 선택해서 조합한다.</p>
</li>
</ul>
<blockquote>
<p>이처럼 Keras는 세 가지 API를 통해 딥러닝 모델을 구성하는 전체 구조를 레고 블록처럼 조립할 수 있게 해준다. 각각의 구성요소는 독립적이지만 조합해서 더 강력한 모델을 만드는 게 가능하다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-2-레이어란">🖇 &nbsp;2. 레이어란?</h3>
<p>딥러닝 모델은 여러 개의 <strong>레이어(Layer)</strong> 로 구성된다. </p>
<p>레이어는 딥러닝 모델에서 정보를 처리하는 기본 단위라고 생각할 수 있다. 구조는 일반적으로 다음과 같다.</p>
<ul>
<li>입력층 (Input Layer)</li>
<li>은닉층 (Hidden Layer)</li>
<li>출력층 (Output Layer)</li>
</ul>
<pre><code>[Input Layer] → [Hidden Layer 1] → [Hidden Layer 2] → ... → [Output Layer]</code></pre><p>각 레이어는 입력 데이터를 받아서 가중치와 바이어스를 적용하고, 활성화 함수를 통해 출력을 변환한다. 이렇게 변환된 출력은 다음 레이어로 전달되고, 이런 과정이 반복되면서 점점 더 복잡한 함수를 학습하게 된다. 결국 이 구조가 딥러닝 모델의 핵심인 것이다.</p>
<blockquote>
<p>💡 쉽게 말하면, 레이어는 정보를 점점 더 복잡하게 바꿔주는 필터 역할을 한다고 보면 된다. 하나의 레이어는 간단한 연산만 하더라도 여러 개가 쌓이면 복잡한 패턴도 잘 잡아낸다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-3-주요-레이어의-종류와-예시">🖇 &nbsp;3. 주요 레이어의 종류와 예시</h3>
<h4 id="1-input-객체">1) Input 객체</h4>
<p>모델의 입력 형상을 정의할 때 사용한다. shape, dtype, batch_size, name 등을 설정할 수 있다.</p>
<pre><code class="language-python">keras.Input(shape=(28, 28), dtype=tf.float32)
keras.Input(shape=(28, 28), dtype=tf.float32, batch_size=16, name=&#39;input&#39;)</code></pre>
<blockquote>
<p>Input은 말 그대로 모델에 데이터를 넣는 입구 역할이다. 모델 설계 시 가장 먼저 정의해 줘야 한다.</p>
</blockquote>
<br>

<h4 id="2-dense-layer-완전연결층">2) Dense Layer (완전연결층)</h4>
<p>모든 입력 노드와 출력 노드를 연결하는 기본 레이어다. (Fully Connected) 유닛 수를 지정하면 해당 개수만큼의 뉴런이 만들어진다. name, activation 등도 설정 가능하다.</p>
<pre><code class="language-python">layers.Dense(10)
layers.Dense(10, name=&#39;layer1&#39;)
layers.Dense(10, activation=&#39;relu&#39;, name=&#39;dense_layer&#39;)</code></pre>
<pre><code class="language-perl">입력 뉴런:    x₁     x₂     x₃
              \    |    /
               \   |   /
                \  |  /
                 \ | /
              Dense Layer
                 / | \
                /  |  \
               y₁     y₂  ← 출력 뉴런
</code></pre>
<p>이렇게 입력의 모든 노드가 출력의 모든 노드에 연결돼 있어서 모든 조합에 대해 계산이 이루어지는 구조다.</p>
<blockquote>
<p>대부분의 MLP에서 가장 많이 쓰이는 레이어로, 뉴런마다 입력을 모두 받아서 가중치 곱을 수행한 뒤 바이어스를 더한다.</p>
</blockquote>
<br>

<h4 id="3-flatten-layer">3) Flatten Layer</h4>
<p>다차원 입력을 1차원으로 변환해주는 레이어다. 주로 CNN 모델에서 Fully Connected Layer로 연결할 때 사용한다.</p>
<pre><code class="language-python">inputs = keras.Input(shape=(28, 28, 1))
layer = layers.Flatten()(inputs)
print(layer.shape)  # 출력: (None, 784)</code></pre>
<blockquote>
<p>Flatten은 CNN에서 마지막에 Dense와 연결하기 전에 꼭 필요하다. (e.g., <a href="https://velog.io/@jul-ee/DS-DL-MNIST-%EC%88%AB%EC%9E%90-%EB%B6%84%EB%A5%98%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EB%94%A5%EB%9F%AC%EB%8B%9D">MNIST 숫자 분류</a>에서 28x28 이미지를 784 벡터로 펴기)</p>
</blockquote>
<br>

<h4 id="4-activation-layer">4) Activation Layer</h4>
<p>활성화 함수만 따로 쓰고 싶을 때 사용하는 레이어다. Dense 안에 넣을 수도 있지만, 더 명시적으로 표현할 수 있다. 비선형성을 추가해서 복잡한 함수 근사가 가능해진다.</p>
<pre><code class="language-python">layer = layers.Activation(&#39;relu&#39;)</code></pre>
<blockquote>
<p>명시적으로 레이어를 나눔으로써 네트워크 구조가 더 읽기 쉬워지고, 중간에 수정하거나 실험할 때 유리하다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-4-활성화-함수-종류와-특징">🖇 &nbsp;4. 활성화 함수 종류와 특징</h3>
<p>1) Sigmoid 함수</p>
<ul>
<li>출력이 0과 1 사이로 제한됨</li>
<li>확률을 예측하는 이진 분류에서 자주 쓰임</li>
<li>포화 영역에서는 gradient가 거의 0에 가까워져서 학습이 느려질 수 있음 (vanishing gradient 문제)</li>
</ul>
<p>2) tanh (하이퍼볼릭 탄젠트)</p>
<ul>
<li>출력 범위가 -1에서 1 사이</li>
<li>중심이 0이라서 sigmoid보다 학습이 더 빠르게 수렴하는 경우가 많음</li>
<li>여전히 포화 구간 문제는 존재함</li>
</ul>
<p>3) ReLU (Rectified Linear Unit)</p>
<ul>
<li>입력이 0보다 크면 그대로 출력, 작으면 0 출력</li>
<li>계산이 빠르고 성능이 좋아서 가장 널리 사용됨</li>
<li>다만, 음수 입력에 대해서는 gradient가 0이 되면서 뉴런이 죽는 문제가 있음 (dying ReLU)</li>
</ul>
<p>4) Leaky ReLU</p>
<ul>
<li>ReLU의 단점을 보완한 함수</li>
<li>음수 입력에 대해 작은 음수 값으로 출력해서 dying ReLU 문제를 완화함</li>
</ul>
<p>5) ELU (Exponential Linear Unit)</p>
<ul>
<li>음수 입력에서도 gradient가 남도록 설계됨</li>
<li>0 이하에서는 exponential 연산을 사용해서 계산량이 많아지는 단점이 있음</li>
</ul>
<table>
<thead>
<tr>
<th>함수명</th>
<th>출력 범위</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Sigmoid</td>
<td>0 ~ 1</td>
<td>확률 해석 쉬움, gradient vanishing 문제</td>
</tr>
<tr>
<td>Tanh</td>
<td>-1 ~ 1</td>
<td>중심 0, sigmoid보다 학습 빠름</td>
</tr>
<tr>
<td>ReLU</td>
<td>0 ~ ∞</td>
<td>가장 많이 사용, 계산 빠름, dying 문제</td>
</tr>
<tr>
<td>Leaky ReLU</td>
<td>(-∞, ∞)</td>
<td>ReLU 보완, 음수 영역도 gradient 유지</td>
</tr>
<tr>
<td>ELU</td>
<td>(-α, ∞)</td>
<td>중심 0, 빠른 수렴, 계산 비용 높음</td>
</tr>
</tbody></table>
<blockquote>
<p>ReLU와 그 변형 함수들은 대부분의 모델에서 성능이 잘 나오기 때문에 기본값처럼 사용된다. 상황에 따라 적절한 선택이 필요하다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-5-레이어-예제-dense-layer와-랜덤-입력">🖇 &nbsp;5. 레이어 예제: Dense Layer와 랜덤 입력</h3>
<pre><code class="language-python">inputs = tf.random.uniform(shape=(5, 2))
layer = layers.Dense(10, activation=&#39;relu&#39;)
outputs = layer(inputs)
print(layer.weights)
print(layer.bias)
print(outputs)</code></pre>
<p>이 코드는 5×2 크기의 입력 데이터를 무작위로 만든 다음, Dense 레이어를 거쳐서 출력 결과와 가중치, 바이어스를 출력해 보는 예시다. 레이어 내부에서 어떤 연산이 이루어지는지 직접 확인해볼 수 있다.</p>
<blockquote>
<p>출력 결과를 보면 레이어가 입력 데이터를 어떻게 처리하고 변환하는지 눈으로 확인할 수 있어서 직관적인 학습에 도움이 된다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>딥러닝 모델은 다양한 구성 요소와 레이어로 이루어져 있고, 각각의 레이어는 입력 데이터를 가공해서 다음 단계로 전달하는 역할을 한다.</p>
<p>모델 구조는 Sequential 방식으로 간단하게 쌓을 수도 있고, Functional 방식으로 유연하게 만들 수도 있다는 것을 알 수 있었다.</p>
<p>Input 객체로 입력을 정의하고, Dense나 Flatten, Activation 같은 레이어들을 조합해서 모델을 구성하게 된다. 또, 어떤 활성화 함수를 쓰느냐에 따라서 학습 속도와 성능이 달라질 수 있으니까 그 특성을 잘 이해하고 모델에 맞는 걸 선택하는 것이 중요하겠다. </p>
<p>이런 기본 개념들을 잘 익히고 직접 실습해본다면 딥러닝을 좀 더 이해할 수 있을 거라 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Tensor의 개념과 구조]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-Tensor%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jul-ee/DS-DL-Tensor%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Mon, 09 Jun 2025 02:29:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 텐서란 무엇인가
🖇 &nbsp;2. 텐서의 차원별 구조
🖇 &nbsp;3. 텐서 생성
🖇 &nbsp;4. 텐서 변환
🖇 &nbsp;5. 텐서 연산</p>
</blockquote>
<br>

<p>딥러닝을 이해하려면 텐서(Tensor)의 개념부터 정확히 짚고 넘어가야 한다.</p>
<p>텐서는 딥러닝 모델에서 데이터를 표현하고 전달하는 기본 단위로, 스칼라부터 고차원 배열까지 다양한 형태로 존재한다. TensorFlow에서는 텐서를 정의하고 연산하는 방식이 직관적이지만 연산 중 발생할 수 있는 타입 오류나 차원 불일치는 종종 혼란을 야기할 수 있다.</p>
<p>이 글에서는 텐서의 구조와 연산 방식, 자주 사용하는 함수들을 코드와 함께 정리하고, 실제 연산 결과를 통해 동작 원리를 직관적으로 이해하는 데 초점을 맞추었다. 텐서의 기본기가 제대로 잡혀있어야 이후의 모델링과 실험에서 시행착오를 줄일 수 있을 거라 생각한다.</p>
<br>
<br>


<h3 id="🖇-1-텐서란-무엇인가">🖇 &nbsp;1. 텐서란 무엇인가</h3>
<p>텐서(Tensor)는 데이터 구조이면서 딥러닝 모델의 핵심 데이터 단위로 작동한다.</p>
<p>입력 데이터, 가중치, 출력, 손실값 등 모든 요소가 텐서 형태로 표현되며 이들은 GPU에서의 연산에 최적화되어 빠르고 효율적으로 처리된다.</p>
<p>간단히 말하면 텐서는 <strong>수치 데이터를 표현하는 다차원 배열</strong>(multidimensional array) 이다. Python의 리스트나 Numpy 배열과 유사하지만 텐서는 딥러닝 연산을 위한 특화 기능과 속성을 포함하고 있다. 일반적으로 수치형 데이터를 저장하고, 동적 크기를 가진다. 텐서 구조를 제대로 이해하는 것은 딥러닝 모델을 자유자재로 구성하고 디버깅하는 데에 꼭 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/b062bcf8-1c0b-4808-9fb0-f0459ac031ae/image.png" alt=""></p>
<h6 id="출처-모두의연구소-lms-텐서-표현과-연산">[출처] 모두의연구소 LMS <em>텐서 표현과 연산</em></h6>
<h4 id="텐서의-세-가지-핵심-속성">텐서의 세 가지 핵심 속성</h4>
<ul>
<li><p><strong>Rank</strong>: 축(axis)의 개수, 즉 차원 수를 의미한다. 예를 들어 0D는 스칼라, 1D는 벡터, 2D는 행렬, 3D는 시계열 데이터, 4D는 이미지 등이다.</p>
</li>
<li><p><strong>Shape</strong>: 각 축의 크기를 나타내는 튜플이다. 예를 들어 <code>(3, 4)</code>는 3행 4열의 2D 텐서를 뜻한다.</p>
</li>
<li><p><strong>Data Type (dtype)</strong>: 저장된 값의 자료형으로 <code>float32</code>, <code>int32</code>, <code>bool</code>, <code>string</code> 등 다양한 타입이 존재한다.</p>
</li>
</ul>
<p>텐서를 제대로 이해하기 위해서는 다양한 차원의 구조를 직접 눈으로 보고 경험하는 것이 필요하다.</p>
<hr>
<br>
<br>

<h3 id="🖇-2-텐서의-차원별-구조">🖇 &nbsp;2. 텐서의 차원별 구조</h3>
<p>텐서의 차원 수는 곧 데이터의 구조를 의미한다. 데이터가 어떤 형태를 가지고 있는지 이해하려면 각 차원의 의미를 알아야 한다.</p>
<p>아래는 차원별 텐서의 구조와 예시를 코드와 살펴본 것이다.</p>
<pre><code class="language-python"># 0D 텐서 (스칼라)
scalar = tf.constant(7)
print(scalar.shape)  # 출력: ()

# 1D 텐서 (벡터)
vector = tf.constant([1.0, 2.0, 3.0])
print(vector.shape)  # 출력: (3,)

# 2D 텐서 (행렬)
matrix = tf.constant([[1, 2, 3], [4, 5, 6]])
print(matrix.shape)  # 출력: (2, 3)

# 3D 텐서 (시계열 데이터)
time_series = tf.random.normal(shape=(10, 5, 2))  # 10개 샘플, 5개의 타임스텝, 2개 특성
print(time_series.shape)  # 출력: (10, 5, 2)

# 4D 텐서 (이미지 배치)
images = tf.random.normal(shape=(32, 64, 64, 3))  # 32개의 RGB 이미지
print(images.shape)  # 출력: (32, 64, 64, 3)

# 5D 텐서 (비디오)
videos = tf.random.normal(shape=(16, 10, 64, 64, 3))  # 16개 비디오, 각 10프레임, 64x64 RGB
print(videos.shape)  # 출력: (16, 10, 64, 64, 3)
</code></pre>
<p>이처럼 차원이 올라갈수록 더 복잡한 데이터를 표현할 수 있고, 실제 모델에서 자주 사용되는 구조도 함께 익혀두는 것이 좋다. 아래에서 각 차원별 텐서 구조의 특징을 조금 더 자세히 살펴보자.</p>
<br>

<h4 id="0d-tensor-scalar">0D Tensor (Scalar)</h4>
<ul>
<li>축이 없는 텐서이며 단일 값을 담고 있다.</li>
<li>예: <code>tf.constant(1)</code></li>
</ul>
<pre><code class="language-python">t0 = tf.constant(1)
print(t0)              # 출력: 1
print(tf.rank(t0))     # 출력: 0
</code></pre>
<h4 id="1d-tensor-vector">1D Tensor (Vector)</h4>
<ul>
<li>하나의 축을 가지는 텐서로, 값들의 리스트와 유사하다.</li>
<li>예: <code>tf.constant([1, 2, 3])</code></li>
</ul>
<pre><code class="language-python">t1 = tf.constant([1, 2, 3])
print(t1)
print(tf.rank(t1))     # 출력: 1
</code></pre>
<h4 id="2d-tensor-matrix">2D Tensor (Matrix)</h4>
<ul>
<li>두 개의 축을 가지며 행과 열의 구조로 데이터를 저장한다.</li>
<li>통계 데이터, 샘플과 특성 구조에 사용됨</li>
</ul>
<pre><code class="language-python">t2 = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(t2)
print(tf.rank(t2))     # 출력: 2
</code></pre>
<h4 id="3d-tensor">3D Tensor</h4>
<ul>
<li>세 개의 축을 가지며 주로 시계열 또는 순차적 데이터 표현에 사용된다.</li>
<li>축 예시: (samples, timesteps, features)</li>
</ul>
<pre><code class="language-python">t3 = tf.constant([[[1, 1], [2, 2]], [[3, 3], [4, 4]]])
print(t3)
print(tf.rank(t3))     # 출력: 3
</code></pre>
<h4 id="4d-tensor">4D Tensor</h4>
<ul>
<li>네 개의 축을 가지며 주로 컬러 이미지 데이터에서 사용된다.</li>
<li>축 예시: (samples, height, width, channels)</li>
<li>흑백 이미지는 3D 텐서로 표현 가능함</li>
</ul>
<h4 id="5d-tensor">5D Tensor</h4>
<ul>
<li>다섯 개의 축을 가지며 비디오 데이터처럼 시간 축까지 포함된 경우에 사용된다.</li>
<li>축 예시: (samples, frames, height, width, channels)</li>
</ul>
<hr>
<br>
<br>


<h3 id="🖇-3-텐서-생성">🖇 &nbsp;3. 텐서 생성</h3>
<p><code>tf.constant()</code>는 텐서를 생성하는 가장 기본적인 방법으로, 초기값을 고정된 값으로 설정할 때 사용된다. </p>
<p>데이터 타입(<code>dtype</code>)을 명시하면 연산 중 타입 불일치를 방지할 수 있고, 모델의 메모리 효율성도 조절할 수 있다.</p>
<ul>
<li>정수형 텐서는 카운팅 작업에 자주 사용</li>
<li>실수형 텐서는 대부분의 연산 (가중치, 손실 등)에 활용</li>
<li>문자열 텐서는 레이블 인코딩, 토큰 처리 등에 사용</li>
</ul>
<pre><code class="language-python"># 정수형 텐서 (기본: int32)
i = tf.constant(2)
print(i)

# 실수형 텐서 (기본: float32)
f = tf.constant(2.)
print(f)

# 문자열 텐서
s = tf.constant(&quot;Suan&quot;)
print(s)

# dtype 명시 생성
f16 = tf.constant(2., dtype=tf.float16)
print(f16)

i8 = tf.constant(2, dtype=tf.int8)
print(i8)
</code></pre>
<blockquote>
<h4 id="텐서의-타입">텐서의 타입</h4>
<table>
<thead>
<tr>
<th>Data Type</th>
<th>TF Type</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>DT_FLOAT</td>
<td>tf.float32</td>
<td>32비트 부동 소수</td>
</tr>
<tr>
<td>DT_FLOAT64</td>
<td>tf.float64</td>
<td>64비트 부동 소수</td>
</tr>
<tr>
<td>DT_INT8</td>
<td>tf.int8</td>
<td>8비트 정수</td>
</tr>
<tr>
<td>DT_INT32</td>
<td>tf.int32</td>
<td>32비트 정수 (기본값)</td>
</tr>
<tr>
<td>DT_UINT8</td>
<td>tf.uint8</td>
<td>부호 없는 8비트 정수</td>
</tr>
<tr>
<td>DT_STRING</td>
<td>tf.string</td>
<td>문자열 (바이트 배열 형태)</td>
</tr>
<tr>
<td>DT_BOOL</td>
<td>tf.bool</td>
<td>불리언 타입</td>
</tr>
<tr>
<td>DT_COMPLEX64</td>
<td>tf.complex64</td>
<td>복소수 (실수+허수 각 32비트)</td>
</tr>
<tr>
<td>DT_QINT8</td>
<td>tf.qint8</td>
<td>정수형 양자화 연산용</td>
</tr>
<tr>
<td>DT_QUINT8</td>
<td>tf.quint8</td>
<td>부호 없는 양자화 정수</td>
</tr>
</tbody></table>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-4-텐서-변환">🖇 &nbsp;4. 텐서 변환</h3>
<h4 id="타입-변환">타입 변환</h4>
<p><code>tf.cast()</code>는 텐서의 데이터 타입을 변환할 때 사용된다.  </p>
<ul>
<li>예를 들어, <code>int</code>와 <code>float</code> 타입을 더하려고 할 때 오류가 발생할 수 있는데, 이때 타입을 맞춰주는 것이 중요하다.</li>
<li>정확한 연산을 위해선 <code>tf.float32</code>, <code>tf.int32</code> 등 원하는 타입으로 명시적 변환이 필요하다.</li>
</ul>
<pre><code class="language-python">f32 = tf.cast(f16, tf.float32)
print(f32)
</code></pre>
<br>

<h4 id="형상-변환">형상 변환</h4>
<p><code>tf.reshape()</code>은 텐서의 전체 원소 수를 유지하면서 형태만 바꿔준다.  </p>
<ul>
<li>주로 모델 입력에 맞게 데이터를 변환할 때 사용된다.</li>
<li>CNN에서 <code>Flatten</code>하거나, RNN에 넣기 위해 <code>(batch, time, feature)</code> 형태로 바꿀 때 유용하다.</li>
</ul>
<pre><code class="language-python">x = tf.constant([[1], [2], [3]])
print(x.shape)

y = tf.reshape(x, [1, 3])
print(y)
</code></pre>
<br>

<h4 id="전치-transpose">전치 (Transpose)</h4>
<p><code>tf.transpose()</code>는 행과 열 또는 다차원 텐서의 축을 뒤집을 때 사용된다.  </p>
<ul>
<li>주로 행렬 곱셈 전, 이미지 채널 순서 변경, attention 모델 내부에서 key/query 전치 등에서 자주 쓰인다.</li>
</ul>
<pre><code class="language-python">print(tf.transpose(y))
</code></pre>
<br>

<h4 id="차원-압축추가">차원 압축/추가</h4>
<p><code>tf.squeeze()</code>는 차원 중 1인 차원을 제거한다.<br>반대로, <code>tf.expand_dims()</code>는 새로운 축을 추가한다.</p>
<ul>
<li>이 연산들은 모델 입력 형식을 맞출 때 필수적으로 사용된다.</li>
<li>예를 들어 <code>(28, 28)</code> 이미지를 CNN에 넣기 위해 <code>(28, 28, 1)</code>로 확장하는 것이 대표적이다.</li>
</ul>
<pre><code class="language-python">print(tf.squeeze([[1], [2]]))
print(tf.expand_dims([1, 2], axis=0))
print(tf.expand_dims([1, 2], axis=1))
</code></pre>
<br>

<h4 id="텐서-분리-및-연결">텐서 분리 및 연결</h4>
<p><code>tf.split()</code>은 하나의 텐서를 여러 조각으로 나누고,<br><code>tf.concat()</code>은 여러 텐서를 하나로 붙일 수 있다.</p>
<ul>
<li>예를 들어 배치된 데이터를 여러 GPU에 나누거나, 다양한 소스의 데이터를 하나로 합치는 데 활용된다.</li>
</ul>
<pre><code class="language-python">print(tf.split(x, 3))               # 3개로 분리
print(tf.concat([x, x], axis=0))    # 연결 (행 방향)
print(tf.concat([x, x], axis=1))    # 연결 (열 방향)
</code></pre>
<hr>
<br>
<br>

<h3 id="🖇-5-텐서-연산">🖇 &nbsp;5. 텐서 연산</h3>
<p>딥러닝 모델은 텐서를 중심으로 구성되기 때문에 텐서 연산은 데이터를 변환하고 모델을 구성하는 데 중요하게 작용한다.  </p>
<h4 id="산술-연산-0d">산술 연산 (0D)</h4>
<p>0차원(스칼라) 텐서 간 연산은 Python의 기본 수치 연산과 매우 유사하게 동작한다. 연산 결과 역시 0D 스칼라로 반환되고, 텐서플로우 내부에서는 모두 <code>tf.Tensor</code> 객체로 처리된다.</p>
<pre><code class="language-python">print(tf.constant(2) + tf.constant(2))
print(tf.constant(2) - tf.constant(2))
print(tf.multiply(2, 3))
print(tf.divide(4, 2))
</code></pre>
<h4 id="텐서-타입-불일치와-캐스팅">텐서 타입 불일치와 캐스팅</h4>
<p>TensorFlow는 타입이 다른 텐서 간 연산을 자동으로 허용하지 않기 때문에 연산 전 <code>tf.cast()</code>로 명시적 형 변환이 필요히다.   모델 구현 중에서도 <code>정수형 인덱스</code>와 <code>실수형 확률값</code>을 혼용할 경우 이런 오류가 자주 발생한다.</p>
<pre><code class="language-python"># 오류: int + float
# tf.constant(2) + tf.constant(2.2)

# 해결: 타입 캐스팅
print(tf.cast(tf.constant(2), tf.float32) + tf.constant(2.2))
</code></pre>
<h4 id="1d-2d-텐서-연산">1D, 2D 텐서 연산</h4>
<p>연산 대상이 2D 텐서인 경우, 같은 위치에 있는 원소끼리 연산이 수행된다. 단, 두 텐서의 shape이 다르더라도 연산이 가능한 경우가 있는데 이를 <strong>브로드캐스팅(broadcasting)</strong> 이라고 한다.</p>
<ul>
<li>예를 들어 <code>(2, 3)</code> 텐서와 <code>(1, 3)</code> 텐서를 더하면 <code>(2, 3)</code> 형태로 자동 확장되어 연산된다.</li>
</ul>
<pre><code class="language-python"># 2D 텐서
a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[5, 6], [7, 8]])

print(a + b)
print(a - b)
print(a * b)
print(a @ b)  # 행렬곱
print(a / b)
</code></pre>
<br>

<h4 id="함수형-연산-방식">함수형 연산 방식</h4>
<p>Python 연산자(<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>@</code>)를 직접 사용해도 되지만,   동일한 기능을 수행하는 TensorFlow의 함수형 API(<code>tf.add()</code>, <code>tf.matmul()</code> 등)를 사용하는 것이 가독성과 확장성 측면에서 유리할 때가 많다.</p>
<pre><code class="language-python">print(tf.add(a, b))
print(tf.subtract(a, b))
print(tf.multiply(a, b))
print(tf.matmul(a, b))
print(tf.divide(a, b))
</code></pre>
<h4 id="유용한-함수들">유용한 함수들</h4>
<pre><code class="language-python">x = tf.constant([[4.0, 5.0, 6.0], [10.0, 9.0, 8.0]])

print(tf.reduce_max(x))     # 최대값
print(tf.argmax(x))         # 최대값의 위치
print(tf.nn.softmax(x))     # 소프트맥스 결과
</code></pre>
<br>

<blockquote>
<h4 id="텐서-함수-정리">텐서 함수 정리</h4>
<p>복잡한 모델을 구현할 때 텐서의 모양을 바꾸거나 데이터를 나누고 붙이는 작업이 빈번하게 발생한다. 이때 유용하게 사용되는 주요 함수들이다.</p>
<table>
<thead>
<tr>
<th>함수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>tf.shape</td>
<td>텐서 구조 확인</td>
</tr>
<tr>
<td>tf.size</td>
<td>전체 원소 수 확인</td>
</tr>
<tr>
<td>tf.rank</td>
<td>차원 수 확인</td>
</tr>
<tr>
<td>tf.reshape</td>
<td>형상 재구성</td>
</tr>
<tr>
<td>tf.squeeze</td>
<td>1인 차원 제거</td>
</tr>
<tr>
<td>tf.expand_dims</td>
<td>축 추가</td>
</tr>
<tr>
<td>tf.slice</td>
<td>슬라이싱</td>
</tr>
<tr>
<td>tf.split</td>
<td>분할</td>
</tr>
<tr>
<td>tf.concat</td>
<td>연결</td>
</tr>
<tr>
<td>tf.tile</td>
<td>반복 생성</td>
</tr>
<tr>
<td>tf.reverse</td>
<td>역순 배열</td>
</tr>
<tr>
<td>tf.transpose</td>
<td>전치</td>
</tr>
<tr>
<td>tf.gather</td>
<td>인덱싱 수집</td>
</tr>
</tbody></table>
</blockquote>
<blockquote>
<h4 id="텐서-연산-정리">텐서 연산 정리</h4>
<p>앞서 실습한 텐서 연산들을 기호별로 정리하였다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>연산 기호</th>
<th>함수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>+</code></td>
<td>add()</td>
<td>더하기</td>
</tr>
<tr>
<td><code>-</code></td>
<td>subtract()</td>
<td>빼기</td>
</tr>
<tr>
<td><code>*</code></td>
<td>multiply()</td>
<td>곱하기</td>
</tr>
<tr>
<td><code>/</code></td>
<td>divide()</td>
<td>나누기</td>
</tr>
<tr>
<td><code>@</code></td>
<td>matmul()</td>
<td>행렬곱</td>
</tr>
<tr>
<td></td>
<td>reduce_max()</td>
<td>최대값</td>
</tr>
<tr>
<td></td>
<td>argmax()</td>
<td>최대값 위치</td>
</tr>
</tbody></table>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>TensorFlow의 텐서 구조와 연산을 실습하면서 내부 작동 원리를 직관적으로 이해할 수 있었다. 막연하게 딥러닝에서 사용되는 라이브러리라고 이해하고 있었는데 제대로 짚고 넘어갈 수 있었다.</p>
<p>0D부터 2D까지 다양한 차원의 텐서를 다뤄보며 shape와 rank의 개념이 확실히 정리되었고, 타입 미스매치나 차원 불일치로 인한 오류 발생 시 해결 방향도 생각해 볼 수 있었다.</p>
<p>연산 방식의 차이(연산자 vs 함수형 API)를 비교하면서 코드 가독성과 안정성을 어떻게 고려할 수 있을지도 중요한 포인트라고 생각한다. 실제 모델링 전에 텐서 조작 흐름을 전체적으로 정리할 수 있는 유익한 시간이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 딥러닝의 구조와 발전]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%B0%9C%EC%A0%84</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%B0%9C%EC%A0%84</guid>
            <pubDate>Mon, 09 Jun 2025 02:18:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 인공신경망과 딥러닝
🖇 &nbsp;2. 전통적 신경망 vs 딥러닝 신경망
🖇 &nbsp;3. 기울기 소멸 문제
🖇 &nbsp;4. 과적합과 일반화 문제
🖇 &nbsp;5. 딥러닝 최신 트렌드</p>
</blockquote>
<br>

<p>앞선 글에서 살펴봤듯이 딥러닝은 인공지능 분야에서 가장 빠르게 발전하고 있는 기술 중 하나로, 다양한 분야에 적용되어 탁월한 성능을 보여주고 있다. 특히 컴퓨터 비전, 자연어 처리, 음성 인식, 자율주행 등에서 혁신적인 성과를 만들어내며 주목받고 있다.</p>
<p>이러한 딥러닝의 중심에는 인공신경망(Artificial Neural Network)이라는 핵심 개념이 존재한다. 이 글에서는 인공신경망의 구조와 원리를 시작으로, 딥러닝 기술이 어떻게 발전해왔고 어떤 방식으로 작동하는지 정리해 보았다.</p>
<br>
<br>

<h3 id="🖇-1-인공신경망과-딥러닝">🖇 &nbsp;1. 인공신경망과 딥러닝</h3>
<p>인공신경망(Artificial Neural Network)은 인간의 뇌를 수학적으로 모델링한 구조다. 입력 데이터를 받아 일정한 연산 과정을 통해 출력값을 내는 계산 네트워크로, 딥러닝은 이러한 인공신경망을 다층으로 쌓아 복잡한 문제를 해결하는 방식이다.</p>
<p>일반적으로 인공신경망은 1~2개의 은닉층(hidden layer)을 가진 얕은 구조였다. 반면 딥러닝은 수십 개 이상의 은닉층을 포함하며, 이로 인해 <strong>심층신경망(Deep Neural Network)</strong>이라 불린다. 많은 층을 통해 입력 데이터의 다양한 특성(feature)을 점차 추상화하며 이미지 분류나 음성 인식처럼 복잡한 문제를 해결하는 데 유용하게 작용하고 있다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/9b9a9b3f-ae95-490d-9b41-cc5bd0630d3f/image.png" alt=""></p>
<h4 id="계층별-정보-처리-방식">계층별 정보 처리 방식</h4>
<ul>
<li>입력층(input layer): 원본 데이터를 받는다</li>
<li>은닉층(hidden layers): 데이터의 패턴을 점점 고차원적으로 표현</li>
<li>출력층(output layer): 최종 예측값이나 결과를 출력</li>
</ul>
<blockquote>
<p>💡 예를 들어,</p>
<p>얼굴 인식 문제에서는 처음 은닉층이 픽셀의 밝기 등을 구분하고, 다음 층은 테두리(edge), 윤곽선, 눈·코·입과 같은 구조적 특징을 인식하여, 마지막 출력층에서는 해당 인물이라는 최종 클래스를 예측하는 방식이다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-2-전통적-신경망-vs-딥러닝-신경망">🖇 &nbsp;2. 전통적 신경망 vs 딥러닝 신경망</h3>
<p>기존의 신경망은 데이터를 입력받기 전에 사람이 직접 특징을 추출하여 사용했다. 이미지 데이터라면 색상 히스토그램이나 가장자리 위치 등을 사람이 정의한 feature vector로 변환한 뒤 신경망의 입력으로 사용했다.</p>
<p>이런 방식은 특징 추출 품질이 전체 성능에 큰 영향을 준다는 단점이 있었고, 좋은 성능을 내기 위해 도메인 지식이 필요했다.</p>
<p>반면 딥러닝 신경망은 <strong>특징 추출과 학습을 동시에</strong> 수행한다. 데이터로부터 의미 있는 표현을 자동으로 학습하고, 그 위에 모델을 쌓아 최종적인 예측을 한다. 이로 인해 성능과 범용성 측면에서 기존 방식보다 뛰어난 결과를 보여준다.</p>
<h4 id="특징-추출을-포함한-딥러닝의-장점">특징 추출을 포함한 딥러닝의 장점</h4>
<ul>
<li>자동화: 사람이 개입하지 않아도 학습 데이터에서 직접 특징을 학습함</li>
<li>범용성: 도메인에 관계없이 적용 가능하며 특징 설계 부담을 줄임</li>
<li>성능 향상: 고차원적이고 추상적인 특징을 학습할 수 있어 예측 정확도가 높아짐</li>
</ul>
<p>이처럼 딥러닝은 높은 성능과 뛰어난 확장성으로 인해 이미지 처리, 자연어 처리, 음성 인식 등 다양한 분야에서 널리 활용되고 있다.</p>
<p>하지만 그만큼 <strong>많은 파라미터(parameter)</strong>를 학습해야 하기 때문에, 다음과 같은 부담이 존재한다.</p>
<h4 id="딥러닝-신경망의-문제점">딥러닝 신경망의 문제점</h4>
<ul>
<li>많은 데이터 요구: 자동 특징 학습에는 대량의 라벨된 데이터가 필요함</li>
<li>학습 비용: 연산량과 메모리 사용이 커서 고성능 하드웨어가 필요함</li>
<li>해석 불가능성: 결과는 뛰어나지만, 내부 결정 과정을 이해하기 어려움</li>
</ul>
<p>이러한 단점들을 극복하기 위한 연구가 활발히 이루어지고 있으며, 설명 가능한 인공지능(XAI) 등의 분야가 이에 해당한다.</p>
<blockquote>
<p>💡 딥러닝의 발전과 주요 인물</p>
</blockquote>
<ul>
<li>제프리 힌튼(Geoffrey Hinton): 역전파 알고리즘, 볼츠만 머신, 딥러닝 혁신 주도</li>
<li>조슈아 벤지오(Yoshua Bengio): 시퀀스 모델, 어텐션 메커니즘 등 자연어 처리 발전 기여</li>
<li>얀 르쿤(Yann LeCun): 컨볼루션 신경망(CNN)과 시각 인식 시스템 발전<blockquote>
</blockquote>
이들은 2018년 튜링상을 공동 수상하며 딥러닝의 대중화와 이론적 기초를 동시에 확립했다.</li>
</ul>
<hr>
<br>
<br>


<h3 id="🖇-3-기울기-소멸-문제">🖇 &nbsp;3. 기울기 소멸 문제</h3>
<p>딥러닝에서는 역전파(backpropagation)를 통해 오차를 출력층에서 입력층 방향으로 전달하며 가중치를 업데이트한다. 하지만 층이 깊어질수록 <strong>기울기(gradient)가 작아져 거의 0에 가까워지는 현상</strong>이 발생할 수 있다. 이를 기울기 소멸(Vanishing Gradient) 문제라고 한다.</p>
<p>기울기 소멸은 주로 시그모이드(<code>sigmoid</code>), 쌍곡탄젠트(<code>tanh</code>)와 같은 비선형 함수에서 나타난다. 이로 인해 초깃값과 상관없이 가중치가 거의 업데이트되지 않는 문제가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/8587b47f-73e2-4d54-9f4e-b51f77defee0/image.png" alt=""></p>
<p>이 문제는 딥러닝 초기 학습이 어려운 원인 중 하나였고, 이후 ReLU 같은 새로운 활성화 함수와 배치 정규화(batch normalization) 기법이 대안으로 제시되었다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/9e861991-0a45-48f4-9775-9a590becd1b3/image.png" alt=""></p>
<hr>
<br>
<br>

<h3 id="🖇-4-과적합과-일반화-문제">🖇 &nbsp;4. 과적합과 일반화 문제</h3>
<p>딥러닝 모델은 복잡도가 크기 때문에 과적합(overfitting)이 자주 발생한다. 과적합이란 학습 데이터에는 잘 맞지만 보지 못한 새로운 데이터에 대해서는 성능이 떨어지는 상태를 말한다.</p>
<ul>
<li>과적합(overfitting): 학습 데이터에 지나치게 특화됨</li>
<li>과소적합(underfitting): 모델이 너무 단순하거나 학습이 부족해 데이터를 제대로 설명하지 못함</li>
</ul>
<h4 id="과적합을-방지하는-방법">과적합을 방지하는 방법</h4>
<ul>
<li><p>규제화(Regularization)
: 큰 가중치 값에 큰 규제를 가하여 과적합되지 않도록 모델을 제한
: 규제의 강도를 정하는 적절한 가중치가 중요</p>
</li>
<li><p>드롭아웃(Dropout)
: 학습 시 임의로 일부 노드의 출력을 제거해 일반화 능력을 높임</p>
</li>
<li><p>배치 정규화(Batch Normalization)
: 모델에 입력되는 샘플들을 균일하게 만드는 방법
: 미니 배치 단위로 평균이 0, 표준편차가 1이 되도록 정규화</p>
</li>
</ul>
<p>이러한 기법들은 딥러닝 모델의 일반화 성능을 높이는 데 중요한 역할을 한다.</p>
<hr>
<br>
<br>

<h3 id="🖇-5-딥러닝-최신-트렌드">🖇 &nbsp;5. 딥러닝 최신 트렌드</h3>
<p>딥러닝은 최근 다양한 학습 패러다임을 받아들이며 더욱 확장되고 있다.</p>
<ul>
<li><p>전이 학습(Transfer Learning)
: 대규모 데이터로 미리 학습한 모델을 다른 문제에 응용</p>
</li>
<li><p>자기지도학습(Self-supervised Learning)
: 라벨 없이 스스로 학습하도록 설계</p>
</li>
<li><p>메타 학습(Meta Learning)
: 학습하는 방법을 학습함으로써 적은 데이터로도 빠르게 학습</p>
</li>
<li><p>설명 가능한 인공지능(XAI)
: 신경망 내부 작동을 이해 가능하도록 설명 모델 개발</p>
</li>
<li><p>NAS(Neural Architecture Search)
: 최적의 신경망 구조를 자동으로 탐색</p>
</li>
<li><p>AutoML
: 강화학습 기반으로 최적 모델 구조를 생성</p>
</li>
</ul>
<p>이러한 기법들은 복잡한 문제를 더 빠르고 효과적으로 해결하기 위한 노력의 일환이다.</p>
<blockquote>
<p>주요 딥러닝 프레임워크</p>
</blockquote>
<ul>
<li><strong>TensorFlow</strong>: 구글이 주도하여 개발한 프레임워크로, 다양한 언어와 플랫폼 지원</li>
<li><strong>Keras</strong>: 사용자 친화적인 고수준 API로 빠른 프로토타이핑 가능</li>
<li><strong>PyTorch</strong>: 유연하고 직관적인 코드 구조로 연구 개발자들이 선호<blockquote>
</blockquote>
이러한 프레임워크 덕분에 모델 구축과 실험이 쉬워졌고, 산업과 학계의 접점이 넓어졌다.</li>
</ul>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>딥러닝은 단순한 인공신경망에서 출발해 오늘날의 고도화된 지능형 시스템으로 발전했다는 점이 흥미롭게 다가왔다. 다양한 구조, 학습 알고리즘, 프레임워크의 등장으로 앞으로도 더 많은 응용 가능성이 기대된다.</p>
<p>지금은 데이터의 양과 질, 모델의 구조, 학습 방식의 최적화가 모두 중요하게 작용하는 시대라고 생각한다. 이러한 딥러닝 기술을 정확히 이해하고 발전 방향을 읽는 것은 앞으로의 인공지능 분야에서 중요한 기반으로 작용할 수 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 퍼셉트론의 한계와 MPL]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%ED%8D%BC%EC%85%89%ED%8A%B8%EB%A1%A0%EC%9D%98-%ED%95%9C%EA%B3%84%EC%99%80-MPL</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%ED%8D%BC%EC%85%89%ED%8A%B8%EB%A1%A0%EC%9D%98-%ED%95%9C%EA%B3%84%EC%99%80-MPL</guid>
            <pubDate>Mon, 09 Jun 2025 01:57:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 퍼셉트론(Perceptron)이란?
🖇 &nbsp;2. 퍼셉트론: AND 연산
🖇 &nbsp;3. 퍼셉트론: XOR 문제
🖇 &nbsp;4. 퍼셉트론의 한계
🖇 &nbsp;5. 다층 퍼셉트론(MLP)의 등장
🖇 &nbsp;6. MPL 학습 한계
🖇 &nbsp;7. MPL 학습 한계 해결</p>
</blockquote>
<br>

<p>퍼셉트론은 입력과 가중치의 선형 결합을 바탕으로 이진 분류를 수행하는 가장 기본적인 인공 신경망 구조이다.</p>
<p>AND, OR처럼 선형적으로 구분 가능한 문제는 퍼셉트론 하나로 해결할 수 있지만, XOR처럼 비선형적인 구조를 가진 문제는 해결하지 못하는 한계가 존재한다. 이러한 한계를 극복하기 위해 은닉층을 추가한 구조인 MLP(Multi-Layer Perceptron)가 등장하게 된다.</p>
<p>이 글에서는 퍼셉트론의 동작 원리를 짚어보고, XOR 문제를 통해 구조적 한계를 이해한 뒤 MLP로의 확장 과정을 정리해 보았다.</p>
<br>
<br>
<br>

<h3 id="🖇-1-퍼셉트론perceptron이란">🖇 &nbsp;1. 퍼셉트론(Perceptron)이란?</h3>
<p>퍼셉트론은 1957년 프랭크 로젠블랫(Frank Rosenblatt)이 제안한 인공 뉴런의 초기 모델이다.</p>
<p>인간의 신경세포(뉴런)가 일정한 자극을 받았을 때만 신호를 전달하듯, 퍼셉트론도 입력값을 받아 <strong>조건에 따라 출력을 할지 말지를 결정</strong>한다.</p>
<p>이때 출력을 할지 말지를 정하는 기준이 되는 것이 <strong>활성화 함수</strong>다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/26c67afa-bae2-4f0a-b6d7-3a6435131f2b/image.png" alt=""></p>
<p>퍼셉트론의 계산은 다음과 같은 흐름을 따른다.</p>
<p>$$
z = \sum_{i=1}^{n} w_i x_i + b \quad \Rightarrow \quad
\text{output} =
\begin{cases}
1 &amp; \text{if } z &gt; 0 \
0 &amp; \text{otherwise}
\end{cases}
$$</p>
<ul>
<li>$x_i$: &nbsp;입력값</li>
<li>$w_i$: &nbsp;가중치</li>
<li>$b$: &nbsp;바이어스</li>
<li>$z$: &nbsp;입력과 가중치의 선형 조합</li>
<li>$output$: &nbsp;활성화 함수의 결과값 (0 또는 1)</li>
</ul>
<p>이 구조는 신경세포의 ‘발화 여부 결정’, 즉 세포체(soma)에서 <strong>받아들인 자극이 일정 기준을 넘으면 반응하고, 넘지 않으면 반응하지 않는</strong> 생물학적 원리를 모방한 것이다.</p>
<br>

<h4 id="퍼셉트론-vs-생체신경망">퍼셉트론 vs 생체신경망</h4>
<p>인공신경망은 실제 생물학적 뉴런의 작동 원리를 수학적으로 단순화한 구조이다. 퍼셉트론은 이러한 생물학적 뉴런을 수식으로 표현한 가장 기초적인 모델인데, 두 구조는 구성 요소와 정보 처리 방식에서 많은 유사점을 보인다.</p>
<p>아래 표는 퍼셉트론의 연산 구조와 생체신경망의 생리적 작동 방식을 비교한 것이다. 이를 통해 인공신경망이 뇌의 뉴런을 어떤 식으로 모방했는지를 이해하는 데 도움이 될 수 있다.</p>
<blockquote>
<p>퍼셉트론은 입력값과 가중치를 곱해 모두 더하고, 이 결과에 바이어스를 더한 뒤, 활성화 함수를 통해 출력 신호를 만들어낸다. 이 흐름은 뇌에서의 자극 수용 → 연산 → 발화 → 전달의 구조와 흡사하다.</p>
</blockquote>
<table>
<thead>
<tr>
<th><strong>분류</strong></th>
<th><strong>퍼셉트론</strong></th>
<th><strong>생체신경망</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>입력</strong></td>
<td>입력 벡터 x = (x₁, x₂, ..., xₙ)</td>
<td>이전 뉴런이 발화한 신호</td>
</tr>
<tr>
<td><strong>가중치</strong></td>
<td>가중치 벡터 w = (w₁, w₂, ..., wₙ)</td>
<td>시냅스의 연결 강도</td>
</tr>
<tr>
<td><strong>입력과 가중치 곱</strong></td>
<td>각 입력과 가중치의 곱: wᵢ × xᵢ (i = 1, 2, ..., n)</td>
<td>시냅스 강도에 따라 신호가 강화되거나 약해지는 과정</td>
</tr>
<tr>
<td><strong>가중 합산</strong></td>
<td>선형 결합: z = ∑ (wᵢ × xᵢ) + b</td>
<td>세포체에서 수상돌기를 통해 들어온 신호를 종합하는 과정</td>
</tr>
<tr>
<td><strong>활성 함수</strong></td>
<td>f(z) = 1 if z ≥ 0, else 0</td>
<td>세포체의 신호 발화 여부 결정</td>
</tr>
<tr>
<td><strong>출력</strong></td>
<td>f(x) = f(wᵗx + b)</td>
<td>축삭을 따라 시냅스로 신호가 전달되는 과정</td>
</tr>
</tbody></table>
<p>이처럼 퍼셉트론은 생체신경망의 작동 원리를 기반으로 한 수학적 모델로, 신호의 입력부터 출력까지 전반적인 처리 과정을 단순하지만 효과적으로 모사한다. 이 개념은 이후에 나올 될 다층 신경망, 역전파, 딥러닝의 기반이 된다.</p>
<hr>
<br>
<br>

<h3 id="🖇-2-퍼셉트론-and-연산">🖇 &nbsp;2. 퍼셉트론: AND 연산</h3>
<p>퍼셉트론은 간단한 논리 연산을 매우 잘 수행한다.
퍼셉트론으로 AND 연산을 수행하는 예시를 하나 살펴보자.</p>
<ul>
<li>입력: $x_1$ = 1, $x_2$ = 1</li>
<li>가중치: $w_1$ = 0.6, $w_2$ = 0.6</li>
<li>바이어스: $b$ = -1</li>
</ul>
<p>$z = (0.6 \times 1) + (0.6 \times 1) + (-1) = 0.2 \Rightarrow \text{output} = 1$</p>
<br>

<ul>
<li>반면 $x_1 = 0$, $x_2 = 1$이면</li>
</ul>
<p>$z = (0.6 \times 0) + (0.6 \times 1) + (-1) = -0.4 \Rightarrow \text{output} = 0$</p>
<p>이처럼 퍼셉트론은 직선 하나를 기준으로 데이터를 양분하는 데 매우 적합하다.</p>
<hr>
<br>
<br>

<h3 id="🖇-3-퍼셉트론-xor-문제">🖇 &nbsp;3. 퍼셉트론: XOR 문제</h3>
<p>그러나 퍼셉트론은 <strong>선형 분리가 불가능한 문제</strong>에서는 제대로 작동하지 않는다.
그 대표적인 예가 바로 XOR 문제다.</p>
<table>
<thead>
<tr>
<th>x1x_1x1</th>
<th>x2x_2x2</th>
<th>출력 (XOR)</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody></table>
<p>XOR 진리표의 네 개의 좌표를 2차원 평면에 표시하면 다음과 같은 분포가 된다.</p>
<ul>
<li>(0,0)과 (1,1)은 클래스 0</li>
<li>(0,1)과 (1,0)은 클래스 1</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/7b49a1a3-ef85-44b2-b1f6-51ad2cd1721e/image.png" alt=""></p>
<p>이들은 직선 하나로는 나눌 수 없다. 즉, 퍼셉트론의 구조로는 XOR 문제를 해결할 수 없는 것이다.</p>
<blockquote>
<p>💡 단일 퍼셉트론은 하나의 직선(결정 경계)만 학습할 수 있기 때문에 비선형 문제나 곡선 경계를 요구하는 문제는 풀 수 없다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-4-퍼셉트론의-한계">🖇 &nbsp;4. 퍼셉트론의 한계</h3>
<p>1969년, 마빈 민스키(Marvin Minsky)와 시모어 페퍼트(Seymour Papert)는 저서 &nbsp; <em>『Perceptrons』</em> 에서 퍼셉트론이 XOR 문제를 해결하지 못한다는 사실을 수학적으로 증명했다.</p>
<p>이로 인해 인공지능에 대한 과도한 기대가 꺾였고, 정부와 기업의 투자도 끊기면서 AI 연구는 1970~80년대 동안 정체 상태에 빠지게 되었고, 이를 AI 겨울(AI Winter)이라고 부른다.</p>
<p>그러나 이 시기의 좌절은 오히려 더 강력한 신경망 구조의 필요성을 자각하게 해준 중요한 계기로 작용했다.</p>
<hr>
<br>
<br>

<h3 id="🖇-5-다층-퍼셉트론mlp의-등장">🖇 &nbsp;5. 다층 퍼셉트론(MLP)의 등장</h3>
<p>XOR 문제는 입력 공간을 비선형적으로 변형해야 해결할 수 있다.</p>
<p>이러한 변형을 가능하게 만드는 구조로 다층 퍼셉트론(MLP, Multi-Layer Perceptron)이 등장하였다.</p>
<h4 id="mlp의-구조">MLP의 구조</h4>
<ul>
<li>입력층: 원본 데이터를 입력받음</li>
<li>은닉층: 입력을 조합하여 중간 표현을 만들어냄 (1개 이상 존재)</li>
<li>출력층: 최종 예측값을 출력</li>
</ul>
<p>MLP는 여러 뉴런과 여러 층을 활용해 단일 퍼셉트론이 학습할 수 없는 복잡한 관계나 곡선 경계까지도 학습할 수 있게 해준다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/d5629dcd-e4ef-4eb1-936e-ceae059b11e9/image.png" alt=""></p>
<ol>
<li><strong>첫 번째 은닉 뉴런</strong>이 (0,1), (1,0)을 감지한다.</li>
<li><strong>두 번째 은닉 뉴런</strong>이 (0,0), (1,1)을 감지한다.</li>
<li><strong>출력 뉴런</strong>은 위 둘을 조합하여 XOR 출력값을 만든다.</li>
</ol>
<p>결과적으로, XOR 문제도 MLP에서는 해결 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/a70158b2-0e07-4c60-b3dc-8986e6997a0b/image.png" alt=""></p>
<br>

<h4 id="다층-퍼셉트론과-딥러닝의-경계">다층 퍼셉트론과 딥러닝의 경계</h4>
<p>참고로 MLP은 은닉층이 1<del>2개 정도인 얕은 구조를 말할 때가 많고, 딥러닝(Deep Learning)은 은닉층이 수십</del>수백 개 이상으로 구성된 깊은 구조(Deep Neural Network, DNN)를 의미한다.</p>
<p>또한 딥러닝에는 CNN, RNN, Transformer 등 다양한 구조도 포함된다.</p>
<p>하지만 이 모든 복잡한 모델도 결국 <strong>퍼셉트론 구조를 층층이 확장한 것</strong>이라는 점에서 MLP은 딥러닝의 뿌리라고 할 수 있다.</p>
<hr>
<br>
<br>

<h3 id="🖇-6-mpl-학습-한계">🖇 &nbsp;6. MPL 학습 한계</h3>
<p>구조를 확장한다고 해서 모든 문제가 해결되지는 않는다.
MLP은 구조적으로는 유연하지만 학습 자체는 매우 어렵다. 크게 두 가지의 이유로 설명할 수 있다.</p>
<h4 id="1-계단-함수-사용의-한계">1) 계단 함수 사용의 한계</h4>
<ul>
<li>퍼셉트론은 보통 계단 함수(step function)를 활성화 함수로 사용한다.</li>
<li>계단 함수는 불연속적이기 때문에 <strong>기울기(gradient)</strong>를 계산할 수 없고, 따라서 가중치를 수정하는 학습이 불가능하다.</li>
</ul>
<h4 id="2-은닉층의-오차-측정-불가능">2) 은닉층의 오차 측정 불가능</h4>
<ul>
<li>출력층은 정답과 비교해 오차를 알 수 있지만</li>
<li>은닉층은 &quot;이 뉴런이 얼마나 잘못된 결과에 기여했는지&quot;를 측정하기 어렵다.
→ 학습이 불가능한 구조가 되어버린다.</li>
</ul>
<hr>
<br>
<br>

<h3 id="🖇-7-mpl-학습-한계-해결">🖇 &nbsp;7. MPL 학습 한계 해결</h3>
<h4 id="1-미분-가능한-활성화-함수-도입">(1) 미분 가능한 활성화 함수 도입</h4>
<p>퍼셉트론의 계단 함수 대신 Sigmoid, Tanh, ReLU 같은 함수가 도입되었다.</p>
<table>
<thead>
<tr>
<th>함수</th>
<th>출력 범위</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Sigmoid</td>
<td>0 ~ 1</td>
<td>미분 가능, 초창기 사용</td>
</tr>
<tr>
<td>Tanh</td>
<td>-1 ~ 1</td>
<td>중심 대칭, 수렴 빠름</td>
</tr>
<tr>
<td>ReLU</td>
<td>0 또는 입력값</td>
<td>계산 효율 높고, 딥러닝에서 표준</td>
</tr>
</tbody></table>
<p>이 함수들은 <strong>미분 가능</strong>하기 때문에 가중치의 기울기를 계산할 수 있게 해주며 경사하강법 적용이 가능해진다.</p>
<br>

<h4 id="2-역전파-알고리즘backpropagation">(2) 역전파 알고리즘(Backpropagation)</h4>
<p>1986년, 데이비드 루멜하트(David Rumelhart)와 제프리 힌튼(Geoffrey Hinton) 등은 역전파 알고리즘을 소개했다.</p>
<p>이 알고리즘은 오차를 출력층 → 은닉층 → 입력층 방향으로 거슬러 올라가며 가중치를 조정하는 방식이다.</p>
<ul>
<li>출력층에서 손실을 계산</li>
<li>각 뉴런이 손실에 얼마나 영향을 끼쳤는지 기울기 계산</li>
<li>이 기울기를 바탕으로 가중치를 조금씩 수정</li>
</ul>
<p>$$
w_i \leftarrow w_i - \eta \cdot \frac{\partial L}{\partial w_i}
$$</p>
<ul>
<li>$\eta$: &nbsp;학습률 (learning rate), 가중치를 얼마나 크게 조정할지를 결정하는 하이퍼파라미터</li>
<li>$\frac{\partial L}{\partial w_i}$: &nbsp;손실 함수 $L$에 대한 가중치 $w_i$의 변화율 (기울기)</li>
</ul>
<br>

<blockquote>
<p>퍼셉트론은 인공지능 모델의 시작이었지만, XOR 같은 단순한 비선형 문제도 해결할 수 없는 구조적 한계를 가지고 있었다.</p>
<p>이 한계를 정확히 짚어낸 연구 덕분에 다층 퍼셉트론 구조가 등장했고, 이어서 활성화 함수 개선과 역전파 알고리즘이 도입되며 현대 딥러닝의 기반이 완성되었다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>퍼셉트론은 신경망의 기초를 이루는 개념이지만 선형 분류 문제만 해결 가능하다는 한계를 알 수 있었다.</p>
<p>XOR 문제처럼 두 집단이 직선 하나로 나뉘지 않는 경우, 퍼셉트론은 어떤 조합의 가중치와 바이어스로도 올바른 출력을 만들지 못한다는 점이 흥미로웠다. 딥러닝을 막연히 공부했을 때는 &#39;왜 층을 더 쌓아야 하는가?&#39;에 대한 의문을 가지지 못했는데 MPL의 원리를 이해하면서 대답까지 이끌어낼 수 있었다.</p>
<p>여기서도 마찬가지로, 결국 중요한 건 모델보다는 문제에 맞는 구조를 설계하는 능력이라고 생각된다. 이 글을 통해 단일 뉴런 구조에서부터 계층적 구조로 넘어가는 배경을 명확히 이해할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] Artificial Neural Network]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-Artificial-Neural-Network</link>
            <guid>https://velog.io/@jul-ee/DS-DL-Artificial-Neural-Network</guid>
            <pubDate>Mon, 09 Jun 2025 01:48:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 인공신경망이란?
🖇 &nbsp;2. 생물학적 뉴런의 구조
🖇 &nbsp;3. 인공 뉴런 모델
🖇 &nbsp;4. 인공신경망의 계층 구조
🖇 &nbsp;5. 활성화 함수의 역할
🖇 &nbsp;6. 역전파 알고리즘</p>
</blockquote>
<br>

<p>딥러닝에서 가장 먼저 이해해야 하는 개념이 <strong>인공신경망(Artificial Neural Network, ANN)</strong>이다. 핵심 원리는 생각보다 간단할 수 있다. 인간의 뇌 구조에서 영감을 받아 수학적으로 구현된 모델이기 때문이다.</p>
<p>이 글에서는 인공신경망의 동작 원리를 생물학적 뉴런의 구조와 비교하며 설명하고, 실제로 컴퓨터가 데이터를 처리하고 학습하는 과정을 단계별로 다루고 있다.</p>
<ul>
<li>생물학적 뉴런은 어떤 구조로 신호를 주고받을까?</li>
<li>인공 뉴런은 이를 어떻게 수학적으로 흉내 낼까?</li>
<li>신경망은 입력부터 출력까지 어떤 과정을 거쳐 학습할까?</li>
</ul>
<p>이 질문을 바탕으로 입력층-은닉층-출력층으로 구성된 계층 구조와 활성화 함수, 역전파 알고리즘의 순서로 딥러닝의 기초를 정리해 보았다.</p>
<br>
<br>
<br>



<h3 id="🖇-1-인공신경망이란">🖇 &nbsp;1. 인공신경망이란?</h3>
<p>인공신경망(Artificial Neural Network)은 인간의 뇌를 수학적으로 모방한 구조로, 기계가 데이터를 학습하고 패턴을 인식할 수 있게 해주는 계산 모델이다. 뇌의 뉴런들이 자극을 받고 신호를 전달하는 생물학적 구조에서 착안해 설계되었다.</p>
<p>기본적으로 인공신경망은 <strong>입력층(Input layer)</strong>, <strong>은닉층(Hidden layer)</strong>, <strong>출력층(Output layer)</strong>의 계층 구조를 가지고, 각 층은 여러 개의 뉴런(노드)으로 구성된다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/3509be6c-b12a-4311-8cd3-233d143c0b60/image.png" alt=""></p>
<blockquote>
<p>신경망은 데이터가 입력되어 → 가중치 연산을 거치고 → 비선형 변환을 통해 → 예측 또는 분류 결과를 출력하는 흐름으로 동작한다.</p>
</blockquote>
<p>이러한 구조는 이미지 분류, 음성 인식, 자연어 처리 등 실제 문제를 해결하기 위해 딥러닝에서 광범위하게 활용된다.</p>
<hr>
<br>
<br>

<h3 id="🖇-2-생물학적-뉴런의-구조">🖇 &nbsp;2. 생물학적 뉴런의 구조</h3>
<p>인간의 뇌에는 약 860억 개의 뉴런이 존재하며 이들은 서로 전기·화학적 신호를 주고받는다.</p>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/63b713ff-086f-4ae0-8db6-11c4383ec212/image.png" alt=""></p>
<ul>
<li>수상돌기(Dendrite): 다른 뉴런에서 오는 신호를 받아들이는 역할</li>
<li>세포체(Cell Body): 신호를 종합하고 필요한 연산을 수행</li>
<li>축삭(Axon): 처리된 신호를 다른 뉴런으로 전달</li>
<li>축삭 말단(Axon Terminal): 신호가 다음 뉴런의 수상돌기로 전달되는 지점</li>
<li>시냅스(Synapse): 두 뉴런이 연결되어 신호를 주고받는 접합부</li>
</ul>
<p>생물학적 뉴런의 정보 흐름은 ‘자극의 수용 → 종합 → 전달’이고, 이는 인공 뉴런의 정보 처리 방식에 그대로 반영된다.</p>
<hr>
<br>
<br>

<h3 id="🖇-3-인공-뉴런-모델">🖇 &nbsp;3. 인공 뉴런 모델</h3>
<p>인공 뉴런은 실제 뉴런의 작동 원리를 수학적으로 단순화한 구조이다.</p>
<p>하나의 인공 뉴런은 다음과 같은 계산 과정을 거친다.</p>
<p>$$
z = \sum_{i=1}^{n} w_i x_i + b \quad \Rightarrow \quad y = f(z)
$$</p>
<ul>
<li>$x_i$: &nbsp;입력값 (이미지의 각 픽셀, 사용자 행동 데이터 등)</li>
<li>$w_i$: &nbsp;입력에 대한 가중치. 입력의 중요도를 조절하는 파라미터</li>
<li>$b$: &nbsp;바이어스. 뉴런의 출력 기준을 조정</li>
<li>$f(z)$: &nbsp;활성화 함수. 출력값을 비선형적으로 변환하여 표현력을 높임</li>
</ul>
<p>이 모델은 단순한 연산처럼 보이지만 뉴런을 여러 개 연결하면 매우 복잡한 함수도 근사할 수 있다. 실제로 신경망은 이런 단순한 계산 단위를 수천, 수만 개 쌓아 복잡한 문제를 해결한다.</p>
<h6 id="이미지-최초의-인공신경망">[이미지] 최초의 인공신경망</h6>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/21b595f9-cde9-49ec-bb2e-360bc1c49ec7/image.png" alt=""></p>
<hr>
<br>
<br>

<h3 id="🖇-4-인공신경망의-계층-구조">🖇 &nbsp;4. 인공신경망의 계층 구조</h3>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/86594e6d-9d87-4c8c-b2f7-c15f3cfb6758/image.png" alt=""></p>
<p>인공신경망은 하나의 인공 뉴런이 아닌 <strong>수많은 뉴런들이 층 단위로 연결된 구조</strong>를 가진다.</p>
<p><code>입력층(Input Layer)</code></p>
<ul>
<li>데이터를 받아들이는 역할. 원본 데이터를 은닉층으로 전달</li>
</ul>
<p><code>은닉층(Hidden Layer)</code></p>
<ul>
<li>입력을 바탕으로 특징을 추출하거나 가공</li>
<li>깊은 신경망일수록 이 층이 많아짐</li>
<li>은닉층은 일반적으로 Dense Layer, 즉 모든 뉴런이 완전히 연결된 구조(Fully Connected)로 구성됨. 이 구조는 각각의 입력 특징이 출력 결과에 모두 반영되도록 함.</li>
</ul>
<p><code>출력층(Output Layer)</code></p>
<ul>
<li>최종적으로 예측값을 출력
  e.g., 분류 문제에서는 softmax 함수 등을 통해 확률값 출력</li>
</ul>
<p>각 층의 뉴런은 이전 층의 모든 뉴런과 연결되며, 이로 인해 네트워크는 고차원의 비선형 함수도 학습할 수 있다.</p>
<blockquote>
<p>예를 들어 고양이 사진과 개 사진을 분류하는 경우, 입력층은 픽셀 값, 은닉층은 귀, 털, 코 같은 중간 특징을, 출력층은 ‘고양이’, ‘개’ 같은 결과를 출력하게 된다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-5-활성화-함수의-역할">🖇 &nbsp;5. 활성화 함수의 역할</h3>
<p>활성화 함수는 선형 연산만으로는 표현할 수 없는 복잡한 관계를 학습하기 위한 핵심 구성 요소이다.</p>
<p>선형 함수만 사용하면 신경망의 모든 층을 합쳐도 결국 하나의 선형 함수로 축소되며 이로 인해 표현력이 떨어지게 된다.</p>
<p>대표적인 활성화 함수는 다음과 같다.</p>
<ul>
<li>Step Function: 초기 퍼셉트론에서 사용. 0 이상이면 1, 미만이면 0. 하지만 미분 불가능하여 학습이 불가능</li>
<li>Sigmoid Function: 출력값을 0~1 사이로 압축. 하지만 gradient vanishing 문제 발생</li>
<li>ReLU (Rectified Linear Unit): 0 이하에서는 0, 0 이상은 그대로 출력. 계산이 빠르고 성능이 뛰어나 현대 딥러닝에서 가장 널리 사용됨</li>
</ul>
<p>활성화 함수는 신경망에 <strong>비선형성(non-linearity)</strong>을 부여해주므로 이 덕분에 신경망은 매우 복잡한 문제도 다룰 수 있다.</p>
<hr>
<br>
<br>

<h3 id="🖇-6-역전파-알고리즘">🖇 &nbsp;6. 역전파 알고리즘</h3>
<p>다층 구조의 신경망은 수많은 가중치와 바이어스를 가진다. 이들을 효과적으로 학습시키기 위해서는 역전파 알고리즘이 필요하다.</p>
<p>역전파(Backpropagation)는 예측값과 실제값의 차이(오차)를 기준으로, 각 가중치가 얼마나 잘못된 결과에 영향을 끼쳤는지 계산하여 이를 바탕으로 가중치를 수정하는 방식이다.</p>
<p>다음과 같은 흐름으로 학습하게 된다.</p>
<ol>
<li>순전파 (Forward Propagation): 입력 → 은닉층 → 출력층으로 예측값 계산</li>
<li>오차 계산 (Loss Function): 예측값과 정답의 차이 측정 (MSE, Cross Entropy 등)</li>
<li>역전파 (Backpropagation): 오차를 뒤로 전달하면서 각 파라미터의 기울기(gradient) 계산</li>
<li>가중치 갱신 (Gradient Descent): 기울기를 기반으로 가중치 업데이트</li>
</ol>
<p>이 알고리즘은 1980년대 중반 루멜하트(Rumelhart), 힌튼(Hinton) 등이 실용화하면서 본격적으로 딥러닝이 부활하게 된 계기가 되었다.</p>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>인공신경망은 인간의 뇌를 본떠 만든 모델이지만 단순한 수학 연산을 반복적으로 구성한 구조로 만들어진다. 이 구조를 깊고 넓게 쌓아 올리면 매우 복잡한 문제도 해결할 수 있는 강력한 모델이 된다는 것이 흥미롭다.</p>
<p>흔히 &quot;딥러닝은 어렵다&quot;고 생각하는 이유 중 많은 부분이 용어나 구조에 대한 첫 진입 장벽 때문이라는 생각이 들었다.</p>
<p>뉴런 하나의 작동 원리와 학습 흐름을 명확히 이해하면 그 위에 다양한 모델을 쌓을 수 있을 것이다. 이 글을 통해 인공신경망의 기본 개념, 작동 방식, 학습 흐름을 체계적으로 정리할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DL] 가위바위보 이미지 분류: 웹캠으로 데이터 수집]]></title>
            <link>https://velog.io/@jul-ee/DS-DL-%EA%B0%80%EC%9C%84%EB%B0%94%EC%9C%84%EB%B3%B4-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B6%84%EB%A5%98-%EC%9B%B9%EC%BA%A0%EC%9C%BC%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91</link>
            <guid>https://velog.io/@jul-ee/DS-DL-%EA%B0%80%EC%9C%84%EB%B0%94%EC%9C%84%EB%B3%B4-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B6%84%EB%A5%98-%EC%9B%B9%EC%BA%A0%EC%9C%BC%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91</guid>
            <pubDate>Fri, 06 Jun 2025 11:27:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🖇 &nbsp;1. 데이터 수집
🖇 &nbsp;2. 데이터 전처리
🖇 &nbsp;3. 이미지 불러오기 및 정규화
🖇 &nbsp;4. 딥러닝 모델 구성 및 학습
🖇 &nbsp;5. 테스트셋 평가 및 시각화</p>
</blockquote>
<br>

<p>이전 글에서는 정제된 MNIST 데이터셋으로 딥러닝 모델에 학습시켜 보는 실습을 진행하였다. 실제로는 데이터를 직접 만들고 가공해야 하는 경우가 많기 때문에 이번에는 직접 수집한 데이터를 기반으로 분류 모델을 구성해 보는 실습을 진행해 보았다.</p>
<p>웹캠을 이용해 가위, 바위, 보 이미지를 직접 촬영하고, 이를 바탕으로 딥러닝 모델을 학습시켜 분류기를 구현한다. <a href="https://velog.io/@jul-ee/DL-MNIST-%EC%88%AB%EC%9E%90-%EB%B6%84%EB%A5%98">MNIST 숫자 분류</a>와 마찬가지로 데이터 수집 → 전처리 → 모델 훈련 → 평가 → 시각화 → 일반화 성능 점검까지의 전체 흐름을 살펴보았다.</p>
<p>다음과 같은 목표를 세우고 수행하였다.</p>
<ul>
<li>직접 촬영한 RGB 컬러 이미지를 분류하는 모델 만들기</li>
<li>사용자 정의 데이터셋을 구성하고 전처리하기</li>
<li>CNN 기반 딥러닝 모델을 구성하고 훈련하기</li>
<li>훈련셋과 테스트셋 간 중복 여부를 검사하여 데이터셋 검증하기</li>
<li>테스트셋으로 모델 일반화 성능을 평가하고 혼동행렬로 시각화하기</li>
</ul>
<hr>
<br>
<br>

<h3 id="🖇-1-데이터-수집">🖇 &nbsp;1. 데이터 수집</h3>
<h4 id="11-teachable-machine이란">1.1 Teachable Machine이란?</h4>
<p><a href="https://teachablemachine.withgoogle.com/">Teachable Machine</a>은 구글이 제공하는 웹 기반 머신러닝 훈련 도구다.</p>
<p>코딩 지식 없이도 웹캠, 마이크 등을 통해 데이터를 수집하고, 모델을 훈련하고, 결과를 바로 확인할 수 있다.</p>
<p>본 실습에서는 Teachable Machine의 <code>Image Project &gt; Standard image model</code> 기능을 활용하여 가위, 바위, 보 각각의 이미지를 웹캠으로 촬영하였다.</p>
<br>

<h4 id="12-이미지-수집-절차">1.2 이미지 수집 절차</h4>
<ol>
<li>Teachable Machine 사이트 접속 → <code>Get Started</code> 클릭</li>
<li><code>Image Project</code> → <code>Standard image model</code> 선택</li>
<li>세 개의 클래스(<code>가위</code>, <code>바위</code>, <code>보</code>)를 생성하고, 웹캠으로 <strong>각각 100장씩</strong> 이미지 수집</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/245ea387-deb0-4881-ae79-3fcbba9b8819/image.jpg" alt=""></p>
<br>

<h4 id="13-수집-시-유의사항">1.3 수집 시 유의사항</h4>
<ul>
<li>한 사람만 촬영한 데이터는 각도, 조명, 손 모양이 제한적이므로 일반화 성능이 낮아질 수 있다.</li>
<li>여러 사람이 협업하여 서로 다른 손 크기, 배경, 밝기 등을 반영한 데이터가 포함되면 학습에 유리하다.</li>
<li>손 모양이 명확히 드러나도록, 웹캠에 가까이 대고, 흰 배경 등으로 명도 대비를 높이면 정확도 향상에 도움이 된다.</li>
</ul>
<blockquote>
<p>💡 이번 실습에서는 이 사항을 간과하고 손 색과 비슷한 배경에서 촬영을 진행하였다. 학습 데이터 성능에 따라 재촬영을 고려하고 있었다.</p>
</blockquote>
<br>

<h4 id="14-데이터-저장">1.4 데이터 저장</h4>
<p>촬영을 마친 뒤, 우측 상단 메뉴에서 <code>Export Image</code> 기능을 사용해 각 클래스를 <code>.zip</code> 파일로 다운로드했다.</p>
<p>파일 이름은 다음과 같이 사용하였다.</p>
<ul>
<li><code>scissor.zip</code> – 가위</li>
<li><code>rock.zip</code> – 바위</li>
<li><code>paper.zip</code> – 보</li>
</ul>
<hr>
<br>
<br>

<h3 id="🖇-2-데이터-전처리">🖇 &nbsp;2. 데이터 전처리</h3>
<p>이제 수집한 데이터를 바탕으로 다음의 단계로 실습을 진행한다.</p>
<ol>
<li>Teachable Machine을 통해 웹캠 기반 이미지 수집</li>
<li>이미지 전처리 (리사이징 및 정규화)</li>
<li>CNN 모델 구성 및 학습</li>
<li>직접 만든 테스트셋을 활용한 일반화 성능 평가</li>
<li>혼동행렬 시각화 및 성능 분석</li>
<li>훈련셋과 테스트셋 간의 중복 이미지 검사</li>
</ol>
<p>각 단계에서 왜 이 과정을 수행하는지, 이 코드가 무슨 역할을 하는지를 충분히 이해하면서 작성해 보았다.</p>
<h4 id="21-라이브러리-임포트-및-버전-확인">2.1 라이브러리 임포트 및 버전 확인</h4>
<ul>
<li>딥러닝 프레임워크로 <code>TensorFlow</code>, 수치 계산용 <code>NumPy</code>를 사용한다.</li>
<li>호환성 문제가 없도록 실습 환경의 버전을 확인한다.</li>
</ul>
<pre><code class="language-python">import tensorflow as tf
import numpy as np

print(tf.__version__)
print(np.__version__)
</code></pre>
<br>

<h4 id="22-압축-해제-및-이미지-불러오기">2.2 압축 해제 및 이미지 불러오기</h4>
<p>Google Colab을 활용하여 Google Drive에 저장된 <code>scissor.zip</code>, <code>rock.zip</code>, <code>paper.zip</code> 파일을 각각 다음 폴더에 압축 해제한다.</p>
<ul>
<li><code>!</code>는 Colab 셀에서 리눅스 명령어를 실행할 수 있도록 해주는 명령어이다.</li>
<li>각 클래스별 이미지를 디렉토리에 나눠 저장한다.</li>
</ul>
<pre><code class="language-python">from google.colab import drive
drive.mount(&#39;/content/drive&#39;)

!unzip /content/drive/MyDrive/DS/DL/scissor.zip -d 가위
!unzip /content/drive/MyDrive/DS/DL/rock.zip -d 바위
!unzip /content/drive/MyDrive/DS/DL/paper.zip -d 보</code></pre>
<br>

<h4 id="23-이미지-크기-통일-리사이징">2.3 이미지 크기 통일 (리사이징)</h4>
<p>딥러닝 모델에 이미지를 입력하기 위해 입력 크기를 통일해야 한다.</p>
<ul>
<li><code>glob</code>은 디렉토리 내의 모든 <code>.jpg</code> 파일을 리스트로 가져온다.</li>
<li><code>PIL.Image.resize</code>를 사용하여 28x28 픽셀로 변환한다.</li>
</ul>
<pre><code class="language-python"># 이미지 파일을 열고 처리하기 위한 라이브러리(Pillow 패키지의 핵심 모듈)
from PIL import Image  # 이미지 로딩, 리사이징, 색 변환 등에 유용
# 특정 경로 내 파일들을 패턴에 맞게 한 번에 불러올 수 있도록 도와주는 모듈
import glob
# 디렉토리 경로 조작, 파일 존재 여부 확인 등 운영체제 관련 기능 제공
import os

def resize_images(img_path):
    images = glob.glob(img_path + &#39;/*.jpg&#39;)  # 지정 경로에서 .jpg 이미지 파일 목록을 리스트로 가져오기

    print(len(images), &#39; images to be resized.&#39;)  # 총 이미지 수 출력

    # 이미지 크기 리사이징 대상 크기 정의 (CNN 입력 형식에 맞춤)
    target_size = (28, 28)

    for img in images:
        old_img = Image.open(img)  # 이미지 파일 열기 (PIL 객체)
        new_img = old_img.resize(target_size, Image.Resampling.LANCZOS)  # 리사이즈 (LANCZOS는 고급 보간법)
        new_img.save(img, &#39;JPEG&#39;)  # 기존 파일에 덮어쓰기 저장

    print(len(images), &#39; images resized.&#39;)  # 완료된 이미지 수 출력</code></pre>
<pre><code class="language-python"># 각 클래스별 이미지가 저장된 폴더(가위/바위/보)를 대상으로 resize_images() 함수 호출
# : 모든 이미지를 28x28 크기로 리사이즈하고, 완료 여부 출력
# : CNN 모델의 입력 크기를 통일하기 위한 전처리 작업
image_dir_path = &#39;./가위&#39;       # 이미지가 들어있는 폴더 경로 지정
resize_images(image_dir_path)

image_dir_path = &#39;./바위&#39;
resize_images(image_dir_path)

image_dir_path = &#39;./보&#39;
resize_images(image_dir_path)
</code></pre>
<hr>
<br>
<br>

<h3 id="🖇-3-이미지-불러오기-및-정규화">🖇 &nbsp;3. 이미지 불러오기 및 정규화</h3>
<h4 id="31-데이터셋-불러오기">3.1 데이터셋 불러오기</h4>
<p>이미지를 <code>numpy array</code>로 변환하여 모델에 입력할 수 있도록 처리한다.</p>
<ul>
<li>이미지 resize 함수 정의</li>
</ul>
<pre><code class="language-python">def load_data(img_path, folder_names):
    # 클래스 이름에 따라 라벨을 0(가위), 1(바위), 2(보)로 지정
    label_map = {&#39;가위&#39;: 0, &#39;바위&#39;: 1, &#39;보&#39;: 2, ...}
    all_imgs, all_labels = [], []

    for folder in folder_names:
        files = glob.glob(f&quot;{img_path}/{folder}/*.jpg&quot;)

        for file in files:
            img = Image.open(file).resize((28, 28))
            all_imgs.append(np.array(img))
            all_labels.append(label_map[folder])

    return np.array(all_imgs), np.array(all_labels)
</code></pre>
<pre><code class="language-python">x_train, y_train = load_data(&#39;.&#39;, [&#39;가위&#39;, &#39;바위&#39;, &#39;보&#39;])
</code></pre>
<ul>
<li><p>이미지 데이터 로드 및 전처리 함수 정의</p>
<pre><code class="language-python">def load_data(img_path, folder_names):
  &quot;&quot;&quot;
  이미지 데이터를 불러오고 라벨을 부여하는 함수
  - 가위: 0, 바위: 1, 보: 2 라벨 부여
    - 테스트용 폴더도 라벨은 동일하게 부여
  - 학습용: folder_names = [&#39;가위&#39;, &#39;바위&#39;, &#39;보&#39;]
  - 테스트용: folder_names = [&#39;가위_test&#39;, &#39;바위_test&#39;, &#39;보_test&#39;]
  &quot;&quot;&quot;

  img_size = 28       # 이미지 가로, 세로 크기
  color = 3           # RGB 컬러 채널 (3채널)

  # 라벨 맵: 폴더 이름에 따라 라벨 부여
  label_map = {
      &#39;가위&#39;: 0, &#39;바위&#39;: 1, &#39;보&#39;: 2,
      &#39;가위_test&#39;: 0, &#39;바위_test&#39;: 1, &#39;보_test&#39;: 2,
      &#39;가위_test2&#39;: 0, &#39;바위_test2&#39;: 1, &#39;보_test2&#39;: 2,
      &#39;가위_test3&#39;: 0, &#39;바위_test3&#39;: 1, &#39;보_test3&#39;: 2
  }

  # 이미지 배열 및 라벨 배열 초기화
  all_imgs = []
  all_labels = []

  # 지정된 폴더 목록에 대해 이미지 로드
  for folder in folder_names:
      files = glob.glob(os.path.join(img_path, folder, &#39;*.jpg&#39;))
      label = label_map[folder]

      for file in files:
          try:
              img = Image.open(file).resize((img_size, img_size))  # 이미지 로드 및 리사이즈
              img = np.array(img, dtype=np.int32)                  # 배열 변환
              all_imgs.append(img)
              all_labels.append(label)
          except Exception as e:
              print(f&quot;[오류] {file} 읽기 실패:&quot;, e)

  print(&#39;데이터의 이미지 개수:&#39;, len(all_imgs))
  return np.array(all_imgs), np.array(all_labels)  # 이미지 데이터와 라벨 반환</code></pre>
</li>
</ul>
<br>

<h4 id="32-데이터-정규화">3.2 데이터 정규화</h4>
<ul>
<li>RGB 이미지의 픽셀 값은 0<del>255 범위를 가지므로 0</del>1로 정규화하여 학습 안정성을 높인다.</li>
</ul>
<pre><code class="language-python"># 함수 실행: 현재 디렉토리 기준으로 데이터 로드
image_dir_path = &#39;.&#39;
(x_train, y_train) = load_data(image_dir_path, [&#39;가위&#39;, &#39;바위&#39;, &#39;보&#39;])

# 데이터를 무작위로 섞어 모델이 편향 없이 학습할 수 있도록 준비
x_train, y_train = shuffle(x_train, y_train, random_state=42)

# 이미지 정규화: 픽셀 값을 0~1 사이로 변환해 학습 안정화
x_train_norm = x_train / 255.0

# 데이터 형태 확인
print(&#39;x_train shape: {}&#39;.format(x_train.shape))  # (샘플 수, 28, 28, 3)
print(&#39;y_train shape: {}&#39;.format(y_train.shape))  # (샘플 수,)</code></pre>
<br>

<h4 id="33-학습-데이터-확인">3.3 학습 데이터 확인</h4>
<p>모델 학습에 들어가기 전에 불러온 학습 데이터 이미지와 라벨이 어떻게 출력되는지 확인해 보았다.</p>
<pre><code class="language-python">import matplotlib.pyplot as plt

plt.imshow(x_train[224])         # RGB 이미지 그대로 출력
plt.axis(&#39;off&#39;)                  # 축 눈금 숨김
plt.show()

# 해당 이미지의 실제 정답 라벨 출력
print(&#39;라벨: &#39;, y_train[224])    # 0=가위, 1=바위, 2=보</code></pre>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/c26008d6-b922-40c9-afae-99cc8c057f29/image.png" alt=""></p>
<p>28x28 이미지로 출력되다 보니 픽셀이 깨지는 것을 육안으로 확인할 수 있다. 아마 이러한 이유로 인해 이후 테스트 데이터에서 모델 성능이 낮게 측정되는 것이 아닌가 하는 추측을 해 볼 수 있었다.</p>
<hr>
<br>
<br>

<h3 id="🖇-4-딥러닝-모델-구성-및-학습">🖇 &nbsp;4. 딥러닝 모델 구성 및 학습</h3>
<p>가위/바위/보 이미지 분류를 위한 CNN 모델을 정의한다.</p>
<ul>
<li>입력: 크기 (<code>28x28x3</code>)의 컬러 이미지</li>
<li>출력: 3개의 클래스(가위=0, 바위=1, 보=2)에 대한 확률 분포</li>
</ul>
<h4 id="41-cnn-모델-설계">4.1 CNN 모델 설계</h4>
<p>CNN 모델의 파라미터에 대한 설명은 <a href="https://velog.io/@jul-ee/DL-MNIST-%EC%88%AB%EC%9E%90-%EB%B6%84%EB%A5%98">MNIST 숫자 분류</a>에서 자세히 다뤄보았다.</p>
<ul>
<li><code>Conv2D</code>: 필터를 이용해 이미지 특징 추출</li>
<li><code>MaxPooling2D</code>: 특성 맵을 축소하여 계산량 줄이고 주요 특징 유지</li>
<li><code>Flatten</code>: 2D를 1D로 펼쳐서 Fully Connected Layer로 전달</li>
<li><code>Dense</code>: 분류를 위한 신경망 계층</li>
<li>마지막 <code>softmax</code>: 클래스별 확률 출력</li>
</ul>
<pre><code class="language-python">import tensorflow as tf         # TensorFlow: 딥러닝 프레임워크
from tensorflow import keras    # Keras: 고수준 API
import numpy as np              # NumPy: 수치 연산용

# 하이퍼파라미터 설정
n_channel_1 = 16       # 첫 번째 Conv 층의 필터 수
n_channel_2 = 32       # 두 번째 Conv 층의 필터 수
n_dense = 32           # Dense 층 뉴런 수
n_train_epoch = 2      # 학습 epoch 수

# CNN 모델 정의
model = keras.models.Sequential()
model.add(keras.layers.Conv2D(n_channel_1, (3, 3), activation=&#39;relu&#39;, input_shape=(28, 28, 3)))
model.add(keras.layers.MaxPool2D(2, 2))
model.add(keras.layers.Conv2D(n_channel_2, (3, 3), activation=&#39;relu&#39;))
model.add(keras.layers.MaxPooling2D((2, 2)))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(n_dense, activation=&#39;relu&#39;))
model.add(keras.layers.Dense(3, activation=&#39;softmax&#39;))

# 모델 요약 정보 출력: 각 층의 출력 형태, 파라미터 수 확인 가능
model.summary()</code></pre>
<pre><code>Model: &quot;sequential_1&quot;
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_2 (Conv2D)               │ (None, 26, 26, 16)     │           448 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 13, 13, 16)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 11, 11, 32)     │         4,640 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 5, 5, 32)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_1 (Flatten)             │ (None, 800)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 32)             │        25,632 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (Dense)                 │ (None, 3)              │            99 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 30,819 (120.39 KB)
 Trainable params: 30,819 (120.39 KB)
 Non-trainable params: 0 (0.00 B)</code></pre><br>

<h4 id="42-모델-컴파일-및-훈련">4.2 모델 컴파일 및 훈련</h4>
<p>정의한 CNN 모델을 컴파일하고 학습시키기</p>
<ul>
<li><code>compile()</code>: 모델 학습 방식(손실 함수, 최적화 알고리즘, 평가 지표 등)을 설정</li>
<li>주어진 입력(x_train)과 정답(y_train)을 사용해 모델을 실제로 학습</li>
</ul>
<pre><code class="language-python"># 모델 컴파일: 학습 방법과 평가 방식 정의
model.compile(
    optimizer=&#39;adam&#39;,                        # Adam 옵티마이저: 학습률 자동 조절로 효율적인 학습 수행
    loss=&#39;sparse_categorical_crossentropy&#39;,  # 정수형 클래스 레이블(0~2)을 사용할 때 적합한 다중 클래스 손실 함수
    metrics=[&#39;accuracy&#39;]                     # 학습 중 정확도(accuracy)를 측정해 성능 확인
)

# 모델 훈련: 학습 데이터를 사용하여 weight 업데이트 진행
model.fit(
    x_train,              # 학습에 사용할 입력 이미지 데이터
    y_train,              # 각 이미지의 실제 정답 레이블
    epochs=n_train_epoch  # 전체 데이터를 n_train_epoch번 반복해서 학습 (기본값 10)
)</code></pre>
<pre><code>Epoch 1/2
10/10 ━━━━━━━━━━━━━━━━━━━━ 2s 18ms/step - accuracy: 0.3608 - loss: 10.7289
Epoch 2/2
10/10 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step - accuracy: 0.7551 - loss: 1.0474
&lt;keras.src.callbacks.history.History at 0x7b357b9f2dd0&gt;</code></pre><blockquote>
<p>epochs를 10으로 설정했을 때 epoch 7/10부터는 accuracy가 1.0000이 나왔다. 특히 5~10 epoch 사이에 급격한 향상과 정교한 미세조정이 이루어진 것을 확인할 수 있었다.</p>
<p>그러나 이후에는 과적합을 방지하기 위해 epoch를 2로 설정해 보았다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="🖇-5-테스트셋-평가-및-시각화">🖇 &nbsp;5. 테스트셋 평가 및 시각화</h3>
<p>테스트셋은 같은 실습을 진행하는 팀원들이 각자 촬영한 이미지를 받아 진행하였다. 즉, 촬영한 사람과 배경 등의 환경이 완전히 다른 데이터셋이다.</p>
<h4 id="51-테스트셋-로딩-및-정규화">5.1 테스트셋 로딩 및 정규화</h4>
<p>테스트셋도 동일한 전처리(리사이징 및 정규화)를 적용해야 한다.</p>
<pre><code class="language-python">test_image_dir_path = &#39;./가위_test&#39;
resize_images(test_image_dir_path)

test_image_dir_path = &#39;./바위_test&#39;
resize_images(test_image_dir_path)

test_image_dir_path = &#39;./보_test&#39;
resize_images(test_image_dir_path)

# 테스트 데이터 로드 (가위: 0, 바위: 1, 보: 2)
# : 이미지 데이터를 배열로 읽고 라벨링
test_image_dir_path = &#39;.&#39;
(x_test, y_test) = load_data(test_image_dir_path, [&#39;가위_test&#39;, &#39;바위_test&#39;, &#39;보_test&#39;])
# # 정규화: 이미지 픽셀 값을 0~1 사이로 스케일링
x_test_norm = x_test/255.0</code></pre>
<br>

<h4 id="52-훈련셋과-테스트셋-중복-확인">5.2 훈련셋과 테스트셋 중복 확인</h4>
<p>테스트 데이터셋을 불러오는 과정에서 경로 문제가 있어서 accuracy가 1.0 이 나오는 문제가 발생했었다.</p>
<p>동일한 이미지가 훈련셋과 테스트셋에 동시에 포함되면 성능이 왜곡될 수 있기 때문에 정확히 같은 이미지가 있는지 체크하는 과정이 필요하다고 판단하였다.</p>
<pre><code class="language-python">overlap_count = 0
for i in range(len(x_test)):
    for j in range(len(x_train)):
        if np.array_equal(x_test[i], x_train[j]):
            overlap_count += 1
print(&#39;중복 이미지 개수: &#39;, overlap_count)
</code></pre>
<pre><code>중복 이미지 개수: 0</code></pre><br>

<h4 id="52-모델-평가">5.2 모델 평가</h4>
<p>학습한 CNN 모델을 테스트 데이터셋에 대해 평가한다. 학습되지 않은 데이터에 대한 성능을 평가하여 과적합 여부나 일반화 수준을 확인할 수 있다.</p>
<ul>
<li>모델이 x_test 데이터를 기반으로 예측한 결과와 실제 라벨 y_test를 비교하여</li>
<li>손실 함수(loss)와 정확도(accuracy) 출력</li>
</ul>
<pre><code class="language-python"># 테스트 데이터로 모델 성능 평가: 손실값, 정확도 return
test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=2)
print(&#39;test_loss: {} &#39;.format(test_loss))
print(&#39;test_accuracy: {}&#39;.format(test_accuracy))</code></pre>
<pre><code>10/10 - 0s - 25ms/step - accuracy: 0.3567 - loss: 4.7789
test_loss: 4.778869152069092 
test_accuracy: 0.3566666543483734</code></pre><p>테스트 데이터에서 성능 해석 (accuracy: 0.356)</p>
<ul>
<li>일반화 성능 부족</li>
<li>학습에서 본 적 없는 데이터에 대해 모델이 잘 예측하지 못하고 있다는 뜻</li>
<li>loss 값이 크게 나온 것도 모델이 예측을 틀리는 경우가 많다는 의미</li>
<li>모델이 너무 복잡하거나 너무 단순해서 적절한 일반화를 하지 못한다고 판단할 수 있음</li>
</ul>
<br>

<h4 id="53-혼동행렬-시각화">5.3 혼동행렬 시각화</h4>
<p>예측된 결과가 어떤 클래스에서 잘못 분류되었는지를 시각적으로 확인하여 패턴을 찾아내기 위해 혼동행렬을 확인해 보았다.</p>
<ul>
<li>각 행은 실제 라벨, 열은 예측 라벨</li>
</ul>
<pre><code class="language-python"># 모델이 예측한 클래스 라벨 (확률에서 가장 높은 값 선택)
y_pred = model.predict(x_test_norm)
y_pred_labels = np.argmax(y_pred, axis=1)

# 혼동행렬 계산
cm = confusion_matrix(y_test, y_pred_labels)

class_names = [0, 1, 2]
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)
disp.plot(cmap=plt.cm.Blues)
plt.title(&#39;Confusion Matrix of Rock-Paper-Scissors Model&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/jul-ee/post/f985e4ac-aa6d-4ebb-8430-027d2e5a18b5/image.png" alt=""></p>
<p>정확하게 분류된 경우</p>
<ul>
<li>가위(0) 클래스는 100개 중 100개를 정확히 가위(0)로 예측됨 (100% 정확도)</li>
<li>실제가 바위(1)인 이미지 100개 중 98개가 가위(0)로 잘못 예측됨</li>
<li>실제가 보(2)인 이미지 100개 중 92개도 가위(0)로 잘못 예측됨</li>
</ul>
<p>이를 통해 모든 클래스가 가위로 예측되는 경향을 보인다는 것을 알 수 있다. 모델이 가위만 잘 인식하고, 나머지는 구분하지 못하고 있는 상태라고 해석할 수 있다.</p>
<p>총 3개의 서로 다른 테스트 데이터셋으로 똑같은 평가를 진행했지만 전부 위와 비슷한 경향이 나타났다.</p>
<br>

<h4 id="54-원인-분석">5.4 원인 분석</h4>
<p>이와 같은 현상이 발생하는 원인을 추정해 보았다.</p>
<p>(1) 데이터 불균형이나 라벨링 오류가 원인일 가능성?</p>
<ul>
<li>훈련 데이터에서 가위 이미지가 과도하게 많았다면 모델이 &quot;무조건 가위로 예측하면 손해가 없다&quot;고 학습했을 수 있지만 가위, 바위, 보 각각의 이미지를 직접 촬영했기 때문에 이것이 원인이 되지는 못한다.</li>
</ul>
<p>(2) 데이터 품질 저하</p>
<ul>
<li>바위와 보 데이터가 유독 흐리거나, 작거나, 배경이 복잡했을 수 있다. 모델이 특성을 제대로 학습하지 못하고 가위 특징만 강하게 학습했을 수 있다. 이것도 같은 환경과 픽셀을 가지고 있기 때문에 이렇다할 원인이라고 볼 수는 없을 것 같다.</li>
</ul>
<p>(4) 모델 과적합 또는 학습 부족</p>
<ul>
<li>Epoch 수가 너무 적거나 모델 복잡도가 낮아 모든 클래스를 구분할 표현력이 부족했을 수 있다. 단순히 ‘가위’ 특징만 빠르게 학습하고 조기 종료되었을 가능성도 있을 것 같다. 이후 딥러닝 모듈을 더 공부하면서 원인을 찾아낼 필요가 있다.</li>
</ul>
<blockquote>
<p>혼동행렬을 통해 모델이 특정 클래스(가위)에 과도하게 치우친 상태임을 명확히 볼 수 있었다.</p>
<p>모델 구조의 문제를 의심하기 전에 일단 데이터 구성과 라벨링, 학습 조건을 먼저 파악해 볼 필요가 있다. 우선 훈련 데이터와 테스트 데이터를 다시 제작해 보고, 여러 환경에서 테스트를 진행해 보는 것도 하나의 방법이겠다.</p>
<p>이미지의 픽셀이나 하이퍼파라미터 조정 등 초가적인 모델 개선과 함께 테스트셋에 대한 성능의 원인을 분석해 보아야겠다.</p>
</blockquote>
<hr>
<br>
<br>

<h3 id="인사이트-및-회고">인사이트 및 회고</h3>
<p>웹캠을 활용해 직접 데이터를 수집하고 분류 모델들어 성능을 평가하는 과정까지 수행해 볼 수 있었다. CNN 구조 설계와 이미지 전처리 전 과정 이후 테스트셋 구성과 평가의 중요성, 혼동행렬을 통한 성능 분석을 통해 해결해야 할 문제가 무엇인지 인지할 수 있었다.</p>
<p>복잡한 모델이 아니라도 작은 이미지에 대한 분류는 간단한 CNN으로도 충분한 성능을 낼 수 있다는 것을 확인했다는 점에서 흥미로웠지만, 테스트셋에 대한 평가 과정에서 정확도가 무너지는 것을 보고 무엇이 진짜 원인인지 파악하여 해결하는 과정이 필요하다는 것을 알 수 있었다. 지금까지는 딥러닝의 기본적은 흐름을 이해하기 위한 실습이었지만, 이후 더 공부해 나가면서 이 실습으로 다시 돌아와 원인을 분석해 볼 예정이다.</p>
]]></description>
        </item>
    </channel>
</rss>