์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿง  ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ

๐Ÿง  ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ

ํ…์ŠคํŠธ์™€ ์Œ์„ฑ์—์„œ ๊ฐ์ •์„ ๋ฝ‘์•„๋‚ด๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๊ธฐ์ˆ  ์™„์ „ ์ •๋ณต ๐ŸŽฏ
NLP ๊ฐ์ • AI ์‘์šฉํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋”ฅ๋Ÿฌ๋‹

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

๊ทผ๋ฐ ์ด๊ฒŒ ์ง„์งœ๋กœ ๊ฐ€๋Šฅํ•ด. ๊ทธ๊ฒƒ๋„ ๊ฝค ๋†’์€ ์ •ํ™•๋„๋กœ! ์˜ค๋Š˜์€ ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ(Sentiment Analysis / Emotion Recognition)์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€, ํ…์ŠคํŠธ์™€ ์Œ์„ฑ ๊ฐ๊ฐ์—์„œ ์–ด๋–ป๊ฒŒ ๊ฐ์ •์„ ์ถ”์ถœํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•˜๋Š”์ง€๊นŒ์ง€ ๋‚ฑ๋‚ฑ์ด ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ๐Ÿ”

์‘์šฉํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๊ฝƒ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” ์ด ๋ถ„์•ผ, ๊ฐ™์ด ์‹ ๋‚˜๊ฒŒ ํƒํ—˜ํ•ด๋ณด์ž๊ณ !

๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ ์ „์ฒด ํ๋ฆ„ ๐Ÿ“ ํ…์ŠคํŠธ ์ž…๋ ฅ ๐ŸŽ™๏ธ ์Œ์„ฑ ์ž…๋ ฅ ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ํ† ํฐํ™” / ์ •๊ทœํ™” ์Œ์„ฑ ์ „์ฒ˜๋ฆฌ MFCC / ์ŠคํŽ™ํŠธ๋Ÿผ ๐Ÿค– AI ๋ชจ๋ธ BERT / Transformer CNN / LSTM ๊ฐ์ • ๋ถ„๋ฅ˜ ์—”์ง„ ๋”ฅ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜ ๐Ÿ˜Š ๊ธ์ • (Positive) ๐Ÿ˜ ์ค‘๋ฆฝ (Neutral) ๐Ÿ˜  ๋ถ€์ • (Negative) ํ…์ŠคํŠธ์™€ ์Œ์„ฑ โ†’ ์ „์ฒ˜๋ฆฌ โ†’ AI ๋ชจ๋ธ โ†’ ๊ฐ์ • ๋ ˆ์ด๋ธ” ์ถœ๋ ฅ ๊ฐ์ • ๋ถ„์„์€ ์ž์—ฐ์–ด์ฒ˜๋ฆฌ(NLP)์™€ ์Œ์„ฑ์ธ์‹(ASR)์ด ๊ฒฐํ•ฉ๋œ ์‘์šฉ ๊ธฐ์ˆ ์ด์•ผ!

๐Ÿค” ๊ฐ์ • ๋ถ„์„, ๋„๋Œ€์ฒด ๋ญ์•ผ?

๊ฐ์ • ๋ถ„์„(Sentiment Analysis ๋˜๋Š” Opinion Mining)์€ ํ…์ŠคํŠธ, ์Œ์„ฑ, ์˜์ƒ ๋“ฑ์˜ ๋ฐ์ดํ„ฐ์—์„œ ์‚ฌ๋žŒ์˜ ๊ฐ์ •, ์˜๊ฒฌ, ํƒœ๋„๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ถœํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด "์ด ์‚ฌ๋žŒ์ด ์ง€๊ธˆ ๊ธฐ๋ถ„์ด ์ข‹์€ ๊ฑด์ง€ ๋‚˜์œ ๊ฑด์ง€, ํ™”๊ฐ€ ๋‚ฌ๋Š”์ง€ ์Šฌํ”ˆ์ง€๋ฅผ ์ปดํ“จํ„ฐ๊ฐ€ ์•Œ์•„๋‚ด๋Š” ๊ฒƒ"์ด์ง€.

์ด ๊ธฐ์ˆ ์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ๋ ˆ๋ฒจ๋กœ ๋‚˜๋‰˜์–ด:

๐Ÿ“Š ๊ฐ์ • ๋ถ„์„์˜ ๋‘ ๊ฐ€์ง€ ํฐ ์ถ•

1
๊ฐ์„ฑ ๋ถ„์„ (Sentiment Analysis)
๊ธ์ •(Positive) / ๋ถ€์ •(Negative) / ์ค‘๋ฆฝ(Neutral)์˜ 3๊ฐ€์ง€๋กœ ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ํ˜•ํƒœ. ๋ฆฌ๋ทฐ ๋ถ„์„, ์—ฌ๋ก  ์กฐ์‚ฌ ๋“ฑ์— ๋งŽ์ด ์“ฐ์—ฌ.
2
๊ฐ์ • ์ธ์‹ (Emotion Recognition)
๊ธฐ์จ(Joy), ์Šฌํ””(Sadness), ๋ถ„๋…ธ(Anger), ๊ณตํฌ(Fear), ํ˜์˜ค(Disgust), ๋†€๋ผ์›€(Surprise) ๋“ฑ ์„ธ๋ถ„ํ™”๋œ ๊ฐ์ •์„ ๋ถ„๋ฅ˜. ์‹ฌ๋ฆฌ ์ƒ๋‹ด AI, ๊ณ ๊ฐ ์„œ๋น„์Šค ๋“ฑ์— ํ™œ์šฉ.
๐Ÿ’ก ์™œ ์ด๊ฒŒ ์ค‘์š”ํ•˜๋ƒ๊ณ ?
์ „ ์„ธ๊ณ„์—์„œ ํ•˜๋ฃจ์— ์ƒ์„ฑ๋˜๋Š” ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๋งŒ ํ•ด๋„ ์–ด๋งˆ์–ด๋งˆํ•ด. ํŠธ์œ„ํ„ฐ ํ•˜๋ฃจ ๊ฒŒ์‹œ๋ฌผ 5์–ต ๊ฐœ, ์œ ํŠœ๋ธŒ ๋Œ“๊ธ€ ์ˆ˜์–ต ๊ฐœ, ์‡ผํ•‘๋ชฐ ๋ฆฌ๋ทฐ... ์ด๊ฑธ ์‚ฌ๋žŒ์ด ์ผ์ผ์ด ์ฝ๊ณ  ๋ถ„์„ํ•˜๋Š” ๊ฑด ๋ถˆ๊ฐ€๋Šฅํ•˜์ž–์•„. ๊ฐ์ • ๋ถ„์„ AI๊ฐ€ ์ด ๋ชจ๋“  ๊ฑธ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฑฐ์•ผ!

๊ฐ์ • ๋ถ„์„์˜ ์—ญ์‚ฌ๋ฅผ ์ž ๊น ์‚ดํŽด๋ณด๋ฉด, 2000๋…„๋Œ€ ์ดˆ๋ฐ˜์—๋Š” ๋‹จ์ˆœํžˆ "์ข‹๋‹ค", "๋‚˜์˜๋‹ค" ๊ฐ™์€ ๋‹จ์–ด๋ฅผ ์„ธ๋Š” ์‚ฌ์ „ ๊ธฐ๋ฐ˜(Lexicon-based) ๋ฐฉ์‹์ด ์ฃผ๋ฅ˜์˜€์–ด. ๊ทธ๋Ÿฌ๋‹ค๊ฐ€ ๋จธ์‹ ๋Ÿฌ๋‹์ด ๋ฐœ์ „ํ•˜๋ฉด์„œ SVM, Naive Bayes ๊ฐ™์€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๋“ฑ์žฅํ–ˆ๊ณ , 2017๋…„ ์ดํ›„ Transformer ์•„ํ‚คํ…์ฒ˜์™€ BERT์˜ ๋“ฑ์žฅ์œผ๋กœ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์‹œ๋Œ€๊ฐ€ ์—ด๋ ธ์ง€. ์ง€๊ธˆ์€ GPT ๊ณ„์—ด ๋ชจ๋ธ๋“ค์ด ๊ฐ์ • ๋ถ„์„์—์„œ๋„ ์—„์ฒญ๋‚œ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์–ด.


๐Ÿ“ ํ…์ŠคํŠธ์—์„œ ๊ฐ์ • ์ถ”์ถœํ•˜๊ธฐ

ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„์€ ๊ฐ€์žฅ ๋„๋ฆฌ ์—ฐ๊ตฌ๋˜๊ณ  ํ™œ์šฉ๋˜๋Š” ๋ถ„์•ผ์•ผ. ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€ ์ ‘๊ทผ๋ฒ•์ด ์žˆ์–ด:

๐Ÿ”ค ๋ฐฉ๋ฒ• 1: ์‚ฌ์ „ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ๋ฒ• (Lexicon-based)

๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ๊ฐ์ • ๋‹จ์–ด ์‚ฌ์ „์„ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด "ํ–‰๋ณต", "์ข‹์•„", "์ตœ๊ณ " ๊ฐ™์€ ๋‹จ์–ด๋Š” ๊ธ์ • ์ ์ˆ˜๋ฅผ, "์ตœ์•…", "์‹ค๋ง", "์งœ์ฆ" ๊ฐ™์€ ๋‹จ์–ด๋Š” ๋ถ€์ • ์ ์ˆ˜๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , ๋ฌธ์žฅ ์ „์ฒด์˜ ์ ์ˆ˜๋ฅผ ํ•ฉ์‚ฐํ•ด์„œ ๊ฐ์ •์„ ํŒ๋‹จํ•ด.

๋Œ€ํ‘œ์ ์ธ ์‚ฌ์ „์œผ๋กœ๋Š” VADER(Valence Aware Dictionary and sEntiment Reasoner), SentiWordNet, ํ•œ๊ตญ์–ด๋Š” KNU ํ•œ๊ตญ์–ด ๊ฐ์„ฑ์‚ฌ์ „ ๋“ฑ์ด ์žˆ์–ด.

์žฅ์ : ๋น ๋ฅด๊ณ  ํ•ด์„ ๊ฐ€๋Šฅ, ํ•™์Šต ๋ฐ์ดํ„ฐ ๋ถˆํ•„์š”
๋‹จ์ : ๋ฌธ๋งฅ ํŒŒ์•… ๋ถˆ๊ฐ€, ์‹ ์กฐ์–ดยท์€์–ด์— ์ทจ์•ฝ, ๋น„๊ผฌ๋Š” ํ‘œํ˜„(์•„์ด๋Ÿฌ๋‹ˆ) ์ฒ˜๋ฆฌ ์–ด๋ ค์›€

์˜ˆ์‹œ: "์ด ์˜ํ™” ์ง„์งœ ์ตœ์•…์ด์•ผ ๊ทผ๋ฐ ์™œ ์ด๋ ‡๊ฒŒ ์žฌ๋ฐŒ์ง€?" โ†’ ์‚ฌ์ „ ๊ธฐ๋ฐ˜์€ "์ตœ์•…" ๋•Œ๋ฌธ์— ๋ถ€์ •์œผ๋กœ ๋ถ„๋ฅ˜ํ•  ์ˆ˜ ์žˆ์–ด ๐Ÿ˜…

๐Ÿค– ๋ฐฉ๋ฒ• 2: ๋จธ์‹ ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜

๋ ˆ์ด๋ธ”์ด ๋‹ฌ๋ฆฐ ํ•™์Šต ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด์„œ ๋ชจ๋ธ์„ ํ›ˆ๋ จ์‹œํ‚ค๋Š” ๋ฐฉ์‹์ด์•ผ. ํ…์ŠคํŠธ๋ฅผ ์ˆ˜์น˜ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜(TF-IDF, Word2Vec ๋“ฑ)ํ•œ ๋‹ค์Œ, ๋ถ„๋ฅ˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ ์šฉํ•ด.

# ์‚ฌ์ดํ‚ท๋Ÿฐ์œผ๋กœ ๊ฐ„๋‹จํ•œ ๊ฐ์ • ๋ถ„๋ฅ˜๊ธฐ ๋งŒ๋“ค๊ธฐ
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

# ํ•™์Šต ๋ฐ์ดํ„ฐ ์˜ˆ์‹œ
texts = [
    "์ด ์ œํ’ˆ ์ •๋ง ์ตœ๊ณ ์˜ˆ์š”!",
    "์™„์ „ ์‹ค๋งํ–ˆ์–ด์š” ๋‹ค์‹œ๋Š” ์•ˆ ์‚ฝ๋‹ˆ๋‹ค",
    "๋ฐฐ์†ก์ด ๋น ๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ์ข‹๋„ค์š”",
    "๋ˆ ์•„๊น๊ณ  ํ’ˆ์งˆ ์ตœ์•…",
    "๊ทธ๋ƒฅ ํ‰๋ฒ”ํ•ด์š” ๋ณดํ†ต์ด์—์š”"
]
labels = ['๊ธ์ •', '๋ถ€์ •', '๊ธ์ •', '๋ถ€์ •', '์ค‘๋ฆฝ']

# ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ
pipeline = Pipeline([
    ('tfidf', TfidfVectorizer(analyzer='char', ngram_range=(2, 4))),
    ('clf', LogisticRegression(max_iter=1000))
])

pipeline.fit(texts, labels)

# ์˜ˆ์ธก
test = ["์ด๊ฑฐ ์ง„์งœ ๋Œ€๋ฐ•์ด์—์š”!"]
print(pipeline.predict(test))  # ['๊ธ์ •']

๐Ÿง  ๋ฐฉ๋ฒ• 3: ๋”ฅ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜ (ํ˜„์žฌ ์ตœ๊ฐ•์ž)

ํ˜„์žฌ ๊ฐ€์žฅ ๋†’์€ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์ด์•ผ. ํŠนํžˆ BERT(Bidirectional Encoder Representations from Transformers)์™€ ๊ทธ ๋ณ€ํ˜• ๋ชจ๋ธ๋“ค์ด ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„์—์„œ ์••๋„์ ์ธ ์„ฑ๋Šฅ์„ ์ž๋ž‘ํ•ด.

ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ถ„์„์—๋Š” KoBERT, KoELECTRA, KLUE-RoBERTa ๊ฐ™์€ ํ•œ๊ตญ์–ด ํŠนํ™” ๋ชจ๋ธ๋“ค์ด ์žˆ์–ด.

# KoBERT ๊ธฐ๋ฐ˜ ๊ฐ์ • ๋ถ„์„ (transformers ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ)
from transformers import pipeline

# ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ถ„์„ ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋“œ
# snunlp/KR-FinBert-SC ๋˜๋Š” monologg/koelectra-base-finetuned-sentiment
sentiment_analyzer = pipeline(
    "text-classification",
    model="snunlp/KR-FinBert-SC",
    tokenizer="snunlp/KR-FinBert-SC"
)

texts = [
    "์˜ค๋Š˜ ๋‚ ์”จ๋„ ์ข‹๊ณ  ๊ธฐ๋ถ„๋„ ์ตœ๊ณ ์•ผ!",
    "์™œ ์ด๋ ‡๊ฒŒ ์ผ์ด ์•ˆ ํ’€๋ฆฌ์ง€... ๋„ˆ๋ฌด ํž˜๋“ค๋‹ค",
    "๊ทธ๋ƒฅ ํ‰๋ฒ”ํ•œ ํ•˜๋ฃจ์˜€์–ด"
]

for text in texts:
    result = sentiment_analyzer(text)
    print(f"ํ…์ŠคํŠธ: {text}")
    print(f"๊ฐ์ •: {result[0]['label']}, ํ™•์‹ ๋„: {result[0]['score']:.3f}")
    print()
BERT ๊ธฐ๋ฐ˜ ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„ ๊ตฌ์กฐ ์ž…๋ ฅ ๋ฌธ์žฅ: "์ด ์˜ํ™” ์ •๋ง ๊ฐ๋™์ ์ด์•ผ!" [CLS] ์ด ์˜ํ™” ์ •๋ง ๊ฐ๋™์ ์ด์•ผ ! [SEP] ๐Ÿค– BERT ์ธ์ฝ”๋” (12๊ฐœ Transformer ๋ ˆ์ด์–ด) ์–‘๋ฐฉํ–ฅ Self-Attention โ†’ ๋ฌธ๋งฅ ์ดํ•ด โ†’ ๊ฐ์ • ํŒจํ„ด ํ•™์Šต [CLS] ๋ฒกํ„ฐ ์ถ”์ถœ 768์ฐจ์› ๋ฌธ์žฅ ํ‘œํ˜„ ๋ฒกํ„ฐ ๐Ÿ˜Š ๊ธ์ • (ํ™•์‹ ๋„: 97.3%)
โœ… ์‹ค์ „ ํŒ! ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ถ„์„์„ ํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ํ•œ๊ตญ์–ด๋กœ ์‚ฌ์ „ํ•™์Šต๋œ ๋ชจ๋ธ์„ ์จ์•ผ ํ•ด. ์˜์–ด BERT๋ฅผ ํ•œ๊ตญ์–ด์— ๊ทธ๋ƒฅ ์“ฐ๋ฉด ์„ฑ๋Šฅ์ด ํ™• ๋–จ์–ด์ง€๊ฑฐ๋“ . KoBERT, KoELECTRA, KLUE-RoBERTa ์ค‘์—์„œ ๊ณจ๋ผ๋ด!

๐Ÿ”ง ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ: ๊ฐ์ • ๋ถ„์„์˜ ์ˆจ์€ ์˜์›…

์•„๋ฌด๋ฆฌ ์ข‹์€ AI ๋ชจ๋ธ๋„ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๊ฐ€ ์—‰๋ง์ด๋ฉด ๊ฒฐ๊ณผ๋„ ์—‰๋ง์ด์•ผ. "์“ฐ๋ ˆ๊ธฐ ์ž…๋ ฅ โ†’ ์“ฐ๋ ˆ๊ธฐ ์ถœ๋ ฅ(Garbage In, Garbage Out)"์ด๋ผ๋Š” ๋ง์ด ์žˆ์ž–์•„. ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ๋Š” ๊ฐ์ • ๋ถ„์„์˜ ์„ฑ๋Šฅ์„ ์ขŒ์šฐํ•˜๋Š” ํ•ต์‹ฌ ๋‹จ๊ณ„์•ผ!

์ „์ฒ˜๋ฆฌ ๋‹จ๊ณ„ ์„ค๋ช… ์˜ˆ์‹œ
์ •๊ทœํ™” ๋Œ€์†Œ๋ฌธ์ž ํ†ต์ผ, ํŠน์ˆ˜๋ฌธ์ž ์ œ๊ฑฐ ใ…‹ใ…‹ใ…‹ใ…‹ โ†’ ใ…‹, !!!!! โ†’ !
ํ† ํฐํ™” ๋ฌธ์žฅ์„ ๋‹จ์–ด/ํ˜•ํƒœ์†Œ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ "๋‚˜๋Š” ํ–‰๋ณตํ•ด" โ†’ ["๋‚˜", "๋Š”", "ํ–‰๋ณต", "ํ•ด"]
๋ถˆ์šฉ์–ด ์ œ๊ฑฐ ๊ฐ์ •๊ณผ ๋ฌด๊ด€ํ•œ ๋‹จ์–ด ์ œ๊ฑฐ "์ด", "๊ทธ", "์ €", "๊ฒƒ" ๋“ฑ
์ด๋ชจ์ง€ ์ฒ˜๋ฆฌ ์ด๋ชจ์ง€๋ฅผ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ ๋˜๋Š” ์ œ๊ฑฐ ๐Ÿ˜Š โ†’ "ํ–‰๋ณต", โค๏ธ โ†’ "์‚ฌ๋ž‘"
๋งž์ถค๋ฒ• ๊ต์ • ์˜คํƒˆ์ž ์ˆ˜์ • "์กฐ์•„์š”" โ†’ "์ข‹์•„์š”"
# ํ•œ๊ตญ์–ด ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ์˜ˆ์‹œ
import re
from konlpy.tag import Okt

okt = Okt()

def preprocess_korean(text):
    # 1. URL ์ œ๊ฑฐ
    text = re.sub(r'http\S+|www\S+', '', text)
    
    # 2. ์ด๋ฉ”์ผ ์ œ๊ฑฐ
    text = re.sub(r'\S+@\S+', '', text)
    
    # 3. ๋ฐ˜๋ณต ๋ฌธ์ž ์ •๊ทœํ™” (ใ…‹ใ…‹ใ…‹ใ…‹ โ†’ ใ…‹ใ…‹)
    text = re.sub(r'(.)\1{2,}', r'\1\1', text)
    
    # 4. ํŠน์ˆ˜๋ฌธ์ž ์ œ๊ฑฐ (ํ•œ๊ธ€, ์˜๋ฌธ, ์ˆซ์ž, ๊ธฐ๋ณธ ๋ฌธ์žฅ๋ถ€ํ˜ธ๋งŒ ์œ ์ง€)
    text = re.sub(r'[^\w\s๊ฐ€-ํžฃใ„ฑ-ใ…Žใ…-ใ…ฃ.,!?]', ' ', text)
    
    # 5. ํ˜•ํƒœ์†Œ ๋ถ„์„ ๋ฐ ํ’ˆ์‚ฌ ํƒœ๊น…
    morphs = okt.pos(text, norm=True, stem=True)
    
    # 6. ๋ช…์‚ฌ, ํ˜•์šฉ์‚ฌ, ๋™์‚ฌ๋งŒ ์ถ”์ถœ (๊ฐ์ • ๊ด€๋ จ ํ’ˆ์‚ฌ)
    filtered = [word for word, pos in morphs 
                if pos in ['Noun', 'Adjective', 'Verb', 'Adverb']]
    
    return ' '.join(filtered)

# ํ…Œ์ŠคํŠธ
raw_text = "์ด ์˜ํ™” ใ…ˆใ„ด ์žฌ๋ฐŒ์—ˆ์–ด์š”!!!! ์™„์ „ ๊ฐ•์ถ”ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹"
processed = preprocess_korean(raw_text)
print(f"์›๋ณธ: {raw_text}")
print(f"์ „์ฒ˜๋ฆฌ ํ›„: {processed}")
โš ๏ธ ์ฃผ์˜! ํ•œ๊ตญ์–ด๋Š” ์˜์–ด์™€ ๋‹ฌ๋ฆฌ ๋„์–ด์“ฐ๊ธฐ๊ฐ€ ๋ถˆ๊ทœ์น™ํ•˜๊ณ  ํ˜•ํƒœ์†Œ ๋ถ„์„์ด ๋ณต์žกํ•ด. KoNLPy์˜ Okt, Mecab, Komoran ์ค‘์—์„œ ๋ชฉ์ ์— ๋งž๋Š” ๊ฑธ ์„ ํƒํ•ด์•ผ ํ•ด. ์†๋„๋Š” Mecab์ด ๊ฐ€์žฅ ๋น ๋ฅด๊ณ , ์ •ํ™•๋„๋Š” Mecab์ด๋‚˜ Komoran์ด ์ข‹์•„.

๐ŸŽ™๏ธ ์Œ์„ฑ์—์„œ ๊ฐ์ • ์ถ”์ถœํ•˜๊ธฐ: ์†Œ๋ฆฌ๋กœ ๋งˆ์Œ ์ฝ๊ธฐ

์Œ์„ฑ ๊ฐ์ • ๋ถ„์„(Speech Emotion Recognition, SER)์€ ํ…์ŠคํŠธ ๋ถ„์„๋ณด๋‹ค ํ›จ์”ฌ ๋ณต์žกํ•˜๊ณ  ๋„์ „์ ์ธ ๋ถ„์•ผ์•ผ. ์™œ๋ƒํ•˜๋ฉด ์Œ์„ฑ์—๋Š” ํ…์ŠคํŠธ ๋‚ด์šฉ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์–ต์–‘, ์Œ๋†’์ด, ๋ง ์†๋„, ์Œ๋Ÿ‰, ์Œ์ƒ‰ ๊ฐ™์€ ๋น„์–ธ์–ด์  ์ •๋ณด๊ฐ€ ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์ด ๋‹ด๊ฒจ ์žˆ๊ฑฐ๋“ .

์˜ˆ๋ฅผ ๋“ค์–ด "๊ทธ๋ž˜, ์•Œ๊ฒ ์–ด"๋ผ๋Š” ๋ง๋„ ์–ด๋–ป๊ฒŒ ๋งํ•˜๋А๋ƒ์— ๋”ฐ๋ผ ๊ธฐ์จ, ๋ถ„๋…ธ, ์Šฌํ””, ์ฒด๋… ๋“ฑ ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ฐ์ •์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์ž–์•„!

์Œ์„ฑ ๊ฐ์ • ๋ถ„์„ ํŒŒ์ดํ”„๋ผ์ธ ์›์‹œ ์Œ์„ฑ ์‹ ํ˜ธ (Raw Audio Waveform) ๐Ÿ”ฌ ์Œํ–ฅ ํŠน์ง• ์ถ”์ถœ (Feature Extraction) MFCC ๋ฉœ ์ฃผํŒŒ์ˆ˜ ์ผ‘์ŠคํŠธ๋Ÿผ ๊ณ„์ˆ˜ (13~40๊ฐœ) ํ”ผ์น˜ (F0) ๊ธฐ๋ณธ ์ฃผํŒŒ์ˆ˜ ์Œ๋†’์ด ๋ณ€ํ™” ํŒจํ„ด ์—๋„ˆ์ง€/์Œ๋Ÿ‰ RMS Energy ๋ง์˜ ๊ฐ•๋„ ๋ณ€ํ™” ์ŠคํŽ™ํŠธ๋Ÿผ ํŠน์ง• Spectral Centroid Zero Crossing Rate ๐Ÿง  ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ (CNN + LSTM / Wav2Vec 2.0) ์‹œ๊ณ„์—ด ํŒจํ„ด ํ•™์Šต โ†’ ๊ฐ์ • ํŠน์ง• ์ถ”์ถœ โ†’ ๋ถ„๋ฅ˜ ๐Ÿ˜Š ๊ธฐ์จ ๐Ÿ˜ข ์Šฌํ”” ๐Ÿ˜  ๋ถ„๋…ธ ๐Ÿ˜จ ๊ณตํฌ ๐Ÿ˜ฒ ๋†€๋ผ์›€

๐ŸŽต ํ•ต์‹ฌ ์Œํ–ฅ ํŠน์ง•: MFCC๊ฐ€ ๋ญ์•ผ?

MFCC(Mel-Frequency Cepstral Coefficients)๋Š” ์Œ์„ฑ ์ฒ˜๋ฆฌ์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ํŠน์ง•์ด์•ผ. ์ธ๊ฐ„์˜ ์ฒญ๊ฐ ์‹œ์Šคํ…œ์ด ์†Œ๋ฆฌ๋ฅผ ์ธ์‹ํ•˜๋Š” ๋ฐฉ์‹์„ ์ˆ˜ํ•™์ ์œผ๋กœ ๋ชจ๋ธ๋งํ•œ ๊ฑฐ์•ผ.

์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด: ์Œ์„ฑ ์‹ ํ˜ธ๋ฅผ ์งง์€ ๊ตฌ๊ฐ„์œผ๋กœ ๋‚˜๋ˆ„๊ณ  โ†’ ๊ฐ ๊ตฌ๊ฐ„์˜ ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์„ ๋ถ„์„ํ•˜๊ณ  โ†’ ์ธ๊ฐ„ ๊ท€์˜ ํŠน์„ฑ(๋ฉœ ์Šค์ผ€์ผ)์— ๋งž๊ฒŒ ๋ณ€ํ™˜ํ•˜๊ณ  โ†’ ์••์ถ•ํ•ด์„œ 13~40๊ฐœ์˜ ์ˆซ์ž๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฑฐ์•ผ. ์ด ์ˆซ์ž๋“ค์ด ๊ทธ ์Œ์„ฑ ๊ตฌ๊ฐ„์˜ "์Œ์ƒ‰ ์ง€๋ฌธ" ๊ฐ™์€ ์—ญํ• ์„ ํ•ด!

# librosa๋กœ ์Œ์„ฑ ํŠน์ง• ์ถ”์ถœํ•˜๊ธฐ
import librosa
import numpy as np

def extract_audio_features(audio_path):
    # ์Œ์„ฑ ํŒŒ์ผ ๋กœ๋“œ
    y, sr = librosa.load(audio_path, sr=22050)
    
    # 1. MFCC ์ถ”์ถœ (40๊ฐœ ๊ณ„์ˆ˜)
    mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40)
    mfcc_mean = np.mean(mfcc, axis=1)  # ์‹œ๊ฐ„ ์ถ• ํ‰๊ท 
    mfcc_std = np.std(mfcc, axis=1)   # ์‹œ๊ฐ„ ์ถ• ํ‘œ์ค€ํŽธ์ฐจ
    
    # 2. ํ”ผ์น˜(F0) ์ถ”์ถœ
    pitches, magnitudes = librosa.piptrack(y=y, sr=sr)
    pitch_mean = np.mean(pitches[pitches > 0]) if np.any(pitches > 0) else 0
    
    # 3. ์—๋„ˆ์ง€ (RMS)
    rms = librosa.feature.rms(y=y)
    rms_mean = np.mean(rms)
    
    # 4. ์ŠคํŽ™ํŠธ๋Ÿผ ์ค‘์‹ฌ (Spectral Centroid)
    spectral_centroid = librosa.feature.spectral_centroid(y=y, sr=sr)
    sc_mean = np.mean(spectral_centroid)
    
    # 5. Zero Crossing Rate
    zcr = librosa.feature.zero_crossing_rate(y)
    zcr_mean = np.mean(zcr)
    
    # 6. Chroma ํŠน์ง•
    chroma = librosa.feature.chroma_stft(y=y, sr=sr)
    chroma_mean = np.mean(chroma, axis=1)
    
    # ๋ชจ๋“  ํŠน์ง• ํ•ฉ์น˜๊ธฐ
    features = np.concatenate([
        mfcc_mean, mfcc_std,
        [pitch_mean, rms_mean, sc_mean, zcr_mean],
        chroma_mean
    ])
    
    return features

# ํŠน์ง• ์ถ”์ถœ ํ›„ ๋ชจ๋ธ์— ์ž…๋ ฅ
features = extract_audio_features("sample_audio.wav")
print(f"์ถ”์ถœ๋œ ํŠน์ง• ๋ฒกํ„ฐ ํฌ๊ธฐ: {features.shape}")
# ์ถœ๋ ฅ: ์ถ”์ถœ๋œ ํŠน์ง• ๋ฒกํ„ฐ ํฌ๊ธฐ: (96,)

๐Ÿš€ ์ตœ์‹  ํŠธ๋ Œ๋“œ: Wav2Vec 2.0๊ณผ ์—”๋“œํˆฌ์—”๋“œ ํ•™์Šต

์ตœ๊ทผ์—๋Š” ์ˆ˜๋™์œผ๋กœ ํŠน์ง•์„ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ์‹ ๋Œ€์‹ , ์›์‹œ ์Œ์„ฑ ํŒŒํ˜•(raw waveform)์„ ์ง์ ‘ ์ž…๋ ฅ๋ฐ›์•„์„œ ํŠน์ง• ์ถ”์ถœ๋ถ€ํ„ฐ ๋ถ„๋ฅ˜๊นŒ์ง€ ํ•œ ๋ฒˆ์— ํ•™์Šตํ•˜๋Š” ์—”๋“œํˆฌ์—”๋“œ(End-to-End) ๋ฐฉ์‹์ด ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ์–ด.

Facebook AI(ํ˜„ Meta)๊ฐ€ ๊ฐœ๋ฐœํ•œ Wav2Vec 2.0์€ ๋Œ€๊ทœ๋ชจ ๋น„์ง€๋„ ํ•™์Šต์œผ๋กœ ์Œ์„ฑ์˜ ํ‘œํ˜„์„ ํ•™์Šตํ•œ ๋‹ค์Œ, ์†Œ๋Ÿ‰์˜ ๋ ˆ์ด๋ธ” ๋ฐ์ดํ„ฐ๋กœ ํŒŒ์ธํŠœ๋‹ํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ. ์Œ์„ฑ ๊ฐ์ • ์ธ์‹์—์„œ SOTA(State of the Art) ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์–ด.

# Wav2Vec 2.0 ๊ธฐ๋ฐ˜ ์Œ์„ฑ ๊ฐ์ • ๋ถ„์„
from transformers import Wav2Vec2ForSequenceClassification, Wav2Vec2Processor
import torch
import librosa

# ๋ชจ๋ธ ๋กœ๋“œ (๊ฐ์ • ๋ถ„๋ฅ˜์šฉ ํŒŒ์ธํŠœ๋‹๋œ ๋ชจ๋ธ)
model_name = "ehcalabres/wav2vec2-lg-xlsr-en-speech-emotion-recognition"
processor = Wav2Vec2Processor.from_pretrained(model_name)
model = Wav2Vec2ForSequenceClassification.from_pretrained(model_name)

def predict_emotion(audio_path):
    # ์Œ์„ฑ ๋กœ๋“œ (16kHz๋กœ ๋ฆฌ์ƒ˜ํ”Œ๋ง)
    speech, sr = librosa.load(audio_path, sr=16000)
    
    # ์ „์ฒ˜๋ฆฌ
    inputs = processor(
        speech, 
        sampling_rate=16000, 
        return_tensors="pt", 
        padding=True
    )
    
    # ์ถ”๋ก 
    with torch.no_grad():
        logits = model(**inputs).logits
    
    # ๊ฐ์ • ๋ ˆ์ด๋ธ” ๋ณ€ํ™˜
    predicted_id = torch.argmax(logits, dim=-1).item()
    emotion_labels = ['๋ถ„๋…ธ', 'ํ˜์˜ค', '๊ณตํฌ', '๊ธฐ์จ', '์ค‘๋ฆฝ', '์Šฌํ””', '๋†€๋ผ์›€']
    
    # ์†Œํ”„ํŠธ๋งฅ์Šค๋กœ ํ™•๋ฅ  ๊ณ„์‚ฐ
    probs = torch.softmax(logits, dim=-1)[0]
    
    return {
        'emotion': emotion_labels[predicted_id],
        'confidence': probs[predicted_id].item(),
        'all_probs': {label: prob.item() 
                      for label, prob in zip(emotion_labels, probs)}
    }

result = predict_emotion("test_audio.wav")
print(f"๊ฐ์ง€๋œ ๊ฐ์ •: {result['emotion']}")
print(f"ํ™•์‹ ๋„: {result['confidence']:.2%}")

๐Ÿ”€ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๊ฐ์ • ๋ถ„์„: ํ…์ŠคํŠธ + ์Œ์„ฑ ํ•ฉ์น˜๊ธฐ

ํ…์ŠคํŠธ๋งŒ ๋ถ„์„ํ•˜๊ฑฐ๋‚˜ ์Œ์„ฑ๋งŒ ๋ถ„์„ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค, ๋‘ ๊ฐ€์ง€๋ฅผ ํ•จ๊ป˜ ๋ถ„์„ํ•˜๋ฉด ํ›จ์”ฌ ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด. ์ด๊ฑธ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ(Multimodal) ๊ฐ์ • ๋ถ„์„์ด๋ผ๊ณ  ํ•ด.

์‹ค์ œ๋กœ ์‚ฌ๋žŒ๋„ ๋Œ€ํ™”ํ•  ๋•Œ ๋ง์˜ ๋‚ด์šฉ(ํ…์ŠคํŠธ)๊ณผ ๋ชฉ์†Œ๋ฆฌ ํ†ค(์Œ์„ฑ)์„ ๋™์‹œ์— ๊ณ ๋ คํ•ด์„œ ์ƒ๋Œ€๋ฐฉ์˜ ๊ฐ์ •์„ ํŒŒ์•…ํ•˜์ž–์•„. AI๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์•ผ!

๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๊ฐ์ • ๋ถ„์„ ์œตํ•ฉ ๊ตฌ์กฐ ๐Ÿ“ ํ…์ŠคํŠธ ์ž…๋ ฅ "์ •๋ง ํ™”๊ฐ€ ๋‚˜!" BERT ์ธ์ฝ”๋” ํ…์ŠคํŠธ ์ž„๋ฒ ๋”ฉ ๋ฒกํ„ฐ ๐ŸŽ™๏ธ ์Œ์„ฑ ์ž…๋ ฅ ๋†’์€ ํ”ผ์น˜, ๋น ๋ฅธ ์†๋„ Wav2Vec ์ธ์ฝ”๋” ์Œ์„ฑ ์ž„๋ฒ ๋”ฉ ๋ฒกํ„ฐ ๐Ÿ”€ ํฌ๋กœ์Šค-๋ชจ๋‹ฌ ์–ดํ…์…˜ ์œตํ•ฉ ํ…์ŠคํŠธ โ†” ์Œ์„ฑ ์ƒํ˜ธ์ž‘์šฉ ํ•™์Šต ๐Ÿ˜  ๋ถ„๋…ธ (์ •ํ™•๋„ +15% ํ–ฅ์ƒ!)

๐Ÿ”ฌ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์œตํ•ฉ ๋ฐฉ๋ฒ• 3๊ฐ€์ง€

1
์ดˆ๊ธฐ ์œตํ•ฉ (Early Fusion)
ํ…์ŠคํŠธ์™€ ์Œ์„ฑ ํŠน์ง•์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ํ•ฉ์ณ์„œ ํ•˜๋‚˜์˜ ๋ชจ๋ธ์— ์ž…๋ ฅ. ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๊ฐ ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ์˜ ํŠน์„ฑ์„ ์ถฉ๋ถ„ํžˆ ํ™œ์šฉํ•˜๊ธฐ ์–ด๋ ค์›Œ.
2
ํ›„๊ธฐ ์œตํ•ฉ (Late Fusion)
ํ…์ŠคํŠธ ๋ชจ๋ธ๊ณผ ์Œ์„ฑ ๋ชจ๋ธ์„ ๊ฐ๊ฐ ๋”ฐ๋กœ ํ•™์Šต์‹œํ‚จ ๋‹ค์Œ, ์ตœ์ข… ์˜ˆ์ธก ๊ฒฐ๊ณผ๋ฅผ ์•™์ƒ๋ธ”. ๊ฐ ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ์˜ ๊ฐ•์ ์„ ์‚ด๋ฆด ์ˆ˜ ์žˆ์–ด.
3
ํฌ๋กœ์Šค-๋ชจ๋‹ฌ ์–ดํ…์…˜ (Cross-Modal Attention)
ํ…์ŠคํŠธ์™€ ์Œ์„ฑ์ด ์„œ๋กœ ์–ดํ…์…˜์„ ์ฃผ๊ณ ๋ฐ›์œผ๋ฉฐ ์ƒํ˜ธ์ž‘์šฉ. ํ˜„์žฌ ๊ฐ€์žฅ ๋†’์€ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์ด์•ผ. CMT(Cross-Modal Transformer) ๊ฐ™์€ ๋ชจ๋ธ์ด ๋Œ€ํ‘œ์ .
# ํ›„๊ธฐ ์œตํ•ฉ(Late Fusion) ๋ฐฉ์‹ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๊ฐ์ • ๋ถ„์„
import torch
import torch.nn as nn
import numpy as np

class MultimodalSentimentFusion(nn.Module):
    def __init__(self, text_dim=768, audio_dim=96, num_classes=7):
        super().__init__()
        
        # ํ…์ŠคํŠธ ๋ธŒ๋žœ์น˜
        self.text_encoder = nn.Sequential(
            nn.Linear(text_dim, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, 128)
        )
        
        # ์Œ์„ฑ ๋ธŒ๋žœ์น˜
        self.audio_encoder = nn.Sequential(
            nn.Linear(audio_dim, 128),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(128, 128)
        )
        
        # ํฌ๋กœ์Šค-๋ชจ๋‹ฌ ์–ดํ…์…˜
        self.cross_attention = nn.MultiheadAttention(
            embed_dim=128, num_heads=4, batch_first=True
        )
        
        # ์ตœ์ข… ๋ถ„๋ฅ˜๊ธฐ
        self.classifier = nn.Sequential(
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(128, num_classes)
        )
    
    def forward(self, text_features, audio_features):
        # ๊ฐ ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ ์ธ์ฝ”๋”ฉ
        text_enc = self.text_encoder(text_features).unsqueeze(1)
        audio_enc = self.audio_encoder(audio_features).unsqueeze(1)
        
        # ํฌ๋กœ์Šค-๋ชจ๋‹ฌ ์–ดํ…์…˜ (ํ…์ŠคํŠธ๊ฐ€ ์Œ์„ฑ์— ์–ดํ…์…˜)
        fused, _ = self.cross_attention(
            query=text_enc, 
            key=audio_enc, 
            value=audio_enc
        )
        
        # ํ…์ŠคํŠธ์™€ ์œตํ•ฉ ๊ฒฐ๊ณผ ํ•ฉ์น˜๊ธฐ
        combined = torch.cat([
            text_enc.squeeze(1), 
            fused.squeeze(1)
        ], dim=-1)
        
        # ์ตœ์ข… ๋ถ„๋ฅ˜
        output = self.classifier(combined)
        return output

# ๋ชจ๋ธ ์ƒ์„ฑ ๋ฐ ํ…Œ์ŠคํŠธ
model = MultimodalSentimentFusion()
text_feat = torch.randn(4, 768)   # ๋ฐฐ์น˜ ํฌ๊ธฐ 4
audio_feat = torch.randn(4, 96)

output = model(text_feat, audio_feat)
print(f"์ถœ๋ ฅ ํฌ๊ธฐ: {output.shape}")  # torch.Size([4, 7])
print(f"์˜ˆ์ธก ๊ฐ์ •: {torch.argmax(output, dim=1)}")

๐Ÿ› ๏ธ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์‹ค์‹œ๊ฐ„ ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ ๋งŒ๋“ค๊ธฐ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ์—ฌ๊ธฐ์„œ๋Š” ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๊ฐ์ •์„ ๋ถ„์„ํ•˜๊ณ  ์‹œ๊ฐํ™”ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์›น ์•ฑ์„ ๋งŒ๋“ค ๊ฑฐ์•ผ. Python์˜ Flask์™€ Hugging Face Transformers๋ฅผ ํ™œ์šฉํ• ๊ฒŒ.

๐ŸŽฏ ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ: ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด โ†’ ๊ฐ์ • ๋ถ„์„ โ†’ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ชจ์ง€์™€ ํ™•๋ฅ ๋กœ ํ‘œ์‹œํ•˜๋Š” ์›น ์•ฑ

์‚ฌ์šฉ ๊ธฐ์ˆ : Python, Flask, Transformers, Chart.js

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

emotion_analyzer/
โ”œโ”€โ”€ app.py              # Flask ์„œ๋ฒ„
โ”œโ”€โ”€ model.py            # ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ
โ”œโ”€โ”€ requirements.txt    # ์˜์กด์„ฑ ํŒจํ‚ค์ง€
โ””โ”€โ”€ templates/
    โ””โ”€โ”€ index.html      # ํ”„๋ก ํŠธ์—”๋“œ

๐Ÿ model.py - ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ

# model.py
from transformers import pipeline
import torch

class EmotionAnalyzer:
    def __init__(self):
        # ๋‹ค์ค‘ ๊ฐ์ • ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๋กœ๋“œ
        # j-hartmann/emotion-english-distilroberta-base: 7๊ฐ€์ง€ ๊ฐ์ • ๋ถ„๋ฅ˜
        self.text_classifier = pipeline(
            "text-classification",
            model="j-hartmann/emotion-english-distilroberta-base",
            return_all_scores=True,
            device=0 if torch.cuda.is_available() else -1
        )
        
        # ๊ฐ์ • ๋ ˆ์ด๋ธ” ํ•œ๊ตญ์–ด ๋งคํ•‘
        self.emotion_map = {
            'anger': {'ko': '๋ถ„๋…ธ', 'emoji': '๐Ÿ˜ ', 'color': '#fc8181'},
            'disgust': {'ko': 'ํ˜์˜ค', 'emoji': '๐Ÿคข', 'color': '#68d391'},
            'fear': {'ko': '๊ณตํฌ', 'emoji': '๐Ÿ˜จ', 'color': '#9f7aea'},
            'joy': {'ko': '๊ธฐ์จ', 'emoji': '๐Ÿ˜Š', 'color': '#f6e05e'},
            'neutral': {'ko': '์ค‘๋ฆฝ', 'emoji': '๐Ÿ˜', 'color': '#a0aec0'},
            'sadness': {'ko': '์Šฌํ””', 'emoji': '๐Ÿ˜ข', 'color': '#63b3ed'},
            'surprise': {'ko': '๋†€๋ผ์›€', 'emoji': '๐Ÿ˜ฒ', 'color': '#f6ad55'}
        }
    
    def analyze(self, text: str) -> dict:
        if not text.strip():
            return {'error': 'ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'}
        
        # ๊ฐ์ • ๋ถ„์„ ์‹คํ–‰
        results = self.text_classifier(text)[0]
        
        # ๊ฒฐ๊ณผ ์ •๋ฆฌ
        emotions = []
        for item in results:
            label = item['label']
            score = item['score']
            info = self.emotion_map.get(label, {})
            
            emotions.append({
                'label': label,
                'label_ko': info.get('ko', label),
                'emoji': info.get('emoji', 'โ“'),
                'color': info.get('color', '#gray'),
                'score': round(score * 100, 2)
            })
        
        # ์ ์ˆ˜ ๊ธฐ์ค€ ์ •๋ ฌ
        emotions.sort(key=lambda x: x['score'], reverse=True)
        
        # ์ฃผ์š” ๊ฐ์ •
        dominant = emotions[0]
        
        return {
            'text': text,
            'dominant_emotion': dominant,
            'all_emotions': emotions,
            'sentiment': self._get_sentiment(dominant['label'])
        }
    
    def _get_sentiment(self, emotion: str) -> str:
        positive = ['joy', 'surprise']
        negative = ['anger', 'disgust', 'fear', 'sadness']
        
        if emotion in positive:
            return '๊ธ์ •'
        elif emotion in negative:
            return '๋ถ€์ •'
        return '์ค‘๋ฆฝ'

# ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค
analyzer = EmotionAnalyzer()

๐ŸŒ app.py - Flask ์„œ๋ฒ„

# app.py
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
from model import analyzer
import logging

app = Flask(__name__)
CORS(app)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/analyze', methods=['POST'])
def analyze_emotion():
    try:
        data = request.get_json()
        text = data.get('text', '').strip()
        
        if not text:
            return jsonify({'error': 'ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'}), 400
        
        if len(text) > 512:
            return jsonify({'error': 'ํ…์ŠคํŠธ๊ฐ€ ๋„ˆ๋ฌด ๊น๋‹ˆ๋‹ค (์ตœ๋Œ€ 512์ž)'}), 400
        
        # ๊ฐ์ • ๋ถ„์„
        result = analyzer.analyze(text)
        logger.info(f"๋ถ„์„ ์™„๋ฃŒ: {text[:50]}... โ†’ {result['dominant_emotion']['label_ko']}")
        
        return jsonify(result)
    
    except Exception as e:
        logger.error(f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
        return jsonify({'error': '๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค'}), 500

@app.route('/api/batch', methods=['POST'])
def batch_analyze():
    """์—ฌ๋Ÿฌ ํ…์ŠคํŠธ ํ•œ ๋ฒˆ์— ๋ถ„์„"""
    data = request.get_json()
    texts = data.get('texts', [])
    
    if len(texts) > 50:
        return jsonify({'error': '์ตœ๋Œ€ 50๊ฐœ๊นŒ์ง€ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค'}), 400
    
    results = [analyzer.analyze(text) for text in texts]
    return jsonify({'results': results})

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)
โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ! ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ๋ชจ๋ธ์„ ๋งค๋ฒˆ ๋กœ๋“œํ•˜๋ฉด ๋„ˆ๋ฌด ๋А๋ ค. ์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์œผ๋กœ ์•ฑ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋“œํ•˜๊ณ , ์—ฌ๋Ÿฌ ์š”์ฒญ์—์„œ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํ•ต์‹ฌ์ด์•ผ. GPU๊ฐ€ ์žˆ๋‹ค๋ฉด CUDA๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ถ”๋ก  ์†๋„๊ฐ€ 10๋ฐฐ ์ด์ƒ ๋นจ๋ผ์ ธ!

๐Ÿ“Š ๊ฐ์ • ๋ถ„์„ ์ฃผ์š” ๋ฐ์ดํ„ฐ์…‹๊ณผ ๋ฒค์น˜๋งˆํฌ

์ข‹์€ ๋ชจ๋ธ์„ ๋งŒ๋“ค๋ ค๋ฉด ์ข‹์€ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•ด. ๊ฐ์ • ๋ถ„์„ ๋ถ„์•ผ์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ์…‹๋“ค์„ ์•Œ์•„๋ณด์ž!

๐Ÿ“ ํ…์ŠคํŠธ ๊ฐ์ • ๋ถ„์„ ๋ฐ์ดํ„ฐ์…‹

๋ฐ์ดํ„ฐ์…‹ ์–ธ์–ด ํฌ๊ธฐ ํŠน์ง•
SST-2 ์˜์–ด ~11,000๊ฐœ ์˜ํ™” ๋ฆฌ๋ทฐ, ๊ธ์ •/๋ถ€์ • ์ด์ง„ ๋ถ„๋ฅ˜
IMDB ์˜์–ด 50,000๊ฐœ ์˜ํ™” ๋ฆฌ๋ทฐ, ๊ธ์ •/๋ถ€์ •
GoEmotions ์˜์–ด 58,000๊ฐœ Reddit ๋Œ“๊ธ€, 28๊ฐ€์ง€ ๊ฐ์ •
NSMC ํ•œ๊ตญ์–ด 200,000๊ฐœ ๋„ค์ด๋ฒ„ ์˜ํ™” ๋ฆฌ๋ทฐ, ๊ธ์ •/๋ถ€์ •
AIHub ๊ฐ์„ฑ๋Œ€ํ™” ํ•œ๊ตญ์–ด 270,000๊ฐœ 60๊ฐ€์ง€ ๊ฐ์ •, ๋Œ€ํ™” ํ˜•์‹
KLUE-YNAT ํ•œ๊ตญ์–ด ~45,000๊ฐœ ๋‰ด์Šค ํ—ค๋“œ๋ผ์ธ ๋ถ„๋ฅ˜

๐ŸŽ™๏ธ ์Œ์„ฑ ๊ฐ์ • ๋ถ„์„ ๋ฐ์ดํ„ฐ์…‹

๋ฐ์ดํ„ฐ์…‹ ์–ธ์–ด ๊ฐ์ • ์ˆ˜ ํŠน์ง•
RAVDESS ์˜์–ด 8๊ฐ€์ง€ ์ „๋ฌธ ๋ฐฐ์šฐ ๋…น์Œ, ๊ณ ํ’ˆ์งˆ
IEMOCAP ์˜์–ด 9๊ฐ€์ง€ ๋Œ€ํ™” ํ˜•์‹, ํ…์ŠคํŠธ+์Œ์„ฑ+์˜์ƒ
MSP-IMPROV ์˜์–ด 4๊ฐ€์ง€ ์ฆ‰ํฅ ์—ฐ๊ธฐ ๊ธฐ๋ฐ˜
KESDy ํ•œ๊ตญ์–ด 7๊ฐ€์ง€ ํ•œ๊ตญ์–ด ๊ฐ์ • ์Œ์„ฑ ๋ฐ์ดํ„ฐ
AIHub ๊ฐ์ • ์Œ์„ฑ ํ•œ๊ตญ์–ด 6๊ฐ€์ง€ ํ•œ๊ตญ์–ด ์›์–ด๋ฏผ ๋…น์Œ
๐Ÿ’ก ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ถ„์„ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๊ผญ ์•Œ์•„์•ผ ํ•  ๊ฒƒ!
AIHub(aihub.or.kr)์—์„œ ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ฐ์ดํ„ฐ์…‹์„ ๋ฌด๋ฃŒ๋กœ ๋‹ค์šด๋ฐ›์„ ์ˆ˜ ์žˆ์–ด. ๊ฐ์„ฑ๋Œ€ํ™” ๋ง๋ญ‰์น˜, ๊ฐ์ • ๋ถ„๋ฅ˜๋ฅผ ์œ„ํ•œ ๋Œ€ํ™” ์Œ์„ฑ ๋ฐ์ดํ„ฐ์…‹ ๋“ฑ ๋‹ค์–‘ํ•œ ํ•œ๊ตญ์–ด ํŠนํ™” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋‹ˆ ๊ผญ ํ™œ์šฉํ•ด๋ด!

๐Ÿ“ˆ ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€ํ•˜๊ธฐ

๋ชจ๋ธ์„ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์–ผ๋งˆ๋‚˜ ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ‰๊ฐ€ํ•ด์•ผ๊ฒ ์ง€? ๊ฐ์ • ๋ถ„์„์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ํ‰๊ฐ€ ์ง€ํ‘œ๋“ค์„ ์•Œ์•„๋ณด์ž.

๊ฐ์ • ๋ถ„๋ฅ˜ ํ˜ผ๋™ ํ–‰๋ ฌ (Confusion Matrix) ์˜ˆ์‹œ โ† ์˜ˆ์ธก๊ฐ’ โ†’ โ† ์‹ค์ œ๊ฐ’ โ†’ ๊ธฐ์จ๐Ÿ˜Š ์Šฌํ””๐Ÿ˜ข ๋ถ„๋…ธ๐Ÿ˜  ๊ณตํฌ๐Ÿ˜จ ์ค‘๋ฆฝ๐Ÿ˜ ๊ธฐ์จ๐Ÿ˜Š ์Šฌํ””๐Ÿ˜ข ๋ถ„๋…ธ๐Ÿ˜  ๊ณตํฌ๐Ÿ˜จ ์ค‘๋ฆฝ๐Ÿ˜ 142 8 5 2 3 6 128 10 7 4 4 12 135 8 1 3 14 9 118 6 2 5 3 7 133 ์ •๋‹ต ์˜ค๋‹ต
# ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€
from sklearn.metrics import (
    accuracy_score, 
    f1_score, 
    classification_report,
    confusion_matrix
)
import numpy as np

def evaluate_emotion_model(y_true, y_pred, labels):
    """
    ๊ฐ์ • ๋ถ„๋ฅ˜ ๋ชจ๋ธ ์ข…ํ•ฉ ํ‰๊ฐ€
    
    Args:
        y_true: ์‹ค์ œ ๋ ˆ์ด๋ธ”
        y_pred: ์˜ˆ์ธก ๋ ˆ์ด๋ธ”
        labels: ๊ฐ์ • ๋ ˆ์ด๋ธ” ๋ชฉ๋ก
    """
    # 1. ์ •ํ™•๋„ (Accuracy)
    accuracy = accuracy_score(y_true, y_pred)
    
    # 2. F1 ์Šค์ฝ”์–ด (Macro: ํด๋ž˜์Šค ๋ถˆ๊ท ํ˜• ๊ณ ๋ ค)
    f1_macro = f1_score(y_true, y_pred, average='macro')
    f1_weighted = f1_score(y_true, y_pred, average='weighted')
    
    # 3. ์ƒ์„ธ ๋ถ„๋ฅ˜ ๋ณด๊ณ ์„œ
    report = classification_report(y_true, y_pred, target_names=labels)
    
    # 4. ํ˜ผ๋™ ํ–‰๋ ฌ
    cm = confusion_matrix(y_true, y_pred)
    
    print(f"=" * 50)
    print(f"๐Ÿ“Š ๊ฐ์ • ๋ถ„๋ฅ˜ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€ ๊ฒฐ๊ณผ")
    print(f"=" * 50)
    print(f"โœ… ์ •ํ™•๋„ (Accuracy): {accuracy:.4f} ({accuracy*100:.2f}%)")
    print(f"๐Ÿ“ˆ F1 ์Šค์ฝ”์–ด (Macro): {f1_macro:.4f}")
    print(f"๐Ÿ“ˆ F1 ์Šค์ฝ”์–ด (Weighted): {f1_weighted:.4f}")
    print(f"\n๐Ÿ“‹ ํด๋ž˜์Šค๋ณ„ ์ƒ์„ธ ์„ฑ๋Šฅ:")
    print(report)
    
    # ๊ฐ€์žฅ ํ˜ผ๋™์ด ๋งŽ์€ ๊ฐ์ • ์Œ ์ฐพ๊ธฐ
    np.fill_diagonal(cm, 0)  # ๋Œ€๊ฐ์„  ์ œ๊ฑฐ
    max_idx = np.unravel_index(cm.argmax(), cm.shape)
    print(f"โš ๏ธ ๊ฐ€์žฅ ํ˜ผ๋™์ด ๋งŽ์€ ์Œ: {labels[max_idx[0]]} โ†’ {labels[max_idx[1]]} ({cm[max_idx]}๊ฑด)")
    
    return {
        'accuracy': accuracy,
        'f1_macro': f1_macro,
        'f1_weighted': f1_weighted
    }

# ์˜ˆ์‹œ ์‹คํ–‰
emotion_labels = ['๊ธฐ์จ', '์Šฌํ””', '๋ถ„๋…ธ', '๊ณตํฌ', '์ค‘๋ฆฝ']
y_true = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
y_pred = [0, 1, 2, 3, 4, 0, 2, 2, 3, 4, 0, 1, 2, 1, 4]

metrics = evaluate_emotion_model(y_true, y_pred, emotion_labels)

๐Ÿ“ ๊ฐ์ • ๋ถ„์„ ํ‰๊ฐ€ ์ง€ํ‘œ ์ •๋ฆฌ

์ •ํ™•๋„(Accuracy): ์ „์ฒด ์˜ˆ์ธก ์ค‘ ๋งž์€ ๋น„์œจ. ํด๋ž˜์Šค ๋ถˆ๊ท ํ˜• ์‹œ ์™œ๊ณก๋  ์ˆ˜ ์žˆ์–ด.
์ •๋ฐ€๋„(Precision): ๊ธ์ •์œผ๋กœ ์˜ˆ์ธกํ•œ ๊ฒƒ ์ค‘ ์‹ค์ œ ๊ธ์ • ๋น„์œจ.
์žฌํ˜„์œจ(Recall): ์‹ค์ œ ๊ธ์ • ์ค‘ ๊ธ์ •์œผ๋กœ ์˜ˆ์ธกํ•œ ๋น„์œจ.
F1 ์Šค์ฝ”์–ด: ์ •๋ฐ€๋„์™€ ์žฌํ˜„์œจ์˜ ์กฐํ™” ํ‰๊ท . ๋ถˆ๊ท ํ˜• ๋ฐ์ดํ„ฐ์—์„œ ํ•ต์‹ฌ ์ง€ํ‘œ!
Macro F1: ๋ชจ๋“  ํด๋ž˜์Šค์— ๋™์ผํ•œ ๊ฐ€์ค‘์น˜. ์†Œ์ˆ˜ ํด๋ž˜์Šค๋„ ์ค‘์š”ํ•  ๋•Œ.
Weighted F1: ํด๋ž˜์Šค ๋นˆ๋„์— ๋น„๋ก€ํ•œ ๊ฐ€์ค‘์น˜. ์‹ค์ œ ๋ถ„ํฌ ๋ฐ˜์˜.


๐ŸŒ ๊ฐ์ • ๋ถ„์„์˜ ์‹ค์ œ ํ™œ์šฉ ์‚ฌ๋ก€๋“ค

๊ฐ์ • ๋ถ„์„ ๊ธฐ์ˆ ์€ ์ด๋ฏธ ์šฐ๋ฆฌ ์ผ์ƒ ๊ณณ๊ณณ์— ์Šค๋ฉฐ๋“ค์–ด ์žˆ์–ด. ์–ด๋””์„œ ์–ด๋–ป๊ฒŒ ์“ฐ์ด๋Š”์ง€ ์‚ดํŽด๋ณด์ž!

๊ฐ์ • ๋ถ„์„ ๊ธฐ์ˆ  ํ™œ์šฉ ๋ถ„์•ผ ๐Ÿง  ๊ฐ์ • ๋ถ„์„ AI ์‹œ์Šคํ…œ ๐ŸŽง ๊ณ ๊ฐ ์„œ๋น„์Šค ์ฝœ์„ผํ„ฐ ๊ฐ์ • ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ“ฑ ์†Œ์…œ ๋ฏธ๋””์–ด ์—ฌ๋ก  ๋ถ„์„ยทํŠธ๋ Œ๋“œ ๐Ÿฅ ์˜๋ฃŒยท์ •์‹ ๊ฑด๊ฐ• ์šฐ์šธ์ฆ ์กฐ๊ธฐ ๊ฐ์ง€ ๐Ÿ“ˆ ๊ธˆ์œตยทํˆฌ์ž ๋‰ด์Šค ๊ฐ์„ฑ ํˆฌ์ž ์‹ ํ˜ธ ๐Ÿ“š ๊ต์œกยท์—๋“€ํ…Œํฌ ํ•™์Šต์ž ๊ฐ์ • ์ ์‘ํ˜• ๐ŸŽฎ ๊ฒŒ์ž„ยท์—”ํ„ฐ NPC ๊ฐ์ • ๋ฐ˜์‘ ์‹œ์Šคํ…œ

๐Ÿ”ฅ ๊ฐ€์žฅ ํ•ซํ•œ ํ™œ์šฉ ์‚ฌ๋ก€๋“ค

1. ์ฝœ์„ผํ„ฐ ์‹ค์‹œ๊ฐ„ ๊ฐ์ • ๋ชจ๋‹ˆํ„ฐ๋ง ๐ŸŽง
๊ณ ๊ฐ๊ณผ ์ƒ๋‹ด์›์˜ ๋Œ€ํ™”๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถ„์„ํ•ด์„œ ๊ณ ๊ฐ์ด ํ™”๊ฐ€ ๋‚ฌ์„ ๋•Œ ์ฆ‰์‹œ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๊ฑฐ๋‚˜, ์ƒ๋‹ด ํ’ˆ์งˆ์„ ์ž๋™์œผ๋กœ ํ‰๊ฐ€ํ•ด. ๊ตญ๋‚ด ์ฃผ์š” ํ†ต์‹ ์‚ฌ์™€ ๊ธˆ์œต์‚ฌ์—์„œ ์ด๋ฏธ ๋„์ž… ์ค‘์ด์•ผ.

2. ์†Œ์…œ ๋ฏธ๋””์–ด ๋ธŒ๋žœ๋“œ ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ“ฑ
ํŠธ์œ„ํ„ฐ, ์ธ์Šคํƒ€๊ทธ๋žจ, ์œ ํŠœ๋ธŒ ๋Œ“๊ธ€ ๋“ฑ์—์„œ ์ž์‚ฌ ๋ธŒ๋žœ๋“œ์— ๋Œ€ํ•œ ์–ธ๊ธ‰์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜์ง‘ํ•˜๊ณ  ๊ฐ์ •์„ ๋ถ„์„ํ•ด์„œ ์œ„๊ธฐ ์ƒํ™ฉ์„ ์กฐ๊ธฐ์— ๊ฐ์ง€ํ•ด.

3. ์ •์‹ ๊ฑด๊ฐ• AI ์ฑ—๋ด‡ ๐Ÿฅ
์‚ฌ์šฉ์ž์˜ ํ…์ŠคํŠธ์™€ ์Œ์„ฑ์—์„œ ์šฐ์šธ, ๋ถˆ์•ˆ, ์ŠคํŠธ๋ ˆ์Šค ์‹ ํ˜ธ๋ฅผ ๊ฐ์ง€ํ•ด์„œ ์ ์ ˆํ•œ ๊ฐœ์ž…์„ ์ œ๊ณตํ•˜๋Š” AI ์ƒ๋‹ด ์‹œ์Šคํ…œ. ์ฝ”๋กœ๋‚˜ ์ดํ›„ ๊ธ‰๊ฒฉํžˆ ์„ฑ์žฅํ•œ ๋ถ„์•ผ์•ผ.

4. ๊ธˆ์œต ๋‰ด์Šค ๊ฐ์„ฑ ๋ถ„์„ ๐Ÿ“ˆ
๊ฒฝ์ œ ๋‰ด์Šค, ๊ธฐ์—… ๊ณต์‹œ, SNS ์—ฌ๋ก ์„ ๋ถ„์„ํ•ด์„œ ์ฃผ๊ฐ€ ๋ฐฉํ–ฅ์„ ์˜ˆ์ธกํ•˜๋Š” ํ€€ํŠธ ํˆฌ์ž ์ „๋žต์— ํ™œ์šฉ. ํ—ค์ง€ํŽ€๋“œ๋“ค์ด ์ ๊ทน ๋„์ž… ์ค‘์ด์•ผ.

๐ŸŒŸ ์žฌ๋Šฅ๋„ท์—์„œ๋„ ๊ฐ์ • ๋ถ„์„ ๊ด€๋ จ ์žฌ๋Šฅ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด!
์žฌ๋Šฅ๋„ท์—์„œ NLP ๊ฐœ๋ฐœ, ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ ๊ตฌ์ถ•, AI ์ฑ—๋ด‡ ๊ฐœ๋ฐœ ๋“ฑ ๋‹ค์–‘ํ•œ AI ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ฐ€์ง„ ์ „๋ฌธ๊ฐ€๋“ค์„ ๋งŒ๋‚  ์ˆ˜ ์žˆ์–ด. ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•œ๋ฒˆ ์ฐพ์•„๋ด!

โš ๏ธ ๊ฐ์ • ๋ถ„์„์˜ ๋„์ „ ๊ณผ์ œ์™€ ํ•œ๊ณ„

๊ฐ์ • ๋ถ„์„์ด ์—„์ฒญ๋‚˜๊ฒŒ ๋ฐœ์ „ํ–ˆ์ง€๋งŒ, ์•„์ง๋„ ํ•ด๊ฒฐํ•ด์•ผ ํ•  ์–ด๋ ค์šด ๋ฌธ์ œ๋“ค์ด ๋งŽ์•„. ์†”์งํ•˜๊ฒŒ ํ•œ๊ณ„๋„ ์•Œ์•„์•ผ ์ง„์งœ ์‹ค๋ ฅ์ž์ง€!

๐ŸŽญ 1. ์•„์ด๋Ÿฌ๋‹ˆ์™€ ๋น„๊ผฌ๊ธฐ (Sarcasm & Irony)

"์™€, ์ง„์งœ ๋Œ€๋‹จํ•˜๋‹ค ใ…‹ใ…‹" โ†’ ์ด๊ฒŒ ์ง„์งœ ์นญ์ฐฌ์ธ์ง€ ๋น„๊ผฌ๋Š” ๊ฑด์ง€ AI๊ฐ€ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์—„์ฒญ ์–ด๋ ค์›Œ.
๋ฌธ๋งฅ, ์ด์ „ ๋Œ€ํ™”, ํ™”์ž์˜ ๊ด€๊ณ„ ๋“ฑ ๋‹ค์–‘ํ•œ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ๋ฐ, ํ…์ŠคํŠธ๋งŒ์œผ๋กœ๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์–ด.
์ตœ๊ทผ ์—ฐ๊ตฌ์—์„œ๋Š” ๋Œ€์กฐ ํ•™์Šต(Contrastive Learning)๊ณผ ์ƒ์‹ ์ง€์‹ ๊ทธ๋ž˜ํ”„๋ฅผ ํ™œ์šฉํ•ด์„œ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ์‹œ๋„ ์ค‘์ด์•ผ.

๐ŸŒ 2. ๋ฌธํ™”์  ์ฐจ์ด์™€ ์–ธ์–ด ํŠน์ˆ˜์„ฑ

๊ฐ™์€ ํ‘œํ˜„๋„ ๋ฌธํ™”๊ถŒ์— ๋”ฐ๋ผ ๊ฐ์ •์ด ๋‹ค๋ฅด๊ฒŒ ํ•ด์„๋  ์ˆ˜ ์žˆ์–ด. ํ•œ๊ตญ์–ด์˜ "๊ดœ์ฐฎ์•„"๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ง„์งœ ๊ดœ์ฐฎ๋‹ค๋Š” ๋œป์ผ ์ˆ˜๋„, ์ „ํ˜€ ๊ดœ์ฐฎ์ง€ ์•Š๋‹ค๋Š” ๋œป์ผ ์ˆ˜๋„ ์žˆ์ž–์•„.
๋˜ํ•œ ์‹ ์กฐ์–ด, ์ค„์ž„๋ง, ์ธํ„ฐ๋„ท ๋ฐˆ ๋“ฑ์€ ๊ณ„์† ์ƒˆ๋กœ ์ƒ๊ฒจ๋‚˜์„œ ๋ชจ๋ธ์ด ๋”ฐ๋ผ๊ฐ€๊ธฐ ์–ด๋ ค์›Œ.

๐ŸŽญ 3. ๋ณตํ•ฉ ๊ฐ์ • (Mixed Emotions)

"์กธ์—…์‹์—์„œ ๊ธฐ์˜๋ฉด์„œ๋„ ์Šฌํސ์–ด" โ†’ ๊ธฐ์จ๊ณผ ์Šฌํ””์ด ๋™์‹œ์— ์กด์žฌํ•˜๋Š” ๋ณตํ•ฉ ๊ฐ์ •.
๋Œ€๋ถ€๋ถ„์˜ ๋ชจ๋ธ์€ ํ•˜๋‚˜์˜ ๊ฐ์ •๋งŒ ์ถœ๋ ฅํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์–ด์„œ, ์ด๋Ÿฐ ๋ณตํ•ฉ ๊ฐ์ •์„ ์ œ๋Œ€๋กœ ํ‘œํ˜„ํ•˜์ง€ ๋ชปํ•ด.
๋‹ค์ค‘ ๋ ˆ์ด๋ธ” ๋ถ„๋ฅ˜(Multi-label Classification)๋กœ ์ ‘๊ทผํ•˜๋ฉด ์–ด๋А ์ •๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ”’ 4. ํ”„๋ผ์ด๋ฒ„์‹œ์™€ ์œค๋ฆฌ ๋ฌธ์ œ

๊ฐ์ • ๋ถ„์„์€ ๊ฐœ์ธ์˜ ์‹ฌ๋ฆฌ ์ƒํƒœ๋ฅผ ์ถ”๋ก ํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ๋™์˜ ์—†์ด ์‚ฌ์šฉํ•˜๋ฉด ์‹ฌ๊ฐํ•œ ํ”„๋ผ์ด๋ฒ„์‹œ ์นจํ•ด๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด.
ํŠนํžˆ ์ง์žฅ์—์„œ ์ง์›์˜ ๊ฐ์ •์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ฑฐ๋‚˜, ๊ด‘๊ณ ์— ๊ฐ์ • ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฑด ์œค๋ฆฌ์ ์œผ๋กœ ํฐ ๋…ผ๋ž€์ด ์žˆ์–ด.
EU์˜ AI Act์—์„œ๋Š” ์ผ๋ถ€ ๊ฐ์ • ์ธ์‹ ์‹œ์Šคํ…œ์˜ ์‚ฌ์šฉ์„ ์ œํ•œํ•˜๊ณ  ์žˆ์–ด.

๐Ÿ“Š 5. ๋ฐ์ดํ„ฐ ํŽธํ–ฅ (Bias)

ํ•™์Šต ๋ฐ์ดํ„ฐ์— ํŠน์ • ์ธ๊ตฌ ์ง‘๋‹จ, ๋ฌธํ™”, ์–ธ์–ด๊ฐ€ ๊ณผ๋Œ€/๊ณผ์†Œ ๋Œ€ํ‘œ๋˜๋ฉด ๋ชจ๋ธ๋„ ํŽธํ–ฅ๋˜๊ฒŒ ํ•™์Šต๋ผ.
์˜ˆ๋ฅผ ๋“ค์–ด ์˜์–ด ๋ฐ์ดํ„ฐ๋กœ๋งŒ ํ•™์Šตํ•œ ๋ชจ๋ธ์„ ํ•œ๊ตญ์–ด์— ์ ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ๋–จ์–ด์ง€๊ณ , ํŠน์ • ๋ฐฉ์–ธ์ด๋‚˜ ํ‘œํ˜„ ๋ฐฉ์‹์„ ์ž˜๋ชป ๋ถ„๋ฅ˜ํ•  ์ˆ˜ ์žˆ์–ด.

โš ๏ธ ๊ฐœ๋ฐœ์ž๋กœ์„œ ๋ช…์‹ฌํ•ด์•ผ ํ•  ๊ฒƒ!
๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ์„ ๊ฐœ๋ฐœํ•˜๊ณ  ๋ฐฐํฌํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉ์ž์˜ ๋™์˜๋ฅผ ๋ฐ›๊ณ , ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•ด์•ผ ํ•ด. ๋˜ํ•œ ๋ชจ๋ธ์˜ ํ•œ๊ณ„์™€ ์˜ค๋ฅ˜ ๊ฐ€๋Šฅ์„ฑ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ํˆฌ๋ช…ํ•˜๊ฒŒ ์•Œ๋ ค์•ผ ํ•ด. ๊ธฐ์ˆ ์  ์™„์„ฑ๋„๋งŒํผ ์œค๋ฆฌ์  ๊ณ ๋ ค๋„ ์ค‘์š”ํ•ด!

๐Ÿš€ 2024-2025 ์ตœ์‹  ํŠธ๋ Œ๋“œ

๊ฐ์ • ๋ถ„์„ ๋ถ„์•ผ๋Š” ์ •๋ง ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์ง€๊ธˆ ๊ฐ€์žฅ ํ•ซํ•œ ์—ฐ๊ตฌ ๋ฐฉํ–ฅ๋“ค์„ ์‚ดํŽด๋ณด์ž!

1
LLM ๊ธฐ๋ฐ˜ ๊ฐ์ • ๋ถ„์„ (GPT-4, Claude, Gemini)
๋Œ€ํ˜• ์–ธ์–ด ๋ชจ๋ธ(LLM)์„ ๊ฐ์ • ๋ถ„์„์— ํ™œ์šฉํ•˜๋Š” ์—ฐ๊ตฌ๊ฐ€ ํญ๋ฐœ์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ์–ด. ํŠนํžˆ Few-shot, Zero-shot ํ•™์Šต์œผ๋กœ ์†Œ๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋งŒ์œผ๋กœ๋„ ๋†’์€ ์„ฑ๋Šฅ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์–ด. Chain-of-Thought ํ”„๋กฌํ”„ํŒ…์œผ๋กœ ๊ฐ์ • ๋ถ„์„์˜ ์ด์œ ๊นŒ์ง€ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ด์กŒ์–ด.
2
๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๋Œ€ํ˜• ๋ชจ๋ธ (GPT-4V, Gemini Pro Vision)
ํ…์ŠคํŠธ, ์Œ์„ฑ, ์ด๋ฏธ์ง€, ์˜์ƒ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๋ชจ๋ธ์ด ๊ฐ์ • ๋ถ„์„์— ํ˜๋ช…์„ ์ผ์œผํ‚ค๊ณ  ์žˆ์–ด. ์–ผ๊ตด ํ‘œ์ • + ๋ชฉ์†Œ๋ฆฌ + ํ…์ŠคํŠธ๋ฅผ ๋™์‹œ์— ๋ถ„์„ํ•ด์„œ ํ›จ์”ฌ ์ •ํ™•ํ•œ ๊ฐ์ • ์ธ์‹์ด ๊ฐ€๋Šฅํ•ด์กŒ์–ด.
3
์—ฐ์† ๊ฐ์ • ํ‘œํ˜„ (Valence-Arousal-Dominance)
๊ธฐ์จ/์Šฌํ”” ๊ฐ™์€ ์ด์‚ฐ์  ์นดํ…Œ๊ณ ๋ฆฌ ๋Œ€์‹ , ๊ฐ์ •์„ ์—ฐ์†์ ์ธ 3์ฐจ์› ๊ณต๊ฐ„(๊ธ๋ถ€์ •์„ฑ-๊ฐ์„ฑ๋„-์ง€๋ฐฐ์„ฑ)์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ์—ฐ๊ตฌ๊ฐ€ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ์–ด. ๋” ์„ธ๋ฐ€ํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ์ • ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•ด.
4
์„ค๋ช… ๊ฐ€๋Šฅํ•œ ๊ฐ์ • ๋ถ„์„ (Explainable AI)
"์™œ ์ด ํ…์ŠคํŠธ๋ฅผ ๋ถ€์ •์œผ๋กœ ๋ถ„๋ฅ˜ํ–ˆ๋Š”์ง€" ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ธ์— ๋Œ€ํ•œ ์ˆ˜์š”๊ฐ€ ๊ธ‰์ฆํ•˜๊ณ  ์žˆ์–ด. LIME, SHAP, Attention Visualization ๋“ฑ์˜ ๊ธฐ๋ฒ•์œผ๋กœ ๋ชจ๋ธ์˜ ํŒ๋‹จ ๊ทผ๊ฑฐ๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋Š” ์—ฐ๊ตฌ๊ฐ€ ํ™œ๋ฐœํ•ด.
5
์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ๊ฐ์ • ๋ถ„์„
ํ™”์ƒ ํšŒ์˜, ๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ, ์‹ค์‹œ๊ฐ„ ๊ณ ๊ฐ ์ƒ๋‹ด ๋“ฑ์—์„œ ์ง€์—ฐ ์—†์ด ๊ฐ์ •์„ ๋ถ„์„ํ•˜๋Š” ๊ธฐ์ˆ . ์—ฃ์ง€ ์ปดํ“จํŒ…๊ณผ ๋ชจ๋ธ ๊ฒฝ๋Ÿ‰ํ™”(Quantization, Pruning) ๊ธฐ์ˆ ์˜ ๋ฐœ์ „์œผ๋กœ ์‹คํ˜„ ๊ฐ€๋Šฅํ•ด์ง€๊ณ  ์žˆ์–ด.
# GPT-4๋ฅผ ํ™œ์šฉํ•œ ๊ฐ์ • ๋ถ„์„ (Few-shot ๋ฐฉ์‹)
from openai import OpenAI

client = OpenAI()

def analyze_emotion_with_gpt4(text: str) -> dict:
    """GPT-4๋ฅผ ์ด์šฉํ•œ ๊ณ ๊ธ‰ ๊ฐ์ • ๋ถ„์„"""
    
    system_prompt = """๋‹น์‹ ์€ ๊ฐ์ • ๋ถ„์„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
์ฃผ์–ด์ง„ ํ…์ŠคํŠธ์—์„œ ๊ฐ์ •์„ ๋ถ„์„ํ•˜๊ณ  ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•˜์„ธ์š”:
- ์ฃผ์š” ๊ฐ์ •: [๊ธฐ์จ/์Šฌํ””/๋ถ„๋…ธ/๊ณตํฌ/ํ˜์˜ค/๋†€๋ผ์›€/์ค‘๋ฆฝ]
- ๊ฐ์ • ๊ฐ•๋„: [1-10]
- ๊ฐ์ • ์ด์œ : [ํ…์ŠคํŠธ์—์„œ ๊ฐ์ •์„ ์œ ๋ฐœํ•œ ์š”์†Œ ์„ค๋ช…]
- ๋ณตํ•ฉ ๊ฐ์ •: [์žˆ๋‹ค๋ฉด ์ถ”๊ฐ€ ๊ฐ์ • ์–ธ๊ธ‰]
- ์ „์ฒด ๊ฐ์„ฑ: [๊ธ์ •/๋ถ€์ •/์ค‘๋ฆฝ]"""

    few_shot_examples = [
        {"role": "user", "content": "์˜ค๋Š˜ ๋“œ๋””์–ด ์ทจ์—…ํ–ˆ์–ด! 3๋…„ ๋™์•ˆ ์ค€๋น„ํ–ˆ๋Š”๋ฐ ๋„ˆ๋ฌด ๊ธฐ๋ป"},
        {"role": "assistant", "content": "์ฃผ์š” ๊ฐ์ •: ๊ธฐ์จ\n๊ฐ์ • ๊ฐ•๋„: 9\n๊ฐ์ • ์ด์œ : 3๋…„๊ฐ„์˜ ๋…ธ๋ ฅ ๋์— ์ทจ์—… ์„ฑ๊ณต์ด๋ผ๋Š” ๋ชฉํ‘œ ๋‹ฌ์„ฑ\n๋ณตํ•ฉ ๊ฐ์ •: ์•ˆ๋„๊ฐ, ์„ฑ์ทจ๊ฐ\n์ „์ฒด ๊ฐ์„ฑ: ๊ธ์ •"},
        {"role": "user", "content": "๋˜ ์•ผ๊ทผ์ด์•ผ... ์ง‘์— ๊ฐ€๊ณ  ์‹ถ๋‹ค"},
        {"role": "assistant", "content": "์ฃผ์š” ๊ฐ์ •: ์Šฌํ””\n๊ฐ์ • ๊ฐ•๋„: 6\n๊ฐ์ • ์ด์œ : ๋ฐ˜๋ณต๋˜๋Š” ์•ผ๊ทผ์œผ๋กœ ์ธํ•œ ํ”ผ๋กœ์™€ ๊ท€๊ฐ€ ์š•๊ตฌ\n๋ณตํ•ฉ ๊ฐ์ •: ํ”ผ๋กœ๊ฐ, ์ฒด๋…\n์ „์ฒด ๊ฐ์„ฑ: ๋ถ€์ •"}
    ]
    
    messages = [
        {"role": "system", "content": system_prompt},
        *few_shot_examples,
        {"role": "user", "content": text}
    ]
    
    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages,
        temperature=0.1,  # ์ผ๊ด€๋œ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•ด ๋‚ฎ๊ฒŒ ์„ค์ •
        max_tokens=200
    )
    
    result_text = response.choices[0].message.content
    
    # ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
    lines = result_text.strip().split('\n')
    result = {}
    for line in lines:
        if ':' in line:
            key, value = line.split(':', 1)
            result[key.strip()] = value.strip()
    
    return result

# ํ…Œ์ŠคํŠธ
text = "์ด ์˜ํ™” ์ง„์งœ ๊ธฐ๋Œ€ํ–ˆ๋Š”๋ฐ ์™„์ „ ์‹ค๋ง์ด์•ผ. ๋ˆ ์•„๊น๋‹ค."
result = analyze_emotion_with_gpt4(text)
for key, value in result.items():
    print(f"{key}: {value}")

๐Ÿ’ป ๊ฐ์ • ๋ถ„์„ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ… ๊ฐ€์ด๋“œ

์ž, ์ด์ œ ์ง์ ‘ ํ•ด๋ณด๊ณ  ์‹ถ์ง€? ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ…๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž!

๐Ÿ“ฆ ํ•„์ˆ˜ ํŒจํ‚ค์ง€ ์„ค์น˜

# requirements.txt

# ํ•ต์‹ฌ ๋”ฅ๋Ÿฌ๋‹ ํ”„๋ ˆ์ž„์›Œํฌ
torch>=2.0.0
torchvision>=0.15.0
torchaudio>=2.0.0

# Hugging Face ์ƒํƒœ๊ณ„
transformers>=4.35.0
datasets>=2.14.0
tokenizers>=0.14.0
accelerate>=0.24.0

# ํ•œ๊ตญ์–ด NLP
konlpy>=0.6.0
soynlp>=0.0.493

# ์Œ์„ฑ ์ฒ˜๋ฆฌ
librosa>=0.10.0
soundfile>=0.12.0
pyaudio>=0.2.13

# ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
numpy>=1.24.0
pandas>=2.0.0
scikit-learn>=1.3.0

# ์‹œ๊ฐํ™”
matplotlib>=3.7.0
seaborn>=0.12.0
plotly>=5.17.0

# ์›น ์„œ๋ฒ„
flask>=3.0.0
flask-cors>=4.0.0

# ์œ ํ‹ธ๋ฆฌํ‹ฐ
tqdm>=4.66.0
python-dotenv>=1.0.0
# ์„ค์น˜ ๋ช…๋ น์–ด
pip install -r requirements.txt

# KoNLPy Java ์˜์กด์„ฑ (macOS/Linux)
# Java 8 ์ด์ƒ ํ•„์š”
brew install java  # macOS
sudo apt-get install default-jdk  # Ubuntu

# MeCab ์„ค์น˜ (๋” ๋น ๋ฅธ ํ•œ๊ตญ์–ด ํ˜•ํƒœ์†Œ ๋ถ„์„)
# macOS
brew install mecab
pip install python-mecab-ko

# Ubuntu
sudo apt-get install mecab libmecab-dev mecab-ipadic-utf8
pip install python-mecab-ko

๐Ÿ”ง ๋น ๋ฅธ ์‹œ์ž‘ ์ฝ”๋“œ (Quick Start)

# quick_start.py - 5๋ถ„ ๋งŒ์— ๊ฐ์ • ๋ถ„์„ ์‹œ์ž‘ํ•˜๊ธฐ!
from transformers import pipeline

print("๐Ÿš€ ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” ์ค‘...")

# ๋ฐฉ๋ฒ• 1: ์˜์–ด ๊ฐ์ • ๋ถ„์„ (7๊ฐ€์ง€ ๊ฐ์ •)
emotion_pipe = pipeline(
    "text-classification",
    model="j-hartmann/emotion-english-distilroberta-base",
    return_all_scores=True
)

# ๋ฐฉ๋ฒ• 2: ํ•œ๊ตญ์–ด ๊ฐ์„ฑ ๋ถ„์„ (๊ธ์ •/๋ถ€์ •)
# ํ•œ๊ตญ์–ด ๋ชจ๋ธ์€ Hugging Face Hub์—์„œ ๊ฒ€์ƒ‰
# ์˜ˆ: "snunlp/KR-FinBert-SC", "monologg/koelectra-base-finetuned-sentiment"
ko_sentiment = pipeline(
    "text-classification",
    model="monologg/koelectra-base-finetuned-sentiment"
)

print("โœ… ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!\n")

# ํ…Œ์ŠคํŠธ ๋ฌธ์žฅ๋“ค
test_sentences = [
    "I'm so happy today! Everything is going great!",
    "This is absolutely terrible. I'm so disappointed.",
    "I can't believe what just happened. I'm shocked!"
]

print("๐Ÿ“Š ์˜์–ด ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ:")
print("-" * 50)
for sentence in test_sentences:
    results = emotion_pipe(sentence)[0]
    # ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜์˜ ๊ฐ์ •
    top_emotion = max(results, key=lambda x: x['score'])
    print(f"ํ…์ŠคํŠธ: {sentence[:50]}...")
    print(f"์ฃผ์š” ๊ฐ์ •: {top_emotion['label']} ({top_emotion['score']:.2%})")
    print()

# ํ•œ๊ตญ์–ด ํ…Œ์ŠคํŠธ
ko_sentences = [
    "์˜ค๋Š˜ ์ •๋ง ๊ธฐ๋ถ„ ์ตœ๊ณ ์•ผ! ์ข‹์€ ์ผ๋งŒ ์ƒ๊ฒผ์–ด!",
    "์ด๊ฑฐ ์ง„์งœ ๋ณ„๋กœ๋‹ค. ๋ˆ ์•„๊น๊ณ  ์‹œ๊ฐ„ ๋‚ญ๋น„์•ผ.",
    "๊ทธ๋ƒฅ ํ‰๋ฒ”ํ•œ ํ•˜๋ฃจ์˜€์–ด. ํŠน๋ณ„ํ•œ ๊ฑด ์—†์—ˆ์–ด."
]

print("\n๐Ÿ“Š ํ•œ๊ตญ์–ด ๊ฐ์„ฑ ๋ถ„์„ ๊ฒฐ๊ณผ:")
print("-" * 50)
for sentence in ko_sentences:
    result = ko_sentiment(sentence)[0]
    label = "๊ธ์ • ๐Ÿ˜Š" if result['label'] == 'positive' else "๋ถ€์ • ๐Ÿ˜ž"
    print(f"ํ…์ŠคํŠธ: {sentence}")
    print(f"๊ฐ์„ฑ: {label} (ํ™•์‹ ๋„: {result['score']:.2%})")
    print()
โœ… GPU ์—†์–ด๋„ ๊ดœ์ฐฎ์•„! ์œ„ ์ฝ”๋“œ๋Š” CPU์—์„œ๋„ ์ž˜ ๋Œ์•„๊ฐ€. ๋‹ค๋งŒ ์†๋„๊ฐ€ ์ข€ ๋А๋ฆด ์ˆ˜ ์žˆ์–ด. ๋น ๋ฅธ ์ถ”๋ก ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด Google Colab์˜ ๋ฌด๋ฃŒ GPU๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜, ๋ชจ๋ธ์„ ๊ฒฝ๋Ÿ‰ํ™”(distilbert, tiny-bert ๋“ฑ)ํ•ด์„œ ์‚ฌ์šฉํ•ด๋ด!

๐Ÿ’ก ๊ฐ์ • ๋ถ„์„ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•œ ์‹ค์ „ ํŒ

๊ธฐ๋ณธ ๋ชจ๋ธ์„ ๋„˜์–ด์„œ ๋” ๋†’์€ ์„ฑ๋Šฅ์„ ์›ํ•œ๋‹ค๋ฉด? ์—ฌ๊ธฐ ๊ฒ€์ฆ๋œ ํŒ๋“ค์„ ๊ณต์œ ํ• ๊ฒŒ!

๐ŸŽฏ ํŒ 1: ๋„๋ฉ”์ธ ํŠนํ™” ํŒŒ์ธํŠœ๋‹

์ผ๋ฐ˜ ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ์„ ๊ทธ๋Œ€๋กœ ์“ฐ๋Š” ๊ฒƒ๋ณด๋‹ค, ์ž์‹ ์˜ ๋„๋ฉ”์ธ ๋ฐ์ดํ„ฐ๋กœ ํŒŒ์ธํŠœ๋‹ํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ์˜ฌ๋ผ๊ฐ€. ์˜ˆ๋ฅผ ๋“ค์–ด ์‡ผํ•‘๋ชฐ ๋ฆฌ๋ทฐ ๋ถ„์„์ด๋ผ๋ฉด ์‡ผํ•‘ ๊ด€๋ จ ํ…์ŠคํŠธ๋กœ, ์˜๋ฃŒ ์ƒ๋‹ด์ด๋ผ๋ฉด ์˜๋ฃŒ ๋Œ€ํ™” ๋ฐ์ดํ„ฐ๋กœ ํŒŒ์ธํŠœ๋‹ํ•ด๋ด.
ํŒŒ์ธํŠœ๋‹์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์ง€ ์•Š์•„. ํด๋ž˜์Šค๋‹น 500~1000๊ฐœ ์ •๋„๋ฉด ์ถฉ๋ถ„ํžˆ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์–ด!

๐ŸŽฏ ํŒ 2: ๋ฐ์ดํ„ฐ ์ฆ๊ฐ• (Data Augmentation)

ํ•™์Šต ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ€์กฑํ•  ๋•Œ ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•๋“ค:
โ€ข ์—ญ๋ฒˆ์—ญ(Back Translation): ํ•œ๊ตญ์–ด โ†’ ์˜์–ด โ†’ ํ•œ๊ตญ์–ด๋กœ ๋ฒˆ์—ญํ•ด์„œ ๋‹ค์–‘ํ•œ ํ‘œํ˜„ ์ƒ์„ฑ
โ€ข ๋™์˜์–ด ๊ต์ฒด: ๋‹จ์–ด๋ฅผ ๋™์˜์–ด๋กœ ๋ฐ”๊ฟ”์„œ ์ƒˆ๋กœ์šด ๋ฌธ์žฅ ์ƒ์„ฑ
โ€ข EDA(Easy Data Augmentation): ์‚ฝ์ž…, ์‚ญ์ œ, ๊ต์ฒด, ๊ตํ™˜ ๋“ฑ ๊ฐ„๋‹จํ•œ ๋ณ€ํ˜•
โ€ข LLM ๊ธฐ๋ฐ˜ ์ฆ๊ฐ•: GPT์—๊ฒŒ "์ด ๊ฐ์ •์„ ํ‘œํ˜„ํ•˜๋Š” ๋‹ค๋ฅธ ๋ฌธ์žฅ 10๊ฐœ ๋งŒ๋“ค์–ด์ค˜" ์š”์ฒญ

๐ŸŽฏ ํŒ 3: ์•™์ƒ๋ธ” (Ensemble)

์—ฌ๋Ÿฌ ๋ชจ๋ธ์˜ ์˜ˆ์ธก์„ ํ•ฉ์น˜๋ฉด ๋‹จ์ผ ๋ชจ๋ธ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์˜ฌ๋ผ๊ฐ€. ํŠนํžˆ ๋‹ค์Œ ์กฐํ•ฉ์ด ํšจ๊ณผ์ ์ด์•ผ:
โ€ข BERT ๊ณ„์—ด + LSTM ๊ณ„์—ด ์•™์ƒ๋ธ”
โ€ข ํ…์ŠคํŠธ ๋ชจ๋ธ + ์Œ์„ฑ ๋ชจ๋ธ ์•™์ƒ๋ธ”
โ€ข ๋‹ค๋ฅธ ์‚ฌ์ „ํ•™์Šต ๋ชจ๋ธ๋“ค์˜ ์•™์ƒ๋ธ” (KoBERT + KoELECTRA + KLUE-RoBERTa)

๐ŸŽฏ ํŒ 4: ํด๋ž˜์Šค ๋ถˆ๊ท ํ˜• ์ฒ˜๋ฆฌ

์‹ค์ œ ๋ฐ์ดํ„ฐ์—์„œ๋Š” "์ค‘๋ฆฝ"์ด ์••๋„์ ์œผ๋กœ ๋งŽ๊ณ  "ํ˜์˜ค"๋‚˜ "๊ณตํฌ"๋Š” ์ ์€ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ์ด๋Ÿฐ ๋ถˆ๊ท ํ˜•์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•:
โ€ข ๊ฐ€์ค‘ ์†์‹ค ํ•จ์ˆ˜(Weighted Loss): ์†Œ์ˆ˜ ํด๋ž˜์Šค์— ๋” ๋†’์€ ๊ฐ€์ค‘์น˜ ๋ถ€์—ฌ
โ€ข ์˜ค๋ฒ„์ƒ˜ํ”Œ๋ง(SMOTE): ์†Œ์ˆ˜ ํด๋ž˜์Šค ๋ฐ์ดํ„ฐ ์ฆ๊ฐ€
โ€ข ์–ธ๋”์ƒ˜ํ”Œ๋ง: ๋‹ค์ˆ˜ ํด๋ž˜์Šค ๋ฐ์ดํ„ฐ ๊ฐ์†Œ
โ€ข Focal Loss: ์–ด๋ ค์šด ์ƒ˜ํ”Œ์— ๋” ์ง‘์ค‘ํ•˜๋Š” ์†์‹ค ํ•จ์ˆ˜

# ํด๋ž˜์Šค ๊ฐ€์ค‘์น˜๋ฅผ ์ ์šฉํ•œ ํŒŒ์ธํŠœ๋‹ ์˜ˆ์‹œ
import torch
import torch.nn as nn
from transformers import AutoModelForSequenceClassification, Trainer, TrainingArguments
from sklearn.utils.class_weight import compute_class_weight
import numpy as np

# ํด๋ž˜์Šค ๊ฐ€์ค‘์น˜ ๊ณ„์‚ฐ
labels = [0, 0, 0, 1, 1, 2, 2, 2, 2, 3]  # ์‹ค์ œ ๋ ˆ์ด๋ธ” ๋ถ„ํฌ
class_weights = compute_class_weight(
    'balanced', 
    classes=np.unique(labels), 
    y=labels
)
weights_tensor = torch.FloatTensor(class_weights)

# ์ปค์Šคํ…€ Trainer ํด๋ž˜์Šค (๊ฐ€์ค‘ ์†์‹ค ํ•จ์ˆ˜ ์ ์šฉ)
class WeightedTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False):
        labels = inputs.get("labels")
        outputs = model(**inputs)
        logits = outputs.get("logits")
        
        # ๊ฐ€์ค‘ ํฌ๋กœ์Šค ์—”ํŠธ๋กœํ”ผ ์†์‹ค
        loss_fct = nn.CrossEntropyLoss(
            weight=weights_tensor.to(logits.device)
        )
        loss = loss_fct(logits.view(-1, self.model.config.num_labels), 
                       labels.view(-1))
        
        return (loss, outputs) if return_outputs else loss

# ํ•™์Šต ์„ค์ •
training_args = TrainingArguments(
    output_dir="./emotion_model",
    num_train_epochs=5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    metric_for_best_model="f1",
    fp16=True,  # GPU ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ
    dataloader_num_workers=4
)

# ๋ชจ๋ธ ๋กœ๋“œ ๋ฐ ํŒŒ์ธํŠœ๋‹
model = AutoModelForSequenceClassification.from_pretrained(
    "klue/roberta-base",
    num_labels=7  # 7๊ฐ€์ง€ ๊ฐ์ •
)

trainer = WeightedTrainer(
    model=model,
    args=training_args,
    # train_dataset=train_dataset,
    # eval_dataset=eval_dataset,
    # compute_metrics=compute_metrics
)

# trainer.train()  # ์‹ค์ œ ํ•™์Šต ์‹คํ–‰

๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌ: ๊ฐ์ • ๋ถ„์„์˜ ๋ฏธ๋ž˜

์šฐ๋ฆฌ๊ฐ€ ์˜ค๋Š˜ ํ•จ๊ป˜ ํƒํ—˜ํ•œ ๊ฐ์ • ๋ถ„์„ ์‹œ์Šคํ…œ, ์–ด๋•Œ? ์ƒ๊ฐ๋ณด๋‹ค ํ›จ์”ฌ ๊นŠ๊ณ  ๋„“์€ ์„ธ๊ณ„์ง€? ๐Ÿ˜„

ํ…์ŠคํŠธ์—์„œ BERT๋กœ ๊ฐ์ •์„ ์ฝ์–ด๋‚ด๊ณ , ์Œ์„ฑ์—์„œ MFCC์™€ Wav2Vec์œผ๋กœ ๋ชฉ์†Œ๋ฆฌ์˜ ๊ฐ์ •์„ ํฌ์ฐฉํ•˜๊ณ , ์ด ๋‘˜์„ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ๋กœ ํ•ฉ์ณ์„œ ๋” ์ •ํ™•ํ•œ ๊ฐ์ • ์ธ์‹์„ ํ•˜๋Š” ๊ฒƒ๊นŒ์ง€. ์ด ๋ชจ๋“  ๊ธฐ์ˆ ์ด ์ง€๊ธˆ ์ด ์ˆœ๊ฐ„์—๋„ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด.

์•ž์œผ๋กœ์˜ ๊ฐ์ • ๋ถ„์„ ๊ธฐ์ˆ ์€ ์–ด๋””๋กœ ํ–ฅํ• ๊นŒ?

๐Ÿ”ฎ ๊ฐ์ • ๋ถ„์„์˜ ๋ฏธ๋ž˜ ์ „๋ง

1. ๊ฐœ์ธํ™”๋œ ๊ฐ์ • ๋ชจ๋ธ: ๊ฐœ์ธ๋งˆ๋‹ค ๊ฐ์ • ํ‘œํ˜„ ๋ฐฉ์‹์ด ๋‹ค๋ฅด๋‹ค๋Š” ๊ฑธ ์ธ์‹ํ•˜๊ณ , ๊ฐœ์ธ์—๊ฒŒ ๋งž์ถคํ™”๋œ ๊ฐ์ • ๋ถ„์„ ๋ชจ๋ธ์ด ๋“ฑ์žฅํ•  ๊ฑฐ์•ผ.

2. ์ƒ๋ฆฌ ์‹ ํ˜ธ ํ†ตํ•ฉ: ์‹ฌ๋ฐ•์ˆ˜, ํ”ผ๋ถ€ ์ „๋„๋„, ๋‡ŒํŒŒ ๋“ฑ ์ƒ๋ฆฌ ์‹ ํ˜ธ์™€ ํ…์ŠคํŠธ/์Œ์„ฑ์„ ๊ฒฐํ•ฉํ•œ ์ดˆ์ •๋ฐ€ ๊ฐ์ • ์ธ์‹ ์‹œ์Šคํ…œ์ด ๊ฐœ๋ฐœ๋  ๊ฑฐ์•ผ.

3. ์‹ค์‹œ๊ฐ„ ๊ฐ์ • ๊ณต๊ฐ AI: ์‚ฌ์šฉ์ž์˜ ๊ฐ์ • ์ƒํƒœ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํŒŒ์•…ํ•˜๊ณ  ๊ทธ์— ๋งž๊ฒŒ ๋ฐ˜์‘ํ•˜๋Š” ์ง„์ •ํ•œ ๊ณต๊ฐํ˜• AI ์–ด์‹œ์Šคํ„ดํŠธ๊ฐ€ ๋‚˜์˜ฌ ๊ฑฐ์•ผ.

4. ๊ฐ์ • ์ƒ์„ฑ AI: ๋ถ„์„์„ ๋„˜์–ด์„œ ํŠน์ • ๊ฐ์ •์„ ์œ ๋ฐœํ•˜๋Š” ํ…์ŠคํŠธ๋‚˜ ์Œ์„ฑ์„ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ์ˆ ๋„ ๋ฐœ์ „ํ•  ๊ฑฐ์•ผ.

๊ฐ์ • ๋ถ„์„์€ ๋‹จ์ˆœํ•œ ๊ธฐ์ˆ ์ด ์•„๋‹ˆ์•ผ. ์ธ๊ฐ„์˜ ๊ฐ€์žฅ ๋‚ด๋ฐ€ํ•œ ๋ถ€๋ถ„์ธ ๊ฐ์ •์„ ์ดํ•ดํ•˜๊ณ  ์†Œํ†ตํ•˜๋ ค๋Š” ์‹œ๋„์•ผ. ๊ทธ๋ž˜์„œ ๊ธฐ์ˆ ์  ์™„์„ฑ๋„๋งŒํผ ์œค๋ฆฌ์  ์ฑ…์ž„๊ฐ๋„ ํ•จ๊ป˜ ๊ฐ€์ ธ์•ผ ํ•ด.

์ด ๊ธ€์„ ์ฝ๊ณ  ๊ฐ์ • ๋ถ„์„ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์‹ถ์–ด์กŒ๋‹ค๋ฉด, ์ž‘์€ ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ด. ์˜ํ™” ๋ฆฌ๋ทฐ ๊ฐ์„ฑ ๋ถ„์„๊ธฐ, ์ผ๊ธฐ ๊ฐ์ • ํŠธ๋ž˜์ปค, ๊ณ ๊ฐ ๋ฆฌ๋ทฐ ์ž๋™ ๋ถ„๋ฅ˜๊ธฐ... ๋ญ๋“  ์ข‹์•„. ์‹œ์ž‘์ด ๋ฐ˜์ด๋‹ˆ๊นŒ! ๐Ÿš€

๊ทธ๋ฆฌ๊ณ  ํ˜ผ์ž ํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ AI ๊ฐœ๋ฐœ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ํ•จ๊ป˜ํ•˜๋ฉด ๋” ๋นจ๋ฆฌ, ๋” ๋ฉ€๋ฆฌ ๊ฐˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ!

๐Ÿ“Œ ์˜ค๋Š˜ ๋ฐฐ์šด ํ•ต์‹ฌ ๋‚ด์šฉ ์š”์•ฝ

โœ… ๊ฐ์ • ๋ถ„์„ = ํ…์ŠคํŠธ/์Œ์„ฑ์—์„œ ๊ฐ์ •์„ ์ž๋™ ์ถ”์ถœํ•˜๋Š” AI ๊ธฐ์ˆ 
โœ… ํ…์ŠคํŠธ: ์‚ฌ์ „ ๊ธฐ๋ฐ˜ โ†’ ๋จธ์‹ ๋Ÿฌ๋‹ โ†’ BERT/Transformer ๋”ฅ๋Ÿฌ๋‹ ์ˆœ์œผ๋กœ ๋ฐœ์ „
โœ… ์Œ์„ฑ: MFCC ๋“ฑ ์Œํ–ฅ ํŠน์ง• ์ถ”์ถœ โ†’ CNN/LSTM โ†’ Wav2Vec 2.0 ์—”๋“œํˆฌ์—”๋“œ
โœ… ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ: ํ…์ŠคํŠธ + ์Œ์„ฑ ์œตํ•ฉ์œผ๋กœ ์ •ํ™•๋„ ํ–ฅ์ƒ
โœ… ํ•œ๊ตญ์–ด: KoBERT, KoELECTRA, KLUE-RoBERTa ํ™œ์šฉ
โœ… ๋ฐ์ดํ„ฐ: AIHub, NSMC ๋“ฑ ํ•œ๊ตญ์–ด ํŠนํ™” ๋ฐ์ดํ„ฐ์…‹ ํ™œ์šฉ
โœ… ํ‰๊ฐ€: F1 ์Šค์ฝ”์–ด(Macro/Weighted)๊ฐ€ ํ•ต์‹ฌ ์ง€ํ‘œ
โœ… ํ•œ๊ณ„: ์•„์ด๋Ÿฌ๋‹ˆ, ๋ฌธํ™”์  ์ฐจ์ด, ๋ณตํ•ฉ ๊ฐ์ •, ์œค๋ฆฌ ๋ฌธ์ œ ์กด์žฌ
โœ… ํŠธ๋ Œ๋“œ: LLM ๊ธฐ๋ฐ˜, ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ, ์„ค๋ช… ๊ฐ€๋Šฅ AI, ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ
๋Œ“๊ธ€ ์ž‘์„ฑ

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

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

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