DISCIPLINA: Tópicos Especiais em Engenharia de Produção 5 (Machine Studying)
ALUNOS: Luiz Hanrry e Maria Vitória
Primeiramente, faz-se necessário explicar o conteúdo do banco de dados que será utilizado no trabalho. O 3W é um dataset realista e público que contém informações sobre eventos raros e indesejáveis em poços de petróleo. Para facilitar o entendimento, os 10 estados em que um poço de petróleo pode se encontrar no banco de dados serão explicados:
0- Funcionamento regular.
1- Aumento Abrupto do BSW: Aumento abrupto do BSW (Primary Sediment and Water, ou Sedimentos Básicos e Água).
2- Fechamento Espúrio da DHSV: Fechamento inesperado da válvula de segurança do poço (Downhole Security Valve, ou DHSV).
3- Extreme Slugging (Golpe de Líquido Grave): Oscilações severas no fluxo causadas pela formação alternada de bolhas de gás e líquido na tubulação.
4- Instabilidade de Fluxo: Alterações irregulares no fluxo de produção.
5- Perda Rápida de Produtividade: Redução súbita na produtividade do poço.
6- Restrição Rápida no PCK: Restrição súbita no estrangulador de produção (Manufacturing Choke, ou PCK).
7- Incrustação no PCK: Formação de incrustações no estrangulador de produção (PCK).
8- Formação de Hidrato na Linha de Produção: Formação de hidratos (compostos sólidos) na linha de produção.
9- Formação de Hidrato na Linha de Serviço: Formação de hidratos na linha de serviço.
Observação: Existem observações que podem considerar o estado como transitório, ou seja, pode estar indo para algum outro estado, mas ainda não está de fato nele. Estes estados são representados por um 10 na frente, por exemplo: 109 — O poço está transitando para o estado 9.
São coletados em cada observação os seguintes dados:
- timestamp: Momento no qual a observação foi gerada.
- ABER-CKGL: Abertura do GLCK (estrangulador de gasoline elevate) [%].
- ABER-CKP: Abertura do PCK (estrangulador de produção) [%].
- ESTADO-DHSV: Estado da válvula de segurança de fundo (DHSV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-M1: Estado da válvula mestra de produção (PMV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-M2: Estado da válvula mestra do anular (AMV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-PXO: Estado da válvula pig-crossover (PXO). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-SDV-GL: Estado da válvula de bloqueio de gasoline elevate (SDV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-SDV-P: Estado da válvula de bloqueio de produção (SDV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-W1: Estado da válvula lateral de produção (PWV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-W2: Estado da válvula lateral do anular (AWV). Valores possíveis:
[0, 0.5 ou 1]
. - ESTADO-XO: Estado da válvula crossover (XO). Valores possíveis:
[0, 0.5 ou 1]
. - P-ANULAR: Pressão no anular do poço [Pa].
- P-JUS-BS: Pressão a jusante da SP (bomba de serviço) [Pa].
- P-JUS-CKGL: Pressão a jusante do GLCK (estrangulador de gasoline elevate) [Pa].
- P-JUS-CKP: Pressão a jusante do PCK (estrangulador de produção) [Pa].
- P-MON-CKGL: Pressão a montante do GLCK (estrangulador de gasoline elevate) [Pa].
- P-MON-CKP: Pressão a montante do PCK (estrangulador de produção) [Pa].
- P-MON-SDV-P: Pressão a montante da SDV de produção [Pa].
- P-PDG: Pressão no PDG (sensor permanente de fundo do poço) [Pa].
- PT-P: Pressão a jusante da PWV (válvula lateral de produção) no tubo de produção [Pa].
- P-TPT: Pressão no TPT (transdutor de temperatura e pressão) [Pa].
- QBS: Vazão na SP (bomba de serviço) [m³/s].
- QGL: Vazão de gasoline elevate [m³/s].
- T-JUS-CKP: Temperatura a jusante do PCK (estrangulador de produção) [°C].
- T-MON-CKP: Temperatura a montante do PCK (estrangulador de produção) [°C].
- T-PDG: Temperatura no PDG (sensor permanente de fundo do poço) [°C].
- T-TPT: Temperatura no TPT (transdutor de temperatura e pressão) [°C].
- class: Rótulo da observação.
- state: Estado operacional do poço.
A priori, tem-se que fazer a importação dos dados que serão utilizados no treinamento e no teste do modelo, basta fazer o obtain do dataset do 3w que está disponível no GitHub.
No banco de dados 3w, a fim de que as informações contidas nele representem da fato os estados do poço, os dados não são tratados de nenhuma forma. Por esse motivo, os dados apresentam valores NaN (Not a Quantity). Assim, tem-se que retirar ou modificar esses valores para que a próxima etapa seja realizada. Isso foi realizado da seguinte forma:
def limpar_dados(df, estrategia_nan='interpolar'):df['class'] = df.pop('class')
df = df.dropna(subset=['class']) # Take away linhas com NaN na classe
# Remoção de colunas com muitos NaN (>15%)
total_linhas = len(df)
colunas_remover = [col for col in df.columns
if df[col].isnull().sum() / total_linhas > 0.15]
if colunas_remover:
print(f"Colunas removidas por excesso de NaNs: {colunas_remover}")
df = df.drop(colunas_remover, axis=1)
Para que uma coluna fosse aceita ela teria ter menos que 15% da quantidade whole de dados sendo NaNs, caso contrario a coluna seria completamente retirada do dataframe. As colunas aceitas tiveram seus valores NaNs substituídos através de interpolação.
Das 28 características (options) as seguintes foram eliminadas:
Por fim, 6 colunas / características foram analisadas, sendo elas : P-MON-CKP, P-PDG, P-TPT, T-JUS-CKP, T-TPT, state.
Continuando o tratamento dos dados, foram utilizadas janelas deslizantes para séries temporais, de modo a preparar os dados para o modelo. Além disso, foram removidos os valores infinitos, enquanto os outliers foram substituídos por limites baseados em percentis.
def criar_janelas(information, window_size=10, step=1, target_col=None):if isinstance(information, pd.DataFrame):
data_array = information.values
if isinstance(target_col, str):
target_col = information.columns.get_loc(target_col)
else:
data_array = np.array(information)
X, y = [], []
n_samples = len(data_array)
for i in vary(0, n_samples - window_size, step):
window = data_array[i:i+window_size]
X.append(window)
if target_col shouldn't be None:
goal = data_array[i+window_size, target_col]
y.append(goal)
X = np.array(X)
if target_col shouldn't be None:
return X, np.array(y)
return X
def tratar_valores_extremos(df):
df = df.substitute([np.inf, -np.inf], np.nan)
# Calcula limites baseados nos percentis 1 e 99
limites = {}
for col in df.select_dtypes(embody=[np.number]).columns:
if col != 'class': # Evita processar a coluna de lessons
q1 = df[col].quantile(0.01)
q99 = df[col].quantile(0.99)
limites[col] = (q1, q99)
# Aplica os limites
for col, (min_val, max_val) in limites.gadgets():
df[col] = df[col].clip(decrease=min_val, higher=max_val)
return df
Um Autoencoder é uma rede neural de aprendizado não supervisionada, projetada para codificar dados de entrada com eficiência até seus recursos essenciais e, em seguida, reconstruir (decodificar) a entrada authentic dessa representação compactada.
O Autoencoder é usado principalmente para aprender compressão de dados e aprende inerentemente uma função de identidade, mas outras aplicações podem ser:
- Remover ruído de uma imagem (redução de ruído);
- Redução de dimensionalidade, agrupamento e em sistemas de recomendação;
- Os autocodificadores são usados como um extrator de recursos para tarefas posteriores, como classificação e detecção.
A Lengthy Brief-Time period Reminiscence (LSTM) é um tipo de rede neural recorrente (RNN) projetada para capturar padrões em sequências de dados ao longo do tempo, mitigando o problema do desvanecimento do gradiente que afeta as RNNs tradicionais. Seu diferencial está nas células de memória, que utilizam três portas principais:
- Porta de entrada: Controla quais novas informações devem ser armazenadas na célula.
- Porta de esquecimento: Determine quais informações antigas devem ser descartadas.
- Porta de saída: Determina quais informações da célula devem ser utilizadas para gerar a saída da rede.
Essas características tornam a LSTM supreme para lidar com séries temporais, detecção de anomalias, previsão de tendências e modelagem de sequências complexas.
Autoencoder LSTM
Quando combinada com um autoencoder, a LSTM pode ser utilizada para aprendizagem não supervisionada, comprimindo sequências temporais em uma representação latente e depois reconstruindo os dados. O autoencoder LSTM é amplamente empregado em detecção de anomalias, onde o modelo aprende os padrões normais de uma sequência e detecta anomalias ao observar discrepâncias significativas na reconstrução. Isso é particularmente útil para aplicações como manutenção preditiva, detecção de fraudes e análise de comportamento em dados sequenciais. Para este estudo, O autoencoder LSTM é utilizado para a detecção de anomalias no dataset 3W.
Aplicação
import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import (accuracy_score, precision_score, recall_score,
f1_score, confusion_matrix, roc_auc_score)
import tensorflow as tf
from tensorflow.keras.fashions import Mannequin
from tensorflow.keras.layers import Enter, LSTM, Dense, RepeatVector, TimeDistributed
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.optimizers import Adam
from sklearn.preprocessing import StandardScaler, MinMaxScaler
Para a inicialização do estudo, os dados foram carregados e em seguida os valores extremos foram removidos. Com a remoção das colunas com mais de 15% de NaNs, os demais, presentes nas colunas restantes, foram preenchidos by way of interpolação.
As janelas temporais foram definidas com tamanho 10, deslocamento de 5 posições por vez. A variável “rotulos”, que contém os valores da coluna “class”, é usada para treinamento de modelos de Machine Studying.
dados_limpos = limpar_dados(dados, estrategia_nan='interpolar')
window_size = 10
step_size = 5
dados_window, rotulos = criar_janelas(dados_limpos, window_size, step_size, target_col='class')
A função “convert_to_float” converte um array de objetos para float, tratando valores inválidos. Primeiro, tenta a conversão direta e, se falhar, cria um array de zeros e percorre os elementos individualmente.
def convert_to_float(arr):attempt:
# Tenta converter diretamente
transformed = np.asarray(arr, dtype=np.float32)
besides ValueError:
# Se falhar, converte elemento por elemento
transformed = np.zeros(arr.form, dtype=np.float32)
for idx in np.ndindex(arr.form):
attempt:
transformed[idx] = float(arr[idx])
besides (ValueError, TypeError):
transformed[idx] = 0.0
return transformed
dados_window = convert_to_float(dados_window)
rotulos = np.asarray(rotulos, dtype=np.float32)
Na sequência é aplicada a normalização aos dados. A ação é caracterizada por 3 ações:
- Achatar os dados: Converte “dados_window” de formato
(n_samples, n_steps, n_features)
para(n_samples * n_steps, n_features)
, permitindo que o scaler normalize cada characteristic corretamente. - Aplica a normalização: Usa
StandardScaler
(média 0, desvio 1) para padronizar os dados. - Reshape para o formato authentic: Após a normalização, os dados são remodelados de volta para
(n_samples, n_steps, n_features)
, mantendo a estrutura necessária para modelos de séries temporais.
n_samples, n_steps, n_features = dados_window.form
data_reshaped = dados_window.reshape(-1, n_features)scaler = StandardScaler()
dados_window = scaler.fit_transform(data_reshaped)
dados_window = dados_window.reshape(n_samples, n_steps, n_features)
A partir de agora, seguindo os passos do artigo “Bettering efficiency of one-class classifiers utilized to anomaly detection in oil effectively”, será executado o Autoencoder com LSTM para a detecção de anomalias, onde o modelo será treinado apenas com dados normais e depois testado em um conjunto equilibrado.
Divisão de dados:
- 80% para treinamento e 20% para validação (lembrando que o treinamento é feito apenas com dados normais).
- O conjunto de teste é formado por 50% de dados normais e 50% anômalos.
x_normal = dados_window[rotulos == 0]
x_anomaly = dados_window[rotulos != 0]x_train, x_val = train_test_split(x_normal, test_size=0.2, random_state=42)
n_test_samples = min(len(x_val), len(x_anomaly))
x_test = np.concatenate([x_val[:n_test_samples], x_anomaly[:n_test_samples]])
y_test = np.concatenate([np.zeros(n_test_samples), np.ones(n_test_samples)])
É construído e treinado o modelo com LSTM para detectar as anomalias. O encoder usa uma camada LSTM. Após a definição de parâmetros, inicia-se a construção do modelo.
O encoder usa uma camada LSTM seguida de uma “dense”. Enquanto o Decoder usa “RepeatVector” para expandir a saída e uma LSTM para reconstrução.
timesteps = window_size
n_features = x_train.form[-1]
latent_dim = 16
encoder_units = 32
decoder_units = 16
learning_rate = 0.0001
batch_size = 64
epochs = 50inputs = Enter(form=(timesteps, n_features))
encoded = LSTM(encoder_units, activation='tanh', return_sequences=False)(inputs)
encoded = Dense(latent_dim, activation='tanh', title='latent_space')(encoded)
decoded = RepeatVector(timesteps)(encoded)
decoded = LSTM(decoder_units, activation='tanh', return_sequences=True)(decoded)
decoded = TimeDistributed(Dense(n_features))(decoded)
O modelo é compilado com o otimizador “Adam” e erro quadrático médio (MSE) como função de perda.
autoencoder = Mannequin(inputs, decoded, title='lstm_autoencoder')
autoencoder.compile(optimizer=Adam(learning_rate=learning_rate), loss='mse')
O autoencoder é treinado para reconstruir os dados normais (x_train
→ x_train
). O treinamento ocorre com “EarlyStopping”, para evitar o overfitting, interrompando-o se “val_loss” não melhorar por 5 épocas, restaurando os melhores pesos. Além disso, “x_train” é usado como entrada e saída para aprender a reconstruir dados normais.
historical past = autoencoder.match(
x_train, x_train,
epochs=epochs,
batch_size=batch_size,
shuffle=True,
validation_data=(x_val, x_val),
callbacks=[EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)]
)
O modelo gera as reconstruções das amostras de entrada e calcula o MSE entre os valores reais e os reconstruídos. Caso o “threshold” (limite para classificação de anomalias) não seja fornecido, ele é definido automaticamente como o percentil 95 do erro nos dados normais, garantindo que apenas valores com erro significativamente alto sejam considerados anômalos. Posteriormente, cada amostra é classificada como anômala (True) ou regular (False) com base no MSE em relação ao threshold.
def detectar_anomalias(mannequin, information, threshold=None):
"""Detecta anomalias com base no erro de reconstrução"""
reconstructions = mannequin.predict(information, verbose=0)
mse = np.imply(np.energy(information - reconstructions, 2), axis=(1, 2))if threshold is None:
# Calcula threshold como percentil 95 do erro nos dados normais
reconstructions_train = mannequin.predict(x_train, verbose=0)
mse_train = np.imply(np.energy(x_train - reconstructions_train, 2), axis=(1, 2))
threshold = np.percentile(mse_train, 95)
return mse > threshold, mse, threshold
y_pred, test_mse, threshold = detectar_anomalias(autoencoder, x_test)
Com base nos rótulos verdadeiros, nas previsões do modelo e nos erros de reconstrução, são calculadas métricas de desempenho para a detecção de anomalias. Algumas dessas métricas são: acurácia, precisão, recall, F1, specificity (capacidade de evitar falsos positivos) e AUC (área sob a curva ROC, que mede separabilidade entre lessons).
Outras duas métricas, mais avançadas, foram utilizadas no artigo, são elas: Tempo Médio de Detecção (verifica uma janela de tamanho window_size
(60 por padrão) e considera que a anomalia foi corretamente detectada se pelo menos 40% da janela contiver alertas positivos).
def calcular_metricas(y_true, y_pred, mse_scores, window_size=60):
"""Métricas conforme o artigo"""
# Matriz de confusão
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()# Métricas básicas
metrics = {
'Accuracy': accuracy_score(y_true, y_pred),
'Precision': precision_score(y_true, y_pred),
'Recall': recall_score(y_true, y_pred),
'F1': f1_score(y_true, y_pred),
'Specificity': tn / (tn + fp + 1e-9),
'AUC': roc_auc_score(y_true, mse_scores),
'Threshold': threshold
}
return metrics
# Calcular métricas
metricas = calcular_metricas(y_test, y_pred, test_mse)
print("n=== MÉTRICAS DE DESEMPENHO ===")
for nome, valor in metricas.gadgets():
print(f"{nome}: {valor:.15f}")
Visualizações do modelo