์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŽญ ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„ ์†Œ์Šค์ฝ”๋“œ ์™„์ „์ •๋ณต

๐ŸŽญ ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„ ์†Œ์Šค์ฝ”๋“œ ์™„์ „์ •๋ณต

NLP๋กœ ํ…์ŠคํŠธ ์† ๊ฐ์ •์„ ์ฝ์–ด๋‚ด๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๊ธฐ์ˆ !

์•ˆ๋…•! ์˜ค๋Š˜์€ ์ •๋ง ์žฌ๋ฏธ์žˆ๋Š” ์ฃผ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์™”์–ด. ๋ฐ”๋กœ ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„(Sentiment Analysis)์ด์•ผ! ๐ŸŽฏ

์š”์ฆ˜ SNS์— ์˜ฌ๋ผ์˜ค๋Š” ์ˆ˜๋งŽ์€ ๋Œ“๊ธ€๋“ค, ๊ณ ๊ฐ ๋ฆฌ๋ทฐ, ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€... ์ด๋Ÿฐ ํ…์ŠคํŠธ๋“ค ์†์— ์ˆจ์–ด์žˆ๋Š” ๊ฐ์ •์„ ์ปดํ“จํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์–ผ๋งˆ๋‚˜ ์ข‹์„๊นŒ? ์‹ค์ œ๋กœ ์ด๊ฒŒ ๊ฐ€๋Šฅํ•ด! ๊ทธ๊ฒƒ๋„ ์ƒ๊ฐ๋ณด๋‹ค ํ›จ์”ฌ ์‰ฝ๊ฒŒ ๋ง์ด์•ผ. ๐Ÿ˜Š

์ด ๊ธ€์—์„œ๋Š” ์‹ค์ „์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ • ๋ถ„์„ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์œผ๋กœ ์†Œ๊ฐœํ•  ๊ฑฐ์•ผ. ์ดˆ๋ณด์ž๋„ ๋”ฐ๋ผํ•  ์ˆ˜ ์žˆ๊ฒŒ ์นœ์ ˆํ•˜๊ฒŒ ์„ค๋ช…ํ•  ํ…Œ๋‹ˆ๊นŒ ๊ฑฑ์ • ๋งˆ!

๊ฐ์ • ๋ถ„์„ ํ”„๋กœ์„ธ์Šค ๐Ÿ˜Š ๊ธ์ • Positive ๐Ÿ˜ ์ค‘๋ฆฝ Neutral ๐Ÿ˜ข ๋ถ€์ • Negative ํ…์ŠคํŠธ ์ž…๋ ฅ ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ ์—”์ง„ ๊ฐ์ • ์ ์ˆ˜ ์‚ฐ์ถœ

๐Ÿค” ๊ฐ์ • ๋ถ„์„์ด ๋ญ๊ธธ๋ž˜?

๊ฐ์ • ๋ถ„์„(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')

์‹œ๊ฐํ™”๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด! ๊ฒฝ์˜์ง„์—๊ฒŒ ๋ณด๊ณ ํ•  ๋•Œ๋„ ํ›จ์”ฌ ํšจ๊ณผ์ ์ด์ง€. ๐Ÿ“Š

๊ฐ์ • ๋ถ„์„ ์„ฑ๋Šฅ ๋น„๊ต VADER 85% ์ •ํ™•๋„ TextBlob 78% ์ •ํ™•๋„ BERT 92% ์ •ํ™•๋„ Custom ML 88% ์ •ํ™•๋„ 0% 50% 100%

๐Ÿ”ง ์ปค์Šคํ…€ ๋ชจ๋ธ ํ•™์Šตํ•˜๊ธฐ

๊ธฐ์กด ๋ชจ๋ธ๋„ ์ข‹์ง€๋งŒ, ์ž์‹ ๋งŒ์˜ ๋ฐ์ดํ„ฐ๋กœ ๋ชจ๋ธ์„ ํ•™์Šต์‹œํ‚ค๋ฉด ํ›จ์”ฌ ๋” ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด! ๋จธ์‹ ๋Ÿฌ๋‹์œผ๋กœ ์ปค์Šคํ…€ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ๐ŸŽ“

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 ๋“ฑ)๋Š” ์ตœ๊ณ  ์„ฑ๋Šฅ, ๋ฌธ๋งฅ ์ดํ•ด ๋Šฅ๋ ฅ ํƒ์›”
โœ… ์ปค์Šคํ…€ ๋ชจ๋ธ์€ ํŠน์ • ๋„๋ฉ”์ธ์— ์ตœ์ ํ™” ๊ฐ€๋Šฅ
โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ๋Œ€๋Ÿ‰ ์ฒ˜๋ฆฌ์‹œ ํ•„์ˆ˜ (์บ์‹ฑ, ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ)
โœ… ์‹ค๋ฌด ํ™œ์šฉ์€ ๋ฆฌ๋ทฐ ๋ชจ๋‹ˆํ„ฐ๋ง, ๋ธŒ๋žœ๋“œ ์ถ”์ , ๊ณ ๊ฐ ์„œ๋น„์Šค ๋“ฑ ๋‹ค์–‘
โœ… ํ•œ๊ณ„ ์ธ์‹๋„ ์ค‘์š” - ๋ฐ˜์–ด๋ฒ•, ๋ฌธํ™”์  ์ฐจ์ด, ๋ณตํ•ฉ ๊ฐ์ • ์ฒ˜๋ฆฌ ์–ด๋ ค์›€

์ด์ œ ๋„ˆ๋„ ๊ฐ์ • ๋ถ„์„ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด! ์˜ค๋Š˜ ๋ฐฐ์šด ์ฝ”๋“œ๋“ค์„ ์ง์ ‘ ์‹คํ–‰ํ•ด๋ณด๊ณ , ์ž์‹ ๋งŒ์˜ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ด. ์ฒ˜์Œ์—๋Š” ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ณ„์† ์—ฐ์Šตํ•˜๋‹ค ๋ณด๋ฉด ๊ธˆ๋ฐฉ ์ต์ˆ™ํ•ด์งˆ ๊ฑฐ์•ผ! ๐Ÿ’ช

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•ด์„œ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ๋งŽ์€ ๊ธฐ์—…๋“ค์ด ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ๊ณ  ์žˆ๊ฑฐ๋“ ! ๐ŸŒŸ

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์œ„์—์„œ ์†Œ๊ฐœํ•œ ํ•™์Šต ์ž๋ฃŒ๋“ค์„ ์ฐธ๊ณ ํ•ด๋ด. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ถ€๋”ชํžˆ๋Š” ๋ฌธ์ œ๋“ค์„ ํ•˜๋‚˜์”ฉ ํ•ด๊ฒฐํ•ด๋‚˜๊ฐ€๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ข‹์€ ํ•™์Šต ๋ฐฉ๋ฒ•์ด์•ผ! ๐Ÿš€

์ž, ์ด์ œ ์‹œ์ž‘์ด์•ผ! ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„์˜ ์„ธ๊ณ„๋กœ ๋›ฐ์–ด๋“ค์–ด๋ด! ํ™”์ดํŒ…! ๐ŸŽŠ

Happy Coding! ๐ŸŽ‰ ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„์œผ๋กœ ๋ฐ์ดํ„ฐ์˜ ๊ฐ์ •์„ ์ฝ์–ด๋ณด์„ธ์š”! ๐Ÿ˜Š ๐Ÿš€ ๐Ÿ’ก
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.