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?
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:
- 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.