River na Prática: Resolvendo um problema de classificação com Aprendizado de Máquina Online

nobrelucas

Lucas Nobre Barbosa

Posted on October 24, 2023

River na Prática: Resolvendo um problema de classificação com Aprendizado de Máquina Online

Requisitos

Para seguir esse tutorial espera-se que você tenha conhecimento suficiente da linguagem de programação Python e do uso de ferramentas como Google Colab ou Jupyter-notebook.

Ter lido os dois primeiros artigos da série de artigos sobre River que eu escrevi é desejável.

Instalação

A biblioteca River é feita para funcionar a partir do Python 3.8. A instalação pode ser feita usando o gerenciador de pacotes pip:

# Via notebook
!pip install river

# Via terminal
pip install river
Enter fullscreen mode Exit fullscreen mode

Problema de Classificação

Nesse tutorial vamos explorar um problema de classificação usando o famoso dataset titanic.

💡 O motivo de eu usar um dataset tradicional e em lote para um tutorial de aprendizado em streaming é puramente educacional. Além disso, como dito no artigo anterior, a biblioteca River tem ferramentas para ler conjuntos de dados em lote no formato de streaming para avaliação do modelo e para propósitos de estudo, como o desse tutorial.

💡 Esse dataset poderia ser considerado um dataset proativo, pois temos controle direto sobre os dados. Para saber mais confira o artigo de introdução ao River: https://dev.to/nobrelucas/river-aprendizado-de-maquina-online-com-python-uma-introducao-conceitual-3bb2

Instale os três arquivos do dataset no link: https://www.kaggle.com/competitions/titanic/data

Adicione o dataset no seu workspace e vamos começar a trabalhar.

💡 Recomendo criar uma pasta “data” no seu workspace para adicionar todos os dados

Preparação dos Dados

Com o seu notebook aberto, vamos ao código. Começamos criando um DataFrame pandas com os dados de treino e observando as cinco primeiras linhas.

import pandas as pd

dataframe = pd.read_csv('data/train.csv')
dataframe.head()
Enter fullscreen mode Exit fullscreen mode

Isso deve mostrar o seguinte resultado:

Cinco primeiras linhas do conjunto de treino

💡 Os detalhes de cada coluna desse dataset podem ser encontrados no link no qual você baixou o conjunto de dados.

Vamos nos concentrar em fazer um modelo simples. Nesse caso, as colunas que mais nos interessam são:

  • Survived (Alvo): É uma coluna categórica com duas classes (1 para sobreviventes e 0 para não sobreviventes)
  • Pclass: É uma coluna categórica com três classes (1 para primeira classe, 2 para segunda classe e 3 para terceira classe)
  • Sex: Coluna categórica indicando o sexo do passageiro

Escolhi as features Pclass e Sex porque, historicamente, mulheres ricas foram as pessoas que mais sobreviveram.

Vamos filtrar essas colunas que são as únicas que nos interessam no momento

dataframe = dataframe[['Survived', 'Pclass', 'Sex']]
dataframe.head()
Enter fullscreen mode Exit fullscreen mode

Mostrando o seguinte resultado:

cinco primeiras linhas do conjunto de treino após filtrado

Agora vamos transformar a coluna Sex em uma coluna numérica (1 para male e 2 para female)

dataframe['Sex'] = dataframe['Sex'].replace({'male': 1, 'female': 2})
dataframe.head()
Enter fullscreen mode Exit fullscreen mode

Obtendo como resultado:

Cinco primeiras linhas do conjunto de treino após mudança na coluna de sexo do passageiro

Por fim precisamos separar o dataset em features de treino e alvo (target). No nosso caso, Survived é o alvo e as demais colunas são as features de treino. Também irei transformar a coluna alvo de numérica para booleana para se adaptar ao modelo e a forma que o River trabalha.

features = dataframe[['Pclass', 'Sex']]
target = dataframe['Survived'].replace({0: False, 1: True})
Enter fullscreen mode Exit fullscreen mode

Com todos nossos dados estando no formato desejado e separados em features e target, podemos começar a efetivamente trabalhar com o modelo.

Criação e treinamento do modelo online

O objetivo de um classificador é predizer qual a categoria de uma determinada amostra, predizer um alvo y para um conjunto de características x. Vamos fazer isso usando um modelo de regressão logística.

from river import linear_model

model = linear_model.LogisticRegression()
Enter fullscreen mode Exit fullscreen mode

Com o código acima nós instanciamos o modelo.

💡 Eu irei frequentemente instanciar o modelo do começo para treiná-lo de formas diferentes.

Para treinar o modelo, basta alimentar ele com uma amostra dos dados por vez, mas como fazer isso se nossos dados são um grande lote? O módulo stream do River nos ajuda a lidar com isso e tem uma função chamada iter_pandas, que nos permite iterar um dataframe ou uma série pandas como se os dados estivessem chegando para nós em stream.

Experimente:

from river import stream

for X, y in stream.iter_pandas(features, target):
    print(X, y)
Enter fullscreen mode Exit fullscreen mode

Cada print vai mostrar um dicionário com as features e um valor para cada uma delas e ao lado o valor do target para aquela amostra.

Mas o que queremos não é simplesmente ver as amostras e sim ensinar ao modelo a predizer se dada uma amostra um passageiro sobreviveu ou não.

Para isso, podemos usar o método learn_one(X, y). Entretanto, vamos criar um objeto iterável chamado “stream_dataset” ao invés de chamar a função iter_pandas diretamente no laço de repetição. A motivação para isso vai ficar mais clara futuramente.

from river import stream
stream_dataset = stream.iter_pandas(features, target)

for X, y in stream_dataset:
    model.learn_one(X, y)
Enter fullscreen mode Exit fullscreen mode

Como estamos em um ambiente de aprendizado, vamos usar o mesmo dataframe para testar o modelo. Para um primeiro teste, vamos tentar predizer a primeira amostra do dataframe (homem que estava na terceira classe do navio e que não sobreviveu).

stream_dataset = stream.iter_pandas(features, target)
x, y = next(stream_dataset)
model.predict_one(x)
Enter fullscreen mode Exit fullscreen mode

💡 Notem que eu instancio o objeto stream_dataset novamente para poder iterar ele novamente desde o início. Isso irá se repetir durante o resto do tutorial para experimentar diversas abordagens de treino e de validação.

O resultado do código acima deve ser false pois de fato esse passageiro não sobreviveu.

Verificar o que o modelo prediz uma amostra de cada vez não é uma boa estratégia, então vamos avaliar o modelo de uma forma bem comum para iniciantes em aprendizado de máquina: armazenando a predição do modelo para cada iteração e verificar a classe real para aquela amostra e depois calcular uma certa “acurácia” para essas predições.

💡 Nesse tutorial vou usar como métrica o ROC AUC, uma métrica muito comum para modelos de classificação. Você pode saber mais sobre essa métrica no link: https://mariofilho.com/guia-completo-sobre-roc-auc-em-machine-learning/

from river import metrics

metric = metrics.ROCAUC(n_thresholds=20)

stream_dataset = stream.iter_pandas(features, target)

for X, y in stream_dataset:
    y_pred = model.predict_proba_one(X)
    model.learn_one(X, y)
    metric.update(y, y_pred)

metric
Enter fullscreen mode Exit fullscreen mode

O resultado do código acima deve retornar algo por volta de 81%, o que não é um valor a se jogar fora. Mas vamos experimentar uma outra abordagem, mais próxima do padrão de treinamento e de validação do River.

A biblioteca River permite criar um modelo como uma Pipeline, ou seja, um processo de passos para o treinamento. Isso é feito simplesmente adicionando “|” entre os processos desejados.

Além disso, como dito no artigo de introdução do River, o aprendizado e a inferência acontecem na mesma ordem que acontece no ambiente de produção e para isso a biblioteca usa a função progressive_val_score, que recebe o dataset em formato de stream, o modelo e a métrica usada.

from river import evaluate
from river import optim
from river import preprocessing
from river import compose

stream_dataset = stream.iter_pandas(features, target)

model = (
    preprocessing.StandardScaler() |
    linear_model.LogisticRegression(optimizer=optim.SGD(.1))
)

metric = metrics.ROCAUC(n_thresholds=20)

evaluate.progressive_val_score(stream_dataset, model, metric)
Enter fullscreen mode Exit fullscreen mode

Para otimizar o modelo, antes de os dados alimentarem o modelo, eles são redimensionados com o StandardScaler(). O modelo recebe um otimizador do tipo SGD para descida de gradiente estocástica simples. Por fim avaliamos o modelo com progressive_val_score.

O resultado retornado pela função na última linha deve ser por volta de 82%. Uma melhora não tão considerável em comparação à nossa versão anterior, mas uma melhora é uma melhora.

Por fim, caso você queira identificar com mais facilidade os elementos do seu pipeline do modelo, basta digitar model em uma célula do seu notebook e ver a imagem que define o pipeline. No nosso caso, por exemplo, temos:

desenho esquemático mostrando o Scaler acima do modelo de LogisticRegression

Conclusão

Com esse artigo você é capaz de experimentar várias das formas de trabalhar com a biblioteca River para treinar e avaliar modelos de aprendizado de máquina online. Você limpou um conjunto de dados e aprendeu desde como lidar com dados em lote para que funcionem como dados em streaming até como construir e avaliar um modelo de aprendizado online.

Se esse conteúdo foi do seu interesse, não esqueça de avaliá-lo de alguma forma e de me seguir para acompanhar os próximos artigos.

Dúvidas, sugestões ou correções são muito bem-vindas na seção de comentários abaixo. Até a próxima.

💖 💪 🙅 🚩
nobrelucas
Lucas Nobre Barbosa

Posted on October 24, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related