最近用PyTorch训练一个新的CNN,需要在输入的训练图像上增加简单的噪声,最开始是利用NumPy提供的随机数函数去添加噪声,基于PyTorch提供的dataloader进行图像加载。在使用dataloader时,发现当worker数量大于1时,每个worker生成的随机数的序列是完全一样的。虽然每次启动dataloader,NumPy给出的随机数序列不同,但是每个worker使用的是一模一样的随机数列。这是我不需要的行为。
简单思考一下,原因可能是当使用一个以上的worker时,dataloader采用python的multiprocessing package,生成多个进程去加载数据,在生成进程后,各个子进程没有各自生成自己独立的seed,而是继承了父进程的seed,从而各自生成的随机数列是完全一致的。于是进行了简单的测试
from __future__ import print_function from multiprocessing import Process import os import numpy as np import torch def func(name, n=4): np.random.seed( int(os.getpid()) ) for i in range(n): print("%s: %d, np.random.randn(1) = %+f, torch.randn(1) = %+f. " % ( name, i, np.random.randn(1), torch.randn(1) )) if __name__ == "__main__": processes = [] processes.append( Process(target=func, args=("p1", 4)) ) processes.append( Process(target=func, args=("p2", 4)) ) print("Start all the processes.") for p in processes: p.start() for p in processes: p.join() print("All processes joined.")上述代码在注释掉func( )中的np.random.seed( ) 函数的调用时,输出如下: 而使用np.random.seed( ) 函数后,输出变为 可以看到NumPy输出的随机数的变化。在上面两个测试中,同样使用了PyTorch提供的torch.randn( )函数,可以看到torch.rand( ) 的配置和NumPy是独立的,它总是输出相同的序列。当然,与NumPy类似,可以使用torch.manual_seed( ) 函数配置PyTorch使用的seed,下图为同时配置NumPy和PyTorch的seed后的输出情况 此时,两个进程输出的随机数序列是不同的。
现在的问题是如何修改我之前的dataloader,经过几次实测,发现PyTorch已经内部进行了处理,在dataloader的不同worker内,PyTorch配置了不同的seed,worker之间所使用torch.randn( )输出的随机数序列是不同的。但是PyTorch并没有配置NumPy,所以默认情况下,dataloader的workder使用NumPy的random.randn() 函数将得到一样的序列。
from __future__ import print_function import numpy as np import os import torch import torch.utils.data as data class RandomDataFolder(data.Dataset): def __init__(self, n=3): super(RandomDataFolder, self).__init__() self.data = [ i for i in range(n) ] def __len__(self): return len( self.data ) def __getitem__(self, idx): a = torch.FloatTensor( (self.data[idx], ) ) print("a[0] = %f, np.random.rand(1) = %f, torch.randn(1) = %+f, os.getpid() = %d. " % (a[0], np.random.rand(1), torch.randn(1), os.getpid())) return a def show(self): for d in self.data: print(d) if __name__ == "__main__": print("Test the random functions with dataloader.") # Create the dataset. dataset = RandomDataFolder(8) print("The original data: ") dataset.show() print("") # Create the dataloader. dataloader = data.DataLoader( dataset, \ batch_size=2, shuffle=False, num_workers=2, drop_last=False ) # import ipdb; ipdb.set_trace() # Test. print("The actual loaded data.") for batchIdx, (data) in enumerate( dataloader ): for i in range(data.size()[0]): print("batchIdx = %d, data = %f. " % ( batchIdx, data[i, 0] ))输出如下图
在PyTorch的dataloader的worker之间,torch.randn( )将输出不同的随机序列,NumPy的random.randn( )将输出相同的随机序列。