语音识别|基于CNN+DFSMN(简化版:标量+无步长因子)的声学模型实现及代码开源(keras)

由于我主要研究问答系统,因此本博客仅更新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,其代码如下,其中normconv2d分别为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)

   
   
   
   
   
   
   
   
  • 1
  • 2
  • 3

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/

                                

你可能感兴趣的:(日常)