全序列卷积神经网络( deep fully convolutional neural network, DFCNN)实践记录
了解语音识别中特征提取过程
1 #读取音频文件 2 import scipy.io.wavfile as wav 3 # Scipy高级科学计算库,包含各种运算 4 #io输入输出包,不同格式文本的输入输出,.wavfile操作wav文件 5 import matplotlib.pyplot as plt 6 #matplotlib 用于画图的库 7 # .pyplot()绘图接收一个list,如果只有一个list默认其为Y轴,\ 8 # X轴数据为其索引值,从0开始 9 10 filepath = 'test.wav' 11 #单声道是一维的,立体音是二维 12 fs, wavsignal = wav.read(filepath) 13 print('采样率:%d'%fs) 14 print(wavsignal) 15 plt.plot(wavsignal) 16 plt.show() 17 #图像的显示 18 19 #构建汉明窗 20 import numpy as np 21 #numpy完成基础数值计算 22 import matplotlib.pyplot as plt 23 x=np.linspace(0, 1102 - 1, 1102, dtype = np.int64) 24 #创建数组linspace(起始值,结束值,元素数量); 25 # arange(开始值,结束值,步长).reshape(行数,列数) 26 #print(x) 27 w = 0.54 - 0.46 * np.cos(2 * np.pi * (x) / (1102 - 1)) 28 29 plt.plot(w) 30 plt.show() 31 32 #对数据分帧 33 #帧长:25ms; 帧移:10ms 34 #采样点(s) = fs 35 #采样点(ms)= fs / 1000 36 #采样点(帧)= fs / 1000 * 帧长 37 time_window = 25 #帧长 38 window_length = fs / 1000 * 25 #采样点 39 print(window_length) 40 #分帧加窗 41 #分帧 42 p_begin = 0 43 p_end = int(p_begin + window_length) 44 frame = wavsignal[p_begin : p_end] 45 plt.plot(frame) 46 plt.show() 47 #加窗 48 49 frame = frame * w 50 #np.dot(a, b)数组矩阵相乘 51 plt.plot(frame) 52 plt.show() 53 54 #傅里叶变换 55 from scipy.fftpack import fft 56 57 # 进行快速傅里叶变换 58 frame_fft = np.abs(fft(frame))[:551] #取1102的一半,因为对称性 59 #np.abs()返回绝对值 60 plt.plot(frame_fft) 61 plt.show() 62 63 # 取对数,求db 64 frame_log = np.log(frame_fft) 65 #np.log() e为底的对数, np.10log() 以10为底的对数 66 plt.plot(frame_log) 67 plt.show()
实现语音识别汉字的全过程:
thchs30的下载:http://www.openslr.org/18/
import numpy as np import scipy.io.wavfile as wav from scipy.fftpack import fft import os # 一.获取信号的时频图 def compute_fbank(file): x = np.linspace(0, 400 - 1, 400, dtype=np.int64) w = 0.54 - 0.46 * np.cos(2 * np.pi * (x) / (400 - 1)) # 汉明窗 fs, wavsignal = wav.read(file) ##单声道是一维的,立体音是二维 # wav波形 加时间窗以及时移10ms time_window = 25 # 单位ms window_length = fs / 1000 * time_window # 计算窗长度的公式,目前全部为400固定值 wav_arr = np.array(wavsignal) wav_length = len(wavsignal) range0_end = int(len(wavsignal) / fs * 1000 - time_window) // 10 # 计算循环终止的位置,也就是最终生成的窗数 print(range0_end) data_input = np.zeros((range0_end, 200), dtype=np.float64) # 用于存放最终的频率特征数据 # np.zeros((a,b), dtype = np.float) 输出一个a行b列的float类型的矩阵 data_line = np.zeros((1, 400), dtype=np.float64) for i in range(0, range0_end): p_start = i * 160 p_end = p_start + 400 data_line = wav_arr[p_start:p_end] data_line = data_line * w # 加窗 data_line = np.abs(fft(data_line)) data_input[i] = data_line[0:200] # 设置为400除以2的值(即200)是取一半数据,因为是对称的 data_input = np.log(data_input + 1) # data_input = data_input[::] return data_input import matplotlib.pyplot as plt filepath = 'test1.wav' a = compute_fbank(filepath) plt.imshow(a.T, origin='lower') # imshow(X, camp, origin) X可以是数组和图片,camp的值是控制颜色, # origin坐标轴样式有'lower'坐标原点在左上角和'upper'坐标原点在左下角两种 plt.show() # 二.数据处理 # 1.获取训练音频文件及标注文件列表 def source_get(source_file): train_file = source_file + '\data' label_lst = [] wav_lst = [] for root, dirs, files in os.walk(train_file): # os.walk(file)是文件、目录遍历器, # root 所指的是当前正在遍历的这个文件夹的本身的地址 # dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录) # files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录) for file in files: if file.endswith('.wav') or file.endswith('.WAV'): # file.endswith('.wav')判断文件是否以.wav结尾,返回值为布尔 wav_file = os.sep.join([root, file]) # os.sep为了解决不同平台上文件路径分隔符差异问题,os.sep.join([a,b])将a和b以文件分隔符分开,a\b label_file = wav_file + '.trn' wav_lst.append(wav_file) label_lst.append(label_file) return label_lst, wav_lst # 读取的每一个sample的时间轴长都不一样,所以需要对时间轴进行处理,选择batch内最长的那个时间为基准,进行padding。 source_file = 'data_thchs30' label_lst, wav_lst = source_get(source_file) print(label_lst[:10]) print(wav_lst[:10]) # 确认相同id对应的音频文件和标签文件相同 for i in range(10000): wavname = (wav_lst[i].split('/')[-1]).split('.')[0] # a.split('/')a中的元素见到/符号就分隔开,返回一个list labelname = (label_lst[i].split('/')[-1]).split('.')[0] if wavname != labelname: print('error') # 2.label数据处理 # 读取音频文件对应的拼音label def read_label(label_file): # 读取文件内容 with open(label_file, 'r', encoding='utf8') as f: data = f.readlines() return data[1] print(read_label(label_lst[0])) # 输出第一个文件内容 def gen_label_data(label_lst): label_data = [] for label_file in label_lst: # 迭代文件 pny = read_label(label_file) # 读取一个文件内容 label_data.append(pny.strip('\n')) # pny.strip(‘a')函数可删除pny字符串两端的a字符并返回新的字符串 return label_data # 全部文件内容 label_data = gen_label_data(label_lst) print(len(label_data)) # 为label建立拼音到id的映射,即词典 def mk_vocab(label_data): vocab = [] for line in label_data: line = line.split(' ') # 将label_data每个元素以空格划分为新list for pny in line: if pny not in vocab: vocab.append(pny) vocab.append('_') # 打印字典 return vocab vocab = mk_vocab(label_data) print(vocab[:10]) print(len(vocab)) # 读取到的label映射到对应的id def word2id(line, vocab): # b.index(a)返回a在列表b中的索引 return [vocab.index(pny) for pny in line.split(' ')] label_id = word2id(label_data[0], vocab) print(label_data[0]) print(label_id) # 3.音频文件处理 fbank = compute_fbank(wav_lst[0]) # compute_fbank时频转化的函数在前面已经定义好了 # 由于声学模型网络结构原因(3个maxpooling层),我们的音频数据的每个维度需要能够被8整除。(不理解) fbank = fbank[:fbank.shape[0] // 8 * 8, :] # 第一个音频文件的时频图 print(fbank.shape) plt.imshow(fbank.T, origin='lower') plt.show() # 4.数据生成器 from random import shuffle # shuffle(list)随机排列list中的元素,无返回值 shuffle_list = [i for i in range(10000)] # 打乱生成的数列 shuffle(shuffle_list) # generator(生成器):带有yield的函数。返回一个迭代对象(iteraable),通过next()方法得到一个迭代值 def get_batch(batch_size, shuffle_list, wav_lst, label_data, vocab): # batch_size的信号时频图和标签数据,存放到两个list中去 for i in range(10000 // batch_size): wav_data_lst = [] label_data_lst = [] # 开始和结尾间隔batch_size位 begin = i * batch_size end = begin + batch_size sub_list = shuffle_list[begin:end] # 切片每次取四个元素 for index in sub_list: # 获得随机的音频文件的时频图 fbank = compute_fbank(wav_lst[index]) fbank = fbank[:fbank.shape[0] // 8 * 8, :] # 获得字典标签(索引) label = word2id(label_data[index], vocab) wav_data_lst.append(fbank) label_data_lst.append(label) yield wav_data_lst, label_data_lst # 每次返回batch_size个时频图和四个标注文件数据的索引 batch = get_batch(4, shuffle_list, wav_lst, label_data, vocab) # 每次使用next()就执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。 wav_data_lst, label_data_lst = next(batch) for wav_data in wav_data_lst: # 迭代时频图数组 print(wav_data.shape) # plt.imshow(wav_data.T, origin='lower') # plt.show() for label_data in label_data_lst: # 迭代四个文件数据的索引 print(label_data) # 获得四个时频图的大小 lens = [len(wav) for wav in wav_data_lst] print(max(lens)) print(lens) # padding # 然而,每一个batch_size内的数据有一个要求,就是需要构成成一个tensorflow块,这就要求每个样本数据形式是一样的。 # 除此之外,ctc需要获得的信息还有输入序列的长度。 # 这里输入序列经过卷积网络后,长度缩短了8倍,因此我们训练实际输入的数据为wav_len//8(网络结构导致)。 def wav_padding(wav_data_lst): # 获得四个音频文件时频图的长度 wav_lens = [len(data) for data in wav_data_lst] # 取出最大一个 wav_max_len = max(wav_lens) # 将长度缩短8倍 wav_lens = np.array([leng // 8 for leng in wav_lens]) # np.zreos(a, b, c, d):建立一个a行b列的矩阵,其中数组的每一个元素都是c行d列的零矩阵 # 列为wav_max_len可保证全部数据都能存下且每个时频文件数据存储格式相同 new_wav_data_lst = np.zeros((len(wav_data_lst), wav_max_len, 200, 1)) # 为什么要这样做? for i in range(len(wav_data_lst)): # d = list[a, :b, :, c] 指d等于矩阵list的第a行的0~b列元素矩阵中每行的第c列元素 # 将第i个时频图数据放入第i行的0~wav_data_lst[i].shape[0]列 new_wav_data_lst[i, :wav_data_lst[i].shape[0], :, 0] = wav_data_lst[i] return new_wav_data_lst, wav_lens pad_wav_data_lst, wav_lens = wav_padding(wav_data_lst) print(pad_wav_data_lst.shape) print(wav_lens) # 对label进行padding和长度获取,不同的是数据维度不同,且label的长度就是输入给ctc的长度,不需要额外处理 def label_padding(label_data_lst): # 获得四个标签文件索引的长度和最长的一个 label_lens = np.array([len(label) for label in label_data_lst]) max_label_len = max(label_lens) # 建立一个4行max_label_len列的零矩阵 # 列为max_label_len表示能将全部标签都放入且对其(存储格式相同) new_label_data_lst = np.zeros((len(label_data_lst), max_label_len)) for i in range(len(label_data_lst)): # 将第i个标签文件数据的索引赋值给新数组的第i行len(label_data_lst[i])]列 new_label_data_lst[i][:len(label_data_lst[i])] = label_data_lst[i] return new_label_data_lst, label_lens pad_label_data_lst, label_lens = label_padding(label_data_lst) print(pad_label_data_lst.shape) print(label_lens) # 用于训练格式的数据生成器 def data_generator(batch_size, shuffle_list, wav_lst, label_data, vocab): for i in range(len(wav_lst) // batch_size): wav_data_lst = [] label_data_lst = [] begin = i * batch_size end = begin + batch_size # 将打乱的数列每次按顺序取四位 sub_list = shuffle_list[begin:end] for index in sub_list: # 提取一个时频图数据 fbank = compute_fbank(wav_lst[index]) # 让提取出来的数据存入一个可以整除8的数组且保证全部数据都能存入 pad_fbank = np.zeros((fbank.shape[0] // 8 * 8 + 8, fbank.shape[1])) pad_fbank[:fbank.shape[0], :] = fbank # 从字典中获取标签文件的索引 label = word2id(label_data[index], vocab) wav_data_lst.append(pad_fbank) label_data_lst.append(label) # 取出满足CTC算法的时频图数据和时频图数据文件长度能整除8的长度 pad_wav_data, input_length = wav_padding(wav_data_lst) # 取出标签文件的索引和索引的长度 pad_label_data, label_length = label_padding(label_data_lst) inputs = {'the_inputs': pad_wav_data, 'the_labels': pad_label_data, 'input_length': input_length, 'label_length': label_length, } # ctc指向全为0的一个一维数组 outputs = {'ctc': np.zeros(pad_wav_data.shape[0], )} yield inputs, outputs import keras from keras.layers import Input, Conv2D, BatchNormalization, MaxPooling2D from keras.layers import Reshape, Dense, Lambda # reshape从新定义shape from keras.optimizers import Adam # optimizers优化器 from keras import backend as K from keras.models import Model # 卷积和池化是为了提取特征,全连接是对特征归类 # ReLU激活函数是对卷积网络引进非线性,单纯的卷积操作是线性的,现实问题基本上是非线性的 from keras.utils import multi_gpu_model def conv2d(size): # 二维卷积Conv2D(filters卷积核个数, kelnel_size卷积核大小,strides卷积步长, # padding补0策略‘valid’边界不处理same保留边界卷积结果,activation激活函数,use_bias是否使用偏置项, # kelnel_initializer权值初始化) return Conv2D(size, (3, 3), use_bias=True, activation='relu', padding='same', kernel_initializer='he_normal') def norm(x): # 对每个batch归一化处理 return BatchNormalization(axis=-1)(x) def maxpool(x): # 最大池化 return MaxPooling2D(pool_size=(2, 2), strides=None, padding="valid")(x) def dense(units, activation="relu"): # 全连接,对上一层的神经元进行全部连接,实现特征的非线性组合 return Dense(units, activation=activation, use_bias=True, kernel_initializer='he_normal') # x.shape=(none, none, none) # output.shape = (1/2, 1/2, 1/2) def cnn_cell(size, x, pool=True): # cnn + cnn + maxpool构成的组合 x = norm(conv2d(size)(x)) x = norm(conv2d(size)(x)) if pool: x = maxpool(x) return x # 添加CTC损失函数,由backend引入 def ctc_lambda(args): labels, y_pred, input_length, label_length = args y_pred = y_pred[:, :, :] return K.ctc_batch_cost(labels, y_pred, input_length, label_length) # 搭建cnn+dnn+ctc的声学模型 class Amodel(): """docstring for Amodel.""" def __init__(self, vocab_size): super(Amodel, self).__init__() self.vocab_size = vocab_size self._model_init() self._ctc_init() self.opt_init() def _model_init(self): self.inputs = Input(name='the_inputs', shape=(None, 200, 1)) self.h1 = cnn_cell(32, self.inputs) self.h2 = cnn_cell(64, self.h1) self.h3 = cnn_cell(128, self.h2) self.h4 = cnn_cell(128, self.h3, pool=False) # 200 / 8 * 128 = 3200 self.h6 = Reshape((-1, 3200))(self.h4) self.h7 = dense(256)(self.h6) self.outputs = dense(self.vocab_size, activation='softmax')(self.h7) self.model = Model(inputs=self.inputs, outputs=self.outputs) 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=0.0008, beta_1=0.9, beta_2=0.999, decay=0.01, epsilon=10e-8) # self.ctc_model=multi_gpu_model(self.ctc_model,gpus=2) self.ctc_model.compile(loss={'ctc': lambda y_true, output: output}, optimizer=opt) am = Amodel(1176) am.ctc_model.summary() total_nums = 100 batch_size = 16 # batch_size太大会出现内存溢出 batch_num = total_nums // batch_size epochs = 50 # Batch大小是在更新模型之前处理的多个样本。Epoch数是通过训练数据集的完整传递次数。批处理的大小必须大于或等于1且小于或等于训练数据集中的样本数。 # 假设您有一个包含200个样本(数据行)的数据集,并且您选择的Batch大小为5和1,000个Epoch。 # 这意味着数据集将分为40个Batch,每个Batch有5个样本。每批五个样品后,模型权重将更新。 # 这也意味着一个epoch将涉及40个Batch或40个模型更新。 # 有1000个Epoch,模型将暴露或传递整个数据集1,000次。在整个培训过程中,总共有40,000Batch。 source_file = 'data_thchs30' label_lst, wav_lst = source_get(source_file) label_data = gen_label_data(label_lst[:100]) vocab = mk_vocab(label_data) vocab_size = len(vocab) print(vocab_size) shuffle_list = [i for i in range(100)] am = Amodel(vocab_size) for k in range(epochs): print('this is the', k + 1, 'th epochs trainning !!!') # shuffle(shuffle_list) batch = data_generator(batch_size, shuffle_list, wav_lst, label_data, vocab) am.ctc_model.fit_generator(batch, steps_per_epoch=batch_num, epochs=1) def decode_ctc(num_result, num2word): result = num_result[:, :, :] in_len = np.zeros((1), dtype=np.int32) in_len[0] = result.shape[1] r = K.ctc_decode(result, in_len, greedy=True, beam_width=10, top_paths=1) r1 = K.get_value(r[0][0]) r1 = r1[0] text = [] for i in r1: text.append(num2word[i]) return r1, text # 测试模型 predict(x, batch_size=None, verbose=0, steps=None) batch = data_generator(1, shuffle_list, wav_lst, label_data, vocab) for i in range(10): # 载入训练好的模型,并进行识别 inputs, outputs = next(batch) x = inputs['the_inputs'] y = inputs['the_labels'][0] result = am.model.predict(x, steps=1) # 将数字结果转化为文本结果 result, text = decode_ctc(result, vocab) print('数字结果: ', result) print('文本结果:', text) print('原文结果:', [vocab[int(i)] for i in y])
学习路径:https://blog.csdn.net/chinatelecom08/article/details/85013535