關(guān)鍵字語(yǔ)音識(shí)別
在本周的視頻中,你學(xué)習(xí)了如何將深度學(xué)習(xí)應(yīng)用于語(yǔ)音識(shí)別。在此作業(yè)中,你將構(gòu)建語(yǔ)音數(shù)據(jù)集并實(shí)現(xiàn)用于關(guān)鍵詞檢測(cè)(有時(shí)也稱為喚醒詞或觸發(fā)詞檢測(cè))的算法。關(guān)鍵詞識(shí)別是一項(xiàng)技術(shù),可讓諸如Amazon Alexa,Google Home,Apple Siri和Baidu DuerOS之類的設(shè)備在聽(tīng)到某個(gè)特定單詞時(shí)回應(yīng)。
對(duì)于本練習(xí),我們的觸發(fā)詞將是"Activate."。每次聽(tīng)到你說(shuō)“激活”時(shí),它都會(huì)發(fā)出“蜂鳴聲”。作業(yè)完成后,你將可以錄制自己的講話片段,并在算法檢測(cè)到你說(shuō)"Activate"時(shí)觸發(fā)提示音。
完成此任務(wù)后,也許你還可以將其擴(kuò)展為在筆記本電腦上運(yùn)行,以便每次你說(shuō)“Activate”時(shí),它就會(huì)啟動(dòng)你喜歡的應(yīng)用程序,或者打開(kāi)房屋中的網(wǎng)絡(luò)連接燈,或觸發(fā)其他事件?
在本作業(yè)中,你將學(xué)習(xí):
- 構(gòu)建語(yǔ)言識(shí)別項(xiàng)目
- 合成和處理音頻記錄以創(chuàng)建訓(xùn)練/開(kāi)發(fā)數(shù)據(jù)集
- 訓(xùn)練關(guān)鍵詞檢測(cè)模型并做出預(yù)測(cè)
import numpy as np
from pydub import AudioSegment
import random
import sys
import io
import os
import glob
import IPython
from td_utils import *
%matplotlib inline
d:\vr\virtual_environment\lib\site-packages\pydub\utils.py:165: RuntimeWarning: Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work
warn("Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work", RuntimeWarning)
1 合成數(shù)據(jù):創(chuàng)建語(yǔ)音數(shù)據(jù)集
讓我們從為觸發(fā)詞檢測(cè)算法構(gòu)建數(shù)據(jù)集開(kāi)始。語(yǔ)音數(shù)據(jù)集在理想情況下應(yīng)盡可能接近要在其上運(yùn)行它的應(yīng)用程序。在這種情況下,你想在工作環(huán)境(圖書(shū)館,家庭,辦公室,開(kāi)放空間…)中檢測(cè)到"activate"一詞。因此,你需要在不同的背景聲音上混合使用positive詞(“activate”)和negative詞(除activate以外的隨機(jī)詞)來(lái)創(chuàng)建錄音。讓我們看看如何創(chuàng)建這樣的數(shù)據(jù)集。
1.1 試聽(tīng)數(shù)據(jù)
你的一位朋友正在幫助你完成這個(gè)項(xiàng)目,他們?nèi)チ嗽摰貐^(qū)各地的圖書(shū)館,咖啡館,餐館,家庭和辦公室,以記錄背景噪音以及人們說(shuō)positive/negative詞的音頻片段。該數(shù)據(jù)集包括以各種口音講話的人。
在raw_data目錄中,你可以找到原始音頻文件的子集,包括正詞,負(fù)詞和背景噪音。你將使用這些音頻文件來(lái)合成數(shù)據(jù)集以訓(xùn)練模型。"activate"目錄包含人們說(shuō)"activate"一詞的正面示例。"negatives"目錄包含人們說(shuō)"activate"以外的隨機(jī)單詞的否定示例。每個(gè)音頻記錄只有一個(gè)字。"backgrounds"目錄包含10秒的不同環(huán)境下的背景噪音片段。
運(yùn)行下面的單元格以試聽(tīng)一些示例。
IPython.display.Audio("./raw_data/activates/1.wav")
CSDN不支持播放音頻
IPython.display.Audio("./raw_data/negatives/4.wav")
CSDN不支持播放音頻
IPython.display.Audio("./raw_data/backgrounds/1.wav")
CSDN不支持播放音頻
你將使用這三種類型的記錄(positives/negatives/backgrounds)來(lái)創(chuàng)建標(biāo)記的數(shù)據(jù)集。
1.2 從錄音到頻譜圖
錄音到底是什么?麥克風(fēng)記錄隨時(shí)間變化很小的氣壓,而這些氣壓的微小變化也會(huì)使你的耳朵感覺(jué)到聲音。你可以認(rèn)為錄音是一長(zhǎng)串?dāng)?shù)字,用于測(cè)量麥克風(fēng)檢測(cè)到的氣壓變化很小。我們將使用以44100Hz(或44100赫茲)采樣的音頻。這意味著麥克風(fēng)每秒可以為我們提供44100個(gè)號(hào)碼。因此,一個(gè)10秒的音頻剪輯由441000個(gè)數(shù)字表示(= 10 × 44100 10 \times 44100 10×44100)。
從音頻的這種“原始”表示中很難弄清是否說(shuō)了"activate"這個(gè)詞。為了幫助你的序列模型更輕松地學(xué)習(xí)檢測(cè)觸發(fā)詞,我們將計(jì)算音頻的spectrogram。頻譜圖告訴我們音頻片段在某個(gè)時(shí)刻存在多少不同的頻率。
(如果你曾經(jīng)在信號(hào)處理或傅立葉變換方面上過(guò)高級(jí)課程,則可以通過(guò)在原始音頻信號(hào)上滑動(dòng)一個(gè)窗口來(lái)計(jì)算頻譜圖,并使用傅立葉變換來(lái)計(jì)算每個(gè)窗口中最活躍的頻率。如果你不理解前面的句子,也不用擔(dān)心。)
讓我們來(lái)看一個(gè)例子。
IPython.display.Audio("audio_examples/example_train.wav")
CSDN不支持播放音頻
x = graph_spectrogram("audio_examples/example_train.wav")
上圖表示在多個(gè)時(shí)間步長(zhǎng)(x軸)上每個(gè)頻率(y軸)的活躍程度。
圖1:錄音的頻譜圖,其中的顏色表示在不同的時(shí)間點(diǎn)音頻中不同頻率出現(xiàn)(響亮)的程度。綠色方塊表示某個(gè)頻率在音頻剪輯(揚(yáng)聲器)中更活躍或更活躍。藍(lán)色方塊表示較不活躍的頻率。
輸出頻譜圖的尺寸取決于頻譜圖軟件的超參數(shù)和輸入的長(zhǎng)度。在此筆記本中,我們將使用10秒的音頻剪輯作為訓(xùn)練示例的“標(biāo)準(zhǔn)長(zhǎng)度”。頻譜圖的時(shí)間步數(shù)為5511。稍后你將看到頻譜圖將是網(wǎng)絡(luò)中的輸入 x x x,因此 T x = 5511 T_x=5511 Tx?=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)
現(xiàn)在,你可以定義:
Tx = 5511 # 從頻譜圖輸入到模型的時(shí)間步數(shù)
n_freq = 101 # 在頻譜圖的每個(gè)時(shí)間步輸入模型的頻率數(shù)
請(qǐng)注意,即使將10秒作為我們的默認(rèn)訓(xùn)練示例長(zhǎng)度,也可以將10秒的時(shí)間離散化為不同數(shù)量的值。你已經(jīng)看到441000(原始音頻)和5511(頻譜圖)。在前一種情況下,每個(gè)步驟代表 10 / 441000 ≈ 0.000023 10/441000 \approx 0.000023 10/441000≈0.000023秒。在第二種情況下,每個(gè)步驟代表 10 / 5511 ≈ 0.0018 10/5511 \approx 0.0018 10/5511≈0.0018秒。
對(duì)于10秒的音頻,你將在此作業(yè)中看到的關(guān)鍵值為:
- 441000 441000 441000(原始音頻)
- 5511 = T x 5511 = T_x 5511=Tx?(頻譜圖輸出,以及神經(jīng)網(wǎng)絡(luò)的輸入維數(shù))。
- 10000 10000 10000(用pydub模塊來(lái)合成音頻)
- 1375 = T y 1375=T_y 1375=Ty?(要構(gòu)建的GRU輸出中的步驟數(shù))。
請(qǐng)注意,這些表示中的每個(gè)表示都恰好對(duì)應(yīng)10秒的時(shí)間。只是他們?cè)诓煌潭壬想x散化了他們。所有這些都是超參數(shù),可以更改(441000除外,這是麥克風(fēng)函數(shù))。我們選擇的值在語(yǔ)音系統(tǒng)使用的標(biāo)準(zhǔn)范圍內(nèi)。
上面的 T y = 1375 T_y=1375 Ty?=1375數(shù)字意味著對(duì)于模型的輸出,我們將10s離散為1375個(gè)時(shí)間間隔(每個(gè)時(shí)間間隔的長(zhǎng)度為 10 / 1375 ≈ 0.0072 10/1375 \approx 0.0072 10/1375≈0.0072秒),并嘗試針對(duì)每個(gè)時(shí)間間隔預(yù)測(cè)是否有人最近說(shuō)完“activate”。
上面的10000對(duì)應(yīng)于將10秒剪輯離散化為10/10000 = 0.001秒迭代。0.001秒也稱為1毫秒或1ms。因此,當(dāng)我們說(shuō)要按照1ms的間隔離散時(shí),這意味著我們正在使用10,000個(gè)步長(zhǎng)。
Ty = 1375 # 我們模型輸出中的時(shí)間步數(shù)
1.3 生成單個(gè)訓(xùn)練示例
由于語(yǔ)音數(shù)據(jù)很難獲取和標(biāo)記,因此你將使用激活,否定和背景的音頻片段來(lái)合成訓(xùn)練數(shù)據(jù)。錄制很多帶有隨機(jī)"activates"內(nèi)容的10秒音頻剪輯非常慢。取而代之的是,錄制許多肯定詞和否定詞以及分別記錄背景噪音(或從免費(fèi)的在線資源下載背景噪音)會(huì)變得更加容易。
要合成一個(gè)訓(xùn)練示例,你將:
- 隨機(jī)選擇10秒鐘的背景音頻剪輯
- 將"activates"的0-4個(gè)音頻片段隨機(jī)插入此10秒的片段中
- 將10個(gè)否定詞的音頻剪輯隨機(jī)插入此10秒剪輯中
因?yàn)槟阋呀?jīng)將"activates"一詞合成到了背景剪輯中,所以你確切知道"activates"在10秒剪輯中何時(shí)出現(xiàn)。稍后你將看到,這也使得生成標(biāo)簽 y ? t ? y^{\langle t \rangle} y?t? 更加容易。
你將使用pydub包來(lái)處理音頻。Pydub將原始音頻文件轉(zhuǎn)換為Pydub數(shù)據(jù)結(jié)構(gòu)的列表(在此處了解詳細(xì)信息并不重要)。Pydub使用1毫秒作為離散時(shí)間間隔(1毫秒等于1毫秒= 1/1000秒),這也是為什么始終以10,000步表示10秒剪輯的原因。
# 使用pydub加載音頻片段
activates, negatives, backgrounds = load_raw_audio()
print("background len: " + str(len(backgrounds[0]))) # 應(yīng)該是10,000,因?yàn)樗且粋€(gè)10秒的剪輯
print("activate[0] len: " + str(len(activates[0]))) # 也許大約1000,因?yàn)?"activate" 音頻剪輯通常大約1秒(但變化很大)
print("activate[1] len: " + str(len(activates[1]))) # 不同的 "activate" 剪輯可以具有不同的長(zhǎng)度
background len: 10000
activate[0] len: 721
activate[1] len: 731
在背景上疊加正/負(fù)詞:
給定一個(gè)10秒的背景剪輯和一個(gè)簡(jiǎn)短的音頻剪輯(positive or negative word),你需要能夠?qū)卧~的簡(jiǎn)短音頻剪輯“添加”或“插入”到背景上。為確保插入背景的音頻片段不重疊,你將跟蹤以前插入的音頻片段的時(shí)間。你將在背景中插入多個(gè)正/負(fù)詞剪輯,而又不想在與先前添加的另一個(gè)剪輯重疊的位置插入"activate"或隨機(jī)詞。
為了清楚起見(jiàn),當(dāng)你在10秒的咖啡館噪音片段中插入1秒的 “activate” 時(shí),你最終會(huì)得到一個(gè)10秒的片段,聽(tīng)起來(lái)像有人在咖啡館中說(shuō) “activate”,背景咖啡館噪音中疊加了 “activate” 。注意你沒(méi)有以11秒的剪輯結(jié)尾。稍后你將看到pydub如何幫助你執(zhí)行此操作。
在疊加的同時(shí)創(chuàng)建標(biāo)簽:
還記得標(biāo)簽 y ? t ? y^{\langle t \rangle} y?t?代表某人是否剛剛說(shuō)完"activate.“。給定一個(gè)背景剪輯,我們可以為所有 t t t初始化 y ? t ? = 0 y^{\langle t \rangle}=0 y?t?=0,因?yàn)樵摷糨嫴话魏?activates.”。
當(dāng)插入或覆蓋"activate"剪輯時(shí),還將更新
y
?
t
?
y^{\langle t \rangle}
y?t?的標(biāo)簽,以便輸出的50個(gè)步驟現(xiàn)在具有目標(biāo)標(biāo)簽1。你將訓(xùn)練GRU來(lái)檢測(cè)何時(shí)某人完成說(shuō)"activate"。例如,假設(shè)合成的"activate"剪輯在10秒音頻中的5秒標(biāo)記處結(jié)束-恰好在剪輯的一半處。回想一下
T
y
=
1375
T_y=1375
Ty?=1375,因此時(shí)間步長(zhǎng)$687 = $ int(1375*0.5)
對(duì)應(yīng)于進(jìn)入音頻5秒的時(shí)刻。因此,你將設(shè)置
y
?
688
?
=
1
y^{\langle 688 \rangle} = 1
y?688?=1。此外,如果GRU在此刻之后的短時(shí)間內(nèi)(在內(nèi)部)在任何地方檢測(cè)到"activate",你將非常滿意,因此我們實(shí)際上將標(biāo)簽
y
?
t
?
y^{\langle t \rangle}
y?t?的50個(gè)連續(xù)值設(shè)置為1。我們有
y
?
688
?
=
y
?
689
?
=
?
=
y
?
737
?
=
1
y^{\langle 688 \rangle} = y^{\langle 689 \rangle} = \cdots = y^{\langle 737 \rangle} = 1
y?688?=y?689?=?=y?737?=1。
這是合成訓(xùn)練數(shù)據(jù)的另一個(gè)原因:如上所述,生成這些標(biāo)簽 y ? t ? y^{\langle t \rangle} y?t?相對(duì)簡(jiǎn)單。相反,如果你在麥克風(fēng)上錄制了10秒的音頻,那么一個(gè)人收聽(tīng)它并在 “activate” 完成時(shí)準(zhǔn)確手動(dòng)進(jìn)行標(biāo)記會(huì)非常耗時(shí)。
下圖顯示了標(biāo)簽
y
?
t
?
y^{\langle t \rangle}
y?t?,對(duì)于我們插入了"activate", “innocent”,activate", "baby"的剪輯,請(qǐng)注意,正標(biāo)簽“1”是關(guān)聯(lián)的只用positive的詞。
圖2
要實(shí)現(xiàn)合成訓(xùn)練集過(guò)程,你將使用以下幫助函數(shù)。所有這些函數(shù)將使用1ms的離散時(shí)間間隔,因此將10秒的音頻離散化為10,000步。
-
get_random_time_segment(segment_ms)
在我們的背景音頻中獲得隨機(jī)的時(shí)間段 -
is_overlapping(segment_time,existing_segments)
檢查時(shí)間段是否與現(xiàn)有時(shí)間段重疊 -
insert_audio_clip(background,audio_clip,existing_times)
使用get_random_time_segment
和is_overlapping
在我們的背景音頻中隨機(jī)插入一個(gè)音頻片段。 -
insert_ones(y,segment_end_ms)
在我們的標(biāo)簽向量y的"activate"詞之后插入1。
函數(shù)get_random_time_segment(segment_ms)
返回一個(gè)隨機(jī)的時(shí)間段,我們可以在其中插入持續(xù)時(shí)間為segment_ms
的音頻片段。 通讀代碼以確保你了解它在做什么。
def get_random_time_segment(segment_ms):
"""
獲取 10,000 ms音頻剪輯中時(shí)間長(zhǎng)為 segment_ms 的隨機(jī)時(shí)間段。
參數(shù):
segment_ms -- 音頻片段的持續(xù)時(shí)間,以毫秒為單位("ms" 代表 "毫秒")
返回:
segment_time -- 以ms為單位的元組(segment_start,segment_end)
"""
segment_start = np.random.randint(low=0, high=10000-segment_ms) # 確保段不會(huì)超過(guò)10秒背景
segment_end = segment_start + segment_ms - 1
return (segment_start, segment_end)
接下來(lái),假設(shè)你在(1000,1800)和(3400,4500)段插入了音頻剪輯。即第一個(gè)片段開(kāi)始于1000步,結(jié)束于1800步?,F(xiàn)在,如果我們考慮在(3000,3600)插入新的音頻剪輯,這是否與先前插入的片段之一重疊?在這種情況下,(3000,3600)和(3400,4500)重疊,因此我們應(yīng)該決定不要在此處插入片段。
出于此函數(shù)的目的,將(100,200)和(200,250)定義為重疊,因?yàn)樗鼈冊(cè)跁r(shí)間步200處重疊。但是,(100,199)和(200,250)是不重疊的。
練習(xí):實(shí)現(xiàn)is_overlapping(segment_time,existing_segments)
來(lái)檢查新的時(shí)間段是否與之前的任何時(shí)間段重疊。你需要執(zhí)行2個(gè)步驟:
- 創(chuàng)建一個(gè)“False”標(biāo)志,如果發(fā)現(xiàn)有重疊,以后將其設(shè)置為“True”。
- 循環(huán)遍歷
previous_segments
的開(kāi)始和結(jié)束時(shí)間。將這些時(shí)間與細(xì)分的開(kāi)始時(shí)間和結(jié)束時(shí)間進(jìn)行比較。如果存在重疊,請(qǐng)將(1)中定義的標(biāo)志設(shè)置為T(mén)rue。你可以使用:
for ....:
if ... <= ... and ... >= ...:
...
提示:如果該段在上一個(gè)段結(jié)束之前開(kāi)始,并且該段在上一個(gè)段開(kāi)始之后結(jié)束,則存在重疊。
# GRADED FUNCTION: is_overlapping
def is_overlapping(segment_time, previous_segments):
"""
檢查段的時(shí)間是否與現(xiàn)有段的時(shí)間重疊。
參數(shù):
segment_time -- 新段的元組(segment_start,segment_end)
previous_segments -- 現(xiàn)有段的元組列表(segment_start,segment_end)
返回:
如果時(shí)間段與任何現(xiàn)有段重疊,則為T(mén)rue,否則為False
"""
segment_start, segment_end = segment_time
# 第一步:將重疊標(biāo)識(shí) overlap 初始化為“False”標(biāo)志 (≈ 1 line)
overlap = False
# 第二步:循環(huán)遍歷 previous_segments 的開(kāi)始和結(jié)束時(shí)間。
# 比較開(kāi)始/結(jié)束時(shí)間,如果存在重疊,則將標(biāo)志 overlap 設(shè)置為T(mén)rue (≈ 3 lines)
for previous_start, previous_end in previous_segments:
if segment_start <= previous_end and segment_end >= previous_start:
overlap = True
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
現(xiàn)在,讓我們使用以前的輔助函數(shù)在10秒鐘的隨機(jī)時(shí)間將新的音頻片段插入到背景中,但是要確保任何新插入的片段都不會(huì)與之前的片段重疊。
練習(xí):實(shí)現(xiàn)insert_audio_clip()
以將音頻片段疊加到背景10秒片段上。你將需要執(zhí)行4個(gè)步驟:
- 以ms為單位獲取正確持續(xù)時(shí)間的隨機(jī)時(shí)間段。
- 確保該時(shí)間段與之前的任何時(shí)間段均不重疊。如果重疊,則返回步驟1并選擇一個(gè)新的時(shí)間段。
- 將新時(shí)間段添加到現(xiàn)有時(shí)間段列表中,以便跟蹤你插入的所有時(shí)間段。
- 使用pydub在背景上覆蓋音頻片段。我們已經(jīng)為你實(shí)現(xiàn)了這一點(diǎn)。
# GRADED FUNCTION: insert_audio_clip
def insert_audio_clip(background, audio_clip, previous_segments):
"""
在隨機(jī)時(shí)間步驟中在背景噪聲上插入新的音頻片段,確保音頻片段與現(xiàn)有片段不重疊。
參數(shù):
background -- 10秒背景錄音。
audio_clip -- 要插入/疊加的音頻剪輯。
previous_segments -- 已放置的音頻片段的時(shí)間
返回:
new_background -- 更新的背景音頻
"""
# 以ms為單位獲取音頻片段的持續(xù)時(shí)間
segment_ms = len(audio_clip)
# 第一步:使用其中一個(gè)輔助函數(shù)來(lái)選擇要插入的隨機(jī)時(shí)間段
# 新的音頻剪輯。 (≈ 1 line)
segment_time = get_random_time_segment(segment_ms)
# 第二步:檢查新的segment_time是否與previous_segments之一重疊。
# 如果重疊如果是這樣,請(qǐng)繼續(xù)隨機(jī)選擇新的 segment_time 直到它不重疊。(≈ 2 lines)
while is_overlapping(segment_time, previous_segments):
segment_time = get_random_time_segment(segment_ms)
# 第三步: 將新的 segment_time 添加到 previous_segments 列表中 (≈ 1 line)
previous_segments.append(segment_time)
# 第四步: 疊加音頻片段和背景
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")
Segment Time: (2915, 3635)
CSDN不支持播放音頻
# 預(yù)期的音頻
IPython.display.Audio("audio_examples/insert_reference.wav")
CSDN不支持播放音頻
最后,假設(shè)你剛剛插入了"activate." ,則執(zhí)行代碼以更新標(biāo)簽
y
?
t
?
y^{\langle t \rangle}
y?t?。在下面的代碼中,由于
T
y
=
1375
T_y=1375
Ty?=1375,所以y
是一個(gè) (1,1375)
維向量。
如果"activate"在時(shí)間步驟
t
t
t結(jié)束,則設(shè)置
y
?
t
+
1
?
=
1
y^{\langle t+1 \rangle} = 1
y?t+1?=1以及最多49個(gè)其他連續(xù)值。但是,請(qǐng)確保你沒(méi)有用完數(shù)組的末尾并嘗試更新y[0][1375]
,由于
T
y
=
1375
T_y=1375
Ty?=1375,所以有效索引是y[0][0]
至y[0][1374]
。因此,如果"activate" 在1370步結(jié)束,則只會(huì)得到y[0][1371] = y[0][1372] = y[0][1373] = y[0][1374] = 1
練習(xí):實(shí)現(xiàn)insert_ones()
。你可以使用for循環(huán)。(如果你是python的slice運(yùn)算的專家,請(qǐng)隨時(shí)使用切片對(duì)此向量化。)如果段以segment_end_ms
結(jié)尾(使用10000步離散化),請(qǐng)將其轉(zhuǎn)換為輸出
y
y
y的索引(使用
1375
1375
1375步離散化),我們將使用以下公式:
segment_end_y = int(segment_end_ms * Ty / 10000.0)
# GRADED FUNCTION: insert_ones
def insert_ones(y, segment_end_ms):
"""
更新標(biāo)簽向量y。段結(jié)尾的后面50個(gè)輸出的標(biāo)簽應(yīng)設(shè)為 1。
嚴(yán)格來(lái)說(shuō),我們的意思是 segment_end_y 的標(biāo)簽應(yīng)該是 0,而隨后的50個(gè)標(biāo)簽應(yīng)該是1。
參數(shù):
y -- numpy數(shù)組的維度 (1, Ty), 訓(xùn)練樣例的標(biāo)簽
segment_end_ms -- 以ms為單位的段的結(jié)束時(shí)間
返回:
y -- 更新標(biāo)簽
"""
# 背景持續(xù)時(shí)間(以頻譜圖時(shí)間步長(zhǎng)表示)
segment_end_y = int(segment_end_ms * Ty / 10000.0)
# 將1添加到背景標(biāo)簽(y)中的正確索引
for i in range(segment_end_y + 1, segment_end_y + 51):
if i < Ty:
y[0, i] = 1
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
來(lái)創(chuàng)建一個(gè)新的訓(xùn)練示例。
練習(xí):實(shí)現(xiàn)create_training_example()
。你需要執(zhí)行以下步驟:
- 將標(biāo)簽向量 y y y初始化為維度為 ( 1 , T y ) (1,T_y) (1,Ty?)的零numpy數(shù)組
- 將現(xiàn)有段的集合初始化為一個(gè)空列表
- 隨機(jī)選擇0到4個(gè)"activate"音頻剪輯,并將其插入10秒剪輯中。還要在標(biāo)簽向量 y y y的正確位置插入標(biāo)簽。
- 隨機(jī)選擇0到2個(gè)負(fù)音頻片段,并將其插入10秒片段中。
# GRADED FUNCTION: create_training_example
def create_training_example(background, activates, negatives):
"""
創(chuàng)建具有給定背景,正例和負(fù)例的訓(xùn)練示例。
參數(shù):
background -- 10秒背景錄音
activates -- "activate" 一詞的音頻片段列表
negatives -- 不是 "activate" 一詞的音頻片段列表
返回:
x -- 訓(xùn)練樣例的頻譜圖
y -- 頻譜圖的每個(gè)時(shí)間步的標(biāo)簽
"""
# 設(shè)置隨機(jī)種子
np.random.seed(18)
# 讓背景更安靜
background = background - 20
# 第一步:初始化 y (標(biāo)簽向量)為0 (≈ 1 line)
y = np.zeros((1, Ty))
# 第二步:將段時(shí)間初始化為空列表 (≈ 1 line)
previous_segments = []
# 從整個(gè) "activate" 錄音列表中選擇0-4隨機(jī) "activate" 音頻片段
number_of_activates = np.random.randint(0, 5)
random_indices = np.random.randint(len(activates), size=number_of_activates)
random_activates = [activates[i] for i in random_indices]
# 第三步: 循環(huán)隨機(jī)選擇 "activate" 剪輯插入背景
for random_activate in random_activates:
# 插入音頻剪輯到背景
background, segment_time = insert_audio_clip(background, random_activate, previous_segments)
# 從 segment_time 中取 segment_start 和 segment_end
segment_start, segment_end = segment_time
# 在 "y" 中插入標(biāo)簽
y = insert_ones(y, segment_end_ms=segment_end)
# 從整個(gè)負(fù)例錄音列表中隨機(jī)選擇0-2個(gè)負(fù)例錄音
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]
# 第四步: 循環(huán)隨機(jī)選擇負(fù)例片段并插入背景中
for random_negative in random_negatives:
# 插入音頻剪輯到背景
background, _ = insert_audio_clip(background, random_negative, previous_segments)
# 標(biāo)準(zhǔn)化音頻剪輯的音量
background = match_target_amplitude(background, -20.0)
# 導(dǎo)出新的訓(xùn)練樣例
file_handle = background.export("train" + ".wav", format="wav")
print("文件 (train.wav) 已保存在您的目錄中。")
# 獲取并繪制新錄音的頻譜圖(正例和負(fù)例疊加的背景)
x = graph_spectrogram("train.wav")
return x, y
x, y = create_training_example(backgrounds[0], activates, negatives)
文件 (train.wav) 已保存在您的目錄中。
現(xiàn)在,您可以聆聽(tīng)您創(chuàng)建的訓(xùn)練示例,并將其與上面生成的頻譜圖進(jìn)行比較。
IPython.display.Audio("train.wav")
CSDN不支持播放音頻
IPython.display.Audio("audio_examples/train_reference.wav")
CSDN不支持播放音頻
最后,你可以為生成的訓(xùn)練示例繪制關(guān)聯(lián)的標(biāo)簽。
plt.plot(y[0])
1.4 完整訓(xùn)練集
現(xiàn)在,你已經(jīng)實(shí)現(xiàn)了生成單個(gè)訓(xùn)練示例所需的代碼。我們使用此過(guò)程生成了大量的訓(xùn)練集。為了節(jié)省時(shí)間,我們已經(jīng)生成了一組訓(xùn)練示例。
# 加載預(yù)處理的訓(xùn)練樣例
X = np.load("./XY_train/X.npy")
Y = np.load("./XY_train/Y.npy")
1.5 開(kāi)發(fā)集
為了測(cè)試我們的模型,我們記錄了包含25個(gè)示例的開(kāi)發(fā)集。在合成訓(xùn)練數(shù)據(jù)的同時(shí),我們希望使用與實(shí)際輸入相同的分布來(lái)創(chuàng)建開(kāi)發(fā)集。因此,我們錄制了25個(gè)10秒鐘的人們說(shuō)"activate"和其他隨機(jī)單詞的音頻剪輯,并手動(dòng)標(biāo)記了它們。這遵循課程3中描述的原則,即我們應(yīng)該將開(kāi)發(fā)集創(chuàng)建為與測(cè)試集盡可能相似。這就是為什么我們的開(kāi)發(fā)人員使用真實(shí)音頻而非合成音頻的原因。
# 加載預(yù)處理開(kāi)發(fā)集示例
X_dev = np.load("./XY_dev/X_dev.npy")
Y_dev = np.load("./XY_dev/Y_dev.npy")
2 模型
現(xiàn)在,你已經(jīng)建立了數(shù)據(jù)集,讓我們編寫(xiě)和訓(xùn)練關(guān)鍵字識(shí)別模型!
該模型將使用一維卷積層,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
Using TensorFlow backend.
2.1 建立模型
這是我們將使用的模型架構(gòu)?;ㄒ恍r(shí)間查看模型,看看它是否合理。
圖3
該模型的一個(gè)關(guān)鍵步驟是一維卷積步驟(圖3的底部附近)。它輸入5511步頻譜圖,并輸出1375步,然后由多層進(jìn)一步處理以獲得最終的 T y = 1375 T_y=1375 Ty?=1375步輸出。該層的作用類似于你在課程4中看到的2D卷積,其作用是提取低級(jí)特征,然后生成較小尺寸的輸出。
通過(guò)計(jì)算,一維轉(zhuǎn)換層還有助于加快模型的速度,因?yàn)楝F(xiàn)在GRU只需要處理1375個(gè)時(shí)間步,而不是5511個(gè)時(shí)間步。這兩個(gè)GRU層從左到右讀取輸入序列,然后最終使用dense+sigmoid層對(duì) y ? t ? y^{\langle t \rangle} y?t?進(jìn)行預(yù)測(cè)。因?yàn)?span id="n5n3t3z" class="katex--inline"> y y y是二進(jìn)制值(0或1),所以我們?cè)谧詈笠粚邮褂肧igmoid輸出來(lái)估計(jì)輸出為1的機(jī)率,對(duì)應(yīng)用戶剛剛說(shuō)過(guò)"activate"。
請(qǐng)注意,我們使用的是單向RNN,而不是雙向RNN。這對(duì)于關(guān)鍵字檢測(cè)確實(shí)非常重要,因?yàn)槲覀兿M軌蛟谡f(shuō)出觸發(fā)字后立即檢測(cè)到觸發(fā)字。如果我們使用雙向RNN,則必須等待記錄整個(gè)10秒的音頻,然后才能知道在音頻剪輯的第一秒中是否說(shuō)了"activate"。
可以通過(guò)四個(gè)步驟來(lái)實(shí)現(xiàn)模型:
步驟1:CONV層。使用Conv1D()
和196個(gè)濾波器來(lái)實(shí)現(xiàn),
濾波器大小為15(kernel_size = 15
),步幅為4。[See documentation.]
步驟2:第一個(gè)GRU層。要生成GRU層,請(qǐng)使用:
X = GRU(units = 128, return_sequences = True)(X)
設(shè)置return_sequences = True
可以確保所有GRU的隱藏狀態(tài)都被feed
到下一層。請(qǐng)記住,在Dropout和BatchNorm層之后進(jìn)行此操作。
步驟3:第二個(gè)GRU層。這類似于先前的GRU層(請(qǐng)記住使用return_sequences = True
),但是有一個(gè)額外的dropout層。
步驟4:按以下步驟創(chuàng)建一個(gè)時(shí)間分布的密集層:
X = TimeDistributed(Dense(1, activation = "sigmoid"))(X)
這將創(chuàng)建一個(gè)緊隨其后的Sigmoid密集層,因此用于密集層的參數(shù)對(duì)于每個(gè)時(shí)間步都是相同的。[See documentation.]
練習(xí):實(shí)現(xiàn)model(),其架構(gòu)如圖3所示。
# GRADED FUNCTION: model
def model(input_shape):
"""
用 Keras 創(chuàng)建模型的圖 Function creating the model's graph in Keras.
參數(shù):
input_shape -- 模型輸入數(shù)據(jù)的維度(使用Keras約定)
返回:
model -- Keras 模型實(shí)例
"""
X_input = Input(shape = input_shape)
# 第一步:卷積層 (≈4 lines)
X = Conv1D(196, 15, strides=4)(X_input) # CONV1D
X = BatchNormalization()(X) # Batch normalization 批量標(biāo)準(zhǔn)化
X = Activation('relu')(X) # ReLu activation ReLu 激活
X = Dropout(0.8)(X) # dropout (use 0.8)
# 第二步:第一個(gè) GRU 層 (≈4 lines)
X = GRU(units = 128, return_sequences=True)(X) # GRU (使用128個(gè)單元并返回序列)
X = Dropout(0.8)(X) # dropout (use 0.8)
X = BatchNormalization()(X) # Batch normalization 批量標(biāo)準(zhǔn)化
# 第三步: 第二個(gè) GRU 層 (≈4 lines)
X = GRU(units = 128, return_sequences=True)(X) # GRU (使用128個(gè)單元并返回序列)
X = Dropout(0.8)(X) # dropout (use 0.8)
X = BatchNormalization()(X) # Batch normalization 批量標(biāo)準(zhǔn)化
X = Dropout(0.8)(X) # dropout (use 0.8)
# 第四步: 時(shí)間分布全連接層 (≈1 line)
X = TimeDistributed(Dense(1, activation = "sigmoid"))(X) # time distributed (sigmoid)
model = Model(inputs = X_input, outputs = X)
return model
model = model(input_shape = (Tx, n_freq))
讓我們輸出模型總結(jié)以查看維度。
model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 5511, 101) 0
_________________________________________________________________
conv1d_1 (Conv1D) (None, 1375, 196) 297136
_________________________________________________________________
batch_normalization_1 (Batch (None, 1375, 196) 784
_________________________________________________________________
activation_1 (Activation) (None, 1375, 196) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 1375, 196) 0
_________________________________________________________________
gru_1 (GRU) (None, 1375, 128) 124800
_________________________________________________________________
dropout_2 (Dropout) (None, 1375, 128) 0
_________________________________________________________________
batch_normalization_2 (Batch (None, 1375, 128) 512
_________________________________________________________________
gru_2 (GRU) (None, 1375, 128) 98688
_________________________________________________________________
dropout_3 (Dropout) (None, 1375, 128) 0
_________________________________________________________________
batch_normalization_3 (Batch (None, 1375, 128) 512
_________________________________________________________________
dropout_4 (Dropout) (None, 1375, 128) 0
_________________________________________________________________
time_distributed_1 (TimeDist (None, 1375, 1) 129
=================================================================
Total params: 522,561
Trainable params: 521,657
Non-trainable params: 904
_________________________________________________________________
網(wǎng)絡(luò)的輸出為(None,1375,1),輸入為(None,5511,101)。Conv1D將步數(shù)從頻譜圖上的5511減少到1375。
2.2 擬合模型
關(guān)鍵詞檢測(cè)需要很長(zhǎng)時(shí)間來(lái)訓(xùn)練。為了節(jié)省時(shí)間,我們已經(jīng)使用你上面構(gòu)建的架構(gòu)在GPU上訓(xùn)練了大約3個(gè)小時(shí)的模型,并提供了大約4000個(gè)示例的大型訓(xùn)練集。讓我們加載模型吧。
model = load_model('./models/tr_model.h5')
你可以使用Adam優(yōu)化器和二進(jìn)制交叉熵?fù)p失進(jìn)一步訓(xùn)練模型,如下所示。這將很快運(yùn)行,因?yàn)槲覀冎挥?xùn)練一個(gè)epoch,并提供26個(gè)例子的小訓(xùn)練集。
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 [==============================] - 10s 381ms/step - loss: 0.0893 - accuracy: 0.9717
2.3 測(cè)試模型
最后,讓我們看看你的模型在開(kāi)發(fā)集上的表現(xiàn)。
loss, acc = model.evaluate(X_dev, Y_dev)
print("Dev set accuracy = ", acc)
25/25 [==============================] - 1s 37ms/step
Dev set accuracy = 0.9507200121879578
看起來(lái)不錯(cuò)!但是,精度并不是這項(xiàng)任務(wù)的重要指標(biāo),因?yàn)闃?biāo)簽嚴(yán)重偏斜到0,因此僅輸出0的神經(jīng)網(wǎng)絡(luò)的精度將略高于90%。我們可以定義更有用的指標(biāo),例如F1得分或“精確度/召回率”。但是,我們不要在這里使用它,而只是憑經(jīng)驗(yàn)看看模型是如何工作的。
3 預(yù)測(cè)
現(xiàn)在,你已經(jīng)建立了用于觸發(fā)詞檢測(cè)的工作模型,讓我們使用它來(lái)進(jìn)行預(yù)測(cè)吧。此代碼段通過(guò)網(wǎng)絡(luò)運(yùn)行音頻(保存在wav文件中)。
可以使用你的模型對(duì)新的音頻片段進(jìn)行預(yù)測(cè)。
你首先需要計(jì)算輸入音頻剪輯的預(yù)測(cè)。
練習(xí):實(shí)現(xiàn)predict_activates()。你需要執(zhí)行以下操作:
- 計(jì)算音頻文件的頻譜圖
- 使用
np.swap
和np.expand_dims
將輸入調(diào)整為(1,Tx,n_freqs)大小 - 在模型上使用正向傳播來(lái)計(jì)算每個(gè)輸出步驟的預(yù)測(cè)
def detect_triggerword(filename):
plt.subplot(2, 1, 1)
x = graph_spectrogram(filename)
# 頻譜圖輸出(freqs,Tx),我們想要(Tx,freqs)輸入到模型中
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
一旦估計(jì)了在每個(gè)輸出步驟中檢測(cè)到"activate"一詞的可能性,就可以在該可能性高于某個(gè)閾值時(shí)觸發(fā)出"chiming(蜂鳴)"聲。此外,在說(shuō)出"activate"之后,對(duì)于許多連續(xù)值, y ? t ? y^{\langle t \rangle} y?t?可能接近1,但我們只希望發(fā)出一次提示音。因此,每75個(gè)輸出步驟最多將插入一次鈴聲。這將有助于防止我們?yōu)?activate"的單個(gè)實(shí)例插入兩個(gè)提示音。(該作用類似于計(jì)算機(jī)視覺(jué)中的非極大值抑制)
練習(xí):實(shí)現(xiàn)chime_on_activate()
。你需要執(zhí)行以下操作:
- 遍歷每個(gè)輸出步驟的預(yù)測(cè)概率
- 當(dāng)預(yù)測(cè)大于閾值并且經(jīng)過(guò)了連續(xù)75個(gè)以上的時(shí)間步長(zhǎng)時(shí),在原始音頻剪輯中插入"chime"
使用以下代碼將1375步離散化轉(zhuǎn)換為10000步離散化,并使用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]
# 第一步:將連續(xù)輸出步初始化為0
consecutive_timesteps = 0
# 第二步: 循環(huán)y中的輸出步
for i in range(Ty):
# 第三步: 增加連續(xù)輸出步
consecutive_timesteps += 1
# 第四步: 如果預(yù)測(cè)高于閾值并且已經(jīng)過(guò)了超過(guò)75個(gè)連續(xù)輸出步
if predictions[0,i,0] > threshold and consecutive_timesteps > 75:
# 第五步:使用pydub疊加音頻和背景
audio_clip = audio_clip.overlay(chime, position = ((i / Ty) * audio_clip.duration_seconds)*1000)
# 第六步: 將連續(xù)輸出步重置為0
consecutive_timesteps = 0
audio_clip.export("chime_output.wav", format='wav')
3.1 測(cè)試開(kāi)發(fā)集
讓我們探討一下我們的模型在開(kāi)發(fā)集中的兩個(gè)未知的音頻剪輯上表現(xiàn)如何。首先讓我們聽(tīng)聽(tīng)兩個(gè)開(kāi)發(fā)集剪輯。
IPython.display.Audio("./raw_data/dev/1.wav")
CSDN不支持播放音頻
IPython.display.Audio("./raw_data/dev/2.wav")
CSDN不支持播放音頻
現(xiàn)在,讓我們?cè)谶@些音頻剪輯上運(yùn)行模型,看看在"activate"之后它是否添加了提示音!
filename = "./raw_data/dev/1.wav"
prediction = detect_triggerword(filename)
chime_on_activate(filename, prediction, 0.5)
IPython.display.Audio("./chime_output.wav")
CSDN不支持播放音頻
filename = "./raw_data/dev/2.wav"
prediction = detect_triggerword(filename)
chime_on_activate(filename, prediction, 0.5)
IPython.display.Audio("./chime_output.wav")
CSDN不支持播放音頻
這是你應(yīng)該記住的:
- 數(shù)據(jù)合成是創(chuàng)建針對(duì)語(yǔ)音問(wèn)題(尤其是觸發(fā)詞檢測(cè))大型訓(xùn)練集的有效方法。
- 在將音頻數(shù)據(jù)傳遞到RNN,GRU或LSTM之前,使用頻譜圖和可選的1D轉(zhuǎn)換層是常見(jiàn)的預(yù)處理步驟。
- 可以使用端到端的深度學(xué)習(xí)方法來(lái)構(gòu)建非常有效的觸發(fā)詞檢測(cè)系統(tǒng)。
4 試試你自己的例子!
在此筆記本的此可選練習(xí)中,你可以在自己的音頻剪輯上嘗試使用你的模型!
錄制一個(gè)10秒鐘的音頻片段,說(shuō)"activate"和其他隨機(jī)單詞,然后將其作為myaudio.wav
上傳到Coursera hub。確保將音頻作為WAV文件上傳。如果你的音頻以其他格式(例如mp3)錄制,則可以在線找到免費(fèi)軟件以將其轉(zhuǎn)換為wav。如果你的錄音時(shí)間不是10秒,則下面的代碼將根據(jù)需要修剪或填充該聲音,以使其達(dá)到10秒。
# 將音頻預(yù)處理為正確的格式
def preprocess_audio(filename):
# 將音頻片段修剪或填充到 10000ms
padding = AudioSegment.silent(duration=10000)
segment = AudioSegment.from_wav(filename)[:10000]
segment = padding.overlay(segment)
# 將幀速率設(shè)置為 44100
segment = segment.set_frame_rate(44100)
# 導(dǎo)出為wav
segment.export(filename, format='wav')
將音頻文件上傳到Coursera后,將文件路徑放在下面的變量中。
your_filename = "audio_examples/my_audio.wav"
preprocess_audio(your_filename)
IPython.display.Audio(your_filename) # 聽(tīng)你上傳的音頻
CSDN不支持播放音頻
最后,使用該模型預(yù)測(cè)在10秒的音頻剪輯中何時(shí)說(shuō)了"activate"并觸發(fā)提示音。如果沒(méi)有適當(dāng)添加嗶聲,請(qǐng)嘗試調(diào)整chime_threshold。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-500058.html
chime_threshold = 0.5
prediction = detect_triggerword(your_filename)
chime_on_activate(your_filename, prediction, chime_threshold)
IPython.display.Audio("./chime_output.wav")
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-500058.html
CSDN不支持播放音頻
到了這里,關(guān)于Python吳恩達(dá)深度學(xué)習(xí)作業(yè)24 -- 語(yǔ)音識(shí)別關(guān)鍵字的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!