<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>분석하고싶은코코</title>
    <link>https://coco0414.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 21 May 2026 07:06:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코코로코코</managingEditor>
    <item>
      <title>대용량 데이터 처리 방법(1) - Parquet(파케이)</title>
      <link>https://coco0414.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;취업하고 업무 적응하느라 정신이 없어서 대회나 서브프로젝트에 대한 포스팅을 못하고 있었는데 좀 여유가 생겨서 다시 포스팅을 시작해보려 합니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&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;데이터 분석을 적은 데이터로 할 경우도 있지만 대용량의 고객데이터를 통해 분석을 진행할 경우가 생깁니다. csv, json 저장된 수백, 수천만개의 고객 로그 데이터를 분석을 해본적이 없기 때문에 어떻게 접근해야하는지 막막했습니다. 그래서 찾아보게된게. parquet입니다. cuDF라는 것도 있지만 이 기능은 NIVIDA GPU가 장착된 환경에서만 사용할 수 있습니다. 그저 갓비디아.... 맥 유저는 웁니다... cuDF는 추후에 다뤄보겠습니다.&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;a href=&quot;https://www.kaggle.com/competitions/otto-recommender-system/data&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.kaggle.com/competitions/otto-recommender-system&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720485969985&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;OTTO &amp;ndash; Multi-Objective Recommender System | Kaggle&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.kaggle.com&quot; data-og-source-url=&quot;https://www.kaggle.com/competitions/otto-recommender-system/data&quot; data-og-url=&quot;https://www.kaggle.com/competitions/otto-recommender-system/data&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/competitions/otto-recommender-system/data&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.kaggle.com/competitions/otto-recommender-system/data&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;OTTO &amp;ndash; Multi-Objective Recommender System | Kaggle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.kaggle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터는 약 12GB입니다. 단순히 12GB만 올리는 것이라면 그럴 수 있지 하겠지만 데이터를 열어보면 json의 하나의 열로 데이터가 압축되어 있어 하나의 열(row)에 최소 10개 이상의 로그 데이터가 합쳐서 들어가져 있습니다. 이러한 데이터가 총 약 1300만개가 되는 데이터입니다. 이러한 데이터를 한 번에 메모리에 올려서 분석 혹은 추천 시스템을 적용하는 것은 매우매우매우매우 비효율적입니다. 그래서 사용되는 parquet과 cuDF를 알아보겠습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Kaggle API 사용하여 데이터 다운로드 받기&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1720487261034&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;#pip install kaggle
import os

os.environ['KAGGLE_USERNAME'] = 'kaggle_username'
os.environ['KAGGLE_KEY'] = 'kaggle_api_key'

!kaggle competitions download -c expedia-hotel-recommendations

import shutil

filename = 'expedia-hotel-recommendations.zip' # 압축 해제할 파일 이름
extrac_dir = 'data' # 압축 해제할 폴더 이름
archive_format = 'zip' # 압축 파일 형태

shutil.unpack_archive(filename, extrac_dir, archive_format)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;Parquet&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Parquet는 Apache Hadoop의 에코시스템에서 널리 사용되는 열 지향 저장 형식입니다. 주로 대규모 데이터 처리를 위해 설계되었으며, 데이터 웨어하우스와 분석 작업의 현업에서 사용되고 있는 데이터입니다.&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;span style=&quot;color: #000000; text-align: start;&quot;&gt;데이터를 처음 접하게 되면서 가장 많이 다뤄봤을 CSV와 비교하면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 82px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.542635%; height: 18px; text-align: center;&quot;&gt;특성&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; height: 18px; text-align: center;&quot;&gt;CSV&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; height: 18px; text-align: center;&quot;&gt;Parquet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 20.542635%; height: 10px; text-align: center;&quot;&gt;저장 방식&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; height: 10px; text-align: center;&quot;&gt;행(column) 지향&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; height: 10px; text-align: center;&quot;&gt;&amp;nbsp;열(row) 지향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.542635%; height: 18px; text-align: center;&quot;&gt;압축&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; height: 18px; text-align: center;&quot;&gt;기본 제공X&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; height: 18px; text-align: center;&quot;&gt;열 단위 압축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.542635%; height: 18px; text-align: center;&quot;&gt;데이터 타입&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; height: 18px; text-align: center;&quot;&gt;모든 데이터가 문자열로 저장&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; height: 18px; text-align: center;&quot;&gt;다양한 데이터 타입 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.542635%; height: 18px; text-align: center;&quot;&gt;메타데이터&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; height: 18px; text-align: center;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; height: 18px; text-align: center;&quot;&gt;스키마 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.542635%; text-align: center;&quot;&gt;읽기 성능&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; text-align: center;&quot;&gt;특정 열 접근시 성능 저하&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; text-align: center;&quot;&gt;특정 열 접근시 성능 향상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.542635%; text-align: center;&quot;&gt;파일 크기&lt;/td&gt;
&lt;td style=&quot;width: 39.496124%; text-align: center;&quot;&gt;비압축시 큼&lt;/td&gt;
&lt;td style=&quot;width: 39.96124%; 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;p data-ke-size=&quot;size16&quot;&gt;표만 보면 CSV를 왜 쓰는가 싶지만 사실 CSV의 가장 큰 장점은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;단순하고 호환성이 좋아 소규모 데이터나 간단한 데이터 교환에 매우 적합합니다. 그래서 대용량 데이터를 다루는게 아니라면 CSV를 통한 데이터 핸들링이 훨씬 쉽고 간편하기 때문에 일반적으로 CSV를 많이 씁니다.&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;따라서 일반적으로 소규모 데이터를 다룰때는 CSV로도 충분히 가능하지만 로그 데이터 혹은 긴 기간의 유제 행동 데이터에 대한 핸들링 할때 Parquet 데이터가 유용하니 상황에 맞추어 선택할 수 있게 됩니다.&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;그럼 바로 예제 데이터를 Parquet으로 변환하여 확인하는 작업을 해보겠습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;Parquet&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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_1720658454643&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;path = 'data/train.jsonl'
num_lines = sum(1 for line in open(path))
print(f'number of lines in train: {num_lines:_}')

chunksize = 100_000
num_chunks = int(np.ceil(num_lines / chunksize))
print(f'number of chunks: {num_chunks:_}')


# Output
# number of lines in train: 12_899_779
# number of chunks: 129&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;Json2Parquet&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;chunk의 events에는 아래 사진과 같이 이중으로 데이터가 들어가 있습니다. 분석 및 추천 리스트 제작에 용이한 형태로 분리해줍니다. 이 작업은 test.json도 동일합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/URouH/btsItIKmAgw/wRyg7kzjBn0ORNdk5yVzaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/URouH/btsItIKmAgw/wRyg7kzjBn0ORNdk5yVzaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/URouH/btsItIKmAgw/wRyg7kzjBn0ORNdk5yVzaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FURouH%2FbtsItIKmAgw%2FwRyg7kzjBn0ORNdk5yVzaK%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;400&quot; height=&quot;95&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1720658492144&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chunks = pd.read_json(path, lines=True, chunksize=chunksize)

# 청크 단위로 파일 불러오기, 10만개(chunksize)로 데이터 분할
for e, chunk in tqdm(enumerate(chunks), total = num_chunks):
    data_dict = {
        'session': [],
        'aid': [],
        'ts': [],
        'type': [],
    }
    
    # 각 청크에 있는 데이터를 가져와서 하나의 row로 만듬
    for session, events in zip(chunk['session'].tolist(), chunk['events'].tolist()):
        for event in events:
            data_dict['session'].append(session)
            data_dict['aid'].append(event['aid'])
            data_dict['ts'].append(event['ts'])
            data_dict['type'].append(event['type'])
        
    # parquet형식으로 저장
    # df = pd.DataFrame(data_dict)
    # df.to_parquet(f&quot;data/train/train_{datetime.datetime.now().strftime('%y-%m-%d_%H-%M-%S')}.parquet&quot;)
    break&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Load Parquet&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cuDF를 사용할 것이 아니기 때문에 Pandas를 사용해 csv 읽어오는 방식과 동일하게 사용해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1720659032762&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def read_file_to_cache(f):
    df = pd.read_parquet(f)
    df.ts = (df.ts/1000).astype('int32')
    df['type'] = df['type'].map(type_labels).astype('int8')
    return df

# CACHE THE DATA ON CPU BEFORE PROCESSING ON GPU
data_cache = {}
type_labels = {'clicks':0, 'carts':1, 'orders':2}
files = glob.glob('data/train/*')
for f in files: data_cache[f] = read_file_to_cache(f)&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;총 129개의 청크로 나눴기 때문에 129개의 parquet파일이 있을 것이고 그러면 하나의 parquet을 불러왔을때 약 20mb, 전체는 5.9GB로 절반정도 감소한 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1720659205790&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;class 'pandas.core.frame.DataFrame'&amp;gt;
RangeIndex: 1036544 entries, 0 to 1036543
Data columns (total 4 columns):
 #   Column   Non-Null Count    Dtype
---  ------   --------------    -----
 0   session  1036544 non-null  int64
 1   aid      1036544 non-null  int64
 2   ts       1036544 non-null  int32
 3   type     1036544 non-null  int8 
dtypes: int32(1), int64(2), int8(1)
memory usage: 20.8 MB



&amp;lt;class 'pandas.core.frame.DataFrame'&amp;gt;
Int64Index: 216716096 entries, 0 to 978993
Data columns (total 4 columns):
 #   Column   Dtype
---  ------   -----
 0   session  int64
 1   aid      int64
 2   ts       int32
 3   type     int8 
dtypes: int32(1), int64(2), int8(1)
memory usage: 5.9 GB&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;이후부터는 기존 pandas이용과 동일하게 진행하면 됩니다. 이 방법은 대용량 데이터를 불러오는데 어려움을 겪는 분들에게 도움이 되는 방법입니다. 결국 수백, 수천만이 넘는 데이터를 다루고 처리하는데 속도를 높이기 위한 방법은 아닙니다. 리소스 자원이 허용한다면 cuDF를 사용해보시길 추천드립니다! 아래는 cudf의 처리 시간에 대한 비교인데 pandas에 비해 얼마나 빠른지 한눈에 보이네요.. 공식 Docs를 참고하여 사용해보시길 추천드립니다!&lt;/p&gt;
&lt;figure id=&quot;og_1720659541276&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;Welcome to the cuDF documentation! &amp;mdash; cudf 24.06.00 documentation&quot; data-og-description=&quot;Welcome to the cuDF documentation! cuDF (pronounced &amp;ldquo;KOO-dee-eff&amp;rdquo;) is a Python GPU DataFrame library (built on the Apache Arrow columnar memory format) for loading, joining, aggregating, filtering, and otherwise manipulating data. cuDF also provides a &quot; data-og-host=&quot;docs.rapids.ai&quot; data-og-source-url=&quot;https://docs.rapids.ai/api/cudf/stable/&quot; data-og-url=&quot;https://docs.rapids.ai/api/cudf/stable/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/82YvU/hyWvUbR8YG/YfWwBkO97qhOXXOkyMSow0/img.png?width=1920&amp;amp;height=706&amp;amp;face=0_0_1920_706&quot;&gt;&lt;a href=&quot;https://docs.rapids.ai/api/cudf/stable/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.rapids.ai/api/cudf/stable/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/82YvU/hyWvUbR8YG/YfWwBkO97qhOXXOkyMSow0/img.png?width=1920&amp;amp;height=706&amp;amp;face=0_0_1920_706');&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;Welcome to the cuDF documentation! &amp;mdash; cudf 24.06.00 documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to the cuDF documentation! cuDF (pronounced &amp;ldquo;KOO-dee-eff&amp;rdquo;) is a Python GPU DataFrame library (built on the Apache Arrow columnar memory format) for loading, joining, aggregating, filtering, and otherwise manipulating data. cuDF also provides a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.rapids.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZjxJP/btsIvtEPvtf/RUlIz2ONwFD39raU0D953K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZjxJP/btsIvtEPvtf/RUlIz2ONwFD39raU0D953K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZjxJP/btsIvtEPvtf/RUlIz2ONwFD39raU0D953K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZjxJP%2FbtsIvtEPvtf%2FRUlIz2ONwFD39raU0D953K%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;1280&quot; height=&quot;775&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;775&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;</description>
      <category>Python</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/121</guid>
      <comments>https://coco0414.tistory.com/121#entry121comment</comments>
      <pubDate>Thu, 11 Jul 2024 18:20:17 +0900</pubDate>
    </item>
    <item>
      <title>로컬 서버에 LLM 올리기 - Docker, Flask, LLM</title>
      <link>https://coco0414.tistory.com/118</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 로컬서버에서 LLM을 Flask를 활용해서 API형태로 만들고 이를 Docker 컨테이너로 만드는 과정에 대해서 포스팅합니다.&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;LLM&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 LLM이 아닌 기본 LLM을 사용합니다. 일반적으로 LLM을 불러오는 과정과 동일합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712732402630&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# app.py

from transformers import GPT2LMHeadModel, GPT2Tokenizer

model_name = &quot;gpt2&quot;  # 예시는 GPT-2, 필요시 수정 할 부분
model = GPT2LMHeadModel.from_pretrained(model_name)
tokenizer = GPT2Tokenizer.from_pretrained(model_name)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Flask&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;endPoint는 predict 부분입니다. 로컬에서 API형태로 감싸서 호출할때 127.0.0.1:8000/predict로 호출하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712732530200&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# app.py

from flask import Flask, request, jsonify


app = Flask(__name__)

@app.route('/predict', methods=['POST'])
def predict():
    data = request.json
    prompt = data['prompt']
    
    # 프롬프트를 토크나이즈하고 모델에 입력
    inputs = tokenizer.encode(prompt, return_tensors=&quot;pt&quot;)
    outputs = model.generate(inputs, max_length=50, num_return_sequences=1)
    
    # 생성된 텍스트를 디코딩
    text_generated = tokenizer.decode(outputs[0], skip_special_tokens=True)
    
    response = {&quot;result&quot;: text_generated}
    
    return jsonify(response)

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8000)&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;위 처럼 app.py를 구성했다면 터미널로 아래 명령어를 실행하여 Flask로 로컬 서버에 올려두시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712732745826&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python3 app.py&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;모델을 불러와 정상적으로 실행이 됐다면 curl를 통해 호출 하면 됩니다. 그러면 아래와 같은 결과를 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-X [호출 방식] [호출 주소]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-H 전달할 데이터 형식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-d 데이터&lt;/p&gt;
&lt;pre id=&quot;code_1712733030008&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8000/predict -H &quot;Content-Type: application/json&quot; -d '{&quot;prompt&quot;:&quot;Hello, world!&quot;}'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-10 오후 4.13.49.png&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/utqOc/btsGutuPKTG/UA5JGJ0yzXGpWcroMMIvKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/utqOc/btsGutuPKTG/UA5JGJ0yzXGpWcroMMIvKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/utqOc/btsGutuPKTG/UA5JGJ0yzXGpWcroMMIvKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FutqOc%2FbtsGutuPKTG%2FUA5JGJ0yzXGpWcroMMIvKK%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;1926&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2024-04-10 오후 4.13.49.png&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;178&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 로컬서버에서 실행한 API를 도커 컨테이너로 만드는 과정을 진행해보겠습니다.&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;Docker&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker컨테이너를 만드는데 구성은 간단합니다. 저는 아래와 같이 구성했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. app.py (실행할 py)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. requirments.txt&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Dockerfile&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-10 오후 4.16.55.png&quot; data-origin-width=&quot;2664&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCmmaE/btsGxAlcjRq/ayv76CLmzglne76RzUWFH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCmmaE/btsGxAlcjRq/ayv76CLmzglne76RzUWFH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCmmaE/btsGxAlcjRq/ayv76CLmzglne76RzUWFH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCmmaE%2FbtsGxAlcjRq%2Fayv76CLmzglne76RzUWFH0%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;2664&quot; height=&quot;1234&quot; data-filename=&quot;스크린샷 2024-04-10 오후 4.16.55.png&quot; data-origin-width=&quot;2664&quot; data-origin-height=&quot;1234&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;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&quot;&gt;&lt;b&gt;Dockerfile에 작성한 내용은 순차적으로 다음과 같은 명령을 실행합니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Python 버전 설정&lt;/b&gt;: FROM python:3.8 명령어를 통해 Python의 특정 버전(여기서는 3.8)을 포함하는 베이스 이미지를 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작업 디렉토리 설정&lt;/b&gt;: WORKDIR /app 명령어를 통해 생성될 Docker 이미지 내에서의 작업 디렉토리를 /app으로 설정. 이후의 명령어들은 이 디렉토리를 기준으로 실행 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존성 파일 복사 및 설치&lt;/b&gt;: COPY requirements.txt ./ 명령어로 현재 빌드 컨텍스트(즉, Dockerfile이 위치한 디렉토리)에서 requirements.txt 파일을 Docker 이미지의 /app 디렉토리로 복사 및 설치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소스 코드 복사&lt;/b&gt;: COPY . . 명령어는 현재 디렉토리(빌드 컨텍스트)에 있는 모든 파일과 디렉토리를 Docker 이미지의 현재 작업 디렉토리(/app)로 복사.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Flask 서버 실행 명령어 설정&lt;/b&gt;: CMD [&quot;flask&quot;, &quot;run&quot;, &quot;--host=0.0.0.0&quot;, &quot;--port=8000&quot;] 명령어는 Docker 컨테이너가 시작될 때 실행될 기본 명령어를 설정.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성이 됐다면 터미널에서 아래 명령어를 실행하면 도커 이미지가 만들어집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712734026453&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t flask-gpt2-app .&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;이후 아래 명렁어를 통해 실행중인 도커를 확인할 수 있습니다. 만약 실행중인 도커가 없다면 위에서 빌드한 도커 이미지의 이름을 run 명렁어로 실행시켜 주면 됩니다. 8000:8000은 로컬의 8000포트와 도커의 8000포트가 연결되는 것을 의미합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712734091438&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행중인 도커 확인
sudo docker ps

# 실행중이지 않은 도커 확인
# sudo docker ps -a

# 도커 실행
docker run -d -p 8000:8000 flask-gpt2-app&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;이후 호출 방식은 동일합니다. 이전에는 Flask가 로컬 8000포트를 통해 실행되고 있었지만 이제는 로컬의 8000포트로 내보내고 이게 도커의 8000포트로 들어가 답변을 받아오는 구조가 되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712734294077&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8000/predict -H &quot;Content-Type: application/json&quot; -d '{&quot;prompt&quot;:&quot;Hello, world!&quot;}'&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;1970&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPaguK/btsGxBRXgo4/5MHa4CUl53UNYODmB0vtjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPaguK/btsGxBRXgo4/5MHa4CUl53UNYODmB0vtjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPaguK/btsGxBRXgo4/5MHa4CUl53UNYODmB0vtjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPaguK%2FbtsGxBRXgo4%2F5MHa4CUl53UNYODmB0vtjK%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;1970&quot; height=&quot;160&quot; data-origin-width=&quot;1970&quot; data-origin-height=&quot;160&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;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;도커 이미지 서버에올리고 접근하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 해당 도커를 서버에 올리고 싶다면 도커이미지를 서버에서 올려서(pull) 실행(run)한다는 가정을 한다면 아래 명령어로 실행할 수 있습니다. 첫번째 5000은 외부에서 서버에 접근할 수 있는 포트이고 뒤에 8000은 Flask에서 접속할 포트를 의미합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712734904845&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 서버에서 실행
docker run -d -p 5000:8000 flask-gpt2-app

# 외부에서 서버에 요청시
curl -X POST http://[서버주소]:5000/predict -H &quot;Content-Type: application/json&quot; -d '{&quot;prompt&quot;:&quot;Hello, world!&quot;}'&lt;/code&gt;&lt;/pre&gt;</description>
      <category>머신러닝&amp;amp;딥러닝</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/118</guid>
      <comments>https://coco0414.tistory.com/118#entry118comment</comments>
      <pubDate>Wed, 10 Apr 2024 16:44:51 +0900</pubDate>
    </item>
    <item>
      <title>CNN으로 온라인 게임 어뷰저 탐지해보기</title>
      <link>https://coco0414.tistory.com/116</link>
      <description>&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;글에서 세세한 정보 모두를 다루지는 않고 간단하게만 다뤄보겠습니다. 순서는 EDA -&amp;gt; Modeling순서로 해보겠습니다.&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;EDA&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;각 피처들은 가상의 피처들이고 정확히 어떤 정보인지는 모르지만 어떤 종류의 데이터인지 정도만으로 진행하였습니다. 또한, 모든 피처를 다루지 않고 과정중 확인해본 주요 피처들에 대해서만 다루겠습니다.&lt;/i&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;이번에 사용될 데이터는 총 104,399개의 데이터로 다음과 같은 정보들을 갖고 있습니다. 캐릭터에 대한 정보와 캐릭터가 상호작용한 여러 정보들을 갖고 있는 데이터입니다. 해당 데이터 값들은 정규화가 되지 않고 각자 단위값들로 기록이 되어있습니다. 피처중 Class가 있는데 이는 어뷰저인지 아닌지를 판별하기 위한 라벨링 값입니다. 대부분이 None값을 갖고 있고 일부 데이터만 1(어뷰저), 0(일반유저)의 값을 갖고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8xjwv/btsFg3CZxGG/3S5FaUy8pLzjwHeefio160/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8xjwv/btsFg3CZxGG/3S5FaUy8pLzjwHeefio160/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;294&quot; data-filename=&quot;스크린샷 2024-02-25 오전 11.18.55.png&quot; data-widthpercent=&quot;56.29&quot; width=&quot;476&quot; height=&quot;169&quot; style=&quot;width: 55.6394%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8xjwv/btsFg3CZxGG/3S5FaUy8pLzjwHeefio160/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8xjwv%2FbtsFg3CZxGG%2F3S5FaUy8pLzjwHeefio160%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;828&quot; height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/binpOL/btsFfd7yDg1/naBAlzLo6P0qd3TonKoIh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/binpOL/btsFfd7yDg1/naBAlzLo6P0qd3TonKoIh0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;536&quot; data-filename=&quot;스크린샷 2024-02-25 오전 11.22.49.png&quot; style=&quot;width: 43.1978%;&quot; data-widthpercent=&quot;43.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/binpOL/btsFfd7yDg1/naBAlzLo6P0qd3TonKoIh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbinpOL%2FbtsFfd7yDg1%2FnaBAlzLo6P0qd3TonKoIh0%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;1172&quot; height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/div&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;어뷰저와 어뷰저가 아닌 그룹을 나눴고 ID에 중복값을 제외한 수를 카운트해보니 다음과 같이 나왔습니다. 이를 통해 알 수 있는 것은 총 10,441개의 계정 데이터가 있고 그중 937개의 어뷰징 계정, 625의 정상 계정 정보가 있음을 알 수 있습니다. 추가적으로 알 수 있는것은 평균적으로 1인당 약 10개의 캐릭터를 소유하고 있는 것도 유추해볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1708827949533&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ab_df['ID'].nunique(), non_ab_df['ID'].nunique(), df['ID'].nunique()

(937, 625, 10441)&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;클래스&lt;/b&gt;)를 사용하고 있는지 입니다. 어뷰저들의 목적에는 여러가지가 존재할 수 있지만 공통적인 전제 조건은 최대한 &lt;b&gt;간단하게 반복적인 활동&lt;/b&gt;을 &lt;b&gt;단시간&lt;/b&gt;에 할 수 있어야한다입니다. 이를 확인하고자 각 그룹으로 나눈 데이터를 시각화 해보니 그 모습이 확실하게 들어났습니다. 순서는 각각 어뷰저, 정상, 미판별 유저의 분포 그래프입니다. 정상 유저의 경우 직업군이 고르게 분포해 있는 반면 캐릭터 수가 훨씬 많은 어뷰저 그룹군에는 5개의 특정 클래스만 분포해 있는 것을 확인할 수 있습니다. 특히 30번 클래스가 압도적이고 이는 30번 캐릭터가 어뷰징하기 가장 적합한 캐릭터임을 유추해볼 수 있습니다. 그 뒤로 29, 31번 클래스가 따르고 있습니다.&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;하지만 29,30,31번 클래스가 어뷰징 유저가 많다고 해서 뭔가를 할 수는 없습니다. 왜냐하면 아래 정상과 미판별 그룹군에서도 해당 직업군이 타 클래스에 비해 압도적으로 많습니다. 이를 통해 유추해볼 수 있는건 해당 클래스들이 흔히 말하는 0티어 직업군으로 분류되고 있고 많은 유저들이 보유하고 있는 캐릭터라는 것입니다. 그래서 더욱 결과 도출에 조심해야한다는 것을 알려주기도 합니다.&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1467&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btf9Vx/btsFeHgIHTW/T4AsvBIXf1o4AU8129KMDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btf9Vx/btsFeHgIHTW/T4AsvBIXf1o4AU8129KMDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btf9Vx/btsFeHgIHTW/T4AsvBIXf1o4AU8129KMDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtf9Vx%2FbtsFeHgIHTW%2FT4AsvBIXf1o4AU8129KMDK%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;1467&quot; height=&quot;481&quot; data-origin-width=&quot;1467&quot; data-origin-height=&quot;481&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;레벨&lt;/b&gt; 분포입니다. 분포만 보더라도 두 그룹군에 큰 차이가 존재한다고 보이지 않습니다. 해당 피처로는 어뷰저를 구분짓기는 어려워보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1449&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkaMNm/btsFirDdi4G/MhZN1kAzGKkkXyUCvtIrMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkaMNm/btsFirDdi4G/MhZN1kAzGKkkXyUCvtIrMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkaMNm/btsFirDdi4G/MhZN1kAzGKkkXyUCvtIrMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkaMNm%2FbtsFirDdi4G%2FMhZN1kAzGKkkXyUCvtIrMk%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;1449&quot; height=&quot;497&quot; data-origin-width=&quot;1449&quot; data-origin-height=&quot;497&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;소셜 데이터&lt;/b&gt; 정보 분포입니다. 첫 3개에 대한 피처를 보면 어뷰저는 전부 0값을 갖고 있습니다. 어뷰저가 아닌 유저들의 경우도 0에 많이 분포해 되어 있지만 그렇지 않은 피처들이 몇개 눈에 들어옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;931&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfGHwn/btsFg2YpPCQ/s4kEMWBVcvwhSbOlrT0bAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfGHwn/btsFg2YpPCQ/s4kEMWBVcvwhSbOlrT0bAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfGHwn/btsFg2YpPCQ/s4kEMWBVcvwhSbOlrT0bAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfGHwn%2FbtsFg2YpPCQ%2Fs4kEMWBVcvwhSbOlrT0bAK%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;1455&quot; height=&quot;931&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;931&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;거래정보 데이터&lt;/b&gt;에서도 0에 많이 분포해 있지만 확실하게 어뷰징 유저와 정상 유저간의 어느정도 차이가 존재함을 알 수 있습니다. 조금 다른 부분은 첫번째 거래정보 피처가 정상 유저에 비해 높은 지표를 보이고 있는 유일한 데이터임을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;931&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEscbt/btsFiOZsi7p/p9O6H3NvMYKA4U5Cpzrtz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEscbt/btsFiOZsi7p/p9O6H3NvMYKA4U5Cpzrtz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEscbt/btsFiOZsi7p/p9O6H3NvMYKA4U5Cpzrtz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEscbt%2FbtsFiOZsi7p%2Fp9O6H3NvMYKA4U5Cpzrtz1%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;1455&quot; height=&quot;931&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;931&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;행동 데이터&lt;/b&gt;에서도 둘의 차이가 존재합니다. 어떠한 행동인지에 대해서는 알 수 없지만 첫번째, 두번째와 마지막 데이터에서 확실한 차이가 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;1692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXVQGA/btsFf5H8u4j/PFgyYMzDvwqze7b7s3dSl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXVQGA/btsFf5H8u4j/PFgyYMzDvwqze7b7s3dSl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXVQGA/btsFf5H8u4j/PFgyYMzDvwqze7b7s3dSl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXVQGA%2FbtsFf5H8u4j%2FPFgyYMzDvwqze7b7s3dSl1%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;1455&quot; height=&quot;1692&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;1692&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;상관관계&lt;/b&gt;를 확인해보았습니다. 저는 그룹간 데이터 분포와 상관관계를 통해 모델링에 사용할 피처 선택의 기준으로 하였습니다. 상관관계는 각 그룹에서 진행하였습니다. 따라서 피처간 상관관계는 두 개의 값이 나왔고 이 값이 역의 관계 혹은 2배 이상의 상관을 갖는 경우 의미있는 차이가 있다 가정하여 필터링 하였습니다. 따라서 사용될 피처는 총 18개입니다.(액션 7개, 거래 2개, 소셜 3개, 캐릭터 특성 6개)&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(모든 상관관계를 포스팅하기에는 반복적인 작업이라 따로 하지 않겠습니다. 직접 확인해보고 선정 기준을 정해보는 것도 좋을 것 같습니다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Modeling&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 EDA를 통해 선정한 피처를 가지고 딥러닝(CNN)을 통한 어뷰저를 탐지해보겠습니다.&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_1708840502441&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from sklearn.preprocessing import MinMaxScaler
import numpy as np

df = pd.read_csv('./data.csv')

min_max_col = [
#	... input selected column for modeling
]

# Min-Max 스케일러 생성
scaler = MinMaxScaler()

# 데이터를 스케일링
scaled_df = pd.DataFrame(scaler.fit_transform(df[min_max_col]), columns=min_max_col)

# 직업 원-핫 인코딩
one_hot_df = pd.get_dummies(df[['char_jobcode']], columns=['char_jobcode'])

# 정규화, 원-핫인코딩, 라벨 데이터 합치기
df = pd.concat([scaled_df, one_hot_df, df['Class']], axis=1)&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;모델링을 위한 데이터 처리를 완료했으니 이제 모델에 입력값으로 사용하기 위한 처리를 진행합니다. 그런데 여기서 추가적인 필터링을 진행하였습니다. 그 이유는 EDA과정에서 '&lt;b&gt;주어진 어뷰저 데이터에 특정 클래스만 있었다&lt;/b&gt;'를 확인했기 때문에 해당 5개의 클래스로만 데이터를 축소 시켜서 해당 클래스에서만 어뷰저를 탐지를 잘 하는 모델로 만드는 접근입니다. 이렇게 한 이유는 실제로 어뷰저 탐지는 굉장히 민감한 문제입니다. 비유하자면 이는 암에 대한 진찰이 비슷한 예시일 수 있습니다. 그래서 이후 모델 평가 지표를 정확도(A&lt;span style=&quot;color: #3b3b3b;&quot;&gt;ccuracy&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;)가 아닌 정밀도(Precision)을 사용하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708840906690&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Null값이 아닌 값들만 가져오기
tmp = df[~df['Class'].isna()]

# 어뷰저 정보가 있는 클래스만 걸러내기
 tmp = tmp[(tmp['char_jobcode_21'] == 1) |
         (tmp['char_jobcode_29'] == 1) |
         (tmp['char_jobcode_30'] == 1) |
         (tmp['char_jobcode_31'] == 1) |
         (tmp['char_jobcode_32'] == 1) 
         ]

X = tmp[use_col].values.tolist()
y = tmp['Class'].values

# 데이터를 훈련 및 테스트셋으로 나누기
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 데이터를 PyTorch Tensor로 변환
X_train_tensor = torch.FloatTensor(X_train)
y_train_tensor = torch.FloatTensor(y_train)
X_test_tensor = torch.FloatTensor(X_test)
y_test_tensor = torch.FloatTensor(y_test)

# 데이터 로더 생성 batch=4
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
test_dataset = TensorDataset(X_test_tensor, y_test_tensor)

batch_size = 4
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)&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;모델 설계는 정말 간단한 CNN모델로 설계하였습니다. 마지막에 층에 Sigmoid 활성화 층을 쌓아 1(어뷰저)에 대한 확률을 받도록 설정하였습니다. 손실함수는 이진분류 문제로 Binary-Cross-Entropy함수를 사용하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1708841636337&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DetectAbuser(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super(DetectAbuser, self).__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_dim, output_dim)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        x = self.sigmoid(x)
        return x

# 모델 초기화 및 손실 함수, 최적화기 설정
input_dim = len(X_train[0])
hidden_dim = 128
output_dim = 1  
model = DetectAbuser(input_dim, hidden_dim, output_dim)
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 모델 훈련
epochs = TRAIN_EPOCH
for epoch in tqdm(range(epochs)):
    for batch_X, batch_y in train_loader:
        optimizer.zero_grad()
        predictions = model(batch_X).squeeze(1)
        loss = criterion(predictions, batch_y.float())
        loss.backward()
        optimizer.step()&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;i&gt;어뷰저로 분류된다는 것은 굉장히 민감하고 불쾌한 경험을 유발한다.'&lt;/i&gt;의 전제으로 최대한 오분류가 이러나지 않는 수치를 찾기 위해 지표로 정밀도로 선택하고 가장 높은 정밀도를 보여주는 임계값을 찾아 최종 모델로 선발하였습니다. 이 과정에서 오분류가 완전 일어나지 않는 경우는 제외하고 True Positive도 10개 미만인 경우도 제외하는 조건을 타협점으로 어뷰저를 탐지 임계값을 설정해보았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1708843531644&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테스트 데이터에 대한 예측 및 평가
model.eval()
best_p, precision = 0, 0
best_threshold, best_tp, best_fp =0, 0, 0
for i in tqdm(range(1, 1000)):
    threshold = i*0.001
    tp, fp = 0, 0
    with torch.no_grad():
        correct = 0
        total = 0
        for batch_X, batch_y in test_loader:
            predictions = model(batch_X).squeeze(1)
            rounded_predictions = torch.Tensor([1 if prediction &amp;gt; threshold else 0 for prediction in predictions])
            for rounded_prediction, true_y in zip(rounded_predictions, batch_y):
                if rounded_prediction == 1:
                    ab_cnt+=1
				
                # True Pos
                if (rounded_prediction == 1) &amp;amp; (true_y == 1):
                    tp +=1
                    
                # False Pos
                if (rounded_prediction==1) &amp;amp; (true_y == 0):
                    fp +=1
            total += batch_y.size(0)
            correct += (rounded_predictions == batch_y).sum().item()
            
    precision = tp/(tp+fp)
    if (precision == 100) &amp;amp; (tp &amp;lt;10) :
        # print(f'FIND Best precision, But Sample under 10 i-&amp;gt; {i}')
        continue
    if best_p &amp;lt; precision:
        # print(f'CHANGE : values i -&amp;gt; {i}')
        best_p = precision
        best_fp = fp
        best_tp = tp
        best_threshold = threshold

accuracy = correct / total
precision = best_tp/(best_tp+best_fp)
print(f'Test Accuracy: {accuracy * 100:.2f}%')
print(f'Test Precision : {precision * 100:.2f}%')
print(f'Cnt Statics : {best_threshold}, {best_tp}, {best_fp}')&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;다음은 훈련된 모델에 가장 높은 지표를 얻을 수 있었던 임계값을 베스트를 저장한 결과로 정확도는&amp;nbsp; 40.62%로 굉장히 낮게 나왔습니다. 그 이유는 어뷰저인 유저도 어뷰저가 아닌 유저로 판별했기 때문입니다. 하지만 제가 평가 지표로 삼은 정밀도는 94.74%로 굉장히 높게 나왔습니다. 실제로 19개의 어뷰저를 탐지했고 18개는 실제로 어뷰저였고 1개가 오분류된 정상유저였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1708843650997&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Test Accuracy: 40.62%
Test Precision : 94.74%
Cnt Statics : 18, 1&lt;/code&gt;&lt;/pre&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;마치며&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;</description>
      <category>머신러닝&amp;amp;딥러닝</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/116</guid>
      <comments>https://coco0414.tistory.com/116#entry116comment</comments>
      <pubDate>Sun, 25 Feb 2024 17:06:10 +0900</pubDate>
    </item>
    <item>
      <title>모델 최적화를 위한 옵티마이저(Optimizer)</title>
      <link>https://coco0414.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스팅할 주제는 머신러닝, 딥러닝 모델에서 필수적인 존재인 옵티마이저(Optimizer)에 대해서 이야기해보겠습니다.&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;i&gt;'&lt;b&gt;목적지까지 가는 길을 최적화 해주는 방법(알고리즘)이다.&lt;/b&gt;'&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 간단한 예시로 아래와 같은 상황에서 출발지에서 맛집을 찾아가는 과정으로 예를 들어보겠습니다. 저희는 전지적 시점이기에 그냥 오른쪽으로 쭉 가면 되잖아? 라고 하지만 그렇지 않고 맛집이 존재하는 것에 대해서는 알고 있지만 맛집이 어디에 있는지는 모르는 상황에서 찾아가는 것입니다. 그렇다면 우리는 출발지에서 오른쪽만 가는게 아니라 아래쪽으로도 가보는 선택을 하고 맛집까지 도착하는 경험을 하게 됩니다. 그렇게 모든 갈 수 있는 모든 길을 탐색하고나서 '&lt;i&gt;아, 출발지에서 오른쪽으로 쭉 가는게 맛집으로 가는 최적화된 경로구나!&lt;/i&gt;'라고 생각하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 38.0232%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignCenter&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;출발지&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;?&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;?&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;?(맛집)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;?&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;?&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;?&lt;/td&gt;
&lt;td style=&quot;width: 25%; 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;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;산 내려오는 작은 오솔길 찾기 과정을 옵티마이저에 적용한 방법은 왼쪽 상단에 있는 경사 하강법(GD)입니다. 즉 우리는 지금까지 모든 경로(자료)를 다 검토해보고 내 위치에서 어디로 가야할지 정했던 것이죠. 이 경사 하강법을 좀 더 이해하기 쉽게 표현해보자면 '&lt;b&gt;경사는 미분 값이고 그 미분 값으로 (가중치를)움직여 모델을 최적화한다.&lt;/b&gt;'로 표현할 수 있습니다. 그런데 이 방법이 모든 데이터를 바탕으로 이뤄지다보니 너무 비효율적이니 일부만 사용해서 그 경사 하강법을 진행하자는게 확률적 경사하강법(SGD)입니다. 그런데 SGD 방법을 더 효율적으로 만들자하여 파생된 옵티마이저 알고리즘들이 있습니다. 그러면 이렇게 파생된 다양한 옵티마이저들을 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-14 오후 2.16.56.png&quot; data-origin-width=&quot;2662&quot; data-origin-height=&quot;1474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m6NoZ/btsEHFRrBMP/9hQsj8P8FlGMkdW0Vg0VlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m6NoZ/btsEHFRrBMP/9hQsj8P8FlGMkdW0Vg0VlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m6NoZ/btsEHFRrBMP/9hQsj8P8FlGMkdW0Vg0VlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm6NoZ%2FbtsEHFRrBMP%2F9hQsj8P8FlGMkdW0Vg0VlK%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;332&quot; data-filename=&quot;스크린샷 2024-02-14 오후 2.16.56.png&quot; data-origin-width=&quot;2662&quot; data-origin-height=&quot;1474&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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;확률적 경사 하강법(Stochastic&lt;span style=&quot;background-color: #ffffff; color: #2c3e50; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Gradient Descent, SGD&lt;/span&gt;)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확률적 경사 하강법은 GD와 다르게 한번 학습할 때 모든 데이터에 대해 가중치를 조절하는 것이 아니라, 랜덤하게 추출한 일부 데이터에 대해 가중치를 조절합니다. 결과적으로 속도는 개선되었지만 항상 좋은 최적 해를 찾는다는 보장은 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QYDee/btsEQPkOkj8/P8JWi1DnrwJoVMrEO6XMG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QYDee/btsEQPkOkj8/P8JWi1DnrwJoVMrEO6XMG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QYDee/btsEQPkOkj8/P8JWi1DnrwJoVMrEO6XMG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQYDee%2FbtsEQPkOkj8%2FP8JWi1DnrwJoVMrEO6XMG0%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;490&quot; height=&quot;231&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;291&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;속도 개선을 위해 SGD는 전체 데이터가 아닌 일부 데이터만을 통해서 나아간다는 것이 차이점입니다. 그래서 나아가는 과정에서 사용하는 수식은 아래의 식으로 동일합니다. 알파(&lt;span style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;&amp;alpha;)&lt;/span&gt;는 learning rate, Cost(w)는 비용함수로 훈련 데이터를 통해 나온 손실의 평균입니다. 이 값이 GD는 모든 데이터, SGD는 일부 데이터만을 사용해 가중치 업데이트가 이뤄지게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Th1m1/btsEP3cyyQb/q3PeD562bG3aqiKWQ6mFP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Th1m1/btsEP3cyyQb/q3PeD562bG3aqiKWQ6mFP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Th1m1/btsEP3cyyQb/q3PeD562bG3aqiKWQ6mFP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTh1m1%2FbtsEP3cyyQb%2Fq3PeD562bG3aqiKWQ6mFP0%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;351&quot; height=&quot;54&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;88&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;&lt;span&gt;&lt;b&gt;&lt;span&gt;모멘텀&lt;/span&gt;&lt;/b&gt;(&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;Momentum&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘텀은 관성, 탄력이라는 뜻입니다. 말 그대로 내가 가려고 하는 방향이 있지만 이전에 했던 행동으로 인해서 영향을 받는 것이죠. 즉, '이전 스텝에서 진행했던 업데이트에 대한 방향의 관성'으로 이해하시면 됩니다. 이 방향은 음의 방향과 양의 방향이 존재하게 되는데 관성으로 인해 한쪽 방향으로만 움직이면 이 또한 문제가 되니 비율을 통해서 그 정도를 정하게 되고 일정 수준이되면 반대 방향으로 바뀌게 되는 것이죠. 이를 통해서 좀더 일관성 있는 방법으로 최적 해를 찾아가는 과정이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4X101/btsESqx8yz9/bP3utSz0L4Dk2C6PdTgCdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4X101/btsESqx8yz9/bP3utSz0L4Dk2C6PdTgCdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4X101/btsESqx8yz9/bP3utSz0L4Dk2C6PdTgCdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4X101%2FbtsESqx8yz9%2FbP3utSz0L4Dk2C6PdTgCdK%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;391&quot; height=&quot;184&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;291&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;이러한 모멘텀 과정을 간단하게 예를 들어보겠습니다. 모멘텀의 수식은 아래와 같습니다. V(t)가 관성이 적용된 업데이트 할 값이 됩니다. 아래 수식의 m은 모멘텀 값으로 하이퍼파라미터가 됩니다. 이제 이 수식에 실제 값을 대입한 예를 들어보겠습니다. gradient 값이 0.5이고 두 번째 gradient 값이 -0.3이라 할 때 m이 0.9라면 V(1)은 &lt;b&gt;-0.5&lt;/b&gt;, V(2)는 0.9 * -0.5 +0.3 = -0.45 + 0.3 = &lt;b&gt;-0.15&lt;/b&gt;가 됩니다. 즉 방향(음-&amp;gt;양)이 변경 되어야 하는 상황임에도 이전 업데이트에 대한 관성으로 그 방향이 틀어지지 않고 나아가는 정도가 달라지게 되는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOw5Hf/btsEI9ESNTT/TfZIHkuYkhLEK0AteYwRw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOw5Hf/btsEI9ESNTT/TfZIHkuYkhLEK0AteYwRw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOw5Hf/btsEI9ESNTT/TfZIHkuYkhLEK0AteYwRw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOw5Hf%2FbtsEI9ESNTT%2FTfZIHkuYkhLEK0AteYwRw0%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;349&quot; height=&quot;84&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;148&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;네스테로브 모멘텀(&lt;/b&gt;Nesterov Accelrated Gradient, NAG)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAG는 모멘텀에서 조금 더 효율적인 방법으로 파생된 알고리즘입니다. 조금 복잡해보이지만 그림을 통해서 이해하면 쉽습니다. 아래와 같이 모멘텀은 그라디언트와 모멘텀 두 가지를 따로 계산하여, 두 값을 바탕으로 최종 값을 찾았습니다. 그런데 이 과정에서 좀 더 효율적인 방법으로 제시된 것이 모멘텀을 구하고 그 자리에서 그라디언트를 구하자라는 접근법입니다. 그러면 하나의 과정이 사라지고 좀 더 효율적이다라는 방법이었던 것이죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pG6uP/btsEPJSW5Sv/w4FWdCrxALPqlRT3K0u2F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pG6uP/btsEPJSW5Sv/w4FWdCrxALPqlRT3K0u2F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pG6uP/btsEPJSW5Sv/w4FWdCrxALPqlRT3K0u2F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpG6uP%2FbtsEPJSW5Sv%2Fw4FWdCrxALPqlRT3K0u2F1%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;639&quot; height=&quot;230&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;295&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1RAds/btsEP4ijaei/GA8wdexI5X1bYCreUtbI11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1RAds/btsEP4ijaei/GA8wdexI5X1bYCreUtbI11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1RAds/btsEP4ijaei/GA8wdexI5X1bYCreUtbI11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1RAds%2FbtsEP4ijaei%2FGA8wdexI5X1bYCreUtbI11%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;606&quot; height=&quot;105&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;142&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;Adagrad&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 이야기한 모멘텀의 경우 방향에 초점을 맞춘 파생 알고리즘들입니다. 그런데 지금부터 이야기할 Adagrad, RMSporp의 경우에는 방향이 아닌 경험(?)에 초점을 맞춘 방법입니다. 지금 저희가 다루고 있는 최적화 알고리즘은 SGD에서 파생된 알고리즘들입니다. 그렇기 때문에 업데이트 과정에서 사용된 데이터가 있고 아닌 데이터가 있게 됩니다. 이 부분에서 새로운 접근법을 적용한 것이죠. '&lt;b&gt;업데이트 된 가중치들은 좀 더 최적 해에 가까운 값들로 변경됐을 것'&lt;/b&gt;이라는 가정이 있는 알고리즘입니다. 즉, 학습을 통해서 최적 해에 가까워 지고 있으니 이후 학습에서 너무 많은 변화를 주면 좋지 않다는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Adagrad&lt;/span&gt;는 같은 입력 데이터가 여러번 학습되는 학습모델에 유용합니다. 대표적으로는 자언어와 관련된 Word2Vec이나 GloVe입니다. 학습 단어의 등장 확률에 따라 변수의 사용 비율이 확연하게 차이나기 때문에 많이 등장한 단어는 가중치를 적게 수정하고 적게 등장한 단어는 많이 수정할 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I6mnb/btsESplJaah/22RRATwZxkudZpKC7SVWB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I6mnb/btsESplJaah/22RRATwZxkudZpKC7SVWB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I6mnb/btsESplJaah/22RRATwZxkudZpKC7SVWB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI6mnb%2FbtsESplJaah%2F22RRATwZxkudZpKC7SVWB1%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;410&quot; height=&quot;177&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;354&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;식을 보면 지금까지 변화했던 과정들에 대한 값G(t)를 구합니다. W(t+1)을 업데이트하는 식을보면 G(t)값을 지금의 그라디언트 값과 lr를 곱하여 업데이트를 하는 것을 볼 수 있습니다. 그런데 여기서 문제점이 G(t)값이 너무 커지면 업데이트 값이 0에 수렴하여 움직이지 않는 상황이 발생해 버립니다. 이를 해결하고자 등장한 것이 RMSprop입니다.&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;RMSprop&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RMSprop는 Adagrad에 지수 이동 평균을 적용하자라는 접근법입니다. 지수 이동평균에 대한 이해를 위해 간단한 예를 들어보자면 시계열 데이터가 대표적입니다. 이에 대한 값으로 주식, 비트코인에 대한 값을 생각해보죠. 외부세력에 대한 간섭이 없다고 가정했을때 값에 대한 변화가 과연 30일전에 값에 영향을 많이 받을까요 아니면 어제의 값에 영향을 많이 받을까요? 당연히 오늘의 값은 어제에 값에 더 큰 영향을 받겠죠? 이를 통해 오늘의 값을 예측한다고 했을때 가중치를 알파&lt;span style=&quot;color: #000000;&quot;&gt;(&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;alpha;)라고 했을때 (1-&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;alpha;)의 n승으로 그 영향을 낮출 수 있게 된다는 것이죠.&lt;/span&gt;&lt;/span&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이 방법을 Adagard의 이전 학습에서 최근 학습에 조금더 가중치를 주어 G(t)값을 구하면 가중치가 업데이트되는 과정에서 0이 발생하는 것을 막을 수 있다는 방법론이 됩니다. 여기서 지수 이동평균에 대한 가중치는 감마로 표현되었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkIPqq/btsESMVwu4M/rvFLnlO1Y6UnAxSxB0Gnik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkIPqq/btsESMVwu4M/rvFLnlO1Y6UnAxSxB0Gnik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkIPqq/btsESMVwu4M/rvFLnlO1Y6UnAxSxB0Gnik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkIPqq%2FbtsESMVwu4M%2FrvFLnlO1Y6UnAxSxB0Gnik%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;429&quot; height=&quot;124&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;237&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;아담(Adaptive Moment Estimation, Adam)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아담은 앞서 알아봤던 방향에 대한 접근법과 학습 경험에 대한 접근법 두 가지를 융합한 알고리즘입니다. 그래서 그런지 많은 논문들에서 사용한 옵티마이저를 보면 아담 혹은 아담 베이스의 변형 옵티마이저들이 많이 사용되는 것을 확인할 수 있었습니다. 그렇다고 이 아담이 항상 최고의 옵타마이저라고는 할 수 없습니다. 여튼 이 아담의 접근은 반반 옵티마이저로 모멘텀과 RMSprop 두 가지를 결합한 옵티마이저라는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VIups/btsESMg1d0i/AEVSEoDELziVTaCKqdfZ00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VIups/btsESMg1d0i/AEVSEoDELziVTaCKqdfZ00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VIups/btsESMg1d0i/AEVSEoDELziVTaCKqdfZ00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVIups%2FbtsESMg1d0i%2FAEVSEoDELziVTaCKqdfZ00%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;520&quot; height=&quot;278&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;438&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;그런데 업데이트를 위한 값W(t+1)을 찾을때 단순하게 두 알고리즘에서 값을 구해 결합한 M(t), V(t)를 사용하지 않습니다. ^(hat)을 씌운 보정 값을 사용하게 됩니다. 그 이유는 M_0, V_0모두 0의 값을 갖는데 이를 그대로 사용하게 되면 0에 수렴하는 현상이 나타날 수 있습니다. 이를 방지하고자 보정값을 사용하게 되고 이 값은 초기에 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;nbsp;&amp;alpha;(lr)=0.001, &amp;beta;1로는 0.9, &amp;beta;2로는 0.999, ϵ 으로는 10^-8 값이 가장 좋은 Default값이라고 &lt;a href=&quot;https://arxiv.org/pdf/1412.6980.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;논문&lt;/a&gt;에 명시되어 있습니다.&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;결론(?)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저의 근본적인 접근법인 GD이외에도 Newton's-Method(뉴턴 법)같은게 존재합니다. 오늘 포스팅에서 다룬 옵티마이저들은 모두 SGD의 파생으로 &lt;span style=&quot;background-color: #ffffff; color: #2c3e50; text-align: start;&quot;&gt;first-order optimization에 속합니다. 그런데 뉴턴 법의 경우 second&lt;span style=&quot;background-color: #ffffff; color: #2c3e50; text-align: start;&quot;&gt;-order optimization에 속합니다. 1차와 2차의 차이는 몇번째 도함수로 미분을 몇번 시행하는가?라는 뜻입니다. 이는 수렴 속도와 계산량의 trade-off관계를 갖고 있습니다. 그럼에도 SGD파생 옵타마이저들이 주류로 잡은 이유는 trade-off관계에 있어 압도적인 계산량 증가가 있기 때문입니다.&lt;/span&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;모든 모델, 데이터에 가장 잘 맞는 옵티마이저는 없다고 합니다. 왜냐하면 모든 모델도 각각의 특색을 갖고 있고 그에 사용하는 데이터 역시 사용자들에 따라 다르게 형성되어 있습니다. 심지어 같은 데이터를 사용한다고 하더라도 모델에 넣기위한 전처리를 어떻게 했느냐에 따라서도 결과가 달라지듯이 그 입력값 자체가 다릅니다. 그렇기에 옵티마이저 역시 가장 잘 맞는 옵티마이저가 무엇인지에 대해서는 사용자가 직접 판단하거나 혹은 여러 옵티마이저를 사용해 최적의 옵티마이저와 그에 맞는 하이퍼파라미터들을 찾는것이 필요합니다. 그럼에도 최근 논문들이나 다양한 모델들의 소개를 보면 기본적인 SGD혹은 Adam, 변형 Adam이 많이 사용되는게 눈이 보이긴합니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/yongho/ss-79607172&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.slideshare.net/yongho/ss-79607172&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707887980765&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;자습해도 모르겠던 딥러닝, 머리속에 인스톨 시켜드립니다.&quot; data-og-description=&quot;자습해도 모르겠던 딥러닝, 머리속에 인스톨 시켜드립니다. - Download as a PDF or view online for free&quot; data-og-host=&quot;www.slideshare.net&quot; data-og-source-url=&quot;https://www.slideshare.net/yongho/ss-79607172&quot; data-og-url=&quot;https://www.slideshare.net/yongho/ss-79607172&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zGnFp/hyVjbyj3wC/vte09Wx1MNRBXNSxf32LdK/img.jpg?width=640&amp;amp;height=360&amp;amp;face=0_0_640_360,https://scrap.kakaocdn.net/dn/wktZW/hyVjmmin3P/WIvEyKUkpPZ64I6RBB0Lo1/img.jpg?width=640&amp;amp;height=360&amp;amp;face=0_0_640_360&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/yongho/ss-79607172&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.slideshare.net/yongho/ss-79607172&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zGnFp/hyVjbyj3wC/vte09Wx1MNRBXNSxf32LdK/img.jpg?width=640&amp;amp;height=360&amp;amp;face=0_0_640_360,https://scrap.kakaocdn.net/dn/wktZW/hyVjmmin3P/WIvEyKUkpPZ64I6RBB0Lo1/img.jpg?width=640&amp;amp;height=360&amp;amp;face=0_0_640_360');&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;자습해도 모르겠던 딥러닝, 머리속에 인스톨 시켜드립니다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자습해도 모르겠던 딥러닝, 머리속에 인스톨 시켜드립니다. - Download as a PDF or view online for free&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.slideshare.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707902351407&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;Gradient Descent Optimization Algorithms 정리&quot; data-og-description=&quot;Neural network의 weight을 조절하는 과정에는 보통 &amp;lsquo;Gradient Descent&amp;rsquo; 라는 방법을 사용한다. 이는 네트워크의 parameter들을 $\theta$라고 했을 때, 네트워크에서 내놓는 결과값과 실제 결과값 사이의 차이&quot; data-og-host=&quot;shuuki4.github.io&quot; data-og-source-url=&quot;https://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html&quot; data-og-url=&quot;https://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g90dK/hyVjdwdpHb/bvGNNrBs3ustRa7LToJZQ1/img.jpg?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254&quot;&gt;&lt;a href=&quot;https://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g90dK/hyVjdwdpHb/bvGNNrBs3ustRa7LToJZQ1/img.jpg?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254');&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;Gradient Descent Optimization Algorithms 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Neural network의 weight을 조절하는 과정에는 보통 &amp;lsquo;Gradient Descent&amp;rsquo; 라는 방법을 사용한다. 이는 네트워크의 parameter들을 $\theta$라고 했을 때, 네트워크에서 내놓는 결과값과 실제 결과값 사이의 차이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;shuuki4.github.io&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;</description>
      <category>머신러닝&amp;amp;딥러닝</category>
      <category>ADAM</category>
      <category>Optimizer</category>
      <category>SGD</category>
      <category>옵티마이저</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/115</guid>
      <comments>https://coco0414.tistory.com/115#entry115comment</comments>
      <pubDate>Wed, 14 Feb 2024 19:44:16 +0900</pubDate>
    </item>
    <item>
      <title>YOLOv8 object tracking(detecting)</title>
      <link>https://coco0414.tistory.com/114</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스팅할 내용은 YOLO입니다. YOLO는 이미지 혹은 이미지들로 이뤄진 비디오에서 물체를 탐지에 있어서 SOTA모듈입니다.&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;Python에는 OpenCV가 유명하긴하지만 물체에 대한 트랙킹을 진행할때 비디오 내에서 물체가 사라졌다가 다시 등장했을 경우 별도의 지정없이 트랙킹 하는 것이 어렵습니다. 물체를 다시 감지하기 위한 코드를 작성해야하는데 여간 쉬운일이 아닙니다. 그래서 자동으로 다시 트랙킹 해줄 수 있는 방법을 찾다가 발견한 것이 YOLOv8입니다.&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;YOLO에서 제공해주는 기본 모델은 아주 기본적인 트랙킹 데이터를 갖고 있지만 결국은 사용자가 원하는 물체에 대한 트랙킹을 해주지는 못합니다. 이게 무슨 말이냐면 기본적인 사람, 물체와 같은 트랙킹은 기본 모델에서 학습이 되어 있기 때문에 가능하지만 예를들어서 미니카에 대한 트랙킹이 필요한 영상 혹은 사진을 갖고 있다고 해보겠습니다. 이 경우 기본 모델에서는 아무리 사진을 구워 삶아봐도 트랙킹을 하지 못합니다. 물론 잡긴잡습니다만 놓치는 경우가 허다하고 미니카로 인식해서 트랙킹을 하는것이 아닙니다. 그래서 결국 내가 원하는 물체에 대한 트랙킹을 해줄 수 있게하려면 YOLO 모델에 맞는 fine-tuning과정이 필요합니다. 제가 지금까지 다뤄왔던 자연어처리를 위한 fine-tuning과정과 크게 다를게 없습니다. YOLO 모델에 맞는 데이터셋만 맞춰주면 그 과정 자체는 똑같습니다.&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;이번 포스팅에서 제가 커스텀 자료로 만든 자료는 로스트아크의 가디언 레이드 영상을 사용합니다. 그 중에서도 하누마탄에 대한 트랙킹 작업을 진행해보겠습니다. 거두절미하고 커스텀 데이터로 fine-tuning된 모델로 하누마탄을 트랙킹 하는 장면 예시 3개를 보여드리고 그 과정에 대해서 이야기 해보겠습니다.&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(배틀마스터 플레이)&lt;/b&gt;&lt;/p&gt;
&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAqbu1/btsEqYbDq4j/0UNk10XkXcDHkhKWUGKs9K/img.gif&quot; style=&quot;width:300px;height:300px&quot;&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;img src=&quot;https://blog.kakaocdn.net/dn/bjp0GH/btsEniPjOOz/mhpOgkaF87X9ibIszakzy0/img.gif&quot; style=&quot;width:300px;height:300px&quot;&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;img src=&quot;https://blog.kakaocdn.net/dn/qcngS/btsEvuN9Mwa/pukKAqH7sXBpEMzsweXC70/img.gif&quot; style=&quot;width:300px;height:300px&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3개의 영상에서 프레임이 끊어져 보이는데 실제로 영상의 4프레임씩 재생시킨것도 있고 YOLO가 물체를 탐색하는 시간이 딜레이 되면서 끊어져 보이는 것입니다. 영상을 보면 YOLO가 커스텀한 물체인 하누마탄을 잘 트랙킹하고 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;YOLO 구현하기 in Python&lt;/b&gt;&lt;/h3&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;1. 사전준비&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델에게 학습시켜줄 데이터를 만들기 위한 작업이 필요합니다. 이 부분은 각자 원하는 물체가 들어간 영상 파일을 준비해주시면 될 것 같습니다. 저는 유튜브에서 하누마탄 레이드와 관련된 영상 3개를 준비하였습니다. 3개 데이터는 각각 훈련, 테스트, 검증 데이터로 사용할 예정입니다.&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;이렇게 3개의 영상을 준비했다면 이제 프레임단위로 무작위하게 가져올 필요가 있는데 저는 opencv를 통해 96프레임 간격으로 영상에 대한 프레임을 저장해 이미지 파일을 만들었습니다. 방법은 아래아 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1707186125592&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import cv2

video_path = 'lostark_wb_hanu_01.mp4'

# 동영상 파일 열기
cap = cv2.VideoCapture(video_path)

frame_cnt = 0
count = 0

while True:
    frame_cnt += 1
    frame = cap.read()[1]

    if frame_cnt % 96 != 0:
        continue

    if frame is None:
        break

    cv2.imshow('tracking', frame) # 이미지 보여주기
    cv2.imwrite(&quot;./path_output_frame/%d.jpg&quot; % count, frame) # 이미지 저장

    count+=1
    k = cv2.waitKey(30)

cap.release()
cv2.destroyAllWindows()&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;이렇게 준비된 이미지들에서 YOLO를 위한 물체의 좌표를 설정해줘야합니다. 다양한 방법들이 있지만 저는&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://app.cvat.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://app.cvat.ai/&lt;/a&gt; 이 페이지를 사용하여 작업을 진행하였습니다. 별도의 비용이 들어가지는 않았고 회원가입 이후에 프로젝트를 생성하여 라벨을 추가하고 TASK를 생성하여 사전에 생성한 프로젝트를 지정하고 이미지를 업로드하면 됩니다. 그 이후는 노가다 작업으로 물체에 대한 트랙킹 범위를 이미지마다 지정해주면 됩니다. 이후 테스트로 나와 Export를 누르시면 파일을 저장할 유형을 선택할때 YOLO를 선택하고 이미지를 제외한 상태로 저장하게 되면 이미지에 대한 YOLO용 좌표 텍스트 파일이 자동으로 다운로드 됩니다. 아래 사진이 작업을 통해서 만들어진 YOLO용 파일입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-06 오전 11.27.53.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd35zK/btsEu5ODBcx/0s0XGFWrvuZ5aBZUxTtCm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd35zK/btsEu5ODBcx/0s0XGFWrvuZ5aBZUxTtCm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd35zK/btsEu5ODBcx/0s0XGFWrvuZ5aBZUxTtCm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd35zK%2FbtsEu5ODBcx%2F0s0XGFWrvuZ5aBZUxTtCm1%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;379&quot; height=&quot;219&quot; data-filename=&quot;스크린샷 2024-02-06 오전 11.27.53.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;460&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;이 과정을 3개 영상 모두 작업을 해주셔야합니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 하면 이제 YOLO를 훈련시킬 준비과정은 끝입니다. 저는 아래와 같이 준비하였고 반드시 지켜야할 부분은&lt;span&gt; images와 labels로 구분되어 있지만 훈련을 위한 파일이름은 같아야합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wANqy/btsEne0u13s/orsQocde4W8gw6usMU25O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wANqy/btsEne0u13s/orsQocde4W8gw6usMU25O0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;314&quot; data-filename=&quot;스크린샷 2024-02-06 오전 11.29.31.png&quot; style=&quot;width: 35.4218%; margin-right: 10px;&quot; data-widthpercent=&quot;36.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wANqy/btsEne0u13s/orsQocde4W8gw6usMU25O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwANqy%2FbtsEne0u13s%2ForsQocde4W8gw6usMU25O0%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;432&quot; height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chlRaW/btsEmQr1bs4/PD3UGj829vUb3B8NpcRrs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chlRaW/btsEmQr1bs4/PD3UGj829vUb3B8NpcRrs0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;268&quot; data-filename=&quot;스크린샷 2024-02-06 오전 11.29.51.png&quot; style=&quot;width: 33.0477%; margin-right: 10px;&quot; data-widthpercent=&quot;33.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chlRaW/btsEmQr1bs4/PD3UGj829vUb3B8NpcRrs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchlRaW%2FbtsEmQr1bs4%2FPD3UGj829vUb3B8NpcRrs0%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;344&quot; height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGTVwY/btsEoLjcXxu/yyvhdlklRO7CBathVRillk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGTVwY/btsEoLjcXxu/yyvhdlklRO7CBathVRillk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;402&quot; data-filename=&quot;스크린샷 2024-02-06 오전 11.30.18.png&quot; style=&quot;width: 29.2049%;&quot; data-widthpercent=&quot;29.9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGTVwY/btsEoLjcXxu/yyvhdlklRO7CBathVRillk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGTVwY%2FbtsEoLjcXxu%2FyyvhdlklRO7CBathVRillk%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;456&quot; height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2. 모델 훈련&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이제 위에서 준비한 데이터를 가지고 YOLO 기본 모델에 훈련을 위한 과정이 필요합니다. 그렇게 복잡한 부분은 아니기에 별도의 포스팅 없이 제가 진행한 코드를 공유하고 넘어가겠습니다. 저는 Colab의 TPU V4를 사용하여 진행하였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://colab.research.google.com/drive/1rEl_xe4rBKmzrbAUp5YRKLq27hCdVuR2?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;YOLO train in Colab&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707187156895&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;YOLO_train_custom_data.ipynb&quot; data-og-description=&quot;Colaboratory notebook&quot; data-og-host=&quot;colab.research.google.com&quot; data-og-source-url=&quot;https://colab.research.google.com/drive/1rEl_xe4rBKmzrbAUp5YRKLq27hCdVuR2?usp=sharing&quot; data-og-url=&quot;https://colab.research.google.com/drive/1rEl_xe4rBKmzrbAUp5YRKLq27hCdVuR2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TYIOq/hyVf73Ig1w/clTlMMat0ptXL3MYPoX5r0/img.png?width=260&amp;amp;height=260&amp;amp;face=0_0_260_260&quot;&gt;&lt;a href=&quot;https://colab.research.google.com/drive/1rEl_xe4rBKmzrbAUp5YRKLq27hCdVuR2?usp=sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://colab.research.google.com/drive/1rEl_xe4rBKmzrbAUp5YRKLq27hCdVuR2?usp=sharing&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TYIOq/hyVf73Ig1w/clTlMMat0ptXL3MYPoX5r0/img.png?width=260&amp;amp;height=260&amp;amp;face=0_0_260_260');&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;YOLO_train_custom_data.ipynb&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Colaboratory notebook&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;colab.research.google.com&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 모델 실험&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 훈련된 모델을 가져와서 진행하면됩니다. 위 코드를 통해 훈련을 진행했다면 project경로의 weights안에 best와 last pt파일이 있을텐데 그중에 best를 사용하면됩니다. 그러면 이제 정말 간단합니다. YOLO를 통해서 물체를 트랙킹하고 OpenCV를 통해 프레임을 보여주면됩니다. YOLO에서는 프레임에 있는 물체를 트랙킹한 정보를 담고 있어서 그 정보를 가져와 보여주기만 하면 됩니다. 핵심 코드는 다음과 같습니다. 순서대로 모델과 비디오를 불러오고, 반복문으로 프레임을 하나씩 가져와서 YOLO로 물체를 트랙킹하고 그걸 프레임으로 가져와 보여주면 끝입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1707187644948&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ultralytics import YOLO

# Load the YOLOv8 model
model = YOLO('best.pt')

video_path = &quot;./lostark_wb_hanu_01.mp4&quot;
cap = cv2.VideoCapture(video_path)

while True:
    success, frame = cap.read()

    results = model.track(frame, persist=True, conf=0.6)

    boxes = results[0].boxes.xywh.cpu()

    annotated_frame = results[0].plot()

    cv2.imshow(&quot;YOLOv8 Tracking&quot;, annotated_frame)&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_1707187480113&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import cv2
import numpy as np

from ultralytics import YOLO

# Load the YOLOv8 model
model = YOLO('best.pt')

# Open the video file
video_path = &quot;./lostark_wb_hanu_01.mp4&quot;
cap = cv2.VideoCapture(video_path)

frame_cnt = 0
while cap.isOpened():
    frame_cnt += 1
    success, frame = cap.read()

    if frame_cnt % 4 != 0:
        continue

    if success:
        results = model.track(frame, persist=True, conf=0.6)

        # Get the boxes and track IDs
        boxes = results[0].boxes.xywh.cpu()

        # Visualize the results on the frame
        annotated_frame = results[0].plot()

        # object points in boxes
        # for box in boxes:
        #    x, y, w, h = box

        cv2.imshow(&quot;YOLOv8 Tracking&quot;, annotated_frame)

        if cv2.waitKey(1) &amp;amp; 0xFF == ord(&quot;q&quot;):
            break
    else:
        break



cap.release()
cv2.destroyAllWindows()&lt;/code&gt;&lt;/pre&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;Reference&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;YOLOv8&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.ultralytics.com/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.ultralytics.com/ko&lt;/a&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;YOLO Custom 훈련하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=beyondlegend&amp;amp;logNo=223050797442&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/PostView.naver?blogId=beyondlegend&amp;amp;logNo=223050797442&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;후기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Python을 통해 물체 트랙킹하는 과정을 진행해보았습니다. 이러한 트랙킹 기술을 예시를 게임에 해보았지만 자율주행, 의학분야 등 다양한 분야에서 사용할 수 있는 아주 유용한 기술인것 같습니다. 저는 이를 통해 접근하려하는 프로젝트는 강화학습을 통해 AI가 로스트아크의 상황에 따른 스킬 운영법을 제안해 줄 수 있는가 입니다. 이 부분에 대해서는 이번 포스팅이 아니라 어느정도 진행되면 자세히 포스팅하여 다뤄보도록 하겠습니다!&lt;/p&gt;</description>
      <category>머신러닝&amp;amp;딥러닝</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/114</guid>
      <comments>https://coco0414.tistory.com/114#entry114comment</comments>
      <pubDate>Tue, 6 Feb 2024 12:51:15 +0900</pubDate>
    </item>
    <item>
      <title>RLHF_Chatbot 만들기(던전앤파이터 Chatbot)</title>
      <link>https://coco0414.tistory.com/113</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RLHF를 활용한 던전앤파이터 챗봇 만들기 프로젝트를 진행하였습니다. 사실 HF가 아닌 랜덤성이긴 하지만 비슷한 형태로 진행되기는 했습니다...ㅎ 여튼 이번 프로젝트에서 느낀점은 HF... RM을 위한 데이터 구성, 결국은 훈련시킬 데이터가 중요하다라는 것을 느낄 수 있었습니다. 느낀점은 후기에서 좀 더 작성하고 진행한 프로젝트에 대해서 이야기를 시작해보겠습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;지난번 &lt;a href=&quot;https://coco0414.tistory.com/109&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;포스팅&lt;/span&gt;&lt;/a&gt;에서 데이터 수집을 진행하였습니다. 완벽하게 던전앤파이터에 대한 데이터를 학습할 정도는 아니지만 던전앤파이터 세계관에 대해 이야기할 수 있는 Chatbot을 만들기에는 충분한 데이터가 수집되어 훈련을 진행하였습니다. 던전앤파이터 공식 홈페이지와 루리웹, 나무위키의 메인스토리 텍스트 데이터를 사용하였습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;포스팅에서는 SFT, RM, PPO 3단계에 대해 프로젝트를 진행하면서 경험한 주요 내용들로만 포스팅을 진행하겠습니다. 훈련을 위한 자세한 내용은 코드에서 참고해주시면 될 것 같습니다. 추후 github와 colab에서 바로 실행해볼 수 있도록 업로드할 예정입니다.(코드들 여기저기 작성해놔서 정리중입니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;이번 프로젝트는 &lt;b&gt;QLoRA&lt;/b&gt;와 &lt;b&gt;Prompt finetuning&lt;/b&gt; 두 가지를 사용하여 진행한 프로젝트입니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Trainable Model(SFT)&lt;/b&gt;&lt;/h2&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Select Base Model&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 겪은 어려움? 문제점?이라고하면 사실 모델 선택 문제가 가장 힘들었습니다. 이번 프로젝트의 베이스 LLM은 'EleutherAI/polyglot-ko'으로 선택하였습니다. 지난번 RLHF를 연습을 위해 사용한 KoGPT로 사용하기에는 생각보다 표현력이 떨어지는 모습을 많이 보여줬기 때문에 좀 더 큰 모델을 선택해야했습니다. 그렇다고 너무 큰 모델을 선택하기에는 제가 사용할 수 있는 컴퓨팅 자원 범위 안에서 선택해야했습니다. 이러한 조건하에 훈련 데이터가 충분한 모델들 중에 polyglot-ko를 선택하였습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;polyglot-ko에는 12.8b, 5.8b, 3.8b, 1.3b 4가지가 존재하는데 저는 그 중에서 1.3b로 가장 작은 모델을 최종 모델로 선택하였습니다. 더 많은 파라미터를 가진 모델을 통해서 진행할 수 있지만 그렇게 진행하지 않은 이유는 추후에 PPO과정을 진행할때 3개의 모델을 사용하기에 충분한 GPU 메모리 확보가 필요했기 때문에 쉽게 큰 모델을 선택하기 어려웠습니다. 실제로 처음에는 12.8b모델로 학습을 시켰는데 SFT과정에서는 OOM(out of memory)가 발생하지 않았지만 KL을 계산하는 과정에서 2개의 모델이 GPU에 올라가다보니 OOM를 제어할 수 없는 상황이 발생하였습니다. 그래서 1.3b를 선택하여 훈련을 진행하였습니다. (A100 40G로 테스트해보닌 RM과정까지 진행해보니 1step에서 GPU 20G 메모리 사용하는 것을 확인하였습니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;그러면 이렇게 Base Model을 선택했으니 훈련을 어떻게 진행했는지 간단하게 이야기해보겠습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Preprocess Data&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 수집단계에서 반복문자, 한글, 숫자, [.,'&quot;], 眞 문자를 제외하고 모두 삭제하였고 sonlpy를 사용하여 반복 문자의 경우 1개로 줄이는 전처리를 진행한 데이터입니다. 이후 &lt;span style=&quot;color: #333333;&quot;&gt;아래와 같은 간단한 형태로 prompt 파인튜닝을 위한 형태로 전처리를 진행하였습니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;data = data.map(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lambda x: {'text': f&quot;### 질문: {x['prompt']}\n\n### 답변: {x['completion']}&amp;lt;|endoftext|&amp;gt;&quot; }
)&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;QLoRA and Train&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 큰 모델을 양자화하여 모델을 로드하는 과정입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;bnb_config = BitsAndBytesConfig(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;load_in_4bit=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bnb_4bit_use_double_quant=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bnb_4bit_quant_type=&quot;nf4&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bnb_4bit_compute_dtype=torch.bfloat16
)

model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;아래는 peft모듈을 사용하여 quantization된 모델에 LoRA를 적용하는 과정입니다. trl에서는 peft와 연동이 되어 있어서 peft에서 제공해주는 모델로도 훈련이 가능합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;config = LoraConfig(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;r=8,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lora_alpha=32,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;target_modules=[&quot;query_key_value&quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lora_dropout=0.05,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bias=&quot;none&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;task_type=&quot;CAUSAL_LM&quot;
)

model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, config)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이후에는 훈련을 진행합니다. 저는 transformers에 있는 기본 Trainer를 사용했지만 trl에서 제공하는 SFTTrainer를 사용하여도 됩니다. 사용법은 다르지 않습니다. 총 epoch는 3회 진행하였습니다. 동일한 훈련 데이터로 colab T4 GPU로 훈련한 결과 1.3b모델은 4시간, 12.8b는 28시간의 학습 시간이 걸렸습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;trainer = transformers.Trainer(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;train_dataset=data[&quot;train&quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args=transformers.TrainingArguments(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;per_device_train_batch_size=8,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;gradient_accumulation_steps=2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;learning_rate=1e-4,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fp16=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output_dir=&quot;/content/drive/MyDrive/ds_study/던파_story/output_1_SFT&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;optim=&quot;paged_adamw_8bit&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_total_limit = 3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;seed = random_seed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False),
)

model.config.use_cache = False&amp;nbsp;&amp;nbsp;# silence the warnings. Please re-enable for inference!
trainer.train()&lt;/code&gt;&lt;/pre&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;Result&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;SFT과정을 거친 모델이 생성한 텍스트는 아래와 같습니다. 던전앤파이터라는 세계관에 대해서 모델이 학습한게 확실히 알 수 있습니다. 이제 이 모델을 베이스 모델로 사용하겠습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# setting generation
# num_beams=2, no_repeat_ngram_size=4, top_p = 0.9, max_new_tokens=128, early_stopping=True, do_sample=True, eos_token_id=2,

list_prompt = ['일곱 사도들에 대해서 알려줘',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '아라드 대륙에 대해서 설명해줘.',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '바칼이 천계를 공격한 이유는?.',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '게이볼그 프로젝트에 대해서 요약해주세요'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
------RESULT-----

SMALL MODEL GENERATION
### 질문: 일곱 사도들에 대해서 알려줘
### 답변: 일곱 사도는 사도들 중에서도 가장 강력한 힘을 가지고 있으며, 가장 많은 신도를 거느리고 있습니다. 그들은 천계를 지키기 위해 천계인들과 싸우고 있으며 그들의 힘을 이용하여 천계의 치안을 유지하고 있습니다. 일곱 사도는 다른 사도들과는 다르게 일곱 개의 머리를 가지고 있는 것으로 알려져 있습니다. 이들은 일곱 개의 머리로 천계를 수호하고 있으며, 일곱 개의 머리는 일곱 사도의 힘을 상징한다고 합니다. 일곱 사도의 이름은 사도들의 이름을 따서 지

LARGE MODEL GENERATION
### 질문: 일곱 사도들에 대해서 알려줘
### 답변: 일곱 사도는 성역의 수호자로서 그들의 역할은 매우 중요합니다. 그들은 아라드 대륙에 전이된 성역을 지키기 위해 존재하며, 성역을 침범하는 적들을 막아냅니다. 그들의 힘은 매우 강력하며, 그들의 공격은 매우 치명적입니다. 따라서 그들은 성역을 보호하기 위해 항상 전투를 준비하고 있습니다. 또한, 그들은 자신들의 힘을 강화하기 위해 노력하고 있으며, 성역의 기운을 받아 더욱 강력한 힘을 얻게 됩니다. 이로 인해 그들


SMALL MODEL GENERATION
### 질문: 아라드 대륙에 대해서 설명해줘.
### 답변: 아라드에는 크게 마계와 천계로 나뉘어 있습니다. 마계는 바칼의 제국이 지배하고 있으며, 천계는 천계인들이 지배하고 있습니다. 아라드 대륙은 마계의 영향을 받은 대륙으로, 대륙 곳곳에 마계의 흔적이 남아있습니다. 아라딘은 천계의 대표적인 인물로, 바칼이 아라드 대륙을 지배하기 위해 파견한 인물입니다. 천계인들은 아라드 대륙에서 자유롭게 생활하고 있으며 아라딘의 통치를 받으며 살고 있습니다. 천계의 영향

LARGE MODEL GENERATION
### 질문: 아라드 대륙에 대해서 설명해줘.
### 답변: 아라드는 대륙의 이름으로, 서쪽에 위치한 벨 마이어 공국과 데 로스 제국, 동쪽에 위치한 반투족의 국가인 흑요정들의 나라 흑요정 왕국, 북쪽에 위치한 마법의 나라 마이어, 그리고 남쪽에 위치한 해상 국가인 아젤리아 로트가 있습니다. 아라드 대륙은 수많은 종족과 다양한 문화가 공존하는 곳으로, 모험가들은 이곳에서 다양한 경험을 할 수 있습니다. 또한, 아라드 대륙에서는 수많은 던전과 모험을 즐길 수 있으며, 다양한 종족들과의 교류


SMALL MODEL GENERATION
### 질문: 바칼이 천계를 공격한 이유는?
### 답변: 바칼은 천계를 점령한 후, 자신의 왕국을 건설하기 위해 바칼의 명령을 받고 천계를 침공했습니다. 그는 천계의 모든 것을 파괴하고 새로운 왕국을 건설했습니다. 이로 인해 바칼의 분노는 극에 달했고, 바칼은 그의 왕국을 공격하게 되었습니다. 바칼의 왕국은 그의 분노로 인해 폐허가 되었고, 그의 왕국은 멸망의 위기에 처했습니다. 이에 바칼은 자신의 왕국이 멸망하는 것을 막기 위해 그의 왕국에 침입하게 되

LARGE MODEL GENERATION
### 질문: 바칼이 천계를 공격한 이유는?.
### 답변: 바칼은 천계를 점령하기 위해 천계로 공격을 감행하였습니다. 그는 천계를 지배하기 위해 아라드 대륙을 공격하고 있습니다. 그의 목적은 천계의 모든 것을 지배하는 것입니다. 그는 자신의 야망을 실현하기 위해 계속해서 천계를 향해 공격을 시도할 것입니다. 이러한 이유로 바칼은 계속 천계를 침공하고 있습니다.&quot;나의 야망을 위해서라면 천계도, 아라드 대륙도 모두 나의 것으로 만들 것이다.&quot; 바칼의 외침입니다. 그의 야망은 천계


SMALL MODEL GENERATION
### 질문: 게이볼그 프로젝트에 대해서 요약해주세요
### 답변: 게이볼그라 불리는 이 프로젝트는 하늘성을 점령하기 위한 프로젝트입니다. 게이볼그는 하늘성의 궤도를 따라 움직이며, 그 위를 지나가는 적들을 공격하는 역할을 합니다. 이 프로젝트는 게이볼그를 개발하고, 하늘성에 배치하는 것으로 시작되었습니다. 그러나 게이볼그의 개발에 많은 어려움이 있었고, 이 프로젝트는 실패로 끝났습니다. 이 프로젝트가 실패한 이후, 게이볼그에 대한 연구가 다시 시작되었고, 그 결과 현재의 게이볼그가 완성되었습니다 이 프로젝트의 성공

LARGE MODEL GENERATION
### 질문: 게이볼그 프로젝트에 대해서 요약해주세요
### 답변: 게이볼크 프로젝트는 게이볼그를 개발하는 프로젝트입니다. 이 프로젝트는 겐트의 하늘성에서 진행되고 있으며, 겐트를 지키기 위해 설계되었습니다. 게이볼그는 하늘성의 일부를 차지하고 있으며, 하늘성을 지키기 위한 무기로서 사용되고 있습니다. 이 프로젝트의 목적은 게이볼그의 성능을 높이고, 게이볼그로 하여금 하늘성을 방어하는 역할을 수행하도록 하는 것입니다. 현재 게이볼그에는 다양한 무기들이 장착되어 있으며, 추가적인 무기를 장착하기&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. RewardModel(RM)&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;RM 역시 동일한 1.3b모델을 사용했습니다. 모델은 textclassification으로 불러와 주고 num_label=1로 불러와 스코어를 리턴하도록 하였습니다. 모든 데이터는 아래와 같이 KoGPT 예제에서 사용했던 데이터 형태를 그대로 사용하였고 아래 데이터는 ChatGPT를 사용해 만든 답변입니다. ranking은 좋은 답변에 대한 순서를 나타내며 숫자가 낮을수록 더 좋은 답변임을 의미합니다. 추가적으로 사용한 답변은 던전앤파이터에 대한 내용 뿐만 아니라 보다 좋은 형태의 답변을 생성하기 위해 KoGPT에서 사용한 RM데이터를 같이 사용했습니다. 그 이유는 던파 데이터는 한정적인것도 있고 ChatGPT로 만든 답변은 장문, 중문, 단문으로 구성되어 있기 때문에 좋다 나쁘다로 구분짓기 어려운 부분이 있었기 때문에 완전 잘못된 답변을 만드는 경우를 방지하고자 KoGPT 데이터를 사용했습니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;{'prompt': '바다와 전쟁의 흔적, 그리고 마계의 사도들에 대한 이야기를 요약 해주세요.', 
'completion_0': '소녀는 처음으로 넓은 바다를 보고 감탄하며 그 아름다움에 매료되었습니다. 그러나 그녀가 바라본 바다 주변의 섬은 과거 전쟁으로 황폐해진 곳이었습니다. 이곳에서 바다와 다른 풍경을 이루는 활화산이 있는 섬에 사도 안톤이 존재했다는 사실에 놀라움을 감추지 못했습니다. 소녀는 마계와 천계를 이어주는 통신 장치를 통해 연결된 리아 리히터와 통신을 하며, 자신이 마계에서 사라진 사도들을 찾는 데에 중요한 역할을 할 것이라는 기대감을 주고받습니다. 그러나 갑자기 차원의 틈이 닫혀 통신은 끊기고, 이제 그녀는 천계와 마계의 경계에서 불확실한 미래를 향해 자신의 역할을 다하기 위해 각오를 다집니다.',
'completion_1': '바다를 처음 본 소녀는 과거 전쟁터였던 섬의 아름다움에 놀랍니다. 그리고 통신기를 통해 리아와 사도 안톤의 존재 및 상황을 나누며 정보를 주고받지만, 차원의 틈이 닫혀 다시 연락을 기다려야 하는 상황에 처하게 됩니다.',
'completion_2': '바다에 감탄한 소녀가 마계의 사도들과 관련한 중요한 통신을 하다 차원의 틈이 닫혀 소통이 끊깁니다.',
'ranking': [0, 2, 1]}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;위 데이터를 바탕으로 chosen, rejected 데이터로 만들었습니다. trl에서 사용할 수 있게끔 토크나이징 작업을 진행하였습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# div dataset to chosen and rejected 
total_data_ranking2chosen = []
for tmp in rm_data_combine:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;one_data_ranking2chosen = []

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# data 1) 0 VS 1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data = {}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['prompt'] = tmp['prompt']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if tmp['ranking'][0] &amp;lt; tmp['ranking'][1]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['chosen'] = tmp['completion_0']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['rejected'] = tmp['completion_1']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['chosen'] = tmp['completion_1']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['rejected'] = tmp['completion_0']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;one_data_ranking2chosen.append(data)


&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# data 2) 0 VS 2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data = {}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['prompt'] = tmp['prompt']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if tmp['ranking'][0] &amp;lt; tmp['ranking'][2]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['chosen'] = tmp['completion_0']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['rejected'] = tmp['completion_2']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['chosen'] = tmp['completion_2']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['rejected'] = tmp['completion_0']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;one_data_ranking2chosen.append(data)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# data 1) 1 VS 2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data = {}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['prompt'] = tmp['prompt']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if tmp['ranking'][1] &amp;lt; tmp['ranking'][2]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['chosen'] = tmp['completion_1']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['rejected'] = tmp['completion_2']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['chosen'] = tmp['completion_2']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data['rejected'] = tmp['completion_1']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;one_data_ranking2chosen.append(data)



&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;total_data_ranking2chosen.extend(one_data_ranking2chosen)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
# tokenzing dataset
def formatting_func(examples):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kwargs = {&quot;padding&quot;: &quot;max_length&quot;, &quot;truncation&quot;: True, &quot;max_length&quot;: 512, &quot;return_tensors&quot;: &quot;pt&quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chosen = examples[&quot;prompt&quot;] + &quot;\n&quot; + examples[&quot;chosen&quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rejected = examples[&quot;prompt&quot;] + &quot;\n&quot; + examples[&quot;rejected&quot;]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tokens_chosen = tokenizer.encode_plus(chosen, **kwargs)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tokens_rejected = tokenizer.encode_plus(rejected, **kwargs)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;input_ids_chosen&quot;: tokens_chosen[&quot;input_ids&quot;][0], &quot;attention_mask_chosen&quot;: tokens_chosen[&quot;attention_mask&quot;][0],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;input_ids_rejected&quot;: tokens_rejected[&quot;input_ids&quot;][0], &quot;attention_mask_rejected&quot;: tokens_rejected[&quot;attention_mask&quot;][0]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
from datasets import Dataset
train_data = Dataset.from_list(total_data_ranking2chosen[:-3000])
eval_data = Dataset.from_list(total_data_ranking2chosen[-3000:])

train_data.to_pandas()
eval_data.to_pandas()

train_dataset = train_data.map(formatting_func)
eval_dataset = eval_data.map(formatting_func)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이후에는 trainer를 사용하여 훈련을 진행하였습니다. 이번엔 기본 Trainer가 아니라 trl에서 제공하는 RewardTrainer를 사용하였습니다. 아무래도 데이터량이 훨씬 많다보니 1.3b모델로 진행했음이도 3epoch 진행하는 시간은 총 17시간이 소요됐습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;training_args = TrainingArguments(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;per_device_train_batch_size=8,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;gradient_accumulation_steps=2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;learning_rate=1e-4,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fp16=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output_dir=&quot;/content/drive/MyDrive/ds_study/던파_story/output_2_RM&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;optim=&quot;paged_adamw_8bit&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_total_limit = 3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;seed = random_seed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)


trainer = RewardTrainer(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args=training_args,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tokenizer=tokenizer,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;train_dataset=train_dataset,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;eval_dataset=eval_dataset,
)

model.config.use_cache = False&amp;nbsp;&amp;nbsp;# silence the warnings. Please re-enable for inference!
model.config.pad_token_id = model.config.eos_token_id

trainer.train()&lt;/code&gt;&lt;/pre&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;Result&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;점수 확인을 위해 간단하게 텍스트로만 넣었지만 실제로 더 정확한 점수를 얻기 위해서는 모델을 훈련을 위해 사용했던 prompt에 적용하여 점수 측정을 해주면 됩니다. 그래도 확실히 좋은 답변에 좀 더 높은 점수를 주는 것을 확인할 수 있습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# inference Score
def inference_RM(rm_model, input_text):

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input_ids = tokenizer.encode(input_text, return_tensors='pt').to(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;torch.cuda.current_device())

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output = rm_model(input_ids)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output_reward = output.logits.cpu().detach().numpy()[0]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print('input: %s\nreward score: %.1f'%(input_text, output_reward))

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return output_reward

input_texts = ['한국에서 사는건 어떤가요?\n한국에서 사는 것은 행복한 일입니다.',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'왜 점수가 안바뀌는거죠?\n제가 대답할 수 있는 질문이 아닙니다.',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'핀드에 대해서 알려줘\n융합형 핀드는 불 속성과 물 속성의 속성을 가지고 있습니다. 이 두 속성의 융합으로 인해 융합형 핀드는 정신적으로 불안한 상태에 놓여있지만, 불 속성의 성격과 물 속성의 성격을 모두 가지는 특징을 가지고 있습니다.',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]


for input_text in input_texts:
&amp;nbsp;&amp;nbsp;print('-'*70)
&amp;nbsp;&amp;nbsp;inference_RM(rm_model, input_text=input_text)
&amp;nbsp;&amp;nbsp;torch.cuda.empty_cache()
&amp;nbsp;&amp;nbsp;print()
&amp;nbsp;&amp;nbsp;

-----RESULT------
----------------------------------------------------------------------
input: 한국에서 사는건 어떤가요?
한국에서 사는 것은 행복한 일입니다.
reward score: -2.3

----------------------------------------------------------------------
input: 왜 점수가 안바뀌는거죠?
제가 대답할 수 있는 질문이 아닙니다.
reward score: -0.8

----------------------------------------------------------------------
input: 핀드에 대해서 알려줘
융합형 핀드는 불 속성과 물 속성의 속성을 가지고 있습니다. 이 두 속성의 융합으로 인해 융합형 핀드는 정신적으로 불안한 상태에 놓여있지만, 불 속성의 성격과 물 속성의 성격을 모두 가지는 특징을 가지고 있습니다.
reward score: 0.2&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. PPO&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PPO는 이전에 학습했던 모델들을 불러와 사용합니다. 데이터는 RM모델에 학습할때 사용했던 prompt(질문)데이터를 입력 데이터로 사용하여 훈련을 진행하였습니다. 지난번 trl 패키지를 알아본 내용에서 달라진 점이 있습니다. trl에서 제공해주는 함수 중 freeze model을 불러오는 함수인 'create_reference_model'가 양자화 모델을 불러와 복사하는 기능이 구현되지 않았다고 github 이슈에서 확인하여 그냥 모델을 불러와 사용하였습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;model_name = &quot;/content/drive/MyDrive/ds_study/던파_story/output_1_SFT/model_QnA_FIN&quot;
rm_adapter_id = &quot;/content/drive/MyDrive/ds_study/던파_story/output_2_RM/RM_FIN&quot;
origin_model = &quot;EleutherAI/polyglot-ko-1.3b&quot; #huggingFace

bnb_config = BitsAndBytesConfig(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;load_in_4bit=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bnb_4bit_use_double_quant=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bnb_4bit_quant_type=&quot;nf4&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bnb_4bit_compute_dtype=torch.bfloat16
)

# PPO adapter
lora_config = LoraConfig(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;r=8,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lora_alpha=32,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lora_dropout=0.05,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bias=&quot;none&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;task_type=&quot;CAUSAL_LM&quot;,
)

# Load SFT Model
model = AutoModelForCausalLMWithValueHead.from_pretrained(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model_name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;peft_config=lora_config,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;quantization_config=bnb_config,
)

# QLoRA를 통한 로드 모델은 deepcopy가 구현되지 않음 직접 하나 더 불러와야함
# model_ref = create_reference_model(model) # &amp;lt;--- TRL패키지에서도 copy.deepcopy()를 사용해 모델을 복사하고 있기 때문에 작동하지 않음. 이유는 QLoRA를 통한 로드를 고려하지 않았기 때문 수정진행중이라는 github issue확인.
model_ref = AutoModelForCausalLMWithValueHead.from_pretrained(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model_name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;peft_config=lora_config,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;quantization_config=bnb_config,
)

# Load RM
rm_model = AutoModelForSequenceClassification.from_pretrained(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rm_adapter_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;quantization_config=bnb_config,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;num_labels=1)

# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(origin_model)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;PPOTrainer를 초기화는 다음과 같이 진행합니다. collator는 들어오는 데이터가 하나의 batch안에서 동일한 형태를 맞춰주어야 하기에 꼭 필요합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def collator(data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return dict((key, [d[key] for d in data]) for key in data[0])

# initialize trainer
ppo_config = PPOConfig(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_size=16,
)
ppo_trainer = PPOTrainer(ppo_config, model, model, tokenizer, dataset=dataset, data_collator = collator)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;훈련은 다음과 같이 진행합니다. PPOTrainer안에서는 query와 input_ids값이 필수적으로 필요해 훈련을 위한 전처리 과정에서 이에 맞게 처리해주면 됩니다. 생성 옵션에 num_beams옵션을 올리면 좀 더 다양한 텍스트를 생성하게 됩니다. 단순히 max_new_tokens와 end_token만으로 깔끔한 문장이 만들어지지 않기 때문에 end_point를 마지막 온점이 있는 부분을 문장 슬라이싱 부분으로 선정하여 보상모델에 전달하도록 하였습니다. 해당 훈련 작업에서는 inference하는 작업이 들어가 있어서 1epoch에도 오랜 시간이 걸렸기에 1번만 진행하였습니다. 훈련시간은 총 8시간이었습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;generation_kwargs = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;min_length&quot;: -1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'max_new_tokens' : 128,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;top_p&quot;: 0.95,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;do_sample&quot;: True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pad_token_id&quot;: tokenizer.eos_token_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'eos_token_id' : 2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'early_stopping' : True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'num_beams' : 2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'no_repeat_ngram_size' : 4
}
epoch = 1
TARGET_EPOCH = 1
while True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;for train_step, batch in tqdm(enumerate(ppo_trainer.dataloader)):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;queries = batch['query']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query_tensors = batch[&quot;input_ids&quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query_tensors = [tensor for tensor in query_tensors]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# get model response
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response_tensors = []
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for query in (query_tensors):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query_len = len(query)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response = ppo_trainer.generate(query, **generation_kwargs)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end_point = (torch.flip(response.squeeze(), [0]) == 17).nonzero(as_tuple=False)[0] # tokenizer '.' token_id = 17
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response_tensors.append(response.squeeze()[:-end_point][query_len:])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch[&quot;response&quot;] = [tokenizer.decode(r.squeeze()) for r in response_tensors]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# get RM score
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rm_inputs = [q + '\n' + r for q, r in zip(batch[&quot;query&quot;], batch[&quot;response&quot;])] # query_txt + '\n' + tokenizer.decode(response_tensor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rewards = []
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for rm_input in rm_inputs:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rewards.append(inference_RM(rm_model, rm_input))

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# PPO
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stats = ppo_trainer.step(query_tensors, response_tensors, rewards)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ppo_trainer.log_stats(stats, batch, rewards)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# make checkpoint
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (train_step+1) % 50 == 0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;checkponit = '/content/drive/MyDrive/ds_study/던파_story/output_3_PPO/checkpoint-' + str(train_step+1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if not os.path.exists(checkponit):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.makedirs(checkponit)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model.save_pretrained(checkponit)

&amp;nbsp;&amp;nbsp;if epoch == TARGET_EPOCH:

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f'\nEpoch {epoch} FIN\n')
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break
&amp;nbsp;&amp;nbsp;epoch+=1&lt;/code&gt;&lt;/pre&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;Result&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;16개의 dataset을 가져와서 freeze 모델(before)과 ppo로 학습된 모델(after)의 결과 그리고 그 결과에 따른 reward값을 확인해보겠습니다. 아래는 5개의 대한 결과만 확인했습니다. 5개에서는 비슷해보이지만 그 밑의 스코어를 보면 확실히 RM에게 좋은 평가를 받을 수 있도록 수정되고 있다는 것을 알 수 있습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;Query :&amp;nbsp;&amp;nbsp;타우킹 샤우타는 누구인가요?
Response (before) :&amp;nbsp;&amp;nbsp; 타우킹 사도들은 샤우타를 타우로 부르고, 그들은 그를 타우킹이라 칭합니다. 이들은 샤우드의 우두머리로서 역할을 수행합니다. 또한, 타우킹은 타우라는 이름의 사도에 의해 소환되는 존재로서 그의 권능을 통해 자신의 권능으로 다른 존재를 제어하고 지배할 수 있습니다.
Response (after) :&amp;nbsp;&amp;nbsp; 타우킹 타우는 타우타의 조상입니다. 원래는 불을 잘 다루었지만 그 능력이 점차 쇠퇴해지면서 현재는 타우킹이 되었습니다. 이들은 주로 숲에서 활동하며 타우타와 같이 나무를 자릅니다. 타우타는 나무를 잘 다루며 숲을 지키기 위해 노력하고 있습니다.&amp;nbsp;&amp;nbsp;타우타가 자신의 모습을 본떠 만들어졌다고 말하는 이가 있지만 타우타 자체는 타우타나 타우타를 본떠 만들지 않았습니다.
Rewards (before)/(after) :&amp;nbsp;&amp;nbsp;tensor([0.6245]) / tensor([3.5957])

Query :&amp;nbsp;&amp;nbsp;'오필리아 베이그란스'는 어떤 종교 집단의 신도인가요?
Response (before) :&amp;nbsp;&amp;nbsp; '오필리어 베이그란스는 루크를 따르는 자들 중 하나로, 현재는 교단에서 운영하는 특수 조직의 일원입니다. 그녀는 루크의 특별한 능력을 받았으며, 이 능력으로 자신들을 이끌어 루크의 제국을 지키는 역할을 하고 있습니다.' 오필리어 베이 그란스는 종교 집단의 정신적인 지주로서 그의 능력을 믿고 따르는 신도입니다. 그녀가 자신들을 지키기 위해 수호신의 힘을 빌린다는 이야기를 들은 적이 있습니다.
Response (after) :&amp;nbsp;&amp;nbsp; '오필리오 베이그란스'()는 황도군에 합류한 후에도 종교 단체에 남아 있었습니다. 그러나 현재는 신도들의 반발로 신도들과 함께 집단 자살을 하는 것으로 알려졌습니다. 그 후 오필리아 베이 그란스는 자신의 배를 타고 도주하며 종교적인 힘을 이용해 집단을 결속했습니다. 이후 신도들은 이 종교 집단에 다시 합류할 것을 희망하고 있습니다. 하지만 현재는 신도 수가 매우 적어 다시 합류할 확률은 매우 적은 상황입니다.
Rewards (before)/(after) :&amp;nbsp;&amp;nbsp;tensor([3.3848]) / tensor([2.3184])

Query :&amp;nbsp;&amp;nbsp;에너지 생산실은 어떤 기능을 가지고 있나요?
Response (before) :&amp;nbsp;&amp;nbsp; 에너지 생산실은 에너지 저장과 전송의 기능을 가지며, 각 에너지는 다양한 형태로 사용됩니다. 에너지 저장을 위해서는 거대한 공간을 사용하며, 공간의 에너지는 다양하게 사용됩니다. 전송을 위해서는 공간을 이용한 전자기기들을 사용할 수 있으며, 이 기능은 다른 에너지 시스템과 다른 에너지 저장 및 전송 시스템이 갖고 있는 능력입니다. 이와 관련된 기능은 에너지 생산실이 갖는 고유한 기능입니다.
Response (after) :&amp;nbsp;&amp;nbsp; 에너지 생산실은 검은 대지를 관리하는 차원의 균열의 에너지를 다루는 곳입니다. 이 시설은 검은 대지와 차원의 에너지를 관리하고 연구하는 데 사용됩니다. 이곳에서는 차원의 에너지로 에너지의 흐름을 조종하고, 그로 인해 검은 대지에 새로운 에너지가 모이게 됩니다. 시설은 거대한 에너지와 검은 대지의 에너지를 결합하고 관리하여 차원의 균열에서 흘러들어오는 에너지를 효과적으로 통제할 수 있습니다.
Rewards (before)/(after) :&amp;nbsp;&amp;nbsp;tensor([-0.5986]) / tensor([2.6523])

Query :&amp;nbsp;&amp;nbsp;'람바녹'은 어떤 사람이었나요?
Response (before) :&amp;nbsp;&amp;nbsp; 람바녹은 그란플로리스의 주인으로, 검은 성전에 참가하여 모든 무기들을 파괴하고 많은 희생을 치른 전설적인 인물입니다. 검은 성전이 끝난 후, 검은 교단에 포로로 잡혀 검은 성수로 끌려갔습니다. 그의 몸에는 많은 무기들과 무기들과 같이 죽은 검은 성혈을 가지고 있었습니다. 람바녹이 남긴 검은 성수는 무기에 대한 힘을 가진 사람이며, 이 무기들로 많은 사람들을 살육한 것으로 전해집니다. 
Response (after) :&amp;nbsp;&amp;nbsp; 람바녹은 천계의 다른 모험가들과 달리 자신의 경험을 살려 무기를 다루는 법을 알려주고, 무기상에게 장비와 재료들을 구입하는 방법을 알려주는 모험가였습니다. 그래서 자신의 모험가 생활을 자랑하기 보다는 도움을 받은 모험가들을 생각하며 자신의 무기도 아낌없이 나누었다고 합니다. 그래서 그의 이름이 널리 알려져있는 것 같습니다. 그가 무기를 나누었다는 사실은 모험가 생활에서 중요한 위치를 차지하는 것으로 보입니다.
Rewards (before)/(after) :&amp;nbsp;&amp;nbsp;tensor([5.9219]) / tensor([4.1758])

Query :&amp;nbsp;&amp;nbsp;'머크우드'는 어떤 곳인가요?
Response (before) :&amp;nbsp;&amp;nbsp; '머크 우드'는 마계를 상징하는 지역 중 하나로, 마계의 중심지인 마계 도시입니다. 이곳은 거대한 숲과 계곡이 있어 모험가들의 눈을 사로 잡는 곳이기도 합니다. 현재는 '머크 타운'이라는 이름으로 마계 도시로 자리 잡고 있습니다. 모험가들은 이곳에서 모험을 시작해 마계를 정복하고 마계에 뿌리를 내리려는 야망을 가지고 있습니다..
Response (after) :&amp;nbsp;&amp;nbsp; '머크 우드'는 노스피스의 북쪽으로, 황량한 사막과 끝없이 펼쳐진 바다가 끝없이 펼쳐지는 곳입니다. 이곳은 모험가들의 성지이며, 신비한 고대 마법진들이 존재하는 곳입니다.&quot; 머크우드에는 오래된 마법진이 있는데, 그곳에 들어가면 다양한 마법진을 만나볼 수 있습니다.&quot;라고 '머크위드'에 대한 자세한 정보가 제공됩니다. 머크우드는 오래된 고대 마법진이 존재하는 장소로 알려져 있습니다.
Rewards (before)/(after) :&amp;nbsp;&amp;nbsp;tensor([3.6680]) / tensor([1.8340])&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;샘플 16개에 대한 생성된 텍스트 RM 평가입니다. 평균값과 중앙값 모두 훈련후 상승한 모습을 볼 수 있습니다. PPO를 1회만 진행했기 때문에 점수 상승이 큰지 크지 않은지 판단하기는 어렵지만 확실히 PPO를 통해 trainable model이 RM평가에 맞추어 변화하고 있다라는 것을 추론할 수 있는 결과를 얻었습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;print(&quot;mean:&quot;)
display(df_results[[&quot;rewards (before)&quot;, &quot;rewards (after)&quot;]].mean())
print()
print(&quot;median:&quot;)
display(df_results[[&quot;rewards (before)&quot;, &quot;rewards (after)&quot;]].median())



--------Result--------
mean:
rewards (before)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.307434
rewards (after)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.206085

median:
rewards (before)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.567383
rewards (after)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.184570&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&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;RL통해 Chatbot 제작 프로젝트를 진행하였습니다. 앞서 포스팅들을 통해 공부했던 RLHF에서 HF인 사람의 피드백이 아닌 RM으로 구성된 것이 RLHF 프로젝트라고 부르긴 조금 모호하긴 했지만 흐름 자체는 다를게 없었습니다. HF를 직접 구상할까도 생각해봤지만 HF 데이터를 제작하는데 그만큼 시간이 소요되기도 했고 던파 스토리에 대해서 완벽하게 알고 있는게 아니었기에 너무 긴 시간이 소요되는 문제점이 존재함을 알고 있어 HF데이터 구축이 얼마나 중요한지 알 수 있었습니다. 실제로 PPO과정을 통해 모델이 나아가는 과정은 RM이 가장 큰 영향을 주기 때문이죠...&lt;br&gt;&amp;nbsp;&lt;br&gt;다음에는 강화학습을 통해 질의응답이 아닌 이루다, 심심이와 같은 챗봇을 제작하는 프로젝트를 진행해볼까도 생각이됩니다. 근데 데이터를 모으는데 막막할것 같다는 생각도 들기는 합니다ㅎ... 아마도 그때는 개인정보로 인해 토크나이저에 특별토큰들을 추가하거나 전처리 단계에서 텍스트에 블라인딩이 필요할 것 같긴한데... 지금 이야기할 내용은 아닌 것 같으니 여기서 포스팅 및 프로젝트를 마무리하는걸로 하겠습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>머신러닝&amp;amp;딥러닝/RF(강화학습)</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/113</guid>
      <comments>https://coco0414.tistory.com/113#entry113comment</comments>
      <pubDate>Wed, 31 Jan 2024 21:50:15 +0900</pubDate>
    </item>
    <item>
      <title>PPO 구현을 위한 TRL패키지 살펴보기</title>
      <link>https://coco0414.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이번 포스팅에는 NLP분야에서 PPO 알고리즘을 구현하기 위한 TRL패키지에 대한 이야기와 이전에 포스팅했던 ColossalAI에서 제공하는 패키지의 다른점들들 몇가지에 대해서 기록합니다.&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;현재 던전앤파이터의 세계관에 대해서 이야기할 수 있는 챗봇을 만드는 작업을 진행하고 있습니다. 지난번 RLHF에 대한 이야기를 하면서 작성했던 코드들은 모두 ColossalAI에서 제공하는 패키지로 KoGPT모델을 통한 훈련을 진행하였습니다. 그런데 해당 실습을 하면서 문제점은 작은 모델이여서 생각만큼 원하는 결과가 나오지 않는다는 것이었고, 두번째로 사용하는 패키지가 최근 버전과는 맞지 않아서 강제로 다운그레이드를 해서 진행을 해야하는 번거로움이 존재했습니다. 그래서 던파 챗봇 프로젝트에서는 해당 패키지를 사용하지 않고 Huggingface에서 제공하는 TRL패키지를 사용하여 진행하고 있습니다.&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;TRL패키지를 선택한 이유는 위에서 언급한 두 가지를 모두 해결할 수 있었기 때문이었습니다. KoGPT보다 큰 모델을 불러와서 베이스 모델로 사용하는 것이 목표였습니다. 그래서 선택한 모델은 'EleutherAI/polyglot' 모델입니다. 해당 모델의 소형버전부터 원본 버전까지 존재하는데 저는 SFT모델에는 12.8B를 선택하였고 RM은 1.2B모델을 선택하였습니다. 이부분에 대해서는 해당 프로젝트 포스팅때 자세히 다루겠습니다. 여하튼 이러한 큰 모델을 불러와 작업하기에는 저의 컴퓨팅 자원이 그렇게 녹록치 않기 때문에 지난번 포스팅한&amp;nbsp;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;QLoRA를 통해 모델을 불러와 적은 자원에서 모델을 훈련하여 이 문제를 해결하였습니다. 다음 문제가 이 QLoRA와 이어지는 문제인데 큰 모델을 불러오기 위해서는 bitbyteconfg 패키지를 사용해 모델을 불러올때 나눠서 불러오는게 필요했습니다. 그런데 이 부분을 사용하기에는 ColossalAI의 패키지와 버전이 맞지 않는 상황이 발생했습니다. 그래서 다른 방안을 찾아보니 TRL패키지에서도 PPO를 위한 모듈을 제공해주고 있음을 알고 이에 맞는 코드로 수정하는 작업을 진행하고 있습니다. 현재는 PPO 훈련만 남겨둔 상황입니다. 여튼 그래서 이러한 이유로 지난번에 학습과정에서 사용한 모듈이 아닌 TRL패키지에 대한 이야기를 해보겠습니다.&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;a href=&quot;https://github.com/huggingface/trl/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TRL&lt;/a&gt;패키지에 대한 코드 내용들은 모두 아래 링크를 통해서 확인이 가능합니다. 이번 포스팅은 아래 링크와 &lt;a href=&quot;https://huggingface.co/docs/trl/main/en/index&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;huggingface 문서&lt;/a&gt;를 토대로 작성하였습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1706496662029&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - huggingface/trl: Train transformer language models with reinforcement learning.&quot; data-og-description=&quot;Train transformer language models with reinforcement learning. - GitHub - huggingface/trl: Train transformer language models with reinforcement learning.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/huggingface/trl/tree/main&quot; data-og-url=&quot;https://github.com/huggingface/trl&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GtTCm/hyVb5xQQ6w/gyFEabZkrg51FILKefqKHk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/huggingface/trl/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/huggingface/trl/tree/main&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GtTCm/hyVb5xQQ6w/gyFEabZkrg51FILKefqKHk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - huggingface/trl: Train transformer language models with reinforcement learning.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Train transformer language models with reinforcement learning. - GitHub - huggingface/trl: Train transformer language models with reinforcement learning.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SFTTrainer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SFT를 위한 Trainer를 제공합니다. 해당 Trainer는 기존 huggingface에서 사용하던 trainer와 크게 다르지 않습니다. 아래는 가장 기초적인 호출 방법으로 기존의 Trainer에서 쉽게 보던 모습입니다. 모델을 직접 불러와 Trainer에게 넘겨주어도 되고 아래처럼 huggingface에 업로드된 경로 혹은 로컬의 path를 넘겨주면 해당 경로에 있는 모델과 토크나이저를 자동으로 모델이 불러올 수 있게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706499691157&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 trl import SFTTrainer

dataset = load_dataset(&quot;imdb&quot;, split=&quot;train&quot;)

trainer = SFTTrainer(
    &quot;facebook/opt-350m&quot;,
    train_dataset=dataset,
    dataset_text_field=&quot;text&quot;,
    max_seq_length=512,
)
trainer.train()&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;TRL에 좋은점은 PEFT를 사용할 수 있게 설계되어 있다는데 있습니다. 이 부분이 Finetuning에서 크게 시간을 단축할 수 있는 LoRA를 쉽게 사용할 수 있도록 되어 있습니다. 아래와 같이 peft를 불러와 LoraConfig만 작성하여 SFTTrainer에 넘겨주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706499827314&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 trl import SFTTrainer
from peft import LoraConfig

dataset = load_dataset(&quot;imdb&quot;, split=&quot;train&quot;)

peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;,
)

trainer = SFTTrainer(
    &quot;EleutherAI/gpt-neo-125m&quot;,
    train_dataset=dataset,
    dataset_text_field=&quot;text&quot;,
    peft_config=peft_config
)

trainer.train()&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;여기에 더해 제가 봉착했던 대형 자연어 모델을 불러오기 위한 방법도 적용이 가능합니다. 이 방법에서는 Trainer밖에서 모델을 불러오고 Trainer에게 넘겨주어야만 합니다. load_in_8bit or load_in_4bit옵션을 통해 불러오면 되겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706499978449&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;,
)

model = AutoModelForCausalLM.from_pretrained(
    &quot;EleutherAI/gpt-neo-125m&quot;,
    load_in_8bit=True,
    device_map=&quot;auto&quot;,
)

trainer = SFTTrainer(
    model,
    train_dataset=dataset,
    dataset_text_field=&quot;text&quot;,
    peft_config=peft_config,
)

trainer.train()&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;SFTTrainer는 훈련을 위한 데이터는 기존에 사용하던 datasets를 통해서 사용할 수 있으니 따로 언급하지는 않겠습니다.&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;RewardTrainer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RewardTrainer에서도 크게 다를게 없습니다. 주의할점은 모델을 불러올때와 데이터의 형태입니다. 모델을 불러올때 AutoModelForSequenceClassification을 사용하여 모델을 불러오는 것을 추천하고 있습니다. 옵션을 설정하지 않고 불러올 경우 아래서 볼 데이터셋인 비교군에 대한 점수를 얻을 수 있게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706500427152&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from peft import LoraConfig, TaskType
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from trl import RewardTrainer, RewardConfig

model = AutoModelForSequenceClassification.from_pretrained(&quot;gpt2&quot;)
peft_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    inference_mode=False,
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
)

...

trainer = RewardTrainer(
    model=model,
    args=training_args,
    tokenizer=tokenizer,
    train_dataset=dataset,
    peft_config=peft_config,
)

trainer.train()&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;RewardTrainer에 넘겨줄 dataset은 아래 4가지에 대한 값을 갖고 있어야만 정상적으로 작동합니다. 따라서 이에 대한 전처리를 토크나이저를 통해 만들어주시면 되겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #4b5563; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;input_ids_chosen&lt;/li&gt;
&lt;li&gt;attention_mask_chosen&lt;/li&gt;
&lt;li&gt;input_ids_rejected&lt;/li&gt;
&lt;li&gt;attention_mask_rejected&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PPOTrainer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PPOTrainer의 사용법은 간단합니다. 이전에 훈련했던 모델들을 불러와 지정해주면 됩니다. PPO를 위해서 필요한 모델은 총 3개입니다. Trainable Model, Freeze Model, Reward Model입니다. 초기화시 Trainable, Freeze 모델 두 가지는 같은 모델입니다. 그래서 불러오는 작업을 똑같이 두 번 진행해도 되지만 trl 패키지 내에서 모델을 deepcopy하는 기능도 제공하고 있습니다. 또한, freeze model을 제공하지 않을 경우 훈련 모델을 copy하도록 내부적으로 설계되어 있습니다.&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;이후에 진행될 작업은 PPO과정을 그대로 구현해주면 됩니다. trainer.step에서는 입력 tensor, 생성된 tensor, 보상값 이렇게 전달하여 step을 진행하면됩니다. 이 과정을 통해서 trainalbe model의 가중치가 업데이트가 이뤄집니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; style=&quot;color: #212121; text-align: start;&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# trl 패키지 내에 있는 freeze model create function
#from trl import create_reference_model
#ref_model = create_reference_model(model)

ppo_trainer = PPOTrainer(config, model, ref_model, tokenizer, dataset=dataset)

for epoch, batch in tqdm(enumerate(ppo_trainer.dataloader)):
    query_tensors = batch[&quot;input_ids&quot;]

    #### Get response from gpt2
    response_tensors = []
    for query in query_tensors:
        gen_len = output_length_sampler()
        generation_kwargs[&quot;max_new_tokens&quot;] = gen_len
        response = ppo_trainer.generate(query, **generation_kwargs)
        response_tensors.append(response.squeeze()[-gen_len:])
    batch[&quot;response&quot;] = [tokenizer.decode(r.squeeze()) for r in response_tensors]

    #### Compute sentiment score
    texts = [q + r for q, r in zip(batch[&quot;query&quot;], batch[&quot;response&quot;])]
    pipe_outputs = sentiment_pipe(texts, **sent_kwargs)
    rewards = [torch.tensor(output[1][&quot;score&quot;]) for output in pipe_outputs]

    #### Run PPO step
    stats = ppo_trainer.step(query_tensors, response_tensors, rewards)
    ppo_trainer.log_stats(stats, batch, rewards)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>머신러닝&amp;amp;딥러닝/RF(강화학습)</category>
      <category>NLP</category>
      <category>PPO</category>
      <category>trl</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/112</guid>
      <comments>https://coco0414.tistory.com/112#entry112comment</comments>
      <pubDate>Wed, 31 Jan 2024 11:17:53 +0900</pubDate>
    </item>
    <item>
      <title>QLoRA-Efficient Finetuning of Quantized LLMs</title>
      <link>https://coco0414.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 포스팅에서는 LoRA에 대한 포스팅을 진행했습니다. LoRA가 등장하게 된 배경은 LLM모델의 크기가 시간이 지날수록 커지고 있어 finetuning과정에서 모든 파라미터들을 학습시키기에는 컴퓨팅 자원에 대한 부담이 커진다라는 것이였습니다. LoRA를 통해 속도와 메모리에 대한 부담을 줄일 수 있는것은 사실이였지만 중요한건 LoRA에서도 원래의 모델에 대한 가중치를 사용한다는 것입니다.(자세한 내용은 지난 포스팅을 참고해주세요)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;예를들어서&amp;nbsp; GPT-3를 finetuning하는 과정을 생각해보겠습니다. 150B의 파라미터를 갖고 있습니다. 이 모델 자체를 일반적인 GPU에 올리기란 쉽지 않습니다. 순수하게 fully finetuning을 진행한다고 하면 A100 80G GPU가 약 70개가 되는 자원이 필요하고, Inference는 7개의 A100 GPU가 필요할 것입니다. 물론 LoRA를 통한다면 finetuning과정은 크게 축소될 수 있습니다. 하지만 Inference과정에서는 그 영향을 받지 못합니다. 이러한 어려움을 해결하고자 나온 방법이 QLoRA입니다. 대표적으로 QLoRA는 65B의 파라미터를 갖고 있는 모델을 finetuning하는데 고작 싱글 GPU(48G)만으로 훈련을 성공했다고 합니다. 단순히 하나의 모델이 아니라 1000개가 되는 모델들을 통해 실험을 진행하였다고합니다. Quantization(양자화) + LoRA로 그 방법에 대해서 알아보겠습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;제가 이해한 내용을 바탕으로 해당 글은 작성되었으며 좀 더 자세한 내용은 아래 링크를 참고해주세요.&lt;br /&gt;&lt;a href=&quot;https://github.com/artidoro/qlora?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/artidoro/qlora?tab=readme-ov-file&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - artidoro/qlora: QLoRA: Efficient Finetuning of Quantized LLMs&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;QLoRA: Efficient Finetuning of Quantized LLMs. Contribute to artidoro/qlora development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/artidoro/qlora?tab=readme-ov-file&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d6usCB/hyU84NfZol/QgrxeLCyR96AMu9zrHGSEK/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_131_1064_236&quot; data-og-url=&quot;https://github.com/artidoro/qlora&quot;&gt;&lt;a href=&quot;https://github.com/artidoro/qlora&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/artidoro/qlora?tab=readme-ov-file&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d6usCB/hyU84NfZol/QgrxeLCyR96AMu9zrHGSEK/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_131_1064_236');&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;GitHub - artidoro/qlora: QLoRA: Efficient Finetuning of Quantized LLMs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;QLoRA: Efficient Finetuning of Quantized LLMs. Contribute to artidoro/qlora development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Model Quantization?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QLoRA에 대한 설명에 앞서 모델 양자화(Model Quantization)에 대해서 간단하게 짚고 넘어가보려고 합니다. 사실 이 부분만 알면 이를 응용하고 LoRA와 결합했다는게&amp;nbsp; QLoRA의 전부이기는합니다...&lt;br /&gt;&amp;nbsp;&lt;br /&gt;양자화(Quantization)는 딥러닝 모델의 파라미터를 더 작은 비트 수로 표현하는 기술을 말합니다. 부동 소수점 수를 표현하는데 사용되는 비트 수를 감소시킴으로써 모델을 효과적으로 압축하고 메모리의 우위를 확보하여 추론 속도를 향상시킵니다. 이런 양자화는 본질적으로 한 data type을 다른 data type으로 반올림하며 수행됩니다. 모델의 양자화에 대해서 이야기 해보았는데 좀 추상적이니 예를들어서 그 과정을 이해해보겠습니다. 양자화가 진행되는 과정은 크게 정규화, 양자화, 역양자화 3가지 단계로 구분됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;정규화&lt;/b&gt;&lt;br /&gt;-&amp;gt; 모델의 파라미터 확은 가중치 혹은 활성화 값을 정규화합니다. 예를들어, 0과 1사이의 값으로 스케일링을 진행한다고 해보겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;양자화&lt;/b&gt;&lt;br /&gt;-&amp;gt; 정규화된 값을 8비트 양자화로 표현한다고 가정해보겠습니다. 8비트 양자화는 2의 8승으로 256개의 값으로 표현하게 됩니다. 양자화 하려는 값이 정규화를 통해 0.2라는 값을 갖고 있었다면 8비트 양자화는 이와 가장 가까운 값인 0.19921875로 양자화가 진행됩니다. 이를 8비트로 표현하여 값을 갖고 있게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역양자화&lt;/b&gt;&lt;br /&gt;8비트화해서 갖고 있던 값은 역양자화를 통해 다시 부동 소수점으로 변환하게 되는데 당연히 가장 가까운 값이었던 0.19921875로 값이 변환되게 됩니다. 원래 정규화를 통해 얻은 값은 0.2였지만 양자화를 통해 변화된 값은 작지만 오차를 발생 시키는 모습을 볼 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 양자화는 역전파 과정이 많이 일어날수록 이러한 오차가 누적되어 그 의미가 퇴색된다고 볼 수 있습니다. 이러한 문제점을 극복하여 제안한 방법이 QLoRA입니다. 그 방법을 지금부터 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;QLoRA&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://arxiv.org/pdf/2305.14314.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;QLoRA 논문&lt;/span&gt;&lt;/a&gt;의 내용을 바탕으로 이야기 해보겠습니다. 앞서 이야기했지만 QLoRA는 4비트 양자화 + LoRA를 활용하여 65B 모델을 48GB 싱글 GPU로 finetuning작업에 성공하였습니다. 일반적으로는 65B모델을 사용하려면 VRAM 260GB+&amp;alpha;가 필요합니다. 그런데 QLoRA는 자원을 엄청난 이점을 취했음에도 성능면에서 크게 뒤쳐지는 모습이 없었습니다. 과연 어떻게 이렇게 모델 사이즈를 줄일 수 있었을까요? 이에 대한 테크닉은 3가지를 통해 이뤄질 수 있었습니다. 이제 그 세 가지를 알아보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 4-bit NormalFloat Quantization(NF4)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NF4의 이 데이터 형식은 Quantile Quantization(분위수 양자화)을 기반으로 하며, 각 양자화 구간이 입력 텐서에서 동일한 수의 값을 갖도록 하는 정보 이론적으로 최적인 데이터 형식입니다. 그런데 이 분위수 양자화의 단저믄 분위수 추정 과정에서의 비용이 크게 들어간다는 점입니다. 이를 해결하기 위해 SRAM quantiles와 같은 빠른 분위수 근사 알고리즘이 사용됩니다. 그런데 이렇게 하면 또 문제가 발생할 수 있는게 이상치에 대해 큰 양자화 오차를 범할 수 있다는 것입니다. 이 오차를 피하기 위한 방법중 하나는 양자화를 위한 상수가 고정된 분포에서 나오는 경우입니다. 그런데 훈련된 신경망 모델의 가중치는 표준 정규 분포의 형태를 갖고 있습니다. 따라서 이를 통해 하나의 고정된 분포로 변화할 수 있고 이를 표준편차를 이용해 [-1, 1] 범위로 설정하여 정규화를 진행합니다. 이를 통해 얻게 되는 것은 양자화 속도와 오차의 감소 두 효과 모두 얻을 수 있는 방법이 되었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) Double Quantization(DQ)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양자화를 진행하게 되면 앞서 이야기한 양자화 상수라는게 존재합니다. DQ는 1차로 진행한 양자화에서의 상수를 다시 양자화를 진행합니다. 이렇게 함으로서 메모리에 대한 이점을 얻으려고 했습니다. 실제로 이 방법을 통해 파라미터당 0.373 bit의 이득을 취할 수 있다고 합니다. 이러한 DQ가 가능한 이유는 &lt;a href=&quot;https://arxiv.org/pdf/2212.09720.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;다음 논문&lt;/span&gt;&lt;/a&gt;에서 증명한 내용을 바탕으로 8비트 양자화가 결과에 큰 영향을 주지 못한다는 가정을 바탕으로 진행하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2uiou/btsDTvg4IbK/6hYtKgMGUlDb3gyYl3kOF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2uiou/btsDTvg4IbK/6hYtKgMGUlDb3gyYl3kOF1/img.png&quot; data-alt=&quot;DQ예시 논문 중 발췌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2uiou/btsDTvg4IbK/6hYtKgMGUlDb3gyYl3kOF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2uiou%2FbtsDTvg4IbK%2F6hYtKgMGUlDb3gyYl3kOF1%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;720&quot; height=&quot;67&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DQ예시 논문 중 발췌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) Paged Optimizers&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nvidia의 통합 메모리 기능을 사용하여 GPU가 메모리가 부족한 상황이 생긴다면 CPU RAM 혹은 Disk로 작업을 전송하여 작업을 처리할 수 있도록 한다고 합니다. 이는 RAM도 작업량이 메모리보다 클 경우 가상메모리로 HDD,SSD를 활용하는 방식으로 똑같이 활용되기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bidads/btsDW3DAOTD/nKZFOPArsecaHwQI0BTcO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bidads/btsDW3DAOTD/nKZFOPArsecaHwQI0BTcO0/img.png&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;776&quot; style=&quot;width: 21.7177%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bidads/btsDW3DAOTD/nKZFOPArsecaHwQI0BTcO0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbidads%2FbtsDW3DAOTD%2FnKZFOPArsecaHwQI0BTcO0%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;646&quot; height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJfShO/btsDQSD4N50/j8SxERAK5HiQkczhpQb1vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJfShO/btsDQSD4N50/j8SxERAK5HiQkczhpQb1vK/img.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;433&quot; style=&quot;width: 77.1195%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJfShO/btsDQSD4N50/j8SxERAK5HiQkczhpQb1vK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJfShO%2FbtsDQSD4N50%2Fj8SxERAK5HiQkczhpQb1vK%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;1280&quot; height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/div&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;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;IN CODE&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QLoRA는 huggingface에서 쉽게 호출이 가능합니다. 아래는 직접 구현보다는 QLoRA에서 설명한 요소들을 설정하여 모델을 불러오는 간단한 예시로 글을 마무리하겠습니다.&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;아래는 huggingface에서 모델을 불러올때 설정할 수 있는 Config 클래스입니다. LLM.int8, FP4, NF4를 선택하여 모델을 불러올 수 있도록 현재는 지원하고 있습니다. 이후에 추가적으로 불러올 수 있는 arguments가 추가된다면 이 클래스에 추가 될 것이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706148653939&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@dataclass
class BitsAndBytesConfig(QuantizationConfigMixin):
    &quot;&quot;&quot;
    This is a wrapper class about all possible attributes and features that you can play with a model that has been
    loaded using `bitsandbytes`.

    This replaces `load_in_8bit` or `load_in_4bit`therefore both options are mutually exclusive.

    Currently only supports `LLM.int8()`, `FP4`, and `NF4` quantization. If more methods are added to `bitsandbytes`,
    then more arguments will be added to this class.

    Args:
        load_in_8bit (`bool`, *optional*, defaults to `False`):
            This flag is used to enable 8-bit quantization with LLM.int8().
        load_in_4bit (`bool`, *optional*, defaults to `False`):
            This flag is used to enable 4-bit quantization by replacing the Linear layers with FP4/NF4 layers from
            `bitsandbytes`.
        llm_int8_threshold (`float`, *optional*, defaults to 6.0):
            This corresponds to the outlier threshold for outlier detection as described in `LLM.int8() : 8-bit Matrix
            Multiplication for Transformers at Scale` paper: https://arxiv.org/abs/2208.07339 Any hidden states value
            that is above this threshold will be considered an outlier and the operation on those values will be done
            in fp16. Values are usually normally distributed, that is, most values are in the range [-3.5, 3.5], but
            there are some exceptional systematic outliers that are very differently distributed for large models.
            These outliers are often in the interval [-60, -6] or [6, 60]. Int8 quantization works well for values of
            magnitude ~5, but beyond that, there is a significant performance penalty. A good default threshold is 6,
            but a lower threshold might be needed for more unstable models (small models, fine-tuning).
        llm_int8_skip_modules (`List[str]`, *optional*):
            An explicit list of the modules that we do not want to convert in 8-bit. This is useful for models such as
            Jukebox that has several heads in different places and not necessarily at the last position. For example
            for `CausalLM` models, the last `lm_head` is kept in its original `dtype`.
        llm_int8_enable_fp32_cpu_offload (`bool`, *optional*, defaults to `False`):
            This flag is used for advanced use cases and users that are aware of this feature. If you want to split
            your model in different parts and run some parts in int8 on GPU and some parts in fp32 on CPU, you can use
            this flag. This is useful for offloading large models such as `google/flan-t5-xxl`. Note that the int8
            operations will not be run on CPU.
        llm_int8_has_fp16_weight (`bool`, *optional*, defaults to `False`):
            This flag runs LLM.int8() with 16-bit main weights. This is useful for fine-tuning as the weights do not
            have to be converted back and forth for the backward pass.
        bnb_4bit_compute_dtype (`torch.dtype` or str, *optional*, defaults to `torch.float32`):
            This sets the computational type which might be different than the input time. For example, inputs might be
            fp32, but computation can be set to bf16 for speedups.
        bnb_4bit_quant_type (`str`,  *optional*, defaults to `&quot;fp4&quot;`):
            This sets the quantization data type in the bnb.nn.Linear4Bit layers. Options are FP4 and NF4 data types
            which are specified by `fp4` or `nf4`.
        bnb_4bit_use_double_quant (`bool`, *optional*, defaults to `False`):
            This flag is used for nested quantization where the quantization constants from the first quantization are
            quantized again.
        kwargs (`Dict[str, Any]`, *optional*):
            Additional parameters from which to initialize the configuration object.
    &quot;&quot;&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;그러면 이제 위에서 논문에서 설정한 내용을 바탕으로 불러오는 코드를 작성해보겠습니다. 양자화 타입은 NF4로 설정하고 Double Quantization을 진행하도록 설정하겠습니다. 또한 추가적으로 compute type은 실질적인 속도에 영향을 줄 수 있는 요소로 타입을 fp32가 아닌 bf16으로 설정하여 모델을 불러와 보겠습니다. 아래 코드를 실행하면 큰 모델을 colab에서도 불러올 수 있게 됩니다.&lt;span style=&quot;color: #d4d4d4;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706149097338&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

# # 모델, 토크나이저

model_id = &quot;EleutherAI/polyglot-ko-12.8b&quot; #huggingFace

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type=&quot;nf4&quot;,
    bnb_4bit_compute_dtype=torch.bfloat16
)

model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map={&quot;&quot;:0})&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QLoRA를 직접적으로 바로 체감할 수 있는 부분은 Colab에서 감당할 수 없는 모델을 불러와서 finetuning하는 과정입니다. 논문의 예시처럼 너무 큰 모델을 불러올 수는 없습니다. 기본 GPU가 그렇게 크지 않기 떄문이기도 합니다. 그런데 최근의 LLM들은 small사이즈 모델로도 나오기 때문에 이러한 모델을 불러와 직접 QLoRA를 통해 LLM모델에 대한 finetuning을 진행해볼 수 있습니다. 저도 이 방법을 통해서 기존에 던전앤파이터 챗봇 만들기 프로젝트를 KoGPT가 아닌 polyglot-ko-12.8b모델을 활용해 진행해보려 합니다. 이에 대한 결과는 훈련이 성공하면 다음 포스팅에 올려보겠습니다!&lt;span style=&quot;color: #ce9178;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>머신러닝&amp;amp;딥러닝</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/111</guid>
      <comments>https://coco0414.tistory.com/111#entry111comment</comments>
      <pubDate>Wed, 24 Jan 2024 18:42:40 +0900</pubDate>
    </item>
    <item>
      <title>LoRA(Low-Rank Adaptation of LLM)</title>
      <link>https://coco0414.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 LoRA(Low-Rank Adaptation of LLM of Large Language Model)에 대한 이야기를 해보겠습니다.&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;LoRA는 마이크로소프트에서 Finf-tuning에 대한 새로운 접근법을 제안한 것입니다. LoRA가 등장하게 된 배경에는 많은 배경들이 있지만 그 중 하나가 바로 거대한 자연어 모델(LLM)들이 속속히 등장한 것이 가장 큰 이유입니다. LLM 그 전에는 없었는가? 그렇지는 않습니다. 그런데 OpenAI의 GPT-3의 발표로 인해 그 성장 폭이 엄청났죠. LoRA 논문이 발표된 해는 2021년입니다. 아래 그림을 보면 이 방법이 왜 등장했는지에 이해할 수 있습니다. 왼쪽 그림에서 GPT-3의 파라미터 증가량이 얼마나 큰 변화량이었는지 알 수 있습니다. 그런데 지금 2024년 이에 맞추어 대부분의 LLM들이 기본적으로 100B(Billion) 근처의 파라미터들을 갖고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhoUaY/btsDJmE7dDJ/dlrMFyLmbjpD3w1RStF05k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhoUaY/btsDJmE7dDJ/dlrMFyLmbjpD3w1RStF05k/img.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;848&quot; data-is-animation=&quot;false&quot; style=&quot;width: 36.2545%; margin-right: 10px;&quot; data-widthpercent=&quot;36.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhoUaY/btsDJmE7dDJ/dlrMFyLmbjpD3w1RStF05k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhoUaY%2FbtsDJmE7dDJ%2FdlrMFyLmbjpD3w1RStF05k%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;1200&quot; height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkaVpV/btsDQSoJg2V/jfICDSVyWrsJBxzzQf2IM0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkaVpV/btsDQSoJg2V/jfICDSVyWrsJBxzzQf2IM0/img.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;262&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;63.32&quot; style=&quot;width: 62.5828%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkaVpV/btsDQSoJg2V/jfICDSVyWrsJBxzzQf2IM0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkaVpV%2FbtsDQSoJg2V%2FjfICDSVyWrsJBxzzQf2IM0%2Fimg.jpg&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;640&quot; height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/div&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;다양하고 복잡한 자연어 처리를 위한 많은 파라미터들은 모델의 성능과도 연결이 됩니다. 그런데 모든 기업에서 이 모델의 모든 파라미터들이 필요하지는 않습니다. 그런데 일반적으로 Fine-tuning을 한다고 하면 순전파와 역전파 과정을 거치는 과정에서 이 파라미터들의 가중치들이 모두 업데이트가 이뤄집니다. 가령, GPT-3모델을 예로 들어보면 150B이면 한 번의 업데이트 과정에서 1500억개의 파라미터들의 계산이 이뤄져야합니다. 서비스를 위해 이 계산량을 감당하기란 쉽지 않습니다. 그런데 그래프를 보시면 아시겠지만 파라미터들은 점점 우상향 하고 있습니다. 물론 하드웨어의 발전으로 처리속도가 증가하고 있지만 그만큼 고가의 장비들일수밖에 없습니다. 이에 대한 해결방법들이 많이 연구되었고 그 중 하나가 LoRA입니다.&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;LoRA는 PDA 혹은 요약(Summarize)를 떠올리시면 조금 이해가 쉽게 됩니다. PDA는 고차원의 데이터를 저차원으로 축소 시켜 데이터 처리를 좀 더 쉽게 하고 그럼에도 데이터에 대한 중요 정보를 잃지 않는다는 것이 장점입니다. 요약 역시 똑같습니다. 물론 PDA를 활용도 하기는 하지만 다양한 방법들이 있긴합니다만 결국 요약 역시 많은 문장들로 구성되어 있는 글에서 핵심이 되는 문장을 뽑아내거나 혹은 그 안에서 또 키워드를 찾아 요약의 문장을 만들어 내는 것입니다. 이것 역시 똑같이 많은 글에서 적은 글로 축소하는 것이죠. LoRA의 접근은 이러한 접근에서 시작되었고 &lt;b&gt;파라미터도 저차원으로도 충분히 표현이 가능하다&lt;/b&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;그런데 이런 문제는 이야기했지만 LoRA가 등장하기 전에도 많은 방법이 있었지만 그 중 하나가 Adapter를 사용해 Fine-tuning을 하고 있었습니다. 그런데 LoRA도 논문에 보면 같은 Adaptation이라는 단어를 사용합니다. Adapter와 다른점이 무엇인지 알아보겠습니다.&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;Adapter는 아래 그림과 같은 구조로 설계했습니다. Fine-tuning에서 너무 많은 파라미터들을 훈련시키기 어려우니 중간에 Adapter층을 넣어서 그 수를 감소시켜 이득을 취하자라는 접근법입니다. 오른쪽 그림의 Adapter내부를 보면 입력을 축소 시키고 출력에서는 원래대로 다시 돌려서 내보냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nw8eh/btsDJCA0ijS/zCmS7FUFrnsorvl59OR38K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nw8eh/btsDJCA0ijS/zCmS7FUFrnsorvl59OR38K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nw8eh/btsDJCA0ijS/zCmS7FUFrnsorvl59OR38K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnw8eh%2FbtsDJCA0ijS%2FzCmS7FUFrnsorvl59OR38K%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;598&quot; height=&quot;378&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;572&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 현재의 솔루션들이 만족하는가에대한 공격적인 의문을 던지며 Adapter와 Prefix tuning방법에 대한 단점들을 이야기하고 있고 이를 LoRA에서는 해소가 된다라고 이야가힙니다. Adapter에서는 앞서 이야기한 Inference Latency를 이야기하고 Prefix에서는 sequence_length에 대한 점을 이야기하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 7.45.20.png&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WTYZv/btsDRHHdo9B/fk6YVwCLhkaB6MUlCStw0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WTYZv/btsDRHHdo9B/fk6YVwCLhkaB6MUlCStw0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WTYZv/btsDRHHdo9B/fk6YVwCLhkaB6MUlCStw0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWTYZv%2FbtsDRHHdo9B%2Ffk6YVwCLhkaB6MUlCStw0K%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;1908&quot; height=&quot;582&quot; data-filename=&quot;스크린샷 2024-01-22 오후 7.45.20.png&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;582&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;그렇다면 LoRA에서는 어떻게 이러한 부분을 해결했을까요? 아래 그림은 더 효율적인 Fine-tuning을 위해 제시한 LoRA의 접근 방법입니다. 그림만 보더라도 훨씬 간단해 보입니다. 그런데 흐름은 위에서 본 Adapter와 비슷합니다. 파란삭 사각형에는 기존 모델의 weights들이 존재합니다. 그리고 A와 B Matrix가 존재하는데 이는 훈련된 모델의 가중치 과정에 존재하는게 아니라 따로 존재합니다. 기존 Adapter와 다른점이 바로 이부분입니다. 이를 통해서 inference latency를 크게 상승시킬 수 있다는 것입니다. 이게 무슨 이야기냐하면 기존의 Adapter 역시 파라미터를 줄이는 접근법은 같아 fine-tuning과정에서 연산량이 같이 적어질 수 있지만 이후 inference 과정에서 기존 모델에 층을 삽입했기 때문에 그만큼 연산량이 증가할 수 밖에 없습니다. 그런데 LoRA는 기존 모델의 가중치와 별개로 연산이 이뤄지는 A와 B가 존재하고 이를 계산하여 그냥 기존 모델의 가중치에 더해주기만 하면 결과가 나오게 됩니다. 그래서 연산량이 당연히 훨씬 적을 수 밖에 없습니다. 이게 LoRA가 기존 Adapter와 다른점입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 7.01.46.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blbSli/btsDQeFxAHq/CrlAVcdlwkHftAv84t3u1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blbSli/btsDQeFxAHq/CrlAVcdlwkHftAv84t3u1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blbSli/btsDQeFxAHq/CrlAVcdlwkHftAv84t3u1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblbSli%2FbtsDQeFxAHq%2FCrlAVcdlwkHftAv84t3u1k%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;430&quot; height=&quot;476&quot; data-filename=&quot;스크린샷 2024-01-22 오후 7.01.46.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;678&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;아래 두 식은 Fully Fine-tuning과 LoRA에서 제안하는 Fine-tuning 수식입니다. 왼쪽 식은 모든 파라미터를 업데이트하여&amp;nbsp; &amp;Phi; 최대화 하는 것이 목표입니다. 오른쪽은 그와 다르게 &amp;Theta;를 최대화 하는 것이 목표입니다. 이게 위에서 쭉 확인하면서 내려온 Adapter층을 삽입한 것과 A와 B를 추가하여 따로 파라미터만을 계산한 LoRA의 다른점을 식으로 표현한 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/84duC/btsDQaXwZue/4lpv5iIfq9MGfGKeSAKA30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/84duC/btsDQaXwZue/4lpv5iIfq9MGfGKeSAKA30/img.png&quot; style=&quot;width:45.552150628724654%&quot; data-widthpercent=&quot;46.09&quot; data-filename=&quot;스크린샷 2024-01-22 오후 7.36.31.png&quot; data-origin-height=&quot;184&quot; data-origin-width=&quot;720&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/84duC/btsDQaXwZue/4lpv5iIfq9MGfGKeSAKA30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F84duC%2FbtsDQaXwZue%2F4lpv5iIfq9MGfGKeSAKA30%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;720&quot; height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tEEXe/btsDJWzo2Yy/niHf1F9WA1AjGcZsiKRJj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tEEXe/btsDJWzo2Yy/niHf1F9WA1AjGcZsiKRJj1/img.png&quot; style=&quot;width:53.28505867360093%&quot; data-widthpercent=&quot;53.91&quot; data-filename=&quot;스크린샷 2024-01-22 오후 7.36.59.png&quot; data-origin-height=&quot;194&quot; data-origin-width=&quot;888&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tEEXe/btsDJWzo2Yy/niHf1F9WA1AjGcZsiKRJj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtEEXe%2FbtsDJWzo2Yy%2FniHf1F9WA1AjGcZsiKRJj1%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;888&quot; height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/div&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;LoRA를 통한 이점은 다음과 같다고 이야기합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Replace LoRA for each downstream task, not finetuning all parameters&lt;br /&gt;사전 학습된 모델은 LoRA 모듈이 서로 다른 task에 빌드하도록 공유, 사용 가능. 사전 학습 모델의 매개변수를 동결시키고 이를 행렬 A와 B로 대체함으로써 스토리지 요구 사항과 작업 전환 오버헤드를 크게 줄일 수 있다.&lt;/li&gt;
&lt;li&gt;no store gradient &amp;amp; just optimize low rank decomposition matrice&lt;br /&gt;LoRA는 adaptation 옵티마이저를 사용해서 대부분의 매개변수에 대한 옵티마이저 상태를 유지하거나 그래디언트를 계산할 필요가 없기 때문에 학습을 보다 효율적으로 만들고 진입에 대한 하드웨어 장벽을 최대 3배까지 줄이는 대신 low-rank 행렬을 최적화시킨다.&lt;/li&gt;
&lt;li&gt;no inference latency&lt;br /&gt;간단한 선형 구조로 추가적인 inference latency없이 훈련 가능한 행렬을 동결시킨 가중치와 병합할 수 있다.&lt;/li&gt;
&lt;li&gt;Combining LoRA with prefix tuning&lt;br /&gt;LoRA는 기존에 존재하는 많은 방식들과 직교하기 때문에 prefix 기반 접근과 함께 사용될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이 방법을 사용해서&amp;nbsp; &lt;a href=&quot;https://github.com/Beomi/KoAlpaca&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KoAlpaca&lt;/a&gt; 모델이 학습되었습니다. 마지막 이점인 완벽한 prefix는 아니지만 이 부분과 같이 함께 사용되어 해당 모델이 학습되었으니 궁금하신분은 참고하면 좋을 것 같습니다.&lt;/p&gt;</description>
      <category>머신러닝&amp;amp;딥러닝/NLP</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/110</guid>
      <comments>https://coco0414.tistory.com/110#entry110comment</comments>
      <pubDate>Mon, 22 Jan 2024 20:23:00 +0900</pubDate>
    </item>
    <item>
      <title>RLHF모델 실습(1)_데이터 수집 및 정제_던전앤파이터 챗봇 만들기</title>
      <link>https://coco0414.tistory.com/109</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;i&gt;해당 실습은 단순히 RLHF 예제를 따라해보는게 아니라 실제로 나만의 데이터를 수집하고 만들어서 훈련을 시켜보는 목적에 있습니다.&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아직 RLHF에 대한 포스팅이 마무리되지는 않았지만 내용을 정리하고 있어서 포스팅하지 못했습니다. 너무 포스팅만하면 집중이 잘 안되기도 해서 공부한 내용을 바탕으로 실습 프로젝트를 진행하고 있습니다. 이 글에 포스팅된 부분도 아직 진행중이지만 시간이 해결해줄 부분만 남았고 간단한 내용이라 먼저 포스팅을 진행합니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RLHF실습에 선택된 데이터는 던전앤파이터의 스토리 관련 데이터를 선택하였습니다. 게임 쪽에 관심이 많았고 궁극적으로 만들어 보고 싶은건 게임 캐릭터의 성격을 가진 자연어 모델을 만들어보고 싶었습니다. 그래서 게임 분야의 스토리를 선택하였습니다. 그 중에서도 던전앤파이터를 선택한 이유는 해당 모델 실습을 진행하기 위해서는 데이터가 충분히 많았어야 했습니다. 단순히 스토리를 정리된 텍스트만으로는 모델이 학습할 형태의 텍스트를 만드는데 한계가 있어서 정보 제공이 많이 되고 있는 게임 스토리를 선택하다보니 던전앤파이터 스토리를 선택하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데이터 수집 및 출저&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;던전앤파이터 스토리 텍스트 데이터를 수집은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://bbs.ruliweb.com/news/board/17/read/61&quot;&gt;루리웹-던파 스토리&lt;/a&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.dnf-universe.com/&quot;&gt;던전앤파이터 공식 홈페이지(DFU)&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;두 사이트를 통해 데이터를 수집하였습니다. 단순히 크롤링을 진행한 것이기 때문에 크롤링과 관련된 내용은 포스팅에서는 스킵하겠습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1705655684172&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ddrhJo/hyUTJJj4Ue/K1nyTpLzKdKz3roq7q3l31/img.jpg?width=236&amp;amp;height=177&amp;amp;face=0_0_236_177,https://scrap.kakaocdn.net/dn/qdW0K/hyUXPVq0BY/V82EasxFnsLOeflusivRQk/img.jpg?width=236&amp;amp;height=177&amp;amp;face=0_0_236_177,https://scrap.kakaocdn.net/dn/UX83e/hyUTKnYrqu/r7DlKnJ4Ker5iFSaSLdK41/img.png?width=740&amp;amp;height=555&amp;amp;face=0_0_740_555&quot; data-og-url=&quot;https://bbs.ruliweb.com/news/board/17/read/61&quot; data-og-source-url=&quot;https://bbs.ruliweb.com/news/board/17/read/61&quot; data-og-host=&quot;bbs.ruliweb.com&quot; data-og-description=&quot;연재 목차 ─────────────────────── ■ 던파 스토리 총정리 1부&amp;nbsp;-&amp;nbsp;현재 페이지&amp;nbsp;● - 던전 앤 파이터 : 세계관 ■ 던파 스토리 총정리 2부 - 아라드 역사 전반기 (아라&quot; data-og-title=&quot;&amp;lsquo;던전 앤 파이터&amp;rsquo; 세계관/스토리 총정리 1부 | 루리웹 게임 스토리 | 루리웹&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://bbs.ruliweb.com/news/board/17/read/61&quot; data-source-url=&quot;https://bbs.ruliweb.com/news/board/17/read/61&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ddrhJo/hyUTJJj4Ue/K1nyTpLzKdKz3roq7q3l31/img.jpg?width=236&amp;amp;height=177&amp;amp;face=0_0_236_177,https://scrap.kakaocdn.net/dn/qdW0K/hyUXPVq0BY/V82EasxFnsLOeflusivRQk/img.jpg?width=236&amp;amp;height=177&amp;amp;face=0_0_236_177,https://scrap.kakaocdn.net/dn/UX83e/hyUTKnYrqu/r7DlKnJ4Ker5iFSaSLdK41/img.png?width=740&amp;amp;height=555&amp;amp;face=0_0_740_555');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;던전 앤 파이터&amp;rsquo; 세계관/스토리 총정리 1부 | 루리웹 게임 스토리 | 루리웹&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 연재 목차 ─────────────────────── ■ 던파 스토리 총정리 1부&amp;nbsp;-&amp;nbsp;현재 페이지&amp;nbsp;● - 던전 앤 파이터 : 세계관 ■ 던파 스토리 총정리 2부 - 아라드 역사 전반기 (아라&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;bbs.ruliweb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1705655684173&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/crkjPW/hyUXVuACIa/ITgXKeFkdUHms6xHnbQO61/img.png?width=1080&amp;amp;height=540&amp;amp;face=0_0_1080_540&quot; data-og-url=&quot;https://www.dnf-universe.com/&quot; data-og-source-url=&quot;https://www.dnf-universe.com/&quot; data-og-host=&quot;www.dnf-universe.com&quot; data-og-description=&quot;2,000여개의 던파 스토리, 아트웍, 멀티미디어 콘텐츠를 즐겨보세요.&quot; data-og-title=&quot;던파 IP 포털, DFU&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.dnf-universe.com/&quot; data-source-url=&quot;https://www.dnf-universe.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/crkjPW/hyUXVuACIa/ITgXKeFkdUHms6xHnbQO61/img.png?width=1080&amp;amp;height=540&amp;amp;face=0_0_1080_540');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;던파 IP 포털, DFU&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;2,000여개의 던파 스토리, 아트웍, 멀티미디어 콘텐츠를 즐겨보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;www.dnf-universe.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데이터 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 수집은 SFT훈련을 위한 데이터 수집을 진행하였고 수집 방법은 SFT만으로 만들어진 모델인&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/Beomi/KoAlpaca&quot;&gt;KoAlpaca&lt;/a&gt;의 데이터 수집 방법을 참고하여 진행하였습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1705655684174&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ejfuSU/hyUTA6Hple/f5TavZivJQwAxMVKJFRVf1/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_116_1040_164&quot; data-og-url=&quot;https://github.com/Beomi/KoAlpaca&quot; data-og-source-url=&quot;https://github.com/Beomi/KoAlpaca&quot; data-og-host=&quot;github.com&quot; data-og-description=&quot;KoAlpaca: 한국어 명령어를 이해하는 오픈소스 언어모델. Contribute to Beomi/KoAlpaca development by creating an account on GitHub.&quot; data-og-title=&quot;GitHub - Beomi/KoAlpaca: KoAlpaca: 한국어 명령어를 이해하는 오픈소스 언어모델&quot; data-og-type=&quot;object&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/Beomi/KoAlpaca&quot; data-source-url=&quot;https://github.com/Beomi/KoAlpaca&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ejfuSU/hyUTA6Hple/f5TavZivJQwAxMVKJFRVf1/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_116_1040_164');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Beomi/KoAlpaca: KoAlpaca: 한국어 명령어를 이해하는 오픈소스 언어모델&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;KoAlpaca: 한국어 명령어를 이해하는 오픈소스 언어모델. Contribute to Beomi/KoAlpaca development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수집한 텍스트 데이터를 통해서 질문-응답 형식의 데이터를 만들었고 이에 대한 작업을 ChatGPT - gpt3.5-turbo 모델을 활용하여 데이터를 만들었습니다. 해당 프롬프트 작업은 다음과 같은 내용으로 전달하였습니다. 추가적으로 4번만 작성했을 경우 본문에 있지 않은 내용을 생성하는 답변을 받아서 추가적으로 7번의 내용을 추가하여 진행하였습니다. 그런데 공식 홈페이지에서 게시된 내용 혹은 크롤링한 내용 중에서 인코딩 후 토큰이 gpt3.5-turbo에서 지원해주는 최대 토큰 수를 넘어가는 데이터들이 존재해서 이 경우 좀 더 많은 입력 토큰을 사용할 수 있는 gpt4-1106-preview 모델을 사용하여 답변을 생성하도록 처리 하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705655684176&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;system_prompt = '아래는 작업을 설명하는 명령어입니다. 명령어에 따른 요청을 적절히 완료하는 응답을 작성하세요. 추가적 입력이 있다면 작업을 설명하는 명령어와 추가 컨텍스트를 제공하는 입력에 따라 요청을 적절히 완료하는 응답을 작성하세요.'

prompt = &quot;&quot;&quot;다음 요구사항에 맞는 답변을 생성해주세요.
요구 사항은 다음과 같습니다:
1. 본문에 대한 내용을 요약하는 작업을 진행합니다.
2. 답변은 한국어로 작성해야 합니다.
3. 답변은 하나의 질문과 장문, 중문, 단문 응답으로 총 네 가지 모두 작성해야 합니다.
4. 장문, 중문, 단문은 같은 질문에 대한 답변으로 작성해야 합니다.
5. 장문은 5문장 이상으로 구성해야 합니다.
6. 중문은 3문장 이하로 구성해야 합니다.
7. 단문은 1문장으로 구성해야 합니다.
8. 장문, 중문, 단문은 내용에 겹치는 부분이 있어도 됩니다.


아래의 본문에 대한 내용을 바탕으로 답변을 작성해주세요.
질문과 응답은 아래와 같은 형식으로 입력에 알맞게 작성하세요.

질문과 각 응답 사이는 ###으로 구분해주세요.
질문 : ###
장문 : ###
중문 : ###
단문 :


본문 : 다음 내용은 {subject}에 관한 내용입니다.\n{content}

&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ChatGPT를 활용하여 만든 데이터는 아래와 같습니다. 해당 데이터는 SFT를 위한 데이터를 위한 준비이기도 했지만 RM에 대한 학습도 필요 했기 때문에 위와 같이 하나의 질문에 대한 답변을 3개로 받았고 RM모델 튜닝을 위한 rank는 장문이 80%, 중문 15%, 단문 5%의 비율로 설정하여 훈련 데이터를 만들 예정입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705655684177&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;**********************************************************************
IDX :  1
질문 : 바다와 전쟁의 흔적, 그리고 마계의 사도들에 대한 이야기를 요약 해주세요. ###
장문 : 소녀는 처음으로 넓은 바다를 보고 감탄하며 그 아름다움에 매료되었습니다. 그러나 그녀가 바라본 바다 주변의 섬은 과거 전쟁으로 황폐해진 곳이었습니다. 이곳에서 바다와 다른 풍경을 이루는 활화산이 있는 섬에 사도 안톤이 존재했다는 사실에 놀라움을 감추지 못했습니다. 소녀는 마계와 천계를 이어주는 통신 장치를 통해 연결된 리아 리히터와 통신을 하며, 자신이 마계에서 사라진 사도들을 찾는 데에 중요한 역할을 할 것이라는 기대감을 주고받습니다. 그러나 갑자기 차원의 틈이 닫혀 통신은 끊기고, 이제 그녀는 천계와 마계의 경계에서 불확실한 미래를 향해 자신의 역할을 다하기 위해 각오를 다집니다. ###
중문 : 바다를 처음 본 소녀는 과거 전쟁터였던 섬의 아름다움에 놀랍니다. 그리고 통신기를 통해 리아와 사도 안톤의 존재 및 상황을 나누며 정보를 주고받지만, 차원의 틈이 닫혀 다시 연락을 기다려야 하는 상황에 처하게 됩니다. ###
단문 : 바다에 감탄한 소녀가 마계의 사도들과 관련한 중요한 통신을 하다 차원의 틈이 닫혀 소통이 끊깁니다. ###

**********************************************************************
IDX :  2
질문 : '성채 에피소드 성채'의 내용을 요약해주세요. ###
장문 : '성채 에피소드 성채'에서 화자는 어둠 속에서 빛의 경계 너머로 신비한 힘에 이끌려 여행을 합니다. 이 여정에서 화자는 슬픔을 느끼며, 인간을 넘어서 신성한 존재로 거듭난 경험을 합니다. 화자는 성채의 감시자인 호그니와 불꽃과 같은 헤딘을 만나 성채를 지키는 사명에 대해 이야기하고 있습니다. 마지막으로, 꿈에서 깨어난 그란디스는 현실에서 호그니로부터 성채의 등장을 알리는 소식을 듣게 됩니다. 그리하여 그란디스에게 싸움에 임하라는 메시지가 전달됩니다. ###
중문 : 화자는 빛의 경계를 넘어 성채의 감시자 호그니와 뜨거운 헤딘을 만나고, 그들에게 성채를 지키라는 계시를 전합니다. 잠에서 깬 그란디스는 현실 속에서 성채의 등장과 싸움을 준비하라는 메시지를 받습니다. ###
단문 : 화자는 신비한 여정을 통해 성채를 지키는 전사들과 만나며 마지막에는 현실에서 성채의 등장을 알게 됩니다. ###

**********************************************************************
IDX :  3
질문 : '사도'가 무엇이며, 마계에서의 그들의 입지는 어떠한가요? ###
장문 : '사도'는 다양한 세계에서 온 이계의 존재들로, 마계에서 가장 강력한 힘을 가진 자들입니다. 이들은 각자의 세계에서 올라와 마계를 떠돌며, 그들의 거대한 힘은 이들을 접하는 생명체들에게 압도적인 공포를 안겨줍니다. 실제로 일부 '사도'의 존재만으로도 마계인들의 생명을 위협할 정도이며, 이로 인해 많은 마계인들이 '사도'가 거주하는 지역을 피해 숨어 다니고 있는 실정입니다. 이러한 상황은 '사도'들이 마계에서 어떠한 강력한 위치에 있다는 것을 반증하는 예입니다. ###
중문 : '사도'는 이계에서 온 강력한 존재들을 일컫으며, 그들의 힘 앞에 마계인들은 큰 공포를 느낍니다. 이들은 자신의 힘으로 마계인의 생명을 위협할 정도로 막강하며, 마계인들은 이들을 피해 살고 있습니다. ###
단문 : '사도'는 이계의 강력한 존재들로, 그들의 위압적인 힘 앞에 마계인들은 공포와 위협을 느낍니다. ###


**********************************************************************
IDX :  4
질문 : '사도 서로의 관계'에 대해 설명해주세요. ###
장문 : '사도 서로의 관계'는 아주 독특한 것으로 표현됩니다. 사도들은 각기 다른 세계에서 온 인물들로, 우연한 만남을 가졌음에도 불구하고 서로 간에는 설명할 수 없는 특별한 기운이 존재한다고 합니다. 이 기운은 모든 사도가 공유하며 서로에게 살상 의도를 가질 수 없게 만듭니다. 예를 들어, 용의 전쟁에서 카인이 다른 사도인 바칼을 죽이지 못한 이유도 이 특별한 기운 때문이었다고 전해집니다. 사도들의 이러한 독특한 관계는 그들의 행동에 큰 영향을 미치고 있습니다. ###
중문 : 사도들은 서로 다른 세계 출신임에도 불구하고 공통된 기운을 공유하여 서로를 직접 죽일 수 없습니다. 이 기운은 용의 전쟁에서 카인이 바칼을 죽이지 못한 원인이기도 합니다. ###
단문 : 사도들은 특별한 기운을 통해 서로를 죽일 수 없는 독특한 관계를 가지고 있습니다. ###



**********************************************************************
IDX :  5
질문 : '사도 사도의 전이'에 대해 설명해주실 수 있나요?
###
장문 : '사도 사도의 전이'는 일부 사도가 아라드 대륙으로 전이되는 현상을 말합니다. 전이된 사도들은 자신의 힘을 온전히 발휘할 수 없는 지역에 떨어지게 되는데, 이는 특정한 공통점으로 보입니다. 그 결과, 사도들은 그 지역에 재앙 같은 피해를 주기 시작하지만, 결국 저항을 만나 죽게 됩니다. 이 과정에서 사도가 가져오는 파괴와 그 종말에 대한 이야기가 '사도 사도의 전이' 안에서 다뤄집니다.
###
중문 : 사도가 아라드 대륙으로 전이되는 현상을 '사도 사도의 전이'라 합니다. 이들은 자신의 힘을 제대로 사용하지 못하는 지역에 도착하여 큰 피해를 주다가 저항에 의해 죽게 됩니다.
###
단문 : '사도 사도의 전이'는 사도들이 아라드 대륙에 전이되어 재앙을 일으키다 결국 죽음을 맞는 현상입니다.


**********************************************************************
IDX :  6
질문 : '사도 문양 트리비아'에 대해 설명해주세요.
### 
장문 : '사도 문양 트리비아'는 각 사도들의 문양에 대한 유래를 다루고 있습니다. 제 1사도 카인의 문양은 그를 두려워하고 경외하는 자들에 의해 만들어졌으며, 제 2사도 힐더는 자신의 우는 눈을 문양으로 형상화했습니다. 제 3사도 이시스-프레이의 문양은 추종자들이 만든 후 밤의 마천루 전투를 기념하여 상징이 추가되었습니다. 추종자들은 제 4사도 카시야스의 무력을 문양으로, 제 5사도 시로코의 무형의 모습을 문양으로 형상화했습니다. 각각의 사도들은 추종자들, 경외하는 사람들, 또는 특정 공동체에 의해 다양한 방식으로 그들의 모습이나 상징을 문양으로 변환하여 사용되게 되었으며, 이는 경고, 존중, 신성함 등의 다양한 의미를 내포하고 있습니다.
### 
중문 : 총 11명의 사도 각각의 문양은 해당 사도를 두려워하거나 존경하는 자들에 의해 만들어졌습니다. 예를 들어 카인 문양은 경외감을 표현하기 위해, 힐더의 문양은 그녀의 우는 눈을 형상화했습니다. 이러한 문양들은 종교적, 경고적, 심미적 의미를 갖고 문화 속에 흔적을 남겼습니다.
### 
단문 : '사도 문양 트리비아'는 각 사도의 창시자 또는 추종자들에 의해 만들어진 문양들의 배경과 의미를 설명합니다.

**********************************************************************
IDX :  7
질문 : '숙명의 카인'에 관해 설명해주세요. ###
장문 : '숙명의 카인'은 마계에서 최고의 강자로 칭해지는 인물로, 마계 브루클린 동부의 유니언 스퀘어에 거주합니다. 그에 대한 정보는 많지 않으나, 그의 육체는 어떠한 물질이나 속임수로도 파괴할 수 없다고 알려져 있습니다. 또한, 그의 엄청난 힘은 그에게 도전하는 모든 것을 파괴할 수 있는 것으로 전해집니다. 가장 높은 서열의 사도임에도 불구하고 자신의 영역 외에는 세력 확장에 관심이 없는 것으로 보입니다. ###
중문 : '숙명의 카인'은 마계 최강의 왕으로 알려져 있으며, 그의 육체는 불파손성을 가졌고 모든 것을 파괴하는 힘을 지녔습니다. 그는 마계 브루클린의 유니언 스퀘어에 살고 있으나, 추가적인 세력 확장에는 관심이 없어 보입니다. ###
단문 : 마계에서 강한 힘을 지닌 '숙명의 카인'은 유니언 스퀘어에 거주하며 세력 확장에 무관심합니다.

**********************************************************************
IDX :  8
질문 : '숙명의 카인 전투 행적'에 대해 간략하게 설명해주세요. ###
장문 : '숙명의 카인 전투 행적' 이야기는 카인과 이시스 - 프레이 간의 명운을 건 전투에 관한 것입니다. 이시스 - 프레이는 카인을 재앙의 원인으로 여겨 대치한 상태에서 질문을 던졌으나, 대답 대신 공격을 받았습니다. 이후 두 존재는 수일간 치열한 전투를 벌이며 서로 다치지 않는 끝없는 대결을 펼쳤습니다. 마계가 테이베르스에서 물러난 다섯째 날, 힐더의 중재로 둘의 싸움은 무승부로 마무리됩니다. ###
중문 : 이시스 - 프레이와 카인 사이에 오랜 기간의 혈투가 벌어진 '숙명의 카인 전투 행적'에서, 서로를 이기지 못하며 싸움은 장기화됩니다. 최종적으로 힐더의 중재로 두 전사의 싸움은 무승부로 끝이 납니다. ###
단문 : '숙명의 카인 전투 행적'에서 이시스 - 프레이와 카인의 장기 전투는 무승부로 종결됩니다. ###

**********************************************************************
IDX :  9
질문 : '숙명의 카인 정복자 카시야스'에 대해 설명해주세요. ###
장문 : '숙명의 카인 정복자 카시야스' 이야기에서 카시야스는 타의 추종을 불허하는 강함을 지향하며 항상 더 강한 존재를 찾아 결투를 신청합니다. 이러한 그의 성향은 다른 사도들에게도 나타나지만, 카인과의 결투에서는 그의 모든 노력과 기술이 무색해질 만큼 카인의 힘이 압도적입니다. 결국 카시야스는 카인에게 참패하며, 그 이후로는 카인을 두려워하게 되죠. 이러한 경험을 통해 카시야스는 자신의 한계를 인식하고 새로운 겸손함을 배우게 됩니다. ###
중문 : '숙명의 카인 정복자 카시야스'에서 주인공 카시야스는 강함에 대한 집념으로 카인에게 결투를 신청합니다. 그러나 카시야스의 모든 기술과 경험은 카인의 월등한 힘 앞에서 무너지고, 치명적인 패배를 맛보게 됩니다. 결국 그는 카인을 정복하려던 야심을 접고 카인을 두려워하게 됩니다. ###
단문 : 카시야스는 강함을 추구하다 카인에게 결투를 신청했으나 완패하여 그를 두려워하게 됩니다.

**********************************************************************
IDX :  10
질문 : '숙명의 카인 폭룡왕 바칼'에 대해 설명해주세요. ###
장문 : '숙명의 카인 폭룡왕 바칼'은 마계로 오른 바칼이 죽음에 대한 두려움을 처음 경험하고, 카인과의 상상 속 대결에서 늘 패배하며 자신이 패배하는 모습을 반복해서 그렸습니다. 시간이 흘러 용의 전쟁을 일으킨 바칼은 전쟁 끝에 사도들에게 둘러싸였을 때 자신이 자신을 죽음에 이르게 하지 않을 것을 깨닫고, 카인에게 돌진합니다. 카인도 바칼을 처치하려 했으나 멈칫하고 피해, 결과적으로 바칼에게 도망칠 기회를 제공하게 되었습니다. ###
중문 : 마계의 폭룡왕 바칼은 카인과의 대결을 상상하며 자신의 죽음에 대한 공포를 느낍니다. 전쟁 끝에 사도들에게 포위당한 후, 자신의 운명을 깨닫고 자살하지 않기로 하면서 도망칩니다. 카인은 그를 처치하려다 망설이며 바칼에게 탈출 기회를 주었습니다. ###
단문 : 마계의 바칼은 카인과의 대결을 상상하다 사도들에 포위된 후 도망치는 결심을 하고, 카인의 망설임으로 탈출에 성공합니다.

**********************************************************************
IDX :  11
질문 : '우는 눈의 힐더'는 누구이며 그녀의 역할은 무엇인가요?
###장문 : '우는 눈의 힐더'는 과거 테라로 불리던 시절부터 살아온 인물로, 마법의 최초 발견자로 알려져 있습니다. 그녀는 세계의 멸망과 가족의 죽음을 기억하면서 모든 것을 복구하기 위해 오랜 시간을 투자해왔습니다. 현재는 마계의 중요한 사건을 관장하며, 정신감응 능력을 통해 사도들을 조율하고 분쟁을 중재하는 역할을 하고 있습니다. 전투력이 약함에도 불구하고 두 번째 서열로 인정받는 이유는 카시야스와 루크와 같은 귀족들과의 긴밀한 관계 때문입니다. 그녀는 마계 브루클린 남부의 블루밍데일에 살고 있으며, 자주 모습을 드러내지 않습니다.
###중문 : '우는 눈의 힐더'는 자연의 힘을 이용한 마법을 처음 발견한 마계의 중요 인물입니다. 그녀는 사도들을 조율하고 분쟁을 중재하는 역할을 하며, 마계의 브루클린 지역 블루밍데일에 거주합니다.
###단문 : '우는 눈의 힐더'는 마법을 최초로 발견한 마계의 조율자로 분쟁을 중재하고 사도들을 결속시키는 역할을 합니다.

**********************************************************************
IDX :  12
질문 : '우는 눈의 힐더 카인과 만남'에 관한 내용을 요약해주세요. ###
장문 : 테라가 멸망하면서 우주 방사선에 큰 피해를 입은 마계인들은, 힐더가 마계를 이차원으로 이동시켜 현실로부터 분리함으로써 방사선 피해로부터 보호받게 됩니다. 그러나, 이차원에서 무제한으로 유영하게 된 마계는 다른 행성과 결합하면서 거인과 괴수들이 침공해 마계를 파괴하고 마계인들에게 피해를 주었습니다. 이에 카인이 나타나 이 괴물들을 처치하고 힐더는 마계를 지키기 위해 그에게 제안을 하여 함께하게 됩니다. 이 사건이 그들의 만남의 시작이었습니다. ###
중문 : 마계가 우주 방사선의 피해로부터 이차원으로 이동된 후, 다른 행성의 거인과 괴수들이 침입해 피해를 입히자, 카인이 나타나 그들을 처치한다. 이를 계기로 힐더는 카인에게 마계를 지킬 제안을 하여 그와 만나게 된다. ###
단문 : 힐더가 마계를 이차원으로 옮겨 방사선으로부터 보호한 후, 카인이 거인과 괴수의 침략을 막아내어 만나게 됩니다.

**********************************************************************
IDX :  13
질문 : '우는 눈의 힐더 사도의 탄생'에 대해 설명해주세요. ###
장문 : '우는 눈의 힐더 사도의 탄생'은 마계가 계속하여 타 행성들과 접촉하며 다른 이계의 존재들의 침입을 받는 상황을 배경으로 하고 있습니다. 대부분의 침입자들은 카인에 의해 격퇴되지만 일부는 그의 감시를 피해 몰래 숨어들게 됩니다. 이 중 강력한 존재들이 마계에 등장하면 힐더는 그들을 모아 자신을 포함한 구원자 '사도'로 칭하기까지 합니다. 이들 중 힐더만이 마계의 재건에 목마르고, 다른 사도들은 이에 무관심한 상태입니다. ###
중문 : 마계는 타 행성과 접촉하며 이계의 존재들에게 침입당하고, 카인에 의해 대부분 격퇴되지만 몇몇은 숨어든다. 힐더는 강력한 존재들을 '사도'로 모으나, 그들 대부분은 마계의 재건에 관심이 없다. ###
단문 : 마계가 침입자들에게 시달리고 힐더가 다른 강자들을 모아 '사도'를 창설하나 재건에 무관심한 사도들이 존재한다. ###

**********************************************************************
IDX :  14
질문 : '우는 눈의 힐더 창신세기의 발견'에 대해 설명해주세요. ###
장문 : '우는 눈의 힐더 창신세기의 발견'은 고대 마계의 숨겨진 공간에서 힐더가 발견한 네 개의 비석에 관한 이야기입니다. 그 중 두 개는 심하게 파괴되어 형체를 알아보기 힘들었지만, 나머지 두 개의 비석인 창신세기는 온전했습니다. 이 예언서에 매료된 힐더는 밤낮으로 이를 연구하며, 이것이 마계의 비극을 해결할 열쇠라고 믿고 있습니다. 이 이야기는 이현진 성우가 목소리로 녹음한 힐더에 관한 서사 중 하나입니다. ###
중문 : '우는 눈의 힐더 창신세기의 발견'은 힐더가 마계에서 찾은 네 개의 비석 중 온전한 두 개의 창신세기를 연구하며 비극의 해답을 찾으려는 내용을 담고 있습니다. 이 이야기는 성우 이현진이 녹음했습니다. ###
단문 : 고대 마계 비석 '창신세기'를 연구하는 힐더의 이야기로, 비극의 해결을 추구하며 이현진 성우가 목소리를 맡았습니다.

**********************************************************************
IDX :  15
질문 : '창공의 이시스 - 프레이'에 대해 설명해주실 수 있나요? ###
장문 : '창공의 이시스 - 프레이'는 테이베르스 행성 출신으로 고향에서 가장 뛰어난 능력을 지녔던 인물입니다. 그는 높이 날고 멀리 볼 수 있는 능력을 가지고 있었으며, 위대한 전사로서 많은 존경을 받았습니다. 어느 날 테이베르스에 검은 비가 내리며 평화가 깨지자, 그 원인을 찾아 창공으로 날아올랐고 마계와의 연결을 발견했습니다. 카인과의 장기전으로 프레이는 다시는 고향에 돌아갈 수 없게 되었으나, 힐더의 조언에 따라 마계에 머물며 테이베르스의 결착을 기다리고 있습니다. ###
중문 : '창공의 이시스 - 프레이'는 자신의 행성이 위험에 처했을 때 마계와 싸움을 벌이고 마계에 머무는 존재가 되었습니다. 왕성한 능력으로 존경받던 그는 테이베르스의 평화를 되찾기 위해 자신을 희생했고, 현재는 고향의 결착을 기다리는 중입니다. ###
단문 : '창공의 이시스 - 프레이'는 고향 테이베르스를 위해 마계에서 싸우고 대기하고 있는 전사입니다.

**********************************************************************
IDX :  16
질문 : '정복자 카시야스'는 누구인가요? ###
장문 : '정복자 카시야스'는 호전적인 귀면족의 일원으로 마계의 에컨에서 태어났으며, 강한 기운을 찾아 전투를 치르는 존재입니다. 이 종족은 평범한 인간보다 약 1.5배 큰 체구를 가지고 있으며, 두 자루의 검을 사용하여 싸웁니다. 카시야스는 사도 중에서도 최고의 전투 실력을 자랑하지만, 제 1사도인 숙명의 카인에게는 패하여 더 강해지기 위해 마계 소환사와 계약을 맺고 아라드 대륙으로 건너옵니다. 새로운 세계에서 새로운 적들과의 대결을 통해 자신을 강화하며, 항상 새로운 도전을 기대하는 낙천주의자입니다. 성우 안장혁이 이 캐릭터의 목소리를 담당했습니다. ###
중문 : '정복자 카시야스'는 마계에 살던 귀면족으로 강함을 추구하여 아라드로 온 전사입니다. 최강이라는 명성에 걸맞게 다양한 전투 경험을 쌓으나 1사도에게 패배하고, 이를 계기로 끝없이 강해지고자 합니다. 그는 또한 끊임없는 새로운 도전을 좋아하는 낙천적 기질을 가지고 있습니다. ###
단문 : '정복자 카시야스'는 호전적인 귀면족으로 강함을 추구해 아라드 대륙에서 성장을 거듭하는 전사입니다. ###

**********************************************************************
IDX :  17
질문 : '무형의 시로코'에 대해 설명해주세요. ###
장문 : '무형의 시로코'는 변이 세계 주알라바돈 출신의 사도로, 다양한 물질로 변신할 능력이 있습니다. 그러나 그의 능력은 따뜻한 온도에서만 제대로 발휘되며, 이 약점으로 인해 마계의 메트로센터에 거주했습니다. 그곳에서는 제 7사도 안톤과 에너지를 두고 지속적으로 대립했습니다. 사도 중 최초로 전이를 겪은 시로코는 힐더의 계획을 읽어내기도 했으며, 이는 그에게 중대한 변화를 가져왔음을 시사합니다. ###
중문 : '무형의 시로코'는 변이가 가능한 사도로, 온도에 의해 능력이 제한되며, 메트로센터에서 안톤과 권력 투쟁을 벌였습니다. 사도 중 최초로 전이 피해를 입었으며, 힐더의 계획을 알아차렸습니다. ###
단문 : '무형의 시로코'는 변이 능력을 가진 사도로서, 온도에 영향을 받으며 안톤과 대립하고 힐더의 계획을 간파했습니다. ###

**********************************************************************
IDX :  18
질문 : '무형의 시로코 전이'에 대해 설명해주세요.
###
장문 : '무형의 시로코 전이'는 벨 마이어 공국 북부의 비명굴로의 전이 사건을 말합니다. 이 사건은 발생 후 3년이 지나고 비명굴에서 큰 사건의 주범으로 알려지게 되는데요. 결국 시로코는 아간조, 반 발슈테트, 시란, 브왕가 등 여러 지역에서 온 강력한 인물들에게 패하여 죽음을 맞이합니다. 이로 인해 비명굴 사건은 일단락되면서 시로코는 역사 속의 인물로 기억되게 됩니다.
###
중문 : 벨 마이어 공국 북부 비명굴로 전이된 '무형의 시로코'는 3년 후 주범이 되었으나 아간조와 반 발슈테트 등 강자들에 의해 패배하며 죽는다. 이 사건은 비명굴 사건으로 알려져 있다.
###
단문 : '무형의 시로코'는 비명굴 전이 후 3년 만에 강자들에게 패배해 사망합니다.


**********************************************************************
IDX :  19
질문 : '무형의 시로코' 내용의 요약은 무엇인가요? ###
장문 : 이 작품은 절망 속에서도 생존을 위해 분투하는 주인공의 내면적 갈등을 그리고 있습니다. 주인공은 심각한 갈증과 배고픔에 시달리며, 견디기 힘든 상황 속에서 고통과 환영에 시달립니다. 그럼에도 불구하고 살고자 하는 의지는 강렬하며, 자신이 처한 환경과 운명에 저항하면서 복수와 부활의 꿈을 꾸고 있습니다. 결국, 넘을 수 없는 난관에도 불구하고 거대한 폭풍과 같은 힘을 발휘하여 살아남겠다는 각오를 다지면서, 그 어려움을 여흥으로 여기는 태도를 보입니다. ###
중문 : '무형의 시로코'는 주인공의 생존 의지와 내면의 고투를 다룬 내용입니다. 주인공은 마지막까지 살기 위해 고통과 환영에 저항하며 부활을 꿈꾸며 극복하고자 합니다. 힘든 상황 속에서도 슬픈 미소를 지으며 삶을 긍정하려는 모습이 인상적입니다. ###
단문 : 주인공은 절망적인 상황에서도 살고자 하는 의지를 불태우며 부활을 꿈꿉니다.

**********************************************************************
IDX :  20
질문 : '검은 질병의 디레지에'는 어떤 존재인가요? ###
장문 : '검은 질병의 디레지에'는 치명적인 병원균들로 이루어진 위험한 생물로서 모든 질병의 원인으로 알려져 있습니다. 이 검은 야수는 생명체를 말 그대로 먼지로 변하게 만드는 능력을 지닌 것으로 악명이 높으며, 마계인들 사이에서도 흑사병과 인플루엔자 같은 질병을 유발하고 변형과 액화 현상을 일으킵니다. 그럼에도 불구하고 다른 사도들에게 밀려, 브롱크스 북부의 변두리에 살게 되었으며, 이제 다른 사도들과의 싸움을 기피하는 상태입니다. ###
중문 : '검은 질병의 디레지에'는 치명적 병원균으로 구성된 생명체로, 질병을 유발하며 마계에서 소외되어 브롱크스 북부 변두리에서 살면서 다른 사도들과의 싸움을 피하고 있습니다. ###
단문 : '검은 질병의 디레지에'는 병원균 생물로, 질병을 일으키고 브롱크스 변두리에서 다른 사도들과 싸우기를 기피합니다. ###&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;현재 데이터를 만들고 있는 단계이기 때문에 수집이 완료되면 데이터를 통해서 SFT와 RM 모델의 훈련을 진행하고 PPO알고리즘을 적용하여 던전앤파이터에 관련된 이야기를 잘 대답할 수 있는 모델을 만들어 보도록 하겠습니다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>머신러닝&amp;amp;딥러닝/RF(강화학습)</category>
      <author>코코로코코</author>
      <guid isPermaLink="true">https://coco0414.tistory.com/109</guid>
      <comments>https://coco0414.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 19 Jan 2024 19:22:01 +0900</pubDate>
    </item>
  </channel>
</rss>