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.
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. Utilizapip
(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
.
- A biblioteca Ultralytics :
-
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.
- Para este tutorial, todos os ficheiros de anotação encontram-se na pasta
Geração de vectores de características para o conjunto de dados de deteção de objectos
-
Começa por criar um novo ficheiro Python e importa as bibliotecas necessárias.
-
Prossegue para recuperar todos os ficheiros de etiquetas do teu conjunto de dados.
-
Agora, lê o conteúdo do ficheiro YAML do conjunto de dados e extrai os índices das etiquetas das classes.
-
Inicializa um ficheiro vazio
pandas
DataFrame. -
Conta as instâncias de cada etiqueta de classe presente nos ficheiros de anotação.
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`
-
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
-
Agora vamos utilizar o
KFold
classe desklearn.model_selection
para gerark
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
ondeM
é um número inteiro escolhido, podes obter resultados repetíveis.
- Definição
- Importante:
-
O conjunto de dados foi agora dividido em
k
dobras, cada uma com uma lista detrain
eval
índices. Constrói um DataFrame para apresentar estes resultados de forma mais clara. -
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 notrain
.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.
-
Em seguida, criamos os directórios e os ficheiros YAML do conjunto de dados para cada divisão.
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)
-
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.
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
-
Primeiro, carrega o modelo YOLO .
-
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
ename
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!