觸發字檢測 trigger word detection
歡迎來到這個專業的最終編程任務!
在本周的視頻中,您學習了如何將深度學習應用於語音識別。在此任務中,您將構建語音數據集並實現觸發詞檢測算法(有時也稱為關鍵字檢測或喚醒字檢測)。觸發詞檢測技術允許Amazon Alexa,Google Home,Apple Siri和Baidu DuerOS 等設備聽到某個單詞后喚醒。
對於本練習,我們的觸發詞將是“Activate”。每次聽到你說“Activate”,它都會發出“chiming”的聲音。在此作業結束時,您將能夠錄制自己說話的片段,並在檢測到您說“activate”時讓算法觸發鈴聲。
完成此任務后,您也可以將其擴展為在筆記本電腦上運行,這樣每次您說“激活”它都會啟動您喜歡的應用程序,或打開您家中的網絡連接燈,或觸發其他一些事件?
在此作業中,您將學習:
- 構建語音識別項目
- 合成和處理錄音以創建訓練/開發數據集
- 訓練觸發詞檢測模型並進行預測
讓我們開始吧!運行以下單元格以加載要使用的包。
import numpy as np
from pydub import AudioSegment #需要安裝ffmpeg
import random
import sys
import io
import os
import glob
import IPython
from td_utils import *
%matplotlib inline
1 - 數據合成:創建語音數據集
讓我們首先為觸發詞檢測算法構建數據集。理想情況下,語音數據集應盡可在您要運行它的應用程序的地方采集。在這種情況下,您希望在工作環境(圖書館,家庭,辦公室,開放空間......)中檢測“activate”一詞。因此,您需要在不同的背景聲音上創建正面詞(“activate”)和負面詞(除激活之外的隨機詞)的混合錄音。我們來看看如何創建這樣的數據集。
1.1 - 聽取數據
你的一個朋友正在幫助你完成這個項目,他們去了該地區的圖書館,咖啡館,餐館,家庭和辦公室,以記錄背景噪音,以及人們說正面/負面詞語的音頻片段。該數據集包括以各種說話口音的人。
在raw_data目錄中,您可以找到正面單詞,負面單詞和背景噪音的原始音頻文件的子集。您將使用這些音頻文件來合成數據集以訓練模型。 “activate”目錄包含人們說“activate”一詞的正面例子。 “negatives”目錄包含人們說“activate”以外的隨機詞的負面例子。每個錄音有一個單詞。 “backgrounds”目錄包含10s的不同環境中的背景噪聲剪輯。
運行下面的單元格以聽取一些示例。
IPython.display.Audio("./raw_data/activates/1.wav")
IPython.display.Audio("./raw_data/negatives/4.wav")
IPython.display.Audio("./raw_data/backgrounds/1.wav")
您將使用這三種類型的記錄(正/負/背景)來創建標記數據集 labelled dataset。
1.2 - 從錄音到頻譜圖
什么是錄音?麥克風記錄的空氣壓力隨時間變化很小,正是這些氣壓的微小變化使您的耳朵也感覺到聲音。您可以認為錄音是一長串數字,用於測量麥克風檢測到的微小氣壓變化。我們將使用44100 Hz(或44100Hertz)的音頻采樣。這意味着麥克風每秒給我們44100個數字。因此,10秒音頻剪輯由441000個數字表示(= \(10 \times 44100\))。
很難從音頻的這種“"raw" representation audio中找出“activate”這個詞是否被說出來。為了幫助您的序列模型更容易學習檢測觸發詞,我們將計算音頻的譜圖spectrogram 。頻譜圖告訴我們在某個時刻音頻片段中存在多少不同的頻率。
(如果你曾經進行過信號處理或傅立葉變換的高級課程,則通過在原始音頻信號上滑動窗口來計算頻譜圖,並使用傅里葉變換計算每個窗口中最活躍的頻率。如果不理解上一句話,不用擔心。)
讓我們看一個例子。
IPython.display.Audio("audio_examples/example_train.wav")
x = graph_spectrogram("audio_examples/example_train.wav")

上圖表示了在多個時間步長(x軸)上每個頻率(y軸)的活動程度。
The graph above represents how active each frequency is (y axis) over a number of time-steps (x axis).
音頻錄制的頻譜圖,其中顏色顯示不同時間點音頻中不同頻率(大聲)的存在程度。 綠色方塊表示某個頻率在音頻片段中更活躍或更多(更響亮); 藍色方塊表示較不活躍的頻率。
輸出頻譜圖的尺寸取決於頻譜圖軟件的pydub模塊使用它來合成音頻超參數和輸入的長度。 在這款筆記本中,我們將使用10秒的音頻剪輯作為我們培訓示例的“標准長度”。 頻譜圖的時間步長為5511.稍后您將看到頻譜圖將是進入網絡的輸入x,因此Tx = 5511。
輸出頻譜圖的尺寸取決於頻譜圖軟件的超參數和輸入的長度。 這里,我們將使用10秒音頻剪輯作為我們培訓示例的“標准長度”。 頻譜圖的時間步長為5511.稍后您將看到頻譜圖將是輸入網絡的 $ x $ ,因此 $ T_x = 5511 $。
_, data = wavfile.read("audio_examples/example_train.wav")
print("Time steps in audio recording before spectrogram", data[:,0].shape)
print("Time steps in input after spectrogram(頻譜)", x.shape)
Time steps in audio recording before spectrogram (441000,)
Time steps in input after spectrogram(頻譜) (101, 5511)
現在你可以定義:
Tx = 5511 # 從頻譜圖輸入到模型的時間步數
#The number of time steps input to the model from the spectrogram
n_freq = 101 # 在頻譜圖的每個時間步輸入模型的頻率數
#Number of frequencies input to the model at each time step of the spectrogram
請注意,即使我們的默認訓練示例長度為10秒,也可以將10秒的時間離散化為不同的值。你已經看過441000(原始音頻)和5511(頻譜圖 spectrogram)。在前一種情況下,每一步代表\(10/441000 \approx 0.000023\)秒。在第二種情況下,每一步代表\(10/5511 \approx 0.0018\) 秒。
對於10秒的音頻,您將在此作業中看到的鍵值為:
- \(10000\) (由
pydub
模塊用於合成音頻) - \(1375 = T_y\) (將構建的GRU輸出中的步)
請注意,這些表示中的每一個 each of these representations 都恰好對應於10秒的時間。只是他們在不同程度上將它們離散化。所有這些都是超參數並且可以更改(除了441000,這是麥克風的功能)。我們選擇了語音系統標准范圍內的值。
考慮上面的$ T_y = 1375 $數字。這意味着對於模型的輸出,我們將10s離散化為1375個時間間隔 (each one of length \(10/1375 \approx 0.0072\)s),並嘗試預測每個時間間隔是否有人最近說完“activate”。 “
還要考慮上面的10000號碼。這相當於將10秒剪輯離散化為10/10000 = 0.001秒的間隔itervals。 0.001秒也稱為1 millisecond 毫秒,或1ms。因此,當我們說我們按照1ms間隔進行離散化時,這意味着我們使用了10,000步。
Ty = 1375 # The number of time steps in the output of our model
1.3 - 生成單個訓練示例
由於語音數據難以獲取和標記,因此您將使用 activates,negatives和backgrounds的音頻剪輯合成訓練數據。 錄制大量10秒音頻片段並且其中有隨機“activates”是很慢的。 相反,我們更容易記錄大量的正面和負面詞,並分別記錄背景噪音(或從免費在線資源下載背景噪音)。
要綜合單個訓練示例,您將:
- 選擇一個隨機的10秒背景音頻剪輯
- 隨機插入0-4個"activate"到這個10秒剪輯
- 隨機插入0-2個 negative words 到這個10秒剪輯
因為您已將“activate”一詞合成到背景剪輯中,所以明確知道在10秒剪輯中“activate”出現的時間。 稍后您會看到,這樣也可以更容易地生成label \(y^{\langle t \rangle}\)
您將使用pydub包來合成音頻。 Pydub將原始音頻文件轉換為Pydub數據結構列表 lists of Pydub data structures(這里了解詳細信息並不重要)。 Pydub使用1ms作為離散化間隔discretization interval(1ms是1毫秒= 1/1000秒),這就是為什么10sec剪輯總是用10,000步表示的原因。
# Load audio segments using pydub
activates, negatives, backgrounds = load_raw_audio()
print("background len: " + str(len(backgrounds[0]))) # Should be 10,000, since it is a 10 sec clip
print("activate[0] len: " + str(len(activates[0]))) # Maybe around 1000, since an "activate" audio clip is usually around 1 sec (but varies a lot)
print("activate[1] len: " + str(len(activates[1]))) # Different "activate" clips can have different lengths
background len: 10000
activate[0] len: 721
activate[1] len: 731
在背景上合成 positive/negative 單詞:
給定10秒背景剪輯和短音頻剪輯(正面或負面單詞),您需要能夠將單詞的短音頻剪輯“添加”或“插入”到背景音頻上。 為了確保插入到背景中的音頻片段不重疊,您將跟蹤先前插入的音頻片段的時間。 您將在背景上插入多個positive/negative單詞的剪輯,並且您不希望在某個與您之前添加的另一個剪輯重疊的地方插入“activate”或隨機字。
為了清楚起見,當您在咖啡館噪音的10秒剪輯中插入1秒“activate”時,您最終得到一個10秒的剪輯,聽起來像某人在咖啡館中“激活”,“activate”疊加在背景咖啡廳噪音上。 你沒有不會得到一個11秒的剪輯。 稍后您將看到pydub如何完成它。
在疊加的同時創建標簽:::
回想一下,標簽 \(y^{\langle t \rangle}\) 表示某人是否剛剛說完“激活”。 給定背景剪輯,我們可以為所有\(t\)初始化\(y^{\langle t \rangle}=0\) ,因為剪輯不包含任何“activates”。
當您插入或覆蓋“activate”剪輯時,您還將更新\(y^{\langle t \rangle}\),以便輸出的50 steps具有target label 1. 您將訓練GRU以檢測何時有人完成說“activate”。 例如,假設合成的“activate”剪輯在10秒音頻中的5秒標記處結束---正好在剪輯的一半處。 回想一下$ T_y = 1375 $,所以時間步 $687 = $ int(1375*0.5)
對應於5秒進入音頻的那一刻。 所以,你將設置\(y^{\langle 688 \rangle} = 1\)。 此外,如果GRU在說完的短時間內檢測到“activate”,你會非常滿意,因此我們實際上將標簽\(y^{\langle t \rangle}\)的50個連續值設置為1.具體來說, 我們有\(y^{\langle 688 \rangle} = y^{\langle 689 \rangle} = \cdots = y^{\langle 737 \rangle} = 1\)。
這是合成訓練數據的另一個原因:如上所述生成這些標簽\(y^{\langle t \rangle}\)相對簡單。 相比之下,如果您在麥克風上錄制了10秒的音頻,那么一個人收聽它並在"activate" 完成后手動標記是非常耗時的。
這是一個圖形說明標簽 \(y^{\langle t \rangle}\),我們插入“activate”,“innocent”,activate“,”baby“ 的剪輯。 注意,正面標簽”1“是只關聯 positive words。
要實現訓練集合成過程,您將使用以下輔助函數。 所有這些功能都將使用1ms的離散化間隔,因此10秒的音頻可以離散化為10,000步。
1.get_random_time_segment(segment_ms)
在我們的背景音頻中獲得一個隨機時間段
2.is_overlapping(segment_time,existing_segments)
檢查時間段是否與現有段重疊
3.insert_audio_clip(background,audio_clip,existing_times)
使用get_random_time_segment
和is_overlapping
在我們的背景音頻中隨機插入音頻片段。
4.insert_ones(y,segment_end_ms)
在“activate”一詞之后將1插入到我們的標簽向量y中
函數get_random_time_segment(segment_ms)返回一個隨機時間段,我們可以在其上插入持續時間為segment_ms的音頻剪輯。 仔細閱讀代碼,確保您了解它的作用。
def get_random_time_segment(segment_ms):
"""
Gets a random time segment of duration segment_ms in a 10,000 ms audio clip.
Arguments:
segment_ms -- the duration of the audio clip in ms ("ms" stands for "milliseconds")
Returns:
segment_time -- a tuple of (segment_start, segment_end) in ms
"""
#10秒的音頻10000 steps, 隨機選擇一個初始點
segment_start = np.random.randint(low=0, high=10000-segment_ms) # Make sure segment doesn't run past the 10sec background
segment_end = segment_start + segment_ms - 1
return (segment_start, segment_end)
接下來,假設您已在段(1000,1800)和(3400,4500)處插入音頻剪輯。 即,第一段從步驟1000開始,並在步驟1800結束。現在,如果我們正在考慮在(3000,3600)處插入新的音頻片段,這是否與先前插入的片段之一重疊? 在這種情況下,(3000,3600)和(3400,4500)重疊,所以我們應該決定不在這里插入一個剪輯。
出於該功能的目的,定義(100,200)和(200,250)是重疊的,因為它們在時間步長200處重疊。但是,(100,199)和(200,250)是不重疊的。
練習:實現is_overlapping(segment_time,existing_segments)
來檢查新的時間段是否與任何先前的段重疊。 您需要執行兩個步驟:
1.創建一個“False”標志,如果發現存在重疊,稍后將設置為“True”。
2.遍歷previous_segments的開始和結束時間。 將這些時間與段的開始和結束時間進行比較。 如果存在重疊,請將(1)中定義的標志設置為True。 您可以使用:
for ....:
if ... <= ... and ... >= ...:
...
提示:如果段 segment 在前一段結束之前開始,並且段在前一段開始之后結束 是重疊。
Hint: There is overlap if the starts before the previous segment ends, and the segment ends after the previous segment starts.
# GRADED FUNCTION: is_overlapping
def is_overlapping(segment_time, previous_segments):
"""
Checks if the time of a segment overlaps with the times of existing segments.
Arguments:
segment_time -- a tuple of (segment_start, segment_end) for the new segment
previous_segments -- a list of tuples of (segment_start, segment_end) for the existing segments
#當前存在的所有插入的時間段集合 (segment_start, segment_end)
Returns:
True if the time segment overlaps with any of the existing segments, False otherwise
"""
segment_start, segment_end = segment_time
### START CODE HERE ### (≈ 4 line)
# Step 1: Initialize overlap as a "False" flag. (≈ 1 line)
overlap = False
# Step 2: loop over the previous_segments start and end times.
# Compare start/end times and set the flag to True if there is an overlap (≈ 3 lines)
for previous_start, previous_end in previous_segments:
if segment_start >= previous_start and segment_start <= previous_end:
overlap = True
### END CODE HERE ###
return overlap
overlap1 = is_overlapping((950, 1430), [(2000, 2550), (260, 949)])
overlap2 = is_overlapping((2305, 2950), [(824, 1532), (1900, 2305), (3424, 3656)])
print("Overlap 1 = ", overlap1)
print("Overlap 2 = ", overlap2)
Overlap 1 = False
Overlap 2 = True
Expected Output:
**Overlap 1** | False |
**Overlap 2** | True |
現在,讓我們使用以前的輔助函數在隨機時間將新的音頻剪輯插入到10sec背景上,但確保任何新插入的片段不會與之前的片段重疊。
練習:實現insert_audio_clip()
將音頻片段疊加到背景10秒片段上。 您需要執行4個步驟:
1.以ms為單位獲取正確持續時間的隨機時間段。
2.確保時間段不與之前的任何時間段重疊。 如果它重疊,則返回步驟1並選擇一個新的時間段。
3.將新時間段添加到現有時間段列表中,以便跟蹤您插入的所有時間段。
4.使用pydub在背景上疊加音頻片段。 我們已經為您實現了這一點。
# GRADED FUNCTION: insert_audio_clip
def insert_audio_clip(background, audio_clip, previous_segments):
"""
Insert a new audio segment over the background noise at a random time step, ensuring that the
audio segment does not overlap with existing segments.
Arguments:
background -- a 10 second background audio recording.
audio_clip -- the audio clip to be inserted/overlaid.
previous_segments -- times where audio segments have already been placed
Returns:
new_background -- the updated background audio
"""
# Get the duration of the audio clip in ms
segment_ms = len(audio_clip) #需要插入的音頻的長度(ms)
### START CODE HERE ###
# Step 1: Use one of the helper functions to pick a random time segment onto which to insert
# the new audio clip. (≈ 1 line)
segment_time = get_random_time_segment(segment_ms)
# Step 2: Check if the new segment_time overlaps with one of the previous_segments. If so, keep
# picking new segment_time at random until it doesn't overlap. (≈ 2 lines)
while is_overlapping(segment_time, previous_segments):
segment_time = get_random_time_segment(segment_ms)
# Step 3: Add the new segment_time to the list of previous_segments (≈ 1 line)
previous_segments.append(segment_time)
### END CODE HERE ###
# Step 4: Superpose audio segment and background 合成新的聲音
new_background = background.overlay(audio_clip, position = segment_time[0])
return new_background, segment_time
np.random.seed(5)
audio_clip, segment_time = insert_audio_clip(backgrounds[0], activates[0], [(3790, 4400)])
audio_clip.export("insert_test.wav", format="wav")
print("Segment Time: ", segment_time)
IPython.display.Audio("insert_test.wav")
Expected Output
**Segment Time** | (2254, 3169) |
# Expected audio
IPython.display.Audio("audio_examples/insert_reference.wav")
最后,使用代碼更新標簽\(y^{\langle t \rangle}\),假設您剛剛插入了“activate”。 在下面的代碼中,y
是一個(1,1375)
維向量,因為$ T_y = 1375 $。
如果“activate”在時間步\(t\)結束,則設置\(y^{\langle t+1 \rangle} = 1\)以及后面最多49個附加連續值為1。 但是,請確保不要運行到數組的末尾並嘗試更新y [0] [1375]
,因為有效索引是y [0] [0]
到y [0] [ 1374]
因為$ T_y = 1375 $。 因此,如果“激活”在步驟1370結束,則僅得到 y[0][1371] = y[0][1372] = y[0][1373] = y[0][1374] = 1
練習:實現insert_ones()
。 你可以使用for循環。(如果你會使用python切片操作,也可以使用切片來對其進行矢量化。)如果一個段以segment_end_ms
結尾(使用10000 step discretization),則將其轉換為輸出的索引 \(y\) (使用$ 1375 $ step discretization),我們將使用以下公式:
segment_end_y = int(segment_end_ms * Ty / 10000.0)
# GRADED FUNCTION: insert_ones
def insert_ones(y, segment_end_ms):
"""
Update the label vector y. The labels of the 50 output steps strictly after the end of the segment
should be set to 1. By strictly we mean that the label of segment_end_y should be 0 while, the
50 followinf labels should be ones.
Arguments:
y -- numpy array of shape (1, Ty), the labels of the training example
segment_end_ms -- the end time of the segment in ms
Returns:
y -- updated labels
"""
# duration of the background (in terms of spectrogram time-steps)
segment_end_y = int(segment_end_ms * Ty / 10000.0)
# segment_end_y / Ty = segment_end_ms (activate結束step) / 10000 (總的step)
# Add 1 to the correct index in the background label (y)
### START CODE HERE ### (≈ 3 lines)
for i in range(segment_end_y + 1, segment_end_y + 51):
if i < Ty:
y[0, i] = 1
### END CODE HERE ###
return y
arr1 = insert_ones(np.zeros((1, Ty)), 9700)
plt.plot(insert_ones(arr1, 4251)[0,:])
print("sanity checks:", arr1[0][1333], arr1[0][634], arr1[0][635])
sanity checks: 0.0 1.0 0.0
最后,您可以使用insert_audio_clip
和insert_ones
來創建一個新的訓練示例。
練習:實現create_training_example()
。 您需要執行以下步驟:
1.將標簽向量$ y $初始化為numpy數組,shape \((1,T_y)\)。
2.將 existing segments的集合初始化為空列表。
3.隨機選擇0到4個“activate”音頻剪輯,然后將它們插入10秒剪輯。 還要在標簽向量$ y $中的正確位置插入標簽。
4.隨機選擇0到2個負音頻剪輯,並將它們插入10秒剪輯中。
# GRADED FUNCTION: create_training_example
def create_training_example(background, activates, negatives):
"""
Creates a training example with a given background, activates, and negatives.
Arguments:
background -- a 10 second background audio recording
activates -- a list of audio segments of the word "activate"
negatives -- a list of audio segments of random words that are not "activate"
Returns:
x -- the spectrogram of the training example
y -- the label at each time step of the spectrogram
"""
# Set the random seed
np.random.seed(18)
# Make background quieter
background = background - 20
### START CODE HERE ###
# Step 1: Initialize y (label vector) of zeros (≈ 1 line)
y = np.zeros((1, Ty))
# Step 2: Initialize segment times as empty list (≈ 1 line)
previous_segments = []
### END CODE HERE ###
# Select 0-4 random "activate" audio clips from the entire list of "activates" recordings
number_of_activates = np.random.randint(0, 5)
#print(number_of_activates)
random_indices = np.random.randint(len(activates), size=number_of_activates)
random_activates = [activates[i] for i in random_indices]
### START CODE HERE ### (≈ 3 lines)
# Step 3: Loop over randomly selected "activate" clips and insert in background
for random_activate in random_activates:
# Insert the audio clip on the background
background, segment_time = insert_audio_clip(background, random_activate, previous_segments)
# Retrieve segment_start and segment_end from segment_time
segment_start, segment_end = segment_time
# Insert labels in "y"
y = insert_ones(y, segment_end)
### END CODE HERE ###
# Select 0-2 random negatives audio recordings from the entire list of "negatives" recordings
number_of_negatives = np.random.randint(0, 3)
random_indices = np.random.randint(len(negatives), size=number_of_negatives)
random_negatives = [negatives[i] for i in random_indices]
### START CODE HERE ### (≈ 2 lines)
# Step 4: Loop over randomly selected negative clips and insert in background
for random_negative in random_negatives:
# Insert the audio clip on the background
background, _ = insert_audio_clip(background, random_negative, previous_segments)
### END CODE HERE ###
# Standardize the volume of the audio clip
background = match_target_amplitude(background, -20.0)
# Export new training example
file_handle = background.export("train" + ".wav", format="wav")
print("File (train.wav) was saved in your directory.")
# Get and plot spectrogram of the new recording (background with superposition of positive and negatives)
x = graph_spectrogram("train.wav")
return x, y
x, y = create_training_example(backgrounds[0], activates, negatives)
File (train.wav) was saved in your directory.
D:\software\Anaconda3\envs\tensorflow\lib\site-packages\matplotlib\axes\_axes.py:7674: RuntimeWarning: divide by zero encountered in log10
Z = 10. * np.log10(spec)
現在,您可以聆聽您創建的訓練示例,並將其與上面生成的頻譜圖進行比較。
IPython.display.Audio("train.wav")
Expected Output
IPython.display.Audio("audio_examples/train_reference.wav")
最后,您可以繪制生成的訓練示例的關聯標簽。
plt.plot(y[0])
Expected Output
1.4 - 完整的訓練集
您現在已經實現了生成單個訓練示例所需的代碼。 我們使用此過程生成一個大型訓練集。 為了節省時間,我們已經生成了一組訓練樣本。
# Load preprocessed training examples
X = np.load("./XY_train/X.npy")
Y = np.load("./XY_train/Y.npy")
1.5 - Development set
為了測試我們的模型,我們記錄了一個包含25個示例的development set。 雖然我們的訓練數據是合成的,但我們希望使用與實際輸入相同的分布來創建development set。 因此,我們錄制了25個10秒鍾的人們說“activate”和其他隨機單詞的音頻剪輯,並用手標記。 這遵循課程3中描述的原則,即我們應該將dev set設置為盡可能與測試集分布相似; 這就是我們的 dev set使用真實而非合成音頻的原因。
# Load preprocessed dev set examples
X_dev = np.load("./XY_dev/X_dev.npy")
Y_dev = np.load("./XY_dev/Y_dev.npy")
2 - 模型
現在您已經構建了一個數據集,讓我們編寫並訓練一個觸發詞檢測模型!
該模型將使用1-D卷積層,GRU層和密集層。 讓我們加載允許您在Keras中使用這些圖層的包。 這可能需要一分鍾才能加載。
from keras.callbacks import ModelCheckpoint
from keras.models import Model, load_model, Sequential
from keras.layers import Dense, Activation, Dropout, Input, Masking, TimeDistributed, LSTM, Conv1D
from keras.layers import GRU, Bidirectional, BatchNormalization, Reshape
from keras.optimizers import Adam
D:\software\Anaconda3\envs\tensorflow\lib\importlib\_bootstrap.py:222: RuntimeWarning: numpy.dtype size changed, may indicate binary incompatibility. Expected 88 from C header, got 96 from PyObject
return f(*args, **kwds)
Using TensorFlow backend.
2.1 - 建立模型
這是我們將使用的架構。 花一些時間來查看模型,看看它是否有意義。
該模型的一個關鍵步驟是1D卷積步驟(靠近圖3的底部)。它輸入5511步的頻譜,並輸出一個1375步輸出,然后由多個層進一步處理,以獲得最終的$ T_y = 1375 $步輸出。該層的作用類似於您在課程4中看到的2D卷積層(這里是1D),提取低級特征(low-level features),然后可能生成較小維度的輸出。
在計算上,1-D轉換層(conv layer)也有助於加速模型,因為現在GRU只需要處理1375個時間步而不是5511個時間步。兩個GRU層從左到右讀取輸入序列,然后最終使用dense+sigmoid層來預測\(y^{\langle t \rangle}\)。因為\(y\)是二進制值(0或1),我們在最后一層使用sigmoid輸出來估計輸出為1的概率,對應於用戶剛剛說“activate”。
請注意,我們使用單向RNN而不是雙向RNN。這對於觸發字檢測非常重要,因為我們希望能夠在說出觸發字后立即檢測到觸發字(detect the trigger word almost immediately after it is said)。如果我們使用雙向RNN,我們必須等待整個10秒的音頻被記錄,然后才能判斷音頻片段的第一秒是否有“激活”。
實現模型可以分四步完成:
步驟1:CONV層。使用Conv1D()
來實現這一點,使用196個過濾器,
過濾器大小為15(kernel_size = 15
),步幅stride為4. [See documentation.]
步驟2:第一個GRU層。要生成GRU圖層,請使用:
X = GRU(units = 128, return_sequences = True)(X)
設置return_sequences = True
可確保將所有GRU的隱藏狀態喂給下一層。請記住使用Dropout和BatchNorm層。
步驟3:第二個GRU層。這類似於以前的GRU層(記得使用return_sequences = True
),但有一個額外的dropout層。
步驟4:創建時間分布time-distributed 的 dense layer,如下所示:
X = TimeDistributed(Dense(1, activation = "sigmoid"))(X)
這將創建一個密集的層,后跟一個sigmoid,因此用於密集層的參數對於每個時間步都是相同的。[See documentation.]
練習:實現model()
,體系結構如圖3所示。
# GRADED FUNCTION: model
def model(input_shape):
"""
Function creating the model's graph in Keras.
Argument:
input_shape -- shape of the model's input data (using Keras conventions)
Returns:
model -- Keras model instance
"""
X_input = Input(shape = input_shape)
### START CODE HERE ###
# Step 1: CONV layer (≈4 lines)
X = Conv1D(196, 15, strides=4)(X_input) # CONV1D
X = BatchNormalization()(X) # Batch normalization
X = Activation('relu')(X) # ReLu activation
X = Dropout(0.8)(X) # dropout (use 0.8)
# Step 2: First GRU Layer (≈4 lines)
X = GRU(128, return_sequences=True)(X) # GRU (use 128 units and return the sequences)
X = Dropout(0.8)(X) # dropout (use 0.8)
X = BatchNormalization()(X) # Batch normalization
# Step 3: Second GRU Layer (≈4 lines)
X = GRU(128, return_sequences=True)(X) # GRU (use 128 units and return the sequences)
X = Dropout(0.8)(X) # dropout (use 0.8)
X = BatchNormalization()(X) # Batch normalization
X = Dropout(0.8)(X) # dropout (use 0.8)
# Step 4: Time-distributed dense layer (≈1 line)
X = TimeDistributed(Dense(1, activation = "sigmoid"))(X) # time distributed (sigmoid)
### END CODE HERE ###
model = Model(inputs = X_input, outputs = X)
return model
model = model(input_shape = (Tx, n_freq))
讓我們打印模型摘要以跟蹤形狀。
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) (None, 5511, 101) 0
_________________________________________________________________
conv1d_2 (Conv1D) (None, 1375, 196) 297136
_________________________________________________________________
batch_normalization_4 (Batch (None, 1375, 196) 784
_________________________________________________________________
activation_2 (Activation) (None, 1375, 196) 0
_________________________________________________________________
dropout_5 (Dropout) (None, 1375, 196) 0
_________________________________________________________________
gru_3 (GRU) (None, 1375, 128) 124800
_________________________________________________________________
dropout_6 (Dropout) (None, 1375, 128) 0
_________________________________________________________________
batch_normalization_5 (Batch (None, 1375, 128) 512
_________________________________________________________________
gru_4 (GRU) (None, 1375, 128) 98688
_________________________________________________________________
dropout_7 (Dropout) (None, 1375, 128) 0
_________________________________________________________________
batch_normalization_6 (Batch (None, 1375, 128) 512
_________________________________________________________________
dropout_8 (Dropout) (None, 1375, 128) 0
_________________________________________________________________
time_distributed_2 (TimeDist (None, 1375, 1) 129
=================================================================
Total params: 522,561
Trainable params: 521,657
Non-trainable params: 904
_________________________________________________________________
Expected Output:
**Total params** | 522,561 |
**Trainable params** | 521,657 |
**Non-trainable params** | 904 |
網絡的輸出是形狀(None, 1375, 1) ,而輸入是(None, 5511, 101)。 Conv1D將頻譜的步數(steps)從5511減少到1375。
2.2 - Fit the model
觸發字檢測需要很長時間才能進行訓練。 為了節省時間,我們已經使用您在上面構建的架構在GPU上訓練了大約3個小時的模型,以及大約4000個示例的大型訓練集。 讓我們加載模型。
model = load_model('./models/tr_model.h5')
D:\software\Anaconda3\envs\tensorflow\lib\site-packages\keras\engine\saving.py:327: UserWarning: Error in loading the saved optimizer state. As a result, your model is starting with a freshly initialized optimizer.
warnings.warn('Error in loading the saved optimizer '
您可以使用Adam優化器和二進制交叉熵損失來進一步訓練模型(train the model further),如下所示。 這將很快運行,因為我們只訓練一個epoch,並且訓練集包含26個樣本。
opt = Adam(lr=0.0001, beta_1=0.9, beta_2=0.999, decay=0.01)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=["accuracy"])
model.fit(X, Y, batch_size = 5, epochs=1)
Epoch 1/1
26/26 [==============================] - 7s 286ms/step - loss: 0.0582 - acc: 0.9815
<keras.callbacks.History at 0x44923128>
2.3 - 測試模型
最后,讓我們看看您的模型在dev set上的表現。
loss, acc = model.evaluate(X_dev, Y_dev)
print("Dev set accuracy = ", acc)
25/25 [==============================] - 1s 52ms/step
Dev set accuracy = 0.9406254291534424
這看起來很不錯! 然而,准確性對於這項任務來說並不是一個很好的指標,因為標簽嚴重偏向0,因此只輸出0的神經網絡將獲得略高於90%的准確度。 我們可以定義更多有用的指標,例如F1得分或精確/召回。 但是,讓我們不要在這里困擾這些,而只是憑經驗看到模型是如何做的。
3 - 做出預測
現在您已經為觸發詞檢測構建了一個工作模型,讓我們用它來進行預測。 此代碼段通過網絡運行音頻(保存在wav文件中)。
可以使用您的模型對新的音頻剪輯進行預測。您首先需要計算輸入音頻剪輯的預測。
練習:實現predict_activates()。 您需要執行以下操作:
1.計算音頻文件的頻譜圖(spectrogram)
2.使用np.swap
和np.expand_dims
將輸入重新整形為大小(1,Tx,n_freqs)
3.在模型上使用向前傳播來計算每個輸出步驟的預測
def detect_triggerword(filename):
plt.subplot(2, 1, 1)
x = graph_spectrogram(filename)
# the spectogram outputs (freqs, Tx) and we want (Tx, freqs) to input into the model
x = x.swapaxes(0,1)
x = np.expand_dims(x, axis=0)
predictions = model.predict(x)
plt.subplot(2, 1, 2)
plt.plot(predictions[0,:,0])
plt.ylabel('probability')
plt.show()
return predictions
一旦您估計了在每個輸出步驟檢測到“activate”一詞的概率,您就可以在概率超過某個閾值時觸發“鳴響”聲音。此外,\(y^{\langle t \rangle}\)可能在接近“activate”之后連續的多個值接近1,但我們只想響一次。因此,我們將每75個輸出步驟最多產生一次鈴聲(產生一次后,下75個時間步內不產生)。這將有助於防止我們為單個“activate”實例插入兩個鍾聲。 (這與計算機視覺中的非最大抑制 non-max suppression 類似。)
練習:實現chime_on_activate()。您需要執行以下操作:
1.在每個輸出步驟循環預測概率
2.當預測值大於閾值且超過75個連續時間步長時,在原始音頻片段上插入“chime”聲音
使用下面代碼將1,375步離散化轉換為10,000步離散化,並使用pydub插入“chime”:
audio_clip = audio_clip.overlay(chime,position =((i / Ty)* audio.duration_seconds)* 1000)
chime_file = "audio_examples/chime.wav"
def chime_on_activate(filename, predictions, threshold):
audio_clip = AudioSegment.from_wav(filename)
chime = AudioSegment.from_wav(chime_file)
Ty = predictions.shape[1]
# Step 1: Initialize the number of consecutive output steps to 0
consecutive_timesteps = 0
# Step 2: Loop over the output steps in the y
for i in range(Ty):
# Step 3: Increment consecutive output steps
consecutive_timesteps += 1
# Step 4: If prediction is higher than the threshold and more than 75 consecutive output steps have passed
if predictions[0,i,0] > threshold and consecutive_timesteps > 75:
# Step 5: Superpose audio and background using pydub
audio_clip = audio_clip.overlay(chime, position = ((i / Ty) * audio_clip.duration_seconds)*1000)
# Step 6: Reset consecutive output steps to 0
consecutive_timesteps = 0
audio_clip.export("chime_output.wav", format='wav')
3.3 - Test on dev examples
讓我們探索一下我們的模型如何在development set中的兩個看不見的音頻剪輯上執行。 讓我們先聽兩個dev set的剪輯。
IPython.display.Audio("./raw_data/dev/1.wav")
IPython.display.Audio("./raw_data/dev/2.wav")
現在讓我們在這些音頻片段上運行模型,看看它是否在“activate”后添加了一個鈴聲!
filename = "./raw_data/dev/1.wav"
prediction = detect_triggerword(filename)
chime_on_activate(filename, prediction, 0.5)
IPython.display.Audio("./chime_output.wav")
filename = "./raw_data/dev/2.wav"
prediction = detect_triggerword(filename)
chime_on_activate(filename, prediction, 0.5)
IPython.display.Audio("./chime_output.wav")
Congratulations
這是你應該記住的:
- 數據合成是為語音問題創建大型訓練集的有效方法,特別是觸發單詞檢測。
- 在將音頻數據傳遞到RNN,GRU或LSTM之前,使用頻譜圖和可選的1D conv layer是常見的預處理步驟。
- 端到端 end-to-end 深度學習方法可用於構建非常有效的觸發字檢測系統。
恭喜你完成了最終的任務!
感謝您堅持到最后,並為您學習深度學習所付出的辛勤勞動。 我們希望您喜歡這門課程!
4 - 試試你自己樣本! (OPTIONAL/UNGRADED)
您可以在自己的音頻剪輯上試用您的模型!
錄制你說“activate”和其他隨機單詞的10秒音頻片段,並將其作為“myaudio.wav”上傳到Coursera集線器。 請務必將音頻上傳為wav文件。 如果您的音頻以不同的格式(例如mp3)錄制,則可以在線找到將其轉換為wav的免費軟件。 如果您的錄音時間不是10秒,則下面的代碼將根據需要修剪或填充它以使其達到10秒。
# Preprocess the audio to the correct format
def preprocess_audio(filename):
# Trim or pad audio segment to 10000ms
padding = AudioSegment.silent(duration=10000)
segment = AudioSegment.from_wav(filename)[:10000]
segment = padding.overlay(segment)
# Set frame rate to 44100
segment = segment.set_frame_rate(44100)
# Export as wav
segment.export(filename, format='wav')
Once you've uploaded your audio file to Coursera, put the path to your file in the variable below.
your_filename = "audio_examples/my_audio.wav"
preprocess_audio(your_filename)
IPython.display.Audio(your_filename) # listen to the audio you uploaded
最后,使用模型預測何時在10秒音頻片段中激活,並觸發鈴聲。 如果沒有正確添加蜂鳴聲,請嘗試調整chime_threshold。
chime_threshold = 0.5
prediction = detect_triggerword(your_filename)
chime_on_activate(your_filename, prediction, chime_threshold)
IPython.display.Audio("./chime_output.wav")