Ver3.5 ๐๏ธ ์์ฑ ๊ฐ์ ๋ถ์ ํ๋ก๊ทธ๋จ ์์ค์ฝ๋

๐๏ธ ์์ฑ ๊ฐ์ ๋ถ์ ํ๋ก๊ทธ๋จ ์์ค์ฝ๋
์ผ, ์์งํ ๋งํด๋ด. ๐ ์นด์นด์คํก ์์ฑ๋ฉ์์ง ๋ค์ ๋ "์ด ์ฌ๋ ์ง๊ธ ํ๋ฌ๋?" ์ถ์๋ ์ ์์ด?
์๋๋ฉด ๊ณ ๊ฐ์ผํฐ ํตํ ๋
น์ ๋ฃ๋ค๊ฐ "์ด ๊ณ ๊ฐ ์์ฒญ ๋ถ๋ง์กฑ์ค๋ฌ์ ๋ณด์ด๋๋ฐ..." ๋๋ ์ ?
๊ทธ๊ฒ ๋ฐ๋ก ์์ฑ ๊ฐ์ ๋ถ์(Speech Emotion Recognition, SER)์ด ํ๋ ์ผ์ด์ผ.
์ฌ๋์ ๋ชฉ์๋ฆฌ์๋ ๋จ์ํ ๋จ์ด ์ด์์ ์ ๋ณด๊ฐ ๋ด๊ฒจ ์๊ฑฐ๋ . ํผ์น(์๋์ด), ์๋์ง, ๋ฆฌ๋ฌ, ๋จ๋ฆผ...
์ด ๋ชจ๋ ๊ฑธ ์์น๋ก ๋ฝ์๋ด์ "์ด ์ฌ๋ ์ง๊ธ ์ฌํ๋ค" "์ด ์ฌ๋ ์ ๋ฌ๋ค" ๋ฅผ AI๊ฐ ํ๋จํ๋ ๊ฑฐ์ง.
์ค๋์ ์ด๊ฑธ ์ค์ ๋ก ๊ตฌํํ๋ ์์ค์ฝ๋๋ฅผ ์ฒ์๋ถํฐ ๋๊น์ง ๊ฐ์ด ๋ง๋ค์ด๋ณผ ๊ฑฐ์ผ. ๐
์ด๋ก ๋ง ๋์ด๋๋ ์ง๋ฃจํ ๊ธ ์๋๊ณ , ์ง์ง ๋์๊ฐ๋ ์ฝ๋ ์์ฃผ๋ก ์ค๋ช
ํ ๊ฒ!
๐ง 1. ์์ฑ ๊ฐ์ ๋ถ์์ด๋? ํต์ฌ ๊ฐ๋ ์ ๋ฆฌ
๋จผ์ ๊ฐ๋
๋ถํฐ ๋น ๋ฅด๊ฒ ์ก๊ณ ๊ฐ์. ์์ฑ ๊ฐ์ ๋ถ์(SER: Speech Emotion Recognition)์
์ฌ๋์ ๋ชฉ์๋ฆฌ์์ ๊ฐ์ ์ํ๋ฅผ ์๋์ผ๋ก ์ธ์ํ๋ ๊ธฐ์ ์ด์ผ.
๋จ์ํ "๋ญ๋ผ๊ณ ๋งํ๋"๋ฅผ ๋ถ์ํ๋ ์์ฑ ์ธ์(STT)๊ณผ๋ ๋ฌ๋ฆฌ,
"์ด๋ค ๊ฐ์ ์ผ๋ก ๋งํ๋"๋ฅผ ๋ถ์ํ๋ ๊ฑฐ์ง. ์์ ๋ค๋ฅธ ์์ญ์ด์ผ!
๐ SER์ด ํ์ฉ๋๋ ์ค์ ๋ถ์ผ
๐ง ์ฝ์ผํฐ ํ์ง ๊ด๋ฆฌ โ ๊ณ ๊ฐ ๋ถ๋ง ๊ฐ์ง, ์๋ด์ฌ ๊ฐ์ ๋ชจ๋ํฐ๋ง
๐ฎ ๊ฒ์/๋ฉํ๋ฒ์ค โ ํ๋ ์ด์ด ๊ฐ์ ์ ๋ฐ์ํ๋ NPC
๐ฅ ์ ์ ๊ฑด๊ฐ ๋ชจ๋ํฐ๋ง โ ์ฐ์ธ์ฆ, ์คํธ๋ ์ค ์กฐ๊ธฐ ๊ฐ์ง
๐ ์๋์ฐจ ์์ โ ์ด์ ์ ์กธ์/์คํธ๋ ์ค ๊ฐ์ง
๐ฑ ์ค๋งํธ ์คํผ์ปค โ ์ฌ์ฉ์ ๊ฐ์ ์ ๋ง๋ ์๋ต ์์ฑ
๐ ๊ต์ก ํ๋ซํผ โ ํ์ต์ ์ง์ค๋/ํฅ๋ฏธ ๋ถ์
๊ฐ์ ์ ์ข
๋ฅ๋ ์ฐ๊ตฌ๋ง๋ค ๋ค๋ฅด์ง๋ง, ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ๊ฑด Ekman์ 6๊ฐ์ง ๊ธฐ๋ณธ ๊ฐ์ ์ด์ผ:
๊ธฐ์จ(Happy), ์ฌํ(Sad), ๋ถ๋
ธ(Angry), ๊ณตํฌ(Fear), ํ์ค(Disgust), ๋๋(Surprise)
์ฌ๊ธฐ์ ์ค๋ฆฝ(Neutral)์ ์ถ๊ฐํด์ 7๊ฐ๋ก ๋ถ๋ฅํ๋ ๊ฒ ์ผ๋ฐ์ ์ด์ผ.
๐ ๏ธ 2. ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ โ ๋ญ๊ฐ ํ์ํด?
์, ์ด์ ์ง์ง ์ฝ๋ ์๊ธฐ ์์! ๐ฅ
์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ์ฃผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด์ผ. Python ๊ธฐ๋ฐ์ผ๋ก ๊ฐ ๊ฑฐ์ผ.
| ๋ผ์ด๋ธ๋ฌ๋ฆฌ | ์ญํ | ๋ฒ์ |
|---|---|---|
| librosa | ์์ฑ ํ์ผ ๋ก๋ & ํน์ง ์ถ์ถ | 0.10.x |
| numpy | ์์น ์ฐ์ฐ | 1.24.x |
| tensorflow / keras | ๋ฅ๋ฌ๋ ๋ชจ๋ธ ๊ตฌ์ถ | 2.13.x |
| scikit-learn | ๋ฐ์ดํฐ ๋ถํ , ํ๊ฐ ์งํ | 1.3.x |
| soundfile | ์ค๋์ค ํ์ผ I/O | 0.12.x |
| matplotlib | ์๊ฐํ | 3.7.x |
| pandas | ๋ฐ์ดํฐ ๊ด๋ฆฌ | 2.0.x |
์ค์น๋ ์ด๋ ๊ฒ ํ๋ฉด ๋ผ:
pip install librosa numpy tensorflow scikit-learn soundfile matplotlib pandas
์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ๋ฐ์ดํฐ์
์ RAVDESS (Ryerson Audio-Visual Database of Emotional Speech and Song)์ผ.
๊ณต๊ฐ ๋ฐ์ดํฐ์
์ค์์ ๊ฐ์ฅ ๋๋ฆฌ ์ฐ์ด๋ ์์ด ๊ฐ์ ์์ฑ ๋ฐ์ดํฐ์
์ด๊ณ ,
24๋ช
์ ๋ฐฐ์ฐ๊ฐ 8๊ฐ์ง ๊ฐ์ ์ผ๋ก ๋
น์ํ 1,440๊ฐ์ ์ค๋์ค ํ์ผ์ด ๋ค์ด์์ด.
ํ๊ตญ์ด ๋ฐ์ดํฐ์
์ผ๋ก๋ KESDy (Korean Emotional Speech Dataset)๋
ETRI ๊ฐ์ ์์ฑ ๋ฐ์ดํฐ์
์ ํ์ฉํ ์ ์์ด. ํ๊ตญ์ด ํ๋ก์ ํธ๋ผ๋ฉด ์ด์ชฝ์ ์จ๋ด!
๐ 3. ๋ฐ์ดํฐ ๋ก๋ฉ & ์ ์ฒ๋ฆฌ ์์ค์ฝ๋
RAVDESS ๋ฐ์ดํฐ์
์ ํ์ผ๋ช
์๋ ๊ฐ์ ๋ ์ด๋ธ์ด ์ธ์ฝ๋ฉ๋์ด ์์ด.
์๋ฅผ ๋ค์ด 03-01-05-01-01-01-01.wav ์์ ์ธ ๋ฒ์งธ ์ซ์(05)๊ฐ ๊ฐ์ ์ฝ๋์ผ.
์ด๊ฑธ ํ์ฑํด์ ๋ ์ด๋ธ์ ๋ง๋๋ ์ฝ๋๋ถํฐ ์์ํ์!
import os
import glob
import numpy as np
import pandas as pd
import librosa
import soundfile as sf
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
# =============================================
# 1. RAVDESS ๊ฐ์ ๋ ์ด๋ธ ๋งคํ
# =============================================
EMOTION_MAP = {
'01': 'neutral', # ์ค๋ฆฝ
'02': 'calm', # ์ฐจ๋ถ
'03': 'happy', # ๊ธฐ์จ
'04': 'sad', # ์ฌํ
'05': 'angry', # ๋ถ๋
ธ
'06': 'fearful', # ๊ณตํฌ
'07': 'disgust', # ํ์ค
'08': 'surprised' # ๋๋
}
# =============================================
# 2. ๋ฐ์ดํฐ์
๊ฒฝ๋ก์์ ํ์ผ ๋ชฉ๋ก & ๋ ์ด๋ธ ์ถ์ถ
# =============================================
def load_ravdess_data(data_path):
"""
RAVDESS ๋ฐ์ดํฐ์
๋ก๋ ํจ์
data_path: RAVDESS ํด๋ ๊ฒฝ๋ก (Actor_01 ~ Actor_24 ํฌํจ)
"""
file_paths = []
labels = []
# ๋ชจ๋ wav ํ์ผ ํ์
for wav_file in glob.glob(os.path.join(data_path, '**', '*.wav'), recursive=True):
filename = os.path.basename(wav_file)
parts = filename.replace('.wav', '').split('-')
# ํ์ผ๋ช
ํ์ ๊ฒ์ฆ
if len(parts) >= 3:
emotion_code = parts[2] # ์ธ ๋ฒ์งธ ์์๊ฐ ๊ฐ์ ์ฝ๋
if emotion_code in EMOTION_MAP:
file_paths.append(wav_file)
labels.append(EMOTION_MAP[emotion_code])
print(f"์ด {len(file_paths)}๊ฐ ํ์ผ ๋ก๋ ์๋ฃ")
# ๊ฐ์ ๋ณ ๋ถํฌ ํ์ธ
label_series = pd.Series(labels)
print("\n๊ฐ์ ๋ณ ๋ฐ์ดํฐ ๋ถํฌ:")
print(label_series.value_counts())
return file_paths, labels
# ์ฌ์ฉ ์์
# file_paths, labels = load_ravdess_data('./RAVDESS')
์ด์ ํต์ฌ์ธ ํน์ง ์ถ์ถ(Feature Extraction) ์ฝ๋์ผ.
์์ฑ์์ ์ด๋ค ํน์ง์ ๋ฝ๋๋๊ฐ ๋ชจ๋ธ ์ฑ๋ฅ์ ์ข์ฐํ๋ ํต์ฌ์ด๊ฑฐ๋ ! ๐ฏ
# =============================================
# 3. ์์ฑ ํน์ง ์ถ์ถ ํจ์ (ํต์ฌ!)
# =============================================
def extract_features(file_path, sr=22050, duration=3.0):
"""
์์ฑ ํ์ผ์์ ๋ค์ํ ํน์ง์ ์ถ์ถํ๋ ํจ์
์ถ์ถ ํน์ง:
- MFCC (Mel-Frequency Cepstral Coefficients): 40๊ฐ
- Mel Spectrogram: 128๊ฐ
- Chroma Features: 12๊ฐ
- Spectral Contrast: 7๊ฐ
- Zero Crossing Rate: 1๊ฐ
- RMS Energy: 1๊ฐ
"""
try:
# ์ค๋์ค ๋ก๋ (๊ณ ์ ๊ธธ์ด๋ก ํจ๋ฉ/์๋ฅด๊ธฐ)
audio, sample_rate = librosa.load(
file_path,
sr=sr,
duration=duration,
mono=True
)
# ๊ณ ์ ๊ธธ์ด ๋ง์ถ๊ธฐ (ํจ๋ฉ ๋๋ ์๋ฅด๊ธฐ)
target_length = int(sr * duration)
if len(audio) < target_length:
# ์งง์ผ๋ฉด 0์ผ๋ก ํจ๋ฉ
audio = np.pad(audio, (0, target_length - len(audio)))
else:
# ๊ธธ๋ฉด ์๋ฅด๊ธฐ
audio = audio[:target_length]
# ---- MFCC ์ถ์ถ ----
# ์์ฑ์ ์คํํธ๋ผ ํฌ๋ฝ์ ์ ํํ, ๊ฐ์ ์ธ์์ ๊ฐ์ฅ ์ค์ํ ํน์ง
mfcc = librosa.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40)
mfcc_mean = np.mean(mfcc, axis=1) # ์๊ฐ์ถ ํ๊ท
mfcc_std = np.std(mfcc, axis=1) # ์๊ฐ์ถ ํ์คํธ์ฐจ
mfcc_delta = np.mean(librosa.feature.delta(mfcc), axis=1) # 1์ฐจ ๋ฏธ๋ถ
# ---- Mel Spectrogram ----
mel_spec = librosa.feature.melspectrogram(
y=audio, sr=sample_rate, n_mels=128
)
mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
mel_mean = np.mean(mel_spec_db, axis=1)
# ---- Chroma Features (์์ ๊ด๋ จ) ----
chroma = librosa.feature.chroma_stft(y=audio, sr=sample_rate)
chroma_mean = np.mean(chroma, axis=1)
# ---- Spectral Contrast ----
contrast = librosa.feature.spectral_contrast(y=audio, sr=sample_rate)
contrast_mean = np.mean(contrast, axis=1)
# ---- Zero Crossing Rate (๋ชฉ์๋ฆฌ ๊ฑฐ์น ๊ธฐ) ----
zcr = librosa.feature.zero_crossing_rate(audio)
zcr_mean = np.mean(zcr)
# ---- RMS Energy (์๋/์๋์ง) ----
rms = librosa.feature.rms(y=audio)
rms_mean = np.mean(rms)
# ---- Pitch (F0) ์ถ์ถ ----
pitches, magnitudes = librosa.piptrack(y=audio, sr=sample_rate)
pitch_mean = np.mean(pitches[pitches > 0]) if np.any(pitches > 0) else 0
pitch_std = np.std(pitches[pitches > 0]) if np.any(pitches > 0) else 0
# ๋ชจ๋ ํน์ง ๊ฒฐํฉ
features = np.concatenate([
mfcc_mean, # 40
mfcc_std, # 40
mfcc_delta, # 40
mel_mean, # 128
chroma_mean, # 12
contrast_mean, # 7
[zcr_mean], # 1
[rms_mean], # 1
[pitch_mean], # 1
[pitch_std] # 1
])
# ์ด ํน์ง ๋ฒกํฐ ํฌ๊ธฐ: 271
return features
except Exception as e:
print(f"ํน์ง ์ถ์ถ ์ค๋ฅ ({file_path}): {e}")
return None
# =============================================
# 4. ์ ์ฒด ๋ฐ์ดํฐ์
ํน์ง ์ถ์ถ
# =============================================
def prepare_dataset(file_paths, labels):
"""์ ์ฒด ๋ฐ์ดํฐ์
์ ํน์ง์ ์ถ์ถํ๊ณ ๋ฐฐ์ด๋ก ๋ฐํ"""
X = []
y = []
print("ํน์ง ์ถ์ถ ์ค...")
for i, (fp, label) in enumerate(zip(file_paths, labels)):
if i % 100 == 0:
print(f" ์งํ: {i}/{len(file_paths)}")
features = extract_features(fp)
if features is not None:
X.append(features)
y.append(label)
X = np.array(X)
y = np.array(y)
print(f"\nํน์ง ์ถ์ถ ์๋ฃ!")
print(f"X shape: {X.shape}")
print(f"y shape: {y.shape}")
return X, y
๐ค 4. ๋ฅ๋ฌ๋ ๋ชจ๋ธ ๊ตฌ์ถ โ CNN + LSTM ํ์ด๋ธ๋ฆฌ๋
ํน์ง ์ถ์ถ์ด ๋๋ฌ์ผ๋ฉด ์ด์ ๋ชจ๋ธ์ ๋ง๋ค ์ฐจ๋ก์ผ! ๐
์์ฑ ๊ฐ์ ๋ถ์์๋ ์ฌ๋ฌ ๋ชจ๋ธ ์ํคํ
์ฒ๊ฐ ์ฐ์ด๋๋ฐ,
์ค๋์ CNN + LSTM ํ์ด๋ธ๋ฆฌ๋ ๋ชจ๋ธ์ ๋ง๋ค์ด๋ณผ ๊ฑฐ์ผ.
์ ์ด ์กฐํฉ์ด๋๊ณ ? CNN์ ๊ณต๊ฐ์ ํจํด(์คํํธ๋ผ ํํ)์ ์ ์ก๊ณ ,
LSTM์ ์๊ฐ์ ํจํด(์์ฑ์ ํ๋ฆ)์ ์ ์ก๊ฑฐ๋ . ๋์ ์ฅ์ ์ ํฉ์น ๊ฑฐ์ง!
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Model
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import classification_report, confusion_matrix
import numpy as np
# =============================================
# 5. ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ (์ค์ผ์ผ๋ง & ์ธ์ฝ๋ฉ)
# =============================================
def preprocess_data(X, y, test_size=0.2, val_size=0.1):
"""
๋ฐ์ดํฐ ์ค์ผ์ผ๋ง, ๋ ์ด๋ธ ์ธ์ฝ๋ฉ, ๋ฐ์ดํฐ ๋ถํ
"""
# ๋ ์ด๋ธ ์ธ์ฝ๋ฉ (๋ฌธ์์ด โ ์ซ์)
le = LabelEncoder()
y_encoded = le.fit_transform(y)
y_onehot = keras.utils.to_categorical(y_encoded)
print(f"๊ฐ์ ํด๋์ค: {le.classes_}")
print(f"ํด๋์ค ์: {len(le.classes_)}")
# ํน์ง ์ ๊ทํ (StandardScaler)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# ํ์ต/๊ฒ์ฆ/ํ
์คํธ ๋ถํ
X_temp, X_test, y_temp, y_test = train_test_split(
X_scaled, y_onehot, test_size=test_size,
random_state=42, stratify=y_encoded
)
val_ratio = val_size / (1 - test_size)
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp, test_size=val_ratio,
random_state=42
)
print(f"\n๋ฐ์ดํฐ ๋ถํ ๊ฒฐ๊ณผ:")
print(f" ํ์ต: {X_train.shape[0]}๊ฐ")
print(f" ๊ฒ์ฆ: {X_val.shape[0]}๊ฐ")
print(f" ํ
์คํธ: {X_test.shape[0]}๊ฐ")
return X_train, X_val, X_test, y_train, y_val, y_test, le, scaler
# =============================================
# 6. CNN + LSTM ํ์ด๋ธ๋ฆฌ๋ ๋ชจ๋ธ
# =============================================
def build_cnn_lstm_model(input_dim, num_classes):
"""
CNN + LSTM ํ์ด๋ธ๋ฆฌ๋ ๊ฐ์ ๋ถ๋ฅ ๋ชจ๋ธ
๊ตฌ์กฐ:
Input โ Reshape โ Conv1D ร 3 โ LSTM ร 2 โ Dense โ Output
"""
inputs = keras.Input(shape=(input_dim,), name='audio_features')
# 1D ๋ฐฐ์ด์ ์ํ์ค ํํ๋ก ๋ณํ (CNN/LSTM ์
๋ ฅ์ฉ)
x = layers.Reshape((input_dim, 1))(inputs)
# ---- CNN ๋ธ๋ก 1 ----
x = layers.Conv1D(64, kernel_size=5, padding='same', activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.MaxPooling1D(pool_size=2)(x)
x = layers.Dropout(0.3)(x)
# ---- CNN ๋ธ๋ก 2 ----
x = layers.Conv1D(128, kernel_size=5, padding='same', activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.MaxPooling1D(pool_size=2)(x)
x = layers.Dropout(0.3)(x)
# ---- CNN ๋ธ๋ก 3 ----
x = layers.Conv1D(256, kernel_size=3, padding='same', activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.MaxPooling1D(pool_size=2)(x)
x = layers.Dropout(0.3)(x)
# ---- LSTM ๋ธ๋ก 1 ----
x = layers.LSTM(128, return_sequences=True, dropout=0.3)(x)
x = layers.BatchNormalization()(x)
# ---- LSTM ๋ธ๋ก 2 ----
x = layers.LSTM(64, return_sequences=False, dropout=0.3)(x)
x = layers.BatchNormalization()(x)
# ---- Fully Connected ๋ ์ด์ด ----
x = layers.Dense(256, activation='relu')(x)
x = layers.Dropout(0.4)(x)
x = layers.Dense(128, activation='relu')(x)
x = layers.Dropout(0.3)(x)
# ---- ์ถ๋ ฅ ๋ ์ด์ด ----
outputs = layers.Dense(num_classes, activation='softmax', name='emotion_output')(x)
model = Model(inputs=inputs, outputs=outputs, name='SER_CNN_LSTM')
return model
# =============================================
# 7. ๋ชจ๋ธ ์ปดํ์ผ & ํ์ต
# =============================================
def train_model(X_train, X_val, y_train, y_val, num_classes, epochs=100):
"""๋ชจ๋ธ ํ์ต ํจ์"""
input_dim = X_train.shape[1]
model = build_cnn_lstm_model(input_dim, num_classes)
# ๋ชจ๋ธ ๊ตฌ์กฐ ์ถ๋ ฅ
model.summary()
# ์ปดํ์ผ
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='categorical_crossentropy',
metrics=['accuracy']
)
# ์ฝ๋ฐฑ ์ค์
callbacks = [
# ๊ฒ์ฆ ์์ค์ด ๊ฐ์ ๋์ง ์์ผ๋ฉด ํ์ต๋ฅ ๊ฐ์
keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=10,
min_lr=1e-6,
verbose=1
),
# ๊ณผ์ ํฉ ๋ฐฉ์ง: ๊ฒ์ฆ ์์ค ๊ธฐ์ค ์กฐ๊ธฐ ์ข
๋ฃ
keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=20,
restore_best_weights=True,
verbose=1
),
# ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ ์ ์ฅ
keras.callbacks.ModelCheckpoint(
'best_ser_model.h5',
monitor='val_accuracy',
save_best_only=True,
verbose=1
)
]
# ํ์ต ์์!
history = model.fit(
X_train, y_train,
validation_data=(X_val, y_val),
epochs=epochs,
batch_size=32,
callbacks=callbacks,
verbose=1
)
return model, history
๐ 5. ๋ฐ์ดํฐ ์ฆ๊ฐ โ ์์ ๋ฐ์ดํฐ์ ๊ทน๋ณตํ๊ธฐ
์์ฑ ๋ฐ์ดํฐ ์ฆ๊ฐ์ ํ
์คํธ๋ ์ด๋ฏธ์ง๋ณด๋ค ํจ์ฌ ๋ค์ํ ๋ฐฉ๋ฒ์ด ์์ด.
์ค์ ๋ก ์ด ๊ธฐ๋ฒ๋ค์ ์ ์ฉํ๋ฉด ๋ชจ๋ธ ์ฑ๋ฅ์ด 5~15% ํฅ์๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์! ๐
# =============================================
# 8. ์์ฑ ๋ฐ์ดํฐ ์ฆ๊ฐ ๊ธฐ๋ฒ๋ค
# =============================================
def augment_audio(audio, sr=22050, augment_type='random'):
"""
์์ฑ ๋ฐ์ดํฐ ์ฆ๊ฐ ํจ์
์ฆ๊ฐ ๊ธฐ๋ฒ:
1. ๋
ธ์ด์ฆ ์ถ๊ฐ (Noise Addition)
2. ์๊ฐ ์ด๋ (Time Shifting)
3. ํผ์น ๋ณํ (Pitch Shifting)
4. ์๋ ๋ณํ (Time Stretching)
5. ๋ณผ๋ฅจ ์กฐ์ (Volume Scaling)
"""
import random
if augment_type == 'random':
augment_type = random.choice([
'noise', 'shift', 'pitch', 'stretch', 'volume'
])
augmented = audio.copy()
if augment_type == 'noise':
# ๊ฐ์ฐ์์ ๋
ธ์ด์ฆ ์ถ๊ฐ (์ค์ ํ๊ฒฝ ์๋ฎฌ๋ ์ด์
)
noise_factor = 0.005
noise = np.random.randn(len(audio))
augmented = audio + noise_factor * noise
augmented = augmented.astype(np.float32)
elif augment_type == 'shift':
# ์๊ฐ ์ด๋ (์๋ค๋ก ์ต๋ 1์ด ์ด๋)
shift_max = int(sr * 0.5) # 0.5์ด
shift = np.random.randint(-shift_max, shift_max)
augmented = np.roll(audio, shift)
# ์ด๋๋ ๋ถ๋ถ 0์ผ๋ก ์ฑ์ฐ๊ธฐ
if shift > 0:
augmented[:shift] = 0
elif shift < 0:
augmented[shift:] = 0
elif augment_type == 'pitch':
# ํผ์น ๋ณํ (-3 ~ +3 ๋ฐ์)
n_steps = np.random.uniform(-3, 3)
augmented = librosa.effects.pitch_shift(
audio, sr=sr, n_steps=n_steps
)
elif augment_type == 'stretch':
# ์๋ ๋ณํ (0.8x ~ 1.2x)
rate = np.random.uniform(0.8, 1.2)
augmented = librosa.effects.time_stretch(audio, rate=rate)
# ๊ธธ์ด ๋ง์ถ๊ธฐ
target_len = len(audio)
if len(augmented) > target_len:
augmented = augmented[:target_len]
else:
augmented = np.pad(augmented, (0, target_len - len(augmented)))
elif augment_type == 'volume':
# ๋ณผ๋ฅจ ์กฐ์ (0.5x ~ 1.5x)
factor = np.random.uniform(0.5, 1.5)
augmented = audio * factor
return augmented
# =============================================
# 9. ์ฆ๊ฐ ํฌํจ ํน์ง ์ถ์ถ (๋ฐ์ดํฐ์
ํ์ฅ)
# =============================================
def extract_features_with_augmentation(file_path, sr=22050,
duration=3.0, n_augments=2):
"""
์๋ณธ + ์ฆ๊ฐ ๋ฐ์ดํฐ์ ํน์ง์ ํจ๊ป ์ถ์ถ
n_augments: ์ฆ๊ฐ ํ์ (์๋ณธ 1๊ฐ + ์ฆ๊ฐ n๊ฐ)
"""
features_list = []
try:
audio, sample_rate = librosa.load(file_path, sr=sr,
duration=duration, mono=True)
target_length = int(sr * duration)
if len(audio) < target_length:
audio = np.pad(audio, (0, target_length - len(audio)))
else:
audio = audio[:target_length]
# ์๋ณธ ํน์ง ์ถ์ถ
# (extract_features ํจ์๋ฅผ ์ค๋์ค ๋ฐฐ์ด ๋ฒ์ ์ผ๋ก ์์ ํ์)
features_list.append(extract_features_from_array(audio, sr))
# ์ฆ๊ฐ ๋ฐ์ดํฐ ํน์ง ์ถ์ถ
for _ in range(n_augments):
aug_audio = augment_audio(audio, sr)
features_list.append(extract_features_from_array(aug_audio, sr))
except Exception as e:
print(f"์ค๋ฅ: {e}")
return features_list
def extract_features_from_array(audio, sr=22050):
"""์ค๋์ค ๋ฐฐ์ด์์ ์ง์ ํน์ง ์ถ์ถ (ํ์ผ ๊ฒฝ๋ก ๋์ ๋ฐฐ์ด ์
๋ ฅ)"""
mfcc = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=40)
mfcc_mean = np.mean(mfcc, axis=1)
mfcc_std = np.std(mfcc, axis=1)
mfcc_delta = np.mean(librosa.feature.delta(mfcc), axis=1)
mel_spec = librosa.feature.melspectrogram(y=audio, sr=sr, n_mels=128)
mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
mel_mean = np.mean(mel_spec_db, axis=1)
chroma = librosa.feature.chroma_stft(y=audio, sr=sr)
chroma_mean = np.mean(chroma, axis=1)
contrast = librosa.feature.spectral_contrast(y=audio, sr=sr)
contrast_mean = np.mean(contrast, axis=1)
zcr_mean = np.mean(librosa.feature.zero_crossing_rate(audio))
rms_mean = np.mean(librosa.feature.rms(y=audio))
pitches, _ = librosa.piptrack(y=audio, sr=sr)
pitch_mean = np.mean(pitches[pitches > 0]) if np.any(pitches > 0) else 0
pitch_std = np.std(pitches[pitches > 0]) if np.any(pitches > 0) else 0
return np.concatenate([
mfcc_mean, mfcc_std, mfcc_delta, mel_mean,
chroma_mean, contrast_mean,
[zcr_mean], [rms_mean], [pitch_mean], [pitch_std]
])
๐ 6. ๋ชจ๋ธ ํ๊ฐ & ์๊ฐํ
๋ชจ๋ธ์ ํ์ตํ์ผ๋ฉด ์ผ๋ง๋ ์ ๋๋์ง ํ์ธํด์ผ์ง! ๐
๋จ์ ์ ํ๋(Accuracy)๋ง ๋ณด๋ฉด ์ ๋๊ณ , ๊ฐ ๊ฐ์ ๋ณ ์ฑ๋ฅ์ ๊ผผ๊ผผํ ๋ด์ผ ํด.
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
# =============================================
# 10. ๋ชจ๋ธ ํ๊ฐ ํจ์
# =============================================
def evaluate_model(model, X_test, y_test, le):
"""
๋ชจ๋ธ ์ฑ๋ฅ ํ๊ฐ ๋ฐ ์๊ฐํ
"""
# ์์ธก
y_pred_prob = model.predict(X_test)
y_pred = np.argmax(y_pred_prob, axis=1)
y_true = np.argmax(y_test, axis=1)
# ์ ์ฒด ์ ํ๋
accuracy = np.mean(y_pred == y_true)
print(f"\n=== ๋ชจ๋ธ ํ๊ฐ ๊ฒฐ๊ณผ ===")
print(f"ํ
์คํธ ์ ํ๋: {accuracy:.4f} ({accuracy*100:.2f}%)")
# ๊ฐ์ ๋ณ ์์ธ ์ฑ๋ฅ (Precision, Recall, F1-Score)
print("\n=== ๊ฐ์ ๋ณ ์ฑ๋ฅ ===")
print(classification_report(
y_true, y_pred,
target_names=le.classes_
))
# ํผ๋ ํ๋ ฌ ์๊ฐํ
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(10, 8))
sns.heatmap(
cm,
annot=True,
fmt='d',
cmap='Blues',
xticklabels=le.classes_,
yticklabels=le.classes_
)
plt.title('๊ฐ์ ๋ถ๋ฅ ํผ๋ ํ๋ ฌ (Confusion Matrix)', fontsize=14, pad=15)
plt.ylabel('์ค์ ๊ฐ์ ', fontsize=12)
plt.xlabel('์์ธก ๊ฐ์ ', fontsize=12)
plt.tight_layout()
plt.savefig('confusion_matrix.png', dpi=150, bbox_inches='tight')
plt.show()
return accuracy, y_pred, y_pred_prob
# =============================================
# 11. ํ์ต ๊ณก์ ์๊ฐํ
# =============================================
def plot_training_history(history):
"""ํ์ต/๊ฒ์ฆ ์์ค ๋ฐ ์ ํ๋ ์๊ฐํ"""
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# ์ ํ๋ ๊ทธ๋ํ
axes[0].plot(history.history['accuracy'],
label='ํ์ต ์ ํ๋', color='#667eea', linewidth=2)
axes[0].plot(history.history['val_accuracy'],
label='๊ฒ์ฆ ์ ํ๋', color='#764ba2', linewidth=2, linestyle='--')
axes[0].set_title('๋ชจ๋ธ ์ ํ๋', fontsize=13, fontweight='bold')
axes[0].set_xlabel('์ํญ')
axes[0].set_ylabel('์ ํ๋')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# ์์ค ๊ทธ๋ํ
axes[1].plot(history.history['loss'],
label='ํ์ต ์์ค', color='#e17055', linewidth=2)
axes[1].plot(history.history['val_loss'],
label='๊ฒ์ฆ ์์ค', color='#fdcb6e', linewidth=2, linestyle='--')
axes[1].set_title('๋ชจ๋ธ ์์ค', fontsize=13, fontweight='bold')
axes[1].set_xlabel('์ํญ')
axes[1].set_ylabel('์์ค')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('training_history.png', dpi=150, bbox_inches='tight')
plt.show()
๐ ์ผ๋ฐ์ ์ธ ์ฑ๋ฅ ๊ธฐ์ค
RAVDESS ๋ฐ์ดํฐ์
๊ธฐ์ค์ผ๋ก ํ์ฌ ์ฐ๊ตฌ๋ค์ ์ฑ๋ฅ ๋ฒ์์ผ:
๊ธฐ๋ณธ MLP ์ฝ 55~65% ์ ํ๋
CNN ์ฝ 65~75% ์ ํ๋
CNN+LSTM ์ฝ 70~80% ์ ํ๋
Transformer ๊ธฐ๋ฐ ์ฝ 80~90% ์ ํ๋
์ฐ๋ฆฌ๊ฐ ๋ง๋ CNN+LSTM ๋ชจ๋ธ๋ก 70~78% ์ ๋๋ฅผ ๋ชฉํ๋ก ํ๋ฉด ๋ผ.
๋ฐ์ดํฐ ์ฆ๊ฐ๊ณผ ํ์ดํผํ๋ผ๋ฏธํฐ ํ๋์ผ๋ก ๋ ์ฌ๋ฆด ์ ์์ด!
๐ค 7. ์ค์๊ฐ ์์ฑ ๊ฐ์ ๋ถ์ โ ๋ง์ดํฌ ์ ๋ ฅ ์ฒ๋ฆฌ
ํ์ต๋ ๋ชจ๋ธ๋ก ์ค์๊ฐ์ผ๋ก ๋ง์ดํฌ ์
๋ ฅ์ ๋ถ์ํ๋ ์ฝ๋์ผ.
์ด๊ฒ ์ง์ง ์ค์ฉ์ ์ธ ๋ถ๋ถ์ด์ง! ๐๏ธ
import pyaudio
import wave
import threading
import queue
import time
# =============================================
# 12. ์ค์๊ฐ ์์ฑ ๊ฐ์ ๋ถ์๊ธฐ
# =============================================
class RealTimeSER:
"""
์ค์๊ฐ ์์ฑ ๊ฐ์ ๋ถ์ ํด๋์ค
๋ง์ดํฌ ์
๋ ฅ์ ๋ฐ์ ์ค์๊ฐ์ผ๋ก ๊ฐ์ ์ ๋ถ๋ฅ
"""
# ๊ฐ์ ์ด๋ชจ์ง ๋งคํ
EMOTION_EMOJI = {
'neutral': '๐',
'calm': '๐',
'happy': '๐',
'sad': '๐ข',
'angry': '๐ ',
'fearful': '๐จ',
'disgust': '๐คข',
'surprised': '๐ฒ'
}
def __init__(self, model, scaler, label_encoder,
sr=22050, duration=3.0, chunk_size=1024):
self.model = model
self.scaler = scaler
self.le = label_encoder
self.sr = sr
self.duration = duration
self.chunk_size = chunk_size
self.is_running = False
self.audio_queue = queue.Queue()
def record_audio(self):
"""๋ง์ดํฌ์์ ์ค๋์ค ๋
น์ (๋ณ๋ ์ค๋ ๋)"""
p = pyaudio.PyAudio()
stream = p.open(
format=pyaudio.paFloat32,
channels=1,
rate=self.sr,
input=True,
frames_per_buffer=self.chunk_size
)
print("๐๏ธ ๋
น์ ์์! (Ctrl+C๋ก ์ข
๋ฃ)")
while self.is_running:
frames = []
n_frames = int(self.sr * self.duration / self.chunk_size)
for _ in range(n_frames):
if not self.is_running:
break
data = stream.read(self.chunk_size, exception_on_overflow=False)
frames.append(np.frombuffer(data, dtype=np.float32))
if frames:
audio_data = np.concatenate(frames)
self.audio_queue.put(audio_data)
stream.stop_stream()
stream.close()
p.terminate()
def analyze_emotion(self):
"""ํ์์ ์ค๋์ค๋ฅผ ๊บผ๋ด ๊ฐ์ ๋ถ์ (๋ณ๋ ์ค๋ ๋)"""
while self.is_running:
try:
audio_data = self.audio_queue.get(timeout=1.0)
# ํน์ง ์ถ์ถ
features = extract_features_from_array(audio_data, self.sr)
features_scaled = self.scaler.transform(features.reshape(1, -1))
# ๊ฐ์ ์์ธก
pred_prob = self.model.predict(features_scaled, verbose=0)[0]
pred_idx = np.argmax(pred_prob)
pred_emotion = self.le.classes_[pred_idx]
confidence = pred_prob[pred_idx]
# ๊ฒฐ๊ณผ ์ถ๋ ฅ
emoji = self.EMOTION_EMOJI.get(pred_emotion, 'โ')
print(f"\r{emoji} ๊ฐ์ : {pred_emotion:10s} | "
f"์ ๋ขฐ๋: {confidence:.2%} | "
f"{'โ' * int(confidence * 20)}", end='', flush=True)
# ์์ 3๊ฐ ๊ฐ์ ์ถ๋ ฅ
top3_idx = np.argsort(pred_prob)[-3:][::-1]
print(f"\n ์์ 3๊ฐ: ", end='')
for idx in top3_idx:
em = self.le.classes_[idx]
prob = pred_prob[idx]
print(f"{self.EMOTION_EMOJI.get(em,'')} {em}({prob:.1%}) ", end='')
print()
except queue.Empty:
continue
except Exception as e:
print(f"\n๋ถ์ ์ค๋ฅ: {e}")
def start(self):
"""์ค์๊ฐ ๋ถ์ ์์"""
self.is_running = True
# ๋
น์ ์ค๋ ๋
record_thread = threading.Thread(target=self.record_audio)
record_thread.daemon = True
record_thread.start()
# ๋ถ์ ์ค๋ ๋
analyze_thread = threading.Thread(target=self.analyze_emotion)
analyze_thread.daemon = True
analyze_thread.start()
try:
while True:
time.sleep(0.1)
except KeyboardInterrupt:
print("\n\n๋ถ์ ์ข
๋ฃ!")
self.is_running = False
def analyze_file(self, file_path):
"""๋จ์ผ ํ์ผ ๊ฐ์ ๋ถ์"""
features = extract_features(file_path)
if features is None:
return None
features_scaled = self.scaler.transform(features.reshape(1, -1))
pred_prob = self.model.predict(features_scaled, verbose=0)[0]
pred_idx = np.argmax(pred_prob)
pred_emotion = self.le.classes_[pred_idx]
confidence = pred_prob[pred_idx]
result = {
'emotion': pred_emotion,
'confidence': float(confidence),
'emoji': self.EMOTION_EMOJI.get(pred_emotion, 'โ'),
'all_probabilities': {
self.le.classes_[i]: float(pred_prob[i])
for i in range(len(self.le.classes_))
}
}
return result
# =============================================
# ์ฌ์ฉ ์์
# =============================================
# ๋ชจ๋ธ ๋ก๋ ํ ์ค์๊ฐ ๋ถ์ ์์
# model = keras.models.load_model('best_ser_model.h5')
# ser = RealTimeSER(model, scaler, le)
#
# ํ์ผ ๋ถ์
# result = ser.analyze_file('test_audio.wav')
# print(f"๊ฐ์ : {result['emoji']} {result['emotion']} ({result['confidence']:.1%})")
#
# ์ค์๊ฐ ๋ถ์
# ser.start()
pip install pyaudio๊ฐ ์ ์ ๋ ์ ์์ด. ์ด๋ด ๋๋ pip install pipwin ํ pipwin install pyaudio๋ฅผ ์จ๋ด. Mac์์๋ brew install portaudio ๋จผ์ ํ๊ณ pip ์ค์นํ๋ฉด ๋ผ!
๐ 8. ๊ณ ๊ธ ๋ชจ๋ธ โ Wav2Vec2 ๊ธฐ๋ฐ Transfer Learning
์ด์ ํ ๋จ๊ณ ๋ ๋์๊ฐ๋ณผ๊น? ๐
์์ฆ ์์ฑ ๊ฐ์ ๋ถ์์ ์ต๊ฐ์๋ Wav2Vec2๋ HuBERT ๊ฐ์
์ฌ์ ํ์ต๋ ์์ฑ ๋ชจ๋ธ์ ํ์ธํ๋ํ๋ ๋ฐฉ์์ด์ผ.
Hugging Face ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ๋ฉด ์๊ฐ๋ณด๋ค ์ฝ๊ฒ ๊ตฌํํ ์ ์์ด!
# pip install transformers datasets torch torchaudio
from transformers import (
Wav2Vec2Processor,
Wav2Vec2ForSequenceClassification,
TrainingArguments,
Trainer
)
import torch
import torchaudio
from datasets import Dataset, DatasetDict
import numpy as np
# =============================================
# 13. Wav2Vec2 ๊ธฐ๋ฐ ๊ฐ์ ๋ถ๋ฅ ๋ชจ๋ธ
# =============================================
# ๊ฐ์ ๋ ์ด๋ธ ์ ์
EMOTION_LABELS = ['neutral', 'calm', 'happy', 'sad',
'angry', 'fearful', 'disgust', 'surprised']
label2id = {label: i for i, label in enumerate(EMOTION_LABELS)}
id2label = {i: label for i, label in enumerate(EMOTION_LABELS)}
def load_wav2vec2_model(num_labels=8):
"""
์ฌ์ ํ์ต๋ Wav2Vec2 ๋ชจ๋ธ ๋ก๋ ๋ฐ ๋ถ๋ฅ ํค๋ ์ถ๊ฐ
"""
model_name = "facebook/wav2vec2-base" # ๋๋ "facebook/wav2vec2-large"
# ํ๋ก์ธ์ ๋ก๋ (์ค๋์ค ์ ์ฒ๋ฆฌ ๋ด๋น)
processor = Wav2Vec2Processor.from_pretrained(model_name)
# ๋ถ๋ฅ ๋ชจ๋ธ ๋ก๋
model = Wav2Vec2ForSequenceClassification.from_pretrained(
model_name,
num_labels=num_labels,
label2id=label2id,
id2label=id2label,
ignore_mismatched_sizes=True
)
# ํน์ง ์ถ์ถ๊ธฐ ๋ ์ด์ด ๋๊ฒฐ (ํ์ธํ๋ ์ ์ ํ์ )
# model.freeze_feature_encoder()
return model, processor
def preprocess_audio_for_wav2vec2(file_path, processor, sr=16000, max_duration=5.0):
"""
Wav2Vec2 ์
๋ ฅ ํ์์ผ๋ก ์ค๋์ค ์ ์ฒ๋ฆฌ
"""
# ์ค๋์ค ๋ก๋ (16kHz ํ์!)
audio, orig_sr = torchaudio.load(file_path)
# ๋ชจ๋
ธ ๋ณํ
if audio.shape[0] > 1:
audio = torch.mean(audio, dim=0, keepdim=True)
# ๋ฆฌ์ํ๋ง (Wav2Vec2๋ 16kHz ์
๋ ฅ ํ์)
if orig_sr != sr:
resampler = torchaudio.transforms.Resample(orig_sr, sr)
audio = resampler(audio)
audio = audio.squeeze().numpy()
# ๊ธธ์ด ์ ํ
max_length = int(sr * max_duration)
if len(audio) > max_length:
audio = audio[:max_length]
# ํ๋ก์ธ์๋ก ์ ๊ทํ
inputs = processor(
audio,
sampling_rate=sr,
return_tensors="pt",
padding=True,
max_length=max_length,
truncation=True
)
return inputs
# =============================================
# 14. Trainer API๋ฅผ ํ์ฉํ ํ์ธํ๋
# =============================================
def finetune_wav2vec2(train_dataset, eval_dataset, model, processor):
"""
Wav2Vec2 ํ์ธํ๋ ์ค์ ๋ฐ ํ์ต
"""
training_args = TrainingArguments(
output_dir="./wav2vec2-emotion",
num_train_epochs=10,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
gradient_accumulation_steps=2,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="accuracy",
learning_rate=3e-5,
warmup_ratio=0.1,
logging_dir="./logs",
logging_steps=10,
fp16=True, # GPU ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ (CUDA ํ์)
dataloader_num_workers=4,
report_to="none"
)
def compute_metrics(eval_pred):
"""ํ๊ฐ ์งํ ๊ณ์ฐ"""
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
accuracy = np.mean(predictions == labels)
return {"accuracy": accuracy}
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
compute_metrics=compute_metrics,
)
trainer.train()
# ์ต์ข
ํ๊ฐ
results = trainer.evaluate()
print(f"\nWav2Vec2 ํ์ธํ๋ ๊ฒฐ๊ณผ: {results}")
return trainer
๐ ๋ชจ๋ธ ์ํคํ ์ฒ ๋น๊ต
CNN+LSTM (์ฐ๋ฆฌ๊ฐ ๋ง๋ ๊ฒ)
โ ํ์ต ๋น ๋ฆ, ๊ฐ๋ฒผ์, ์ปค์คํฐ๋ง์ด์ง ์ฌ์, ์ ํ๋ 70~78%
Wav2Vec2 ํ์ธํ๋
โ ๋์ ์ ํ๋(85~92%), GPU ํ์, ํ์ต ๋๋ฆผ, ๋์ฉ๋ ๋ชจ๋ธ
HuBERT ํ์ธํ๋
โ ์ต๊ณ ์์ค ์ ํ๋(88~93%), ๊ฐ์ฅ ๋ฌด๊ฑฐ์, ์ฐ๊ตฌ์ฉ
์ค์ฉ์ ์ ํ ๊ธฐ์ค:
๋น ๋ฅธ ํ๋กํ ํ์
โ CNN+LSTM
ํ๋ก๋์
์๋น์ค โ Wav2Vec2
์ต๊ณ ์ฑ๋ฅ ์ฐ๊ตฌ โ HuBERT
๐ 9. Flask API ์๋ฒ๋ก ๋ฐฐํฌํ๊ธฐ
๋ชจ๋ธ์ ๋ง๋ค์์ผ๋ฉด ์ค์ ์๋น์ค๋ก ๋ฐฐํฌํด์ผ์ง! ๐
Flask๋ก REST API ์๋ฒ๋ฅผ ๋ง๋ค์ด์ ์ด๋์๋ ํธ์ถํ ์ ์๊ฒ ํด๋ณด์.
from flask import Flask, request, jsonify
import tensorflow as tf
from tensorflow import keras
import numpy as np
import librosa
import pickle
import io
import os
import tempfile
# =============================================
# 15. Flask REST API ์๋ฒ
# =============================================
app = Flask(__name__)
# ์ ์ญ ๋ณ์๋ก ๋ชจ๋ธ ๋ก๋ (์๋ฒ ์์ ์ 1ํ๋ง)
MODEL = None
SCALER = None
LABEL_ENCODER = None
def load_model_and_preprocessors():
"""์๋ฒ ์์ ์ ๋ชจ๋ธ ๋ฐ ์ ์ฒ๋ฆฌ๊ธฐ ๋ก๋"""
global MODEL, SCALER, LABEL_ENCODER
MODEL = keras.models.load_model('best_ser_model.h5')
with open('scaler.pkl', 'rb') as f:
SCALER = pickle.load(f)
with open('label_encoder.pkl', 'rb') as f:
LABEL_ENCODER = pickle.load(f)
print("โ
๋ชจ๋ธ ๋ก๋ ์๋ฃ!")
@app.route('/health', methods=['GET'])
def health_check():
"""์๋ฒ ์ํ ํ์ธ"""
return jsonify({
'status': 'healthy',
'model_loaded': MODEL is not None,
'version': '1.0.0'
})
@app.route('/analyze', methods=['POST'])
def analyze_emotion():
"""
์์ฑ ํ์ผ ๊ฐ์ ๋ถ์ API
Request: multipart/form-data
- file: ์ค๋์ค ํ์ผ (WAV, MP3 ๋ฑ)
Response: JSON
- emotion: ์์ธก๋ ๊ฐ์
- confidence: ์ ๋ขฐ๋
- all_emotions: ์ ์ฒด ๊ฐ์ ๋ณ ํ๋ฅ
"""
if 'file' not in request.files:
return jsonify({'error': 'ํ์ผ์ด ์์ต๋๋ค'}), 400
audio_file = request.files['file']
if audio_file.filename == '':
return jsonify({'error': 'ํ์ผ๋ช
์ด ๋น์ด์์ต๋๋ค'}), 400
# ํ์ฉ๋ ํ์ผ ํ์ ํ์ธ
allowed_extensions = {'.wav', '.mp3', '.flac', '.ogg', '.m4a'}
file_ext = os.path.splitext(audio_file.filename)[1].lower()
if file_ext not in allowed_extensions:
return jsonify({
'error': f'์ง์ํ์ง ์๋ ํ์ผ ํ์: {file_ext}'
}), 400
try:
# ์์ ํ์ผ๋ก ์ ์ฅ
with tempfile.NamedTemporaryFile(
suffix=file_ext, delete=False
) as tmp_file:
audio_file.save(tmp_file.name)
tmp_path = tmp_file.name
# ํน์ง ์ถ์ถ
features = extract_features(tmp_path)
# ์์ ํ์ผ ์ญ์
os.unlink(tmp_path)
if features is None:
return jsonify({'error': 'ํน์ง ์ถ์ถ ์คํจ'}), 500
# ์ค์ผ์ผ๋ง & ์์ธก
features_scaled = SCALER.transform(features.reshape(1, -1))
pred_prob = MODEL.predict(features_scaled, verbose=0)[0]
pred_idx = np.argmax(pred_prob)
pred_emotion = LABEL_ENCODER.classes_[pred_idx]
confidence = float(pred_prob[pred_idx])
# ์ ์ฒด ๊ฐ์ ํ๋ฅ ๋์
๋๋ฆฌ
all_emotions = {
LABEL_ENCODER.classes_[i]: float(pred_prob[i])
for i in range(len(LABEL_ENCODER.classes_))
}
# ์์ 3๊ฐ ๊ฐ์
top3_idx = np.argsort(pred_prob)[-3:][::-1]
top3 = [
{
'emotion': LABEL_ENCODER.classes_[i],
'probability': float(pred_prob[i])
}
for i in top3_idx
]
return jsonify({
'success': True,
'emotion': pred_emotion,
'confidence': confidence,
'top3_emotions': top3,
'all_emotions': all_emotions
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/batch_analyze', methods=['POST'])
def batch_analyze():
"""์ฌ๋ฌ ํ์ผ ์ผ๊ด ๋ถ์"""
files = request.files.getlist('files')
if not files:
return jsonify({'error': 'ํ์ผ์ด ์์ต๋๋ค'}), 400
results = []
for audio_file in files:
# ๊ฐ ํ์ผ ์ฒ๋ฆฌ (analyze_emotion ๋ก์ง ์ฌ์ฌ์ฉ)
# ... (์๋ต)
pass
return jsonify({'results': results})
if __name__ == '__main__':
load_model_and_preprocessors()
app.run(host='0.0.0.0', port=5000, debug=False)
API ํ ์คํธ๋ ์ด๋ ๊ฒ ํ๋ฉด ๋ผ:
# curl๋ก ํ
์คํธ
# curl -X POST http://localhost:5000/analyze \
# -F "file=@test_audio.wav"
# Python requests๋ก ํ
์คํธ
import requests
url = "http://localhost:5000/analyze"
with open("test_audio.wav", "rb") as f:
response = requests.post(url, files={"file": f})
result = response.json()
print(f"๊ฐ์ : {result['emotion']}")
print(f"์ ๋ขฐ๋: {result['confidence']:.2%}")
print(f"์์ 3๊ฐ: {result['top3_emotions']}")
๐ก 10. ์ฑ๋ฅ ํฅ์ ๊ฟํ & ์ค์ ๋ ธํ์ฐ
์ฝ๋๋ง ์๋ค๊ณ ๋์ด ์๋์ผ! ์ค์ ๋ก ์ข์ ์ฑ๋ฅ์ ๋ด๋ ค๋ฉด
์ด๋ฐ ๊ฒ๋ค์ ์ ๊ฒฝ ์จ์ผ ํด. ๋ด๊ฐ ์ง์ ๊ฒช์ด๋ณธ ๊ฒ๋ค ์์ฃผ๋ก ์ ๋ฆฌํ์ด. ๐ค
๋ชจ๋ ํน์ง์ด ๋ค ์ ์ฉํ ๊ฑด ์๋์ผ. MFCC 40๊ฐ + delta + delta-delta ์กฐํฉ์ด ๊ฐ์ฅ ์์ ์ ์ด๊ณ ,
Mel Spectrogram์ 2D ์ด๋ฏธ์ง๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์๋ CNN์์ ํจ๊ณผ์ ์ด์ผ.
๊ฐ์ ๋ฐ์ดํฐ๋ ์ค๋ฆฝ์ด ๋ง๊ณ ํ์ค/๊ณตํฌ๊ฐ ์ ์ ๊ฒฝ์ฐ๊ฐ ๋ง์.
class_weight ํ๋ผ๋ฏธํฐ๋ ์ค๋ฒ์ํ๋ง(SMOTE)์ ํ์ฉํด๋ด.
ํ์ ๋ ๋ฆฝ์ (Speaker-Independent) ํ๊ฐ๋ฅผ ์ํด
Leave-One-Speaker-Out(LOSO) ๊ต์ฐจ ๊ฒ์ฆ์ ์จ์ผ ์ง์ง ์ฑ๋ฅ์ ์ ์ ์์ด.
CNN+LSTM ๋ชจ๋ธ ์ฌ๋ฌ ๊ฐ๋ฅผ ํ์ต์์ผ์ ์์ธก์ ํ๊ท ๋ด๋ฉด ๋จ์ผ ๋ชจ๋ธ๋ณด๋ค 2~5% ์ฑ๋ฅ์ด ์ฌ๋ผ๊ฐ.
LSTM ์์ Self-Attention ๋ ์ด์ด๋ฅผ ์ถ๊ฐํ๋ฉด ์๊ฐ์ ์ผ๋ก ์ค์ํ ๋ถ๋ถ์ ์ง์คํ ์ ์์ด.
Transformer ์ํคํ ์ฒ์ ํต์ฌ ์์ด๋์ด์ผ.
๐ง 11. ์ ์ฒด ํ์ดํ๋ผ์ธ ํตํฉ ์ฝ๋
์ง๊ธ๊น์ง ๋ง๋ ๋ชจ๋ ์ฝ๋๋ฅผ ํ๋๋ก ํฉ์ณ์ ์คํํ ์ ์๋ ๋ฉ์ธ ์คํฌ๋ฆฝํธ์ผ.
์ด๊ฑธ main.py๋ก ์ ์ฅํ๊ณ ์คํํ๋ฉด ๋ผ!
# =============================================
# main.py โ ์์ฑ ๊ฐ์ ๋ถ์ ์ ์ฒด ํ์ดํ๋ผ์ธ
# =============================================
import os
import pickle
import numpy as np
import warnings
warnings.filterwarnings('ignore')
def main():
print("=" * 60)
print("๐๏ธ ์์ฑ ๊ฐ์ ๋ถ์ ์์คํ
(SER)")
print("=" * 60)
# ---- ์ค์ ----
DATA_PATH = './RAVDESS' # ๋ฐ์ดํฐ์
๊ฒฝ๋ก
MODEL_PATH = 'best_ser_model.h5' # ๋ชจ๋ธ ์ ์ฅ ๊ฒฝ๋ก
SCALER_PATH = 'scaler.pkl'
LE_PATH = 'label_encoder.pkl'
# ---- 1๋จ๊ณ: ๋ฐ์ดํฐ ๋ก๋ ----
print("\n[1/5] ๋ฐ์ดํฐ ๋ก๋ ์ค...")
file_paths, labels = load_ravdess_data(DATA_PATH)
# ---- 2๋จ๊ณ: ํน์ง ์ถ์ถ ----
print("\n[2/5] ํน์ง ์ถ์ถ ์ค...")
X, y = prepare_dataset(file_paths, labels)
# ---- 3๋จ๊ณ: ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ ----
print("\n[3/5] ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ ์ค...")
X_train, X_val, X_test, y_train, y_val, y_test, le, scaler = \
preprocess_data(X, y)
# ์ ์ฒ๋ฆฌ๊ธฐ ์ ์ฅ
with open(SCALER_PATH, 'wb') as f:
pickle.dump(scaler, f)
with open(LE_PATH, 'wb') as f:
pickle.dump(le, f)
print(f"์ ์ฒ๋ฆฌ๊ธฐ ์ ์ฅ ์๋ฃ: {SCALER_PATH}, {LE_PATH}")
# ---- 4๋จ๊ณ: ๋ชจ๋ธ ํ์ต ----
print("\n[4/5] ๋ชจ๋ธ ํ์ต ์ค...")
num_classes = len(le.classes_)
model, history = train_model(
X_train, X_val, y_train, y_val,
num_classes=num_classes,
epochs=100
)
# ํ์ต ๊ณก์ ์๊ฐํ
plot_training_history(history)
# ---- 5๋จ๊ณ: ๋ชจ๋ธ ํ๊ฐ ----
print("\n[5/5] ๋ชจ๋ธ ํ๊ฐ ์ค...")
accuracy, y_pred, y_pred_prob = evaluate_model(model, X_test, y_test, le)
print(f"\n{'='*60}")
print(f"โ
ํ์ต ์๋ฃ!")
print(f" ์ต์ข
ํ
์คํธ ์ ํ๋: {accuracy:.4f} ({accuracy*100:.2f}%)")
print(f" ๋ชจ๋ธ ์ ์ฅ ์์น: {MODEL_PATH}")
print(f"{'='*60}")
# ---- ๋จ์ผ ํ์ผ ํ
์คํธ ----
print("\n[ํ
์คํธ] ๋จ์ผ ํ์ผ ๊ฐ์ ๋ถ์:")
test_file = file_paths[0] # ์ฒซ ๋ฒ์งธ ํ์ผ๋ก ํ
์คํธ
ser = RealTimeSER(model, scaler, le)
result = ser.analyze_file(test_file)
if result:
print(f" ํ์ผ: {os.path.basename(test_file)}")
print(f" ์์ธก ๊ฐ์ : {result['emoji']} {result['emotion']}")
print(f" ์ ๋ขฐ๋: {result['confidence']:.2%}")
print(f" ์ ์ฒด ํ๋ฅ :")
for emotion, prob in sorted(
result['all_probabilities'].items(),
key=lambda x: x[1], reverse=True
):
bar = 'โ' * int(prob * 30)
print(f" {emotion:12s}: {bar} {prob:.2%}")
return model, scaler, le
if __name__ == '__main__':
model, scaler, le = main()
๐ฑ 12. ํ๊ตญ์ด ์์ฑ ๊ฐ์ ๋ถ์ โ ํน๋ณ ๊ณ ๋ ค์ฌํญ
์์ด ๋ฐ์ดํฐ์
์ผ๋ก ํ์ตํ ๋ชจ๋ธ์ ํ๊ตญ์ด์ ๋ฐ๋ก ์ฐ๋ฉด ์ฑ๋ฅ์ด ๋ ๋จ์ด์ ธ. ๐
ํ๊ตญ์ด ์์ฑ ๊ฐ์ ๋ถ์์ ์ํ ํน๋ณํ ๊ณ ๋ ค์ฌํญ๋ค์ ์ ๋ฆฌํด๋ดค์ด!
๐ฐ๐ท ํ๊ตญ์ด SER ํน์ด์ฌํญ
1. ํ๊ตญ์ด ์ ์ฉ ๋ฐ์ดํฐ์
ํ์ฉ
โ ETRI ๊ฐ์ ์์ฑ ๋ฐ์ดํฐ์
, KESDy, ํ๊ตญ์ด ๊ฐ์ ์์ฑ ์ฝํผ์ค ๋ฑ
โ AI Hub(aihub.or.kr)์์ ํ๊ตญ์ด ๊ฐ์ ์์ฑ ๋ฐ์ดํฐ ๋ฌด๋ฃ ๋ค์ด๋ก๋ ๊ฐ๋ฅ
2. ํ๊ตญ์ด ์์ฑ ํน์ฑ ๋ฐ์
โ ํ๊ตญ์ด๋ ์ฑ์กฐ ์ธ์ด๊ฐ ์๋์ง๋ง, ์ต์ ํจํด์ด ์์ด์ ๋ฌ๋ผ
โ ๊ฒฝ์ํ, ์ฐ์ ๋ฑ ํ๊ตญ์ด ํน์ ์ ์์ด ๋ณ๋ ๊ณ ๋ ค ํ์
3. ํ์ ๋ค์์ฑ
โ ์ง์ญ ๋ฐฉ์ธ(๊ฒฝ์๋, ์ ๋ผ๋ ๋ฑ)์ ๋ฐ๋ฅธ ์ต์ ์ฐจ์ด ์กด์ฌ
โ ์ฐ๋ น๋๋ณ ์์ฑ ํน์ฑ ์ฐจ์ด๋ ๊ณ ๋ คํด์ผ ํด
4. ํ๊ตญ์ด ์ฌ์ ํ์ต ๋ชจ๋ธ
โ kresnik/wav2vec2-large-xlsr-korean (Hugging Face)
โ NAVER์ ํ๊ตญ์ด ์์ฑ ๋ชจ๋ธ ํ์ฉ ๊ฐ๋ฅ
# ํ๊ตญ์ด Wav2Vec2 ๋ชจ๋ธ ํ์ฉ ์์
from transformers import Wav2Vec2Processor, Wav2Vec2ForSequenceClassification
# ํ๊ตญ์ด ์ฌ์ ํ์ต ๋ชจ๋ธ ๋ก๋
KO_MODEL_NAME = "kresnik/wav2vec2-large-xlsr-korean"
processor = Wav2Vec2Processor.from_pretrained(KO_MODEL_NAME)
model = Wav2Vec2ForSequenceClassification.from_pretrained(
KO_MODEL_NAME,
num_labels=7, # ํ๊ตญ์ด ๊ฐ์ ํด๋์ค ์
label2id={
'๊ธฐ์จ': 0, '์ฌํ': 1, '๋ถ๋
ธ': 2, '๊ณตํฌ': 3,
'ํ์ค': 4, '๋๋': 5, '์ค๋ฆฝ': 6
},
id2label={
0: '๊ธฐ์จ', 1: '์ฌํ', 2: '๋ถ๋
ธ', 3: '๊ณตํฌ',
4: 'ํ์ค', 5: '๋๋', 6: '์ค๋ฆฝ'
},
ignore_mismatched_sizes=True
)
# ์ดํ ํ์ธํ๋ ๊ณผ์ ์ ์์ด ๋ชจ๋ธ๊ณผ ๋์ผ!
๐ฏ 13. ํ๋ก์ ํธ ํ์ฅ ์์ด๋์ด
๊ธฐ๋ณธ SER ์์คํ
์ ๋ง๋ค์์ผ๋ฉด, ์ด๊ฑธ ์ด๋ป๊ฒ ๋ฐ์ ์ํฌ ์ ์์๊น? ๐ค
์ค์ ๋ก ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ์ฉํ ์ ์๋ ์์ด๋์ด๋ค์ด์ผ!
๐ผ ์ค์ฉ์ ์ธ ํ์ฅ ํ๋ก์ ํธ
๐ง ์ฝ์ผํฐ ๊ฐ์ ๋ชจ๋ํฐ๋ง ๋์๋ณด๋
โ ์ค์๊ฐ ํตํ ๊ฐ์ ๋ถ์ + ์๊ฐํ ๋์๋ณด๋
โ ๊ณ ๊ฐ ๋ถ๋ง ์๋ ๊ฐ์ง & ์๋ฆผ ์์คํ
๐ฎ ๊ฐ์ ๋ฐ์ํ ์์
ํ๋ ์ด์ด
โ ๋ชฉ์๋ฆฌ ๊ฐ์ ์ ๋ฐ๋ผ ์๋์ผ๋ก ์์
์ฅ๋ฅด ๋ณ๊ฒฝ
โ ๊ธฐ์ ๋ ์ ๋๋ ์์
, ์ฌํ ๋ ์๋ก ์์
๐ ์ธ์ด ํ์ต ์ฑ ๋ฐ์ ์ฝ์น
โ ํ์ต์์ ๋ฐ์ ๊ฐ์ ํค ๋ถ์
โ ์์ฐ์ค๋ฌ์ด ์ต์ ํผ๋๋ฐฑ ์ ๊ณต
๐ฅ ์ ์ ๊ฑด๊ฐ ๋ชจ๋ํฐ๋ง ์ฑ
โ ์ผ์ ๋ํ์์ ์ฐ์ธ/์คํธ๋ ์ค ํจํด ๊ฐ์ง
โ ์ ๋ฌธ๊ฐ ์ฐ๊ฒฐ ์ถ์ฒ ์์คํ
๐ค ํ์ํ์ ๊ฐ์ ๋ถ์ ํ๋ฌ๊ทธ์ธ
โ Zoom/Teams ํ๋ฌ๊ทธ์ธ์ผ๋ก ํ์ ์ฐธ์ฌ์ ๊ฐ์ ๋ถ์
โ ํ ๋ถ์๊ธฐ ๋ฆฌํฌํธ ์๋ ์์ฑ
์ด๋ฐ ํ๋ก์ ํธ๋ค์ ํผ์ ๋ค ๋ง๋ค๊ธฐ ์ด๋ ต๋ค๋ฉด,
์ฌ๋ฅ๋ท์์ ํ๋ก ํธ์๋, ๋ฐฑ์๋, ๋ฐ์ดํฐ ์ฌ์ด์ธ์ค ์ ๋ฌธ๊ฐ๋ค๊ณผ ํ์
ํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ.
๊ฐ์์ ์ ๋ฌธ ์์ญ์ ๋๋ ์ ๋ ๋น ๋ฅด๊ณ ์์ฑ๋ ๋์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค ์ ์๊ฑฐ๋ ! ๐
๐ 14. ์ ์ฒด ์ฝ๋ ๊ตฌ์กฐ ์์ฝ
| ํ์ผ๋ช | ์ญํ | ์ฃผ์ ํจ์ |
|---|---|---|
data_loader.py |
๋ฐ์ดํฐ์ ๋ก๋ & ๋ ์ด๋ธ ํ์ฑ | load_ravdess_data() |
feature_extractor.py |
MFCC, Mel-Spec ๋ฑ ํน์ง ์ถ์ถ | extract_features() |
augmentation.py |
์์ฑ ๋ฐ์ดํฐ ์ฆ๊ฐ | augment_audio() |
model.py |
CNN+LSTM ๋ชจ๋ธ ์ ์ | build_cnn_lstm_model() |
train.py |
ํ์ต ํ์ดํ๋ผ์ธ ํตํฉ | train_model(), evaluate_model() |
realtime_ser.py |
์ค์๊ฐ ๋ง์ดํฌ ๋ถ์ | RealTimeSER ํด๋์ค |
app.py |
Flask REST API ์๋ฒ | analyze_emotion() ์๋ํฌ์ธํธ |
main.py |
์ ์ฒด ํ์ดํ๋ผ์ธ ์คํ | main() |
๐ 15. ์์ฃผ ๋ฐ์ํ๋ ์ค๋ฅ & ํด๊ฒฐ๋ฒ
์ค์ ๋ก ์ฝ๋ ์ง๋ค ๋ณด๋ฉด ์ด๋ฐ ์ค๋ฅ๋ค์ ์์ฃผ ๋ง๋๊ฒ ๋ผ. ๋ฏธ๋ฆฌ ์์๋์! ๐ ๏ธ
NoBackendError: No suitable audio I/O backend foundโ ํด๊ฒฐ:
pip install soundfile audioread ์ค์น. MP3 ํ์ผ์ ffmpeg ํ์: conda install -c conda-forge ffmpeg
โ ํด๊ฒฐ: batch_size๋ฅผ 32โ16โ8๋ก ์ค์ด๊ฑฐ๋,
tf.config.experimental.set_memory_growth(gpu, True) ์ถ๊ฐ
โ ํด๊ฒฐ: ํน์ง ์ ๊ทํ ํ์ธ (StandardScaler ์ ์ฉ ์ฌ๋ถ), ํ์ต๋ฅ ์กฐ์ (0.001โ0.0001), ์ํญ ์ ์ฆ๊ฐ, ๋ฐ์ดํฐ ์ฆ๊ฐ ์ ์ฉ
โ ํด๊ฒฐ: ๋ถ์ ์๋์ฐ ํฌ๊ธฐ ์ค์ด๊ธฐ (3์ดโ1.5์ด), ๋ชจ๋ธ ๊ฒฝ๋ํ (Quantization), TensorFlow Lite ๋ณํ ๊ณ ๋ ค
np.isnan(features).any()๋ก NaN ๊ฐ ์ฒดํฌ ๊ผญ ํด๋ด. ํผ์น ์ถ์ถ ์ ๋ฌด์ ๊ตฌ๊ฐ์์ NaN์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ ๋ชจ๋ธ ํ์ต์ ๋ง์น๋ ์ฃผ๋ฒ์ด ๋๊ฑฐ๋ !
๐ ๋ง๋ฌด๋ฆฌ โ ์์ฑ ๊ฐ์ ๋ถ์์ ๋ฏธ๋
์, ์ค๋ ์ ๋ง ๋ง์ ๊ฑธ ๋ค๋ค์ด! ๐
๋ฐ์ดํฐ ๋ก๋ฉ๋ถํฐ ํน์ง ์ถ์ถ, CNN+LSTM ๋ชจ๋ธ ๊ตฌ์ถ, ๋ฐ์ดํฐ ์ฆ๊ฐ, Flask API ๋ฐฐํฌ๊น์ง
์์ฑ ๊ฐ์ ๋ถ์์ ์ ์ฒด ํ์ดํ๋ผ์ธ์ ์์ฑํ์ด.
์์ฑ ๊ฐ์ ๋ถ์ ๊ธฐ์ ์ ์ง๊ธ๋ ๋น ๋ฅด๊ฒ ๋ฐ์ ํ๊ณ ์์ด.
GPT-4o๊ฐ ์ค์๊ฐ ์์ฑ ๊ฐ์ ์ธ์์ ์ ๋ณด์ด๋ฉด์ ์ด ๋ถ์ผ๊ฐ ๋ ์ฃผ๋ชฉ๋ฐ๊ณ ์๊ณ ,
๋ฉํฐ๋ชจ๋ฌ ๊ฐ์ ์ธ์(์์ฑ + ํ์ + ํ
์คํธ ๋์ ๋ถ์)์ด ์ฐจ์ธ๋ ํธ๋ ๋์ผ.
์ค๋ ๋ง๋ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ ๋ฐ์ ์์ผ๋ด. ์ฒ์์ 70% ์ ํ๋์์ ์์ํด๋,
๋ฐ์ดํฐ๋ฅผ ๋๋ฆฌ๊ณ ๋ชจ๋ธ์ ๊ฐ์ ํ๋ค ๋ณด๋ฉด ์ด๋์ 90%์ ๋๋ฌํ ์ ์์ ๊ฑฐ์ผ! ๐ช
๐ ๋ ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด
๋
ผ๋ฌธ: "Speech Emotion Recognition Using Deep Learning" (IEEE ๊ฒ์)
๋ฐ์ดํฐ์
: RAVDESS, IEMOCAP, CREMA-D, MSP-IMPROV, AI Hub ํ๊ตญ์ด ๊ฐ์ ์์ฑ
๋ชจ๋ธ: Wav2Vec2, HuBERT, Whisper (OpenAI), SpeechBrain ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๋ฒค์น๋งํฌ: SUPERB (Speech processing Universal PERformance Benchmark)
์ปค๋ฎค๋ํฐ: Papers With Code - Speech Emotion Recognition ์น์
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.