Ver3.5 ๐ง ๊ฐ์ ๋ถ์ ์์คํ

๐ง ๊ฐ์ ๋ถ์ ์์คํ
์ผ, ์์งํ ๋งํด๋ด. ๐ ์นด์นด์คํก์์ ์น๊ตฌ๊ฐ "ใ
ใ
"์ด๋ผ๊ณ ๋ต์ฅ ๋ณด๋์ ๋, ๊ทธ๊ฒ ๊ธฐ๋ถ ์ข์ ๊ฑด์ง ํ๋ ๊ฑด์ง ํท๊ฐ๋ฆฐ ์ ์์ด? ์ฌ๋๋ ์ด๋ ๊ฒ ๊ฐ์ ํ์
์ด ์ด๋ ค์ด๋ฐ, ์ปดํจํฐ๊ฐ ํ
์คํธ๋ ์์ฑ์์ ๊ฐ์ ์ ์ฝ์ด๋ธ๋ค๋ ๊ฒ ์ ๊ธฐํ์ง ์์?
๊ทผ๋ฐ ์ด๊ฒ ์ง์ง๋ก ๊ฐ๋ฅํด. ๊ทธ๊ฒ๋ ๊ฝค ๋์ ์ ํ๋๋ก! ์ค๋์ ๊ฐ์ ๋ถ์ ์์คํ
(Sentiment Analysis / Emotion Recognition)์ด ์ด๋ป๊ฒ ์๋ํ๋์ง, ํ
์คํธ์ ์์ฑ ๊ฐ๊ฐ์์ ์ด๋ป๊ฒ ๊ฐ์ ์ ์ถ์ถํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฝ๋๋ก ๊ตฌํํ๋์ง๊น์ง ๋ฑ๋ฑ์ด ํํค์ณ๋ณผ ๊ฑฐ์ผ. ๐
์์ฉํ๋ก๊ทธ๋๋ฐ์ ๊ฝ์ด๋ผ๊ณ ํ ์ ์๋ ์ด ๋ถ์ผ, ๊ฐ์ด ์ ๋๊ฒ ํํํด๋ณด์๊ณ !
๐ค ๊ฐ์ ๋ถ์, ๋๋์ฒด ๋ญ์ผ?
๊ฐ์ ๋ถ์(Sentiment Analysis ๋๋ Opinion Mining)์ ํ
์คํธ, ์์ฑ, ์์ ๋ฑ์ ๋ฐ์ดํฐ์์ ์ฌ๋์ ๊ฐ์ , ์๊ฒฌ, ํ๋๋ฅผ ์๋์ผ๋ก ์ถ์ถํ๊ณ ๋ถ๋ฅํ๋ ๊ธฐ์ ์ด์ผ. ์ฝ๊ฒ ๋งํ๋ฉด "์ด ์ฌ๋์ด ์ง๊ธ ๊ธฐ๋ถ์ด ์ข์ ๊ฑด์ง ๋์ ๊ฑด์ง, ํ๊ฐ ๋ฌ๋์ง ์ฌํ์ง๋ฅผ ์ปดํจํฐ๊ฐ ์์๋ด๋ ๊ฒ"์ด์ง.
์ด ๊ธฐ์ ์ ํฌ๊ฒ ๋ ๊ฐ์ง ๋ ๋ฒจ๋ก ๋๋์ด:
๐ ๊ฐ์ ๋ถ์์ ๋ ๊ฐ์ง ํฐ ์ถ
๊ธ์ (Positive) / ๋ถ์ (Negative) / ์ค๋ฆฝ(Neutral)์ 3๊ฐ์ง๋ก ๋ถ๋ฅํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ. ๋ฆฌ๋ทฐ ๋ถ์, ์ฌ๋ก ์กฐ์ฌ ๋ฑ์ ๋ง์ด ์ฐ์ฌ.
๊ธฐ์จ(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()
๐ง ํ ์คํธ ์ ์ฒ๋ฆฌ: ๊ฐ์ ๋ถ์์ ์จ์ ์์
์๋ฌด๋ฆฌ ์ข์ 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}")
๐๏ธ ์์ฑ์์ ๊ฐ์ ์ถ์ถํ๊ธฐ: ์๋ฆฌ๋ก ๋ง์ ์ฝ๊ธฐ
์์ฑ ๊ฐ์ ๋ถ์(Speech Emotion Recognition, SER)์ ํ
์คํธ ๋ถ์๋ณด๋ค ํจ์ฌ ๋ณต์กํ๊ณ ๋์ ์ ์ธ ๋ถ์ผ์ผ. ์๋ํ๋ฉด ์์ฑ์๋ ํ
์คํธ ๋ด์ฉ๋ฟ๋ง ์๋๋ผ ์ต์, ์๋์ด, ๋ง ์๋, ์๋, ์์ ๊ฐ์ ๋น์ธ์ด์ ์ ๋ณด๊ฐ ์์ฒญ๋๊ฒ ๋ง์ด ๋ด๊ฒจ ์๊ฑฐ๋ .
์๋ฅผ ๋ค์ด "๊ทธ๋, ์๊ฒ ์ด"๋ผ๋ ๋ง๋ ์ด๋ป๊ฒ ๋งํ๋๋์ ๋ฐ๋ผ ๊ธฐ์จ, ๋ถ๋
ธ, ์ฌํ, ์ฒด๋
๋ฑ ์์ ํ ๋ค๋ฅธ ๊ฐ์ ์ ํํํ ์ ์์์!
๐ต ํต์ฌ ์ํฅ ํน์ง: 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๋ ๋ง์ฐฌ๊ฐ์ง์ผ!
๐ฌ ๋ฉํฐ๋ชจ๋ฌ ์ตํฉ ๋ฐฉ๋ฒ 3๊ฐ์ง
ํ ์คํธ์ ์์ฑ ํน์ง์ ์ฒ์๋ถํฐ ํฉ์ณ์ ํ๋์ ๋ชจ๋ธ์ ์ ๋ ฅ. ๊ตฌํ์ด ๊ฐ๋จํ์ง๋ง ๊ฐ ๋ชจ๋ฌ๋ฆฌํฐ์ ํน์ฑ์ ์ถฉ๋ถํ ํ์ฉํ๊ธฐ ์ด๋ ค์.
ํ ์คํธ ๋ชจ๋ธ๊ณผ ์์ฑ ๋ชจ๋ธ์ ๊ฐ๊ฐ ๋ฐ๋ก ํ์ต์ํจ ๋ค์, ์ต์ข ์์ธก ๊ฒฐ๊ณผ๋ฅผ ์์๋ธ. ๊ฐ ๋ชจ๋ฌ๋ฆฌํฐ์ ๊ฐ์ ์ ์ด๋ฆด ์ ์์ด.
ํ ์คํธ์ ์์ฑ์ด ์๋ก ์ดํ ์ ์ ์ฃผ๊ณ ๋ฐ์ผ๋ฉฐ ์ํธ์์ฉ. ํ์ฌ ๊ฐ์ฅ ๋์ ์ฑ๋ฅ์ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ด์ผ. 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)
๐ ๊ฐ์ ๋ถ์ ์ฃผ์ ๋ฐ์ดํฐ์ ๊ณผ ๋ฒค์น๋งํฌ
์ข์ ๋ชจ๋ธ์ ๋ง๋ค๋ ค๋ฉด ์ข์ ๋ฐ์ดํฐ๊ฐ ํ์ํด. ๊ฐ์ ๋ถ์ ๋ถ์ผ์์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ์ ๋ค์ ์์๋ณด์!
๐ ํ ์คํธ ๊ฐ์ ๋ถ์ ๋ฐ์ดํฐ์
| ๋ฐ์ดํฐ์ | ์ธ์ด | ํฌ๊ธฐ | ํน์ง |
|---|---|---|---|
| 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)์์ ํ๊ตญ์ด ๊ฐ์ ๋ฐ์ดํฐ์ ์ ๋ฌด๋ฃ๋ก ๋ค์ด๋ฐ์ ์ ์์ด. ๊ฐ์ฑ๋ํ ๋ง๋ญ์น, ๊ฐ์ ๋ถ๋ฅ๋ฅผ ์ํ ๋ํ ์์ฑ ๋ฐ์ดํฐ์ ๋ฑ ๋ค์ํ ํ๊ตญ์ด ํนํ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ ๊ผญ ํ์ฉํด๋ด!
๐ ๊ฐ์ ๋ถ์ ๋ชจ๋ธ ์ฑ๋ฅ ํ๊ฐํ๊ธฐ
๋ชจ๋ธ์ ๋ง๋ค์์ผ๋ฉด ์ผ๋ง๋ ์ ์๋ํ๋์ง ํ๊ฐํด์ผ๊ฒ ์ง? ๊ฐ์ ๋ถ์์์ ์ฌ์ฉํ๋ ์ฃผ์ ํ๊ฐ ์งํ๋ค์ ์์๋ณด์.
# ๊ฐ์ ๋ถ์ ๋ชจ๋ธ ์ฑ๋ฅ ํ๊ฐ
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: ํด๋์ค ๋น๋์ ๋น๋กํ ๊ฐ์ค์น. ์ค์ ๋ถํฌ ๋ฐ์.
๐ ๊ฐ์ ๋ถ์์ ์ค์ ํ์ฉ ์ฌ๋ก๋ค
๊ฐ์ ๋ถ์ ๊ธฐ์ ์ ์ด๋ฏธ ์ฐ๋ฆฌ ์ผ์ ๊ณณ๊ณณ์ ์ค๋ฉฐ๋ค์ด ์์ด. ์ด๋์ ์ด๋ป๊ฒ ์ฐ์ด๋์ง ์ดํด๋ณด์!
๐ฅ ๊ฐ์ฅ ํซํ ํ์ฉ ์ฌ๋ก๋ค
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 ์ต์ ํธ๋ ๋
๊ฐ์ ๋ถ์ ๋ถ์ผ๋ ์ ๋ง ๋น ๋ฅด๊ฒ ๋ฐ์ ํ๊ณ ์์ด. ์ง๊ธ ๊ฐ์ฅ ํซํ ์ฐ๊ตฌ ๋ฐฉํฅ๋ค์ ์ดํด๋ณด์!
๋ํ ์ธ์ด ๋ชจ๋ธ(LLM)์ ๊ฐ์ ๋ถ์์ ํ์ฉํ๋ ์ฐ๊ตฌ๊ฐ ํญ๋ฐ์ ์ผ๋ก ์ฆ๊ฐํ๊ณ ์์ด. ํนํ Few-shot, Zero-shot ํ์ต์ผ๋ก ์๋์ ๋ฐ์ดํฐ๋ง์ผ๋ก๋ ๋์ ์ฑ๋ฅ์ ๋ฌ์ฑํ ์ ์์ด. Chain-of-Thought ํ๋กฌํํ ์ผ๋ก ๊ฐ์ ๋ถ์์ ์ด์ ๊น์ง ์ค๋ช ํ๋ ๊ฒ๋ ๊ฐ๋ฅํด์ก์ด.
ํ ์คํธ, ์์ฑ, ์ด๋ฏธ์ง, ์์์ ๋์์ ์ฒ๋ฆฌํ๋ ๋ฉํฐ๋ชจ๋ฌ ๋ชจ๋ธ์ด ๊ฐ์ ๋ถ์์ ํ๋ช ์ ์ผ์ผํค๊ณ ์์ด. ์ผ๊ตด ํ์ + ๋ชฉ์๋ฆฌ + ํ ์คํธ๋ฅผ ๋์์ ๋ถ์ํด์ ํจ์ฌ ์ ํํ ๊ฐ์ ์ธ์์ด ๊ฐ๋ฅํด์ก์ด.
๊ธฐ์จ/์ฌํ ๊ฐ์ ์ด์ฐ์ ์นดํ ๊ณ ๋ฆฌ ๋์ , ๊ฐ์ ์ ์ฐ์์ ์ธ 3์ฐจ์ ๊ณต๊ฐ(๊ธ๋ถ์ ์ฑ-๊ฐ์ฑ๋-์ง๋ฐฐ์ฑ)์ผ๋ก ํํํ๋ ์ฐ๊ตฌ๊ฐ ์ฃผ๋ชฉ๋ฐ๊ณ ์์ด. ๋ ์ธ๋ฐํ๊ณ ์์ฐ์ค๋ฌ์ด ๊ฐ์ ํํ์ด ๊ฐ๋ฅํด.
"์ ์ด ํ ์คํธ๋ฅผ ๋ถ์ ์ผ๋ก ๋ถ๋ฅํ๋์ง" ์ค๋ช ํ ์ ์๋ ๋ชจ๋ธ์ ๋ํ ์์๊ฐ ๊ธ์ฆํ๊ณ ์์ด. LIME, SHAP, Attention Visualization ๋ฑ์ ๊ธฐ๋ฒ์ผ๋ก ๋ชจ๋ธ์ ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ์๊ฐํํ๋ ์ฐ๊ตฌ๊ฐ ํ๋ฐํด.
ํ์ ํ์, ๋ผ์ด๋ธ ์คํธ๋ฆฌ๋ฐ, ์ค์๊ฐ ๊ณ ๊ฐ ์๋ด ๋ฑ์์ ์ง์ฐ ์์ด ๊ฐ์ ์ ๋ถ์ํ๋ ๊ธฐ์ . ์ฃ์ง ์ปดํจํ ๊ณผ ๋ชจ๋ธ ๊ฒฝ๋ํ(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()
๐ก ๊ฐ์ ๋ถ์ ์ฑ๋ฅ ํฅ์์ ์ํ ์ค์ ํ
๊ธฐ๋ณธ ๋ชจ๋ธ์ ๋์ด์ ๋ ๋์ ์ฑ๋ฅ์ ์ํ๋ค๋ฉด? ์ฌ๊ธฐ ๊ฒ์ฆ๋ ํ๋ค์ ๊ณต์ ํ ๊ฒ!
๐ฏ ํ 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
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.