吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测

Part 2: 触发字检测

关键词语音唤醒

触发字检测

欢迎来到这个专业课程的最终编程任务!

在本周的视频中,你了解了如何将深度学习应用于语音识别。在本作业中,您将构建一个语音数据集并实现触发字检测算法(有时也称为关键字检测或唤醒检测)。触发字检测技术,可以让亚马逊Alexa,Google Home,Apple Siri和百度DuerOS等设备在听到某个词语时进行唤醒。

本练习中,我们的触发词将是“Activate”。每当它听到你说“Activate”时,它就会发出“chiming”声音。在此作业结束时,您将能够录制自己正在讲话的片段,并在算法检测到您说出“chiming”时让算法触发一次钟声。

完成作业后,也许你还可以扩展到笔记本电脑上运行,这样每当你说“chiming”它启动你最喜欢的应用程序,或打开你家的网络连接灯,或触发一些其他事件。

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第1张图片

本作业中,你将学到

  • 构建一个语音识别项目
  • 合成和处理音频记录以创建训练/开发测试集
  • 训练触发字检测模型并进行预测

导包

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

1 数据合成:创建语音数据集

首先为触发字检测算法构建一个数据集。 理想情况下,语音数据集尽可能接近您希望运行的应用程序。 在这种情况下,您希望在工作环境(图书馆,家庭,办公室,开放空间等)中检测到“activate”一词。 因此,您需要在不同的背景声音中混合使用正面词语(“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")

你将使用这三种类型的音频数据创建标签数据集。

1.2 从录音到声谱图

什么是录音? 麦克风随着时间的推移记录气压的微小变化,正是这些气压的微小变化让你的耳朵感觉到了声音。 你可以想象一个录音是一个长长的数字列表,用于测量麦克风检测到的微小气压变化。 我们将使用以44100赫兹采样的音频。 这意味着麦克风每秒给我们44100个数字。 因此,10秒音频剪辑由441000个数字(= 10×44100)表示。

从这个音频的原始数据表示中找出是否包含“activate”这个词是相当困难的。 为了帮助你的序列模型更容易学习检测触发字,我们将计算音频的谱图。 频谱图告诉我们一段时间内音频片段中存在多少不同的频率。

(如果你曾经学习过信号处理或傅里叶变换上的课程,频谱的计算时通过在原始音频信号上滑动窗口计算的,并使用傅立叶变换计算每个窗口中最活跃的频率。 如果你不理解前面的句子,也不用担心。)

让我们看一个例子:

IPython.display.Audio("audio_examples/example_train.wav")
x = graph_spectrogram("audio_examples/example_train.wav")

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第2张图片

上面的图表表示每个频率(y轴)在各个时间步(x轴)上的活动情况。

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第3张图片

上图是音频记录的频谱图,其中颜色显示的是不同时间点音频不同频率的程度。 绿色方块意味着音频片段中的某个频率更加活跃(声音更响亮); 蓝色方块表示较少的活动频率。

输出谱图的维度取决于谱图软件的超参数和输入的长度。 在本文中,我们将使用10秒音频剪辑作为我们培训示例的“标准长度”。 频谱图的时间步数将为5511.稍后你会看到频谱图作为输入X给带网络中,因此 Tx 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(频谱图)。 在前一种情况下,每个时间步代表10/441000≈0.000023秒。 在第二种情况下,每个时间步代表10/5511≈0.0018秒。

对于10s的音频,关键的值有:

  • 441000(原始音频)
  • 5511= Tx T x (频谱图输出,也是神经网络输入的维度)
  • 10000(由pydub模块用于合成的音频)
  • 1375= Ty T y (即将构建的GRU的输出时间步的数量)

注意: 每一个样本恰好10秒的时间,被不同类型进行离散化。这些都是超参数,可以更改(除了441000,这是麦克风的功能)。 这里选择了语音系统标准范围内的值。

比如 Ty T y = 1375意味着对于模型的输出,我们将10秒离散成1375个时间间隔(每个长度为10 /1375≈0.0072秒),并尝试预测这些时间间隔是否最近有人说过“activate”。

又如上面的10000这个数字,将10秒剪辑离散化为10/10000 = 0.001秒的间隔。 0.001秒也被称为1毫秒 所以当我们说按照1ms间隔进行离散化时,这意味着正在使用10,000步。

Ty = 1375 # The number of time steps in the output of our model

1.3 生成一个训练示例

由于语音数据很难获取和标记,因此您将使用正向、反向和背景的音频剪辑合成训练数据。 记录大量10秒的随机“activates”音频剪辑是很慢的。 相反,记录大量正向和反向词汇,并单独记录背景噪音(或从免费在线渠道下载背景噪音)更容易。

为了合成一个训练样本,你需要:

  • 随机选择一个10秒的背景音频剪辑
  • 随机将0-4个正向音频片段插入此10秒剪辑中
  • 随机将0-2个反向音频片段插入此10秒剪辑中

因为您已将“activates”一词合成到背景剪辑中,所以您确切知道10秒剪辑中何时出现“activates”。 稍后您会看到,这样也更容易生成标签y⟨t⟩。

您将使用pydub软件包来处理音频。 Pydub将原始音频文件转换为Pydub数据结构列表(这里了解细节并不重要)。 Pydub使用1ms作为离散化间隔(1ms是1毫秒= 1/1000秒),这就是为什么10秒剪辑总是使用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: 916
# activate[1] len: 1579

在背景上覆盖正面/负面的词语

给定一个10秒的背景剪辑和一个短的音频剪辑(正面或负面的单词),您需要能够将单词的短片段“添加”或“插入”背景。 为确保插入到背景上的音频片段不重叠,需要跟踪以前插入的音频片段的时间。 您将在背景中插入多个正面/负面单词剪辑,并且不希望插入有重叠。

为了清楚起见,当您在咖啡厅噪音的10秒剪辑中插入1秒“activate”时,最终会出现10秒的剪辑,听起来像某人在咖啡厅中说“activate”。你不会以11秒的剪辑结束。 稍后你会看到pydub如何让你做到这一点。

在插入的同时创建标签

回想一下,标签y⟨t⟩表示某人是否刚说完“activate”。给定一个背景剪辑,我们可以初始化所有t的y⟨t⟩= 0,因为该剪辑不包含任何“activate”。

当你插入或覆盖“activate”剪辑时,您还将更新 yt y ⟨ t ⟩ 的标签,以便输出的50个步骤具有目标标签1.您将训练GRU以检测某人何时说完“激活”。 例如,假设合成的“activate”剪辑在10秒音频中的5秒处结束 - 恰好在剪辑的中途。 回想一下 Ty T y = 1375,所以时间步长687 = int(1375 * 0.5)对应于5秒进入音频的时刻。 所以,你会设置 y688 y ⟨ 688 ⟩ = 1。 此外,如果GRU在短时间内在任何时间内检测到“activate”,那么在此时刻之后,您会非常满意,所以我们实际上将标签 yt y ⟨ t ⟩ 的50个连续值设置为1.具体来说,我们有 y688=y689==y737=1 y ⟨ 688 ⟩ = y ⟨ 689 ⟩ = ⋯ = y ⟨ 737 ⟩ = 1

这是合成训练数据的另一个原因:上面描述的生成这些标签y⟨t⟩比较简单;相反,如果在麦克风上录制了10秒的音频,那么听到该音频并且在“activate”完成时手动标记是非常耗时的。

下面是一张插图,展示了插入“activate”,“innocent”,“activate”,“baby” 的剪辑的标签y⟨t⟩。请注意,正面标签“1”仅与正面字词相关。

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第4张图片

要实现训练集合成过程,您将使用以下辅助函数。 所有这些功能将使用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
    """

    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. 浏览之前插入时间片的开始和结束时间,比较与新时间片是否有重叠,如果有则将标签置为”true”。

for ....:
        if ... <= ... and ... >= ...:
            ...

提示:如果新的时间片在上一个时间片结束之前开始,或者在下一个时间片开始之后结束,都是有重叠。

# 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

    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_end and segment_end >= previous_start:
            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

期待的输出

key value
Overlap 1 False
Overlap 2 True

现在,我们随机将一个新的音频片段插入到10秒的背景中,但要确保任何新插入的片段都不会与之前的片段重叠。

练习:实现 insert_audio_clip()

将一个新的音频片段插入到10秒的背景中,你需要完成4步:

  1. 以毫秒为单位获取随机时间段。
  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)

    ### 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")

# Segment Time:  (2254, 3169)

期待的输出

key value
Segment Time (2254, 3169)
# Expected audio
IPython.display.Audio("audio_examples/insert_reference.wav")

最后,假设你刚刚插入一个“activate”,实现代码来更新标签 yt y ⟨ t ⟩ 。在下面的代码中,y是一个(1,1375)维向量,因为 Ty T y = 1375。

如果“activate”在时间步骤t结束,则设置 yt+1=y<t+2>=y<t+50> y ⟨ t + 1 ⟩ = y < t + 2 > = y < t + 50 > 个连续值,但是 Ty T y = 1375,注意 y<t+m> y < t + m > 不能越界。

练习:实现 insert_ones()

你可以使用for循环。
(如果你是python的slice操作的专家,也可以使用切片来将其向量化)。
如果一个段在segment_end_ms处结束(使用10000步离散化),将其转换为输出y的索引(使用1375步离散化),我们将使用这个公式:

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)

    # 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.0
    ### 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

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第5张图片

最后,你可以使用insert_audio_clip 和 insert_ones 创建一个新的训练样本。

练习:实现 create_training_example()
  1. 将标签向量y初始化为零值的(1, Ty T y )numpy数组
  2. 将已存在时间片集合初始化为空列表。
  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)
    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, _ = background, segment_time = 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.

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第6张图片

现在你可以听一下你创建的新样本,和上面的频谱比较一下。

IPython.display.Audio("train.wav")

期待的输出

IPython.display.Audio("audio_examples/train_reference.wav")

最后,你可以将生成的训练样本的相关标签绘制成图。

plt.plot(y[0])

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第7张图片

1.4 全部训练集

你现在已经实现了生成单个训练样本所需的代码。 我们将使用这个过程来生成一个大的训练集。 为了节省时间,我们已经生成了一组训练示例。

# Load preprocessed training examples
X = np.load("./XY_train/X.npy")
Y = np.load("./XY_train/Y.npy")

1.5 开发测试集

为了测试我们的模型,我们记录了25个样本的开发集。 虽然我们的训练数据是合成的,但我们希望创建一个与实际输入相同分布的开发集。 因此,我们记录了人们说“activate”和其他随机单词的25个10秒钟音频剪辑,并手工进行标记。 这遵循在课程3中描述的原则,我们应该创建一个尽可能与测试集分布相似的开发集; 这就是为什么我们的开发测试集使用真实而不是合成音频。

# Load preprocessed dev set examples
X_dev = np.load("./XY_dev/X_dev.npy")
Y_dev = np.load("./XY_dev/Y_dev.npy")

2 模型

现在建立好了数据集,让我们写一个训练触发字检测的模型吧。

该模型将使用一维卷积层,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 构建模型

下面是将要使用的模型结构。 花点时间看看模型,看看它是否有意义。

吴恩达Coursera深度学习课程 deeplearning.ai (5-3) 序列模型和注意力机制--编程作业(二):触发字检测_第8张图片

该模型的一个关键步骤是1维卷积步骤(靠近图的底部)。 它输入5511步频谱,并输出一个1375步输出,然后再进行多层处理得到最终的 Ty T y = 1375步输出。 该层的作用类似于您在课程4中看到的提取低级特征的2D卷积,然后可能会生成较小维度的输出。

从计算角度而言,1-D 卷积层有助于加速模型,因为现在GRU仅处理1375个时步而不是5511个时间步。 两个GRU层从左到右读取输入序列,然后最终使用密集+sigmoid层对 yt y ⟨ t ⟩ 进行预测。 因为y是二进制值(0或1),所以我们在最后一层使用sigmoid输出来估计输出为1的机会,对应于刚刚说过“activate”的用户。

注意: 我们使用单向RNN而不是双向RNN。 这对于触发字检测非常重要,因为我们希望能够在它说出后立即检测触发字。 如果我们使用双向RNN,我们必须等待整个10秒的音频被记录下来,然后才能确定音频片段的第一秒是否有“activate”。

通过以下四步来实现模型:

  1. 卷积层
    • 使用Conv1D()实现,有196个kernel_size=15的filter,stride=4。[See documentation.]
  2. 第一个GRU层

    X = GRU(units = 128, return_sequences = True)(X)
    • 设置return_sequences=True确保所有GRU的隐藏状态都被传送到下一层。
    • 记住这一层后面紧跟着Dropout和BatchNorm。
  3. 第二个GRU层
    • 与第一个GRU层类似,只是有一个额外的dropout层
  4. 创建时间分布的dense层

    X = TimeDistributed(Dense(1, activation = "sigmoid"))(X)
    • 这将创建一个dense层,后跟一个sigmoid,以便用于dense层的参数对于每个时间步都是相同的。[See documentation.]
练习:实现 model()
# 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(units = 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(units = 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_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
# _________________________________________________________________

期待的输出

key value
Total params 522,561
Trainable params 521,657
Non-trainable params 904

网络的输入是(无,5511,101),输出是(无,1375,1)。Conv1D将频谱图中5511步减少到1375步。

2.2 适应模型

触发字检测需要很长时间来训练。 为了节省时间,我们已经使用上面构建的模型结构在GPU上训练了约3小时的模型,并使用大约4000个样本进行了训练。 让我们加载这个模型。

model = load_model('./models/tr_model.h5')

您可以使用Adam优化器和二元交叉熵损失进一步训练模型,如下所示。
执行速度很快,因为我们只训练一个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 [==============================] - 29s - loss: 0.0728 - acc: 0.9806

2.3 测试你的模型

最后,我们看看模型在开发测试集上表现如何。

loss, acc = model.evaluate(X_dev, Y_dev)
print("Dev set accuracy = ", acc)

# 25/25 [==============================] - 4s
# Dev set accuracy =  0.944989085197

看起来很不错! 然而,对于这项任务来说,准确性并不是一个很好的指标,因为标签严重倾斜到0,所以只输出0的神经网络的准确性会略高于90%。 我们可以定义更多有用的指标,如F1分数或Precision / Recall。 但是,这里我们不要烦恼,只是凭经验去看模型是如何工作的。

3 做预测

现在您已经构建了触发字检测的工作模型,让我们用它来进行预测。 此代码段通过网络运行音频(保存在wav文件中)。

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”这个词的概率,当概率超过某个阈值时,你就可以触发“chiming”声音。
此外,在“activate”之后,对于连续的许多值, yt y ⟨ t ⟩ 可能接近1,但我们只想要一次铃声。
所以我们每75个输出步骤最多插入一次“chiming”声音。
这将有助于防止我们为单个“chiming”实例插入两个“chiming”声音。
(这起到类似于计算机视觉非最大抑制的作用。)

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 在开发测试集上进行测试

让我们来看看我们的模型在开发集中的两个看不见的音频片段上表现如何。 让我们先听两个开发者设定的剪辑。

IPython.display.Audio("./raw_data/dev/1.wav")
IPython.display.Audio("./raw_data/dev/1.wav")

现在让我们在这些音频剪辑上运行模型,看看它是否在“activate”之后添加了“chiming”声音!

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")

谨记

  • 数据合成是为语音问题创建大型训练集的有效方式,特别是触发词检测。
  • 在将音频数据传送到RNN,GRU或LSTM之前,使用频谱图和可选的1D conv层是一个常见的预处理步骤。
  • 可以使用端到端的深度学习方法来构建非常有效的触发字检测系统。

恭喜你完成了最后的作业!

感谢你一直坚持到最后以及为学习深度学习所付出的所有努力。 我们希望你喜欢这门课程!

4 试验你自己的例子!(可选)

录制10秒的音频片段,说出“activate”和其他随机单词,然后将其上传到Coursera中心作为myaudio.wav。 确保将音频上传为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')

一旦将音频文件上传到Coursera,请将文件路径放在下面的变量中。

your_filename = "audio_examples/my_audio.wav"
preprocess_audio(your_filename)
IPython.display.Audio(your_filename) # listen to the audio you uploaded 

最后,使用模型来预测在10秒音频片段中何时有”activate”并触发“chiming”声音。 如果没有正确添加“chiming”声音,请尝试调整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")

你可能感兴趣的:(机器学习,深度学习,吴恩达)