텍스트와 음성 데이터를 이용한 한국어 감정 분류 모델 (2)

반디·2023년 1월 20일
0

NLP

목록 보기
2/2
  • 데이터: AI-Hub 감정 분류를 위한 대화 음성 데이터셋

지난 포스팅에서는
1. 데이터를 바탕으로 각 발화문에 새로운 감정 label 달기
2. 데이터 분포 확인
3. 학습에 이용할 1000개의 데이터 추출하기
위 세가지 작업을 수행했습니다.

이번 포스팅에서는 텍스트, 음성 데이터로부터 feature vector를 추출하는 작업을 해보겠습니다.

음성 데이터 feature vector 추출

이 부분은 참고문헌 [1]의 kaggle notebook을 참고하였습니다.

다음 함수들을 이용하여 data augmentation을 수행할 수 있습니다.

def noise(data):
    noise_amp = 0.035*np.random.uniform()*np.amax(data)
    data = data + noise_amp*np.random.normal(size=data.shape[0])
    return data

def stretch(data, rate=0.8):
    return librosa.effects.time_stretch(data, rate)

def shift(data):
    shift_range = int(np.random.uniform(low=-5, high = 5)*1000)
    return np.roll(data, shift_range)

def pitch(data, sampling_rate, pitch_factor=0.7):
    return librosa.effects.pitch_shift(data, sampling_rate, pitch_factor)

다음 함수는 음성 데이터로부터 feature vector를 만드는 함수입니다. 크게 다섯가지의 spectral 특성을 가지고 feature vector를 만듭니다.
(각 특성들이 어떤 의미를 가지는지는 추후 포스팅에서 살펴보겠습니다!)

def extract_features(data, sample_rate):
    # ZCR
    result = np.array([])
    zcr = np.mean(librosa.feature.zero_crossing_rate(y=data).T, axis=0)
    result=np.hstack((result, zcr)) # stacking horizontally

    # Chroma_stft
    stft = np.abs(librosa.stft(data))
    chroma_stft = np.mean(librosa.feature.chroma_stft(S=stft, sr=sample_rate).T, axis=0)
    result = np.hstack((result, chroma_stft)) # stacking horizontally

    # MFCC
    mfcc = np.mean(librosa.feature.mfcc(y=data, sr=sample_rate).T, axis=0)
    result = np.hstack((result, mfcc)) # stacking horizontally

    # Root Mean Square Value
    rms = np.mean(librosa.feature.rms(y=data).T, axis=0)
    result = np.hstack((result, rms)) # stacking horizontally

    # MelSpectogram
    mel = np.mean(librosa.feature.melspectrogram(y=data, sr=sample_rate).T, axis=0)
    result = np.hstack((result, mel)) # stacking horizontally
    
    return result

다음은 data augmentation을 수행하는 함수들과 extract_feature 함수를 이용하여 각 음성 데이터의 feature vector를 만드는 함수입니다.

def get_features(path):
    
    data, sample_rate = librosa.load(path, duration=2.5, offset=0.0)
    
    # without augmentation
    res1 = extract_features(data, sample_rate)
    result = np.array(res1)

    # data with noise
    noise_data = noise(data)
    res2 = extract_features(noise_data, sample_rate)
    result = np.concatenate((result, res2), axis = 0) 

    # data with stretching and pitching
    new_data = stretch(data)
    data_stretch_pitch = pitch(new_data, sample_rate)
    res3 = extract_features(data_stretch_pitch, sample_rate)
    result = np.concatenate((result, res3), axis = 0) 

    return result

이제, 위 함수들을 이용하여 음성 데이터 파일로부터 feature vector를 만들어보겠습니다.

X_audio, Y = [], []
for path, label in zip(wav_df['wav_id'], wav_df['final_label']):
    audio_features = get_features(audio_path+'/'+path+'.wav')
    X_audio.append(audio_features)
    Y.append(label)
    
audio_features = pd.DataFrame(X_audio)
final_df = pd.concat([audio_features, wav_df[['wav_id', 'final_label', 'sentence']]], axis = 1)
final_df.head(3)

final_df는 음성 데이터로 만든 feature vector와 wav_id, final_label, 발화문(sentence)를 포함하는 데이터 프레임입니다.

텍스트 embedding vector

우선, embedding vector 생성 및 이후 모델 훈련에 필요한 라이브러리들을 import 하겠습니다.

from sentence_transformers import SentenceTransformer

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler, OneHotEncoder
import keras
from keras.callbacks import ReduceLROnPlateau
from keras.models import Sequential
from keras.layers import Dense, Conv1D, MaxPooling1D, Flatten, Dropout, BatchNormalization
from keras.utils import np_utils, to_categorical
from keras.callbacks import ModelCheckpoint

여러 pre-trained 모델을 이용하여 텍스트 embedding vector를 만들어보려고 합니다.
이를 위해서 다음 class를 생성하겠습니다.

class text_embedding():
  def __init__(self, model_name):
    self.model_name = model_name

  def fit(self, X, y=None):
        return self
  
  def transform(self, X):
        embedding_model = SentenceTransformer(self.model_name)
        embedding_vec = embedding_model.encode(X['sentence'])
        X_val = np.concatenate((X.drop(['final_label', 'wav_id', 'sentence'], axis = 1), embedding_vec), axis = 1)
        return X_val

모델링

아래 모델은 [1]의 kaggle notebook을 참고하였습니다.

def custom_model(x_train):
  model=Sequential()
  model.add(Conv1D(256, kernel_size=5, strides=1, padding='same', activation='relu', input_shape=(x_train.shape[1], 1)))
  model.add(MaxPooling1D(pool_size=5, strides = 2, padding = 'same'))

  model.add(Conv1D(256, kernel_size=5, strides=1, padding='same', activation='relu'))
  model.add(MaxPooling1D(pool_size=5, strides = 2, padding = 'same'))

  model.add(Conv1D(128, kernel_size=5, strides=1, padding='same', activation='relu'))
  model.add(MaxPooling1D(pool_size=5, strides = 2, padding = 'same'))
  model.add(Dropout(0.2))

  model.add(Conv1D(64, kernel_size=5, strides=1, padding='same', activation='relu'))
  model.add(MaxPooling1D(pool_size=5, strides = 2, padding = 'same'))

  model.add(Flatten())
  model.add(Dense(units=32, activation='relu'))
  model.add(Dropout(0.3))

  model.add(Dense(units=6, activation='softmax'))
  model.compile(optimizer = 'adam' , loss = 'categorical_crossentropy' , metrics = ['accuracy'])

  #model.summary()
  return model
rlrp = ReduceLROnPlateau(monitor='loss', factor=0.4, verbose=0, patience=2, min_lr=0.0000001) #learning rate 조절 

훈련 및 평가

pre_trained_models에 있는 여러 사전 훈련 모델을 이용하여 text embedding vector를 만들고 이를 미리 만들어둔 음성 데이터 feature vector와 합쳐서 훈련을 시켜보겠습니다.

scaler = StandardScaler()
encoder = OneHotEncoder()

pre_trained_models = ['sentence-transformers/xlm-r-100langs-bert-base-nli-stsb-mean-tokens',
'sentence-transformers/multi-qa-distilbert-cos-v1',
'jhgan/ko-sroberta-multitask',
'all-distilroberta-v1',
'jhgan/ko-sbert-multitask',
'all-MiniLM-L12-v2', 'jhgan/ko-sroberta-sts']


Y = final_df['final_label'].values
Y = encoder.fit_transform(np.array(Y).reshape(-1,1)).toarray()

for i in pre_trained_models:
  txt_embed = text_embedding(model_name = i)
  X = txt_embed.transform(final_df)

  x_train, x_test, y_train, y_test = train_test_split(X, Y, random_state=0, shuffle=True)
  x_train = scaler.fit_transform(x_train)
  x_test = scaler.transform(x_test)

  x_train = np.expand_dims(x_train, axis=2)
  x_test = np.expand_dims(x_test, axis=2)
  x_train.shape, y_train.shape, x_test.shape, y_test.shape

  model = custom_model(x_train)
  history=model.fit(x_train, y_train, batch_size=64, epochs=50, validation_data=(x_test, y_test), callbacks=[rlrp])

  test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
  print("Pre-trained Model: ", i)
  print("Test Accuracy: ",test_acc)

테스트 결과는 다음과 같습니다.

  • Pre-trained Model: sentence-transformers/xlm-r-100langs-bert-base-nli-stsb-mean-tokens
    • Test Accuracy: 0.7319999933242798
  • Pre-trained Model: sentence-transformers/multi-qa-distilbert-cos-v1
    • Test Accuracy: 0.6320000290870667
  • Pre-trained Model: 'jhgan/ko-sroberta-multitask'
    • Test Accuracy: 0.7239999771118164
  • Pre-trained Model: all-distilroberta-v1
    • Test Accuracy: 0.6639999747276306
  • Pre-trained Model: jhgan/ko-sbert-multitask
    • Test Accuracy: 0.7680000066757202
  • Pre-trained Model: all-MiniLM-L12-v2
    • Test Accuracy: 0.6359999775886536
  • Pre-trained Model: jhgan/ko-sroberta-sts
    • Test Accuracy: 0.7599999904632568

확실히 한국어 사전 학습 모델에 대해서 성능이 좋게 나왔습니다.
이제 음성 데이터의 특성을 더 자세히 살펴보고 모델을 바꿔가면서 테스트를 해보겠습니다 :-)

참고문헌
[1] https://www.kaggle.com/code/shivamburnwal/speech-emotion-recognition
[2] https://librosa.org/doc/main/feature.html
[3] https://pytorch.org/docs/stable/generated/torch.optim.lr_scheduler.ReduceLROnPlateau.html

profile
꾸준히!

1개의 댓글

comment-user-thumbnail
2023년 5월 3일

안녕하세요. 모델을 따라 해보고 있는데 TypeError: time_stretch() takes 1 positional argument but 2 were given 이렇게 오류가 뜨는데 해결방법이 무엇인지 알 수 있을까요?

답글 달기