##概要
今回は、PyTorchにおけるシーケンスデータ入力の方法について、まとめてみました。
いろいろと至らぬ面もあるかと存じますが、技術的なご指導いただけると幸いです。
当記事でご理解いただけるのは、PyTorchにおけるデータセットを固定長の動画像の塊にして、読み込む方法についてです。特に、UCSD DATASETのような、動画像として保存されているのではなく、フォルダーごとに連番画像として保存されているようなデータセットを扱うことを想定しております。
DATASET/
├ train/
│ └ img_0001.png ← 動画の1フレーム目
│ └ img_0002.png ← 動画の2フレーム目
│ └ img_0003.png :
│ :
└ test/
PyTorchを使って、LSTMを教師なし学習させていろいろとやりたかったのですが、動画像のロードモジュールが存在しなかった(私の調べでは)ので、しぶしぶ自作に至った次第です。
想定としては、画像形式のデータセットをまず読み込み、そこから一定の固定長を持つ動画像(部分時系列)を作成し、それをバッチサイズ分固めて、LSTMに学習させるという流れになります。
##1. PyTorchにおけるデータセットの読み込み法
PyTorchでは、学習用データセットの読み込みのための、DatasetやDataLoaderクラスが用意されていて、オブジェクト宣言時に与えたdirの中に存在するデータを、1epoch毎にbatchsize分用意してくれるので、学習時に非常に便利です。
こちらを参考にすると、読み込み関連で、以下3つの登場人物が存在します。
-
transforms - データの前処理を担当するモジュール
-
Dataset - データとそれに対応するラベルを1組返すモジュール - データを返すときにtransformsを使って前処理したものを返す。
-
DataLoader - データセットからデータをバッチサイズに固めて返すモジュール
一般的には、transformsにて、データセットの前処理(標準化やサイズ変換など)について設定し、次にDatasetを使ってラベルとの対応付けと前処理を適用し、最後にDataLoaderでバッチサイズ分の塊にして返すという流れになると思います。
しかし、これは、データセットの入力がi.i.d.であればの話であり、シーケンスデータを入力としたい場合は問題です。
シーケンスデータ、特に動画像データを扱えるモジュールがほしいので、考えてみました。
##2. Datasetクラスの継承・拡張
まずベースとなるのはDatasetクラスなので、これを継承し、Dsを親クラス(スーパークラス)としたsubクラス(Seq_Dataset:SD)を宣言します。
変更したいmethodのみ改めてSD上で記述することになります。(未定義のmethodは自動的にオーバーライドされます。)
基本的にDatasetクラスを継承し、拡張する際には、__len__
および__getitem__
に対する変更を記述することになります。
特に、__getitem__
において、読み込んだDatasetオブジェクト(今回は画像データ)に対する処理(動画像変換)を記述します。
今回想定している流れは、
transformで前処理設定→ImageFolder(Dataset)で画像データ読み込みと処理→最後にSeq_Datasetにて、固定長の動画像(部分時系列)を作り、更にそれのバッチサイズ分返すになります。
以下に、今回Dsを拡張したSDクラスを載せます。各関数について簡単に説明いたします。
import torch
from torch.utils.data import Dataset
class Seq_Dataset(Dataset):
def __init__(self, datasets ,time_steps):
self.dataset = datasets
self.time_steps = time_steps
channels = self.dataset[0][0].size(0)
img_size = self.dataset[0][0].size(1)
video = torch.Tensor()
self.video = video.new_zeros((time_steps,channels,img_size,img_size))
def __len__(self):
return len(self.dataset)-self.time_steps
def __getitem__(self, index):
for i in range(self.time_steps):
self.video[i] = self.dataset[index+i][0]
img_label =self.dataset[index]
return self.video,img_label
__init__
においては、単純に必要な変数を定義しているのみです。今回は固定長、つまりtime_steprsを引数としました。また、videoという変数は、固定長の部分時系列を格納するtensorであり、0で初期化しています。ここに詠みこんだdatasetsの中にある画像データを格納する形になります。
__len__
においては、データの総数を返すのみです。今回はまず読み込んだ画像データを、最終的に固定長の動画像にして返すので、その総数はlen(dataset)-time_stepsとなります。
__getitem__
においては、time_steps分の部分時系列を生成し、videoに代入して戻しています。ここで、画像に対するレベルの操作に関しても記述可能です。今回は教師なし学習を行う背景があるため、labelに関しては何も指定せず、そのまま画像の値を代入するという暴挙に出ています。ラベル指定の方法に関しては、他を参照すればあると参考例がたくさんあると思います。(他力本願ですみません)
##3.Seq_Datasetの使用例
実際に学習する際には、data_loaderオブジェクトを用いて、forで回してモデルを学習させる形になると思います。data_loader取得までの手順としては以下の通りで、各変数を定義して、ImageFolder→Seq_Dataset→DataLoaderという流れになります。
data_dir = "./data/train/"
time_steps = 10
num_workers = 4
dataset = datasets.ImageFolder(data_dir, transform=transform)
data_ = dataset.Seq_Dataset(dataset, time_steps)
data_loader = DataLoader(data_, batch_size=opt.batch_size, shuffle=True, num_workers=num_workers)
最終的に出力される部分時系列tensorのshapeは、[batchsize,timesteps,channels,imgsize,imgsize]を有します。
今後はこの自作したモジュールを使って、PyTorchでのLSTM実装などを公開できればと思っています。
最後までご覧頂き、ありがとうございまいた。