<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SkyWith628</title>
    <link>https://hskywith.tistory.com/</link>
    <description>404...</description>
    <language>ko</language>
    <pubDate>Sat, 5 Sep 2026 00:34:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>harang Lee</managingEditor>
    <image>
      <title>SkyWith628</title>
      <url>https://tistory1.daumcdn.net/tistory/6966183/attach/4de9a3862caa411488bb3f595a17e19b</url>
      <link>https://hskywith.tistory.com</link>
    </image>
    <item>
      <title>모델 선택과 평가 제대로 이해하기 &amp;mdash; NFL부터 교차검증&amp;middot;Selection Bias까지</title>
      <link>https://hskywith.tistory.com/130</link>
      <description>&lt;!--
  tistory.html — 티스토리 HTML 모드 붙여넣기용 다크 전용 스켈레톤.
  본문은 {{CONTENT}} 자리에 주입됨.
--&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;
&lt;!-- title: 모델 선택과 평가 제대로 이해하기 — NFL부터 교차검증·Selection Bias까지 | date: 2026-09-03 | tags: 머신러닝, 모델선택, 교차검증, scikit-learn, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 내용만 붙여넣는다. --&gt;

&lt;p&gt;이번 강의자료는 &quot;모델을 &lt;em&gt;만드는&lt;/em&gt; 법&quot;이 아니라 &quot;만든 모델을 어떻게 &lt;em&gt;고르고 믿을 수 있게 평가하느냐&lt;/em&gt;&quot;에 관한 파트였습니다. 슬라이드가 목적 함수·가설 같은 용어 정리에서 시작해서 No Free Lunch, 오컴의 면도날 같은 철학적 전제를 깔고, 거기서 Bias-Variance 분해로 오차의 정체를 파고든 다음, 홀드아웃·교차검증·하이퍼파라미터 탐색까지 쭉 이어지더라고요. 중간중간 Wolpert, Domingos, Kohavi, Bergstra·Bengio, Cawley·Talbot 같은 원논문 인용이 계속 튀어나오는 게 인상적이었습니다.&lt;/p&gt;

&lt;p&gt;개념이 워낙 많아서, 이 글에서는 자료의 뼈대를 이루는 &lt;b&gt;네 개의 질문&lt;/b&gt;으로 압축해 정리하려고 합니다. &quot;완벽한 모델이 왜 없는가 → 오차는 어디서 오는가 → 성능을 어떻게 공정하게 재는가 → 튜닝은 어떻게 하고 어떤 함정이 있는가&quot;. 각 주장은 scikit-learn 공식 문서와 원논문으로 근거를 붙였고, 공식 문서가 그 개념을 실제로 어떤 예제 코드로 보여주는지도 같이 담았습니다.&lt;/p&gt;

&lt;div class=&quot;toc&quot;&gt;
  &lt;div class=&quot;t&quot;&gt;목차&lt;/div&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;#s1&quot;&gt;큰 그림 — 네 개의 질문&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s2&quot;&gt;완벽한 모델은 없다 — No Free Lunch &amp; 오컴의 면도날&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s3&quot;&gt;오차의 정체 — Bias · Variance · Noise&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s4&quot;&gt;공정하게 평가하기 — 재치환에서 교차검증까지&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s5&quot;&gt;튜닝과 그 함정 — Train/Val/Test, Grid vs Random, Selection Bias&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s6&quot;&gt;치트시트 &amp; 핵심 5가지&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;

&lt;h2 id=&quot;s1&quot;&gt;1. 큰 그림 — 네 개의 질문&lt;/h2&gt;

&lt;p&gt;세부 개념으로 들어가기 전에, 이 조각들이 어떻게 맞물리는지 한 장으로 보겠습니다. 아래 네 칸이 이 글의 나머지 섹션과 그대로 대응합니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;모델 선택·평가&lt;/b&gt; 를 이렇게 쪼갠다&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;완벽한 모델?&lt;/span&gt;애초에 없다&lt;span class=&quot;s&quot;&gt;NFL · Occam&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;오차는 어디서?&lt;/span&gt;세 갈래로 분해&lt;span class=&quot;s&quot;&gt;Bias+Var+Noise&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 재나?&lt;/span&gt;데이터를 아껴 쓴다&lt;span class=&quot;s&quot;&gt;Holdout→CV&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;튜닝·함정?&lt;/span&gt;선택도 과적합된다&lt;span class=&quot;s&quot;&gt;Search · Selection Bias&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;왼쪽에서 오른쪽으로 갈수록 &quot;이론적 전제 → 실전 절차&quot;로 내려옵니다.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;핵심을 관통하는 한 문장은 강의자료 표현처럼 &lt;em&gt;&quot;우리가 가진 일부 데이터로 특정 문제를 풀 최적의 모델을 만들고 고른다&quot;&lt;/em&gt; 입니다. 문제는 이 &lt;em&gt;최적&lt;/em&gt;과 &lt;em&gt;고른다&lt;/em&gt;가 생각보다 위험하다는 거죠. 왜 위험한지 첫 칸부터 봅시다.&lt;/p&gt;

&lt;h2 id=&quot;s2&quot;&gt;2. 완벽한 모델은 없다 — No Free Lunch &amp; 오컴의 면도날&lt;/h2&gt;

&lt;p&gt;모델 선택 이야기를 왜 &quot;완벽한 모델은 없다&quot;로 시작할까요? 만약 모든 문제에서 항상 이기는 만능 알고리즘이 있다면, 애초에 &lt;em&gt;선택&lt;/em&gt;이라는 고민 자체가 필요 없을 테니까요. 그런 게 없다는 걸 못 박는 정리가 바로 No Free Lunch입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;No Free Lunch (공짜 점심은 없다)&lt;/code&gt;: 가능한 모든 문제에 대해 성능을 평균 내면, 어떤 알고리즘도 다른 알고리즘보다 낫지 않다는 정리 / 예: 한 데이터셋에서 이긴 모델은 다른 데이터셋에서 반드시 그만큼 진다.&lt;/p&gt;

&lt;p&gt;직관적으로 말하면, 특정 문제에서 잘 통하는 건 그 문제의 구조에 맞는 &lt;em&gt;가정&lt;/em&gt;을 심어놨기 때문입니다. 그 가정이 안 맞는 다른 문제로 가면 그대로 손해로 돌아옵니다. 강의자료의 막대그래프(Algorithm A vs B)가 딱 이걸 보여줬죠 — 한쪽이 튀어나온 만큼 다른 쪽이 꺼져 있고, 평균선은 결국 같습니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;원논문은 이렇게 말합니다.&lt;/b&gt; Wolpert와 Macready는 어떤 알고리즘이 한 부류의 문제에서 얻은 성능 이득은 다른 부류의 문제에서의 손해로 상쇄된다고 증명했습니다. 한 줄로 요약된 유명한 문장이 &lt;em&gt;&quot;두 알고리즘은 모든 문제에 걸쳐 평균 내면 동등하다&quot;&lt;/em&gt; 입니다. &lt;span class=&quot;src&quot;&gt;(출처: Wolpert &amp; Macready, &quot;No Free Lunch Theorems for Optimization&quot;, IEEE TEC, 1997)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;그럼 실무 지침은 &quot;정답 알고리즘을 외우지 말고, 문제마다 여러 개를 비교하라&quot;가 됩니다. 그런데 후보가 여럿이면 어느 걸 고를지 기준이 필요하고, 여기서 두 번째 원리인 오컴의 면도날이 등장합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Occam's Razor (오컴의 면도날)&lt;/code&gt;: 성능이 비슷하게 경쟁하는 가설이 있으면 &lt;em&gt;가정이 가장 적은(가장 단순한)&lt;/em&gt; 쪽을 고르라는 원칙 / 모델링에선 보통 파라미터 수가 더 적은 모델.&lt;/p&gt;

&lt;div class=&quot;box warn&quot;&gt;⚠️ &lt;b&gt;흔한 오해&lt;/b&gt; — &quot;단순한 모델이 항상 더 정확하다&quot;는 &lt;em&gt;아닙니다&lt;/em&gt;. 강의자료가 인용한 Domingos는 오컴의 면도날을 실용적 &lt;em&gt;휴리스틱&lt;/em&gt;으로는 인정하되, &quot;단순함이 곧 높은 정확도&quot;라는 해석은 이론적으로 반증됐다고 지적합니다. 그래서 슬라이드가 곧바로 &lt;code&gt;c.f) ensemble&lt;/code&gt;(여러 모델 결합 — 오히려 복잡한데 잘 되는 사례)을 붙여둔 거고요. &lt;span class=&quot;src&quot;&gt;(출처: Domingos, &quot;A Few Useful Things to Know about Machine Learning&quot;, CACM, 2012)&lt;/span&gt;&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 새 문제를 받으면 &quot;이 도메인엔 이 모델이 국룰&quot;이라고 단정하지 말고, 선형모델 같은 단순 후보부터 트리·부스팅·신경망까지 &lt;em&gt;여러 개를 같은 조건에서 비교&lt;/em&gt;하는 습관. 그게 NFL의 실전 번역입니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 여러 모델을 비교하려면 &quot;성능&quot;을 숫자로 재야 합니다. 그런데 그 숫자, 즉 오차가 대체 무엇으로 이루어져 있는지부터 봐야 합니다.&lt;/p&gt;

&lt;h2 id=&quot;s3&quot;&gt;3. 오차의 정체 — Bias · Variance · Noise&lt;/h2&gt;

&lt;p&gt;모델을 고른다는 건 결국 &quot;오차가 가장 작은 걸 고른다&quot;는 뜻인데, 그 오차는 한 덩어리가 아니라 성질이 다른 세 조각의 합입니다. 이 분해를 알아야 &quot;지금 내 모델이 덜 배운 건지, 너무 외운 건지&quot;를 진단할 수 있어요.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bias (편향)&lt;/code&gt;: 복잡한 현실을 너무 단순한 모델로 근사해서 &lt;em&gt;체계적으로&lt;/em&gt; 빗나가는 오차 / 예: 곡선 데이터를 직선으로 억지로 맞출 때.&lt;br&gt;
&lt;code&gt;Variance (분산)&lt;/code&gt;: 훈련 데이터가 조금만 바뀌어도 모델이 크게 출렁이는 정도 / 예: 데이터의 노이즈까지 통째로 외운 고차 다항식.&lt;br&gt;
&lt;code&gt;Noise (irreducible error, 줄일 수 없는 오차)&lt;/code&gt;: 데이터 자체에 낀 잡음이라 어떤 모델로도 제거 불가능한 하한.&lt;/p&gt;

&lt;p&gt;강의자료의 과녁 그림이 이 셋을 직관적으로 보여줬습니다. 화살이 과녁 중심에서 한쪽으로 쏠려 몰리면 &lt;em&gt;높은 편향&lt;/em&gt;(계속 같은 방향으로 틀림), 사방으로 흩어지면 &lt;em&gt;높은 분산&lt;/em&gt;(쏠 때마다 제멋대로)이죠. Domingos의 표현을 빌리면 편향은 &quot;계속 같은 걸 틀리는 경향&quot;, 분산은 &quot;진짜 신호와 무관하게 랜덤한 걸 학습하는 경향&quot;입니다.&lt;/p&gt;

&lt;p&gt;수식으로는 기대 예측 오차가 이렇게 세 조각으로 깔끔하게 나뉩니다(슬라이드 p.84의 유도).&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 기대 예측 오차의 Bias-Variance 분해&lt;/span&gt;
E[(y - f̂)²] = Bias[f̂]²  +  Var[f̂]  +  σ²
              (편향²)       (분산)     (노이즈, 줄일 수 없음)&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; scikit-learn의 underfitting/overfitting 예제는 편향과 분산을 모델 복잡도(다항식 차수)로 직접 보여줍니다. 차수 1은 데이터를 못 따라가는 &lt;em&gt;과소적합(underfitting)&lt;/em&gt;, 차수가 너무 높으면 &lt;em&gt;&quot;훈련 데이터의 노이즈를 학습한다&quot;&lt;/em&gt;는 &lt;em&gt;과대적합(overfitting)&lt;/em&gt;이라고 짚죠. 중요한 건 이걸 눈대중이 아니라 &lt;em&gt;교차검증으로 정량화&lt;/em&gt;한다는 점입니다. &lt;span class=&quot;src&quot;&gt;(출처: scikit-learn, &quot;Underfitting vs. Overfitting&quot; 예제)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# scikit-learn 공식 예제 — 복잡도(차수)별 일반화 오차를 CV로 측정&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.model_selection &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; cross_val_score
scores = cross_val_score(
    pipeline, X[:, np.newaxis], y,
    scoring=&lt;span class=&quot;st&quot;&gt;&quot;neg_mean_squared_error&quot;&lt;/span&gt;, cv=&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;)
&lt;span class=&quot;cm&quot;&gt;# degree 1  → MSE 4.08e-01  (underfit, 높은 편향)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# degree 4  → MSE 4.32e-02  (딱 좋음)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# degree 15 → MSE 2.20e-01  (overfit, 높은 분산)&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여기서 자연스럽게 &lt;code&gt;Bias-Variance Trade-off&lt;/code&gt;가 나옵니다. 모델을 복잡하게 만들면 편향은 줄지만 분산이 커지고, 단순하게 만들면 반대가 됩니다. 그래서 총 오차는 U자를 그리고, 그 최저점이 &lt;em&gt;최적 복잡도&lt;/em&gt;예요. 학습 곡선(train/validation accuracy)의 간격으로도 진단할 수 있는데, 두 곡선이 낮은 데서 붙어 수렴하면 높은 편향, 간격이 크게 벌어지면 높은 분산입니다.&lt;/p&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;핵심&lt;/b&gt; — 훈련 데이터로 잰 정확도는 진짜보다 &lt;em&gt;낙관적으로 부풀려집니다&lt;/em&gt;(편향된 추정). 다만 강의자료의 통찰처럼, 이 낙관적 편향이 &lt;em&gt;모든 후보 모델에 똑같이&lt;/em&gt; 작용한다면 모델을 &lt;em&gt;서로 비교&lt;/em&gt;하는 데는 문제가 없습니다. 우리가 알고 싶은 건 절대 성능이 아니라 상대 순위니까요. 문제는 이 &quot;똑같이&quot;가 깨질 때 생깁니다 — 그게 뒤의 Selection Bias죠.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 검증 성능이 낮을 때 처방이 갈립니다. 높은 편향(과소적합)이면 feature를 늘리거나 더 복잡한 모델로, 높은 분산(과대적합)이면 데이터를 더 모으거나 규제(L2·Dropout)·조기 종료로. 어느 쪽인지 모르면 엉뚱한 처방을 하게 됩니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 그런데 이 &quot;일반화 오차&quot;를 대체 어떤 데이터로 재야 부풀려지지 않을까요? 평가 방법의 발전사를 따라가 봅니다.&lt;/p&gt;

&lt;h2 id=&quot;s4&quot;&gt;4. 공정하게 평가하기 — 재치환에서 교차검증까지&lt;/h2&gt;

&lt;p&gt;오차를 세 조각으로 나눴으니, 이제 그걸 &lt;em&gt;믿을 수 있게 측정&lt;/em&gt;할 차례입니다. 평가 방법은 &quot;왜 이전 방법이 위험한가&quot;를 하나씩 메우며 발전해 왔어요.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;① 하면 안 되는 것 — 재치환 평가.&lt;/b&gt; &lt;code&gt;Resubstitution evaluation (재치환 평가)&lt;/code&gt;: 훈련에 쓴 바로 그 데이터로 성능을 재는 것. 공식 문서도 이걸 대놓고 경고합니다 — 방금 본 라벨을 그대로 되뇌는 모델은 &lt;em&gt;&quot;완벽한 점수&quot;&lt;/em&gt;를 받지만 새 데이터엔 쓸모가 없고, 이게 바로 과대적합이라고요. &lt;span class=&quot;src&quot;&gt;(출처: scikit-learn, &quot;Cross-validation&quot; 개요)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;② 기본 — 홀드아웃.&lt;/b&gt; &lt;code&gt;Holdout (홀드아웃)&lt;/code&gt;: 데이터를 훈련용과 테스트용으로 나눠, 테스트셋은 학습에 쓰지 않고 &quot;처음 보는 데이터&quot; 역할로 남겨두는 것. scikit-learn의 기본 예제가 딱 이겁니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — 홀드아웃: iris를 train 60% / test 40%로 분리&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.model_selection &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=&lt;span class=&quot;nu&quot;&gt;0.4&lt;/span&gt;, random_state=&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;)

clf = svm.SVC(kernel=&lt;span class=&quot;st&quot;&gt;'linear'&lt;/span&gt;, C=&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;).fit(X_train, y_train)
clf.score(X_test, y_test)   &lt;span class=&quot;cm&quot;&gt;# → 0.96&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;홀드아웃엔 피할 수 없는 &lt;em&gt;trade-off&lt;/em&gt;가 있습니다. 테스트셋을 크게 떼면 모델이 학습에 쓸 데이터가 줄어 성능이 실제보다 낮게 나오고(비관적 편향), 작게 떼면 성능 추정이 운에 크게 흔들립니다(분산 증가). 게다가 한 번의 분할은 &lt;em&gt;어쩌다 쉬운/어려운 테스트셋&lt;/em&gt;이 걸리는 우연을 통제하지 못하죠.&lt;/p&gt;

&lt;p&gt;여기서 두 가지 보강이 나옵니다. 하나는 분할할 때 클래스 비율을 유지하는 &lt;code&gt;Stratified sampling (계층 샘플링)&lt;/code&gt;인데, 공식 문서는 &lt;code&gt;StratifiedKFold&lt;/code&gt;가 각 fold에 원본과 &lt;em&gt;거의 같은 클래스 비율&lt;/em&gt;을 담아준다고 설명합니다 — 불균형 데이터에서 필수죠. 이게 추정치의 분산·편향에 좋다는 건 Kohavi(1995)가 실증했습니다. 다른 하나는 시드를 바꿔 홀드아웃을 여러 번 반복해 평균 내는 &lt;code&gt;반복 홀드아웃 = Monte Carlo CV&lt;/code&gt;고요.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;③ 표준 — K-Fold 교차검증.&lt;/b&gt; &lt;code&gt;K-Fold Cross-Validation (K-겹 교차검증)&lt;/code&gt;: 데이터를 k조각으로 나눠, 번갈아 한 조각을 검증용으로 쓰고 나머지 k-1조각으로 학습하기를 k번 반복한 뒤 평균 내는 방법. 모든 데이터를 검증에 한 번씩 써서 홀드아웃의 &quot;운&quot; 문제를 눌러줍니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — 5-fold CV로 정확도의 평균과 표준편차를 함께 본다&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.model_selection &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; cross_val_score
scores = cross_val_score(clf, X, y, cv=&lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;)
&lt;span class=&quot;cm&quot;&gt;# scores → [0.96, 1. , 0.96, 0.96, 1. ]&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 0.98 accuracy with a standard deviation of 0.02&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;공식 문서가 콕 집어 말하는 포인트: &lt;em&gt;&quot;최종 평가용 테스트셋은 여전히 따로 떼어둬야 하고, CV를 하면 별도의 검증셋은 더 필요 없다&quot;&lt;/em&gt;. 즉 CV는 훈련셋 &lt;em&gt;안에서&lt;/em&gt; 검증을 돌리는 것이고, 테스트셋은 건드리지 않습니다. 그리고 강의자료가 강조했듯 &lt;b&gt;CV로 얻은 모델은 최종 모델이 아닙니다&lt;/b&gt; — CV는 &quot;이 설정이 좋다&quot;를 알아내는 절차일 뿐, 최종 모델은 전체 데이터로 다시 학습합니다.&lt;/p&gt;

&lt;div class=&quot;box warn&quot;&gt;⚠️ &lt;b&gt;Data Leakage (데이터 누수)&lt;/b&gt; — 테스트/검증 정보가 학습에 새어드는 것. 강의자료의 Kaggle 암 예측 사례처럼, &quot;전립선 수술 여부&quot; 필드가 사실상 &quot;이미 암 진단을 받았다&quot;를 의미하면 정답이 새어듭니다. &lt;em&gt;시계열 데이터를 무작위 분할&lt;/em&gt;하면 미래가 과거 학습에 섞이는 것도 같은 함정이고요. 스케일링을 전체 데이터에 &lt;code&gt;fit&lt;/code&gt;한 뒤 나누는 것도 흔한 누수입니다 — &lt;b&gt;분할을 먼저, 전처리는 훈련셋에만 fit&lt;/b&gt;이 원칙입니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 데이터가 넉넉하면 홀드아웃으로 빠르게, 데이터가 적거나 성능 추정을 안정적으로 보고해야 하면 (Stratified) K-Fold. 불균형 분류는 거의 항상 &lt;code&gt;StratifiedKFold&lt;/code&gt;. 시계열은 무작위 CV 대신 &lt;code&gt;TimeSeriesSplit&lt;/code&gt;으로 시간 순서를 지켜야 합니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 여기까지가 &quot;성능을 재는 법&quot;이었습니다. 이제 그 측정을 &lt;em&gt;반복해서 최적 설정을 고르는&lt;/em&gt; 튜닝 단계로 넘어가는데, 바로 여기에 가장 미묘한 함정이 숨어 있습니다.&lt;/p&gt;

&lt;h2 id=&quot;s5&quot;&gt;5. 튜닝과 그 함정 — Train/Val/Test, Grid vs Random, Selection Bias&lt;/h2&gt;

&lt;p&gt;하이퍼파라미터를 바꾸면 매번 다른 모델이 나옵니다. 그중 가장 좋은 걸 고르는 게 &lt;code&gt;Model Selection (모델 선택)&lt;/code&gt;인데, 이 &quot;고르는&quot; 행위 자체가 데이터에 과적합될 수 있다는 게 이 섹션의 핵심입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;① 역할을 셋으로 나눈다.&lt;/b&gt; 강의자료가 반복 강조한 3분할입니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;세트&lt;/th&gt;&lt;th&gt;역할&lt;/th&gt;&lt;th&gt;쓰는 곳&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Training&lt;/b&gt;&lt;/td&gt;&lt;td&gt;모델 파라미터 학습&lt;/td&gt;&lt;td&gt;&lt;code&gt;fit()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Validation&lt;/b&gt;&lt;/td&gt;&lt;td&gt;모델·하이퍼파라미터 선택&lt;/td&gt;&lt;td&gt;Grid / Random Search&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Test&lt;/b&gt;&lt;/td&gt;&lt;td&gt;최종 성능 평가 (딱 1회)&lt;/td&gt;&lt;td&gt;배포 직전 확인&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;가장 성능 좋은 모델만 고르면 되고 편향 없는 일반화 성능 추정엔 관심이 없다면 2분할(train/test)만으로도 됩니다. 하지만 튜닝까지 하려면 validation이 필요해요. 그리고 테스트셋으로 정확도만 높은 모델을 고르기보다, &lt;em&gt;목적에 맞는 지표&lt;/em&gt;(불균형이면 F1·Recall 등)로 골라야 한다는 점도 슬라이드가 짚었습니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;② 탐색 방법 — Grid vs Random.&lt;/b&gt; &lt;code&gt;Grid Search (격자 탐색)&lt;/code&gt;: 지정한 값들의 &lt;em&gt;모든 조합&lt;/em&gt;을 전수 평가. 공식 문서 표현으로는 fit할 때 가능한 조합을 다 평가하고 최고를 남깁니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — GridSearchCV용 파라미터 격자 (조합을 전수 탐색)&lt;/span&gt;
param_grid = [
  {&lt;span class=&quot;st&quot;&gt;'C'&lt;/span&gt;: [&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;100&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1000&lt;/span&gt;], &lt;span class=&quot;st&quot;&gt;'kernel'&lt;/span&gt;: [&lt;span class=&quot;st&quot;&gt;'linear'&lt;/span&gt;]},
  {&lt;span class=&quot;st&quot;&gt;'C'&lt;/span&gt;: [&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;100&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1000&lt;/span&gt;], &lt;span class=&quot;st&quot;&gt;'gamma'&lt;/span&gt;: [&lt;span class=&quot;nu&quot;&gt;0.001&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;0.0001&lt;/span&gt;],
   &lt;span class=&quot;st&quot;&gt;'kernel'&lt;/span&gt;: [&lt;span class=&quot;st&quot;&gt;'rbf'&lt;/span&gt;]},
]&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;Randomized Search (랜덤 탐색)&lt;/code&gt;: 분포에서 정해진 횟수(&lt;code&gt;n_iter&lt;/code&gt;)만큼 조합을 &lt;em&gt;샘플링&lt;/em&gt;. 공식 문서가 밝히는 두 가지 이점이 명확합니다 — &lt;em&gt;&quot;파라미터 개수·값 수와 무관하게 예산을 정할 수 있고&quot;&lt;/em&gt;, &lt;em&gt;&quot;성능에 영향 없는 파라미터를 추가해도 효율이 떨어지지 않는다&quot;&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — RandomizedSearchCV용 분포 (연속 구간에서 샘플링)&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.utils.fixes &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; loguniform
{&lt;span class=&quot;st&quot;&gt;'C'&lt;/span&gt;: loguniform(&lt;span class=&quot;nu&quot;&gt;1e0&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1e3&lt;/span&gt;),
 &lt;span class=&quot;st&quot;&gt;'gamma'&lt;/span&gt;: loguniform(&lt;span class=&quot;nu&quot;&gt;1e-4&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1e-3&lt;/span&gt;),
 &lt;span class=&quot;st&quot;&gt;'kernel'&lt;/span&gt;: [&lt;span class=&quot;st&quot;&gt;'rbf'&lt;/span&gt;],
 &lt;span class=&quot;st&quot;&gt;'class_weight'&lt;/span&gt;: [&lt;span class=&quot;st&quot;&gt;'balanced'&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;None&lt;/span&gt;]}&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;원논문은 이렇게 말합니다.&lt;/b&gt; Bergstra와 Bengio는 랜덤 탐색이 격자 탐색보다 경험적으로도 이론적으로도 더 효율적이며, 같은 도메인에서 &lt;em&gt;훨씬 적은 계산으로도 동등하거나 더 나은 모델&lt;/em&gt;을 찾는다고 보고했습니다. 강의자료의 그 유명한 그림 — 소수의 중요한 파라미터가 성능을 좌우할 때, 격자는 중요 축을 3개 값만 훑지만 랜덤은 같은 예산으로 9개 값을 훑는다 — 이 그게 핵심 직관입니다. &lt;span class=&quot;src&quot;&gt;(출처: Bergstra &amp; Bengio, &quot;Random Search for Hyper-Parameter Optimization&quot;, JMLR, 2012)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;③ 가장 미묘한 함정 — Selection Bias.&lt;/b&gt; &lt;code&gt;Selection Bias (선택 편향)&lt;/code&gt;: 검증 성능만 보고 하이퍼파라미터를 고르다 보면, 모델이 &lt;em&gt;검증셋에까지 과적합&lt;/em&gt;되어 최종 성능이 낙관적으로 부풀려지는 현상.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;원논문은 이렇게 말합니다.&lt;/b&gt; Cawley와 Talbot은 모델 선택 기준의 &lt;em&gt;불편성(unbiasedness)&lt;/em&gt;보다 &lt;em&gt;낮은 분산&lt;/em&gt;이 최소한 그만큼 중요하다고 강조합니다. 검증 성능 추정에 무시 못 할 분산이 있으면, 그 노이즈에 맞춰 하이퍼파라미터를 고르는 순간 &lt;em&gt;모델 선택 단계에서도 과적합&lt;/em&gt;이 일어나기 때문이죠. 처방은 &lt;code&gt;Nested Cross-Validation (중첩 교차검증)&lt;/code&gt; — 안쪽 루프는 하이퍼파라미터 튜닝, 바깥쪽 루프는 성능 평가로 &lt;em&gt;선택과 평가를 분리&lt;/em&gt;합니다. &lt;span class=&quot;src&quot;&gt;(출처: Cawley &amp; Talbot, &quot;On Over-fitting in Model Selection and Subsequent Selection Bias in Performance Evaluation&quot;, JMLR, 2010)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 개념 — Nested CV: 선택(inner)과 평가(outer)를 분리&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.model_selection &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; GridSearchCV, cross_val_score, KFold

inner = KFold(n_splits=&lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;, shuffle=&lt;span class=&quot;kw&quot;&gt;True&lt;/span&gt;, random_state=&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;)
outer = KFold(n_splits=&lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;, shuffle=&lt;span class=&quot;kw&quot;&gt;True&lt;/span&gt;, random_state=&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;)

clf = GridSearchCV(estimator, param_grid, cv=inner)      &lt;span class=&quot;cm&quot;&gt;# 안쪽: 하이퍼파라미터 선택&lt;/span&gt;
scores = cross_val_score(clf, X, y, cv=outer)            &lt;span class=&quot;cm&quot;&gt;# 바깥: 편향 없는 성능 추정&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# scores.mean() ← 튜닝까지 감안한 정직한 일반화 성능&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;핵심&lt;/b&gt; — 3번 섹션에서 &quot;낙관적 편향이 모든 모델에 똑같이 작용하면 비교엔 문제없다&quot;고 했었죠. Selection Bias는 바로 그 &quot;똑같이&quot;가 깨지는 지점입니다. 후보를 많이 시도할수록, 그중 검증셋에 &lt;em&gt;운 좋게&lt;/em&gt; 잘 맞은 하나가 뽑힐 확률이 커지거든요. 그래서 시도 횟수가 많을수록 Nested CV의 가치가 커집니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 논문·경진대회처럼 &lt;em&gt;정직한 최종 성능 숫자&lt;/em&gt;를 보고해야 할 때는 Nested CV. 반대로 데이터가 크고 시도가 적어 선택 편향이 작을 땐 단순 train/val/test 3분할로도 충분합니다(비용이 큰 만큼 상황을 보고 결정).&lt;/div&gt;

&lt;h2 id=&quot;s6&quot;&gt;6. 치트시트 &amp; 핵심 5가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;상황&lt;/th&gt;&lt;th&gt;패턴&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;기본 홀드아웃&lt;/td&gt;&lt;td&gt;&lt;code&gt;train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;안정적 성능 추정&lt;/td&gt;&lt;td&gt;&lt;code&gt;cross_val_score(clf, X, y, cv=5)&lt;/code&gt; → 평균 ± 표준편차&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;불균형 분류&lt;/td&gt;&lt;td&gt;&lt;code&gt;StratifiedKFold(n_splits=5)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;시계열&lt;/td&gt;&lt;td&gt;&lt;code&gt;TimeSeriesSplit&lt;/code&gt; (무작위 CV 금지 — 누수)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;파라미터 적음·전수&lt;/td&gt;&lt;td&gt;&lt;code&gt;GridSearchCV(estimator, param_grid, cv=5)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;파라미터 많음·예산 제한&lt;/td&gt;&lt;td&gt;&lt;code&gt;RandomizedSearchCV(estimator, dist, n_iter=50, cv=5)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;정직한 최종 성능&lt;/td&gt;&lt;td&gt;Nested CV: &lt;code&gt;cross_val_score(GridSearchCV(...), X, y, cv=outer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;최종 배포 모델&lt;/td&gt;&lt;td&gt;고른 설정으로 &lt;b&gt;전체 데이터&lt;/b&gt; 재학습&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 5가지만 기억하면 됩니다.&lt;br&gt;
1. &lt;b&gt;만능 모델은 없다&lt;/b&gt; — 문제마다 여러 후보를 같은 조건에서 비교(NFL).&lt;br&gt;
2. &lt;b&gt;오차 = 편향² + 분산 + 노이즈&lt;/b&gt; — 진단이 처방을 가른다.&lt;br&gt;
3. &lt;b&gt;같은 데이터로 학습·평가 금지&lt;/b&gt; — 재치환은 낙관적 거짓말, 테스트셋은 최종 1회.&lt;br&gt;
4. &lt;b&gt;데이터를 아껴 쓰려면 CV&lt;/b&gt; — 불균형은 Stratified, 시계열은 시간 순서 유지.&lt;br&gt;
5. &lt;b&gt;선택도 과적합된다&lt;/b&gt; — 튜닝까지 정직하게 재려면 Nested CV(Selection Bias).&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/cross_validation.html&quot;&gt;scikit-learn — Cross-validation: evaluating estimator performance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/grid_search.html&quot;&gt;scikit-learn — Tuning the hyper-parameters of an estimator&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/auto_examples/model_selection/plot_underfitting_overfitting.html&quot;&gt;scikit-learn — Underfitting vs. Overfitting&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ieeexplore.ieee.org/document/585893&quot;&gt;Wolpert &amp; Macready (1997) — No Free Lunch Theorems for Optimization, IEEE TEC&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://homes.cs.washington.edu/~pedrod/papers/cacm12.pdf&quot;&gt;Domingos (2012) — A Few Useful Things to Know about Machine Learning, CACM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jmlr.org/papers/v13/bergstra12a.html&quot;&gt;Bergstra &amp; Bengio (2012) — Random Search for Hyper-Parameter Optimization, JMLR&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jmlr.org/papers/v11/cawley10a.html&quot;&gt;Cawley &amp; Talbot (2010) — On Over-fitting in Model Selection and Subsequent Selection Bias, JMLR&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Kohavi (1995) — A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection, IJCAI&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/130</guid>
      <comments>https://hskywith.tistory.com/130#entry130comment</comments>
      <pubDate>Thu, 3 Sep 2026 14:15:29 +0900</pubDate>
    </item>
    <item>
      <title>LLM 에이전트의 Human-in-the-Loop &amp;mdash; LangGraph interrupt로 멈추고 재개하기</title>
      <link>https://hskywith.tistory.com/129</link>
      <description>&lt;!-- title: LLM 에이전트의 Human-in-the-Loop — LangGraph interrupt로 멈추고 재개하기 | date: 2026-09-02 | tags: LLM Agent, Human-in-the-Loop, LangGraph, interrupt, checkpointer, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 .rp 블록만 붙여넣는다. --&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;부트캠프에서 LLM 에이전트를 만들다 보면 슬슬 무서워지는 순간이 옵니다. 에이전트가 스스로 판단해서 &lt;em&gt;도구를 호출&lt;/em&gt;하는데, 그게 메일 발송이나 DB 삭제 같은 되돌릴 수 없는 행동이면요. &quot;얘가 혼자 다 처리하게 두는 게 맞나?&quot; 싶은 지점, 바로 여기서 &lt;strong&gt;Human-in-the-Loop&lt;/strong&gt;가 등장합니다.&lt;/p&gt;

&lt;p&gt;이 글은 HITL이 무엇이고 왜 필요한지, 그리고 실전에서 LangGraph의 &lt;code&gt;interrupt()&lt;/code&gt;로 이걸 어떻게 구현하는지를 공식 문서를 근거로 정리합니다. 앞서 정리한 프레임워크 시리즈가 &quot;모델을 학습시키는 법&quot;이었다면, 이건 &quot;학습된 모델(에이전트)을 &lt;em&gt;안전하게 굴리는&lt;/em&gt; 법&quot;에 가깝습니다.&lt;/p&gt;

&lt;h2&gt;1. 큰 그림 — 멈추고, 묻고, 재개한다&lt;/h2&gt;

&lt;p&gt;HITL의 뼈대는 단순합니다. 위험한 순간에 &lt;em&gt;멈추고&lt;/em&gt;, 사람에게 &lt;em&gt;묻고&lt;/em&gt;, 답을 받아 &lt;em&gt;재개&lt;/em&gt;하는 것. LangGraph는 이 세 동작에 각각 도구를 하나씩 붙여놨습니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;Human-in-the-Loop&lt;/b&gt; 를 네 조각으로&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;왜 멈추나?&lt;/span&gt;위험·불확실 행동 전&lt;span class=&quot;s&quot;&gt;발송·삭제·결제&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 멈추나?&lt;/span&gt;노드 안에서 정지&lt;span class=&quot;s&quot;&gt;interrupt()&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 재개?&lt;/span&gt;사람 답으로 이어감&lt;span class=&quot;s&quot;&gt;Command(resume=)&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;무엇을 저장?&lt;/span&gt;멈춘 상태 보존&lt;span class=&quot;s&quot;&gt;checkpointer&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;2장 개념·이유, 3장 패턴, 4장 LangGraph 구현 순으로 채웁니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;2. HITL이 뭐고, 왜 필요한가&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Human-in-the-Loop&lt;/code&gt; (HITL, 휴먼 인 더 루프): 자동화 과정 중간에 사람의 승인·수정·검토를 끼워 넣는 설계 / AI가 제안하면 사람이 확인한 뒤 이어감.&lt;/p&gt;

&lt;p&gt;비유하자면 &lt;em&gt;수술 동의서&lt;/em&gt; 같은 겁니다. 의사(에이전트)가 판단은 내리되, 몸에 칼을 대기(되돌릴 수 없는 행동) 직전엔 환자(사람)의 서명을 받는 거죠. 판단은 자동으로, 결단은 사람이.&lt;/p&gt;

&lt;p&gt;왜 필요할까요. LLM 에이전트의 핵심은 &lt;em&gt;도구 호출&lt;/em&gt;(tool call: 에이전트가 외부 함수·API를 실행하는 것)인데, 이 도구가 실제 세계를 바꿉니다. 검색이야 몇 번 틀려도 되지만, &quot;환불 메일 일괄 발송&quot;이나 &quot;테이블 DROP&quot;은 한 번 나가면 끝입니다. 에이전트가 프롬프트 한 줄 오해로 이런 행동을 하면 사고가 되죠. 그래서 &lt;strong&gt;영향이 크고 되돌리기 어려운 지점&lt;/strong&gt; 앞에 사람을 세웁니다.&lt;/p&gt;

&lt;div class=&quot;box note&quot;&gt;  사실 지금 여러분이 쓰는 AI 도구 상당수가 이미 HITL입니다. 코딩 에이전트가 파일을 지우기 전 확인을 받거나, 어시스턴트가 메일을 보내기 전 &quot;이대로 보낼까요?&quot;라고 묻는 것 전부 같은 패턴이에요.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 고객 응대 자동화(환불·계정 변경 전 상담사 승인), 코드 에이전트(배포·마이그레이션 전 개발자 확인), 데이터 파이프라인(삭제성 쿼리 전 검토). &quot;자동화하되 사고는 막고 싶은&quot; 모든 곳.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 그럼 사람을 어떤 방식으로 끼워 넣을까요? 대표 패턴 넷을 봅니다.&lt;/p&gt;

&lt;h2&gt;3. 대표 패턴 4가지&lt;/h2&gt;

&lt;p&gt;HITL이라고 다 &quot;예/아니오&quot;만 있는 게 아닙니다. LangGraph 공식 문서는 개입 방식을 크게 네 가지로 정리합니다. &lt;span class=&quot;src&quot;&gt;(참고: LangGraph — Human-in-the-loop concepts)&lt;/span&gt;&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;패턴&lt;/th&gt;&lt;th&gt;사람이 하는 일&lt;/th&gt;&lt;th&gt;예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Approve / Reject&lt;/b&gt;&lt;br&gt;(승인/거부)&lt;/td&gt;&lt;td&gt;위험 행동을 통과시키거나 막기&lt;/td&gt;&lt;td&gt;결제·발송·배포 직전 게이트&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Edit state&lt;/b&gt;&lt;br&gt;(상태 수정)&lt;/td&gt;&lt;td&gt;에이전트가 만든 결과를 고쳐서 진행&lt;/td&gt;&lt;td&gt;초안 메일 문구 다듬기&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Review tool call&lt;/b&gt;&lt;br&gt;(도구 호출 검토)&lt;/td&gt;&lt;td&gt;실행 전 인자·쿼리를 확인·교정&lt;/td&gt;&lt;td&gt;생성된 SQL 검토 후 실행&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Validate input&lt;/b&gt;&lt;br&gt;(입력 검증)&lt;/td&gt;&lt;td&gt;사람에게 필요한 정보를 되물어 받기&lt;/td&gt;&lt;td&gt;모자란 값 재확인&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;넷 다 근본은 같습니다 — &quot;실행 흐름을 잠깐 멈추고 사람에게 제어권을 넘긴다.&quot; 다른 건 &lt;em&gt;사람이 돌려주는 값&lt;/em&gt;이 승인 여부냐, 수정된 텍스트냐, 교정된 인자냐일 뿐이에요.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 위험도에 따라 패턴을 섞습니다. 저위험은 Review로 슬쩍 보여만 주고, 고위험(자금 이체 등)은 Approve/Reject로 명시적 승인을 강제하는 식으로요.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 이제 이걸 실제 코드로. LangGraph의 interrupt입니다.&lt;/p&gt;

&lt;h2&gt;4. LangGraph interrupt — 멈추고 재개하기&lt;/h2&gt;

&lt;p&gt;패턴이 다 &quot;멈추고 → 사람 값 받고 → 재개&quot;로 수렴한다고 했는데, LangGraph는 이걸 함수 하나로 제공합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;interrupt()&lt;/code&gt; (인터럽트): 노드 안에서 그래프 실행을 멈추고 사람에게 값을 넘긴 뒤, 답이 오면 그 자리부터 이어가는 함수.&lt;br&gt;
&lt;code&gt;Command(resume=값)&lt;/code&gt;: 멈춘 그래프를 다시 굴리는 명령. 여기 넣은 값이 &lt;code&gt;interrupt()&lt;/code&gt;의 반환값이 됨.&lt;br&gt;
&lt;code&gt;checkpointer&lt;/code&gt; (체크포인터): 멈춘 시점의 그래프 상태를 저장해 두는 저장소.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; &lt;code&gt;interrupt()&lt;/code&gt;는 노드 안에서 &lt;em&gt;&quot;a resumable exception&quot;&lt;/em&gt;, 즉 재개 가능한 예외를 던져 그래프를 멈추고, 넘긴 값을 클라이언트로 보냅니다. 그리고 사람이 답하면 &lt;code&gt;Command(resume=...)&lt;/code&gt;으로 재개하죠. &lt;span class=&quot;src&quot;&gt;(출처: LangChain Reference — interrupt)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; langgraph.types &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; interrupt, Command

&lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;node&lt;/span&gt;(state):
    answer = interrupt(&lt;span class=&quot;st&quot;&gt;&quot;what is your age?&quot;&lt;/span&gt;)   &lt;span class=&quot;cm&quot;&gt;# 여기서 멈춤 → 사람에게 질문 전달&lt;/span&gt;
    &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; {&lt;span class=&quot;st&quot;&gt;&quot;human_value&quot;&lt;/span&gt;: answer}         &lt;span class=&quot;cm&quot;&gt;# answer = 사람이 준 값&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# 1차 실행 — interrupt에서 정지&lt;/span&gt;
graph.stream({&lt;span class=&quot;st&quot;&gt;&quot;foo&quot;&lt;/span&gt;: &lt;span class=&quot;st&quot;&gt;&quot;abc&quot;&lt;/span&gt;}, config)

&lt;span class=&quot;cm&quot;&gt;# 사람 입력으로 재개&lt;/span&gt;
graph.stream(Command(resume=&lt;span class=&quot;st&quot;&gt;&quot;some input from a human!!!&quot;&lt;/span&gt;), config)
&lt;span class=&quot;cm&quot;&gt;# &gt; human_value = &quot;some input from a human!!!&quot;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;승인/거부 패턴이라면 이런 모습이 됩니다. 넘긴 값(초안)을 사람이 보고, 돌아온 값(&lt;code&gt;decision&lt;/code&gt;)으로 분기하는 거죠.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;approval_node&lt;/span&gt;(state):
    decision = interrupt({&lt;span class=&quot;st&quot;&gt;&quot;action&quot;&lt;/span&gt;: &lt;span class=&quot;st&quot;&gt;&quot;send_email&quot;&lt;/span&gt;,
                          &lt;span class=&quot;st&quot;&gt;&quot;draft&quot;&lt;/span&gt;: state[&lt;span class=&quot;st&quot;&gt;&quot;draft&quot;&lt;/span&gt;]})   &lt;span class=&quot;cm&quot;&gt;# 사람에게 초안 보여주고 대기&lt;/span&gt;
    &lt;span class=&quot;kw&quot;&gt;if&lt;/span&gt; decision == &lt;span class=&quot;st&quot;&gt;&quot;approve&quot;&lt;/span&gt;:
        &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; {&lt;span class=&quot;st&quot;&gt;&quot;status&quot;&lt;/span&gt;: &lt;span class=&quot;st&quot;&gt;&quot;sending&quot;&lt;/span&gt;}
    &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; {&lt;span class=&quot;st&quot;&gt;&quot;status&quot;&lt;/span&gt;: &lt;span class=&quot;st&quot;&gt;&quot;cancelled&quot;&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;그런데 이게 동작하려면 전제가 하나 있습니다. 멈춘 상태를 어딘가 저장해 둬야 나중에 이어갈 수 있겠죠. 공식 문서도 &lt;em&gt;&quot;you must enable a checkpointer&quot;&lt;/em&gt; — 체크포인터를 반드시 켜라고 못박습니다. &lt;span class=&quot;src&quot;&gt;(출처: LangChain Reference — interrupt)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; langgraph.checkpoint.memory &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; InMemorySaver

graph = builder.compile(checkpointer=InMemorySaver())   &lt;span class=&quot;cm&quot;&gt;# 상태 저장소 필수&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 재개 때 어느 대화인지 찾도록 thread_id를 config로 넘긴다&lt;/span&gt;
config = {&lt;span class=&quot;st&quot;&gt;&quot;configurable&quot;&lt;/span&gt;: {&lt;span class=&quot;st&quot;&gt;&quot;thread_id&quot;&lt;/span&gt;: &lt;span class=&quot;st&quot;&gt;&quot;user-42&quot;&lt;/span&gt;}}&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ ★ 최대 함정 · 노드가 통째로 재실행된다&lt;/b&gt; — 공식 문서는 &lt;em&gt;&quot;The graph resumes from the start of the node, re-executing all logic.&quot;&lt;/em&gt;라고 합니다. 재개하면 그 노드가 &lt;code&gt;interrupt()&lt;/code&gt; 지점부터가 아니라 &lt;b&gt;맨 처음부터&lt;/b&gt; 다시 돕니다. 그래서 &lt;code&gt;interrupt()&lt;/code&gt; &lt;em&gt;앞&lt;/em&gt;에 부작용 있는 코드(메일 발송, 카운터 증가, 랜덤 생성)를 두면 &lt;b&gt;두 번 실행&lt;/b&gt;됩니다. 부작용은 반드시 interrupt &lt;em&gt;뒤&lt;/em&gt;로 빼거나 별도 노드로 분리하세요.&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  &lt;code&gt;InMemorySaver&lt;/code&gt;는 프로세스가 죽으면 상태도 사라집니다. 실서비스에서 사람이 며칠 뒤 승인할 수도 있는 흐름이라면 DB 기반 체크포인터(예: Postgres saver)로 영속화해야 재개가 보장됩니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 챗봇이 &quot;이 주문을 취소할까요?&quot;라 묻고 사용자 클릭을 기다리는 흐름, 승인 대기 중 며칠이 지나도 되는 결재 워크플로. 멈춤과 재개 사이에 시간이 흐를 수 있는 모든 에이전트에 필수입니다.&lt;/div&gt;

&lt;h2&gt;5. 언제 걸어야 하나 — 과하지 않게&lt;/h2&gt;

&lt;p&gt;마지막으로 실무 감각 하나. 모든 스텝에 사람을 넣으면 자동화한 의미가 없습니다. 개입 지점은 두 축으로 고릅니다 — &lt;strong&gt;영향의 크기&lt;/strong&gt;(되돌릴 수 있나?)와 &lt;strong&gt;불확실성&lt;/strong&gt;(에이전트가 확신하나?).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;영향 크고 · 불확실&lt;/b&gt; → 무조건 승인 게이트 (자금 이체, 대량 발송)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;영향 크고 · 확실&lt;/b&gt; → 요약만 보여주고 통과 (되돌릴 순 있게 로그 남김)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;영향 작음&lt;/b&gt; → 사람 개입 없이 자동 (검색, 조회)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;신뢰도 임계값을 두어 &quot;에이전트가 확신 못 하는 경우에만&quot; 사람에게 넘기는 것도 흔한 설계입니다. 자동화율과 안전성 사이의 &lt;em&gt;트레이드오프&lt;/em&gt;를 조절하는 손잡이인 셈이죠.&lt;/p&gt;

&lt;h2&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;하고 싶은 것&lt;/th&gt;&lt;th&gt;패턴&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;노드에서 멈추고 묻기&lt;/td&gt;&lt;td&gt;&lt;code&gt;answer = interrupt(payload)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;사람 답으로 재개&lt;/td&gt;&lt;td&gt;&lt;code&gt;graph.stream(Command(resume=값), config)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;상태 저장(필수)&lt;/td&gt;&lt;td&gt;&lt;code&gt;compile(checkpointer=InMemorySaver())&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;대화 식별&lt;/td&gt;&lt;td&gt;&lt;code&gt;config={&quot;configurable&quot;:{&quot;thread_id&quot;:...}}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;부작용 코드 위치&lt;/td&gt;&lt;td&gt;반드시 &lt;code&gt;interrupt()&lt;/code&gt; &lt;b&gt;뒤&lt;/b&gt;에&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 4가지만 기억하면 됩니다.&lt;br&gt;
1. HITL = &lt;b&gt;되돌릴 수 없는 행동 앞에 사람&lt;/b&gt;을 세우는 것.&lt;br&gt;
2. 패턴은 &lt;b&gt;승인/거부 · 수정 · 도구검토 · 입력검증&lt;/b&gt; 넷, 뿌리는 &quot;멈추고 제어권 넘기기&quot;.&lt;br&gt;
3. LangGraph는 &lt;code&gt;interrupt()&lt;/code&gt;로 멈추고 &lt;code&gt;Command(resume=)&lt;/code&gt;로 재개, &lt;b&gt;checkpointer 필수&lt;/b&gt;.&lt;br&gt;
4. 재개 시 &lt;b&gt;노드가 통째로 재실행&lt;/b&gt;된다 — 부작용은 interrupt 뒤로!&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://reference.langchain.com/python/langgraph/types/interrupt&quot;&gt;LangChain Reference — interrupt (공식 문서)&lt;/a&gt; : &quot;resumable exception&quot;, &quot;resumes from the start of the node&quot;, &quot;must enable a checkpointer&quot;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/&quot;&gt;LangGraph — Human-in-the-loop (공식 개념 문서)&lt;/a&gt; : approve/reject·edit·review·validate 패턴&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://langchain-ai.github.io/langgraph/concepts/persistence/&quot;&gt;LangGraph — Persistence &amp; Checkpointers (공식 문서)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/129</guid>
      <comments>https://hskywith.tistory.com/129#entry129comment</comments>
      <pubDate>Wed, 2 Sep 2026 13:56:52 +0900</pubDate>
    </item>
    <item>
      <title>TensorFlow 전문가 학습 루프 &amp;mdash; 서브클래싱&amp;middot;GradientTape&amp;middot;@tf.function</title>
      <link>https://hskywith.tistory.com/128</link>
      <description>&lt;!-- title: TensorFlow 전문가 학습 루프 — 서브클래싱·GradientTape·@tf.function | date: 2026-09-01 | tags: TensorFlow, GradientTape, tf.function, custom training loop, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 .rp 블록만 붙여넣는다. --&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;공식 TensorFlow 튜토리얼에는 &quot;초보자용&quot;과 &quot;전문가용&quot; 두 개의 quickstart가 나란히 있습니다. 초보자용은 &lt;code&gt;model.fit()&lt;/code&gt; 한 줄로 학습이 끝나는데, 전문가용은 학습 루프를 손으로 다 씁니다. 처음엔 &quot;왜 굳이 번거롭게?&quot; 싶었는데, 뜯어보니 &lt;code&gt;fit()&lt;/code&gt;이 안에서 대신 해주던 일이 전부 코드로 드러나더라고요.&lt;/p&gt;

&lt;p&gt;이 글은 전문가용 quickstart(MNIST 분류기)를 근거로, 커스텀 학습 루프의 세 축 — &lt;strong&gt;모델 서브클래싱&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;GradientTape&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;@tf.function&lt;/code&gt;&lt;/strong&gt; — 을 공식 문서와 함께 정리합니다. GAN이나 강화학습처럼 &lt;code&gt;fit()&lt;/code&gt;으로 안 되는 걸 만들려면 반드시 넘어야 하는 문턱이에요.&lt;/p&gt;

&lt;h2&gt;1. 큰 그림 — 학습 루프의 톱니바퀴&lt;/h2&gt;

&lt;p&gt;모델을 학습시킨다는 건 결국 &quot;예측하고 → 틀린 만큼 재고 → 그만큼 고치기&quot;의 반복입니다. 전문가판은 이 톱니바퀴를 직접 조립하는데, 조각은 이렇게 나뉩니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;커스텀 학습 루프&lt;/b&gt; 를 네 조각으로&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 계산?&lt;/span&gt;모델 서브클래싱&lt;span class=&quot;s&quot;&gt;call()&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 미분?&lt;/span&gt;기울기 기록&lt;span class=&quot;s&quot;&gt;GradientTape&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 배우나?&lt;/span&gt;가중치 갱신&lt;span class=&quot;s&quot;&gt;apply_gradients&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;왜 빠른가?&lt;/span&gt;그래프 컴파일&lt;span class=&quot;s&quot;&gt;@tf.function&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;2장 모델, 3장 기울기(GradientTape), 4장 속도(@tf.function) 순으로 채웁니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;2. 모델 서브클래싱 — call()에 순전파를 쓴다&lt;/h2&gt;

&lt;p&gt;먼저 &quot;어떻게 계산하는가&quot;, 즉 모델부터. 전문가판은 &lt;code&gt;Sequential&lt;/code&gt;로 쌓는 대신 &lt;code&gt;Model&lt;/code&gt;을 상속받아 만듭니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;subclassing&lt;/code&gt; (서브클래싱): 상위 클래스를 상속해 원하는 메서드를 직접 구현하는 것 / 여기선 &lt;code&gt;Model&lt;/code&gt;을 상속해 &lt;code&gt;call()&lt;/code&gt;을 채움.&lt;/p&gt;

&lt;p&gt;레이어는 &lt;code&gt;__init__&lt;/code&gt;에서 미리 만들어 두고, 데이터가 흐르는 순서(순전파)는 &lt;code&gt;call()&lt;/code&gt;에 파이썬 코드로 씁니다. &quot;부품을 사물함에 넣어두고(&lt;code&gt;__init__&lt;/code&gt;), 조립 순서를 설명서에 적는(&lt;code&gt;call&lt;/code&gt;)&quot; 셈이죠. &lt;span class=&quot;src&quot;&gt;(참고: TensorFlow — Quickstart for experts)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;MyModel&lt;/span&gt;(Model):
  &lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;__init__&lt;/span&gt;(self):
    &lt;span class=&quot;fn&quot;&gt;super&lt;/span&gt;().__init__()
    self.conv1 = Conv2D(&lt;span class=&quot;nu&quot;&gt;32&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, activation=&lt;span class=&quot;st&quot;&gt;'relu'&lt;/span&gt;)
    self.flatten = Flatten()
    self.d1 = Dense(&lt;span class=&quot;nu&quot;&gt;128&lt;/span&gt;, activation=&lt;span class=&quot;st&quot;&gt;'relu'&lt;/span&gt;)
    self.d2 = Dense(&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;)              &lt;span class=&quot;cm&quot;&gt;# 출력 10클래스, softmax 없음(logits)&lt;/span&gt;

  &lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;call&lt;/span&gt;(self, x):                  &lt;span class=&quot;cm&quot;&gt;# 순전파 경로를 직접 기술&lt;/span&gt;
    x = self.conv1(x)
    x = self.flatten(x)
    x = self.d1(x)
    &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; self.d2(x)&lt;/pre&gt;

&lt;p&gt;출력층에 &lt;code&gt;softmax&lt;/code&gt;가 없는 게 눈에 띕니다. 확률 대신 &lt;em&gt;logits&lt;/em&gt;(로짓: 정규화 전 원점수)를 그대로 내보내고, 손실 함수에서 &lt;code&gt;SparseCategoricalCrossentropy(from_logits=True)&lt;/code&gt;로 받습니다. 이렇게 하면 softmax와 로그를 한데 묶어 계산해 수치적으로 더 안정적입니다.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 레이어 사이에 조건 분기가 필요하거나(입력에 따라 경로가 갈리는 모델), 하나의 &lt;code&gt;call&lt;/code&gt;에서 여러 출력을 내야 할 때. 연구용 커스텀 아키텍처는 대부분 이 서브클래싱 방식으로 짭니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 다음은 이 모델의 오차를 어떻게 미분하는가, GradientTape입니다.&lt;/p&gt;

&lt;h2&gt;3. GradientTape — 자동미분의 녹음기&lt;/h2&gt;

&lt;p&gt;모델이 예측을 내면 손실을 구하고, 그 손실을 가중치로 미분해야 &quot;어느 방향으로 고칠지&quot;를 압니다. 이 미분을 자동으로 해주는 게 &lt;code&gt;GradientTape&lt;/code&gt;입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tf.GradientTape&lt;/code&gt; (그래디언트 테이프): 연산 과정을 기록해 두었다가 자동으로 미분해주는 도구 / &lt;code&gt;with&lt;/code&gt; 블록 안의 계산을 추적함.&lt;/p&gt;

&lt;p&gt;이름 그대로 &lt;em&gt;테이프(녹음기)&lt;/em&gt;가 직관입니다. &lt;code&gt;with&lt;/code&gt; 블록 안에서 벌어지는 계산을 쭉 녹음해 두었다가, 나중에 거꾸로 재생하며 기울기를 뽑아냅니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; 텐서플로가 테이프 안에서 실행된 연산을 &lt;em&gt;&quot;'records' relevant operations ... onto a 'tape'&quot;&lt;/em&gt;, 즉 테이프에 기록해 두고, 그 테이프로 &lt;em&gt;역방향 미분(reverse mode)&lt;/em&gt;을 수행해 기울기를 계산한다고 합니다. &lt;span class=&quot;src&quot;&gt;(출처: TensorFlow — Introduction to gradients and automatic differentiation)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;dec cm&quot;&gt;# 배치 하나에 대한 학습 스텝&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;with&lt;/span&gt; tf.GradientTape() &lt;span class=&quot;kw&quot;&gt;as&lt;/span&gt; tape:     &lt;span class=&quot;cm&quot;&gt;# ① 연산 녹음 시작&lt;/span&gt;
    predictions = model(images, training=&lt;span class=&quot;kw&quot;&gt;True&lt;/span&gt;)  &lt;span class=&quot;cm&quot;&gt;# 순전파&lt;/span&gt;
    loss = loss_object(labels, predictions)      &lt;span class=&quot;cm&quot;&gt;# 손실&lt;/span&gt;
gradients = tape.gradient(loss, model.trainable_variables)   &lt;span class=&quot;cm&quot;&gt;# ② 역재생 → 기울기&lt;/span&gt;
optimizer.apply_gradients(&lt;span class=&quot;fn&quot;&gt;zip&lt;/span&gt;(gradients, model.trainable_variables))  &lt;span class=&quot;cm&quot;&gt;# ③ 갱신&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여기서 &lt;code&gt;training=True&lt;/code&gt;는 Dropout·BatchNorm처럼 학습/추론 때 동작이 다른 레이어를 위한 스위치입니다. 평가할 땐 &lt;code&gt;training=False&lt;/code&gt;로 두고, 그 스텝에는 &lt;code&gt;GradientTape&lt;/code&gt;도 &lt;code&gt;apply_gradients&lt;/code&gt;도 두지 않습니다(가중치를 안 바꾸니까요).&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 기본 분류·회귀는 물론, 손실을 커스텀하는 모든 경우. GAN의 생성자/판별자 각각의 손실, 강화학습의 정책 경사, 적대적 예제 생성처럼 &quot;표준 손실 하나로 안 끝나는&quot; 학습이 전부 GradientTape 위에서 굴러갑니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 마지막은 이 루프를 수십 배 빠르게 만드는 @tf.function입니다.&lt;/p&gt;

&lt;h2&gt;4. @tf.function — 파이썬을 그래프로 컴파일&lt;/h2&gt;

&lt;p&gt;학습 스텝을 파이썬으로 한 줄씩 실행하면 느립니다. 여기에 데코레이터 하나를 붙이면 텐서플로가 그 함수를 정적 &lt;em&gt;계산 그래프&lt;/em&gt;로 컴파일해 크게 가속합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@tf.function&lt;/code&gt; (그래프 컴파일 데코레이터): 파이썬 함수를 텐서플로 그래프로 변환해 빠르게 실행하게 만드는 것.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;fn&quot;&gt;@tf.function&lt;/span&gt;                       &lt;span class=&quot;cm&quot;&gt;# 이 한 줄이 함수를 그래프로&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;train_step&lt;/span&gt;(images, labels):
    &lt;span class=&quot;kw&quot;&gt;with&lt;/span&gt; tf.GradientTape() &lt;span class=&quot;kw&quot;&gt;as&lt;/span&gt; tape:
        predictions = model(images, training=&lt;span class=&quot;kw&quot;&gt;True&lt;/span&gt;)
        loss = loss_object(labels, predictions)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(&lt;span class=&quot;fn&quot;&gt;zip&lt;/span&gt;(gradients, model.trainable_variables))&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 함정 · 그래프 모드의 부작용&lt;/b&gt; — &lt;code&gt;@tf.function&lt;/code&gt; 안에서는 파이썬 &lt;code&gt;print&lt;/code&gt;나 리스트 append 같은 부작용이 기대와 다르게(추적 시점에 한 번만) 동작할 수 있습니다. 디버깅할 땐 데코레이터를 잠깐 떼고 &lt;em&gt;eager 모드&lt;/em&gt;(즉시 실행)로 값을 확인한 뒤 다시 붙이는 게 안전합니다. &lt;span class=&quot;src&quot;&gt;(참고: TensorFlow — Better performance with tf.function)&lt;/span&gt;&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 매 배치 수천 번 호출되는 &lt;code&gt;train_step&lt;/code&gt;/&lt;code&gt;test_step&lt;/code&gt;에 붙이면 체감 속도가 확 올라갑니다. 반대로 한 번만 실행하는 코드나 디버깅 중인 코드엔 굳이 붙이지 않습니다.&lt;/div&gt;

&lt;h2&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;단계&lt;/th&gt;&lt;th&gt;코드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;모델 정의&lt;/td&gt;&lt;td&gt;&lt;code&gt;class M(Model): def call(self, x): ...&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;손실(로짓)&lt;/td&gt;&lt;td&gt;&lt;code&gt;SparseCategoricalCrossentropy(from_logits=True)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;기울기 기록&lt;/td&gt;&lt;td&gt;&lt;code&gt;with tf.GradientTape() as tape: ...&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;기울기 계산&lt;/td&gt;&lt;td&gt;&lt;code&gt;tape.gradient(loss, model.trainable_variables)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;가중치 갱신&lt;/td&gt;&lt;td&gt;&lt;code&gt;optimizer.apply_gradients(zip(grads, vars))&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;가속&lt;/td&gt;&lt;td&gt;&lt;code&gt;@tf.function&lt;/code&gt; (train_step 위에)&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 4가지만 기억하면 됩니다.&lt;br&gt;
1. 커스텀 모델은 &lt;b&gt;Model 서브클래싱 + call()&lt;/b&gt;. 출력은 logits, 손실은 &lt;code&gt;from_logits=True&lt;/code&gt;.&lt;br&gt;
2. &lt;b&gt;GradientTape&lt;/b&gt;는 &lt;code&gt;with&lt;/code&gt; 블록을 녹음했다가 &lt;code&gt;tape.gradient&lt;/code&gt;로 역재생해 미분한다.&lt;br&gt;
3. 갱신은 &lt;code&gt;apply_gradients&lt;/code&gt;, 평가 스텝엔 테이프·갱신을 두지 않는다.&lt;br&gt;
4. &lt;code&gt;@tf.function&lt;/code&gt;으로 스텝을 그래프 컴파일 → 빠름. 단 디버깅 땐 떼기.&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.tensorflow.org/tutorials/quickstart/advanced&quot;&gt;TensorFlow — Quickstart for experts (공식 문서)&lt;/a&gt; : 서브클래싱·GradientTape 학습 루프 전체&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.tensorflow.org/guide/autodiff&quot;&gt;TensorFlow — Introduction to gradients and automatic differentiation (공식 문서)&lt;/a&gt; : &quot;records ... onto a tape&quot;, 역방향 미분&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.tensorflow.org/guide/function&quot;&gt;TensorFlow — Better performance with tf.function (공식 문서)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#&quot;&gt;「PyTorch 학습 루프」&lt;/a&gt; (다음 글) : 같은 루프를 &lt;code&gt;loss.backward()&lt;/code&gt;로 — TF↔PyTorch 대응&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/128</guid>
      <comments>https://hskywith.tistory.com/128#entry128comment</comments>
      <pubDate>Tue, 1 Sep 2026 14:59:07 +0900</pubDate>
    </item>
    <item>
      <title>PyTorch 학습 루프 &amp;mdash; nn.Module&amp;middot;loss.backward()와 zero_grad 함정 (TF 대응)</title>
      <link>https://hskywith.tistory.com/127</link>
      <description>&lt;!-- title: PyTorch 학습 루프 — nn.Module·loss.backward()와 zero_grad 함정 (TF 대응) | date: 2026-09-01 | tags: PyTorch, autograd, backward, zero_grad, nn.Module, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 .rp 블록만 붙여넣는다. --&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;바로 앞 글에서 TensorFlow 전문가 학습 루프(&lt;a href=&quot;#&quot;&gt;「TensorFlow 전문가 학습 루프」&lt;/a&gt;)를 정리했는데요. PyTorch 공식 quickstart를 열어보니 구조가 거의 판박이였습니다. 모델을 클래스로 정의하고, 배치마다 예측→손실→미분→갱신을 도는 흐름이요. 다만 문법이 다르고, PyTorch에만 있는 &lt;em&gt;함정&lt;/em&gt;이 하나 있더라고요 — 바로 &lt;code&gt;zero_grad()&lt;/code&gt;입니다.&lt;/p&gt;

&lt;p&gt;이 글은 PyTorch quickstart(FashionMNIST 분류기)를 근거로 &lt;strong&gt;&lt;code&gt;nn.Module&lt;/code&gt; 서브클래싱&lt;/strong&gt;, &lt;strong&gt;autograd와 &lt;code&gt;loss.backward()&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;train()&lt;/code&gt;/&lt;code&gt;eval()&lt;/code&gt;&lt;/strong&gt;를 정리하되, TF를 아는 사람이 바로 매핑할 수 있게 대응 관계를 먼저 깔고 갑니다.&lt;/p&gt;

&lt;h2&gt;1. 큰 그림 — TF와 같은 루프, 다른 문법&lt;/h2&gt;

&lt;p&gt;학습 루프의 네 조각(계산·미분·갱신·초기화)은 TF와 동일합니다. PyTorch식 답을 지도로 먼저 보겠습니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;PyTorch 학습 루프&lt;/b&gt; 를 네 조각으로&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 계산?&lt;/span&gt;모듈 서브클래싱&lt;span class=&quot;s&quot;&gt;forward()&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 미분?&lt;/span&gt;역전파&lt;span class=&quot;s&quot;&gt;loss.backward()&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 배우나?&lt;/span&gt;가중치 갱신&lt;span class=&quot;s&quot;&gt;optimizer.step()&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;★ 초기화?&lt;/span&gt;기울기 리셋&lt;span class=&quot;s&quot;&gt;zero_grad()&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;TF엔 없던 네 번째 칸(zero_grad)이 PyTorch 초심자 1순위 함정입니다.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;TF 전문가판을 이미 봤다면, 아래 대응표 하나로 문법이 거의 다 매핑됩니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;하는 일&lt;/th&gt;&lt;th&gt;TensorFlow&lt;/th&gt;&lt;th&gt;PyTorch&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;모델 정의&lt;/td&gt;&lt;td&gt;&lt;code&gt;Model&lt;/code&gt; → &lt;code&gt;call()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; → &lt;code&gt;forward()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;기울기 계산&lt;/td&gt;&lt;td&gt;&lt;code&gt;tape.gradient(loss, vars)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;loss.backward()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;가중치 갱신&lt;/td&gt;&lt;td&gt;&lt;code&gt;optimizer.apply_gradients(...)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;optimizer.step()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;기울기 초기화&lt;/td&gt;&lt;td&gt;테이프가 매번 새로 시작&lt;/td&gt;&lt;td&gt;&lt;code&gt;optimizer.zero_grad()&lt;/code&gt; 필수&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;추론 모드&lt;/td&gt;&lt;td&gt;&lt;code&gt;training=False&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;model.eval()&lt;/code&gt; + &lt;code&gt;torch.no_grad()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;2. nn.Module 서브클래싱 — forward()에 순전파&lt;/h2&gt;

&lt;p&gt;계산 방식, 즉 모델부터. TF의 &lt;code&gt;call()&lt;/code&gt;에 해당하는 게 PyTorch의 &lt;code&gt;forward()&lt;/code&gt;입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nn.Module&lt;/code&gt; (신경망 모듈): PyTorch 모든 모델·레이어의 기반 클래스 / 상속받아 &lt;code&gt;forward()&lt;/code&gt;를 구현하면 모델이 됨.&lt;/p&gt;

&lt;p&gt;구성은 TF와 판박이입니다. 레이어는 &lt;code&gt;__init__&lt;/code&gt;에서 만들고, 순전파는 &lt;code&gt;forward()&lt;/code&gt;에 씁니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;NeuralNetwork&lt;/span&gt;(nn.Module):
    &lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;__init__&lt;/span&gt;(self):
        &lt;span class=&quot;fn&quot;&gt;super&lt;/span&gt;().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(&lt;span class=&quot;nu&quot;&gt;28&lt;/span&gt;*&lt;span class=&quot;nu&quot;&gt;28&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;512&lt;/span&gt;), nn.ReLU(),
            nn.Linear(&lt;span class=&quot;nu&quot;&gt;512&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;512&lt;/span&gt;), nn.ReLU(),
            nn.Linear(&lt;span class=&quot;nu&quot;&gt;512&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;),                &lt;span class=&quot;cm&quot;&gt;# logits (softmax 없음)&lt;/span&gt;
        )
    &lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;forward&lt;/span&gt;(self, x):
        x = self.flatten(x)
        &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; self.linear_relu_stack(x)

model = &lt;span class=&quot;fn&quot;&gt;NeuralNetwork&lt;/span&gt;().to(device)      &lt;span class=&quot;cm&quot;&gt;# 모델을 GPU 등으로 명시적 이동&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여기도 출력층에 softmax가 없습니다. PyTorch의 &lt;code&gt;nn.CrossEntropyLoss&lt;/code&gt;가 내부에서 &lt;code&gt;log_softmax&lt;/code&gt;와 음의 로그가능도를 함께 처리하기 때문인데요. TF의 &lt;code&gt;from_logits=True&lt;/code&gt;와 정확히 같은 이유입니다. 앞 글에서 본 그 논리 그대로예요.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 사전학습 모델을 불러와 &lt;code&gt;forward&lt;/code&gt; 일부만 바꿔 파인튜닝하거나, 커스텀 레이어를 끼워 넣을 때. 허깅페이스 Transformers 모델도 전부 &lt;code&gt;nn.Module&lt;/code&gt;이라 이 구조를 알면 그대로 응용됩니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 다음은 PyTorch의 심장, autograd와 backward입니다.&lt;/p&gt;

&lt;h2&gt;3. autograd와 backward — 그리고 zero_grad 함정&lt;/h2&gt;

&lt;p&gt;TF가 &lt;code&gt;GradientTape&lt;/code&gt;로 연산을 녹음했다면, PyTorch는 텐서에 연산 이력을 자동으로 붙여 둡니다. 이걸 &lt;em&gt;autograd&lt;/em&gt;라 부르고, 미분은 &lt;code&gt;loss.backward()&lt;/code&gt; 한 줄로 촉발됩니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;autograd&lt;/code&gt; (자동 미분 엔진): 텐서 연산을 그래프로 추적해 &lt;code&gt;backward()&lt;/code&gt; 때 기울기를 자동 계산 / TF GradientTape에 대응.&lt;br&gt;
&lt;code&gt;optimizer.zero_grad()&lt;/code&gt;: 파라미터에 쌓인 기울기를 0으로 비우는 것.&lt;/p&gt;

&lt;p&gt;학습 스텝의 뼈대는 이렇습니다.&lt;/p&gt;

&lt;pre&gt;pred = model(X)              &lt;span class=&quot;cm&quot;&gt;# ① 순전파&lt;/span&gt;
loss = loss_fn(pred, y)      &lt;span class=&quot;cm&quot;&gt;# ② 손실&lt;/span&gt;

loss.backward()              &lt;span class=&quot;cm&quot;&gt;# ③ 역전파: 기울기 계산 &amp; 누적(+=)&lt;/span&gt;
optimizer.step()             &lt;span class=&quot;cm&quot;&gt;# ④ 가중치 갱신&lt;/span&gt;
optimizer.zero_grad()        &lt;span class=&quot;cm&quot;&gt;# ⑤ 기울기 0으로 — 다음 배치 대비&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 못박습니다.&lt;/b&gt; PyTorch는 기울기를 덮어쓰지 않고 &lt;strong&gt;더합니다&lt;/strong&gt; — &lt;em&gt;&quot;Gradients by default add up; ... we explicitly zero them at each iteration.&quot;&lt;/em&gt; 기본적으로 기울기가 누적되니, 이중 계산을 막으려면 매 반복마다 명시적으로 0으로 비워야 한다는 뜻입니다. &lt;span class=&quot;src&quot;&gt;(출처: PyTorch — Optimizing Model Parameters)&lt;/span&gt;&lt;/p&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ ★ 1순위 함정 · zero_grad를 잊으면&lt;/b&gt; — &lt;code&gt;zero_grad()&lt;/code&gt;를 빼먹으면 이전 배치의 기울기가 계속 더해집니다. 그러면 갱신 방향이 엉키고 손실이 요동치거나 발산하죠. TF의 &lt;code&gt;GradientTape&lt;/code&gt;는 &lt;code&gt;with&lt;/code&gt; 블록마다 새로 시작해 이 문제가 없었지만, PyTorch는 누적이 기본이라 &lt;em&gt;내가 챙겨야&lt;/em&gt; 합니다. (이 누적 특성을 역이용하면 작은 GPU에서 여러 배치를 모아 큰 배치처럼 학습하는 &lt;em&gt;gradient accumulation&lt;/em&gt; 기법이 됩니다.)&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 모든 PyTorch 학습 코드의 심장부입니다. 특히 GPU 메모리가 부족할 때 &lt;code&gt;zero_grad&lt;/code&gt;를 몇 스텝에 한 번만 호출해 기울기를 일부러 누적하는 기법은 대형 모델 파인튜닝의 단골 트릭이에요.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 마지막은 학습과 평가를 가르는 두 스위치, train/eval과 no_grad입니다.&lt;/p&gt;

&lt;h2&gt;4. train() / eval() 과 no_grad()&lt;/h2&gt;

&lt;p&gt;학습할 때와 평가할 때는 모델이 다르게 동작해야 합니다. PyTorch는 이걸 두 개의 서로 다른 스위치로 다룹니다 — 여기가 TF와 문법이 가장 갈리는 지점입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;model.train()&lt;/code&gt; / &lt;code&gt;model.eval()&lt;/code&gt;: Dropout·BatchNorm 등 레이어의 &lt;em&gt;동작 모드&lt;/em&gt;를 학습용/추론용으로 전환.&lt;br&gt;
&lt;code&gt;torch.no_grad()&lt;/code&gt;: autograd의 &lt;em&gt;기울기 추적&lt;/em&gt;을 꺼서 메모리·속도를 아끼는 컨텍스트.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;test&lt;/span&gt;(dataloader, model, loss_fn):
    model.eval()                     &lt;span class=&quot;cm&quot;&gt;# 추론 모드(Dropout 끔 등)&lt;/span&gt;
    &lt;span class=&quot;kw&quot;&gt;with&lt;/span&gt; torch.no_grad():             &lt;span class=&quot;cm&quot;&gt;# 기울기 추적 끔&lt;/span&gt;
        &lt;span class=&quot;kw&quot;&gt;for&lt;/span&gt; X, y &lt;span class=&quot;kw&quot;&gt;in&lt;/span&gt; dataloader:
            pred = model(X)
            &lt;span class=&quot;cm&quot;&gt;# ... 손실·정확도 집계 (가중치 갱신 없음)&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;둘은 다른 일을 한다&lt;/b&gt; — &lt;code&gt;eval()&lt;/code&gt;은 &lt;em&gt;레이어 동작&lt;/em&gt;을, &lt;code&gt;no_grad()&lt;/code&gt;는 &lt;em&gt;기울기 계산&lt;/em&gt;을 끕니다. 목적이 달라 평가 땐 &lt;b&gt;둘 다&lt;/b&gt; 써야 합니다. TF의 &lt;code&gt;training=False&lt;/code&gt; 하나가 사실상 이 둘을 합쳐 놓은 셈이라, TF만 하다 오면 하나를 빠뜨리기 쉽습니다.&lt;/div&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 함정&lt;/b&gt; — 평가에서 &lt;code&gt;model.eval()&lt;/code&gt;을 빼면 Dropout이 켜진 채라 결과가 매번 흔들리고, &lt;code&gt;no_grad()&lt;/code&gt;를 빼면 불필요한 기울기 그래프가 쌓여 메모리가 터질 수 있습니다. 학습 루프 시작엔 &lt;code&gt;model.train()&lt;/code&gt;, 평가엔 &lt;code&gt;model.eval()&lt;/code&gt;을 짝으로 붙이는 습관을 들이세요.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 검증/테스트 루프, 그리고 배포 후 추론 서버. 추론만 하는 서비스 코드에선 &lt;code&gt;eval()&lt;/code&gt; + &lt;code&gt;no_grad()&lt;/code&gt;(또는 &lt;code&gt;inference_mode()&lt;/code&gt;)가 기본 세팅입니다.&lt;/div&gt;

&lt;h2&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;단계&lt;/th&gt;&lt;th&gt;코드&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;모델 정의&lt;/td&gt;&lt;td&gt;&lt;code&gt;class M(nn.Module): def forward(self, x): ...&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;손실(로짓)&lt;/td&gt;&lt;td&gt;&lt;code&gt;nn.CrossEntropyLoss()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;학습 스텝&lt;/td&gt;&lt;td&gt;&lt;code&gt;backward()&lt;/code&gt; → &lt;code&gt;step()&lt;/code&gt; → &lt;code&gt;zero_grad()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;학습/추론 전환&lt;/td&gt;&lt;td&gt;&lt;code&gt;model.train()&lt;/code&gt; / &lt;code&gt;model.eval()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;평가 컨텍스트&lt;/td&gt;&lt;td&gt;&lt;code&gt;with torch.no_grad():&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 4가지만 기억하면 됩니다.&lt;br&gt;
1. 모델은 &lt;b&gt;nn.Module 서브클래싱 + forward()&lt;/b&gt;. 출력은 logits, 손실은 &lt;code&gt;CrossEntropyLoss&lt;/code&gt;.&lt;br&gt;
2. 학습 스텝은 &lt;b&gt;backward → step → zero_grad&lt;/b&gt;. 기울기는 기본이 &lt;b&gt;누적&lt;/b&gt;이다.&lt;br&gt;
3. &lt;code&gt;zero_grad()&lt;/code&gt;를 잊으면 학습이 망가진다 (TF엔 없던 함정).&lt;br&gt;
4. 평가엔 &lt;code&gt;eval()&lt;/code&gt;과 &lt;code&gt;no_grad()&lt;/code&gt;를 &lt;b&gt;둘 다&lt;/b&gt; — 각각 레이어·기울기를 끈다.&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html&quot;&gt;PyTorch — Quickstart (공식 문서)&lt;/a&gt; : nn.Module·학습/평가 루프 전체&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/tutorials/beginner/basics/optimization_tutorial.html&quot;&gt;PyTorch — Optimizing Model Parameters (공식 문서)&lt;/a&gt; : &quot;Gradients by default add up ... zero them at each iteration&quot;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html&quot;&gt;PyTorch — Automatic Differentiation with torch.autograd (공식 문서)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#&quot;&gt;「TensorFlow 전문가 학습 루프」&lt;/a&gt; (이전 글) : 같은 루프를 GradientTape로 — 대응 관계의 반대편&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/127</guid>
      <comments>https://hskywith.tistory.com/127#entry127comment</comments>
      <pubDate>Tue, 1 Sep 2026 14:58:45 +0900</pubDate>
    </item>
    <item>
      <title>pandas 선택과 집계 제대로 &amp;mdash; .loc&amp;middot;iloc, SettingWithCopyWarning, groupby</title>
      <link>https://hskywith.tistory.com/126</link>
      <description>&lt;!-- title: pandas 선택과 집계 제대로 — .loc·iloc, SettingWithCopyWarning, groupby | date: 2026-09-01 | tags: pandas, loc, iloc, SettingWithCopyWarning, groupby, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 .rp 블록만 붙여넣는다. --&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;넘파이 인덱싱을 정리하고 나니(&lt;a href=&quot;#&quot;&gt;「NumPy 인덱싱 5종」&lt;/a&gt;), 자연스럽게 판다스가 궁금해졌습니다. 표(表) 데이터를 다루다 보면 &lt;code&gt;df[&quot;A&quot;]&lt;/code&gt;, &lt;code&gt;df.loc[...]&lt;/code&gt;, &lt;code&gt;df.iloc[...]&lt;/code&gt;가 뒤섞여 나오는데, 어떤 땐 되고 어떤 땐 &lt;em&gt;SettingWithCopyWarning&lt;/em&gt;이라는 노란 경고가 떠서 늘 찜찜했거든요.&lt;/p&gt;

&lt;p&gt;공식 &lt;em&gt;&quot;10 minutes to pandas&quot;&lt;/em&gt;와 인덱싱 가이드를 다시 읽어보니, 판다스 선택의 핵심은 딱 세 갈래(&lt;code&gt;[]&lt;/code&gt; · &lt;code&gt;.loc&lt;/code&gt; · &lt;code&gt;.iloc&lt;/code&gt;)였고, 그 경고도 &quot;왜 뜨는지&quot;를 알면 두 번 다시 안 볼 수 있더라고요. 이 글에선 그 세 갈래와 경고의 정체, 그리고 데이터 분석의 심장인 &lt;code&gt;groupby&lt;/code&gt;까지 공식 문서를 근거로 짚습니다.&lt;/p&gt;

&lt;h2&gt;1. 큰 그림 — 고르고, 안전하게 고치고, 묶는다&lt;/h2&gt;

&lt;p&gt;판다스로 하는 일의 상당수는 이 세 동작으로 수렴합니다. 원하는 데이터를 &lt;em&gt;고르고&lt;/em&gt;, 값을 &lt;em&gt;안전하게 고치고&lt;/em&gt;, 그룹으로 &lt;em&gt;묶어 요약&lt;/em&gt;하는 것.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;DataFrame 다루기&lt;/b&gt; 를 세 동작으로&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;라벨로 고르기&lt;/span&gt;이름으로 선택&lt;span class=&quot;s&quot;&gt;.loc['a':'c']&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;위치로 고르기&lt;/span&gt;번호로 선택&lt;span class=&quot;s&quot;&gt;.iloc[0:3]&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;안전하게 수정&lt;/span&gt;한 번의 .loc&lt;span class=&quot;s&quot;&gt;.loc[mask,col]=x&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;그룹별 요약&lt;/span&gt;쪼개고·집계&lt;span class=&quot;s&quot;&gt;.groupby().agg()&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;2장에서 선택 3축, 3장에서 수정의 함정, 4장에서 그룹 집계를 채웁니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;2. 선택의 3축 — [], .loc, .iloc&lt;/h2&gt;

&lt;p&gt;먼저 &quot;고르기&quot;부터. 판다스에는 인덱싱 진입점이 세 개 있고, 각자 역할이 다릅니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.loc&lt;/code&gt; (라벨 기반): 인덱스·컬럼 &lt;em&gt;이름&lt;/em&gt;으로 선택 / &lt;code&gt;df.loc[&quot;2013-01-02&quot;, &quot;A&quot;]&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;.iloc&lt;/code&gt; (위치 기반): 0부터 세는 &lt;em&gt;정수 위치&lt;/em&gt;로 선택 / &lt;code&gt;df.iloc[1, 0]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;직관적으로는 &lt;code&gt;[]&lt;/code&gt;는 &quot;컬럼 꺼내기 전용 지름길&quot;, &lt;code&gt;.loc&lt;/code&gt;는 &quot;이름표로 찾기&quot;, &lt;code&gt;.iloc&lt;/code&gt;는 &quot;몇 번째냐로 찾기&quot;입니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# [] — 주로 컬럼 선택 / 행 슬라이스&lt;/span&gt;
df[&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;]            &lt;span class=&quot;cm&quot;&gt;# 단일 컬럼(Series)&lt;/span&gt;
df[[&lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;]]     &lt;span class=&quot;cm&quot;&gt;# 여러 컬럼&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# .loc — 라벨로 (행, 열)&lt;/span&gt;
df.loc[:, [&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;]]
df.loc[&lt;span class=&quot;st&quot;&gt;&quot;2013-01-02&quot;&lt;/span&gt;:&lt;span class=&quot;st&quot;&gt;&quot;2013-01-04&quot;&lt;/span&gt;, [&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;]]

&lt;span class=&quot;cm&quot;&gt;# .iloc — 위치로 (행, 열)&lt;/span&gt;
df.iloc[&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]
df.iloc[[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]]   &lt;span class=&quot;cm&quot;&gt;# 위치 리스트(팬시와 같은 결)&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; 두 슬라이싱의 끝값 처리가 다른 게 핵심인데요. &lt;code&gt;.loc&lt;/code&gt;는 라벨 슬라이스라 &lt;em&gt;&quot;both the start and the stop are included&quot;&lt;/em&gt; — 시작·끝 라벨을 &lt;strong&gt;둘 다 포함&lt;/strong&gt;합니다. 반면 &lt;code&gt;.iloc&lt;/code&gt;는 파이썬 관례대로 끝을 &lt;strong&gt;제외&lt;/strong&gt;합니다. &lt;span class=&quot;src&quot;&gt;(출처: pandas — Indexing and selecting data)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;s = pd.Series([&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;20&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;30&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;40&lt;/span&gt;], index=[&lt;span class=&quot;st&quot;&gt;&quot;a&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;b&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;c&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;d&quot;&lt;/span&gt;])
s.loc[&lt;span class=&quot;st&quot;&gt;&quot;a&quot;&lt;/span&gt;:&lt;span class=&quot;st&quot;&gt;&quot;c&quot;&lt;/span&gt;]     &lt;span class=&quot;cm&quot;&gt;# a, b, c  ← 'c' 포함!&lt;/span&gt;
s.iloc[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]       &lt;span class=&quot;cm&quot;&gt;# a, b     ← 2번(=c) 제외&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 오프바이원 단골&lt;/b&gt; — &lt;code&gt;.loc[&quot;a&quot;:&quot;c&quot;]&lt;/code&gt;는 3개, &lt;code&gt;.iloc[0:3]&lt;/code&gt;은 3개인데 &lt;code&gt;.loc[0:3]&lt;/code&gt;(정수 라벨)은 &lt;b&gt;4개&lt;/b&gt;가 나올 수 있습니다. 끝값 포함/제외가 진입점마다 달라서 생기는 실수예요. &quot;라벨 슬라이스는 끝 포함&quot;을 못박아 두세요.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 시계열에서 &lt;code&gt;df.loc[&quot;2024-01&quot;:&quot;2024-03&quot;]&lt;/code&gt;로 분기 데이터를 이름으로 자르거나, &lt;code&gt;df.iloc[:1000]&lt;/code&gt;로 앞 1000행만 빠르게 떼어 실험할 때. 라벨이 의미를 담으면 &lt;code&gt;.loc&lt;/code&gt;, 순수 위치면 &lt;code&gt;.iloc&lt;/code&gt;가 정석입니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 다음은 이 선택으로 값을 &quot;고칠 때&quot; 터지는 SettingWithCopyWarning입니다.&lt;/p&gt;

&lt;h2&gt;3. 체인 인덱싱과 SettingWithCopyWarning&lt;/h2&gt;

&lt;p&gt;고르는 것까진 쉬운데, 고른 자리에 값을 &lt;em&gt;대입&lt;/em&gt;할 때 판다스가 노란 경고를 냅니다. 이게 넘파이에서 본 view/copy 문제의 판다스 버전이에요.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chained indexing&lt;/code&gt; (체인 인덱싱): 대괄호를 연달아 쓰는 것 / &lt;code&gt;df[&quot;A&quot;][0]&lt;/code&gt;, &lt;code&gt;df[df.A&amp;gt;0][&quot;B&quot;]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;왜 위험할까요. &lt;code&gt;df[df.A&amp;gt;0]&lt;/code&gt;가 &lt;em&gt;복사본&lt;/em&gt;일 수 있는데, 거기에 다시 &lt;code&gt;[&quot;B&quot;] = 0&lt;/code&gt;을 하면 그 복사본만 바뀌고 &lt;strong&gt;원본 &lt;code&gt;df&lt;/code&gt;는 그대로&lt;/strong&gt;일 수 있습니다. 판다스는 이 애매함을 감지하면 경고를 띄웁니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 권합니다.&lt;/b&gt; 값 대입 때 &lt;code&gt;.loc&lt;/code&gt;는 축(axis)을 정렬해 한 번에 처리하지만, 체인 인덱싱은 정렬이 대입보다 먼저 일어나 &lt;em&gt;&quot;will not modify df&quot;&lt;/em&gt;가 될 수 있다고 설명합니다. 그래서 대입은 &lt;strong&gt;하나의 &lt;code&gt;.loc&lt;/code&gt;&lt;/strong&gt;으로 하라는 게 정석입니다. &lt;span class=&quot;src&quot;&gt;(출처: pandas — Indexing and selecting data)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# ✗ 체인 인덱싱 — 경고 + 원본 안 바뀔 수 있음&lt;/span&gt;
df[df[&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;] &amp;gt; &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;][&lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;] = &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ✓ 한 번의 .loc — 행·열을 동시에 지정해 안전하게 대입&lt;/span&gt;
df.loc[df[&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;] &amp;gt; &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;] = &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;규칙&lt;/b&gt; — &quot;고를 땐 &lt;code&gt;[]&lt;/code&gt;도 괜찮지만, &lt;em&gt;고치는(대입)&lt;/em&gt; 순간엔 무조건 &lt;code&gt;.loc&lt;/code&gt; 한 방.&quot; 이 습관 하나로 SettingWithCopyWarning은 거의 사라집니다.&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  판다스 3.x부터 도입되는 &lt;b&gt;Copy-on-Write&lt;/b&gt;(쓸 때 복사)로 이 경고의 동작 방식이 정리되고 있습니다. 다만 &quot;체인 인덱싱으로 대입하지 말라&quot;는 원칙 자체는 버전과 무관하게 그대로 유효합니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 이상치 클리핑(&lt;code&gt;df.loc[df.x &amp;gt; cap, &quot;x&quot;] = cap&lt;/code&gt;), 조건부 라벨링(&lt;code&gt;df.loc[df.score &amp;gt;= 60, &quot;pass&quot;] = True&lt;/code&gt;)처럼 &quot;조건에 맞는 행의 특정 열만 수정&quot;은 데이터 전처리에서 매일 나옵니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 마지막으로, 고른 데이터를 그룹으로 묶어 요약하는 groupby입니다.&lt;/p&gt;

&lt;h2&gt;4. groupby — 쪼개고, 적용하고, 합친다&lt;/h2&gt;

&lt;p&gt;선택이 &quot;행/열 고르기&quot;였다면, &lt;code&gt;groupby&lt;/code&gt;는 &quot;같은 부류끼리 묶어 통계 내기&quot;입니다. 판다스 분석의 심장이라 할 만해요.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;groupby&lt;/code&gt; (그룹 연산): 특정 컬럼 값으로 행을 묶어 그룹마다 집계하는 것 / SQL의 &lt;code&gt;GROUP BY&lt;/code&gt;와 같은 개념.&lt;/p&gt;

&lt;p&gt;공식 문서는 이 과정을 &lt;strong&gt;split-apply-combine&lt;/strong&gt;(쪼개기–적용–합치기)이라는 세 단계로 설명합니다. 데이터를 그룹으로 &lt;em&gt;쪼개고&lt;/em&gt;, 각 그룹에 함수를 &lt;em&gt;적용&lt;/em&gt;하고, 결과를 하나로 &lt;em&gt;합치는&lt;/em&gt; 흐름이죠. &lt;span class=&quot;src&quot;&gt;(출처: pandas — Group by: split-apply-combine)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;df = pd.DataFrame({
    &lt;span class=&quot;st&quot;&gt;&quot;팀&quot;&lt;/span&gt;:   [&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;],
    &lt;span class=&quot;st&quot;&gt;&quot;득점&quot;&lt;/span&gt;: [&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;7&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;],
})
df.groupby(&lt;span class=&quot;st&quot;&gt;&quot;팀&quot;&lt;/span&gt;)[&lt;span class=&quot;st&quot;&gt;&quot;득점&quot;&lt;/span&gt;].sum()
&lt;span class=&quot;cm&quot;&gt;# 팀&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# A    18&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# B    15&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여러 집계를 한 번에 낼 땐 이름을 붙이는 &lt;em&gt;named aggregation&lt;/em&gt;이 깔끔합니다. 결과 컬럼명이 정돈되고, 파이썬 함수를 &lt;code&gt;apply&lt;/code&gt;로 도는 것보다 내장 집계가 훨씬 빠릅니다.&lt;/p&gt;

&lt;pre&gt;df.groupby(&lt;span class=&quot;st&quot;&gt;&quot;팀&quot;&lt;/span&gt;).agg(
    총득점=(&lt;span class=&quot;st&quot;&gt;&quot;득점&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;sum&quot;&lt;/span&gt;),
    평균=(&lt;span class=&quot;st&quot;&gt;&quot;득점&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;mean&quot;&lt;/span&gt;),
    경기수=(&lt;span class=&quot;st&quot;&gt;&quot;득점&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;count&quot;&lt;/span&gt;),
)&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 함정 · agg vs transform&lt;/b&gt; — &lt;code&gt;agg&lt;/code&gt;는 그룹을 &lt;em&gt;하나의 값으로 축소&lt;/em&gt;(합·평균)하고, &lt;code&gt;transform&lt;/code&gt;은 &lt;em&gt;원래 행 수 그대로&lt;/em&gt; 돌려줍니다. &quot;각 행에 그 그룹의 평균을 붙이고 싶다&quot;면 &lt;code&gt;groupby(...).transform(&quot;mean&quot;)&lt;/code&gt;이어야지 &lt;code&gt;agg&lt;/code&gt;가 아닙니다. 둘을 헷갈리면 형태가 안 맞아 에러가 나거나 조용히 잘못된 결과가 됩니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — &quot;카테고리별 평균 구매액&quot;, &quot;사용자별 세션 수&quot;처럼 집계 리포트 전부가 groupby입니다. 머신러닝에선 그룹 통계를 피처로 만들 때(&lt;code&gt;transform&lt;/code&gt;으로 그룹 평균을 각 행에 붙이기) 특히 유용해요.&lt;/div&gt;

&lt;h2&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;하고 싶은 것&lt;/th&gt;&lt;th&gt;패턴&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;컬럼 꺼내기&lt;/td&gt;&lt;td&gt;&lt;code&gt;df[&quot;A&quot;]&lt;/code&gt;, &lt;code&gt;df[[&quot;A&quot;,&quot;B&quot;]]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;라벨로 (행, 열)&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.loc[row_label, col]&lt;/code&gt; (끝 포함)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;위치로 (행, 열)&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.iloc[i, j]&lt;/code&gt; (끝 제외)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;조건 행의 열 수정&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.loc[mask, &quot;col&quot;] = 값&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;그룹 집계&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.groupby(&quot;k&quot;).agg(...)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;그룹 통계를 각 행에&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.groupby(&quot;k&quot;)[&quot;v&quot;].transform(&quot;mean&quot;)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 4가지만 기억하면 됩니다.&lt;br&gt;
1. 고르기 진입점은 &lt;b&gt;[] · .loc · .iloc&lt;/b&gt; 셋, 역할이 다르다.&lt;br&gt;
2. &lt;code&gt;.loc&lt;/code&gt; 슬라이스는 끝 &lt;b&gt;포함&lt;/b&gt;, &lt;code&gt;.iloc&lt;/code&gt;는 끝 &lt;b&gt;제외&lt;/b&gt;.&lt;br&gt;
3. &lt;em&gt;대입&lt;/em&gt;은 체인 인덱싱 말고 &lt;b&gt;한 번의 .loc&lt;/b&gt; — SettingWithCopyWarning의 해법.&lt;br&gt;
4. groupby는 &lt;b&gt;split-apply-combine&lt;/b&gt;. 축소는 &lt;code&gt;agg&lt;/code&gt;, 형태 유지는 &lt;code&gt;transform&lt;/code&gt;.&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://pandas.pydata.org/docs/user_guide/10min.html&quot;&gt;pandas — 10 minutes to pandas (공식 문서)&lt;/a&gt; : 선택·groupby 기본 예제&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pandas.pydata.org/docs/user_guide/indexing.html&quot;&gt;pandas — Indexing and selecting data (공식 문서)&lt;/a&gt; : .loc 끝값 포함, 체인 인덱싱 경고, 대입은 .loc&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pandas.pydata.org/docs/user_guide/groupby.html&quot;&gt;pandas — Group by: split-apply-combine (공식 문서)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#&quot;&gt;「NumPy 인덱싱 5종」&lt;/a&gt; (이전 글) : 판다스 불리언 선택의 뿌리인 넘파이 불리언 인덱싱&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/126</guid>
      <comments>https://hskywith.tistory.com/126#entry126comment</comments>
      <pubDate>Tue, 1 Sep 2026 14:58:16 +0900</pubDate>
    </item>
    <item>
      <title>NumPy 인덱싱 5종 제대로 &amp;mdash; Basic&amp;middot;Advanced와 View/Copy 함정</title>
      <link>https://hskywith.tistory.com/125</link>
      <description>&lt;!-- title: NumPy 인덱싱 5종 제대로 — Basic·Advanced와 View/Copy 함정 | date: 2026-09-01 | tags: NumPy, indexing, fancy indexing, boolean indexing, view, copy, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 .rp 블록만 붙여넣는다. --&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;넘파이 공식 &lt;em&gt;Quickstart&lt;/em&gt;를 다시 훑다가, &quot;Indexing&quot;이라는 짧은 절 하나에 발이 걸렸습니다. 인덱싱이 파이썬 리스트처럼 &lt;code&gt;a[2]&lt;/code&gt;, &lt;code&gt;a[1:5]&lt;/code&gt; 정도인 줄 알았는데, 정수·슬라이스·불리언·정수배열까지 종류가 여러 갈래더라고요. 특히 &lt;em&gt;fancy indexing&lt;/em&gt;(팬시 인덱싱)이라는 이름이 낯설어서 &quot;이게 대체 뭔데 이름까지 붙었나&quot; 싶었습니다.&lt;/p&gt;

&lt;p&gt;파보니 인덱싱의 진짜 핵심은 &quot;종류가 몇 개냐&quot;가 아니라, &lt;strong&gt;어떤 인덱싱은 원본을 공유(view)하고 어떤 인덱싱은 복사본(copy)을 만든다&lt;/strong&gt;는 갈림길이었습니다. 이 글은 넘파이 인덱싱 5종을 정리하면서, 그 밑에 깔린 Basic/Advanced 구분과 view/copy 함정까지 공식 문서를 근거로 짚어봅니다. (배열이 메모리에 어떻게 놓이길래 view가 가능한지, 그 &lt;em&gt;왜&lt;/em&gt;는 지난 글 &lt;a href=&quot;#&quot;&gt;「NumPy는 왜 빠른가 — ndarray 메모리 모델」&lt;/a&gt;에서 다뤘으니 여기선 인덱싱 사용법에 집중합니다.)&lt;/p&gt;

&lt;h2&gt;1. 큰 그림 — 무엇으로 고르느냐&lt;/h2&gt;

&lt;p&gt;인덱싱은 결국 &quot;배열에서 원하는 원소를 골라내는 방법&quot;입니다. 무엇을 기준으로 고르느냐에 따라 갈래가 나뉘는데, 이걸 한 장으로 정리하면 이렇게 됩니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;배열에서 원소 고르기&lt;/b&gt; 를 4가지 기준으로&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;정수 하나로&lt;/span&gt;위치 콕 집기&lt;span class=&quot;s&quot;&gt;a[2], a[1,3]&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;범위로&lt;/span&gt;연속 구간 슬라이스&lt;span class=&quot;s&quot;&gt;a[1:5]&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;위치 배열로&lt;/span&gt;흩어진 위치 한 번에&lt;span class=&quot;s&quot;&gt;a[[0,3,1]]&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;조건으로&lt;/span&gt;참인 것만 거르기&lt;span class=&quot;s&quot;&gt;a[a&amp;gt;0]&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;앞 두 칸은 &lt;b&gt;Basic(뷰)&lt;/b&gt;, 뒤 두 칸은 &lt;b&gt;Advanced(복사본)&lt;/b&gt; — 이 경계가 이 글의 핵심입니다.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;2장에서 5종을 빠르게 훑고, 3장에서 그 5종이 사실 &lt;em&gt;Basic&lt;/em&gt;과 &lt;em&gt;Advanced&lt;/em&gt; 두 무리로 갈린다는 것(그리고 그게 왜 중요한지)을 봅니다. 그다음 4·5장에서 Advanced에 속하는 팬시 인덱싱과 불리언 인덱싱을 하나씩 깊게 팝니다.&lt;/p&gt;

&lt;h2&gt;2. 인덱싱 5종 — 한눈에&lt;/h2&gt;

&lt;p&gt;먼저 종류부터 훑겠습니다. 넘파이 인덱싱은 보통 아래 5가지로 나눠 설명합니다. 예제 배열 하나로 전부 보여드릴게요.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;indexing&lt;/code&gt; (인덱싱): 배열에서 특정 위치의 원소(들)를 지정해 읽거나 쓰는 것 / 예: &lt;code&gt;a[0]&lt;/code&gt;은 첫 원소.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; numpy &lt;span class=&quot;kw&quot;&gt;as&lt;/span&gt; np
a = np.array([[&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;20&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;30&lt;/span&gt;],
              [&lt;span class=&quot;nu&quot;&gt;40&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;50&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;60&lt;/span&gt;],
              [&lt;span class=&quot;nu&quot;&gt;70&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;80&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;90&lt;/span&gt;]])

&lt;span class=&quot;cm&quot;&gt;# ① 정수 인덱싱 — 위치 하나&lt;/span&gt;
a[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;]          &lt;span class=&quot;cm&quot;&gt;# array([10, 20, 30])  (0번 행)&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ② 슬라이싱 — 연속 범위&lt;/span&gt;
a[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]        &lt;span class=&quot;cm&quot;&gt;# 0~1번 행&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ③ 다차원 인덱싱 — 콤마로 축마다&lt;/span&gt;
a[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]       &lt;span class=&quot;cm&quot;&gt;# 60  (1번 행, 2번 열)&lt;/span&gt;
a[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;]     &lt;span class=&quot;cm&quot;&gt;# array([20, 50])&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ④ 팬시 인덱싱 — 정수 &quot;배열&quot;로 임의 선택&lt;/span&gt;
a[[&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;]]     &lt;span class=&quot;cm&quot;&gt;# 2번, 0번 행을 이 순서로&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ⑤ 불리언 인덱싱 — 조건 필터&lt;/span&gt;
a[a &amp;gt; &lt;span class=&quot;nu&quot;&gt;50&lt;/span&gt;]      &lt;span class=&quot;cm&quot;&gt;# array([60, 70, 80, 90])&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;공식 문서도 인덱싱을 크게 두 부류로 나눠 설명하는데, 재미있는 건 위 다섯이 딱 5개의 독립된 기능이 아니라는 점입니다. &lt;span class=&quot;src&quot;&gt;(참고: NumPy Indexing on ndarrays)&lt;/span&gt; 종류를 외우기보다, 다음 장에서 볼 &lt;strong&gt;Basic이냐 Advanced냐&lt;/strong&gt;의 경계를 잡는 게 훨씬 남는 장사입니다.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — ①②③은 데이터의 특정 행/열을 꺼내 볼 때(예: 이미지 한 채널 &lt;code&gt;img[:, :, 0]&lt;/code&gt;), ④⑤는 조건에 맞는 샘플만 골라 학습셋을 만들 때 일상적으로 씁니다.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 다음은 이 5종을 가르는 진짜 경계, Basic vs Advanced입니다.&lt;/p&gt;

&lt;h2&gt;3. 진짜 경계 — Basic이냐 Advanced냐 (View냐 Copy냐)&lt;/h2&gt;

&lt;p&gt;5종을 외우는 것보다 중요한 건, 넘파이가 인덱싱을 내부적으로 &lt;strong&gt;Basic indexing&lt;/strong&gt;과 &lt;strong&gt;Advanced indexing&lt;/strong&gt; 두 무리로 구분한다는 사실입니다. 그리고 이 구분이 곧 &quot;결과가 원본과 메모리를 공유하는가&quot;를 결정합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;view&lt;/code&gt; (뷰): 원본과 &lt;em&gt;같은 데이터 버퍼&lt;/em&gt;를 공유하며 보는 방식만 다른 배열 / 뷰를 고치면 원본도 바뀜.&lt;br&gt;
&lt;code&gt;copy&lt;/code&gt; (복사본): 데이터까지 새로 복제한 독립 배열 / 고쳐도 원본은 그대로.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; 기본 인덱싱(정수·슬라이스)은 오프셋과 스트라이드로 주소를 매길 수 있어서 언제나 뷰를 만들고, 고급 인덱싱은 언제나 복사본을 만든다고 못박습니다 — &lt;em&gt;&quot;basic indexing always creates views&quot;&lt;/em&gt;, &lt;em&gt;&quot;Advanced indexing ... always creates copies&quot;&lt;/em&gt;. &lt;span class=&quot;src&quot;&gt;(출처: NumPy — Copies and views)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 문서 예제 — 슬라이싱은 뷰&lt;/span&gt;
x = np.arange(&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;)
y = x[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;]        &lt;span class=&quot;cm&quot;&gt;# 뷰 생성&lt;/span&gt;
x[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;:&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;] = [&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;11&lt;/span&gt;]
y                  &lt;span class=&quot;cm&quot;&gt;# array([10, 11]) — x를 바꿨는데 y도 바뀐다&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# 팬시(고급) 인덱싱은 복사본&lt;/span&gt;
z = x[[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]]
z.base &lt;span class=&quot;kw&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;None&lt;/span&gt;     &lt;span class=&quot;cm&quot;&gt;# True — 원본을 참조하지 않는 독립 배열&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여기서 &lt;code&gt;.base&lt;/code&gt;가 판별기 역할을 합니다. 뷰라면 원본 배열을 가리키고, 복사본이라면 &lt;code&gt;None&lt;/code&gt;입니다. 공식 문서 표현을 빌리면 base는 뷰에선 원본을, 복사본에선 &lt;em&gt;None&lt;/em&gt;을 돌려줍니다. &lt;span class=&quot;src&quot;&gt;(출처: NumPy — Copies and views)&lt;/span&gt;&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;무리&lt;/th&gt;&lt;th&gt;어떤 인덱싱&lt;/th&gt;&lt;th&gt;결과&lt;/th&gt;&lt;th&gt;&lt;code&gt;arr[...] = 값&lt;/code&gt; 하면&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Basic&lt;/b&gt;&lt;/td&gt;&lt;td&gt;정수 · 슬라이스 · 다차원(콤마)&lt;/td&gt;&lt;td&gt;View (뷰)&lt;/td&gt;&lt;td&gt;원본이 바뀐다&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;Advanced&lt;/b&gt;&lt;/td&gt;&lt;td&gt;팬시(정수배열) · 불리언&lt;/td&gt;&lt;td&gt;Copy (복사본)&lt;/td&gt;&lt;td&gt;새 배열이라 원본 안 바뀔 수 있음&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;한 줄 판별법&lt;/b&gt; — 대괄호 &lt;code&gt;[ ]&lt;/code&gt; 안에 &lt;em&gt;리스트/배열&lt;/em&gt;이 들어가면(팬시·불리언) Advanced → Copy. 정수와 슬라이스(&lt;code&gt;:&lt;/code&gt;)만 있으면 Basic → View.&lt;/div&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 흔한 함정&lt;/b&gt; — 큰 배열을 슬라이싱해서 조각만 남기면, 그건 &lt;em&gt;뷰&lt;/em&gt;라 원본 전체가 메모리에 계속 붙잡혀 있습니다. 정말로 조각만 떼어 쓰려면 &lt;code&gt;arr[1:100].copy()&lt;/code&gt;로 명시적 복사하세요. 반대로, 팬시 인덱싱 결과를 고쳐도 원본이 안 바뀌어서 &quot;왜 안 되지?&quot; 하는 경우도 여기서 옵니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 전처리에서 원본을 보존하며 정규화할 때 &lt;code&gt;df.copy()&lt;/code&gt;/&lt;code&gt;arr.copy()&lt;/code&gt;를 습관적으로 쓰는 이유가 바로 이 view/copy 경계입니다. 판다스의 &lt;em&gt;SettingWithCopyWarning&lt;/em&gt;도 같은 뿌리예요.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 다음은 Advanced의 첫 주자, 팬시 인덱싱입니다.&lt;/p&gt;

&lt;h2&gt;4. 팬시 인덱싱 — 흩어진 위치를 한 번에&lt;/h2&gt;

&lt;p&gt;앞에서 Advanced 무리가 복사본을 만든다고 했는데, 그 대표가 팬시 인덱싱입니다. 슬라이싱이 &quot;연속 범위&quot;만 잘라내는 데 비해, 팬시 인덱싱은 &lt;strong&gt;흩어진 위치를 임의 순서로, 중복까지 허용해서&lt;/strong&gt; 뽑아냅니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fancy indexing&lt;/code&gt; (팬시 인덱싱): 정수(또는 불리언) &lt;em&gt;배열&lt;/em&gt;을 인덱스로 넘겨 여러 원소를 한 번에 선택하는 것 / 예: &lt;code&gt;a[[0, 2, 0]]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;직관은 &quot;번호표 뭉치를 건네는 것&quot;에 가깝습니다. 슬라이싱이 &quot;3번부터 7번까지&quot;라면, 팬시는 &quot;3번, 0번, 3번 다시, 5번 줘&quot;처럼 순서와 중복을 마음대로 지정합니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; 인덱스 배열의 &lt;em&gt;모양(shape)&lt;/em&gt;이 곧 결과의 모양이 된다는 점을 예제로 보여줍니다. &lt;span class=&quot;src&quot;&gt;(출처: NumPy Quickstart — Indexing with arrays of indices)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 문서 예제&lt;/span&gt;
a = np.arange(&lt;span class=&quot;nu&quot;&gt;12&lt;/span&gt;)**&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;              &lt;span class=&quot;cm&quot;&gt;# [0,1,4,9,16,25,...,121]&lt;/span&gt;
i = np.array([&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;])
a[i]                          &lt;span class=&quot;cm&quot;&gt;# array([ 1,  1,  9, 64, 25]) — 중복 OK&lt;/span&gt;

j = np.array([[&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;9&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;7&lt;/span&gt;]])   &lt;span class=&quot;cm&quot;&gt;# 2차원 인덱스&lt;/span&gt;
a[j]                          &lt;span class=&quot;cm&quot;&gt;# j와 같은 (2,2) 모양으로 반환&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여러 축을 각각의 인덱스 배열로 넘기면, 두 배열이 &lt;em&gt;짝지어져&lt;/em&gt; 원소를 뽑습니다. 이때 두 인덱스 배열은 브로드캐스팅 규칙으로 맞춰지는데, 그 자세한 규칙은 앞서 링크한 메모리 모델 글에서 다뤘습니다.&lt;/p&gt;

&lt;pre&gt;a = np.arange(&lt;span class=&quot;nu&quot;&gt;12&lt;/span&gt;).reshape(&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;)
i = np.array([[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]])   &lt;span class=&quot;cm&quot;&gt;# 행 인덱스&lt;/span&gt;
j = np.array([[&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;]])   &lt;span class=&quot;cm&quot;&gt;# 열 인덱스&lt;/span&gt;
a[i, j]                       &lt;span class=&quot;cm&quot;&gt;# a[0,2],a[1,1],a[1,3],a[2,3] → [[2,5],[7,11]]&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 면접 단골 함정&lt;/b&gt; — 같은 위치를 여러 번 넣고 &lt;code&gt;+=&lt;/code&gt; 하면 어떻게 될까요?
&lt;pre&gt;a = np.arange(&lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;)
a[[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;]] += &lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;
a                &lt;span class=&quot;cm&quot;&gt;# array([1, 1, 3, 3, 4]) — 0번은 +1이 두 번 아니라 한 번!&lt;/span&gt;&lt;/pre&gt;
0번이 두 번 나오니 2가 될 것 같지만 실제로는 한 번만 오릅니다. &lt;code&gt;a[idx] += 1&lt;/code&gt;이 &quot;뽑고 → 더하고 → 되쓰기&quot;를 한 번에 처리(buffered)하기 때문인데요. 정말 누적하고 싶다면 &lt;code&gt;np.add.at(a, [0,0,2], 1)&lt;/code&gt;을 써야 합니다. &lt;span class=&quot;src&quot;&gt;(출처: NumPy Quickstart)&lt;/span&gt;&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 매 epoch마다 데이터를 섞을 때 &lt;code&gt;idx = np.random.permutation(len(X)); X[idx], y[idx]&lt;/code&gt;로 입력·라벨을 짝 맞춰 셔플하는 게 팬시 인덱싱의 교과서적 활용입니다. 라벨→색 팔레트 매핑(&lt;code&gt;palette[labels]&lt;/code&gt;)도 같은 패턴이에요.&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 다음은 조건으로 거르는 불리언 인덱싱입니다.&lt;/p&gt;

&lt;h2&gt;5. 불리언 인덱싱 — 조건으로 거르기&lt;/h2&gt;

&lt;p&gt;팬시가 &quot;위치&quot;로 골랐다면, 불리언 인덱싱은 &quot;조건&quot;으로 고릅니다. 역시 Advanced라 결과는 복사본입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;boolean indexing&lt;/code&gt; (불리언 인덱싱 / 마스킹): 원본과 같은 모양의 True/False 배열을 넘겨, True인 자리의 원소만 뽑는 것 / 예: &lt;code&gt;a[a &amp;gt; 0]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;비유하면 배열 위에 &lt;em&gt;구멍 뚫린 마스크(mask)&lt;/em&gt;를 겹쳐, 구멍(True) 아래 값만 통과시키는 셈입니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 문서 예제&lt;/span&gt;
a = np.arange(&lt;span class=&quot;nu&quot;&gt;12&lt;/span&gt;).reshape(&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;)
b = a &amp;gt; &lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;              &lt;span class=&quot;cm&quot;&gt;# a와 같은 모양의 True/False 배열&lt;/span&gt;
a[b]                    &lt;span class=&quot;cm&quot;&gt;# array([5,6,7,8,9,10,11]) — 1D로 뽑힘&lt;/span&gt;
a[b] = &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;               &lt;span class=&quot;cm&quot;&gt;# 4보다 큰 값 전부 0으로 (조건부 대입)&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;조건을 여러 개 엮을 때가 진짜 함정입니다. 파이썬의 &lt;code&gt;and&lt;/code&gt;/&lt;code&gt;or&lt;/code&gt;는 배열에 쓸 수 없고, 원소별 논리연산자 &lt;code&gt;&amp;amp;&lt;/code&gt;(AND) &lt;code&gt;|&lt;/code&gt;(OR) &lt;code&gt;~&lt;/code&gt;(NOT)를 써야 합니다. 게다가 우선순위 때문에 &lt;strong&gt;각 조건을 괄호로&lt;/strong&gt; 감싸야 하고요.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 올바른 방법 — 괄호 필수&lt;/span&gt;
a[(a &amp;gt; &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;) &amp;amp; (a &amp;lt; &lt;span class=&quot;nu&quot;&gt;9&lt;/span&gt;)]

&lt;span class=&quot;cm&quot;&gt;# a &gt; 2 and a &lt; 9   → ValueError (truth value 모호)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# a &gt; 2 &amp; a &lt; 9     → a &gt; (2 &amp; a) &lt; 9 로 잘못 묶임&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 주의&lt;/b&gt; — &lt;code&gt;&amp;amp;&lt;/code&gt;/&lt;code&gt;|&lt;/code&gt;는 비교연산자보다 우선순위가 높습니다. 괄호를 빼면 &lt;code&gt;a &amp;gt; (2 &amp;amp; a) &amp;lt; 9&lt;/code&gt;처럼 엉뚱하게 묶여 조용히 틀린 결과가 나오거나 에러가 납니다. &quot;불리언 조건엔 무조건 괄호&quot;로 외우세요.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — &quot;결측 아닌 행만&quot;, &quot;임계값 넘는 픽셀만&quot; 같은 필터링 전부가 불리언 인덱싱입니다. 딥러닝에선 어텐션 패딩 마스크, 손실에서 특정 클래스 제외 등에서 매일 만나게 됩니다.&lt;/div&gt;

&lt;h2&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;하고 싶은 것&lt;/th&gt;&lt;th&gt;패턴&lt;/th&gt;&lt;th&gt;무리&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;위치 하나&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[i]&lt;/code&gt;, &lt;code&gt;a[r, c]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Basic → View&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;연속 범위&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[start:stop]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Basic → View&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;흩어진 위치·재정렬·중복&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[[0, 3, 1]]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Advanced → Copy&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;조건 필터&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[a &amp;gt; 0]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Advanced → Copy&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;여러 조건&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[(a&amp;gt;2) &amp;amp; (a&amp;lt;9)]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;괄호 필수&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;인덱스 누적 대입&lt;/td&gt;&lt;td&gt;&lt;code&gt;np.add.at(a, idx, 1)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;+=&lt;/code&gt; 대신&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;독립 조각 확보&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[1:100].copy()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;뷰 → 복사&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 4가지만 기억하면 됩니다.&lt;br&gt;
1. 인덱싱은 &lt;b&gt;Basic(정수·슬라이스)&lt;/b&gt;과 &lt;b&gt;Advanced(배열·불리언)&lt;/b&gt;로 갈린다.&lt;br&gt;
2. Basic은 &lt;b&gt;View&lt;/b&gt;(원본 공유), Advanced는 &lt;b&gt;Copy&lt;/b&gt;(독립) — &lt;code&gt;.base&lt;/code&gt;로 확인.&lt;br&gt;
3. 팬시 인덱싱의 &lt;code&gt;a[[0,0,2]] += 1&lt;/code&gt;은 &lt;b&gt;한 번만&lt;/b&gt; 오른다. 누적은 &lt;code&gt;np.add.at&lt;/code&gt;.&lt;br&gt;
4. 불리언 여러 조건은 &lt;code&gt;and&lt;/code&gt;가 아니라 &lt;code&gt;&amp;amp; | ~&lt;/code&gt; + &lt;b&gt;괄호&lt;/b&gt;.&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/user/basics.indexing.html&quot;&gt;NumPy — Indexing on ndarrays (공식 문서)&lt;/a&gt; : 인덱싱 종류와 Basic/Advanced 구분&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/user/basics.copies.html&quot;&gt;NumPy — Copies and views (공식 문서)&lt;/a&gt; : &quot;basic indexing always creates views&quot;, &quot;advanced ... always creates copies&quot;, &lt;code&gt;.base&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/user/quickstart.html&quot;&gt;NumPy — Quickstart (공식 문서)&lt;/a&gt; : 팬시/불리언 인덱싱 예제, &lt;code&gt;a[[0,0,2]] += 1&lt;/code&gt; 동작&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#&quot;&gt;「NumPy는 왜 빠른가 — ndarray 메모리 모델부터 einsum까지」&lt;/a&gt; (이전 글) : strides로 view가 가능한 이유, 브로드캐스팅 규칙&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/125</guid>
      <comments>https://hskywith.tistory.com/125#entry125comment</comments>
      <pubDate>Tue, 1 Sep 2026 14:57:54 +0900</pubDate>
    </item>
    <item>
      <title>데이터 전처리, 왜 모델보다 먼저인가 &amp;mdash; sklearn으로 정리하는 ML 전처리 파이프라인</title>
      <link>https://hskywith.tistory.com/124</link>
      <description>&lt;!-- title: 데이터 전처리, 왜 모델보다 먼저인가 — sklearn으로 정리하는 ML 전처리 파이프라인 | date: 2026-09-01 | tags: 머신러닝, 데이터전처리, scikit-learn, 데이터누수, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 {{CONTENT}}만 붙여넣는다. --&gt;

&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;이번 강의자료는 「기계학습」 개론이었는데요, 앞부분에서 &quot;기계가 데이터로부터 규칙을 학습한다&quot;는 정의와 지도·비지도·강화 학습 분류를 훑고, 뒷부분(53쪽 이후)에서 &lt;b&gt;2장. 전처리의 중요성&lt;/b&gt;으로 넘어갑니다. 저는 이 뒷부분에 꽂혔습니다. 모델 알고리즘은 한 줄이면 바꾸는데, 정작 성능을 좌우하는 건 그 앞단의 데이터 손질이더라고요.&lt;/p&gt;

&lt;p&gt;강의는 인코딩·결측치 처리·스케일링·차원 축소·특성 선택을 쭉 나열하고 마지막에 scikit-learn 파이프라인으로 닫습니다. 그런데 슬라이드만 보면 &quot;이런 도구들이 있다&quot;는 카탈로그처럼 읽혀요. 이 글에서는 그 도구들을 &lt;b&gt;실제 데이터가 모델에 들어가기까지의 순서&lt;/b&gt;로 다시 엮고, 강의가 개념 그림으로 보여준 것들을 sklearn 공식 API·예제와 하나씩 맞춰보겠습니다. 강의자료엔 실행 코드가 없어서, &quot;강의는 이 개념을 이렇게 그림으로 설명했다 ↔ 공식 문서/실무에선 이 코드로 한다&quot;로 대조합니다.&lt;/p&gt;

&lt;div class=&quot;toc&quot;&gt;
  &lt;div class=&quot;t&quot;&gt;이 글의 순서&lt;/div&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;#s1&quot;&gt;큰 그림 — 전처리가 왜 모델보다 먼저인가&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s2&quot;&gt;Encoding — 글자를 숫자로&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s3&quot;&gt;Impute — 빈칸 메우기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s4&quot;&gt;Feature Scaling — 눈금 맞추기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s5&quot;&gt;차원 축소 — 열이 너무 많을 때&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s6&quot;&gt;Feature Selection — 쓸모없는 열 골라내기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s7&quot;&gt;Pipeline — 순서를 통째로 묶고 데이터 누수 막기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s8&quot;&gt;치트시트 &amp;amp; 핵심 6가지&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;

&lt;h2 id=&quot;s1&quot;&gt;1. 큰 그림 — 전처리가 왜 모델보다 먼저인가&lt;/h2&gt;

&lt;p&gt;강의 55쪽에 &lt;b&gt;Raw data vs Featured Data&lt;/b&gt; 비교표가 나옵니다. 원본 데이터를 그대로 쓰면 &quot;정보 손실이 없다&quot;는 장점이 있지만, 차원의 저주·높은 계산비용·큰 저장공간이라는 엄지척 반대 방향의 대가가 따라붙죠. 반대로 전처리해서 특성을 다듬으면(Featured) 약간의 정보 손실을 감수하는 대신 차원이 줄고 학습이 빨라집니다. 전처리는 결국 이 트레이드오프(trade-off, 하나 얻으면 하나 잃는 관계)를 우리 문제에 맞게 조율하는 작업입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Feature (특성/피처)&lt;/code&gt; (한국어 의미): 모델의 입력이 되는 하나의 열(변수) — 예: 붓꽃 데이터의 꽃잎 길이, 꽃잎 너비. 강의 41쪽 Iris 데이터로 치면 &lt;code&gt;sepal_length&lt;/code&gt;, &lt;code&gt;petal_width&lt;/code&gt; 같은 각 칸이 특성이고, &lt;code&gt;class&lt;/code&gt;가 정답(레이블)입니다.&lt;/p&gt;

&lt;p&gt;강의 56쪽은 한 발 더 나갑니다. 전통적인 머신러닝은 사람이 &lt;b&gt;Feature extraction(특성 추출)&lt;/b&gt;을 손으로 해준 뒤 분류기에 넘기지만, 딥러닝은 특성 추출까지 신경망이 알아서 합니다. 그래서 &quot;전처리를 얼마나 사람이 하느냐&quot;는 곧 &quot;머신러닝이냐 딥러닝이냐&quot;를 가르는 축이기도 해요. 이 글에서 다루는 건 앞쪽, 즉 &lt;b&gt;사람이 명시적으로 하는 전처리&lt;/b&gt;입니다.&lt;/p&gt;

&lt;p&gt;아래는 원본 표(raw table) 한 장이 모델에 들어갈 수 있는 행렬이 되기까지 거치는 관문들입니다. 이후 섹션이 각 칸을 하나씩 채웁니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;원본 데이터&lt;/b&gt; 를 모델이 먹을 수 있게 손질하기&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼ 데이터가 흐르는 순서&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;글자를 숫자로?&lt;/span&gt;Encoding&lt;span class=&quot;s&quot;&gt;OneHotEncoder&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;빈칸은?&lt;/span&gt;Impute&lt;span class=&quot;s&quot;&gt;SimpleImputer&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;눈금이 제각각?&lt;/span&gt;Scaling&lt;span class=&quot;s&quot;&gt;StandardScaler&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;열이 너무 많다&lt;/span&gt;차원 축소&lt;span class=&quot;s&quot;&gt;PCA&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;쓸모없는 열?&lt;/span&gt;Feature Selection&lt;span class=&quot;s&quot;&gt;SelectKBest / RFE&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;매번 반복?&lt;/span&gt;Pipeline&lt;span class=&quot;s&quot;&gt;make_pipeline&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;마지막 Pipeline이 앞의 다섯 단계를 하나로 묶고, 동시에 &quot;데이터 누수&quot;를 막아줍니다.&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 실무에서 캐글이든 회사 프로젝트든, 새 데이터셋을 받으면 제일 먼저 하는 게 이 관문 점검입니다. &quot;이 열은 범주형인가 수치형인가, 결측치는 몇 %인가, 스케일 차이가 큰가&quot;를 훑는 것만으로 모델 선택과 전처리 계획의 절반이 정해져요.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 먼저 가장 앞 관문, 글자를 숫자로 바꾸는 Encoding부터 봅니다.&lt;/p&gt;

&lt;h2 id=&quot;s2&quot;&gt;2. Encoding — 글자를 숫자로&lt;/h2&gt;

&lt;p&gt;모델은 &lt;code&gt;&quot;green&quot;&lt;/code&gt;이나 &lt;code&gt;&quot;XL&quot;&lt;/code&gt; 같은 문자열을 곧바로 계산하지 못합니다. 그래서 범주형 값을 숫자로 바꿔야 하는데, 이때 &quot;순서가 있는 범주냐 없는 범주냐&quot;에 따라 방법이 갈립니다. 강의 57쪽이 정확히 이 두 갈래를 보여줍니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;One-Hot Encoding (원핫 인코딩)&lt;/code&gt; (한국어 의미): 순서 없는 범주(nominal)를 값의 개수만큼의 0/1 열로 펼치는 방식 — 예: 색(green/red/blue) → 열 3개, 해당 색만 1. &lt;code&gt;Ordinal Encoding (순서형 인코딩)&lt;/code&gt; (한국어 의미): 순서 있는 범주(ordinal)를 크기를 보존하는 정수로 매핑 — 예: 사이즈 M/L/XL → 1/2/3.&lt;/p&gt;

&lt;p&gt;직관은 이렇습니다. 색깔에 &quot;green=1, red=2, blue=3&quot;처럼 번호를 매기면 모델은 &quot;blue가 green의 3배&quot;라는 &lt;em&gt;없던 순서&lt;/em&gt;를 학습해버립니다. 그래서 순서가 없는 건 원핫으로 펼쳐 서로 대등하게 두고, 진짜 순서가 있는 사이즈는 정수로 눌러도 됩니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; scikit-learn은 원핫을 &quot;각 범주형 특성을 &lt;i&gt;n_categories&lt;/i&gt;개의 이진 특성으로 변환하되 그중 하나만 1&quot;이라고 정의합니다 &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/modules/preprocessing.html&quot;&gt;sklearn User Guide 6.3&lt;/a&gt;)&lt;/span&gt;. 순서형은 &lt;code&gt;OrdinalEncoder&lt;/code&gt;로 &quot;각 범주형 특성을 0~(n_categories-1)의 정수 하나로&quot; 바꿉니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — OneHotEncoder (순서 없는 범주)&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; preprocessing
enc = preprocessing.&lt;span class=&quot;fn&quot;&gt;OneHotEncoder&lt;/span&gt;()
X = [[&lt;span class=&quot;st&quot;&gt;'male'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'from US'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'uses Safari'&lt;/span&gt;],
     [&lt;span class=&quot;st&quot;&gt;'female'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'from Europe'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'uses Firefox'&lt;/span&gt;]]
enc.&lt;span class=&quot;fn&quot;&gt;fit&lt;/span&gt;(X)
enc.&lt;span class=&quot;fn&quot;&gt;transform&lt;/span&gt;([[&lt;span class=&quot;st&quot;&gt;'female'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'from US'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'uses Safari'&lt;/span&gt;]]).&lt;span class=&quot;fn&quot;&gt;toarray&lt;/span&gt;()
&lt;span class=&quot;cm&quot;&gt;# array([[1., 0., 0., 1., 0., 1.]])  ← 3개 특성이 6개 0/1 열로 펼쳐짐&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — OrdinalEncoder (순서 있는 범주)&lt;/span&gt;
enc = preprocessing.&lt;span class=&quot;fn&quot;&gt;OrdinalEncoder&lt;/span&gt;()
enc.&lt;span class=&quot;fn&quot;&gt;fit&lt;/span&gt;(X)
enc.&lt;span class=&quot;fn&quot;&gt;transform&lt;/span&gt;([[&lt;span class=&quot;st&quot;&gt;'female'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'from US'&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;'uses Safari'&lt;/span&gt;]])
&lt;span class=&quot;cm&quot;&gt;# array([[0., 1., 1.]])  ← 범주마다 정수 하나&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;강의 슬라이드에서는 이렇게 봤습니다.&lt;/b&gt; color(green/red/blue)를 &lt;code&gt;(1,0,0) (0,1,0) (0,0,1)&lt;/code&gt;로, size(M/L/XL)를 &lt;code&gt;1/2/3&lt;/code&gt;으로 나눠 그렸죠. 개념은 위 공식 API와 정확히 같습니다. 다만 슬라이드 제목이 &quot;Label Encoding&quot;으로 붙어 있는데, 여기에 함정이 하나 있습니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;강의 슬라이드(57쪽)&lt;/th&gt;&lt;th&gt;sklearn 공식&lt;/th&gt;&lt;th&gt;차이 · 이유&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;순서 없는 범주&lt;/td&gt;&lt;td&gt;color → (1,0,0)…&lt;/td&gt;&lt;td&gt;&lt;code&gt;OneHotEncoder&lt;/code&gt;&lt;/td&gt;&lt;td&gt;동일 개념. 공식은 희소행렬(sparse)로 반환해 메모리 절약&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;순서 있는 범주&lt;/td&gt;&lt;td&gt;size M/L/XL → 1/2/3&lt;/td&gt;&lt;td&gt;&lt;code&gt;OrdinalEncoder&lt;/code&gt;&lt;/td&gt;&lt;td&gt;동일 개념. 강의는 &quot;Label Encoding&quot;이라 불렀지만 실제로는 순서형 인코딩&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;정답(y) 인코딩&lt;/td&gt;&lt;td&gt;(명시 안 함)&lt;/td&gt;&lt;td&gt;&lt;code&gt;LabelEncoder&lt;/code&gt;&lt;/td&gt;&lt;td&gt;공식은 이건 &lt;b&gt;타깃 y 전용&lt;/b&gt;이라 못 박음&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box warn&quot;&gt;⚠️ &lt;b&gt;흔한 실수&lt;/b&gt; — &quot;Label Encoding&quot;이라는 이름 때문에 입력 특성 X에 &lt;code&gt;LabelEncoder&lt;/code&gt;를 쓰는 경우가 많은데요, 공식 문서는 딱 잘라 말합니다. &lt;span class=&quot;src&quot;&gt;&quot;This transformer should be used to encode target values, &lt;i&gt;i.e.&lt;/i&gt; y, and not the input X.&quot;&lt;/span&gt; &lt;span class=&quot;src&quot;&gt;(&lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html&quot;&gt;LabelEncoder 문서&lt;/a&gt;)&lt;/span&gt; 특성에 순서를 넣고 싶으면 &lt;code&gt;OrdinalEncoder&lt;/code&gt;, 정답 라벨(예: setosa/versicolor/virginica)을 정수로 바꿀 때만 &lt;code&gt;LabelEncoder&lt;/code&gt;입니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 성별·지역·상품카테고리처럼 순서 없는 범주는 원핫, 학점(A/B/C)·설문 만족도(하/중/상)·옷 사이즈처럼 순서 있는 범주는 순서형 인코딩. 트리 기반 모델(랜덤포레스트·XGBoost)은 순서형만으로도 잘 도는 반면, 선형·거리 기반 모델은 순서 없는 범주에 반드시 원핫이 필요합니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 인코딩으로 글자를 숫자로 바꿨으면, 이제 그 표의 빈칸을 메울 차례입니다.&lt;/p&gt;

&lt;h2 id=&quot;s3&quot;&gt;3. Impute — 빈칸 메우기&lt;/h2&gt;

&lt;p&gt;현실 데이터엔 빈칸이 널려 있습니다. 강의 58쪽은 결측치(missing value) 처리를 &lt;b&gt;삭제&lt;/b&gt;와 &lt;b&gt;대체(Imputation)&lt;/b&gt; 둘로 나누고, 대체 방식으로 더미·평균·빈도·회귀 대체를 나열합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Imputation (대체/보간)&lt;/code&gt; (한국어 의미): 누락된 값을 통계량이나 예측값으로 채워 넣는 것 — 예: 나이 열의 빈칸을 나머지 나이의 평균으로 채우기. 삭제는 간단하지만 행이 통째로 날아가면 소중한 데이터가 줄어드니, 결측이 적당할 땐 대체가 기본 선택입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; &lt;code&gt;SimpleImputer&lt;/code&gt;를 &quot;평균·중앙값·최빈값 같은 기술통계량으로, 또는 상수로 열마다 결측을 채우는 단변량 대체기&quot;로 정의합니다 &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html&quot;&gt;SimpleImputer 문서&lt;/a&gt;)&lt;/span&gt;. &lt;code&gt;strategy&lt;/code&gt; 인자로 &lt;code&gt;mean&lt;/code&gt;(수치 평균)·&lt;code&gt;median&lt;/code&gt;(중앙값)·&lt;code&gt;most_frequent&lt;/code&gt;(최빈값, 문자열도 가능)·&lt;code&gt;constant&lt;/code&gt;(고정값)를 고릅니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — 평균 대체&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; numpy &lt;span class=&quot;kw&quot;&gt;as&lt;/span&gt; np
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.impute &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; SimpleImputer
imp = &lt;span class=&quot;fn&quot;&gt;SimpleImputer&lt;/span&gt;(missing_values=np.nan, strategy=&lt;span class=&quot;st&quot;&gt;'mean'&lt;/span&gt;)
imp.&lt;span class=&quot;fn&quot;&gt;fit&lt;/span&gt;([[&lt;span class=&quot;nu&quot;&gt;7&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;, np.nan, &lt;span class=&quot;nu&quot;&gt;6&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;9&lt;/span&gt;]])
X = [[np.nan, &lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;, np.nan, &lt;span class=&quot;nu&quot;&gt;6&lt;/span&gt;], [&lt;span class=&quot;nu&quot;&gt;10&lt;/span&gt;, np.nan, &lt;span class=&quot;nu&quot;&gt;9&lt;/span&gt;]]
&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(imp.&lt;span class=&quot;fn&quot;&gt;transform&lt;/span&gt;(X))
&lt;span class=&quot;cm&quot;&gt;# [[ 7.   2.   3. ]   ← 1열 빈칸 = (7+4+10)/3 = 7&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;#  [ 4.   3.5  6. ]   ← 2열 빈칸 = (2+5)/2 = 3.5&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;#  [10.   3.5  9. ]]&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;강의 58쪽 용어&lt;/th&gt;&lt;th&gt;sklearn &lt;code&gt;strategy&lt;/code&gt;&lt;/th&gt;&lt;th&gt;언제&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;평균 대체&lt;/td&gt;&lt;td&gt;&lt;code&gt;mean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;수치형, 이상치 적을 때&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;(중앙값)&lt;/td&gt;&lt;td&gt;&lt;code&gt;median&lt;/code&gt;&lt;/td&gt;&lt;td&gt;수치형, 이상치 많을 때(더 안전)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;빈도 대체&lt;/td&gt;&lt;td&gt;&lt;code&gt;most_frequent&lt;/code&gt;&lt;/td&gt;&lt;td&gt;범주형/최빈값&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;더미 대체&lt;/td&gt;&lt;td&gt;&lt;code&gt;constant&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&quot;없음&quot;을 명시적 값(0, &quot;Unknown&quot;)으로&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;회귀 대체&lt;/td&gt;&lt;td&gt;&lt;code&gt;IterativeImputer&lt;/code&gt;*&lt;/td&gt;&lt;td&gt;다른 열로 예측해 채움(고급)&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p class=&quot;src&quot;&gt;* 강의의 &quot;회귀 대체&quot;는 &lt;code&gt;SimpleImputer&lt;/code&gt;가 아니라 sklearn의 &lt;code&gt;IterativeImputer&lt;/code&gt;(다변량 대체)에 해당합니다.&lt;/p&gt;

&lt;div class=&quot;box warn&quot;&gt;⚠️ &lt;b&gt;핵심 함정&lt;/b&gt; — 평균값을 &lt;b&gt;전체 데이터로 계산하면 안 됩니다.&lt;/b&gt; 반드시 학습(train) 데이터의 평균으로만 채워야 해요. 테스트 데이터의 값이 평균 계산에 섞이면, 아직 보지 못했어야 할 정보가 새어 들어옵니다(데이터 누수). 이 문제는 7번 섹션에서 정면으로 다룹니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 설문·의료·센서 데이터처럼 결측이 흔한 도메인의 필수 단계. 실무 팁 하나: 결측 여부 자체가 신호일 때가 많아서, 채우기 전에 &quot;이 값이 비어 있었다&quot;는 이진 플래그 열(&lt;code&gt;was_missing&lt;/code&gt;)을 하나 더 만들어두면 성능이 오르기도 합니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 빈칸까지 채웠으면, 이제 열마다 제각각인 눈금(스케일)을 맞춥니다.&lt;/p&gt;

&lt;h2 id=&quot;s4&quot;&gt;4. Feature Scaling — 눈금 맞추기&lt;/h2&gt;

&lt;p&gt;키(150~190cm)와 연봉(3천만~1억)을 한 모델에 넣으면, 숫자가 큰 연봉이 거리 계산을 지배해버립니다. 강의 59쪽이 바로 이 &lt;b&gt;Feature Scaling&lt;/b&gt;을 표준화 공식과 함께 제시하고, 60쪽에서 스케일러 4종을 산점도로 비교합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Standardization (표준화)&lt;/code&gt; (한국어 의미): 각 특성을 평균 0, 분산 1로 맞추는 변환. 공식은 강의 59쪽 그대로 &lt;code&gt;z = (x − μ) / σ&lt;/code&gt;입니다(μ는 평균, σ는 표준편차).&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; &quot;표준화는 많은 추정기의 공통 요구사항이며, 특성들이 &lt;i&gt;평균 0, 단위 분산&lt;/i&gt;의 표준정규분포처럼 보이지 않으면 모델이 나쁘게 동작할 수 있다&quot;고 말합니다 &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/modules/preprocessing.html&quot;&gt;sklearn User Guide 6.3&lt;/a&gt;)&lt;/span&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — StandardScaler&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.preprocessing &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; StandardScaler
scaler = &lt;span class=&quot;fn&quot;&gt;StandardScaler&lt;/span&gt;().&lt;span class=&quot;fn&quot;&gt;fit&lt;/span&gt;(X_train)
scaler.mean_       &lt;span class=&quot;cm&quot;&gt;# array([1., 0., 0.33])  ← 학습 데이터의 평균 μ&lt;/span&gt;
scaler.scale_      &lt;span class=&quot;cm&quot;&gt;# array([0.81, 0.81, 1.24]) ← 표준편차 σ&lt;/span&gt;
X_scaled = scaler.&lt;span class=&quot;fn&quot;&gt;transform&lt;/span&gt;(X_train)
X_scaled.&lt;span class=&quot;fn&quot;&gt;mean&lt;/span&gt;(axis=&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;)  &lt;span class=&quot;cm&quot;&gt;# array([0., 0., 0.]) → 평균 0&lt;/span&gt;
X_scaled.&lt;span class=&quot;fn&quot;&gt;std&lt;/span&gt;(axis=&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;)   &lt;span class=&quot;cm&quot;&gt;# array([1., 1., 1.]) → 분산 1&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;강의 슬라이드에서는 이렇게 봤습니다.&lt;/b&gt; 59쪽 공식 옆에 아주 중요한 붉은 메모가 달려 있습니다 — &lt;span class=&quot;src&quot;&gt;&quot;use same parameters for the test/new data!&quot;&lt;/span&gt; 즉 학습 데이터에서 구한 &lt;code&gt;mean_&lt;/code&gt;·&lt;code&gt;scale_&lt;/code&gt;를 테스트 데이터에도 &lt;b&gt;그대로&lt;/b&gt; 써야 한다는 뜻이죠. 이게 &lt;code&gt;fit&lt;/code&gt;은 train에만, &lt;code&gt;transform&lt;/code&gt;은 양쪽에 적용하는 sklearn의 패턴과 정확히 일치합니다.&lt;/p&gt;

&lt;p&gt;60쪽 산점도 4종은 스케일러의 성격 차이를 보여줍니다. 공식 문서의 정의로 정리하면:&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;스케일러&lt;/th&gt;&lt;th&gt;하는 일&lt;/th&gt;&lt;th&gt;공식 설명 요지&lt;/th&gt;&lt;th&gt;언제&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;&lt;code&gt;StandardScaler&lt;/code&gt;&lt;/td&gt;&lt;td&gt;평균 0, 분산 1&lt;/td&gt;&lt;td&gt;표준정규분포 형태로&lt;/td&gt;&lt;td&gt;기본값. 대부분 여기서 시작&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;code&gt;MinMaxScaler&lt;/code&gt;&lt;/td&gt;&lt;td&gt;[0, 1] 범위로&lt;/td&gt;&lt;td&gt;최소·최대로 눌러 담기&lt;/td&gt;&lt;td&gt;범위가 정해져야 할 때(이미지 픽셀 등)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;code&gt;RobustScaler&lt;/code&gt;&lt;/td&gt;&lt;td&gt;중앙값·IQR 기준&lt;/td&gt;&lt;td&gt;&quot;이상치가 많으면 평균·분산은 잘 안 통한다&quot;&lt;/td&gt;&lt;td&gt;이상치가 많을 때&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;code&gt;Normalizer&lt;/code&gt;&lt;/td&gt;&lt;td&gt;샘플(행)을 단위 벡터로&lt;/td&gt;&lt;td&gt;&quot;개별 &lt;b&gt;샘플&lt;/b&gt;을 단위 노름으로&quot;&lt;/td&gt;&lt;td&gt;행 방향 정규화(텍스트 TF-IDF 등)&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;code&gt;Normalizer&lt;/code&gt;만 방향이 다릅니다. 나머지 셋은 &lt;b&gt;열(특성)&lt;/b&gt; 단위로 스케일을 맞추지만, &lt;code&gt;Normalizer&lt;/code&gt;는 &lt;b&gt;행(샘플)&lt;/b&gt; 하나하나를 길이 1인 벡터로 만듭니다. 60쪽 산점도에서 Normalizer만 점들이 원호 위에 몰려 보이는 이유가 이겁니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — k-NN·SVM·로지스틱 회귀·신경망처럼 거리나 경사하강법을 쓰는 모델엔 스케일링이 사실상 필수입니다(강의 59쪽도 &quot;gradient descent 수렴이 빨라진다&quot;고 언급). 반대로 트리 계열은 스케일에 둔감해서 생략해도 됩니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 여기까지가 &quot;값을 다듬는&quot; 전처리였다면, 이제부터는 &quot;열의 개수를 줄이는&quot; 전처리입니다.&lt;/p&gt;

&lt;h2 id=&quot;s5&quot;&gt;5. 차원 축소 — 열이 너무 많을 때&lt;/h2&gt;

&lt;p&gt;강의 앞부분 49~50쪽에서 &lt;b&gt;차원의 저주&lt;/b&gt;를 짚었던 게 여기서 회수됩니다. 차원(특성 수)이 늘수록 같은 공간을 채우는 데 필요한 데이터가 기하급수로 늘어나고, 데이터가 부족하면 과적합(overfitting)에 빠지죠. 해결책으로 강의가 든 게 차원 축소와 특성 선택인데, 먼저 차원 축소입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PCA (주성분 분석)&lt;/code&gt; (한국어 의미): 데이터의 분산을 가장 많이 담는 새 축을 찾아 그 축으로 데이터를 투영해 차원을 줄이는 기법 — 예: 13개 열을 2개 축으로 압축해 산점도로 그리기. &lt;code&gt;LDA (선형판별분석)&lt;/code&gt;는 클래스를 가장 잘 구분하는 축을 찾는다는 점이 다릅니다(강의 61쪽에 둘 다 등장).&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; PCA를 &quot;특이값 분해(SVD)를 이용한 선형 차원 축소로, 데이터를 더 낮은 차원 공간으로 투영한다&quot;고 정의합니다 &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html&quot;&gt;PCA 문서&lt;/a&gt;)&lt;/span&gt;. 각 축이 원본 분산의 몇 %를 설명하는지는 &lt;code&gt;explained_variance_ratio_&lt;/code&gt;로 확인합니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 공식 예제 — PCA&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.decomposition &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; PCA
pca = &lt;span class=&quot;fn&quot;&gt;PCA&lt;/span&gt;(n_components=&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;)
X_transformed = pca.&lt;span class=&quot;fn&quot;&gt;fit_transform&lt;/span&gt;(X)
&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(pca.explained_variance_ratio_)
&lt;span class=&quot;cm&quot;&gt;# [0.9924 0.0075] ← 첫 축이 분산의 99.24%를 설명 → 2차원으로 줄여도 정보 대부분 보존&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;강의 61쪽은 차원 축소를 &quot;왜 하나&quot;로 세 가지를 듭니다 — &lt;b&gt;시각화·해석 가능성&lt;/b&gt;(2·3차원으로 줄이면 눈으로 봄), &lt;b&gt;저장·속도&lt;/b&gt;, &lt;b&gt;예측 성능&lt;/b&gt;. 위 &lt;code&gt;explained_variance_ratio_&lt;/code&gt;가 딱 첫 번째 이유를 뒷받침합니다. 99%가 한 축에 담기면 2D 그림 한 장으로 데이터 구조가 거의 다 보이니까요. 강의자료엔 실행 코드가 없어 이 예제가 곧 공식 기준이 됩니다.&lt;/p&gt;

&lt;div class=&quot;box warn&quot;&gt;⚠️ PCA도 스케일에 민감합니다. 분산이 큰 축을 찾는 방식이라, 스케일이 큰 열이 주성분을 독차지해버려요. 그래서 &lt;b&gt;StandardScaler → PCA&lt;/b&gt; 순서가 정석입니다. (공식도 &quot;입력을 센터링은 하지만 스케일링은 하지 않는다&quot;고 명시 — 스케일링은 우리 몫)&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 수백 차원 임베딩을 2D로 찍어 군집을 눈으로 확인하거나, 이미지·유전자처럼 특성이 수천 개인 데이터를 압축해 학습 속도를 올릴 때. 다만 PCA를 거치면 축이 원래 특성의 조합이라 &quot;어느 특성이 중요한지&quot; 해석은 어려워집니다 — 그게 필요하면 다음 섹션의 특성 선택이 답입니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ PCA가 특성을 &quot;합쳐서&quot; 줄인다면, Feature Selection은 특성을 &quot;골라서&quot; 줄입니다.&lt;/p&gt;

&lt;h2 id=&quot;s6&quot;&gt;6. Feature Selection — 쓸모없는 열 골라내기&lt;/h2&gt;

&lt;p&gt;강의 62쪽은 특성 선택을 &lt;b&gt;Filter / Wrapper / Embedded&lt;/b&gt; 세 방법으로 나누고, sklearn &lt;code&gt;feature_selection&lt;/code&gt; 모듈의 &lt;code&gt;SelectKBest&lt;/code&gt;·&lt;code&gt;RFE&lt;/code&gt;·&lt;code&gt;VarianceThreshold&lt;/code&gt;를 소개합니다. 그리고 63·64쪽에서 순차 선택과 RFE의 동작을 그림으로 풀죠. PCA와 달리 &lt;b&gt;원래 특성을 그대로 남기므로 해석이 유지&lt;/b&gt;되는 게 장점입니다.&lt;/p&gt;

&lt;p&gt;세 방법의 직관부터 잡고 갑니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;Filter(필터)&lt;/b&gt;: 모델과 무관하게 통계로 먼저 거른다. 싸고 빠름.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Wrapper(래퍼)&lt;/b&gt;: 실제 모델을 돌려보며 특성 조합을 넣었다 뺐다 한다. 정확하지만 느림.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Embedded(임베디드)&lt;/b&gt;: 모델이 학습하면서 스스로 중요도를 매긴다(예: L1 규제, 트리 중요도).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# ① Filter — VarianceThreshold: 분산 낮은(거의 안 변하는) 열 제거&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;#   &quot;분산이 문턱을 못 넘는 특성을 모두 제거한다&quot;&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.feature_selection &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; VarianceThreshold
sel = &lt;span class=&quot;fn&quot;&gt;VarianceThreshold&lt;/span&gt;(threshold=(.&lt;span class=&quot;nu&quot;&gt;8&lt;/span&gt; * (&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt; - .&lt;span class=&quot;nu&quot;&gt;8&lt;/span&gt;)))
sel.&lt;span class=&quot;fn&quot;&gt;fit_transform&lt;/span&gt;(X)

&lt;span class=&quot;cm&quot;&gt;# ① Filter — SelectKBest: 점수 높은 상위 k개만 남김&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.datasets &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; load_iris
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.feature_selection &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; SelectKBest, f_classif
X, y = &lt;span class=&quot;fn&quot;&gt;load_iris&lt;/span&gt;(return_X_y=&lt;span class=&quot;kw&quot;&gt;True&lt;/span&gt;)     &lt;span class=&quot;cm&quot;&gt;# X.shape (150, 4)&lt;/span&gt;
X_new = &lt;span class=&quot;fn&quot;&gt;SelectKBest&lt;/span&gt;(f_classif, k=&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;fit_transform&lt;/span&gt;(X, y)
X_new.shape                          &lt;span class=&quot;cm&quot;&gt;# (150, 2) ← 4개 중 2개로&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;강의 슬라이드 63·64쪽은 Wrapper 방법을 그림으로 보여줍니다.&lt;/b&gt; 63쪽 &lt;b&gt;Sequential Feature Selection&lt;/b&gt;은 특성을 하나씩 &lt;i&gt;추가&lt;/i&gt;하며 최적 조합을 찾고(&quot;fit model, pick best, repeat&quot;), 64쪽 &lt;b&gt;RFE&lt;/b&gt;는 반대로 전부 넣고 &lt;i&gt;가중치 낮은 것부터 제거&lt;/i&gt;합니다(&quot;remove lowest weight, repeat&quot;). 공식 정의와 나란히 두면 이렇게 맞물립니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;강의 그림&lt;/th&gt;&lt;th&gt;방법 분류&lt;/th&gt;&lt;th&gt;sklearn&lt;/th&gt;&lt;th&gt;공식 정의 요지&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;63쪽: 하나씩 추가&lt;/td&gt;&lt;td&gt;Wrapper&lt;/td&gt;&lt;td&gt;&lt;code&gt;SequentialFeatureSelector&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0개에서 시작해 교차검증 점수를 최대화하는 특성을 탐욕적으로 추가&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;64쪽: 하나씩 제거&lt;/td&gt;&lt;td&gt;Wrapper&lt;/td&gt;&lt;td&gt;&lt;code&gt;RFE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;모델을 학습해 &lt;code&gt;coef_&lt;/code&gt;/중요도가 낮은 특성을 재귀적으로 제거&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;62쪽: 통계 필터&lt;/td&gt;&lt;td&gt;Filter&lt;/td&gt;&lt;td&gt;&lt;code&gt;SelectKBest&lt;/code&gt;&lt;/td&gt;&lt;td&gt;단변량 통계 검정 점수 상위 k개 선택&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;(강의 언급 없음)&lt;/td&gt;&lt;td&gt;Embedded&lt;/td&gt;&lt;td&gt;&lt;code&gt;SelectFromModel&lt;/code&gt;&lt;/td&gt;&lt;td&gt;L1 규제·트리 중요도로 학습 중 자동 선택&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  공식 문서의 한 가지 포인트: &lt;code&gt;SequentialFeatureSelector&lt;/code&gt;는 &lt;code&gt;RFE&lt;/code&gt;·&lt;code&gt;SelectFromModel&lt;/code&gt;과 달리 모델이 &lt;code&gt;coef_&lt;/code&gt;나 &lt;code&gt;feature_importances_&lt;/code&gt;를 내놓지 않아도 됩니다. k-NN처럼 중요도를 못 뽑는 모델에도 쓸 수 있다는 뜻이에요.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 특성이 수백 개인데 &quot;어떤 게 예측에 중요한지&quot; 보고서에 써야 할 때. RFE로 상위 특성을 추리면 모델은 가벼워지고, 남은 특성 목록 자체가 도메인 인사이트가 됩니다. 빠르게 훑을 땐 &lt;code&gt;VarianceThreshold&lt;/code&gt;로 상수 열부터 날리는 게 첫 수입니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 지금까지 관문을 다섯 개 지났습니다. 문제는, 이걸 매번 순서대로 train·test에 따로 적용하다 보면 실수가 생긴다는 거예요. 마지막 관문이 이걸 해결합니다.&lt;/p&gt;

&lt;h2 id=&quot;s7&quot;&gt;7. Pipeline — 순서를 통째로 묶고 데이터 누수 막기&lt;/h2&gt;

&lt;p&gt;강의의 마지막 슬라이드(66쪽) &lt;b&gt;Scikit-learn Pipelines&lt;/b&gt;는 지금까지의 이야기를 한 장으로 요약합니다. Scaling → Dimensionality Reduction → Learning Algorithm이 하나의 상자(Pipeline) 안에 들어 있고, 학습 데이터엔 &lt;code&gt;fit &amp;amp; transform&lt;/code&gt;, 테스트 데이터엔 &lt;code&gt;transform&lt;/code&gt;만 적용되는 흐름이 화살표로 그려져 있죠. 이 그림이 왜 중요한지가 이 글의 진짜 결론입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Data Leakage (데이터 누수)&lt;/code&gt; (한국어 의미): 예측 시점엔 알 수 없어야 할 정보가 모델 학습에 새어 들어가, 성능이 실제보다 부풀려지는 현상 — 예: 테스트 데이터까지 포함해 평균을 구한 뒤 스케일링하기.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; &quot;데이터 누수는 예측 시점에 사용할 수 없는 정보가 모델 구축에 쓰일 때 발생하며, 지나치게 낙관적인 성능 추정으로 이어져 실제 신규 데이터에서 성능이 나빠진다&quot;고 정의합니다 &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/common_pitfalls.html&quot;&gt;sklearn Common Pitfalls&lt;/a&gt;)&lt;/span&gt;. 그리고 못 박습니다 — &lt;span class=&quot;src&quot;&gt;&quot;전처리 변환은 오직 학습 데이터로부터만 학습되어야 한다.&quot;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;왜 이게 4번 섹션의 붉은 메모(&quot;use same parameters for test data!&quot;)와 같은 말인지 코드로 보면 명확합니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# ❌ 잘못된 방법 — 전체 데이터로 먼저 변환한 뒤 나눔 (누수 발생)&lt;/span&gt;
X_selected = &lt;span class=&quot;fn&quot;&gt;SelectKBest&lt;/span&gt;(k=&lt;span class=&quot;nu&quot;&gt;25&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;fit_transform&lt;/span&gt;(X, y)   &lt;span class=&quot;cm&quot;&gt;# X 전체를 봄!&lt;/span&gt;
X_train, X_test, y_train, y_test = &lt;span class=&quot;fn&quot;&gt;train_test_split&lt;/span&gt;(X_selected, y, random_state=&lt;span class=&quot;nu&quot;&gt;42&lt;/span&gt;)
&lt;span class=&quot;cm&quot;&gt;# → 정확도 0.76 (부풀려진 값)&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ✅ 올바른 방법 — 먼저 나누고, 학습 데이터로만 fit&lt;/span&gt;
X_train, X_test, y_train, y_test = &lt;span class=&quot;fn&quot;&gt;train_test_split&lt;/span&gt;(X, y, random_state=&lt;span class=&quot;nu&quot;&gt;42&lt;/span&gt;)
select = &lt;span class=&quot;fn&quot;&gt;SelectKBest&lt;/span&gt;(k=&lt;span class=&quot;nu&quot;&gt;25&lt;/span&gt;)
X_train_sel = select.&lt;span class=&quot;fn&quot;&gt;fit_transform&lt;/span&gt;(X_train, y_train)  &lt;span class=&quot;cm&quot;&gt;# train에만 fit&lt;/span&gt;
X_test_sel  = select.&lt;span class=&quot;fn&quot;&gt;transform&lt;/span&gt;(X_test)             &lt;span class=&quot;cm&quot;&gt;# test는 transform만&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# → 정확도 0.50 (무작위 데이터의 정직한 실력)&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;0.76과 0.50. 같은 데이터·같은 모델인데 &lt;b&gt;단지 전처리를 언제 fit 했느냐&lt;/b&gt;로 결과가 이만큼 갈립니다. 강의 59쪽이 스케일링에서 &quot;테스트엔 같은 파라미터를 써라&quot;라고 한 것과, 같은 쪽 Sampling 항목의 &quot;검증셋은 맨 마지막까지 쓰지 마라(overfitting)&quot;가 전부 이 하나의 원칙을 가리키고 있었던 거죠.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;그래서 Pipeline입니다.&lt;/b&gt; 공식 문서는 파이프라인의 세 번째 이점을 &quot;안전성&quot;으로 꼽으며, &quot;같은 샘플로만 변환기와 예측기를 학습시켜, 테스트 데이터의 통계가 새어 드는 걸 막는다&quot;고 설명합니다 &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/modules/compose.html&quot;&gt;sklearn Pipelines&lt;/a&gt;)&lt;/span&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 강의 66쪽 그림을 코드로 — 전처리+모델을 하나로 묶기&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.pipeline &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; make_pipeline
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.preprocessing &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; StandardScaler
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.decomposition &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; PCA
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.linear_model &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; LogisticRegression

pipe = &lt;span class=&quot;fn&quot;&gt;make_pipeline&lt;/span&gt;(&lt;span class=&quot;fn&quot;&gt;StandardScaler&lt;/span&gt;(), &lt;span class=&quot;fn&quot;&gt;PCA&lt;/span&gt;(n_components=&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;), &lt;span class=&quot;fn&quot;&gt;LogisticRegression&lt;/span&gt;())
pipe.&lt;span class=&quot;fn&quot;&gt;fit&lt;/span&gt;(X_train, y_train)   &lt;span class=&quot;cm&quot;&gt;# 스케일·PCA·모델을 train에만 fit — 한 줄로&lt;/span&gt;
pipe.&lt;span class=&quot;fn&quot;&gt;score&lt;/span&gt;(X_test, y_test)  &lt;span class=&quot;cm&quot;&gt;# test엔 자동으로 transform만 적용 (누수 없음)&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;make_pipeline&lt;/code&gt; 한 줄이 스케일링·차원축소·학습을 순서대로 엮고, &lt;code&gt;fit&lt;/code&gt;은 train에만·&lt;code&gt;transform&lt;/code&gt;은 test에 자동으로 나눠 적용합니다. 실수할 여지 자체를 없애는 거죠. 강의 66쪽 그림에서 왼쪽(train)은 &lt;code&gt;fit &amp;amp; transform&lt;/code&gt;, 오른쪽(test)은 &lt;code&gt;transform&lt;/code&gt;이었던 이유가 바로 이겁니다. 참고로 범주형·수치형 열에 서로 다른 전처리를 적용하려면 &lt;code&gt;ColumnTransformer&lt;/code&gt;를 파이프라인 안에 넣습니다.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 교차검증(cross-validation)이나 하이퍼파라미터 탐색(GridSearchCV)을 할 때는 파이프라인이 사실상 필수입니다. fold를 나눌 때마다 전처리가 그 fold의 train에만 다시 fit 되어야 누수가 없는데, 파이프라인이 이걸 자동으로 처리해주거든요. 손으로 하면 거의 틀립니다.&lt;/p&gt;&lt;/div&gt;

&lt;h2 id=&quot;s8&quot;&gt;치트시트 &amp;amp; 핵심 6가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;상황&lt;/th&gt;&lt;th&gt;도구&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;순서 없는 범주 → 숫자&lt;/td&gt;&lt;td&gt;&lt;code&gt;OneHotEncoder&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;순서 있는 범주 → 숫자&lt;/td&gt;&lt;td&gt;&lt;code&gt;OrdinalEncoder&lt;/code&gt; (정답 y는 &lt;code&gt;LabelEncoder&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;결측치 채우기&lt;/td&gt;&lt;td&gt;&lt;code&gt;SimpleImputer(strategy='mean'|'median'|'most_frequent')&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;스케일 맞추기(기본)&lt;/td&gt;&lt;td&gt;&lt;code&gt;StandardScaler&lt;/code&gt; (이상치 많으면 &lt;code&gt;RobustScaler&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;차원 압축(합치기)&lt;/td&gt;&lt;td&gt;&lt;code&gt;PCA(n_components=k)&lt;/code&gt; — 스케일링 먼저&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;특성 고르기(남기기)&lt;/td&gt;&lt;td&gt;&lt;code&gt;SelectKBest&lt;/code&gt;(빠름) / &lt;code&gt;RFE&lt;/code&gt;(정확)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;전부 묶고 누수 방지&lt;/td&gt;&lt;td&gt;&lt;code&gt;make_pipeline(...)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 6가지만 기억하면 됩니다.&lt;br&gt;
1. &lt;b&gt;글자는 숫자로&lt;/b&gt; — 순서 없으면 One-Hot, 있으면 Ordinal.&lt;br&gt;
2. &lt;b&gt;빈칸은 채우거나 지우거나&lt;/b&gt; — 기본은 평균/중앙값/최빈값 대체.&lt;br&gt;
3. &lt;b&gt;눈금은 맞춘다&lt;/b&gt; — 거리·경사하강 모델엔 표준화가 사실상 필수.&lt;br&gt;
4. &lt;b&gt;차원은 줄인다&lt;/b&gt; — 합치면 PCA, 고르면 Feature Selection.&lt;br&gt;
5. &lt;b&gt;fit은 train에만, transform은 양쪽에&lt;/b&gt; — 이 한 줄이 데이터 누수를 막는다.&lt;br&gt;
6. &lt;b&gt;순서는 Pipeline으로&lt;/b&gt; — 실수할 여지를 코드에서 없앤다.&lt;/div&gt;

&lt;p&gt;강의가 도구들의 카탈로그로 끝났다면, 다시 엮어본 결론은 하나였습니다. 전처리의 진짜 요점은 어떤 스케일러·인코더를 쓰느냐가 아니라, &lt;b&gt;&quot;테스트 데이터의 정보를 학습에 흘리지 않는 것&quot;&lt;/b&gt;이고, scikit-learn Pipeline은 그 원칙을 강제하기 위해 존재한다는 것. 강의 59쪽의 붉은 메모 한 줄이 66쪽의 파이프라인 그림으로 이어지는 이 흐름이, 이 자료 뒷부분 전체를 관통하는 이야기였습니다.&lt;/p&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/preprocessing.html&quot;&gt;scikit-learn User Guide — 6.3 Preprocessing data&lt;/a&gt; (표준화·인코딩·스케일러)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html&quot;&gt;LabelEncoder 문서&lt;/a&gt; (&quot;타깃 y 전용&quot; 명시)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html&quot;&gt;SimpleImputer 문서&lt;/a&gt; (결측치 대체 전략)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html&quot;&gt;PCA 문서&lt;/a&gt; (선형 차원 축소, explained_variance_ratio_)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/feature_selection.html&quot;&gt;scikit-learn User Guide — 1.13 Feature selection&lt;/a&gt; (VarianceThreshold·SelectKBest·RFE·SFS)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/modules/compose.html&quot;&gt;scikit-learn User Guide — 6.1 Pipelines and composite estimators&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/common_pitfalls.html&quot;&gt;scikit-learn — Common Pitfalls (Data leakage)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/124</guid>
      <comments>https://hskywith.tistory.com/124#entry124comment</comments>
      <pubDate>Tue, 1 Sep 2026 14:08:09 +0900</pubDate>
    </item>
    <item>
      <title>Bad Data 제대로 다루기 &amp;mdash; 데이터 클리닝&amp;middot;랭글링부터 ML 데이터 누수까지</title>
      <link>https://hskywith.tistory.com/123</link>
      <description>&lt;!-- title: Bad Data 제대로 다루기 — 데이터 클리닝·랭글링부터 ML 데이터 누수까지 | date: 2026-08-31 | tags: 데이터전처리, 데이터클리닝, 데이터누수, MLOps, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 내용만 붙여넣는다. --&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;

&lt;p&gt;이번 강의자료는 제목부터 &lt;em&gt;bad_data&lt;/em&gt;였습니다. 67장 내내 &quot;데이터는 원래 지저분하다&quot;는 한 문장을 여러 각도에서 못 박는 덱이었는데요. 슬라이드를 넘기다 보면 &lt;code&gt;217&lt;/code&gt;과 &lt;code&gt;2.17&lt;/code&gt;이 뒤섞인 CSV, 전화번호 형식이 제각각인 표, &quot;Isreal&quot;처럼 오타 난 국가명, 심지어 성이 &quot;Null&quot;이라서 시스템에서 사라져 버린 사람 이야기까지 나옵니다. 그러다 후반부에서 갑자기 결이 바뀌어서 &lt;em&gt;데이터 누수(data leakage)&lt;/em&gt;·&lt;em&gt;데이터셋 시프트(dataset shift)&lt;/em&gt; 같은 머신러닝 전용 함정으로 넘어가더라고요.&lt;/p&gt;

&lt;p&gt;즉 이 자료의 진짜 메시지는 &quot;지저분한 데이터를 어떻게 청소하느냐&quot;가 아니라, &lt;b&gt;&quot;에러를 원인부터 이해하고 → 탐지하고 → 예방하고 → 고치는&quot; 하나의 사이클&lt;/b&gt;이고, 그 사이클이 ML 파이프라인에서는 특유의 함정을 하나 더 품는다는 겁니다. 이 글에서는 넓게 흩어진 67장을 그 사이클 순서로 다시 꿰어 정리하려 합니다. 실습 코드 파일은 따로 없었기 때문에, 개념마다 공식 문서가 그걸 어떻게 설명하는지를 근거로 붙였습니다.&lt;/p&gt;

&lt;div class=&quot;toc&quot;&gt;
  &lt;div class=&quot;t&quot;&gt;이 글의 순서&lt;/div&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;#s1&quot;&gt;큰 그림 — 에러의 생애주기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s2&quot;&gt;실무 데이터는 지저분하다 (GIGO)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s3&quot;&gt;전처리 vs 랭글링 vs 클리닝 — 헷갈리는 3형제&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s4&quot;&gt;에러의 원인과 탐지 — 스키마로 걸러내기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s5&quot;&gt;에러 예방 — 애초에 안 새게 만들기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s6&quot;&gt;에러 수정 — 결측·이상치·중복 다루기&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s7&quot;&gt;⭐ ML 특화 함정 — 데이터 누수와 데이터셋 시프트&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#s8&quot;&gt;치트시트 &amp;amp; 핵심 7가지&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;

&lt;h2 id=&quot;s1&quot;&gt;1. 큰 그림 — 에러의 생애주기&lt;/h2&gt;

&lt;p&gt;세부로 들어가기 전에 조각들이 어떻게 맞물리는지부터 잡고 가겠습니다. 강의자료는 순서가 뒤죽박죽처럼 보이지만, 사실 에러 하나가 태어나서 죽을 때까지의 &lt;b&gt;생애주기&lt;/b&gt;를 따라갑니다. 이 지도를 머릿속에 두면 나머지가 &quot;따로 노는 목록&quot;이 아니라 &quot;각 칸을 채우는 이야기&quot;로 읽힙니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;Bad Data&lt;/b&gt; 는 이 흐름으로 다룬다&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;왜 생기나?&lt;/span&gt;사람·기계의 실수&lt;span class=&quot;s&quot;&gt;오타 · 센서 · 직렬화&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 찾나?&lt;/span&gt;도메인 지식 + 스키마&lt;span class=&quot;s&quot;&gt;JSON Schema · Pydantic&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 막나?&lt;/span&gt;포맷·서명·파이프라인&lt;span class=&quot;s&quot;&gt;Parquet · 해시 · ETL&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 고치나?&lt;/span&gt;결측·이상치·중복 처리&lt;span class=&quot;s&quot;&gt;impute · cap · dedup&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;그리고 ML에선 함정이 하나 더 — &lt;b&gt;데이터 누수 · 데이터셋 시프트&lt;/b&gt; (7장)&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;2~3장은 이 문제가 왜 중요한지(GIGO)와 용어 정리, 4~6장은 지도의 &quot;찾기·막기·고치기&quot; 세 칸, 마지막 7장은 AI 엔지니어라면 반드시 짚어야 하는 ML 전용 함정입니다.&lt;/p&gt;

&lt;p class=&quot;next&quot;&gt;→ 먼저 &quot;왜 데이터는 늘 지저분한가&quot;부터 봅니다.&lt;/p&gt;

&lt;h2 id=&quot;s2&quot;&gt;2. 실무 데이터는 지저분하다 (GIGO)&lt;/h2&gt;

&lt;p&gt;강의자료가 가장 먼저 던진 문장은 &lt;b&gt;&quot;Real world data is messy&quot;&lt;/b&gt;였습니다. 교과서 예제 데이터는 깨끗하지만, 실무에서 마주치는 데이터는 예외 없이 어딘가 망가져 있다는 거죠.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Dirty data&lt;/code&gt; (더티 데이터): 값이 틀렸거나 빠졌거나 형식이 어긋난, 그대로 쓰면 분석·학습을 망치는 데이터. 강의에서 정리한 유형을 표로 다시 묶으면 이렇습니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;유형&lt;/th&gt;&lt;th&gt;무슨 뜻&lt;/th&gt;&lt;th&gt;강의 속 예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;bad / invalid&lt;/td&gt;&lt;td&gt;값 자체가 틀림 (오타)&lt;/td&gt;&lt;td&gt;나이 &quot;25&quot;를 &quot;225&quot;로 입력&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;missing&lt;/td&gt;&lt;td&gt;값이 비어 있음&lt;/td&gt;&lt;td&gt;결혼 여부·월급 무응답&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;obsolete&lt;/td&gt;&lt;td&gt;낡아서 현재와 안 맞음&lt;/td&gt;&lt;td&gt;갱신 안 된 주소·전화번호&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;non-standard&lt;/td&gt;&lt;td&gt;표기 규칙이 제각각&lt;/td&gt;&lt;td&gt;&quot;홍길동&quot;을 &quot;홍*동&quot;, &quot;홍**&quot;으로&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;incomplete&lt;/td&gt;&lt;td&gt;일부만 채워짐&lt;/td&gt;&lt;td&gt;&quot;성남시 수정구 복정동&quot;을 &quot;성남시&quot;로만&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;redundant / duplicated&lt;/td&gt;&lt;td&gt;같은 레코드 중복&lt;/td&gt;&lt;td&gt;동일 거래가 두 번 기록&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;inconsistent&lt;/td&gt;&lt;td&gt;서로 모순되는 값&lt;/td&gt;&lt;td&gt;생일은 1990인데 나이는 50&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;noisy / outlier&lt;/td&gt;&lt;td&gt;튀는 값&lt;/td&gt;&lt;td&gt;가격 2.17을 217로 (소수점 유실)&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;왜 이걸 이렇게까지 강조할까요. 슬라이드 중간에 빨간 글씨로 &lt;b&gt;GIGO&lt;/b&gt;가 등장합니다. &lt;code&gt;GIGO&lt;/code&gt; (Garbage In, Garbage Out): 쓰레기를 넣으면 쓰레기가 나온다 — 입력 데이터가 엉망이면 아무리 좋은 모델·알고리즘도 엉망인 결과만 낸다는 컴퓨팅의 오래된 격언입니다. 모델을 아무리 튜닝해도, 학습에 들어간 데이터가 틀렸으면 결과는 틀립니다.&lt;/p&gt;

&lt;p&gt;그래서 강의는 데이터 정제 분야의 유명한 문장을 인용합니다 — 탐색적 데이터 마이닝과 클리닝이 &lt;em&gt;&quot;전체 노력의 80%&quot;&lt;/em&gt;를 차지한다는(T. Dasu &amp;amp; T. Johnson). 좋은 데이터 과학자는 시간 대부분을 &lt;b&gt;데이터를 청소하고 형식 맞추는 데&lt;/b&gt; 쓰고, 나머지는 &quot;쓸 데이터가 없다&quot;고 불평하는 데 쓴다는 슬라이드의 뼈 있는 농담도 같은 얘기고요.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 캐글 노트북과 실무의 결정적 차이가 여기입니다. 실무 프로젝트는 모델링 코드보다 &quot;이 컬럼은 왜 이렇게 더럽지?&quot;를 파헤치는 데이터 이해(EDA) 시간이 훨씬 깁니다. 포트폴리오에 &quot;데이터 X% 정제 → 성능 Y% 개선&quot;을 적을 수 있으면 모델 자랑보다 강력합니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 그런데 &quot;청소&quot;라고 뭉뚱그렸지만, 사실 비슷해 보이는 용어가 세 개나 섞여 있습니다.&lt;/p&gt;

&lt;h2 id=&quot;s3&quot;&gt;3. 전처리 vs 랭글링 vs 클리닝 — 헷갈리는 3형제&lt;/h2&gt;

&lt;p&gt;강의자료가 유독 여러 장을 할애한 지점이 이 용어 정리였습니다. 실무에서 자주 섞어 쓰지만 뉘앙스가 다릅니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Data Cleaning&lt;/code&gt; (데이터 클리닝): 손상되거나 부정확한 레코드를 &lt;b&gt;탐지하고 제거·수정&lt;/b&gt;하는 과정 — 위 3형제 중 가장 좁고 구체적인 작업입니다. (data cleansing, data scrubbing과 거의 같은 말)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Data Wrangling&lt;/code&gt; (데이터 랭글링, = munging): 원시 데이터를 분석하기 좋은 형태로 &lt;b&gt;변환·매핑&lt;/b&gt;하는 기술. 클리닝을 포함하는 더 넓은 &quot;데이터를 요리하는&quot; 전 과정입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Data Preprocessing&lt;/code&gt; (데이터 전처리): 데이터 소스에서 막 꺼낸 직후 하는 준비 작업.&lt;/p&gt;

&lt;p&gt;이 셋의 핵심 구분선은 강의의 &quot;Data preprocessing vs wrangling&quot; 슬라이드에 있었습니다. 제 언어로 다시 쓰면:&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;전처리(Preprocessing)&lt;/th&gt;&lt;th&gt;랭글링(Wrangling)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;시점&lt;/td&gt;&lt;td&gt;분석 &lt;b&gt;시작 전&lt;/b&gt;, 소스에서 꺼낸 직후&lt;/td&gt;&lt;td&gt;분석·모델링 &lt;b&gt;도중&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;실행 횟수&lt;/td&gt;&lt;td&gt;보통 &lt;b&gt;한 번&lt;/b&gt; (once)&lt;/td&gt;&lt;td&gt;&lt;b&gt;반복적&lt;/b&gt; (iteratively)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;주체&lt;/td&gt;&lt;td&gt;개발자·데이터 과학자&lt;/td&gt;&lt;td&gt;데이터 과학자·비즈니스 분석가&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;목적&lt;/td&gt;&lt;td&gt;초기 변환·집계·클리닝&lt;/td&gt;&lt;td&gt;인사이트 찾을 때까지 데이터 모양 바꾸기&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;비유하자면 전처리는 &lt;em&gt;장 보고 재료를 다듬는 단계&lt;/em&gt;(한 번 하면 끝), 랭글링은 &lt;em&gt;요리하면서 간을 보고 계속 고치는 단계&lt;/em&gt;(맛있어질 때까지 반복)입니다. 그리고 강의는 전체를 이렇게 요약했죠 — &lt;b&gt;Data Preparation = Data Cleansing + Feature Engineering&lt;/b&gt;. 즉 데이터 준비란 &quot;청소&quot;와 &quot;특징 만들기&quot;를 합친 것이고, 그 안에 샘플링·데이터 분할·정규화·비닝·결측 대치(imputation) 같은 세부 작업이 다 들어갑니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Imputation&lt;/code&gt; (대치): 통계적 알고리즘으로 빠진 값을 채워 넣는 것 — 뒤 6장에서 다시 나옵니다.&lt;/p&gt;

&lt;div class=&quot;box note&quot;&gt;&lt;b&gt;  헷갈릴 때&lt;/b&gt; — &quot;좁다→넓다&quot; 순서로 기억하세요. &lt;b&gt;Cleaning&lt;/b&gt;(오류 수정) ⊂ &lt;b&gt;Wrangling&lt;/b&gt;(요리 전 과정) 이고, &lt;b&gt;Preprocessing&lt;/b&gt;은 &quot;언제/몇 번 하느냐&quot;로 랭글링과 갈립니다. 전처리=한 번, 랭글링=반복.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 용어를 정리했으니, 이제 지도의 첫 칸 &quot;에러를 어떻게 찾나&quot;로 갑니다.&lt;/p&gt;

&lt;h2 id=&quot;s4&quot;&gt;4. 에러의 원인과 탐지 — 스키마로 걸러내기&lt;/h2&gt;

&lt;p&gt;고치려면 먼저 찾아야 하고, 잘 찾으려면 어디서 생기는지를 알아야 합니다. 강의는 에러의 원인을 딱 둘로 나눴습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;사람(Human)&lt;/b&gt;: 오타, 형식 착오(&quot;Isreal&quot;), 잘못 고른 드롭다운, 규칙 없는 입력. 이메일·성별처럼 &quot;정답 형식&quot;을 딱 정하기 어려운 필드에서 특히 많습니다. (성이 &quot;Null&quot;이라 DB에서 실종된 Mr. Null 사례가 압권이었죠.)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;기계(Machine)&lt;/b&gt;: 계산 코드의 버그(예: BMI 산출식 오류 → 그래서 &lt;em&gt;원본 raw 데이터를 꼭 저장하라&lt;/em&gt;), 고장 난 센서, 부정확한 시계, 그리고 &lt;b&gt;직렬화(serialization) 에러&lt;/b&gt; — 데이터를 빅엔디안으로 보냈는데 받는 쪽이 리틀엔디안으로 읽는 식의 바이트 순서 불일치.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;탐지 전략도 둘로 제시됐습니다. 하나는 &lt;b&gt;도메인 지식(domain knowledge)&lt;/b&gt; — 예를 들어 위도는 −90~90, 경도는 −180~180을 벗어날 수 없다는 걸 알면 범위 밖 값을 바로 오류로 잡습니다. 다른 하나는 &lt;b&gt;스키마(scheme) + 검증(validation)&lt;/b&gt;입니다. 이쪽이 엔지니어가 자동화하기 좋은 방향이라 강의도 여기에 지면을 많이 썼습니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Schema&lt;/code&gt; (스키마): 데이터가 &quot;어떤 필드를, 어떤 타입으로, 어떤 제약 아래&quot; 가져야 하는지를 미리 선언한 청사진. 실제 데이터가 아니라 데이터의 &lt;em&gt;설계도&lt;/em&gt;입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; JSON Schema는 스스로를 &quot;JSON 문서에 주석을 달고 검증하게 해주는 선언적 언어&quot;라고 정의합니다. 기존 데이터 포맷을 기술하고, 사람·기계가 모두 읽을 수 있는 문서를 제공하며, 자동화된 테스트와 제출 데이터의 품질 보증에 쓰인다고 하고요. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://json-schema.org/&quot;&gt;json-schema.org&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;파이썬 진영에서 이 &quot;스키마로 검증&quot; 아이디어를 가장 널리 구현한 게 &lt;b&gt;Pydantic&lt;/b&gt;입니다. &lt;b&gt;공식 문서 표현&lt;/b&gt;으로는 &quot;파이썬 타입 애너테이션을 이용한 데이터 검증&quot; 라이브러리로, 타입 힌트를 런타임에 강제하고 데이터가 유효하지 않으면 친절한 에러를 낸다고 소개합니다. 검증 코어가 Rust로 작성돼 파이썬에서 가장 빠른 축에 든다는 점도 강조하고요. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://docs.pydantic.dev/latest/&quot;&gt;docs.pydantic.dev&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# Pydantic 스타일: 타입 힌트가 곧 검증 규칙이 된다&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; pydantic &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; BaseModel, EmailStr, field_validator

&lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;Donation&lt;/span&gt;(BaseModel):
    name: str
    country: str
    card_number: str
    amount: int                       &lt;span class=&quot;cm&quot;&gt;# &quot;2O&quot;(알파벳 O) 넣으면 여기서 에러&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;# amount는 양수여야 한다 — 도메인 지식을 코드로&lt;/span&gt;
    @field_validator(&lt;span class=&quot;st&quot;&gt;&quot;amount&quot;&lt;/span&gt;)
    &lt;span class=&quot;kw&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;positive&lt;/span&gt;(cls, v):
        &lt;span class=&quot;kw&quot;&gt;if&lt;/span&gt; v &amp;lt;= &lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;:
            &lt;span class=&quot;kw&quot;&gt;raise&lt;/span&gt; ValueError(&lt;span class=&quot;st&quot;&gt;&quot;amount must be positive&quot;&lt;/span&gt;)
        &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; v

&lt;span class=&quot;cm&quot;&gt;# 잘못된 데이터가 들어오면 조용히 통과하지 않고 즉시 막힌다&lt;/span&gt;
Donation(name=&lt;span class=&quot;st&quot;&gt;&quot;Miki&quot;&lt;/span&gt;, country=&lt;span class=&quot;st&quot;&gt;&quot;Israel&quot;&lt;/span&gt;,
         card_number=&lt;span class=&quot;st&quot;&gt;&quot;1234&quot;&lt;/span&gt;, amount=&lt;span class=&quot;st&quot;&gt;&quot;2O&quot;&lt;/span&gt;)  &lt;span class=&quot;cm&quot;&gt;# ValidationError!&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;강의는 여기에 더해 데이터프레임 전용 검증 도구 &lt;b&gt;pandera&lt;/b&gt;(&quot;dataframe을 위한 통계적 데이터 테스팅 툴킷&quot;, 공식 문서상 pandas·dask·pyspark 스키마를 정의해 런타임 검증), 설정·데이터 검증 언어 &lt;b&gt;CUE&lt;/b&gt;, 직렬화+검증 라이브러리 &lt;b&gt;marshmallow&lt;/b&gt;, 그리고 구조화 데이터 스키마로 &lt;b&gt;Protocol Buffers&lt;/b&gt;·관계형 DB &lt;b&gt;schema&lt;/b&gt;까지 소개했습니다. 공통 아이디어는 하나예요 — &lt;b&gt;&quot;데이터가 파이프라인에 들어오는 입구에서 규칙 위반을 막아라.&quot;&lt;/b&gt;&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — FastAPI로 추론 서버를 만들면 요청 바디를 Pydantic 모델로 받는 순간 입력 검증이 공짜로 됩니다. LLM이 뱉은 JSON을 파싱할 때도 Pydantic 모델로 검증하면 &quot;형식 안 맞는 응답&quot;을 애플리케이션 로직 전에 걸러낼 수 있고요(구조화 출력의 핵심). 학습 데이터 적재 단계엔 pandera로 컬럼 타입·범위를 못 박아 둡니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 찾는 것도 좋지만, 애초에 에러가 안 생기게 막는 게 더 낫겠죠.&lt;/p&gt;

&lt;h2 id=&quot;s5&quot;&gt;5. 에러 예방 — 애초에 안 새게 만들기&lt;/h2&gt;

&lt;p&gt;강의의 &quot;Preventing Errors&quot; 슬라이드는 예방 수단을 한 줄로 나열했습니다 — 직렬화 포맷 · 디지털 서명 · ETL/ELT·파이프라인 · 자동화 · 트랜잭션 · tidy data · 품질 지표. 하나씩 짚습니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;① 직렬화 포맷.&lt;/b&gt; &lt;code&gt;Serialization&lt;/code&gt; (직렬화): 메모리 속 자료구조·객체를, 저장하거나 전송할 수 있는 형태(파일·바이트 스트림)로 바꾸고 나중에 복원하는 과정. 포맷 선택이 곧 에러 예방입니다. 강의의 비교를 정리하면:&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;포맷&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;th&gt;스키마&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;CSV&lt;/td&gt;&lt;td&gt;텍스트뿐, 타입 정보 없음 → &lt;code&gt;217&lt;/code&gt;/&lt;code&gt;2.17&lt;/code&gt; 혼동 취약&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;JSON&lt;/td&gt;&lt;td&gt;사람이 읽기 좋음, 숫자 타입 단순&lt;/td&gt;&lt;td&gt;데이터에 내장 안 됨(외부 스키마 필요)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;Parquet&lt;/td&gt;&lt;td&gt;컬럼 지향, 압축·분석 쿼리에 강함&lt;/td&gt;&lt;td&gt;파일에 타입·스키마 내장&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 Parquet를 이렇게 설명합니다.&lt;/b&gt; &quot;효율적 데이터 저장·조회를 위해 설계된 오픈소스 &lt;em&gt;컬럼 지향&lt;/em&gt; 데이터 파일 포맷&quot;으로, 강력한 압축·인코딩과 향상된 성능으로 대량의 복잡한 데이터를 다룬다고 합니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;parquet.apache.org&lt;/a&gt;)&lt;/span&gt; 강의도 짚었듯 판다스에서 Parquet를 쓰려면 &lt;b&gt;Apache Arrow(PyArrow)&lt;/b&gt;가 필요합니다 — Arrow는 언어 독립적인 인메모리 컬럼 포맷이라 직렬화 오버헤드 없이 데이터를 주고받게 해줍니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# CSV는 타입을 잃지만, Parquet는 스키마를 데이터와 함께 저장한다&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; pandas &lt;span class=&quot;kw&quot;&gt;as&lt;/span&gt; pd
df.to_parquet(&lt;span class=&quot;st&quot;&gt;&quot;sales.parquet&quot;&lt;/span&gt;)      &lt;span class=&quot;cm&quot;&gt;# dtype(int/float/datetime) 보존&lt;/span&gt;
df2 = pd.read_parquet(&lt;span class=&quot;st&quot;&gt;&quot;sales.parquet&quot;&lt;/span&gt;)  &lt;span class=&quot;cm&quot;&gt;# 다시 읽어도 price는 float 그대로&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;② 디지털 서명 / 체크섬.&lt;/b&gt; 강의는 MD5·SHA1·SHA256·CRC32를 나열했습니다. 전송 중 데이터가 깨졌는지 &lt;b&gt;해시값 대조&lt;/b&gt;로 확인하는 거죠. 여기서 실무 주의점 하나 — MD5·SHA1은 &lt;em&gt;무결성 확인용 체크섬&lt;/em&gt;으론 여전히 쓰이지만, &lt;b&gt;보안(비밀번호 해싱·서명)에는 이미 깨진 알고리즘&lt;/b&gt;이라 절대 쓰면 안 됩니다. 비밀번호는 bcrypt·argon2가 정답입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;③ ETL / ELT 와 데이터 파이프라인.&lt;/b&gt; &lt;code&gt;ETL&lt;/code&gt; (Extract-Transform-Load): 여러 소스에서 &lt;b&gt;추출&lt;/b&gt;→&lt;b&gt;변환&lt;/b&gt;→적재. 변환을 먼저 하고 &lt;em&gt;데이터 웨어하우스&lt;/em&gt;에 넣습니다. &lt;code&gt;ELT&lt;/code&gt;는 순서를 바꿔 원본을 먼저 &lt;em&gt;데이터 레이크&lt;/em&gt;에 적재하고 나중에 변환합니다. 강의의 비교표에서 핵심만 뽑으면:&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;기준&lt;/th&gt;&lt;th&gt;ETL&lt;/th&gt;&lt;th&gt;ELT&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;소스 데이터&lt;/td&gt;&lt;td&gt;주로 정형&lt;/td&gt;&lt;td&gt;정형+반정형+비정형&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;데이터 규모&lt;/td&gt;&lt;td&gt;비교적 소량&lt;/td&gt;&lt;td&gt;대량에 적합&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;지연(Latency)&lt;/td&gt;&lt;td&gt;높음(먼저 다 변환)&lt;/td&gt;&lt;td&gt;낮음(최소 처리 후 적재)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;유연성&lt;/td&gt;&lt;td&gt;낮음(초반에 변환 정의)&lt;/td&gt;&lt;td&gt;높음(새 소스 붙이기 쉬움)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;저장 요구&lt;/td&gt;&lt;td&gt;변환분만 저장, 적음&lt;/td&gt;&lt;td&gt;원본까지 저장, 큼&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;강의는 랭글링과 ETL의 차이도 구분했는데요 — 랭글링은 비즈니스 분석가가 &lt;em&gt;새로 생긴 다양한 데이터를 분석용으로 다루는&lt;/em&gt; 쪽이고, ETL은 IT 전문가가 &lt;em&gt;정형 데이터를 중앙 웨어하우스로 옮기는&lt;/em&gt; 쪽입니다. 파이프라인 도구로는 &lt;b&gt;Apache Airflow&lt;/b&gt;(워크플로를 코드로 작성·스케줄·모니터링)를 소개했고요.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;④ 자동화.&lt;/b&gt; 반복되는 정제·빌드 작업을 손으로 하면 사람이 실수합니다. 강의는 파이썬 태스크 실행 도구 &lt;b&gt;Invoke&lt;/b&gt;를 예로 들었습니다. &lt;code&gt;@task&lt;/code&gt;로 셸 작업을 함수처럼 정의해 재현 가능하게 만드는 거죠 — &lt;code&gt;make&lt;/code&gt;/&lt;code&gt;rake&lt;/code&gt;의 파이썬판입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;⑤ Tidy Data.&lt;/b&gt; 예방의 마지막 축은 데이터의 &quot;모양&quot;을 표준화하는 겁니다. 강의는 &lt;em&gt;Tidy Data = Narrow Data = long form&lt;/em&gt;이라고만 적어 뒀는데, 이건 통계학자 Hadley Wickham의 유명한 논문에서 온 개념입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;원 논문은 이렇게 정의합니다.&lt;/b&gt; tidy data란 &lt;em&gt;&quot;각 변수는 하나의 열, 각 관측치는 하나의 행, 각 관측 단위 종류는 하나의 표&quot;&lt;/em&gt;인 구조를 말합니다. 이렇게 정리해 두면 조작·모델링·시각화가 쉬워지고, 적은 도구로 다양한 지저분한 데이터를 다룰 수 있다는 것이 핵심 주장입니다. &lt;span class=&quot;src&quot;&gt;(출처: Wickham, H. &quot;Tidy Data&quot;, &lt;a href=&quot;https://www.jstatsoft.org/article/view/v059i10&quot;&gt;J. Statistical Software, 2014&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;강의 후반의 &lt;b&gt;Row/Column Compression&lt;/b&gt; 슬라이드가 이 &quot;모양 바꾸기&quot;의 실전 예입니다. 여러 동물 행을 &quot;Carnivore&quot; 하나로 묶거나(행 압축), 8개의 상품 컬럼을 Health Food·Apparel·Digital 3개 카테고리로 합치는(열 압축) 식으로, 목적에 맞게 wide↔long을 오가는 거죠.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;⑥ 품질 지표.&lt;/b&gt; &quot;관리하려면 측정하라.&quot; 강의는 시계열 모니터링 스택 &lt;b&gt;Prometheus&lt;/b&gt;·&lt;b&gt;InfluxDB&lt;/b&gt;를 소개하며, 데이터 에러 건수·정확도·완전성·일관성 같은 지표를 지속 추적하라고 했습니다. 이건 다음 장의 &quot;Process&quot; 질문과 연결됩니다 — &lt;em&gt;불량 레코드가 10%를 넘으면 어떻게 할 것인가? 언제 전체를 멈추고 경보를 울릴 것인가?&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 학습 데이터는 CSV보다 Parquet로 관리하면 재현성·용량·속도가 다 좋아집니다. 데이터 적재 파이프라인은 Airflow로 스케줄링하고, 매 실행마다 &quot;결측률·이상치 비율&quot; 같은 데이터 품질 지표를 Prometheus에 남겨 임계치 초과 시 알람을 겁니다. MLOps 면접에서 &quot;데이터 드리프트를 어떻게 모니터링하나?&quot;의 답이 여기서 시작됩니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 예방을 뚫고 들어온 에러는 결국 손으로 고쳐야 합니다.&lt;/p&gt;

&lt;h2 id=&quot;s6&quot;&gt;6. 에러 수정 — 결측·이상치·중복 다루기&lt;/h2&gt;

&lt;p&gt;강의의 &quot;Fixing Errors&quot;는 작업 목록을 나열했습니다 — 필드 이름 바꾸기, 타입 고치기, 데이터 조인·분리, 불량 삭제, 결측 채우기, 모양 바꾸기(reshaping). 그리고 이걸 문제 유형별 대응 전략으로 다시 묶어 줬는데, 이게 실무에서 제일 자주 쓰는 의사결정 트리입니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;문제&lt;/th&gt;&lt;th&gt;대응 전략&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;결측(Missing)&lt;/b&gt;&lt;/td&gt;&lt;td&gt;무시(ignore) / 행 삭제(listwise deletion) / 수동 채움 / &lt;b&gt;계산값으로 채움&lt;/b&gt;(imputation)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;이상치·노이즈(Noisy)&lt;/b&gt;&lt;/td&gt;&lt;td&gt;비닝(binning) / 군집화(clustering) / ML 알고리즘 / 수동 제거 / &lt;b&gt;상·하한 자르기&lt;/b&gt;(cap/floor)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;불일치(Inconsistent)&lt;/b&gt;&lt;/td&gt;&lt;td&gt;외부 레퍼런스 참조 / 지식 엔지니어링 도구&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;중복(Duplicated)&lt;/b&gt;&lt;/td&gt;&lt;td&gt;충돌 처리 전략 — 무시 / 회피(avoidance) / 해결(resolution)&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;두 개념만 용어를 풀고 가겠습니다. &lt;code&gt;Listwise Deletion&lt;/code&gt; (목록 삭제): 값이 하나라도 빠진 행 전체를 버리는 방법 — 간단하지만 데이터가 줄고 편향(bias)이 생길 수 있음. &lt;code&gt;Imputation&lt;/code&gt; (대치): 삭제 대신 평균·중앙값·모델 예측 등 &lt;em&gt;계산값&lt;/em&gt;으로 빈 칸을 메우는 방법.&lt;/p&gt;

&lt;p&gt;이상치(outlier) 탐지는 강의가 세 갈래로 나눴습니다 — &lt;b&gt;통계 기반&lt;/b&gt;(가설검정, 분포 적합), &lt;b&gt;거리 기반&lt;/b&gt;(전역/지역), &lt;b&gt;모델 기반&lt;/b&gt;. 어느 쪽이든 &quot;정상 범위를 정의하고 그 밖을 튀는 값으로 본다&quot;는 뼈대는 같습니다.&lt;/p&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 결측 처리의 함정&lt;/b&gt; — 결측을 무심코 평균으로 채우면 분산이 줄고 상관관계가 왜곡됩니다. 또 &quot;결측이라는 사실 자체&quot;가 신호일 때가 많습니다(예: 소득 무응답 ↔ 고소득). 그래서 강의의 Data Preparation 트리도 결측을 &lt;em&gt;&quot;자기만의 그룹으로 처리(handling as its own group)&quot;&lt;/em&gt;하는 비닝을 따로 언급합니다. 무조건 채우지 말고 &quot;왜 비었나&quot;를 먼저 보세요.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 여기까지가 일반적인 데이터 이야기입니다. 이제 ML만의 함정으로 넘어갑니다.&lt;/p&gt;

&lt;h2 id=&quot;s7&quot;&gt;7. ⭐ ML 특화 함정 — 데이터 누수와 데이터셋 시프트&lt;/h2&gt;

&lt;p&gt;강의 마지막 &quot;One More Things&quot;부터 결이 확 바뀝니다. 앞까지가 &quot;데이터를 깨끗하게&quot;였다면, 여기부터는 &lt;b&gt;&quot;깨끗한 데이터로도 조용히 망하는 법&quot;&lt;/b&gt;입니다. AI 엔지니어라면 이 두 개가 이 자료의 진짜 하이라이트예요.&lt;/p&gt;

&lt;h3 style=&quot;color:#f4f5f7;margin:26px 0 10px;font-size:1.12rem;&quot;&gt;데이터 누수 (Data Leakage)&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Data Leakage&lt;/code&gt; (데이터 누수): &lt;em&gt;예측 시점에는 알 수 없는 정보&lt;/em&gt;가 모델 학습에 새어 들어가는 것. 검증 점수는 비현실적으로 높은데 실제 운영에서는 성능이 폭락하는 원인입니다.&lt;/p&gt;

&lt;p&gt;직관적으로는 &quot;시험 문제를 미리 보고 공부한 학생&quot;입니다. 모의고사 점수는 만점인데 실전에서 무너지죠. 강의는 누수의 원인을 &lt;b&gt;전처리를 전체 데이터에 적용&lt;/b&gt;, &lt;b&gt;테스트·검증 데이터 공유&lt;/b&gt;, &lt;b&gt;중복&lt;/b&gt;, &lt;b&gt;시간 데이터의 암묵적 누수&lt;/b&gt;(미래 정보가 과거로 새는 것)로 정리했습니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; scikit-learn은 데이터 누수를 &lt;em&gt;&quot;예측 시점에 사용할 수 없는 정보가 모델 구축에 쓰이는 것&quot;&lt;/em&gt;이라 정의하고, 가장 흔한 원인으로 &lt;b&gt;전처리기를 훈련·테스트 분리 전에 전체 데이터에 학습시키는 것&lt;/b&gt;을 듭니다. 규칙은 단순합니다 — &lt;b&gt;&quot;테스트 데이터에는 절대 &lt;code&gt;fit&lt;/code&gt;을 호출하지 마라.&quot;&lt;/b&gt; &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://scikit-learn.org/stable/common_pitfalls.html&quot;&gt;scikit-learn — Common pitfalls&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;공식 문서의 예제가 이걸 극적으로 보여줍니다. 같은 데이터·같은 모델인데 특징 선택을 &lt;em&gt;언제&lt;/em&gt; 하느냐만 다릅니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# ❌ 잘못 — 전체 데이터로 특징 선택 후 분리 (테스트가 선택에 관여)&lt;/span&gt;
X_selected = SelectKBest(k=&lt;span class=&quot;nu&quot;&gt;25&lt;/span&gt;).fit_transform(X, y)
X_train, X_test, y_train, y_test = train_test_split(X_selected, y, random_state=&lt;span class=&quot;nu&quot;&gt;42&lt;/span&gt;)
gbc = HistGradientBoostingClassifier(random_state=&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;).fit(X_train, y_train)
accuracy_score(y_test, gbc.predict(X_test))
&lt;span class=&quot;cm&quot;&gt;# → 0.76  (누수로 부풀려진 가짜 점수)&lt;/span&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# ✅ 올바름 — 먼저 분리하고, 훈련 데이터로만 fit&lt;/span&gt;
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=&lt;span class=&quot;nu&quot;&gt;42&lt;/span&gt;)
select = SelectKBest(k=&lt;span class=&quot;nu&quot;&gt;25&lt;/span&gt;).fit(X_train, y_train)   &lt;span class=&quot;cm&quot;&gt;# fit은 훈련에만&lt;/span&gt;
X_train_s, X_test_s = select.transform(X_train), select.transform(X_test)
gbc = HistGradientBoostingClassifier(random_state=&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;).fit(X_train_s, y_train)
accuracy_score(y_test, gbc.predict(X_test_s))
&lt;span class=&quot;cm&quot;&gt;# → 0.50  (진짜 성능. 이 데이터엔 신호가 없었던 것)&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;0.76과 0.50입니다. 누수 하나로 &quot;쓸 만한 모델&quot;이 &quot;동전 던지기&quot;로 바뀝니다. scikit-learn이 권하는 근본 해법은 &lt;b&gt;Pipeline&lt;/b&gt;이에요 — 전처리와 모델을 하나로 묶으면 교차검증·하이퍼파라미터 탐색에서 &lt;em&gt;각 폴드마다&lt;/em&gt; 전처리가 훈련분에만 적용되도록 자동 보장됩니다.&lt;/p&gt;

&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# 권장 — Pipeline이 누수를 구조적으로 막는다&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; sklearn.pipeline &lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; make_pipeline
pipe = make_pipeline(SelectKBest(k=&lt;span class=&quot;nu&quot;&gt;25&lt;/span&gt;), HistGradientBoostingClassifier(random_state=&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;))
pipe.fit(X_train, y_train)                &lt;span class=&quot;cm&quot;&gt;# fit이 훈련분에만 닿는다&lt;/span&gt;
accuracy_score(y_test, pipe.predict(X_test))  &lt;span class=&quot;cm&quot;&gt;# → 0.50&lt;/span&gt;&lt;/pre&gt;

&lt;div class=&quot;box warn&quot;&gt;&lt;b&gt;⚠️ 가장 흔한 실수&lt;/b&gt; — &lt;code&gt;StandardScaler&lt;/code&gt;·정규화를 &lt;b&gt;분리 전에&lt;/b&gt; 전체 데이터에 걸면 테스트셋의 평균·분산이 훈련에 새어 들어갑니다. 강의도 &lt;em&gt;&quot;정규화는 훈련/테스트 분리 &lt;b&gt;후에&lt;/b&gt; 하라&quot;&lt;/em&gt;고 못 박았죠. EDA조차 훈련셋에만 하는 게 원칙입니다 — 테스트셋을 눈으로 보는 순간 그것도 누수입니다.&lt;/p&gt;&lt;/div&gt;

&lt;h3 style=&quot;color:#f4f5f7;margin:30px 0 10px;font-size:1.12rem;&quot;&gt;데이터셋 시프트 (Dataset Shift)&lt;/h3&gt;

&lt;p&gt;누수가 &quot;학습 중 미래를 훔쳐보는&quot; 문제라면, 시프트는 &quot;배포 후 세상이 바뀌는&quot; 문제입니다. &lt;code&gt;Dataset Shift&lt;/code&gt; (데이터셋 시프트): 학습 때와 운영 때 데이터의 &lt;em&gt;결합 확률분포 P(X, Y)&lt;/em&gt;가 달라지는 현상. 학습 분포와 실제 분포가 어긋나면 모델은 서서히(혹은 갑자기) 틀리기 시작합니다.&lt;/p&gt;

&lt;p&gt;강의는 세 종류로 나눴는데, 이건 Moreno-Torres 등의 정리를 따른 표준 분류입니다. &lt;span class=&quot;src&quot;&gt;(출처: Moreno-Torres et al., &quot;A unifying view on dataset shift in classification&quot;, &lt;a href=&quot;https://doi.org/10.1016/j.patcog.2011.06.019&quot;&gt;Pattern Recognition, 2012&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;종류&lt;/th&gt;&lt;th&gt;무엇이 변하나&lt;/th&gt;&lt;th&gt;예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;공변량 시프트&lt;/b&gt;&lt;br&gt;(Covariate shift)&lt;/td&gt;&lt;td&gt;입력 분포 &lt;code&gt;P(X)&lt;/code&gt;는 변하지만 &lt;code&gt;P(Y|X)&lt;/code&gt;는 그대로&lt;/td&gt;&lt;td&gt;학습은 낮 사진, 운영은 밤 사진&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;사전확률 시프트&lt;/b&gt;&lt;br&gt;(Prior probability shift)&lt;/td&gt;&lt;td&gt;레이블 분포 &lt;code&gt;P(Y)&lt;/code&gt;가 변함 (label shift)&lt;/td&gt;&lt;td&gt;사기 거래 비율이 학습 때보다 급증&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;&lt;b&gt;개념 시프트&lt;/b&gt;&lt;br&gt;(Concept shift / drift)&lt;/td&gt;&lt;td&gt;입력↔출력 관계 &lt;code&gt;P(Y|X)&lt;/code&gt; 자체가 변함&lt;/td&gt;&lt;td&gt;스팸의 정의·패턴이 시간이 지나며 변함&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;여기서 강의가 슬쩍 끼워 넣은 소분류 하나가 &lt;b&gt;내부 공변량 시프트(internal covariate shift)&lt;/b&gt;입니다. 이건 딥러닝에서 특히 중요한데, &lt;b&gt;원 논문은&lt;/b&gt; 이를 &lt;em&gt;&quot;학습 도중 앞 층의 파라미터가 바뀌면서 각 층의 입력 분포가 계속 변하는 현상&quot;&lt;/em&gt;으로 정의하고, 이 때문에 학습이 느려진다고 진단합니다. 그 해법으로 나온 게 바로 &lt;b&gt;Batch Normalization&lt;/b&gt;이고요 — 층 입력을 정규화해 같은 정확도를 &lt;em&gt;14배 적은 스텝&lt;/em&gt;으로 달성했다는 논문입니다. &lt;span class=&quot;src&quot;&gt;(출처: Ioffe &amp;amp; Szegedy, &quot;Batch Normalization…&quot;, &lt;a href=&quot;https://arxiv.org/abs/1502.03167&quot;&gt;arXiv:1502.03167, 2015&lt;/a&gt;)&lt;/span&gt; BatchNorm을 &quot;왜 쓰나&quot;의 원류가 이 시프트 개념이라는 걸 알면 면접에서 한 단계 깊게 답할 수 있습니다.&lt;/p&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 운영 중인 모델의 성능이 이유 없이 떨어지면 1순위 용의자가 데이터셋 시프트입니다. 그래서 MLOps에선 입력 분포를 지속 모니터링하다가(5장의 품질 지표와 연결) 드리프트가 감지되면 재학습을 트리거합니다. &quot;왜 재학습이 필요한가&quot;의 이론적 근거가 바로 이 세 가지 시프트예요.&lt;/p&gt;&lt;/div&gt;

&lt;p&gt;강의는 데이터 누수 방지법(교차검증 전 올바른 정규화, 훈련/검증/테스트 3분할, 중복 제거)을 한 번 더 강조하며 &lt;b&gt;&quot;바퀴를 다시 발명하지 마라(You don't have to reinvent the wheel)&quot;&lt;/b&gt;는 슬라이드로 끝맺습니다. 검증엔 Pydantic·pandera, 파이프라인엔 Airflow, 누수 방지엔 sklearn Pipeline — 이미 잘 만들어진 도구를 쓰라는 거죠.&lt;/p&gt;

&lt;h2 id=&quot;s8&quot;&gt;치트시트 &amp;amp; 핵심 7가지&lt;/h2&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;상황&lt;/th&gt;&lt;th&gt;도구 · 패턴&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;입력 데이터 검증&lt;/td&gt;&lt;td&gt;&lt;code&gt;Pydantic&lt;/code&gt; / &lt;code&gt;pandera&lt;/code&gt; — 입구에서 규칙 위반 차단&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;학습 데이터 저장&lt;/td&gt;&lt;td&gt;&lt;code&gt;Parquet&lt;/code&gt; + &lt;code&gt;PyArrow&lt;/code&gt; — 타입·스키마 보존&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;전송 무결성&lt;/td&gt;&lt;td&gt;&lt;code&gt;SHA256&lt;/code&gt; 체크섬 (비밀번호엔 절대 X → bcrypt/argon2)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;파이프라인 자동화&lt;/td&gt;&lt;td&gt;&lt;code&gt;Airflow&lt;/code&gt; (스케줄) · &lt;code&gt;Invoke&lt;/code&gt; (태스크)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;결측 처리&lt;/td&gt;&lt;td&gt;삭제(listwise) vs 대치(impute) — &quot;왜 비었나&quot; 먼저&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;누수 방지&lt;/td&gt;&lt;td&gt;&lt;code&gt;train_test_split&lt;/code&gt; &lt;b&gt;먼저&lt;/b&gt; → &lt;code&gt;Pipeline&lt;/code&gt;으로 fit 격리&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;드리프트 감시&lt;/td&gt;&lt;td&gt;입력 분포 모니터링 → 시프트 감지 시 재학습&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  딱 7가지만 기억하면 됩니다.&lt;br&gt;
1. &lt;b&gt;GIGO&lt;/b&gt; — 데이터가 틀리면 모델도 틀린다. 준비가 일의 80%.&lt;br&gt;
2. Cleaning ⊂ Wrangling, Preprocessing은 &quot;한 번&quot;, Wrangling은 &quot;반복&quot;.&lt;br&gt;
3. 에러는 &lt;b&gt;사람·기계&lt;/b&gt;에서 나고, &lt;b&gt;스키마 검증&lt;/b&gt;으로 입구에서 막는다.&lt;br&gt;
4. 예방 = 포맷(Parquet)·서명(해시)·파이프라인(ETL)·tidy data·품질 지표.&lt;br&gt;
5. 수정은 결측·이상치·중복별로 다르게 — 무조건 채우거나 지우지 말 것.&lt;br&gt;
6. &lt;b&gt;데이터 누수&lt;/b&gt;: 테스트에 fit 금지. 전처리는 분리 후, Pipeline으로.&lt;br&gt;
7. &lt;b&gt;데이터셋 시프트&lt;/b&gt;: 공변량·사전확률·개념 3종. BatchNorm의 뿌리이자 재학습의 이유.&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/common_pitfalls.html&quot;&gt;scikit-learn — Common pitfalls and recommended practices (Data leakage)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jstatsoft.org/article/view/v059i10&quot;&gt;Hadley Wickham — Tidy Data, Journal of Statistical Software (2014)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1502.03167&quot;&gt;Ioffe &amp;amp; Szegedy — Batch Normalization: …Reducing Internal Covariate Shift (2015)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://doi.org/10.1016/j.patcog.2011.06.019&quot;&gt;Moreno-Torres et al. — A unifying view on dataset shift in classification (2012)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pydantic.dev/latest/&quot;&gt;Pydantic — Data validation using Python type hints&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pandera.readthedocs.io/&quot;&gt;pandera — A Statistical Data Testing Toolkit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://json-schema.org/&quot;&gt;JSON Schema&lt;/a&gt; · &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;Apache Parquet&lt;/a&gt; · &lt;a href=&quot;https://arrow.apache.org/&quot;&gt;Apache Arrow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/&quot;&gt;Apache Airflow&lt;/a&gt; · &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/123</guid>
      <comments>https://hskywith.tistory.com/123#entry123comment</comments>
      <pubDate>Mon, 31 Aug 2026 16:58:12 +0900</pubDate>
    </item>
    <item>
      <title>Tidy Data 제대로 이해하기 &amp;mdash; 지저분한 표를 pandas로 정돈하기</title>
      <link>https://hskywith.tistory.com/122</link>
      <description>&lt;!--
  tistory.html — 티스토리 HTML 모드 붙여넣기용 다크 전용 스켈레톤.
  사용법: {{CONTENT}} 자리에 본문 HTML(h2/p/pre/table/div.box 등)을 넣는다.
  - 모든 스타일이 .rp 로 스코프됨 → 블로그 테마와 충돌 없음
  - .rp 배경은 transparent → 블로그(다크) 배경을 그대로 사용, &quot;카드 안 카드&quot; 이중배경 방지
  - 코드블록은 테두리 없이 단일 배경
  콜아웃: &lt;div class=&quot;box note|tip|warn|use&quot;&gt;…&lt;/div&gt;  (use는   어디에 쓰나)
  구문강조 span: cm(주석) st(문자열) nu(숫자) fn(함수) kw(키워드)
  개념 지도: div.map &gt; (c&gt;mb / ar / row&gt;cell&gt;q,s / ft)
--&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@latest/dist/web/static/pretendard.css&quot;&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;

&lt;div class=&quot;rp&quot;&gt;
&lt;!-- title: Tidy Data 제대로 이해하기 — 지저분한 표를 pandas로 정돈하기 | date: 2026-08-31 | tags: Tidy Data, pandas, 데이터전처리, 데이터분석, 복습 --&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 div.rp 안쪽만 붙여넣으면 됩니다. --&gt;

&lt;p&gt;이번 강의는 &lt;em&gt;&quot;Bad Data&quot;&lt;/em&gt;라는 넓은 주제였습니다. 실무 데이터가 왜 지저분한지(오타·결측·중복·비표준·이상치…), 그걸 어떻게 청소하고(cleaning) 주무르는지(wrangling), 에러를 어떻게 탐지·예방하는지까지 훑고, 마지막에 &lt;b&gt;Tidy Data&lt;/b&gt;로 마무리하더라고요. 그리고 강의와 함께 받은 &lt;code&gt;tidy-data-master&lt;/code&gt; 폴더에는 이 Tidy Data 개념의 원전 — Hadley Wickham(해들리 위컴)의 논문과 실제 지저분한 데이터 파일들(종교·소득 조사, 빌보드 차트, 결핵 통계, 기상 관측)이 들어 있었습니다.&lt;/p&gt;

&lt;p&gt;강의 전체를 다 옮기면 물타지니까, 이 글은 &lt;b&gt;&quot;지저분한 표를 어떻게 분석 가능한 표로 바꾸는가&quot;&lt;/b&gt; 한 갈래에 집중합니다. Tidy Data가 뭔지 정의부터 잡고, 위컴이 정리한 지저분한 데이터 5대 유형 중 자주 만나는 3가지를 실제 강의 데이터에 &lt;code&gt;pandas&lt;/code&gt;로 직접 손봐가며 볼게요. 참고로 폴더의 &lt;code&gt;README&lt;/code&gt;는 R 코드 파일(&lt;code&gt;tidy-data.R&lt;/code&gt;)을 언급하지만 실제 파일은 빠져 있어서, 논문의 R 예제를 제가 pandas로 재현했습니다.&lt;/p&gt;

&lt;div class=&quot;toc&quot;&gt;
&lt;div class=&quot;t&quot;&gt;목차&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#s1&quot;&gt;큰 그림 — 조각들이 어떻게 맞물리나&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#s2&quot;&gt;Tidy Data란 무엇인가 (3원칙)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#s3&quot;&gt;지저분한 데이터 5대 유형&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#s4&quot;&gt;melt — 열 이름이 값일 때 (종교·소득 데이터)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#s5&quot;&gt;split — 한 열에 변수가 두 개일 때 (결핵 데이터)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#s6&quot;&gt;pivot — 값이 행·열 양쪽에 흩어졌을 때 (기상 데이터)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#s7&quot;&gt;치트시트 &amp;amp; 핵심 4가지&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

&lt;h2 id=&quot;s1&quot;&gt;1. 큰 그림 — 조각들이 어떻게 맞물리나&lt;/h2&gt;
&lt;p&gt;Tidy Data는 결국 &lt;b&gt;&quot;표 하나에 딱 세 가지 질문을 던지고, 어긋나면 정해진 도구로 고친다&quot;&lt;/b&gt;는 이야기입니다. 세 가지 질문이 tidy의 3원칙이고, 어긋나는 방식이 지저분한 유형이며, 고치는 도구가 &lt;code&gt;melt·split·pivot&lt;/code&gt;입니다. 아래 지도가 이 글의 뼈대예요. 이후 각 섹션이 이 칸을 하나씩 채웁니다.&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;지저분한 표&lt;/b&gt; 에 이 질문들을 던진다&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;변수 = 열?&lt;/span&gt;tidy 3원칙&lt;span class=&quot;s&quot;&gt;§2&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;열 이름이 값?&lt;/span&gt;→ melt&lt;span class=&quot;s&quot;&gt;wide→long&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;한 열에 변수 2개?&lt;/span&gt;→ split&lt;span class=&quot;s&quot;&gt;str.split&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;값이 행·열 양쪽?&lt;/span&gt;→ pivot&lt;span class=&quot;s&quot;&gt;long→wide&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;세 도구는 서로 반대·보완 관계 — melt는 펴고(long), pivot은 접고(wide), split은 쪼갠다&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;핵심 한 줄&lt;/b&gt; — Tidy Data는 &quot;예쁜 데이터&quot;가 아니라, 분석 도구가 바로 먹을 수 있게 &lt;b&gt;구조(structure)와 의미(semantics)를 일치&lt;/b&gt;시킨 데이터입니다. 사람이 보기 좋은 표와 기계가 다루기 좋은 표는 다릅니다.&lt;/div&gt;

&lt;h2 id=&quot;s2&quot;&gt;2. Tidy Data란 무엇인가 (3원칙)&lt;/h2&gt;
&lt;p&gt;먼저 용어부터 정확히 잡고 갑시다. 강의 슬라이드에도 한 줄로 &lt;em&gt;&quot;Tidy Data = Narrow Data = long form&quot;&lt;/em&gt;이라고만 적혀 있어서, 이게 정확히 뭘 뜻하는지 원전에서 확인하는 게 중요합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Tidy Data&lt;/code&gt; (정돈된 데이터): 데이터의 &lt;b&gt;의미&lt;/b&gt;를 &lt;b&gt;구조&lt;/b&gt;에 규칙적으로 대응시킨 표 — 변수는 열, 관측치는 행에 오도록 맞춘 형태.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;variable&lt;/code&gt; (변수): 같은 속성을 여러 대상에 걸쳐 잰 값들의 묶음 (예: 키, 온도, 소득구간). &lt;code&gt;observation&lt;/code&gt; (관측치): 한 대상에 대해 잰 값들의 묶음 (예: 사람 한 명, 하루, 경기 하나).&lt;/p&gt;

&lt;p&gt;비유하자면, 엑셀에서 사람이 보기 좋으라고 만든 &quot;요약표&quot;는 셀 하나에 정보가 응축돼 있어 눈으로 읽기엔 좋지만, 컴퓨터가 &lt;code&gt;groupby&lt;/code&gt;·집계·시각화를 하려면 매번 다른 방식으로 값을 뜯어내야 합니다. Tidy Data는 그 뜯어내기를 &lt;b&gt;단 한 가지 방식&lt;/b&gt;으로 통일해 줍니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;원전은 이렇게 정의합니다.&lt;/b&gt; 위컴은 Tidy Data를 세 가지 원칙으로 못박습니다 — 각 변수는 하나의 열을, 각 관측치는 하나의 행을, 각 관측 단위 유형은 하나의 표를 이룬다. 그리고 이건 관계형 데이터베이스의 &lt;em&gt;제3정규형(3rd normal form)&lt;/em&gt;을 통계 언어로 바꿔 쓴 것이라고 밝힙니다. &lt;span class=&quot;src&quot;&gt;(출처: Hadley Wickham, &lt;a href=&quot;https://vita.had.co.nz/papers/tidy-data.pdf&quot;&gt;Tidy Data&lt;/a&gt;, Journal of Statistical Software, §2.3)&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# Tidy Data 3원칙 (Wickham, 2014)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 1. Each variable forms a column.        각 변수 = 하나의 열&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 2. Each observation forms a row.         각 관측치 = 하나의 행&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 3. Each observational unit forms a table. 각 관측 단위 = 하나의 표&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;여기서 강의의 &quot;long form&quot;이 연결됩니다. 같은 데이터를 담아도 &lt;b&gt;wide form&lt;/b&gt;(넓은 형태: 변수를 옆으로 펼쳐 열을 늘림)과 &lt;b&gt;long form&lt;/b&gt;(긴 형태: 변수를 아래로 쌓아 행을 늘림)이 있는데, tidy는 보통 long form 쪽입니다. 아래 표가 같은 정보의 두 얼굴입니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;wide form (넓은/지저분하기 쉬움)&lt;/th&gt;&lt;th&gt;long form (긴/tidy)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;모양&lt;/td&gt;&lt;td&gt;열이 옆으로 많음&lt;/td&gt;&lt;td&gt;행이 아래로 많음&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;예&lt;/td&gt;&lt;td&gt;&lt;code&gt;religion, &amp;lt;$10k, $10-20k, …&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;religion, income, frequency&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;사람 눈&lt;/td&gt;&lt;td&gt;읽기 좋음(요약표)&lt;/td&gt;&lt;td&gt;장황해 보임&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;기계 처리&lt;/td&gt;&lt;td&gt;매번 다른 방식으로 뜯어야 함&lt;/td&gt;&lt;td&gt;groupby·집계·플롯 한 방에&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — &lt;code&gt;seaborn&lt;/code&gt;·&lt;code&gt;plotly&lt;/code&gt; 같은 시각화 라이브러리, &lt;code&gt;groupby().agg()&lt;/code&gt; 집계, 그리고 ML 모델의 입력 &lt;code&gt;X&lt;/code&gt; 만들기 전 단계가 전부 tidy(long) 형태를 전제로 돌아갑니다. &quot;데이터가 왜 이렇게 플롯이 안 그려지지?&quot; 태반은 형태가 wide라서입니다.&lt;/p&gt;&lt;/div&gt;

&lt;p class=&quot;next&quot;&gt;→ 그럼 실무 데이터는 이 3원칙을 어떻게 어길까요? 위컴이 정리한 5대 유형을 봅니다.&lt;/p&gt;

&lt;h2 id=&quot;s3&quot;&gt;3. 지저분한 데이터 5대 유형&lt;/h2&gt;
&lt;p&gt;위컴은 &quot;행복한 가정은 다 비슷하지만 불행한 가정은 저마다의 방식으로 불행하다&quot;는 톨스토이를 빌려, 지저분한 데이터도 저마다의 방식으로 지저분하다고 말합니다. 그런데 실제로 겪어보면 대부분 아래 다섯 가지로 수렴하더라는 게 논문의 관찰입니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://vita.had.co.nz/papers/tidy-data.pdf&quot;&gt;Tidy Data&lt;/a&gt;, §3)&lt;/span&gt;&lt;/p&gt;

&lt;div class=&quot;map&quot;&gt;
  &lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;지저분한 데이터 &lt;b&gt;5대 유형&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;① 열이름=값&lt;/span&gt;변수가 헤더에&lt;span class=&quot;s&quot;&gt;→ melt&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;② 한 열 = 여러 변수&lt;/span&gt;성별+나이 뭉침&lt;span class=&quot;s&quot;&gt;→ split&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;③ 행·열 양쪽&lt;/span&gt;변수가 흩어짐&lt;span class=&quot;s&quot;&gt;→ pivot&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;④⑤ 표 문제&lt;/span&gt;여러 단위 한 표 /&lt;br&gt;한 단위 여러 표&lt;span class=&quot;s&quot;&gt;→ 정규화&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;ft&quot;&gt;이 글은 실무 빈도가 높은 ①②③을 실제 데이터로 다룹니다&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;④(한 표에 여러 종류의 관측 단위가 섞임)와 ⑤(한 관측 단위가 여러 표에 쪼개짐)는 데이터베이스 정규화·조인의 영역이라 별도 글로 미루고, 여기서는 표 하나를 재구조화하는 ①②③에 집중하겠습니다.&lt;/p&gt;
&lt;p class=&quot;next&quot;&gt;→ 첫 번째, 가장 흔한 &quot;열 이름이 사실은 값&quot;인 경우입니다.&lt;/p&gt;

&lt;h2 id=&quot;s4&quot;&gt;4. melt — 열 이름이 값일 때 (종교·소득 데이터)&lt;/h2&gt;
&lt;p&gt;앞의 지도에서 ①번 칸입니다. 발표용으로 예쁘게 만든 표는 대개 이 함정에 빠져 있습니다. 강의 데이터 중 &lt;code&gt;pew.txt&lt;/code&gt;(미국 Pew Research Center의 종교별 소득 조사)가 딱 그렇습니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;melt&lt;/code&gt; (녹이다): 옆으로 펼쳐진 여러 열을 &lt;b&gt;두 개의 열&lt;/b&gt;(이름 열 + 값 열)로 녹여 아래로 쌓는 변형 — wide를 long으로 바꾸는 대표 도구.&lt;/p&gt;
&lt;p&gt;직관적으로는 얼음 틀(각 칸=열)을 &lt;b&gt;녹여서&lt;/b&gt; 한 줄기 물(이름·값 두 열)로 흘려보내는 그림입니다. 그래서 이름이 &quot;melt&quot;예요.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; pandas 공식 레퍼런스는 &lt;code&gt;melt&lt;/code&gt;를 &quot;&lt;code&gt;id_vars&lt;/code&gt;로 지정한 식별 열은 그대로 두고, 나머지 열들을 &lt;code&gt;variable&lt;/code&gt;/&lt;code&gt;value&lt;/code&gt; 두 열로 unpivot한다&quot;고 정의합니다. 아래가 공식 문서에 실린 예제 그대로입니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.melt.html&quot;&gt;pandas.melt&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# pandas 공식 문서 예제 — 대조 기준&lt;/span&gt;
df = pd.&lt;span class=&quot;fn&quot;&gt;DataFrame&lt;/span&gt;({&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;:[&lt;span class=&quot;st&quot;&gt;&quot;a&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;b&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;c&quot;&lt;/span&gt;], &lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;:[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;,&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;,&lt;span class=&quot;nu&quot;&gt;5&lt;/span&gt;], &lt;span class=&quot;st&quot;&gt;&quot;C&quot;&lt;/span&gt;:[&lt;span class=&quot;nu&quot;&gt;2&lt;/span&gt;,&lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;,&lt;span class=&quot;nu&quot;&gt;6&lt;/span&gt;]})
pd.&lt;span class=&quot;fn&quot;&gt;melt&lt;/span&gt;(df, id_vars=[&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;], value_vars=[&lt;span class=&quot;st&quot;&gt;&quot;B&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;C&quot;&lt;/span&gt;])
&lt;span class=&quot;cm&quot;&gt;#    A variable  value&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 0  a        B      1     ← B,C 열 이름이 'variable' 열의 값으로 내려오고&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 1  b        B      3        원래 셀 값은 'value' 열로 쌓인다&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 3  a        C      2&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;강의 데이터에 적용하면 이렇게 됩니다.&lt;/b&gt; &lt;code&gt;pew.txt&lt;/code&gt;는 &lt;code&gt;religion&lt;/code&gt; 한 열을 빼면 나머지 열 이름(&lt;code&gt;&amp;lt;$10k&lt;/code&gt;, &lt;code&gt;$10-20k&lt;/code&gt;…)이 전부 &quot;소득 구간&quot;이라는 &lt;b&gt;변수의 값&lt;/b&gt;입니다. 즉 열 이름 자리에 데이터가 앉아 있는 거죠. &lt;code&gt;religion&lt;/code&gt;만 식별 열로 고정하고 melt하면 tidy해집니다.&lt;/p&gt;
&lt;pre&gt;pew = pd.&lt;span class=&quot;fn&quot;&gt;read_csv&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&quot;pew.txt&quot;&lt;/span&gt;, sep=&lt;span class=&quot;st&quot;&gt;&quot;\t&quot;&lt;/span&gt;)
&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(pew.shape)              &lt;span class=&quot;cm&quot;&gt;# (18, 11)  ← 18개 종교 × 10개 소득열 + religion&lt;/span&gt;

tidy = pew.&lt;span class=&quot;fn&quot;&gt;melt&lt;/span&gt;(id_vars=&lt;span class=&quot;st&quot;&gt;&quot;religion&quot;&lt;/span&gt;,
                &lt;span class=&quot;cm&quot;&gt;# 나머지 소득 열 전부를 녹인다&lt;/span&gt;
                var_name=&lt;span class=&quot;st&quot;&gt;&quot;income&quot;&lt;/span&gt;, value_name=&lt;span class=&quot;st&quot;&gt;&quot;frequency&quot;&lt;/span&gt;)
&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(tidy.shape)             &lt;span class=&quot;cm&quot;&gt;# (180, 3)  ← 18 × 10 = 180 행으로 길어짐&lt;/span&gt;
&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(tidy.&lt;span class=&quot;fn&quot;&gt;head&lt;/span&gt;(&lt;span class=&quot;nu&quot;&gt;4&lt;/span&gt;))
&lt;span class=&quot;cm&quot;&gt;#            religion income  frequency&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 0          Agnostic  &lt;$10k         27&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 1           Atheist  &lt;$10k         12&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 2          Buddhist  &lt;$10k         27&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 3          Catholic  &lt;$10k        418&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;재밌는 지점 하나. 위컴은 R 사람이라 논문에서는 &lt;code&gt;reshape2::melt&lt;/code&gt;를 썼고, 지금 그의 후속 도구 &lt;code&gt;tidyr&lt;/code&gt;에서는 &lt;code&gt;pivot_longer()&lt;/code&gt;가 이 역할을 합니다. 그런데 &lt;code&gt;tidyr&lt;/code&gt; 공식 문서의 대표 예제가 바로 &lt;b&gt;이 Pew 데이터(&lt;code&gt;relig_income&lt;/code&gt;)&lt;/b&gt;예요. 강의 데이터와 공식 문서 예제가 같은 데이터인 셈입니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://tidyr.tidyverse.org/reference/pivot_longer.html&quot;&gt;tidyr::pivot_longer&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# R tidyr 공식 예제 — 완전히 같은 데이터, 같은 결과 (18×11 → 180×3)&lt;/span&gt;
relig_income |&amp;gt; &lt;span class=&quot;fn&quot;&gt;pivot_longer&lt;/span&gt;(!religion, names_to = &lt;span class=&quot;st&quot;&gt;&quot;income&quot;&lt;/span&gt;, values_to = &lt;span class=&quot;st&quot;&gt;&quot;count&quot;&lt;/span&gt;)&lt;/pre&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;pandas 공식 예제&lt;/th&gt;&lt;th&gt;강의 데이터(pew) 적용&lt;/th&gt;&lt;th&gt;차이 · 이유&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;식별 열&lt;/td&gt;&lt;td&gt;&lt;code&gt;id_vars=[&quot;A&quot;]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;id_vars=&quot;religion&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;고정할 변수 지정 — 개념 동일&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;녹일 열&lt;/td&gt;&lt;td&gt;&lt;code&gt;value_vars=[&quot;B&quot;,&quot;C&quot;]&lt;/code&gt; 명시&lt;/td&gt;&lt;td&gt;생략(나머지 전부 자동)&lt;/td&gt;&lt;td&gt;열이 10개라 일일이 안 씀 — 생략 시 id 외 전부&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;결과 열 이름&lt;/td&gt;&lt;td&gt;&lt;code&gt;variable&lt;/code&gt;/&lt;code&gt;value&lt;/code&gt; 기본값&lt;/td&gt;&lt;td&gt;&lt;code&gt;income&lt;/code&gt;/&lt;code&gt;frequency&lt;/code&gt;&lt;/td&gt;&lt;td&gt;의미가 드러나게 개명 — 실무 권장&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;모양 변화&lt;/td&gt;&lt;td&gt;3행 → 6행&lt;/td&gt;&lt;td&gt;18행 → 180행&lt;/td&gt;&lt;td&gt;둘 다 wide→long, 원리 동일&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  &lt;code&gt;var_name&lt;/code&gt;·&lt;code&gt;value_name&lt;/code&gt;을 &lt;code&gt;&quot;variable&quot;&lt;/code&gt;/&lt;code&gt;&quot;value&quot;&lt;/code&gt; 기본값으로 두지 말고 꼭 의미 있는 이름으로 바꾸세요. 6개월 뒤의 내가 &lt;code&gt;value&lt;/code&gt;가 빈도인지 금액인지 몰라 헤맵니다.&lt;/div&gt;

&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 설문·통계청·엑셀에서 받은 &quot;가로로 긴 요약표&quot;를 분석용으로 바꿀 때 1순위 도구입니다. 연도별·월별 열이 옆으로 쭉 늘어선 매출표를 시계열 분석용으로 펴는 것도 정확히 이 패턴입니다.&lt;/div&gt;
&lt;p class=&quot;next&quot;&gt;→ melt로 폈더니 이번엔 열 이름 하나에 변수가 &lt;b&gt;두 개&lt;/b&gt; 뭉쳐 있는 경우가 나옵니다.&lt;/p&gt;

&lt;h2 id=&quot;s5&quot;&gt;5. split — 한 열에 변수가 두 개일 때 (결핵 데이터)&lt;/h2&gt;
&lt;p&gt;지도의 ②번 칸입니다. melt를 하고 나면 &lt;code&gt;variable&lt;/code&gt; 열의 값이 사실은 여러 정보를 합쳐 놓은 코드인 경우가 잦습니다. 강의의 &lt;code&gt;tb.csv&lt;/code&gt;(WHO 결핵 통계)가 전형적이에요. 열 이름이 &lt;code&gt;new_sp_m1524&lt;/code&gt; 같은데, 여기엔 &lt;b&gt;성별(m)&lt;/b&gt;과 &lt;b&gt;나이대(15–24세)&lt;/b&gt;가 한 덩어리로 붙어 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;split&lt;/code&gt; (쪼개다): 한 문자열을 구분 규칙에 따라 여러 조각으로 나누는 것 — 여기서는 뭉친 열 이름을 성별·나이대 두 변수로 분리.&lt;/p&gt;
&lt;p&gt;tidy 원칙 1번(&quot;한 변수 = 한 열&quot;)을 정면으로 어긴 상태라, 붙어 있는 걸 떼어 각자 열을 주는 게 목표입니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; pandas &lt;code&gt;Series.str.split&lt;/code&gt;은 &lt;code&gt;expand=True&lt;/code&gt;를 주면 쪼갠 조각들이 &lt;b&gt;여러 개의 열로 펼쳐진다&lt;/b&gt;고 명시합니다(부족한 칸은 &lt;code&gt;NaN&lt;/code&gt;으로 채움). &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.Series.str.split.html&quot;&gt;pandas.Series.str.split&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# pandas 공식 문서 예제 — expand=True 는 조각을 '열'로 펼친다&lt;/span&gt;
s = pd.&lt;span class=&quot;fn&quot;&gt;Series&lt;/span&gt;([&lt;span class=&quot;st&quot;&gt;&quot;this is a regular sentence&quot;&lt;/span&gt;, ...])
s.str.&lt;span class=&quot;fn&quot;&gt;split&lt;/span&gt;(expand=&lt;span class=&quot;kw&quot;&gt;True&lt;/span&gt;)
&lt;span class=&quot;cm&quot;&gt;#        0    1    2        3         4&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# 0   this   is    a  regular  sentence   ← 공백 기준으로 5개 열 생성&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;강의 데이터에 적용하면 이렇게 됩니다.&lt;/b&gt; &lt;code&gt;tb.csv&lt;/code&gt;는 구분자가 없이 &lt;code&gt;m1524&lt;/code&gt;처럼 딱 붙어 있어서, 공식 예제처럼 구분자 split이 아니라 &lt;b&gt;위치 기반 슬라이싱&lt;/b&gt;이 더 깔끔합니다(첫 글자=성별, 나머지=나이대). 도구는 &lt;code&gt;.str[...]&lt;/code&gt;로 살짝 바꾸되, &quot;한 열의 뭉친 정보를 두 변수로 떼어낸다&quot;는 목적은 공식 예제와 완전히 같습니다.&lt;/p&gt;
&lt;pre&gt;tb = pd.&lt;span class=&quot;fn&quot;&gt;read_csv&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&quot;tb.csv&quot;&lt;/span&gt;)
m  = tb.&lt;span class=&quot;fn&quot;&gt;melt&lt;/span&gt;(id_vars=[&lt;span class=&quot;st&quot;&gt;&quot;iso2&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;year&quot;&lt;/span&gt;],
             var_name=&lt;span class=&quot;st&quot;&gt;&quot;column&quot;&lt;/span&gt;, value_name=&lt;span class=&quot;st&quot;&gt;&quot;cases&quot;&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;dropna&lt;/span&gt;()

&lt;span class=&quot;cm&quot;&gt;# &quot;new_sp_m1524&quot; → 접두어 떼고 → 첫 글자=성별, 나머지=나이대&lt;/span&gt;
demo    = m[&lt;span class=&quot;st&quot;&gt;&quot;column&quot;&lt;/span&gt;].str.&lt;span class=&quot;fn&quot;&gt;replace&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&quot;new_sp_&quot;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&quot;&quot;&lt;/span&gt;, regex=&lt;span class=&quot;kw&quot;&gt;False&lt;/span&gt;)
m[&lt;span class=&quot;st&quot;&gt;&quot;sex&quot;&lt;/span&gt;] = demo.str[&lt;span class=&quot;nu&quot;&gt;0&lt;/span&gt;]      &lt;span class=&quot;cm&quot;&gt;# 'm' 또는 'f'&lt;/span&gt;
m[&lt;span class=&quot;st&quot;&gt;&quot;age&quot;&lt;/span&gt;] = demo.str[&lt;span class=&quot;nu&quot;&gt;1&lt;/span&gt;:]     &lt;span class=&quot;cm&quot;&gt;# '1524', '014' …&lt;/span&gt;

&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(m[[&lt;span class=&quot;st&quot;&gt;&quot;iso2&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;year&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;sex&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;age&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;cases&quot;&lt;/span&gt;]].&lt;span class=&quot;fn&quot;&gt;head&lt;/span&gt;(&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;))
&lt;span class=&quot;cm&quot;&gt;# iso2  year sex  age  cases&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;#   AE  1999   m  014    4.0     ← 성별·나이대가 각자의 열로 분리됨&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;#   AE  2000   m  014    2.0&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;#   AE  2002   m  014    1.0&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;R 쪽에서는 위컴이 이걸 아예 한 함수로 흡수했습니다. &lt;code&gt;pivot_longer(names_to = c(&quot;sex&quot;,&quot;age&quot;), names_sep = ...)&lt;/code&gt;처럼 &lt;b&gt;펴면서 동시에 쪼개기&lt;/b&gt;가 됩니다 — melt와 split이 한 스텝으로 합쳐진 셈입니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://tidyr.tidyverse.org/reference/pivot_longer.html&quot;&gt;tidyr::pivot_longer&lt;/a&gt;, &lt;code&gt;names_sep&lt;/code&gt;/&lt;code&gt;names_to&lt;/code&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;pandas 공식 예제&lt;/th&gt;&lt;th&gt;강의 데이터(tb) 적용&lt;/th&gt;&lt;th&gt;차이 · 이유&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;도구&lt;/td&gt;&lt;td&gt;&lt;code&gt;str.split(expand=True)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;str[0]&lt;/code&gt; / &lt;code&gt;str[1:]&lt;/code&gt; 슬라이싱&lt;/td&gt;&lt;td&gt;구분자가 없어 위치로 자름 — 목적은 동일&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;결과&lt;/td&gt;&lt;td&gt;공백마다 N개 열&lt;/td&gt;&lt;td&gt;&lt;code&gt;sex&lt;/code&gt;, &lt;code&gt;age&lt;/code&gt; 2개 열&lt;/td&gt;&lt;td&gt;쪼갠 뒤 각자 열이 되는 원리 같음&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;선행 단계&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;td&gt;먼저 &lt;code&gt;melt&lt;/code&gt; 필요&lt;/td&gt;&lt;td&gt;열 이름이 값이자 뭉친 변수 — ①+② 복합&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box warn&quot;&gt;⚠️ 결핵 데이터에는 값이 &lt;code&gt;0&lt;/code&gt;인 칸과 &lt;b&gt;결측(&lt;code&gt;NaN&lt;/code&gt;)&lt;/b&gt;인 칸이 섞여 있습니다. 논문도 짚듯 이 둘은 의미가 달라요(&quot;0건 확인&quot;과 &quot;조사 안 함&quot;). &lt;code&gt;dropna()&lt;/code&gt;로 결측만 걸러내되, &lt;code&gt;0&lt;/code&gt;을 결측과 뭉뚱그려 지우지 않도록 주의하세요.&lt;/div&gt;
&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — 로그·센서·설문 필드에서 &lt;code&gt;2026-08-31_seoul_temp&lt;/code&gt;처럼 날짜·지역·측정값이 한 컬럼명/문자열에 뭉쳐 오는 경우가 흔합니다. 이걸 각 변수 열로 떼어야 필터·집계가 됩니다.&lt;/div&gt;
&lt;p class=&quot;next&quot;&gt;→ 마지막, 변수가 행에도 있고 열에도 있어 melt만으로는 안 되는 가장 까다로운 경우입니다.&lt;/p&gt;

&lt;h2 id=&quot;s6&quot;&gt;6. pivot — 값이 행·열 양쪽에 흩어졌을 때 (기상 데이터)&lt;/h2&gt;
&lt;p&gt;지도의 ③번, 논문이 &quot;가장 복잡한 형태&quot;라고 부른 유형입니다. 강의의 &lt;code&gt;weather.txt&lt;/code&gt;(멕시코 기상관측소 일별 기온)가 그렇습니다. 날짜는 &lt;code&gt;d1&lt;/code&gt;~&lt;code&gt;d31&lt;/code&gt; 열로 &lt;b&gt;가로로&lt;/b&gt; 흩어져 있고, 동시에 최고기온·최저기온이 &lt;code&gt;element&lt;/code&gt; 열 안에서 &lt;code&gt;TMAX&lt;/code&gt;/&lt;code&gt;TMIN&lt;/code&gt; &lt;b&gt;행으로&lt;/b&gt; 흩어져 있어요. 한 방향은 melt로 펴야 하고, 다른 방향은 다시 접어야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pivot&lt;/code&gt; (돌려 세우다 / cast): 한 열에 쌓여 있던 값을 여러 열로 &lt;b&gt;펼쳐 세우는&lt;/b&gt; 변형 — melt의 반대. 논문 용어로는 &lt;em&gt;cast&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;melt가 열을 녹여 아래로 쌓았다면, pivot은 그 쌓인 걸 다시 옆으로 세웁니다. 여기서는 &lt;code&gt;TMAX&lt;/code&gt;/&lt;code&gt;TMIN&lt;/code&gt;이 &lt;b&gt;서로 다른 변수&lt;/b&gt;인데 한 열에 값으로 눌려 있으니, 각자의 열로 세워 올려야 tidy해집니다.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; pandas &lt;code&gt;pivot_table&lt;/code&gt;은 &lt;code&gt;index&lt;/code&gt;(행이 될 키), &lt;code&gt;columns&lt;/code&gt;(열로 펼칠 키), &lt;code&gt;values&lt;/code&gt;(칸을 채울 값)로 long 데이터를 wide로 재구성한다고 정의합니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.pivot_table.html&quot;&gt;pandas.pivot_table&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;# pandas 공식 문서 예제 — long을 index/columns/values로 세운다&lt;/span&gt;
pd.&lt;span class=&quot;fn&quot;&gt;pivot_table&lt;/span&gt;(df, values=[&lt;span class=&quot;st&quot;&gt;&quot;D&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;E&quot;&lt;/span&gt;], index=[&lt;span class=&quot;st&quot;&gt;&quot;A&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;C&quot;&lt;/span&gt;], aggfunc=&lt;span class=&quot;st&quot;&gt;&quot;mean&quot;&lt;/span&gt;)
&lt;span class=&quot;cm&quot;&gt;# A,C 조합이 행이 되고, D·E가 열이 되어 값이 채워짐&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;강의 데이터에 적용하면 이렇게 됩니다.&lt;/b&gt; 2단계예요. ① 먼저 &lt;code&gt;d1~d31&lt;/code&gt;을 melt로 펴서 &lt;code&gt;day&lt;/code&gt; 열을 만들고(가로 흩어짐 해결), ② 그다음 &lt;code&gt;element&lt;/code&gt;(TMAX/TMIN)를 pivot으로 세워 두 개의 열로 올립니다(세로 흩어짐 해결).&lt;/p&gt;
&lt;pre&gt;w = pd.&lt;span class=&quot;fn&quot;&gt;read_csv&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&quot;weather.txt&quot;&lt;/span&gt;, sep=&lt;span class=&quot;st&quot;&gt;&quot;\t&quot;&lt;/span&gt;)

&lt;span class=&quot;cm&quot;&gt;# ① melt: 날짜 열 d1~d31 을 'day' 한 열로 편다&lt;/span&gt;
long = w.&lt;span class=&quot;fn&quot;&gt;melt&lt;/span&gt;(id_vars=[&lt;span class=&quot;st&quot;&gt;&quot;id&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;year&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;month&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;element&quot;&lt;/span&gt;],
             var_name=&lt;span class=&quot;st&quot;&gt;&quot;day&quot;&lt;/span&gt;, value_name=&lt;span class=&quot;st&quot;&gt;&quot;value&quot;&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;dropna&lt;/span&gt;()
long[&lt;span class=&quot;st&quot;&gt;&quot;day&quot;&lt;/span&gt;] = long[&lt;span class=&quot;st&quot;&gt;&quot;day&quot;&lt;/span&gt;].str.&lt;span class=&quot;fn&quot;&gt;replace&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&quot;d&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;&quot;&lt;/span&gt;,regex=&lt;span class=&quot;kw&quot;&gt;False&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;astype&lt;/span&gt;(&lt;span class=&quot;kw&quot;&gt;int&lt;/span&gt;)
&lt;span class=&quot;cm&quot;&gt;# 이 시점: element(TMAX/TMIN)가 아직 '값'으로 한 열에 눌려 있음 → 아직 안 tidy&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;# ② pivot: element 를 열로 세운다 (TMAX, TMIN 각자 열로)&lt;/span&gt;
wide = long.&lt;span class=&quot;fn&quot;&gt;pivot_table&lt;/span&gt;(index=[&lt;span class=&quot;st&quot;&gt;&quot;id&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;year&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;month&quot;&lt;/span&gt;,&lt;span class=&quot;st&quot;&gt;&quot;day&quot;&lt;/span&gt;],
                        columns=&lt;span class=&quot;st&quot;&gt;&quot;element&quot;&lt;/span&gt;, values=&lt;span class=&quot;st&quot;&gt;&quot;value&quot;&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;reset_index&lt;/span&gt;()
&lt;span class=&quot;fn&quot;&gt;print&lt;/span&gt;(wide.&lt;span class=&quot;fn&quot;&gt;head&lt;/span&gt;(&lt;span class=&quot;nu&quot;&gt;3&lt;/span&gt;))
&lt;span class=&quot;cm&quot;&gt;#            id  year  month  day   TMAX   TMIN&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# MX000017004  2010      1   30  278.0  145.0   ← 하루 = 한 행, 기온 = 각 열&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# MX000017004  2010      2    2  273.0  144.0   (값은 0.1℃ 단위: 278 = 27.8℃)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;# MX000017004  2010      2    3  241.0  144.0&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;포인트는 &lt;code&gt;element&lt;/code&gt; 열이 &lt;b&gt;변수가 아니라 변수 이름을 담은 열&lt;/b&gt;이었다는 겁니다. &lt;code&gt;TMAX&lt;/code&gt;와 &lt;code&gt;TMIN&lt;/code&gt;은 서로 다른 측정량이니 각자 열이어야 맞습니다. pivot으로 세우고 나면 &quot;하루 = 한 행, 최고·최저기온 = 각 열&quot;이라는 tidy 형태가 완성됩니다. R에서는 이 반대 방향을 &lt;code&gt;pivot_wider()&lt;/code&gt;가 맡습니다.&lt;/p&gt;

&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;pandas 공식 예제&lt;/th&gt;&lt;th&gt;강의 데이터(weather) 적용&lt;/th&gt;&lt;th&gt;차이 · 이유&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;행 키&lt;/td&gt;&lt;td&gt;&lt;code&gt;index=[&quot;A&quot;,&quot;C&quot;]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;index=[id,year,month,day]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;관측치를 식별하는 키 — 개념 동일&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;펼칠 키&lt;/td&gt;&lt;td&gt;&lt;code&gt;columns&lt;/code&gt; 미사용(예제)&lt;/td&gt;&lt;td&gt;&lt;code&gt;columns=&quot;element&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TMAX/TMIN을 열로 세우는 게 핵심&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;선행 단계&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;td&gt;&lt;code&gt;melt&lt;/code&gt; 먼저&lt;/td&gt;&lt;td&gt;가로·세로 양쪽 흩어짐이라 melt+pivot 2단&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;방향&lt;/td&gt;&lt;td&gt;long → wide&lt;/td&gt;&lt;td&gt;long → wide&lt;/td&gt;&lt;td&gt;melt의 역연산 — 원리 동일&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  &lt;code&gt;pivot&lt;/code&gt; vs &lt;code&gt;pivot_table&lt;/code&gt;: 같은 (행,열) 조합에 값이 하나뿐이면 &lt;code&gt;pivot&lt;/code&gt;, 여러 개라 집계(mean·sum 등)가 필요하면 &lt;code&gt;pivot_table&lt;/code&gt;을 씁니다. 중복 키에서 &lt;code&gt;pivot&lt;/code&gt;은 에러, &lt;code&gt;pivot_table&lt;/code&gt;은 &lt;code&gt;aggfunc&lt;/code&gt;로 뭉갭니다.&lt;/div&gt;
&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; — &quot;long으로 저장, wide로 표현&quot;이 실무 공식입니다. DB·수집은 tidy(long)로 쌓아두고, 리포트 피벗테이블이나 상관행렬·히트맵을 그릴 때만 &lt;code&gt;pivot&lt;/code&gt;으로 잠깐 wide로 세웁니다.&lt;/div&gt;
&lt;p class=&quot;next&quot;&gt;→ 세 도구를 한 장으로 정리하고 닫겠습니다.&lt;/p&gt;

&lt;h2 id=&quot;s7&quot;&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;
&lt;div class=&quot;ts&quot;&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;증상 (지저분한 유형)&lt;/th&gt;&lt;th&gt;도구&lt;/th&gt;&lt;th&gt;pandas 패턴&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;열 이름이 사실은 값 (wide)&lt;/td&gt;&lt;td&gt;melt&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.melt(id_vars=..., var_name=..., value_name=...)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;한 열에 변수가 여러 개&lt;/td&gt;&lt;td&gt;split&lt;/td&gt;&lt;td&gt;&lt;code&gt;s.str.split(sep, expand=True)&lt;/code&gt; / &lt;code&gt;s.str[a:b]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;값이 행·열 양쪽에 흩어짐&lt;/td&gt;&lt;td&gt;pivot&lt;/td&gt;&lt;td&gt;&lt;code&gt;df.pivot_table(index=..., columns=..., values=...)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;(참고) long→wide 되돌리기&lt;/td&gt;&lt;td&gt;pivot&lt;/td&gt;&lt;td&gt;melt의 역연산 — R은 &lt;code&gt;pivot_wider()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class=&quot;box note&quot;&gt;  &lt;b&gt;핵심 4가지&lt;/b&gt;&lt;br&gt;
1. &lt;b&gt;Tidy = 변수는 열, 관측치는 행, 관측단위는 표&lt;/b&gt; (Wickham 3원칙 = DB 제3정규형).&lt;br&gt;
2. 사람이 보기 좋은 표(wide)와 기계가 다루기 좋은 표(long)는 다르다 — 분석은 &lt;b&gt;long&lt;/b&gt;이 기본.&lt;br&gt;
3. 지저분함은 대부분 &lt;b&gt;열이름=값 / 한 열에 여러 변수 / 행·열 양쪽 흩어짐&lt;/b&gt;으로 수렴한다.&lt;br&gt;
4. 고치는 도구는 &lt;b&gt;melt(펴기) · split(쪼개기) · pivot(세우기)&lt;/b&gt; 셋이면 충분하다.&lt;/div&gt;

&lt;div class=&quot;box tip&quot;&gt;  강의의 다른 명언도 같은 이야기입니다 — &lt;em&gt;&quot;데이터 분석의 80%는 데이터 청소와 준비에 쓰인다&quot;&lt;/em&gt;(Dasu &amp;amp; Johnson, 위컴 논문 서두 인용). tidy 구조를 처음에 잡아두면 그 80%가 훨씬 가벼워집니다.&lt;/div&gt;

&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Hadley Wickham, &lt;a href=&quot;https://vita.had.co.nz/papers/tidy-data.pdf&quot;&gt;Tidy Data&lt;/a&gt; — Journal of Statistical Software (tidy 3원칙 §2.3, 5대 유형 §3의 원전)&lt;/li&gt;
&lt;li&gt;pandas 공식 문서 — &lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.melt.html&quot;&gt;melt&lt;/a&gt; · &lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.pivot_table.html&quot;&gt;pivot_table&lt;/a&gt; · &lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.Series.str.split.html&quot;&gt;Series.str.split&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;tidyr(R, Wickham) 공식 문서 — &lt;a href=&quot;https://tidyr.tidyverse.org/reference/pivot_longer.html&quot;&gt;pivot_longer&lt;/a&gt; (melt/split의 후속, &lt;code&gt;relig_income&lt;/code&gt; 예제)&lt;/li&gt;
&lt;li&gt;데이터 출처: 강의 첨부 &lt;code&gt;tidy-data-master/data&lt;/code&gt; (pew·tb·weather — WHO·Pew·GHCN 원자료)&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/122</guid>
      <comments>https://hskywith.tistory.com/122#entry122comment</comments>
      <pubDate>Mon, 31 Aug 2026 14:03:02 +0900</pubDate>
    </item>
    <item>
      <title>NumPy는 왜 빠른가 &amp;mdash; ndarray 메모리 모델부터 einsum까지</title>
      <link>https://hskywith.tistory.com/121</link>
      <description>&lt;!-- title: NumPy는 왜 빠른가 — ndarray 메모리 모델부터 einsum까지 | date: 2026-08-27 | tags: NumPy, ndarray, strides, broadcasting, einsum, 복습 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- ↑ 티스토리 편집기에서 제목·태그 입력용 메모. 본문엔 아래 {{CONTENT}}만 붙여넣는다. --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;
.rp{max-width:760px;margin:0 auto;padding:4px 0 40px;background:transparent;color:#e6e7ea;
 font-family:&quot;Pretendard&quot;,&quot;Pretendard Variable&quot;,system-ui,-apple-system,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,sans-serif;
 font-size:17px;line-height:1.8;-webkit-font-smoothing:antialiased;box-sizing:border-box;}
.rp *{box-sizing:border-box;}
.rp h2{font-size:1.4rem;line-height:1.35;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid #2c2e35;color:#f4f5f7;scroll-margin-top:16px;}
.rp p{margin:0 0 1.15em;}
.rp em{color:#cdd0d6;font-style:italic;}
.rp a{color:#7da9ff;text-decoration:none;border-bottom:1px solid rgba(125,169,255,.4);}
.rp ul{padding-left:1.3em;margin:0 0 1.15em;}
.rp li{margin:.35em 0;}
.rp .next{color:#9aa0ab;font-style:italic;margin:0 0 1.15em;}
.rp code{font-family:&quot;D2Coding&quot;,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85em;background:#262932;padding:2px 6px;border-radius:5px;color:#e9d9b0;}
.rp pre{background:#0f1013;color:#e6e6e6;font-family:&quot;D2Coding&quot;,ui-monospace,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.7;padding:16px 18px;border-radius:10px;overflow-x:auto;margin:0 0 1.4em;border:0;}
.rp pre code{background:none;padding:0;font-size:inherit;color:inherit;}
.rp .cm{color:#7f8796;} .rp .st{color:#98c379;} .rp .nu{color:#d19a66;} .rp .fn{color:#61afef;} .rp .kw{color:#c586c0;}
.rp .ts{overflow-x:auto;margin:0 0 1.4em;}
.rp table{border-collapse:collapse;width:100%;font-size:.92rem;}
.rp th,.rp td{text-align:left;padding:8px 12px;border-bottom:1px solid #2c2e35;vertical-align:top;}
.rp thead th{border-bottom:2px solid #3a3d45;font-weight:700;white-space:nowrap;color:#f4f5f7;}
.rp td code,.rp th code{white-space:nowrap;}
.rp .box{border-radius:10px;padding:13px 17px;margin:0 0 1.4em;border:1px solid #2c2e35;}
.rp .box p:last-child{margin-bottom:0;}
.rp .note{background:#151d2b;border-color:#24344d;} .rp .tip{background:#132318;border-color:#245036;}
.rp .warn{background:#241f12;border-color:#4a3d1c;} .rp .use{background:#1d1830;border-color:#3a2f5c;}
.rp .use .h{font-weight:700;color:#b794f6;}
.rp hr{border:0;border-top:1px solid #2c2e35;margin:40px 0;}
.rp .src{color:#9aa0ab;font-size:.9em;}
.rp .toc{background:#1e2026;border:1px solid #2c2e35;border-radius:12px;padding:14px 20px;margin:0 0 34px;font-size:.93rem;}
.rp .toc .t{font-weight:700;font-size:.82rem;color:#9aa0ab;letter-spacing:.03em;margin-bottom:6px;}
.rp .toc ol{margin:0;padding-left:1.4em;}
.rp .toc li{margin:3px 0;} .rp .toc a{color:#d7dae0;border:0;} .rp .toc a:hover{color:#7da9ff;}
.rp .map{border:1px solid #2c2e35;border-radius:14px;padding:20px 16px;margin:0 0 18px;background:#1e2026;}
.rp .map .c{text-align:center;margin-bottom:5px;}
.rp .map .mb{display:inline-block;background:#26282f;border:1px solid #363943;border-radius:9px;padding:7px 13px;font-size:.9rem;}
.rp .map .mb b{color:#7da9ff;}
.rp .map .ar{text-align:center;color:#9aa0ab;font-size:.84rem;margin:4px 0;}
.rp .map .row{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:6px 0;}
.rp .map .cell{background:#26282f;border:1px solid #363943;border-radius:9px;padding:9px 7px;text-align:center;font-size:.8rem;line-height:1.45;}
.rp .map .cell .q{display:block;color:#7da9ff;font-weight:700;margin-bottom:2px;}
.rp .map .cell .s{display:block;font-family:&quot;D2Coding&quot;,monospace;font-size:.76rem;color:#9aa0ab;margin-top:3px;}
.rp .map .ft{text-align:center;font-size:.85rem;color:#9aa0ab;margin-top:8px;}
@media(max-width:520px){.rp .map .row{grid-template-columns:repeat(2,1fr);}}
&lt;/style&gt;
&lt;/div&gt;
&lt;div class=&quot;rp&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 강의자료는 &lt;i&gt;NumPy/SciPy&lt;/i&gt; 한 편인데, 분량이 124장이나 됩니다. &quot;왜 파이썬은 느린가&quot;에서 출발해서 ndarray 내부 구조, stride, broadcasting, ufunc, 그리고 마지막엔 &lt;code&gt;einsum&lt;/code&gt;까지 훑는, 사실상 &quot;NumPy를 어떻게 &lt;i&gt;생각&lt;/i&gt;해야 하는가&quot;를 다룬 자료더라고요. 슬라이드가 그림 위주라 개념 흐름은 좋은데, 정작 &quot;그래서 왜?&quot;를 묻는 대목마다 근거가 비어 있어서 이 글에서 공식 문서로 메꿔가며 다시 정리해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의 전체를 한 문장으로 압축하면 이렇습니다 &amp;mdash; &lt;b&gt;&quot;파이썬 리스트가 아니라 ndarray로 생각하라. 메모리 레이아웃을 이해하면 뷰&amp;middot;브로드캐스팅&amp;middot;벡터화가 왜 거의 공짜인지 보인다.&quot;&lt;/b&gt; 그래서 이 글도 그 축을 따라, 메모리 모델을 바닥에 깔고 위로 쌓아 올리는 순서로 갑니다. 자료에 나온 axis&amp;middot;dtype&amp;middot;indexing 같은 조각들은 이 네 기둥 안에 녹여 넣었어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 큰 그림 &amp;mdash; 조각들이 어떻게 맞물리나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NumPy 함수를 외우려 들면 끝이 없습니다. 대신 질문 네 개로 쪼개면, 강의에 나온 수십 개 개념이 각자 어느 칸에 들어가는지 보여요.&lt;/p&gt;
&lt;div class=&quot;map&quot;&gt;
&lt;div class=&quot;c&quot;&gt;&lt;span class=&quot;mb&quot;&gt;&lt;b&gt;NumPy가 빠른 이유&lt;/b&gt; 를 이렇게 쪼갠다&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ar&quot;&gt;▼&lt;/div&gt;
&lt;div class=&quot;row&quot;&gt;
&lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;무엇을 저장?&lt;/span&gt;연속 메모리 + 타입&lt;span class=&quot;s&quot;&gt;data&amp;middot;dtype&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;어떻게 위치를?&lt;/span&gt;보폭으로 계산&lt;span class=&quot;s&quot;&gt;strides&amp;middot;view&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;루프 없이 계산?&lt;/span&gt;벡터화&amp;middot;확장&lt;span class=&quot;s&quot;&gt;ufunc&amp;middot;broadcast&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;cell&quot;&gt;&lt;span class=&quot;q&quot;&gt;짧게 표현?&lt;/span&gt;첨자로 지정&lt;span class=&quot;s&quot;&gt;einsum&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ft&quot;&gt;아래 각 섹션이 이 네 칸을 왼쪽부터 하나씩 채웁니다.&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 왼쪽 칸이 가장 밑바닥입니다. 여기서부터 시작하죠.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. ndarray &amp;mdash; 연속 메모리 한 덩어리 + 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;NumPy가 리스트보다 빠르다&quot;는 말은 많이 듣지만, 그 이유는 결국 &lt;i&gt;메모리에 값을 어떻게 놓느냐&lt;/i&gt;에서 갈립니다. 그래서 첫 칸이 ndarray의 정체예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ndarray&lt;/code&gt; (N차원 배열 객체): 같은 타입의 값들을 &lt;b&gt;메모리에 빈틈없이 이어 붙인&lt;/b&gt; 한 덩어리 + 그걸 어떻게 읽을지 알려주는 설명서. 강의 슬라이드는 이걸 &lt;i&gt;&quot;block of memory + indexing scheme + data type descriptor&quot;&lt;/i&gt;라는 세 조각으로 표현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유하면, 리스트는 &quot;여기저기 흩어진 물건들의 주소록&quot;이고 ndarray는 &quot;번호표 붙여 한 줄로 세워둔 사물함&quot;입니다. 파이썬 리스트는 각 원소가 사실 &lt;i&gt;객체를 가리키는 포인터&lt;/i&gt;라, 값들이 메모리 곳곳에 흩어져 있어요(강의 11쪽의 그 화살표 그림이 이걸 말합니다). 흩어져 있으면 CPU 캐시가 무력해지죠. ndarray는 반대로 값 자체가 한 곳에 붙어 있어서, CPU가 &quot;다음 것, 그다음 것&quot;을 예측하며 훑을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; ndarray는 &quot;연속된 1차원 메모리 구간과, N개의 정수를 그 블록 안 원소 위치로 대응시키는 인덱싱 방식의 결합&quot;이라고 정의합니다. 각 원소가 몇 바이트이고 그 바이트를 어떻게 해석할지는 &lt;code&gt;dtype&lt;/code&gt;이 정한다고 못 박고요. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout-of-an-ndarray&quot;&gt;NumPy &amp;mdash; Internal memory layout of an ndarray&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 공식 문서 예제 &amp;mdash; ndarray의 세 조각을 눈으로 확인
import numpy as np
x = np.array([[1, 2, 3], [4, 5, 6]], np.int32)
x.shape     # (2, 3)  &amp;larr; 인덱싱 방식(모양)
x.dtype     # dtype('int32')  &amp;larr; 데이터 타입 descriptor
x.strides   # (12, 4)  &amp;larr; 다음 원소까지 몇 바이트 점프?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dtype&lt;/code&gt; (data type, 자료형): 원소 하나가 차지하는 바이트 수와 해석 방식 &amp;mdash; &lt;code&gt;int32&lt;/code&gt;면 4바이트 정수, &lt;code&gt;float64&lt;/code&gt;면 8바이트 실수. 강의가 &lt;i&gt;&quot;타입을 명시하여 원소의 배열로 데이터를 유지&quot;&lt;/i&gt;한다고 한 게 이 부분입니다. 리스트처럼 아무 타입이나 섞을 수 없는(homogeneous, 동종) 제약이 오히려 속도의 대가예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강의 실습에서는 이렇게 짚었습니다.&lt;/b&gt; 슬라이드 50쪽이 &lt;code&gt;float32&lt;/code&gt; vs &lt;code&gt;float64&lt;/code&gt;를 직접 재봤는데, 이게 dtype 선택이 왜 성능 문제인지 보여주는 좋은 예제라 가져왔습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;a = np.zeros((1e6,), dtype=np.float64)
b = np.zeros((1e6,), dtype=np.float32)
%timeit a*a   # 1.78 ms per loop
%timeit b*b   # 1.07 ms per loop  &amp;larr; 절반 크기 = 더 빠름&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;ts&quot;&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;공식 문서 예제&lt;/th&gt;
&lt;th&gt;강의 실습&lt;/th&gt;
&lt;th&gt;차이 &amp;middot; 이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;보려는 것&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shape/dtype/strides&lt;/code&gt; 구조 확인&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float32/64&lt;/code&gt; 속도 비교&lt;/td&gt;
&lt;td&gt;같은 &quot;dtype이 메모리를 정한다&quot;를 각각 &lt;i&gt;구조&lt;/i&gt;와 &lt;i&gt;성능&lt;/i&gt; 관점으로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심 메시지&lt;/td&gt;
&lt;td&gt;dtype이 바이트 해석을 결정&lt;/td&gt;
&lt;td&gt;작은 dtype = 절반 메모리 = 빠름&lt;/td&gt;
&lt;td&gt;강의는 결과로, 문서는 원리로 같은 곳을 가리킴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;box warn&quot;&gt;⚠️ 강의도 경고하듯, &lt;code&gt;float32&lt;/code&gt;는 반올림 오차가 더 큽니다. &quot;정말 필요할 때만&quot; 쓰라고 하죠 &amp;mdash; 딥러닝 추론처럼 메모리&amp;middot;대역폭이 병목일 때가 대표적인 &quot;정말 필요한&quot; 경우입니다.&lt;/div&gt;
&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; &amp;mdash; 이미지&amp;middot;텐서를 다룰 때 &lt;code&gt;uint8&lt;/code&gt;(0~255)로 읽어 메모리를 아끼고, 학습 직전에 &lt;code&gt;astype(np.float32)&lt;/code&gt;로 올립니다. 강의 25~36쪽의 &quot;이미지 = (H, W, 3) 텐서, RGB는 0~255&quot; 이야기가 정확히 이 dtype 선택 문제로 이어져요.&lt;/div&gt;
&lt;p class=&quot;next&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 다음은 &lt;code&gt;strides&lt;/code&gt;입니다. 방금 &lt;code&gt;(12, 4)&lt;/code&gt;로 슬쩍 봤는데, 이 숫자 두 개가 NumPy의 마법 절반을 담당합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. strides와 view &amp;mdash; 값을 복사하지 않고 다르게 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 값이 한 줄로 깔려 있다면, 2차원 배열의 &lt;code&gt;a[1][2]&lt;/code&gt;는 그 한 줄에서 몇 번째 바이트일까요? 이 &quot;몇 번째&quot;를 계산하는 규칙이 stride입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;strides&lt;/code&gt; (보폭): 각 축에서 &lt;b&gt;다음 원소로 가려면 메모리를 몇 바이트 건너뛰어야 하는지&lt;/b&gt; 적어둔 튜플. 앞의 &lt;code&gt;(12, 4)&lt;/code&gt;는 &quot;다음 행은 12바이트, 다음 열은 4바이트 점프&quot;라는 뜻이에요(int32 = 4바이트니까, 한 행에 원소 3개 &amp;rarr; 12바이트).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; N차원 인덱스 &lt;code&gt;(n₀, &amp;hellip;, n_{N-1})&lt;/code&gt;가 가리키는 바이트 위치(offset)는 &lt;b&gt;각 인덱스 &amp;times; 그 축의 stride를 전부 더한 값&lt;/b&gt;이라고 정의합니다. 수식으로는 &lt;code&gt;offset = &amp;Sigma; (sₖ &amp;times; nₖ)&lt;/code&gt;. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout-of-an-ndarray&quot;&gt;NumPy &amp;mdash; Internal memory layout of an ndarray&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 공식 문서 예제 &amp;mdash; 같은 데이터, 다른 stride = 다른 배열
x = np.array([[1,2,3],[4,5,6]], np.int32)
x.strides            # (12, 4)  &amp;rarr; C-order(행 우선)
# a[1][2] 위치 = 1*12 + 2*4 = 20바이트째 = 값 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 강의의 핵심 통찰이 나옵니다. &lt;b&gt;모양(shape)을 바꾸거나 축을 뒤집는 일은 데이터를 옮기지 않고 stride만 고쳐 쓰면 된다&lt;/b&gt;는 거죠. 강의 57~58쪽의 C-order(행 우선, NumPy 기본) vs Fortran-order(열 우선) 그림이 이걸 말합니다 &amp;mdash; 같은 메모리를 stride &lt;code&gt;(12,4)&lt;/code&gt;로 읽으면 C, &lt;code&gt;(4,12)&lt;/code&gt;로 읽으면 Fortran입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강의 실습에서는 이렇게 짚었습니다.&lt;/b&gt; 슬라이드 66쪽이 &lt;i&gt;&quot;Not a copy, but a VIEW!!!&quot;&lt;/i&gt;라고 강조하는데, 이게 stride의 실전 귀결입니다. 슬라이싱은 stride를 조정한 &lt;i&gt;새 창문&lt;/i&gt;일 뿐, 데이터를 복사하지 않아요.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;a = np.arange(10)
y = a[1:3]        # 기본 슬라이싱 &amp;rarr; view(뷰)
a[1:3] = [10, 11]
y                  # array([10, 11])  &amp;larr; 원본을 바꿨는데 y도 바뀜!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;view&lt;/code&gt; (뷰): 같은 데이터 버퍼를 stride&amp;middot;dtype만 바꿔 다르게 바라보는 배열 &amp;mdash; 복사가 아니라서 &lt;b&gt;한쪽을 고치면 다른 쪽도 바뀝니다&lt;/b&gt;. 반대로 &lt;code&gt;copy&lt;/code&gt;(복사)는 버퍼까지 통째로 복제해 서로 독립이고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서는 경계를 이렇게 긋습니다.&lt;/b&gt; &quot;기본 인덱싱(슬라이싱)은 &lt;b&gt;항상 뷰&lt;/b&gt;를 만들고, 고급 인덱싱(fancy indexing)은 &lt;b&gt;항상 복사&lt;/b&gt;를 만든다&quot;고요. 뷰인지 복사인지는 &lt;code&gt;.base&lt;/code&gt;로 확인 &amp;mdash; 뷰면 원본을, 복사면 &lt;code&gt;None&lt;/code&gt;을 돌려줍니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://numpy.org/doc/stable/user/basics.copies.html&quot;&gt;NumPy &amp;mdash; Copies and views&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 공식 문서 예제 &amp;mdash; view와 copy를 .base로 구별
x = np.arange(9).reshape(3, 3)
y = x[1:3]        # 기본 슬라이싱
y.base is None    # False  &amp;rarr; 뷰 (원본 공유)
z = x[[1, 2]]     # fancy indexing(정수 배열)
z.base is None    # True   &amp;rarr; 복사 (독립)&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;ts&quot;&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;인덱싱 방식&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;결과&lt;/th&gt;
&lt;th&gt;왜&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기본 슬라이싱&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a[1:3]&lt;/code&gt;, &lt;code&gt;a[::2]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;view&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;offset&amp;middot;stride 조정만으로 표현 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fancy indexing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a[[0,2,4]]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;copy&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;불규칙한 위치 &amp;rarr; stride로 못 그림, 새로 모아야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;boolean mask&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a[a&amp;lt;0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;copy&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;위와 같은 이유 (고급 인덱싱)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;box note&quot;&gt;  강의 63쪽이 던진 &lt;code&gt;np.may_share_memory(a, b)&lt;/code&gt;가 바로 이 확인용 함수입니다. 두 배열이 메모리를 공유하는지(=뷰 관계인지) 알려줘요. 슬라이스를 수정했는데 엉뚱한 배열이 바뀐다면 십중팔구 뷰 문제입니다.&lt;/div&gt;
&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; &amp;mdash; 대용량 이미지 배치를 슬라이싱으로 잘라 전처리할 때, 뷰라서 메모리가 안 늘어 좋지만 &lt;i&gt;원본을 건드리면 원본이 오염됩니다&lt;/i&gt;. 안전하게 떼어내려면 &lt;code&gt;.copy()&lt;/code&gt;를 명시하세요. &quot;왜 원본 데이터가 학습 중에 바뀌었지?&quot; 버그의 단골 원인이 이겁니다.&lt;/div&gt;
&lt;p class=&quot;next&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 지금까지가 &quot;데이터를 어떻게 놓고 어떻게 짚나&quot;였습니다. 이제 그 위에서 &lt;b&gt;계산&lt;/b&gt;을 어떻게 루프 없이 하는지로 넘어갑니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. vectorization &amp;middot; broadcasting &amp;middot; ufunc &amp;mdash; 루프를 C로 내려보내기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연속 메모리에 값이 깔려 있으면, &quot;전부 다 더해&quot;라는 연산을 파이썬 &lt;code&gt;for&lt;/code&gt; 없이 한 방에 내려보낼 수 있습니다. 이게 세 번째 칸이고, 서로 맞물린 세 단어로 이뤄져 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;vectorization&lt;/code&gt; (벡터화): 원소마다 &lt;code&gt;for&lt;/code&gt;를 도는 대신, &lt;b&gt;배열 전체에 연산을 한 번에 거는&lt;/b&gt; 방식. 강의 15쪽의 &quot;5 operations &amp;rarr; 2 operations&quot; 그림이 이 아이디어입니다. 루프가 사라지는 게 아니라, &lt;i&gt;느린 파이썬 루프가 빠른 C 루프로 내려가는&lt;/i&gt; 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ufunc&lt;/code&gt; (universal function, 범용 함수): 배열을 &lt;b&gt;원소별(element-wise)로 처리하는, 벡터화된 함수&lt;/b&gt; &amp;mdash; &lt;code&gt;np.add&lt;/code&gt;, &lt;code&gt;np.sqrt&lt;/code&gt;, &lt;code&gt;np.sin&lt;/code&gt; 같은 것들. 강의 84쪽이 &lt;i&gt;&quot;이런 종류의 연산을 ufunc라 한다&quot;&lt;/i&gt;고 한 그거요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; ufunc는 &quot;ndarray를 &lt;b&gt;원소 대 원소로 처리&lt;/b&gt;하며, 배열 브로드캐스팅과 타입 캐스팅 등을 지원하는 함수&quot;이고, &quot;고정된 입력을 받아 고정된 출력을 내는 함수의 &lt;b&gt;'벡터화된' 래퍼&lt;/b&gt;&quot;라고 정의합니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://numpy.org/doc/stable/reference/ufuncs.html&quot;&gt;NumPy &amp;mdash; Universal functions (ufunc)&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 공식 문서 예제 &amp;mdash; 원소별 처리 + 브로드캐스팅
a = np.array([1.0, 2.0, 3.0])
b = 2.0
a * b     # array([2., 4., 6.])  &amp;larr; 스칼라가 전체에 퍼짐&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 위에서 &lt;code&gt;a&lt;/code&gt;는 모양 &lt;code&gt;(3,)&lt;/code&gt;이고 &lt;code&gt;b&lt;/code&gt;는 스칼라입니다. 모양이 다른데 어떻게 곱해질까요? 여기서 세 번째 단어가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;broadcasting&lt;/code&gt; (브로드캐스팅): 모양이 다른 배열끼리 연산할 때, &lt;b&gt;작은 배열을 큰 배열 모양에 맞춰 자동으로 늘려주는&lt;/b&gt; 규칙. 강의 79~82쪽이 스칼라&amp;middot;1D&amp;middot;2D&amp;middot;3D로 단계별로 보여준 바로 그 그림입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서는 규칙을 이렇게 못 박습니다.&lt;/b&gt; 두 배열의 모양을 &lt;b&gt;맨 뒤(오른쪽) 차원부터 왼쪽으로&lt;/b&gt; 비교해서, 각 차원이 &lt;b&gt;①크기가 같거나 ②한쪽이 1이면&lt;/b&gt; 호환된다고요. 그리고 결정적으로 &amp;mdash; 이 과정에서 &lt;i&gt;&quot;데이터를 불필요하게 복사하지 않고, 루프가 파이썬 대신 C에서 돈다&quot;&lt;/i&gt;고 합니다. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://numpy.org/doc/stable/user/basics.broadcasting.html&quot;&gt;NumPy &amp;mdash; Broadcasting&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강의 실습에서는 이렇게 짚었습니다.&lt;/b&gt; 슬라이드 80쪽의 &lt;code&gt;(4,3) + (3,)&lt;/code&gt; 예제가 규칙을 정확히 따릅니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 강의 실습 &amp;mdash; (4,3) 행렬에 (3,) 벡터 더하기
A = np.arange(12).reshape(4, 3)   # shape (4,3)
v = np.array([0, 1, 2])            # shape (3,)
A + v
# 맨 뒤 차원: 3 == 3 ✓ / 그 앞: 4 vs (없음&amp;rarr;1) ✓
# v가 4개 행에 각각 복사된 것처럼 동작 (실제 복사는 안 함)&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;ts&quot;&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;왼쪽 shape&lt;/th&gt;
&lt;th&gt;오른쪽 shape&lt;/th&gt;
&lt;th&gt;맞춰본 결과&lt;/th&gt;
&lt;th&gt;되나?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(4, 3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(3,)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(4, 3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 뒤부터 3=3, 4=4(1로 채움)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(4, 3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(4, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(4, 3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 3 vs 1 &amp;rarr; 1이 늘어남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(4, 3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(4,)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;❌ 뒤부터 3 vs 4, 둘 다 1 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산 결과를 &lt;i&gt;어느 축으로&lt;/i&gt; 뭉갤지는 &lt;code&gt;axis&lt;/code&gt;가 정합니다. &lt;code&gt;axis&lt;/code&gt; (축): 다차원 배열에서 연산이 진행되는 방향 &amp;mdash; &lt;code&gt;axis=0&lt;/code&gt;은 행 방향(세로로 훑음), &lt;code&gt;axis=1&lt;/code&gt;은 열 방향(가로로 훑음). 강의 60쪽의 &lt;code&gt;np.mean&lt;/code&gt; 예제가 헷갈리기 딱 좋은 대목이라 짚고 갑니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;a = np.arange(30).reshape(10, 3)
a.mean(axis=0)   # shape (3,) &amp;rarr; 각 '열'의 평균. 축 0이 사라짐
a.mean(axis=1)   # shape (10,) &amp;rarr; 각 '행'의 평균. 축 1이 사라짐&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;box tip&quot;&gt;  &lt;code&gt;axis=k&lt;/code&gt;는 &quot;&lt;b&gt;k번째 축을 없앤다&lt;/b&gt;&quot;로 외우면 안 헷갈립니다. &lt;code&gt;(10, 3)&lt;/code&gt;에서 &lt;code&gt;axis=0&lt;/code&gt;을 주면 0번 축(크기 10)이 뭉개져 &lt;code&gt;(3,)&lt;/code&gt;이 남죠. &quot;0이니까 행별?&quot;로 외우면 매번 헷갈립니다.&lt;/div&gt;
&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; &amp;mdash; 데이터 정규화가 교과서적 사례입니다. &lt;code&gt;(N, D)&lt;/code&gt; 특성 행렬에서 &lt;code&gt;X - X.mean(axis=0)&lt;/code&gt;는 브로드캐스팅으로 각 특성의 평균을 빼주죠(평균 &lt;code&gt;(D,)&lt;/code&gt;가 &lt;code&gt;N&lt;/code&gt;개 행에 퍼짐). 이미지에서 채널별 평균을 빼는 전처리도 정확히 같은 패턴입니다.&lt;/div&gt;
&lt;p class=&quot;next&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 여기까지가 강의의 &quot;기초 3기둥&quot;입니다. 마지막으로, 이 모든 연산을 &lt;b&gt;첨자 문자열 하나&lt;/b&gt;로 표현하는 einsum으로 마무리합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. einsum &amp;mdash; 첨자로 텐서 연산을 받아쓰기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬곱, 전치, 대각합, 배치 연산&amp;hellip; 강의 후반 20쪽이 통째로 &lt;code&gt;einsum&lt;/code&gt; 하나에 할애돼 있습니다. 슬라이드 105쪽 제목이 &lt;i&gt;&quot;Einsum is All You Need&quot;&lt;/i&gt;일 정도예요. 앞의 벡터화&amp;middot;브로드캐스팅을 &lt;i&gt;선언적으로&lt;/i&gt; 적는 방법이라 마지막 칸에 놓았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;einsum&lt;/code&gt; (Einstein summation, 아인슈타인 합 표기): 각 축에 &lt;b&gt;알파벳 라벨을 붙이고, 그 라벨의 등장 패턴만으로 연산을 지정&lt;/b&gt;하는 방법. &lt;code&gt;&quot;ik,kj-&amp;gt;ij&quot;&lt;/code&gt; 한 줄이 행렬곱이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙은 딱 두 개예요(강의 122쪽 요약과 일치합니다):&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;반복되는 라벨은 합친다&lt;/b&gt; &amp;mdash; 곱하고 그 축으로 더함(summation index).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력(&lt;code&gt;-&amp;gt;&lt;/code&gt; 뒤)에 남긴 라벨은 유지한다&lt;/b&gt; &amp;mdash; 안 남기면 그 축은 사라짐(free index).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서는 이렇게 설명합니다.&lt;/b&gt; &quot;라벨이 반복되면 그 축으로 합산되므로 &lt;code&gt;np.einsum('i,i', a, b)&lt;/code&gt;는 &lt;code&gt;np.inner(a,b)&lt;/code&gt;와 같고, 라벨이 한 번만 나오면 합산하지 않는다&quot;고요. &lt;span class=&quot;src&quot;&gt;(출처: &lt;a href=&quot;https://numpy.org/doc/stable/reference/generated/numpy.einsum.html&quot;&gt;NumPy &amp;mdash; numpy.einsum&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;# 공식 문서 예제 &amp;mdash; 라벨 패턴만으로 연산이 결정됨
a = np.arange(25).reshape(5, 5)
np.einsum('ii', a)        # 60      &amp;rarr; 대각합(trace): i가 반복 &amp;rarr; 합산
np.einsum('ii-&amp;gt;i', a)     # [0,6,12,18,24] &amp;rarr; 대각선: i를 남김 &amp;rarr; 유지
np.einsum('ij-&amp;gt;ji', a)    # 전치(transpose): 라벨 순서만 뒤집음
np.einsum('ij,jk-&amp;gt;ik', a, b) # 행렬곱: j가 반복 &amp;rarr; j축으로 합산&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강의 실습에서는 이렇게 짚었습니다.&lt;/b&gt; 슬라이드 111쪽이 3중 &lt;code&gt;for&lt;/code&gt; 루프 행렬곱을 einsum 한 줄로 접는 과정을 색 화살표로 보여줍니다. &quot;이 장황한 루프가 결국 이 한 줄&quot;이라는 대비가 einsum의 매력을 정확히 찌릅니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 강의 실습 &amp;mdash; 행렬곱을 루프로 vs einsum으로
# [기존] 3중 루프
C = np.empty((Ni, Nj))
for i in range(Ni):
    for j in range(Nj):
        total = 0
        for k in range(Nk):   # k: 합산 인덱스(반복 라벨)
            total += A[i, k] * B[k, j]
        C[i, j] = total          # i, j: 자유 인덱스(출력에 남음)

# [einsum] 한 줄 &amp;mdash; k가 반복 &amp;rarr; k로 합산, ij는 출력에 남김
C = np.einsum(&quot;ik,kj-&amp;gt;ij&quot;, A, B)&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;ts&quot;&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산&lt;/th&gt;
&lt;th&gt;강의 표기&lt;/th&gt;
&lt;th&gt;공식 표기&lt;/th&gt;
&lt;th&gt;차이 &amp;middot; 이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;대각합(trace)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;ii-&amp;gt;&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;ii&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;같은 결과. 강의는 &quot;출력 비움&quot;을 &lt;code&gt;-&amp;gt;&lt;/code&gt;로 명시(explicit), 공식은 생략(implicit) &amp;mdash; &lt;b&gt;명시형이 덜 헷갈려 실무 권장&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;ij-&amp;gt;ji&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;ij-&amp;gt;ji&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;동일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;행렬곱&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;ik,kj-&amp;gt;ij&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;ij,jk-&amp;gt;ik&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;라벨 이름만 다를 뿐 구조 동일(반복 라벨로 합산)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;box note&quot;&gt;  강의가 강조한 einsum의 진가는 &lt;b&gt;배치(batch) 차원&lt;/b&gt;입니다. 슬라이드 117쪽의 배치 외적 &lt;code&gt;&quot;Bi,Bj-&amp;gt;Bij&quot;&lt;/code&gt;처럼, 앞에 &lt;code&gt;B&lt;/code&gt; 라벨 하나만 붙이면 &quot;배치마다 반복&quot;이 공짜로 붙어요. 슬라이드 120쪽은 MLP 역전파(backprop) 전체를 einsum 몇 줄로 적는데, &lt;code&gt;np.dot&lt;/code&gt;에서 매번 고민하던 &lt;i&gt;전치&amp;middot;축 순서를 einsum이 알아서 처리&lt;/i&gt;한다는 게 핵심입니다.&lt;/div&gt;
&lt;div class=&quot;box warn&quot;&gt;⚠️ einsum은 만능처럼 보이지만, 라벨 규칙을 어기면 조용히 틀리는 게 아니라 에러를 냅니다(강의 198쪽 &quot;Bad&quot; 사례 모음). 대표적으로 &amp;mdash; 입력 첨자 그룹 수 &amp;ne; 인자 개수, 출력에 없는 입력 라벨을 출력에 넣기, 같은 라벨인데 축 크기가 다름. 라벨은 반드시 ASCII 알파벳이어야 하고요.&lt;/div&gt;
&lt;div class=&quot;box use&quot;&gt;&lt;span class=&quot;h&quot;&gt;  어디에 쓰나&lt;/span&gt; &amp;mdash; 어텐션(attention) 스코어 &lt;code&gt;QKᵀ&lt;/code&gt;를 &lt;code&gt;&quot;bqd,bkd-&amp;gt;bqk&quot;&lt;/code&gt;로 적으면 배치&amp;middot;헤드 차원을 라벨로 관리하며 실수를 줄입니다. PyTorch &lt;code&gt;torch.einsum&lt;/code&gt;, TensorFlow &lt;code&gt;tf.einsum&lt;/code&gt;도 같은 문법이라, 한 번 익히면 프레임워크를 넘나들며 씁니다.&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;치트시트 &amp;amp; 핵심 4가지&lt;/h2&gt;
&lt;div class=&quot;ts&quot;&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;하고 싶은 것&lt;/th&gt;
&lt;th&gt;패턴&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구조 확인&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x.shape&lt;/code&gt;, &lt;code&gt;x.dtype&lt;/code&gt;, &lt;code&gt;x.strides&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;뷰/복사 판별&lt;/td&gt;
&lt;td&gt;&lt;code&gt;y.base is None&lt;/code&gt;, &lt;code&gt;np.may_share_memory(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안전하게 떼어내기&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a[1:3].copy()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;축별 집계&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X.mean(axis=0)&lt;/code&gt; (0번 축을 없앰)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모양 다른 연산&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X - X.mean(axis=0)&lt;/code&gt; (broadcasting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;행렬곱&lt;/td&gt;
&lt;td&gt;&lt;code&gt;np.einsum(&quot;ik,kj-&amp;gt;ij&quot;, A, B)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대각합 / 전치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;np.einsum(&quot;ii-&amp;gt;&quot;, M)&lt;/code&gt; / &lt;code&gt;np.einsum(&quot;ij-&amp;gt;ji&quot;, M)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;box tip&quot;&gt;  딱 4가지만 기억하면 이 강의가 정리됩니다.&lt;br /&gt;1. &lt;b&gt;ndarray = 연속 메모리 + dtype + strides&lt;/b&gt; &amp;mdash; 리스트보다 빠른 근본 이유.&lt;br /&gt;2. &lt;b&gt;슬라이싱은 뷰, fancy/bool 인덱싱은 복사&lt;/b&gt; &amp;mdash; stride로 그릴 수 있으면 뷰.&lt;br /&gt;3. &lt;b&gt;broadcasting은 뒤 차원부터, 같거나 1이면 OK&lt;/b&gt; &amp;mdash; 복사 없이 C 루프로.&lt;br /&gt;4. &lt;b&gt;einsum은 반복 라벨=합산, 남긴 라벨=유지&lt;/b&gt; &amp;mdash; 텐서 연산의 받아쓰기.&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의 123쪽의 &lt;i&gt;&quot;Zen of NumPy&quot;&lt;/i&gt;가 이 네 가지를 한 줄씩 압축해 뒀더라고요 &amp;mdash; &quot;흩어진 것보다 보폭이 낫고(strided), 보폭보다 연속이 낫다(contiguous). 명령형보다 선언형이 낫다(데이터 타입을 써라). 벡터화가 명시적 루프보다 낫다.&quot; 결국 &lt;b&gt;메모리를 이해하고, 루프를 NumPy에게 넘겨라&lt;/b&gt;로 수렴합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout-of-an-ndarray&quot;&gt;NumPy &amp;mdash; Internal memory layout of an ndarray (메모리 구조 + strides 공식)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/user/basics.copies.html&quot;&gt;NumPy &amp;mdash; Copies and views (뷰 vs 복사, .base)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/user/basics.broadcasting.html&quot;&gt;NumPy &amp;mdash; Broadcasting (브로드캐스팅 규칙)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/reference/ufuncs.html&quot;&gt;NumPy &amp;mdash; Universal functions (ufunc)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://numpy.org/doc/stable/reference/generated/numpy.einsum.html&quot;&gt;NumPy &amp;mdash; numpy.einsum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rockt.github.io/2018/04/30/einsum&quot;&gt;Tim Rockt&amp;auml;schel &amp;mdash; Einsum is All You Need (강의 105쪽 인용 원문)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>AI</category>
      <author>harang Lee</author>
      <guid isPermaLink="true">https://hskywith.tistory.com/121</guid>
      <comments>https://hskywith.tistory.com/121#entry121comment</comments>
      <pubDate>Thu, 27 Aug 2026 14:35:59 +0900</pubDate>
    </item>
  </channel>
</rss>