【中文】【吴恩达课后编程作业】Course 5 - 序列模型 - 第三周作业 - 机器翻译与触发词检测
1 机器翻译欢迎来到本周的第一个编程作业!您将构建一个神经机器翻译 (NMT) 模型,将人类可读日期 (“25th of June, 2009”) 翻译为机器可读日期 (“2009-06-25”). 您将使用注意模型执行此操作, 序列模型中最复杂的序列之一。这个 notebook 是与NVIDIA的深度学习研究所共同制作的。让我们加载此作业所需的所有包。from keras.layers...
【中文】【吴恩达课后编程作业】Course 5 - 序列模型 - 第三周作业 - 机器翻译与触发词检测
致谢:
感谢@etangyushan翻译了本文,他给大家留言说:“希望人人都能献出一点爱”。
资料下载
- 本文所使用的资料已上传到百度网盘【点击下载(209.31MB)】,提取码:6reh ,请在开始之前下载好所需资料。
【博主使用的python版本:3.6.2】
1 机器翻译
欢迎来到本周的第一个编程作业!
您将构建一个神经机器翻译 (NMT) 模型,将人类可读日期 (“25th of June, 2009”) 翻译为机器可读日期 (“2009-06-25”). 您将使用注意模型执行此操作, 序列模型中最复杂的序列之一。
这个 notebook 是与NVIDIA的深度学习研究所共同制作的。
让我们加载此作业所需的所有包。
from keras.layers import Bidirectional, Concatenate, Permute, Dot, Input, LSTM, Multiply
from keras.layers import RepeatVector, Dense, Activation, Lambda
from keras.optimizers import Adam
from keras.utils import to_categorical
from keras.models import load_model, Model
import keras.backend as K
import numpy as np
from faker import Faker
import random
from tqdm import tqdm
from babel.dates import format_date
from nmt_utils import *
import matplotlib.pyplot as plt
%matplotlib inline
Using TensorFlow backend.
1.1 - 将人类可读日期翻译成机器可读日期
您将在此处构建的模型可用于从一种语言翻译到另一种语言, 例如从英语翻译成印地语。 但是,语言翻译需要大量数据集,并且通常需要使用 GPU 训练数天。 为了让您在不使用大量数据集的情况下尝试使用这些模型,我们将使用更简单的“日期转换”任务。 “date translation” task.
网络将输入以各种可能格式编写的日期 (例如:“the 29th of August 1958”, “03/30/1968”, “24 JUNE 1987”) 将它们转换为标准化的机器可读日期 (例如:“1958-08-29”, “1968-03-30”, “1987-06-24”). 我们将让网络学会以通用的机器可读格式输出日期YYYY-MM-DD.
(查看所有格式请查看文件: nmt_utils.py, 计算并弄清楚格式的工作原理, 以后会用到 !)
1.1.1 - 数据集
我们将在 10000 组人类可读日期,并且与之对应的,标准化的机器可读日期的数据集上训练模型。运行下面的单元价值数据集并且打印一些样例。
m = 10000
dataset, human_vocab, machine_vocab, inv_machine_vocab = load_dataset(m)
执行结果:
100%|████████████████████████████████████████████████████████████████| 10000/10000 [00:00<00:00, 14551.92it/s]
dataset[:10]
执行结果:
[('9 may 1998', '1998-05-09'),
('10.09.70', '1970-09-10'),
('4/28/90', '1990-04-28'),
('thursday january 26 1995', '1995-01-26'),
('monday march 7 1983', '1983-03-07'),
('sunday may 22 1988', '1988-05-22'),
('tuesday july 8 2008', '2008-07-08'),
('08 sep 1999', '1999-09-08'),
('1 jan 1981', '1981-01-01'),
('monday may 22 1995', '1995-05-22')]
已经加载了:
dataset
: 一个元组列表 (人类可读日期, 机器可读日期)。human_vocab
: 一个python字典,将人类可读日期中使用的所有字符映射到整数值索引。machine_vocab
: 一个python字典,将机器可读日期中使用的所有字符映射到整数值索引。这些索引不一定与human_vocab
的索引一致。inv_machine_vocab
:machine_vocab
的逆字典,从索引到字符的映射。
让我们对数据进行预处理,将原始文本数据映射到索引值。
我们使用:
Tx=30 (我们假设人类可读日期的最大长度; 如果我们得到更长的输入,我们将截断它)
Ty=10 (因为 “YYYY-MM-DD” 是 10 个字符长度).
Tx = 30
Ty = 10
X, Y, Xoh, Yoh = preprocess_data(dataset, human_vocab, machine_vocab, Tx, Ty)
print("X.shape:", X.shape)
print("Y.shape:", Y.shape)
print("Xoh.shape:", Xoh.shape)
print("Yoh.shape:", Yoh.shape)
测试结果:
X.shape: (10000, 30)
Y.shape: (10000, 10)
Xoh.shape: (10000, 30, 37)
Yoh.shape: (10000, 10, 11)
# print(Xoh)
你已经有:
X
: 训练集中人类可读日期的处理版本, 其中每个字符都被它在human_vocab
中映射该字符的索引替换。每个日期都使用特殊字符()进一步填充。维度为X.shape = (m, Tx)
Y
: 训练集中机器可读日期的处理版本, 其中每个字符都被它在machine_vocab
中映射的索引替换。 维度为Y.shape = (m, Ty)
。Xoh
:X
的 one-hot 版本, one-hot 中条目 “1” 的索引被映射到在human_vocab
中对应字符。维度为Xoh.shape = (m, Tx, len(human_vocab))
Yoh
:Y
的 one-hot 版本, one-hot 中条目 “1” 的索引被映射到由于machine_vocab
中对应字符。维度为Yoh.shape = (m, Tx, len(machine_vocab))
。 这里,len(machine_vocab) = 11
因为有 11 字符 (’-’ 以及 0-9).
让我们看一下预处理训练样本的一些示例。随意使用index
来导航数据集,了解源/目标日期的预处理方式。
index = 0
print("Source date:", dataset[index][0])
print("Target date:", dataset[index][1])
print()
print("Source after preprocessing (indices):", X[index])
print("Target after preprocessing (indices):", Y[index])
print()
print("Source after preprocessing (one-hot):", Xoh[index])
print("Target after preprocessing (one-hot):", Yoh[index])
测试结果:
Source date: 9 may 1998
Target date: 1998-05-09
Source after preprocessing (indices): [12 0 24 13 34 0 4 12 12 11 36 36 36 36 36 36 36 36 36 36 36 36 36 36
36 36 36 36 36 36]
Target after preprocessing (indices): [ 2 10 10 9 0 1 6 0 1 10]
Source after preprocessing (one-hot): [[0. 0. 0. ... 0. 0. 0.]
[1. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
...
[0. 0. 0. ... 0. 0. 1.]
[0. 0. 0. ... 0. 0. 1.]
[0. 0. 0. ... 0. 0. 1.]]
Target after preprocessing (one-hot): [[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
1.2 - 带注意力的神经机器翻译
如果你不得不将一本书的段落从法语翻译成英语, 你不会阅读整段,然后合上书并翻译。甚至在翻译过程中, 您将阅读/重新阅读并专注于与您所写的英语部分相对应的法文段落部分。
注意机制告诉神经机器翻译模型,它应该在任何一步都要有注意力。
1.2.1 - 注意机制
在这部分, 你将实现课程中介绍的注意力机制。这是一个图,以提醒您模型的工作原理。 左侧的图表展示注意力模型。右侧的图表展示了一个“注意”步骤:计算注意力变量 α ⟨ t , t ′ ⟩ \alpha^{\langle t, t' \rangle} α⟨t,t′⟩, 使用注意力变量计算输出中每个时间步( t = 1 , … , T y t=1, \ldots, T_y t=1,…,Ty)的上下文变量 c o n t e x t ⟨ t ⟩ context^{\langle t \rangle} context⟨t⟩
| |
以下是您可能要注意的模型的一些属性:
-
模型中有两个单独的 LSTM(见左图)。一个是图片底部的那个是 Bi-LSTM(双向LSTM)在 Attention 前,我们叫做 pre-attention Bi-LSTM。图的顶部的 LSTM 在 Attention 后,我们叫做 post-attention LSTM。pre-attention Bi-LSTM 经历 T x T_x Tx 时间步;post-attention LSTM 经历 T y T_y Ty 时间步。
-
post-attention LSTM 通过 s ⟨ t ⟩ , c ⟨ t ⟩ s^{\langle t \rangle}, c^{\langle t \rangle} s⟨t⟩,c⟨t⟩ 从一个时间步到下一个。在视频讲座中, 对于post-attention 序列模型我们仅使用了基本的 RNN,状态被 RNN 输出激活捕获 s ⟨ t ⟩ s^{\langle t\rangle} s⟨t⟩。但是因为我们在这里使用 LSTM , LSTM 有输出激活 s ⟨ t ⟩ s^{\langle t\rangle} s⟨t⟩ 和隐藏单元状态 c ⟨ t ⟩ c^{\langle t\rangle} c⟨t⟩。但是,与之前的文本生成示例(例如第1周的Dinosaurus)不同,在此模型中, post-activation LSTM 在时间 t t t 不会用具体生成的 y ⟨ t − 1 ⟩ y^{\langle t-1 \rangle} y⟨t−1⟩ 作为输入; 只需要 s ⟨ t ⟩ s^{\langle t\rangle} s⟨t⟩ 和 c ⟨ t ⟩ c^{\langle t\rangle} c⟨t⟩ 作为输入。我们以这种方式设计了模型,因为(与相邻字符高度相关的语言生成不同) 在YYYY-MM-DD日期中,前一个字符与下一个字符之间的依赖性不强。
-
我们用 a ⟨ t ⟩ = [ a → ⟨ t ⟩ ; a ← ⟨ t ⟩ ] a^{\langle t \rangle} = [\overrightarrow{a}^{\langle t \rangle}; \overleftarrow{a}^{\langle t \rangle}] a⟨t⟩=[a⟨t⟩;a⟨t⟩] 表示 pre-attention Bi-LSTM的前向和后向激活串联起来
-
右边的图使用了
RepeatVector
节点复制 s ⟨ t − 1 ⟩ s^{\langle t-1 \rangle} s⟨t−1⟩ 的值 T x T_x Tx 次, 然后Concatenation
串联 s ⟨ t − 1 ⟩ s^{\langle t-1 \rangle} s⟨t−1⟩ 和 a ⟨ t ⟩ a^{\langle t \rangle} a⟨t⟩ 计算 e ⟨ t , t ′ ⟩ e^{\langle t, t' \rangle} e⟨t,t′⟩, 再通过 softmax 计算 α ⟨ t , t ′ ⟩ \alpha^{\langle t, t' \rangle} α⟨t,t′⟩. 我们将在下面解释如何在 Keras 中使用RepeatVector
和Concatenation
。
让我们实现这个模型。您将从实现两个功能开始: one_step_attention()
和 model()
。
1) one_step_attention()
: 在步骤
t
t
t, 给出 Bi-LSTM 的所有隐藏状态 (
[
a
⟨
1
⟩
,
a
⟨
2
⟩
,
.
.
.
,
a
⟨
T
x
⟩
]
[a^{\langle 1 \rangle},a^{\langle 2 \rangle}, ..., a^{\langle T_x \rangle}]
[a⟨1⟩,a⟨2⟩,...,a⟨Tx⟩]) 以及第二个LSTM的先前隐藏状态 (
s
⟨
t
−
1
⟩
s^{\langle t-1 \rangle}
s⟨t−1⟩), one_step_attention()
计算注意力权重 (
[
α
⟨
t
,
1
⟩
,
α
⟨
t
,
2
⟩
,
.
.
.
,
α
⟨
t
,
T
x
⟩
]
[\alpha^{\langle t,1 \rangle},\alpha^{\langle t,2 \rangle}, ..., \alpha^{\langle t,T_x \rangle}]
[α⟨t,1⟩,α⟨t,2⟩,...,α⟨t,Tx⟩]) 并输出上下文向量 (详情见图 1.1 (右侧)):
c
o
n
t
e
x
t
⟨
t
⟩
=
∑
t
′
=
0
T
x
α
⟨
t
,
t
′
⟩
a
⟨
t
′
⟩
(1)
context^{\langle t \rangle} = \sum_{t' = 0}^{T_x} \alpha^{\langle t,t' \rangle}a^{\langle t' \rangle}\tag{1}
context⟨t⟩=t′=0∑Txα⟨t,t′⟩a⟨t′⟩(1)
请注意,我们在这个 notebook 中用 c o n t e x t ⟨ t ⟩ context^{\langle t \rangle} context⟨t⟩ 表示注意力。在视频讲座中用 c ⟨ t ⟩ c^{\langle t \rangle} c⟨t⟩ 表示 context, 但在这里我们称之为 c o n t e x t ⟨ t ⟩ context^{\langle t \rangle} context⟨t⟩ ,避免与 (post-attention) LSTM 内部存储器单元变量混淆,它有时也用 c ⟨ t ⟩ c^{\langle t \rangle} c⟨t⟩ 表示.
2) model()
: 实现整个模型。首先根据输入执行 Bi-LSTM 返回
[
a
⟨
1
⟩
,
a
⟨
2
⟩
,
.
.
.
,
a
⟨
T
x
⟩
]
[a^{\langle 1 \rangle},a^{\langle 2 \rangle}, ..., a^{\langle T_x \rangle}]
[a⟨1⟩,a⟨2⟩,...,a⟨Tx⟩]。 然后,调用 one_step_attention()
T
y
T_y
Ty 次 (for
循环)。在这个循环的每次迭代中, 将获得上下文向量
c
⟨
t
⟩
c^{\langle t \rangle}
c⟨t⟩ 给第二个 LSTM, 根据 LSTM 的输出,运行 softmax 激活的全连接层以生成预测
y
^
⟨
t
⟩
\hat{y}^{\langle t \rangle}
y^⟨t⟩。
练习: 实现 one_step_attention()
。 函数 model()
使用 for 循环调用 one_step_attention()
中的层
T
y
T_y
Ty 次, 很重要的一点是所有
T
y
T_y
Ty 的拷贝有相同权重。 也就是, 不应该每次都重新确定权重。换句话说所有
T
y
T_y
Ty 步骤都共享权重。以下是如何在Keras中实现具有可共享权重的层:
- 定义层对象 (作为样本的全局变量)
- 在传播输入时调用这些对象
我们已经将您需要的层定义为全局变量。请运行以下单元格来创建它们。请查看Keras文档以确保您了解这些图层做什么: RepeatVector(), Concatenate(), Dense(), Activation(), Dot().
# 将共享层定义为全局变量
repeator = RepeatVector(Tx)
concatenator = Concatenate(axis=-1)
densor1 = Dense(10, activation = "tanh")
densor2 = Dense(1, activation = "relu")
activator = Activation(softmax, name='attention_weights') # 在这个 notebook 我们正在使用自定义的 softmax(axis = 1)
dotor = Dot(axes = 1)
现在您可以使用这些图层来实现 one_step_attention()
。为了通过这些层之一传播Keras张量对象X,使用 layer(X)
(或 layer([X,Y])
如果它需要多个输入), 例如, densor(X)
将通过上面定义的Dense(1)
层传播 X。
# GRADED FUNCTION: one_step_attention
def one_step_attention(a, s_prev):
"""
执行一步 attention: 输出一个上下文向量,输出作为注意力权重的点积计算的上下文向量
"alphas" Bi-LSTM的 隐藏状态 "a"
参数:
a -- Bi-LSTM的输出隐藏状态 numpy-array 维度 (m, Tx, 2*n_a)
s_prev -- (post-attention) LSTM的前一个隐藏状态, numpy-array 维度(m, n_s)
返回:
context -- 上下文向量, 下一个(post-attetion) LSTM 单元的输入
"""
# 使用 repeator 重复 s_prev 维度 (m, Tx, n_s) 这样你就可以将它与所有隐藏状态"a" 连接起来。 (≈ 1 line)
s_prev = repeator(s_prev)
# 使用 concatenator 在最后一个轴上连接 a 和 s_prev (≈ 1 line)
concat = concatenator([a, s_prev])
# 使用 densor1 传入参数 concat, 通过一个小的全连接神经网络来计算“中间能量”变量 e。(≈1 lines)
e = densor1(concat)
# 使用 densor2 传入参数 e , 通过一个小的全连接神经网络来计算“能量”变量 energies。(≈1 lines)
energies = densor2(e)
# 使用 activator 传入参数 "energies" 计算注意力权重 "alphas" (≈ 1 line)
alphas = activator(energies)
# 使用 dotor 传入参数 "alphas" 和 "a" 计算下一个((post-attention) LSTM 单元的上下文向量 (≈ 1 line)
context = dotor([alphas, a])
return context
在编写 model()
函数之后,您将能够检查 one_step_attention()
的预期输出。
练习: 实现 model()
如图1.1和上文所述。 同样,我们已经定义了全局图层用于在 model()
中共享权重。
n_a = 32
n_s = 64
post_activation_LSTM_cell = LSTM(n_s, return_state = True)
output_layer = Dense(len(machine_vocab), activation=softmax)
现在您可以在for
循环中使用这些图层
T
y
T_y
Ty 次来生成输出,并且他们的参数不会重新初始化。您必须执行以下步骤:
-
传入输入参数到 Bidirectional LSTM
-
迭代 for t = 0 , … , T y − 1 t = 0, \dots, T_y-1 t=0,…,Ty−1:
- 调用
one_step_attention()
使用 [ α < t , 1 > , α < t , 2 > , . . . , α < t , T x > ] [\alpha^{<t,1>},\alpha^{<t,2>}, ..., \alpha^{<t,T_x>}] [α<t,1>,α<t,2>,...,α<t,Tx>] 和 s < t − 1 > s^{<t-1>} s<t−1> 为参数,获取上下文向量 c o n t e x t < t > context^{<t>} context<t>。 - 使用
c
o
n
t
e
x
t
<
t
>
context^{<t>}
context<t> 作为参数给 post-attention LSTM单元,记得传入这个LSTM以前的隐藏状态
s
⟨
t
−
1
⟩
s^{\langle t-1\rangle}
s⟨t−1⟩ 和单元状态
c
⟨
t
−
1
⟩
c^{\langle t-1\rangle}
c⟨t−1⟩ ,使用
initial_state= [previous hidden state, previous cell state]
。 返回性的隐藏状态 s < t > s^{<t>} s<t> 和新的单元状态 c < t > c^{<t>} c<t>。 - 应用softmax图层 s < t > s^{<t>} s<t>,获取输出 。
- 通过将输出添加到输出列表来保存输出。
- 调用
-
创建您的Keras模型实例,它应该有三个输入(“输入列表”, s < 0 > s^{<0>} s<0> and c < 0 > c^{<0>} c<0>) 和输出列表。
# GRADED FUNCTION: model
def model(Tx, Ty, n_a, n_s, human_vocab_size, machine_vocab_size):
"""
参数:
Tx -- 输入序列的长度
Ty -- 输出序列的长度
n_a -- Bi-LSTM的隐藏状态大小
n_s -- post-attention LSTM的隐藏状态大小
human_vocab_size -- python字典 "human_vocab" 的大小
machine_vocab_size -- python字典 "machine_vocab" 的大小
返回:
model -- Keras 模型实例
"""
# 定义模型的输入,维度 (Tx,)
# 定义 s0 和 c0, 初始化解码器 LSTM 的隐藏状态,维度 (n_s,)
X = Input(shape=(Tx, human_vocab_size))
s0 = Input(shape=(n_s,), name='s0')
c0 = Input(shape=(n_s,), name='c0')
s = s0
c = c0
# 初始化一个空的输出列表
outputs = []
# 第一步:定义 pre-attention Bi-LSTM。 记得使用 return_sequences=True. (≈ 1 line)
a = Bidirectional(LSTM(n_a, return_sequences=True), input_shape=(m, Tx, n_a * 2))(X)
# 第二步:迭代 Ty 步
for t in range(Ty):
# 第二步.A: 执行一步注意机制,得到在 t 步的上下文向量 (≈ 1 line)
context = one_step_attention(a, s)
# 第二步.B: 使用 post-attention LSTM 单元得到新的 "context"
# 别忘了使用: initial_state = [hidden state, cell state] (≈ 1 line)
s, _, c = post_activation_LSTM_cell(context, initial_state=[s, c])
# 第二步.C: 使用全连接层处理post-attention LSTM 的隐藏状态输出 (≈ 1 line)
out = output_layer(s)
# 第二步.D: 追加 "out" 到 "outputs" 列表 (≈ 1 line)
outputs.append(out)
# 第三步:创建模型实例,获取三个输入并返回输出列表。 (≈ 1 line)
model = Model(inputs=[X, s0, c0], outputs=outputs)
return model
运行以下单元以创建模型。
model = model(Tx, Ty, n_a, n_s, len(human_vocab), len(machine_vocab))
让我们查看模型的摘要,以检查它是否与预期输出相匹配。
model.summary()
执行结果:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 30, 37) 0
__________________________________________________________________________________________________
s0 (InputLayer) (None, 64) 0
__________________________________________________________________________________________________
bidirectional_1 (Bidirectional) (None, 30, 64) 17920 input_1[0][0]
__________________________________________________________________________________________________
repeat_vector_1 (RepeatVector) (None, 30, 64) 0 s0[0][0]
lstm_1[0][0]
lstm_1[1][0]
lstm_1[2][0]
lstm_1[3][0]
lstm_1[4][0]
lstm_1[5][0]
lstm_1[6][0]
lstm_1[7][0]
lstm_1[8][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 30, 128) 0 bidirectional_1[0][0]
repeat_vector_1[0][0]
bidirectional_1[0][0]
repeat_vector_1[1][0]
bidirectional_1[0][0]
repeat_vector_1[2][0]
bidirectional_1[0][0]
repeat_vector_1[3][0]
bidirectional_1[0][0]
repeat_vector_1[4][0]
bidirectional_1[0][0]
repeat_vector_1[5][0]
bidirectional_1[0][0]
repeat_vector_1[6][0]
bidirectional_1[0][0]
repeat_vector_1[7][0]
bidirectional_1[0][0]
repeat_vector_1[8][0]
bidirectional_1[0][0]
repeat_vector_1[9][0]
__________________________________________________________________________________________________
dense_1 (Dense) (None, 30, 10) 1290 concatenate_1[0][0]
concatenate_1[1][0]
concatenate_1[2][0]
concatenate_1[3][0]
concatenate_1[4][0]
concatenate_1[5][0]
concatenate_1[6][0]
concatenate_1[7][0]
concatenate_1[8][0]
concatenate_1[9][0]
__________________________________________________________________________________________________
dense_2 (Dense) (None, 30, 1) 11 dense_1[0][0]
dense_1[1][0]
dense_1[2][0]
dense_1[3][0]
dense_1[4][0]
dense_1[5][0]
dense_1[6][0]
dense_1[7][0]
dense_1[8][0]
dense_1[9][0]
__________________________________________________________________________________________________
attention_weights (Activation) (None, 30, 1) 0 dense_2[0][0]
dense_2[1][0]
dense_2[2][0]
dense_2[3][0]
dense_2[4][0]
dense_2[5][0]
dense_2[6][0]
dense_2[7][0]
dense_2[8][0]
dense_2[9][0]
__________________________________________________________________________________________________
dot_1 (Dot) (None, 1, 64) 0 attention_weights[0][0]
bidirectional_1[0][0]
attention_weights[1][0]
bidirectional_1[0][0]
attention_weights[2][0]
bidirectional_1[0][0]
attention_weights[3][0]
bidirectional_1[0][0]
attention_weights[4][0]
bidirectional_1[0][0]
attention_weights[5][0]
bidirectional_1[0][0]
attention_weights[6][0]
bidirectional_1[0][0]
attention_weights[7][0]
bidirectional_1[0][0]
attention_weights[8][0]
bidirectional_1[0][0]
attention_weights[9][0]
bidirectional_1[0][0]
__________________________________________________________________________________________________
c0 (InputLayer) (None, 64) 0
__________________________________________________________________________________________________
lstm_1 (LSTM) [(None, 64), (None, 33024 dot_1[0][0]
s0[0][0]
c0[0][0]
dot_1[1][0]
lstm_1[0][0]
lstm_1[0][2]
dot_1[2][0]
lstm_1[1][0]
lstm_1[1][2]
dot_1[3][0]
lstm_1[2][0]
lstm_1[2][2]
dot_1[4][0]
lstm_1[3][0]
lstm_1[3][2]
dot_1[5][0]
lstm_1[4][0]
lstm_1[4][2]
dot_1[6][0]
lstm_1[5][0]
lstm_1[5][2]
dot_1[7][0]
lstm_1[6][0]
lstm_1[6][2]
dot_1[8][0]
lstm_1[7][0]
lstm_1[7][2]
dot_1[9][0]
lstm_1[8][0]
lstm_1[8][2]
__________________________________________________________________________________________________
dense_3 (Dense) (None, 11) 715 lstm_1[0][0]
lstm_1[1][0]
lstm_1[2][0]
lstm_1[3][0]
lstm_1[4][0]
lstm_1[5][0]
lstm_1[6][0]
lstm_1[7][0]
lstm_1[8][0]
lstm_1[9][0]
==================================================================================================
Total params: 52,960
Trainable params: 52,960
Non-trainable params: 0
__________________________________________________________________________________________________
像往常一样,在Keras创建模型后,你需要编译它并定义模型使用的损失, 优化和评估指标。编译模型时,损失使用 categorical_crossentropy
,优化算法使用 Adam optimizer (learning rate = 0.005
,
β
1
=
0.9
\beta_1 = 0.9
β1=0.9,
β
2
=
0.999
\beta_2 = 0.999
β2=0.999, decay = 0.01
),评估指标使用 ['accuracy']
:
### START CODE HERE ### (≈2 lines)
opt = Adam(lr=0.005, beta_1=0.9, beta_2=0.999, decay=0.01)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
### END CODE HERE ###
最后一步式定义所有的输入和输出并训练模型:
- 你已经有包含训练样例的 X, 维度 ( m = 10000 , T x = 30 ) (m = 10000, T_x = 30) (m=10000,Tx=30)
- 你需要创建用0初始化的
s0
和c0
,用于初始的post_activation_LSTM_cell
。 - 给定的
model()
,你需要“输出”11个维度为(m,T_y)元素的列表,以便outputs[i][0], ..., outputs[i][Ty]
表示第 i t h i^{th} ith 个训练样本 (X[i]
)对应的真实标签(字符)。大多数情况下,第 i t h i^{th} ith 个训练样本中第 j t h j^{th} jth 个字符的真正的标签是outputs[i][j]
。
s0 = np.zeros((m, n_s))
c0 = np.zeros((m, n_s))
outputs = list(Yoh.swapaxes(0,1))
现在我们训练模型,迭代一次
model.fit([Xoh, s0, c0], outputs, epochs=1, batch_size=100)
在训练时,您可以看到输出的10个位置中的每个位置的损失和准确性 下表给出了一个例子,说明如果批次有两个例子,精度可能是多少:
dense_2_acc_8: 0.89
表示您在当前批量数据中,89%的时间正确预测输出了第7个字符。
我们已经运行这个模型的时间更长时间,并且保存了权重。运行下面的单元加载我们的权重。(通过几分钟模型训练,您应该能够获得类似精度的模型,但加载我们的模型将节省您的时间。)
model.load_weights('models/model.h5')
您现在可以在新示例中看到结果。
EXAMPLES = ['3 May 1979', '5 April 09', '21th of August 2016', 'Tue 10 Jul 2007', 'Saturday May 9 2018', 'March 3 2001', 'March 3rd 2001', '1 March 2001']
for example in EXAMPLES:
source = string_to_int(example, Tx, human_vocab)
source = np.array(list(map(lambda x: to_categorical(x, num_classes=len(human_vocab)), source)))
source = np.expand_dims(source, axis=0)
prediction = model.predict([source, s0, c0])
prediction = np.argmax(prediction, axis = -1)
output = [inv_machine_vocab[int(i)] for i in prediction]
print("source:", example)
print("output:", ''.join(output))
测试结果:
source: 3 May 1979
output: 1979-05-03
source: 5 April 09
output: 2009-05-05
source: 21th of August 2016
output: 2016-08-21
source: Tue 10 Jul 2007
output: 2007-07-10
source: Saturday May 9 2018
output: 2018-05-09
source: March 3 2001
output: 2001-03-03
source: March 3rd 2001
output: 2001-03-03
source: 1 March 2001
output: 2001-03-01
您还可以更改这些示例以使用您自己的示例进行测试。 下一部分将让您更好地了解注意机制在做什么 – 即,在生成特定输出字符时网络注意哪些部分输入。
1.3 - 可视化注意力 (选学)
由于问题的固定输出长度为10,也可以使用10个不同的softmax单元执行此任务,以生成输出的10个字符,但注意模型的一个优点是输出的每个部分(比如月份)都知道它只需要依赖于输入的一小部分(输入中给出月份的字符)我们可以看到输出的哪个部分正在查看输入的哪个部分。
考虑翻译的任务 “Saturday 9 May 2018” to “2018-05-09”. 如果我们可视化计算的
α
⟨
t
,
t
′
⟩
\alpha^{\langle t, t' \rangle}
α⟨t,t′⟩ 我们得到下面这个:
注意输出为什么忽略输入的 “Saturday” 部分。输出时间步长没有注意到输入的那部分,我们还看到 9 已被翻译为 09 并且 May 已被正确翻译为 05,输出时要注意翻译所需的输入部分。年份主要要求它注意输入的“18”以生成“2018”。
1.3.1 - 从网络获取激活
现在让我们可视化您网络中的注意力值。我们将通过传入一个样例给网络,然后可视化 α ⟨ t , t ′ ⟩ \alpha^{\langle t, t' \rangle} α⟨t,t′⟩ 的值。
为了确定注意力值的位置,让我们首先打印模型的摘要。
model.summary()
执行结果:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 30, 37) 0
__________________________________________________________________________________________________
s0 (InputLayer) (None, 64) 0
__________________________________________________________________________________________________
bidirectional_1 (Bidirectional) (None, 30, 64) 17920 input_1[0][0]
__________________________________________________________________________________________________
repeat_vector_1 (RepeatVector) (None, 30, 64) 0 s0[0][0]
lstm_1[0][0]
lstm_1[1][0]
lstm_1[2][0]
lstm_1[3][0]
lstm_1[4][0]
lstm_1[5][0]
lstm_1[6][0]
lstm_1[7][0]
lstm_1[8][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 30, 128) 0 bidirectional_1[0][0]
repeat_vector_1[0][0]
bidirectional_1[0][0]
repeat_vector_1[1][0]
bidirectional_1[0][0]
repeat_vector_1[2][0]
bidirectional_1[0][0]
repeat_vector_1[3][0]
bidirectional_1[0][0]
repeat_vector_1[4][0]
bidirectional_1[0][0]
repeat_vector_1[5][0]
bidirectional_1[0][0]
repeat_vector_1[6][0]
bidirectional_1[0][0]
repeat_vector_1[7][0]
bidirectional_1[0][0]
repeat_vector_1[8][0]
bidirectional_1[0][0]
repeat_vector_1[9][0]
__________________________________________________________________________________________________
dense_1 (Dense) (None, 30, 10) 1290 concatenate_1[0][0]
concatenate_1[1][0]
concatenate_1[2][0]
concatenate_1[3][0]
concatenate_1[4][0]
concatenate_1[5][0]
concatenate_1[6][0]
concatenate_1[7][0]
concatenate_1[8][0]
concatenate_1[9][0]
__________________________________________________________________________________________________
dense_2 (Dense) (None, 30, 1) 11 dense_1[0][0]
dense_1[1][0]
dense_1[2][0]
dense_1[3][0]
dense_1[4][0]
dense_1[5][0]
dense_1[6][0]
dense_1[7][0]
dense_1[8][0]
dense_1[9][0]
__________________________________________________________________________________________________
attention_weights (Activation) (None, 30, 1) 0 dense_2[0][0]
dense_2[1][0]
dense_2[2][0]
dense_2[3][0]
dense_2[4][0]
dense_2[5][0]
dense_2[6][0]
dense_2[7][0]
dense_2[8][0]
dense_2[9][0]
__________________________________________________________________________________________________
dot_1 (Dot) (None, 1, 64) 0 attention_weights[0][0]
bidirectional_1[0][0]
attention_weights[1][0]
bidirectional_1[0][0]
attention_weights[2][0]
bidirectional_1[0][0]
attention_weights[3][0]
bidirectional_1[0][0]
attention_weights[4][0]
bidirectional_1[0][0]
attention_weights[5][0]
bidirectional_1[0][0]
attention_weights[6][0]
bidirectional_1[0][0]
attention_weights[7][0]
bidirectional_1[0][0]
attention_weights[8][0]
bidirectional_1[0][0]
attention_weights[9][0]
bidirectional_1[0][0]
__________________________________________________________________________________________________
c0 (InputLayer) (None, 64) 0
__________________________________________________________________________________________________
lstm_1 (LSTM) [(None, 64), (None, 33024 dot_1[0][0]
s0[0][0]
c0[0][0]
dot_1[1][0]
lstm_1[0][0]
lstm_1[0][2]
dot_1[2][0]
lstm_1[1][0]
lstm_1[1][2]
dot_1[3][0]
lstm_1[2][0]
lstm_1[2][2]
dot_1[4][0]
lstm_1[3][0]
lstm_1[3][2]
dot_1[5][0]
lstm_1[4][0]
lstm_1[4][2]
dot_1[6][0]
lstm_1[5][0]
lstm_1[5][2]
dot_1[7][0]
lstm_1[6][0]
lstm_1[6][2]
dot_1[8][0]
lstm_1[7][0]
lstm_1[7][2]
dot_1[9][0]
lstm_1[8][0]
lstm_1[8][2]
__________________________________________________________________________________________________
dense_3 (Dense) (None, 11) 715 lstm_1[0][0]
lstm_1[1][0]
lstm_1[2][0]
lstm_1[3][0]
lstm_1[4][0]
lstm_1[5][0]
lstm_1[6][0]
lstm_1[7][0]
lstm_1[8][0]
lstm_1[9][0]
==================================================================================================
Total params: 52,960
Trainable params: 52,960
Non-trainable params: 0
__________________________________________________________________________________________________
浏览上面 model.summary()
的输出。你可以看到图层名为 attention_weights
的输出 alphas
维度 (m, 30, 1) 在 dot_2
计算每个时间步
t
=
0
,
…
,
T
y
−
1
t = 0, \ldots, T_y-1
t=0,…,Ty−1 的上下文向量之前。
函数 attention_map()
从模型中提取注意值并绘制它们。
attention_map = plot_attention_map(model, human_vocab, inv_machine_vocab, "Tuesday 09 Oct 1993", num = 7, n_s = 64)
执行结果:
<Figure size 432x288 with 0 Axes>
在生成的图上,您可以观察预测输出的每个字符的注意权重值。检查此图并检查网络注意的位置对您有意义,在日期翻译应用程序中,您将观察到大多数时间注意力有助于预测年份,并且对预测日期/月份没有太大影响。
需要记住的是::
- 机器翻译模型可用于从一个序列映射到另一个序列。 它们不仅可用于翻译人类语言(如法语 - >英语),还可用于日期格式翻译等任务。
- 注意机制允许网络在生成输出的特定部分时关注输入的最相关部分。
- 使用注意机制的网络可以从长度为 T x T_x Tx 的输入转换为长度为 T y T_y Ty 的输出,其中 T x T_x Tx 和 T y T_y Ty 可以不同。
- 你可以将注意力权重 α ⟨ t , t ′ ⟩ \alpha^{\langle t,t' \rangle} α⟨t,t′⟩ 可视化,查看在生成每个输出时网络正在关注什么。
您现在可以实现注意模型并使用它来学习从一个序列到另一个序列的复杂映射。
2 触发词检测
欢迎来到这个专业的最终编程任务!
在本周的视频中, 你学会了将深度学习应用于语音识别。 在这个任务中,您将构建语音数据集并实现触发词检测的算法(有时也称为关键字检测或唤醒字检测)。 触发字检测是类似于 Amazon Alexa, Google Home, Apple Siri, and Baidu DuerOS 等设备听到某个词后被唤醒的功能。
对于这个练习,我们的触发词是 “Activate.” 每次听到你说 “activate” 它会发出“鸣叫”的声音。 在此作业结束时,您将能够录制自己说话的片段,并在检测到您说 "activate"时让算法触发铃声。
完成此任务后,您也可以将其扩展为在笔记本电脑上运行,这样每次您说 “activate” 它都会启动您喜爱的应用程序,或打开您家中连接网络的灯,或触发一些其他的事件?
在此作业中,您将学习到:
- 构建语音识别项目
- 合成和处理录音,创建训练/开发数据集
- 训练触发词检测模型并进行预测
让我们开始吧!
运行以下单元格以加载要使用的包。
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
2.1 - 数据合成:创建语音数据集
让我们首先为触发词检测算法构建数据集。 理想情况下,语音数据集应尽可能接近您要运行它的应用程序。在这种情况下,您希望在工作环境(图书馆,家庭,办公室,开放空间 …)中检测 “activate” 一词。 因此,您需要创建带有正例单词(“activate”)的录音和负例单词(activate 单词以外的随机单词)在不同的背景声音下。 我们来看看如何创建这样的数据集。
2.1.1 - 听取数据
你的一个朋友正在帮助你完成这个项目,他们去了该地区的图书馆,咖啡馆,餐馆,家里和办公室,以记录背景噪音,以及人们说正例/负例词语的音频片段。该数据集包括以各种口音的人说的话。
在raw_data目录中您可以找到正例词,负例词和背景噪音的原始音频文件的子集。您将使用这些音频文件来合成数据集以训练模型。activates 目录包含人们说 “activate” 一词的正例样本。negatives 目录包含人们说 “activate” 以外的随机词的负例样本。每个录音有一个词。 “backgrounds” 目录包含10个不同环境中的背景噪声剪辑。
运行下面的单元格以听一些示例。
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不支持播放音频
您将使用这三种类型的记录(正例/负例/背景)来创建标签数据集。
2.1.2 - 从录音到频谱图
什么是录音? 麦克风记录随时间空气压力的微小变化,正是这些气压的微小变化使您的耳朵也感觉到声音。您可以认为录音是一长串通过麦克风检测到的气压微小。变化的测量数值。我们将使用 44100 Hz(或者 44100 Hertz) 采样的音频,这意味着麦克风每秒给我们 44100 个数字。因此,10 秒的音频剪辑由 441000 (= 10 × 44100 10 \times 44100 10×44100)个数字表示。
很难从音频的这种“原始”表示中找出 “activate” 这个词是否被说出来。为了帮助您的序列模型更容易学习检测触发词,我们将计算音频的谱图。频谱图告诉我们在某个时刻音频片段中存在多少不同的频率。
(如果你曾经进行过信号处理或傅立叶变换的高级课程,则通过在原始音频信号上滑动窗口来计算频谱图,并使用傅立叶变换计算每个窗口中最活跃的频率。如果您不理解上一句话,请不要担心。)
让我们看一个例子。
IPython.display.Audio("audio_examples/example_train.wav")
执行结果:
CSDN不支持播放音频
x = graph_spectrogram("audio_examples/example_train.wav")
执行结果:
上图表示在多个时间步长(x轴)上每个频率(y轴)的活动程度。
绿色方块表示某个频率在音频片段中更活跃或更多(更响亮);蓝色方块表示较不活跃的频率。
输出频谱图的尺寸取决于频谱图软件的超参数和输入的长度。在这个 notebook 中,我们将使用10秒音频剪辑作为我们训练样例的“标准长度”。频谱图的时间步数为5511。稍后您会看到频谱图将输入 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)
现在,您可以定义:
Tx = 5511 # 从频谱图输入到模型的时间步数
n_freq = 101 # 在频谱图的每个时间步输入模型的频率数
请注意,即使我们的默认训练示例长度为10秒,也可以将10秒的时间离散化为不同的值。你已经知道 441000(原始音频)和 5511(频谱图)。在前一种情况下,每一步代表 10 / 441000 ≈ 0.000023 10/441000 \approx 0.000023 10/441000≈0.000023 秒。在第二种情况下,每个步骤代表 10 / 5511 ≈ 0.0018 10/5511 \approx 0.0018 10/5511≈0.0018 秒。
对于10秒的音频,您将在此作业中看到的键值为:
- 441000 441000 441000 (原始音频)
- 5511 = T x 5511 = T_x 5511=Tx (频谱图输出和神经网络输入的维数).
-
10000
10000
10000 (由
pydub
模块用来合成音频) - 1375 = T y 1375 = T_y 1375=Ty (您将构建的GRU输出中的步骤数).
请注意,这些表示中的每一个都恰好对应于10秒的时间。只是他们在不同程度上将它们离散化。所有这些都是超参数,可以更改(除了 441000,这是麦克风的性能)。我们选择了语音系统标准范围内的值。
考虑上面 T y = 1375 T_y = 1375 Ty=1375 的值,这意味着对于模型的输出,我们将10秒离散化为 1375 个时间间隔(每一个长度 10 / 1375 ≈ 0.0072 10/1375 \approx 0.0072 10/1375≈0.0072s) 并且尝试预测每个区间是否有人最近说完 “activate” 。
还要考虑上面的 10000 的值。这相当于将10秒剪辑离散为 10/10000 = 0.001秒间隔。0.001秒也称为1毫秒,或者1ms。因此,当我们说我们按照1ms间隔进行离散化时,这意味着我们使用了10,000步。
Ty = 1375 # 我们模型输出中的时间步数
2.1.3 - 生成单个训练样例
由于语音数据难以获取和标记,您将使用正例,负例和背景的音频剪辑合成您的训练数据。录制大量10秒音频片段并且里面随机的有 “activates” 是很慢的。相反,很容易录制很多正例和负例单词,背景噪音(或者从网上免费的下载背景噪音)
要合成单个训练示例,您将:
- 选择一个随机的10秒背景音频剪辑。
- 随机将 0-4 段正例(“activate”)的音频剪辑插入到这个10秒音频剪辑中。
- 随机将 0-2 段负例(不是 “activate”)的音频剪辑插入到这个10秒音频剪辑中。
因为您已将 “activate” 一词合成到背景剪辑中,所以您确切地知道在10秒剪辑中 “activate” 出现的时间。稍后您会看到,这样也可以更容易地生成标签 y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩。
您将使用pydub包来操纵音频。Pydub将原始音频文件转换为Pydub数据结构列表(这里不需要了解详细信息过程)。Pydub使用1ms作为离散化间隔(1ms是1毫秒= 1/1000秒),这就是为什么10秒剪辑总是用10,000步表示的原因。
# 使用pydub加载音频片段
activates, negatives, backgrounds = load_raw_audio()
print("background len: " + str(len(backgrounds[0]))) # 应该是10,000,因为它是一个10秒的剪辑
print("activate[0] len: " + str(len(activates[0]))) # 也许大约1000,因为 "activate" 音频剪辑通常大约1秒(但变化很大)
print("activate[1] len: " + str(len(activates[1]))) # 不同的 "activate" 剪辑可以具有不同的长度
执行结果:
background len: 10000
activate[0] len: 721
activate[1] len: 731
在背景上叠加正例/负例单词:
给定10秒背景剪辑和短音频剪辑(正例或负例单词),您需要能够将单词的短音频剪辑“添加”或“插入”到背景上。要确保插入到背景中的音频片段不重叠,您将跟踪先前插入的音频片段的时间。您将在背景上插入多个正例/负例字的剪辑,并且您不希望在某个与您之前添加的另一个剪辑重叠的地方插入 “activate” 或随机词。
为了清楚起见,当您在咖啡馆噪音的10秒剪辑中插入1秒 “activate” 时,您最终得到一个10秒的剪辑,听起来像某人在咖啡馆中说 “activate” ,“activate” 叠加在背景咖啡厅噪音上。你最终没有得到一个11秒的剪辑。稍后您将看到pydub如何允许您这样做。
在叠加的同时创建标签:
回想一下,标签
y
⟨
t
⟩
y^{\langle t \rangle}
y⟨t⟩ 表示某人是否刚刚说完 “activate”。给定背景剪辑,我们可以为所有
t
t
t 初始化
y
⟨
t
⟩
=
0
y^{\langle t \rangle}=0
y⟨t⟩=0,因为背景剪辑不包含任何 “activates”。
当您插入或叠加“激活”剪辑时,您还将更新
y
⟨
t
⟩
y^{\langle t \rangle}
y⟨t⟩ 的标签,以便输出的50个步骤现在具有目标标签1。您将训练 GRU 以检测何时有人完成说 “activate”。例如,假设合成的 “activate” 剪辑在10秒音频中的5秒标记处结束 — 正好在剪辑的一半处。回想一下
T
y
=
1375
T_y = 1375
Ty=1375 ,所以时间步长 $687 = $ int(1375*0.5)
对应于进入音频5秒的那一刻。所以,你会设置
y
⟨
688
⟩
=
1
y^{\langle 688 \rangle} = 1
y⟨688⟩=1。
此外,如果GRU在这个时刻之后在短时间内检测到 “activate” ,你会非常满意,因此我们实际上将标签
y
⟨
t
⟩
y^{\langle t \rangle}
y⟨t⟩ 的50个连续值设置为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。
这是合成训练数据的另一个原因:如上所述生成这些标签 y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩ 相对简单。相比之下,如果您在麦克风上录制了10秒的音频,那么一个人收听它并在说 “activate” 完成后手动标记是非常耗时的。
这是一个图形说明标签 y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩ 对于我们插入了 “activate”, “innocent”, activate", “baby” 的剪辑。请注意,正例标签“1”仅与正例词相关联。
要实现训练集合成过程,您将使用以下辅助函数。所有这些功能都将使用1ms的离散化间隔,因此10秒的音频可以离散化为10,000步。
get_random_time_segment(segment_ms)
从我们的背景音频中获取随机时间段is_overlapping(segment_time, existing_segments)
检查时间段是否与现有段重叠insert_audio_clip(background, audio_clip, existing_times)
在我们的背景音频中随机插入音频片段 使用get_random_time_segment
和is_overlapping
insert_ones(y, segment_end_ms)
在 “activate” 一词后面的标签向量y中插入1
函数 get_random_time_segment(segment_ms)
返回一个随机时间段,我们可以在其上插入持续时间的音频剪辑 segment_ms
。仔细阅读代码,确保您了解它的作用。
def get_random_time_segment(segment_ms):
"""
获取 10,000 ms音频剪辑中时间长为 segment_ms 的随机时间段。
参数:
segment_ms -- 音频片段的持续时间,以毫秒为单位("ms" 代表 "毫秒")
返回:
segment_time -- 以ms为单位的元组(segment_start,segment_end)
"""
segment_start = np.random.randint(low=0, high=10000-segment_ms) # 确保段不会超过10秒背景
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)
检查新时间段是否与任何先前的段重叠。
您需要执行两个步骤:
- 创建一个“False”标志,如果发现重叠,将设置为“True
- 循环遍历 previous_segments 的开始和结束时间。将这些时间与段的开始和结束时间进行比较。如果存在重叠,请将(1)中定义的标志设置为True。您可以使用:
for ....:
if ... <= ... and ... >= ...:
...
提示:如果段在前一段结束之前开始,则段重叠,并且段在前一段开始之后结束。
# GRADED FUNCTION: is_overlapping
def is_overlapping(segment_time, previous_segments):
"""
检查段的时间是否与现有段的时间重叠。
参数:
segment_time -- 新段的元组(segment_start,segment_end)
previous_segments -- 现有段的元组列表(segment_start,segment_end)
返回:
如果时间段与任何现有段重叠,则为True,否则为False
"""
segment_start, segment_end = segment_time
# 第一步:将重叠标识 overlap 初始化为“False”标志 (≈ 1 line)
overlap = False
# 第二步:循环遍历 previous_segments 的开始和结束时间。
# 比较开始/结束时间,如果存在重叠,则将标志 overlap 设置为True (≈ 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
现在,让我们使用前面的辅助函数在随机时间将新的音频剪辑插入到10秒背景上,但确保任何新插入的片段不会与之前的片段重叠。
练习: 实现 insert_audio_clip()
将音频剪辑叠加到背景10秒剪辑上。您需要执行4个步骤:
- 以ms为单位获取持续时间的随机时间段。
- 确保时间段不与之前的任何时间段重叠。如果它重叠,则返回步骤1并选择一个新的时间段。
- 将新时间段添加到现有时间段列表中,以便跟踪您插入的所有段。
- 使用pydub在背景上叠加音频剪辑。我们已经为您实现了这一点。
# GRADED FUNCTION: insert_audio_clip
def insert_audio_clip(background, audio_clip, previous_segments):
"""
在随机时间步骤中在背景噪声上插入新的音频片段,确保音频片段与现有片段不重叠。
参数:
background -- 10秒背景录音。
audio_clip -- 要插入/叠加的音频剪辑。
previous_segments -- 已放置的音频片段的时间
返回:
new_background -- 更新的背景音频
"""
# 以ms为单位获取音频片段的持续时间
segment_ms = len(audio_clip)
# 第一步:使用其中一个辅助函数来选择要插入的随机时间段
# 新的音频剪辑。 (≈ 1 line)
segment_time = get_random_time_segment(segment_ms)
# 第二步:检查新的segment_time是否与previous_segments之一重叠。
# 如果重叠如果是这样,请继续随机选择新的 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不支持播放音频
# 预期的音频
IPython.display.Audio("audio_examples/insert_reference.wav")
测试结果:
CSDN不支持播放音频
最后,实现代码来更新标签
y
⟨
t
⟩
y^{\langle t \rangle}
y⟨t⟩, 假设您刚刚插入 “activate”。 在下面的代码中, y
是一个向量,维度为 (1,1375)
, 因此
T
y
=
1375
T_y = 1375
Ty=1375.
如果 “activate” 在时间
t
t
t 步结束 , 则设置
y
⟨
t
+
1
⟩
=
1
y^{\langle t+1 \rangle} = 1
y⟨t+1⟩=1 以及后面额外的连续49个的值也为 1。 但是,请确保不要运行数组的末尾并尝试更新 y[0][1375]
, 因为有效的指数是 y[0][0]
到 y[0][1374]
因为
T
y
=
1375
T_y = 1375
Ty=1375。所以,如果 “activate” 在 1370 步结束, 你会得到 y[0][1371] = y[0][1372] = y[0][1373] = y[0][1374] = 1
练习: 实现 insert_ones()
。 你可以使用for循环。 (如果您是python切片操作的专家,也可以使用切片来对此进行矢量化。) 如果某个片段结束于 segment_end_ms
(使用10000步离散化), 将其转换为输出的索引
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):
"""
更新标签向量y。段结尾的后面50个输出的标签应设为 1。
严格来说,我们的意思是 segment_end_y 的标签应该是 0,而随后的50个标签应该是1。
参数:
y -- numpy数组的维度 (1, Ty), 训练样例的标签
segment_end_ms -- 以ms为单位的段的结束时间
返回:
y -- 更新标签
"""
# 背景持续时间(以频谱图时间步长表示)
segment_end_y = int(segment_end_ms * Ty / 10000.0)
# 将1添加到背景标签(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
去创建一个新的训练样例。
练习: 实现 create_training_example()
。您需要执行以下步骤:
- 将标签向量 y y y 初始化为0的numpy数组,维度 ( 1 , T y ) (1, T_y) (1,Ty)。
- 将现有段的集合初始化为空列表。
- 随机选择0到4个 “activate” 音频剪辑,并将它们插入10秒剪辑。还要在标签向量 y y y 中的正确位置插入标签。
- 随机选择0到2个负例音频剪辑,并将它们插入10秒剪辑中。
# GRADED FUNCTION: create_training_example
def create_training_example(background, activates, negatives):
"""
创建具有给定背景,正例和负例的训练示例。
参数:
background -- 10秒背景录音
activates -- "activate" 一词的音频片段列表
negatives -- 不是 "activate" 一词的音频片段列表
返回:
x -- 训练样例的频谱图
y -- 频谱图的每个时间步的标签
"""
# 设置随机种子
np.random.seed(18)
# 让背景更安静
background = background - 20
# 第一步:初始化 y (标签向量)为0 (≈ 1 line)
y = np.zeros((1, Ty))
# 第二步:将段时间初始化为空列表 (≈ 1 line)
previous_segments = []
# 从整个 "activate" 录音列表中选择0-4随机 "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]
# 第三步: 循环随机选择 "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" 中插入标签
y = insert_ones(y, segment_end_ms=segment_end)
# 从整个负例录音列表中随机选择0-2个负例录音
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]
# 第四步: 循环随机选择负例片段并插入背景中
for random_negative in random_negatives:
# 插入音频剪辑到背景
background, _ = insert_audio_clip(background, random_negative, previous_segments)
# 标准化音频剪辑的音量
background = match_target_amplitude(background, -20.0)
# 导出新的训练样例
file_handle = background.export("train" + ".wav", format="wav")
print("文件 (train.wav) 已保存在您的目录中。")
# 获取并绘制新录音的频谱图(正例和负例叠加的背景)
x = graph_spectrogram("train.wav")
return x, y
x, y = create_training_example(backgrounds[0], activates, negatives)
执行结果:
文件 (train.wav) 已保存在您的目录中。
现在,您可以聆听您创建的训练示例,并将其与上面生成的频谱图进行比较。
IPython.display.Audio("train.wav")
执行结果:
CSDN不支持播放音频
最后,您可以绘制生成的训练示例的关联标签。
plt.plot(y[0])
执行结果:
2.1.4 - 完整的训练集
您现在已经实现了生成单个训练示例所需的代码。我们使用此过程生成一个大型训练集。为了节省时间,我们已经生成了一组训练样例。
# 加载预处理的训练样例
X = np.load("./XY_train/X.npy")
Y = np.load("./XY_train/Y.npy")
2.1.5 - 开发集
为了测试我们的模型,我们记录了一个包含25个示例的开发集。虽然我们的训练数据是合成的,但我们希望使用与实际输入相同的分布来创建开发集。因此,我们录制了25个10秒钟的人们说 “activate” 和其他随机单词的音频剪辑,并用手标记。这遵循课程3中描述的原则,即我们创建的开发集应该尽可能与测试集分布相似;这就是我们的开发集使用真实音频而非合成音频的原因。
# 加载预处理开发集示例
X_dev = np.load("./XY_dev/X_dev.npy")
Y_dev = np.load("./XY_dev/Y_dev.npy")
2.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
2.2.1 - 建立模型
这是我们将使用的架构。
花一些时间来查看模型,看看它是否有意义。
该模型的一个关键步骤是1D卷积步骤(靠近图2.3的底部)。它输入5511步频谱图,并输出1375步,然后由多个层进一步处理,以获得最终的 T y = 1375 T_y = 1375 Ty=1375 步输出。该层的作用类似于您在课程4中看到的2D卷积,提取低级特征,然后可能生成较小维度的输出。
在计算上,1-D 卷积层也有助于加速模型,因为现在GRU只需要处理1375个时间步而不是5511个时间步。两个GRU层从左到右读取输入序列,然后最终使用全连接+ sigmoid层来预测 y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩。因为 y y y 是二进制值(0或1),我们在最后一层使用sigmoid输出来估计输出为1的可能性,对应于刚刚说过 “activate” 的用户。
请注意,我们使用单向RNN而不是双向RNN。这对于触发字检测非常重要,因为我们希望能够在说出触发字后立即检测到触发字。如果我们使用双向RNN,我们必须等待整个10秒的音频被记录,然后才能判断音频剪辑的第一秒是否有 “activate” 。
实现模型可以分四步完成:
第一步: 卷积层。使用 Conv1D()
实现,用 196 filters,每个 filter大小为 15(kernel_size=15
), 步长为 4. [参考文档]
第二步: 第一个GRU层。要生成GRU图层,使用:
X = GRU(units = 128, return_sequences = True)(X)
设置 return_sequences=True
确保所有GRU的隐藏状态都被送到下一层。 记住使用 Dropout 和 BatchNorm 图层。
第三步: 第二个GRU层。这与之前的GRU层类似 (记得要用 return_sequences=True
), 但有另外的 dropout 层。
第四步: 创建时间分布的全连接层,如下所示:
X = TimeDistributed(Dense(1, activation = "sigmoid"))(X)
这里创建一个激活为sigmoid的全连接层,全连接层的每个时间步的参数是相同的。[参考文档.]
练习: 实现 model()
, 该架构如图2.3所示。
# GRADED FUNCTION: model
def model(input_shape):
"""
用 Keras 创建模型的图 Function creating the model's graph in Keras.
参数:
input_shape -- 模型输入数据的维度(使用Keras约定)
返回:
model -- Keras 模型实例
"""
X_input = Input(shape = input_shape)
# 第一步:卷积层 (≈4 lines)
X = Conv1D(196, 15, strides=4)(X_input) # CONV1D
X = BatchNormalization()(X) # Batch normalization 批量标准化
X = Activation('relu')(X) # ReLu activation ReLu 激活
X = Dropout(0.8)(X) # dropout (use 0.8)
# 第二步:第一个 GRU 层 (≈4 lines)
X = GRU(units = 128, return_sequences=True)(X) # GRU (使用128个单元并返回序列)
X = Dropout(0.8)(X) # dropout (use 0.8)
X = BatchNormalization()(X) # Batch normalization 批量标准化
# 第三步: 第二个 GRU 层 (≈4 lines)
X = GRU(units = 128, return_sequences=True)(X) # GRU (使用128个单元并返回序列)
X = Dropout(0.8)(X) # dropout (use 0.8)
X = BatchNormalization()(X) # Batch normalization 批量标准化
X = Dropout(0.8)(X) # dropout (use 0.8)
# 第四步: 时间分布全连接层 (≈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))
让我们打印模型摘要以跟踪维度。
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
_________________________________________________________________
网络的输出维度是 (None, 1375, 1) 而输入是 (None, 5511, 101)。Conv1D 将步数从 5511 减少到了 1375。
2.2.2 - 训练模型
触发字检测训练需要花费很长时间。为了节省时间,我们已经使用您在上面构建的架构在GPU上训练了大约3个小时的模型,以及大约4000个示例的大型训练集。
让我们加载模型。
model = load_model('./models/tr_model.h5')
您可以使用Adam优化器和二进制交叉熵损失进一步训练模型,如下所示。这将很快运行,因为我们仅训练迭代一次,并且是一个包含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 [==============================] - 30s 1s/step - loss: 0.0893 - acc: 0.9717
<keras.callbacks.History at 0x19249d8f358>
2.2.3 - 测试模型
最后,让我们看看您的模型在开发集上的表现。
loss, acc = model.evaluate(X_dev, Y_dev)
print("Dev set accuracy = ", acc)
执行结果:
25/25 [==============================] - 3s 122ms/step
Dev set accuracy = 0.9296872615814209
这看起来很不错!但是,准确性并不是此任务的重要指标,因为标签严重偏向0,因此,神经网络只输出0也将获得略高于90%的准确度。我们可以定义更多有用的指标,例如:F1得分,精确度/召回。但是,这里我们先不考虑这些,而只是看看模型是如何做的。
2.2.4 - 做出预测
现在您已经为触发词检测构建了一个工作模型,让我们用它来进行预测。此代码段通过网络运行音频(保存在wav文件中)。
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
一旦您估计了在每个输出步骤检测到 “activate” 一词的概率,您就可以在概率超过某个阈值时触发“鸣响”声音。此外, y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩ 可能在接近 “activate” 之后连续的多个值接近 1,但我们只想响一次。因此,我们将每75个输出步最多插入一次铃声。这将有助于防止我们为单个 “activate” 实例插入两个铃声。(这与计算机视觉中的非最大抑制类似。)
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]
# 第一步:将连续输出步初始化为0
consecutive_timesteps = 0
# 第二步: 循环y中的输出步
for i in range(Ty):
# 第三步: 增加连续输出步
consecutive_timesteps += 1
# 第四步: 如果预测高于阈值并且已经过了超过75个连续输出步
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)
# 第六步: 将连续输出步重置为0
consecutive_timesteps = 0
audio_clip.export("chime_output.wav", format='wav')
2.3 - 测试开发样例
让我们探讨一下我们的模型如何在开发集中的两个没有见过的音频剪辑上执行。
让我们先听两个开发设置的剪辑。
IPython.display.Audio("./raw_data/dev/1.wav")
测试结果:
CSDN不支持播放音频
IPython.display.Audio("./raw_data/dev/2.wav")
测试结果:
CSDN不支持播放音频
现在让我们在这些音频片段上运行模型,看看它是否在 “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不支持播放音频
你已经完成了这项任务!
下面是你应该记住的:
- 数据合成是为语音问题创建大型训练集的有效方法,特别是触发单词检测。
- 使用频谱图和可选的 1D 卷积层是将音频数据传递到 RNN,GRU 或 LSTM 之前的常见预处理步骤。
- 端到端深度学习方法可用于构建非常有效的触发字检测系统。
祝贺您 完成最后的任务!
感谢您坚持到最后,并为您学习深度学习所付出的辛勤劳动。
我们希望您喜欢这门课程!
2.4 - 试试你自己的样例! (选学)
【博主注】:以下操作供Coursera 正版付费课程购买者学习,网易云版课程不能进行,纵然两者视频课程一模一样,但是Coursera 支持以下操作。
录制你说 “activate” 和其他随机单词的10秒音频片段,并将其命名为“myaudio.wav”上传到 Coursera 中心。请务必将音频上传为wav文件。如果您的音频以不同的格式(例如mp3)录制,则可以使用免费软件将其转换为wav格式。如果您的录音时间不是10秒,则下面的代码将根据需要修剪或填充它以使其达到10秒。
# 将音频预处理为正确的格式
def preprocess_audio(filename):
# 将音频片段修剪或填充到 10000ms
padding = AudioSegment.silent(duration=10000)
segment = AudioSegment.from_wav(filename)[:10000]
segment = padding.overlay(segment)
# 将帧速率设置为 44100
segment = segment.set_frame_rate(44100)
# 导出为wav
segment.export(filename, format='wav')
将音频文件上传到Coursera后,将文件路径放在下面的变量中。
your_filename = "audio_examples/my_audio.wav"
preprocess_audio(your_filename)
IPython.display.Audio(your_filename) # 听你上传的音频
CSDN不支持播放音频
最后,使用模型预测何时在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")
CSDN不支持播放音频
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)