#!/usr/bin/env python # coding: utf-8 # # RNN을 이용한 창작 (작곡) - 1 # - https://tykimos.github.io/2017/04/09/RNN_Layer_Talk/ # ## 0. 코딩환경준비 # conda create -n tf20 python==3.7
# conda activate tf20
# pip install jupyter
# pip install matplotlib
# pip install scipy
# pip install music21
# pip install tensorflow==2.0.0-alpha0 # - Musescore2 다운로드 및 설치 # - https://musescore.org/ko/download#older-versions # In[1]: import tensorflow as tf print(tf.__version__) # In[4]: import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, LSTM import tensorflow.keras.utils as utils import os # In[5]: #http://web.mit.edu/music21/doc/usersGuide/usersGuide_08_installingMusicXML.html import music21 # ## 1. 데이터 준비하기 # ### 시퀀스 데이터 정의 # In[7]: 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'] print("length of seq: {0}".format(len(seq))) # In[8]: note_seq = "" for note in seq: note_seq += note + " " m = music21.converter.parse("2/4 " + note_seq, format='tinyNotation') m.show("midi") # In[9]: m.show() # ### 코드 사전 정의 # In[10]: 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'} # ## 2. 데이터셋 생성하기 # ### 데이터셋 생성 함수 # # In[11]: def seq2dataset(seq, window_size): dataset = [] for i in range(len(seq) - window_size): subset = seq[i: (i + window_size + 1)] dataset.append([code2idx[item] for item in subset]) return np.array(dataset) # In[9]: test_seq = ['c4', 'd4', 'e4', 'f4', 'g4', 'd8', 'b8'] dataset = seq2dataset(seq=test_seq, window_size=4) print(dataset) # ### 생성 # In[12]: n_steps = 4 n_inputs = 1 dataset = seq2dataset(seq, window_size=n_steps) print("dataset.shape: {0}".format(dataset.shape)) # ### 입력(X)과 출력(Y) 변수로 분리하기 # In[13]: x_train = dataset[:, 0: n_steps] y_train = dataset[:, n_steps] print("x_train: {0}".format(x_train.shape)) print("y_train: {0}".format(y_train.shape)) print(x_train[0]) print(y_train[0]) # In[14]: max_idx_value = len(code2idx) - 1 print("max_idx_value: {0}".format(max_idx_value)) # ### 입력값 정규화 시키기 # In[15]: x_train = x_train / float(max_idx_value) # ### 입력을 (샘플 수, 타입스텝, 특성 수)로 형태 변환 # In[16]: x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], n_inputs)) # ### 라벨값에 대한 one-hot 인코딩 수행 # In[17]: y_train = utils.to_categorical(y_train) # In[18]: one_hot_vec_size = y_train.shape[1] print("one hot encoding vector size is {0}".format(one_hot_vec_size)) print("After pre-processing") print("x_train: {0}".format(x_train.shape)) print("y_train: {0}".format(y_train.shape)) # ## 3. 모델 구성하기 # In[19]: model = Sequential() model.add(LSTM( units=128, kernel_initializer='glorot_normal', bias_initializer='zero', batch_input_shape=(1, n_steps, n_inputs), stateful=True )) model.add(Dense( units=one_hot_vec_size, kernel_initializer='glorot_normal', bias_initializer='zero', activation='softmax' )) # ## 4. 모델 학습과정 설정하기 # In[20]: model.compile( loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'] ) # ## 5. 모델 학습시키기 # ### 손실 이력 클래스 정의 # In[21]: class LossHistory(tf.keras.callbacks.Callback): def init(self): self.epoch = 0 self.losses = [] def on_epoch_end(self, batch, logs={}): self.losses.append(logs.get('loss')) if self.epoch % 100 == 0: print("epoch: {0} - loss: {1:8.6f}".format(self.epoch, logs.get('loss'))) self.epoch += 1 # ### 학습 # In[20]: num_epochs = 1500 history = LossHistory() # 손실 이력 객체 생성 history.init() for epoch_idx in range(num_epochs + 1): model.fit( x=x_train, y=y_train, epochs=1, batch_size=1, verbose=0, shuffle=False, callbacks=[history] ) if history.losses[-1] < 1e-5: print("epoch: {0} - loss: {1:8.6f}".format(epoch_idx, history.losses[-1])) model.reset_states() break model.reset_states() # ## 6. 학습과정 살펴보기 # In[21]: import matplotlib.pyplot as plt get_ipython().run_line_magic('matplotlib', 'inline') plt.plot(history.losses) plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train'], loc='upper right') plt.show() # ## 7. 모델 평가하기 # In[22]: scores = model.evaluate(x_train, y_train, batch_size=1) print("{0}: {1}".format(model.metrics_names[1], scores[1]*100)) model.reset_states() # ## 8. 모델 사용하기 # ### 한 스텝 예측 # In[23]: pred_count = 50 # 최대 예측 개수 정의 # 한 스텝 예측 seq_out = ['g8', 'e8', 'e4', 'f8'] pred_out = model.predict(x_train) for i in range(pred_count): idx = np.argmax(pred_out[i]) # one-hot 인코딩을 인덱스 값으로 변환 seq_out.append(idx2code[idx]) # seq_out는 최종 악보이므로 인덱스 값을 코드로 변환하여 저장 model.reset_states() print("one step prediction : ", seq_out) # ### 곡 전체 예측 # In[24]: seq_in = ['g8', 'c8', 'f4', 'e8'] seq_out = seq_in seq_in = [code2idx[note] / float(max_idx_value) for note in seq_in] # 코드를 인덱스값으로 변환 for i in range(pred_count): sample_in = np.array(seq_in) sample_in = np.reshape(sample_in, (1, n_steps, n_inputs)) # 샘플 수, 타입스텝 수, 속성 수 pred_out = model.predict(sample_in) idx = np.argmax(pred_out) seq_out.append(idx2code[idx]) seq_in.append(idx / float(max_idx_value)) seq_in.pop(0) model.reset_states() print("full song prediction : ") for note in seq_out: print(note, end=" ") # In[25]: #http://web.mit.edu/music21/doc/usersGuide/usersGuide_08_installingMusicXML.html import music21 note_seq = "" for note in seq_out: note_seq += note + " " conv_midi = music21.converter.subConverters.ConverterMidi() m = music21.converter.parse("2/4 " + note_seq, format='tinyNotation') m.show("midi") # In[26]: m.show() # In[27]: m.write("midi", fp="./new_music.mid")