Validação Holdout e geração de dados sintéticos

Como os dados sintéticos podem nos ajudar a entender melhor o funcionamento dos modelos de Machine Learning?

Gabriel Nobre
5 min readOct 19, 2023

Quem já não tentou estudar Machine Learning e teve que pegar um dataset do Kaggle ou de outro local e ele veio sujo, tendo que passar um bom tempo limpando enquanto poderia está testando e aprendendo os modelos. Bom, seus problemas acabaram hehehehe.

Essa ultima semana eu estava aprofundando os estudos e decidi utilizar fake data ou dados sintéticos gerados por máquina, justamente para isolar o aprendizado em ML, na área de Validação Holdout, Cross Validation e Fine Tuning.

Cuidados ao trabalhar com dados sintéticos

Em primeiro lugar, é importante alertar que o estudo com dados sintéticos deve ser utilizado com cautela e observado caso a caso, pois eles não trazem as complexidades de dados reais, como outliers, NaN, null entre outros. Podem ser gerados, mas ainda sim são dados sintéticos.

Segundo, é interessante utilizar esse tipo de dado quando você quer isolar o seu objeto de estudo de tal forma que consiga controlar as variáveis disponíveis. O exemplo aqui é para estudo da aplicação da técnica de validação holdout.

Também podemos usar para cross-validation, fine tunning, avaliação de métricas de algoritmos, entre outras aplicações.

Como construir dados sintéticos

Para isso, decidi utilizar a biblioteca sklear.dataset.

import pandas                       as pd
import numpy as np

from sklearn import datasets as ds
from sklearn import model_selection as ms
from sklearn import tree as tr
from sklearn import metrics as mt

from matplotlib import pyplot as plt

# DATASET 1
n_samples = 20000
n_features = 2
n_informative = 2
n_redundant = 0
random_state = 0

x, y = ds.make_classification(n_samples = n_samples,
n_features = n_features,
n_informative = n_informative,
n_redundant = n_redundant,
random_state = random_state)

Para a Validação Holdout é necessário separar o dataset em treino, teste e validação como abaixo:

  1. Na primeira separação eu isolo o teste do restante dos dados justamente para garantir a qualidade do modelo no futuro.
#Total (100%) -> Treino1(80%) e Teste(20%) (split = 0.2)
x_train, x_test, y_train, y_test = ms.train_test_split( x, y, test_size=0.2, random_state= random_state)

2. Na segunda separação eu isolo a validação para conseguir identificar os melhores parâmetros para treinar o modelo.

#Treino2 (80%) -> Treino(60%) e Validação(20%) (split = 0.25)
x_train, x_val, y_train, y_val = ms.train_test_split( x_train, y_train, test_size=0.25, random_state= random_state)

Após a separação, vamos para o primeiro treinamento e a escolha dos melhores parâmetros. Aqui é só uma exemplificação simples do cross-validadtion e para esse caso cada modelo irá requerer uma técnica.

# Modelo  -> Treino    -> Treinar o algoritmo

# Treinamento do Modelo + "Fine Tunning" (Escolha dos Melhores Parâmetros) Exemplo
values = [i for i in range (1,60)]
val_score = list()

for i in values:
model = tr.DecisionTreeClassifier(max_depth=i)
model.fit(x_train, y_train)

yhat_val = model.predict(x_val)
acc_val = mt.accuracy_score(y_val,yhat_val)
val_score.append(acc_val)

plt.plot(values, val_score,'-o',label='Validação')

Esses são os valores para cada nível de profundidade da árvore. No caso o melhor valor é a profundidade 9.

#Modelo  -> Validação -> Melhores parâmetros
model = tr.DecisionTreeClassifier(max_depth=9)
model.fit(x_train, y_train)

yhat_val = model.predict(x_val)
acc_val = mt.accuracy_score(y_val,yhat_val)
acc_val

O primeiro valor da validação foi de acc_val = 0.88025. Testei também com o valor de max_depth=1 e acc_val = 0.83675. Vale salientar que no estudo é interessante você brincar com esses valores afim de entender o comportamento do modelo.

Após a escolha do melhor parêmetro vamos para o teste do modelo com dados que ele nunca viu anterior mente.

#Treino (80%) -> Treino(60%) + Validação(20%)
train_80 = np.concatenate( (x_train, x_val) )
val_80 = np.concatenate( (y_train, y_val) )

#Treino Last -> Modelo + Melhor Parêmetro + Treino (80%)
model_last = tr.DecisionTreeClassifier(max_depth=9)
model_last.fit(train_80, val_80 )

yhat_test = model_last.predict(x_test)
acc_test = mt.accuracy_score(y_test, yhat_test)

#Modelo Last -> Teste(20%) -> Performance de Gêneralização (Métrica para reportar ao time de negócio)
acc_test

Neste caso o acc_test = 0.87975, sendo uma métrica muito próxima da métrica de validação. Desta forma posso concluir que o modelo pode está generalizando bem.

Mesmo fazendo isso tome muito cuidado, pois há diversas outras métricas que demos observer para que o modelo não cause overfitting em produção. O que estou mostrando serve para aprender a trabalhar com o modelo e não é um passo a passo de como fazer com dados reais.

Uma vez que o modelo esteja generalizando bem, podemos terminar seu treinamento com toda a base de dados e coloca-lo em produção.

#Treino ( Treino(60%) + Validação(20%) ) + Teste(20%)
train_100 = np.concatenate( (train_80, x_test) )
val_100 = np.concatenate( (val_80, y_test) )

# Modelo em Produção
model_last = tr.DecisionTreeClassifier(max_depth=9)
model_last.fit( train_100, val_100 )

Após o modelo em produção eu sugiro fazermos mais 2 testes 1 com 5% de novos dados em relação ao tamanho do dataset original e outro com 50% de dados novos, para vê como o modelo está se comportanto. Lembre-se de mudar a aleatoriedade na hora de gerar o data-set.

# Dataset em produção com os primeiro 5% dos dados
n_samples = 1000
n_features = 2
n_informative = 2
n_redundant = 0
random_state = 2

x_prod, y_prod = ds.make_classification(n_samples = n_samples,
n_features = n_features,
n_informative = n_informative,
n_redundant = n_redundant,
random_state = random_state)

Colocando os dados novos para o modelo prevê.

# Funcionamento do modelo em Produção
yhat_prod = model.predict(x_prod)
acc_prod = mt.accuracy_score(y_prod, yhat_prod)
acc_prod

O meu acc_prod = 0.874. A pontuação ainda está dentro de uma variação aceitável. Vamos ver como o modelo funciona para 50% de dados novos e com uma nova aleatoriedade, supondo que aquele comportamento do fenômeno foi sendo modificado ao longo do tempo.

# Dataset em produção com 50% de dados novos
n_samples = 10000
n_features = 2
n_informative = 2
n_redundant = 0
random_state = 13

x_prod2, y_prod2 = ds.make_classification(n_samples = n_samples,
n_features = n_features,
n_informative = n_informative,
n_redundant = n_redundant,
random_state = random_state)
yhat_prod2 = model.predict(x_prod2)
acc_prod2 = mt.accuracy_score(y_prod2, yhat_prod2)
acc_prod2

O resultado final para 50% de dados a mais é de acc_prod2 = 0.4729. Neste caso podemos observar 2 coisas, se o fenomeno for modificado um pouco que seja o modelo já não aguenta prevê, e se a quantidade de dados for muito grande ele acaba perdendo na acuracia.

E como resolver esse problema?

# Se o modelo não conseguir performar bem, então:
# Caso não esteja perfomando bem, verificar essas 3 métricas:
# 1. Distribuição da Variável resposta
# 2. Proporção de NA's
# 3. Outliers
# Troca o modelo e segue o passo a passo a cima

# Se não:
# Continua treinando o Modelo

Outras bibliotecas

Podemos utilizar bibliotecas complementares para ir montando um dataset que queremos. Existem outras duas bibliotecas que sugiro dê uma olhada, são a Mimessis, Faker.

Por hoje é só. Este é o link caso queira me acompanhar no linked-in.

--

--