# Различия между Dataset и IterableDataset

Существует два типа объектов датасетов: [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) и [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset). Выбор типа датасета, который вы будете использовать или создавать, зависит от его размера. Как правило, [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset) идеально подходит для больших датасетов (речь может идти о сотнях гигабайт!) благодаря ленивому поведению и преимуществам в скорости, тогда как [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) отлично справляется со всеми остальными задачами. На этой странице мы сравним различия между [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) и [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset), чтобы помочь вам выбрать подходящий объект датасета.

## Загрузка и потоковая передача

Если у вас есть обычный [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset), к нему можно обращаться с помощью `my_dataset[0]`. Это обеспечивает случайный доступ к строкам. Такие наборы данных также называют «map-style». Например, вы можете скачать ImageNet-1k следующим образом и получить доступ к любой строке:

```python
from datasets import load_dataset

imagenet = load_dataset("timm/imagenet-1k-wds", split="train")  # downloads the full dataset
print(imagenet[0])
```

Однако есть одно ограничение: весь набор данных должен быть сохранён на диске или в памяти, что не позволяет работать с наборами данных, превышающими объём диска. Поскольку это может быть неудобно для больших наборов данных, существует другой тип набора данных — [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset). Если у вас есть `IterableDataset`, вы можете обращаться к нему с помощью цикла `for`, чтобы загружать данные по мере итерации по набору. Таким образом, в памяти загружается лишь небольшая часть примеров, и ничего не записывается на диск.

Например, вы можете стримить датасет ImageNet-1k, не скачивая его на диск:

```python
from datasets import load_dataset

imagenet = load_dataset("timm/imagenet-1k-wds", split="train", streaming=True)  # will start loading the data when iterated over
for example in imagenet:
    print(example)
    break
```

Streaming позволяет читать данные из сети, не записывая файлы на диск. Например, можно стримить датасеты, состоящие из нескольких шардов, каждый из которых занимает сотни гигабайт, как [C4](https://huggingface.co/datasets/c4) или [LAION-2B](https://huggingface.co/datasets/laion/laion2B-en). Подробнее о том, как стримить датасет, читайте в [Dataset Streaming Guide](./stream).

Однако это не единственное различие, поскольку «ленивое» поведение `IterableDataset` проявляется и при создании и обработке набора данных.

## Создание наборов данных в стиле map и итерируемых наборов данных

Вы можете создать [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) с помощью списков или словарей, при этом данные полностью преобразуются в формат Arrow, что позволяет легко обращаться к любой строке:
```python
my_dataset = Dataset.from_dict({"col_1": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]})
print(my_dataset[0])
```

Чтобы создать `IterableDataset`, с другой стороны, необходимо указать «ленивый» способ загрузки данных. В Python для этого обычно используют генераторные функции. Эти функции `yield` по одному примеру за раз, что означает, что вы не можете получить доступ к строке, нарезав её, как в обычном `Dataset`:
```python
def my_generator(n):
    for i in range(n):
        yield {"col_1": i}

my_iterable_dataset = IterableDataset.from_generator(my_generator, gen_kwargs={"n": 10})
for example in my_iterable_dataset:
    print(example)
    break
```

## Загрузка локальных файлов целиком и по частям

Локальные или удалённые файлы данных можно преобразовать в набор данных Arrow [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) с помощью [load_dataset()](/docs/datasets/v5.2.0/ru/package_reference/loading_methods#datasets.load_dataset):
```python
data_files = {"train": ["path/to/data.csv"]}
my_dataset = load_dataset("csv", data_files=data_files, split="train")
print(my_dataset[0])
```

Однако для этого требуется этап преобразования из формата CSV в формат Arrow, что занимает время и место на диске, если ваш набор данных большой.

Чтобы сэкономить место на диске и пропустить этап конвертации, можно определить `IterableDataset`, стримя данные непосредственно из локальных файлов. В этом случае данные читаются из локальных файлов по мере итерации по набору данных:

```python
data_files = {"train": ["path/to/data.csv"]}
my_iterable_dataset = load_dataset("csv", data_files=data_files, split="train", streaming=True)
for example in my_iterable_dataset:  # this reads the CSV file progressively as you iterate over the dataset
    print(example)
    break
```

Поддерживается множество форматов файлов, таких как CSV, JSONL и Parquet, а также изображения и аудиофайлы. Дополнительную информацию можно найти в соответствующих руководствах по загрузке [табличных](./tabular_load), [текстовых](./nlp_load), [изображений](./image_load) и [аудио](./audio_load]) данных.

## Обработка данных в режиме Eager и обработка данных в режиме Lazy

При обработке объекта [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) с помощью [Dataset.map()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset.map) весь набор данных обрабатывается немедленно и возвращается. Это похоже на то, как, например, работает `pandas`.

```python
my_dataset = my_dataset.map(process_fn)  # process_fn is applied on all the examples of the dataset
print(my_dataset[0])
```

С другой стороны, в силу «ленивой» природы `IterableDataset` вызов [IterableDataset.map()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.map) не применяет вашу функцию `map` ко всему набору данных. Вместо этого ваша функция `map` применяется на лету.

Поэтому вы можете объединить несколько этапов обработки в цепочку, и все они будут выполнены одновременно при начале итерации по набору данных:

```python
my_iterable_dataset = my_iterable_dataset.map(process_fn_1)
my_iterable_dataset = my_iterable_dataset.filter(filter_fn)
my_iterable_dataset = my_iterable_dataset.map(process_fn_2)

# process_fn_1, filter_fn and process_fn_2 are applied on-the-fly when iterating over the dataset
for example in my_iterable_dataset:  
    print(example)
    break
```

## Точное и быстрое приближённое перемешивание

При перемешивании [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) с помощью [Dataset.shuffle()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset.shuffle) выполняется точное перемешивание набора данных. Это работает так: берётся список индексов `[0, 1, 2, ... len(my_dataset) - 1]` и перемешивается. Затем обращение к `my_dataset[0]` возвращает строку и индекс, определённые первым элементом перемешанного отображения индексов:
```python
my_dataset = my_dataset.shuffle(seed=42)
print(my_dataset[0])
```

Поскольку в случае `IterableDataset` у нас нет случайного доступа к строкам, мы не можем использовать перемешанный список индексов и обращаться к строке в произвольной позиции. Это исключает возможность точного перемешивания. Вместо этого в [IterableDataset.shuffle()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.shuffle) используется быстрое приближённое перемешивание. Оно применяет буфер перемешивания для итеративного выборки случайных примеров из набора данных. Поскольку набор данных по-прежнему читается итеративно, это обеспечивает высокую скорость:
```python
my_iterable_dataset = my_iterable_dataset.shuffle(seed=42, buffer_size=100)
for example in my_iterable_dataset:
    print(example)
    break
```

Однако использование буфера перемешивания недостаточно для обеспечения удовлетворительного перемешивания при обучении моделей машинного обучения. Поэтому [IterableDataset.shuffle()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.shuffle) также перемешивает фрагменты набора данных, если он состоит из нескольких файлов или источников. Кроме того, буфер заполняется с использованием не более чем `max_buffer_input_shards` фрагментов за один раз (по умолчанию — 10), что значительно повышает качество перемешивания:

```python
# Stream from the internet
my_iterable_dataset = load_dataset("deepmind/code_contests", split="train", streaming=True)
my_iterable_dataset.num_shards  # 39

# Stream from local files
data_files = {"train": [f"path/to/data_{i}.csv" for i in range(1024)]}
my_iterable_dataset = load_dataset("csv", data_files=data_files, split="train", streaming=True)
my_iterable_dataset.num_shards  # 1024

# From a generator function
def my_generator(n, sources):
    for source in sources:
        for example_id_for_current_source in range(n):
            yield {"example_id": f"{source}_{example_id_for_current_source}"}

gen_kwargs = {"n": 10, "sources": [f"path/to/data_{i}" for i in range(1024)]}
my_iterable_dataset = IterableDataset.from_generator(my_generator, gen_kwargs=gen_kwargs)
my_iterable_dataset.num_shards  # 1024
```

> [!TIP]
>
> Если в вашем наборе данных мало файлов, вы можете повысить качество перемешивания, вызвав [reshard()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.reshard) перед перемешиванием. Например, для наборов данных в формате Parquet это перераспределяет данные по группам строк, а не по одному файлу на каждый шард, что позволяет получить значительно больше шардов для перемешивания.

## Различия в скорости

Обычные объекты [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) основаны на Arrow, который обеспечивает быстрый случайный доступ к строкам. Благодаря memory mapping и тому, что Arrow — это формат в памяти, чтение данных с диска не требует дорогостоящих системных вызовов и десериализации. При итерации с помощью цикла `for` загрузка данных происходит ещё быстрее, поскольку итерация выполняется по непрерывным батчам записей Arrow.

Однако как только ваш [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) получает отображение индексов (например, через [Dataset.shuffle()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset.shuffle)), скорость может снизиться в 10 раз. Это происходит потому, что появляется дополнительный шаг для получения индекса строки для чтения с использованием отображения индексов, и, что наиболее важно, вы больше не читаете непрерывные блоки данных. Чтобы восстановить скорость, вам нужно будет снова переписать весь набор данных на диске с помощью [Dataset.flatten_indices()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset.flatten_indices), что удаляет отображение индексов. Однако в зависимости от размера вашего набора данных это может занять много времени:

```python
my_dataset[0]  # fast
my_dataset = my_dataset.shuffle(seed=42)
my_dataset[0]  # up to 10x slower
my_dataset = my_dataset.flatten_indices()  # rewrite the shuffled dataset on disk as contiguous chunks of data
my_dataset[0]  # fast again
```

В этом случае мы рекомендуем перейти на [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset) и использовать его быстрый метод приблизительного перемешивания [IterableDataset.shuffle()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.shuffle). Он перемешивает только порядок шардов и добавляет буфер перемешивания к вашему набору данных, что позволяет сохранить оптимальную скорость работы набора данных. Вы также можете легко перемешать набор данных заново:

```python
for example in enumerate(my_iterable_dataset):  # fast
    pass

shuffled_iterable_dataset = my_iterable_dataset.shuffle(seed=42, buffer_size=100)

for example in enumerate(shuffled_iterable_dataset):  # as fast as before
    pass

shuffled_iterable_dataset = my_iterable_dataset.shuffle(seed=1337, buffer_size=100)  # reshuffling using another seed is instantaneous

for example in enumerate(shuffled_iterable_dataset):  # still as fast as before
    pass
```

Если вы используете свой набор данных в нескольких эпохах, эффективный сид для перемешивания порядка шардов в буфере перемешивания равен `seed + epoch`. Это упрощает перемешивание набора данных между эпохами:
```python
for epoch in range(n_epochs):
    my_iterable_dataset.set_epoch(epoch)
    for example in my_iterable_dataset:  # fast + reshuffled at each epoch using `effective_seed = seed + epoch`
        pass
```

Чтобы перезапустить итерацию набора данных map-стиля, достаточно просто пропустить первые примеры:

```python
my_dataset = my_dataset.select(range(start_index, len(dataset)))
```

Но если вы используете `DataLoader` с `Sampler`, вам следует сохранить состояние вашего сэмплера (возможно, вы написали собственный сэмплер, который позволяет возобновлять процесс).

С другой стороны, итерируемые наборы данных не предоставляют случайный доступ к конкретному индексу примера для возобновления с него. Однако вы можете использовать [IterableDataset.state_dict()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.state_dict) и [IterableDataset.load_state_dict()](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset.load_state_dict), чтобы возобновить работу с контрольной точки, аналогично тому, как это делается для моделей и оптимизаторов:

```python
>>> iterable_dataset = Dataset.from_dict({"a": range(6)}).to_iterable_dataset(num_shards=3)
>>> # save in the middle of training
>>> state_dict = iterable_dataset.state_dict()
>>> # and resume later
>>> iterable_dataset.load_state_dict(state_dict)
```

Под капотом итерируемый набор данных отслеживает текущий шард, из которого выполняется чтение, и индекс примера в текущем шарде, сохраняя эту информацию в `state_dict`.

Для возобновления с контрольной точки набор данных пропускает все сегменты, которые были прочитаны ранее, чтобы начать с текущего сегмента. Затем он читает сегмент и пропускает примеры, пока не достигнет точного примера из контрольной точки.

Поэтому перезапуск набора данных происходит достаточно быстро, так как он не перечитывает уже обработанные фрагменты. Тем не менее возобновление работы с набором данных обычно не происходит мгновенно, поскольку необходимо начать чтение с начала текущего фрагмента и пропустить примеры, пока не будет достигнута позиция контрольной точки.

Это можно использовать вместе с `StatefulDataLoader` из `torchdata`, см. [стриминг с PyTorch DataLoader](./use_with_pytorch#stream-data).

## Переключение с map-style на iterable

Если вы хотите воспользоваться «ленивым» поведением [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset) или их преимуществами в скорости, вы можете переключить ваш map-style [Dataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.Dataset) на [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset):
```python
my_iterable_dataset = my_dataset.to_iterable_dataset()
```

Если вы хотите перемешать набор данных или [использовать его с PyTorch DataLoader](./use_with_pytorch#stream-data), мы рекомендуем создать шардированный [IterableDataset](/docs/datasets/v5.2.0/ru/package_reference/main_classes#datasets.IterableDataset):
```python
my_iterable_dataset = my_dataset.to_iterable_dataset(num_shards=1024)
my_iterable_dataset.num_shards  # 1024
```

