设定并理解随机数种子 && Pytorch在Dataloader类中设置shuffle的随机数种子方式

1 PyTorch设置随机数的种子

为了保证模型和数据生成的可再现性,需要设定随机种子。
只要知道随机性来自哪里,设置对应的随机种子即可。
比如使用了numpy.random,则应设置numpy.random.seed(seed)。

在 PyTorch 中主要有以下几种随机种子设置规则:
主要是torch.initial_seed()torch.manual_seed()torch.seed()

1.1 torch.initial_seed()

无参数,返回一个python long类型的数值。

1.2 torch.manual_seed(seed)

参数int,随机数种子。如果seed为负值,则会映射为正值。

# 为CPU设置种子用于生成随机数,以使得结果是确定的
torch.manual_seed(args.seed)
 
# torch.cuda.manual_seed()为当前GPU设置随机种子
if args.cuda:
    torch.cuda.manual_seed(args.seed)
 
# 如果使用多个GPU,应该使用torch.cuda.manual_seed_all()为所有的GPU设置种子。
if args.cuda:
    torch.cuda.manual_seed_all(args.seed)

1.3 torch.seed()

无参数,主要功能就是产生一个随机数字,作为manual_seed()函数的种子,并执行manual_seed()。

2 python调试技巧之设定随机数种子

2.1 一般设置

为了能稳定复现各种结果,我们往往需要固定random, np, pytorch的随机数种子。如下:

def setup_seed(seed):
     torch.manual_seed(seed)
     torch.cuda.manual_seed_all(seed)
     np.random.seed(seed)
     random.seed(seed)
     torch.backends.cudnn.deterministic = True

一般情况下,对于同一个工程而言,大家知道这个方法就足够了。

2.2 多工程设置

但当我们需要同步不同工程当中的某些计算流程的结果时,这是不够的。
假设目前有一个开源项目A,是一个基于叫做torch的深度学习框架所开发的。然后我们碰到了一个需求,该需求是实现一个项目A的tensorflow1.x版本,称其为项目B。
由于静态图和动态图的关系,torch的代码是无法直接移植到tenserflow上的,实现B项目时,其中就很容易就牵涉到计算流程的改变。
假设A项目中有三个频繁使用的函数a,b,c,它们用于实现诸如随机采样等功能,里面均采用了np.random模块的函数;而B项目也用到了函数a,b,c,但顺序与A不太一致。
现在的问题是每次循环迭代计算流程时,A项目和B项目调用的函数a,b,c的次数一致,但先后顺序不同。虽然我们利用seed设置了np.random库的初始状态,但由于A,B调用a,b,c函数的顺序不同,导致两个项目的三个随机函数的输出结果始终无法保持一致。
示例代码如下:

import numpy as np

def setup_seed(seed):
    np.random.seed(seed)
def random_func_a(): # return a random array
    return np.random.rand(10)
def random_func_b(): # get a random index between 0-9
    return np.random.randint(0,10)
def random_func_c(arr): # randomly choose a val from an array
    return np.random.choice(arr)

class workflowA:
    def __call__(self,):
        arr = random_func_a()
        idx = random_func_b()
        val = random_func_c(arr)
        print('A arr is:',arr)
        print('A idx is:',idx)
        print('A val is:',val)
        print('A val - arr[idx] =',val - arr[idx])
class workflowB:
    def __call__(self,):
        idx = random_func_b()
        arr = random_func_a()
        val = random_func_c(arr)
        print('B arr is:',arr)
        print('B idx is:',idx)
        print('B val is:',val)
        print('B val - arr[idx] =',val - arr[idx])

if __name__=='__main__':
    print('project A start')
    setup_seed(888)
    A = workflowA()
    A()
    print('project B start')
    # init np.random seed
    setup_seed(888)
    B = workflowB()
    B()

在这里插入图片描述

原因以及应该如何解决?

一个随机库输出的随机数是由它当前的状态决定的,每一次产生随机数以后,状态都会发生相应的改变来产生下一个随机数,所以只要保证初始的随机数种子一致,两个程序产生的随机数次数也一致,它们的结果就能始终保持一致。

所以如果我们可以让a,b,c函数都各自独享一个随机状态,每一次调用时更新一次状态,这样就能保证两个程序只要调用随机函数的次数一致,它们的结果便能始终一致,哪怕顺序有差异。

其中用到numpy的两个函数:
numpy.random.get_state() :返回当前的状态
numpy.random.set_state(state) :设置当前的状态
只要我们为三个函数各自保存一个state,每次调用它们的时候,把state拿出来设置到numpy.random库中,这样就能做到a,b,c三个函数各自独享一个状态。

为了更便捷地实现这一功能,我们可以采用python的装饰器:

import numpy as np

class state_manager:
    def __init__(self,seed=888):
        current_state = np.random.get_state()  # 返回当前的状态
        np.random.seed(seed)  # 设置随机数种子
        self.state = np.random.get_state()  # 保存当前的状态
        np.random.set_state(current_state)  # 设置当前状态

    def __call__(self,func):
        def new_func(*args,**kwargs):
            current_state = np.random.get_state()  # 返回当前状态
            np.random.set_state(self.state)  # 设置当前状态
            results = func(*args,**kwargs)  
            self.state = np.random.get_state()
            np.random.set_state(current_state)
            return results
        return new_func

class workflowA:
    @state_manager(888)
    def random_func_a(self,):
        return np.random.rand(10)
    @state_manager(666)
    def random_func_b(self,):
        return np.random.randint(0, 10)
    @state_manager(8888)
    def random_func_c(self,arr):
        return np.random.choice(arr)
    def __call__(self,):
        arr = self.random_func_a()
        idx = self.random_func_b()
        val = self.random_func_c(arr)
        print('A arr is:',arr)
        print('A idx is:',idx)
        print('A val is:',val)
        print('A val - arr[idx] =',val - arr[idx])

class workflowB:
    @state_manager(888)
    def random_func_a(self,):
        return np.random.rand(10)
    @state_manager(666)
    def random_func_b(self,):
        return np.random.randint(0, 10)
    @state_manager(8888)
    def random_func_c(self,arr):
        return np.random.choice(arr)
    def __call__(self,):
        idx = self.random_func_b()
        arr = self.random_func_a()
        val = self.random_func_c(arr)
        print('B arr is:',arr)
        print('B idx is:',idx)
        print('B val is:',val)
        print('B val - arr[idx] =',val - arr[idx])

if __name__=='__main__':
    # init a,b,c state
    print('project A start')
    A = workflowA()
    A()

    print('')
    print('project B start')
    # init a,b,c state
    B = workflowB()
    B()

运行结果一致。

3 Pytorch在dataloader类中设置shuffle的随机数种子方式

DataLoader用于加载数据到模型中
在pytorch 中的数据加载到模型的操作顺序是这样的:
①创建一个 Dataset 对象 (可以自己定义)
②创建一个 DataLoader 对象
③循环这个 DataLoader 对象,将数据加载到模型中进行训练
注意
①DataLoader中的shuffer=False表示不打乱数据的顺序,然后以batch为单位从头到尾按顺序取用数据。
②DataLoader中的shuffer=Ture表示在每一次epoch中都打乱所有数据的顺序,然后以batch为单位从头到尾按顺序取用数据,不同epoch中的数据都是乱序的。
③在训练网络时,同样的结构与数据,但是训练后结果每次都不同,有时结果相差还很大,复现不了以前的结果,这除了和模型的参数随机初始化有关,还有是因为你这一次训练是与你上一次训练的数据乱得不一样

设置随机种子的作用就是让你的每一次训练都乱的一样,即可以让你在单次训练内部数据保持乱序但不同训练之间都是一样的乱序。

设置shuffle=Ture并设置随机种子

def setup_seed(seed):
   torch.manual_seed(seed)
   torch.cuda.manual_seed_all(seed)
   np.random.seed(seed)
   random.seed(seed)
   torch.backends.cudnn.deterministic = True

# 设置随机数种子
setup_seed(20)
train_loader2 = DataLoader(dataset=dealDataset,
                           batch_size=2,
                           shuffle=True)
for epoch in range(3):
    for i, data in enumerate(train_loader2):
        inputs, labels = data
        print(inputs)

不论运行几次运行结果都是一样滴!

猜你喜欢

转载自blog.csdn.net/weixin_45928096/article/details/126938723