Universidade Federal da Bahia - UFBA
Escola Politécnica
Programa de Pós-Graduação em Engenharia Industrial - PEI/UFBA
Departamento de Engenharia Química - DEQ
Ciência dos Dados na Engenharia
ENG438 - Tópicos Especiais em Engenharia Química
Estuo de caso – agrupamento e reconhecimento de padrões em séries univariadas (objetos com o mesmo comprimento)
Os arquivos fault.mat e normal.mat contêm, respectivamente, as matrizes tridimensionais mat_f (33 X 1 X 10) e mat_n (33 X 1 X 60) com 10 e 60 objetos de falha e de partida normal de uma turbina a gás. Cada objeto é uma série temporal com 33 instantes de medição ( 16 min) da temperatura de entrada do gás natural na turbina. Os valores estão nas dimensões originais. Portanto, devem ser normalizados (entre 0 e 1) com base no máximo e no mínimo de toda a amostra.
a) Aplicar o FCM, com distância Euclideana, com 2 e 3 grupos e verificar resultados de classificação (neste caso, é possível usar a função FCM do próprio matlab, ou rodar o fcm com otimização usando o código fá feito em aplicações anteriores);
Antes da aplicação da função FCM é necessário fazer algumas manipulações nos dados. Inicialmente os pacotes de interesse devem ser carregados, depois o banco de dados do estudo de caso importado.
# Bibliotecas----
if(!require("readxl")) install.packages("readxl") ; library(readxl)
if(!require("caret")) install.packages("caret") ; library(caret)
if(!require("e1071")) install.packages("e1071") ; library(e1071)
if(!require("factoextra")) install.packages("factoextra") ; library(factoextra)
if(!require("cluster")) install.packages("cluster") ; library(cluster)
if(!require("dtwclust")) install.packages("dtwclust") ; library(dtwclust)
if(!require("reshape2")) install.packages("reshape2") ; library(reshape2)
if(!require("dplyr")) install.packages("dplyr") ; library(dplyr)
if(!require("ggpmisc")) install.packages("ggpmisc") ; library(ggpmisc)
# Importando dados ----
# Setando a pasta de trabalho
setwd("C:/Caminho/da/sua/pasta/turbina_univariavel")
# Importando dados de falha
falhas <- read_excel("dados_falha.xlsx", col_names = T)
# Importando dados de partida normal
normal <- read_excel("dados_normal.xlsx", col_names = T)
# Juntando os dados
total <- cbind(falhas[2:length(falhas)], normal[2:length(normal)])
head(total[4:8], n=10)
FALSE Falha 4 Falha 5 Falha 6 Falha 7 Falha 8
FALSE 1 61.52344 50.68535 32.88574 45.90960 46.41342
FALSE 2 61.54107 51.65011 32.69213 47.25789 47.76129
FALSE 3 61.56006 52.40750 32.65116 48.49101 49.00912
FALSE 4 61.59234 53.11027 32.48291 49.54768 50.20883
FALSE 5 61.60866 53.60664 32.43006 50.45054 51.29020
FALSE 6 61.66992 54.01379 32.34810 51.17963 52.34450
FALSE 7 61.66992 54.39731 33.17585 51.75291 53.27072
FALSE 8 61.70654 54.62735 37.38317 52.23630 54.10664
FALSE 9 61.70654 54.87338 39.12951 52.69107 54.90797
FALSE 10 61.77979 55.01820 39.70932 52.98671 55.65471
Após a importação dos dados é necessário ainda normalizar e transpor a matriz de dados antes da aplicação das funções.
# Normalizando os dados ----
# Funcao que normaliza os dados Max = 1 e Min = 0
normalizando <- function(x)
{
return((x- min(x)) /(max(x)-min(x)))
}
# Dados totais
dados_t <- normalizando(total)
T_dados_t <- as.data.frame(t(dados_t))
head(dados_t[4:8], n=10)
FALSE V4 V5 V6 V7 V8
FALSE Falha 1 0.8603629 0.8608412 0.8597608 0.8597630 0.8597604
FALSE Falha 2 0.7629974 0.7627568 0.7620365 0.7613162 0.7599978
FALSE Falha 3 0.7128808 0.7179235 0.7229665 0.7261448 0.7299251
FALSE Falha 4 0.8013439 0.8016113 0.8026156 0.8026156 0.8032158
FALSE Falha 5 0.6623099 0.6704462 0.6771200 0.6834064 0.6871772
FALSE Falha 6 0.3241965 0.3233301 0.3219868 0.3355548 0.4045191
FALSE Falha 7 0.6039137 0.6187129 0.6306639 0.6400608 0.6479844
FALSE Falha 8 0.6147510 0.6324763 0.6497578 0.6649400 0.6786420
FALSE Falha 9 0.7175737 0.7190466 0.7203564 0.7220172 0.7305286
FALSE Falha 10 0.1540038 0.1561645 0.1581907 0.1598874 0.1616723
Com os dados importados e normalizados é interessante uma investigação visual antes da aplicação dos métodos para entender o comportamento das turbinas.
# Comportamento geral das turbinas----
g.total <- dados_t %>%
cbind(falhas$Instantes) %>%
melt(id.vars = "falhas$Instantes") %>%
mutate(tempo = `falhas$Instantes`) %>%
select(-`falhas$Instantes`) %>%
ggplot(mapping = aes(x = tempo, y = value)) +
geom_line(mapping = aes(color = variable), size = 0.75) +
theme_light() +
labs(title = "Comportamento da temperatura da turbina de todas as partidas",
x = "Tempo (minutos)", y = "Temperatura",
subtitle = "Obs: Dados normalizados") +
theme(legend.position="none") +
geom_smooth(method = "loess", se = TRUE, color = "black", size = 1.5, linetype = 5)
g.total
g.div <- dados_t %>%
cbind(falhas$Instantes) %>%
melt(id.vars = "falhas$Instantes") %>%
mutate(tempo = `falhas$Instantes`,
operacao = ifelse(grepl("Falha", variable) == T, "Falha", "Normal")) %>%
mutate(operacao = factor(operacao, levels = c("Normal", "Falha"))) %>%
select(-`falhas$Instantes`) %>%
ggplot(mapping = aes(x = tempo, y = value)) +
geom_line(mapping = aes(color = variable), size = 0.75) +
theme_light() +
labs(title = "Comportamento da temperatura da turbina de todas as partidas",
x = "Tempo (minutos)", y = "Temperatura",
subtitle = "Obs: Dados normalizados") +
theme(legend.position="none") +
facet_wrap(facets = ~operacao, nrow = 1) +
geom_smooth(method = "loess", se = TRUE, color = "black", size = 1.5, linetype = 5)
g.div
A representação inicial indica que as partidas que falharam tendem a começar com uma temperatura mais eleveda do que as partidas normais.
Para aplicação do FCM pode-se utilizar a função fanny() do pacote cluster. Um dos argumentos dessa função é o “metric”, através dele é possível definir que a métrica de distância utilizada seja a euclidiana.
# Letra (a)----
# Aplicando FCM - 2 grupos (Distancia Euclidiana)
d_a_2 <- fanny(T_dados_t, k = 2, metric = "euclidean", stand = FALSE)
d1 <- dados_t %>%
cbind(falhas$Instantes) %>%
melt(id.vars = "falhas$Instantes") %>%
mutate(tempo = `falhas$Instantes`,
operacao = ifelse(grepl("Falha", variable) == T, "Falha", "Normal")) %>%
select(-`falhas$Instantes`) %>%
mutate(operacao = factor(operacao, levels = c("Normal", "Falha")))
d2 <- as.data.frame(t(d_a_2$clustering)) %>%
melt() %>%
mutate(op = ifelse(grepl("Normal", variable) == T, 1, 2))
dt.1 <- left_join(d1, d2, by = c("variable")) %>%
mutate(temperatura = value.x, grupo = value.y) %>%
select(-value.x, -value.y) %>%
mutate(grupo = ifelse(grupo == 1, 3, grupo),
grupo = ifelse(grupo == 2, 1, 2))
g.a.1 <- dt.1 %>%
ggplot(mapping = aes(x = tempo, y = temperatura)) +
geom_line(mapping = aes(color = variable), size = 0.75) +
theme_light() +
labs(title = "Comportamento da turbina agrupado por FCM",
x = "Tempo (minutos)", y = "Temperatura",
subtitle = "Distâncias euclidianas e 2 grupos") +
theme(legend.position="none") +
geom_smooth(method = "loess", se = TRUE, color = "black", size = 1.5, linetype = 5) +
facet_wrap(facets = ~grupo, nrow = 1)
g.a.1
# Aplicando FCM - 3 grupos (Distancia Euclidiana)
d_a_3 <- fanny(T_dados_t, k = 3, metric = "euclidean", stand = FALSE)
d3 <- as.data.frame(t(d_a_3$clustering)) %>%
melt()
dt.2 <- left_join(d1, d3, by = c("variable")) %>%
mutate(temperatura = value.x, grupo = value.y) %>%
select(-value.x, -value.y) %>%
mutate(grupo = ifelse(grupo == 3, 4, grupo),
grupo = ifelse(grupo == 1, 3, grupo),
grupo = ifelse(grupo == 4, 1, grupo))
g.a.2 <- dt.2 %>%
ggplot(mapping = aes(x = tempo, y = temperatura)) +
geom_line(mapping = aes(color = variable), size = 0.75) +
theme_light() +
labs(title = "Comportamento da turbina agrupado por FCM",
x = "Tempo (minutos)", y = "Temperatura",
subtitle = "Distâncias euclidianas e 3 grupos") +
theme(legend.position="none") +
geom_smooth(method = "loess", se = TRUE, color = "black", size = 1.5, linetype = 5) +
facet_wrap(facets = ~grupo, nrow = 1)
g.a.2
A aplicação do FCM para 2 grupos segue a tendência indicada na representação inicial, o que se percebe como principal diferença é que após o agrupamento algumas partidas normais que começaram em altas temperaturas foram para o grupo 2, das “falhas”, e as partidas de falha que começaram em baixas temperaturam foram para o grupo 1, das partidas normais.
Já a aplicação para 3 grupos, percebe-se que o grupo das “falhas”, que dessa vez é o 3, permanece sem muita alteração, já o grupo de partidas normais foi dividido em dois. Trazendo essas interpretações para o estudo de caso, pode-se indicar como mais apropriada a aplicação do FCM para 2 grupos, onde é possível investigar o agrupamento que representa a maioria das partidas de falha e o agrupamento de partidas normais.
b) Aplicar o FCM, com a distância DTW, com 2 e 3 grupos. Neste caso a função FCM intrínseca do matlab não se aplica porque está baseada na distância Euclideana (portanto, é necessário rodar otimização calculando a distância do objeto ao centro através do DTW). O matlab possui a função DTW.
Como a função função fanny() do pacote cluster não permite escolher DTW como metrica, a função utilizada para aplicar o FCM é o tsclust() do pacote dtwclust, onde os argumentos “type” e “distance” permitem definir o tipo fuzzy de argupamento e o método DTW para a distância.
# Aplicando FCM usando dtw para 2 grupos
d_dtw.1 <- tsclust(series = T_dados_t, type = "fuzzy", k = 2L, distance = "dtw", centroid = "fcm")
d2.b <- as.data.frame(t(d_a_2$clustering)) %>%
rbind(d_dtw.1@cluster) %>%
slice(-1) %>%
melt() %>%
mutate(op = ifelse(grepl("Normal", variable) == T, 1, 2))
dt.1.b <- left_join(d1, d2.b, by = c("variable")) %>%
mutate(temperatura = value.x, grupo = value.y) %>%
select(-value.x, -value.y)
g.b.1 <- dt.1.b %>%
ggplot(mapping = aes(x = tempo, y = temperatura)) +
geom_line(mapping = aes(color = variable), size = 0.75) +
theme_light() +
labs(title = "Comportamento da turbina agrupado por FCM",
x = "Tempo (minutos)", y = "Temperatura",
subtitle = "DTW e 2 grupos") +
theme(legend.position="none") +
geom_smooth(method = "loess", se = TRUE, color = "black", size = 1.5, linetype = 5) +
facet_wrap(facets = ~grupo, nrow = 1)
g.b.1
# Aplicando FCM usando dtw para 3 grupos
d_dtw.2 <- tsclust(series = T_dados_t, type = "fuzzy", k = 3L, distance = "dtw", centroid = "fcm")
d3.b <- as.data.frame(t(d_a_2$clustering)) %>%
rbind(d_dtw.2@cluster) %>%
slice(-1) %>%
melt()
dt.2.b <- left_join(d1, d3.b, by = c("variable")) %>%
mutate(temperatura = value.x, grupo = value.y) %>%
select(-value.x, -value.y)
g.b.2 <- dt.2.b %>%
ggplot(mapping = aes(x = tempo, y = temperatura)) +
geom_line(mapping = aes(color = variable), size = 0.75) +
theme_light() +
labs(title = "Comportamento da turbina agrupado por FCM",
x = "Tempo (minutos)", y = "Temperatura",
subtitle = "DTW e 3 grupos") +
theme(legend.position="none") +
geom_smooth(method = "loess", se = TRUE, color = "black", size = 1.5, linetype = 5) +
facet_wrap(facets = ~grupo, nrow = 1)
g.b.2
Visivelmente a diferença entre o FCM usando a distância euclidiana e o FCM usando o DTW é bem pequena, então uma matriz de confusão é gerada considerando a abordagem de 2 grupos para os resultados da letra a) e letra b). Para isso a função confusionMatrix() do pacote caret é utilizada.
# Comparando matriz de confusão de A(Euclidiana) x B(DTW)
dx.a <- confusionMatrix(table(d2$value, d2$op))
dx.a <- as.data.frame(t(dx.a$byClass)) %>%
melt()
dx.b <- confusionMatrix(table(d2.b$value, d2.b$op))
dx.b <- as.data.frame(t(dx.b$byClass)) %>%
melt() %>%
left_join(dx.a, by = "variable") %>%
rename("index" = variable, "DTW" = value.x, "Euclidean" = value.y) %>%
melt() %>%
rename("Method" = variable)
g.compare.1 <- ggplot(dx.b) +
geom_point(mapping = aes(x = index, y = value, color = Method), size = 5, alpha = 0.6) +
theme_light() +
labs(title = "Comparing: DTW vs Euclidean",
x = "", y = "Value") +
theme(legend.position="top") +
theme(axis.text.x = element_text(angle = 90))
g.compare.1
Observando o resultado gráfico é possível indicar o método DTW como superior. Apenas na prevalência os métodos se equivalem, em todos os outrso o DTW apresenta um desempenho superior.
1 - Verificar se houve coalescência/fusão entre os grupos (o que indica que não houve sucesso no agrupamento);
O índice de silhueta varia de −1 a +1, em que um valor alto indica que o objeto é bem compatível com seu próprio cluster e mal combinado com os clusters vizinhos. Se a maioria dos objetos tiver um valor alto, a configuração de cluster será apropriada. Então através da função fviz_silhouette() do pacote factoextra é possível representar gráficamente o índice para os objetos dos grupos.
# Avaliando a qualidade do agrupamento para 2 grupos
g.sil.2 <- fviz_silhouette(d_a_2, palette = "jco", ggtheme = theme_light())
g.sil.2
# Avaliando a qualidade do agrupamento para 3 grupos
g.sil.3 <- fviz_silhouette(d_a_3, palette = "jco", ggtheme = theme_light())
g.sil.3
FALSE cluster size ave.sil.width
FALSE 1 1 23 0.6
FALSE 2 2 47 0.6
FALSE cluster size ave.sil.width
FALSE 1 1 21 0.55
FALSE 2 2 26 0.47
FALSE 3 3 23 0.43
A aplicação do silhueta aos resultados da letra a) indica que as abordagens podem ser consideradas satisfatórias, sendo o agrupamento por 2 grupos o que retorna melhor índice. Trazendo para interpretação do problema, já era esparo que a abordagem por 2 grupos fosse a mais indicada, já que deseja-se entender os padrões de falha.
2 - Verificar os padrões reconhecidos.
g.b.1
Considerando a aplicação do FCM utilizando DTW, já que apresenta um desempenho melhor do que utilizando a métrica euclidiana, para uma abordagem de 2 grupos, por apresentar um índice de silhueta maior do que para 3 grupos, é possível entender que o grupo de interesse ainda mantém a característica da investigação inicial, ou seja, o agrupamento que representa o grupo de partidas que falharam continua com uma curva de tendência parecida, a partida já começa com uma temperatua elevada em comparação com as partidas normais.
confusionMatrix(table(d2.b$value, d2.b$op))$overall
FALSE Accuracy Kappa AccuracyLower AccuracyUpper AccuracyNull
FALSE 0.785714286 0.400000000 0.671314855 0.874816429 0.857142857
FALSE AccuracyPValue McnemarPValue
FALSE 0.963986339 0.009823275
Calculando a acurácia através da função confusionMatrix() do pacote caret que já foi utilizada em outro momento desse mesmo desenvolvimento, o agrupamento em análise retorna um valor de 0.7857.
Se você está tentando reproduzir este código talvez o gráfico “Comparing: DTW vs Euclidean” e os valores de acurácia apresentados não sejam os mesmos que você encontrou. Isso se deve a um problema que encontrei de combinar os levels dos agrupamentos, aparentemente por mais que eu “set” os levels dos grupos, a função nomeia de forma aleatória quem é o “grupo 1” e quem é o “grupo 2”. Seguem abaixo alguns links que podem ajudar a solucionar este problema.
Discentes:
Brenner Silva;
Herica Oliveira;
Lucas Mascarenhas.
Docentes:
Cristiano Fontes;
Karla Esquerre.