Alura > Cursos de Dados > Cursos de MLOps e Operacionalização > Conteúdos de MLOps e Operacionalização > Primeiras aulas do curso Projeto de engenharia de machine learning: repositório, Docker e deploy na AWS com FastAPI

Projeto de engenharia de machine learning: repositório, Docker e deploy na AWS com FastAPI

Estrutura de projeto e modelo de machine learning - Apresentação do curso e objetivos

Apresentando o curso e o instrutor

Olá! É um prazer enorme estar aqui com vocês. Nós vamos começar nosso curso do modelo à implantação na AWS. O objetivo será consolidar o conhecimento das disciplinas vistas até este momento. Nós vamos desenvolver a estrutura do repositório para um projeto de aprendizado de máquina, criar um modelo de aprendizado de máquina para a previsão de anomalias, escrever um Dockerfile, expor por meio de uma API utilizando FastAPI e realizar a implantação na AWS.

Meu nome é Julios Vitoro, serei o instrutor deste curso de integração.

Audiodescrição: Sou um homem branco, visto uma camisa verde e, ao fundo, há uma parede em tons de azul e lilás.

Detalhando a estrutura dos módulos

Agora, sobre o conteúdo do curso. Este curso estará estruturado ao longo de seis módulos.

No primeiro módulo, nós vamos desenvolver o projeto, apresentar a estrutura inicial do repositório e criar a versão inicial do nosso modelo de aprendizado de máquina para previsão de anomalias.

No módulo 2, nós vamos tratar da containerização: containerizaremos o projeto e o exporemos via API utilizando FastAPI.

No módulo 3, nós vamos abordar a implantação do modelo de aprendizado de máquina usando AWS. Até esse ponto, trabalharemos apenas com a estrutura de back-end (camada de servidor). Nós vamos desenvolver o modelo, e o objetivo será consumi-lo por meio de chamadas a um endpoint (ponto de acesso). Nesse caso, faremos a implantação na AWS.

No módulo 4, nós vamos desenvolver um dashboard (painel) de forma bastante simplificada, utilizando Streamlit. Desenvolveremos esse painel e consumiremos o modelo que implantamos no módulo 3.

No módulo 5, nós vamos realizar a implantação com GitHub Actions e um pipeline (esteira), porque, até então, no módulo 3, desenvolvemos o processo de implantação manual. No módulo 5, faremos a implantação com GitHub Actions e automatizaremos esse processo de desenvolvimento.

Realizaremos um ciclo completo de desenvolvimento. No módulo 6, publicaremos nossa aplicação como um todo, abrangendo front-end (interface) e back-end (lógica de negócio), e trataremos de boas práticas relacionadas ao desenvolvimento e à implantação de aplicações e modelos de aprendizado de máquina.

Listando requisitos e ferramentas

Agora, sobre repositórios e requisitos obrigatórios. Precisaremos usar o Docker Desktop. Há links de referência para Windows, macOS e Linux. No GitHub, criaremos nosso repositório, e você precisará ter o Git instalado na sua máquina. Nós vamos usar o Visual Studio Code, mas você pode utilizar o IDE (ambiente de desenvolvimento integrado) de sua preferência. Você precisará de uma conta na AWS, e o interessante é que há acesso a créditos gratuitos suficientes para desenvolver este projeto. Você pode criar sua conta e receber uma quantidade de créditos necessária para que desenvolvamos este projeto.

Este projeto, diferentemente dos anteriores, será desenvolvido de ponta a ponta utilizando recursos de inteligência artificial. Podemos usar GitHub Copilot, Cloud Code e Antigravity. Nosso objetivo é transmitir a você a experiência real de desenvolvimento de software em 2026. Vamos procurar fazer algo muito semelhante ao que se faz em ambiente profissional, mostrando como projetos são desenvolvidos em empresas.

Esses são os requisitos.

Explicando a metodologia e próximos passos

Sobre a metodologia: já há um repositório pronto com todo o projeto desenvolvido; cada ramo corresponde a um módulo, e essa é a estrutura que vamos seguir. Começaremos criando um repositório, e cada módulo será um projeto isolado. Desenvolveremos o módulo 1 e criaremos o ramo módulo-1. No entanto, o módulo seguinte herdará o que foi desenvolvido no módulo anterior. O ramo do módulo 6, nosso capítulo final, será basicamente um repositório pronto para movermos ao ambiente de produção.

Dito isso, vamos passar ao desenvolvimento. Eu fico por aqui e volto em nossa primeira aula. Agradeço a todas as pessoas.

Estrutura de projeto e modelo de machine learning - Repositório e primeiros passos

Iniciando o projeto e definindo o problema

Olá, pessoal. Agora vamos iniciar, de fato, nosso projeto.

Como dissemos no início, neste primeiro módulo, vamos identificar qual é o problema que vamos resolver, estruturar nosso repositório, organizar a estrutura de código e desenvolver o modelo de Machine Learning (Aprendizado de Máquina) para realizar a previsão de anomalias em um conjunto de dados específico que vamos utilizar.

O caso de uso que vamos adotar como referência é o problema de detecção de anomalias em um motor elétrico. Teremos acesso a um conjunto de dados com diversas variáveis sobre esse motor, como temperatura, vibração, rotação e também disponibilidade elétrica. As classes possíveis nesse conjunto de dados são: normal, que indica condições nas quais o motor está operando adequadamente; desbalanceamento; superaquecimento; e falha mecânica.

Nosso objetivo será desenvolver um modelo de Machine Learning (Aprendizado de Máquina) para identificar possíveis anomalias na operação de um motor elétrico. Será um caso clássico de classificação, porque, neste conjunto de dados, há uma feature (atributo) que indica exatamente quais são as condições desse motor.

Dito isso, o objetivo deste primeiro módulo será estruturar o repositório e desenvolver o modelo de Machine Learning (Aprendizado de Máquina) para prever anomalias. Vamos detalhar as informações desse motor ao longo das etapas, seguindo um passo a passo organizado. Neste primeiro momento, vamos criar nosso repositório.

Criando o repositório no github

Aqui, temos acesso aos meus repositórios. A maioria é pública. Mantemos materiais da área de ciência de dados, especialmente voltados para aplicações de inteligência artificial, desenvolvimento de chatbots (robôs de conversa) e outros temas relacionados. Você pode utilizar esse repositório também para estudar outros assuntos, caso tenha interesse.

Na seção de repositórios, vamos criar um novo repositório. Vamos clicar em Italic New (Novo). Vamos nomeá-lo como Projeto Motor Anomalias. Esse será o nome do projeto, e o repositório será público. Em seguida, vamos prosseguir com a criação do repositório.

Ao criar um repositório do zero, já temos acesso a informações relevantes. Há um Italic Quick Start (inicialização rápida), com instruções para criar um novo repositório via linha de comando ou para fazer um Italic push (envio) a partir de um repositório que já existe.

Organizando o diretório local do projeto

No nosso computador, dentro da pasta "Documents", vamos trabalhar em uma pasta dedicada ao projeto. Pelo terminal, vamos acessar esse diretório utilizando o comando apropriado para entrar na pasta "Documents". Para isso, executamos:

cd Documents

Dentro de "Documents", estamos buscando a pasta "Deploy App". Entramos em "Deploy App" com o comando cd Deploy App e confirmamos que estamos no diretório esperado. No terminal, essa pasta aparece sem espaço, como "DeployApp", então usamos:

cd DeployApp

Em seguida, vamos criar um diretório com esse mesmo nome utilizando mkdir. Observação: usaremos o nome do projeto em formato kebab-case, conforme a prática comum para nomes de pastas:

mkdir projeto-motor-anomalias

Depois, usamos um comando para criar um readme. No entanto, criamos o readme no local incorreto: ele foi criado dentro de "Deploy App". Primeiro, o comando foi executado ainda fora da pasta do projeto:

echo "# projeto-motor-anomalias" >> README.md

Precisamos executar cd para entrar na pasta "Projeto Motor Anomalies" (aqui nomeada como projeto-motor-anomalias no sistema de arquivos) e, em seguida, repetir o comando para criar o readme no local correto:

cd projeto-motor-anomalias

Agora repetimos a criação do arquivo README.md no diretório certo:

echo "# projeto-motor-anomalias" >> README.md

Ao executar ls, confirmamos que agora o readme está na pasta do projeto. Vamos remover o readme que foi criado no local errado.

Inicializando o repositório git e configurando branches

No próximo passo, vamos inicializar o repositório com git init. Em seguida, adicionamos o readme com git add readme. No nosso caso, o arquivo foi criado como README.md, então os comandos ficam:

git init
git add README.md

Fazemos o primeiro commit com git commit -m "<mensagem>". Aqui utilizamos uma mensagem inicial padrão:

git commit -m "first commit"

Criamos a branch main com git branch main. Aqui, vamos criar também a branch módulo 01. Na execução que estamos mostrando, renomeamos a branch atual diretamente para modulo01, o que, na prática, a torna a principal por enquanto:

git branch -M modulo01

Depois, configuramos o remoto com git remote add origin e executamos git push:

git remote add origin https://github.com/JulesMitoura/projeto-motor-anomalias.git
git push

Será solicitado configurar o upstream; repetimos o comando com a configuração de upstream para a branch modulo01:

git push --set-upstream origin modulo01

Ao atualizar, verificamos que a branch módulo 01 está disponível. O único ponto é que ela ficou como branch padrão; poderíamos ter definido a main como padrão ou criado uma branch dev, o que podemos corrigir nos próximos passos.

Abrindo o projeto no vs code e apresentando próximos passos

Com isso, podemos passar ao desenvolvimento do projeto. Vamos utilizar o Visual Studio Code. Abrimos o Visual Studio Code e navegamos até a pasta do repositório. Para facilitar a visualização, ajustamos a interface. Utilizamos a opção do menu Open Folder (Abrir Pasta), acessamos "Documents" > "Deploy App" > "Projeto Motor Anomalies" e confirmamos que está tudo conforme o esperado. Ao abrir um terminal integrado e executar git status, verificamos que estamos na branch módulo 01 e já podemos iniciar o desenvolvimento do projeto:

git status

Vamos finalizar esta aula por aqui. Na próxima aula, eu vou trazer quais são os dados que vamos utilizar e uma breve documentação sobre esses dados para seguirmos com as etapas de análise exploratória e desenvolvimento do projeto. Eu agradeço pela atenção e nos vemos na próxima aula.

Estrutura de projeto e modelo de machine learning - Analise exploratória de dados

Apresentando os dados e os arquivos do módulo

Olá. Já falamos sobre o que vamos fazer, mas ainda não temos uma visão clara sobre quais são os dados que vamos utilizar.

Junto com o material recebido referente a cada módulo, no material do módulo 1 temos dois arquivos: motor.db e motor.md. Todos os dados referentes aos motores estão dentro do arquivo motor.db, portanto, vamos realizar consultas usando query (consulta) SQL. Em motor.md, temos a documentação sobre esse dataset (conjunto de dados).

Vamos criar a pasta "data" e copiar os arquivos para dentro dela. O motor.db contém basicamente três tabelas, e também temos a descrição desses dados. Vamos observar um preview (pré-visualização): são 20 motores monitorados, com 30 mil leituras (1 leitura por minuto), sem valores nulos; cada motor possui aproximadamente 1.500 leituras.

Sobre as tabelas:

Preparando o ambiente e a estrutura do projeto

Antes de realizar a leitura e a visualização dos dados, precisamos criar o ambiente virtual e instalar as dependências. No README, já deixamos as informações e os comandos que serão necessários. Vamos criar o ambiente virtual .venv e ativá-lo (no macOS utilizamos o comando com source; no Windows, utilizamos o comando equivalente no PowerShell). Também vamos criar o arquivo .gitignore e o arquivo requirements.txt. No .gitignore, adicionaremos .venv para não versionar o ambiente virtual. As dependências serão instaladas com o gerenciador de pacotes apropriado a partir do requirements.txt.

Para criar o ambiente virtual, execute:

python3.11 -m venv .venv

Como primeiro passo prático, vamos ler os dados para visualizar os tipos existentes. Sabemos, pela documentação, que não há valores nulos. Vamos criar a pasta "notebooks" para organizar uma estrutura de projeto semelhante à que utilizamos no trabalho. Dentro de "notebooks", colocaremos uma série de arquivos que podem ser úteis e executados com certa recorrência; vamos criar o arquivo 01eda.ipynb.

No primeiro import, vamos utilizar sqlite3. Precisamos selecionar o kernel (núcleo) do Jupyter, que neste caso é o ambiente virtual. Ao executar pela primeira vez um notebook (caderno) nesse ambiente, o Jupyter poderá solicitar a instalação do pacote ipykernel. Após isso, já podemos nos comunicar com o banco de dados.

Conectando ao banco de dados SQLite

Para isso, comece importando a biblioteca padrão do SQLite:

import sqlite3

No primeiro passo, vamos criar uma conexão com o SQLite, informando o caminho para o arquivo motor.db. Se estivermos no diretório "notebooks", o caminho relativo data/motor.db não será encontrado por estarmos fora da raiz do projeto; portanto, devemos ajustar para ../data/motor.db. O erro “Não foi possível abrir o arquivo de banco de dados” ocorreu porque o arquivo motor.db está na raiz (dentro de "data") e o notebook está na pasta "notebooks". Ajustado o caminho, a conexão é estabelecida corretamente.

Primeiro, a tentativa incorreta (a partir do diretório notebooks):

path = 'data/motor.db'
conn = sqlite3.connect(path)

Agora, corrigindo o caminho relativo para alcançar a pasta na raiz do projeto:

path = '../data/motor.db'
conn = sqlite3.connect(path)

Consultando o banco e carregando os dados com pandas

Em seguida, vamos escrever a nossa query (consulta) SQL. Iremos consultar os dados na tabela leituras e realizar um join (junção) com a tabela motores para já trazer as informações relacionadas ao tipo de motor (como fabricante, modelo e potencia). Note que, no dataset, algumas colunas podem aparecer com sufixos de unidade (por exemplo, rotacao_rpm, vibracao_mm_s, temperatura_c, corrente_a), o que é equivalente às variáveis mencionadas na explicação.

query = """
SELECT 
    l.motor_id,
    l.timestamp,
    l.rotacao_rpm,
    l.vibracao_mm_s,
    l.temperatura_c,
    l.corrente_a,
    l.falha,
    m.fabricante,
    m.modelo,
    m.potencia_kw
FROM leituras l
JOIN motores m ON m.motor_id = l.motor_id
ORDER BY l.motor_id, l.timestamp
"""

Para manipular os dados, vamos importar pandas como pd. Caso ocorra erro por ausência do pacote, adicionaremos pandas ao requirements.txt e reinstalaremos as dependências.

import pandas as pd

Para executar a query (consulta) e carregar os dados, vamos utilizar a função pd.read_sql_query, passando a consulta e a conexão, e armazenando o resultado em df. Com isso, já conseguimos visualizar o dataset (conjunto de dados). Como são cerca de 30 mil linhas, a operação pode levar alguns instantes. Além do campo falha, também teremos fabricante, modelo e potencia graças ao join (junção) entre motor_id da tabela leituras e motor_id da tabela motores.

df = pd.read_sql_query(query, conn)
df.head()

Estruturando tipos e mapeando as falhas

Agora, precisamos estruturar e organizar os tipos de dados. Ao verificar df.dtypes, observaremos que timestamp está como texto; porém, timestamp deve ser do tipo datetime. Vamos converter a coluna timestamp com pd.to_datetime. Após a conversão, timestamp ficará no formato esperado e as demais variáveis numéricas aparecerão como float.

df.dtypes
# 01. converter a coluna timestamp para o tipo datetime
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.dtypes

Em seguida, vamos analisar os tipos de falha. Em falha, temos os códigos 0, 1, 2 e 3. Queremos verificar as nomenclaturas correspondentes a cada código. Para isso, vamos executar uma query (consulta) na tabela tipos_falha para trazer as colunas codigo e nome, ordenadas por codigo. Assim, por exemplo, 0 = normal, 1 = desbalanceamento, 2 = sobreaquecimento, 3 = falha mecânica.

# 02. mapeamento dos tipos de falhas
query = "SELECT codigo, nome FROM tipos_falha ORDER BY codigo"
df_tipos = pd.read_sql_query(query, conn)
df_tipos

Com essas informações, vamos criar um mapeamento dos rótulos. Vamos definir um label_map como um dicionário (dict) construído com zip (emparelhamento) entre codigo e nome do DataFrame carregado a partir de tipos_falha. Esse mapeamento será útil nas próximas etapas de análise.

label_map = dict(zip(df_tipos['codigo'], df_tipos['nome']))

Ainda que já saibamos pela documentação que não há valores nulos, vamos confirmar programaticamente, verificando df.isnull().sum(). Não há valores faltantes em nenhuma variável, portanto não precisaremos incluir esse tratamento no processo.

# 03. verificar a presenca de valores nulos
df.isnull().sum()

Explorando a distribuição das classes de falha

Agora, entramos na etapa de visualização. Vamos verificar a distribuição das classes de falha. Primeiro, podemos obter a contagem com df['falha'].value_counts(), o que nos dá a frequência de cada código (por exemplo, o código 0, que representa condição normal, é o mais frequente, indicando que os motores operam predominantemente em normalidade).

# 04. verificar a distribuicao das falhas
df['falha'].value_counts()

Para avaliar visualmente essa distribuição, vamos preparar um gráfico. Faremos a contagem, coletaremos os rótulos e criaremos a visualização utilizando as bibliotecas matplotlib e “cyborg”. Se essas dependências ainda não estiverem instaladas, vamos adicioná-las ao requirements.txt, reinstalar as dependências, reiniciar o ambiente e executar tudo novamente. Ajustaremos o tamanho da figura para facilitar a leitura. Em geral, observamos que os motores operam em condições de normalidade, mas há ocorrências de desbalanceamento, sobreaquecimento e falha mecânica.

Primeiro, importe as bibliotecas de visualização:

import matplotlib.pyplot as plt
import seaborn as sns

Agora, gere a tabela de frequências, imprima um sumário e plote o gráfico de barras:

contagem = df['falha'].value_counts().sort_index()
labels = [label_map[i] for i in contagem.index]

for codigo, qtd in contagem.items():
    pct = qtd / len(df) * 100
    print(f"{label_map[codigo]:20s} ({codigo}): {qtd:6d} ({pct:.1f}%)")

fig, ax = plt.subplots(figsize=(8, 5))

bar_colors = sns.color_palette("tab10", len(labels))
bars = ax.bar(labels, contagem.values, color=bar_colors)

ax.set_title("Distribuicao de Classes de Falha")
ax.set_xlabel("Tipo de Falha")
ax.set_ylabel("Quantidade de Leituras")

for bar, val in zip(bars, contagem.values):
    ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 50,
            str(val), ha='center', fontsize=10)

plt.tight_layout()
plt.show()

Analisando correlações entre variáveis e alvo

Nosso próximo objetivo é entender a correlação entre as variáveis de processo e a ocorrência dessas falhas. O modelo buscará a existência de anomalias e utilizará como entrada as variáveis relacionadas ao motor. Essa distribuição de classes é desbalanceada, o que era esperado, pois esperamos que o motor opere majoritariamente em condição de normalidade.

Como próximo passo, vamos verificar a correlação entre as características (features, características) e o tipo de falha. Para isso, podemos utilizar df.corr().

# 05. verificar a correlacao entre as features e a target
df.corr()

Tivemos um problema, pois incluímos também a coluna que contém valores do tipo string. Neste caso, estamos tentando analisar apenas as variáveis numéricas. As variáveis numéricas que temos são estas, e, para a correlação, vamos considerar apenas os dados relacionados a essas variáveis.

# 05. verificar a correlacao entre as features e a target
features = ['rotacao_rpm', 'vibracao_mm_s', 'temperatura_c', 'corrente_a']
df[features].corr()

Agora podemos verificar as correlações. Ainda há um ponto: não incluímos a coluna falha. Precisamos incluí-la, pois vamos verificar correlações em função da falha. Com isso, obtemos uma visualização mais interessante. Em seguida, faz mais sentido observar um heatmap (mapa de calor). Para isso, precisamos nomear o conjunto de variáveis como features. O gráfico estava um pouco grande; ajustamos o tamanho para 5 e então visualizamos.

# 05. verificar a correlacao entre as features e a target
features = ['rotacao_rpm', 'vibracao_mm_s', 'temperatura_c', 'corrente_a', 'falha']
df[features].corr()
fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(df[features].corr(), annot=True, fmt=".2f", cmap="coolwarm", ax=ax)
ax.set_title("Correlacao entre Features e Target")
plt.tight_layout()
plt.show()

Ao observar a coluna falha, verificamos correlação de 0,48 com vibracao, 0,36 com temperatura e 0,04 com corrente. Isso indica que temos capacidade de desenvolver um modelo, pois identificamos correlações suficientes entre nossas variáveis e o objetivo, que aqui é o nosso target (alvo), a falha.

O ponto é: se tivermos um dataset (conjunto de dados) em que a variável objetivo que queremos prever não tem correlação com as demais variáveis, provavelmente teremos dificuldade para desenvolver um modelo que a preveja. Sem correlação entre as variáveis explicativas e a variável objetivo, o cenário é desfavorável para desenvolver modelos de machine learning (aprendizado de máquina). Neste caso, porém, temos boa correlação entre nossas variáveis e a falha.

Investigando correlações para sobreaquecimento

Ainda assim, essa análise está geral. Podemos tentar entender, por exemplo, quais são as variáveis que mais influenciam a ocorrência de falhas por sobreaquecimento. Esperamos que seja a temperatura. Para isso, vamos definir qual será nossa falha_objetivo. Dentro do nosso df, onde temos o tipo de falha, percebemos que ainda não adicionamos a coluna tipo_falha, cujo objetivo é permitir filtragem. Não há problema, pois temos a coluna falha. Se criarmos um DataFrame filtrando falha == 2 e plotarmos, teremos um DataFrame que contém apenas dados relacionados à falha do tipo sobreaquecimento.

Podemos então calcular novamente a correlação. Ao calcular df.features com correlação, percebemos que isso estava trazendo a correlação para todos os tipos de falha, enquanto agora queremos verificar a correlação entre variáveis e o alvo apenas para as falhas de sobreaquecimento. Se atribuirmos esse subconjunto a features_objetivo, veremos que não funciona bem porque falha agora tem apenas um valor (sempre igual a 2), o que inviabiliza o cálculo de correlação com o alvo constante. Portanto, não é esse tipo de correlação que vamos usar.

Vamos utilizar corrwith, pois agora queremos a correlação com algo específico. Passamos a correlação com falha_objetivo usando falha. Ao exibir o resultado, inicialmente não obtemos nada porque precisamos passar uma sequência de valores; usamos então uma tupla. Em seguida, percebemos que a correlação de falha com falha não faz sentido. Para resolver, excluímos falha do conjunto features. Para não incluir a última variável em features, utilizamos fatiamento para remover a última coluna.

Primeiro, uma tentativa que não funciona bem ao passar um DataFrame filtrado:

falha_alvo = df[df["falha"] == 2]
df[features].corrwith(falha_alvo)

Em seguida, percebemos que precisamos de uma série booleana que represente o alvo (falha de sobreaquecimento). Trocamos para uma máscara booleana:

falha_alvo = df["falha"] == 2
df[features].corrwith(falha_alvo)

Agora sim: para calcular apenas sobre as variáveis explicativas, removemos falha de features usando fatiamento e, então, ordenamos o resultado:

features = ['rotacao_rpm', 'vibracao_mm_s', 'temperatura_c', 'corrente_a']
falha_alvo = df["falha"] == 2
df[features].corrwith(falha_alvo).sort_values(ascending=False)

Agora sim: para a falha de sobreaquecimento, a variável com maior correlação é temperatura (0,53), seguida de corrente. Essas duas variáveis predominam entre as que têm efeito sobre o sobreaquecimento, o que é coerente.

Avaliando o comportamento temporal por motor

Vamos seguir com nossas análises. Até aqui, não observamos o comportamento temporal. É isso que faremos agora: avaliação do comportamento temporal para cada variável em relação a cada tipo de falha. Para isso, executamos um laço for para cada variável que vamos analisar (neste caso, vibracao, temperatura e corrente). Para cada uma, geramos um plot mostrando a evolução em função do tempo e marcamos os tipos de falha. O tamanho inicial ficou grande; ajustamos para 3, o que melhorou. Assim, para cada variável, visualizamos a ocorrência das falhas e em que momento ocorrem. Em geral, todos os eventos estão agrupados em um mesmo intervalo de tempo, o que permite até um cômputo aproximado das ocorrências.

Para reproduzir essa visualização temporal por motor, você pode usar:

# 08. avaliacao do comportamento temporal para cada variavel em relacao a cada tipo de falha
motor_id = 1
motor_df = df[df["motor_id"] == motor_id].sort_values("timestamp")

for feature in ['vibracao_mm_s', 'temperatura_c', 'corrente_a']:
    fig, ax = plt.subplots(figsize=(12, 4))
    for falha_val, group in motor_df.groupby("falha"):
        ax.scatter(group["timestamp"], group[feature],
                   label=label_map[falha_val], s=5)
    ax.set_title(f"Motor {motor_id} - {feature} ao longo do tempo")
    ax.set_xlabel("Timestamp")
    ax.set_ylabel(feature)
    ax.legend(markerscale=3)
    plt.tight_layout()
    plt.show()

Gerando boxplots por tipo de falha e discutindo outliers

Também nos interessa fazer um boxplot (diagrama de caixas). Executamos, então, um processo para gerar um boxplot para cada variável em relação ao tipo de falha, novamente usando um laço for para cada uma das nossas variáveis. Lembramos de remover falha, que é a última coluna. Ao tentar executar, tivemos um problema: faltava a coluna tipo_falha no df. Precisamos incluí-la antes.

Incluímos a coluna tipo_falha em df mapeando os rótulos com map e label_map. Após executar e visualizar, confirmamos a presença de tipo_falha (por exemplo, “Normal, Normal, Normal”) e então o procedimento passou a funcionar.

# Incluindo a coluna tipo_falha dentro do df
df['tipo_falha'] = df['falha'].map(label_map)

Agora, geramos os boxplots para cada variável:

# 09. boxplot para cada variavel em relacao a cada tipo de falha
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
for ax, col in zip(axes.flat, features[:-1]):
    sns.boxplot(data=df, x="tipo_falha", y=col, hue="tipo_falha",
                legend=False, ax=ax, palette="tab10")
    ax.set_title(col)
    ax.set_xlabel("")
    ax.set_ylabel(col)
    ax.tick_params(axis='x', rotation=15)
plt.suptitle("Distribuicao das Features por Tipo de Falha", y=1.01)
plt.tight_layout()
plt.show()

Os resultados mostram que, para rotacao, há muitos valores discrepantes (outliers), exceto para falha mecânica. Para vibracao, observamos outliers em sobreaquecimento, operação normal e desbalanceamento. Para temperatura, a maioria dos outliers está relacionada à falha mecânica, havendo também ocorrências em sobreaquecimento. Para corrente, aparecem outliers para todos os tipos de falha.

Seria razoável pensar em um tratamento para remover outliers. Porém, observamos um problema: há grande concentração de outliers quando o motor opera fora de condições de normalidade. Se removermos outliers de todas as variáveis, podemos eliminar muitos dados que descrevem a ocorrência de falhas, retirando leituras importantes exatamente nos momentos de falha. Isso pode ser problemático para desenvolver o modelo. Portanto, decidimos não remover os outliers.

Visualizando séries temporais agregadas

Vale também fazer uma avaliação temporal sobre todas as variáveis sem o mapeamento por tipo de falha, apenas verificando o comportamento em função do tempo. Para isso, tomamos nossas features (conjunto já mostrado anteriormente) e removemos falha. Para cada feature em features, exibimos uma indicação no console e geramos um gráfico com plt.plot, usando timestamp no eixo x e a feature no eixo y. Isso funcionou, exibindo a primeira variável e, em seguida, as demais.

Primeiro, imprimimos cada feature que será exibida:

# 10. evolucao temporal das features em funcao do tempo
for feature in features[:-1]:
    print(f"Feature: {feature}")

Em seguida, fazemos os gráficos de linha, adicionando título, eixos, legenda e layout:

for feature in features[:-1]:
    plt.figure(figsize=(12, 4))
    plt.plot(df['timestamp'], df[feature], label=feature)
    plt.title(f"Evolucao Temporal de {feature}")
    plt.xlabel("Timestamp")
    plt.ylabel(feature)
    plt.legend()
    plt.tight_layout()
    plt.show()

Em seguida, adicionamos mais informações: título, rótulo do eixo de timestamp, rótulo da feature, legenda e, ao final, show. Ainda assim, a visualização fica pouco informativa para leitura rápida; seria necessário observar com mais atenção e em maior detalhe. De modo geral, é o comportamento esperado de séries temporais: não teremos um alinhamento a uma função convencional, mas sim variações em torno da média.

Encerrando a aula e antecipando os próximos passos

Vimos os dados de vibracao, temperatura, a evolução temporal de corrente e, por fim, estes são os dados com os quais vamos trabalhar para desenvolver nosso modelo.

Com isso, encerramos esta aula. Na próxima aula, retornaremos já com conhecimento sobre quais são os dados e qual é a estrutura básica deles. Em seguida, vamos desenvolver uma versão inicial de um modelo para fazer a predição e entender como se comportam essas anomalias.

Eu agradeço a atenção e nos vemos na próxima aula.

Sobre o curso Projeto de engenharia de machine learning: repositório, Docker e deploy na AWS com FastAPI

O curso Projeto de engenharia de machine learning: repositório, Docker e deploy na AWS com FastAPI possui 242 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de MLOps e Operacionalização em Dados, ou leia nossos artigos de Dados.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda MLOps e Operacionalização acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas