PyTorchにおけるDataLoaderクラスと抽象化に関する包括的ガイド
この投稿では、機械学習と深層学習の分野における最も難しい問題の一つ、さまざまな種類のデータを読み込み、扱うという課題について扱います。
すでにPyTorchでニューラルネットワークのコーディングに馴染んでいると仮定し、現在は多層パーセプトロンを使用してMNISTデータセットから数字を予測する作業を行っています。その場合、画像を直接ロードしてテンソルに変換するためにtorchの
- データセットに取り組んでいます
- PyTorchにおけるデータの読み込み
- MNISTデータセットの詳細な分析
- データの変換と再スケーリング
- PyTorchでカスタムデータセットを作成する
- 要約
前提条件
このチュートリアルに従うには、少なくとも8GBのVRAMを備えた十分な性能を持つNVIDIA GPUが必要です。また、Pythonのクラスとオブジェクトの基本的な理解も、全体の議論を理解するために重要です。
データセットに取り組んでいます
リアルタイムプロジェクトでディープラーニングに取り組んでいる場合、ほとんどの時間が構築するニューラルネットワークよりもデータの処理に費やされることが一般的です。これはデータがニューラルネットワークの燃料のようなものであり、データが適切であればあるほど、結果は速く、より正確になるからです!ニューラルネットワークのパフォーマンスが低下する主な理由の1つは、悪いか、十分に理解されていないデータによるものかもしれません。そのため、データを理解し、前処理し、より直感的にネットワークにロードすることが重要です。
多くの場合、私たちはMNISTやCIFARのようなデフォルトまたはよく知られたデータセットでニューラルネットワークをトレーニングします。これらに取り組んでいるとき、予測および分類タイプの問題に対して90%以上の精度を簡単に達成できます。その理由は、これらのデータセットがきちんと整理されており、前処理が容易だからです。しかし、自分のデータセットに取り組むと、高精度を達成するのは非常に難しく、挑戦的です。次のセクションではカスタムデータセットでの作業について学びます。その前に、PyTorchライブラリに含まれているデータセットを簡単に見てみましょう。
PyTorchにはいくつかの組み込みデータセットがあり、すべてはクラスtorch.datasetsに予め読み込まれています。それは何か思い出させますか?前の例では、MNIST画像を分類する際に、同じクラスを使用して画像をダウンロードしました。パッケージtorchとtorchvisionには何が入っているのでしょうか?パッケージtorchは、ニューラルネットワークを実装するために必要なすべてのコアクラスとメソッドで構成されており、torchvisionは、コンピュータビジョンのための人気のあるデータセット、モデルアーキテクチャ、および一般的な画像変換を含むサポートパッケージです。もう1つのパッケージtorchtextは、PyTorchの自然言語処理に必要な基本的なユーティリティを持っています。このパッケージは、テキストに関連するデータセットで構成されています。
ここでは、クラスtorchvisionとtorchtextに含まれるデータセットの簡単な概要を示します。
Torchvisionのデータセット
MNIST: MNISTは、正規化されて中央で切り取られた手書きの画像からなるデータセットです。60,000枚以上の訓練画像と10,000枚のテスト画像があります。これは、学習や実験目的のために最も使用されるデータセットの1つです。データセットをロードして使用するには、torchvisionパッケージがインストールされた後に、以下の構文を使用してインポートできます。
torchvision.datasets.MNIST()
ファッションMNIST:このデータセットはMNISTに似ていますが、手書きの数字の代わりに、Tシャツ、ズボン、バッグなどの衣服アイテムが含まれています。トレーニングサンプルとテストサンプルの数はそれぞれ60,000と10,000です。下にFMNISTクラスの場所があります。
torchvision.datasets.FashionMNIST()
CIFAR: CIFARデータセットにはCIFAR10とCIFAR100の2つのバージョンがあります。CIFAR10は10種類のラベルの画像で構成されており、CIFAR100は100種類のクラスを持っています。これにはトラック、カエル、ボート、車、シカなどの一般的な画像が含まれています。このデータセットはCNNを構築するのに推奨されます。
torchvision.datasets.CIFAR10()torchvision.datasets.CIFAR100()
COCO: このデータセットは、人、ボトル、文房具、本など、10万以上の日常的な物体で構成されています。この画像のデータセットは、物体検出や画像キャプション作成のアプリケーションで広く使用されています。以下は、COCOをロードできる場所です:
torchvision.datasets.CocoCaptions()
EMNIST: このデータセットは、MNIST データセットの高度なバージョンです。数字とアルファベットの両方を含む画像で構成されています。画像からテキストを認識する問題に取り組んでいる場合、これはトレーニングに最適なデータセットです。以下はクラスです:
torchvision.datasets.EMNIST()
IMAGE-NET: ImageNetは、高性能なニューラルネットワークを訓練するために使用されるフラッグシップデータセットの1つです。これは、10,000のクラスにわたって120万以上の画像で構成されています。通常、このデータセットは高性能なハードウェアシステムにロードされます。CPUだけではこのサイズのデータセットを処理することはできません。以下は、ImageNetデータセットをロードするためのクラスです:
torchvision.datasets.ImageNet()
これらは、PyTorchでニューラルネットワークを構築する際に最も頻繁に使用されるいくつかのデータセットです。他にもKMNIST、QMNIST、LSUN、STL10、SVHN、PhotoTour、SBU、Cityscapes、SBD、USPS、Kinetics-400などがあります。これらについての詳細は、PyTorchの公式ドキュメントで学ぶことができます。
Torchtextのデータセット
以前に話し合ったように、torchtextは自然言語処理のための基本的なユーティリティをすべて含むサポートパッケージです。もしあなたがNLPに不慣れであれば、それは人工知能のサブフィールドであり、大量の自然言語データ(主にテキストに関連するもの)を処理・分析します。
さて、実験や作業に使うためにいくつかの人気のテキストデータセットを見てみましょう。
IMDB: これは、トレーニング用の25,000件の高度に偏った映画レビューと、テスト用の25,000件のレビューを含む感情分類のためのデータセットです。このデータは、torchtextの次のクラスを使用して読み込むことができます:
torchtext.datasets.IMDB()
WikiText2: この言語モデル用データセットは、1億以上のトークンのコレクションです。ウィキペディアから抽出されており、句読点や実際の文字ケースを保持しています。長期依存性を伴うアプリケーションで広く使用されています。このデータは、次のようにtorchtextからロードできます:
torchtext.datasets.WikiText2()
上記の2つの人気のあるデータセットに加えて、torchtextライブラリには、SST、TREC、SNLI、MultiNLI、WikiText-2、WikiText103、PennTreebank、Multi30kなど、まだ多くのデータセットがあります。
これまでに、事前に定義された画像とテキストのセットに基づくデータセットを見てきました。しかし、もし自分自身のデータセットがある場合はどうでしょうか?それをどのように読み込むのでしょうか?今のところ、自分の画像データセットを読み込むために使用できるImageFolderクラスを学びましょう。
イメージフォルダクラス
ImageFolderは、torchvisionで用意された汎用データローダークラスで、自分の画像データセットを読み込むのに役立ちます。あなたが分類問題に取り組んでいて、特定の画像がリンゴかオレンジかを識別するためのニューラルネットワークを構築していると想像してみましょう。これをPyTorchで行うための最初のステップは、以下に示すデフォルトのフォルダー構造に画像を整理することです:
root
├── orange
│ ├── orange_image1.png
│ └── orange_image1.png
├── apple
│ └── apple_image1.png
│ └── apple_image2.png
│ └── apple_image3.png
データセットを上記のように配置した後、ImageLoaderクラスを使用してこれらの画像をすべて読み込むことができます。以下は、そのために使用するコードスニペットです:
torchvision.datasets.ImageFolder(root, transform)
次のセクションでは、データをプログラムに読み込む方法を見ていきましょう。
PyTorchにおけるデータの読み込み
データの読み込みは、ディープラーニングパイプラインを構築したりモデルをトレーニングしたりする際の最初のステップの一つです。このタスクは、データの複雑さが増すとより困難になります。このセクションでは、データセット内の要素を読み込み、反復処理するために役立つPyTorchのDataLoaderクラスについて学びます。このクラスは、torch.utils.dataモジュール内のDataLoaderとして利用可能です。DataLoaderは次のようにインポートできます:
from torch.utils.data import DataLoader
では、以下に示すDataLoaderクラスが受け入れるパラメータについて詳細に説明しましょう。
from torch.utils.data import DataLoader
DataLoader(
dataset,
batch_size=1,
shuffle=False,
num_workers=0,
collate_fn=None,
pin_memory=False,
)
1. データセット: DataLoader クラスの最初のパラメータは dataset です。ここに私たちはデータを読み込みます。
2. データのバッチ処理: batch_size は1回のイテレーションで使用されるトレーニングサンプルの数を指します。通常、データをトレーニングセットとテストセットに分割し、それぞれに異なるバッチサイズを使用することがあります。
3. データのシャッフル: shuffle は DataLoader クラスに渡される別の引数です。この引数はブール値(True/False)を受け取ります。shuffleがTrueに設定されている場合、すべてのサンプルがシャッフルされ、バッチで読み込まれます。それ以外の場合は、シャッフルせずに1つずつ送信されます。
4. マルチプロセスの許可: 深層学習は大量のデータでモデルを訓練するため、単一プロセスのみを実行していると多くの時間がかかります。PyTorchでは、引数num_workersを使用してマルチプロセッシングを許可することで、同時に実行されるプロセスの数を増やすことができます。これはバッチサイズにも依存しますが、各ワーカーが単一のバッチを読み込んで、準備ができたときにのみそれを返すため、num_workersを同じ数に設定することはお勧めしません。
num_workers=0は、必要なときにデータの読み込みを行うのがメインプロセスであることを意味します。num_workers=1は、ワーカーが1つだけであることを意味するため、動作が遅くなる可能性があります。
5. データセットの統合: collate_fn 引数は、データセットを統合したい場合に使用されます。この引数はオプションであり、主にマップスタイルのデータセットからバッチを読み込むときに使用されます。
6. CUDAテンソルにデータを読み込む: pin_memory引数を使用して、データセットを直接CUDAテンソルとして読み込むことができます。これはオプションのパラメータで、Boolean値を受け取ります。Trueに設定すると、DataLoaderクラスはテンソルをCUDAピン留めメモリにコピーしてから返します。
通常のデータロードパイプラインをよりよく理解するために、例を見てみましょう。
MNISTデータセットの詳細な分析
PyTorchのtorchvisionリポジトリは、標準的なデータセットをいくつかホストしており、MNISTは最も人気のあるものの一つです。次に、PyTorchがpytorch/visionリポジトリからMNISTデータセットをどのようにロードするかを見ていきます。まず、データセットをダウンロードし、data_trainという変数にロードします。そして、サンプル画像を印刷します。
Import MNIST
from torchvision.datasets import MNIST
Download and Save MNIST
data_train = MNIST('~/mnist_data', train=True, download=True)
Print Data
print(data_train)
print(data_train[12])
出力:
Dataset MNIST Number of datapoints: 60000 Root location: /Users/viharkurama/mnist_data Split: Train (<PIL.Image.Image image mode=L size=28x28 at 0x11164A100>, 3)
まず、最初の値が画像に対応し、2番目の値がそれに対応するラベルになるタプルを抽出してみましょう。以下はコードのスニペットです:
import matplotlib.pyplot as plt
random_image = data_train[0][0]
random_image_label = data_train[0][1]
Print the Image using Matplotlib
plt.imshow(random_image)
print("The label of the image is:", random_image_label)
ほとんどの場合、インデックスで画像にアクセスするのではなく、画像を含む行列をモデルに送信することになります。これは、データバッチを準備する必要があるとき(おそらく、毎回の実行の前にシャッフルすることも)に便利です。では、これがリアルタイムでどのように機能するか見てみましょう。以下のように、DataLoaderクラスを使用してデータセットをロードします。
import torch
from torchvision import transforms
data_train = torch.utils.data.DataLoader(
MNIST(
'~/mnist_data', train=True, download=True,
transform = transforms.Compose([
transforms.ToTensor()
])),
batch_size=64,
shuffle=True
)
for batch_idx, samples in enumerate(data_train):
print(batch_idx, samples)
これはDataLoaderを使ってシンプルなデータセットをロードする方法です。しかし、すべてのデータセットに対して常にDataLoaderを頼ることはできません。私たちはしばしば非対称解像度の画像を含む大規模または不規則なデータセットを扱っており、ここでGPUが重要な役割を果たします。
GPU上でデータを読み込む
私たちはモデルのトレーニングを速くするためにGPUを有効にできます。それでは、データを読み込む際に使用できるCUDA(PyTorchのGPUサポート)の構成を見てみましょう。こちらがコードスニペットの例です:
device = "cuda" if torch.cuda.is_available() else "cpu"
kwargs = {'num_workers': 1, 'pin_memory': True} if device=='cuda' else {}
train_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('/files/', train=True, download=True),
batch_size=batch_size_train, **kwargs)
test_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('files/', train=False, download=True),
batch_size=batch_size, **kwargs)
上記では、deviceという新しい変数を宣言しました。次に、現在のハードウェア構成をチェックするシンプルなif条件を記述します。もしGPUがサポートされている場合、deviceをcudaに設定し、それ以外の場合はcpuに設定します。num_workers変数は、並行してバッチを生成するプロセスの数を示します。データ読み込みのために、DataLoaderクラスにpin_memory=Trueを渡すと、自動的に取得したデータテンソルを固定メモリに配置し、CUDA対応のGPUへのデータ転送を高速化します。
次のセクションでは、データを読み込むための前処理ステップを定義する変換について学びます。
データの変換と再スケーリング
PyTorchの変換は、データセット全体をユニークなフォーマットに変換するシンプルな画像変換技術を定義しています。例えば、異なる解像度のさまざまな車の画像が含まれたデータセットを考えてみましょう。トレーニング中、トレーニングデータセット内のすべての画像は同じ解像度サイズである必要があります。すべての画像を手動で必要な入力サイズに変換するのは時間がかかるため、代わりに変換を使用できます。数行のPyTorchコードで、データセット内のすべての画像を希望の入力サイズと解像度に変換できます。また、transformsモジュールを使用してリサイズすることもできます。最も一般的に使用される操作はいくつかあり、transforms.Resize()は画像をリサイズするために、transforms.CenterCrop()は画像の中心からクロップするために、transforms.RandomResizedCrop()はデータセット内のすべての画像をランダムにリサイズするために使用されます。
では、torchvision.datasets から CIFAR10 を読み込み、次の変換を適用しましょう:
- すべての画像を32×32にリサイズする
- 画像にセンタークロップ変換を適用する
- 切り取られた画像をテンソルに変換する
- 画像を正規化する
まず、必要なモジュールをインポートし、torchvisionモジュールからtransformsをインポートします。データセットを視覚化するためにNumPyとMatplotlibライブラリが使用されます。
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
次に、すべての前処理ステップを順番に書くためのtransformsという名前の変数を定義します。私たちはComposeクラスを使用して、すべての変換操作を連結しました。
transform = transforms.Compose([
# resize
transforms.Resize(32),
# center-crop
transforms.CenterCrop(32),
# to-tensor
transforms.ToTensor(),
# normalize
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
resize: このResizeトランスフォームは、すべての画像を定義されたサイズに変換します。この場合、すべての画像を 32×32 にリサイズしたいので、引数として32を渡します。センタークロップ: 次に、CenterCrop変換を使用して画像を切り抜きます。ここで送る引数も解像度/サイズですが、すでに画像を32x32にリサイズしたので、画像はこのクロップで中央に揃えられます。つまり、画像は中央から32ユニット切り抜かれることになります(垂直方向と水平方向の両方で)。to-tensor: 私たちは画像をTensorデータ型に変換するために、ToTensor()メソッドを使用しました。normalize: これはテンソル内のすべての値を正規化し、それらが0.5と1の間に収まるようにします。
次のステップでは、先ほど定義した変換を行った後に、trainloaderを使用してtrainsetに
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=False)
私たちは、torchvision.datasetsからCIFARデータセットを取得し、trainとdownloadの引数をTrueに設定しました。次に、transform引数を定義されたtransform変数に設定しました。DataLoaderのイテラブルが初期化され、trainsetをそれに引数として渡しました。batch_sizeは4に設定され、シャッフルはFalseに設定されました。次に、以下のコードスニペットを使用して画像を視覚化できます。
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
def imshow(img):
img = img / 2 + 0.5
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
dataiter = iter(trainloader)
images, labels = dataiter.next()
imshow(torchvision.utils.make_grid(images))
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
Resize()、CenterCrop()、およびRandomResizedCrop()の他にも、さまざまなTransformクラスがあります。最も使用されるものを見てみましょう。
変換クラス
RandomCrop:このクラスはPyTorchにおいて、指定されたPIL画像をランダムな位置で切り抜きます。以下はRandomCropが受け付ける引数です:
torchvision.transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0)
size: この引数は、ランダムクロップの希望出力サイズを示す整数を取ります。例えば、サイズが32に設定された場合、出力はサイズ32×32のランダムにクロップされた画像になります。padding: これは最初にNoneに設定される整数引数です。整数に設定すると、画像に追加の境界を加えます。例えば、paddingが4に設定されている場合、左、上、右、下の境界をそれぞれ4ユニットずつ追加します。pad_if_needed: これはブール値を取るオプションのパラメータです。Trueに設定されている場合、画像の周囲に小さな領域を追加して、最小解像度のエラーを避けます。デフォルトでは、このパラメータはFalseに設定されています。fill: この定数値は、すべてのパディングされたピクセルの値を初期化します。デフォルトのフィル値は0です。
2. RandomHorizontalFlip: モデルを訓練中に堅牢にするために、画像をランダムに反転させることがあります。クラス RandomHorizontalFlip はそのような結果を得るために使用されます。これは1つのデフォルト引数 p を持ち、画像が反転される確率(0から1の間)を示します。デフォルト値は 0.5 です。
torchvision.transforms.RandomHorizontalFlip(p=0.5)
3. Normalize: これは画像を正規化し、引数として与えられた平均と標準偏差を用います。このクラスは以下に示す4つの引数を取ります:
torchvision.transforms.functional.normalize(tensor, mean, std, inplace=False)
- この
tensor引数は、C、H、Wの三つの値を持つテンソルを受け取ります。これらはそれぞれ、チャンネル数、高さ、幅を表しています。与えられた引数に基づいて、入力画像のすべてのピクセル値が正規化されます。 - この
meanおよびstd引数は、各チャネルに関する平均と標準偏差のシーケンスを受け取ります。 - この
inplace引数はブール値です。Trueに設定すると、すべての操作はその場で計算されます。
4. ToTensor: このクラスは、PIL画像またはNumPy n次元配列をテンソルに変換します。
torchvision.transforms.functional.to_tensor(img)
では、組み込みデータセットを使用するのではなく、カスタムデータセットを読み込むメカニズムについて理解していきましょう。
PyTorchでカスタムデータセットを作成する
これまでのところ、データセットを読み込む方法やデータを前処理するさまざまな方法について学んできました。このセクションでは、数字とテキストからなるシンプルなカスタムデータセットを作成します。数値ファイルやテキストファイルを処理するのを助けるPyTorchのDatasetオブジェクトについて説明し、特定のタスクのためにパイプラインを最適化する方法についても触れます。ここでのポイントは、Datasetクラスの__getitem__()および__len__()メソッドを抽象化することです。
__getitem__()メソッドは、インデックスによってデータセット内の選択されたサンプルを返します。__len__()メソッドは、データセットの総サイズを返します。たとえば、データセットに100,000サンプルが含まれている場合、lenメソッドは100,000を返すべきです。
この時点では、データはまだメモリにロードされていないことに注意してください。
以下は、__getitem__()および__len__()メソッドの実装について説明する抽象的なビューです:
class Dataset(object):
def __getitem__(self, index):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
カスタムデータセットの作成は複雑ではありませんが、データを読み込む通常の手順に加え、良い抽象(少なくとも良い構文糖と呼べるもの)を得るためのインターフェースを構築する必要があります。さて、数値とその平方値を持つ新しいデータセットを作成します。それをSquareDatasetと呼ぶことにしましょう。これの目的は、[a,b]の範囲内の値の平方を返すことです。以下に関連するコードを示します:
import torch
import torchvision
from torch.utils.data import Dataset, DataLoader
from torchvision import datasets, transforms
class SquareDataset(Dataset):
def __init__(self, a=0, b=1):
super(Dataset, self).__init__()
assert a <= b
self.a = a
self.b = b
def __len__(self):
return self.b - self.a + 1
def __getitem__(self, index):
assert self.a <= index <= self.b
return index, index**2
data_train = SquareDataset(a=1,b=64)
data_train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
print(len(data_train))
上記のコードブロックでは、PyTorchのDatasetクラスを継承したSquareDatasetというPythonクラスを作成しました。次に、__init__()コンストラクタを呼び出し、aとbをそれぞれ0と1に初期化しました。superクラスを使用して、継承されたDatasetクラスからlenおよびget_itemメソッドにアクセスします。次に、assert文を使用して、aがb以下であることを確認しました。これは、aからbまでの値が含まれるデータセットを作成したいからです。
次に、SquareDatasetクラスを使用して、データ値が1から64の範囲にあるデータセットを作成しました。これをdata_trainという変数に読み込みました。最後に、Dataloaderクラスは、data_train_loaderに格納されたデータに対するイテレータを作成し、batch_sizeを64に設定し、shuffleをTrueにしました。
データローダーは、オブジェクト指向プログラミングの概念を取り入れることで、Pythonの良さを活用しています。良い練習として、CelebA、PIMA、COCO、ImageNet、CIFAR-10/100などの人気のあるデータセットを使ってさまざまなデータローダーを試してみることをお勧めします。
要約
この投稿では、データの読み込みと抽象化について学びました。最初に、torchvision と torchtext パッケージで利用可能なデータセットを紹介し、いくつかの人気のあるデータセットをレビューしました。その後、DataLoader クラスについて学び、与えられたパラメータに従ってデータを整理することで、データをきれいに扱うことの重要性を確認しました。さらに、様々な手法を用いてMNISTデータセットをワークスペースに呼び込む方法を深く分析しました。データローダーとトランスフォームも紹介され、MNISTの例によってその重要性が挙げられました。また、RandomCrop、RandomHorizontalFlip、Normalize、ToTensor、および RandomRotate クラスを通じてトランスフォームとそのクラスへのより深い洞察が示されました。 その後、GPUがCPUに対して優位性を持つ理由が、PyTorch CUDAを使った例を通じて説明されました。カスタムデータセットの作成は複雑な作業ではなく、この文は短いコードスニペットを使用して正当化されました。このチュートリアルで学んだ概念や基礎は、すべてPyTorchを使用する上で基本的なものです。
