hiro_5656's blog

機械学習やクラウド技術について勉強したことを発信していきます!

【PyTorch】自作のDataset, DataLoader のつくり方!

まえがき

機械学習モデルの検証の際にPyTorchに付属のMNISTなどのデータセットを使用することも多いと思いますが、
実際の現場では独自のデータを使う機会の方が多いと思います。
今回は機械学習フレームワークPyTorchで自作のDataset, DataLoaderのつくり方をご紹介していきます!

Dataset のつくり方

まず必要なライブラリをインポートします。
Dataset, DataLoader はまとめてインポートできます。

import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

サンプルデータを作成します。
numpy でデータを扱うことが多いと思うので、numpyで作成します。

import numpy as np

sample_data = np.random.rand(100, 32, 32, 3)
sample_data.shape

### -------------------------
(100, 32, 32, 3)

では実際にDatasetを作成しましょう。雛形はこちらです。

class MyDataset(Dataset):
    def __init__(self, input_data):
        self.input_data = input_data
        self.len = input_data.shape[0]
        
    def __getitem__(self, index):
        img = self.input_data[index]
            
        return img
    
    def __len__(self):
        return self.len

必要な要素は2つです。

 __getitem__(self, index)

①指定されたindexのデータを返すメソットです。

__len__(self)

②データセットの大きさ(長さ)を返すメソットです。

こちらに先ほどのサンプルデータを渡し、データセットを作成します。

dataset = MyDataset(sample_data)

print(type(dataset[0]))
print(dataset[0].shape)

### ----------------------
<class 'numpy.ndarray'>
(32, 32, 3)

ただしこのままだと元のnumpy配列のままのデータセットです。
PyTorchで扱うには、Tensor に直さなくてなならないのでDataset内部でTensorに直す処理を加えましょう。
そのように修正したDatasetがこちらです。

class MyDataset(Dataset):
    def __init__(self, input_data, transform=None):
        self.input_data = input_data
        self.len = input_data.shape[0]
        self.transform = transform
        
    def __getitem__(self, index):
        img = self.input_data[index]
        
        if (self.transform is not None):
            img = self.transform(img)
            
        return img
    
    def __len__(self):
        return self.len
transform = transforms.Compose([transforms.ToTensor()])

dataset = MyDataset(sample_data, transform)

print(type(dataset[0]))
print(dataset[0].size())

### ----------------------------
<class 'torch.Tensor'>
torch.Size([3, 32, 32])

配列もnumpy(H, W, C) → tensor(C, H, W) にきちんと変換されてますね。

このtransforms.Compose()内にデータセットに加えたい一連の処理を順番に記述できます。
例えば、

transform = transforms.Compose([
  transforms.Resize(256), 
  transforms.CenterCrop(224), 
  transforms.ToTensor()])

とすると、
①画像サイズを256に変換
②画像の中心を切り取ってサイズを224にする
テンソルに変換
というような一連の処理を加えることができます。
どのような処理があるかは他の参考ページをご参照ください。

pystyle.info

ちなみに教師あり学習の場合、入力データとその教師ラベルをセットでデータセットにすることが多いと思うのでそのコードも載せておきます。

class MyDataset(Dataset):
    def __init__(self, input_data, label_data, transform=None):
        self.input_data = input_data
        self.label_data = label_data
        self.len = input_data.shape[0]
        self.transform = transform
        
    def __getitem__(self, index):
        img = self.input_data[index]
        label = self.label_data[index]
        
        if (self.transform is not None):
            img = self.transform(img)
            
        return img, label
    
    def __len__(self):
        return self.len
transform = transforms.Compose([transforms.ToTensor()])
label_data = np.ones(sample_data.shape[0])

dataset = MyDataset(sample_data, label_data, transform)

print(dataset[0][0].size())
print(dataset[0][1])

### ----------------------------
torch.Size([3, 32, 32])
1.0

DataLoader のつくり方

Dataset が作成できたので、続けてDataLoaderを作成しましょう。
DataLoader はとても簡単です。

dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

batch_size を指定することで、1回のiterateで何個のデータが渡されるかを指定できます。
shuffleはデフォルトはFalseになっており、datasetの順番通りにデータが渡されるのですが、
過学習を防ぎたい場合に訓練データをランダムな順番で渡して欲しい場合もあるので、
その際はshuffle=Trueにしておきましょう。

for img, label in dataloader:
    print(img.size(), label.size())

### ---------------------------------------
torch.Size([32, 3, 32, 32]) torch.Size([32])
torch.Size([32, 3, 32, 32]) torch.Size([32])
torch.Size([32, 3, 32, 32]) torch.Size([32])
torch.Size([4, 3, 32, 32]) torch.Size([4])

モデルへの入力時のエラー

for img, label in dataloader:
    img = img.to(device)
    output = model(img)

というような形でデータローダからモデルへ入力する際に
RuntimeError: expected scalar type Double but found Float
というエラーが出されることがあります。
基本的にモデル内のパラメータの型と入力データの型は一致している必要があります。

この際の解決法は入力データを img.float() として型変換することです。

for img, label in dataloader:
    img = img.to(device).float()
    output = model(img)

あとがき

いかがだったでしょうか。
DatasetやDataLoaderは自作でモデル作成をやり始めると必ず通る最初の関門なので、
(モデル作成のメイン部分ではないですし(>_<))簡単にコーディングできるようなっておきたいですね。