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. 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 = [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!

FAQ

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

A validação cruzada K-Fold é uma técnica em que o conjunto de dados é dividido em "k" subconjuntos (dobras) para avaliar o desempenho do modelo de forma mais fiável. Cada dobra serve como dados de treino e de validação. No contexto da deteção de objectos, a utilização da validação cruzada K-Fold ajuda a garantir que o desempenho do teu modelo Ultralytics YOLO é robusto e generalizável em diferentes divisões de dados, aumentando a sua fiabilidade. Para obter instruções detalhadas sobre como configurar a validação cruzada K-Fold com Ultralytics YOLO , consulte Validação cruzada K-Fold com 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 oferece deteção de objectos de última geração e em tempo real com elevada precisão e eficiência. É versátil, suportando várias tarefas de visão computacional, como deteção, segmentação e classificação. Além disso, integra-se perfeitamente com ferramentas como Ultralytics HUB para treinamento e implantação de modelos sem código. Para mais detalhes, explora os benefícios e características na nossa páginaUltralytics YOLO .

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

As tuas anotações devem seguir o formato de deteção YOLO . Cada ficheiro de anotação deve listar a classe do objeto, juntamente com as coordenadas da sua caixa delimitadora na imagem. O formato YOLO garante um processamento de dados simplificado e padronizado para treinar modelos de deteção de objectos. Para obter mais informações sobre a formatação adequada das anotações, visita o guia de formato de deteçãoYOLO .

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.



Criado em 2023-11-12, Atualizado em 2024-07-05
Autores: glenn-jocher (10), IvorZhu331 (1), Burhan-Q (1)

Comentários