最近一直在做A3C框架中的强化学习算法,发现随机种子的设定非常重要。首先,为了代码的可复现性,要设定随机种子,让每次代码跑出来的结果一样。其次,由于A3C框架的特殊性,每个worker(一个子线程)之间要保证其多样性。但是,我在设定完了之后,发现跑出来的结果并不能保证完全一模一样,于是对于seed做了一些实验,得出了一点心得。
首先,说一下几个随机种子:
python&numpy:
random.seed(seed) np.random.seed(seed)
cpu&gpu:
torch.manual_seed(seed) # 为cpu设置随机种子 torch.cuda.manual_seed(seed) # 为当前GPU设置随机种子 torch.cuda.manual_seed_all(seed) # 为所有GPU设置随机种子
cudnn:(对结果影响不大,会影响性能)
torch.backends.cudnn.deterministic = True torch.backends.benchmark = False
先说一下为什么实验结果可能每次运行出来的结果不一样:
因为你的代码中有诸如np.random.random()的代码,如果你不设置随机种子,可能每次跑出来的结果都不一样。
下面分别来说说不同的随机种子都对哪些函数进行作用:
首先是对于python和numpy的随机种子,如果是调用random函数或者是np.random族下的函数,只能使用
random.seed(seed) np.random.seed(seed)
来对随机种子进行设定,使用torch下的随机种子没有作用。
还有就是,得看函数在哪里调用,如果是在类初始化的时候调用的函数,则不同实例,只要是设置的随机种子相同,产生的随机数也相同,如下代码所示:
class test(nn.Module): def __init__(self, seed, rank): super(test, self).__init__() random.seed(seed) np.random.seed(seed) self.rank = rank self.get() def get(self): for _ in range(5): print(self.rank, np.random.randint(0, 5))
def main(): test_1 = test(0, 0) test_2 = test(0, 1)
if __name__ == '__main__': main()
上述代码运行的结果是:
可以看出,两个实例随机出来的随机数相同。
如果产生随机数的get函数是从外部调用的,改成如下的代码:
class test(nn.Module): def __init__(self, seed, rank): super(test, self).__init__() random.seed(seed) np.random.seed(seed) seed_torch(seed) self.rank = rank def get(self): for _ in range(5): print(self.rank, np.random.randint(0, 5))
def main(): test_1 = test(0, 0) test_2 = test(0, 1) test_1.get() test_2.get()
结果如下:
就算是把get函数的调用顺序改变,改成:
def main(): test_1 = test(0, 0) test_2 = test(0, 1) test_2.get() test_1.get()
产生的随机数也不会变:
如果我把main函数中加入了全局的随机种子,其他不变:
def main(): random.seed(4) np.random.seed(4) test_1 = test(0, 0) test_2 = test(0, 1) test_1.get() test_2.get()
结果仍然不变:
可以看出,类中产生的随机数,是每个类的随机种子所决定的。如果去除类初始化中的随机种子,改为在主函数中定义随机种子,如下:
class test(nn.Module): def __init__(self, seed, rank): super(test, self).__init__() self.rank = rank def get(self): for _ in range(5): print(self.rank, np.random.randint(0, 5))
def main(): random.seed(4) np.random.seed(4) test_1 = test(0, 0) test_2 = test(0, 1) test_1.get() test_2.get()
结果如下:
可以看出,结果取决于main函数设定的随机种子。