学习tensorflow的时候,在加载数据集部分(tf.data.Dataset)遇到了一个打乱数据集的函数shuffle,里面有一个参数buffer_size。官方文档描述为:
参数buffer_size:
一个 tf.int64 标量 tf.张量,表示此数据集中新数据集将从中采样的元素数
官方文档说明:
This dataset fills a buffer with elements, then randomly samples elements from this buffer, replacing the selected elements with new elements. For perfect shuffling, a buffer size greater than or equal to the full size of the dataset is required
此数据集用元素填充缓冲区,然后从此缓冲区中随机采样元素,用新元素替换所选元素。为了进行完美的随机排序,需要缓冲区大小大于或等于数据集的完整大小。
For instance, if your dataset contains 10,000 elements but is set to 1,000, then will initially select a random element from only the first 1,000 elements in the buffer. Once an element is selected, its space in the buffer is replaced by the next (i.e. 1,001-st) element, maintaining the 1,000 element buffer
例如,如果您的数据集包含 10,000 个元素,但设置为 1,000 个,则最初将仅从缓冲区中的前 1,000 个元素中选择一个随机元素。选择元素后,其在缓冲区中的空间将被替换为下一个(即 1,001-st)元素,从而保留 1,000 元素缓冲区。
函数为:
shuffle(
buffer_size, seed=None, reshuffle_each_iteration=None, name=None
)
buffer_size个人理解:
先看官方文档说明,‘a buffer size greater than or equal to the full size of the dataset is required’这句话说参数 buffer size需要大于或等于数据集的大小,但是用例里面, buffer size<数据集的大小,貌似有些许的冲突
eg1:buffer size小于数据集
我们有5个球命名为1-5,按顺序放置在一个有5个格子的盒子A里(全量数据),我们需要打乱它,取出3个球作为训练集,首先要准备另一个3个格子的盒子B(缓冲区)然后进行以下操作:
1.从A取1-3号球放置在盒子B;
2.随机从B中取出一个球,假设是2号;
3.4号球放置在盒子B的2号位,此时盒子B填充满(球编号为1-4-3),再随机取出一个球,比如1号球。
4.5号球放置在盒子B的1号位,此时盒子B填充满(球编号为5-4-3),再随机取出一个球,比如5号球。
5.随机抽取从B(3-4)中抽取3号;
6.随机抽取从B(4)中抽取4号;
7.最终会形成一个新的序列,即2-1-5-3-4
eg2:buffer size大于数据集
可以看作,每次随机不放回的抽走一个球,最终排出一个新的序列,少了顺序插值替换这一步
下面进行三组测试查看一下区别
##测试1
buffer_size=3 #盒子B大小为3
data = np.array([1, 2, 3, 4, 5]) #五个球排排坐
label = np.array([1, 1, 1, 0, 0]) #随便搞得标签
dataset = tf.data.Dataset.from_tensor_slices((data, label))
dataset = dataset.shuffle(buffer_size)
it = dataset.__iter__()
for i in range(5):
x, y = it.next()
print(x, y)
输出为:
tf.Tensor(2, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
测试2:buffer_size=6
某次运行结果
tf.Tensor(5, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
测试3:buffer_size=1
运行结果:
tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64) tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
总结
1.在shuffle中,buffer size是一个必要的参数,查了一些博客,一般来说都是将buffer size设置为小于数据集大小,即和官方用例相同。(测试1)
2.尽管官方文档有冲突,但是如果buffer size需要大于数据集,就等于是一个普通的不放回抽样(测试2)
3.上述二者最明显的区别:测试2中,4和5号球可能会被首次抽中,而测试1则不行。
4.buffer 不能太小,比如测试3,极端情况下,数据等于没打乱。
猜测(无证明):
增加一个buffer的意义在于
1.数据量足够大时,有了buffer 之后可以增加随机性,随着数据的抽取,buffer 会变得越来越混乱,新的序列也会越来越混乱
2.还有一个是从打乱数据的储存来着手,即eg1中,第一次取出的2号小球放在哪里,第一种是放在新的数据集的第一个位置,第二种就是放在原数据集的1号位置,即放在盒子A的一号位,因为此时盒子A前三个储存位置是空置的。个人更倾向于第二种情况,因为这样避免了生成一个空置的新序列,也就是说将buffer作为一个置换空间。
这部分的疑惑来自官方文档用例和描述的矛盾,期待有更专业的答案0.0
这部分来自我学习过程中的一个小实验
(学习项目链接)