Ver3.0 ๐ญ ํ ์คํธ ๊ฐ์ ๋ถ์ ์์ค์ฝ๋ ์์ ์ ๋ณต

๐ญ ํ ์คํธ ๊ฐ์ ๋ถ์ ์์ค์ฝ๋ ์์ ์ ๋ณต
NLP๋ก ํ ์คํธ ์ ๊ฐ์ ์ ์ฝ์ด๋ด๋ ๋ง๋ฒ ๊ฐ์ ๊ธฐ์ !
์๋ ! ์ค๋์ ์ ๋ง ์ฌ๋ฏธ์๋ ์ฃผ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ๋ฐ๋ก ํ ์คํธ ๊ฐ์ ๋ถ์(Sentiment Analysis)์ด์ผ! ๐ฏ
์์ฆ SNS์ ์ฌ๋ผ์ค๋ ์๋ง์ ๋๊ธ๋ค, ๊ณ ๊ฐ ๋ฆฌ๋ทฐ, ์ฑํ ๋ฉ์์ง... ์ด๋ฐ ํ ์คํธ๋ค ์์ ์จ์ด์๋ ๊ฐ์ ์ ์ปดํจํฐ๊ฐ ์๋์ผ๋ก ๋ถ์ํ ์ ์๋ค๋ฉด ์ผ๋ง๋ ์ข์๊น? ์ค์ ๋ก ์ด๊ฒ ๊ฐ๋ฅํด! ๊ทธ๊ฒ๋ ์๊ฐ๋ณด๋ค ํจ์ฌ ์ฝ๊ฒ ๋ง์ด์ผ. ๐
์ด ๊ธ์์๋ ์ค์ ์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ ๊ฐ์ ๋ถ์ ์์ค์ฝ๋๋ฅผ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ผ๋ก ์๊ฐํ ๊ฑฐ์ผ. ์ด๋ณด์๋ ๋ฐ๋ผํ ์ ์๊ฒ ์น์ ํ๊ฒ ์ค๋ช ํ ํ ๋๊น ๊ฑฑ์ ๋ง!
๐ค ๊ฐ์ ๋ถ์์ด ๋ญ๊ธธ๋?
๊ฐ์ ๋ถ์(Sentiment Analysis)์ ์์ฐ์ด ์ฒ๋ฆฌ(NLP)์ ํ ๋ถ์ผ๋ก, ํ ์คํธ์ ๋ด๊ธด ๊ฐ์ ์ด๋ ์๊ฒฌ์ ์๋์ผ๋ก ํ์ ํ๋ ๊ธฐ์ ์ด์ผ. ์ฝ๊ฒ ๋งํ๋ฉด ์ปดํจํฐ๊ฐ ๊ธ์ ์ฝ๊ณ "์ด ์ฌ๋ ์ง๊ธ ๊ธฐ๋ถ ์ข์๊ฐ? ๋์๊ฐ?"๋ฅผ ํ๋จํ๋ ๊ฑฐ์ง! ๐
๐ก ์ค์ํ ํ์ฉ ์์
โข ๊ณ ๊ฐ ๋ฆฌ๋ทฐ ๋ถ์: ์์ฒ ๊ฐ์ ์ํ ๋ฆฌ๋ทฐ๋ฅผ ์๋์ผ๋ก ๋ถ์ํด์ ๊ธ์ /๋ถ์ ๋น์จ ํ์
โข ์์
๋ฏธ๋์ด ๋ชจ๋ํฐ๋ง: ๋ธ๋๋์ ๋ํ ๋์ค์ ๊ฐ์ ์ถ์
โข ๊ณ ๊ฐ ์๋น์ค: ๋ถ๋ง ๊ณ ๊ฐ์ ๋น ๋ฅด๊ฒ ์๋ณํด์ ์ฐ์ ๋์
โข ์ฃผ์ ์์ฅ ์์ธก: ๋ด์ค ๊ธฐ์ฌ์ ๊ฐ์ ์ ๋ถ์ํด์ ์์ฅ ๋ํฅ ์์ธก
โข ์ฑ๋ด ๊ฐ์ : ์ฌ์ฉ์์ ๊ฐ์ ์ํ์ ๋ง์ถฐ ์๋ต ์กฐ์
์ค์ ๋ก ๋ง์ ๊ธฐ์ ๋ค์ด ์ด ๊ธฐ์ ์ ํ์ฉํ๊ณ ์์ด. ์๋ง์กด, ๋ทํ๋ฆญ์ค, ํธ์ํฐ ๊ฐ์ ๋๊ธฐ์ ์ ๋ฌผ๋ก ์ด๊ณ , ์คํํธ์ ๋ค๋ ๊ณ ๊ฐ ํผ๋๋ฐฑ ๋ถ์์ ์ ๊ทน ํ์ฉํ๊ณ ์์ง! ๐
๐ ๏ธ ๊ฐ์ ๋ถ์์ ๊ธฐ๋ณธ ์๋ฆฌ
๊ฐ์ ๋ถ์์ ํฌ๊ฒ ์ธ ๊ฐ์ง ์ ๊ทผ ๋ฐฉ์์ด ์์ด:
| ๋ฐฉ์ | ์ค๋ช | ์ฅ์ | ๋จ์ |
|---|---|---|---|
| ๊ท์น ๊ธฐ๋ฐ | ๊ฐ์ ๋จ์ด ์ฌ์ ์ ๋ง๋ค์ด์ ๋งค์นญ | ๊ตฌํ ๊ฐ๋จ, ํด์ ์ฌ์ | ์ ํ๋ ๋ฎ์, ๋ฌธ๋งฅ ํ์ ์ด๋ ค์ |
| ๋จธ์ ๋ฌ๋ | ํ์ต ๋ฐ์ดํฐ๋ก ๋ชจ๋ธ ํ๋ จ | ๋์ ์ ํ๋, ํ์ฅ์ฑ ์ข์ | ๋ง์ ๋ฐ์ดํฐ ํ์ |
| ๋ฅ๋ฌ๋ | ์ ๊ฒฝ๋ง์ผ๋ก ๋ณต์กํ ํจํด ํ์ต | ์ต๊ณ ์์ค ์ ํ๋ | ๊ณ์ฐ ๋น์ฉ ๋์, ๋ฐ์ดํฐ ๋ง์ด ํ์ |
์ค๋์ ์ด ์ธ ๊ฐ์ง ๋ฐฉ์์ ๋ชจ๋ ์ค์ ์ฝ๋๋ก ๊ตฌํํด๋ณผ ๊ฑฐ์ผ! ๐
๐ Python์ผ๋ก ์์ํ๋ ๊ฐ์ ๋ถ์
๊ฐ์ฅ ๋จผ์ Python์ ์ด์ฉํ ๊ธฐ๋ณธ์ ์ธ ๊ฐ์ ๋ถ์๋ถํฐ ์์ํด๋ณด์. Python์ NLP ์์ ์ ์ต์ ํ๋ ์ธ์ด๊ฑฐ๋ ! ๐ฏ
๐ฆ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
๋จผ์ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ค์นํด์ผ ํด. ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํด์ค:
pip install nltk textblob vaderSentiment transformers torch
pip install konlpy # ํ๊ธ ๋ถ์์ฉ
pip install pandas numpy matplotlib
๐ก Tip: ๊ฐ์ํ๊ฒฝ(venv)์ ๋ง๋ค์ด์ ์์ ํ๋ ๊ฑธ ์ถ์ฒํด! ํ๋ก์ ํธ๋ณ๋ก ๋ ๋ฆฝ์ ์ธ ํ๊ฒฝ์ ์ ์งํ ์ ์๊ฑฐ๋ .
๐ ๋ฐฉ๋ฒ 1: TextBlob์ผ๋ก ๊ฐ๋จํ๊ฒ ์์ํ๊ธฐ
TextBlob์ ์ ๋ง ์ด๋ณด์ ์นํ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ผ. ๋ช ์ค์ ์ฝ๋๋ง์ผ๋ก ๊ฐ์ ๋ถ์์ด ๊ฐ๋ฅํด! ๐
from textblob import TextBlob
def analyze_sentiment_textblob(text):
"""
TextBlob์ ์ด์ฉํ ๊ฐ์ ๋ถ์
polarity: -1(๋ถ์ ) ~ 1(๊ธ์ )
subjectivity: 0(๊ฐ๊ด์ ) ~ 1(์ฃผ๊ด์ )
"""
blob = TextBlob(text)
polarity = blob.sentiment.polarity
subjectivity = blob.sentiment.subjectivity
# ๊ฐ์ ๋ถ๋ฅ
if polarity > 0.1:
sentiment = "๊ธ์ ๐"
elif polarity < -0.1:
sentiment = "๋ถ์ ๐ข"
else:
sentiment = "์ค๋ฆฝ ๐"
return {
'text': text,
'sentiment': sentiment,
'polarity': round(polarity, 3),
'subjectivity': round(subjectivity, 3)
}
# ํ
์คํธ ์์
texts = [
"I absolutely love this product! It's amazing!",
"This is the worst experience ever. Terrible!",
"The weather is okay today.",
"I'm so happy and excited about this opportunity!"
]
print("=" * 60)
print("TextBlob ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ")
print("=" * 60)
for text in texts:
result = analyze_sentiment_textblob(text)
print(f"\nํ
์คํธ: {result['text']}")
print(f"๊ฐ์ : {result['sentiment']}")
print(f"๊ทน์ฑ ์ ์: {result['polarity']}")
print(f"์ฃผ๊ด์ฑ ์ ์: {result['subjectivity']}")
print("-" * 60)
์ด ์ฝ๋๋ฅผ ์คํํ๋ฉด ๊ฐ ๋ฌธ์ฅ์ ๊ฐ์ ์ ์๋์ผ๋ก ๋ถ์ํด์ค. polarity๋ ๊ธ์ /๋ถ์ ์ ์ ๋๋ฅผ, subjectivity๋ ์ฃผ๊ด์ ์ธ์ง ๊ฐ๊ด์ ์ธ์ง๋ฅผ ๋ํ๋ด๋ ์งํ์ผ! ๐
๐ ์ฝ๋ ์ค๋ช
โข TextBlob(text): ํ
์คํธ๋ฅผ TextBlob ๊ฐ์ฒด๋ก ๋ณํ
โข sentiment.polarity: -1(๋งค์ฐ ๋ถ์ )์์ 1(๋งค์ฐ ๊ธ์ ) ์ฌ์ด์ ๊ฐ
โข sentiment.subjectivity: 0(์์ ๊ฐ๊ด์ )์์ 1(์์ ์ฃผ๊ด์ ) ์ฌ์ด์ ๊ฐ
โข ์๊ณ๊ฐ 0.1์ ๊ธฐ์ค์ผ๋ก ๊ธ์ /์ค๋ฆฝ/๋ถ์ ๋ถ๋ฅ
๐ฏ ๋ฐฉ๋ฒ 2: VADER - ์์ ๋ฏธ๋์ด ํนํ ๋ถ์๊ธฐ
VADER(Valence Aware Dictionary and sEntiment Reasoner)๋ ์์ ๋ฏธ๋์ด ํ ์คํธ ๋ถ์์ ํนํ๋ ๋๊ตฌ์ผ. ์ด๋ชจํฐ์ฝ, ๋๋ฌธ์, ๋๋ํ ๊ฐ์ ์์๋ค๋ ๊ณ ๋ คํด์ ๋ถ์ํด! ๐ฅ
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
import pandas as pd
def analyze_sentiment_vader(text):
"""
VADER๋ฅผ ์ด์ฉํ ๊ฐ์ ๋ถ์
์์
๋ฏธ๋์ด ํ
์คํธ์ ์ต์ ํ๋จ
"""
analyzer = SentimentIntensityAnalyzer()
scores = analyzer.polarity_scores(text)
# compound ์ ์๋ก ๊ฐ์ ๋ถ๋ฅ
compound = scores['compound']
if compound >= 0.05:
sentiment = "๊ธ์ ๐"
elif compound <= -0.05:
sentiment = "๋ถ์ ๐ข"
else:
sentiment = "์ค๋ฆฝ ๐"
return {
'text': text,
'sentiment': sentiment,
'positive': scores['pos'],
'neutral': scores['neu'],
'negative': scores['neg'],
'compound': scores['compound']
}
# ์์
๋ฏธ๋์ด ์คํ์ผ ํ
์คํธ ํ
์คํธ
social_texts = [
"OMG!!! This is AMAZING!!! ๐๐๐",
"I hate this so much ๐ก",
"It's okay, nothing special",
"BEST DAY EVER!!!! ๐๐",
"Disappointed... not what I expected ๐"
]
print("\n" + "=" * 60)
print("VADER ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ (์์
๋ฏธ๋์ด ํนํ)")
print("=" * 60)
results = []
for text in social_texts:
result = analyze_sentiment_vader(text)
results.append(result)
print(f"\nํ
์คํธ: {result['text']}")
print(f"๊ฐ์ : {result['sentiment']}")
print(f"๊ธ์ : {result['positive']:.3f} | "
f"์ค๋ฆฝ: {result['neutral']:.3f} | "
f"๋ถ์ : {result['negative']:.3f}")
print(f"์ข
ํฉ ์ ์: {result['compound']:.3f}")
print("-" * 60)
# DataFrame์ผ๋ก ์ ๋ฆฌ
df = pd.DataFrame(results)
print("\n๐ ๊ฒฐ๊ณผ ์์ฝ:")
print(df[['sentiment', 'compound']].to_string(index=False))
๐ก VADER์ ํน๋ณํ ์ :
โข ๋๋ฌธ์ ์ฌ์ฉ(AMAZING vs amazing) ๊ฐ์ง
โข ๋๋ํ ๊ฐ์(! vs !!!) ๊ณ ๋ ค
โข ์ด๋ชจํฐ์ฝ ํด์ ๊ฐ๋ฅ
โข ๋ถ์ ์ด(not, never) ์ฒ๋ฆฌ ์ฐ์
โข ์ ๋ ๋ถ์ฌ(very, extremely) ์ธ์
VADER๋ ํนํ ํธ์ํฐ, ์ธ์คํ๊ทธ๋จ, ํ์ด์ค๋ถ ๊ฐ์ SNS ๋ฐ์ดํฐ ๋ถ์์ ๊ฐ๋ ฅํด! ์ค์ ๋ก ๋ง์ ๊ธฐ์ ๋ค์ด ์์ ๋ฏธ๋์ด ๋ชจ๋ํฐ๋ง์ VADER๋ฅผ ์ฌ์ฉํ๊ณ ์์ด. ๐ญ
๐ง ๋ฐฉ๋ฒ 3: ํธ๋์คํฌ๋จธ ๋ชจ๋ธ๋ก ๊ณ ๊ธ ๋ถ์ํ๊ธฐ
์ด์ ์ง์ง ๊ฐ๋ ฅํ ๋ ์์ ์๊ฐํ ๊ฒ! Hugging Face์ ํธ๋์คํฌ๋จธ ๋ชจ๋ธ์ ์ฌ์ฉํ๋ฉด ์ต์ ๋ฅ๋ฌ๋ ๊ธฐ์ ๋ก ๊ฐ์ ์ ๋ถ์ํ ์ ์์ด. ๐
from transformers import pipeline
import warnings
warnings.filterwarnings('ignore')
def analyze_sentiment_transformer(text, model_name='distilbert-base-uncased-finetuned-sst-2-english'):
"""
ํธ๋์คํฌ๋จธ ๋ชจ๋ธ์ ์ด์ฉํ ๊ณ ๊ธ ๊ฐ์ ๋ถ์
์ฌ์ ํ๋ จ๋ BERT ๊ธฐ๋ฐ ๋ชจ๋ธ ์ฌ์ฉ
"""
# ๊ฐ์ ๋ถ์ ํ์ดํ๋ผ์ธ ์์ฑ
sentiment_pipeline = pipeline(
"sentiment-analysis",
model=model_name,
device=-1 # CPU ์ฌ์ฉ (GPU๋ 0)
)
# ๋ถ์ ์คํ
result = sentiment_pipeline(text)[0]
# ์ด๋ชจ์ง ๋งคํ
emoji_map = {
'POSITIVE': '๐',
'NEGATIVE': '๐ข',
'NEUTRAL': '๐'
}
label = result['label']
score = result['score']
return {
'text': text,
'sentiment': f"{label} {emoji_map.get(label, '๐')}",
'confidence': round(score * 100, 2)
}
# ๋ณต์กํ ๋ฌธ์ฅ ํ
์คํธ
complex_texts = [
"While the service was slow, the food quality exceeded my expectations.",
"I wanted to love this, but it just didn't work for me.",
"The product is good, but the price is too high for what you get.",
"Absolutely phenomenal experience from start to finish!",
"Not bad, but I've seen better options elsewhere."
]
print("\n" + "=" * 60)
print("ํธ๋์คํฌ๋จธ ๋ชจ๋ธ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ")
print("=" * 60)
for text in complex_texts:
result = analyze_sentiment_transformer(text)
print(f"\nํ
์คํธ: {result['text']}")
print(f"๊ฐ์ : {result['sentiment']}")
print(f"์ ๋ขฐ๋: {result['confidence']}%")
print("-" * 60)
ํธ๋์คํฌ๋จธ ๋ชจ๋ธ์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ ๋ฌธ๋งฅ์ ์ดํดํ๋ค๋ ๊ฑฐ์ผ! "์ข๊ธด ํ๋ฐ..."์ฒ๋ผ ๋ณต์กํ ๊ฐ์ ๋ ์ ํ์ ํด๋ด์ง. ๐ฏ
โ ๏ธ ์ฃผ์์ฌํญ: ํธ๋์คํฌ๋จธ ๋ชจ๋ธ์ ์ฒ์ ์คํํ ๋ ๋ชจ๋ธ์ ๋ค์ด๋ก๋ํ๊ธฐ ๋๋ฌธ์ ์๊ฐ์ด ์ข ๊ฑธ๋ฆด ์ ์์ด. ์ธํฐ๋ท ์ฐ๊ฒฐ์ด ํ์ํ๊ณ , ๋ชจ๋ธ ํฌ๊ธฐ์ ๋ฐ๋ผ ์๋ฐฑ MB์์ ์ GB๊น์ง ๋ค์ด๋ก๋๋ ์ ์์ด!
๐ฐ๐ท ํ๊ธ ๊ฐ์ ๋ถ์ ๊ตฌํํ๊ธฐ
์์ด๋ง ๋ถ์ํ๋ฉด ๋ญํด? ์ฐ๋ฆฌ๋ ํ๊ตญ ์ฌ๋์ด์์! ํ๊ธ ๊ฐ์ ๋ถ์๋ ๋น์ฐํ ๊ฐ๋ฅํด. ๐
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import torch.nn.functional as F
class KoreanSentimentAnalyzer:
"""
ํ๊ตญ์ด ๊ฐ์ ๋ถ์ ํด๋์ค
KoBERT ๊ธฐ๋ฐ ๋ชจ๋ธ ์ฌ์ฉ
"""
def __init__(self):
# ํ๊ตญ์ด ๊ฐ์ ๋ถ์ ๋ชจ๋ธ ๋ก๋
model_name = "beomi/kcbert-base"
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(
"beomi/KcELECTRA-base-v2022"
)
self.model.eval()
def analyze(self, text):
"""ํ
์คํธ ๊ฐ์ ๋ถ์"""
# ํ ํฐํ
inputs = self.tokenizer(
text,
return_tensors="pt",
truncation=True,
max_length=512,
padding=True
)
# ์์ธก
with torch.no_grad():
outputs = self.model(**inputs)
predictions = F.softmax(outputs.logits, dim=-1)
# ๊ฒฐ๊ณผ ํด์
scores = predictions[0].tolist()
labels = ['๋ถ์ ๐ข', '์ค๋ฆฝ ๐', '๊ธ์ ๐']
max_idx = scores.index(max(scores))
sentiment = labels[max_idx]
confidence = max(scores) * 100
return {
'text': text,
'sentiment': sentiment,
'confidence': round(confidence, 2),
'scores': {
'๋ถ์ ': round(scores[0] * 100, 2),
'์ค๋ฆฝ': round(scores[1] * 100, 2),
'๊ธ์ ': round(scores[2] * 100, 2)
}
}
# ๊ฐ๋จํ ํ๊ธ ๊ฐ์ ๋ถ์ (KoNLPy ์ฌ์ฉ)
from konlpy.tag import Okt
def simple_korean_sentiment(text):
"""
๊ฐ๋จํ ํ๊ธ ๊ฐ์ ๋ถ์ (์ฌ์ ๊ธฐ๋ฐ)
"""
okt = Okt()
# ๊ธ์ /๋ถ์ ๋จ์ด ์ฌ์
positive_words = [
'์ข๋ค', 'ํ๋ฅญํ๋ค', '์ต๊ณ ', '๊ฐ์ฌ', 'ํ๋ณต', '์ฌ๋', '์๋ฒฝ',
'๋ฉ์ง๋ค', '์์๋ค', '๋ง์๋ค', '์ฌ๋ฏธ์๋ค', '์ ๋๋ค', '๊ธฐ์๋ค'
]
negative_words = [
'๋์๋ค', '์ซ๋ค', '์ต์
', '์ค๋ง', '์ฌํ๋ค', 'ํ๋๋ค', '์ง์ฆ',
'๋ณ๋ก', 'ํํธ์๋ค', '์ง๋ฃจํ๋ค', '๋ถํธํ๋ค', 'ํํ', '์์ฝ๋ค'
]
# ํํ์ ๋ถ์
morphs = okt.morphs(text)
# ์ ์ ๊ณ์ฐ
pos_score = sum(1 for word in morphs if any(pw in word for pw in positive_words))
neg_score = sum(1 for word in morphs if any(nw in word for nw in negative_words))
# ๊ฐ์ ํ๋จ
if pos_score > neg_score:
sentiment = "๊ธ์ ๐"
elif neg_score > pos_score:
sentiment = "๋ถ์ ๐ข"
else:
sentiment = "์ค๋ฆฝ ๐"
return {
'text': text,
'sentiment': sentiment,
'positive_score': pos_score,
'negative_score': neg_score
}
# ํ๊ธ ํ
์คํธ
korean_texts = [
"์ด ์ํ ์ ๋ง ์ฌ๋ฏธ์๊ณ ๊ฐ๋์ ์ด์์! ์ต๊ณ ์์!",
"์๋น์ค๊ฐ ๋๋ฌด ๋ณ๋ก์์ด์. ๋ค์๋ ์ ๊ฐ ๊ฒ ๊ฐ์์.",
"๊ทธ๋ฅ ๊ทธ๋์. ํน๋ณํ ๊ฑด ์์์ด์.",
"๊ฐ๊ฒฉ ๋๋น ํ๋ฅญํ ํ์ง์ด๋ค์. ๊ฐ๋ ฅ ์ถ์ฒํฉ๋๋ค!",
"๊ธฐ๋ํ๋๋ฐ ์ค๋ง์ค๋ฌ์์. ์์ฝ๋ค์."
]
print("\n" + "=" * 60)
print("ํ๊ธ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ")
print("=" * 60)
for text in korean_texts:
result = simple_korean_sentiment(text)
print(f"\nํ
์คํธ: {result['text']}")
print(f"๊ฐ์ : {result['sentiment']}")
print(f"๊ธ์ ๋จ์ด: {result['positive_score']}๊ฐ | "
f"๋ถ์ ๋จ์ด: {result['negative_score']}๊ฐ")
print("-" * 60)
ํ๊ธ ๋ถ์์ ์์ด๋ณด๋ค ์กฐ๊ธ ๋ ๋ณต์กํด. ์๋ํ๋ฉด ํ๊ตญ์ด๋ ์กฐ์ฌ, ์ด๋ฏธ ๋ณํ๊ฐ ๋ง๊ฑฐ๋ ! ๊ทธ๋์ ํํ์ ๋ถ์์ด ํ์์ผ. ๐
๐ ์ค์ ํ๋ก์ ํธ: ๋ฆฌ๋ทฐ ๋ฐ์ดํฐ ๋๋ ๋ถ์
์ด์ ๋ฐฐ์ด ๊ฑธ ์ค์ ์ ์ ์ฉํด๋ณด์! ์ค์ ๋ก ์๋ฐฑ, ์์ฒ ๊ฐ์ ๋ฆฌ๋ทฐ๋ฅผ ํ ๋ฒ์ ๋ถ์ํ๋ ์์คํ ์ ๋ง๋ค์ด๋ณผ๊ฒ. ๐ช
import pandas as pd
import matplotlib.pyplot as plt
from collections import Counter
import re
class ReviewSentimentAnalyzer:
"""
๋๋ ๋ฆฌ๋ทฐ ๊ฐ์ ๋ถ์ ์์คํ
"""
def __init__(self, analyzer_type='vader'):
self.analyzer_type = analyzer_type
if analyzer_type == 'vader':
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
self.analyzer = SentimentIntensityAnalyzer()
elif analyzer_type == 'textblob':
from textblob import TextBlob
self.analyzer = TextBlob
def preprocess_text(self, text):
"""ํ
์คํธ ์ ์ฒ๋ฆฌ"""
# ์๋ฌธ์ ๋ณํ
text = text.lower()
# ํน์๋ฌธ์ ์ ๊ฑฐ (์ด๋ชจํฐ์ฝ์ ์ ์ง)
text = re.sub(r'[^\w\s!?.,๐๐ข๐ก๐๐]', '', text)
# ๊ณต๋ฐฑ ์ ๋ฆฌ
text = ' '.join(text.split())
return text
def analyze_single(self, text):
"""๋จ์ผ ํ
์คํธ ๋ถ์"""
text = self.preprocess_text(text)
if self.analyzer_type == 'vader':
scores = self.analyzer.polarity_scores(text)
compound = scores['compound']
if compound >= 0.05:
sentiment = 'positive'
elif compound <= -0.05:
sentiment = 'negative'
else:
sentiment = 'neutral'
return {
'sentiment': sentiment,
'score': compound,
'details': scores
}
elif self.analyzer_type == 'textblob':
blob = self.analyzer(text)
polarity = blob.sentiment.polarity
if polarity > 0.1:
sentiment = 'positive'
elif polarity < -0.1:
sentiment = 'negative'
else:
sentiment = 'neutral'
return {
'sentiment': sentiment,
'score': polarity,
'details': {'polarity': polarity}
}
def analyze_batch(self, texts):
"""๋๋ ํ
์คํธ ๋ถ์"""
results = []
for text in texts:
result = self.analyze_single(text)
result['text'] = text
results.append(result)
return pd.DataFrame(results)
def generate_report(self, df):
"""๋ถ์ ๋ฆฌํฌํธ ์์ฑ"""
report = {
'total_reviews': len(df),
'positive_count': len(df[df['sentiment'] == 'positive']),
'negative_count': len(df[df['sentiment'] == 'negative']),
'neutral_count': len(df[df['sentiment'] == 'neutral']),
'average_score': df['score'].mean(),
'positive_ratio': len(df[df['sentiment'] == 'positive']) / len(df) * 100,
'negative_ratio': len(df[df['sentiment'] == 'negative']) / len(df) * 100
}
return report
# ์ํ ๋ฆฌ๋ทฐ ๋ฐ์ดํฐ
sample_reviews = [
"This product exceeded all my expectations! Absolutely love it!",
"Terrible quality. Broke after one day. Very disappointed.",
"It's okay, nothing special but does the job.",
"Best purchase ever! Highly recommend to everyone!",
"Not worth the money. Poor customer service too.",
"Good value for the price. Satisfied with my purchase.",
"Worst experience. Will never buy again.",
"Amazing quality and fast shipping! Very happy!",
"Average product. Expected more for this price.",
"Fantastic! Exactly what I was looking for!"
] * 10 # 100๊ฐ ๋ฆฌ๋ทฐ ์๋ฎฌ๋ ์ด์
# ๋ถ์ ์คํ
print("\n" + "=" * 60)
print("๋๋ ๋ฆฌ๋ทฐ ๊ฐ์ ๋ถ์ ์์คํ
")
print("=" * 60)
analyzer = ReviewSentimentAnalyzer(analyzer_type='vader')
results_df = analyzer.analyze_batch(sample_reviews)
report = analyzer.generate_report(results_df)
print(f"\n๐ ๋ถ์ ๊ฒฐ๊ณผ ์์ฝ:")
print(f"์ด ๋ฆฌ๋ทฐ ์: {report['total_reviews']}๊ฐ")
print(f"๊ธ์ ๋ฆฌ๋ทฐ: {report['positive_count']}๊ฐ ({report['positive_ratio']:.1f}%)")
print(f"๋ถ์ ๋ฆฌ๋ทฐ: {report['negative_count']}๊ฐ ({report['negative_ratio']:.1f}%)")
print(f"์ค๋ฆฝ ๋ฆฌ๋ทฐ: {report['neutral_count']}๊ฐ")
print(f"ํ๊ท ๊ฐ์ ์ ์: {report['average_score']:.3f}")
# ์์/ํ์ ๋ฆฌ๋ทฐ ์ถ๋ ฅ
print("\n๐ ๊ฐ์ฅ ๊ธ์ ์ ์ธ ๋ฆฌ๋ทฐ TOP 3:")
top_positive = results_df.nlargest(3, 'score')
for idx, row in top_positive.iterrows():
print(f" โข {row['text'][:60]}... (์ ์: {row['score']:.3f})")
print("\n๐ ๊ฐ์ฅ ๋ถ์ ์ ์ธ ๋ฆฌ๋ทฐ TOP 3:")
top_negative = results_df.nsmallest(3, 'score')
for idx, row in top_negative.iterrows():
print(f" โข {row['text'][:60]}... (์ ์: {row['score']:.3f})")
์ด ์ฝ๋๋ ์ค์ ๋น์ฆ๋์ค์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ ์์ค์ด์ผ! ๊ณ ๊ฐ ๋ฆฌ๋ทฐ๋ฅผ ์๋์ผ๋ก ๋ถ๋ฅํ๊ณ , ๊ธ์ /๋ถ์ ๋น์จ์ ํ์ ํ๊ณ , ๊ฐ์ฅ ๊ทน๋จ์ ์ธ ๋ฆฌ๋ทฐ๋ค์ ์ฐพ์๋ผ ์ ์์ง. ๐
๐จ ์๊ฐํ๋ก ๊ฒฐ๊ณผ ํํํ๊ธฐ
๋ฐ์ดํฐ ๋ถ์์ ๊ฝ์ ์ญ์ ์๊ฐํ์ง! ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์์๊ฒ ๊ทธ๋ํ๋ก ๋ง๋ค์ด๋ณด์. ๐จ
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
class SentimentVisualizer:
"""
๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ ์๊ฐํ ํด๋์ค
"""
def __init__(self):
# ํ๊ธ ํฐํธ ์ค์ (ํ์์)
plt.rcParams['font.family'] = 'DejaVu Sans'
plt.rcParams['axes.unicode_minus'] = False
sns.set_style('whitegrid')
def plot_sentiment_distribution(self, df, save_path=None):
"""๊ฐ์ ๋ถํฌ ํ์ด ์ฐจํธ"""
sentiment_counts = df['sentiment'].value_counts()
colors = ['#81c784', '#e0e0e0', '#ef5350']
labels = ['Positive ๐', 'Neutral ๐', 'Negative ๐ข']
plt.figure(figsize=(10, 6))
plt.pie(
sentiment_counts.values,
labels=labels,
colors=colors,
autopct='%1.1f%%',
startangle=90,
textprops={'fontsize': 12, 'weight': 'bold'}
)
plt.title('Sentiment Distribution', fontsize=16, weight='bold', pad=20)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def plot_score_histogram(self, df, save_path=None):
"""๊ฐ์ ์ ์ ํ์คํ ๊ทธ๋จ"""
plt.figure(figsize=(12, 6))
# ๊ธ์ /๋ถ์ ๋ถ๋ฆฌ
positive = df[df['sentiment'] == 'positive']['score']
negative = df[df['sentiment'] == 'negative']['score']
neutral = df[df['sentiment'] == 'neutral']['score']
plt.hist(positive, bins=20, alpha=0.7, color='#81c784', label='Positive')
plt.hist(neutral, bins=20, alpha=0.7, color='#e0e0e0', label='Neutral')
plt.hist(negative, bins=20, alpha=0.7, color='#ef5350', label='Negative')
plt.xlabel('Sentiment Score', fontsize=12, weight='bold')
plt.ylabel('Frequency', fontsize=12, weight='bold')
plt.title('Distribution of Sentiment Scores', fontsize=16, weight='bold', pad=20)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def plot_timeline(self, df, date_column, save_path=None):
"""์๊ฐ๋๋ณ ๊ฐ์ ์ถ์ด"""
if date_column not in df.columns:
print("๋ ์ง ์ปฌ๋ผ์ด ์์ต๋๋ค.")
return
df['date'] = pd.to_datetime(df[date_column])
daily_sentiment = df.groupby([df['date'].dt.date, 'sentiment']).size().unstack(fill_value=0)
plt.figure(figsize=(14, 6))
daily_sentiment.plot(
kind='area',
stacked=True,
color=['#81c784', '#e0e0e0', '#ef5350'],
alpha=0.7
)
plt.xlabel('Date', fontsize=12, weight='bold')
plt.ylabel('Number of Reviews', fontsize=12, weight='bold')
plt.title('Sentiment Trend Over Time', fontsize=16, weight='bold', pad=20)
plt.legend(['Positive', 'Neutral', 'Negative'], fontsize=11)
plt.grid(True, alpha=0.3)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def create_wordcloud(self, texts, sentiment_type='all', save_path=None):
"""์๋ํด๋ผ์ฐ๋ ์์ฑ"""
text = ' '.join(texts)
# ๊ฐ์ ๋ณ ์์
color_map = {
'positive': '#81c784',
'negative': '#ef5350',
'all': '#7b68a6'
}
wordcloud = WordCloud(
width=1200,
height=600,
background_color='white',
colormap='viridis' if sentiment_type == 'all' else None,
max_words=100,
relative_scaling=0.5,
min_font_size=10
).generate(text)
plt.figure(figsize=(14, 7))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.title(
f'Word Cloud - {sentiment_type.capitalize()} Sentiment',
fontsize=16,
weight='bold',
pad=20
)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
# ์๊ฐํ ์คํ
visualizer = SentimentVisualizer()
print("\n๐ ์๊ฐํ ์์ฑ ์ค...")
visualizer.plot_sentiment_distribution(results_df)
visualizer.plot_score_histogram(results_df)
# ๊ธ์ /๋ถ์ ๋ฆฌ๋ทฐ ์๋ํด๋ผ์ฐ๋
positive_texts = results_df[results_df['sentiment'] == 'positive']['text'].tolist()
negative_texts = results_df[results_df['sentiment'] == 'negative']['text'].tolist()
print("\nโ๏ธ ์๋ํด๋ผ์ฐ๋ ์์ฑ ์ค...")
visualizer.create_wordcloud(positive_texts, sentiment_type='positive')
visualizer.create_wordcloud(negative_texts, sentiment_type='negative')
์๊ฐํ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ํ๋์ ํ์ ํ ์ ์์ด! ๊ฒฝ์์ง์๊ฒ ๋ณด๊ณ ํ ๋๋ ํจ์ฌ ํจ๊ณผ์ ์ด์ง. ๐
๐ง ์ปค์คํ ๋ชจ๋ธ ํ์ตํ๊ธฐ
๊ธฐ์กด ๋ชจ๋ธ๋ ์ข์ง๋ง, ์์ ๋ง์ ๋ฐ์ดํฐ๋ก ๋ชจ๋ธ์ ํ์ต์ํค๋ฉด ํจ์ฌ ๋ ์ ํํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ด! ๋จธ์ ๋ฌ๋์ผ๋ก ์ปค์คํ ๋ชจ๋ธ์ ๋ง๋ค์ด๋ณด์. ๐
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.metrics import classification_report, confusion_matrix
import numpy as np
class CustomSentimentModel:
"""
์ปค์คํ
๊ฐ์ ๋ถ์ ๋ชจ๋ธ
๋จธ์ ๋ฌ๋ ๊ธฐ๋ฐ
"""
def __init__(self, model_type='logistic'):
self.vectorizer = TfidfVectorizer(
max_features=5000,
ngram_range=(1, 2), # 1-gram๊ณผ 2-gram ์ฌ์ฉ
min_df=2,
max_df=0.8
)
# ๋ชจ๋ธ ์ ํ
if model_type == 'naive_bayes':
self.model = MultinomialNB()
elif model_type == 'logistic':
self.model = LogisticRegression(max_iter=1000, random_state=42)
elif model_type == 'svm':
self.model = LinearSVC(random_state=42)
self.model_type = model_type
self.is_trained = False
def prepare_data(self, texts, labels):
"""๋ฐ์ดํฐ ์ค๋น ๋ฐ ๋ถํ """
X_train, X_test, y_train, y_test = train_test_split(
texts, labels,
test_size=0.2,
random_state=42,
stratify=labels
)
return X_train, X_test, y_train, y_test
def train(self, X_train, y_train):
"""๋ชจ๋ธ ํ์ต"""
print(f"\n๐ {self.model_type} ๋ชจ๋ธ ํ์ต ์ค...")
# ํ
์คํธ ๋ฒกํฐํ
X_train_vec = self.vectorizer.fit_transform(X_train)
# ๋ชจ๋ธ ํ์ต
self.model.fit(X_train_vec, y_train)
self.is_trained = True
print("โ
ํ์ต ์๋ฃ!")
def evaluate(self, X_test, y_test):
"""๋ชจ๋ธ ํ๊ฐ"""
if not self.is_trained:
print("โ ๋ชจ๋ธ์ด ํ์ต๋์ง ์์์ต๋๋ค.")
return
X_test_vec = self.vectorizer.transform(X_test)
y_pred = self.model.predict(X_test_vec)
print("\n๐ ๋ชจ๋ธ ํ๊ฐ ๊ฒฐ๊ณผ:")
print("\n๋ถ๋ฅ ๋ฆฌํฌํธ:")
print(classification_report(y_test, y_pred))
print("\nํผ๋ ํ๋ ฌ:")
cm = confusion_matrix(y_test, y_pred)
print(cm)
accuracy = np.mean(y_pred == y_test)
print(f"\n์ ํ๋: {accuracy * 100:.2f}%")
return accuracy
def predict(self, text):
"""์๋ก์ด ํ
์คํธ ์์ธก"""
if not self.is_trained:
print("โ ๋ชจ๋ธ์ด ํ์ต๋์ง ์์์ต๋๋ค.")
return None
text_vec = self.vectorizer.transform([text])
prediction = self.model.predict(text_vec)[0]
# ํ๋ฅ ๊ณ์ฐ (๊ฐ๋ฅํ ๊ฒฝ์ฐ)
if hasattr(self.model, 'predict_proba'):
probabilities = self.model.predict_proba(text_vec)[0]
confidence = max(probabilities) * 100
else:
confidence = None
return {
'text': text,
'sentiment': prediction,
'confidence': confidence
}
def get_important_features(self, n=20):
"""์ค์ํ ํน์ฑ(๋จ์ด) ์ถ์ถ"""
if not self.is_trained:
return None
feature_names = self.vectorizer.get_feature_names_out()
if hasattr(self.model, 'coef_'):
coef = self.model.coef_[0]
# ๊ธ์ ์ ๋จ์ด
top_positive_idx = np.argsort(coef)[-n:]
top_positive = [(feature_names[i], coef[i]) for i in top_positive_idx]
# ๋ถ์ ์ ๋จ์ด
top_negative_idx = np.argsort(coef)[:n]
top_negative = [(feature_names[i], coef[i]) for i in top_negative_idx]
return {
'positive': top_positive[::-1],
'negative': top_negative
}
return None
# ํ์ต ๋ฐ์ดํฐ ์ค๋น (์ค์ ๋ก๋ ๋ ๋ง์ ๋ฐ์ดํฐ ํ์)
training_texts = [
"This is amazing and wonderful",
"I love this product so much",
"Absolutely fantastic experience",
"Best thing ever purchased",
"Highly recommend to everyone",
"Terrible and disappointing",
"Worst purchase I ever made",
"Complete waste of money",
"Very poor quality product",
"Never buying this again"
] * 50 # ๋ฐ์ดํฐ ์ฆ๊ฐ
training_labels = (
['positive'] * 250 + # ๊ธ์ ๋ฆฌ๋ทฐ
['negative'] * 250 # ๋ถ์ ๋ฆฌ๋ทฐ
)
# ๋ชจ๋ธ ํ์ต ๋ฐ ํ๊ฐ
print("\n" + "=" * 60)
print("์ปค์คํ
๊ฐ์ ๋ถ์ ๋ชจ๋ธ ํ์ต")
print("=" * 60)
# ์ฌ๋ฌ ๋ชจ๋ธ ๋น๊ต
model_types = ['naive_bayes', 'logistic', 'svm']
results = {}
for model_type in model_types:
print(f"\n{'='*60}")
print(f"๐ง {model_type.upper()} ๋ชจ๋ธ")
print(f"{'='*60}")
model = CustomSentimentModel(model_type=model_type)
X_train, X_test, y_train, y_test = model.prepare_data(training_texts, training_labels)
model.train(X_train, y_train)
accuracy = model.evaluate(X_test, y_test)
results[model_type] = accuracy
# ์ค์ ํน์ฑ ์ถ๋ ฅ
important_features = model.get_important_features(n=10)
if important_features:
print("\n๐ ๊ฐ์ฅ ๊ธ์ ์ ์ธ ๋จ์ด:")
for word, score in important_features['positive'][:5]:
print(f" โข {word}: {score:.3f}")
print("\n๐ ๊ฐ์ฅ ๋ถ์ ์ ์ธ ๋จ์ด:")
for word, score in important_features['negative'][:5]:
print(f" โข {word}: {score:.3f}")
# ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ ์ ํ
best_model_type = max(results, key=results.get)
print(f"\n๐ ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ: {best_model_type.upper()} (์ ํ๋: {results[best_model_type]*100:.2f}%)")
๐ฏ ๋ชจ๋ธ ์ ํ ๊ฐ์ด๋
โข Naive Bayes: ๋น ๋ฅด๊ณ ๊ฐ๋จ, ์์ ๋ฐ์ดํฐ์
์ ์ ํฉ
โข Logistic Regression: ๊ท ํ์กํ ์ฑ๋ฅ, ํด์ ๊ฐ๋ฅ
โข SVM: ๋์ ์ ํ๋, ์ค๊ฐ ํฌ๊ธฐ ๋ฐ์ดํฐ์
์ ์ต์
โข ๋ฅ๋ฌ๋(BERT ๋ฑ): ์ต๊ณ ์ฑ๋ฅ, ๋๋ ๋ฐ์ดํฐ์ ์ปดํจํ
ํ์ ํ์
๐ ์ค์๊ฐ ๊ฐ์ ๋ถ์ API ๋ง๋ค๊ธฐ
์ด์ ๋ฐฐ์ด ๊ฑธ API๋ก ๋ง๋ค์ด์ ๋ค๋ฅธ ์๋น์ค์์๋ ์ฌ์ฉํ ์ ์๊ฒ ํด๋ณด์! Flask๋ฅผ ์ฌ์ฉํด์ ๊ฐ๋จํ REST API๋ฅผ ๊ตฌ์ถํ ๊ฑฐ์ผ. ๐
from flask import Flask, request, jsonify
from flask_cors import CORS
import json
app = Flask(__name__)
CORS(app) # CORS ํ์ฉ
# ์ ์ญ ๋ถ์๊ธฐ ์ด๊ธฐํ
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
vader_analyzer = SentimentIntensityAnalyzer()
@app.route('/')
def home():
"""API ํํ์ด์ง"""
return jsonify({
'message': 'Sentiment Analysis API',
'version': '1.0',
'endpoints': {
'/analyze': 'POST - ๋จ์ผ ํ
์คํธ ๋ถ์',
'/analyze/batch': 'POST - ๋ค์ค ํ
์คํธ ๋ถ์',
'/health': 'GET - ์๋ฒ ์ํ ํ์ธ'
}
})
@app.route('/health')
def health():
"""์๋ฒ ์ํ ํ์ธ"""
return jsonify({'status': 'healthy', 'message': 'API is running'})
@app.route('/analyze', methods=['POST'])
def analyze_text():
"""
๋จ์ผ ํ
์คํธ ๊ฐ์ ๋ถ์
Request Body:
{
"text": "๋ถ์ํ ํ
์คํธ",
"language": "en" (optional, default: "en")
}
"""
try:
data = request.get_json()
if not data or 'text' not in data:
return jsonify({
'error': 'Missing required field: text'
}), 400
text = data['text']
language = data.get('language', 'en')
# VADER ๋ถ์
scores = vader_analyzer.polarity_scores(text)
compound = scores['compound']
# ๊ฐ์ ๋ถ๋ฅ
if compound >= 0.05:
sentiment = 'positive'
emoji = '๐'
elif compound <= -0.05:
sentiment = 'negative'
emoji = '๐ข'
else:
sentiment = 'neutral'
emoji = '๐'
# ์ ๋ขฐ๋ ๊ณ์ฐ
confidence = abs(compound) * 100
return jsonify({
'success': True,
'data': {
'text': text,
'sentiment': sentiment,
'emoji': emoji,
'confidence': round(confidence, 2),
'scores': {
'positive': scores['pos'],
'neutral': scores['neu'],
'negative': scores['neg'],
'compound': scores['compound']
},
'language': language
}
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
@app.route('/analyze/batch', methods=['POST'])
def analyze_batch():
"""
๋ค์ค ํ
์คํธ ๊ฐ์ ๋ถ์
Request Body:
{
"texts": ["ํ
์คํธ1", "ํ
์คํธ2", ...],
"language": "en" (optional)
}
"""
try:
data = request.get_json()
if not data or 'texts' not in data:
return jsonify({
'error': 'Missing required field: texts'
}), 400
texts = data['texts']
language = data.get('language', 'en')
if not isinstance(texts, list):
return jsonify({
'error': 'texts must be a list'
}), 400
results = []
for text in texts:
scores = vader_analyzer.polarity_scores(text)
compound = scores['compound']
if compound >= 0.05:
sentiment = 'positive'
emoji = '๐'
elif compound <= -0.05:
sentiment = 'negative'
emoji = '๐ข'
else:
sentiment = 'neutral'
emoji = '๐'
results.append({
'text': text,
'sentiment': sentiment,
'emoji': emoji,
'confidence': round(abs(compound) * 100, 2),
'compound': round(compound, 3)
})
# ํต๊ณ ๊ณ์ฐ
sentiments = [r['sentiment'] for r in results]
stats = {
'total': len(results),
'positive': sentiments.count('positive'),
'negative': sentiments.count('negative'),
'neutral': sentiments.count('neutral'),
'positive_ratio': round(sentiments.count('positive') / len(results) * 100, 2),
'negative_ratio': round(sentiments.count('negative') / len(results) * 100, 2)
}
return jsonify({
'success': True,
'data': {
'results': results,
'statistics': stats,
'language': language
}
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
if __name__ == '__main__':
print("\n" + "=" * 60)
print("๐ ๊ฐ์ ๋ถ์ API ์๋ฒ ์์")
print("=" * 60)
print("\n๐ก ์๋ฒ ์ฃผ์: http://localhost:5000")
print("\n์ฌ์ฉ ๊ฐ๋ฅํ ์๋ํฌ์ธํธ:")
print(" โข GET /health - ์๋ฒ ์ํ ํ์ธ")
print(" โข POST /analyze - ๋จ์ผ ํ
์คํธ ๋ถ์")
print(" โข POST /analyze/batch - ๋ค์ค ํ
์คํธ ๋ถ์")
print("\n" + "=" * 60 + "\n")
app.run(debug=True, host='0.0.0.0', port=5000)
์ด์ API ์๋ฒ๊ฐ ์์ฑ๋์ด! ๋ค๋ฅธ ํ๋ก๊ทธ๋จ์์ HTTP ์์ฒญ์ผ๋ก ๊ฐ์ ๋ถ์์ ์ฌ์ฉํ ์ ์์ง. ๐
๐ก API ์ฌ์ฉ ์์ (Python):
import requests
# ๋จ์ผ ํ
์คํธ ๋ถ์
response = requests.post('http://localhost:5000/analyze',
json={'text': 'This is amazing!'})
print(response.json())
# ๋ค์ค ํ
์คํธ ๋ถ์
response = requests.post('http://localhost:5000/analyze/batch',
json={'texts': ['I love it!', 'Terrible product', 'It\'s okay']})
print(response.json())
๐ผ ์ค๋ฌด ํ์ฉ ์ฌ๋ก์ ํ
์ด๋ก ๊ณผ ์ฝ๋๋ง ์๋ฉด ๋ญํด? ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉํ๋์ง๊ฐ ์ค์ํ์ง! ์ค๋ฌด์์ ์์ฃผ ๋ง์ฃผ์น๋ ์ํฉ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ. ๐ช
๐ฏ ์ฌ๋ก 1: ๊ณ ๊ฐ ๋ฆฌ๋ทฐ ๋ชจ๋ํฐ๋ง ์์คํ
import schedule
import time
from datetime import datetime
class ReviewMonitor:
"""
์ค์๊ฐ ๋ฆฌ๋ทฐ ๋ชจ๋ํฐ๋ง ์์คํ
๋ถ์ ๋ฆฌ๋ทฐ ์๋ ์๋ฆผ
"""
def __init__(self, alert_threshold=-0.5):
self.analyzer = SentimentIntensityAnalyzer()
self.alert_threshold = alert_threshold
self.negative_reviews = []
def check_new_reviews(self):
"""์ ๋ฆฌ๋ทฐ ํ์ธ (์ค์ ๋ก๋ DB๋ API์์ ๊ฐ์ ธ์ด)"""
# ์ฌ๊ธฐ์๋ ์๋ฎฌ๋ ์ด์
new_reviews = [
"Product broke after 2 days. Very disappointed!",
"Great quality, love it!",
"Worst customer service ever. Never again!"
]
for review in new_reviews:
self.analyze_and_alert(review)
def analyze_and_alert(self, review):
"""๋ฆฌ๋ทฐ ๋ถ์ ๋ฐ ์๋ฆผ"""
scores = self.analyzer.polarity_scores(review)
compound = scores['compound']
if compound <= self.alert_threshold:
alert = {
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'review': review,
'score': compound,
'severity': 'HIGH' if compound < -0.7 else 'MEDIUM'
}
self.negative_reviews.append(alert)
self.send_alert(alert)
def send_alert(self, alert):
"""์๋ฆผ ์ ์ก (์ด๋ฉ์ผ, ์ฌ๋ ๋ฑ)"""
print(f"\n๐จ ๋ถ์ ๋ฆฌ๋ทฐ ๊ฐ์ง!")
print(f"์๊ฐ: {alert['timestamp']}")
print(f"์ฌ๊ฐ๋: {alert['severity']}")
print(f"์ ์: {alert['score']:.3f}")
print(f"๋ด์ฉ: {alert['review']}")
print("-" * 60)
def generate_daily_report(self):
"""์ผ์ผ ๋ฆฌํฌํธ ์์ฑ"""
if not self.negative_reviews:
print("\nโ
์ค๋์ ์ฌ๊ฐํ ๋ถ์ ๋ฆฌ๋ทฐ๊ฐ ์์ต๋๋ค!")
return
print(f"\n๐ ์ผ์ผ ๋ถ์ ๋ฆฌ๋ทฐ ๋ฆฌํฌํธ")
print(f"์ด {len(self.negative_reviews)}๊ฑด์ ๋ถ์ ๋ฆฌ๋ทฐ ๊ฐ์ง")
print("\n๊ฐ์ฅ ์ฌ๊ฐํ ๋ฆฌ๋ทฐ TOP 3:")
sorted_reviews = sorted(self.negative_reviews, key=lambda x: x['score'])
for i, review in enumerate(sorted_reviews[:3], 1):
print(f"\n{i}. {review['review'][:50]}...")
print(f" ์ ์: {review['score']:.3f}")
def start_monitoring(self):
"""๋ชจ๋ํฐ๋ง ์์"""
print("๐ ๋ฆฌ๋ทฐ ๋ชจ๋ํฐ๋ง ์์...")
# 10๋ถ๋ง๋ค ์ฒดํฌ
schedule.every(10).minutes.do(self.check_new_reviews)
# ๋งค์ผ ์คํ 6์ ๋ฆฌํฌํธ
schedule.every().day.at("18:00").do(self.generate_daily_report)
while True:
schedule.run_pending()
time.sleep(60)
# ๋ชจ๋ํฐ ์คํ (์ค์ ์ด์์)
# monitor = ReviewMonitor(alert_threshold=-0.5)
# monitor.start_monitoring()
๐ฏ ์ฌ๋ก 2: ์์ ๋ฏธ๋์ด ๋ธ๋๋ ๋ชจ๋ํฐ๋ง
class BrandSentimentTracker:
"""
๋ธ๋๋ ๊ฐ์ ์ถ์ ์์คํ
์์
๋ฏธ๋์ด ๋ฉ์
๋ถ์
"""
def __init__(self, brand_name):
self.brand_name = brand_name
self.analyzer = SentimentIntensityAnalyzer()
self.mentions = []
def track_mentions(self, posts):
"""๋ธ๋๋ ๋ฉ์
์ถ์ """
for post in posts:
if self.brand_name.lower() in post.lower():
sentiment_data = self.analyze_post(post)
self.mentions.append(sentiment_data)
return self.mentions
def analyze_post(self, post):
"""ํฌ์คํธ ๊ฐ์ ๋ถ์"""
scores = self.analyzer.polarity_scores(post)
return {
'post': post,
'sentiment': self.classify_sentiment(scores['compound']),
'score': scores['compound'],
'engagement_potential': self.calculate_engagement(post, scores)
}
def classify_sentiment(self, compound):
"""๊ฐ์ ๋ถ๋ฅ"""
if compound >= 0.05:
return 'positive'
elif compound <= -0.05:
return 'negative'
return 'neutral'
def calculate_engagement(self, post, scores):
"""์ฐธ์ฌ ๊ฐ๋ฅ์ฑ ๊ณ์ฐ"""
# ๊ฐ๋จํ ํด๋ฆฌ์คํฑ
engagement_score = abs(scores['compound'])
# ๋๋ํ, ๋ฌผ์ํ ๊ฐ์ค์น
engagement_score += post.count('!') * 0.1
engagement_score += post.count('?') * 0.05
return min(engagement_score, 1.0)
def get_insights(self):
"""์ธ์ฌ์ดํธ ์์ฑ"""
if not self.mentions:
return "๋ฉ์
์ด ์์ต๋๋ค."
total = len(self.mentions)
positive = sum(1 for m in self.mentions if m['sentiment'] == 'positive')
negative = sum(1 for m in self.mentions if m['sentiment'] == 'negative')
avg_score = sum(m['score'] for m in self.mentions) / total
# ๋์ ์ฐธ์ฌ ๊ฐ๋ฅ์ฑ ํฌ์คํธ
high_engagement = [m for m in self.mentions if m['engagement_potential'] > 0.7]
insights = {
'total_mentions': total,
'sentiment_breakdown': {
'positive': positive,
'negative': negative,
'neutral': total - positive - negative
},
'average_sentiment': avg_score,
'positive_ratio': (positive / total) * 100,
'high_engagement_posts': len(high_engagement),
'recommendation': self.get_recommendation(avg_score, negative, total)
}
return insights
def get_recommendation(self, avg_score, negative_count, total):
"""์ถ์ฒ ์ก์
"""
if avg_score < -0.3:
return "โ ๏ธ ๊ธด๊ธ: ๋ธ๋๋ ์ด๋ฏธ์ง ๊ฐ์ ํ์. ์ฆ๊ฐ์ ์ธ ๋์ ๊ถ์ฅ"
elif negative_count / total > 0.3:
return "โก ์ฃผ์: ๋ถ์ ์ ๋ฉ์
์ฆ๊ฐ. ๋ชจ๋ํฐ๋ง ๊ฐํ ํ์"
elif avg_score > 0.5:
return "โ
์ฐ์: ๊ธ์ ์ ๋ฐ์. ํ์ฌ ์ ๋ต ์ ์ง"
else:
return "๐ ๋ณดํต: ์์ ์ ์ํ. ์ง์์ ๋ชจ๋ํฐ๋ง ๊ถ์ฅ"
# ์ฌ์ฉ ์์
tracker = BrandSentimentTracker("MyBrand")
sample_posts = [
"Just tried MyBrand's new product and I'm blown away! ๐",
"MyBrand customer service is terrible. Very disappointed.",
"Saw MyBrand at the store today. Looks interesting.",
"MyBrand never disappoints! Best quality ever! ๐ฅ",
"Not impressed with MyBrand's latest release."
]
tracker.track_mentions(sample_posts)
insights = tracker.get_insights()
print("\n" + "=" * 60)
print(f"๐ {tracker.brand_name} ๋ธ๋๋ ๊ฐ์ ๋ถ์ ๋ฆฌํฌํธ")
print("=" * 60)
print(f"\n์ด ๋ฉ์
: {insights['total_mentions']}๊ฑด")
print(f"๊ธ์ : {insights['sentiment_breakdown']['positive']}๊ฑด")
print(f"๋ถ์ : {insights['sentiment_breakdown']['negative']}๊ฑด")
print(f"์ค๋ฆฝ: {insights['sentiment_breakdown']['neutral']}๊ฑด")
print(f"\n๊ธ์ ๋น์จ: {insights['positive_ratio']:.1f}%")
print(f"ํ๊ท ๊ฐ์ ์ ์: {insights['average_sentiment']:.3f}")
print(f"๋์ ์ฐธ์ฌ ๊ฐ๋ฅ์ฑ ํฌ์คํธ: {insights['high_engagement_posts']}๊ฑด")
print(f"\n๐ก ์ถ์ฒ ์ก์
: {insights['recommendation']}")
โก ์ฑ๋ฅ ์ต์ ํ ํ
๋๋์ ํ ์คํธ๋ฅผ ์ฒ๋ฆฌํ ๋๋ ์ฑ๋ฅ์ด ์ ๋ง ์ค์ํด! ๋ช ๊ฐ์ง ์ต์ ํ ๊ธฐ๋ฒ์ ์๊ฐํ ๊ฒ. ๐
๐ฅ ์ฑ๋ฅ ์ต์ ํ ์ฒดํฌ๋ฆฌ์คํธ
1. ๋ฐฐ์น ์ฒ๋ฆฌ ํ์ฉ
โข ํ ๋ฒ์ ์ฌ๋ฌ ํ
์คํธ๋ฅผ ์ฒ๋ฆฌํ๋ฉด ์ค๋ฒํค๋ ๊ฐ์
โข ๋ฒกํฐํ ์ฐ์ฐ์ ๋ฐฐ์น๋ก ์ฒ๋ฆฌํ๋ ๊ฒ ํจ์ฌ ๋น ๋ฆ
2. ์บ์ฑ ์ ๋ต
โข ๋์ผํ ํ
์คํธ๋ ๊ฒฐ๊ณผ๋ฅผ ์บ์์ ์ ์ฅ
โข Redis๋ ๋ฉ๋ชจ๋ฆฌ ์บ์ ํ์ฉ
3. ๋ฉํฐํ๋ก์ธ์ฑ
โข CPU ์ฝ์ด๋ฅผ ๋ชจ๋ ํ์ฉ
โข ๋
๋ฆฝ์ ์ธ ํ
์คํธ๋ ๋ณ๋ ฌ ์ฒ๋ฆฌ
4. ๋ชจ๋ธ ๊ฒฝ๋ํ
โข ํ์ํ ๊ฒฝ์ฐ ์์ ๋ชจ๋ธ ์ฌ์ฉ
โข DistilBERT ๊ฐ์ ๊ฒฝ๋ ๋ชจ๋ธ ๊ณ ๋ ค
5. ์ ์ฒ๋ฆฌ ์ต์ ํ
โข ๋ถํ์ํ ์ ์ฒ๋ฆฌ ๋จ๊ณ ์ ๊ฑฐ
โข ์ ๊ทํํ์ ์ต์ ํ
from multiprocessing import Pool
from functools import lru_cache
import hashlib
class OptimizedSentimentAnalyzer:
"""
์ต์ ํ๋ ๊ฐ์ ๋ถ์๊ธฐ
"""
def __init__(self, use_cache=True, n_workers=4):
self.analyzer = SentimentIntensityAnalyzer()
self.use_cache = use_cache
self.n_workers = n_workers
self.cache = {}
def get_cache_key(self, text):
"""์บ์ ํค ์์ฑ"""
return hashlib.md5(text.encode()).hexdigest()
def analyze_single(self, text):
"""๋จ์ผ ํ
์คํธ ๋ถ์ (์บ์ฑ ํฌํจ)"""
if self.use_cache:
cache_key = self.get_cache_key(text)
if cache_key in self.cache:
return self.cache[cache_key]
result = self.analyzer.polarity_scores(text)
if self.use_cache:
self.cache[cache_key] = result
return result
def analyze_batch_parallel(self, texts):
"""๋ณ๋ ฌ ๋ฐฐ์น ์ฒ๋ฆฌ"""
with Pool(self.n_workers) as pool:
results = pool.map(self.analyze_single, texts)
return results
def analyze_batch_sequential(self, texts, batch_size=100):
"""์์ฐจ ๋ฐฐ์น ์ฒ๋ฆฌ (๋ฉ๋ชจ๋ฆฌ ํจ์จ์ )"""
results = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
batch_results = [self.analyze_single(text) for text in batch]
results.extend(batch_results)
return results
# ์ฑ๋ฅ ๋น๊ต
import time
def benchmark_analyzers(texts, n_runs=3):
"""๋ถ์๊ธฐ ์ฑ๋ฅ ๋ฒค์น๋งํฌ"""
print("\n" + "=" * 60)
print("โก ์ฑ๋ฅ ๋ฒค์น๋งํฌ")
print("=" * 60)
# ๊ธฐ๋ณธ ๋ถ์๊ธฐ
print("\n1๏ธโฃ ๊ธฐ๋ณธ ๋ถ์๊ธฐ (์์ฐจ ์ฒ๋ฆฌ)")
basic_times = []
analyzer = SentimentIntensityAnalyzer()
for run in range(n_runs):
start = time.time()
for text in texts:
analyzer.polarity_scores(text)
elapsed = time.time() - start
basic_times.append(elapsed)
print(f" Run {run+1}: {elapsed:.3f}์ด")
avg_basic = sum(basic_times) / n_runs
print(f" ํ๊ท : {avg_basic:.3f}์ด")
# ์บ์ฑ ๋ถ์๊ธฐ
print("\n2๏ธโฃ ์บ์ฑ ๋ถ์๊ธฐ")
cache_times = []
cached_analyzer = OptimizedSentimentAnalyzer(use_cache=True)
for run in range(n_runs):
start = time.time()
cached_analyzer.analyze_batch_sequential(texts)
elapsed = time.time() - start
cache_times.append(elapsed)
print(f" Run {run+1}: {elapsed:.3f}์ด")
avg_cache = sum(cache_times) / n_runs
print(f" ํ๊ท : {avg_cache:.3f}์ด")
print(f" ๊ฐ์ : {((avg_basic - avg_cache) / avg_basic * 100):.1f}% ๋น ๋ฆ")
# ๋ณ๋ ฌ ์ฒ๋ฆฌ
print("\n3๏ธโฃ ๋ณ๋ ฌ ์ฒ๋ฆฌ ๋ถ์๊ธฐ")
parallel_times = []
parallel_analyzer = OptimizedSentimentAnalyzer(use_cache=False, n_workers=4)
for run in range(n_runs):
start = time.time()
parallel_analyzer.analyze_batch_parallel(texts)
elapsed = time.time() - start
parallel_times.append(elapsed)
print(f" Run {run+1}: {elapsed:.3f}์ด")
avg_parallel = sum(parallel_times) / n_runs
print(f" ํ๊ท : {avg_parallel:.3f}์ด")
print(f" ๊ฐ์ : {((avg_basic - avg_parallel) / avg_basic * 100):.1f}% ๋น ๋ฆ")
# ๋ฒค์น๋งํฌ ์คํ
test_texts = [
"This is an amazing product!",
"Terrible experience, very disappointed.",
"It's okay, nothing special."
] * 100 # 300๊ฐ ํ
์คํธ
# benchmark_analyzers(test_texts)
๐ ๊ณ ๊ธ ๊ธฐ๋ฒ: ๋ฉํฐ๋ชจ๋ฌ ๊ฐ์ ๋ถ์
ํ ์คํธ๋ง์ผ๋ก๋ ๋ถ์กฑํด! ์ด๋ฏธ์ง, ์์ฑ๊น์ง ํจ๊ป ๋ถ์ํ๋ ๋ฉํฐ๋ชจ๋ฌ ๊ฐ์ ๋ถ์๋ ๊ฐ๋ฅํด. ๐ญ
from PIL import Image
import torch
from transformers import CLIPProcessor, CLIPModel
class MultimodalSentimentAnalyzer:
"""
๋ฉํฐ๋ชจ๋ฌ ๊ฐ์ ๋ถ์
ํ
์คํธ + ์ด๋ฏธ์ง ํตํฉ ๋ถ์
"""
def __init__(self):
# CLIP ๋ชจ๋ธ ๋ก๋ (ํ
์คํธ-์ด๋ฏธ์ง ์ฐ๊ฒฐ)
self.clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
self.clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# ํ
์คํธ ๊ฐ์ ๋ถ์๊ธฐ
self.text_analyzer = SentimentIntensityAnalyzer()
def analyze_text(self, text):
"""ํ
์คํธ ๊ฐ์ ๋ถ์"""
scores = self.text_analyzer.polarity_scores(text)
return scores['compound']
def analyze_image_sentiment(self, image_path):
"""
์ด๋ฏธ์ง ๊ฐ์ ๋ถ์
CLIP์ ์ฌ์ฉํด ์ด๋ฏธ์ง์ ๊ฐ์ ๋ ์ด๋ธ ๋งค์นญ
"""
image = Image.open(image_path)
# ๊ฐ์ ๋ ์ด๋ธ
sentiment_labels = [
"a happy and joyful scene",
"a sad and depressing scene",
"an angry and aggressive scene",
"a calm and peaceful scene",
"an exciting and energetic scene"
]
# CLIP์ผ๋ก ์ด๋ฏธ์ง-ํ
์คํธ ์ ์ฌ๋ ๊ณ์ฐ
inputs = self.clip_processor(
text=sentiment_labels,
images=image,
return_tensors="pt",
padding=True
)
outputs = self.clip_model(**inputs)
logits_per_image = outputs.logits_per_image
probs = logits_per_image.softmax(dim=1)
# ๊ฐ์ฅ ๋์ ํ๋ฅ ์ ๊ฐ์
max_idx = probs.argmax().item()
confidence = probs[0][max_idx].item()
sentiment_map = {
0: ('positive', 0.8),
1: ('negative', -0.8),
2: ('negative', -0.6),
3: ('neutral', 0.0),
4: ('positive', 0.6)
}
sentiment, score = sentiment_map[max_idx]
return {
'sentiment': sentiment,
'score': score,
'confidence': confidence,
'label': sentiment_labels[max_idx]
}
def analyze_multimodal(self, text, image_path, text_weight=0.6, image_weight=0.4):
"""
ํ
์คํธ + ์ด๋ฏธ์ง ํตํฉ ๋ถ์
๊ฐ์ค์น ๊ธฐ๋ฐ ๊ฒฐํฉ
"""
# ํ
์คํธ ๋ถ์
text_score = self.analyze_text(text)
# ์ด๋ฏธ์ง ๋ถ์
image_result = self.analyze_image_sentiment(image_path)
image_score = image_result['score']
# ๊ฐ์ค ํ๊ท
combined_score = (text_score * text_weight) + (image_score * image_weight)
# ์ต์ข
๊ฐ์ ๋ถ๋ฅ
if combined_score >= 0.1:
final_sentiment = 'positive ๐'
elif combined_score <= -0.1:
final_sentiment = 'negative ๐ข'
else:
final_sentiment = 'neutral ๐'
return {
'text': text,
'text_score': round(text_score, 3),
'image_score': round(image_score, 3),
'image_label': image_result['label'],
'combined_score': round(combined_score, 3),
'final_sentiment': final_sentiment,
'confidence': round(image_result['confidence'] * 100, 2)
}
# ์ฌ์ฉ ์์ (์ด๋ฏธ์ง ํ์ผ์ด ์์ ๋)
# analyzer = MultimodalSentimentAnalyzer()
# result = analyzer.analyze_multimodal(
# text="Had an amazing day at the beach!",
# image_path="beach_photo.jpg"
# )
# print(result)
๋ฉํฐ๋ชจ๋ฌ ๋ถ์์ SNS ํฌ์คํธ์ฒ๋ผ ํ ์คํธ์ ์ด๋ฏธ์ง๊ฐ ํจ๊ป ์๋ ์ฝํ ์ธ ๋ถ์์ ํนํ ์ ์ฉํด! ๐ธ
๐ก๏ธ ์ฃผ์์ฌํญ๊ณผ ํ๊ณ์
๊ฐ์ ๋ถ์์ ๊ฐ๋ ฅํ์ง๋ง ์๋ฒฝํ์ง๋ ์์. ์์๋์ด์ผ ํ ํ๊ณ์ ๋ค์ด ์์ด! โ ๏ธ
โ ๏ธ ๊ฐ์ ๋ถ์์ ํ๊ณ
1. ๋ฌธ๋งฅ ์ดํด์ ์ด๋ ค์
โข "์ด ์ํ ์ ๋ง ์ฃฝ์ธ๋ค!" โ ๊ธ์ ์ธ๋ฐ '์ฃฝ์ธ๋ค'๋ ๋ถ์ ๋จ์ด
โข ๋ฐ์ด๋ฒ, ๋น๊ผฌ๊ธฐ ๊ฐ์ง ์ด๋ ค์
2. ๋ฌธํ์ ์ฐจ์ด
โข ๊ฐ์ ํํ๋ ๋ฌธํ๊ถ๋ง๋ค ์๋ฏธ๊ฐ ๋ค๋ฆ
โข ํ๊ตญ์ด์ ์๊ณกํ ํํ ์ฒ๋ฆฌ ์ด๋ ค์
3. ๋๋ฉ์ธ ํน์์ฑ
โข ์๋ฃ, ๋ฒ๋ฅ ๋ฑ ์ ๋ฌธ ๋ถ์ผ๋ ์ผ๋ฐ ๋ชจ๋ธ๋ก ๋ถ์ ํ
โข ๋๋ฉ์ธ๋ณ ์ปค์คํ
๋ชจ๋ธ ํ์
4. ํธํฅ ๋ฌธ์
โข ํ์ต ๋ฐ์ดํฐ์ ํธํฅ์ด ๊ฒฐ๊ณผ์ ์ํฅ
โข ํน์ ์ง๋จ์ ๋ํ ๋ถ๊ณต์ ํ ํ๋จ ๊ฐ๋ฅ
5. ๋ณตํฉ ๊ฐ์
โข "์ข๊ธด ํ๋ฐ ๋น์ธ์" ๊ฐ์ ๋ณตํฉ ๊ฐ์ ์ฒ๋ฆฌ ์ด๋ ค์
โข ์๋ฉด์ ์๊ฒฌ ๋ถ์ ํ๊ณ
def handle_edge_cases(text):
"""
์ฃ์ง ์ผ์ด์ค ์ฒ๋ฆฌ ์์
"""
# ๋ฐ์ด๋ฒ ๊ฐ์ง ํจํด
sarcasm_patterns = [
r"yeah right",
r"sure thing",
r"oh great",
r"wonderful.*not"
]
# ์ด์ค ๋ถ์
double_negative_patterns = [
r"not.*bad",
r"not.*terrible",
r"can't.*complain"
]
# ์กฐ๊ฑด๋ถ ๊ธ์
conditional_patterns = [
r"good.*but",
r"nice.*however",
r"love.*except"
]
import re
flags = {
'has_sarcasm': any(re.search(p, text, re.I) for p in sarcasm_patterns),
'has_double_negative': any(re.search(p, text, re.I) for p in double_negative_patterns),
'has_conditional': any(re.search(p, text, re.I) for p in conditional_patterns)
}
return flags
# ํ
์คํธ
edge_cases = [
"Oh great, another delay. Just wonderful!", # ๋ฐ์ด๋ฒ
"It's not bad at all.", # ์ด์ค ๋ถ์
"I love the design but hate the price." # ์กฐ๊ฑด๋ถ
]
for text in edge_cases:
flags = handle_edge_cases(text)
print(f"\nํ
์คํธ: {text}")
print(f"ํ๋๊ทธ: {flags}")
๐ ์ฌ๋ฅ๋ท์์ ๊ฐ์ ๋ถ์ ์๋น์ค ์ ๊ณตํ๊ธฐ
์ด์ ๋ฐฐ์ด ๊ธฐ์ ์ ํ์ฉํด์ ์ฌ๋ฅ๋ท์์ ๊ฐ์ ๋ถ์ ์๋น์ค๋ฅผ ์ ๊ณตํ ์๋ ์์ด! ๋ง์ ๊ธฐ์ ๋ค์ด ๊ณ ๊ฐ ๋ฆฌ๋ทฐ ๋ถ์, ์์ ๋ฏธ๋์ด ๋ชจ๋ํฐ๋ง ์๋น์ค๋ฅผ ํ์๋ก ํ๊ฑฐ๋ . ๐ผ
๐ก ์ฌ๋ฅ๋ท ์๋น์ค ์์ด๋์ด
โข ๋ฆฌ๋ทฐ ๋ถ์ ๋ํ: ์ผํ๋ชฐ ๋ฆฌ๋ทฐ ์๋ ๋ถ์ ๋ฐ ๋ฆฌํฌํธ ์ ๊ณต
โข SNS ๋ชจ๋ํฐ๋ง: ๋ธ๋๋ ๋ฉ์
๊ฐ์ ์ถ์ ์๋น์ค
โข ๊ณ ๊ฐ ํผ๋๋ฐฑ ๋ถ์: ์ค๋ฌธ์กฐ์ฌ, ๊ณ ๊ฐ ์๊ฒฌ ์๋ ๋ถ๋ฅ
โข ์ปค์คํ
๋ชจ๋ธ ๊ฐ๋ฐ: ํน์ ๋๋ฉ์ธ์ ์ต์ ํ๋ ๊ฐ์ ๋ถ์ ๋ชจ๋ธ ์ ์
โข API ๊ฐ๋ฐ: ํด๋ผ์ด์ธํธ ์์คํ
์ ํตํฉ ๊ฐ๋ฅํ API ์ ๊ณต
โข ๋์๋ณด๋ ๊ตฌ์ถ: ์ค์๊ฐ ๊ฐ์ ๋ถ์ ๋ชจ๋ํฐ๋ง ๋์๋ณด๋
์ค์ ๋ก ๊ฐ์ ๋ถ์ ๊ธฐ์ ์ ๋ง์ผํ , ๊ณ ๊ฐ ์๋น์ค, ์ ํ ๊ฐ๋ฐ ๋ฑ ๋ค์ํ ๋ถ์ผ์์ ํ์ฉ๋๊ณ ์์ด. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ด๋ฐ ์ ๋ฌธ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ฉด ์ข์ ์์ต์ ์ฌ๋ฆด ์ ์์ง! ๐
๐ ์ถ๊ฐ ํ์ต ์๋ฃ
๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ์๋ฃ๋ค์ ์ถ์ฒํด! ๐
| ๋ถ์ผ | ์ถ์ฒ ์๋ฃ | ๋์ด๋ |
|---|---|---|
| ๊ธฐ์ด NLP | NLTK Book, spaCy ๊ณต์ ๋ฌธ์ | ์ด๊ธ |
| ๋จธ์ ๋ฌ๋ | Scikit-learn Documentation | ์ค๊ธ |
| ๋ฅ๋ฌ๋ | Hugging Face Transformers Course | ์ค๊ธ~๊ณ ๊ธ |
| ํ๊ธ NLP | KoNLPy, KoBERT ๋ฌธ์ | ์ค๊ธ |
| ์ค์ ํ๋ก์ ํธ | Kaggle Sentiment Analysis Competitions | ์ค๊ธ~๊ณ ๊ธ |
๐ก ํ์ต ๋ก๋๋งต ์ถ์ฒ
1๏ธโฃ 1์ฃผ์ฐจ: Python ๊ธฐ์ด, NLTK/TextBlob์ผ๋ก ๊ฐ๋จํ ๋ถ์
2๏ธโฃ 2์ฃผ์ฐจ: VADER, ๊ท์น ๊ธฐ๋ฐ ๋ถ์ ๋ง์คํฐ
3๏ธโฃ 3์ฃผ์ฐจ: Scikit-learn์ผ๋ก ๋จธ์ ๋ฌ๋ ๋ชจ๋ธ ํ์ต
4๏ธโฃ 4์ฃผ์ฐจ: ํธ๋์คํฌ๋จธ ๋ชจ๋ธ ํ์ฉ, ํ์ธํ๋
5๏ธโฃ 5์ฃผ์ฐจ: ์ค์ ํ๋ก์ ํธ, API ๊ฐ๋ฐ
6๏ธโฃ 6์ฃผ์ฐจ: ์ต์ ํ, ๋ฐฐํฌ, ๋ชจ๋ํฐ๋ง
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ํ ์คํธ ๊ฐ์ ๋ถ์์ ๊ธฐ์ด๋ถํฐ ๊ณ ๊ธ ๊ธฐ๋ฒ, ์ค์ ํ์ฉ๊น์ง ๋ชจ๋ ๋ค๋ค๋ดค์ด! ๐
ํต์ฌ์ ์ ๋ฆฌํ์๋ฉด:
๐ ํต์ฌ ์์ฝ
โ
๊ฐ์ ๋ถ์์ ํ
์คํธ์ ๊ธ์ /๋ถ์ /์ค๋ฆฝ์ ์๋์ผ๋ก ํ๋จํ๋ NLP ๊ธฐ์
โ
TextBlob์ ์ด๋ณด์์๊ฒ ์ต์ , ๊ฐ๋จํ๊ณ ์ง๊ด์
โ
VADER๋ ์์
๋ฏธ๋์ด ๋ถ์์ ํนํ, ์ด๋ชจํฐ์ฝ๊ณผ ๋๋ฌธ์ ์ฒ๋ฆฌ ์ฐ์
โ
ํธ๋์คํฌ๋จธ(BERT ๋ฑ)๋ ์ต๊ณ ์ฑ๋ฅ, ๋ฌธ๋งฅ ์ดํด ๋ฅ๋ ฅ ํ์
โ
์ปค์คํ
๋ชจ๋ธ์ ํน์ ๋๋ฉ์ธ์ ์ต์ ํ ๊ฐ๋ฅ
โ
์ฑ๋ฅ ์ต์ ํ๋ ๋๋ ์ฒ๋ฆฌ์ ํ์ (์บ์ฑ, ๋ณ๋ ฌ์ฒ๋ฆฌ)
โ
์ค๋ฌด ํ์ฉ์ ๋ฆฌ๋ทฐ ๋ชจ๋ํฐ๋ง, ๋ธ๋๋ ์ถ์ , ๊ณ ๊ฐ ์๋น์ค ๋ฑ ๋ค์
โ
ํ๊ณ ์ธ์๋ ์ค์ - ๋ฐ์ด๋ฒ, ๋ฌธํ์ ์ฐจ์ด, ๋ณตํฉ ๊ฐ์ ์ฒ๋ฆฌ ์ด๋ ค์
์ด์ ๋๋ ๊ฐ์ ๋ถ์ ์ ๋ฌธ๊ฐ๊ฐ ๋ ์ ์์ด! ์ค๋ ๋ฐฐ์ด ์ฝ๋๋ค์ ์ง์ ์คํํด๋ณด๊ณ , ์์ ๋ง์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ด. ์ฒ์์๋ ์ด๋ ค์ธ ์ ์์ง๋ง, ๊ณ์ ์ฐ์ตํ๋ค ๋ณด๋ฉด ๊ธ๋ฐฉ ์ต์ํด์ง ๊ฑฐ์ผ! ๐ช
๊ทธ๋ฆฌ๊ณ ์ด๋ฐ ๊ธฐ์ ์ ํ์ฉํด์ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ๋ง์ ๊ธฐ์ ๋ค์ด ๋ฐ์ดํฐ ๋ถ์ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ๊ณ ์๊ฑฐ๋ ! ๐
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด, ์์์ ์๊ฐํ ํ์ต ์๋ฃ๋ค์ ์ฐธ๊ณ ํด๋ด. ๊ทธ๋ฆฌ๊ณ ์ค์ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ถ๋ชํ๋ ๋ฌธ์ ๋ค์ ํ๋์ฉ ํด๊ฒฐํด๋๊ฐ๋ ๊ฒ ๊ฐ์ฅ ์ข์ ํ์ต ๋ฐฉ๋ฒ์ด์ผ! ๐
์, ์ด์ ์์์ด์ผ! ํ ์คํธ ๊ฐ์ ๋ถ์์ ์ธ๊ณ๋ก ๋ฐ์ด๋ค์ด๋ด! ํ์ดํ ! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.