• 看一下这个工程中的数据加载方式

数据加载

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相关推荐

  1. 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(八)—— 模型训练-训练

    系列目录: 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(一)--数据 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(二)-- 介绍及分词 菜鸟笔记-DuReader阅读理解基线模 ...

  2. 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(九)—— 预测与校验

    系列目录: 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(一)--数据 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(二)-- 介绍及分词 菜鸟笔记-DuReader阅读理解基线模 ...

  3. [置顶] Linux协议栈代码阅读笔记(一)

    Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...

  4. linux 协议栈 位置,[置顶] Linux协议栈代码阅读笔记(一)

    Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...

  5. BNN Pytorch代码阅读笔记

    BNN Pytorch代码阅读笔记 这篇博客来写一下我对BNN(二值化神经网络)pytorch代码的理解,我是第一次阅读项目代码,所以想仔细的自己写一遍,把细节理解透彻,希望也能帮到大家! 论文链接: ...

  6. leveldb代码阅读笔记(一)

    leveldb代码阅读笔记 above all leveldb是一个单机的键值存储的内存数据库,其内部使用了 LSM tree 作为底层存储结构,支持多版本数据控制,代码设计巧妙且简洁高效,十分值得作 ...

  7. C++ Primer Plus 6th代码阅读笔记

    C++ Primer Plus 6th代码阅读笔记 第一章没什么代码 第二章代码 carrots.cpp : cout 可以拼接输出,cin.get()接受输入 convert.cpp 函数原型放在主 ...

  8. [原创]fetchmail代码阅读笔记---ESMTP的认证方式

    fetchmail代码阅读笔记---ESMTP的认证方式 作者: 默难 ( monnand@gmail.com ) 0    引言 fetchmail是Eric S. Raymond组织编写的一款全功 ...

  9. CNN去马赛克代码阅读笔记

    有的博客链接是之前几周写好的草稿,最近整理的时候才发布的 CNN去马赛克论文及代码下载地址 有torch,minimal torch和caffe三种版本 关于minimal torch版所做的努力,以 ...

最新文章

  1. 最新版,别的可以不用看了,zabbix 监控 esxi
  2. python哪一版好用-Python最好用的编辑器是哪款?北京老男孩教育
  3. MATLAB reshape()函数和sub2ind()函数
  4. CountDownLatch 的使用 || enum 枚举使用的小技巧
  5. webpack 零基础到工程实战(1)
  6. mysql设置php权限_MYSQL新建用户并设置权限
  7. 从 0 到 1 实现浏览器端沙盒运行环境
  8. 检查 Linux 服务器性能
  9. 指纹识别开源竞赛启动,5000张指纹图像匹配
  10. 【李宏毅2020 ML/DL】P59 Unsupervised Learning - Auto-encoder
  11. 【Linux】Linux JSON 格式化输出
  12. CCS软件安装教程(超级详细)
  13. 应用ITIL提升企业IT服务管理
  14. android app 嵌入广告,流氓来了!如何拯救手机中嵌入广告的应用
  15. Google广告数据分析与优化总结
  16. 中国科学院大学2015年数学分析高等代数考研试题
  17. nlohmann json使用
  18. js计算文件MD5值
  19. java 学习7.13 正则表达式 Pattern和Matcher类 Math类 Random类 System类 BigDecimal类 Date类 SimpleDateFormat类 Cale
  20. win10“User Profile Service 服务未能登录,无法加载用户配置文件问题

热门文章

  1. 第八章 SQL聚合函数 MAX
  2. ECCV / TNNLS 20 - 如何在异常检测中利用“结构structure - 纹理texture”一致性【P-Net,MemSTC-Net】
  3. linux小红帽自带游戏在哪里,安卓手机自带的隐藏游戏在哪 自带游戏位置介绍
  4. 华为交换机vlan配置举例_华为交换机配置VLAN和VLANif
  5. 哪吒壁纸来袭,教你不用PS也能制作哪吒1080P超清壁纸,你不看看
  6. 全双工,双工,单工的区别
  7. (148)FPGA高扇出信号优化方法(三)
  8. Python之父加入微软
  9. Android开发:长连接通信设计与实现
  10. uni-app 开发app 打包后的尺寸与使用hbuilderx预览时不符