Teste da Rede Neural Artificial

Nesta página será realizado o teste da rede treinada com os dados que foram coletados em circuitos previamente escolhidos.

Todos os dados aqui testados são de vias plurais, garantindo assim o bom desempenho do algoritmo em diversos ambientes.

Configurações

As variáveis de ambiente, bibliotecas e caminhos serão aqui definidos.

Montando o Google Drive

[1]:
from google.colab import drive
import os
drive.mount('/content/drive')
Mounted at /content/drive

Clonando a biblioteca geoglib

A biblioteca geoglib é um módulo desenvolvido pelo autor para manipulações de dados geoespaciais. Para utilizá-la necessitamos clonar a mesma.

[2]:
!git clone https://github.com/GabrielLima1995/geoglib.git
Cloning into 'geoglib'...
remote: Enumerating objects: 54, done.
remote: Counting objects: 100% (54/54), done.
remote: Compressing objects: 100% (38/38), done.
remote: Total 54 (delta 26), reused 42 (delta 14), pack-reused 0
Unpacking objects: 100% (54/54), done.

Clonando a biblioteca Easy_lstm

Há tambem, uma biblioteca chamada easy_lstm, biblioteca essa desenvolvida pelo autor para manipulação e preparação de dados para a rede do tipo lstm. Para utilizá-la necessitamos clonar a mesma.

[3]:
!git clone https://github.com/GabrielLima1995/easy_lstm.git
Cloning into 'easy_lstm'...
remote: Enumerating objects: 17, done.
remote: Counting objects: 100% (17/17), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 17 (delta 5), reused 15 (delta 3), pack-reused 0
Unpacking objects: 100% (17/17), done.

Caminhos necessários

Definimos também os caminhos necessários para os dados de interesse.

[4]:
lib_path = '/content'
[5]:
main_path = '/content/drive/MyDrive/IG2021/'

Import das bibliotecas utilizadas

Importando as bibliotecas e dependências necessárias:

[6]:
!pip install geopandas
Collecting geopandas
  Downloading geopandas-0.9.0-py2.py3-none-any.whl (994 kB)
     |████████████████████████████████| 994 kB 5.2 MB/s
Requirement already satisfied: pandas>=0.24.0 in /usr/local/lib/python3.7/dist-packages (from geopandas) (1.1.5)
Requirement already satisfied: shapely>=1.6 in /usr/local/lib/python3.7/dist-packages (from geopandas) (1.7.1)
Collecting pyproj>=2.2.0
  Downloading pyproj-3.2.0-cp37-cp37m-manylinux_2_24_x86_64.whl (6.3 MB)
     |████████████████████████████████| 6.3 MB 69.0 MB/s
Collecting fiona>=1.8
  Downloading Fiona-1.8.20-cp37-cp37m-manylinux1_x86_64.whl (15.4 MB)
     |████████████████████████████████| 15.4 MB 37 kB/s
Requirement already satisfied: certifi in /usr/local/lib/python3.7/dist-packages (from fiona>=1.8->geopandas) (2021.5.30)
Collecting cligj>=0.5
  Downloading cligj-0.7.2-py3-none-any.whl (7.1 kB)
Requirement already satisfied: six>=1.7 in /usr/local/lib/python3.7/dist-packages (from fiona>=1.8->geopandas) (1.15.0)
Collecting munch
  Downloading munch-2.5.0-py2.py3-none-any.whl (10 kB)
Requirement already satisfied: attrs>=17 in /usr/local/lib/python3.7/dist-packages (from fiona>=1.8->geopandas) (21.2.0)
Collecting click-plugins>=1.0
  Downloading click_plugins-1.1.1-py2.py3-none-any.whl (7.5 kB)
Requirement already satisfied: click>=4.0 in /usr/local/lib/python3.7/dist-packages (from fiona>=1.8->geopandas) (7.1.2)
Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from fiona>=1.8->geopandas) (57.4.0)
Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.24.0->geopandas) (2.8.2)
Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.24.0->geopandas) (2018.9)
Requirement already satisfied: numpy>=1.15.4 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.24.0->geopandas) (1.19.5)
Installing collected packages: munch, cligj, click-plugins, pyproj, fiona, geopandas
Successfully installed click-plugins-1.1.1 cligj-0.7.2 fiona-1.8.20 geopandas-0.9.0 munch-2.5.0 pyproj-3.2.0
[7]:
import sys
sys.path.insert(0,lib_path)
[8]:
import pandas as pd
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
from keras.models import load_model
from geoglib import gpd_manipulation
from geoglib import plot_bokeh as plot
from geoglib import osrm
from easy_lstm import preparation,operations
from bokeh.plotting import show
from sklearn.metrics.pairwise import pairwise_distances
from sklearn.cluster import DBSCAN
from bokeh.layouts import gridplot
import joblib

Váriaveis uteis

Seção para definição de alguns parâmetros e variáveis necessárias.

[9]:
cols = ['DataTimeStamp','GPSLong','GPSLat','Vel','ExernalAcelX','ExernalAcelY',
 'ExernalAcelZ','ExernalGyroscopeX','ExernalGyroscopeY','ExernalGyroscopeZ',
 'Pitch','Roll','Yaw']
[10]:
dic = {0:'Circuito 1 - volta 1',
       1:'Circuito 1 - volta 2',
       2:'Circuito 2 - volta 1',
       3:'Circuito 2 - volta 2',}
[11]:
test_files = os.listdir(main_path + 'circuit/')
test_files.sort()
[12]:
endpoint='http://router.project-osrm.org'
[13]:
threshold = 0.3

Definições e inicializações graficas

Seção para inicialização do método gráfico da biblioteca geoglib e afins.

[14]:
plt.style.use('dark_background')
[15]:
plot._initialize()

Carregamento dos dados

Seção para load dos dados de teste e parâmetros da rede, como seus pesos e scalers.

Carregamento do scaler de Treino

Para a normalização dos dados de teste será utilizado o scaler de treino, para que assim possamos garantir que as ordens de grandeza sejam respeitadas.

[16]:
my_scaler = joblib.load(main_path + 'usados/scaler.save')

Carregamento da Rede Treinada

Para a inferência será utilizada a rede que foi treinada previamente.

[17]:
model = load_model(main_path + 'usados/treino_1.h5')

Para ver a rede que foi instanciada, fazemos:

[18]:
model.summary()
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         [(None, 30, 10)]          0
_________________________________________________________________
lstm (LSTM)                  (None, 30, 16)            1728
_________________________________________________________________
lstm_1 (LSTM)                (None, 4)                 336
_________________________________________________________________
repeat_vector (RepeatVector) (None, 30, 4)             0
_________________________________________________________________
lstm_2 (LSTM)                (None, 30, 4)             144
_________________________________________________________________
lstm_3 (LSTM)                (None, 30, 16)            1344
_________________________________________________________________
time_distributed (TimeDistri (None, 30, 10)            170
=================================================================
Total params: 3,722
Trainable params: 3,722
Non-trainable params: 0
_________________________________________________________________

Carregamento dos dados de teste

Os dados de Teste serão aqui lidos pela biblioteca pandas.

Cada circuito possui duas voltas, onde cada volta será armazenada em 1 dataframe.

[19]:
dataframes = [pd.read_csv(main_path + 'circuit/{}'.format(files),delimiter=';',
                          usecols= cols) for files in test_files]
[20]:
for  i in dataframes:
  print(i.shape)
(7488, 13)
(6529, 13)
(2725, 13)
(2719, 13)

Análise da Integridade dos dados carregados

Para garantirmos que os dados estão integros verificaremos se há dados nulos.

[21]:
for i,val in enumerate(dataframes):
  print('{} - Número de dados Nulos ou faltantes : {}'.\
        format(dic[i],val.isna().sum(axis=1).sum()))
Circuito 1 - volta 1 - Número de dados Nulos ou faltantes : 0
Circuito 1 - volta 2 - Número de dados Nulos ou faltantes : 0
Circuito 2 - volta 1 - Número de dados Nulos ou faltantes : 0
Circuito 2 - volta 2 - Número de dados Nulos ou faltantes : 0

Preparação dos dados para inferência

Converteremos os dados para a forma que a rede lstm requer e tambem os normalizaremos

[22]:
list_test =[]
for i in dataframes:
  test = preparation.to_lstm([i],30,cols)
  list_test.append(test)
[23]:
list_X_test =[]
for i in list_test :
  X_test = operations.normalize_transform(i[:,:,3:],my_scaler)
  list_X_test.append(X_test)
[24]:
for i,val in enumerate(list_X_test):
  print('{} - Shape do dataframe preparado para lstm : {}'.\
        format(dic[i],val.shape))
Circuito 1 - volta 1 - Shape do dataframe preparado para lstm : (7459, 30, 10)
Circuito 1 - volta 2 - Shape do dataframe preparado para lstm : (6500, 30, 10)
Circuito 2 - volta 1 - Shape do dataframe preparado para lstm : (2696, 30, 10)
Circuito 2 - volta 2 - Shape do dataframe preparado para lstm : (2690, 30, 10)

Inferência dos dados

Passaremos os dados pela rede treinada para obtermos a reconstrução dos dados.

[25]:
list_X_test_pred =[]
for i in list_X_test:
  X_test_pred = model.predict(i)
  list_X_test_pred.append(X_test_pred)

Calculo do Erro absoluto médio

Calcularemos aqui o erro absoluto médio para a reconstrução de cada volta.

[26]:
for i in range(len(list_X_test)): list_X_test[i] = list_X_test[i][:,1,:]
[27]:
for i in range(len(list_X_test_pred)):
  list_X_test_pred[i] = list_X_test_pred[i][:,1,:]
[28]:
list_test_loss = []
for i in range(0,4):
  test_loss = np.mean(np.abs(list_X_test_pred[i] - list_X_test[i]),axis=1)
  list_test_loss.append(test_loss)

Detecção de Anomalias

Os pontos que contém anomalias são definidos como sendo os pontos com o valor do erro absoluto médio maior que o limiar estudado.

[29]:
list_defeitos = []
for i in list_test_loss:
  defeitos = i > threshold
  list_defeitos.append(defeitos)
[30]:
for i,val in enumerate(list_defeitos):
  print('{} - Número de defeitos detectados : {}'.format(dic[i],val.sum()))
Circuito 1 - volta 1 - Número de defeitos detectados : 56
Circuito 1 - volta 2 - Número de defeitos detectados : 57
Circuito 2 - volta 1 - Número de defeitos detectados : 28
Circuito 2 - volta 2 - Número de defeitos detectados : 39
[31]:
list_index=[]
for i in list_defeitos:
  index = i.nonzero()
  list_index.append(index)

Criaremos então um dataframe com os defeitos geolocalizados de cada circuito para posteriormente podermos comparar com o ground truth.

[32]:
defeitos_dataframe_list = [pd.DataFrame(list_test[i][list_index[i][0],0,:3],
                                        columns=cols[:3]) for i in range(4)]

Converteremos os dataframes em geodataframes.

[33]:
defeitos_geodataframe_list=[]
for i in defeitos_dataframe_list:
  defeitos_geodataframe  = gpd_manipulation.\
  create_geopandas(i,['GPSLong','GPSLat'],'EPSG:4326','EPSG:4326')
  defeitos_geodataframe_list.append(defeitos_geodataframe)

Correção da rota via OSRM

Frequentemente os pontos coletados em campo não são plotados no devido lugar, pois há um erro intrínseco do GPS. Por esse motivo será utilizado uma API chamada OSRM, onde a mesma corrigirá as localizações (latitude e longitude) através da rota realizada.

[34]:
list_OSRM=[]
for i in defeitos_geodataframe_list:
  OSRM = osrm.Route(i,geometry='geometry',base_url=endpoint)
  list_OSRM.append(OSRM)

Visualização dos Defeitos sem agrupamento

Plotaremos então os defeitos brutos (sem agrupamento)

[35]:
list_figures=[]
for i,val in enumerate(list_OSRM):
  fig = plot.geo_circle(geodataframe=val,title='Defeitos - {}'.format(dic[i]),
                       w=300,h=300,tile=True,alpha=0.4,circle_size=5,
                       circle_color='red')
  list_figures.append(fig)
show(gridplot([list_figures[:2],list_figures[2:]]))

Agrupamento das anomalias detectadas.

Os defeitos detectados normalmente são plotados mais que uma vez no mesmo ponto, isso se dá por diversos motivos (as janelas temporais possuem memórias, erros de gps, etc) e para minimizar esse erro agruparemos as anomalias espacialmente.

[36]:
coords_list=[]
for i in list_OSRM:
  coords = i[['OSRMLong', 'OSRMLat']].values
  coords_list.append(coords)
[37]:
distance_matrix_list=[]
for i in coords_list:
  distance_matrix=pairwise_distances(i,metric=gpd_manipulation.\
                                     distance_in_meters)
  distance_matrix_list.append(distance_matrix)
[38]:
dbscan_list=[]
for i in distance_matrix_list:
  dbscan = DBSCAN(metric='precomputed', eps=25, min_samples=1)
  dbscan_list.append(dbscan.fit(i))
[39]:
for i,val in enumerate(list_OSRM):
  val['id'] = dbscan_list[i].labels_
[40]:
defeitos_agrupados_list=[]
for i in list_OSRM:
  defeitos_agrupados = i[~(i['id'] == -1)].groupby(by='id').first()
  defeitos_agrupados_list.append(defeitos_agrupados)

Visualização dos Defeitos com agrupamento

Plotaremos então os defeitos líquidos (agrupados)

[41]:
list_figures_2=[]
for i,val in enumerate(defeitos_agrupados_list):
  fig = plot.geo_circle(geodataframe=val,title='Defeitos - {}'.format(dic[i]),
                       w=300,h=300,tile=True,alpha=0.4,circle_size=5,
                       circle_color='red')
  list_figures_2.append(fig)
show(gridplot([list_figures_2[:2],list_figures_2[2:]]))