<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>seora_dev</title>
    <link>https://seoldev.tistory.com/</link>
    <description>데이터를 이용해 가치 있는 서비스를 기획하고 개발하는 것들을 공부하고 있습니다</description>
    <language>ko</language>
    <pubDate>Sun, 17 May 2026 15:27:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>seoraroong</managingEditor>
    <image>
      <title>seora_dev</title>
      <url>https://tistory1.daumcdn.net/tistory/6929521/attach/cb200d6374e74ddf89995ad7efe90c48</url>
      <link>https://seoldev.tistory.com</link>
    </image>
    <item>
      <title>[NLP/LLM] LLM 캐시의 작동 원리를 알아보고 OpenAI API로 캐시를 구현해보자</title>
      <link>https://seoldev.tistory.com/113</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LLM Cache&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIuqmU/btsMxFaOABU/4VUSWXP31T2Y9p4IeO50Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIuqmU/btsMxFaOABU/4VUSWXP31T2Y9p4IeO50Z0/img.png&quot; data-alt=&quot;LLM Application Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIuqmU/btsMxFaOABU/4VUSWXP31T2Y9p4IeO50Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIuqmU%2FbtsMxFaOABU%2F4VUSWXP31T2Y9p4IeO50Z0%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;602&quot; height=&quot;461&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LLM Application Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 캐시는 LLM의 응답을 저장하고 재사용하는 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 같은 질문 (프롬프트)에 대한 답변을 매번 새로 생성하지 않고 이전에 생성된 결과를 캐싱하여 빠르게 응답할 수 있도록 하는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상업용 API를 사용하거나 직접 모델을 서빙해 LLM을 추론할 수 있는데, 두 가지 모두 추론을 가능한 한 줄이는 것이 자원이나 비용 측면에서 효율적이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LLM Cache를 왜 사용할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 비용 절감&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OPENAI API 같은 유료 LLM 서비스를 사용할 때, 같은 질문을 반복하면 불필요한 비용이 발생한다.&lt;/p&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;(2) 속도 향상&lt;/b&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;캐싱을 이용하면 즉시 결과를 반환할 수 있어 사용자 경험을 향상시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) 시스템 안정성 향상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM API 서버가 일시적으로 다운되거나 속도가 느려지더라고 캐시된 응답을 반환하면 서비스 지속 가능성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 일관된 응답 유지&lt;/b&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;캐싱을 이용하면 동일한 프롬프트에 대해 일관된 응답을 제공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LLM Cache의 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 캐시는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;일치 캐시(exact cache)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;유사 검색 캐시(similar cache)&lt;/span&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 일치 캐시 (exact cache)&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;문자열 그대로 동일한지 판단하기 때문에 파이썬 딕셔너리와 같은 자료 구조에 프롬프트와 그에 대한 응답을 저장하고, 새로운 요청이 들어왔을 때 딕셔너리의 key에 동일한 프롬프트가 있는지 확인한다.&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;빠르고 간단한 방식이며 정밀한 캐싱이 가능하다는 장점이 있지만, 띄어쓰기나 단어 순서가 조금만 달라도 다른 요청으로 인식된다는 단점이 있다.&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;예를 들어보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;What is AI?&quot;, &quot;What is AI&quot;, &quot;what is ai?&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;그래서 일치 캐시를 사용하기 위해서는 소문자 변환, 공백 제거등의 입력 정규화 과정을 중요하게 고려해야한다.&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;&lt;b&gt;- 유사 검색 캐시 (similar cache)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유사 검색 캐시는 이전에 유사한 요청이 있었는지 확인하는 방식으로, 문자열을 그대로 비교하지 않고 문자열을 임베딩 모델을 통해 변환한 &lt;u&gt;임베딩 벡터로 변환 후 유사도를 계산&lt;/u&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;What is AI?&quot; 와 &quot;Tell me about artificial intelligence?&quot;는 문자열 자체는 다르지만 의미적으로 유사한 질문이기 때문에 캐싱을 이용해 동일한 응답을 제공할 수 있다.&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%; height: 140px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;일치 캐시 (exact cache)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;유사 검색 캐시 (similar cache)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;캐시 검색 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;문자열 일치 비교&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;벡터 임베딩을 이용한 유사 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;매우 빠름&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;벡터 연산으로 인해 상대적으로 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;정확도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;정확히 같은 프롬프트만 캐싱&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;의미적으로 유사한 질문도 캐싱 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 40px; text-align: center;&quot;&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 40px; text-align: center;&quot;&gt;문자 하나만 달라져도 다른 요청으로 취급하기에 유연성이 낮음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 40px; text-align: center;&quot;&gt;비슷한 질문도 같은 응답을 제공하기에 유연성이 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;사용 예시&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;동일한 질문을 자주 받는 챗봇&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OpenAI API로 캐시를 구현해보자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습은 &quot;LLM을 활용한 실전&amp;nbsp; AI 애플리케이션 개발&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;파이썬 딕셔너리와 Chroma를 사용해 캐시 기능을 구현하는 실습이다.&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;&lt;b&gt;- OpenAI와 Chroma 클라이언트 생성하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740574468471&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
import chromadb
from openai import OpenAI

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;openai api key 입력&quot;

openai_client = OpenAI()
chroma_client = chromadb.Client()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI 클라이언트는 언어 모델과 임베딩 모델을 사용하기 위한 것이고, Chroma 클라이언트는 임베딩 벡터를 저장하고 검색하기 위한 것이다.&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;&lt;b&gt;- LLM 캐시를 사용하지 않았을 때 동일한 요청에 대해 걸린 시간을 확인해보기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740574954969&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import time

def response_text(openai_resp):
  return openai_resp.choices[0].message.content

question = &quot;북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?&quot;

for _ in range(2):
  start_time = time.time()
  response = openai_client.chat.completions.create(
      model=&quot;gpt-3.5-turbo&quot;,
      messages=[
          {
              'role': 'user',
              'content': question
          }
      ],
  )
  
  response = response_text(response)
  print(f&quot;질문: {question}&quot;)
  print(&quot;소요 시간: {:.2f}s&quot;.format(time.time() - start_time))
  print(f&quot;답변: {response}\n&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740575004560&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;질문: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?
소요 시간: 2.04s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은 주로 가을에서 겨울 사이인 
10월부터 3월까지입니다. 이 기간 동안 한반도 지역은 추위가 깊어지며, 맑은 날씨가 많이 이어지는 
특징을 보입니다.

질문: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?
소요 시간: 1.97s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은 주로 가을부터 겨울까지이며, 
주로 11월부터 3월까지 머무르는 것으로 알려져 있습니다. 이 기간 동안 한반도와 주변 지역은 추운 날씨와 
함께 이들 기단의 영향을 받아 한파와 눈으로 인한 날씨 변화가 발생할 수 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 질문을 두 번 요청했을 때 걸린 시간은 각각 2.04초, 1.97초이다.&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;일치 캐시를 구현하면 요청 시간에 어떤 변화가 있을까?&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;&lt;b&gt;- 파이썬 딕셔너리 자료구조를 이용한 일치 캐시 구현하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740575581830&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OpenAICache:
  def __init__(self, openai_client):
    self.openai_client = openai_client
    self.cache = {} # 딕셔너리
  
  def generate(self, prompt):
    if prompt not in self.cache:
      response = self.openai_client.chat.completions.create(
          model = &quot;gpt-3.5-turbo&quot;,
          messages = [
              {
                  'role': 'user',
                  'content': prompt
              }
          ],
      )
      self.cache[prompt] = response_text(response)
    return self.cache[prompt]

openai_cache = OpenAICache(openai_client)

question = &quot;북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 시간은?&quot;

for _ in range(2):
  start_time = time.time()
  response = openai_cache.generate(question)
  print(f&quot;질문: {question}&quot;)
  print(&quot;소요 시간: {:.2f}s&quot;.format(time.time() - start_time))
  print(f&quot;답변: {response}\n&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740575832052&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;질문: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 시간은?
소요 시간: 1.75s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 시간은 보통 약 3~5일 정도입니다. 
이 기단들이 만날 때는 대기가 불안정해져서 비 또는 폭우가 내릴 확률이 높아지며, 기온 변화도 크게 
일어날 수 있습니다. 이러한 기단들이 국내에 머무르는 기간 동안은 날씨가 불안정하고 변화무쌍할 수 
있으니, 주의가 필요합니다.

질문: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 시간은?
소요 시간: 0.00s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 시간은 보통 약 3~5일 정도입니다. 
이 기단들이 만날 때는 대기가 불안정해져서 비 또는 폭우가 내릴 확률이 높아지며, 기온 변화도 크게 
일어날 수 있습니다. 이러한 기단들이 국내에 머무르는 기간 동안은 날씨가 불안정하고 변화무쌍할 수 
있으니, 주의가 필요합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 입력으로 받은 프롬프트는 self.cache에 없기 때문에 1.75초가 소요되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 첫 번째 프롬프트로부터 응답을 받고, 해당 프롬프트가 self.cache에 저장되었기 때문에 다음 두 번째 요청에서는 0.00초가 걸린 것을 확인할 수 있다.&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;&lt;b&gt;- 유사 검색 캐시 구현하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 파이썬 딕셔너리로 일치 캐시를 구현한 OpenAICache 클래스에 기능을 추가하면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740624554732&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OpenAICache:
  def __init__(self, openai_client):
    self.openai_client = openai_client
    self.cache = {} 
    self.semantic_cache = semantic_cache
  
  def generate(self, prompt):
    if prompt not in self.cache:
      similar_doc = self.semantic_cache.query(query_texts=[prompt], n_results=1)
      if len(similar_doc['distances'][0]) &amp;gt; 0 and similar_doc['distances'][0][0] &amp;lt; 0.2:
        return similar_doc['metadatas'][0][0]['response']
      else:
        response = self.openai_client.chat.completions.create(
          model = &quot;gpt-3.5-turbo&quot;,
          messages = [
              {
                  'role': 'user',
                  'content': prompt
              }
          ],
      )
      self.cache[prompt] = response_text(response)
      self.semantic_cache.add(documents=[prompt],
                              metadatas=[{&quot;response&quot;:response_text(response)}], ids=[prompt])
    return self.cache[prompt]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;self.semantic_cache를 추가한 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;generate 메서드에서 일치 캐시를 통해 동일한 프롬프트가 있는지 확인하는 과정을 거치고, 동일한 프롬프트가 있다면 저장된 결과를 반환하고 없다면 유사 검색 캐시를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chroma 벡터 데이터베이스의 query 메서드에 query_text를 입력하면 벡터 데이터베이스에 등록된 임베딩 모델을 사용해 텍스트를 임베딩 벡터로 변환하고 검색을 수행하게 된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740633138624&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
import time
openai_ef = OpenAIEmbeddingFunction(
    api_key=os.environ[&quot;OPENAI_API_KEY&quot;],
    model_name=&quot;text-embedding-ada-002&quot;
)

semantic_cache = chroma_client.get_or_create_collection(name=&quot;semantic_cache&quot;,
                                                 embedding_function=openai_ef, metadata={&quot;hnsw:space&quot;: &quot;cosine&quot;})

openai_cache = OpenAICache(openai_client, semantic_cache)

questions = [&quot;북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?&quot;,
             &quot;북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?&quot;,
             &quot;북태평양 기단과 오호츠크해 기단이 만나 한반도에 머무르는 기간은?&quot;,
             &quot;국내에 북태평양 기단과 오호츠크해 기단이 함께 머무르는 기간은?&quot;]

for question in questions:
  start_time = time.time()
  response = openai_cache.generate(question)
  print(f&quot;질문: {question}&quot;)
  print(&quot;소요 시간: {:.2f}s&quot;.format(time.time() - start_time))
  print(f&quot;답변: {response}\n&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740633164586&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;질문: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?
소요 시간: 0.61s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은 보통 3~4주 정도입니다. 
이 기간 동안 한반도 지역에 추위가 찾아와 날씨가 매우 추워지는 현상이 발생하게 됩니다.

질문: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은?
소요 시간: 0.61s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은 보통 3~4주 정도입니다. 
이 기간 동안 한반도 지역에 추위가 찾아와 날씨가 매우 추워지는 현상이 발생하게 됩니다.

질문: 북태평양 기단과 오호츠크해 기단이 만나 한반도에 머무르는 기간은?
소요 시간: 0.16s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은 보통 3~4주 정도입니다. 
이 기간 동안 한반도 지역에 추위가 찾아와 날씨가 매우 추워지는 현상이 발생하게 됩니다.

질문: 국내에 북태평양 기단과 오호츠크해 기단이 함께 머무르는 기간은?
소요 시간: 0.18s
답변: 북태평양 기단과 오호츠크해 기단이 만나 국내에 머무르는 기간은 보통 3~4주 정도입니다.
이 기간 동안 한반도 지역에 추위가 찾아와 날씨가 매우 추워지는 현상이 발생하게 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Learning/NLP</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/113</guid>
      <comments>https://seoldev.tistory.com/113#entry113comment</comments>
      <pubDate>Thu, 27 Feb 2025 14:13:18 +0900</pubDate>
    </item>
    <item>
      <title>[NLP/LLM] RAG(Retrieval Augumented Generation)에 대해 알아보고 라마 인덱스로 구현해보자</title>
      <link>https://seoldev.tistory.com/112</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RAG (Retrieval Augumented Generation, 검색 증강 생성)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;871&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z3hDS/btsMvSWwMkQ/CSPhiJIPYGAxS1AbndnnY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z3hDS/btsMvSWwMkQ/CSPhiJIPYGAxS1AbndnnY1/img.png&quot; data-alt=&quot;LLM Application Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z3hDS/btsMvSWwMkQ/CSPhiJIPYGAxS1AbndnnY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ3hDS%2FbtsMvSWwMkQ%2FCSPhiJIPYGAxS1AbndnnY1%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;552&quot; height=&quot;417&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;871&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LLM Application Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT가 없는 사실이나 거짓말을 그렇듯하게 만들어내는 현상을 &lt;b&gt;환각(Hallucination)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환각을 줄이기 위해 LLM이 답변할 때 필요한 정보를 프롬프트에 함께 전달하는 검색 증강 생성(Retrieval Augumented Generation)을 사용할 수 있다.&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;Data Source에서 검색하고자하는 데이터를 가져와 Embedding Model을 통해 Embedding Vector로 만들고 Vector Database에 저장하는 과정을 검색 증강 생성이라고 한다.&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;LLM에게 단순한 질문이나 요청만 전달하고 생성하는 것이 아니라, 답변에 필요한 충분한 정보와 맥락을 제공하고 답변하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변에 필요한 정보를 검색(retrieval)을 통해 선택하기 때문에 RAG라는 이름이 붙기도 했다.&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;LLM Application Architecture에서 볼 수 있는 LLM Orchestration Tools는 User Interface, Embedding Model, Vector Database 등의 LLM 애플리케이션을 위한 다양한 구성요소를 연결하는 프레임워크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 라마인덱스(LlamaIndex), 랭체인(LangChain) 등이 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGSzv/btsMxqdFz77/Am1MQUMG2yBqa25GQYuCRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGSzv/btsMxqdFz77/Am1MQUMG2yBqa25GQYuCRK/img.png&quot; data-alt=&quot;RAG Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGSzv/btsMxqdFz77/Am1MQUMG2yBqa25GQYuCRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGSzv%2FbtsMxqdFz77%2FAm1MQUMG2yBqa25GQYuCRK%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;556&quot; height=&quot;455&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RAG Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;Data source&lt;/span&gt;는 텍스트, 이미지와 같은 비정형 데이터가 저장된 데이터 저장소이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data source의 텍스트를 Embedding Model을 사용해 Embedding Vector로 변환하고, 변환된 벡터는 Vector Database에 저장된다.&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;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;Embedding Model&lt;/span&gt;은 텍스트나 이미지같은 비정형 데이터를 입력했을 때 &lt;u&gt;그 의미를 담은 Embedding Vector로 변환&lt;/u&gt;하는 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, 컴퓨터는 자연어의 의미를 직접 이해하지 못하므로 Embedding Model을 통해 의미를 반영한 숫자 형태의 Embedding Vector로 변환해 처리할 수 있도록 하는 것이다.&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;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;Vector Database&lt;/span&gt;는 Embedding Vector의 저장소로, 입력한 벡터와 유사한 벡터를 찾는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 크로마(Chroma), 밀버스(Milvus)와 같은 오픈 소스, 파인콘(Pinecone), 위비에이트(Weaviate) 같은 상업 서비스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강력한 관계형데이터베이스로 알려진 PostgreSQL에서도 pgvector와 같은 벡터 검색 기능을 도입하고 있다.&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;정리하면, 특정 문서를 Embedding Model을 통해 Embedding Vector로 변환하고 Vector Database에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 문장 (검색 쿼리)으로 검색을 수행하는 경우, Embedding Model을 통해 검색 쿼리도 벡터로 변환해 Vector Database에서 위치를 찾고 Query Embedding과 가장 가까운 벡터를 찾게된다.&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;전체적인 흐름을 보면, 사용자의 요청을 Embedding Model을 통해 Embedding Vector로 변환한 후 Vector Database에서 검색 벡터와 가장 가까운 벡터를 받아서 검색 결과를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과는 Prompt Module에서 사용자의 요청과 하나로 통합되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Conventional LLM vs. RAG based LLM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 LLM은 대량의 데이터를 학습한 후 파라미터에 정보를 저장하는 방식으로 동작한다.&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;&lt;b&gt;- Conventional LLM의 호출 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;전통적인 LLM은 학습된 데이터를 기반으로 답변을 생성한다.&lt;/span&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 들면, 2023년 이후의 정보를 알지 못하는 ChatGPT 버전에게 &quot;2024년의 노벨 물리학상 수상자를 알려줘&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;&lt;b&gt;- RAG based LLM의 호출 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 기반의 LLM은 최신 정보가 필요한 경우, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;외부 데이터베이스에서 관련 문서를 검색&lt;/span&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;라마인덱스로 RAG를 구현해보자!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 LLM Orchestration 라이브러리인 라마인덱스를 사용해 KLUE MRC 데이터셋을 활용한 질문-답변 RAG를 구현하는 실습이다.&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;&lt;a href=&quot;https://www.llamaindex.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.llamaindex.ai/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740561318659&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;LlamaIndex - Build Knowledge Assistants over your Enterprise Data&quot; data-og-description=&quot;LlamaIndex is a simple, flexible framework for building knowledge assistants using LLMs connected to your enterprise data.&quot; data-og-host=&quot;www.llamaindex.ai&quot; data-og-source-url=&quot;https://www.llamaindex.ai/&quot; data-og-url=&quot;https://www.llamaindex.ai/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pi6SG/hyYjuIJEqQ/hcECPl57AQpgu4NaJUUU5K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bYDFLb/hyYjExJug5/6poWKzrX2FBlsMHjMx36sk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.llamaindex.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.llamaindex.ai/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pi6SG/hyYjuIJEqQ/hcECPl57AQpgu4NaJUUU5K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bYDFLb/hyYjExJug5/6poWKzrX2FBlsMHjMx36sk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;LlamaIndex - Build Knowledge Assistants over your Enterprise Data&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LlamaIndex is a simple, flexible framework for building knowledge assistants using LLMs connected to your enterprise data.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.llamaindex.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 실습 환경: Google Colab&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 준비물: OpenAI API Key&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 데이터셋: Hugging Face - KLUE MRC&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;&lt;b&gt;- 라이브러리 세팅&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740562720006&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!pip install datasets llama-index==0.10.34 langchain-openai==0.1.6 
&quot;nemoguardrails[openai]==0.8.0&quot; openai==1.25.1 chromadb==0.5.0 wandb==0.16.6 
llama-index-callbacks-wandb==0.1.2 -qqq&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;b&gt;데이터 100개 추출 후 임베딩 벡터로 변환해 벡터 데이터베이스에 저장하기&amp;nbsp;&lt;/b&gt;과정에서 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740564061105&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TypeError                                 Traceback (most recent call last)
&amp;lt;ipython-input-2-e4289b6d5f0f&amp;gt; in &amp;lt;cell line: 0&amp;gt;()
      5 
      6 # index 만들기
----&amp;gt; 7 index = VectorStoreIndex.from_documents(documents)

13 frames
/usr/local/lib/python3.11/dist-packages/openai/_base_client.py in __init__(self, version, base_url, max_retries, timeout, transport, proxies, limits, http_client, custom_headers, custom_query, _strict_response_validation)
    744             _strict_response_validation=_strict_response_validation,
    745         )
--&amp;gt; 746         self._client = http_client or httpx.Client(
    747             base_url=base_url,
    748             # cast to a valid type because mypy doesn't understand our type narrowing

TypeError: Client.__init__() got an unexpected keyword argument 'proxies'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류의 원인을 찾아보니 openai 라이브러리에서 proxies 매개변수를 지원하지 않는 버전을 사용하고 있기 때문이란다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링해보니 proxies 매개변수를 openai에서 삭제했다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아낸 방법은 openai 라이브러리를 다운그레이드하고, httpx 라이브러리를 명시해주는 것이었다..! (ㅠㅠ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740564215900&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!pip install datasets llama-index==0.10.34 langchain-openai==0.1.6 
&quot;nemoguardrails[openai]==0.8.0&quot; openai==1.59.6 chromadb==0.5.0 wandb==0.16.6 
llama-index-callbacks-wandb==0.1.2 httpx==0.27.2 -qqq&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 데이터셋 다운로드 및 API 키 설정하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740561958365&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from datasets import load_dataset
import os

os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;발급받은 키 값 입력&quot;

dataset = load_dataset('klue', 'mrc', split='train')
dataset[0]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xSd5c/btsMy4ge6XY/KZq6OZzrPdps8DI7g5ZIt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xSd5c/btsMy4ge6XY/KZq6OZzrPdps8DI7g5ZIt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xSd5c/btsMy4ge6XY/KZq6OZzrPdps8DI7g5ZIt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxSd5c%2FbtsMy4ge6XY%2FKZq6OZzrPdps8DI7g5ZIt1%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;1585&quot; height=&quot;363&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 확인해보면, 질문(question)과 맥락(context) 컬럼을 확인할 수 있다.&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;&lt;b&gt;- 데이터 100개 추출 후 임베딩 벡터로 변환해 벡터 데이터베이스에 저장하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740564243879&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from llama_index.core import Document, VectorStoreIndex

text_list = dataset[:100]['context']
documents = [Document(text=t) for t in text_list]

# index 만들기
index = VectorStoreIndex.from_documents(documents)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgwid3/btsMzeJQT3b/yCpPrPxlZFWWtw6R5KjdN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgwid3/btsMzeJQT3b/yCpPrPxlZFWWtw6R5KjdN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgwid3/btsMzeJQT3b/yCpPrPxlZFWWtw6R5KjdN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgwid3%2FbtsMzeJQT3b%2FyCpPrPxlZFWWtw6R5KjdN1%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;580&quot; height=&quot;94&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터셋에서 100개의 context를 추출하고 라마인덱스의 Document 클래스의 text 인자에 context 데이터를 전달해 라마 인덱스가 어떤 텍스트를 임베딩 벡터로 변환할지 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라마인덱스는 기본 임베딩 모델로 OpenAI의 text-embedding-ada-002 모델을 사용하고 기본 벡터 데이터베이스로 인메모리 방식을 사용한다고 한다.&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;VectorStoreIndex 클래스의 from_document()로 Document 클래스로 생성한 documents를 입력으로 라마인덱스가 내부적으로 텍스트를 임베딩으로 변환해 데이터베이스에 저장하게 된다.&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;&lt;b&gt;- 100개의 기본 기사 본문 (context) 데이터에서 질문과 가까운 기사 찾기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740569883141&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(dataset[0]['question'])

retrieval_engine = index.as_retriever(similarity_top_k=5, verbose=True)
response = retrieval_engine.retrieve(
    dataset[0]['question']
)
print(len(response))
print(response[0].node.text)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEzVir/btsMxp0eNQL/MJGXUZZvK7FJXvhOOkvsf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEzVir/btsMxp0eNQL/MJGXUZZvK7FJXvhOOkvsf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEzVir/btsMxp0eNQL/MJGXUZZvK7FJXvhOOkvsf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEzVir%2FbtsMxp0eNQL%2FMJGXUZZvK7FJXvhOOkvsf0%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;1564&quot; height=&quot;82&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100개의 기사 본문을 저장한 벡터 데이터베이스에서 예시 질문과 유사한 기사 본문을 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기사 본문을 저장한 인덱스를 벡터 검색에 사용할 수있도록 as_retriever 메서드를 이용해 검색 엔진으로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 가장 가까운 5개의 기사를 반환하도록 similarity_top_k 인자에 5을 전달했는데, 출력값은 4가 나왔다.&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;&lt;b&gt;- 라마인덱스를 활용해 검색 증강 생성 수행하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740570125969&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;query_engine = index.as_query_engine(similarity_top_k=1)
response = query_engine.query(
    dataset[0]['question']
)
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lrJ9b/btsMwcm3vNf/W1lemTkGMVFe20fp2jB721/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lrJ9b/btsMwcm3vNf/W1lemTkGMVFe20fp2jB721/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lrJ9b/btsMwcm3vNf/W1lemTkGMVFe20fp2jB721/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlrJ9b%2FbtsMwcm3vNf%2FW1lemTkGMVFe20fp2jB721%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;292&quot; height=&quot;40&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&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>Deep Learning/NLP</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/112</guid>
      <comments>https://seoldev.tistory.com/112#entry112comment</comments>
      <pubDate>Wed, 26 Feb 2025 20:43:39 +0900</pubDate>
    </item>
    <item>
      <title>[Transformers] Hungging Face Transformers 라이브러리를 통해 모델 학습하기 (feat. Trainer API vs. PyTorch)</title>
      <link>https://seoldev.tistory.com/111</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hugging Face Transformers&lt;/b&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;span style=&quot;background-color: #f6e199;&quot;&gt; transformers 라이브러리&lt;/span&gt;, 원하는 데이터셋을 가져다 쓸 수 있도록 하는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;datasets 라이브러리&lt;/span&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;&lt;b&gt;- BERT와 GPT-2 모델을 활용하기 위한 허깅페이스 트랜스포머 코드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740449476216&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import AutoModel, AutoTokenizer

text = &quot;What's the goal of transformer models?&quot;

# BERT
bert_model = AutoModel.from_pretrained(&quot;bert-base-uncased&quot;) # BERT 모델 불러오기
bert_tokenizer = AutoTokenizer.from_pretrained(&quot;bert-base-uncased&quot;) # 토크나이저 불러오기
encoded_input = bert_tokenizer(text, return_tensors=&quot;pt&quot;) # 입력 text 토큰화
bert_output = bert_model(**encoded_input) # BERT 모델에 입력

# GPT-2
gpt_model = AutoModel.from_pretrained(&quot;gpt2&quot;) # GPT-2 모델 불러오기
gpt_tokenizer = AutoTokenizer.from_pretrained(&quot;gpt2&quot;) # 토크나이저 불러오기
encoded_input = gpt_tokenizer(text, return_tensors=&quot;pt&quot;) # 입력 text 토큰화
gpt_output = gpt_model(**encoded_input)&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hugging Face Library 사용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 모델 활용하기&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허깅페이스에서는 모델을 &lt;b&gt;바디(Body)&lt;/b&gt;와 &lt;b&gt;헤드(Head)&lt;/b&gt;로 구분한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 바디를 가져와서 사용하되, 본인의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;태스크에 맞게 헤드를 선택해 사용&lt;/span&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 동일한 BERT 모델의 바디를 가져온 뒤, 텍스트 분류나 토큰 분류와 같은 서로 다른 태스크에 맞는 헤드를 가져와 사용할 수 있다.&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;&lt;b&gt;-&amp;nbsp; (1) 모델 바디만 불러오기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740450070106&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import AutoModel

model_id = 'klue/roberta-base'
model = AutoModel.from_pretrained(model_id)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* AutoModel: 모델의 바디를 불러오는 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* KLUE/RoBERTa: RoBERTa 모델을 한국어로 학습한 모델&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;&lt;b&gt;- (2) 분류 헤드가 붙어 있는 모델 불러오기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740450354221&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import AutoModelForSequenceClassification

model_id = 'SamLove/roberta-base-go_emotions'
Classification_model = AutoModelForSequenceClassification.from_pretrained(model_id)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* SamLove/roberta-base-go_emotions: 입력 문장이 어떤 감성을 나타내는지 분류하는 분류 헤드가 포함된 RoBERTa 모델&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 토크나이저 활용하기&lt;/b&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;필요한 경우에는 특수 토큰을 추가할 수도 있다.&amp;nbsp;&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;&lt;b&gt;- 토크나이저 불러오기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740452274854&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import AutoTokenizer

model_id = 'klue/roberta-base'
tokenizer = AutoTokenizer.from_pretrained(model_id)&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;토크나이저에 텍스트를 입력하면 다음을 반환하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- input_ids: 토큰 아이디 리스트&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;gt; 각 토큰이 토크나이저 사전의 몇 번째 항목인지 나타낸다.&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;ex. input_ids가 0인 경우 [CLS] 토큰에 대응&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;- attention_mask: 토큰이 실제 텍스트인지 아니면 길이를 맞추기 위해 추가한 패딩(Padding)인지 알려준다.&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; &amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt; ex. attention_mask 값이 1인 경우 패딩이 아닌 실제 토큰&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;- token_type_ids: 토큰이 속한 문장의 아이디&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; &amp;nbsp; &amp;nbsp; -&amp;gt; ex. token_type_ids가 0이면 첫 번째 문장임을 의미한다.&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;&lt;b&gt;- 토크나이저 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740455102346&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tokenized = tokenizer(&quot;토크나이저는 텍스트를 토큰 단위로 나누는 역할을 합니다.&quot;)
print(tokenized)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ffmbm/btsMt5H3y3U/gshicFjCwDyYwKUKojdNlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ffmbm/btsMt5H3y3U/gshicFjCwDyYwKUKojdNlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ffmbm/btsMt5H3y3U/gshicFjCwDyYwKUKojdNlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFfmbm%2FbtsMt5H3y3U%2FgshicFjCwDyYwKUKojdNlK%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;1144&quot; height=&quot;40&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o3n0q/btsMwrW1kDw/84jd3EuWuhce4udFATW5YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o3n0q/btsMwrW1kDw/84jd3EuWuhce4udFATW5YK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o3n0q/btsMwrW1kDw/84jd3EuWuhce4udFATW5YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo3n0q%2FbtsMwrW1kDw%2F84jd3EuWuhce4udFATW5YK%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;608&quot; height=&quot;32&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cskJ9x/btsMt8xYCLN/QpNdslSwCKeRNueMjKKVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cskJ9x/btsMt8xYCLN/QpNdslSwCKeRNueMjKKVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cskJ9x/btsMt8xYCLN/QpNdslSwCKeRNueMjKKVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcskJ9x%2FbtsMt8xYCLN%2FQpNdslSwCKeRNueMjKKVaK%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;563&quot; height=&quot;35&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740455270016&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(tokenizer.convert_ids_to_tokens(tokenized['input_ids']))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1491&quot; data-origin-height=&quot;45&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enEFGZ/btsMw4AsbKO/SX5WoMKJbqufRUGUli5zYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enEFGZ/btsMw4AsbKO/SX5WoMKJbqufRUGUli5zYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enEFGZ/btsMw4AsbKO/SX5WoMKJbqufRUGUli5zYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenEFGZ%2FbtsMw4AsbKO%2FSX5WoMKJbqufRUGUli5zYK%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;926&quot; height=&quot;28&quot; data-origin-width=&quot;1491&quot; data-origin-height=&quot;45&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740455368657&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(tokenizer.decode(tokenized['input_ids']))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BuCas/btsMt8kvl9G/gKy8hrVQXhIHapkjcZJgLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BuCas/btsMt8kvl9G/gKy8hrVQXhIHapkjcZJgLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BuCas/btsMt8kvl9G/gKy8hrVQXhIHapkjcZJgLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBuCas%2FbtsMt8kvl9G%2FgKy8hrVQXhIHapkjcZJgLK%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;501&quot; height=&quot;31&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;decode 메서드를 사용하면 토큰 아이디를 다시 텍스트로 변환할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740455439586&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(tokenizer.decode(tokenized['input_ids'], skip_special_tokens=True))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjTyC/btsMvdrWB5k/uPoyCtcopnIufb54yRDst1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjTyC/btsMvdrWB5k/uPoyCtcopnIufb54yRDst1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjTyC/btsMvdrWB5k/uPoyCtcopnIufb54yRDst1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjTyC%2FbtsMvdrWB5k%2FuPoyCtcopnIufb54yRDst1%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;444&quot; height=&quot;31&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;skip_special_tokens 옵션을 True로 설정하면 [CLS], [SEP]와 같은 특수 토큰을 제외한 채 텍스트로 변환한다.&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;&lt;b&gt;- 토크나이저에 여러 문장 넣기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740455642121&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tokenized = tokenizer(['첫 번째 문장입니다.', '두 번째 문장입니다.'])
print(tokenized)&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;999&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPgrZR/btsMueyn4Kr/yektAGre9Y0RD0cgJWSdzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPgrZR/btsMueyn4Kr/yektAGre9Y0RD0cgJWSdzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPgrZR/btsMueyn4Kr/yektAGre9Y0RD0cgJWSdzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPgrZR%2FbtsMueyn4Kr%2FyektAGre9Y0RD0cgJWSdzk%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;33&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GcEf0/btsMtGBJ8zX/ctPWVNhI8ISRFKo9drlb9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GcEf0/btsMtGBJ8zX/ctPWVNhI8ISRFKo9drlb9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GcEf0/btsMtGBJ8zX/ctPWVNhI8ISRFKo9drlb9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGcEf0%2FbtsMtGBJ8zX%2FctPWVNhI8ISRFKo9drlb9K%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;696&quot; height=&quot;32&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QqdaJ/btsMvSU0Iuu/4arFztN4ZHPdo83Dof6ULk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QqdaJ/btsMvSU0Iuu/4arFztN4ZHPdo83Dof6ULk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QqdaJ/btsMvSU0Iuu/4arFztN4ZHPdo83Dof6ULk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQqdaJ%2FbtsMvSU0Iuu%2F4arFztN4ZHPdo83Dof6ULk%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;655&quot; height=&quot;30&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&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;만약 2개의 문장이 서로 원인과 결과 관계인지 학습시키고자 한다면 두 문장을 한 번에 모델의 입력으로 넣어주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는&lt;b&gt; 2개의 문장이 하나의 데이터라는 것을 표현&lt;/b&gt;하기 위해 아래 예제와 같이 &lt;b&gt;한 번 더 리스트로 감싸주어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740455933063&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tokenized = tokenizer([['첫 번째 문장입니다.', '두 번째 문장입니다.']])
print(tokenized)&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;949&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ee1t4x/btsMugwdKwh/qJd0bOKkQ6feqvXkL7092k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ee1t4x/btsMugwdKwh/qJd0bOKkQ6feqvXkL7092k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ee1t4x/btsMugwdKwh/qJd0bOKkQ6feqvXkL7092k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fee1t4x%2FbtsMugwdKwh%2FqJd0bOKkQ6feqvXkL7092k%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;949&quot; height=&quot;31&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4josd/btsMwZ0jXGu/gSN8rLLPHpKke8LGtHluak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4josd/btsMwZ0jXGu/gSN8rLLPHpKke8LGtHluak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4josd/btsMwZ0jXGu/gSN8rLLPHpKke8LGtHluak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4josd%2FbtsMwZ0jXGu%2FgSN8rLLPHpKke8LGtHluak%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;627&quot; height=&quot;29&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qLPTn/btsMuwewukM/cFR94f8MjmihZINIdwA8W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qLPTn/btsMuwewukM/cFR94f8MjmihZINIdwA8W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qLPTn/btsMuwewukM/cFR94f8MjmihZINIdwA8W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqLPTn%2FbtsMuwewukM%2FcFR94f8MjmihZINIdwA8W0%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;632&quot; height=&quot;32&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&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;여러 문장을 넣는 첫 번째 예제가 2개의 리스트를 반환하는 결과값과 다르게, 결과가 하나로 반환되는 것을 확인할 수 있다.&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;그렇다면 두 가지 예제 토큰을 다시 문자열로 복원하면 어떻게 될까?&lt;/p&gt;
&lt;pre id=&quot;code_1740456760713&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;first_tokenized_result = tokenizer(['첫 번째 문장입니다.', '두 번째 문장입니다.'])['input_ids']
first_decode_results = tokenizer.batch_decode(first_tokenized_result)
print(first_decode_results)

second_tokenized_result = tokenizer([['첫 번째 문장입니다.', '두 번째 문장입니다.']])['input_ids']
second_decode_results = tokenizer.batch_decode(second_tokenized_result)
print(second_decode_results)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yggwk/btsMvxqc1Kz/KG2803CXauReApMYWVFioK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yggwk/btsMvxqc1Kz/KG2803CXauReApMYWVFioK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yggwk/btsMvxqc1Kz/KG2803CXauReApMYWVFioK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyggwk%2FbtsMvxqc1Kz%2FKG2803CXauReApMYWVFioK%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;543&quot; height=&quot;49&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&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;2개의 문장을 각각 모델의 입력으로 넣어준 첫 번째 예제의 경우, 각 문장마다 [CLS], [SEP] 토큰이 붙어 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 2개의 문장을 하나의 입력으로 넣어준 두 번째 예제의 경우, [SEP] 토큰으로 두 문장을 구분한다는 것을 알 수 있다.&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;&lt;b&gt;- BERT 토크나이저와 RoBERTa 토크나이저 비교하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740457297908&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bert_tokenizer = AutoTokenizer.from_pretrained('klue/bert-base')
bert_results = bert_tokenizer([['첫 번째 문장입니다.', '두 번째 문장입니다.']])
print(bert_results)

roberta_tokenizer = AutoTokenizer.from_pretrained('klue/roberta-base')
roberta_results = roberta_tokenizer([['첫 번째 문장입니다.', '두 번째 문장입니다.']])
print(roberta_results)&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;946&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5oubC/btsMvTGxNMa/TqeKiTMmXcwihA188jLRtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5oubC/btsMvTGxNMa/TqeKiTMmXcwihA188jLRtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5oubC/btsMvTGxNMa/TqeKiTMmXcwihA188jLRtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5oubC%2FbtsMvTGxNMa%2FTqeKiTMmXcwihA188jLRtk%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;946&quot; height=&quot;55&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT 토크나이저로 토큰화한 결과를 보면 [2, .... , 3] 인 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 [2]는 문장의 시작을 나타내는 CLS 토큰, [3]은 문장의 끝을 나타내는 SEP 토큰이다.&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;RoBERTa 토크나이저로 토큰화한 결과를 보면 [0, ..., 2] 인 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 [0]은 문장의 시작을 나타내는 BOS 토큰, [2]는 문장의 끝을 나타내는 EOS 토큰이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mbvvG/btsMwclvCAo/arHQLkdJWors6mQxDdc7KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mbvvG/btsMwclvCAo/arHQLkdJWors6mQxDdc7KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mbvvG/btsMwclvCAo/arHQLkdJWors6mQxDdc7KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmbvvG%2FbtsMwclvCAo%2FarHQLkdJWors6mQxDdc7KK%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;580&quot; height=&quot;50&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT 토크나이저로 토큰화한 결과를 보면 token_type_ids가 0과 1로 나누어져 있는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 문장은 0, 두 번째 문장은 1이 된다.&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;RoBERTa의 경우 token_type_ids를 사용하지 않고 문장 구분을 자동으로 처리하기 때문에 모두 0인 것을 알 수 있다.&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;gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt; BERT&lt;/b&gt;는 문장 분리를 명확하게 하기 위해 &lt;b&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;token_type_ids&lt;/span&gt;&lt;/b&gt;를 사용하지만, &lt;b&gt;RoBERTa&lt;/b&gt;는 이를 &lt;b&gt;제거해 더 단순한 토큰화&lt;/b&gt;를 수행한다는 차이를 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분은 BERT가 2개의 문장이 이어지는지 맞추는 학습을 할 때 &lt;b&gt;NSP(Next Sentence Prediction)&lt;/b&gt;을 사용한다는 사실을 기억한다면 빠르게 이해할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVhgBs/btsMvcT90wu/9qP2Tiey5NqCgs0qOkGGJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVhgBs/btsMvcT90wu/9qP2Tiey5NqCgs0qOkGGJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVhgBs/btsMvcT90wu/9qP2Tiey5NqCgs0qOkGGJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVhgBs%2FbtsMvcT90wu%2F9qP2Tiey5NqCgs0qOkGGJ1%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;600&quot; height=&quot;50&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;attention_mask 값은 두 모델 모두 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;attention_mask가 1이면 해당 토큰이 패딩이 아니며, 모델이 해당 토큰을 고려해야한다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 데이터셋 활용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;datasets 라이브러리를 사용하면 허깅페이스 허브에서 데이터셋을 코드로 불러올 수 있다.&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;&lt;b&gt;- KLUE MRC 데이터셋 다운로드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740458749907&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from datasets import load_dataset

klue_mrc_dataset = load_dataset('klue', 'mrc')
print(klue_mrc_dataset)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6fky7/btsMv7xVGKl/ekFjwfzDqXYdNjkshBqKTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6fky7/btsMv7xVGKl/ekFjwfzDqXYdNjkshBqKTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6fky7/btsMv7xVGKl/ekFjwfzDqXYdNjkshBqKTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6fky7%2FbtsMv7xVGKl%2FekFjwfzDqXYdNjkshBqKTk%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;1180&quot; height=&quot;394&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;klue_mrc_dataset의 내용을 확인하면 train, validation 데이터가 각각 17554, 5841 개가 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 title, context, question, answers와 같은 컬럼이 존재한다는 것을 확인할 수 있다.&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;허깅페이스의 데이터셋 저장소에 있는 데이터만 불러올 수 있는 것이 아니라, 로컬에 있는 파일이나 파이썬 객체를 데이터셋으로 변환해 사용할 수도 있다!&lt;/p&gt;
&lt;pre id=&quot;code_1740459128047&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from datasets import load_dataset
from datasets import Dataset

# 로컬의 csv 데이터 파일 활용하기
dataset = load_dataset(&quot;csv&quot;, data_files=&quot;my_file.csv&quot;)

# 파이썬 딕셔너리 활용하기
my_dict = {&quot;a&quot;: [1, 2, 3]}
dataset = Dataset.from_dict(my_dict)

# Pandas 데이터프레임 활용하기
import pandas as pd
df = pd.DataFrame({&quot;a&quot;: [1, 2, 3]})
dataset = Dataset.from_pandas(df)&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모델 학습시키기&lt;/b&gt;&lt;/h3&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 데이터 준비&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740459394444&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;klue_tc_train = load_dataset('klue', 'ynat', split='train')
klue_tc_eval = load_dataset('klue', 'ynat', split='validation')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nXD5i/btsMvAtQ58z/kenlm9DbewKk3GJNNNINp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nXD5i/btsMvAtQ58z/kenlm9DbewKk3GJNNNINp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nXD5i/btsMvAtQ58z/kenlm9DbewKk3GJNNNINp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnXD5i%2FbtsMvAtQ58z%2Fkenlm9DbewKk3GJNNNINp0%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;526&quot; height=&quot;93&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train 데이터를 확인해보면 뉴스 제목(title), 카테고리(label) 등 컬럼으로 이루어진 45678개의 데이터가 존재함을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc7prI/btsMt8dVXib/uKdZblktkHkQ3SQD7m1u20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc7prI/btsMt8dVXib/uKdZblktkHkQ3SQD7m1u20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc7prI/btsMt8dVXib/uKdZblktkHkQ3SQD7m1u20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc7prI%2FbtsMt8dVXib%2FuKdZblktkHkQ3SQD7m1u20%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;970&quot; height=&quot;118&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&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;label 값이 숫자로 되어 있는데 각 숫자가 의미하는 카테고리명이 무엇인지 출력해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740459632061&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;klue_tc_train.features['label'].names&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OzZkB/btsMvfXJHvY/dqgiPOt4q6HNSUFbyrh6wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OzZkB/btsMvfXJHvY/dqgiPOt4q6HNSUFbyrh6wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OzZkB/btsMvfXJHvY/dqgiPOt4q6HNSUFbyrh6wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOzZkB%2FbtsMvfXJHvY%2FdqgiPOt4q6HNSUFbyrh6wk%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;528&quot; height=&quot;35&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터셋의 정보를 저장하고 있는 features 속성으로 label 컬럼의 항목별 이름을 확인할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과를 통해 첫 번째 데이터의 label은 '생활문화' 카테고리였음을 알 수 있다.&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;&lt;b&gt;- 불필요한 컬럼 제거하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 모델을 학습시킬 때 title, label을 제외한 컬럼들은 필요하지 않기 때문에 제거해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740459801540&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;klue_tc_train = klue_tc_train.remove_columns(['guid', 'url', 'date'])
klue_tc_eval = klue_tc_eval.remove_columns(['guid', 'url', 'date'])&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;&lt;b&gt;- 카테고리 매핑하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리가 숫자로 되어 있어 확인하기 어려우므로 label_str 컬럼을 추가해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;features 속성에서 label 컬럼을 확인해보면 레이블 ID와 카테고리를 연결할 수 있는 &lt;b&gt;ClassLabel&lt;/b&gt; 객체가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClassLabel 객체에는 ID를 카테고리로 변환하는 &lt;b&gt;int2str&lt;/b&gt; 메서드가 있는데, 이를 활용해 아이디 3을 입력하면 '생활문화' 카테고리를 뱉어내도록 변환할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740460713754&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(klue_tc_train.features['label'])

print(klue_tc_train.features['label'].int2str(3))

klue_tc_label = klue_tc_train.features['label']

def make_str_label(batch):
    batch['label_str'] = klue_tc_label.int2str(batch['label'])
    return batch  

klue_tc_train = klue_tc_train.map(make_str_label, batched=True, batch_size=1000)
print(klue_tc_train[0])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8wfNe/btsMuTHoyp3/oqkQMeMkK16kkU4HMq63kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8wfNe/btsMuTHoyp3/oqkQMeMkK16kkU4HMq63kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8wfNe/btsMuTHoyp3/oqkQMeMkK16kkU4HMq63kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8wfNe%2FbtsMuTHoyp3%2FoqkQMeMkK16kkU4HMq63kK%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;725&quot; height=&quot;97&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;- train/validation/test 데이터셋 분할하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train_test_split 메서드를 사용해 test_size에 맞춰 학습 데이터셋과 테스트 데이터셋을 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 데이터 10000개, 검증 데이터 1000개, 테스트 데이터 1000개로 분리했다.&lt;/p&gt;
&lt;pre id=&quot;code_1740461182760&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;train_dataset = klue_tc_train.train_test_split(test_size=10000, shuffle=True, seed=0)['test']

dataset = klue_tc_eval.train_test_split(test_size=1000, shuffle=True, seed=0)
test_dataset = dataset['test']

valid_dataset = dataset['train'].train_test_split(test_size=1000, shuffle=True, seed=0)['test']&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Trainer API를 사용한 학습: (1) 준비&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허깅페이스는 학습에 필요한 다양한 기능(데이터 로더 준비, logging, 평가, 저장) 등을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;학습 인자(TrainingArguments)만으로 쉽게 활용할 수 있는 Trainer API를 제공&lt;/span&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740461899197&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import torch
import numpy as np
from transformers import Trainer, TrainingArguments, AutoModelForSequenceClassification, AutoTokenizer

def tokenize_function(examples):
  return tokenizer(examples['title'], padding=&quot;max_length&quot;, truncation=True)

model_id = &quot;klue/roberta-base&quot;
model = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=len(train_dataset.features['label'].names))
tokenizer = AutoTokenizer.from_pretrained(model_id)

train_dataset =train_dataset.map(tokenize_function, batched=True)
valid_dataset = valid_dataset.map(tokenize_function, batched=True)
test_dataset = test_dataset.map(tokenize_function, batched=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습에 사용할 분류 모델을 불러오기 위해 AutoModelForSequenceClassification 클래스로 klue/roberta-base 모델을 불러왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 분류 헤드의 분류 클래스 수를 지정하기 위해 num_labels 인자에 데이터셋의 레이블 수를 사용했다.&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;&lt;b&gt;- Trainer API를 사용한 학습: (2) 학습 인자와 평가 함수 준비&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740462257179&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;training_args = TrainingArguments(
    output_dir=&quot;./results&quot;,
    num_train_epochs=1,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    evaluation_strategy=&quot;epoch&quot;,
    learning_rate=5e-5,
    push_to_hub=False,
    report_to=&quot;none&quot;,
)

def compute_metrics(eval_pred):
  logits, labels = eval_pred
  predictions = np.argmax(logits, axis=-1)
  return {&quot;accuracy&quot;: (predictions == labels).mean()}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습에 사용할 인자를 설정하는 TrainingArguments에 학습 인자를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에포크 수는 3, 배치 크기는 8, 결과는 results 폴더에 저장하고 한 에포크 학습이 끝날 때마다 검증 데이터셋에 대한 평가를 수행하도록 evaluation_strategy를 epoch로 설정했다.&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;&lt;b&gt;compute_metrics&lt;/b&gt;를 정의해 학습에 대한 평가 지표를 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델의 예측 결과인 eval_pred를 입력으로 받아 예측 결과 중 가장 큰 값을 갖는 클래스를 np.argmax 함수로 뽑아 predictions에 저장하고, predictions와 정답이 저장된 labels가 같은 값을 갖는 결과의 비율을 정확도로 반환하도록 했다.&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;report_to='none' 인자는Trainer의 wandb를 사용하지 않도록 하기 위함이다.&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;&lt;b&gt;- Trainer API를 사용한 학습: (3) 학습 진행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740462585180&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=valid_dataset,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)

trainer.train()

trainer.evaluate(test_dataset)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trainer에 준비한 데이터셋과 설정을 인자로 전달한 뒤 train() 으로 학습을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습이 끝나면 evaluate() 로 테스트 데이터셋에 대한 평가를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX7xJe/btsMvO6D5BR/PS7v9jVpYRUQQzPoiWFJRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX7xJe/btsMvO6D5BR/PS7v9jVpYRUQQzPoiWFJRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX7xJe/btsMvO6D5BR/PS7v9jVpYRUQQzPoiWFJRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX7xJe%2FbtsMvO6D5BR%2FPS7v9jVpYRUQQzPoiWFJRK%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;621&quot; height=&quot;292&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과를 확인한 결과 약 86%의 정확도로 기사의 카테고리를 분류한 것을 알 수 있다!&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;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Trainer API는 추상화를 통해 간편하게 학습을 할 수 있다는 장점이 있으나, 내부 동작을 파악하기 어렵다는 단점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Trainer API를 사용하지 않고 직접 학습 코드를 작성해보고, 두 가지 방식의 차이를 이해해보려고 한다.&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;&lt;b&gt;- Trainer API를 사용하지 않는 학습: (1) 모델과 토크나이저 준비&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740464003252&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import torch
from tqdm.auto import tqdm
from torch.utils.data import DataLoader
from transformers import AdamW

def tokenizer_function(examples):
	return tokenizer(examples[&quot;title&quot;], padding=&quot;max_length&quot;, truncation=True)

# 모델과 토크나이저 불러오기
device = torch.device(&quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;)
model_id = &quot;klue/roberta-base&quot;
model = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=len(train_dataset.features['label'].names))
tokenizer = AutoTokenizer.from_pretrained(model_id)
model.to(device)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trainer를 사용한 방법과 비슷하게 모델과 토크나이저를 불러오고 토큰화 함수를 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점은, Trainer에서는 내부적으로 수행하던&lt;b&gt; GPU로의 모델 이동을 직접 수행&lt;/b&gt;한다는 것이다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;975&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FXmqj/btsMuRXkJ4J/kaIqCiUPCqlB49xz40FoK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FXmqj/btsMuRXkJ4J/kaIqCiUPCqlB49xz40FoK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FXmqj/btsMuRXkJ4J/kaIqCiUPCqlB49xz40FoK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFXmqj%2FbtsMuRXkJ4J%2FkaIqCiUPCqlB49xz40FoK0%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;644&quot; height=&quot;530&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;975&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;- Trainer API를 사용하지 않는 학습: (2) 데이터 준비&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740464553016&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def make_dataloader(dataset, batch_size, shuffle=True):
	dataset = dataset.map(tokenize_function, batched=True).with_format(&quot;torch&quot;)

	# 데이터셋에 토큰화 수행
    dataset = dataset.rename_column(&quot;label&quot;, &quot;labels&quot;) # 컬럼 이름 변경
    dataset = dataset.remove_columns(column_names=[&quot;title&quot;]) # 불필요한 컬럼 제거
    return DataLoader(dataset, batch_size=batch_size, shuffle=shuffle)
    
# 데이터 로더 생성
train_dataloader = make_dataloader(train_dataset, batch_size=8, shuffle=True)
valid_dataloader = make_dataloader(valid_dataset, batch_size=8, shuffle=False)
test_dataloader = make_dataloader(test_dataset, batch_size=8, shuffle=False)&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;rename_column 메서드를 통해 컬럼 이름을 labels로 변경하고, remove_columns 메서드를 사용해 토큰화 후 불필요해진 title 컬럼을 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 파이토치에서 제공하는 DataLoader 클래스를 사용해 데이터셋을 배치 데이터로 만들었다.&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;&lt;b&gt;- Trainer API를 사용하지 않는 학습: (3) 학습 함수 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740465686110&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def train_epoch(model, data_loader, optimizer):
  model.train()
  total_loss=0
  for batch in tqdm(data_loader):
    optimizer.zero_grad()
    input_ids = batch['input_ids'].to(device) # 모델에 입력할 토큰 아이디
    attention_mask = batch['attention_mask'].to(device) # 모델에 입력할 attention mask
    labels = batch['labels'].to(device) # 모델에 입력할 label
    outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
    loss = outputs.loss # 손실
    loss.backward # 역전파
    optimizer.step() # 모델 업데이트
    total_loss += loss.item()
  avg_loss = total_loss / len(data_loader)
  return avg_loss&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습을 수행하는 함수 train_epoch를 정의하고&lt;b&gt; train()&lt;/b&gt; 메서드를 사용해 &lt;b&gt;모델을 학습 모드로 변경&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 생성한 데이터로더에서 배치 데이터를 가져와 모델에 입력으로 전달하고, input_ids, attention_mask, labels 키를 각각 model에 인자로 전달해 계산을 수행한다.&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;b&gt;옵티마이저의 step()을 호출하면 역천파 결과를 바탕으로 모델을 업데이트&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;total_loss를 통해 학습이 잘 되고 있는지 집계한다.&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;&lt;b&gt;- Trainer API를 사용하지 않는 학습: (4) 평가 함수 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740466603172&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def evaluate(model, data_loader):
  model.eval()
  total_loss = 0
  predictions = []
  true_labels = []
  with torch.no_grad():
    for batch in tqdm(data_loader):
      input_ids = batch['input_ids'].to(device)
      attention_mask = batch['attention_mask'].to(device)
      labels = batch['labels'].to(device)
      outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
      logits = outputs.logits
      loss = outputs.loss
      total_loss += loss.item()
      preds = torch.argmax(logits, dim=-1)
      predictions.extend(preds.cpu().numpy())
      true_labels.extend(labels.cpu().numpy())
  avg_loss = total_loss / len(data_loader)
  accuracy = np.mean(np.array(predictions) == np.array(true_labels))
  return avg_loss, accuracy&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;&lt;b&gt;- Trainer API를 사용하지 않는 학습: (5) 학습 수행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740466885890&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;num_epochs = 1
optimizer = AdamW(model.parameters(), lr=5e-5)

# 학습 루프
for epoch in range(num_epochs):
  print(f&quot;Epoch {epoch+1}/{num_epochs}&quot;)
  train_loss = train_epoch(model, train_dataloader, optimizer)
  print(f&quot;Training loss: {train_loss}&quot;)
  valid_loss, valid_accuracy = evaluate(model, valid_dataloader)
  print(f&quot;Validation loss: {valid_loss}&quot;)
  print(f&quot;Validation accuracy: {valid_accuracy}&quot;)

# 테스트
_, test_accuracy = evaluate(model, test_dataloader)
print(f&quot;Test accuracy: {test_accuracy}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Trainer API vs. PyTorch Custom Training&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Trainer API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;장점&lt;/span&gt;: - 학습, 평가, 저장 등 주요 기능을 자동화할 수 있다.&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;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- save_strategy=&quot;epoch&quot; 설정을 통해 체크포인트를 자동으로 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- torch.nn.DataParallel과 torch.nn.DistributedDataParallel을 내부적으로 지원해 쉽게 다중 GPU를 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- tokenizer 및 data_collator와 결합해 배치 크기를 자동으로 맞출 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;단점&lt;/span&gt;: - PyTorch의 torch.optim 처럼 직접 optimizer를 설정하려면 별도로 Trainer의 compute_loss()를 오버라이딩해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- Trainer는 학습 과정 대부분을 자동화하므로 특정 연산을 추가하는 것이 어려울 수 있다.&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;&lt;b&gt;PyTorch Custom Training&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;장점&lt;/span&gt;: - Gradient Clipping, Custom Loss 등의 학습 과정을 세부적으로 조정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- PyTorch에서 지원하는 모든 옵티마이저와 스케줄러를 사용할 수 있다.&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;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;단점&lt;/span&gt;: - 코드가 길고 복잡하며 직접 구현해야한다는 어려움이 있다.&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;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- 멀티 GPU를 사용하기 위해서는 직접 구현해야 한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Learning</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/111</guid>
      <comments>https://seoldev.tistory.com/111#entry111comment</comments>
      <pubDate>Tue, 25 Feb 2025 16:19:55 +0900</pubDate>
    </item>
    <item>
      <title>[Opensearch] Opensearch 개념 정리</title>
      <link>https://seoldev.tistory.com/109</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Opensearch&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opensearch는 AWS가 Elasticsearch 7.10을 기반으로 fork한 검색 및 분석 엔진이다.&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;a href=&quot;https://opensearch.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://opensearch.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739273372681&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OpenSearch&quot; data-og-description=&quot;OpenSearch is a community-driven, Apache 2.0-licensed open source search and analytics suite that makes it easy to ingest, search, visualize, and analyze data.&quot; data-og-host=&quot;opensearch.org&quot; data-og-source-url=&quot;https://opensearch.org/&quot; data-og-url=&quot;https://opensearch.org/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhixwq/hyYf3Xmp3S/LJ65TjEl676UOt8ds3ho4k/img.png?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800&quot;&gt;&lt;a href=&quot;https://opensearch.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://opensearch.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhixwq/hyYf3Xmp3S/LJ65TjEl676UOt8ds3ho4k/img.png?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800');&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;OpenSearch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;OpenSearch is a community-driven, Apache 2.0-licensed open source search and analytics suite that makes it easy to ingest, search, visualize, and analyze data.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;opensearch.org&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;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Elasticsearch와 호환&lt;/span&gt; -&amp;gt; 기본적인 API 및 사용법이 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;100% 오픈 소스&lt;/span&gt; -&amp;gt; Apache 2.0 License&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;내장 보안 기능&lt;/span&gt; -&amp;gt; Elasticsearch에서는 유료였던 X-Pack 기능이 일부 내장되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;확장 가능한 플러그인 지원&lt;/span&gt; -&amp;gt; 보안, 모니터링, 머신러닝 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;대체 UI&lt;/span&gt; -&amp;gt; Elastic Stack의 Kibana 대신 Opensearch Dashboards 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Opensearch vs. Elasticsearch&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch 최신 버전에서는 라이선스 제한이 많아졌고, Opensearch는 무료로 기능을 제공하면서 AWS 주도로 발전 중이다.&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 style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;Opensearch&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;Elasticsearch&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;출시 주체&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;AWS&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Elastic N.V.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;라이선스&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Apache 2.0&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Elastic License&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;보안 기능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;기본 제공&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;유료 (X-Pack)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Query DSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Elasticsearch 7.0 기준 동일&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;최신 버전에서 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;UI&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Opensearch DashBoards&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Kibana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;ML 지원&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Anomaly Detection 제공&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Opensearch 주요 개념&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 클러스터 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opensearch는 분산 아키텍처를 사용하여 대량 데이터를 효울적으로 저장하고 검색한다.&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;&lt;b&gt;- 노드 유형&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Master Node&lt;/span&gt;: 클러스터 상태 관리, 노드 추가/제거, 인덱스 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Data Node&lt;/span&gt;: 실제 데이터를 저장하고 검색 요청을 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Ingest Node&lt;/span&gt;: 데이터 전처리 (파이프라인) 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Coordinating Node&lt;/span&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;&lt;b&gt;- 샤드(Shard)와 복제본(Replica)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 샤드 (Shard): 대량 데이터를 분할하여 저장하는 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 복제본 (Replica): 샤드의 사본으로, 장애 발생 시 데이터 보호 및 성능을 향상&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;&lt;b&gt;(2) 문서(Document)와 인덱스(Index)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opensearch에서는 데이터를 JSON 문서 형태로 저장하고, 인덱스를 통해 검색 성능을 최적화한다.&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;- 인덱스 관련 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Mapping&lt;/span&gt;: 문자열, 숫자, 날짜 등의 필드 타입을 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Analyzer&lt;/span&gt;: 텍스트를 토큰화하고 처리하는 방법을 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Dynamic Mapping&lt;/span&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;&lt;b&gt;(3) 검색 (Query DSL)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opensearch에서는 Query DSL을 사용해 데이터를 검색한다. (Elasticsearch 7.10의 쿼리 방식과 동일)&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;- 예시&lt;/p&gt;
&lt;pre id=&quot;code_1739275400458&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET opensearch_index/_search
{
	&quot;query&quot;: {
    	&quot;match&quot;: {
        	&quot;title&quot;: &quot;Opensearch&quot;
        }
    }
}&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;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;match&lt;/span&gt;: 특정 필드에서 키워드 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;bool&lt;/span&gt;: AND, OR 조건 조합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;range&lt;/span&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;&lt;b&gt;(4) Opensearch DashBoards&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch의 Kibana를 대체하는 UI로, 데이터 시각화, 인덱스 관리, 대시보드 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서는 기본포트로 5601을 사용한다.&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;&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) Opensearch Plugin&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Security: 인증, 권한 관리. Role-based Access Control(RBAC)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alerting: 특정 조건 발생 시 알림을 보내는 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Anomaly Detection: 머신 러닝을 활용한 이상 탐지 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Index Management: 인덱스 자동 관리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Plugin: SQL 쿼리 사용 가능&lt;/p&gt;</description>
      <category>Data Engineering/Elasticsearch</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/109</guid>
      <comments>https://seoldev.tistory.com/109#entry109comment</comments>
      <pubDate>Tue, 11 Feb 2025 21:17:56 +0900</pubDate>
    </item>
    <item>
      <title>[Python] copy와 deepcopy의 차이</title>
      <link>https://seoldev.tistory.com/108</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;copy &amp;amp; deepcopy&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;copy와 deepcopy는 파이썬의 copy 모듈에서 제공하는 두 가지 복사 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 차이를 이해하기 위해서는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;얕은 복사(Shallow Copy)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;깊은 복사(Deep Copy)&lt;/span&gt;에 대해 알아야한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;얕은 복사 (copy.copy())&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 새롭게 생성하지만, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;내부의 요소들은 원본 객체와 같은 참조&lt;/span&gt;를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최상위 객체 (바깥쪽 객체)는 새롭게 생성되지만, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;내부의 요소 (하위 객체들)는 원본 객체와 같은 주소를 공유&lt;/span&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739190505561&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import copy

list1 = [[1, 2, 3], [4, 5, 6]]
list2 = copy.copy(list1)

list2[0][0] = 100 # 내부 요소를 변경

print(list1)
print(list2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list2를 copy.copy(list1)로 복사했지만, 내부 리스트는 원본과 공유되기 때문에 list2[0][0]을 변경하면 list1도 영향을 받게되어 각 출력값은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;[[100, 2, 3], [4, 5, 6]]&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;[[100, 2, 3], [4, 5, 6]]&lt;/span&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;깊은 복사 (copy.deepcopy())&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 새롭게 생성하고, 내부의 모든 하위 객체들까지 새롭게 생성해 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;원본과 완전히 독립적인 객체&lt;/span&gt;를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;원본 객체와 복사된 객체가 전혀 다른 메모리 주소&lt;/span&gt;를 가지며, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;한 쪽을 변경해도 다른 쪽에 영향을 주지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739190787266&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import copy

list1 = [[1, 2, 3], [4, 5, 6]]
list2 = copy.deepcopy(list1)

list2[0][0] = 100

print(list1)
print(list2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;copy.deepcopy()를 사용하면 내부 리스트까지도 새로운 객체로 복사되므로, list2의 값을 변경하더라도 list1에는 영향을 주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;copy, deepcopy는 각각 언제 사용해야 하는가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;copy&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;deepcopy&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;</description>
      <category>Language/Python</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/108</guid>
      <comments>https://seoldev.tistory.com/108#entry108comment</comments>
      <pubDate>Mon, 10 Feb 2025 21:35:08 +0900</pubDate>
    </item>
    <item>
      <title>[Python] ContextManager의 개념</title>
      <link>https://seoldev.tistory.com/107</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ContextManager&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;contextmanager는 파이썬의 ContextManager 프로토콜을 구현하는 기능으로, with 문을 사용할 때 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;리소스 (자원)의 획득 및 해제를 자동화하는 역할&lt;/span&gt;을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로, &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;파일 입출력, 데이터베이스 연결, 네트워크 소켓 사용 등에서 활용&lt;/span&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;contextmanager는 __enter__와 __exit__ 메서드를 구현하여 특정 코드 블록이 실행되는 동안 필요한 리소스를 설정하고, 블록이 끝나면 자동으로 해제하는 구조를 제공한다.&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;- 파일을 읽고 파일을 자동으로 닫는 예제&lt;/p&gt;
&lt;pre id=&quot;code_1739189072671&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;with open(&quot;file_txt&quot;, &quot;r&quot;) as f:
	content = f.read() # 파일을 읽는 부분

# with 블록을 벗어나면 파일이 자동으로 닫힌다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ContextManager Decorator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 contextlib 모듈의 contextmanager Decorator를 사용해 간단하게 ContextManager를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 활용해 클래스를 따로 정의하지 않고도 ContextManager를 구현할 수 있다.&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;- 파일을 열고 파일을 자동으로 닫는 예제&lt;/p&gt;
&lt;pre id=&quot;code_1739189315856&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from contextlib import contextmanager

@contextmanager
def open_file(filename, mode):
	f = open(filename, mode)
    try:
    	yield f # 파일 객체를 반환
    finally:
    	f.close() # 블록이 종료되면 자동으로 파일을 닫는다

with open_file(&quot;text.txt&quot;, &quot;w&quot;) as f:
	f.write(&quot;Hello, Context Manager!&quot;) # text.txt 파일에 write

# 블록이 끝나면 자동으로 close()가 호출된다.&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;- 데이터베이스 연결 관리 예제&lt;/p&gt;
&lt;pre id=&quot;code_1739189472818&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from contextlib import contextmanager

@contextmanager
def database_connection():
	print(&quot;데이터베이스 연결&quot;)
    conn = &quot;데이터베이스 연결 객체&quot;
    try:
    	yield conn # 연결 객체를 반환
    finally:
    	print(&quot;데이터베이스 연결 해제&quot;)

with database_connection() as db:
	print(f&quot;데이터 저장 중: {db}&quot;)
    
# 블록 종료 후 자동으로 연결이 해제된다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Class를 사용해 직접 ContextManager 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ContextManager Class로 파일을 관리하는 예제&lt;/p&gt;
&lt;pre id=&quot;code_1739190126773&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OpenFile:
	def __init__(self, filename, mode):
    	self.filename = filename
        self.mode = mode
        self.file = None
        
    def __enter__(self):
    	print(f&quot;{self.filename 파일 열기}&quot;)
        self.file = open(self.filename, self.mode)
        return self.file # with 블록 내부에서 사용될 객체를 반환하는 부분
    
    def __exit__(self, exc_type, exc_value, traceback):
    	print(f&quot;{self.filename 파일 닫기}&quot;)
        if self.file:
        	self.file.close()
            
 with OpenFile(&quot;test.txt&quot;, &quot;w&quot;) as f:
 	f.write(&quot;Hello, This is Class based context manager!&quot;)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Language/Python</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/107</guid>
      <comments>https://seoldev.tistory.com/107#entry107comment</comments>
      <pubDate>Mon, 10 Feb 2025 21:22:37 +0900</pubDate>
    </item>
    <item>
      <title>[FastAPI] FastAPI에서 Decorator 사용하기</title>
      <link>https://seoldev.tistory.com/106</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI는 Python 웹 프레임워크로, 비동기 지원과 높은 성능을 제공하는 것이 특징이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI에서는 Decorator를 사용해 라우팅, 요청 처리, 미들웨어 적용을 간단하게 할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FastAPI에서 Decorator의 역할&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI에서 Decorator는 주로 라우팅을 설정하는 데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@app.get() Decorator를 사용해 특정 URL을 처리하는 함수를 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739187305480&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/&quot;)
def test():
	return {&quot;message&quot;: &quot;Hello, FastAPI!&quot;}&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;위 코드를 main.py에 저장 후 아래 명령어로 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739187351861&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;uvicorn main:app --reload&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;브라우저에서 http://127.0.0.1:8000/ 에 접속해 결과를 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/piXc1/btsMet1Ui4D/OCoQEZ9YAbKmKu3Bj1tNx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/piXc1/btsMet1Ui4D/OCoQEZ9YAbKmKu3Bj1tNx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/piXc1/btsMet1Ui4D/OCoQEZ9YAbKmKu3Bj1tNx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpiXc1%2FbtsMet1Ui4D%2FOCoQEZ9YAbKmKu3Bj1tNx0%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;1209&quot; height=&quot;709&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTTP method에 따른 Decorator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI는 다양한 HTTP method를 지원하는 Decorator를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739187799144&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/items/{item_id}&quot;)
def read_item(item_id: int):
	return {&quot;item_id&quot;: item_id}

@app.post(&quot;/items&quot;)
def create_item(name: str):
	return {&quot;name&quot;: name}
    
@app.put(&quot;/items/{item_id}&quot;)
def update_item(item_id: int, name: str):
	return {&quot;item_id&quot;: item_id, &quot;name&quot;: name}
    
@app.delete(&quot;/items/{item_id}&quot;)
def delete_item(item_id: int):
	return {&quot;message&quot;: f&quot;Item {item_id} is deleted&quot;}&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;미들웨어 Decorator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI에서는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@app.middleware(&quot;http&quot;)&lt;/span&gt; Decorator를 사용해 요청과 응답을 처리하는 미들웨어를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 요청의 실행 시간을 기록해 성능을 모니터링 할 수 있도록 하는 간단한 미들웨어를 구성하는 예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739187997934&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI, Request
import time

app = FastAPI()

@app.middleware(&quot;http&quot;)
async def log_request_time(request: Request, call_next):
	start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    print(f&quot;Request processed in {process_time: .4f} seconds&quot;)
    return response&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;종속성 주입 (Dependency Injection) Decorator&lt;/b&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;FastAPI 에서는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Depends&lt;/span&gt;를 이용해 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739188698317&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI, Depends

def get_db():
	db = {&quot;connection&quot;: &quot;database connected&quot;}
    return db
    
app = FastAPI()

@app.get(&quot;/db&quot;)
def read_db(db: dict = Depends(get_db)):
	return db&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Depends(get_db)는 get_db가 실행된 후 반환 값이 read_db 함수의 db 매개변수에 자동으로 주입된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 요청마다 get_db()가 실행되며, 데이터베이스 연결 같은 공통 기능을 쉽게 관리할 수 있다.&lt;/p&gt;</description>
      <category>Backend/FastAPI</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/106</guid>
      <comments>https://seoldev.tistory.com/106#entry106comment</comments>
      <pubDate>Mon, 10 Feb 2025 20:59:35 +0900</pubDate>
    </item>
    <item>
      <title>[Python] Python Decorator 개념과 활용</title>
      <link>https://seoldev.tistory.com/105</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Python Decorator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python Decorator는&lt;span style=&quot;background-color: #f6e199;&quot;&gt; 기존 함수를 수정하지 않고, 기능을 추가하거나 수정할 수 있는 기능&lt;/span&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;재사용성&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;확장성&lt;/b&gt;&lt;/span&gt;을 가진 것이 특징이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 Decorator 패턴을 구현할 수 있지만, Python에서는 표현적 구문과 기능을 제공함으로써 Decorator 구현을 쉽게 할 수있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;함수의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 함수를 변수에 대입할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739183409133&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 함수를 변수에 할당해보자

def greeting(name):
	return f&quot;Hello, {name}!&quot;
    
say_hello = greeting # 함수 객체를 변수에 대입하기
print(say_hello(&quot;Seora&quot;))&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;&lt;b&gt;2. 함수 내부에 다른 함수를 정의할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739184202453&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def outer_function():
	def inner_function():
    	return &quot;Seora's Tistory Blog&quot;
    return inner_function
    
my_func = outer_function()
print(my_func()) # inner_function의 출력으로 &quot;Seora's Tistory Blog&quot;가 출력됨&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;&lt;b&gt;3. 함수는 다른 함수의 인자로 전달될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739184345744&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def call_function(func):
	return func()

def hello():
	return &quot;Hello, World!&quot;

print(call_function(hello)) # hello 함수의 리턴값인 Hello, World 출력&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;&lt;b&gt;4. 함수는 다른 함수에 의해 리턴될 수 있다. (= 함수는 다른 함수를 생성할 수 있다.)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739184594670&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def outer():
	def inner():
    	return &quot;Inner function이 실행됩니다.&quot;
    return inner
    
new_func = outer()
print(new_func())&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;&lt;b&gt;5. 내부 함수는 둘러싸인 범위 (enclosing scope)에 대해 접근이 가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python에서는 내부 함수가 외부 함수의 변수를 참조할 수 있다. -&amp;gt; 이를 Closure라고 한다!&lt;/p&gt;
&lt;pre id=&quot;code_1739184746911&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def outer_function(message):
	def inner_function():
    	print(f&quot;Inner function이 받은 값: {message}&quot;)
    return inner_function
    
closure_func = outer_function(&quot;Hello from outer function&quot;)
clousure_func() # Inner function이 받은 값: Hello from outer function&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Python Decorator의 기본 개념&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Decorator는 함수를 감싸서 새로운 기능을 추가하는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제를 통해 Decorator 구조를 이해해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1739185304759&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def my_decorator(func):
	def wrapper():
    	print(&quot;함수 호출 전 실행되는 부분&quot;)
        func()
        print(&quot;함수 호출 후 실행되는 부분&quot;)
    return wrapper
    
@my_decorator # decorator 적용하기
def say_hello():
	print(&quot;Hello, Seora!&quot;)
    
say_hello()&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;say_hello() 호출 시 wrapper 함수가 실행되며 출력 결과는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739185377604&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;함수 호출 전 실행되는 부분
Hello, Seora!
함수 호출 후 실행되는 부분&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인자를 받는 함수 Decorator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Decorator가 적용된 함수에 인자를 전달해야할 때는 &lt;b&gt;*args&lt;/b&gt;와 &lt;b&gt;**kwargs&lt;/b&gt;를 활용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739185703199&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def my_decorator(func):
	def wrapper(*args, **kwargs):
    	print(&quot;함수 호출 전 실행되는 부분&quot;)
        result = func(*args, **kwargs):
        print(&quot;함수 호출 후 실행되는 부분&quot;)
        return result
    return wrapper

@my_decorator
def add(a, b):
	return a + b
    
print(add(3, 4))&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;실행 결과는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739185755501&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;함수 호출 전 실행되는 부분
함수 호출 후 실행되는 부분
7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;여러 개의 Decorator 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 함수에 여러 개의 Decorator를 적용할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739185930905&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def decorator1(func):
	def wrapper():
    	print(&quot;Decorator 1 실행&quot;)
     	func()
    return wrapper
    
def decorator2(func):
	def wrapper():
    	print(&quot;Decorator 2 실행&quot;)
        func()
    return wrapper
    
@decorator1
@decorator2
def say_hello():
	print(&quot;Hello, Seora!&quot;)
    
say_hello()&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;실행 결과는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739185961618&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Decorator 1 실행
Decorator 2 실행
Hello, Seora!&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Class Decorator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 Decorator로 사용할 수 있다. __call__ 메서드를 구현해 함수처럼 동작하게 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739186316467&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyDecorator:
	def __init__(self, func):
    	self.func = func
        
    def __call__(self, *args, **kwargs):
    	print(&quot;함수 호출 전 실행되는 부분&quot;)
       	result = self.func(*args, **kwargs)
        print(&quot;함수 호출 후 실행되는 부분&quot;)
        return result

@MyDecorator
def say_hello():
	print(&quot;Hello, Seora!&quot;)
    
 say_hello()&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;실행 결과는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739186349182&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;함수 호출 전 실행되는 부분
함수 호출 후 실행되는 부분
Hello, Seora!&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Language/Python</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/105</guid>
      <comments>https://seoldev.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 10 Feb 2025 20:19:37 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 논문 검색 엔진 구현 프로젝트 (Elasticsearch + Airflow + FastAPI) (4)</title>
      <link>https://seoldev.tistory.com/104</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GitHub Actions 활용한 CI/CD 구축&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 프로젝트던지 최소한의 마무리는 CI/CD 구축이라고 생각하는 사람이다...&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;&lt;b&gt;1. .gitignore 파일 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1738764915766&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Python 관련
__pycache__/
*.pyc
*.pyo
*.pyd
*.sqlite3

# 가상 환경 (venv)
venv/
.env
*.env

# 로그 및 임시 파일
*.log
*.out
*.pid
.DS_Store
*.swp

# Docker 관련
*.tar
*.img
docker-compose.override.yml

# IDE 및 에디터 설정
.vscode/
.idea/
*.iml

# Airflow 관련 (데이터베이스 및 캐시)
airflow/airflow.cfg
airflow/unittests.cfg
airflow/logs/
airflow/tmp/
airflow_db/

# Elasticsearch 데이터 (로컬 실행 시 생길 수 있음)
elasticsearch/data/
elasticsearch/logs/

# FastAPI 관련 (캐시)
fastapi/__pycache__/
fastapi/.pytest_cache/
fastapi/.mypy_cache/

# Streamlit 관련
streamlit/__pycache__/
streamlit/.streamlit/

# GitHub Actions 실행 결과 캐시 방지
.github/&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;&lt;b&gt;2. GitHub Respository 생성 후 코드 Push&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 약간 구닥다리(?) 사람이기 때문에 깃 관련 작업은 Git Bash를 사용한다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738766823672&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738766861361&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git remote add origin &amp;lt;repository 주소&amp;gt;.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738766872728&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738766883405&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738812085290&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git push -u origin main&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;(나는 혼자 작업한 간단한 프로젝트라서 main 브랜치에 바로 push 했지만, 깃 브랜치 전략에 따라 develop 브랜치에서 작업하는 것을 추천한다.)&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;&lt;b&gt;3. GitHub Repository -&amp;gt; Settings -&amp;gt; Secrets and variables -&amp;gt; Actions 경로에서 DOCKER_USERNAME, DOCKER_PASSWORD를 추가한다.&lt;/b&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;만약 Docker Hub 계정이 없다면 Docker Hub 사이트에서 계정을 생성해야하고, 중요한 점 중 하나는 DOCKER_USERNAME은 아이디(이메일)가 아닌 사용자 이름이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(DOCKER_USERNAME을 이메일로 세팅하는 바람에 CD 파이프라인에서 에러가 발생해서 알게 된 사실)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5AR9b/btsL7ClIH1P/EKukKtPTFpiJXtD5beYyUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5AR9b/btsL7ClIH1P/EKukKtPTFpiJXtD5beYyUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5AR9b/btsL7ClIH1P/EKukKtPTFpiJXtD5beYyUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5AR9b%2FbtsL7ClIH1P%2FEKukKtPTFpiJXtD5beYyUk%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;769&quot; height=&quot;608&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1243&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;4. .github/workflows/ci.yml&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트 경로에 해당 파일을 작성해주자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738812379124&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: CI Pipeline

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: &quot;3.9&quot;

      - name: Install Dependencies
        run: |
          pip install -r fastapi/requirements.txt
          pip install -r streamlit/requirements.txt

      - name: Check Docker &amp;amp; Docker Compose
        run: |
          docker --version
          docker compose version || true
          docker-compose version || true

      - name: Install Docker Compose (if needed)
        run: |
          if ! command -v docker-compose &amp;amp;&amp;gt; /dev/null; then
            sudo curl -L &quot;https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose
            sudo chmod +x /usr/local/bin/docker-compose
          fi
          docker-compose version

      - name: Build and Start Containers
        run: |
          cd elasticsearch  # `docker-compose.yml`이 있는 디렉토리로 이동
          docker-compose up -d --build
          sleep 10
          docker-compose ps&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;나는 일단 테스트 과정을 스킵했는데, 테스트 과정이 필요한 경우 반드시 해당 과정을 수행하도록 하자..&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;&lt;b&gt;5. .github/workflows/cd.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1738812413962&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: CD Pipeline 

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Install Docker
        run: |
          curl -fsSL https://get.docker.com | sh

      - name: Login to Docker Hub 
        run: echo &quot;${{ secrets.DOCKER_PASSWORD }}&quot; | docker login -u &quot;${{ secrets.DOCKER_USERNAME }}&quot; --password-stdin

      - name: Build &amp;amp; Push FastAPI Docker Image
        run: |
          cd fastapi
          docker build -t ${{ secrets.DOCKER_USERNAME }}/biosearch-fastapi:latest .
          docker tag ${{ secrets.DOCKER_USERNAME }}/biosearch-fastapi:latest ${{ secrets.DOCKER_USERNAME }}/biosearch-fastapi:$GITHUB_SHA
          docker push ${{ secrets.DOCKER_USERNAME }}/biosearch-fastapi:latest
          docker push ${{ secrets.DOCKER_USERNAME }}/biosearch-fastapi:$GITHUB_SHA

      - name: Build &amp;amp; Push Streamlit Docker Image
        run: |
          cd streamlit
          docker build -t ${{ secrets.DOCKER_USERNAME }}/biosearch-streamlit:latest .
          docker tag ${{ secrets.DOCKER_USERNAME }}/biosearch-streamlit:latest ${{ secrets.DOCKER_USERNAME }}/biosearch-streamlit:$GITHUB_SHA
          docker push ${{ secrets.DOCKER_USERNAME }}/biosearch-streamlit:latest
          docker push ${{ secrets.DOCKER_USERNAME }}/biosearch-streamlit:$GITHUB_SHA&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;&lt;b&gt;6. GitHub Commit &amp;amp; Push&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1738812488241&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738812495509&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738812518455&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git commit -m &quot;commit message 작성&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738812529951&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git push -u origin main&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;Push 후 해당 레포지토리의 [Actions]에서 CI, CD 파이프라인 수행 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;1275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VlhCf/btsL8kSpnco/awykTwc3aC3XlqQOBva1P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VlhCf/btsL8kSpnco/awykTwc3aC3XlqQOBva1P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VlhCf/btsL8kSpnco/awykTwc3aC3XlqQOBva1P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVlhCf%2FbtsL8kSpnco%2FawykTwc3aC3XlqQOBva1P0%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;695&quot; height=&quot;571&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;1275&quot;/&gt;&lt;/span&gt;&lt;/figure&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;세팅 설정 때문에 문제가 몇 번 있었지만 최종적으로 GitHub Actions를 통해 자동으로 Docker Hub에 프로젝트를 build하고 push하는 과정이 잘 구축되었다!&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;내가 구현한 파이프라인은 간단하지만, 앞으로 서버에 자동으로 배포하고, API 테스트를 추가하는 과정을 학습해서 프로젝트를 고도화할 수 있을 것 같다!&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;논문 검색 엔진 프로젝트 마무리 성공~!&lt;/p&gt;</description>
      <category>Data Engineering/Elasticsearch</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/104</guid>
      <comments>https://seoldev.tistory.com/104#entry104comment</comments>
      <pubDate>Thu, 6 Feb 2025 12:32:16 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 논문 검색 엔진 구현 프로젝트 (Elasticsearch + Airflow + FastAPI) (3)</title>
      <link>https://seoldev.tistory.com/103</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UI 개발 (feat. Streamlit)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 진행하면서 CMD로 요청을 보내고 응답을 받는 과정이 번거롭고, 가독성도 좋지 않아서 간단한 UI를 개발했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택은 &lt;b&gt;streamlit&lt;/b&gt;을 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(react를 써보고 싶었으나, 시간적 이슈로 인해 간단한 방법을 선택했다.. react는 추후 포스팅할 프로젝트에서 다룰 예정이다.. ㅎ)&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;streamlit 또한 컨테이너로 만들어서 다른 서비스와 함께 도커 컴포즈로 개발할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 구조는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDc0Hm/btsL8TzYbmg/SHXo1XsvFw1dVklIx9C1K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDc0Hm/btsL8TzYbmg/SHXo1XsvFw1dVklIx9C1K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDc0Hm/btsL8TzYbmg/SHXo1XsvFw1dVklIx9C1K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDc0Hm%2FbtsL8TzYbmg%2FSHXo1XsvFw1dVklIx9C1K0%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;215&quot; height=&quot;429&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Dockerfile (Streamlit)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1738756778628&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM python:3.9

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [&quot;streamlit&quot;, &quot;run&quot;, &quot;streamlit_app.py&quot;, &quot;--server.port=8501&quot;, &quot;--server.address=0.0.0.0&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;requirements.txt (Streamlit)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1738756804811&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;streamlit
requests&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;streamlit_app.py&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1738757636253&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import streamlit as st
import requests

# FastAPI 백엔드 URL (컨테이너 네트워크에서 FastAPI와 통신하기 위함)
BACKEND_URL = &quot;http://fastapi:8000/search&quot;

st.title(&quot;  BioResearch-Paper Search Engine&quot;)
st.write(&quot;Elasticsearch 기반 bio 논문 검색&quot;)

# 검색 입력창
query = st.text_input(&quot;검색어를 입력하세요&quot;, &quot;&quot;)

# 저자 검색 옵션
author = st.text_input(&quot;저자 이름을 입력하세요&quot;, &quot;&quot;)

# 검색 버튼
if st.button(&quot;검색&quot;):
    params = {&quot;query&quot;: query}
    if author:
        params[&quot;author&quot;] = author
    
    response = requests.get(BACKEND_URL, params=params)

    if response.status_code == 200:
        results = response.json().get(&quot;results&quot;, [])
        if results:
            st.write(f&quot;  {len(results)}개의 논문이 검색되었습니다.&quot;)
            for paper in results:
                source = paper[&quot;_source&quot;]
                st.subheader(source[&quot;title&quot;])
                st.write(f&quot;**저자:** {', '.join([author['name'] for author in source['authors']])}&quot;)
                st.write(f&quot;  **출판일:** {source['publication_date']}&quot;)
                st.write(f&quot;  **요약:** {source['abstract'][:500]}...&quot;)
                st.write(f&quot;[  논문 링크]({source['url']})&quot;)
                st.write(&quot;---&quot;)
        else:
            st.warning(&quot;검색 결과가 없습니다&quot;)
    else:
        st.error(&quot;검색 요청에 실패했습니다&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;수정된 docker-compose.yml (Streamlit 서비스 추가)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1738756985400&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.7'
services:

  # PostgreSQL (Airflow metadata DB)
  postgres:
    image: postgres:13
    container_name: postgres_db
    restart: always
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    ports:
      - &quot;5432:5432&quot;
    networks:
      - elastic_network
    volumes:
      - postgres_data:/var/lib/postgresql/data

  # Elasticsearch
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - &quot;9200:9200&quot;
      - &quot;9300:9300&quot;
    networks:
      - elastic_network

  # Kibana
  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    container_name: kibana
    ports:
      - &quot;5601:5601&quot;
    depends_on:
      - elasticsearch
    networks:
      - elastic_network
  
  # FastAPI
  fastapi:
    build: ../fastapi
    container_name: fastapi
    ports:
      - &quot;8000:8000&quot;
    depends_on:
      - elasticsearch
    networks:
      - elastic_network
  
  # Airflow
  airflow:
    build: ../airflow
    container_name: airflow
    restart: always
    ports:
      - &quot;8080:8080&quot;
    depends_on:
      - elasticsearch
      - postgres
    environment:
      - AIRFLOW_HOME=/opt/airflow
      - AIRFLOW__CORE__EXECUTOR=LocalExecutor
      - AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:airflow@postgres:5432/airflow
      - AIRFLOW__CORE__LOAD_EXAMPLES=False    
    volumes:
      - airflow_db:/opt/airflow
      - ../airflow/dags:/opt/airflow/dags # DAG 폴더 마운트
    networks:
      - elastic_network
  
  # Streamlit
  streamlit:
    build: ../streamlit
    container_name: streamlit
    ports:
      - &quot;8501:8501&quot;
    depends_on:
      - fastapi
      - elasticsearch
    networks:
      - elastic_network

networks:
  elastic_network:
    driver: bridge

volumes:
  postgres_data:
  airflow_db:&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;위 코드를 모두 작성 후 컨테이너를 종료한 뒤 다시 시작해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1738757046662&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose down
docker-compose up --build -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Streamlit 접속 및 검색 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:8501에 접속하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjYrkX/btsL7RiBMaq/4nFkpPJ6PK9Z2okuef7IU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjYrkX/btsL7RiBMaq/4nFkpPJ6PK9Z2okuef7IU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjYrkX/btsL7RiBMaq/4nFkpPJ6PK9Z2okuef7IU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjYrkX%2FbtsL7RiBMaq%2F4nFkpPJ6PK9Z2okuef7IU1%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;679&quot; height=&quot;465&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;- 검색 테스트 1: 기본 검색에 &quot;COVID-19&quot; 입력 후 검색 수행&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u4vv7/btsL73J8rzR/391QfD4PH8gMrfhKP8MtCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u4vv7/btsL73J8rzR/391QfD4PH8gMrfhKP8MtCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u4vv7/btsL73J8rzR/391QfD4PH8gMrfhKP8MtCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu4vv7%2FbtsL73J8rzR%2F391QfD4PH8gMrfhKP8MtCk%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;708&quot; height=&quot;788&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;1471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FS0eV/btsL8V5ENaB/MIsvbdmgecc5xKPa5mfTkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FS0eV/btsL8V5ENaB/MIsvbdmgecc5xKPa5mfTkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FS0eV/btsL8V5ENaB/MIsvbdmgecc5xKPa5mfTkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFS0eV%2FbtsL8V5ENaB%2FMIsvbdmgecc5xKPa5mfTkK%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;690&quot; height=&quot;815&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;1471&quot;/&gt;&lt;/span&gt;&lt;/figure&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;논문 링크를 클릭하면 해당 논문 페이지로 이동한다!&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;&lt;b&gt;- 검색 테스트 2: 저자 검색&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;1246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IouFl/btsL7xLsaN0/yoB2ImG94VaR8rwrWBTGcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IouFl/btsL7xLsaN0/yoB2ImG94VaR8rwrWBTGcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IouFl/btsL7xLsaN0/yoB2ImG94VaR8rwrWBTGcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIouFl%2FbtsL7xLsaN0%2FyoB2ImG94VaR8rwrWBTGcK%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;702&quot; height=&quot;1246&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;1246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자 풀네임을 입력해도 검색 결과가 없다는 메시지가 나와서 CMD로 요청을 보내봤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5YCJv/btsL7fdyA0F/4Hugx2uyXb2uBgeLO5WA80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5YCJv/btsL7fdyA0F/4Hugx2uyXb2uBgeLO5WA80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5YCJv/btsL7fdyA0F/4Hugx2uyXb2uBgeLO5WA80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5YCJv%2FbtsL7fdyA0F%2F4Hugx2uyXb2uBgeLO5WA80%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;1105&quot; height=&quot;64&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&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;curl 요청 결과를 분석한 결과, &quot;query&quot; 필드가 필수로 설정되어 있어서 author 검색만 수행할 때 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI의 Query 매개변수에서 query: str을 필수 값으로 받고 있어서 발생한 오류로 추정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FastAPI app.py 수정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query를 선택적 (Optional) 매개변수로 변경해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1738760666381&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI, Query
from elasticsearch import Elasticsearch

app = FastAPI()
es = Elasticsearch(&quot;http://elasticsearch:9200&quot;)

@app.get(&quot;/search&quot;)
async def search_papers(
    query: str = Query(None, description=&quot;Search by keyword&quot;),
    author: str = Query(None, description=&quot;Search by author's name&quot;),
    sort_by: str = Query(&quot;publication_date&quot;, description=&quot;Sort field&quot;),
    order: str = Query(&quot;desc&quot;, description=&quot;Sort order ('asc' or 'desc')&quot;)
):
    &quot;&quot;&quot;Elasticsearch에서 논문 검색 수행&quot;&quot;&quot;

    must_clauses = []

    # 키워드 검색 추가 (query가 존재하는 경우)
    if query:
        must_clauses.append(
            {&quot;multi_match&quot;: {
                &quot;query&quot;: query,
                &quot;fields&quot;: [&quot;title&quot;, &quot;abstract&quot;, &quot;category&quot;]
            }}
        )

    # 저자 검색 추가 (author가 존재하는 경우)
    if author:
        must_clauses.append(
            {
                &quot;nested&quot;: {
                    &quot;path&quot;: &quot;authors&quot;,
                    &quot;query&quot;: {
                        &quot;match&quot;: {
                            &quot;authors.name&quot;: author
                        }
                    }
                }
            }
        )

    # 검색어 또는 저자 중 하나는 반드시 입력해야 함
    if not must_clauses:
        return {&quot;error&quot;: &quot;검색어 또는 저자를 입력해야 합니다.&quot;}

    query_body = {
        &quot;query&quot;: {
            &quot;bool&quot;: {
                &quot;must&quot;: must_clauses
            }
        },
        &quot;sort&quot;: [
            {sort_by: {&quot;order&quot;: order}}
        ]
    }

    response = es.search(index=&quot;research_papers&quot;, body=query_body)

    return {&quot;results&quot;: response[&quot;hits&quot;][&quot;hits&quot;]}


@app.get(&quot;/&quot;)
def read_root():
    return {&quot;message&quot;: &quot;Hello, FastAPI!&quot;}&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;다시 저자 검색 테스트를 수행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;1288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9bgSg/btsL8XbjYor/qBVub9XZBeSKybcGWYVK6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9bgSg/btsL8XbjYor/qBVub9XZBeSKybcGWYVK6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9bgSg/btsL8XbjYor/qBVub9XZBeSKybcGWYVK6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9bgSg%2FbtsL8XbjYor%2FqBVub9XZBeSKybcGWYVK6k%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;644&quot; height=&quot;658&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;1288&quot;/&gt;&lt;/span&gt;&lt;/figure&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;저자 검색도 성공적으로 작동한다!&lt;/p&gt;</description>
      <category>Data Engineering/Elasticsearch</category>
      <author>seoraroong</author>
      <guid isPermaLink="true">https://seoldev.tistory.com/103</guid>
      <comments>https://seoldev.tistory.com/103#entry103comment</comments>
      <pubDate>Wed, 5 Feb 2025 20:44:53 +0900</pubDate>
    </item>
  </channel>
</rss>