LLM 엔지니어링3장 · Attention 과 Transformer, GPT 직접 구현21 / 23← 이전목차다음 →MetaContents

3장. Attention 과 Transformer, GPT 직접 구현

프로젝트 1 — 고객 문의 챗봇 맛보기

가상 쇼핑몰의 고객센터에 실습 6의 작은 챗봇 모델을 붙여 보려 합니다. 쇼핑몰의 자주 묻는 질문(FAQ) 12개를 시스템 메시지에 넣었을 때와 넣지 않았을 때, 고객 질문 다섯 개에 얼마나 맞게 답하는지 견주어 보세요. 파일은 shop_faq.csv 이고, 정책은 연습용으로 지어낸 것입니다.

열뜻
분류배송, 교환, 환불, 주문, 결제, 쿠폰, 회원, 상담
질문자주 묻는 질문
답변쇼핑몰의 정해진 답

시험 질문과, 바른 답이라면 꼭 들어 있어야 할 낱말(정답 열쇠)은 아래와 같습니다.

시험 질문정답 열쇠FAQ 의 근거
배송비가 얼마예요? 2만원어치 샀어요.3,000원3만원보다 적으면 3,000원
옷을 받은 지 5일 됐는데 교환할 수 있나요?7일받은 날부터 7일 안에
환불은 며칠 걸려요?3영업일도착 뒤 3영업일 안에
쿠폰 두 장 같이 써도 돼요?한 장주문 하나에 한 장만
고객센터는 몇 시까지 해요?6시오후 6시까지

요구사항은 다섯 가지입니다.

  1. shop_faq.csv 를 읽어 FAQ 를 "질문 / 답변" 꼴의 글 하나로 엮습니다.
  2. FAQ 없는 시스템 메시지와, FAQ 를 덧붙인 시스템 메시지를 만듭니다.
  3. 시험 질문 다섯 개를 두 시스템 메시지로 각각 물어 답을 받습니다.
  4. 답에 정답 열쇠가 들어 있는지로 맞힌 수를 셉니다.
  5. 틀린 답을 읽고, 왜 틀렸을지 한두 문장으로 적습니다.

단계별 힌트입니다.

  1. FAQ 엮기는 "\n".join(f"- 질문: {q} / 답변: {a}" for q, a in zip(faq["질문"], faq["답변"])) 꼴로 합니다.
  2. FAQ 를 넣는 시스템 메시지에는 "아래 내용만 근거로 답하고, 없는 내용은 모른다고 답합니다" 같은 지시를 함께 적습니다.
  3. 묻는 함수는 실습 6의 ask 를 그대로 씁니다.
  4. 맞혔는지는 key in answer 로 확인합니다. 낱말이 들어 있는지만 보는 거친 채점이라, 마지막에 답을 눈으로 함께 읽습니다.

확인할 것: 다 풀었으면 항목마다 스스로 확인해 체크해 보세요.

실제 데이터로 해 보려면

실제 기관이나 회사의 FAQ 로 같은 실험을 할 수 있습니다. 내려받기 쉬운 순서로 적었습니다. 자료마다 이용 조건과 개인정보 포함 여부를 확인하세요.

자료구하는 곳내용
기관의 자주 묻는 질문공공데이터포털에서 "FAQ", "자주 묻는 질문" 검색기관마다 질문과 답변을 파일로 공개한 것이 있다
내가 다니는 회사나 가게의 FAQ누리집의 FAQ 쪽을 복사해 csv 로 정리공개된 쪽만, 고객 정보가 섞이지 않게
AI Hub 의 민원·상담 대화 데이터AI Hub 누리집(회원 가입 후 신청)상담 대화와 분류. 신청과 승인이 필요하다

FAQ 가 수백 개로 늘어나면 시스템 메시지에 다 넣을 수 없습니다. 질문과 관련된 FAQ 만 골라 넣는 방법이 6장의 RAG 입니다.

코드의 흐름

예시 풀이의 코드가 어떤 차례로 흘러가는지 한 장으로 그렸습니다. 풀기 전에 큰 줄기를 먼저 보고, 풀다가 막히면 지금 어느 상자에 있는지 확인해 보세요.

FAQ 엮기, 모델과 ask 함수, 묻고 채점하기의 세 상자를 화살표로 이은 고객 문의 챗봇 프로젝트의 코드 흐름도

주황 화살표가 실행 차례이고, 상자의 번호는 예시 풀이의 단계 번호와 같습니다. 1단계(청록)에서 shop_faq.csv 를 읽어 FAQ 를 "질문 / 답변" 꼴의 글 하나(faq_text)로 엮습니다. 2단계(마젠타)에서 Qwen/Qwen2.5-0.5B-Instruct 모델과 묻는 함수 ask 를 준비하고, FAQ 없는 시스템 메시지 base 와 FAQ 를 덧붙인 with_faq 를 만듭니다. 3단계(다홍)는 시험 질문 다섯 개를 두 시스템 메시지로 물어 key in answer 로 맞혔는지 보고, groupby 로 조건마다 맞힌 수를 셉니다.

결과 보기 (예시 풀이)

1단계. FAQ 읽고 엮기

Python
import pandas as pd
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

faq = pd.read_csv("shop_faq.csv")
print(faq.shape)

# 질문과 답변을 한 줄씩 "- 질문: … / 답변: …" 꼴로 이어 붙인다
faq_text = "\n".join(f"- 질문: {q} / 답변: {a}" for q, a in zip(faq["질문"], faq["답변"]))
print(faq_text[:150])
출력
(12, 3)
- 질문: 배송은 얼마나 걸리나요? / 답변: 결제 후 1~3일 안에 도착합니다. 제주와 섬 지역은 2일 더 걸립니다.
- 질문: 배송비는 얼마인가요? / 답변: 3만원 이상 주문하면 무료이고, 그보다 적으면 3,000원입니다.
- 질문: 교환은 언제까지 할 수 있나요

이 단계의 함수입니다.

코드쉬운 뜻
pd.read_csv("shop_faq.csv")FAQ 파일을 표로 읽는다. 출력의 (12, 3)은 12행, 3열이다
zip(faq["질문"], faq["답변"])두 열을 한 줄씩 짝지어 꺼낸다
"\n".join(...)줄마다 만든 "- 질문: … / 답변: …" 글을 줄바꿈으로 이어 글 하나로 만든다. 이 글을 시스템 메시지에 덧붙인다
faq_text[:150]앞 150글자만 잘라 확인한다

2단계. 모델과 묻는 함수 준비

Python
name = "Qwen/Qwen2.5-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(name)
model = AutoModelForCausalLM.from_pretrained(name, dtype=torch.float32)
model.eval()

def ask(system, question, max_new_tokens=80):
    messages = [{"role": "system", "content": system}, {"role": "user", "content": question}]
    enc = tokenizer.apply_chat_template(messages, add_generation_prompt=True,
                                        return_tensors="pt", return_dict=True)
    with torch.no_grad():
        out = model.generate(**enc, max_new_tokens=max_new_tokens, do_sample=False)
    return tokenizer.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True).strip()

# FAQ 없는 시스템 메시지와 FAQ 를 덧붙인 시스템 메시지
base = "당신은 온라인 쇼핑몰 고객센터 상담원입니다. 한국어로 두 문장 안에서 짧고 친절하게 답합니다."
with_faq = base + " 아래 자주 묻는 질문의 내용만 근거로 답하고, 없는 내용은 모른다고 답합니다.\n" + faq_text
print(len(base), len(with_faq))
출력
52 840

이 단계의 함수는 실습 6의 표와 같습니다. ask 는 실습 6과 같고, 답을 자르는 두 줄을 한 줄로 합쳤을 뿐입니다.

코드쉬운 뜻
base + " ...\n" + faq_text기본 지시 뒤에 "근거로만 답하라"는 지시와 FAQ 글을 이어 붙여 둘째 시스템 메시지를 만든다
len(base), len(with_faq)두 시스템 메시지의 글자 수이다. 출력은 52 와 840 이다

FAQ 를 넣으면 시스템 메시지가 그만큼 길어집니다. 입력 토큰이 늘어 답 하나에 걸리는 시간도 늘어납니다.

3단계. 두 시스템 메시지로 묻고 채점하기

Python
tests = [
    ("배송비가 얼마예요? 2만원어치 샀어요.", "3,000원"),
    ("옷을 받은 지 5일 됐는데 교환할 수 있나요?", "7일"),
    ("환불은 며칠 걸려요?", "3영업일"),
    ("쿠폰 두 장 같이 써도 돼요?", "한 장"),
    ("고객센터는 몇 시까지 해요?", "6시"),
]

rows = []
for label, system in [("FAQ 없음", base), ("FAQ 있음", with_faq)]:
    for q, key in tests:
        answer = ask(system, q).replace("\n", " ")
        rows.append({"조건": label, "질문": q, "맞힘": key in answer, "답": answer})
        print(f"[{label}] {'O' if key in answer else 'X'} {q}\n    → {answer}")

result = pd.DataFrame(rows)
# 조건마다 맞힌 수를 센다(True 는 1 로 더해진다)
print(result.groupby("조건")["맞힘"].sum())
출력
[FAQ 없음] X 배송비가 얼마예요? 2만원어치 샀어요.
    → 배송비는 상품의 가격보다 비용이므로, 2만 원을 기준으로 배송비는 추가적으로 10%를 부과해야 합니다. 따라서 배송비는 2만 원 × 10% = 2000원입니다.
[FAQ 없음] X 옷을 받은 지 5일 됐는데 교환할 수 있나요?
    → 죄송합니다, 저는 인공지능으로서 옷의 교환에 대한 정보를 제공하지 못합니다. 하지만 옷의 교환은 일반적으로 주변 상점이나 온라인 플랫폼에서 가능하며, 시간이 지남에 따라 교환 가능성이 있습니다. 다른 방법으로도 옷을 찾을 수 있으니 참고해보세요.
[FAQ 없음] X 환불은 며칠 걸려요?
    → 환불 시간은 상황에 따라 달라집니다. 일반적으로 3-7일 정도가 빠르게 처리될 수 있습니다. 하지만, 예상치 못한 경우는 언제든 연락주시기 바랍니다.
[FAQ 없음] X 쿠폰 두 장 같이 써도 돼요?
    → 네, 쿠폰 두 장을 함께 사용할 수 있습니다. 이는 고객님의 주문에 대한 추가 혜택을 제공하는 데 도움이 될 수 있습니다.
[FAQ 없음] X 고객센터는 몇 시까지 해요?
    → 고객센터는 24시간 운영됩니다.
[FAQ 있음] O 배송비가 얼마예요? 2만원어치 샀어요.
    → 3,000원입니다.
[FAQ 있음] O 옷을 받은 지 5일 됐는데 교환할 수 있나요?
    → 물론이죠. 상품을 받은 날부터 7일 안에 신청하면 교환 가능합니다.
[FAQ 있음] X 환불은 며칠 걸려요?
    → 상품 불량이면 쇼핑몰이, 단순 변심이면 고객이 왕복 6,000원을 냅니다.
[FAQ 있음] X 쿠폰 두 장 같이 써도 돼요?
    → 네, 쿠폰 두 장을 동시에 사용하실 수 있습니다.
[FAQ 있음] O 고객센터는 몇 시까지 해요?
    → 고객센터는 평일 오전 9시부터 오후 6시까지 운영됩니다.
조건
FAQ 없음    0
FAQ 있음    3
Name: 맞힘, dtype: int64

이 단계의 함수입니다.

코드쉬운 뜻
ask(system, q).replace("\n", " ")답을 받고 줄바꿈을 빈칸으로 바꾼다. 한 줄로 출력해 읽기 쉽게 하려는 것이다
key in answer답 글에 정답 열쇠 낱말이 들어 있는지 본다. 들어 있으면 True 이다
pd.DataFrame(rows)사전 목록을 표로 만든다. 사전의 키가 열 이름이 된다
result.groupby("조건")["맞힘"].sum()조건마다 맞힘 열을 더한다. True 는 1, False 는 0 으로 더해져 맞힌 수가 된다. 출력은 FAQ 없음 0, FAQ 있음 3 이다

FAQ 없이 물었을 때는 다섯 문제를 모두 틀렸습니다. 틀린 방식이 더 눈여겨볼 만합니다. 배송비는 "10%를 부과해야 합니다. 따라서 2000원"이라는 없는 계산을 만들었고, 쿠폰은 "두 장을 함께 사용할 수 있습니다", 고객센터는 "24시간 운영됩니다"라고 자신 있게 틀렸습니다. 이것이 환각입니다. FAQ 를 넣자 배송비 3,000원, 교환 7일, 오후 6시 세 문제를 맞혔습니다. 그래도 둘은 여전히 틀렸습니다. 환불 질문에는 교환 배송비 항목("왕복 6,000원")을 잘못 집어 답했고, 쿠폰은 FAQ 에 "한 장만"이라고 적혀 있는데도 "두 장을 동시에 사용하실 수 있습니다"라고 답했습니다. 12개 항목이 한꺼번에 들어가니 작은 모델이 맞는 줄을 고르지 못한 것입니다. 정리하면, 근거를 넣어 주면 환각이 줄지만 "질문에 맞는 근거만 골라 넣기"와 "더 큰 모델"이 함께 필요합니다. 앞의 것이 6장의 RAG, 뒤의 것이 4장부터 다루는 모델 고르기와 미세조정입니다. 낱말이 들어 있는지만 보는 채점이라, 실제로는 답을 눈으로 함께 확인해야 합니다.

이 프로젝트가 뜻하는 것

이 프로젝트는 신입 상담원에게 시험을 두 번 보게 하는 것과 같습니다. 먼저 쇼핑몰 안내서를 엮어 두고(1단계), 상담원(모델)과 묻는 방법을 준비한 뒤 안내서를 주지 않은 지시문과 안내서를 붙인 지시문을 만듭니다(2단계). 그리고 같은 질문 다섯 개를 두 번 물어 정답 낱말이 들어 있는지로 채점합니다(3단계). 여기서 안내서가 FAQ, 상담원이 Qwen2.5-0.5B 모델, 지시문이 시스템 메시지입니다. 비유와 다른 점은, 사람은 안내서를 읽고 기억하지만 모델은 질문할 때마다 시스템 메시지 전체를 처음부터 다시 읽는다는 것입니다.

단계출력뜻
1. FAQ 엮기(12, 3)FAQ 가 12줄, 열이 3개(분류, 질문, 답변)입니다. 그 아래는 "- 질문: … / 답변: …" 꼴로 엮은 글의 앞부분입니다
2. 시스템 메시지52 840FAQ 없는 지시문은 52글자, FAQ 를 붙인 지시문은 840글자입니다. 모델이 질문마다 읽을 글이 16배쯤 길어졌습니다
3. FAQ 없음X 다섯 개다섯 문제를 모두 틀렸습니다. "10% 부과, 2000원", "24시간 운영"처럼 없는 사실을 자신 있게 지어냈습니다. 이것이 환각입니다
3. FAQ 있음O 세 개, X 두 개배송비 3,000원, 교환 7일, 오후 6시는 맞혔습니다. 환불 질문에는 교환 배송비 줄을 잘못 골라 답했고, 쿠폰은 "한 장만"이라는 FAQ 와 반대로 답했습니다
3. 합계FAQ 없음 0, FAQ 있음 3근거를 넣어 주자 맞힌 수가 0개에서 3개로 늘었습니다

한 줄로 줄이면, "모델이 모르는 사실은 근거를 넣어 주면 맞히기 시작하지만, 작은 모델은 근거가 많으면 맞는 줄을 잘못 고른다"는 것을 보인 프로젝트입니다. 다섯 문제로 낸 결과라 숫자 하나하나보다 흐름으로 읽어야 하고, 낱말이 들어 있는지만 본 채점이라 맞힘 3개도 답을 눈으로 함께 확인해야 합니다. 질문에 맞는 FAQ 만 골라 넣는 방법은 6장의 RAG 에서 다룹니다.

← 이전 절3장 · 실습 6 — 작은 챗봇 모델 써 보기다음 절 →3장 · 정리