項目筆記(一):實驗——用神經網路實現midi音樂旋律音軌的確定

零、寫在前面

計劃要用seq2seq模型做一個交響樂編曲程序,encoder network的輸入是一個樂句旋律,decoder network的目標target是這個樂句完整的管弦配樂版本。本文記錄的實驗的目的是自動提取出midi樂句的旋律音軌。

一、原理

參考這篇文獻中的方法:提取出這個樂句中各個音軌(樂器)的以下特徵:

  1. 平均力度
  2. 所有音符累加總時值
  3. 時值類型
  4. 最高音與最低音之間的音程
  5. 第二高音與第二低音之間的音程

每個音軌的五個特徵疊在一起,作為一個樂句的神經網路輸入。

對應於主旋律的音軌的one-hot向量作為神經網路的目標輸出。

之前文獻中,使用了其他的特徵作為輸入,但那些特徵並不十分合理。

二、技術細節

1.midi_to_features.py

這個文件用於提取出一個樂句各個音軌的5個特徵值,用了python的pretty_midi包作為處理midi文件的工具。

import pretty_mididef get_avg_velocity(instrument): # 平均力度 velocity = 0 for note in instrument.notes: velocity += note.velocity velocity /= len(instrument.notes) return velocitydef get_sum_duration(instrument): # 所有音符累加總時值 duration = 0 for note in instrument.notes: duration += note.end - note.start return durationdef get_types_of_duration(instrument): # 時值類型 duration = [] for note in instrument.notes: duration.append(note.end - note.start) types = len(set(duration)) return typesdef get_pitch_range(instrument): # 最高音與最低音之間的音程 pitch = [] for note in instrument.notes: pitch.append( note.pitch ) sorted_pitch = sorted( set(pitch) ) range = sorted_pitch[-1] - sorted_pitch[0] return rangedef get_second_pitch_range(instrument): # 第二高音與第二低音之間的音程 pitch = [] for note in instrument.notes: pitch.append( note.pitch ) sorted_pitch = sorted( set(pitch) ) range = sorted_pitch[-2] - sorted_pitch[1] return range

上面的代碼中五個函數對應於提取一個樂句中某一音軌(樂器)的五個特徵值。

我們接下來將每一個音軌(樂器)的這五個值,壓縮到到一個向量中。

def get_train_example(midi_file): pm = pretty_midi.PrettyMIDI( midi_file=midi_file ) train_example = [] for instrument in pm.instruments: train_example.append(get_avg_velocity(instrument)) train_example.append(get_sum_duration(instrument)) train_example.append(get_types_of_duration(instrument)) train_example.append(get_pitch_range(instrument)) train_example.append(get_second_pitch_range(instrument)) return train_example

接下來還要寫函數來遍曆數據集,提取出各個midi樂句的(一個樂句一個midi文件)特徵值。將他們疊在一起,作為訓練集的輸入。

def get_X_train(): X_train = [[]] for i in range(13): # 一共切取了14個midi片段,其中13個作為訓練樣本 midi_file = data/x+str(i+1)+.mid train_example = get_train_example(midi_file) if i==0: X_train[0]=train_example else: X_train.append(train_example) return X_train

類似地,再寫測試集:

def get_X_test(): X_test = [[]] midi_file = data/x14.mid train_example = get_train_example(midi_file) X_test[0]=train_example return X_test

2. features_to_melody.py

這個文件里用tensorflow構建一個二層神經網路,用matplotlib繪圖。我們使用鋼琴曲作為數據集,左右手各一個音軌,每個音軌5個特徵值,所以一個樂句共有10個特徵值。對應的y值顯然也只有兩種可能。

from midi_to_features import *import tensorflow as tfimport matplotlib.pyplot as pltimport numpy as npn_x, n_y = 10, 2

接下來我們製作訓練集和測試集的輸入和輸出,我使用的數據中都是旋律再右手的,所以所有的輸出都相同。

# get training dataX_train = get_X_train()Y_example = [1, 0]Y_train = [[]]for i in range (13): if i == 0: Y_train[0] = Y_example else: Y_train.append( Y_example ) #行列互換,使其符合tensorflow的輸入格式X_train = list(map(list, zip(*X_train)))Y_train = list(map(list, zip(*Y_train)))print("X_train:", X_train)print("Y_train:", Y_train)# get testing dataX_test = get_X_test()X_test = list(map(list, zip(*X_test)))Y_test = [[1],[0]]print("X_test:", X_test)print("Y_test:", Y_test)

接下來是構建神經網路的準備工作:創建placeholder和初始化參數(用xavier來初始化所有w,用全零初始化b)。

# Create placeholdersdef create_placeholders(n_x, n_y): X = tf.placeholder(tf.float32, shape=[n_x, None]) Y = tf.placeholder(tf.float32, shape=[n_y, None]) return X, Y# Initialize the parametersdef initialize_parameters(): W1 = tf.get_variable("W1", [25, n_x], initializer=tf.contrib.layers.xavier_initializer()) b1 = tf.get_variable("b1", [25, 1], initializer=tf.zeros_initializer()) W2 = tf.get_variable("W2", [12, 25], initializer=tf.contrib.layers.xavier_initializer()) b2 = tf.get_variable("b2", [12, 1], initializer=tf.zeros_initializer()) W3 = tf.get_variable("W3", [n_y, 12], initializer=tf.contrib.layers.xavier_initializer()) b3 = tf.get_variable("b3", [n_y, 1], initializer=tf.zeros_initializer()) parameters = {"W1": W1, "b1": b1, "W2": W2, "b2": b2, "W3": W3, "b3": b3} return parameters

神經網路的正向傳播,激活函數使用relu。

# Forward propagationdef forward_propagation(X, parameters): W1 = parameters[W1] b1 = parameters[b1] W2 = parameters[W2] b2 = parameters[b2] W3 = parameters[W3] b3 = parameters[b3] Z1 = tf.add(tf.matmul(W1, X), b1) A1 = tf.nn.relu(Z1) Z2 = tf.add(tf.matmul(W2, A1), b2) A2 = tf.nn.relu(Z2) Z3 = tf.add(tf.matmul(W3, A2), b3) return Z3

使用softmax作為輸出層,定義交叉熵損失。

# Compute Costdef compute_cost(Z3, Y): logits = tf.transpose(Z3) labels = tf.transpose(Y) cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = logits, labels = labels)) return cost

現在我們可以開始構建整個模型了。

# the modeldef model(X_train, Y_train, X_test, Y_test,learning_rate = 0.0001, num_epochs = 1000,print_cost = True): n_x, n_y, = 10, 2, 13 costs = [] # 創建placeholder X, Y = create_placeholders(n_x, n_y) # 初始化參數 parameters = initialize_parameters() # 神經網路前向傳播 Z3 = forward_propagation(X, parameters) # 計算損失函數 cost = compute_cost(Z3, Y) #用adam演算法最小化損失函數 optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) init = tf.global_variables_initializer() # 創建tensorflow的session with tf.Session() as sess: sess.run(init) for epoch in range(num_epochs): _, Cost = sess.run([optimizer, cost], feed_dict={X: X_train, Y: Y_train}) # 列印出cost的變化 if print_cost == True and epoch % 100 == 0: print ("Cost after epoch %i: %f" % (epoch, Cost)) costs.append(Cost) # 用matplotlib繪製 時間-損失圖像 plt.plot(np.squeeze(costs)) plt.ylabel(cost) plt.xlabel(iterations (per tens)) plt.title("Learning rate =" + str(learning_rate)) plt.show() parameters = sess.run(parameters) print ("Parameters have been trained!") correct_prediction = tf.equal(tf.argmax(Z3), tf.argmax(Y)) # 計算準確率 accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) print("Train Accuracy:", accuracy.eval({X: X_train, Y: Y_train})) print("Test Accuracy:", accuracy.eval({X: X_test, Y: Y_test})) return parametersparameters = model(X_train, Y_train, X_test, Y_test)

3.數據集

這個實驗只是為了驗證可行性,用了很小很小的數據集(對應無比強烈的過擬合)。我手工提取了莫扎特土耳其進行曲(這是我寫過的這首曲子的樂理分析)的一共14旋律材料(14個樂句)。13個作為訓練集,最後一個作為測試集。他們可以在這裡下載。

我使用的是降b小調的版本,但樂句整體的移調不會影響輸出,所以,never mind。

三、結論

Cost after epoch 0: 9.850606Cost after epoch 100: 0.593675Cost after epoch 200: 0.267264Cost after epoch 300: 0.170038Cost after epoch 400: 0.108876Parameters have been trained!Train Accuracy: 1.0Test Accuracy: 1.0

我們看到,神經網路對於訓練集擬合地很好,正確率100%(其實是過擬合),測試樣本Test Accuracy: 1.0,即演算法正確地分類了測試樣本。

這說明,用神經網路實現midi音樂旋律音軌的確定,這一方法是可行的。


推薦閱讀:

李飛飛最新論文:構建好奇心驅動的神經網路,複製嬰兒學習能力
基於Numpy實現同態加密神經網路
當Node.js遇上OpenCV深度神經網路
論文解讀 | 基於神經網路的知識推理
谷歌大腦發布神經架構搜索新方法:提速1000倍

TAG:神經網路 | 主旋律 | MIDI |