機器聽覺:四、在自編碼器架構中加入記憶機制
作者:Daniel Rothmann
編譯:weakish
【編者按】Kanda機器學習工程師Daniel Rothmann撰寫的機器聽覺系列第四篇,講解如何在聲音訊譜嵌入中加入記憶機制。
歡迎回來! 這一系列文章將詳細介紹奧胡斯大學和智慧揚聲器生產商Dynaudio A/S合作開發的實時音訊訊號處理框架。
如果你錯過了之前的文章,可以點選下面的連結檢視:
ofollow,noindex" target="_blank">論智:機器聽覺:一、AI在音訊處理上的潛力 論智:機器聽覺:二、基於頻譜圖和CNN處理音訊有何問題? 論智:機器聽覺:三、基於自編碼器學習聲音嵌入表示在上一篇文章中,我們介紹了人類是如何體驗聲音的,在耳蝸形成頻譜印象,接著由腦幹核團“編碼”,並借鑑其思路,基於自編碼器學習聲音訊譜嵌入。在這篇文章中,我們將探索如何構建用來理解聲音的人工神經網路,並在頻譜聲音嵌入生成中整合 記憶 。
餘音記憶
聲音事件的含義,很大程度上源於頻譜特徵間的時域相互關係。
有一個事實可以作為例證,取決於語音的時域上下文的不同,人類聽覺系統會以不同方式編碼同樣的音位 。這意味著,取決於之前的語音,音位/e/在神經方面可能意味著完全不同的東西。
進行聲音分析時,記憶很關鍵。因為只有在某處實際儲存了之前的印象,才可能將之前的印象與“此刻”的印象相比較。
人類的短期記憶元件既包括感官記憶也包括工作記憶 。人類對聲音的感知依靠 聽覺感官記憶 (有時稱為 餘音記憶 )。C. Alain等將聽覺感官記憶描述為“聽覺感知的關鍵初始階段,使聽者可以整合傳入的聽覺資訊和儲存的之前的聽覺事件的表示” 。
從計算的角度出發,我們可以將餘音記憶視作即刻聽覺印象的短期緩衝區。
餘音記憶的持續時間有所爭議。D. Massaro基於純音和語音母音掩碼的研究主張持續時間約為250毫秒,而A. Treisman則根據雙耳分聽試驗主張持續時間約為4秒 。從在神經網路中借鑑餘音記憶思路的角度出發,我們不必糾結感官儲存的餘音記憶的固定時長。我們可以在神經網路上測試幾秒鐘範圍內的記憶的效果。
進入迴圈
在數字化頻譜表示中可以相當直截了當地實現感官記憶。我們可以簡單地分配一個 環形緩衝區 用來儲存之前時步的頻譜,儲存頻譜的數量預先定義。

環形緩衝區是一種資料結構,其中包含一個視作環形的陣列,窮盡陣列長度後索引迴圈往復為0 .
在我們的例子中,可以使用一個長度由所需記憶量決定的多維陣列,環形緩衝區的每個索引指向某一特定時步的完整頻率頻譜。計算出新頻譜後,將其寫入緩衝區,如果緩衝區已滿,就覆蓋最舊的時步。
在填充緩衝區的過程中,我們會更新兩個指標:標記最新加入元素的 尾指標 ,和標記最舊元素的 頭指標 (即緩衝區的開始) 。
下面是一個環形緩衝區的Python實現的例子(作者為 Eric Wieser ):
import numpy as np class CircularBuffer(): # 初始化NumPy陣列和頭/尾指標 def __init__(self, capacity, dtype=float): self._buffer = np.zeros(capacity, dtype) self._head_index = 0 self._tail_index = 0 self._capacity = capacity # 確保頭指標和尾指標迴圈往復 def fix_indices(self): if self._head_index >= self._capacity: self._head_index -= self._capacity self._tail_index -= self._capacity elif self._head_index < 0: self._head_index += self._capacity self._tail_index += self._capacity # 在緩衝區中插入新值,如緩衝區已滿,覆蓋舊值 def insert(self, value): if self.is_full(): self._head_index += 1 self._buffer[self._tail_index % self._capacity] = value self._tail_index += 1 self.fix_indices() # 將環形緩衝區作為以頭指標為開始的陣列返回 def unwrap(self): return np.concatenate(( self._buffer[self._head_index:min(self._tail_index, self._capacity)], self._buffer[:max(self._tail_index - self._capacity, 0)] )) # 指示緩衝區是否已滿 def is_full(self): return self.count() == self._capacity # 返回緩衝區中的當前值 def count(self): return self._tail_index - self._head_index
降低輸入尺寸
為了儲存一整秒的頻率頻譜(每時步5毫秒),我們需要一個包含200個元素的緩衝區,其中每個元素包含頻率幅度的陣列。如果我們需要類人的頻譜解析度,這些陣列將包含3500個值。然後200個時步就需要處理700000個值。
將長度為700000的輸入傳給人工神經網路,在算力上太昂貴了。降低頻譜和時域解析度,或者儲存更短時期的頻譜資訊,可以緩解這一問題。
我們也可以借鑑Wavenet架構,使用 空洞因果卷積(dilated causal convolutions) 以優化原始音訊樣本中的大量序列資料的分析。如A. Van Den Oord等所言,空洞卷積是應用於大於自身長度的區域,以特定步驟跳過輸入值的過濾器 。
最近傳入的頻率資料在瞬時聲音分析中起決定性作用,根據這一假定,我們可以用 空洞頻譜緩衝區 來降低算力記憶的大小。

兩種使用空洞頻譜緩衝區卷積的方法
空洞緩衝區的值可以直接選擇單個值,也可以組合若干時步,提取平均數或中位數。
空洞頻譜緩衝區背後的動機是在記憶中保留最近的頻譜印象的同時,以高效的方式同時保持關於上下文的部分資訊。
下面是使用Gammatone濾波器組構造空洞頻譜的程式碼片段。注意這裡使用的是離線處理,不過濾波器組也同樣可以實時應用,在環形緩衝區中插入頻譜幀。
from gammatone import gtgram import numpy as np class GammatoneFilterbank: def __init__(self, sample_rate, window_time, hop_time, num_filters, cutoff_low): self.sample_rate = sample_rate self.window_time = window_time self.hop_time = hop_time self.num_filters = num_filters self.cutoff_low = cutoff_low def make_spectrogram(self, audio_samples): return gtgram.gtgram(audio_samples, self.sample_rate, self.window_time, self.hop_time, self.num_filters, self.cutoff_low) def make_dilated_spectral_frames(self, audio_samples, num_frames, dilation_factor): spectrogram = self.make_spectrogram(audio_samples) spectrogram = np.swapaxes(spectrogram, 0, 1) dilated_frames = np.zeros((len(spectrogram), num_frames, len(spectrogram[0]))) for i in range(len(spectrogram)): for j in range(num_frames): dilation = np.power(dilation_factor, j) if i - dilation < 0: dilated_frames[i][j] = spectrogram[0] else: dilated_frames[i][j] = spectrogram[i - dilation] return dilated_frames

嵌入緩衝區
在人類記憶的許多模式中,感官記憶經過 選擇性記憶 這個過濾器,以避免短時記憶資訊過載 。由於人類的認知資源有限,分配注意力到特定聽覺感知以優化心智慧量的消耗是一項優勢。
我們可以通過擴充套件 自編碼器 神經網路架構實現這一方法。基於這一架構,我們可以給它傳入空洞頻率頻譜緩衝,以生成嵌入,而不是僅僅傳入瞬時頻率資訊,這就結合了感官聲音記憶和選擇性注意瓶頸。為了處理序列化資訊,我們可以使用 序列到序列自編碼器 架構 。
序列到序列(Seq2Seq)模型通常使用LSTM單元編碼序列資料(例如,一個英語句子)為內部表示(該表示包含整個句子的壓縮“含義”)。這個內部表示之後可以解碼回一個序列(例如,一個含義相同的西班牙語句子) 。
以這種方式得到的聲音嵌入,可以使用算力負擔低的簡單前饋神經網路分析、處理。
據下圖所示訓練完網路後,右半部分(解碼部分)可以“砍掉”,從而得到一個編碼時域頻率資訊至壓縮空間的網路。在這一領域,Y. Chung等的Audio Word2Vec取得了優秀的結果,通過應用Seq2Seq自動編碼器架構成功生成了可以描述語音錄音的序列化語音結構的嵌入 。使用更多樣化的輸入資料,它可以生成以更一般的方式描述聲音的嵌入。

基於Keras
依照之前描述的方法,我們用Keras實現一個生成音訊嵌入的Seq2Seq自動編碼器。我將它稱為 聽者網路 ,因為它的目的是“聽取”傳入的聲音序列,並將它壓縮為一個更緊湊的有意義表示,以供分析和處理。
我們使用了UrbanSound8K資料集(包含3小時左右的音訊)訓練這個網路。UrbanSound8K資料集包含歸好類的環境音片段。我們使用Gammatone濾波器組處理聲音,並將其分割為8時步的空洞頻譜緩衝區(每個包含100個頻譜濾波器)。
from keras.models import Model from keras.layers import Input, LSTM, RepeatVector def prepare_listener(timesteps, input_dim, latent_dim, optimizer_type, loss_type): inputs = Input(shape=(timesteps, input_dim)) encoded = LSTM(int(input_dim / 2), activation="relu", return_sequences=True)(inputs) encoded = LSTM(latent_dim, activation="relu", return_sequences=False)(encoded) decoded = RepeatVector(timesteps)(encoded) decoded = LSTM(int(input_dim / 2), activation="relu", return_sequences=True)(decoded) decoded = LSTM(input_dim, return_sequences=True)(decoded) autoencoder = Model(inputs, decoded) encoder = Model(inputs, encoded) autoencoder.compile(optimizer=optimizer_type, loss=loss_type, metrics=['acc']) return autoencoder, encoder

聽者網路的損失函式使用 均方誤差 ,優化演算法使用 Adagrad ,在一張NVIDIA GTX 1070上訓練了50個epoch,達到了42%的重建精確度。因為訓練耗時比較長,所以我在訓練進度看起來還沒有飽和的時候就停止了訓練。我很想知道,基於更大的資料集和更多算力資源訓練後這一模型表現如何。
肯定還有很多可以改進的地方,不過下面的影象表明,在3.2的壓縮率下,模型捕捉到了輸入序列的大致結構。

結語
在下一篇文章(也是本系列的最後一篇文章)中,我們將建立一個分析音訊嵌入的網路。
欲知後事,敬請關注。喜歡本文,歡迎點贊。
也歡迎在LinkedIn上加我(danielrothmann)。
參考文獻
- J. J. Eggermont, “Between sound and perception: reviewing the search for a neural code.,” Hear. Res., vol. 157, no. 1–2, pp. 1–42, Jul. 2001.
- C. Alain, D. L. Woods, and R. T. Knight, “A distributed cortical network for auditory sensory memory in humans,” Brain Res., vol. 812, no. 1–2, pp. 23–37, Nov. 1998.
- A. Wingfield, “Evolution of Models of Working Memory and Cognitive Resources,” Ear Hear., vol. 37, p. 35S–43S, 2016.
- “Implementing Circular/Ring Buffer in Embedded C”, Embedjournal.com, 2014. [Online]. Available: https:// embedjournal.com/implem enting-circular-buffer-embedded-c/
- A. Van Den Oord et al., “Wavenet: A Generative Model for Raw Audio.”
- Y.-A. Chung, C.-C. Wu, C.-H. Shen, H.-Y. Lee, and L.-S. Lee, “Audio Word2Vec: Unsupervised Learning of Audio Segment Representations Using Sequence-to-Sequence Autoencoder,” in Proceedings of the Annual Conference of the International Speech Communication Association, 2016, pp. 765–769.
- F. Chollet, “A ten-minute introduction to sequence-to-sequence learning in Keras”, Blog.keras.io, 2018. [Online]. Available: https:// blog.keras.io/a-ten-min ute-introduction-to-sequence-to-sequence-learning-in-keras.html