Salta para o conte√ļdo

K-Fold Cross Validation com Ultralytics

Introdução

Este guia abrangente ilustra a implementação do K-Fold Cross Validation para conjuntos de dados de deteção de objectos no ecossistema Ultralytics . Aproveitaremos o formato de deteção YOLO e as principais bibliotecas Python , como sklearn, pandas e PyYaml, para te guiar através da configuração necessária, do processo de geração de vectores de características e da execução de uma divisão de conjunto de dados K-Fold.

Visão geral da validação cruzada K-Fold

Quer o teu projeto envolva o conjunto de dados Fruit Detection ou uma fonte de dados personalizada, este tutorial tem como objetivo ajudar-te a compreender e aplicar o K-Fold Cross Validation para refor√ßar a fiabilidade e a robustez dos teus modelos de aprendizagem autom√°tica. Enquanto estiveres a aplicar k=5 para este tutorial, tem em mente que o n√ļmero ideal de dobras pode variar dependendo do teu conjunto de dados e das especificidades do teu projeto.

Sem mais demoras, vamos l√°!

Instalação

  • As tuas anota√ß√Ķes devem estar no formato de dete√ß√£oYOLO .

  • Este guia assume que os ficheiros de anota√ß√Ķes est√£o dispon√≠veis localmente.

  • Para a nossa demonstra√ß√£o, utilizamos o conjunto de dados Fruit Detection.

    • Este conjunto de dados cont√©m um total de 8479 imagens.
    • Inclui 6 etiquetas de classe, cada uma com a contagem total de inst√Ęncias listada abaixo.
Etiqueta de classe Contagem de inst√Ęncias
Apple 7049
Uvas 7202
Anan√°s 1613
Laranja 15549
Banana 3536
Melancia 1976
  • Necess√°rio Python pacotes incluem:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • Este tutorial funciona com k=5 dobras. No entanto, deves determinar o melhor n√ļmero de dobras para o teu conjunto de dados espec√≠fico.

  • Inicia um novo ambiente virtual Python (venv) para o teu projeto e ativa-o. Utiliza pip (ou o teu gestor de pacotes preferido) para instalar:

    • A biblioteca Ultralytics : pip install -U ultralytics. Em alternativa, podes clonar o ficheiro oficial repo.
    • Scikit-learn, pandas e PyYAML: pip install -U scikit-learn pandas pyyaml.
  • Verifica se as tuas anota√ß√Ķes est√£o no formato de dete√ß√£oYOLO .

    • Para este tutorial, todos os ficheiros de anota√ß√£o encontram-se na pasta Fruit-Detection/labels diret√≥rio.

Geração de vectores de características para o conjunto de dados de deteção de objectos

  1. Start by creating a new example.py Python file for the steps below.

  2. Prossegue para recuperar todos os ficheiros de etiquetas do teu conjunto de dados.

    from pathlib import Path
    
    dataset_path = Path("./Fruit-detection")  # replace with 'path/to/dataset' for your custom data
    labels = sorted(dataset_path.rglob("*labels/*.txt"))  # all data in 'labels'
    
  3. Agora, l√™ o conte√ļdo do ficheiro YAML do conjunto de dados e extrai os √≠ndices das etiquetas das classes.

    yaml_file = "path/to/data.yaml"  # your data YAML with data directories and names dictionary
    with open(yaml_file, "r", encoding="utf8") as y:
        classes = yaml.safe_load(y)["names"]
    cls_idx = sorted(classes.keys())
    
  4. Inicializa um ficheiro vazio pandas DataFrame.

    import pandas as pd
    
    indx = [l.stem for l in labels]  # uses base filename as ID (no extension)
    labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
    
  5. Conta as inst√Ęncias de cada etiqueta de classe presente nos ficheiros de anota√ß√£o.

    from collections import Counter
    
    for label in labels:
        lbl_counter = Counter()
    
        with open(label, "r") as lf:
            lines = lf.readlines()
    
        for l in lines:
            # classes for YOLO label uses integer at first position of each line
            lbl_counter[int(l.split(" ")[0])] += 1
    
        labels_df.loc[label.stem] = lbl_counter
    
    labels_df = labels_df.fillna(0.0)  # replace `nan` values with `0.0`
    
  6. Segue-se uma vista de amostra do DataFrame preenchido:

                                                           0    1    2    3    4    5
    '0000a16e4b057580_jpg.rf.00ab48988370f64f5ca8ea4...'  0.0  0.0  0.0  0.0  0.0  7.0
    '0000a16e4b057580_jpg.rf.7e6dce029fb67f01eb19aa7...'  0.0  0.0  0.0  0.0  0.0  7.0
    '0000a16e4b057580_jpg.rf.bc4d31cdcbe229dd022957a...'  0.0  0.0  0.0  0.0  0.0  7.0
    '00020ebf74c4881c_jpg.rf.508192a0a97aa6c4a3b6882...'  0.0  0.0  0.0  1.0  0.0  0.0
    '00020ebf74c4881c_jpg.rf.5af192a2254c8ecc4188a25...'  0.0  0.0  0.0  1.0  0.0  0.0
     ...                                                  ...  ...  ...  ...  ...  ...
    'ff4cd45896de38be_jpg.rf.c4b5e967ca10c7ced3b9e97...'  0.0  0.0  0.0  0.0  0.0  2.0
    'ff4cd45896de38be_jpg.rf.ea4c1d37d2884b3e3cbce08...'  0.0  0.0  0.0  0.0  0.0  2.0
    'ff5fd9c3c624b7dc_jpg.rf.bb519feaa36fc4bf630a033...'  1.0  0.0  0.0  0.0  0.0  0.0
    'ff5fd9c3c624b7dc_jpg.rf.f0751c9c3aa4519ea3c9d6a...'  1.0  0.0  0.0  0.0  0.0  0.0
    'fffe28b31f2a70d4_jpg.rf.7ea16bd637ba0711c53b540...'  0.0  6.0  0.0  0.0  0.0  0.0
    

As linhas indexam os ficheiros de etiquetas, cada um correspondendo a uma imagem no teu conjunto de dados, e as colunas correspondem aos índices das etiquetas de classe. Cada linha representa um pseudo-vetor de características, com a contagem de cada etiqueta de classe presente no teu conjunto de dados. Esta estrutura de dados permite a aplicação de K-Fold Cross Validation a um conjunto de dados de deteção de objectos.

Divis√£o do conjunto de dados K-Fold

  1. Agora vamos utilizar o KFold classe de sklearn.model_selection para gerar k divide o conjunto de dados.

    • Importante:
      • Defini√ß√£o shuffle=True garante uma distribui√ß√£o aleat√≥ria de classes nas tuas divis√Ķes.
      • Por defini√ß√£o random_state=M onde M √© um n√ļmero inteiro escolhido, podes obter resultados repet√≠veis.
    from sklearn.model_selection import KFold
    
    ksplit = 5
    kf = KFold(n_splits=ksplit, shuffle=True, random_state=20)  # setting random_state for repeatable results
    
    kfolds = list(kf.split(labels_df))
    
  2. O conjunto de dados foi agora dividido em k dobras, cada uma com uma lista de train e val índices. Constrói um DataFrame para apresentar estes resultados de forma mais clara.

    folds = [f"split_{n}" for n in range(1, ksplit + 1)]
    folds_df = pd.DataFrame(index=indx, columns=folds)
    
    for idx, (train, val) in enumerate(kfolds, start=1):
        folds_df[f"split_{idx}"].loc[labels_df.iloc[train].index] = "train"
        folds_df[f"split_{idx}"].loc[labels_df.iloc[val].index] = "val"
    
  3. Agora vamos calcular a distribuição das etiquetas das classes para cada dobra como uma proporção das classes presentes em val para os presentes no train.

    fold_lbl_distrb = pd.DataFrame(index=folds, columns=cls_idx)
    
    for n, (train_indices, val_indices) in enumerate(kfolds, start=1):
        train_totals = labels_df.iloc[train_indices].sum()
        val_totals = labels_df.iloc[val_indices].sum()
    
        # To avoid division by zero, we add a small value (1E-7) to the denominator
        ratio = val_totals / (train_totals + 1e-7)
        fold_lbl_distrb.loc[f"split_{n}"] = ratio
    

    O cenário ideal é que todos os rácios de classe sejam razoavelmente semelhantes para cada divisão e entre classes. No entanto, isto estará sujeito às especificidades do teu conjunto de dados.

  4. Em seguida, criamos os directórios e os ficheiros YAML do conjunto de dados para cada divisão.

    import datetime
    
    supported_extensions = [".jpg", ".jpeg", ".png"]
    
    # Initialize an empty list to store image file paths
    images = []
    
    # Loop through supported extensions and gather image files
    for ext in supported_extensions:
        images.extend(sorted((dataset_path / "images").rglob(f"*{ext}")))
    
    # Create the necessary directories and dataset YAML files (unchanged)
    save_path = Path(dataset_path / f"{datetime.date.today().isoformat()}_{ksplit}-Fold_Cross-val")
    save_path.mkdir(parents=True, exist_ok=True)
    ds_yamls = []
    
    for split in folds_df.columns:
        # Create directories
        split_dir = save_path / split
        split_dir.mkdir(parents=True, exist_ok=True)
        (split_dir / "train" / "images").mkdir(parents=True, exist_ok=True)
        (split_dir / "train" / "labels").mkdir(parents=True, exist_ok=True)
        (split_dir / "val" / "images").mkdir(parents=True, exist_ok=True)
        (split_dir / "val" / "labels").mkdir(parents=True, exist_ok=True)
    
        # Create dataset YAML files
        dataset_yaml = split_dir / f"{split}_dataset.yaml"
        ds_yamls.append(dataset_yaml)
    
        with open(dataset_yaml, "w") as ds_y:
            yaml.safe_dump(
                {
                    "path": split_dir.as_posix(),
                    "train": "train",
                    "val": "val",
                    "names": classes,
                },
                ds_y,
            )
    
  5. Por fim, copia as imagens e as etiquetas para o respetivo diretório ('train' ou 'val') para cada divisão.

    • NOTA: O tempo necess√°rio para esta parte do c√≥digo varia de acordo com o tamanho do conjunto de dados e o hardware do sistema.
    import shutil
    
    for image, label in zip(images, labels):
        for split, k_split in folds_df.loc[image.stem].items():
            # Destination directory
            img_to_path = save_path / split / k_split / "images"
            lbl_to_path = save_path / split / k_split / "labels"
    
            # Copy image and label files to new directory (SamefileError if file already exists)
            shutil.copy(image, img_to_path / image.name)
            shutil.copy(label, lbl_to_path / label.name)
    

Guardar registos (opcional)

Opcionalmente, podes guardar os registos dos DataFrames de divisão K-Fold e de distribuição de etiquetas como ficheiros CSV para referência futura.

folds_df.to_csv(save_path / "kfold_datasplit.csv")
fold_lbl_distrb.to_csv(save_path / "kfold_label_distribution.csv")

Treina YOLO utilizando K-Fold Data Splits

  1. Primeiro, carrega o modelo YOLO .

    from ultralytics import YOLO
    
    weights_path = "path/to/weights.pt"
    model = YOLO(weights_path, task="detect")
    
  2. Em seguida, itera sobre os ficheiros YAML do conjunto de dados para executar o treino. Os resultados ser√£o salvos em um diret√≥rio especificado pelo par√Ęmetro project e name argumentos. Por defeito, este diret√≥rio √© 'exp/runs#' onde # √© um √≠ndice inteiro.

    results = {}
    
    # Define your additional arguments here
    batch = 16
    project = "kfold_demo"
    epochs = 100
    
    for k in range(ksplit):
        dataset_yaml = ds_yamls[k]
        model.train(data=dataset_yaml, epochs=epochs, batch=batch, project=project)  # include any train arguments
        results[k] = model.metrics  # save output metrics for further analysis
    

Conclus√£o

Neste guia, explor√°mos o processo de utiliza√ß√£o da valida√ß√£o cruzada K-Fold para treinar o modelo de dete√ß√£o de objectos YOLO . Aprendemos a dividir o nosso conjunto de dados em K parti√ß√Ķes, garantindo uma distribui√ß√£o de classes equilibrada entre as diferentes dobras.

Tamb√©m explor√°mos o procedimento de cria√ß√£o de DataFrames de relat√≥rio para visualizar as divis√Ķes de dados e as distribui√ß√Ķes de etiquetas nestas divis√Ķes, fornecendo-nos uma vis√£o clara da estrutura dos nossos conjuntos de treino e valida√ß√£o.

Opcionalmente, guard√°mos os nossos registos para refer√™ncia futura, o que pode ser particularmente √ļtil em projectos de grande escala ou na resolu√ß√£o de problemas de desempenho do modelo.

Por fim, implementámos o treino do modelo real utilizando cada divisão num ciclo, guardando os resultados do treino para análise e comparação posteriores.

Esta t√©cnica de valida√ß√£o cruzada K-Fold √© uma forma robusta de tirar o m√°ximo partido dos dados dispon√≠veis e ajuda a garantir que o desempenho do modelo √© fi√°vel e consistente em diferentes subconjuntos de dados. Isto resulta num modelo mais generaliz√°vel e fi√°vel que tem menos probabilidades de se ajustar excessivamente a padr√Ķes de dados espec√≠ficos.

Lembra-te de que, embora tenhamos utilizado YOLO neste guia, estes passos são, na sua maioria, transferíveis para outros modelos de aprendizagem automática. A compreensão destes passos permite-te aplicar a validação cruzada de forma eficaz nos teus próprios projectos de aprendizagem automática. Boa programação!



Created 2023-11-12, Updated 2024-06-18
Authors: glenn-jocher (9), IvorZhu331 (1), Burhan-Q (1)

Coment√°rios