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

##概要
今回は、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分用意してくれるので、学習時に非常に便利です。
[こちら](https://qiita.com/takurooo/items/e4c91c5d78059f92e76d)を参考にすると、読み込み関連で、以下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クラスを載せます。各関数について簡単に説明いたします。


```python: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という流れになります。

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