<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dongmin_1999.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 18 Jun 2026 14:20:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dongmin_1999.log</title>
            <url>https://velog.velcdn.com/images/dongmin_1999/profile/5a44346c-6cac-402e-a741-52344e044e47/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dongmin_1999.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dongmin_1999" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[실무 프로젝트:자율주행(1)]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%8B%A4%EB%AC%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@dongmin_1999/%EC%8B%A4%EB%AC%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Thu, 18 Jun 2026 14:20:59 GMT</pubDate>
            <description><![CDATA[<h2 id="robotics-bootcamp-1-2일차-turtlebot4-셋업부터-depth-카메라-활용까지-🐢">[Robotics Bootcamp] 1-2일차: TurtleBot4 셋업부터 Depth 카메라 활용까지 🐢</h2>
<p>드디어 시작된 로봇 부트캠프! 첫날과 둘째 날은 당장 복잡한 알고리즘을 짜기보다, 로봇 제어의 기초가 되는 탄탄한 환경을 구축하는 데 집중했다. 1일차의 &#39;인프라 구축&#39;부터 2일차의 &#39;로봇 시각 구현&#39;까지의 과정을 정리해 본다.</p>
<h3 id="1-1일차-로봇-제어를-위한-기초-공사">1. 1일차: 로봇 제어를 위한 기초 공사</h3>
<p>첫날은 팀 프로젝트를 위한 환경 세팅의 연속이었다. 로봇이 내리는 명령을 정확히 수행하기 위해서는 PC와 로봇 사이의 통신이 무엇보다 중요하기 때문이다.</p>
<h4 id="🛠️-개발-환경-구축-ubuntu--ros2">🛠️ 개발 환경 구축 (Ubuntu &amp; ROS2)</h4>
<p>시뮬레이션과 딥러닝 구동을 위해 NVIDIA 그래픽 드라이버를 퍼포먼스 모드로 세팅하고, ROS2 Humble 버전을 설치했다.</p>
<ul>
<li><p>환경 설정: Locale 설정(UTF-8)과 .bashrc 환경변수 등록 등 기초적인 세팅을 완료했다.</p>
</li>
<li><p>유틸리티: 화면 녹화용 Peek, 스크린샷 편집용 Flameshot을 설치해 개발 효율을 높였다.</p>
</li>
</ul>
<h4 id="🤖-turtlebot4와-네트워크-연결-discovery-server">🤖 TurtleBot4와 네트워크 연결 (Discovery Server)</h4>
<p>교육장에는 수많은 로봇이 있기에, 내 PC가 우리 팀 로봇만 정확히 제어할 수 있도록 Discovery Server를 구성했다.</p>
<ul>
<li><p>네트워크 분리: 공유기를 허브로 PC와 로봇(Raspberry Pi)을 동일 Wi-Fi 대역으로 묶어 DDS 통신을 안정화했다.</p>
</li>
<li><p>고유 ID 부여: 팀별 ROS_DOMAIN_ID를 설정해 통신 채널을 완벽하게 격리했다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/eae055f6-17ea-418c-b3cc-db08fece64b6/image.png" alt=""></p>
</li>
</ul>
<blockquote>
<h4 id="💡-로봇의-상태-확인-led">💡 로봇의 상태 확인 (LED)</h4>
<p>TurtleBot4 상단의 5개 LED(MTR, Comm, WiFi/Battery, Power)가 모두 초록색인지 확인하자. 하나라도 꺼져 있다면 모터 전원, 네트워크 연결, 배터리 상태 등 해당 항목에 문제가 있다는 신호다.</p>
</blockquote>
<p>🚀 첫 주행과 커스텀 패키지</p>
<p>/cmd_vel 토픽을 활용한 키보드 제어부터 Action 통신을 이용한 거리/각도 기반 주행까지 성공했다. 마지막에는 turtlebot4_beep라는 커스텀 패키지를 빌드하며 ROS2 노드 구성 방식을 익혔다. 첫날은 실수로 Ctrl+C를 눌러 세팅이 꼬이는 바람에 밤 10시까지 고군분투했지만, 덕분에 환경 세팅의 중요성을 뼈저리게 배울 수 있었다.
2. 2일차: 로봇에게 눈을 달아주다 (Depth 카메라)</p>
<p>2일차에는 로봇이 주변 환경을 인지하게 만드는 &#39;시각&#39; 기술을 배웠다. 우리 팀의 첫 프로젝트 주제인 &#39;SLAM 기반 자율주행&#39;의 핵심인 Depth(깊이) 카메라를 다루는 날이었다.
👁️ 시차(Disparity)의 원리</p>
<p>로봇이 거리를 인식하는 핵심은 스테레오 카메라(OAK-D)이다. 두 개의 렌즈에 찍힌 물체의 &#39;위치 차이(시차)&#39;가 클수록 가까운 물체이고, 차이가 작을수록 먼 물체라는 원리를 통해 거리를 역산한다.
🛠️ 실전: Depth 데이터 추출</p>
<p>기본적으로 TurtleBot4는 대역폭 보호를 위해 Depth 데이터를 전송하지 않는다.</p>
<ul>
<li><p>드라이버 수정: 로봇 내부 SSH에 접속해 oakd_pro.yaml 파라미터를 수정하여 RGBD 모드를 활성화했다.</p>
</li>
<li><p>토픽 확인: ros2 topic list를 통해 /oakd/stereo/image_raw와 같은 깊이 관련 토픽들이 발행되는 것을 확인했다.</p>
</li>
<li><p>노드 구현 (rokey_pjt): * depth_checker: 카메라 영상에서 실시간으로 거리 값을 출력하는 노드.</p>
<ul>
<li>depth_checker_click: 특정 픽셀을 마우스로 클릭해 해당 지점의 거리를 측정하는 노드.</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 2일차의 성과
    단순히 화면을 보는 것을 넘어, <strong>&quot;내 앞 0.5m에 장애물이 있다&quot;</strong>는 수치 데이터를 직접 추출했다. 로봇 자율주행의 가장 기초적이면서도 필수적인 데이터를 손에 넣은 것이다.</p>
</blockquote>
<h3 id="📝-2일차를-마치며">📝 2일차를 마치며</h3>
<p>어제 ROS2 세팅에서 고생한 보람이 있었는지, 오늘은 빌드(colcon build) 성공 메시지가 유독 반가웠다. oakd_pro.yaml 설정 오타로 드라이버가 로드되지 않아 헤매기도 했지만, 결국 로봇 뷰어에 정확한 거리 값이 찍히는 것을 확인했을 때의 쾌감은 이루 말할 수 없었다.</p>
<p>이제 로봇이 세상을 수치로 인식할 수 있게 되었으니, 내일은 이 재료들을 활용해 본격적인 지도 작성(Mapping)에 도전할 예정이다. 3일차는 또 얼마나 흥미로울지 기대된다! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nav2  개념 정리]]></title>
            <link>https://velog.io/@dongmin_1999/Nav2-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dongmin_1999/Nav2-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 31 May 2026 05:13:03 GMT</pubDate>
            <description><![CDATA[<p>ROS2 기반의 자율주행 스택인 Nav2 (Navigation 2)의 개념정리</p>
<p>Nav2의 전체적인 아키텍처와 주요 컴포넌트들을 정리하려한다.</p>
<hr>
<h2 id="1-move_base-vs-nav2">1. move_base vs Nav2</h2>
<h3 id="1-move_base--모놀리식monolithic-구조">1) move_base : 모놀리식(Monolithic) 구조</h3>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/469dbdb6-6113-4a07-b96d-8ab57a2273c9/image.png" alt="">
<code>move_base</code>는 ROS1에서 자율주행을 담당하는 핵심 패키지이다. 위의 그림과 같이 하나의 거대한 <code>move_base</code> 블록 안에 모든 기능이 들어 있다.</p>
<ul>
<li>강한 결합 : <code>global_planner</code>, <code>local_planner</code>, <code>costmap, recovery_behaviors</code> 등 핵심 기능들이 하나의 노드 안에서 강하게 결합되어 돌아간다.</li>
<li>단일 장애점 : 내부의 특정 알고리즘 하나에 에러가 생기면 전체 네비게이션 노드가 멈출 수 있다.</li>
<li>경직된 제어 흐름 : 하드코딩된 유한 상태 머신(FSM)을 사용하기 때문에, 로봇의 주행 시나리오나 복구 동작을 커스텀하기가 매우 까다롭다.</li>
</ul>
<blockquote>
<p>FSM(Finite State Machine) : 시스템이 가질 수 있는 상태(State)의 개수가 유한하게 정해져 있고, 특정 조건이 만족되면 다른 상태로 넘어가는 작동 방식</p>
</blockquote>
<h3 id="2-nav2--모듈형modular-구조">2) Nav2 : 모듈형(Modular) 구조</h3>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/b10b0faa-bb03-4936-9cf7-9c511ba090bb/image.png" alt="">
Nav2 그림을 보면, 기능들이 독립적인 캡슐처럼 쪼개져 있는 것을 볼 수 있다.</p>
<ul>
<li>독립적인 Action Server : Planner, Controller, Behavior, Smoother가 각각 별도의 서버(노드)로 분리되었습니다. 서로 영향을 주지 않고 비동기적으로 통신한다.</li>
<li>BT Navigator : 최상단에 있는 BT Navigator Server가 오케스트레이터 역할을 한다.</li>
<li>강력한 Lifecycle 관리 : <code>Lifecycle Manager</code>가 시스템을 모니터링하며 각 서버의 상태를 체계적으로 관리한다.</li>
</ul>
<hr>
<h2 id="2-nav2란-무엇인가">2. Nav2란 무엇인가?</h2>
<p><strong>Navigation</strong>이란 로봇이 주어진 환경에서 <strong>장애물을 피해 목적지까지 최적의 경로를 찾아 이동하는 과정</strong>을 말한다.</p>
<p>이를 위해서는 다음과 같은 기능들이 필수적이다.</p>
<ol>
<li>Localization (위치 추정): 로봇이 지금 어디에 있는가?</li>
<li>Mapping (지도 작성): 주변 환경은 어떻게 생겼는가?</li>
<li>Path Planning (경로 계획): 어디로 가야 하는가?</li>
<li>Obstacle Avoidance (장애물 회피): 이동 중 충돌을 어떻게 피할 것인가?</li>
</ol>
<p>Nav2는 ROS2 환경에서 이 모든 기능을 수행할 수 있도록 여러 ROS 패키지들을 조합하여 만든 <strong>네비게이션 프레임워크</strong>이다.</p>
<hr>
<h2 id="3-nav2의-핵심-개념">3. Nav2의 핵심 개념</h2>
<p>Nav2 시스템을 이해하기 위해 반드시 알아야 할 세 가지 핵심 개념은 <strong>Action Server</strong>, <strong>Lifecycle Node</strong>, <strong>Behavior Tree</strong> 이다.</p>
<h3 id="31-action-server">3.1 Action Server</h3>
<p>네비게이션은 목표 지점까지 이동하는 데 시간이 오래 걸리는 작업이다. 기본적인 ROS Service는 요청 후 응답이 올 때까지 시스템이 차단(Blocking)되지만, <strong>Action</strong>은 장시간 실행되는 작업을 비동기적으로 처리하는 데 설계되었다.</p>
<p>예를 들어, Nav2에서는 최상위 행동 트리(BT) 네비게이터가 <code>MapsToPose.action</code>이라는 액션 메시지를 통해 다른 하위 액션 서버들과 통신하며 작업을 수행한다.</p>
<p>MapsToPose.action 파일 구조</p>
<ul>
<li>Goal: 목표 위치 설정 <pre><code class="language-text"># goal definition
geometry_msgs/PoseStamped pose
string behavior_tree</code></pre>
</li>
<li>Feedback: 작업이 진행되는 동안 서버가 클라이언트에게 주기적으로 보내는 현재 상태 정보<pre><code class="language-text"># feedback definition
geometry_msgs/PoseStamped current_pose
builtin_interfaces/Duration navigation_time
builtin_interfaces/Duration estimated_time_remaining
int16 number_of_recoveries
float32 distance_remaining
float32 position_tracking_error
float32 heading_tracking_error</code></pre>
</li>
<li>Result: 목표에 도달하거나 실패하여 작업이 종료되었을 때, 서버가 클라이언트에게 단 한 번 반환하는 최종 상태 <pre><code class="language-text"># result definition
uint16 NONE=0
uint16 UNKNOWN=9000
uint16 FAILED_TO_LOAD_BEHAVIOR_TREE=9001
uint16 TF_ERROR=9002
uint16 TIMEOUT=9003
uint16 error_code
string error_msg</code></pre>
</li>
</ul>
<h3 id="32-lifecycle-node-생명-주기-노드">3.2 Lifecycle Node (생명 주기 노드)</h3>
<p>ROS 2에서는 노드의 상태를 체계적으로 관리하기 위해 <strong>Lifecycle Node</strong> 개념을 도입했다. 일반적인 노드는 단순히 실행/종료되지만, <strong>Lifecycle Node</strong>는 여러 단계의 상태를 가지며 제어 가능하다.</p>
<p>Nav2의 모든 주요 서버는 <strong>Lifecycle Node</strong>로 구현되어 <code>nav2_lifecycle_manager</code>에 의해 관리된다.</p>
<p>상태 단계: <code>Unconfigured</code> → <code>Inactive</code> → <code>Active</code> → <code>Finalized</code> </p>
<p>장점: 시스템 시작 시 파라미터 로딩, 메모리 할당 등을 거쳐 필요한 노드만 정확한 시점에 <code>Active</code> 상태로 전환하여 실행 효율을 높이고 안정성을 보장한다. 충돌 시 시스템을 안전하게 다운시키는 역할도 한다.</p>
<p>아래는 파이썬으로 작성된 간단한 Lifecycle Node 예제이다.</p>
<pre><code class="language-python">from rclpy.lifecycle import LifecycleNode
from rclpy.lifecycle import State
from rclpy.lifecycle import TransitionCallbackReturn
import rclpy

class MyLifecycleNode(LifecycleNode):
    def __init__(self):
        super().__init__(&#39;my_lifecycle_node&#39;)
        self.get_logger().info(&quot;Lifecycle Node created.&quot;)

    def on_configure(self, state: State):
        self.get_logger().info(&quot;Configuring node...&quot;)
        return TransitionCallbackReturn.SUCCESS

    def on_activate(self, state: State):
        self.get_logger().info(&quot;Activating node...&quot;)
        return TransitionCallbackReturn.SUCCESS

    def on_deactivate(self, state: State):
        self.get_logger().info(&quot;Deactivating node...&quot;)
        return TransitionCallbackReturn.SUCCESS

    def on_cleanup(self, state: State):
        self.get_logger().info(&quot;Cleaning up node...&quot;)
        return TransitionCallbackReturn.SUCCESS

    def on_shutdown(self, state: State):
        self.get_logger().info(&quot;Shutting down node...&quot;)
        return TransitionCallbackReturn.SUCCESS

if __name__ == &#39;__main__&#39;:
    rclpy.init()
    node = MyLifecycleNode()
    rclpy.spin(node)
    rclpy.shutdown()</code></pre>
<p>예제 코드를 보면 <code>LifecycleNode</code>를 상속받아 <code>on_configure</code>, <code>on_activate</code> 등의 콜백 함수로 노드의 상태 전이를 세밀하게 제어한다. 이처럼 설정 단계와 실제 동작 단계를 엄격히 분리하게 되면, 무거운 리소스 할당이나 파라미터 로딩을 원하는 시점에 제어할 수 있어 시스템 초기화 시 노드 간의 실행 순서 꼬임을 원천적으로 방지할 수 있다. 뿐만 아니라, 특정 노드에 장애가 발생하더라도 시스템 전체를 강제 종료할 필요 없이 해당 노드만 안전하게 해제(on_cleanup)하고 재설정하여 복구할 수 있으므로, 복잡한 로봇 네비게이션 환경에서 메모리 관리의 효율성과 구동 안정성을 극대화할 수 있다.</p>
<h3 id="33-behavior-tree-행동-트리-bt">3.3 Behavior Tree (행동 트리, BT)</h3>
<p>Nav2의 의사결정 프로세스를 관리하는 가장 중요한 개념이다. <strong>Behavior Tree</strong>는 여러 동작(Node)을 나무가지처럼 계층적 자료 구조로 연결하여 로봇의 행동 흐름을 제어한다.</p>
<p>과거의 복잡한 FSM(Finite State Machine)을 대체하여, 수십 개의 상태 전환을 훨씬 단순하게 시각화하고 관리할 수 있다. 트리의 왼쪽 아래부터 오른쪽 순으로 탐색하며 의존성이 없는 노드들이 우선 실행되는 구조이다.</p>
<blockquote>
<p>FSM : 상태 기반의 동작 제어(단방향제어 전달)로 단순 작업에는 유용하지만 복잡한 작업에는 문제 발생</p>
</blockquote>
<p>Nav2는 <code>BehaviorTree.CPP V3</code> 라이브러리를 사용하여 기본 제공 노드 및 사용자가 직접 만든 Custom Node Plugin을 조합해 유연한 네비게이션 행동을 구축합니다. 이를 통해 장애물 회피, 복구 동작(Spin, Backup) 등을 조합하여 수행할 수 있습니다.</p>
<hr>
<h2 id="4-nav2-시스템-아키텍처-및-주요-서버-servers">4. Nav2 시스템 아키텍처 및 주요 서버 (Servers)</h2>
<p>Nav2는 각 네비게이션 핵심 기능을 실행하는 독립적인 서버(Node)들로 분리되어 동작한다. 행동 트리(BT)가 상황에 맞는 서버를 호출하여 작업을 처리한다.</p>
<h4 id="41-planner-server-전역-경로-계획">4.1 Planner Server (전역 경로 계획)</h4>
<p>로봇의 현재 위치에서 목표 지점까지 갈 수 있는 전역 경로(Global Path)를 생성한다. 전역 비용 맵(Global Costmap)과 센서 데이터를 기반으로 최적의 경로를 계산한다.</p>
<ul>
<li>Plugin: <code>nav2_navfn_planner/NavfnPlanner</code> (GridBased) 등이 사용된다.</li>
</ul>
<pre><code class="language-yaml"># yaml 예시
planner_server:
  ros__parameters:
    planner_plugins: [&quot;GridBased&quot;]
    GridBased:
      plugin: &quot;nav2_navfn_planner/NavfnPlanner&quot;</code></pre>
<h4 id="42-controller-server-지역-경로-제어">4.2 Controller Server (지역 경로 제어)</h4>
<p>전역 경로를 따라 이동할 수 있도록 로컬 경로(Local Path)를 제어하고 로봇의 속도 및 방향 명령(<code>cmd_vel</code>)을 생성한다. 실시간으로 장애물을 회피하며 로봇 속도를 조절한다.</p>
<ul>
<li>Plugin: <code>dwb_core::DWBLocalPlanner</code> (FollowPath) 등이 사용된다.</li>
</ul>
<pre><code class="language-yaml"># yaml 예시
controller_server:
  ros__parameters:
    controller_plugins: [&quot;FollowPath&quot;]
    FollowPath:
      plugin: &quot;dwb_core::DWBLocalPlanner&quot;</code></pre>
<h4 id="43-smoother-server-경로-부드럽게-하기">4.3 Smoother Server (경로 부드럽게 하기)</h4>
<p>Planner가 생성한 전역 경로의 급격한 회전이나 각진 부분을 부드러운 곡선 경로로 조정하여 로봇이 더 자연스럽게 이동하도록 만든다.</p>
<h4 id="44-behavior-server-복구-동작">4.4 Behavior Server (복구 동작)</h4>
<p>네비게이션 실패 시, 로봇이 스스로 문제를 해결하기 위한 복구 동작(Recovery Behaviors)을 실행한다. 장애물로 막혔을 때 제자리에서 회전(Spin)하거나 후진(BackUp)하여 공간을 확보한다. 비용이 큰 자원(costmap 등)에 접근하여 활용한다.</p>
<hr>
<h2 id="5-환경-표현-및-비용-지도-environmental-representation">5. 환경 표현 및 비용 지도 (Environmental Representation)</h2>
<p>Nav2는 로봇 주변 환경을 인식하고 표현하기 위해 Costmap(비용 지도)을 활용한다.</p>
<h4 id="51-costmap">5.1 Costmap</h4>
<p>로봇이 이동할 수 있는 안전 영역과 장애물 위치를 나타내는 <strong>2D 그리드 맵</strong>이다. 각 셀은 특정 비용(cost)을 가지며, 로봇은 비용이 낮은 곳을 따라 최적의 경로를 설정한다.</p>
<h4 id="52-global-vs-local-costmap">5.2 Global vs Local Costmap</h4>
<ol>
<li><p><strong>Global Costmap</strong> : 전체적인 경로 계획(Planner)을 위해 사용된다. 주로 정적인 장애물(벽, 건물 등)을 반영하며 <code>/map</code> 기반으로 업데이트 빈도가 낮다.</p>
</li>
<li><p><strong>Local Costmap</strong> : 실시간 장애물 회피 및 단기 경로 계획(Controller)을 위해 사용된다. 로봇 주변 장애물을 실시간 센서 데이터로 감지하여 업데이트하며 업데이트 빈도가 높다.</p>
</li>
</ol>
<h4 id="53-costmap-layer">5.3 Costmap Layer</h4>
<p>Costmap은 여러 개의 레이어를 조합하여 생성된다.</p>
<ul>
<li>Static Layer: <code>/map</code>에서 알려진 장애물(벽) 반영</li>
<li>Obstacle Layer: 2D 센서(LIDAR 등)로 실시간 장애물 감지</li>
<li>Voxel Layer: 3D 장애물 데이터(Point Cloud) 반영 (높이 정보 포함)</li>
<li>Inflation Layer: 장애물 주변 일정 거리만큼 안전 영역(안정 거리) 설정</li>
</ul>
<p>장애물 주변에는 Inflation 레이어에 의해 진한 하늘색(Global) 또는 보라색-빨간색(Local)으로 표현되는 고비용 구역이 설정된다. 로봇 반경이 크거나 높은 비용이 설정된 지역은 이동에 어려움을 겪을 수 있으므로 적절한 파라미터 튜닝이 필요하다.</p>
<h4 id="54-costmap-filter">5.4 Costmap Filter</h4>
<p>기존 Costmap에 특정 영역을 필터링하여 추가적인 제한을 적용할 수 있다.</p>
<ul>
<li>Keepout Zones (출입 금지 구역): 특정 지역의 비용을 매우 높게 설정하여 로봇이 절대 진입할 수 없도록 우회 경로를 생성하게 한다.</li>
<li>Speed Restriction Zones (속도 제한 구역): 특정 지역에서 로봇의 최대 속도를 제한한다.</li>
</ul>
<hr>
<h2 id="6-상태-추정-및-기준-standards">6. 상태 추정 및 기준 (Standards)</h2>
<p>네비게이션 시스템을 사용하기 위해서는 커뮤니티 표준(REP 105)을 준수하는 TF 트리 구성이 필수적이다.</p>
<blockquote>
<p>커뮤니티 표준(REP 105)는 좌표게 이름 짓기 및 연결 순서에 대한 표준 규칙이다.
<code>map</code> ➔ <code>odom</code> ➔ <code>base_link</code> ➔ <code>센서들</code></p>
</blockquote>
<h4 id="61-tf-transform">6.1 TF Transform</h4>
<ol>
<li>map → odom: 로봇이 지도상에 어디쯤 있는지</li>
<li>odom → base_link: 로봇의 바퀴가 얼마나 굴러가서 움직였는지</li>
<li>base_link ➔ 센서들: 로봇 몸통 어디에 라이다가 달려 있는지</li>
</ol>
<p>Nav2는 라이다뿐만 아니라 Vision이나 Depth 기반 위치 결정 시스템과도 함께 사용할 수 있다.</p>
<hr>
<h2 id="7-nav2-시스템-아키텍처-개요">7. Nav2 시스템 아키텍처 개요</h2>
<p>마지막으로 Nav2의 전체 아키텍처를 한눈에 정리해 보자.</p>
<ol>
<li>BT Navigator Server: 전체 네비게이션 과정을 관리하는 오케스트레이터이다. Behavior Tree를 사용하여 상황에 맞게 다른 서버들과 통신한다.</li>
<li>Planner, Controller, Smoother, Behavior Servers: 실제 네비게이션 핵심 연산을 수행하는 하위 서버들이다.</li>
<li>Costmap &amp; Footprint Subscriptions: 실시간 환경 정보를 받아 행동을 수정한다.</li>
<li>Velocity Smoother &amp; Collision Monitor: 로봇 속도를 부드럽게 조절하고 충돌을 모니터링하여 안전한 주행을 보장한다.</li>
<li>Lifecycle Manager: 시스템 전반의 상태를 확인하고 관리하여 안정적인 동작을 보장한다.</li>
</ol>
<hr>
<h2 id="8-why">8. why?</h2>
<p>Nav2를 사용하는 이유는 명확하다.</p>
<ul>
<li>ROS 2 기반: 분산 처리 및 실시간 통신 지원으로 더 나은 성능과 안정성</li>
<li>유연성: Behavior Tree를 통한 유연한 제어 구조와 모듈형 설계로 커스터마이징 용이</li>
<li>확장성: 다양한 하드웨어 및 센서 지원과 사용자가 모듈을 쉽게 수정/확장 가능</li>
<li>커뮤니티 지원: 활발한 오픈소스 커뮤니티의 지속적인 업데이트와 지원</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[3D Point Cloud Processing and Learning for Autonomous Driving 간단 리뷰]]></title>
            <link>https://velog.io/@dongmin_1999/3D-Point-Cloud-Processing-and-Learning-for-Autonomous-Driving-%EA%B0%84%EB%8B%A8-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@dongmin_1999/3D-Point-Cloud-Processing-and-Learning-for-Autonomous-Driving-%EA%B0%84%EB%8B%A8-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Sat, 16 May 2026 07:16:28 GMT</pubDate>
            <description><![CDATA[<p><strong>3D Point Cloud Processing and Learning for Autonomous Driving</strong> 논문에서 설명하는 LiDAR 기반의 Point CLoud를 어떻게 처리하는지 알아보고자 합니다.</p>
<p>논문 “3D Point Cloud Processing and Learning for Autonomous Driving” 는 자율주행에서 사용하는 LiDAR 기반 포인트 클라우드(Point Cloud)를 어떻게 처리하고 학습하는지 전체적으로 정리한 논문입니다.
즉, 새로운 모델 하나를 제안하는 논문이라기보다 “자율주행에서 3D 포인트 클라우드를 어떻게 사용하는가?” 를 전체적으로 설명하는 Survey(리뷰) 논문이라고 보면 됩니다.</p>
<hr>
<p>자율주행 자동차는 주변 환경을 이해하기 위해 여러 센서를 사용하는데 
대표적으로 <strong>Camera</strong>, <strong>Radar</strong>, <strong>GPS</strong>, <strong>IMU</strong>, <strong>LiDAR</strong> 가 있습니다.
그중 <strong>LiDAR</strong>는 주변 공간을 3차원 점 데이터로 표현하는데 이렇게 얻은 데이터가 바로 <strong>Point Cloud</strong> 입니다.</p>
<hr>
<h2 id="point-cloud란">Point Cloud란?</h2>
<p>Point Cloud는 공간상의 점들의 집합입니다.
각 점은 보통 $(x,y,z)$ 좌표를 가지며 추가적으로 intensity(반사 강도), color, timestamp 등을 포함할 수도 있습니다. </p>
<p>예를 들어 LiDAR가 자동차 주변을 스캔하면 도로, 차량, 보행자, 건물, 나무 가 수십만 개의 점으로 표현됩니다.</p>
<hr>
<h2 id="왜-point-cloud가-중요한가">왜 Point Cloud가 중요한가?</h2>
<p>카메라는 2D 이미지라 깊이(depth)를 정확히 알기 어렵지만, LiDAR는 거리 측정이 정확하고 3D 구조를 직접 얻을 수 있어서 자율주행에서 매우 중요합니다.</p>
<p>특히 <strong>객체 거리 계산</strong>과 <strong>충돌 회피</strong>, <strong>SLAM</strong>, <strong>Localization</strong>, <strong>3D Object Detection</strong>에 핵심적으로 사용됩니다.</p>
<hr>
<h2 id="point-cloud의-특징">Point Cloud의 특징</h2>
<p>논문에서 가장 중요하게 말하는 부분 중 하나로, Point Cloud는 이미지와 달리</p>
<ul>
<li>불규칙함(unstructured)</li>
<li>순서가 없음(permutation invariant)</li>
<li>밀도가 일정하지 않음</li>
<li>희소함(sparse)</li>
</ul>
<p>이라는 특징이 있습니다. 즉 이미지처럼 <strong>픽셀 격자</strong> 형태가 아니기 때문에 CNN을 적용하기 어렵습니다.</p>
<hr>
<h2 id="point-cloud-표현-방식">Point Cloud 표현 방식</h2>
<p>논문은 Point Cloud를 여러 방식으로 표현한다고 설명합니다.</p>
<h3 id="1-voxel-기반">(1) Voxel 기반</h3>
<p>3D 공간을 작은 큐브(voxel)로 나눕니다. </p>
<ul>
<li>장점: CNN 적용 가능하다.</li>
<li>단점: 메모리를 많이 사용하며 계산량 크다.</li>
</ul>
<h3 id="2-birds-eye-viewbev">(2) Bird’s Eye View(BEV)</h3>
<p>대표적인 자율주행 방식으로 차량 주변 환경을 위에서 내려다본 2D 형태로 변환한 표현합니다.
차선이나 차량 위치, 도로 구조를 보기 좋습니다.</p>
<h3 id="3-range-view">(3) Range View</h3>
<p>LiDAR 데이터를 2D 이미지처럼 펼치는 방식으로 실시간성이 좋아서 실시간 객체 탐지와 Segmentation에서 주로 사용됩니다.</p>
<h3 id="4-raw-point-방식">(4) Raw Point 방식</h3>
<p>센서가 측정한 점(포인트) 데이터를 그대로 입력하여 처리하는 방식, 즉 Point Cloud의 구조를 직접 학습합니다.</p>
<p>대표 모델</p>
<ul>
<li>PointNet</li>
<li>PointNet++</li>
</ul>
<hr>
<h2 id="3-point-cloud-processing">3. Point Cloud Processing</h2>
<p>논문은 전통적인 처리 기법으로 센서로 얻은 Point Cloud 데이터를 전처리합니다.</p>
<p>대표 작업으로는 
<strong>Registration</strong>, <strong>Downsampling</strong>, <strong>Segmetation</strong>, <strong>Denoising</strong> 이 있다.</p>
<p><strong>Registration</strong> : 여러 시점의 Point Cloud를 정렬
<strong>Downsampling</strong> : 점 개수를 줄여서 계산량을 감소
<strong>Segmentation</strong> : Point들을 의미별로 분리
<strong>Denoising</strong> : 노이즈 제거</p>
<hr>
<h2 id="4-딥러닝-기반-learning">4. 딥러닝 기반 Learning</h2>
<p>논문의 핵심 파트 중 하나로, Point Cloud에 딥러닝 적용 방법을 설명합니다.</p>
<p>대표 모델으로는 <strong>PointNet</strong>, <strong>PointNet++</strong>, <strong>VoxelNet</strong>, <strong>SECOND</strong> 가 있다.</p>
<p>PointNet : 포인트를 입력받는 최초의 모델
PointNet++ : PointNet 개선 버전
VoxelNet : Voxel 기반 3D Detection 모델
SECOND : 실시간 3D 객체 탐지 모델</p>
<hr>
<h2 id="5-자율주행-응용">5. 자율주행 응용</h2>
<p>논문은 Point Cloud가 자율주행의 핵심 모듈에 어떻게 사용되는지 설명합니다.</p>
<h3 id="1-hd-map-creation">(1) HD Map Creation</h3>
<p>HD Map(High Definition Map)은 일반적인 내비게이션 지도보다 훨씬 정밀한 고정밀 지도입니다.
자율주행 차량은 LiDAR를 이용해 주변 환경을 스캔하고, 이를 기반으로 도로의 구조를 3차원 Point Cloud 형태로 저장합니다.</p>
<p>HD Map에는 다음과 같은 정보가 포함됩니다.</p>
<ul>
<li>차선 위치 및 형태</li>
<li>연석(curb)</li>
<li>신호등 및 표지판 위치</li>
<li>도로 경계</li>
<li>건물 및 고정 구조물</li>
</ul>
<h3 id="2-localization">(2) Localization</h3>
<p>Localization은 차량이 현재 어디에 위치해 있는지를 추정하는 기술입니다. 자율주행 차량은 주행 중 실시간으로 LiDAR Point Cloud를 생성하고, 이를 미리 구축된 HD Map과 비교하여 자신의 위치를 계산합니다.</p>
<p>즉,</p>
<pre><code class="language-text">현재 LiDAR 스캔 데이터
VS
HD Map의 Point Cloud 데이터</code></pre>
<p>를 정합(Registration/Matching)하는 과정입니다.</p>
<p>대표적으로:</p>
<ul>
<li>ICP(Iterative Closest Point)</li>
<li>NDT(Normal Distributions Transform)
와 같은 알고리즘이 사용됩니다.</li>
</ul>
<p>Localization은 GPS 오차가 발생하기 쉬운 터널과 도심 빌딩 숲, 지하주차장 등의 환경에서 특히 중요합니다.</p>
<h3 id="3-perception">(3) Perception</h3>
<p>Perception은 차량 주변 환경을 인식하고 이해하는 과정입니다.
LiDAR Point Cloud를 기반으로 주변 객체의 위치와 종류를 분석합니다.</p>
<ol>
<li><p><strong>3D Object Detection</strong>
Point CLoud에서 차량, 보행자, 장애물 등을 탐지하는 기술입니다.</p>
<p>기존 2D 이미지 기반 객체 탐지와 달리, LiDAR는 거리와 3차원 위치 정보를 직접 제공하기 때문에 객체의 실제 위치와 크기를 더욱 정확하게 추정할 수 있습니다.
대표 모델 : <strong>PointNet, VoxelNet, SECOND</strong></p>
</li>
<li><p><strong>Semantic Segmentation</strong>
Point Cloud의 각 점(Point)에 의미(label)를 부여하는 작업이다.
예를 들어 도로, 건물, 차량, 보행자 등으로 Point를 분류합니다.
이를 통해 차량은 주행 가능한 영역과 장애물을 구분할 수 있다.</p>
</li>
<li><p><strong>Tracking</strong>
이전 프레임과 현재 프레임의 객체를 연결하여 움직임을 추적하는 기술입니다.
보행자의 이동 방향을 예측하거나 앞 차량의 속도를 추정하는 등 Traking은 자율주행 차량이 주변 객체의 미래 움직임을 예측하고 안전하게 경로를 계획하는 데 매우 중요한 역할을 합니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[자율주행과 산업용 로봇]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%9E%90%EC%9C%A8%EC%A3%BC%ED%96%89%EA%B3%BC-%EC%82%B0%EC%97%85%EC%9A%A9-%EB%A1%9C%EB%B4%87</link>
            <guid>https://velog.io/@dongmin_1999/%EC%9E%90%EC%9C%A8%EC%A3%BC%ED%96%89%EA%B3%BC-%EC%82%B0%EC%97%85%EC%9A%A9-%EB%A1%9C%EB%B4%87</guid>
            <pubDate>Sat, 16 May 2026 01:11:50 GMT</pubDate>
            <description><![CDATA[<p>부트캠프에 참여하면서 파이썬과 컴퓨터비전을 배웠고 지금은 ROS2에 대해 배우고 있으며 ROS2를 마치면 데브옵스를 간단하게 배우고 바로 실무 프로젝트에 들어간다.
이번 포스팅에서는 실무 프로젝트에서 다루게 될 자율 주행과 산업용 로봇에 대해 예습하고자 한다.</p>
<hr>
<h1 id="1-자율주행-로봇-시스템-환경을-이해하고-스스로-판단하는-지능">1. 자율주행 로봇 시스템: 환경을 이해하고 스스로 판단하는 지능</h1>
<p>자율주행은 단순히 로봇이 정해진 목적지까지 이동하는 것을 넘어, 복잡하고 동적인 환경 속에서 실시간으로 상황을 인지하고 최적의 판단을 내리는 종합적인 소프트웨어 아키텍처이다. 실제로 자율주행 시스템을 개발할때 주요 과제들은 다음과 같다.</p>
<h3 id="🎯-비전-인지-vision-perception">🎯 비전 인지 (Vision Perception)</h3>
<p>로봇의 눈 역할을 담당하는 기초적이고 핵심적인 단계이다.</p>
<ul>
<li><p><strong>AI Vision 객체 인식</strong>: 2차원 카메라 데이터에 OpenCV와 딥러닝 기반의 객체 탐지 모델을 적용하여 주행 경로 상의 사람, 장애물, 특정 표식 등을 실시간으로 식별한다.</p>
</li>
<li><p><strong>Point Cloud 3D 공간 분석</strong>: 2D 이미지의 한계를 극복하기 위해 라이다(LiDAR)나 뎁스 카메라를 활용한다. 수많은 점들의 집합인 3D Point Cloud 데이터를 분석하여, 장애물과의 정확한 거리, 부피, 그리고 전반적인 공간의 형태를 입체적으로 파악한다.</p>
<blockquote>
<p>💡 3D Point Cloud Processing and Learning for Autonomous Driving 논문</p>
</blockquote>
<h3 id="🗺️-자율-주행-autonomous-driving">🗺️ 자율 주행 (Autonomous Driving)</h3>
</li>
</ul>
<p>인지한 데이터를 바탕으로 로봇의 뇌와 발이 유기적으로 움직이는 과정이다.</p>
<ul>
<li><p><strong>SLAM 지도 생성</strong>: 미지의 공간에 로봇을 투입하여 실시간으로 주변 환경을 스캔하고 정밀한 환경 지도를 작성함과 동시에 로봇의 현재 위치를 추정한다.</p>
</li>
<li><p><strong>경로 계획 및 Navigation 튜닝</strong>: 작성된 지도 위에서 목적지까지 이동하기 위한 전역 경로(Global Path)를 생성하고, 주행 중 갑자기 나타나는 동적 장애물을 부드럽게 회피하는 지역 경로(Local Path)를 생성한다. 실제 주행 환경에 맞춰 로봇이 안정적으로 주행할 수 있도록 ROS2 NAV2 스택의 다양한 파라미터를 세밀하게 튜닝하는 고난도 작업이 요구된다.</p>
<blockquote>
<p><strong>Navigation Stack</strong>(<strong>NAV2</strong>) : 로봇이 주어진 지도 상에서 <strong>스스로 위치를 추정하고, 경로를 계획하고, 목표까지 이동</strong>하도록 하는 ROS2 패키지
NAV2는 AMCL, Planner ,Controller, Costmap, BT Navigator 등 여러 노드를 가지는데 특히 <strong>Controller</strong>과 <strong>Costmap</strong>, <strong>AMCL</strong>의 파라미터를 튜닝하는 것이 자율주행의 성능을 크게 좌우한다. </p>
</blockquote>
</li>
</ul>
<h3 id="🤝-로봇-협업-robot-collaboration">🤝 로봇 협업 (Robot Collaboration)</h3>
<p>단일 로봇의 한계를 넘어, 여러 대의 로봇(Multi-Robot System)이 유기적으로 상호작용하는 고도화된 단계이다.</p>
<ul>
<li><p>ROS2의 강력한 통신 미들웨어(DDS)를 활용해 각 로봇의 현재 위치, 배터리 상태, 센서 데이터 등을 실시간으로 공유한다.</p>
</li>
<li><p>이러한 상태 공유를 바탕으로 좁은 통로에서 서로 동선이 겹치지 않게 교차 주행을 하거나, 단일 로봇으로는 처리하기 힘든 복잡한 협력 미션을 자동으로 분담하고 수행하도록 알고리즘을 설계한다.</p>
</li>
</ul>
<h3 id="🖥️-관제-시스템-control-system">🖥️ 관제 시스템 (Control System)</h3>
<p>시스템 관리자가 로봇들의 상태를 한눈에 파악하고 통제할 수 있는 &#39;중앙 통제실&#39; 역할을 한다.</p>
<ul>
<li><p>다수의 로봇이 보내는 방대한 데이터를 수집하여 웹 기반 대시보드나 전용 GUI 환경에 시각화한다.</p>
</li>
<li><p>로봇의 주행 상태, 에러 로그, 카메라 스트리밍 화면 등을 실시간으로 모니터링하며, 비상 상황(예: 경로 이탈, 장애물 고립 등) 발생 시 관리자가 즉각적으로 개입하여 로봇을 원격으로 안전하게 제어할 수 있는 직관적인 인터페이스를 구축해야 한다.</p>
</li>
</ul>
<h1 id="2-산업용-로봇-시스템-한-치의-오차도-허용하지-않는-정밀함과-신뢰성">2. 산업용 로봇 시스템: 한 치의 오차도 허용하지 않는 정밀함과 신뢰성</h1>
<p>산업용 로봇(다관절 로봇 암, AGV 등)은 스마트 팩토리나 물류 현장에서 사람을 대신해 위험하거나 반복적인 작업을 수행합니다. 공정의 효율을 극대화하고 작업자의 안전을 보장하기 위해, 실무 프로젝트에서는 다음과 같은 과제들을 해결해야 한다.</p>
<h3 id="🎯-정밀-제어-및-기구학-precision-control--kinematics">🎯 정밀 제어 및 기구학 (Precision Control &amp; Kinematics)</h3>
<p><strong>URDF 로봇 모델링 및 TF(Transform) 좌표계 동기화</strong>
산업 현장에서는 밀리미터 단위의 오차도 불량으로 이어진다.</p>
<ul>
<li><p>로봇 모델링: URDF(Unified Robot Description Format)를 이용해 로봇의 각 관절(Joint)과 뼈대(Link)의 물리적 속성(길이, 무게 중심 등)을 3D 소프트웨어 상에 완벽하게 구현해야 한다.</p>
</li>
<li><p>TF 좌표계 제어: 로봇 팔이 움직일 때마다 복잡하게 얽힌 수많은 관절의 좌표계를 실시간으로 계산하여, 로봇의 끝단(End-effector)이 목표한 위치와 각도로 정확히 이동하도록 제어하는 수학적 기구학 연산이 필수적이다.</p>
</li>
</ul>
<h3 id="🛡️-디지털-트윈-및-사전-검증-digital-twin--pre-validation">🛡️ 디지털 트윈 및 사전 검증 (Digital Twin &amp; Pre-validation)</h3>
<p><strong>Gazebo 기반 물리 엔진 시뮬레이션 및 충돌 회피 검증</strong>
실제 산업용 장비는 수천만 원을 호가하며, 오작동 시 대형 사고로 이어질 수 있다.</p>
<ul>
<li><p>실제 하드웨어에 코드를 적용하기 전, 중력, 마찰력, 관성 등 실제 물리 법칙이 적용된 Gazebo 시뮬레이션 환경에서 수백 번의 가상 테스트를 거쳐야 한다.</p>
</li>
<li><p>이 가상 공간에서 로봇이 주변 구조물이나 다른 로봇과 부딪히지 않도록 충돌 회피 알고리즘을 철저하게 검증하여 위험 요소를 사전에 차단한다.</p>
</li>
</ul>
<h3 id="⚙️-공정-제어-및-비동기-통신-process-control">⚙️ 공정 제어 및 비동기 통신 (Process Control)</h3>
<p><strong>Action 프로그래밍을 활용한 장시간 작업 및 피드백 루프 구축</strong>
&quot;A 라인에서 부품을 집어 B 라인의 상자에 조립하라&quot;와 같은 명령은 실행에 긴 시간이 소요된다.</p>
<ul>
<li><p>단순한 일회성 통신이 아니라, 로봇이 현재 작업을 몇 퍼센트 진행했는지 관리자에게 지속해서 피드백을 주어야 한다.</p>
</li>
<li><p>돌발 상황 발생 시 진행 중인 공정을 즉각적이고 안전하게 취소(Cancel)할 수 있도록 ROS2의 Action 비동기 통신 아키텍처를 정교하게 설계해야 한다.</p>
</li>
</ul>
<h3 id="🏭-시스템-고가용성과-에러-핸들링-high-availability">🏭 시스템 고가용성과 에러 핸들링 (High Availability)</h3>
<p><strong>Lifecycle 및 Component를 통한 무중단 운영과 Fail-Safe</strong></p>
<p>공장 라인이 멈추는 것은 곧 막대한 금전적 손실을 의미하므로, 24시간 멈추지 않는 시스템 안정성이 요구된다.</p>
<ul>
<li><p>Lifecycle 노드 관리: 시스템 노드의 상태(설정, 대기, 활성화, 오류 등)를 엄격하게 제어하여, 예상치 못한 에러가 발생해도 전체 시스템이 다운되지 않고 안전한 상태(Fail-Safe)로 전환되도록 설계한다.</p>
</li>
<li><p>Component 기반 자원 최적화: 다수의 로봇 제어 프로세스를 효율적으로 메모리에 적재하여 연산 자원 소모를 줄이고 통신 속도를 극대화한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ROS2]]></title>
            <link>https://velog.io/@dongmin_1999/ROS-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@dongmin_1999/ROS-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Sat, 02 May 2026 07:51:46 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/dongmin_1999/post/8c30d3d0-91cf-4763-849a-6463725719c4/image.png" width="100%" height="100%">

<p>ROS2를 다뤄보기 전에 ROS 개요와 기본 구조에 대해 알아보고자 한다.</p>
<hr>
<h1 id="1-🤖-ros2-란">1. 🤖 ROS2 란?</h1>
<h4 id="정의-및-역사">정의 및 역사</h4>
<p>ROS(Robot Operating System)는 로봇 소프트웨어를 개발, 관리, 배포하기 위한 도구와 라이브러리를 제공하는 개방형 프레임워크이다. 2007년 스탠포드 인공지능 연구소에서 시작되어 Willow Garage에서 발전되었으며, 현재는 로봇 개발의 표준 플랫폼으로 자리 잡았다.</p>
<h4 id="ros1-vs-ros2-왜-ros2-인가">ROS1 vs ROS2: 왜 ROS2 인가?</h4>
<p>ROS 아키텍처를 보면 알 수 있다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/2a04133a-817b-4531-95eb-52a336c65c9b/image.png" alt=""></p>
<p>2017년에 출시된 ROS2는 기존 ROS1의 한계를 극복하고 현대 로봇 시스템의 요구사항을 반영하였다.</p>
<ul>
<li>분산 처리 및 실시간 운영: 멀티 로봇 시스템 및 실시간 제어 성능 강화<ul>
<li>ROS1에서는 모든 노드가 통신하기 위해 반드시 Master(roscore)를 거쳐야 했으나,
ROS2는 Master 없이 독립적인 구동이 가능하다.</li>
</ul>
</li>
<li>DDS(Data Distribution Service) 도입: 산업 표준 통신 방식을 채택하여 보안 및 안정성 확보<ul>
<li>노드들이 서로의 존재를 스스로 발견하고 직접 데이터를 주고받는 분산 구조</li>
</ul>
</li>
<li>멀티 플랫폼 지원: Linux 외에도 Windows, macOS 등 다양한 환경 지원</li>
<li>실시간 운영(Real-time)이 가능: 엄격한 시간 제약이 있는 환경에서도 안정적인 로봇 제어 지원.</li>
</ul>
<hr>
<h1 id="2-📦-ros2의-핵심-패키지-기반-모듈화">2. 📦 ROS2의 핵심: 패키지 기반 모듈화</h1>
<p>로봇 개발은 기구 설계, 임베디드, 비전, 시뮬레이션 등 다양한 분야의 작업이 동시에 수행되어야 하는 복잡한 영역이다. ROS2는 이러한 복잡한 작업들을 패키지 단위로 모듈화하여 관리함으로써 개발의 효율성을 극대화한다.</p>
<p><strong>패키지(Package)와 워크스페이스(Workspace)</strong> </p>
<ul>
<li><p>패키지(Package): ROS2 소프트웨어의 최소 빌드 단위이다. 하나의 패키지 안에는 특정 기능을 수행하는 코드(C++ 또는 Python), 라이브러리, 의존성 정보가 담긴 설정 파일(package.xml, CMakeLists.txt 등)이 포함된다.</p>
</li>
<li><p>워크스페이스(Workspace): 여러 패키지들을 한데 모아두고 colcon 빌드 시스템을 통해 독립적으로 개발하고 빌드하는 작업 공간이다.</p>
</li>
<li><p>모듈화의 장점: 카메라 센서 제어 패키지, 자율 주행 알고리즘 패키지 등 기능별로 분리하여 개발하면 다른 로봇이나 프로젝트에서도 해당 패키지를 쉽게 가져다 재사용(Reusability)할 수 있다.</p>
</li>
</ul>
<h1 id="3-📡-ros2의-기본-통신-구조-그래프-아키텍처">3. 📡 ROS2의 기본 통신 구조 (그래프 아키텍처)</h1>
<p>ROS2는 각자의 역할을 가진 독립적인 노드(Node) 들이 서로 데이터를 주고받는 네트워크 그래프 형태로 동작한다.</p>
<ol>
<li>노드 (Node)</li>
</ol>
<ul>
<li>ROS2에서 실행되는 최소 단위의 프로세스이다. 예를 들어 &#39;라이다(LiDAR) 센서 값을 읽는 노드&#39;, &#39;바퀴 모터를 제어하는 노드&#39; 등으로 역할을 잘게 쪼개어 구성한다.</li>
</ul>
<ol start="2">
<li>토픽 (Topic)</li>
</ol>
<ul>
<li>단방향 비동기 통신 방식이다. 센서 데이터처럼 지속적으로 스트리밍되어야 하는 데이터에 적합하다.</li>
<li>Publisher(발행자) 가 데이터를 특정 토픽 이름으로 계속 보내면, Subscriber(구독자) 가 그 토픽을 구독하여 데이터를 받아 처리한다.</li>
</ul>
<ol start="3">
<li>서비스 (Service)</li>
</ol>
<ul>
<li>양방향 동기 통신 방식이다.</li>
<li>Client(클라이언트) 가 특정 작업을 요청(Request)하면, Server(서버) 가 해당 작업을 처리한 후 응답(Response)을 반환한다. (예: 로봇 팔에 물건을 집으라는 단발성 명령)</li>
</ul>
<ol start="4">
<li>액션 (Action)</li>
</ol>
<ul>
<li>작업 완료까지 시간이 오래 걸리는 경우에 사용하는 비동기 양방향 통신이다.</li>
<li>목표(Goal)를 전달하면, 작업 도중 중간 진행 상황(Feedback)을 지속적으로 받을 수 있고, 완료 시 최종 결과(Result)를 반환받는다. (예: 목적지까지 자율주행 이동 명령)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[YOLOv8]]></title>
            <link>https://velog.io/@dongmin_1999/YOLOv8</link>
            <guid>https://velog.io/@dongmin_1999/YOLOv8</guid>
            <pubDate>Sun, 12 Apr 2026 07:54:33 GMT</pubDate>
            <description><![CDATA[<h2 id="yolo란">YOLO란?</h2>
<p>YOLO는 컴퓨터 비전 분야에서 객체의 위치와 객체가 무엇인지를 빠르고 정확하게 찾아내는 인공지능 모델이다.
기존에는 객체가 있을 만한 위치를 먼저 찾고, 이후에 그 물체가 무엇인지 분류하는 두 단계를 거치는 2-Stage 방식이 주를 이루었다. 하지만 YOLO는 이미지 전체를 격자 단위로 단 한 번만 분석하여 객체의 위치와 클래스를 동시에 예측하는 1-Stage 방식을 채택함으로써, 실시간 비디오 분석이 가능할 정도로 처리 속도를 비약적으로 향상시킨 혁신적인 아키텍처이다.</p>
<h2 id="yolov8">YOLOv8</h2>
<p>YOLO는 첫 등장 이후 전 세계 수많은 연구자들에 의해 발전해 왔다. 그 수많은 YOLO 버전 중에서도 2023년에 <strong>Ultralytics에서 발표한 YOLOv8</strong>은 속도와 정확도, 그리고 사용 편의성 면에서 현재 가장 완성도 높은 모델로 평가받고 있다.</p>
<h2 id="📌-yolov8-아키텍처-전체-구조">📌 YOLOv8 아키텍처 전체 구조</h2>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/78dfe408-d6b2-4cc0-af04-7359cea667f2/image.png" alt=""></p>
<p>YOLO v8의 네트워크 구조는 Input image $\rightarrow$ Backbone $\rightarrow$ Neck $\rightarrow$ Head 의 4단계 흐름을 거친다.</p>
<ol>
<li><p>Input Image
모델이 처리할 입력 데이터의 형태이다. <code>[B, 3, 640, 640]</code>은 각각 <code>[배치 사이즈, 채널 수, 너비, 높이]</code>를 의미한다.</p>
</li>
<li><p>Backbone
이미지에서 시각적으로 의미 있는 특징을 추출한다.</p>
<ul>
<li>구조: stem 계층을 시작으로 여러 단계(Stage)를 거치며 특징 맵을 축소하고 차원을 압축한다. 마지막 단계에서는 SPPF 모듈을 통과하여 다양한 크기의 수용 영역(Receptive field) 정보를 빠르고 효율적으로 취합한다.</li>
<li>출력: 이 과정을 거쳐 다양한 해상도를 가진 특징 맵인 P3, P4, P5가 만들어지며, 이는 다음 단계인 넥(Neck)으로 전달된다.</li>
</ul>
</li>
<li><p>넥 (Neck): 다중 스케일 특징 융합</p>
<ul>
<li><p>역할: 백본에서 추출된 다양한 크기의 특징 맵들을 서로 융합하여, 크기가 아주 크거나 작은 객체들도 모두 잘 탐지할 수 있도록 정보의 품질을 높여준다.</p>
</li>
<li><p>기술: <strong>FPN</strong>과 <strong>PAN</strong> 구조를 결합해 사용한다. 이를 통해 상위 계층의 깊은 의미(Semantic) 정보와 하위 계층의 세밀한 위치(Spatial) 정보를 양방향으로 효과적으로 교환한다.</p>
</li>
<li><p>출력: 융합이 완료된 특징 맵인 N3, N4, N5가 최종 예측을 위해 헤드(Head)로 넘어간다.</p>
</li>
</ul>
</li>
<li><p>헤드 (Detect Head): 클래스 및 박스 좌표 예측</p>
<ul>
<li><p>역할: 넥에서 전달받은 융합 특징 맵을 바탕으로 최종적인 객체의 종류와 위치를 예측한다.</p>
</li>
<li><p>구조: YOLOv8은 분류 작업과 위치 추정 작업을 나누어 처리하는 Decoupled Head 방식을 채택했다.</p>
<ul>
<li><p>cls 브랜치 (Classification): 해당 객체가 어떤 클래스인지(무엇인지) 예측한다.</p>
<ul>
<li>reg 브랜치 (Regression): 객체가 정확히 어디에 있는지 바운딩 박스 좌표를 예측한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>최종 후처리 (DFL 디코딩 + NMS)</p>
<ul>
<li><p>헤드의 예측 결과들을 다듬어 깔끔한 최종 탐지 결과를 도출한다.</p>
</li>
<li><p>DFL(Distribution Focal Loss)을 통해 바운딩 박스의 경계값을 더 정밀하게 조정(디코딩)하고, NMS(Non-Maximum Suppression) 알고리즘을 적용하여 한 객체에 여러 개 겹쳐 있는 예측 박스들 중 가장 정확도 높은 하나만 남기고 중복을 제거한다.</p>
</li>
</ul>
</li>
</ol>
<hr>
<h2 id="구성">구성</h2>
<pre><code class="language-python">main.py              # Ultralytics로 웹캠 실시간 탐지 (기준점)
yolov8_custom.py     # YOLOv8n 아키텍처 직접 설계
convert_weights.py   # Ultralytics 가중치 → 커스텀 모델 이식</code></pre>
<hr>
<h2 id="왜-직접-설계하는가">왜 직접 설계하는가</h2>
<p><code>main.py</code>의 핵심 코드는 단 2줄이다.                                         </p>
<pre><code class="language-python">from ultralytics import YOLO
model = YOLO(&quot;yolov8n.pt&quot;)
results = model(frame)</code></pre>
<p>Ultralytics는 오픈소스이기 때문에 내부 코드를 열어볼 수 있다. 하지만 수십 개의 모델과 태스크를 지원하다 보니 추상화 레이어가 매우 많고, 특정 레이어 하나를 찾으려면 여러 파일을 타고 들어가야 한다.
라이브러리 구조 전체를 파악하지 않으면 다음 작업들이 어렵다.                 </p>
<ul>
<li>채널 수 변경</li>
<li>레이어 수 조정</li>
<li>활성화 함수 교체</li>
<li>중간 레이어 출력 추출</li>
<li>클래스 수 변경 후 재학습</li>
</ul>
<p>따라서 각 블록이들이 왜 존재하고 어떻게 연결되는지 이해하기 위해 아키텍처를 직접 설계해보았다.</p>
<hr>
<h2 id="1-conv--가장-기본-단위">1. Conv — 가장 기본 단위</h2>
<p>필터(kernel)를 통해 이미지에 특정 패턴이 있는지 감지하는 연산                               </p>
<pre><code class="language-python">class Conv(nn.Module):
  def __init__(self, in_c, out_c, k=1, s=1, p=None):
      super().__init__() 
      p = k // 2 if p is None else p
      self.conv = nn.Conv2d(in_c, out_c, k, s, p, bias=False)           
      self.bn   = nn.BatchNorm2d(out_c)
      self.act  = nn.SiLU(inplace=True)
  def forward(self, x):
      return self.act(self.bn(self.conv(x)))</code></pre>
<p>3가지 연산을 순서대로 실행한다.</p>
<ul>
<li>Conv2d     → 필터로 특징 추출 (학습되는 가중치)</li>
<li>BatchNorm  → 값 범위 정규화  (학습 안정화)</li>
<li>SiLU       → 비선형 활성화   (복잡한 패턴 학습)</li>
</ul>
<p>파라미터 k(kernel size)에 따라 수용 영역이 달라진다.</p>
<ul>
<li>k=1 → 채널 수만 조절, 해상도 변화 없음</li>
<li>k=3 → 3×3 범위의 특징 추출 (YOLOv8에서 가장 많이 사용) </li>
</ul>
<hr>
<h2 id="2-c2f--bottleneck--다채로운-특징의-보존과-융합">2. C2f + Bottleneck — 다채로운 특징의 보존과 융합</h2>
<p>단순한 <code>Conv</code> 레이어가 이미지에서 특징(Feature)을 뽑아내는 역할을 한다면, <code>C2f</code> 레이어는 추출한 특징을 다양한 깊이(Depth)와 시각으로 분석하여 정보의 손실 없이 보존하는 역할을 한다.    </p>
<pre><code class="language-python">class Bottleneck(nn.Module):
    &quot;&quot;&quot;
    잔차 연결(Residual Connection)이 있는 병목 블록
    3x3 Conv → 3x3 Conv, 입출력 채널이 같으면 입력을 더함
    &quot;&quot;&quot;
    def __init__(self, in_c, out_c, shortcut=True, e=0.5):
        super().__init__()
        hidden = int(out_c * e)
        self.cv1 = Conv(in_c, hidden, 3, 1)
        self.cv2 = Conv(hidden, out_c, 3, 1)
        self.add = shortcut and (in_c == out_c)

    def forward(self, x):
        out = self.cv2(self.cv1(x))
        return x + out if self.add else out

class C2f(nn.Module):
    def __init__(self, in_c, out_c, n=1, shortcut=True, e=0.5):
        super().__init__()
        self.hidden = int(out_c * e)
        self.cv1 = Conv(in_c, 2 * self.hidden, 1, 1)
        self.cv2 = Conv((2 + n) * self.hidden, out_c, 1, 1)
        self.bottlenecks = nn.ModuleList(
            [Bottleneck(self.hidden, self.hidden, shortcut, e=1.0) for _ in range(n)]
        )

    def forward(self, x):
        y = list(self.cv1(x).split(self.hidden, dim=1))
        y.extend(m(y[-1]) for m in self.bottlenecks)
        return self.cv2(torch.cat(y, dim=1))</code></pre>
<h3 id="🤔-왜-단순-conv가-아닌-c2f를-사용할까">🤔 왜 단순 Conv가 아닌 C2f를 사용할까?</h3>
<p>Conv만 사용하면 다운샘플링하며 소실되는 정보를 복구할 수 없기 때문에 C2f가 필요하다.</p>
<blockquote>
<p>💡 다운샘플링 (Downsampling) : 이미지의 해상도(크기)를 줄이는 것
해상도가 줄어들면서 정보 손실이 발생할 수 있다.</p>
</blockquote>
<p>반면 C2f를 사용하면 입력된 특징을 쪼개어 일부는 보존하고, 일부는 깊게 가공한 뒤 다시 합친다. 덕분에 얕은 수준의 특징과 깊은 수준의 특징이 동시에 다음 레이어로 전달된다.</p>
<p>만약 C2f 없이 모델을 구성한다면 큼직한 강아지는 잘 찾더라도, 멀리 있는 아주 작은 강아지는 세밀한 특징이 소실되어 탐지해 내지 못할 확률이 높다.</p>
<h3 id="🔍-코드로-보는-c2f의-동작-흐름">🔍 코드로 보는 C2f의 동작 흐름</h3>
<p>강아지 이미지가 백본의 Stage 3을 통과한다고 가정해보자.</p>
<pre><code class="language-python">self.stage3 = nn.Sequential(
    Conv(32, 64, 3, 2),   # 다운샘플링 + 특징 추출
    C2f(64, 64, n=2),     # 추출한 특징을 다각도로 분석 및 융합
)</code></pre>
<p><code>C2f</code> 내부에서는 입력된 텐서가 다음과 같은 흐름을 가진다.</p>
<ol>
<li>Split (분할): 입력을 두 갈래 A와 B로 나눈다.</li>
<li>Bottleneck (심층 분석): 한쪽 갈래 B만 Bottleneck을 거치며 더 깊은 특징(C, D)을 추출한다.</li>
<li>Concat (병합): 쪼개졌던 원본과 각 단계의 출력값을 모두 하나로 뭉친다.</li>
</ol>
<pre><code class="language-text">  Step 1. cv1 — 채널 확장 후 분기
  입력 x [64ch]
      ↓ cv1 (1×1 Conv)
  [128ch]
      ↓ split (절반으로 나누기)
  A[64ch], B[64ch]

  Step 2. Bottleneck 순차 통과
  B[64ch] → Bottleneck1 → C[64ch]
  C[64ch] → Bottleneck2 → D[64ch]

  Step 3. 전체 concat → cv2
  [A, B, C, D] → concat → [256ch]
                      ↓ cv2 (1×1 Conv)
                 출력 [64ch]</code></pre>
<h3 id="🛠-bottleneck의-수용-영역receptive-field-확장">🛠 Bottleneck의 수용 영역(Receptive Field) 확장</h3>
<p>Bottleneck 내부는 3x3 Conv 두 개로 구성되어 있다. 단순히 현재 픽셀 주변만 보는 것이 아니라, 층을 거칠수록 더 넓은 영역의 맥락을 파악하게 된다.</p>
<ul>
<li>Bottleneck 없음: 1×1 시야 (현재 픽셀만)</li>
<li>Bottleneck 1개 통과: 5×5 영역의 맥락 파악</li>
<li>Bottleneck 2개 통과: 9×9 영역의 맥락 파악</li>
</ul>
<p>여기에 <code>shortcut</code> (잔차 연결)이 더해져, 넓은 영역을 분석한 결과물(out)에 원래 정보(x)를 더해줌으로써 기울기 소실(Gradient Vanishing)을 방지하고 학습을 안정적으로 만든다.</p>
<hr>
<h2 id="3-sppf-spatial-pyramid-pooling-fast">3. SPPF (Spatial Pyramid Pooling Fast)</h2>
<p><code>SPPF</code>는 Backbone의 가장 마지막 단계에 위치하여, 다양한 크기의 영역(Receptive Field)을 동시에 바라보고 정보를 취합하는 블록이다.</p>
<pre><code class="language-python"># Backbone stage5 마지막에 배치
self.stage5 = nn.Sequential(
    Conv(128, 256, 3, 2),  
    C2f(256, 256, n=1),    
    SPPF(256, 256, k=5),   # ← 핵심 모듈
)</code></pre>
<p>SPPF 내부 구조</p>
<pre><code class="language-python">class SPPF(nn.Module):
    &quot;&quot;&quot;
    동일한 크기(5x5)의 MaxPool을 직렬로 연결하여
    연산량은 줄이면서 다양한 수용 영역(Receptive Field)을 확보하는 모듈
    &quot;&quot;&quot;
    def __init__(self, in_c, out_c, k=5):
        super().__init__()
        hidden = in_c // 2                      # 예: 256 // 2 = 128
        self.cv1  = Conv(in_c, hidden, 1, 1)    # 256ch → 128ch (연산량 감소를 위해 채널 축소)
        self.cv2  = Conv(hidden * 4, out_c, 1, 1) # 512ch → 256ch (Concat 후 다시 원본 채널로 복구)
        self.pool = nn.MaxPool2d(k, stride=1, padding=k // 2) # 5×5 풀링

    def forward(self, x):
        x  = self.cv1(x)     # [B, 128, 13, 13]

        # MaxPool을 꼬리에 꼬리를 물고 반복
        p1 = self.pool(x)    # MaxPool 1회 적용
        p2 = self.pool(p1)   # MaxPool 2회 누적
        p3 = self.pool(p2)   # MaxPool 3회 누적

        # 원본과 모든 풀링 결과를 하나로 병합(Concat)
        return self.cv2(torch.cat([x, p1, p2, p3], dim=1))
        #                          ↑   ↑   ↑   ↑
        # 수용 영역(시야):         1×1 5×5 9×9 13×13</code></pre>
<h3 id="🤔-왜-maxpool을-3번이나-반복할까">🤔 왜 MaxPool을 3번이나 반복할까?</h3>
<p>가장 핵심적인 이유는 <strong>&#39;하나의 풀링 레이어로 3가지 수용 영역 효과를 내기 위해서&#39;</strong>이다.
앞서 Bottleneck에서 3x3 Conv를 겹쳐 시야를 넓혔던 것과 완벽히 동일한 원리이다.</p>
<p>같은 5×5 풀링(p=2, s=1)의 결과를 누적해서 적용하면 중심점은 유지된 채로 픽셀이 바라보는 영역만 넓어진다.</p>
<ul>
<li>x (원본): 1×1 수용 영역</li>
<li>p1 = pool(x): 5×5 수용 영역</li>
<li>p2 = pool(p1): 9×9 수용 영역</li>
<li>p3 = pool(p2): 13×13 수용 영역</li>
</ul>
<blockquote>
<p>pooling을 누적하는 이유:
처음부터 5×5, 9×9, 13×13 풀링을 각각 따로 연산하는 것(기존 SPP 방식)보다, 5×5 풀링 하나를 직렬로 3번 거치는 것이 연산 속도가 훨씬 빠르기 때문이다.</p>
</blockquote>
<p>결과적으로 <code>Concat</code>을 거치면, [B, 128, H, W] 크기의 특징 맵 4개가 합쳐져 [B, 512, H, W]가 되고, 마지막 1x1 Conv(<code>cv2</code>)를 통해 최종적으로 [B, 256, H, W] 크기로 깔끔하게 정리된다.</p>
<h4 id="📍-왜-하필-backbone의-가장-끝에-있을까">📍 왜 하필 Backbone의 가장 &#39;끝&#39;에 있을까?</h4>
<p>네트워크가 깊어질수록 특징 맵의 해상도(가로세로 크기)는 줄어들고, 압축된 정보(채널)는 늘어난다.</p>
<p><code>Stem: [B, 3, 640, 640]</code> (해상도 높음, 지엽적인 패턴)</p>
<p><code>Stage 3: [B, 64, 52, 52]</code></p>
<p><code>Stage 4: [B, 128, 26, 26]</code></p>
<p><code>Stage 5: [B, 256, 13, 13]</code> (해상도 가장 낮음, 전역적 맥락 필요)</p>
<p>Backbone의 끝자락인 Stage 5는 이미지의 크기가 13x13으로 가장 작게 압축된 상태이다. 이 위치에서는 화면 전체를 아우르는 큰 객체의 전체적인 형태나 문맥(Context)을 파악해야 하므로 가장 넓은 수용 영역이 필요하다.</p>
<hr>
<h2 id="4-backbone--특징-추출기">4. Backbone — 특징 추출기</h2>
<p>Backbone은 앞서 우리가 직접 설계한 <code>Conv</code>, <code>C2f</code>, <code>SPPF</code> 블록들을 레고처럼 조립하여 만든 네트워크의 뼈이다. 입력된 원본 이미지에서 <strong>&quot;무엇이 어디에 있는지&quot;</strong>를 파악하기 위한 시각적 특징(Feature)을 단계별로 추출하는 것이 주된 역할이다.</p>
<pre><code class="language-python">import torch.nn as nn

class Backbone(nn.Module):
    def __init__(self):
        super().__init__()

        # Stem: 첫 번째 다운샘플링 (초기 특징 추출)
        self.stem   = Conv(3, 16, 3, 2)   

        # Stage 2 (P2)
        self.stage2 = nn.Sequential(      
            Conv(16, 32, 3, 2),
            C2f(32, 32, n=1, shortcut=True),
        )

        # Stage 3 (P3) - stride 8 출력
        self.stage3 = nn.Sequential(      
            Conv(32, 64, 3, 2),
            C2f(64, 64, n=2, shortcut=True),
        )

        # Stage 4 (P4) - stride 16 출력
        self.stage4 = nn.Sequential(      
            Conv(64, 128, 3, 2),
            C2f(128, 128, n=2, shortcut=True),
        )

        # Stage 5 (P5) - stride 32 출력
        self.stage5 = nn.Sequential(      
            Conv(128, 256, 3, 2),
            C2f(256, 256, n=1, shortcut=True),
            SPPF(256, 256, k=5), # Backbone의 끝자락
        )

    def forward(self, x):
        x  = self.stem(x)
        x  = self.stage2(x)      # P2는 반환하지 않고 다음 스테이지로 넘김
        p3 = self.stage3(x)
        p4 = self.stage4(p3)
        p5 = self.stage5(p4)

        return p3, p4, p5        # 최종적으로 3개의 특징 맵을 반환</code></pre>
<h3 id="🔍-단계별-텐서tensor의-변화-해상도는-↓-채널은-↑">🔍 단계별 텐서(Tensor)의 변화: 해상도는 ↓ 채널은 ↑</h3>
<p>입력 이미지(640×640)가 Backbone의 각 Stage를 통과할 때마다 텐서의 형태는 다음과 같이 변한다.</p>
<pre><code class="language-text">원본: [B,   3, 640, 640] 
 ↓ 
Stem: [B,  16, 208, 208] (해상도 1/2 감소)
 ↓ 
 P2 : [B,  32, 104, 104] (해상도 1/2 감소)
 ↓ 
 P3 : [B,  64,  52,  52] (해상도 1/2 감소) → 📌 첫 번째 출력
 ↓ 
 P4 : [B, 128,  26,  26] (해상도 1/2 감소) → 📌 두 번째 출력
 ↓ 
 P5 : [B, 256,  13,  13] (해상도 1/2 감소) → 📌 세 번째 출력</code></pre>
<p>각 Stage의 첫 번째 Conv 레이어는 해상도를 절반으로 줄여 연산량을 감소시킨다. 해상도가 줄어들면 픽셀 하나가 바라보는 영역이 넓어지는데, 이때 넓은 영역을 압축해서 보게 되므로 더 복잡하고 전역적인 의미 정보를 담기 위해 채널을 2배로 늘려주는 것이다.</p>
<h4 id="🤔-왜-굳이-p3-p4-p5-세-가지를-따로-출력할까">🤔 왜 굳이 P3, P4, P5 세 가지를 따로 출력할까?</h4>
<p>forward() 함수의 마지막 줄을 보면, 생성된 특징 맵 중 Stage 2의 결과물은 버리고 P3, P4, P5 세 가지만 반환한다.</p>
<p>그 이유는 하나의 이미지 안에서도 객체들의 크기가 천차만별이기 때문이다. 각 출력물은 서로 다른 크기의 객체를 탐지하는 역할을 전담한다.</p>
<ul>
<li><p>P3 : 해상도가 높고 시야가 좁아, 작은 객체를 탐지하는 성능이 좋다.</p>
<ul>
<li>해상도가 높은 P3은 테두리나 윤곽선 같은 <strong>Spatial Info</strong>가 살아있다.</li>
</ul>
</li>
<li><p>P4 : 해상도와 시야가 중간 정도이며 중간 사이즈의 객체를 탐지하는 성능이 좋다.</p>
</li>
<li><p>P5 : 해상도가 낮고 시야가 넓어, 큰 객체를 탐지하는 성능이 좋다.</p>
<ul>
<li>해상도가 낮게 압축된 P5는 &quot;이게 어떤 물체인가&quot;를 유추할 수 있는 <strong>Semantic Info</strong>를 담고 있다.</li>
</ul>
</li>
</ul>
<h2 id="5-neck-fpn--pan--특징-융합의-마법">5. Neck (FPN + PAN) — 특징 융합의 마법</h2>
<p>Neck은 Backbone이 출력한 P3, P4, P5를 받아 서로 다른 스케일의 특징을 융합하는 역할을 한다. Backbone이 &quot;특징을 추출&quot;한다면, Neck은 &quot;추출된 특징들을 서로 교환하여 품질을 높이는&quot; 단계다.</p>
<pre><code class="language-python">import torch
import torch.nn as nn

class Neck(nn.Module):
    def __init__(self):
        super().__init__()

        # 1. FPN: Top-down (위 → 아래로 의미 정보 전달)
        self.up1      = nn.Upsample(scale_factor=2, mode=&quot;nearest&quot;)
        self.c2f_up1  = C2f(256 + 128, 128, n=1, shortcut=False)  # P5(↑) + P4

        self.up2      = nn.Upsample(scale_factor=2, mode=&quot;nearest&quot;)
        self.c2f_up2  = C2f(128 + 64,  64,  n=1, shortcut=False)  # up1(↑) + P3

        # 2. PAN: Bottom-up (아래 → 위로 위치 정보 전달)
        self.down1     = Conv(64,  64,  3, 2)
        self.c2f_down1 = C2f(64 + 128, 128, n=1, shortcut=False)  # down1(↓) + up1

        self.down2     = Conv(128, 128, 3, 2)
        self.c2f_down2 = C2f(128 + 256, 256, n=1, shortcut=False) # down2(↓) + P5

    def forward(self, p3, p4, p5):
        # FPN (위에서 아래로)
        x  = self.c2f_up1(torch.cat([self.up1(p5), p4], dim=1))
        n3 = self.c2f_up2(torch.cat([self.up2(x),  p3], dim=1))

        # PAN (아래서 위로)
        n4 = self.c2f_down1(torch.cat([self.down1(n3), x],  dim=1))
        n5 = self.c2f_down2(torch.cat([self.down2(n4), p5], dim=1))

        return n3, n4, n5  # 최종 융합된 3개의 특징 맵</code></pre>
<h3 id="🤔-왜-굳이-특징을-융합해야-할까">🤔 왜 굳이 특징을 융합해야 할까?</h3>
<p>Backbone에서 넘어온 P3, P4, P5는 각자 잘하는 것이 명확히 다르다.</p>
<p>P5 [13×13]: 해상도가 낮게 압축되어 *&quot;여기에 버스가 있다&quot;*는 전역적인 의미(Semantic) 정보가 풍부하다. 하지만 압축이 심해 정확한 위치나 테두리는 뭉개져 있다.</p>
<p>P3 [52×52]: 해상도가 높아 *&quot;테두리가 정확히 여기 있다&quot;*는 세밀한 위치(Spatial) 정보가 강하다. 하지만 시야가 좁아 그게 버스인지, 트럭인지 확신하기 어렵다.</p>
<p>만약 이 둘을 분리된 채로 사용한다면, 모델은 &quot;뭔지는 아는데 어디 있는지 모르는&quot; 상태(P5)와 &quot;어디 있는지는 아는데 뭔지 모르는&quot; 상태(P3)에 빠지게 된다. 따라서 모든 층이 의미 정보와 위치 정보를 동시에 갖도록 섞어주는 작업이 필수적이다.</p>
<h3 id="🌊-fpn-top-down--깊은-의미-정보를-아래로-전파">🌊 FPN (Top-down) — 깊은 의미 정보를 아래로 전파</h3>
<p>FPN(Feature Pyramid Network)은 가장 깊은 층인 P5의 강력한 의미 정보를 해상도가 높은 P3 방향으로 흘려보내는 역할을 한다.</p>
<pre><code class="language-text">P5 [B, 256, 13, 13]
 │
 └─▶ (Upsample ×2) ─▶ [B, 256, 26, 26] 
                        + P4 [B, 128, 26, 26] ─▶ (Concat &amp; C2f) ─▶ x [B, 128, 26, 26]
                                                                   │
            ┌──────────────────────────────────────────────────────┘
            └─▶ (Upsample ×2) ─▶ [B, 128, 52, 52] 
                                   + P3 [B, 64,  52, 52] ─▶ (Concat &amp; C2f) ─▶ 📌 n3</code></pre>
<ul>
<li><p>Upsample을 통해 텐서의 해상도(가로세로)를 2배로 강제로 키운 뒤, 같은 크기를 가진 아래층의 특징 맵과 cat으로 이어 붙인다.</p>
</li>
<li><p>이 과정을 거치면 하위 계층(P3, P4)도 &quot;아, 내가 보고 있는 선명한 윤곽선이 버스였구나!&quot; 하고 의미를 깨닫게 된다.</p>
</li>
</ul>
<h3 id="🔥-pan-bottom-up--세밀한-위치-정보를-위로-전파">🔥 PAN (Bottom-up) — 세밀한 위치 정보를 위로 전파</h3>
<p>FPN만 사용하면 P3은 똑똑해지지만, 정작 P5는 여전히 정확한 위치 정보를 모른다. 이를 해결하기 위해 PAN(Path Aggregation Network)을 도입하여, 만들어진 n3의 정밀한 위치 정보를 다시 위쪽으로 올려보낸다.</p>
<pre><code class="language-text">📌 n3 [B, 64, 52, 52]
 │
 └─▶ (Conv stride=2) ─▶ [B, 64, 26, 26] 
                          + x [B, 128, 26, 26] ─▶ (Concat &amp; C2f) ─▶ 📌 n4 [B, 128, 26, 26]
                                                                     │
              ┌──────────────────────────────────────────────────────┘
              └─▶ (Conv stride=2) ─▶ [B, 128, 13, 13]
                                       + P5 [B, 256, 13, 13] ─▶ (Concat &amp; C2f) ─▶ 📌 n5</code></pre>
<ul>
<li><p>Conv(stride=2)를 사용하여 해상도를 절반으로 깎아내리며(다운샘플링) 윗층과 cat으로 이어 붙인다.</p>
</li>
<li><p>이로써 상위 계층(P5)도 잃어버렸던 세밀한 위치 감각을 다시 되찾게 된다.</p>
</li>
</ul>
<hr>
<h2 id="6-detecthead--최종-예측">6. DetectHead — 최종 예측</h2>
<p>DetectHead는 Neck이 융합해 준 다중 스케일 특징 맵(N3, N4, N5)을 받아, 최종적으로 <strong>&quot;무엇이(cls) 어디에(reg) 있는지&quot;</strong>를 예측하는 네트워크의 마지막 단계다.</p>
<p>YOLOv5까지는 클래스(종류)와 박스 좌표(위치)를 하나의 헤드에서 동시에 예측했지만, YOLOv8부터는 두 작업을 완전히 분리한 Decoupled Head(분리형 헤드) 방식을 채택하여 성능을 대폭 끌어올렸다.</p>
<pre><code class="language-python">import torch
import torch.nn as nn

class DetectHead(nn.Module):
    REG_MAX = 16  # DFL에서 사용할 최대 픽셀 범위

    def __init__(self, num_classes=80, in_channels=(64, 128, 256)):
        super().__init__()
        self.num_classes = num_classes
        self.dfl = DFL(self.REG_MAX)

        # 브랜치별 채널 수 설정
        c2 = max(16, in_channels[0] // 4, self.REG_MAX * 4)  # reg(좌표) 브랜치 채널 = 64
        c3 = max(in_channels[0], min(num_classes, 100))      # cls(클래스) 브랜치 채널 = 80

        self.cls_convs = nn.ModuleList()  # 클래스 분류를 위한 Conv 계층
        self.reg_convs = nn.ModuleList()  # 박스 좌표를 위한 Conv 계층
        self.cls_preds = nn.ModuleList()  # 클래스 최종 예측 (1x1 Conv)
        self.reg_preds = nn.ModuleList()  # 박스 최종 예측 (1x1 Conv)

        # N3(64), N4(128), N5(256) 각 스케일마다 독립적인 헤드 생성
        for in_c in in_channels:
            self.cls_convs.append(nn.Sequential(
                Conv(in_c, c3, 3, 1),
                Conv(c3,   c3, 3, 1),
            ))
            self.reg_convs.append(nn.Sequential(
                Conv(in_c, c2, 3, 1),
                Conv(c2,   c2, 3, 1),
            ))
            self.cls_preds.append(nn.Conv2d(c3, num_classes,      1)) # 80개 클래스 확률
            self.reg_preds.append(nn.Conv2d(c2, 4 * self.REG_MAX, 1)) # 4개 방향 * 16개 분포</code></pre>
<h3 id="🤔-왜-cls분류와-reg회귀를-분리할까">🤔 왜 cls(분류)와 reg(회귀)를 분리할까?</h3>
<p>두 작업이 바라보는 정보의 성격이 근본적으로 다르기 때문이다.</p>
<ul>
<li>cls 브랜치 (분류): &quot;이게 강아지인가, 자동차인가?&quot; $\rightarrow$ 객체의 생김새, 질감 등 전반적인 의미적(Semantic) 특징에 집중해야 한다.</li>
<li>reg 브랜치 (회귀): &quot;박스의 왼쪽 경계가 정확히 몇 픽셀인가?&quot;$\rightarrow$ 외곽선, 모서리 등 타겟의 정밀한 위치(Spatial) 정보에 민감하게 반응해야 한다.</li>
</ul>
<p>이처럼 지향점이 다른 두 작업을 하나의 헤드에서 처리하면 서로 간섭이 발생해 학습 효율이 떨어진다. 브랜치를 분리하면 각 네트워크가 자신의 역할에만 온전히 집중할 수 있어 탐지 정확도가 향상된다.</p>
<h3 id="🔍-forward--스케일별-독립적인-예측">🔍 forward — 스케일별 독립적인 예측</h3>
<p>각각의 스케일(N3, N4, N5)은 분리된 두 브랜치를 거친 후, 마지막에 다시 하나로 합쳐져(Concat) 출력된다.</p>
<h2 id="7-dfl">7. DFL</h2>
<p>DFL은 DetectHead의 reg 브랜치가 출력한 박스 좌표 분포를 실제 거리값으로 변환하는 디코더이다.</p>
<pre><code class="language-python">class DFL(nn.Module):
    &quot;&quot;&quot;
    Distribution Focal Loss 디코더

    YOLOv8은 박스 좌표를 하나의 숫자 대신 &#39;분포(distribution)&#39;로 예측함
    reg_max=16이면 각 좌표(l,t,r,b)에 대해 0~15 사이 분포를 softmax 후
    가중 평균(expected value)으로 변환 → 더 정밀한 박스 예측 가능

    수식: coord = sum(i * softmax(pred)[i]) for i in 0..reg_max-1
    1x1 Conv 가중치를 [0,1,2,...,15]로 고정하여 기대값 계산
    &quot;&quot;&quot;
    def __init__(self, reg_max=16):
        super().__init__()
        self.reg_max = reg_max
        self.conv = nn.Conv2d(reg_max, 1, 1, bias=False)
        # 고정 가중치: [0, 1, 2, ..., reg_max-1]
        self.conv.weight.data[:] = torch.arange(
            reg_max, dtype=torch.float
        ).reshape(1, reg_max, 1, 1)
        for p in self.conv.parameters():
            p.requires_grad = False  # 학습하지 않는 고정 파라미터

    def forward(self, x):
        # x: [B, 4*reg_max, num_anchors]
        b, _, a = x.shape
        # [B, 4, reg_max, A] → transpose → softmax → conv → [B, 4, A]
        return self.conv(
            x.view(b, 4, self.reg_max, a)
             .transpose(2, 1)          # [B, reg_max, 4, A]
             .softmax(1)               # reg_max 방향으로 확률 분포
        ).view(b, 4, a)</code></pre>
<h2 id="8-nms">8. NMS</h2>
<pre><code class="language-python">def nms(preds, conf_thres=0.4, iou_thres=0.45):
    &quot;&quot;&quot;
    NMS: 같은 물체에 대한 중복 박스 제거

    동작 원리:
    1. 확신도 임계값 미만 제거
    2. 확신도 높은 순으로 정렬
    3. 가장 높은 박스와 IoU가 iou_thres 초과인 박스 제거
    4. 남은 박스 중 다음으로 높은 것 선택 → 반복
    &quot;&quot;&quot;
    results = []
    for pred in preds:
        scores, cls_ids = pred[:, 4:].max(dim=-1)
        mask = scores &gt; conf_thres
        boxes, scores, cls_ids = pred[:, :4][mask], scores[mask], cls_ids[mask]

        if boxes.numel() == 0:
            results.append(torch.zeros((0, 6), device=pred.device))
            continue

        # 클래스별로 NMS 분리 (같은 클래스끼리만 중복 제거)
        offset  = cls_ids.float().unsqueeze(1) * 10000
        shifted = boxes + offset
        keep    = _nms_loop(shifted, scores, iou_thres)

        results.append(torch.cat([
            boxes[keep],
            scores[keep].unsqueeze(1),
            cls_ids[keep].float().unsqueeze(1),
        ], dim=1))
    return results


def _nms_loop(boxes, scores, iou_thres):
    &quot;&quot;&quot;IoU 기반 NMS 루프 (torchvision 의존성 없이 순수 구현)&quot;&quot;&quot;
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    areas = (x2 - x1).clamp(0) * (y2 - y1).clamp(0)
    order = scores.argsort(descending=True)
    keep  = []

    while order.numel() &gt; 0:
        i = order[0].item()
        keep.append(i)
        if order.numel() == 1:
            break
        rest = order[1:]
        # 교집합 영역 계산
        ix1   = x1[rest].clamp(min=x1[i].item())
        iy1   = y1[rest].clamp(min=y1[i].item())
        ix2   = x2[rest].clamp(max=x2[i].item())
        iy2   = y2[rest].clamp(max=y2[i].item())
        inter = (ix2 - ix1).clamp(0) * (iy2 - iy1).clamp(0)
        iou   = inter / (areas[i] + areas[rest] - inter + 1e-7)
        order = rest[iou &lt;= iou_thres]

    return keep</code></pre>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/0ae298ea-0a7e-45c1-8902-06f49eea0348/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Object Detection (5) YOLO v1]]></title>
            <link>https://velog.io/@dongmin_1999/Object-Detection-5-YOLO-v1</link>
            <guid>https://velog.io/@dongmin_1999/Object-Detection-5-YOLO-v1</guid>
            <pubDate>Sat, 04 Apr 2026 14:17:32 GMT</pubDate>
            <description><![CDATA[<p>지난 포스팅까지 우리는 R-CNN 계열(2-Stage Detector)이 어떻게 발전해 왔는지 살펴보았다. 
Faster R-CNN에 이르러 RPN을 도입하며 속도를 비약적으로 끌어올렸지만, 여전히 &#39;후보 영역 추출&#39;과 &#39;분류&#39;라는 두 단계를 거쳐야 한다는 구조적 한계가 있었다.</p>
<p>오늘 소개할 <strong>YOLO(You Only Look Once)</strong>는 이름 그대로 &quot;이미지를 단 한 번만 보고&quot; 객체를 탐지해내는, 1-Stage Detector 모델이다.</p>
<p>객체 탐지 성능을 평가하는 두가지 지표가 있다.
mAP로 측정하는 정확도와 FPS로 측정하는 수행시간(inference time)이다.</p>
<p>YOLO v1이 처음 나왔을 당시에 정확도는 Faster R-CNN보다 부족했지만 속도에서는 매우 큰 발전을 보였다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/4bec2cdd-6141-454c-9a21-556538654cc2/image.png" alt="">
YOLO 구조를 살펴보자</p>
<hr>
<h2 id="📌-yolo-네트워크-구조">📌 YOLO 네트워크 구조</h2>
<p>YOLO v1의 아키텍처는 24+2 구조를 가지고 있다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/b6f90eeb-01e7-49db-a121-24ee6b1c0025/image.png" alt=""></p>
<ol>
<li>두 가지 네트워크 그룹: Pretrained &amp; Training</li>
</ol>
<ul>
<li>Pretrained Network (앞의 20개의 Conv layers)
ImageNet 데이터셋으로 분류 작업에 대해 사전 학습시킨다
이때 연산 효율을 위해  <strong>$224 \times 224$</strong> 해상도로 학습하며, 사물의 핵심적인 특징(Feature)을 추출하는 방법을 학습한다.</li>
<li>Training Network (뒤의 4개의 Conv + 2개의 FC layers)
사전 학습된 층 뒤에 객체 탐지(Detection) 전용 층을 추가한다. 정밀한 위치 추적을 위해 입력 해상도를 <strong>$448 \times 448$</strong> 로 2배 높여서 학습을 진행한다.
Training network 까지 거치고 나면 최종적으로 7x7x30 크기의 tensor가 출력된다.<blockquote>
<p>아키텍처 이미지에 448x448 입력만 보이는 이유는 Figure3 구조도는 Pretrain이 아니라 <strong>&#39;Detection용&#39;</strong> 네트워크 구조이기 때문이라고 함</p>
</blockquote>
</li>
</ul>
<p>24개의 Conv layer와 2개의 FC layer를 거친 결과는 7x7x30 으로 나오게 된다. 그 이유를 알아보자.</p>
<hr>
<h4 id="unified-detection">Unified Detection</h4>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/bf2c57cf-3aa9-42c8-938b-81f838080ad2/image.png" alt=""></p>
<ol>
<li>Input image를 $S \times S$ 로 나눈다.</li>
<li>각각의 grid cell은 B개의 bounding box와 각 bounding box에 대한 confidence score를 갖는다. </li>
<li>각각의 grid cell은 C개의 conditional class probability를 갖는다.</li>
<li>각각의 bounding box는 x, y, w, h, confidence로 구성된다.<ul>
<li>(x,y): Bounding box의 중심점을 의미하며, grid cell의 범위에 대한 상대값이 입력된다.</li>
<li>(w,h): 전체 이미지의 width, height에 대한 상대값이 입력된다.</li>
</ul>
</li>
</ol>
<p>논문에서는 PASCAL VOC를 사용하였으며, S,B,C에는 각각 7, 2, 20이 할당되었다.</p>
<blockquote>
<p>PASCAL VOC(Visual Object Classes) : 컴퓨터 비전 분야에서 객체 탐지(Object Detection), 분할(Segmentation), 분류(Classification) 알고리즘의 성능을 평가하는 대표적인 벤치마크 데이터셋으로, 20개의 클래스로 구성되어 있다.</p>
</blockquote>
<hr>
<h4 id="추론-과정">추론 과정</h4>
<p>Deep systems의 슬라이드를 인용하였다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/1d81c20d-6f80-4729-9e63-32e9049b5f08/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/21a0e424-33a1-42e4-b77e-1f3389d0f738/image.png" alt=""></p>
<p>7x7은 49개의 Grid cell을 의미한다. 각 그리드 셀은 2개의 bounding box를 가지고 있는데, 각각 5차원씩 bounding box에 대한 값이 채워져 있다. 
예) [x좌표,y좌표,너비,높이,객체가 bounding box에 있을 확률]</p>
<br>
<br>
<br>

<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/2e5525ad-9f25-4695-af93-b04645797f12/image.png" alt="">
<img src="https://velog.velcdn.com/images/dongmin_1999/post/ed25860b-e4f9-4102-b77d-e8b9aff4cdc1/image.png" alt=""></p>
<p>나머지 20개의 값은 20개의 class에 대한 conditional class probability에 해당한다.
첫 번째 bounding box의 confidence score와 각 conditional class probability를 곱하면 첫 번째 bounding box의 class specific confidence score가 나온다.
마찬가지로, 두 번째 bounding box의 confidence score와 각 conditional class probability를 곱하면 두 번째 bounding box의 class specific confidence score가 나온다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/e61f6687-1efd-4507-b111-9bfb0d272ec4/image.png" alt="">
이 계산을 각 bounding box에 대해 하게되면 총 98개의 class specific confidence score를 얻을 수 있다.</p>
<p>이 98개의 class specific confidence score에 대해 각 20개의 클래스를 기준으로 non-maximum suppression 연산을 진행하여 탐지된 객체의 prediction 결과만을 얻어낸다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Object detection (4) Faster R-CNN]]></title>
            <link>https://velog.io/@dongmin_1999/Object-detection-4-Faster-R-CNN</link>
            <guid>https://velog.io/@dongmin_1999/Object-detection-4-Faster-R-CNN</guid>
            <pubDate>Fri, 03 Apr 2026 12:38:55 GMT</pubDate>
            <description><![CDATA[<h2 id="faster-r-cnn">Faster R-CNN</h2>
<p>이전 포스팅에서 살펴본 R-CNN과 Fast R-CNN은 딥러닝 기반 객체 탐지의 발전 과정에서 중요한 역할을 했다.</p>
<p>특히 Fast R-CNN은 전체 이미지를 한 번만 CNN에 통과시키고, RoI Pooling을 통해 연산량을 크게 줄이며 속도와 정확도를 동시에 개선하는 데 성공하였다.</p>
<p>하지만 여전히 해결되지 않은 치명적인 문제가 하나 남아 있었다.</p>
<p>👉 바로 <strong>Region Proposal 단계의 병목 현상</strong>이다.</p>
<p>Fast R-CNN에서는 객체 후보 영역을 생성하기 위해 여전히 <strong>Selective Search</strong> 알고리즘을 사용했는데, 이 과정은</p>
<ul>
<li>CPU에서 동작하며</li>
<li>속도가 매우 느리고</li>
<li>딥러닝 모델과 분리되어 있어 학습되지 않는다</li>
</ul>
<p>이 문제를 해결하기 위해 등장한 것이 바로 <strong>Faster R-CNN</strong>이다.
즉, CNN 기반 탐지는 빨라졌지만, Region Proposal 단계가 전체 속도를 제한하는 병목으로 남아 있었다.</p>
<hr>
<h2 id="📌-faster-r-cnn이란">📌 Faster R-CNN이란?</h2>
<p>Faster R-CNN은 기존의 Selective Search를 제거하고, Region Proposal 과정까지 CNN 내부로 통합한 객체 탐지 모델이다.</p>
<p>이를 위해 <strong>RPN (Region Proposal Network)</strong>을 도입하여, Feature Map 위에서 직접 객체 후보 영역을 생성한다.</p>
<blockquote>
<p>💡 RPN 이란?
이미지에서 <strong>객체가 있을 가능성이 높은 영역(Region Proposal)</strong>을 찾아주는 딥러닝 네트워크</p>
</blockquote>
<p>즉, Faster R-CNN은 Region Proposal + Object Detection을 하나의 네트워크로 통합한 모델이며,
이를 통해 속도와 성능을 모두 크게 향상시킨 모델이다.</p>
<h2 id="📌-faster-r-cnn의-장점">📌 Faster R-CNN의 장점</h2>
<h4 id="✅-1-region-proposal-속도-개선">✅ 1. Region Proposal 속도 개선</h4>
<p>기존 Fast R-CNN의 병목이었던 Selective Search를 제거하고, <strong>RPN</strong>을 사용하여 후보 영역을 빠르게 생성한다.</p>
<ul>
<li>CNN 기반 (GPU)</li>
</ul>
<h4 id="✅-2-완전한-end-to-end-학습">✅ 2. 완전한 End-to-End 학습</h4>
<p>Region Proposal + Classification + BBR 의 모든 과정이 하나의 네트워크에서 동시에 학습된다.</p>
<h4 id="✅-3-feature-공유로-연산-효율-증가">✅ 3. Feature 공유로 연산 효율 증가</h4>
<p>RPN과 Object Detection 네트워크가 동일한 CNN Feature Map을 공유한다.</p>
<p>→ 중복 연산이 제거되어 전체 연산 효율이 향상된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Object detection (3) Fast R-CNN]]></title>
            <link>https://velog.io/@dongmin_1999/Object-detection-3-Fast-R-CNN</link>
            <guid>https://velog.io/@dongmin_1999/Object-detection-3-Fast-R-CNN</guid>
            <pubDate>Tue, 31 Mar 2026 11:27:35 GMT</pubDate>
            <description><![CDATA[<h1 id="fast-r-cnn">Fast R-CNN</h1>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/a96c5273-031a-4446-b4a8-ce9d49985c9d/image.png" alt=""></p>
<p>이전 포스팅에서 다룬 R-CNN은 딥러닝 객체 탐지의 서막을 열었지만, 극복해야할 명확한 한계점들을 가지고 있었다.</p>
<h2 id="❗-r-cnn의-치명적인-문제">❗ R-CNN의 치명적인 문제</h2>
<ol>
<li><p>성능 손실: AlexNet에 넣기 위해 이미지를 227x227 으로 강제로 Warping 시켜 형태 왜곡 발생</p>
</li>
<li><p>느린 속도: Selective Search로 뽑은 2,000개의 Region Proposal을 일일이 CNN에 통과시켜 학습/추론 시간이 너무 오래 걸림</p>
</li>
<li><p>GPU 비효율: Selective Search나 SVM은 CPU 기반 알고리즘이라 병렬 처리 불가</p>
</li>
<li><p>Not End-to-End: CNN, SVM, Bounding Box Regression(BBR) 모델이 서로 연산을 공유하지 않고 따로 학습됨</p>
</li>
</ol>
<h2 id="💡-fast-r-cnn의-핵심-혁신">💡 Fast R-CNN의 핵심 혁신</h2>
<p>Fast R-CNN이 R-CNN의 단점을 극복한 핵심 무기는 크게 두 가지이다.</p>
<ol>
<li><p>RoI Pooling 도입: 2,000번의 CNN 연산을 1번으로 줄임.</p>
</li>
<li><p>Multi-task Loss 적용: CNN 특징 추출부터 Classification, BBR까지 하나의 모델에서 한 번에 학습(End-to-End)이 가능해졌다.</p>
</li>
</ol>
<h2 id="spp-spatial-pyramid-pooling-와-roi-pooling">SPP (Spatial Pyramid Pooling) 와 RoI Pooling</h2>
<p>R-CNN에서는 이미지를 CNN에 넣으려면 크기가 똑같아야 한다고 알고있었다. 하지만 실제로는 <strong>&quot;합성곱 층(CNN)은 입력 크기에 상관없고, 그 뒤에 붙는 완전 연결 층(FC Layer)만 입력 크기가 고정되어야 한다&quot;</strong>는 사실이 밝혀졌다. 
따라서 CNN에는 입력 이미지 크기, 비율 관계없이 input으로 들어갈 수 있고 FC layer의 input으로 들어갈때만 size를 맞춰주기만 하면 된다.</p>
<p>여기서 Spatial Pyramid Pooling이 제안된다.</p>
<ol>
<li><p>SPP (Spatial Pyramid Pooling): 제각각인 크기의 Feature Map을 4x4, 2x2, 1x1 등 여러 크기의 피라미드 그리드로 나눈 뒤 Max Pooling을 적용하여, 결국엔 고정된 1차원 Vector로 쭉 이어 붙여주는 획기적인 기법이다.</p>
<blockquote>
<p>💡 피라미드 그리드란?
창문을 여러 개로 쪼개서 보는 것과 같다. 이미지 전체를 통유리(1x1)로 한 번 보고, 십자가로 쪼개서(2x2) 보고, 더 잘게 쪼개서(4x4) 본 뒤, 각 칸에서 가장 특징적인 값(Max)만 뽑아내는 방식이다. 어떤 크기의 이미지가 들어와도 항상 똑같은 개수의 칸으로 쪼개기 때문에 고정된 길이의 결과를 얻을 수 있다.</p>
</blockquote>
</li>
<li><p>RoI Pooling: Fast R-CNN은 이 SPP에서 아이디어를 얻어, 여러 피라미드 층을 쓰지 않고 오직 <strong>하나의 피라미드(7x7 단일 사이즈 그리드)</strong>만 적용하는 방식으로 단순화한 RoI Pooling 기법을 고안해 냈습니다.</p>
</li>
</ol>
<h2 id="⚙️-fast-r-cnn-네트워크-구조와-수행-과정">⚙️ Fast R-CNN 네트워크 구조와 수행 과정</h2>
<p>논문에서 저자들이 제시한 VGG16 기반의 네트워크 구조를 구체적인 차원 숫자들과 함께 따라가 보겠다.</p>
<ol>
<li>CNN Feature Extraction &amp; Region Proposal (동시 진행)</li>
</ol>
<ul>
<li><p>1-1 CNN : Input Image 전체를 VGG16 네트워크(Convolutional Layer 13번째 층까지만)에 단 한 번만 통과시킨다. 그 결과 14x14x512 크기의 하나의 거대한 Feature Map이 추출된다.</p>
</li>
<li><p>1-2 Region Proposal: Selective Search를 통해 RoI를 약 2,000개 찾는다.</p>
</li>
</ul>
<ol start="2">
<li><p>Region Projection (영역 투영)
원본 이미지에서 찾은 2,000개의 RoI(Bounding Box) 위치 좌표를, 1-1 과정에서 CNN을 통과하며 쪼그라든 14x14 Feature Map의 비율에 맞춰 그대로 투영(Projection)한다.</p>
</li>
<li><p>RoI Pooling
VGG16의 마지막 Max Pooling Layer를 RoI Pooling Layer로 대체한다.</p>
</li>
</ol>
<ul>
<li>Input: 제각각의 크기로 투영된 2,000개의 RoI 영역 데이터</li>
<li>Process: 각 RoI 영역을 7x7 크기의 Grid로 나눈 뒤, 각 칸마다 Max Pooling을 수행합니다.</li>
<li>Output: 크기에 상관없이 모든 박스가 7x7x512 크기의 Feature Map으로 통일되어 출력됩니다. (총 2,000개)</li>
</ul>
<ol start="4">
<li><p>FC Layer 통과 (특징 벡터 추출)
RoI Pooling을 거친 7x7x512 데이터를 1차원으로 쭉 펼쳐서(Flatten) FC Layer에 입력하면, 박스 1개당 4096 크기의 고정된 Feature Vector를 얻게 된다. 이 4096 벡터는 두 갈래(Branch)로 나뉘어 최종 예측을 수행한다.</p>
<ul>
<li><p>4-1. Image Classification: 4096 벡터가 FC Layer(Softmax)를 통과하여, K개의 객체 클래스 + 배경(1개)을 포함한 (K+1) 크기의 확률 벡터를 출력한다.</p>
</li>
<li><p>4-2. Bounding Box Prediction: 동시에, 각 클래스별로 박스의 좌표(x, y, w, h)를 정밀하게 예측하기 위해 (K+1) x 4 크기의 벡터를 출력한다.</p>
</li>
</ul>
</li>
<li><p>Multi-task Loss &amp; Training (End-to-End)
분리되어 있던 R-CNN과 달리, Fast R-CNN은 두 갈래에서 나온 예측 결과를 바탕으로 Classification Loss와 BBR Loss를 하나로 묶은 Multi-task Loss를 계산한다. 그리고 이 통합된 Loss를 사용해 Backpropagation을 수행함으로써 전체 네트워크를 한 번에 학습시킨다. (마지막엔 NMS를 거쳐 중복 박스를 제거한다.)
<br>(참고: 논문 저자들은 VGG16의 Conv layer 3까지의 가중치는 고정하고, 이후 Layer만 학습되도록 설정했을 때 성능이 가장 좋았다고 밝혀졌다.)
<img src="https://velog.velcdn.com/images/dongmin_1999/post/4a8072e3-8571-4c42-b8a2-18b68c7d2b7d/image.png" alt=""></p>
</li>
</ol>
<h2 id="⚖️-fast-r-cnn-요약-장점과-남은-단점">⚖️ Fast R-CNN 요약: 장점과 남은 단점</h2>
<h4 id="👍-장점">👍 장점</h4>
<ul>
<li><p>압도적인 연산량 감소: R-CNN에서 2,000번씩 하던 무거운 CNN 연산을 단 1번으로 줄여 속도를 비약적으로 향상시켰다.</p>
</li>
<li><p>성능 손실 방지: RoI Pooling을 통해 이미지 Warping 작업을 제거함으로써, 기하학적 왜곡으로 인한 성능 손실을 막아냈다. (Pascal VOC 2007 데이터 셋 기준 mAP 66% 기록)</p>
</li>
<li><p>End-to-End 학습: CNN 특징 추출부터 Classification, BBR을 모두 하나의 네트워크에서 묶어서 학습시키는 구조를 완성했다.</p>
</li>
</ul>
<h4 id="👎-단점-한계점">👎 단점 (한계점)</h4>
<ul>
<li><p>Fast R-CNN은 이전 모델에 비해 혁신적으로 빨라졌지만, 실시간(Real-time) 탐지에 쓰기에는 여전히 느리다.</p>
</li>
<li><p>가장 큰 원인은 바로 Region Proposal 단계에 있다. 딥러닝 네트워크 내부 연산은 엄청나게 빨라졌지만, 정작 후보 영역을 찾아주는 Selective Search 알고리즘 자체가 CPU에서 돌아가다 보니 심각한 병목(Bottleneck) 현상이 발생한다.</p>
</li>
</ul>
<h3 id="🚀-마무리하며">🚀 마무리하며</h3>
<p>이번 포스팅에서는 RoI Pooling을 무기로 CNN 연산을 하나로 통합하여 속도와 정확도를 동시에 끌어올린 Fast R-CNN에 대해 알아보았다.</p>
<p>하지만 모델 외부(CPU)에서 연산되는 Selective Search 알고리즘의 병목 현상 때문에 완벽한 속도 혁신은 이루지 못했다. 따라서 이 Region Proposal 작업마저 딥러닝 네트워크(GPU) 내부로 끌고 들어와, 더욱 빠르고 정확하게 RoI를 생성해 내는 Faster R-CNN이 등장하게 된다.</p>
<hr>
<h2 id="요약">요약</h2>
<h4 id="🔹-등장-배경">🔹 등장 배경</h4>
<p>👉 R-CNN의 “속도 + 구조 문제” 해결</p>
<h4 id="🔹-핵심-아이디어">🔹 핵심 아이디어</h4>
<ol>
<li>CNN 연산 1번만 수행</li>
<li>RoI Pooling으로 크기 통일</li>
<li>Multi-task Loss → End-to-End 학습</li>
</ol>
<h4 id="🔹-동작-과정">🔹 동작 과정</h4>
<ol>
<li>이미지 전체를 CNN에 한 번 통과 → Feature Map 생성</li>
<li>Selective Search로 RoI 추출</li>
<li>RoI를 Feature Map에 투영</li>
<li>RoI Pooling → 7×7 고정 크기</li>
<li>FC Layer → Feature Vector 생성</li>
<li>두 갈래 출력</li>
<li>Softmax → 분류</li>
<li>BBR → 박스 위치 보정</li>
<li>NMS로 최종 결과 생성</li>
</ol>
<h4 id="🔹-핵심-기술">🔹 핵심 기술</h4>
<ul>
<li>RoI Pooling<ul>
<li>다양한 크기의 영역 → 동일 크기 변환</li>
</ul>
</li>
<li>SPP 아이디어 활용</li>
<li>Multi-task Loss<ul>
<li>Classification + BBR 동시에 학습</li>
</ul>
</li>
</ul>
<h4 id="🔹-장점">🔹 장점</h4>
<ul>
<li>✅ CNN 연산 2000 → 1번 (속도 대폭 향상)</li>
<li>✅ Warping 제거 → 성능 유지</li>
<li>✅ End-to-End 학습 가능</li>
<li>✅ 정확도 증가</li>
</ul>
<h4 id="🔹-한계">🔹 한계</h4>
<ul>
<li>❌ Selective Search 여전히 느림 (CPU 병목)
→ 완전한 실시간 처리 불가</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Obect detection (2) R-CNN]]></title>
            <link>https://velog.io/@dongmin_1999/Obect-detection-2-R-CNN</link>
            <guid>https://velog.io/@dongmin_1999/Obect-detection-2-R-CNN</guid>
            <pubDate>Tue, 31 Mar 2026 09:36:28 GMT</pubDate>
            <description><![CDATA[<p>이 글에서는 초창기 Object Detection 분야에 큰 획을 그은 모델인 <strong>R-CNN (Regions with CNN features)</strong>에 대해 다룬다.</p>
<h1 id="📌-r-cnn이란">📌 R-CNN이란?</h1>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/0a422395-a639-42cf-b541-e933c7ff4e5e/image.png" alt=""></p>
<p>R-CNN이란 이미지 내에서 객체의 위치를 찾고 (Localization) 그 종류를 분류(Classification)하는 딥러닝(CNN)을 본격적으로 도입한 객체 탐지 (Object Detection) 모델이다.</p>
<h1 id="구조">구조</h1>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/016f9c23-f746-4cce-b229-58988bfbb486/image.png" alt=""></p>
<h2 id="🎯-r-cnn의-수행-과정">🎯 R-CNN의 수행 과정</h2>
<ol>
<li><p>Input Image
먼저 탐지하고자 하는 이미지를 입력한다.</p>
</li>
<li><p>Region proposal 
입력 이미지에 Selective search 알고리즘을 적용하여, 객체가 존재할 가능성이 높은 영역 (Region Proposal)을 약 2000개를 추출하고 각각에 Bounding Box를 생성한다.</p>
</li>
<li><p>Warping 
추출된 2,000개의 후보 영역들은 저마다 크기와 비율이 다르다. 이를 CNN 모델에 넣기 위해서는 크기를 통일해야 하므로, 비율을 무시하고 강제로 늘리거나 줄여 227x227 픽셀 사이즈로 변형(Warping) 한다.</p>
</li>
</ol>
<blockquote>
<p>💡 왜 227x227 픽셀인가?
R-CNN은 특징 추출을 위해 당시 ImageNet 우승 모델이었던 AlexNet을 가져와 사용하였다. 이 모델의 고정된 입력 사이즈가 바로 227x227 픽셀이다.</p>
</blockquote>
<ol start="4">
<li><p>CNN feature extract
Warping을 통해 동일한 크기로 조절된 2,000개의 Bounding Box 들을 CNN에 통과시켜, 이미지를 대표하는 고유한 특징 벡터(Feature Vector)를 추출한다.</p>
</li>
<li><p>Image Classification
CNN을 통해 추출된 특징 벡터를 바탕으로, 해당 영역이 어떤 객체인지 분류합니다. 이때 분류기(Classifier)로는 딥러닝 내부의 Softmax 대신, 기계학습 모델인 <strong>SVM (Support Vector Machine)</strong>을 사용한다.</p>
</li>
</ol>
<blockquote>
<p>💡 왜 분류기로 SVM을 썼을까?
논문에 따르면, 당시 CNN 자체의 분류기(Softmax)를 사용하는 것보다 SVM을 따로 붙여서 학습시키는 것이 정확도 측면에서 더 좋은 결과를 보여주었기 때문이다. SVM을 거치고 나면 2,000개의 Bounding Box들은 각각 특정 객체일 확률값을 가지게 된다.</p>
</blockquote>
<ol start="6">
<li><p>Non-Maximum Suppression(NMS)
2,000개의 박스를 모두 최종 결과로 사용지는 않는다. 하나의 동일한 객체 주변에 여러 개의 Bounding Box가 겹쳐서 예측되는 경우가 많기 때문이다. 이를 해결하기 위해 NMS를 적용하여, 겹치는 박스들 중 가장 확률이 높은 박스 하나만 남기고 나머지는 제거한다.</p>
</li>
<li><p>Bounding Box Regression(BBR)
Selective Search로 초기에 찾은 Bounding Box의 위치는 완벽하지 않고 다소 부정확하다. 따라서 박스의 위치와 크기를 더욱 정교하게 교정하여 모델의 최종 성능을 높이는 과정이 필요한데, 이를 Bounding Box Regression이라고 한다. 
실제 정답(Ground Truth)을 $G$, 초기에 예측한 Bounding Box를 $P$라고 할 때, 위치는 중심점 좌표 $(x, y)$와 박스의 크기 $(width, height)$로 표현된다.
$$
P^i = (P^i_x, P^i_y, P^i_w, P^i_h)  \quad G = (G_x, G_y, G_w, G_h)
$$</p>
<p>BBR의 핵심 목표는 입력된 $P$를 이동시키고 크기를 조절하여 정답 $G$에 최대한 가깝게 예측($\hat{G}$)하는 것이다. 이를 위해 $x, y$ 좌표에는 이동량($d_x, d_y$)을 더하고, 너비와 높이에는 크기 조절 비율($\exp(d_w), \exp(d_h)$)을 곱해 예측치 $\hat{G}$를 만든다.여기서 우리가 학습시켜야 하는 것은 $P$를 $\hat{G}$로 이동시키는 $d$ 함수이다. 반면, $P$를 실제 정답 $G$로 이동시키기 위해 필요한 &#39;이상적인 이동량&#39;은 $t$ 함수로 정의한다.
$$\begin{aligned}
\hat{G}<em>x &amp;= P_w d_x(P) + P_x \quad\quad (1) \qquad\qquad &amp; t_x &amp;= (G_x - P_x)/P_w \quad (6) \
\hat{G}_y &amp;= P_h d_y(P) + P_y \quad\quad (2) \qquad\qquad &amp; t_y &amp;= (G_y - P_y)/P_h \quad (7) \[1.5em]
\hat{G}_w &amp;= P_w \exp(d_w(P)) \quad\quad (3) \qquad\qquad &amp; t_w &amp;= \log(G_w/P_w) \quad (8) \[1.5em]
\hat{G}_h &amp;= P_h \exp(d_h(P)) \quad\quad (4) \qquad\qquad &amp; t_h &amp;= \log(G_h/P_h) \quad (9)
\end{aligned}$$
최종적인 Loss Function(손실 함수)은 예측된 이동량($d$ 함수)과 이상적인 이동량($t$ 함수) 간의 차이(MSE)에, 과적합을 방지하기 위한 L2 정규화(Normalization) 항이 추가된 형태를 띤다.
$$
\mathbf{w}</em>{\star} = \underset{\hat{\mathbf{w}}<em>{\star}}{\text{argmin}} \sum</em>{i}^{N} (t_{\star}^{i} - \hat{\mathbf{w}}<em>{\star}^{\text{T}} \phi</em>{5}(P^{i}))^{2} + \lambda |\hat{\mathbf{w}}<em>{\star}|^{2} \quad \quad (5)$$
$$
d</em>{\star}(P) = \mathbf{w}<em>{\star}^{\text{T}} \phi</em>{5}(P)
$$</p>
</li>
</ol>
<h2 id="⚠️-r-cnn의-단점">⚠️ R-CNN의 단점</h2>
<p>R-CNN은 Object Detection 분야에 딥러닝을 성공적으로 안착시킨 획기적인 모델이었지만, 초기 모델인 만큼 여러 가지 명확한 단점들이 존재하였다.</p>
<ol>
<li><p>Warping으로 인한 성능 저하:
AlexNet의 입력 사이즈에 맞추기 위해 2,000개의 이미지를 강제로 찌그러뜨리는 Warping 과정에서 이미지의 기하학적 왜곡과 정보 손실이 발생하여 객체 인식 성능이 떨어진다.</p>
</li>
<li><p>너무 느린 속도 (연산량 폭발):
Selective Search를 통해 뽑힌 2,000개의 Region Proposal 후보를 일일이 각각 CNN에 집어넣어 연산해야 하므로, 학습(Training)과 추론(Testing)에 엄청난 시간이 소요된다.</p>
</li>
<li><p>GPU 비효율성:
핵심 과정인 Selective Search나 SVM은 CPU 기반 알고리즘으로, GPU 병렬 처리의 이점을 살리기에 적합한 구조가 아니다.</p>
</li>
<li><p>분리된 학습 구조 (Not End-to-End):
CNN, SVM, Bounding Box Regression이라는 세 가지 모델이 각각 따로 노는 구조이다. 연산이 공유되지 않을뿐더러, SVM이나 BBR을 학습시킨 결과가 CNN으로 역전파되지 않아 CNN의 가중치를 업데이트할 수 없다. 즉, 한 번에 통째로 학습시키는 End-to-End 훈련이 불가능하다.</p>
<blockquote>
<p>💡 쉽게 말해 이런 문제가 발생한다!
CNN에서 넘겨준 Feature 데이터의 품질이 떨어져서 Loss가 크게 발생하더라도 역전파 과정을 통해 가중치를 수정할 수 없다. 결국 모델 전체가 유기적으로 학습되지 못하고, 사용자가 &#39;CNN 특징 추출 $\rightarrow$ 디스크에 수백 GB 단위로 따로 저장 $\rightarrow$ SVM 따로 학습 $\rightarrow$ BBR 따로 학습&#39; 이라는 번거로운 과정을 단계별로 끊어서 진행해야 한다.</p>
</blockquote>
</li>
</ol>
<p>이러한 R-CNN의 속도 문제와 구조적 한계를 극복하기 위해, 다음 포스팅에서는 한층 진화한 Fast R-CNN에 대해 알아보겠다!</p>
<h2 id="요약">요약</h2>
<h4 id="🔹-개념">🔹 개념</h4>
<ul>
<li>CNN을 처음으로 Object Detection에 적용한 모델</li>
<li>Region Proposal + CNN + SVM + BBR 구조</li>
</ul>
<h4 id="🔹-동작-과정">🔹 동작 과정</h4>
<ol>
<li>Selective Search → 약 2000개 후보 영역 생성</li>
<li>각 영역을 227×227로 변형 (Warping)</li>
<li>CNN으로 특징 추출</li>
<li>SVM으로 분류</li>
<li>BBR로 박스 보정</li>
<li>NMS로 중복 제거</li>
</ol>
<h4 id="🔹-핵심-특징">🔹 핵심 특징</h4>
<ul>
<li>CNN + 전통 ML(SVM) 혼합 구조</li>
<li>단계별로 따로 학습 (비 end-to-end)</li>
</ul>
<h4 id="🔹-문제점-핵심">🔹 문제점 (핵심)</h4>
<ul>
<li>❌ 속도 매우 느림 (2000번 CNN 실행)</li>
<li>❌ Warping으로 정보 손실</li>
<li>❌ GPU 활용 어려움</li>
<li>❌ End-to-End 학습 불가</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Object detection (1)  개요]]></title>
            <link>https://velog.io/@dongmin_1999/Object-detection-1-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@dongmin_1999/Object-detection-1-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Tue, 31 Mar 2026 02:37:46 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-object-detection이란-무엇인가">📌 Object Detection이란 무엇인가?</h1>
<p>이번 포스팅을 시작으로 컴퓨터 비전 분야의 핵심 기술 중 하나인 <strong>Object Detection(객체 탐지)</strong>에 대해 시리즈로 다루어보려고 한다.</p>
<p>딥러닝을 활용한 이미지 인식 기술은 하루가 다르게 발전하고 있다. 앞으로 이 시리즈를 통해 딥러닝 기반 Object Detection의 역사와 핵심 모델들을 하나씩 파헤쳐 볼 예정이다.</p>
<h3 id="object-detection-포스팅-로드맵">Object detection 포스팅 로드맵:</h3>
<ol>
<li>Object Detection의 개요</li>
<li>R-CNN: 초기의 Object detection task</li>
<li>Fast R-CNN: 속도와 정확도를 끌어올리다.</li>
<li>Faster R-CNN: 완벽한 End-to-End 모델의 탄생</li>
<li>YOLO: 실시간 객체 탐지의 혁명</li>
<li>SSD: 다양한 크기의 객체를 한 번에 잡아내다.</li>
</ol>
<hr>
<h2 id="1-object-detection객체-탐지-이란">1. Object Detection(객체 탐지) 이란?</h2>
<p>간단히 설명하자면,  Object Detection은 이미지나 영상 속에서 <strong>&quot;어떤 객체가 어디에 있는지&quot;</strong>를 찾아내는 기술이다.
비슷한 개념인 Image classification, localization, obect detection을 비교해보자.</p>
<ul>
<li>Image classification : 이미지 안에 무엇이 있는가?</li>
<li>Localization : 객체가 어디에 있는가?</li>
<li>Obect detection : 여러 객체들이 각각 무엇이고, 어디에 있는가?</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/e83a759c-c5a5-4a62-8c64-438fed7d4ca1/image.png" alt=""></p>
<p>즉, Object Detection은 Classification과 Localization이 결합된 형태이며, 보통 한 이미지 내의 &#39;다수의 객체&#39;와 &#39;위치 정보&#39;를 찾아내는 복합적인 작업이다.</p>
<hr>
<h2 id="2-알아야-할-핵심-용어">2. 알아야 할 핵심 용어</h2>
<p>Object Detection 논문이나 자료를 읽기 위해 꼭 알아두어야 할 기본 개념들이 있다. 다음 포스팅에서 다룰 R-CNN 모델들과 YOLO를 이해하기 위한 필수 용어들이다.</p>
<ol>
<li>Bounding Box : 객체의 위치를 나타내기 위해 객체를 둘러싸는 직사각형 박스이다. 
모델은 이 박스의 위치 좌표(보통 중심점의 x, y 좌표와 박스의 너비 w, 높이 h)를 예측하도록 학습된다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/3cd38cbe-2d04-43eb-aac5-5a4b8bb4a638/image.png" alt=""></p>
<ol start="2">
<li><p>Confidence Score (신뢰도 점수) : Bounding Box 안에 객체가 있을 확률을 의미한다. 아래 이미지에서는 Cat Box에는 고양이가 있을 확률이 98%, Fluffball Box에는 Fluffball가 있을 확률이 65%로 나타난다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/bc8aa9a7-4919-4709-96a8-fdc70649445c/image.png" alt=""></p>
</li>
<li><p>Conditional Class Probability (조건부 클래스 확률) : 이미지 안에서 어떤 객체가 특정 클래스에 속할 확률을 의미한다.
예) 객체가 있다고 할 때:</p>
<ul>
<li>사람일 확률 : 0.8</li>
<li>강아지일 확률 : 0.1</li>
</ul>
</li>
<li><p>NMS (Non-Maximum Suppression) : 하나의 객체에 여러 개의 Bounding Box가 겹쳐서 예측되는 현상을 방지하기 위한 기법이다. 여러 박스 중 가장 Confidence Score가 높은 박스 하나만 남기고, 나머지 겹치는 박스들은 제거(Suppression)하여 최종 결과를 깔끔하게 만들어준다. <img src="https://velog.velcdn.com/images/dongmin_1999/post/7d05d5fb-2567-4a48-af7b-978f696dce6b/image.png" alt=""></p>
</li>
<li><p>IoU (Intersection over Union) : 모델이 예측한 값과 실제 값(Ground Truth)이 얼마나 일치하는지를 평가하는 지표이다. IoU 값이 1에 가까울수록 예측을 잘 했다고 판단할 수 있다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/ec10538e-86d5-4db4-a1c6-987649cb394e/image.png" alt=""></p>
<blockquote>
<p>$\text{IoU} = \frac{\text{예측 박스와 정답 박스의 교집합 영역}}{\text{예측 박스와 정답 박스의 합집합 영역}}$</p>
</blockquote>
</li>
<li><p>Region Proposal : 객체가 있을 것 같은 영역을 미리 찾아내어 그 영역 내에서만 객체를 찾는 방식
이 알고리즘은 기존의 Sliding window방식의 비효율성을 극복하기 위한 것으로, 초기 R-CNN에서는 Selective Search 알고리즘을 사용하였다.</p>
<blockquote>
<p>Sliding window : 이미지에서 물체를 찾기 위해 다양한 크기와 비율의 window를 이동시키며 전체 영역을 탐색하는 방식
Selective search : 이미지를 비슷한 영역끼리 합쳐가면서 객체가 있을만한 영역을 찾는 알고리즘</p>
</blockquote>
</li>
<li><p>RoI (Region of Interest) : R-CNN에서 region proposal 알고리즘을 통해 생성된 후보 영역들을 RoI(Region of Interest)라고 하며, 모델이 실제로 처리하는 입력 영역을 의미한다.</p>
</li>
<li><p>Ground truth : 지도학습에서 input 데이터와 정답인 label을 이용하여 학습하는데, 이때 신경망 모델이 예측한 값이 아닌 실제 정답 label을 <strong>ground truth</strong> 라고 한다.</p>
</li>
</ol>
<hr>
<h2 id="3-object-detection의-두-가지-큰-흐름">3. Object Detection의 두 가지 큰 흐름</h2>
<p>Object Detection 알고리즘은 작동 방식에 따라 <strong>1-Stage Detector</strong>와 <strong>2-Stage Detector</strong>로 나뉜다.</p>
<h4 id="2-stage-detector-정확도-중심">2-Stage Detector (정확도 중심)</h4>
<ul>
<li><p>동작 방식: 객체가 있을 만한 위치를 먼저 찾고(Region Proposal), 그 영역에 대해 객체의 종류를 분류(Classification)하는 두 단계를 거친다.</p>
</li>
<li><p>특징: 정확도는 매우 높지만, 두 단계를 거쳐야 하므로 속도가 상대적으로 느리다.</p>
</li>
<li><p>대표 모델: R-CNN, Fast R-CNN, Faster R-CNN</p>
</li>
</ul>
<h4 id="1-stage-detector-속도-중심">1-Stage Detector (속도 중심)</h4>
<ul>
<li><p>동작 방식: 위치를 찾는 작업과 객체를 분류하는 작업을 동시에 한 번에 처리한다.</p>
</li>
<li><p>특징: 매우 빠른 처리 속도를 자랑하여 실시간(Real-time) 탐지에 적합하다. 초기에는 2-Stage에 비해 정확도가 낮았으나, 발전을 거듭하며 그 격차를 거의 좁혔다.</p>
</li>
<li><p>대표 모델: YOLO, SSD</p>
</li>
</ul>
<h2 id="마무리하며">마무리하며</h2>
<p>이번 포스팅에서는 Object Detection의 기본 개념과 평가 지표, 그리고 크게 두 갈래로 나뉘는 모델의 흐름을 가볍게 살펴보았다.</p>
<p>이러한 배경지식을 바탕으로, 다음 포스팅에서는 본격적으로 딥러닝 기반 Object Detection의 물꼬를 튼 기념비적인 모델, <strong>R-CNN</strong>에 대해 자세히 알아보겠습니다.</p>
<p>어떻게 무식하지만 확실한 방법으로 객체를 찾아냈는지, 다음 글에서 확인해 보세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[YouTube-8M 영상 데이터를 활용한 다중 라벨 분류 실습]]></title>
            <link>https://velog.io/@dongmin_1999/YouTube-8M-%EC%98%81%EC%83%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%8B%A4%EC%A4%91-%EB%9D%BC%EB%B2%A8-%EB%B6%84%EB%A5%98-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@dongmin_1999/YouTube-8M-%EC%98%81%EC%83%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%8B%A4%EC%A4%91-%EB%9D%BC%EB%B2%A8-%EB%B6%84%EB%A5%98-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Fri, 27 Mar 2026 10:15:11 GMT</pubDate>
            <description><![CDATA[<p>이번 스터디에서는 지난 주에 진행했던 최적화 함수와 활성화 함수에 대해 더 깊이 공부하고자 실습을 추가로 공부하기로 하였고 실습부분을 다뤄보고자 한다.</p>
<p>활성화함수와 최적화 함수를 실제로 다뤄보기 위해 YouTube-8M 데이터셋을 선택하였고 이유는 다음과 같다 ! </p>
<h1 id="1-🎬-youtube-8m-데이터셋이란">1. 🎬 YouTube-8M 데이터셋이란?</h1>
<ol>
<li><p>YouTube-8M 데이터셋은 구글에서 제공하는 대규모 비디오 데이터셋이다.
<code>.mp4</code> 나 <code>.avi</code>  같은 원본 영상 파일이 아닌 수백만 개의 유튜브 영상에서 시각적, 청각적 특징만을 추출하여 1차원 벡터 형태로 압축한 데이터이다.</p>
</li>
<li><p>데이터의 구성: 이 데이터셋은 텐서플로우의 표준 데이터 포맷인 TFRecord 파일 형태로 제공된다. 전처리 과정 없이 바로 신경망에 넣을 수 있도록, 하나의 데이터(Example) 안에 입력값과 정답이 나란히 묶여 있다.</p>
</li>
</ol>
<ul>
<li>Feature (입력 데이터, $X$):<ul>
<li>시각(Video) 피처: 구글의 최고 성능 이미지 모델(Inception-V3)을 통과시켜 뽑아낸 1024차원의 압축된 벡터이다. </li>
<li>청각(Audio) 피처: 오디오 모델(VGGish)을 통해 뽑아낸 128차원의 압축된 벡터이다.</li>
</ul>
</li>
<li>Label (정답 데이터, $Y$):<ul>
<li>해당 영상이 어떤 내용을 담고 있는지 알려주는 카테고리 정보이다.</li>
<li>총 3,862개의 라벨이 존재하며, 하나의 영상에 &#39;자동차&#39;, &#39;도로&#39;, &#39;운전&#39;처럼 여러 개의 정답이 동시에 붙어 있는 다중 라벨 형태를 띤다.</li>
</ul>
</li>
</ul>
<h1 id="2-왜-youtube-8m-데이터셋을-사용할까">2. 왜 Youtube-8M 데이터셋을 사용할까?</h1>
<ul>
<li>압도적인 데이터 규모 : 구글이 제공하는 수백만 개의 실제 유튜브 영상 데이터를 다뤄볼 수 있다.</li>
<li>컴퓨팅 자원의 절약 : 유튜브의 원본 영상을 직접 다운로드하는 방식이 아니라 구글이 이미 영상 데이터의 특징을 추출해놨기 때문에 고성능 GPU 없이도 대용량 데이터 학습 실습을 돌려볼 수 있고 CNN 튜닝같은 작업을 할 필요 없다.<blockquote>
<p>CNN은 특징을 추출하는 알고리즘</p>
</blockquote>
</li>
<li>멀티 라벨 분류 : 현실 세계의 영상은 단 하나의 정답으로 정의되지 않는다. 이 데이터셋을 통해 하나의 입력 데이터에 여러 개의 정답을 동시에 예측하는 신경망 구조를 자연스럽게 설계해 볼 수 있다.</li>
</ul>
<h1 id="3-실습">3. 실습</h1>
<pre><code class="language-python">from google.colab import auth
auth.authenticate_user()</code></pre>
<p>구글 스토리지에 저장된 데이터를 가져오기 위한 권한을 얻기 위해 작성</p>
<pre><code class="language-python">import tensorflow as tf
files = tf.io.gfile.glob(&#39;gs://youtube8m-ml-us-east1/2/frame/train/*.tfrecord&#39;)
</code></pre>
<p>YouTube-8M 전용 폴더 안에 있는 텐서플로우 데이터 형식(.tfrecord)으로 된 모든 파일 가져오기</p>
<pre><code class="language-python">dataset = tf.data.TFRecordDataset(files)
for record in dataset.take(1):
    print(record)</code></pre>
<p>데이터가 잘 연결되었는지 첫 번째 데이터만 딱 1개 가져와서 출력한다.
이때 출력 결과를 보면 b&#39;\n&amp;\n...&#39; 처럼 알 수 없는 문자열이 나오는데, 이는 데이터가 컴퓨터가 가장 빠르게 읽을 수 있는 이진 압축 형태로 뭉쳐져 있기 때문이다.</p>
<pre><code class="language-python">feature_description = {
    &#39;id&#39;: tf.io.FixedLenFeature([], tf.string), # Corrected from video_id to id
    &#39;labels&#39;: tf.io.VarLenFeature(tf.int64),
    &#39;audio&#39;: tf.io.VarLenFeature(tf.float32), # Changed from mean_audio to audio and VarLenFeature
    &#39;rgb&#39;: tf.io.VarLenFeature(tf.float32), # Added rgb feature
}

def parse_example(example_proto):
    return tf.io.parse_single_example(example_proto, feature_description)

parsed_dataset = dataset.map(parse_example)
</code></pre>
<p>이진 데이터를 우리가 쓸 수 있는 tensor 형태로 해독하는 과정이다.
<code>feature_description</code> 딕셔너리를 통해 문자열 형태의 <code>id</code>, 정수 형태의 <code>labels</code>, 실수 형태의 <code>audio</code>가 들어있다라고 알려준다.
<code>parse_example</code> 함수와 <code>dataset.map()</code>을 이용해 전체 데이터셋에 이 설명서를 적용시켜 압축을 풀고 번역해주어야 인공신경망에 데이터를 넣고 학습시킬 수 있다.  </p>
<blockquote>
</blockquote>
<ul>
<li><code>id</code> : 유튜브 원본 영상의 식별키</li>
<li><code>rgb</code>, <code>audio</code> : 원본 영상에서 추출한 feature </li>
<li><code>labels</code> : 해당 영상이 어떤 영상인지 나타내는 카테고리 (모델이 맞혀야 할 정답)</li>
</ul>
<pre><code class="language-python"># 1. 인공신경망 모델 정의 (은닉층 추가 및 확장)
model = tf.keras.Sequential([
    tf.keras.layers.Dense(1024, activation=&#39;relu&#39;, input_shape=(1152,)), 
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Dense(512, activation=&#39;relu&#39;),  # 1024개의 정보를 512개로 압축하면서 더 깊은 수준의 특징을 추출
    tf.keras.layers.Dense(3862, activation=&#39;sigmoid&#39;)
])</code></pre>
<ul>
<li>신경망의 첫 번째 관문. 시각정보 1024차원과 청각정보 128차원이 합쳐진 총 1152개의 데이터가 들어오는 입구의 크기</li>
<li>학습할 때마다 랜덤으로 30%의 뉴런을 잠시 꺼서 모델이 특정 뉴런에만 의존하지 않도록 함
(과적합 방지)</li>
<li>1024개의 정보를 512개로 압축하면서 더 깊은 수준의 특징을 추출</li>
<li>최종 출구. 데이터셋의 총 labels 개수인 3862개로 출력 노드를 설정하고 영상 하나에 정답이 여러 개일 수 있으므로 sigmoid 함수 사용. 하나만 고르는 문제였다면 softmax를 사용했을 것</li>
</ul>
<pre><code class="language-python"># 2. 손실 함수와 최적화 알고리즘 설정
loss_fn = tf.keras.losses.BinaryCrossentropy()
optimizer = tf.keras.optimizers.Adam(learning_rate=0.005)</code></pre>
<ul>
<li><p>BinaryCrossentropy : 손실함수. 예측이 얼마나 틀렸는지를 계산하는 함수</p>
</li>
<li><p>optimizer : 최적화 알고리즘(Adam). 손실함수를 바탕으로 모델의 가중치를 어느 방향으로, 얼마나 수정할지 결정 </p>
</li>
<li><p>learning_rate : 학습 속도</p>
<pre><code class="language-python"># 3. 데이터 전처리 함수 (rgb 포함)
def preprocess(data):
  labels = tf.sparse.to_indicator(data[&#39;labels&#39;], vocab_size=3862)
  labels = tf.cast(labels, tf.float32)
  labels = tf.reshape(labels, [3862])

  # 오디오 특징 처리 (128차원)
  audio_dense = tf.sparse.to_dense(data[&#39;audio&#39;])
  audio_dense = tf.cast(audio_dense, tf.float32)
  audio_flat = tf.reshape(audio_dense, [-1])
  pad_len_audio = tf.maximum(0, 128 - tf.shape(audio_flat)[0])
  audio_padded = tf.pad(audio_flat, [[0, pad_len_audio]])
  audio_feature = audio_padded[:128]
  audio_feature.set_shape([128])

  # 시각(RGB) 특징 처리 (1024차원)
  rgb_dense = tf.sparse.to_dense(data[&#39;rgb&#39;])
  rgb_dense = tf.cast(rgb_dense, tf.float32)
  rgb_flat = tf.reshape(rgb_dense, [-1])
  pad_len_rgb = tf.maximum(0, 1024 - tf.shape(rgb_flat)[0])
  rgb_padded = tf.pad(rgb_flat, [[0, pad_len_rgb]])
  rgb_feature = rgb_padded[:1024]
  rgb_feature.set_shape([1024])

  # 오디오와 RGB 특징을 하나로 합치기 (128 + 1024 = 1152차원)
  combined_features = tf.concat([audio_feature, rgb_feature], axis=0)

  return combined_features, labels    </code></pre>
<h3 id="🔥모델은-정해진-크기의-숫자-벡터만-입력받을-수-있기-때문에-전처리를-진행한다">🔥모델은 정해진 크기의 숫자 벡터만 입력받을 수 있기 때문에 전처리를 진행한다.</h3>
<p><code>parsed_dataset</code>  데이터는 딕셔너리 형태로 길이가 제각각이다.</p>
<blockquote>
<pre><code class="language-python">{
&#39;labels&#39;: SparseTensor(...),   
&#39;audio&#39;: SparseTensor(...), 
&#39;rgb&#39;: SparseTensor(...)    
}</code></pre>
</blockquote>
<pre><code></code></pre></li>
<li><p>labels 을 3862차원 벡터로 변환</p>
</li>
<li><p>padding(길이 맞추기) : <code>audio_feature → 128차원</code>,  <code>rgb_feature → 1024차원</code></p>
</li>
<li><p>audio feature과 rgb_feature를 하나로 합친다.</p>
</li>
</ul>
<pre><code class="language-python"># 배치 단위로 데이터 묶기 (한 번에 32개씩 학습)
train_dataset = parsed_dataset.map(preprocess).batch(32)</code></pre>
<p>데이터를 여러 개 묶어서 한 번에 학습하기 위해 배치로 묶기</p>
<ul>
<li>속도 : cpu/gpu 병렬 처리 가능</li>
<li>안정적인 학습 : 1개만 학습하면 흔들림이 크지만 여러 개의 평균으로 학습하면 안정적이다.</li>
<li>메모리 효율 : 전체 데이터는 너무 크기 때문에 batch 단위로 나누어서 진행한다.</li>
</ul>
<pre><code class="language-python"># 4. 커스텀 학습 루프
epochs = 50  # 학습을 50번 반복
print(&quot;=== 학습 시작 ===&quot;)
for epoch in range(epochs):
    print(f&quot;\n[Epoch {epoch + 1}/{epochs}]&quot;)
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset.take(50)): # 배치 단위(32개씩) 학습, 한 epoch당 50번만 학습

        with tf.GradientTape() as tape: # 기울기 계산
            logits = model(x_batch_train, training=True) # 순전파 : 입력 -&gt; 모델 통과, 예측값 생성
            loss_value = loss_fn(y_batch_train, logits) # 손실 계산 : 예측값과 실제 값 사이의 오차 계산

        grads = tape.gradient(loss_value, model.trainable_weights) # 역전파 : 각 가중치가 얼마나 잘못되었는지 계산
        optimizer.apply_gradients(zip(grads, model.trainable_weights)) # 가중치 업데이트 : 계산된 기울기를 이용하여 가중치를 수정

        if step % 10 == 0:
            print(f&quot;  Step {step}: Loss(ocha) = {float(loss_value):.4f}&quot;)

    print(f&quot;  Step {step}: Loss(ocha) = {float(loss_value):.4f}&quot;)

print(&quot;\n=== 학습 종료 ===&quot;)</code></pre>
<h4 id="한-번의-step에서-일어나는-일-step-50번--epoch-1번">한 번의 step에서 일어나는 일 (step 50번 = epoch 1번)</h4>
<pre><code class="language-python">입력(x_batch)
   ↓
모델 예측 (logits)
   ↓
오차 계산 (loss)
   ↓
기울기 계산 (gradient)
   ↓
가중치 수정 (update)</code></pre>
<hr>
<p>이 데이터셋은 label이 매우 많기 때문에 <code>BinaryCrossentropy</code> 함수를 사용했을 때 매우 치명적인 문제가 발생한다 !</p>
<p>영상 1개당 실제 정답은 평균적으로 3~5개뿐이라 나머지 3850여 개는 전부 오답이다.
따라서 모델이 학습하다가 모든 데이터에 대해 &#39;아니다&#39; 라고 예측하게 되면 찍어도 99.9%는 맞추기 때문에 오차율이 거의 0%가 나온다.
결과적으로 모델이 아무것도 예측 안하는 방향으로 학습된다.</p>
<h2 id="🔥-해결-방법">🔥 해결 방법</h2>
<h3 id="✅-1-positive-class에-가중치-주기">✅ 1. Positive class에 가중치 주기</h3>
<p>❗ 정답(1)을 더 중요하게 취급 -&gt; 정답을 틀렸을 때 loss가 크게 늘어남
🔹 방법: weighted BCE</p>
<pre><code class="language-python">def weighted_bce(y_true, y_pred):
    y_pred = tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7)

    pos_weight = 50.0 
    neg_weight = 1.0

    loss = -(pos_weight * y_true * tf.math.log(y_pred) +
             neg_weight * (1 - y_true) * tf.math.log(1 - y_pred))

    return tf.reduce_mean(loss)
# 2. 손실 함수와 최적화 알고리즘 설정
loss_fn = weighted_bce </code></pre>
<h4 id="weighted_bce-함수">weighted_bce 함수</h4>
<ul>
<li><p><code>y_true</code> : 실제 정답, <code>y_pred</code> : 예측값</p>
</li>
<li><p><code>y_pred</code> 값을 0과 1사이로 제한 : 안정성 확보</p>
</li>
<li><p><code>pos_weight = 50.0</code>,<code>neg_weight = 1.0</code> : 정답(1)을 더 중요하게 학습</p>
</li>
<li><p><code>loss</code> : 오차 계산</p>
<ul>
<li><p>정답이 1일 때: <code>y_true = 1</code>, <code>loss = - pos_weight * log(y_pred)</code></p>
<ul>
<li><code>y_pred</code> 값이 0에 가까울수록 <code>loss</code> 가 크게 증가</li>
</ul>
</li>
<li><p>정답이 0일 때: <code>y_true = 0</code>, <code>loss = - log(1 - y_pred)</code></p>
<ul>
<li><code>y_pred</code> 값이 1에 가까울수록 <code>loss</code> 가 크게 증가</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="✅-2-focal-loss">✅ 2. Focal Loss</h3>
<p>❗ 쉬운 문제는 무시하고, 헷갈리는(어려운) 문제에만 집중 -&gt; 모델이 이미 확신하고 있는 수많은 오답(0)에 대한 가중치를 대폭 깎아버리고, 맞히기 어려운 진짜 정답(1)을 찾는 데 모델의 모든 신경을 집중시킴.
🔹 방법: Focal Loss 도입</p>
<pre><code class="language-python">def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25):
    # 예측값을 0과 1 사이로 제한 (안정성 확보)
    y_pred = tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7)

    # 기존 Cross Entropy 계산
    cross_entropy = -y_true * tf.math.log(y_pred) - (1 - y_true) * tf.math.log(1 - y_pred)

    # 모델이 정답을 맞출 확률 (정답이면 y_pred, 오답이면 1-y_pred)
    p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)

    # Focal Factor 계산 : 맞추기 쉬운 데이터의 영향력을 확 낮춤
    focal_factor = tf.math.pow(1.0 - p_t, gamma)

    # Alpha Factor 계산 : 양성/음성 클래스 자체의 비율 가중치 조정
    alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)

    # 최종 Loss 계산 (기존 Loss에 두 가지 가중치를 곱함)
    loss = alpha_factor * focal_factor * cross_entropy

    return tf.reduce_mean(loss)

# 2. 손실 함수와 최적화 알고리즘 설정
loss_fn = focal_loss</code></pre>
<h4 id="focal_loss-함수">focal_loss 함수</h4>
<ul>
<li><p><code>y_true</code> : 실제 정답, <code>y_pred</code> : 예측값</p>
</li>
<li><p><code>gamma (γ)</code> : 모델이 이미 정답을 쉽게 맞히고 있는 데이터(예: 99.9%의 확률로 &#39;아니다&#39;라고 확신하는 무수한 0들)의 오차 반영률을 0에 가깝게 확 낮춰버리는 핵심 파라미터. (보통 2.0을 많이 사용)</p>
</li>
<li><p><code>alpha (α)</code> : 1과 0 자체의 비율 불균형을 한 번 더 잡아주는 가중치. (앞선 weighted_bce의 역할도 동시에 수행)</p>
</li>
<li><p><code>p_t</code> : 모델이 예측을 확신하는 정도.</p>
<ul>
<li><p>모델이 이미 &quot;이건 무조건 0이야!&quot;라고 확신해서 잘 맞히고 있다면, <code>p_t</code>가 1에 가까워지고 <code>focal_factor</code>는 0에 수렴함. -&gt; Loss 무시함</p>
</li>
<li><p>반대로 모델이 헷갈려하거나 예측을 틀렸다면, <code>focal_factor</code>가 커짐. -&gt; Loss를 크게 반영해서 혼냄</p>
</li>
</ul>
</li>
<li><p>결과적으로 수천 개의 오답(0)들이 뱉어내는 무의미한 Loss의 합을 억제하고, 모델이 놓치고 있는 진짜 정답(1)을 찾을 때만 오차가 크게 발생하도록 유도하여 학습 효율과 성능을 극대화한다.</p>
</li>
</ul>
<h1 id="4-결과">4. 결과</h1>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/4a6a9da0-6a41-4282-8be5-583259012606/image.png" alt=""></p>
<p>위에서 다룬 세 가지 손실 함수를 실제로 적용하여 30 Epoch 동안 학습시켰을 때의 오차(Loss) 감소 추이를 나타낸 그래프이다.</p>
<ul>
<li><p>BCE &amp; Weighted BCE: 그래프 상단에서 시작하여 Loss 값이 상대적으로 높게 누적된다. 특히 Weighted BCE는 정답(1)을 놓쳤을 때 강한 페널티를 주므로 초기 오차가 더 크게 잡힌다. 이 두 선은 수천 개의 &#39;쉬운 오답(0)&#39;에서 발생하는 미세한 오차들이 합쳐져 전체 Loss의 파이를 불필요하게 키우고 있음을 보여준다.</p>
</li>
<li><p>Focal Loss : 시작점부터 Loss 값이 바닥에 붙어 있다. 이는 처음부터 학습이 완벽했다는 뜻이 아니다. Focal Loss가 모델이 이미 &quot;이건 100% 오답(0)이야&quot;라고 확신하는 <strong>압도적 다수의 쉬운 문제들을 채점 대상에서 아예 무시</strong>해버렸기 때문이다. 오직 모델이 헷갈려하는 &#39;진짜 어려운 문제(정답 1)&#39;에 대한 순수한 오차만 남았음을 보여준다.</p>
</li>
</ul>
<h3 id="💡-결론">💡 결론</h3>
<p>YouTube-8M처럼 다중 라벨이면서 클래스 불균형이 극심한 현업 데이터를 다룰 때는 맹목적으로 모델의 깊이만 늘리거나 에폭을 돌리는 것은 의미가 없다. 데이터의 특성을 파악하고, Focal Loss처럼 모델의 시선을 &#39;진짜 중요한 문제&#39;로 강제 집중시키는 최적화 함수(Loss Function)의 선택이 성능을 좌우하는 핵심 키임을 깨달을 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘(7) DFS, BFS]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%987-DFS-BFS</link>
            <guid>https://velog.io/@dongmin_1999/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%987-DFS-BFS</guid>
            <pubDate>Thu, 26 Mar 2026 01:38:38 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-그래프-탐색의-꽃-dfs깊이-우선-탐색-정리">📌 그래프 탐색의 꽃, DFS(깊이 우선 탐색) 정리</h1>
<h3 id="1-그래프graph">1. 그래프(Graph)</h3>
<p>노드(Node)와 그 노드들을 연결하는 간선(Edge)으로 이루어진 네트워크망 자료구조</p>
<h3 id="2-탐색search">2. 탐색(Search)</h3>
<p>그래프 안에 있는 수많은 노드들을 특정 규칙에 따라 중복 없이, 한 번씩 빠짐없이 방문하는 과정</p>
<hr>
<h3 id="3-🕳️깊이-우선-탐색-dfs-depth-first-search">3. 🕳️깊이 우선 탐색 (DFS, Depth First Search)</h3>
<p>DFS는 갈림길이 나타나면 <strong>특정 루트로 최대한 깊숙이 파고들어 탐색한 뒤, 막다른 곳에 도달하면 직전 갈림길로 되돌아와 다른 루트를 탐색하는 방식</strong>이다.</p>
<h4 id="⚙️-dfs의-핵심-원리와-특징">⚙️ DFS의 핵심 원리와 특징</h4>
<p>DFS는 &#39;가장 최근에 지나온 갈림길로 되돌아간다&#39;는 특성 때문에, <strong>가장 나중에 들어온 데이터가 먼저 나가는 후입선출(LIFO) 구조인 스택(Stack)</strong>과 유사하다.</p>
<ul>
<li><p>사용 자료구조 : <code>stack</code>, 재귀함수</p>
</li>
<li><p>장점: 현재 경로상의 노드들만 기억하면 되므로 메모리를 적게 차지한다. 찾으려는 노드가 깊은 곳에 있다면 정답을 빠르게 찾을 수 있다.</p>
</li>
<li><p>단점: 정답이 없는 이상한 경로에 끝없이 빠질 수 있다. 또한 처음 발견한 정답이 최단 경로라는 보장이 없다.</p>
</li>
<li><p>주요 사용처: * 모든 경우의 수를 확인하되 조건에 안 맞으면 돌아가는 백트래킹 문제</p>
<ul>
<li><p>이동하는 경로의 특징(순서, 가중치)을 기억해야 하는 문제</p>
</li>
<li><p>그래프 내의 사이클(순환) 존재 여부를 파악할 때</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="4-🌊-너비-우선-탐색-bfs-breadth-first-search">4. 🌊 너비 우선 탐색 (BFS, Breadth-First Search)</h3>
<p>BFS는 시작 노드에서부터 <strong>가장 가까운 인접 노드들을 먼저 모두 탐색한 후, 그 다음으로 가까운 노드들을 탐색하며 점심적으로 넓게(Breadth) 퍼져나가는 방식</strong>이다.</p>
<h4 id="⚙️-bfs의-핵심-원리와-특징">⚙️ BFS의 핵심 원리와 특징</h4>
<p>BFS는 &#39;먼저 발견한 가까운 노드부터 순서대로 방문해야 한다&#39;는 특성 때문에, <strong>먼저 들어온 데이터가 먼저 나가는 선입선출(FIFO) 구조인 큐(Queue)</strong>를 반드시 사용해야 한다. (파이썬에서는 압도적인 속도를 위해 collections.deque를 사용한다.)</p>
<ul>
<li><p>사용 자료구조: Queue (파이썬의 deque 라이브러리)</p>
</li>
<li><p>장점: 출발지에서 목표지점까지의 &#39;최단 경로&#39;를 무조건 보장한다. (가까운 곳부터 찾으므로, 목표를 발견하는 순간 그 거리가 곧 최단 거리다.)</p>
</li>
<li><p>단점: 탐색해야 할 노드가 많아지면 큐에 저장해야 하는 데이터가 급증하여 메모리를 많이 잡아먹는다.</p>
</li>
<li><p>주요 사용처:</p>
<ul>
<li><p>미로 찾기나 지도에서 <strong>최단 거리(Shortest Path)</strong>를 구하는 문제</p>
</li>
<li><p><strong>&quot;몇 번의 이동(또는 일수) 만에 도달할 수 있는가?&quot;</strong>를 묻는 문제</p>
</li>
<li><p>시작점으로부터 주변으로 퍼져나가는 전염병, 바이러스, 영역 칠하기 문제</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="5-⚔️-dfs-vs-bfs-한눈에-비교하기">5. ⚔️ DFS vs BFS 한눈에 비교하기</h3>
<ul>
<li>DFS는 스택과 재귀함수를, BFS는 큐를 사용한다.</li>
<li>DFS는 최단경로를 보장하지 않지만 BFS는 최단경로를 보장한다</li>
<li>DFS는 메모리 사용이 적고 BFS는 메모리 사용이 많다.</li>
<li>DFS는 검색 속도가 BFS보다 느리다.</li>
<li>DFS는 경로의 특징 저장, 백트래킹 문제에 적합하고 BFS는 최단 거리 구하기, 최소 횟수 구하기에 적합하다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/d858418f-0910-4bf0-b2da-198680312a2b/image.png" alt=""></p>
<hr>
<h3 id="6-dfs-구현">6. DFS 구현</h3>
<pre><code class="language-python">  graph = {
      &#39;A&#39; : [&#39;B&#39;,&#39;C&#39;],
      &#39;B&#39; : [&#39;A&#39;,&#39;D&#39;,&#39;E&#39;],
      &#39;C&#39; : [&#39;A&#39;, &#39;F&#39;],
      &#39;D&#39; : [&#39;B&#39;],
      &#39;E&#39; : [&#39;B&#39;,&#39;F&#39;],
      &#39;F&#39; : [&#39;C&#39;,&#39;E&#39;]
  }

  def dfs_recursive(graph, start_node,visited=None):
    if visited is None:
      visited = []

    visited.append(start_node)

    for neighbor in graph[start_node]:
      if neighbor not in visited:
        dfs_recursive(graph, neighbor, visited)
    return visited

  print(dfs_recursive(graph,&#39;A&#39;))</code></pre>
<h3 id="7-bfs-구현">7. BFS 구현</h3>
<pre><code class="language-python">  from collections import deque

  graph = {
      &#39;A&#39;: [&#39;B&#39;, &#39;C&#39;],
      &#39;B&#39;: [&#39;A&#39;, &#39;D&#39;, &#39;E&#39;],
      &#39;C&#39;: [&#39;A&#39;, &#39;F&#39;],
      &#39;D&#39;: [&#39;B&#39;],
      &#39;E&#39;: [&#39;B&#39;, &#39;F&#39;],
      &#39;F&#39;: [&#39;C&#39;, &#39;E&#39;]
  }

  def bfs_queue(graph, start_node):
      visited = [] # 방문한 노드를 기록
      queue = deque([start_node]) # queue는 방문할 예정인 노드를 담은 리스트. 중복해서 방문할 수 없음

      while queue:
          node = queue.popleft() # 방문하고자 하는 노드

          if node not in visited:
              visited.append(node) # visited에 node가 없으면 visited에 node 추가 -&gt; node를 방문한 것

              queue.extend(graph[node]) # 방문한 노드와 인접한 노드를 큐에 저장

      return visited

  print(&quot;큐 BFS 방문 순서:&quot;, bfs_queue(graph, &#39;A&#39;))</code></pre>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/5fa3d773-cf82-4cba-af1a-d7f570b853a4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘(6) 스택과 큐]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%986-%EC%8A%A4%ED%83%9D%EA%B3%BC-%ED%81%90</link>
            <guid>https://velog.io/@dongmin_1999/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%986-%EC%8A%A4%ED%83%9D%EA%B3%BC-%ED%81%90</guid>
            <pubDate>Mon, 23 Mar 2026 08:40:27 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 가장 대표적인 자료구조인 스택(Stack)과 큐(Queue)에 대해 알아보고자 한다.</p>
<h3 id="1-📚-스택stack이란">1. 📚 스택(Stack)이란?</h3>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/32bb67cf-570e-45d8-b694-48d7a351bb35/image.png" alt=""></p>
<p>스택(Stack)은 가장 나중에 들어온 데이터가 가장 먼저 나가는 구조를 가진 자료구조이다.</p>
<p>이를 후입선출(LIFO, Last In First Out) 구조라고 한다.</p>
<hr>
<h3 id="11-✔️-기본-연산">1.1 ✔️ 기본 연산</h3>
<p>스택은 다음과 같은 핵심 연산을 가진다.</p>
<ul>
<li><p>push : 데이터를 스택에 삽입 
<img src="https://velog.velcdn.com/images/dongmin_1999/post/41a54a17-2105-4089-8bdd-0bc914b0f556/image.png" alt=""></p>
</li>
<li><p>pop : 가장 위에 있는 데이터를 제거
<img src="https://velog.velcdn.com/images/dongmin_1999/post/e41a0eff-a23a-4ab9-a660-129a50e6880c/image.png" alt=""></p>
</li>
<li><p>peek (또는 top) : 가장 위의 데이터를 확인하는 연산</p>
</li>
<li><p>empty : Stack이 비어있는지 확인하는 메서드</p>
</li>
</ul>
<p>👉 모든 연산은 스택의 맨 위(top) 에서만 이루어진다.</p>
<h3 id="12-특징">1.2 특징</h3>
<ul>
<li>후입선출 구조(LIFO)</li>
<li>리스트의 한쪽으로 삽입과 삭제 연산 수행</li>
</ul>
<hr>
<h3 id="13-✅-스택의-사용-사례-후입선출">1.3 ✅ 스택의 사용 사례 (후입선출)</h3>
<ul>
<li>웹 방문기록 뒤로가기</li>
<li>실행 취소(undo)</li>
<li>역 문자열 만들기</li>
</ul>
<hr>
<h3 id="14-💻-구현-코드-연결-리스트-활용">1.4 💻 구현 코드 (연결 리스트 활용)</h3>
<pre><code class="language-python">  class Stack:
    def __init__(self):
      self.items = []

    def push(self, data):
      self.items.append(data)

    def pop(self):
      if self.isEmpty():
        print(&quot;Stack is Empty&quot;)
        return None
      return self.items.pop()

    def peek(self):
      if self.isEmpty():
        print(&quot;Stack is Empty&quot;)
        return None
      return self.items[-1]

    def isEmpty(self):
      return len(self.items) == 0</code></pre>
<h3 id="15-📌-프로그래머스-올바른-괄호---스택stack을-활용한-풀이-lv2">1.5 📌 [프로그래머스] 올바른 괄호 - 스택(Stack)을 활용한 풀이 (Lv.2)</h3>
<p>구현 코드</p>
<pre><code class="language-python">class Stack:
    def __init__(self):
        self.items = []

    def push(self, data):
        self.items.append(data)

    def pop(self):
        if self.isEmpty():
            return 0
        return self.items.pop()

    def isEmpty(self):
        return len(self.items) == 0


def solution(s):
    stack = Stack()

    for char in s:
        if char == &#39;(&#39;:
            stack.push(char)

        elif char == &#39;)&#39;:
            if stack.isEmpty():
                return False

            stack.pop()
    return stack.isEmpty()</code></pre>
<hr>
<h3 id="2-📚-큐queue란">2. 📚 큐(Queue)란?</h3>
<p>큐(Queue)는 먼저 들어온 데이터가 먼저 나가는 구조를 가진 자료구조이다.
식당의 대기열이나 매표소에서 줄을 서는 모습을 생각하면 이해하기 쉽다. 이를 선입선출(FIFO, First In First Out) 구조라고 한다.</p>
<hr>
<h3 id="21-✔️-기본-연산">2.1 ✔️ 기본 연산</h3>
<p>큐는 다음과 같은 핵심 연산을 가진다.</p>
<ul>
<li><p>enqueue : 큐의 뒤쪽(Rear)에 새로운 데이터를 삽입
<img src="https://velog.velcdn.com/images/dongmin_1999/post/55e27068-f1ba-4e20-af99-998333b5a440/image.png" alt=""></p>
</li>
<li><p>dequeue : 큐의 앞쪽(Front)에서 데이터를 제거하고 반환
<img src="https://velog.velcdn.com/images/dongmin_1999/post/a5868e45-9003-4fd6-ba31-9c710501f1e1/image.png" alt=""></p>
</li>
</ul>
<p>👉 스택과 달리 삽입은 뒤(Rear) 에서, 삭제는 앞(Front) 에서 양방향으로 이루어지는 것이 특징이다.</p>
<h3 id="22-✔️-특징">2.2 ✔️ 특징</h3>
<ul>
<li>선입선출 구조(FIFO)</li>
<li>입구와 출구가 다름 (데이터가 들어오는 곳과 나가는 곳이 분리되어 있음)</li>
</ul>
<h3 id="23-✅-큐의-사용-사례">2.3 ✅ 큐의 사용 사례</h3>
<ul>
<li>프린터의 인쇄 대기열</li>
<li>콜센터 고객 대기시간 (먼저 전화한 고객부터 상담)</li>
<li>너비 우선 탐색(BFS, Breadth-First Search) 알고리즘</li>
<li>운영체제의 프로세스 스케줄링</li>
</ul>
<hr>
<h4 id="💻-구현-코드-연결-리스트-활용">💻 구현 코드 (연결 리스트 활용)</h4>
<p>스택에서 사용한 Node 클래스를 그대로 활용하여 큐를 구현할 수 있다.</p>
<pre><code class="language-python">class Queue:
  def __init__(self):
    self.items = []

  def enqueue(self, data):   # 데이터 추가
    self.items.append(data)

  def dequeue(self):         # 데이터 제거 (앞에서)
    if self.isEmpty():
      print(&quot;Queue is Empty&quot;)
      return None
    return self.items.pop(0)

  def peek(self):            # 맨 앞 요소 확인
    if self.isEmpty():
      print(&quot;Queue is Empty&quot;)
      return None
    return self.items[0]

  def isEmpty(self):
    return len(self.items) == 0
</code></pre>
<h3 id="3-deque-라이브러리">3. deque 라이브러리</h3>
<p>앞서 파이썬의 기본 리스트(List)를 활용하여 큐를 구현하고 pop(0)을 사용해 데이터를 맨 앞에서 빼내었다. 하지만 이 방식에는 아주 치명적인 단점이 있다.</p>
<p>파이썬의 리스트 구조상 맨 앞의 데이터를 빼내면, 그 뒤에 있는 수많은 데이터들이 모두 한 칸씩 앞으로 이동해야 한다. 이로 인해 시간 복잡도가 $O(N)$이 되어 데이터가 많을수록 속도가 기하급수적으로 느려진다.</p>
<p>이를 해결하기 위해 파이썬 내장 라이브러리인 <strong>collections.deque</strong>를 사용한다.</p>
<p>데크(deque)는 Double-Ended Queue의 약자로, 큐의 양쪽 끝에서 데이터의 삽입과 삭제가 모두 가능한 이중 연결 큐 자료구조이다. 기차 칸처럼 연결된 구조를 띄고 있어, 양 끝의 데이터를 넣거나 뺄 때 시간 복잡도가 $O(1)$로 압도적으로 빠르다.</p>
<hr>
<h3 id="31-핵심-연산">3.1 핵심 연산</h3>
<ul>
<li>append(x) : 데크의 오른쪽에 데이터를 삽입한다.</li>
<li>popleft(x) : 데크의 왼쪽에서 데이터를 제거하고 반환한다. </li>
<li>appendleft(x) : 데크의 왼쪽에 데이터를 삽입한다.</li>
<li>pop() : 데크의 오른쪽에서 데이터를 제거하고 반환한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘(5) 자료구조의 개념]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%985-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%9D%98-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@dongmin_1999/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%985-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%9D%98-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Mon, 23 Mar 2026 06:31:14 GMT</pubDate>
            <description><![CDATA[<h1 id="📚-자료구조data-structure-정리">📚 자료구조(Data Structure) 정리</h1>
<p>이번 포스팅에서는 <strong>자료구조(Data Structure)</strong>에 대해 공부한 내용을 정리해보려고 한다.
원래 자료구조는 알고리즘의 선수과목이지만, 함께 묶어서 이해하는 것이 더 자연스럽다고 생각하여 같이 정리한다.</p>
<hr>
<h3 id="📌-자료구조data-structure란">📌 자료구조(Data Structure)란?</h3>
<p>데이터를 효율적으로 저장하고 접근하고 수정, 관리하기 위한 방식이다.</p>
<hr>
<h3 id="📌-추상적-자료형adt과-자료구조의-관계">📌 추상적 자료형(ADT)과 자료구조의 관계</h3>
<p>자료구조를 제대로 이해하려면 추상적 자료형(Abstract Data Type, ADT) 개념이 중요하다.</p>
<hr>
<p>✔️ 추상적 자료형(ADT)이란?</p>
<ul>
<li>데이터의 형태와 연산을 수학적으로 정의한 개념적 모델</li>
<li>&quot;무엇을 할 수 있는가?&quot;에 초점</li>
</ul>
<p>✔️ 자료구조란?</p>
<ul>
<li>ADT에서 정의한 연산을 실제로 구현한 것</li>
<li>&quot;어떻게 구현하는가?&quot;에 초점</li>
</ul>
<hr>
<h4 id="💡-예시-스택stack">💡 예시: 스택(Stack)</h4>
<ul>
<li>추상적 자료형ADT:<ul>
<li>연산: push, pop</li>
<li>특징: 후입선출(LIFO)</li>
</ul>
</li>
<li>자료구조:<ul>
<li>배열이나 연결 리스트로 구현된 스택</li>
<li>실제 프로그램에서는 콜 스택(Call Stack) 등으로 사용됨</li>
</ul>
</li>
</ul>
<hr>
<h3 id="📌-자료구조의-종류">📌 자료구조의 종류</h3>
<p>자료구조는 여러 기준으로 나눌 수 있지만, 대표적으로 다음과 같이 구분된다.</p>
<hr>
<h4 id="1️⃣-선형-자료구조-linear-data-structure">1️⃣ 선형 자료구조 (Linear Data Structure)</h4>
<p>데이터가 일렬로 연결된 구조</p>
<ul>
<li>배열 (Array)</li>
<li>연결 리스트 (Linked List)</li>
<li>스택 (Stack)</li>
<li>큐 (Queue)</li>
<li>덱 (Deque)</li>
</ul>
<hr>
<h4 id="2️⃣-비선형-자료구조-non-linear-data-structure">2️⃣ 비선형 자료구조 (Non-linear Data Structure)</h4>
<p>데이터가 계층적 또는 복잡한 관계를 가지는 구조</p>
<ul>
<li>트리 (Tree)</li>
<li>트라이 (Trie)</li>
<li>그래프 (Graph)</li>
</ul>
<hr>
<h4 id="3️⃣-연관키-값-자료구조">3️⃣ 연관(키-값) 자료구조</h4>
<p>데이터를 키(Key) - 값(Value) 형태로 저장</p>
<ul>
<li>맵 (Map) / 연관 배열 (Associative Array)</li>
<li>해시 테이블 (Hash Table)</li>
</ul>
<hr>
<h4 id="4️⃣-기타-분류">4️⃣ 기타 분류</h4>
<ul>
<li>혼합 자료구조 (Composite Data Structure)<ul>
<li>여러 구조가 결합된 형태</li>
</ul>
</li>
</ul>
<hr>
<h3 id="📌-정리">📌 정리</h3>
<ul>
<li>자료구조는 데이터를 효율적으로 다루기 위한 방법</li>
<li>ADT는 개념적 정의, 자료구조는 구현</li>
<li>문제에 따라 적절한 자료구조 선택이 매우 중요하다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘(4)]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%9E%AC%EA%B7%80-%ED%98%B8%EC%B6%9C</link>
            <guid>https://velog.io/@dongmin_1999/%EC%9E%AC%EA%B7%80-%ED%98%B8%EC%B6%9C</guid>
            <pubDate>Sat, 21 Mar 2026 05:59:10 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-재귀recursion란">📌 재귀(Recursion)란?</h1>
<p>알고리즘을 공부하면서 자주 등장하는 개념 중 하나인 재귀 호출에 간단하게 대해 정리해보자.</p>
<h3 id="재귀recursion란"><strong>재귀(Recursion)</strong>란</h3>
<p>👉 함수가 자기 자신을 다시 호출하는 방식을 의미한다.</p>
<h3 id="재귀의-핵심-구조">재귀의 핵심 구조</h3>
<p>재귀 함수는 반드시 아래 두 가지 요소를 포함해야 한다.</p>
<ul>
<li><p>종료 조건 (Base Case)
재귀 호출을 멈추는 조건
이 조건이 없으면 무한 호출이 발생한다 (Stack Overflow)</p>
</li>
<li><p>재귀 호출 (Recursive Call)
문제를 더 작은 단위로 나누어 자기 자신을 다시 호출
📌 예시: 팩토리얼(Factorial)</p>
</li>
</ul>
<hr>
<h3 id="팩토리얼은-재귀를-이해하기-가장-좋은-예시이다">팩토리얼은 재귀를 이해하기 가장 좋은 예시이다.</h3>
<pre><code class="language-python">def factorial(n):
    if n == 1:  # 종료 조건
        return 1
    return n * factorial(n-1)  # 재귀 호출
</code></pre>
<p>🔍 동작 과정
factorial(4)
= 4 * factorial(3)
= 4 * 3 * factorial(2)
= 4 * 3 * 2 * factorial(1)
= 4 * 3 * 2 * 1
= 24</p>
<h3 id="📌-재귀를-사용하는-이유">📌 재귀를 사용하는 이유</h3>
<p>재귀는 문제를 같은 형태의 더 작은 문제로 분할할 수 있을 때 매우 강력하다.</p>
<p>특히 아래와 같은 상황에서 많이 사용된다.</p>
<p>트리 구조 탐색 (DFS)
그래프 탐색
백트래킹 (모든 경우의 수 탐색)
분할 정복 (퀵 정렬, 병합 정렬)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인공신경망(3)]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D3</link>
            <guid>https://velog.io/@dongmin_1999/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D3</guid>
            <pubDate>Thu, 19 Mar 2026 07:11:14 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에서는 <strong>최적화 함수(Optimizer)</strong>와 <strong>손실 함수(Loss Function)</strong>에 대해 다루고자 한다.</p>
<hr>
<h2 id="1-최적화-함수란-무엇인가">1. 최적화 함수란 무엇인가?</h2>
<p>신경망 모델을 학습시키기 위해서는 모델의 오차를 최소화하는 최적의 가중치(Weight)와 편향(Bias) 매개변수를 찾아야 하는데, 이 과정에서 사용되는 것이 바로 <strong>최적화 함수(Optimizer)</strong>이다.</p>
<p>대표적인 방법으로는 <strong>경사하강법(Gradient Descent)</strong>이 있으며,<br>이 방법은 손실 함수의 <strong>기울기(gradient)</strong>를 이용해 가중치를 조정한다.</p>
<p>기울기가 중요한 이유는,<br>👉 <strong>손실 함수 값을 가장 빠르게 감소시키는 방향을 알려주기 때문</strong>이다.</p>
<hr>
<h2 id="2-손실-함수란">2. 손실 함수란?</h2>
<p>손실 함수(Loss Function)는<br>👉 <strong>모델의 예측값과 실제값의 차이를 수치로 나타낸 함수</strong>이다.</p>
<ul>
<li>손실 값이 작을수록 → 모델의 성능이 좋음  </li>
<li>손실 값이 클수록 → 예측이 잘못됨  </li>
</ul>
<p>해결하려는 문제의 종류에 따라 사용하는 손실 함수가 달라지며, 대표적으로 다음과 같은 종류가 있다.</p>
<blockquote>
<p>$n$은 데이터의 총 개수를 의미하며, MSE 함수와 MAE 함수에서 $y_i$는 실제 정답, $\hat{y}_i$는 모델의 예측값을, Cross-Entropy Loss 함수에서는 $$y_i$$ 실제 레이블(0 또는 1)을 의미하고, $$\hat{y}_i$$는 예측 확률(0과 1 사이)을 의미한다.</p>
</blockquote>
<p>📌 평균 제곱 오차 (MSE: Mean Squared Error)
주로 회귀(Regression) 문제에서 사용된다. 예측값과 실제값 사이의 차이를 제곱한 후, 그 값들의 평균을 구하는 방식이다. </p>
<p>$$
MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
$$</p>
<ul>
<li>오차가 클수록 제곱되어 훨씬 큰 페널티가 부여된다. 이상치(Outlier)에 민감하다는 단점이 있지만, 모델이 큰 오차를 더 치명적으로 인식하고 집중적으로 수정하도록 유도할 수 있다.</li>
</ul>
<br>


<p>📌 평균 절대 오차 (MAE: Mean Absolute Error)
$$
MAE = \frac{1}{n} \sum_{i=1}^{n} | y_i - \hat{y}_i |
$$</p>
<ul>
<li>오차의 절댓값을 사용하기 때문에 MSE에 비해 이상치에 덜 민감하다. 오차의 크기가 그대로 반영되므로 모델의 예측 성능을 직관적으로 평가할 때 유용하며, 오차 분포에 대해 균형 잡힌 평가를 제공한다.</li>
</ul>
<br>

<p>📌 교차 엔트로피 손실 (Cross-Entropy Loss)
주로 분류(Classification) 문제에서 두 확률 분포 간의 차이를 계산할 때 사용된다. 아래는 이진 분류에서 사용하는 수식이다.</p>
<p>$$
-\frac{1}{n} \sum_{i=1}^{n} \left[ y_i \log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i) \right]
$$</p>
<ul>
<li>예측 확률이 실제 정답에 가까워질수록 손실 값이 0에 수렴한다. 다중 클래스 분류에서는 이를 확장한 범주형 교차 엔트로피(Categorical Cross-Entropy)가 사용된다.</li>
</ul>
<hr>
<h2 id="3-경사하강법과-다양한-최적화-알고리즘">3. 경사하강법과 다양한 최적화 알고리즘</h2>
<p>최적화 함수의 가장 기본이 되는 <strong>경사하강법(Gradient Descent)</strong>은 다음과 같은 3단계로 이루어진다.</p>
<ol>
<li><p>손실 함수의 기울기 계산: 현재 가중치 위치에서 손실 함수의 미분값(기울기)을 계산한다.</p>
</li>
<li><p>가중치 업데이트: 기울기의 반대 방향(내리막길)으로 가중치를 이동시킨다. 이때 <strong>학습률(Learning Rate)</strong>이라는 매개변수를 곱하여 한 번에 이동할 보폭의 크기를 조절한다.</p>
</li>
<li><p>반복: 손실 함수의 값이 충분히 작아질 때까지(최저점에 도달할 때까지) 위 과정을 반복한다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/8cc8437d-00c8-4a0b-8230-222db7236589/image.png" alt=""> </p>
<h5 id="위의-손실-함수-그래프에서-기울기가-0이-되는-지점이-바로-손실-함수가-최솟값을-가지는-지점이다">위의 손실 함수 그래프에서 기울기가 0이 되는 지점이 바로 손실 함수가 최솟값을 가지는 지점이다!</h5>
<hr>
<h3 id="31-경사하강법의-한계점">3.1 경사하강법의 한계점</h3>
<p>일반적으로 손실 함수는 위의 그림처럼 깔끔한 2차 함수 형태를 가지지 않는다.</p>
<ol>
<li>데이터가 많아질수록 계산량 증가 <ul>
<li>학습용 데이터 셋이 많아진다면 계산량이 기하급수적으로 증가하게 되고, 학습 속도가 매우 느려진다. 기계학습에는 거대한 빅데이터가 사용되기 때문에 비용적인 부분을 무시할 수 없음</li>
</ul>
</li>
<li>Local Minima 문제<ul>
<li>아래 그림과 같이 초기 시작점이 local minimum에 가까운 지점이라면 전역 최솟값으로 향해 다가오는 도중에 지역 극솟값에 수렴해버릴 가능성이 있다. 지역 극솟값에서의 손실 함수의 기울기 또한 0이므로 w가 지역 극솟값에 갇혀 더 이상 업데이트되지 못할 수 있다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/b6646ae9-8f06-4d35-8a32-4cd838770fdd/image.png" alt=""></li>
</ul>
</li>
<li>Plateau 현상<ul>
<li>아래 그림과 같이 기울기가 0에 수렴하는 평탄한 영역에서 경사하강법을 사용하면 결국 가중치가 업데이트되지 못하고 전역 최솟값을 갖는 지점이 아닌 점에서 정지해버릴 수 있다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/a6bacd46-8c64-4b5f-bc44-5e0a22a8c87d/image.png" alt=""></li>
</ul>
</li>
<li>Zigzag 문제<ul>
<li>아래 그림처럼 $w_1$ 의 스케일이 $w_2$보다 크다보니, 손실 함수는 $x$축 방향 가중치인 $w_1$의 변화에 매우 둔감하고 $y$축인 $w_2$의 변화에 매우 민감하다.</li>
<li>즉 $w_2$가 조금만 변해도 손실 함수가 크게 변하게 되어, 매개변수의 변화에 따른 손실 함수의 변화가 일정하지 않다.</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/323bd0fa-74b1-4985-b64c-9be4b06bb5c9/image.png" alt=""></p>
<p>이 문제들을 해결하기 위해 다양한 최적화 함수가 있다.</p>
<hr>
<h2 id="32-다양한-최적화-함수">3.2 다양한 최적화 함수</h2>
<ol>
<li><p>확률적 경사 하강법 (SGD) &amp; Mini-batch GD 👉 계산량 및 학습 속도 문제 해결</p>
<ul>
<li><p>전체 데이터셋의 기울기를 한 번에 다 계산하느라 학습이 너무 느려지는 문제를 해결하기 위해 등장했다.</p>
</li>
<li><p>SGD: 무작위로 추출한 1개의 데이터(혹은 극소량)에 대해서만 기울기를 계산하고 매우 빠르게 가중치를 업데이트한다.</p>
</li>
<li><p>Mini-batch GD: 전체 데이터를 작은 덩어리(배치, Batch)로 나누어 각 배치마다 기울기를 계산한다. 연산 속도와 업데이트 안정성의 균형이 가장 좋아서 실무 모델 학습 시 가장 기본으로 쓰이는 방식이다.</p>
</li>
</ul>
</li>
<li><p>모멘텀 (Momentum) 👉 Local Minima 및 Plateau 문제 극복, 진동 억제</p>
<ul>
<li>물리 법칙의 &#39;관성&#39;을 도입하여, 이전 기울기의 방향을 기억하고 밀어주어 진동을 줄이고 수렴 속도를 크게 향상시킨다.</li>
<li>기울기가 0이 되어버리는 Local Minima나 Plateau를 만나도, 관성을 이용해 멈추지 않고 뚫고 지나갈 수 있다. 또한 좁은 협곡을 내려갈 때 양옆으로 지그재그로 튕기는 Oscillation 현상도 잡아주어 수렴 속도를 크게 향상시킨다.</li>
</ul>
</li>
<li><p>Adam (Adaptive Moment Estimation) 👉 방향과 보폭을 동시에 조정</p>
<ul>
<li><p>최적의 훈련을 위해 <strong>방향</strong>과 <strong>보폭</strong>을 모두 지능적으로 관리하는 알고리즘이다.</p>
</li>
<li><p>모멘텀처럼 진동을 줄이면서도, 데이터의 특징에 따라 각 가중치 매개변수마다 알맞은 학습률을 스스로 계산하여 적응적으로 조절한다. 어떤 복잡한 한계 상황에서도 빠르고 안정적으로 최저점을 찾아가기 때문에, 현재 딥러닝에서 강력한 표준 알고리즘이다.</p>
</li>
</ul>
</li>
</ol>
<hr>
<h2 id="4-인공신경망의-학습-과정">4. 인공신경망의 학습 과정</h2>
<p>신경망은 앞서 배운 개념들을 종합하여, 다음의 4가지 과정을 끊임없이 반복하며 점점 똑똑해진다.</p>
<h3 id="1️⃣-순전파-forward-propagation">1️⃣ 순전파 (Forward Propagation)</h3>
<ul>
<li>입력 데이터를 신경망에 통과시켜 예측값을 계산한다. 가중치 연산 후 활성화 함수를 거쳐 최종 출력을 낸다.<h3 id="2️⃣-오차-계산-loss-calculation">2️⃣ 오차 계산 (Loss Calculation)</h3>
</li>
<li>출력된 예측값과 실제 정답의 차이를 비교하여 <strong>손실 함수 값(Loss)</strong>을 구한다.</li>
</ul>
<h3 id="3️⃣-역전파-backpropagation">3️⃣ 역전파 (Backpropagation)</h3>
<ul>
<li>계산된 오차를 출력층에서 입력층 쪽으로 거꾸로 돌려보내며, 미분을 통해 각 가중치가 오차에 미친 영향도(기울기)를 계산한다.</li>
</ul>
<h3 id="4️⃣-가중치-업데이트-weight-update">4️⃣ 가중치 업데이트 (Weight Update)</h3>
<ul>
<li>계산된 기울기와 <strong>최적화 함수(Optimizer)</strong>를 이용하여 가중치를 알맞게 수정한다.</li>
</ul>
<h3 id="💡-핵심-요약">💡 핵심 요약</h3>
<p>역전파를 통해 구한 <strong>&#39;기울기&#39;</strong>와, 그 기울기를 바탕으로 이동 방향과 보폭을 지시하는 <strong>&#39;최적화 함수&#39;</strong>가 톱니바퀴처럼 함께 작동하며 손실 함수 값을 최소화하는 방향으로 가중치를 점진적으로 업데이트하는 것, 이것이 바로 딥러닝 학습의 본질이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인공신경망(2)]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D2</link>
            <guid>https://velog.io/@dongmin_1999/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D2</guid>
            <pubDate>Thu, 19 Mar 2026 04:54:14 GMT</pubDate>
            <description><![CDATA[<h2 id="🤖-로키-부트캠프-3차-스터디1---활성화-함수">🤖 로키 부트캠프 3차 스터디(1) - 활성화 함수</h2>
<p>이번 스터디에서는 인공신경망의 핵심 요소 중 하나인 <strong>활성화 함수(Activation Function)</strong> 와 <strong>최적화 함수</strong> 에 대해 다뤄보고자 한다. 두 개념 모두 딥러닝을 이해하기 위해 반드시 알아야 하는 내용이므로, 이번 포스트에서는 먼저 <strong>활성화 함수</strong>에 대해 깊이 있게 파헤쳐 보고, 이어지는 다음 포스트에서 <strong>최적화 함수</strong>를 다룰 예정이다.</p>
<h3 id="1-활성화-함수란-무엇인가">1. 활성화 함수란 무엇인가?</h3>
<p>활성화 함수가 무엇인지 이해하기 위해 먼저 <strong>&#39;활성화(Activation)&#39;</strong> 라는 단어의 의미를 생각해 볼 필요가 있다. 활성화란 입력에 대하여 출력을 내보낼 때, 어떠한 조건이나 기준에 따라 그 값을 결정하는 것을 의미한다. 
즉, 활성화 함수는 입력층에서 들어온 가중치($w$)와 편향($b$)의 연산 결과에 적용되어, 출력값을 결정하는 함수이다.</p>
<h3 id="2-활성화-함수를-쓰는-이유">2. 활성화 함수를 쓰는 이유</h3>
<p>활성화 함수의 정의를 알고 나면 이런 의문이 들 수 있다. &quot;입력에 대한 출력값을 바꾸는 것이 뭐가 좋을까?&quot;</p>
<p>가장 핵심적인 이유는 바로 모델의 표현력을 획기적으로 향상시켜 주기 때문이다.
이를 이해하기 위해 아래의 예시를 보자.
$$
f(x) = w_1 x + b_1
$$</p>
<p>이 모델은 $w_1$, $b_1$이라는 2개의 파라미터로 이루어진 형태인데, 이 모델로 $x^2$, $x^5$, $\sin(x)$ 등으로 표현되는 복잡한 곡선 데이터를 학습할 수 있을까?
절대 불가능하다. $w_1$, $b_1$ 값을 아무리 바꿔보아도, 결국은 직선형태인 $f(x)$로는 곡선(비선형) 형태를 표현할 수 없기 때문이다.</p>
<p>따라서 딥러닝 모델이 현실 세계의 복잡한 비선형 데이터를 이해하고 표현하려면, 모델 내부에 반드시 비선형성이 존재해야 하며, 층과 층 사이에 활성화 함수를 넣어줌으로써 모델이 복잡한 패턴도 학습할 수 있게 되는 것이다.</p>
<h3 id="3-활성화-함수의-발전-과정">3. 활성화 함수의 발전 과정</h3>
<p>활성화 함수에는 그 표현 방식에 따라 크게 이진 계단 함수, 선형 활성화 함수, 그리고 현대 딥러닝의 핵심인 비선형 활성화 함수로 나눌 수 있다.</p>
<ul>
<li><p>이진 계단 함수 (Binary Step Function)
들어온 입력이 특정 임계점을 넘으면 1을 출력하고, 그렇지 않으면 0을 출력한다. 구조가 직관적이어서 아주 간단한 이진 분류 문제에 유용하게 쓰였다. </p>
<ul>
<li>한계점: 역전파 과정에서 미분 값이 0이 되어버려 학습이 불가능해지는 문제(기울기 소실)가 발생하며, 오직 이진 분류에만 사용할 수 있어 다중 출력이 불가능하다.</li>
</ul>
</li>
<li><p>선형 활성화 함수 (Linear Activation Function)
말 그대로 &#39;선형&#39; 형태의 함수이다. 이진 계단 함수와 달리 다중 출력이 가능하고 미분도 가능하여 역전파를 쓸 수 있습니다.</p>
<ul>
<li>한계점: 은닉층에 이 함수를 사용하여 아무리 층을 깊게 쌓아도 결국 1차 방정식으로 귀결되므로 비선형적 특성을 지닌 복잡한 데이터를 예측하지 못한다.</li>
</ul>
</li>
<li><p>비선형 활성화 함수 (Non-linear Activation Function)
이전 함수들의 문제점을 모두 극복한 딥러닝의 꽃이다. 역전파 알고리즘을 원활하게 사용할 수 있고, 은닉층을 깊게 쌓을수록 비선형적이고 복잡한 패턴의 데이터를 아주 정교하게 예측할 수 있다.</p>
</li>
</ul>
<h3 id="4-대표적인-활성화-함수의-종류">4. 대표적인 활성화 함수의 종류</h3>
<h3 id="활성화-함수의-종류">활성화 함수의 종류</h3>
<h4 id="📌-시그모이드-함수-sigmoid">📌 시그모이드 함수 (Sigmoid)</h4>
<p>출력값을 0과 1 사이로 변환하는 S자 형태의 함수이다. 결과를 확률로 해석하기 좋아서 이진 분류 문제의 출력층에서 주로 사용된다.</p>
<p>⚠️ 주의: 층이 깊어질수록 값이 0에 수렴하여 학습이 멈춰버리는 &#39;기울기 소실(Vanishing Gradient)&#39; 문제를 일으킬 수 있어 은닉층에서는 사용을 지양한다.</p>
<blockquote>
<p>기울기 소실 : 역전파 과정에서 입력층으로 진행할수록 기울기가 0으로 수렴하는 현상
결국 초기 층은 거의 학습이 안 되며 학습이 매우 느려지고 성능이 안 좋아진다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/320a5d00-7246-478c-9142-0c13c3a74ce7/image.png" alt=""></p>
<h4 id="📌-하이퍼볼릭-탄젠트-함수-tanh">📌 하이퍼볼릭 탄젠트 함수 (Tanh)</h4>
<p>출력값을 -1과 1 사이로 변환하는 함수이다. 시그모이드 함수와 비슷하지만, 신호의 중심을 0으로 맞추기 때문에 시그모이드보다 학습의 수렴 속도가 훨씬 빠르다. 이 함수는 입력 신호의 양과 음을 모두 효과적으로 표현할 수 있어 주로 <strong>순환 신경망(RNN)</strong> 의 은닉층에서 사용된다.</p>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/aa037741-56d0-427a-88e1-dda5ed85d54b/image.png" width="150%">

<p>📌 렐루 함수 (ReLU)
입력값이 0보다 작으면 0을 출력하고, 0보다 크면 입력값을 그대로 출력하는 함수이다. 신경망의 연산 속도를 획기적으로 향상시키고 시그모이드의 &#39;기울기 소실&#39; 문제를 해결하여 현재 대부분의 심층 신경망(DNN) 은닉층에서 표준으로 사용된다.</p>
<ul>
<li>음수 입력에 대해 항상 0을 출력해 뉴런이 죽어버리는 단점을 보완하기 위해 Leaky ReLU, Parametric ReLU 등의 변형 함수들도 널리 쓰이고 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/b2a8069c-458a-40bf-849f-01705232ae7e/image.png" alt=""></p>
<p>📌 소프트맥스 함수 (Softmax)
다중 클래스 분류 문제의 출력층에서 단골로 사용되는 활성화 함수이다. 입력값들의 지수 함수를 계산한 후, 각 출력값을 전체 출력값의 합으로 나누어 줍니다. 결과적으로 모든 출력값의 합이 1(100%)이 되는 확률 분포를 생성하여, 모델이 예측한 정답이 어떤 클래스에 속할 확률이 가장 높은지 명확하게 보여준다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/53823bb6-2e0d-4a91-a40f-6900b9ad3c83/image.png" alt=""></p>
<h3 id="💡-활성화-함수-요약">💡 활성화 함수 요약</h3>
<table>
<thead>
<tr>
<th>활성화 함수</th>
<th>사용 위치</th>
<th>용도</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Sigmoid</td>
<td>출력층</td>
<td>이진분류</td>
<td>값을 0~1 사이의 확률로 변환</td>
</tr>
<tr>
<td>Tanh</td>
<td>은닉층</td>
<td>자연어처리,RNN모델 등</td>
<td>-1과 1 사이로 변환하며 Sigmoid보다 학습 수렴 속도 빠름</td>
</tr>
<tr>
<td>ReLU</td>
<td>은닉층</td>
<td>표준 활성화 함수</td>
<td>계산이 매우 빠르고 기울기 소실 문제를 해결</td>
</tr>
<tr>
<td>Softmax</td>
<td>출력층</td>
<td>다중 클래스 분류</td>
<td>여러 선택지에 대한 예측값을 총합이 1(100%)인 확률로 변환</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[인공 신경망(1)]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%9D%B8%EA%B3%B5-%EC%8B%A0%EA%B2%BD%EB%A7%9D</link>
            <guid>https://velog.io/@dongmin_1999/%EC%9D%B8%EA%B3%B5-%EC%8B%A0%EA%B2%BD%EB%A7%9D</guid>
            <pubDate>Wed, 18 Mar 2026 13:04:31 GMT</pubDate>
            <description><![CDATA[<h1 id="퍼셉트론">퍼셉트론</h1>
<p><strong>&#39;활성화 함수(Activation Function)&#39;</strong> 와 <strong>&#39;최적화 함수(Optimizer)&#39;</strong> 에 대해 이해하고자 인공신경망의 기초가 되는 퍼셉트론에 대해 정리하고자 한다.</p>
<h3 id="1-인공신경망ann과-퍼셉트론의-등장">1. 인공신경망(ANN)과 퍼셉트론의 등장</h3>
<p>인공신경망(Artificial Neural Network)이란 인간 뇌의 신경 구조를 모방하여 설계된 기계 학습 모델이다. &#39;뉴런&#39;이라는 기본 단위가 입력값을 받아 가중치를 적용하고, 활성화 함수를 통해 출력을 생성하는 방식으로 동작하죠. 현재에는 가장 널리 쓰이는 구글 딥마인드의 &#39;Transformer&#39; 모델 역시 이 인공신경망을 발전시킨 형태인데, 자세한 내용은 [다른 포스팅 URL]에서 다루겠다.</p>
<p>인공신경망을 복잡하게 쌓아 올린 &#39;딥러닝&#39;을 이해하기 위해서는, 가장 초기 형태의 인공신경망인 <strong>퍼셉트론(Perceptron)</strong> 을 이해해야 합니다. 퍼셉트론은 다수의 입력으로부터 하나의 결과를 내보내는 단순한 알고리즘이다.</p>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/03bf4061-a0b1-4713-b02e-41c48a05b137/image.png" width="30%" height="30%" align='left'/>

<p>이 그림은 다수의 입력을 받는 퍼셉트론의 구조입니다. 생물학적 뉴런의 입력, 출력 신호가 퍼셉트론에서는 각각 입력값($x$)과 출력값($y$)에 해당한다.
가운데 원은 &#39;인공 뉴런&#39;을 의미하며, 실제 뉴런에서 신호를 전달하는 역할은 <strong>가중치($w$, Weight)</strong> 가 대신한다. 각각의 입력값 $x$는 고유한 가중치 $w$와 곱해져서 인공 뉴런으로 전달된다.</p>
<hr>
<h3 id="2-활성화-함수와-편향bias">2. 활성화 함수와 편향(Bias)</h3>
<p>각 입력값과 가중치의 곱을 모두 더한 값이 특정 <strong>임계치($\theta$)</strong> 를 넘으면 인공 뉴런은 1을 출력하고, 넘지 못하면 0을 출력한다. 이렇게 초기 퍼셉트론에서 사용된 함수를 <strong>계단 함수(Step Function)</strong> 라고 부른다. </p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/e576940f-26b5-44d5-bd7e-fa40dccc4f87/image.png" alt=""></p>
<p>$$if \sum_{i}^{n} w_i x_i \ge \theta \rightarrow y = 1$$
$$if \sum_{i}^{n} w_i x_i &lt; \theta \rightarrow y = 0$$</p>
<p>이때 임계치를 좌변으로 넘겨 <strong>편향($b$, Bias)</strong> 으로 표현할 수도 있다. 
<img src="https://velog.velcdn.com/images/dongmin_1999/post/104b04f6-8d2f-49c2-8b2e-dd7500b7eaf1/image.png" width="30%" /></p>
<p>$$if \sum_{i}^{n} w_i x_i + b \ge 0 \rightarrow y = 1$$
$$if \sum_{i}^{n} w_i x_i + b &lt; 0 \rightarrow y = 0$$</p>
<br>

<p>이렇게 뉴런에서 들어온 신호의 총합을 판단하여 출력값을 변경시키는 함수를 <strong>활성화 함수(Activation Function)</strong> 라고 한다. 초기에는 위와 같은 계단 함수를 사용했지만, 신경망이 발전하면서 현재는 ReLU, Sigmoid 등 다양한 활성화 함수가 사용되고 있다.</p>
<h4 id="💡-핵심-매개변수-정리">💡 핵심 매개변수 정리</h4>
<ul>
<li><p>가중치(Weight): 각 입력 신호가 결과 출력에 미치는 중요도를 조절합니다. 가중치가 클수록 해당 입력값이 중요하다는 것을 의미한다.</p>
</li>
<li><p>편향(Bias): 뉴런의 활성화 조건을 결정합니다. 편향이 크면 뉴런이 쉽게 활성화되어 더 복잡한 모델을 만들고, 작으면 활성화되는 뉴런이 적어져 모델이 단순해진다.</p>
</li>
</ul>
<hr>
<p>단층 퍼셉트론 : 계단함수 $$f(\sum_{i}^{n} w_i x_i + b)$$   -&gt; 1차 방정식 형태</p>
<h3 id="3-단층-퍼셉트론의-한계">3. 단층 퍼셉트론의 한계</h3>
<p>퍼셉트론은 층의 개수에 따라 단층과 다층으로 나뉩니다. 단층 퍼셉트론은 값을 입력받는 &#39;입력층&#39;과 값을 내보내는 &#39;출력층&#39;으로만 구성된 가장 단순한 신경망이다.</p>
<p>단층 퍼셉트론의 식 : $$f(\sum_{i=1}^{n} w_i x_i + b)$$</p>
<p>단층 퍼셉트론은 1차 방정식의 형태를 띠고 있다. 따라서 선형적으로 분리 가능한(Linearly Separable) 데이터 구조인 AND, OR, NAND 논리 게이트 구현에 특화되어 있다.</p>
<br>

<p>AND 게이트 : $x_1$, $x_2$ 가 모두 1인 경우에만 출력값 $y$가 1이 나오는 구조. 
직선 1개로 구분이 가능하다.
<img src="https://velog.velcdn.com/images/dongmin_1999/post/752d6c2e-7f38-4941-add6-4da98dbf4143/image.png" width="50%" height="30%" align='right'></p>
<p><br><br><br><br></p>
<table>
<thead>
<tr>
<th>$x_1$</th>
<th>$x_2$</th>
<th>$y$</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
</tbody></table>
<hr>
<p>OR 게이트 : $x_1$, $x_2$ 중 모두 0인 경우에만 출력값 $y$가 0이 나오는 구조.
직선 1개로 구분이 가능하다.</p>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/e1bb28f4-7faa-4f63-a78f-af9e3fc683c3/image.png" width="50%" height="30%" align='right'>

<p><br><br><br><br></p>
<table>
<thead>
<tr>
<th>$x_1$</th>
<th>$x_2$</th>
<th>$y$</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>1</td>
</tr>
</tbody></table>
<hr>
<p>NAND 게이트 : 두 개의 입력값이 1인 경우에만 0이 나오는 구조.
직선 1개로 구분이 가능하다.</p>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/212c5b21-6387-499f-b88f-ccde1b3f3471/image.png" width="50%" height="30%" align='right'>

<p><br><br><br><br></p>
<table>
<thead>
<tr>
<th>$x_1$</th>
<th>$x_2$</th>
<th>$y$</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>1</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>
<hr>
<h4 id="하지만-입력값-두-개가-서로-다를-때만-1을-출력하는-xor-게이트의-경우는-어떨까">하지만 입력값 두 개가 서로 다를 때만 1을 출력하는 XOR 게이트의 경우는 어떨까?</h4>
<p>XOR 게이트 : $x_1$, $x_2$ 가 $(0,1)$, $(1,0)$ 인 경우에만 1이 나오는 구조.
직선 1개로 두 그룹을 구분할 수 없다.</p>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/cc32aec5-07a6-47c3-8aa7-19b15fc971ec/image.png" width="50%" height="30%" align='right'>
<br><br><br>

<table>
<thead>
<tr>
<th>$x_1$</th>
<th>$x_2$</th>
<th>$y$</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><br><br><br>
위의 그림에서 볼 수 있듯이, 하얀색 원과 검은색 원이 대각선으로 교차하고 있어 직선 하나로는 절대 두 그룹을 나눌 수 없다. 즉, 단층 퍼셉트론으로는 XOR 게이트를 구현할 수 없는 치명적인 한계(선형 분리 불가 문제)가 존재한다. 이를 해결하려면 우측 그림처럼 적어도 두 개의 선이 필요하며, 이에 대한 해답이 바로 &#39;다층 퍼셉트론&#39;이다.</p>
<hr>
<h3 id="4-다층-퍼셉트론과-딥러닝">4. 다층 퍼셉트론과 딥러닝</h3>
<p>다층 퍼셉트론은 단층 퍼셉트론의 입력층과 출력층 사이에 <strong>&#39;은닉층(Hidden Layer)&#39;</strong> 을 추가한 모델이다. 은닉층의 노드 개수가 늘어날수록 도화지에 그을 수 있는 선의 개수가 늘어나기 때문에, XOR 문제처럼 복잡한 영역도 공간을 가두어 분류할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/dd86ec3a-9626-4276-ba81-9976e82fb4a1/image.png" alt=""></p>
<p>위 이미지처럼 은닉층에 여러 개의 노드를 두어 다수의 직선(1차 방정식)을 교차시키면 곡선 형태의 복잡한 경계 영역을 만들어 낼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_1999/post/c1ea07b7-00e7-4207-97f7-e29515622093/image.png" alt=""></p>
<p>이렇게 은닉층이 2개 이상으로 깊게 쌓인 신경망을 <strong>심층 신경망(Deep Neural Network, DNN)</strong> 이라고 부른다.</p>
<p>결국 기계가 정답을 맞히기 위해서는 이 복잡한 신경망 속 수많은 선(가중치와 편향)들의 최적값을 스스로 찾아내도록 자동화해야 한다. 이것이 바로 머신러닝에서 말하는 &#39;학습(Training)&#39; 과정이며, 이때 오차를 계산하는 <strong>손실 함수(Loss function)</strong> 와 최적의 방향을 찾아가는 <strong>옵티마이저(Optimizer)</strong> 가 핵심적인 역할을 수행합니다.</p>
<p>이러한 심층 신경망을 학습시키는 일련의 과정을 우리는 <strong>딥러닝(Deep Learning)</strong> 이라고 부르는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘(2)]]></title>
            <link>https://velog.io/@dongmin_1999/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@dongmin_1999/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Wed, 18 Mar 2026 09:59:43 GMT</pubDate>
            <description><![CDATA[<p>정렬 알고리즘에 대해 공부하려 한다. 이번 포스팅에서는 정렬 알고리즘의 개념과 안정성(Stability), 기초적인 비교 알고리즘에 대해 작성하고자 한다.</p>
<h1 id="1-정렬-알고리즘이란">1. 정렬 알고리즘이란?</h1>
<h3 id="여러-개의-데이터를-일정한-순서오름차순-또는-내림차순로-배열하는-알고리즘이다">여러 개의 데이터를 일정한 순서(오름차순 또는 내림차순)로 배열하는 알고리즘이다.</h3>
<h4 id="11--정렬-알고리즘을-사용하는-이유">1.1  정렬 알고리즘을 사용하는 이유</h4>
<ul>
<li>데이터 검색 속도 향상<ul>
<li>정렬된 데이터는 이진 탐색(Binary Search) 같은 빠른 탐색이 가능하다.</li>
</ul>
</li>
<li>데이터 분석 및 처리 효율 증가<ul>
<li>순위, 통계, 데이터 비교가 쉬워진다.</li>
</ul>
</li>
<li>다른 알고리즘의 기반<ul>
<li>많은 알고리즘이 정렬을 전제로 동작한다.</li>
</ul>
</li>
</ul>
<h4 id="12-내부-정렬-외부-정렬-제자리-정렬의-개념">1.2 내부 정렬, 외부 정렬, 제자리 정렬의 개념</h4>
<ul>
<li><p>내부 정렬 : 입력의 크기가 주 기억 장치보다 크지 않음 -&gt; 컴퓨터 메모리 내부에서 정렬</p>
</li>
<li><p>외부 정렬 : 입력의 크기가 매우 커서 보조 기억 장치에 저장해야 하는 경우에 사용하는 정렬, 보조기억장치에서 부분적으로 주 기억 장치로 옮기면서 처리되는 정렬</p>
</li>
<li><p>제자리 정렬 : 입력 크기에 비례하는 추가 공간을 필요로 하지 않는 알고리즘. 보통 공간 복잡도가 $$
O(1)\ 또는\ O(logn)$$ 를 넘지 않으면 제자리 정렬 알고리즘이라고 한다.</p>
</li>
</ul>
<h4 id="13-정렬-알고리즘에서-안정성이란">1.3 정렬 알고리즘에서 안정성이란?</h4>
<ul>
<li>값이 같은 데이터들의 상대적인 순서가 정렬 후에도 유지되는 성질을 의미한다.</li>
</ul>
<p>예를 들어 점수와 학생 데이터가 있다고 하자.</p>
<table>
<thead>
<tr>
<th>점수</th>
<th>학생</th>
</tr>
</thead>
<tbody><tr>
<td>90</td>
<td>A</td>
</tr>
<tr>
<td>80</td>
<td>B</td>
</tr>
<tr>
<td>90</td>
<td>C</td>
</tr>
<tr>
<td>70</td>
<td>D</td>
</tr>
</tbody></table>
<p>점수를 기준으로 정렬하면 </p>
<h3 id="안정-정렬"><strong>안정 정렬</strong></h3>
<table>
<thead>
<tr>
<th>점수</th>
<th>학생</th>
</tr>
</thead>
<tbody><tr>
<td>70</td>
<td>D</td>
</tr>
<tr>
<td>80</td>
<td>B</td>
</tr>
<tr>
<td>90</td>
<td>A</td>
</tr>
<tr>
<td>90</td>
<td>C</td>
</tr>
<tr>
<td>같은 점수 90인 데이터에서 A → C 순서가 유지된다.</td>
<td></td>
</tr>
</tbody></table>
<h3 id="불안정-정렬"><strong>불안정 정렬</strong></h3>
<table>
<thead>
<tr>
<th>점수</th>
<th>학생</th>
</tr>
</thead>
<tbody><tr>
<td>70</td>
<td>D</td>
</tr>
<tr>
<td>80</td>
<td>B</td>
</tr>
<tr>
<td>90</td>
<td>C</td>
</tr>
<tr>
<td>90</td>
<td>A</td>
</tr>
<tr>
<td>같은 값 90의 순서가</td>
<td></td>
</tr>
<tr>
<td>A → C 에서 C → A 처럼 바뀌었다.</td>
<td></td>
</tr>
</tbody></table>
<p>안정 정렬 종류 : 버블 정렬, 삽입 정렬, 병합 정렬, 팀 소트, 계수 정렬 등이 있다.
불안정 정렬 종류 : 선택 정렬, 퀵 정렬, 힙 정렬, 셸 정렬 등이 있다.</p>
<h1 id="2-기초적인-비교-정렬-알고리즘">2. 기초적인 비교 정렬 알고리즘</h1>
<ul>
<li>선택 정렬, 버블 정렬, 삽입 정렬, 셸 정렬 등이 있다.</li>
<li>모두 구현이 비교적 쉽고 메모리를 적게 사용하므로 제자리 정렬 알고리즘이며 공간 복잡도는 O(1)이다.</li>
<li>대부분의 경우 향상된 정렬 알고리즘에 비해서 비효율적이다.</li>
</ul>
<h3 id="21-선택-정렬">2.1 선택 정렬</h3>
<ul>
<li><strong>선택 정렬</strong>은 이름 그대로 현재 위치에 들어갈 데이터를 &#39;선택&#39;해서 바꾸는 직관적이고 단순한 정렬 알고리즘이다.</li>
<li>배열에서 가장 작은 원소를 찾아 맨 앞의 원소와 자리를 교환하는 과정을 반복하여 정렬을 완성한다.</li>
<li>구현이 매우 간단하고 추가 메모리를 사용하지 않는다는 장점이 있지만, 시간 복잡도가 $O(N^2)$ 이기 때문에 데이터의 개수가 많아질수록 성능이 급격히 떨어져 실제 서비스에서는 잘 사용되지 않는다.<img src="https://velog.velcdn.com/images/dongmin_1999/post/e80c9974-a6b0-490e-bd5c-d3b77d9749ba/image.png" width = "44%" height = "44%">

</li>
</ul>
<p>구현 코드</p>
<pre><code class="language-python">arr = [9, 6, 7, 3, 5]
min_idx = 0
for i in range(len(arr)-1):
  min_idx = i  # 최솟값의 인덱스

  for j in range(i+1, len(arr)): # 실제 최솟값의 위치를 찾는 부분
    if arr[min_idx] &gt; arr[j]:
      min_idx = j

  temp = arr[min_idx]  # 현재 위치의 값과 최소값을 Swap
  arr[min_idx] = arr[i]
  arr[i] = temp</code></pre>
<h3 id="22-버블-정렬">2.2 버블 정렬</h3>
<ul>
<li><strong>버블 정렬</strong>은 서로 인접한 두 원소를 비교하여, 앞의 원소가 뒤의 원소보다 크면 자리를 교환하며 정렬하는 알고리즘이다.</li>
<li>이 과정을 배열 끝까지 반복하면 가장 큰 값이 맨 뒤로 이동한다. 그다음엔 맨 끝을 제외한 나머지 배열에 대해 같은 과정을 반복한다.</li>
<li>구현이 매우 단순하지만, 자리를 바꾸는 연산(Swap)이 빈번하게 일어나기 때문에 실제로는 선택 정렬보다도 느린 경우가 많아 거의 쓰이지 않는다.<img src="https://velog.velcdn.com/images/dongmin_1999/post/6bb79d63-e0e5-4628-8cf7-a0f88f416e97/image.png" width = "90%" height = "50%">

</li>
</ul>
<p>구현 코드</p>
<pre><code class="language-python">  arr = [7, 4, 5, 1, 3]   

  for i in range(len(arr)-1):

    for j in range(0, len(arr)-1-i):
      if arr[j+1] &lt; arr[j]:

        temp = arr[j]
        arr[j] = arr[j+1]
        arr[j+1] = temp</code></pre>
<h3 id="23-삽입-정렬">2.3 삽입 정렬</h3>
<ul>
<li><p>자료 배열의 모든 요소를 앞에서부터 차례대로 이미 정렬된 배열 부분과 비교하여, 자신의 위치를 찾아 삽입함으로써 정렬을 완성하는 알고리즘이다. </p>
</li>
<li><p>매 순서마다 해당 원소를 삽입할 수 있는 위치를 찾아 해당 위치에 삽입한다.</p>
</li>
<li><p>key값은 2번째 인덱스부터 시작되며, key값이 자료의 길이만큼 이동되었을 때 정렬이 완성된다.</p>
</li>
<li><p>데이터가 이미 어느 정도 정렬된 상태라면 매우 효율적이다. </p>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/019cd954-05b1-48d0-a380-a1c6d5a6ee75/image.png" width = "80%" height = "50%">

</li>
</ul>
<pre><code class="language-python">  arr = [9, 4, 3, 5, 1]

  for i in range(1, len(arr)):

      key = arr[i]

      j = i - 1   # arr[j] 는 key와 크기를 비교할 데이터의 인덱스

      while j &gt;= 0 and arr[j] &gt; key:
        arr[j+1] = arr[j]
        j -= 1

      arr[j+1] = key 

  print(arr)
</code></pre>
<h3 id="24-셸-정렬">2.4 셸 정렬</h3>
<ul>
<li>리스트를 일정한 간격에 따라 나누고, 각 부분 리스트를 삽입 정렬을 통해 정렬하는 방법이다.</li>
<li>조금이라도 정렬이 된 상태에 가까운 배열로 만든 후에 삽입정렬을 수행하는 방법이 쉘 정렬이라고 할 수 있겠다. 
→ 쉘 정렬은 삽입 정렬을 보완한 방법!</li>
<li>데이터를 멀리씩 이동시킬 수 있어 삽입 정렬보다 초기 이동 횟수를 크게 줄여준다.</li>
</ul>
<p>쉘 정렬 과정</p>
<ol>
<li>초기 시작 간격(gap)을 정한다. 간격을 k라고 하자.
 1.1 간격의 초깃값: (정렬할 값의 수)/2
1</li>
</ol>
<img src="https://velog.velcdn.com/images/dongmin_1999/post/1d35b895-0e14-4b9d-b3f3-1ee19ee7ccff/image.png" width="70%" height="50%">

<ul>
<li><p>구현 코드</p>
<pre><code class="language-python">def shell_sort(arr):
    n = len(arr)
    gap = n // 2

    while gap &gt; 0:
        for i in range(gap, n):
            temp = arr[i]
            j = i

            while j &gt;= gap and arr[j - gap] &gt; temp:
                arr[j] = arr[j - gap]
                j -= gap

            arr[j] = temp

        gap //= 2

    return arr</code></pre>
<h1 id="3-효율적인-비교-정렬-알고리즘">3. 효율적인 비교 정렬 알고리즘</h1>
</li>
</ul>
<h3 id="31-퀵-정렬">3.1 퀵 정렬</h3>
<h3 id="32-병합-정렬">3.2 병합 정렬</h3>
<h3 id="33-팀-소트">3.3 팀 소트</h3>
<h3 id="34-힙-정렬">3.4 힙 정렬</h3>
<h3 id="35-트리-정렬">3.5 트리 정렬</h3>
<h1 id="4-비교하지-않는-정렬-알고리즘">4. 비교하지 않는 정렬 알고리즘</h1>
<h3 id="41-기수-정렬">4.1 기수 정렬</h3>
<h3 id="42-버킷-정렬">4.2 버킷 정렬</h3>
]]></description>
        </item>
    </channel>
</rss>