LLM을 위한 제품 설명 데이터 세부 조정 기술
📌 실습 목표
- Hugging Face의 Amazon 리뷰 데이터셋을 활용하여 제품 설명 기반 가격 예측 모델을 만들기
- RAG/LLM 기반 모델 훈련을 위한 데이터 전처리 및 프롬프트 구성 실습
🧱 1. 실습 환경 설정
🧪 필요한 라이브러리 임포트
# 📦 기본 라이브러리 임포트
import os # 🧭 환경 변수(.env) 경로 및 파일 관리를 위한 표준 라이브러리
from dotenv import load_dotenv # 🔐 .env 파일에 저장된 API 키 등의 환경 변수 로딩용
from huggingface_hub import login # 🤝 Hugging Face Hub에 로그인하여 인증 토큰 사용 가능하게 함
from datasets import load_dataset, Dataset, DatasetDict
# 📊 Hugging Face Datasets 라이브러리
# - load_dataset: 데이터셋을 불러올 때 사용 (ex: Amazon 리뷰 데이터)
# - Dataset, DatasetDict: 데이터셋의 타입 정의 (단일/다중 데이터셋 처리에 사용)
import matplotlib.pyplot as plt # 📈 데이터 분포 시각화를 위한 Matplotlib의 핵심 모듈
🔐 환경 변수 설정 및 Hugging Face 로그인
# 🌍 환경 변수 설정
load_dotenv(override=True)
# 🔐 .env 파일에 저장된 환경 변수(.env 파일 내 API 키 등)를 로딩합니다
# - override=True 옵션은 이미 설정된 환경 변수라도 .env의 값으로 덮어쓰기 허용
# 🤖 OpenAI API 키 설정
os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', 'your-key-if-not-using-env')
# - .env에서 'OPENAI_API_KEY' 값을 가져와 환경 변수에 설정
# - .env 파일이 없다면 기본값 'your-key-if-not-using-env' 사용
# 🤝 Anthropic API 키 설정
os.environ['ANTHROPIC_API_KEY'] = os.getenv('ANTHROPIC_API_KEY', 'your-key-if-not-using-env')
# - 동일 방식으로 Anthropic API 키를 설정
# 🤗 Hugging Face API 키 설정
os.environ['HF_TOKEN'] = os.getenv('HF_TOKEN', 'your-key-if-not-using-env')
# - Hugging Face Hub에 로그인할 때 사용할 토큰 설정
💡 .env
파일에는 아래처럼 API 키를 저장해 두면 됩니다:
OPENAI_API_KEY=sk-xxx...
ANTHROPIC_API_KEY=claude-xxx...
HF_TOKEN=hf_yyy...
# 🔐 Hugging Face Hub 로그인
hf_token = os.environ['HF_TOKEN']
# ✅ 환경 변수에서 Hugging Face 토큰 값을 불러옵니다
# - .env 파일 또는 코드에서 미리 설정된 'HF_TOKEN'을 사용
login(hf_token, add_to_git_credential=True)
# 🤗 Hugging Face에 로그인합니다
# - 토큰을 사용해 인증 처리
# - add_to_git_credential=True 옵션은 Git credential helper에 저장해 CLI에서도 인증 유지 가능하게 함
💡 참고:
- 이 로그인은
datasets
또는transformers
라이브러리를 통해 Hugging Face의 리소스에 접근할 때 필요해요. - 토큰은 Hugging Face 계정 → Access Tokens 메뉴에서 발급 가능해요.
from items import Item
# 📦 items 모듈에서 Item 클래스 불러오기
# - 이 클래스는 제품의 정보를 구조화된 형태로 다루기 위해 사용됨
# - 예: 제목(title), 설명(description), 가격(price) 등을 속성으로 가질 수 있음
# - 데이터셋을 다루는 과정에서 각 제품을 하나의 객체(Item)로 만들어 처리하면 더 편리하고 명확함
%matplotlib inline
# 📊 주피터 노트북에서 그래프를 셀 아래에 바로 출력하도록 설정하는 매직 명령어
# - matplotlib을 사용할 때 필수적인 설정
# - 별도로 plt.show()를 쓰지 않아도 그래프가 자동으로 노트북 안에 표시됨
# - 노트북 외부 환경(예: 스크립트 파일)에서는 필요 없음
💡 참고:
- 이건 Python 문법이 아니라 IPython의 매직 명령어예요.
- 주피터 노트북에서만 작동합니다!
📦 2. Amazon 리뷰 데이터셋 로딩
📥 데이터셋 불러오기 (가전제품 카테고리)
# 📥 데이터셋 로딩: Amazon 가전제품 리뷰 데이터
dataset = load_dataset(
"McAuley-Lab/Amazon-Reviews-2023", # 📦 Hugging Face에 등록된 Amazon 리뷰 데이터셋 이름
"raw_meta_Appliances", # 🔧 사용할 세부 구성(subset): 'Appliances' 카테고리 (가전제품)
split="full", # 📊 전체 데이터를 불러옴 ('train'이나 'test'가 아닌 전체)
trust_remote_code=True # ✅ 데이터셋 정의에 사용자 정의 코드가 포함되어 있어도 허용함
)
# 🔎 이 데이터셋에는 제품 이름, 설명, 기능, 가격 등 다양한 메타데이터가 포함되어 있음
# - 제품 가격 추정 모델 학습을 위한 핵심 데이터 소스
💡 이 데이터셋은 Hugging Face Datasets 라이브러리를 통해 로드되고, Pandas처럼 .filter()
, .map()
, .to\_pandas()
같은 편리한 메서드들을 사용할 수 있어요.
🔍 3. 데이터 탐색 및 시각화
🧮 전체 가전제품 수 확인
print(f"Number of Appliances: {len(dataset):,}")
# 🧮 데이터셋의 전체 항목(가전제품)의 개수를 쉼표(,) 포함 형식으로 출력
# - len(dataset): 전체 데이터 포인트(예: 제품 리뷰)의 개수를 반환
# - f-string의 `:,` 포맷: 1,000 단위로 쉼표를 자동으로 추가 (예: 94000 → 94,000)
# - 결과 예: "Number of Appliances: 94,328"
💡 이 표현은 데이터셋 규모를 사용자에게 직관적으로 보여줄 때 매우 유용해요.
예를 들어, 학습 전에 데이터 수가 너무 많거나 너무 적은 지 빠르게 파악할 수 있습니다.
🧾 특정 제품 확인
# 🔎 특정 데이터 포인트 확인
datapoint = dataset[2]
# - 전체 데이터셋 중 세 번째 항목을 선택 (Python은 0부터 시작하므로 인덱스 2는 3번째 데이터)
# - datapoint는 딕셔너리 형태로, 하나의 제품 리뷰 정보를 담고 있음
# 예: {'title': ..., 'description': ..., 'price': ..., 'feature': ..., ...}
# - 이 데이터를 출력하거나, 개별 필드(title, price 등)를 꺼내 확인할 수 있음
💡 이어서 다음과 같이 확인할 수 있어요:
# 🔍 선택한 제품 데이터의 주요 정보 출력
print(datapoint["title"])
# 🏷️ 제품 제목 (예: "Countertop Ice Maker")
print(datapoint["description"])
# 📝 제품 설명 (간단한 요약 또는 광고 문구 등)
print(datapoint["features"])
# 📋 제품의 주요 기능 목록
# - 일반적으로 리스트 형태의 문장들이 포함됨
# - 예: ["Makes 26 lbs of ice per day", "Portable design", ...]
print(datapoint["details"])
# ⚙️ 상세 정보 (일반적으로 문자열로 저장된 JSON 형식)
# - 사전(dict)처럼 보이지만 실제로는 문자열
# - json.loads()로 파싱해서 딕셔너리로 변환 가능
print(datapoint["price"])
# 💰 제품 가격
# - 숫자(float 또는 int) 형식
# - 없으면 None 또는 0일 수 있음
💡 참고 팁:
details
는 종종"{"brand": "LG", "color": "silver"}
같은 문자열 형태예요 →json.loads()
로 변환해서 활용 가능!- 일부 항목은
None
이거나 누락될 수 있으므로 출력 전에 확인하는 것도 좋아요.
💰 유효한 가격을 가진 제품 개수 확인
# 💰 가격 정보가 있는 항목 수 세기
prices = 0 # 가격이 있는 제품 개수를 세기 위한 카운터 초기화
# 전체 데이터셋을 순회하며 각 데이터 포인트의 가격 필드를 검사
for datapoint in dataset:
try:
price = float(datapoint["price"]) # 가격을 실수(float)로 변환 시도
if price > 0: # 가격이 0보다 크면 유효한 가격으로 간주
prices += 1 # 유효한 가격인 경우 카운터 증가
except ValueError as e:
pass # 가격이 숫자가 아니거나 변환 실패 시 무시하고 다음 데이터로 넘어감
# 🎯 결과 출력: 가격이 있는 항목 수와 전체 대비 백분율
print(f"There are {prices:,} with prices which is {prices/len(dataset)*100:,.1f}%")
# - {prices:,} : 쉼표(,)가 포함된 가격 개수 출력 (예: 12,345)
# - {prices/len(dataset)*100:,.1f}% : 전체 중 가격이 있는 비율을 소수점 1자리까지 출력 (예: 45.7%)
💡 이 코드는 데이터셋 중 유효한 가격이 있는 제품 비율을 빠르게 파악할 때 매우 유용해요!
📊 텍스트 길이 및 가격 수집
# 💰📝 가격이 있는 항목에 대해 가격과 설명 길이 정보 수집
prices = [] # 유효한 가격들을 저장할 리스트
lengths = [] # 각 제품 설명의 총 텍스트 길이를 저장할 리스트
# 전체 데이터셋을 순회하며 가격이 있는 항목만 추출
for datapoint in dataset:
try:
price = float(datapoint["price"]) # 가격을 실수로 변환
if price > 0: # 0보다 큰 가격만 유효한 것으로 판단
prices.append(price) # ✅ 유효한 가격 저장
# 📄 텍스트 결합: title, description, features, details를 하나의 문자열로 연결
contents = (
datapoint["title"] +
str(datapoint["description"]) +
str(datapoint["features"]) +
str(datapoint["details"])
)
lengths.append(len(contents)) # 🧮 해당 제품 설명의 총 텍스트 길이 저장 (문자 수 기준)
except ValueError as e:
pass # 가격이 숫자가 아닌 경우 (예: None, 문자열 등) 무시하고 넘어감
💡 이 정보를 기반으로 나중에 matplotlib
으로 히스토그램을 그리거나, 텍스트 길이에 따른 가격 분포 분석을 할 수 있어요.
🖼️ 텍스트 길이 히스토그램
# 📊 제품 설명 길이 분포 시각화
plt.figure(figsize=(15, 6))
# - 출력할 그래프의 크기를 지정 (가로 15인치, 세로 6인치)
# - 텍스트가 많고 꼬리가 긴 분포를 보기 좋게 넓게 펼쳐 보여줌
plt.title(f"설명 길이 분포: 평균 {sum(lengths)/len(lengths):,.0f}자, 최대 {max(lengths):,}자\n")
# - 그래프 제목 표시
# - 평균 길이와 최대 길이를 함께 출력 (쉼표로 1,000 단위 구분)
# - 예: "Lengths: Avg 423 and highest 5,489"
plt.xlabel('설명 길이 (문자 수)') # X축 레이블: 텍스트 길이 (문자 수)
plt.ylabel('제품 수') # Y축 레이블: 각 길이에 해당하는 데이터 수
plt.hist(
lengths, # 🔢 분석 대상 데이터: 텍스트 길이 리스트
rwidth=0.7, # 막대 너비 비율 (1보다 작으면 막대 간격 생김)
color="lightblue", # 막대 색상
bins=range(0, 6000, 100) # 0부터 6000까지 100단위로 bin(구간) 나누기
)
plt.show() # 📈 실제 그래프 출력
💡 이 히스토그램은 설명의 길이가 얼마나 다양한지, 대부분이 어느 구간에 몰려 있는지를 직관적으로 보여줍니다.
💵 가격 분포 히스토그램
# 💰 제품 가격 분포 시각화 (히스토그램)
plt.figure(figsize=(15, 6))
# - 출력할 그래프의 가로/세로 크기 설정
# - 넓은 그래프를 통해 가격 범위가 넓은 경우도 보기 쉽게 표시
plt.title(f"제품 가격 분포: 평균 {sum(prices)/len(prices):,.2f} 이고 최대 {max(prices):,}\n")
# - 그래프 제목에 평균 가격과 최고 가격 표시
# - 평균은 소수점 둘째자리까지, 1,000 단위 쉼표 포함
# - 예: "Prices: Avg 142.58 and highest 21,000"
plt.xlabel('가격 ($)') # X축: 가격 (달러 단위)
plt.ylabel('제품 수') # Y축: 해당 가격대에 속한 제품 개수
plt.hist(
prices, # 📊 분석할 가격 데이터 리스트
rwidth=0.7, # 막대 너비 비율 (0.7로 설정해 막대 간격 생성)
color="orange", # 막대 색상 설정 (가시성 높임)
bins=range(0, 1000, 10) # 0부터 1,000까지 10달러 단위로 구간(bins) 나눔
)
plt.show() # 📈 그래프를 화면에 출력
💡 참고 팁:
- $1,000 이상 제품은 이 범위에서 보이지 않을 수 있어요. 그런 경우엔
bins=range(0, 5000, 100)
같은 식으로 범위를 늘릴 수 있어요. - 분포의 왜도(skewness) 확인 → 평균보다 고가 제품이 소수 존재해 평균을 끌어올릴 수 있음!
🔍 21,000달러 이상인 초고가 제품 찾기
for datapoint in dataset: # 전체 데이터셋을 하나씩 순회
try:
price = float(datapoint["price"]) # 가격을 실수(float)로 변환
if price > 21000: # 💸 21,000달러 초과하는 경우에만 조건 통과
print(datapoint['title']) # 해당 제품의 제목 출력 (예: "TurboChef Rapid Cook Oven")
except ValueError as e:
pass # 가격 정보가 비어있거나 문자열 등으로 변환 실패 시 무시하고 계속 진행
💡 이 코드를 통해 알 수 있는 것:
- 데이터셋 내 극단적으로 비싼 제품의 제목을 추출합니다.
- 예측 모델 훈련 시 이런 이상치(outlier)는 따로 다루거나 제거할 수도 있어요.
🧱 4. Item 객체로 구조화 및 필터링
📐 가격이 있는 데이터만 Item 객체로 만들기
# 📦 유효한 가격이 있는 데이터로부터 Item 객체 생성
items = [] # 최종적으로 포함할 Item 객체들을 저장할 리스트
for datapoint in dataset:
try:
price = float(datapoint["price"]) # 💰 가격을 실수(float)로 변환
if price > 0: # 0보다 큰 유효한 가격만 대상
item = Item(datapoint, price) # 🧱 Item 클래스 인스턴스 생성 (텍스트 + 가격 포함)
if item.include: # ✅ 이 항목이 훈련에 적합한지 판단 (예: 토큰 수 조건 충족 등)
items.append(item) # 조건을 만족하는 경우 리스트에 추가
except ValueError as e:
pass # 가격 변환에 실패한 경우 (비어 있음, 잘못된 문자열 등) 무시하고 넘어감
print(f"There are {len(items):,} items")
# 🧮 최종적으로 포함된 항목 수를 출력 (쉼표 포함 형식, 예: 2,374 items)
💡 item.include
는 내부적으로 다음과 같은 조건을 확인할 수 있어요:
- 텍스트 길이가 너무 짧지 않은가?
- 토큰 수가 제한 내(예: 160 또는 180)인가?
# 🔍 큐레이션된 항목 중 두 번째(Item 인스턴스) 확인
items[1]
# - Item 객체 리스트 중 두 번째 항목을 조회 (파이썬 인덱스는 0부터 시작하므로 [1]은 두 번째)
# - 출력 시 Item 클래스의 __repr__ 또는 __str__ 메서드 정의에 따라 요약된 정보가 표시됨
# (예: title, price, prompt 미리보기 등)
# - 제품 설명이 어떻게 요약되었는지, 프롬프트가 잘 구성되었는지 확인 가능
# 💡 참고: Jupyter 환경에서는 변수 마지막 줄에 객체를 그대로 적으면 자동으로 출력됩니다!
🔎 훈련용 프롬프트 예시 출력
# 🎯 훈련에 사용될 프롬프트 내용 확인
print(items[100].prompt)
# - 큐레이션된 Item 중 101번째 항목의 .prompt 속성 출력
# - 이 프롬프트는 LLM이 훈련 중에 입력받는 텍스트입니다
# - 모델은 이 프롬프트를 기반으로 "가격"을 예측하도록 학습됩니다
# 💡 프롬프트는 보통 아래와 같은 형태일 수 있어요:
# ---
# Product: "Mini Countertop Ice Maker"
# Features: ["Makes 26 lbs of ice", "Quiet Operation", ...]
# Description: "A portable and efficient ice maker for home use."
# What is a reasonable price for this item? $
# ---
# 모델은 위 프롬프트를 보고 💬 가격을 이어서 생성하게 됩니다
🔎 테스트용 프롬프트 예시 출력
이 코드는 모델을 테스트할 때 사용되는 프롬프트를 출력하는 부분입니다.
학습용 프롬프트와 비슷하지만, 정답(가격)이 빠진 상태로 모델이 스스로 답을 추론해야 하도록 설계되어 있어요.
# 🧪 모델 테스트 시 사용될 프롬프트 확인 (정답 없이 예측 유도)
print(items[100].test_prompt())
# - 101번째 Item 인스턴스의 test_prompt() 메서드를 호출
# - 훈련이 아닌 "추론(inference)" 상황을 가정한 프롬프트를 반환
# - 즉, 모델이 입력 텍스트만 보고 가격을 스스로 예측하도록 구성됨
# 💡 예시 형태:
# ---
# Product: "Stainless Steel Ice Maker"
# Features: ["Makes 26 lbs of ice", "Portable design", ...]
# Description: "This compact ice maker is perfect for small kitchens or RVs."
# What is a reasonable price for this item? $
# ---
# 👉 모델은 $ 뒤에 적절한 숫자를 직접 생성해야 함
# 📌 실제 애플리케이션에서 사용될 입력 형태와 유사하게 구성된다는 점에서 매우 중요합니다.
🧠 .prompt
vs .test\_prompt()
차이 정리
학습용 프롬프트(.prompt)
와 테스트용 프롬프트(.test\_prompt())
의 차이는 LLM 훈련 시점과 추론 시점의 목적 차이에서 비롯돼요.
아래는 그 차이를 목적 / 구성 / 예시 중심으로 정리한 비교표예요:
구분 | .prompt (학습용) |
.test\_prompt() (테스트용) |
---|---|---|
사용 시점 | 모델 훈련(training) 중 | 모델 테스트/추론(inference) 시 |
목적 | 모델이 가격을 예측하도록 학습 | 모델이 가격을 실제로 생성하게 함 |
형태 | 입력 + 정답 포함 (완성된 문장) | 입력만 제공, 가격은 모델이 채움 |
내용 구성 | 제품 설명 + 기능 + 질문 + 정답(가격) | 제품 설명 + 기능 + 질문까지만 |
예시 | Product: Mini Ice Maker\\nFeatures: \[...\] \\nWhat is a reasonable price for this item? $129.99 |
Product: Mini Ice Maker\\nFeatures: \[...\] \\nWhat is a reasonable price for this item? $ |
학습 방식 | 모델이 가격을 정답처럼 따라 쓰게끔 지도 학습 | 모델이 가격을 직접 생성하는 자율 추론 |
💡 왜 둘 다 필요한가요?
.prompt
는 LLM이 훈련 중에 정답이 포함된 입력을 보며 패턴을 학습합니다..test\_prompt()
는 훈련이 끝난 후, 실제로 제품 설명만 보고 가격을 예측할 수 있는지 평가합니다.
즉, .prompt는 교과서 📘,
.test\_prompt()는 시험 문제지 ✍️ 같은 역할이에요.
📏 5. 토큰 수 분포 분석
📊 프롬프트 토큰 수 히스토그램
이 코드는 Item
객체들에서 토큰 개수 분포를 히스토그램으로 시각화하는 부분이에요.
모델 입력의 길이를 관리하고 분석하는 데 매우 중요한 작업이죠.
# 🔢 각 항목의 토큰 수 분포 시각화 (히스토그램)
tokens = [item.token_count for item in items]
# - 각 Item 객체에 저장된 토큰 수(token_count)를 리스트로 수집
# - token_count는 일반적으로 tokenizer로 인코딩된 프롬프트의 길이 (토큰 기준)
plt.figure(figsize=(15, 6))
# - 그래프의 가로/세로 크기 지정 (폭이 넓어 분포를 보기 좋게 표시)
plt.title(f"Token counts: Avg {sum(tokens)/len(tokens):,.1f} and highest {max(tokens):,}\n")
# - 그래프 제목에 평균 토큰 수와 최댓값 표시
# - 평균은 소수점 첫째 자리까지, 1,000 단위 쉼표 포함
plt.xlabel('Length (tokens)') # X축 레이블: 토큰 길이 (예: 120, 130, 140...)
plt.ylabel('Count') # Y축 레이블: 해당 길이 구간에 속하는 항목 수
plt.hist(
tokens, # 분석할 데이터 리스트 (토큰 수)
rwidth=0.7, # 막대 너비 비율 (0.7은 막대 간 간격이 조금 생김)
color="green", # 막대 색상 설정 (시각적 구분을 위한 초록색)
bins=range(0, 300, 10) # 0~300 토큰까지 10단위 간격으로 구간 나눔
)
plt.show() # 📈 실제 그래프 출력
💡 이 시각화는 다음을 판단할 때 매우 유용해요:
- 데이터가 토큰 수 기준으로 너무 짧거나 긴 게 많은지?
- 설정한 커트오프(예: 180) 근처에 몰려 있는지?
'🧠 LLM 엔지니어링' 카테고리의 다른 글
자연어 처리 기반 가격 예측 모델 개발💸 (BOW vs Word2Vec vs RF) 02 (0) | 2025.05.02 |
---|---|
자연어 처리 기반 가격 예측 모델 개발💸 (BOW vs Word2Vec vs RF) 01 (1) | 2025.05.01 |
문서 기반 챗봇 만들기: RAG 실습 with OpenAI, FAISS, Gradio 02 (0) | 2025.04.29 |
문서 기반 챗봇 만들기: RAG 실습 with OpenAI, FAISS, Gradio 01 (1) | 2025.04.28 |
LangChain과 Chroma를 활용한 문서 임베딩 시각화 실습 02 (0) | 2025.04.27 |