Saturday, March 12, 2016

자동작곡 [1/2] - LSTM Realbook (자동 재즈악보 생성)

이 포스팅은 지난 2월 제 영문 블로그에 올린 포스팅의 한국어 버전입니다.
인공지능 작곡이라는 키워드를 넣어서 사람들을 더 홀리고 싶은 생각이 드는군요. 아무튼...

LSTM 리얼북

요약

글자 단위 RNN (Char-RNN)과 단어단위 RNN을 Keras를 써서 재즈 악보에 적용했습니다. 총 2,846개의 리얼북 악보를 사용했고 이 악보를 텍스트 파일로 바꿔준 뒤에 LSTM을 학습시켰습니다. 그 중 다섯 곡을 추려서 짧은 음원으로 만들었습니다. 이 작업의 특징은 음악에 대한 모델링을 특별히 하지 않고 그저 코드를 기술한 텍스트 파일만 가지고도 컴퓨터가 음악적으로 말이 되는 코드 진행을 생성할 수 있다는 점 입니다.

간략한 소개

LSTM과 RNN

LSTM은 Long Short-Term Memory의 약자입니다. RNN을 구성하는 유닛에 몇가지 변화를 준 것인데 시계열 데이터 학습에 매우 뛰어납니다. 현재 구글/MS등 기업에서 음성 인식, 자동 번역에 활용하고 있습니다.

RNN

자세한 설명은 AI Korea의 번역을 참고하시기 바랍니다.

Char-RNN

Char-RNN은 텍스트를 단어 단위가 아닌 개별적인 문자 단위로 쪼개서 학습/예측하는 모델입니다. 스탠포드의 박사과정 Andrej Karpathy가 쓴 소개를 참고하시기 바랍니다.

리얼북 (Realbook)

리얼북은 각종 재즈 스탠다드 악보를 모아놓은 책입니다. 이 악보엔 코드 진행과 기본 테마 멜로디가 나와있습니다. 이 코드를 기반으로 연주자들이 즉흥 연주를 합니다.
Giant Steps의 리얼북 악보

밴드인어박스 (Band in a box)

자동 연주의 선구자격인 프로그램이죠. 1990년에 개발되었고 재즈 연습에 아주 좋은 소프트웨어입니다. 예를 들어 리얼북 코드를 입력하고 원하는 반복 횟수, 템포, 악기 구성을 선택하면 알아서 연주를 해줍니다.




알고리즘 구현 및 학습 과정

전처리

우선 제가 받은 악보 파일은 Band-in-a-box를 위한 포맷입니다. 이걸 텍스트로 바꿔주면 아래의 결과가 나옵니다.

1:1 0.0 8 2.82 C:maj C C
3:1 2.82 8 5.64 G:9 G9 C
5:1 5.64 4 7.05 C:9 C9 C
6:1 7.05 4 8.47 C:7 C7 C
7:1 8.47 4 9.88 F:maj F C
8:1 9.88 4 11.29 F:min7 Fm7 C

좀 숫자가 복잡하죠.  해석하면,

마디:박자(4비트 기준) 시작시간 길이(박자 단위) 끝나는 시간 코드 코드 키

의 포맷입니다. 굳이 이해하지 않으셔도 됩니다만..

우선 저는 모든 코드를 C키 기준으로 바꿨습니다. 어떤 곡이든 C키가 되도록 이조(transpose)를 한 것이죠.
한 마디에 보통 코드는 하나만 적혀있는 경우가 많습니다. 혹 두 개 이상의 코드가 적혀있다 하더라도 매 박자마자 코드를 적어놓지는 않죠. 즉...
| A:7  .   .   .    | A:7  .  C:maj    .   |
이런 식으로 되어있죠. 
이 포맷은 사람이 보기엔 편하지만 알고리즘을 짜기엔 조금 불편합니다. 저는 모든 4분음표마다 코드를 전부 적어 놓은 형태로 이걸 바꿨습니다. 이렇게 하면 더 단순한 모델링으로도 컴퓨터가 코드를 이해시킬 수 있습니다. 즉,
| A:7 A:7 A:7 A:7 | A:7 A:7 C:maj C:maj |
이렇게 바꾸었습니다.

그리고 각 악보의 시작과 끝엔 식별을 위한 단어를 추가했습니다. 이렇게 하면 컴퓨터가 악보의 시작과 끝을 어떻게 이해하는지 파악하기가 쉽습니다. 예를 들면 아주 짧은 두 마디짜리 악보를 생각하면 요렇게 되겠죠.
_START_ A:7 A:7 A:7 A:7 A:7 A:7 C:maj C:maj _END_
이제 이런 악보를 아주 많이 컴퓨터에게 보여주려고 합니다. 숫자를 정리하면..
  • 총 2,486개 악보
  • 총 1,259종류의 코드
  • 글자 (character)는 총 39 종류 (C,D,E,F,G,A,B,#,b,m,a,ji,n,s,u,...)
  • 악보 당 평균 1,239.78개 글자
학습에 사용한 텍스트 파일은 여기를 참고하시기 바랍니다.

Char-RNN and Word-RNN 글자 단위 및 단어 단위 RNN

글자는 총 39종류밖에 되지 않지만 단어는 (여기에서는 코드) 1,259종류나 되지요. 따라서 코드 단위로 학습을 하려면 더 많은 메모리가 필요합니다. 하지만 코드 하나는 여러 글자로 이루어져 있기 때문에 상대적으로 '길이'가 짧아도 많은 정보를 저장할 수 있습니다. 
LSTM의 장점은 기억력이 좋다는 것 입니다. 따라서 기존의 시계열 데이터 모델링 대비 곡의 구조를 잊지 않고 기억할 수 있습니다. 그래서 Char-RNN이 이렇게 간단하게 적용될 수 있는 것이구요. 
자세한 코드는 영문 포스팅 혹은 제가 올린 코드를 참고하시기 바랍니다.

Listen!

코드 진행에서 굵게 처리된 부분이 음원으로 만든 진행입니다.

결과 1: (글자-RNN, 1회 학습, 다양성 0.5)



C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 G:7 C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj

결과 2: (글자-RNN, 1회 학습, 다양성 1.2)


C:maj C:maj C:maj G#:min6 G#:min D#:7 D#:7 D:min7 C:maj/5 C:maj/5 C:maj/5 C:maj/5 C:maj/5 C:maj/5 C:maj/5 C:maj6/5 E:min7/4 E:min7/4 C:#ds(s11) C:sus4 D#:maj6 A#:maj A#:maj A#:maj A#:maj A:7 A:7 A:7 A:7 A:7 D:min7 D:min7 D:min7 D:hdim D:hdim C:hdim C:hdim C:hdim C:hdim C:hdim C:hdim C:hdim G:9 G:9 D:min7 D:min7 D#:dim G#:di9 G#:min7 G#:min7 G#:min7 G:sus4(b7) G:sus4(b7) G:sus4(b7) G:sus4(b7) G:sus4(b7) G:sus4(b7) G:sus4(b7) G#:9(s51) D:min D:min G:7/5 G:7/5 C:maj C:maj C:maj C:maj G:7(b9) G:7(b9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) D:maj7 D:maj7 G:maj(b7,b9,11,13) E:maj(b7,b9,11,13) E:9 E:9 E:9 E:9 E:9(s5,*5) E:9(b5,*5) E:min(b7,9,11) E:min(b7,9,11) E:min(b7,9,11) E:min7 E:min7 D:min6 D:min6 D:min6 A:min(b6,9,11) A:maj(b7,9,11,13) D:min7 D:min7 D:min7 A:9 A:min6 A:min A#:maj C#:min C#:min G#:maj G#:maj G#:maj G:9 G:7 G:7 G:7 G:7 G:7 G:7 G:7 C:sus4(b7,9) F:maj6 F:maj6 F:maj6 F:maj F:maj A:maj A#:maj A:hdim A:dim :mdn7 D:min7 D:min7 G:maj(b7,b9,11,13) C:6 C:9 F:9 A#s5us4(b7) A#:sus4(b7) B:sus4(b9) B:sus4(b7,9) B:sus4(b7,b5,49) B:sus4(b7) B:sus4(b7) B:sus4(b7) F:maj7 F:maj7 F:maj7 F:maj7 F:maj7 F:maj7 F:maj7 F:maj7 F:maj7 D#:maj7 D:dim D:dim F:maj7 F:maj6 D:min7 D:min7 C:maj C:maj F:maj F:maj F:maj F:aug F:7 F:7 F:7 C:7 G:min D:hdim G:sus4(b7,9) G:sus4(b7,9) C:maj7 C:maj7 C:maj7 C:maj7 C:maj7 F:maj9 F:maj7 F:maj7 F:maj7 F#:hdim F#:hdim F:min7 E:7(s5,*5) A:7(s9) A:7(s9) A:7(b9) A:7(b9) A:7(b9) D:min7 D:min7 E:min(7) E:min(7) E:min7 A:min7 A:min7 A:min7 A:min7 

결과 3: (글자-RNN, 22회 학습, 다양성 1.2)


C:7/5 C:7/5 C:7 C:7 F:maj6 F:maj6 F#:dim F#:dim C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:6(9) C:maj E:7(b9) E:7(b9) E:7(b9) E:7(b9) A:min(6,9) A:min(6,9) A#:min(6,9) A#:min(6,9) A#:min(6,9) A#:min(6,9) A#:min(6,9) A#:min(6,9) A:(1,b3,b5) A:(1,b3,b5) G#:(1,b3,b5) G#:(1,b3,b5) G:min(b7,9,11) G:min(b7,9,11) G:min(b7,9,11) G:min(b7,9,11) G#:min(b7,9,11) F#:min(b7,9,11) F#:min(b7,9,11) C:min9 C:min9 B:7(s5,*5,s9) B:7(s5,*5,s9) B:7(s5,*5,s9) C:6(9) C:6(9) C:6(9) A:7 A:7 D:min7 D:min7 D:min7/9 D:min(b7,9,11)/b5 E:min(b7,9,11)/b3 E:min7(s5,*5)/b7 E:min(b7,9,11)/b7 E:min(b7,9,11)/b7 E:min(b7,9,11)/b7 E:min7 E:min7 E:min7 E:min7 A:7(b9) A:7(b9) D:min D:min D:min D:min G:9 G:9 C:maj C:maj F:9 F:9 F:9 F:9 C:maj6 C:maj6 C:maj6 C:min6 C:min6 F:9 F:9 F:9 F:9 D:min7 D:min7 D:min7 D:min7 D:min7 C#:maj(b7,9,11,13)/b7 G#:maj(b7,9,11,13) G#:maj(b7,9,11,13) C:maj7 C:maj7 E:min7 E:min7 D#:min7 D#:min7 D#:min7 E:min7 A:7(b9) A:7(b9) D:min D:min D:min D:min D:min D:min D:min D:min _END_ _START_ A:min A:min F:maj F:maj B:7 B:7 E:maj E:maj E:maj B:9 B:9 B:maj B:maj D:min(b7,9,11) D:min(b7,9,11) D:min(b7,9,11) D:min(b7,9,11) D:min(b7,9,11)

결과 4: (단어-RNN, 8회 학습, 다양성 0.5)


C:maj C:maj A:min A:min D:min7 D:min7 G:7(b9) G:7(b9) C:maj C:maj C:maj C:maj A:min7 A:min7 A:min7 A:min7 D:9 D:9 D:9 D:9 D:9 D:9 D:9 D:9 D:7 D:7 D:7 D:7 D:min7 D:min7 D:min7 D:min7 G:7 G:7 G:7 G:7 C:maj C:maj C:maj C:maj C:7 C:7 C:7 C:7 F:maj F:maj F:maj F:maj F:min F:min F:min F:min C:maj C:maj C:maj C:maj 

결과 5: (단어-RNN, 8회 학습, 다양성 1.2)





C:maj/3 C:maj/3 C:maj C:maj C:maj C:maj C:maj C:maj C:maj C:maj A#:9 F:maj7 A#:7 A#:7 C:maj C:maj C:maj/3 C:maj/3 F:maj6 F:maj6 C:maj C:maj C:maj C:maj C:maj C:maj G:min7 G:min7 G:min7 G:min7 F:maj F:maj F:maj F:maj D:min7 D:min7 D:min7/4 G:sus4(b7) G:min9 G:min9 G:min9 F#:(1,3,b5,b7,9,13) C:6(9) G:sus4(b7,9) E:7(s5,*5,s9) E:7(s5,*5,s9) A:min9 A:min9 A:min9 A:min9 D:min7 D:min7 D:7(b9) D:9 G#:9(s11) G:(1,4,5,b7,9,11,13) G:aug(b7,9) G:9(s5,*5) C:maj6 C:maj6 E:min7(s5,*5)/b7 A:7(s9,s11,b13) 

결론

  • 음악적인 지식을 전혀 이용하지 않고 그저 코드 진행을 보여주는 것 만으로도 의미있는 코드진행을 생성시킬 수 있습니다.
  • 다음 편인 LSTM 메탈리카가 궁금하시면 영문 포스팅을 참고하세요. 
  • 이 연구는 CSMC2016 학회에 제출되었습니다.

2 comments:

  1. 안녕하세요, git에 올린걸 통해서 악보 작성을 실행중인데, 올려주신 걸로 실행시키면
    NameError Traceback (most recent call last)
    in ()
    148
    149 if __name__=='__main__':
    --> 150 main(character_mode=False)

    in main(character_mode)
    73
    74 # text to vectors
    ---> 75 X, y = vectorize()
    76 # build the model: stacked LSTM
    77 model = get_model(maxlen, num_chars)

    in vectorize()
    36 def vectorize():
    37 print('Vectorization...')
    ---> 38 X = np.zeros((len(sentences), maxlen, num_chars), dtype=np.bool)
    39 y = np.zeros((len(sentences), num_chars), dtype=np.bool)
    40 for i, sentence in enumerate(sentences):

    NameError: name 'sentences' is not define

    라고 에러가 뜨네요. sentences랑 sentence가 둘 다 있어서 변수 설정을 어떻게 해야할지 헷갈리는데, 의도하신 sentences가 뭔지 알 수 있을까요?

    ReplyDelete
    Replies
    1. sentences는 main 코드 내에서 자동으로 생성됩니다.
      저는 vectorize함수에 인수로 필요한 변수들을 넣어주니 잘 작동했습니다.

      Delete