《Selective Experience Replay for Lifelong Learning》与水塘抽样

引子

        最近在调研论文,这个过程中我常常能学到一些好东西,比如本文要聊到的水塘抽样(reservoir sampling)

        先大致说明一下论文要解决的问题以及想法。
        我们知道,卷积神经网络是深度学习中一种很出名的模型结构,给它一个隐层,再加上一个非线性激活函数,它就能够拟合一切函数。正是因为神经网络这种与生俱来的拟合特性,它常常会过度关注当下,而忘记以前的知识,这被称为灾难性遗忘(Catastrophic forgetting)。论文《Selective Experience Replay for Lifelong Learning》致力于减轻强化学习领域中多任务学习时的遗忘问题。这篇文章从经验池着手,考虑通过保留用于训练先前任务的样本来防止遗忘。作者设置了两个经验池,一个是常见的FIFO经验池,用于记录on-policy的样本;另一个是长期记忆经验池,用于记忆先前任务的训练样本。由于是Lifelong Learning,所以样本池总是相对有限,于是论文引入样本的替换/保留机制,其中效果最好的方法是全局分布匹配(Global Distribution Matching),也即让long-term memory中的样本近似能够代表所有任务的总体分布。换句话说,long-term memory中的样本是近似从全局分布中以同等的概率采样得到的。如果我们能够提前知道全局分布,那么我们直接按照全局分布进行采样就可以了,但在强化学习领域,数据常常是按序到达的,这就引出了一个问题,我们如何在不知道全局分布的前提下,采样得到一个数据集,这个数据集的分布与全局分布近似相同呢?

        论文作者借用水塘抽样算法解决了这一问题。接下来对该算法进行简要介绍。

水塘抽样

例1

        给出一个数据流,该数据流无限长或者长度未知。数据流中的每个数据只能访问一次。请写出一个随机选择算法,使得数据流中所有的数据被选中的概率相等。
        拿到这个问题,我们首先看看题目中有哪些关键点。第一,我们不知道数据流的长度(既然是流,那么可以看作顺序数据);第二,每个数据只能访问一次;第三,目标是让数据流中所有数据被选中的概率相等
        如何解决这一问题?一时半会摸不着头脑的话可以对规律进行归纳:
        假设数据流中第 i 个数据到达,先分析 i 为1的情形,这种情况下,直接将数据抽出即可,概率为1.
        再看看 i 为2的情形,此时,第一个数据到达,我们将其抽出,然后在第二个数据到达时,按 1 2 的概率对第二个数据进行保留,换句话说,产生一个随机数,如果概率小于 1 2 则返回直接返回第二个数,否则返回已经保留的第一个数。
        当 i 为3时,我们希望每个数据被输出的概率均为 1 3 。首先,第一个数据到达,直接将其留存;然后第二个数据到达,我们按照 1 2 的概率对二者进行保留;最后第三个数据到达,我们按照 1 3 对这个数据进行保留,或者说,产生一个随机数,如果该随机数小于 1 3 ,则保留第三个数,否则保留前两个数中留存下来的那个数。分析上面这个过程中各个数据的保留概率:第三个数据被输出的概率为 1 3 ;第二个数据输出的概率为 ( 1 1 3 ) 1 2 = 2 3 1 2 = 1 3 ;第一个数据被输出的概率为 ( 1 1 3 ) ( 1 1 2 ) = 2 3 1 2 = 1 3 。此时达到要求。如果我们继续按照这种规律进行采样,则得到算法如下:

import random

data = input("input: ")
i = 1
print data
temp = input("input: ")
print temp

randnum = random.uniform(0, i+1)
while temp != "exit":
    if randnum > i:
        print "rand num: ", randnum
        print "randnum >", i, " change data."
        data = temp
    else:
        print "rand num: ", randnum
        print "randnum <", i, " no op."
    print "momory: ", data
    temp = input("input: ")
    i = i + 1
    randnum = random.uniform(0, i+1)

print "output: ", data

        上面这段代码中,作了稍许处理,即为了不计算分数( 1 i ),直接将 ( 0 , i + 1 ) 中产生的随机数与 i 进行对比,效果是一样的。有一点可能会让人感到困惑,在运行上面这段代码的时候,假如我们前面的 N 个数据中,最终保留下来的是num,在第 N + 1 个数据到来时,有 1 1 N + 1 的概率选择num,当 N 逐渐增大时,我们可能会发现新到来的数据一直无法取代num,这是正确的。要知道,虽然当前这一步的比较中,num被保留下来的概率很大,但是num可是经过了前面好多轮的激烈竞争的呀,这些概率得乘起来呢,最终其概率还是 1 N + 1

例2

        给出一个数据流,该数据流无限长或者长度未知。数据流中的每个数据只能访问一次。请写出一个随机选择算法,在抽取k个数据的情形下,数据流中所有的数据被选中的概率相等。
        这道题与例1差别不大,仅仅只是例1中取一个样本,而本题要取 k 个样本,其思路是一致的。一句话总结本题,其要求是:取到第 n 个元素时,前 n 个元素被留下的几率相等,即 k n 。下面对情形进行归纳:
        假设我们要抽取的数据量 k 是小于数据流中的数据量的。我们将数据放置于memory中,初始情况时,memory中没有数据。
        当到达的数据量小于 k 时,这些数据被留下的概率为1。
        当第 k + 1 个数据到达时,设这个元素去替换掉memory中某个元素的概率为 k k + 1 ,那么不管怎样,这个元素在memory中出现的概率一定是 k k + 1 。因为这个元素将用于替换memory中已存在的各个元素,其中任意一个元素被替换掉的概率为 k k + 1 1 k = 1 k + 1 ,换句话说,memory中任意一个元素最终仍然出现的概率为 k k + 1 ,也即memory中的旧元素最终被输出的概率等于新元素最终被输出的概率,均为 k k + 1 。如果第 k + 1 个数据没有被选择用于替换memory中的数据,那么memory中所有数据被输出的可能性显然相等。
        好了,下面我们给出代码:

import random

def reservoir_sample():
    memory = []
    k = input("input k (the length of memory):")
    if k == 0:
        print "memory size is zero."

    data = input("input: ")
    memory.append(data)
    if k == 1:
        return memory
    else:
        i = 1
        while i < k:    # assume k is 2
            memory.append(input("input: "))
            i = i + 1
        print "Memory is full."    # i = k
        temp = input("input: ")
        randnum = random.randint(0, i)    #randint(0,2)-->(0,1,2)
        while temp != "ok":
            if randnum < k:
                memory[randnum] = temp
            temp = input("input: ")
            i = i + 1
            randnum = random.randint(0, i)
        return memory

def main():
    data = reservoir_sample()
    print data

if __name__ == "__main__":
    main()

        上面这段代码中,我们用的是randint,这与例1中的uniform有什么不同呢?randint(0, i)表示从[0, i]的整数中任取一个,而uniform(0, i)则是从连续的随机数中任取一个。以randint(0, 2)与uniform(0, 3)为例,randint(0, 2)表示从0,1,2中任取一个,如果取到0,1,则表示替换掉memory中的数据,而且0,1这两个位置的数据被替换掉的概率相等。如果用uniform(0, 3),则应该考虑当随机数处于(0, 2)这个区间则替换memory中的数据,而(2, 3)则保留memory中的数据不变,或者说,uniform(0, 3)可以很方便地将区间分为3份,与randint(0, 2)是对应的。本题使用randint是为了更加方便的利用randnum来表示数组下标。


        我们回到本文最开始提出的问题:“如何在不知道全局分布的前提下,采样得到一个数据集,这个数据集的分布与全局分布近似相同呢?”
        在强化学习中,我们采样得到的数据是服从全局分布的,也就是说,如果我们能够让这些数据在memory中留存的概率相等,那么memory中的数据分布可以近似看作与全局分布一致。

        好啦,本文就说到这里咯~




猜你喜欢

转载自blog.csdn.net/u013745804/article/details/79748923