[keras]퍼셉트론을 활용해서 악보 만들기

2018. 6. 10. 23:37Python-이론/python-인공지능2

[keras]퍼셉트론을 사용하여 악보예측해보기


이번에는 퍼셉트론 딥러닝을 이용해서 악보를 훈련한 뒤 예측해보곘다. 


나비야 악보

c(도), d(레), e(미), f(파), g(솔), a(라), b(시)

4(4분음표), 8(8분음표)


저는 이 음표들을 이런식으로 훈련 시킬 것 입니다. note8 노트로 적었는데 글씨를 이쁘게 못적었네요 ㅠㅠ


4개의 음표들이 들어간 후 그때의 결과 그 때의 라벨값에 맞추어 훈련한다. 그리고 각 층은 Relu. softmax 같은 활성화 함수를 사용하고 12개의 결과 중 가장 높은 확률의 값의 idx를 반환한다. 


에를 들어) 0, 1, 2, 3의 악보가 들어갔으면 4번째 음표의 값을 훈련 후 1, 2, 3, 4의 값이 들어간 후 5번째 값을 훈련한다, 이런식의 훈련이 끝나고 난뒤 예측할 때 0, 1, 2, 3의 음표가 들어갔을 때 다음 음표는 무엇이 나오는지 예측하는 모델이다. 


필자는 다음 음표를 예측하는 방법 2가지를 사용하여 구할 것인데. 이번에 할 것은 퍼셉트론을 이용한 것이다. 





import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout, MaxPooling2D, Flatten
from keras.utils import np_utils
import keras
import matplotlib.pyplot as plt

code2idx = {'c4':0, 'd4':1, 'e4':2, 'f4':3, 'g4':4, 'a4':5, 'b4':6,
            'c8':7, 'd8':8, 'e8':9, 'f8':10, 'g8':11, 'a8':12, 'b8':13}

idx2code = {0:'c4', 1:'d4', 2:'e4', 3:'f4', 4:'g4', 5:'a4', 6:'b4',
            7:'c8', 8:'d8', 9:'e8', 10:'f8', 11:'g8', 12:'a8', 13:'b8'}

# 시퀀스 데이터 정의

seq = ['g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'd8', 'e8', 'f8', 'g8', 'g8', 'g4',
       'g8', 'e8', 'e8', 'e8', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4',
       'd8', 'd8', 'd8', 'd8', 'd8', 'e8', 'f4', 'e8', 'e8', 'e8', 'e8', 'e8', 'f8', 'g4',
       'g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4']



class losses_callback(keras.callbacks.Callback):
    def init(self):
        self.losses = []

    def on_batch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))

def dataset(seq, window):
    data = []
    for i in range(len(seq)-window):
        subset = seq[i:i+window+1]
        data.append([code2idx[i] for i in subset])
    return np.array(data)

trainData = dataset(seq, 4)
print(trainData)
X_data = trainData[:, :4]/float(13)
Y_data = trainData[:, 4]
Y_data = np_utils.to_categorical(Y_data)

model = Sequential()
model.add(Dense(128, input_dim=4, activation='relu'))
model.add(Dense(128, activation='relu'))
model.add(Dense(12, activation='softmax'))

callBack = losses_callback()
callBack.init()

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

model.fit(X_data, Y_data, batch_size=10, epochs=2000, verbose=2, callbacks=[callBack])

plt.plot(callBack.losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

score = model.evaluate(X_data, Y_data)
print(score)

seq_in = ['g8', 'e8', 'e4', 'f8']
seq_out = seq_in
seq_in = [code2idx[i]/13 for i in seq_in]

for i in range(50):
    sample_in = np.array(seq_in)
    sample_in = np.reshape(sample_in, (1, 4))
    predict_val = model.predict(sample_in)
    idx = np.argmax(predict_val)
    seq_out.append(idx2code[idx])
    seq_in.append(idx/float(13))
    seq_in.pop(0)

print(seq_out) 


코드 설명 


code2idx = {'c4':0, 'd4':1, 'e4':2, 'f4':3, 'g4':4, 'a4':5, 'b4':6, 'c8':7, 'd8':8, 'e8':9, 'f8':10, 'g8':11, 'a8':12, 'b8':13} idx2code = {0:'c4', 1:'d4', 2:'e4', 3:'f4', 4:'g4', 5:'a4', 6:'b4', 7:'c8', 8:'d8', 9:'e8', 10:'f8', 11:'g8', 12:'a8', 13:'b8'}

도, 레, 미, 파, 솔, 라, 시를 8박자 4박자로 나누어 data를 만든 것이다.


def dataset(seq, window):
    data = []
    for i in range(len(seq)-window):
        subset = seq[i:i+window+1]
        data.append([code2idx[i] for i in subset])
    return np.array(data)

데이터를 윈도우 개수 + 라벨 만큼 짤라서 러닝할 때 사용할 데이터를 만들었다. 


X_data = trainData[:, :4]/float(13)
Y_data = trainData[:, 4]
Y_data = np_utils.to_categorical(Y_data)

0~3개 까지의 인덱스 값을 13으로 모두 나누어 준다. 13인 이유는 도레미~시 까지는 14개지만 계이름 중에 악보에 사용하지 않는 계이름이 있어서 그렇다.


model = Sequential()
model.add(Dense(128, input_dim=4, activation='relu'))
model.add(Dense(128, activation='relu'))
model.add(Dense(12, activation='softmax'))

위의 사진과 같이 딥러닝 층을 만들었다.


class losses_callback(keras.callbacks.Callback):
    def init(self):
        self.losses = []

    def on_batch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))

하나의 배치가 끝날 때 마다 손실율을 losses에 저장한다. 


model.fit(X_data, Y_data, batch_size=10, epochs=2000, verbose=2, callbacks=[callBack])

이런 방식으로 콜백함수를 등록한다. 


seq_in = ['g8', 'e8', 'e4', 'f8']
seq_out = seq_in
seq_in = [code2idx[i]/13 for i in seq_in]

for i in range(50):
    sample_in = np.array(seq_in)
    sample_in = np.reshape(sample_in, (1, 4))
    predict_val = model.predict(sample_in)
    idx = np.argmax(predict_val)
    seq_out.append(idx2code[idx])
    seq_in.append(idx/float(13))
    seq_in.pop(0)

print(seq_out) 

첫 시작 4음표로 예측을 시작한다. 첫 음표들을 모두 13으로 나누어서 예측을 하기 위한 값으로 바꾸어주고 2차원 배열형태로 바꾸어준다. 

얘측 값 중 가장 적절한 값을 출력하기위한 배열에 등록한다. 


손실율과 정확도: [0.14114807188510894, 0.9200000023841858]

예측한 악보: ['g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4', 'd8', 'd8', 'd8', 'd8', 'e8', 'f4', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8', 'e8']


딥러닝 결과과 92%가 나와 원래의 악보와는 다른 모습을 보여주지만 다음시간에 배울 LSTM을 사용하면 정확히 만들 수 있다. 내일 찾아뵙겠습니다. 알바 후 공부는 피곤하다.......