<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>채야미의 코드레시피 </title>
    <link>https://chaeyami.tistory.com/</link>
    <description>Yummy Bytes</description>
    <language>ko</language>
    <pubDate>Thu, 16 Jul 2026 13:22:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ChaeYami</managingEditor>
    <image>
      <title>채야미의 코드레시피 </title>
      <url>https://tistory1.daumcdn.net/tistory/5851422/attach/6c1a476889064b949430e40a2e14f79f</url>
      <link>https://chaeyami.tistory.com</link>
    </image>
    <item>
      <title>[AI설계] RAG 평가와 운영 - 검색이 틀렸는가, 생성이 틀렸는가</title>
      <link>https://chaeyami.tistory.com/295</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chaeyami.tistory.com/286&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RAG&lt;/a&gt; 시스템은 단순한 LLM 호출이 아니라, 여러 단계로 이루어져있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자 질문&lt;/li&gt;
&lt;li&gt;관련 문서 검색&lt;/li&gt;
&lt;li&gt;문서를 프롬프트에 넣기&lt;/li&gt;
&lt;li&gt;LLM 답변 생성&lt;/li&gt;
&lt;li&gt;출처와 함께 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 RAG 답변이 틀렸을 때 원인도 여러 가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색이 잘못됐을 수 있다.&lt;/li&gt;
&lt;li&gt;검색은 맞았는데 LLM이 문서를 잘못 읽었을 수 있다.&lt;/li&gt;
&lt;li&gt;답은 맞았는데 출처가 틀렸을 수 있다.&lt;/li&gt;
&lt;li&gt;문서 자체가 오래됐을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RAG 평가&lt;/b&gt;는 이 원인을 분리해서 보는 작업이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시로 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문이 들어왔다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;질문:&lt;br /&gt;해외배송 상품을 환불하면 위약금은 얼마인가요?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답을 만들려면 두 개의 근거가 필요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;근거 A: 해외배송 상품의 환불 정책&lt;/li&gt;
&lt;li&gt;근거 B: 환불 정책이 참조하는 위약금 산정 조항&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 시스템은 이 두 근거를 찾아서 LLM에게 줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적인 흐름은&lt;br /&gt;질문 &amp;rarr; 검색 단계에서 A와 B를 찾음 &amp;rarr; LLM이 A와 B를 함께 읽음 &amp;rarr; 위약금 금액과 조건을 답변 &amp;rarr; A와 B를 출처로 표시&lt;br /&gt;이지만, 실제로는 여러 실패가 있을 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RAG 실패 유형&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검색 실패&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 단계에서 필요한 근거를 못 찾은 경우.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;검색 결과:&lt;br /&gt;A. 해외배송 환불 정책&lt;br /&gt;C. 국내배송 교환 정책&lt;br /&gt;D. 고객센터 운영 시간&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 근거 B: 위약금 산정 조항이 빠졌을 경우.&lt;br /&gt;이 때 LLM은 정확한 답을 만들기 어렵다. 없는 근거를 정확히 사용할 수는 없기 때문에.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성 실패&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색은 필요한 근거를 찾았지만, LLM이 잘못 답한 경우.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;검색 결과에는 A와 B가 모두 있음.&lt;br /&gt;그런데 LLM이 B의 위약금 조건을 잘못 해석함.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 검색(retrieval)은 성공했지만 생성(generation)이 실패한 경우&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처 실패&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변 내용은 맞지만 출처 표시가 틀린 경우.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;답변:&lt;br /&gt;위약금은 10,000원입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실제 근거:&lt;br /&gt;B. 위약금 산정 조항&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;표시된 출처:&lt;br /&gt;A. 해외배송 환불 정책&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 맞아도 출처가 틀리면 사용자가 검증할 수 없다. 법령, 정책, 계약서 기반 RAG에서는 출처 정확도가 매우 중요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최신성 실패&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 문서가 오래된 경우.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;현재 정책:&lt;br /&gt;2026년 6월 개정판&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;검색된 문서:&lt;br /&gt;2025년 12월 구버전&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 검색된 문서를 기준으로 답변하기 때문에, 문서가 오래되면 답변도 오래된 기준으로 나간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bif6vI/dJMcaaFZbsn/OmBeDcGpkxkAgYaM0K2vc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bif6vI/dJMcaaFZbsn/OmBeDcGpkxkAgYaM0K2vc1/img.png&quot; data-alt=&quot;RAG 실패 원인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bif6vI/dJMcaaFZbsn/OmBeDcGpkxkAgYaM0K2vc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbif6vI%2FdJMcaaFZbsn%2FOmBeDcGpkxkAgYaM0K2vc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RAG 실패 원인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;평가를 단계별로 나누는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG를 최종 결과(end-to-end)만 보고 평가하면 원인을 알기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 최종 답변이 틀렸다고 했을 때, 가능한 원인은&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;검색 결과에 정답 근거가 없었다.&lt;/li&gt;
&lt;li&gt;검색 결과에는 있었지만 LLM이 무시했다.&lt;/li&gt;
&lt;li&gt;LLM이 맞게 읽었지만 답변 형식이 틀렸다.&lt;/li&gt;
&lt;li&gt;출처를 잘못 붙였다.&lt;/li&gt;
&lt;li&gt;문서 버전이 오래됐다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인데, 이 중 어떤 것인지 원인을 모르면 개선도 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 평가는 보통 세 단계로 나눈다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;검색(Retrieval) 평가: 필요한 문서를 찾았는가?&lt;/li&gt;
&lt;li&gt;생성(Generation) 평가: 찾은 문서를 바탕으로 맞게 답했는가?&lt;/li&gt;
&lt;li&gt;최종 결과(End-to-end) 평가: 사용자 입장에서 최종 답변이 좋은가?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Golden Set&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 시스템을 평가하기 위해 미리 만들어둔 기준 질문 세트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성요소&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문&lt;/li&gt;
&lt;li&gt;정답&lt;/li&gt;
&lt;li&gt;필요한 근거 문서 ID&lt;/li&gt;
&lt;li&gt;허용 가능한 답변 범위&lt;/li&gt;
&lt;li&gt;주의해야 할 문서 버전&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1783402519225&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- id: q_001
  question: &quot;해외배송 상품을 환불하면 위약금은 얼마인가요?&quot;
  answer: &quot;해외배송 상품 환불 시 위약금은 10,000원이며, 단순 변심 환불에 적용된다.&quot;
  required_contexts:
    - policy_refund_overseas_2026
    - clause_penalty_2026
  must_not_use:
    - policy_refund_overseas_2025
  category: &quot;환불/해외배송&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Golden Set이 없으면 RAG 개선이 감으로 흐른다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;전보다 좋아진 것 같은데?&lt;br /&gt;이 질문은 잘 되는데 다른 질문은?&lt;br /&gt;검색 설정을 바꾸면 전체적으로 좋아진 건가?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Golden Set이 있으면 변경 전후를 비교할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 Golden Set은 쉬운 질문만 모아두지 않는다.&lt;br /&gt;자주 들어오는 질문, 자주 실패하는 질문, 문서 여러 개를 함께 봐야 하는 질문, 최신 버전이 중요한 질문을 섞어야 한다.&lt;br /&gt;그래야 설정을 바꿨을 때 &amp;ldquo;데모용 질문만 좋아진 것&amp;rdquo;인지 &amp;ldquo;운영에서 중요한 질문도 좋아진 것&amp;rdquo;인지 구분할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Retrieval 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 단계만 평가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문에 대해 필요한 근거 문서가 검색 결과 안에 들어왔는지 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 LLM 답변이 예쁜지, 문장이 자연스러운지는 보지 않는다.&lt;br /&gt;오직 &amp;ldquo;답변에 필요한 재료를 검색이 가져왔는가&amp;rdquo;만 본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hit@k&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 문서가 top-k 검색 결과 안에 하나라도 있으면 성공.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;질문에 필요한 문서: B&lt;br /&gt;검색 top-5 결과: A, C, B, D, E&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B가 top-5 안에 있으므로 Hit@5 = 1&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;질문에&amp;nbsp;필요한&amp;nbsp;문서:&amp;nbsp;B&lt;br /&gt;검색 top-5 결과: A, C, D, E, F&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B가 없으므로 Hit@5 = 0&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hit@k는 단순하고 직관적&lt;/li&gt;
&lt;li&gt;필요한 문서가 여러 개인 경우에는 부족할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Recall@k&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 문서들 중 top-k 안에 들어온 비율.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;필요한 문서: A, B, C&lt;br /&gt;검색 top-5 결과: A, D, B, E, F&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; A와 B는 찾았지만 C는 못 찾았다.&lt;br /&gt;&amp;rarr; Recall@5 = 2 / 3 = 0.667&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는 Recall@k가 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 필요한 근거가 검색 결과에 없으면 LLM이 답을 만들 수 없기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Precision@k&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top-k 결과 중 실제로 관련 있는 문서의 비율.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;검색 top-5 결과: A, B, D, E, F&lt;br /&gt;관련 문서: A, B&lt;br /&gt;&amp;rarr;&amp;nbsp;Precision@5 = 2 / 5 = 0.4&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision이 낮으면 LLM 프롬프트에 불필요한 문서가 많이 들어가고 불필요한 문서가 많으면 문제가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM이 잘못된 문서를 참고할 수 있음&lt;/li&gt;
&lt;li&gt;프롬프트 길이 증가&lt;/li&gt;
&lt;li&gt;비용 증가&lt;/li&gt;
&lt;li&gt;답변 집중도 하락&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MRR&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 관련 문서가 몇 번째에 나왔는지.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;검색 결과:&lt;br /&gt;1위 D&lt;br /&gt;2위 A&lt;br /&gt;3위 B&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째 관련 문서: A, 2위&lt;/b&gt;&lt;br /&gt;Reciprocal Rank = 1 / 2 = 0.5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 관련 문서가 1위에 나오면 1.0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MRR은 관련 문서가 검색 결과 상단에 나오는지를 평가한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nDCG&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과의 순서와 관련도 등급.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련도를 0/1이 아니라 여러 단계로 줄 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3점: 반드시 필요한 핵심 근거&lt;/li&gt;
&lt;li&gt;2점: 도움이 되는 보조 근거&lt;/li&gt;
&lt;li&gt;1점: 약간 관련 있음&lt;/li&gt;
&lt;li&gt;0점: 무관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nDCG는 높은 관련도의 문서가 상위에 있을수록 높은 점수를 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는 문서가 단순히 관련/무관으로 나뉘지 않는 경우가 많기 때문에, nDCG가 유용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Generation 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과가 좋다고 최종 답변이 항상 좋은 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 근거를 잘못 읽거나, 없는 내용을 만들어낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Generation 평가는 다음을 본다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변이 정답과 맞는가?&lt;/li&gt;
&lt;li&gt;근거에 충실한가?&lt;/li&gt;
&lt;li&gt;출처가 정확한가?&lt;/li&gt;
&lt;li&gt;불필요한 추측이 없는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과가 좋았는데도 답변이 틀렸다면, 검색 설정을 바꾸는 것보다 프롬프트, 모델, 답변 형식, 검증 로직을 봐야 한다.&lt;br /&gt;이 구분을 하지 않으면 이미 잘 찾고 있는 검색기를 계속 만지게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Correctness&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변 내용이 정답과 맞는지 보는 지표.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답: 위약금은 10,000원이다.&lt;/li&gt;
&lt;li&gt;답변: 위약금은 10,000원입니다.&lt;br /&gt;&amp;rarr; correct&lt;/li&gt;
&lt;li&gt;답변: 위약금은 5,000원입니다.&lt;br /&gt;&amp;rarr; incorrect&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Correctness는 최종 사용자가 가장 직접적으로 느끼는 품질이다. 그러나 이것만&amp;nbsp;보면 근거 사용 여부를 알 수 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Faithfulness / Groundedness&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변이 제공된 근거에 충실한지 보는 개념.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;검색 근거: 위약금은 10,000원이다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;답변: 위약금은 10,000원입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&amp;rarr; 근거에 충실&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;답변: 위약금은 10,000원이며, VIP 고객은 면제됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; VIP 면제 내용이 근거에 없으면 불충실&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변이 사실처럼 보이더라도, 근거에 없는 내용이면 RAG 답변으로는 문제가 있다.&lt;br /&gt;RAG의 목표는 LLM의 상식이나 추측이 아니라, 제공된 문서에 근거한 답변이기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Citation Accuracy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Citation Accuracy는 답변의 각 주장에 맞는 출처가 붙었는지 본다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;답변:&lt;br /&gt;위약금은 10,000원입니다. [출처: B]&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실제 근거:&lt;br /&gt;B. 위약금 산정 조항&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 citation은 맞다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;답변:&lt;br /&gt;위약금은 10,000원입니다. [출처: A]&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A는 해외배송 환불 정책이고,&lt;br /&gt;위약금 금액은 B에 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변 내용은 맞아도 출처는 틀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책/법령/계약서 RAG에서는 citation accuracy가 특히 중요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Answer Completeness&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변이 필요한 조건을 빠짐없이 포함했는지 본다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;질문:&lt;br /&gt;해외배송 상품 환불 시 위약금은 얼마인가요?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전한 답변:&lt;br /&gt;위약금은 10,000원이며, 단순 변심 환불에 적용됩니다.&lt;br /&gt;상품 하자 환불에는 적용되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불완전한 답변:&lt;br /&gt;위약금은 10,000원입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;금액은 맞지만 적용 조건이 빠졌다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;End-to-End 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 결과(end-to-end) 평가는 사용자가 보는 최종 결과를 평가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음을 모두 포함한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문 이해&lt;/li&gt;
&lt;li&gt;검색 품질&lt;/li&gt;
&lt;li&gt;답변 정확성&lt;/li&gt;
&lt;li&gt;근거 충실성&lt;/li&gt;
&lt;li&gt;출처 정확성&lt;/li&gt;
&lt;li&gt;표현 명확성&lt;/li&gt;
&lt;li&gt;응답 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영에서는 최종 결과 만족도가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개선할 때는 반드시 retrieval과 generation을 분리해서 원인을 봐야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1Z3iL/dJMcacDN07V/KMGzrQcihGbnpYzP5vUHP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1Z3iL/dJMcacDN07V/KMGzrQcihGbnpYzP5vUHP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1Z3iL/dJMcacDN07V/KMGzrQcihGbnpYzP5vUHP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1Z3iL%2FdJMcacDN07V%2FKMGzrQcihGbnpYzP5vUHP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영 로그 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG를 운영하려면 로그를 잘 남겨야 한다.&lt;br /&gt;답변만 저장하면 원인 분석이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장하면 좋은 정보:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문 원문&lt;/li&gt;
&lt;li&gt;질문 임베딩 모델 버전&lt;/li&gt;
&lt;li&gt;검색 top-k 문서 ID&lt;/li&gt;
&lt;li&gt;검색 점수&lt;/li&gt;
&lt;li&gt;metadata 필터 조건&lt;/li&gt;
&lt;li&gt;reranker 결과&lt;/li&gt;
&lt;li&gt;LLM 프롬프트 버전&lt;/li&gt;
&lt;li&gt;LLM 모델 버전&lt;/li&gt;
&lt;li&gt;최종 답변&lt;/li&gt;
&lt;li&gt;출처 ID&lt;/li&gt;
&lt;li&gt;응답 시간&lt;/li&gt;
&lt;li&gt;사용자 피드백&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 로그가 있어야 나중에 이런 분석이 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색이 문서를 못 찾았나?&lt;/li&gt;
&lt;li&gt;찾았는데 LLM이 무시했나?&lt;/li&gt;
&lt;li&gt;특정 문서 버전에서만 문제가 생기나?&lt;/li&gt;
&lt;li&gt;top-k가 너무 작나?&lt;/li&gt;
&lt;li&gt;필터가 너무 강한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실패 분석 예시&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;해외배송 상품을 환불하면 위약금은 얼마인가요?&lt;/b&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;케이스 1: 검색 결과에 B가 없음&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 결과:&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A. 해외배송 환불 정책&lt;/li&gt;
&lt;li&gt;C. 국내배송 환불 정책&lt;/li&gt;
&lt;li&gt;D. 고객센터 안내&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;원인: 검색 실패&lt;/li&gt;
&lt;li&gt;개선:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;chunking 재검토&lt;/li&gt;
&lt;li&gt;hybrid search 추가&lt;/li&gt;
&lt;li&gt;GraphRAG로 참조 조항 확장&lt;/li&gt;
&lt;li&gt;top-k 증가&lt;/li&gt;
&lt;li&gt;reranker 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;케이스 2: 검색 결과에 A/B가 모두 있음, 답변이 틀림&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 결과:&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A. 해외배송 환불 정책&lt;/li&gt;
&lt;li&gt;B. 위약금 산정 조항&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;답변: 위약금은 5,000원입니다.&lt;/li&gt;
&lt;li&gt;원인: 생성 실패&lt;/li&gt;
&lt;li&gt;개선:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프롬프트에 근거 인용 규칙 강화&lt;/li&gt;
&lt;li&gt;답변 형식 구조화&lt;/li&gt;
&lt;li&gt;계산/금액 추출 검증 로직 추가&lt;/li&gt;
&lt;li&gt;더 강한 모델 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;케이스 3: 답은 맞지만 출처가 틀림&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변: 위약금은 10,000원입니다. [A]&lt;/li&gt;
&lt;li&gt;실제 금액 근거: B&lt;/li&gt;
&lt;li&gt;원인: citation 실패&lt;/li&gt;
&lt;li&gt;개선:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문장별 citation 요구&lt;/li&gt;
&lt;li&gt;출처-주장 매칭 검증&lt;/li&gt;
&lt;li&gt;LLM 후처리 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 루프&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 운영은 한 번 만들고 끝이 아니라,&amp;nbsp;반복적으로 개선해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자 질문 수집&lt;/li&gt;
&lt;li&gt;실패 사례 라벨링&lt;/li&gt;
&lt;li&gt;실패 원인 분류&lt;/li&gt;
&lt;li&gt;검색/생성/문서 중 원인 파악&lt;/li&gt;
&lt;li&gt;설정 변경&lt;/li&gt;
&lt;li&gt;Golden Set으로 회귀 테스트&lt;/li&gt;
&lt;li&gt;배포&lt;/li&gt;
&lt;li&gt;모니터링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 변경 후 반드시 기존 질문들이 나빠지지 않았는지 확인하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 질문은 좋아졌는데 기존 핵심 질문이 망가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 regression test가 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변경할 수 있는 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 품질을 개선할 때 바꿀 수 있는 것은 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Retrieval&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;chunk 크기&lt;/li&gt;
&lt;li&gt;chunk overlap&lt;/li&gt;
&lt;li&gt;embedding 모델&lt;/li&gt;
&lt;li&gt;벡터 검색 metric&lt;/li&gt;
&lt;li&gt;top-k&lt;/li&gt;
&lt;li&gt;메타데이터 필터&lt;/li&gt;
&lt;li&gt;hybrid search 가중치&lt;/li&gt;
&lt;li&gt;reranker 사용 여부&lt;br /&gt;GraphRAG 확장 범위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Generation&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM 모델&lt;/li&gt;
&lt;li&gt;프롬프트&lt;/li&gt;
&lt;li&gt;temperature&lt;/li&gt;
&lt;li&gt;답변 형식&lt;/li&gt;
&lt;li&gt;citation 규칙&lt;/li&gt;
&lt;li&gt;모르는 경우 답변 정책&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Data 쪽
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서 최신화&lt;/li&gt;
&lt;li&gt;중복 문서 제거&lt;/li&gt;
&lt;li&gt;문서 버전 관리&lt;/li&gt;
&lt;li&gt;문서 구조 정리&lt;/li&gt;
&lt;li&gt;표/이미지 텍스트화&lt;/li&gt;
&lt;li&gt;메타데이터 보강&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;평균 검색 점수&lt;/li&gt;
&lt;li&gt;top-k score 분포&lt;/li&gt;
&lt;li&gt;응답 latency p50/p95/p99&lt;/li&gt;
&lt;li&gt;LLM 오류율&lt;/li&gt;
&lt;li&gt;사용자 재질문율&lt;/li&gt;
&lt;li&gt;사용자 thumbs down 비율&lt;/li&gt;
&lt;li&gt;citation 누락률&lt;/li&gt;
&lt;li&gt;구버전 문서 사용률&lt;/li&gt;
&lt;li&gt;fallback 발생률&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 RAG에서는 문서 변경이 성능에 큰 영향을 준다.&lt;br /&gt;문서가 업데이트되었는데 재색인이 안 되면 최신성 문제가 생기기 때문.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배포 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 설정을 바꿀 때는 바로 전체 배포하지 않는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 방식:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오프라인 Golden Set 평가&lt;/li&gt;
&lt;li&gt;소수 트래픽 A/B 테스트&lt;/li&gt;
&lt;li&gt;내부 사용자 대상 베타&lt;/li&gt;
&lt;li&gt;점진적 rollout&lt;/li&gt;
&lt;li&gt;문제 발생 시 rollback&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 검색 인덱스, 문서, 프롬프트, 모델이 모두 얽혀 있어,&amp;nbsp;작은 변경도 예상보다 큰 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/AI 시스템 설계</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/295</guid>
      <comments>https://chaeyami.tistory.com/295#entry295comment</comments>
      <pubDate>Tue, 7 Jul 2026 16:03:41 +0900</pubDate>
    </item>
    <item>
      <title>[AI설계] Vector DB와 ANN 검색 - 벡터를 빠르게 찾는 인프라</title>
      <link>https://chaeyami.tistory.com/294</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RAG나 임베딩 검색을 만들면 결국 이런 작업을 해야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;질문을 벡터로 바꾼다.&lt;br /&gt;문서 조각들도 벡터로 저장해둔다.&lt;br /&gt;질문 벡터와 가장 가까운 문서 벡터를 찾는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 이렇게 묻는다고 하자: &lt;b&gt;&quot;택배가 아직 안 왔을 때 보상 받을 수 있나요?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문은 문서의 정확한 문장과 단어가 다를 수 있는데,&lt;br /&gt;문서에는&lt;b&gt; &quot;배송 지연으로 인한 보상 기준은 다음과 같다.&quot;&lt;/b&gt; 라고 쓰여있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 키워드는 완전히 같지 않고, 그러나 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;의미는 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chaeyami.tistory.com/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;임베딩&lt;/a&gt; 검색은 이런 의미 유사성을 이용하는데,&amp;nbsp;문제는 문서가 많아졌을 때다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;문서 조각 1,000개 &amp;rarr; 하나씩 비교 가능&lt;br /&gt;문서 조각 100만 개 &amp;rarr; 하나씩 비교하면 느림&lt;br /&gt;문서 조각 1억 개 &amp;rarr; 단순 비교는 거의 불가능&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 필요한 것이 &lt;b&gt;Vector DB와 ANN&lt;/b&gt; 검색이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vector DB&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 저장하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector DB는 벡터만 저장하는 게 아니라&amp;nbsp;보통 다음을 함께 저장한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;벡터&lt;/li&gt;
&lt;li&gt;원본 텍스트 또는 chunk ID&lt;/li&gt;
&lt;li&gt;메타데이터(metadata)&lt;/li&gt;
&lt;li&gt;검색 인덱스&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g:&lt;/p&gt;
&lt;pre id=&quot;code_1783322004291&quot; class=&quot;json&quot; data-ke-language=&quot;json&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;chunk_1024&quot;,
  &quot;text&quot;: &quot;배송 지연 보상 기준은 ...&quot;,
  &quot;vector&quot;: [0.12, -0.03, 0.88, ...],
  &quot;metadata&quot;: {
    &quot;document_id&quot;: &quot;policy_2026_배송&quot;,
    &quot;category&quot;: &quot;배송&quot;,
    &quot;version&quot;: &quot;2026-06&quot;,
    &quot;created_at&quot;: &quot;2026-06-15&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector DB의 역할은 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 벡터가 들어오면 저장된 벡터 중 가까운 것들을 빠르게 찾아준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반 DB와 무엇이 다른가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 관계형 DB는 정확한 조건 검색에 강하다.&lt;/p&gt;
&lt;pre id=&quot;code_1783322117378&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM documents
WHERE category = '배송'
AND version = '2026-06';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 검색은 정확히 일치하는 값을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Vector DB는 &quot;&lt;b&gt;이 질문 벡터와 의미적으로 가까운 문서 벡터는 무엇인가?&lt;/b&gt;&quot; 라는 질문을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 검색 기준이 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 101px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;구분&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;일반 DB&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Vector DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;검색 기준&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;정확한 값, 조건&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;벡터 간 거리/유사도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;예시&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;category = '배송'&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;질문과 의미가 가까운 문서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;강점&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;필터링, 정합성, 트랜잭션&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;의미 검색, 유사도 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;RAG에서 역할&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;메타데이터 필터&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;관련 chunk 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실의 RAG 시스템에서는 둘을 같이 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타데이터로 후보를 제한하고, 벡터 유사도로 가까운 문서를 찾는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Exact Search&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exact Search는 저장된 모든 벡터와 질문 벡터를 전부 비교한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;질문 벡터 q&lt;br /&gt;문서 벡터 d1, d2, d3, ..., d1,000,000&lt;br /&gt;&lt;br /&gt;q와 d1 비교&lt;br /&gt;q와 d2 비교&lt;br /&gt;q와 d3 비교&lt;br /&gt;...&lt;br /&gt;q와 d1,000,000 비교&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 정확하지만 데이터가 많으면 느려진다.&amp;nbsp;&lt;br /&gt;거리 계산 한 번은 빠를 수 있지만, 수백만, 수천만 개와 비교하면 비용이 커지는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도를 생각하면,&amp;nbsp;서울에서 내 위치와 가장 가까운 카페를 찾고 싶다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 때 Exact Search는 서울의 모든 카페와 내 위치 사이의 거리를 하나씩 계산한다.&lt;br /&gt;카페가 100개면 괜찮지만, 카페가 100만 개면 비효율적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ANN(Approximate Nearest Neighbor) 검색&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 최근접 이웃을 반드시 찾는 대신, 매우 가까운 후보를 훨씬 빠르게 찾는 방식.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Exact Search:&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 가까운 벡터를 정확히 찾음&lt;/li&gt;
&lt;li&gt;느릴 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ANN Search:&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거의 가까운 벡터를 빠르게 찾음&lt;/li&gt;
&lt;li&gt;약간의 오차 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 approximate는 대충 아무거나 찾는다는 뜻이 아니라&lt;br /&gt;검색 속도를 크게 높이기 위해 정확도 일부와 속도를 맞바꾸는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는 보통 top-k 후보를 여러 개 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 1등이 완벽히 정확하지 않아도, 관련 문서가 top-k 안에 들어오면 실용적으로 충분한 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 top-k 안에 필요한 근거가 아예 들어오지 않으면, 뒤의 LLM은 좋은 답을 만들기 어렵다.&lt;br /&gt;그래서 ANN 검색은 &amp;ldquo;가장 가까운 1개를 반드시 맞히는 기술&amp;rdquo;이라기보다 &amp;ldquo;충분히 빠르게 좋은 후보 묶음을 가져오는 기술&amp;rdquo;로 이해하는 편이 좋다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Recall과 Latency Trade-off&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;더 정확하게 찾을수록 느려진다.&lt;br /&gt;더 빠르게 찾을수록 일부 관련 문서를 놓칠 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 품질은 보통 recall로 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Exact Search로 찾은 진짜 top-k 결과 중 ANN이 몇 개를 찾아냈는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Exact top-10 결과: A, B, C, D, E, F, G, H, I, J&lt;br /&gt;ANN top-10 결과: A, B, C, D, F, H, K, L, M, N&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;겹친 결과: A, B, C, D, F, H = 6개&lt;br /&gt;Recall@10 = 6 / 10 = 0.6&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 다음을 함께 본다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Recall@k&lt;/li&gt;
&lt;li&gt;Latency p50 / p95 / p99&lt;/li&gt;
&lt;li&gt;Index size&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Update cost&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/rq7eu/dJMcad3EkbE/AAAAAAAAAAAAAAAAAAAAAIt1CF26VAyDS17E33_b-M_nE0np8Rdkunuq_iIdn5Bf/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1785509999&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=vu1vlaSdgOO8XInQEpW1SPGm31c%3D&quot; data-origin-width=&quot;1691&quot; data-origin-height=&quot;930&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exact Search와 ANN Search의 차이&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;거리/유사도 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색에서는 가까움을 어떻게 정의할지 정해야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&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;Cosine Similarity&lt;/td&gt;
&lt;td&gt;방향이 비슷한가&lt;/td&gt;
&lt;td&gt;텍스트 임베딩에서 자주 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dot Product&lt;/td&gt;
&lt;td&gt;내적이 큰가&lt;/td&gt;
&lt;td&gt;벡터 크기 영향 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Euclidean Distance&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;p data-ke-size=&quot;size16&quot;&gt;중요한 점은,&amp;nbsp;임베딩 모델이 어떤 유사도 기준으로 학습되었는지 확인해야 한다는 것.&lt;br /&gt;모델이 cosine 기준으로 쓰라고 만들어졌는데, DB에서 euclidean 기준으로 검색하면 의도와 다를 수 있다.&lt;br /&gt;또 벡터를 L2 normalize하면 cosine similarity와 dot product가 사실상 같은 순위를 줄 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HNSW(Hierarchical Navigable Small World graph)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가까운 벡터끼리 길을 연결해둔다.&lt;/li&gt;
&lt;li&gt;검색할 때 모든 점을 보지 않고,&lt;/li&gt;
&lt;li&gt;길을 따라가며 점점 더 가까운 점으로 이동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도 비유로 보면:&lt;br /&gt;모든 건물을 하나씩 확인하지 않고, 큰 도로 &amp;rarr; 작은 도로 &amp;rarr; 골목길 순서로 목적지 근처까지 이동한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계층 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HNSW는 여러 층의 그래프를 만든다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 layer: 점이 적고 멀리 이동하기 좋음&lt;/li&gt;
&lt;li&gt;하위 layer: 점이 많고 세밀하게 탐색하기 좋음&lt;/li&gt;
&lt;li&gt;검색 흐름:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상위 layer에서 대략 가까운 위치로 이동&lt;/li&gt;
&lt;li&gt;아래 layer로 내려감&lt;/li&gt;
&lt;li&gt;더 가까운 점으로 이동&lt;/li&gt;
&lt;li&gt;가장 아래 layer에서 후보를 정밀하게 찾음&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 파라미터&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;M
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 노드가 연결할 이웃 수&lt;/li&gt;
&lt;li&gt;M이 클수록 그래프 연결이 풍부해짐&lt;/li&gt;
&lt;li&gt;검색 recall이 좋아질 수 있음&lt;/li&gt;
&lt;li&gt;메모리 사용량 증가&lt;/li&gt;
&lt;li&gt;인덱스 생성 비용 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;efConstruction
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스를 만들 때 얼마나 넓게 후보를 볼지 결정&lt;/li&gt;
&lt;li&gt;크면 인덱스 품질 증가 but&amp;nbsp;생성 시간 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;efSearch
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색할 때 얼마나 넓게 탐색할지 결정한다.&lt;/li&gt;
&lt;li&gt;크면 recall 증가 but&amp;nbsp;latency 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;efSearch를 높이면 더 정확하지만 느려진다.&lt;br /&gt;efSearch를 낮추면 더 빠르지만 놓치는 문서가 늘 수 있다.&lt;br /&gt;&lt;br /&gt;운영에서는 보통 목표 latency를 먼저 정한 뒤 efSearch를 올려가며 recall이 충분한 지점을 찾는다.&lt;br /&gt;검색 품질만 보고 efSearch를 크게 잡으면 사용자가 느끼는 응답 속도가 나빠질 수 있다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uOkfp/dJMcabZa0n1/bZNpi3pOPIW6ZJidaQ14TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uOkfp/dJMcabZa0n1/bZNpi3pOPIW6ZJidaQ14TK/img.png&quot; data-alt=&quot;HNSW의 계층 그래프 탐색&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uOkfp/dJMcabZa0n1/bZNpi3pOPIW6ZJidaQ14TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuOkfp%2FdJMcabZa0n1%2FbZNpi3pOPIW6ZJidaQ14TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HNSW의 계층 그래프 탐색&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IVF(Inverted&amp;nbsp;File&amp;nbsp;Index)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 공간을 여러 구역으로 나눠두는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도 비유:&lt;br /&gt;전국의 모든 카페를 하나씩 비교하지 않는다.&lt;br /&gt;먼저 서울/부산/대구 같은 큰 구역을 고른다.&lt;br /&gt;그 구역 안에서 가까운 카페를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색에서는 먼저 전체 벡터를 여러 cluster로 나눈다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;cluster 1&lt;br /&gt;cluster 2&lt;br /&gt;cluster 3&lt;br /&gt;...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 벡터가 들어오면 가장 가까운 cluster 몇 개만 탐색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IVF에서 중요한 위험은 질문과 가까운 벡터가 선택하지 않은 cluster에 숨어 있을 수 있다는 점이다.&lt;br /&gt;그래서 nprobe를 너무 작게 잡으면 빠르지만 중요한 후보를 놓칠 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nlist와 nprobe&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nlist
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;몇 개의 cluster로 나눌지 결정한다.&lt;/li&gt;
&lt;li&gt;nlist가 크다 &amp;rarr; 구역이 많고 세밀함&lt;/li&gt;
&lt;li&gt;nlist가 작다 &amp;rarr; 구역이 적고 큼&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;nprobe
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색할 때 몇 개의 cluster를 볼지 결정한다.&lt;/li&gt;
&lt;li&gt;nprobe가 크다 &amp;rarr; 더 많은 구역 탐색 &amp;rarr; recall 증가, latency 증가&lt;/li&gt;
&lt;li&gt;nprobe가 작다 &amp;rarr; 적은 구역 탐색 &amp;rarr; 빠름, 놓칠 가능성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PQ(Product&amp;nbsp;Quantization)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터를 압축해서 저장하는 방식.&lt;br /&gt;고차원 벡터를 그대로 저장하면 메모리를 많이 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1536차원 float 벡터&lt;br /&gt;문서 chunk 1억 개&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 규모에서는 저장 공간과 메모리가 큰 문제가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PQ는 벡터를 여러 조각으로 나누고, 각 조각을 대표 코드로 근사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 원본 벡터를 그대로 저장하지 않고, 압축된 코드로 저장하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해 정밀한 좌표를 그대로 들고 있는 대신, &amp;ldquo;이 조각은 대표 패턴 17번에 가깝다&amp;rdquo;처럼 짧은 코드로 바꿔 저장하는 것이다.&lt;br /&gt;그래서 메모리는 줄지만 원본 벡터와 완전히 같지는 않다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 사용량 감소&lt;/li&gt;
&lt;li&gt;검색 속도 개선 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;근사 오차 발생&lt;/li&gt;
&lt;li&gt;정확도 하락 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PQ는 대규모 검색에서 자주 쓰지만, 검색 품질과 메모리 사이의 절충 관계를 이해하고 써야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메타데이터 필터링(Metadata Filtering)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 유사도만으로 검색하면 관련 없는 문서가 섞일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음과 같이 묻는다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;2026년 해외배송 환불 규정 알려줘&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 유사도만 보면 2024년 문서가 가까울 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 메타데이터 필터를 같이 건다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;version = 2026&lt;br /&gt;category = 해외배송&lt;br /&gt;status = active&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 흐름은 두 가지 방식이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pre-filter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메타데이터로 먼저 후보를 줄인 뒤 벡터 검색&lt;/li&gt;
&lt;li&gt;빠르고 안전하다.&lt;/li&gt;
&lt;li&gt;후보가 너무 줄어 관련 문서를 놓칠 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Post-filter:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;벡터 검색 후 메타데이터 조건에 맞지 않는 결과 제거&lt;/li&gt;
&lt;li&gt;검색 후보를 넓게 볼 수 있다.&lt;/li&gt;
&lt;li&gt;필터 후 결과가 부족할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hybrid Search&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;벡터 검색과 키워드 검색을 함께 쓰는 방식.&lt;/li&gt;
&lt;li&gt;벡터 검색: 동의어와 의미 유사성을 잘 찾음&lt;/li&gt;
&lt;li&gt;키워드 검색: 정확한 용어, 코드, 조항 번호, 상품명에 강함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&amp;nbsp;&quot;&lt;b&gt;제12조 위약금 산정 기준&lt;/b&gt;&quot;이라는 질문이 있다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 &lt;b&gt;제12조&lt;/b&gt; 같은 정확한 키워드는 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색만 쓰는 경우, 숫자간의 의미는 벡터에게 결국 숫자이기 떄문에 다른 조항이 나올 수 있다.&lt;br /&gt;이런 경우를 보완하기 위해, Hybrid Search는 둘의 점수를 결합한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;최종 점수 = 벡터 점수 + 키워드 점수&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 두 점수의 스케일이 다르므로 정규화가 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reranker&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector DB는 보통 빠르게 top-k 후보를 가져오는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 top-k 안에서 순서가 완벽하지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reranker는 후보 문서와 질문을 다시 함께 보고 순서를 재정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 흐름:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Vector DB에서 top-50 후보 검색&lt;/li&gt;
&lt;li&gt;Reranker가 질문-문서 쌍을 다시 평가&lt;/li&gt;
&lt;li&gt;top-5만 LLM 프롬프트에 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector DB 검색은 빠른 1차 후보 추출이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reranker는 느리지만 더 정밀한 2차 판단이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RAG에서의 위치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 파이프라인에서 Vector DB는 검색(retrieval) 단계에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 수집&lt;br /&gt;&amp;rarr; chunking&lt;br /&gt;&amp;rarr; embedding&lt;br /&gt;&amp;rarr; Vector DB 저장&lt;br /&gt;&amp;rarr; 사용자 질문 embedding&lt;br /&gt;&amp;rarr; Vector DB 검색&lt;br /&gt;&amp;rarr; reranking&lt;br /&gt;&amp;rarr; LLM 답변 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;LLM은 주어진 근거 안에서 답변하기 때문에, &lt;/span&gt;Vector DB 품질이 낮으면 좋은 답변을 만들기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 문서를 못 찾음&lt;br /&gt;&amp;rarr; LLM에게 근거가 안 들어감&lt;br /&gt;&amp;rarr; 답변이 틀리거나 모른다고 해야 함&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영에서 봐야 할 것&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 재생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서가 바뀌면 embedding과 index도 바뀌어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 수정&lt;br /&gt;&amp;rarr; chunk 재생성&lt;br /&gt;&amp;rarr; embedding 재계산&lt;br /&gt;&amp;rarr; vector index update 또는 rebuild&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;버전 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩 모델이 바뀌면 기존 벡터와 새 벡터를 섞어 쓰면 안 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;embedding_model = text-embedding-A&lt;br /&gt;embedding_model = text-embedding-B&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 다르면 벡터 공간도 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전별로 분리하거나 전체 재색인이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 시스템에서는 다음을 모니터링한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 latency&lt;/li&gt;
&lt;li&gt;empty result 비율&lt;/li&gt;
&lt;li&gt;top-k 평균 score&lt;/li&gt;
&lt;li&gt;사용자가 클릭한 문서&lt;/li&gt;
&lt;li&gt;LLM 답변 실패율&lt;/li&gt;
&lt;li&gt;문서 버전 불일치&lt;/li&gt;
&lt;/ul&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;Vector DB는 질문 벡터와 가까운 문서 벡터를 빠르게 찾기 위한 저장소이자 검색 인프라.&lt;/li&gt;
&lt;li&gt;ANN은 모든 벡터를 정확히 비교하지 않고, 가까운 후보를 빠르게 찾기 위한 근사 검색 방식.&lt;/li&gt;
&lt;li&gt;더 정확하게 찾을수록 느려지고,&lt;/li&gt;
&lt;li&gt;더 빠르게 찾을수록 일부 관련 문서를 놓칠 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/AI 시스템 설계</category>
      <category>Ann</category>
      <category>VectorDB</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/294</guid>
      <comments>https://chaeyami.tistory.com/294#entry294comment</comments>
      <pubDate>Tue, 7 Jul 2026 11:35:39 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 전통적인 머신러닝 모델 &amp;mdash; 로지스틱 회귀부터 부스팅까지</title>
      <link>https://chaeyami.tistory.com/293</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습에서는 입력 `x`와 정답 `y`가 함께 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 `x`를 보고 `y`를 맞히도록 학습한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CS 문의 분류 문제를 생각해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력 `x`: 문의 문장, 고객 등급, 주문 상태, 배송 상태, 결제 금액&lt;/li&gt;
&lt;li&gt;정답 `y`: 배송 문의 / 환불 문의 / 교환 문의 / 상품 문의 / 기타 문의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 딥러닝 이전부터 널리 쓰인 머신러닝 모델을 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;먼저 볼 기준&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;/td&gt;
&lt;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 크기&lt;/td&gt;
&lt;td&gt;샘플 수와 특징(feature) 수가 어느 정도인가?&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;p data-ke-size=&quot;size16&quot;&gt;모델은 &amp;ldquo;좋은 모델 하나&amp;rdquo;를 고르는 문제가 아니라,&amp;nbsp;데이터와 목적에 맞는 모델을 고르는 문제다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트 분류라면 TF-IDF + Logistic Regression이 의외로 강한 출발점일 수 있다.&lt;/li&gt;
&lt;li&gt;반대로 표 형태의 정형 데이터에서 비선형 조건이 많다면 Gradient Boosting 계열이 더 잘 맞을 수 있다.&lt;/li&gt;
&lt;li&gt;설명 가능성이 최우선이면 성능이 조금 낮아도 선형 모델이나 작은 Decision Tree가 더 적합할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Linear Regression&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linear Regression은 회귀 문제에 쓰인다. 즉, &lt;b&gt;정답이 연속적인 숫자일 때&lt;/b&gt;&amp;nbsp;사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;집값 예측&lt;br /&gt;매출 예측&lt;br /&gt;배송 소요 시간 예측&lt;br /&gt;고객의 다음 달 구매 금액 예측&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델은 입력 특징(feature)들의 가중합으로 값을 예측한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\hat{y} = w_1x_1 + w_2x_2 + \cdots + w_nx_n + b&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 배송 소요 시간을 `배송 소요 시간 = 0.3 &amp;times; 거리` 로 예측한다고 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1.2 &amp;times; 주말 여부&lt;/li&gt;
&lt;li&gt;0.8 &amp;times; 물량 지수&lt;/li&gt;
&lt;li&gt;기본값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 학습하는 것: 각 특징의 가중치&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;거리의 영향은 얼마나 큰가?&lt;br /&gt;주말 여부는 얼마나 영향을 주는가?&lt;br /&gt;물량 지수는 얼마나 중요한가?&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠르다.&lt;/li&gt;
&lt;li&gt;해석하기 쉽다.&lt;/li&gt;
&lt;li&gt;특징과 예측값의 방향성을 보기 좋다.&lt;/li&gt;
&lt;li&gt;첫 기준 모델(baseline)로 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계가 직선적이라는 가정이 강하다.&lt;/li&gt;
&lt;li&gt;복잡한 비선형 패턴을 잘 못 잡는다.&lt;/li&gt;
&lt;li&gt;이상치에 민감할 수 있다.&lt;/li&gt;
&lt;li&gt;특징 스케일링이 필요한 경우가 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Logistic Regression&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이름은 회귀지만 분류 모델&lt;/b&gt;이다. 보통 분류 문제에 사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;스팸 / 정상 메일&lt;br /&gt;이상 거래 / 정상 거래&lt;br /&gt;환불 문의 / 환불 아님&lt;br /&gt;이탈 고객 / 유지 고객&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델도 먼저 특징들의 가중합을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;z = w_1x_1 + w_2x_2 + \cdots + w_nx_n + b&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 이 값을 sigmoid 함수에 넣어 0~1 사이 확률로 바꾼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;p = \frac{1}{1 + e^{-z}}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&amp;nbsp;p = 0.82 일 때, 이 문의가 환불 문의일 확률 82%로 해석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 분류 기준값(threshold)을 기준으로 분류한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 p &amp;gt;= 0.5 &amp;rarr; 환불 문의 / p &amp;lt; 0.5 &amp;rarr; 환불 아님 이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Logistic Regression은 단순하지만 강력한 첫 기준 모델이다.&lt;br /&gt;특히 TF-IDF처럼 대부분의 값이 0인 희소 텍스트 특징과 잘 맞는 경우가 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학습과 예측이 빠르다.&lt;/li&gt;
&lt;li&gt;해석 가능성이 좋다.&lt;/li&gt;
&lt;li&gt;확률 출력이 가능하다.&lt;/li&gt;
&lt;li&gt;고차원 희소 특징에서도 잘 동작한다.&lt;/li&gt;
&lt;li&gt;L1/L2 정규화를 적용하기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 비선형 관계를 직접 잡기는 어렵다.&lt;/li&gt;
&lt;li&gt;특징 설계(feature engineering)의 영향을 많이 받는다.&lt;/li&gt;
&lt;li&gt;클래스가 복잡하게 섞여 있으면 한계가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;KNN&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KNN(K-Nearest Neighbors)은 새 데이터가 들어오면, 학습 데이터 중 가장 가까운 K개를 찾고 그 이웃들의 정답을 참고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&amp;nbsp;&quot;택배가 아직 안 왔어요&quot;라는 새 문의가 왔다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KNN은 이 문장과 가장 비슷한 기존 문의들을 찾는다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;배송이 너무 늦어요&quot; &amp;rarr; 배송 문의&lt;/li&gt;
&lt;li&gt;&quot;상품이 아직 도착하지 않았어요&quot; &amp;rarr; 배송 문의&lt;/li&gt;
&lt;li&gt;&quot;택배 조회가 안 돼요&quot; &amp;rarr; 배송 문의&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가까운 3개가 모두 배송 문의라면 새 문의도 배송 문의로 예측하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KNN은 일반적인 의미의 파라미터 학습이 거의 없다. 대신&amp;nbsp;학습 단계에서는 데이터를 저장하고,&amp;nbsp;예측 단계에서 거리를 계산한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학습: 기존 데이터를 저장&lt;/li&gt;
&lt;li&gt;예측: 새 데이터와 기존 데이터 사이의 거리 계산&lt;/li&gt;
&lt;li&gt;&amp;rarr; lazy learning이라고도 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점 및 단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이디어가 직관적이다.&lt;/li&gt;
&lt;li&gt;복잡한 결정 경계도 어느 정도 표현할 수 있다.&lt;/li&gt;
&lt;li&gt;학습 과정이 단순하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예측할 때 느릴 수 있다.&lt;/li&gt;
&lt;li&gt;데이터가 많으면 거리 계산 비용이 커진다.&lt;/li&gt;
&lt;li&gt;특징 스케일링에 매우 민감하다.&lt;/li&gt;
&lt;li&gt;고차원 데이터에서는 거리 개념이 약해질 수 있다.&lt;/li&gt;
&lt;li&gt;K 값을 잘 골라야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K가 너무 작으면 노이즈에 민감하다.&lt;/li&gt;
&lt;li&gt;K가 너무 크면 세밀한 패턴을 놓친다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;KNN을 쓸 때는 &amp;ldquo;가깝다&amp;rdquo;의 기준이 특히 중요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 나이와 연봉을 함께 쓰는데 연봉 값의 범위가 훨씬 크면, 거리 계산은 거의 연봉 차이에 끌려갈 수 있다.&lt;br /&gt;그래서 KNN에서는 스케일링을 하지 않으면 모델이 엉뚱한 특징을 더 중요하게 보는 일이 자주 생긴다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Naive Bayes&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Naive Bayes는 베이즈 정리를 기반으로 한 확률 모델로,&amp;nbsp;텍스트 분류에서 자주 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 문의 문장에 이런 단어들이 있다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;환불, 취소, 결제, 돈&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Naive Bayes는 각 클래스에서 이 단어들이 얼마나 자주 나왔는지를 보고 확률을 계산한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;환불 문의일 확률&lt;br /&gt;배송 문의일 확률&lt;br /&gt;교환 문의일 확률&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 가장 확률이 높은 클래스를 고른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Naive Bayes는 특징들이 서로 독립이라고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시라면,&lt;br /&gt;&quot;환불&quot;이라는 단어가 나올 확률과 &quot;결제&quot;라는 단어가 나올 확률을 서로 독립적으로 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 현실에서는 &quot;환불&quot;과 &quot;결제&quot;는 함께 나올 가능성이 높아서, 완전히 독립은 아니다. 모델에서만 독립적으로 계산하는데,&lt;br /&gt;그래서 naive, 즉 &lt;b&gt;단순한 가정&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단순한 가정에도 불구하고 텍스트 분류에서는 꽤 잘 동작하는 경우가 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매우 빠르다.&lt;/li&gt;
&lt;li&gt;데이터가 적어도 첫 기준 모델로 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;텍스트 분류와 잘 맞는 경우가 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특징 독립 가정이 강하다.&lt;/li&gt;
&lt;li&gt;복잡한 특징 상호작용을 잘 못 잡는다.&lt;/li&gt;
&lt;li&gt;확률값 자체가 잘 보정되어 있지 않을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Decision Tree&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델은 질문을 하나씩 던지며 데이터를 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 환불 문의를 분류한다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;문의 내용에 &quot;환불&quot;이 있는가?&lt;/b&gt;&lt;br /&gt;예 &amp;rarr; 환불 문의 가능성 높음&lt;br /&gt;아니오 &amp;rarr; 다음 질문&lt;br /&gt;&lt;br /&gt;&lt;b&gt;문의 내용에 &quot;배송&quot;이 있는가?&lt;/b&gt;&lt;br /&gt;예 &amp;rarr; 배송 문의 가능성 높음&lt;br /&gt;아니오 &amp;rarr; 다음 질문&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리는 이런 조건 분기를 여러 단계로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Decision Tree는 각 단계에서 데이터를 가장 잘 나누는 조건을 찾는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 특징을 기준으로 나눌까?&lt;/li&gt;
&lt;li&gt;어떤 기준값으로 나눌까?&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;결제 금액 &amp;gt; 100,000원?&lt;br /&gt;배송 지연일 &amp;gt; 3일?&lt;br /&gt;문의 내용에 &quot;환불&quot; 포함?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 분기는 나눈 뒤 각 그룹이 더 순수해지는 분기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) 왼쪽 그룹은 대부분 환불 문의 / 오른쪽 그룹은 대부분 배송 문의&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해석하기 쉽다.&lt;/li&gt;
&lt;li&gt;비선형 관계를 잡을 수 있다.&lt;/li&gt;
&lt;li&gt;숫자형/범주형 특징을 다룰 수 있다.&lt;/li&gt;
&lt;li&gt;스케일링이 거의 필요 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과적합되기 쉽다.&lt;/li&gt;
&lt;li&gt;데이터가 조금 바뀌면 트리 구조가 크게 바뀔 수 있다.&lt;/li&gt;
&lt;li&gt;단일 트리는 성능이 불안정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Random Forest&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Random Forest는 여러 개의 Decision Tree를 만든 뒤 투표한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Decision Tree 하나: 한 명의 심사위원&lt;/li&gt;
&lt;li&gt;Random Forest: 여러 심사위원이 각자 판단한 뒤 다수결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 트리는 조금씩 다른 데이터를 보고, 조금씩 다른 특징을 사용한다.&lt;br /&gt;그래서 모두가 같은 실수를 하지 않게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;왜 random인가&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Random Forest의 random은 보통 두 가지에서 나온다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 랜덤하게 뽑아 각 트리를 학습&lt;/li&gt;
&lt;li&gt;각 분기에서 사용할 특징 후보를 랜덤하게 제한&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 트리들이 서로 다양해지고,&amp;nbsp;트리들이 다양해야 ensemble 효과가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 트리보다 안정적이다.&lt;/li&gt;
&lt;li&gt;과적합이 줄어든다.&lt;/li&gt;
&lt;li&gt;전처리가 비교적 적다.&lt;/li&gt;
&lt;li&gt;특징 중요도를 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 트리보다 해석이 어렵다.&lt;/li&gt;
&lt;li&gt;모델 크기가 커질 수 있다.&lt;/li&gt;
&lt;li&gt;매우 희소한 고차원 텍스트 특징에서는 선형 모델보다 비효율적일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Gradient Boosting&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradient Boosting도 여러 개의 트리를 쓰지만, 하지만 Random Forest와 방식이 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Random Forest: 여러 사람이 각자 독립적으로 답을 쓰고 투표&lt;/li&gt;
&lt;li&gt;Gradient Boosting: 첫 번째 사람이 푼 뒤, 두 번째 사람이 첫 번째 사람의 오답을 보완, 세 번째 사람이 남은 오답을 또 보완&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;아주 단순한 모델로 시작&lt;/li&gt;
&lt;li&gt;현재 모델이 틀린 부분을 계산&lt;/li&gt;
&lt;li&gt;그 오차를 줄이는 새 트리를 추가&lt;/li&gt;
&lt;li&gt;여러 트리를 순서대로 쌓음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 boosting은 모델을 순차적으로 쌓는다. 각 단계의 새 모델은 이전 모델의 약점을 보완하는 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정형 데이터(tabular data)에서 매우 강력하다.&lt;/li&gt;
&lt;li&gt;비선형 관계와 특징 상호작용을 잘 잡는다.&lt;/li&gt;
&lt;li&gt;다양한 실무 문제에서 첫 기준 모델 이상의 성능을 낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하이퍼파라미터에 민감하다.&lt;/li&gt;
&lt;li&gt;과적합을 조심해야 한다.&lt;/li&gt;
&lt;li&gt;학습 시간이 길 수 있다.&lt;/li&gt;
&lt;li&gt;해석이 단순 모델보다 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;XGBoost, LightGBM, CatBoost&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 셋은 Gradient Boosting 계열의 대표 구현체다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋 모두 &amp;ldquo;트리를 순서대로 쌓아 오차를 줄인다&amp;rdquo;는 큰 방향은 같지만, 속도와 범주형 처리 방식, 기본 튜닝 감각이 다르다.&lt;br /&gt;실무에서는 특정 구현체가 항상 이긴다기보다 데이터 형태와 운영 조건에 따라 결과가 갈린다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XGBoost
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강력하고 널리 쓰이는 boosting 라이브러리&lt;br /&gt;정규화, missing value 처리, 병렬화 등 실전 기능이 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LightGBM
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대용량 데이터에서 빠른 학습을 목표로 설계&lt;br /&gt;leaf-wise 성장 방식 사용&lt;br /&gt;범주형 특징 처리 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CatBoost
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;범주형 특징이 많은 데이터에 강점&lt;br /&gt;target encoding으로 인한 누수를 줄이는 방식 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정형 데이터에서 딥러닝보다 이런 boosting 모델이 더 좋은 경우도 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 행과 열로 이루어진 테이블 데이터에서는 데이터 수가 충분해도 딥러닝이 항상 유리하지 않다.&lt;br /&gt;트리 기반 부스팅은 결측치, 비선형 조건, 특징 간 상호작용을 비교적 적은 전처리로 잘 잡는 편이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SVM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVM(Support Vector Machine)은&amp;nbsp;두 클래스 사이의 간격이 최대가 되는 경계를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 A와 클래스 B를 나누되, 가장 가까운 데이터들과 경계 사이의 여백을 최대화하는 것.&lt;br /&gt;이 여백을 margin이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;margin이 넓으면 새 데이터가 조금 흔들려도 안정적으로 분류될 가능성이 높다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kernel Trick&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 직선으로 나뉘지 않을 때는 kernel을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kernel은 데이터를 더 높은 차원의 공간에서 분리되는 것처럼 다루게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 공간에서는 직선으로 안 나뉨&lt;br /&gt;&amp;rarr; kernel을 사용하면 더 복잡한 경계 표현 가능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중소규모 데이터에서 강력할 수 있다.&lt;/li&gt;
&lt;li&gt;고차원 데이터에서도 잘 동작하는 경우가 있다.&lt;/li&gt;
&lt;li&gt;margin 최대화라는 직관이 명확하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;약점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 매우 크면 느릴 수 있다.&lt;/li&gt;
&lt;li&gt;kernel과 파라미터 선택이 중요하다.&lt;/li&gt;
&lt;li&gt;확률 출력이 기본적으로 자연스럽지 않다.&lt;/li&gt;
&lt;li&gt;스케일링이 중요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Random Forest와 Gradient Boosting 차이&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Random&amp;nbsp;Forest&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Gradient&amp;nbsp;Boosting&lt;/b&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;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;병렬화&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DXOyL/dJMcacKyHY5/8on4mvfssNQnm3BLXT8RzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DXOyL/dJMcacKyHY5/8on4mvfssNQnm3BLXT8RzK/img.png&quot; data-alt=&quot;Random Forest와 Gradient Boosting의 차이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DXOyL/dJMcacKyHY5/8on4mvfssNQnm3BLXT8RzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDXOyL%2FdJMcacKyHY5%2F8on4mvfssNQnm3BLXT8RzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Random Forest와 Gradient Boosting의 차이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모델별 전처리 요구사항&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;모델&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;스케일링&amp;nbsp;필요성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;범주형&amp;nbsp;처리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;결측치&amp;nbsp;처리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;해석&amp;nbsp;가능성&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linear Regression&lt;/td&gt;
&lt;td&gt;높음&lt;/td&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;Logistic Regression&lt;/td&gt;
&lt;td&gt;높음&lt;/td&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;KNN&lt;/td&gt;
&lt;td&gt;매우 높음&lt;/td&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;Naive Bayes&lt;/td&gt;
&lt;td&gt;경우에 따라&lt;/td&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;Decision Tree&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&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;Random Forest&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&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;Gradient Boosting&lt;/td&gt;
&lt;td&gt;낮음~중간&lt;/td&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;SVM&lt;/td&gt;
&lt;td&gt;높음&lt;/td&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;h2 data-ke-size=&quot;size26&quot;&gt;실무에서의 실험 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 복잡한 모델을 쓰기보다, 단순한 첫 기준 모델부터 시작하는 게 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;텍스트 분류&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TF-IDF + Logistic Regression&lt;/li&gt;
&lt;li&gt;TF-IDF + Linear SVM&lt;/li&gt;
&lt;li&gt;임베딩 + Logistic Regression&lt;/li&gt;
&lt;li&gt;필요하면 Transformer fine-tuning&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정형 데이터 분류/회귀&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Logistic Regression 또는 Linear Regression&lt;/li&gt;
&lt;li&gt;Decision Tree&lt;/li&gt;
&lt;li&gt;Random Forest&lt;/li&gt;
&lt;li&gt;LightGBM / XGBoost / CatBoost&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터가 작고 해석이 중요할 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Logistic Regression&lt;br /&gt;Decision Tree&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능이 가장 중요하고 정형 데이터일 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LightGBM / XGBoost / CatBoost&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;/li&gt;
&lt;li&gt;선형 모델은 가중합으로, 트리는 조건 분기로, 앙상블은 여러 모델의 판단을 결합해서 예측한다.&lt;/li&gt;
&lt;li&gt;모델 선택의 핵심: 가장 유명한 모델을 고르는 것이 아니라, 데이터 형태와 문제 목적에 맞는 모델을 고르는 것.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/293</guid>
      <comments>https://chaeyami.tistory.com/293#entry293comment</comments>
      <pubDate>Mon, 6 Jul 2026 16:04:05 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 과적합(Overfitting)과 정규화(Regularization) - 훈련 데이터만 잘 맞추는 모델을 막는 법</title>
      <link>https://chaeyami.tistory.com/292</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 모델을 만들면 보통 이런 기대를 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;학습 데이터에서 잘 맞으면&lt;br /&gt;새 데이터에서도 잘 맞겠지.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 그렇지 않을 수 있다.&lt;br /&gt;모델이 학습 데이터에서는 거의 완벽한데, 처음 보는 데이터에서는 성능이 떨어지는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 현상을 &lt;b&gt;과적합(overfitting)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과적합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과적합은 모델이 데이터의 일반적인 패턴이 아니라 &lt;b&gt;학습 데이터에만 있는 우연한 흔들림과 노이즈까지 외워버리는 상태&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 학생이 시험공부를 한다고 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 공부:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제의 원리를 이해한다.&lt;/li&gt;
&lt;li&gt;새 문제가 나와도 풀 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;나쁜 공부:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기출문제 답만 외운다.&lt;/li&gt;
&lt;li&gt;기출과 똑같은 문제는 맞힌다.&lt;/li&gt;
&lt;li&gt;조금만 바뀌면 틀린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과적합된 모델은 두 번째 학생과 비슷하다.&lt;br /&gt;학습 데이터라는 기출문제는 잘 맞히지만, 실제 운영에서 들어오는 새 데이터에는 약하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차원 데이터가 있다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x가 증가하면 y도 대체로 증가한다. 그러나&amp;nbsp;데이터에는 약간의 노이즈가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터를 설명하는 모델을 세 가지로 생각해볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과소적합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 단순한 직선 하나만 그린다.&lt;/li&gt;
&lt;li&gt;데이터의 휘어진 패턴을 제대로 못 따라감&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;좋은 적합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체적인 추세를 따라가는 부드러운 곡선을 그린다.&lt;/li&gt;
&lt;li&gt;노이즈 하나하나에는 휘둘리지 않고 일반 패턴을 잡음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;과적합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 점을 지나가려고 매우 구불구불한 선을 그린다.&lt;/li&gt;
&lt;li&gt;학습 데이터에는 잘 맞지만 새 데이터에는 약함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VCnGC/dJMcaixaCzQ/Zcyu8k9cgF4spebH5LkL3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VCnGC/dJMcaixaCzQ/Zcyu8k9cgF4spebH5LkL3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VCnGC/dJMcaixaCzQ/Zcyu8k9cgF4spebH5LkL3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVCnGC%2FdJMcaixaCzQ%2FZcyu8k9cgF4spebH5LkL3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과소적합과 과적합&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&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;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;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;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;td&gt;학습 데이터의 노이즈까지 외움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 train 성능과 validation/test 성능의 차이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train 성능도 낮고 validation 성능도 낮다&lt;br /&gt;&amp;rarr; 과소적합 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train 성능은 높은데 validation 성능이 낮다&lt;br /&gt;&amp;rarr; 과적합 가능성&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 과적합이 생기는가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모델이 너무 복잡함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 모델은 데이터를 더 세밀하게 맞출 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 decision tree의 깊이가 너무 깊으면, 각 샘플을 거의 따로따로 외울 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깊이 2인 트리: 큰 기준 몇 개로 나눔&lt;/li&gt;
&lt;li&gt;깊이 30인 트리: 매우 세세한 조건까지 나눔&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깊은 트리는 학습 데이터에는 잘 맞는다. 그러나 그 조건들이 실제 일반 패턴이 아니라 우연한 조합일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터가 적음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적으면 모델이 일반 패턴과 노이즈를 구분하기 어렵다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;환불 문의 데이터가 20건뿐이다.&lt;br /&gt;그중 15건에 &quot;급해요&quot;라는 단어가 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 &quot;급해요&quot;가 있으면 환불 문의일 가능성이 높다고 착각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영에서는 배송 문의에도, 교환 문의에도 &amp;ldquo;급해요&amp;rdquo;가 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적으면 우연한 상관관계를 진짜 패턴처럼 배울 위험이 커진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;노이즈가 많음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라벨이 틀리거나 입력 데이터가 불안정하면 과적합이 쉬워진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실제 배송 문의인데 라벨이 환불 문의로 잘못 붙음&lt;br /&gt;고객이 오타를 많이 냄&lt;br /&gt;중복 데이터가 많음&lt;br /&gt;비슷한 문장이 서로 다른 라벨을 가짐&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 이런 노이즈까지 억지로 맞추면 새 데이터 성능이 떨어진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bias와 Variance&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과적합/과소적합은 bias와 variance로도 설명할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bias
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델이 문제를 지나치게 단순하게 보는 경향&lt;/li&gt;
&lt;li&gt;bias가 높으면 복잡한 패턴을 못 잡음&lt;/li&gt;
&lt;li&gt;모든 데이터를 직선 하나로만 설명하려고 함&lt;/li&gt;
&lt;li&gt;이 경우 과소적합이 생기기 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Variance
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학습 데이터가 조금만 바뀌어도 모델이 크게 흔들리는 경향&lt;/li&gt;
&lt;li&gt;variance가 높으면 데이터의 작은 노이즈에도 민감하게 반응&lt;/li&gt;
&lt;li&gt;학습 데이터의 점 하나가 바뀌면 곡선 모양이 크게 바뀜&lt;/li&gt;
&lt;li&gt;이 경우 과적합이 생기기 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Learning Curve로 확인하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Learning Curve는 학습 과정에서 train 성능과 validation 성능이 어떻게 변하는지 보는 그래프다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 학습
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;train loss 감소&lt;/li&gt;
&lt;li&gt;validation loss 감소&lt;/li&gt;
&lt;li&gt;둘 사이 차이가 크지 않음&lt;/li&gt;
&lt;li&gt;모델이 일반 패턴을 배우고 있을 가능성이 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;과적합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;train loss는 계속 감소&lt;/li&gt;
&lt;li&gt;validation loss는 어느 순간부터 증가&lt;/li&gt;
&lt;li&gt;모델이 학습 데이터에만 점점 더 맞춰지고 있다는 신호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;과소적합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;train loss도 높음&lt;/li&gt;
&lt;li&gt;validation loss도 높음&lt;/li&gt;
&lt;li&gt;둘 다 잘 줄지 않음&lt;/li&gt;
&lt;li&gt;모델이 충분히 학습하지 못했거나, 모델이 너무 단순할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WPKbu/dJMcacqdixW/Fi3NUDboN6kY8zxL4FOz7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WPKbu/dJMcacqdixW/Fi3NUDboN6kY8zxL4FOz7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WPKbu/dJMcacqdixW/Fi3NUDboN6kY8zxL4FOz7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWPKbu%2FdJMcacqdixW%2FFi3NUDboN6kY8zxL4FOz7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규화란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화(regularization)는 모델이 학습 데이터에 너무 복잡하게 맞춰지는 것을 막기 위해 &lt;b&gt;복잡한 모델에 벌점을 주는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 학습 목표는 손실 최소화이지만, 정규화를 넣으면 목표가 바뀐다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;손실도 작게 만들고,&lt;/b&gt;&lt;br /&gt;&lt;b&gt;모델도 너무 복잡하지 않게 만든다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;최종 목적함수 = 원래 손실 + 정규화 벌점&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 모델이 학습 데이터를 조금 더 잘 맞히려고 너무 복잡한 파라미터를 만들면, 그만큼 벌점을 받는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파라미터가 크다는 것은 무슨 뜻인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 모델을 예로 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\hat{y} = w_1x_1 + w_2x_2 + b&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 w1, w2가 가중치다. 가중치가 크면 작은 입력 변화에도 출력이 크게 흔들릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;w = 0.2&lt;br /&gt;&amp;rarr; x가 조금 변해도 예측값이 조금 변함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;w = 50&lt;br /&gt;&amp;rarr; x가 조금만 변해도 예측값이 크게 변함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 경우에 큰 가중치가 나쁜 것은 아니지만&lt;br /&gt;과도하게 큰 가중치는 모델이 학습 데이터의 세세한 흔들림에 민감하게 반응한다는 신호일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화는 이런 가중치를 억제한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;L2 정규화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L2 정규화는 가중치 제곱합에 벌점을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Loss_{total} = Loss + \lambda \sum_i w_i^2&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lambda;(lambda): 정규화 강도&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lambda;가 작다 &amp;rarr; 벌점 약함&lt;/li&gt;
&lt;li&gt;&amp;lambda;가 크다 &amp;rarr; 벌점 강함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;효과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L2는 큰 가중치에 강한 벌점을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;w = 2 &amp;rarr; w&amp;sup2; = 4&lt;br /&gt;w = 10 &amp;rarr; w&amp;sup2; = 100&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가중치가 커질수록 벌점이 빠르게 커진다. 그래서 L2는 가중치를 전반적으로 작고 부드럽게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징을 완전히 버리기보다는&lt;br /&gt;여러 특징을 조금씩 사용하게 만드는 경향&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;L1 정규화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L1 정규화는 가중치 절댓값 합에 벌점을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Loss_{total} = Loss + \lambda \sum_i |w_i|&lt;br /&gt;$$&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;효과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L1은 일부 가중치를 정확히 0으로 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;w1 = 0.8&lt;br /&gt;w2 = 0&lt;br /&gt;w3 = 0&lt;br /&gt;w4 = 1.2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가중치가 0이 된 특징은 모델이 사용하지 않는 특징이 된다. 그래서 L1은 특징 선택(feature selection) 효과가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 특징 중 일부만 남기고 싶을 때 유용&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;L1과 L2 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분L1L2&lt;/p&gt;
&lt;table id=&quot;392c2707-14d8-80f9-ae56-ca70632561d6&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;L1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;L2&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-801f-b348-c4df43b32f17&quot;&gt;
&lt;td id=&quot;tHCU&quot;&gt;&lt;b&gt;벌점&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;Dwdu&quot;&gt;가중치 절댓값 합&lt;/td&gt;
&lt;td id=&quot;iDJl&quot;&gt;가중치 제곱합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-80d6-9098-f688f9f19972&quot;&gt;
&lt;td id=&quot;tHCU&quot;&gt;&lt;b&gt;효과&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;Dwdu&quot;&gt;일부 가중치를 0으로 만듦&lt;/td&gt;
&lt;td id=&quot;iDJl&quot;&gt;가중치를 전체적으로 작게 만듦&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-8071-834e-f170275304ac&quot;&gt;
&lt;td id=&quot;tHCU&quot;&gt;&lt;b&gt;특징 선택&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;Dwdu&quot;&gt;강함&lt;/td&gt;
&lt;td id=&quot;iDJl&quot;&gt;약함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-8005-9a74-e5b7fbcc6a67&quot;&gt;
&lt;td id=&quot;tHCU&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;Dwdu&quot;&gt;희소한 모델&lt;/td&gt;
&lt;td id=&quot;iDJl&quot;&gt;부드러운 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;931&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MQkYM/dJMcabdExq0/hvbDDaMaBdidh9lAkolor0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MQkYM/dJMcabdExq0/hvbDDaMaBdidh9lAkolor0/img.png&quot; data-alt=&quot;L1과 L2 정규화가 가중치에 주는 영향&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MQkYM/dJMcabdExq0/hvbDDaMaBdidh9lAkolor0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMQkYM%2FdJMcabdExq0%2FhvbDDaMaBdidh9lAkolor0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1690&quot; height=&quot;931&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;L1과 L2 정규화가 가중치에 주는 영향&lt;/figcaption&gt;
&lt;/figure&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 직접적인 방법&lt;/li&gt;
&lt;li&gt;데이터가 많아지면 모델이 우연한 패턴과 일반 패턴을 구분하기 쉬워진다.&lt;/li&gt;
&lt;li&gt;하지만 항상 더 많은 라벨 데이터를 얻을 수 있는 것은 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Early Stopping
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;validation 성능이 더 이상 좋아지지 않으면 학습을 멈춘다.&lt;/li&gt;
&lt;li&gt;train loss는 계속 내려가도 validation loss가 올라가기 시작하면 멈춤&lt;/li&gt;
&lt;li&gt;딥러닝이나 boosting 모델에서 자주 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dropout
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;신경망에서 일부 뉴런을 학습 중 랜덤하게 꺼버리는 방법&lt;/li&gt;
&lt;li&gt;매번 모든 뉴런에 의존하지 못하게 함&lt;/li&gt;
&lt;li&gt;여러 부분 표현을 함께 쓰도록 유도&lt;/li&gt;
&lt;li&gt;특정 뉴런 조합을 외우는 것을 줄이는 효과가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Data Augmentation
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 데이터를 변형해서 학습 데이터를 늘리는 방법이다.&lt;/li&gt;
&lt;li&gt;이미지에서는 흔히 사용한다.&lt;/li&gt;
&lt;li&gt;좌우 반전 / 작은 회전 / 밝기 변화 / 잘라내기&lt;/li&gt;
&lt;li&gt;텍스트에서는 더 조심해야 한다.(문장을 조금 바꿨는데 라벨 의미가 달라질 수 있기 때문)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tree Pruning
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Decision Tree가 너무 깊게 자라지 않도록 제한한다.&lt;/li&gt;
&lt;li&gt;e.g) max_depth 제한, min_samples_leaf 설정,&amp;nbsp;&lt;br /&gt;min_samples_split&amp;nbsp;설정&lt;/li&gt;
&lt;li&gt;트리는 깊어질수록 학습 데이터를 세밀하게 외우기 쉬우므로, 깊이와 리프 조건을 조절해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;Linear/Logistic Regression&lt;/td&gt;
&lt;td&gt;L1, L2 정규화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision Tree&lt;/td&gt;
&lt;td&gt;max_depth, min_samples_leaf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Forest&lt;/td&gt;
&lt;td&gt;트리 수 증가, depth 제한, 특징 샘플링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient Boosting&lt;/td&gt;
&lt;td&gt;learning_rate 감소, early stopping, depth 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Neural Network&lt;/td&gt;
&lt;td&gt;dropout, weight decay, early stopping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KNN&lt;/td&gt;
&lt;td&gt;너무 작은 k 피하기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 체크리스트&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;train 성능과 validation 성능 차이가 큰가?&lt;/li&gt;
&lt;li&gt;validation loss가 어느 순간부터 증가하는가?&lt;/li&gt;
&lt;li&gt;모델이 데이터 크기에 비해 너무 복잡한가?&lt;/li&gt;
&lt;li&gt;라벨 오류나 중복 데이터가 많은가?&lt;/li&gt;
&lt;li&gt;정규화 강도를 높이면 validation 성능이 좋아지는가?&lt;/li&gt;
&lt;li&gt;tree depth나 boosting iteration을 줄이면 좋아지는가?&lt;/li&gt;
&lt;li&gt;early stopping 지점이 있는가?&lt;/li&gt;
&lt;/ol&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;/li&gt;
&lt;li&gt;정규화는 손실함수에 복잡도 벌점을 추가해 모델이 너무 복잡해지는 것을 막는다.&lt;/li&gt;
&lt;li&gt;학습 데이터만 잘 맞히는 모델이 아니라 처음 보는 데이터에서도 적당히 잘 맞는 모델을 만드는 것이 목표.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>과적합</category>
      <category>정규화</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/292</guid>
      <comments>https://chaeyami.tistory.com/292#entry292comment</comments>
      <pubDate>Fri, 3 Jul 2026 16:55:23 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 분류성능평가지표(Classification Evaluation Metrics) - Confusion Matrix, Accuracy, Precision, ect</title>
      <link>https://chaeyami.tistory.com/291</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분류 모델은 입력을 보고 정해진 범주 중 하나를 고른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이상거래 탐지 모델을 만든다고 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력: 거래 금액, 거래 시간, 사용자 위치, 결제 수단, 과거 거래 패턴&lt;/li&gt;
&lt;li&gt;출력: 정상 거래 / 이상 거래&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 예측을 끝내면 &lt;b&gt;이 모델은 얼마나 잘 맞혔을까?&amp;nbsp;&lt;/b&gt;라는 질문이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 가장 먼저 떠오르는 지표는 정확도다.(정확도 = 전체 중 맞힌 비율)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 분류 문제에서는 정확도만 보면 위험한 경우가 많다.&lt;br /&gt;특히 정답 비율이 한쪽으로 치우친 데이터에서는 정확도가 매우 그럴듯하게 속일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래 탐지 예시를 끝까지 사용해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 거래: 10,000건&lt;/li&gt;
&lt;li&gt;실제 정상 거래: 9,900건&lt;/li&gt;
&lt;li&gt;실제 이상 거래: 100건&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이상거래는 전체의 1%뿐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 예측한 결과가 다음과 같다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;모델이 이상 거래라고 예측한 거래: 80건&lt;br /&gt;그중 실제 이상 거래: 60건&lt;br /&gt;그중 실제 정상 거래: 20건&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 나머지도 계산할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 이상 거래 100건 중 60건을 잡았다.&lt;br /&gt;&amp;rarr; 놓친 이상 거래 = 40건&lt;/li&gt;
&lt;li&gt;실제 정상 거래 9,900건 중 20건을 이상 거래로 잘못 잡았다.&lt;br /&gt;&amp;rarr; 정상으로 잘 맞힌 거래 = 9,880건&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;td&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;이상 거래를 이상 거래라고 맞힘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FP&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;정상 거래를 이상 거래라고 잘못 잡음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FN&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;이상 거래를 정상 거래라고 놓침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TN&lt;/td&gt;
&lt;td&gt;9,880&lt;/td&gt;
&lt;td&gt;정상 거래를 정상 거래라고 맞힘&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 네 개가 분류 평가 지표의 출발점이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Confusion Matrix&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Confusion Matrix는 분류 모델의 예측 결과를 네 구역으로 나눠 보여주는 표다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 분류에서는 보통 다음처럼 본다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&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;b&gt;실제 이상&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;TP&lt;/td&gt;
&lt;td&gt;FN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;실제 정상&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;FP&lt;/td&gt;
&lt;td&gt;TN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TP(True Positive): 잡아야 할 것을 잡음&lt;/li&gt;
&lt;li&gt;FP(False Positive): 잡지 말아야 할 것을 잡음&lt;/li&gt;
&lt;li&gt;FN(False Negative): 잡아야 할 것을 놓침&lt;/li&gt;
&lt;li&gt;TN(True Negative): 잡지 말아야 할 것을 안 잡음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 positive는 좋은 결과라는 뜻이 아니라, 모델이 특별히 찾아내야 하는 대상을 양성(positive)이라고 부르는 약속에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래 탐지에서는 이상 거래가 양성이고, 질병 진단에서는 실제 환자가 양성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제마다 양성이 무엇인지 먼저 정해야 Precision과 Recall을 헷갈리지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이상거래 예시의 Confusion Matrix&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;모델 예측: 이상 거래&amp;nbsp;&lt;/span&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;b&gt;실제 이상 거래&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;TP = 60&lt;/td&gt;
&lt;td&gt;FN = 40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;실제 정상 거래&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;FP = 20&lt;/td&gt;
&lt;td&gt;TN = 9,880&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상 거래 100건 중 60건은 잡았다.&lt;/li&gt;
&lt;li&gt;하지만 40건은 놓쳤다.&lt;/li&gt;
&lt;li&gt;정상 거래 9,900건 중 20건은 잘못 잡았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mQ9ly/dJMcahrwBdW/lEiKmLSgPsqLw8QoEUZEx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mQ9ly/dJMcahrwBdW/lEiKmLSgPsqLw8QoEUZEx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mQ9ly/dJMcahrwBdW/lEiKmLSgPsqLw8QoEUZEx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmQ9ly%2FdJMcahrwBdW%2FlEiKmLSgPsqLw8QoEUZEx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Accuracy&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Accuracy는 전체 중 맞힌 비율이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Accuracy = \frac{TP + TN}{TP + FP + FN + TN}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래 예시에 대입하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Accuracy = \frac{60 + 9880}{60 + 20 + 40 + 9880} = \frac{9940}{10000} = 0.994&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확도는 99.4%다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 보면 매우 좋아 보인다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Accuracy의 함정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이상거래는 전체의 1%뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아예 아무것도 탐지하지 않는 모델을 생각해보자. 이는 모든 거래를 정상 거래라고 예측한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정상 거래 9,900건은 전부 맞힘&lt;/li&gt;
&lt;li&gt;이상 거래 100건은 전부 놓침&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Accuracy는:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Accuracy = \frac{9900}{10000} = 0.99&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확도 99%다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 모델은 이상거래를 하나도 잡지 못한다.&lt;br /&gt;즉, &lt;b&gt;정확도만 보면 쓸모없는 모델도 좋아 보일 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Precision&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision은 모델이 양성(positive)이라고 예측한 것 중 &lt;b&gt;실제 양성&lt;/b&gt;이 얼마나 되는지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래 탐지에서는 &lt;b&gt;모델이 이상 거래라고 잡은 것 중 진짜 이상 거래의 비율&lt;/b&gt;로 해석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Precision = \frac{TP}{TP + FP}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시에 대입하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Precision = \frac{60}{60 + 20} = \frac{60}{80} = 0.75&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision은 75%다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 이상 거래라고 잡은 80건 중 60건이 진짜 이상 거래다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Precision이 중요한 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision은 &lt;b&gt;잘못 잡으면 비용이 큰 문제&lt;/b&gt;에서 중요하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;정상 거래를 이상 거래로 막으면 고객 불편 발생&lt;br /&gt;정상 이메일을 스팸으로 보내면 중요한 메일을 놓침&lt;br /&gt;정상 사용자를 부정 사용자로 차단하면 서비스 신뢰도 하락&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision이 낮으면 거짓 양성(false positive), 즉 정상인데 이상이라고 잘못 잡은 경우가 많다는 뜻이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;모델이 너무 많이 의심한다.&lt;br /&gt;잡은 것 중 가짜가 많다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Recall&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recall은 실제 양성(positive) 중 모델이 얼마나 많이 찾아냈는지다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Recall = \frac{TP}{TP + FN}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시에 대입하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Recall = \frac{60}{60 + 40} = \frac{60}{100} = 0.6&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recall은 60%다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이상 거래 100건 중 60건을 잡았고, 40건은 놓쳤다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Recall이 중요한 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recall은 &lt;b&gt;놓치면 비용이 큰 문제&lt;/b&gt;에서 중요하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;암 진단에서 실제 환자를 놓치면 위험&lt;br /&gt;이상거래 탐지에서 사기 거래를 놓치면 손실&lt;br /&gt;보안 탐지에서 공격을 놓치면 침해 발생&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recall이 낮으면 거짓 음성(false negative), 즉 이상인데 정상이라고 놓친 경우가 많다는 뜻이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;모델이 너무 소극적이다.&lt;br /&gt;잡아야 할 것을 많이 놓친다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Precision과 Recall의 trade-off&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision과 Recall은 자주 충돌한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래 모델은 보통 각 거래에 대해 점수를 낸다. (e.g.&amp;nbsp;이 거래가 이상 거래일 확률: 0.82)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 분류 기준값(threshold)을 기준으로 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점수 &amp;gt;= 0.5 &amp;rarr; 이상 거래&lt;br /&gt;점수 &amp;lt; 0.5 &amp;rarr; 정상 거래&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분류 기준값을 높이면 모델은 더 확실한 것만 잡는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;threshold = 0.9&lt;br /&gt;&amp;rarr; 정말 의심스러운 거래만 잡음&lt;br /&gt;&amp;rarr; FP 감소&lt;br /&gt;&amp;rarr; Precision 상승 가능&lt;br /&gt;&amp;rarr; FN 증가&lt;br /&gt;&amp;rarr; Recall 하락 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분류 기준값을 낮추면 모델은 더 많이 잡는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;threshold = 0.2&lt;br /&gt;&amp;rarr; 조금만 의심스러워도 잡음&lt;br /&gt;&amp;rarr; TP 증가 가능&lt;br /&gt;&amp;rarr; Recall 상승 가능&lt;br /&gt;&amp;rarr; FP 증가&lt;br /&gt;&amp;rarr; Precision 하락 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;더 많이 잡으면 Recall은 올라가기 쉽지만 Precision은 떨어질 수 있다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;더 신중하게 잡으면 Precision은 올라가기 쉽지만 Recall은 떨어질 수 있다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU78RP/dJMcahEX4sJ/5eCBhp8Ny0IkbMR5nl4fYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU78RP/dJMcahEX4sJ/5eCBhp8Ny0IkbMR5nl4fYK/img.png&quot; data-alt=&quot;threshold 변화에 따른 precision/recall trade-off&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU78RP/dJMcahEX4sJ/5eCBhp8Ny0IkbMR5nl4fYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU78RP%2FdJMcahEX4sJ%2F5eCBhp8Ny0IkbMR5nl4fYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1693&quot; height=&quot;929&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;threshold 변화에 따른 precision/recall trade-off&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;F1 Score&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision과 Recall을 따로 보면 선택이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 두 모델이 있다고 하자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; 모델 &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Precision &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; Recall &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A는 잡은 것 중에는 진짜가 많지만, 실제 이상거래를 많이 놓친다.&lt;br /&gt;B는 둘 다 적당하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘을 하나의 숫자로 비교하고 싶을 때 F1을 쓴다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1은 Precision과 Recall의 &lt;b&gt;조화평균&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F1 = 2 \times \frac{Precision \times Recall}{Precision + Recall}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래 예시에서는:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F1 = 2 \times \frac{0.75 \times 0.6}{0.75 + 0.6}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F1 \approx 0.667&lt;br /&gt;$$&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 산술평균이 아닌가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;산술평균은 한쪽이 매우 낮아도 점수가 꽤 높게 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;Precision = 1.0&lt;/b&gt; 이고, &lt;b&gt;Recall = 0.0&lt;/b&gt; 이라고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 산술평균은 0.5이지만, &lt;b&gt;Recall이 0이면 실제 양성을 하나도 못 잡은 모델&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1은 이런 모델에 0점을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Precision = 1.0&lt;br /&gt;Recall = 0.0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;산술평균 = 0.5 &lt;br /&gt;F1 = 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1은 Precision과 Recall 중 낮은 쪽에 더 민감하다. &amp;rarr; 둘 사이의 균형을 보려는 지표&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;F-beta Score&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1은 Precision과 Recall을 &lt;b&gt;같은 중요도&lt;/b&gt;로 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 실무에서는 둘 중 하나가 나머지 하나보다 더 중요할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이상거래 탐지: Recall을 더 중요하게 볼 수 있음&lt;br /&gt;스팸 차단: Precision을 더 중요하게 볼 수 있음&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 F-beta를 쓴다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;beta &amp;gt; 1 &amp;rarr; Recall을 더 중요하게 봄&lt;br /&gt;beta &amp;lt; 1 &amp;rarr; Precision을 더 중요하게 봄&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;F2: Recall을 더 중요하게 봄&lt;br /&gt;F0.5: Precision을 더 중요하게 봄&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ROC-AUC&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ROC Curve란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROC Curve는 threshold를 바꿔가며 다음 두 값을 그린 곡선이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TPR(True Positive Rate) = Recall&lt;/li&gt;
&lt;li&gt;FPR(False Positive Rate) = 실제 정상 중 잘못 이상으로 잡은 비율&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;FPR = \frac{FP}{FP + TN}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROC-AUC는 이 곡선 아래 면적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 1에 가까울수록 양성과 음성을 잘 구분한다.&lt;br /&gt;0.5에 가까우면 무작위로 찍는 것과 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROC-AUC는 특정 기준값 하나에서의 성능이 아니라, 기준값을 전반적으로 바꿔봤을 때 양성을 음성보다 위에 잘 올려놓는지 보는 지표다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 유용한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROC-AUC는 모델의 점수 순위가 얼마나 좋은지 보는 데 유용하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실제 양성에 더 높은 점수를 주고&lt;br /&gt;실제 음성에 더 낮은 점수를 주는가?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 클래스 불균형이 심하면 ROC-AUC가 좋아 보여도 실제 양성 탐지 성능이 부족할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상거래처럼 양성이 매우 적은 문제에서는 PR-AUC도 같이 봐야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PR-AUC&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PR Curve란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR Curve는 threshold를 바꿔가며 Precision과 Recall의 관계를 그린 곡선이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;x축: Recall&lt;/b&gt;&lt;br /&gt;&lt;b&gt;y축: Precision&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR-AUC는 이 곡선 아래 면적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR Curve는 &amp;ldquo;많이 찾는 것&amp;rdquo;과 &amp;ldquo;정확히 찾는 것&amp;rdquo;의 관계를 직접 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recall을 높이려면 더 많은 후보를 양성으로 잡아야 한다.&lt;br /&gt;그러면 관련 없는 것도 같이 들어와 Precision이 떨어질 수 있다.&lt;br /&gt;PR Curve는 이 변화가 얼마나 완만하거나 급격한지 보여준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 더 중요한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR-AUC는 양성 클래스가 희귀한 문제에서 특히 중요하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이상거래 탐지&lt;br /&gt;질병 진단&lt;br /&gt;불량품 탐지&lt;br /&gt;악성 사용자 탐지&lt;br /&gt;보안 공격 탐지&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제에서는 음성이 너무 많아서, ROC-AUC만 보면 모델이 괜찮아 보일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR-AUC는 실제로 양성을 잡을 때 Precision과 Recall이 어느 정도 균형을 이루는지 더 직접적으로 보여준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다중 분류에서의 평균 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 분류에서는 클래스가 여러 개다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;배송 문의&lt;br /&gt;환불 문의&lt;br /&gt;교환 문의&lt;br /&gt;상품 문의&lt;br /&gt;기타 문의&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 클래스마다 Precision, Recall, F1을 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이것을 하나의 점수로 어떻게 합치느냐다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Macro Average
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 클래스 점수를 단순 평균한다. &amp;rarr; 클래스별 점수를 똑같은 비중으로 평균&lt;/li&gt;
&lt;li&gt;데이터가 적은 클래스도 큰 클래스와 같은 중요도로 본다.&lt;/li&gt;
&lt;li&gt;클래스 불균형이 있을 때 소수 클래스를 놓치고 싶지 않다면 macro average가 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Micro Average
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 TP, FP, FN을 합쳐서 한 번에 계산&lt;/li&gt;
&lt;li&gt;샘플 수가 많은 클래스의 영향이 커짐&lt;/li&gt;
&lt;li&gt;전체적인 예측 성능을 보고 싶을 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weighted Average
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스별 점수를 샘플 수 비율로 가중평균&lt;/li&gt;
&lt;li&gt;데이터가 많은 클래스는 더 크게 반영&lt;br /&gt;데이터가 적은 클래스는 덜 반영&lt;/li&gt;
&lt;li&gt;전체 데이터 분포를 반영한 평균&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Calibration&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Calibration은 모델이 출력한 확률이 실제 확률과 잘 맞는지 보는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 모델이 100건에 대해 각각 0.8 확률로 이상거래라고 예측했다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 보정된 모델이라면 그 100건 중 약 80건이 실제 이상거래여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측 확률 0.8&lt;br /&gt;&amp;rarr; 실제로도 약 80% 정도 맞아야 함&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 중요한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 모델을 단순히 맞히는 데만 쓰면 calibration이 덜 중요할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 확률을 의사결정에 쓰면 중요해진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이상거래 확률 0.95 이상이면 즉시 차단&lt;br /&gt;0.70~0.95면 사람이 검토&lt;br /&gt;0.70 미만이면 통과&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 확률값 자체가 믿을 만해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확률이 과하게 높거나 낮게 나오면 운영 정책이 흔들린다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지표 선택 기준&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;Accuracy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;잘못 잡는 비용이 큼&lt;/td&gt;
&lt;td&gt;Precision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;놓치는 비용이 큼&lt;/td&gt;
&lt;td&gt;Recall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision/Recall 균형 필요&lt;/td&gt;
&lt;td&gt;F1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recall을 더 중요하게 보고 싶음&lt;/td&gt;
&lt;td&gt;F2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision을 더 중요하게 보고 싶음&lt;/td&gt;
&lt;td&gt;F0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;불균형 데이터에서 양성 탐지 중요&lt;/td&gt;
&lt;td&gt;PR-AUC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전체 기준값 변화에 따른 구분 능력 확인&lt;/td&gt;
&lt;td&gt;ROC-AUC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확률값으로 의사결정&lt;/td&gt;
&lt;td&gt;Calibration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;br /&gt;어떤 종류의 실수를 얼마나 했는지 보는 일이다.&lt;/li&gt;
&lt;li&gt;Accuracy: 전체 정답률.&lt;/li&gt;
&lt;li&gt;Precision: 잡은 것 중 진짜의 비율.&lt;/li&gt;
&lt;li&gt;Recall: 진짜 중 잡아낸 비율.&lt;/li&gt;
&lt;li&gt;F1: Precision과 Recall의 균형.&lt;/li&gt;
&lt;li&gt;PR-AUC: 희귀한 양성을 탐지할 때 중요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>Accuracy</category>
      <category>Calibration</category>
      <category>confusion matrix</category>
      <category>F1 Score</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>PR-AUC</category>
      <category>Precision</category>
      <category>recall</category>
      <category>roc-auc</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/291</guid>
      <comments>https://chaeyami.tistory.com/291#entry291comment</comments>
      <pubDate>Fri, 3 Jul 2026 15:16:56 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 데이터 분리(Data Split)와 데이터 누수(Data Leakage) - 모델 평가를 믿을 수 있게 만드는 법</title>
      <link>https://chaeyami.tistory.com/290</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 모델은 학습 데이터에 잘 맞는 것만으로는 충분하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 질문은 이거다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 모델이 처음 보는 새 데이터에서도 잘 맞을까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 CS 문의 분류 모델을 만든다고 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목표: 고객 문의 문장을 보고 문의 유형을 예측한다.&lt;/li&gt;
&lt;li&gt;입력 예시: &quot;배송이 너무 늦어요&quot;&lt;/li&gt;
&lt;li&gt;정답 예시: 배송 지연 문의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 10,000건 있다고 해서, 10,000건 전부로 학습하고 같은 10,000건으로 평가하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그건 모델이 &lt;b&gt;시험 문제를 미리 보고 공부한 뒤 같은 문제로 시험을 보는 것&lt;/b&gt;과 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 진짜로 잘하는지 보려면, 학습에 쓰지 않은 데이터를 따로 남겨두고 평가해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터 10,000건&lt;br /&gt;&amp;rarr; 학습용 데이터 8,000건&lt;br /&gt;&amp;rarr; 검증용 데이터 1,000건&lt;br /&gt;&amp;rarr; 테스트용 데이터 1,000건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분리의 목적은 단순히 데이터를 나누는 것이 아니다.&lt;br /&gt;모델이 실제 운영 환경에서 만날 새 데이터에 얼마나 잘 대응하는지 추정하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 추정이 믿을 만하려면, 평가 데이터의 정보가 학습 과정에 들어가면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 정보가 들어가는 순간 평가 점수는 실제보다 좋아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 &lt;b&gt;데이터 누수(data leakage)&lt;/b&gt;다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 용어&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Train 데이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 실제로 학습하는 데이터.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 train 데이터에서 패턴을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;배송&quot;, &quot;도착&quot;, &quot;택배&quot;가 자주 나오면 배송 문의일 가능성이 높다.&lt;br /&gt;&quot;환불&quot;, &quot;취소&quot;, &quot;돈&quot;이 자주 나오면 환불 문의일 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train 데이터는 모델의 파라미터를 바꾸는 데 직접 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Validation 데이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 고르는 데 사용하는 데이터.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 중 어떤 모델이 좋은지 고를 때 validation 데이터를 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 A: Logistic Regression&lt;br /&gt;모델 B: Random Forest&lt;br /&gt;모델 C: LightGBM&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans K=10&lt;br /&gt;KMeans K=30&lt;br /&gt;KMeans K=70&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validation 데이터는 학습에는 직접 쓰지 않지만, 모델 선택에 영향을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 validation 데이터도 너무 많이 보면 거기에 맞춰질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validation 점수가 좋은 모델을 계속 고르다 보면&lt;br /&gt;결국 validation 데이터에 과하게 맞춘 선택을 하게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Test 데이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 딱 한 번 확인하는 최종 평가용 데이터.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test 데이터는 다음이 모두 끝난 뒤에만 봐야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;모델 종류 선택&lt;br /&gt;하이퍼파라미터 선택&lt;br /&gt;전처리 방식 선택&lt;br /&gt;특징 선택&lt;br /&gt;분류 기준값(threshold) 선택&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test 데이터를 보고 다시 모델을 고치면, 그 순간 test 데이터는 더 이상 순수한 test 데이터가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test 점수 확인&lt;br /&gt;&amp;rarr; 결과가 별로라서 모델 수정&lt;br /&gt;&amp;rarr; 다시 test 점수 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 test 데이터는 사실상 validation 데이터처럼 쓰인 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Train / Validation / Test 차이&lt;/h2&gt;
&lt;table id=&quot;392c2707-14d8-802e-bb95-d0ffc00b7bac&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&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;td&gt;여러 번 봐도 되는가&lt;/td&gt;
&lt;td&gt;비유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-80d8-aa32-cafd353c8714&quot;&gt;
&lt;td id=&quot;:cvg&quot;&gt;Train&lt;/td&gt;
&lt;td id=&quot;WIP|&quot;&gt;패턴 학습&lt;/td&gt;
&lt;td id=&quot;Ck{m&quot;&gt;예&lt;/td&gt;
&lt;td id=&quot;?Ld{&quot;&gt;예&lt;/td&gt;
&lt;td id=&quot;[Z[E&quot;&gt;문제집&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-80f1-84bb-e7d7c2464a19&quot;&gt;
&lt;td id=&quot;:cvg&quot;&gt;Validation&lt;/td&gt;
&lt;td id=&quot;WIP|&quot;&gt;모델 선택&lt;/td&gt;
&lt;td id=&quot;Ck{m&quot;&gt;아니오&lt;/td&gt;
&lt;td id=&quot;?Ld{&quot;&gt;어느 정도 가능&lt;/td&gt;
&lt;td id=&quot;[Z[E&quot;&gt;중간고사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;392c2707-14d8-80d8-86c0-e46b622edc46&quot;&gt;
&lt;td id=&quot;:cvg&quot;&gt;Test&lt;/td&gt;
&lt;td id=&quot;WIP|&quot;&gt;최종 평가&lt;/td&gt;
&lt;td id=&quot;Ck{m&quot;&gt;아니오&lt;/td&gt;
&lt;td id=&quot;?Ld{&quot;&gt;원칙적으로 한 번&lt;/td&gt;
&lt;td id=&quot;[Z[E&quot;&gt;최종 시험&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Train: 모델을 만드는 데 사용&lt;/li&gt;
&lt;li&gt;Validation: 어떤 모델을 쓸지 고르는 데 사용&lt;/li&gt;
&lt;li&gt;Test: 최종 성능을 추정하는 데 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IEu3T/dJMcafN11eB/DvHC9yRlXvWJXAceDU8g7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IEu3T/dJMcafN11eB/DvHC9yRlXvWJXAceDU8g7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IEu3T/dJMcafN11eB/DvHC9yRlXvWJXAceDU8g7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIEu3T%2FdJMcafN11eB%2FDvHC9yRlXvWJXAceDU8g7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 누수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 누수는 &lt;b&gt;실제 예측 시점에는 알 수 없는 정보가 학습 또는 평가 과정에 들어가는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 평가 데이터의 힌트가 모델 학습 과정에 새어 들어간 것이라고도 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누수가 생기면 평가 점수가 높아지지만, 그 점수를 믿을 수 없다.&lt;br /&gt;모델이 진짜 패턴을 배운 것이 아니라, 평가 데이터나 미래 정보를 미리 본 상태로 문제를 푼 것이기 때문.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;누수를 판단하는 기준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;실제&amp;nbsp;운영에서&amp;nbsp;이&amp;nbsp;예측을&amp;nbsp;하는&amp;nbsp;순간,&amp;nbsp;이&amp;nbsp;정보를&amp;nbsp;알고&amp;nbsp;있었을까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고 있었다면 사용할 수 있다.&lt;br /&gt;몰랐다면 사용하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 고객 이탈 예측을 한다고 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목표: 이번 달 고객이 이탈할지 예측&lt;/li&gt;
&lt;li&gt;예측 시점: 이번 달 1일&lt;/li&gt;
&lt;li&gt;사용 가능한 정보:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지난달까지의 로그인 횟수&lt;/li&gt;
&lt;li&gt;지난달까지의 결제 이력&lt;/li&gt;
&lt;li&gt;지난달까지의 문의 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용하면 안 되는 정보:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이번 달 20일에 실제로 탈퇴한 날짜&lt;/li&gt;
&lt;li&gt;이탈 처리 완료 여부&lt;/li&gt;
&lt;li&gt;해지 사유 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탈퇴일이나 해지 사유는 예측하고 싶은 결과가 일어난 뒤에 생기는 정보다.&lt;br /&gt;이런 정보를 넣으면 모델은 &lt;b&gt;미래를 맞힌 게 아니라 미래를 훔쳐본 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 누수가 위험한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누수가 있으면 validation/test 점수가 실제보다 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 환불 문의 분류 모델에 이런 컬럼이 있다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1783056835140&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;문의 내용: &quot;상품을 반품하고 싶어요&quot;
처리 상태: 환불 완료
정답 라벨: 환불 문의&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리 상태 = 환불 완료는 정답을 거의 직접 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 문의 문장을 이해하지 않아도 된다. (처리 상태가 환불 완료면 &amp;rarr; 환불 문의)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 학습한 모델은 평가 데이터에서도 높은 점수를 낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영에서는 문의가 들어온 순간 아직 처리 상태가 없기 때문에, 운영에 올리면 성능이 무너진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;잘못된 의사결정을 만든다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누수가 있으면 이런 착각을 하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;이 모델은 정확도가 98%네.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;운영에 바로 써도 되겠다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 모델이 업무에 필요한 패턴을 배운 것이 아닐 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;진짜 배운 것: 고객 문장의 의미&lt;/li&gt;
&lt;li&gt;누수로 배운 것: 나중에 생긴 처리 결과 컬럼&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이를 놓치면, 개발 단계에서는 좋아 보였던 모델이 운영에서 바로 실패한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대표적인 데이터 누수 유형&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전처리 누수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리 누수는 &lt;b&gt;전체 데이터에 전처리기를 먼저 fit한 뒤 train/test를 나누거나, validation/test 정보가 전처리 과정에 들어가는 경우&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리기는 단순 변환처럼 보이지만, fit 단계에서 데이터를 보고 통계를 학습한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 StandardScaler는 평균과 표준편차를 계산한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fit: 평균과 표준편차 계산&lt;/li&gt;
&lt;li&gt;transform: 계산된 평균과 표준편차로 값을 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF도 마찬가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fit: 전체 단어 목록과 IDF 계산&lt;/li&gt;
&lt;li&gt;transform: 문서를 TF-IDF 벡터로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;나쁜 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터로 TF-IDF fit&lt;br /&gt;&amp;rarr; 전체 데이터를 벡터화&lt;br /&gt;&amp;rarr; train/test split&lt;br /&gt;&amp;rarr; 모델 학습 및 평가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 test 문서가 이미 TF-IDF의 vocabulary와 IDF 계산에 참여했다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, test 데이터의 단어 분포가 학습 과정에 들어갔다.&lt;/p&gt;
&lt;pre id=&quot;code_1783056980635&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 나쁜 예: 전체 데이터로 먼저 fit
vectorizer = TfidfVectorizer()
X_all = vectorizer.fit_transform(texts)

X_train, X_test, y_train, y_test = train_test_split(X_all, y)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;좋은 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train/test split 먼저 수행&lt;br /&gt;&amp;rarr; train 데이터로만 TF-IDF fit&lt;br /&gt;&amp;rarr; train 데이터 transform&lt;br /&gt;&amp;rarr; test 데이터는 transform만 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 이거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fit은 train에만 한다.&lt;br /&gt;test에는 transform만 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1783056987752&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 좋은 예: split 먼저, fit은 train에만
X_train_text, X_test_text, y_train, y_test = train_test_split(texts, y)

vectorizer = TfidfVectorizer()
X_train = vectorizer.fit_transform(X_train_text)
X_test = vectorizer.transform(X_test_text)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Target Leakage&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Target leakage는 &lt;b&gt;정답을 직간접적으로 알려주는 컬럼이 입력 특징(feature)에 들어가는 경우&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 환불 여부를 예측하는데 이런 컬럼이 있다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1783057017715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;환불 처리일
환불 완료 여부
환불 담당자 ID
환불 사유 코드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컬럼들은 환불이 발생한 뒤에 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측 시점에는 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이런 컬럼을 입력 특징에 넣으면 모델은 너무 쉽게 맞힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 문의 접수 시점에 이 문의가 환불 문의인지 예측한다고 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용 가능한 정보:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문의 제목&lt;/li&gt;
&lt;li&gt;문의 내용&lt;/li&gt;
&lt;li&gt;접수 시간&lt;/li&gt;
&lt;li&gt;고객 등급&lt;/li&gt;
&lt;li&gt;주문 상품 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용하면 안 되는 정보:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리 완료 상태&lt;/li&gt;
&lt;li&gt;환불 승인 여부&lt;/li&gt;
&lt;li&gt;환불 처리일&lt;/li&gt;
&lt;li&gt;최종 상담 분류 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 상담 분류 코드는 정답 라벨과 거의 같다.&lt;br /&gt;그걸 입력 특징으로 넣으면 모델이 &amp;ldquo;학습&amp;rdquo;하는 것이 아니라 답안지를 보는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 누수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간 누수는 &lt;b&gt;미래 정보를 과거 예측에 사용하는 경우&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시계열 데이터나 운영 로그 데이터에서 자주 발생한다.&lt;br /&gt;예를 들어 7월 주문 취소를 예측하는데 8월 행동 데이터가 들어가면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 랜덤 분리가 위험한 경우 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터에 시간 순서가 있을 때 무작위로 train/test를 나누면 미래 데이터가 train에 들어갈 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1월 데이터&lt;br /&gt;2월 데이터&lt;br /&gt;3월 데이터&lt;br /&gt;4월 데이터&lt;br /&gt;5월 데이터&lt;br /&gt;6월 데이터&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작위 분리를 하면 이렇게 될 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Train: 1월, 3월, 5월, 6월&lt;br /&gt;Test: 2월, 4월&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 모델은 5월, 6월의 패턴을 학습한 뒤 2월, 4월을 평가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영에서는 과거를 예측할 때 미래를 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;좋은 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 순서가 중요하면 과거로 학습하고 미래로 평가해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1783057239224&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Train: 1월~4월
Validation: 5월
Test: 6월&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 rolling validation을 쓸 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1783057254434&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Fold 1: 1~3월 학습 &amp;rarr; 4월 평가
Fold 2: 1~4월 학습 &amp;rarr; 5월 평가
Fold 3: 1~5월 학습 &amp;rarr; 6월 평가&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcaONQ/dJMcahZhl2r/nhNm04PgpfYx8q15DWSL51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcaONQ/dJMcahZhl2r/nhNm04PgpfYx8q15DWSL51/img.png&quot; data-alt=&quot;시간 데이터에서 랜덤 분리가 위험한 이유&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcaONQ/dJMcahZhl2r/nhNm04PgpfYx8q15DWSL51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcaONQ%2FdJMcahZhl2r%2FnhNm04PgpfYx8q15DWSL51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;시간 데이터에서 랜덤 분리가 위험한 이유&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그룹 누수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룹 누수는 &lt;b&gt;같은 사용자, 같은 상품, 같은 문서, 같은 환자처럼 서로 강하게 연결된 샘플이 train과 test에 동시에 들어가는 경우&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 새로운 대상을 맞힌 것이 아니라, 이미 본 대상의 다른 조각을 맞힌 것일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시1: 사용자 데이터&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 사용자가 여러 개의 샘플을 만든다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;user_001의 1월 행동&lt;br /&gt;user_001의 2월 행동&lt;br /&gt;user_001의 3월 행동&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤 split을 하면 이렇게 될 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Train: user_001의 1월, 2월 행동&lt;br /&gt;Test: user_001의 3월 행동&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 user_001의 특성을 이미 봤다.&lt;br /&gt;그래서 test 점수가 좋아 보일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영 목표가 &amp;ldquo;처음 보는 사용자&amp;rdquo;에 대한 예측이라면 이 평가는 과하게 낙관적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 예시 2: RAG 문서 청크 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 문서를 여러 청크로 잘랐다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;문서 A - chunk 1&lt;br /&gt;문서 A - chunk 2&lt;br /&gt;문서 A - chunk 3&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chunk 단위로 랜덤 split을 하면 같은 문서의 일부가 train과 test에 동시에 들어갈 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Train: 문서 A chunk 1, chunk 2&lt;br /&gt;Test: 문서 A chunk 3&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 모델이나 검색 시스템은 완전히 새로운 문서를 만난 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 단위로 나눠야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Train: 문서 A, B, C&lt;br /&gt;Test: 문서 D, E&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;중복 누수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 또는 거의 같은 데이터가 train과 test에 같이 들어가는 경우다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 같은 문의가 약간만 바뀌어 여러 번 저장되어 있을 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;배송이 너무 늦어요&quot;&lt;br /&gt;&quot;배송이 너무 늦습니다&quot;&lt;br /&gt;&quot;배송 너무 늦어요&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 하나가 train에 있고 하나가 test에 있으면, 모델이 진짜 일반화한 것처럼 보이지만 사실상 같은 문제를 다시 푼 것에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 제거 또는 그룹화가 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분리 전략 선택하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 랜덤 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 방식. 전체 데이터에서 무작위로 train/validation/test를 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 가능한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;각 샘플이 서로 독립적이다.&lt;br /&gt;시간 순서가 중요하지 않다.&lt;br /&gt;같은 사용자/상품/문서가 반복되지 않는다.&lt;br /&gt;클래스 불균형이 심하지 않다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stratified Split&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 문제에서 클래스 비율을 유지하면서 나누는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 전체 데이터가 이렇게 생겼다고 하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;정상 문의: 9,000건&lt;br /&gt;환불 문의: 1,000건&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 랜덤 split을 하면 작은 데이터에서는 특정 클래스가 한쪽에 치우칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stratified split은 train/test에 클래스 비율이 비슷하게 유지되도록 나눈다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Train: 정상 7,200건 / 환불 800건&lt;br /&gt;Test: 정상 1,800건 / 환불 200건&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 가능한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;분류 문제다.&lt;br /&gt;클래스 불균형이 있다.&lt;br /&gt;시간/그룹 조건보다 클래스 비율 유지가 더 중요하다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Group Split&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 그룹이 train과 test에 동시에 들어가지 않게 나누는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그룹 예시:&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;사용자 ID&lt;br /&gt;상품 ID&lt;br /&gt;회사 ID&lt;br /&gt;문서 ID&lt;br /&gt;환자 ID&lt;br /&gt;계약 ID&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 가능한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;같은 대상에서 여러 샘플이 만들어진다.&lt;br /&gt;새로운 대상에 대한 일반화 성능을 보고 싶다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Time-based Split&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 순서대로 나누는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 가능한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;운영에서 과거 데이터로 미래를 예측한다.&lt;br /&gt;데이터 분포가 시간에 따라 변한다.&lt;br /&gt;월별/일별 로그 데이터다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 기반 split에서는 test 데이터가 train 데이터보다 미래여야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Train: 과거&lt;br /&gt;Validation: 그 다음 기간&lt;br /&gt;Test: 가장 미래 기간&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cross Validation&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 쓰는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적으면 한 번의 train/test split만으로 성능을 판단하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운 좋게 쉬운 데이터가 test에 들어가면 점수가 높게 나오고,&lt;br /&gt;운 나쁘게 어려운 데이터가 test에 들어가면 점수가 낮게 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cross Validation은 데이터를 여러 번 나눠 평가한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Fold 1: A/B/C/D로 학습 &amp;rarr; E로 평가&lt;br /&gt;Fold 2: A/B/C/E로 학습 &amp;rarr; D로 평가&lt;br /&gt;Fold 3: A/B/D/E로 학습 &amp;rarr; C로 평가&lt;br /&gt;Fold 4: A/C/D/E로 학습 &amp;rarr; B로 평가&lt;br /&gt;Fold 5: B/C/D/E로 학습 &amp;rarr; A로 평가&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 점수를 평균낸다. &lt;b&gt;최종 점수 = 여러 fold 점수의 평균&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cross Validation에서도 누수는 생긴다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cross Validation을 쓴다고 자동으로 안전해지는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리기를 전체 데이터에 먼저 fit한 뒤 K-Fold를 돌리면 누수가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나쁜 흐름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 데이터로 TF-IDF fit &amp;rarr; K-Fold split &amp;rarr; 각 fold 평가&lt;/li&gt;
&lt;li&gt;문제: 각 validation fold가 이미 vocabulary와 IDF 계산에 참여했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;좋은 흐름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fold마다 train fold로만 TF-IDF fit &amp;rarr; validation fold에는 transform만 적용&lt;/li&gt;
&lt;li&gt;scikit-learn에서는 Pipeline을 사용하면 이런 실수를 줄일 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Pipeline 안에 넣으면 각 fold마다 train fold에서만 `fit`이 수행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1783057808715&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

pipe = Pipeline([
    (&quot;tfidf&quot;, TfidfVectorizer()),
    (&quot;clf&quot;, LogisticRegression(max_iter=1000))
])

scores = cross_val_score(pipe, texts, labels, cv=5)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무에서 자주 하는 실수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;test 점수를 보고 threshold를 고침&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;test에서 recall이 낮네.&lt;br /&gt;분류 기준값(threshold)을 0.5에서 0.3으로 낮추자.&lt;br /&gt;다시 test 평가하자.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 test 데이터를 모델 선택에 사용한 것.&lt;br /&gt;분류 기준값은 validation 데이터에서 정해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 데이터로 결측치 평균을 채움&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;전체 데이터 평균 나이 = 34.7&lt;br /&gt;모든 결측치를 34.7로 채움&lt;br /&gt;그다음 train/test split&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test 데이터의 통계가 train 전처리에 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋은 방식:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;train 평균으로 결측치 채움&lt;/li&gt;
&lt;li&gt;test는 train 평균을 그대로 사용해서 채움&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;미래에 생기는 컬럼을 feature로 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측 시점 이후에 생기는 정보는 쓰면 안 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;환불 완료일&lt;br /&gt;상담 처리 결과&lt;br /&gt;배송 완료 여부&lt;br /&gt;탈퇴 사유&lt;br /&gt;최종 승인 상태&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 컬럼은 모델 점수를 쉽게 올리지만, 운영에서는 사용할 수 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;같은 문서의 청크를 랜덤으로 나눔&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 평가에서 자주 생긴다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;같은 PDF의 chunk 일부는 train&lt;br /&gt;같은 PDF의 다른 chunk는 test&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 문서 단위 일반화 평가가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 단위로 분리해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분리 전략 선택 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 아래 순서로 생각하면 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시간 순서가 중요한가?&lt;br /&gt;&amp;rarr; time-based split 우선&lt;/li&gt;
&lt;li&gt;같은 사용자/상품/문서/환자가 여러 샘플을 만드는가?&lt;br /&gt;&amp;rarr; group split 우선&lt;/li&gt;
&lt;li&gt;분류 문제이고 클래스 불균형이 있는가?&lt;br /&gt;&amp;rarr; stratified split 고려&lt;/li&gt;
&lt;li&gt;데이터가 적은가?&lt;br /&gt;&amp;rarr; cross validation 고려&lt;/li&gt;
&lt;li&gt;전처리기가 있는가?&lt;br /&gt;&amp;rarr; 반드시 train 또는 train fold 안에서만 fit&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 조건이 동시에 있으면 더 강한 제약을 우선한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 시간 순서와 사용자 그룹이 둘 다 중요하다면, 단순 랜덤 split은 거의 항상 부적절하다.&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;/li&gt;
&lt;li&gt;데이터 누수는 평가 데이터나 미래 정보가 학습 과정에 섞여 점수를 가짜로 좋게 만드는 문제다.&lt;/li&gt;
&lt;li&gt;가장 중요한 규칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fit은 train에만.&lt;/li&gt;
&lt;li&gt;test는 마지막에 한 번만.&lt;/li&gt;
&lt;li&gt;실제 예측 시점에 모르는 정보는 입력 특징으로 쓰지 말 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>데이터누수</category>
      <category>데이터분리</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/290</guid>
      <comments>https://chaeyami.tistory.com/290#entry290comment</comments>
      <pubDate>Fri, 3 Jul 2026 11:51:59 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 지도학습과 비지도학습 - 정답 라벨이 있느냐 없느냐</title>
      <link>https://chaeyami.tistory.com/289</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝&amp;nbsp;문제를&amp;nbsp;처음&amp;nbsp;볼&amp;nbsp;때&amp;nbsp;가장&amp;nbsp;먼저&amp;nbsp;확인할&amp;nbsp;것&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정답 라벨이 있느냐?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답&amp;nbsp;라벨이&amp;nbsp;있으면&amp;nbsp;지도학습에&amp;nbsp;가깝다.&lt;br /&gt;정답 라벨이 없고 데이터의 구조를 찾는다면 비지도학습에 가깝다.&lt;br /&gt;라벨이 조금만 있거나, 데이터 자체에서 학습 문제를 만들거나, 행동에 대한 보상을 통해 배우는 경우도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지도학습: 입력과 정답이 함께 있는가?&lt;/li&gt;
&lt;li&gt;비지도학습: 정답 없이 데이터 구조를 찾는가?&lt;/li&gt;
&lt;li&gt;반지도학습: 라벨 있는 데이터가 적고, 라벨 없는 데이터가 많은가?&lt;/li&gt;
&lt;li&gt;자기지도학습: 데이터 자체에서 맞혀야 할 문제를 만들 수 있는가?&lt;/li&gt;
&lt;li&gt;강화학습: 행동의 결과로 보상을 받고, 장기 보상을 키우는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 문제는 보통 다음 순서로 생각하면 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 정답 y가 있는가?
   &amp;rarr; 있다: 지도학습
   &amp;rarr; 없다: 다음 질문

2. 데이터끼리 구조를 찾고 싶은가?
   &amp;rarr; 그렇다: 비지도학습

3. 라벨은 조금 있고, 라벨 없는 데이터가 많은가?
   &amp;rarr; 그렇다: 반지도학습

4. 데이터 일부를 가리거나 변형해서 맞히게 할 수 있는가?
   &amp;rarr; 그렇다: 자기지도학습

5. 정답이 아니라 행동의 결과로 보상이 주어지는가?
   &amp;rarr; 그렇다: 강화학습
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은, 이 분류들은 완전히 서로 배타적인 상자가 아니라는 것. 실제&amp;nbsp;시스템에서는&amp;nbsp;여러&amp;nbsp;방식을&amp;nbsp;조합할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;e.g.&lt;br /&gt;&lt;b&gt;LLM 사전학습: 자기지도학습 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;LLM 지시 따르기 파인튜닝: 지도학습&lt;/b&gt;&lt;br /&gt;&lt;b&gt; RLHF: 강화학습 계열 아이디어 포함 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;RAG 시스템 평가 데이터 구축: 지도학습적 평가 세트 활용&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/331U3/dJMcaiKCc4B/5E8dcobQ8fBWI9Yfaiu8U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/331U3/dJMcaiKCc4B/5E8dcobQ8fBWI9Yfaiu8U0/img.png&quot; data-alt=&quot;학습 방식 선택 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/331U3/dJMcaiKCc4B/5E8dcobQ8fBWI9Yfaiu8U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F331U3%2FdJMcaiKCc4B%2F5E8dcobQ8fBWI9Yfaiu8U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;1402&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;학습 방식 선택 흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지도학습(Supervised Learning)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습은 &lt;b&gt;입력 데이터와 정답 라벨이 함께 있는 상태에서 학습하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;입력 x &amp;rarr; 정답 y
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 입력을 보고 정답에 가까운 출력을 내도록 학습한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 &amp;ldquo;지도&amp;rdquo;학습인가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 지도는 선생님이 옆에서 답을 알려주는 상황에 가깝다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력: &quot;배송이 너무 늦어요&quot;
정답: 배송 지연 문의
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 처음에 틀리게 예측해도 정답이 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;모델 예측: 환불 문의
실제 정답: 배송 지연 문의
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 이 차이를 손실함수로 계산하고, 다음에는 정답에 더 가까워지도록 파라미터를 조정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지도학습의 핵심 구조&lt;/h3&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;학습 데이터:
(x1, y1), (x2, y2), (x3, y3), ...

목표:
새로운 x가 들어왔을 때 적절한 y를 예측하기
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;입력&amp;nbsp;x&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;정답&amp;nbsp;y&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;배송이 안 와요&quot;&lt;/td&gt;
&lt;td&gt;배송 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;환불하고 싶어요&quot;&lt;/td&gt;
&lt;td&gt;환불 문의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;상품을 바꾸고 싶어요&quot;&lt;/td&gt;
&lt;td&gt;교환 문의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 이 예시들을 보고 새로운 문장이 들어왔을 때 분류한다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력: &quot;택배가 아직 도착하지 않았습니다&quot;
예측: 배송 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지도학습의 대표 문제 1 - 분류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류는 정해진 클래스 중 하나를 고르는 문제다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;입력 &amp;rarr; 클래스
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;td&gt;&lt;b&gt;출력&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CS 문의 분류&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;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;p data-ke-size=&quot;size16&quot;&gt;분류 문제의 출력은 보통 확률로 나온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;배송 지연: 0.82
환불 문의: 0.12
교환 문의: 0.04
기타: 0.02
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 높은 확률의 클래스를 최종 예측으로 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지도학습의 대표 문제 2 - 회귀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회귀는 연속적인 숫자를 예측하는 문제다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;입력 &amp;rarr; 숫자
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;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;/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;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;p data-ke-size=&quot;size16&quot;&gt;분류와 다른 점은 출력이 클래스가 아니라 숫자라는 것이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;분류: 배송 / 환불 / 교환 중 하나
회귀: 3.7일, 128000원, 0.83 같은 숫자
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지도학습이 잘 맞는 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습은 다음 조건일 때 강하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답&amp;nbsp;라벨이&amp;nbsp;충분히&amp;nbsp;있다&lt;/li&gt;
&lt;li&gt;미래에도 비슷한 형태의 데이터가 들어온다&lt;/li&gt;
&lt;li&gt;예측해야 할 목표가 명확하다&lt;/li&gt;
&lt;li&gt;오답을 손실함수로 계산할 수 있다&lt;/li&gt;
&lt;li&gt;e.g) 과거 문의 문장과 담당자 분류 결과가 많이 있다. &amp;rarr; 새 문의를 자동 분류하는 모델 학습 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지도학습의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습은 라벨이 필요한데,&amp;nbsp;문제는 라벨을 만드는 데 비용이 든다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터가 10만건이라면, 사람이 하나씩 배송/환불/교환으로 라벨링해야 한다.&lt;br /&gt;&amp;rarr;&amp;nbsp;시간과&amp;nbsp;비용이&amp;nbsp;큼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 라벨&amp;nbsp;품질이&amp;nbsp;낮으면&amp;nbsp;모델도&amp;nbsp;잘못&amp;nbsp;배운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 정답 라벨 &amp;rarr; 잘못된 패턴 학습 &amp;rarr;&amp;nbsp;실제&amp;nbsp;예측도&amp;nbsp;흔들림&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비지도학습(Unsupervised Learning)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비지도학습은 &lt;b&gt;정답 라벨 없이 데이터의 구조를 찾는 학습 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력&amp;nbsp;x만&amp;nbsp;있음&lt;/li&gt;
&lt;li&gt;정답 y는 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은&amp;nbsp;&amp;ldquo;이&amp;nbsp;데이터의&amp;nbsp;정답이&amp;nbsp;무엇인지&amp;rdquo;를&amp;nbsp;배우는&amp;nbsp;게&amp;nbsp;아니라,&amp;nbsp;데이터끼리의&amp;nbsp;관계를&amp;nbsp;찾는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤&amp;nbsp;데이터들이&amp;nbsp;서로&amp;nbsp;비슷한가?&lt;/li&gt;
&lt;li&gt;어떤 데이터들이 멀리 떨어져 있는가?&lt;/li&gt;
&lt;li&gt;데이터는 몇 개의 큰 덩어리로 나뉘는가?&lt;/li&gt;
&lt;li&gt;고차원 데이터를 낮은 차원으로 줄이면 어떤 구조가 보이는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비지도학습은 &amp;ldquo;아무 목표도 없는 학습&amp;rdquo;이 아니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비지도학습에는 정답 라벨이 없지만, 목표가 아예 없는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 K-Means와 SVD 차원축소는 다음 목표를 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K-Means
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 군집 안의 점들은 가깝게&lt;/li&gt;
&lt;li&gt;다른 군집의 점들은 상대적으로 멀게&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SVD 차원축소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 데이터의 주요 구조를 최대한 보존하면서 차원을 줄이기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 비지도학습은 &lt;b&gt;정답 y가 없는 상태에서 구조를 찾는 것&lt;/b&gt;이지, 아무 계산이나 하는 것은 아니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비지도학습의 대표 문제 1 - 클러스터링&lt;/h3&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://chaeyami.tistory.com/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클러스터링&lt;/a&gt;: 비슷한 데이터끼리 묶는 작업&lt;/li&gt;
&lt;li&gt;e.g) 수만 개의 CS문의 제목 &amp;rarr; 비슷한 표현끼리 자동으로 묶기&lt;/li&gt;
&lt;li&gt;결과
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;군집 1: 배송 지연 관련 문의&lt;/li&gt;
&lt;li&gt;군집 2: 환불 요청 관련 문의&lt;/li&gt;
&lt;li&gt;군집 3: 제품 파손 관련 문의&lt;/li&gt;
&lt;li&gt;군집 4: 쿠폰/결제 관련 문의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중요한 점: &lt;b&gt;모델은 군집 1이 &quot;배송 지연&quot;이라고 이름 붙여주지 않는다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;이 데이터들이 서로 가깝다&amp;rdquo;까지만 알려준다.&lt;/li&gt;
&lt;li&gt;그 군집이 어떤 의미인지는 사람이 보고 해석해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비지도학습의 대표 문제 2 - 차원축소&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차원축소: 높은 차원의 데이터를 낮은 차원으로 줄이는 작업&lt;/li&gt;
&lt;li&gt;e.g) &lt;a href=&quot;https://chaeyami.tistory.com/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TF-IDF&lt;/a&gt; 벡터: 8000차원 &amp;rarr; &lt;a href=&quot;https://chaeyami.tistory.com/282&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SVD&lt;/a&gt;로&amp;nbsp;100차원으로&amp;nbsp;축소&lt;/li&gt;
&lt;li&gt;왜 줄이는가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계산을 빠르게 하기 위해&lt;/li&gt;
&lt;li&gt;시각화하기 위해&lt;/li&gt;
&lt;li&gt;노이즈를 줄이기 위해&lt;/li&gt;
&lt;li&gt;고차원 거리 문제를 완화하기 위해&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차원축소는 단순히 데이터를&amp;nbsp;버리는&amp;nbsp;게 아니라, 가능한 한 중요한 구조를 보면서 압축하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비지도학습이 잘 맞는 상황&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답 라벨이 없다&lt;/li&gt;
&lt;li&gt;데이터를 먼저 탐색하고 싶다&lt;/li&gt;
&lt;li&gt;숨은 패턴을 찾고 싶다&lt;/li&gt;
&lt;li&gt;비슷한 것끼리 묶고 싶다&lt;/li&gt;
&lt;li&gt;시각화하거나 압축하고 싶다&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;새로운 유형의 문의가 계속 쌓인다 &lt;br /&gt;정답 라벨은 없다 &lt;br /&gt;&amp;rarr; 클러스터링으로 비슷한 문의 묶음부터 찾는다&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비지도학습의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비지도학습 결과는 해석이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 군집이 5개 나왔다면, 그 5개가 실무적으로 의미 있는지는 별도 판단이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 평가도 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습처럼 &amp;ldquo;정답과 비교해서 정확도 92%&amp;rdquo;라고 말하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;a href=&quot;https://chaeyami.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실루엣 스코어&lt;/a&gt; 같은 지표를 쓰지만, 최종적으로는 사람이 데이터 샘플을 확인해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지도학습과 비지도학습 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 지도학습 비지도학습&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;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;/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;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;결과 해석&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;h2 data-ke-size=&quot;size26&quot;&gt;반지도학습(Semi-Supervised Learning)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반지도학습은 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;라벨 있는 데이터는 적고, 라벨 없는 데이터는 많을 때 사용하는 학습 방식&lt;/span&gt;&lt;/b&gt;이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;라벨 있는 데이터 1,000개 &lt;br /&gt;라벨 없는 데이터 100,000개&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습처럼 라벨을 쓰지만, 비지도학습처럼 라벨 없는 데이터도 함께 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &amp;ldquo;반지도&amp;rdquo;라고 부른다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 필요한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 &lt;b&gt;원본 데이터는 많이 쌓여 있지만, 사람이 정답 라벨을 붙인 데이터는 적은 경우&lt;/b&gt;가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 고객 문의 원본이 50만 건, 담당자가 유형 라벨을 붙인 문의가 3천 건이라고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벨 3천 건만으로 지도학습을 하면 데이터가 부족할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 라벨 없는 50만 건을 버리기엔 아깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반지도학습은 이 라벨 없는 데이터를 학습에 활용하려는 방법이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대표 방식 - Pseudo Labeling&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pseudo labeling은 가장 직관적인 반지도학습 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;라벨 있는 데이터로 모델을 먼저 학습힌다.&lt;/li&gt;
&lt;li&gt;학습된 모델로 라벨 없는 데이터를 예측한다.&lt;/li&gt;
&lt;li&gt;모델이 높은 확신을 가진 예측만 가짜 라벨로 사용한다.&lt;/li&gt;
&lt;li&gt;원래 라벨 데이터 + 가짜 라벨 데이터를 합쳐 다시 학습한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작은 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라벨 있는 데이터:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;배송이 늦어요&quot; &amp;rarr; 배송 지연
&quot;환불하고 싶어요&quot; &amp;rarr; 환불 문의
&quot;상품을 바꾸고 싶어요&quot; &amp;rarr; 교환 문의
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터로 모델을 먼저 학습한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 라벨 없는 문장을 모델에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;입력: &quot;택배가 아직 안 왔어요&quot;
모델 예측:
배송 지연 0.94
환불 문의 0.04
교환 문의 0.02
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확신도가 높다. 이 경우 가짜 라벨을 붙일 수 있다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;택배가 아직 안 왔어요&quot; &amp;rarr; 배송 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 이런 경우는 조심해야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;입력: &quot;주문 취소하고 다시 결제하고 싶어요&quot;
모델 예측:
환불 문의 0.45
결제 문의 0.39
기타 0.16
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확신도가 낮다. 이런 데이터를 가짜 라벨로 넣으면 오히려 모델이 잘못 배울 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Confidence Threshold&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pseudo labeling에서는 보통 임계값을 둔다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;예측 확률 0.9 이상만 가짜 라벨로 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임계값이 너무 낮으면 잘못된 가짜 라벨이 많아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임계값이 너무 높으면 사용할 수 있는 데이터가 너무 적어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낮은&amp;nbsp;threshold&amp;nbsp;&amp;rarr;&amp;nbsp;데이터는&amp;nbsp;많지만&amp;nbsp;오류&amp;nbsp;위험&amp;nbsp;큼&lt;/li&gt;
&lt;li&gt;높은 threshold &amp;rarr; 데이터는 적지만 품질은 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반지도학습의 위험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가짜 라벨이 틀렸는데 그걸 정답처럼 학습하면 문제가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잘못된 예측 &amp;rarr; 잘못된 가짜 라벨 &amp;rarr; 다시 학습 &amp;rarr;&amp;nbsp;모델이&amp;nbsp;더&amp;nbsp;강하게&amp;nbsp;잘못&amp;nbsp;배움&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 오류 누적이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 반지도학습은 &amp;ldquo;라벨 없는 데이터를 많이 쓰니까 무조건 좋다&amp;rdquo;가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가짜 라벨 품질 관리가 핵심이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Active Learning과의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반지도학습과 헷갈리는 개념이 active learning이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;td&gt;&lt;b&gt; Active Learning &lt;/b&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;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;p data-ke-size=&quot;size16&quot;&gt;Active learning은 보통&amp;nbsp;&lt;br /&gt;모델이 헷갈리는 데이터 찾기 &amp;rarr; 사람에게 그 데이터만 라벨링 요청 &amp;rarr;&amp;nbsp;적은&amp;nbsp;라벨링으로&amp;nbsp;성능&amp;nbsp;개선&lt;br /&gt;의 흐름이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자기지도학습(Self-Supervised Learning)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기지도학습은 &lt;b&gt;사람이 정답 라벨을 붙이지 않고, 데이터 자체에서 정답처럼 사용할 문제를 만들어 학습하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 데이터가 있다.&lt;br /&gt;&amp;rarr; 일부를 가리거나 변형한다.&lt;br /&gt;&amp;rarr; 모델에게 원래 무엇이었는지 맞히게 한다.&lt;br /&gt;&amp;rarr; 정답은 원본 데이터 안에 이미 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 직접 &quot;이 문장은 환불 문의다&quot;, &quot;이 이미지는 고양이다&quot;, &quot;이 문장 쌍은 같은 의미다&quot; 와 같은 라벨을 붙이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 대신 원본 데이터 안에서 맞혀야 할 목표를 만든다. &lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 &amp;ldquo;자기지도&amp;rdquo;라고 부르는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습은 사람이 정답을 준다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력: &quot;배송이 너무 늦어요&quot;
정답: 배송 지연 문의
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기지도학습은 사람이 정답을 주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 데이터 안에서 정답을 만든다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;원본 문장:
&quot;배송이 지연되어 환불을 요청합니다&quot;

모델에게 보여주는 입력:
&quot;배송이 지연되어 [MASK]을 요청합니다&quot;

모델이 맞혀야 하는 정답:
&quot;환불&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 `환불`이라는 정답은 사람이 새로 붙인 라벨이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 문장 안에 있던 단어를 잠깐 가렸을 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 자기지도학습은 &lt;b&gt;사람이 만든 정답표는 없지만, 데이터 자체를 이용해서 맞혀야 할 문제를 만든다&lt;/b&gt;고 볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가짜 문제를 푸는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기지도학습에서 만드는 문제를 보통 pretext task, 즉 사전 과제라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제 자체가 최종 목적은 아닐 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 `[MASK]` 단어 맞히기가 최종 서비스 기능은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 건 문제를 푸는 과정에서 모델이 데이터의 구조를 배운다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;고객님, 주문하신 상품의 [MASK]이 지연되고 있습니다.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 [MASK]를 맞히려면 후보 단어의 문맥을 이해해야 한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;배송
출고
도착
환불
결제
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문맥상 자연스러운 단어는 배송, 출고, 도착 쪽이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환불, 결제도 CS 도메인 단어지만 이 문장에서는 덜 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 수많은 문장으로 반복하면 모델은 다음을 배운다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단어들이 어떤 문맥에서 같이 쓰이는지&lt;/li&gt;
&lt;li&gt;문장 구조가 어떻게 이어지는지&lt;/li&gt;
&lt;li&gt;표현이 달라도 의미가 비슷한 경우가 무엇인지&lt;/li&gt;
&lt;li&gt;문맥상 자연스러운 다음 단어가 무엇인지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 1 - Masked Language Modeling&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT 계열 모델에서 자주 쓰인 방식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;학습 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문장의 일부 단어를 가린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 가려진 단어를 맞힌다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;원본:
&quot;고객이 환불을 요청했다&quot;

입력:
&quot;고객이 [MASK]을 요청했다&quot;

정답:
&quot;환불&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모델이 배우는 것&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 `요청했다` 앞에 어떤 단어가 자연스러운지 배운다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;환불을 요청했다
교환을 요청했다
취소를 요청했다
상담을 요청했다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 `고객이`라는 주어와 `요청했다`라는 동사 사이에서 어떤 명사가 자주 오는지도 학습한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 패턴이 쌓이면 모델은 단어의 의미와 문맥을 어느 정도 구분하게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사전학습한 모델은 다음 작업에 활용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문장 분류&lt;/li&gt;
&lt;li&gt;질문 답변&lt;/li&gt;
&lt;li&gt;문서 검색&lt;/li&gt;
&lt;li&gt;개체명 인식&lt;/li&gt;
&lt;li&gt;문장 임베딩&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 2 - Next Token Prediction&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT 같은 생성형 언어모델에서 쓰는 방식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;학습 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 토큰을 보고 다음 토큰을 맞힌다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력:
&quot;고객님, 주문하신 상품의&quot;

정답:
&quot;배송&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음에는 정답까지 포함해서 다시 다음 토큰을 맞힌다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력:
&quot;고객님, 주문하신 상품의 배송&quot;

정답:
&quot;이&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;고객님, 주문하신 상품의
&amp;rarr; 배송

고객님, 주문하신 상품의 배송
&amp;rarr; 이

고객님, 주문하신 상품의 배송이
&amp;rarr; 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모델이 배우는 것&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 다음을 배운다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단어&amp;nbsp;순서&lt;/li&gt;
&lt;li&gt;문장 구조&lt;/li&gt;
&lt;li&gt;자주 이어지는 표현&lt;/li&gt;
&lt;li&gt;문맥상 자연스러운 답변&lt;/li&gt;
&lt;li&gt;말투와 문체&lt;/li&gt;
&lt;li&gt;도메인별 표현 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 LLM은 단어를 아무렇게나 이어 붙이는 게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 문맥에서 다음에 올 가능성이 높은 토큰을 계속 예측하면서 문장을 생성한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 3 - Contrastive Learning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색용 임베딩 모델이나 이미지-텍스트 모델에서 자주 쓰이는 방식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;학습 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 쌍은 가깝게, 다른 쌍은 멀게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 같은 의미의 문장 쌍이 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;문장 A:
&quot;배송이 너무 늦어요&quot;

문장 B:
&quot;상품이 아직 도착하지 않았어요&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문장은 표현은 다르지만 의미는 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 두 문장의 벡터가 가까워지도록 학습한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 의미가 다른 문장은 멀어지게 한다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;문장 C:
&quot;결제 수단을 변경하고 싶어요&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 목표는 이렇게 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A와 B는 가깝게
A와 C는 멀게
B와 C는 멀게
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모델이 배우는 것&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 단어가 완전히 같지 않아도 의미가 비슷한 문장을 가까운 벡터로 배치하는 법을 배운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 검색에서 이런 일이 가능해진다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;검색어:
&quot;택배가 안 와요&quot;

찾아야 할 문서:
&quot;배송 지연으로 인한 보상 기준&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키워드는 다르지만 의미가 연결되어 있으므로 임베딩 검색에서 가까운 결과로 나올 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자기지도학습과 비지도학습의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 사람이 라벨을 붙이지 않는다는 점에서는 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 학습 목표가 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;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;/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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비지도학습: 정답&amp;nbsp;없이&amp;nbsp;데이터&amp;nbsp;구조를&amp;nbsp;찾는다.&lt;/li&gt;
&lt;li&gt;자기지도학습: 사람&amp;nbsp;라벨은&amp;nbsp;없지만,&amp;nbsp;데이터&amp;nbsp;자체로&amp;nbsp;만든&amp;nbsp;정답을&amp;nbsp;맞힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 중요한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 AI에서 자기지도학습이 중요한 이유는 단순하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라벨 있는 데이터는 비싸다.&lt;/li&gt;
&lt;li&gt;라벨 없는 데이터는 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 모든 문장, 이미지, 음성에 정답을 붙이는 건 비용이 크다.&lt;br /&gt;하지만 원본 데이터는 훨씬 많다. 웹 문서, 사내 문서, 로그, 이미지, 음성, 코드 등.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기지도학습은 이 많은 원본 데이터를 이용해서 모델을 먼저 학습시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량의&amp;nbsp;라벨&amp;nbsp;없는&amp;nbsp;데이터 &lt;br /&gt;&amp;rarr;&amp;nbsp;자기지도학습으로&amp;nbsp;사전학습 &lt;br /&gt;&amp;rarr;&amp;nbsp;언어/이미지/패턴에&amp;nbsp;대한&amp;nbsp;기본&amp;nbsp;표현&amp;nbsp;학습 &lt;br /&gt;&amp;rarr;&amp;nbsp;적은&amp;nbsp;라벨&amp;nbsp;데이터로&amp;nbsp;특정&amp;nbsp;작업에&amp;nbsp;맞게&amp;nbsp;조정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름이 사전학습(pretraining)과 파인튜닝(fine-tuning)의 기본 구조다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnQleE/dJMcadoZEmE/spu62X5JTnJAiKgDXaP4uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnQleE/dJMcadoZEmE/spu62X5JTnJAiKgDXaP4uk/img.png&quot; data-alt=&quot;자기지도학습 파이프라인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnQleE/dJMcadoZEmE/spu62X5JTnJAiKgDXaP4uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnQleE%2FdJMcadoZEmE%2Fspu62X5JTnJAiKgDXaP4uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자기지도학습 파이프라인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;강화학습(Reinforcement Learning)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화학습은 &lt;b&gt;행동의 결과로 보상을 받고, 장기적으로 더 큰 보상을 얻도록 학습하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습처럼 매 순간 정답 라벨이 주어지지 않는다.&lt;br /&gt;대신 어떤 행동을 했을 때 결과가 좋았는지 나빴는지를 보상으로 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를&amp;nbsp;본다 &lt;br /&gt;&amp;rarr;&amp;nbsp;행동을&amp;nbsp;선택한다 &lt;br /&gt;&amp;rarr;&amp;nbsp;환경이&amp;nbsp;반응한다 &lt;br /&gt;&amp;rarr;&amp;nbsp;보상을&amp;nbsp;받는다 &lt;br /&gt;&amp;rarr;&amp;nbsp;다음에는&amp;nbsp;더&amp;nbsp;좋은&amp;nbsp;행동을&amp;nbsp;선택하도록&amp;nbsp;조정한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 구성요소&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;td&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;행동을 선택하는 주체&lt;/td&gt;
&lt;td&gt;게임 AI, 로봇, 추천 시스템&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;agent가 상호작용하는 세계&lt;/td&gt;
&lt;td&gt;게임 맵, 실제 공간, 사용자 반응&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;현재 상황&lt;/td&gt;
&lt;td&gt;현재 위치, 점수, 사용자 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;선택 가능한 행동&lt;/td&gt;
&lt;td&gt;이동, 클릭 추천, 답변 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reward&lt;/td&gt;
&lt;td&gt;행동 결과로 받는 점수&lt;/td&gt;
&lt;td&gt;승리 +1, 실패 -1, 클릭 +1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy&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;h3 data-ke-size=&quot;size23&quot;&gt;지도학습과의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도학습에서는 정답이 바로 주어진다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력: 이미지
정답: 고양이
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화학습에서는 정답 행동이 바로 주어지지 않는다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;현재 상태: 미로의 한 칸
행동 후보: 위 / 아래 / 왼쪽 / 오른쪽
정답: 알려주지 않음
보상: 나중에 목표 지점에 도착하면 +1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 직접 행동해보고, 결과로 받은 보상을 통해 어떤 행동이 좋은지 배운다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;즉시 보상과 장기 보상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화학습이 어려운 이유는 좋은 행동의 결과가 바로 나타나지 않을 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) 체스에서 지금 말 하나를 희생함 &lt;br /&gt;&amp;rarr;&amp;nbsp;당장은&amp;nbsp;손해처럼&amp;nbsp;보임 &lt;br /&gt;&amp;rarr;&amp;nbsp;5수&amp;nbsp;뒤에&amp;nbsp;체크메이트&amp;nbsp;가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 보상만 보면 나쁜 행동처럼 보이지만, 장기적으로는&amp;nbsp;좋은&amp;nbsp;행동일&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;강화학습은&amp;nbsp;현재의&amp;nbsp;보상만이&amp;nbsp;아니라&amp;nbsp;미래의&amp;nbsp;보상까지&amp;nbsp;고려해야&amp;nbsp;한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;탐험과 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화학습의 핵심 딜레마는 exploration vs exploitation이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐험(exploration): 아직&amp;nbsp;잘&amp;nbsp;모르는&amp;nbsp;행동을&amp;nbsp;시도해봄&lt;/li&gt;
&lt;li&gt;활용(exploitation): 지금까지&amp;nbsp;알게&amp;nbsp;된&amp;nbsp;좋은&amp;nbsp;행동을&amp;nbsp;선택함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐험을&amp;nbsp;너무&amp;nbsp;안&amp;nbsp;하면&amp;nbsp;더&amp;nbsp;좋은&amp;nbsp;전략을&amp;nbsp;발견하지&amp;nbsp;못한다.&lt;br /&gt;활용을 너무 안 하면 이미 좋은 전략이 있는데도 계속 실험만 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐험&amp;nbsp;부족&amp;nbsp;&amp;rarr;&amp;nbsp;지역&amp;nbsp;최적해에&amp;nbsp;갇힘 &lt;br /&gt;탐험&amp;nbsp;과다&amp;nbsp;&amp;rarr;&amp;nbsp;성능이&amp;nbsp;안정되지&amp;nbsp;않음&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Policy와 Value&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화학습에서는 두 가지 개념이 중요하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Policy&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Policy는 상태에서 어떤 행동을 선택할지 정하는 전략이다.&lt;br /&gt;&amp;rarr; &lt;b&gt;현재 상태 s에서 행동 a를 선택하는 규칙&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;미로에서 벽이 오른쪽에 있으면 위로 이동&lt;/b&gt; &lt;br /&gt;&lt;b&gt;상대가 공격적으로 나오면 방어 선택&lt;/b&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Value&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Value는 어떤 상태나 행동이 장기적으로 얼마나 좋은지 나타내는 값이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;이 상태에 있으면 앞으로 보상을 많이 받을 가능성이 큰가? &lt;/b&gt;&lt;br /&gt;&lt;b&gt;이 행동을 하면 장기적으로 유리한가?&lt;/b&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LLM에서 말하는 RLHF&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에서 자주 나오는 RLHF는 Reinforcement Learning from Human Feedback이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모델이 여러 답변을 생성한다.&lt;/li&gt;
&lt;li&gt;사람이 어떤 답변이 더 좋은지 비교한다.&lt;/li&gt;
&lt;li&gt;그 선호 데이터를 바탕으로 reward model을 만든다.&lt;/li&gt;
&lt;li&gt;모델이 더 선호되는 답변을 하도록 조정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서도 &amp;ldquo;정답 문장 하나&amp;rdquo;를 직접 주는 지도학습과는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람의 선호를 보상 신호로 바꿔서 모델을 조정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 전통적인 게임 강화학습과 완전히 같은 상황은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM의 RLHF는 언어모델을 사람 선호에 맞추기 위한 정렬(alignment) 기법으로 이해하는 편이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8MH5T/dJMcag0k1bv/ARvMfNk2wLW9QjN6KRutU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8MH5T/dJMcag0k1bv/ARvMfNk2wLW9QjN6KRutU1/img.png&quot; data-alt=&quot;강화학습 루프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8MH5T/dJMcag0k1bv/ARvMfNk2wLW9QjN6KRutU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8MH5T%2FdJMcag0k1bv%2FARvMfNk2wLW9QjN6KRutU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;강화학습 루프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한눈에 비교하기&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;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;/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;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;td&gt;pseudo labeling&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;td&gt;next token, MLM&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;td&gt;게임 AI, 로봇, RLHF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지도학습 우선 검토&lt;/li&gt;
&lt;li&gt;e.g) 문의 유형 라벨이 10만 건 있음 &amp;rarr;&amp;nbsp;분류&amp;nbsp;모델&amp;nbsp;학습&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;정답 라벨이 없다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비지도학습으로 데이터 구조 탐색&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;e.g)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 라벨 없는 문의 제목 수십만 건 &amp;rarr; 클러스터링으로 주제 묶음 찾기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라벨은 조금 있고 무라벨 데이터가 많다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반지도학습 검토&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;e.g)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 라벨 데이터 1천 건 / 무라벨 데이터 20만 건 &amp;rarr; pseudo labeling 시도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대규모 원본 데이터로 기본 표현을 배우고 싶다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자기지도학습 검토&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;e.g)&lt;span&gt; 대량 텍스트 &amp;rarr; 다음 토큰 예측으로 사전학습 &amp;rarr; 특정 도메인 작업에 파인튜닝&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;행동의 결과가 보상으로 주어진다&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;강화학습 검토&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;e.g) 로봇 제어 / 게임 플레이 / 추천 정책 최적화&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;정리&lt;/span&gt;&lt;/span&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;/li&gt;
&lt;li&gt;비지도학습은 정답 없이 구조를 찾는다.&lt;/li&gt;
&lt;li&gt;반지도학습은 적은 라벨과 많은 무라벨 데이터를 함께 쓴다.&lt;/li&gt;
&lt;li&gt;자기지도학습은 데이터 자체로 문제를 만들어 배운다&lt;/li&gt;
&lt;li&gt;강화학습은 행동의 결과로 받은 보상을 키우도록 배운다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>MachineLearniing</category>
      <category>ML</category>
      <category>강화학습</category>
      <category>반지도학습</category>
      <category>비지도학습</category>
      <category>자기지도학습</category>
      <category>지도학습</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/289</guid>
      <comments>https://chaeyami.tistory.com/289#entry289comment</comments>
      <pubDate>Fri, 3 Jul 2026 11:25:14 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 학습이란 무엇인가 - 손실함수와 경사하강법</title>
      <link>https://chaeyami.tistory.com/288</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 글에서 다루는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝에서 &amp;ldquo;학습한다&amp;rdquo;는 말은 막연하게 똑똑해진다는 뜻이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는 다음 과정을 반복한다는 뜻이다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;입력 데이터를 넣는다
&amp;rarr; 모델이 예측값을 만든다
&amp;rarr; 정답과 비교해서 얼마나 틀렸는지 숫자로 계산한다
&amp;rarr; 그 숫자를 줄이는 방향으로 모델 내부 값을 조금 바꾼다
&amp;rarr; 이 과정을 여러 번 반복한다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 목표는 다음 개념을 하나의 흐름으로 연결하는 것이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;/td&gt;
&lt;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/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;h2 data-ke-size=&quot;size26&quot;&gt;사람의 학습과 모델의 학습은 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 &amp;ldquo;학습한다&amp;rdquo;고 하면 보통 의미를 이해하고, 기억하고, 응용하는 과정을 떠올린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝의 학습은 &lt;b&gt;손실함수&amp;nbsp;값을&amp;nbsp;줄이도록&amp;nbsp;파라미터를&amp;nbsp;조정하는&amp;nbsp;과정&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 스스로 &amp;ldquo;아, 배송 지연 문의는 이런 의미구나&amp;rdquo;라고 깨닫지 못한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 파라미터로 예측한다&lt;/li&gt;
&lt;li&gt;예측값과 정답을 비교한다&lt;/li&gt;
&lt;li&gt;틀린 정도를 숫자로 만든다&lt;/li&gt;
&lt;li&gt;그 숫자가 작아지도록 파라미터를 바꾼다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 반복하며 학습한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아주 작은 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CS 문의 문장을 보고 문의 유형을 분류하는 모델이 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력: &quot;배송이 너무 늦어요&quot;
정답: 배송 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 모델은 아무것도 모른다. 그래서 이렇게 예측할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;예측: 환불 문의
정답: 배송 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀렸다. 이때 모델은 &amp;ldquo;내가 왜 틀렸지?&amp;rdquo;라고 사람처럼 생각하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 다음 질문을 숫자로 바꾼다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정답 클래스의 확률을 너무 낮게 줬는가?
틀린 클래스의 확률을 너무 높게 줬는가?
그 차이를 줄이려면 어떤 파라미터를 어느 방향으로 바꿔야 하는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 숫자 조정이 반복되면, 모델은 점점 이런 문장에 높은 확률을 준다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;배송이 너무 늦어요&quot; &amp;rarr; 배송 지연
&quot;택배가 아직 안 왔어요&quot; &amp;rarr; 배송 지연
&quot;상품이 도착하지 않았습니다&quot; &amp;rarr; 배송 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모델은 함수다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 모델은 입력을 받아 출력을 내는 함수로 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;입력 x &amp;rarr; 모델 f &amp;rarr; 예측값 ŷ
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식으로 쓰면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\hat{y} = f(x; \theta)&lt;br /&gt;$$&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;$x$&lt;/td&gt;
&lt;td&gt;입력 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\hat{y}$&lt;/td&gt;
&lt;td&gt;모델의 예측값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$f$&lt;/td&gt;
&lt;td&gt;모델 함수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\theta$&lt;/td&gt;
&lt;td&gt;모델 파라미터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 건 $\theta$다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 구조가 같아도 파라미터 값이 다르면 예측이 달라진다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;학습 전 파라미터 &amp;rarr; 엉뚱한 예측
학습 후 파라미터 &amp;rarr; 더 나은 예측
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파라미터란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터는 모델 내부의 숫자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 숫자들이 입력을 어떻게 해석할지 결정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선형 모델 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 모델을 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\hat{y} = wx + b&lt;br /&gt;$$&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;$x$&lt;/td&gt;
&lt;td&gt;입력값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\hat{y}$&lt;/td&gt;
&lt;td&gt;예측값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$w$&lt;/td&gt;
&lt;td&gt;가중치(weight)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$b$&lt;/td&gt;
&lt;td&gt;편향(bias)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 학습으로 바뀌는 값은 $w$와 $b$다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;모델 구조: ŷ = wx + b
학습 전: w = 0.1, b = 0.0
학습 후: w = 2.3, b = 1.7
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 공식 자체가 바뀌는 게 아니다. 공식 안의 숫자가 바뀐다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;신경망도 결국 파라미터의 묶음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신경망은 훨씬 복잡하지만 원리는 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;입력
&amp;rarr; 여러 층의 계산
&amp;rarr; 예측값
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 층에는 가중치와 편향이 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1층 가중치
2층 가중치
3층 가중치
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습은 이 많은 숫자를 조금씩 바꾸는 과정이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;손실함수란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;손실함수&amp;nbsp;=&amp;nbsp;예측이&amp;nbsp;얼마나&amp;nbsp;틀렸는지&amp;nbsp;숫자로&amp;nbsp;계산하는&amp;nbsp;함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 예측을 했다면, 그 예측이 좋은지 나쁜지 판단해야 한다. 사람은 다음처럼 눈으로 판단할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;예측: 환불 문의
정답: 배송 지연
&amp;rarr; 틀렸네
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 모델을 학습시키려면 &amp;ldquo;틀렸다&amp;rdquo;를 숫자로 바꿔야 하는데,&amp;nbsp;그 역할을 하는 게 &lt;b&gt;손실함수(loss function)&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 즉, 손실이 작을수록 예측이 정답에 가깝다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;손실 0.02 &amp;rarr; 잘 맞힘
손실 1.84 &amp;rarr; 많이 틀림
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회귀 문제의 손실함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회귀는&amp;nbsp;&lt;b&gt;숫자를&amp;nbsp;예측하는&amp;nbsp;문제&lt;/b&gt;다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;e.g.&lt;br /&gt;집 면적 &amp;rarr; 집값 예측 &lt;br /&gt;공부 시간 &amp;rarr; 시험 점수 예측 &lt;br /&gt;광고비 &amp;rarr; 매출 예측&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;평균제곱오차(MSE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 회귀 손실함수는 평균제곱오차다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2&lt;br /&gt;$$&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;$y_i$&lt;/td&gt;
&lt;td&gt;실제 정답&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\hat{y}_i$&lt;/td&gt;
&lt;td&gt;모델 예측값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$y_i - \hat{y}_i$&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;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;손으로 계산해보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 시험 점수를 예측한다고 하자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;td&gt;&lt;b&gt;예측 점수&lt;/b&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;A&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;-5&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSE는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;MSE = \frac{100 + 25 + 25}{3} = 50&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 이 50이라는 숫자가 줄어들도록 파라미터를 바꾼다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분류 문제의 손실함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류는 여러 &lt;b&gt;클래스 중 하나를 고르는 문제&lt;/b&gt;다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;e.g.&lt;br /&gt;문의 문장 &amp;rarr; 배송 / 환불 / 교환 / 기타 &lt;br /&gt;리뷰 문장 &amp;rarr; 긍정 / 부정 &lt;br /&gt;이미지 &amp;rarr; 고양이 / 강아지 / 자동차&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 모델은 보통 각 클래스의 확률을 출력한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;입력: &quot;배송이 너무 늦어요&quot;

모델 출력:
배송 지연: 0.70
환불 문의: 0.20
교환 문의: 0.07
기타: 0.03
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답이 배송 지연이라면 좋은 예측이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 이렇게 나오면 나쁜 예측이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;배송 지연: 0.05
환불 문의: 0.80
교환 문의: 0.10
기타: 0.05
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교차엔트로피(Cross Entropy)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 문제에서 자주 쓰는 손실함수는 교차엔트로피다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 클래스에 모델이 부여한 확률이 높을수록 손실이 작다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 클래스 확률이 낮을수록 손실이 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 샘플 기준으로 보면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Loss = -\log(p_{correct})&lt;br /&gt;$$&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;0.90&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;매우 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 로그를 쓰는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 쓰면 확률이 아주 낮을 때 강한 벌점을 준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;e.g. &lt;br /&gt;정답 확률 0.9 &amp;rarr; 조금 틀림 &lt;br /&gt;정답 확률 0.1 &amp;rarr; 많이 틀림 &lt;br /&gt;정답 확률 0.001 &amp;rarr; 심각하게 틀림&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 정답을 거의 불가능하다고 판단하면 큰 손실을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 학습 과정에서 정답 클래스의 확률을 높이는 방향으로 파라미터가 조정된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;손실을 줄인다는 것은 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 한 번 예측하고 끝나면 학습이 아니다. &lt;b&gt;학습은 반복이다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; 처음 예측 &amp;rarr; 손실 큼 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;파라미터 수정 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;다시 예측 &amp;rarr; 손실 조금 작아짐 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;파라미터 수정 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;다시 예측 &amp;rarr; 손실 더 작아짐 ... &lt;/b&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;손실이 줄어드는 예&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;td&gt;&lt;b&gt;손실 느낌&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1회&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;td&gt;많이 틀림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10회&lt;/td&gt;
&lt;td&gt;0.38&lt;/td&gt;
&lt;td&gt;조금 나아짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100회&lt;/td&gt;
&lt;td&gt;0.71&lt;/td&gt;
&lt;td&gt;꽤 잘 맞힘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000회&lt;/td&gt;
&lt;td&gt;0.88&lt;/td&gt;
&lt;td&gt;안정적으로 맞힘&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 정답을 직접 &amp;ldquo;이해&amp;rdquo;해서 바뀌는 게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실이 줄어드는 방향으로 숫자가 계속 조정되기 때문에 예측이 좋아진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경사란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;경사: 파라미터를 조금 바꿨을 때 손실이 얼마나 변하는지 알려주는 값&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실을 줄이려면 파라미터를 어느 방향으로 바꿔야 하는데,&amp;nbsp;&lt;br /&gt;문제는 파라미터가 많다는 것이다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이 가중치를 키워야 하나?
줄여야 하나?
얼마나 바꿔야 하나?
다른 가중치는 어떻게 해야 하나?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 방향 정보를 알려주는 것이 경사(gradient)다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;산을 내려가는 비유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실함수를 산의 높이라고 생각하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 곳 = 손실 큼 = 예측이 많이 틀림&lt;/li&gt;
&lt;li&gt;낮은 곳 = 손실 작음 = 예측이 잘 맞음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 가장 낮은 곳으로 가고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경사는 현재 위치에서 어느 방향이 오르막인지 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실을 줄이려면 오르막의 반대 방향으로 움직이면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경사&amp;nbsp;방향&amp;nbsp;=&amp;nbsp;손실이&amp;nbsp;커지는&amp;nbsp;방향&lt;/li&gt;
&lt;li&gt;경사의 반대 방향 = 손실이 작아지는 방향&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경사하강법이란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경사하강법(Gradient Descent)은 경사의 반대 방향으로 파라미터를 조금씩 움직이는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\theta_{new} = \theta_{old} - \eta \nabla L(\theta)&lt;br /&gt;$$&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;$\theta$&lt;/td&gt;
&lt;td&gt;파라미터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$L(\theta)$&lt;/td&gt;
&lt;td&gt;손실함수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\nabla L(\theta)$&lt;/td&gt;
&lt;td&gt;손실의 경사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\eta$&lt;/td&gt;
&lt;td&gt;학습률(learning rate)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 빼기 기호다. 경사 방향으로 가면 손실이 커지기 때문에, 반대 방향으로 가야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pycRU/dJMcaiX8MPw/WEfkkmEV0i47SOUWexMrH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pycRU/dJMcaiX8MPw/WEfkkmEV0i47SOUWexMrH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pycRU/dJMcaiX8MPw/WEfkkmEV0i47SOUWexMrH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpycRU%2FdJMcaiX8MPw%2FWEfkkmEV0i47SOUWexMrH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습률이란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습률은 한 번에 얼마나 크게 움직일지 정하는 값이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학습률&amp;nbsp;작음&amp;nbsp;&amp;rarr;&amp;nbsp;조금씩&amp;nbsp;조심스럽게&amp;nbsp;이동
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 안정적&lt;/li&gt;
&lt;li&gt;단점: 너무 느림&lt;/li&gt;
&lt;li&gt;-&amp;gt; 손실은 줄어들지만 너무 오래 걸린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;학습률 큼 &amp;rarr; 한 번에 크게 이동
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 빠르게 이동할 수 있음&lt;/li&gt;
&lt;li&gt;단점: 최저점을 지나쳐 튈 수 있음.&lt;/li&gt;
&lt;li&gt;-&amp;gt; 손실이 줄어들지 않고 오히려 흔들릴 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 적절한 학습률일 때 좋은 학습률, 즉 손실을 안정적으로 줄이게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배치, 스텝, 에폭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 관련 글에서 자주 나오는 단어가 있다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;batch
step
epoch
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 보면 비슷해 보이지만 서로 다르다.&lt;/p&gt;
&lt;table id=&quot;391c2707-14d8-804c-8b6a-cf5ac442016a&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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 id=&quot;391c2707-14d8-80fd-b7c8-f81d5f01d2ac&quot;&gt;
&lt;td id=&quot;_Nur&quot;&gt;batch&lt;/td&gt;
&lt;td id=&quot;&amp;lt;{FE&quot;&gt;한 번에 처리하는 데이터 묶음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-8012-a1c7-c587ee55716c&quot;&gt;
&lt;td id=&quot;_Nur&quot;&gt;step&lt;/td&gt;
&lt;td id=&quot;&amp;lt;{FE&quot;&gt;파라미터를 한 번 업데이트하는 단위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-8047-9206-f40eb4b0cb94&quot;&gt;
&lt;td id=&quot;_Nur&quot;&gt;epoch&lt;/td&gt;
&lt;td id=&quot;&amp;lt;{FE&quot;&gt;전체 학습 데이터를 한 번 다 보는 단위&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배치(batch)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치는 한 번에 모델에 넣는 데이터 묶음이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 10만 건 있다고 해서 한 번에 전부 넣지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g)&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;전체 학습 데이터: 10,000개
batch size: 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 100개씩 잘라서 학습한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스텝(step)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스텝은 파라미터를 한 번 업데이트하는 단위다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치 하나로 예측 &amp;rarr; 손실 계산 &amp;rarr; 경사 계산 &amp;rarr;&amp;nbsp;파라미터&amp;nbsp;업데이트 &lt;br /&gt;=&amp;nbsp;1&amp;nbsp;step&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서는 전체 데이터 10,000개를 batch size 100으로 나누므로:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1 epoch = 100 steps
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에폭(epoch)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에폭은 전체 학습 데이터를 한 번 다 본 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터 10,000개를 모두 한 번 사용하면 1 epoch,이를 10번 반복하면 10 epochs가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;887&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmuOeu/dJMcaiqlPqa/LZoMfQizFzA4J1JOwDEkXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmuOeu/dJMcaiqlPqa/LZoMfQizFzA4J1JOwDEkXk/img.png&quot; data-alt=&quot;batch, step, epoch 관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmuOeu/dJMcaiqlPqa/LZoMfQizFzA4J1JOwDEkXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmuOeu%2FdJMcaiqlPqa%2FLZoMfQizFzA4J1JOwDEkXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1774&quot; height=&quot;887&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;887&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;batch, step, epoch 관계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;옵티마이저란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경사하강법은 기본 아이디어지만,&amp;nbsp;실제 학습에서는 단순히 경사의 반대 방향으로만 움직이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저는 다음을 조절한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어느 방향으로 움직일지&lt;/li&gt;
&lt;li&gt;얼마나 크게 움직일지&lt;/li&gt;
&lt;li&gt;과거의 경사를 얼마나 참고할지&lt;/li&gt;
&lt;li&gt;파라미터마다 다른 속도로 움직일지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대표 옵티마이저&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;SGD&lt;/td&gt;
&lt;td&gt;가장 기본적인 경사하강법 계열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Momentum&lt;/td&gt;
&lt;td&gt;이전 이동 방향을 일부 기억해서 관성처럼 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSProp&lt;/td&gt;
&lt;td&gt;파라미터별 경사 크기에 따라 학습률 조정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adam&lt;/td&gt;
&lt;td&gt;Momentum + RMSProp 계열 아이디어를 결합한 방식&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 Adam 계열이 자주 쓰인다. 물론&amp;nbsp;&amp;ldquo;Adam을 쓰면 무조건 좋다&amp;rdquo;는 뜻은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델, 데이터, 학습률, 배치 크기에 따라 결과가 달라진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;역전파란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신경망은 &lt;b&gt;입력층 &amp;rarr; 은닉층1 &amp;rarr; 은닉층2 &amp;rarr; 출력층&lt;/b&gt; 으로 이루어져 있는데,&lt;br /&gt;예측은 &lt;b&gt;입력 &amp;rarr; 계산 &amp;rarr; 계산 &amp;rarr; 예측값&lt;/b&gt;으로 앞에서 뒤로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸&amp;nbsp;&lt;b&gt;순전파&lt;/b&gt;(forward&amp;nbsp;propagation)라고&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실을 계산한 뒤에는 반대로,&lt;br /&gt;&lt;b&gt;손실 &amp;rarr; 출력층 &amp;rarr; 은닉층2 &amp;rarr; 은닉층1 &amp;rarr; 입력 쪽&lt;/b&gt;으로 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸&amp;nbsp;&lt;b&gt;역전파&lt;/b&gt;(backpropagation)라고&amp;nbsp;한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역전파가 하는 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역전파는 각 파라미터가 손실에 얼마나 영향을 줬는지 계산한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 가중치를 조금 키우면 손실이 커지는가? &lt;br /&gt;줄이면 손실이 작아지는가? &lt;br /&gt;얼마나 민감하게 변하는가?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp;역전파는&amp;nbsp;&lt;b&gt;경사를&amp;nbsp;계산하는&amp;nbsp;방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;순전파: 예측값을 만든다 &lt;br /&gt;손실 계산: 얼마나 틀렸는지 계산한다 &lt;br /&gt;역전파: 각 파라미터가 손실에 미친 영향을 계산한다 &lt;br /&gt;옵티마이저: 그 정보를 바탕으로 파라미터를 갱신한다&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 루프 의사코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 학습 코드는 복잡해 보이지만 핵심 흐름은 단순하다.&lt;/p&gt;
&lt;pre id=&quot;code_1782974930793&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for epoch in range(num_epochs):
    for batch_x, batch_y in train_loader:
        # 1. 예측
        y_pred = model(batch_x)

        # 2. 손실 계산
        loss = loss_function(y_pred, batch_y)

        # 3. 이전 경사 초기화
        optimizer.zero_grad()

        # 4. 역전파로 경사 계산
        loss.backward()

        # 5. 파라미터 갱신
        optimizer.step()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 다음 문장을 그대로 옮긴 것이다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;예측한다
&amp;rarr; 틀린 정도를 계산한다
&amp;rarr; 틀린 원인을 파라미터별로 계산한다
&amp;rarr; 손실이 줄어드는 방향으로 파라미터를 바꾼다
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyknpN/dJMcadikKKH/1kFPOcD2Gl6AYgUVTNnYi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyknpN/dJMcadikKKH/1kFPOcD2Gl6AYgUVTNnYi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyknpN/dJMcadikKKH/1kFPOcD2Gl6AYgUVTNnYi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyknpN%2FdJMcadikKKH%2F1kFPOcD2Gl6AYgUVTNnYi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&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;학습(training)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답이 있는 데이터 사용&lt;/li&gt;
&lt;li&gt;입력 + 정답 &amp;rarr; 예측 &amp;rarr; 손실 계산 &amp;rarr;&amp;nbsp;파라미터&amp;nbsp;업데이트&lt;/li&gt;
&lt;li&gt;학습 중 파라미터 바뀜&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;추론(inference)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 학습된 모델을 사용하는 단계&lt;/li&gt;
&lt;li&gt;새 입력 &amp;rarr; 예측 &amp;rarr; 결과 반환&lt;/li&gt;
&lt;li&gt;정답이 없고, 손실도 계산하지 않음&lt;/li&gt;
&lt;li&gt;파라미터도 바뀌지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&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;b&gt;정답 데이터&lt;/b&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;손실 계산&lt;/b&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;역전파&lt;/b&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;파라미터 업데이트&lt;/b&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;목적&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;손실이 낮으면 항상 좋은 모델인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훈련 데이터의 손실이 낮다는 건, 훈련 데이터에 대해서 잘 맞는다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 실제로 중요한 데이터는 훈련 데이터가 아니라 처음 보는 데이터다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;훈련 손실 낮음
검증 손실 높음
&amp;rarr; 과적합 가능성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 훈련 데이터를 거의 외워버리면, 훈련 손실은 낮아도 새 데이터에는 약할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 학습할 때는 손실만 보지 않고 검증 데이터 성능도 함께 본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작은 전체 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문장 분류 모델을 학습한다고 하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;입력: &quot;배송이 지연됐어요&quot;
정답: 배송 지연

입력: &quot;환불하고 싶어요&quot;
정답: 환불 문의

입력: &quot;상품을 교환하고 싶습니다&quot;
정답: 교환 문의
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모델 출력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 엉뚱하다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&quot;배송이 지연됐어요&quot;
배송 지연: 0.20
환불 문의: 0.60
교환 문의: 0.20
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 클래스인 배송 지연 확률이 낮다. 손실이 크다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 후&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&quot;배송이 지연됐어요&quot;
배송 지연: 0.86
환불 문의: 0.10
교환 문의: 0.04
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 클래스 확률이 높아졌다. 손실이 작아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 학습했다는 건 이런 변화가 일어났다는 뜻이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;흔한 오해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;오해 1. 손실이 0이면 완벽한 모델이다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훈련 데이터에서 손실이 0에 가까워도 실제 데이터에서 잘한다는 보장은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과적합일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;오해 2. 정확도만 높으면 된다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 문제에서는 정확도만 보면 위험할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불균형 데이터에서는 대부분 클래스를 찍어도 정확도가 높게 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;오해 3. 학습률은 크면 클수록 빠르다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 큰 학습률은 최저점 근처를 지나쳐서 손실을 불안정하게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;오해 4. 에폭을 늘리면 무조건 좋아진다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 좋아질 수 있지만, 너무 오래 학습하면 과적합될 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝의&amp;nbsp;학습은&amp;nbsp;예측이&amp;nbsp;틀린&amp;nbsp;정도를&amp;nbsp;손실함수로&amp;nbsp;계산하고, &lt;br /&gt;그&amp;nbsp;손실이&amp;nbsp;줄어드는&amp;nbsp;방향으로&amp;nbsp;파라미터를&amp;nbsp;반복적으로&amp;nbsp;조정하는&amp;nbsp;과정이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력: 데이터를 모델에 넣음&lt;/li&gt;
&lt;li&gt;예측: 현재 파라미터로 답을 냄&lt;/li&gt;
&lt;li&gt;손실 계산: 정답과 비교해 틀린 정도를 숫자로 만듦&lt;/li&gt;
&lt;li&gt;역전파 각 파라미터가 손실에 미친 영향을 계산&lt;/li&gt;
&lt;li&gt;갱신: 손실이 줄어드는 방향으로 파라미터 수정&lt;/li&gt;
&lt;li&gt;반복: 여러 배치와 에폭 동안 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>경사하강법</category>
      <category>손실함수</category>
      <category>학습</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/288</guid>
      <comments>https://chaeyami.tistory.com/288#entry288comment</comments>
      <pubDate>Thu, 2 Jul 2026 14:10:17 +0900</pubDate>
    </item>
    <item>
      <title>[AI설계] GraphRAG - 지식그래프로 확장한 RAG</title>
      <link>https://chaeyami.tistory.com/287</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;RAG의 한계에서 출발&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/286&quot;&gt;RAG&lt;/a&gt;는 질문과 벡터가 &quot;직접 비슷한&quot; 문서만 찾는다. &lt;br /&gt;그런데 실제 질문은 종종 &lt;b&gt;여러 단계를 거쳐야 답이 나오는 경우&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) &quot;해외배송 상품을 환불하면 위약금은 얼마인가?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 &quot;해외배송 상품의 환불 정책이 무엇인지&quot;를 찾고&lt;/li&gt;
&lt;li&gt;그다음 &quot;그 환불 정책이 참조하는 위약금 산정 조항&quot;을 찾아야 한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 조각이 임베딩 공간에서 서로 가깝지 않을 수 있다. &quot;위약금 산정 조항&quot;이라는 텍스트는 &quot;해외배송 환불 정책&quot;이라는 텍스트와 단어도 다르고 문맥도 다를 수 있다. &lt;br /&gt;벡터 검색만으로는 이런 &quot;간접적으로 연결된&quot; 정보를 놓치기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphRAG는 문서 조각들 사이의 &lt;b&gt;명시적인 관계(어떤 조항이 어떤 조항을 참조하는지, 어떤 정책이 어떤 정책을 인용하는지)&lt;/b&gt;를 그래프로 미리 저장해두고, 검색할 때 이 그래프를 따라가며 관련 정보를 추가로 찾아낸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1OlaB/dJMb9970dzH/FWTiBrPUPO7UgAoaWHF0rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1OlaB/dJMb9970dzH/FWTiBrPUPO7UgAoaWHF0rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1OlaB/dJMb9970dzH/FWTiBrPUPO7UgAoaWHF0rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1OlaB%2FdJMb9970dzH%2FFWTiBrPUPO7UgAoaWHF0rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지식그래프란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드(node)와 엣지(edge)로 이루어진 구조. 여기서는 노드가 문서 조각(정책 조항 등)이고, 엣지가 &quot;이 조항이 저 조항을 참조한다&quot; 같은 관계다. 이 구조를 저장하고 탐색하는 데 그래프 데이터베이스를 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) PostgreSQL 확장인 Apache AGE는 그래프를 Cypher라는 질의언어로 다룰 수 있게 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1782893157900&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MATCH (a:조항)-[:참조]-&amp;gt;(b:조항) WHERE a.id = $start RETURN b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 &quot;이 조항에서 참조로 연결된 다른 조항들을 찾아라&quot;는 질의를 던질 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 방식&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;진입(entry) 단계&lt;/b&gt;: 일반 RAG처럼 벡터/키워드 검색으로 질문과 직접 관련된 조항을 먼저 찾음(예: 점수 1.0으로 취급)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장(expand) 단계&lt;/b&gt;: 찾은 조항에서 그래프를 따라 1~2단계(multi-hop) 떨어진 연결 조항들도 추가로 가져옴(예: 점수 0.5로 취급 &amp;mdash; 직접 검색된 것보다는 신뢰도를 낮게 매김)&lt;/li&gt;
&lt;li&gt;확장 범위가 너무 넓어지지 않도록 가져올 연결 노드 수에 상한(예: 최대 6개)을 둠&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 모은 &quot;직접 검색 결과 + 그래프로 확장한 결과&quot;를 합쳐서 LLM에게 근거로 넘긴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMSA24/dJMcacXYo1r/EE9cawKJImjf5CYb7TjN5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMSA24/dJMcacXYo1r/EE9cawKJImjf5CYb7TjN5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMSA24/dJMcacXYo1r/EE9cawKJImjf5CYb7TjN5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMSA24%2FdJMcacXYo1r%2FEE9cawKJImjf5CYb7TjN5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;확장 범위: multi-hop&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;1-hop&quot;은 진입 노드에서 엣지 하나만 따라간 직접 이웃, &quot;2-hop&quot;은 그 이웃의 이웃까지 가는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hop 수를 $h$, 한 노드의 평균 연결 수(차수, degree)를 $D$라 하면, 이론상 도달 가능한 노드 수는 최대 $D^h$로 늘어나는데,&lt;br /&gt;KMeans나 룰베이스에서 봤던 &quot;경우의 수가 기하급수적으로 늘어나는&quot; 패턴이 여기서도 똑같이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실전에서는 보통 2-hop을 넘기지 않고, 위에서 말한 &quot;최대 6개&quot; 같은 상한을 같이 걸어서 폭주를 막는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 점수를 다르게 매기는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진입 노드(질문과 직접 관련)는 확실하니까 점수 1.0, 확장 노드(간접적으로 연결됨)는 관련성이 약할 수 있으니 점수 0.5로 낮춰서 다룰 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\다만 이 숫자를 프롬프트에 그냥 써둔다고 LLM이 항상 안정적으로 &quot;낮은 신뢰도&quot;로 해석하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는 이 점수를 검색 랭킹, 근거 정렬 순서, 프롬프트 메타데이터, 답변 시 우선 인용할 근거의 순서에 명시적으로 반영해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 점수는 LLM의 마음속 신호라기보다, 시스템이 근거를 정렬하고 제한하는 제어값에 가깝다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래프 구축 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphRAG를 쓰려면 &quot;어떤 노드가 어떤 노드와 연결되는가&quot;를 미리 정해서 그래프를 만들어야 한다.&lt;br /&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 과정이 GraphRAG의 핵심 선행 작업.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 기반 추출&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서의 구조나 언어 패턴으로 관계를 추출한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;제3조 제2항에 의하면...&quot; &amp;rarr; &quot;제3조 제2항&quot;을 참조 관계로 추출&lt;/li&gt;
&lt;li&gt;&quot;위 조항에서 정한 바에 따라...&quot; &amp;rarr; 앞에서 언급된 조항을 참조&lt;/li&gt;
&lt;li&gt;정규식 또는 NLP 파서(spaCy 등)로 &quot;참조&quot;, &quot;인용&quot;, &quot;준용&quot; 같은 키워드를 찾아 엣지로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점: 빠르고 저렴, 결정적(같은 입력 &amp;rarr; 같은 그래프). 단점: 명시적으로 드러난 참조만 잡고 암묵적 관계는 놓침.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LLM 기반 추출&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에게 문서를 읽게 하고 &quot;이 문서에서 중요한 엔티티와 관계를 추출해줘&quot;라고 지시한다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;입력: &quot;해외배송 상품의 환불은 수령일로부터 7일 이내에 신청해야 하며,
      위약금은 상품가격의 10%로 한다.&quot;

LLM 출력:
- 노드: [해외배송 환불 정책, 위약금 규정]
- 엣지: 해외배송 환불 정책 --[위약금 기준 참조]--&amp;gt; 위약금 규정
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 명시적이지 않은 암묵적 관계까지 포착 가능.&lt;/li&gt;
&lt;li&gt;단점: 비용이 크고(문서마다 LLM 호출), 결과가 일관적이지 않을 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는 두 방식을 섞거나(규칙으로 기본 그래프를 만들고 LLM으로 보완), 도메인 특성에 따라 선택한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Microsoft GraphRAG 방식 &amp;mdash; 커뮤니티 기반&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Microsoft에서 공개한 GraphRAG는 위에서 설명한 &quot;진입 + 확장&quot; 방식과 다른 접근법을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 아이디어&lt;/b&gt;: 그래프에서 연결이 밀한 노드들을 &lt;b&gt;커뮤니티(community)&lt;/b&gt;로 묶고, 각 커뮤니티를 LLM으로 요약해서 미리 저장해둔다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;그래프 구축 단계:
문서 &amp;rarr; LLM으로 엔티티&amp;middot;관계 추출 &amp;rarr; 그래프 생성
&amp;rarr; 커뮤니티 감지(Leiden 알고리즘 등)
&amp;rarr; 각 커뮤니티 요약 LLM으로 생성 &amp;rarr; 저장

검색 단계:
질문 &amp;rarr; 관련 커뮤니티 요약들을 검색 &amp;rarr; LLM에 넣어 답변 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식이 유리한 상황: &quot;전체 문서에 걸쳐 종합적인 이해가 필요한 질문&quot;(Global Query).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) &quot;이 약관에서 소비자 보호 관련 조항들의 공통점은 무엇인가?&quot;&lt;br /&gt;&amp;rarr; 벡터 검색으로 특정 조항 하나를 찾는 게 아니라, 전체를 아울러 요약해야 하는 경우.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Local Query vs Global Query&lt;/h3&gt;
&lt;table id=&quot;390c2707-14d8-8097-9f40-dde10ab0a29f&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Local&amp;nbsp;Query&lt;/td&gt;
&lt;td&gt;Global&amp;nbsp;Query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8026-9d9e-fbacdce09f09&quot;&gt;
&lt;td id=&quot;fnRy&quot;&gt;예시&lt;/td&gt;
&lt;td id=&quot;L&amp;lt;[V&quot;&gt;&quot;해외배송 환불 기간은?&quot;&lt;/td&gt;
&lt;td id=&quot;pxmA&quot;&gt;&quot;전체 약관에서 소비자 보호 원칙은?&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8045-b3b2-d28994214d42&quot;&gt;
&lt;td id=&quot;fnRy&quot;&gt;방식&lt;/td&gt;
&lt;td id=&quot;L&amp;lt;[V&quot;&gt;벡터 검색 + 그래프 확장&lt;/td&gt;
&lt;td id=&quot;pxmA&quot;&gt;커뮤니티 요약 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80b8-85d5-e26c1ffa20ed&quot;&gt;
&lt;td id=&quot;fnRy&quot;&gt;답변 범위&lt;/td&gt;
&lt;td id=&quot;L&amp;lt;[V&quot;&gt;특정 조항 몇 개&lt;/td&gt;
&lt;td id=&quot;pxmA&quot;&gt;문서 전체의 흐름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8035-9179-fc4967924dae&quot;&gt;
&lt;td id=&quot;fnRy&quot;&gt;GraphRAG 적용&lt;/td&gt;
&lt;td id=&quot;L&amp;lt;[V&quot;&gt;이 글에서 주로 다룬 방식&lt;/td&gt;
&lt;td id=&quot;pxmA&quot;&gt;Microsoft GraphRAG가 강점&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;b&gt;속도&amp;middot;복잡도&lt;/b&gt;: 그래프 탐색을 위한 추가 질의가 필요해서 일반 RAG보다 느리고 복잡하다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;선행 작업 필요&lt;/b&gt;: 문서를 미리 분석해서 참조 관계를 추출하고 그래프로 저장하는 별도 파이프라인이 선행되어야 한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;적용 도메인&lt;/b&gt;: 간접 연결이 자주 중요한 도메인(정책 문서처럼 조항끼리 참조가 많은 경우)에만 선택적으로 적용한다 &amp;mdash; 모든 RAG 시스템에 쓰는 게 아니다&lt;/li&gt;
&lt;/ul&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;GraphRAG는 RAG에 지식그래프 멀티홉 탐색을 더해서, 벡터로는 안 가깝지만 그래프로는 연결된 정보를 찾아냄&lt;/li&gt;
&lt;li&gt;진입(직접 검색) + 확장(그래프 탐색) 두 단계로 동작하고, 확장 노드는 점수를 낮춰서 &quot;간접 정보&quot;라는 신호를 LLM에 전달&lt;/li&gt;
&lt;li&gt;hop 수가 늘수록 도달 가능한 노드 수가 기하급수적으로 늘어서 보통 2-hop 이내로 제한&lt;/li&gt;
&lt;li&gt;그래프 구축이라는 선행 작업이 필요해서, 간접 연결이 자주 중요한 도메인에 선택적으로 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG와 GraphRAG는 모두 &quot;검색&amp;rarr;LLM&quot;이라는 단일 경로였다. 그런데 모든 질문에 똑같이 무거운 처리(비싼 모델, 긴 검색)를 쓰는 건 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉬운 질문은 가볍게, 어려운 질문만 무겁게 처리하고 싶다. 이걸 가능하게 하는 설계가 신뢰도 기반 에스컬레이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/AI 시스템 설계</category>
      <category>graphrag</category>
      <category>LLM활용</category>
      <category>rag</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/287</guid>
      <comments>https://chaeyami.tistory.com/287#entry287comment</comments>
      <pubDate>Wed, 1 Jul 2026 17:08:43 +0900</pubDate>
    </item>
    <item>
      <title>[AI설계] RAG(Retrieval-Augmented Generation, 검색 증강 생성) - LLM에게 모르는 지식을 주는 법</title>
      <link>https://chaeyami.tistory.com/286</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.RAG?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;검색(Retrieval)&lt;/b&gt;: 질문과&amp;nbsp;관련된&amp;nbsp;문서&amp;nbsp;조각을&amp;nbsp;데이터베이스에서&amp;nbsp;먼저&amp;nbsp;찾아온다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;증강(Augumented)&lt;/b&gt;: 찾아온&amp;nbsp;문서&amp;nbsp;조각을&amp;nbsp;LLM&amp;nbsp;프롬프트에&amp;nbsp;근거로&amp;nbsp;추가(증강)한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생성(Generation)&lt;/b&gt;: LLM이&amp;nbsp;그&amp;nbsp;근거를&amp;nbsp;바탕으로&amp;nbsp;답변을&amp;nbsp;생성한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은&amp;nbsp;학습&amp;nbsp;시점까지의&amp;nbsp;데이터만&amp;nbsp;알고&amp;nbsp;있다.&lt;br /&gt;&amp;nbsp;회사&amp;nbsp;내부&amp;nbsp;정책&amp;nbsp;문서,&amp;nbsp;최신&amp;nbsp;이용약관,&amp;nbsp;특정&amp;nbsp;서비스의&amp;nbsp;세부&amp;nbsp;매뉴얼처럼&amp;nbsp;모델이&amp;nbsp;애초에&amp;nbsp;본&amp;nbsp;적&amp;nbsp;없는&amp;nbsp;정보는&amp;nbsp;답변에&amp;nbsp;반영할&amp;nbsp;수&amp;nbsp;없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를&amp;nbsp;들어&amp;nbsp;쇼핑몰&amp;nbsp;CS&amp;nbsp;챗봇이&amp;nbsp;&quot;이번에&amp;nbsp;바뀐&amp;nbsp;해외배송&amp;nbsp;환불&amp;nbsp;규정&quot;을&amp;nbsp;답변에&amp;nbsp;반영하려면,&amp;nbsp;그&amp;nbsp;규정&amp;nbsp;문서를&amp;nbsp;어떻게든&amp;nbsp;모델에게&amp;nbsp;전달해야&amp;nbsp;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸&amp;nbsp;해결하는&amp;nbsp;가장&amp;nbsp;직관적인&amp;nbsp;방법은&amp;nbsp;&quot;필요한&amp;nbsp;정보를&amp;nbsp;프롬프트에&amp;nbsp;직접&amp;nbsp;넣어주는&amp;nbsp;것&quot;인데,&amp;nbsp;&lt;br /&gt;문서&amp;nbsp;전체를&amp;nbsp;매번&amp;nbsp;통째로&amp;nbsp;넣으면&amp;nbsp;비용이&amp;nbsp;크고&amp;nbsp;비효율적이다&amp;nbsp;(게다가&amp;nbsp;모델이&amp;nbsp;한&amp;nbsp;번에&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;길이에도&amp;nbsp;한계가&amp;nbsp;있다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG의 해법: 질문과 관련된 부분만 먼저 찾아서(검색, Retrieval), 그 부분만 LLM에게 넣어준다(생성, Generation).&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;카테고리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 &lt;b&gt;여러 기술을 조합하는 시스템 설계 패턴&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU와 RAM이 &quot;부품&quot;이고 그걸 조합하는 게 &quot;컴퓨터 아키텍처&quot;인 것처럼, &lt;br /&gt;&lt;a href=&quot;https://blog.chaenii.me/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;임베딩&lt;/a&gt;과 &lt;a href=&quot;https://blog.chaenii.me/285&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LLM&lt;/a&gt;이 부품이고 RAG는 &quot;그 부품들을 어떤 순서로 호출할지에 대한 규칙&quot;이다.&lt;/p&gt;
&lt;figure id=&quot;og_1782889928316&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] 벡터 임베딩(Vector Embedding) - 의미를 벡터로&quot; data-og-description=&quot;cf) TF-IDF [ML] TF-IDF(Term Frequency-Inverse Document Frequency)1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &amp;quot;중요도&amp;quot;를 가중치로 매기는 방법단순히 &amp;quot;몇 번 나왔는가&amp;quot;만 세는 빈&quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/284&quot; data-og-url=&quot;https://blog.chaenii.me/284&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/btnRTQ/dJMb8XSgMlm/oDbspkBSWdpqfkC1YBLXwk/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/kMimN/dJMb8U84BSB/llnRODOIKuWZ2Q6nRL65B0/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/gvp55/dJMb8ZvMkEk/UR5DxKfaAJdoCKozSaasA1/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/284&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/btnRTQ/dJMb8XSgMlm/oDbspkBSWdpqfkC1YBLXwk/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/kMimN/dJMb8U84BSB/llnRODOIKuWZ2Q6nRL65B0/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/gvp55/dJMb8ZvMkEk/UR5DxKfaAJdoCKozSaasA1/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] 벡터 임베딩(Vector Embedding) - 의미를 벡터로&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;cf) TF-IDF [ML] TF-IDF(Term Frequency-Inverse Document Frequency)1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &quot;중요도&quot;를 가중치로 매기는 방법단순히 &quot;몇 번 나왔는가&quot;만 세는 빈&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1782889926712&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] 트랜스포머와 LLM(Large Language Model) - 텍스트를 생성하는 신경망&quot; data-og-description=&quot;1. 임베딩과의 관계 임베딩은 숫자로 끝나고, LLM은 다음 단어를 계속 만들어낸다. 임베딩은 &amp;quot;텍스트&amp;rarr;벡터&amp;quot; 변환만 하고 끝남LLM은 같은 트랜스포머 계열 구조를 쓰지만 벡터에서 다시 텍스트를 &quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/285&quot; data-og-url=&quot;https://blog.chaenii.me/285&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RbLLu/dJMb9ia1V5U/eRCYYw1FB2rdHSXGypwqtK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ebTfgf/dJMb9efoPzt/QfYwnjXkerbXUcKOzn7WO0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/tZtrB/dJMb9jgIcTc/0FjWYnfBpzaoWXKRs0Sb6k/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/285&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/285&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RbLLu/dJMb9ia1V5U/eRCYYw1FB2rdHSXGypwqtK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ebTfgf/dJMb9efoPzt/QfYwnjXkerbXUcKOzn7WO0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/tZtrB/dJMb9jgIcTc/0FjWYnfBpzaoWXKRs0Sb6k/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] 트랜스포머와 LLM(Large Language Model) - 텍스트를 생성하는 신경망&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 임베딩과의 관계 임베딩은 숫자로 끝나고, LLM은 다음 단어를 계속 만들어낸다. 임베딩은 &quot;텍스트&amp;rarr;벡터&quot; 변환만 하고 끝남LLM은 같은 트랜스포머 계열 구조를 쓰지만 벡터에서 다시 텍스트를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파이프라인&lt;/h4&gt;
&lt;pre id=&quot;code_1782890081112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;사용자 질문
   &amp;darr;
[검색 단계] 질문과 관련된 문서 조각(청크) 찾기
   &amp;darr;
[생성 단계] 찾은 조각들을 프롬프트에 넣고 LLM에게 질문 + 근거 전달
   &amp;darr;
LLM이 근거를 참고해서 답변 생성&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 검색 단계&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;청킹(chunking)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서&amp;nbsp;전체를&amp;nbsp;검색&amp;nbsp;단위로&amp;nbsp;쓰면&amp;nbsp;비효율적이다(문서가&amp;nbsp;길면&amp;nbsp;그&amp;nbsp;안의&amp;nbsp;관련&amp;nbsp;없는&amp;nbsp;부분도&amp;nbsp;다&amp;nbsp;끌려옴).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&amp;nbsp;문서를&amp;nbsp;의미&amp;nbsp;있는&amp;nbsp;단위(예:&amp;nbsp;정책&amp;nbsp;문서의&amp;nbsp;&quot;조항&quot;&amp;nbsp;단위,&amp;nbsp;FAQ의&amp;nbsp;&quot;항목&quot;&amp;nbsp;단위)로&amp;nbsp;잘라서&amp;nbsp;저장해두고,&amp;nbsp;&lt;br /&gt;검색은&amp;nbsp;이&amp;nbsp;조각(청크)&amp;nbsp;단위로&amp;nbsp;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUCpwc/dJMcaicS2yN/nAVuZetcWKKI4XKLaLk6z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUCpwc/dJMcaicS2yN/nAVuZetcWKKI4XKLaLk6z1/img.png&quot; data-alt=&quot;청킹&amp;amp;rarr;임베딩 흐름&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUCpwc/dJMcaicS2yN/nAVuZetcWKKI4XKLaLk6z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUCpwc%2FdJMcaicS2yN%2FnAVuZetcWKKI4XKLaLk6z1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;청킹&amp;rarr;임베딩 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;두&amp;nbsp;가지&amp;nbsp;검색&amp;nbsp;방식&lt;/h4&gt;
&lt;table id=&quot;390c2707-14d8-8055-b64f-ff46816c83a9&quot; style=&quot;border-collapse: collapse; width: 100%; height: 168px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;키워드 검색&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;벡터(임베딩)&amp;nbsp;검색&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8073-94a3-e2b51a230e47&quot; style=&quot;height: 42px;&quot;&gt;
&lt;td id=&quot;f&amp;#96;&amp;#96;&amp;lt;&quot; style=&quot;height: 42px;&quot;&gt;&lt;b&gt;작동 방식&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;KTct&quot; style=&quot;height: 42px;&quot;&gt;질문에 등장한 단어가 문서 조각에 몇 번 나오는지 카운팅&lt;/td&gt;
&lt;td id=&quot;Al~Q&quot; style=&quot;height: 42px;&quot;&gt;질문과 문서 조각을 각각 임베딩 후 코사인 유사도 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80d4-82fa-c6494cc73d7c&quot; style=&quot;height: 42px;&quot;&gt;
&lt;td id=&quot;f&amp;#96;&amp;#96;&amp;lt;&quot; style=&quot;height: 42px;&quot;&gt;&lt;b&gt;동의어&amp;middot;유사 의미&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;KTct&quot; style=&quot;height: 42px;&quot;&gt;✗ &amp;mdash; 정확히 같은 단어만&lt;/td&gt;
&lt;td id=&quot;Al~Q&quot; style=&quot;height: 42px;&quot;&gt;✓ &amp;mdash; &quot;강아지&quot;로 검색해도 &quot;개&quot; 관련 문서 찾을 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80ae-bbcd-e128261ec716&quot; style=&quot;height: 21px;&quot;&gt;
&lt;td id=&quot;f&amp;#96;&amp;#96;&amp;lt;&quot; style=&quot;height: 21px;&quot;&gt;&lt;b&gt;비용&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;KTct&quot; style=&quot;height: 21px;&quot;&gt;별도&amp;nbsp;모델/API&amp;nbsp;비용은&amp;nbsp;작지만&amp;nbsp;인덱스&amp;nbsp;저장&amp;middot;검색&amp;nbsp;비용은&amp;nbsp;있음&lt;/td&gt;
&lt;td id=&quot;Al~Q&quot; style=&quot;height: 21px;&quot;&gt;임베딩&amp;nbsp;생성&amp;nbsp;비용&amp;middot;벡터&amp;nbsp;인덱스&amp;nbsp;저장&amp;middot;검색&amp;nbsp;비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80c3-a40f-fa31cca739e6&quot; style=&quot;height: 42px;&quot;&gt;
&lt;td id=&quot;f&amp;#96;&amp;#96;&amp;lt;&quot; style=&quot;height: 42px;&quot;&gt;&lt;b&gt;예측 가능성&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;KTct&quot; style=&quot;height: 42px;&quot;&gt;높음 (결정적, 룰베이스에 가까움)&lt;/td&gt;
&lt;td id=&quot;Al~Q&quot; style=&quot;height: 42px;&quot;&gt;낮음&lt;br /&gt;의미상 무관한데 우연히 벡터가 가까운 경우(false positive) 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하이브리드 검색: 둘을 합치기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는 둘 다 계산해서 점수를 합친다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{최종점수} = w_v \times \text{벡터유사도} + w_k \times \text{키워드점수}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: $w_v=0.7$, $w_k=0.3$. 벡터 검색이 &quot;의미&quot;를, 키워드 검색이 &quot;정확한 용어 일치&quot;를 보완해서 둘 중 하나만 쓸 때보다 안정적인 결과를 낸다. &lt;br /&gt;키워드 점수는 보통 등장 횟수를 정규화해서 0~1 범위로 맞춘다(예: 등장 5회 이상이면 만점).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 가중합을 쓸 때 가장 중요한 건 &lt;b&gt;점수 스케일 정규화&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 유사도는 보통 -1~1 또는 0~1 범위인데, BM25 같은 키워드 점수는 쿼리와 문서 길이에 따라 범위가 크게 달라진다. &lt;br /&gt;두 점수를 그대로 더하면 한쪽 점수가 다른 쪽을 압도할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실전에서는 min-max scaling, z-score, rank-based fusion(RRF) 같은 방식으로 스케일을 맞춘 뒤 합친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;숫자로 감을 잡아보면&lt;/b&gt;: 청크 A가 벡터유사도 0.85, 키워드점수 0.2(질문 단어가 거의 안 겹침)라 하고, 청크 B가 벡터유사도 0.60, 키워드점수 1.0(질문 단어가 정확히 다 들어있음)이라 하자. $w_v=0.7$, $w_k=0.3$을 적용하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{청크 A} = 0.7 \times 0.85 + 0.3 \times 0.2 = 0.595 + 0.06 = 0.655&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{청크 B} = 0.7 \times 0.60 + 0.3 \times 1.0 = 0.42 + 0.30 = 0.72&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터유사도만 봤다면 A가 이겼겠지만, 키워드까지 합치면 B가 더 높은 점수로 역전된다.&lt;br /&gt;&quot;의미는 약간 덜 비슷해도 질문의 핵심 단어를 정확히 담고 있는 문서&quot;가 최종적으로 더 우선시되는 경우를 수치로 보여준다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;후포 풀 (candidate pool)&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;1차 후보 추출&lt;/b&gt;: 벡터 검색으로 최종 목표보다 넉넉하게 후보를 뽑는다 (예: 최종 4개가 목표라면 최소 12~16개)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2차 정렬&lt;/b&gt;: 그 후보들 안에서 하이브리드 점수(벡터+키워드)로 다시 정렬한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 선택&lt;/b&gt;: 상위 N개(예: 4개)만 LLM 프롬프트에 넣는다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색으로 거칠게 거른 뒤 하이브리드 기준으로 정밀하게 추리는 2단계 구조.&lt;br /&gt;처음부터 모든 청크에 하이브리드 점수를 계산하면 느리므로, 먼저 후보 풀을 좁히고 그 안에서만 정밀 계산하는 방식이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 청크 크기 설계 &amp;mdash; 작게 vs 크게&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크를 어떤 크기로 자르느냐는 RAG 품질에 큰 영향을 미친다.&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 정밀도는 높아지지만, 하나의&amp;nbsp; LLM이 답변에 필요한 앞뒤 문맥을 잃는다.&lt;/li&gt;
&lt;li&gt;청크 안에 맥락이 충분히 담기지 않기 때문.&lt;/li&gt;
&lt;li&gt;e.g) 조항 하나를 세 문장씩 잘랐을 때. &quot;단, 다음의 경우는 제외한다&quot;는 절이 이전 청크에 있어서 조건을 놓치는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;너무 크게 자르면&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;청크 하나 안에 관련 없는 내용이 섞여 들어온다.&lt;/li&gt;
&lt;li&gt;컨텍스트 윈도우를 빨리 소진한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Parent Document Retriever 패턴&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 문제를 절충하는 흔한 방법.&lt;/li&gt;
&lt;li&gt;문서를 작은 청크로 잘라서 임베딩&amp;middot;검색하되, 실제로 LLM에 넘기는 건 그 청크가 속한 &lt;b&gt;상위 단락(parent document)&lt;/b&gt;으로 확장한다.&lt;/li&gt;
&lt;li&gt;작은 단위로 정밀하게 찾고, 큰 단위로 맥락을 제공하는 2단계 구조.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 재순위화(Reranking)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색(bi-encoder) 후 LLM에 넣기 전에, 검색 결과를 &lt;b&gt;cross-encoder 모델로 한 번 더 정렬&lt;/b&gt;하는 패턴.&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;Bi-encoder(임베딩 검색)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문과 청크를 각각 따로 임베딩해서 코사인 유사도 계산.&lt;/li&gt;
&lt;li&gt;빠르지만 질문과 청크를 함께 보지 않아서 미묘한 관련성 판단이 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cross-encoder(재순위화 모델)&lt;/b&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;으로 받아 관련도 점수를 계산.&lt;/li&gt;
&lt;li&gt;두 텍스트를 함께 보므로 정확하지만 느림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;흐름:
질문 &amp;rarr; [임베딩 검색] &amp;rarr; 후보 20개 &amp;rarr; [Cross-encoder 재순위화] &amp;rarr; 상위 4개 &amp;rarr; LLM
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재순위화는 속도 때문에 전체 청크에 적용하지 않고, 임베딩 검색으로 좁힌 후보에만 적용한다. &lt;br /&gt;Cohere Rerank, BGE-reranker 같은 모델이 자주 쓰인다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 메타데이터 필터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색 이전에 &lt;b&gt;메타데이터로 후보를 먼저 좁히는&lt;/b&gt; 방법. 벡터 유사도 계산 전에 조건을 거는 pre-filter다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜 필터: &quot;2024년 이후에 업데이트된 문서만&quot;&lt;/li&gt;
&lt;li&gt;카테고리 필터: &quot;환불 정책 관련 문서만&quot;&lt;/li&gt;
&lt;li&gt;출처 필터: &quot;공식 약관 문서만, 고객 리뷰 제외&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타데이터를 청크 저장 시 함께 저장해두고, 벡터 DB의 필터 쿼리(where 조건)로 적용한다. &lt;br /&gt;전체 청크 수를 줄여서 검색 속도도 올라가고, 관련 없는 카테고리의 false positive도 줄어든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. RAG 품질 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 시스템이 잘 작동하는지 어떻게 측정하는가? 단순히 &quot;답변이 자연스러운가&quot;로는 부족하다. 주요 평가 축 세 가지:&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;Context Relevancy(검색 품질)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색된 청크들이 실제 질문과 관련 있는가.&lt;/li&gt;
&lt;li&gt;관련 없는 청크가 많이 섞이면 LLM이 엉뚱한 정보를 참조할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Faithfulness(근거 충실도)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM의 답변이 검색된 청크에 근거하고 있는가.&lt;/li&gt;
&lt;li&gt;청크에 없는 내용을 만들어냈다면(환각) faithfulness가 낮다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Answer Relevancy(답변 관련성)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종 답변이 원래 질문에 잘 답하고 있는가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;이상적인 RAG = 높은 Context Relevancy + 높은 Faithfulness + 높은 Answer Relevancy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RAGAS&lt;/b&gt; 같은 평가 프레임워크는 이 세 가지를 자동으로 측정해준다.&lt;br /&gt;LLM을 평가자로 써서 &quot;이 답변이 이 청크에 근거하고 있는가&quot;를 판단하는 방식이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 우아한 성능저하(graceful degradation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색에 필요한 임베딩 API나 벡터 데이터베이스가 장애가 나면 어떻게 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 통째로 멈추는 대신, &lt;b&gt;키워드 검색만으로라도 동작하게 폴백&lt;/b&gt;시키는 설계가 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 &quot;검색이 아예 안 되는 것&quot;보다 &quot;정확도가 조금 떨어지는 검색&quot;이 낫다는 전제로 설계되는 경우가 많다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 데이터 적재: 변경감지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG가 검색할 문서(정책&amp;middot;약관 등)는 시간이 지나면서 바뀐다. 매번 전체를 다시 임베딩하면 비용이 크므로 변경감지 패턴을 쓴다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;청크를 임베딩할 때 원문의 해시값(예: SHA-256)도 함께 저장한다&lt;/li&gt;
&lt;li&gt;다음번 수집 시 해시를 비교해서 변경된 청크만 감지한다&lt;/li&gt;
&lt;li&gt;바뀐 청크만 (필요하다면 LLM으로 요약&amp;middot;재작성한 뒤) 재임베딩한다&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. RAG의 본질: 사실은 룰베이스적 절차다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 점: &lt;b&gt;RAG 자체는 &quot;학습&quot;되지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;벡터검색을 먼저 하고, 그 결과를 LLM 프롬프트에 어떤 형식으로 넣어라&quot;는 절차는 사람이 코드로 짠 고정된 규칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습되는 부분은 그 안에 들어가는 임베딩 모델과 LLM뿐이다. &lt;br /&gt;그래서 RAG는 분류상 &quot;머신러닝 기법&quot;이 아니라 &quot;머신러닝 구성요소를 활용하는 시스템 설계&quot;라고 보는 게 정확하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RAG는 알고리즘이 아니라 &quot;검색 후 그 결과를 LLM에 넣는다&quot;는 시스템 설계 패턴&lt;/li&gt;
&lt;li&gt;키워드 검색과 벡터 검색을 하이브리드로 합쳐서 서로의 약점(동의어 vs 정확 일치)을 보완&lt;/li&gt;
&lt;li&gt;청킹 &amp;rarr; 임베딩 &amp;rarr; 변경감지 기반 재처리까지가 데이터 적재 파이프라인&lt;/li&gt;
&lt;li&gt;임베딩 API 장애 시 키워드 검색만으로라도 동작하는 우아한 성능저하 설계가 일반적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG의 검색을 한 단계 더 확장한 게 &lt;a href=&quot;https://chaeyami.tistory.com/287&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GraphRAG&lt;/a&gt;다. 벡터 검색에 지식그래프 기반 탐색을 추가해서, 직접 비슷하진 않지만 &quot;같은 근거를 공유하는&quot; 문서까지 찾아낸다.&lt;/p&gt;
&lt;figure id=&quot;og_1782895239755&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[AI설계] GraphRAG - 지식그래프로 확장한 RAG&quot; data-og-description=&quot;RAG의 한계에서 출발RAG는 질문과 벡터가 &amp;quot;직접 비슷한&amp;quot; 문서만 찾는다. 그런데 실제 질문은 종종 여러 단계를 거쳐야 답이 나오는 경우가 있다.e.g) &amp;quot;해외배송 상품을 환불하면 위약금은 얼마인가?&quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://chaeyami.tistory.com/287&quot; data-og-url=&quot;https://blog.chaenii.me/287&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9ji1w/dJMb84X9EEn/kZNh6r9KR66Jagz0aDsnsK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/cjRz8s/dJMb87N7I4W/tOQsLwU8A9pyybYOlJAmg0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/byeBye/dJMb85vZXlw/DtbxNv5IYb6lRAkhTKje9K/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941&quot;&gt;&lt;a href=&quot;https://chaeyami.tistory.com/287&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chaeyami.tistory.com/287&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9ji1w/dJMb84X9EEn/kZNh6r9KR66Jagz0aDsnsK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/cjRz8s/dJMb87N7I4W/tOQsLwU8A9pyybYOlJAmg0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/byeBye/dJMb85vZXlw/DtbxNv5IYb6lRAkhTKje9K/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[AI설계] GraphRAG - 지식그래프로 확장한 RAG&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;RAG의 한계에서 출발RAG는 질문과 벡터가 &quot;직접 비슷한&quot; 문서만 찾는다. 그런데 실제 질문은 종종 여러 단계를 거쳐야 답이 나오는 경우가 있다.e.g) &quot;해외배송 상품을 환불하면 위약금은 얼마인가?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/AI 시스템 설계</category>
      <category>llm</category>
      <category>LLM활용</category>
      <category>rag</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/286</guid>
      <comments>https://chaeyami.tistory.com/286#entry286comment</comments>
      <pubDate>Wed, 1 Jul 2026 17:01:43 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 트랜스포머와 LLM(Large Language Model) - 텍스트를 생성하는 신경망</title>
      <link>https://chaeyami.tistory.com/285</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 임베딩과의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt; 임베딩은 숫자로 끝나고, LLM은 다음 단어를 계속 만들어낸다. &lt;/b&gt;&lt;/span&gt;&lt;/p&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://blog.chaenii.me/284&quot;&gt;임베딩&lt;/a&gt;은 &quot;텍스트&amp;rarr;벡터&quot; 변환만 하고 끝남&lt;/li&gt;
&lt;li&gt;LLM은 같은 트랜스포머 계열 구조를 쓰지만 벡터에서 다시 텍스트를 생성한다.&lt;/li&gt;
&lt;li&gt;둘 다 &quot;문맥을 이해한다&quot;는 점은 같은데, 출력 형태가 다른 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1782887934273&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] 벡터 임베딩(Vector Embedding) - 의미를 벡터로&quot; data-og-description=&quot;cf) TF-IDF [ML] TF-IDF(Term Frequency-Inverse Document Frequency)1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &amp;quot;중요도&amp;quot;를 가중치로 매기는 방법단순히 &amp;quot;몇 번 나왔는가&amp;quot;만 세는 빈&quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/284&quot; data-og-url=&quot;https://blog.chaenii.me/284&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/btnRTQ/dJMb8XSgMlm/oDbspkBSWdpqfkC1YBLXwk/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/kMimN/dJMb8U84BSB/llnRODOIKuWZ2Q6nRL65B0/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/gvp55/dJMb8ZvMkEk/UR5DxKfaAJdoCKozSaasA1/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/284&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/btnRTQ/dJMb8XSgMlm/oDbspkBSWdpqfkC1YBLXwk/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/kMimN/dJMb8U84BSB/llnRODOIKuWZ2Q6nRL65B0/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/gvp55/dJMb8ZvMkEk/UR5DxKfaAJdoCKozSaasA1/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] 벡터 임베딩(Vector Embedding) - 의미를 벡터로&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;cf) TF-IDF [ML] TF-IDF(Term Frequency-Inverse Document Frequency)1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &quot;중요도&quot;를 가중치로 매기는 방법단순히 &quot;몇 번 나왔는가&quot;만 세는 빈&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 트랜스포머&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머의 핵심은 &lt;b&gt;어텐션(attention)&lt;/b&gt; 메커니즘이다. &lt;br /&gt;깊은 구조까지 알 필요는 없고, 풀고자 한 문제만 이해하면 충분하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이전 모델의 한계&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문장을&amp;nbsp;한&amp;nbsp;단어씩&amp;nbsp;순서대로&amp;nbsp;읽으면서&amp;nbsp;&quot;기억&quot;을&amp;nbsp;누적하는&amp;nbsp;방식&lt;/li&gt;
&lt;li&gt;문장이 길어지면 앞부분의 정보가 점점 희석되는 문제가 있었다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트랜스포머의 해법 - 전체를 한 번에&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문장&amp;nbsp;전체를&amp;nbsp;동시에&amp;nbsp;보면서,&amp;nbsp;각&amp;nbsp;단어가&amp;nbsp;다른&amp;nbsp;모든&amp;nbsp;단어와&amp;nbsp;얼마나&amp;nbsp;&quot;관련&amp;nbsp;있는지&quot;를&amp;nbsp;직접&amp;nbsp;계산한다&amp;nbsp;(어텐션&amp;nbsp;스코어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) &quot;그것은 책상 위에 있었다. 강아지가 그것을 물어뜯었다&quot;에서 &quot;그것&quot;이 무엇을 가리키는지, 문장 전체를 동시에 보면서 가중치를 매겨 판단한다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;인코더 계열 모델은 입력 문장 전체를 양방향으로 볼 수 있고, GPT 같은 디코더 계열 생성 모델은 causal mask를 써서 현재 위치보다 미래의 토큰은 보지 못하게 막는다. 그래서 생성 시점에는 &quot;지금까지 나온 토큰&quot;만 보고 다음 토큰을 예측한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어텐션 수식&lt;/b&gt; (구조만 보면 충분)&lt;br /&gt;각&amp;nbsp;단어는&amp;nbsp;세&amp;nbsp;가지&amp;nbsp;벡터로&amp;nbsp;변환된다:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 76px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&lt;b&gt;벡터&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&lt;b&gt;비유&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&lt;b&gt;Query&amp;nbsp;($Q$) &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&quot;나는&amp;nbsp;무엇과&amp;nbsp;관련&amp;nbsp;있는가?&quot;&amp;nbsp;를&amp;nbsp;묻는다&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;검색&amp;nbsp;키워드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;b&gt;Key&amp;nbsp;($K$)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&quot;나는&amp;nbsp;어떤&amp;nbsp;내용을&amp;nbsp;담고&amp;nbsp;있는가?&quot;&amp;nbsp;를&amp;nbsp;알린다&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;책의&amp;nbsp;목차&amp;middot;태그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;b&gt;Value&amp;nbsp;($V$) &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;실제로&amp;nbsp;전달할&amp;nbsp;정보&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;책의&amp;nbsp;본문&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산&amp;nbsp;순서:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한 단어의 $Q$와 다른 모든 단어의 $K$를 내적 &amp;rarr; 관련도 점수&lt;/li&gt;
&lt;li&gt;소프트맥스로 정규화 &amp;rarr; 가중치 (전체 합 = 1)&lt;/li&gt;
&lt;li&gt;그 가중치로 $V$들을 가중평균 &amp;rarr; 최종 출력&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V&lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$\sqrt{d_k}$로&amp;nbsp;나누는&amp;nbsp;건&amp;nbsp;벡터&amp;nbsp;차원이&amp;nbsp;커질수록&amp;nbsp;내적&amp;nbsp;값이&amp;nbsp;커져서&amp;nbsp;소프트맥스가&amp;nbsp;한쪽으로&amp;nbsp;쏠리는&amp;nbsp;걸&amp;nbsp;막기&amp;nbsp;위한&amp;nbsp;스케일&amp;nbsp;조정이다.&lt;/li&gt;
&lt;li&gt;흥미로운 점: $QK^T$가 &lt;a href=&quot;https://blog.chaenii.me/279&quot;&gt;벡터 유사도&lt;/a&gt;에서 본 내적과 본질적으로 같은 연산이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;이 단어와 저 단어가 얼마나 관련 있는가&quot;를 코사인 유사도와 같은 방식(내적)으로 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$\sqrt{d_k}$로 나누는 건 벡터 차원이 커질수록 내적 값이 커져서 softmax 출력이 한쪽으로 쏠리는 걸 막기 위한 스케일 조정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ee2lsH/dJMcaasl3KD/5EmNcqc5UKDkmE5WV9giTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ee2lsH/dJMcaasl3KD/5EmNcqc5UKDkmE5WV9giTK/img.png&quot; data-alt=&quot;N&amp;amp;times;N 어텐션 가중치 히트맵&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ee2lsH/dJMcaasl3KD/5EmNcqc5UKDkmE5WV9giTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fee2lsH%2FdJMcaasl3KD%2F5EmNcqc5UKDkmE5WV9giTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1254&quot; height=&quot;1254&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;N&amp;times;N 어텐션 가중치 히트맵&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문맥이 길어도 정보가 희석되지 않음&lt;/li&gt;
&lt;li&gt;병렬 계산 가능: 순서대로 읽지 않아도 됨&lt;/li&gt;
&lt;li&gt;학습 속도 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. LLM 텍스트 생성 원리: 다음 토큰 예측&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM이 텍스트를 생성하는 원리 - 지금까지 나온 단어들을 보고, 다음에 올 단어(정확히는 토큰)가 무엇일지 확률로 예측하는 것:&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P(w_t \mid w_1, w_2, \ldots, w_{t-1})&lt;br /&gt;$$&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전체 어휘(e.g. 5만 개 토큰)에 대해 &quot;다음 토큰이 이것일 확률&quot; 전부 계산 &amp;rarr; 확률분포 생성&lt;/li&gt;
&lt;li&gt;그중 하나를 골라 출력&lt;/li&gt;
&lt;li&gt;그 단어를 다시 입력에 추가 &amp;rarr; 그다음 단어를 또 예측&lt;/li&gt;
&lt;li&gt;반복 &amp;rarr; 문장 완성&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확률분포는 어떻게 만들어지는가: 소프트맥스(softmax):&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;신경망의 마지막 층은 어휘의 각 토큰마다 &quot;점수&quot;(logit)라는 임의의 실수를 출력한다.&lt;/li&gt;
&lt;li&gt;소프트맥스가 이 점수들을 합이 1인 확률처럼 바꿔준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P(w_i) = \frac{e^{z_i}}{\sum_j e^{z_j}}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$z_i$는 토큰 $i$의 점수(logit).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수함수를 쓰는 이유&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 양수를 보장해서 확률처럼 다룰 수 있게 함&lt;/li&gt;
&lt;li&gt;점수 차이를 &quot;증폭&quot;시켜서 점수가 높은 토큰일수록 확률이 비례 이상으로 더 커지게 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;점수가 약간만 높아도 확률은 훨씬 더 높아지는 식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccd0hS/dJMcaijCvDn/UD7LkxUq4H1J5fVm5RRv1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccd0hS/dJMcaijCvDn/UD7LkxUq4H1J5fVm5RRv1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccd0hS/dJMcaijCvDn/UD7LkxUq4H1J5fVm5RRv1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccd0hS%2FdJMcaijCvDn%2FUD7LkxUq4H1J5fVm5RRv1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Temperature: 확률분포에서 어떻게 뽑을 것인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확률분포가 만들어진 다음, &lt;b&gt;실제로 어떤 토큰을 뽑을지&lt;/b&gt;를 결정하는 파라미터가 temperature다.&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;temperature = 0&lt;/b&gt;: 항상 확률이 가장 높은 토큰을 그대로 선택(그리디 디코딩). 같은 입력엔 거의 항상 같은 출력&lt;/li&gt;
&lt;li&gt;&lt;b&gt;temperature 높음(예: 1.0 이상)&lt;/b&gt;: 확률이 낮은 토큰도 가끔 선택될 수 있게 함 &amp;mdash; 같은 질문에도 매번 표현이나 결론이 조금씩 달라질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;temperature는 소프트맥스에 들어가기 전에 점수(logit) $z_i$를 $T$로 나누는 방식으로 구현된다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P(w_i) = \frac{e^{z_i/T}}{\sum_j e^{z_j/T}}&lt;br /&gt;$$&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;$T$가 작을수록&lt;/b&gt;(0에 가까울수록): 점수 차이가 나누기를 통해 더 크게 벌어져서 소프트맥스가 1등 토큰에 거의 모든 확률을 몰아줌(분포가 뾰족해짐)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;$T$가 클수록&lt;/b&gt;: 점수 차이가 줄어들어 여러 토큰에 확률이 고르게 퍼짐(분포가 평평해짐)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;$T=0$&lt;/b&gt;: 수학적으로는 나눗셈이 정의 안 되지만, 실제 구현에서는 &quot;그냥 1등을 그대로 선택&quot;하는 것으로 처리함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ReEfD/dJMcabELwQz/gPdiilwqGlM3zkwZrIcMv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ReEfD/dJMcabELwQz/gPdiilwqGlM3zkwZrIcMv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ReEfD/dJMcabELwQz/gPdiilwqGlM3zkwZrIcMv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FReEfD%2FdJMcabELwQz%2FgPdiilwqGlM3zkwZrIcMv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;temperature=0이라고 해서 &quot;신경망을 안 쓰고 룰베이스로 작동하는 것&quot;이 아니다.&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;text-align: start;&quot;&gt;temperature=0이어도 모델은 여전히 입력 전체를 신경망으로 통과시켜 문맥을 이해하고 확률분포를 계산하는 무거운 연산을 그대로 한다. &lt;br /&gt;&lt;b&gt;temperature가 바꾸는 건&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;그 확률분포에서 마지막에 토큰 하나를 뽑는 방식뿐.&lt;/b&gt;&lt;br /&gt;룰베이스는 신경망 연산이 전혀 없는 if-then 코드라서, temperature와 상관없이 LLM과는 완전히 다른 메커니즘이다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;법률&amp;middot;의료처럼 일관성이 중요한 도메인에서는 같은 질문에 항상 같은 답이 나오게 하기 위해&amp;nbsp; temperature를 0 또는 0에 가깝게(e.g. 0.1) 낮게 설정하는 게 일반적이다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 토크나이저: 텍스트를 토큰으로 쪼개는 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토크나이저(tokenizer):&amp;nbsp;LLM이 텍스트를 처리하기 전에 텍스트를 토큰으로 쪼갠다&lt;/li&gt;
&lt;li&gt;각 토큰을 정수 ID로 변환한 뒤 모델에 입력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 단어와 다르다. 현대 LLM은 거의 다 &lt;b&gt;서브워드(subword) 토크나이저&lt;/b&gt;를 쓴다. &lt;br /&gt;&quot;unhappiness&quot; &amp;rarr; [&quot;un&quot;, &quot;happiness&quot;] 처럼 자주 나오는 조합은 하나의 토큰, 드문 단어는 더 작게 쪼개진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어는 영어보다 토큰 효율이 낮다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영어: 단어 하나 &amp;asymp; 토큰 1~2개&lt;/li&gt;
&lt;li&gt;한국어: 어절 하나 &amp;asymp; 토큰 2~5개 (조사&amp;middot;어미가 별도 토큰으로 분리되는 경우가 많음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 실전에서 두 가지 영향을 미친다:&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;: API는 보통 토큰 수 기준으로 과금한다. 한국어 문서는 같은 분량이어도 영어보다 토큰 수가 더 많이 나와 비용이 높아진다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 윈도우 소진&lt;/b&gt;: 아래 항목 참고&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 컨텍스트 윈도우&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컨텍스트&amp;nbsp;윈도우(context&amp;nbsp;window)&lt;/b&gt;: LLM이 입력으로 받을 수 있는 최대 토큰 수&lt;/li&gt;
&lt;li&gt;입력(프롬프트 + 이전 대화)과 출력(생성할 텍스트)을 합친 토큰 수가 이 한계를 넘으면 잘리거나 오류가 난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 윈도우가 크면 좋지만, &lt;b&gt;토큰이 많아질수록 비용&amp;middot;지연도 선형 이상으로 증가&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 컨텍스트가 너무 길면 &lt;b&gt;&quot;lost in the middle&quot;&lt;/b&gt; 현상이 생긴다.&lt;br /&gt;모델이 입력의 앞부분과 뒷부분은 잘 참조하는 반면 중간 부분을 잘 못 참조하는 경향.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 설계에서 너무 많은 청크를 프롬프트에 넣지 않는 이유 중 하나다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 프롬프트 유형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에게 같은 작업을 시키더라도 프롬프트 방식에 따라 품질이 크게 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시스템 프롬프트(system prompt)&lt;/b&gt;: 사용자 메시지 앞에 붙는 특별 지시문. 모델의 역할, 행동 방침, 응답 형식을 미리 설정한다. &lt;br /&gt;e.g) &quot;당신은 쇼핑몰 CS 챗봇입니다. 답변은 한국어로, 200자 이내로 합니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프롬프팅 방식:&lt;/b&gt;&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;Zero-shot&lt;/b&gt;: 예시 없이 지시만 한다. &quot;이 리뷰의 감성을 긍정/부정으로 분류해줘.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Few-shot&lt;/b&gt;: 예시 몇 개를 먼저 보여주고 패턴을 따르게 한다. 복잡한 작업일수록 Few-shot이 Zero-shot보다 안정적인 결과를 낸다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;예시:
리뷰: &quot;배송이 너무 빠르고 포장이 깔끔해요!&quot; &amp;rarr; 긍정
리뷰: &quot;제품 불량이 있어서 실망했어요&quot; &amp;rarr; 부정
---
리뷰: &quot;가격 대비 품질이 괜찮네요&quot; &amp;rarr; ?
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Chain-of-thought(CoT)&lt;/b&gt;: &quot;단계별로 생각하면서 답해줘&quot;라고 지시해서 모델이 중간 추론 과정을 출력하게 한다. 수학 문제&amp;middot;복잡한 논리 추론에서 정확도가 높아진다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 환각(Hallucination)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;환각(hallucination)&lt;/b&gt;: LLMdl &lt;b&gt;없는 사실을 그럴듯하게 만들어내는&lt;/b&gt; 경향.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모르는 내용에 대해 &quot;확인이 필요합니다&quot;가 아니라 그냥 만들어서 답한다&lt;/li&gt;
&lt;li&gt;존재하지 않는 논문&amp;middot;법령&amp;middot;인물 정보를 자신감 있게 제시한다&lt;/li&gt;
&lt;li&gt;특히 정확한 숫자, 날짜, 고유명사에서 자주 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 생기는가&lt;/b&gt;: &lt;br /&gt;LLM은 &quot;다음에 올 토큰이 뭘지 확률적으로 예측&quot;하는 모델이다. &lt;br /&gt;틀린 정보도 문맥상 자연스럽게 이어지면 높은 확률로 선택될 수 있다.&lt;br /&gt;사실 여부를 확인하는 메커니즘이 기본 구조에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완화 방법&lt;/b&gt;:&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;RAG&lt;/b&gt;: 근거 문서를 프롬프트에 넣어주면 모델이 그 범위 안에서 답하도록 유도할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;낮은 temperature&lt;/b&gt;: 확률 분포를 뾰족하게 만들어 덜 창의적인(= 더 보수적인) 답변을 유도&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출처 인용 요청&lt;/b&gt;: &quot;답변마다 근거 문서를 인용하라&quot;고 지시하면 환각이 줄어드는 경향&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증 레이어&lt;/b&gt;: LLM 출력을 다른 LLM으로 검증하거나 외부 API로 팩트체크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환각은 현재 LLM의 구조적 한계라서 완전히 제거할 수 없다. 높은 정확성이 요구되는 도메인(법률&amp;middot;의료&amp;middot;금융)에서는 반드시 검증 레이어를 따로 두어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 함수 호출(Function Calling / Tool Use)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 외부 시스템과 연동하는 방법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 자체가 직접 API를 호출하는 게 아니라, &lt;b&gt;LLM이 &quot;어떤 함수를 어떤 인자로 호출해달라&quot;는 JSON을 출력&lt;/b&gt;하면, 그걸 받은 코드가 실제 함수를 실행하고 결과를 다시 LLM에 넘기는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;흐름:
사용자: &quot;현재 서울 날씨는?&quot;
 &amp;darr;
LLM 출력: { &quot;function&quot;: &quot;get_weather&quot;, &quot;args&quot;: { &quot;city&quot;: &quot;서울&quot; } }
 &amp;darr;
코드가 날씨 API 호출 &amp;rarr; &quot;맑음, 23&amp;deg;C&quot; 반환
 &amp;darr;
LLM: &quot;서울은 현재 맑고 23&amp;deg;C입니다.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴이 유용한 이유:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM이 학습 시점 이후의 실시간 데이터(날씨, 주가, 재고 등)에 접근 가능&lt;/li&gt;
&lt;li&gt;데이터베이스 조회, 외부 서비스 호출, 계산 실행 등 LLM이 직접 할 수 없는 작업을 연결&lt;/li&gt;
&lt;li&gt;도구(tool)를 여러 개 정의해두고 LLM이 상황에 따라 선택해서 쓰게 할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG도 넓은 의미에서 &quot;검색 도구를 LLM이 활용하는 패턴&quot;으로 볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. JSON 스키마: 출력 형식을 강제하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM의 출력은 기본적으로 자유로운 텍스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 시스템에서 LLM의 답을 코드로 처리하려면 형식이 고정돼 있어야 한다. &lt;br /&gt;그래서 프롬프트에 &quot;이 JSON 스키마 형태로만 답하라&quot;고 명시적으로 지시하고, 모델이 그 형식을 따르도록 유도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 형식을 어기고 설명을 덧붙이거나 마크다운 코드블록으로 감싸서 답하는 경우도 흔해서, &lt;br /&gt;실전에서는 응답을 받은 뒤 그런 잡음을 제거하고 JSON 부분만 추출하는 파싱 로직을 따로 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파싱이 실패하면(JSON이 아니거나 필드가 빠졌으면) 이를 &quot;신뢰할 수 없는 응답&quot;으로 간주하고 별도 처리(재시도, 에스컬레이션, 폴백)를 하는 게 일반적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 배치(batch)와 동시성(concurrency)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM API 호출은 비용과 시간이 든다. &lt;br /&gt;항목 하나하나마다 호출하면 비효율적이라, 여러 항목을 한 번의 프롬프트에 묶어서(배치) 한 번의 호출로 처리하는 방식을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 배치가 너무 크면 모델의 응답 시간이 길어지고, 일부 추론(reasoning) 특화 모델은 처리시간이 오래 걸려 네트워크 타임아웃에 걸릴 수 있다.&lt;br /&gt;그래서 배치 크기는 모델의 응답속도&amp;middot;타임아웃 제약을 보고 실전적으로 조정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 배치를 처리해야 한다면, 순차적으로 하나씩 호출하는 대신 &lt;b&gt;동시에 여러 개를 병렬로 호출&lt;/b&gt;(concurrency)해서 전체 처리시간을 줄인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 동시에 너무 많이 호출하면 API의 속도제한(rate limit)에 걸리므로, 동시 호출 개수에 상한을 두는 게 일반적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜스포머는 어텐션으로 토큰 간 관련도를 직접 계산.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인코더는 양방향 문맥을 보고, GPT 같은 생성 모델은 causal mask로 과거 토큰만 본다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LLM은 &quot;다음 토큰 확률분포&quot;를 소프트맥스로 만들고, temperature로 그 분포에서 어떻게 뽑을지를 조절&lt;/li&gt;
&lt;li&gt;temperature=0이어도 신경망 연산(문맥 이해, 확률분포 계산)은 그대로 수행됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;룰베이스와는 본질적으로 다른 메커니즘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;출력 형식을 강제(JSON 스키마)하고, 배치&amp;middot;동시성으로 비용&amp;middot;지연을 조절하는 게 실전 운영의 핵심&lt;/li&gt;
&lt;li&gt;한국어는 영어보다 토큰 수가 많아 비용&amp;middot;컨텍스트 윈도우 소진에 더 주의 필요&lt;/li&gt;
&lt;li&gt;컨텍스트 윈도우 한계 + &quot;lost in the middle&quot; 현상 &amp;rarr; RAG에서 청크 수를 과도하게 넣지 않는 이유&lt;/li&gt;
&lt;li&gt;환각(hallucination)은 LLM의 구조적 한계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RAG&amp;middot;낮은 temperature&amp;middot;검증 레이어로 완화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Few-shot 프롬프팅, Chain-of-thought, 함수 호출(tool use)은 LLM 활용도를 크게 높이는 핵심 기법&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 학습된 지식(모델 학습 시점까지의 데이터)만 갖고 있다. 회사 내부 문서나 최신 정책처럼 모델이 모르는 정보를 답변에 반영하려면, 검색을 LLM 호출 앞에 끼워넣어야 한다. 그 설계 패턴이 &lt;a href=&quot;https://chaeyami.tistory.com/286&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RAG&lt;/a&gt;다.&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>딥러닝</category>
      <category>신경망</category>
      <category>트랜스포머</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/285</guid>
      <comments>https://chaeyami.tistory.com/285#entry285comment</comments>
      <pubDate>Wed, 1 Jul 2026 15:59:14 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 벡터 임베딩(Vector Embedding) - 의미를 벡터로</title>
      <link>https://chaeyami.tistory.com/284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;cf) &lt;a href=&quot;https://chaeyami.tistory.com/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TF-IDF&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782884998308&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] TF-IDF(Term Frequency-Inverse Document Frequency)&quot; data-og-description=&quot;1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &amp;quot;중요도&amp;quot;를 가중치로 매기는 방법단순히 &amp;quot;몇 번 나왔는가&amp;quot;만 세는 빈도수 카운트(Bag of Words)의 한계를 보완쓰임문서 간&quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://chaeyami.tistory.com/278&quot; data-og-url=&quot;https://blog.chaenii.me/278&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yXy4b/dJMb87N7HsB/SvDjXkJPfgeNS5o0Kqnqh1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bztYJ9/dJMb9kmnlG9/vxI5FWKkFAknKQk4Qh0dr1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/cLiXEW/dJMb88GfWX2/kKCQs4Az4NVpuqHt91Gyj1/img.png?width=1957&amp;amp;height=1122&amp;amp;face=0_0_1957_1122&quot;&gt;&lt;a href=&quot;https://chaeyami.tistory.com/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chaeyami.tistory.com/278&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yXy4b/dJMb87N7HsB/SvDjXkJPfgeNS5o0Kqnqh1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bztYJ9/dJMb9kmnlG9/vxI5FWKkFAknKQk4Qh0dr1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/cLiXEW/dJMb88GfWX2/kKCQs4Az4NVpuqHt91Gyj1/img.png?width=1957&amp;amp;height=1122&amp;amp;face=0_0_1957_1122');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] TF-IDF(Term Frequency-Inverse Document Frequency)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &quot;중요도&quot;를 가중치로 매기는 방법단순히 &quot;몇 번 나왔는가&quot;만 세는 빈도수 카운트(Bag of Words)의 한계를 보완쓰임문서 간&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF도 텍스트를 벡터로 바꾼다. 그런데 TF-IDF는 순수하게 &quot;단어가 몇 번 등장했는가&quot;라는 통계에 기반한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;강아지&quot;와 &quot;개&quot;는 글자가 다르니 전혀 다른 차원으로 취급 &amp;rarr;&amp;nbsp;&lt;b&gt;의미가 같다는 것을 전혀 모른다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;임베딩(embedding): 신경망이 대량의 텍스트를 학습 &amp;rarr; &quot;이 단어가 어떤 맥락에서 쓰이는가&quot; 함께 학습&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;강아지&quot;와 &quot;개&quot;는 비슷한 문맥에서 자주 등장하므로 임베딩 공간에서 가까운 벡터가 된다.(TF-IDF는 절대 할 수 없는 일)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 분포 가설 (Distributional Hypothesis)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &quot;비슷한 맥락에서 등장하는 단어는 비슷한 의미를 가진다&quot;&lt;/span&gt; &lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임베딩이 작동하는 이론적 토대.&lt;/li&gt;
&lt;li&gt;&quot;나는 강아지를 키운다&quot;와 &quot;나는 개를 키운다&quot; &amp;rarr; 같은 맥락에서 한 단어만 다름.&lt;/li&gt;
&lt;li&gt;이런 패턴을 대량의 텍스트에서 통계적으로 학습하면, 신경망은 &quot;강아지&quot;와 &quot;개&quot;를 비슷한 벡터로 배치하게 된다.&lt;/li&gt;
&lt;li&gt;단어의 &quot;정의&quot;를 가르친 게 아니라, &quot;어떤 맥락에서 쓰이는지&quot;의 패턴만 학습해서 의미가 따라 나온 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q763O/dJMcabxZw1G/kEsiDoTBtfZg13x6kx70BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q763O/dJMcabxZw1G/kEsiDoTBtfZg13x6kx70BK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q763O/dJMcabxZw1G/kEsiDoTBtfZg13x6kx70BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ763O%2FdJMcabxZw1G%2FkEsiDoTBtfZg13x6kx70BK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 벡터 연산 &amp;rarr; 의미 연산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분포 가설이 잘 학습된 임베딩 공간에서는 벡터의 &lt;b&gt;덧셈&amp;middot;뺄셈이 의미상의 관계&lt;/b&gt;로 이어지는 성질이 나타난다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;e.g) king에서&amp;nbsp;man의&amp;nbsp;의미를&amp;nbsp;빼고&amp;nbsp;woman의&amp;nbsp;의미를&amp;nbsp;더하면&amp;nbsp;queen에&amp;nbsp;가까운&amp;nbsp;벡터가&amp;nbsp;나온다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\vec{v}(\text{king}) - \vec{v}(\text{man}) + \vec{v}(\text{woman}) \approx \vec{v}(\text{queen})&lt;br /&gt;$$&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;: &quot;king &amp;minus; man&quot;이라는 벡터 차이가 대략 &quot;왕족의 성별을 결정짓는 방향&quot; 같은 의미를 포착하고 있어서, 그 방향을 woman에 더하면 queen 쪽으로 이동한다는 직관이다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 가능한가&lt;/b&gt;: 임베딩이 학습되는 과정 자체가 &quot;비슷한 맥락&amp;rarr;비슷한 벡터&quot;라는 패턴을 통계적으로 누적한 결과라서, 의미적 관계가 벡터 공간상의 기하학적 관계(방향과 거리)로 자연스럽게 인코딩되기 때문이다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한계&lt;/b&gt;: 실제로는 항상 완벽하게 들어맞지는 않고, 모델&amp;middot;학습 데이터에 따라 정확도가 다르다. 그래도 임베딩이 &quot;의미&quot;를 다룬다는 걸 보여주는 상징적인 예시로 자주 인용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 임베딩이 만들어지는 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로는 신경망이 텍스트를 입력받아 고차원 벡터를 출력한다. 다만 &quot;임베딩이 만들어지는 방식&quot;은 모델 세대와 목적에 따라 조금씩 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Word2Vec 같은 초기 단어 임베딩은 주변 단어를 예측하거나, 주변 단어로 중심 단어를 예측하는 식으로 학습했다.&lt;/li&gt;
&lt;li&gt;BERT 계열은 문장 중 일부 토큰을 가리고 맞히는 masked language modeling을 통해 문맥 표현을 학습했다.&lt;/li&gt;
&lt;li&gt;RAG&amp;middot;의미 검색용 sentence embedding 모델은 &quot;관련 있는 질문-문서 쌍은 가깝게, 무관한 쌍은 멀게&quot; 만드는 대조학습(contrastive learning)을 많이 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통점은 하나다. 사람이 &quot;이 단어와 저 단어는 비슷하다&quot;고 직접 규칙을 적은 게 아니라, 대량의 데이터에서 &lt;b&gt;같이 쓰이는 패턴과 구별되어야 하는 패턴&lt;/b&gt;을 학습한 결과로 벡터 공간이 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 트랜스포머 구조(&lt;a href=&quot;https://chaeyami.tistory.com/285&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트랜스포머와 LLM&lt;/a&gt; 참고)를 깊이 몰라도, &quot;이 벡터는 신경망이 학습한 의미 표현&quot;이라는 결과물만 알고 써도 무방하다.&lt;br /&gt;실전에서는 이미 학습된 임베딩 모델을 API로 호출해서 쓰는 게 대부분이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차원(dimension)의 의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임베딩은 고정된 차원의 실수 벡터로 나온다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;e.g) OpenAI `text-embedding-3-small` = 1536차원, &amp;nbsp;`text-embedding-3-large` = 3072차원&lt;/li&gt;
&lt;li&gt;모델에 따라 `dimensions` 같은 파라미터로 더 짧은 벡터를 요청할 수도 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각 차원에 &quot;이건 동물 관련 정도&quot; 같은 명시적 의미는 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수천 개 숫자 전체가 조합되어 의미를 표현하는 &lt;b&gt;분산표현(distributed representation)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중요한 실전 규칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차원이 다른 임베딩 모델끼리는 직접 비교할 수 없다. &lt;br /&gt;e.g) 1536차원 벡터와 1024차원 벡터는 애초에 같은 공간에 있지 않으므로 코사인 유사도 계산 자체가 불가능하다.&lt;/li&gt;
&lt;li&gt;설령&amp;nbsp;차원이&amp;nbsp;같아도&amp;nbsp;서로&amp;nbsp;다른&amp;nbsp;모델이&amp;nbsp;만든&amp;nbsp;벡터는&amp;nbsp;좌표계&amp;nbsp;자체가&amp;nbsp;다르므로&amp;nbsp;의미&amp;nbsp;있는&amp;nbsp;비교가&amp;nbsp;어렵다.&lt;/li&gt;
&lt;li&gt;한 시스템 안에서는 반드시 같은 임베딩 모델을 일관되게 써야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유사도 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩끼리의&amp;nbsp;유사도는&amp;nbsp;거의&amp;nbsp;항상&amp;nbsp;&lt;a href=&quot;https://blog.chaenii.me/279#1.%20%EC%BD%94%EC%82%AC%EC%9D%B8%20%EC%9C%A0%EC%82%AC%EB%8F%84%3A%20%EB%B0%A9%ED%96%A5%EC%9D%B4%20%EB%B9%84%EC%8A%B7%ED%95%9C%EA%B0%80-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;코사인&amp;nbsp;유사도&lt;/a&gt;로&amp;nbsp;계산한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두&amp;nbsp;문장을&amp;nbsp;각각&amp;nbsp;임베딩하고&amp;nbsp;코사인&amp;nbsp;유사도를&amp;nbsp;구하면,&amp;nbsp;값이&amp;nbsp;높을수록&amp;nbsp;의미적으로&amp;nbsp;비슷한&amp;nbsp;문장이라는&amp;nbsp;뜻이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MgGdY/dJMcahEWbWV/u5FJFx1hjakbEmmOOkNQDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MgGdY/dJMcahEWbWV/u5FJFx1hjakbEmmOOkNQDk/img.png&quot; data-alt=&quot;코사인 유사도 N&amp;amp;times;N 히트맵&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MgGdY/dJMcahEWbWV/u5FJFx1hjakbEmmOOkNQDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMgGdY%2FdJMcahEWbWV%2Fu5FJFx1hjakbEmmOOkNQDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1254&quot; height=&quot;1254&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코사인 유사도 N&amp;times;N 히트맵&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;폴백(fallback)&amp;nbsp;임베더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 임베딩 모델을 쓸 때 생길 수 있는 문제:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 호출 비용이 든다&lt;/li&gt;
&lt;li&gt;네트워크&amp;middot;인증 문제로 실패할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런&amp;nbsp;상황에&amp;nbsp;대비해&amp;nbsp;&quot;의미는&amp;nbsp;없지만&amp;nbsp;항상&amp;nbsp;똑같은&amp;nbsp;결과를&amp;nbsp;내는&quot;&amp;nbsp;가짜&amp;nbsp;임베더를&amp;nbsp;폴백으로&amp;nbsp;두는&amp;nbsp;패턴이&amp;nbsp;있다.&amp;nbsp;&lt;br /&gt;e.g) 텍스트의 SHA-256 해시값을 반복해서 고정 길이 숫자 배열을 만들고 정규화한 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진짜&amp;nbsp;임베딩과의&amp;nbsp;차이:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 101px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;진짜 임베딩&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;풀백 임베더(해시 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;같은&amp;nbsp;텍스트&amp;nbsp;&amp;rarr;&amp;nbsp;같은&amp;nbsp;벡터&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;✓&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;비슷한&amp;nbsp;텍스트&amp;nbsp;&amp;rarr;&amp;nbsp;가까운&amp;nbsp;벡터&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;✓&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;API&amp;nbsp;비용&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;있음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;장애&amp;nbsp;가능성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;있음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쓰는 상황:&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발&amp;nbsp;단계에서&amp;nbsp;비용&amp;nbsp;없이&amp;nbsp;파이프라인을&amp;nbsp;테스트할&amp;nbsp;때&lt;/li&gt;
&lt;li&gt;실제 임베딩 API가 죽었을 때 시스템이 완전히 멈추지 않게 하는 안전장치&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 단어 임베딩 vs 문장 임베딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩에는 크게 두 계열이 있다.&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;단어 임베딩(Word Embedding)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Word2Vec, GloVe 같은 초기 모델.&lt;/li&gt;
&lt;li&gt;단어 하나에 고정된 벡터 하나를 대응시킨다.&lt;/li&gt;
&lt;li&gt;&quot;bank&quot;가 금융 문맥이든 강둑 문맥이든 항상 같은 벡터가 나온다.&lt;/li&gt;
&lt;li&gt;문맥에 따라 의미가 달라지는 다의어를 제대로 표현할 수 없다는 근본적 한계가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문장 임베딩(Sentence / Contextual Embedding)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BERT, sentence-transformers 같은 트랜스포머 기반 모델.&lt;/li&gt;
&lt;li&gt;입력 전체 문맥을 보고 각 단어의 벡터를 결정한다.&lt;/li&gt;
&lt;li&gt;&quot;은행 계좌를 열었다&quot;의 &quot;은행&quot;과 &quot;강 은행에 앉았다&quot;의 &quot;은행&quot;이 다른 벡터로 나온다.&lt;/li&gt;
&lt;li&gt;RAG, 의미 검색에서 쓰이는 임베딩은 거의 다 이 계열이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;단어&amp;nbsp;임베딩&amp;nbsp;(Word2Vec,&amp;nbsp;GloVe)&lt;/td&gt;
&lt;td&gt;문장 임베딩 (BERT, sentence-transformers)&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;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;RAG, 의미 검색, 클러스터링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 임베딩 파인튜닝&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 임베딩 모델을 &lt;b&gt;도메인 특화 데이터로 추가 학습(fine-tuning)&lt;/b&gt; 하면 성능이 크게 올라가는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g. 쇼핑몰 CS 데이터로 파인튜닝한 임베딩 모델&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;배송 지연&quot;과 &quot;출고 지체&quot;를 더 가깝게 배치한다 (도메인 동의어)&lt;/li&gt;
&lt;li&gt;&quot;환불&quot;과 &quot;취소&quot;를 더 명확히 구분할 수 있다 (맥락에 따라 다른 개념)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파인튜닝 방식:&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;대조 학습(contrastive learning)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;(질문, 정답 문서)&quot; 쌍은 가깝게, &quot;(질문, 관련 없는 문서)&quot; 쌍은 멀게 학습.&lt;/li&gt;
&lt;li&gt;주로 검색 시스템에 쓰인다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레이블 데이터 활용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분류 태그가 달린 데이터로 학습해서 같은 카테고리는 가깝게, 다른 카테고리는 멀게&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 다국어 임베딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어로 주로 학습된 일반 임베딩 모델은 &lt;b&gt;한국어 텍스트에서 성능이 낮을 수 있다&lt;/b&gt;. 특히:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한국어 형태소 분리(토크나이저가 한국어에 최적화되지 않은 경우)&lt;/li&gt;
&lt;li&gt;한국어 특유의 조사&amp;middot;어미 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위한 대안:&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;: multilingual-e5, paraphrase-multilingual-mpnet 같은 모델은 100개 이상 언어로 학습되어 한국어도 어느 정도 커버&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한국어 특화 모델&lt;/b&gt;: KLUE-BERT, KoSentenceBERT 등 한국어 코퍼스로 학습된 모델. 한국어 텍스트 분류&amp;middot;검색에는 이 쪽이 더 정확한 경우가 많음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한국어 포함 파인튜닝&lt;/b&gt;: 영어 기반 모델을 한국어 도메인 데이터로 파인튜닝&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임베딩은 신경망이 &quot;어떤 맥락에서 쓰이는가&quot;를 학습해서 만드는 의미 벡터 &amp;mdash; TF-IDF와 달리 동의어 관계를 포착함&lt;/li&gt;
&lt;li&gt;분포 가설(&quot;비슷한 맥락에서 등장하는 단어는 비슷한 의미를 가진다&quot;)이 이론적 토대&lt;/li&gt;
&lt;li&gt;벡터의 덧셈&amp;middot;뺄셈이 의미 관계로 이어지는 성질이 나타남(king&amp;minus;man+woman&amp;asymp;queen)&lt;/li&gt;
&lt;li&gt;차원이 다른 임베딩 모델끼리는 직접 비교 불가 &amp;mdash; 한 시스템 안에서는 모델을 통일해야 함&lt;/li&gt;
&lt;li&gt;유사도는 거의 항상 &lt;a href=&quot;https://blog.chaenii.me/279#1.%20%EC%BD%94%EC%82%AC%EC%9D%B8%20%EC%9C%A0%EC%82%AC%EB%8F%84%3A%20%EB%B0%A9%ED%96%A5%EC%9D%B4%20%EB%B9%84%EC%8A%B7%ED%95%9C%EA%B0%80-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;코사인 유사도&lt;/a&gt;로 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩은&amp;nbsp;&quot;텍스트&amp;rarr;벡터&quot;&amp;nbsp;변환만&amp;nbsp;하고&amp;nbsp;끝난다.&amp;nbsp;새로운&amp;nbsp;텍스트를&amp;nbsp;생성하지는&amp;nbsp;않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면&amp;nbsp;같은&amp;nbsp;트랜스포머&amp;nbsp;계열이지만&amp;nbsp;&quot;다음에&amp;nbsp;올&amp;nbsp;단어를&amp;nbsp;예측해서&amp;nbsp;텍스트를&amp;nbsp;생성하는&quot;&amp;nbsp;모델이&amp;nbsp;LLM이다.&amp;nbsp;&amp;rarr;&amp;nbsp;트랜스포머와&amp;nbsp;LLM&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>딥러닝</category>
      <category>벡터임베딩</category>
      <category>임베딩</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/284</guid>
      <comments>https://chaeyami.tistory.com/284#entry284comment</comments>
      <pubDate>Wed, 1 Jul 2026 15:28:45 +0900</pubDate>
    </item>
    <item>
      <title>[ML] SOM(Self-Organizing Map) - 자기조직화지도</title>
      <link>https://chaeyami.tistory.com/283</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. SOM?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Self-Organizing Map. 입력 데이터를 2차원 격자 위에 스스로 배치하도록 학습하는 &lt;span data-token-index=&quot;1&quot;&gt;&lt;b&gt;신경망 구조의 고전적 비지도학습 알고리즘&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적은 &lt;a href=&quot;https://chaeyami.tistory.com/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KMeans&lt;/a&gt;와 같지만(데이터를 비슷한 그룹으로 묶기), 중심점들이 고정된 격자 위에서 이웃과 함께 움직인다는 점이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 SOM을 보통 CNN/Transformer 같은 현대적 의미의 &quot;딥러닝&quot;으로 분류하지는 않는다는 것이다. &lt;br /&gt;여러 층을 깊게 쌓은 모델이라기보다, 경쟁학습(competitive learning)을 사용하는 얕은 신경망에 가깝다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. K-Means와 다른 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KMeans
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$K$개의 중심점이 공간에서 자유롭게 독립적으로 움직임.&lt;/li&gt;
&lt;li&gt;중심점 5번이 어디로 가든 50번에게 영향 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SOM
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드들이 고정된 격자(grid) 위에 배치&lt;/li&gt;
&lt;li&gt;학습 중 한 노드가 움직이면 격자에서 가까운 이웃 노드도 같이 끌려감&lt;/li&gt;
&lt;li&gt;SOM은 &quot;이웃 관계&quot;라는 제약을 처음부터 깔고 학습한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, KMeans는 70명이 각자 자유롭게 움직이며 자기 자리를 찾는 것. SOM은 70명이 미리 정해진 10&amp;times;7 격자 위에 서서, 한 사람이 움직이면 손을 잡고 있는 양옆 사람도 같이 끌려오는 것.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tWjtC/dJMcadCso4I/Hdx88585kAyoBK6ilUphN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tWjtC/dJMcadCso4I/Hdx88585kAyoBK6ilUphN1/img.png&quot; data-alt=&quot;K-Means와 SOM의 차이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tWjtC/dJMcadCso4I/Hdx88585kAyoBK6ilUphN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtWjtC%2FdJMcadCso4I%2FHdx88585kAyoBK6ilUphN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;K-Means와 SOM의 차이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 알고리즘&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기화&lt;/b&gt;: 가중치 행렬(각 격자 노드가 갖는 벡터)을 무작위 값으로 초기화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BMU(Best Matching Unit) 탐색&lt;/b&gt;: 입력 벡터 하나를 보고, 격자의 모든 노드 중 가장 가까운(거리가 가장 작은) 노드를 찾음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가중치 갱신&lt;/b&gt;: BMU와 그 이웃 노드들의 가중치를 입력 벡터 방향으로 이동&lt;/li&gt;
&lt;li&gt;모든 입력에 대해 2~3을 반복, 학습률을 점점 줄여가며 여러 epoch 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 수식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;거리 계산 (BMU 탐색)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;d_j = \sqrt{\sum_i (x_i - w_{ij})^2}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 벡터 $x$와 노드 $j$의 가중치 벡터 $w_j$ 사이의 유클리드 거리. 가장 작은 $d_j$를 가진 노드가 BMU.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;가중치 업데이트&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;w_{new} = w_{old} + \alpha (x - w_{old})&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$(x - w_{old})$는 현재 가중치에서 입력 방향으로 향하는 벡터. 학습률 $\alpha$를 곱해서 그 방향으로 일부만 이동(전부 이동하면 $\alpha=1$). $\alpha$는 학습이 진행될수록 점점 작아진다 &amp;mdash; 처음엔 크게 움직이고 나중엔 미세조정만.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;이웃함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;h(j, \text{BMU}) = \exp\left(-\frac{d_{\text{grid}}^2}{2\sigma^2}\right)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$d_{\text{grid}}$는 격자 위에서 노드 $j$가 BMU로부터 얼마나 떨어져 있는지(격자 거리, 유클리드 거리와는 다른 개념). $\sigma$(시그마)는 이웃 반경을 조절하는 값. 가우시안 함수라서 BMU 자신($d_{\text{grid}}=0$)은 $h=1$(100% 이동), 격자에서 멀어질수록 $h \to 0$(거의 안 움직임).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 업데이트 식은 이웃함수가 곱해진 형태가 된다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;w_{new} = w_{old} + \alpha \cdot h(j,\text{BMU}) \cdot (x - w_{old})&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$\sigma$가 클수록 더 넓은 영역의 이웃이 함께 끌려온다. 학습 후반부에는 $\sigma$도 점점 줄여서(이웃 반경을 좁혀서) 미세조정 단계로 넘어가는 구현이 많다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;969&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MXJyK/dJMcabxZwQi/Qoexe4PtaPLHCzocLnGCK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MXJyK/dJMcabxZwQi/Qoexe4PtaPLHCzocLnGCK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MXJyK/dJMcabxZwQi/Qoexe4PtaPLHCzocLnGCK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMXJyK%2FdJMcabxZwQi%2FQoexe4PtaPLHCzocLnGCK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1624&quot; height=&quot;969&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;969&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;학습률과 $\sigma$가 시간에 따라 줄어드는 방식&lt;/b&gt;은 보통 지수적으로 감소시킨다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\alpha(t) = \alpha_0 \exp\left(-\frac{t}{\tau_\alpha}\right), \qquad \sigma(t) = \sigma_0 \exp\left(-\frac{t}{\tau_\sigma}\right)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$t$는 현재까지 진행된 학습 스텝(또는 epoch), $\tau$는 감쇠 속도를 조절하는 상수.&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;($t$가 작을 때): $\alpha$, $\sigma$가 커서 큰 폭으로, 넓은 이웃까지 움직이며 전체적인 큰 구조를 빠르게 잡음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습 후반&lt;/b&gt;($t$가 커질수록): 두 값이 작아지면서 점점 더 좁고 미세한 조정만 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans의&amp;nbsp;여러&amp;nbsp;초기화&amp;nbsp;실험처럼,&amp;nbsp;SOM에서도&amp;nbsp;&quot;초반엔&amp;nbsp;넓게&amp;nbsp;탐색,&amp;nbsp;후반엔&amp;nbsp;좁게&amp;nbsp;미세조정&quot;하는&amp;nbsp;패턴이&amp;nbsp;중요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만&amp;nbsp;KMeans의&amp;nbsp;`n_init`은&amp;nbsp;여러&amp;nbsp;초기값을&amp;nbsp;독립적으로&amp;nbsp;시도하는&amp;nbsp;장치이고,&amp;nbsp;&lt;br /&gt;SOM의&amp;nbsp;학습률&amp;middot;이웃반경&amp;nbsp;감소는&amp;nbsp;한&amp;nbsp;번의&amp;nbsp;학습&amp;nbsp;과정&amp;nbsp;안에서&amp;nbsp;탐색&amp;nbsp;범위를&amp;nbsp;줄여가는&amp;nbsp;장치라는&amp;nbsp;차이가&amp;nbsp;있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 결과: 위상 보존(topology preservation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습이 끝나면 &lt;b&gt;격자에서 가까운 노드는 비슷한 데이터를 대표하게 된다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; SOM의 가장 큰 특징이자 K-Means와의 본질적 차이.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K-Means는 군집 5번과 군집 6번이 번호만 인접할 뿐 내용은 전혀 무관해도 된다.&lt;/li&gt;
&lt;li&gt;SOM은 격자 위 (2,3) 노드와 (2,4) 노드가 항상 서로 비슷한 의미를 갖는다. &amp;rarr; 위상 보존&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 시각화: U-matrix(Unified Distance Matrix)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 위상 보존 덕분에 SOM은 학습 후 시각화가 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;10&amp;times;7&amp;nbsp;격자&amp;nbsp;위에&amp;nbsp;U-matrix(Unified Distance Matrix): 격자 위 인접한 두 노드의 가중치 벡터 사이 거리를 계산해서, 그 거리를 색(밝기)으로 칠한 지도를 만드는 것.&lt;/li&gt;
&lt;li&gt;인접 노드끼리 가중치가 비슷하면(거리가 가까우면) 밝게, 가중치가 크게 다르면(군집 경계) 어둡게 칠한다.&lt;/li&gt;
&lt;li&gt;(KMeans는 중심점들이 서로 독립적이라 이런 &quot;이웃 간 거리 지도&quot;라는 개념 자체가 성립하지 않는다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0I1S5/dJMcabdEvsr/8HMcMFdPeevTVds0HeFf21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0I1S5/dJMcabdEvsr/8HMcMFdPeevTVds0HeFf21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0I1S5/dJMcabdEvsr/8HMcMFdPeevTVds0HeFf21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0I1S5%2FdJMcabdEvsr%2F8HMcMFdPeevTVds0HeFf21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. SOM의 약점: 빈 노드(empty cluster)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격자가 &lt;b&gt;고정&lt;/b&gt;되어 있다는 게 장점이자 약점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터가 70개의 균등한 군집으로 자연스럽게 나뉘지 않는다면, &lt;br /&gt;격자의 일부 위치는 어떤 데이터와도 가장 가까운 노드가 되지 못하고 텅 비게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K-Means는 중심점이 자유롭게 이동해서 데이터가 있는 곳으로 따라가지만&lt;br /&gt;SOM은 격자 모양이 고정이라 일부 칸이 비어버릴 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;e.g) 6만 건의 텍스트 데이터를 70개 군집/노드로 나눌 때&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;지표&lt;/td&gt;
&lt;td&gt;K-Means&lt;/td&gt;
&lt;td&gt;SOM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silhouette Score&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;p data-ke-size=&quot;size16&quot;&gt;데이터가 70개 군집에 고르게 나뉘지 않는 경우,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;격자가 고정된 SOM: 일부 노드가 통째로 비어버림(격자 제약 때문에 노드를 자유롭게 줄일 수 없음)&lt;/li&gt;
&lt;li&gt;자유롭게 움직이는 KMeans는 빈 군집이 거의 안 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp; &lt;b&gt;그냥 군집 품질만 중요하고 위상 보존은 필요 없는&lt;/b&gt; 상황이라면 K-Means가 더 효율적인 경우가 많다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SOM은 격자 위에서 이웃 노드끼리 함께 끌려가며 학습하는 신경망 기반 클러스터링&lt;/li&gt;
&lt;li&gt;KMeans와 목적(클러스터링)은 같지만, 중심점이 서로 독립적인 KMeans와 달리 SOM은 &quot;이웃 관계&quot;라는 제약을 깔고 학습함&lt;/li&gt;
&lt;li&gt;학습률&amp;middot;이웃반경($\sigma$)을 점점 줄여가며 큰 구조 &amp;rarr; 미세조정 순서로 수렴&lt;/li&gt;
&lt;li&gt;위상 보존 덕분에 U-matrix 같은 시각화가 가능하지만, 격자가 고정돼 있어 빈 노드가 생길 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOM에서&amp;nbsp;&quot;신경망의&amp;nbsp;가중치가&amp;nbsp;학습된다&quot;는&amp;nbsp;개념을&amp;nbsp;봤다면,&amp;nbsp;&lt;br /&gt;그 다음 자연스러운 질문은 &quot;그럼 텍스트의 '의미'를 신경망으로 어떻게 벡터화하는가&quot;다. &amp;rarr; 벡터 임베딩.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOM은 TF-IDF 벡터를 입력으로 받아 클러스터링했지만, 임베딩은 그 자체로 신경망이 &quot;의미&quot;를 학습해서 만들어내는 벡터라는 점에서 한 단계 더 나간 개념이다.&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>Som</category>
      <category>딥러닝</category>
      <category>자기조직화지도</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/283</guid>
      <comments>https://chaeyami.tistory.com/283#entry283comment</comments>
      <pubDate>Wed, 1 Jul 2026 14:43:05 +0900</pubDate>
    </item>
    <item>
      <title>[ML] SVD (Singular Value Decomposition) / 차원축소</title>
      <link>https://chaeyami.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는&amp;nbsp;텍스트(고차원&amp;nbsp;희소&amp;nbsp;벡터)를&amp;nbsp;그대로&amp;nbsp;클러스터링했다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[STUDY/ML(Machine Learning)] - [ML] K-means clustering&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782882548670&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] K-means clustering&quot; data-og-description=&quot;1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(centroid)을 그 군집에 속한 데이터들의 평균(mean) 위치로 잡아가면서 군집을 &quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/280&quot; data-og-url=&quot;https://blog.chaenii.me/280&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c6dZet/dJMb9frQcGB/udX8GkpwQvI1UCKspp41y0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/q8Lea/dJMb8UH0asq/Zft5UE61PYBTELOqFYt3N1/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/BXg9A/dJMb8WeKIhK/gXFJIUkaqyNEHOdSIiq3gK/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/280&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c6dZet/dJMb9frQcGB/udX8GkpwQvI1UCKspp41y0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/q8Lea/dJMb8UH0asq/Zft5UE61PYBTELOqFYt3N1/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/BXg9A/dJMb8WeKIhK/gXFJIUkaqyNEHOdSIiq3gK/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] K-means clustering&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(centroid)을 그 군집에 속한 데이터들의 평균(mean) 위치로 잡아가면서 군집을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[STUDY/ML(Machine Learning)] - [ML] 실루엣 스코어(Silhouette Score) - 군집화 평가 지표&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782882553983&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] 실루엣 스코어(Silhouette Score) - 군집화 평가 지표&quot; data-og-description=&quot;1. Silhouette Score?[STUDY/ML(Machine Learning)] - [ML] K-means clustering [ML] K-means clustering1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(c&quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/281&quot; data-og-url=&quot;https://blog.chaenii.me/281&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c3UCJ9/dJMb9b32Oor/YPvLKcMt2hsXyewSgWNXqK/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/blRfoT/dJMb9ia1VfJ/upKBwkk95mFqmRUhLC5qL1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bgdvbw/dJMb9bwc2zf/GGCZoLMsbYPQtfqpRFpOk0/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/281&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c3UCJ9/dJMb9b32Oor/YPvLKcMt2hsXyewSgWNXqK/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/blRfoT/dJMb9ia1VfJ/upKBwkk95mFqmRUhLC5qL1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bgdvbw/dJMb9bwc2zf/GGCZoLMsbYPQtfqpRFpOk0/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] 실루엣 스코어(Silhouette Score) - 군집화 평가 지표&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. Silhouette Score?[STUDY/ML(Machine Learning)] - [ML] K-means clustering [ML] K-means clustering1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(c&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신경망 기반 클러스터링(SOM)으로 넘어가기 전에, &lt;br /&gt;고차원 데이터를 다루기 쉬운 저차원으로 줄이는 SVD/차원축소를 먼저 알아야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SVD(특이값분해)?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans, 임베딩 같은 글에서 데이터를 계속 &quot;벡터&quot;와 &quot;행렬&quot;로 다루는데,&lt;br /&gt;행렬을 다루다 보면 &quot;이 복잡한 행렬을 더 단순한 형태로 쪼개고 싶다&quot;는 상황이 자주 온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 &lt;b&gt;행렬분해&lt;/b&gt;(matrix decomposition)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;SVD&lt;/b&gt;(Singular Value Decomposition, 특이값분해)는 그중 가장 널리 쓰이는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVD/차원축소에서 이걸 어떻게 응용하는지 다루기 전에, SVD 자체가 뭘 하는 건지부터 짚고 간다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;행렬 - 공간을 바꾸는 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터에 행렬을 곱하면 그 벡터는 다른 위치로 움직인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 예를 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평면 위의 점 하나에 어떤 2&amp;times;2 행렬을 곱하면 그 점은 회전하거나 늘어나거나 찌그러진 위치로 이동한다. &lt;br /&gt;이런 식으로 평면 전체&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(원 모양으로 늘어선 점들이라고 해보자)&lt;/span&gt;에 같은 행렬을 곱하면&lt;br /&gt;원은 보통 한쪽으로 길쭉하게 늘어난 타원이 되고, 방향도 돌아간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAPkOQ/dJMcabrgQR9/ZK2fV3G8L7XI57jvoVHkhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAPkOQ/dJMcabrgQR9/ZK2fV3G8L7XI57jvoVHkhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAPkOQ/dJMcabrgQR9/ZK2fV3G8L7XI57jvoVHkhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAPkOQ%2FdJMcabrgQR9%2FZK2fV3G8L7XI57jvoVHkhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;행렬 = 공간을 회전시키거나 늘리거나 줄이는 변환&lt;/span&gt;&lt;/b&gt;이라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SVD&lt;/b&gt;는 &lt;b&gt;&quot;이 변환이 정확히 어떤 회전과 어떤 늘이기로 이루어져 있는지&quot;를 분해해서 보여주는 도구&lt;/b&gt;다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;SVD의 핵심 아이디어: 회전 &amp;rarr; 확대 &amp;rarr; 회전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 복잡해 보이는 행렬 변환이라도, 항상 다음 세 단계로 쪼갤 수 있다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;X = U \Sigma V^T&lt;br /&gt;$$&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;$V^T$ (회전)&lt;/b&gt;: 먼저 입력 공간을 한 번 회전시켜 &quot;가장 많이 늘어나는 방향&quot;이 좌표축에 딱 맞게 정렬되도록 만든다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;$\Sigma$ (확대/축소)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그렇게 정렬된 축들을 따라 각각 다른 비율로 늘리거나 줄인다.&lt;/li&gt;
&lt;li&gt;이 비율들이 대각선에 놓이는데, 이게 바로 &lt;b&gt;특이값&lt;/b&gt;(singular value)이다. (큰 것부터 순서대로 나열됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;$U$ (다시 회전)&lt;/b&gt;: 마지막으로 결과를 다시 한 번 회전시켜서 최종 위치로 옮긴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유하면: 종이를 한 번 돌리고, 가로세로 비율을 다르게 늘렸다가, 다시 한 번 돌리는 것. &lt;br /&gt;이 세 동작만으로 어떤 모양의 변형이든 표현할 수 있다는 게 SVD의 핵심 주장이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭행렬&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(transpose해도 자기 자신과 같은 행렬)&lt;/span&gt;을 예로 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;A = \begin{pmatrix} 3 &amp;amp; 1 \\ 1 &amp;amp; 3 \end{pmatrix}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 행렬의 고유값(eigenvalue)을 구하면 $\lambda = 4$ 또는 $\lambda = 2$가 나오고&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(특성방정식 $(3-\lambda)^2 - 1 = 0$을 풀면 됨)&lt;/span&gt;, &lt;br /&gt;각 고유값에 대응하는 고유벡터를 정규화하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Q = \begin{pmatrix} \frac{1}{\sqrt2} &amp;amp; \frac{1}{\sqrt2} \\ \frac{1}{\sqrt2} &amp;amp; -\frac{1}{\sqrt2} \end{pmatrix}, \qquad \Sigma = \begin{pmatrix} 4 &amp;amp; 0 \\ 0 &amp;amp; 2 \end{pmatrix}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭행렬인 경우 $U = V = Q$가 되어서, $A = Q \Sigma Q^T$로 분해된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 $Q \Sigma Q^T$를 계산해보면 정확히 원래 $A$로 돌아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &quot;45도 방향으로는 4배 늘리고, 그 수직 방향으로는 2배 늘리는 변환&quot;이 바로 행렬 $A$의 정체였다는 뜻.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;주의: 이 예시는 대칭행렬이라서 $U=V$로 단순해졌을 뿐, 일반적인 행렬(특히 정사각형이 아닌 행렬)에서는 $U$와 $V$가 서로 다른 행렬이다.&lt;/blockquote&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;특이값이 크다?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특이값&lt;/b&gt;은 &quot;&lt;b&gt;그 방향으로 얼마나 많이 늘어나는가&lt;/b&gt;&quot;를 나타낸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특이값이 큰 방향: 원본 데이터에서 변동이 가장 큰(=가장 중요한 정보를 담은) 방향&lt;/li&gt;
&lt;li&gt;특이값이 작은 방향: 거의 안 늘어나는, 미세한 노이즈에 가까운 방향&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 성질 때문에 &quot;특이값이 작은 방향은 버려도 정보 손실이 적다&quot;는 아이디어가 나오고,&lt;br /&gt;이게 바로 SVD를 이용한 차원축소의 출발점이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;고유값분해와의 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대칭행렬의 고유값분해(eigendecomposition) - 정사각 행렬에서만 가능&lt;/li&gt;
&lt;li&gt;반면 SVD - 행과 열의 개수가 달라도(m \times n, m \ne n이어도) 항상 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; TF-IDF처럼 보통 정사각형이 아닌&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(문서 수 &amp;times; 단어 종류 수)&lt;/span&gt; 행렬에도 SVD는 바로 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 머신러닝 파이프라인에서 고유값분해 대신 SVD를 쓰는 실용적인 이유.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체&amp;nbsp;SVD와&amp;nbsp;얇은&amp;nbsp;SVD&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식으로는&amp;nbsp;$X&amp;nbsp;=&amp;nbsp;U\Sigma&amp;nbsp;V^T$라고&amp;nbsp;쓰지만,&amp;nbsp;&lt;br /&gt;실전에서는&amp;nbsp;전체&amp;nbsp;$U$,&amp;nbsp;전체&amp;nbsp;$V$를&amp;nbsp;모두&amp;nbsp;만들지&amp;nbsp;않는&amp;nbsp;경우가&amp;nbsp;많다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 문서-단어 행렬처럼 행과 열이 큰 직사각형 행렬에서는 필요한 상위 성분만 계산하는 &lt;b&gt;thin SVD&lt;/b&gt; 또는 &lt;b&gt;truncated SVD&lt;/b&gt; 를 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념은 같지만, 메모리와 속도 때문에 &quot;필요한 방향만 뽑아 쓴다&quot;고 보면 된다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;SVD 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;행렬은 &quot;공간을 회전&amp;middot;확대&amp;middot;축소하는 변환&quot;으로 볼 수 있다&lt;/li&gt;
&lt;li&gt;SVD는 어떤 행렬이든 &quot;회전($V^T$) &amp;rarr; 축별로 다르게 늘이기($\Sigma$) &amp;rarr; 다시 회전($U$)&quot;의 세 단계로 쪼개는 분해&lt;/li&gt;
&lt;li&gt;특이값(대각선 값)은 각 방향으로 얼마나 늘어나는지를 나타내고, 클수록 더 중요한 변동 방향&lt;/li&gt;
&lt;li&gt;정사각 행렬에만 되는 고유값분해와 달리, SVD는 어떤 모양의 행렬에도 항상 존재한다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SVD가 뭘 하는지 알았다면, 이제 이걸 실제로 어떻게 써먹는지, &lt;br /&gt;즉 고차원 데이터를 저차원으로 압축하는 데 어떻게 쓰이는지 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF로 텍스트를 벡터화하면 차원이 수천~수만이 된다 (단어 종류만큼). 이런 고차원 희소(sparse) 벡터는:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;계산이 무겁다: 차원이 클수록 거리 계산, 행렬 연산이 느려짐&lt;/li&gt;
&lt;li&gt;일부 알고리즘은 &lt;b&gt;희소행렬을 직접 못 다룬다&lt;/b&gt;: 예를 들어 SOM은 내부적으로 dense(밀집) 벡터 연산을 전제로 짜여 있어서, 수천 차원 희소행렬을 그대로 넣으면 메모리&amp;middot;속도 문제가 생긴다&lt;/li&gt;
&lt;li&gt;&quot;차원의 저주(curse of dimensionality)&quot;: 차원이 높아질수록 거리 개념 자체가 무의미해지는 경향이 있다 (모든 점이 서로 비슷하게 멀어짐)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결책: 정보를 최대한 보존하면서 차원을 확 줄인다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SVD의 핵심 아이디어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의의 행렬 $X$ (크기 $m \times n$)는 다음과 같이 분해된다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;X = U \Sigma V^T&lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$U$: $m \times m$ 직교행렬 (좌측 특이벡터)&lt;/li&gt;
&lt;li&gt;$\Sigma$: $m \times n$ 대각행렬, 대각선에 특이값(singular value)들이 큰 것부터 순서대로 나열&lt;/li&gt;
&lt;li&gt;$V^T$: $n \times n$ 직교행렬 (우측 특이벡터)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 핵심: 특이값이 큰 순서대로 정렬되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특이값이 클수록 그 방향(성분)이 원본 데이터의 &quot;주요한 변동&quot;을 더 많이 설명한다.&lt;/b&gt; &lt;br /&gt;작은 특이값에 대응하는 방향은 노이즈에 가까운 미세한 변동일 가능성이 높다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TruncatedSVD: 상위 k개만 남기기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 $\Sigma$를 다 쓰지 않고, 가장 큰 특이값 $k$개만 남기고 나머지를 버린다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;X \approx U_k \Sigma_k V_k^T&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 원본을 $n$차원에서 $k$차원으로 압축한 근사치를 얻는다. &lt;br /&gt;$k$를 원본보다 훨씬 작게 잡으면(예: 8000차원 &amp;rarr; 100차원) 정보 손실은 있지만 대부분의 &quot;중요한 변동&quot;은 보존된 채로 차원이 확 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;이게 &quot;최선의&quot; 근사라는 보장이 있는가?&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에카르트-영 정리(Eckart&amp;ndash;Young theorem)에 의하면, 상위 $k$개의 특이값만으로 만든 $U_k\Sigma_k V_k^T$는 원본 행렬 $X$와의 차이(프로베니우스 노름 기준 재구성 오차)를 최소화하는, &lt;b&gt;랭크(rank)가 $k$인 행렬 중에서 가장 가까운 행렬&lt;/b&gt;이다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;U_k\Sigma_k V_k^T = \arg\min_{\text{rank}(B)=k} \|X - B\|_F&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &quot;차원을 $k$개로 줄인다&quot;는 제약 안에서 임의로 잘 줄인 게 아니라, 수학적으로 증명 가능한 최적해라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans처럼 반복해서 수렴시키는 방식이 아니라, 한 번의 행렬분해로 바로 최적해가 나오는 &quot;닫힌 형태(closed-form) 해&quot;라는 점이 KMeans와의 중요한 차이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scikit-learn의 TruncatedSVD(n_components=k)가 이 작업을 한다. PCA(주성분분석)와 매우 비슷한 개념인데, PCA는 데이터를 먼저 평균중심화(centering)하는 반면 TruncatedSVD는 희소행렬에 그대로 적용 가능하다는 실전적 차이가 있다 (평균중심화를 하면 희소행렬이 dense가 되어버려서 메모리 이점이 사라짐).&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정보 손실의 트레이드오프&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$k$를 어떻게 잡느냐에 따라 트레이드오프가 갈린다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$k$를 작게 잡으면: 계산은 빨라지지만 정보 손실이 커짐&lt;/li&gt;
&lt;li&gt;$k$를 크게 잡으면: 정보는 더 보존되지만 차원축소의 이점이 줄어듦&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;얼마나 정보를 보존하는가&quot;는 특이값의 제곱이 전체 분산에서 차지하는 비율로 정량화할 수 있다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$i$번째&amp;nbsp;특이값을&amp;nbsp;$\sigma_i$라&amp;nbsp;하면,&amp;nbsp;상위&amp;nbsp;$k$개를&amp;nbsp;썼을&amp;nbsp;때의&amp;nbsp;누적&amp;nbsp;비율은:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\frac{\sum_{i=1}^{k}&amp;nbsp;\sigma_i^2}{\sum_{i=1}^{r}&amp;nbsp;\sigma_i^2}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PCA에서는 이 값을 보통 &lt;b&gt;누적 분산 설명비율&lt;/b&gt;이라고 부르고, &quot;원본 분산의 몇 %를 보존했는가&quot;로 해석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, `TruncatedSVD`는 PCA와 달리 데이터를 평균중심화(centering)하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 TF-IDF 같은 희소 행렬에 바로 적용할 수 있지만, &lt;br /&gt;이 비율을 PCA에서처럼 완전히 같은 의미의 &quot;분산 설명비율&quot;로 해석하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는 이 값을 &lt;b&gt;원본 행렬의 주요 구조/에너지를 얼마나 담았는지 보는 참고 지표&lt;/b&gt;로 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 0.9 근처에서 곡선이 완만해진다면 &quot;상위 성분들이 대부분의 큰 구조를 담고 있다&quot;고 보고, &lt;br /&gt;이후 단계(SOM/KMeans)의 계산 비용과 성능을 함께 보면서 $k$를 정한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g: &quot;8000차원 TF-IDF 벡터를 100차원으로 줄인다&quot;는 결정도 이런 과정을 거쳐 나온 값이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzI84s/dJMcajiwCgQ/d2TcakwgDNGwkOKWSKnQwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzI84s/dJMcajiwCgQ/d2TcakwgDNGwkOKWSKnQwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzI84s/dJMcajiwCgQ/d2TcakwgDNGwkOKWSKnQwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzI84s%2FdJMcajiwCgQ%2Fd2TcakwgDNGwkOKWSKnQwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doXGsx/dJMcabLD26r/Dt0uNv4AKz65A6ucJ6U8f1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doXGsx/dJMcabLD26r/Dt0uNv4AKz65A6ucJ6U8f1/img.png&quot; data-alt=&quot;고차원 데이터를 SVD로 2차원까지 줄였다고 가정한 산점도(scatter plot)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doXGsx/dJMcabLD26r/Dt0uNv4AKz65A6ucJ6U8f1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoXGsx%2FdJMcabLD26r%2FDt0uNv4AKz65A6ucJ6U8f1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고차원 데이터를 SVD로 2차원까지 줄였다고 가정한 산점도(scatter plot)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SVD의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVD는 데이터의 &lt;b&gt;선형 구조&lt;/b&gt;만 포착한다. 행렬 분해 자체가 선형 연산이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 나선형&amp;middot;원형&amp;middot;S자 같은 비선형 구조로 분포해 있다면, SVD로 차원을 줄여도 그 구조가 보존되지 않는다.&lt;br /&gt;2차원으로 줄였더니 원래는 분리돼 있던 군집들이 섞여버리는 경우가 생길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우를 위한 &lt;b&gt;비선형 차원축소&lt;/b&gt; 방법들이 있다:&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;t-SNE(t-distributed Stochastic Neighbor Embedding)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가까운 점들끼리의 거리 관계를 보존하면서 저차원으로 압축.&lt;/li&gt;
&lt;li&gt;비선형 구조를 잘 잡아서 &lt;b&gt;시각화(2~3차원)&lt;/b&gt; 목적으로 주로 쓰인다.&lt;/li&gt;
&lt;li&gt;단, 축소된 공간의 전역적 거리 관계는 보존되지 않고, 랜덤성이 있어서 실행마다 결과가 달라질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UMAP(Uniform Manifold Approximation and Projection)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;t-SNE보다 빠르고 전역 구조도 어느 정도 보존.&lt;/li&gt;
&lt;li&gt;마찬가지로 시각화와 탐색 목적으로 쓰인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요한 차이&lt;/b&gt;: t-SNE/UMAP은 학습(훈련)에 쓰기보다 &lt;b&gt;탐색&amp;middot;시각화&lt;/b&gt; 도구에 가깝다. SVD와 달리 새로운 데이터 포인트를 기존 학습 공간에 투영하는 게 어렵고(out-of-sample 문제), 결과가 확정적이지 않아서 모델 입력으로 쓰기엔 불안정하다. 반면 SVD/PCA는 학습 후 새 데이터를 동일하게 변환할 수 있어서 ML 파이프라인에 안정적으로 붙일 수 있다.&lt;/p&gt;
&lt;table id=&quot;390c2707-14d8-8063-aefc-dc316f168d94&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SVD&amp;nbsp;/&amp;nbsp;PCA&lt;/td&gt;
&lt;td&gt;t-SNE&amp;nbsp;/&amp;nbsp;UMAP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80e5-b0d5-f533aa5ff6d8&quot;&gt;
&lt;td id=&quot;trKi&quot;&gt;구조&lt;/td&gt;
&lt;td id=&quot;T~\n&quot;&gt;선형&lt;/td&gt;
&lt;td id=&quot;lS~}&quot;&gt;비선형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8089-8e61-df424e6860cd&quot;&gt;
&lt;td id=&quot;trKi&quot;&gt;새 데이터 투영&lt;/td&gt;
&lt;td id=&quot;T~\n&quot;&gt;✓ 안정적&lt;/td&gt;
&lt;td id=&quot;lS~}&quot;&gt;△ 어렵거나 불안정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8034-8ce1-ca0f88761ed5&quot;&gt;
&lt;td id=&quot;trKi&quot;&gt;주요 용도&lt;/td&gt;
&lt;td id=&quot;T~\n&quot;&gt;ML 파이프라인 전처리&lt;/td&gt;
&lt;td id=&quot;lS~}&quot;&gt;데이터 탐색&amp;middot;시각화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-8050-a2b2-efa28dbef6ac&quot;&gt;
&lt;td id=&quot;trKi&quot;&gt;속도&lt;/td&gt;
&lt;td id=&quot;T~\n&quot;&gt;빠름&lt;/td&gt;
&lt;td id=&quot;lS~}&quot;&gt;느림 (t-SNE), 중간 (UMAP)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 TF-IDF 다음에, SOM 전에 위치하는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인 순서:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;텍스트 &amp;rarr; TF-IDF (수천차원 희소행렬) &amp;rarr; TruncatedSVD (100차원 dense) &amp;rarr; SOM 입력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans는 희소행렬을 그대로 받아도 잘 작동하지만(scikit-learn의 KMeans는 sparse 입력을 지원), &lt;br /&gt;SOM 라이브러리(MiniSom 등)는 dense 입력을 전제로 짜여 있는 경우가 많아서 SVD로 차원을 줄이고 dense로 변환하는 전처리가 필요하다.&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;SVD는 고차원 데이터를 정보 손실을 최소화하면서 저차원으로 압축하는 행렬분해 기법&lt;/li&gt;
&lt;li&gt;TruncatedSVD로 상위 $k$개 특이값만 남기면, 에카르트-영 정리에 의해 그게 랭크 $k$ 근사 중 수학적으로 최적&lt;/li&gt;
&lt;li&gt;$k$는 누적 분산 설명비율이나 스크리 플롯을 보고 경험적으로 정함&lt;/li&gt;
&lt;li&gt;TF-IDF(희소&amp;middot;고차원) &amp;rarr; SVD(압축) &amp;rarr; SOM(밀집 입력 필요) 파이프라인의 중간 단계 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>SvD</category>
      <category>비지도학습</category>
      <category>차원축소</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/282</guid>
      <comments>https://chaeyami.tistory.com/282#entry282comment</comments>
      <pubDate>Wed, 1 Jul 2026 13:53:45 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 실루엣 스코어(Silhouette Score) - 군집화 평가 지표</title>
      <link>https://chaeyami.tistory.com/281</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Silhouette Score?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[STUDY/ML(Machine Learning)] - [ML] K-means clustering&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782808762973&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] K-means clustering&quot; data-og-description=&quot;1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(centroid)을 그 군집에 속한 데이터들의 평균(mean) 위치로 잡아가면서 군집을 &quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/280&quot; data-og-url=&quot;https://blog.chaenii.me/280&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rHvPH/dJMb87ghixt/HzU68sULRwX7Oz3ShFZJl1/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bu49QH/dJMb9iIRyHY/7mCRVcdBaMiFnKzWnMmmrK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/dkO2o1/dJMb84X9vSX/6GszuXpS0q5fOEZlX8XgEK/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/280&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rHvPH/dJMb87ghixt/HzU68sULRwX7Oz3ShFZJl1/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bu49QH/dJMb9iIRyHY/7mCRVcdBaMiFnKzWnMmmrK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/dkO2o1/dJMb84X9vSX/6GszuXpS0q5fOEZlX8XgEK/img.png?width=1672&amp;amp;height=941&amp;amp;face=0_0_1672_941');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] K-means clustering&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. K-means clustering?데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘각 군집을 대표하는 중심점(centroid)을 그 군집에 속한 데이터들의 평균(mean) 위치로 잡아가면서 군집을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링은 비지도학습이라 &quot;정답&quot;이 없다. KMeans를 $K=10$으로 돌린 결과와 $K=70$으로 돌린 결과 중 뭐가 더 &quot;좋은&quot; 군집인지 어떻게 판단할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;b&gt;좋은 군집화&lt;/b&gt;란: &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;같은 군집 안의 데이터끼리는 가깝고, 다른 군집과는 멀어야 한다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 두 조건을 하나의 점수로 합쳐, &lt;b&gt;좋은 군집화의 표준 지표&lt;/b&gt;가 되는 것이 Silhouette Score다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 계산&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 포인트 하나(예: $x$)에 대해:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$a$ = $x$와 같은 군집에 속한 다른 모든 점들까지의 평균 거리 (&quot;내부 응집도&quot; &amp;mdash; 작을수록 좋음)&lt;/li&gt;
&lt;li&gt;$b$ = $x$와 가장 가까운 &lt;b&gt;다른&lt;/b&gt; 군집에 속한 점들까지의 평균 거리 (&quot;외부 분리도&quot; &amp;mdash; 클수록 좋음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;s(x) = \frac{b - a}{\max(a, b)}&lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 실루엣 스코어: 모든 데이터 포인트의 $s(x)$를 평균낸 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;정의 조건&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실루엣&amp;nbsp;스코어는&amp;nbsp;군집이&amp;nbsp;최소&amp;nbsp;2개&amp;nbsp;이상이어야&amp;nbsp;계산할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든&amp;nbsp;점이&amp;nbsp;하나의&amp;nbsp;군집이면&amp;nbsp;&quot;다른&amp;nbsp;군집과의&amp;nbsp;거리&quot;&amp;nbsp;$b$가&amp;nbsp;없고,&amp;nbsp;반대로&amp;nbsp;모든&amp;nbsp;점이&amp;nbsp;자기&amp;nbsp;혼자만의&amp;nbsp;군집이면&amp;nbsp;&quot;같은&amp;nbsp;군집&amp;nbsp;내&amp;nbsp;평균&amp;nbsp;거리&quot;&amp;nbsp;$a$를&amp;nbsp;안정적으로&amp;nbsp;정의하기&amp;nbsp;어렵다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무&amp;nbsp;라이브러리에서는&amp;nbsp;보통&amp;nbsp;$2&amp;nbsp;\le&amp;nbsp;n_{labels}&amp;nbsp;\le&amp;nbsp;n_{samples}-1$&amp;nbsp;조건에서만&amp;nbsp;계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;왜 분모가 $\max(a,b)$인가?&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분모 역할은 분자 $(b-a)$를 $[-1,1]$ 범위로 정규화하는 것이다. &lt;br /&gt;$a, b$ 둘 다 양수인 거리이고, 둘 중 큰 값보다 둘의 차이가 더 클 수 없으므로 $|b-a| \le \max(a,b)$가 항상 성립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; $\max(a,b)$로 나누면 자동으로 $-1 \le s(x) \le 1$ 범위에 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 $a+b$나 다른 값으로 나누지 않고 $\max(a,b)$를 쓰는 이유는, &lt;br /&gt;둘 중 더 &quot;안 좋은&quot;(더 큰) 거리를 기준으로 잡아서 점수가 한쪽 극단에 치우치지 않게 균형을 맞추기 위함이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계산해보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 작은 예시로 감을 잡아보자. &lt;br /&gt;1차원 직선 위에 점 5개가 있고, 군집이 2개로 나뉘어 있다고 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;군집 A = {1, 2, 3}, 군집 B = {8, 9}.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점 $x=2$ (군집 A 소속)를 기준으로 계산하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$a$ = 같은 군집(A)의 다른 점들까지 평균 거리 = $\frac{|2-1| + |2-3|}{2} = \frac{1+1}{2} = 1$&lt;/li&gt;
&lt;li&gt;$b$ = 가장 가까운 다른 군집(B)의 점들까지 평균 거리 = $\frac{|2-8| + |2-9|}{2} = \frac{6+7}{2} = 6.5$&lt;/li&gt;
&lt;li&gt;$s(2) = \frac{b-a}{\max(a,b)} = \frac{6.5 - 1}{6.5} \approx 0.846$&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 군집과는 가깝고(1) 다른 군집과는 멀어서(6.5), 점수가 1에 가깝게 나온다.&lt;br /&gt;-&amp;gt; 군집이 잘 분리된 경우의 전형적인 모습.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 방식으로 모든 점에 대해 계산해서 평균을 내면 전체 실루엣 스코어가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yq6cL/dJMcadWTZSY/ZaJfB5PdDpYPo606QE4cw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yq6cL/dJMcadWTZSY/ZaJfB5PdDpYPo606QE4cw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yq6cL/dJMcadWTZSY/ZaJfB5PdDpYPo606QE4cw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyq6cL%2FdJMcadWTZSY%2FZaJfB5PdDpYPo606QE4cw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;값 해석&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 \le s \le 1&lt;/li&gt;
&lt;li&gt;$s \approx 1$: $a \ll b$ &amp;mdash; 자기 군집과는 아주 가깝고 다른 군집과는 멀다. 군집 분리가 잘 됨&lt;/li&gt;
&lt;li&gt;$s \approx 0$: $a \approx b$ &amp;mdash; 군집 경계에 애매하게 걸쳐 있음&lt;/li&gt;
&lt;li&gt;$s \approx -1$: $a \gg b$ &amp;mdash; 사실 다른 군집에 더 가까운데 잘못 배정됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서 0.7 이상이면 꽤 잘 분리된 것으로 보고, 0.5 이하면 군집 경계가 흐릿하다고 본다 (절대적인 기준은 아니고 도메인에 따라 다름).&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 거리 메트릭 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$a$, $b$를 계산할 때 어떤 거리 함수를 쓰느냐를 고를 수 있다.(유클리드 거리, 코사인 거리 등.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 기반 군집(TF-IDF 벡터 등)에는 코사인 거리를 쓰는 게 일반적이고, &lt;br /&gt;이미 저차원으로 변환된 dense 벡터(SVD 적용 후)에는 유클리드 거리를 쓰는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;같은 데이터라도 어떤 거리 메트릭을 쓰느냐에 따라 점수가 다르게 나올 수 있으니, &lt;br /&gt;서로 다른 군집화 방법을 비교할 때는 같은 메트릭으로 맞춰서 비교해야 한다.&lt;br /&gt;&lt;/b&gt;(혹은 메트릭이 다르다는 걸 명시해야 함)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 계산 비용과 샘플링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실루엣 스코어 계산은 모든 점 쌍 사이의 거리를 계산해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; &lt;b&gt;데이터가 $N$개면 $O(N^2)$ 비용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 데이터가 수만~수십만 건이면 이 계산이 너무 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실전에서는 전체 데이터가 아니라 무작위로 뽑은 일부(예: 1만 개)에 대해서만 계산하고, &lt;br /&gt;이를 &quot;샘플링된 실루엣 스코어&quot;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체를 정확히 대표하진 않지만 합리적인 근사치를 빠르게 얻을 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 다른 수학적 평가 지표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실루엣&amp;nbsp;스코어의&amp;nbsp;$O(N^2)$&amp;nbsp;비용이&amp;nbsp;부담스러울&amp;nbsp;때,&amp;nbsp;또는&amp;nbsp;다른&amp;nbsp;각도의&amp;nbsp;관점이&amp;nbsp;필요할&amp;nbsp;때&amp;nbsp;쓰는&amp;nbsp;지표들이&amp;nbsp;있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Davies-Bouldin Index(DBI)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;군집 내 분산 대비 군집 간 거리.&lt;br /&gt;&lt;br /&gt;$$\text{DBI}&amp;nbsp;=&amp;nbsp;\frac{1}{K}&amp;nbsp;\sum_{i=1}^{K}&amp;nbsp;\max_{j&amp;nbsp;\neq&amp;nbsp;i}&amp;nbsp;\left(&amp;nbsp;\frac{s_i&amp;nbsp;+&amp;nbsp;s_j}{d_{ij}}&amp;nbsp;\right)$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$s_i$는 군집 $i$의 평균 내부 거리(군집이 얼마나 흩어져 있는가), $d_{ij}$는 군집 $i$와 $j$의 중심점 사이 거리.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낮을수록 좋다. 군집 내부는 조밀하고 군집끼리는 멀리 떨어진 경우&lt;/li&gt;
&lt;li&gt;계산 비용: 라벨과 중심/분산을 이미 구해둔 뒤에는 대략 $O(N + K^2)$ &amp;mdash; 각 점의 군집 내 퍼짐을 한 번 훑고, 군집 쌍 사이 거리를 비교한다. 실루엣보다 훨씬 빠른 편이라 보통 샘플링 없이 계산 가능&lt;/li&gt;
&lt;li&gt;단점: 볼록한(convex) 모양의 군집을 가정해서, 불규칙한 모양의 군집에는 잘 맞지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Calinski-Harabasz Score(CH) (분산 비율 기준)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\text{CH} = \frac{\text{군집 간 분산} / (K-1)}{\text{군집 내 분산} / (N-K)}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;군집 간 분산이 크고(군집끼리 잘 떨어짐) 군집 내 분산이 작을수록(군집 내 조밀) 점수가 높다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높을수록 좋다 (DBI와 반대)&lt;/li&gt;
&lt;li&gt;계산 비용: 대체로 데이터 수에 선형적인 빠른 지표 &amp;mdash; 실루엣처럼 모든 점 쌍 거리를 보지 않아 훨씬 가볍다&lt;/li&gt;
&lt;li&gt;단점: $K$가 커질수록 점수가 올라가는 경향이 있어서 $K$ 선택 시 과적합 주의&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세 지표 비교&lt;/h4&gt;
&lt;table id=&quot;391c2707-14d8-8054-9b41-db4a33792092&quot; style=&quot;border-collapse: collapse; width: 100%; height: 156px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 17.907%;&quot;&gt;지표&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 18.6046%;&quot;&gt;좋은 방향&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 12.3256%;&quot;&gt;계산 비용&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 50.9302%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-806f-9def-de5d9bfa10bf&quot; style=&quot;height: 38px;&quot;&gt;
&lt;td id=&quot;y?sH&quot; style=&quot;height: 38px; width: 17.907%;&quot;&gt;실루엣 스코어&lt;/td&gt;
&lt;td id=&quot;AzrT&quot; style=&quot;height: 38px; width: 18.6046%;&quot;&gt;높을수록 (&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;[&amp;minus;1,1][-1,1]&lt;/span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&amp;minus;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;)&lt;/td&gt;
&lt;td id=&quot;fj]=&quot; style=&quot;height: 38px; width: 12.3256%;&quot;&gt;$O(N^2)$&lt;/td&gt;
&lt;td id=&quot;r&amp;lt;v@&quot; style=&quot;height: 38px; width: 50.9302%;&quot;&gt;개별 점 수준 진단 가능, 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-80db-8589-c3d6f1ff8253&quot; style=&quot;height: 34px;&quot;&gt;
&lt;td id=&quot;y?sH&quot; style=&quot;height: 34px; width: 17.907%;&quot;&gt;Davies-Bouldin&lt;/td&gt;
&lt;td id=&quot;AzrT&quot; style=&quot;height: 34px; width: 18.6046%;&quot;&gt;낮을수록&lt;/td&gt;
&lt;td id=&quot;fj]=&quot; style=&quot;height: 34px; width: 12.3256%;&quot;&gt;$O(K^2)$&lt;/td&gt;
&lt;td id=&quot;r&amp;lt;v@&quot; style=&quot;height: 34px; width: 50.9302%;&quot;&gt;빠름, 볼록 군집 가정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-809c-b47c-e68ebde28a19&quot; style=&quot;height: 42px;&quot;&gt;
&lt;td id=&quot;y?sH&quot; style=&quot;height: 42px; width: 17.907%;&quot;&gt;Calinski-Harabasz&lt;/td&gt;
&lt;td id=&quot;AzrT&quot; style=&quot;height: 42px; width: 18.6046%;&quot;&gt;높을수록&lt;/td&gt;
&lt;td id=&quot;fj]=&quot; style=&quot;height: 42px; width: 12.3256%;&quot;&gt;$O(N&amp;nbsp;\cdot&amp;nbsp;K)$&lt;/td&gt;
&lt;td id=&quot;r&amp;lt;v@&quot; style=&quot;height: 42px; width: 50.9302%;&quot;&gt;빠름, $K$클수록 올라가는 경향&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는 세 가지를 함께 계산해서 방향이 일치하는 $K$를 선택하는 게 가장 안정적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 다른 평가 지표: empty cluster, 군집 크기 분포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실루엣 스코어 외에도 실전에서 중요하게 보는 것들:&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;빈 군집(empty cluster) 개수&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목표가 $K$개일 때, 실제로 데이터가 하나도 배정 안 된 군집이 몇 개인지.&lt;/li&gt;
&lt;li&gt;많으면 $K$를 잘못 잡았거나 알고리즘이 데이터 분포에 안 맞는다는 신호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;군집 크기 분포&lt;/b&gt;(최대/최소/중간값)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 군집에 데이터가 몰려있고 나머지는 텅 비어있다면 &quot;균형 잡힌 군집화&quot;가 아니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 지표는 K-Means와 SOM처럼 서로 다른 클러스터링 방법을 비교할 때 실루엣 스코어보다 오히려 더 직관적으로 차이를 보여줄 때가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 격자가 고정된 SOM은 데이터 분포가 70개 칸에 균등하게 안 맞으면 빈 칸이 많이 생기는데, 자유롭게 움직이는 KMeans는 빈 군집이 거의 안 생긴다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 마무리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실루엣 스코어 = &quot;같은 군집과는 가깝고, 다른 군집과는 먼가&quot;를 $-1$~$1$로 수치화한 지표&lt;/li&gt;
&lt;li&gt;$s(x) = (b-a)/\max(a,b)$ &amp;mdash; 1에 가까울수록 잘 분리된 군집, 0이면 경계가 애매, 음수면 잘못 배정된 것&lt;/li&gt;
&lt;li&gt;거리 메트릭(유클리드/코사인) 선택에 따라 점수가 달라지므로, 서로 다른 군집화 방법을 비교할 땐 메트릭을 통일해야 함&lt;/li&gt;
&lt;li&gt;계산량이 $O(N^2)$이라 대용량에서는 샘플링해서 근사치를 구함&lt;/li&gt;
&lt;li&gt;빈 군집 개수, 군집 크기 분포도 함께 보는 게 실전적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>SilhouetteScore</category>
      <category>군집화</category>
      <category>실루엣스코어</category>
      <category>클러스터링</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/281</guid>
      <comments>https://chaeyami.tistory.com/281#entry281comment</comments>
      <pubDate>Tue, 30 Jun 2026 17:47:37 +0900</pubDate>
    </item>
    <item>
      <title>[ML] K-means clustering</title>
      <link>https://chaeyami.tistory.com/280</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. K-means clustering?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 $K$개의 그룹(군집)으로 자동으로 나눠주는 비지도학습 알고리즘&lt;/li&gt;
&lt;li&gt;각 군집을 대표하는 중심점(centroid)을 그 군집에 속한 데이터들의 평균(mean) 위치로 잡아가면서 군집을 갱신해 나가기 때문에 &quot;K-평균(K-means)&quot;라고 함.&lt;/li&gt;
&lt;li&gt;정답 라벨 없이, 데이터들끼리 얼마나 가까운지만 보고 그룹을 찾아냄.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수만 개의 데이터(예: 고객 문의 티켓)가 있는데, 사람이 일일이 보지 않고도 &quot;비슷한 것들끼리 자동으로 묶고 싶다.&quot; &lt;br /&gt;라벨(정답)은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 데이터의 분포만 보고 그룹을 찾아내는 것, -&amp;gt; &lt;b&gt;비지도학습(unsupervised learning).&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K-Means는 그중 가장 단순하고 널리 쓰이는 알고리즘이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$K$개의 그룹으로 나눈다고 하자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기화&lt;/b&gt;: $K$개의 중심점(centroid)을 무작위로(혹은 똑똑하게) 배치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;할당(assign)&lt;/b&gt;: 모든 데이터를 가장 가까운 중심점에 배정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;갱신(update)&lt;/b&gt;: 각 중심점을 자기에게 배정된 데이터들의 평균 위치로 이동&lt;/li&gt;
&lt;li&gt;2~3을 반복하다가 더 이상 바뀌지 않으면(수렴) 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1782805277011&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;초기 중심점 무작위 배치
   &amp;darr;
[반복] 모든 점 &amp;rarr; 가장 가까운 중심점에 배정
   &amp;darr;
[반복] 중심점 &amp;rarr; 자기 그룹의 평균으로 이동
   &amp;darr;
변화 없음? &amp;rarr; 종료 / 있음? &amp;rarr; 반복으로&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPcjZO/dJMcahdYiuD/0XkARx9dgS7qwEsnrbnyl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPcjZO/dJMcahdYiuD/0XkARx9dgS7qwEsnrbnyl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPcjZO/dJMcahdYiuD/0XkARx9dgS7qwEsnrbnyl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPcjZO%2FdJMcahdYiuD%2F0XkARx9dgS7qwEsnrbnyl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 - 중심점은&amp;nbsp;서로&amp;nbsp;독립적이다.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KMeans의 가장 중요한 특징.&lt;/li&gt;
&lt;li&gt;$K$개의 중심점은 &lt;b&gt;서로 아무 관계가 없다.&lt;br /&gt;&lt;/b&gt;e.g.) 중심점 5번이 중심점 50번 바로 근처로 이동해도, 50번에게 아무 영향이 없다.&lt;/li&gt;
&lt;li&gt;각 중심점은 오직 &quot;자기에게 배정된 데이터의 평균&quot;만 보고 독립적으로 움직인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 특징이 나중에 &lt;b&gt;SOM과 비교할 때 핵심 차이점&lt;/b&gt;이 되는데,&lt;br /&gt;SOM은 중심점(노드)들이 격자 위에서 서로 연동되어 움직인다는 점이 다르다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. k-means++: 초기화를 똑똑하게&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 1단계(&quot;무작위로&quot;)를 그대로 쓰면 운이 나쁠 때 문제가 생긴다. &lt;br /&gt;두 중심점이 우연히 같은 군집 근처에 가깝게 찍히면, 알고리즘이 그 근처에서만 수렴해버려서 다른 진짜 군집을 놓치는 지역 최적해(local optimum)에 빠지기 쉽다.&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;k-means++&lt;/b&gt;는 이 문제를 줄이기 위한 초기화 방법이다.&lt;/li&gt;
&lt;li&gt;아이디어: &quot;이미 찍은 중심점들로부터 먼 데이터일수록 다음 중심점으로 뽑힐 확률을 높이자.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;첫 번째 중심점은 데이터 중에서 무작위로 하나 뽑는다&lt;/li&gt;
&lt;li&gt;그다음 중심점은, 각 데이터 $x$에 대해 &quot;이미 뽑힌 중심점들 중 가장 가까운 것까지의 거리&quot; $D(x)$를 구하고, 다음 확률로 하나를 뽑는다:&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P(x) = \frac{D(x)^2}{\sum_{x' \in X} D(x')^2}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거리가 먼 데이터일수록(=기존 중심점들과 안 겹치는 영역일수록) 뽑힐 확률이 커진다. &lt;br /&gt;이렇게 $K$개를 다 뽑은 뒤, 일반 KMeans(할당&amp;rarr;갱신 반복)를 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scikit-learn의 KMeans는 기본값이 init='k-means++'라서, 따로 설정 안 해도 이미 이 방식을 쓰고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만&amp;nbsp;최신&amp;nbsp;scikit-learn의&amp;nbsp;`n_init=&quot;auto&quot;`&amp;nbsp;설정에서는&amp;nbsp;`k-means++`일&amp;nbsp;때&amp;nbsp;초기화&amp;nbsp;실행&amp;nbsp;횟수가&amp;nbsp;적게&amp;nbsp;잡힐&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;텍스트처럼&amp;nbsp;고차원&amp;middot;희소&amp;nbsp;데이터에서는&amp;nbsp;초기값에&amp;nbsp;따른&amp;nbsp;흔들림이&amp;nbsp;커질&amp;nbsp;수&amp;nbsp;있으므로,&amp;nbsp;실험&amp;nbsp;단계에서는&amp;nbsp;`n_init=10`처럼&amp;nbsp;여러&amp;nbsp;번&amp;nbsp;돌려&amp;nbsp;가장&amp;nbsp;낮은&amp;nbsp;inertia/WCSS&amp;nbsp;결과를&amp;nbsp;고르는&amp;nbsp;편이&amp;nbsp;안전하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 목적함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans가 최소화하려는 값은 &quot;&lt;b&gt;군집 내 분산의 합&lt;/b&gt;&quot;(Within-Cluster Sum of Squares, WCSS):&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;J = \sum_{k=1}^{K} \sum_{x \in C_k} |x - \mu_k|^2&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$C_k$는 $k$번째 군집, $\mu_k$는 그 군집의 중심점. &lt;br /&gt;각 데이터가 자기 군집의 중심점으로부터 얼마나 떨어져 있는지(거리의 제곱)를 전부 더한 값.&lt;br /&gt;이게 작을수록 &quot;군집이 빽빽하게 잘 모여있다&quot;는 뜻.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;왜 갱신 단계에서 하필 &quot;평균&quot;으로 이동하는가?&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관이 아니라 수학적으로 정해진다. 군집 $C_k$의 데이터들이 고정돼 있다고 하고, &lt;br /&gt;$\mu_k$에 대해 $J$를 최소화하는 지점을 찾아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$J$를 $\mu_k$로 미분해서 0이 되는 지점을 구하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\frac{\partial J}{\partial \mu_k} = \sum_{x \in C_k} -2(x - \mu_k) = 0&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\Rightarrow \mu_k = \frac{1}{|C_k|}\sum_{x \in C_k} x&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &quot;그 군집에 속한 데이터들의 평균&quot;이 WCSS를 최소화하는 유일한 지점이다. &lt;br /&gt;갱신 단계가 평균으로 이동하는 건 임의의 선택이 아니라, 그 시점에서 $J$를 가장 작게 만드는 수학적으로 최적인 위치로 움직이는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 반복마다 이 $J$ 값이 줄어들거나 그대로 유지된다(절대 늘지 않음).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 할당 단계는 각 점을 더 가까운 중심점으로 옮겨서 $J$를 줄이고, 갱신 단계는 방금 증명한 대로 $\mu_k$를 최적 위치로 옮겨서 또 줄인다. &lt;br /&gt;두 단계 모두 $J$를 늘리는 방향으로는 절대 움직이지 않으니 알고리즘이 결국 멈춘다(수렴이 보장됨).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &lt;b&gt;전역 최적해(global optimum)가 보장되는 건 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 초기 중심점 위치에 따라 다른 (지역적으로 최적인) 결과가 나올 수 있다. 그래서 보통 여러 번 다른 초기값으로 시도해서(n_init) 가장 좋은 결과를 채택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WpaGe/dJMcaiDVJDY/IUBH9Cz8zLTGFUXzgpz9GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WpaGe/dJMcaiDVJDY/IUBH9Cz8zLTGFUXzgpz9GK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WpaGe/dJMcaiDVJDY/IUBH9Cz8zLTGFUXzgpz9GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWpaGe%2FdJMcaiDVJDY%2FIUBH9Cz8zLTGFUXzgpz9GK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 계산 복잡도: 데이터가 많아도 감당 가능한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번의 반복(할당+갱신)에 드는 연산량을 따져보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할당 단계에서 $n$개의 데이터 각각에 대해 $K$개의 중심점까지 거리를 계산($d$차원 벡터 간 거리 계산은 $O(d)$)하므로 $O(nKd)$. &lt;br /&gt;갱신 단계는 각 데이터를 자기 군집의 합산에 한 번씩만 더하면 되므로 $O(nd)$로 할당 단계보다 가볍다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 반복 횟수를 $i$라 하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;O(nKdi)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 데이터 개수 $n$에 대해 &lt;b&gt;선형(linear)&lt;/b&gt;이라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 10배 늘어나면 연산량도 대략 10배만 늘어난다(제곱이나 그 이상으로 폭증하지 않는다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 수만~수백만 건 규모에서도 KMeans가 비교적 감당 가능한 속도로 동작한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 할당 경계의 모양: 보로노이 다이어그램&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중심점들이&amp;nbsp;고정된&amp;nbsp;상태에서&amp;nbsp;&quot;이&amp;nbsp;점은&amp;nbsp;어느&amp;nbsp;중심점에&amp;nbsp;배정되는가&quot;를&amp;nbsp;평면&amp;nbsp;전체에&amp;nbsp;대해&amp;nbsp;그려보면,&amp;nbsp;&lt;br /&gt;중심점 사이의 경계선은 항상 두 중심점을 잇는 선분의 수직이등분선이 된다.&lt;span style=&quot;color: #9d9d9d;&quot;&gt; (두 중심점으로부터 거리가 같은 점들의 집합이니까)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MBOmK/dJMb991g1ft/Nzr6domyJYwikFKnEItUu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MBOmK/dJMb991g1ft/Nzr6domyJYwikFKnEItUu1/img.png&quot; data-alt=&quot;보로노이 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MBOmK/dJMb991g1ft/Nzr6domyJYwikFKnEItUu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMBOmK%2FdJMb991g1ft%2FNzr6domyJYwikFKnEItUu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;보로노이 다이어그램&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경계선들이 만들어내는, 즉 평면을 다각형 영역으로 나누는 패턴을&lt;b&gt; 보로노이 다이어그램(Voronoi diagram)&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans의 &quot;할당&quot; 단계는 본질적으로 각 점이 어느 보로노이 셀(cell)에 속하는지를 찾는 것과 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 실전&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주의점: 스케일과 군집 모양&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans는&amp;nbsp;&quot;유클리드&amp;nbsp;거리&quot;를&amp;nbsp;기준으로&amp;nbsp;가까운&amp;nbsp;점을&amp;nbsp;묶는다.&amp;nbsp;그래서&amp;nbsp;입력&amp;nbsp;특징의&amp;nbsp;스케일이&amp;nbsp;다르면&amp;nbsp;결과가&amp;nbsp;크게&amp;nbsp;흔들릴&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;예를&amp;nbsp;들어&amp;nbsp;어떤&amp;nbsp;열은&amp;nbsp;0~1&amp;nbsp;범위이고,&amp;nbsp;다른&amp;nbsp;열은&amp;nbsp;0~10000&amp;nbsp;범위라면&amp;nbsp;큰&amp;nbsp;범위의&amp;nbsp;열이&amp;nbsp;거리&amp;nbsp;계산을&amp;nbsp;거의&amp;nbsp;지배한다.&amp;nbsp;숫자형&amp;nbsp;특징을&amp;nbsp;섞어&amp;nbsp;쓸&amp;nbsp;때는&amp;nbsp;보통&amp;nbsp;표준화(`StandardScaler`)나&amp;nbsp;정규화가&amp;nbsp;필요하다. &lt;br /&gt;&lt;br /&gt;또 하나의 가정은 군집이 대체로 &lt;b&gt;둥근 모양(spherical cluster)&lt;/b&gt;에 가깝다는 점이다. KMeans는 중심점과의 거리만 보기 때문에 길쭉한 초승달 모양, 밀도가 크게 다른 군집, 이상치가 많은 데이터에는 약하다. 이런 경우에는 DBSCAN/HDBSCAN 같은 밀도 기반 클러스터링이나 계층적 클러스터링이 더 적합할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변형:&amp;nbsp;MiniBatchKMeans&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 아주 많을 때(수만~수백만 건), 매번 전체 데이터를 다 보고 중심점을 갱신하면 느리다. `MiniBatchKMeans`는 매 반복마다 전체가 아니라 무작위로 뽑은 작은 배치(batch)만 보고 중심점을 갱신한다 .&lt;br /&gt;약간 부정확해질 수 있지만 훨씬 빠르다. 배치 크기(`batch_size`)와 초기화 시도 횟수(`n_init`)가 주요 하이퍼파라미터.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 결과를 어떻게 쓰는가: 학습과 추론의 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서 중요한 패턴:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모델을 한 번 학습시켜서 중심점을 고정해두고, 그 이후 새로운 데이터는 그 고정된 중심점에 &quot;가장 가까운 곳 찾기&quot;만 한다&lt;/b&gt;. (train-once, infer-many).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학습 시점: 전체 데이터(예: 6만 건)로 $K$개 중심점을 찾는다. 결과를 파일로 저장(`joblib.dump`)&lt;/li&gt;
&lt;li&gt;추론 시점: 새로 들어온 데이터 한 건에 대해, 저장된 중심점들과의 거리만 계산해서 가장 가까운 곳에 배정(`predict()`). 중심점은 다시 학습하지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분리하는 이유: 매번 새 데이터가 들어올 때마다 전체를 다시 학습시키면 느리고, 기준(중심점)이 계속 바뀌면 &quot;어제는 군집 A였는데 오늘은 군집 B가 됐다&quot; 같은 일관성 문제가 생긴다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. $K$는 어떻게 정하는가: Elbow Method&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMeans를 쓰려면 군집 개수 $K$를 미리 정해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 &quot;몇 개로 나눠야 적절한지&quot;는 데이터만 보고는 바로 알기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 가장 단순하고 널리 쓰이는 방법이 elbow method(팔꿈치 방법)다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;절차&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;$K$를 1, 2, 3, ...으로 늘려가며 매번 KMeans를 돌린다.&lt;/li&gt;
&lt;li&gt;그때마다 목적함수 $J$(WCSS)를 기록한다.&lt;/li&gt;
&lt;li&gt;$K$(x축) vs $J$(y축) 그래프를 그린다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 팔꿈치 모양?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$K$가 작을 때: 군집을 하나 늘릴 때마다 $J$가 크게 줄어든다&lt;/li&gt;
&lt;li&gt;어느 지점을 넘어서면: 군집을 더 늘려도 $J$가 거의 안 줄어든다.&lt;br /&gt;이미 자연스러운 그룹들을 다 찾았기 때문에, 그 이상 쪼개는 건 억지로 나누는 것에 가깝다&lt;/li&gt;
&lt;li&gt;이 &quot;기울기가 급격히 완만해지는 지점&quot;이 팔꿈치처럼 꺾여 보여서 elbow라고 부르고, 그 지점의 $K$를 적절한 군집 수로 채택한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqEadJ/dJMcah58Jxa/ePynoznGiPnYAlPg1zZ46k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqEadJ/dJMcah58Jxa/ePynoznGiPnYAlPg1zZ46k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqEadJ/dJMcah58Jxa/ePynoznGiPnYAlPg1zZ46k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqEadJ%2FdJMcah58Jxa%2FePynoznGiPnYAlPg1zZ46k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1693&quot; height=&quot;929&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터에서는 팔꿈치가 이렇게 선명하게 안 꺾이고 완만하게 이어지는 경우가 많음.&lt;br /&gt;-&amp;gt; 어디가 &quot;꺾이는 지점&quot;인지 보는 사람마다 다르게 판단할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실전에서는 elbow method 하나만 보지 않고, &lt;br /&gt;&lt;a href=&quot;https://chaeyami.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실루엣스코어&lt;/a&gt;를 같이 계산해서 더 객관적인 근거로 $K$를 정하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;a href=&quot;https://chaeyami.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실루엣스코어&lt;/a&gt;는 군집화 후에 &quot;군집 분리가 잘 됐는지&quot;의 평가 지표로도 쓰인다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K-Means = 중심점 $K$개를 할당&amp;harr;갱신 반복해서 WCSS를 최소화하는 비지도 클러스터링&lt;/li&gt;
&lt;li&gt;갱신 단계가 &quot;평균&quot;으로 이동하는 건 임의가 아니라 WCSS를 최소화하는 수학적 최적해(미분=0 지점)&lt;/li&gt;
&lt;li&gt;k-means++로 초기화하면 지역 최적해에 빠질 위험을 줄일 수 있음&lt;/li&gt;
&lt;li&gt;계산량이 데이터 수에 선형($O(nKdi)$)이라 대용량에도 비교적 잘 버팀&lt;/li&gt;
&lt;li&gt;$K$는 elbow method나 &lt;a href=&quot;https://chaeyami.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Silhouette Score&lt;/a&gt;로 정함&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>K-means</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>비지도학습</category>
      <category>클러스터링</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/280</guid>
      <comments>https://chaeyami.tistory.com/280#entry280comment</comments>
      <pubDate>Tue, 30 Jun 2026 17:00:58 +0900</pubDate>
    </item>
    <item>
      <title>[ML] 벡터 유사도(Vector Similarity)</title>
      <link>https://chaeyami.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[STUDY/ML(Machine Learning)] - [ML] TF-IDF(Term Frequency-Inverse Document Frequency)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782882229435&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] TF-IDF(Term Frequency-Inverse Document Frequency)&quot; data-og-description=&quot;1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &amp;quot;중요도&amp;quot;를 가중치로 매기는 방법단순히 &amp;quot;몇 번 나왔는가&amp;quot;만 세는 빈도수 카운트(Bag of Words)의 한계를 보완쓰임문서 간&quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://blog.chaenii.me/278&quot; data-og-url=&quot;https://blog.chaenii.me/278&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VdPeB/dJMb8U84BfN/gauFfQ97WkzRjqImWNQzb1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bZoSks/dJMb85vZVSC/bLDNVfZ0KU1GS6s9lNneR0/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bGNcl1/dJMb8XSgLLN/TLrtZvnWLqel4ChdmPCfPk/img.png?width=1957&amp;amp;height=1122&amp;amp;face=0_0_1957_1122&quot;&gt;&lt;a href=&quot;https://blog.chaenii.me/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.chaenii.me/278&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VdPeB/dJMb8U84BfN/gauFfQ97WkzRjqImWNQzb1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bZoSks/dJMb85vZVSC/bLDNVfZ0KU1GS6s9lNneR0/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bGNcl1/dJMb8XSgLLN/TLrtZvnWLqel4ChdmPCfPk/img.png?width=1957&amp;amp;height=1122&amp;amp;face=0_0_1957_1122');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] TF-IDF(Term Frequency-Inverse Document Frequency)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &quot;중요도&quot;를 가중치로 매기는 방법단순히 &quot;몇 번 나왔는가&quot;만 세는 빈도수 카운트(Bag of Words)의 한계를 보완쓰임문서 간&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF든 임베딩이든,&amp;nbsp;텍스트를 벡터로 바꿨다면 그다음 필요한 건&lt;br /&gt;&lt;b&gt;&quot;이 두 벡터가 얼마나 비슷한가&quot;&lt;/b&gt;를 숫자 하나로 표현하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 쓰는 두 가지가 코사인 유사도와 유클리드 거리인데, 이 둘은 측정하는 대상이 다르다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 코사인 유사도: 방향이 비슷한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\cos\theta&amp;nbsp;=&amp;nbsp;\frac{a&amp;nbsp;\cdot&amp;nbsp;b}{|a||b|}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분자&amp;nbsp;$a&amp;nbsp;\cdot&amp;nbsp;b$는&amp;nbsp;내적(dot&amp;nbsp;product),&amp;nbsp;분모는&amp;nbsp;각&amp;nbsp;벡터의&amp;nbsp;길이(norm)를&amp;nbsp;곱한&amp;nbsp;것.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 두 벡터 사이의 &lt;b&gt;각도의 코사인 값&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 벡터가 같은 방향이면 $\cos\theta = 1$ (완전히 유사)&lt;/li&gt;
&lt;li&gt;직각이면 $\cos\theta = 0$ (무관)&lt;/li&gt;
&lt;li&gt;반대 방향이면 $\cos\theta = -1$ (반대)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;핵심:&amp;nbsp;벡터의&amp;nbsp;크기(길이)는&amp;nbsp;무시하고&amp;nbsp;방향만&amp;nbsp;본다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g.) 문서 A가 짧고 문서 B가 길어도,둘 다 &quot;환불&quot;이라는 단어가 같은 비율로 들어가 있다면 방향이 같음&lt;br /&gt;-&amp;gt; 코사인 유사도가 높게 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 텍스트 비교에서 코사인 유사도를 선호하는 이유. &lt;b&gt;문서 길이에 영향을 안 받는다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVBmM/dJMcahEX2cM/HAvR2eiKbtHSIkD5Qzxrfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVBmM/dJMcahEX2cM/HAvR2eiKbtHSIkD5Qzxrfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVBmM/dJMcahEX2cM/HAvR2eiKbtHSIkD5Qzxrfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVBmM%2FdJMcahEX2cM%2FHAvR2eiKbtHSIkD5Qzxrfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$|a \cdot b| \le |a||b|$$&lt;br /&gt;에서 양변을&amp;nbsp;$|a||b|$로&amp;nbsp;나누면: &lt;br /&gt;&lt;br /&gt;$$-1&amp;nbsp;\le&amp;nbsp;\frac{a&amp;nbsp;\cdot&amp;nbsp;b}{|a||b|}&amp;nbsp;\le&amp;nbsp;1$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 코사인 유사도는 별도의 정규화 없이도 항상 $[-1, 1]$ 사이로 깔끔하게 떨어진다.&lt;br /&gt;(직접 새로운 유사도 지표를 설계할 때 이 성질이 없으면 값의 범위를 따로 정규화해줘야 해서 번거롭다.)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;주의: 0 벡터에는 코사인 유사도가 정의되지 않는다.&lt;/b&gt;&lt;br /&gt;분모에 $|a||b|$가 들어가므로, 둘 중 하나라도 모든 값이 0인 벡터라면 나눗셈을 할 수 없다. 실전 코드에서는 빈 문서, 전처리 후 단어가 하나도 남지 않은 문서, 희소 벡터가 전부 0이 된 케이스를 따로 처리해야 한다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 유클리드 거리: 절대적인 위치가 얼마나 가까운가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$d&amp;nbsp;=&amp;nbsp;\sqrt{\sum_i&amp;nbsp;(a_i&amp;nbsp;-&amp;nbsp;b_i)^2}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두&amp;nbsp;점&amp;nbsp;사이의&amp;nbsp;&quot;직선&amp;nbsp;거리&quot;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코사인과&amp;nbsp;달리&amp;nbsp;&lt;b&gt;크기(magnitude)도&amp;nbsp;함께&amp;nbsp;&lt;/b&gt;본다.&amp;nbsp;&lt;br /&gt;벡터의&amp;nbsp;길이가&amp;nbsp;다르면&amp;nbsp;방향이&amp;nbsp;같아도&amp;nbsp;거리가&amp;nbsp;멀게&amp;nbsp;나올&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 언제 뭘 쓰는가&lt;/h2&gt;
&lt;table id=&quot;38fc2707-14d8-80d7-8498-f660f902d139&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;코사인 유사도&lt;/td&gt;
&lt;td&gt;유클리드 거리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;38fc2707-14d8-804c-863d-f22dc0bd380d&quot;&gt;
&lt;td id=&quot;:ECb&quot;&gt;보는 것&lt;/td&gt;
&lt;td id=&quot;pjoC&quot;&gt;방향(각도)&lt;/td&gt;
&lt;td id=&quot;ELM;&quot;&gt;절대 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;38fc2707-14d8-80e3-a8dc-ee19fa725081&quot;&gt;
&lt;td id=&quot;:ECb&quot;&gt;길이(크기) 영향&lt;/td&gt;
&lt;td id=&quot;pjoC&quot;&gt;없음&lt;/td&gt;
&lt;td id=&quot;ELM;&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;38fc2707-14d8-805c-b224-d5130348b590&quot;&gt;
&lt;td id=&quot;:ECb&quot;&gt;주로 쓰이는 곳&lt;/td&gt;
&lt;td id=&quot;pjoC&quot;&gt;텍스트(TF-IDF, 임베딩) 비교&lt;/td&gt;
&lt;td id=&quot;ELM;&quot;&gt;군집화의 중심점 거리 (KMeans), SOM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;텍스트 유사도 비교는 거의 항상 코사인.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;반면 KMeans나 SOM처럼 &quot;공간상의 위치&quot;를 다루는 군집화 알고리즘은 보통 유클리드 거리를 쓴다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(단, KMeans도 코사인 거리로 평가 가능. 평가지표인 실루엣 스코어는 메트릭을 선택할 수 있음).&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvf10O/dJMcaf8cJbQ/VR8GaKeR37BWkRKkB1HZ21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvf10O/dJMcaf8cJbQ/VR8GaKeR37BWkRKkB1HZ21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvf10O/dJMcaf8cJbQ/VR8GaKeR37BWkRKkB1HZ21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdvf10O%2FdJMcaf8cJbQ%2FVR8GaKeR37BWkRKkB1HZ21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정규화된 벡터에서는 내적&amp;middot;코사인&amp;middot;유클리드 거리가 연결된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터를 L2 정규화해서 길이를 모두 1로 맞추면, 두 벡터의 내적은 곧 코사인 유사도와 같아진다. &lt;br /&gt;또한 이때 유클리드 거리도 코사인 유사도와 단조 관계를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 임베딩 검색 시스템에서는 벡터를 미리 정규화해두고 내적 검색으로 코사인 검색을 빠르게 구현하는 경우가 많다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른&amp;nbsp;유사도&amp;nbsp;측정&amp;nbsp;방법들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코사인과&amp;nbsp;유클리드&amp;nbsp;외에도&amp;nbsp;상황에&amp;nbsp;따라&amp;nbsp;쓰이는&amp;nbsp;측정법들이&amp;nbsp;있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Jaccard 유사도&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 집합이 얼마나 겹치는가.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;J(A, B) = \frac{|A \cap B|}{|A \cup B|}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터가 아니라 &lt;b&gt;집합&lt;/b&gt; 간 유사도다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g) 문서 A의 단어 집합과 문서 B의 단어 집합이 얼마나 겹치는지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어 순서나 빈도는 무시하고 &quot;이 단어가 나왔는가/안 나왔는가&quot;만 본다. &lt;br /&gt;중복 제거 집합을 쓰므로 같은 단어가 10번 나오든 1번 나오든 동일 취급.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 범위: 0(겹치는 단어 없음) ~ 1(완전히 같은 단어 집합)&lt;/li&gt;
&lt;li&gt;주로 쓰이는 곳: 짧은 텍스트(태그, 키워드 목록) 비교, 추천 시스템에서 아이템 유사도&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맨해튼&amp;nbsp;거리(Manhattan&amp;nbsp;/&amp;nbsp;L1&amp;nbsp;distance)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;격자를 따라 이동하는 거리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;d_1 = \sum_i |a_i - b_i|&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유클리드 거리(직선)가 아니라, 각 차원의 차이를 절댓값으로 더한 것. &lt;br /&gt;&quot;블록을 따라 걷는 거리&quot;라서 맨해튼(뉴욕 격자 도로)이라는 이름이 붙었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상치(outlier)에 유클리드보다 덜 민감하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유클리드는 차이를 제곱하므로 큰 차이가 과도하게 증폭되지만, 맨해튼은 그냥 더한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고차원 데이터에서 유클리드보다 안정적으로 동작하는 경우가 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상황별 선택 기준&lt;/h4&gt;
&lt;table id=&quot;390c2707-14d8-802e-95ce-d6d9e68d0a8a&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&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 id=&quot;390c2707-14d8-805f-9ac2-c3853195a1c5&quot;&gt;
&lt;td id=&quot;kPp&amp;gt;&quot;&gt;코사인 유사도&lt;/td&gt;
&lt;td id=&quot;dRRf&quot;&gt;방향(각도)&lt;/td&gt;
&lt;td id=&quot;O;wv&quot;&gt;텍스트 벡터(TF-IDF, 임베딩) 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-806f-997d-d234d73cc74f&quot;&gt;
&lt;td id=&quot;kPp&amp;gt;&quot;&gt;유클리드 거리&lt;/td&gt;
&lt;td id=&quot;dRRf&quot;&gt;절대 위치&lt;/td&gt;
&lt;td id=&quot;O;wv&quot;&gt;군집화 중심점 거리, 저차원 dense 벡터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80e3-814f-ec3021b74ff2&quot;&gt;
&lt;td id=&quot;kPp&amp;gt;&quot;&gt;Jaccard 유사도&lt;/td&gt;
&lt;td id=&quot;dRRf&quot;&gt;집합 겹침&lt;/td&gt;
&lt;td id=&quot;O;wv&quot;&gt;태그&amp;middot;키워드 집합 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-807f-a8f7-ff8dee674cb7&quot;&gt;
&lt;td id=&quot;kPp&amp;gt;&quot;&gt;맨해튼 거리&lt;/td&gt;
&lt;td id=&quot;dRRf&quot;&gt;L1 절대 거리&lt;/td&gt;
&lt;td id=&quot;O;wv&quot;&gt;이상치에 강건한 거리 계산 필요 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 거리를 유사도로 변환&lt;/h2&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;: 군집화에서 &quot;이 데이터가 이 중심점과 얼마나 가까운가&quot;를 직관적으로 나타낸 0~1 사이의 점수.&lt;/li&gt;
&lt;li&gt;유사도 변환:&lt;br /&gt;$$\text{similarity}&amp;nbsp;=&amp;nbsp;\frac{1}{1+d}$$&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$d$: (유클리드) 거리.&lt;/li&gt;
&lt;li&gt;$d = 0$ : 유사도 1(완전 일치),&lt;/li&gt;
&lt;li&gt;거리가 커질수록 유사도가 0에 가까워지지만 절대 음수가 되지 않으며 절대 0이 되지도 않는다(부드럽게 감소).&lt;/li&gt;
&lt;li&gt;거리라는 &quot;작을수록 좋은&quot; 지표를 &quot;클수록 좋은&quot; 점수로 바꿔주는 간단하지만 실용적인 트릭.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코사인 유사도는 벡터의 &quot;방향&quot;만 보고, 유클리드 거리는 &quot;절대 위치(크기 포함)&quot;를 봄&lt;/li&gt;
&lt;li&gt;텍스트 비교는 거의 항상 코사인, 군집화의 중심점 거리는 유클리드가 일반적&lt;/li&gt;
&lt;li&gt;코사인 값은 코시-슈바르츠 부등식에 의해 항상 $[-1,1]$ 범위로 깔끔하게 떨어짐&lt;/li&gt;
&lt;li&gt;Jaccard 유사도는 집합 겹침을 보며 태그&amp;middot;키워드 비교에 적합, 맨해튼 거리는 이상치에 강건&lt;/li&gt;
&lt;li&gt;거리를 0~1 사이 유사도 점수로 바꿀 때는 $1/(1+d)$ 같은 변환을 씀&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터&amp;nbsp;사이&amp;nbsp;거리를&amp;nbsp;계산할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;됐다면,&amp;nbsp;이제&amp;nbsp;&quot;&lt;b&gt;비슷한&amp;nbsp;벡터들을&amp;nbsp;그룹으로&amp;nbsp;묶는&lt;/b&gt;&quot;&amp;nbsp;작업을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 &lt;a href=&quot;https://chaeyami.tistory.com/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;K-Means 클러스터링&lt;/a&gt;이다.&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>벡터유사도</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/279</guid>
      <comments>https://chaeyami.tistory.com/279#entry279comment</comments>
      <pubDate>Tue, 30 Jun 2026 15:35:06 +0900</pubDate>
    </item>
    <item>
      <title>[ML] TF-IDF(Term Frequency-Inverse Document Frequency)</title>
      <link>https://chaeyami.tistory.com/278</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. TF-IDF?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &quot;중요도&quot;를 가중치로 매기는 방법&lt;/li&gt;
&lt;li&gt;단순히 &quot;몇 번 나왔는가&quot;만 세는 빈도수 카운트(Bag of Words)의 한계를 보완&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쓰임&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서 간 유사도 계산&lt;/li&gt;
&lt;li&gt;검색 결과 랭킹(중요한 단어가 많이 매칭될수록 상위 노출)&lt;/li&gt;
&lt;li&gt;특정 문서 안에서 핵심 키워드 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 필요한가? - 단순 빈도수의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터는 &quot;배송 문의 요청&quot;이라는 문자열을 그 자체로 이해하지 못함 &amp;rarr; 비교&amp;middot;계산을 하려면 숫자(벡터)로 바꿔야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 방법: 단어가 몇 번 나왔는지 세는 것(Bag of Words)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: &quot;문의&quot;, &quot;요청&quot; 같이 거의 모든 문서에 등장하는 흔한 단어가 카운트를 지배&lt;/li&gt;
&lt;li&gt;진짜 그 문서를 구별짓는 단어(&quot;환불&quot;, &quot;파손&quot;, &quot;교환&quot;)는 빈도수만으로는 묻혀버림&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF의 해법: 흔한 단어는 점수를 깎고, 흔치 않은(=특징적인) 단어는 점수를 올린다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 계산&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\text{TF-IDF}(t,d) = \text{TF}(t,d) \times \text{IDF}(t)$$&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TF (Term Frequency, 단어빈도)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장&amp;nbsp;단순한&amp;nbsp;TF는&amp;nbsp;그냥&amp;nbsp;등장&amp;nbsp;횟수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\text{TF}(t,d) = f_{t,d}$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$f_{t,d}$ = 문서 $d$ 안에서 단어 $t$가 등장한 횟수&lt;/li&gt;
&lt;li&gt;설명용 예제에서는 이 raw count를 쓰면 계산이 가장 직관적이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는&amp;nbsp;문서&amp;nbsp;길이&amp;nbsp;차이를&amp;nbsp;줄이려고&amp;nbsp;다음처럼&amp;nbsp;문서&amp;nbsp;길이로&amp;nbsp;나눈&amp;nbsp;정규화&amp;nbsp;TF를&amp;nbsp;쓰기도&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ &lt;br /&gt;\text{TF}_{\text{norm}}(t,d)&amp;nbsp;=&amp;nbsp;\frac{f_{t,d}}{\sum_k&amp;nbsp;f_{k,d}} &lt;br /&gt;$$&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IDF (Inverse Document Frequency, 역문서빈도)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명용으로&amp;nbsp;가장&amp;nbsp;직관적인&amp;nbsp;IDF는&amp;nbsp;다음과&amp;nbsp;같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ &lt;br /&gt;\text{IDF}(t)&amp;nbsp;=&amp;nbsp;\log\frac{N}{\text{DF}(t)} &lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$N$= 전체 문서 수&lt;/li&gt;
&lt;li&gt;$\text{DF}(t)$ = 단어 $t$가 등장한 문서의 개수&lt;/li&gt;
&lt;li&gt;모든 문서에 등장하는 단어 &amp;rarr; $\text{DF}=N$ &amp;rarr; $\log(1)=0$ &amp;rarr; 문서를 구별하는 힘이 없음&lt;/li&gt;
&lt;li&gt;일부 문서에만 등장하는 단어 &amp;rarr; $\text{DF}$가 작음 &amp;rarr; IDF 값이 큼&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관: &quot;흔할수록 정보량이 적다&quot;는 정보이론의 기본 원리. 모든 문서에 &quot;이/그/것&quot;이 나온다면 그 단어는 문서를 구별하는 데 도움이 안 됨(정보량 0). 반면 &quot;환불&quot;이 일부 문서에만 나온다면 &quot;이 문서가 무엇에 관한 것인지&quot; 알려주는 정보량이 큼.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜&amp;nbsp;하필&amp;nbsp;로그(log)인가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &quot;흔할수록 깎는다&quot;는 거라면 $1/\text{DF}$ 같은 역수를 써도 될 텐데, 굳이 로그를 쓰는 이유가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어&amp;nbsp;$t$가&amp;nbsp;무작위로&amp;nbsp;고른&amp;nbsp;문서에&amp;nbsp;등장할&amp;nbsp;확률을&amp;nbsp;$P(t)&amp;nbsp;\approx&amp;nbsp;\text{DF}(t)/N$로&amp;nbsp;보면,&amp;nbsp;섀넌(Shannon)의&amp;nbsp;정보량&amp;nbsp;공식은: &lt;br /&gt;&lt;br /&gt;$$&lt;br /&gt;I(t)&amp;nbsp;=&amp;nbsp;-\log&amp;nbsp;P(t)&amp;nbsp;=&amp;nbsp;\log\frac{N}{\text{DF}(t)}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 IDF 공식의 형태다. $1/\text{DF}$ 같은 역수를 쓰면 희귀한 단어의 가중치가 지나치게 폭주하는데, 로그를 쓰면 그 폭주를 완만하게 눌러주는 효과도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실전 구현은 보통 스무딩을 넣는다.&lt;/b&gt; scikit-learn `TfidfVectorizer`의 기본 IDF는 다음 형태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{IDF}_{\text{sklearn}}(t)=\log\frac{1+N}{1+\text{DF}(t)}+1&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분자와 분모에 1을 더하고, 마지막에 1을 더해서 어떤 단어도 정확히 0이 되지 않게 만든다. 그래서 직접 손으로 계산한 설명용 공식과 scikit-learn 출력값은 일부 다르게 나올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otRhz/dJMcaijA5JT/G96w7Zdykcvgkatbafndp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otRhz/dJMcaijA5JT/G96w7Zdykcvgkatbafndp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otRhz/dJMcaijA5JT/G96w7Zdykcvgkatbafndp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotRhz%2FdJMcaijA5JT%2FG96w7Zdykcvgkatbafndp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1693&quot; height=&quot;929&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 문서 4개 (쇼핑몰 CS 문의 제목 같은 짧은 텍스트라고 가정)&lt;/p&gt;
&lt;pre id=&quot;code_1782795605807&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;문서1: 배송 문의 요청
문서2: 교환 문의 요청
문서3: 배송 환불 지연 파손 요청
문서4: 포장 문의&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF (단어 등장 횟수)&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&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;/th&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;문서1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문서2&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문서3&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문서4&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IDF&amp;nbsp;(자연로그&amp;nbsp;ln&amp;nbsp;사용,&amp;nbsp;$N=4$,&amp;nbsp;설명용&amp;nbsp;unsmoothed&amp;nbsp;공식)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단어&amp;nbsp;&lt;/td&gt;
&lt;td&gt;DF(등장&amp;nbsp;문서&amp;nbsp;수)&lt;/td&gt;
&lt;td&gt;IDF = ln(4/DF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배송&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;ln(4/2) = 0.6931&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문의&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ln(4/3) = 0.2877&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요청&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ln(4/3) = 0.2877&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;교환&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ln(4/1) = 1.3863&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;환불&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ln(4/1) = 1.3863&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지연&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ln(4/1) = 1.3863&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파손&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ln(4/1) = 1.3863&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포장&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ln(4/1) = 1.3863&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp;중요한&amp;nbsp;점:&amp;nbsp;&quot;문의&quot;와&amp;nbsp;&quot;요청&quot;은&amp;nbsp;완전히&amp;nbsp;사라지진&amp;nbsp;않지만,&amp;nbsp;다른&amp;nbsp;특징&amp;nbsp;단어보다&amp;nbsp;점수가&amp;nbsp;낮아진다.&amp;nbsp;4개&amp;nbsp;중&amp;nbsp;3개&amp;nbsp;문서에&amp;nbsp;등장하므로&amp;nbsp;구별력이&amp;nbsp;약하다고&amp;nbsp;판단된&amp;nbsp;것이다.&amp;nbsp;반대로&amp;nbsp;&quot;환불&quot;,&amp;nbsp;&quot;파손&quot;,&amp;nbsp;&quot;교환&quot;&amp;nbsp;같은&amp;nbsp;단어는&amp;nbsp;일부&amp;nbsp;문서에만&amp;nbsp;등장하므로&amp;nbsp;IDF가&amp;nbsp;높게&amp;nbsp;유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF-IDF (TF &amp;times; IDF)&lt;/b&gt;&lt;/p&gt;
&lt;table id=&quot;391c2707-14d8-8062-82f2-fb0f3b015c78&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;배송&lt;/td&gt;
&lt;td&gt;문의&lt;/td&gt;
&lt;td&gt;요청&lt;/td&gt;
&lt;td&gt;교환&lt;/td&gt;
&lt;td&gt;환불&lt;/td&gt;
&lt;td&gt;지연&lt;/td&gt;
&lt;td&gt;파손&lt;/td&gt;
&lt;td&gt;보상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-802b-9b3e-c3d7f2ad102d&quot;&gt;
&lt;td id=&quot;^sEE&quot;&gt;문서1&lt;/td&gt;
&lt;td id=&quot;hvfF&quot;&gt;0.6931&lt;/td&gt;
&lt;td id=&quot;vbRz&quot;&gt;0.2877&lt;/td&gt;
&lt;td id=&quot;&amp;#96;GRi&quot;&gt;0.2877&lt;/td&gt;
&lt;td id=&quot;PorQ&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;vDe^&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;]j~X&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;uVFE&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;LGeW&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-806e-a32a-e5ebfea79ecb&quot;&gt;
&lt;td id=&quot;^sEE&quot;&gt;문서2&lt;/td&gt;
&lt;td id=&quot;hvfF&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;vbRz&quot;&gt;0.2877&lt;/td&gt;
&lt;td id=&quot;&amp;#96;GRi&quot;&gt;0.2877&lt;/td&gt;
&lt;td id=&quot;PorQ&quot;&gt;1.3863&lt;/td&gt;
&lt;td id=&quot;vDe^&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;]j~X&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;uVFE&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;LGeW&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-805d-8f25-e9368e82ad02&quot;&gt;
&lt;td id=&quot;^sEE&quot;&gt;문서3&lt;/td&gt;
&lt;td id=&quot;hvfF&quot;&gt;0.6931&lt;/td&gt;
&lt;td id=&quot;vbRz&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;&amp;#96;GRi&quot;&gt;0.2877&lt;/td&gt;
&lt;td id=&quot;PorQ&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;vDe^&quot;&gt;1.3863&lt;/td&gt;
&lt;td id=&quot;]j~X&quot;&gt;1.3863&lt;/td&gt;
&lt;td id=&quot;uVFE&quot;&gt;1.3863&lt;/td&gt;
&lt;td id=&quot;LGeW&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;391c2707-14d8-80aa-a2db-c7df2c20235d&quot;&gt;
&lt;td id=&quot;^sEE&quot;&gt;문서4&lt;/td&gt;
&lt;td id=&quot;hvfF&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;vbRz&quot;&gt;0.2877&lt;/td&gt;
&lt;td id=&quot;&amp;#96;GRi&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;PorQ&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;vDe^&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;]j~X&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;uVFE&quot;&gt;0&lt;/td&gt;
&lt;td id=&quot;LGeW&quot;&gt;1.3863&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table id=&quot;391c2707-14d8-8062-82f2-fb0f3b015c78&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈도만&amp;nbsp;봤을&amp;nbsp;때는&amp;nbsp;&quot;문의&quot;,&amp;nbsp;&quot;요청&quot;도&amp;nbsp;중요해&amp;nbsp;보였지만,&amp;nbsp;TF-IDF를&amp;nbsp;적용하면&amp;nbsp;&quot;환불&quot;,&amp;nbsp;&quot;파손&quot;,&amp;nbsp;&quot;교환&quot;,&amp;nbsp;&quot;포장&quot;처럼&amp;nbsp;문서를&amp;nbsp;구별짓는&amp;nbsp;단어가&amp;nbsp;더&amp;nbsp;크게&amp;nbsp;남는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KMYZK/dJMcad3CwYl/CqFC2KuEtsyFZBSI7oGvnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KMYZK/dJMcad3CwYl/CqFC2KuEtsyFZBSI7oGvnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KMYZK/dJMcad3CwYl/CqFC2KuEtsyFZBSI7oGvnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKMYZK%2FdJMcad3CwYl%2FCqFC2KuEtsyFZBSI7oGvnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 구현&lt;/h2&gt;
&lt;pre id=&quot;code_1782795618512&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pandas as pd
from math import log

docs = [
    '배송 문의 요청',
    '교환 문의 요청',
    '배송 환불 지연 파손 요청',
    '포장 문의',
]
vocab = sorted(set(w for doc in docs for w in doc.split()))
N = len(docs)

def tf(t, d):
    return d.split().count(t)  # 설명용 raw count

def idf(t):
    df = sum(t in doc.split() for doc in docs)
    return log(N / df)         # 설명용 unsmoothed IDF

def tfidf(t, d):
    return tf(t, d) * idf(t)

result = [[tfidf(t, d) for t in vocab] for d in docs]
pd.DataFrame(result, columns=vocab)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서&amp;nbsp;손으로&amp;nbsp;계산한&amp;nbsp;표와&amp;nbsp;같은&amp;nbsp;방식의&amp;nbsp;값이&amp;nbsp;나온다. &lt;br /&gt;&lt;br /&gt;문서&amp;nbsp;길이&amp;nbsp;차이를&amp;nbsp;더&amp;nbsp;엄격하게&amp;nbsp;줄이고&amp;nbsp;싶다면&amp;nbsp;TF를&amp;nbsp;이렇게&amp;nbsp;바꿀&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;pre id=&quot;code_1782956114620&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def tf_norm(t, d):
    words = d.split()
    return words.count(t) / len(words)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;경우&amp;nbsp;문서3처럼&amp;nbsp;단어&amp;nbsp;수가&amp;nbsp;긴&amp;nbsp;문서의&amp;nbsp;각&amp;nbsp;단어&amp;nbsp;점수는&amp;nbsp;조금&amp;nbsp;낮아진다.&amp;nbsp;같은&amp;nbsp;공식&amp;nbsp;안에서도&amp;nbsp;TF를&amp;nbsp;어떻게&amp;nbsp;정의하느냐에&amp;nbsp;따라&amp;nbsp;결과가&amp;nbsp;달라질&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;점이&amp;nbsp;중요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;scikit-learn&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는&amp;nbsp;직접&amp;nbsp;구현하지&amp;nbsp;않고&amp;nbsp;TfidfVectorizer를&amp;nbsp;쓴다.&lt;/p&gt;
&lt;pre id=&quot;code_1782795762958&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
    '배송 문의 요청',
    '교환 문의 요청',
    '배송 환불 지연 파손 요청',
    '포장 문의',
]

tfidfv = TfidfVectorizer().fit(corpus)
print(tfidfv.transform(corpus).toarray())
print(tfidfv.vocabulary_)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손으로 구한 값과 미묘하게 다른 숫자가 나오는데, scikit-learn의 기본 구현이 위 기본 공식에서 두 가지를 조정하기 때문이다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;IDF 분자에도 +1&lt;/b&gt;: $\text{IDF}(t)=\ln\frac{1+N}{1+\text{DF}(t)}+1$ &amp;mdash; 어떤 단어의 IDF도 정확히 0이 되지 않도록 함(가중치가 완전히 사라지는 것을 방지)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;L2 정규화&lt;/b&gt;: 각 문서 벡터의 길이(norm)를 1로 맞춤 &amp;mdash; 문서 길이가 달라도 벡터끼리 공정하게 비교 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도(흔한 단어는 깎고 특징적인 단어는 올림)는 동일하지만, 실전에서는 0이 되는 가중치를 막고 벡터 비교를 안정시키기 위한 보정이 들어간다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. sublinear_tf : 빈도 폭주 막기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 TF는 등장 횟수에 선형 비례한다. 그런데 한 문서에 어떤 단어가 100번 나온다고 해서 1번 나온 것보다 100배 더 &quot;중요&quot;한 건 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{TF}{\text{sublinear}}(t,d) = 1 + \log(f{t,d})&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 씌워서 빈도가 늘어날수록 증가폭이 완만해지게 만든다(체감효과). TfidfVectorizer(sublinear_tf=True)로 켤 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. n-gram: 단어를 어떤 단위로 자를 것인가&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;word n-gram&lt;/b&gt; (`ngram_range=(1,2)`): 단어 1개 또는 연속 2개를 하나의 단위로. &quot;환불&quot;뿐 아니라 &quot;환불 요청&quot;도 하나의 토큰으로 봄 &amp;rarr; 단어 조합의 의미를 일부 포착&lt;/li&gt;
&lt;li&gt;&lt;b&gt;char n-gram&lt;/b&gt; (`analyzer=&quot;char_wb&quot;, ngram_range=(2,4)`): 글자 2~4개 단위로 자름. &quot;환불요청&quot; &amp;rarr; [&quot;환불&quot;, &quot;불요&quot;, &quot;요청&quot;, &quot;환불요&quot;, &quot;불요청&quot;, &quot;환불요청&quot;] 같은 조각이 만들어짐. 1글자 조각(&quot;환&quot;, &quot;불&quot;)까지 포함하려면 `ngram_range=(1,4)`로 설정해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;char n-gram은 오타&amp;middot;줄임말&amp;middot;띄어쓰기 차이에 강하다. &quot;환불요청&quot;과 &quot;환불 요청해요&quot;는 단어 단위로는 다르게 잘릴 수 있지만, 글자 단위로는 &quot;환불&quot;, &quot;요청&quot; 같은 공통 조각을 공유해서 어느 정도 유사하게 잡힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서는 word n-gram과 char n-gram을 &lt;b&gt;같이&lt;/b&gt; 쓰고 가중치를 다르게 줘서 합치는 경우가 많다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 실전에서 여러 특징을 합칠 때: 가중치 결합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 word TF-IDF 하나만 쓰기보다, &lt;b&gt;word TF-IDF + char TF-IDF + 구조적 범주형 변수&lt;/b&gt;(카테고리 등)를 합쳐서 하나의 특징 행렬로 쓰는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g.) 텍스트 분류&amp;middot;클러스터링 시스템&lt;/p&gt;
&lt;pre id=&quot;code_1782795923298&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;word_vec = TfidfVectorizer(
    analyzer=&quot;word&quot;, ngram_range=(1, 2),
    min_df=2, max_features=4000, sublinear_tf=True,
)
char_vec = TfidfVectorizer(
    analyzer=&quot;char_wb&quot;, ngram_range=(2, 4),
    min_df=3, max_features=8000, sublinear_tf=True,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 블록을 합칠 때는 각 블록의 중요도를 가중치로 조절해서 가로로 이어붙인다(`scipy.sparse.hstack`):&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$X&amp;nbsp;=&amp;nbsp;w_{\text{word}}&amp;nbsp;X_{\text{word}}&amp;nbsp;\;\Vert\;&amp;nbsp;w_{\text{char}}&amp;nbsp;X_{\text{char}}&amp;nbsp;\;\Vert\;&amp;nbsp;w_{\text{struct}}&amp;nbsp;X_{\text{struct}}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 텍스트(word/char)에 더 높은 가중치를 주고, 구조적 변수(카테고리 코드 등)는 보조 신호로만 낮은 가중치를 주는 식이 일반적.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 $w_{\text{word}}=1.0$, $w_{\text{char}}=0.7$, $w_{\text{struct}}=0.35$ 같은 비율. 텍스트가 주된 신호를 만들고, 구조적 변수는 그걸 살짝 보정하는 역할만 하게 하려는 의도다. &lt;br /&gt;&lt;br /&gt;결과물은&amp;nbsp;문서&amp;nbsp;수&amp;nbsp;&amp;times;&amp;nbsp;단어&amp;nbsp;종류&amp;nbsp;수&amp;nbsp;크기의&amp;nbsp;행렬인데,&amp;nbsp;한&amp;nbsp;문서에&amp;nbsp;모든&amp;nbsp;단어가&amp;nbsp;다&amp;nbsp;등장할&amp;nbsp;리는&amp;nbsp;없으니&amp;nbsp;대부분이&amp;nbsp;0이다(희소&amp;nbsp;행렬,&amp;nbsp;sparse&amp;nbsp;matrix).&amp;nbsp;0을&amp;nbsp;일일이&amp;nbsp;저장하면&amp;nbsp;메모리&amp;nbsp;낭비가&amp;nbsp;심해서,&amp;nbsp;`scipy.sparse`로&amp;nbsp;&quot;0이&amp;nbsp;아닌&amp;nbsp;값의&amp;nbsp;위치와&amp;nbsp;값만&quot;&amp;nbsp;저장한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. TF-IDF는&amp;nbsp;머신러닝인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄밀히 말하면 &lt;b&gt;아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝이라 부르려면 보통 &quot;명시적인 목적함수를 데이터에 맞춰 최적화하는 과정&quot;이 있어야 하는데, &lt;br /&gt;TF-IDF는 말뭉치에서 단어 등장 횟수를 세서 위 공식에 대입하는 것뿐이다. &lt;br /&gt;최소화할 목적함수도, 반복적으로 수렴시키는 과정도 없다. &lt;br /&gt;&lt;br /&gt;비교하면:&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;KMeans&lt;/b&gt;: WCSS라는 목적함수를 반복적으로 최소화 &amp;rarr; 명백한 학습&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SVD&lt;/b&gt;: 재구성 오차를 최소화하는 최적해를 구함(닫힌 형태긴 해도 명시적 최적화) &amp;rarr; 학습이라 부를 만함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TF-IDF&lt;/b&gt;: 빈도 통계를 공식에 대입 &amp;rarr; 최적화 과정 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는&amp;nbsp;통계적&amp;nbsp;특징공학(feature&amp;nbsp;engineering),&amp;nbsp;또는&amp;nbsp;전통적&amp;nbsp;정보검색(IR)&amp;nbsp;기법으로&amp;nbsp;분류하는&amp;nbsp;게&amp;nbsp;맞다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 scikit-learn에서 `fit()`/`transform()`이라는 ML 모델과 똑같은 API로 제공되고, 결과가 데이터(말뭉치)에 따라 달라지며, 실전에서 거의 항상 ML 파이프라인의 전처리 단계로 쓰이다 보니 &quot;고전적 ML&quot;의 일부처럼 취급되는 경우가 많다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. BM25: TF-IDF의 발전형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF를 그대로 쓰는 검색 시스템은 이제 거의 없다. 대부분의 현대 검색 엔진(Elasticsearch, Solr, Lucene)은 **BM25(Best Match 25)**를 기본 랭킹 함수로 쓴다. TF-IDF와 아이디어는 같지만 두 가지 핵심 문제를 보완했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 1 &amp;mdash; TF 포화(saturation)&lt;/b&gt;: 단어가 1번 나온 문서와 100번 나온 문서는 중요도 차이가 분명히 있지만, 100배까지 차이가 나진 않는다. TF-IDF는 TF에 선형 비례하거나 sublinear_tf를 써도 여전히 폭주 가능성이 있다. BM25는 TF에 포화 함수를 적용한다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{TF}{\text{BM25}}(t,d) = \frac{f{t,d} \cdot (k_1 + 1)}{f_{t,d} + k_1}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$k_1$(보통 1.2~2.0)이 포화 속도를 조절한다. $f_{t,d}$가 아무리 커져도 TF 값은 $k_1+1$에 수렴한다.&lt;br /&gt;즉, 단어가 무한히 등장해도 점수가 무한히 올라가지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 2 &amp;mdash; 문서 길이 정규화&lt;/b&gt;: 긴 문서일수록 같은 단어가 더 많이 등장할 확률이 높다. TF-IDF는 이를 문서 길이로 나누는 식(TF 정의에서)으로 보정하지만 BM25는 더 명시적으로 처리한다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{BM25}(t,d) = \text{IDF}(t) \cdot \frac{f_{t,d} \cdot (k_1 + 1)}{f_{t,d} + k_1 \cdot \left(1 - b + b \cdot \frac{|d|}{\text{avgdl}}\right)}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$|d|$는 문서 길이, $\text{avgdl}$은 전체 문서의 평균 길이, $b$(보통 0.75)가 길이 정규화 강도를 조절한다. $b=0$이면 길이 무시, $b=1$이면 완전 정규화.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전에서 TF-IDF와 BM25의 파라미터 비교:&lt;/p&gt;
&lt;table id=&quot;390c2707-14d8-80f7-857e-d1aeced922f5&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;TF-IDF&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; BM25 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-80a2-89ca-dfbf3e3fcc0a&quot;&gt;
&lt;td id=&quot;KGIA&quot;&gt;&lt;b&gt;TF 포화&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;yWTz&quot;&gt;sublinear_tf로 일부 완화&lt;/td&gt;
&lt;td id=&quot;P&amp;gt;mj&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;k1k_1&lt;/span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;으로 명시적 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-804e-a7e8-fd12857db1c5&quot;&gt;
&lt;td id=&quot;KGIA&quot;&gt;&lt;b&gt;문서 길이&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;yWTz&quot;&gt;TF 정규화로 간접 처리&lt;/td&gt;
&lt;td id=&quot;P&amp;gt;mj&quot;&gt;@import url('&lt;a href=&quot;https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex-swap.min.css')&quot;&gt;https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex-swap.min.css')&lt;/a&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;로 직접 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;390c2707-14d8-808c-8b8a-c5c4189c46bf&quot;&gt;
&lt;td id=&quot;KGIA&quot;&gt;&lt;b&gt;주요 사용처&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;yWTz&quot;&gt;scikit-learn ML 파이프라인&lt;/td&gt;
&lt;td id=&quot;P&amp;gt;mj&quot;&gt;검색 엔진(Elasticsearch 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TF-IDF = TF(단어빈도) &amp;times; IDF(역문서빈도)&lt;/li&gt;
&lt;li&gt;흔한 단어는 가중치를 깎고, 희귀한(특징적인) 단어는 가중치를 올림&lt;/li&gt;
&lt;li&gt;모든 문서에 등장하는 단어는 설명용 unsmoothed IDF에서 0이 되고, scikit-learn처럼 smoothing을 쓰면 0이 되지 않음&lt;/li&gt;
&lt;li&gt;실전에서는 sublinear_tf(빈도 폭주 완화), word/char n-gram 조합, 가중치 결합 등으로 보강해서 사용&lt;/li&gt;
&lt;li&gt;엄밀히는 머신러닝이 아니라 통계적 특징공학이지만, ML 파이프라인의 표준 전처리 단계로 취급됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF로 텍스트를 벡터로 바꿨다면, 그다음 질문은 &quot;두 벡터가 얼마나 비슷한가?&quot;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 &lt;a href=&quot;https://chaeyami.tistory.com/279&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;벡터 유사도&lt;/a&gt;(코사인 유사도)의 역할이다.&lt;/p&gt;
&lt;figure id=&quot;og_1782893856862&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ML] 벡터 유사도(Vector Similarity)&quot; data-og-description=&quot;[STUDY/ML(Machine Learning)] - [ML] TF-IDF(Term Frequency-Inverse Document Frequency) [ML] TF-IDF(Term Frequency-Inverse Document Frequency)1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &amp;quot;중요도&amp;quot;를 &quot; data-og-host=&quot;blog.chaenii.me&quot; data-og-source-url=&quot;https://chaeyami.tistory.com/279&quot; data-og-url=&quot;https://blog.chaenii.me/279&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Hk01d/dJMb9ia1WQm/KWiKy4GWFx4NW1lbW5Z1E1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/u2GGJ/dJMb8WMAB4l/8ZPMR3fLzYL3SaWUTJ7rVK/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/baUP7w/dJMb9g5lO8y/br9plkIuao79PaCrB9lMn0/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086&quot;&gt;&lt;a href=&quot;https://chaeyami.tistory.com/279&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chaeyami.tistory.com/279&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Hk01d/dJMb9ia1WQm/KWiKy4GWFx4NW1lbW5Z1E1/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/u2GGJ/dJMb8WMAB4l/8ZPMR3fLzYL3SaWUTJ7rVK/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/baUP7w/dJMb9g5lO8y/br9plkIuao79PaCrB9lMn0/img.png?width=1448&amp;amp;height=1086&amp;amp;face=0_0_1448_1086');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ML] 벡터 유사도(Vector Similarity)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[STUDY/ML(Machine Learning)] - [ML] TF-IDF(Term Frequency-Inverse Document Frequency) [ML] TF-IDF(Term Frequency-Inverse Document Frequency)1. TF-IDF?정의단어의 빈도(TF)와 역문서빈도(IDF)를 곱해서, 문서 내 각 단어의 &quot;중요도&quot;를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.chaenii.me&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TF-IDF는 &quot;텍스트&amp;rarr;숫자&quot; 변환이고, 코사인 유사도는 &quot;숫자&amp;rarr;숫자 사이 거리&quot; 계산이라는 점에서 서로 다른 단계임을 구분해두면 헷갈리지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY/ML(Machine Learning)</category>
      <category>machinelearning</category>
      <category>ML</category>
      <category>TF-IDF</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/278</guid>
      <comments>https://chaeyami.tistory.com/278#entry278comment</comments>
      <pubDate>Tue, 30 Jun 2026 14:10:08 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴] Observer Pattern</title>
      <link>https://chaeyami.tistory.com/277</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Observer Pattern?&lt;/h2&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 객체(Subject)의 상태가 바뀌면, 그 객체에 의존하는 여러 옵저버(Observers)들이 자동으로 알림을 받고 갱신되는 구조&lt;/li&gt;
&lt;li&gt;즉, &lt;b&gt;1 &amp;rarr; N(일대다)&lt;/b&gt; 의존성을 정의하는 패턴&lt;/li&gt;
&lt;li&gt;흔히 &lt;b&gt;Pub/Sub(발행/구독) 모델&lt;/b&gt;이라고도 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과&lt;/b&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;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;느슨한 결합(Loose Coupling)&lt;/b&gt; 확보&lt;/li&gt;
&lt;li&gt;새로운 옵저버를 쉽게 추가/삭제할 수 있어 &lt;b&gt;&lt;a href=&quot;https://www.notion.so/CH-1-Intro-to-Design-Pattern-25bc270714d88032963dd931ce847a68?source=copy_link#25bc270714d880fe8674fd054b4176fd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OCP(Open/Closed 원칙)&lt;/a&gt;&lt;/b&gt; 만족&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;뉴스나 유튜브 같은 것!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유튜버(Subject) &amp;harr; 구독자(Observers)&lt;/li&gt;
&lt;li&gt;유튜버가 영상 업로드 &amp;rarr; 여러명의 구독자에게 영상 업로드 알림&lt;/li&gt;
&lt;li&gt;구독자들은 -해당 채널을 구독함으로써 채널에 어떠한 변화(영상을 올리는 등)가 생기게 되면 바로 연락을 받아 탐지&lt;/li&gt;
&lt;li&gt;반면 구독 안 한 시청자에게는 알림이 가지 않고, 구독 해지하면 마찬가지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 필요한가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;변화(Change)&lt;/b&gt; 는 피할 수 없다&lt;/li&gt;
&lt;li&gt;변화가 일어나면 동시에 여러 곳에 반영해야 할 때가 많다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날씨 데이터 변경 &amp;rarr; 현재 날씨/통계/예보 화면이 자동 갱신&lt;/li&gt;
&lt;li&gt;버튼 클릭 &amp;rarr; 여러 이벤트 핸들러 실행&lt;/li&gt;
&lt;li&gt;주문 생성 &amp;rarr; 이메일 발송, 포인트 적립, 재고 차감&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Observer Pattern 기본 구조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject 인터페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`registerObserver()`, `removeObserver()`, `notifyObservers()`&lt;/li&gt;
&lt;li&gt;&amp;rarr; 옵저버 등록/제거/알림을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Observer 인터페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`update()`&lt;/li&gt;
&lt;li&gt;&amp;rarr; 상태 변경 시 호출되는 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ConcreteSubject&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 데이터를 들고 있고, 상태가 변하면 `notifyObservers()` 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ConcreteObserver&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`update()`를 구현해 변경된 상태를 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqP0bq/btsQpg5Tp9c/E0u8PyKgLn671jsKidhcIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqP0bq/btsQpg5Tp9c/E0u8PyKgLn671jsKidhcIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqP0bq/btsQpg5Tp9c/E0u8PyKgLn671jsKidhcIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqP0bq%2FbtsQpg5Tp9c%2FE0u8PyKgLn671jsKidhcIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;999&quot; height=&quot;243&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  핵심: &lt;b&gt;구현체가 아니라 인터페이스에 의존&lt;/b&gt;해서 의존성과 결합도를 낮춘다. (&lt;b&gt;Loose Coupling&lt;/b&gt;)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;느슨한 결합?&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;느슨한 결합(Loose Coupling)&lt;/b&gt;: 서로 필요한 최소한의 정보만 알아도 상호작용이 가능한 것.&lt;/li&gt;
&lt;li&gt;Subject는 Observer에 대해 &amp;ldquo;Observer 인터페이스를 구현했다&amp;rdquo; 정도만 알면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 객체가 구체적으로 뭘 하는지는 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;언제든 새로운 Observer 추가 가능&lt;/li&gt;
&lt;li&gt;언제든 Observer 제거 가능&lt;/li&gt;
&lt;li&gt;Subject는 수정할 필요 없음&lt;/li&gt;
&lt;li&gt;Observer와 Subject를 독립적으로 재사용 가능&lt;/li&gt;
&lt;li&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;디자인 원칙&lt;/b&gt;: &amp;ldquo;상호작용하는 객체들 사이에는 느슨한 결합을 유지하라.&amp;rdquo;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경(유연), 재사용성, 유지보수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;설계 핵심 요약:&lt;br /&gt;1. 옵저버는 &lt;b&gt;등록(register/subscribe)&lt;/b&gt; 해야 알림을 받는다.&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; 하도록 게약(인터페이스)만 맞춘다.&lt;br /&gt;5. &lt;b&gt;미가입 객체는 영향 없음&lt;/b&gt; &amp;rarr; 확장/교체/선택적 참여가 가능.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 패턴으로 문제 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 예시 &amp;rarr; 기상 관측&lt;/h3&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; Weather-O-Rama 회사가 인터넷 기반 기상 관측 앱을 만든다.&lt;/li&gt;
&lt;li&gt;요구사항
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;센서에서 &lt;b&gt;온도/습도/기압&lt;/b&gt;을 측정&lt;/li&gt;
&lt;li&gt;세 가지 화면 제공: &lt;b&gt;현재 상태&lt;/b&gt;, &lt;b&gt;통계&lt;/b&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;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WeatherData - Subject&lt;br /&gt;디스플레이들 - Observers&lt;/p&gt;
&lt;/blockquote&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`getTemperature()`, `getHumidity()`, `getPressure()` &amp;rarr; &lt;b&gt;가장 최근 값&lt;/b&gt;을 돌려준다.&lt;/li&gt;
&lt;li&gt;`measurementsChanged()` &amp;rarr; &amp;ldquo;&lt;b&gt;측정값이 갱신될 때 호출되는&lt;/b&gt;&amp;rdquo; 콜백 메서드. &lt;b&gt;아직 비어 있음&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;즉, 이 안에서 업데이트 메커니즘이 필요함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 잘못된 접근(직접 호출)&lt;/h3&gt;
&lt;pre id=&quot;code_1757984829996&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WeatherData {
	// instance variable declarations
	public void measurementsChanged() {
	
	    float temp = getTemperature();
	    float humidity = getHumidity();
	    float pressure = getPressure();
	
	    currentConditionsDisplay.update(temp, humidity, pressure);
	    statisticsDisplay.update(temp, humidity, pressure);
	    forecastDisplay.update(temp, humidity, pressure);
	}
	// other WeatherData methods here
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;업데이트 시점(measurementsChanged)&amp;rdquo;에 &lt;b&gt;각 화면의 update를 직접 호출&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;주제가 &lt;b&gt;구체 클래스에 직접 의존&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;새 화면이 추가될 때마다 주제 코드를 수정해야 함 (&lt;b&gt;OCP 위반&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;런타임에 디스플레이를 추가/삭제할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제 요약&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;느슨한 결합 실패/확장성 부족, 재사용성 떨어짐&lt;/li&gt;
&lt;li&gt;변화하는 축(옵저버 수&amp;middot;종류)을 캡슐화하지 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Observer Pattern!&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;계약(인터페이스) 분리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`Subject`: 등록/제거/알림&lt;/li&gt;
&lt;li&gt;`Observer`: `update()`로 값을 받음 (알림 받음)&lt;/li&gt;
&lt;li&gt;`DisplayElement`: `display()` (화면에 표시하는 책임 분리)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구현&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WeatherData &amp;rarr; Subject 구현체&lt;/li&gt;
&lt;li&gt;각 디스플레이 &amp;rarr; Observer + DisplayElement 구현체&lt;/li&gt;
&lt;li&gt;새 디스플레이를 추가해도 WeatherData 수정 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1095&quot; data-origin-height=&quot;759&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IuHkI/btsQqFRv3Nc/58iKWemKPpBR6R6FLDvUu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IuHkI/btsQqFRv3Nc/58iKWemKPpBR6R6FLDvUu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IuHkI/btsQqFRv3Nc/58iKWemKPpBR6R6FLDvUu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIuHkI%2FbtsQqFRv3Nc%2F58iKWemKPpBR6R6FLDvUu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1095&quot; height=&quot;759&quot; data-origin-width=&quot;1095&quot; data-origin-height=&quot;759&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject 인터페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`registerObserver()` / `removeObserver()` / `notifyObservers()`&lt;/li&gt;
&lt;li&gt;&amp;rarr; 관찰자 &lt;b&gt;관리 &amp;amp; 통지&lt;/b&gt; 책임 (구현 방식은 캡슐화)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Observer 인터페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`update()`&lt;/li&gt;
&lt;li&gt;&amp;rarr; 주제가 바뀌면 &lt;b&gt;알림 받고 반응&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DisplayElement 인터페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`display()`&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;화면 표시 책임 분리&lt;/b&gt; (Observer와 별도 관심사)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WeatherData (Subject 구현체)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subject 구현 + 온도/습도/기압 getter&lt;/li&gt;
&lt;li&gt;값 변경 시 &lt;b&gt;notifyObservers()&lt;/b&gt; 호출 &amp;rarr; 브로드캐스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디스플레이들 (Observers + DisplayElement)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`CurrentConditionsDisplay`, `StatisticsDisplay`, `ForecastDisplay`&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;업데이트 + 표시&lt;/b&gt; 동시에 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ThirdPartyDisplay&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observer/DisplayElement 구현만 하면 &lt;b&gt;외부 개발자도 플러그인 추가 가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;OCP (개방&amp;ndash;폐쇄 원칙)&lt;/b&gt; 실현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구조 핵심&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject/Observer&lt;/b&gt; = 알림 계약&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DisplayElement&lt;/b&gt; = 표시 계약&lt;/li&gt;
&lt;li&gt;WeatherData = Subject, 화면들 = Observer + DisplayElement&lt;/li&gt;
&lt;li&gt;생성 시 &lt;b&gt;자동 등록&lt;/b&gt;, 느슨한 결합 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;알림 흐름&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/36IqV/btsQnHQ3Ss4/in0FejzRmFg2IjkS77kXy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/36IqV/btsQnHQ3Ss4/in0FejzRmFg2IjkS77kXy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/36IqV/btsQnHQ3Ss4/in0FejzRmFg2IjkS77kXy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F36IqV%2FbtsQnHQ3Ss4%2Fin0FejzRmFg2IjkS77kXy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1391&quot; height=&quot;970&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) Push vs Pull 모델&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Push&lt;/b&gt;: `update(온도, 습도, 기압)` 등등 데이터 자체를 통지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 단순함&lt;/li&gt;
&lt;li&gt;단점: 전달 필드가 늘어나면 모든 옵저버를 수정해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pull&lt;/b&gt;:`update()`는 &amp;ldquo;업데이트됨&amp;rdquo; 신호만 받고, 옵저버가 &lt;b&gt;getter&lt;/b&gt;로 필요한 값 가져옴
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 인터페이스 안정성, 유연성&lt;/li&gt;
&lt;li&gt;단점: 옵저버가 주제 참조를 알아야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 123px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 21px;&quot;&gt;Push&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 21px;&quot;&gt;Pull&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;`update()`&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 21px;&quot;&gt;값들을 &lt;b&gt;인자로 전달&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 21px;&quot;&gt;&lt;b&gt;신호만 받고&lt;/b&gt;, 값은 &lt;b&gt;getter&lt;/b&gt;로 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 64px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 64px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 64px;&quot;&gt;옵저버 구현이 단순, 호출 1번에 끝&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 64px;&quot;&gt;인터페이스 &lt;b&gt;안정&lt;/b&gt;(필드 추가에도 시그니처 고정),&lt;br /&gt;옵저버별 &lt;b&gt;필요한 값&lt;/b&gt;만 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 17px;&quot;&gt;전달&amp;nbsp;필드가&amp;nbsp;늘면&amp;nbsp;모든&amp;nbsp;옵저버&amp;nbsp;수정&lt;/td&gt;
&lt;td style=&quot;width: 40%; height: 17px;&quot;&gt;옵저버가 주제 참조 필요, 조회 타이밍 고려&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 따라 선택&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 구현 예시 (Push 방식)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 인터페이스 분리&lt;/h3&gt;
&lt;pre id=&quot;code_1757985314807&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Subject: 옵저버 관리 + 알림
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

// Observer(푸시): 알림과 함께 값 수신
interface Observer {
    void update(float temperature, float humidity, float pressure);
}

// 화면: 출력
interface DisplayElement {
    void display();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Subject 구현체 (`WeatherData`)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누가 구독 중인가?(리스트 가지고 있음)&lt;/li&gt;
&lt;li&gt;값이 바뀌면 구독자들에게 알림!&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757985368583&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;

class WeatherData implements Subject {
    private final List&amp;lt;Observer&amp;gt; observers = new ArrayList&amp;lt;&amp;gt;();
    private float temperature;
    private float humidity;
    private float pressure;

    @Override
    public void registerObserver(Observer o) {
        if (o != null &amp;amp;&amp;amp; !observers.contains(o)) observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        // Push: 값 자체를 밀어넣어 전달
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }

    // 센서 값이 바뀔 때 호출되는 지점(훅)
    public void measurementsChanged() {
        notifyObservers();
    }

    // 테스트/실행용: 내부 상태를 바꾸고 통지
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    // (선택) 게터 &amp;mdash; push에선 필수 아님, pull 전환 시 유용
    public float getTemperature() { return temperature; }
    public float getHumidity()    { return humidity; }
    public float getPressure()    { return pressure; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Observer 구현체 (Observers + DisplayElement)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자에서 Subject 구독&lt;/li&gt;
&lt;li&gt;`update()` : 알림 받기&lt;/li&gt;
&lt;li&gt;알림 받으면 값 저장하고 출력함 (`display()`)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757985440023&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// -- 현재 상태

class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private final Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this); // 생성과 동시에 등록(플러그인 느낌)
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    @Override
    public void display() {
        System.out.println(&quot;Current: &quot; + temperature + &quot;F, &quot; + humidity + &quot;% humidity&quot;);
    }
}

// -- 통계

class StatisticsDisplay implements Observer, DisplayElement {
    private float sumTemp = 0f;
    private int numReadings = 0;
    private final Subject weatherData;

    public StatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        sumTemp += temperature;
        numReadings++;
        display();
    }

    @Override
    public void display() {
        float avg = (numReadings == 0) ? 0f : sumTemp / numReadings;
        System.out.println(&quot;Stats: avg temp = &quot; + avg);
    }
}

// -- 예보

class ForecastDisplay implements Observer, DisplayElement {
    private float currentPressure = 29.92f;
    private float lastPressure;
    private final Subject weatherData;

    public ForecastDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        lastPressure = currentPressure;
        currentPressure = pressure;
        display();
    }

    @Override
    public void display() {
        if (currentPressure &amp;gt; lastPressure) {
            System.out.println(&quot;Forecast: Improving weather on the way!&quot;);
        } else if (currentPressure == lastPressure) {
            System.out.println(&quot;Forecast: More of the same&quot;);
        } else {
            System.out.println(&quot;Forecast: Watch out for cooler, rainy weather&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실행&lt;/h3&gt;
&lt;pre id=&quot;code_1757985468284&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        new CurrentConditionsDisplay(weatherData);
        new StatisticsDisplay(weatherData);
        new ForecastDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 확장 - HeatIndexDisplay 추가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 체감온도(`HeatIndexDisplay`)도 추가하라고 한다면? (공식 존재 가정)&lt;/li&gt;
&lt;li&gt;WeatherData(&lt;b&gt;Subject&lt;/b&gt;)&lt;b&gt;는 수정하지 않고&lt;/b&gt;, 화면(Observer + DisplayElement)만 수정해야 한다. &lt;b&gt;&amp;rarr;&lt;/b&gt; &lt;span style=&quot;background-color: #ffc1c8; color: #333333;&quot;&gt;&lt;b&gt;OCP&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;class HeatIndexDisplay implements Observer, DisplayElement {
    private float heatIndex;
    private final Subject weatherData;

	//  구독 등록 
    public HeatIndexDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

	 // 업데이트 콜백 - push 방식 : Subject가 값(temperature, humidity, pressure)을 '밀어넣어' 전달
    @Override
    public void update(float t, float rh, float pressure) {
        heatIndex = computeHeatIndex(t, rh);
        display();
    }

    private float computeHeatIndex(float t, float rh) {
        
        return (float)( // 공식 );
    }

    @Override
    public void display() {
        System.out.println(&quot;Heat index: &quot; + heatIndex);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject(WeatherData) 수정 없음&lt;/b&gt;. &amp;rarr; &lt;b&gt;플러그인처럼 추가/제거&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;메시징/스트림(Rx, Kafka 소비자)은 &lt;b&gt;대규모 옵저버&lt;/b&gt;로 볼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Pull 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDK 내장(Observable) 버전 변형&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Subject&lt;/h3&gt;
&lt;pre id=&quot;code_1757985548012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Observable;

public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public void setMeasurements(float t, float h, float p) {
        this.temperature = t;
        this.humidity    = h;
        this.pressure    = p;
        measurementsChanged();
    }

	// 알림 보내기
    public void measurementsChanged() {
        setChanged();        // 반드시 먼저 호출
        notifyObservers();   // 인자 없음 &amp;rarr; PULL 모델
    }

    // PULL용 getter
    public float getTemperature() { return temperature; }
    public float getHumidity()    { return humidity; }
    public float getPressure()    { return pressure; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. HeatIndexDisplay (Observer)&lt;/h3&gt;
&lt;pre id=&quot;code_1757985558639&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Observable;
import java.util.Observer;

public class HeatIndexDisplay implements Observer, DisplayElement {
    private float heatIndex;
    private final Observable observable;   // Observable 참조를 보관(해지/재등록 대비)

    public HeatIndexDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);      //생성 시 등록
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {    // Subject 확인 후 캐스팅
            WeatherData wd = (WeatherData) o;
            float t  = wd.getTemperature();  // Pull: getter로 끌어옴
            float rh = wd.getHumidity();
            heatIndex = computeHeatIndex(t, rh);
            display();
        }
    }

    private float computeHeatIndex(float t, float rh) {
        return (float)(
            // 공식
        );
    }

    @Override
    public void display() {
        System.out.printf(&quot;Heat index: %.2f%n&quot;, heatIndex);
    }

    // (선택) 해지 지원 
    public void unsubscribe() {
        observable.deleteObserver(this);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`java.util.Observable`은 &lt;b&gt;인터페이스가 아니라 클래스 &amp;rarr; 상속해야 함.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알림은 2단계&lt;/b&gt;: `setChanged()` &amp;rarr; `notifyObservers()`&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알림 순서 비보장&lt;/b&gt;&amp;rarr; 순서 의존 로직 금지.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JDK로 Push 사용하기?&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subject:&lt;/p&gt;
&lt;pre id=&quot;code_1757985731565&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setChanged();
notifyObservers(new Measurements(temp, humidity, pressure)); // Push&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observer:&lt;/p&gt;
&lt;pre id=&quot;code_1757985737662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void update(Observable o, Object arg) {
    if (o instanceof WeatherData &amp;amp;&amp;amp; arg instanceof Measurements m) {
        heatIndex = computeHeatIndex(m.temp(), m.humidity());
        display();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 규칙은 동일: setChanged() &amp;rarr; notifyObservers(&amp;hellip;), 그리고 순서 의존 금지.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 확인해야 할 것&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;[JDK 버전] `setChanged()` 호출 누락&lt;/b&gt; &amp;rarr; `notifyObservers()`가 &lt;b&gt;무효&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구현체 의존&lt;/b&gt;: `currentConditionsDisplay.update(...)`처럼 &lt;b&gt;구체 클래스에 직접 의존&lt;/b&gt;하지 말 것 &amp;rarr; &lt;b&gt;인터페이스(Observer)&lt;/b&gt; 로.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알림 순서 가정&lt;/b&gt;: &amp;ldquo;항상 A가 먼저, 그다음 B&amp;rdquo; 같은 가정 금지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;등록/해지 누락&lt;/b&gt;: 생성 시 `registerObserver(this)`(또는 `addObserver`) 잊지 말고, 필요 시 `removeObserver` 구현/호출.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불필요한 결합&lt;/b&gt;: 표시(UI) 책임과 관찰(업데이트) 책임은 &lt;b&gt;분리&lt;/b&gt;(`DisplayElement`)가 깔끔.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 앞 Strategy 패턴과 비교&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Strategy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkwDUZ/btsQAmktXOp/a5GKN0MIcUMD9RsKZLOklK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkwDUZ/btsQAmktXOp/a5GKN0MIcUMD9RsKZLOklK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkwDUZ/btsQAmktXOp/a5GKN0MIcUMD9RsKZLOklK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkwDUZ%2FbtsQAmktXOp%2Fa5GKN0MIcUMD9RsKZLOklK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;375&quot; height=&quot;385&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byawIu/btsQzhxapRf/anF5LkFfAivpSyYoEaQz0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byawIu/btsQzhxapRf/anF5LkFfAivpSyYoEaQz0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byawIu/btsQzhxapRf/anF5LkFfAivpSyYoEaQz0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyawIu%2FbtsQzhxapRf%2FanF5LkFfAivpSyYoEaQz0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;165&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Strategy&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Observer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;동작(알고리즘) 교체&lt;/td&gt;
&lt;td&gt;상태 변화 알림(1&amp;rarr;N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결합도&lt;/td&gt;
&lt;td&gt;컨텍스트&amp;harr;전략 (구성)&lt;/td&gt;
&lt;td&gt;주제&amp;harr;옵저버 (콜백)&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;h2 data-ke-size=&quot;size26&quot;&gt;9. 구현 예시 (Java)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시나리오&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주식 가격 알림 시스템&lt;/li&gt;
&lt;li&gt;특정 주식의 가격이 변하면 &amp;rarr; 모든 구독자(앱, SMS, 이메일)에 알림.&lt;/li&gt;
&lt;li&gt;주제(Subject): Stock&lt;/li&gt;
&lt;li&gt;옵저버(Observer): Display/알림 채널&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;

// Subject 인터페이스
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

// Observer 인터페이스
interface Observer {
    void update(float price);
}

// 주식 클래스 (Subject)
class Stock implements Subject {
    private List&amp;lt;Observer&amp;gt; observers = new ArrayList&amp;lt;&amp;gt;();
    private float price;

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer o : observers) {
            o.update(price);
        }
    }

    public void setPrice(float price) {
        this.price = price;
        notifyObservers(); // 가격 변경 시 자동 알림
    }
}

// Observer: 모바일 앱 알림
class MobileApp implements Observer {
    private String name;

    public MobileApp(String name) {
        this.name = name;
    }

    @Override
    public void update(float price) {
        System.out.println(name + &quot; 앱: 주식 가격 업데이트 - &quot; + price + &quot;원&quot;);
    }
}

// Observer: 이메일 알림
class EmailAlert implements Observer {
    private String email;

    public EmailAlert(String email) {
        this.email = email;
    }

    @Override
    public void update(float price) {
        System.out.println(&quot;이메일(&quot; + email + &quot;): 가격 알림 - &quot; + price + &quot;원&quot;);
    }
}

// 실행 예시
public class Main {
    public static void main(String[] args) {
        Stock samsung = new Stock();

        MobileApp app1 = new MobileApp(&quot;1번 증권&quot;);
        MobileApp app2 = new MobileApp(&quot;2번 증권&quot;);
        EmailAlert emailAlert = new EmailAlert(&quot;user@email.com&quot;);

        // 옵저버 등록
        samsung.registerObserver(app1);
        samsung.registerObserver(app2);
        samsung.registerObserver(emailAlert);

        // 가격 변경 &amp;rarr; 자동으로 모든 옵저버에게 알림
        samsung.setPrice(55000);
        samsung.setPrice(56000);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pull로 바꾼다면?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// Observer: 주제 참조만 받음
interface Observer { void update(Subject s); }

// Stock: 값은 getter로 노출, 알림은 주체 자신을 전달
class Stock implements Subject {
    ...
    public float getPrice() { return price; }
    public void notifyObservers() {
        for (Observer o : observers) o.update(this);
    }
}

// 옵저버 쪽: 필요할 때 주제에서 끌어오기
class MobileApp implements Observer {
    public void update(Subject s) {
        float price = ((Stock) s).getPrice();
        System.out.println(name + &quot; 가격 알림 - &quot; + price + &quot;원&quot;);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 마무리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 또는 Observer 수&amp;middot;종류 &amp;rarr; 변하는 것 &amp;rarr; Subject 내부로 캡슐화&lt;/li&gt;
&lt;li&gt;Subject 코드를 건드리지 않고도 옵저버를 자유롭게 추가/제거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스 프로그래밍
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subject&amp;harr;Observer는 인터페이스로 통신(느슨한 결합)&lt;/li&gt;
&lt;li&gt;Subject는 &amp;ldquo;Observer 인터페이스를 구현한 무언가&amp;rdquo;만 알면 됨&lt;/li&gt;
&lt;li&gt;Observer는 &amp;ldquo;Subject 인터페이스&amp;rdquo;에 등록/알림만 알면 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;로 런타임 조합.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;⌜Head First Design Patterns⌟&lt;/b&gt; Eric Freeman 외, O&amp;rsquo;Reilly Media, 2004&lt;br /&gt;[Chapter ] (p.~)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;* 책을 &lt;b&gt;참고&lt;/b&gt;해 필요한 내용을 정리한 것이므로, 책의 내용과 다를 수 있습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>STUDY/Design Pattern</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/277</guid>
      <comments>https://chaeyami.tistory.com/277#entry277comment</comments>
      <pubDate>Tue, 16 Sep 2025 10:29:11 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴] Strategy Pattern</title>
      <link>https://chaeyami.tistory.com/276</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 디자인 패턴이란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정의: 반복 출현하는 설계 문제를 문제-맥락-해법 템플릿으로 정리한 이름 있는 해결책&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #c1bef9; color: #000000;&quot;&gt;Someone has already solved your problems.&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&amp;rarr; &quot;누군가 당신의 문제를 이미 해결했다&quot;: 디자인 패턴은 정해지거나 만들어진 프레임워크 또는 모듈 같은 기능이 아닌, &lt;u&gt;&lt;b&gt;선배 개발자들이 경험하며 쌓아 온 노하우&lt;/b&gt;&lt;/u&gt;라는 것.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;패턴 &amp;ne; 코드 스니펫&lt;br /&gt;&lt;/b&gt;&amp;rarr; &lt;b&gt;설계 수준의 역할&amp;middot;책임&amp;middot;협력&lt;/b&gt;에 대한 약속(의도&amp;middot;트레이드오프 포함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과&lt;/b&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀원간의 의사소통이 편리해진다. 또한 통일성을 줄 수 있음.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;e.g.) &amp;ldquo;여긴 Strategy 패턴을 사용하자&amp;rdquo; 한마디로 의도와 구조가 모두 전달됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재사용 가능한 구조&lt;/b&gt; 습득, 설계 품질(확장성/유지보수성/가독성) 상승&lt;/li&gt;
&lt;li&gt;리뷰&amp;middot;온보딩 속도 &amp;uarr;, 설계 토론 비용 &amp;darr;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;경험
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 아니라 설계 지침&lt;/li&gt;
&lt;li&gt;발명된 게 아니라 &lt;b&gt;발견된 것&lt;/b&gt; (여러 개발자가 쌓아온 경험)&lt;/li&gt;
&lt;li&gt;변화에 대응하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 필요한가?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af; color: #000000;&quot;&gt;&lt;b&gt;The one constant in soft ware development is CHANGE.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 OOP 설계 = &lt;b&gt;재사용성 + 확장성 + 유지보수성&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제 상황&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항이 바뀜 &amp;rarr; 모든 코드가 바뀜 &amp;rarr; 테스트/확장 비용, 시간 증가&lt;/li&gt;
&lt;li&gt;상속으로 공통화 &amp;rarr; &lt;b&gt;예외가 생기는 순간&lt;/b&gt; 빈 오버라이드/플래그 분기/중복 확산&lt;/li&gt;
&lt;li&gt;결과: 복잡하고 쓸데없이 긴 코드(e.g. if-else/switch 지옥), 테스트 어려움, 결합도&amp;uarr;, 버그율&amp;uarr;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ffc1c8;&quot;&gt;&lt;u&gt;&amp;rArr; &lt;b&gt;변하는 것&lt;/b&gt;을 식별해 고정된 것과 구조적으로 &lt;b&gt;분리&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OOP 개념을 잘 안다고 해도, 유연하게 만들수 있어야 한다.&lt;/li&gt;
&lt;li&gt;결국 &lt;b&gt;변하는 것과 변하지 않는 것을 분리&lt;/b&gt;하는 게 관건&lt;/li&gt;
&lt;li&gt;즉, &amp;ldquo;디자인 패턴&amp;rdquo;은 사실 &lt;b&gt;원칙을 잘 활용한 결과물&lt;/b&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Strategy Pattern&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 문제 상황&lt;/h3&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; 이 자주 바뀐다.&lt;/li&gt;
&lt;li&gt;상속으로 해결하려다 보면 하위클래스 폭발, 중복/불일치 발생, 공통 변경이 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;근본 원인&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;동작(알고리즘)&amp;rdquo;이 변한다는 사실을 &lt;b&gt;클래스 계층 내부에 고정&lt;/b&gt;해둔 것.&lt;/li&gt;
&lt;li&gt;변화하는 부분을 추상화/분리하지 않은 설계.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;책 예시 요약 &amp;rarr; &lt;a href=&quot;https://secretive-enthusiasm-4ee.notion.site/SimUDuck-262c270714d880bebb26e579d1bc0251&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;오리 게임&lt;/a&gt;&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;오리 시뮬레이션 기본 설계: &lt;u&gt;상속 기반&lt;/u&gt;, 처음엔 괜찮아 보임&lt;/li&gt;
&lt;li&gt;새로운 요구사항(fly) 추가 &amp;rarr; RubberDuck, DecoyDuck 같은 특수 케이스에서, &lt;u&gt;모든 오리가 날아버리는 문제 발생&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;오버라이딩으로 임시 해결 &amp;rarr; 클래스가 늘어나며 유지보수 지옥&lt;/li&gt;
&lt;li&gt;인터페이스 시도 &amp;rarr; 코드 중복, 관리 어려움&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffc1c8; color: #000000;&quot;&gt;&lt;b&gt;&amp;rarr; 첫 번째 설계 원칙 : 변하는 것과 변하지 않는 것을 분리하라&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 인터페이스&lt;/h3&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;&lt;/li&gt;
&lt;li&gt;목표:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각각 오리마다 Behavior(날기/울기)을 &lt;b&gt;자유롭게&lt;/b&gt; 할당
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Duck&lt;/code&gt; 인스턴스마다 서로 다른 &lt;b&gt;Behavior 구현체&lt;/b&gt;를 연결 가능해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;런타임에도 교체 가능&lt;/b&gt;해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(예: ModelDuck은 처음엔 못 날다가, 나중에 로켓 추진 Behavior로 교체 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&amp;rarr; &lt;b&gt;&lt;code&gt;Duck&lt;/code&gt;에 Behavior setter를 두어 동적 교체 가능&lt;/b&gt;하게 설계.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffc1c8; color: #000000;&quot;&gt;&lt;b&gt;&amp;rarr; 두 번째 설계 원칙 : 구현이 아닌 인터페이스(추상 타입) 에 맞춰서 프로그래밍하라&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작을 나타내는 타입(Behavior)&lt;/b&gt; 을 부모 클래스에서 분리 &amp;rarr; &lt;b&gt;독립 인터페이스&lt;/b&gt;로 정의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;FlyBehavior&lt;/code&gt;, &lt;code&gt;QuackBehavior&lt;/code&gt; = Behavior &lt;b&gt;인터페이스 &amp;rarr; Strategy&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오리는 &lt;b&gt;구현 세부사항을 모른다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단지 &amp;ldquo;&lt;code&gt;fly()&lt;/code&gt; 메시지를 보낼 수 있는 객체&amp;rdquo;라는 사실(= 인터페이스 계약)만 알고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;호출 시 &amp;rarr; &lt;b&gt;인터페이스에 위임&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Strategy (인터페이스)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 알고리즘 규격 (공통 계약)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Strategy 구현체 (Concrete Strategy)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 알고리즘의 실제 구현 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Context (ex. Duck)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: Strategy를 &amp;ldquo;has-a&amp;rdquo; 관계로 보유하고 실행 시 위임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: Context에 어떤 Strategy 구현체를 사용할지 주입/교체&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO52Id/btsQlocZJ0c/SMV3wgTtZlIrOJWVBGjkI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO52Id/btsQlocZJ0c/SMV3wgTtZlIrOJWVBGjkI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO52Id/btsQlocZJ0c/SMV3wgTtZlIrOJWVBGjkI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO52Id%2FbtsQlocZJ0c%2FSMV3wgTtZlIrOJWVBGjkI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;302&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Program to an interface?&lt;/h3&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;code&gt;interface&lt;/code&gt; 키워드&lt;/b&gt;여야 하는 건 아님&lt;/li&gt;
&lt;li&gt;핵심은 &lt;b&gt;&amp;ldquo;구체 타입에 묶이지 않고, 추상(supertype)에 맞춰 코딩&amp;rdquo;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상 클래스도 가능&lt;/li&gt;
&lt;li&gt;중요한 건 &lt;b&gt;구현이 아닌 추상에 의존&lt;/b&gt;한다는 점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현에 맞춘 경우&lt;/h4&gt;
&lt;pre id=&quot;code_1757062751083&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Dog d = new Dog();
d.bark();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수 타입이 Dog &amp;rarr; 코드가 Dog에 고정됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;추상에 맞춘 경우&lt;/h4&gt;
&lt;pre id=&quot;code_1757062857976&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Animal animal = new Dog();
animal.makeSound();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수 타입이 Animal &amp;rarr; Cat으로 교체해도 호출부는 수정할 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;런타임 바인딩&lt;/h4&gt;
&lt;pre id=&quot;code_1757062867066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Animal a = getAnimal(); // 런타임에 어떤 동물일지 모름
a.makeSound();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 생성까지 하드코딩하지 않고, 팩토리/DI 등을 활용 &amp;rarr; 결합도&amp;darr;, 확장성&amp;uarr;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오리 게임 예시에서&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;두 인터페이스 정의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;FlyBehavior { fly(); }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QuackBehavior { quack(); }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Behavior 구현 클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날기:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;FlyWithWings&lt;/code&gt; : 날개로 날 수 있음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FlyNoWay&lt;/code&gt; : 날 수 없음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FlyRocketPowered&lt;/code&gt; : 로켓으로 날 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;울기:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Quack&lt;/code&gt; : 오리 울음소리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Squeak&lt;/code&gt; : 장난감 오리 소리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MuteQuack&lt;/code&gt; : 무음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;설계 효과
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오리 외의 다른 객체도 &lt;code&gt;FlyBehavior&lt;/code&gt;, &lt;code&gt;QuackBehavior&lt;/code&gt; 재사용 가능&lt;/li&gt;
&lt;li&gt;새 동작(Behavior)을 추가해도 기존 Duck이나 기존 Behavior 구현을 수정할 필요 없음 &amp;rarr; &lt;b&gt;OCP&lt;/b&gt; 만족&lt;/li&gt;
&lt;li&gt;상속 대신 &lt;b&gt;Composition + Delegation&lt;/b&gt; 으로 유연성과 확장성 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Strategy Pattern으로 오리 게임 완성하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조 통합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Duck&lt;/code&gt; (Context)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;FlyBehavior&lt;/code&gt;, &lt;code&gt;QuackBehavior&lt;/code&gt; 필드를 가짐 &amp;rarr; &lt;code&gt;perform&lt;/code&gt; 메서드에서 Behavior에 위임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구체 오리(&lt;code&gt;MallardDuck&lt;/code&gt;, &lt;code&gt;ModelDuck&lt;/code&gt; 등) : 생성자에서 기본 Behavior 세팅&lt;/li&gt;
&lt;li&gt;Behavior 인터페이스 + 구현 클래스: 필요 시 런타임에 교체 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;FlyBehavior&lt;/code&gt; &amp;rarr; &lt;code&gt;FlyWithWings&lt;/code&gt;, &lt;code&gt;FlyNoWay&lt;/code&gt;, &lt;code&gt;FlyRocketPowered&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QuackBehavior&lt;/code&gt; &amp;rarr; &lt;code&gt;Quack&lt;/code&gt;, &lt;code&gt;Squeak&lt;/code&gt;, &lt;code&gt;MuteQuack&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1757290724408&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// -- 인터페이스

  interface FlyBehavior {void fly();}

  interface QuackBehavior {void quack();}


  // -- FlyBehavior 구현 클래스

  class FlyWithWings implements FlyBehavior {
      @Override
      public void fly() {
          System.out.println(&quot;I'm flying!!&quot;);
      }
  }

  class FlyNoWay implements FlyBehavior {
      @Override
      public void fly() {
          System.out.println(&quot;I can't fly&quot;);
      }
  }

  class FlyRocketPowered implements FlyBehavior {
      @Override
      public void fly() {
          System.out.println(&quot;I'm flying with a rocket!&quot;);
      }
  }


  // -- QuackBehavior 구현 클래스

  class Quack implements QuackBehavior {
      @Override
      public void quack() {
          System.out.println(&quot;Quack&quot;);
      }
  }

  class MuteQuack implements QuackBehavior {
      @Override
      public void quack() {
          System.out.println(&quot;&amp;lt;&amp;lt; Silence &amp;gt;&amp;gt;&quot;);
      }
  }

  class Squeak implements QuackBehavior {
      @Override
      public void quack() {
          System.out.println(&quot;Squeak&quot;);
      }
  }

  // -- Context = 전략을 사용하는 주체
  // 행동은 Duck이 직접 구현하지 않고 전략에 위임
  abstract class Duck {
      private FlyBehavior flyBehavior;       // 현재 장착된 '날기' 전략
      private QuackBehavior quackBehavior;   // 현재 장착된 '울기' 전략

      public Duck() {}

      public abstract void display();

      public void performFly() {
          if (flyBehavior == null) throw new IllegalStateException(&quot;flyBehavior not set&quot;);
          flyBehavior.fly();
      }

      public void performQuack() {
          if (quackBehavior == null) throw new IllegalStateException(&quot;quackBehavior not set&quot;);
          quackBehavior.quack();
      }

      public void swim() {
          System.out.println(&quot;All ducks float, even decoys!&quot;);
      }

      // 런타임 교체(Setter)
      public void setFlyBehavior(FlyBehavior fb) { this.flyBehavior = fb; }
      public void setQuackBehavior(QuackBehavior qb) { this.quackBehavior = qb; }

      // 하위 클래스/팩토리에서 초기 장착용(선택)
      protected void initBehaviors(FlyBehavior fb, QuackBehavior qb) {
          this.flyBehavior = fb;
          this.quackBehavior = qb;
      }
  }

  // -- 서브클래스
  class MallardDuck extends Duck {
      public MallardDuck() {
          initBehaviors(new FlyWithWings(), new Quack());
      }
      @Override
      public void display() {
          System.out.println(&quot;I'm a real Mallard duck&quot;);
      }
  }

  class ModelDuck extends Duck {
      public ModelDuck() {
          initBehaviors(new FlyNoWay(), new Quack());
      }
      @Override
      public void display() {
          System.out.println(&quot;I'm a model duck&quot;);
      }
  }

  // Demo = Strategy 패턴의 유연성 확인
  // - MallardDuck: 고정된 날개 비행/꽥
  // - ModelDuck: 런타임에 로켓 비행으로 교체
  public class MiniDuckSimulator {
      public static void main(String[] args) {
          Duck mallard = new MallardDuck();
          mallard.display();
          mallard.performQuack();   // Quack
          mallard.performFly();     // I'm flying!!
          mallard.swim();
          System.out.println();

          Duck model = new ModelDuck();
          model.display();
          model.performFly();       // I can't fly
          // 런타임 교체
          model.setFlyBehavior(new FlyRocketPowered());
          model.performFly();       // I'm flying with a rocket!
          model.performQuack();     // Quack
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. HAS-A&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존: &lt;code&gt;Duck IS-A Flying/Quacking thing&lt;/code&gt; &amp;rarr; &lt;b&gt;상속&lt;/b&gt;으로 행동 구현&lt;/li&gt;
&lt;li&gt;개선: &lt;code&gt;Duck HAS-A FlyBehavior, QuackBehavior&lt;/code&gt; &amp;rarr; &lt;b&gt;구성(Composition)&lt;/b&gt; 으로 행동 보유&lt;/li&gt;
&lt;li&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;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재사용 가능&lt;/b&gt; (다른 클래스에서도 활용)&lt;/li&gt;
&lt;li&gt;상속처럼 계층 구조에 묶이지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffc1c8; color: #000000;&quot;&gt;&lt;b&gt;&amp;rarr; 세 번째 설계 원칙 : 상속보다는 객체 조합을 선택하자.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 핵심 설계 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 나왔던 핵심 설계 원칙을 모아보면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Identify the aspects of your application that vary and separate them from what stays the same.&lt;/b&gt;&lt;br /&gt;(변하는 것과 변하지 않는 것을 분리하라)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변하는 부분은 캡슐화해서 독립시킨다.&lt;/li&gt;
&lt;li&gt;상속은 공통화에는 좋지만 예외 케이스가 생기면 유지보수성이 급격히 떨어짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Program to an interface, not an implementation.&lt;br /&gt;&lt;/b&gt;(구현이 아닌 인터페이스에 맞춰 프로그래밍)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호출자는 &amp;ldquo;무엇을 할 수 있나(계약)&amp;rdquo;만 알고, &amp;ldquo;어떻게 하느냐(구현)&amp;rdquo;는 모른다.&lt;/li&gt;
&lt;li&gt;다형성을 극대화.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Favor composition over inheritance.&lt;br /&gt;&lt;/b&gt;(상속보다는 객체 조합)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HAS-A 관계를 통해 조립/교체 가능하게 한다.&lt;/li&gt;
&lt;li&gt;런타임 교체, 조합 다양화 가능.&lt;/li&gt;
&lt;li&gt;결합도&amp;darr;, 응집도&amp;uarr;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보조 원칙 (연계)&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;개방-폐쇄 원칙(OCP)&lt;/b&gt;: 확장에는 열려 있고 변경에는 닫혀 있어야 한다. &amp;rarr; 확장시에도 기존 코드 변경X&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단일 책임 원칙(SRP)&lt;/b&gt;: 한 모듈은 한 가지 이유로만 바뀌어야 한다. &amp;rarr; 기능이 하나의 책임만&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 구현 예시 (Java)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시나리오&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결제 시스템을 만든다고 가정.&lt;/li&gt;
&lt;li&gt;결제 방식이 여러 가지 있음 (카드, 무통장 입금, 카카오페이, 네이버페이 등)&lt;/li&gt;
&lt;li&gt;결제 방법은 계속 추가/변경될 수 있음 &amp;rarr; 변화 부분을 캡슐화해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// Strategy 인터페이스
public interface PaymentStrategy {
    void pay(int amount);
}

// 구체 전략: 카드 결제
public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + &quot;원을 신용카드(&quot; + cardNumber + &quot;)로 결제했습니다.&quot;);
    }
}

// 구체 전략: 페이팔 결제
public class PaypalPayment implements PaymentStrategy {
    private String email;

    public PaypalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + &quot;원을 PayPal(&quot; + email + &quot;)로 결제했습니다.&quot;);
    }
}

// Context 클래스: 결제 처리기
public class Order {
    private PaymentStrategy paymentStrategy;

    // 실행 중에 결제 전략을 바꿀 수 있음
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        if (paymentStrategy == null) {
            throw new IllegalStateException(&quot;결제 방식이 선택되지 않았습니다.&quot;);
        }
        paymentStrategy.pay(amount);
    }
}

// 실행 예시
public class Main {
    public static void main(String[] args) {
        Order order = new Order();

        // 카드 결제
        order.setPaymentStrategy(new CreditCardPayment(&quot;1234-5678-9012-3456&quot;));
        order.checkout(10000);

        // 실행 중에 전략을 교체
        order.setPaymentStrategy(new PaypalPayment(&quot;user@example.com&quot;));
        order.checkout(20000);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 마무리 (정리)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OO Principles (원칙)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Encapsulate what varies (변하는 것 캡슐화)&lt;/li&gt;
&lt;li&gt;Favor composition over inheritance (상속보다 합성)&lt;/li&gt;
&lt;li&gt;Program to interfaces, not implementations (인터페이스에 맞춰 프로그래밍)&lt;/li&gt;
&lt;li&gt;  이 3가지는 앞으로 모든 디자인 패턴에 반복적으로 적용될 핵심 원칙.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OO Patterns (패턴)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지 배운 것 = &lt;b&gt;Strategy Pattern&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;핵심: 알고리즘을 캡슐화 + 교체 가능 + 클라이언트 독립&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Strategy 패턴은 설계 원칙을 실천한 결과물&lt;/b&gt;이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변하는 것(알고리즘)을 분리하고 &amp;rarr; 인터페이스에 맞춰 프로그래밍 &amp;rarr; 상속 대신 합성으로 유연성 확보.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알고리즘을 자유롭게 교체 가능 (런타임까지 포함)&lt;/li&gt;
&lt;li&gt;코드 중복, 조건문 지옥 방지&lt;/li&gt;
&lt;li&gt;OCP/SRP 등 객체지향 원칙 준수&lt;/li&gt;
&lt;li&gt;팀 내 &amp;ldquo;전략(Strategy) 패턴&amp;rdquo;이라는 이름만으로 설계 의도를 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디자인 패턴은 &amp;ldquo;새로운 문법이나 트릭&amp;rdquo;이 아니라, &lt;b&gt;좋은 설계 원칙을 반복 검증한 결과&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Strategy는 그중 가장 기본적이고 강력한 패턴으로, 이후 나올 다른 패턴들의 이해를 위한 기초가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;⌜Head First Design Patterns⌟&lt;/b&gt; Eric Freeman 외, O&amp;rsquo;Reilly Media, 2004&lt;br /&gt;[Chapter 1] Intro to Design Pattern (p.1~35)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;* 책을 &lt;b&gt;참고&lt;/b&gt;해 필요한 내용을 정리한 것이므로, 책의 내용과 다를 수 있습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>STUDY/Design Pattern</category>
      <author>ChaeYami</author>
      <guid isPermaLink="true">https://chaeyami.tistory.com/276</guid>
      <comments>https://chaeyami.tistory.com/276#entry276comment</comments>
      <pubDate>Mon, 8 Sep 2025 10:15:56 +0900</pubDate>
    </item>
  </channel>
</rss>