<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>LEE_CODER.log</title>
        <link>https://velog.io/</link>
        <description>도전을 멈추지 않는 개발자</description>
        <lastBuildDate>Wed, 19 Aug 2026 12:44:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>LEE_CODER.log</title>
            <url>https://velog.velcdn.com/images/daehyun_lee/profile/63690b22-bf68-4cfb-bacc-59d229aa14f2/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. LEE_CODER.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/daehyun_lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | USC에서 보낸 두 번째 일주일, 그리고 한국으로]]></title>
            <link>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-USC%EC%97%90%EC%84%9C-%EB%B3%B4%EB%82%B8-%EB%91%90-%EB%B2%88%EC%A7%B8-%EC%9D%BC%EC%A3%BC%EC%9D%BC-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%9C%EA%B5%AD%EC%9C%BC%EB%A1%9C</link>
            <guid>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-USC%EC%97%90%EC%84%9C-%EB%B3%B4%EB%82%B8-%EB%91%90-%EB%B2%88%EC%A7%B8-%EC%9D%BC%EC%A3%BC%EC%9D%BC-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%9C%EA%B5%AD%EC%9C%BC%EB%A1%9C</guid>
            <pubDate>Wed, 19 Aug 2026 12:44:54 GMT</pubDate>
            <description><![CDATA[<h1 id="🇺🇸-usc-빅데이터-컨퍼런스-참가기🇺🇸--usc에서-보낸-두-번째-일주일-그리고-한국으로">🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | USC에서 보낸 두 번째 일주일, 그리고 한국으로</h1>
<p>저번 글에서는 USC에서 보낸 <strong>첫 일주일의 생활</strong>을 간략하게 정리해봤다.</p>
<p>학교를 돌아다니고, LA 시내도 구경하고,<br>기숙사에서 친구들과 이것저것 해먹고, 마지막에는 해커톤까지...</p>
<p>1주차 이야기가 궁금하다면 아래 글에서 볼 수 있다.</p>
<p><a href="https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-USC%EC%97%90%EC%84%9C-%EB%B3%B4%EB%82%B8-%EC%B2%AB-%EC%9D%BC%EC%A3%BC%EC%9D%BC">🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | USC에서 보낸 첫 일주일</a></p>
<p>이번에는 그 뒤를 이어<br><strong>USC에서 보낸 두 번째 일주일과 한국으로 돌아오기까지의 기록</strong>을 남겨보려고 한다.</p>
<p>첫 주도 정신없이 돌아다녔는데,<br>사진을 다시 모아보니 2주 차는 더 열심히 놀러 다닌 것 같다...</p>
<hr>
<h2 id="일요일-아침-la에서-샌디에이고로">일요일 아침, LA에서 샌디에이고로</h2>
<p>첫 주 일정이 끝나고<br>일요일 아침부터 LA를 떠나 샌디에이고로 향했다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c39c98db-6ded-48c4-8b37-1c6defb400ea/image.jpeg" alt=""></p>
<p>그리고 여기서 꿀팁 하나.</p>
<blockquote>
<p><strong>다른 지역으로 넘어갈 때 장거리 버스는 개인적으로 정말 비추천한다.</strong></p>
</blockquote>
<p>내가 탔던 버스는 한 시간 정도 늦었고,<br>이용 환경도 생각보다 편하지 않았다.</p>
<p>심지어 내가 이용했을 때는 왕복 비용도 대략 15만 원 정도 들었다.</p>
<p>다시 간다면?</p>
<pre><code class="language-text">기차
또는
비행기</code></pre>
<p>를 먼저 알아볼 것 같다.</p>
<p>시간도 여행에서는 돈인데<br>버스를 기다리면서 시간을 날리는 게 생각보다 너무 아까웠다.</p>
<hr>
<h2 id="그리고-내가-제일-좋아하는-in-n-out">그리고 내가 제일 좋아하는 In-N-Out</h2>
<p>샌디에이고에 도착해서 먹은 것은<br>내가 미국에서 제일 좋아하는 햄버거 중 하나인 <strong>인앤아웃</strong>.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/acecc3a2-0c5e-4c03-a085-69792846d943/image.jpeg" alt=""></p>
<p>모자까지 쓰고 음료수 빠는 중...</p>
<p>미국 오면 인앤아웃은 못 참지.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/ec144922-0504-425b-8076-fd8dccc9c3ee/image.jpeg" alt=""></p>
<p>햄버거 + 감자튀김.</p>
<p>개인적으로 미국 햄버거 중에서는<br>여전히 인앤아웃이 제일 취향이다.</p>
<p>엄청 자극적이지도 않고<br>가격도 비교적 괜찮아서 미국 갈 때마다 한 번씩은 먹는 것 같다.</p>
<hr>
<h2 id="샌디에이고-바다">샌디에이고 바다</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/9dc8bb2f-07ed-4f98-a767-92248dccc5b1/image.jpeg" alt=""></p>
<p>그리고 샌디에이고 바닷가.</p>
<p>LA 바다와는 또 느낌이 달랐다.</p>
<p>날씨도 좋고 바다도 예뻐서<br>그냥 걷는 것만으로도 좋았다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/814fc0d5-8015-4dc6-ada5-e935f4d2ebd6/image.jpeg" alt=""></p>
<p>그리고 해변에서 만난 바다사자들.</p>
<p>사진으로 볼 때보다<br>실제로 보니까 신기했다.</p>
<p>미국에 와서 도시 구경만 하다가<br>이런 풍경을 보고 있으니 여행 온 느낌이 확 났다.</p>
<hr>
<h2 id="주중에는-stüssy도-갔다">주중에는 Stüssy도 갔다</h2>
<p>다시 LA로 돌아와서는<br>주중에 스투시 매장도 다녀왔다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/1471b9f3-7efa-4ffa-ad8d-756f5e83f7c5/image.jpeg" alt=""></p>
<p>구경하다가 가방이 생각보다 싸길래...</p>
<p>하나 사버림.</p>
<p>여행에서 계획에 없던 소비가 제일 무섭다.</p>
<hr>
<p>그리고 한국에서 출발하기 전부터<br>노리고 있던 게 하나 있었다.</p>
<p>바로 <strong>Los Angeles 프린팅이 들어간 스투시 후디</strong>.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/594bfae9-aef7-49fe-aff6-06253750460d/image.jpeg" alt=""></p>
<p>거울샷 한 장 찍어주고...</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/8839e24d-4f29-4657-a076-da0b2d076353/image.jpeg" alt=""></p>
<p>뒷면에는 Los Angeles 프린팅이 크게 들어가 있다.</p>
<p>LA에서 산 옷에 Los Angeles가 박혀 있으니<br>여행 기념품으로도 딱이었다.</p>
<p>한국에서도 열심히 입어야지.</p>
<hr>
<h2 id="다시-hollywood">다시 Hollywood</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/d739bf78-7604-4f7a-a06c-cc7232a77059/image.jpeg" alt=""></p>
<p>시내에서도 보이는 Hollywood Sign.</p>
<p>처음에는 저 사인이 뭐가 그렇게 특별한가 싶었는데<br>LA에 며칠 있다 보니 저게 보일 때마다</p>
<blockquote>
<p>&quot;아 내가 지금 LA에 있구나.&quot;</p>
</blockquote>
<p>싶었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/b7a18be1-d32a-43f9-bb92-b6c6581b22d5/image.jpeg" alt=""></p>
<p>그리고 엄청 큰 피자.</p>
<p>이걸 계속 먹다가는...</p>
<blockquote>
<p>성인병 안 걸리나? 지방간 생길 듯.</p>
</blockquote>
<p>미국 음식은 진짜 양 하나는 인정해야 한다.</p>
<hr>
<h2 id="hollywood-walk-of-fame">Hollywood Walk of Fame</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/52da0e5c-57a2-49be-adf8-164599e55154/image.jpeg" alt=""></p>
<p>헐리우드 거리를 걷다가 발견한<br><strong>마이클 잭슨 별</strong>.</p>
<p>유명한 사람들의 이름을 하나씩 찾아보는 재미가 있었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/42aa1a0b-3cdd-42f6-b787-bcd510934e6b/image.jpeg" alt=""></p>
<p>헐리우드 거리.</p>
<p>관광지 느낌이 정말 강해서<br>사람도 많고 정신없긴 했지만,</p>
<p>그래도 LA에 왔다면 한 번쯤은 걸어볼 만한 곳이었다.</p>
<hr>
<h2 id="분위기-좋았던-이탈리아-식당">분위기 좋았던 이탈리아 식당</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/b6729868-c692-4030-8dfe-5a9c76579e20/image.jpeg" alt=""></p>
<p>어느 이탈리아 식당.</p>
<p>정확히 뭘 먹었는지보다<br>사진에서 보이는 분위기가 너무 좋았다.</p>
<p>느좋...</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/950dc81e-3902-4d82-811c-fa47a68d4cc4/image.jpeg" alt=""></p>
<p>그리고 이탈리아 갔을 때 생각이 나서 시켜본 <strong>Spritz</strong>.</p>
<p>여행하다 보면 음식 하나 때문에<br>예전에 갔던 여행이 갑자기 생각나는 순간이 있는데,</p>
<p>이날이 딱 그랬다.</p>
<hr>
<h2 id="기숙사에서-진행된-mens-night">기숙사에서 진행된 Men&#39;s Night</h2>
<p>그리고 어느 날 밤...</p>
<p>기숙사에서 진행된 <strong>Men&#39;s Night</strong>.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/cb8ea708-4d11-43b8-af91-4cbc8377cc79/image.jpeg" alt=""></p>
<p>이번 프로그램에는 여학생들이 훨씬 많았고<br>기숙사에서도 남자 인원이 상대적으로 적었다.</p>
<p>그래서 남자들끼리 따로 모여 친목회를 열었다ㅋㅋㅋㅋㅋ</p>
<p>별거 한 것도 아닌데 이날 진짜 개꿀잼이었다.</p>
<p>해외 프로그램에서 결국 오래 기억에 남는 건<br>수업도 수업이지만 이런 사람들이랑 놀았던 순간들인 것 같다.</p>
<hr>
<h2 id="la-dodgers-경기-직관">LA Dodgers 경기 직관</h2>
<p>그리고 이번 여행에서 정말 기대했던 일정 중 하나.</p>
<p><strong>LA 다저스 경기 직관!</strong></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/87980a9a-71b0-4133-8fb7-05cb9e076eb9/image.jpeg" alt=""></p>
<p>다저 스타디움 입구.</p>
<p>미국까지 왔는데<br>MLB 직관은 한 번 해보고 싶었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/ba808838-fafa-4e3d-b57c-2fba11bc8e5a/image.jpeg" alt=""></p>
<p>그리고 오타니 뒷모습.</p>
<p>진짜 오타니를 실제로 봤다...</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c7434a5f-383e-4132-b8ca-d2601fe39cf2/image.jpeg" alt=""></p>
<p>경기장 분위기도 정말 좋았다.</p>
<p>사진으로 보면 잘 안 느껴지는데<br>실제로 들어가면 경기장 크기와 관중 분위기 때문에 압도되는 느낌이 있다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/9840c09d-360d-44ef-8f32-d2b64bc9e3c0/image.jpeg" alt=""></p>
<p>경기가 끝난 뒤 찍은 다저 스타디움.</p>
<p>이날 경기가 진짜 재밌었다.</p>
<p>처음부터 <strong>오타니가 홈런을 쳤고</strong>,  
이후에도 계속 엎치락뒤치락하면서 경기가 진행됐다.</p>
<p>결과적으로 다저스가 지기는 했지만<br>경기 자체가 너무 재밌어서 아쉬움은 별로 없었다.</p>
<p>한국에서 보던 야구와는 또 다른 느낌이었다.</p>
<p>선수들의 경기력이나 경기장 규모, 관중 문화까지<br>MLB를 직접 보는 재미가 확실히 있었다.</p>
<p>미국에 다시 간다면<br>MLB 직관은 또 하고 싶다.</p>
<hr>
<h2 id="manhattan-beach">Manhattan Beach</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/90af3741-07ae-40bc-b089-584c5e7b1b70/image.jpeg" alt=""></p>
<p>맨해튼 비치에 간 날.</p>
<p>Pier 쪽에서 전체적으로 찍은 사진인데<br>개인적으로 LA에서 봤던 바다 중에서도 정말 좋았다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/00a26baf-2cf1-4224-9324-492b4af2e349/image.jpeg" alt=""></p>
<p>바다도 한 장.</p>
<p>LA는 도시 구경하다가<br>조금만 이동하면 이렇게 바다가 나온다는 게 정말 좋은 것 같다.</p>
<hr>
<h2 id="그리고-또-간-북창동-순두부">그리고 또 간 북창동 순두부</h2>
<p>미국에서 한식이 그리울 때쯤 다시 찾아간 곳.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/99c4ce2d-c2fc-47bc-983e-2fe48491e5d7/image.jpeg" alt=""></p>
<p><strong>북창동 순두부.</strong></p>
<p>여기서 꿀팁 하나.</p>
<blockquote>
<p>여기는 개인적으로 순두부보다 갈비 맛집이다.</p>
</blockquote>
<p>갈비 맛있음.</p>
<p>미국에서 며칠 동안 햄버거, 피자 같은 것만 먹다가<br>한식 먹으면 진짜 정신이 번쩍 든다.</p>
<hr>
<h2 id="usc에서의-마지막-날">USC에서의 마지막 날</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/4db287f3-5010-4fa7-a04f-e42b7a38721b/image.jpeg" alt=""></p>
<p>학교 안에서 마지막 날 찍은 사진.</p>
<p>그냥...</p>
<p>느좋이라 올려봄.</p>
<p>처음 USC에 도착했을 때는<br>2주가 꽤 길 것 같았는데,</p>
<p>막상 마지막 날이 되니까</p>
<blockquote>
<p>&quot;벌써 끝이라고?&quot;</p>
</blockquote>
<p>싶었다.</p>
<hr>
<h2 id="마지막은-santa-monica">마지막은 Santa Monica</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/105e0a29-6e87-46e8-990a-c9bb830d4697/image.jpeg" alt=""></p>
<p>산타 모니카 쪽 야자수.</p>
<p>이런 사진 보면<br>왜 사람들이 캘리포니아 감성이라고 하는지 알 것 같다.</p>
<pre><code class="language-text">파란 하늘
+
야자수
+
바다</code></pre>
<p>이 조합은 반칙이다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/4a9ebe2c-3787-442c-94d2-d91522bc63ae/image.jpeg" alt=""></p>
<p>그리고 그 유명한 <strong>Route 66</strong> 표지판.</p>
<p>사진 한 장 남겨주고.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/48d5a7da-e302-4ed3-9f42-7a4f9a9464af/image.jpeg" alt=""></p>
<p>산타 모니카 Pier의 놀이기구.</p>
<p>타고 싶다...</p>
<p>시간만 조금 더 있었으면<br>하루 종일 여기서 놀았을 것 같다.</p>
<hr>
<h2 id="그리고-한국으로">그리고 한국으로</h2>
<p>그렇게 모든 일정을 마치고<br>드디어 한국으로 돌아가는 비행기를 탔다.</p>
<p>그런데 여기서 마지막 행운이 터졌다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/fb96d7ff-b291-4719-9703-487473576542/image.jpeg" alt=""></p>
<p>운 좋게도 내 옆자리가 비어 있었다.</p>
<p>덕분에...</p>
<blockquote>
<p><strong>눕코노미 당첨.</strong></p>
</blockquote>
<p>장거리 비행에서 옆자리가 비어 있는 것만큼<br>행복한 일이 없다.</p>
<p>개꿀.</p>
<hr>
<h2 id="한국-도착하자마자-먹은-것">한국 도착하자마자 먹은 것</h2>
<p>그리고 한국 도착하자마자 먹으러 간 곳.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/65ac0e95-45c6-4770-83ae-0b05a16460bb/image.jpeg" alt=""></p>
<p>내가 제일 좋아하는<br><strong>돼지국밥 + 수육</strong>.</p>
<p>거기에 소주 한잔까지.</p>
<p>미국 음식도 맛있지만<br>2주 동안 먹고 돌아오니까 역시...</p>
<blockquote>
<p>한국 음식이 최고다.</p>
</blockquote>
<p>ㄹㅇ 개꿀맛.</p>
<hr>
<h2 id="그리고-수료증">그리고 수료증</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/31cafb5c-bda9-4e16-ace7-9c0ab5caea58/image.jpeg" alt=""></p>
<p>USC에서 받은 수료증은<br>한국에 돌아와서 액자에 고이 모셔놨다.</p>
<p>처음 받았을 때 농담으로</p>
<blockquote>
<p>&quot;이건 액자에 걸어놔야지.&quot;</p>
</blockquote>
<p>했는데,</p>
<p>진짜 걸어놨다.</p>
<p>볼 때마다 2주 동안 있었던 일들이 생각날 것 같다.</p>
<hr>
<h1 id="2주간의-usc-생활을-마치며">2주간의 USC 생활을 마치며</h1>
<p>처음 미국으로 출발할 때는<br>이번 프로그램에서 무엇을 얼마나 얻어올 수 있을지 잘 몰랐다.</p>
<p>물론 가장 큰 목적은<br><strong>USC에서 데이터 사이언스와 AI를 배우는 것</strong>이었다.</p>
<p>실제로 수업에서는 평소 소프트웨어 공부를 하면서 깊게 접하지 않았던<br>데이터 사이언스의 여러 개념들을 배울 수 있었다.</p>
<p>하지만 2주가 끝나고 돌아보니<br>강의실 안에서 배운 것만큼 강의실 밖에서 경험한 것들도 기억에 많이 남는다.</p>
<pre><code class="language-text">USC 캠퍼스에서 생활해보고,

친구들과 LA를 돌아다니고,

기숙사에서 같이 밥을 해먹고,

해커톤 때문에 정신없이 개발하고,

샌디에이고도 다녀오고,

MLB도 직접 보고,

마지막에는 산타 모니카 바다까지.</code></pre>
<p>2주라는 시간이 짧다면 짧은 시간이지만<br>사진을 다시 정리해보니 그 안에서 정말 많은 것을 하고 돌아왔다.</p>
<p>그리고 이번 프로그램에서 개인적으로 가장 크게 느낀 것은<br><strong>내가 아직 모르는 것이 정말 많다는 것</strong>이었다.</p>
<p>평소에는 백엔드 개발이나 알고리즘처럼<br>내가 공부하던 분야를 중심으로만 바라봤다면,</p>
<p>USC에서는 데이터 사이언스와 머신러닝, AI를 조금 더 넓은 관점에서 접할 수 있었다.</p>
<p>모든 것을 2주 안에 깊게 이해할 수는 없었지만,</p>
<blockquote>
<p>&quot;아, 이런 분야가 있고 이런 방식으로 문제를 바라보는구나.&quot;</p>
</blockquote>
<p>를 직접 배우고 경험한 것만으로도 충분히 의미가 있었다고 생각한다.</p>
<p>무엇보다 새로운 내용을 배우면서<br>내가 기존에 공부했던 컴퓨터 사이언스 지식과 연결되는 순간들이 꽤 재미있었다.</p>
<p>그래프 탐색이나 알고리즘처럼 이미 학교에서 배웠던 내용도 다시 등장했고,<br>데이터 사이언스처럼 상대적으로 익숙하지 않았던 분야도 새롭게 공부할 수 있었다.</p>
<hr>
<h1 id="여행은-끝났지만-공부는-이제부터">여행은 끝났지만 공부는 이제부터</h1>
<p>USC에서의 생활은 2주로 끝났지만,<br>개인적으로는 오히려 한국에 돌아온 지금부터가 진짜 시작이라고 생각한다.</p>
<p>수업을 들을 때는 새로운 개념이 계속 쏟아지다 보니<br>그 자리에서 모든 내용을 완벽하게 이해하기는 어려웠다.</p>
<p>그래서 강의를 들으면서</p>
<pre><code class="language-text">이건 다시 공부해야겠다.

이 개념은 학교에서 배운 내용이랑 연결되는 것 같은데?

이 부분은 조금 더 찾아봐야겠다.</code></pre>
<p>싶었던 내용들을 계속 메모해두었다.</p>
<p>한국에 돌아온 뒤에는<br>그 메모와 강의 자료를 다시 하나씩 살펴보면서 공부하고 있다.</p>
<p>그리고 배운 내용을 그냥 혼자 정리하고 끝내기보다는<br>가능하면 이 블로그에 계속 기록해보려고 한다.</p>
<hr>
<h2 id="usc에서-배운-내용들을-하나씩-다시-정리해보려고-한다">USC에서 배운 내용들을 하나씩 다시 정리해보려고 한다</h2>
<p>이번 프로그램에서는 생각보다 정말 다양한 내용을 다뤘다.</p>
<pre><code class="language-text">Data Science
Data Exploration
Data Preprocessing
Tabular Data
Traditional Machine Learning
Deep Learning for Tabular Data
Representation Learning
LLM for Tabular Data
Tabular Foundation Model
Search Algorithm
A* Search
Clustering
Classification
Regression
Anomaly Detection</code></pre>
<p>짧은 기간 동안 상당히 많은 내용을 빠르게 훑었다.</p>
<p>그래서 수업 당시에는</p>
<blockquote>
<p>&quot;아 대충 이런 개념이구나.&quot;</p>
</blockquote>
<p>정도로 넘어간 부분도 많았다.</p>
<p>하지만 블로그에 글을 쓰려고 다시 자료를 읽어보면<br>그때는 그냥 지나갔던 내용들이 새롭게 보이기도 한다.</p>
<p>그리고 내가 잘못 이해했던 부분도 꽤 발견된다.</p>
<p>개인적으로 블로그에 글을 쓰는 이유도 이것 때문이다.</p>
<p>누군가에게 설명하기 위해 글을 쓰다 보면<br>내가 정확하게 이해하지 못한 부분이 어디인지 바로 드러난다.</p>
<pre><code class="language-text">알고 있다고 생각함
↓
글로 설명해보려고 함
↓
설명이 안 됨
↓
다시 공부함
↓
조금 더 제대로 이해함</code></pre>
<p>이 과정이 생각보다 공부에 도움이 많이 된다.</p>
<p>그래서 앞으로 USC에서 배운 내용들도<br>가능한 한 하나씩 다시 정리해서 올릴 예정이다.</p>
<hr>
<h2 id="데이터-사이언스를-전공하지-않기-때문에-더-재미있었던-것-같다">데이터 사이언스를 전공하지 않기 때문에 더 재미있었던 것 같다</h2>
<p>나는 데이터 사이언스를 전공하고 있는 것은 아니다.</p>
<p>그래서 처음에는</p>
<blockquote>
<p>&quot;내가 이 내용을 얼마나 이해할 수 있을까?&quot;</p>
</blockquote>
<p>라는 걱정도 조금 있었다.</p>
<p>하지만 오히려 내가 평소 공부하던 분야와 다른 내용을 배우다 보니<br>새롭게 연결되는 부분들이 많았다.</p>
<p>예를 들어 Search Algorithm 수업에서는</p>
<pre><code class="language-text">BFS
DFS
Greedy
A*</code></pre>
<p>같은 내용이 등장했는데,</p>
<p>이건 학교의 <strong>컴퓨팅 문제와 알고리즘</strong> 과목이나<br>코딩테스트를 준비하면서 계속 공부하던 내용이었다.</p>
<p>반대로 Tabular Data나 Feature Engineering,<br>Anomaly Detection 같은 내용은 상대적으로 생소했다.</p>
<p>그래도 결국 문제를 해결하기 위해</p>
<pre><code class="language-text">데이터를 어떻게 표현할 것인가?
어떤 알고리즘을 사용할 것인가?
어떤 기준으로 결과를 평가할 것인가?
어떻게 시스템에 적용할 것인가?</code></pre>
<p>를 고민한다는 점에서는<br>소프트웨어 개발과 크게 다르지 않다는 생각도 들었다.</p>
<hr>
<h2 id="가장-좋았던-것은-새로운-분야를-직접-접해봤다는-것">가장 좋았던 것은 새로운 분야를 직접 접해봤다는 것</h2>
<p>이번 프로그램을 통해 갑자기</p>
<blockquote>
<p>&quot;나는 앞으로 데이터 사이언티스트가 되어야겠다.&quot;</p>
</blockquote>
<p>라는 생각이 든 것은 아니다.</p>
<p>내가 앞으로 어떤 개발자가 되고 싶은지는 여전히 비슷하다.</p>
<p>다만 이번 경험을 통해<br>내가 사용할 수 있는 도구가 하나 더 늘어난 느낌이다.</p>
<p>예전에는 어떤 문제를 보면</p>
<pre><code class="language-text">서버를 어떻게 만들까?
데이터베이스를 어떻게 설계할까?
어떤 알고리즘을 사용할까?</code></pre>
<p>정도를 먼저 생각했다면,</p>
<p>이제는 여기에</p>
<pre><code class="language-text">이 데이터에는 어떤 패턴이 있을까?
이 문제를 머신러닝으로 해결할 수 있을까?
어떤 Feature가 중요할까?
데이터를 어떻게 수집해야 할까?</code></pre>
<p>같은 질문도 조금씩 추가될 것 같다.</p>
<p>결국 개발자는 문제를 해결하는 사람이고,<br>문제를 해결할 수 있는 방법을 많이 알고 있을수록 선택지도 많아진다고 생각한다.</p>
<p>그런 의미에서 이번 USC 프로그램은<br>내가 기존에 잘 알지 못했던 영역을 직접 경험해본 시간이었다.</p>
<hr>
<h2 id="다시-갈-수-있다면">다시 갈 수 있다면?</h2>
<p>간다.</p>
<p>ㅋㅋㅋㅋㅋㅋㅋㅋ</p>
<p>수업도 좋았고,<br>LA에서 생활했던 것도 좋았고,<br>좋은 사람들을 만난 것도 좋았다.</p>
<p>특히 대학생일 때 이런 프로그램을 경험해볼 수 있다는 것 자체가<br>생각보다 큰 행운이라는 생각이 들었다.</p>
<p>물론 미국 물가는 사악하고,</p>
<pre><code class="language-text">환율
+
외식비
+
교통비</code></pre>
<p>를 볼 때마다 정신이 아찔했지만...</p>
<p>그래도 다시 기회가 생긴다면<br>망설이지 않고 갈 것 같다.</p>
<hr>
<h2 id="마지막으로">마지막으로</h2>
<p>처음 USC에 도착했을 때는</p>
<blockquote>
<p>&quot;2주 동안 뭘 얼마나 할 수 있을까?&quot;</p>
</blockquote>
<p>라는 생각을 했다.</p>
<p>그런데 돌아와서 사진과 메모를 정리해보니<br>생각보다 정말 많은 것들이 남아 있었다.</p>
<pre><code class="language-text">새로운 공부
새로운 사람들
해커톤
LA에서의 생활
여행
그리고 수많은 사진들</code></pre>
<p>아마 시간이 더 지나면<br>세세한 기억들은 조금씩 흐려질 것이다.</p>
<p>그래서 더더욱 지금 기록해두려고 한다.</p>
<p>여행 사진도 그렇고,<br>수업에서 배운 내용도 그렇고,</p>
<p>내가 그때 무엇을 보고, 무엇을 배우고,<br>어떤 생각을 했는지를 남겨놓는 것 자체가<br>나중에는 꽤 좋은 자산이 될 것 같다.</p>
<p>USC에서의 2주 생활기는 여기서 끝!</p>
<p>하지만 USC에서 배운 내용에 대한 글은<br>앞으로도 계속 올라올 예정이다.</p>
<blockquote>
<p><strong>여행은 끝났지만 공부는 이제부터.</strong></p>
</blockquote>
<p>강의에서 배운 내용들을 하나씩 다시 공부하면서<br>잘 정리해서 이 블로그에 계속 기록해보려고 한다.</p>
<p>🇺🇸 USC 시리즈는 계속됩니다 🇺🇸</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | USC에서 보낸 첫 일주일]]></title>
            <link>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-USC%EC%97%90%EC%84%9C-%EB%B3%B4%EB%82%B8-%EC%B2%AB-%EC%9D%BC%EC%A3%BC%EC%9D%BC</link>
            <guid>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-USC%EC%97%90%EC%84%9C-%EB%B3%B4%EB%82%B8-%EC%B2%AB-%EC%9D%BC%EC%A3%BC%EC%9D%BC</guid>
            <pubDate>Wed, 19 Aug 2026 12:15:32 GMT</pubDate>
            <description><![CDATA[<h1 id="🇺🇸-usc-빅데이터-컨퍼런스-참가기🇺🇸--usc에서-보낸-첫-일주일">🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | USC에서 보낸 첫 일주일</h1>
<p>이번 글은 강의 이야기는 잠깐 내려놓고,<br>USC에서 보낸 첫 일주일의 일상을 간단하게 기록해보려고 한다.</p>
<p>학교 구경도 하고, LA 시내도 돌아다니고,<br>친구들과 밥도 해먹고, 마지막에는 해커톤까지...</p>
<p>사진을 다시 보니까 짧은 시간 동안 정말 이것저것 많이 하고 다녔다.</p>
<hr>
<h2 id="usc-캠퍼스-구경">USC 캠퍼스 구경</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/b4aac207-f62c-4b95-af1f-fe4872e2d2ab/image.jpeg" alt=""></p>
<p>서던 캘리포니아 대학교 로고....</p>
<p>멋지다.</p>
<p>확실히 학교 곳곳에 USC 특유의 색깔이 있어서<br>그냥 돌아다니는 것만으로도 미국 대학에 왔다는 느낌이 났다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/f7bab14d-b73e-431a-a80b-8781f4aeb38b/image.jpeg" alt=""></p>
<p>학교 중간에 있는 탑(?)인데<br>딱 봐도 뭔가 상징성이 있어 보였다.</p>
<p>캠퍼스 건물들도 전체적으로 분위기가 비슷해서<br>학교를 돌아다니는 재미가 꽤 있었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/83ee7ba4-a43c-4276-aee5-8d91efbe43fc/image.jpeg" alt=""></p>
<p>학교 안의 고요한 모습.</p>
<p>사람이 없는 시간대에 돌아다니면<br>캠퍼스가 정말 조용하다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/0333c025-3082-415a-9004-78bce1c26817/image.jpeg" alt=""></p>
<p>그리고 내가 생각했던 미국 대학 느낌 그 자체.</p>
<pre><code class="language-text">넓은 잔디
+
서구적인 건물
+
야자수
+
LA의 날씨</code></pre>
<p>영화에서만 보던 미국 대학 느낌이 정말 이런 건가 싶었다.</p>
<p>사진만 봐도 느좋...</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/4d5b4939-b734-41fb-ad6f-fc97b2eb9924/image.jpeg" alt=""></p>
<p>학교 건물 중앙 쪽으로 오면<br>이렇게 시야가 뻥 뚫린 공간도 나온다.</p>
<p>날씨까지 좋아서 그냥 아무 데나 앉아 있어도 좋았다.</p>
<p>LA는 날씨가 진짜 사기다.</p>
<hr>
<h2 id="usc-안에서-만난-도산-안창호-선생님의-흔적">USC 안에서 만난 도산 안창호 선생님의 흔적</h2>
<p>그리고 캠퍼스를 돌아다니다가 발견한 곳.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/de3372a0-ce92-475c-968f-e4d1f0c35590/image.jpeg" alt=""></p>
<p><strong>도산 안창호 하우스.</strong></p>
<p>미국 대학 캠퍼스를 돌아다니다가<br>한국 독립운동가의 이름을 만나니까 느낌이 묘했다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/fb3d6cb7-b2c9-48d0-801b-b66aca1a9dc4/image.jpeg" alt=""></p>
<p>&lt;도산 안창호 하우스 간판&gt;</p>
<p>감사합니다.</p>
<hr>
<h2 id="미국에서의-첫-점심은-chick-fil-a">미국에서의 첫 점심은 Chick-fil-A</h2>
<p>첫날 점심은 친구들과 칙필레로 갔다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c06c707e-d346-43b5-b368-b7f0f40b2c64/image.jpeg" alt=""></p>
<p>치킨버거로 유명한 곳인데<br>딱 생각하는 미국 치킨버거 맛이다.</p>
<p>나는 워낙 많이 먹어봐서...</p>
<blockquote>
<p>미국 3회차라...</p>
</blockquote>
<p>대충 어떤 맛일지 예상이 갔다.</p>
<p>그냥 무난하게 맛있었다.</p>
<hr>
<h2 id="그리고-la-시내로">그리고 LA 시내로</h2>
<p>첫날 학교 투어를 마치고<br>친구들과 <strong>그랜드 센트럴 마켓</strong>으로 향했다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c2bb9ea2-6848-437c-97ec-cf1158b96323/image.jpeg" alt=""></p>
<p>&lt;가는 길에 찍은 건물들&gt;</p>
<p>뭔가 여의도랑 비슷했다.</p>
<p>여기에 NH투자증권 간판 하나만 붙어 있으면 딱일 듯...</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/b865b0cb-7d9e-4258-bf4f-67ef727a39d3/image.jpeg" alt=""></p>
<p>그리고 <strong>라라랜드에 나온 그 열차!</strong></p>
<p>영화에서 봤던 장소를 실제로 보니까<br>괜히 신기했다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/1a7d7897-9dc9-4fd3-a442-40cdacd44e40/image.jpeg" alt=""></p>
<p>LA 시내에 처음 나온 날이라<br>LA뽕 한가득 차서 이런 것도 열심히 찍고 다녔다.</p>
<p>지금 보면</p>
<blockquote>
<p>내가 저걸 왜 찍었지?</p>
</blockquote>
<p>싶은 사진도 많은데,</p>
<p>그때는 그냥 모든 게 신기했다.</p>
<hr>
<h2 id="grand-central-market">Grand Central Market</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/f1f4a021-1999-42ba-bc4b-dd2665ebdef5/image.jpeg" alt=""></p>
<p>그랜드 센트럴 마켓 내부!</p>
<p>분위기 있다.</p>
<p>여러 음식점이 한곳에 모여 있어서<br>구경하는 재미도 꽤 있었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/f17a0f35-29c2-4877-9f08-8751cb5ff549/image.jpeg" alt=""></p>
<p>이탈리아식 피자를 만들어주시는 주인장.</p>
<p>이런 게 또 미국 시장 구경하는 맛이지...</p>
<hr>
<h2 id="hollywood-sign도-갔다">Hollywood Sign도 갔다</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/dd1b81f0-e658-4223-82a0-314bbbbae34f/image.jpeg" alt=""></p>
<p>헐리우드 사인도 갔다.</p>
<p>사실 가기 전에는</p>
<blockquote>
<p>&quot;그냥 산에 HOLLYWOOD 써있는 거 아닌가?&quot;</p>
</blockquote>
<p>싶었다.</p>
<p>근데 실제로 가보니까 생각보다 좋았다.</p>
<p>LA 풍경이랑 헐리우드 사인을 같이 보고 있으니까<br>진짜 영화 속에 들어와 있는 느낌이었다.</p>
<p>사진으로 보는 것보다 직접 보는 게 훨씬 좋았던 곳.</p>
<hr>
<h2 id="usc-기숙사-생활-꿀팁">USC 기숙사 생활 꿀팁</h2>
<p>그리고 USC 기숙사에서 생활한다면<br>개인적으로 이건 꿀팁이다.</p>
<p>기숙사 바로 앞 USC Village 안에<br><strong>Trader Joe&#39;s와 Target</strong>이 있다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c1028724-86b6-4597-b506-1dcdbbc1b95f/image.jpeg" alt=""></p>
<p>생각보다 식재료가 그렇게 비싸지 않았다.</p>
<p>특히</p>
<pre><code class="language-text">소고기
주스
빵
과일
치킨</code></pre>
<p>같은 것들은 외식하는 것과 비교하면 확실히 저렴하다.</p>
<p>그래서 기숙사에서 요리할 수 있다면<br>치킨 윙이나 고기를 사서 직접 해먹는 것을 추천한다.</p>
<p>단,</p>
<blockquote>
<p><strong>냄비와 프라이팬은 필수.</strong></p>
</blockquote>
<p>우리 기숙사 친구들은 조리도구를 꽤 잘 챙겨와서<br>다 같이 이것저것 엄청 해먹었다.</p>
<p>미국은 외식비가 비싸기 때문에<br>이게 생각보다 생활비를 많이 아껴준다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/5bae8576-0808-4fe6-b6ce-e84102e4d0ad/image.jpeg" alt=""></p>
<p>그리고 소고기 스테이크가 <strong>10달러!</strong></p>
<p>말이 돼?</p>
<p>외식 한 번 할 돈이면<br>기숙사에서 고기를 몇 번을 구워 먹을 수 있다.</p>
<hr>
<h2 id="그리고-대망의-해커톤">그리고 대망의 해커톤</h2>
<p>그리고 대망의 <strong>해커톤 날...</strong></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/45c4867c-a617-441b-bb0e-f3dd7feb2e00/image.jpeg" alt=""></p>
<p>우리 팀은 주제를 조금 늦게 정했다.</p>
<p>그만큼 개발 시작도 늦어졌고,</p>
<blockquote>
<p>&quot;이거 시간 안에 완성할 수 있나?&quot;</p>
</blockquote>
<p>라는 걱정을 계속하면서 개발했다.</p>
<p>그래도 어떻게든 범위를 줄이고 개발을 이어가면서<br>제출 약 1시간 전에 MVP를 완성했다.</p>
<p>진짜 겨우 마무리...</p>
<p>해커톤 이야기는 프로젝트 내용까지 들어가면 길어질 것 같아서<br>별도의 글에서 제대로 정리해볼 예정이다.</p>
<hr>
<h2 id="해커톤을-끝내고">해커톤을 끝내고</h2>
<p>그리고 찍은 단체사진!</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/553974a7-3755-4d63-8f84-1c3475117035/image.jpeg" alt=""></p>
<p>사진을 보니까<br>그때 정신없이 개발하던 게 다시 생각난다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/7613a765-2f47-4b7f-8759-4a5afc9194d3/image.jpeg" alt=""></p>
<p>해커톤을 마치고 나면<br>이렇게 USC 인증 증서도 준다.</p>
<p>이건...</p>
<blockquote>
<p>액자에 걸어놔야지.</p>
</blockquote>
<hr>
<h2 id="usc에서의-첫-일주일을-마치며">USC에서의 첫 일주일을 마치며</h2>
<p>이렇게 USC에서 보낸 첫 일주일을<br>사진과 함께 간략하게 정리해봤다.</p>
<pre><code class="language-text">USC 캠퍼스 구경
↓
LA 시내 구경
↓
Grand Central Market
↓
Hollywood
↓
기숙사에서 열심히 해먹기
↓
그리고 해커톤</code></pre>
<p>사진을 모아서 다시 보니까<br>일주일 동안 생각보다 정말 많은 것을 하고 다녔다.</p>
<p>강의를 들으러 미국에 왔지만<br>강의실 밖에서 보고 경험했던 것들도 꽤 기억에 많이 남는다.</p>
<p>특히 영화에서만 보던 미국 대학 캠퍼스에서 생활하고,<br>수업이 끝나면 친구들과 LA를 돌아다니고,<br>기숙사에 돌아와서는 같이 밥을 해먹던 일상이 꽤 재미있었다.</p>
<p>그리고 첫 주의 마지막은 해커톤으로 아주 제대로 불태웠다...</p>
<p>2주 차에는 또 다른 일정들이 기다리고 있었으니,</p>
<p><strong>Coming Soon...</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[레벨 1] 프로그래머스 오늘의 문제 : 명예의 전당 (1) - Priority Queue로 Top-K 관리하기]]></title>
            <link>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%9E%98%EB%B2%A8-1</link>
            <guid>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%9E%98%EB%B2%A8-1</guid>
            <pubDate>Tue, 18 Aug 2026 06:34:48 GMT</pubDate>
            <description><![CDATA[<h1 id="레벨-1-프로그래머스-오늘의-문제--명예의-전당-1---priority-queue로-top-k-관리하기">[레벨 1] 프로그래머스 오늘의 문제 : 명예의 전당 (1) - Priority Queue로 Top-K 관리하기</h1>
<blockquote>
<p>문제풀이 시간 : 20분</p>
</blockquote>
<p>오늘의 코테 연습문제는 프로그래머스의<br><strong>명예의 전당 (1)</strong> 문제다.</p>
<p>문제 링크는 아래와 같다.</p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/138477?language=cpp">https://school.programmers.co.kr/learn/courses/30/lessons/138477?language=cpp</a></p>
<hr>
<h2 id="문제-설명">문제 설명</h2>
<p>&quot;명예의 전당&quot;이라는 TV 프로그램에서는 매일 한 명의 가수가 노래를 부르고,<br>시청자들의 문자 투표수에 따라 점수를 받는다.</p>
<p>매일 새로운 점수가 들어왔을 때<br>지금까지 등장한 점수 중 <strong>상위 K개의 점수만 명예의 전당에 유지</strong>한다.</p>
<p>그리고 매일 발표해야 하는 값은</p>
<blockquote>
<p><strong>현재 명예의 전당에 올라가 있는 점수들 중 가장 낮은 점수</strong></p>
</blockquote>
<p>다.</p>
<p>예를 들어</p>
<pre><code class="language-text">k = 3

score = [10, 100, 20, 150, 1, 100, 200]</code></pre>
<p>라면 매일 발표되는 점수는</p>
<pre><code class="language-text">[10, 10, 10, 20, 20, 100, 100]</code></pre>
<p>이 된다.</p>
<hr>
<h2 id="제한-사항">제한 사항</h2>
<pre><code class="language-text">3 ≤ k ≤ 100

7 ≤ score의 길이 ≤ 1,000

0 ≤ score[i] ≤ 2,000</code></pre>
<hr>
<h2 id="처음-문제를-봤을-때">처음 문제를 봤을 때</h2>
<p>문제를 읽고</p>
<blockquote>
<p>&quot;이거 생각보다 너무 쉬운데?&quot;</p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>처음에는 <code>queue</code>, <code>priority_queue</code>가 바로 떠올랐다.</p>
<p>그런데 문제의 입력 크기를 보니<br>일반 <code>vector</code>를 이용해서 정렬해도 충분히 풀 수 있겠다는 생각이 들었다.</p>
<p>문제를 다시 아주 단순하게 정리하면 다음과 같다.</p>
<blockquote>
<p><strong>매일 지금까지 나온 점수 중 상위 K개를 기준으로,<br>그 K개 중 가장 낮은 점수를 출력하라.</strong></p>
</blockquote>
<p>처음 K명이 다 차기 전까지는<br>지금까지 등장한 점수 중 최솟값을 출력하면 된다.</p>
<pre><code class="language-text">현재까지 나온 사람 수 ≤ K
→ 지금까지 점수 중 최솟값</code></pre>
<p>그리고 K명보다 많은 사람이 등장한 이후부터는<br>전체 점수를 오름차순으로 정렬했을 때</p>
<pre><code class="language-text">rank.size() - k</code></pre>
<p>번째 원소가 현재 상위 K개 중 가장 작은 점수가 된다.</p>
<p>예를 들어</p>
<pre><code class="language-text">score = [10, 20, 100, 150]

정렬 후
[10, 20, 100, 150]

k = 3</code></pre>
<p>이라면 상위 3개는</p>
<pre><code class="language-text">20, 100, 150</code></pre>
<p>이고,</p>
<pre><code class="language-text">rank.size() - k

= 4 - 3
= 1</code></pre>
<p>이므로</p>
<pre><code class="language-text">rank[1] = 20</code></pre>
<p>을 출력하면 된다.</p>
<p>아이디어는 단순했다.</p>
<p>곧바로 구현에 들어갔다.</p>
<hr>
<h2 id="나의-풀이">나의 풀이</h2>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt; 
using namespace std;

vector&lt;int&gt; solution(int k, vector&lt;int&gt; score) { 
    // 명예의 전당 목록의 점수의 개수 K,
    // 1일부터 마지막 날까지 출연한 가수들의 점수인 score,
    // 결과물은 그 날의 가장 최하위 점수
    vector&lt;int&gt; answer;

    // 1단계: 먼저 rank 라는 vector를 만든다.
    // 그리고 K보다 작을 경우의 로직까지 설계 (가장 최하위만 return)
    vector&lt;int&gt; rank; 

    for (int x : score) {

        rank.push_back(x); // score에 있는 숫자를 rank로 하나씩 넣은 후
        sort(rank.begin(), rank.end()); // 정렬까지 맡는다

        // case 1) : k보다 작을 때는 그냥 sort로 정렬 후
        // 0번째에 있는 숫자를 넣으면 간단
        if (rank.size() &lt;= k) {
            answer.push_back(rank[0]); 
            continue; 
        }

        // case 2) : 현재 rank는 오름차순으로 정렬되어 있음.
        // 즉 rank.size() - k 위치가
        // 현재 상위 k개의 점수 중 가장 작은 숫자임
        answer.push_back(rank[rank.size() - k]);    
    }

    return answer;
}</code></pre>
<hr>
<h2 id="결과는">결과는?</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/75772a1e-2673-45a1-92f4-2421debada5a/image.png" alt=""></p>
<p>정답이었다.</p>
<p>생각보다 쉽게 풀려서 조금 당황했지만...</p>
<blockquote>
<p>그래... 이게 레벨 1이지...</p>
</blockquote>
<p>라는 생각도 들었다.</p>
<p>하지만 문제를 맞히고 나서 코드를 다시 보니<br>효율성 측면에서는 꽤 아쉬운 부분이 있었다.</p>
<hr>
<h1 id="문제는-매번-정렬한다는-것">문제는 매번 정렬한다는 것</h1>
<p>핵심은 이 부분이다.</p>
<pre><code class="language-cpp">for (int x : score) {

    rank.push_back(x);
    sort(rank.begin(), rank.end());</code></pre>
<p>매일 새로운 점수가 하나 들어올 때마다<br><code>rank</code> 전체를 다시 정렬하고 있다.</p>
<p>예를 들어 점수가 총 <code>N</code>개라면<br>정렬되는 배열의 크기는 다음처럼 증가한다.</p>
<pre><code class="language-text">1개 정렬
2개 정렬
3개 정렬
...
N개 정렬</code></pre>
<p>각 정렬 비용은 대략</p>
<pre><code class="language-text">1 log 1
2 log 2
3 log 3
...
N log N</code></pre>
<p>이 된다.</p>
<p>따라서 전체 연산량은</p>
<pre><code class="language-text">Σ i log i</code></pre>
<p>형태가 되고,</p>
<p>Big-O로 보면 대략</p>
<pre><code class="language-text">O(N² log N)</code></pre>
<p>으로 볼 수 있다.</p>
<p>물론 이 문제에서는</p>
<pre><code class="language-text">N ≤ 1,000</code></pre>
<p>이라 충분히 통과한다.</p>
<p>하지만 생각해보면 조금 이상하다.</p>
<p>문제에서 우리가 실제로 필요한 것은</p>
<pre><code class="language-text">지금까지 등장한 모든 점수</code></pre>
<p>가 아니다.</p>
<p>필요한 것은 오직</p>
<pre><code class="language-text">현재 상위 K개의 점수</code></pre>
<p>뿐이다.</p>
<p>즉 굳이 모든 점수를 계속 저장하고<br>매번 전체를 다시 정렬할 필요가 없는 것이다.</p>
<hr>
<h1 id="더-나은-자료구조는-없을까">더 나은 자료구조는 없을까?</h1>
<p>여기서 처음 문제를 읽었을 때 떠올렸던<br><strong>Priority Queue</strong>가 다시 등장한다.</p>
<p>우선순위 큐는 일반 Queue처럼</p>
<pre><code class="language-text">먼저 들어온 값</code></pre>
<p>을 먼저 꺼내는 것이 아니라,</p>
<pre><code class="language-text">우선순위가 가장 높은 값</code></pre>
<p>을 먼저 꺼낼 수 있는 자료구조다.</p>
<p>C++의 <code>priority_queue</code>는 기본적으로 <strong>Max Heap</strong>으로 동작한다.</p>
<pre><code class="language-cpp">priority_queue&lt;int&gt; pq;</code></pre>
<p>즉 가장 큰 값이 <code>top()</code>에 위치한다.</p>
<p>하지만 이번 문제에서는<br>명예의 전당 상위 K명 중</p>
<blockquote>
<p><strong>가장 작은 점수</strong></p>
</blockquote>
<p>를 계속 알아야 한다.</p>
<p>따라서 필요한 것은 <strong>Min Heap</strong>이다.</p>
<hr>
<h1 id="왜-min-heap이-필요한가">왜 Min Heap이 필요한가?</h1>
<p>명예의 전당에 다음 점수들이 있다고 해보자.</p>
<pre><code class="language-text">20
100
150</code></pre>
<p>여기서 우리가 매일 발표해야 하는 점수는</p>
<pre><code class="language-text">20</code></pre>
<p>이다.</p>
<p>그리고 새로운 점수</p>
<pre><code class="language-text">200</code></pre>
<p>이 들어오면</p>
<pre><code class="language-text">20
100
150
200</code></pre>
<p>중 상위 3명만 남겨야 한다.</p>
<p>즉 가장 작은 값인</p>
<pre><code class="language-text">20</code></pre>
<p>을 제거하면 된다.</p>
<p>결과는</p>
<pre><code class="language-text">100
150
200</code></pre>
<p>이다.</p>
<p>다시 말하면 매번 필요한 연산은</p>
<pre><code class="language-text">현재 상위 K개 중 최솟값 확인
↓
새로운 점수가 들어옴
↓
K개를 초과하면 최솟값 제거</code></pre>
<p>이다.</p>
<p>이 구조는 Min Heap과 정확히 맞는다.</p>
<hr>
<h1 id="c에서-min-heap-만들기">C++에서 Min Heap 만들기</h1>
<p>C++에서는 다음처럼 작성할 수 있다.</p>
<pre><code class="language-cpp">priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; rank;</code></pre>
<p>처음 보면 굉장히 복잡해 보인다.</p>
<p>하나씩 뜯어보자.</p>
<pre><code class="language-text">int
→ 저장할 자료형

vector&lt;int&gt;
→ 내부적으로 데이터를 저장할 Container

greater&lt;int&gt;
→ 작은 값을 더 높은 우선순위로 두는 비교 기준</code></pre>
<p>즉,</p>
<pre><code class="language-cpp">priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt;</code></pre>
<p>는</p>
<blockquote>
<p><strong>int 값을 저장하고, 가장 작은 값을 top()에서 바로 꺼낼 수 있는 Priority Queue</strong></p>
</blockquote>
<p>라고 보면 된다.</p>
<hr>
<h1 id="priority-queue를-활용한-풀이">Priority Queue를 활용한 풀이</h1>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt;
using namespace std;

vector&lt;int&gt; solution(int k, vector&lt;int&gt; score) {
    vector&lt;int&gt; answer;

    priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; rank;

    for (int x : score) {

        rank.push(x);

        if (rank.size() &gt; k) {
            rank.pop();
        }

        answer.push_back(rank.top());
    }

    return answer;
}</code></pre>
<p>처음 내 코드보다 훨씬 짧아졌다.</p>
<hr>
<h1 id="코드-흐름을-하나씩-살펴보자">코드 흐름을 하나씩 살펴보자</h1>
<p>먼저 Min Heap을 만든다.</p>
<pre><code class="language-cpp">priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; rank;</code></pre>
<p>그리고 하루의 점수를 하나씩 넣는다.</p>
<pre><code class="language-cpp">rank.push(x);</code></pre>
<p>아직 K명이 다 차지 않았다면<br>그냥 계속 넣으면 된다.</p>
<p>예를 들어</p>
<pre><code class="language-text">k = 3

10 입력
→ [10]

100 입력
→ [10, 100]

20 입력
→ [10, 20, 100]</code></pre>
<p>Min Heap의 <code>top()</code>에는 항상<br>현재 가장 작은 값이 존재한다.</p>
<p>따라서</p>
<pre><code class="language-cpp">rank.top();</code></pre>
<p>을 그대로 발표하면 된다.</p>
<hr>
<h1 id="k개를-초과하면">K개를 초과하면?</h1>
<p>새로운 점수를 넣은 뒤</p>
<pre><code class="language-cpp">if (rank.size() &gt; k) {
    rank.pop();
}</code></pre>
<p>을 수행한다.</p>
<p>Min Heap에서는 <code>pop()</code>을 하면<br>가장 작은 값이 제거된다.</p>
<p>예를 들어</p>
<pre><code class="language-text">현재 명예의 전당

10
20
100</code></pre>
<p>에</p>
<pre><code class="language-text">150</code></pre>
<p>이 들어온다고 해보자.</p>
<p>우선 넣는다.</p>
<pre><code class="language-text">10
20
100
150</code></pre>
<p>크기가 <code>k = 3</code>을 초과했으므로</p>
<pre><code class="language-cpp">rank.pop();</code></pre>
<p>을 수행한다.</p>
<p>그러면 가장 작은</p>
<pre><code class="language-text">10</code></pre>
<p>이 제거된다.</p>
<p>결과적으로</p>
<pre><code class="language-text">20
100
150</code></pre>
<p>만 남는다.</p>
<p>정확히 상위 3개의 점수만 유지되는 것이다.</p>
<hr>
<h1 id="현재-최하위-점수는">현재 최하위 점수는?</h1>
<p>Min Heap에는 항상 상위 K개의 점수만 남아 있다.</p>
<p>그리고 그중 가장 작은 값은</p>
<pre><code class="language-cpp">rank.top()</code></pre>
<p>이다.</p>
<p>따라서 매일</p>
<pre><code class="language-cpp">answer.push_back(rank.top());</code></pre>
<p>만 하면 된다.</p>
<p>정말 문제의 요구사항과 자료구조의 특성이 정확하게 맞아떨어진다.</p>
<hr>
<h1 id="두-풀이의-차이를-비교해보자">두 풀이의 차이를 비교해보자</h1>
<p>내 첫 번째 풀이는</p>
<pre><code class="language-text">지금까지의 모든 점수를 저장
↓
매번 전체 정렬
↓
상위 K번째 위치 확인</code></pre>
<p>이었다.</p>
<p>반면 Priority Queue 풀이는</p>
<pre><code class="language-text">상위 K개의 점수만 저장
↓
가장 작은 점수는 항상 top()
↓
K개를 넘으면 가장 작은 값 제거</code></pre>
<p>이다.</p>
<p>구조 자체가 훨씬 문제의 요구사항에 가깝다.</p>
<hr>
<h1 id="시간복잡도-비교">시간복잡도 비교</h1>
<h2 id="첫-번째-풀이">첫 번째 풀이</h2>
<p>매번 커지는 전체 배열을 다시 정렬한다.</p>
<pre><code class="language-text">O(N² log N)</code></pre>
<p>정도로 볼 수 있다.</p>
<h2 id="priority-queue-풀이">Priority Queue 풀이</h2>
<p>Heap에는 최대 K개의 원소만 존재한다.</p>
<p><code>push()</code>와 <code>pop()</code>은 각각</p>
<pre><code class="language-text">O(log K)</code></pre>
<p>이다.</p>
<p>이를 N개의 점수에 대해 반복하므로</p>
<pre><code class="language-text">O(N log K)</code></pre>
<p>가 된다.</p>
<p>그리고 Heap의 크기도 최대 K이므로<br>공간복잡도는</p>
<pre><code class="language-text">O(K)</code></pre>
<p>이다.</p>
<p>첫 번째 풀이는 모든 점수를 저장하기 때문에<br>공간은</p>
<pre><code class="language-text">O(N)</code></pre>
<p>이 필요하다.</p>
<hr>
<h1 id="여기서-한-가지-중요한-점">여기서 한 가지 중요한 점</h1>
<p>처음에는</p>
<blockquote>
<p>&quot;내 코드도 결국 score를 한 번 순회하니까 O(score.size()) 정도 아닌가?&quot;</p>
</blockquote>
<p>라고 생각할 수 있다.</p>
<p>하지만 <strong>반복문이 한 번 있다고 해서 무조건 O(N)은 아니다.</strong></p>
<p>반복문 안에서 어떤 연산을 하는지도 봐야 한다.</p>
<p>내 코드에서는 매 반복마다</p>
<pre><code class="language-cpp">sort(rank.begin(), rank.end());</code></pre>
<p>가 실행된다.</p>
<p><code>sort()</code> 자체가 배열 크기 <code>M</code>에 대해</p>
<pre><code class="language-text">O(M log M)</code></pre>
<p>의 시간이 필요하다.</p>
<p>그리고 <code>M</code>이</p>
<pre><code class="language-text">1, 2, 3, ..., N</code></pre>
<p>으로 계속 커진다.</p>
<p>따라서 단순한</p>
<pre><code class="language-text">O(N)</code></pre>
<p>순회라고 볼 수 없다.</p>
<p>이번 문제에서 개인적으로 가장 다시 기억해둘 만한 부분이었다.</p>
<hr>
<h1 id="사실-내-첫-번째-풀이도-실전에서는-나쁘지-않다">사실 내 첫 번째 풀이도 실전에서는 나쁘지 않다</h1>
<p>그렇다고 첫 번째 풀이가 잘못된 풀이는 아니다.</p>
<p>문제의 제한을 보면</p>
<pre><code class="language-text">score.size() ≤ 1,000</code></pre>
<p>이다.</p>
<p>따라서 매번 정렬하더라도 충분히 빠르게 통과한다.</p>
<p>코딩테스트에서 가장 중요한 것은 우선</p>
<pre><code class="language-text">정확하게 풀리는 코드를 제한 시간 안에 작성하는 것</code></pre>
<p>이다.</p>
<p>처음부터 항상 가장 최적화된 풀이만 찾으려고 하다가<br>문제 자체를 못 푸는 것보다는</p>
<pre><code class="language-text">1. 먼저 확실한 풀이를 구현한다.
2. 시간복잡도를 확인한다.
3. 필요하다면 더 적절한 자료구조로 개선한다.</code></pre>
<p>라는 접근이 실전에서는 훨씬 현실적이라고 생각한다.</p>
<p>이번에도 내가 처음 떠올린 <code>vector + sort</code> 방식으로<br>문제를 먼저 해결한 뒤,</p>
<blockquote>
<p>&quot;현재 상위 K개만 유지하면 되는데 왜 전체 데이터를 정렬하고 있지?&quot;</p>
</blockquote>
<p>라는 질문을 통해 Priority Queue 풀이로 개선할 수 있었다.</p>
<hr>
<h1 id="문제를-풀고-나서">문제를 풀고 나서</h1>
<p>처음 문제를 읽었을 때는 생각보다 너무 간단해서<br>그냥 배열을 이용해 바로 풀었다.</p>
<p>실제로 제한사항만 본다면<br>내 풀이도 충분히 통과하는 코드였다.</p>
<p>하지만 문제를 풀고 나서 다시 보니<br>이 문제는 Priority Queue를 연습하기 정말 좋은 문제였다.</p>
<p>핵심은 이것이다.</p>
<pre><code class="language-text">상위 K개만 유지한다.

그리고 그 K개 중
가장 작은 값이 계속 필요하다.</code></pre>
<p>이 문장을 보는 순간</p>
<pre><code class="language-text">Min Heap</code></pre>
<p>을 떠올릴 수 있어야 한다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>이번 문제를 통해 가져갈 핵심은 크게 세 가지다.</p>
<pre><code class="language-text">1. 먼저 단순한 풀이로 문제를 해결하는 것도 충분히 좋은 접근이다.

2. 반복문 안에서 sort()를 수행한다면
   단순히 O(N)이라고 생각하면 안 된다.

3. &quot;상위 K개를 계속 유지하면서 그중 최솟값이 필요하다&quot;
   는 문제에서는 Min Heap을 떠올릴 수 있다.</code></pre>
<p>특히 Priority Queue 풀이의 시간복잡도는</p>
<pre><code class="language-text">O(N log K)</code></pre>
<p>이고,</p>
<p>내 첫 번째 <code>vector + sort</code> 풀이보다<br>문제의 구조 자체를 훨씬 직접적으로 표현한다.</p>
<p>처음 문제를 풀 때</p>
<pre><code class="language-text">queue?
priority_queue?
vector?</code></pre>
<p>를 고민하다가 가장 익숙한 <code>vector</code>로 풀었는데,<br>결과적으로는 Priority Queue를 다시 공부할 좋은 계기가 됐다.</p>
<p>앞으로 코딩테스트에서</p>
<pre><code class="language-text">최댓값 또는 최솟값을 계속 꺼내야 한다.

상위 K개 또는 하위 K개만 유지해야 한다.

값이 계속 들어오는데 매번 전체 정렬하기에는 비효율적이다.</code></pre>
<p>라는 조건이 보이면<br>Priority Queue를 먼저 떠올려봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Activity Diagram이란 무엇이고, 언제 그릴까?]]></title>
            <link>https://velog.io/@daehyun_lee/Activity-Diagram%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%96%B8%EC%A0%9C-%EA%B7%B8%EB%A6%B4%EA%B9%8C</link>
            <guid>https://velog.io/@daehyun_lee/Activity-Diagram%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%96%B8%EC%A0%9C-%EA%B7%B8%EB%A6%B4%EA%B9%8C</guid>
            <pubDate>Fri, 14 Aug 2026 04:59:34 GMT</pubDate>
            <description><![CDATA[<h1 id="activity-diagram이란-무엇이고-언제-그릴까">Activity Diagram이란 무엇이고, 언제 그릴까?</h1>
<p>소프트웨어 모델링을 공부하다 보면<br>UML(Unified Modeling Language)이라는 말을 정말 자주 보게 된다.</p>
<p>그중에서도 이번에 정리할 것은 <strong>Activity Diagram</strong>이다.</p>
<p>처음 Activity Diagram을 봤을 때는</p>
<h4 id=""></h4>
<blockquote>
<p>&quot;그냥 순서도(Flowchart)랑 비슷한 거 아닌가?&quot;</p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>실제로 둘은 꽤 비슷하게 생겼다.</p>
<p>하지만 Activity Diagram은 단순히</p>
<pre><code class="language-text">A를 하고
↓
B를 하고
↓
C를 한다</code></pre>
<p>정도의 실행 순서만 표현하는 것이 아니다.</p>
<p>조건에 따라 흐름이 나뉘거나,<br>여러 작업이 병렬적으로 진행되거나,<br>Action 사이에서 어떤 데이터가 전달되는지까지 표현할 수 있다.</p>
<p>이번 글에서는 Activity Diagram이 무엇인지부터<br>각 구성 요소가 어떤 역할을 하는지 하나씩 정리해보고자 한다.</p>
<hr>
<h2 id="activity-diagram이란">Activity Diagram이란?</h2>
<p>Activity Diagram은 UML에서</p>
<blockquote>
<p><strong>시스템이나 비즈니스 프로세스의 작업 흐름(Workflow)을 표현하는 Diagram</strong></p>
</blockquote>
<p>이라고 볼 수 있다.</p>
<p>쉽게 말하면</p>
<pre><code class="language-text">어떤 일이 시작되고
↓
어떤 작업들이 수행되고
↓
어떤 조건에서 흐름이 나뉘고
↓
어떤 작업들이 동시에 수행되고
↓
어디에서 끝나는가</code></pre>
<p>를 시각적으로 표현하는 것이다.</p>
<p>예를 들어 쇼핑몰의 주문 과정을 생각해보자.</p>
<pre><code class="language-text">상품 선택
↓
주문서 작성
↓
결제
↓
주문 완료</code></pre>
<p>단순하게 보면 이런 흐름이지만 실제 시스템에서는 조금 더 복잡하다.</p>
<pre><code class="language-text">상품 선택
↓
주문서 작성
↓
결제 성공?
├─ Yes → 주문 완료
└─ No  → 결제 재시도</code></pre>
<p>주문이 완료된 뒤에는</p>
<pre><code class="language-text">고객에게 알림 전송
재고 감소
주문 기록 저장</code></pre>
<p>같은 작업들이 동시에 또는 서로 다른 흐름으로 수행될 수도 있다.</p>
<p>Activity Diagram은 이런 전체적인 <strong>행동의 흐름</strong>을 표현하기 좋다.</p>
<hr>
<h1 id="activity-diagram은-언제-그릴까">Activity Diagram은 언제 그릴까?</h1>
<p>Activity Diagram은 특히 다음과 같은 상황에서 유용하다.</p>
<h3 id="1-비즈니스-프로세스를-표현할-때">1. 비즈니스 프로세스를 표현할 때</h3>
<p>예를 들어 쇼핑몰 주문 프로세스가 있다.</p>
<pre><code class="language-text">상품 선택
↓
주문
↓
결제
↓
배송</code></pre>
<p>이런 Workflow를 개발자뿐만 아니라<br>기획자나 비개발자와 함께 확인해야 할 때 Activity Diagram이 꽤 직관적이다.</p>
<h3 id="2-use-case의-내부-흐름을-자세히-표현할-때">2. Use Case의 내부 흐름을 자세히 표현할 때</h3>
<p>Use Case Diagram에서</p>
<pre><code class="language-text">사용자 → 상품 주문</code></pre>
<p>이라고만 표현하면<br>&quot;상품 주문&quot; 내부에서 실제로 어떤 일이 일어나는지는 알 수 없다.</p>
<p>Activity Diagram을 사용하면</p>
<pre><code class="language-text">상품 선택
↓
주문서 작성
↓
결제
↓
결제 성공 여부 확인
↓
주문 완료</code></pre>
<p>처럼 내부 흐름까지 구체화할 수 있다.</p>
<h3 id="3-복잡한-조건-분기를-표현할-때">3. 복잡한 조건 분기를 표현할 때</h3>
<p>프로그램에서 <code>if</code>, <code>else</code>, <code>switch</code> 같은 조건 분기가 많아지면<br>코드만 보고 전체 흐름을 이해하기 어려울 수 있다.</p>
<p>Activity Diagram에서는 Decision Node를 이용해</p>
<pre><code class="language-text">결제 성공?
├─ Yes → 주문 완료
└─ No  → 재시도</code></pre>
<p>처럼 표현할 수 있다.</p>
<h3 id="4-병렬적으로-진행되는-작업을-표현할-때">4. 병렬적으로 진행되는 작업을 표현할 때</h3>
<p>하나의 작업이 끝난 뒤 여러 일이 독립적으로 진행될 수도 있다.</p>
<p>예를 들어 주문 완료 후</p>
<pre><code class="language-text">알림 전송
재고 감소
포인트 적립</code></pre>
<p>이 병렬적으로 진행될 수 있다.</p>
<p>이런 흐름은 Fork / Join Node를 이용해서 표현할 수 있다.</p>
<hr>
<h1 id="activity-diagram의-구성-요소">Activity Diagram의 구성 요소</h1>
<p>Activity Diagram에서 자주 사용하는 구성 요소는 다음과 같다.</p>
<pre><code class="language-text">Action / Activity
Object Node
Control Flow
Object Flow
Initial Node
Final Node
Decision Node
Merge Node
Fork Node
Join Node</code></pre>
<p>처음 보면 꽤 많아 보인다.</p>
<p>그래서 나는 다음과 같이 묶어서 이해하는 것이 편했다.</p>
<pre><code class="language-text">실제 작업
→ Action / Activity

흐름
→ Control Flow / Object Flow

데이터
→ Object Node

시작과 종료
→ Initial / Final

조건 분기
→ Decision / Merge

병렬 처리
→ Fork / Join</code></pre>
<hr>
<h1 id="action--activity">Action / Activity</h1>
<p>가장 먼저 실제로 수행하는 작업을 표현하는<br><strong>Action</strong>이 있다.</p>
<p>예를 들어 다음과 같은 것들이다.</p>
<pre><code class="language-text">상품을 선택한다.
주문서를 작성한다.
결제를 진행한다.
주문을 취소한다.</code></pre>
<p>보통 Action의 이름은</p>
<pre><code class="language-text">동사 + 명사</code></pre>
<p>형태로 작성하면 무엇을 수행하는지 이해하기 쉽다.</p>
<p>예를 들어</p>
<pre><code class="language-text">상품 선택
결제 진행
재고 확인
주문 저장</code></pre>
<p>같은 식이다.</p>
<h2 id="action과-activity는-같은-것일까">Action과 Activity는 같은 것일까?</h2>
<p>처음에는 이 둘이 조금 헷갈렸다.</p>
<p>Diagram에서 비슷한 둥근 사각형 형태로 표현되는 경우가 많기 때문이다.</p>
<p>하지만 개념적으로는 차이가 있다.</p>
<h3 id="action">Action</h3>
<p>Action은 하나의 Activity 안에서 실행되는<br><strong>더 이상 세분화하지 않고 하나의 실행 단위로 보는 작업</strong>이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">상품 선택
결제 진행
주문 저장</code></pre>
<p>같은 작업이다.</p>
<h3 id="activity">Activity</h3>
<p>Activity는 여러 Action이나 다른 제어 흐름을 포함할 수 있는<br><strong>전체적인 행동 또는 Workflow</strong>에 가깝다.</p>
<p>예를 들어</p>
<pre><code class="language-text">상품 주문</code></pre>
<p>이라는 Activity 안에는</p>
<pre><code class="language-text">상품 선택
↓
주문서 작성
↓
결제
↓
주문 완료</code></pre>
<p>라는 여러 Action이 포함될 수 있다.</p>
<p>즉,</p>
<pre><code class="language-text">Activity
→ 전체적인 행동 흐름

Action
→ 그 안에서 수행되는 하나의 실행 단위</code></pre>
<p>정도로 이해하면 편하다.</p>
<p>다만 실제 UML 모델링에서는 Activity 안에 다른 Activity를 호출하는 Action이 존재할 수도 있기 때문에<br>단순히 &quot;Activity는 큰 것, Action은 작은 것&quot;이라고만 외우기보다</p>
<blockquote>
<p><strong>Action은 실행되는 하나의 Node이고, Activity는 전체 Behavior의 흐름을 정의한다</strong></p>
</blockquote>
<p>정도로 이해하는 것이 더 정확하다.</p>
<hr>
<h1 id="object-node">Object Node</h1>
<p>Object Node는</p>
<blockquote>
<p><strong>Activity 안에서 Action이 사용하거나 만들어내는 데이터 또는 객체</strong></p>
</blockquote>
<p>를 표현한다.</p>
<p>예를 들어 주문 과정을 생각해보자.</p>
<pre><code class="language-text">[주문 생성]
      ↓
  주문 정보
      ↓
[결제 처리]</code></pre>
<p>여기에서</p>
<pre><code class="language-text">주문 정보</code></pre>
<p>가 Object Node다.</p>
<p>즉,</p>
<pre><code class="language-text">Action
↓
데이터 생성
↓
다른 Action에서 사용</code></pre>
<p>되는 흐름에서<br>중간의 데이터 자체를 표현한다고 보면 된다.</p>
<h2 id="코드로-생각하면-더-쉽다">코드로 생각하면 더 쉽다</h2>
<p>예를 들어 Java 코드가 다음과 같다고 해보자.</p>
<pre><code class="language-java">Order order = createOrder();

pay(order);</code></pre>
<p><code>createOrder()</code>에서</p>
<pre><code class="language-text">Order 객체</code></pre>
<p>가 만들어진다.</p>
<p>그리고 그 객체가</p>
<pre><code class="language-text">pay(order)</code></pre>
<p>에 전달된다.</p>
<p>Activity Diagram에서는 이 <code>order</code>와 같은 객체를<br>Object Node로 표현할 수 있다.</p>
<hr>
<h1 id="control-flow">Control Flow</h1>
<p>Control Flow는</p>
<blockquote>
<p><strong>Action이 어떤 순서로 실행되는지를 나타내는 화살표</strong></p>
</blockquote>
<p>다.</p>
<p>예를 들어</p>
<pre><code class="language-text">[상품 선택]
      ↓
[주문서 작성]
      ↓
[결제 진행]
      ↓
[주문 완료]</code></pre>
<p>가 있다면<br>각 Action 사이의 화살표가 Control Flow다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>&quot;다음에 무엇을 실행하지?&quot;</strong></p>
</blockquote>
<p>를 표현한다.</p>
<hr>
<h1 id="object-flow">Object Flow</h1>
<p>Object Flow는 Control Flow와 비슷하게 화살표를 사용하지만<br>의미가 조금 다르다.</p>
<p>Object Flow는</p>
<blockquote>
<p><strong>Object나 Data가 Action 사이에서 어떻게 전달되는지를 나타낸다.</strong></p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-text">[주문 생성]
      │
      ▼
 ┌─────────┐
 │ 주문 정보 │
 └─────────┘
      │
      ▼
[결제 처리]</code></pre>
<p>이 흐름에서</p>
<pre><code class="language-text">주문 정보 → 결제 처리</code></pre>
<p>처럼 데이터가 전달되는 흐름이 Object Flow다.</p>
<p>앞에서 봤던 코드로 생각하면</p>
<pre><code class="language-java">Order order = createOrder();

pay(order);</code></pre>
<p>에서 <code>order</code> 객체가 <code>pay()</code>로 전달되는 흐름이라고 볼 수 있다.</p>
<h2 id="control-flow와-object-flow의-차이">Control Flow와 Object Flow의 차이</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>의미</th>
<th>핵심 질문</th>
</tr>
</thead>
<tbody><tr>
<td>Control Flow</td>
<td>실행 순서</td>
<td>&quot;다음에 무엇을 실행하지?&quot;</td>
</tr>
<tr>
<td>Object Flow</td>
<td>데이터 전달</td>
<td>&quot;어떤 데이터가 어디로 넘어가지?&quot;</td>
</tr>
</tbody></table>
<hr>
<h1 id="control-node">Control Node</h1>
<p>Activity Diagram에는 작업 자체를 수행하지 않고<br><strong>Flow를 제어하는 Node</strong>들이 있다.</p>
<p>대표적으로 다음 여섯 가지다.</p>
<pre><code class="language-text">Initial Node
Final Node
Decision Node
Merge Node
Fork Node
Join Node</code></pre>
<p>처음에는 이름이 많아서 헷갈렸는데<br>세 쌍으로 묶어서 생각하면 상당히 편하다.</p>
<pre><code class="language-text">시작 / 종료
→ Initial ↔ Final

조건 분기
→ Decision ↔ Merge

병렬 처리
→ Fork ↔ Join</code></pre>
<hr>
<h1 id="initial-node">Initial Node</h1>
<p>Initial Node는</p>
<blockquote>
<p><strong>Activity가 시작되는 지점</strong></p>
</blockquote>
<p>이다.</p>
<p>검은색 원으로 표현한다.</p>
<pre><code class="language-text">●
│
▼
[상품 선택]</code></pre>
<p>즉,</p>
<pre><code class="language-text">여기서부터 Activity가 시작된다.</code></pre>
<p>라는 의미다.</p>
<hr>
<h1 id="final-node">Final Node</h1>
<p>Final Node는</p>
<blockquote>
<p><strong>Activity 전체가 종료되는 지점</strong></p>
</blockquote>
<p>을 의미한다.</p>
<p>보통 다음과 같이 표현한다.</p>
<pre><code class="language-text">[주문 완료]
     │
     ▼
     ◉</code></pre>
<p>전체 흐름으로 보면</p>
<pre><code class="language-text">● 시작
↓
[상품 선택]
↓
[결제]
↓
[주문 완료]
↓
◉ 종료</code></pre>
<p>가 된다.</p>
<p>즉,</p>
<pre><code class="language-text">Initial Node
→ 시작

Activity Final Node
→ Activity 전체 종료</code></pre>
<p>라고 이해하면 된다.</p>
<h2 id="final-node에서-하나-주의할-점">Final Node에서 하나 주의할 점</h2>
<p>UML에는 실제로 종료 Node가 하나만 있는 것은 아니다.</p>
<p>대표적으로</p>
<pre><code class="language-text">Activity Final Node
Flow Final Node</code></pre>
<p>가 있다.</p>
<h3 id="activity-final-node">Activity Final Node</h3>
<p>Activity 전체를 끝낸다.</p>
<pre><code class="language-text">◉</code></pre>
<h3 id="flow-final-node">Flow Final Node</h3>
<p>해당 Flow 하나만 종료한다.</p>
<p>다른 병렬 Flow는 계속 진행될 수 있다.</p>
<p>처음 Activity Diagram을 배울 때는<br>대부분 Activity Final을 기준으로 배우지만,</p>
<p>병렬 처리까지 복잡하게 다루게 되면<br>이 둘을 구분할 필요가 있다.</p>
<hr>
<h1 id="decision-node">Decision Node</h1>
<p>Decision Node는</p>
<blockquote>
<p><strong>조건에 따라 실행 경로를 나누는 Node</strong></p>
</blockquote>
<p>다.</p>
<p>마름모로 표현한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">        ◇ 결제 성공?
       /              [성공]          [실패]
     ↓               ↓
[주문 완료]      [결제 재시도]</code></pre>
<p>와 같이 표현할 수 있다.</p>
<p>프로그래밍에서 생각하면 <code>if-else</code>와 상당히 비슷하다.</p>
<h2 id="guard-condition">Guard Condition</h2>
<p>Decision Node에서 분기되는 각 경로에는<br>보통 <strong>Guard Condition</strong>을 작성한다.</p>
<p>UML에서는 일반적으로</p>
<pre><code class="language-text">[조건]</code></pre>
<p>형태로 표시한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">[결제 성공]
[결제 실패]</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>즉 Decision Node에서 중요한 것은</p>
<blockquote>
<p><strong>각 분기 조건이 명확하게 정의되어 있어야 한다</strong></p>
</blockquote>
<p>는 것이다.</p>
<hr>
<h1 id="merge-node">Merge Node</h1>
<p>Merge Node는</p>
<blockquote>
<p><strong>여러 개의 대체 경로를 다시 하나의 흐름으로 합치는 Node</strong></p>
</blockquote>
<p>다.</p>
<p>예를 들어 결제 방법에 따라 흐름이 나뉜다고 해보자.</p>
<pre><code class="language-text">           ◇ 결제 방법?
          /               [카드 결제]     [계좌 결제]
          \           /
           \         /
               ◇
               │
               ▼
          [주문 완료]</code></pre>
<p>첫 번째 마름모는</p>
<pre><code class="language-text">Decision Node</code></pre>
<p>이고,</p>
<p>두 번째 마름모는</p>
<pre><code class="language-text">Merge Node</code></pre>
<p>다.</p>
<p>모양은 동일하지만 역할은 완전히 다르다.</p>
<pre><code class="language-text">Decision
→ 하나의 Flow를 여러 대안으로 나눈다.

Merge
→ 여러 대안 Flow를 하나로 합친다.</code></pre>
<hr>
<h1 id="fork-node">Fork Node</h1>
<p>Fork Node는</p>
<blockquote>
<p><strong>하나의 Flow를 여러 개의 병렬적인 Flow로 나누는 Node</strong></p>
</blockquote>
<p>다.</p>
<p>굵은 막대로 표현한다.</p>
<p>예를 들어 주문이 완료된 뒤</p>
<pre><code class="language-text">고객에게 알림 전송
재고 감소</code></pre>
<p>두 작업을 병렬적으로 진행한다고 해보자.</p>
<pre><code class="language-text">          [주문 완료]
               │
        ━━━━━━━━━━━━━
          │         │
          ▼         ▼
     [알림 전송]  [재고 감소]</code></pre>
<p>가운데 굵은 막대가 Fork Node다.</p>
<h2 id="decision과-fork의-차이">Decision과 Fork의 차이</h2>
<h3 id="decision">Decision</h3>
<pre><code class="language-text">A 또는 B</code></pre>
<h3 id="fork">Fork</h3>
<pre><code class="language-text">A 그리고 B</code></pre>
<p>따라서</p>
<pre><code class="language-text">Decision
→ OR

Fork
→ AND</code></pre>
<p>라고 기억하면 상당히 편하다.</p>
<p>다만 Fork는 &quot;CPU가 반드시 정확히 같은 순간에 두 작업을 실행한다&quot;는 뜻이라기보다<br>UML 의미상 <strong>서로 독립적으로 진행될 수 있는 Concurrent Flow</strong>를 표현한다고 이해하는 것이 더 정확하다.</p>
<hr>
<h1 id="join-node">Join Node</h1>
<p>Join Node는 Fork의 반대다.</p>
<blockquote>
<p><strong>Fork로 나뉜 여러 Flow가 모두 끝난 뒤 다시 하나의 Flow로 합쳐지는 Node</strong></p>
</blockquote>
<p>다.</p>
<pre><code class="language-text">          [주문 완료]
               │
        ━━━━━━━━━━━━━
          │         │
          ▼         ▼
     [알림 전송]  [재고 감소]
          │         │
          ▼         ▼
        ━━━━━━━━━━━━━
               │
               ▼
        [주문 기록 저장]</code></pre>
<p>위쪽 막대가 Fork,<br>아래쪽 막대가 Join이다.</p>
<p>정리하면</p>
<pre><code class="language-text">Fork
→ 여러 Flow를 시작한다.

Join
→ 여러 Flow가 끝날 때까지 기다린 뒤 다음으로 진행한다.</code></pre>
<hr>
<h1 id="merge와-join도-다르다">Merge와 Join도 다르다</h1>
<p>이 둘 역시</p>
<pre><code class="language-text">여러 Flow를 하나로 합친다.</code></pre>
<p>라는 점 때문에 헷갈릴 수 있다.</p>
<p>하지만 의미가 다르다.</p>
<h3 id="merge">Merge</h3>
<p>여러 경로 중 <strong>실제로 실행된 하나의 경로</strong>가 들어오면<br>다음 Flow로 진행한다.</p>
<pre><code class="language-text">A 또는 B
↓
Merge</code></pre>
<h3 id="join">Join</h3>
<p>여러 병렬 Flow가 <strong>모두 끝날 때까지 기다린다.</strong></p>
<pre><code class="language-text">A 그리고 B
↓
Join</code></pre>
<p>즉,</p>
<pre><code class="language-text">Merge
→ OR를 다시 합친다.

Join
→ AND를 다시 합친다.</code></pre>
<p>라고 생각하면 된다.</p>
<hr>
<h1 id="control-node를-한-번에-정리하면">Control Node를 한 번에 정리하면</h1>
<table>
<thead>
<tr>
<th>Node</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Initial</td>
<td>Activity 시작</td>
</tr>
<tr>
<td>Final</td>
<td>Activity 종료</td>
</tr>
<tr>
<td>Decision</td>
<td>조건에 따라 Flow 분기</td>
</tr>
<tr>
<td>Merge</td>
<td>대안 Flow를 다시 하나로 합침</td>
</tr>
<tr>
<td>Fork</td>
<td>하나의 Flow를 병렬 Flow로 분리</td>
</tr>
<tr>
<td>Join</td>
<td>병렬 Flow가 모두 끝난 뒤 하나로 합침</td>
</tr>
</tbody></table>
<p>그리고 세 쌍으로 기억하면 된다.</p>
<pre><code class="language-text">Initial ↔ Final
시작 / 종료

Decision ↔ Merge
분기 / 병합

Fork ↔ Join
병렬 시작 / 병렬 종료</code></pre>
<hr>
<h1 id="activity-diagram을-볼-때-던질-질문">Activity Diagram을 볼 때 던질 질문</h1>
<p>구성 요소를 전부 외우려고 하면<br>생각보다 헷갈린다.</p>
<p>그래서 나는 각 요소를 다음 질문과 연결해서 기억하려고 한다.</p>
<pre><code class="language-text">Action
→ &quot;무엇을 하는가?&quot;

Control Flow
→ &quot;다음에 무엇을 하는가?&quot;

Object Node
→ &quot;어떤 데이터가 존재하는가?&quot;

Object Flow
→ &quot;그 데이터가 어디로 이동하는가?&quot;

Decision / Merge
→ &quot;조건에 따라 어디로 갈 것인가?&quot;

Fork / Join
→ &quot;어떤 작업들이 병렬적으로 진행되는가?&quot;

Initial / Final
→ &quot;어디서 시작하고 어디서 끝나는가?&quot;</code></pre>
<hr>
<h1 id="activity-diagram과-flowchart는-무엇이-다를까">Activity Diagram과 Flowchart는 무엇이 다를까?</h1>
<p>처음 Activity Diagram을 보면<br>Flowchart와 상당히 비슷해 보인다.</p>
<p>실제로 둘 다</p>
<pre><code class="language-text">작업
조건
분기
흐름</code></pre>
<p>을 표현한다.</p>
<p>하지만 Activity Diagram은 UML의 일부이기 때문에<br>소프트웨어 시스템의 Behavior를 모델링하는 데 조금 더 초점이 맞춰져 있다.</p>
<p>특히 다음 요소들을 명확하게 표현할 수 있다.</p>
<pre><code class="language-text">Object Flow
Concurrent Flow
Fork / Join
Activity / Action
UML의 다른 Diagram과의 연계</code></pre>
<p>그래서 단순한 알고리즘 순서만 표현한다면<br>Flowchart로도 충분할 수 있다.</p>
<p>하지만</p>
<pre><code class="language-text">비즈니스 Workflow
Use Case 내부 동작
데이터 전달
병렬 처리
복잡한 시스템 Behavior</code></pre>
<p>를 모델링한다면 Activity Diagram이 더 적합할 수 있다.</p>
<hr>
<h1 id="언제-activity-diagram을-그리면-좋을까">언제 Activity Diagram을 그리면 좋을까?</h1>
<p>모든 기능마다 Activity Diagram을 그릴 필요는 없다고 생각한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">getUserName()</code></pre>
<p>처럼 정말 단순한 메서드 하나를 설명하기 위해<br>Activity Diagram까지 만드는 것은 오히려 과할 수 있다.</p>
<p>Activity Diagram은 다음과 같은 상황에서 특히 가치가 있다.</p>
<pre><code class="language-text">조건 분기가 많다.

여러 시스템 또는 기능이 연결된다.

작업 순서가 중요하다.

병렬적으로 실행되는 작업이 있다.

데이터 전달 흐름까지 확인해야 한다.

비개발자와 Workflow를 공유해야 한다.</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>코드 한 줄 한 줄을 설명하는 Diagram이라기보다<br>시스템의 행동 흐름 전체를 이해하기 위해 그리는 Diagram</strong></p>
</blockquote>
<p>이라고 생각하면 될 것 같다.</p>
<hr>
<h1 id="정리하면서-느낀-점">정리하면서 느낀 점</h1>
<p>처음 Activity Diagram의 구성 요소를 봤을 때는</p>
<pre><code class="language-text">Initial Node
Final Node
Decision Node
Merge Node
Fork Node
Join Node
Object Node
Object Flow
Control Flow
...</code></pre>
<p>처럼 용어가 너무 많아서<br>정의를 하나씩 외워야 하나 싶었다.</p>
<p>그런데 실제 Workflow에 적용해서 생각해보니<br>생각보다 구조는 단순했다.</p>
<pre><code class="language-text">작업한다.

순서대로 이동한다.

데이터를 전달한다.

조건에 따라 나뉜다.

다시 합친다.

병렬적으로 실행한다.

모두 끝나면 다시 합친다.

종료한다.</code></pre>
<p>결국 우리가 프로그램을 작성하면서<br>평소 코드로 표현하던 것을 그림으로 바꾼 것에 가깝다.</p>
<pre><code class="language-text">if
→ Decision

if 종료
→ Merge

병렬 처리
→ Fork

모두 완료될 때까지 대기
→ Join

메서드 실행
→ Action

객체 전달
→ Object Flow</code></pre>
<p>이런 식으로 코드와 연결해서 생각하니<br>각 Node의 의미가 훨씬 쉽게 들어왔다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>Activity Diagram은</p>
<blockquote>
<p><strong>시스템이나 비즈니스 프로세스에서 어떤 작업들이 어떤 순서와 조건으로 수행되는지를 표현하는 UML Behavior Diagram</strong></p>
</blockquote>
<p>이라고 정리할 수 있다.</p>
<p>핵심 구성 요소를 다시 압축하면 다음과 같다.</p>
<pre><code class="language-text">Action / Activity
→ 무엇을 하는가?

Control Flow
→ 다음에 무엇을 하는가?

Object Node / Object Flow
→ 어떤 데이터가 어디로 이동하는가?

Decision / Merge
→ 어떤 조건으로 흐름이 나뉘고 다시 합쳐지는가?

Fork / Join
→ 어떤 작업이 병렬적으로 진행되고 언제 다시 합쳐지는가?

Initial / Final
→ Activity가 어디서 시작하고 어디서 끝나는가?</code></pre>
<p>정의를 달달 외우기보다는<br>실제 주문 Process 같은 예시를 하나 그려보면서</p>
<pre><code class="language-text">이건 Action인가?

이 화살표는 Control Flow인가 Object Flow인가?

여기서는 Decision인가 Fork인가?

다시 합칠 때 Merge인가 Join인가?</code></pre>
<p>를 하나씩 판단해보는 것이 훨씬 이해하기 좋은 것 같다.</p>
<p>Activity Diagram을 그리는 목적도 결국 하나다.</p>
<blockquote>
<p><strong>코드로 들어가기 전에 시스템의 행동 흐름을 사람이 이해할 수 있는 형태로 만드는 것.</strong></p>
</blockquote>
<p>이 관점만 기억하면<br>각 구성 요소가 왜 필요한지도 자연스럽게 이해할 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | [USC AI/DS] Lesson 4. Informed Search와 A* 알고리즘]]></title>
            <link>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-Lesson-4.-Informed-Search%EC%99%80-A-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EA%B0%80%EA%B8%B0-Lesson-4.-Informed-Search%EC%99%80-A-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Thu, 13 Aug 2026 06:43:02 GMT</pubDate>
            <description><![CDATA[<h1 id="🇺🇸-usc-빅데이터-컨퍼런스-참가기🇺🇸--usc-aids-lesson-4-informed-search와-a-알고리즘">🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | [USC AI/DS] Lesson 4. Informed Search와 A* 알고리즘</h1>
<p>Lesson 3에서는 BFS, DFS, Uniform Cost Search와 같은<br>기본적인 Search Algorithm에 대해 정리했다.</p>
<p>이번 Lesson 4에서는 여기에서 한 단계 더 나아가<br><strong>Informed Search</strong>를 다룬다.</p>
<p>말 그대로</p>
<blockquote>
<p><strong>목표에 대한 추가적인 정보를 가지고 탐색하는 방법</strong></p>
</blockquote>
<p>이다.</p>
<p>이번 단원에서도 Greedy Search와 같은 내용은<br>코딩테스트를 준비하면서 어느 정도 접해본 내용들이라 비교적 익숙했다.</p>
<p>다만 단순히</p>
<pre><code class="language-text">Greedy = 지금 가장 좋아 보이는 것을 선택한다.</code></pre>
<p>정도로 알고 있었는데,</p>
<p>이번 강의에서는 이를 <strong>Heuristic</strong>, <strong>Best-First Search</strong>, 그리고 <strong>A* Search</strong>까지 연결해서 설명했다.</p>
<p>특히 A* Search는 예전에 알고리즘 수업에서도 이름만 몇 번 들어봤는데,<br>이번에는</p>
<pre><code class="language-text">왜 Greedy Search보다 안정적인지
왜 최적해를 찾을 수 있는지
Heuristic은 어떤 조건을 만족해야 하는지</code></pre>
<p>까지 조금 더 자세히 이해할 수 있었다.</p>
<p>그리고 후반부에는</p>
<pre><code class="language-text">Local Search
Hill Climbing
Simulated Annealing
Local Beam Search
Genetic Algorithm
Gradient Descent / Ascent</code></pre>
<p>까지 다루면서</p>
<blockquote>
<p><strong>탐색이라는 개념이 단순한 그래프 경로 탐색을 넘어 최적화 문제까지 연결될 수 있다.</strong></p>
</blockquote>
<p>는 것을 보여준다.</p>
<hr>
<h1 id="uninformed-search와-informed-search">Uninformed Search와 Informed Search</h1>
<p>먼저 이전 Lesson에서 다뤘던 알고리즘을 생각해보자.</p>
<pre><code class="language-text">BFS
DFS
Uniform Cost Search</code></pre>
<p>이 알고리즘들은 기본적으로<br>문제에서 주어진 정보만 가지고 탐색한다.</p>
<p>예를 들어 BFS라면</p>
<pre><code class="language-text">가장 얕은 Node부터 탐색</code></pre>
<p>하고,</p>
<p>Uniform Cost Search라면</p>
<pre><code class="language-text">현재까지의 Path Cost가 가장 작은 Node부터 탐색</code></pre>
<p>한다.</p>
<p>그런데 실제로 우리가 목적지에 대한 정보를 조금이라도 알고 있다면 어떨까?</p>
<p>예를 들어 서울에서 부산까지 운전한다고 해보자.</p>
<p>모든 도로를 하나하나 탐색하는 것보다</p>
<pre><code class="language-text">&quot;부산은 대략 남동쪽에 있다.&quot;</code></pre>
<p>라는 정보라도 가지고 있는 편이 훨씬 유리하다.</p>
<p>이러한 추가적인 정보를 탐색에 사용하는 것이<br><strong>Informed Search</strong>다.</p>
<hr>
<h1 id="best-first-search">Best-First Search</h1>
<p>강의에서는 먼저 <strong>Best-First Search</strong>라는 큰 개념부터 설명한다.</p>
<p>핵심 아이디어는 간단하다.</p>
<p>각 Node에 대해</p>
<pre><code class="language-text">이 Node가 얼마나 좋아 보이는가?</code></pre>
<p>를 평가하는 함수를 만든다.</p>
<p>이것을 <strong>Evaluation Function</strong>이라고 한다.</p>
<p>그리고 Frontier에 있는 Node 중</p>
<pre><code class="language-text">가장 좋아 보이는 Node</code></pre>
<p>를 먼저 탐색한다.</p>
<p>즉,</p>
<pre><code class="language-text">Frontier
↓
각 Node의 Evaluation Value 계산
↓
가장 좋은 Node 선택
↓
Expand</code></pre>
<p>하는 방식이다.</p>
<p>Best-First Search의 대표적인 두 가지 형태가 바로</p>
<pre><code class="language-text">Greedy Search
A* Search</code></pre>
<p>다.</p>
<hr>
<h1 id="greedy-search">Greedy Search</h1>
<p>먼저 Greedy Search다.</p>
<p>Greedy라는 단어 그대로</p>
<blockquote>
<p><strong>현재 순간 가장 좋아 보이는 선택을 한다.</strong></p>
</blockquote>
<p>라는 아이디어다.</p>
<p>코딩테스트에서도 상당히 자주 등장하는 방법이다.</p>
<p>예를 들어 동전을 최소 개수로 사용해서 금액을 만든다고 할 때<br>특정 조건에서는 가장 큰 동전부터 선택하는 방식이 Greedy가 될 수 있다.</p>
<pre><code class="language-text">500원
100원
50원
10원</code></pre>
<p>이 있다고 하면</p>
<pre><code class="language-text">가장 큰 동전부터 사용</code></pre>
<p>하는 방식이다.</p>
<p>Search Problem에서는 조금 다르게 표현한다.</p>
<hr>
<h1 id="heuristic이란">Heuristic이란?</h1>
<p>Greedy Search에서는 <strong>Heuristic Function</strong>이라는 것을 사용한다.</p>
<p>보통</p>
<pre><code class="language-text">h(n)</code></pre>
<p>이라고 표현한다.</p>
<p><code>h(n)</code>은</p>
<blockquote>
<p><strong>현재 Node n에서 Goal까지 얼마나 비용이 들 것 같은지 추정한 값</strong></p>
</blockquote>
<p>이다.</p>
<p>중요한 점은 <strong>실제 비용이 아니라 추정값</strong>이라는 것이다.</p>
<p>예를 들어 전주에서 서울까지 간다고 생각해보자.</p>
<p>실제 도로를 따라 이동하는 거리는</p>
<pre><code class="language-text">약 200km</code></pre>
<p>일 수 있지만,</p>
<p>지도에서 직선거리만 계산하면</p>
<pre><code class="language-text">약 170km</code></pre>
<p>가 나올 수 있다.</p>
<p>이 직선거리를</p>
<pre><code class="language-text">h(n)</code></pre>
<p>으로 사용할 수 있다.</p>
<hr>
<h1 id="romania-예제">Romania 예제</h1>
<p>강의에서는 이전 Lesson에서도 사용했던<br>루마니아 도시 문제를 다시 사용한다.</p>
<p>목표는</p>
<pre><code class="language-text">Arad
↓
Bucharest</code></pre>
<p>로 이동하는 것이다.</p>
<p>이번에는 각 도시에서 Bucharest까지의<br><strong>Straight-Line Distance</strong>, 즉 직선거리를 추가적으로 알고 있다고 가정한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Arad → Bucharest 직선거리 = 366

Sibiu → Bucharest 직선거리 = 253

Timisoara → Bucharest 직선거리 = 329

Zerind → Bucharest 직선거리 = 374</code></pre>
<p>같은 정보가 있다.</p>
<p>Arad에서 갈 수 있는 곳이</p>
<pre><code class="language-text">Sibiu
Timisoara
Zerind</code></pre>
<p>라면 Greedy Search는</p>
<pre><code class="language-text">h(Sibiu) = 253
h(Timisoara) = 329
h(Zerind) = 374</code></pre>
<p>를 비교한다.</p>
<p>그리고 가장 작은 값인</p>
<pre><code class="language-text">Sibiu</code></pre>
<p>를 선택한다.</p>
<p>왜냐하면</p>
<blockquote>
<p>&quot;현재 기준으로 Bucharest에 가장 가까워 보이는 도시&quot;</p>
</blockquote>
<p>이기 때문이다.</p>
<hr>
<h1 id="greedy-search의-핵심">Greedy Search의 핵심</h1>
<p>Greedy Search는 오직</p>
<pre><code class="language-text">h(n)</code></pre>
<p>만 본다.</p>
<p>즉,</p>
<pre><code class="language-text">지금까지 얼마나 왔는가?</code></pre>
<p>는 크게 신경 쓰지 않고</p>
<pre><code class="language-text">앞으로 Goal까지 얼마나 남았는가?</code></pre>
<p>만 본다.</p>
<p>그래서 평가 함수는 다음과 같다.</p>
<pre><code class="language-text">f(n) = h(n)</code></pre>
<p>라고 생각할 수 있다.</p>
<p>이 방식의 장점은 분명하다.</p>
<p>Heuristic이 좋다면<br>쓸데없는 Node를 탐색하지 않고 Goal 방향으로 빠르게 갈 수 있다.</p>
<p>하지만 문제가 있다.</p>
<hr>
<h1 id="greedy는-항상-최적일까">Greedy는 항상 최적일까?</h1>
<p>아니다.</p>
<p>Greedy Search는 현재 순간의 선택만 보기 때문에<br>전체 경로가 최적이라는 보장이 없다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Start
├── A → Goal
└── B → C → Goal</code></pre>
<p>이라는 경로가 있다고 해보자.</p>
<p>A가 Goal에 훨씬 가까워 보인다고 해서<br>반드시</p>
<pre><code class="language-text">Start → A → Goal</code></pre>
<p>의 실제 비용이 작은 것은 아니다.</p>
<p>A로 가는 Edge Cost가 엄청나게 클 수도 있다.</p>
<p>즉,</p>
<pre><code class="language-text">Goal과 가까워 보인다
≠
전체 비용이 작다</code></pre>
<p>이다.</p>
<p>또한 반복 State를 제대로 처리하지 않는다면<br>Loop에 빠질 수도 있다.</p>
<p>강의에서도 Greedy Search는 일반적으로</p>
<pre><code class="language-text">Complete X
Optimal X</code></pre>
<p>라고 설명한다.</p>
<p>다만 finite state space에서 repeated-state checking을 한다면<br>Completeness를 확보할 수 있다.</p>
<hr>
<h1 id="그렇다면-지금까지-온-거리도-같이-보면-되지-않을까">그렇다면 지금까지 온 거리도 같이 보면 되지 않을까?</h1>
<p>여기에서 A* Search가 등장한다.</p>
<p>Lesson 3에서 다뤘던 Uniform Cost Search는</p>
<pre><code class="language-text">g(n)</code></pre>
<p>을 사용했다.</p>
<p><code>g(n)</code>은</p>
<blockquote>
<p>Start Node에서 현재 Node까지 실제로 사용한 비용</p>
</blockquote>
<p>이다.</p>
<p>반면 Greedy Search는</p>
<pre><code class="language-text">h(n)</code></pre>
<p>을 사용했다.</p>
<pre><code class="language-text">g(n)
= 지금까지 실제로 사용한 비용

h(n)
= 앞으로 Goal까지 필요할 것으로 예상되는 비용</code></pre>
<p>그렇다면 두 개를 더하면 어떨까?</p>
<pre><code class="language-text">f(n) = g(n) + h(n)</code></pre>
<p>바로 이것이 <strong>A* Search</strong>의 핵심이다.</p>
<hr>
<h1 id="a-search">A* Search</h1>
<p>A* Search는</p>
<pre><code class="language-text">Uniform Cost Search
+
Greedy Search</code></pre>
<p>의 아이디어를 합친 알고리즘이라고 볼 수 있다.</p>
<p>강의에서도 다음과 같이 설명한다.</p>
<pre><code class="language-text">Uniform Cost Search
→ backward cost g(n)

Greedy Search
→ forward cost h(n)

A*
→ g(n) + h(n)</code></pre>
<p>즉 A*의 평가 함수는</p>
<pre><code class="language-text">f(n) = g(n) + h(n)</code></pre>
<p>이다.</p>
<p>각각의 의미는 다음과 같다.</p>
<pre><code class="language-text">g(n)
= Start에서 n까지 실제로 사용한 비용

h(n)
= n에서 Goal까지 예상되는 비용

f(n)
= n을 거쳐 Goal까지 갈 것으로 예상되는 전체 비용</code></pre>
<hr>
<h1 id="간단한-예시로-이해해보자">간단한 예시로 이해해보자</h1>
<p>자동차 내비게이션을 생각하면 꽤 이해하기 쉽다.</p>
<p>현재까지</p>
<pre><code class="language-text">전주 → 대전</code></pre>
<p>으로 100km를 이동했다고 하자.</p>
<p>그리고 대전에서 서울까지 남은 거리를<br>약 150km라고 추정한다면</p>
<pre><code class="language-text">g(n) = 100
h(n) = 150</code></pre>
<p>이므로</p>
<pre><code class="language-text">f(n) = 250</code></pre>
<p>이다.</p>
<p>반면 다른 경로에서는</p>
<pre><code class="language-text">g(n) = 150
h(n) = 80</code></pre>
<p>일 수도 있다.</p>
<p>그러면</p>
<pre><code class="language-text">f(n) = 230</code></pre>
<p>이다.</p>
<p>A*는 이 둘 중</p>
<pre><code class="language-text">230</code></pre>
<p>인 경로를 먼저 탐색한다.</p>
<p>즉,</p>
<blockquote>
<p><strong>이미 사용한 비용과 앞으로 사용할 것 같은 비용을 동시에 고려한다.</strong></p>
</blockquote>
<hr>
<h1 id="a가-greedy보다-좋은-이유">A*가 Greedy보다 좋은 이유</h1>
<p>Greedy Search는</p>
<pre><code class="language-text">h(n)</code></pre>
<p>만 본다.</p>
<p>따라서 목표에 가까워 보인다는 이유만으로<br>매우 비싼 경로를 선택할 수도 있다.</p>
<p>A*는</p>
<pre><code class="language-text">g(n) + h(n)</code></pre>
<p>을 보기 때문에</p>
<pre><code class="language-text">앞으로 얼마나 남았는가?
+
지금까지 얼마나 썼는가?</code></pre>
<p>를 모두 고려한다.</p>
<p>개인적으로 이 부분을 보고</p>
<blockquote>
<p>&quot;A*는 Greedy를 조금 더 현실적으로 만든 알고리즘이구나.&quot;</p>
</blockquote>
<p>라고 이해했다.</p>
<hr>
<h1 id="a는-goal을-발견하면-바로-끝날까">A*는 Goal을 발견하면 바로 끝날까?</h1>
<p>여기서 중요한 주의점이 하나 있다.</p>
<p>A*에서는 Goal Node를 <strong>Frontier에 넣었다고 바로 종료하면 안 된다.</strong></p>
<p>즉,</p>
<pre><code class="language-text">Goal을 Enqueue
→ 종료 X</code></pre>
<p>이다.</p>
<p>Goal이 Frontier에서 실제로</p>
<pre><code class="language-text">Dequeue</code></pre>
<p>될 때 종료해야 한다.</p>
<p>왜냐하면 Goal을 처음 발견했더라도<br>Frontier 안에 더 작은 <code>f(n)</code> 값을 가진 다른 Node가 존재할 수 있기 때문이다.</p>
<p>그 다른 경로를 통해</p>
<pre><code class="language-text">더 작은 실제 비용으로 Goal</code></pre>
<p>에 도달할 가능성이 있다.</p>
<p>강의의 Romania 예제에서도<br>Bucharest가 처음 Frontier에 등장한 시점보다</p>
<pre><code class="language-text">Pitesti</code></pre>
<p>의 <code>f(n)</code> 값이 더 작아서 Pitesti를 먼저 탐색한다.</p>
<p>결국 Pitesti를 거쳐가는 경로가<br>더 저렴한 Bucharest 경로를 만들어낸다.</p>
<hr>
<h1 id="a에서-가장-중요한-것은-heuristic이다">A*에서 가장 중요한 것은 Heuristic이다</h1>
<p>여기까지 들으면 한 가지 의문이 생긴다.</p>
<pre><code class="language-text">h(n)</code></pre>
<p>은 그냥 내가 아무렇게나 추정해도 되는가?</p>
<p>그렇지 않다.</p>
<p>A*가 최적해를 보장하기 위해서는<br>Heuristic이 특정 조건을 만족해야 한다.</p>
<p>이를 <strong>Admissible Heuristic</strong>이라고 한다.</p>
<hr>
<h1 id="admissible-heuristic">Admissible Heuristic</h1>
<p>실제 n에서 Goal까지의 최소 비용을</p>
<pre><code class="language-text">h*(n)</code></pre>
<p>이라고 하자.</p>
<p>A*에서 사용하는 Heuristic <code>h(n)</code>은</p>
<pre><code class="language-text">h(n) ≤ h*(n)</code></pre>
<p>을 만족해야 한다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>실제 비용보다 크게 추정하면 안 된다.</strong></p>
</blockquote>
<p>라는 것이다.</p>
<p>이를 강의에서는</p>
<pre><code class="language-text">optimistic</code></pre>
<p>하다고 표현한다.</p>
<p>조금 낙관적으로 보는 것이다.</p>
<hr>
<h1 id="왜-비용을-크게-잡으면-안-될까">왜 비용을 크게 잡으면 안 될까?</h1>
<p>예를 들어 실제 Goal까지의 거리가</p>
<pre><code class="language-text">10</code></pre>
<p>인데</p>
<pre><code class="language-text">h(n) = 100</code></pre>
<p>이라고 해버렸다고 하자.</p>
<p>그러면 A*는 이 경로를</p>
<pre><code class="language-text">너무 비싼 경로</code></pre>
<p>라고 판단해 탐색하지 않을 수 있다.</p>
<p>그런데 실제로는 그 경로가 최적 경로일 수도 있다.</p>
<p>즉 Heuristic이 실제 비용을 과대평가하면</p>
<pre><code class="language-text">좋은 경로를 미리 버릴 수 있다.</code></pre>
<p>그래서 A*에서 사용하는 Heuristic은</p>
<pre><code class="language-text">실제 비용 이하</code></pre>
<p>여야 한다.</p>
<hr>
<h1 id="직선거리가-좋은-heuristic인-이유">직선거리가 좋은 Heuristic인 이유</h1>
<p>Romania 예제에서 사용했던</p>
<pre><code class="language-text">Straight-Line Distance</code></pre>
<p>는 좋은 예다.</p>
<p>두 도시 사이를 실제 도로로 이동하면<br>보통 직선거리보다 짧아질 수 없다.</p>
<p>즉,</p>
<pre><code class="language-text">직선거리 ≤ 실제 도로거리</code></pre>
<p>이다.</p>
<p>따라서 Bucharest까지의 직선거리를 <code>h(n)</code>으로 사용하면<br>실제 비용을 과대평가하지 않는다.</p>
<p>그래서 Admissible Heuristic이 된다.</p>
<hr>
<h1 id="8-puzzle의-heuristic">8-Puzzle의 Heuristic</h1>
<p>강의에서는 8-Puzzle을 이용해서<br>두 가지 Heuristic을 소개한다.</p>
<p>Lesson 3에서도 나왔던 퍼즐이다.</p>
<pre><code class="language-text">현재 퍼즐
↓
타일 이동
↓
Goal State</code></pre>
<p>여기에서 대표적인 Heuristic이 두 가지 있다.</p>
<hr>
<h2 id="h1--잘못-놓인-타일의-개수">h1 : 잘못 놓인 타일의 개수</h2>
<p>첫 번째는</p>
<pre><code class="language-text">h1(n)
= 목표 위치에 있지 않은 Tile의 개수</code></pre>
<p>다.</p>
<p>예를 들어 8개의 Tile 중<br>6개가 잘못된 위치에 있다면</p>
<pre><code class="language-text">h1(n) = 6</code></pre>
<p>이다.</p>
<p>각 Tile은 최소 한 번은 움직여야 제자리로 돌아갈 수 있으므로<br>이 값은 실제 비용을 넘지 않는다.</p>
<p>따라서 Admissible하다.</p>
<hr>
<h2 id="h2--manhattan-distance">h2 : Manhattan Distance</h2>
<p>두 번째는 <strong>Manhattan Distance</strong>다.</p>
<p>각 Tile이 현재 위치에서<br>자신의 목표 위치까지 이동해야 하는</p>
<pre><code class="language-text">가로 칸 수 + 세로 칸 수</code></pre>
<p>를 계산한다.</p>
<p>예를 들어 한 Tile이</p>
<pre><code class="language-text">오른쪽 2칸
아래 3칸</code></pre>
<p>을 이동해야 한다면</p>
<pre><code class="language-text">Manhattan Distance = 5</code></pre>
<p>이다.</p>
<p>그리고 모든 Tile의 Manhattan Distance를 합한다.</p>
<p>강의 예제에서는</p>
<pre><code class="language-text">h2(S) = 14</code></pre>
<p>가 나온다.</p>
<hr>
<h1 id="어떤-heuristic이-더-좋은가">어떤 Heuristic이 더 좋은가?</h1>
<p>둘 다 Admissible하다고 해서<br>성능이 똑같은 것은 아니다.</p>
<p>강의에서는 <strong>Dominance</strong>라는 개념을 설명한다.</p>
<p>만약 모든 Node에 대해</p>
<pre><code class="language-text">h2(n) ≥ h1(n)</code></pre>
<p>이고 두 Heuristic 모두 Admissible이라면</p>
<pre><code class="language-text">h2가 h1을 dominate한다.</code></pre>
<p>고 한다.</p>
<p>왜 더 큰 값이 좋은 걸까?</p>
<p>단,</p>
<pre><code class="language-text">실제 비용을 넘지 않는 범위</code></pre>
<p>에서는 Goal까지의 실제 비용을 더 정확하게 추정하기 때문이다.</p>
<p>즉,</p>
<pre><code class="language-text">너무 작게 추정하는 Heuristic</code></pre>
<p>보다</p>
<pre><code class="language-text">실제 비용에 최대한 가깝게 추정하는 Heuristic</code></pre>
<p>이 탐색할 Node를 더 많이 줄일 수 있다.</p>
<p>강의의 8-Puzzle 예제에서도 Manhattan Distance를 사용했을 때<br>잘못 놓인 Tile 개수를 사용하는 것보다 탐색 Node 수가 크게 감소한다.</p>
<hr>
<h1 id="좋은-heuristic은-search-cost를-크게-줄인다">좋은 Heuristic은 Search Cost를 크게 줄인다</h1>
<p>이 부분이 꽤 인상적이었다.</p>
<p>Heuristic이 조금만 좋아져도<br>탐색해야 하는 Node의 개수가 엄청나게 달라질 수 있다.</p>
<p>결국 A*의 성능은</p>
<pre><code class="language-text">A* 알고리즘 자체</code></pre>
<p>뿐만 아니라</p>
<pre><code class="language-text">얼마나 좋은 h(n)을 만들 수 있는가?</code></pre>
<p>에 크게 좌우된다.</p>
<p>즉,</p>
<blockquote>
<p><strong>A*를 잘 쓰는 핵심은 결국 문제에 적합한 Heuristic을 설계하는 것이다.</strong></p>
</blockquote>
<hr>
<h1 id="여러-heuristic을-합칠-수도-있다">여러 Heuristic을 합칠 수도 있다</h1>
<p>만약 두 개의 Admissible Heuristic</p>
<pre><code class="language-text">ha(n)
hb(n)</code></pre>
<p>이 있다면</p>
<pre><code class="language-text">h(n) = max(ha(n), hb(n))</code></pre>
<p>으로 사용할 수도 있다.</p>
<p>둘 모두 실제 비용을 넘지 않는다면<br>그중 큰 값을 사용하더라도 여전히 실제 비용을 넘지 않는다.</p>
<p>따라서 새로운 <code>h(n)</code>도 Admissible하다.</p>
<p>그리고 기존 두 Heuristic보다<br>더 실제 값에 가까운 추정치를 사용할 가능성이 높다.</p>
<hr>
<h1 id="relaxed-problem">Relaxed Problem</h1>
<p>그렇다면 좋은 Heuristic은 어떻게 만들까?</p>
<p>강의에서는 한 가지 중요한 방법으로<br><strong>Relaxed Problem</strong>을 소개한다.</p>
<p>원래 문제의 조건을 조금 완화한 문제를 만드는 것이다.</p>
<p>예를 들어 8-Puzzle에서는 실제로 Tile이 움직일 수 있는 방법에 제약이 있다.</p>
<p>그런데</p>
<pre><code class="language-text">Tile이 아무 위치로 이동할 수 있다.</code></pre>
<p>고 조건을 완화하면 문제는 훨씬 쉬워진다.</p>
<p>이 Relaxed Problem에서 구한 최적 비용은<br>원래 문제의 최적 비용보다 클 수 없다.</p>
<pre><code class="language-text">Relaxed Problem Cost
≤
Original Problem Cost</code></pre>
<p>따라서 Relaxed Problem의 정확한 Solution Cost를<br>원래 문제의 Heuristic으로 사용할 수 있다.</p>
<hr>
<h1 id="tsp에서도-활용할-수-있다">TSP에서도 활용할 수 있다</h1>
<p>강의에서는 Travelling Salesperson Problem, 즉 TSP도 예로 든다.</p>
<p>TSP는</p>
<blockquote>
<p>모든 도시를 한 번씩 방문하면서 총 이동 거리를 최소화하는 문제</p>
</blockquote>
<p>다.</p>
<p>굉장히 어려운 최적화 문제다.</p>
<p>하지만 문제의 조건을 완화해서<br>Minimum Spanning Tree를 계산하면</p>
<pre><code class="language-text">실제 TSP Tour의 비용보다 작은 Lower Bound</code></pre>
<p>를 얻을 수 있다.</p>
<p>이 값을 Heuristic으로 활용할 수 있다.</p>
<hr>
<h1 id="a-search의-특징">A* Search의 특징</h1>
<p>강의 자료에서 A*의 특징을 정리하면</p>
<pre><code class="language-text">Complete: Yes

Optimal: Yes</code></pre>
<p>이다.</p>
<p>물론 앞에서 설명한 것처럼<br>적절한 Heuristic 조건이 필요하다.</p>
<p>또한 A*는 같은 Heuristic을 사용하는 Optimal Search Algorithm 중<br>필요 이상으로 많은 Node를 확장하지 않는다는 의미에서<br><strong>Optimally Efficient</strong>하다고 설명한다.</p>
<p>다만 문제가 있다.</p>
<hr>
<h1 id="a의-가장-큰-단점-memory">A*의 가장 큰 단점, Memory</h1>
<p>A*는 Frontier와 탐색한 Node들을 계속 저장한다.</p>
<p>그래서 상태 공간이 매우 커지면</p>
<pre><code class="language-text">Memory</code></pre>
<p>가 큰 문제가 된다.</p>
<p>Heuristic이 좋아지면 탐색 Node 수를 크게 줄일 수 있지만,<br>문제가 충분히 크다면 A* 역시 많은 Memory를 요구한다.</p>
<p>즉,</p>
<pre><code class="language-text">좋은 Heuristic
→ 탐색량 감소

하지만
→ 모든 문제가 갑자기 쉬워지는 것은 아니다.</code></pre>
<hr>
<h1 id="a는-어디에-사용될까">A*는 어디에 사용될까?</h1>
<p>강의에서는 A*의 활용 분야도 여러 개 소개했다.</p>
<p>대표적으로</p>
<pre><code class="language-text">Video Game
Path Finding
Routing
Resource Planning
Robot Motion Planning
Language Analysis
Machine Translation
Speech Recognition</code></pre>
<p>등이다.</p>
<p>개인적으로 가장 익숙한 것은 역시 게임의 Path Finding이다.</p>
<p>NPC가 장애물을 피해 목적지까지 이동해야 하는 상황에서</p>
<pre><code class="language-text">현재 위치
↓
A*
↓
목적지까지 효율적인 경로</code></pre>
<p>를 계산할 수 있다.</p>
<hr>
<h1 id="이제-경로-자체가-중요하지-않은-문제를-생각해보자">이제 경로 자체가 중요하지 않은 문제를 생각해보자</h1>
<p>여기부터 강의가 조금 다른 방향으로 넘어간다.</p>
<p>지금까지는</p>
<pre><code class="language-text">Start
↓
...
↓
Goal</code></pre>
<p>이라는 <strong>경로</strong>가 중요했다.</p>
<p>그런데 어떤 최적화 문제에서는<br>경로가 별로 중요하지 않다.</p>
<p>중요한 것은</p>
<pre><code class="language-text">최종 상태 자체</code></pre>
<p>다.</p>
<p>예를 들어 TSP에서는</p>
<pre><code class="language-text">어떤 과정을 거쳐 이 Tour를 만들었는가?</code></pre>
<p>보다</p>
<pre><code class="language-text">최종 Tour의 길이가 얼마인가?</code></pre>
<p>가 중요하다.</p>
<p>이러한 문제를 해결하기 위해<br><strong>Local Search</strong>를 사용할 수 있다.</p>
<hr>
<h1 id="local-search">Local Search</h1>
<p>Local Search는 현재 State에서 시작해서</p>
<pre><code class="language-text">주변의 Neighbor State</code></pre>
<p>를 탐색한다.</p>
<p>하지만 기존 Graph Search와 달리<br>지나온 경로를 모두 저장하지 않는다.</p>
<pre><code class="language-text">현재 State
↓
Neighbor 평가
↓
더 좋은 State로 이동
↓
다시 Neighbor 평가</code></pre>
<p>를 반복한다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>현재 답을 조금씩 수정하면서 더 좋은 답을 만들어가는 방식</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="tsp를-local-search로-풀어보자">TSP를 Local Search로 풀어보자</h1>
<p>예를 들어 아무 Tour 하나를 만든다.</p>
<pre><code class="language-text">A → B → C → D → E → A</code></pre>
<p>그리고 두 경로를 서로 바꿔본다.</p>
<pre><code class="language-text">현재 Tour
↓
일부 Edge 교환
↓
새로운 Tour</code></pre>
<p>새로운 Tour의 비용이 더 작다면<br>그 상태를 채택한다.</p>
<p>이러한 과정을 반복하면<br>점점 더 좋은 Solution으로 이동할 수 있다.</p>
<hr>
<h1 id="n-queens">n-Queens</h1>
<p>Local Search의 또 다른 대표 예제가<br><strong>n-Queens Problem</strong>이다.</p>
<p>체스판 위에 n개의 Queen을 놓는데</p>
<pre><code class="language-text">같은 Row X
같은 Column X
같은 Diagonal X</code></pre>
<p>가 되도록 해야 한다.</p>
<p>여기에서 Heuristic을</p>
<pre><code class="language-text">서로 공격 가능한 Queen Pair의 수</code></pre>
<p>라고 정의할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">h = 5</code></pre>
<p>라면 충돌이 5개 있다는 뜻이고,</p>
<p>Queen을 하나 이동해서</p>
<pre><code class="language-text">h = 2</code></pre>
<p>로 만든다.</p>
<p>계속 줄여서</p>
<pre><code class="language-text">h = 0</code></pre>
<p>이 되면 Solution이다.</p>
<hr>
<h1 id="hill-climbing">Hill Climbing</h1>
<p>이 아이디어를 가장 단순하게 구현한 것이<br><strong>Hill Climbing</strong>이다.</p>
<p>강의의 표현이 재미있었다.</p>
<blockquote>
<p>&quot;Like climbing Everest in thick fog with amnesia&quot;</p>
</blockquote>
<p>안개가 자욱한 에베레스트에서<br>주변만 보면서 더 높은 곳으로 계속 올라가는 것과 같다.</p>
<p>현재 위치에서 주변을 본다.</p>
<pre><code class="language-text">현재보다 좋은 Neighbor가 있는가?</code></pre>
<p>있다면 이동한다.</p>
<pre><code class="language-text">현재 State
↓
가장 좋은 Neighbor
↓
이동
↓
반복</code></pre>
<p>더 좋은 Neighbor가 없다면 종료한다.</p>
<hr>
<h1 id="hill-climbing의-문제">Hill Climbing의 문제</h1>
<p>문제는 우리가 도착한 곳이<br>진짜 최고의 지점이라는 보장이 없다는 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">        Global Maximum
           /\
          /  \
    /\   /    \
   /  \_/      \
Local Maximum</code></pre>
<p>같은 공간이 있다고 해보자.</p>
<p>Hill Climbing은 Local Maximum에 도착하면</p>
<pre><code class="language-text">주변에 더 좋은 곳이 없음</code></pre>
<p>이라고 판단하고 종료할 수 있다.</p>
<p>하지만 멀리 떨어진 곳에는<br>더 좋은 Global Maximum이 있을 수도 있다.</p>
<hr>
<h1 id="random-restart">Random Restart</h1>
<p>이 문제를 완화하기 위한 간단한 방법은</p>
<pre><code class="language-text">다른 위치에서 다시 시작</code></pre>
<p>하는 것이다.</p>
<p>이를 <strong>Random-Restart Hill Climbing</strong>이라고 한다.</p>
<pre><code class="language-text">Random Start
↓
Hill Climbing

Random Start
↓
Hill Climbing

Random Start
↓
Hill Climbing</code></pre>
<p>을 반복하면서 더 좋은 Solution을 찾는다.</p>
<hr>
<h1 id="simulated-annealing">Simulated Annealing</h1>
<p>Local Maximum 문제를 해결하기 위한<br>더 재미있는 방법도 있다.</p>
<p>바로 <strong>Simulated Annealing</strong>이다.</p>
<p>핵심 아이디어는</p>
<blockquote>
<p><strong>가끔은 일부러 더 나쁜 선택도 허용하자.</strong></p>
</blockquote>
<p>이다.</p>
<p>Hill Climbing은 항상</p>
<pre><code class="language-text">좋아지는 방향</code></pre>
<p>으로만 이동한다.</p>
<p>그런데 그러면 Local Optimum을 빠져나오기 어렵다.</p>
<p>Simulated Annealing은 경우에 따라</p>
<pre><code class="language-text">나쁜 상태</code></pre>
<p>로도 이동할 수 있게 한다.</p>
<hr>
<h1 id="왜-일부러-나쁜-선택을-할까">왜 일부러 나쁜 선택을 할까?</h1>
<p>예를 들어 현재 Local Minimum에 갇혀 있다고 해보자.</p>
<pre><code class="language-text">     Global Minimum
        \        /
         \      /
      ___ \____/
     /
Local Minimum</code></pre>
<p>Global Minimum으로 가려면<br>일시적으로 더 높은 비용의 상태를 지나야 할 수도 있다.</p>
<p>Hill Climbing / Descending은 이를 허용하지 않는다.</p>
<p>하지만 Simulated Annealing은 일정 확률로</p>
<pre><code class="language-text">일시적으로 더 나쁜 상태</code></pre>
<p>를 받아들인다.</p>
<p>그래서 Local Optimum을 빠져나갈 가능성이 생긴다.</p>
<hr>
<h1 id="temperature">Temperature</h1>
<p>Simulated Annealing에서는 <strong>Temperature T</strong>라는 개념을 사용한다.</p>
<p>초반에는 T가 크다.</p>
<pre><code class="language-text">T가 큼
→ 나쁜 선택도 비교적 자주 허용
→ Exploration</code></pre>
<p>시간이 지나면 T를 점점 줄인다.</p>
<pre><code class="language-text">T가 작음
→ 나쁜 선택을 거의 허용하지 않음
→ Exploitation</code></pre>
<p>즉,</p>
<pre><code class="language-text">초반
→ 다양하게 탐색

후반
→ 좋은 지역에 집중</code></pre>
<p>한다.</p>
<p>Machine Learning에서 자주 듣는</p>
<pre><code class="language-text">Exploration vs Exploitation</code></pre>
<p>의 관계와 비슷하다.</p>
<hr>
<h1 id="local-beam-search">Local Beam Search</h1>
<p>Local Search를 하나의 State만 가지고 하지 않고<br>여러 개의 State를 동시에 유지할 수도 있다.</p>
<p>이것이 <strong>Local Beam Search</strong>다.</p>
<pre><code class="language-text">현재 State k개
↓
각 State의 Successor 생성
↓
전체 중 좋은 k개 선택
↓
반복</code></pre>
<p>중요한 것은</p>
<pre><code class="language-text">독립적인 Search를 k개 돌린다</code></pre>
<p>는 의미가 아니라는 것이다.</p>
<p>모든 Successor를 함께 비교하고<br>그중 가장 좋은 k개를 선택한다.</p>
<hr>
<h1 id="그런데-좋은-state들만-남기면-비슷해지지-않을까">그런데 좋은 State들만 남기면 비슷해지지 않을까?</h1>
<p>맞다.</p>
<p>계속 좋은 State만 선택하면<br>결국 k개의 State가 모두 비슷한 Local Optimum에 모일 수 있다.</p>
<p>그래서 좋은 State를 선택할 확률을 높이되<br>Randomness를 섞는 방법도 사용할 수 있다.</p>
<p>여기에서 강의는 자연스럽게<br><strong>Genetic Algorithm</strong>으로 연결한다.</p>
<hr>
<h1 id="genetic-algorithm">Genetic Algorithm</h1>
<p>Genetic Algorithm은<br>생물의 자연 선택과 진화를 모방한 알고리즘이다.</p>
<p>핵심은</p>
<pre><code class="language-text">Population
Selection
Crossover
Mutation
Next Generation</code></pre>
<p>이다.</p>
<hr>
<h1 id="representation">Representation</h1>
<p>먼저 하나의 Solution을<br>어떤 형태로 표현해야 한다.</p>
<p>Genetic Algorithm에서는 보통 하나의 Solution을</p>
<pre><code class="language-text">String</code></pre>
<p>으로 표현한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">1011010011</code></pre>
<p>같은 Binary String을 사용할 수도 있다.</p>
<p>이것을 생물의</p>
<pre><code class="language-text">Chromosome</code></pre>
<p>처럼 생각하는 것이다.</p>
<hr>
<h1 id="population">Population</h1>
<p>Solution 하나만 사용하는 것이 아니라<br>여러 Candidate Solution을 만든다.</p>
<pre><code class="language-text">Solution 1
Solution 2
Solution 3
Solution 4
...</code></pre>
<p>이 집합을 <strong>Population</strong>이라고 한다.</p>
<p>초기 Population은 보통 Random하게 생성한다.</p>
<hr>
<h1 id="fitness-function">Fitness Function</h1>
<p>각 Solution이 얼마나 좋은지 평가해야 한다.</p>
<p>이를 <strong>Fitness Function</strong>이라고 한다.</p>
<pre><code class="language-text">Fitness가 높다
→ 좋은 Solution

Fitness가 낮다
→ 좋지 않은 Solution</code></pre>
<p>이라고 생각할 수 있다.</p>
<hr>
<h1 id="selection">Selection</h1>
<p>좋은 Solution일수록<br>다음 Generation에 자신의 정보를 전달할 확률을 높인다.</p>
<p>강의에서는 <strong>Roulette Wheel Selection</strong>을 예로 든다.</p>
<p>Fitness가 높은 개체일수록<br>Roulette Wheel에서 더 큰 영역을 차지한다.</p>
<p>따라서 선택될 확률이 높다.</p>
<hr>
<h1 id="crossover">Crossover</h1>
<p>선택된 두 Parent를 섞어서<br>새로운 Child를 만든다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Parent 1

11011 | 00100

Parent 2

10101 | 11100</code></pre>
<p>이라고 하면</p>
<pre><code class="language-text">Child 1

11011 | 11100

Child 2

10101 | 00100</code></pre>
<p>처럼 만들 수 있다.</p>
<p>이를 <strong>Crossover</strong>라고 한다.</p>
<p>한 지점에서 자르면</p>
<pre><code class="language-text">Single Point Crossover</code></pre>
<p>이고,</p>
<p>두 지점에서 자르면</p>
<pre><code class="language-text">Two Point Crossover</code></pre>
<p>가 될 수 있다.</p>
<hr>
<h1 id="mutation">Mutation</h1>
<p>하지만 Parent끼리 계속 섞기만 하면<br>Population이 점점 비슷해질 수 있다.</p>
<p>그러면 Local Optimum에 빠질 가능성이 높다.</p>
<p>그래서 아주 작은 확률로 일부 값을 Random하게 변경한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">101101</code></pre>
<p>에서</p>
<pre><code class="language-text">101001</code></pre>
<p>로 Bit 하나를 바꾸는 것이다.</p>
<p>이를 <strong>Mutation</strong>이라고 한다.</p>
<p>Mutation은 Population에 새로운 다양성을 제공한다.</p>
<hr>
<h1 id="next-generation">Next Generation</h1>
<p>Selection, Crossover, Mutation을 통해<br>새로운 Candidate Solution을 만든다.</p>
<p>그리고 이들을 다음 Generation으로 사용한다.</p>
<pre><code class="language-text">Generation 1
↓
Selection
↓
Crossover
↓
Mutation
↓
Generation 2
↓
...</code></pre>
<p>이 과정을 반복한다.</p>
<p>때로는 이전 Generation의 가장 좋은 Solution을<br>다음 Generation에 그대로 유지시키기도 한다.</p>
<p>이를 <strong>Elitism</strong>이라고 한다.</p>
<hr>
<h1 id="결국-genetic-algorithm도-search다">결국 Genetic Algorithm도 Search다</h1>
<p>처음에는 Genetic Algorithm이<br>Graph Search와 완전히 다른 내용처럼 보였다.</p>
<p>하지만 강의를 따라가다 보면<br>결국 이것도</p>
<pre><code class="language-text">State Space에서 더 좋은 State를 찾는 Search</code></pre>
<p>라는 큰 틀 안에 있다.</p>
<p>단지</p>
<pre><code class="language-text">BFS / DFS
→ 명확한 Frontier를 탐색

Local Search
→ Neighbor를 이용해 개선

Genetic Algorithm
→ Population을 진화시키며 탐색</code></pre>
<p>이라는 차이가 있을 뿐이다.</p>
<hr>
<h1 id="마지막은-continuous-state-space">마지막은 Continuous State Space</h1>
<p>지금까지 대부분의 Search 문제는<br>State가 명확하게 구분되는 Discrete Space였다.</p>
<p>예를 들어</p>
<pre><code class="language-text">City A
City B
City C</code></pre>
<p>또는</p>
<pre><code class="language-text">Puzzle State 1
Puzzle State 2</code></pre>
<p>처럼 구분된다.</p>
<p>그런데 현실에는 Continuous한 문제도 많다.</p>
<p>예를 들어 지도 위에 공항 3개를 어디에 배치할지 결정한다고 해보자.</p>
<p>각 공항의 위치가</p>
<pre><code class="language-text">(x1, y1)
(x2, y2)
(x3, y3)</code></pre>
<p>라고 하면</p>
<p>총 6개의 Continuous Variable이 존재한다.</p>
<hr>
<h1 id="gradient-based-algorithm">Gradient-Based Algorithm</h1>
<p>Continuous State Space에서는<br>Neighbor를 하나하나 생성하기보다 <strong>Gradient</strong>를 사용할 수 있다.</p>
<p>Gradient는 쉽게 생각하면</p>
<blockquote>
<p><strong>현재 위치에서 함수 값이 가장 빠르게 변하는 방향</strong></p>
</blockquote>
<p>이다.</p>
<p>값을 최대화하고 싶다면</p>
<pre><code class="language-text">Gradient Ascent</code></pre>
<p>를 사용하고,</p>
<p>값을 최소화하고 싶다면</p>
<pre><code class="language-text">Gradient Descent</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h1 id="gradient-descent">Gradient Descent</h1>
<p>Machine Learning을 조금 공부했다면<br>정말 자주 보는 이름이다.</p>
<p>예를 들어 Loss Function을 줄이고 싶다고 해보자.</p>
<p>현재 Parameter에서</p>
<pre><code class="language-text">Loss가 가장 빠르게 증가하는 방향</code></pre>
<p>이 Gradient라면,</p>
<p>그 반대 방향으로 이동한다.</p>
<pre><code class="language-text">Parameter
↓
Gradient 계산
↓
Gradient 반대 방향 이동
↓
Loss 감소
↓
반복</code></pre>
<p>이것이 Gradient Descent다.</p>
<p>결국 Deep Learning에서 사용하는 Optimization도<br>큰 관점에서 보면</p>
<pre><code class="language-text">더 좋은 State를 찾는 Search</code></pre>
<p>라고 볼 수 있다는 점이 인상적이었다.</p>
<hr>
<h1 id="lesson-4를-정리하면">Lesson 4를 정리하면</h1>
<p>이번 Lesson은 사실 꽤 많은 알고리즘을 다루고 있다.</p>
<p>하지만 큰 흐름으로 묶으면 생각보다 단순하다.</p>
<p>먼저</p>
<pre><code class="language-text">Uninformed Search</code></pre>
<p>에서 벗어나</p>
<pre><code class="language-text">Heuristic</code></pre>
<p>이라는 추가 정보를 사용한다.</p>
<p>그리고</p>
<pre><code class="language-text">Greedy Search
→ h(n)

Uniform Cost Search
→ g(n)

A*
→ g(n) + h(n)</code></pre>
<p>으로 연결된다.</p>
<p>그 이후에는 경로 자체보다<br>최종 상태가 중요한 Optimization Problem으로 확장하면서</p>
<pre><code class="language-text">Local Search
↓
Hill Climbing
↓
Simulated Annealing
↓
Local Beam Search
↓
Genetic Algorithm</code></pre>
<p>을 다룬다.</p>
<p>마지막으로 Continuous State Space에서는</p>
<pre><code class="language-text">Gradient Ascent
Gradient Descent</code></pre>
<p>까지 연결된다.</p>
<hr>
<h1 id="수업을-듣고-나서">수업을 듣고 나서</h1>
<p>이번 Lesson의 앞부분은 꽤 익숙했다.</p>
<p>Greedy Algorithm은 코딩테스트를 준비하면서 자주 접해왔고,<br>문제를 보고</p>
<pre><code class="language-text">현재 가장 좋은 선택을 반복해서 해도 되는가?</code></pre>
<p>를 고민하는 과정도 어느 정도 익숙했다.</p>
<p>그런데 이번 강의를 들으면서<br>Greedy를 단순한 코딩테스트 풀이 기법이 아니라</p>
<pre><code class="language-text">Heuristic을 이용하는 Search Strategy</code></pre>
<p>라는 관점에서 볼 수 있었다.</p>
<p>특히</p>
<pre><code class="language-text">Greedy
→ h(n)

Uniform Cost
→ g(n)

A*
→ g(n) + h(n)</code></pre>
<p>이라는 연결이 가장 기억에 남았다.</p>
<p>그동안 A* Search는</p>
<blockquote>
<p>&quot;길 찾기에 사용하는 유명한 알고리즘&quot;</p>
</blockquote>
<p>정도로만 알고 있었는데,</p>
<p>왜 <code>g(n)</code>과 <code>h(n)</code>을 더하는지 이해하고 나니<br>알고리즘 구조가 훨씬 자연스럽게 느껴졌다.</p>
<p>또한 Heuristic이 단순히</p>
<pre><code class="language-text">대충 Goal과 가까운 정도</code></pre>
<p>가 아니라,</p>
<p>A*의 최적성을 보장하기 위해</p>
<pre><code class="language-text">h(n) ≤ 실제 남은 비용</code></pre>
<p>이라는 조건을 만족해야 한다는 점도 새롭게 정리할 수 있었다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>Lesson 3에서는</p>
<blockquote>
<p><strong>어떤 순서로 State Space를 탐색할 것인가?</strong></p>
</blockquote>
<p>를 배웠다면,</p>
<p>Lesson 4에서는</p>
<blockquote>
<p><strong>Goal에 대한 정보를 알고 있다면 그 정보를 어떻게 탐색에 활용할 것인가?</strong></p>
</blockquote>
<p>를 배웠다고 생각한다.</p>
<p>특히 가장 핵심적인 내용을 하나만 고르라고 한다면<br>역시 A* Search다.</p>
<pre><code class="language-text">g(n)
= 지금까지 실제 비용

h(n)
= 앞으로 필요할 것으로 예상되는 비용

f(n)
= g(n) + h(n)</code></pre>
<p>그리고 좋은 Heuristic을 사용하면<br>불필요한 탐색을 크게 줄이면서도 최적해를 찾을 수 있다.</p>
<p>후반부의 Local Search와 Genetic Algorithm, Gradient Descent까지 보고 나니<br>Search라는 개념이 생각보다 훨씬 넓다는 것도 알게 되었다.</p>
<pre><code class="language-text">Graph에서 길을 찾는 것도 Search

최적의 TSP Tour를 찾는 것도 Search

Queen의 배치를 개선하는 것도 Search

Neural Network의 Loss를 줄이는 것도 Search</code></pre>
<p>결국 많은 알고리즘 문제는</p>
<blockquote>
<p><strong>수많은 가능한 상태 중에서 더 좋은 상태를 어떻게 효율적으로 찾아갈 것인가?</strong></p>
</blockquote>
<p>라는 하나의 질문으로 연결되는 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 |[USC AI/DS] Lesson 3. Problem Solving via Search]]></title>
            <link>https://velog.io/@daehyun_lee/AI-Problem-solving-via-Search</link>
            <guid>https://velog.io/@daehyun_lee/AI-Problem-solving-via-Search</guid>
            <pubDate>Thu, 13 Aug 2026 06:37:16 GMT</pubDate>
            <description><![CDATA[<h1 id="🇺🇸-usc-빅데이터-컨퍼런스-참가기🇺🇸-usc-aids-lesson-3-problem-solving-via-search">🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 |[USC AI/DS] Lesson 3. Problem Solving via Search</h1>
<p>학교에서 <strong>컴퓨팅 문제와 알고리즘</strong>이라는 과목을 들을 때 배웠던 내용들이 거의 대다수 등장한 단원이었다.</p>
<p>기초적인 그래프 탐색을 시작으로<br>그래프와 트리의 개념, 그리고 대표적인 탐색 알고리즘인 <strong>BFS와 DFS</strong>를 다룬다.</p>
<p>그런데 이번 강의에서는 단순히</p>
<pre><code class="language-text">DFS는 깊게 탐색한다.
BFS는 넓게 탐색한다.</code></pre>
<p>정도에서 끝나지 않았다.</p>
<p>조금 더 근본적인 관점에서</p>
<blockquote>
<p><strong>컴퓨터가 어떤 문제를 &#39;탐색 문제&#39;로 바꾸고, 그 안에서 해답을 어떻게 찾아가는가?</strong></p>
</blockquote>
<p>를 설명했다.</p>
<p>그리고 BFS와 DFS뿐만 아니라</p>
<ul>
<li>Uniform Cost Search</li>
<li>Depth-Limited Search</li>
<li>Iterative Deepening Search</li>
</ul>
<p>와 같은 탐색 전략도 함께 다뤘다.</p>
<p>개인적으로 알고리즘 수업에서 이미 공부했던 내용이라 익숙한 부분도 많았지만,<br>이번에는 이를 <strong>AI의 Problem-Solving Agent 관점</strong>에서 바라본다는 점이 재미있었다.</p>
<hr>
<h1 id="problem-solving이란-무엇일까">Problem Solving이란 무엇일까?</h1>
<p>강의에서는 문제 해결을 굉장히 추상적으로 정의한다.</p>
<p>어떤 문제에는 현재 상태가 있고,</p>
<pre><code class="language-text">Initial State</code></pre>
<p>우리가 도달하고 싶은 상태가 있다.</p>
<pre><code class="language-text">Goal State</code></pre>
<p>그리고 현재 상태를 다른 상태로 변화시키는 행동이 존재한다.</p>
<pre><code class="language-text">Operator / Action</code></pre>
<p>결국 문제 해결이라는 것은</p>
<pre><code class="language-text">Initial State
↓
Action
↓
State
↓
Action
↓
State
↓
...
↓
Goal State</code></pre>
<p>처럼 <strong>초기 상태에서 목표 상태까지 도달할 수 있는 행동의 순서를 찾는 것</strong>이라고 볼 수 있다.</p>
<p>예를 들어 길 찾기를 생각해보자.</p>
<pre><code class="language-text">현재 위치 : 전북대학교
목표 위치 : 전주역</code></pre>
<p>그리고 내가 할 수 있는 행동은</p>
<pre><code class="language-text">버스를 탄다.
택시를 탄다.
걷는다.</code></pre>
<p>등이 될 수 있다.</p>
<p>컴퓨터 입장에서는 결국</p>
<blockquote>
<p>&quot;어떤 행동들을 어떤 순서로 수행해야 목표 상태까지 갈 수 있을까?&quot;</p>
</blockquote>
<p>를 찾아내는 것이 문제 해결이다.</p>
<p>강의에서는 특히 <strong>가능하면 최소의 연산 또는 최소 비용으로 목표 상태에 도달하는 것</strong>을 중요하게 다룬다.</p>
<hr>
<h1 id="offline-problem-solving과-online-problem-solving">Offline Problem Solving과 Online Problem Solving</h1>
<p>여기서 문제 해결 방식도 두 가지로 나눌 수 있다.</p>
<h2 id="offline-problem-solving">Offline Problem Solving</h2>
<p>Offline Problem Solving은<br>실제로 행동하기 전에 해결 방법을 먼저 찾아놓는 방식이다.</p>
<pre><code class="language-text">문제 분석
↓
해결 경로 탐색
↓
경로 결정
↓
실제 행동</code></pre>
<p>말 그대로 계획을 모두 세운 뒤 움직인다.</p>
<p>예를 들어 내비게이션이 경로를 먼저 계산하고</p>
<pre><code class="language-text">전북대
→ 백제대로
→ 기린대로
→ 전주역</code></pre>
<p>이라는 길을 알려주는 것과 비슷하다.</p>
<p>강의에서는 이를 약간 재미있게</p>
<blockquote>
<p>solution executed &quot;eyes closed&quot;</p>
</blockquote>
<p>라고 표현했다.</p>
<p>이미 해결 방법을 알고 있기 때문에<br>그 계획을 그대로 실행하면 된다는 의미다.</p>
<hr>
<h2 id="online-problem-solving">Online Problem Solving</h2>
<p>반대로 Online Problem Solving은<br>환경에 대한 정보를 완전히 알지 못하는 상태에서 행동한다.</p>
<pre><code class="language-text">행동
↓
새로운 정보 획득
↓
다음 행동 결정
↓
새로운 정보 획득
↓
...</code></pre>
<p>예를 들어 미로 전체 지도를 모르는 상태에서<br>직접 움직이며 길을 찾는 로봇을 생각하면 된다.</p>
<p>이 경우에는</p>
<pre><code class="language-text">탐색 → 행동 → 관찰 → 다시 탐색</code></pre>
<p>이 반복된다.</p>
<hr>
<h1 id="problem의-종류">Problem의 종류</h1>
<p>강의에서는 문제를 환경에 대한 정보가 얼마나 주어지는지에 따라 여러 종류로 나누었다.</p>
<p>처음에는 용어가 조금 복잡해 보였는데<br>결국 핵심은</p>
<blockquote>
<p><strong>현재 상황을 얼마나 정확하게 알고 있는가?</strong></p>
</blockquote>
<p>라고 생각하면 이해하기 쉬웠다.</p>
<hr>
<h2 id="single-state-problem">Single-State Problem</h2>
<p>가장 단순한 경우다.</p>
<p>환경이 <strong>Deterministic</strong>하고 <strong>Fully Observable</strong>하다.</p>
<p>즉,</p>
<pre><code class="language-text">현재 내가 어디 있는지 정확히 알고 있고
+
어떤 행동을 했을 때 어떤 결과가 나오는지도 알고 있다.</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">A → B → C → Goal</code></pre>
<p>이라는 길을 정확하게 알고 있다면</p>
<pre><code class="language-text">Right
Right
Right</code></pre>
<p>와 같은 행동 Sequence를 만들면 된다.</p>
<p>우리가 코딩테스트에서 일반적으로 풀게 되는<br>그래프 탐색 문제 대부분은 이런 형태라고 볼 수 있다.</p>
<hr>
<h2 id="conformant-problem">Conformant Problem</h2>
<p>이번에는 현재 상태를 정확하게 알 수 없다.</p>
<p>즉 <strong>Non-observable</strong> 환경이다.</p>
<p>센서가 없어서 내가 정확히 어떤 상태인지 모르지만<br>그래도 모든 경우를 고려해서 목표에 도달해야 한다.</p>
<p>이때는 단순한 물리적인 State 하나가 아니라</p>
<pre><code class="language-text">현재 존재할 가능성이 있는 State들의 집합</code></pre>
<p>을 고려하게 된다.</p>
<p>강의에서는 이를 <strong>Belief State</strong>라고 표현한다.</p>
<hr>
<h2 id="contingency-problem">Contingency Problem</h2>
<p>환경이</p>
<pre><code class="language-text">Nondeterministic
또는
Partially Observable</code></pre>
<p>한 경우다.</p>
<p>행동의 결과가 항상 동일하지 않을 수도 있고<br>환경의 일부 정보만 관찰할 수도 있다.</p>
<p>따라서 해결 방법도 단순한 하나의 Action Sequence가 아니라</p>
<pre><code class="language-text">만약 A라면 → 행동 1
만약 B라면 → 행동 2</code></pre>
<p>같은 <strong>조건부 계획(Contingent Plan)</strong> 또는 <strong>Policy</strong>가 된다.</p>
<p>즉,</p>
<pre><code class="language-text">Search
↓
Execution
↓
새로운 정보
↓
Search</code></pre>
<p>가 반복될 수 있다.</p>
<hr>
<h1 id="vacuum-world">Vacuum World</h1>
<p>강의에서는 이러한 차이를 설명하기 위해<br>AI 분야에서 자주 등장하는 <strong>Vacuum World</strong> 예제를 사용했다.</p>
<p>청소기가 두 공간을 이동하면서<br>먼지를 제거하는 매우 단순한 환경이다.</p>
<p>청소기가 수행할 수 있는 행동은</p>
<pre><code class="language-text">Left
Right
Vacuum
NoOp</code></pre>
<p>정도다.</p>
<p>목표는 단순하다.</p>
<pre><code class="language-text">모든 공간에 먼지가 없는 상태</code></pre>
<p>이다.</p>
<p>Single-State Problem이라면 현재 청소기 위치와<br>먼지가 어디 있는지 모두 알고 있으므로</p>
<pre><code class="language-text">Right
Vacuum</code></pre>
<p>같은 명확한 해결 방법을 만들 수 있다.</p>
<p>하지만 현재 위치나 먼지의 위치를 알 수 없다면<br>가능한 모든 상태를 고려해야 한다.</p>
<p>그리고 환경이 불확실하다면</p>
<pre><code class="language-text">Right
if dirt then Vacuum
Left
if dirt then Vacuum
...</code></pre>
<p>처럼 조건에 따라 행동을 결정해야 한다.</p>
<p>결국 같은 청소 문제라도</p>
<blockquote>
<p><strong>환경에 대한 정보를 얼마나 알고 있느냐에 따라 문제의 형태가 완전히 달라진다.</strong></p>
</blockquote>
<hr>
<h1 id="그렇다면-문제를-어떻게-표현할까">그렇다면 문제를 어떻게 표현할까?</h1>
<p>이제부터가 그래프 탐색과 직접적으로 연결되는 부분이다.</p>
<p>강의에서는 하나의 Single-State Problem을 네 가지 요소로 정의한다.</p>
<pre><code class="language-text">1. Initial State
2. Successor Function
3. Goal Test
4. Path Cost</code></pre>
<p>하나씩 살펴보자.</p>
<hr>
<h1 id="1-initial-state">1. Initial State</h1>
<p>탐색이 시작되는 상태다.</p>
<p>예를 들어 루마니아의 도시 간 길 찾기 문제에서</p>
<pre><code class="language-text">현재 위치 = Arad</code></pre>
<p>라면</p>
<pre><code class="language-text">Initial State = Arad</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="2-successor-function">2. Successor Function</h1>
<p>현재 상태에서<br>다음으로 이동할 수 있는 상태들을 정의한다.</p>
<p>예를 들어 Arad에서 직접 갈 수 있는 도시가</p>
<pre><code class="language-text">Zerind
Sibiu
Timisoara</code></pre>
<p>라면</p>
<pre><code class="language-text">S(Arad)
=
{Zerind, Sibiu, Timisoara}</code></pre>
<p>정도로 생각할 수 있다.</p>
<p>즉,</p>
<blockquote>
<p><strong>현재 상태에서 어떤 행동을 수행할 수 있으며 그 결과 어떤 상태가 되는가?</strong></p>
</blockquote>
<p>를 정의한다.</p>
<p>코딩테스트에서 그래프의 <strong>인접 노드</strong>를 찾는 것과 거의 같다.</p>
<hr>
<h1 id="3-goal-test">3. Goal Test</h1>
<p>현재 상태가 우리가 원하는 목표인지 검사한다.</p>
<p>예를 들어 목적지가 Bucharest라면</p>
<pre><code class="language-text">현재 도시 == Bucharest ?</code></pre>
<p>를 검사하면 된다.</p>
<p>이처럼 목표가 정확한 하나의 상태로 주어질 수도 있고,</p>
<p>청소기 문제처럼</p>
<pre><code class="language-text">모든 먼지가 제거되었는가?</code></pre>
<p>처럼 조건 형태로 정의될 수도 있다.</p>
<hr>
<h1 id="4-path-cost">4. Path Cost</h1>
<p>목표까지 이동하는 데 필요한 비용이다.</p>
<p>예를 들어 도시 간 이동에서는</p>
<pre><code class="language-text">거리</code></pre>
<p>가 비용이 될 수 있다.</p>
<p>반면 퍼즐에서는</p>
<pre><code class="language-text">한 번 이동 = 비용 1</code></pre>
<p>이라고 정의할 수도 있다.</p>
<p>그러면 전체 경로 비용은</p>
<pre><code class="language-text">각 이동 비용의 합</code></pre>
<p>이 된다.</p>
<p>결국 우리가 원하는 것은 단순히 Goal에 도착하는 경로가 아니라<br>경우에 따라서는</p>
<blockquote>
<p><strong>Goal까지 가는 경로 중 가장 비용이 작은 경로</strong></p>
</blockquote>
<p>일 수 있다.</p>
<hr>
<h1 id="state-space-problem-space">State Space, Problem Space</h1>
<p>이제 문제를 그래프로 표현할 수 있다.</p>
<p>강의에서는 이를 <strong>State Space</strong>, 또는 <strong>Problem Space</strong>라고 부른다.</p>
<p>State Space는 결국 하나의 Graph다.</p>
<pre><code class="language-text">Node
→ 문제에서 가능한 State

Edge
→ 한 State에서 다른 State로 이동할 수 있는 Action</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">A → B → C
     ↓
     D → Goal</code></pre>
<p>이라는 구조가 있다면</p>
<p>A, B, C, D, Goal은 각각 State이고<br>화살표는 가능한 Action을 의미한다.</p>
<p>즉,</p>
<blockquote>
<p><strong>문제를 그래프로 바꾸고, 그래프에서 Initial State부터 Goal State까지의 경로를 찾는 것</strong></p>
</blockquote>
<p>이 Search Problem이라고 볼 수 있다.</p>
<hr>
<h1 id="romania-route-finding">Romania Route Finding</h1>
<p>강의에서는 이러한 구조를 설명하기 위해<br>루마니아의 도시 지도를 사용한다.</p>
<p>예를 들어 현재 위치가</p>
<pre><code class="language-text">Arad</code></pre>
<p>이고 목적지가</p>
<pre><code class="language-text">Bucharest</code></pre>
<p>라고 해보자.</p>
<p>그러면 문제를 다음과 같이 정의할 수 있다.</p>
<pre><code class="language-text">Initial State
= Arad

States
= 각 도시

Actions
= 연결된 도시로 이동

Goal
= Bucharest

Path Cost
= 도시 간 거리</code></pre>
<p>그러면 문제는 완전히 그래프 탐색 문제가 된다.</p>
<pre><code class="language-text">Arad
↓
Sibiu
↓
Fagaras
↓
Bucharest</code></pre>
<p>같은 경로를 찾을 수 있다.</p>
<p>물론 이 경로가 항상 가장 짧은 경로라는 의미는 아니다.</p>
<p>이후 어떤 Search Strategy를 사용하느냐에 따라<br>찾게 되는 경로가 달라질 수 있다.</p>
<hr>
<h1 id="8-puzzle도-결국-graph-문제다">8-Puzzle도 결국 Graph 문제다</h1>
<p>다음 예제는 <strong>8-Puzzle</strong>이다.</p>
<p>3×3 칸 안에서 하나의 빈칸을 이동시키면서<br>숫자를 목표 배열로 만드는 퍼즐이다.</p>
<p>처음 보면 그래프와 별로 관련이 없어 보인다.</p>
<p>하지만 이것도 State Space로 표현할 수 있다.</p>
<p>하나의 퍼즐 배치를</p>
<pre><code class="language-text">State</code></pre>
<p>라고 생각한다.</p>
<p>그리고 빈칸을</p>
<pre><code class="language-text">Left
Right
Up
Down</code></pre>
<p>으로 움직이는 것을 Action으로 생각한다.</p>
<p>그러면</p>
<pre><code class="language-text">현재 퍼즐
↓
빈칸 이동
↓
새로운 퍼즐</code></pre>
<p>이 된다.</p>
<p>즉 각각의 퍼즐 배치가 Node이고<br>한 번의 이동이 Edge인 거대한 Graph가 만들어진다.</p>
<p>Goal Test는</p>
<pre><code class="language-text">현재 퍼즐 == 목표 퍼즐</code></pre>
<p>이고,</p>
<p>한 번 움직일 때 비용을 1이라고 두면</p>
<pre><code class="language-text">Path Cost = 이동 횟수</code></pre>
<p>가 된다.</p>
<p>처음에는 완전히 다른 문제처럼 보이지만<br>추상화하고 나면 결국 같은 그래프 탐색 문제라는 점이 재미있다.</p>
<hr>
<h1 id="graph란-무엇인가">Graph란 무엇인가?</h1>
<p>여기서 그래프의 기본 개념도 다시 다뤘다.</p>
<p>그래프는 크게</p>
<pre><code class="language-text">Node(Vertex)
Edge</code></pre>
<p>로 구성된다.</p>
<p>Node는 하나의 State를 나타내고<br>Edge는 Node 사이의 연결 관계를 의미한다.</p>
<p>강의에서는 주로 <strong>Directed Graph</strong>를 기준으로 설명한다.</p>
<pre><code class="language-text">A → B</code></pre>
<p>가 있다고 해서 반드시</p>
<pre><code class="language-text">B → A</code></pre>
<p>가 존재하는 것은 아니다.</p>
<p>그리고 연결된 Node들을 따라가는 순서를 <strong>Path</strong>라고 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">A → B → C → D</code></pre>
<p>가 하나의 Path다.</p>
<p>Edge가 3개이므로 이 경로의 길이는 3이고<br>포함된 Node의 개수는 4개다.</p>
<hr>
<h1 id="search-graph와-search-tree는-다르다">Search Graph와 Search Tree는 다르다</h1>
<p>여기에서 개인적으로 다시 한번 정리할 만했던 부분이 있었다.</p>
<p>바로</p>
<pre><code class="language-text">State Space Graph</code></pre>
<p>와</p>
<pre><code class="language-text">Search Tree</code></pre>
<p>의 차이다.</p>
<p>State Space Graph에서는<br>하나의 State는 기본적으로 한 번만 존재한다.</p>
<p>하지만 Search Tree에서는 같은 State가 여러 번 등장할 수 있다.</p>
<p>왜냐하면 Search Tree의 Node는 단순히 State 하나를 표현하는 것이 아니라</p>
<blockquote>
<p><strong>그 State에 도달하기까지의 전체 경로</strong></p>
</blockquote>
<p>를 표현하기 때문이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">S → A → C</code></pre>
<p>와</p>
<pre><code class="language-text">S → B → C</code></pre>
<p>가 있다고 해보자.</p>
<p>State Space Graph에서는 C라는 Node가 하나만 존재한다.</p>
<p>하지만 Search Tree에서는</p>
<pre><code class="language-text">S-A-C

S-B-C</code></pre>
<p>라는 서로 다른 경로가 있기 때문에<br>C가 두 번 등장할 수 있다.</p>
<p>따라서 Search Tree에는 상당히 많은 중복 구조가 생길 수 있다.</p>
<hr>
<h1 id="state-node와-tree-node도-다르다">State Node와 Tree Node도 다르다</h1>
<p>State 자체와<br>Search 과정에서 사용하는 Tree Node도 구분해야 한다.</p>
<p>State는 말 그대로</p>
<pre><code class="language-text">현재 세계의 상태</code></pre>
<p>다.</p>
<p>하지만 Tree Node는 탐색 알고리즘을 수행하기 위해 사용하는 자료구조이기 때문에<br>다음과 같은 정보도 가지고 있을 수 있다.</p>
<pre><code class="language-text">State
Parent
Children
Depth
Path Cost
Action</code></pre>
<p>즉,</p>
<pre><code class="language-text">State ≠ Search Tree Node</code></pre>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="search의-핵심-frontier">Search의 핵심, Frontier</h1>
<p>Search Algorithm에서 매우 중요한 개념이 하나 등장한다.</p>
<p>바로 <strong>Frontier</strong>, 또는 <strong>Fringe</strong>다.</p>
<p>Frontier는</p>
<blockquote>
<p><strong>아직 탐색해야 할 후보 Node들의 집합</strong></p>
</blockquote>
<p>이라고 생각하면 된다.</p>
<p>탐색 과정을 굉장히 단순하게 표현하면 다음과 같다.</p>
<pre><code class="language-text">Start Node를 Frontier에 넣는다.

while Frontier가 비어있지 않다면

    Frontier에서 Node 하나를 선택한다.

    Goal인지 확인한다.

    Goal이라면 종료한다.

    아니라면 Neighbor들을 Frontier에 넣는다.</code></pre>
<p>여기서 정말 중요한 부분은</p>
<pre><code class="language-text">Frontier에서 &quot;어떤 Node를 먼저 선택할 것인가?&quot;</code></pre>
<p>이다.</p>
<p>이 선택 방식이 바로 <strong>Search Strategy</strong>가 된다.</p>
<hr>
<h1 id="search-strategy를-평가하는-기준">Search Strategy를 평가하는 기준</h1>
<p>탐색 알고리즘은 단순히</p>
<pre><code class="language-text">답을 찾았는가?</code></pre>
<p>만으로 평가하지 않는다.</p>
<p>강의에서는 네 가지 기준을 사용한다.</p>
<h3 id="completeness">Completeness</h3>
<pre><code class="language-text">해답이 존재한다면 반드시 찾을 수 있는가?</code></pre>
<h3 id="time-complexity">Time Complexity</h3>
<pre><code class="language-text">얼마나 많은 Node를 생성하고 탐색해야 하는가?</code></pre>
<h3 id="space-complexity">Space Complexity</h3>
<pre><code class="language-text">얼마나 많은 Node를 메모리에 저장해야 하는가?</code></pre>
<h3 id="optimality">Optimality</h3>
<pre><code class="language-text">찾은 답이 최소 비용의 최적해인가?</code></pre>
<p>이 네 가지 기준으로<br>각 탐색 알고리즘의 특징을 비교할 수 있다.</p>
<hr>
<h1 id="uninformed-search">Uninformed Search</h1>
<p>이번 강의에서 다루는 탐색 전략은<br>주로 <strong>Uninformed Search</strong>다.</p>
<p>말 그대로 추가적인 정보 없이<br>문제 정의에 주어진 정보만으로 탐색한다.</p>
<p>대표적인 방법은 다음과 같다.</p>
<pre><code class="language-text">Breadth-First Search
Uniform-Cost Search
Depth-First Search
Depth-Limited Search
Iterative Deepening Search</code></pre>
<hr>
<h1 id="bfs--breadth-first-search">BFS : Breadth-First Search</h1>
<p>먼저 코딩테스트에서 정말 자주 등장하는 <strong>BFS</strong>다.</p>
<p>BFS는</p>
<blockquote>
<p><strong>가장 얕은 Node부터 탐색한다.</strong></p>
</blockquote>
<p>예를 들어 다음 Tree가 있다고 해보자.</p>
<pre><code class="language-text">        A
      /   \
     B     C
    / \   / \
   D   E F   G</code></pre>
<p>BFS의 탐색 순서는</p>
<pre><code class="language-text">A
↓
B, C
↓
D, E, F, G</code></pre>
<p>가 된다.</p>
<p>즉,</p>
<pre><code class="language-text">A → B → C → D → E → F → G</code></pre>
<p>처럼 Level 단위로 탐색한다.</p>
<hr>
<h1 id="bfs는-왜-queue를-사용할까">BFS는 왜 Queue를 사용할까?</h1>
<p>BFS에서는 먼저 들어온 Node를 먼저 탐색해야 한다.</p>
<p>따라서 <strong>FIFO(First In First Out)</strong> 구조가 필요하다.</p>
<p>바로 Queue다.</p>
<pre><code class="language-text">Queue

A 삽입

A 제거
B, C 삽입

B 제거
D, E 삽입

C 제거
F, G 삽입</code></pre>
<p>이런 방식으로 탐색한다.</p>
<p>그래서 BFS 구현을 보면 거의 항상</p>
<pre><code class="language-cpp">queue&lt;Node&gt; q;</code></pre>
<p>가 등장한다.</p>
<hr>
<h1 id="bfs의-장점">BFS의 장점</h1>
<p>BFS의 가장 중요한 특징은</p>
<blockquote>
<p><strong>Edge의 비용이 모두 같다면 최단 경로를 찾을 수 있다는 것</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어 모든 이동 비용이 1이라면</p>
<pre><code class="language-text">한 번 이동
두 번 이동
세 번 이동
...</code></pre>
<p>순서로 탐색하기 때문에<br>Goal을 처음 발견했을 때의 경로가 가장 짧다.</p>
<p>그래서</p>
<pre><code class="language-text">미로 최단 거리
게임 맵 최단 거리
최소 이동 횟수</code></pre>
<p>같은 문제에서 BFS를 많이 사용한다.</p>
<p>최근 프로그래머스의 <strong>게임 맵 최단거리</strong> 문제를 풀면서<br>DFS로 접근했다가 효율성 테스트에서 시간 초과가 났던 이유도 이것과 연결된다.</p>
<p>최단 경로를 찾는 문제인데 모든 경로를 끝까지 확인하려고 했던 것이다.</p>
<hr>
<h1 id="bfs의-단점">BFS의 단점</h1>
<p>문제는 <strong>Memory</strong>다.</p>
<p>BFS는 같은 Level의 Node들을 모두 Frontier에 저장해야 한다.</p>
<p>Branching Factor를 <code>b</code>,  
가장 가까운 해답의 깊이를 <code>d</code>라고 하면<br>탐색해야 하는 Node의 수는 대략 지수적으로 증가한다.</p>
<pre><code class="language-text">1
+ b
+ b²
+ b³
+ ...
+ bᵈ</code></pre>
<p>따라서 시간뿐만 아니라<br>메모리 사용량이 매우 커질 수 있다.</p>
<p>강의에서도 BFS에서 특히 <strong>Space Complexity가 큰 문제</strong>라고 강조했다.</p>
<hr>
<h1 id="dfs--depth-first-search">DFS : Depth-First Search</h1>
<p>다음은 <strong>DFS</strong>다.</p>
<p>DFS는 BFS와 완전히 반대다.</p>
<blockquote>
<p><strong>현재 갈 수 있는 곳까지 최대한 깊게 들어간다.</strong></p>
</blockquote>
<p>예를 들어 같은 Tree가 있다고 해보자.</p>
<pre><code class="language-text">        A
      /   \
     B     C
    / \   / \
   D   E F   G</code></pre>
<p>더 아래에 Node가 존재한다고 가정하면<br>DFS는 대략</p>
<pre><code class="language-text">A
↓
B
↓
D
↓
D의 자식
↓
...</code></pre>
<p>처럼 한쪽 끝까지 내려간다.</p>
<p>더 이상 갈 곳이 없으면 다시 돌아와<br>다른 경로를 탐색한다.</p>
<hr>
<h1 id="dfs는-왜-stack을-사용할까">DFS는 왜 Stack을 사용할까?</h1>
<p>DFS에서는 가장 최근에 발견한 Node를 먼저 탐색한다.</p>
<p>따라서</p>
<pre><code class="language-text">LIFO
Last In First Out</code></pre>
<p>구조가 적합하다.</p>
<p>즉 <strong>Stack</strong>이다.</p>
<p>그리고 재귀 함수도 내부적으로 Call Stack을 사용하기 때문에<br>DFS를 재귀로 구현하는 경우가 굉장히 많다.</p>
<pre><code class="language-cpp">void dfs(int node) {

    visited[node] = true;

    for (int next : graph[node]) {

        if (!visited[next]) {
            dfs(next);
        }
    }
}</code></pre>
<p>나도 그래프 문제를 보면 가장 먼저 떠오르는 방식이<br>이 재귀 DFS인 것 같다.</p>
<hr>
<h1 id="dfs의-장점">DFS의 장점</h1>
<p>DFS의 가장 큰 장점은 메모리다.</p>
<p>BFS는 각 Level의 Node를 대부분 보관해야 하지만<br>DFS는 현재 탐색 중인 경로 중심으로 정보를 가지고 있으면 된다.</p>
<p>그래서 일반적으로 BFS보다 훨씬 적은 메모리를 사용할 수 있다.</p>
<p>또한 정답이 깊은 위치에 있고<br>운 좋게 해당 방향부터 탐색한다면 매우 빠르게 답을 찾을 수도 있다.</p>
<hr>
<h1 id="dfs의-단점">DFS의 단점</h1>
<p>하지만 DFS에는 중요한 문제가 있다.</p>
<p>먼저 <strong>최단 경로를 보장하지 않는다.</strong></p>
<p>예를 들어</p>
<pre><code class="language-text">A → B → C → D → E → Goal</code></pre>
<p>이라는 경로를 먼저 발견했더라도</p>
<p>실제로는</p>
<pre><code class="language-text">A → F → Goal</code></pre>
<p>이라는 훨씬 짧은 경로가 존재할 수 있다.</p>
<p>DFS는 첫 번째 Goal을 찾았다고 해서<br>그것이 최적이라는 보장이 없다.</p>
<p>또한 Cycle이 존재하는 Graph에서 방문 처리를 하지 않으면</p>
<pre><code class="language-text">A → B → C → A → B → C → ...</code></pre>
<p>처럼 무한히 반복될 수도 있다.</p>
<p>그래서 실제 구현에서는 보통</p>
<pre><code class="language-cpp">visited[]</code></pre>
<p>를 사용한다.</p>
<hr>
<h1 id="bfs와-dfs를-비교해보면">BFS와 DFS를 비교해보면</h1>
<p>둘의 차이를 가장 단순하게 표현하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>BFS</th>
<th>DFS</th>
</tr>
</thead>
<tbody><tr>
<td>넓게 탐색</td>
<td>깊게 탐색</td>
</tr>
<tr>
<td>Queue</td>
<td>Stack / Recursion</td>
</tr>
<tr>
<td>얕은 Solution에 유리</td>
<td>깊은 Solution에 유리할 수 있음</td>
</tr>
<tr>
<td>동일 비용에서 최단 경로 보장</td>
<td>최단 경로 보장 X</td>
</tr>
<tr>
<td>메모리 사용량이 큼</td>
<td>상대적으로 메모리 효율적</td>
</tr>
</tbody></table>
<p>결국</p>
<blockquote>
<p><strong>DFS와 BFS 중 무엇이 더 좋은 알고리즘인가?</strong></p>
</blockquote>
<p>라는 질문은 의미가 없다.</p>
<p>문제가 무엇을 요구하는지를 먼저 봐야 한다.</p>
<hr>
<h1 id="uniform-cost-search">Uniform Cost Search</h1>
<p>여기에서 BFS의 한계가 하나 등장한다.</p>
<p>BFS는 <strong>이동 횟수가 가장 적은 경로</strong>를 찾는다.</p>
<p>하지만</p>
<pre><code class="language-text">이동 횟수가 적다
=
비용이 가장 작다</code></pre>
<p>는 항상 성립하지 않는다.</p>
<p>예를 들어</p>
<pre><code class="language-text">A → B → Goal</code></pre>
<p>이라는 경로의 비용이</p>
<pre><code class="language-text">A → B = 100
B → Goal = 100

Total = 200</code></pre>
<p>이라고 해보자.</p>
<p>반면</p>
<pre><code class="language-text">A → C → D → Goal</code></pre>
<p>은 이동 횟수는 더 많지만</p>
<pre><code class="language-text">10 + 10 + 10 = 30</code></pre>
<p>일 수도 있다.</p>
<p>BFS는 이동 횟수만 보면 첫 번째 경로를 선택할 수 있다.</p>
<p>하지만 진짜 최소 비용 경로는 두 번째다.</p>
<p>이 문제를 해결하는 것이 <strong>Uniform Cost Search(UCS)</strong>다.</p>
<hr>
<h1 id="uniform-cost-search의-핵심">Uniform Cost Search의 핵심</h1>
<p>UCS는 Frontier에서</p>
<pre><code class="language-text">가장 Path Cost가 작은 Node</code></pre>
<p>부터 선택한다.</p>
<p>따라서 일반 Queue가 아니라<br><strong>Priority Queue</strong>를 사용한다.</p>
<pre><code class="language-text">Frontier

Path Cost 3
Path Cost 7
Path Cost 12</code></pre>
<p>가 있다면 항상 Cost 3부터 탐색한다.</p>
<p>즉,</p>
<blockquote>
<p><strong>지금까지 들어간 비용이 가장 작은 경로부터 확장한다.</strong></p>
</blockquote>
<p>모든 Step Cost가 동일하다면<br>UCS는 BFS와 동일하게 동작한다.</p>
<p>그리고 적절한 양의 Step Cost 조건에서는<br>최소 비용의 Optimal Solution을 찾을 수 있다.</p>
<hr>
<h1 id="depth-limited-search">Depth-Limited Search</h1>
<p>DFS의 가장 큰 문제 중 하나는</p>
<pre><code class="language-text">끝없이 깊게 들어갈 수 있다.</code></pre>
<p>는 것이다.</p>
<p>그래서 아예 깊이에 제한을 둘 수 있다.</p>
<p>이것이 <strong>Depth-Limited Search</strong>다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Depth Limit = 3</code></pre>
<p>이라고 하면</p>
<pre><code class="language-text">Depth 0
Depth 1
Depth 2
Depth 3</code></pre>
<p>까지만 탐색하고<br>그 아래는 탐색하지 않는다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>DFS에 최대 깊이 제한을 추가한 방식</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="iterative-deepening-search">Iterative Deepening Search</h1>
<p>그런데 Depth Limit을 얼마로 설정해야 할까?</p>
<pre><code class="language-text">1?
3?
10?
100?</code></pre>
<p>정답이 어디에 있는지 모르기 때문에<br>적절한 값을 미리 결정하기 어렵다.</p>
<p>그래서 나온 방법이 <strong>Iterative Deepening Search(IDS)</strong>다.</p>
<p>아이디어는 매우 단순하다.</p>
<pre><code class="language-text">Depth Limit = 0으로 DFS

없으면

Depth Limit = 1로 DFS

없으면

Depth Limit = 2로 DFS

없으면

Depth Limit = 3로 DFS

...</code></pre>
<p>정답이 나올 때까지 Depth Limit을 계속 증가시킨다.</p>
<hr>
<h1 id="그런데-같은-node를-계속-다시-탐색하는-것-아닌가">그런데 같은 Node를 계속 다시 탐색하는 것 아닌가?</h1>
<p>나도 이 부분을 보고 처음에는</p>
<blockquote>
<p>&quot;이거 너무 비효율적인 것 아닌가?&quot;</p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>예를 들어 Depth 3까지 가려면</p>
<pre><code class="language-text">Depth 1 탐색
Depth 2 탐색
Depth 3 탐색</code></pre>
<p>을 반복하므로<br>위쪽 Node를 여러 번 방문하게 된다.</p>
<p>그런데 Tree 탐색에서는 보통<br>가장 마지막 Level의 Node 수가 압도적으로 많다.</p>
<p>예를 들어 Branching Factor가 10이면</p>
<pre><code class="language-text">Depth 0 → 1개
Depth 1 → 10개
Depth 2 → 100개
Depth 3 → 1,000개
Depth 4 → 10,000개</code></pre>
<p>가 된다.</p>
<p>즉 상위 Level을 몇 번 반복하는 비용보다<br>가장 깊은 Level을 탐색하는 비용이 훨씬 크다.</p>
<p>그래서 생각보다 반복 탐색의 손해가 크지 않다.</p>
<hr>
<h1 id="ids는-bfs와-dfs의-장점을-섞는다">IDS는 BFS와 DFS의 장점을 섞는다</h1>
<p>Iterative Deepening의 목적은 꽤 명확하다.</p>
<pre><code class="language-text">DFS의 낮은 Memory 사용량

+

BFS의 얕은 Solution 탐색 능력</code></pre>
<p>을 함께 얻는 것이다.</p>
<p>강의에서도</p>
<blockquote>
<p><strong>DFS의 Space Advantage와 BFS의 Shallow-Solution Advantage를 함께 얻는 방식</strong></p>
</blockquote>
<p>으로 설명했다.</p>
<p>Step Cost가 동일한 경우<br>IDS 역시 얕은 Goal부터 발견하기 때문에 Optimal Solution을 찾을 수 있다.</p>
<hr>
<h1 id="결국-search-strategy의-차이는-무엇일까">결국 Search Strategy의 차이는 무엇일까?</h1>
<p>여기까지 보고 나니 탐색 알고리즘들이<br>사실 완전히 다른 알고리즘처럼 느껴지지 않았다.</p>
<p>기본 구조는 거의 동일하다.</p>
<pre><code class="language-text">Frontier에서 Node를 하나 꺼낸다.
↓
Goal인지 확인한다.
↓
Neighbor를 추가한다.
↓
반복한다.</code></pre>
<p>달라지는 것은 딱 하나다.</p>
<blockquote>
<p><strong>Frontier에서 어떤 Node를 먼저 꺼낼 것인가?</strong></p>
</blockquote>
<p>이다.</p>
<p>BFS는</p>
<pre><code class="language-text">가장 얕은 Node</code></pre>
<p>DFS는</p>
<pre><code class="language-text">가장 깊은 Node</code></pre>
<p>Uniform Cost Search는</p>
<pre><code class="language-text">현재까지 비용이 가장 작은 Node</code></pre>
<p>를 선택한다.</p>
<p>이 선택 기준이 달라지면서<br>알고리즘의 성질도 완전히 달라진다.</p>
<hr>
<h1 id="search-algorithm-비교">Search Algorithm 비교</h1>
<p>강의 마지막에는 지금까지 다룬 Search Algorithm을<br>Completeness, Time, Space, Optimality 관점에서 비교했다.</p>
<p>큰 특징만 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>Algorithm</th>
<th>Complete</th>
<th>Optimal</th>
<th>핵심 특징</th>
</tr>
</thead>
<tbody><tr>
<td>BFS</td>
<td>O</td>
<td>Step Cost가 동일하면 O</td>
<td>얕은 Node부터 탐색</td>
</tr>
<tr>
<td>Uniform Cost</td>
<td>O</td>
<td>O</td>
<td>Path Cost가 작은 순서로 탐색</td>
</tr>
<tr>
<td>DFS</td>
<td>일반적으로 X</td>
<td>X</td>
<td>깊게 탐색, Memory 효율적</td>
</tr>
<tr>
<td>Depth-Limited</td>
<td>Limit에 따라 결정</td>
<td>X</td>
<td>DFS에 최대 깊이 설정</td>
</tr>
<tr>
<td>Iterative Deepening</td>
<td>O</td>
<td>Step Cost가 동일하면 O</td>
<td>DFS의 Memory + BFS의 장점</td>
</tr>
</tbody></table>
<p>결국 어떤 알고리즘을 사용할지는<br>문제가 무엇을 요구하느냐에 따라 달라진다.</p>
<hr>
<h1 id="수업을-듣고-나서">수업을 듣고 나서</h1>
<p>이번 Lesson은 개인적으로 다른 단원보다 훨씬 익숙했다.</p>
<p>학교에서 <strong>컴퓨팅 문제와 알고리즘</strong> 과목을 들으며<br>Graph, Tree, BFS, DFS 같은 내용을 이미 공부했기 때문이다.</p>
<p>그때는 주로</p>
<pre><code class="language-text">DFS 구현하기
BFS 구현하기
최단 경로 찾기
그래프 탐색하기</code></pre>
<p>같은 알고리즘 자체에 집중했던 것 같다.</p>
<p>하지만 이번 강의에서는 조금 더 상위 개념에서 접근했다.</p>
<pre><code class="language-text">현실의 문제
↓
State와 Action으로 추상화
↓
State Space 구성
↓
Graph로 표현
↓
Search Strategy 선택
↓
Solution 탐색</code></pre>
<p>이라는 전체 흐름으로 바라본다.</p>
<p>그래서 예전에는</p>
<blockquote>
<p>&quot;그래프가 주어졌으니까 BFS를 사용한다.&quot;</p>
</blockquote>
<p>정도로 생각했다면,</p>
<p>이번에는</p>
<blockquote>
<p>&quot;현실의 문제 자체를 State Space로 표현하면 결국 Graph Search Problem으로 바꿀 수 있구나.&quot;</p>
</blockquote>
<p>라는 생각을 하게 되었다.</p>
<p>이 차이가 생각보다 컸다.</p>
<hr>
<h1 id="특히-bfs와-dfs를-다시-생각하게-됐다">특히 BFS와 DFS를 다시 생각하게 됐다</h1>
<p>코딩테스트를 풀다 보면<br>DFS와 BFS 문제를 정말 많이 만나게 된다.</p>
<p>그런데 막상 문제를 보면 나도 가끔</p>
<pre><code class="language-text">그래프 문제네?
→ 일단 DFS?</code></pre>
<p>처럼 습관적으로 접근할 때가 있다.</p>
<p>최근 프로그래머스의 <strong>게임 맵 최단거리</strong> 문제에서도<br>처음에는 별 생각 없이 DFS 재귀를 작성했다.</p>
<p>정답 자체는 나왔지만<br>모든 경로를 계속 탐색하다 보니 효율성 테스트에서 시간 초과가 발생했다.</p>
<p>문제에서 요구한 것은</p>
<pre><code class="language-text">도착 가능한가?</code></pre>
<p>가 아니라</p>
<pre><code class="language-text">최소 몇 칸을 지나야 하는가?</code></pre>
<p>였다.</p>
<p>즉 <strong>동일한 비용의 Graph에서 최단 경로를 찾는 문제</strong>였고<br>이 경우에는 Level 순서대로 탐색하는 BFS가 훨씬 자연스러운 선택이었다.</p>
<p>이번 강의를 들으면서 다시 느꼈다.</p>
<blockquote>
<p><strong>알고리즘을 구현할 줄 아는 것도 중요하지만,<br>문제의 특성을 보고 어떤 Search Strategy를 선택해야 하는지를 판단하는 것이 훨씬 중요하다.</strong></p>
</blockquote>
<hr>
<h1 id="마무리">마무리</h1>
<p>이번 Lesson 3의 핵심을 한 문장으로 정리하면 다음과 같다.</p>
<blockquote>
<p><strong>Problem Solving은 결국 가능한 State들을 탐색하면서 Initial State에서 Goal State까지의 경로를 찾는 과정이다.</strong></p>
</blockquote>
<p>그리고 많은 문제를</p>
<pre><code class="language-text">State
Action
Goal
Cost</code></pre>
<p>로 추상화하면<br>Graph Search Problem으로 표현할 수 있다.</p>
<p>그 Graph를 어떤 순서로 탐색하느냐에 따라</p>
<pre><code class="language-text">BFS
DFS
Uniform Cost Search
Depth-Limited Search
Iterative Deepening Search</code></pre>
<p>와 같은 서로 다른 Search Strategy가 만들어진다.</p>
<p>예전에 배웠던 DFS와 BFS를 다시 복습한 단원이기도 했지만,<br>이번에는 단순한 알고리즘 구현을 넘어</p>
<blockquote>
<p><strong>왜 Search가 문제 해결의 기본 구조가 되는가</strong></p>
</blockquote>
<p>를 이해할 수 있었던 단원이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[레벨 1] 프로그래머스 오늘의 문제 : 삼총사 - next_permutation으로 조합 구현하기]]></title>
            <link>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-1%EB%8B%A8%EA%B3%84-%EC%82%BC%EC%B4%9D%EC%82%AC</link>
            <guid>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-1%EB%8B%A8%EA%B3%84-%EC%82%BC%EC%B4%9D%EC%82%AC</guid>
            <pubDate>Thu, 13 Aug 2026 06:17:54 GMT</pubDate>
            <description><![CDATA[<h1 id="레벨-1-프로그래머스-오늘의-문제--삼총사---next_permutation으로-조합-구현하기">[레벨 1] 프로그래머스 오늘의 문제 : 삼총사 - next_permutation으로 조합 구현하기</h1>
<p>오늘의 코테 연습문제는 프로그래머스의<br><strong>삼총사</strong> 문제다.</p>
<p>문제 링크는 아래와 같다.</p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/131705?language=cpp">https://school.programmers.co.kr/learn/courses/30/lessons/131705?language=cpp</a></p>
<hr>
<h2 id="문제-설명">문제 설명</h2>
<p>한국중학교 학생들은 각자 정수 번호를 하나씩 가지고 있다.</p>
<p>이 중 <strong>3명의 정수 번호를 더했을 때 합이 0이 되면</strong><br>이 세 학생을 <code>삼총사</code>라고 한다.</p>
<p>예를 들어 다음과 같은 학생들이 있다고 해보자.</p>
<pre><code class="language-text">[-2, 3, 0, 2, -5]</code></pre>
<p>여기서</p>
<pre><code class="language-text">-2 + 0 + 2 = 0
3 + 2 + (-5) = 0</code></pre>
<p>이므로 만들 수 있는 삼총사는 총 2개다.</p>
<p>결국 문제에서 요구하는 것은 간단하다.</p>
<blockquote>
<p>학생들 중 3명을 선택했을 때<br>세 학생의 번호 합이 0이 되는 경우의 수를 구하면 된다.</p>
</blockquote>
<hr>
<h2 id="제한-사항">제한 사항</h2>
<pre><code class="language-text">3 ≤ number의 길이 ≤ 13
-1,000 ≤ number의 각 원소 ≤ 1,000
서로 다른 학생의 정수 번호가 같을 수 있다.</code></pre>
<p>여기서 마지막 조건이 꽤 중요하다.</p>
<p><strong>번호가 같다고 해서 같은 학생은 아니다.</strong></p>
<p>예를 들어</p>
<pre><code class="language-text">[-1, -1, 0, 1, 1]</code></pre>
<p>에서 첫 번째 <code>-1</code>과 두 번째 <code>-1</code>은  
같은 값을 가지고 있지만 서로 다른 학생이다.</p>
<p>즉 이 문제에서는 단순히 숫자만 보는 것이 아니라<br><strong>각 위치에 존재하는 서로 다른 학생을 선택한다</strong>고 생각해야 한다.</p>
<hr>
<h2 id="처음-문제를-봤을-때">처음 문제를 봤을 때</h2>
<p>문제를 읽자마자 든 생각은 이거였다.</p>
<blockquote>
<p>음... 완전탐색 문제인가?</p>
</blockquote>
<p>학생 수가 최대 13명밖에 되지 않고<br>그중 3명을 선택하면 된다.</p>
<p>그러면 경우의 수는 그냥</p>
<pre><code class="language-text">number.size() C 3</code></pre>
<p>이다.</p>
<p>즉,</p>
<pre><code class="language-text">모든 3명의 조합을 확인한다.
↓
세 학생의 번호를 더한다.
↓
합이 0이면 answer++</code></pre>
<p>하면 된다.</p>
<p>아이디어 자체는 상당히 간단해 보였다.</p>
<p>그래서 처음에는 별생각 없이<br><code>next_permutation()</code>을 이용하면 되겠다고 생각했다.</p>
<hr>
<h2 id="먼저-도메인-지식부터-정리해보자">먼저 도메인 지식부터 정리해보자</h2>
<p>이 문제에서 가장 중요한 것은<br><strong>완전탐색</strong>, 그리고 <strong>순열과 조합의 차이</strong>다.</p>
<h3 id="완전탐색이란">완전탐색이란?</h3>
<p>완전탐색(Brute Force)은 말 그대로<br>가능한 모든 경우를 직접 확인하는 방법이다.</p>
<p>이번 문제에서는</p>
<pre><code class="language-text">학생 3명을 선택할 수 있는 모든 경우</code></pre>
<p>를 확인하면 된다.</p>
<p>학생이 <code>N</code>명이라면<br>3명을 선택하는 경우의 수는</p>
<pre><code class="language-text">N C 3</code></pre>
<p>이다.</p>
<p>문제에서 학생 수는 최대 13명이므로</p>
<pre><code class="language-text">13C3 = 286</code></pre>
<p>밖에 되지 않는다.</p>
<p>따라서 모든 경우를 직접 확인해도 충분히 빠르다.</p>
<p>즉 이 문제는</p>
<blockquote>
<p>&quot;완전탐색을 사용해도 되는가?&quot;</p>
</blockquote>
<p>를 고민할 필요는 거의 없었다.</p>
<p>진짜 중요한 문제는</p>
<blockquote>
<p><strong>모든 경우를 어떤 방식으로 만들 것인가?</strong></p>
</blockquote>
<p>였다.</p>
<hr>
<h2 id="순열과-조합의-차이">순열과 조합의 차이</h2>
<h3 id="순열permutation">순열(Permutation)</h3>
<p>순열은 <strong>순서를 고려해서 선택하는 것</strong>이다.</p>
<p>예를 들어 A, B, C라는 세 사람이 있다면</p>
<pre><code class="language-text">A B C
A C B
B A C
B C A
C A B
C B A</code></pre>
<p>를 모두 서로 다른 경우로 본다.</p>
<p>반면 조합은 <strong>순서를 고려하지 않는다.</strong></p>
<pre><code class="language-text">A, B, C</code></pre>
<p>라는 세 사람을 선택했다면</p>
<pre><code class="language-text">A, C, B
B, A, C
C, B, A</code></pre>
<p>는 전부 같은 세 사람을 선택한 것이다.</p>
<p>이번 문제에서 필요한 것은</p>
<pre><code class="language-text">순열 X
조합 O</code></pre>
<p>이다.</p>
<hr>
<h2 id="첫-번째-문제풀이">첫 번째 문제풀이</h2>
<p>하지만 처음에는 이 부분을 깊게 생각하지 않고<br>다음과 같은 코드를 작성했다.</p>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt; 

using namespace std;

int solution(vector&lt;int&gt; number) {
    int answer = 0;

    do { 
        int sum = 0; 

        for (int i = 0; i &lt; 3; i++) { 
            sum += number[i]; 
        }

        if (sum == 0) {
            answer++;
        }

    } while (next_permutation(number.begin(), number.end())); 

    return answer;
}</code></pre>
<p>처음 봤을 때는 나름 그럴싸했다.</p>
<pre><code class="language-text">next_permutation()으로 모든 경우를 만든다.
↓
앞의 3개 숫자를 더한다.
↓
0이면 삼총사다.</code></pre>
<p>그런데 생각해보니 이건 아니었다.</p>
<hr>
<h2 id="문제는-숫자가-아니라-사람을-선택하는-것이다">문제는 숫자가 아니라 &quot;사람&quot;을 선택하는 것이다</h2>
<p>예를 들어 다음 배열이 있다고 해보자.</p>
<pre><code class="language-text">[-2, 3, 0, 2, -5]</code></pre>
<p>이를 학생의 관점에서 보면 다음과 같다.</p>
<pre><code class="language-text">0번 학생 → -2
1번 학생 →  3
2번 학생 →  0
3번 학생 →  2
4번 학생 → -5</code></pre>
<p>즉 우리가 선택해야 하는 것은<br>숫자 자체가 아니라 <strong>학생의 인덱스</strong>다.</p>
<p>예를 들어</p>
<pre><code class="language-text">0번 학생
2번 학생
3번 학생</code></pre>
<p>을 선택했다면</p>
<pre><code class="language-text">-2 + 0 + 2 = 0</code></pre>
<p>이므로 하나의 삼총사가 된다.</p>
<p>그런데 이 세 사람의 순서를 바꾼다고 해서<br>새로운 삼총사가 되는 것은 아니다.</p>
<hr>
<h2 id="next_permutationnumber이-문제가-되는-이유">next_permutation(number)이 문제가 되는 이유</h2>
<p><code>next_permutation(number)</code>으로 배열 자체의 모든 순열을 만들면<br>같은 세 사람이 앞의 세 자리에 등장하는 경우가 여러 번 생긴다.</p>
<p>예를 들어 선택된 사람이 A, B, C라면</p>
<pre><code class="language-text">A B C
A C B
B A C
B C A
C A B
C B A</code></pre>
<p>는 전부 같은 세 사람이다.</p>
<p>하지만 배열의 순열을 기준으로 보면<br>서로 다른 경우로 취급된다.</p>
<p>즉 내가 처음 작성한 코드는</p>
<pre><code class="language-text">조합</code></pre>
<p>을 세어야 하는데</p>
<pre><code class="language-text">순열</code></pre>
<p>을 만들고 있었던 것이다.</p>
<p>우리가 원하는 것은</p>
<pre><code class="language-text">nP3</code></pre>
<p>가 아니라</p>
<pre><code class="language-text">nC3</code></pre>
<p>이다.</p>
<blockquote>
<p>문제는 완전탐색이 맞았다.<br>하지만 완전탐색해야 하는 대상이 순열이 아니라 조합이었다.</p>
</blockquote>
<hr>
<h2 id="그런데-조합을-어떻게-구현하지">그런데 조합을 어떻게 구현하지?</h2>
<p>여기까지는 알았다.</p>
<pre><code class="language-text">이 문제는 순열이 아니다.
조합이다.</code></pre>
<p>그런데 막상 구현하려고 하니 고민이 생겼다.</p>
<blockquote>
<p><code>next_permutation()</code>을 이용해서 조합을 만들 수는 없을까?</p>
</blockquote>
<p>아무리 생각해도 아이디어가 바로 떠오르지 않았다.</p>
<p>이 시점에서 AI에게 문제를 풀어달라고 한 것이 아니라<br>현재 상황에서 사용할 수 있는 아이디어가 무엇인지 물어봤다.</p>
<p>그러자 생각하지 못했던 방법을 알게 됐다.</p>
<hr>
<h2 id="0과-1로-선택-여부를-표현한다">0과 1로 선택 여부를 표현한다</h2>
<p>예를 들어 학생이 5명이고<br>그중 3명을 선택해야 한다고 해보자.</p>
<p>다음과 같은 배열을 만든다.</p>
<pre><code class="language-cpp">vector&lt;int&gt; select = {0, 0, 1, 1, 1};</code></pre>
<p>여기서</p>
<pre><code class="language-text">0 = 선택하지 않음
1 = 선택함</code></pre>
<p>이라고 생각하는 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">0 0 1 1 1</code></pre>
<p>이라면 2번, 3번, 4번 학생을 선택한 하나의 조합이다.</p>
<hr>
<h2 id="여기에-next_permutation을-사용하면">여기에 next_permutation을 사용하면?</h2>
<p>이제 <code>select</code> 배열에 <code>next_permutation()</code>을 사용한다.</p>
<pre><code class="language-cpp">do {

} while (next_permutation(select.begin(), select.end()));</code></pre>
<p>그러면 다음과 같은 패턴들이 만들어진다.</p>
<pre><code class="language-text">0 0 1 1 1
0 1 0 1 1
0 1 1 0 1
0 1 1 1 0
1 0 0 1 1
...</code></pre>
<p>모든 배열에는 항상 <code>1</code>이 정확히 3개 존재한다.</p>
<p>즉 학생 N명 중 정확히 3명을 선택한 모든 경우가 만들어진다.</p>
<p>이걸 보고 조금 허무했다.</p>
<blockquote>
<p>아... <code>next_permutation()</code>으로 학생들의 번호를 섞는 것이 아니라<br><strong>선택 여부를 섞으면 되는구나.</strong></p>
</blockquote>
<hr>
<h2 id="왜-이-방법은-중복되지-않을까">왜 이 방법은 중복되지 않을까?</h2>
<p>예를 들어</p>
<pre><code class="language-text">0 1 1 0 1</code></pre>
<p>은 1번, 2번, 4번 학생을 선택했다는 뜻이다.</p>
<p>여기에서 <code>1</code> 세 개는 서로 구분되지 않는다.</p>
<p>중요한 것은 오직</p>
<pre><code class="language-text">어느 위치가 1인가?</code></pre>
<p>이다.</p>
<p>따라서 서로 다른 <code>select</code> 배열 하나가<br>서로 다른 학생 조합 하나와 정확하게 대응한다.</p>
<p>학생이 5명이라면 만들어지는 경우의 수도 정확하게</p>
<pre><code class="language-text">5C3 = 10</code></pre>
<p>개가 된다.</p>
<hr>
<h2 id="next_permutation은-정렬-후-사용해야-한다">next_permutation은 정렬 후 사용해야 한다</h2>
<p><code>next_permutation()</code>은 현재 순열보다<br>사전순으로 바로 다음에 오는 순열을 만들어준다.</p>
<p>따라서 모든 순열을 탐색하려면<br>가장 작은 순열부터 시작하는 것이 안전하다.</p>
<p>처음 <code>select</code>는</p>
<pre><code class="language-text">1 1 1 0 0</code></pre>
<p>이므로 정렬한다.</p>
<pre><code class="language-cpp">sort(select.begin(), select.end());</code></pre>
<p>그러면</p>
<pre><code class="language-text">0 0 1 1 1</code></pre>
<p>이 된다.</p>
<p>이 상태에서 <code>next_permutation()</code>을 반복하면<br>모든 선택 조합을 빠짐없이 확인할 수 있다.</p>
<hr>
<h2 id="두-번째-문제풀이">두 번째 문제풀이</h2>
<p>바로 이 아이디어를 적용했다.</p>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt; 

using namespace std;

int solution(vector&lt;int&gt; number) {
    int answer = 0;

    vector&lt;int&gt; select(number.size()); 

    for (int i = 0; i &lt; 3; i++) { 
        select[i] = 1;
    }

    sort(select.begin(), select.end()); 

    do { 
        int sum = 0; 

        for (int i = 0; i &lt; number.size(); i++) { 
            if (select[i] == 1) { 
                sum += number[i]; 
            }
        }

        if (sum == 0) {
            answer++; 
        }

    } while (next_permutation(select.begin(), select.end())); 

    return answer;
}</code></pre>
<hr>
<h2 id="결과는">결과는?</h2>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/a388451a-9699-47db-8d34-1d750d7f97a7/image.png" alt=""></p>
<p>정답이었다!</p>
<hr>
<h2 id="그런데-사실-더-간단한-방법도-있다">그런데 사실 더 간단한 방법도 있다</h2>
<p>문제를 풀고 나서 생각해보니<br>이 문제에서는 반드시 <code>next_permutation()</code>을 사용할 필요는 없다.</p>
<p>선택해야 하는 사람이 정확히 3명으로 고정되어 있기 때문에<br>3중 <code>for</code>문으로도 상당히 간단하게 풀 수 있다.</p>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt;

using namespace std;

int solution(vector&lt;int&gt; number) {
    int answer = 0;

    for (int i = 0; i &lt; number.size(); i++) {
        for (int j = i + 1; j &lt; number.size(); j++) {
            for (int k = j + 1; k &lt; number.size(); k++) {
                if (number[i] + number[j] + number[k] == 0) {
                    answer++;
                }
            }
        }
    }

    return answer;
}</code></pre>
<p>항상</p>
<pre><code class="language-text">i &lt; j &lt; k</code></pre>
<p>가 유지되기 때문에 같은 세 학생을 순서만 바꿔 중복해서 세지 않는다.</p>
<p>개인적으로 실제 코딩테스트에서 이 문제를 다시 만난다면<br>3중 <code>for</code>문을 사용할 것 같다.</p>
<p>하지만 이번 문제를 통해<br><code>next_permutation()</code>을 조합 생성에도 활용할 수 있다는 것을 배운 점은 꽤 의미 있었다.</p>
<hr>
<h2 id="시간복잡도">시간복잡도</h2>
<p>학생 수를 <code>N</code>이라고 하면 확인해야 하는 조합의 수는</p>
<pre><code class="language-text">N C 3</code></pre>
<p>이다.</p>
<p>문제에서는</p>
<pre><code class="language-text">N ≤ 13</code></pre>
<p>이므로 최대 조합 수도</p>
<pre><code class="language-text">13C3 = 286</code></pre>
<p>밖에 되지 않는다.</p>
<p>따라서 이 문제에서는 복잡한 최적화보다<br><strong>정확하게 조합을 구현할 수 있는가</strong>가 더 중요하다.</p>
<hr>
<h2 id="문제를-풀고-나서">문제를 풀고 나서</h2>
<p>개인적으로 이 문제는 아이디어 자체는 상당히 쉬운 문제라고 생각한다.</p>
<p>문제를 읽으면 거의 바로</p>
<pre><code class="language-text">3명을 뽑는다.
↓
합이 0인지 확인한다.</code></pre>
<p>가 떠오른다.</p>
<p>즉 완전탐색이라는 것까지는 어렵지 않았다.</p>
<p>하지만 막상 구현하려고 하니</p>
<blockquote>
<p><strong>&quot;모든 경우&quot;와 &quot;모든 순서&quot;는 다르다.</strong></p>
</blockquote>
<p>라는 것을 다시 생각하게 됐다.</p>
<p>처음에는 <code>next_permutation()</code>이라는 함수를 알고 있으니<br>그냥 모든 순열을 만들면 되겠다고 생각했다.</p>
<p>하지만 문제에서 요구하는 것은 순열이 아니라 조합이었다.</p>
<p>그리고 이번 문제에서 가장 기억에 남았던 것은<br><code>next_permutation()</code>으로 조합도 만들 수 있다는 점이었다.</p>
<pre><code class="language-text">0 = 선택하지 않음
1 = 선택함</code></pre>
<p>으로 표현하고</p>
<pre><code class="language-text">0 0 1 1 1</code></pre>
<p>을 <code>next_permutation()</code>으로 순회하면<br>선택 위치의 모든 조합을 만들 수 있다.</p>
<p>함수 하나를 알고 있는 것과<br>그 함수를 어떻게 활용할 수 있는지를 아는 것은<br>확실히 다른 문제인 것 같다.</p>
<hr>
<h2 id="결론">결론</h2>
<p>이번 문제에서 가져갈 것은 크게 세 가지다.</p>
<pre><code class="language-text">1. 완전탐색 문제에서도 순열과 조합을 먼저 구분해야 한다.

2. next_permutation()은 숫자 자체뿐 아니라
   0/1 선택 배열을 이용해 조합 생성에도 활용할 수 있다.

3. 뽑는 개수가 고정되어 있다면
   중첩 for문이 오히려 더 단순하고 직관적일 수 있다.</code></pre>
<p>결국 이 문제는 단순히</p>
<pre><code class="language-text">세 수의 합이 0인가?</code></pre>
<p>를 묻는 문제가 아니었다.</p>
<blockquote>
<p><strong>순열과 조합의 차이를 이해하고,<br>원하는 경우의 수를 코드로 정확하게 만들어낼 수 있는가?</strong></p>
</blockquote>
<p>를 묻는 문제에 더 가까웠다고 생각한다.</p>
<p>앞으로 완전탐색 문제가 나오면<br>무작정 <code>next_permutation()</code>부터 떠올리기보다</p>
<pre><code class="language-text">순서가 중요한가?
↓
순열

순서가 중요하지 않은가?
↓
조합</code></pre>
<p>을 먼저 판단해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Tabular ML (6)] 새로운 패러다임, TabPFN은 무엇일까?]]></title>
            <link>https://velog.io/@daehyun_lee/Tabular-ML-6</link>
            <guid>https://velog.io/@daehyun_lee/Tabular-ML-6</guid>
            <pubDate>Wed, 12 Aug 2026 08:35:10 GMT</pubDate>
            <description><![CDATA[<h1 id="tabular-ml-6-새로운-패러다임-tabpfn은-무엇일까">[Tabular ML (6)] 새로운 패러다임, TabPFN은 무엇일까?</h1>
<p>이전 글에서는 LLM을 활용해<br>Tabular Data를 다루는 방법에 대해 정리했다.</p>
<p>LLM이 가지고 있는 Semantic Knowledge를 활용하거나,<br>테이블을 Text 형태로 Serialization하여 예측하거나,<br>Feature Engineering이나 Anomaly Detection에 LLM을 활용하는 방식이었다.</p>
<p>그런데 Tabular Foundation Model을 만드는 방법이<br>LLM만 있는 것은 아니다.</p>
<p>이번 강의에서는 최근 Tabular ML에서 상당히 주목받고 있는<br><strong>TabPFN</strong>이라는 완전히 다른 방향의 Foundation Model을 다뤘다.</p>
<p>처음 이름을 봤을 때는 그냥</p>
<blockquote>
<p>&quot;Transformer를 Tabular Data에 적용한 또 다른 모델인가?&quot;</p>
</blockquote>
<p>정도로 생각했다.</p>
<p>그런데 공부해보니 핵심 아이디어가 꽤 신기했다.</p>
<p>일반적인 머신러닝은 새로운 데이터셋이 들어올 때마다<br>모델을 다시 학습한다.</p>
<pre><code class="language-text">새로운 Dataset
↓
Train / Validation 분리
↓
모델 학습
↓
Hyperparameter Tuning
↓
Test Prediction</code></pre>
<p>그런데 TabPFN은 조금 다르다.</p>
<pre><code class="language-text">수많은 가상의 머신러닝 문제를 미리 풀어본다.
↓
새로운 Task를 푸는 방법 자체를 학습한다.
↓
실제 새로운 Dataset이 들어온다.
↓
Training Data를 Context로 입력한다.
↓
별도의 Parameter Update 없이 Prediction</code></pre>
<p>처음 들었을 때는</p>
<blockquote>
<p>이게 어떻게 가능하지?</p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>이번 글에서는 이 아이디어가 어디에서 출발했고,<br>TabPFN v1 → v2 → v3로 어떻게 발전했는지,<br>그리고 최근에는 어떤 방향으로 확장되고 있는지 정리해보고자 한다.</p>
<hr>
<h1 id="먼저-tabular-foundation-model이란">먼저 Tabular Foundation Model이란?</h1>
<p>Foundation Model이라고 하면 보통 GPT 같은 LLM을 먼저 떠올린다.</p>
<p>GPT는 엄청나게 많은 Text를 미리 학습해놓고<br>새로운 문제가 주어졌을 때 이미 학습한 지식을 활용한다.</p>
<p>Tabular Data에서도 비슷한 질문을 할 수 있다.</p>
<blockquote>
<p>여러 Table과 Task에서 사용할 수 있는<br>범용적인 Tabular Foundation Model을 만들 수 없을까?</p>
</blockquote>
<p>문제는 Table마다 생김새가 너무 다르다는 것이다.</p>
<pre><code class="language-text">금융 데이터
→ 소득, 신용점수, 부채, 연체 여부

의료 데이터
→ 나이, 혈압, BMI, 질병 여부

마케팅 데이터
→ 방문 횟수, 클릭 수, 구매 금액, 전환 여부</code></pre>
<p>Column의 개수도 다르고,<br>Feature의 의미도 다르고,<br>Classification일 수도 Regression일 수도 있다.</p>
<p>즉, 자연어처럼 모든 데이터를 동일한 Token Sequence로 표현하기가 쉽지 않다.</p>
<p>그래서 Tabular Foundation Model 연구에서는<br>크게 두 가지 방향이 등장했다.</p>
<pre><code class="language-text">1. LLM-based Tabular Foundation Models
2. TabPFN-style Foundation Models</code></pre>
<p>지난 글에서 첫 번째를 다뤘다면<br>이번 글에서는 두 번째를 살펴본다.</p>
<hr>
<h1 id="tabpfn에서-pfn이란">TabPFN에서 PFN이란?</h1>
<p>TabPFN을 이해하려면 먼저<br><strong>PFN(Prior-Fitted Network)</strong>을 이해해야 한다.</p>
<p>PFN의 핵심 아이디어는 다음과 같다.</p>
<blockquote>
<p>실제 문제를 만나기 전에<br>다양한 가상의 머신러닝 문제를 엄청나게 만들어서 미리 풀어보자.</p>
</blockquote>
<p>조금 더 정확하게 말하면<br>먼저 데이터가 만들어지는 방식에 대한 <strong>Prior</strong>를 정의한다.</p>
<hr>
<h2 id="prior란">Prior란?</h2>
<p>Prior는 우리말로 하면 사전분포라고 번역한다.</p>
<p>처음 보면 상당히 어렵게 느껴지는데<br>여기서는 그냥 이렇게 생각해도 된다.</p>
<blockquote>
<p>&quot;세상에 존재할 법한 데이터셋은 대충 이런 방식으로 만들어질 것이다.&quot;</p>
</blockquote>
<p>라는 가정이다.</p>
<p>강의에서는 이를 다음과 같이 표현했다.</p>
<pre><code class="language-text">p(D)</code></pre>
<p><code>p(D)</code>는 여러 종류의 데이터 생성 분포에 대한 Prior다.</p>
<p>그리고 여기에서 하나를 Sampling한다.</p>
<pre><code class="language-text">D ~ p(D)</code></pre>
<p>이렇게 뽑힌 <code>D</code>는  
하나의 데이터 생성 방식, 즉 하나의 <strong>Task</strong>라고 생각할 수 있다.</p>
<p>예를 들어 어떤 <code>D</code>는 다음과 같은 문제를 만들 수 있다.</p>
<pre><code class="language-text">공부 시간
출석률
과제 점수
↓
합격 여부</code></pre>
<p>또 다른 <code>D</code>는</p>
<pre><code class="language-text">소득
부채
신용점수
↓
대출 승인</code></pre>
<p>과 같은 관계를 만들 수도 있다.</p>
<p>핵심은 특정 Dataset 하나를 미리 외우는 것이 아니라<br><strong>다양한 데이터 생성 규칙을 Sampling하면서 수많은 Task를 만든다는 것</strong>이다.</p>
<hr>
<h1 id="task를-엄청나게-만들어보자">Task를 엄청나게 만들어보자</h1>
<p>일반적인 머신러닝은 다음과 같다.</p>
<pre><code class="language-text">Dataset A
↓
Model A 학습</code></pre>
<p>새로운 Dataset이 생기면 다시 학습한다.</p>
<pre><code class="language-text">Dataset B
↓
Model B 학습</code></pre>
<p>PFN은 조금 다르다.</p>
<pre><code class="language-text">Prior p(D)
↓
D1 Sampling → Task 1
D2 Sampling → Task 2
D3 Sampling → Task 3
...
↓
수많은 Synthetic Task</code></pre>
<p>그리고 각 Task에서는 다시</p>
<pre><code class="language-text">Training Dataset

Test Sample</code></pre>
<p>을 만든다.</p>
<p>예를 들어 하나의 Task에서는</p>
<pre><code class="language-text">(x1, y1)
(x2, y2)
(x3, y3)

x_test → ?</code></pre>
<p>라는 문제가 만들어질 수 있다.</p>
<p>PFN은 이런 문제를 정말 많이 풀어본다.</p>
<p>쉽게 비유하면 코딩테스트와 비슷하다.</p>
<p>한 문제의 정답을 외우는 것이 아니라</p>
<pre><code class="language-text">그래프 문제
DP 문제
그리디 문제
정렬 문제</code></pre>
<p>를 수없이 풀면서</p>
<blockquote>
<p>&quot;처음 보는 문제를 어떻게 풀어야 하는가?&quot;</p>
</blockquote>
<p>를 학습하는 것이다.</p>
<hr>
<h1 id="pfn은-무엇을-학습하는가">PFN은 무엇을 학습하는가?</h1>
<p>일반적인 머신러닝 모델은<br>하나의 Dataset에서 Feature와 Label의 관계를 학습한다.</p>
<p>예를 들어 XGBoost라면</p>
<pre><code class="language-text">D_train
↓
XGBoost 학습
↓
학습된 Model
↓
x_test
↓
y_test</code></pre>
<p>이다.</p>
<p>PFN은 조금 다르다.</p>
<p>PFN이 받는 입력은 다음과 같다.</p>
<pre><code class="language-text">(x1, y1)
(x2, y2)
(x3, y3)
...
(xn, yn)

그리고

x_test</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>Training Dataset 자체가 모델의 입력으로 들어간다.</strong></p>
</blockquote>
<p>그리고 모델은 최종적으로</p>
<pre><code class="language-text">y_test</code></pre>
<p>를 예측한다.</p>
<p>강의에서는 이를 다음처럼 표현한다.</p>
<pre><code class="language-text">qθ( · | x_test, D_train )</code></pre>
<p>쉽게 풀어쓰면</p>
<blockquote>
<p>&quot;여기 Training Example들이 있고 새로운 Test Sample 하나가 있는데,<br>앞의 Example들을 참고해서 Test Label을 맞혀봐.&quot;</p>
</blockquote>
<p>라는 것이다.</p>
<p>이 구조는 어디서 많이 본 것 같다.</p>
<hr>
<h1 id="이거-in-context-learning-아닌가">이거 In-Context Learning 아닌가?</h1>
<p>맞다.</p>
<p>나도 이 부분을 들으면서<br>LLM의 Few-shot Prompting이 바로 떠올랐다.</p>
<p>예를 들어 LLM에게 다음 예시를 준다고 해보자.</p>
<pre><code class="language-text">2 + 3 = 5
4 + 7 = 11
10 + 2 = 12

20 + 3 = ?</code></pre>
<p>LLM은 앞의 예시를 Context로 사용해<br>마지막 문제의 답을 추론한다.</p>
<p>PFN도 상당히 비슷하다.</p>
<pre><code class="language-text">Training Sample 1
Training Sample 2
Training Sample 3
...
↓
Context

새로운 Test Sample
↓
Prediction</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>PFN에서는 Training Dataset 자체가 Few-shot Example의 역할을 한다.</strong></p>
</blockquote>
<p>그래서 PFN의 구조는<br><strong>In-Context Learning(ICL)</strong>과 상당히 닮아 있다.</p>
<hr>
<h1 id="그래서-transformer를-사용한다">그래서 Transformer를 사용한다</h1>
<p>그렇다면 Training Sample들 사이의 관계와<br>Test Sample과 Training Sample 사이의 관계를<br>어떻게 계산할까?</p>
<p>여기에서 Transformer가 등장한다.</p>
<p>Transformer의 Attention은<br>여러 입력 사이의 관계를 계산하는 데 강하다.</p>
<p>예를 들어 다음 Training Data가 있다고 해보자.</p>
<pre><code class="language-text">Sample 1 → Class A
Sample 2 → Class A
Sample 3 → Class B

Test Sample → ?</code></pre>
<p>Test Sample은 Attention을 통해<br>Training Sample들의 정보를 참고할 수 있다.</p>
<p>물론 이것을 단순히</p>
<pre><code class="language-text">&quot;가장 비슷한 Sample을 찾는다&quot;</code></pre>
<p>정도로만 이해하면 조금 부족하다.</p>
<p>핵심은 Transformer가 Training Dataset 전체의 패턴을 보면서</p>
<pre><code class="language-text">이 Task에서 Feature와 Label이 어떤 방식으로 연결되는지</code></pre>
<p>를 Context 안에서 추론한다는 것이다.</p>
<hr>
<h1 id="prior-fitting과-inference">Prior Fitting과 Inference</h1>
<p>강의 자료에서 이 부분이 PFN을 이해하는 핵심이라고 느꼈다.</p>
<p>PFN은 크게 두 단계로 나뉜다.</p>
<pre><code class="language-text">1. Prior Fitting
2. Inference</code></pre>
<hr>
<h2 id="1-prior-fitting">1. Prior Fitting</h2>
<p>Prior Fitting은 <strong>한 번 Offline에서 수행하는 학습 과정</strong>이다.</p>
<pre><code class="language-text">Prior에서 Synthetic Dataset 생성
↓
D1, D2, D3, ... Dn
↓
수많은 Task 학습
↓
PFN Parameter θ 학습</code></pre>
<p>즉, PFN은 새로운 Dataset마다 처음부터 다시 학습하는 것이 아니라<br>먼저 수많은 Synthetic Task를 이용해</p>
<blockquote>
<p><strong>&quot;Training Data를 보고 새로운 Test Sample을 예측하는 방법&quot;</strong></p>
</blockquote>
<p>자체를 미리 학습한다.</p>
<hr>
<h2 id="2-inference">2. Inference</h2>
<p>이제 실제 Dataset이 들어온다.</p>
<p>예를 들어 처음 보는 고객 이탈 데이터라고 해보자.</p>
<pre><code class="language-text">Training Dataset

고객 A → 이탈
고객 B → 유지
고객 C → 유지

Test Sample

고객 D → ?</code></pre>
<p>PFN은 다음 정보 전체를 입력으로 받는다.</p>
<pre><code class="language-text">고객 A + Label
고객 B + Label
고객 C + Label
고객 D</code></pre>
<p>그리고 한 번의 Forward Pass를 통해</p>
<pre><code class="language-text">고객 D → 이탈 확률</code></pre>
<p>을 계산한다.</p>
<p>여기서 정말 중요한 점이 있다.</p>
<pre><code class="language-text">Gradient Descent X
Fine-tuning X
Parameter Update X</code></pre>
<p>즉, 새로운 Dataset이 들어왔다고 해서<br>PFN의 Parameter를 다시 학습하지 않는다.</p>
<p>강의 자료의 그림을 보면<br>왼쪽은 Synthetic Dataset들을 생성해서 한 번 학습하는 <strong>Prior Fitting</strong>,  
오른쪽은 실제 Dataset과 Test Sample을 넣어 한 번의 Forward Pass로 예측하는 <strong>Inference</strong>로 나뉘어 있다.</p>
<blockquote>
<p>여기에 강의자료 4페이지의 Prior Fitting / Inference 그림을 넣으면 이해하기 좋을 것 같다.</p>
</blockquote>
<hr>
<h1 id="그런데-왜-모델을-다시-학습하지-않아도-될까">그런데 왜 모델을 다시 학습하지 않아도 될까?</h1>
<p>처음에는 이 부분이 가장 이해가 안 됐다.</p>
<p>일반적인 머신러닝에서는 새로운 Dataset이 들어오면</p>
<pre><code class="language-text">model.fit(X_train, y_train)</code></pre>
<p>을 해야 한다.</p>
<p>그런데 PFN은 왜 이 과정이 필요하지 않을까?</p>
<p>핵심은 다음과 같다.</p>
<blockquote>
<p><strong>PFN에게 새로운 Dataset의 Training Data는<br>Parameter를 업데이트하기 위한 데이터가 아니라<br>Prediction을 위한 Context다.</strong></p>
</blockquote>
<p>일반적인 모델은</p>
<pre><code class="language-text">Training Data
↓
Parameter에 Dataset의 규칙을 저장
↓
학습이 끝나면 Training Data 없이 Prediction</code></pre>
<p>한다.</p>
<p>반면 PFN은</p>
<pre><code class="language-text">미리 학습된 Parameter
+
현재 Dataset의 Training Data
+
Test Sample
↓
Prediction</code></pre>
<p>을 수행한다.</p>
<p>즉 Dataset의 정보를 Parameter 안에 새로 저장하는 것이 아니라<br>Inference 시점에 Training Data를 직접 입력으로 넣어 참고한다.</p>
<p>PFN은 Prior Fitting 과정에서 이미</p>
<pre><code class="language-text">Training Data가 이렇게 주어졌을 때
↓
어떤 규칙을 읽어야 하는지
↓
Test Label을 어떻게 예측해야 하는지</code></pre>
<p>를 수많은 Task를 통해 반복적으로 학습해두었다.</p>
<p>그래서 실제 Dataset에서는<br>Parameter Update 없이도 새로운 Task에 적응할 수 있다.</p>
<p>이 부분이 PFN을 이해하는 가장 중요한 포인트라고 생각한다.</p>
<hr>
<h1 id="tabpfn-v1">TabPFN v1</h1>
<p>이 PFN 아이디어를<br>Tabular Data에 적용한 것이 <strong>TabPFN</strong>이다.</p>
<p>TabPFN v1은 ICLR 2023에 발표되었다.</p>
<p>초기 논문의 제목부터 상당히 직관적이다.</p>
<pre><code class="language-text">TabPFN:
A Transformer That Solves Small Tabular
Classification Problems in a Second</code></pre>
<p>즉 초기 TabPFN은<br><strong>작은 Tabular Classification 문제를 빠르게 해결하는 것</strong>에 초점을 맞췄다.</p>
<hr>
<h1 id="하나의-row를-하나의-token으로">하나의 Row를 하나의 Token으로</h1>
<p>TabPFN v1의 핵심 아이디어는 단순하다.</p>
<blockquote>
<p><strong>하나의 Row를 하나의 Token으로 만들자.</strong></p>
</blockquote>
<p>예를 들어 다음 Table이 있다고 해보자.</p>
<table>
<thead>
<tr>
<th>Age</th>
<th>Income</th>
<th>Debt</th>
<th>Target</th>
</tr>
</thead>
<tbody><tr>
<td>25</td>
<td>3000</td>
<td>500</td>
<td>0</td>
</tr>
<tr>
<td>42</td>
<td>7000</td>
<td>2000</td>
<td>1</td>
</tr>
<tr>
<td>31</td>
<td>5000</td>
<td>1000</td>
<td>0</td>
</tr>
</tbody></table>
<p>각 Row 전체를 하나의 Token처럼 취급한다.</p>
<pre><code class="language-text">Row 1 → Token 1
Row 2 → Token 2
Row 3 → Token 3</code></pre>
<p>Training Sample은 Feature <code>x</code>와 Label <code>y</code>를 모두 알고 있다.</p>
<p>그래서 다음처럼 표현한다.</p>
<pre><code class="language-text">MLP_x(x) + MLP_y(y)</code></pre>
<p>반면 Test Sample은 Label을 모른다.</p>
<pre><code class="language-text">MLP_x(x_test) + MLP_y([MASK])</code></pre>
<p>처럼 Label 위치를 Mask 처리한다.</p>
<p>또한 Dataset마다 Column 수가 다른 문제는<br>Zero Padding으로 맞춘다.</p>
<hr>
<h1 id="tabpfn-v1의-attention은-어떻게-동작할까">TabPFN v1의 Attention은 어떻게 동작할까?</h1>
<p>강의 자료의 그림에서 Training Row와 Test Row가 함께 Transformer에 들어간다.</p>
<p>Training Row들은 Training Row끼리 정보를 주고받을 수 있고,<br>Test Row는 Training Row를 참고해 Prediction을 만든다.</p>
<p>개념적으로는 다음과 같이 이해하면 된다.</p>
<pre><code class="language-text">Training ↔ Training

Test → Training

Training → Test X

Test → Test X</code></pre>
<p>왜 Test Token끼리는 서로 참고하지 않도록 할까?</p>
<p>각 Test Sample은 기본적으로</p>
<pre><code class="language-text">D_train + x_test</code></pre>
<p>만 주어진 하나의 독립적인 Query라고 생각해야 하기 때문이다.</p>
<p>Test Sample끼리 정보를 공유하게 만들면<br>다른 Test Sample의 존재 여부에 따라 Prediction이 달라질 수 있다.</p>
<p>그래서 각 Test Sample은<br>현재 Training Dataset을 Context로 사용해 독립적으로 예측한다.</p>
<hr>
<h1 id="최종-예측은-어떻게-할까">최종 예측은 어떻게 할까?</h1>
<p>Transformer를 통과하고 나면<br>각 Test Token에는 마지막 Hidden State가 만들어진다.</p>
<pre><code class="language-text">Test Token
↓
Transformer
↓
Final Hidden State</code></pre>
<p>그리고 이 값을 MLP Classifier에 넣는다.</p>
<pre><code class="language-text">Final Hidden State
↓
MLP Classifier
↓
Class Prediction</code></pre>
<p>즉 v1의 전체 흐름은 다음과 같다.</p>
<pre><code class="language-text">Training Rows + Labels
+
Test Row
↓
Row Token 생성
↓
Transformer
↓
Training Dataset을 Context로 활용
↓
Test Token Hidden State
↓
MLP Classifier
↓
Prediction</code></pre>
<hr>
<h1 id="그런데-synthetic-dataset은-어떻게-만들까">그런데 Synthetic Dataset은 어떻게 만들까?</h1>
<p>여기까지 이해하고 나니<br>가장 중요한 의문이 생겼다.</p>
<blockquote>
<p>수많은 Synthetic Task를 만든다고 했는데<br>그 데이터는 도대체 어떻게 만드는 걸까?</p>
</blockquote>
<p>TabPFN v1에서는 대표적으로 두 가지 Prior를 사용한다.</p>
<pre><code class="language-text">1. SCM
2. BNN</code></pre>
<hr>
<h1 id="scm">SCM</h1>
<p>SCM은 <strong>Structural Causal Model</strong>이다.</p>
<p>쉽게 생각하면</p>
<blockquote>
<p>변수들이 서로 어떤 구조적인 관계를 가지도록<br>가상의 Graph를 만든 뒤 데이터를 생성하는 방식</p>
</blockquote>
<p>이라고 보면 된다.</p>
<p>예를 들어 다음과 같은 관계를 생각해보자.</p>
<pre><code class="language-text">나이
 ↓
소득
 ↓
대출 승인

부채 ─────→ 대출 승인</code></pre>
<p>TabPFN에서는 Random Graph를 생성하고<br>그 Graph에서 일부 Node를 관측 Feature로,<br>특정 Node를 Target으로 선택한다.</p>
<pre><code class="language-text">Random Graph 생성
↓
Feature Node 선택
↓
Target Node 선택
↓
Graph를 따라 값 생성
↓
Synthetic Dataset 완성</code></pre>
<p>네 필기에서 적었던</p>
<blockquote>
<p>&quot;사이클이 없는 그래프를 만든다&quot;</p>
</blockquote>
<p>라는 이해는 방향은 맞다.</p>
<p>SCM은 보통 인과관계를 표현하기 위해<br>DAG(Directed Acyclic Graph) 형태를 사용할 수 있다.</p>
<p>중요한 것은 단순히 랜덤 숫자를 만드는 것이 아니라<br>Feature 사이에 <strong>구조적인 Dependency</strong>를 넣어준다는 것이다.</p>
<hr>
<h1 id="bnn">BNN</h1>
<p>두 번째는 <strong>BNN(Bayesian Neural Network)</strong> Prior다.</p>
<p>처음 들었을 때는</p>
<blockquote>
<p>&quot;랜덤하게 Input을 넣는다는 말인가?&quot;</p>
</blockquote>
<p>싶었는데 핵심은 조금 다르다.</p>
<p>먼저 Neural Network 자체를 하나 Sampling한다.</p>
<pre><code class="language-text">Random Neural Network</code></pre>
<p>그리고 Input <code>x</code>를 넣는다.</p>
<pre><code class="language-text">x
↓
Sampling된 Neural Network
↓
y</code></pre>
<p>즉, 이 Neural Network 하나가<br>하나의 데이터 생성 함수가 된다.</p>
<p>다른 Network를 Sampling하면<br>완전히 다른 관계를 가진 Task가 만들어진다.</p>
<pre><code class="language-text">BNN 1 → Task 1
BNN 2 → Task 2
BNN 3 → Task 3</code></pre>
<p>초기 TabPFN v1은 Classification을 목표로 했기 때문에<br>Continuous Target은 Class Label로 변환해 사용한다.</p>
<p>SCM과 BNN의 목적은 결국 동일하다.</p>
<blockquote>
<p><strong>현실에 존재할 법한 다양한 Feature-Target 관계를 가진<br>Synthetic Task를 최대한 많이 만들어내는 것</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="그런데-가상의-문제만-학습해서-실제-문제도-풀-수-있을까">그런데 가상의 문제만 학습해서 실제 문제도 풀 수 있을까?</h1>
<p>여기까지 이해하고 나니 또 의문이 생겼다.</p>
<p>TabPFN은 실제 Dataset 하나하나를 학습한 것이 아니라<br>SCM과 BNN으로 만든 Synthetic Task를 엄청나게 학습했다.</p>
<p>그렇다면</p>
<blockquote>
<p>&quot;가짜로 만든 문제만 풀어봤는데<br>실제 금융, 의료, 마케팅 데이터도 잘 풀 수 있을까?&quot;</p>
</blockquote>
<p>라는 질문이 자연스럽게 생긴다.</p>
<p>강의에서는 TabPFN이 실제 Dataset에서도 동작할 수 있는 이유를<br>크게 세 가지로 설명했다.</p>
<hr>
<h2 id="1-synthetic-prior가-충분히-다양하다">1. Synthetic Prior가 충분히 다양하다</h2>
<p>첫 번째는 Prior의 다양성이다.</p>
<p>SCM과 BNN을 이용하면</p>
<pre><code class="language-text">Feature 관계가 다른 Dataset
선형적인 관계
비선형적인 관계
Feature 간 Dependency가 존재하는 Dataset
다양한 Target 결정 규칙</code></pre>
<p>등 상당히 다양한 Task를 만들 수 있다.</p>
<p>실제 세상의 모든 Dataset을<br>완벽하게 복제할 필요는 없다.</p>
<p>대신 충분히 다양한 Task를 만들어두면</p>
<pre><code class="language-text">실제 Dataset
≈
Pretraining에서 경험했던 Task 중 어떤 형태와 비슷함</code></pre>
<p>이 될 가능성이 높아진다.</p>
<p>코딩테스트로 비유하면<br>실제 시험 문제를 미리 정확하게 알고 있을 필요는 없다.</p>
<p>DFS, BFS, DP, Greedy 같은 다양한 유형을 충분히 풀어봤다면<br>처음 보는 문제도 기존 문제와의 구조적 유사성을 바탕으로 접근할 수 있다.</p>
<hr>
<h2 id="2-transformer가-그-규칙을-학습한다">2. Transformer가 그 규칙을 학습한다</h2>
<p>Synthetic Task를 많이 만들어도<br>모델이 그 관계를 제대로 학습하지 못하면 의미가 없다.</p>
<p>강의에서는 Transformer가<br>주어진 Prior에 대한 <strong>Bayesian Posterior</strong>를 학습할 수 있을 만큼<br>충분히 유연한 Function Approximator라고 설명한다.</p>
<p>처음 보면 상당히 어려운 말이다.</p>
<p>지금 단계에서는 조금 단순하게</p>
<pre><code class="language-text">수많은 Synthetic Task를 경험
↓
Training Data가 이런 모습일 때
어떤 Label이 나올 가능성이 높은지 학습
↓
새로운 Dataset에서도
Training Data를 보고 적절한 규칙을 추론</code></pre>
<p>정도로 이해했다.</p>
<p>즉,</p>
<pre><code class="language-text">Prior
→ 어떤 문제들을 경험할지 결정

Transformer
→ 그 문제들에서 어떻게 답을 추론할지 학습</code></pre>
<p>한다고 볼 수 있다.</p>
<hr>
<h2 id="3-실제-새로운-dataset의-training-data도-직접-본다">3. 실제 새로운 Dataset의 Training Data도 직접 본다</h2>
<p>그리고 가장 직관적인 이유가 이것이다.</p>
<p>TabPFN은 Pretraining에서 배운 지식만으로<br>아무 정보 없이 답을 찍는 모델이 아니다.</p>
<p>실제 새로운 Dataset이 들어오면</p>
<pre><code class="language-text">현재 Dataset의 Training Data
+
Test Sample</code></pre>
<p>을 함께 본다.</p>
<p>즉</p>
<pre><code class="language-text">Pretraining
→ 다양한 Tabular Task를 푸는 방법 학습

+

Inference
→ 지금 풀고 있는 Dataset의 Training Data 확인</code></pre>
<p>이 합쳐지는 것이다.</p>
<p>이렇게 생각하면</p>
<blockquote>
<p>왜 별도의 Parameter Update 없이도<br>새로운 Dataset에 적응할 수 있는지</p>
</blockquote>
<p>조금 더 이해가 된다.</p>
<hr>
<h1 id="그래서-tabpfn-v1의-성능은-어땠을까">그래서 TabPFN v1의 성능은 어땠을까?</h1>
<p>강의 자료에서는 TabPFN v1이<br><strong>작은 Dataset에서 MLP와 GBDT 계열 Baseline보다 좋은 성능을 보였고, Runtime도 매우 빨랐다</strong>고 설명한다.</p>
<p>여기에서 특히 중요한 조건은</p>
<blockquote>
<p><strong>Small Dataset</strong></p>
</blockquote>
<p>이다.</p>
<p>TabPFN은 이미 Prior Fitting을 끝낸 모델이기 때문에<br>새 Dataset이 들어와도</p>
<pre><code class="language-text">Model Training
Hyperparameter Search
Cross Validation</code></pre>
<p>을 매번 처음부터 다시 할 필요가 없다.</p>
<p>Inference에서는</p>
<pre><code class="language-text">Training Data + Test Sample
↓
Forward Pass
↓
Prediction</code></pre>
<p>으로 끝난다.</p>
<p>그래서 작은 Dataset에서는<br>성능뿐 아니라 속도 측면에서도 강점이 있었다.</p>
<hr>
<h1 id="tabpfn-v1의-한계">TabPFN v1의 한계</h1>
<p>당연히 좋은 점만 있는 것은 아니었다.</p>
<p>강의 자료에서 정리한 v1의 제약은 다음과 같다.</p>
<pre><code class="language-text">Training Sample
→ 1,000개 미만

Column
→ 100개 미만

Multiclass Classification
→ 10개 Class 미만

Missing Value
→ 별도 Preprocessing 필요

Categorical Value
→ 별도 Preprocessing 필요</code></pre>
<p>그리고 Architecture 측면에서도<br>더 근본적인 문제가 하나 있었다.</p>
<p>바로</p>
<blockquote>
<p><strong>Tabular Data의 2차원 구조를 제대로 활용하지 못한다는 것</strong></p>
</blockquote>
<p>이다.</p>
<p>v1에서는 Row 전체를 하나의 Token으로 만든다.</p>
<pre><code class="language-text">Age | Income | Debt | Job
          ↓
      Row Token 하나</code></pre>
<p>하지만 Table은 원래</p>
<pre><code class="language-text">Row × Column</code></pre>
<p>이라는 2차원 구조다.</p>
<p>예를 들어 하나의 Row 안에서는</p>
<pre><code class="language-text">Income ↔ Debt
Age ↔ Income</code></pre>
<p>같은 Feature 간 관계가 존재할 수 있다.</p>
<p>그리고 하나의 Column을 기준으로 보면</p>
<pre><code class="language-text">고객 A의 Income
고객 B의 Income
고객 C의 Income</code></pre>
<p>처럼 Sample 간 관계도 생각할 수 있다.</p>
<p>v1은 Row 전체를 하나의 Token으로 압축했기 때문에<br>이런 2차원 구조를 직접 모델링하기 어렵다.</p>
<p>그래서 다음 버전에서는 아주 자연스러운 질문이 생긴다.</p>
<blockquote>
<p>&quot;Table이 2차원이라면<br>그 2차원 구조 자체를 모델이 보게 만들면 되지 않을까?&quot;</p>
</blockquote>
<p>여기서 <strong>TabPFN v2</strong>가 등장한다.</p>
<hr>
<h1 id="tabpfn-v2-row-하나가-아니라-table의-2차원-구조를-보자">TabPFN v2: Row 하나가 아니라 Table의 2차원 구조를 보자</h1>
<p>v2의 첫 번째 큰 변화는<br><strong>Row Token 중심에서 2D Table Modeling으로 넘어간 것</strong>이다.</p>
<p>강의 자료에서는</p>
<blockquote>
<p>Represent each cell as one token</p>
</blockquote>
<p>이라고 설명한다.</p>
<p>즉 v1에서는</p>
<pre><code class="language-text">한 Row 전체
↓
Token 하나</code></pre>
<p>였다면</p>
<p>v2에서는 각 Cell의 Feature-level 정보를 더 직접적으로 표현한다.</p>
<p>그리고 두 가지 Attention을 번갈아 사용한다.</p>
<pre><code class="language-text">Feature-wise Attention

Sample-wise Attention</code></pre>
<hr>
<h1 id="feature-wise-attention">Feature-wise Attention</h1>
<p>Feature-wise Attention은<br>같은 Row 안의 Feature들이 서로 어떤 관계가 있는지 본다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Age
Income
Debt</code></pre>
<p>가 있다면</p>
<pre><code class="language-text">Income이 높은데 Debt도 높은가?

Age와 Income은 어떤 관계가 있는가?</code></pre>
<p>같은 Feature 사이의 관계를 모델링할 수 있다.</p>
<hr>
<h1 id="sample-wise-attention">Sample-wise Attention</h1>
<p>Sample-wise Attention은<br>여러 Row 사이의 관계를 본다.</p>
<p>예를 들어</p>
<pre><code class="language-text">고객 A
고객 B
고객 C</code></pre>
<p>가 있을 때</p>
<pre><code class="language-text">비슷한 Feature 패턴을 가진 고객들은
어떤 Target을 가지는가?</code></pre>
<p>를 참고할 수 있다.</p>
<p>즉 v2는</p>
<pre><code class="language-text">Column 방향
+
Row 방향</code></pre>
<p>을 모두 본다.</p>
<p>이게 v1과 가장 큰 차이다.</p>
<blockquote>
<p>여기에 강의자료 11페이지의 2D Attention 그림을 넣으면 좋을 것 같다.</p>
</blockquote>
<hr>
<h1 id="v2에서는-synthetic-prior도-더-현실적으로-바뀌었다">v2에서는 Synthetic Prior도 더 현실적으로 바뀌었다</h1>
<p>v2의 변화는 Architecture만이 아니다.</p>
<p>Pretraining에 사용하는 Synthetic Task도<br>현실 Table에 더 가까워졌다.</p>
<p>강의 자료에서는 다음 요소들을 추가했다고 설명한다.</p>
<pre><code class="language-text">Categorical Features
Missing Values
Quantization
...</code></pre>
<p>또 Synthetic Task 자체도 더 큰 규모로 확장됐다.</p>
<pre><code class="language-text">최대 약 10,000 Samples
최대 약 500 Features</code></pre>
<p>그리고 Synthetic Data를 생성할 때도<br>단순한 Neural Network뿐만 아니라</p>
<pre><code class="language-text">Neural Network
Tree
Discretization</code></pre>
<p>등 여러 형태의 구조를 활용해<br>더 다양한 Task를 만든다.</p>
<p>즉 v2는 두 가지 방향으로 발전했다고 볼 수 있다.</p>
<pre><code class="language-text">1. Table Architecture를 더 잘 본다.
2. Pretraining Task도 실제 Table에 더 가깝게 만든다.</code></pre>
<hr>
<h1 id="v2의-성능">v2의 성능</h1>
<p>강의 자료에서는 TabPFN v2 역시<br>작은 Dataset에서 MLP와 GBDT Baseline보다 좋은 성능을 보였다고 설명한다.</p>
<p>특히 v2부터는 Classification뿐 아니라<br>Regression 결과도 함께 제시된다.</p>
<p>그래서</p>
<blockquote>
<p>&quot;TabPFN은 원래 Classification만 가능한 모델이다&quot;</p>
</blockquote>
<p>라고 일반화하면 정확하지 않다.</p>
<p>그 설명은 <strong>v1의 초기 제약</strong>에 더 가깝다.</p>
<p>v2는 Regression까지 범위를 넓혔다.</p>
<hr>
<h1 id="하지만-v2도-문제가-있다">하지만 v2도 문제가 있다</h1>
<p>Cell-level Representation과 2D Attention을 사용하면<br>당연히 계산량이 늘어난다.</p>
<p>예를 들어</p>
<pre><code class="language-text">10,000 Rows
×
500 Columns</code></pre>
<p>의 Table이라면<br>굉장히 많은 Cell 정보를 처리해야 한다.</p>
<p>그리고</p>
<pre><code class="language-text">Feature-wise Attention
+
Sample-wise Attention</code></pre>
<p>을 반복한다.</p>
<p>따라서 v2의 주요 한계는 다음과 같다.</p>
<pre><code class="language-text">Computational Cost 증가

Memory 사용량 증가

큰 Context Table 처리 어려움

Classification Evaluation은
최대 10개 Class 수준으로 제한</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>Table을 더 잘 이해하게 되었지만<br>그만큼 계산 비용이 비싸졌다.</strong></p>
</blockquote>
<p>그래서 다음 질문이 생긴다.</p>
<blockquote>
<p>&quot;Cell 수준의 정보를 활용하되<br>모든 정보를 끝까지 들고 다니지 않으면 안 될까?&quot;</p>
</blockquote>
<p>이 질문에서 v3가 등장한다.</p>
<hr>
<h1 id="tabpfn-v3-다시-row로-압축하자">TabPFN v3: 다시 Row로 압축하자</h1>
<p>v3의 핵심 변화는 두 가지다.</p>
<pre><code class="language-text">1. Row Compression
2. Retrieval-based Prediction</code></pre>
<hr>
<h1 id="row-compression">Row Compression</h1>
<p>v2에서는 Feature-level 정보를 상당히 오래 유지했다.</p>
<p>v3에서는 먼저 Feature-level 정보를 처리한 뒤<br>이를 <strong>Compact Row Embedding</strong>으로 압축한다.</p>
<p>강의 그림에는</p>
<pre><code class="language-text">4 CLS per row</code></pre>
<p>라고 표시되어 있다.</p>
<p>즉 한 Row의 많은 Feature 정보를<br>몇 개의 CLS Token에 압축하는 형태다.</p>
<p>하지만 여기에서 중요한 것은<br>&quot;정확히 4개를 사용한다&quot;는 사실을 외우는 것이 아니다.</p>
<p>핵심은</p>
<blockquote>
<p><strong>Feature-level 정보를 ICL 전에 작은 Row Representation으로 압축한다.</strong></p>
</blockquote>
<p>는 것이다.</p>
<pre><code class="language-text">많은 Feature 정보
↓
Feature Aggregation
↓
Compact Row Embedding
↓
Row-level ICL</code></pre>
<p>v2가</p>
<pre><code class="language-text">Cell 정보를 많이 유지해서 비싸다</code></pre>
<p>라는 문제가 있었다면,</p>
<p>v3는</p>
<pre><code class="language-text">Feature 정보를 먼저 압축해서
Row 수준에서 효율적으로 ICL하자</code></pre>
<p>라는 방향으로 바뀐 것이다.</p>
<hr>
<h1 id="retrieval-based-prediction">Retrieval-based Prediction</h1>
<p>v3의 두 번째 변화는<br>최종 Prediction 방식이다.</p>
<p>기존에는 Fixed Classification Head를 사용하는 방식이었다면<br>v3에서는</p>
<p><strong>Attention-based Soft Nearest-Neighbor Decoder</strong></p>
<p>를 사용한다.</p>
<p>이름만 보면 어렵지만<br>아이디어는 꽤 직관적이다.</p>
<p>Test Row가 들어오면</p>
<pre><code class="language-text">어떤 Training Row들이
이 Test Row와 관련이 있는가?</code></pre>
<p>를 Attention Weight로 계산한다.</p>
<p>그리고 그 Training Row들의 Label 정보를 이용해<br>최종 Prediction을 만든다.</p>
<p>쉽게 비유하면 kNN과 조금 닮았다.</p>
<p>하지만</p>
<pre><code class="language-text">가장 가까운 K개를 딱 잘라서 동일하게 보는 것</code></pre>
<p>이 아니라</p>
<pre><code class="language-text">Training Row마다 얼마나 참고할지를
Attention Weight로 부드럽게 결정</code></pre>
<p>한다고 이해하면 된다.</p>
<hr>
<h1 id="v3의-성능과-확장성">v3의 성능과 확장성</h1>
<p>강의 자료에서는 v3가<br>빠른 Inference Speed와 SOTA 성능을 보여준다고 설명한다.</p>
<p>특히 더 중요한 것은 Scalability다.</p>
<p>v3의 Benchmark에는</p>
<pre><code class="language-text">100K ~ 1M Training Samples</code></pre>
<p>규모의 Classification과 Regression Task가 등장한다.</p>
<p>즉 초기 v1의</p>
<pre><code class="language-text">1,000 Samples 미만</code></pre>
<p>수준에서 시작했던 모델이<br>v3에서는 훨씬 큰 Dataset까지 확장되고 있는 것이다.</p>
<hr>
<h1 id="relational-table까지-확장-tabpfn-rel">Relational Table까지 확장: TabPFN-REL</h1>
<p>v3에서는 단순한 Flat Table뿐 아니라<br>Relational Data에도 적용하려는 시도가 등장한다.</p>
<p>예를 들어 데이터베이스에는</p>
<pre><code class="language-text">User Table
Order Table
Product Table</code></pre>
<p>처럼 여러 Table이 관계를 맺는 경우가 많다.</p>
<p>TabPFN-REL은 이러한 Relational Table을<br>자동으로 Flatten한 뒤 TabPFN-3를 적용한다.</p>
<p>강의 자료에서는</p>
<blockquote>
<p>Relational Foundation Model 중에서는 SOTA</p>
</blockquote>
<p>라고 설명하지만,</p>
<p>전체적으로는 <strong>Supervised RelGNN이 여전히 더 좋은 성능을 보인다</strong>는 한계도 함께 언급한다.</p>
<p>즉 Foundation Model 방식이 Relational Data까지 확장되고 있지만<br>아직 모든 상황에서 기존 Supervised Model을 압도하는 것은 아니다.</p>
<hr>
<h1 id="v1-v2-v3를-한-번에-정리하면">v1, v2, v3를 한 번에 정리하면</h1>
<p>여기까지 내용을 가장 단순하게 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>버전</th>
<th>핵심 변화</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>v1</td>
<td>Row Token + Synthetic Pretraining</td>
<td>작은 Tabular Task에서 빠른 In-Context Prediction</td>
</tr>
<tr>
<td>v2</td>
<td>2D Table Modeling + Richer Synthetic Prior</td>
<td>Table 구조를 더 잘 이해</td>
</tr>
<tr>
<td>v3</td>
<td>Row Compression + Retrieval Decoder</td>
<td>더 큰 Dataset으로 확장</td>
</tr>
</tbody></table>
<p>조금 더 직관적으로 보면</p>
<pre><code class="language-text">v1
&quot;Row 하나를 Token으로 만들자&quot;
↓
문제:
Table의 2차원 구조를 잘 못 본다.

v2
&quot;그러면 Cell과 Row/Column 관계를 모두 보자&quot;
↓
문제:
좋은데 너무 비싸다.

v3
&quot;Feature 정보를 먼저 압축하고
Row 수준에서 효율적으로 ICL하자&quot;</code></pre>
<p>이라고 이해할 수 있다.</p>
<hr>
<h1 id="여기서-끝이-아니다-최근-tabpfn-스타일-연구">여기서 끝이 아니다: 최근 TabPFN 스타일 연구</h1>
<p>강의 후반부에서는<br>TabPFN v1, v2, v3뿐 아니라 최근 변형 모델들을 꽤 많이 소개한다.</p>
<p>모델 이름은 많지만<br>하나씩 외우기보다는</p>
<blockquote>
<p><strong>&quot;기존 TabPFN의 어떤 문제를 해결하려고 하는가?&quot;</strong></p>
</blockquote>
<p>라는 관점으로 보면 이해하기 쉽다.</p>
<hr>
<h1 id="tabdpt-전체-dataset을-다-볼-필요가-있을까">TabDPT: 전체 Dataset을 다 볼 필요가 있을까?</h1>
<p>TabDPT의 핵심 아이디어는 매우 직관적이다.</p>
<blockquote>
<p>Test Sample을 예측할 때<br>전체 Training Dataset을 모두 볼 필요가 있을까?</p>
</blockquote>
<p>오히려 Test Sample과 관련 있는 Row만 보는 것이<br>더 좋을 수도 있다.</p>
<p>그래서 TabDPT는</p>
<pre><code class="language-text">Test Sample
↓
Training Data에서 비슷한 Row 검색
↓
Top-K Row 선택
↓
그 Row들만 Context로 사용
↓
ICL Prediction</code></pre>
<p>을 수행한다.</p>
<p>즉 네 필기에서 적었던</p>
<blockquote>
<p>&quot;K개만 뽑아서 한다&quot;</p>
</blockquote>
<p>가 맞다.</p>
<p>정확히는 <strong>K개의 가장 비슷한 Row를 Retrieval해서 Context로 사용</strong>한다.</p>
<p>TabDPT는 Row를 하나의 Token으로 본다는 점에서는<br>TabPFN v1과 비슷하다.</p>
<hr>
<h1 id="tabdpt의-또-다른-차이-실제-table로-pretraining">TabDPT의 또 다른 차이: 실제 Table로 Pretraining</h1>
<p>기존 TabPFN은 Synthetic Prior가 굉장히 중요했다.</p>
<p>TabDPT는 조금 다른 방향도 시도한다.</p>
<blockquote>
<p><strong>실제 Real-world Tabular Data를 이용해 Pretraining하자.</strong></p>
</blockquote>
<p>그리고 <strong>Masked Column Self-Supervised Learning</strong>을 사용한다.</p>
<p>쉽게 말하면</p>
<pre><code class="language-text">Table의 일부 Column 값을 가린다.
↓
나머지 정보를 보고
가려진 값을 맞혀본다.</code></pre>
<p>와 비슷하다.</p>
<p>강의 자료에서는</p>
<pre><code class="language-text">Real-world Pretraining
+
ICL Retrieval
+
SSL</code></pre>
<p>의 조합이 Robust한 Downstream Generalization을 만든다고 설명한다.</p>
<p>또 실제 데이터로 Pretraining했을 때<br>순수 Synthetic Data Generator만 사용한 것보다<br>더 빠른 Convergence와 좋은 Downstream Accuracy를 보였다고 소개한다.</p>
<hr>
<h1 id="semi-supervised-상황에서도-활용할-수-있다">Semi-supervised 상황에서도 활용할 수 있다</h1>
<p>TabDPT에서는 Semi-supervised Setting도 다룬다.</p>
<p>즉 Training Data 중 일부에만 Label이 있는 상황이다.</p>
<pre><code class="language-text">Label 있음
Label 있음
Label 없음
Label 없음
...</code></pre>
<p>이때 모델이 Unlabeled Data에 대해<br>자신의 Prediction을 만들어</p>
<pre><code class="language-text">Pseudo-label</code></pre>
<p>로 사용한다.</p>
<p>그리고 그 데이터를 다시 Context에 추가한다.</p>
<p>즉,</p>
<pre><code class="language-text">Label이 적다
↓
모델이 Unlabeled Data를 예측
↓
Pseudo-label 생성
↓
Context 확장
↓
더 나은 ICL</code></pre>
<p>이라는 구조다.</p>
<hr>
<h1 id="tabicl-v2의-2d-attention이-너무-비싸다면">TabICL: v2의 2D Attention이 너무 비싸다면?</h1>
<p>TabPFN v2의 문제는<br>Row/Column Attention을 반복하면서 비용이 커진다는 것이다.</p>
<p>TabICL은 이를 두 단계로 나눈다.</p>
<pre><code class="language-text">Stage 1
Feature 정보를 처리
↓
Fixed-dimensional Row Embedding 생성

Stage 2
Row Embedding들 사이에서 ICL</code></pre>
<p>즉</p>
<blockquote>
<p><strong>Feature 관계를 먼저 Row 하나에 압축하고<br>그다음 Row끼리 In-Context Learning을 한다.</strong></p>
</blockquote>
<p>는 것이다.</p>
<p>이렇게 하면 큰 Dataset에서 계산량을 줄일 수 있다.</p>
<p>강의 자료의 Complexity를 보면</p>
<pre><code class="language-text">TabICL
O(m²n + n²)

TabPFN v2
O(m²n + mn²)</code></pre>
<p>로 정리된다.</p>
<p>여기서</p>
<pre><code class="language-text">n = Row 수
m = Column 수</code></pre>
<p>다.</p>
<p>특히 Row 수가 커질수록<br><code>mn²</code>에서 <code>n²</code>로 줄어드는 차이가 커진다.</p>
<p>강의에서는 TabICL이 TabPFN v2와 경쟁력 있는 성능을 유지하면서<br>더 큰 Dataset에 잘 Scaling된다고 설명한다.</p>
<hr>
<h1 id="tabiclv2">TabICLv2</h1>
<p>TabICLv2는 TabICL을 더 발전시킨 모델이다.</p>
<p>강의에서는 다음 변화들을 소개한다.</p>
<pre><code class="language-text">Richer Synthetic Data Generation Engine

Circular-shift Feature Grouping

Target-aware Embedding

QASSMax

Optimized Pretraining Protocol

Muon Optimizer</code></pre>
<p>각각을 깊게 들어가면 꽤 복잡하지만<br>큰 방향은 명확하다.</p>
<blockquote>
<p><strong>더 다양한 Task를 만들고,<br>Feature Representation이 무너지는 것을 줄이고,<br>Label 정보를 더 일찍 활용하고,<br>긴 Context에서도 Attention을 효율적으로 처리하자.</strong></p>
</blockquote>
<p>강의 자료에서는 TabICLv2가<br>최근 Baseline보다 Overall Performance가 좋고,<br>큰 Dataset에서도 좋은 Scalability를 보이며,<br>여러 Hardware에서 TabPFN-2.5보다 빠른 Inference를 보였다고 소개한다.</p>
<hr>
<h1 id="taco-context-자체를-압축하면">TACO: Context 자체를 압축하면?</h1>
<p>TACO의 아이디어는 더 단순하다.</p>
<p>Tabular ICL에서 가장 비싼 것 중 하나가<br>긴 Context 전체에 Attention을 수행하는 것이다.</p>
<p>그렇다면</p>
<blockquote>
<p><strong>Context 자체를 미리 압축하면 안 될까?</strong></p>
</blockquote>
<p>라는 생각이다.</p>
<pre><code class="language-text">Large Training Context
↓
Compressor
↓
Compact Latent Representation
↓
Predictor</code></pre>
<p>Training Sample 전체를 그대로 들고 다니는 대신<br>중요한 정보를 작은 Latent Representation에 압축한다.</p>
<p>강의 자료에서는 Predictor-only Transformer와 비교해</p>
<pre><code class="language-text">53× Speedup

94% Memory Reduction</code></pre>
<p>을 달성하면서도 큰 성능 저하는 없었다고 소개한다.</p>
<p>이 수치만 봐도<br>최근 Tabular Foundation Model 연구에서<br><strong>Compression과 Scalability</strong>가 얼마나 중요한지 알 수 있다.</p>
<hr>
<h1 id="mitra-좋은-architecture만큼-prior도-중요하다">MITRA: 좋은 Architecture만큼 Prior도 중요하다</h1>
<p>PFN을 공부하면서 계속 등장하는 것이 있다.</p>
<p>바로 <strong>Prior</strong>다.</p>
<p>MITRA는 이 부분을 직접 다룬다.</p>
<p>기존처럼 하나의 Synthetic Prior에만 의존하지 않고</p>
<pre><code class="language-text">Prior A
Prior B
Prior C
...</code></pre>
<p>처럼 여러 Synthetic Task Generator를 섞는다.</p>
<p>핵심 아이디어는 다음과 같다.</p>
<blockquote>
<p><strong>Prior Design은 Model Architecture만큼 중요하다.</strong></p>
</blockquote>
<p>강의 결과에서도 SCM Prior 자체가 강력한 Standalone Prior였고,<br>여러 Prior를 혼합했을 때 Robustness가 향상되는 모습을 보였다.</p>
<p>즉 아무리 좋은 Transformer를 만들어도<br>Pretraining에서 경험하는 Task가 너무 단순하거나 편향되어 있다면<br>좋은 Generalization을 기대하기 어렵다는 것이다.</p>
<hr>
<h1 id="contexttab-column-이름의-의미도-활용하자">ConTextTab: Column 이름의 의미도 활용하자</h1>
<p>기존 TabPFN 계열은<br>주로 Table의 값과 구조에 집중한다.</p>
<p>하지만 실제 Table에서는 Column 이름 자체에도<br>굉장히 중요한 정보가 들어 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">25</code></pre>
<p>라는 값만 보면 아무 의미가 없다.</p>
<p>하지만</p>
<pre><code class="language-text">Age = 25</code></pre>
<p>라고 하면 의미가 생긴다.</p>
<p>또</p>
<pre><code class="language-text">Price = 25</code></pre>
<p>라면 완전히 다른 뜻이다.</p>
<p>ConTextTab은 이런 <strong>Semantic Information</strong>을  
In-Context Learning에 넣는다.</p>
<p>강의에서는</p>
<pre><code class="language-text">Column Name

Text / Categorical Value</code></pre>
<p>에 Pretrained Text Embedding을 사용한다고 설명한다.</p>
<p>Text/Categorical Feature에는</p>
<pre><code class="language-text">all-MiniLM-L6-v2</code></pre>
<p>를 사용하고,</p>
<p>날짜는</p>
<pre><code class="language-text">Day
Month
Year</code></pre>
<p>를 각각 Embedding한 뒤 합친다.</p>
<p>강의 자료에서는 ConTextTab이<br>Semantic Information이 풍부한 CARTE Benchmark에서 SOTA 결과를 보였다고 소개한다.</p>
<hr>
<h1 id="multimodalpfn-table에-이미지와-텍스트까지-있다면">MultiModalPFN: Table에 이미지와 텍스트까지 있다면?</h1>
<p>현실의 데이터가 항상 순수한 Table만으로 구성되는 것은 아니다.</p>
<p>예를 들어 쇼핑 데이터라면</p>
<pre><code class="language-text">상품 가격
상품 카테고리
상품 판매량
+
상품 이미지
+
상품 설명 Text</code></pre>
<p>가 함께 존재할 수 있다.</p>
<p>MultiModalPFN은 TabPFN을<br>이런 Multimodal Setting으로 확장한다.</p>
<pre><code class="language-text">Tabular Feature
Image
Text
↓
각 Modality Encoder
↓
TabPFN이 사용할 수 있는 Representation으로 변환
↓
함께 Prediction</code></pre>
<p>즉</p>
<blockquote>
<p><strong>Table + Image + Text</strong></p>
</blockquote>
<p>를 동시에 사용하도록<br>TabPFN의 범위를 확장하는 것이다.</p>
<hr>
<h1 id="nanotabpfn">nanoTabPFN</h1>
<p>nanoTabPFN은<br>완전히 새로운 모델이라기보다</p>
<blockquote>
<p><strong>TabPFN v2를 쉽게 공부하고 실험할 수 있도록 만든 Lightweight Reimplementation</strong></p>
</blockquote>
<p>에 가깝다.</p>
<p>강의에서는</p>
<pre><code class="language-text">500줄 이하의 코드</code></pre>
<p>로 TabPFN v2의 핵심 Architecture와 Training Loop를 구현했다고 소개한다.</p>
<p>그리고 Single GPU에서도<br>몇 분 안에 Pretraining을 실험할 수 있도록 했다.</p>
<p>개인적으로는 TabPFN 내부 동작을 직접 공부하고 싶다면<br>이런 프로젝트가 오히려 가장 도움이 될 것 같았다.</p>
<hr>
<h1 id="결국-최근-연구는-어디로-가고-있을까">결국 최근 연구는 어디로 가고 있을까?</h1>
<p>강의 마지막에서는 Tabular Foundation Model의 Open Direction을<br>크게 네 가지로 정리한다.</p>
<hr>
<h2 id="1-scalability-and-efficiency">1. Scalability and Efficiency</h2>
<p>TabPFN은 처음에는 작은 Dataset에 강한 모델이었다.</p>
<p>하지만 최근에는</p>
<pre><code class="language-text">100K
1M</code></pre>
<p>규모의 Dataset까지 확장되고 있다.</p>
<p>그럼에도</p>
<pre><code class="language-text">Memory
Latency
Long-context Processing</code></pre>
<p>은 여전히 중요한 Bottleneck이다.</p>
<p>그래서</p>
<pre><code class="language-text">Row Compression
Context Compression
Retrieval
Efficient Attention</code></pre>
<p>같은 방법이 계속 등장하고 있다.</p>
<hr>
<h2 id="2-pretraining-distribution">2. Pretraining Distribution</h2>
<p>또 하나 중요한 문제는</p>
<blockquote>
<p><strong>무엇으로 Pretraining할 것인가?</strong></p>
</blockquote>
<p>이다.</p>
<p>한쪽에는</p>
<pre><code class="language-text">Synthetic Prior</code></pre>
<p>가 있고,</p>
<p>다른 한쪽에는</p>
<pre><code class="language-text">Real-world Table</code></pre>
<p>이 있다.</p>
<p>Synthetic Data는 원하는 만큼 다양한 Task를 만들 수 있다는 장점이 있고,<br>Real-world Data는 실제 Table의 복잡한 특성을 직접 반영한다는 장점이 있다.</p>
<p>강의에서는 이 두 방향이 경쟁 관계라기보다<br><strong>서로 보완적인 Signal</strong>이라고 설명한다.</p>
<hr>
<h2 id="3-semantic-and-heterogeneous-tables">3. Semantic and Heterogeneous Tables</h2>
<p>현실 Table에는 숫자만 존재하지 않는다.</p>
<pre><code class="language-text">Column Name
Text
Categorical Value
Missingness
Date
Mixed Feature Types</code></pre>
<p>같은 정보가 존재한다.</p>
<p>ConTextTab처럼<br>이런 의미 정보를 어떻게 활용할지 역시 중요한 연구 방향이다.</p>
<hr>
<h2 id="4-beyond-standard-flat-tables">4. Beyond Standard Flat Tables</h2>
<p>마지막으로 앞으로는<br>단순한 하나의 Flat Table만 다루지 않을 가능성이 크다.</p>
<pre><code class="language-text">Relational
Temporal
Multimodal
Causal</code></pre>
<p>구조까지 Tabular Foundation Model이 확장되고 있다.</p>
<p>TabPFN-REL과 MultiModalPFN이<br>이미 그 방향을 보여주고 있다고 생각한다.</p>
<hr>
<h1 id="이번-강의를-들으며-느낀-점">이번 강의를 들으며 느낀 점</h1>
<p>처음에는 Foundation Model이라고 하면<br>GPT처럼 엄청나게 많은 데이터를 학습한 거대한 모델만 떠올렸다.</p>
<p>그런데 PFN은 조금 다른 관점을 보여준다.</p>
<pre><code class="language-text">다양한 문제를 미리 만들어서 많이 풀어본다.
↓
&quot;문제를 푸는 방법&quot;을 학습한다.
↓
새로운 Dataset이 들어오면
그 Training Data를 Context로 본다.
↓
별도의 재학습 없이 Prediction한다.</code></pre>
<p>이 아이디어가 가장 인상 깊었다.</p>
<p>또 v1 → v2 → v3의 발전 과정도 재미있었다.</p>
<pre><code class="language-text">v1
Row 하나를 Token으로 만들자.
↓
문제:
Table의 2차원 구조를 잘 못 본다.

v2
그러면 Row와 Column 방향을 모두 보자.
↓
문제:
계산량과 Memory가 너무 커진다.

v3
Feature 정보를 먼저 압축하고
Row 수준에서 효율적으로 ICL하자.</code></pre>
<p>결국 모델이 발전하는 과정은<br>무조건 더 복잡하게 만드는 것이 아니라</p>
<blockquote>
<p><strong>표현력을 높이면서 계산 비용을 다시 줄이는 과정</strong></p>
</blockquote>
<p>처럼 느껴졌다.</p>
<p>그리고 이번 강의를 통해<br>Architecture만큼 <strong>Pretraining Task를 어떻게 만들 것인가</strong>도 중요하다는 점을 알게 됐다.</p>
<p>좋은 Transformer를 만드는 것만으로 끝나는 것이 아니라</p>
<pre><code class="language-text">어떤 Prior를 사용할 것인가?

Synthetic Data를 얼마나 현실적으로 만들 것인가?

실제 Table도 Pretraining에 사용할 것인가?</code></pre>
<p>같은 질문들이 성능에 직접적인 영향을 준다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>TabPFN의 핵심을 한 문장으로 정리하면 다음과 같다.</p>
<blockquote>
<p><strong>수많은 Tabular Task를 미리 학습해두고,<br>새로운 Dataset의 Training Data를 Context로 사용하여<br>별도의 Parameter Update 없이 예측하는 Tabular Foundation Model이다.</strong></p>
</blockquote>
<p>전체 흐름은 다음과 같다.</p>
<pre><code class="language-text">Prior 설계
↓
수많은 Synthetic Task 생성
↓
Transformer 기반 PFN 사전학습
↓
새로운 Dataset 등장
↓
Training Data를 Context로 입력
↓
In-Context Learning
↓
Parameter Update 없이 Prediction</code></pre>
<p>그리고 TabPFN은 계속 발전하고 있다.</p>
<pre><code class="language-text">v1
Row Token

↓

v2
2D Table Modeling
+ Richer Synthetic Prior

↓

v3
Row Compression
+ Retrieval-based Prediction</code></pre>
<p>최근 연구는 여기에</p>
<pre><code class="language-text">Top-K Retrieval
Real-world Pretraining
Context Compression
Mixed Synthetic Priors
Semantic Information
Multimodal Data</code></pre>
<p>까지 결합하고 있다.</p>
<p>이전까지 Tabular ML에서는<br>새로운 Dataset이 주어지면 그 Dataset에 맞는 모델을 학습하고<br>Hyperparameter를 튜닝하는 것이 당연하다고 생각했다.</p>
<p>TabPFN은 이 전제를 바꾼다.</p>
<blockquote>
<p><strong>&quot;모델을 데이터셋마다 다시 학습하는 대신,<br>데이터셋을 보고 문제를 푸는 방법 자체를 미리 학습할 수는 없을까?&quot;</strong></p>
</blockquote>
<p>아마 이 질문이<br>TabPFN이라는 새로운 패러다임의 가장 핵심적인 아이디어가 아닐까 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring의 `@Component`, `@RestController`, `@Bean`은 대체 뭘까?]]></title>
            <link>https://velog.io/@daehyun_lee/Spring%EC%9D%98-Component-RestController-Bean%EC%9D%80-%EB%8C%80%EC%B2%B4-%EB%AD%98%EA%B9%8C</link>
            <guid>https://velog.io/@daehyun_lee/Spring%EC%9D%98-Component-RestController-Bean%EC%9D%80-%EB%8C%80%EC%B2%B4-%EB%AD%98%EA%B9%8C</guid>
            <pubDate>Tue, 11 Aug 2026 06:45:57 GMT</pubDate>
            <description><![CDATA[<h1 id="spring의-component-restcontroller-bean은-대체-뭘까">Spring의 <code>@Component</code>, <code>@RestController</code>, <code>@Bean</code>은 대체 뭘까?</h1>
<p>오늘은 오픈소스 개발자 프로젝트를 진행하면서 마주친<br>Spring의 간단한 원칙들과 <strong>의존성 주입(DI)</strong>을 이해하기 전에 알아야 할 내용을 정리해보고자 한다.</p>
<p>프로젝트 코드를 보다 보니 다음과 같은 코드들이 계속 등장했다.</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<pre><code class="language-java">@RestController
class LsController {
}</code></pre>
<pre><code class="language-java">@Bean
SimulationEngine simulationEngine() {
    return new SimulationEngine(...);
}</code></pre>
<p>Java 코드는 어느 정도 익숙한데<br>문제는 앞에 붙어 있는 녀석들이었다.</p>
<pre><code class="language-text">@Component
@RestController
@Bean</code></pre>
<blockquote>
<p>@ 뒤에 붙어 있는 저 꼬리표들은 대체 뭘까?</p>
</blockquote>
<p>이번 글에서는 이 세 가지가 무엇인지부터<br>Spring이 왜 객체를 직접 관리하려고 하는지까지 정리해보려고 한다.</p>
<hr>
<h2 id="먼저-annotation이란">먼저 Annotation이란?</h2>
<p>Java에서 <code>@</code>로 시작하는 것을<br><strong>Annotation(애노테이션)</strong>이라고 한다.</p>
<p>예를 들어 다음과 같은 것들이다.</p>
<pre><code class="language-java">@Override
@Deprecated
@Component
@RestController
@Bean</code></pre>
<p>애노테이션은 쉽게 말하면<br><strong>코드에 추가적인 정보나 의미를 붙이는 표시</strong>라고 생각할 수 있다.</p>
<p>그리고</p>
<pre><code class="language-java">@Component
@RestController
@Bean</code></pre>
<p>은 Spring에서 사용하는 애노테이션들이다.</p>
<p>이 애노테이션들을 통해 Spring에게</p>
<pre><code class="language-text">&quot;이 클래스는 네가 관리해.&quot;

&quot;이 클래스는 REST API 요청을 처리해.&quot;

&quot;이 메서드가 반환하는 객체를 네가 관리해.&quot;</code></pre>
<p>같은 정보를 전달할 수 있다.</p>
<p>그렇다면 하나씩 알아보자.</p>
<hr>
<h1 id="1-component">1. <code>@Component</code></h1>
<p><code>@Component</code>를 정말 쉽게 표현하면 다음과 같다.</p>
<blockquote>
<p>&quot;이 클래스의 객체는 Spring이 만들어서 관리해줘.&quot;</p>
</blockquote>
<p>예를 들어 다음 클래스가 있다고 해보자.</p>
<pre><code class="language-java">class LsTrDispatcher {
}</code></pre>
<p>일반적인 Java 코드라면 이 객체가 필요할 때<br>개발자가 직접 객체를 생성할 수 있다.</p>
<pre><code class="language-java">LsTrDispatcher lsTrDispatcher = new LsTrDispatcher();</code></pre>
<p>즉,</p>
<pre><code class="language-text">개발자가 직접 new
↓
객체 생성
↓
개발자가 사용</code></pre>
<p>하는 것이다.</p>
<p>그런데 클래스에 <code>@Component</code>를 붙이면 달라진다.</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<p>Spring은 애플리케이션이 시작될 때<br>컴포넌트 스캔 대상에서 이 클래스를 발견하고 객체를 생성해 관리한다.</p>
<p>쉽게 표현하면 Spring이 이렇게 받아들이는 것이다.</p>
<pre><code class="language-text">@Component 발견

&quot;아, LsTrDispatcher는 내가 관리해야 하는 객체구나.&quot;

↓

LsTrDispatcher 객체 생성

↓

Spring Container에서 관리</code></pre>
<p>그러면 개발자가 필요한 곳마다</p>
<pre><code class="language-java">new LsTrDispatcher();</code></pre>
<p>를 반복해서 작성하며 직접 관리할 필요가 줄어든다.</p>
<p>처음 이걸 알고 나서는</p>
<blockquote>
<p>엄청 편리한데?<br>이래서 Java 개발자들이 Spring을 쓰는 건가?</p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>물론 Spring을 사용하는 이유가 이것 하나뿐인 것은 아니지만,<br>객체 생성과 관계를 Spring에게 맡길 수 있다는 점은 굉장히 큰 장점이다.</p>
<hr>
<h1 id="spring-bean이란">Spring Bean이란?</h1>
<p>여기서 하나의 새로운 용어가 등장한다.</p>
<p><strong>Bean</strong>이다.</p>
<p>Spring이 생성하고 Spring Container에서 관리하는 객체를<br>일반적으로 <strong>Spring Bean</strong>이라고 부른다.</p>
<p>예를 들어</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<p>가 Spring에 의해 등록되었다면<br><code>LsTrDispatcher</code> 객체는 Spring Bean이 된다.</p>
<p>구조를 단순하게 표현하면 다음과 같다.</p>
<pre><code class="language-text">Spring Container

└── LsTrDispatcher 객체
        ↑
     Spring Bean</code></pre>
<p>즉,</p>
<pre><code class="language-text">Bean
=
Spring Container가 관리하는 객체</code></pre>
<p>라고 우선 이해하면 된다.</p>
<hr>
<h1 id="2-restcontroller">2. <code>@RestController</code></h1>
<p>다음은 <code>@RestController</code>다.</p>
<pre><code class="language-java">@RestController
class LsController {
}</code></pre>
<p>이것도 Spring이 해당 클래스를 관리하도록 만드는 애노테이션이다.</p>
<p>하지만 <code>@Component</code>보다 <strong>추가적인 의미</strong>가 있다.</p>
<p>쉽게 표현하면 다음과 같다.</p>
<blockquote>
<p>&quot;이 클래스는 Spring이 관리하면서 HTTP 요청도 처리하는 REST Controller야.&quot;</p>
</blockquote>
<p>예를 들어 다음 코드가 있다고 해보자.</p>
<pre><code class="language-java">@RestController
class LsController {

    @GetMapping(&quot;/orders&quot;)
    public String orders() {
        return &quot;주문 목록&quot;;
    }
}</code></pre>
<p>클라이언트가 다음 요청을 보낸다고 해보자.</p>
<pre><code class="language-text">GET /orders</code></pre>
<p>그러면 Spring이 해당 요청을 적절한 Controller 메서드와 연결하고<br><code>orders()</code>가 실행된다.</p>
<pre><code class="language-text">사용자 / 클라이언트
        ↓
    HTTP 요청
        ↓
@RestController
   LsController
        ↓
    orders()
        ↓
    &quot;주문 목록&quot;
        ↓
    HTTP 응답</code></pre>
<p>즉, <code>@RestController</code>는  
외부 HTTP 요청이 애플리케이션 내부로 들어오는 입구 역할을 하는 클래스에 붙일 수 있다.</p>
<hr>
<h2 id="component와-restcontroller의-관계"><code>@Component</code>와 <code>@RestController</code>의 관계</h2>
<p>여기서 조금 헷갈렸다.</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<p>도 Spring이 관리하고,</p>
<pre><code class="language-java">@RestController
class LsController {
}</code></pre>
<p>도 Spring이 관리한다.</p>
<p>그렇다면 둘의 차이는 뭘까?</p>
<p>핵심은 <strong>역할</strong>이다.</p>
<pre><code class="language-text">@Component
→ 일반적인 Spring 관리 Component

@RestController
→ Spring이 관리하는 Component
  + REST API 요청 처리 역할</code></pre>
<p>실제로 <code>@RestController</code>는 내부적으로 <code>@Controller</code>와 <code>@ResponseBody</code>의 의미를 결합한 애노테이션이고, <code>@Controller</code> 역시 Spring의 component stereotype에 속한다.</p>
<p>따라서 지금 단계에서는 이렇게 이해하면 편하다.</p>
<blockquote>
<p><code>@RestController</code>도 Spring Bean으로 관리되지만,<br>HTTP API Controller라는 추가적인 역할을 가진다.</p>
</blockquote>
<hr>
<h1 id="3-bean">3. <code>@Bean</code></h1>
<p>마지막으로 <code>@Bean</code>이다.</p>
<pre><code class="language-java">@Bean
SimulationEngine simulationEngine() {
    return new SimulationEngine(...);
}</code></pre>
<p>쉽게 표현하면 다음과 같다.</p>
<blockquote>
<p>&quot;이 메서드가 반환하는 객체를 Spring이 관리해줘.&quot;</p>
</blockquote>
<p>그런데 처음 봤을 때 이런 생각이 들었다.</p>
<blockquote>
<p>뭐야? <code>@Component</code>랑 똑같은 거 아닌가?</p>
</blockquote>
<p>맞다.</p>
<p>결과만 보면 둘 다<br><strong>객체를 Spring Bean으로 등록해 관리하도록 한다</strong>는 공통점이 있다.</p>
<p>하지만 Bean을 등록하는 <strong>방법</strong>이 다르다.</p>
<hr>
<h1 id="component-vs-bean"><code>@Component</code> VS <code>@Bean</code></h1>
<p>가장 큰 차이는<br>애노테이션을 어디에 붙이는지 보면 이해하기 쉽다.</p>
<h2 id="component"><code>@Component</code></h2>
<p>클래스에 붙인다.</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<p>Spring은 컴포넌트 스캔을 통해 이 클래스를 발견하고<br>객체를 생성해 관리한다.</p>
<pre><code class="language-text">@Component
      ↓
LsTrDispatcher 클래스 발견
      ↓
Spring이 객체 생성
      ↓
Spring Container에 등록</code></pre>
<hr>
<h2 id="bean"><code>@Bean</code></h2>
<p>메서드에 붙인다.</p>
<pre><code class="language-java">@Bean
SimulationEngine simulationEngine() {
    return new SimulationEngine(...);
}</code></pre>
<p>Spring은 이 메서드가 반환한 객체를<br>Spring Bean으로 등록해 관리한다.</p>
<pre><code class="language-text">@Bean
  ↓
simulationEngine() 실행
  ↓
SimulationEngine 객체 반환
  ↓
Spring Container에 등록</code></pre>
<p>개념적으로 보면 다음 코드로 만들어지는 객체를</p>
<pre><code class="language-java">SimulationEngine engine =
        new SimulationEngine(...);</code></pre>
<p>Spring Container가 관리하도록 등록한다고 이해할 수 있다.</p>
<hr>
<h2 id="그런데-왜-굳이-두-가지-방법이-존재할까">그런데 왜 굳이 두 가지 방법이 존재할까?</h2>
<p>여기서 중요한 차이가 하나 더 있다.</p>
<p>내가 직접 작성한 클래스라면<br>클래스 자체에 <code>@Component</code>를 붙일 수 있다.</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<p>하지만 외부 라이브러리의 클래스라면 어떨까?</p>
<p>예를 들어 내가 직접 수정할 수 없는 클래스가 있다고 해보자.</p>
<pre><code class="language-java">SimulationEngine</code></pre>
<p>클래스 소스 코드에 직접</p>
<pre><code class="language-java">@Component</code></pre>
<p>를 붙일 수 없다면,<br>설정 클래스에서 객체를 직접 생성해 Bean으로 등록할 수 있다.</p>
<p>예를 들어 다음과 같은 형태다.</p>
<pre><code class="language-java">@Configuration
class AppConfig {

    @Bean
    SimulationEngine simulationEngine() {
        return new SimulationEngine(...);
    }
}</code></pre>
<p>즉,</p>
<pre><code class="language-text">@Component
→ 클래스 자체를 Spring이 찾아서 등록

@Bean
→ 개발자가 객체 생성 방법을 직접 정의해서 등록</code></pre>
<p>이라고 이해하면 훨씬 명확하다.</p>
<p><code>@Bean</code>은 외부 라이브러리뿐 아니라, 생성 과정에 세밀한 설정이 필요한 객체를 등록할 때도 유용하다.</p>
<hr>
<h1 id="세-가지를-비교하면">세 가지를 비교하면?</h1>
<p>지금까지 살펴본 내용을 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>애노테이션</th>
<th>붙이는 곳</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>@Component</code></td>
<td>클래스</td>
<td>이 클래스의 객체를 Spring이 생성하고 관리</td>
</tr>
<tr>
<td><code>@RestController</code></td>
<td>클래스</td>
<td>Spring이 관리 + HTTP REST API 요청 처리</td>
</tr>
<tr>
<td><code>@Bean</code></td>
<td>메서드</td>
<td>이 메서드가 반환한 객체를 Spring이 관리</td>
</tr>
</tbody></table>
<p>조금 더 쉽게 정리하면 다음과 같다.</p>
<pre><code class="language-text">@Component

&quot;이 클래스 Spring이 관리해.&quot;</code></pre>
<pre><code class="language-text">@RestController

&quot;이 클래스 Spring이 관리하고
REST API 요청도 처리해.&quot;</code></pre>
<pre><code class="language-text">@Bean

&quot;이 메서드가 만든 객체 Spring이 관리해.&quot;</code></pre>
<hr>
<h1 id="결국-핵심은-spring이-객체를-관리한다는-것">결국 핵심은 Spring이 객체를 관리한다는 것</h1>
<p>여기까지 공부하고 나니<br>세 애노테이션을 관통하는 하나의 공통점이 보였다.</p>
<p>바로 <strong>객체 관리</strong>다.</p>
<p>일반적인 Java 코드에서는 개발자가 직접 객체를 만들 수 있다.</p>
<pre><code class="language-java">new SimulationEngine();

new LsTrDispatcher();

new LsController();</code></pre>
<p>그리고 객체 사이의 관계도 개발자가 직접 연결한다.</p>
<p>하지만 Spring에서는<br>이런 객체들의 생성과 관계 설정을 Spring Container에 맡기는 방식을 적극적으로 사용한다.</p>
<p>내 프로젝트의 구조를 아주 단순화하면<br>다음과 같이 생각할 수 있다.</p>
<pre><code class="language-text">Spring Container
│
├── LsTrDispatcher 객체
│       ↑
│   @Component
│
├── LsController 객체
│       ↑
│   @RestController
│
└── SimulationEngine 객체
        ↑
      @Bean</code></pre>
<p>Spring Container 안에는<br>애플리케이션에서 사용할 여러 객체들이 Bean으로 등록되어 있다.</p>
<p>그리고 필요한 객체가 있다면<br>Spring이 그 객체를 필요한 곳에 연결해줄 수 있다.</p>
<p>여기서 다음 개념으로 이어진다.</p>
<hr>
<h1 id="그런데-왜-spring이-객체를-관리해야-할까">그런데 왜 Spring이 객체를 관리해야 할까?</h1>
<p>여기까지 이해하고 나니 새로운 의문이 생겼다.</p>
<p>예를 들어 <code>LsController</code>가  
<code>LsTrDispatcher</code>를 사용해야 한다고 해보자.</p>
<p>그냥 이렇게 하면 되는 것 아닌가?</p>
<pre><code class="language-java">@RestController
class LsController {

    private final LsTrDispatcher dispatcher =
            new LsTrDispatcher();
}</code></pre>
<p>Java 문법상 전혀 이상하지 않다.</p>
<p>그런데 Spring에서는 왜 굳이</p>
<pre><code class="language-java">@Component
class LsTrDispatcher {
}</code></pre>
<p>처럼 객체를 Bean으로 등록하고<br>Spring에게 관리를 맡기는 것일까?</p>
<p>이 질문을 이해하면</p>
<pre><code class="language-text">@Component

@Bean

Spring Container</code></pre>
<p>가 왜 필요한지도 자연스럽게 연결된다.</p>
<p>바로 <strong>의존성 주입(Dependency Injection, DI)</strong> 때문이다.</p>
<hr>
<h1 id="다음-글-의존성-주입di">다음 글: 의존성 주입(DI)</h1>
<p>이번 글에서는 일단 여기까지만 정리하고자 한다.</p>
<pre><code class="language-text">@Component
→ 클래스를 Spring Bean으로 등록

@RestController
→ REST API Controller 역할을 하는 Spring Bean

@Bean
→ 메서드가 반환한 객체를 Spring Bean으로 등록</code></pre>
<p>그리고 이 객체들은<br>Spring Container에서 관리된다.</p>
<p>그렇다면 다음 질문은 이것이다.</p>
<blockquote>
<p><code>LsController</code>가 <code>LsTrDispatcher</code>를 필요로 할 때<br>왜 직접 <code>new LsTrDispatcher()</code>를 하지 않을까?</p>
</blockquote>
<p>이 질문이 바로<br>Spring의 핵심 개념 중 하나인 <strong>의존성 주입(DI)</strong>으로 이어진다.</p>
<blockquote>
<p>Spring의 중요 원칙, 의존성 주입(DI)에 관한 이야기는 다음 글에서 이어서 정리해보고자 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | YOLO 트럭 객체 탐지 성능 개선 + 근데 이제 LA 관광을 곁들인(?)]]></title>
            <link>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B0%9C%EC%9D%B8-%EA%B3%BC%EC%A0%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-YOLO-%ED%8A%B8%EB%9F%AD-%EA%B0%9D%EC%B2%B4-%ED%83%90%EC%A7%80-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-LA-%EA%B4%80%EA%B4%91%EC%9D%84-%EA%B3%81%EB%93%A4%EC%9D%B8</link>
            <guid>https://velog.io/@daehyun_lee/USC-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B0%9C%EC%9D%B8-%EA%B3%BC%EC%A0%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-YOLO-%ED%8A%B8%EB%9F%AD-%EA%B0%9D%EC%B2%B4-%ED%83%90%EC%A7%80-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-LA-%EA%B4%80%EA%B4%91%EC%9D%84-%EA%B3%81%EB%93%A4%EC%9D%B8</guid>
            <pubDate>Sun, 09 Aug 2026 06:20:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/cc37d89c-5f00-4502-ad64-5492b6473789/image.jpeg" alt=""></p>
<h1 id="usc-빅데이터-개인-과제-프로젝트--yolo-트럭-객체-탐지-성능-개선--근데-이제-la-관광을-곁들인">(USC 빅데이터) 개인 과제 프로젝트 : YOLO 트럭 객체 탐지 성능 개선 + 근데 이제 LA 관광을 곁들인(?)</h1>
<p>USC에서 미국 대학생활을 체험하고 있던 어느 날.</p>
<p>조교님들로부터 갑작스럽게 과제가 하나 주어졌다.</p>
<p>그런데 그냥 과제가 아니었다.</p>
<p><strong>개인전.</strong></p>
<p>그리고 주어진 시간은 사실상 <strong>단 하루.</strong></p>
<p>각자 YOLO 모델의 성능을 최대한 끌어올리고, 최종 성능 점수에 따라 순위를 매겨 상위권 학생은 자신의 성능 개선 방법을 발표하는 작은 대회가 열린 것이다.</p>
<p>심지어 간단한 상품도 있다고 했다.</p>
<p>이 이야기를 듣자마자 갑자기 욕심이 생겼다.</p>
<blockquote>
<p><strong>&quot;이거 1등 하고 싶은데?&quot;</strong></p>
</blockquote>
<p>앞서 공식적으로 진행됐던 팀 해커톤에서는 아쉽게 수상하지 못했기 때문에 여기서라도 지금까지 공부했던 AI 역량을 마음껏 펼쳐보고 싶었다.</p>
<p>마침 이번에는 <strong>개인전</strong>이기도 했다.</p>
<p>그렇게 나만의 작은 해커톤이 시작됐다.</p>
<hr>
<h1 id="과제부터-제대로-읽어보자">과제부터 제대로 읽어보자</h1>
<p>일단 무작정 모델부터 학습시키기보다는 과제가 정확히 무엇을 요구하는지부터 살펴봤다.</p>
<p>과제의 제목은 아주 직관적이었다.</p>
<blockquote>
<p><strong>Homework: Detect the Trucks</strong></p>
</blockquote>
<p>트럭을 탐지하는 YOLO 모델을 학습시키고, Test Set에서 얼마나 정확하게 탐지하는지 확인하는 것이 목표였다.</p>
<h2 id="원문">원문</h2>
<blockquote>
<p><strong>Homework: Detect the Trucks</strong><br><strong>Due: Friday, at the start of class.</strong></p>
<p><strong>The task</strong><br>Train a YOLO model on the truck dataset and find out how accurate it is on the test set.</p>
<p>That&#39;s the whole assignment. But you have room to be clever about it, and the top 3 scores get recognized on Friday.</p>
</blockquote>
<h2 id="간단하게-번역하면">간단하게 번역하면</h2>
<blockquote>
<p><strong>과제: 트럭을 탐지하라</strong><br><strong>제출: 금요일 수업 시작 전</strong></p>
<p>제공된 트럭 데이터셋으로 YOLO 모델을 학습하고 Test Set에서 모델의 정확도를 측정한다.</p>
<p>기본적인 과제는 여기까지지만, 각자 다양한 방법으로 모델의 성능을 개선할 수 있으며 <strong>상위 3개의 성능은 금요일에 발표한다.</strong></p>
</blockquote>
<p>기본으로 제공된 모델은 <code>yolov8n.pt</code>였다.</p>
<pre><code class="language-python">from ultralytics import YOLO

model = YOLO(&#39;yolov8n.pt&#39;)

model.train(
    data=&#39;/content/truck_dataset/truck_data.yaml&#39;,
    epochs=30,
    imgsz=640,
    batch=8,
    name=&#39;my_run&#39;
)</code></pre>
<p>학습이 끝나면 Test Set에서 다음 지표를 측정한다.</p>
<pre><code class="language-python">metrics = model.val(split=&#39;test&#39;)

print(f&quot;mAP50     : {metrics.box.map50:.4f}&quot;)
print(f&quot;mAP50-95  : {metrics.box.map:.4f}&quot;)
print(f&quot;Precision : {metrics.box.mp:.4f}&quot;)
print(f&quot;Recall    : {metrics.box.mr:.4f}&quot;)</code></pre>
<p>최종적으로 확인하는 값은</p>
<ul>
<li>mAP50</li>
<li>mAP50-95</li>
<li>Precision</li>
<li>Recall</li>
</ul>
<p>등이었다.</p>
<p>이 중 <strong>순위를 결정하는 가장 중요한 지표는 mAP50</strong>이었고, 동점일 경우 mAP50-95를 기준으로 순위를 결정한다고 했다.</p>
<hr>
<h1 id="그냥-돌리면-다-비슷한-점수가-나온다">그냥 돌리면 다 비슷한 점수가 나온다</h1>
<p>재미있는 부분은 여기부터였다.</p>
<p>과제 설명에는 이런 내용이 있었다.</p>
<blockquote>
<p><strong>Everyone who follows the tutorial exactly will get roughly the same score.</strong></p>
</blockquote>
<p>즉,</p>
<p><strong>튜토리얼을 그대로 따라 하면 모두 비슷한 점수가 나온다는 것이다.</strong></p>
<p>상위권으로 올라가려면 무언가를 직접 바꿔야 했다.</p>
<p>조교님들이 제시한 방법은 여러 가지였다.</p>
<h3 id="1-더-오래-학습하기">1. 더 오래 학습하기</h3>
<p>기본 30 Epoch에서 50 또는 100 Epoch까지 늘려볼 수 있다.</p>
<p>다만 Loss가 다시 증가하기 시작하는지도 확인해야 한다.</p>
<h3 id="2-더-큰-모델-사용하기">2. 더 큰 모델 사용하기</h3>
<pre><code class="language-text">yolov8n
   ↓
yolov8s
   ↓
yolov8m</code></pre>
<p>모델의 표현력이 높아질 수 있지만 계산량과 학습 시간도 함께 증가한다.</p>
<h3 id="3-더-새로운-yolo-사용하기">3. 더 새로운 YOLO 사용하기</h3>
<pre><code class="language-text">YOLOv8
  ↓
YOLO11</code></pre>
<p><code>yolov8n.pt</code> 대신 <code>yolo11n.pt</code>, <code>yolo11s.pt</code> 같은 모델도 사용할 수 있었다.</p>
<h3 id="4-이미지-크기-변경하기">4. 이미지 크기 변경하기</h3>
<p>기본 설정은</p>
<pre><code class="language-python">imgsz=640</code></pre>
<p>이었지만, 작은 트럭이나 멀리 있는 트럭을 더 잘 잡기 위해</p>
<pre><code class="language-python">imgsz=960</code></pre>
<p>처럼 입력 해상도를 변경해 볼 수 있었다.</p>
<h3 id="5-learning-rate-조절하기">5. Learning Rate 조절하기</h3>
<pre><code class="language-text">lr0 = 0.001
lr0 = 0.0001</code></pre>
<p>처럼 학습률을 직접 조절할 수도 있었다.</p>
<h3 id="6-ensemble">6. Ensemble</h3>
<p>두 모델이 서로 다른 실수를 한다는 점을 이용해 여러 모델의 결과를 합치는 방법도 Extra Credit으로 소개됐다.</p>
<p>대표적으로</p>
<ul>
<li>NMS Ensembling</li>
<li>Weighted Boxes Fusion(WBF)</li>
</ul>
<p>등이 있었다.</p>
<hr>
<h1 id="그런데-점수만-높으면-되는-건-아니었다">그런데 점수만 높으면 되는 건 아니었다</h1>
<p>개인적으로 과제 설명에서 마음에 들었던 부분이다.</p>
<p>조교님들은 단순히</p>
<blockquote>
<p><strong>&quot;누가 가장 높은 점수를 얻었는가?&quot;</strong></p>
</blockquote>
<p>만 보는 것이 아니었다.</p>
<p>과제에는 다음과 같은 문장이 있었다.</p>
<blockquote>
<p><strong>Keep a log. Write down what you changed and what score you got. The log is worth as much as the score — on Friday we want to hear why things did or didn&#39;t work.</strong></p>
</blockquote>
<p>즉,</p>
<p><strong>무엇을 변경했고, 그 결과 점수가 어떻게 변했는지를 기록하라는 것</strong>이었다.</p>
<p>금요일에는 단순히</p>
<blockquote>
<p>&quot;점수가 이렇게 나왔습니다.&quot;</p>
</blockquote>
<p>가 아니라</p>
<blockquote>
<p>왜 좋아졌는지<br>왜 떨어졌는지</p>
</blockquote>
<p>설명할 수 있어야 했다.</p>
<p>또 하나 굉장히 중요한 규칙도 있었다.</p>
<blockquote>
<p><strong>Never train on the test set.</strong></p>
</blockquote>
<p>Test Set을 학습에 사용하는 것은 절대 금지였다.</p>
<p>이건 단순한 과제 규칙이 아니라 실제 머신러닝에서도 굉장히 중요한 원칙이라고 강조하셨다.</p>
<p>좋다.</p>
<p>규칙은 이해했다.</p>
<p>이제 진짜 성능을 올려볼 차례다.</p>
<hr>
<h1 id="그런데-yolo가-정확히-뭐지">그런데 YOLO가 정확히 뭐지?</h1>
<p>과제가 YOLO를 이용한 Object Detection이라는 것은 알겠는데,</p>
<p>막상 생각해보니 YOLO의 원리를 제대로 설명해 본 적은 없었다.</p>
<p>그래서 우선 <strong>Object Detection</strong>부터 다시 정리했다.</p>
<p>Object Detection은 단순히</p>
<pre><code class="language-text">이 이미지에는 트럭이 있다.</code></pre>
<p>라고 분류하는 문제가 아니다.</p>
<p>이미지 안에서</p>
<pre><code class="language-text">어떤 객체인가?
+
어디에 있는가?</code></pre>
<p>를 동시에 찾아야 한다.</p>
<p>즉 모델은 일반적으로</p>
<ul>
<li>객체의 클래스</li>
<li>Bounding Box</li>
<li>Confidence</li>
</ul>
<p>를 함께 출력한다.</p>
<p>이번 과제에서는 여러 종류의 객체를 찾는 것이 아니라 <strong>truck이라는 단일 클래스의 위치를 정확하게 찾는 것</strong>이 목표였다.</p>
<p>그리고 여기서 사용하는 것이 <strong>YOLO</strong>다.</p>
<p>YOLO는</p>
<blockquote>
<p><strong>You Only Look Once</strong></p>
</blockquote>
<p>의 약자다.</p>
<p>이미지 전체를 하나의 통합된 추론 과정에서 분석해 객체의 위치와 클래스를 함께 예측하는 대표적인 <strong>Single-Stage Object Detector</strong> 계열이다.</p>
<p>전통적인 Two-Stage Detector는</p>
<pre><code class="language-text">객체가 있을 만한 영역 탐색
        ↓
각 영역 분류 및 위치 보정</code></pre>
<p>처럼 여러 단계를 거치는 반면,</p>
<p>YOLO 계열은 하나의 모델 안에서 객체의 위치와 클래스를 함께 예측하는 방식이라 비교적 빠른 추론이 가능하다.</p>
<p>그래서</p>
<ul>
<li>CCTV</li>
<li>자율주행</li>
<li>로봇 비전</li>
<li>실시간 객체 탐지</li>
</ul>
<p>같은 분야에서도 많이 활용된다.</p>
<p>물론 YOLO가 항상 다른 탐지 모델보다 좋은 것은 아니다.</p>
<p>작거나 멀리 있는 객체,</p>
<p>가려진 객체,</p>
<p>화면 밖으로 일부가 잘린 객체 등은 여전히 어려운 문제다.</p>
<p>그리고 이번 과제에서 바로 <strong>작은 객체</strong>가 중요한 문제가 되었다.</p>
<hr>
<h1 id="바로-구현하지-않고-파이프라인부터-만들었다">바로 구현하지 않고 파이프라인부터 만들었다</h1>
<p>YOLO가 어떤 모델인지 이해한 다음에도 바로 학습부터 시작하지 않았다.</p>
<p>먼저 Codex와 함께 작업할 프로젝트 폴더를 만들고</p>
<ul>
<li>과제 PDF</li>
<li>데이터</li>
<li>코드</li>
<li>실험 로그</li>
<li>제출 자료</li>
</ul>
<p>를 한곳에서 관리할 수 있도록 구성했다.</p>
<p>그리고 생각했다.</p>
<blockquote>
<p><strong>&quot;하루 동안 여러 실험을 해야 하는데 매번 코드를 수정하고 결과를 직접 기록하고 있으면 너무 느리지 않을까?&quot;</strong></p>
</blockquote>
<p>그래서 먼저 <strong>실험 파이프라인을 만드는 것</strong>에 집중했다.</p>
<p>대략 다음 흐름으로 설계했다.</p>
<pre><code class="language-text">과제 요구사항 확인
      ↓
실행 환경 검사
      ↓
데이터 무결성 검사
      ↓
Baseline 학습
      ↓
Validation
      ↓
실험 설정 기록
      ↓
학습 결과 저장
      ↓
그래프 / 예측 이미지 확인
      ↓
이전 실험과 비교
      ↓
다음 실험 결정</code></pre>
<p>그리고 몇 가지 안전장치도 넣었다.</p>
<pre><code class="language-text">Ultralytics 버전 고정
Seed 고정
Deterministic 실행
Dataset Commit 기록
SHA256 저장
GPU 연결 여부 검사
best.pt 자동 저장
중단 시 last.pt에서 복구
실험 설정 및 점수 자동 기록</code></pre>
<p>특히 공식 Test는 모델 선택 과정에서 실수로 실행하지 않도록 따로 막아두었다.</p>
<p>이 구조를 먼저 만든 것이 결과적으로 꽤 큰 도움이 됐다.</p>
<p>다른 설정을 시험할 때마다 코드를 다시 만들 필요가 없었고,</p>
<p>나는</p>
<blockquote>
<p><strong>&quot;다음에는 무엇을 바꿔야 할까?&quot;</strong></p>
</blockquote>
<p>에 더 많은 시간을 사용할 수 있었다.</p>
<hr>
<h1 id="validation은-단순한-점수-확인이-아니었다">Validation은 단순한 점수 확인이 아니었다</h1>
<p>이번 프로젝트를 진행하면서 Validation을 보는 시각도 조금 달라졌다.</p>
<p>처음에는 Validation을</p>
<blockquote>
<p>&quot;학습 끝나고 점수 한 번 보는 것&quot;</p>
</blockquote>
<p>정도로 생각하기 쉬웠다.</p>
<p>하지만 실제 역할은</p>
<blockquote>
<p><strong>여러 모델 중 무엇을 선택할지 판단하는 데이터</strong></p>
</blockquote>
<p>에 가깝다.</p>
<p>그래서 다음 원칙을 잡았다.</p>
<pre><code class="language-text">Train
→ 모델 학습

Validation
→ 실험 비교 / 모델 선택

Test
→ 모델 확정 후 최종 평가</code></pre>
<p>Test 결과를 계속 보면서 설정을 바꾸기 시작하면 결국 Test에 맞춰 간접적으로 모델을 튜닝하게 된다.</p>
<p>그래서 가능하면 Test를 최대한 마지막까지 보지 않는 구조를 유지했다.</p>
<hr>
<h1 id="visualization도-적극적으로-사용했다">Visualization도 적극적으로 사용했다</h1>
<p>숫자 하나만 보고 모델을 선택하고 싶지는 않았다.</p>
<p>그래서</p>
<ul>
<li>Train / Validation Loss</li>
<li>Epoch별 mAP50</li>
<li>Epoch별 mAP50-95</li>
<li>Precision–Recall Curve</li>
<li>F1–Confidence Curve</li>
<li>실제 Bounding Box 예측 결과</li>
</ul>
<p>등도 함께 확인했다.</p>
<p>특히 마지막 Epoch의 점수 하나만 보는 것이 아니라</p>
<blockquote>
<p><strong>후반 Epoch에서 점수가 안정적으로 유지되는가?</strong></p>
</blockquote>
<p>도 중요하게 봤다.</p>
<p>좋은 점수가 한 번 튀어나왔다가 바로 무너지는 모델과,</p>
<p>조금 낮아도 계속 안정적으로 유지되는 모델은 다르게 봐야 한다고 생각했기 때문이다.</p>
<hr>
<h1 id="일단-데이터부터-보자">일단 데이터부터 보자</h1>
<p>이제 본격적으로 EDA를 시작했다.</p>
<blockquote>
<p><strong>&quot;내가 지금 학습시키려는 데이터가 대체 어떻게 생겼지?&quot;</strong></p>
</blockquote>
<p>부터 확인했다.</p>
<p>전체 데이터는 다음과 같았다.</p>
<table>
<thead>
<tr>
<th>Split</th>
<th align="right">이미지</th>
<th align="right">Bounding Box</th>
</tr>
</thead>
<tbody><tr>
<td>Train</td>
<td align="right">307</td>
<td align="right">565</td>
</tr>
<tr>
<td>Validation</td>
<td align="right">88</td>
<td align="right">165</td>
</tr>
<tr>
<td>Test</td>
<td align="right">44</td>
<td align="right">79</td>
</tr>
<tr>
<td>전체</td>
<td align="right"><strong>439</strong></td>
<td align="right"><strong>809</strong></td>
</tr>
</tbody></table>
<p>특징은 다음과 같았다.</p>
<pre><code class="language-text">Class : truck 단일 클래스
총 이미지 : 439장
Bounding Box : 809개
원본 이미지 : 640×360
이미지당 평균 트럭 : 약 1.84개</code></pre>
<p>439장.</p>
<p>생각보다 굉장히 작은 데이터셋이었다.</p>
<p>따라서 처음부터 너무 큰 모델을 사용하는 것도 조금 위험해 보였다.</p>
<p>데이터가 적을수록 모델이 데이터를 외워버리는 Overfitting 가능성이 높아지기 때문이다.</p>
<hr>
<h1 id="트럭-크기를-살펴봤더니">트럭 크기를 살펴봤더니</h1>
<p>Bounding Box 크기도 분석했다.</p>
<p>COCO 기준으로 나누었을 때 분포는 다음과 같았다.</p>
<table>
<thead>
<tr>
<th>객체 크기</th>
<th align="right">비율</th>
</tr>
</thead>
<tbody><tr>
<td>Small</td>
<td align="right">29%</td>
</tr>
<tr>
<td>Medium</td>
<td align="right">65%</td>
</tr>
<tr>
<td>Large</td>
<td align="right">6%</td>
</tr>
</tbody></table>
<p>즉,</p>
<blockquote>
<p><strong>전체 객체의 94%가 Small 또는 Medium 객체였다.</strong></p>
</blockquote>
<p>여기서 하나의 가설을 세웠다.</p>
<blockquote>
<p>작은 트럭과 먼 거리의 트럭이 대부분이라면 Epoch를 무작정 늘리는 것보다 입력 해상도를 높이는 것이 더 직접적인 개선 방법 아닐까?</p>
</blockquote>
<p>원본 설정은</p>
<pre><code class="language-text">imgsz = 640</code></pre>
<p>이었다.</p>
<p>그래서</p>
<pre><code class="language-text">640
↓
960
↓
1280</code></pre>
<p>까지 입력 크기를 늘려보기로 했다.</p>
<p>해상도를 높인다고 원본 이미지에 없던 정보가 새로 생기는 것은 아니다.</p>
<p>하지만 모델 내부에서 이미지를 처리할 때 작은 객체가 지나치게 축소되는 현상을 줄일 수 있다고 판단했다.</p>
<p>최종적으로는 <code>imgsz=1280</code>을 사용했다.</p>
<hr>
<h1 id="yolov8n에서-yolo11s로">YOLOv8n에서 YOLO11s로</h1>
<p>기본 모델은</p>
<pre><code class="language-text">YOLOv8n</code></pre>
<p>이었다.</p>
<p>Nano 모델은 굉장히 가볍고 빠르다는 장점이 있다.</p>
<p>하지만 작은 객체가 많은 현재 데이터에서는 조금 더 높은 표현력을 가진 모델을 사용해 볼 가치가 있다고 생각했다.</p>
<p>그렇다고 가장 큰 모델을 사용할 수도 없었다.</p>
<pre><code class="language-text">데이터 : 307장
GPU : Colab T4
시간 : 하루</code></pre>
<p>라는 조건이 있었기 때문이다.</p>
<p>모델이 너무 커지면 한 번의 실험 시간이 길어지고,</p>
<p>그만큼 내가 시도할 수 있는 실험 횟수가 줄어든다.</p>
<p>그래서 성능과 실험 속도 사이의 타협점으로</p>
<pre><code class="language-text">YOLO11s</code></pre>
<p>를 선택했다.</p>
<hr>
<h1 id="그런데-데이터가-뭔가-이상하다">그런데 데이터가 뭔가 이상하다</h1>
<p>EDA를 하면서 이미지를 계속 보다 보니 이상한 느낌이 들었다.</p>
<p>배경이 너무 비슷했다.</p>
<blockquote>
<p><strong>&quot;이거 혹시 같은 CCTV 영상에서 뽑은 거 아닌가?&quot;</strong></p>
</blockquote>
<p>파일명을 확인해봤다.</p>
<pre><code class="language-text">Regular-18-XX.MP4#t=OO.jpg</code></pre>
<p>실제로 이미지들은 독립적인 사진이 아니라 <strong>9개의 원본 영상에서 특정 시간의 프레임을 추출한 데이터</strong>였다.</p>
<p>그래서 영상 ID를 파싱해 Train / Validation / Test 분포를 확인했다.</p>
<p>결과는 조금 충격적이었다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th align="right">결과</th>
</tr>
</thead>
<tbody><tr>
<td>전체 원본 영상</td>
<td align="right">9</td>
</tr>
<tr>
<td>Train에 등장하는 영상</td>
<td align="right">9</td>
</tr>
<tr>
<td>Validation에 등장하는 영상</td>
<td align="right">9</td>
</tr>
<tr>
<td>Test에 등장하는 영상</td>
<td align="right">9</td>
</tr>
<tr>
<td>Train·Test가 공유하는 영상</td>
<td align="right"><strong>9 / 9</strong></td>
</tr>
</tbody></table>
<p>즉,</p>
<p><strong>모든 Test 이미지가 Train과 같은 원본 영상에서 나온 프레임이었다.</strong></p>
<hr>
<h1 id="파일은-다른데-장면은-같은데">파일은 다른데 장면은 같은데?</h1>
<p>처음에는 파일 중복 검사를 해봤다.</p>
<p>동일한 파일은 없었다.</p>
<p>그런데 문제가 이것이었다.</p>
<pre><code class="language-text">Train : 영상의 10초 프레임
Test  : 영상의 11초 프레임</code></pre>
<p>이라면 파일 자체는 다르지만 거의 같은 장면이다.</p>
<p>그래서 Test 이미지와 같은 영상에서 추출된 가장 가까운 Train 프레임의 시간 차이를 계산했다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th align="right">결과</th>
</tr>
</thead>
<tbody><tr>
<td>최소 시간 차이</td>
<td align="right">1초</td>
</tr>
<tr>
<td>중앙값</td>
<td align="right">1초</td>
</tr>
<tr>
<td>평균</td>
<td align="right">1.2초</td>
</tr>
<tr>
<td>최대</td>
<td align="right">3초</td>
</tr>
<tr>
<td>1초 이내</td>
<td align="right">39 / 44</td>
</tr>
<tr>
<td>3초 이내</td>
<td align="right"><strong>44 / 44</strong></td>
</tr>
</tbody></table>
<p>Test 이미지 44장 전부가 Train 이미지와 최대 3초 이내의 프레임이었다.</p>
<p>여기서 생각했다.</p>
<blockquote>
<p><strong>&quot;이 정도면 새로운 장면을 맞히는 것보다 익숙한 배경을 다시 보는 것에 가까운 것 아닌가?&quot;</strong></p>
</blockquote>
<hr>
<h1 id="phash까지-확인해봤다">pHash까지 확인해봤다</h1>
<p>그래서 이미지 자체가 얼마나 비슷한지도 확인해봤다.</p>
<p>일반적인 파일 Hash는 픽셀이 조금만 달라져도 완전히 다른 값이 나오기 때문에 이런 문제를 찾기에는 적절하지 않았다.</p>
<p>대신 <strong>pHash(Perceptual Hash)</strong> 를 이용했다.</p>
<p>pHash는 이미지의 시각적인 특징을 압축해서 표현하고, 두 값의 Hamming Distance가 작을수록 두 이미지가 비슷하다고 판단하는 방식이다.</p>
<p>결과는 다음과 같았다.</p>
<table>
<thead>
<tr>
<th>측정값</th>
<th align="right">결과</th>
</tr>
</thead>
<tbody><tr>
<td>pHash 거리 최소</td>
<td align="right">0</td>
</tr>
<tr>
<td>중앙값</td>
<td align="right">1</td>
</tr>
<tr>
<td>평균</td>
<td align="right">2.2</td>
</tr>
<tr>
<td>최대</td>
<td align="right">13</td>
</tr>
<tr>
<td>거리 10 이하</td>
<td align="right"><strong>43 / 44</strong></td>
</tr>
</tbody></table>
<p>즉,</p>
<p>Test 44장 중 <strong>43장이 Train 이미지와 시각적으로 굉장히 비슷한 장면</strong>이었다.</p>
<p>여기서 중요한 점이 있다.</p>
<p>내가 Test 데이터를 Train에 넣은 것이 아니다.</p>
<p><strong>교수님이 제공한 공식 Split 자체가 프레임 단위로 구성되어 있었던 것</strong>이다.</p>
<p>그리고 모든 학생이 같은 Split으로 평가받기 때문에 <strong>수업 내 순위 비교 자체는 공정했다.</strong></p>
<p>하지만 공식 Test 점수를</p>
<blockquote>
<p>&quot;완전히 새로운 카메라와 도로에서도 이 정도 성능이 나온다.&quot;</p>
</blockquote>
<p>라고 해석하는 것은 무리가 있다고 판단했다.</p>
<hr>
<h1 id="그래서-validation을-새로-만들었다">그래서 Validation을 새로 만들었다</h1>
<p>공식 Test는 과제의 평가 기준이기 때문에 당연히 그대로 두었다.</p>
<p>대신 모델 선택을 위한 Validation을 하나 더 만들었다.</p>
<p>기존 Train + Validation 395장을 원본 영상 단위로 다시 분리했다.</p>
<pre><code class="language-text">새로운 Train
→ 7개 영상 / 307장

새로운 Validation
→ 완전히 제외한 2개 영상 / 88장

공식 Test
→ 모델 선택에 사용하지 않음</code></pre>
<p>이것을 <strong>Clip-held-out Validation</strong>으로 사용했다.</p>
<p>이제는 같은 영상의 프레임이 Train과 Validation에 동시에 들어가지 않는다.</p>
<p>완벽한 외부 검증은 아니다.</p>
<p>같은 종류의 CCTV 환경에서 촬영된 데이터이기 때문에 새로운 날씨나 새로운 도로, 새로운 카메라까지 일반화되는지는 알 수 없다.</p>
<p>그래도 기존 프레임 단위 Validation보다 훨씬 엄격한 조건에서 모델을 비교할 수 있었다.</p>
<hr>
<h1 id="처음-실험-설계도-완벽하지-않았다">처음 실험 설계도 완벽하지 않았다</h1>
<p>초기에는 다음과 같이 A부터 F까지 실험을 미리 만들어 놓았다.</p>
<pre><code class="language-text">A
YOLOv8n / 640 / 30 Epoch

↓

B
+ imgsz 960

↓

C
+ YOLO11s

↓

D
+ Rotation 10°

↓

E
+ Epoch 100

↓

F
+ Early Stopping</code></pre>
<p>각 단계에서는 바로 이전 실험에서 하나의 변수만 바뀐다.</p>
<p>그래서 처음에는</p>
<blockquote>
<p>&quot;한 번에 하나만 바꾸니까 잘 설계했네?&quot;</p>
</blockquote>
<p>라고 생각했다.</p>
<p>그런데 다시 보니 문제가 있었다.</p>
<p>예를 들어 B가 나쁜 설정이어도</p>
<pre><code class="language-text">B → C → D → E → F</code></pre>
<p>로 계속 물려받게 된다.</p>
<p>즉, <strong>앞 단계의 잘못된 선택이 이후 실험 전체에 영향을 줄 수 있다.</strong></p>
<p>또 설정을 어떤 순서로 적용했는가에 따라서도 결과가 달라질 수 있었다.</p>
<p>AI와 이 부분을 검토하면서 실험 방식을 바꿨다.</p>
<pre><code class="language-text">현재 가장 좋은 설정
        ↓
변수 하나 변경
        ↓
Validation
        ↓
좋아짐?
   ↙         ↘
 YES         NO
 유지        원상복귀
   ↓           ↓
다음 가설 설계</code></pre>
<p>즉,</p>
<p><strong>미리 A~F를 모두 정해두는 방식에서 결과를 보고 다음 실험을 결정하는 방식으로 바꿨다.</strong></p>
<p>개인적으로 이게 이번 과제에서 꽤 중요한 변화였다고 생각한다.</p>
<p>처음부터 완벽한 실험 설계를 한 것은 아니었다.</p>
<p>하지만 내 설계의 문제점을 발견하고 다시 수정했다.</p>
<hr>
<h1 id="이제-제대로-실험해보자">이제 제대로 실험해보자</h1>
<p>Clip-held-out Validation을 고정한 후부터는 모델과 데이터를 같은 조건으로 유지하면서 한 번에 하나의 변수만 바꿨다.</p>
<p>대표적으로 I부터 L까지의 결과는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>실험</th>
<th align="right">Epoch</th>
<th align="right">imgsz</th>
<th>Optimizer</th>
<th align="right">lr0</th>
<th align="right">mAP50</th>
<th align="right">mAP50-95</th>
<th align="right">Precision</th>
<th align="right">Recall</th>
</tr>
</thead>
<tbody><tr>
<td>I</td>
<td align="right">30</td>
<td align="right">1280</td>
<td>Auto→AdamW</td>
<td align="right">약 0.002</td>
<td align="right">0.9681</td>
<td align="right">0.8299</td>
<td align="right">0.9438</td>
<td align="right">0.8842</td>
</tr>
<tr>
<td>J</td>
<td align="right">30</td>
<td align="right">1280</td>
<td>AdamW</td>
<td align="right">0.001</td>
<td align="right">0.9705</td>
<td align="right">0.8461</td>
<td align="right">0.9485</td>
<td align="right">0.8889</td>
</tr>
<tr>
<td>K</td>
<td align="right">50</td>
<td align="right">1280</td>
<td>AdamW</td>
<td align="right">0.001</td>
<td align="right">0.9567</td>
<td align="right">0.8274</td>
<td align="right">0.8994</td>
<td align="right">0.8947</td>
</tr>
<tr>
<td>L</td>
<td align="right">30</td>
<td align="right">1280</td>
<td>AdamW</td>
<td align="right">0.0001</td>
<td align="right">0.9689</td>
<td align="right"><strong>0.8552</strong></td>
<td align="right">0.9151</td>
<td align="right"><strong>0.9006</strong></td>
</tr>
</tbody></table>
<p>여기부터 하나씩 결과를 해석했다.</p>
<hr>
<h1 id="learning-rate를-낮춰보자">Learning Rate를 낮춰보자</h1>
<p>실험 I에서는 <code>optimizer=auto</code>를 사용했고 내부적으로 AdamW와 약 <code>0.002</code>의 Learning Rate가 선택됐다.</p>
<p>그런데 학습 과정 중 한 번 Validation 성능이 크게 무너지는 현상이 있었다.</p>
<p>그래서</p>
<blockquote>
<p><strong>&quot;현재 데이터셋에 비해 학습률이 조금 큰 것 아닐까?&quot;</strong></p>
</blockquote>
<p>라는 가설을 세웠다.</p>
<p>AdamW를 명시적으로 사용하고</p>
<pre><code class="language-text">lr0
0.002
  ↓
0.001</code></pre>
<p>로 낮춘 J를 실행했다.</p>
<p>결과는</p>
<pre><code class="language-text">mAP50-95
0.8299 → 0.8461

Precision
0.9438 → 0.9485

Recall
0.8842 → 0.8889</code></pre>
<p>였다.</p>
<p>특히 마지막 5 Epoch에서 mAP50-95의 표준편차도</p>
<pre><code class="language-text">I : 0.0153
J : 0.0025</code></pre>
<p>로 크게 줄었다.</p>
<p>단순히 최종 점수뿐만 아니라 <strong>학습 후반의 안정성도 좋아졌다.</strong></p>
<p>그래서 J 설정을 유지하기로 했다.</p>
<hr>
<h1 id="오래-학습하면-더-좋아지지-않을까">오래 학습하면 더 좋아지지 않을까?</h1>
<p>J의 마지막 구간에서 성능이 계속 올라가는 모습을 보고 이런 생각이 들었다.</p>
<blockquote>
<p><strong>&quot;그럼 30 Epoch보다 50 Epoch까지 돌리면 더 좋아지겠는데?&quot;</strong></p>
</blockquote>
<p>그래서 다른 조건은 그대로 두고</p>
<pre><code class="language-text">Epoch
30 → 50</code></pre>
<p>만 변경한 K를 실행했다.</p>
<p>그런데 결과는 예상과 달랐다.</p>
<table>
<thead>
<tr>
<th>지표</th>
<th align="right">30 Epoch</th>
<th align="right">50 Epoch</th>
</tr>
</thead>
<tbody><tr>
<td>mAP50</td>
<td align="right">0.9705</td>
<td align="right">0.9567</td>
</tr>
<tr>
<td>mAP50-95</td>
<td align="right">0.8461</td>
<td align="right">0.8274</td>
</tr>
<tr>
<td>Precision</td>
<td align="right">0.9485</td>
<td align="right">0.8994</td>
</tr>
<tr>
<td>Recall</td>
<td align="right">0.8889</td>
<td align="right">0.8947</td>
</tr>
</tbody></table>
<p>Recall은 아주 조금 올라갔다.</p>
<p>하지만 Precision과 mAP는 오히려 떨어졌다.</p>
<p>즉,</p>
<blockquote>
<p><strong>이번 조건에서는 오래 학습한다고 무조건 성능이 좋아지는 것이 아니었다.</strong></p>
</blockquote>
<p>그래서 K는 과감하게 버리고 다시 30 Epoch로 돌아갔다.</p>
<p>실패한 실험이었지만 오히려 꽤 중요한 결과였다.</p>
<p>내가 가지고 있던</p>
<blockquote>
<p>&quot;더 오래 학습하면 더 좋겠지.&quot;</p>
</blockquote>
<p>라는 가설 하나를 직접 기각할 수 있었기 때문이다.</p>
<hr>
<h1 id="learning-rate를-한-번-더-낮춰보자">Learning Rate를 한 번 더 낮춰보자</h1>
<p>다음으로 Epoch는 30으로 유지하고</p>
<p>Learning Rate만</p>
<pre><code class="language-text">0.001
  ↓
0.0001</code></pre>
<p>로 낮춘 L을 실행했다.</p>
<p>결과는 조금 재미있었다.</p>
<table>
<thead>
<tr>
<th>지표</th>
<th align="right">J</th>
<th align="right">L</th>
</tr>
</thead>
<tbody><tr>
<td>mAP50</td>
<td align="right"><strong>0.9705</strong></td>
<td align="right">0.9689</td>
</tr>
<tr>
<td>mAP50-95</td>
<td align="right">0.8461</td>
<td align="right"><strong>0.8552</strong></td>
</tr>
<tr>
<td>Precision</td>
<td align="right"><strong>0.9485</strong></td>
<td align="right">0.9151</td>
</tr>
<tr>
<td>Recall</td>
<td align="right">0.8889</td>
<td align="right"><strong>0.9006</strong></td>
</tr>
</tbody></table>
<p>모든 점수가 좋아진 것은 아니었다.</p>
<p>mAP50과 Precision은 조금 떨어졌다.</p>
<p>반면</p>
<ul>
<li>mAP50-95</li>
<li>Recall</li>
</ul>
<p>은 올라갔다.</p>
<p>여기서 고민이 생겼다.</p>
<blockquote>
<p><strong>&quot;그럼 어떤 모델이 더 좋은 거지?&quot;</strong></p>
</blockquote>
<p>mAP50-95는 IoU 0.5 하나만 보는 것이 아니라 0.5부터 0.95까지 여러 기준으로 Bounding Box 품질을 평가한다.</p>
<p>그래서 작은 트럭의 위치를 좀 더 정확하게 잡는 능력을 보고 싶었던 이번 과제에서는 mAP50-95의 상승도 의미가 있다고 판단했다.</p>
<p>그리고 한 번 잘 나온 결과만으로 L을 선택하고 싶지는 않았다.</p>
<hr>
<h1 id="혹시-그냥-운이-좋았던-거-아닐까">혹시 그냥 운이 좋았던 거 아닐까?</h1>
<p>딥러닝 학습에는 Random Seed에 따라 결과가 조금씩 달라질 수 있다.</p>
<p>그래서</p>
<pre><code class="language-text">Seed 7
Seed 42
Seed 123</code></pre>
<p>세 가지 환경에서 J와 L을 다시 학습했다.</p>
<p>결과는 다음과 같았다.</p>
<table>
<thead>
<tr>
<th align="right">Seed</th>
<th align="right">J mAP50-95</th>
<th align="right">L mAP50-95</th>
</tr>
</thead>
<tbody><tr>
<td align="right">7</td>
<td align="right">0.845389</td>
<td align="right"><strong>0.850525</strong></td>
</tr>
<tr>
<td align="right">42</td>
<td align="right">0.846065</td>
<td align="right"><strong>0.855159</strong></td>
</tr>
<tr>
<td align="right">123</td>
<td align="right">0.833602</td>
<td align="right"><strong>0.840870</strong></td>
</tr>
</tbody></table>
<p>세 Seed 모두에서 L이 더 높은 mAP50-95를 기록했다.</p>
<p>평균을 내보면</p>
<pre><code class="language-text">J : 0.841685
L : 0.848851</code></pre>
<p>였다.</p>
<p>물론 Seed 세 개만 가지고 통계적으로 완벽하게 우월하다고 말할 수는 없다.</p>
<p>하지만 적어도</p>
<blockquote>
<p><strong>&quot;Seed 42에서 우연히 한 번 잘 나온 것&quot;</strong></p>
</blockquote>
<p>은 아니라는 근거는 얻을 수 있었다.</p>
<p>그래서 최종적으로 L을 선택했다.</p>
<hr>
<h1 id="그런데-문제가-하나-있었다">그런데 문제가 하나 있었다</h1>
<p>여기까지 집중해서 실험하다 보니 어느새 수업이 끝났다.</p>
<p>그리고 나에게 주어진 시간은 단 하루.</p>
<p>마음 같아서는 숙소에 들어가서 밤새 이것만 하고 싶었다.</p>
<p>그런데...</p>
<blockquote>
<p><strong>내가 LA까지 와서 하루 종일 Colab만 보고 있을 수는 없잖아...?</strong></p>
</blockquote>
<p>굉장히 어려운 선택이었다.</p>
<pre><code class="language-text">YOLO 성능 개선
      VS
LA 관광</code></pre>
<p>둘 다 포기하고 싶지 않았다.</p>
<p>그래서 생각했다.</p>
<blockquote>
<p><strong>&quot;둘 다 하면 안 되나?&quot;</strong></p>
</blockquote>
<hr>
<h1 id="모델은-학습시키고-나는-la로-간다">모델은 학습시키고, 나는 LA로 간다</h1>
<p>마침 Codex를 모바일에서도 확인하면서 작업을 이어갈 수 있는 방법이 있었다.</p>
<p>그래서 학습과 실험은 계속 돌려놓고,</p>
<p>밖에서는 휴대폰으로 진행 상황과 결과를 확인하는 방식으로 작업하기로 했다.</p>
<pre><code class="language-text">실험 설정
    ↓
모델 학습 시작
    ↓
나는 LA 관광
    ↓
학습 완료
    ↓
휴대폰으로 결과 확인
    ↓
결과 분석
    ↓
다음 실험 결정
    ↓
다시 학습</code></pre>
<p>완벽하다.</p>
<p>그래서 나는...</p>
<p><strong>리틀 도쿄로 갔다.</strong></p>
<hr>
<h1 id="근데-이제-la-관광을-곁들인">근데 이제 LA 관광을 곁들인(?)</h1>
<p>리틀 도쿄를 돌아다니다 발견한 오타니 벽화.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/fe01d327-d5dd-4adb-a936-00caefc60e2e/image.jpeg" alt=""></p>
<p>역시 LA라 그런지 오타니의 존재감이 엄청났다.</p>
<p>그리고 리틀 도쿄의 상징 중 하나인 야구라(櫓).</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c8b147eb-7447-4d41-9f98-4875752a037b/image.jpeg" alt=""></p>
<p>당연히 일본 음식도 먹었다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/94b27eb3-4573-4a1a-a14e-c92f52f52ec2/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/e1c74732-6be3-45b5-b1e4-2523b4484391/image.jpeg" alt=""></p>
<p>그리고 맥주와 도쿠리까지.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/377c065b-bab2-4dcf-b11f-d55b1fa3d0dc/image.jpeg" alt=""></p>
<p>하지만 놀러 나왔다고 해서 모델을 완전히 잊은 것은 아니었다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/bb56e78e-d294-49b0-900e-4f011382f6d3/image.jpeg" alt=""></p>
<p><strong>밖에서도 계속 결과 확인 중...</strong></p>
<p>어떤 작업은 무려 <strong>1시간 18분</strong> 동안 진행됐다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/217fa621-4ac3-4d8f-86b8-76d39ef42108/image.PNG" alt=""></p>
<p>(토큰 녹는다 녹아...)</p>
<p>학습이 끝나면 결과를 확인하고,</p>
<p>이전 실험과 비교한 뒤,</p>
<blockquote>
<p>&quot;그러면 다음에는 이 값을 바꿔볼까?&quot;</p>
</blockquote>
<p>라고 생각하며 다음 실험을 다시 돌렸다.</p>
<p>이렇게 밖에서도 계속 실험 흐름을 이어갈 수 있었던 것이 결과적으로 꽤 도움이 됐던 것 같다.</p>
<hr>
<h1 id="모델-돌려놓고-현지-밴드-보러-가기">모델 돌려놓고 현지 밴드 보러 가기</h1>
<p>리틀 도쿄를 구경한 뒤에는 근처에 있는 현지 밴드 공연을 하는 바에도 갔다.</p>
<p>물론...</p>
<p><strong>이때도 모델은 열심히 학습 중이었다.</strong></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/579c00bb-b270-473c-8117-7886e3235df1/image.jpeg" alt=""></p>
<p>맥주 한 잔.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/f2993240-a954-46eb-a038-98bdbfafa71b/image.jpeg" alt=""></p>
<p>전체적인 분위기는 이런 느낌이었다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/5b9f25e6-488e-45a0-98a0-2e202f8b9995/image.jpeg" alt=""></p>
<p>공연을 보다가 중간중간 휴대폰을 확인하면</p>
<p>모델은 열심히 트럭을 찾고 있었다.</p>
<pre><code class="language-text">나 : 🍺🎸
YOLO : 🚚🚚🚚🚚🚚</code></pre>
<p>각자의 LA를 즐기는 중이었다.</p>
<hr>
<h1 id="최종-모델을-결정하다">최종 모델을 결정하다</h1>
<p>결국 최종적으로 선택한 설정은 다음과 같았다.</p>
<pre><code class="language-yaml">model: yolo11s.pt
epochs: 30
imgsz: 1280
batch: 8
optimizer: AdamW
lr0: 0.0001
momentum: 0.9
degrees: 0
close_mosaic: 10
seed: 42
ultralytics: 8.4.112</code></pre>
<p>최종 모델을 확정한 뒤에는 설정을 더 이상 변경하지 않았다.</p>
<p>그리고 그제야 공식 Test를 평가했다.</p>
<hr>
<h1 id="최종-test-결과">최종 Test 결과</h1>
<p>공식 Test 44장에 대한 결과는 다음과 같았다.</p>
<table>
<thead>
<tr>
<th>지표</th>
<th align="right">결과</th>
</tr>
</thead>
<tbody><tr>
<td>mAP50</td>
<td align="right"><strong>0.989180</strong></td>
</tr>
<tr>
<td>mAP50-95</td>
<td align="right"><strong>0.866877</strong></td>
</tr>
<tr>
<td>Precision</td>
<td align="right"><strong>0.946595</strong></td>
</tr>
<tr>
<td>Recall</td>
<td align="right"><strong>0.962025</strong></td>
</tr>
</tbody></table>
<p>반올림하면</p>
<pre><code class="language-text">mAP50     : 0.9892
mAP50-95  : 0.8669
Precision : 0.9466
Recall    : 0.9620</code></pre>
<p>이다.</p>
<p>특히 과제의 순위 기준이었던 <strong>mAP50은 98.92%</strong>까지 올라갔다.</p>
<hr>
<h1 id="실제-탐지-결과도-확인해봤다">실제 탐지 결과도 확인해봤다</h1>
<p>숫자만 보는 것보다 실제 Bounding Box도 직접 확인했다.</p>
<p>가까이 있고 가림이 적은 트럭은 confidence 약 <code>0.97</code> 수준으로 안정적으로 탐지했다.</p>
<p>또한 고해상도 입력을 사용하면서 상대적으로 멀리 있는 작은 트럭도 꽤 잘 탐지하는 모습을 확인할 수 있었다.</p>
<p>하지만 실패 사례도 있었다.</p>
<p>특히 화면 오른쪽 끝에서 트럭의 상당 부분이 잘려 있는 경우,</p>
<p>객체 자체는 굉장히 컸지만 탐지하지 못하는 사례가 있었다.</p>
<p>즉,</p>
<blockquote>
<p><strong>객체가 크다고 항상 쉬운 것은 아니었다.</strong></p>
</blockquote>
<p>화면 밖으로 많이 잘려 있거나,</p>
<p>가림이 심하거나,</p>
<p>학습 데이터에서 자주 보지 못한 형태라면</p>
<p>큰 객체도 놓칠 수 있었다.</p>
<p>다음에 데이터를 추가한다면</p>
<ul>
<li>화면 경계의 Partial Object</li>
<li>가려진 트럭</li>
<li>다른 카메라</li>
<li>다른 날씨</li>
<li>다른 도로</li>
</ul>
<p>등을 더 많이 포함시키는 것이 필요할 것 같았다.</p>
<hr>
<h1 id="그리고-다음-날-결과-발표">그리고 다음 날, 결과 발표</h1>
<p>그렇게 하루 동안 여러 실험을 진행하고 다음 날 수업에 들어갔다.</p>
<p>드디어 결과 발표 시간.</p>
<p>학생들의 성능 점수가 하나씩 공개됐고,</p>
<p>결과는...</p>
<h1 id="내가-1등이었다">내가 1등이었다.</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/7569a67f-dbb4-48cc-931e-2e2180a60405/image.jpeg" alt=""></p>
<p>그래서 앞으로 나가 어떤 방식으로 모델의 성능을 개선했는지 발표하게 되었다.</p>
<p>그런데 사실 발표에서는 꽤 간단하게 이야기했다.</p>
<blockquote>
<p><strong>&quot;AI를 적극적으로 활용해서 자료를 검토하고, 여러 설정을 바꿔가며 실험했습니다.&quot;</strong></p>
</blockquote>
<p>조금 더 솔직하게 말하면</p>
<blockquote>
<p><strong>&quot;AI 돌려서 했습니다.&quot;</strong></p>
</blockquote>
<p>정도의 느낌이었다.</p>
<p>실제로 Codex를 굉장히 적극적으로 활용한 것도 맞다.</p>
<p>이번 과제에서 AI는</p>
<ul>
<li>과제 자료 검토</li>
<li>반복 코드 작성</li>
<li>실험 로그 자동화</li>
<li>오류 원인 탐색</li>
<li>분석 코드 작성 보조</li>
<li>학습 상태 확인</li>
<li>결과 정리</li>
<li>다음 실험 후보 제안</li>
</ul>
<p>등의 작업에서 꽤 많은 도움을 줬다.</p>
<p>그래서 발표만 들으면</p>
<blockquote>
<p><strong>&quot;그냥 AI한테 성능 높여달라고 해서 1등한 거 아닌가?&quot;</strong></p>
</blockquote>
<p>라고 생각할 수도 있을 것 같다.</p>
<p>그런데 당시 실험 기록을 다시 정리해보니 실제 과정은 조금 달랐다.</p>
<pre><code class="language-text">과제 요구사항 분석
        ↓
Object Detection / YOLO 이해
        ↓
실험 파이프라인 설계
        ↓
EDA
        ↓
Small·Medium 객체 94% 확인
        ↓
입력 해상도 조정
        ↓
YOLOv8n → YOLO11s
        ↓
데이터 Split의 이상한 점 발견
        ↓
원본 영상 / 프레임 구조 분석
        ↓
pHash를 통한 유사성 확인
        ↓
Clip-held-out Validation 구축
        ↓
Learning Rate / Epoch 실험
        ↓
실패한 실험 기각
        ↓
Multi-Seed 재현성 검증
        ↓
최종 모델 선정
        ↓
공식 Test 평가</code></pre>
<p>물론 이 과정에서도 AI의 도움을 계속 받았다.</p>
<p>하지만 AI가 제안한 내용을 그대로 실행하고 가장 높은 숫자 하나를 고른 것은 아니었다.</p>
<p>오히려 내가 계속 고민했던 것은 이런 질문들이었다.</p>
<blockquote>
<p><strong>&quot;왜 이 실험을 해야 하지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;이 Validation 점수를 믿어도 되는 걸까?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;왜 Train과 Test의 배경이 이렇게 비슷하지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;50 Epoch까지 학습했는데 왜 오히려 성능이 떨어졌지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;이번 성능 개선이 우연히 잘 나온 한 번의 결과는 아닐까?&quot;</strong></p>
</blockquote>
<p>AI가 답이나 실험 후보를 제안하면 실제 데이터와 Validation 결과를 확인하고,</p>
<p>그 결과를 바탕으로 다음 실험을 유지할지, 버릴지 결정했다.</p>
<p>초기에 만들었던 A~F 누적 실험 구조도 AI와 함께 다시 검토하다가 문제가 있다는 것을 발견했다.</p>
<p>앞 단계에서 좋지 않은 설정을 선택해도 다음 실험이 그대로 그 설정을 물려받는 구조였기 때문이다.</p>
<p>그래서 이후에는</p>
<pre><code class="language-text">현재 가장 좋은 설정
        ↓
변수 하나 변경
        ↓
Validation
        ↓
성능 개선?
   ↙           ↘
 YES           NO
 유지          되돌림
   ↓             ↓
     다음 가설 설계</code></pre>
<p>처럼 결과를 확인한 뒤 다음 실험을 결정하는 방식으로 바꿨다.</p>
<p>50 Epoch 실험 역시 마찬가지였다.</p>
<p>처음에는</p>
<blockquote>
<p>&quot;더 오래 학습하면 더 좋아지겠지.&quot;</p>
</blockquote>
<p>라고 생각했지만 실제 Validation에서는 mAP가 떨어졌다.</p>
<p>그래서 미련 없이 기각하고 다시 30 Epoch로 돌아갔다.</p>
<p>결국 이번 과제를 하면서 느낀 것은</p>
<blockquote>
<p><strong>AI를 잘 활용하는 것과 AI에게 판단을 맡기는 것은 전혀 다른 문제다.</strong></p>
</blockquote>
<p>라는 점이었다.</p>
<p>AI는 코드를 훨씬 빨리 작성할 수 있었고,</p>
<p>반복 작업도 빠르게 처리할 수 있었다.</p>
<p>그 덕분에 나는 직접 코드를 반복해서 작성하는 시간 대신</p>
<p><strong>데이터를 보고, 결과를 해석하고, 다음 실험을 고민하는 데 더 많은 시간을 사용할 수 있었다.</strong></p>
<p>돌이켜보면 이번 프로젝트에서 Codex는</p>
<p><strong>Pair Programmer이자 실험 보조 도구</strong></p>
<p>에 가까웠던 것 같다.</p>
<p>전날까지만 해도</p>
<p>리틀 도쿄에서 스시를 먹고,</p>
<p>맥주를 마시고,</p>
<p>밴드 공연을 보면서</p>
<p>휴대폰으로 YOLO 학습 결과를 확인하고 있었는데,</p>
<p>다음 날에는 앞에 나가 1등 발표를 하고 있으니 기분이 조금 묘했다.</p>
<p>그리고 대망의 상품 수여식.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/d4a0b933-6e64-42dc-bd94-a70cc0b73403/image.jpeg" alt=""></p>
<p>상품은...</p>
<p><strong>교수님의 특급 볼펜.</strong></p>
<p>잘 간직하겠습니다...</p>
<h1 id="1등보다-더-기억에-남은-것">1등보다 더 기억에 남은 것</h1>
<p>최종적으로 과제에서 1등을 했다.</p>
<p>물론 좋았다.</p>
<p>아주 좋았다.</p>
<p>하지만 이번 프로젝트를 다시 정리하면서 가장 기억에 남는 것은 <code>mAP50=0.9892</code>라는 숫자보다 그 과정이었다.</p>
<p>처음에는 단순하게 생각했다.</p>
<pre><code class="language-text">큰 모델 쓰고
Epoch 늘리고
해상도 높이면

성능 올라가겠지?</code></pre>
<p>그런데 실제로는 그렇지 않았다.</p>
<p>50 Epoch까지 학습했더니 오히려 성능이 떨어졌다.</p>
<p>mAP50이 조금 떨어지더라도 mAP50-95와 Recall은 올라가는 경우도 있었다.</p>
<p>같은 설정도 Seed에 따라 결과가 달랐다.</p>
<p>그리고 가장 높은 공식 점수조차 데이터의 분할 방식 때문에 그대로 일반화 성능이라고 믿을 수는 없었다.</p>
<p>결국 이번 과제에서 가장 크게 배운 것은</p>
<blockquote>
<p><strong>좋은 점수가 좋은 모델을 보장하지는 않는다.</strong></p>
</blockquote>
<p>는 것이었다.</p>
<p>왜 그 점수가 나왔는지,</p>
<p>데이터가 어떻게 구성되어 있는지,</p>
<p>어떤 Validation을 사용했는지,</p>
<p>같은 결과가 다시 나오는지까지 설명할 수 있어야 그 결과를 조금 더 믿을 수 있었다.</p>
<p>그래서 이번 과제를 통해 가장 많이 배운 것은 YOLO 자체보다 오히려 다음과 같은 실험 과정이었다.</p>
<pre><code class="language-text">데이터를 먼저 본다.

↓

가설을 세운다.

↓

하나의 변수만 변경한다.

↓

결과를 확인한다.

↓

가설이 틀리면 과감하게 버린다.

↓

다음 실험을 설계한다.

↓

다른 조건에서도 다시 확인한다.</code></pre>
<p>특히 실패한 실험 역시</p>
<p><strong>실패한 결과가 아니라 다음 선택의 근거</strong></p>
<p>가 될 수 있다는 점이 인상 깊었다.</p>
<p>그리고 AI를 쓰는 방식에 대해서도 다시 생각하게 됐다.</p>
<p>AI는 반복 작업을 빠르게 처리해 줬고,</p>
<p>나는 그 시간을 이용해</p>
<p><strong>데이터를 보고, 결과를 해석하고, 다음 실험을 고민하는 데 더 집중할 수 있었다.</strong></p>
<p>결국 중요한 것은 AI를 쓰느냐 안 쓰느냐가 아니라,</p>
<blockquote>
<p><strong>AI가 만든 결과를 어디까지 믿고, 무엇을 다시 검증할 것인가</strong></p>
</blockquote>
<p>였던 것 같다.</p>
<p>그리고 무엇보다...</p>
<p>LA까지 와서 AI 과제만 하고 있지 않으면서도 결국 1등까지 했다.</p>
<pre><code class="language-text">스시 🍣

맥주 🍺

밴드 공연 🎸

그리고 YOLO 🚚</code></pre>
<p>나름 완벽한 하루였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[주식 자동매매 프로그램 만들기 (1) - 요구사항 분석과 MVP 범위 정하기]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%A3%BC%EC%8B%9D-%EC%9E%90%EB%8F%99%EB%A7%A4%EB%A7%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@daehyun_lee/%EC%A3%BC%EC%8B%9D-%EC%9E%90%EB%8F%99%EB%A7%A4%EB%A7%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Fri, 07 Aug 2026 07:52:13 GMT</pubDate>
            <description><![CDATA[<h1 id="주식-자동매매-프로그램-만들기-1---요구사항-분석과-mvp-범위-정하기">주식 자동매매 프로그램 만들기 (1) - 요구사항 분석과 MVP 범위 정하기</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/b119d552-c8b2-4678-8257-ff68a7cb7131/image.jpg" alt=""></p>
<p>올해 초부터 계속 고민하던 개인 프로젝트가 하나 있었다.</p>
<p>바로 <strong>주식 자동매매 프로그램</strong>이다.</p>
<p>평소 증권 도메인에 관심이 많기도 했고, 자동매매 시스템이 실제로 어떤 구조로 동작하는지도 궁금했다.</p>
<p>특히 최근에는 금융권에서도 AI를 활용해 데이터를 분석하거나 투자 의사결정을 보조하는 사례가 늘어나고 있다.</p>
<p>그래서 단순히 증권사 API를 호출해서 주문을 보내는 프로그램보다는, <strong>거래 시스템과 투자 판단 시스템을 분리해서 만들어보면 어떨까?</strong>라는 생각이 들었다.</p>
<p>대략적인 구조는 다음과 같다.</p>
<pre><code class="language-text">                    주식 자동매매 시스템

        ┌─────────────────────────────┐
        │   Python 분석 / 판단 시스템   │
        │                             │
        │  시장 데이터 분석             │
        │       ↓                     │
        │  매수 / 매도 전략             │
        │       ↓                     │
        │  BUY / SELL / HOLD 판단      │
        └──────────────┬──────────────┘
                       │
                       │ 거래 판단 결과
                       ↓
        ┌─────────────────────────────┐
        │   Java / Spring 거래 시스템   │
        │                             │
        │  인증 / 계좌 / 주문 / 체결     │
        │       ↓                     │
        │       증권사 API              │
        └─────────────────────────────┘</code></pre>
<p>증권사 API와 통신하고 계좌, 주문 등의 도메인 객체를 관리하는 부분은 <strong>Java / Spring</strong>으로 구현하고,</p>
<p>실질적으로 데이터를 분석하여 매수와 매도를 판단하는 부분은 <strong>Python</strong>을 활용할 생각이다.</p>
<p>처음에는 간단한 규칙 기반 전략부터 시작하고, 이후 데이터를 충분히 확보하면 머신러닝이나 AI 모델을 적용해 판단 로직을 발전시키는 것이 최종적인 목표다.</p>
<hr>
<h2 id="그런데-무엇부터-만들어야-할까">그런데 무엇부터 만들어야 할까?</h2>
<p>아이디어 자체는 올해 초부터 가지고 있었다.</p>
<p>하지만 학교도 다니고 여러 활동에도 참여하다 보니 정작 증권 도메인과 시스템 자체에 대해서는 제대로 공부하지 못했다.</p>
<p>그래서 이번 여름방학에는 미뤄두었던 이 프로젝트를 본격적으로 진행해 보려고 한다.</p>
<p>다만 이번 프로젝트에서는 곧바로 코드를 작성하고 싶지는 않았다.</p>
<p>학교에서 소프트웨어 공학과 개발론을 배우면서 항상 들었던 이야기가 있다.</p>
<blockquote>
<p><strong>개발은 코드를 작성하는 것부터 시작하는 것이 아니다.</strong></p>
</blockquote>
<p>무엇을 만들어야 하는지 정의하고, 필요한 기능을 분석하고, 구조를 설계한 뒤에야 실제 구현이 시작된다.</p>
<p>개인 프로젝트를 할 때는 아이디어가 떠오르면 바로 IDE부터 켜는 경우가 많았는데, 이번에는 학교에서 배운 개발 프로세스를 실제 개인 프로젝트에 적용해 보고 싶었다.</p>
<p>그래서 다음과 같은 순서로 프로젝트를 진행해 볼 생각이다.</p>
<pre><code class="language-text">요구사항 분석
      ↓
기능 목록 정리
      ↓
도메인 분석
      ↓
화면 / API / 데이터 구조 설계
      ↓
아키텍처 설계
      ↓
MVP 범위 결정
      ↓
개발
      ↓
테스트
      ↓
배포 / 운영
      ↓
개선 반복</code></pre>
<p>물론 실제 개발에서는 이 과정이 항상 일직선으로 흘러가지는 않을 것이다.</p>
<p>개발하면서 새로운 요구사항이 생길 수도 있고, 설계가 잘못되었다면 이전 단계로 돌아갈 수도 있다.</p>
<p>그래도 <strong>&#39;일단 만들고 나중에 생각하기&#39;보다는 무엇을 왜 만드는지 먼저 정의해 보는 것</strong>을 이번 프로젝트의 또 다른 목표로 잡았다.</p>
<hr>
<h1 id="첫-번째-단계-요구사항-분석">첫 번째 단계, 요구사항 분석</h1>
<p>그렇다면 가장 먼저 해야 할 것은 <strong>요구사항 분석</strong>이다.</p>
<p>프로그램을 만들기 전에 최소한 다음 질문들에는 답할 수 있어야 한다고 생각했다.</p>
<ol>
<li>이 프로그램은 사용자에게 무엇을 제공해야 하는가?</li>
<li>어떤 증권사 API와 통신할 것인가?</li>
<li>어떤 데이터를 가져와야 하는가?</li>
<li>어떤 조건에서 매수와 매도를 판단할 것인가?</li>
<li>주문 실패, API 오류, 잔고 부족 같은 상황은 어떻게 처리할 것인가?</li>
<li>첫 번째 버전에서는 어디까지 구현할 것인가?</li>
</ol>
<p>처음 적어두었던 이 질문들은 별도의 개발 단계라기보다는 <strong>요구사항을 구체화하기 위해 스스로에게 던진 질문</strong>에 가깝다.</p>
<p>그리고 이를 바탕으로 요구사항을 <strong>사용자 요구사항</strong>과 <strong>시스템 요구사항</strong>으로 나누어 정리했다.</p>
<hr>
<h1 id="사용자-요구사항과-시스템-요구사항">사용자 요구사항과 시스템 요구사항</h1>
<p>두 요구사항은 비슷해 보이지만 관점이 다르다.</p>
<h3 id="사용자-요구사항user-requirement">사용자 요구사항(User Requirement)</h3>
<p>사용자 입장에서</p>
<blockquote>
<p><strong>&quot;이 프로그램을 이용해서 무엇을 할 수 있어야 하는가?&quot;</strong></p>
</blockquote>
<p>를 정의한다.</p>
<h3 id="시스템-요구사항system-requirement">시스템 요구사항(System Requirement)</h3>
<p>사용자 요구사항을 만족시키기 위해</p>
<blockquote>
<p><strong>&quot;시스템 내부에서는 어떤 기능을 수행해야 하는가?&quot;</strong></p>
</blockquote>
<p>를 정의한다.</p>
<p>예를 들어 사용자는 단순히</p>
<blockquote>
<p>&quot;내 계좌 상태를 보고 싶다.&quot;</p>
</blockquote>
<p>라고 생각할 수 있다.</p>
<p>하지만 시스템 입장에서는 이를 위해</p>
<pre><code class="language-text">사용자 인증
    ↓
Access Token 발급
    ↓
증권사 API 요청
    ↓
계좌 정보 조회
    ↓
응답 데이터 변환
    ↓
사용자에게 전달</code></pre>
<p>과 같은 여러 작업이 필요하다.</p>
<p>이 차이를 기준으로 요구사항을 정리해 보았다.</p>
<hr>
<h1 id="사용자-요구사항">사용자 요구사항</h1>
<p>현재 생각한 사용자 요구사항은 다음과 같다.</p>
<ol>
<li>사용자는 증권 계좌에 투자금을 입금한다.</li>
<li>사용자는 자동매매 프로그램을 실행할 수 있다.</li>
<li>사용자는 계좌에서 현재 사용 가능한 잔액을 확인할 수 있다.</li>
<li>프로그램은 설정된 매수/매도 전략에 따라 거래 여부를 판단한다.</li>
<li>조건이 충족되면 프로그램은 매수 또는 매도를 수행할 수 있다.</li>
<li>사용자는 거래 결과와 현재 계좌 상태를 확인할 수 있어야 한다.</li>
</ol>
<p>아직 초기 단계이기 때문에 상당히 큰 단위의 요구사항만 작성했다.</p>
<p>프로젝트를 진행하면서 실제 사용자 흐름을 구체화하면 더 세분화할 예정이다.</p>
<hr>
<h1 id="시스템-요구사항">시스템 요구사항</h1>
<p>사용자 요구사항을 실제로 구현하기 위해 시스템이 제공해야 하는 기능도 정리했다.</p>
<ol>
<li>시스템은 증권사 API 인증 토큰을 발급받고 관리할 수 있어야 한다.</li>
<li>시스템은 계좌 잔고를 조회할 수 있어야 한다.</li>
<li>시스템은 보유 종목을 조회할 수 있어야 한다.</li>
<li>시스템은 특정 종목의 현재가를 조회할 수 있어야 한다.</li>
<li>시스템은 매수/매도 전략에 필요한 데이터를 판단 시스템에 전달할 수 있어야 한다.</li>
<li>판단 시스템은 데이터를 기반으로 매수/매도 여부를 결정할 수 있어야 한다.</li>
<li>시스템은 판단 결과에 따라 증권사에 매수/매도 주문을 요청할 수 있어야 한다.</li>
<li>시스템은 주문 및 체결 결과를 확인할 수 있어야 한다.</li>
<li>시스템은 거래 내역과 판단 결과를 로그로 저장할 수 있어야 한다.</li>
<li>시스템은 API 오류, 인증 실패, 잔고 부족 등의 예외 상황을 처리하고 기록할 수 있어야 한다.</li>
</ol>
<p>아직 어떤 증권사 API를 사용할지, 두 시스템을 어떤 방식으로 통신시킬지는 완전히 결정하지 않았다.</p>
<p>REST API를 사용할 수도 있고, Python 판단 시스템을 별도의 서비스로 분리할 수도 있다.</p>
<p>이 부분은 이후 <strong>아키텍처 설계 단계</strong>에서 조금 더 구체적으로 고민해 보려고 한다.</p>
<hr>
<h1 id="그런데-처음부터-이걸-전부-만들어야-할까">그런데 처음부터 이걸 전부 만들어야 할까?</h1>
<p>요구사항을 작성하고 보니 생각보다 구현해야 할 것이 많았다.</p>
<pre><code class="language-text">인증
계좌
시세
전략
주문
체결
로그
예외 처리
UI
백테스트
AI
...</code></pre>
<p>여기에 처음부터 머신러닝 모델과 실시간 UI, 실제 주문까지 전부 넣으려고 하면 프로젝트가 끝나기도 전에 지칠 가능성이 높다.</p>
<p>그래서 필요한 것이 <strong>MVP</strong>다.</p>
<hr>
<h1 id="mvp란">MVP란?</h1>
<p>MVP는 <strong>Minimum Viable Product</strong>, 즉 <strong>최소 기능 제품</strong>을 의미한다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>&quot;우리 서비스의 핵심 가치를 확인하기 위해 첫 번째 버전에서는 어디까지 만들 것인가?&quot;</strong></p>
</blockquote>
<p>를 정하는 것이다.</p>
<p>여기서 중요한 것은 단순히 기능을 적게 만드는 것이 아니다.</p>
<p>최소한의 기능만 가지고도 <strong>이 시스템의 핵심 흐름이 실제로 동작하는지 확인할 수 있어야 한다.</strong></p>
<p>내 자동매매 프로그램에서 가장 먼저 검증하고 싶은 흐름은 다음과 같다.</p>
<pre><code class="language-text">증권사 API 연결
        ↓
계좌 / 시장 데이터 조회
        ↓
Python 판단 시스템으로 데이터 전달
        ↓
매수 / 매도 / 대기 판단
        ↓
결과 기록</code></pre>
<p>이 흐름이 정상적으로 동작한다면 그다음에 실제 주문이나 복잡한 전략을 추가할 수 있다.</p>
<hr>
<h1 id="1차-mvp">1차 MVP</h1>
<p>그래서 첫 번째 MVP의 범위를 다음과 같이 잡았다.</p>
<h3 id="1-api-인증">1. API 인증</h3>
<p>증권사 API를 사용하기 위한 인증 토큰을 정상적으로 발급받고 관리한다.</p>
<h3 id="2-계좌-잔고-조회">2. 계좌 잔고 조회</h3>
<p>API를 통해 실제 계좌의 사용 가능한 잔액을 가져온다.</p>
<h3 id="3-특정-종목-현재가-조회">3. 특정 종목 현재가 조회</h3>
<p>지정한 종목의 현재 가격 데이터를 가져온다.</p>
<h3 id="4-단순한-매수매도-전략-구현">4. 단순한 매수/매도 전략 구현</h3>
<p>Python으로 가장 단순한 규칙 기반 전략부터 구현한다.</p>
<pre><code class="language-text">시장 데이터
    ↓
Python 전략
    ↓
BUY / SELL / HOLD</code></pre>
<p>이 단계에서는 처음부터 복잡한 AI 모델을 사용하는 것이 아니라 <strong>Java/Spring 시스템과 Python 판단 시스템이 정상적으로 연결되는지 확인하는 것</strong>을 우선한다.</p>
<h3 id="5-모의-판단-결과-기록">5. 모의 판단 결과 기록</h3>
<p>1차 MVP에서는 실제 주문을 보내지 않는다.</p>
<pre><code class="language-text">[14:30:01] 삼성전자 현재가 : 80,000원
[14:30:01] 전략 판단 : BUY
[14:30:01] 주문 실행 : SKIP (MVP Simulation Mode)</code></pre>
<p>이처럼 실제 돈이 움직이지 않는 상태에서 전체 파이프라인이 정상적으로 동작하는지 먼저 검증한다.</p>
<hr>
<h1 id="1차-mvp에서-제외할-것">1차 MVP에서 제외할 것</h1>
<p>반대로 처음부터 만들지 않을 기능도 명확하게 정했다.</p>
<ol>
<li>실제 매수/매도 주문 실행</li>
<li>복잡한 투자 전략</li>
<li>머신러닝/딥러닝 기반 투자 판단</li>
<li>실시간 차트 UI</li>
<li>백테스트 시스템</li>
<li>서버 배포</li>
<li>모바일 알림</li>
</ol>
<p>특히 <strong>AI 기반 투자 판단은 일부러 1차 MVP에서 제외했다.</strong></p>
<p>최종적으로는 Python을 이용해 데이터를 분석하고 모델이 매수와 매도를 판단하도록 만드는 것이 목표지만, 아직 데이터 파이프라인조차 만들어지지 않은 상태에서 AI부터 붙이는 것은 순서가 아니라고 생각했다.</p>
<p>먼저</p>
<pre><code class="language-text">데이터를 제대로 가져올 수 있는가?

        ↓

Java/Spring과 Python이 통신할 수 있는가?

        ↓

판단 결과를 안정적으로 전달할 수 있는가?

        ↓

로그와 예외 처리가 정상적으로 이루어지는가?</code></pre>
<p>부터 확인하려고 한다.</p>
<p>그다음 전략을 하나씩 고도화하면 된다.</p>
<hr>
<h1 id="앞으로의-방향">앞으로의 방향</h1>
<p>여기까지 내가 만들고자 하는 자동매매 프로그램의 요구사항과 1차 MVP를 간단하게 정리해 보았다.</p>
<p>아직은 설계 초기 단계라 앞으로 개발하면서 바뀌는 부분도 분명히 있을 것이다.</p>
<p>하지만 이번 프로젝트에서는 단순히</p>
<blockquote>
<p><strong>&quot;자동매매 프로그램 하나 만들어봤다.&quot;</strong></p>
</blockquote>
<p>에서 끝내고 싶지는 않다.</p>
<p>요구사항 분석부터 도메인 설계, 아키텍처 설계, 구현, 테스트와 배포까지 학교에서 배운 소프트웨어 개발 과정을 실제 개인 프로젝트에 하나씩 적용해 보는 것이 또 하나의 목표다.</p>
<p>그리고 그 과정에서 왜 이런 구조를 선택했고, 어떤 문제가 발생했고, 어떻게 개선했는지도 계속 기록해 보려고 한다.</p>
<p>다음 글에서는 요구사항을 기반으로 <strong>실제로 구현해야 할 기능 목록을 조금 더 구체적으로 정리해 볼 예정이다.</strong></p>
<blockquote>
<p><strong>다음 글 : 주식 자동매매 프로그램 만들기 (2) - 기능 목록 정의하기</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | Oracle 김장원 박사 초청강연 후기 (3)
- AI 시대에 살아남는 개발자의 조건, 그리고 앞으로의 커리어]]></title>
            <link>https://velog.io/@daehyun_lee/Oracle-%EA%B9%80%EC%9E%A5%EC%9B%90-%EB%B0%95%EC%82%AC-%EC%B4%88%EC%B2%AD%EA%B0%95%EC%97%B0-%ED%9B%84%EA%B8%B0-3-AI-%EC%8B%9C%EB%8C%80%EC%97%90-%EC%82%B4%EC%95%84%EB%82%A8%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%A1%B0%EA%B1%B4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%95%9E%EC%9C%BC%EB%A1%9C%EC%9D%98-%EC%BB%A4%EB%A6%AC%EC%96%B4</link>
            <guid>https://velog.io/@daehyun_lee/Oracle-%EA%B9%80%EC%9E%A5%EC%9B%90-%EB%B0%95%EC%82%AC-%EC%B4%88%EC%B2%AD%EA%B0%95%EC%97%B0-%ED%9B%84%EA%B8%B0-3-AI-%EC%8B%9C%EB%8C%80%EC%97%90-%EC%82%B4%EC%95%84%EB%82%A8%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%A1%B0%EA%B1%B4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%95%9E%EC%9C%BC%EB%A1%9C%EC%9D%98-%EC%BB%A4%EB%A6%AC%EC%96%B4</guid>
            <pubDate>Mon, 03 Aug 2026 22:33:15 GMT</pubDate>
            <description><![CDATA[<h1 id="oracle-김장원-박사-초청강연-후기-3">Oracle 김장원 박사 초청강연 후기 (3)</h1>
<h1 id="ai-시대에-살아남는-개발자의-조건-그리고-앞으로의-커리어">AI 시대에 살아남는 개발자의 조건, 그리고 앞으로의 커리어</h1>
<p>앞선 두 편에서는</p>
<ul>
<li>AI-Native 시대가 무엇인지</li>
<li>Oracle와 Amazon에서는 AI를 어떻게 활용하고 있는지</li>
<li>AI가 개발자의 업무를 어떻게 바꾸고 있는지</li>
</ul>
<p>에 대해 이야기했다.</p>
<p>이번 글에서는 개인적으로 가장 인상 깊었던 내용,</p>
<p>바로 <strong>AI 시대를 살아가는 개발자의 태도</strong>에 대해 정리해 보려고 한다.</p>
<p>사실 기술적인 내용보다도 이 부분이 훨씬 오래 기억에 남았다.</p>
<hr>
<h1 id="ai-시대에도-사람은-여전히-필요하다">AI 시대에도 사람은 여전히 필요하다</h1>
<p>강연 후반부에서</p>
<p>박사님은 계속해서</p>
<p><strong>Perspective(관점)</strong></p>
<p>이라는 단어를 강조하셨다. Staying Relevant in the AI-Native Era.pdf</p>
<p>AI는 앞으로 더 많은 일을 하게 될 것이다.</p>
<p>코드를 작성하고,</p>
<p>문서를 만들고,</p>
<p>발표 자료를 만들고,</p>
<p>심지어 여러 AI가 협업하는 Multi-Agent 시스템도 점점 일반화되고 있다.</p>
<p>하지만</p>
<p>AI가 대신하지 못하는 것이 있다.</p>
<p>바로</p>
<blockquote>
<p><strong>무엇을 해결해야 하는가</strong></p>
</blockquote>
<p>를 결정하는 일이다.</p>
<p>AI는</p>
<p>문제를 해결하는 도구이지,</p>
<p>문제를 정의하는 존재는 아니라는 말이 특히 기억에 남았다.</p>
<hr>
<h1 id="결국-중요한-것은-context">결국 중요한 것은 Context</h1>
<p>강연 중 한 학생이 질문했다.</p>
<blockquote>
<p>&quot;AI가 결국 이런 영역도 대체하지 않을까요?&quot;</p>
</blockquote>
<p>박사님의 대답은 굉장히 현실적이었다.</p>
<p>AI는</p>
<p>많은 작업(Task)을 대신할 수 있다.</p>
<p>하지만</p>
<p><strong>Context를 이해하는 사람</strong></p>
<p>까지 대체하기는 어렵다고 이야기하셨다.</p>
<p>같은 코드라도</p>
<p>왜 이렇게 설계했는지,</p>
<p>비즈니스 목적이 무엇인지,</p>
<p>앞으로 어떤 방향으로 발전해야 하는지를 이해하는 것은</p>
<p>여전히 사람의 역할이라는 것이다.</p>
<p>AI가 발전할수록</p>
<p>오히려 Context를 이해하는 능력이 더 중요해진다는 말이 인상 깊었다.</p>
<hr>
<h1 id="task는-자동화되지만-job은-다르다">Task는 자동화되지만 Job은 다르다</h1>
<p>박사님은</p>
<p>AI가 앞으로</p>
<p><strong>Task Automation</strong></p>
<p>은 계속 발전할 것이라고 이야기하셨다.</p>
<p>반복적인 업무,</p>
<p>정형화된 작업,</p>
<p>문서 작성,</p>
<p>코드 생성 등은</p>
<p>점점 AI가 담당하게 될 가능성이 높다.</p>
<p>하지만</p>
<p>그렇다고 해서</p>
<p>직업(Job) 자체가 바로 사라지는 것은 아니라고 말씀하셨다.</p>
<p>사람은</p>
<p>여러 Task를 연결하고,</p>
<p>우선순위를 정하고,</p>
<p>최종 결정을 내리는 역할을 계속 맡게 될 것이라는 설명이 인상 깊었다.</p>
<hr>
<h1 id="문제를-가장-빨리-정의하는-사람이-강하다">문제를 가장 빨리 정의하는 사람이 강하다</h1>
<p>이번 강연에서</p>
<p>개인적으로 가장 공감했던 조언이다.</p>
<p>박사님은</p>
<p>회사에서 잘하는 사람들을 보면</p>
<p>코딩을 가장 잘하는 사람이 아니라,</p>
<blockquote>
<p><strong>문제를 가장 빨리 파악하는 사람</strong></p>
</blockquote>
<p>이었다고 이야기하셨다.</p>
<p>그리고</p>
<p>사람들을 잘 리드해서</p>
<p>좋은 결과를 만드는 사람들이</p>
<p>결국 높은 성과를 냈다고 한다.</p>
<p>학생 때는</p>
<p>코딩 실력만 중요하다고 생각하기 쉽다.</p>
<p>하지만</p>
<p>실제 회사에서는</p>
<p>문제 정의와 의사결정 능력이</p>
<p>생각보다 훨씬 중요하다는 것을 다시 한번 느꼈다.</p>
<hr>
<h1 id="6070-준비되면-일단-지원하라">60~70% 준비되면 일단 지원하라</h1>
<p>강연 중</p>
<p>학생들의 질문 시간이 있었는데,</p>
<p>가장 기억에 남는 답변이 하나 있었다.</p>
<blockquote>
<p>&quot;아직 준비가 부족한 것 같은데 지원해도 될까요?&quot;</p>
</blockquote>
<p>박사님의 대답은 의외였다.</p>
<blockquote>
<p><strong>60~70% 정도 준비되었다고 생각하면 일단 지원하세요.</strong></p>
</blockquote>
<p>그리고</p>
<p>부족한 30%는</p>
<p>정말 미친 듯이 채우면 된다고 말씀하셨다.</p>
<p>완벽하게 준비될 때까지 기다리다 보면</p>
<p>기회 자체를 놓치는 경우가 많다고 하셨다.</p>
<p>이 말을 들으면서</p>
<p>나 역시</p>
<p>지원하기 전에</p>
<p>&quot;아직 부족한데...&quot;</p>
<p>라는 생각을 많이 했던 순간들이 떠올랐다.</p>
<hr>
<h1 id="질문을-더-구체적으로-하라">질문을 더 구체적으로 하라</h1>
<p>또 하나 기억에 남았던 조언이 있다.</p>
<p>학생들은 종종</p>
<blockquote>
<p>&quot;무엇을 준비해야 할까요?&quot;</p>
</blockquote>
<p>라고 질문한다.</p>
<p>하지만</p>
<p>박사님은</p>
<p>그보다</p>
<blockquote>
<p>&quot;이 직무를 위해 앞으로 2주 동안 무엇을 준비하면 좋을까요?&quot;</p>
</blockquote>
<p>처럼</p>
<p>구체적인 질문을 하는 것이 훨씬 좋은 답을 얻을 수 있다고 말씀하셨다.</p>
<p>AI에게 질문할 때도,</p>
<p>사람에게 질문할 때도,</p>
<p>좋은 질문이 좋은 답을 만든다는 점은</p>
<p>공통적인 원리인 것 같았다.</p>
<hr>
<h1 id="persistence와-curiosity">Persistence와 Curiosity</h1>
<p>강연 마지막에는</p>
<p>에릭 슈미트(Eric Schmidt)가 중요하게 생각했던 두 가지 가치도 소개되었다.</p>
<p>첫 번째는</p>
<p><strong>Persistence</strong></p>
<p>포기하지 않는 끈기.</p>
<p>두 번째는</p>
<p><strong>Curiosity</strong></p>
<p>끊임없이 궁금해하는 태도.</p>
<p>화려한 기술보다</p>
<p>이 두 가지가</p>
<p>오랫동안 성장하는 사람들의 공통점이라는 설명이 인상 깊었다.</p>
<hr>
<h1 id="실패를-두려워하지-말고-빨리-움직여라">실패를 두려워하지 말고 빨리 움직여라</h1>
<p>또 하나 기억에 남는 조언.</p>
<blockquote>
<p><strong>빨리 실패하고, 빨리 방향을 수정하라.</strong></p>
</blockquote>
<p>완벽한 계획을 세우는 것보다</p>
<p>먼저 시도하고,</p>
<p>실패하면서</p>
<p>계속 방향을 수정하는 것이 훨씬 중요하다고 하셨다.</p>
<p>스타트업에서 일했던 경험과</p>
<p>대기업에서의 경험을 함께 이야기해 주셨는데,</p>
<p>결국</p>
<p>행동하는 사람이</p>
<p>가장 많은 것을 배우게 된다는 말이 크게 와닿았다.</p>
<hr>
<h1 id="reach-out">Reach Out</h1>
<p>개인적으로 가장 현실적인 조언이었다.</p>
<p>박사님은</p>
<p>기회를 기다리기보다</p>
<p>직접 사람들에게 연락하는 것이 중요하다고 말씀하셨다.</p>
<p>거창한 메일이 아니라</p>
<pre><code>안녕하세요.

논문 잘 읽었습니다.

축하드립니다.

혹시 학회에서 잠깐 커피챗 가능하실까요?</code></pre><p>정도의 짧은 메시지면 충분하다고 하셨다.</p>
<p>실제로</p>
<p>이런 Cold Message 하나로</p>
<p>새로운 기회를 얻었던 경험도 들려주셨다.</p>
<p>네트워킹은</p>
<p>특별한 사람만 하는 것이 아니라,</p>
<p>먼저 용기 내어 연락하는 사람에게 찾아온다는 점이 인상 깊었다.</p>
<hr>
<h1 id="커뮤니케이션은-생각보다-훨씬-중요하다">커뮤니케이션은 생각보다 훨씬 중요하다</h1>
<p>학생 때는</p>
<p>코딩만 잘하면 될 것 같지만,</p>
<p>회사에서는</p>
<p>생각보다</p>
<p>커뮤니케이션의 비중이 매우 크다고 하셨다.</p>
<p>특히</p>
<p><strong>Written Communication</strong></p>
<p>즉,</p>
<p>문서로 명확하게 전달하는 능력이 굉장히 중요하다고 말씀하셨다.</p>
<p>또한</p>
<p>말로 대화하는 상황에서는</p>
<p>내가 하고 싶은 말보다</p>
<p><strong>상대방이 무엇을 원하는지 이해하는 능력</strong></p>
<p>이 더 중요하다고 강조하셨다.</p>
<p>AI 시대에도</p>
<p>사람과 협업하는 능력은</p>
<p>여전히 핵심 역량이라는 생각이 들었다.</p>
<hr>
<h1 id="추천해-주신-책들">추천해 주신 책들</h1>
<p>강연 마지막에는</p>
<p>앞으로 읽어보면 좋을 책들도 추천해 주셨다.</p>
<ul>
<li><strong>The Coming Wave</strong></li>
<li><strong>Superagency</strong></li>
<li><strong>Eat That Frog</strong></li>
<li><strong>The One Thing</strong></li>
<li><strong>내가 가진 것을 세상이 원하게 하라</strong></li>
</ul>
<p>단순한 AI 기술서가 아니라,</p>
<p>AI 시대를 살아가는 방법,</p>
<p>우선순위를 정하는 방법,</p>
<p>그리고 자신의 커리어를 만들어가는 방법에 대한 책들이었다.</p>
<p>시간이 날 때 하나씩 읽어보려고 한다.</p>
<hr>
<h1 id="강연을-마치며">강연을 마치며</h1>
<p>이번 강연은</p>
<p>AI 기술 자체를 배우는 시간이라기보다,</p>
<p><strong>AI 시대를 살아가는 개발자의 태도</strong>를 배우는 시간이었다.</p>
<p>처음에는</p>
<p>&quot;AI가 개발자를 대체할까?&quot;</p>
<p>라는 질문을 가지고 강연을 들었다.</p>
<p>하지만 끝나고 나니</p>
<p>질문 자체가 조금 달라졌다.</p>
<blockquote>
<p><strong>AI 시대에 나는 어떤 개발자가 되어야 할까?</strong></p>
</blockquote>
<p>AI는 앞으로 더 많은 일을 대신하게 될 것이다.</p>
<p>하지만</p>
<p>문제를 정의하고,</p>
<p>좋은 방향을 설계하고,</p>
<p>사람들과 협업하며,</p>
<p>최종 결정을 내리는 일은</p>
<p>여전히 사람의 역할이다.</p>
<p>이번 강연을 통해</p>
<p>기술을 공부하는 것만큼이나</p>
<p><strong>좋은 질문을 하는 능력, 문제를 정의하는 능력, 그리고 끊임없이 배우려는 태도</strong>가 중요하다는 것을 다시 한번 느낄 수 있었다.</p>
<p>USC에서 들었던 여러 강의 중에서도</p>
<p>앞으로의 커리어를 가장 많이 돌아보게 만든 강연이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | Oracle 김장원 박사 초청강연 후기 (2)
- AI 시대에도 결국 중요한 것은 사람이다]]></title>
            <link>https://velog.io/@daehyun_lee/Oracle-%EA%B9%80%EC%9E%A5%EC%9B%90-%EB%B0%95%EC%82%AC-%EC%B4%88%EC%B2%AD%EA%B0%95%EC%97%B0-%ED%9B%84%EA%B8%B0-2-AI-%EC%8B%9C%EB%8C%80%EC%97%90%EB%8F%84-%EA%B2%B0%EA%B5%AD-%EC%A4%91%EC%9A%94%ED%95%9C-%EA%B2%83%EC%9D%80-%EC%82%AC%EB%9E%8C%EC%9D%B4%EB%8B%A4</link>
            <guid>https://velog.io/@daehyun_lee/Oracle-%EA%B9%80%EC%9E%A5%EC%9B%90-%EB%B0%95%EC%82%AC-%EC%B4%88%EC%B2%AD%EA%B0%95%EC%97%B0-%ED%9B%84%EA%B8%B0-2-AI-%EC%8B%9C%EB%8C%80%EC%97%90%EB%8F%84-%EA%B2%B0%EA%B5%AD-%EC%A4%91%EC%9A%94%ED%95%9C-%EA%B2%83%EC%9D%80-%EC%82%AC%EB%9E%8C%EC%9D%B4%EB%8B%A4</guid>
            <pubDate>Mon, 03 Aug 2026 22:32:29 GMT</pubDate>
            <description><![CDATA[<h1 id="oracle-김장원-박사-초청강연-후기-2">Oracle 김장원 박사 초청강연 후기 (2)</h1>
<h1 id="ai-시대에도-결국-중요한-것은-사람이다">AI 시대에도 결국 중요한 것은 사람이다</h1>
<p>지난 글에서는 Oracle에서 바라보는 <strong>AI-Native 시대</strong>와 개발자의 역할 변화에 대해 이야기했다.</p>
<p>강연을 들으며 가장 인상 깊었던 부분은 AI 기술 자체보다,</p>
<blockquote>
<p><strong>&quot;AI 시대에도 결국 무엇을 잘하는 사람이 살아남는가?&quot;</strong></p>
</blockquote>
<p>에 대한 이야기였다.</p>
<p>생각보다 현실적이었고, 앞으로 어떤 개발자가 되어야 하는지 다시 한번 생각해 보게 만든 시간이었다.</p>
<hr>
<h1 id="ai가-바꾸는-것은-코딩이-아니라-일하는-방식">AI가 바꾸는 것은 코딩이 아니라 &#39;일하는 방식&#39;</h1>
<p>강연 중 가장 기억에 남았던 키워드는</p>
<p><strong>Workflow</strong></p>
<p>였다.</p>
<p>요즘은 AI를 얼마나 잘 사용하는지가 중요하다고 많이 이야기한다.</p>
<p>하지만 박사님은</p>
<blockquote>
<p>AI를 사용하는 것보다</p>
</blockquote>
<blockquote>
<p><strong>AI를 활용한 Workflow를 만드는 능력</strong></p>
</blockquote>
<p>이 더 중요해질 것이라고 말씀하셨다.</p>
<p>예전에는</p>
<pre><code>문제 발생

↓

선배에게 질문

↓

코드 찾기

↓

문서 읽기

↓

수정</code></pre><p>이라는 흐름이었다.</p>
<p>하지만 앞으로는</p>
<pre><code>문제 발생

↓

AI에게 Context 전달

↓

AI가 관련 코드 분석

↓

수정 방향 제안

↓

사람이 최종 판단</code></pre><p>이라는 형태로 바뀌게 된다.</p>
<p>즉,</p>
<p>AI는 개발자를 대체하는 것이 아니라</p>
<p><strong>개발 프로세스 자체를 바꾸고 있는 것</strong>이었다.</p>
<hr>
<h1 id="회사에서는-남의-코드를-더-많이-본다">회사에서는 남의 코드를 더 많이 본다</h1>
<p>학생 때는</p>
<p>대부분 내가 작성한 코드만 본다.</p>
<p>하지만 회사에서는 그렇지 않다.</p>
<p>오히려</p>
<p>남이 작성한 코드가 훨씬 많다.</p>
<p>박사님도</p>
<p>회사에서는</p>
<p>기존 코드베이스를 이해하는 시간이 굉장히 많다고 이야기하셨다.</p>
<p>예전에는</p>
<p>관련 문서를 찾고,</p>
<p>기존 개발자에게 질문하고,</p>
<p>하나씩 분석해야 했다.</p>
<p>하지만 지금은</p>
<p>AI에게</p>
<blockquote>
<p>&quot;이 프로젝트 구조를 설명해줘.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;이 함수가 왜 필요한지 알려줘.&quot;</p>
</blockquote>
<p>라고 물어보면</p>
<p>생각보다 빠르게 이해할 수 있다.</p>
<p>이 부분을 들으면서</p>
<p>AI는 단순히 코드를 생성하는 도구가 아니라,</p>
<p><strong>기존 시스템을 이해하는 도구</strong>로도 엄청난 가치를 가진다는 생각이 들었다.</p>
<hr>
<h1 id="ai는-커뮤니케이션도-바꿀-것이다">AI는 커뮤니케이션도 바꿀 것이다</h1>
<p>강연에서 조금 놀랐던 부분도 있었다.</p>
<p>박사님은</p>
<p>AI가 단순히 개발만 바꾸는 것이 아니라</p>
<p><strong>사람과 사람 사이의 커뮤니케이션도 크게 바꿀 것</strong>이라고 이야기하셨다.</p>
<p>예전에는</p>
<p>주니어 개발자가</p>
<p>시니어 개발자에게</p>
<p>질문하면서 배우는 과정이 많았다.</p>
<p>하지만</p>
<p>이제는</p>
<p>AI에게 먼저 질문하고,</p>
<p>필요한 정보를 바로 얻을 수 있다.</p>
<p>즉,</p>
<p>단순한 질문과 답변은</p>
<p>점점 AI가 대신하게 될 가능성이 높다는 것이다.</p>
<p>물론</p>
<p>사람 사이의 협업이 사라지는 것은 아니지만,</p>
<p>커뮤니케이션의 형태는 분명히 달라질 것이라는 생각이 들었다.</p>
<hr>
<h1 id="그렇다면-사람은-무엇을-해야-할까">그렇다면 사람은 무엇을 해야 할까?</h1>
<p>강연에서 한 학생이 질문했다.</p>
<blockquote>
<p>&quot;AI가 결국 이런 영역도 대체할 것이라고 생각하시나요?&quot;</p>
</blockquote>
<p>박사님의 답변은 인상 깊었다.</p>
<p>AI는</p>
<p>많은 일을 대신할 수 있다.</p>
<p>하지만</p>
<p><strong>중요한 Context를 이해하고 판단하는 사람</strong></p>
<p>까지 대체하기는 어렵다는 것이다.</p>
<p>예를 들어</p>
<p>같은 코드라도</p>
<p>왜 이런 구조를 선택했는지,</p>
<p>비즈니스 요구사항이 무엇인지,</p>
<p>앞으로 어떻게 확장될지를 이해하는 것은</p>
<p>여전히 사람의 역할이라는 것이다.</p>
<p>결국</p>
<p>기술보다</p>
<p><strong>맥락(Context)을 이해하는 능력</strong>이 더욱 중요해질 것이라는 이야기였다.</p>
<hr>
<h1 id="좋은-코드를-많이-보는-것이-중요하다">좋은 코드를 많이 보는 것이 중요하다</h1>
<p>이 부분은 개인적으로 가장 공감했던 내용이었다.</p>
<p>박사님은</p>
<p>코딩 연습도 중요하지만,</p>
<blockquote>
<p><strong>좋은 코드를 많이 보는 것이 훨씬 중요하다.</strong></p>
</blockquote>
<p>고 말씀하셨다.</p>
<p>처음에는</p>
<p>조금 의외였다.</p>
<p>하지만 생각해 보면</p>
<p>좋은 글을 많이 읽어야 글을 잘 쓰게 되듯,</p>
<p>좋은 코드를 많이 봐야</p>
<p>좋은 코드가 무엇인지 판단할 수 있다.</p>
<p>AI가 코드를 생성해 주는 시대일수록</p>
<p>그 코드가</p>
<p>좋은 코드인지,</p>
<p>나쁜 코드인지,</p>
<p>수정해야 하는 코드인지를</p>
<p>판단하는 능력이 훨씬 중요해질 것이라는 말이 인상 깊었다.</p>
<hr>
<h1 id="코딩테스트는-없어질까">코딩테스트는 없어질까?</h1>
<p>많은 사람들이 궁금해하는 질문도 나왔다.</p>
<blockquote>
<p>&quot;AI 시대에도 코딩테스트가 필요할까요?&quot;</p>
</blockquote>
<p>박사님의 대답은</p>
<p>&quot;당분간은 필요하다.&quot;</p>
<p>였다.</p>
<p>이유는 단순했다.</p>
<p>현재로서는</p>
<p>지원자의 문제 해결 능력을 평가할 수 있는</p>
<p>더 좋은 방법을 찾기 어렵기 때문이다.</p>
<p>다만</p>
<p>코딩테스트에서</p>
<p>면접관이 정말 보고 싶은 것은</p>
<p>단순히 정답을 맞히는 것이 아니라고 하셨다.</p>
<p>오히려</p>
<ul>
<li><p>어떻게 문제를 접근했는지</p>
</li>
<li><p>왜 그렇게 생각했는지</p>
</li>
<li><p>다른 사람과 어떻게 설명하고 소통하는지</p>
</li>
</ul>
<p>같은 부분을 더 중요하게 본다고 이야기하셨다.</p>
<p>이 말을 듣고</p>
<p>앞으로 코딩테스트를 준비할 때도</p>
<p>단순히 정답만 맞추는 것이 아니라,</p>
<p>내 생각을 설명하는 연습도 함께 해야겠다는 생각이 들었다.</p>
<hr>
<h1 id="이번-강연에서-가장-기억에-남은-한-문장">이번 강연에서 가장 기억에 남은 한 문장</h1>
<p>이번 강연을 들으며</p>
<p>가장 오래 기억에 남은 문장은</p>
<blockquote>
<p><strong>&quot;좋은 코드를 작성하는 사람보다, 좋은 코드를 판단할 수 있는 사람이 되어라.&quot;</strong></p>
</blockquote>
<p>였다.</p>
<p>AI는 앞으로도 계속 발전할 것이다.</p>
<p>하지만</p>
<p>그 결과를 검증하고,</p>
<p>더 나은 방향을 선택하는 일은</p>
<p>결국 사람의 몫이다.</p>
<p>이번 강연을 통해</p>
<p>앞으로 개발자는</p>
<p>단순히 코드를 작성하는 사람이 아니라,</p>
<p><strong>좋은 방향을 선택하는 사람</strong>이 되어야 한다는 생각을 하게 되었다.</p>
<p>다음 글에서는</p>
<p>김장원 박사님이 들려주신</p>
<p>가장 현실적인 커리어 조언과</p>
<p>추천 도서,</p>
<p>그리고 개인적으로 가장 인상 깊었던 이야기들을 정리해 보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | Oracle 김장원 박사 초청강연 후기 (1) - AI-Native 시대, 개발자는 정말 코딩을 덜 하게 될까?]]></title>
            <link>https://velog.io/@daehyun_lee/Oracle-%EA%B9%80%EC%9E%A5%EC%9B%90-%EB%B0%95%EC%82%AC-%EC%B4%88%EC%B2%AD%EA%B0%95%EC%97%B0-%ED%9B%84%EA%B8%B0-1-AI-Native-%EC%8B%9C%EB%8C%80-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EC%A0%95%EB%A7%90-%EC%BD%94%EB%94%A9%EC%9D%84-%EB%8D%9C-%ED%95%98%EA%B2%8C-%EB%90%A0%EA%B9%8C</link>
            <guid>https://velog.io/@daehyun_lee/Oracle-%EA%B9%80%EC%9E%A5%EC%9B%90-%EB%B0%95%EC%82%AC-%EC%B4%88%EC%B2%AD%EA%B0%95%EC%97%B0-%ED%9B%84%EA%B8%B0-1-AI-Native-%EC%8B%9C%EB%8C%80-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EC%A0%95%EB%A7%90-%EC%BD%94%EB%94%A9%EC%9D%84-%EB%8D%9C-%ED%95%98%EA%B2%8C-%EB%90%A0%EA%B9%8C</guid>
            <pubDate>Mon, 03 Aug 2026 22:30:55 GMT</pubDate>
            <description><![CDATA[<h1 id="oracle-김장원-박사-초청강연-후기-1">Oracle 김장원 박사 초청강연 후기 (1)</h1>
<h1 id="ai-native-시대-개발자는-정말-코딩을-덜-하게-될까">AI-Native 시대, 개발자는 정말 코딩을 덜 하게 될까?</h1>
<p>USC 프로그램 중 가장 기대했던 일정 중 하나는 Oracle의 <strong>김장원 박사님 초청강연</strong>이었다.</p>
<p>강연 제목은</p>
<blockquote>
<p><strong>Staying Relevant in the AI-Native Era</strong></p>
</blockquote>
<p>였다.</p>
<p>처음에는 단순히 AI 기술 동향이나 Oracle에서 사용하는 최신 기술을 소개하는 강연일 것이라고 생각했다.</p>
<p>그런데 강연을 듣고 나니 예상과는 조금 달랐다.</p>
<p>이번 강연은</p>
<blockquote>
<p><strong>&quot;AI 시대에 개발자는 무엇을 준비해야 하는가?&quot;</strong></p>
</blockquote>
<p>에 대한 이야기였다.</p>
<p>기술보다도 앞으로의 개발 문화와 커리어에 대해 훨씬 많은 생각을 하게 만든 시간이었다.</p>
<hr>
<h1 id="ai가-코딩을-대신하는-시대">AI가 코딩을 대신하는 시대?</h1>
<p>강연 초반에는 AI가 얼마나 빠르게 발전하고 있는지 다양한 사례를 보여주셨다.</p>
<p>최근 AI는 단순히 코드를 생성하는 수준을 넘어</p>
<ul>
<li>문서를 작성하고</li>
<li>전략을 세우고</li>
<li>여러 AI가 협업하는 <strong>Multi-Agent System</strong>까지 발전하고 있다.</li>
</ul>
<p>발표 자료에서도 최근 AI 연구 흐름으로</p>
<ul>
<li>Multi-Agent Systems</li>
<li>Vision-Language Models</li>
<li>Reasoning</li>
<li>AI Coding</li>
<li>AI for Strategy &amp; Design</li>
</ul>
<p>등이 소개되었다. Staying Relevant in the AI-Native Era.pdf</p>
<p>특히 기억에 남았던 말은</p>
<blockquote>
<p><strong>&quot;이제 코딩하는 시간은 없어졌다.&quot;</strong></p>
</blockquote>
<p>였다.</p>
<p>물론 코딩이 필요 없어졌다는 의미는 아니었다.</p>
<p>단순히 코드를 직접 입력하는 시간이 점점 줄어들고,</p>
<p>AI에게 적절한 Instruction를 주고,</p>
<p>전체 시스템을 설계하는 시간이 훨씬 많아질 것이라는 의미였다.</p>
<hr>
<h1 id="oracle에서는-ai를-어떻게-활용하고-있을까">Oracle에서는 AI를 어떻게 활용하고 있을까?</h1>
<p>Oracle 내부에서도 이미 다양한 AI Agent가 활용되고 있었다.</p>
<p>강연에서는 Oracle Health에서 개발 중인 <strong>Clinical AI Agent</strong> 사례가 소개되었다. Staying Relevant in the AI-Native Era.pdf</p>
<p>또한 Oracle의 하나의 서비스가 아니라</p>
<p>각 컴포넌트마다 전담 팀이 존재하며,</p>
<p>Agent들이 서로 협력하는 구조로 발전하고 있다는 설명도 이어졌다.</p>
<p>예전에는 하나의 AI가 하나의 일을 했다면,</p>
<p>앞으로는 여러 Agent가 역할을 나누어 하나의 업무를 수행하는 방향으로 발전한다는 것이다.</p>
<p>마침 얼마 전 샌디에이고에서 열린 AI 컨퍼런스에서도</p>
<p><strong>Multi-Agent Systems</strong>가 주요 주제로 다뤄졌다고 소개해 주셨다.</p>
<p>AI의 발전 방향이 단순한 ChatGPT가 아니라</p>
<p>여러 AI가 협업하는 구조라는 점이 굉장히 인상 깊었다.</p>
<hr>
<h1 id="amazon에서-시작된-경험">Amazon에서 시작된 경험</h1>
<p>강연 중 재미있었던 부분은 박사님의 커리어 이야기였다.</p>
<p>Amazon Alexa 프로젝트를 진행했던 경험과,</p>
<p>이후 Healthcare 분야에서 AI를 활용했던 사례를 들려주셨다.</p>
<p>특히 기억에 남았던 것은</p>
<blockquote>
<p>&quot;보고 싶은 드라마가 있는데 어느 채널인지 기억이 나지 않는다.&quot;</p>
</blockquote>
<p>는 예시였다.</p>
<p>리모컨으로 하나씩 채널을 찾는 대신</p>
<p>TV에게</p>
<blockquote>
<p>&quot;OO 드라마 틀어줘.&quot;</p>
</blockquote>
<p>라고 말하면</p>
<p>알아서 채널을 찾아주는 세상이 더 자연스럽지 않을까?</p>
<p>이러한 생각에서 음성 AI에 관심을 갖게 되었다고 말씀하셨다.</p>
<p>단순한 기술이 아니라</p>
<p><strong>실제 사용자의 불편함에서 출발한 문제 정의</strong>라는 점이 인상 깊었다.</p>
<hr>
<h1 id="앞으로는-ui도-달라질-것이다">앞으로는 UI도 달라질 것이다</h1>
<p>또 하나 기억에 남았던 내용은</p>
<p>앞으로의 사용자 인터페이스(UI)에 대한 이야기였다.</p>
<p>지금은</p>
<ul>
<li>버튼을 누르고</li>
<li>메뉴를 찾고</li>
<li>콘솔에 명령어를 입력하는 방식이 익숙하다.</li>
</ul>
<p>하지만 앞으로는</p>
<p>이러한 방식 자체가 점점 줄어들 것이라고 이야기하셨다.</p>
<p>AI와 자연스럽게 대화하는 것이</p>
<p>새로운 인터페이스가 될 가능성이 높다는 것이다.</p>
<p>생각해 보면</p>
<p>이미 ChatGPT나 음성비서도</p>
<p>기존 UI와는 완전히 다른 방식으로 동작한다.</p>
<p>이번 강연을 들으며</p>
<p>UI도 결국 AI 중심으로 계속 변화하고 있다는 점을 다시 느낄 수 있었다.</p>
<hr>
<h1 id="개발자는-무엇을-하게-될까">개발자는 무엇을 하게 될까?</h1>
<p>강연에서 가장 기억에 남았던 질문은 이것이었다.</p>
<blockquote>
<p>&quot;AI가 코딩을 다 하면 개발자는 무엇을 해야 할까?&quot;</p>
</blockquote>
<p>박사님의 대답은 명확했다.</p>
<p>앞으로 개발자는</p>
<p>코드를 한 줄 한 줄 작성하는 사람이 아니라</p>
<p><strong>Strategy와 Design을 담당하는 사람</strong>이 되어야 한다는 것이다.</p>
<p>AI에게</p>
<p>무엇을 만들지,</p>
<p>어떻게 만들지,</p>
<p>어떤 방향으로 문제를 해결해야 하는지를 설명하는 능력이</p>
<p>점점 더 중요해질 것이라고 말씀하셨다.</p>
<p>즉,</p>
<p>개발자의 역할이 사라지는 것이 아니라</p>
<p><strong>역할 자체가 바뀌고 있는 것</strong>이었다.</p>
<hr>
<h1 id="이번-강연에서-가장-인상-깊었던-한-문장">이번 강연에서 가장 인상 깊었던 한 문장</h1>
<p>이번 강연을 들으며 가장 오래 기억에 남은 문장은</p>
<blockquote>
<p><strong>&quot;코딩하는 시간은 줄어들고, 생각하는 시간은 늘어난다.&quot;</strong></p>
</blockquote>
<p>였다.</p>
<p>AI는 코드를 빠르게 만들어 줄 수 있다.</p>
<p>하지만</p>
<p>무엇을 만들지,</p>
<p>왜 그렇게 만들어야 하는지,</p>
<p>어떤 문제를 해결해야 하는지는</p>
<p>여전히 사람이 결정해야 한다.</p>
<p>강연을 들으며</p>
<p>앞으로는 단순히 프로그래밍 언어를 많이 아는 것보다</p>
<p>문제를 정의하고,</p>
<p>좋은 방향을 설계하는 능력이 훨씬 중요해질 것이라는 생각이 들었다.</p>
<p>다음 글에서는</p>
<p>AI 시대에도 여전히 중요한 <strong>커뮤니케이션 능력</strong>, <strong>좋은 코드를 보는 능력</strong>, 그리고 박사님이 들려주신 현실적인 커리어 조언들을 정리해보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기 - 드디어 출국🇺🇸]]></title>
            <link>https://velog.io/@daehyun_lee/USC-%EC%84%9C%EB%8D%98-%EC%BA%98%EB%A6%AC%ED%8F%AC%EB%8B%88%EC%95%84-%EB%8C%80%ED%95%99%EA%B5%90-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4%EC%97%90-%EC%B0%B8%EA%B0%80%ED%95%98%EB%8B%A4-2%ED%8E%B8</link>
            <guid>https://velog.io/@daehyun_lee/USC-%EC%84%9C%EB%8D%98-%EC%BA%98%EB%A6%AC%ED%8F%AC%EB%8B%88%EC%95%84-%EB%8C%80%ED%95%99%EA%B5%90-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4%EC%97%90-%EC%B0%B8%EA%B0%80%ED%95%98%EB%8B%A4-2%ED%8E%B8</guid>
            <pubDate>Mon, 03 Aug 2026 11:01:59 GMT</pubDate>
            <description><![CDATA[<h1 id="🇺🇸-usc-빅데이터-컨퍼런스-참가기---드디어-출국🇺🇸">🇺🇸 USC 빅데이터 컨퍼런스 참가기 - 드디어 출국🇺🇸</h1>
<p>1편에서는 USC 빅데이터 컨퍼런스에 지원하게 된 계기와 선발 과정, 그리고 출국 직전까지의 이야기를 적어보았다.</p>
<blockquote>
<p>📖 이전 이야기<br><strong><a href="https://velog.io/@daehyun_lee/USC-%EC%84%9C%EB%8D%98-%EC%BA%98%EB%A6%AC%ED%8F%AC%EB%8B%88%EC%95%84-%EB%8C%80%ED%95%99%EA%B5%90-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4%EC%97%90-%EC%B0%B8%EA%B0%80%ED%95%98%EB%8B%A4-1%ED%8E%B8">🇺🇸 USC (서던 캘리포니아 대학교) 빅데이터 컨퍼런스에 참가하다 - 1편</a></strong></p>
</blockquote>
<p>드디어 출국.</p>
<p>이번 글에서는 미국에 도착해서 처음 느꼈던 분위기와 USC에서의 첫인상, 그리고 개인적으로 오래 기억에 남을 이야기를 적어보려고 한다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/2ab2f2b1-eb00-43f2-958a-02fa4838ba88/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/78845c83-b56a-4194-a602-4d79f26d00e5/image.jpeg" alt=""></p>
<h1 id="드디어-la-도착">드디어 LA 도착</h1>
<p>LA에 도착한 첫날.</p>
<p>미국은 이번이 처음은 아니었지만, 오랜만에 밟는 미국 땅은 여전히 자유로운 분위기였다.</p>
<p>공항을 나와 바깥 공기를 마셨는데 생각보다 덥지 않았다.</p>
<p>한국의 한여름을 생각하고 왔는데 오히려 선선해서 놀랐다.</p>
<p>역시 캘리포니아는 날씨 하나만큼은 정말 좋다는 생각이 들었다.</p>
<hr>
<h1 id="생각보다-큰-usc-캠퍼스">생각보다 큰 USC 캠퍼스</h1>
<p>학교에 도착해서 가장 먼저 느낀 건</p>
<blockquote>
<p>&quot;생각보다 정말 크다.&quot;</p>
</blockquote>
<p>였다.</p>
<p>학교 안에 마트도 있고 식당도 있고 카페도 있고...</p>
<p>거의 작은 도시 하나가 학교 안에 들어와 있는 느낌이었다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/4b29ce95-4535-45b2-9eeb-b2c9fd954c2c/image.jpeg" alt=""></p>
<p>특히 신기했던 건</p>
<p><strong>Trader Joe&#39;s</strong>가 학교 바로 옆에 있다는 점이었다.</p>
<p>미국에 오면 한 번쯤 꼭 가보는 마트인데,</p>
<p>학교와 너무 가까워서 학생들이 장보기에는 정말 좋은 환경이라는 생각이 들었다.</p>
<p>&#39;USC 학생들은 정말 살기 좋은 환경에서 학교를 다니는구나.&#39;</p>
<p>라는 생각이 가장 먼저 들었다.</p>
<hr>
<h1 id="첫날-저녁은-역시-한식">첫날 저녁은 역시 한식</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/2c7261c8-84d8-4be2-92a9-0d7ad2c6b721/image.jpeg" alt=""></p>
<p>첫날 저녁은 코리아타운에 있는 <strong>북창동순두부(BCD Tofu House)</strong> 로 갔다.</p>
<p>사실 미국에 오면 햄버거나 스테이크부터 먹을 줄 알았는데,</p>
<p>비행 시간이 워낙 길다 보니 자연스럽게 따뜻한 국물이 생각났다.</p>
<p>순두부도 맛있었지만,</p>
<p>개인적으로는 여기 갈비가 정말 맛있다.</p>
<p>미국에 갈 일이 있다면 한 번쯤은 꼭 추천하고 싶은 식당이다.</p>
<hr>
<h1 id="우버에서-만난-70살-기사님">우버에서 만난 70살 기사님</h1>
<p>하지만 이날 가장 기억에 남았던 건 저녁이 아니었다.</p>
<p>기숙사로 돌아가는 우버에서 만난 한 분과의 대화였다.</p>
<p>기사님은 올해 70살이셨고,</p>
<p>35년 전 미국으로 건너오셨다고 했다.</p>
<p>예전 럭키금성(LG)에서 근무하시다가</p>
<p>사업 때문에 미국에 오셨고,</p>
<p>정착하여 작년까지 일을 하셨다고 했다.</p>
<p>이야기를 나누다 보니 자연스럽게</p>
<p>한국의 취업 이야기로 이어졌다.</p>
<p>그때 기사님이 하신 말이 아직도 기억에 남는다.</p>
<blockquote>
<p>&quot;요즘 한국의 젊은 친구들은 정말 힘들겠다.&quot;</p>
</blockquote>
<p>사실 조금 놀랐다.</p>
<p>보통은</p>
<blockquote>
<p>&quot;요즘 젊은 친구들은 끈기가 부족하다.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;노력을 안 해서 그렇다.&quot;</p>
</blockquote>
<p>같은 이야기를 많이 들었기 때문이다.</p>
<p>그런데 기사님의 생각은 전혀 달랐다.</p>
<hr>
<p>기사님은 본인이 <strong>75학번</strong>이라고 말씀하시며</p>
<p>당시를 이렇게 회상하셨다.</p>
<blockquote>
<p>&quot;우리 때는 취업이 정말 쉬웠어.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;조금 과장해서 말하면 지원서만 넣어도 여러 군데에서 합격 연락이 왔지.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;회사에서 사람을 뽑는 시대였어.&quot;</p>
</blockquote>
<p>그리고 잠시 말을 멈추시더니</p>
<p>이어서 이렇게 말씀하셨다.</p>
<blockquote>
<p>&quot;근데 지금은 아니잖아.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;너희는 우리보다 훨씬 좋은 교육을 받고 더 열심히 살아도 취업이 어렵잖아.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;우리 때랑은 비교 자체가 안 되는 시대야.&quot;</p>
</blockquote>
<p>그 말을 듣는데 괜히 마음이 묵직해졌다.</p>
<hr>
<h1 id="한국-청년들은-정말-잘한다">&quot;한국 청년들은 정말 잘한다&quot;</h1>
<p>기사님은 미국에서 오래 살아오면서 느낀 점도 이야기해 주셨다.</p>
<blockquote>
<p>&quot;한국 젊은 친구들은 정말 똑똑해.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;미국 친구들보다 더 성실하고 더 열심히 하는 경우도 많아.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;그런데 한국 사회가 그걸 다 받아줄 수 없는 구조인 것 같아.&quot;</p>
</blockquote>
<p>그리고 한국과 미국의 가장 큰 차이도 이야기해 주셨다.</p>
<p>기사님의 생각으로는</p>
<p>한국은 자본이 부동산에 너무 많이 묶여 있고,</p>
<p>도전보다 안정이 우선되는 분위기가 강하다고 하셨다.</p>
<p>반면 미국에서는</p>
<p>새로운 아이디어나 창업,</p>
<p>투자에 훨씬 적극적으로 돈이 흘러간다고 말씀하셨다.</p>
<p>그 차이가</p>
<p>새로운 기업을 만드는 문화의 차이로 이어지는 것 같다고도 했다.</p>
<p>물론 모든 사람이 같은 생각을 하는 것은 아니겠지만,</p>
<p>35년 동안 미국에서 생활한 한 사람의 경험이라 그런지</p>
<p>더 흥미롭게 들렸다.</p>
<hr>
<p>마지막으로 기사님은 이런 말을 남기셨다.</p>
<blockquote>
<p>&quot;아직 젊잖아.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;꿈이 있다면 절대 도전을 멈추지 마.&quot;</p>
</blockquote>
<p>그리고</p>
<p>본인도 올해 70살이 되었지만,</p>
<p>미국의 높은 물가와 사회 구조 때문에</p>
<p>지금도 우버를 운행하고 있다고 담담하게 말씀하셨다.</p>
<p>짧은 이동 시간이었지만,</p>
<p>미국에 도착한 첫날부터</p>
<p>생각보다 많은 것을 배우게 된 시간이었다.</p>
<hr>
<h1 id="요세미티-국립공원">요세미티 국립공원</h1>
<p>USC 프로그램이 시작되기 전,</p>
<p>주말을 이용해 요세미티 국립공원에도 다녀왔다.</p>
<p>사실 예전 미국 여행 때는 그랜드 캐니언을 갔었기 때문에,</p>
<p>이번에는 다른 곳을 가보고 싶었다.</p>
<p>결과적으로는 정말 좋은 선택이었다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c6d4b8d0-573b-4728-8430-59913fcc3aff/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/baa95959-880e-445b-b671-8eafcb4e9f0e/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/c9e08c5d-fb4e-49df-9c05-a71f12cc4bd9/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/92d171fc-23ae-431e-b468-44d58385a16e/image.jpeg" alt=""></p>
<p>사진으로는 다 담기지 않지만,</p>
<p>실제로 보면 압도적인 자연이라는 표현이 가장 잘 어울리는 곳이었다.</p>
<p>미국의 국립공원이 왜 유명한지 직접 와보니 알 것 같았다.</p>
<hr>
<h1 id="드디어-시작되는-usc-생활">드디어 시작되는 USC 생활</h1>
<p>요세미티를 다녀온 뒤</p>
<p>본격적으로 USC에서의 생활이 시작되었다.</p>
<p>학교를 돌아다니면서 캠퍼스도 구경하고,</p>
<p>학생증도 발급받았다.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/22f911fc-05d1-4ab3-8ecf-4cbb10232ad9/image.jpeg" alt=""></p>
<p>미국 드라마에서만 보던 캠퍼스를 직접 걷고 있다는 사실이 아직도 신기했다.</p>
<p>그리고 드디어 받은 USC 학생증.</p>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/0b1f1e88-49ee-4828-a057-a8b2d53aef50/image.jpeg" alt=""></p>
<p>비록 단기간 프로그램이었지만,</p>
<p>학생증 하나만으로도 괜히 진짜 USC 학생이 된 기분이었다.</p>
<hr>
<p>여기까지가 USC에서의 첫 번째 주말이었다.</p>
<p>새로운 환경,</p>
<p>새로운 사람들,</p>
<p>그리고 우연히 만난 우버 기사님까지.</p>
<p>아직 프로그램은 시작도 하지 않았지만,</p>
<p>벌써부터 많은 것을 느끼고 배우고 있었다.</p>
<p>다음 편에서는</p>
<p>드디어 <strong>USC에서 진행된 첫 번째 데이터 사이언스 강의와 컨퍼런스 이야기</strong>를 적어보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[레벨 2] 프로그래머스 오늘의 문제 : 타겟 넘버 (DFS)]]></title>
            <link>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-C-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84-DFS%EB%A1%9C-%EB%AA%A8%EB%93%A0-%EA%B2%BD%EC%9A%B0%EC%9D%98-%EC%88%98-%ED%83%90%EC%83%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-C-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84-DFS%EB%A1%9C-%EB%AA%A8%EB%93%A0-%EA%B2%BD%EC%9A%B0%EC%9D%98-%EC%88%98-%ED%83%90%EC%83%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Jul 2026 23:17:33 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래머스-c-타겟-넘버---dfs로-모든-경우의-수-탐색하기">[프로그래머스 C++] 타겟 넘버 - DFS로 모든 경우의 수 탐색하기</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/3732eae9-8fa1-4e29-84de-2a62c850663a/image.jpg" alt=""></p>
<h2 id="문제-링크">문제 링크</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43165">프로그래머스 - 타겟 넘버</a></p>
<hr>
<h1 id="문제-설명">문제 설명</h1>
<p><code>numbers</code> 배열에 들어 있는 모든 숫자를 순서대로 사용하면서, 각 숫자 앞에 <code>+</code> 또는 <code>-</code>를 붙여 <code>target</code>을 만드는 방법의 수를 구하는 문제다.</p>
<p>예를 들어 다음과 같은 입력이 주어진다.</p>
<pre><code class="language-text">numbers = [1, 1, 1, 1, 1]
target = 3</code></pre>
<p>숫자 3을 만드는 방법은 다음과 같다.</p>
<pre><code class="language-text">-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3</code></pre>
<p>따라서 정답은 <code>5</code>가 된다.</p>
<hr>
<h1 id="처음-문제를-보고-든-생각">처음 문제를 보고 든 생각</h1>
<p>처음 문제를 읽었을 때는 배열에 있는 숫자를 모두 사용해 가능한 경우의 수를 계산해야 하므로,</p>
<blockquote>
<p>완전탐색 문제인가?</p>
</blockquote>
<p>라는 생각이 먼저 들었다.</p>
<p>각 숫자마다 더하는 경우와 빼는 경우가 존재하므로, 모든 조합을 확인해야 하기 때문이다.</p>
<p>그런데 문제를 조금 더 생각해보니 이 과정을 <strong>그래프 탐색</strong>, 정확히는 트리 탐색으로 표현할 수 있었다.</p>
<p>사실 이 문제가 프로그래머스의 DFS/BFS 알고리즘 문제에 포함되어 있어서 DFS나 BFS로 풀어야 한다는 힌트를 어느 정도 받은 것도 있다.</p>
<p>그중에서도 나는 DFS가 더 자연스럽다고 생각했다.</p>
<hr>
<h1 id="문제를-트리로-생각해보기">문제를 트리로 생각해보기</h1>
<p>각 단계에서는 현재 숫자를 더할지, 뺄지 선택할 수 있다.</p>
<p>예를 들어 첫 번째 숫자가 <code>1</code>이라면 다음 두 방향으로 나뉜다.</p>
<pre><code class="language-text">현재 합계 0
├── +1
└── -1</code></pre>
<p>다음 숫자에서도 다시 두 가지 선택지가 생긴다.</p>
<pre><code class="language-text">                 0
              /     \
            +1       -1
           /  \     /  \
         +1   -1  +1   -1</code></pre>
<p>여기서 정확히 말하면 배열의 각 숫자 자체가 노드라기보다는,</p>
<blockquote>
<p><strong>지금까지 몇 번째 숫자까지 사용했고, 현재 합계가 얼마인지</strong></p>
</blockquote>
<p>를 하나의 상태, 즉 노드로 볼 수 있다.</p>
<p>그리고 각 노드에서 다음 숫자를</p>
<ul>
<li>더하는 방향</li>
<li>빼는 방향</li>
</ul>
<p>으로 두 개의 가지가 만들어진다.</p>
<p>따라서 숫자가 <code>n</code>개라면 트리의 깊이는 <code>n</code>이 되고, 마지막 깊이까지 내려갔을 때 현재 합계가 <code>target</code>과 같은지 확인하면 된다.</p>
<hr>
<h1 id="dfs를-선택한-이유">DFS를 선택한 이유</h1>
<p>이 문제는 BFS로도 해결할 수 있다.</p>
<p>하지만 모든 숫자를 끝까지 사용한 뒤 결과를 확인해야 하므로, 한 경로를 마지막까지 내려가는 DFS가 구현하기 더 간단하다고 생각했다.</p>
<p>DFS의 흐름은 다음과 같다.</p>
<pre><code class="language-text">현재 합계에서 numbers[index]를 더한다.
↓

다음 숫자로 이동한다.

↓

현재 합계에서 numbers[index]를 뺀다.
↓

다음 숫자로 이동한다.</code></pre>
<p>그리고 모든 숫자를 사용했을 때 현재 합계가 <code>target</code>과 같다면 정답의 개수를 증가시킨다.</p>
<hr>
<h2 id="나의-풀이">&lt;나의 풀이&gt;</h2>
<pre><code class="language-cpp">#include &lt;string&gt;
#include &lt;vector&gt;
using namespace std;

int count = 0; 

int DFS(int node , vector&lt;int&gt; arr , int index , int targetnum){

    if(arr.size() == index){ 
        if(node == targetnum){ 
            count++; 
        }
        return 0; 
    }

    DFS(node + arr[index] , arr , index + 1 , targetnum); 
    DFS(node - arr[index], arr , index + 1 , targetnum); 
    return 0; 
}

int solution(vector&lt;int&gt; numbers, int target) {  // 숫자는 모두 사용해야함
    // numbers 에는 숫자들이 나열되어 있다. target은 각 숫자들을 더하거나 빼서 만들 숫자
    int answer = 0;

    // left = -  , right = + 로 잡고 출발한다
    DFS(0 , numbers , 0 , target);

    answer = count; 

    return answer;
}</code></pre>
<p>DFS는 다음 상태를 인자로 받는다.</p>
<pre><code class="language-text">node      : 현재까지 계산한 합계
arr       : 사용할 숫자 배열
index     : 현재 사용할 숫자의 위치
targetnum : 만들어야 하는 목표 숫자</code></pre>
<p>처음에는 아직 아무 숫자도 사용하지 않았기 때문에 다음과 같이 시작한다.</p>
<pre><code class="language-cpp">DFS(0, numbers, 0, target);</code></pre>
<ul>
<li>현재 합계는 <code>0</code></li>
<li>첫 번째 숫자의 인덱스는 <code>0</code></li>
</ul>
<p>이다.</p>
<hr>
<h1 id="종료-조건">종료 조건</h1>
<p>재귀 함수에서 가장 중요한 부분은 종료 조건이다.</p>
<pre><code class="language-cpp">if (arr.size() == index) {
    if (node == targetnum) {
        count++;
    }

    return 0;
}</code></pre>
<p><code>index</code>가 배열의 크기와 같아졌다는 것은 모든 숫자를 사용했다는 의미다.</p>
<p>이때 현재까지 계산한 값인 <code>node</code>가 <code>targetnum</code>과 같다면 타겟 넘버를 만든 것이므로 <code>count</code>를 증가시킨다.</p>
<hr>
<h1 id="두-개의-재귀-호출">두 개의 재귀 호출</h1>
<p>이 문제의 핵심은 다음 두 줄이다.</p>
<pre><code class="language-cpp">DFS(node + arr[index], arr, index + 1, targetnum);
DFS(node - arr[index], arr, index + 1, targetnum);</code></pre>
<p>현재 숫자를 더한 경우와 뺀 경우를 각각 탐색한다.</p>
<p>처음 숫자뿐만 아니라 모든 숫자에 대해 <code>+</code>, <code>-</code> 두 방향을 모두 확인해야 한다.</p>
<p>예를 들어 현재 합계가 <code>3</code>이고 현재 숫자가 <code>2</code>라면 다음 상태는 두 개가 된다.</p>
<pre><code class="language-text">3 + 2 = 5
3 - 2 = 1</code></pre>
<p>DFS는 먼저 한쪽 경로를 마지막까지 탐색하고, 탐색이 끝나면 되돌아와 다른 경로를 탐색한다.</p>
<hr>
<h1 id="실행-결과">실행 결과</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/85e05195-8cd4-4a8f-802a-1ae94f5137d0/image.png" alt=""></p>
<p>정답이었다.</p>
<p>간단한 DFS를 구현하는 문제처럼 보이지만, 생각보다 문제에서 요구하는 핵심 아이디어가 분명히 있었다.</p>
<hr>
<h1 id="이-문제의-핵심-포인트">이 문제의 핵심 포인트</h1>
<h2 id="1-문제를-보고-dfs를-떠올릴-수-있는가">1. 문제를 보고 DFS를 떠올릴 수 있는가?</h2>
<p>보통 DFS/BFS 문제라고 하면 그래프의 정점과 간선이 직접 주어지는 경우가 많다.</p>
<p>하지만 이 문제에는 일반적인 그래프가 등장하지 않는다.</p>
<p>대신 각 숫자마다 다음 두 가지 선택이 존재한다.</p>
<pre><code class="language-text">현재 숫자를 더한다.
현재 숫자를 뺀다.</code></pre>
<p>하나의 상태에서 두 개의 다음 상태로 분기되고, 모든 숫자를 사용한 마지막 상태에서 결과를 확인한다.</p>
<p>따라서 이 문제는 다음과 같은 이진 트리 탐색 문제로 볼 수 있다.</p>
<pre><code class="language-text">현재 상태
├── 현재 숫자 더하기
└── 현재 숫자 빼기</code></pre>
<p>DFS라고 생각해야 하는 핵심 이유는,</p>
<blockquote>
<p><strong>각 숫자마다 발생하는 두 가지 선택을 하나의 분기로 보고, 모든 선택 경로를 끝까지 탐색해야 하기 때문</strong></p>
</blockquote>
<p>이다.</p>
<p>그래프가 직접 주어지지 않아도 선택의 과정을 그래프로 바꿔 생각할 수 있어야 한다.</p>
<hr>
<h2 id="2-와---두-가지-경우를-모두-탐색해야-한다">2. <code>+</code>와 <code>-</code> 두 가지 경우를 모두 탐색해야 한다</h2>
<p>두 번째 핵심은 재귀 호출을 반드시 두 번 해야 한다는 것이다.</p>
<pre><code class="language-cpp">DFS(node + arr[index], arr, index + 1, targetnum);
DFS(node - arr[index], arr, index + 1, targetnum);</code></pre>
<p>한쪽만 탐색하면 가능한 경우의 절반을 놓치게 된다.</p>
<p>특히 첫 번째 숫자를 무조건 양수로 시작한다고 생각하면 안 된다.</p>
<p>문제에서는 첫 번째 숫자에도 <code>+</code> 또는 <code>-</code>를 붙일 수 있기 때문이다.</p>
<pre><code class="language-text">+numbers[0]
-numbers[0]</code></pre>
<p>따라서 초기값을 <code>0</code>으로 두고, 첫 번째 숫자부터 두 방향으로 분기해야 한다.</p>
<hr>
<h1 id="시간-복잡도">시간 복잡도</h1>
<p>숫자가 하나 추가될 때마다 선택지는 두 배가 된다.</p>
<p>숫자의 개수를 <code>n</code>이라고 하면 가능한 경우의 수는 다음과 같다.</p>
<pre><code class="language-text">2 × 2 × 2 × ... × 2 = 2ⁿ</code></pre>
<p>따라서 시간 복잡도는</p>
<pre><code class="language-text">O(2ⁿ)</code></pre>
<p>이다.</p>
<p>문제에서 숫자의 개수는 최대 20개이므로 최대 경우의 수는 다음과 같다.</p>
<pre><code class="language-text">2²⁰ = 1,048,576</code></pre>
<p>약 100만 개 정도이므로 완전탐색으로 충분히 해결할 수 있다.</p>
<p>재귀 호출의 최대 깊이는 숫자의 개수와 같으므로 공간 복잡도는 호출 스택 기준으로 <code>O(n)</code>이다.</p>
<hr>
<h1 id="코드를-조금-개선한다면">코드를 조금 개선한다면</h1>
<p>내 코드는 정답이지만 몇 가지 개선할 수 있는 부분이 있다.</p>
<h2 id="1-배열을-참조로-전달하기">1. 배열을 참조로 전달하기</h2>
<p>현재 코드는 DFS를 호출할 때마다 <code>vector</code>를 복사한다.</p>
<pre><code class="language-cpp">int DFS(int node, vector&lt;int&gt; arr, int index, int targetnum)</code></pre>
<p>배열의 내용을 수정하지 않으므로 <code>const</code> 참조로 전달하는 것이 더 효율적이다.</p>
<pre><code class="language-cpp">void DFS(
    int node,
    const vector&lt;int&gt;&amp; arr,
    int index,
    int targetnum
)</code></pre>
<hr>
<h2 id="2-전역-변수-사용-줄이기">2. 전역 변수 사용 줄이기</h2>
<p><code>count</code>를 전역 변수로 선언하면 함수가 여러 번 호출될 때 이전 값이 남아 있을 수 있다.</p>
<pre><code class="language-cpp">int count = 0;</code></pre>
<p>최소한 <code>solution()</code>이 실행될 때 <code>count</code>를 초기화하는 것이 안전하다.</p>
<pre><code class="language-cpp">count = 0;</code></pre>
<p>또는 DFS가 경우의 수를 직접 반환하도록 만들면 전역 변수 없이 해결할 수 있다.</p>
<hr>
<h1 id="개선한-풀이">개선한 풀이</h1>
<pre><code class="language-cpp">#include &lt;vector&gt;

using namespace std;

int dfs(
    const vector&lt;int&gt;&amp; numbers,
    int index,
    int currentSum,
    int target
) {
    if (index == numbers.size()) {
        return currentSum == target ? 1 : 0;
    }

    int plusCount = dfs(
        numbers,
        index + 1,
        currentSum + numbers[index],
        target
    );

    int minusCount = dfs(
        numbers,
        index + 1,
        currentSum - numbers[index],
        target
    );

    return plusCount + minusCount;
}

int solution(vector&lt;int&gt; numbers, int target) {
    return dfs(numbers, 0, 0, target);
}</code></pre>
<p>이 방식은 각 경로에서 타겟 넘버를 만들었다면 <code>1</code>, 만들지 못했다면 <code>0</code>을 반환한다.</p>
<p>그리고 두 방향에서 반환된 값을 더한다.</p>
<pre><code class="language-cpp">return plusCount + minusCount;</code></pre>
<p>전역 변수 없이 함수의 반환값만으로 정답을 계산할 수 있어 조금 더 깔끔하다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>처음에는 단순한 완전탐색 문제라고 생각했지만, 핵심은 선택 과정을 트리로 변환하는 데 있었다.</p>
<p>각 숫자에서</p>
<pre><code class="language-text">더할 것인가?
뺄 것인가?</code></pre>
<p>라는 두 개의 선택지가 만들어지고, 이를 마지막 깊이까지 탐색하면 모든 경우의 수를 확인할 수 있다.</p>
<p>이번 문제에서 기억해야 할 포인트는 다음과 같다.</p>
<pre><code class="language-text">각 상태에서 두 가지 선택지가 생긴다.
↓

선택 과정을 이진 트리로 표현할 수 있다.
↓

모든 숫자를 사용한 뒤 결과를 확인해야 한다.
↓

DFS로 각 경로를 마지막까지 탐색한다.</code></pre>
<p>DFS/BFS 문제라고 해서 항상 그래프가 직접 주어지는 것은 아니다.</p>
<p>문제 속 선택과 상태 변화를 보고 스스로 그래프 구조를 만들어내는 것이 더 중요한 문제였다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터는 어떤 상품을 함께 선택할까? - Association Rule Learning]]></title>
            <link>https://velog.io/@daehyun_lee/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%8A%94-%EC%96%B4%EB%96%A4-%EC%83%81%ED%92%88%EC%9D%84-%ED%95%A8%EA%BB%98-%EC%84%A0%ED%83%9D%ED%95%A0%EA%B9%8C-Association-Rule-Learning</link>
            <guid>https://velog.io/@daehyun_lee/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%8A%94-%EC%96%B4%EB%96%A4-%EC%83%81%ED%92%88%EC%9D%84-%ED%95%A8%EA%BB%98-%EC%84%A0%ED%83%9D%ED%95%A0%EA%B9%8C-Association-Rule-Learning</guid>
            <pubDate>Tue, 28 Jul 2026 18:17:50 GMT</pubDate>
            <description><![CDATA[<h1 id="usc에서-배우는-데이터-사이언스-8">USC에서 배우는 데이터 사이언스 (8)</h1>
<h1 id="데이터는-어떤-상품을-함께-선택할까---association-rule-learning">데이터는 어떤 상품을 함께 선택할까? - Association Rule Learning</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/252303bb-52e2-43cf-b925-3163ca0cbcbc/image.jpg" alt=""></p>
<p>이번 강의에서는 지금까지 배웠던 Classification이나 Regression과는 조금 다른 주제를 다뤘다.</p>
<p>바로 <strong>Association Learning(연관 규칙 학습)</strong> 이다.</p>
<p>처음에는 &quot;Association&quot;이라는 단어만 보고 추천 시스템을 만드는 알고리즘 정도라고 생각했다.</p>
<p>하지만 강의를 듣고 보니,</p>
<p>Association Rule은 <strong>예측(Prediction)</strong> 을 위한 알고리즘이라기보다,</p>
<blockquote>
<p><strong>데이터 속에서 숨겨진 관계를 발견하는(Data Mining) 기법</strong></p>
</blockquote>
<p>이라는 점이 가장 큰 차이였다.</p>
<hr>
<h1 id="association-learning이란">Association Learning이란?</h1>
<p>교수님은 가장 먼저 Association Learning을 이렇게 설명하셨다.</p>
<blockquote>
<p>데이터 안에서 함께 자주 등장하는 패턴을 찾아내는 방법</p>
</blockquote>
<p>예를 들어 마트에서 장을 본다고 생각해 보자.</p>
<pre><code>우유
빵
버터</code></pre><p>이 세 상품이 항상 같이 구매된다면</p>
<p>이 세 상품 사이에는 어떤 관계가 존재할 가능성이 높다.</p>
<p>Association Learning은</p>
<p>이처럼</p>
<p><strong>&quot;무엇이 무엇과 함께 나타나는가?&quot;</strong></p>
<p>를 찾아내는 것이 목표이다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="하지만-association은-인과관계가-아니다">하지만 Association은 인과관계가 아니다</h1>
<p>강의에서 가장 먼저 강조했던 부분도 이것이었다.</p>
<pre><code>Bread

↓

Butter</code></pre><p>라는 Rule이 있다고 해서</p>
<p>빵이 버터를 구매하게 만든 것은 아니다.</p>
<p>Association Rule은</p>
<p>단순히</p>
<blockquote>
<p><strong>함께 나타날 가능성이 높다</strong></p>
</blockquote>
<p>는 의미일 뿐이다.</p>
<p>즉,</p>
<p>Association은 Correlation이지</p>
<p>Causation은 아니다.</p>
<p>이 부분은 교수님이 여러 번 강조했던 내용이라 특히 기억에 남았다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="rule은-어떻게-표현할까">Rule은 어떻게 표현할까?</h1>
<p>Association Rule은</p>
<p>아주 간단하게 표현된다.</p>
<pre><code>X → Y</code></pre><p>예를 들어</p>
<pre><code>{Bread}

↓

{Butter}</code></pre><p>라는 Rule은</p>
<p>빵을 구매한 사람이</p>
<p>버터도 함께 구매할 가능성이 높다는 의미이다.</p>
<p>하지만</p>
<p>이 Rule이 좋은 Rule인지는</p>
<p>아직 알 수 없다.</p>
<p>그래서</p>
<p>세 가지 기준이 등장한다.</p>
<hr>
<h1 id="support">Support</h1>
<p>첫 번째는</p>
<p>Support이다.</p>
<p>Support는</p>
<blockquote>
<p>이 조합이 얼마나 자주 등장했는가</p>
</blockquote>
<p>를 의미한다.</p>
<p>예를 들어</p>
<p>100개의 거래 중</p>
<pre><code>Bread + Butter</code></pre><p>가</p>
<p>30번 등장했다면</p>
<p>Support는 30%이다.</p>
<p>즉,</p>
<p>인기 있는 조합인지</p>
<p>먼저 확인하는 과정이다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="confidence">Confidence</h1>
<p>두 번째는</p>
<p>Confidence이다.</p>
<p>Confidence는</p>
<p>조금 더 의미 있는 값이다.</p>
<pre><code>Bread를 구매한 사람 중

Butter도 구매한 사람은?</code></pre><p>즉,</p>
<p>조건부 확률이다.</p>
<p>Support가</p>
<p>얼마나 자주 등장했는지를 본다면,</p>
<p>Confidence는</p>
<p>실제로 함께 구매될 가능성을 의미한다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="confidence가-높다고-좋은-rule일까">Confidence가 높다고 좋은 Rule일까?</h1>
<p>교수님은 여기서 재미있는 예시를 보여주셨다.</p>
<pre><code>Toothbrush

↓

Milk</code></pre><p>Confidence가</p>
<p>70%였다.</p>
<p>처음에는</p>
<p>&quot;굉장히 좋은 Rule 아닌가?&quot;</p>
<p>라고 생각했다.</p>
<p>그런데</p>
<p>알고 보니</p>
<p>원래 우유는 거의 모든 사람이 구매하는 상품이었다.</p>
<p>즉,</p>
<p>칫솔 때문이 아니라</p>
<p>우유 자체가 인기 상품이었던 것이다.</p>
<p>그래서</p>
<p>Confidence 하나만으로는</p>
<p>좋은 Rule인지 판단할 수 없다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="lift가-필요한-이유">Lift가 필요한 이유</h1>
<p>그래서 등장한 것이</p>
<p>Lift이다.</p>
<p>Lift는</p>
<blockquote>
<p>정말로 두 상품이 서로 영향을 주는가?</p>
</blockquote>
<p>를 측정한다.</p>
<p>교수님은</p>
<p>Lift가 1보다 크면</p>
<p>실제로 함께 구매되는 경향이 있다고 설명하셨다.</p>
<p>반대로</p>
<p>1보다 작으면</p>
<p>Confidence가 높더라도</p>
<p>의미 있는 Rule이 아닐 수 있다.</p>
<p>Confidence만 보고 판단하면</p>
<p>잘못된 결론을 내릴 수 있다는 점이 흥미로웠다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="lift만-봐도-안-된다">Lift만 봐도 안 된다</h1>
<p>그런데</p>
<p>Lift도 완벽하지 않았다.</p>
<p>교수님은</p>
<p>두 가지 경우를 비교해 주셨다.</p>
<p>첫 번째는</p>
<p>Lift는 매우 높지만</p>
<p>Support가 매우 낮은 경우.</p>
<p>이 경우는</p>
<p>우연히 발생한 패턴일 수도 있다.</p>
<p>반대로</p>
<p>Support는 매우 높지만</p>
<p>Lift가 1에 가까운 경우도 있다.</p>
<p>이 경우는</p>
<p>그 상품이 원래 많이 팔리는 상품일 뿐</p>
<p>특별한 연관성은 없다.</p>
<p>결국</p>
<p>Support</p>
<p>Confidence</p>
<p>Lift</p>
<p>세 가지를 함께 봐야 한다는 것이 이번 강의의 핵심이었다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="어디에-활용될까">어디에 활용될까?</h1>
<p>교수님은 Association Learning이</p>
<p>생각보다 다양한 분야에서 사용된다고 설명하셨다.</p>
<p>대표적으로</p>
<ul>
<li>마트 상품 배치</li>
<li>추천 시스템</li>
<li>의료 데이터 분석</li>
<li>콘텐츠 추천</li>
</ul>
<p>등이 있다.</p>
<p>특히 마트에서는</p>
<p>같이 구매되는 상품을 가까운 위치에 배치해</p>
<p>추가 구매를 유도한다고 한다.</p>
<p>우리가 마트에서</p>
<p>우유 옆에 시리얼,</p>
<p>맥주 옆에 과자가 있는 이유도</p>
<p>이런 데이터 분석 덕분이라는 점이 재미있었다. Lecture-Note-06-Association.pdf</p>
<hr>
<h1 id="그런데-문제가-하나-있다">그런데 문제가 하나 있다</h1>
<p>여기까지는</p>
<p>Association Rule이 굉장히 쉬워 보였다.</p>
<p>하지만 교수님은</p>
<p>곧바로 하나의 질문을 던지셨다.</p>
<blockquote>
<p>상품이 100,000개라면?</p>
</blockquote>
<p>가능한 Rule의 개수는</p>
<p>상상하기 어려울 정도로 많아진다.</p>
<p>모든 조합을 전부 계산하는 것은</p>
<p>사실상 불가능하다.</p>
<p>그래서</p>
<p>효율적으로</p>
<p>필요한 조합만 찾아내는 알고리즘이 등장한다.</p>
<p>바로</p>
<p><strong>Apriori Algorithm</strong>이다.</p>
<p>다음 강의에서는</p>
<p>Apriori가 어떻게</p>
<p>수많은 조합을 효율적으로 줄여가며</p>
<p>Frequent Itemset을 찾는지 정리해보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | 모델을 학습하기 전에 데이터를 먼저 의심해야 한다 | Data Exploration 7 Steps (2) ]]></title>
            <link>https://velog.io/@daehyun_lee/%EB%AA%A8%EB%8D%B8%EC%9D%84-%ED%95%99%EC%8A%B5%ED%95%98%EA%B8%B0-%EC%A0%84%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%A8%BC%EC%A0%80-%EC%9D%98%EC%8B%AC%ED%95%B4%EC%95%BC-%ED%95%9C%EB%8B%A4-Data-Exploration-7-Steps</link>
            <guid>https://velog.io/@daehyun_lee/%EB%AA%A8%EB%8D%B8%EC%9D%84-%ED%95%99%EC%8A%B5%ED%95%98%EA%B8%B0-%EC%A0%84%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%A8%BC%EC%A0%80-%EC%9D%98%EC%8B%AC%ED%95%B4%EC%95%BC-%ED%95%9C%EB%8B%A4-Data-Exploration-7-Steps</guid>
            <pubDate>Wed, 22 Jul 2026 18:37:21 GMT</pubDate>
            <description><![CDATA[<h1 id="모델을-학습하기-전에-데이터를-먼저-의심해야-한다--data-exploration-7-steps">모델을 학습하기 전에 데이터를 먼저 의심해야 한다 | Data Exploration 7 Steps</h1>
<p>대부분의 현실 데이터는<br>행과 열로 구성된 <strong>Tabular Data</strong> 형태로 저장되어 있다.</p>
<p>처음 머신러닝을 공부할 때는<br>데이터가 준비되어 있으면 곧바로 모델을 선택하고 학습하면 되는 줄 알았다.</p>
<pre><code class="language-text">데이터 준비
↓
모델 선택
↓
학습
↓
평가</code></pre>
<p>하지만 실제 데이터는 그렇게 친절하지 않았다.</p>
<pre><code class="language-text">값이 비어 있고,

같은 의미인데 표기가 다르고,

숫자형 Column에 문자열이 섞여 있고,

무한대가 들어가 있고,

앞뒤 공백이 붙어 있고,

정상 범위를 크게 벗어난 값도 존재한다.</code></pre>
<p>예를 들어 다음과 같은 데이터가 있다고 해보자.</p>
<table>
<thead>
<tr>
<th>feature 1</th>
<th>feature 2</th>
<th>feature 3</th>
<th>feature 4</th>
<th>feature 5</th>
<th>feature 6</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>NaN</td>
<td>1</td>
<td><code>&quot;tall&quot;</code></td>
<td><code>&quot;red&quot;</code></td>
<td>0.1</td>
</tr>
<tr>
<td>5</td>
<td>NaN</td>
<td>1</td>
<td><code>&quot; tall&quot;</code></td>
<td><code>&quot;green&quot;</code></td>
<td>0.9</td>
</tr>
<tr>
<td>-∞</td>
<td>NaN</td>
<td>NaN</td>
<td><code>&quot;short &quot;</code></td>
<td><code>&quot;green&quot;</code></td>
<td>3.5</td>
</tr>
<tr>
<td>∞</td>
<td>NaN</td>
<td>10</td>
<td><code>&quot;medium&quot;</code></td>
<td><code>&quot;green&quot;</code></td>
<td><code>&quot;?&quot;</code></td>
</tr>
</tbody></table>
<p>사람이 보면 금방 이상한 부분을 찾을 수 있다.</p>
<pre><code class="language-text">NaN
무한대
앞뒤 공백
문자열 형태의 알 수 없는 값
서로 다른 Target 표기</code></pre>
<p>하지만 컴퓨터는 이것을 보고</p>
<blockquote>
<p>이 값은 이상하니까 알아서 고쳐야겠다.</p>
</blockquote>
<p>라고 판단하지 않는다.</p>
<p>우리가 넣어준 데이터 그대로 계산한다.</p>
<p>따라서 제대로 정리되지 않은 데이터는<br>잘못된 분석과 잘못된 학습 결과로 이어질 수 있다.</p>
<p>이번 강의에서는 모델링보다 먼저 수행해야 하는<br><strong>Data Exploration</strong>의 7단계를 다뤘다.</p>
<p>이번 글을 통해 데이터 탐색이 무엇이고,<br>결측치와 이상치를 어떻게 바라봐야 하며,<br>변수 변환과 Feature Creation이 왜 필요한지 정리해보고자 한다.</p>
<hr>
<h1 id="data-exploration이란">Data Exploration이란?</h1>
<p>Data Exploration은<br>본격적인 모델링에 들어가기 전에 데이터를 살펴보며<br>그 특성을 이해하는 과정이다.</p>
<p>쉽게 말하면 다음 질문에 답하는 것이다.</p>
<pre><code class="language-text">어떤 데이터가 들어 있는가?

각 Column은 무엇을 의미하는가?

값은 어떤 분포를 가지는가?

서로 관련된 변수는 무엇인가?

누락되거나 잘못된 값은 없는가?

모델링 전에 어떤 처리가 필요한가?</code></pre>
<p>강의 자료에서는 데이터 탐색의 주요 목적을 다음과 같이 설명했다.</p>
<pre><code class="language-text">- 적절한 전처리 및 분석 도구 선택
- 사람이 가진 패턴 인식 능력 활용
- 흥미로운 변수 발견
- 데이터 내부의 패턴 발견
- 데이터 그룹 발견
- 이상치 발견
- 오류 발견</code></pre>
<p>결국 Data Exploration은<br>단순히 그래프 몇 개를 그리는 과정이 아니다.</p>
<blockquote>
<p>모델이 데이터를 학습하기 전에<br>사람이 먼저 데이터를 이해하는 과정이다.</p>
</blockquote>
<hr>
<h1 id="데이터-분석과-데이터-탐색의-차이">데이터 분석과 데이터 탐색의 차이</h1>
<p>데이터 분석은 데이터를 정리하고,<br>표현하고, 요약하고, 질문에 답하는 과정이다.</p>
<pre><code class="language-text">Organizing
Displaying
Summarizing
Asking Questions</code></pre>
<p>반면 데이터 탐색은<br>아직 명확하게 정해지지 않은 가능성을 여러 방향으로 확인하는 과정에 가깝다.</p>
<pre><code class="language-text">이 변수에 이상한 값은 없을까?

두 변수 사이에 숨은 관계가 있지 않을까?

특정 그룹에서만 다른 패턴이 나타나지 않을까?</code></pre>
<p>분석에는 구조적인 사고가 필요하고,<br>탐색에는 데이터를 여러 방향으로 잘라보는 인내심이 필요하다는 설명이 기억에 남았다.</p>
<p>결국 입력 데이터의 품질이<br>출력 결과의 품질을 결정한다.</p>
<pre><code class="language-text">Garbage In
↓
Garbage Out</code></pre>
<p>좋지 않은 데이터를 강력한 모델에 넣는다고 해서<br>좋은 결과가 나오는 것은 아니다.</p>
<hr>
<h1 id="데이터는-먼저-시각화해야-한다">데이터는 먼저 시각화해야 한다</h1>
<p>데이터를 탐색할 때는<br>먼저 각 변수를 독립적으로 확인한 뒤<br>변수 사이의 관계를 살펴보는 것이 좋다.</p>
<p>강의에서는 다음 순서를 제시했다.</p>
<pre><code class="language-text">각 변수 개별 확인
↓
그래프로 분포 확인
↓
수치적 요약 추가
↓
변수 사이의 관계 분석</code></pre>
<p>왜 시각화가 중요할까?</p>
<p>평균과 표준편차가 같더라도<br>실제 데이터의 형태는 완전히 다를 수 있기 때문이다.</p>
<p>대표적인 사례가 <strong>Anscombe&#39;s Quartet</strong>이다.</p>
<p>네 개의 데이터셋은 다음 통계량이 거의 같다.</p>
<pre><code class="language-text">평균
표준편차
상관계수
회귀선</code></pre>
<p>하지만 산점도로 보면<br>각 데이터의 패턴은 완전히 다르다.</p>
<pre><code class="language-text">직선 관계
곡선 관계
이상치 하나가 결과를 좌우하는 관계
특정 x값에 몰려 있는 관계</code></pre>
<p>즉, 통계 수치만 보고 데이터의 모습을 판단하면<br>중요한 문제를 놓칠 수 있다.</p>
<blockquote>
<p>숫자로 요약하기 전에<br>그래프로 직접 확인해야 한다.</p>
</blockquote>
<hr>
<h1 id="표본을-통해-모집단을-추론한다">표본을 통해 모집단을 추론한다</h1>
<p>현실에서는 모든 데이터를 수집하기 어려운 경우가 많다.</p>
<p>예를 들어 미국 전체 성인의 평균 소비 패턴을 분석한다고 해보자.</p>
<p>모든 사람의 데이터를 전부 수집하는 것은<br>시간과 비용 측면에서 현실적이지 않다.</p>
<p>그래서 모집단 전체가 아니라<br>이를 대표할 수 있는 Sample을 선택한다.</p>
<pre><code class="language-text">Population
↓
Representative Sample 수집
↓
Sample 분석
↓
Population에 대한 Inference</code></pre>
<p>여기서 중요한 것은<br>표본의 크기만이 아니다.</p>
<p>표본이 모집단을 제대로 대표하지 못한다면<br>아무리 복잡한 분석을 해도 잘못된 결론에 도달할 수 있다.</p>
<p>따라서 Sampling 과정에서도 다음을 생각해야 한다.</p>
<pre><code class="language-text">특정 집단만 과도하게 포함되지 않았는가?

중요한 집단이 빠지지는 않았는가?

수집 방식에서 편향이 발생하지 않았는가?</code></pre>
<p>모든 데이터를 사용할 필요는 없지만,<br>사용하는 데이터가 전체를 잘 대표해야 한다.</p>
<hr>
<h1 id="data-exploration의-7단계">Data Exploration의 7단계</h1>
<p>강의에서는 데이터 탐색 과정을<br>다음 7단계로 정리했다.</p>
<pre><code class="language-text">1. Variable Identification
2. Univariate Analysis
3. Bi-variate Analysis
4. Missing Values Treatment
5. Outlier Treatment
6. Variable Transformation
7. Variable Creation</code></pre>
<p>하나씩 정리해보자.</p>
<hr>
<h1 id="step-1-variable-identification">Step 1. Variable Identification</h1>
<p>첫 번째 단계는<br>데이터에 어떤 변수가 존재하는지 확인하는 것이다.</p>
<p>먼저 Predictor와 Target을 구분해야 한다.</p>
<pre><code class="language-text">Predictor Variable
→ 예측에 사용하는 입력값

Target Variable
→ 모델이 최종적으로 예측해야 하는 값</code></pre>
<p>예를 들어 학생이 크리켓을 할지 예측하는 데이터가 있다고 해보자.</p>
<table>
<thead>
<tr>
<th>Student ID</th>
<th>Gender</th>
<th>Exam Score</th>
<th>Height</th>
<th>Weight</th>
<th>Play Cricket</th>
</tr>
</thead>
<tbody><tr>
<td>S001</td>
<td>M</td>
<td>65</td>
<td>178</td>
<td>61</td>
<td>1</td>
</tr>
<tr>
<td>S002</td>
<td>F</td>
<td>75</td>
<td>174</td>
<td>56</td>
<td>0</td>
</tr>
</tbody></table>
<p>이때 변수는 다음과 같이 구분할 수 있다.</p>
<pre><code class="language-text">Predictor
- Gender
- Exam Score
- Height
- Weight

Target
- Play Cricket</code></pre>
<p>그다음 각 변수의 자료형과 범주를 확인한다.</p>
<pre><code class="language-text">Student ID → Character / Identifier

Gender → Categorical

Exam Score → Quantitative

Height → Quantitative

Play Cricket → Binary Categorical</code></pre>
<p>여기서 <code>Student ID</code>는 숫자처럼 보일 수 있지만<br>크기의 의미가 있는 수치형 변수가 아니다.</p>
<pre><code class="language-text">S002가 S001보다 두 배 크다</code></pre>
<p>같은 해석은 성립하지 않는다.</p>
<p>즉, 숫자처럼 보인다고 모두 수치형 데이터는 아니다.</p>
<hr>
<h2 id="범주형-변수와-수치형-변수">범주형 변수와 수치형 변수</h2>
<p>변수는 크게 두 가지로 나눌 수 있다.</p>
<h3 id="categorical-variable">Categorical Variable</h3>
<p>개체를 특정 그룹에 배치하는 변수다.</p>
<pre><code class="language-text">성별
지역
직업
결혼 여부
상품 종류</code></pre>
<p>범주형 데이터는 다시 나눌 수 있다.</p>
<pre><code class="language-text">Nominal
→ 순서가 없는 범주

예시:
국가, 혈액형, 직업</code></pre>
<pre><code class="language-text">Ordinal
→ 순서가 있는 범주

예시:
만족도 1~5
질병 단계
등급</code></pre>
<h3 id="quantitative-variable">Quantitative Variable</h3>
<p>수치적 크기가 의미를 가지며<br>평균과 같은 연산을 적용할 수 있는 변수다.</p>
<pre><code class="language-text">나이
키
몸무게
소득
시험 점수</code></pre>
<p>어떤 유형의 변수인지 알아야<br>적절한 분석과 시각화 방법을 선택할 수 있다.</p>
<hr>
<h1 id="step-2-univariate-analysis">Step 2. Univariate Analysis</h1>
<p>Univariate Analysis는<br>변수를 하나씩 독립적으로 분석하는 과정이다.</p>
<p>여기서 가장 중요한 것은<br><strong>Distribution</strong>, 즉 분포를 이해하는 것이다.</p>
<p>분포는 다음을 알려준다.</p>
<pre><code class="language-text">어떤 값이 존재하는가?

각 값이 얼마나 자주 등장하는가?</code></pre>
<p>분포를 이해하는 것이 중요한 이유는<br>이후의 거의 모든 선택에 영향을 주기 때문이다.</p>
<pre><code class="language-text">정규분포인가?

한쪽으로 치우쳐 있는가?

여러 봉우리가 존재하는가?

극단적으로 큰 값이 있는가?

결측치가 많은가?</code></pre>
<p>분포에 따라 다음이 달라진다.</p>
<pre><code class="language-text">사용할 통계 검정

시각화 방법

결측치 처리

이상치 처리

변수 변환

모델 선택</code></pre>
<hr>
<h2 id="연속형-변수-분석">연속형 변수 분석</h2>
<p>연속형 변수는<br>중심 경향과 퍼짐 정도를 확인한다.</p>
<h3 id="중심-경향">중심 경향</h3>
<pre><code class="language-text">Mean
Median
Mode
Minimum
Maximum</code></pre>
<h3 id="분산-정도">분산 정도</h3>
<pre><code class="language-text">Range
Quartile
IQR
Variance
Standard Deviation
Skewness
Kurtosis</code></pre>
<h3 id="시각화">시각화</h3>
<pre><code class="language-text">Histogram
Box Plot
Dot Plot</code></pre>
<p>예를 들어 고객 소비 금액의 평균이 100만 원이라고 해보자.</p>
<p>하지만 대부분의 고객이 2만 원을 소비하고<br>일부 고객만 1억 원을 소비했다면<br>평균 100만 원은 일반적인 고객을 잘 설명하지 못한다.</p>
<p>따라서 평균만 보는 것이 아니라</p>
<pre><code class="language-text">중앙값
분포의 모양
이상치</code></pre>
<p>를 함께 확인해야 한다.</p>
<hr>
<h2 id="범주형-변수-분석">범주형 변수 분석</h2>
<p>범주형 데이터는<br>각 범주가 몇 번 나타나는지 확인한다.</p>
<p>주로 다음을 사용한다.</p>
<pre><code class="language-text">Count
Count Percentage
Frequency Table
Bar Chart</code></pre>
<p>예를 들어 지역 변수가 다음과 같다고 해보자.</p>
<table>
<thead>
<tr>
<th>지역</th>
<th align="right">인원</th>
<th align="right">비율</th>
</tr>
</thead>
<tbody><tr>
<td>Rural</td>
<td align="right">14</td>
<td align="right">14%</td>
</tr>
<tr>
<td>Suburb</td>
<td align="right">53</td>
<td align="right">53%</td>
</tr>
<tr>
<td>Small Town</td>
<td align="right">25</td>
<td align="right">25%</td>
</tr>
<tr>
<td>Big City</td>
<td align="right">8</td>
<td align="right">8%</td>
</tr>
</tbody></table>
<p>이 표만 봐도<br>특정 범주에 데이터가 지나치게 몰려 있는지 확인할 수 있다.</p>
<p>분류 문제에서는 이런 불균형이<br>Class Imbalance 문제로 이어질 수 있다.</p>
<hr>
<h1 id="step-3-bi-variate-analysis">Step 3. Bi-variate Analysis</h1>
<p>Bi-variate Analysis는<br>두 변수 사이의 관계를 분석하는 과정이다.</p>
<p>예를 들어 다음 질문을 확인할 수 있다.</p>
<pre><code class="language-text">소득이 증가하면 구매 금액도 증가하는가?

나이가 증가하면 이탈 가능성이 낮아지는가?

성별에 따라 상품 선택 비율이 다른가?

특정 Feature가 Target과 관련이 있는가?</code></pre>
<p>이 분석을 통해 다음을 판단할 수 있다.</p>
<pre><code class="language-text">Target과 관련된 변수

도움이 되지 않는 변수

서로 중복되는 변수

비선형 관계

새로운 Feature 생성 가능성</code></pre>
<p>예를 들어 집값 예측에서</p>
<pre><code class="language-text">대지 면적
→ 집값과 강한 관계

현관문의 색상
→ 집값과 의미 있는 관계가 없음</code></pre>
<p>이라면 현관문 색상은<br>모델에 큰 가치를 제공하지 않을 가능성이 있다.</p>
<p>또 다음 두 변수가 있다고 해보자.</p>
<pre><code class="language-text">연간 소득
월간 소득</code></pre>
<p>두 값은 거의 같은 정보를 담고 있다.</p>
<p>이 둘을 모두 사용하면<br>중복 정보와 다중공선성 문제가 발생할 수 있으므로<br>하나를 선택하는 것이 나을 수 있다.</p>
<hr>
<h2 id="continuous--continuous">Continuous &amp; Continuous</h2>
<p>두 연속형 변수 사이의 관계는<br>주로 Scatter Plot으로 확인한다.</p>
<pre><code class="language-text">강한 양의 상관관계

약한 양의 상관관계

상관관계 없음

음의 상관관계

곡선 관계</code></pre>
<p>선형 관계의 강도를 수치화할 때는<br>Correlation을 사용할 수 있다.</p>
<pre><code class="language-text">-1
→ 완전한 음의 선형관계

0
→ 선형 상관관계가 없음

+1
→ 완전한 양의 선형관계</code></pre>
<p>주의해야 할 점도 있다.</p>
<blockquote>
<p>상관계수가 0이라고 해서<br>두 변수 사이에 아무 관계도 없다는 뜻은 아니다.</p>
</blockquote>
<p>곡선 형태의 비선형 관계가 존재한다면<br>선형 상관계수는 낮게 나올 수 있다.</p>
<p>그래서 상관계수만 보지 말고<br>Scatter Plot을 함께 확인해야 한다.</p>
<hr>
<h2 id="categorical--categorical">Categorical &amp; Categorical</h2>
<p>두 범주형 변수 사이의 관계는<br>다음 방법으로 확인할 수 있다.</p>
<pre><code class="language-text">Two-way Table

Count / Count Percentage

Stacked Bar Chart

Chi-square Test</code></pre>
<p>예를 들어 성별과 상품 구매 여부의 관계를 확인하려면<br>성별마다 구매와 미구매 비율을 비교할 수 있다.</p>
<hr>
<h2 id="categorical--continuous">Categorical &amp; Continuous</h2>
<p>범주형 변수와 연속형 변수를 비교할 때는<br>범주별 수치 분포를 비교한다.</p>
<p>예를 들어 직업별 연봉을 분석한다면<br>다음과 같은 방법을 사용할 수 있다.</p>
<pre><code class="language-text">Box Plot

Violin Plot

범주별 Mean / Median

분산 비교</code></pre>
<p>이 과정을 통해<br>특정 범주가 수치형 Target과 관련이 있는지 확인할 수 있다.</p>
<hr>
<h1 id="step-4-missing-values-treatment">Step 4. Missing Values Treatment</h1>
<p>Missing Value는<br>데이터에 값이 존재하지 않는 상태다.</p>
<p>일반적으로 다음처럼 표현된다.</p>
<pre><code class="language-text">NaN
NULL
None
빈 문자열
?</code></pre>
<p>결측치를 그대로 두면<br>다음 문제가 발생할 수 있다.</p>
<pre><code class="language-text">모델의 학습 능력 감소

편향된 결과 발생

잘못된 분류와 예측

통계량 왜곡

일부 알고리즘 실행 실패</code></pre>
<p>Linear Regression이나 SVM처럼<br>결측치를 직접 처리하지 못하는 알고리즘도 있다.</p>
<p>하지만 결측치가 문제인 이유는<br>단순히 값이 없기 때문만은 아니다.</p>
<blockquote>
<p>어떤 데이터가 비어 있다는 사실 자체가<br>중요한 정보일 수 있다.</p>
</blockquote>
<p>예를 들어 특정 연령대만<br>소득 입력을 자주 거부했다면<br>결측 여부 자체가 해당 집단의 특성과 관련될 수 있다.</p>
<hr>
<h1 id="결측치는-왜-발생했는지부터-확인해야-한다">결측치는 왜 발생했는지부터 확인해야 한다</h1>
<p>결측치 처리에서 가장 중요한 것은<br>결측이 발생한 원인을 이해하는 것이다.</p>
<p>강의에서는 다음과 같은 유형을 다뤘다.</p>
<hr>
<h2 id="mcar">MCAR</h2>
<p>MCAR는<br>Missing Completely At Random의 약자다.</p>
<p>결측 발생 확률이<br>관측된 값과 관측되지 않은 값 모두와 무관한 경우다.</p>
<p>예를 들어 설문 응답자가 동전을 던져서<br>앞면이면 소득을 응답하고<br>뒷면이면 응답하지 않는다고 해보자.</p>
<p>누구에게나 같은 확률로 결측이 생긴다.</p>
<p>이 경우 결측은 데이터 특성과 무관하다.</p>
<p>MCAR라면 일부 데이터를 삭제하더라도<br>추론의 편향이 비교적 적을 수 있다.</p>
<p>하지만 표본 수가 줄어든다는 문제는 여전히 존재한다.</p>
<hr>
<h2 id="mar">MAR</h2>
<p>MAR는 Missing At Random의 약자다.</p>
<p>이름만 보면 완전히 무작위처럼 느껴지지만,<br>실제로는 <strong>관측된 다른 변수에 따라 결측 확률이 달라지는 경우</strong>다.</p>
<p>예를 들어 나이 정보의 결측률이<br>성별에 따라 다르다고 해보자.</p>
<pre><code class="language-text">남성의 나이 결측률: 5%

여성의 나이 결측률: 20%</code></pre>
<p>결측 여부는 <code>성별</code>이라는 관측 가능한 변수와 관련되어 있다.</p>
<p>이 상태를 무시하면<br>특정 집단이 데이터에서 과소 표현되어<br>결과가 편향될 수 있다.</p>
<hr>
<h2 id="mnar에-가까운-경우">MNAR에 가까운 경우</h2>
<p>결측 여부가<br>관측되지 않은 원인이나 결측값 그 자체와 관련될 수도 있다.</p>
<p>예를 들어 고소득자와 저소득자가<br>중간 소득자보다 자신의 소득을 공개하지 않을 가능성이 높다고 해보자.</p>
<p>이때 소득의 결측 여부는<br>실제 소득값 자체와 관련되어 있다.</p>
<p>또 의료 검사에 통증이 심한 사람들이<br>검사를 중단해 결과가 누락될 수도 있다.</p>
<p>이런 경우에는 단순 삭제나 평균 대체가<br>큰 편향을 만들 수 있다.</p>
<hr>
<h1 id="결측치를-처리하는-방법">결측치를 처리하는 방법</h1>
<p>결측치 처리 방법은 크게 다음과 같다.</p>
<pre><code class="language-text">Deletion

Simple Imputation

Similar Case Imputation

Prediction Model

Missing Indicator</code></pre>
<hr>
<h2 id="listwise-deletion">Listwise Deletion</h2>
<p>한 행에 하나라도 결측치가 있으면<br>그 행 전체를 삭제한다.</p>
<pre><code class="language-text">Age | Income | Job
25  | 5000   | Developer
30  | NaN    | Designer</code></pre>
<p>두 번째 행에 결측치가 있으므로<br>행 전체를 제거한다.</p>
<p>장점은 간단하다는 것이다.</p>
<p>하지만 단점이 크다.</p>
<pre><code class="language-text">표본 수 감소

정보 손실

결측이 무작위가 아니라면 편향 발생</code></pre>
<hr>
<h2 id="pairwise-deletion">Pairwise Deletion</h2>
<p>분석에 필요한 변수의 값이 존재하는 행만 사용한다.</p>
<p>예를 들어 <code>Age</code>와 <code>Income</code>의 상관관계를 구한다면<br>이 두 변수의 값이 있는 행만 사용한다.</p>
<p>다른 분석에서는 다른 표본이 사용될 수 있다.</p>
<p>장점은 더 많은 데이터를 유지하는 것이다.</p>
<p>하지만 분석마다 표본 크기와 구성원이 달라져<br>결과를 서로 비교하기 어려울 수 있다.</p>
<hr>
<h2 id="mean--median--mode-imputation">Mean / Median / Mode Imputation</h2>
<p>결측치를 대표값으로 채우는 방식이다.</p>
<pre><code class="language-text">수치형
→ Mean 또는 Median

범주형
→ Mode</code></pre>
<p>예를 들어 다음 값이 있다고 해보자.</p>
<pre><code class="language-text">20, 22, 25, NaN, 28</code></pre>
<p>평균이나 중앙값을 계산해<br>결측치를 대체할 수 있다.</p>
<p>하지만 이상치가 많은 데이터에서는<br>평균보다 중앙값이 더 안정적일 수 있다.</p>
<p>대표값 대체는 간단하지만<br>데이터 분산을 인위적으로 줄이고<br>변수 사이의 관계를 왜곡할 수 있다는 한계가 있다.</p>
<hr>
<h2 id="similar-case-imputation">Similar Case Imputation</h2>
<p>전체 평균이 아니라<br>비슷한 집단의 평균이나 중앙값으로 채운다.</p>
<p>예를 들어 인력 수가 성별에 따라 다르다면<br>다음처럼 계산할 수 있다.</p>
<pre><code class="language-text">남성 그룹 평균

여성 그룹 평균</code></pre>
<p>결측치가 발생한 행의 성별을 기준으로<br>해당 그룹의 대표값을 사용한다.</p>
<p>전체 평균을 사용하는 것보다<br>집단 특성을 더 잘 반영할 수 있다.</p>
<hr>
<h2 id="prediction-model">Prediction Model</h2>
<p>결측치가 있는 변수를<br>하나의 Target으로 보고 예측 모델을 학습할 수도 있다.</p>
<p>흐름은 다음과 같다.</p>
<pre><code class="language-text">결측치가 없는 데이터
→ Training Data

결측치가 있는 데이터
→ Prediction 대상

결측된 Column
→ Target Variable</code></pre>
<p>다른 Feature를 이용해<br>결측값을 예측하고 채운다.</p>
<p>사용 가능한 모델은 다음과 같다.</p>
<pre><code class="language-text">Linear Regression

kNN

Random Forest

Iterative Imputation</code></pre>
<hr>
<h2 id="knn-imputation이란">kNN Imputation이란?</h2>
<p>kNN은 새로운 데이터와<br>가장 가까운 기존 데이터를 찾는 방법이다.</p>
<p>결측값을 채울 때는<br>해당 행과 가장 비슷한 이웃들의 값을 사용한다.</p>
<p>예를 들어 어떤 행의 소득이 비어 있고<br>가장 비슷한 두 사람의 소득이 다음과 같다고 해보자.</p>
<pre><code class="language-text">5000
6000</code></pre>
<p><code>k = 2</code>라면 평균인 5500으로 채울 수 있다.</p>
<p>장점은 데이터의 지역적인 구조를 반영한다는 것이다.</p>
<p>하지만 다음 문제가 있다.</p>
<pre><code class="language-text">스케일링에 민감함

데이터가 크면 계산량 증가

적절한 k 선택 필요

의미 없는 Feature가 거리 계산을 방해할 수 있음</code></pre>
<hr>
<h2 id="linear-regression-imputation이란">Linear Regression Imputation이란?</h2>
<p>결측값이 있는 변수를<br>다른 변수의 선형 조합으로 예측한다.</p>
<p>예를 들어 소득이 비어 있다면</p>
<pre><code class="language-text">소득
=
나이 × w1
+
경력 × w2
+
학력 × w3
+
b</code></pre>
<p>와 같은 모델을 학습할 수 있다.</p>
<p>변수 사이에 선형 관계가 존재한다면 유용하다.</p>
<p>하지만 실제 관계가 비선형이거나<br>이상치가 많다면 잘못된 값을 생성할 수 있다.</p>
<hr>
<h2 id="결측치-처리-가이드라인">결측치 처리 가이드라인</h2>
<p>강의 자료에서는 다음과 같은<br>예시 정책을 제시했다.</p>
<pre><code class="language-text">결측 발생
    │
    ▼
결측이 완전히 무작위에 가까운가?
    │
 ┌──┴──┐
 │     │
YES    NO
 │      │
삭제 검토  ▼
       결측률이 높은가?
           │
      ┌────┴────┐
      │         │
    YES        NO
      │         │
ML 기반     표본이 작은가?
대체 고려        │
            ┌────┴────┐
            │         │
          YES        NO
            │         │
결측 정보가 중요한가?  단순 대체 또는
            │          모델의 자체 처리 검토
       ┌────┴────┐
       │         │
     YES        NO
       │         │
KNN, RF 등     평균·중앙값·최빈값
복합 대체       대체 검토</code></pre>
<p>다만 이 흐름은 어디까지나<br>간단한 예시 정책이다.</p>
<p>특히 다음은 조심해야 한다.</p>
<pre><code class="language-text">결측률이 낮다고 무조건 무시하지 않는다.

0이 실제 의미를 가질 때만 0으로 채운다.

10%라는 기준은 절대적인 법칙이 아니다.

결측 원인과 도메인 의미가 우선이다.</code></pre>
<p>예를 들어 계좌 잔액의 결측치를 <code>0</code>으로 채우면<br>실제로 잔액이 0인 계좌와<br>수집에 실패한 계좌를 구분할 수 없게 된다.</p>
<p>따라서 결측 여부를 별도 Feature로 만드는 방법도 있다.</p>
<pre><code class="language-text">income_missing = 1</code></pre>
<p>scikit-learn에서는 다음 도구를 사용할 수 있다.</p>
<pre><code class="language-text">SimpleImputer

IterativeImputer

KNNImputer

MissingIndicator</code></pre>
<p>이미 필요한 도구가 대부분 구현되어 있으므로<br>직접 알고리즘을 만드는 것보다<br>각 방법의 가정과 한계를 이해하고 적절하게 선택하는 것이 중요하다.</p>
<hr>
<h1 id="step-5-outlier-treatment">Step 5. Outlier Treatment</h1>
<p>Outlier는 전체적인 패턴에서<br>멀리 떨어져 있는 관측값이다.</p>
<p>경진대회에서 데이터를 시각화하다 보면<br>다른 데이터들과 동떨어져 혼자 튀어 있는 점을 볼 때가 있다.</p>
<p>그런 값이 이상치다.</p>
<p>가장 빠르게 이해할 수 있는 예시를 들어보자.</p>
<p>대한민국 20대와 30대의 평균 소득을 조사하는데<br>세계적인 연예인의 소득이 포함됐다고 해보자.</p>
<p>대부분의 소득이 비슷한 범위에 몰려 있어도<br>극단적으로 높은 소득 몇 개가 평균을 크게 끌어올릴 수 있다.</p>
<pre><code class="language-text">일반적인 소득 데이터
+
블랙핑크 멤버의 소득
↓
평균이 크게 상승</code></pre>
<p>이때 평균값만 보면<br>일반적인 20·30대의 소득 수준을 잘못 이해할 수 있다.</p>
<hr>
<h1 id="이상치는-왜-발생할까">이상치는 왜 발생할까?</h1>
<p>이상치를 발견했다고 해서<br>곧바로 삭제하면 안 된다.</p>
<p>먼저 발생 이유를 확인해야 한다.</p>
<p>강의에서는 이상치 원인을 다음과 같이 나눴다.</p>
<pre><code class="language-text">인위적인 오류

자연적으로 발생한 이상치</code></pre>
<p>구체적으로는 다음과 같다.</p>
<hr>
<h2 id="data-entry-error">Data Entry Error</h2>
<p>사람이 데이터를 잘못 입력한 경우다.</p>
<pre><code class="language-text">키 175cm
→ 1750cm로 입력</code></pre>
<hr>
<h2 id="measurement-error">Measurement Error</h2>
<p>측정 장비나 센서의 오류다.</p>
<pre><code class="language-text">정상 온도: 24도

센서 오류: 240도</code></pre>
<hr>
<h2 id="intentional-outlier">Intentional Outlier</h2>
<p>민감한 질문에<br>응답자가 의도적으로 잘못된 값을 입력할 수 있다.</p>
<pre><code class="language-text">소득
건강 정보
정치적 성향</code></pre>
<hr>
<h2 id="data-processing-error">Data Processing Error</h2>
<p>여러 데이터 소스를 합치거나 변환하는 과정에서<br>버그가 발생할 수 있다.</p>
<pre><code class="language-text">달러와 원화를 같은 단위로 처리

초와 밀리초를 혼동

중복 집계</code></pre>
<hr>
<h2 id="sampling-error">Sampling Error</h2>
<p>전체 Sample과 다른 모집단에 속한 데이터가<br>잘못 포함된 경우다.</p>
<p>예를 들어 성인 키를 분석하는 데이터에<br>아동 데이터가 들어간 경우다.</p>
<hr>
<h2 id="natural-outlier">Natural Outlier</h2>
<p>오류가 아니라<br>현실에서 실제로 존재하는 극단적인 값이다.</p>
<pre><code class="language-text">매우 높은 소득

드문 질병

초대형 거래

극심한 기상 현상</code></pre>
<p>이런 값은 삭제 대상이 아니라<br>오히려 중요한 분석 대상일 수 있다.</p>
<hr>
<h1 id="이상치는-항상-나쁜-것일까">이상치는 항상 나쁜 것일까?</h1>
<p>아니다.</p>
<p>도메인에 따라 이상치가<br>가장 중요한 데이터가 될 수도 있다.</p>
<p>예를 들어 사고 발생 예측을 생각해보자.</p>
<p>대부분의 데이터는 정상 주행일 것이다.</p>
<p>하지만 우리가 정말 알고 싶은 것은<br>사고가 발생한 드문 상황이다.</p>
<pre><code class="language-text">정상 데이터
→ 대부분

사고 데이터
→ 매우 적음</code></pre>
<p>이때 사고 데이터를 이상치라고 삭제한다면<br>분석의 목적 자체가 사라진다.</p>
<p>사기 거래 탐지, 침입 탐지, 장비 고장 탐지에서도 마찬가지다.</p>
<blockquote>
<p>이상치는 통계적으로 드문 값일 뿐,<br>무조건 잘못된 값이라는 뜻은 아니다.</p>
</blockquote>
<hr>
<h1 id="이상치를-탐지하는-방법">이상치를 탐지하는 방법</h1>
<p>가장 먼저 해야 할 것은 시각화다.</p>
<p>대표적인 방법은 다음과 같다.</p>
<pre><code class="language-text">Box Plot

Histogram

Scatter Plot</code></pre>
<p>이후 통계적인 기준을 함께 사용할 수 있다.</p>
<hr>
<h2 id="iqr-방식">IQR 방식</h2>
<p>IQR은 Interquartile Range의 약자다.</p>
<pre><code class="language-text">IQR = Q3 - Q1</code></pre>
<p>일반적으로 다음 범위를 벗어난 값을<br>이상치 후보로 본다.</p>
<pre><code class="language-text">Lower Bound = Q1 - 1.5 × IQR

Upper Bound = Q3 + 1.5 × IQR</code></pre>
<p>중요한 점은 이것이<br>이상치의 절대적인 정의가 아니라<br>후보를 찾기 위한 경험적 기준이라는 것이다.</p>
<hr>
<h2 id="percentile-방식">Percentile 방식</h2>
<p>다음과 같이 일정 백분위 밖의 값을<br>이상치 후보로 판단할 수 있다.</p>
<pre><code class="language-text">5th Percentile 미만

95th Percentile 초과</code></pre>
<p>이후 값을 삭제하는 대신<br>경계값으로 제한하는 Capping을 사용할 수도 있다.</p>
<p>다만 5%와 95% 역시 고정된 정답이 아니다.</p>
<p>데이터 분포와 도메인에 따라<br>1%와 99%를 사용할 수도 있다.</p>
<hr>
<h2 id="standard-deviation-방식">Standard Deviation 방식</h2>
<p>정규분포에 가까운 데이터에서는<br>평균으로부터 세 표준편차 이상 떨어진 값을<br>이상치 후보로 볼 수 있다.</p>
<pre><code class="language-text">|z-score| &gt;= 3</code></pre>
<p>하지만 분포가 심하게 치우쳐 있다면<br>이 기준은 적절하지 않을 수 있다.</p>
<p>결국 어떤 방식을 사용하든<br>그래프와 도메인 지식을 함께 확인해야 한다.</p>
<hr>
<h1 id="이상치는-어떻게-처리할까">이상치는 어떻게 처리할까?</h1>
<p>이상치 처리 방법은 다음과 같다.</p>
<pre><code class="language-text">삭제

Capping

변수 변환

값 대체

별도의 그룹으로 처리

이상치에 강한 모델 사용</code></pre>
<hr>
<h2 id="삭제">삭제</h2>
<p>명백한 데이터 입력 오류나 처리 오류라면<br>삭제를 고려할 수 있다.</p>
<pre><code class="language-text">나이 = 250세

키 = -170cm</code></pre>
<p>하지만 실제로 존재하는 값이라면<br>단순 삭제하면 안 된다.</p>
<hr>
<h2 id="capping">Capping</h2>
<p>상한과 하한을 설정하고<br>범위를 벗어난 값을 경계값으로 바꾼다.</p>
<pre><code class="language-text">95th Percentile보다 큼
→ 95th Percentile 값으로 변경</code></pre>
<p>극단값의 영향을 줄이면서<br>행 자체는 유지할 수 있다.</p>
<hr>
<h2 id="transforming-values">Transforming Values</h2>
<p>로그 변환 등을 사용해<br>극단적인 값의 크기를 압축할 수 있다.</p>
<pre><code class="language-text">원본:
1, 2, 3, 10, 1000

로그 변환:
0, 0.69, 1.10, 2.30, 6.91</code></pre>
<p>1000이라는 값이 여전히 가장 크지만<br>다른 값과의 격차가 줄어든다.</p>
<hr>
<h2 id="imputation">Imputation</h2>
<p>명백한 오류로 판단되는 이상치를<br>평균, 중앙값이나 예측값으로 바꿀 수 있다.</p>
<p>하지만 자연적으로 발생한 이상치를<br>일반적인 값으로 덮어쓰면 중요한 정보를 잃을 수 있다.</p>
<hr>
<h2 id="별도-그룹으로-처리">별도 그룹으로 처리</h2>
<p>이상치가 충분히 많고<br>일반 데이터와 다른 행동 패턴을 보인다면<br>별도의 그룹으로 나누어 모델링할 수 있다.</p>
<pre><code class="language-text">일반 고객 모델

고액 자산가 모델</code></pre>
<p>각 그룹에 별도의 모델을 적용한 뒤<br>결과를 합치는 방식이다.</p>
<hr>
<h1 id="실시간-데이터의-이상치는-어떻게-처리할까">실시간 데이터의 이상치는 어떻게 처리할까?</h1>
<p>수업에서 다룬 대부분의 방법은<br>이미 수집된 Tabular Data를 분석하는 Batch 환경에 가깝다.</p>
<p>하지만 실시간 센서나 거래 데이터에서는<br>상황이 조금 다르다.</p>
<p>실시간 환경에서는 다음이 중요하다.</p>
<pre><code class="language-text">최근 구간의 통계량

이동 평균

Rolling Standard Deviation

시간대별 정상 범위

Concept Drift

Streaming Anomaly Detection</code></pre>
<p>예를 들어 서버 응답 시간이 평소 100ms인데<br>갑자기 500ms가 되었다고 해보자.</p>
<p>전체 데이터 기준으로는 이상하지 않을 수 있지만<br>최근 5분 평균과 비교하면 이상할 수 있다.</p>
<p>즉, 실시간 이상치는<br>고정된 전체 분포가 아니라<br>시간에 따라 변하는 정상 상태를 기준으로 판단해야 한다.</p>
<hr>
<h1 id="step-6-variable-transformation">Step 6. Variable Transformation</h1>
<p>Variable Transformation은<br>기존 변수에 함수를 적용해 다른 형태로 바꾸는 과정이다.</p>
<p>예를 들어 변수 <code>x</code>를 다음과 같이 바꿀 수 있다.</p>
<pre><code class="language-text">log(x)

sqrt(x)

x³

구간화</code></pre>
<p>변환의 목적은 단순히 숫자를 바꾸는 것이 아니다.</p>
<pre><code class="language-text">분포의 형태를 조정하고,

극단값의 영향을 줄이고,

변수 사이의 관계를 더 명확하게 만들고,

모델이 학습하기 쉬운 형태로 바꾸는 것</code></pre>
<p>이다.</p>
<hr>
<h1 id="언제-변수-변환을-사용할까">언제 변수 변환을 사용할까?</h1>
<p>대표적으로 다음 상황에서 사용한다.</p>
<pre><code class="language-text">변수들의 단위가 크게 다를 때

분포가 한쪽으로 심하게 치우쳤을 때

극단값의 영향이 클 때

비선형 관계를 선형 관계에 가깝게 만들고 싶을 때

해석과 실제 정책 적용을 쉽게 만들고 싶을 때</code></pre>
<p>예를 들어 나이와 소득을 함께 사용한다고 해보자.</p>
<pre><code class="language-text">나이: 20 ~ 70

소득: 20,000,000 ~ 1,000,000,000</code></pre>
<p>거리 기반 모델에서는 소득의 크기가<br>나이의 영향을 압도할 수 있다.</p>
<p>이 경우 Standardization이나 Normalization을 적용할 수 있다.</p>
<hr>
<h1 id="로그-변환은-왜-사용할까">로그 변환은 왜 사용할까?</h1>
<p>로그 변환은<br>오른쪽으로 길게 늘어진 분포를 완화하는 데 자주 사용된다.</p>
<p>소득, 매출, 조회수와 같은 데이터는<br>일부 값이 매우 큰 경우가 많다.</p>
<pre><code class="language-text">대부분: 10 ~ 100

일부: 10,000 이상</code></pre>
<p>로그를 적용하면 큰 값을 압축할 수 있다.</p>
<pre><code class="language-text">10 → 1

100 → 2

10,000 → 4</code></pre>
<p>밑이 10인 로그의 예시다.</p>
<p>값의 차이가 줄어들면서<br>분포가 더 대칭적인 형태에 가까워질 수 있다.</p>
<p>로그 변환의 장점은 다음과 같다.</p>
<pre><code class="language-text">오른쪽 Skewness 감소

극단값 영향 완화

곱셈 관계를 덧셈 관계로 변환

비선형 관계를 선형에 가깝게 표현</code></pre>
<p>다만 일반 로그는<br>0이나 음수에 바로 적용할 수 없다.</p>
<p>따라서 데이터 특성에 따라</p>
<pre><code class="language-text">log1p(x)

값 이동 후 로그

다른 변환</code></pre>
<p>을 고려해야 한다.</p>
<hr>
<h1 id="대표적인-변수-변환-방법">대표적인 변수 변환 방법</h1>
<h2 id="logarithm">Logarithm</h2>
<p>오른쪽으로 치우친 분포를 줄이는 데 사용한다.</p>
<pre><code class="language-text">조건:
일반적으로 양수 데이터에 사용</code></pre>
<hr>
<h2 id="square-root">Square Root</h2>
<p>로그보다 완만한 변환이다.</p>
<pre><code class="language-text">0 이상의 값에 사용 가능</code></pre>
<p>Count Data 등에서 사용할 수 있다.</p>
<hr>
<h2 id="cube-root">Cube Root</h2>
<p>음수와 0에도 적용할 수 있다.</p>
<p>로그나 제곱근보다 변환 효과는 약할 수 있지만<br>부호를 유지할 수 있다는 장점이 있다.</p>
<hr>
<h2 id="standardization">Standardization</h2>
<p>평균을 0, 표준편차를 1로 맞춘다.</p>
<pre><code class="language-text">z = (x - mean) / standard deviation</code></pre>
<p>Feature의 단위를 통일하는 데 유용하다.</p>
<hr>
<h2 id="normalization">Normalization</h2>
<p>값을 특정 범위로 조정한다.</p>
<pre><code class="language-text">0 ~ 1</code></pre>
<p>거리 기반 모델이나 Neural Network에서 자주 사용한다.</p>
<hr>
<h2 id="binning">Binning</h2>
<p>연속형 변수를 범주형 구간으로 나눈다.</p>
<p>예를 들어 나이를 다음과 같이 바꿀 수 있다.</p>
<pre><code class="language-text">30세 미만

30 ~ 45세

45세 초과</code></pre>
<p>소득은 다음처럼 바꿀 수 있다.</p>
<pre><code class="language-text">Low

Average

High</code></pre>
<p>구간화는 해석과 정책 적용을 쉽게 만들지만<br>원래 수치 정보가 손실될 수 있다.</p>
<p>구간을 어떻게 나눌지는<br>비즈니스와 도메인에 대한 이해가 필요하다.</p>
<hr>
<h1 id="step-7-variable-creation">Step 7. Variable Creation</h1>
<p>Variable Creation은<br>기존 변수를 이용해 새로운 Feature를 만드는 과정이다.</p>
<p>예를 들어 날짜 Column이 있다고 해보자.</p>
<pre><code class="language-text">2026-07-22</code></pre>
<p>이 값을 그대로 사용하는 대신<br>다음 Feature로 나눌 수 있다.</p>
<pre><code class="language-text">Year = 2026

Month = 7

Day = 22

Weekday = Wednesday

Weekend = 0</code></pre>
<p>Target과 더 강한 관계를 가진 Feature가<br>새롭게 드러날 수 있다.</p>
<p>예를 들어 주문량은 날짜 자체보다<br>요일이나 주말 여부와 더 관련이 있을 수 있다.</p>
<hr>
<h1 id="variable-transformation과-feature-creation의-차이">Variable Transformation과 Feature Creation의 차이</h1>
<p>두 개념은 비슷해 보이지만 조금 다르다.</p>
<h2 id="variable-transformation">Variable Transformation</h2>
<p>기존 변수를 다른 형태로 바꾸는 것이다.</p>
<pre><code class="language-text">income
→ log(income)</code></pre>
<p>보통 원래 Feature의 분포나 관계를 조정하는 것이 목적이다.</p>
<h2 id="feature-creation">Feature Creation</h2>
<p>기존 변수로부터<br>새로운 의미를 가진 Feature를 추가하는 것이다.</p>
<pre><code class="language-text">debt / income
→ debt_ratio</code></pre>
<pre><code class="language-text">weight / height²
→ BMI</code></pre>
<pre><code class="language-text">현재 날짜 - 마지막 로그인 날짜
→ days_since_last_login</code></pre>
<p>즉,</p>
<pre><code class="language-text">Transformation
→ 표현 방식 변경

Creation
→ 새로운 정보 생성</code></pre>
<p>이라고 볼 수 있다.</p>
<p>다만 실제 머신러닝에서는<br>로그값이나 구간화 결과를 새로운 Column으로 추가할 수도 있어<br>두 개념이 완전히 분리되지는 않는다.</p>
<hr>
<h1 id="feature-creation의-목적">Feature Creation의 목적</h1>
<p>Feature Creation의 가장 중요한 목적은<br>원본 데이터에 숨어 있는 관계를 드러내는 것이다.</p>
<p>예를 들어 키와 몸무게가 각각 Target과 약한 관계를 보이더라도<br>두 값을 조합한 BMI는 강한 관계를 가질 수 있다.</p>
<pre><code class="language-text">Height
+
Weight
↓
BMI</code></pre>
<p>자동매매 프로그램에서도 비슷하다.</p>
<p>원본 데이터가 다음과 같다고 해보자.</p>
<pre><code class="language-text">현재 가격

직전 가격

매수 체결량

매도 체결량

현재 시간</code></pre>
<p>이를 이용해 다음 Feature를 만들 수 있다.</p>
<pre><code class="language-text">가격 변화율

매수·매도 체결량 비율

최근 5분 변동성

장 시작 후 경과 시간

거래량 이동 평균</code></pre>
<p>모델 입장에서는 원본 값보다<br>이런 Feature가 문제를 더 직접적으로 표현할 수 있다.</p>
<blockquote>
<p>좋은 Feature가 많을수록<br>모델이 문제의 구조를 학습하기 쉬워진다.</p>
</blockquote>
<p>하지만 무조건 많은 Feature가 좋은 것은 아니다.</p>
<pre><code class="language-text">메모리 사용량 증가

학습 시간 증가

중복 Feature 증가

과적합 가능성

노이즈 증가</code></pre>
<p>가 발생할 수 있다.</p>
<p>Feature는 많이 만드는 것보다<br>문제와 관련된 의미 있는 값을 만드는 것이 중요하다.</p>
<hr>
<h1 id="derived-variable">Derived Variable</h1>
<p>Derived Variable은<br>기존 변수들을 조합해 만든 새로운 변수다.</p>
<p>대표적인 예시는 다음과 같다.</p>
<pre><code class="language-text">BMI = Weight / Height²

Revenue = Price × Quantity

Debt Ratio = Debt / Income

Age = Current Date - Birth Date</code></pre>
<p>어떤 변수를 만들지는<br>정해진 공식만으로 결정하기 어렵다.</p>
<p>분석가의 다음 능력이 필요하다.</p>
<pre><code class="language-text">도메인 지식

호기심

가설 설정

데이터에 대한 이해</code></pre>
<p>결국 Feature Engineering은<br>단순한 기술 작업이 아니라<br>문제를 어떻게 바라보느냐와 연결된다.</p>
<hr>
<h1 id="dummy-variable이란">Dummy Variable이란?</h1>
<p>대부분의 머신러닝 모델은<br>숫자 형태의 입력을 요구한다.</p>
<p>하지만 데이터에는 다음과 같은 범주형 값이 존재한다.</p>
<pre><code class="language-text">성별

국가

직업

결혼 여부</code></pre>
<p>이 값을 숫자로 변환하기 위해<br>Dummy Variable을 사용할 수 있다.</p>
<p>Dummy Variable은<br>Indicator Variable이라고도 부른다.</p>
<p>예를 들어 성별이 다음과 같다고 해보자.</p>
<table>
<thead>
<tr>
<th>Gender</th>
</tr>
</thead>
<tbody><tr>
<td>Male</td>
</tr>
<tr>
<td>Female</td>
</tr>
</tbody></table>
<p>이를 다음처럼 변환할 수 있다.</p>
<table>
<thead>
<tr>
<th align="right">Gender_Male</th>
<th align="right">Gender_Female</th>
</tr>
</thead>
<tbody><tr>
<td align="right">1</td>
<td align="right">0</td>
</tr>
<tr>
<td align="right">0</td>
<td align="right">1</td>
</tr>
</tbody></table>
<p>각 범주에 해당하면 1,<br>해당하지 않으면 0을 넣는다.</p>
<p>이 과정을 일반적으로<br>One-Hot Encoding이라고 부른다.</p>
<p>다만 선형 회귀와 같이<br>완전한 다중공선성을 피해야 하는 모델에서는<br>범주 하나를 기준으로 제외하는 방식도 사용한다.</p>
<pre><code class="language-text">drop_first = true</code></pre>
<p>즉, 범주형 데이터를 수치형으로 변환할 때도<br>모델의 특성을 고려해야 한다.</p>
<hr>
<h1 id="데이터-탐색-전에-확인할-현실적인-문제">데이터 탐색 전에 확인할 현실적인 문제</h1>
<p>7단계를 시작하기 전에도<br>확인해야 하는 부분이 있다.</p>
<pre><code class="language-text">데이터 출처를 신뢰할 수 있는가?

오래된 데이터는 아닌가?

CSV, JSON, XML, SQL 중 어떤 형식인가?

데이터 크기는 어느 정도인가?

저장 공간은 충분한가?

여러 출처의 데이터를 합쳐야 하는가?

데이터 버전은 관리되고 있는가?

각 Column에 대한 문서가 존재하는가?</code></pre>
<p>데이터가 작다면 Excel이나 Pandas만으로도 처리할 수 있다.</p>
<p>하지만 수백만 개 이상의 데이터와<br>중첩 JSON, 이미지, 시계열이 포함된다면<br>Spark나 Cloud Storage 같은 도구가 필요할 수 있다.</p>
<p>Data Exploration은 단순 분석 기술이 아니라<br>데이터의 크기와 구조에 맞는 시스템 선택도 포함한다.</p>
<hr>
<h1 id="교내-ai-경진대회에서-내가-놓쳤던-부분">교내 AI 경진대회에서 내가 놓쳤던 부분</h1>
<p>이번 강의를 들으며<br>이전에 참가했던 AI 경진대회가 떠올랐다.</p>
<p>당시 나는 주어진 데이터를 빠르게 모델에 넣고<br>성능을 높이는 데 집중했다.</p>
<pre><code class="language-text">TF-IDF

LinearSVC

후처리 Rule

Masking</code></pre>
<p>같은 방법을 여러 번 시도했다.</p>
<p>하지만 돌이켜보면<br>초반 데이터 탐색이 충분히 구조적이지 않았다.</p>
<pre><code class="language-text">어떤 클래스가 특히 부족한지

특정 Feature가 Target과 어떤 관계인지

잘못된 형식의 Sample은 없는지

모호한 Sample이 어떤 그룹에 몰려 있는지

Outlier에 가까운 입력은 무엇인지

분포가 Train과 Blind Test에서 달라지는지</code></pre>
<p>를 처음부터 체계적으로 정리했다면<br>실험 방향이 덜 중구난방이었을 것이다.</p>
<p>실제로 대회 후반부에 성능을 높인 것도<br>모델을 더 복잡하게 만든 결과라기보다</p>
<pre><code class="language-text">어떤 유형에서 틀리는지 분석하고,

Masking이 언제 살리고 언제 망치는지 보고,

특정 그룹을 따로 진단한 것</code></pre>
<p>에 가까웠다.</p>
<p>결국 그것도 Data Exploration의 일부였다.</p>
<p>나는 당시 모델링을 먼저 하고<br>나중에 데이터 탐색을 수행한 셈이다.</p>
<blockquote>
<p>모델이 틀렸다고 생각하기 전에<br>먼저 데이터가 어떤 구조를 가지고 있는지 확인했어야 했다.</p>
</blockquote>
<hr>
<h1 id="이번-강의를-들으며-느낀-점">이번 강의를 들으며 느낀 점</h1>
<p>처음에는 Data Exploration을<br>그냥 데이터의 평균을 구하고 그래프를 그리는 과정이라고 생각했다.</p>
<p>하지만 실제로는 훨씬 넓은 과정이었다.</p>
<pre><code class="language-text">변수의 의미를 확인하고,

분포를 이해하고,

변수 사이의 관계를 찾고,

결측치의 발생 원인을 추론하고,

이상치가 오류인지 중요한 신호인지 판단하고,

필요한 변환과 Feature를 만드는 것</code></pre>
<p>이 모든 것이 Data Exploration에 포함된다.</p>
<p>그리고 가장 인상 깊었던 점은<br>정해진 하나의 처리 방법이 없다는 것이다.</p>
<pre><code class="language-text">결측치
→ 무조건 삭제하지 않는다.

이상치
→ 무조건 제거하지 않는다.

로그 변환
→ 모든 수치형 변수에 적용하지 않는다.

Feature
→ 많을수록 무조건 좋은 것이 아니다.</code></pre>
<p>모든 판단은<br>문제의 목적과 데이터가 생성된 도메인에 따라 달라진다.</p>
<p>이미 Python과 scikit-learn에는<br>결측치 처리와 이상치 탐지를 위한 도구가 많이 존재한다.</p>
<p>하지만 도구를 실행하는 것과<br>결과를 올바르게 해석하는 것은 다른 문제다.</p>
<p>결국 중요한 것은</p>
<blockquote>
<p>어떤 함수를 사용할지 외우는 것이 아니라,<br>왜 이 처리가 필요한지를 이해하는 것</p>
</blockquote>
<p>이라고 느꼈다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>결론은 간단하다.</p>
<p>모델을 학습하기 전에<br>데이터를 먼저 이해해야 한다.</p>
<p>Data Exploration의 7단계는 다음과 같다.</p>
<pre><code class="language-text">1. Variable Identification
2. Univariate Analysis
3. Bi-variate Analysis
4. Missing Values Treatment
5. Outlier Treatment
6. Variable Transformation
7. Variable Creation</code></pre>
<p>이 과정을 통해 다음을 확인할 수 있다.</p>
<pre><code class="language-text">데이터의 구조

각 변수의 분포

변수 사이의 관계

결측치의 원인

이상치의 의미

필요한 전처리

새롭게 만들 수 있는 Feature</code></pre>
<p>좋은 모델을 선택하는 것도 중요하다.</p>
<p>하지만 모델이 학습하는 것은<br>결국 우리가 제공한 데이터다.</p>
<pre><code class="language-text">강력한 모델
+
잘못된 데이터
=
잘못된 결과</code></pre>
<p>반대로 단순한 모델이라도<br>데이터를 제대로 이해하고 좋은 Feature를 제공하면<br>충분히 좋은 결과를 만들 수 있다.</p>
<p>이번 강의를 통해<br>머신러닝의 시작점은 모델 선택이 아니라는 것을 다시 느꼈다.</p>
<blockquote>
<p>모델을 의심하기 전에<br>먼저 데이터를 의심하자.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🇺🇸 USC 빅데이터 컨퍼런스 참가기🇺🇸 | 데이터 사이언스란 무엇인가? (1) ]]></title>
            <link>https://velog.io/@daehyun_lee/USC-Data-Science-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@daehyun_lee/USC-Data-Science-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Wed, 22 Jul 2026 18:36:52 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터-사이언스란-무엇인가">데이터 사이언스란 무엇인가?</h1>
<p>USC에서 첫 번째로 들은 강의의 주제는<br><strong>Data Science</strong>였다.</p>
<p>수업을 듣기 전에는 데이터 사이언스를 조금 단순하게 생각했다.</p>
<pre><code class="language-text">데이터를 모은다.
↓
분석한다.
↓
그래프를 그린다.</code></pre>
<p>대충 이런 느낌이었다.</p>
<p>하지만 강의를 듣고 보니<br>데이터 사이언스는 단순히 데이터를 분석하는 기술 하나를 의미하지 않았다.</p>
<p>통계학, 컴퓨터 과학, 도메인 지식이 함께 결합된 분야였고,<br>데이터를 수집하는 순간부터 실제 의사결정에 활용하는 순간까지의<br>전체 과정을 다루는 분야에 가까웠다.</p>
<p>이번 글에서는 USC에서 들은 첫 번째 강의를 바탕으로<br>데이터 사이언스가 무엇인지 정리해보고자 한다.</p>
<hr>
<h2 id="데이터-사이언스란">데이터 사이언스란?</h2>
<p>강의 자료에서는 데이터 사이언스를 다음과 같이 설명했다.</p>
<blockquote>
<p>통계학, 과학적 컴퓨팅, 과학적 방법론, 알고리즘과 시스템을 이용해<br>정형·비정형 데이터로부터 지식과 인사이트를 추출하는 학문 분야</p>
</blockquote>
<p>말이 조금 어렵다.</p>
<p>조금 쉽게 풀어보면 다음과 같다.</p>
<pre><code class="language-text">다양한 형태의 데이터를 수집하고,

필요한 형태로 정리하고,

통계와 알고리즘으로 분석한 뒤,

현실의 문제를 이해하거나 해결하는 데 사용하는 것</code></pre>
<p>이라고 볼 수 있다.</p>
<p>여기서 중요한 점은<br>데이터 사이언스가 단순히 데이터를 계산하는 분야가 아니라는 것이다.</p>
<p>그 데이터가 발생한 분야에 대한<br><strong>도메인 지식</strong>도 함께 필요하다.</p>
<p>예를 들어 의료 데이터를 분석한다고 해보자.</p>
<pre><code class="language-text">환자의 나이
혈압
혈당
복용 약물
진단 결과</code></pre>
<p>이런 데이터가 있다고 해서<br>컴퓨터와 통계만 알면 모든 문제를 해결할 수 있는 것은 아니다.</p>
<p>각 수치가 의료적으로 어떤 의미를 가지는지 이해해야 하고,<br>어떤 관계가 실제로 중요한지 판단할 수 있어야 한다.</p>
<p>즉, 데이터 사이언스는 다음 세 분야가 만나는 지점에 있다.</p>
<pre><code class="language-text">Computer Science / IT

Math / Statistics

Domain / Business Knowledge</code></pre>
<p>강의 자료의 벤 다이어그램에서도<br>이 세 영역이 겹치는 부분을 Data Science로 표현하고 있었다.</p>
<hr>
<h2 id="데이터-사이언스와-컴퓨터-과학은-같은-것일까">데이터 사이언스와 컴퓨터 과학은 같은 것일까?</h2>
<p>데이터 사이언스는 컴퓨터 과학과 밀접하게 연결되어 있지만<br>완전히 같은 분야는 아니다.</p>
<p>컴퓨터 과학은 주로 다음과 같은 문제를 다룬다.</p>
<pre><code class="language-text">- 컴퓨터는 어떻게 동작하는가?
- 소프트웨어를 어떻게 설계할 것인가?
- 데이터를 어떻게 효율적으로 저장하고 처리할 것인가?
- 시스템의 성능과 안정성을 어떻게 확보할 것인가?</code></pre>
<p>반면 데이터 사이언스는 다음 질문에 더 가깝다.</p>
<pre><code class="language-text">- 이 데이터에는 어떤 패턴이 있는가?
- 이 현상이 왜 발생했는가?
- 앞으로 어떤 일이 발생할 것인가?
- 이 결과를 어떤 의사결정에 활용할 수 있는가?</code></pre>
<p>컴퓨터 과학이 계산을 가능하게 하는 기술과 시스템을 만든다면,<br>데이터 사이언스는 그 시스템 안의 데이터를 이용해<br>의미와 인사이트를 찾아낸다고 볼 수 있다.</p>
<p>물론 현실에서는 둘을 완전히 분리하기 어렵다.</p>
<p>데이터를 수집하려면 서버가 필요하고,<br>대규모 데이터를 저장하려면 데이터베이스가 필요하며,<br>분석 결과를 서비스에 적용하려면 소프트웨어 개발이 필요하다.</p>
<p>결국 데이터 사이언스는<br>컴퓨터 과학과 협력해야 실제 서비스로 이어질 수 있다.</p>
<hr>
<h2 id="백엔드-개발자인-나에게-데이터-사이언스란">백엔드 개발자인 나에게 데이터 사이언스란?</h2>
<p>수업을 들으며 이런 생각이 들었다.</p>
<blockquote>
<p>나는 데이터 사이언티스트가 되려는 것일까?</p>
</blockquote>
<p>지금까지 나는 백엔드 개발자를 목표로 공부해왔다.</p>
<p>서버를 만들고,<br>데이터베이스를 설계하고,<br>API를 구현하고,<br>시스템이 안정적으로 동작하도록 만드는 것에 더 관심이 있었다.</p>
<p>그렇다면 데이터 사이언스를<br>내 진로와 전혀 다른 분야라고 생각해야 할까?</p>
<p>내가 내린 결론은 아니다.</p>
<p>나에게 데이터 사이언스는<br>새로운 직업을 선택하라는 의미라기보다<br>문제를 해결할 때 활용할 수 있는 강력한 도구에 가깝다.</p>
<p>예를 들어 내가 자동매매 프로그램을 만든다고 해보자.</p>
<p>백엔드 개발 능력만 있다면 다음을 만들 수 있다.</p>
<pre><code class="language-text">- 증권사 API 연결
- 주문 서버
- 계좌 데이터 저장
- 실시간 시세 WebSocket 연결
- 주문 내역 관리</code></pre>
<p>하지만 여기에 데이터 사이언스를 활용하면<br>한 단계 더 나아갈 수 있다.</p>
<pre><code class="language-text">- 특정 시장 상황에서 주문 실패율 분석
- 거래 패턴 기반 이상 주문 탐지
- 사용자별 투자 성향 분석
- 시계열 데이터를 이용한 위험도 예측
- 전략별 수익률 비교</code></pre>
<p>즉, 백엔드가 시스템을 만든다면<br>데이터 사이언스는 그 시스템에서 발생하는 데이터를 통해<br>더 나은 판단을 할 수 있도록 도와준다.</p>
<p>그래서 나 같은 개발자는<br>데이터 사이언스를 하나의 독립된 진로로만 볼 필요는 없다고 생각했다.</p>
<blockquote>
<p>자신의 도메인에 데이터 사이언스를 결합하는 것이 중요하다.</p>
</blockquote>
<hr>
<h2 id="데이터란-무엇일까">데이터란 무엇일까?</h2>
<p>데이터베이스 과목에서는 데이터를<br>현실 세계를 표현하는 값이라고 배웠다.</p>
<p>예를 들어 카페 주문 시스템이 있다고 해보자.</p>
<pre><code class="language-text">주문 번호: 1024
메뉴: 아이스 아메리카노
가격: 3000
주문 시간: 08:42
결제 수단: 카드</code></pre>
<p>이 값들은 현실에서 발생한 주문을<br>컴퓨터가 저장할 수 있는 형태로 표현한 것이다.</p>
<p>데이터는 숫자만을 의미하지 않는다.</p>
<pre><code class="language-text">- 숫자
- 문자열
- 이미지
- 영상
- 음성
- 로그
- 위치
- 클릭 기록
- 결제 기록</code></pre>
<p>현실에서 관찰하고 표현할 수 있는 것이라면<br>대부분 데이터가 될 수 있다.</p>
<p>이런 데이터는 현실 세계를 이해하는 데 도움을 준다.</p>
<p>사람의 기억이나 감각만으로는<br>수백만 명의 행동을 동시에 이해할 수 없다.</p>
<p>하지만 데이터를 수집하면<br>수많은 사람의 행동을 하나의 패턴으로 바라볼 수 있다.</p>
<hr>
<h1 id="data-information-knowledge-wisdom">Data, Information, Knowledge, Wisdom</h1>
<p>강의에서는 데이터가 지혜로 발전하는 과정을<br>네 단계로 설명했다.</p>
<pre><code class="language-text">Data
↓
Information
↓
Knowledge
↓
Wisdom</code></pre>
<p>이를 <strong>DIKW 구조</strong>라고 볼 수 있다.</p>
<hr>
<h2 id="1-data">1. Data</h2>
<p>Data는 아직 해석되지 않은 값이다.</p>
<pre><code class="language-text">08:00
3500
아메리카노
서울</code></pre>
<p>이 값들만 보면<br>각각 무엇을 의미하는지 명확하지 않다.</p>
<p>즉, Data는 사실이나 현상을 표현하는<br>가공되지 않은 기호와 값에 가깝다.</p>
<hr>
<h2 id="2-information">2. Information</h2>
<p>Information은 데이터를 정리하고 해석하여<br>의미를 부여한 것이다.</p>
<pre><code class="language-text">오늘 오전 8시에
서울의 한 매장에서
3500원짜리 아메리카노가 판매되었다.</code></pre>
<p>이제 각각의 값이 연결되면서<br>누가, 무엇을, 어디서, 언제 했는지 알 수 있게 되었다.</p>
<p>즉,</p>
<pre><code class="language-text">Data + Context = Information</code></pre>
<p>이라고 볼 수 있다.</p>
<hr>
<h2 id="3-knowledge">3. Knowledge</h2>
<p>Knowledge는 여러 정보 사이의 관계와 패턴을 이해한 것이다.</p>
<p>예를 들어 한 달 동안의 주문 정보를 분석했더니<br>다음과 같은 패턴을 발견했다고 해보자.</p>
<pre><code class="language-text">평일 오전 8시부터 9시 사이에는
아메리카노 판매량이 크게 증가한다.</code></pre>
<p>단순히 주문이 발생했다는 사실을 넘어서<br>시간과 판매량 사이의 관계를 파악한 것이다.</p>
<hr>
<h2 id="4-wisdom">4. Wisdom</h2>
<p>Wisdom은 발견한 지식을<br>올바른 의사결정과 행동에 활용하는 것이다.</p>
<p>앞의 패턴을 바탕으로 다음과 같이 결정할 수 있다.</p>
<pre><code class="language-text">평일 오전 8시 이전에
아메리카노 원두와 컵을 미리 준비한다.

해당 시간대에는
추가 직원을 배치한다.</code></pre>
<p>이제 데이터가 실제 행동으로 연결되었다.</p>
<p>정리하면 다음과 같다.</p>
<pre><code class="language-text">Data
→ 가공되지 않은 값

Information
→ 의미가 부여된 데이터

Knowledge
→ 정보 사이의 관계와 패턴

Wisdom
→ 지식을 이용한 적절한 판단과 행동</code></pre>
<p>단순히 데이터를 많이 모으는 것만으로는 부족하다.</p>
<p>결국 데이터에서 의미를 찾아내고,<br>그 의미를 의사결정에 활용해야 가치가 생긴다.</p>
<hr>
<h2 id="big-data와-data-science는-같은-것일까">Big Data와 Data Science는 같은 것일까?</h2>
<p>처음에는 Big Data와 Data Science를<br>비슷한 개념이라고 생각하기 쉽다.</p>
<p>하지만 강의에서는 둘을 구분했다.</p>
<p>강의 자료의 비유가 굉장히 직관적이었다.</p>
<pre><code class="language-text">Big Data = 원유

Data Science = 원유를 정제하는 과정</code></pre>
<p>Big Data는 대량의 데이터를<br>수집하고, 전송하고, 저장하는 것에 가깝다.</p>
<pre><code class="language-text">- 데이터 수집
- 대규모 저장
- 분산 처리
- 데이터 파이프라인</code></pre>
<p>반면 Data Science는<br>그 데이터를 이용해 유용한 결과를 만드는 과정이다.</p>
<pre><code class="language-text">- 패턴 발견
- 분석
- 예측
- 시각화
- 의사결정</code></pre>
<p>원유를 많이 가지고 있다고 해서<br>곧바로 자동차를 움직일 수 있는 것은 아니다.</p>
<p>정제 과정을 거쳐야 연료로 사용할 수 있다.</p>
<p>데이터도 마찬가지다.</p>
<blockquote>
<p>데이터를 많이 보유하는 것과<br>데이터를 잘 활용하는 것은 다른 문제다.</p>
</blockquote>
<hr>
<h2 id="데이터가-많으면-무조건-좋을까">데이터가 많으면 무조건 좋을까?</h2>
<p>큰 데이터는 더 많은 현상을 표현할 가능성이 있다.</p>
<p>작은 데이터에서는 발견할 수 없던 패턴을<br>큰 데이터에서는 발견할 수 있다.</p>
<p>예를 들어 고객이 10명뿐이라면<br>다양한 연령과 직업, 소비 패턴을 충분히 표현하기 어렵다.</p>
<p>하지만 고객이 100만 명이라면<br>더 다양한 행동과 예외 상황을 관찰할 수 있다.</p>
<p>이런 의미에서 데이터가 많아질수록<br>표현할 수 있는 현실의 범위도 넓어진다.</p>
<p>하지만 데이터가 많다고<br>무조건 좋은 결과를 얻는 것은 아니다.</p>
<pre><code class="language-text">잘못된 데이터 100만 개</code></pre>
<p>보다</p>
<pre><code class="language-text">문제와 관련된 정확한 데이터 1만 개</code></pre>
<p>가 더 유용할 수도 있다.</p>
<p>데이터의 양뿐 아니라 다음 요소도 중요하다.</p>
<pre><code class="language-text">- 정확성
- 완전성
- 일관성
- 최신성
- 문제와의 관련성
- 편향 여부</code></pre>
<p>즉, 데이터의 크기와 함께<br>데이터의 품질을 평가해야 한다.</p>
<hr>
<h2 id="데이터를-수집하는-것만으로는-부족하다">데이터를 수집하는 것만으로는 부족하다</h2>
<p>데이터를 많이 모았다고 해서<br>자동으로 좋은 분석 결과가 나오는 것은 아니다.</p>
<p>예를 들어 자동매매 프로그램에<br>다음과 같은 데이터가 있다고 해보자.</p>
<pre><code class="language-text">주가
거래량
체결 시간
매수·매도 구분</code></pre>
<p>이 값을 그대로 모델에 넣을 수도 있다.</p>
<p>하지만 새로운 Feature를 만들면<br>더 의미 있는 패턴을 발견할 수 있다.</p>
<pre><code class="language-text">- 최근 5분 평균 거래량
- 직전 가격 대비 상승률
- 매수·매도 비율
- 변동성
- 장 시작 이후 경과 시간</code></pre>
<p>이처럼 기존 데이터로부터<br>문제 해결에 더 유용한 값을 만드는 과정을<br>Feature Engineering이라고 한다.</p>
<p>결국 중요한 것은<br>데이터의 개수가 아니라<br>문제를 표현할 수 있는 좋은 Feature를 만드는 것이다.</p>
<hr>
<h1 id="데이터-사이언스와-ai">데이터 사이언스와 AI</h1>
<p>데이터 사이언스와 AI는 밀접하게 연결되어 있지만<br>완전히 같은 개념은 아니다.</p>
<p>강의 자료에서는 둘의 관계를 다음과 같이 표현했다.</p>
<pre><code class="language-text">Data Science
→ Insight를 만든다.

Machine Learning
→ Prediction을 만든다.</code></pre>
<p>데이터 사이언스는 데이터를 통해<br>현상을 이해하고 인사이트를 발견하는 넓은 과정이다.</p>
<p>머신러닝은 그 과정에서 사용할 수 있는<br>하나의 방법이라고 볼 수 있다.</p>
<p>예를 들어 고객 이탈 문제를 생각해보자.</p>
<p>데이터 사이언스의 관점에서는 다음을 분석할 수 있다.</p>
<pre><code class="language-text">- 어떤 고객이 주로 이탈하는가?
- 이탈은 어느 시점에 증가하는가?
- 가격 정책과 이탈률은 어떤 관계가 있는가?</code></pre>
<p>머신러닝을 사용하면 다음 질문을 풀 수 있다.</p>
<pre><code class="language-text">- 이 고객이 다음 달에 이탈할 것인가?</code></pre>
<p>즉, AI와 머신러닝은<br>데이터 사이언스 안에서 예측과 자동화를 담당하는<br>중요한 도구라고 볼 수 있다.</p>
<p>그리고 AI 모델의 성능 역시<br>결국 데이터의 품질과 양에 크게 의존한다.</p>
<p>아무리 좋은 알고리즘을 사용해도<br>학습할 데이터가 부족하거나 잘못되어 있다면<br>좋은 결과를 내기 어렵다.</p>
<hr>
<h2 id="왜-지금-데이터-사이언스가-중요해졌을까">왜 지금 데이터 사이언스가 중요해졌을까?</h2>
<p>데이터 사이언스가 갑자기 생긴 분야는 아니다.</p>
<p>하지만 최근 중요성이 크게 높아진 이유는<br>두 가지라고 볼 수 있다.</p>
<pre><code class="language-text">1. 데이터를 수집할 수 있는 환경이 좋아졌다.
2. 데이터를 처리할 수 있는 컴퓨팅 파워가 증가했다.</code></pre>
<p>과거에는 데이터를 수집하는 것 자체가 어려웠다.</p>
<p>지금은 거의 모든 서비스가<br>사용자의 행동을 디지털 형태로 기록할 수 있다.</p>
<pre><code class="language-text">- 페이지 방문
- 버튼 클릭
- 검색 기록
- 구매 기록
- 영상 시청 시간
- 스크롤 위치
- 앱 체류 시간</code></pre>
<p>클라우드 환경도 발전하면서<br>서버와 데이터베이스를 직접 구축하지 않아도<br>필요한 만큼 자원을 사용할 수 있게 되었다.</p>
<p>강의에서는 AWS SageMaker, GCP Vertex AI, Databricks 같은<br>Cloud-native Platform의 활용도 중요해지고 있다고 설명했다.</p>
<p>즉, 데이터 수집과 저장, 분석에 필요한 비용이 낮아지면서<br>더 많은 조직이 데이터를 활용할 수 있게 되었다.</p>
<hr>
<h1 id="비즈니스에서-데이터를-어떻게-활용할까">비즈니스에서 데이터를 어떻게 활용할까?</h1>
<p>데이터 사이언스의 활용 사례를 떠올리면<br>Amazon과 Netflix를 생각할 수 있다.</p>
<p>Amazon은 단순히<br>사용자가 무엇을 구매했는지만 보는 것이 아니다.</p>
<pre><code class="language-text">- 어떤 상품 페이지를 방문했는가?
- 몇 초 동안 머물렀는가?
- 장바구니에 넣었다가 제거했는가?
- 어떤 상품과 비교했는가?
- 어느 순서로 페이지를 이동했는가?</code></pre>
<p>이런 행동을 종합해<br>상품 추천과 마케팅, 재고 관리에 활용할 수 있다.</p>
<p>Netflix도 마찬가지다.</p>
<pre><code class="language-text">- 어떤 콘텐츠를 클릭했는가?
- 몇 분까지 시청했는가?
- 중간에 이탈했는가?
- 다시 시청했는가?
- 어떤 썸네일에 더 반응했는가?</code></pre>
<p>이런 데이터는<br>단순 추천을 넘어 콘텐츠 제작과 투자 결정에도 활용될 수 있다.</p>
<p>강의 자료에서도 금융, 유통, 전자상거래, 헬스케어, 에너지, 게임 등<br>다양한 분야에서 데이터 사이언스가 사용된다고 설명했다.</p>
<p>데이터는 단순히 분석 보고서를 만들기 위한 것이 아니다.</p>
<pre><code class="language-text">제품
서비스
마케팅
운영
공급망
위험 관리</code></pre>
<p>전체 비즈니스 과정에 활용될 수 있다.</p>
<hr>
<h1 id="data-lifecycle">Data Lifecycle</h1>
<p>데이터 사이언스는<br>모델을 한 번 학습하고 끝나는 작업이 아니다.</p>
<p>강의 자료에서는 데이터의 전체 생명주기를<br>다음과 같이 설명했다.</p>
<pre><code class="language-text">Plan
↓
Acquire
↓
Process
↓
Analyze
↓
Preserve
↓
Publish / Share</code></pre>
<hr>
<h2 id="1-plan">1. Plan</h2>
<p>어떤 문제를 해결할지 계획한다.</p>
<pre><code class="language-text">무엇을 알고 싶은가?

어떤 데이터가 필요한가?

결과를 어디에 사용할 것인가?</code></pre>
<p>목표가 명확하지 않으면<br>필요 없는 데이터만 많이 수집할 수 있다.</p>
<hr>
<h2 id="2-acquire">2. Acquire</h2>
<p>필요한 데이터를 수집한다.</p>
<pre><code class="language-text">- 서비스 로그
- 데이터베이스
- 외부 API
- 센서
- 설문조사
- 공개 데이터</code></pre>
<p>이 과정에서 데이터의 출처와<br>수집 방법도 함께 기록해야 한다.</p>
<hr>
<h2 id="3-process">3. Process</h2>
<p>수집한 데이터를 사용할 수 있도록 정리한다.</p>
<pre><code class="language-text">- 결측치 처리
- 중복 제거
- 형식 통일
- 이상치 확인
- 데이터 결합
- Feature 생성</code></pre>
<p>실제 데이터 사이언스 프로젝트에서<br>많은 시간이 소요되는 단계다.</p>
<hr>
<h2 id="4-analyze">4. Analyze</h2>
<p>정리한 데이터를 분석한다.</p>
<pre><code class="language-text">- 통계 분석
- 시각화
- 공간 분석
- 이미지 분석
- 머신러닝 모델링
- 결과 해석</code></pre>
<p>단순히 모델 점수를 높이는 것뿐 아니라<br>그 결과가 무엇을 의미하는지도 이해해야 한다.</p>
<hr>
<h2 id="5-preserve">5. Preserve</h2>
<p>데이터와 분석 결과를 안전하게 보존한다.</p>
<pre><code class="language-text">- 백업
- 접근 권한 관리
- 데이터 품질 기록
- 메타데이터 관리
- 버전 관리</code></pre>
<p>데이터가 유실되거나 변조되면<br>분석 결과도 신뢰할 수 없게 된다.</p>
<hr>
<h2 id="6-publish--share">6. Publish / Share</h2>
<p>마지막으로 분석 결과를 공유한다.</p>
<pre><code class="language-text">- 보고서
- 대시보드
- API
- 서비스 기능
- 논문
- 데이터셋</code></pre>
<p>좋은 분석도 다른 사람이 이해하거나 활용하지 못하면<br>가치를 만들기 어렵다.</p>
<hr>
<h1 id="데이터-사이언티스트는-무엇을-하는-사람일까">데이터 사이언티스트는 무엇을 하는 사람일까?</h1>
<p>강의 자료에서는 데이터 사이언티스트를<br>단순히 모델을 만드는 사람으로 설명하지 않았다.</p>
<p>데이터 사이언티스트는 다음과 같은 역할을 수행한다.</p>
<pre><code class="language-text">- 문제의 핵심 질문 발견
- 데이터 수집과 품질 검증
- 분석 가설 설정
- 통계적 검증
- 모델링
- 결과 해석
- 의사결정자와의 커뮤니케이션</code></pre>
<p>특히 기억에 남았던 내용은<br>데이터 사이언티스트에게 필요한 가장 중요한 태도가<br><strong>Curiosity</strong>, 즉 호기심이라는 점이었다.</p>
<p>단순히 주어진 데이터를 분석하는 데서 끝나지 않고<br>다음 질문을 계속 던져야 한다.</p>
<pre><code class="language-text">왜 이런 결과가 나왔을까?

이 Feature는 정말 필요한가?

데이터에 편향은 없을까?

다른 원인이 숨어 있지는 않을까?

이 결과를 실제로 사용할 수 있을까?</code></pre>
<hr>
<h2 id="코딩-능력은-여전히-중요하다">코딩 능력은 여전히 중요하다</h2>
<p>강의에서는 데이터 사이언티스트의<br>가장 기본적인 능력 중 하나로 코딩을 언급했다.</p>
<p>Python과 SQL은 여전히 중요하다.</p>
<p>하지만 최근에는 모든 알고리즘을<br>처음부터 직접 구현하는 능력만을 요구하지는 않는다.</p>
<p>좋은 라이브러리와 도구가 많아졌기 때문이다.</p>
<pre><code class="language-text">- Pandas
- NumPy
- scikit-learn
- PyTorch
- XGBoost
- AutoML
- AI Copilot</code></pre>
<p>중요한 것은 라이브러리를 사용하는 것 자체가 아니다.</p>
<pre><code class="language-text">어떤 문제에 어떤 도구를 써야 하는지,

그 결과를 믿어도 되는지,

잘못된 결과를 구분할 수 있는지</code></pre>
<p>판단할 수 있어야 한다.</p>
<p>데이터 사이언티스트의 코딩은<br>단순히 코드를 많이 작성하는 것이 아니라<br>적절한 도구를 조합해 문제를 해결하는 것에 가깝다.</p>
<hr>
<h1 id="과거와-지금의-데이터-사이언티스트">과거와 지금의 데이터 사이언티스트</h1>
<p>강의에서는 데이터 사이언티스트에게 요구되는 능력이<br>과거와 현재 사이에서 어떻게 달라졌는지도 설명했다.</p>
<hr>
<h2 id="과거">과거</h2>
<pre><code class="language-text">- Python, R, SQL 중심의 강한 구현 능력
- 알고리즘을 직접 구현하는 능력
- Hadoop, Spark와 같은 분산 시스템 지식
- 통계와 수학에 대한 깊은 이론적 이해
- 데이터 수집부터 배포까지 혼자 수행하는 능력</code></pre>
<hr>
<h2 id="현재">현재</h2>
<pre><code class="language-text">- AI 도구와 AutoML 활용
- 모델 결과 검증
- Prompt Engineering
- Cloud-native Platform 활용
- 효율적인 Pipeline 설계
- 데이터 거버넌스와 개인정보 보호
- 커뮤니케이션과 스토리텔링
- 특정 산업에 대한 도메인 지식</code></pre>
<p>AI가 코드 생성과 반복 작업을 도와주기 시작하면서<br>단순 구현 능력만으로 차별화하기 어려워졌다.</p>
<p>대신 다음 능력이 더욱 중요해졌다.</p>
<pre><code class="language-text">AI의 결과가 맞는지 판단하는 능력

데이터의 편향과 오류를 찾는 능력

현실에서 검증 가능한 실험을 설계하는 능력

복잡한 결과를 비전문가에게 설명하는 능력</code></pre>
<hr>
<h2 id="커뮤니케이션과-스토리텔링">커뮤니케이션과 스토리텔링</h2>
<p>데이터 분석 결과는<br>데이터 사이언티스트만 이해해서는 의미가 없다.</p>
<p>비즈니스 담당자나 의사결정자가<br>분석 결과를 이해하고 행동할 수 있어야 한다.</p>
<p>예를 들어 다음처럼 말하는 것보다</p>
<pre><code class="language-text">모델의 F1 Score가 0.84입니다.</code></pre>
<p>다음처럼 설명하는 것이 더 중요할 수 있다.</p>
<pre><code class="language-text">현재 모델을 사용하면
이탈 가능성이 높은 고객 100명 중 약 84명을 찾아낼 수 있습니다.

이 고객들에게 사전에 혜택을 제공하면
예상 이탈률을 줄일 가능성이 있습니다.</code></pre>
<p>같은 결과라도<br>현실의 행동과 연결해 설명해야 한다.</p>
<p>그래서 Data Storytelling이 중요해진다.</p>
<hr>
<h2 id="도메인-지식">도메인 지식</h2>
<p>강의에서 특히 강조된 부분 중 하나가<br>도메인 지식이었다.</p>
<p>AI가 일반적인 코드 작성과 분석을 도와줄수록<br>사람이 제공해야 하는 차별점은<br>문제 분야에 대한 깊은 이해가 된다.</p>
<pre><code class="language-text">금융
헬스케어
기후
물류
제조
광고
추천 시스템</code></pre>
<p>각 분야에서 중요한 문제와 데이터는 다르다.</p>
<p>좋은 질문이 좋은 인사이트를 만든다.</p>
<p>그리고 좋은 질문은<br>그 분야를 이해하는 사람만 만들 수 있다.</p>
<blockquote>
<p>Quality Questions determine Quality Insights.</p>
</blockquote>
<p>결국 데이터 사이언티스트도<br>소프트웨어 엔지니어와 마찬가지로<br>도메인 지식이 중요하다.</p>
<hr>
<h2 id="윤리와-책임-있는-ai">윤리와 책임 있는 AI</h2>
<p>데이터의 영향력이 커지면서<br>윤리와 개인정보 보호도 중요해졌다.</p>
<pre><code class="language-text">- 데이터는 적법하게 수집되었는가?
- 특정 집단에 불리한 편향은 없는가?
- 모델의 판단을 설명할 수 있는가?
- 민감한 개인정보가 노출되지는 않는가?
- 데이터에 접근할 권한은 적절히 관리되고 있는가?</code></pre>
<p>특히 의료와 금융 데이터는<br>잘못 사용될 경우 큰 피해를 만들 수 있다.</p>
<p>따라서 데이터 사이언스는<br>성능만 높은 모델을 만드는 분야가 아니다.</p>
<p>정확성, 공정성, 보안, 개인정보 보호를<br>함께 고려해야 한다.</p>
<hr>
<h1 id="데이터는-자산인가">데이터는 자산인가?</h1>
<p>강의 자료에서는 데이터를<br>가치를 가진 자산으로 바라봤다.</p>
<p>좋은 데이터 관리는 다음을 가능하게 한다.</p>
<pre><code class="language-text">- 필요한 데이터를 빠르게 찾는다.
- 여러 조직이 데이터를 공유한다.
- 기존 데이터를 다시 사용한다.
- 중복 수집을 줄인다.
- 시간과 비용을 절감한다.</code></pre>
<p>하지만 모든 데이터를 무조건 저장하는 것이 좋은 것은 아니다.</p>
<p>보관 비용이 발생하고,<br>개인정보 유출 위험도 커지기 때문이다.</p>
<p>따라서 다음 질문이 필요하다.</p>
<pre><code class="language-text">이 데이터는 왜 필요한가?

얼마 동안 보관해야 하는가?

누가 접근할 수 있는가?

다시 사용할 가능성이 있는가?

잘못된 데이터는 어떻게 수정할 것인가?</code></pre>
<p>데이터를 자산으로 본다는 것은<br>무조건 많이 모은다는 뜻이 아니다.</p>
<p>가치 있는 데이터를<br>잘 관리하고 안전하게 활용한다는 뜻에 가깝다.</p>
<hr>
<h1 id="프로젝트-주제는-진로와-연결하자">프로젝트 주제는 진로와 연결하자</h1>
<p>강의에서 프로젝트 주제를 정할 때<br>자신이 앞으로 나아갈 진로와 관련된 분야를 선택하라는 이야기가 있었다.</p>
<p>이 부분에는 나도 공감했다.</p>
<p>단순히 점수를 받기 위한 프로젝트보다<br>내가 실제로 관심 있는 문제를 다루는 것이 더 의미 있다.</p>
<p>예를 들어 내가 진행하고 있는<br>증권사 API 기반 자동매매 프로젝트에<br>데이터 사이언스를 결합할 수 있다.</p>
<pre><code class="language-text">- 주문 실패 데이터 분석
- 시장 상황별 API 응답 지연 분석
- 거래 패턴 이상 탐지
- 종목별 변동성 분석
- 투자 전략 성능 비교</code></pre>
<p>이렇게 하면 프로젝트는<br>단순한 서버 구현에서 끝나지 않는다.</p>
<pre><code class="language-text">Software Engineering
+
Financial Domain Knowledge
+
Data Science</code></pre>
<p>가 결합된 프로젝트가 된다.</p>
<p>이번 USC 활동에서도<br>단순히 데이터 사이언스 기술을 배우는 것보다<br>내가 어떤 문제에 이 기술을 사용할지 고민해보는 것이 중요할 것 같다.</p>
<hr>
<h1 id="이번-강의를-들으며-느낀-점">이번 강의를 들으며 느낀 점</h1>
<p>처음에는 데이터 사이언스를<br>데이터 분석가가 사용하는 기술이라고만 생각했다.</p>
<p>하지만 강의를 듣고 보니<br>데이터 사이언스는 특정 직업만을 위한 기술이 아니었다.</p>
<p>데이터가 발생하는 모든 분야에서<br>문제를 더 정확하게 이해하고<br>더 나은 결정을 내리는 데 사용할 수 있는 방법론이었다.</p>
<p>그리고 데이터 사이언스만으로<br>모든 문제를 해결할 수 있는 것도 아니었다.</p>
<pre><code class="language-text">데이터를 수집하는 시스템

데이터를 저장하는 데이터베이스

분석을 수행하는 모델

결과를 제공하는 서비스

문제를 이해하는 도메인 지식</code></pre>
<p>이 모든 요소가 함께 필요하다.</p>
<p>그래서 데이터 사이언스는<br>혼자 존재하는 분야라기보다<br>여러 분야를 연결하는 영역에 가깝다는 생각이 들었다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>결론은 간단하다.</p>
<p>데이터 사이언스는<br>단순히 데이터를 분석하거나 그래프를 그리는 분야가 아니다.</p>
<pre><code class="language-text">데이터를 수집하고,

저장하고,

정리하고,

분석하고,

지식으로 변환하고,

현실의 의사결정에 적용하는 전체 과정</code></pre>
<p>을 다루는 분야다.</p>
<p>그리고 이 과정에는 다음 요소가 함께 필요하다.</p>
<pre><code class="language-text">Computer Science / IT

Math / Statistics

Domain / Business Knowledge</code></pre>
<p>데이터는 현실을 표현한다.</p>
<p>하지만 데이터 자체만으로는 아무것도 말해주지 않는다.</p>
<p>데이터에 의미를 부여하면 Information이 되고,<br>관계를 이해하면 Knowledge가 되며,<br>이를 올바른 판단에 사용하면 Wisdom이 된다.</p>
<p>결국 이번 강의의 핵심은 이것이라고 생각한다.</p>
<blockquote>
<p>데이터를 많이 모으는 것이 중요한 것이 아니라,<br>그 데이터로 어떤 질문을 하고 어떤 가치를 만들어낼 것인가가 중요하다.</p>
</blockquote>
<p>나 역시 백엔드 개발자로서<br>데이터 사이언스를 별개의 분야로만 바라보기보다<br>내가 만드는 시스템을 더 똑똑하게 만드는 도구로 활용해보고자 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Tabular ML (5)] | LLM은 Tabular Data에 어떻게 활용될 수 있을까?]]></title>
            <link>https://velog.io/@daehyun_lee/LLM%EC%9D%80-Tabular-Data%EC%97%90-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%99%9C%EC%9A%A9%EB%90%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@daehyun_lee/LLM%EC%9D%80-Tabular-Data%EC%97%90-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%99%9C%EC%9A%A9%EB%90%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Fri, 17 Jul 2026 03:38:08 GMT</pubDate>
            <description><![CDATA[<h1 id="llm은-tabular-data에-어떻게-활용될-수-있을까">LLM은 Tabular Data에 어떻게 활용될 수 있을까?</h1>
<p><img src="https://velog.velcdn.com/images/daehyun_lee/post/6282a638-55ba-4f33-aff6-25a4dea3f72e/image.jpg" alt=""></p>
<p>LLM은 Large Language Model의 약자다.</p>
<p>말 그대로 방대한 텍스트를 학습하고,<br>언어를 이해하고 생성하는 모델이다.</p>
<p>LLM은 수많은 문장 속에서<br>단어와 개념 사이의 의미 관계를 학습한다.</p>
<p>그래서 다음과 같은 일을 할 수 있다.</p>
<pre><code class="language-text">- 자연어 지시 이해
- 질문에 대한 답변 생성
- 새로운 문제에 대한 Zero-shot 추론
- 적은 예시만으로 Few-shot 학습
- 현실 세계의 개념과 도메인 지식 활용</code></pre>
<p>그런데 여기서 한 가지 의문이 생겼다.</p>
<blockquote>
<p>LLM은 언어 모델인데<br>행과 열로 구성된 Tabular Data에는 어떻게 활용할 수 있을까?</p>
</blockquote>
<p>Tabular Data는 문장이 아니다.</p>
<pre><code class="language-text">나이 | 연봉 | 직업 | 대출 승인 여부
25  | 3000 | 학생 | 거절
42  | 8000 | 회사원 | 승인</code></pre>
<p>LLM은 기본적으로 token sequence를 입력으로 받는다.</p>
<p>그렇다면 이런 표를<br>LLM이 이해할 수 있는 언어 형태로 바꿔야 한다.</p>
<p>이번 강의는 바로 이 질문에서 시작한다.</p>
<blockquote>
<p>LLM의 Semantic Knowledge를<br>Tabular Machine Learning에 어떻게 활용할 수 있을까?</p>
</blockquote>
<hr>
<h2 id="기존-tabular-model과-llm의-차이">기존 Tabular Model과 LLM의 차이</h2>
<p>기존 Tabular Model은 주로<br>실제로 관찰된 값과 통계적 패턴을 학습한다.</p>
<p>예를 들어 다음 데이터가 있다고 해보자.</p>
<table>
<thead>
<tr>
<th>Age</th>
<th>BMI</th>
<th>Diagnosis Code</th>
<th>Target</th>
</tr>
</thead>
<tbody><tr>
<td>58</td>
<td>31.2</td>
<td>E11</td>
<td>Diabetes</td>
</tr>
<tr>
<td>24</td>
<td>21.4</td>
<td>Z00</td>
<td>Normal</td>
</tr>
</tbody></table>
<p>XGBoost나 Random Forest 같은 모델은<br>각 column의 값과 target 사이의 통계적 관계를 학습한다.</p>
<p>하지만 <code>BMI</code>나 <code>Diagnosis Code</code>가  
실제 세계에서 어떤 의미를 가지는지는 알지 못한다.</p>
<p>반면 LLM은 이미 텍스트 학습을 통해<br>다음과 같은 지식을 가지고 있을 수 있다.</p>
<pre><code class="language-text">BMI는 체질량지수를 의미한다.

E11은 당뇨병과 관련된 진단 코드다.

특정 직업이나 도시, 질병, 상품 이름은
현실에서 각각 특정한 의미를 가진다.</code></pre>
<p>즉, LLM은 Tabular ML에<br><strong>Semantic Context</strong>를 추가할 수 있다.</p>
<p>기존 모델이</p>
<pre><code class="language-text">숫자 값과 통계적 패턴</code></pre>
<p>을 학습했다면, LLM은 여기에</p>
<pre><code class="language-text">Column 이름의 의미
Value의 현실적 의미
Task 설명
도메인 지식</code></pre>
<p>을 활용할 수 있다.</p>
<p>강의의 핵심 문장을 정리하면 다음과 같다.</p>
<blockquote>
<p>LLM은 Tabular ML을 단순한 수치 패턴 학습에서<br>Semantic Context를 활용한 추론으로 확장한다.</p>
</blockquote>
<hr>
<h1 id="강의에서-다룬-네-가지-질문">강의에서 다룬 네 가지 질문</h1>
<p>이번 강의에서는 LLM과 Tabular Data의 관계를<br>크게 네 가지 질문으로 나누었다.</p>
<pre><code class="language-text">1. LLM이 Tabular Data를 직접 예측할 수 있는가?

2. LLM이 다른 Tabular Predictor의 성능을 높일 수 있는가?

3. LLM이 의미적으로 이상한 Sample을 탐지할 수 있는가?

4. 여러 Table과 Task에 전이 가능한
   LLM 기반 Tabular Foundation Model을 만들 수 있는가?</code></pre>
<p>하나씩 정리해보자.</p>
<hr>
<h1 id="1-table을-text로-바꾸기">1. Table을 Text로 바꾸기</h1>
<p>LLM은 표 구조를 그대로 입력받지 않는다.</p>
<p>따라서 Tabular Data를<br>일렬의 Token Sequence로 변환해야 한다.</p>
<p>이 과정을 다음과 같이 부른다.</p>
<pre><code class="language-text">Serialization
또는
Linearization</code></pre>
<p>예를 들어 다음 row가 있다고 해보자.</p>
<table>
<thead>
<tr>
<th>name</th>
<th>age</th>
<th>job</th>
</tr>
</thead>
<tbody><tr>
<td>Helen</td>
<td>47</td>
<td>Engineer</td>
</tr>
</tbody></table>
<p>이를 여러 형식으로 바꿀 수 있다.</p>
<hr>
<h2 id="json-형식">JSON 형식</h2>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;Helen&quot;,
  &quot;age&quot;: 47,
  &quot;job&quot;: &quot;Engineer&quot;
}</code></pre>
<hr>
<h2 id="markdown-형식">Markdown 형식</h2>
<pre><code class="language-markdown">| name | age | job |
| --- | --- | --- |
| Helen | 47 | Engineer |</code></pre>
<hr>
<h2 id="attribute-value-형식">Attribute-Value 형식</h2>
<pre><code class="language-text">name: Helen, age: 47, job: Engineer</code></pre>
<hr>
<h2 id="문장-형식">문장 형식</h2>
<pre><code class="language-text">Helen is 47 years old and works as an engineer.</code></pre>
<hr>
<h2 id="python-dataframe-형식">Python DataFrame 형식</h2>
<pre><code class="language-python">pd.DataFrame({
    &quot;name&quot;: [&quot;Helen&quot;],
    &quot;age&quot;: [47],
    &quot;job&quot;: [&quot;Engineer&quot;]
})</code></pre>
<p>강의 자료에는 이외에도<br>LaTeX, HTML, CSV와 비슷한 구분자 형식 등<br>여러 Serialization 방식이 소개됐다.</p>
<p>여기서 중요한 점은 이것이다.</p>
<blockquote>
<p>Serialization은 단순한 전처리가 아니라<br>모델 설계의 일부다.</p>
</blockquote>
<p>같은 데이터라도 어떤 형식으로 표현하느냐에 따라<br>LLM이 보는 구조가 달라질 수 있다.</p>
<p>또한 Token 사용량도 달라진다.</p>
<pre><code class="language-text">긴 자연어 문장
→ 의미 전달은 좋지만 Token 비용이 큼

짧은 Key-Value 형식
→ Token은 적지만 Context 전달이 부족할 수 있음</code></pre>
<p>즉, LLM을 Tabular Data에 사용할 때는<br>입력 형식을 어떻게 구성하는지가 매우 중요하다.</p>
<hr>
<h1 id="lift">LIFT</h1>
<p>LIFT는 다음 아이디어를 사용한다.</p>
<blockquote>
<p>언어가 아닌 머신러닝 Task를<br>언어 입력과 언어 출력 문제로 바꾸자.</p>
</blockquote>
<p>예를 들어 Iris 분류 문제를 생각해보자.</p>
<p>원래 입력은 다음과 같다.</p>
<pre><code class="language-text">sepal length = 5.1
sepal width = 3.5
petal length = 1.4
petal width = 0.2</code></pre>
<p>이를 문장으로 바꾼다.</p>
<pre><code class="language-text">An Iris plant has a sepal length of 5.1cm,
a sepal width of 3.5cm,
a petal length of 1.4cm,
and a petal width of 0.2cm.
The class is:</code></pre>
<p>LLM은 다음과 같은 텍스트를 출력한다.</p>
<pre><code class="language-text">Iris-setosa</code></pre>
<p>즉, 입력도 언어로 만들고<br>정답도 언어로 출력하게 한다.</p>
<p>LIFT의 특징은<br>LLM의 Architecture나 Loss를 크게 바꾸지 않는다는 점이다.</p>
<p>기존 언어 모델을<br>자연어 인터페이스를 통해 Tabular Task에 Fine-tuning한다.</p>
<hr>
<h1 id="tabllm">TabLLM</h1>
<p>TabLLM은 하나의 Tabular Row를<br>자연어 문자열로 변환한 뒤 분류에 사용한다.</p>
<p>예를 들어 다음 데이터가 있다고 해보자.</p>
<pre><code class="language-text">Age = 42
Education = Master
Capital Gain = 594</code></pre>
<p>이를 문장으로 바꾼다.</p>
<pre><code class="language-text">The person is 42 years old.
The education level is Master&#39;s degree.
The capital gain is 594 dollars.</code></pre>
<p>그리고 Task를 설명한다.</p>
<pre><code class="language-text">Does this person earn more than 50,000 dollars?
Answer Yes or No.</code></pre>
<p>LLM은 Zero-shot이나 Few-shot 방식으로<br>정답을 예측할 수 있다.</p>
<p>TabLLM에서 중요한 결과 중 하나는 다음과 같다.</p>
<blockquote>
<p>같은 Table이라도 Serialization 방식에 따라<br>예측 성능이 달라질 수 있다.</p>
</blockquote>
<p>즉,</p>
<pre><code class="language-text">Column 이름만 나열한 형식
Value만 나열한 형식
자연어 문장
LLM이 변환한 문장</code></pre>
<p>이 각각 다른 결과를 만들 수 있다.</p>
<p>LLM을 사용한다고 끝나는 것이 아니라<br>어떤 형태로 Table을 보여주는지가 중요하다.</p>
<hr>
<h1 id="tablet">TABLET</h1>
<p>TABLET은 자연어 Instruction이<br>Tabular Prediction에 얼마나 도움이 되는지를 분석한다.</p>
<p>예를 들어 환자의 증상 데이터만 주는 경우와<br>질병에 대한 설명까지 함께 주는 경우를 비교한다.</p>
<p>단순한 입력은 다음과 같을 수 있다.</p>
<pre><code class="language-text">기침: 아니오
코막힘: 예
귀 통증: 예</code></pre>
<p>여기에 Task Instruction을 추가할 수 있다.</p>
<pre><code class="language-text">백일해는 보통 반복적인 심한 기침과
특정 호흡기 증상을 동반한다.

주어진 환자가 백일해 진단 대상인지 판단하라.</code></pre>
<p>LLM은 단순한 Feature 값뿐만 아니라<br>질병에 대한 설명과 판단 기준도 함께 활용한다.</p>
<p>TABLET의 결과는 다음을 보여준다.</p>
<pre><code class="language-text">- 자연어 Instruction은 Zero-shot 성능을 높일 수 있다.
- Few-shot 환경에서도 Task 설명이 도움이 될 수 있다.
- 사람이 작성한 Instruction뿐 아니라
  자동 생성된 Instruction도 유용할 수 있다.</code></pre>
<p>강의 자료에서는<br>기존 분류기로부터 두 종류의 Instruction을 만들었다.</p>
<pre><code class="language-text">Ruleset 기반 Instruction
Prototype 기반 Instruction</code></pre>
<p>Ruleset 방식은 다음과 비슷하다.</p>
<pre><code class="language-text">결혼 상태가 Married이고
교육 수준이 Bachelor 이상이라면
고소득일 가능성이 높다.</code></pre>
<p>Prototype 방식은 대표 Sample과의 유사성을 설명한다.</p>
<pre><code class="language-text">이 사람은
고소득 그룹의 대표적인 Sample과 더 비슷하다.</code></pre>
<p>즉, 기존 Tabular Model이 학습한 정보를<br>LLM이 이해할 수 있는 자연어 Instruction으로 바꾼 것이다.</p>
<hr>
<h1 id="llm이-직접-예측하는-방식의-한계">LLM이 직접 예측하는 방식의 한계</h1>
<p>여기까지 보면 LLM이<br>Tabular Predictor 역할을 직접 수행할 수 있다.</p>
<p>하지만 현실적으로 문제가 많다.</p>
<pre><code class="language-text">1. 모든 Row를 Text로 변환해야 한다.

2. 모든 Sample마다 LLM을 호출해야 한다.

3. 입력 Prompt와 Serialization 형식에 민감하다.

4. 긴 Table은 Context Window를 많이 사용한다.

5. Numerical Reasoning이 항상 정확하지는 않다.

6. 추론 비용과 Latency가 크다.</code></pre>
<p>예를 들어 100만 개의 고객 row를 예측해야 한다고 해보자.</p>
<p>각 row마다 LLM API를 호출한다면<br>비용과 시간이 매우 커질 수 있다.</p>
<p>따라서 대규모 Tabular Prediction에서는<br>LLM이 모든 Sample을 직접 예측하는 방식이<br>항상 현실적인 것은 아니다.</p>
<p>여기서 새로운 방향이 등장한다.</p>
<blockquote>
<p>LLM이 최종 예측자가 되지 않고,<br>기존 Tabular Model을 도와주면 어떨까?</p>
</blockquote>
<hr>
<h1 id="2-llm을-semantic-component로-사용하기">2. LLM을 Semantic Component로 사용하기</h1>
<p>직접 예측의 대안은<br>LLM을 Tabular ML Pipeline의 일부로 사용하는 것이다.</p>
<p>예를 들어 LLM은 다음 일을 맡는다.</p>
<pre><code class="language-text">- 새로운 Feature 생성
- Feature 생성 Rule 개선
- Decision Tree Rule 보정
- 도메인 지식 주입</code></pre>
<p>최종 예측은 다음과 같은 모델이 맡는다.</p>
<pre><code class="language-text">XGBoost
LightGBM
Random Forest
Linear Model
MLP</code></pre>
<p>즉, 구조는 다음과 같다.</p>
<pre><code class="language-text">LLM
↓
Semantic Feature 또는 Rule 생성
↓
일반 Tabular Model
↓
최종 Prediction</code></pre>
<p>이 방식은 모든 Test Row를 LLM에 넣지 않아도 된다.</p>
<p>따라서 비용과 Latency를 줄이면서<br>LLM의 Semantic Knowledge를 활용할 수 있다.</p>
<hr>
<h1 id="caafe">CAAFE</h1>
<p>Tabular Data에서는<br>Feature Engineering이 성능에 큰 영향을 준다.</p>
<p>예를 들어 원본 Feature가 다음과 같다고 해보자.</p>
<pre><code class="language-text">Weight
Height
Income
Debt
Last Login Date</code></pre>
<p>도메인 지식을 활용하면 다음 Feature를 만들 수 있다.</p>
<pre><code class="language-text">BMI = Weight / Height²

Debt Ratio = Debt / Income

Days Since Last Login</code></pre>
<p>하지만 이런 Feature를 만들려면<br>데이터와 도메인을 이해해야 한다.</p>
<p>CAAFE는 LLM에게 다음 정보를 제공한다.</p>
<pre><code class="language-text">- Dataset 설명
- Column 이름
- Task 설명
- 기존 Feature</code></pre>
<p>그리고 LLM에게<br>새로운 Feature를 만드는 Python 코드를 작성하게 한다.</p>
<p>흐름은 다음과 같다.</p>
<pre><code class="language-text">1. Dataset Context 제공

2. LLM이 Feature Engineering 코드 생성

3. 생성된 Python 코드 실행

4. Tabular Model로 Cross-validation 수행

5. 성능이 좋아지면 Feature 유지

6. 다시 새로운 Feature 생성</code></pre>
<p>예를 들어 LLM이 다음과 같은 코드를 만들 수 있다.</p>
<pre><code class="language-python">df[&quot;debt_ratio&quot;] = df[&quot;debt&quot;] / (df[&quot;income&quot;] + 1e-6)</code></pre>
<p>새로운 Feature를 추가한 뒤<br>모델 성능을 평가한다.</p>
<pre><code class="language-text">성능 향상
→ Feature 유지

성능 하락
→ Feature 제거</code></pre>
<p>CAAFE의 핵심은 이것이다.</p>
<blockquote>
<p>LLM은 Column 이름과 Task 설명에 담긴 의미를<br>실제 모델이 사용할 수 있는 Feature로 변환한다.</p>
</blockquote>
<p>또한 더 강한 LLM이<br>더 유용한 Feature를 생성할 가능성도 확인됐다.</p>
<hr>
<h1 id="llm-as-few-shot-feature-engineer">LLM as Few-Shot Feature Engineer</h1>
<p>Label이 매우 적은 Few-shot 환경에서는<br>복잡한 모델을 학습하기 어렵다.</p>
<p>이 연구에서는 LLM에게<br>적은 수의 학습 Sample과 Task 설명을 보여준다.</p>
<p>그다음 Class별 Rule을 생성하게 한다.</p>
<p>예를 들면 다음과 같다.</p>
<pre><code class="language-text">Rule 1:
Age &gt;= 50이고 Debt Ratio가 높으면
고위험 Class일 가능성이 높다.

Rule 2:
Age &lt; 30이고 Income이 높으면
저위험 Class일 가능성이 높다.</code></pre>
<p>각 Rule은 새로운 Binary Feature가 된다.</p>
<pre><code class="language-text">Rule 1 만족 → 1
Rule 1 불만족 → 0</code></pre>
<p>이렇게 생성된 Feature를 이용해<br>간단한 Linear Classifier를 학습한다.</p>
<p>또 LLM이 서로 다른 Rule을 여러 번 생성하면<br>각 Rule은 서로 다른 Semantic Hypothesis를 담을 수 있다.</p>
<p>그래서 여러 Predictor를 만든 뒤<br>Ensemble한다.</p>
<pre><code class="language-text">LLM Rule Set 1 → Linear Model 1

LLM Rule Set 2 → Linear Model 2

LLM Rule Set 3 → Linear Model 3

↓ Ensemble

최종 예측</code></pre>
<p>즉, LLM은 직접 모든 Row를 예측하지 않는다.</p>
<p>LLM은 Rule과 Feature를 만들고,<br>최종 Prediction은 가벼운 모델이 수행한다.</p>
<hr>
<h1 id="decision-tree-reasoning을-활용한-feature-generation">Decision Tree Reasoning을 활용한 Feature Generation</h1>
<p>다음 연구에서는 LLM을<br>Feature Generation Rule의 Optimizer로 사용한다.</p>
<p>기본 흐름은 다음과 같다.</p>
<pre><code class="language-text">1. LLM에게 새로운 Feature 아이디어 요청

2. 해당 Feature를 만드는 Rule 생성

3. Rule을 적용해 새로운 Column 생성

4. Tabular Model 학습

5. Validation Score 측정

6. Decision Tree Rule을 이용해 결과 해석

7. 해석 결과를 다시 LLM에 제공

8. Rule 개선</code></pre>
<p>예를 들어 질병 예측 문제에서<br>LLM이 다음 Feature를 제안했다고 해보자.</p>
<pre><code class="language-text">흡연 가능성</code></pre>
<p>원본 데이터에는 흡연 여부가 직접 없을 수 있다.</p>
<p>하지만 다음 Feature를 조합해<br>간접적인 Rule을 만들 수 있다.</p>
<pre><code class="language-text">호흡 곤란이 있고
특정 증상이 존재하면
흡연 가능성이 높다.</code></pre>
<p>새로운 Column을 만든 뒤<br>모델의 성능을 평가한다.</p>
<p>여기서 단순히 Validation Score만 주는 것이 아니라<br>Decision Tree가 만든 Rule을 함께 피드백한다.</p>
<pre><code class="language-text">호흡 곤란 = Yes
피로 = Yes
→ 질병 예측</code></pre>
<p>이 Rule은 LLM에게<br>이전 Feature가 어떤 방식으로 사용되었는지 알려준다.</p>
<p>강의 자료에서는 Decision Tree Reasoning이<br>도움이 되는 이유를 다음처럼 설명한다.</p>
<pre><code class="language-text">Validation Score만 제공
→ 왜 성능이 변했는지 알기 어려움

Decision Tree Rule 제공
→ 어떤 Feature와 조건이 유용했는지 해석 가능</code></pre>
<p>즉, Decision Tree는<br>LLM에게 간결하고 해석 가능한 Feedback을 제공한다.</p>
<p>이 과정을 반복하면<br>LLM이 점점 더 나은 Feature Rule을 생성할 수 있다.</p>
<hr>
<h1 id="delta">DeLTa</h1>
<p>DeLTa는 LLM과 Decision Tree를 결합하지만<br>Feature Generation과는 방향이 조금 다르다.</p>
<p>핵심은 다음과 같다.</p>
<blockquote>
<p>여러 Decision Tree의 Rule을 LLM이 정제하고,<br>그 Rule을 이용해 기존 Tree의 예측 오류를 보정한다.</p>
</blockquote>
<p>구조는 다음과 같다.</p>
<pre><code class="language-text">여러 Decision Tree
↓
Tree Rule 추출
↓
LLM이 Rule 통합 및 개선
↓
Refined Rule 생성
↓
기존 Decision Tree 예측 보정</code></pre>
<p>중요한 점이 하나 있다.</p>
<p>DeLTa는 반드시 Column 이름의 Semantic Knowledge를 사용하는 방식은 아니다.</p>
<p>강의 자료에서는<br>Column 이름의 의미보다 <strong>Decision Tree Rule 자체</strong>를 이용한다고 설명한다.</p>
<p>즉, LLM은 다음과 같은 Rule을 읽고 정제한다.</p>
<pre><code class="language-text">x1 &gt; 3.2
x4 &lt;= 0.7
x7 = 1</code></pre>
<p>Column 이름이 의미 없는 <code>x1</code>, <code>x4</code>여도<br>Rule 구조를 바탕으로 개선을 시도할 수 있다.</p>
<p>이 점에서 DeLTa는<br>LLM의 일반적인 Semantic Prior뿐만 아니라<br>Language Reasoning 능력 자체를 활용하는 사례라고 볼 수 있다.</p>
<hr>
<h1 id="llm을-semantic-component로-사용하는-이유">LLM을 Semantic Component로 사용하는 이유</h1>
<p>이 방식의 장점은 명확하다.</p>
<pre><code class="language-text">- 모든 Test Row를 LLM에 넣지 않아도 된다.
- 추론 비용이 줄어든다.
- Latency를 줄일 수 있다.
- 기존 Tabular Model의 빠른 예측 성능을 유지한다.
- Semantic Knowledge를 Pipeline에 주입할 수 있다.</code></pre>
<p>즉, 실제 시스템에서는</p>
<blockquote>
<p>LLM이 직접 예측하기보다<br>모델이 더 잘 예측할 수 있도록 돕는 역할</p>
</blockquote>
<p>이 더 현실적인 사용 방법일 수 있다.</p>
<hr>
<h1 id="3-llm을-이용한-tabular-anomaly-detection">3. LLM을 이용한 Tabular Anomaly Detection</h1>
<p>Anomaly Detection은 이상치를 찾는 문제다.</p>
<p>처음에는 이상치를 이렇게 생각하기 쉽다.</p>
<pre><code class="language-text">값이 너무 크거나 작으면 이상치다.</code></pre>
<p>하지만 실제 이상치는 그렇게 단순하지 않다.</p>
<p>희귀한 값이라고 항상 이상치는 아니다.</p>
<p>반대로 자주 등장하는 값도<br>잘못된 Context에서는 이상할 수 있다.</p>
<p>예를 들어 다음 거래를 보자.</p>
<pre><code class="language-text">결제 금액: 500만 원
결제 지역: 서울
사용자 직업: 기업 임원</code></pre>
<p>이 거래는 큰 금액이지만<br>반드시 이상 거래라고 할 수는 없다.</p>
<p>반대로 다음 거래는 금액이 작아도 이상할 수 있다.</p>
<pre><code class="language-text">결제 금액: 5만 원
결제 지역: 해외
직전 결제 위치: 서울
시간 차이: 2분</code></pre>
<p>숫자 하나만 보면 평범하지만<br>전체 Context를 보면 의심스럽다.</p>
<p>즉, Anomaly는 도메인과 상황에 따라 결정된다.</p>
<p>기존 모델은 주로 Raw Feature Value를 사용한다.</p>
<p>하지만 LLM은 다음 정보를 활용할 수 있다.</p>
<pre><code class="language-text">- Column 설명
- 단위
- Dataset 설명
- 도메인 지식
- Feature 사이의 의미 관계</code></pre>
<hr>
<h1 id="anollm">AnoLLM</h1>
<p>AnoLLM은 정상 Tabular Row를<br>Text로 변환하고 LLM에 학습시킨다.</p>
<p>기본 아이디어는 다음과 같다.</p>
<blockquote>
<p>정상 데이터와 비슷한 문장은 높은 확률을 갖고,<br>비정상 데이터는 낮은 확률을 가질 것이다.</p>
</blockquote>
<p>흐름은 다음과 같다.</p>
<pre><code class="language-text">1. Normal Row를 표준화된 문장으로 변환

2. 정상 데이터만 사용해 LLM Fine-tuning

3. Test Row를 Text로 변환

4. LLM Likelihood 계산

5. Likelihood가 낮으면 Anomaly로 판단</code></pre>
<p>예를 들어 정상 거래가 다음 형태라면</p>
<pre><code class="language-text">User 1 made a transaction of 50 dollars in San Jose.</code></pre>
<p>모델이 본 적 없는 이상한 조합은<br>낮은 Likelihood를 가질 수 있다.</p>
<pre><code class="language-text">User 1 made a transaction of 1599 dollars in Delhi.</code></pre>
<p>AnoLLM의 장점은<br>숫자형과 범주형이 섞인 데이터도<br>Text 형태로 통합할 수 있다는 것이다.</p>
<p>하지만 한계도 있다.</p>
<pre><code class="language-text">- 정상 데이터로 LLM을 Fine-tuning해야 한다.
- 추론 비용이 크다.
- 낮은 Language Likelihood가
  항상 실제 Semantic Anomaly를 의미하지는 않는다.</code></pre>
<p>즉, 언어 모델이 문장을 낯설게 느끼는 것과<br>현실에서 비정상인 것은 완전히 같지 않을 수 있다.</p>
<hr>
<h1 id="retabad">ReTabAD</h1>
<p>필기에서 <code>RETADAD</code>라고 적은 부분은<br>정확히는 <strong>ReTabAD</strong>다.</p>
<p>ReTabAD가 던지는 질문은 다음과 같다.</p>
<blockquote>
<p>Tabular Anomaly Detection에서<br>Metadata가 얼마나 중요한가?</p>
</blockquote>
<p>기존 Anomaly Detection Benchmark는<br>주로 Feature 값만 제공했다.</p>
<pre><code class="language-text">0.31, 25, 117, 1, 0</code></pre>
<p>하지만 각 값이 무엇을 의미하는지 설명하지 않는 경우가 많다.</p>
<pre><code class="language-text">- Column 이름
- 단위
- Feature 설명
- Dataset 설명
- Label의 의미
- 도메인 Context</code></pre>
<p>이런 정보가 빠져 있었다.</p>
<p>그런데 LLM의 장점은<br>바로 Textual Metadata와 Semantic Knowledge를 활용하는 것이다.</p>
<p>Metadata가 없다면 LLM도<br>값의 의미를 알기 어렵다.</p>
<p>ReTabAD는 기존 Tabular Dataset에<br>다음 정보를 복원한 Benchmark를 만든다.</p>
<pre><code class="language-text">- Dataset 이름
- Dataset 설명
- Column 이름
- Column 설명
- 데이터 타입
- Label 설명
- Anomaly 정의</code></pre>
<p>예를 들어 <code>Prothrombin = 11.5</code>라는 값만 보면<br>무슨 의미인지 알기 어렵다.</p>
<p>하지만 다음 설명을 함께 제공하면 달라진다.</p>
<pre><code class="language-text">Prothrombin:
혈액 응고 시간을 초 단위로 나타낸 값이며,
간 기능 이상과 관련될 수 있다.</code></pre>
<p>이제 LLM은 단순히 숫자가 크다는 것뿐 아니라<br>그 값이 간 기능 저하를 의미할 수 있다는 Context를 활용한다.</p>
<p>ReTabAD는 Semantic Metadata를 활용하면<br>Context-aware Zero-shot Anomaly Detection과<br>더 해석 가능한 Reasoning이 가능하다는 점을 보여준다.</p>
<hr>
<h1 id="autoanoeval">AutoAnoEval</h1>
<p>Anomaly Detection에서는<br>어떤 모델이 좋은지 선택하는 것도 어렵다.</p>
<p>왜냐하면 Validation Set에<br>실제 Anomaly Label이 없을 수 있기 때문이다.</p>
<pre><code class="language-text">정상 Sample만 존재
↓
어떤 Detector가 좋은지 평가하기 어려움</code></pre>
<p>또 Anomaly Detector마다<br>잘 잡는 이상치 종류가 다를 수 있다.</p>
<pre><code class="language-text">- 극단적으로 큰 값
- 정상 경계에 가까운 미세한 이상치
- Feature 조합이 이상한 Sample
- 특정 도메인 Rule을 위반하는 Sample</code></pre>
<p>그래서 AutoAnoEval은 다음 질문을 던진다.</p>
<blockquote>
<p>평가용 Anomaly를 직접 생성할 수는 없을까?</p>
</blockquote>
<p>핵심 아이디어는<br>Decision Tree와 LLM을 이용해<br>의미 있는 Pseudo-Anomaly를 생성하는 것이다.</p>
<p>먼저 정상 데이터를 기반으로<br>Decision Tree의 정상 경로를 추출한다.</p>
<pre><code class="language-text">Temperature &lt;= 37.5
Humidity &lt;= 80
Pressure &gt;= 1000</code></pre>
<p>그다음 LLM이 이 정상 경로의 일부 조건을<br>의미 있게 비튼다.</p>
<pre><code class="language-text">Temperature &gt; 42
Humidity는 정상
Pressure는 정상</code></pre>
<p>너무 무작위적인 값을 만드는 것이 아니라<br>도메인적으로 가능한 Anomaly Rule을 생성한다.</p>
<p>이렇게 만든 Pseudo-Anomaly를 이용해<br>여러 Detector를 평가한다.</p>
<pre><code class="language-text">Pseudo Evaluation Set
↓
여러 Anomaly Detector 평가
↓
가장 좋은 모델 선택</code></pre>
<p>강의 자료에서는<br>이렇게 만들어진 평가 Set이<br>실제 Anomaly에서의 모델 성능과 가까운 추정치를 제공할 수 있다고 설명한다.</p>
<p>즉, LLM은 여기서 Anomaly를 직접 탐지하기보다<br><strong>Detector를 평가하고 선택하기 위한 데이터를 만드는 역할</strong>을 한다.</p>
<hr>
<h1 id="llm이-anomaly-detection에-유용한-이유">LLM이 Anomaly Detection에 유용한 이유</h1>
<p>정리하면 LLM은 Tabular Anomaly Detection에서<br>세 가지 방식으로 활용될 수 있다.</p>
<pre><code class="language-text">1. Anomaly Scoring
정상 Row의 Language Likelihood를 학습

2. Context-aware Detection
Metadata와 Domain Knowledge를 이용해 이상 여부 판단

3. Model Selection
Semantic Pseudo-Anomaly를 생성해 Detector 평가</code></pre>
<p>기존 Anomaly Detection이</p>
<pre><code class="language-text">값이 얼마나 희귀한가?</code></pre>
<p>를 중심으로 봤다면, LLM은</p>
<pre><code class="language-text">이 값과 조합이
현실적인 Context에서 말이 되는가?</code></pre>
<p>를 판단하는 데 도움을 줄 수 있다.</p>
<hr>
<h1 id="4-llm-기반-tabular-foundation-model">4. LLM 기반 Tabular Foundation Model</h1>
<p>마지막 질문은 이것이다.</p>
<blockquote>
<p>여러 Table과 Task에서 학습한 지식을<br>새로운 Table에도 전이할 수 있을까?</p>
</blockquote>
<p>이미 자연어에서는 Foundation Model이 존재한다.</p>
<pre><code class="language-text">대규모 Text Pretraining
↓
새로운 Task에 Zero-shot / Few-shot 적용</code></pre>
<p>Tabular Data에서도 같은 방향을 생각할 수 있다.</p>
<pre><code class="language-text">수많은 Table과 Prediction Task 학습
↓
새로운 Table에 적용</code></pre>
<p>하지만 Tabular Data는 매우 이질적이다.</p>
<pre><code class="language-text">Table마다 Column 수가 다름

Column 이름이 다름

데이터 타입이 다름

Label 의미가 다름

Task가 분류일 수도 있고 회귀일 수도 있음</code></pre>
<p>따라서 여러 Table을<br>하나의 공통 형식으로 통합하는 것이 어렵다.</p>
<hr>
<h1 id="tabula-8b">TabuLa-8B</h1>
<p>TabuLa-8B는<br>LLM을 기반으로 만든 Tabular Foundation Model이다.</p>
<p>Base Model은 Llama 3-8B다.</p>
<p>핵심 아이디어는 다음과 같다.</p>
<blockquote>
<p>수많은 Tabular Prediction Task를 Text로 Serialization한 뒤<br>LLM을 대규모로 Fine-tuning한다.</p>
</blockquote>
<p>강의 자료에 따르면 학습 데이터는 다음 규모다.</p>
<pre><code class="language-text">약 21억 개의 Row

400만 개 이상의 고유 Table

329개의 Unseen Dataset에서 평가</code></pre>
<p>각 Table을 공통된 언어 형식으로 바꾼 뒤<br>여러 Prediction Task를 학습시킨다.</p>
<p>그 결과 새로운 Dataset에서도<br>Zero-shot 또는 Few-shot Prediction을 수행할 수 있다.</p>
<p>Foundation Model의 장점은<br>Label이 적은 환경에서 특히 드러난다.</p>
<pre><code class="language-text">학습 Sample이 충분함
→ 기존 XGBoost도 강력함

학습 Sample이 매우 적음
→ 사전학습된 Tabular Foundation Model이 유리할 수 있음</code></pre>
<p>즉, TabuLa-8B는<br>LLM의 사전학습과 Transfer Learning 능력을<br>Tabular Prediction에 적용한 시도라고 볼 수 있다.</p>
<hr>
<h1 id="전체-흐름-정리">전체 흐름 정리</h1>
<p>이번 강의의 흐름을 정리하면 다음과 같다.</p>
<hr>
<h2 id="1-llm이-직접-예측">1. LLM이 직접 예측</h2>
<pre><code class="language-text">Table
↓
Serialization
↓
Prompt + Task Description
↓
LLM
↓
Prediction</code></pre>
<p>대표 연구:</p>
<pre><code class="language-text">LIFT
TabLLM
TABLET</code></pre>
<p>장점:</p>
<pre><code class="language-text">- Semantic Knowledge 활용
- Zero-shot / Few-shot 가능
- 자연어 Task 설명 활용</code></pre>
<p>단점:</p>
<pre><code class="language-text">- 비용이 크다.
- 긴 Table에 약하다.
- Serialization에 민감하다.
- Numerical Reasoning이 어려울 수 있다.</code></pre>
<hr>
<h2 id="2-llm이-다른-model을-보조">2. LLM이 다른 Model을 보조</h2>
<pre><code class="language-text">LLM
↓
Feature 또는 Rule 생성
↓
일반 Tabular Model
↓
Prediction</code></pre>
<p>대표 연구:</p>
<pre><code class="language-text">CAAFE
LLM Few-shot Feature Engineer
OCTree
DeLTa</code></pre>
<p>장점:</p>
<pre><code class="language-text">- LLM을 매 Row마다 호출하지 않아도 된다.
- 기존 모델의 빠른 추론을 유지할 수 있다.
- Semantic Knowledge를 Feature와 Rule로 변환할 수 있다.</code></pre>
<hr>
<h2 id="3-llm을-anomaly-detection에-활용">3. LLM을 Anomaly Detection에 활용</h2>
<pre><code class="language-text">정상 Row의 Likelihood 학습

Metadata를 이용한 Semantic Reasoning

Pseudo-Anomaly 생성과 Model Selection</code></pre>
<p>대표 연구:</p>
<pre><code class="language-text">AnoLLM
ReTabAD
AutoAnoEval</code></pre>
<hr>
<h2 id="4-llm-기반-tabular-foundation-model-1">4. LLM 기반 Tabular Foundation Model</h2>
<pre><code class="language-text">다양한 Table과 Task에서 사전학습
↓
새로운 Dataset에 Zero-shot / Few-shot 전이</code></pre>
<p>대표 연구:</p>
<pre><code class="language-text">TabuLa-8B</code></pre>
<hr>
<h1 id="이번-강의를-들으며-느낀-점">이번 강의를 들으며 느낀 점</h1>
<p>처음에는 이렇게 생각했다.</p>
<blockquote>
<p>LLM은 언어 모델인데<br>굳이 Tabular Data를 맡길 이유가 있을까?</p>
</blockquote>
<p>숫자 예측은 XGBoost나 LightGBM이 더 빠르고,<br>이미 Tabular Data에서 강력한 모델도 많다.</p>
<p>실제로 LLM에게 모든 Row를 직접 예측하게 하는 방식은<br>비용과 효율 측면에서 좋은 방법이 아닐 수 있다.</p>
<p>하지만 강의를 들으면서<br>LLM의 역할을 조금 다르게 보게 되었다.</p>
<p>LLM의 진짜 장점은<br>숫자 계산 그 자체가 아니었다.</p>
<pre><code class="language-text">Column 이름의 의미를 이해하고,

Task 설명을 이해하고,

도메인 지식을 활용하고,

새로운 Feature와 Rule을 제안하고,

왜 특정 조합이 이상한지 설명하는 것</code></pre>
<p>이 LLM의 강점이었다.</p>
<p>즉, LLM을 기존 Tabular Model의 대체재로만 볼 필요는 없다.</p>
<p>오히려 다음과 같은 역할이 더 잘 맞을 수 있다.</p>
<pre><code class="language-text">Semantic Feature Engineer

Rule Optimizer

Decision Tree Feedback Interpreter

Anomaly Context Reasoner

Foundation Model</code></pre>
<hr>
<h1 id="결론">결론</h1>
<p>결론은 간단하다.</p>
<p>LLM은 Tabular Data를 직접 예측할 수 있다.</p>
<p>Table을 Text로 Serialization하고<br>Task Description과 Instruction을 제공하면<br>Zero-shot 또는 Few-shot Prediction도 가능하다.</p>
<p>하지만 모든 Sample을 LLM에 입력하는 방식은<br>비용과 Context Length 측면에서 한계가 있다.</p>
<p>그래서 더 현실적인 방향은 다음과 같다.</p>
<blockquote>
<p>LLM의 Semantic Knowledge를 활용하고,<br>최종 예측은 효율적인 Tabular Model이 수행하도록 하는 것.</p>
</blockquote>
<p>LLM은 다음과 같은 작업에 강점을 가진다.</p>
<pre><code class="language-text">- 의미 있는 Feature 생성
- Rule 개선
- Decision Tree 예측 보정
- Context-aware Anomaly Detection
- Pseudo-Anomaly 생성
- 새로운 Table로의 Knowledge Transfer</code></pre>
<p>결국 이번 강의의 핵심은 이것이라고 생각한다.</p>
<blockquote>
<p>LLM을 Tabular Predictor로 무조건 사용하는 것이 아니라,<br>Tabular ML Pipeline에서 Semantic Knowledge가 필요한 지점에 배치하자.</p>
</blockquote>
<p>Tabular Data의 성능은<br>Feature를 어떻게 구성하고<br>Task와 Context를 어떻게 이해하느냐에 따라 크게 달라진다.</p>
<p>그리고 LLM은 바로 그<br><strong>의미를 이해하고 활용하는 부분</strong>에서<br>기존 Tabular Model을 보완할 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>