起因
最近用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
现在的问题是如何修改我之前的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( )
将输出相同的随机序列。