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( )將輸出相同的隨機序列。

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