HomeAbout Me

피아노 음악 채보 - 2.모델 구현

By kuper0201
Published in 인공 지능
2023-09-12
2 min read
피아노 음악 채보 - 2.모델 구현

Table Of Contents

01
바로가기
02
서론
03
모델 아키텍처 선택
04
모델 구성
05
모델 학습
06
마무리

바로가기

       피아노 음악 채보 - 1.전처리

       피아노 음악 채보 - 2.모델 구현(현재 글)

       피아노 음악 채보 - 3.후처리 및 예측


서론

이전 글에서는 피아노 채보 모델 학습을 위한 데이터셋을 확보하고 해당 데이터셋의 전처리를 수행하였습니다.

해당 글에서는 전처리된 데이터를 공급하여 피아노 음악을 채보 할 수 있는 모델을 구성하고 학습을 시켜보겠습니다.


모델 아키텍처 선택

머신 러닝에는 Transformer, RNN, DNN, CNN 등 무수히 많은 이키텍처들이 존재합니다.

가장 먼저 고려한 아키텍처는 CNN이었습니다. 스펙트로그램은 시간 축과 주파수 축을 가진 2차원 데이터이므로 같은 2차원 데이터인 이미지를 효과적으로 처리 할 수 있는 아키텍처인 CNN을 이용하는 것이 적합해 보였습니다.

하지만 CNN은 이미지에 대한 특징을 추출하므로 음이 존재하는지는 감지 할 수 있지만, 언제 연주되는지는 추출하지 못한다는 단점이 존재합니다. 따라서 시간 또한 고려할 수 있는 아키텍처가 필요했습니다.

RNN 아키텍처는 시계열 데이터의 패턴을 추출하는데 강한 특성을 보입니다. 상기하였듯 시간과 주파수의 특징을 동시에 고려해야 하므로 해당 프로젝트에서는 CNN 아키텍처보다는 RNN 아키텍처를 이용하는 것이 유리하다고 판단했습니다.

하지만 RNN 아키텍처에도 문제점이 존재합니다. "기울기 소실", "기울기 폭발" 문제가 존재하여 장기 의존성을 효과적으로 학습하기 어렵다는 것 입니다. 쉽게 말해, 시퀀스(입력 데이터)의 길이가 길어질수록 학습해야 할 기울기가 희석되어 학습이 어려워지거나 불안정해진다는 것 입니다.

이러한 문제를 개선하기 위해 제안된 것이 LSTM(Long Short Term Memory)입니다. LSTM은 RNN의 변형으로, 상기한 RNN의 문제점들을 완화하여 장기 의존성을 효과적으로 학습 할 수 있습니다.

따라서 최종적으로 해당 프로젝트에서는 LSTM(Long Short Term Memory)을 이용한 방식으로 모델을 구축하기로 계획하였습니다.


모델 구성

모델의 아키텍처를 선택하였으므로 텐서플로우를 이용하여 실제 모델을 구성해 보겠습니다.

모델에서 음의 Onset(시작점)과 Length(길이)를 모두 예측할 수 있기를 원하기 때문에 두 개의 모델을 따로 학습하였습니다. Onset 모델은 음의 시작점을 예측하는 모델이고, Length 모델은 음의 길이를 예측하는 모델입니다. 두 모델의 구조는 동일하며 입력으로 Onset 데이터를 입력으로 받는지 Length 데이터를 입력으로 받는지에 대한 차이만이 존재합니다.

Onset 모델과 Length 모델을 각각 구현하였으므로, 추후 각 모델의 출력을 하나의 MIDI 파일로 병합하는 후처리 작업이 필요할 것입니다. 해당 내용은 다음 글에서 작성하도록 하겠습니다.

피아노 연주는 한 번에 여러 음이 연주 될 수 있는 다성 음악(Polyphonic)이므로 한 타임스텝에 대하여 여러 라벨을 예측해야 합니다. 다시 말해 Softmax를 이용하는 일반적인 다중 분류 모델로는 해당 모델을 구현할 수 없고 Sigmoid를 이용하여 각 라벨 별 이진 분류를 수행하는 다중 라벨 분류 모델을 구축해야 한다는 의미입니다.

따라서 이진 분류 모델을 구성하는 것과 같이 loss 함수는 'binary_crossentropy'를 이용하였고, 출력 레이어의 활성 함수로는 sigmoid를 이용하였습니다.

모델 구성의 다른 세부사항은 아래 코드에 주석으로 설명해 두었습니다.

코드 보기(Python)
import glob
import numpy as np
from keras import Model
from tensorflow import keras
from keras.layers import Dense, Input, Activation, Bidirectional, LSTM, TimeDistributed
from keras.callbacks import ModelCheckpoint, EarlyStopping
import tensorflow.python.keras.optimizers

import warnings
warnings.filterwarnings("ignore")

import tensorflow.python.keras.mixed_precision.policy as mixed_precision
policy = mixed_precision.Policy('mixed_float16')
mixed_precision.set_global_policy(policy)

train_x, train_onset = [], []
valid_x, valid_onset = [], []

model = None

# 데이터셋 제너레이터 함수
def dataSetGenerator(x_path, onset_path):
    for a, b in zip(x_path, onset_path):
        X, ONSET = [], []
        X.append(np.load(a))
        ONSET.append(np.load(b))

        X = np.concatenate(X, axis=0)
        ONSET = np.concatenate(ONSET, axis=0)

        X = X / np.max(X)
        
        # 배치 간의 연결 끊기 위한 함수
        model.reset_states()
        
        for x, onset in zip(X, ONSET):
            yield (x, onset)

# 모델 생성 함수
def buildModel():
    # 입력 레이어
    input_layer = Input(batch_input_shape=(10, 100, 264), name='onset_input')

    # LSTM 레이어
    onset_lstm = Bidirectional(LSTM(128, activation='tanh', return_sequences=True, stateful=True, name='onset_lstm'))(input_layer)

    # 출력 레이어 - sigmoid 사용
    onset_out = TimeDistributed(Dense(88, activation='sigmoid', kernel_initializer='he_normal', name='onset_output'))(onset_lstm)

    model = keras.Model(inputs=input_layer, outputs=onset_out)
    return model

# 학습하기 위한 함수
def train(trainX, trainOnset, validX, validOnset):
    for x_name, onset_name in zip(glob.glob(trainX), glob.glob(trainOnset)):
        train_x.append(x_name)
        train_onset.append(onset_name)

    for x_name, onset_name in zip(glob.glob(validX), glob.glob(validOnset)):
        valid_x.append(x_name)
        valid_onset.append(onset_name)

    input_signature = (tensorflow.float32, tensorflow.int8)
    in_out_shape = ([100, 264], [100, 88])

    global model
    model = buildModel()
    trainSet = tensorflow.data.Dataset.from_generator(dataSetGenerator, input_signature, in_out_shape, args=[train_x, train_onset])
    validSet = tensorflow.data.Dataset.from_generator(dataSetGenerator, input_signature, in_out_shape, args=[valid_x, valid_onset])
    
    # 배치 크기를 10으로, 남는 배치는 버림
    trainSet = trainSet.batch(10, drop_remainder=True).prefetch(tensorflow.data.experimental.AUTOTUNE)
    validSet = validSet.batch(10, drop_remainder=True).prefetch(tensorflow.data.experimental.AUTOTUNE)
    
    # 체크포인트, 조기 종료 콜백 설정
    checkpoint = ModelCheckpoint('onset_detector.h5', monitor='val_loss', verbose=1, save_best_only=True, mode='auto')
    early_stop = EarlyStopping(patience=5, monitor='val_loss', verbose=1, mode='auto')
    
    # loss 함수로 binary_crossentropy 사용
    opt = tensorflow.optimizers.Adam(learning_rate=0.0005)
    model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
    
    # 순차적인 패턴 학습해야 하므로, shuffle을 하지 않음
    model.fit(trainSet, validation_data=validSet, epochs=1000, shuffle=False, callbacks=[checkpoint, early_stop])

if __name__ == '__main__':
    train('../PreProc/trainX/*.npy', '../PreProc/trainONSET/*.npy', '../PreProc/validX/*.npy', '../PreProc/validONSET/*.npy')

모델 학습

모델의 구성을 완료한 이후, GTX1080 8GB GPU를 이용하여 모델을 학습시켰습니다.

Onset 모델과 Length 모델을 모두 학습하는데 약 이틀의 시간이 소요되었습니다.


마무리

모델의 구성과 학습을 진행하였습니다.

다음으로는 각 모델의 출력을 이용해 MIDI 파일 생성하는 후처리 과정과 오디오 데이터의 실제 예측에 대한 글을 작성하겠습니다.


Tags

#AI#projects
Previous Article
피아노 음악 채보 - 1.전처리
kuper0201

kuper0201

안녕하세요!

Related Posts

피아노 음악 채보 - 3.후처리 및 예측
피아노 음악 채보 - 3.후처리 및 예측
2023-09-13
1 min

Quick Links

HomeAbout Me

Social Media