PyTorch dataloader 中的随机数

起因

最近用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()函数后的输出
而使用np.random.seed( ) 函数后,输出变为
使用np.random.seed()函数后的输出
可以看到NumPy输出的随机数的变化。在上面两个测试中,同样使用了PyTorch提供的torch.randn( )函数,可以看到torch.rand( ) 的配置和NumPy是独立的,它总是输出相同的序列。当然,与NumPy类似,可以使用torch.manual_seed( ) 函数配置PyTorch使用的seed,下图为同时配置NumPy和PyTorch的seed后的输出情况
使用np.random.seed()和torch.manual_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] ))
    

输出如下图

dataloader的随机数输出

结论

在PyTorch的dataloader的worker之间,torch.randn( )将输出不同的随机序列,NumPy的random.randn( )将输出相同的随机序列。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章