top of page
  • Foto do escritorCarlos Silva

Série temporal da modelagem à API com Prophet e MLFlow

Neste post, vou explicar como o uso os pacotes python do Prophet e do MLFlow para melhorar a previsão e o gerenciamento de modelos em um projeto de ciência de dados que faz a previsão de vendas de uma loja de varejo.

O Prophet é uma biblioteca desenvolvida pelo Facebook que permite fazer previsões baseadas em séries temporais de forma simples e robusta. O MLFlow é uma plataforma de código aberto que facilita o ciclo de vida dos modelos de aprendizado de máquina, desde o treinamento até a implantação, passando pelo monitoramento e a otimização. Combinando essas duas ferramentas, é possível criar um fluxo de trabalho eficiente e escalável de ciência de dados (O tal do MLOps).

Neste post, meu objetivo é mostrar como servir uma API com o nosso modelo de previsão. No entanto, há vários passos necessários para publicar um modelo em produção de forma sustentável. Por isso, este post será o primeiro de uma série que visa construir uma solução completa para isso. Vamos servir a API, mas também vamos melhorá-la e automatizá-la em posts futuros. Por enquanto, vamos considerar que este post é a primeira sprint do nosso projeto liderado pelo cientista de dados que será passado para uma equipe de engenharia de dados e/ou um engenheiro de machine learning.


Dito isso, nosso alvo de negócio é;

Montar a previsão de como as vendas se comportarão no próximo mês e servir isso como uma API para que analistas de negócio possam através de BI gerenciar estoque, fazer estimativa de receita e outras analises operacionais de curto prazo.

🖥️ Ambiente


Nesse projeto optei por usar a distro virtual WSL do Ubuntu dentro do Windows conectando com o Visual Studio Code.

Dentro do terminal WSL é importante seguir com esses comandos para preparar o ambiente.


As importações necessárias são as seguintes;


💽 Dataset e processamento de dados

Dados de série temporal

A base de dados de origem desse dataset vem desse link. Que é o dataset "olist_orders_dataset.csv" que adaptei e adicionei no GitHub desse projeto. Também foram aplicadas algumas transformações e limpezas como interpolação e eliminação de buraco nos dados. No geral esse processamento diminui nosso dataset em 2% e o deixa com 607 dias de pedidos diários.





📊Análise exploratória


Série temporal de vendas

Os dados parecem irregulares com uma quebra no padrão sazonal semanal depois '2018-08-25, o padrão é os pedidos ficarem dentro da semana em uma queda de 5 dias e depois uma alta abrupta. Depois dessa data os pedidos entram em uma queda de mais de 10 dias. Por conta disso decidi extrair eles da modelagem por conta da possibilidade de dados incompletos. Nesse caso é interessante ver com o cliente e auditar a base de dados.


Série temporal

Série temporal e detalhes da série

Série temporal descrita

Ficou evidenciado uma marcação semanal que na Terça temos valores médios altos e que no domingo temos valores médios baixos.

Na terça os valores são elevados, porém os mais dispersos, o segundo maior é quarta, mas menos disperso que terça. Já no domingo além das médias menores também é menos disperso.

Dias da semana na Série temporal

Dias do mês e da semana na série temporal

Outliers

No box-plot podemos observar que existem valores extremos acima de 400 pedidos/dia, essas datas podem ser de eventos específicos como:

  • Altas em 24 e 25 de 2017/11, Black Friday de 2017.

  • Altas em 23 e 24 de 2018/04, final de semana próximo ao dia das mães, também encontrei itens relacionados a Dia do Frete Grátis.

  • Para o pico em 2018-07-05 não encontrei nada específico.

Entender essas datas é importante porque são eventos que impactam os pedidos fora de um padrão que possa ser treinado, logo impactando na precisão do mesmo, mas se categorizarmos e planejarmos esses eventos e adiciona-los como características do modelo podemos minimizar esses impactos contudo esse tipo de refinamento não é o foco no momento.

Outliers da Série temporal

Decomposição

Testando estacionaridade

Para p < 0,05 é considerado estacionário. No geral a série não atende esse critério em um nível de significância de 5%, mas por bem pouco. Quando fazemos o recorte dessa série temporal em duas partes vemos que no mesmo teste a porção mais recente é considerada estacionária, por se tratar do período mais recente vou considerar uma estacionaridade para o futuro dessa série.

Estacionaridade da Série temporal

Aqui vemos a sazonalidade semanal da série temporal que corrobora com as médias de pedidos por dia de semana que vimos anteriormente.

Sazonalidade da Série temporal

Autocorrelação

A Autocorrelação (ACF) é uma medida estatística que nos ajuda a entender a relação entre os valores de uma série temporal em diferentes momentos no tempo, mede o quanto o momento atual é correlacionado ao conjunto de momentos imediatamente anteriores.


Na nossa série o ela se mostrar superior a 0.5 em até dois dias e em 7 dias, e superior a 0.3 em até duas semanas (isso varia de -1 a 1).


Autocorrelação da Série temporal

Autocorrelação Parcial

A Autocorrelação Parcial (PACF) é uma medida estatística que nos ajuda a entender a relação direta entre duas observações em uma série temporal, removendo a influência das observações intermediárias, isto é, ao contrário da ACF ela não considera o conjunto de itens imediatamente anteriores e sim a relação do momento atual com outro anterior em uma distância especifica.

Na nossa série, com exceção ao dia anterior com quase 0.75 temos os dias 6 e 7 próximos a 0.25, todos os outros são abaixo disso.

Autocorrelação Parcial da Série temporal

⚙️Modelagem Prophet


Primeiro separando treino e teste.

Também crio algumas funções para extrair informações de atributos dos modelos e também gráficos de resultado.


Modelo inicial

Antes de prosseguir é importante iniciar qual é o local que vamos salvar nossos modelos, existem muitas formas de fazer isso com o MLFlow, nesse projeto vamos salvar localmente os artefatos e em um SQLite as demais informações, para mais detalhes sobre as formas de funcionamento sugiro acessa a documentação sobre esse assunto aqui.


MLflow em localhost com SQLite
MLflow em localhost com SQLite, fonte mlflow.org

No terminal do WLS execute os seguintes comandos, tudo relacionado aos modelos vai ser salvo na pasta 'models'. Isso vai travar o terminal e gerar um link para o UI de gerenciamento do MLFlow.


mlflow ui

Além disso é preciso definir o arquivo com as dependências do modelo, vamos precisar disso para fazer o deploy mais tarde.


Sobre os Hiperparâmetros

seasonality_mode: Este parâmetro controla se a sazonalidade é aditiva ou multiplicativa.

weekly_seasonality, daily_seasonality e yearly_seasonality: Esses parâmetros controlam se a sazonalidade semanal, diária e anual deve ser incluída no modelo.

changepoint_range: Este parâmetro controla a proporção da série temporal em que os pontos de mudança são permitidos. O padrão é 0,8, o que significa que os primeiros 80% da série temporal são considerados para possíveis pontos de mudança.

changepoint_prior_scale: Este parâmetro controla a flexibilidade dos pontos de mudança. Valores maiores permitem mudanças mais flexíveis, enquanto valores menores resultam em mudanças mais rígidas.

seasonality_prior_scale: Este parâmetro controla a força da sazonalidade. Valores maiores permitem uma sazonalidade mais forte, enquanto valores menores resultam em uma sazonalidade mais fraca.


Treinado o modelo

Como artefatos serão salvos esses gráficos.

Série temporal

Série temporal detalhes

Observe que isso foi salvo e gerenciado pelo MLFlow e podemos ver tudo dentro da interface gráfica do mesmo...


mlflow ui

... artefatos, métricas de treino e teste, hiperparâmetros, dependência e até o próprio modelo.

mlflow ui modelo e artefatos

Otimização de hiperparâmetros com MLFlow


Criando experimento

Um experimento no MLflow é uma maneira de organizar e agrupar execuções de modelos de aprendizado de máquina. Cada experimento pode conter várias execuções, cada uma representando uma iteração diferente do modelo ou um conjunto diferente de hiperparâmetros. O MLflow Tracking permite que você registre informações sobre cada execução, como parâmetros, métricas e arquivos de saída, para que você possa comparar e avaliar diferentes execuções dentro de um experimento. Nesse projeto quero otimizar os hiperparâmetros e isso irá gerar vários modelos, um experimento é perfeito para guardar isso.

Se não apontarmos definirmos lugar para os modelos eles serão salvos no ambiente padrão, ou experimento zero, foi o que aconteceu no modelo inicial, mas aqui vou criar um experimento para a otimização de hiperparâmetros.

Detalhe experimento mlflow


Treino de Otimização de hiperparâmetros

Essa otimização de hiperparâmetros está testando 192 combinações e também realizando validação cruzada da série temporal salvando tudo dentro do MLFlow.


Avaliando modelos treinados na otimização

Consultando os resultados otimização de hiperparâmetros dentro do MLFlow verificamos que o modelo 47 foi o que teve as melhores métricas e a menor diferença entre treino e teste.

mlflow comparando modelos de experimento

Modelo Final

Uma vez escolhido os hiperparâmetros ótimos é o momento de criar um experimento de produção e treinar um modelo com todos os dados disponíveis. É nesse experimento que gerenciaremos todos os modelos que venham surgir.

Detalhe experimento mlflow

Em seguida capturamos o hiperparâmetros do melhor modelo...

hiperparâmetros  do melhor modelo

... e treinamos nosso modelo de produção.

Série temporal


📥Registrando


É preciso registrar o modelo que temos a intenção de colocar em produção, aqui pode haver uma confusão, que também é a grande mágica do MLFlow. Podemos interpretar que quando registramos um modelo estamos registrando uma saída de informação, se fosse um parque eu poderia registrar um quiosque de pipoca e outro para sorvete, cada um recebe um tipo de pedido especifico (input) e responde com um produto especifico (output), além disso podemos trocar os ingredientes e os atendentes para melhorarmos a qualidade dos produtos entregues, mas os registros ainda são os mesmos.

No código abaixo criamos esse registro e depois delegamos qual é o modelo que poderá estar servindo no mesmo, podemos ir delegando novos modelos e criando novas versões e também definir qual é a versão que entrará no estágio de produção. Também podermos fazer tudo isso usando a interface gráfica do MLFlow.



🍧Servindo o modelo em API


O MLFlow vai criar ambientes virtuais para servir o modelo neles. Para isso é preciso fazer algumas preparações, no terminal WSL execute os seguintes comandos que instalaram o pyenv como gerenciador ambiente virtual python.

Em um novo terminal do WLS execute os seguintes comandos para postar o modelo como uma API, isso vai travar o terminal e gerar um link de acesso a API do modelo. Repare que a porta da API tem que ser diferente da porta que você já está usando a interface gráfica.

Podemos fazer o request via python para testar com o seguinte código...

Output api

... ou consumir no PowerBI. Segue um exemplo de código para o editor do Power Query.

let
    Source = List.Dates(#date(2018, 9, 1), 30, #duration(1, 0, 0, 0)),
    TableFromList = Table.FromList(Source, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    RenamedColumns = Table.RenameColumns(TableFromList,{{"Column1", "ds"}}),
    #"Personalização Adicionada" = Table.AddColumn(RenamedColumns, "Personalizar", each Json.Document(
    Web.Contents(
        "http://127.0.0.1:5001/invocations",
        [
            Content = Text.ToBinary(
                "{""dataframe_split"": {""columns"": [""ds""], ""data"": [[""" & Text.From([ds]) & """]]}}"
            ),
            Headers = [#"Content-Type" = "application/json"]
        ]
    )
)),
    #"Personalizar Expandido" = Table.ExpandRecordColumn(#"Personalização Adicionada", "Personalizar", {"predictions"}, {"Personalizar.predictions"}),
    #"Personalizar.predictions Expandido" = Table.ExpandListColumn(#"Personalizar Expandido", "Personalizar.predictions"),
    #"Personalizar.predictions Expandido1" = Table.ExpandRecordColumn(#"Personalizar.predictions Expandido", "Personalizar.predictions", {"ds", "trend", "yhat_lower", "yhat_upper", "trend_lower", "trend_upper", "additive_terms", "additive_terms_lower", "additive_terms_upper", "weekly", "weekly_lower", "weekly_upper", "yearly", "yearly_lower", "yearly_upper", "multiplicative_terms", "multiplicative_terms_lower", "multiplicative_terms_upper", "yhat"}, {"Personalizar.predictions.ds", "Personalizar.predictions.trend", "Personalizar.predictions.yhat_lower", "Personalizar.predictions.yhat_upper", "Personalizar.predictions.trend_lower", "Personalizar.predictions.trend_upper", "Personalizar.predictions.additive_terms", "Personalizar.predictions.additive_terms_lower", "Personalizar.predictions.additive_terms_upper", "Personalizar.predictions.weekly", "Personalizar.predictions.weekly_lower", "Personalizar.predictions.weekly_upper", "Personalizar.predictions.yearly", "Personalizar.predictions.yearly_lower", "Personalizar.predictions.yearly_upper", "Personalizar.predictions.multiplicative_terms", "Personalizar.predictions.multiplicative_terms_lower", "Personalizar.predictions.multiplicative_terms_upper", "Personalizar.predictions.yhat"}),
    #"Tipo Alterado" = Table.TransformColumnTypes(#"Personalizar.predictions Expandido1",{{"Personalizar.predictions.yhat", type number}, {"Personalizar.predictions.multiplicative_terms_upper", type number}, {"Personalizar.predictions.multiplicative_terms_lower", type number}, {"Personalizar.predictions.multiplicative_terms", type number}, {"Personalizar.predictions.yearly_upper", type number}, {"Personalizar.predictions.yearly_lower", type number}, {"Personalizar.predictions.yearly", type number}, {"Personalizar.predictions.weekly_upper", type number}, {"Personalizar.predictions.weekly_lower", type number}, {"Personalizar.predictions.weekly", type number}, {"Personalizar.predictions.additive_terms_upper", type number}, {"Personalizar.predictions.additive_terms_lower", type number}, {"Personalizar.predictions.additive_terms", type number}, {"Personalizar.predictions.trend_upper", type number}, {"Personalizar.predictions.trend_lower", type number}, {"Personalizar.predictions.yhat_upper", type number}, {"Personalizar.predictions.yhat_lower", type number}, {"Personalizar.predictions.trend", type number}, {"ds", type date}}),
    #"Literal inserido" = Table.AddColumn(#"Tipo Alterado", "Tipo", each "Forecast", type text),
    #"Colunas Renomeadas" = Table.RenameColumns(#"Literal inserido",{{"Personalizar.predictions.yhat", "Forecast"}})
in
    #"Colunas Renomeadas"

Consumindo a API no PowerBi

Pronto, seu modelo está em pseudo-produção.


🔁Próximos passos.


A atualização dos dados é um aspecto importante ao trabalhar com previsão de séries temporais. Como as séries temporais são baseadas em dados históricos, é importante garantir que os dados estejam atualizados e precisos para obter previsões confiáveis. Isso envolve a coleta regular de novos dados, a verificação da qualidade dos dados e a correção de quaisquer erros ou inconsistências. Além disso, é importante levar em consideração mudanças nas condições subjacentes que podem afetar a série temporal. Por exemplo, se houver uma mudança significativa na economia ou no mercado que afete os dados da série temporal, pode ser necessário atualizar o modelo para levar em consideração essas mudanças. Levando isso em consideração os próximos passos são;

  • Construir um código de atualização e treinamento automático.

  • Parametrizar a captura e o acompanhamento de métricas de performasse dos modelos.

Considerações finais

  • O caminho que usei para captura de informações dos modelos nesse projeto foi longo porque o modelo Prophet é aceito, porém não tem o recurso de "autolog" que permite que você registre métricas, parâmetros e modelos sem a necessidade de declarações de log explícitas.

  • Para servir o modelo existe o caminho de servi-lo em um contêiner Docker.

Referências


MLFlow - Gerenciador de modelos

Prophet - Modelo de série temporal

GitHub - Código do Post

95 visualizações0 comentário
Post: Blog2_Post
Post: Blog2 Custom Feed
bottom of page