前言
我们回到tensorflow中来, 之前我们使用tensorflow实现了比较简单的dnn和cnn。 了解了tf大概的玩法。 我们在做这些demo的时候使用的是比较简单的数据, dnn中我们是使用numpy创造的多维数组来做演示的。 cnn中我们使用了官方的mnist来演示, tensorflow中针对minist写了一个比较好的库,能帮助我们实现mini batch,多轮迭代等等。但是实际场景中我们的逻辑会复杂的多。 也没有这种专门针对mnist做的数据读取的库, 那么我们怎么来应对这些问题呢? 接来我们来演示一下tf中最新的数据流API, Dataset
dataset
dataset是tf目前比较推荐的数据处理管道API。例如,图片模型的管道可能会汇聚分布式文件系统中的文件中的数据、对每个图片应用随机扰动,并将随机选择的图片合并成用于训练的批次。文本模型的管道可能包括从原始文本数据中提取符号、根据对照表将其转换为嵌入标识符,以及将不同长度的序列组合成批次数据。使用 tf.data API 可以轻松处理大量数据、不同的数据格式以及复杂的转换。
从内存中已有的数据创建dataset
接下来我们从一个例子中看一下,我们如何创建一个dataset并使用它。
我把如果获取一个数据的部分先隐藏掉。 现在我们直接从get_table方法中拿到了一份数据,并转换为pandas的dataframe了(如果不了解pandas的同学可以google一下)。 那么通过之前的学习我们知道我们是不能够直接使用这样一份数据的。因为在训练过程中我们要训练不同的轮数, 要做随机打乱顺序, 要做mini batch等等。 所以我们不能可能直接使用一份没有经过处理的数据。 所以我们在使用pd把训练样本和label都抽取出来以后。 使用tf.data.Dataset.from_tensor_slices 方法把这两份数据都加入到了dataset中管理。 tf.data.Dataset 对象上的方法调用将其转换为新的 Dataset。 我们可以把数组或者已经存在的tensor都交给它处理。
对dataset中的数据做转换和处理
熟悉函数式编程的人一定对map方法很熟悉。 我之前写spark的教程的时候也写过关于map reduce这些操作。 那么dataset也有类似的操作,也叫map。 我们可以看看下面的应用。
看上面我们这个比较简单的做法。 我们现实定义了一个deal_data的方法用来做数据处理, 传递data和label两个数据进来。 刚才我们把df中的数据放入dataset的时候是把训练样本和label一起了进来。 这个方法里面做的事情很简单。 就是把之前的数据类型都使用tf.cast方法做类型转换,变成float32. 这样我们之后的训练过程才能够做统一处理。 然后在下面的使用dataset对数据做处理的方法中,我们看到了map这个调用。 关于其他的方法大家可以暂时先不去看。 就先看map的调用。 这个map跟python中的map还有js中的还有spark中的map都是一样的。 都是使用函数式编程的方式, 针对数据集合中的每一个数据进行处理。map中我们传递的是一个函数, 就是我们上面的针对每个数据做类型转换的方法。
训练工作流程
在上面的例子中我们说明了map是做什么用的。但是我们还有一些其他的数据流处理方式来达到在训练过程中对数据的需求。比如说处理多个周期。
处理多个周期(多轮迭代训练)
在机器学习中,我们经常能听到训练多少轮。 也有人叫迭代轮数,迭代次数。我们之前讲逻辑回归的原理的时候说过。 在这种算法中, 公式是y= w1x1 + w2x2 + .... wnxn +b 。 其中x是特征,w是权重参数。 我们机器学习,学的就是其中的w和b。通过名字叫梯度下降的算法更新w 和b的值。 我们在训练数据上执行一次全量的计算, 就是一轮迭代。 就会产生一轮w和b的值。 然后我们可以根据这个w和b的值计算出预测值和真实值的误差,我们叫损失函数。 之后我们可以使用同样的数据在上一次计算好的w和b的值的基础上,在进行一轮计算。 这里我们就叫做再训练一轮。 然后会计算出新的w和b的值。 再根据损失函数继续计算出误差。 这样我们可以在一份数据上这样一轮一轮的计算迭代下去。 这就是我们的迭代计算。 所以我们一般在做模型训练的时候,都会有一个超参数叫训练轮数。 这就需要我们的算发在训练数据中执行多次。 或者说,在tf中我们需要通过dataset的API去获取数据, 但是我们不希望有复杂的针对这些数据的处理逻辑。 我们希望的就是在训练函数中,每一次都向dataset取固定数量的数据。 我不关心它怎么取的。 那怎么做呢, 我们就用到了dataset中的repeat这个函数。
上面我们看到,在数据处理流中最先调用的就是dataset.repeat(10)。 可以认为我们取数据的时候把这份数据重复了10次。 这样在之后我们的训练函数去用for 循环读取数据做训练的时候。 就能拿到10份这样的数据。 也就是实现了我们训练10轮的目的了。 我们把这种行为叫做训练轮数,迭代次数。 也有人叫epoch(虽然在吴恩达的视频中管mini batch才叫epoch)
处理mini batch
之前讲逻辑回归的时候也说过为了增加迭代速度和节省资源开销。 我们不会一下子把所有的数据都读入内存中做整体的训练过程,而是走mini batch 的过程。先读取一小部分数据,走一次迭代训练,并更新w和b的参数。然后再读取另一小部分根据之前训练的参数的基础上继续走梯度下降的算法更新参数。 这样的过程就叫做mini batch。 那么在tf中的dataset中,也是可以很容易的实现这个操作的。还是上面的例子,再经过了repeat和map后。我们调用的就是batch函数,这个函数跟他的名字一样,就是为了mini batch而生的。 它规定了之后没读取一份数据的时候,都只能读取32条数据(因为我们参数写的是32)。
随机打乱数据的顺序(shuffle)
shuffle 意思为洗牌, 随机打乱之前的数据的顺序。我们在训练中肯定希望数据是随机分布的,所以用shuffle这个函数可以很方面的实现这一点。
消耗dataset中的数据
消耗dataset中的数据的方式我们也很熟悉。 就是迭代器(Iterator)。 跟我们数值的迭代器一样, 他会消耗掉dataset中一个mini batch的数据(之前设置的是32, 所以取32条数据), 我们可以通过下面的方式取得一个batch的数据
可以看到上面的demo, 从dataset中初始化一个迭代器, 然后使用get_next函数就可以取出一份batch,也就是32条数据了。 包含了label和data。
训练代码
训练代码跟以前的一样。就是一个简单的单隐藏层的神经网络。 使用sigmoid作为输出层的激活函数来处理2分类的问题。 需要注意的就是我们在运行的时候传递的是从dataset中取得的一个batch的data和label在做。 而且在训练过程中我们使用的是一个死循环而不是之前的在for训练中指定训练的次数。 这时候我们把控制权全放在了dataset中。 只要通过迭代器能从dataset中取得数据,我们会一直训练下去。 因为之前我们在dataset中使用repeat来重复了10次数据,间接的达到了训练10轮的目的, 通过batch函数让每次只取32条数据来达到mini batch的目的 。 所以我们在训练的时候就不需要像以前一样自己写训练轮数和mini batch的逻辑了。 当迭代器把dataset中的数据消耗干净之后, 就会抛出outofRageError。 所以我们捕获一下就可以了。 这样我们就完成了对模型训练中针对训练轮数和mini batch的操控。
结尾
dataset其实还有一些比较细节的API。 像是迭代器有几种不同的迭代器的用法。 dataset的创建方式可以从我演示的从内存中来, 也可以从文本文件中来, 也可以从TFRecord文件中来, 我没有详细说明。 之后慢慢讲
领取专属 10元无门槛券
私享最新 技术干货