摘要:我將詳細討論兩種基於深度學習的自動生成音樂的架構——WaveNet和LSTM。比較WaveNet與建立自動音樂生成模型的LSTM的性能。

概述

  • 學習如何開發自動生成音樂的端到端模型
  • 理解WaveNet架構,並使用Keras從頭實現它
  • 比較WaveNet與建立自動音樂生成模型的LSTM的性能

介紹

“如果我不是物理學家,我可能會成爲音樂家。我經常在音樂上思考。我在音樂中實現我的白日夢。我從音樂的角度來看待我的生活。——愛因斯坦

我可能不是像愛因斯坦先生那樣的物理學家,但我完全同意他對音樂的看法!我不記得有哪一天我沒有打開我的音樂播放器。我上下班的旅途伴隨着音樂,老實說,它幫助我專注於我的工作。

我一直夢想着作曲,但對樂器卻不是很熟悉。直到我遇到了深度學習。使用特定的技術和框架,我可以創作自己的原創音樂,而不需要真正瞭解任何音樂理論!

這是我最喜歡的專業項目之一。我把我的兩個愛好——音樂和深度學習——結合起來,創造了一個自動生成音樂的模型。夢想成真了!

我很高興與你分享我的方法,包括整個代碼,使你能夠生成自己的音樂!我們將首先快速理解自動音樂生成的概念,然後再深入探討我們可以使用的不同方法。最後,我們將啓動Python並設計我們自己的自動音樂生成模型。

目錄

  1. 什麼是自動音樂生成?
  2. 音樂的構成要素是什麼?
  3. 不同的音樂生成方法
    • 使用WaveNet架構
    • 使用長短時記憶(LSTM)
  4. 實現-使用Python自動作曲

什麼是自動音樂生成?

音樂是一種藝術,一種通用的語言。

我把音樂定義爲不同頻率的音調的集合。因此,音樂的自動生成是一個創作一小段音樂的過程,並且在在這個過程中人類的介入不多。

產生音樂的最簡單形式是什麼?

這一切都是從隨機選擇聲音並將它們組合成一段音樂開始的。在1787年,莫扎特提出了一個骰子游戲,這些隨機的聲音選擇。他手工創作了近272個音調!然後,他根據兩個骰子的總和選擇一個音調。

另一個有趣的想法是利用音樂語法來產生音樂。

音樂語法包括正確安排和組合音樂聲音以及正確演奏音樂作品所必需的知識 -音樂語法基礎

在20世紀50年代早期,Iannis Xenakis使用統計和概率的概念來創作音樂——通常被稱爲隨機音樂。他把音樂定義爲偶然發生的一系列元素(或聲音)。因此,他用隨機理論來闡述它。他對元素的隨機選擇完全依賴於數學概念。

最近,深度學習架構已經成爲自動生成音樂的最新技術。在本文中,我將討論使用WaveNet和LSTM(長短時記憶)架構實現自動作曲的兩種不同方法。

音樂的構成要素是什麼?

音樂基本上是由音符和和絃組成的。讓我從鋼琴樂器的角度來解釋這些術語:

  • 注:單個鍵發出的聲音稱爲音符
  • 和絃:兩個或多個鍵同時發出的聲音稱爲和絃。一般來說,大多數和絃包含至少3個鍵音
  • 八度:重複的模式稱爲八度。每個八度音階包含7個白鍵和5個黑鍵

自動生成音樂的不同方法

我將詳細討論兩種基於深度學習的自動生成音樂的架構——WaveNet和LSTM。但是,爲什麼只有深度學習架構呢?

深度學習是受神經結構啓發的機器學習領域。這些網絡自動從數據集中提取特徵,並且能夠學習任何非線性函數。這就是爲什麼神經網絡被稱爲通用函數逼近器。

因此,深度學習模型是自然語言處理(NLP)、計算機視覺、語音合成等領域的最新技術。讓我們看看如何爲音樂作曲建立這些模型。

方法1:使用WaveNet

WaveNet是一個基於深度學習的原始音頻生成模型,由谷歌DeepMind開發。

WaveNet的主要目標是從原始數據分佈中生成新的樣本。因此,它被稱爲生成模型。

Wavenet類似於NLP中的語言模型。

在一個語言模型中,給定一個單詞序列,該模型試圖預測下一個單詞:

與WaveNet中的語言模型類似,給定一個樣本序列,它嘗試預測下一個樣本。

方法2:使用長短時記憶(LSTM)模型

長短時記憶模型(Long- Short-Term Memory Model,俗稱LSTM)是循環神經網絡(RNNs)的一種變體,它能夠捕獲輸入序列中的長期依賴關係。LSTM在序列到序列建模任務中有着廣泛的應用,如語音識別、文本摘要、視頻分類等。

讓我們詳細討論如何使用這兩種方法來訓練我們的模型。

方法1:使用WaveNet

Wavenet:訓練階段

這是一個多對一的問題,輸入是一系列振幅值,輸出是隨後的值。

讓我們看看如何準備輸入和輸出序列。

波形網輸入:

WaveNet將原始音頻作爲輸入。原始音頻波是指波在時間序列域中的表示。

在時間序列域中,音頻波以振幅值的形式表示,振幅值記錄在不同的時間間隔內:

波形網輸出:

給定振幅值的序列,WaveNet嘗試預測連續的振幅值。

讓我們通過一個例子來理解這一點。考慮一個採樣率爲16,000(即每秒16,000個次採樣)的5秒的音頻波。現在,我們有80000個樣本,每隔5秒記錄一次。讓我們把音頻分成大小相等的塊,比如1024(這是一個超參數)。

下圖展示了模型的輸入和輸出序列:

我們可以對其餘的塊使用類似的過程。

從上面我們可以推斷出每個塊的輸出只依賴於過去的信息(即以前的時間步),而不依賴於未來的時間步。因此,該任務稱爲自迴歸任務,該模型稱爲自迴歸模型。

推理階段

在推理階段,我們將嘗試生成新的樣本。讓我們看看怎麼做:

  1. 選擇一個隨機的樣本值數組作爲建模的起點
  2. 現在,模型輸出所有樣本的概率分佈
  3. 選擇具有最大概率的值並將其附加到樣本數組中
  4. 刪除第一個元素並作爲下一個迭代的輸入傳遞
  5. 對一定數量的迭代重複步驟2和4

理解WaveNet架構

WaveNet的構建塊是因果擴展的一維卷積層。讓我們首先了解相關概念的重要性。

爲什麼?什麼是卷積?

使用卷積的一個主要原因是從輸入中提取特徵。

例如,在圖像處理的情況下,將圖像與過濾器進行卷積可以得到一個特徵圖。

卷積是結合兩個函數的數學運算。在圖像處理中,卷積是圖像某些部分與核的線性組合

什麼是一維卷積?

一維卷積的目標類似於長短時記憶模型。它用於解決與LSTM類似的任務。在一維卷積中,一個核或一個濾波器只沿着一個方向運動:

卷積的輸出取決於核的大小、輸入形狀、填充類型和步幅。現在,我將帶你通過不同類型的填充來理解使用因果擴展一維卷積層的重要性。

當我們設置padding有效值時,輸入和輸出序列的長度會發生變化。輸出的長度小於輸入的長度:

當我們將padding設爲相同時,在輸入序列的兩邊填充0,使輸入和輸出的長度相等:

1D卷積的優點:

  • 捕獲輸入序列中出現的順序信息
  • 與GRU或LSTM相比,訓練要快得多,因爲它們不像循環神經網絡是串聯的

1D卷積的缺點:

  • 當padding設置爲same時,時間步t的輸出與之前的t-1和未來的時間步t+1進行卷積。因此,它違反了自迴歸原則
  • 當填充被設置爲valid時,輸入和輸出序列的長度會發生變化,這是計算殘差連接所需的(稍後將介紹)

這爲因果卷積掃清了道路。

注意:我在這裏提到的優點和缺點是針對這個問題的。

什麼是一維因果卷積?

這被定義爲卷積,其中在t時刻的輸出只與來自t時刻以及更早的前一層的元素進行卷積。

簡單地說,普通卷積和因果卷積只在填充上不同。在因果卷積中,在輸入序列的左側添加0,以保持自迴歸原理:

因果1D卷積的優點:

  • 因果卷積不考慮未來的時間步,而未來的時間步是構建生成模型的一個標準

因果1D卷積的缺點:

  • 因果卷積不能回溯到過去或者序列中較早發生的時間步。因此,因果卷積的接受域非常低。網絡的接受域是指影響輸出的輸入數量:

正如你在這裏看到的,輸出僅受5個輸入的影響。因此,網絡的接受域爲5,非常低。網絡的接受域也可以通過添加大尺寸的核來增加,但是要記住計算複雜度會增加。

這讓我們想到了因果擴展一維卷積的概念。

什麼是因果擴展一維卷積?

在覈值之間存在空穴或空隙的因果一維卷積層稱爲擴展一維卷積。

要添加的空格數由擴展率給出。它定義了網絡的接收域。大小爲k且擴展速率爲d的核在k的每個值之間有d-1個洞。

正如你在這裏看到的,將一個3 3的核與一個7 7的輸入用擴展率2來進行卷積,其接收域爲5*5。

擴展一維因果卷積的優點:

  • 擴展1D卷積網絡通過指數增加每一隱藏層的擴展率來增加接受域:

正如你在這裏看到的,輸出受到所有輸入的影響。因此,網絡的接受域是16。

WaveNet的殘差塊:

一個塊包含殘差連接和跳躍連接,這些連接只是爲了加快模型的收斂速度而添加的:

WaveNet的工作流程:

  • 輸入被輸入到一個因果一維卷積
  • 然後,輸出被饋送到兩個不同的sigmoid和tanh激活的擴展一維卷積層
  • 兩個不同激活值的元素相乘接着是跳躍連接
  • 在元素上加入一個跳躍連接和因果1D的輸出會產生殘差
  • 跳躍連接和因果一維輸出來計算殘差

方法2:長短時記憶(LSTM)方法

另一種自動生成音樂的方法是基於長短時記憶(LSTM)模型。輸入和輸出序列的準備類似於WaveNet。在每個時間步,振幅值被輸入到長短時記憶單元-然後計算隱藏的向量,並將其傳遞到下一個時間步。

在$h_t$時刻的當前隱藏向量是基於$h_t$時刻的當前輸入$a_t$和之前的隱藏向量$h_{t-1}$計算的。這是如何捕獲序列信息在任何循環神經網絡:

LSTM的優點:

  • 捕獲輸入序列中出現的順序信息

LSTM的缺點:

  • 由於它按順序處理輸入,因此會消耗大量的訓練時間

實現-使用Python自動生成音樂

等待結束了!讓我們開發一個自動生成音樂的端到端模型。打開你的Jupyter 筆記本或者Colab(或者任何你喜歡的IDE)。

下載數據集:

我從衆多資源中下載並組合了一架數字鋼琴的多個古典音樂文件。你可以從這裏下載最終的數據集。

https://drive.google.com/file/d/1qnQVK17DNVkU19MgVA4Vg88zRDvwCRXw/view?usp=sharing

讓我們首先爲可重複的結果設置種子。這是因爲深度學習模型在執行時由於隨機性可能會輸出不同的結果。這確保了我們每次都能得到相同的結果。

from numpy.random import seed
seed(1)
from tensorflow import set_random_seed
set_random_seed(2)

導入庫:

Music 21是由麻省理工學院爲理解音樂數據而開發的Python庫。MIDI是存儲音樂文件的標準格式(它代表樂器數字接口)。MIDI文件包含的是指令而不是實際的音頻。因此,它佔用的空間很少。這就是爲什麼它通常是首選傳輸文件。

#處理MIDI文件
from music21 import * 

#數組處理
import numpy as np     
import os

#隨機數生成
import random         

#keras構建深度學習模型
from keras.layers import * 
from keras.models import *
import keras.backend as K

讀取數據:

定義一個讀取MIDI文件的函數。它返回音樂文件中的一組音符和和絃。

def read_midi(file):
  notes=[]
  notes_to_parse = None

  #解析MIDI文件
  midi = converter.parse(file)
  #基於樂器分組
  s2 = instrument.partitionByInstrument(midi)

  #遍歷所有的樂器
  for part in s2.parts:
    #只選擇鋼琴
    if 'Piano' in str(part): 
      notes_to_parse = part.recurse() 
      #查找特定元素是音符還是和絃
      for element in notes_to_parse:
        if isinstance(element, note.Note):
          notes.append(str(element.pitch))
        elif isinstance(element, chord.Chord):
          notes.append('.'.join(str(n) for n in element.normalOrder))

  return notes

從目錄中讀取MIDI文件:

#讀取所有文件名
files=[i for i in os.listdir() if i.endswith(".mid")]

#讀取每個midi文件
all_notes=[]
for i in files:
  all_notes.append(read_midi(i))

#所有midi文件的音符和和絃
notes = [element for notes in all_notes for element in notes]

準備文章中提到的輸入和輸出序列:

#輸入序列的長度
no_of_timesteps = 128      

n_vocab = len(set(notes))  
pitch = sorted(set(item for item in notes))  

#爲每個note分配唯一的值
note_to_int = dict((note, number) for number, note in enumerate(pitch))  
#準備輸入和輸出序列
X = []
y = []
for notes in all_notes:
  for i in range(0, len(notes) - no_of_timesteps, 1):
    input_ = notes[i:i + no_of_timesteps]
    output = notes[i + no_of_timesteps]
    X.append([note_to_int[note] for note in input_])
    y.append(note_to_int[output])

卷積1D或LSTM的輸入必須是(樣本、時間步、特徵)的形式。因此,讓我們根據需要的形狀來重新設計輸入數組。注意,每個時間步中的特徵數量爲1:

X = np.reshape(X, (len(X), no_of_timesteps, 1))
#標準化輸入
X = X / float(n_vocab)

我在這裏定義了兩個架構——WaveNet和LSTM。請嘗試這兩種架構來理解WaveNet架構的重要性。

def lstm():
  model = Sequential()
  model.add(LSTM(128,return_sequences=True))
  model.add(LSTM(128))
  model.add(Dense(256))
  model.add(Activation('relu'))
  model.add(Dense(n_vocab))
  model.add(Activation('softmax'))
  model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')
  return model

我簡化了WaveNet的結構,沒有添加殘差連接和跳躍連接,因爲這些層的作用是提高更快的收斂速度(而WaveNet將原始音頻作爲輸入)。但在我們的情況下,輸入將是一組節點和和絃,因爲我們正在生成音樂:

K.clear_session()
def simple_wavenet():
  no_of_kernels=64
  num_of_blocks= int(np.sqrt(no_of_timesteps)) - 1 

  model = Sequential()
  for i in range(num_of_blocks):
    model.add(Conv1D(no_of_kernels,3,dilation_rate=(2**i),padding='causal',activation='relu'))
  model.add(Conv1D(1, 1, activation='relu', padding='causal'))
  model.add(Flatten())
  model.add(Dense(128, activation='relu'))
  model.add(Dense(n_vocab, activation='softmax'))
  model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')
  return model

定義每50個epoch後保存模型的回調:

import keras
mc = keras.callbacks.ModelCheckpoint('model{epoch:03d}.h5', save_weights_only=False, period=50)

實例化並訓練批處理大小爲128的模型:

model = simple_wavenet()
model.fit(X,np.array(y), epochs=300, batch_size=128,callbacks=[mc])

這是本文中提到的推理階段的一個實現。它爲一定次數的迭代後預測最有可能的元素:

def generate_music(model, pitch, no_of_timesteps, pattern):

    int_to_note = dict((number, note) for number, note in enumerate(pitch))
    prediction_output = []

    # 生成50個
    for note_index in range(50):
        #輸入數據
        input_ = np.reshape(pattern, (1, len(pattern), 1))
        #預測並選出最大可能的元素
        proba = model.predict(input_, verbose=0)
        index = np.argmax(proba)

        pred = int_to_note[index]
        prediction_output.append(pred)
        pattern = list(pattern)
        pattern.append(index/float(n_vocab))
        #將第一個值保留在索引0處
        pattern = pattern[1:len(pattern)]

    return prediction_output

下面是一個函數轉換成一個MIDI文件組成的音樂:

def convert_to_midi(prediction_output):
    offset = 0
    output_notes = []

    # 根據模型生成的值創建音符和和絃對象
    for pattern in prediction_output:
        # 模式是和絃
        if ('.' in pattern) or pattern.isdigit():
            notes_in_chord = pattern.split('.')
            notes = []
            for current_note in notes_in_chord:
                new_note = note.Note(int(current_note))
                new_note.storedInstrument = instrument.Piano()
                notes.append(new_note)
            new_chord = chord.Chord(notes)
            new_chord.offset = offset
            output_notes.append(new_chord)
        # 模式是音符
        else:
            new_note = note.Note(pattern)
            new_note.offset = offset
            new_note.storedInstrument = instrument.Piano()
            output_notes.append(new_note)

        # 指定兩個音符之間的持續時間
        offset+  = 0.5
       # offset += random.uniform(0.5,0.9)

    midi_stream = stream.Stream(output_notes)
    midi_stream.write('midi', fp='music.mid')

現在讓我們來譜寫自己的音樂吧!

#爲第一次迭代選擇隨機塊
start = np.random.randint(0, len(X)-1)
pattern = X[start]
#載入最好的模型
model=load_model('model300.h5')
#生成和保存音樂
music = generate_music(model,pitch,no_of_timesteps,pattern)
convert_to_midi(music)

以下是我們的模型創作的幾首曲子。是時候欣賞音樂了!

太棒了,對吧?但你的學習還不止於此。這裏有一些方法可以進一步提高模型的性能:

  • 增加訓練數據集的大小會產生更好的旋律,因爲深度學習模型可以很好地在大型訓練數據集上進行泛化
  • 在構建具有大量層的模型時添加跳躍和殘差連接

結尾

深度學習在我們的日常生活中有着廣泛的應用。解決任何問題的關鍵步驟都是理解問題陳述,制定它並定義解決問題的體系結構。

在做這個項目的時候,我有很多樂趣(和學習)。音樂是我的愛好,把深度學習和音樂結合起來是很有趣的。

原創文章,作者:磐石,如若轉載,請註明出處:https://panchuang.net/2020/02/26/%e9%80%9a%e8%bf%87%e6%b7%b1%e5%ba%a6%e5%ad%a6%e4%b9%a0%e6%9d%a5%e5%88%9b%e4%bd%9c%e8%87%aa%e5%b7%b1%e7%9a%84%e9%9f%b3%e4%b9%90-%ef%bc%88%e9%99%84%e4%bb%a3%e7%a0%81%ef%bc%89/

相關文章