Composing music with recurrent neural netwroks(번역)

원문:

https://github.com/hexahedria/biaxial-rnn-music-composition

 

 

067BF5C6-8569-41F1-B68F-ABF7A2F67227.png

 

 

 

Reccurent Neural Networks

 

지금까지 배운 neural network는 feedforward network, 즉, input이 들어오면 한 방향으로 output을 내는 구조이다.

(정보가 한 방향으로만 흐른다.)

Recurrent neural network는 hidden layer의 각 layer 마다 얻은 output을 다음 layer의 추가적인 input으로 집어 넣는 것이다.

Hidden layer의 각 노드들은, 바로 전 레이어의 output 뿐만 아니라,

자기가 속한 레이어의 전 time step에서의 output을 함께 input으로 받게 된다.

 

56D49BE4-9042-4490-A67E-49073A920908.pngC77D119A-9CF6-49F2-9C58-A6F74FBFA205.png

 

 

이 때, 레이어들의 각 horizontal line은 하나의 time step을 진행중이다.

각 hidden layer 들은 input을 전 layer와 한 time step 전의 자기 자신으로 부터 input을 받게 된다.

Recurrent neural network의 장점은 네트워크로 하여금 단순한 버전의 메모리를(최소한의 오버헤드를 갖는) 갖게 한다는 점이다.

이것은 input과 output의 길이를 다양하게 할 수 있도록 해준다. 우리는 input을 한번에 하나씩 feed할 수 있고, 네트워크로 하여금 그것들을 결합해서 사용하도록 할 수 있다. (각 time step에서 저장한 state를 활용해서)

 

문제는 이 메모리가 short-term 이라는 점이다. 한번의 time step에서 output으로 얻은 값은 다음번에 input이 되는데, 다음번에도 같은 값이 나오지 않는 이상 이 값은 사라진다.

이런 문제를 해결하기 위해, 우리는 기존의 일반적인 node 대신에 Long Short-Term Meomory(LSTM) node를 사용한다. 이는 여러번의 time step 동안 ‘memory cell’ 값이 전해지도록 한다.(이 값은 매 tick 마다 더해지거나 빼진다.)

 

4CC9633D-E2C0-41CE-8ACB-4DE8CDECEFF6.png

이 memory cell data 가 activation output과 평행해서 보내진다고 생각할 수 있다.

 

Training Neural Networks

Cost를 정의한 후, Backpropagation을 통해서 cost의 gradient를 찾아내고, optimization method를 통해서 최소화한다.

이러한 optimization method에는 여러가지가 있다.

stochastic gradient descent,

Hessian-free optimization,

AdaGrad,

AdaDelta

 

5B6F3861-2E6D-4E22-B33B-9EDAF87CCBE7.gif

 

 

 

 

 

자, 그럼 이제 이걸로 어떻게 음악을 만들것인가?

 

다른 사람들이 어떤 시도를 했는지 살펴보자.

 

Recurrent neural networks for folk music generation

https://highnoongmt.wordpress.com/2015/05/22/lisls-stis-recurrent-neural-networks-for-folk-music-generation/

: abc notation을 학습 시켜서 (textual representation을 학습시킴) folk song의 코드진행을 만들어냄.

A first look at music composition using LSTM recurrent neural networks
: LSTM을 사용해서 blues improvisation을 구현.
sequence 들은 같은 코드들의 set을 가지고 있고,
network의 구성을 보면 note들(음악 노트)마다 하나의 output 노드를 가지고 있다.
(그 노트가 연주될 확률을 output으로 내는 것이다.)
temporal structure를 배우게 된다는 점에서 흥미롭지만, output이 매우 제한적이다.
또한, playing a note 와 holding a note의 구분이 없다. (note를 hold하고 유지하는 것을 표현하지 못한다.)
Modeling Temporal Dependencies in High-Dimensional Sequences: Application to Polyphonic Music Generation and Transcription
: 2개의 파트로 구성된 network 사용
 1) time dependeny를 핸들링하는 RNN
     : restricted Boltzmann machine 의 parameter로 사용하기 위한 output을 만들어냄.

: 어떤 노트가 다른 노트와 함께 연주되어야 하는지에 대한 조건 분포를 모델링함.

좋게 들리는 음악 생성, 하지만 time signature에 대한 개념이 없음. 두개의 코드만을 연주함.

 

 

나의 네트워크 디자인을 위해서, 내가 원하는 특징들을 나열해보았다.

– time signature에 대한 개념 탑재.

– time-invariant : 시간불변성. 무한하게 작곡을 계속했으면 좋겠다. (네트워크가 매 time step 마다 동일해야 한다.)

– note-invariant : 음표불변성. 만드는 음악에 대한 transpose가 자유롭게 되었으면 좋겠다. (그러면서도 기본적인 요소들이 유지되도록) 뉴럴넷의 structure가 각 노트들에 대해서 동일했으면 좋겠다.

– 여러개의 노트가 동시에 연주되었으면 좋겠다. 그리고 연관된 코드들을 선택할 수 있게 했으면 좋겠다.

– 같은 노트가 반복되는 걸 허용했으면 좋겠다. 예를 들으 도를 두번 누르는 거랑 도를 한번 누르고 유지하는 거랑 구분했으면 좋겠다.

 

기존의 RNN-base 자동작곡 방식들은 시간에 따라 불변한다. time step들이 네트워크를 한번 iteration하는 것이기 때문이다.

하지만 보통 그것들은 note에 따라 불변하지는 않다. 주로 특정 output node가 하나의 note를 의미하는 방식이 때문이다.

따라서, 예를 들어 노트를 한 음 transpose 한다면 전혀 다른 output을 얻게 되는 것이다. 하지만 transpose를 해도 같은 output을 얻는 것도 필요한 기능이다. (음악에서는 음표들의 상대적 관계가 중요하기 때문이다.)

 

다양한 방향에 대해서 invariant 한 성질을 갖는 뉴럴네트워크가 있다.(근래 많은 곳에서 쓰이고 있다.) Convolutional Neural Networks for image recognition 이다.

이 네트워크는 하나의 convolution kernel을 배운 후에,

같은 convolution kernel 을 input image의 매 픽셀마다 적용하는 방식으로 작동한다.

 

 

438A1BAD-E2B7-4731-BA53-B13C8761F2FF.jpg

 

 

 

만약 우리가 convolution kernel을 recurrent neural network로 교체하면 어떻게 될까?

그럼 각 pixel은 주변 영역으로부터 input을 받는 각자의 neural network를 갖게 될 것이다.

각 neural network는 자연히 각자의 memory cell과 시간에 따른 recurrent connetion을 갖게 될 것이다.

 

자, 그럼 픽셀을 노트로 바꾸어보자.

우리가 각각의 output note 마다 동일한 recurrent neural network 의 stack을 만든 후에, 각각의 RNN에 주변 local neighborhood(예를 들어 한 옥타브 이내라던지)를 input으로 준다면, 우리는 시간과 note에 invariant한 시스템을 만들 수 있다.

이 네트워크는 상대적인 인풋을 활용할 수 있다. (시간과 note 두 방향 모두)

456CE1C0-20CF-4A03-9C80-04E52A87F0DE.png

 

 

 

 

Note: 난 여기서 시간축을 돌렸다. 시간축 (time step)은 page로 부터 튀어 나오고, recurrent connection도 마찬가지이다. 각 슬라이스는 기본 RNN의 복사본이라고 보면 된다. 그림에서 보면 각 layer는 위와 아래의 한 note부터 input을 받고 있다.

즉, 실제 네트워크는 각 방향에서 12개의 note로 부터(한 옥타브의 반음계 개수)  input을 받고 있다.

 

하지만 이 네트워크에도 여전히 문제가 있다. Recurrent connection이 시간축에서는 pattern을 갖지만, 좋은 chord를 얻을 수 있는 mechanism은 없다. : 각 노트의 output은 다른 노트의 output과 완전히 독립적이다.

위에서 언급한 RNN-RBM combination을 참고해서 이 부분을 개선해보자. 네트워크의 첫번째 파트는 시간을 다루도록 하고, 두번째 파트는 좋은 chord를 만들어 내도록 한다. 하지만 RBM은 여러 output들에 대한 하나의 조건 분포를 만들어내고, 이건 note 하나마다 하나의 network를 사용하는 방식과는 호환하지 않는다.

그래서 나는 ‘biaxial RNN’을 만들기로 했다. 이 아이디어는 두개의 좌표계를 만드는 것이다(거기에 추가로 하나의 pseudo-axis). time axis와 note axis. (그리고 direction-of-computation pseudo-axis 계산방향의 가상좌표계) 각 recurrent layer는 input을 output으로 바꿈과 동시에, recurrent connection을 이 좌표계들중 하나를 따라 보낸다. 하지만 모든 recurrent layer가 같은 좌표계를 따라 보낼 필요는 없다!

 

 

 

5F1016D9-9314-432D-9BC1-DCDB2C43C0C9.png

 

그림에서 보면 첫 두 layer는 time step을 따라 연결되어 있지만, note들에 대해서는 독립적이다. 반면 마지막 두 layer는 note들 사이에는 연결되어 있고, 시간(time step)에 대해서는 독립적이다. 이 둘을 합치면 시간과 note 간격 둘 모두에 대한 pattern을 얻을 수 있다.  (invariance 불변성을 해치지 않음과 동시에!)

 

이 dimension들 중 하나를 없애면 약간 더 이해하기 쉽다.

275A5C65-BA0C-4B31-9E67-98448368D3ED.png

 

시간에 대한 연결은 loop로 나타냈다. 루프가 항상 1 time step 만에 돌아온다는 것을 기억해야 한다. time t 에서의 output은 time t+1에서의 input이 된다.

 

 

Input and Output details

나의 네트워크는 지금까지 살펴본 이런 구조에 기초한다. 하지만 실제 적용은 좀더 복잡하다.

먼저, 매 time step마다 첫번째 time-axis 레이어에 다음과 같은 인풋이 필요하다. ( [ ] 안의 숫자는 인풋 벡터의 element의 갯수이다. )

 

1) Position [1] : 현재 note의 MIDI 노트값. 이 노트가 얼마나 높은지에 대한 대략의 감을 잡기 위해 사용되고, 노트들 간의 차이점을 구분하기 위해서도 사용된다. (높은 노트들은 멜로디이고, 낮은 노트들은 코드이다.)

 

2) Pitchclass [12] : 현재 노트의 포지션에서는 1이 될 것이다. A를 0으로 해서 반음마다 1씩 증가하고, 다른 모든 경우에는 0이 된다. 좀 더 익숙한 코드를 고르기 위해 사용된다. (C major코드를 얻는 것이 Eb major 코드를 얻는 것보다 더 익숙하다.)

 

3) Previous Vicinity [50] : 바로 전 time step의 노트들을 둘러싸는 맥락을 준다. 각 방향마다 한 옥타브씩.

현재 노트에서부터 오프셋 i 에서의 노트가 바로 전 time step에서 연주되었으면, 2( i + 12 ) 번째의 값은 1이 되고, 연주되지 않았으면 0 이 된다.

바로 전 time step에서 노트가 명확히 연주되었으면, 2( i + 12 ) + 1 에서의 값은 1이 된다.

아니면, 0이 된다.

(따라서, 노트를 연주하고 유지하고 있으면, 첫번째 time step 에서는 양쪽 모두 1을 갖고, 두번째 time step에서는 2( i + 12 )에서만

1이 된다. 만약 한 노트를 반복연주하면,  2( i + 12 ) + 1도 1을 갖게 된다.

 

4) Previous context [12] : 인덱스 i 에서의 값은, 바로 전 time step에서 어떤 노트 x가(mod 12) 연주된 횟수이다.

만약 이번노트가 C이고 지난번 timestep에는 2개의 E가 있었다면, index 4에서의 값은 2가 된다. (E가 C보다 4개의 반음 위이기 때문에)

 

5) Beat [4] : measure 내에서의 (4/4 박자 곡이라고 가정하고) position을 효과적으로 binary representation으로 나타낸 것. 각 행은 하나의 beat input을 나타내고, 각 행은 time step을 나타낸다. 기본적으로 다음 패턴을 반복한다.

528FF5B4-1E22-4C00-BA59-B523B44890A0.png

그러나 [0, 1] 대신에 [-1, 1]로 scale된다.

 

다음으로는 첫 hidden LSTM stack이 있다. 이 LSTM stack은 time-axis에 대해 recurrent connection을 가지고 있는 LSTM들로 구성되어 있다. 마지막 time-axis layer output 는 어떤 노트의 state (시간 pattern을 나타내는)을 output으로 내놓는다. 두번째 LSTM stack은 note axis를 따라서 recurrent 하는데, 낮은 노트부터 높은 노트들까지 스캔한다. 각 노트 단계 (note step) 마다 (time-step과 같음) 다음의 내용을 input으로 받는다.

1) 이전(previous) LSTM stack으로 받은 note-state 벡터

2) 이전 노트(반음 낮은)가 선택되어 연주될 것인지에 대한 값(0 또는 1) – 이전 note-step에 기초함/0에서 시작

3) 이전 노트(반음 낮은)가 선택되어 이어질 것인지에 대한 값(0 또는 1) – 이전 note-step에 기초함/0에서 시작

 

가장 마지막 LSTM 후에는, 단순하고 non-recurrent한 output layer가 있는데, 이것은 두 값을 내놓는다.

1) 연주 가능성 : 이 노트가 선택되어 연주될 것인지에 대한 확률

2) 이어짐 가능성 : 이 노트가 연주된 상태라는 가정하에, 이어질 것인지에 대한 확률.  (이 값은 held notes가 계속 이어지는지를 결정하는 데에만 쓰인다.)

 

 

모델은 Theano에서 구현되었다. GPU최적화 코드로 neural network를 컴파일하고 gradient를 자동으로 계산해주는 파이썬 라이브러리이다.

 

트레이닝하는 동안, 우리는 랜덤하게 선택된 짧은 음악 segment의 batch들을 feed한다. 그 후에 우리는 output 확률들을 가지고 cross-entropy를 계산한다. 주어진 output 확률들을 가지고 알맞은 output을 생성하는 멋진 방법이다. 확률들이 너무 작지 않도록 로그함수를 이용한 몇가지 조작을 한 후에, 최소화 문제(minimization problem)가 되도록 그것을 무효화하고, AdaDelta optimizer에 cost로 집어 넣고 우리의 weight을 optimize 하도록 한다.

우리는 매 time step에서 정확히 어느 output을 골라야 하는지 우리가 이미 알고 있다는 사실을 이용해서 training을 더 빠르게 진행할 수 있다. 기본적으로, 모든 노트를 한 batch로 해서 time-axis layer를 train시킬 수 있다. 그 후엔 모든 time을 한 batch로 재구성해서 모든 note-axis layer들을 train시킬 수 있다. 이 방식으로 우리는 GPU를 효율적으로 활용할 수 있다.(GPU는 커다란 matrix의 계산에 적합하다.)

Overfit 문제를 막기 위해, 우리는 dropout이라는 걸 사용한다. 매 training step 마다, 각 레이어에서 hidden 노드의 반을 랜덤하게 골라서 제거하는 방법을 의미한다. 이 방식을 사용하면 노드들이 서로에게 연약하게 의존해서 끌리는 대신 개별적인 특성을 발전시키게 된다. (각 레이어의 output에 mask를 곱하는 식으로 구현한다. 노드들은 해당하는 time step의 output에서 0으로 만듦으로써 제거된다.)

작곡하는 과정에서는, 우리는 모든 것을 효과적으로 묶을 수는 없다. 매 time step에서, 우리는 먼저 time-axis 레이어를 한 tick 마다 실행하고, note-axis 레이어의 전체 recurrent sequence를 실행함으로써, 다음 tick에서 time-axis 레이어에 어떤 인풋을 넣을지 결정하도록 한다. 이 과정은 composition이 느리게 작동하게 한다. 게다가 우리는 correction factor를 추가해서 training 동안 생겨난 dropout에 대해서 해명하도록(보완하도록) 해야 한다. 실질적으로는, 이것은 각 노드의 output에 0.5를 곱하는 걸 의미한다. 이것은 네트워크에 활성화된 노드의 수가 너무 많음으로 인해서 overexcited되지 않도록 하기 위함이다.

 

나는 AWS의 g2.2xlarge 인스턴스를 사용해서 모델을 train 했다. 나는 ‘spot instance’를 사용해서 돈을 절약할 수 있었다.

AWS 사용할 때 자주 저장하고, ‘Delete on Termination’ 을 uncheck하는 것도 잊지 말아라!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s