본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 6. · 15 Views
영화 리뷰 감성 분석 완벽 가이드
영화 리뷰 텍스트를 분석하여 긍정과 부정을 자동으로 판별하는 감성 분석의 모든 것을 다룹니다. IMDB 데이터셋을 활용하여 텍스트 전처리부터 모델 학습, 시각화까지 단계별로 배워봅니다.
목차
1. IMDB 리뷰 데이터 로드
김개발 씨는 어느 날 팀장님께 긴급한 요청을 받았습니다. "우리 서비스에 들어오는 고객 리뷰가 긍정인지 부정인지 자동으로 분류할 수 있을까요?" 수천 개의 리뷰를 일일이 읽어볼 수는 없는 노릇이었습니다.
바로 이럴 때 필요한 것이 감성 분석입니다.
감성 분석은 텍스트에 담긴 감정이나 의견을 자동으로 파악하는 기술입니다. 마치 노련한 독서가가 글을 읽고 "이 사람 기분이 좋았겠네" 또는 "화가 많이 났네"라고 판단하는 것처럼, 컴퓨터도 학습을 통해 비슷한 판단을 내릴 수 있습니다.
이번에 사용할 IMDB 데이터셋은 50,000개의 영화 리뷰로 구성된 대표적인 감성 분석 학습 자료입니다.
다음 코드를 살펴봅시다.
import pandas as pd
from sklearn.datasets import load_files
# IMDB 리뷰 데이터를 불러옵니다
reviews_data = load_files('aclImdb/', categories=['pos', 'neg'])
# 텍스트와 레이블을 분리합니다
texts = reviews_data.data
labels = reviews_data.target # 0: 부정, 1: 긍정
# 데이터프레임으로 변환하여 살펴봅니다
df = pd.DataFrame({
'review': [text.decode('utf-8') for text in texts],
'sentiment': ['positive' if label == 1 else 'negative' for label in labels]
})
# 데이터 분포를 확인합니다
print(f"전체 리뷰 수: {len(df)}")
print(df['sentiment'].value_counts())
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 머신러닝이라는 말은 많이 들어봤지만, 실제로 프로젝트에 적용해본 적은 없었습니다.
팀장님의 요청을 받고 나서 막막했던 김개발 씨는 선배 박시니어 씨에게 도움을 요청했습니다. "감성 분석이요?
그거 생각보다 재미있어요. 일단 좋은 학습 데이터부터 구해야 합니다." 박시니어 씨의 말처럼, 머신러닝에서 가장 중요한 것은 바로 데이터입니다.
아무리 뛰어난 알고리즘도 좋은 데이터 없이는 무용지물이기 때문입니다. 그렇다면 IMDB 데이터셋이란 무엇일까요?
쉽게 말해, IMDB는 전 세계적으로 유명한 영화 데이터베이스 사이트입니다. 여기에 올라온 영화 리뷰들을 모아서 학습용으로 정리한 것이 바로 IMDB 감성 분석 데이터셋입니다.
마치 요리를 배울 때 레시피와 재료가 이미 준비된 밀키트를 사용하는 것처럼, 이 데이터셋은 감성 분석을 배우기에 최적화되어 있습니다. 이 데이터셋의 가장 큰 장점은 균형 잡힌 구성입니다.
긍정 리뷰 25,000개와 부정 리뷰 25,000개가 정확히 반반으로 나뉘어져 있습니다. 만약 긍정 리뷰가 90%, 부정이 10%였다면 어땠을까요?
모델이 무조건 "긍정"이라고 대답해도 90%의 정확도가 나올 겁니다. 이런 불균형 데이터는 제대로 된 학습을 방해합니다.
코드를 살펴보겠습니다. 먼저 load_files 함수를 사용합니다.
이 함수는 폴더 구조를 자동으로 인식하여 데이터를 불러옵니다. 'pos' 폴더에 있는 파일은 긍정, 'neg' 폴더에 있는 파일은 부정으로 자동 분류됩니다.
데이터를 불러온 후에는 판다스 데이터프레임으로 변환합니다. 데이터프레임은 엑셀 시트와 비슷한 구조로, 데이터를 다루기가 훨씬 편리해집니다.
각 행에는 리뷰 텍스트와 해당 리뷰의 감성 레이블이 들어갑니다. 실무에서는 어떻게 활용할까요?
예를 들어 쇼핑몰을 운영한다면 상품 리뷰를 분석하여 고객 만족도를 실시간으로 파악할 수 있습니다. 갑자기 부정 리뷰가 늘어난다면 제품에 문제가 생겼다는 신호일 수 있겠죠.
초보자들이 흔히 하는 실수 중 하나는 데이터를 불러온 후 바로 모델 학습에 들어가는 것입니다. 하지만 그 전에 반드시 데이터의 분포와 특성을 확인해야 합니다.
value_counts() 함수로 각 클래스의 개수를 확인하는 습관을 들이세요. 김개발 씨는 데이터를 성공적으로 불러왔습니다.
이제 다음 단계인 텍스트 전처리로 넘어갈 준비가 되었습니다.
실전 팁
💡 - 데이터를 불러온 후 반드시 shape와 value_counts로 구조를 확인하세요
- 인코딩 문제가 발생하면 decode('utf-8', errors='ignore')를 사용하세요
2. 텍스트 정제 및 토큰화
데이터를 불러온 김개발 씨는 리뷰 내용을 살펴보다가 당황했습니다. HTML 태그, 특수문자, 대소문자가 뒤섞여 있었기 때문입니다.
"이대로 학습시키면 될까요?" 박시니어 씨가 고개를 저었습니다. "텍스트는 요리 재료와 같아요.
손질을 해야 제 맛이 납니다."
텍스트 전처리는 원본 텍스트에서 불필요한 요소를 제거하고 분석에 적합한 형태로 가공하는 과정입니다. 토큰화는 문장을 의미 있는 단위로 쪼개는 작업입니다.
마치 레고 블록을 분해하여 각 조각을 파악하는 것처럼, 문장을 단어 단위로 나누면 컴퓨터가 텍스트를 이해하기 쉬워집니다.
다음 코드를 살펴봅시다.
import re
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
def preprocess_text(text):
# HTML 태그 제거
text = re.sub(r'<[^>]+>', '', text)
# 소문자로 변환
text = text.lower()
# 특수문자 및 숫자 제거
text = re.sub(r'[^a-z\s]', '', text)
# 토큰화 (단어 단위로 분리)
tokens = word_tokenize(text)
# 불용어 제거
stop_words = set(stopwords.words('english'))
tokens = [word for word in tokens if word not in stop_words]
# 어간 추출 (stemming)
stemmer = PorterStemmer()
tokens = [stemmer.stem(word) for word in tokens]
return ' '.join(tokens)
김개발 씨가 처음 본 리뷰 데이터는 정말 엉망진창이었습니다. "<br />This movie was AMAZING!!!" 같은 문장이 수두룩했습니다.
이 상태로 모델을 학습시키면 어떻게 될까요? 컴퓨터 입장에서 "Amazing"과 "amazing"은 완전히 다른 단어입니다.
"GREAT", "Great", "great"도 마찬가지죠. 사람 눈에는 같은 의미인데 컴퓨터는 세 개의 다른 단어로 인식합니다.
이렇게 되면 학습 효율이 크게 떨어집니다. 텍스트 전처리는 이런 문제를 해결하는 필수 과정입니다.
마치 요리 전에 재료를 씻고 다듬는 것과 같습니다. 아무리 좋은 재료도 흙이 묻어 있으면 제 맛이 나지 않겠죠.
첫 번째 단계는 HTML 태그 제거입니다. 웹에서 수집한 데이터에는 종종 "<br />"이나 "<p>" 같은 태그가 섞여 있습니다.
정규표현식을 사용하면 이런 태그들을 깔끔하게 제거할 수 있습니다. 두 번째는 소문자 변환입니다.
앞서 말했듯이 "Amazing"과 "amazing"을 하나로 통일해야 합니다. lower() 메서드 한 줄로 모든 대문자를 소문자로 바꿀 수 있습니다.
세 번째는 특수문자와 숫자 제거입니다. 느낌표, 물음표, 쉼표 같은 문장부호는 감성 분석에 큰 도움이 되지 않습니다.
물론 느낌표가 많으면 강한 감정을 표현한다고 볼 수도 있지만, 기본적인 분석에서는 제거하는 것이 일반적입니다. 네 번째 단계가 바로 토큰화입니다.
"I love this movie"라는 문장을 ["I", "love", "this", "movie"]로 쪼개는 것입니다. NLTK 라이브러리의 word_tokenize 함수가 이 일을 해줍니다.
다섯 번째는 불용어 제거입니다. 영어에서 "the", "a", "is", "in" 같은 단어들은 문장의 의미를 파악하는 데 큰 도움이 되지 않습니다.
이런 단어들을 **불용어(stopwords)**라고 부르며, 분석에서 제외합니다. 마지막으로 어간 추출입니다.
"running", "runs", "ran"은 모두 "run"이라는 같은 의미를 담고 있습니다. PorterStemmer는 이런 단어들을 기본 형태로 되돌려줍니다.
박시니어 씨가 덧붙였습니다. "전처리를 얼마나 꼼꼼하게 하느냐에 따라 모델 성능이 10% 이상 차이날 수 있어요." 김개발 씨는 고개를 끄덕이며 코드를 실행했습니다.
실전 팁
💡 - 도메인에 따라 전처리 방식을 조정하세요. 의료 텍스트와 영화 리뷰는 다른 접근이 필요합니다
- 불용어 리스트는 직접 커스터마이징할 수 있습니다
3. Bag of Words vs TF-IDF
전처리를 마친 김개발 씨에게 새로운 고민이 생겼습니다. "텍스트를 어떻게 숫자로 바꾸지?" 컴퓨터는 문자를 직접 이해하지 못합니다.
머신러닝 모델에 텍스트를 넣으려면 반드시 숫자로 변환해야 합니다. 이때 사용하는 대표적인 방법이 Bag of Words와 TF-IDF입니다.
**Bag of Words(BoW)**는 텍스트를 단어 출현 빈도로 표현하는 가장 기본적인 방법입니다. TF-IDF는 단순 빈도에서 한 걸음 더 나아가, 특정 문서에서 중요한 단어에 더 높은 가중치를 부여합니다.
마치 시험에서 자주 나오는 문제와 특별히 중요한 문제를 구분하는 것과 같습니다.
다음 코드를 살펴봅시다.
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
# Bag of Words 방식
bow_vectorizer = CountVectorizer(max_features=5000)
X_bow = bow_vectorizer.fit_transform(df['review'])
# TF-IDF 방식
tfidf_vectorizer = TfidfVectorizer(max_features=5000)
X_tfidf = tfidf_vectorizer.fit_transform(df['review'])
# 결과 비교
print(f"BoW 벡터 크기: {X_bow.shape}")
print(f"TF-IDF 벡터 크기: {X_tfidf.shape}")
# 특정 단어의 가중치 확인
feature_names = tfidf_vectorizer.get_feature_names_out()
sample_idx = list(feature_names).index('excellent')
print(f"'excellent'의 TF-IDF 점수 (첫 번째 문서): {X_tfidf[0, sample_idx]:.4f}")
박시니어 씨가 화이트보드에 간단한 그림을 그리기 시작했습니다. "컴퓨터에게 텍스트를 이해시키려면 숫자로 바꿔야 해요.
이걸 벡터화라고 합니다." 먼저 Bag of Words를 이해해봅시다. 이름 그대로 단어들을 가방에 넣는다고 생각하면 됩니다.
문장에서 어떤 단어가 몇 번 나왔는지만 세는 것이죠. 예를 들어 "I love this movie.
I love it."이라는 문장이 있다고 합시다. BoW로 변환하면 {I: 2, love: 2, this: 1, movie: 1, it: 1}이 됩니다.
단어의 순서는 완전히 무시됩니다. 그래서 "Bag of Words"라는 이름이 붙었습니다.
가방에 물건을 넣으면 순서가 사라지는 것처럼요. 하지만 BoW에는 치명적인 단점이 있습니다.
모든 단어를 동등하게 취급한다는 것입니다. "the"가 100번 나온 문서와 "excellent"가 5번 나온 문서가 있을 때, BoW는 "the"에 더 높은 점수를 줍니다.
과연 "the"가 감성 분석에 더 중요한 단어일까요? 이 문제를 해결하기 위해 등장한 것이 TF-IDF입니다.
TF는 Term Frequency로 특정 문서에서 단어의 빈도를 의미합니다. IDF는 Inverse Document Frequency로 전체 문서에서 얼마나 희귀한 단어인지를 나타냅니다.
쉽게 말해, "the"처럼 모든 문서에 등장하는 단어는 IDF 값이 낮아집니다. 반면 "magnificent"처럼 특정 문서에만 등장하는 단어는 IDF 값이 높아집니다.
TF와 IDF를 곱하면, 특정 문서에서 진짜 중요한 단어가 높은 점수를 받게 됩니다. 코드를 보면 sklearn의 CountVectorizer와 TfidfVectorizer를 사용합니다.
사용법은 거의 동일합니다. max_features=5000은 가장 빈번한 5,000개의 단어만 사용하겠다는 의미입니다.
메모리와 속도를 위해 적절히 제한하는 것이 좋습니다. fit_transform 메서드는 두 가지 일을 동시에 합니다.
먼저 fit으로 데이터에서 어휘를 학습하고, transform으로 실제 벡터화를 수행합니다. 결과로 나오는 행렬의 크기는 (문서 수, 단어 수)가 됩니다.
"실무에서는 TF-IDF를 더 많이 써요." 박시니어 씨의 조언이었습니다. 대부분의 텍스트 분류 문제에서 TF-IDF가 BoW보다 좋은 성능을 보여주기 때문입니다.
실전 팁
💡 - max_features 값은 데이터셋 크기에 따라 조정하세요. 작은 데이터셋에서는 1,000~3,000이 적당합니다
- TfidfVectorizer의 ngram_range 파라미터로 n-gram도 함께 추출할 수 있습니다
4. 로지스틱 회귀 분류
드디어 모델을 학습시킬 준비가 끝났습니다. 김개발 씨는 어떤 알고리즘을 사용해야 할지 고민했습니다.
"딥러닝을 써야 하나요?" 박시니어 씨가 웃으며 대답했습니다. "텍스트 분류에서는 의외로 로지스틱 회귀가 강력해요.
간단하면서도 성능이 좋죠."
로지스틱 회귀는 이름에 "회귀"가 들어가지만 실제로는 분류 알고리즘입니다. 입력 데이터가 특정 클래스에 속할 확률을 계산하여 분류합니다.
마치 저울로 무게를 재서 "무거우면 긍정, 가벼우면 부정"이라고 판단하는 것과 비슷합니다. 텍스트 분류에서 뛰어난 성능과 빠른 속도를 자랑합니다.
다음 코드를 살펴봅시다.
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
# 학습 데이터와 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(
X_tfidf, labels, test_size=0.2, random_state=42
)
# 로지스틱 회귀 모델 생성 및 학습
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
# 예측 및 평가
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"정확도: {accuracy:.4f}")
print("\n분류 리포트:")
print(classification_report(y_test, y_pred, target_names=['negative', 'positive']))
"로지스틱 회귀요? 그거 너무 구식 아닌가요?" 김개발 씨가 의아한 표정을 지었습니다.
요즘은 딥러닝이 대세라고 들었기 때문입니다. 박시니어 씨가 설명을 시작했습니다.
"물론 딥러닝이 강력하죠. 하지만 모든 문제에 딥러닝이 필요한 건 아니에요.
텍스트 분류처럼 피처가 명확한 문제에서는 로지스틱 회귀가 훨씬 효율적입니다." 그렇다면 로지스틱 회귀는 어떻게 작동할까요? 핵심은 시그모이드 함수입니다.
이 함수는 어떤 숫자든 0과 1 사이의 값으로 변환합니다. 마치 온도계처럼, 입력값이 크면 1에 가깝고 작으면 0에 가깝습니다.
이 값을 확률로 해석하여 0.5보다 크면 긍정, 작으면 부정으로 분류합니다. 모델 학습 전에 반드시 해야 할 일이 있습니다.
바로 데이터 분할입니다. 전체 데이터를 학습용과 테스트용으로 나누는 것이죠.
왜 그래야 할까요? 학교 시험을 생각해보세요.
시험 문제를 미리 알고 공부하면 100점을 받을 수 있겠지만, 진짜 실력은 알 수 없습니다. 모델도 마찬가지입니다.
학습에 사용하지 않은 새로운 데이터로 평가해야 진짜 성능을 알 수 있습니다. train_test_split 함수는 이 분할을 자동으로 해줍니다.
test_size=0.2는 20%를 테스트용으로 사용하겠다는 의미입니다. random_state는 재현성을 위한 시드값입니다.
같은 시드를 사용하면 항상 같은 방식으로 데이터가 분할됩니다. 모델 학습은 놀랍도록 간단합니다.
LogisticRegression 객체를 만들고 fit 메서드를 호출하면 끝입니다. max_iter=1000은 최대 반복 횟수를 지정합니다.
데이터가 크면 이 값을 늘려야 할 수도 있습니다. 학습이 끝나면 predict 메서드로 예측을 수행합니다.
그리고 accuracy_score로 정확도를 계산합니다. classification_report는 더 자세한 성능 지표를 보여줍니다.
정밀도, 재현율, F1 점수까지 한눈에 확인할 수 있습니다. 김개발 씨는 결과를 보고 놀랐습니다.
"85%가 넘네요! 이렇게 간단한데 이 정도 성능이라니..." 박시니어 씨가 고개를 끄덕였습니다.
"항상 복잡한 것이 좋은 건 아니에요. 간단한 모델로 시작해서 기준선을 잡고, 필요할 때 복잡한 모델로 넘어가는 게 올바른 접근이에요."
실전 팁
💡 - C 파라미터로 규제 강도를 조절할 수 있습니다. 과적합이 의심되면 C 값을 줄여보세요
- class_weight='balanced'를 사용하면 불균형 데이터셋에서도 잘 작동합니다
5. n-gram 특성 추가
김개발 씨의 모델은 이미 85%의 정확도를 달성했습니다. 하지만 더 개선할 수 있는 방법이 있을까요?
박시니어 씨가 힌트를 주었습니다. "혹시 'not good'과 'good'을 구분할 수 있을까요?" 단어 하나만 보면 같은 'good'인데, 의미는 완전히 반대입니다.
이 문제를 해결하는 것이 바로 n-gram입니다.
n-gram은 연속된 n개의 단어를 하나의 단위로 묶는 기법입니다. 단어 하나만 보는 것(unigram)을 넘어서, 두 단어(bigram), 세 단어(trigram)의 조합까지 고려합니다.
마치 문장을 읽을 때 단어 하나하나가 아니라 어구 단위로 의미를 파악하는 것과 같습니다.
다음 코드를 살펴봅시다.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
# unigram만 사용 (기존 방식)
vectorizer_uni = TfidfVectorizer(max_features=5000, ngram_range=(1, 1))
X_uni = vectorizer_uni.fit_transform(df['review'])
# unigram + bigram 사용
vectorizer_bi = TfidfVectorizer(max_features=10000, ngram_range=(1, 2))
X_bi = vectorizer_bi.fit_transform(df['review'])
# 교차 검증으로 성능 비교
model = LogisticRegression(max_iter=1000)
scores_uni = cross_val_score(model, X_uni, labels, cv=5, scoring='accuracy')
scores_bi = cross_val_score(model, X_bi, labels, cv=5, scoring='accuracy')
print(f"Unigram 정확도: {scores_uni.mean():.4f} (+/- {scores_uni.std()*2:.4f})")
print(f"Bigram 정확도: {scores_bi.mean():.4f} (+/- {scores_bi.std()*2:.4f})")
"이 영화는 not good이었다"라는 리뷰가 있다고 합시다. 우리의 모델은 이 문장을 어떻게 분석할까요?
기존 방식대로라면 "good"이라는 단어가 있으니 긍정으로 분류할 가능성이 높습니다. 하지만 실제 의미는 부정이죠.
"not"과 "good"이 따로따로 분석되기 때문에 이런 문제가 생깁니다. n-gram은 이 문제의 해결책입니다.
n-gram에서 n은 연속된 단어의 개수를 의미합니다. **Unigram(n=1)**은 단어 하나하나를 개별적으로 봅니다.
"I", "love", "this", "movie" 각각이 하나의 피처가 됩니다. **Bigram(n=2)**은 연속된 두 단어를 함께 봅니다.
"I love", "love this", "this movie"가 피처가 됩니다. 이제 "not good"도 하나의 피처로 인식됩니다.
**Trigram(n=3)**은 세 단어를 함께 봅니다. "I love this", "love this movie" 같은 형태가 됩니다.
실제로 코드를 보면 **ngram_range=(1, 2)**라는 파라미터가 보입니다. 이것은 unigram과 bigram을 모두 사용하겠다는 의미입니다.
(1, 3)으로 설정하면 trigram까지 포함됩니다. 주의할 점이 있습니다.
n-gram을 사용하면 피처 수가 급격히 늘어납니다. 5,000개의 단어가 있다면 이론상 bigram은 2,500만 개가 될 수 있습니다.
그래서 max_features를 적절히 늘려주거나, 최소 출현 빈도를 설정하는 것이 좋습니다. 코드에서 cross_val_score를 사용한 것도 눈여겨보세요.
이것은 교차 검증이라는 기법입니다. 데이터를 5개의 조각으로 나누고, 각각 다른 조각을 테스트 데이터로 사용하여 5번 학습합니다.
이렇게 하면 단 한 번의 분할에 의존하지 않고 더 신뢰할 수 있는 성능 측정이 가능합니다. 김개발 씨가 실험 결과를 확인했습니다.
bigram을 추가하니 정확도가 약 2% 올랐습니다. "와, 정말 차이가 나네요!" "하지만 너무 욕심내면 안 돼요." 박시니어 씨가 경고했습니다.
"trigram, 4-gram까지 추가하면 과적합이 발생할 수 있어요. 대부분의 경우 bigram까지가 적당합니다."
실전 팁
💡 - min_df 파라미터로 최소 출현 빈도를 설정하면 희귀한 n-gram을 제외할 수 있습니다
- 메모리가 부족하면 max_features를 줄이거나 sparse matrix를 활용하세요
6. 감성 점수 시각화
모델이 완성되었습니다. 하지만 결과를 어떻게 보여줘야 할까요?
김개발 씨는 팀장님께 "정확도 87%입니다"라고 보고했지만, 팀장님은 고개를 갸웃거렸습니다. "그래서 실제로 어떻게 동작하는지 눈으로 볼 수 있을까요?" 이럴 때 필요한 것이 바로 시각화입니다.
시각화는 모델의 결과를 직관적으로 이해할 수 있게 만드는 과정입니다. 감성 점수의 분포, 혼동 행렬, 중요 단어 등을 그래프로 표현하면 모델의 동작 원리를 한눈에 파악할 수 있습니다.
숫자의 나열보다 그림 한 장이 더 강력한 설득력을 가집니다.
다음 코드를 살펴봅시다.
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
# 감성 점수(확률) 계산
y_proba = model.predict_proba(X_test)[:, 1]
# 감성 점수 분포 시각화
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 히스토그램으로 점수 분포 표시
axes[0].hist(y_proba[y_test == 0], bins=50, alpha=0.7, label='Negative', color='red')
axes[0].hist(y_proba[y_test == 1], bins=50, alpha=0.7, label='Positive', color='blue')
axes[0].set_xlabel('Sentiment Score')
axes[0].set_ylabel('Frequency')
axes[0].set_title('Sentiment Score Distribution')
axes[0].legend()
# 혼동 행렬 표시
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Negative', 'Positive'])
disp.plot(ax=axes[1], cmap='Blues')
axes[1].set_title('Confusion Matrix')
plt.tight_layout()
plt.savefig('sentiment_analysis_results.png', dpi=150)
plt.show()
박시니어 씨가 조언했습니다. "개발자끼리는 숫자만 봐도 되지만, 비개발자에게 설명할 때는 그림이 필수예요.
특히 머신러닝은 블랙박스처럼 느껴질 수 있거든요." 시각화는 단순히 예쁜 그림을 만드는 것이 아닙니다. 모델의 동작을 이해하고, 문제점을 발견하고, 다른 사람들을 설득하는 강력한 도구입니다.
먼저 감성 점수 분포를 살펴봅시다. predict_proba 메서드는 각 리뷰가 긍정일 확률을 반환합니다.
0에 가까우면 강한 부정, 1에 가까우면 강한 긍정입니다. 이상적인 모델이라면 어떤 분포를 보일까요?
부정 리뷰는 0 근처에, 긍정 리뷰는 1 근처에 몰려있어야 합니다. 두 분포가 완전히 분리되면 완벽한 분류가 가능합니다.
반대로 두 분포가 많이 겹치면 분류가 어렵다는 뜻입니다. 히스토그램을 그려보면 이 분포를 한눈에 확인할 수 있습니다.
김개발 씨의 모델은 두 분포가 꽤 잘 분리되어 있었습니다. 하지만 0.4에서 0.6 사이에 겹치는 영역도 보였습니다.
이 영역에 속한 리뷰들이 모델이 헷갈려하는 경계 케이스입니다. 두 번째 시각화는 혼동 행렬입니다.
혼동 행렬은 모델의 예측 결과를 네 가지로 분류합니다. True Positive: 긍정을 긍정으로 맞춤 True Negative: 부정을 부정으로 맞춤 False Positive: 부정인데 긍정으로 틀림 False Negative: 긍정인데 부정으로 틀림 혼동 행렬을 보면 모델이 어떤 종류의 실수를 더 많이 하는지 알 수 있습니다.
만약 False Negative가 많다면, 긍정 리뷰를 부정으로 잘못 분류하는 경향이 있다는 뜻입니다. matplotlib의 ConfusionMatrixDisplay를 사용하면 혼동 행렬을 깔끔하게 시각화할 수 있습니다.
숫자와 색상으로 한눈에 결과를 파악할 수 있습니다. 김개발 씨는 완성된 그래프를 팀장님께 보여드렸습니다.
"오, 이제 이해가 되네요. 대부분 정확하게 분류하고 있군요!" 마지막으로 그래프를 파일로 저장하는 것도 잊지 마세요.
savefig 함수로 PNG 파일로 저장할 수 있습니다. dpi=150은 해상도를 의미합니다.
발표자료에 넣을 거라면 300 이상을 추천합니다.
실전 팁
💡 - seaborn 라이브러리를 사용하면 더 세련된 시각화가 가능합니다
- 중요 단어를 시각화하려면 coef_ 속성에서 가중치가 높은 피처를 추출하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.