Работа с датасетами в ClearML

В инструкции мы рассматриваем примеры работы с датасетами в ClearML на примере датасета изображений CIDFAR10.

Для работы с датасетами в ClearML используется класс Dataset. В ClearML WebApp датасет отображается как эксперимент (Experiment) с типом Data Processing.

Перед началом работы с датасетами нужно подготовить окружение.

Подготовить окружение

  1. Установите систему для управления пакетами conda.

  2. Создайте окружение для conda:

    conda env create --file environment.yml
    
Файл environment.yml
name: clearml_datasets
channels:
  - defaults
dependencies:
  - ca-certificates=2022.4.26
  - certifi=2022.5.18.1
  - libffi=3.3
  - ncurses=6.3
  - openssl=1.1.1o
  - pip=21.2.4
  - python=3.8.13
  - readline=8.1.2
  - setuptools=61.2.0
  - sqlite=3.38.3
  - tk=8.6.12
  - wheel=0.37.1
  - xz=5.2.5
  - zlib=1.2.12
  - pip:
    - attrs==21.4.0
    - boto3==1.24.22
    - botocore==1.27.22
    - charset-normalizer==2.0.12
    - clearml==1.5.0
    - furl==2.1.3
    - future==0.18.2
    - idna==3.3
    - importlib-resources==5.8.0
    - jsonschema==4.6.0
    - numpy==1.22.4
    - orderedmultidict==1.0.1
    - pathlib2==2.3.7.post1
    - pillow==9.1.1
    - psutil==5.9.1
    - pyjwt==2.4.0
    - pyparsing==3.0.9
    - pyrsistent==0.18.1
    - python-dateutil==2.8.2
    - pyyaml==6.0
    - python-mnist==0.7
    - requests==2.28.0
    - tqdm==4.64.0
    - six==1.16.0
    - urllib3==1.26.9
    - zipp==3.8.0
  1. Активируйте окружение:

    conda activate clearml_datasets
    
  2. Проверьте соединение ClearML SDK с ClearML Server:

    clearml-init
    
  3. Убедитесь, в конфигурационном файле clearml.conf указан правильный URL ClearML Server вида http://yourdomain.cmlp.selectel.ru. Подробнее в документации ClearML.

  4. Проверьте, что в конфигурационном файле clearml.conf описано подключение к хранилищу. Мы рекомендуем подключить ClearML к объектному хранилищу Selectel.

Подготовить данные

Перед использованием примеров загрузите датасет CIDFAR10 и подготовьте его к работе.

Пример скрипта для подготовки данных
import os
import numpy as np
import tqdm
import shutil
import requests
from typing import Dict, Tuple, List, Text
from PIL import Image

from source.auxiliary_code.global_config import get_temp_data_path


def unpickle(file: Text) -> Dict:
    import pickle
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict


def prepare_temp_folder(dataset_name: Text) -> Text:
    temp_folder_path = get_temp_data_path()

    if not os.path.exists(temp_folder_path):
        os.mkdir(temp_folder_path)

    if not os.path.exists(os.path.join(temp_folder_path, dataset_name)):
        os.mkdir(os.path.join(temp_folder_path, dataset_name))

    return temp_folder_path


def get_data_archive(temp_folder: Text) -> Text:
    archive_name = "cifar-10-python.tar.gz"
    archive_url = "https://www.cs.toronto.edu/~kriz/{}".format(archive_name)

    archive_path = os.path.join(temp_folder, archive_name)

    print("Downloading data archive from {}".format(archive_url))

    if not os.path.exists(archive_path):
        r = requests.get(archive_url)

        open(os.path.join(temp_folder, archive_name), 'wb').write(r.content)

    return archive_path


def unzip_data(archive_path: Text) -> Text:
    data_folder = archive_path.split("/")[-1].split(".")[0]

    extract_dir = "{}/{}".format("/".join(archive_path.split("/")[:-1]), data_folder)

    print("Extracting data archive to {}".format(extract_dir))

    shutil.unpack_archive(archive_path, extract_dir)

    return extract_dir


def transform_images(batches: List[Text]) -> List[Dict]:
    images = []

    for batch_path in batches:
        batch = unpickle(batch_path)

        for i in tqdm.tqdm(range(len(batch[b'data']))):
            images.append({
                "image": Image.fromarray(np.reshape(batch[b'data'][i], (32, 32, 3), order='F')),
                "label": str(batch[b'labels'][i]),
                "file_name": batch[b'filenames'][i].decode('utf-8')
            })

    return images


def save_images(images: List[Dict], folder: Text) -> List[Text]:
    image_paths = []
    for i in tqdm.tqdm(range(len(images))):
        if not os.path.exists(os.path.join(folder, images[i]["label"])):
            os.mkdir(os.path.join(folder, images[i]["label"]))

        images[i]["image"].save(os.path.join(folder, images[i]["label"], images[i]["file_name"]))
        image_paths.append(os.path.join(folder, images[i]["label"], images[i]["file_name"]))

    return image_paths


def extract(dataset_name: Text) -> Text:
    temp_folder_path = prepare_temp_folder(dataset_name)

    archive_path = get_data_archive(os.path.join(temp_folder_path, dataset_name))

    data_path = unzip_data(archive_path)

    return data_path


def transform(data_path: Text) -> Tuple[List, List]:
    test_batches = [
        os.path.join(data_path, "cifar-10-batches-py", "test_batch")
    ]
    train_batches = [
        os.path.join(data_path, "cifar-10-batches-py", "data_batch_1"),
        os.path.join(data_path, "cifar-10-batches-py", "data_batch_2"),
        os.path.join(data_path, "cifar-10-batches-py", "data_batch_3"),
        os.path.join(data_path, "cifar-10-batches-py", "data_batch_4"),
        os.path.join(data_path, "cifar-10-batches-py", "data_batch_5")
    ]

    print("Extracting train images from pickle batches")
    train_images = transform_images(train_batches)
    print("Extracting test images from pickle batches")
    test_images = transform_images(test_batches)

    return test_images, train_images


def load(images: Tuple[List, List], dataset_name: Text) -> Tuple[List, List]:
    test_images, train_images = images

    temp_folder_path = get_temp_data_path()

    dataset_folder = os.path.join(temp_folder_path, dataset_name)

    dataset_train_folder = os.path.join(dataset_folder, "train")
    dataset_test_folder = os.path.join(dataset_folder, "test")

    if not os.path.exists(dataset_train_folder):
        os.mkdir(dataset_train_folder)
    if not os.path.exists(dataset_test_folder):
        os.mkdir(dataset_test_folder)

    print("Saving train images to {}".format(dataset_train_folder))
    train_image_paths = save_images(train_images, dataset_train_folder)
    print("Saving test images to {}".format(dataset_test_folder))
    test_image_paths = save_images(test_images, dataset_test_folder)

    return train_image_paths, test_image_paths


if __name__ == "__main__":
    data_path = extract("CIFAR10")

    images = transform(data_path)

    res = load(images, "CIFAR10")

Создать датасет

Для работы с датасетами в ClearML используется класс Dataset.

Пример скрипта для создания Datset:

from clearml import Dataset

from source.auxiliary_code import global_config

if __name__ == "__main__":
    dataset_name = 'CIFAR10'

    cifar10_dataset = Dataset.create(dataset_project=global_config.DATASET_PROJECT, dataset_name=dataset_name)

    for dataset in Dataset.list_datasets(dataset_project=global_config.DATASET_PROJECT, only_completed=False):
        print(dataset)

Загрузить данные в датасет

В существующий датасет можно загрузить новые данные.

В примере для хранения данных используется File Server. Вы можете сконфигурировать ClearML Server для работы с любыми хранилищами, например, подключить объектное хранилище Selectel.

Пример скрипта для загрузки данных в датасет:

import os

from clearml import Dataset

from source.auxiliary_code import global_config


if __name__ == "__main__":
    dataset_name = 'CIFAR10'

    cifar10_dataset = Dataset.get(dataset_project=global_config.DATASET_PROJECT, dataset_name=dataset_name)

    data_path = os.path.join(global_config.get_temp_data_path(), dataset_name)

    cifar10_dataset.add_files(
        path=os.path.join(data_path, 'train'),
        dataset_path=os.path.join(dataset_name, 'train'),
        verbose=True
    )

    Dataset.upload(cifar10_dataset, verbose=True)

Добавить метаданные для датасета

Для датасета можно добавить метаданные. В примере добавляются теги (Tags) — их можно использовать для фильтрации датасетов.

Пример скрипта для добавления тегов:

from clearml import Dataset

from source.auxiliary_code import global_config


if __name__ == "__main__":
    dataset_name = 'CIFAR10'

    cifar10_dataset = Dataset.get(dataset_project=global_config.DATASET_PROJECT, dataset_name=dataset_name)

    cifar10_dataset.add_tags(['image', 'classification', 'example', 'small'])

    for dataset in Dataset.list_datasets(dataset_project=global_config.DATASET_PROJECT, only_completed=False):
        print(dataset)