
Overview
감성분석은 텍스트에 담긴 주관적인 감정/의견/평가(긍정·부정 등) 를 자동으로 판별하는 기술입니다.
대표 활용처는 소셜미디어 반응 분석, 여론조사, 제품/영화 리뷰 분석, 고객 VOC 분석 등이 있습니다.
이번 글은 아래 2가지 축을 “입문자 관점에서” 확실히 구분해 정리합니다.
- 지도학습 기반 감성분석(분류 문제로 풀기)
- 감성 어휘 사전 기반 감성분석(룰/사전 기반)
- SentiWordNet
- VADER(소셜미디어 최적화 룰 기반)
그리고 IMDB 리뷰 데이터로 전처리 → 학습/평가 → 사전기반 평가까지 흐름을 한 번에 연결합니다.
1) 감성분석이란?
- 감성분석은 문서/문장/단어에 포함된 감정(positive/negative), 의견(opinion), 태도(attitude) 를 추정합니다.
- 가장 흔한 형태는 이진 분류(긍정=1, 부정=0) 이지만,
- 다중 감정(기쁨/분노/슬픔 등),
- 점수 회귀(감성 점수),
- 문장 단위 vs 문서 단위
로도 확장됩니다.
2) 감성분석 접근 2가지
2-1) 지도학습 기반(ML 분류)
핵심 아이디어:
“정답 라벨이 붙은 리뷰(긍정/부정)를 학습 → 새 리뷰의 라벨 예측”
즉, 텍스트 분류와 구조가 거의 동일합니다.
- 장점: 데이터만 충분하면 일반적으로 성능이 좋음
- 단점: 라벨 데이터 구축 비용이 큼(사람이 달아야 함), 도메인 바뀌면 성능 저하 가능
2-2) 감성 어휘 사전 기반(lexicon/rule)
핵심 아이디어:
“단어(또는 표현)마다 감성 점수/극성이 들어있는 사전(lexicon)을 이용해 문서 점수를 계산”
- 장점: 라벨 데이터 없이도 동작, 빠르게 베이스라인 구축 가능
- 단점: 문맥(반어/부정/강조/도메인 특화 표현) 처리 한계, 정확도가 지도학습보다 낮아질 수 있음
3) IMDB 영화 리뷰 긍/부정 예측(지도학습) — 데이터 로딩/전처리
아래 코드는 IMDB 라벨 데이터(labeledTrainData.tsv)를 읽고, HTML/특수문자/숫자 등을 제거하는 정규화 전처리입니다.
# 지도학습 기반 감성 분석 실습
import pandas as pd
# ✅ (목적) IMDB 라벨 데이터 로드: review(텍스트), sentiment(정답 라벨) 포함
review_df = pd.read_csv('./IMDB/labeledTrainData.tsv',
sep='\t')
review_df.head(5)
# ✅ (목적) 원문 리뷰 예시 출력: HTML 태그, 기호, 숫자 등이 섞여 있어 전처리가 필요함을 확인
print(review_df['review'][1]) # -> html 및 숫자 삭제
전처리는 정규표현식을 사용합니다.
import re #정규화
# ✅ (목적) <br /> 같은 HTML 개행 태그 제거(공백으로 치환)
review_df['review'] = review_df['review'].str.replace('<br />',' ')
# ✅ (핵심) 영어 알파벳(a-zA-Z)만 남기고 나머지는 공백 처리
# - 숫자/특수문자/기호 제거로 토큰의 잡음을 줄임
review_df['review'] = review_df['review'].apply( lambda x : re.sub("[^a-zA-Z]", " ", x) )
# ✅ (확인) 전처리 후 결과 출력: HTML과 숫자가 제거된 것을 확인
print(review_df['review'][1]) # -> html 및 숫자 삭제
왜 이런 전처리를 하나요?
- <br />, ", 숫자, 괄호 같은 것들은 “감성”과 직접 관련이 적고
- 단어 토큰을 쓸데없이 늘려서 벡터 공간만 커지고 희소성이 증가합니다.
- 특히 BoW/TF-IDF에서는 불필요 토큰이 늘어날수록 성능과 효율이 떨어질 수 있습니다.
4) 학습/테스트 데이터 분리
#학습 / 테스트 데이터 분리
from sklearn.model_selection import train_test_split
# ✅ (목적) sentiment가 정답(y), review 텍스트가 입력(X)
class_df = review_df['sentiment']
# ✅ (목적) id와 sentiment를 제외하고 feature(텍스트)만 남김
feature_df = review_df.drop(['id','sentiment'],
axis=1,
inplace=False)
# ✅ (핵심) 학습/테스트 분리
X_train, X_test, y_train, y_test = train_test_split(feature_df, class_df, test_size=0.3, random_state=156)
X_train.shape, X_test.shape
((17500, 1), (7500, 1))
포인트
- (17500, 1)은 “리뷰 문서 17500개, 컬럼 1개(review)”라는 뜻입니다.
- 실전에서는 train/test를 먼저 나눈 뒤, 벡터라이저는 반드시 train으로 fit 해야 합니다.
5) Pipeline으로 CountVectorizer + LogisticRegression 학습/평가
이 블록은 “전처리된 텍스트 → CountVectorizer로 벡터화 → 로지스틱 회귀 분류”를 파이프라인으로 묶습니다.
#pipe라인을 통해 Count 기반 피처 백터화 및 머신러닝 학습/예측/평가
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
# ✅ (목적) stop_words='english'로 흔한 불용어 제거
# ✅ (목적) ngram_range=(1,2)로 unigram+bigram을 함께 사용(표현력 증가)
# ✅ (목적) LogisticRegression C=10은 규제를 약하게 해서 더 복잡한 결정경계를 허용
pipeline = Pipeline([
('cnt_vect', CountVectorizer(stop_words='english', ngram_range=(1,2))),
('lr_clf',LogisticRegression(C=10))
])
# ✅ (학습) 벡터화 + 모델 학습을 한 번에 실행
pipeline.fit(X_train['review'],y_train)
# ✅ (예측) 테스트 리뷰 예측
pred = pipeline.predict(X_test['review'])
# ✅ (확률) ROC-AUC 계산을 위해 positive(1) 확률값 추출
pred_probs = pipeline.predict_proba(X_test['review'])[:, 1]
# ✅ (평가) Accuracy: 단순 맞춘 비율 / ROC-AUC: 임계치 변화에도 강건한 분류 품질 지표
print(f'Accuracy : {accuracy_score(y_test, pred)}')
print(f'ROC-AUC : {roc_auc_score(y_test, pred_probs)}')
Accuracy : 0.8848
ROC-AUC : 0.9509112508536308
Accuracy vs ROC-AUC (입문자 필수 개념)
- Accuracy(정확도): 0/1을 임계치 기준으로 잘 맞췄는지
- ROC-AUC: 임계치를 바꿔가며 “양성/음성 분리 능력” 자체를 평가
→ 특히 데이터가 불균형하거나, “확률의 품질”을 보고 싶을 때 유용합니다.
6) TF-IDF 기반 Pipeline (주의: 코드에 오타/실수가 있습니다)
사용자가 공유한 코드에는 아래 라인이 있습니다.
pipeline = Pipeline([
('tfidf_vect', CountVectorizer(stop_words='english', ngram_range=(1,2))),
('lr_clf',LogisticRegression(C=10))
])
여기서 단계 이름은 tfidf_vect인데 실제로 CountVectorizer를 쓰고 있습니다.
그래서 결과가 위 CountVectorizer와 동일하게 나옵니다(출력도 동일했죠).
✅ 의도대로 “TF-IDF”를 쓰려면 아래처럼 TfidfVectorizer가 들어가야 합니다.
(요청하신 “코드 순서 유지” 원칙 때문에 원문 코드는 그대로 설명하되, 정확한 개념을 위해 수정 방향을 함께 명시합니다.)
- 올바른 형태(개념상 정답):
pipeline = Pipeline([
('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2))),
('lr_clf', LogisticRegression(C=10))
])
TF-IDF를 쓰면 뭐가 달라지나요?
- “the, movie, film”처럼 거의 모든 리뷰에 등장하는 단어의 영향력이 줄고
- 특정 리뷰에서만 강하게 나타나는 단어(“masterpiece”, “terrible” 등)가 상대적으로 돋보입니다.
→ 특히 감성 분석에서 TF-IDF는 자주 좋은 베이스라인이 됩니다.
7) 감성 어휘 사전 기반 분석
지도학습이 “데이터로 학습”하는 방식이라면, 사전 기반은 “이미 구축된 감성 지식”을 사용합니다.
7-1) SentiWordNet 개념
- WordNet의 synset(동의어 집합) 개념에 감성 점수를 붙인 형태
- 각 synset은 보통 다음 점수를 가집니다.
- pos_score() : 긍정 성향
- neg_score() : 부정 성향
- obj_score() : 객관 성향
즉, 단어 하나가 아니라 단어+의미(sense) 단위로 감성이 정의됩니다.
7-2) WordNet synset 확인(“present” 예시)
import nltk
nltk.download('all')
from nltk.corpus import wordnet as wn
term = 'present'
# ✅ (목적) 단어 'present'가 가지는 의미(sense) 목록(synsets)을 가져옴
synsets = wn.synsets(term)
print('synsets() 반환 type :', type(synsets))
print('synsets() 반환 값 갯수:', len(synsets))
print('synsets() 반환 값 :', synsets)
왜 synset이 중요하죠?
“present”는
- 명사: 선물(present)
- 동사: 제시하다(present)
- 형용사: 현재의(present)
처럼 의미가 여러 개입니다.
감성 사전도 의미별로 점수가 달라질 수 있으므로 어떤 의미(synset)를 택하느냐가 결과에 영향을 줍니다.
7-3) SentiWordNet 점수 확인 예시
import nltk
from nltk.corpus import sentiwordnet as swn
father = swn.senti_synset('father.n.01')
print('father 긍정감성 지수: ', father.pos_score())
print('father 부정감성 지수: ', father.neg_score())
print('father 객관성 지수: ', father.obj_score())
print('\n')
fabulous = swn.senti_synset('fabulous.a.01')
print('fabulous 긍정감성 지수: ',fabulous .pos_score())
print('fabulous 부정감성 지수: ',fabulous .neg_score())
해석
- “father”는 감성 중립에 가까워 obj_score가 높게 나올 수 있고
- “fabulous”는 긍정 단어라 pos_score가 크게 나옵니다.
8) SentiWordNet 기반 문서 감성 분류 함수(swn_polarity)
이 함수는 전체 리뷰 텍스트를:
- 문장 분리 → 단어 토큰화 → 품사 태깅 → WordNet 품사 매핑 → 표제어화 → synset 선택 → senti 점수 합산
의 흐름으로 점수를 계산합니다.
from nltk.corpus import wordnet as wn
# ✅ (목적) NTLK PennTreebank POS 태그를 WordNet POS 태그로 변환
def penn_to_wn(tag):
if tag.startswith('J'):
return wn.ADJ
elif tag.startswith('N'):
return wn.NOUN
elif tag.startswith('R'):
return wn.ADV
elif tag.startswith('V'):
return wn.VERB
return
from nltk.stem import WordNetLemmatizer
from nltk.corpus import sentiwordnet as swn
from nltk import sent_tokenize, word_tokenize, pos_tag
def swn_polarity(text):
# ✅ (목적) 리뷰 전체의 감성 점수 누적 변수
sentiment = 0.0
tokens_count = 0
lemmatizer = WordNetLemmatizer()
raw_sentences = sent_tokenize(text)
# ✅ (핵심 흐름) 문장 단위로 처리 → 단어 토큰 → 품사 태깅 → 표제어화 → SentiWordNet 점수 합산
for raw_sentence in raw_sentences:
tagged_sentence = pos_tag(word_tokenize(raw_sentence))
for word , tag in tagged_sentence:
wn_tag = penn_to_wn(tag)
# ✅ (목적) 감성에 유의미한 품사(명사/형용사/부사) 중심으로 사용
if wn_tag not in (wn.NOUN , wn.ADJ, wn.ADV):
continue
# ✅ (목적) 표제어(lemma)로 변환해 사전 매칭 정확도 향상
lemma = lemmatizer.lemmatize(word, pos=wn_tag)
if not lemma:
continue
# ✅ (목적) lemma와 품사로 synset 후보를 찾음
synsets = wn.synsets(lemma , pos=wn_tag)
if not synsets:
continue
# ✅ (단순화) 첫 번째 synset만 사용 (여기서 오차가 생길 수 있음)
synset = synsets[0]
swn_synset = swn.senti_synset(synset.name())
# ✅ (핵심) 긍정은 +, 부정은 -로 합산해 문서 감성 점수로 만듦
sentiment += (swn_synset.pos_score() - swn_synset.neg_score())
tokens_count += 1
if not tokens_count:
return 0
# ✅ (결정 규칙) 0 이상이면 긍정(1), 아니면 부정(0)
if sentiment >= 0 :
return 1
return 0
입문자 관점 “왜 성능이 떨어질 수 있나?”
- synsets[0]만 쓰면 의미 중의성이 제대로 처리되지 않습니다.
- “not good”(부정) 같은 부정어 처리가 약합니다.
- 리뷰 도메인(영화) 특유의 표현을 사전에 다 반영하지 못합니다.
8-1) SentiWordNet 기반 예측 및 평가
review_df['preds'] = review_df['review'].apply( lambda x : swn_polarity(x) )
y_target = review_df['sentiment'].values
preds = review_df['preds'].values
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score
from sklearn.metrics import recall_score, f1_score, roc_auc_score
print(confusion_matrix( y_target, preds))
print("정확도:", accuracy_score(y_target , preds))
print("정밀도:", precision_score(y_target , preds))
print("재현율:", recall_score(y_target, preds))
[[7669 4831]
[3635 8865]]
정확도: 0.66136
정밀도: 0.6472692757009346
재현율: 0.7092
해석(핵심만)
- 정확도 ~0.66 수준으로, 지도학습(0.88+)보다 낮습니다.
- 사전 기반은 “라벨 없이도 돌아간다”는 장점이 있지만, 문맥 처리 한계로 성능이 제한될 수 있습니다.
9) VADER 기반 감성 분석
VADER는 특히 소셜미디어 문장(짧고 감탄/대문자/이모티콘/강조가 많은 문장)에 강한 룰 기반 분석기입니다.
- polarity_scores()가 다음을 반환합니다.
- neg, neu, pos : 각 비율
- compound : 최종 감성 점수(-1 ~ +1)
#Vader lexicon을 이용한 Sentimen Anlysis
from nltk.sentiment.vader import SentimentIntensityAnalyzer
senti_analyzer = SentimentIntensityAnalyzer()
senti_scores = senti_analyzer.polarity_scores(review_df['review'][0])
print(senti_scores)
{'neg': 0.13, 'neu': 0.743, 'pos': 0.127, 'compound': -0.7943}
임계값(threshold)로 긍/부정을 나누는 함수:
def vader_polarity(review, threshold=0.1):
analyzer = SentimentIntensityAnalyzer()
scores = analyzer.polarity_scores(review)
# ✅ compound 값이 threshold 이상이면 긍정(1), 아니면 부정(0)
agg_score = scores['compound']
final_sentiment = 1 if agg_score >= threshold else 0
return final_sentiment
review_df['vader_preds'] = review_df['review'].apply(lambda x : vader_polarity(x,0.1))
y_target = review_df['sentiment'].values
vader_preds = review_df['vader_preds'].values
print('#### VADER 예측 성능 평가 ####')
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score
from sklearn.metrics import recall_score, f1_score, roc_auc_score
print(confusion_matrix( y_target, vader_preds))
print("정확도:", accuracy_score(y_target , vader_preds))
print("정밀도:", precision_score(y_target , vader_preds))
print("재현율:", recall_score(y_target, vader_preds))
VADER의 특징(실전 감각)
- “완전한 문장 규칙 + 감성 사전” 조합이라 빠르고 간편합니다.
- 다만 IMDB처럼 긴 리뷰(서술형)에서는 지도학습에 비해 한계가 있을 수 있습니다.
- threshold(예: 0.1)는 데이터에 따라 조정할 수 있습니다.
10) 정리: 언제 무엇을 쓰면 좋을까?
지도학습이 유리한 경우
- 라벨 데이터가 있고(또는 만들 수 있고)
- 목표 도메인(영화/쇼핑/정치 등)이 명확하며
- 높은 정확도가 필요할 때
✅ 추천 조합(베이스라인)
- TfidfVectorizer + LogisticRegression
- 필요 시 ngram, max_df, min_df, C 튜닝
감성사전/룰 기반이 유리한 경우
- 라벨이 없고 빠르게 “대략적 감성 흐름”만 보고 싶을 때
- 프로토타입/대시보드/실시간 모니터링 같은 상황
✅ 추천
- 짧은 문장/소셜 텍스트: VADER
- WordNet 기반 실험/연구용: SentiWordNet(단, 중의성 처리 주의)
감성분석은 크게
- 지도학습 기반(텍스트 분류) 과
- 감성사전 기반(룰/lexicon) 으로 나뉘며,
IMDB 리뷰처럼 “라벨이 존재하고 문장이 긴 리뷰”에서는
대부분 지도학습 기반(TF-IDF + 로지스틱 회귀) 이 강력한 베이스라인이 됩니다.
반면, 라벨 없이도 감성 흐름을 빠르게 보고 싶다면
SentiWordNet/VADER 같은 사전 기반 방법이 유용한 출발점이 됩니다.
'Programming' 카테고리의 다른 글
| Mercari Price Suggestion— 대규모 텍스트 + 카테고리 데이터를 활용한 가격 예측 실전 프로젝트 (0) | 2026.02.14 |
|---|---|
| 토픽 모델링(Topic Modeling) 완전 정리— LDA 이론부터 20 Newsgroups 실습까지 (0) | 2026.02.14 |
| CountVectorizer부터 Pipeline/GridSearchCV까지: 뉴스그룹 분류로 배우는 피처 벡터화 + 희소행렬(COO/CSR) (0) | 2026.02.14 |
| NLP(Natural Language Processing)와 텍스트 전처리 핵심 정리― 개념부터 NLTK 실습까지 한 번에 이해하기 (0) | 2026.02.14 |
| 텍스트 분석 & NLP 핵심 요약 (완전 통합 정리) (0) | 2026.02.14 |