[Rnn]lstm을 이용해서 악보예측해보기

2018. 6. 11. 14:09Python-이론/python-인공지능2

lstm을 이요해서 악보 예측하기


이전의 코드들중 일부만 수정해서 lstm알고리즘을 실행시켜 보겠다. 

수정된 코드들 

trainData = dataset(seq, 4)
X_data = trainData[:, :4]/float(13)
X_data = np.reshape(X_data, (50, 4, 1))
Y_data = trainData[:, 4]
Y_data = np_utils.to_categorical(Y_data)

reshape를 통해 X_data를 50, 4, 1의 형태로 바꾸어 주었다. 그이유는 LSTM을 잘사용하기 위해선 타임 스텝, 속성, 배치사이즈, 상태유지모드 등이있다. 이중에서 타임스텝은 매 시퀀스마다 들어가게 되는 X_data의 개수 그러니깐 4개의 음표들이 들어가니깐 4가된다. 속성 값 1은 하나의 음표당 하나의 배열씩 입력값으로 들어가게된다. 안에 데이터형태를 보면 

이사진을 보면 하나의 음표 값이 하나의 배열에 쌓여있다. 따라서 하나의 입력값이 하나의 배열에 쌓여 입력되는 것을 결정하는 것이 속성값이 되는 것이다.  만약에 2, 2이면 2개의 배열에 2개의 값들이 들어가게 된다. 쉽게 생각해보면 input_dim = 1과 같다고 볼 수 있다. 


model = Sequential()
model.add(LSTM(128, input_shape=(4, 1)))
model.add(Dense(12, activation='softmax'))


LSTM(출력값, 입력되는 모형) 등의 형태로 사용할 수 있다. 


전체코드



import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout, MaxPooling2D, Flatten, LSTM
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)
X_data = trainData[:, :4]/float(13)
X_data = np.reshape(X_data, (50, 4, 1))
Y_data = trainData[:, 4]
Y_data = np_utils.to_categorical(Y_data)
print(X_data)

model = Sequential()
model.add(LSTM(128, input_shape=(4, 1)))
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, 1))
    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)

정확도 


[0.27173307418823245, 0.8999999904632568]


예측된 악보


['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']

우리의 예측과 달리 90%라는 초라한 확률이 나왔다 이는 퍼셉트론 층보다 2% 안좋은 확률이다. 

무엇이 문제였을까??? 

lstm의 특징 중 하나는 과거의 상태와 현재의 상태 모두 고려하여 미래의 형태를 생각하는 것인데 우리는 상태유지의 대한 코드를 사용하지 않았다. 밑에서 부턴 상태 유지의 코드를 추가하여 예측해보겠다. 

상태예측이란 ????? 
현재 상태의 정보를 그 다음 상태의 초기상황으로 전달 시켜주는 것이다. 
그럼 현재 상태를 예측해서 코드를 작성 해보겠다. 생각보다 어렵지 않다. 
model.add(LSTM(128, batch_input_shape=(1, 4, 1), stateful=True))
이 코드에 stateful만 추가해주면 상태 예측을 하면서 다음 값을 예측해줄 수 있다. 그리고 입력 모형이 1, 4, 1로 변한 이유는 한개의 데이터가 각 뱃치 할 때 마다 1개의 데이터가 들어간다는 뜻이다. 

과거에는 epochs를 2000을주고 돌렸지만 
for i in range(2000):
    print('epochs', i)
    model.fit(X_data, Y_data, batch_size=1, epochs=1, verbose=2, shuffle=False, callbacks=[callBack])
    model.reset_states()
이 코드에서는 악보를 한번 훈련 시킨 뒤 다음 악보와는 연관이 없기 때문에  model.reset_states()를 실행 후 다시 악보를 훈련한다.   model.reset_states() 이코드는 다음 훈련할 데이터가 이전의 훈련할 데이터와 완전히 연관 없을 때 사용하는 것이다. 예를 들어 챗봇을 만들 때 .이 오면 상태를 초기화 시킬 때 사용해도 됩니다. 

전체코드

import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout, MaxPooling2D, Flatten, LSTM
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)
X_data = trainData[:, :4]/13
X_data = np.reshape(X_data, (50, 4, 1))
Y_data = trainData[:, 4]
Y_data = np_utils.to_categorical(Y_data)

model = Sequential()
model.add(LSTM(128, batch_input_shape=(1, 4, 1), stateful=True))
model.add(Dense(12, activation='softmax'))

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


callBack = losses_callback()
callBack.init()

for i in range(2000):
    print('epochs', i)
    model.fit(X_data, Y_data, batch_size=1, epochs=1, verbose=2, shuffle=False, callbacks=[callBack])
    model.reset_states()

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

score = model.evaluate(X_data, Y_data, batch_size=1)
model.reset_states()
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, 1))
    predict_val = model.predict(sample_in)
    idx = np.argmax(predict_val)
    seq_out.append(idx2code[idx])
    seq_in.append(idx/13)
    seq_in.pop(0)
model.reset_states()
print(seq_out)


확률

[0.0001798874410951612, 1.0]


결과

['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']


앞서 우리가 사용했던 데이터의 악보와 동일하게 나오는 것을 알 수 있다.