GeneratorとIteratorについて

# 背景
Iterabledatasetと出会った！困った゜゜(´□｀。)°゜。


# GeneratorとIteratorの違い

**Iteratorの中にGeneratorがいるイメージ**

|  |Generator  |Iterator |
|---|---|---|
|説明文|generator iteratorを返す関数|反復可能オブジェクト<br>＝1つずつ要素を返せる|
|共通点 |反復処理を行える| 反復処理を行える |
|異なる点 |必要な時にのみ値を生成なのでメモリが少なくてもよい<br>Generatorは中身が分からない<br>generate関数がよばれたときにしか次の要素は分からない|一度に全ての値をメモリに読み込む<br>最初から長さと中身が分かっている|
|実装の方法 |yieldを持つ関数  |自身を戻り値とする```__iter__```メソッド、次の要素を返す```__next__```メソッドを持つ。クラスで定義されがち |



generator iterator：Generatorによって生成されたIteratorのこと (```__iter__```メソッドにyieldを使用しているとき、Iteratorであり、かつgenerator iterator)

# Iterabledataset
- ```__getitem__```が定義されていない
- ```__iter__```の定義のみで成り立っている
- 毎回同じ順番で呼び出す(シャッフルは基本ない)


- 大規模データセットでよく使用されている
- ストリーミングで呼び出す時などに使用

**困ったこと**：インデックスで呼び出しができない、```len()```が使用できないなど

**map-style dataset**：基本的にDatasetには```__getitem__```が定義されているので、indexを使用してアクセスが可能。ただし、データセット全体をメモリにのせる必要があるので、ディスクより大きいデータセットにはアクセスできない！！鬼強いPCが必要！

**Iterabledataset**：データセットを反復処理しながら徐々にデータを必要な分のみ読み込むので、サンプルのほんの一部だけがメモリにロードされ、ディスクには何も書き込まれません。便利！！





# Iterabledatasetでデータローダーを作りたい
```__getitem__```の代わりに```generate```を定義


## yield
**yield**：関数を一時的に止める役割。その時点で値を返し、また続きから再開する
**return**：値を返し、終了する



```py
class ElsaDataset
# generateが呼ばれたときにおこること
    # generateが1回目によばれたとき, indexは0, yieldがある行でforが一時的に止まって、imgとtargetがreturnされる
    # generateが2回目によばれたとき, indexは1, yieldがある行でforが一時的に止まって、imgとtargetがreturnされる
    # generateが3回目によばれたとき, indexは2, yieldがある行でforが一時的に止まって、imgとtargetがreturnされる
    # generateが4回目によばれたとき, indexは3, yieldがある行でforが一時的に止まって、imgとtargetがreturnされる
    # ....
    #
    # もしyeildがreturnだったらおこること
    # generateが1回目によばれたとき, indexは0, returnがある行でimgとtargetがreturnされる
    # generateが2回目によばれたとき, indexは0, returnがある行でimgとtargetがreturnされる
    # generateが3回目によばれたとき, indexは0, returnがある行でimgとtargetがreturnされる
    # generateが4回目によばれたとき, indexは0, returnがある行でimgとtargetがreturnされる
    # ....


    def generate(self):
        """データを生成するジェネレータ."""
        # generateが呼ばれたときにおこること
        # 1回目によばれたとき, 外側のindexは0, 内側のindexは0(REAL) yieldがある行で一時的に止まって、imgとtargetが返却される
        # 2回目によばれたとき, 外側のindexは0, 内側のindexは1(FAKE) yieldがある行で一時的に止まって、imgとtargetが返却される
        # 3回目によばれたとき, 外側のindexは1, 内側のindexは0(REAL) yieldがある行で一時的に止まって、imgとtargetが返却される
        # 4回目によばれたとき, 外側のindexは1, 内側のindexは1(FAKE) yieldがある行で一時的に止まって、imgとtargetが返却される
        # ...

        for sample in self.elsa_data:  # 1つのデータに対して2つの画像があるため
            for label in range(2):
                if label == DeepFakeDetectionLabel.REAL: 
                    url_real_image = sample.pop("url_real_image")
                    response = requests.get(url_real_image, timeout=5)
                    img = Image.open(BytesIO(response.content))
                    target = DeepFakeDetectionLabel.REAL
                else:
                    img = sample.pop("image")
                    target = DeepFakeDetectionLabel.FAKE
                yield img, target

    def __iter__(self):
        """データを生成するイテレータ."""
        return iter(self.generate())


```



# 公式
https://pytorch.org/docs/stable/data.html#iterable-style-datasets


# datasetが無限にある場合
https://discuss.pytorch.org/t/how-to-use-dataloader-with-iterabledataset/179242

# haggingface
https://huggingface.co/docs/datasets/en/about_mapstyle_vs_iterable

# 今回使用したデータセット
https://huggingface.co/elsaEU