在“百度架构师手把手带你零基础实践深度学习”课程中,第一周课程的实践作业中,要求写一个cifar-10数据集的数据读取器。这是这门课程第一个作业,也是我现在到课程结束,感觉最能0距离体验paddle程序架构的一个作业。
何出此言呢?后面的作业,虽然程序越来越复杂,但以笔者机器学习0基础上手的前提,在短短的三周学习时间内,其实只能大致把握其思路与流程,并没有深入钻研每个算法的实现和设计思路的机会。
但这个数据读取器大大满足了我“钻研算法实现”的愿望,要是没有群里助教的指点险些没做出来。下面分享一下我的学习过程:
首先,作业要求写一个读写器,实现读入和乱序。
读入一般有两种思路,第一种是采用本地数据集,调用方法是:
for counter in range(本地数据个数),(其中counter代表循环计数器,下同),
由于本地数据个数已知,不管是做乱序还是读入,都可以采用这种最简单可靠的全部遍历方法。
另一种方法是在飞桨里看到的,构造一个读取器,类型reader_creator(),函数返回一个reader()函数。这个函数的特点有点类似于链表,只能顺序读取,不能直接全局定位到特定的第N个元素。
缺点是不能任意定位读取
优点是可以节约空间,你可以用多少数据读多少数据,不用一口气全部读完才开始后续工作。
cifa-10数据集在paddle网络上有配置,如果采用该种方法就可以不下载数据的情况下完成读取任务。
reader()函数的基本用法是
for counter in enumerate(reader())
每执行一次for循环相当于对reader()函数使用一次next(reader()),让读取器reader()去调用获得一个新的值以后,再进行循环内的操作。
但前提是reader()函数必须是一个带有yield的函数,类型为generator(生成器)。
这样的迭代循环方式颠覆了我对“for”函数的理解,在使用yield结构后,函数居然可以用“for”进行迭代调用,而且可以用于enumerate()(枚举函数),即函数为“可数的”,“枚举变量”。
正当我惊讶于这个神奇的结构时,我对新想法的实验程序报错了:
我的完美计划是:
trainset是paddle提供的数据读取器,类型是reader_creator()
任务是打包成patch、把数据乱序,生成新的读取器。
那么很自然的想到先打乱,再分批次,是比较合理的设计。
1, paddle.reader.shuffle(reader=trainset(),buf_size=1000)
shuffle是一个修饰reader_creator的函数,返回一个读取器reader(),作用是读取buf_size数量数据元素后打乱。
2, train_creator=paddle.batch(paddle.reader.shuffle(),batch_size)
把打乱以后的数据再分批次就好了
但这样做是不行的。
以batch函数为例,读取器修饰函数(源代码:https://github.com/PaddlePaddle/Paddle/blob/release/1.8/python/paddle/reader/decorator.py#L102),之所以可以对reader_creator()进行处理后返回reader()函数,是因为其源代码定义如下:
def batch(reader, batch_size, drop_last=False):
def batch_reader():
……
return batch_reader
按照官网的api说明:“该接口是一个reader的装饰器。返回的reader将输入reader的数据打包成指定的batch_size大小的批处理数据(batched data)。”
返回:batched reader
返回类型:generator
而batch函数调用的是reader_creator类型。paddle的api中并没有明确给出reader_creator的定义.
但《【深度学习系列】关于PaddlePaddle的一些避“坑”技巧》一文中,给出了一段样例程序:
def reader_creator(data,label):
def reader():
for i in xrange(len(data)):
yield data[i,:],int(label[i])
return reader
对比可知,batch函数,shuffle函数,也属于reader_creator类型。其能够处理并返回一个reader()类型函数的理由就在于,它在函数定义时,嵌套定义了一个函数"def reader():"
因此我的代码:
train_creator=paddle.batch( paddle.reader.shuffle() ,batch_size)
不能运行的原理就在于,shuffle输出的函数是reader()类型,没有进行嵌套定义成reader_creator类型,因此不能被batch函数调用。
当时作业采取的方案是绕过了这个问题,现在看看对这个答案并不十分满意。将输出的shuffle用reader_creator()格式包装一下,就可以被batch进行调用,这样才是当时本来设计的思路的满意答案。