• 全序列卷积神经网络( 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
                    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('
    '))
        # 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

  • 相关阅读:
    异步加载text资源,加载一次、执行一次、链式回调
    贝叶斯判断类别
    通过贝叶斯概率机器学习
    什么是 Dropout
    什么是CNN--Convolutional Neural Networks
    神经网络介绍
    神经网络之线性单元
    机器学习十大常用算法
    对比学习用 Keras 搭建 CNN RNN 等常用神经网络
    机器学习,人工智能相关最新图书推荐
  • 原文地址:https://www.cnblogs.com/Uriel-w/p/14951600.html
Copyright © 2020-2023  润新知