Salta para o conteúdo

K-Fold Cross Validation com Ultralytics

Introdução

This comprehensive guide illustrates the implementation of K-Fold Cross Validation for object detection datasets within the Ultralytics ecosystem. We'll leverage the YOLO detection format and key Python libraries such as sklearn, pandas, and PyYaml to guide you through the necessary setup, the process of generating feature vectors, and the execution of a K-Fold dataset split.

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

Whether your project involves the Fruit Detection dataset or a custom data source, this tutorial aims to help you comprehend and apply K-Fold Cross Validation to bolster the reliability and robustness of your machine learning models. While we're applying 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 classeContagem de instâncias
Apple7049
Uvas7202
Ananás1613
Laranja15549
Banana3536
Melancia1976
  • 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. Comece criando um novo example.py Python arquivo para as etapas abaixo.

  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 = [label.stem for label 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 line in lines:
            # classes for YOLO label uses integer at first position of each line
            lbl_counter[int(line.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 = YOLO(weights_path, task="detect")
        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!

FAQ

O que é o K-Fold Cross Validation e porque é que é útil na deteção de objectos?

K-Fold Cross Validation is a technique where the dataset is divided into 'k' subsets (folds) to evaluate model performance more reliably. Each fold serves as both training and validation data. In the context of object detection, using K-Fold Cross Validation helps to ensure your Ultralytics YOLO model's performance is robust and generalizable across different data splits, enhancing its reliability. For detailed instructions on setting up K-Fold Cross Validation with Ultralytics YOLO, refer to K-Fold Cross Validation with Ultralytics.

Como é que implemento a validação cruzada K-Fold utilizando Ultralytics YOLO ?

Para implementar a validação cruzada K-Fold com Ultralytics YOLO , tens de seguir estes passos:

  1. Verifica se as anotações estão no formato de deteçãoYOLO .
  2. Utiliza as bibliotecas Python como sklearn, pandase pyyaml.
  3. Cria vectores de características a partir do teu conjunto de dados.
  4. Divide o teu conjunto de dados utilizando KFold de sklearn.model_selection.
  5. Treina o modelo YOLO em cada divisão.

Para obter um guia completo, consulte a secção K-Fold Dataset Split na nossa documentação.

Por que razão devo utilizar Ultralytics YOLO para a deteção de objectos?

Ultralytics YOLO offers state-of-the-art, real-time object detection with high accuracy and efficiency. It's versatile, supporting multiple computer vision tasks such as detection, segmentation, and classification. Additionally, it integrates seamlessly with tools like Ultralytics HUB for no-code model training and deployment. For more details, explore the benefits and features on our Ultralytics YOLO page.

Como posso garantir que as minhas anotações estão no formato correto para Ultralytics YOLO ?

Your annotations should follow the YOLO detection format. Each annotation file must list the object class, alongside its bounding box coordinates in the image. The YOLO format ensures streamlined and standardized data processing for training object detection models. For more information on proper annotation formatting, visit the YOLO detection format guide.

Posso utilizar a validação cruzada K-Fold com conjuntos de dados personalizados para além da deteção de frutos?

Sim, podes utilizar a validação cruzada K-Fold com qualquer conjunto de dados personalizado, desde que as anotações estejam no formato de deteção YOLO . Substitui os caminhos do conjunto de dados e os rótulos de classe por aqueles específicos do seu conjunto de dados personalizado. Essa flexibilidade garante que qualquer projeto de deteção de objetos possa se beneficiar da avaliação robusta de modelos usando o K-Fold Cross Validation. Para um exemplo prático, consulta a nossa secção Gerar vectores de características.

📅 C riado há 1 ano ✏️ Atualizado há 12 dias

Comentários