Como criar K-Fold cross-validation na mão em Python

Saiba o que é e como implementar esse processo importante na validação de modelos

Photo by Charles 🇵🇭 on Unsplash

Um dos métodos mais importante para avaliação de modelos de machine learning é o cross-validation que, em resumo, o que ele faz é dividir um dataset em conjuntos de treino e teste, usando o conjunto de treino para treinar o modelo e o conjunto de teste para avaliar quão bom o modelo generaliza para dados que ele ainda não viu.

Motivação

Photo by Fab Lentz on Unsplash

K-fold é o método de cross-validation mais conhecido e utilizado. O método consiste em dividir o dataset em k partes, usando k-1 partes para treino e a parte remanescente para teste, fazendo isso k vezes. Em cada uma das k vezes, testa-se o modelo com um fold diferente calculando a métrica escolhida para avaliação do modelo. Ao final do processo, teremos k medidas da métrica de avaliação escolhida, com as quais geralmente calculamos a média e o desvio-padrão.

Bibliotecas como Scikit-Learn e MLlib, por exemplo, oferecem funções super otimizadas para realização de tais tarefas, por isso são extremamente recomendadas para códigos em produção. Contudo, isso não exime um aspirante a cientista de dados de aprender como implementar um K-fold cross-validation. Implementar uma funcionalidade ou rotina que já existe em bibliotecas é um ótimo exercício para verificação de entendimento dos fundamentos e também uma excelente oportunidade de aprofundamento das habilidades de programação, que estão sendo cada vez mais exigidas por empresas em seus processos de seleção para cientista de dados.

Mão na Massa!

Photo by Mimi Thian on Unsplash

Dado um dataset que desejamos usar para treinar e avaliar o nosso modelo, para usarmos o k-fold precisamos pegar apenas os seus índices. Logo, o nosso input será o conjunto dos índices do dataset, que pode ser uma lista em python, e o número k que nos indica em quantas partes dividir e quantas vezes treinar e avaliar o nosso modelo (número de folds). Usaremos um dataset com 10 linhas para mantermos a simplicidade e k = 2.

indices = list(range(10))
k = 2

O próximo passo é determinar o tamanho de cada subset da nossa lista de índices. No nosso caso, como temos uma lista com 10 elementos e k = 2, cada subset terá 5 elementos. Uma forma de escrever isso em Python é:

subset_size = round(len(indices) / k)

Dado que já temos o tamanho de cada subset para o k e o tamanho do dataset, podemos criar uma nova lista, em que cada elemento dela será um dos subsets da lista indices. Daremos à essa nova lista o nome sugestivo de subsets:

subsets = [indices[x:x+subset_size] for x in range(0, len(indices), subset_size)]

Agora que temos os 2 subsets salvos na lista subsets, podemos usá-los para criar 2 folds com os conjuntos de treino e teste para serem usados na avaliação do nosso modelo:

kfolds = []
for i in range(k):
test = subsets[i]
train = []
for subset in subsets:
if subset != test:
train.append(subset)
kfolds.append((train, test))
# output:
# kfolds = [([0,1,2,3,4],[5,6,7,8,9]), ([5,6,7,8,9],[0,1,2,3,4])]

A lista kfolds possui todos os índices dos conjuntos de treino e teste agrupados em tuplas. Cada tupla, ou fold, possui um conjunto de treino (primeiro elemento da tupla) e um conjunto de teste (segundo elemento da tupla). Logo, basta iterarmos sobre a lista kfold e em cada iteração, treinarmos o nosso modelo no primeiro elemento da tupla e testarmos no segundo elemento da tupla.

Abaixo está o código encapsulado em uma função conveniente chamada de kfoldcv:

def kfoldcv(indices, k):

size = len(indices)
subset_size = round(size / k)

subsets = [indices[x:x+subset_size] for x in range(0, len(indices), subset_size)]
    kfolds = []
for i in range(k):
test = subsets[i]
train = []
for subset in subsets:
if subset != test:
train.append(subset)
kfolds.append((train,test))

return kfolds
Acesse o post e saiba com contribuir para o Data Hackers

Uma observação muito importante a ser feita: quando treinar um modelo de Machine Learning, ao selecionarmos uma parte do dataset para treino e outra para teste, é preciso garantir que esses conjuntos representem amostras cuidadosamente retiradas do nosso conjunto total de dados. A maneira mais simples de se fazer isso é dar um shuffle nos índices para que tenhamos uma garantia mínima de que o modelo não irá aprender alguma relação de ordem entrem os registros do dataset. Se você leu com calma até aqui, provavelmente percebeu que a nossa função kfoldcv não tem uma linha em que embaralhamos os índices do dataframe. Para nossa sorte, a função built-in do Python, random, nos ajuda a fazer isso:

import random
def kfoldcv(indices, k = 10, seed = 42):

size = len(indices)
subset_size = round(size / k)
random.Random(seed).shuffle(indices)
    subsets = [indices[x:x+subset_size] for x in range(0, len(indices), subset_size)]
    kfolds = []
for i in range(k):
test = subsets[i]
train = []
for subset in subsets:
if subset != test:
train.append(subset)
kfolds.append((train,test))

return kfolds

Na função acima incluímos também, para efeito de reproducibilidade, o parâmetro seed, que garante que se duas pessoas pessoas diferentes usarem o mesmo dataset e o mesmo número de folds (k), mantendo a mesma seed, ambas irão obter o mesmo resultado.

Finalizando

Claro que bibliotecas como Scikit-Learn, MLlib, Pandas e companhia estão aí para nos ajudar e devemos tirar o máximo proveito delas, mas nada impede de tentarmos ir mais a fundo no entendimento de algumas funcionalidades oferecidas por essas bibliotecas, indo além do entendimento teórico e tentando implementar um código. Muito provavelmente a nossa versão da funcionalidade não será a mais otimizada e recomendada para ser posta em produção, mas com certeza sairemos desse processo com um conhecimento mais profundo.


Fiquem a vontade para compartilhar suas opiniões aqui nos comentários ou me adicionando no Linkedin.