LLM 엔지니어링1장 · 머신러닝 프로세스와 기본 개념21 / 23← 이전목차다음 →MetaContents

1장. 머신러닝 프로세스와 기본 개념

프로젝트 1 — 쇼핑몰 리뷰 긍정·부정 분류하기

쇼핑몰 고객센터에서 리뷰 800건을 받았습니다. 부정 리뷰를 빨리 찾아 답을 달고 싶습니다. 리뷰 글을 보고 긍정(1)인지 부정(0)인지 맞히는 모델을 만들어 보세요. 파일은 reviews.csv 이고, 문장 조각을 이어 붙여 만든 연습용 가상 데이터입니다. 별점을 잘못 누른 것처럼 표시가 뒤바뀐 리뷰도 조금 섞여 있습니다.

열뜻
리뷰번호행 번호
상품분류의류, 식품, 가전, 도서, 생활용품
리뷰리뷰 글
긍정1이면 긍정, 0이면 부정

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

  1. 긍정과 부정의 비율을 세고, 기준선 정확도를 구합니다.
  2. 학습용 75%, 시험용 25% 로 나눕니다. 긍정 비율을 맞춰 나눕니다.
  3. 리뷰 글을 숫자로 바꾸고, 로지스틱 회귀로 학습합니다.
  4. 시험용으로 정확도, 그리고 부정 리뷰의 정밀도와 재현율을 구합니다.
  5. 틀린 리뷰를 읽어 보고, 새 리뷰 세 문장을 판정합니다.

단계별 힌트입니다.

  1. 기준선은 df["긍정"].value_counts(normalize=True) 에서 큰 쪽의 비율입니다. normalize=True 는 개수 대신 비율로 보여 줍니다.
  2. 나누기는 실습 4와 같습니다. 특성이 df["리뷰"], 레이블이 df["긍정"] 이고, stratify=df["긍정"] 을 줍니다.
  3. 글자를 숫자로 바꾸는 도구는 from sklearn.feature_extraction.text import TfidfVectorizer 의 TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 3)) 를 그대로 씁니다. 리뷰를 두세 글자 조각으로 잘라, 조각마다 얼마나 눈에 띄는지를 숫자로 적은 표를 만들어 줍니다. 글을 숫자로 바꾸는 원리는 3장에서 토큰과 임베딩으로 자세히 다룹니다.
  4. 바꾸는 도구도 fit 은 학습용에만 합니다. 학습용은 fit_transform, 시험용은 transform 을 씁니다. 시험용으로 fit 하면 시험 문제를 미리 본 셈입니다.
  5. 부정 리뷰의 재현율은 recall_score(y_test, pred, pos_label=0) 처럼 pos_label 로 구합니다.
  6. 틀린 리뷰는 y_test != pred 로 골라 냅니다.

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

코드의 흐름

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

데이터 읽기, 기준선, 나누기 뒤 학습용과 시험용 두 갈래로 갈라져 글을 숫자로 바꾸고 학습·예측한 다음, 채점, 틀린 리뷰 읽기, 새 리뷰 판정으로 이어지는 코드 흐름도

주황 화살표를 따라가면 실행 차례입니다. 상자 왼쪽 위의 "1단계"~"4단계"는 예시 풀이의 단계 이름과 같고, 상자 아래의 글은 그 상자에서 쓰는 핵심 코드입니다. 맨 윗줄(청록)에서 데이터를 읽고 기준선을 구한 뒤 학습용 600건과 시험용 200건으로 나눕니다. 여기서 길이 두 갈래로 갈라집니다. 가운데 줄(마젠타)이 학습용 갈래로, 글을 숫자로 바꾸는 조각 목록을 만들고(fit_transform) 모델을 학습합니다(fit). 그 아래 줄(다홍)이 시험용 갈래로, 숫자로 바꾸기만 하고(transform) 예측만 합니다(predict). 회색 점선은 학습용 갈래에서 만든 조각 목록과 학습한 모델을 시험용 갈래가 그대로 가져다 쓴다는 뜻입니다. 시험용으로는 아무것도 새로 배우지 않으므로, 시험 문제를 미리 보는 일이 생기지 않습니다(힌트 4). 맨 아랫줄(짙은 청록)에서 시험용으로 채점하고, 틀린 리뷰를 골라 읽고, 새 리뷰를 판정합니다. 새 리뷰도 학습용에서 만든 vec 으로 바꾼 뒤 판정합니다.

실제 데이터로 해 보려면

같은 코드를 실제 리뷰에 그대로 돌려 볼 수 있습니다. 내려받기 쉬운 순서로 적었습니다. 내려받기 전에 자료마다 이용 약관과 출처 표기 조건을 확인하세요.

자료구하는 곳내용
네이버 영화 리뷰 말뭉치(NSMC)깃허브 e9t/nsmc 저장소영화 리뷰 20만 건과 긍정·부정 표시. 탭으로 나뉜 txt 파일
AI Hub 의 감성 분석 데이터AI Hub 누리집(회원 가입 후 신청)상품 리뷰와 감성 표시. 신청과 승인이 필요하다
공공데이터포털의 민원·후기 데이터공공데이터포털에서 "후기", "만족도" 검색기관마다 형식이 다르다. 긍정·부정 표시가 없으면 직접 붙인다

NSMC 파일을 읽는 모양은 아래와 같습니다. 이 PC 에서 실행하지 않은 안내 코드입니다. 열 이름이 document(글), label(긍정 1, 부정 0)이므로 아래 풀이의 열 이름만 바꾸면 됩니다.

출력
df = pd.read_csv("ratings_train.txt", sep="\t").dropna()
df = df.rename(columns={"document": "리뷰", "label": "긍정"})
결과 보기 (예시 풀이)

1단계. 비율과 기준선

Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score

df = pd.read_csv("reviews.csv")

# 긍정(1)과 부정(0)이 몇 건씩인지 센다
print(df["긍정"].value_counts())
# 큰 쪽의 비율이 "늘 그 답만 말하는 모델"의 정확도, 곧 기준선이다
print("기준선 정확도:", round(df["긍정"].value_counts(normalize=True).max(), 3))
출력
긍정
1    474
0    326
Name: count, dtype: int64
기준선 정확도: 0.592

2단계. 나누고, 글을 숫자로 바꾸고, 학습하기

Python
# 긍정 비율을 맞춰 학습용 75%, 시험용 25% 로 나눈다
X_train, X_test, y_train, y_test = train_test_split(
    df["리뷰"], df["긍정"], test_size=0.25, random_state=42, stratify=df["긍정"]
)

# char_wb: 낱말 안에서 글자 조각을 만든다. ngram_range=(2, 3): 두 글자, 세 글자 조각
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 3))
# 학습용으로 조각 목록을 만들고(fit) 바로 숫자 표로 바꾼다(transform)
X_train_vec = vec.fit_transform(X_train)
# 시험용은 학습용에서 만든 조각 목록으로 바꾸기만 한다
X_test_vec = vec.transform(X_test)
# 결과: (리뷰 수, 글자 조각 수)
print(X_train_vec.shape)

model = LogisticRegression(max_iter=1000).fit(X_train_vec, y_train)
pred = model.predict(X_test_vec)
출력
(600, 470)

3단계. 채점하기

Python
print("정확도:", round(accuracy_score(y_test, pred), 3))
# pos_label=0: 부정(0) 리뷰를 기준으로 정밀도와 재현율을 계산한다
print("부정 정밀도:", round(precision_score(y_test, pred, pos_label=0), 3))
print("부정 재현율:", round(recall_score(y_test, pred, pos_label=0), 3))
출력
정확도: 0.98
부정 정밀도: 0.976
부정 재현율: 0.976

4단계. 틀린 리뷰 읽기와 새 리뷰 판정

Python
# 리뷰, 정답, 예측을 한 표로 모은 뒤 정답과 예측이 다른 행만 남긴다
wrong = pd.DataFrame({"리뷰": X_test, "정답": y_test, "예측": pred})[y_test != pred]
print(len(wrong), "건 틀림")
print(wrong.to_string(index=False))

# 새 리뷰도 학습용에서 만든 vec 으로 바꾼 뒤 긍정일 확률을 구한다
new = ["포장이 꼼꼼하고 배송도 빨랐어요", "색이 사진과 너무 달라서 실망이에요", "가격은 비싸지만 품질은 좋아요"]
for text, p in zip(new, model.predict_proba(vec.transform(new))[:, 1]):
    print(f"{p:.3f}  {'긍정' if p >= 0.5 else '부정'}  {text}")
출력
4 건 틀림
                리뷰  정답  예측
  맛있어서 또 시켰어요 추천해요   0   1
설명과 다르게 작동합니다 아쉬워요   1   0
       가격에 비해 별로네요   1   0
  가격 대비 만족합니다 추천해요   0   1
0.851  긍정  포장이 꼼꼼하고 배송도 빨랐어요
0.239  부정  색이 사진과 너무 달라서 실망이에요
0.724  긍정  가격은 비싸지만 품질은 좋아요

부정과 긍정이 326건과 474건이라 기준선은 0.592 입니다. 모델의 정확도는 0.980 이고, 부정 리뷰의 정밀도와 재현율도 모두 0.976 입니다. 그런데 틀린 4건을 읽어 보면 "맛있어서 또 시켰어요 추천해요" 가 부정으로, "가격에 비해 별로네요" 가 긍정으로 표시되어 있습니다. 넷 모두 모델이 틀린 것이 아니라 표시가 뒤바뀐 리뷰입니다. 틀린 예를 직접 읽어야 하는 까닭이 여기에 있습니다. 점수만 보면 모델 탓을 하게 되지만, 데이터에 붙은 정답이 틀린 경우도 있습니다. 새 리뷰 가운데 "가격은 비싸지만 품질은 좋아요" 는 0.724 로 긍정이 나왔습니다. 다만 이 데이터는 짧은 문장 조각으로 지어낸 것이라 점수가 높게 나옵니다. NSMC 같은 실제 리뷰에서는 반어와 맞춤법이 섞여 점수가 이보다 낮게 나오기 쉽습니다.

이 프로젝트가 뜻하는 것

이 프로젝트는 고객센터 신입 직원에게 리뷰 분류를 가르치는 일과 같습니다. 칭찬 리뷰와 불만 리뷰가 몇 장씩인지 세어 "늘 칭찬이라고만 말하면 몇 점일까"를 먼저 정하고(1단계), 리뷰 묶음을 연습용과 시험용으로 나눈 뒤 글을 두세 글자 조각의 숫자로 바꿔 가르치고(2단계), 시험용으로 채점하고(3단계), 틀린 리뷰를 직접 읽고 새 리뷰도 판정해 봅니다(4단계). 여기서 "늘 칭찬" 점수가 기준선, 글자 조각의 숫자 표가 TfidfVectorizer 가 만든 TF-IDF(Term Frequency–Inverse Document Frequency, 낱말 빈도와 역문서 빈도) 표, 신입 직원이 로지스틱 회귀입니다. 비유와 다른 점은, 사람은 뜻을 읽지만 이 모델은 글자 조각이 얼마나 자주 보이는지만 본다는 것입니다.

단계출력뜻
1. 비율긍정 474, 부정 326리뷰 800건 가운데 긍정이 조금 더 많습니다
1. 기준선0.592늘 "긍정"이라고만 해도 이만큼은 맞습니다. 모델은 이 점수를 넘어야 합니다
2. 숫자 표(600, 470)학습용 리뷰 600건이 글자 조각 470가지의 숫자로 바뀌었습니다
3. 채점정확도 0.98, 부정 정밀도·재현율 0.976시험용 200건 가운데 4건만 틀렸고, 부정 리뷰도 거의 빠짐없이 찾았습니다
4. 틀린 리뷰4 건 틀림"또 시켰어요 추천해요"가 부정으로, "별로네요"가 긍정으로 적힌 것처럼, 넷 모두 표시가 뒤바뀐 리뷰였습니다. 모델보다 정답 쪽이 틀린 경우입니다
4. 새 리뷰0.851, 0.239, 0.724긍정일 확률입니다. "실망이에요"는 부정으로, "비싸지만 품질은 좋아요"는 긍정으로 판정했습니다

한 줄로 줄이면, 기준선 0.592 를 0.98 까지 끌어올렸고, 남은 틀림은 모델보다 데이터의 표시 쪽 문제였다는 것을 보인 프로젝트입니다. 점수만 보지 말고 틀린 예를 직접 읽어야 한다는 것이 핵심입니다. 짧은 문장 조각으로 지어낸 연습용 데이터라 점수가 높게 나왔으므로, 실제 리뷰에서도 이만큼 나온다는 뜻은 아닙니다. 글을 숫자로 바꾸는 원리는 3장에서 토큰과 임베딩으로 다룹니다.

← 이전 절1장 · 실습 6 — 기준값 바꾸고 과적합 확인하기다음 절 →1장 · 정리