- 이전글
[작곡 인공지능 개발기] 4. keras를 활용한 의류 데이터 분류
이전 글에서 keras 라이브러리에 익숙해지기 위해 의류 데이터 분류 예제를 살펴보았다. 이제 Keras Chopin LSTM의 코드를 뜯어보며 이해햐려고 한다.
https://tykimos.github.io/2018/09/14/How_to_Generate_Music_using_a_LSTM_Neural_Network_in_Keras/
Keras Chopin LSTM Source Code
Google Colab에서 작성된 코드입니다. Jupyter Notebook 과 달리, Google Colab의 Tab은 띄어쓰기 4칸이 아닌 2칸입니다. Jupyter Notebook 에 쓰실 때 이 점 유의하시길 바랍니다. 다른 저장소로부터 Chopin 피아노곡
bcdeep.tistory.com
1. LSTM이 무엇이고 왜 작곡에 적합한가
이 블로그에 설명이 너무 잘 되어있어 생략한다.
https://dgkim5360.tistory.com/entry/understanding-long-short-term-memory-lstm-kr
Long Short-Term Memory (LSTM) 이해하기
이 글은 Christopher Olah가 2015년 8월에 쓴 글을 우리 말로 번역한 것이다. Recurrent neural network의 개념을 쉽게 설명했고, 그 중 획기적인 모델인 LSTM을 이론적으로 이해할 수 있도록 좋은 그림과 함께
dgkim5360.tistory.com
2. 코드의 전체적인 흐름 살펴보기
일단 코드에서 print 되는 출력을 통해 전체적인 흐름을 파악해보자.
Parsing file 0 ./zelda/Ludwig Van Beethoven - Moonlight Sonata 3rd Movement.midClasses of notes : 130
notes : ['C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', '1', 'G#2', 'C2', 'G#2', 'C2', 'G#2', 'C2', 'G#2', 'C2', 'G#2', 'C2', 'G#2', 'C2', 'G#2', 'C2', 'G#2', '0', 'G#2', 'B1', 'G#2', 'B1', 'G#2', 'B1', 'G#2', 'B1', 'G#2', 'B1', 'G#2', 'B1', 'G#2', 'B1', 'G#2', '11', 'G#2', 'A1', 'A2', 'A1', 'A2', 'A1', 'A2', 'A1', 'A2', 'A1', 'A2', 'A1', 'A2', 'A1', 'A2', 'A1', 'A2', '8', 'C4', 'G#3', 'C4', 'C#4', 'E-4', 'E4', 'F#4', 'E-4', 'F#4', 'G#3', 'E4', 'A4', 'G#4', 'F#4', 'E4', 'E-4', 'C#4', 'C4', 'G#3', 'C4', 'C4', 'C#4', 'E-4', 'E4', 'F#4', 'E-4', 'F#4', 'G#3', 'E4', 'A4', 'G#4', 'F#4', 'E4', 'E-4', 'C#4', '8.0', '8.1', '8.0', '8.1', '8.0', '8.1', '8.0', '8.1', '8.0', '8', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', 'C#2', 'G#2', '1', 'G#2', 'B-1', 'C#3', 'B-2', 'C#3', 'B-2', 'C#3', 'B-2', 'C#3', 'B-2', 'C#3', 'B-2', 'C#3', 'B-2', 'C#3', '9.10', 'C#3', 'G1', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'G#2', 'E-3', 'B2', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B-2', 'E-3', 'C#3', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'B2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'G2', 'E-3', 'F#2', 'E-3', 'F#2', 'E-3', 'F#2', 'E-3', 'F#2', 'E-3', 'E2', 'C#3', 'E2', 'C#3', 'E2', 'C#3', 'E2', 'C#3', 'E2', 'C#3', 'F#2', 'C#3', 'E-2', 'B2', 'F#2', 'B2', 'E-2', 'B2', 'F#2', 'B2', 'E-2', 'B2', 'F#2', 'B2', 'D2', 'B2', 'E2', 'B2', 'D2', 'B2', 'E2', 'B2', 'C#2', 'A2', 'E2', 'A2', 'C#2', 'A2', 'E2', 'A2', 'C#2', 'B-2', 'E-2', 'B-2', 'B1', 'G#2', 'E-2', 'G#2', 'B1', 'G#2', 'E-2', 'G#2', 'B1', 'G#2', 'E-2', 'G#2', '9.1.4', 'G#2', 'C#3', 'E3', 'G#3', 'C#3', 'E3', 'G#3', 'C#4', 'E3', 'G#3', 'C#4', 'E4', 'G#3', 'C#4', 'E4', '9.1.4', 'G#4', 'C#4', 'E4', 'G#4', 'C#5', 'E4', 'G#4', 'C#5', '9.1.4', 'E5', 'E5', 'G#5', 'C#6', '4', '4', '9.1.4', 'E3', 'G3', 'C#4', '9.1.4', 'E4', 'G3', 'C#4', 'E4', '9.1.4', 'G4', 'C#4', 'E4', 'G4', '9.1.4', 'C#5', 'E4', 'G4', 'C#5', 'E-4', 'B4', 'E5', 'G4', 'G#4', 'C#5', 'B4', 'E5', 'E-4', 'B4', 'G5', 'C#5', 'G#4', 'E5', 'B4', 'G5', 'E-4', 'C#5', 'C#6', 'E5', 'G4', 'G5', 'C#5', 'C#6', 'E-4', 'C#5', '4', 'G4', '4', 'C#5', '4.8.11', 'E-3', 'B-3', 'C#4', '9.1.4', 'E-4', 'B-3', 'C#4', 'E-4', 'B-4', 'C#4', 'E-4', 'B-4', 'C#5', 'E-4', 'B-4', 'C#5', 'E-5', 'B-4', 'C#5', 'E-5', '9.1.4', 'B-5', 'C#5', 'E-5', 'B-5', 'C#6', 'B-5', 'E-5', 'C#5', '9.1.4', 'B-5', 'E-5', 'C#5', 'B-4', 'B4', '9.1.4', 'E-5', '9.1.4', '9.1.4', 'B4', '9.1.4', 'G#4', 'B4', '9.1.4', '7.8', 'B-4', 'G#4', 'G4', 'G4', 'E-5', 'G4', 'E-5', 'C#3', 'A3', 'B-4', 'E3', 'A3', 'C#3', 'A3', 'G#4', 'E3', 'A3', 'D3', 'B3', 'F3', 'G#4', 'B3', 'D3']
length of notes : 4454
학습할 미디 파일이 담긴 zelda 폴더에서 파일을 하나씩 읽어 notes 배열에 노트 문자열 데이터를 추가한다.
pitchnames : ['0', '0.1', '0.3', '0.3.6', '0.3.6.9', '0.6', '1', '1.3', '1.3.7', '1.4', '1.4.6.7.9', '1.4.7.10', '1.4.8', '1.5', '1.5.8', '1.6', '1.7', '10', '10.0', '10.1', '10.1.4.6.7', '10.11', '10.3', '11', '11.3', '2', '2.6', '2.6.9', '3', '3.4', '3.6', '3.6.7.10.11', '3.6.9', '3.7', '3.8', '4', '4.10', '4.5', '4.7', '4.8', '4.8.11', '4.9', '5', '5.6', '6', '6.10', '6.7', '6.8', '6.8.0', '6.9', '6.9.1', '7', '7.10', '7.8', '8', '8.0', '8.0.3', '8.1', '8.11', '8.11.1', '8.11.3', '8.9', '9', '9.0', '9.0.3', '9.1', '9.1.3', '9.1.4', '9.10', '9.10.0.3.6', '9.2', 'A1', 'A2', 'A3', 'A4', 'A5', 'B-1', 'B-2', 'B-3', 'B-4', 'B-5', 'B1', 'B2', 'B3', 'B4', 'B5', 'C#2', 'C#3', 'C#4', 'C#5', 'C#6', 'C2', 'C3', 'C4', 'C5', 'D2', 'D3', 'D4', 'D5', 'D6', 'E-2', 'E-3', 'E-4', 'E-5', 'E-6', 'E2', 'E3', 'E4', 'E5', 'E6', 'F#1', 'F#2', 'F#3', 'F#4', 'F#5', 'F1', 'F2', 'F3', 'F4', 'F5', 'G#1', 'G#2', 'G#3', 'G#4', 'G#5', 'G1', 'G2', 'G3', 'G4', 'G5']
length of pitchnames : 130
중복된 항을 제거하고 문자열데이터를 숫자 데이터로 바꾼 pitchnames 배열을 생성한다.
note_to_int : {'0': 0, '0.1': 1, '0.3': 2, '0.3.6': 3, '0.3.6.9': 4, '0.6': 5, '1': 6, '1.3': 7, '1.3.7': 8, '1.4': 9, '1.4.6.7.9': 10, '1.4.7.10': 11, '1.4.8': 12, '1.5': 13, '1.5.8': 14, '1.6': 15, '1.7': 16, '10': 17, '10.0': 18, '10.1': 19, '10.1.4.6.7': 20, '10.11': 21, '10.3': 22, '11': 23, '11.3': 24, '2': 25, '2.6': 26, '2.6.9': 27, '3': 28, '3.4': 29, '3.6': 30, '3.6.7.10.11': 31, '3.6.9': 32, '3.7': 33, '3.8': 34, '4': 35, '4.10': 36, '4.5': 37, '4.7': 38, '4.8': 39, '4.8.11': 40, '4.9': 41, '5': 42, '5.6': 43, '6': 44, '6.10': 45, '6.7': 46, '6.8': 47, '6.8.0': 48, '6.9': 49, '6.9.1': 50, '7': 51, '7.10': 52, '7.8': 53, '8': 54, '8.0': 55, '8.0.3': 56, '8.1': 57, '8.11': 58, '8.11.1': 59, '8.11.3': 60, '8.9': 61, '9': 62, '9.0': 63, '9.0.3': 64, '9.1': 65, '9.1.3': 66, '9.1.4': 67, '9.10': 68, '9.10.0.3.6': 69, '9.2': 70, 'A1': 71, 'A2': 72, 'A3': 73, 'A4': 74, 'A5': 75, 'B-1': 76, 'B-2': 77, 'B-3': 78, 'B-4': 79, 'B-5': 80, 'B1': 81, 'B2': 82, 'B3': 83, 'B4': 84, 'B5': 85, 'C#2': 86, 'C#3': 87, 'C#4': 88, 'C#5': 89, 'C#6': 90, 'C2': 91, 'C3': 92, 'C4': 93, 'C5': 94, 'D2': 95, 'D3': 96, 'D4': 97, 'D5': 98, 'D6': 99, 'E-2': 100, 'E-3': 101, 'E-4': 102, 'E-5': 103, 'E-6': 104, 'E2': 105, 'E3': 106, 'E4': 107, 'E5': 108, 'E6': 109, 'F#1': 110, 'F#2': 111, 'F#3': 112, 'F#4': 113, 'F#5': 114, 'F1': 115, 'F2': 116, 'F3': 117, 'F4': 118, 'F5': 119, 'G#1': 120, 'G#2': 121, 'G#3': 122, 'G#4': 123, 'G#5': 124, 'G1': 125, 'G2': 126, 'G3': 127, 'G4': 128, 'G5': 129}
pitchnames 배열의 각 항을 정수형 데이터로 매핑시키는 note_to_int dict를 만든다.
(4354, 100)
(4354,)
n_patterns : 4354
shape of net_in : (4354, 100, 1)
shape of net_out : (4354, 130)
dict를 통해 LSTM모델의 입출력을 만들어 준다.
Model: "Chopin_LSTM"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
lstm (LSTM) (None, 100, 512) 1052672
dropout (Dropout) (None, 100, 512) 0
lstm_1 (LSTM) (None, 100, 512) 2099200
dropout_1 (Dropout) (None, 100, 512) 0
lstm_2 (LSTM) (None, 512) 2099200
dense (Dense) (None, 256) 131328
dropout_2 (Dropout) (None, 256) 0
dense_1 (Dense) (None, 130) 33410
=================================================================
Total params: 5,415,810
Trainable params: 5,415,810
Non-trainable params: 0
_________________________________________________________________
사용한 모델의 구조이다.
Epoch 1/75
69/69 [==============================] - 208s 3s/step - loss: 4.2607
Epoch 2/75
69/69 [==============================] - 180s 3s/step - loss: 4.0899
Epoch 3/75
69/69 [==============================] - 177s 3s/step - loss: 4.0209
Epoch 4/75
69/69 [==============================] - 174s 3s/step - loss: 3.9865
Epoch 5/75
69/69 [==============================] - 194s 3s/step - loss: 3.9663
Epoch 6/75
69/69 [==============================] - 199s 3s/step - loss: 3.9430
Epoch 7/75
69/69 [==============================] - 200s 3s/step - loss: 3.9247
Epoch 8/75
69/69 [==============================] - 200s 3s/step - loss: 3.9523
Epoch 9/75
69/69 [==============================] - 198s 3s/step - loss: 3.9002
Epoch 10/75
69/69 [==============================] - 180s 3s/step - loss: 3.8564
Epoch 11/75
69/69 [==============================] - 175s 3s/step - loss: 3.8459
Epoch 12/75
69/69 [==============================] - 201s 3s/step - loss: 3.8173
Epoch 13/75
69/69 [==============================] - 204s 3s/step - loss: 3.7953
Epoch 14/75
69/69 [==============================] - 202s 3s/step - loss: 3.7606
Epoch 15/75
69/69 [==============================] - 192s 3s/step - loss: 3.7418
Epoch 16/75
69/69 [==============================] - 202s 3s/step - loss: 3.7448
Epoch 17/75
69/69 [==============================] - 194s 3s/step - loss: 3.6940
Epoch 18/75
69/69 [==============================] - 211s 3s/step - loss: 3.6269
Epoch 19/75
69/69 [==============================] - 206s 3s/step - loss: 3.7066
Epoch 20/75
69/69 [==============================] - 208s 3s/step - loss: 3.5235
Epoch 21/75
69/69 [==============================] - 207s 3s/step - loss: 3.3872
Epoch 22/75
69/69 [==============================] - 201s 3s/step - loss: 3.3031
Epoch 23/75
69/69 [==============================] - 164s 2s/step - loss: 3.2041
Epoch 24/75
69/69 [==============================] - 167s 2s/step - loss: 3.0210
Epoch 25/75
69/69 [==============================] - 166s 2s/step - loss: 2.8705
Epoch 26/75
69/69 [==============================] - 166s 2s/step - loss: 2.8140
Epoch 27/75
69/69 [==============================] - 167s 2s/step - loss: 2.6915
Epoch 28/75
69/69 [==============================] - 165s 2s/step - loss: 2.4699
Epoch 29/75
69/69 [==============================] - 165s 2s/step - loss: 2.2937
Epoch 30/75
69/69 [==============================] - 165s 2s/step - loss: 2.1816
Epoch 31/75
69/69 [==============================] - 165s 2s/step - loss: 2.1007
Epoch 32/75
69/69 [==============================] - 168s 2s/step - loss: 1.8648
Epoch 33/75
69/69 [==============================] - 165s 2s/step - loss: 1.7360
Epoch 34/75
69/69 [==============================] - 168s 2s/step - loss: 1.5873
Epoch 35/75
69/69 [==============================] - 165s 2s/step - loss: 1.7283
Epoch 36/75
69/69 [==============================] - 166s 2s/step - loss: 1.5765
Epoch 37/75
69/69 [==============================] - 169s 2s/step - loss: 1.2454
Epoch 38/75
69/69 [==============================] - 166s 2s/step - loss: 1.0332
Epoch 39/75
69/69 [==============================] - 166s 2s/step - loss: 0.8615
Epoch 40/75
69/69 [==============================] - 166s 2s/step - loss: 0.7392
Epoch 41/75
69/69 [==============================] - 168s 2s/step - loss: 0.7397
Epoch 42/75
69/69 [==============================] - 167s 2s/step - loss: 0.5503
Epoch 43/75
69/69 [==============================] - 161s 2s/step - loss: 0.4847
Epoch 44/75
69/69 [==============================] - 166s 2s/step - loss: 0.4350
Epoch 45/75
69/69 [==============================] - 166s 2s/step - loss: 0.4268
Epoch 46/75
69/69 [==============================] - 171s 2s/step - loss: 0.3205
Epoch 47/75
69/69 [==============================] - 168s 2s/step - loss: 0.2419
Epoch 48/75
69/69 [==============================] - 166s 2s/step - loss: 0.1821
Epoch 49/75
69/69 [==============================] - 166s 2s/step - loss: 0.1475
Epoch 50/75
69/69 [==============================] - 166s 2s/step - loss: 0.1549
Epoch 51/75
69/69 [==============================] - 166s 2s/step - loss: 0.3719
Epoch 52/75
69/69 [==============================] - 168s 2s/step - loss: 0.2794
Epoch 53/75
69/69 [==============================] - 166s 2s/step - loss: 0.1415
Epoch 54/75
69/69 [==============================] - 168s 2s/step - loss: 0.1238
Epoch 55/75
69/69 [==============================] - 166s 2s/step - loss: 0.0867
Epoch 56/75
69/69 [==============================] - 166s 2s/step - loss: 0.0613
Epoch 57/75
69/69 [==============================] - 166s 2s/step - loss: 0.0639
Epoch 58/75
69/69 [==============================] - 168s 2s/step - loss: 0.0494
Epoch 59/75
69/69 [==============================] - 166s 2s/step - loss: 0.0480
Epoch 60/75
69/69 [==============================] - 166s 2s/step - loss: 0.0492
Epoch 61/75
69/69 [==============================] - 159s 2s/step - loss: 0.0531
Epoch 62/75
69/69 [==============================] - 158s 2s/step - loss: 0.0530
Epoch 63/75
69/69 [==============================] - 168s 2s/step - loss: 0.6411
Epoch 64/75
69/69 [==============================] - 166s 2s/step - loss: 0.3451
Epoch 65/75
69/69 [==============================] - 170s 2s/step - loss: 0.1237
Epoch 66/75
69/69 [==============================] - 167s 2s/step - loss: 0.0703
Epoch 67/75
69/69 [==============================] - 166s 2s/step - loss: 0.0610
Epoch 68/75
69/69 [==============================] - 166s 2s/step - loss: 0.0385
Epoch 69/75
69/69 [==============================] - 159s 2s/step - loss: 0.0415
Epoch 70/75
69/69 [==============================] - 166s 2s/step - loss: 0.2028
Epoch 71/75
69/69 [==============================] - 166s 2s/step - loss: 0.1515
Epoch 72/75
69/69 [==============================] - 166s 2s/step - loss: 0.0834
Epoch 73/75
69/69 [==============================] - 169s 2s/step - loss: 0.0481
Epoch 74/75
69/69 [==============================] - 166s 2s/step - loss: 0.0379
Epoch 75/75
69/69 [==============================] - 166s 2s/step - loss: 0.0464
75번의 epoch를 거치며 모델을 학습시킨다.
Random Sequence : [124, 101, 86, 101, 121, 101, 124, 86, 9, 124, 101, 107, 121, 123, 101, 89, 86, 108, 101, 123, 121, 89, 101, 108, 86, 124, 87, 89, 121, 108, 87, 124, 86, 90, 87, 108, 121, 124, 87, 90, 124, 86, 109, 101, 90, 121, 124, 101, 108, 86, 90, 101, 124, 121, 108, 101, 89, 86, 124, 87, 108, 121, 89, 87, 123, 86, 108, 87, 89, 121, 123, 87, 107, 123, 86, 89, 101, 123, 121, 107, 101, 88, 86, 123, 101, 107, 121, 88, 101, 122, 86, 87, 121, 87, 86, 87, 121, 87, 86, 87]
학습된 모델에 입력할 랜덤 숫자배열을 생성하고 입력한다.
Predicted 499 length of pred_out : 500
pred_out : ['G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'B1', '1.4.8', 'B2', 'C#2', 'B2', 'B1', 'B2', 'C#2', 'B2', 'B1', '1.4.8', 'B2', 'C#2', 'B2', 'B1', 'B2', 'C#2', 'B2', '9', 'F#2', 'A1', 'F#2', 'A1', 'G#2', 'A1', 'G#2', 'A1', 'F#2', 'A1', 'F#2', 'A1', 'F#2', '9', 'F#2', 'G#1', 'E2', 'G#1', 'E2', 'G#1', 'E2', 'G#1', 'E2', 'G#1', 'E2', 'G#1', 'E2', 'G#1', 'E2', '8', 'E2', 'G1', '7.10', '1.4', '10.1.4.6.7', '6.10', '1.4', '1.4.6.7.9', '1.4.7.10', 'F#1', '7.10', '11.3', '3.6.7.10.11', '6.10', '0.3', '0.3.6.9', '0.3.6.9', 'C#3', 'G#3', 'E3', 'C#3', 'C#3', 'C3', 'C3', 'G#3', 'C3', 'E-3', 'C#3', 'C#3', 'G#3', 'C#3', 'E3', 'E-3', 'E-3', 'G#3', 'E3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'E3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C3', 'G#3', 'C#3', 'G#3', 'C3', 'G#3', 'C#3', 'G#3', 'B2', 'G#3', 'B2', 'G#3', 'C#3', 'G#3', 'B2', 'C#3', 'B2', 'C#3', 'B2', 'C#3', 'B2', 'C#3', 'A2', 'C#3', 'A2', 'C#3', 'A2', 'C#3', 'A2', 'C#3', 'F2', 'C#3', 'F2', 'C#3', 'F2', 'C#3', 'F2', 'C#3', '1.6', 'A3', 'F#3', 'C#3', 'A3', '1.6', 'F#3', 'C#3', 'A2', 'C#3', 'F#3', 'A3', 'C#4', '2.6', 'A3', 'F#3', 'D3', 'A3', '2.6', 'F#3', 'D3', 'A2', 'D3', 'F#3', 'A3', 'D4', '1.7', '1.7', 'B-3', 'G3', 'E3', 'C#3', 'E3', 'G3', '8.1', '8.1', 'G#3', 'E4', 'C#4', 'G#3', 'E3', 'C#4', 'G#3', 'E3', 'C#3', 'G#3', 'E3', 'C#3', 'G#2', 'A2', 'B-2', 'B2', 'C3', 'C#3', 'D3', 'E-3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'B-3', 'B3', '6.8.0', '7', '7', '8', '8', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'E-3', 'G#2', 'E-3', 'E-2', 'E-3', 'G#2', 'E-3', 'E-2', 'E-3', 'G#2', 'E-3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'C#3', 'G#2', 'C#3', 'C#2', 'E-3', 'G#2', 'E-3', 'C#2', 'E-3', 'G#2', 'E-3', '8.1', 'E3', 'G#3', 'C#4', 'E4', 'G#3', 'C#4', 'E4', 'G#4', 'C#4', 'E4', 'G#4', 'C#5', 'E4', 'G#4', 'C#5', 'E5', 'C#5', 'G#4', 'E4', 'C#5', 'G#4', 'E4', 'C#4', 'G#4', 'E4', 'C#4', 'G#3', 'E4', 'C#4', 'G#3', 'E3', 'C#4', 'G#3', 'E3', 'C#3', 'G#3', 'E3', 'C#3', 'G#2', '4', '1', '8', '4', '1', '8', '4', '8', '1', '1.4.8', '1.4.8', '1.4.8', '1.4.8', '1.3', '1.4.8', '1.4.8', '8', '1.4.8', '8', '10', 'G#2', '0', '8.11', '3.6.9', '11.3', 'C#3', '1', '8.1', '8.1', '1.4', '1', 'E-3', 'B-4', '10.11', '8.11', '8.11', 'B-5', '8.11', '11', '11.3', 'E-3', '7.10', '7.10', '10.3', '11', '8', '8', '3.8', '3.8', '3.8', '3.8', '9', '3.8', '10.3', '11.3', '9', '1.3', 'G4', '7.8', '1.3', '7.8', '4.7', '1.3', '8', '1.3', '1.3', '1.3', '11.3', '10.3', '8', '8', '9.1.4', '3.8', '3.8', '3.8', '3.8', 'E5', '3.8', 'F#5', 'G#5', '10.3', 'A5', 'B5', '11.3', 'C#6', 'B5', '1.3', 'A5', 'E5', '1.3', 'F#5', 'G#5', '1.3', 'A5', 'B5', '1.3', 'C#6', 'B5', '1.3', 'A5']
출력된 숫자배열을 note_to_int dict를 사용하여 문자열 노트 데이터로 바꾸고 미디파일을 생성한다.
3. 코드구성
주석을 달며 이해해보았다.
import glob, pickle # 파일 불러오기에 유용한 라이브러리들
import numpy as np # 행렬 계산
# MIDI 파일을 다루기 위한 라이브러리
from music21 import converter, instrument, note, chord, stream
# 순차 모델 생성을 위한 라이브러리
from keras.models import Sequential
# LSTM : CPU 동작 / CuDNNLSTM : GPU 동작
from keras.layers import Dense, Dropout, LSTM, CuDNNLSTM
# One-hot Vector 만들기 위한 라이브러리
from keras.utils import np_utils
# MIDI 파일을 잘 불러왔는지 테스트
# MIDI 파일을 불러오는 함수
midi = converter.parse("./Zelda_Overworld.mid")
# MIDI 파일 내의 notes(음정, 박자를 포함하는 정보)를 불러온다
notes_to_parse = midi.flat.notes
# 불러온 notes의 갯수
print(np.shape(notes_to_parse))
# 10개 테스트 출력
for e in notes_to_parse[:20]:
print(e, e.offset)
# Note / Chord 두 종류로 나뉜다, Chord는 Note의 집합이다
# zelda 폴더의 모든 MIDI 파일의 정보를 뽑아 하나로 만든다
# MIDI 파일로부터 Note 정보만 뽑아서 저장할 리스트
notes = []
# zelda 폴더 내의 모든 MIDI 파일에 반복문으로 접근
# glob.glob() : *를 제외한 나머지 부분이 같은 파일 이름들을 배열로 저장
# enumerate : 파일이름 배열을 순차적으로 하나씩 file에 넣는다
# i : 0 부터 1씩 증가 / file : 각 파일 이름
for i,file in enumerate(glob.glob("./zelda/*.mid")):
# midi: MIDI 파일의 전체 정보를 담고 있다 ------------------------------------------
midi = converter.parse(file)
print('\r', 'Parsing file ', i, " ",file, end='') # 현재 진행 상황 출력
# notes_to_parse : MIDI 파일을 Notes로 나누어 다루기 위한 변수
notes_to_parse = None
# try / except : try 수행 중 에러 발생 시 except 수행 -----------------------------
# MIDI 파일 구조 차이로 인한 에러 방지
# MIDI 파일의 Note / Chord / Tempo 정보만 가져온다
try: # file has instrument parts
s2 = instrument.partitionByInstrument(midi)
notes_to_parse = s2.parts[0].recurse()
except: # file has notes in a flat structure
notes_to_parse = midi.flat.notes
# Note / Chord / Tempo 정보 중 Note, Chord 의 경우 따로 처리, Tempo 정보는 무시 ----
for e in notes_to_parse:
# Note 인 경우 높이(Pitch), Octave 로 저장
if isinstance(e, note.Note):
notes.append(str(e.pitch))
# Chord 인 경우 각 Note의 음높이(Pitch)를 '.'으로 나누어 저장
elif isinstance(e, chord.Chord):
# ':'.join([0, 1, 2]) : [0, 1, 2] -> [0:1:2]
# str(n) for n in e.normalOrder
# => e.normalOrder 라는 배열 내의 모든 원소 n에 대해 str(n) 해준 새 배열을 만든다.
# ex) str(i) for i in [1, 2, 3] => ['1', '2', '3']
notes.append('.'.join(str(n) for n in e.normalOrder))
# 배열 내의 모든 원소 n에 대해 str(n) 해준 새 배열을 만든다.
# # ex) str(i) for i in [1, 2, 3] => ['1', '2', '3']
# notes.append('.'.join(str(n) for n in e.normalOrder))
# 해당 경로의 MIDI 파일 이 잘 Parsing 되었다는 출력
# 각기 음정 (Note / Chord) 마다 각각 다른 정수에 할당합니다.
# 여기서 쓰는 각각의 음정이 456가지이므로 1 ~ 456 에 각각 음이 할당되게 됩니다.
# MIDI 파일 정보를 다루기 쉽게 바꿔준다
# n_vocab : 모델 출력의 가짓수를 정하기 위해 Note의 총 가짓수를 센다
# set() : 중복되는 원소는 한번만 쓴다 / ex) set("Hello") => {'e', 'H', 'l', 'o'}
n_vocab = (len(set(notes)))
print('Classes of notes : ', n_vocab, '\n')
print('notes : ', notes[:500])
print('length of notes : ', len(notes), '\n')
# pitchnames : notes 배열의 모든 가능한 Note / Chord 를 정렬해놓은 배열
pitchnames = sorted(set(item for item in notes))
print('pitchnames : ', pitchnames)
print('length of pitchnames : ', len(pitchnames), '\n')
# create a dictionary to map pitches to integers
# 음높이(Pitch)를 정수에 매핑하는 dictionary 자료형 생성
# ex) dict = {'key': value} => dict['key'] = value
note_to_int = dict((note, number) for number, note in enumerate(pitchnames))
print('note_to_int : ', note_to_int)
# LSTM 모델에 필요한 Dataset을 만들어줍니다.
# 시계열을 100개씩 잘라주는 이유는 LSTM 학습 시 BPTT가 무한하게 역전파될 수 없기 때문입니다.
# LSTM 모델을 위한 Training Dataset 생성
# 곡을 베끼는 것이 아닌 작곡이기 때문에 Validation이 필요없다
seq_len = 100 # 시퀀스 길이
# Pitch를 정수로 바꾸어 LSTM 모델의 입출력으로 만들어준다
net_in = []
net_out = []
# LSTM 모델의 입출력을 만들기 위해 ( 전체 길이 - 시퀀스 길이(=100) ) 만큼 반복
# ex) 입력 : 출력 짝지어주기
# [0 ~ 99] : [100] / [1 ~ 100] : [101] / ... / [전체 길이-100 ~ 전체 길이-1] : [전체 길이]
for i in range(0, len(notes) - seq_len):
# LSTM 모델 입력과 출력을 만들어준다
seq_in = notes[i:i + seq_len] # ex) [0:100] => [0 ~ 99]
seq_out = notes[i + seq_len] # ex) [100]
# LSTM은 문자열이 아닌 숫자를 입출력으로 하므로 문자열을 정수로 바꿔야 한다
net_in.append([note_to_int[char] for char in seq_in]) # 배열 안의 모든 원소에 대해 실행
net_out.append(note_to_int[seq_out]) # 출력값 하나에 대해 실행
print(np.shape(net_in))
print(np.shape(net_out))
# 만들어준 Dataset에 전처리를 해줍니다.
# 데이터의 경우 1 ~ 456 범위에서 0 ~ 1 범위로,
# 라벨의 경우 1차원 값에서 456차원 One-hot Vector로 만들어줍니다.
# One-hot encoding 과정을 정리하자면 다음과 같습니다.
# 1. 각 단어에 고유한 인덱스 부여
# 2. 표현하고 싶은 단어의 인덱스 위치에 1을 부여, 다른 단어의 인덱스 위치에는 0을 부여
# LSTM 모델 입출력에 맞게 Dataset 전처리
# 시퀀스 길이(100) 만큼을 빼고 반복했으므로 100개 적은 패턴이 생긴다
n_patterns = len(net_in)
print('n_patterns : ', n_patterns)
# reshape the input into a format compatible with LSTM layers
# LSTM 입력에 맞는 모양으로 바꿔준다 : (샘플 수, 시퀀스 길이, 자료의 차원)
net_in = np.reshape(net_in, (n_patterns, seq_len, 1))
print('shape of net_in : ', net_in.shape)
# 데이터 범위 정규화 : 0 ~ (n_vocab - 1) => 0 ~ 1
net_in = net_in / float(n_vocab)
# 분류이므로 출력을 One-hot Vector로 만들어주어야 한다.
net_out = np_utils.to_categorical(net_out)
print('shape of net_out : ', net_out.shape)
# LSTM 모델을 구성해줍니다.
# 3층 짜리 Stacked LSTM 으로 마지막 층은 Many to One 형태이며, 각 층에 Dropout을 적용하였습니다.
# 456가지의 음정 중 하나를 선택하는 작업이므로 분류 작업이며, 출력 층 활성화 함수는 Softmax가 됩니다.
# 모델 구성
# 데이터의 Feature(특징) 수 or Dimension(차원)
data_dim = net_in.shape[2]
# GPU 환경 : CuDNNLSTM() / CPU 환경 : LSTM()
model = Sequential(name="Chopin_LSTM")
# return_sequences : True : Many to Many / False : Many to One
# seq_len : 입력으로 넣을 시계열 데이터의 길이 / data_dim : 각 데이터의 차원
model.add(LSTM(512, input_shape=(seq_len, data_dim), return_sequences=True))
# GPU / CUDA / CuDNN 이 없는 환경에선 CuDNNLSTM만 LSTM으로 바꾸어 쓰면 됩니다.
# model.add(LSTM(512, input_shape=(seq_len, data_dim), return_sequences=True))
model.add(Dropout(rate=0.3))
model.add(LSTM(512, return_sequences=True))
model.add(Dropout(rate=0.3))
model.add(LSTM(512))
model.add(Dense(256))
model.add(Dropout(rate=0.3))
model.add(Dense(n_vocab, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.summary()
# 기본적인 학습 방법을 쓰므로 fit 함수를 사용합니다.
# 모델 학습
# 음악 작곡이기 때문에 Validation Set이 없다
# LSTM : 13시간 / CuDNN : 3시간 반
# loss 는 0.7 ~ 0.8 만 되어도 충분히 작곡 능력을 갖는다.
model.fit(net_in, net_out, epochs=75, batch_size=64)
# 학습된 모델을 사용하기 위해 LSTM 입력을 다시 만들어주겠습니다.
# 작곡을 위해 LSTM 모델 입력을 다시 만든다
# 위와 동일하므로 주석 생략
net_in = []
output = []
for i in range(0, len(notes) - seq_len, 1):
seq_in = notes[i:i + seq_len]
seq_out = notes[i + seq_len]
net_in.append([note_to_int[char] for char in seq_in])
output.append(note_to_int[seq_out])
n_patterns = len(net_in)
# LSTM 모델은 초기에 길이 100의 시계열 데이터 (시퀀스)를 받으면 그 뒤에 한 음씩 작곡해냅니다.
# LSTM 모델이 작곡을 시작하기 위해 시작점으로써 랜덤한 시퀀스를 골라야 한다
# pattern : Dataset의 입력 전체 시퀀스 중 랜덤하게 고른 시퀀스
start = np.random.randint(0, len(net_in)-1)
pattern = net_in[start]
print('Random Sequence : ', pattern)
# int_to_note: 정수를 다시 Note로 바꾸기 위한 dictionary 자료형
int_to_note = dict((number, note) for number, note in enumerate(pitchnames))
print('int_to_note : ', int_to_note)
# 이제 초기 100개 음정을 갖는 LSTM 모델이 500개의 음정을 작곡해냅니다.
# LSTM 모델이 만든 출력값을 저장하기 위한 빈 리스트
pred_out = []
# generate 500 notes
for i in range(0, 500):
# 랜덤하게 고른 시퀀스를 LSTM 모델 입력에 맞게 바꿔준다
pred_in = np.reshape(pattern, (1, len(pattern), 1))
# 입력 범위 정규화 / 0 ~ (n_vocab -1) => 0 ~ 1
pred_in = pred_in / float(n_vocab)
# LSTM 모델 사용
prediction = model.predict(pred_in, verbose=0)
# 출력 중 값이 가장 큰 Index 선택
index = np.argmax(prediction)
# 정수 값을 Note 값으로 변경
result = int_to_note[index]
print('\r', 'Predicted ', i, " ",result, end='')
# LSTM이 만든 Note를 하나씩 리스트에 담는다
pred_out.append(result)
# 다음 입력을 위해 입력에 새 값 추가 후 가장 과거 값 제거
# ex) [0:99] -> [1:100] -> ... -> [n : n + 99]
pattern.append(index)
pattern = pattern[1:len(pattern)]
# LSTM이 출력한 500개의 음정을 확인해봅시다.
# 학습이 제대로 안 된 경우, 같은 음정만을 출력할수도 있습니다.
print('length of pred_out : ', len(pred_out))
print('pred_out : ', pred_out)
# LSTM 작곡 결과를 다시 우리가 들을 수 있게 MIDI 파일로 만들어줍니다.
# Code가 실행되면 Google Drive에 새로운 MIDI 파일이 생깁니다.
# 우리는 박자를 고려하지 않았기 때문에 모든 음이 같은 박자로 재생됩니다.
# LSTM 모델이 예측한 값들로부터 MIDI 파일을 만들어준다
offset = 0 # 음(Note/Chord)을 언제 들려줄지 정하는 timing offset (박자 정보를 대신 함)
# MIDI 파일 생성을 위한 빈 리스트
output_notes = []
# create note and chord objects based on the values generated by the model
# LSTM 모델 예측 값을 하나씩 처리
for pattern in pred_out:
# pattern이 Chord 일 때
if ('.' in pattern) or pattern.isdigit():
notes_in_chord = pattern.split('.') # ['8.1'].split('.') => ['8', '1']
notes = [] # Note 정보를 저장할 빈 리스트
# notes_in_chord의 텍스트를 Note 정보로 바꿔준다
for current_note in notes_in_chord:
new_note = note.Note(int(current_note)) # Text => 정수 => Note
new_note.storedInstrument = instrument.Piano() # 악기 정보 설정
notes.append(new_note) # notes 리스트에 더해준다
# Note => Chord
new_chord = chord.Chord(notes)
new_chord.offset = offset # 시간 정보 설정
output_notes.append(new_chord)
# pattern이 Note 일 때
else:
new_note = note.Note(pattern)
new_note.offset = offset
new_note.storedInstrument = instrument.Piano()
output_notes.append(new_note)
# 반복마다 offset을 증가시킨다 (고정 박자)
offset += 0.5
# Note/Chord => Stream => MIDI File
midi_stream = stream.Stream(output_notes)
midi_stream.write('midi', fp='./test_output.mid')
# 학습이 잘 된 LSTM 모델을 저장해줍시다.
# 모델은 h5 파일 형태로 저장됩니다
# 경로에 주의합시다
model.save('./Chopin_LSTM.h5')
# 모델을 불러오기 위해 지워줍니다
del model
# 저장되어 있는 모델을 불러오기 위한 load_model 함수
from keras.models import load_model
# 'model' 에 해당 모델을 불러옵니다
model = load_model('./Chopin_LSTM.h5')
- 다음 글