A versão deste post em Jupyter Notebook pode ser acessada aqui.
Vamos à solução:
Geração de dados
O método gen_data recebe como parâmetro de entrada o número de pontos que devem ser gerados para a simulação da rede neural. É gerado um DataFrame no qual cada coluna corresponde, respectivamente aos valores de: x,y,y∗exp(sin(x)),∗sin(x)∗cos(y)x,y,y∗exp(sin(x)),∗sin(x)∗cos(y) e sen(x)+cos(y)sen(x)+cos(y). Cada uma dessas colunas foi rotulada, respectivamente, de X, Y, Z1, Z2 e Z3 .
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from random import seed
from matplotlib import cm
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
seed(128)
def gen_data(sampleSize):
# Criacao dos dados
data = np.zeros((sampleSize, 5))
for i in range(sampleSize):
x = np.random.uniform(0, 2*np.pi)
y = np.random.uniform(0, 2*np.pi)
data[i] = [x, y, y * np.exp(np.sin(x)), np.sin(x) * np.cos(y), np.sin(x) + np.cos(y)]
return data
samples = 1500
dataset = gen_data(samples)
data = pd.DataFrame(dataset)
data.columns = ['X','Y','Z1','Z2','Z3']
Normaliza os valores dos dados
Para realizar o treinamento da rede neural de modo mais fácil, os valores de cada uma das variáveis deverá ser normalizado. Ou seja, os valores normalizados serão tais que a média deverá ser zero e o desvio padrão 1.
scaled_features = {}
for each in data:
mean, std = data[each].mean(), data[each].std()
scaled_features[each] = [mean, std]
data.loc[:, each] = (data[each] - mean)/std
Separando a amostra nos conjuntos de treinamento, teste e validação.
Será separado 10% do conjunto de dados para teste.
# 10% dos dados
test_data = data[-int(data.shape[0]*.10):]
# Now remove the test data from the data set
data = data[:-int(data.shape[0]*.10)]
# Separate the data into features and targets
target_fields = ['Z1', 'Z2', 'Z3']
features, targets = data.drop(target_fields, axis=1), data[target_fields]
test_features, test_targets = test_data.drop(target_fields, axis=1), test_data[target_fields]
Os dados serão novamente divididos (25% dos dados), porém agora em conjuntos de treinamento e validação.
##### Separa 25% dos dados restantes para validação
train_features, train_targets = features[:-int(data.shape[0]*.25)], targets[:-int(data.shape[0]*.25)]
val_features, val_targets = features[-int(data.shape[0]*.25):], targets[-int(data.shape[0]*.25):]
Construção da Rede Neural
class NeuralNetwork(object):
def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
# configura o número de nós nas camadas de entrada, escondida e saída.
self.input_nodes = input_nodes
self.hidden_nodes = hidden_nodes
self.output_nodes = output_nodes
# inicializa os pesos
self.weights_input_to_hidden = np.random.normal(0.0, self.hidden_nodes**-0.5,
(self.hidden_nodes, self.input_nodes))
self.weights_hidden_to_output = np.random.normal(0.0, self.output_nodes**-0.5,
(self.output_nodes, self.hidden_nodes))
self.lr = learning_rate
# função de ativação sigmoid
self.activation_function = lambda x : 1 / (1 + np.exp(-x)) # Replace 0 with your sigmoid calculation.
def train(self, inputs_list, targets_list):
# 2d array
inputs = np.array(inputs_list, ndmin=2).T
targets = np.array(targets_list, ndmin=2).T
#### Transmite o sinal para frente ####
hidden_inputs = np.dot(self.weights_input_to_hidden,inputs) # sinal para a camada escondida
hidden_outputs = self.activation_function(hidden_inputs) # sinal saindo da camada escondida
final_inputs = np.dot(self.weights_hidden_to_output, hidden_outputs) # signals into final output layer
final_outputs = final_inputs # signals from final output layer
#### Transmite o sinal para trás ####
# Erro da saída
output_errors = targets - final_outputs
# Erro retropropagado.
hidden_errors = np.dot(self.weights_hidden_to_output.T, output_errors ) # error propagado para a camada escondida
hidden_grad = hidden_outputs * (1-hidden_outputs)# gradientes da camada escondida
# Atualiza os pesos com o gradiente descendente
self.weights_hidden_to_output += self.lr * np.dot(output_errors, hidden_outputs.T)
self.weights_input_to_hidden += self.lr * np.dot(hidden_errors*hidden_grad, inputs.T)
def run(self, inputs_list):
# Executa a transmissão do sinal para frente através da rede
inputs = np.array(inputs_list, ndmin=2).T
#### Implementa a transmissão para frente ####
# Camada escondida.
hidden_inputs = np.dot(self.weights_input_to_hidden, inputs) # sinal para dentro da camada escondida
hidden_outputs = self.activation_function(hidden_inputs) # sinal sai da camada escondida
# Camada de saída
final_inputs = np.dot(self.weights_hidden_to_output, hidden_outputs ) # sinal para dentro da camada de saída
final_outputs = final_inputs # sinal saindo da camada final
return final_outputs
cálculo do erro quadrático médio
def MSE(y, Y):
return np.mean((y-Y)**2)
Treinando a rede neural
import sys
from scipy.stats import gmean
### Parâmetros gerais da rede neural ###
epochs = 10000 # número de épocas de treinamento
learning_rate = 0.0001 # taxa de treinamento
hidden_nodes = 4 # número de neurônios na camada escondida
output_nodes = 3 # número de neurônios na camada de saída (3 saídas, 3 neurônios)
N_i = train_features.shape[1] # número de neurônios na camada de entrada
network = NeuralNetwork(N_i, hidden_nodes, output_nodes, learning_rate) # constrói a rede neural
losses = {'train':[], 'validation':[]}
for e in range(epochs):
# Executa um mini-batch aleatório de 80 registros a partir do conjunto de treinamento
batch = np.random.choice(train_features.index, size=100)
for record, target in zip(train_features.ix[batch].values,
train_targets.ix[batch]['Z1']):
network.train(record, target)
batch = np.random.choice(train_features.index, size=80)
for record, target in zip(train_features.ix[batch].values,
train_targets.ix[batch]['Z2']):
network.train(record, target)
batch = np.random.choice(train_features.index, size=80)
for record, target in zip(train_features.ix[batch].values,
train_targets.ix[batch]['Z3']):
network.train(record, target)
# imprime o progresso do treinamento
# a perda no treinamento é a média geométrica das perdas
train_loss = gmean( (MSE(network.run(train_features), train_targets['Z1'].values),
MSE(network.run(train_features), train_targets['Z2'].values),
MSE(network.run(train_features), train_targets['Z3'].values)))
# a perda na validação é a média geométrica das perdas
val_loss = gmean( (MSE(network.run(val_features), val_targets['Z1'].values),
MSE(network.run(val_features), val_targets['Z2'].values),
MSE(network.run(val_features), val_targets['Z3'].values)) )
sys.stdout.write("\rProgresso: " + str(100 * e/float(epochs))[:4] \
+ "% ... Custo do treinamento: " + str(train_loss)[:5] \
+ " ... Custo da validação: " + str(val_loss)[:5])
losses['train'].append(train_loss)
losses['validation'].append(val_loss)
# imprime na tela a seguinte mensagem
# Progresso: 99.9% ... Custo do treinamento: 0.620 ... Custo da validação: 0.660
Desenha gráfico de evolução do treinamento
plt.plot(losses['train'], label='Custo de Treinamento')
plt.plot(losses['validation'], label='Custo de Validação')
plt.legend()
plt.ylim(ymax=1.)

Checando as previsões
Os dados de previsão para a função Z1 serão verificados em relação aos dados de teste. Conforme pode ser visto no gráfico, as previsões ficaram bastante aderentes aos dados.
fig4, ax4 = plt.subplots(figsize=(8,4))
mean, std = scaled_features['Z1']
predictions = network.run(test_features)*std + mean
ax4.plot(predictions[0], label='Previsão Z1')
ax4.plot((test_targets['Z1']*std + mean).values, label='Dados Z1')
ax4.legend()
