目前深度學習模型能處理許多不同類型的問題,對於一些教程或框架用圖像分類舉例是一種流行的做法,常常作為類似「hello, world」 那樣的引例。FastAI 是一個構建在 PyTorch 之上的高級庫,用這個庫進行圖像分類非常容易,其中有一個僅用四行代碼就可訓練精準模型的例子。隨著v1版的發布,該版本中帶有一個data_block的API,它允許用戶靈活地簡化數據加載過程。今年夏天我參加了Kaggle舉辦的Freesound General-Purpose Audio Tagging 競賽,後來我決定調整其中一些代碼,利用fastai的便利做音頻分類。本文將簡要介紹如何用Python處理音頻文件,然後給出創建頻譜圖像(spectrogram images)的一些背景知識,示範一下如何在事先不生成圖像的情況下使用預訓練圖像模型。
點擊原文查看文中涉及的代碼,以及相關的notebooks。
音頻文件轉圖像
起初把音頻文件作為圖像分類聽起來有些怪異。圖像是二維數據(其中包含RGBA等4個通道), 而音頻文件通常是一維的 (可能包含2個維度的通道,單聲道和立體聲)。本文只關注單聲道的音頻文件。我們知道,每個音頻文件會有一個採樣率,即音頻的每秒採樣數。如果文件是一個3秒長採樣率為44100Hz的聲音片段,這就意味著文件是由 3*44100 = 132300 表示氣壓變化的連續數字組成。 librosa是Python中處理音頻效果最好的庫。
clip, sample_rate = librosa.load(filename, sr=None)clip = clip[:132300] # first three seconds of file
雖然從上圖可以感受到各時點音頻的響亮或安靜程度,但圖中基本看不出當前所在的頻率。為獲得頻率,一種非常通用的方案是去獲取一小塊互相重疊的信號數據,然後運行Fast Fourier Transform (FFT) 將數據從時域轉換為頻域。經過FFT處理後,我們可以將結果轉換為極坐標,就得到不同頻率的幅度和相位。雖然相位信息在某些情況下適用,本文中主要適用幅度信息,我們將其轉換為分貝單位,因為耳朵是以對數尺度感知聲音的。
n_fft = 1024 # frame length
start = 45000 # start at a part of the sound thats not silence
x = clip[start:start+n_fft]
X = fft(x, n_fft)
X_magnitude, X_phase = librosa.magphase(X)
X_magnitude_db = librosa.amplitude_to_db(X_magnitude)
以1024為長度計算FFT,我們得到一個以1024為頻點的頻譜。譜的第二部分是多餘的,因而實際處理我們只用前(N/2)+1個頻點,在本例中也就是513。
我們用一個採樣窗口長度為1024的FFT計算獲取整個文件的頻譜信息,每次計算向前滑動512個樣本(hop length),這樣採樣窗口就會互相重疊。第二個文件將產生步長為259的頻譜,可以看作是一張二維圖像。我們把這些操作稱為短時傅立葉變化(STFT),它可以提供一段時間內頻率變化的信息。
stft = librosa.stft(clip, n_fft=n_fft, hop_length=hop_length)stft_magnitude, stft_phase = librosa.magphase(stft)
stft_magnitude_db = librosa.amplitude_to_db(stft_magnitude)
本例中我們可以看到那些有趣的頻率,所有低於12500 Hz的數據。另外可以看到有相當多的無用的頻點,這些信息並沒有準確反映人類是如何感知頻率的。事實上人類是以對數尺度的頻率結合聲音強弱來進行感知的。我們可以分辨對數尺度上相同『距離』的頻率,比如50Hz到100Hz,這感受如同400Hz到800Hz的變化。
這就是為什麼許多人會用 melspectrogram 表示頻譜的原因,該操作即將頻點轉換為梅爾刻度(mel scale)。用Librosa庫,可以方便的把常規的譜數據轉換為melspectrogram格式,我們需要定義有多少「點」 ,並給出需要劃分的最大最小頻率範圍。
mel_spec = librosa.feature.melspectrogram(clip, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels, sr=sample_rate, power=1.0, fmin=fmin, fmax=fmax)mel_spec_db = librosa.amplitude_to_db(mel_spec, ref=np.max)
上面的melspectrogram我採用的頻點數為64(n_mels)。不同點在於,右側圖像裡只關注20Hz到8000Hz的頻率範圍。 這樣顯著減少了從最初513點每時點進行轉換的規模。
用fastai分類聲音頻譜
雖然我們可以分類原始聲音波形數據,但目前更流行用melspectrogram分類音頻,這種方法相當好用。那麼我們需要將整個數據集用上述方法轉換為頻譜圖。在GCP實例上用了所有CPU,我大約花了10分鐘處理完這些數據。以下是我生成melspectrogram用到的參數:
n_fft = 1024hop_length = 256
n_mels = 40
f_min = 20
f_max = 8000
sample_rate = 16000
本文從此處往下,我採用的都是NSynth數據集,該數據集由Google Magenta團隊提供。該數據集非常有趣,是由305979個音符組成,每段長4秒。我裁剪了這個數據集,只保留用聲學方法生成的音符,這樣管理起來相對簡單。分類目標是從10種樂器家族中分辨出音符是由哪個樂器家族產生的。
用fastai最新的data_block API,大大簡化了構建DataBunch對象的過程,數據集包括所有頻譜圖像機器對應的標籤——本例中用正則表達式通過解析文件名獲得所有分類標籤。
NSYNTH_IMAGES = 'data/nsynth_acoustic_images'instrument_family_pattern = r'(\w+)_\w+_\d+-\d+-\d+.png$'
data = (ImageItemList.from_folder(NSYNTH_IMAGES)
.split_by_folder()
.label_from_re(instrument_family_pattern)
.databunch())
數據一旦加載完成實例化預訓練卷積神經網絡 (CNN) ,這裡用的是 resnet18,然後在頻譜上做fine-tune。
learn = create_cnn(data, models.resnet18, metrics=accuracy)learn.fit_one_cycle(3)
2分14秒後,模型在驗證集(與訓練集完全分離的數據集)上準確度達到了84% 。當然數據一定有一些過擬合,這裡沒有做數據增強或類似正則化的優化,不過這是一個很不錯的開始!
利用fastai提供的ClassificationInterpretation類,可以快速查看錯誤是從哪來的,如下:
interp = ClassificationInterpretation.from_learner(learn)interp.plot_confusion_matrix(figsize=(10, 10), dpi=60)
看起來木槌跟吉他有點混了,另外簧片聲音月銅管樂器分不清。有了這些信息,我們可以更進一步查看這些樂器的頻譜數據,看看是否可以調整參數,從而更好的分辨它們。
為什麼在訓練過程中生成頻譜?
如果用圖像分類音頻效果這麼好,你也許會問在訓練過程中生成頻譜圖有什麼好處(相對於之前的方法)。可能有這麼幾個原因:
生成圖像的時間前例中,我們花了10分鐘產生所有圖像的頻譜圖。我經常會嘗試不同的參數設置,或把melspectrogram換成簡單的STFT,這樣就需要重新生成所有圖片,這樣就很難快速測試不同的參數配置。磁碟空間同樣的每次生成數據集後,數據集就會佔用大量磁碟空間,大小依賴於數據集以及變換。本例中,生成的數據佔了1G空間。數據增強提升圖像分類器性能的一個最有效的策略是採用數據增強。常規圖像變換諸如(rotating, flipping, cropping等) 在譜分類算法中可能不怎麼用得上。但是我們可以處理基於時域的音頻文件,然後再轉換為頻譜,最後進行分類。GPU 與 CPU過去我一直用 librosa 進行轉換,主要用CPU。但我們可以用 PyTorch提供的stft方法,該方法可直接使用GPU處理,這樣就會快很多,並且可以進行批處理 (而不是一次處理一張圖)。 如何在訓練過程中生成頻譜?
前幾天我一直在試驗創建一個新的基於fastai的聲音處理模塊。後來參考great new fastai documentation,寫出一個簡單類用於加載原始音頻文件,然後用PyTorch提供的方法使用GPU以批處理方式生成頻譜。我也創建了一個 create_cnn 函數,裁剪預訓練模型用以預測單通道數據(頻譜) ,此前模型是使用3通道。讓我驚喜的是,代碼和圖像分類器運行的速度差不多,不需要額外創建實際的圖像。現在建立數據集的代碼如下:
tfms = get_frequency_batch_transforms(n_fft=n_fft,
n_hop=n_hop,
n_mels=n_mels,
sample_rate=sample_rate)
data = (AudioItemList
.from_folder(NSYNTH_AUDIO)
.split_by_folder()
.label_from_re(instrument_family_pattern)
.databunch(bs=batch_size, tfms=tfms))
fastai庫支持預覽批次中的數據:
data.show_batch(3)
在預訓練模型上進行fine tuning跟之前步驟一樣,這裡不同的是需要把卷積的第一層修改為只接收單通道數據 (感謝fastai論壇的David Gutman).
learn = create_cnn(data, models.resnet18, metrics=accuracy)
learn.fit_one_cycle(3)
這一次訓練多花了30秒,執行了3個epoch後在驗證集上的精度為80%! 之前在CPU上創建整個數據集大約需要10分鐘。這樣就可以進行快速試驗,可以微調頻譜的參數,同時也可以對譜計算進行各種增強。
未來的工作
現在的方法已經可以通過不落地的方法直接生成不同譜的表示,我對如何通過數據增強改進原始音頻文件非常感興趣。在librosa庫中有很多方法,從pitch shifting到time stretching,隨機選出音頻的一段,可以做很多實驗。
同時比較感興趣的地方是,如果預訓練模型是基於聲音圖像(而不是基於圖像的),能否達到更好的精度。