使用Tensorflow进行语音识别 代码阅读笔记2
- 看一下这个工程中的数据加载方式
数据加载
1 Dataset 类
examples/timit/data/load_dataset_ctc.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-"""Load dataset for the CTC model (TIMIT corpus).In addition, frame stacking and skipping are used.You can use only the single GPU version.
"""from __future__ import absolute_import
from __future__ import division
from __future__ import print_functionfrom os.path import join, isfile
import pickle
import numpy as np# 首先从这里引入了DatasetBase这个类
from utils.dataset.ctc import DatasetBase# Dataset 继承了 DatasetBase这个类,再去看DatasetBase 这个类可以看到继承了Base这个类
class Dataset(DatasetBase):def __init__(self, data_type, label_type, batch_size,max_epoch=None, splice=1,num_stack=1, num_skip=1,shuffle=False, sort_utt=False, sort_stop_epoch=None,progressbar=False):"""A class for loading dataset.Args:data_type (string): train or dev or testlabel_type (string): phone39 or phone48 or phone61 orcharacter or character_capital_dividebatch_size (int): the size of mini-batchmax_epoch (int, optional): the max epoch. None means infinite loop.splice (int, optional): frames to splice. Default is 1 frame.num_stack (int, optional): the number of frames to stacknum_skip (int, optional): the number of frames to skipshuffle (bool, optional): if True, shuffle utterances. This isdisabled when sort_utt is True.sort_utt (bool, optional): if True, sort all utterances by thenumber of frames and utteraces in each mini-batch are shuffled.Otherwise, shuffle utteraces.sort_stop_epoch (int, optional): After sort_stop_epoch, trainingwill revert back to a random orderprogressbar (bool, optional): if True, visualize progressbar"""# 这里先调用了父类的构造函数,这里没有参数super(Dataset, self).__init__()self.is_test = True if data_type == 'test' else Falseself.data_type = data_typeself.label_type = label_typeself.batch_size = batch_sizeself.max_epoch = max_epochself.splice = spliceself.num_stack = num_stackself.num_skip = num_skipself.shuffle = shuffleself.sort_utt = sort_uttself.sort_stop_epoch = sort_stop_epochself.progressbar = progressbarself.num_gpu = 1# paths where datasets exist# 设置dataset路径dataset_root = ['/data/inaguma/timit','/n/sd8/inaguma/corpus/timit/dataset']input_path = join(dataset_root[0], 'inputs', data_type)# NOTE: ex.) save_path: timit_dataset_path/inputs/data_type/***.npylabel_path = join(dataset_root[0], 'labels', data_type, label_type)# NOTE: ex.) save_path:# timit_dataset_path/labels/data_type/label_type/***.npy# Load the frame number dictionary# 加载帧数字典,看后面的代码可以知道字典键名是文件名,键值是帧数# 加载的是 .pickle文件, pickle存储的是结构化文件,如用pickle.dump()存储一个字典,再pickle.load()出来就是字典,而不是文本# 这里的if-else 就是为了判断刚才两个目录哪个下面有这个文件if isfile(join(input_path, 'frame_num.pickle')):with open(join(input_path, 'frame_num.pickle'), 'rb') as f:self.frame_num_dict = pickle.load(f)else:dataset_root.pop(0)input_path = join(dataset_root[0], 'inputs', data_type)label_path = join(dataset_root[0], 'labels', data_type, label_type)with open(join(input_path, 'frame_num.pickle'), 'rb') as f:self.frame_num_dict = pickle.load(f)# Sort paths to input & label# 为输入和标签的路径排序axis = 1 if sort_utt else 0# 刚才加载了frame_num_dict,一个字典。 字典.items()就是字典的值,这一句是常见的字典排序代码# key=lambda x:x[axis] 表示是按键名还是按键值排序,如果是x[0],则按键名,默认是升序# 注意sorted函数返回的是列表,不是字典。返回的列表中每个元素对应的是一个由键名和键值组成的元组frame_num_tuple_sorted = sorted(self.frame_num_dict.items(),key=lambda x: x[axis])input_paths, label_paths = [], []# 遍历排好序的列表,按照这个顺序将输入和标签添加到两个列表中for input_name, frame_num in frame_num_tuple_sorted:# 前面只是设置了输入路径,现在是将具体文件的路径添加到输入和标签的列表中input_paths.append(join(input_path, input_name + '.npy'))label_paths.append(join(label_path, input_name + '.npy'))# 现在input_paths 和 label_paths中保存的都是文件路径# 转化为numpy矩阵# 这里的self.input_paths和 self.label_paths 继承了父类的成员self.input_paths = np.array(input_paths)self.label_paths = np.array(label_paths)# NOTE: Not load dataset yet# **这里还没加载数据,那在哪里加载的?**# set是无序不重复集合,这里取了input_paths的个数,然后生成了一个set。但是好像成员为数字时set中数据是有序的# self.rest应该是继承自父类的成员self.rest = set(range(0, len(self.input_paths), 1))
可以看到这个Dataset中代码的主要作用是从一个文件中读取文件路径,将其保存到一个列表中。这个列表是在父类中进行了处理。
在给模型训练数据时,需要先得到一个list,list中保存了文件名,和相关信息,如这个工程就是保存了帧数。 一般需要对这个list的顺序进行一些处理,如shuffle,即随机打乱。如果输入为语音,也可以按长度排序。
2 Dataset的父类 DatasetBase
utils/dataset/ctc
#! /usr/bin/env python
# -*- coding: utf-8 -*-"""Base class for loading dataset for the CTC model.In this class, all data will be loaded at each step.You can use the multi-GPU version.
"""
# 这里注释说了这个类作用是为CTC模型加载数据,可以用多GPU
from __future__ import absolute_import
from __future__ import division
from __future__ import print_functionfrom os.path import basename
import random
import numpy as npfrom utils.dataset.base import Base
from utils.io.inputs.frame_stacking import stack_frame
from utils.io.inputs.splicing import do_splice# 继承了Base类,Base类在 utils/dataset/base.py 中
class DatasetBase(Base):# 构造函数的参数,这里是可变参数# *args表示任何多个无名参数,它是一个tuple# **kwargs表示关键字参数即有名字的参数,它是一个dict,但是调用这个函数时参数形式为 xx==yydef __init__(self, *args, **kwargs):# 这里调用了父类的构造函数super(DatasetBase, self).__init__(*args, **kwargs) # 类中的特殊函数,如果定义了__getitem__(self,key),那么当时DatasetBase[x]时,就会调用这个函数,x传给这里的index。# 常见的元组,列表等容器就是用了这种方式,它们本身也就是不同的class。def __getitem__(self, index):input_i = np.array(self.input_paths[index])label_i = np.array(self.label_paths[index])return (input_i, label_i)# __net__ () 也是特殊的函数,当使用 for ... in b 语句时可以理解为调用了这个函数,每迭代一次就调用一次# ...是有格式的,是按照这里return的格式def __next__(self, batch_size=None):"""Generate each mini-batch.Args:batch_size (int, optional): the size of mini-batchReturns:A tuple of `(inputs, labels, inputs_seq_len, input_names)`inputs: list of input data of size`[num_gpu, B, T_in, input_size]`labels: list of target labels of size`[num_gpu, B, T_out]`inputs_seq_len: list of length of inputs of size`[num_gpu, B]`input_names: list of file name of input data of size`[num_gpu, B]`is_new_epoch (bool): If true, 1 epoch is finished"""# 函数的作用是生成每个mini_batch# 这里还是没清楚这里输入的数据格式,B指的是数据?# 如果到达了设定的条件,就停止迭代,即for in 执行完毕if self.max_epoch is not None and self.epoch >= self.max_epoch:raise StopIteration# NOTE: max_epoch = None means infinite loopif batch_size is None:batch_size = self.batch_size# resetif self.is_new_epoch:self.is_new_epoch = Falseif not self.is_test:self.padded_value = -1else:self.padded_value = None# TODO(hirofumi): move this# 虽然前面知道了utt的作用。但是这里终于知道utt是什么的缩写的。# 这一部分是对输入按长度进行排序或shuffle,即打乱顺序# 注意这里获得 data_indices 后,self.rest 这个set是剪去了取出的这部分的,避免下次取再取相同的# data_indices 是一个列表,除了最后一个minibatch,都包含了batch_size个数if self.sort_utt:# Sort all uttrances by lengthif len(self.rest) > batch_size:data_indices = sorted(list(self.rest))[:batch_size]self.rest -= set(data_indices)# NOTE: rest is uttrance length orderelse:# Last mini-batchdata_indices = list(self.rest)self.reset()self.is_new_epoch = Trueself.epoch += 1if self.epoch == self.sort_stop_epoch:self.sort_utt = Falseself.shuffle = True# Shuffle data in the mini-batchrandom.shuffle(data_indices)elif self.shuffle:# Randomly sample uttrancesif len(self.rest) > batch_size:data_indices = random.sample(list(self.rest), batch_size)self.rest -= set(data_indices)else:# Last mini-batchdata_indices = list(self.rest)self.reset()self.is_new_epoch = Trueself.epoch += 1# Shuffle selected mini-batchrandom.shuffle(data_indices)else:if len(self.rest) > batch_size:data_indices = sorted(list(self.rest))[:batch_size]self.rest -= set(data_indices)# NOTE: rest is in name orderelse:# Last mini-batchdata_indices = list(self.rest)self.reset()self.is_new_epoch = Trueself.epoch += 1# Load dataset in mini-batch# 将数据加载到mini-batch中,# list() 函数是将元组转化为列表,也可以将numpy array 降低一个维度然后返回一个列表,这里好像不用这个函数也行# 这里将每个输入或label的.npy 文件读入后按行拆开# map() 函数是为后一个参数的所有成员执行第一个参数的函数# map() 返回的是一个list# 这里map() 返回的列表中,每个元素是一个numpy array,它们的shape不一定相同# np.load() 是加载 .npy文件,这里用了 lambda 表达式。 功能是加载每个map()第二个参数中指定路径的.npy文件# np.take() 功能是从 input_paths中取出下标为data_indices的元素,axis为沿着哪个轴取,详细的还是看numpy文档吧# data_indices 是多少? data_indices 就是上面shuffle或sort过的# 同时上面的代码也考虑到了最后一个minibatch的大小# input_list 中保存的是数据,不是路径了# input_list 是一个numpy 矩阵# 注意这里 np.array() 的参数是一个列表,这个列表的每个元素代表一个语音信息,后面可以知道每个元素是一个二维矩阵,每个矩阵的列数相等,行数不相等。# 因为每个矩阵的一行中的数据代表特征量,特征量都是相等的。# 行数代表长度,不一定是相等的。input_list = np.array(list(map(lambda path: np.load(path),np.take(self.input_paths, data_indices, axis=0))))# 同上,label_list也是一个numpy 矩阵。# np.array()的参数是一个列表,列表中的每个元素代表一个标签,每个元素是一个numpy array,后面可以看到每个元素都是一维向量label_list = np.array(list(map(lambda path: np.load(path),np.take(self.label_paths, data_indices, axis=0))))# hasattr() 函数就是如同这个名称,作用是判断对象是否包含属性,这里判断 self 是否包含 input_size ,可以看到前面是没有对 self.input_size 进行赋值的# 如果没包含的话,就给self.input_size赋值if not hasattr(self, 'input_size'):# shape[1] 为列数# input_size为什么等于 shape[1],说明input_list[0]不是一维的?# 猜测 input_list[0] 的每行代表语音信号一个点的信息,如语音信号经过mfcc变换后成为一个13维向量。 行数表示这个语音信号的timestep。# **这样的话,那么要读取的.npy文件中就是一个二维矩阵,行数代表序列的timestep**# 这样后面取最大值也可以理解了# 做nlp时,一般需要把一个词映射为一个向量,这个过程叫embedding,也叫word embeddingself.input_size = input_list[0].shape[1]if self.num_stack is not None and self.num_skip is not None:self.input_size *= self.num_stack# Frame stacking# 这里不明白这个 stack_frame 是什么作用,需要看一下这个函数# utils/io/inputs/frame_stacking.py 中input_list = stack_frame(input_list,self.num_stack,self.num_skip,progressbar=False)# Compute max frame num in mini-batch# 计算mini-batch 中的最大帧数# 用map()函数取出input_list 中每个shape,然后取最大值max_frame_num = max(map(lambda x: x.shape[0], input_list))# Compute max target label length in mini-batch# 计算最大标签长度,这里可以看出label_list 是一个二维矩阵max_seq_len = max(map(len, label_list))################################################################# Initialization# 初始化# np.zeros的第一个参数为要初始化的矩阵的shape,为一个元组,第二个参数为数据类型# 这里可以看出这个要读入的数据是什么格式的inputs = np.zeros((len(data_indices), max_frame_num, self.input_size * self.splice),dtype=np.float32)# 注意这里中括号中表示列表运算# [self.padded_value] * max_seq_len 表示max_seq_len 个 padded_value 组成的列表# []* len(data_indices) 同理,# labels 这里是一个二维矩阵# 其shape为 data_indices * max_seq_lenlabels = np.array([[self.padded_value] * max_seq_len] * len(data_indices))# 这里开一个numpy矩阵,一维的,其中记录了一个minibatch中每个输入序列的长度inputs_seq_len = np.zeros((len(data_indices),), dtype=np.int32)input_names = list(map(lambda path: basename(path).split('.')[0],np.take(self.input_paths, data_indices, axis=0)))##################################################################
# 前面加载了batch_size个输入到input_list 这个 一维的 numpy array 中
# 其有batch_size 个元素,每个元素是一个二维的numpy array
# 注意这里 input_list 并不是三维张量,因为每个元素的shape是不等的
# input_size 是每个timestep输入的向量维数
# 同样的方式加载了 batch_size 个 label 到label_list 这个一维的 numpy array中
# 其有batch_size 个元素,每个元素是一个一维的numpy array
# 注意它不是二维矩阵,因为每个元素的维数是不相等的
##################################################################
# 在Initialization这部分初始化了inputs,labels
# inputs 是一个三维张量,labels是一个二维矩阵
##################################################################
# 这部分要将input_list 中的值给inputs,label_list 中的值给labels
# 同时padding
# 因为训练时input需要是shape为(batch_size,timestep,feature_num)的三维张量
# label 需要是shape为(batch,label_len)的二维矩阵# Set values of each data in mini-batchfor i_batch in range(len(data_indices)):data_i = input_list[i_batch]# 上面说过了 input_list 的每个元素的shape为其 frame_num * input_sizeframe_num, input_size = data_i.shape# Splicing# 将data_i reshape成三维张量data_i = data_i.reshape(1, frame_num, input_size)# 需要看下这个函数的功能data_i = do_splice(data_i,splice=self.splice,batch_size=1,num_stack=self.num_stack)# reshape 成 frame_num 行data_i = data_i.reshape(frame_num, -1)# inputs是上面初始化好的零矩阵,这里给其赋值# 单独一个冒号表示由起到止, :frame_num 表示由起到 frame_numinputs[i_batch, :frame_num, :] = data_i# 这里是不是不太对,labels是个二维矩阵,测试时直接给矩阵中的一个元素赋值?if self.is_test:labels[i_batch, 0] = label_list[i_batch]else:labels[i_batch, :len(label_list[i_batch])] = label_list[i_batch]inputs_seq_len[i_batch] = frame_num################ Multi-GPUs###############if self.num_gpu > 1:# 如果self.num_gpu 大于1,就把inputs 和 label 分为self.num_gpu份# np.array_split() 是按顺序平分的# np.array_split() 函数会多加一个轴,这个轴表示是哪份# Now we split the mini-batch data by num_gpuinputs = np.array_split(inputs, self.num_gpu, axis=0)labels = np.array_split(labels, self.num_gpu, axis=0)inputs_seq_len = np.array_split(inputs_seq_len, self.num_gpu, axis=0)input_names = np.array_split(input_names, self.num_gpu, axis=0)else:# 如果 self.num_gpu 为1,那么就添加一个轴,因为上面使用np.array_split() 时会添加一个轴# 是为了保证两种情况下返回格式是一致的inputs = inputs[np.newaxis, :, :, :]labels = labels[np.newaxis, :, :]inputs_seq_len = inputs_seq_len[np.newaxis, :]input_names = np.array(input_names)[np.newaxis, :]# 这个成员好像是继承父类的self.iteration += len(data_indices)# Clean updel input_listdel label_listreturn (inputs, labels, inputs_seq_len, input_names), self.is_new_epoch
看了这两个代码应该知道,首先有一个保存文件路径的pickle 文件。
其次 每个label和input都是各自单独存储在.npy 文件中,input是numpy array,其shape为 (frame_size , feature_num),这个feature_num 在yml文件中就是Input_size。 label也是 numpy array ,其shape为(1,label_length)。
现在还有一个疑问,input肯定是数字,这里读入的label是数字还是字符?
后来发现,在这个工程里测试集相对于训练集和开发集是区别对待的,训练集和开发集都是数字, shape是(1,n) 。测试集就是一个字符串,shape 是 (1,1)。TIMIT只分训练集和测试集,我之前提取时,全部都转换成数字了,并且没再另外划分出开发集,所以我的训练集和测试集是同一个。(这样虽然不太好。但目前赶进度)
所以就先偷懒暂时把训练中使用测试集的代码注释掉,只使用训练集和开发集。
上篇笔记里看到更新参数时执行了 sess.run(train_op, feed_dict=feed_dict_train)
,这里的train_op 定义了对数据进行的操作,
train_op = model.train(loss_op,optimizer=params['optimizer'],learning_rate=learning_rate_pl)
这个函数是写在 models/ctc/ctc.py 里,下一步看一下这个文件
关于为什么要padding
训练时,每个batch的输入数据一般被抽象成(batchsize,timestep,input dim)的3d张量。在batch里的每个sample,都是一个(timestep,input dim)的矩阵,自然要每个样本的timestep一样了。
还有就是等长序列矩阵运算的时候机器处理速度快
参考了https://jizhi.im/community/discuss/2017-05-07-7-50-48-pm张量和向量
先简单理解为向量可以看成一维的“表格”,矩阵是二维的“表格”,n阶张量就是n维的“表格”,我注释有些地方说的不对。
使用Tensorflow进行语音识别 代码阅读笔记2相关推荐
- 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(八)—— 模型训练-训练
系列目录: 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(一)--数据 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(二)-- 介绍及分词 菜鸟笔记-DuReader阅读理解基线模 ...
- 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(九)—— 预测与校验
系列目录: 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(一)--数据 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(二)-- 介绍及分词 菜鸟笔记-DuReader阅读理解基线模 ...
- [置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- linux 协议栈 位置,[置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- BNN Pytorch代码阅读笔记
BNN Pytorch代码阅读笔记 这篇博客来写一下我对BNN(二值化神经网络)pytorch代码的理解,我是第一次阅读项目代码,所以想仔细的自己写一遍,把细节理解透彻,希望也能帮到大家! 论文链接: ...
- leveldb代码阅读笔记(一)
leveldb代码阅读笔记 above all leveldb是一个单机的键值存储的内存数据库,其内部使用了 LSM tree 作为底层存储结构,支持多版本数据控制,代码设计巧妙且简洁高效,十分值得作 ...
- C++ Primer Plus 6th代码阅读笔记
C++ Primer Plus 6th代码阅读笔记 第一章没什么代码 第二章代码 carrots.cpp : cout 可以拼接输出,cin.get()接受输入 convert.cpp 函数原型放在主 ...
- [原创]fetchmail代码阅读笔记---ESMTP的认证方式
fetchmail代码阅读笔记---ESMTP的认证方式 作者: 默难 ( monnand@gmail.com ) 0 引言 fetchmail是Eric S. Raymond组织编写的一款全功 ...
- CNN去马赛克代码阅读笔记
有的博客链接是之前几周写好的草稿,最近整理的时候才发布的 CNN去马赛克论文及代码下载地址 有torch,minimal torch和caffe三种版本 关于minimal torch版所做的努力,以 ...
最新文章
- 最新版,别的可以不用看了,zabbix 监控 esxi
- python哪一版好用-Python最好用的编辑器是哪款?北京老男孩教育
- MATLAB reshape()函数和sub2ind()函数
- CountDownLatch 的使用 || enum 枚举使用的小技巧
- webpack 零基础到工程实战(1)
- mysql设置php权限_MYSQL新建用户并设置权限
- 从 0 到 1 实现浏览器端沙盒运行环境
- 检查 Linux 服务器性能
- 指纹识别开源竞赛启动,5000张指纹图像匹配
- 【李宏毅2020 ML/DL】P59 Unsupervised Learning - Auto-encoder
- 【Linux】Linux JSON 格式化输出
- CCS软件安装教程(超级详细)
- 应用ITIL提升企业IT服务管理
- android app 嵌入广告,流氓来了!如何拯救手机中嵌入广告的应用
- Google广告数据分析与优化总结
- 中国科学院大学2015年数学分析高等代数考研试题
- nlohmann json使用
- js计算文件MD5值
- java 学习7.13 正则表达式 Pattern和Matcher类 Math类 Random类 System类 BigDecimal类 Date类 SimpleDateFormat类 Cale
- win10“User Profile Service 服务未能登录,无法加载用户配置文件问题
热门文章
- 第八章 SQL聚合函数 MAX
- ECCV / TNNLS 20 - 如何在异常检测中利用“结构structure - 纹理texture”一致性【P-Net,MemSTC-Net】
- linux小红帽自带游戏在哪里,安卓手机自带的隐藏游戏在哪 自带游戏位置介绍
- 华为交换机vlan配置举例_华为交换机配置VLAN和VLANif
- 哪吒壁纸来袭,教你不用PS也能制作哪吒1080P超清壁纸,你不看看
- 全双工,双工,单工的区别
- (148)FPGA高扇出信号优化方法(三)
- Python之父加入微软
- Android开发:长连接通信设计与实现
- uni-app 开发app 打包后的尺寸与使用hbuilderx预览时不符