LoginSignup
14
13

More than 3 years have passed since last update.

PyTorchにおける時系列データ読み込みの方法

Last updated at Posted at 2020-03-23

概要

今回は、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クラスを載せます。各関数について簡単に説明いたします。

dataset.py
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という流れになります。

main.py
  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実装などを公開できればと思っています。
最後までご覧頂き、ありがとうございまいた。

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13