# 신경망 모델을 사용한 멜로디 시퀀스 학습

Jung Choi
Music and Audio Computing lab, GSCT, KAIST

### 오늘 튜토리얼을 진행하기 위해 먼저 아래 주소에 접속해주세요.¶

### https://maclab-kaist.github.io/DeepArt/¶

## Today's Goal:

## 데이터를 통해 멜로디의 패턴을 학습하고,

## 주어진 인풋에 대해 이어지는 멜로디를 생성하는 모델

### 멜로디의 생성 어프로치¶

1. 실제 음악 이론과 작곡가의 멜로디 창작 스타일에 대한 분석을 토대로 직접 작곡 규칙을 설계하는 방법
2. 악곡 데이터에서 통계적인 분석을 통해 생성 모델을 학습하는 방법 (다양한 기계학습 방법론)

이번 튜토리얼에서는 딥러닝의 토대가 되는 인공신경망 아키텍쳐 중 시퀀스 학습을 위해 만들어진 RNN 구조를 활용해서 접근해보고자 합니다.

1. Prepare Library/Data

2. Tensorflow Quick Review

3. Tensorflow Basic RNN Model

4. Data Preprocessing

    - Different approaches on data representation
    - Melody vector approach

5. Model Construction

6. Future Works

### 1. Prepare Library/Data¶

#### - Install libraries.¶

#### - Download the dataset.¶

#### - Extract information from the dataset.¶

#### Install libraries.¶

- music21
  http://web.mit.edu/music21/
  pip install music21

- numpy

#### Downloading the dataset.¶

- 단순한 포크송 형태의 기본적인 미디 데이터셋으로 Nottingham Database를 사용
  (1200개의 영국/미국 포크곡으로 이루어져 있는 데이터셋/모든 미디파일은 멜로디, 화성의 두개의 트랙으로 구성)
  https://github.com/jukedeck/nottingham-dataset

#### Parsing midi files into data form.¶

In [4]:

    import music21
    import numpy as np
    import os
    import pickle

In [5]:

    data_path = '~/Google Drive/data/nottingham-dataset/MIDI/melody/'

In [6]:

    file_name = 'jigs273.mid'

In [7]:

    midi_obj = music21.converter.parse(data_path + file_name)

#### Extracting note information from midi file.¶

In [15]:

    def create_mel_data_each_file(midi_obj):
        mel_data = dict()

        for n in midi_obj.flat.getElementsByClass(music21.chord.Chord):
            if n.offset not in mel_data:
                mel_data[n.offset] = dict()
            mel_data[n.offset]['offset'] = n.offset
            for p in n.pitches:
                mel_data[n.offset]['note'] = p.midi

        for n in midi_obj.flat.getElementsByClass(music21.note.Note):
            if n.offset not in mel_data:
                mel_data[n.offset] = dict()
            mel_data[n.offset]['offset'] = n.offset
            prev_p = 0
            for p in n.pitches:
                if prev_p < p.midi:
                    mel_data[n.offset]['note'] = p.midi
                prev_p = p.midi

        return mel_data

모든 곡에 대해서 데이터를 가져오기위해 폴더안에 있는 모든 파일의 리스트를 먼저 준비합니다.

In [16]:

    file_list = []

    for file_name in os.listdir(os.path.expanduser(data_path)):
        if file_name.endswith('.mid') or file_name.endswith('.midi'):
            file_list.append(data_path + file_name)

모든 곡들에 대해서 실행해봅시다.

In [19]:

    mel_arr_list = []
    for file_name in file_list:
        midi_obj = music21.converter.parse(file_name)
        mel_data = create_mel_data_each_file(midi_obj)
        mel_arr = []
        for key, value in sorted(mel_data.items()):
            mel_arr.append(mel_data[key])
        mel_arr_list.append(mel_arr)

Store data as a file.

In [20]:

    preprocessed_dir = "./preprocessed_data/"
    if not os.path.exists(preprocessed_dir):
        os.makedirs(preprocessed_dir)

    with open(preprocessed_dir + "mel_arr_list.p", "wb") as fp:
        pickle.dump(mel_arr_list, fp)

### 2. Tensorflow quick review¶

#### - What is tensorflow?¶

#### - Installing tensorflow on your mac.¶

#### - Understanding basic pipeline.¶

#### Installing tensorflow on your mac.¶

    pip install --upgrade tensorflow==1.3.0

#### Understanding basic pipeline - simple neural network example¶

simple neural network example

In [43]:

    # import tensorflow
    import tensorflow as tf

    # Import MNIST data
    from tensorflow.examples.tutorials.mnist import input_data
    mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

    Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
    Extracting /tmp/data/train-images-idx3-ubyte.gz
    Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
    Extracting /tmp/data/train-labels-idx1-ubyte.gz
    Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
    Extracting /tmp/data/t10k-images-idx3-ubyte.gz
    Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
    Extracting /tmp/data/t10k-labels-idx1-ubyte.gz

In [44]:

    # Parameters
    learning_rate = 0.1
    num_steps = 500
    batch_size = 128
    display_step = 100

    # Network Parameters
    n_hidden_1 = 256 # 1st layer number of neurons
    n_hidden_2 = 256 # 2nd layer number of neurons
    num_input = 784 # MNIST data input (img shape: 28*28)
    num_classes = 10 # MNIST total classes (0-9 digits)

    # tf Graph input
    X = tf.placeholder("float", [None, num_input])
    Y = tf.placeholder("float", [None, num_classes])

In [45]:

    # Store layers weight & bias
    weights = {
        'h1': tf.Variable(tf.random_normal([num_input, n_hidden_1])),
        'h2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2])),
        'out': tf.Variable(tf.random_normal([n_hidden_2, num_classes]))
    }
    biases = {
        'b1': tf.Variable(tf.random_normal([n_hidden_1])),
        'b2': tf.Variable(tf.random_normal([n_hidden_2])),
        'out': tf.Variable(tf.random_normal([num_classes]))
    }

In [46]:

    # Create model
    def model_simple_nn(x):
        # Hidden fully connected layer with 256 neurons
        layer_1 = tf.add(tf.matmul(x, weights['h1']), biases['b1'])
        # Hidden fully connected layer with 256 neurons
        layer_2 = tf.add(tf.matmul(layer_1, weights['h2']), biases['b2'])
        # Output fully connected layer with a neuron for each class
        out_layer = tf.matmul(layer_2, weights['out']) + biases['out']
        return out_layer

In [47]:

    # Construct model
    logits = model_simple_nn(X)

    # Define loss and optimizer
    loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(
        logits=logits, labels=Y))
    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
    train_op = optimizer.minimize(loss_op)

    # Evaluate model (with test logits, for dropout to be disabled)
    correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax(Y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

    # Initialize the variables (i.e. assign their default value)
    init = tf.global_variables_initializer()

In [48]:

    # Start training
    with tf.Session() as sess:

        # Run the initializer
        sess.run(init)

        for step in range(1, num_steps+1):
            batch_x, batch_y = mnist.train.next_batch(batch_size)
            # Run optimization op (backprop)
            sess.run(train_op, feed_dict={X: batch_x, Y: batch_y})
            if step % display_step == 0 or step == 1:
                # Calculate batch loss and accuracy
                loss, acc = sess.run([loss_op, accuracy], feed_dict={X: batch_x,
                                                                     Y: batch_y})
                print("Step " + str(step) + ", Minibatch Loss= " + \
                      "{:.4f}".format(loss) + ", Training Accuracy= " + \
                      "{:.3f}".format(acc))

        print("Optimization Finished!")

        # Calculate accuracy for MNIST test images
        print("Testing Accuracy:", \
            sess.run(accuracy, feed_dict={X: mnist.test.images,
                                          Y: mnist.test.labels}))

    Step 1, Minibatch Loss= 10820.4658, Training Accuracy= 0.320
    Step 100, Minibatch Loss= 225.8792, Training Accuracy= 0.906
    Step 200, Minibatch Loss= 102.7450, Training Accuracy= 0.828
    Step 300, Minibatch Loss= 30.3271, Training Accuracy= 0.922
    Step 400, Minibatch Loss= 51.0396, Training Accuracy= 0.898
    Step 500, Minibatch Loss= 49.6638, Training Accuracy= 0.828
    Optimization Finished!
    Testing Accuracy: 0.8301

### 3. Tensorflow basic RNN model¶

#### Recurrent Neural Network¶

### 우리는 Char-RNN 모델을 만들어 봅시다.¶

In [ ]:

    import os
    import numpy as np
    import tensorflow as tf
    import tensorflow.contrib.rnn as rnn
    import tensorflow.contrib.layers as layers
    from text_loader import TextLoader

In [ ]:

    num_layers  = 3
    hidden_size = 512
    batch_size  = 200
    max_length  = 30
    learning_rate = 0.001

    loader = TextLoader("data/hamlet.txt")
    vocab_size = len(loader.vocab)

    if not os.path.exists("checkpoints/"):
        os.makedirs("checkpoints/")

#### Recurrent Neural Network¶

In [ ]:

    X = tf.placeholder(tf.int32, [None, max_length])
    y = tf.placeholder(tf.int32, [None, max_length]) # [N, seqlne]

    x_one_hot = tf.one_hot(X, vocab_size)
    y_one_hot = tf.one_hot(y, vocab_size)            # [N, seqlen, vocab_size]

    cells = [rnn.BasicLSTMCell(hidden_size) for _ in range(num_layers)]
    cells = rnn.MultiRNNCell(cells, state_is_tuple=True)

    initial_state = cells.zero_state(batch_size, tf.float32)
    outputs, _ = tf.nn.dynamic_rnn(cells, x_one_hot,
        initial_state=initial_state, dtype=tf.float32)

    outputs = tf.reshape(outputs, [-1, hidden_size]) # [N x seqlen, hidden]
    logits = layers.linear(outputs, vocab_size)      # [N x seqlen, vocab_size]
    y_flat = tf.reshape(y_one_hot, [-1, vocab_size]) # [N x seqlen, vocab_size]

In [ ]:

    loss_op = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y_flat) # [N x seqlen]
    loss_op = tf.reduce_mean(loss_op)

    opt = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss_op)

    y_softmax = tf.nn.softmax(logits)         # [N x seqlen, vocab_size]
    pred = tf.argmax(y_softmax, axis=1)       # [N x seqlen]
    pred = tf.reshape(pred, [batch_size, -1]) # [N, seqlen]

In [ ]:

    saver = tf.train.Saver()

    sess_config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True))
    with tf.Session(config=sess_config) as sess:
        sess.run(tf.global_variables_initializer())
        for step in range(30000):
            batch_X, batch_y = loader.next_batch(batch_size, max_length)
            loss, _ = sess.run([loss_op, opt], feed_dict={X: batch_X, y: batch_y})

            if (step+1) % 500 == 0:
                print("{:08d} step, loss:{:.4f}".format(step+1, loss))

                random = np.random.randint(0, batch_size)
                results = sess.run(pred, feed_dict={X: batch_X})
                words = [loader.words[word] for word in results[random]]
                print("".join(words))

            if (step+1) % 5000 == 0:
                saver.save(sess, "basic_checkpoints/char-rnn_"+str(step+1))

Prediction을 위해서는 batch_size를 1로 만들어줄 필요가 있습니다.

In [ ]:

    import numpy as np
    import tensorflow as tf
    import tensorflow.contrib.rnn as rnn
    import tensorflow.contrib.layers as layers
    from text_loader import TextLoader

    num_layers  = 3
    hidden_size = 512
    batch_size  = 1
    max_length  = 1000

    loader = TextLoader("data/hamlet.txt")
    vocab_size = len(loader.vocab)

In [ ]:

    X = tf.placeholder(tf.int32, [None, None])
    x_one_hot = tf.one_hot(X, vocab_size)

    cells = [rnn.BasicLSTMCell(hidden_size) for _ in range(num_layers)]
    cells = rnn.MultiRNNCell(cells, state_is_tuple=True)

    initial_state = cells.zero_state(batch_size, tf.float32)
    outputs, states = tf.nn.dynamic_rnn(cells, x_one_hot,
                                        initial_state=initial_state, dtype=tf.float32)

    outputs = tf.reshape(outputs, [-1, hidden_size])
    logits = layers.fully_connected(outputs, vocab_size,
                                    activation_fn=None)
    y_softmax = tf.nn.softmax(logits)
    pred = tf.argmax(y_softmax, axis=1)
    pred = tf.reshape(pred, [batch_size, -1])

In [ ]:

    sentence = list()
    # 시작 글자 생성
    sentence += loader.X[:10].tolist()
    print("Start with:", "".join([loader.words[char] for char in sentence]))

    saver = tf.train.Saver()
    sess_config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True))
    with tf.Session(config=sess_config) as sess:
        sess.run(tf.global_variables_initializer())
        saver.restore(sess, "checkpoints/char-rnn_30000")

        # 매 이터레이션마다 글자 하나씩 생성
        pred_char, state = sess.run([pred, states], feed_dict={X:[sentence[:]]})
        for i in range(max_length):
            # 이전 스텝에 갖고 있는 state 값을 다음 스텝에 넣어줌
            pred_char, state = sess.run([pred, states],
                feed_dict={X:[[sentence[-1]]], initial_state: state})
            sentence.append(pred_char[0][-1])

    sentence = [loader.words[char] for char in sentence]
    print("".join(sentence))

### 4. Data Preprocessing¶

#### - Different approaches on data representation¶

#### - Melody vector approach¶

우리의 목표 : 신경망으로 하여금 멜로디 시퀀스의 진행 특성을 학습하도록 해서, 새로운 멜로디 인풋이 들어왔을 때 그 뒤에 이어지는 음의 시퀀스가 생성되도록 하는 것.

"어떤 순서에 따라 어떤 입력이 들어올 때 어떤 출력값을 내어 놓는지" 에 대한 논리를 학습.

### '그렇다면 어떤 시퀀스를 학습시킬것인가?' 에 대한 문제¶

#### (1) Pianoroll 형식의 데이터¶

가장 쉽게 접근할 수 있는 데이터 형식입니다. 세로축을 88개의 음정으로 구성하고 가로축을 16분 음표 단위의 시간으로 구성하는 형태:
http://www.hexahedria.com/2015/08/03/composing-music-with-recurrent-neural-networks/

#### (2) 노트 시퀀스 형식의 데이터 - 노트의 음정, 음의 시작지점(offset)¶

펫메시니의 연주부분(기타솔로)을 가지고 노트 정보의 시퀀스로 데이터를 정의한 예제:
https://github.com/jisungk/deepjazz

#### (3) 멜로디 변화량 벡터 데이터 - 각 노트 사이의 변화량(시간 변화, 음정 변화)¶

멜로디라는 것은 결국 어떤 음의 높낮이의 변화의 흐름이라는 아이디어에서 착안

#### 멜로디 커브 데이터 준비¶

In [50]:

    def create_curve_seq(mel_arr):
        curve_seq = []
        for idx in range(1, len(mel_arr)):
            curr_p_diff = mel_arr[idx]['note'] - mel_arr[idx-1]['note']
            curr_t_diff = mel_arr[idx]['offset'] - mel_arr[idx-1]['offset']
            curve_seq.append((curr_p_diff, curr_t_diff))
        return curve_seq

In [53]:

    curve_seq_list = []
    for mel_arr in mel_arr_list:
        curve_seq_list.append(create_curve_seq(mel_arr))

#### One-hot encoding을 위한 딕셔너리 준비¶

신경망 학습을 잘하기 위해서는 보통 one-hot encoding을 해서 데이터를 준비합니다. 이를 위해 모든 유니크한 멜로디 커브 엘리먼트에 대한 딕셔너리를 구성하고, 각각에 ID 인덱스를 부여합니다.

In [55]:

    # flatten
    curve_corpus= sum(curve_seq_list, [])
    # flattened seq
    mel_seq = curve_corpus

In [56]:

    # prepare the complete set, and pairs of data and indices
    def get_corpus_data(curve_corpus):
        curve_corpus_set = set(curve_corpus)
        val_indices = dict((v, i) for i, v in enumerate(curve_corpus_set))
        indices_val = dict((i, v) for i, v in enumerate(curve_corpus_set))

        return curve_corpus_set, val_indices, indices_val

In [58]:

    mel_set, mel_v_i, mel_i_v = get_corpus_data(curve_corpus)
    vocab_size = len(mel_set)

#### 새로운 입력에 대한 대처 방안¶

만약 우리가 유저로부터 어떤 입력을 받아서 이에 이어지는 시퀀스를 생성하도록 하고 싶다면, 새로 받아온 입력을 똑같은 멜로디 커브 형태로 만들어주어야 합니다.

이때 기존 커브 데이터 딕셔너리에 없는 커브가 들어올 수 있기 때문에, 이를 최대한 비슷한 커브로 대체하는 작업이 필요합니다.

In [59]:

    def truncate(f, n):
        s = '%.12f' % f
        i, p, d = s.partition('.')
        return float('.'.join([i, (d+'0'*n)[:n]]))

    def euclidean_dist(a, b):
        return np.linalg.norm(np.array(a)-np.array(b))

    def find_similar_curve(query_curve, mel_set):
        list_mel_set = list(mel_set)
        min_dist = 10000 # to act like positive infinity
        found_curve_idx = -1 # just to initialize
        for idx, curve in enumerate(list_mel_set):
            if np.array_equal(query_curve, curve):
                found_curve_idx = idx
                break
            elif euclidean_dist(query_curve, curve) < min_dist:
                min_dist = euclidean_dist(query_curve, curve)
                found_curve_idx = idx
        print(list_mel_set[found_curve_idx])
        return found_curve_idx

#### Input data / Label data 구성.¶

다시 언급하자면, 우리가 사용할 모델은 Char-RNN이라고 불리는 단순한 모델로, 한번에 한 캐릭터씩을 인풋으로 받아서 다음 캐릭터를 생성하는 모델입니다. 따라서, 인풋으로 어떤 시퀀스를 준비한 후 이 인풋에 대한 '정답 데이터', 즉 레이블 데이터는 한 스텝 이후의 캐릭터들의 시퀀스라고 볼 수 있습니다.

좀 전에 정의한 멜로디 커브 ID를 사용해서 이 ID의 시퀀스인 인풋데이터를 만들고, 한칸씩 뒤로 밀린 레이블데이터 또한 정의하도록 하겠습니다.

In [62]:

    def get_mel_id_sequence(mel_arr_list, curve_arr_list):

        input_seq_list = []
        label_seq_list = []

        for curve_arr in curve_arr_list:
            mel_id_seq = []
            for curve in curve_arr:
                mel_id = mel_v_i[curve]
                mel_id_seq.append(mel_id)

            input_id_seq = np.array(mel_id_seq)[:-sequence_length]
            label_id_seq = np.roll(np.array(mel_id_seq), -sequence_length)[:-sequence_length]

            input_seq_list.append(input_id_seq)
            label_seq_list.append(label_id_seq)

        return input_seq_list, label_seq_list

In [63]:

    sequence_length = 8
    input_seq_list, label_seq_list = get_mel_id_sequence(mel_arr_list, curve_seq_list)

In [64]:

    def get_batch_sequence(input_seq_list, label_seq_list, sequence_length):

        input_sequences = []
        label_sequences = []

        for idx in range(len(input_seq_list)):
            num_seqs_per_song = max(int((len(input_seq_list[idx]) / sequence_length)) - 1, 0)

            for ns in range(num_seqs_per_song):
                input_sequences.append(input_seq_list[idx][ns * sequence_length:(ns+1) * sequence_length])
                label_sequences.append(label_seq_list[idx][ns * sequence_length:(ns+1) * sequence_length])

        return np.array(input_sequences), np.array(label_sequences)

In [65]:

    input_sequences, label_sequences = get_batch_sequence(input_seq_list, label_seq_list, sequence_length)

### 5. Model Definition and Training¶

#### 이제 준비한 데이터를 가지고 우리의 모델을 학습시킬 차례입니다.¶

#### 먼저, 아주 조금만 더 복잡하게(..) Tensorflow RNN을 다루어 봅시다.¶

텐서플로우에서 RNN을 사용하는 방식은 다음과 같습니다. LSTM Cell을 사용하도록 하겠습니다.

- Cell을 정의합니다. (BasicLSTMCell 등의 구현된 클래스 사용. 내부에 연산한 후 output값과 state값을 넘겨주는 구조가 정의되어 있음.)

In [ ]:

    cell = tf.contrib.rnn.BasicLSTMCell(num_hidden_unit,
                                        state_is_tuple=True)

- RNN cell 내부에서 받을 hidden state값(previous step으로부터 넘겨받는)에 대한 초기값을 지정하기 위해서 일단 cell모양 그대로 0을 채워놓은 텐서를 저장해놓습니다.

In [ ]:

    initial_state = cell.zero_state(batch_size, tf.float32)

- 정의된 cell들을 가지고 (static 혹은) dynamic rnn 구조를 정의합니다. 여기서 최종 레이어 단계에서의 output sequence과 최종 state 값을 리턴받습니다. (만약 이 스테이트 값을 다음 iteration에서 쓰고자 한다면 받아오고, 아닐 경우 사용하지 않음.)

In [ ]:

    output, _ = tf.nn.dynamic_rnn(cell, input_tensor, sequence_length,
                                  time_major=False, dtype=tf.float32)

중요한 부분은 input_tensor로 받아오는 텐서 (즉 인풋)의 shape을 가지고 알아서 time step의 길이를 추정한다는 것입니다. (sequence_length부분을 생략해도 됨.)

    Batch size x time steps x features

이 부분에 유동성을 위해서 time_major라는 argument가 쓰이는데, 보통 [batch_size, num_steps, state_size]의 꼴로 처리를 하지만 이것을 True로 설정하면 [num_steps, batch_size, state_size]의 꼴로 처리합니다. 특정 스텝에서의 결과값을 얻어내는 데에 유용하게 쓰일 수 있습니다.

혹은 이 때 시퀀스의 길이가 일정하다면 static RNN을 사용해도 상관없습니다.(하지만 메모리를 미리 잡는 이슈가 있어서 그냥 dynamic_rnn을 사용하는 것이 좋습니다.)

In [ ]:

    output, _ = tf.nn.static_rnn(cell, input_tensor,
                                 dtype=tf.float32)

- 만약 멀티 레이어 RNN을 사용하고 싶다면, RNN셀을 각각 생성한 뒤에 이를 리스트로 묶어서 tf.contrib.rnn.MultiRNNCell안에 인풋으로 넣어주면 됩니다.

In [ ]:

    rnn_cells = tf.contrib.rnn.MultiRNNCell([cell1, cell2])

혹은 다음과 같이 아예 cell을 생성하는 함수를 만들면 더 편리합니다.

In [ ]:

    # RNN cell layer generating function
    def create_rnn_cell():
        cell = tf.contrib.rnn.BasicLSTMCell(num_units = hidden_size,
                                            state_is_tuple = True)
        return cell

    # 2 layers for hidden layer
    multi_cells = tf.contrib.rnn.MultiRNNCell([create_rnn_cell()
                                               for _ in range(2)],
                                               state_is_tuple=True)

그리고 정의된 multi_cells를 가지고 static 혹은 dynamic rnn 구조를 정의합니다.

In [ ]:

    outputs, _= tf.nn.dynamic_rnn(multi_cells, x_data,
                                  dtype=tf.float32)

tf.nn.dynamic_rnn을 처리한 output의 dimension은 cell의 크기와 동일하게 됩니다. (cell에 지정한 num_units 만큼의 output의 dimension이 자동으로 결정됩니다.)

즉, [batch_size, sequence_length, input_dim]을 인풋으로 넣으면 [batch_size, sequence_length, num_units]의 output이 나오게 되는 것입니다.

이렇게 얻은 dynamic_rnn의 아웃풋에 대해서 보통 마지막 레이어에 Fully-connected layer를 추가하는데, 이 작업을 한 시퀀스 내의 타임스텝 전체에 대해서 행렬 연산으로 한번에 하기 위해서 batch_size * sequence_length 부분을 flat하게 펴줄 필요가 있습니다.

In [ ]:

    rnn_output_flat = tf.reshape(outputs, [-1, hidden_size]) # output : [N x sequence_length, hidden_size]

여기에 Fully connected layer를 더해줍니다. 이 때 아웃풋의 dimension을 one-hot encoding으로 정의한 vocabulary이 크기로 지정해줍니다.

In [ ]:

    logits = tf.contrib.layers.fully_connected(rnn_output_flat, num_vocab, None)
    # output : [N x sequence_length, vocab_size]

이렇게 얻은 logits들에 softmax function을 이용해서 vocab들 중에 모델이 예측하는 가능성의 정도를 측정합니다.

In [ ]:

    y_softmax = tf.nn.softmax(self.logits) # outputs : [N x sequence_length, vocab_size]

이렇게 얻은 값을 이용해서 가장 높게 측정한 character를 뽑는 것이 모델의 prediction 값이 됩니다.

In [ ]:

    pred = tf.argmax(y_softmax, axis=1) # outputs : [N x sequence_length]

아까 Fully-connected layer의 계산을 위해서 flatten했던 데이터를 다시 (배치 사이즈, 시퀀스)의 형태로 만들어주면, 한 미니배치에 대한 우리 모델의 예측값이 완성되는 것입니다.

In [ ]:

    pred = tf.reshape(pred, [batch_size, -1]) # [N, sequence_length]

위의 몇 스텝은 prediction을 위한 부분이었지만, 모델의 학습을 위해서는 가장 높은 값을 뽑는 prediction 이전 단계의 logits값을 가지고 Loss를 계산하는 부분이 필요합니다.

텐서플로우에 미리 정의된 Cross-entropy 함수를 사용해서 각 timestep의 Loss를 계산하고, 전체 timestep에 대해서 평균을 낸 값으로 최종 Loss를 얻게 됩니다. (이 값이 BPTT를 통해서 모든 weight parameter 업데이트에 활용됩니다.)

In [ ]:

    losses = tf.nn.sigmoid_cross_entropy_with_logits(labels=y_flat, logits=logits)
    sequence_loss = tf.reduce_mean(losses)

그 외의 optimizer를 사용하는 부분은 앞서 살펴본 기본 모델과 동일합니다.

Optimizer만 지정해주면, 알아서 내부에 구현된 해당 방법으로 parameter 업데이트를 진행하게 됩니다.

In [ ]:

    opt = tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(sequence_loss)

#### 지금까지 살펴본 원리에 입각해서 간단한 Class 형태로 우리 데이터를 학습하기 위한 RNN을 만들어보겠습니다.¶

##### 시간 관계상 이 코드는 올려놓은 예제코드에서 카피해서 사용하도록 하겠습니다.¶

### 6. Future works¶

그럼 아티스트로서 우리는 어떤 작업을 할 수 있을까요?

어떤 시퀀스를 학습시킬 것인가? (음악을 어떤 각도에서 해석하여서 무엇의 시퀀스를 배우고자 하는가?)

인공지능은 데이터에서 배운 어떤 패턴을 생성해내는데, 이것에 어떤 의미를 부여하고 어떤 표현기법으로 응용할 것인가?

### Thank you. Any questions?¶