前回まではアヤメのデータセットを利用して、学習・予測を行う流れをご紹介しました。今回はPyTorchのDataset・DataLoaderを用いて、バッチサイズごとにデータの学習を行う流れをご紹介します。
Datasetとは
PyTorchにおけるDataset(データセット)はデータの取得や前処理を行ってくれる便利なクラスだと考えてください。torch.utils.data.Dataset
クラスを継承したクラスを定義することで独自のデータセットを定義することができます。
今回の例では、sklearn.datasets
からアヤメのデータセットを取得し、学習データとテストデータに分割した状態でPyTorchのTensor型に変換したデータを返してくれるクラスを定義します。
DataLoaderとは
前回までは1エポックで全てのデータを一気に学習しましたが、通常機械学習では大量のデータを用いて学習を行うため、一度に全てのデータで学習すると非常に効率が悪く、マシンにも大きな負荷がかかってしまいます。そこでDataLoader(データローダー)を利用して、指定したバッチサイズごとに勾配の計算とパラメータの更新を行います。DataLoaderはそのバッチサイズごとのデータの取り出しを行ってくれるクラスとなります。
用語説明
- epoch(エポック): 全てのデータで行う学習の回数。10エポックでは全てのデータでの学習を10回繰り返し行うことになります。
- バッチサイズ: バッチサイズは何件のデータごとに勾配の計算・重みの更新を行うかを指定する数です。1エポックの中でバッチサイズごとに重みが更新されることとなります。
- Iteration(イテレーション): 全データが100件でバッチサイズを20とした場合、1エポックの中で5回反復して重みの更新が行われます。100 / 20 = 5100/20=5この5回がIteration数となります。
ライブラリのインポートと学習モデルの定義
ここでは前回作成したモデルを流用します。モデルの説明は割愛しますので、こちらの内容を確認しながら作業を進めてください。
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
%matplotlib inline
今回は追加でDataset
とDataLoader
をインポートしています。
class Model(nn.Module):
def __init__(self, in_features=4, h1=8, h2=9, out_features=3):
super().__init__()
self.fc1 = nn.Linear(in_features, h1)
self.fc2 = nn.Linear(h1, h2)
self.out = nn.Linear(h2, out_features)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.out(x)
return x
損失関数とオプティマイザーの定義
torch.manual_seed(1)
model = Model()
# 損失関数
criterion = nn.CrossEntropyLoss()
# 最適化関数
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
データセットの実装
# データセット定義
class IrisDataset(Dataset):
def __init__(self, is_train=True):
# データのロード
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
# 花の種類(target)のカラムを作成
df['target'] = iris.target
# 入力データの準備
X = df.drop('target', axis=1).values
y = df['target'].values
# 学習データと検証データに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)
if is_train:
self.X_data = torch.tensor(X_train, dtype=torch.float32)
self.y_data = torch.tensor(y_train)
else:
self.X_data = torch.tensor(X_test, dtype=torch.float32)
self.y_data = torch.tensor(y_test)
self.data_length = len(self.X_data)
def __getitem__(self, index):
return self.X_data[index], self.y_data[index]
def __len__(self):
return self.data_length
データセットを定義するにはtorch.utils.data.Dataset
クラスを継承したクラスを定義します。
__init__()
インスタンスの初期化時に実行される処理を定義します。ここではアヤメのデータセットを読み込み、学習データとテストデータに分割した上で、PyTorchのTensor型に変換しています。
__getitem__()
こちらのメソッドはインデックス(データの番号)を引数に渡して、該当のデータ1件を取得する処理を定義しています。返り値は説明変数と目的変数となります。
__len__()
こちらのメソッドはデータセットのデータの件数を返す処理となっています。
DatasetとDataLoaderの初期化
BATCH_SIZE = 20
dataset = IrisDataset()
train_data_loader = DataLoader(dataset=dataset, batch_size=BATCH_SIZE, shuffle=True)
DataLoaderを初期化する際には引数にdataset
、batch_size
, shuffle
を指定します。
dataset
には事前に定義したデータセットのインスタンスを渡します。batch_size
にはバッチサイズを指定します。今回はバッチサイズを20としています。学習データの件数が120件なので、1エポックあたり6イテレーションとなります。shuffle
はデータを取り出すときにデータをランダムにシャッフルするかどうかを表しています。Trueにするとシャッフルして取り出し、Falseではそのままの並びで取り出します。
学習の実行
続いて実際に学習の実行を行います。ログを出力して、DataLoaderを用いることでデータがどのように取り出されるかをみてみましょう。
epochs = 100
loss_list = []
total_samples = len(dataset)
# データ件数
print(total_samples)
n_iterations = math.ceil(total_samples / BATCH_SIZE)
# イテレーションの数
print(n_iterations)
for epoch in range(epochs):
print(f'Epoch: {epoch+1}/{epochs}')
# バッチサイズごとにループ
for i, data in enumerate(train_data_loader):
inputs, labels = data
print(f'Iteration: {i+1}/{n_iterations}, Inputs: {inputs.shape}, Labels: {labels.shape}')
y_pred = model.forward(inputs.data)
loss = criterion(y_pred, labels.data)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_list.append(loss)
# Epoch: 1/100
# Iteration: 1/6, Inputs: torch.Size([20, 4]), Labels: torch.Size([20])
# Iteration: 2/6, Inputs: torch.Size([20, 4]), Labels: torch.Size([20])
# Iteration: 3/6, Inputs: torch.Size([20, 4]), Labels: torch.Size([20])
# Iteration: 4/6, Inputs: torch.Size([20, 4]), Labels: torch.Size([20])
# Iteration: 5/6, Inputs: torch.Size([20, 4]), Labels: torch.Size([20])
# Iteration: 6/6, Inputs: torch.Size([20, 4]), Labels: torch.Size([20])
- エポックに100を指定し、学習データ全件で100回反復して学習を行います。
enumerate
用いることでデータローダーからバッチサイズ(20件)ずつデータを取得しています。iは何番目のindexかを示しており、dataには説明変数と目的変数が格納されています。- dataから説明変数(inputs)と目的変数(labels)を取り出し、
forward()
で学習を行い、criterion()
で損失を求めています。 optimizer.zero_grad()
で勾配をリセットしたのち、loss.backward()
でバックプロパゲーションを行い、optimizer.step()
でパラメータを更新しています。loss_list.append(loss)
で損失関数をリストに格納しています。- ログを見ると各エポックごとに20件ずつ6回イテレーションが回っていることが確認できます。
最後にlossの変化をプロットしておきましょう。損失関数が徐々に収束しているのが分かります。
plt.plot(loss_list)

まとめ
今回はPyTorchにおけるDatasetの定義方法とデータDataLoaderの使い方を確認しました。PyTorchを利用する上で、DatasetとDataLoaderの利用は必須となってきますので、しっかり復習するようにしましょう。