一文帶你讀懂深度學習之Deepmind WaveNet模型和Keras實現


本文主要通俗講述WaveNet的基本模型和Keras代碼理解,以幫助和我一樣剛剛入坑並難以理解其代碼的小白。

作者:SeanLiao

Blog:https://www.cnblogs.com/seanliao/

原創博文,轉載請注明來源。

一. 什么是WaveNet?

簡單來說,WaveNet是一種生成模型,類似VAE、GAN等,WaveNet最大的特點是可以直接生成raw audio的模型,由2017年DeepMind提出,在TTS(文字轉語音)任務上可以達到state-of-art的效果。

此外WaveNet也可以用來做生成文字、生成圖片、語音識別等。

WaveNet的具體相關可以參考以下資料:

Ref :

DeepMind WaveNet Blog 

Keras實現代碼

WaveNet論文

 

竊以為,學習一種網絡結構應該結合論文和代碼,而理解模型的基礎首先是知道模型的輸入輸出。但是這份代碼最坑爹的地方是!沒!有!注!釋!

DeepMind博客上的動圖非常清楚地展示了這個模型的工作過程。一定要看!體會!

WaveNet的網絡結構並不復雜,說白了其實就是一類變種CNN。但是介紹WaveNet的各種文章只對WaveNet的結構誇誇其談,絲毫沒有涉及模型的輸入輸出到底是什么,對小白非常不友好。

本文着重介紹WaveNet keras實現代碼中的輸入數據組織。

 

二. 模型的運作過程

  這里不談模型的原理和結構(實際上只要理解了CNN,理解WaveNet非常容易)。我們先談談WaveNet到底“做了什么”?

  由於我不知道怎么上傳動圖,大家可以到DeepMind博客上查看那張動圖。再結合下圖論文的原文:

  簡單來說,模型的核心是 對給定的輸入序列(x_1, x_2, x_3, x_4, ..., x_n) , 每次要根據之前的x_1 ~ x_n來預測x_n+1。然后將x_n+1添加在輸入序列,再由x_2 ~ x_n+1得到 x_n+2

  由此 我們就可以由一個原始的序列(x_1, x_2, x_3, x_4, ..., x_n) 作為輸入,按此模型進行構建,可以生成任意長度的序列!

  那么疑問來了,代碼怎么實現呢?博主實在討厭冗長的Tensorflow代碼,於是在github上找到了這個比較多Star的Keras版本代碼,clone下來並成功運行后開始分析。

三. Keras代碼中數據的組織

  本文開頭引用給出的Keras代碼寫得非常好,尤其是自定義層部分。由於封裝得比較高級,對小白而言看懂還是有點困難的。下面我就拿其代碼來進行簡單的分析。

  首先我們要注意到論文中的這部分。

  這樣的話,代碼就應該很明白了。 還是不懂? 沒關系,下面舉個簡單的例子。

  對於一個序列 “我是超級大帥哥”。 假如取長度為2 步長為1 作為數據進行輸入和訓練。

  則訓練時第一次輸入模型的數據:x1 = 我是 , 輸出y1 = 是超

  第二次:x2 =  是超, y2 = 超級;

  第三次: x3 = 超級, y3 = 級大

  ……

  而當訓練完畢,使用訓練好的模型生成數據時,送入x1 = 我是,理想情況是得到y1 = 是超

  只要不斷的將輸出送到輸入再進行預測,就有可能得到一個完整的序列“我是超級大帥哥”!

  (別跟我你到這里還搞不懂模型的輸入輸出是什么......)

  那么對於圖片和文字的情況呢?這里不做講解(因為我也還沒去看啊喂...),大家可以看看論文原文和github上的tensorflow代碼。

  如果到這部分都看懂了,好的,其實代碼實現也很容易理解,讀者請先簡單地閱讀一遍所有代碼,下面對輸入輸出數據的組織做簡單的分析總結。

 

模型的輸入

一段長度為fragment_length的音頻數據, 設為audio[begin : begin + fragment_length]

 

模型的輸出

相鄰於輸入數據的下一段fragment_length長的音頻序列。步長為fragment_stride,輸出為audio[ begin + fragment_stride : begin + fragment_stride + fragment_length ]

 

輸入數據的處理

1. 對單個音頻的處理有:

a) 讀取音頻。

b) 聲道處理(原代碼為單聲道音頻)。

c) 轉化為浮點數(這里應該是進行了一個縮放到0~1之間),代碼如下:

 1 def wav_to_float(x):
 2     try:
 3         max_value = np.iinfo(x.dtype).max
 4         min_value = np.iinfo(x.dtype).min
 5     except:
 6         max_value = np.finfo(x.dtype).max
 7         min_value = np.iinfo(x.dtype).min
 8     x = x.astype('float64', casting='safe')
 9     x -= min_value
10     x /= ((max_value - min_value) / 2.)
11     x -= 1.
12     return x

 

d) 轉化為ulaw編碼,使用ulaw編碼的原因是原始音頻數據是16bit的,則生成的時候一個幀音頻有2^16個輸出值(輸出節點數),再進行softmax取值,這樣的話代價高昂而且數據點取值范圍太大會影響准確率。所以對原始的音頻數據進行了ulaw編碼(可參考:https://blog.csdn.net/wzying25/article/details/79398055)

e) resample到目標采樣率。

f) 轉化為uint8數據, 代碼如下:

1 def float_to_uint8(x):
2     x += 1.
3     x /= 2.
4     uint8_max_value = np.iinfo('uint8').max
5     x *= uint8_max_value
6     x = x.astype('uint8')
7     return x

 

單個音頻處理的完整代碼如下:

 1 def process_wav(desired_sample_rate, filename, use_ulaw):
 2     # print('reading wavfile...',filename)
 3     with warnings.catch_warnings():
 4         # warnings.simplefilter("error")  # 提升警告等級?會導致np.fromstring報錯
 5         channels = scipy.io.wavfile.read(filename)
 6     file_sample_rate, audio = channels
 7     audio = ensure_mono(audio)
 8     audio = wav_to_float(audio)
 9     if use_ulaw:
10         audio = ulaw(audio)
11     audio = ensure_sample_rate(desired_sample_rate, file_sample_rate, audio)
12     audio = float_to_uint8(audio)
13     return audio

 2. 組織成模型的輸入數據

a) 將同一個說話人的音頻經過1中的處理后進行拼接,形成一個full_sequences。

b) 划分測試集,代碼中將full_sequences9 : 1進行切分,后0.1部分加入測試集。

c) full_sequences,以fragment_stride進行分割,長度為fragment_length(這里只記錄序列開始的坐標。得到若干個音頻子序列。代碼如下:

1 def fragment_indices(full_sequences, fragment_length, batch_size, fragment_stride, nb_output_bins):
2     for seq_i, sequence in enumerate(full_sequences):
3         for i in range(0, sequence.shape[0] - fragment_length, fragment_stride):
4             yield seq_i, i
5         # i為input sequence的起點 seq_i為音頻文件的id

 

d) 生成batchc得到一個full_sequence的全部子序列列表。然后按batch_size進行划分。

batches = cycle(partition_all(batch_size, indices)) # indices為列表
for batch in batches:
    if len(batch) < batch_size:
        continue
    yield np.array(
        [one_hot(full_sequences[e[0]][e[1]:e[1] + fragment_length]) for e in batch], dtype='uint8'), np.array(
        [one_hot(full_sequences[e[0]][e[1] + 1:e[1] + fragment_length + 1]) for e in batch], dtype='uint8')

e) 此時數據點的取值為0~255,轉換為onehot編碼,則輸入數據變成了一個二維張量,此時喂入模型即可。相鄰的兩個子序列前者為輸入,后者為輸出。

 

 

model的定義和原理待續。

還有一個簡單的實現Keras WaveNet Demo正在趕工……完成后會附上

實際上,用代碼實現模型最頭疼的部分就是輸入數據的組織,本文以上已經介紹完畢。對於Keras而言已經把網絡結構搭建簡化得非常容易了。只要理解了模型的原理,實現起來就很容易了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM