由于我主要研究问答系统,因此本博客仅更新NLP及问答相关内容,最近创了一个群,如果大家感兴趣可加q群号:376564367
github:https://github.com/makeplanetoheaven/NlpModel/tree/master/SpeechRecognition/AcousticModel/dfsmn_v1
背景知识
FSMN和DFSMN系列模型的结构及其实现原理可参考如下两篇博客:
1.FSMN结构快速解读
2.DFSMN结构快速解读
基于CNN+DFSMN的声学模型实现
本模型是在传统CNN模型的基础上,引入2018年阿里提出的声学模型DFSMN,论文地址:https://arxiv.org/pdf/1803.05030.pdf。
该声学模型使用的输入是具有16KHZ采样率,单声道音频数据经过fbank特征提取以后的特征数据。模型整体的语音识别框架使用的是Github:https://github.com/audier/DeepSpeechRecognition。
在该模块中,主要包含了以下4个部分内容:
- 模型实现代码
- 1.卷积层
- 2.DFSMN层
- 3.softmax层
- 4.梯度更新部分
- 模型调用方式
- 已训练模型库
模型实现代码
模型的实现代码位于目录:/NlpModel/SpeechRecognition/AcousticModel/dfsmn/Model/cnn_dfsmn_ctc.py
,其实现顺序从Am
类开始。
首先,通过调用_model_init
对模型整个数据流图进行构建。在构建数据流图的过程中,需要依次去定义模型以下几个部分:
1.卷积层
在卷积层中,根据输入的音频数据(一个4维矩阵[batch, data_len, feature_len, 1]),对其进行卷积操作,整个卷积共分为4层,其中最后一层不使用pooling操作:
# CNN-layers
self.h1 = cnn_cell(32, self.inputs)
if self.is_training:
self.h1 = Dropout(self.dropout_r)(self.h1)
self.h2 = cnn_cell(64, self.h1)
if self.is_training:
self.h2 = Dropout(self.dropout_r)(self.h2)
self.h3 = cnn_cell(128, self.h2)
if self.is_training:
self.h3 = Dropout(self.dropout_r)(self.h3)
self.h4 = cnn_cell(128, self.h3, pool=False)
if self.is_training:
self.h4 = Dropout(self.dropout_r)(self.h4)
# (200 / 8) * 128 = 3200
self.h5 = Reshape((-1, 3200))(self.h4)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
在卷积层的最后,通过Reshape操作,将提取出的特征转换成一个三维矩阵[batch, data_len, 3200],以作为DFSMN层的输入。
其中,每个卷积层的实现函数是_cnn_cell
,其代码如下,其中norm
和conv2d
分别为BatchNorm和卷积操作的两个函数:
def cnn_cell (size, x, pool=True):
x = norm(conv2d(size)(x))
x = norm(conv2d(size)(x))
if pool:
x = maxpool(x)
return x
def norm (x):
return BatchNormalization(axis=-1)(x)
def conv2d (size):
return Conv2D(size, (3, 3), use_bias=True, activation=‘relu’, padding=‘same’, kernel_initializer=‘he_normal’)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
2.DFSMN层
DFSMN层的实现在代码如下,在该模型中,总共使用了6层dfsmn,其中每层dfsmn的hidden_num设置为1024,前后步长设置为40,并将dfsmn的输出经过layNorm以后,再带入激活函数swish中,最终得到下一层的输出,在每层dfsmn中,DropOut的丢失率设置为0.5。
# FSMN-layers
with tf.variable_scope('cfsmn'):
cfsmn = cfsmn_cell('cfsmn-cell', 512, 512, 1024, 40, 40)
cfsmn_o, cfsmn_p_hatt = Lambda(cfsmn)(self.h6)
cfsmn_o = LayerNormalization()(cfsmn_o)
cfsmn_o = Lambda(tf.nn.swish)(cfsmn_o)
if self.is_training:
cfsmn_o = Dropout(self.dropout_r * 2)(cfsmn_o)
with tf.variable_scope(‘dfsmn1’):
dfsmn1 = dfsmn_cell(‘dfsmn1-cell’, 512, 512, 1024, 40, 40)
dfsmn1_o, dfsmn1_p_hatt = Lambda(dfsmn1)([cfsmn_o, cfsmn_p_hatt])
dfsmn1_o = LayerNormalization()(dfsmn1_o)
dfsmn1_o = Lambda(tf.nn.swish)(dfsmn1_o)
if self.is_training:
dfsmn1_o = Dropout(self.dropout_r * 2)(dfsmn1_o)
with tf.variable_scope(‘dfsmn2’):
dfsmn2 = dfsmn_cell(‘dfsmn2-cell’, 512, 512, 1024, 40, 40)
dfsmn2_o, dfsmn2_p_hatt = Lambda(dfsmn2)([dfsmn1_o, dfsmn1_p_hatt])
dfsmn2_o = LayerNormalization()(dfsmn2_o)
dfsmn2_o = Lambda(tf.nn.swish)(dfsmn2_o)
if self.is_training:
dfsmn2_o = Dropout(self.dropout_r * 2)(dfsmn2_o)
with tf.variable_scope(‘dfsmn3’):
dfsmn3 = dfsmn_cell(‘dfsmn3-cell’, 512, 512, 1024, 40, 40)
dfsmn3_o, dfsmn3_p_hatt = Lambda(dfsmn3)([dfsmn2_o, dfsmn2_p_hatt])
dfsmn3_o = LayerNormalization()(dfsmn3_o)
dfsmn3_o = Lambda(tf.nn.swish)(dfsmn3_o)
if self.is_training:
dfsmn3_o = Dropout(self.dropout_r * 2)(dfsmn3_o)
with tf.variable_scope(‘dfsmn4’):
dfsmn4 = dfsmn_cell(‘dfsmn4-cell’, 512, 512, 1024, 40, 40)
dfsmn4_o, dfsmn4_p_hatt = Lambda(dfsmn4)([dfsmn3_o, dfsmn3_p_hatt])
dfsmn4_o = LayerNormalization()(dfsmn4_o)
dfsmn4_o = Lambda(tf.nn.swish)(dfsmn4_o)
if self.is_training:
dfsmn4_o = Dropout(self.dropout_r * 2)(dfsmn4_o)
with tf.variable_scope(‘dfsmn5’):
dfsmn5 = dfsmn_cell(‘dfsmn5-cell’, 512, 512, 1024, 40, 40)
dfsmn5_o, dfsmn5_p_hatt = Lambda(dfsmn5)([dfsmn4_o, dfsmn4_p_hatt])
dfsmn5_o = LayerNormalization()(dfsmn5_o)
dfsmn5_o = Lambda(tf.nn.swish)(dfsmn5_o)
if self.is_training:
dfsmn5_o = Dropout(self.dropout_r * 2)(dfsmn5_o)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
其中每层dfsmn的实现代码用一个类dfsmn_cell
进行封装,其模型所需参数和实现流程如下所示:
def _build_graph (self):
self.lay_norm = tf_LayerNormalization('dfsmn_laynor', self._hidden_size)
self._V = tf.get_variable("dfsmn_V", [self._input_size, self._hidden_size],
initializer=tf.contrib.layers.xavier_initializer())
self._bias_V = tf.get_variable("dfsmn_V_bias", [self._hidden_size],
initializer=tf.contrib.layers.xavier_initializer())
self._U = tf.get_variable("dfsmn_U", [self._hidden_size, self._output_size],
initializer=tf.contrib.layers.xavier_initializer())
self._bias_U = tf.get_variable("dfsmn_U_bias", [self._output_size],
initializer=tf.contrib.layers.xavier_initializer())
self._memory_weights = tf.get_variable("memory_weights", [self._memory_size],
initializer=tf.contrib.layers.xavier_initializer())
def call (self, args):
inputs = args[0]
last_p_hatt = args[1]
def for_cond (step, memory_matrix):
return step < num_steps
def for_body (step, memory_matrix):
left_pad_num = tf.maximum(0, step + 1 - self._l_memory_size)
right_pad_num = tf.maximum(0, num_steps - step - 1 - self._r_memory_size)
l_mem = self._memory_weights[tf.minimum(step, self._l_memory_size - 1)::-1]
r_mem = self._memory_weights[
self._l_memory_size:self._l_memory_size + tf.minimum(num_steps - step - 1, self._r_memory_size)]
mem = tf.concat([l_mem, r_mem], 0)
d_batch = tf.pad(mem, [[left_pad_num, right_pad_num]])
return step + 1, memory_matrix.write(step, d_batch)
# memory build
num_steps = tf.shape(inputs)[1]
memory_matrix = tf.TensorArray(dtype=tf.float32, size=num_steps)
_, memory_matrix = tf.while_loop(for_cond, for_body, [0, memory_matrix])
memory_matrix = memory_matrix.stack()
p = tf.tensordot(inputs, self._V, axes=1) + self._bias_V
# memory block
p_hatt = tf.transpose(tf.tensordot(tf.transpose(p, [0, 2, 1]), tf.transpose(memory_matrix), axes=1), [0, 2, 1])
p_hatt = self.lay_norm(last_p_hatt + p + p_hatt)
# liner transform
h = tf.tensordot(p_hatt, self._U, axes=1) + self._bias_U
return [h, p_hatt]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
另外,除了dfsmn以外,文件中还提供sfsmn、vfsmn、cfsmn代码实现。
3.softmax层
softmax层将dfsmn的输出带入到一个全连接层进行线性变换后,再经过softmax层,得到每个单词的出现概率,其代码如下:
# softmax-layers
self.h8 = dense(384, activation='relu')(dfsmn5_o)
self.outputs = dense(self.vocab_size, activation='softmax')(self.h8)
4.梯度更新部分
cnn_dfsmn模型在_model_init
的数据流图构建完毕以后,首先通过调用_ctc_init
函数,以CTC作为模型的损失函数,然后再调用opt_init
函数选择相应优化器进行模型训练。
def _ctc_init (self):
self.labels = Input(name='the_labels', shape=[None], dtype='float32')
self.input_length = Input(name='input_length', shape=[1], dtype='int64')
self.label_length = Input(name='label_length', shape=[1], dtype='int64')
self.loss_out = Lambda(ctc_lambda, output_shape=(1,), name='ctc')(
[self.labels, self.outputs, self.input_length, self.label_length])
self.ctc_model = Model(inputs=[self.labels, self.inputs, self.input_length, self.label_length],
outputs=self.loss_out)
def opt_init (self):
opt = Adam(lr=self.lr, beta_1=0.9, beta_2=0.999, decay=0.0004, epsilon=10e-8)
# opt = SGD(lr=self.lr, momentum=0.0, decay=0.00004, nesterov=False)
if self.gpu_nums > 1:
self.ctc_model = multi_gpu_model(self.ctc_model, gpus=self.gpu_nums)
self.ctc_model.compile(loss={‘ctc’: lambda y_true, output: output}, optimizer=opt)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
模型调用方式
模型的调用代码位于目录:/NlpModel/SpeechRecognition/AcousticModel/dfsmn/Debug.py
,其调用方式主要分为以下两种。
1.模型训练
cnn_dfsmn模型的训练通过调用文件中的函数dfsmn_model_train
实现,该函数以一个参数作为输入:
(1)train_data_path,该参数指定训练数据所在路径;
2.模型在线解码
cnn_dfsmn模型的在线解码通过调用文件中的函数dfsmn_model_decode
实现,该函数以一个参数作为输入:
(1)wav_file_path,该参数指定了待解码音频文件路径;
(2)label_data_path,该参数指定标注数据所在路径;
已训练模型库
本模型已训练完的模型文件位于目录:/NlpModel/SpeechRecognition/AcousticModel/dfsmn/ModelMemory/
。