はじめに
複数の実験条件で比較検証を行う場合、比較したい条件以外は同じ条件で実験しなければなりませんが、dataloader の shuffle 機能を使うと実行ごとに毎度変わったデータの読み込み順になってしまいます。これでは学習モデルの再現性を保つことができません。
今回は、データの読み込み順にランダム性をもたせつつ、shuffle機能の再現性をseedで担保する解決策を残しておきます。
解決方法
torch.manual_seed(seed)を加えます。
seed = 20220703
'''
追加コード: shuffle の再現性を担保する
'''
torch.manual_seed(seed)
train_set = mydataset.MyDatasets(input_dir)
train_loader = torch.utils.data.DataLoader(train_set,batch_size=n_batch,shuffle=True,num_workers=4,pin_memory=True,drop_last=True)
for epoch in range(n_epoch):
...
for itr, data_dict in enumerate(train_loader):
...
テストコード
実験を再現するためのコードも載せておきます。
'''
画像データを作成
'''
import os
import random
import cv2
outdir = './traindata'
os.makedirs(outdir, exist_ok=True)
n_data = 20 # 20枚生成
seed = 20220703
random.seed(seed)
for i in range(n_data):
img = 255 * np.random.rand(256,256,3)
cv2.imwrite('%s/%04d.jpg' % (outdir,i), img)
'''
自作のdataloaderを作成
・自作のtransformerを使って、ランダムに反転させる処理を追加
'''
import torch
from glob import glob
import os
import cv2
class MyDatasets(torch.utils.data.Dataset):
def __init__(self,input_dir,transform=None):
self.input_paths = glob('%s/*.jpg' % input_dir)
self.transform = transform
def __len__(self):
return len(self.input_paths)
def __getitem__(self, idx):
data_dict = {}
input_path = self.input_paths[idx]
data_dict['imgname'] = os.path.basename(input_path)
data_dict['img'] = torch.from_numpy(cv2.imread(input_path)[...,::-1].copy()).permute(2,0,1)
data_dict["flip"] = False
# random flip
if self.transform:
data_dict = self.transform(data_dict)
return data_dict
'''
自作のtransformerを作成
・データ拡張のため、画像を左右反転させる処理をランダムに行う
'''
import torch
import random
class MyTransforms():
def __init__(self):
pass
def __call__(self, data_dict):
if random.random() < 0.5:
data_dict["flip"] = True
data_dict["img"] = torch.flip(data_dict["img"],[2])
return data_dict
'''
学習コード
・データの読み込みの様子を確認するだけ
'''
import torch
import mytransform
import mydataset
seed = 20220703
n_epoch = 3
n_batch = 4
num_workers = 2
input_dir='./traindata'
torch.manual_seed(seed)
transform = mytransform.MyTransforms()
train_set = mydataset.MyDatasets(input_dir,transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=n_batch,shuffle=True,pin_memory=True,drop_last=True)
for epoch in range(n_epoch):
print('epoch: %d' % (epoch+1))
for itr, data_dict in enumerate(train_loader):
print(data_dict["imgname"])
print(data_dict["flip"])
実行結果
1回目の実行結果
epoch: 1
['0009.jpg', '0008.jpg', '0000.jpg', '0015.jpg']
tensor([False, False, False, False])
['0017.jpg', '0005.jpg', '0010.jpg', '0014.jpg']
tensor([ True, True, True, False])
['0001.jpg', '0018.jpg', '0011.jpg', '0003.jpg']
tensor([False, False, True, False])
['0016.jpg', '0019.jpg', '0012.jpg', '0002.jpg']
tensor([ True, True, False, False])
['0006.jpg', '0013.jpg', '0007.jpg', '0004.jpg']
tensor([ True, False, False, False])
epoch: 2
['0007.jpg', '0004.jpg', '0017.jpg', '0002.jpg']
tensor([ True, False, False, True])
['0006.jpg', '0010.jpg', '0008.jpg', '0018.jpg']
tensor([False, False, False, False])
['0016.jpg', '0009.jpg', '0013.jpg', '0005.jpg']
tensor([ True, False, False, True])
['0001.jpg', '0003.jpg', '0015.jpg', '0019.jpg']
tensor([ True, False, True, False])
['0011.jpg', '0000.jpg', '0014.jpg', '0012.jpg']
tensor([False, True, False, True])
epoch: 3
['0008.jpg', '0003.jpg', '0011.jpg', '0015.jpg']
tensor([False, True, False, True])
['0014.jpg', '0019.jpg', '0000.jpg', '0006.jpg']
tensor([False, True, False, False])
['0012.jpg', '0009.jpg', '0007.jpg', '0016.jpg']
tensor([False, True, False, False])
['0018.jpg', '0002.jpg', '0004.jpg', '0017.jpg']
tensor([ True, False, False, True])
['0010.jpg', '0013.jpg', '0001.jpg', '0005.jpg']
tensor([False, True, True, False])
各エポックごとに読み込むデータにランダム性を持たせながら...
2回目の実行結果
epoch: 1
['0009.jpg', '0008.jpg', '0000.jpg', '0015.jpg']
tensor([False, False, False, False])
['0017.jpg', '0005.jpg', '0010.jpg', '0014.jpg']
tensor([ True, True, True, False])
['0001.jpg', '0018.jpg', '0011.jpg', '0003.jpg']
tensor([False, False, True, False])
['0016.jpg', '0019.jpg', '0012.jpg', '0002.jpg']
tensor([ True, True, False, False])
['0006.jpg', '0013.jpg', '0007.jpg', '0004.jpg']
tensor([ True, False, False, False])
epoch: 2
['0007.jpg', '0004.jpg', '0017.jpg', '0002.jpg']
tensor([ True, False, False, True])
['0006.jpg', '0010.jpg', '0008.jpg', '0018.jpg']
tensor([False, False, False, False])
['0016.jpg', '0009.jpg', '0013.jpg', '0005.jpg']
tensor([ True, False, False, True])
['0001.jpg', '0003.jpg', '0015.jpg', '0019.jpg']
tensor([ True, False, True, False])
['0011.jpg', '0000.jpg', '0014.jpg', '0012.jpg']
tensor([False, True, False, True])
epoch: 3
['0008.jpg', '0003.jpg', '0011.jpg', '0015.jpg']
tensor([False, True, False, True])
['0014.jpg', '0019.jpg', '0000.jpg', '0006.jpg']
tensor([False, True, False, False])
['0012.jpg', '0009.jpg', '0007.jpg', '0016.jpg']
tensor([False, True, False, False])
['0018.jpg', '0002.jpg', '0004.jpg', '0017.jpg']
tensor([ True, False, False, True])
['0010.jpg', '0013.jpg', '0001.jpg', '0005.jpg']
tensor([False, True, True, False])
2回目でも1回目と同様の読み込み結果を得ることができました。
疑問点と補足
今回、シードを固定した部分は torch.manual_seed(seed) だけなのにも関わらず、なぜか mytransform.py で使用している random.random() の値も再現性が保たれているところが疑問として残っています。
torch.manual_seed() を使用したら random.random() の値も固定されるものなのかと思い、以下のような実験を行いましたが、
import random
import torch
seed = 20220703
torch.manual_seed(seed)
print(random.random())
...
output:
1回目の実行結果:
0.9445797320532637
2回目の実行結果:
0.7288089413186923
のように再現性は保たれていません...
気づいたことがありましたら、ご教授いただけると幸いです。
なお、dataloaderの__getitem__内で乱数を使用し、workerごとに異なるランダム出力を得るためには、https://qiita.com/yagays/items/d413787a78aae825dbd3 で書かれているようにworker_init_fnを定義する必要があります。
今回はshuffle以外でランダム性を持たせたいデータ拡張部分をtransform内に書いたのでその問題は起こっていないようです。