O que é uma Rede Neural Artificial? (código Python)

Alex Souza
blog do zouza
Published in
31 min readJan 21, 2022

Esse artigo esta dividido em duas etapas:

  1. Uma breve descrição sobre Redes Neurais e alguns de seus tipos (Fonte e Direitos);
  2. Detalhando a Rede Neural com Backpropagation com exemplo em Python — com base no artigo em: How to Code a Neural Network with Backpropagation In Python (from scratch) by Jason Brownlee in Code Algorithms From Scratch

Rede Neural Artificial (RNA) pode ser definida como uma estrutura complexa interligada por elementos de processamento simples (neurônios), que possuem a capacidade de realizar operações como cálculos em paralelo, para processamento de dados e representação de conhecimento. Seu primeiro conceito foi introduzido em 1943, mas ganhou popularidade algumas décadas depois com a introdução de algoritmos de treinamento como o backpropagation, que permite a realização de um treinamento posterior para aperfeiçoar os resultados do modelo.

Um Breve Histórico — Um histórico resumido sobre Redes Neurais Artificiais deve começar por três das mais importantes publicações iniciais, desenvolvidas por: McCulloch e Pitts (1943), Hebb (1949), e Rosemblatt (1958). Estas publicações introduziram o primeiro modelo de redes neurais simulando “máquinas”, o modelo básico de rede de auto-organização, e o modelo Perceptron de aprendizado supervisionado, respectivamente.

Alguns históricos sobre a área costumam “pular” os anos 60 e 70 e apontar um reinicio da área com a publicação dos trabalhos de Hopfield (1982) relatando a utilização de redes simétricas para otimização e de Rumelhart, Hinton e Williams que introduziram o poderoso método Backpropagation.

Entretanto, para se ter um histórico completo, devem ser citados alguns pesquisadores que realizaram, nos anos 60 e 70, importantes trabalhos sobre modelos de redes neurais em visão, memória, controle e auto-organização como: Amari, Anderson, Cooper, Cowan, Fukushima, Grossberg, Kohonen, von der Malsburg, Werbos e Widrow.

Fonte
Mais informações históricas…

Desde que foi desenvolvida, essa técnica vem sendo amplamente utilizada e validada por diversas áreas de pesquisa que pretendem antever acontecimentos e auxiliar na tomada de decisão. Atualmente, há diversas topologias das RNA’s que buscam resolver diferentes tipos de problemas, tais como:

Contudo, antes de compreender o funcionamento de uma rede neural artificial, é importante entender no que essa técnica está baseada.

Rede neural artificial é uma abstração da rede neural biológica. Seu objetivo não é replicar, mas sim servir de modelo para o aprendizado e resoluções de problemas complexos. Essa inspiração só foi possível, segundo Rojas, porque nos últimos 100 anos, a medicina avançou na compreensão da complexidade do cérebro humano, premiando aproximadamente 10% dos prêmios Nobel de fisiologia e medicina com estudos relacionados a essa área. Através desses estudos, a RNA foi moldada, herdando características biológicas, como alto paralelismo, robustez, tolerância a falhas e aprendizado através de padrões.

Neurônio biológico

O sistema nervoso biológico possui arquiteturas das mais diversas complexidades. Esses sistemas complexos são compostos por células neurais ou neurônios, que desempenham funções diferentes. Tais células nervosas possuem um corpo celular com dois tipos de ramos, os dendritos e axônios, podendo ser visualizado na seção a da próxima imagem.

O corpo celular carrega informações vitais sobre suas características, além de um plasma que possui as substâncias moleculares necessárias para o funcionamento da célula. A comunicação entre os neurônios ocorre através de impulsos captados pelos dendritos, responsáveis por receber a informação e repassar para o corpo da célula através do axônio. O axônio, que se divide em colaterais, recebe sinais a partir do corpo da célula e os transporta para os dendritos que vão repassar para os dendritos de outros neurônios vizinhos através da sinapse, representada na seção b da imagem abaixo.

Img1 — Biologia de uma célula nervosa e seu mecanismo de comunicação.

A relação entre as redes artificiais e biológica é que ambas possuem axônio e dendrito e comunicam-se por sinapses. A representação dessa relação é exibida na imagem abaixo, onde a letra x representa os sinais recebidos e a força sináptica recebida é simbolizada por w. Ambas as redes possuem a capacidade de ajustar a amplitude das sinapses em uma série de camadas interligadas. O modelo artificial apresentado na imagem abaixo representa a mais simples rede neural artificial, chamada de perceptron.

Mais detalhes podem ser vistos em: Estrutura Biológica de um Neurônio

Relação entre uma rede neural biológica e artificial.

Perceptron

Em 1958, a Rede Neural Artificial Perceptron foi introduzida por Frank Rosenblatt, inspirado nos trabalhos de Walter Pitts e Warren Sturgis McCulloch. Esse modelo é um dos mais antigos e lida com um único neurônio, classificando o resultado de forma linear. Na figura abaixo, o neurônio artificial é um Perceptron que recebe diversos valores de entradas y(n). Essas entradas multiplicam-se pelo peso da sinapse w e, no final, somam-se formando um conjunto de entrada ξ = ∑ w * y(n). Esse resultado passa por uma função de ativação linear (que será explicada detalhadamente mais à frente desse artigo) e transmite a saída v. Quando o valor ξ exceder o limite da função de ativação, o neurônio será ativado e retornará um valor.

Visão geral do funcionamento de uma Rede Neural Artificial.

Multilayer Perceptron

Com o intuito de lidar com os problemas não linearmente separáveis, foram adicionadas camadas de neurônio ocultas no modelo de Rosenblatt, formando então a Rede Neural Artificial Multilayer Perceptron (MLP).

Modelo de uma Rede Neural Artificial MLP.

Essa nova topologia funciona como uma rede feedforward (rede progressiva, a saída de um neurônio se conecta com outro neurônio da próxima camada, no sentido esquerda/direita), formada por um conjunto de neurônios denominados “nós”, como demonstrado na imagem ao lado. A rede possui uma camada de entrada (sem função computacional), uma ou mais camadas ocultas e uma camada de saída. A complexidade da rede MLP se dá pela quantidade de camadas ocultas que houver e a quantidade de neurônios que essas camadas possuírem.

A equação da figura realiza o somatório ponderado entre as sinapses de cada neurônio.

O funcionamento geral de uma rede MLP está representada na figura acima. Cada neurônio recebe todos os valores das entradas, representadas pelo símbolo y, que são multiplicadas pelos pesos sinápticos simbolizados pelo w e somadas entre si junto com uma constante chamada de polarização ou bias, representada pelo símbolo b. Essa constante possui o papel de centralizar a curva da função de ativação em um valor conveniente. Caso seja positivo, o movimento do gráfico é realizado para a esquerda, diminuindo o valor do eixo x. Porém, caso seja negativo, o movimento do gráfico é feito para a direita, aumentando o valor do eixo x. A soma ponderada apresentada na equação da imagem acima gera o potencial de ativação que é utilizado para determinar seu valor e propagar para outros neurônios da próxima camada.

Deslocamento do gráfico através da constante bias.

Diferentes tipos de funções para diferentes tipos de redes neurais.

Função de Ativação

Logo após o somatório ponderado dos neurônios, o valor obtido passa por mais um cálculo, chamado de função de ativação ou transferência. Seu objetivo é limitar a amplitude de saída do neurônio, ou seja, o valor obtido no somatório é normalizado dentro de um intervalo fechado, como [0,1], podendo ser interpretado também como a probabilidade do resultado.

Existem diversas funções lineares como a função degrau e identidade e não lineares como a gaussiana, tangente hiperbólica, sigmoide, entre outras, que testam o potencial de ativação através de sua fórmula. Em funções lineares, uma das técnicas mais populares é a função degrau, que determina, através de condições, o resultado final. Ou seja, é possível determinar que se o resultado da soma ponderada for menor ou igual zero, o algoritmo entregará um resultado, caso seja maior, ele entregará outro valor final. As condições dependem do que está se classificando.

Já em funções não lineares, a técnica logística sigmoidal é uma das mais populares em redes MLP e sua fórmula pode ser visualizada na imagem ao lado, no qual o símbolo x representa o valor obtido na soma ponderada do neurônio.

Backpropagation

Quando uma rede neural artificial é inicializada, os pesos sinápticos recebem valores aleatórios que, quando multiplicados pelos valores recebidos, algumas vezes da camada de entrada e outras de neurônios da camada anterior, não atingem os valores desejados no momento do treinamento. Para corrigir os pesos sinápticos, uma das técnicas populares é a retropropagação, conhecido também como backpropagation, que corrige os valores dos pesos pela diferença entre o valor obtido e o esperado (recebido no treinamento) pelo algoritmo, propagando para todas as células nervosas.

Funcionamento do backpropagation.

Em 1986, David Rumelhart e seus colegas introduziram o algoritmo backpropagation, possibilitando o treinamento das redes neurais com diversas camadas através da retropropagação. O processo de correção ocorre em dois passos. No primeiro passo, chamado de feedforward, é introduzido um valor na camada de entrada e outro na camada de saída. O resultado flui dentro das camadas internas até que a resposta seja reproduzida na camada de saída. Já no segundo passo, ocorre o aprendizado da rede, comparando o valor obtido com o valor desejado, através da equação (1), que subtrai o valor esperado (rr) do valor obtido (ro) do neurônio j e, caso o resultado não esteja no padrão aceitável, a rede calcula o erro e propaga a correção para as demais camadas internas até a entrada, ajustando os seus pesos sinápticos.

A técnica backpropagation propaga os sinais de erro na direção oposta ao feedforward, camada a camada, computando os gradientes locais de cada neurônio, visualizado na imagem acima. Esse processo permite que sejam executadas correções nos pesos sinápticos através da equação (2). Nessa equação, o símbolo Δwji representa o novo valor do peso sináptico i do neurônio j, já y simboliza o sinal de entrada da célula nervosa j, pertencente ao neurônio i, η é a razão de aprendizado (um valor definido no momento da configuração da rede)e o δ é o gradiente local.

Para utilizar a equação (2) de ajuste dos pesos e propagar as correções entre as sinapses, é necessário calcular o gradiente local. Nos neurônios da camada de saída, o gradiente local é o sinal de erro da equação (1), multiplicado pela derivada de sua função de ativação, simbolizada por φ(v), conforme a equação (3). Após a correção na camada de saída, utiliza-se a equação (4)para obter o gradiente local dos neurônios predecessores, escondidos na estrutura da rede. Esse processo de correção é recursivo e termina apenas quando o algoritmo de treinamento chega à primeira camada, a de entrada.

Na equação (4), que visa obter os gradientes locais de neurônios internos da rede, a derivada da função de ativação (função logística sigmoidal, tangente hiperbólica , ou outras) representada pelo símbolo φ(v), é multiplicada pelo somatório de outro gradiente local, simbolizado pelo δ, obtido através da retropropagação advinda dos neurônios posteriores ao j, sendo, nesse exemplo, o neurônio k vezes os pesos sinápticos w entre os neurônios j e k.

Depois dessa breve descrição sobre Redes Neurais, vamos falar com mais detalhes sobre o algoritmo de retropropagação (backpropagation), que é utilizado na rede neural artificial de feed-forward clássica.

É a técnica ainda usada para treinar grandes redes de aprendizado profundo (deep learning).

Algoritmo de retropropagação

O algoritmo de retropropagação é um método de aprendizado supervisionado para redes feed-forward multicamadas do campo de redes neurais artificiais.

As redes neurais feed-forward são inspiradas no processamento de informações de uma ou mais células neurais, chamadas neurônios. Um neurônio aceita sinais de entrada por meio de seus dendritos, que passam o sinal elétrico para o corpo da célula. O axônio transmite o sinal para sinapses, que são as conexões do axônio de uma célula aos dendritos de outras células. (Img1.a)

O princípio da abordagem de retropropagação é modelar uma determinada função modificando as ponderações internas dos sinais de entrada para produzir um sinal de saída esperado. O sistema é treinado usando um método de aprendizado supervisionado, em que o erro entre a saída do sistema e uma saída esperada conhecida é apresentado ao sistema e usado para modificar seu estado interno.

Tecnicamente, o algoritmo de retropropagação é um método para treinar os pesos em uma rede neural feed-forward de multicamadas. Como tal, requer que uma estrutura de rede seja definida de uma ou mais camadas em que uma camada esteja totalmente conectada à próxima camada. Uma estrutura de rede padrão é uma camada de entrada, uma camada oculta e uma camada de saída.

A retropropagação pode ser usada para problemas de classificação e regressão, mas focaremos na classificação neste tutorial.

Em problemas de classificação, os melhores resultados são alcançados quando a rede possui um neurônio na camada de saída para cada valor de classe. Por exemplo, um problema de classificação binária ou de 2 classes com os valores de classe de A e B. Essas saídas esperadas teriam que ser transformadas em vetores binários com uma coluna para cada valor de classe. Como [1, 0] e [0, 1] para A e B, respectivamente. Isso é chamado de uma codificação quente.

Conjunto de dados de sementes de trigo

O conjunto de dados de sementes envolve a previsão de espécies com medidas de sementes de diferentes variedades de trigo.

Existem 201 registros e 7 variáveis ​​de entrada numéricas. É um problema de classificação com 3 classes de saída. A escala para cada valor numérico de entrada varia, portanto, pode ser necessária alguma normalização de dados para uso com algoritmos que ponderam entradas como o algoritmo de retropropagação.

Abaixo está uma amostra das 5 primeiras linhas do conjunto de dados.

15.26,14.84,0.871,5.763,3.312,2.221,5.22,1
14.88,14.57,0.8811,5.554,3.333,1.018,4.956,1
14.29,14.09,0.905,5.291,3.337,2.699,4.825,1
13.84,13.94,0.8955,5.324,3.379,2.259,4.805,1
16.14,14.99,0.9034,5.658,3.562,1.355,5.175,1

Usando o algoritmo Zero Rule que prediz o valor de classe mais comum, a precisão da linha de base para o problema é 28,095%.

Você pode aprender mais e baixar o conjunto de dados de sementes no Repositório de Aprendizado de Máquina da UCI .

Faça o download do conjunto de dados de sementes e coloque-o em seu diretório de trabalho atual com o nome de arquivo seeds_dataset.csv .

O conjunto de dados está no formato separado por tabulação, portanto, você deve convertê-lo para CSV usando um editor de texto ou um programa de planilha.

Atualize, faça o download do conjunto de dados no formato CSV diretamente:

Tutorial

Este tutorial está dividido em 6 partes:

  1. Inicialize a rede (Initialize Network).
  2. Propagar para a frente (Forward Propagate).
  3. Erro de propagação traseira (Back Propagate Error).
  4. Rede de treinamento (Train Network).
  5. Prever (Predict).
  6. Estudo de caso do conjunto de dados Seeds.

Essas etapas fornecerão a base necessária para implementar o algoritmo de backpropagation do zero e aplicá-lo aos seus próprios problemas de modelagem preditiva.

1. Inicialize a rede

Vamos começar com algo fácil, a criação de uma nova rede pronta para treinamento.

Cada neurônio tem um conjunto de pesos que precisam ser mantidos. Um peso para cada conexão de entrada e um peso adicional para a polarização. Precisamos armazenar propriedades adicionais para um neurônio durante o treinamento; portanto, usaremos um dicionário para representar cada neurônio e armazenar propriedades por nomes como ‘ pesos (weights) ‘ para os pesos.

Uma rede é organizada em camadas. A camada de entrada é realmente apenas uma linha do nosso conjunto de dados de treinamento. A primeira camada real é a camada oculta. Isso é seguido pela camada de saída que possui um neurônio para cada valor de classe.

Organizaremos as camadas como matrizes de dicionários e trataremos toda a rede como uma matriz de camadas.

É uma boa prática inicializar os pesos da rede para pequenos números aleatórios. Nesse caso, usaremos números aleatórios no intervalo de 0 a 1.

Abaixo está uma função denominada initialize_network() que cria uma nova rede neural pronta para treinamento. Ele aceita três parâmetros, o número de entradas, o número de neurônios a ter na camada oculta e o número de saídas.

Você pode ver que, para a camada oculta, criamos n_hidden neurônios e cada neurônio na camada oculta possui n_inputs + 1 pesos, um para cada coluna de entrada em um conjunto de dados e um adicional para o viés.

Você também pode ver que a camada de saída que se conecta à camada oculta possui n_outputs de neurônios, cada um com n_hidden + 1 pesos. Isso significa que cada neurônio na camada de saída se conecta a (tem um peso para) cada neurônio na camada oculta.

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
network = list()
hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
network.append(hidden_layer)
output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
network.append(output_layer)
return network

Vamos testar esta função. Abaixo está um exemplo completo que cria uma pequena rede.

from random import seed
from random import random
# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
network = list()
hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
network.append(hidden_layer)
output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
network.append(output_layer)
return network
seed(1)
network = initialize_network(2, 1, 2)
for layer in network:
print(layer)

Executando o exemplo, você pode ver que o código imprime cada camada uma por uma. Você pode ver que a camada oculta possui um neurônio com 2 pesos de entrada mais o viés. A camada de saída possui 2 neurônios, cada um com 1 peso mais o viés.

[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}][{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]

Agora que sabemos como criar e inicializar uma rede, vamos ver como podemos usá-la para calcular uma saída.

2. Propagar para a frente (Forward Propagate)

Podemos calcular uma saída de uma rede neural propagando um sinal de entrada através de cada camada até que a camada de saída produza seus valores.

Chamamos isso de propagação direta.

É a técnica de que precisamos para gerar previsões durante o treinamento que precisará ser corrigida e é o método que precisaremos depois que a rede for treinada para fazer previsões de novos dados.

Podemos dividir a propagação em três partes:

  1. Ativação do neurônio.
  2. Transferência de neurônios.
  3. Propagação para a frente.

2.1 Ativação do Neurônio

O primeiro passo é calcular a ativação de um neurônio que recebe uma entrada.

A entrada pode ser uma linha do nosso conjunto de dados de treinamento, como no caso da camada oculta. Também podem ser as saídas de cada neurônio na camada oculta, no caso da camada de saída.

A ativação do neurônio é calculada como a soma ponderada das entradas. Muito parecido com regressão linear.

activation = sum(weight_i * input_i) + bias

Onde peso é um peso de rede, entrada é uma entrada, i é o índice de um peso ou uma entrada e viés é um peso especial que não tem entrada para multiplicar (ou você pode pensar na entrada como sempre sendo 1.0).

Abaixo está uma implementação disso em uma função chamada enable () . Você pode ver que a função assume que o viés é o último peso na lista de pesos. Isso ajuda aqui e mais tarde a facilitar a leitura do código.

# Calculate neuron activation for an input
def activate(weights, inputs):
activation = weights[-1]
for i in range(len(weights)-1):
activation += weights[i] * inputs[i]
return activation

Agora, vamos ver como usar a ativação do neurônio.

2.2 Transferência de neurônios

Depois que um neurônio é ativado, precisamos transferir a ativação para ver qual é realmente a saída do neurônio.

Diferentes funções de transferência podem ser usadas. É tradicional usar a função de ativação sigmóide , mas você também pode usar a função tanh ( tangente hiperbólica ) para transferir saídas. Mais recentemente, a função de transferência de retificadores tem sido popular em grandes redes de aprendizado profundo.

A função de ativação sigmóide se parece com uma forma de S, também é chamada de função logística. Ele pode pegar qualquer valor de entrada e produzir um número entre 0 e 1 em uma curva S. Também é uma função da qual podemos calcular facilmente a derivada (inclinação) de que precisaremos mais tarde ao retropropagar o erro.

Podemos transferir uma função de ativação usando a função sigmóide da seguinte maneira:

output = 1 / (1 + e^(-activation))

Onde e é a base dos logaritmos naturais ( número de Euler ).

Abaixo está uma função chamada transfer () que implementa a equação sigmóide.

# Transfer neuron activation
def transfer(activation):
return 1.0 / (1.0 + exp(-activation))

Agora que temos as peças, vamos ver como elas são usadas.

2.3 Propagação direta

A propagação direta de uma entrada é simples.

Trabalhamos através de cada camada da nossa rede, calculando as saídas para cada neurônio. Todas as saídas de uma camada se tornam entradas para os neurônios na próxima camada.

Abaixo está uma função chamada forward_propagate () que implementa a propagação direta de uma linha de dados de nosso conjunto de dados com nossa rede neural.

Você pode ver que o valor de saída de um neurônio é armazenado no neurônio com o nome ‘ output ‘. Você também pode ver que coletamos as saídas para uma camada em uma matriz chamada new_inputs que se torna a entrada (inputs) da matriz e é usada como entrada para a camada a seguir.

A função retorna as saídas da última camada, também chamada camada de saída.

# Forward propagate input to a network output
def forward_propagate(network, row):
inputs = row
for layer in network:
new_inputs = []
for neuron in layer:
activation = activate(neuron['weights'], inputs)
neuron['output'] = transfer(activation)
new_inputs.append(neuron['output'])
inputs = new_inputs
return inputs

Vamos juntar todas essas peças e testar a propagação direta da nossa rede.

Definimos nossa rede em linha com um neurônio oculto que espera 2 valores de entrada e uma camada de saída com dois neurônios.

from math import exp# Calculate neuron activation for an input
def activate(weights, inputs):
activation = weights[-1]
for i in range(len(weights)-1):
activation += weights[i] * inputs[i]
return activation
# Transfer neuron activation
def transfer(activation):
return 1.0 / (1.0 + exp(-activation))
# Forward propagate input to a network output
def forward_propagate(network, row):
inputs = row
for layer in network:
new_inputs = []
for neuron in layer:
activation = activate(neuron['weights'], inputs)
neuron['output'] = transfer(activation)
new_inputs.append(neuron['output'])
inputs = new_inputs
return inputs
# test forward propagation
network = [[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}], [{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]]
row = [1, 0, None]
output = forward_propagate(network, row)
print(output)

A execução do exemplo propaga o padrão de entrada [1, 0] e produz um valor de saída que é impresso. Como a camada de saída possui dois neurônios, obtemos uma lista de dois números como saída.

Os valores reais de saída são apenas absurdos por enquanto, mas a seguir, começaremos a aprender como tornar os pesos nos neurônios mais úteis.

[0.6629970129852887, 0.7253160725279748]

3. Erro de Propagação Traseira (Back Propagate Error)

O algoritmo de retropropagação é nomeado para a maneira como os pesos são treinados.

O erro é calculado entre as saídas esperadas e as saídas propagadas a partir da rede. Esses erros são propagados para trás pela rede, da camada de saída para a camada oculta, atribuindo a culpa pelo erro e atualizando os pesos à medida que avançam.

A matemática para o erro de retropropagação está enraizada no cálculo, mas permaneceremos em alto nível nesta seção e focaremos no que é calculado e como, e não por que, os cálculos assumem esse formato específico.

Esta parte é dividida em duas seções.

  1. Derivado de transferência.
  2. Backpropagation de erro.

3.1 Derivado de transferência

Dado um valor de saída de um neurônio, precisamos calcular sua inclinação.

Estamos usando a função de transferência sigmóide, cuja derivada pode ser calculada da seguinte maneira:

derivative = output * (1.0 - output)

Abaixo está uma função chamada transfer_derivative () que implementa esta equação.

# Calculate the derivative of an neuron output
def transfer_derivative(output):
return output * (1.0 - output)

Agora, vamos ver como isso pode ser usado.

3.2 Backpropagation de erro (Error Backpropagation)

O primeiro passo é calcular o erro para cada neurônio de saída, isso nos dará nosso sinal de erro (entrada) para propagar para trás através da rede.

O erro para um determinado neurônio pode ser calculado da seguinte maneira:

error = (expected - output) * transfer_derivative(output)

Onde expected é o valor de saída esperado para o neurônio, output é o valor de saída para o neurônio e transfer_derivative () calcula a inclinação do valor de saída do neurônio, como mostrado acima.

Este cálculo de erro é usado para neurônios na camada de saída (output layer). O valor esperado é o próprio valor da classe. Na camada oculta, as coisas são um pouco mais complicadas.

O sinal de erro de um neurônio na camada oculta é calculado como o erro ponderado de cada neurônio na camada de saída. Pense no erro retornando ao longo dos pesos da camada de saída até os neurônios na camada oculta.

O sinal de erro propagado de volta é acumulado e, em seguida, usado para determinar o erro do neurônio na camada oculta, da seguinte maneira:

error = (weight_k * error_j) * transfer_derivative(output)

Onde error_j é o sinal de erro do j- ésimo neurônio na camada de saída, weight_k é o peso que conecta o k- ésimo neurônio ao neurônio atual e a saída é a saída do neurônio atual.

Abaixo está uma função chamada backward_propagate_error () que implementa este procedimento.

Você pode ver que o sinal de erro calculado para cada neurônio é armazenado com o nome ‘delta’. Você pode ver que as camadas da rede são iteradas na ordem inversa, começando na saída e trabalhando para trás. Isso garante que os neurônios na camada de saída tenham valores ‘delta’ calculados primeiro que os neurônios na camada oculta possam usar na iteração subsequente. Eu escolhi o nome ‘delta’ para refletir a alteração que o erro implica no neurônio (por exemplo, o delta do peso).

Você pode ver que o sinal de erro para neurônios na camada oculta é acumulado a partir de neurônios na camada de saída, onde o número de neurônios ocultos j também é o índice do peso do neurônio no neurônio da camada de saída neuron[‘weights’][j].

# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
for i in reversed(range(len(network))):
layer = network[i]
errors = list()
if i != len(network)-1:
for j in range(len(layer)):
error = 0.0
for neuron in network[i + 1]:
error += (neuron['weights'][j] * neuron['delta'])
errors.append(error)
else:
for j in range(len(layer)):
neuron = layer[j]
errors.append(expected[j] - neuron['output'])
for j in range(len(layer)):
neuron = layer[j]
neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])

Vamos juntar todas as peças e ver como funciona.

Definimos uma rede neural fixa com valores de saída e backpropagate um padrão de saída esperado. O exemplo completo está listado abaixo.

# Calculate the derivative of an neuron output
def transfer_derivative(output):
return output * (1.0 - output)
# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
for i in reversed(range(len(network))):
layer = network[i]
errors = list()
if i != len(network)-1:
for j in range(len(layer)):
error = 0.0
for neuron in network[i + 1]:
error += (neuron['weights'][j] * neuron['delta'])
errors.append(error)
else:
for j in range(len(layer)):
neuron = layer[j]
errors.append(expected[j] - neuron['output'])
for j in range(len(layer)):
neuron = layer[j]
neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
# test backpropagation of error
network = [[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095]}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763]}]]
expected = [0, 1]
backward_propagate_error(network, expected)
for layer in network:
print(layer)

A execução do exemplo imprime a rede após a conclusão da backpropagation de erro. Você pode ver que os valores de erro são calculados e armazenados nos neurônios da camada de saída e da camada oculta.

[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614], 'delta': -0.0005348048046610517}]
[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095], 'delta': -0.14619064683582808}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763], 'delta': 0.0771723774346327}]

Agora vamos usar a backpropagation de erro para treinar a rede.

4. Rede de treinamento (Train Network)

A rede é treinada usando descida de gradiente estocástico.

Isso envolve várias iterações de exposição de um conjunto de dados de treinamento à rede e, para cada linha de dados, propagando as entradas, backpropagating o erro e atualizando os pesos da rede.

Esta parte é dividida em duas seções:

  1. Atualizar pesos.
  2. Rede de trem.

4.1 Atualizar pesos (Update Weights)

Uma vez que os erros são calculados para cada neurônio na rede pelo método de propagação de retorno acima, eles podem ser usados ​​para atualizar pesos.

Os pesos da rede são atualizados da seguinte maneira:

weight = weight + learning_rate * error * input

Onde weight é um dado peso, learning_rate é um parâmetro que você deve especificar, error é o erro calculado pelo procedimento de retropropagação para o neurônio e input é o valor de entrada que causou o erro.

O mesmo procedimento pode ser usado para atualizar o peso da polarização, exceto que não há termo de entrada ou a entrada é o valor fixo de 1.0.

A taxa de aprendizado controla quanto alterar o peso para corrigir o erro. Por exemplo, um valor de 0.1 atualizará o peso em 10% da quantia que possivelmente poderia ser atualizada. Preferem-se pequenas taxas de aprendizado que causam aprendizado mais lento em um grande número de iterações de treinamento. Isso aumenta a probabilidade de a rede encontrar um bom conjunto de pesos em todas as camadas, em vez do conjunto mais rápido de pesos que minimiza o erro (chamado convergência prematura).

Abaixo está uma função chamada update_weights () que atualiza os pesos para uma rede, dada uma linha de dados de entrada, uma taxa de aprendizado e assume que uma propagação para frente e para trás já foi executada.

Lembre-se de que a entrada para a camada de saída é uma coleção de saídas da camada oculta.

# Update network weights with error
def update_weights(network, row, l_rate):
for i in range(len(network)):
inputs = row[:-1]
if i != 0:
inputs = [neuron['output'] for neuron in network[i - 1]]
for neuron in network[i]:
for j in range(len(inputs)):
neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
neuron['weights'][-1] += l_rate * neuron['delta']

Agora que sabemos como atualizar os pesos da rede, vamos ver como podemos fazer isso repetidamente.

4.2 Rede de Treinamento (Train Network)

Como mencionado, a rede é atualizada usando a descida estocástica do gradiente.

Isso envolve o primeiro loop para um número fixo de épocas e, em cada época, a atualização da rede para cada linha no conjunto de dados de treinamento.

Como são feitas atualizações para cada padrão de treinamento, esse tipo de aprendizado é chamado de aprendizado on-line. Se erros foram acumulados em uma época antes de atualizar os pesos, isso é chamado aprendizado em lote ou descida em gradiente em lote.

Abaixo está uma função que implementa o treinamento de uma rede neural já inicializada com um determinado conjunto de dados de treinamento, taxa de aprendizado, número fixo de épocas e número esperado de valores de saída.

O número esperado de valores de saída é usado para transformar valores de classe nos dados de treinamento em uma codificação quente. Esse é um vetor binário com uma coluna para cada valor de classe para corresponder à saída da rede. Isso é necessário para calcular o erro para a camada de saída.

Você também pode ver que o erro da soma ao quadrado entre a saída esperada e a saída da rede é acumulado a cada época e impresso. Isso é útil para criar um rastro do quanto a rede está aprendendo e melhorando a cada época.

# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):
for epoch in range(n_epoch):
sum_error = 0
for row in train:
outputs = forward_propagate(network, row)
expected = [0 for i in range(n_outputs)]
expected[row[-1]] = 1
sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])
backward_propagate_error(network, expected)
update_weights(network, row, l_rate)
print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))

Agora temos todas as peças para treinar a rede. Podemos montar um exemplo que inclui tudo o que vimos até agora, incluindo inicialização de rede e treinar uma rede em um pequeno conjunto de dados.

Abaixo está um pequeno conjunto de dados artificial que podemos usar para testar o treinamento de nossa rede neural.

X1          X2           Y
2.7810836 2.550537003 0
1.465489372 2.362125076 0
3.396561688 4.400293529 0
1.38807019 1.850220317 0
3.06407232 3.005305973 0
7.627531214 2.759262235 1
5.332441248 2.088626775 1
6.922596716 1.77106367 1
8.675418651 -0.242068655 1
7.673756466 3.508563011 1

Abaixo está o exemplo completo. Vamos usar 2 neurônios na camada oculta. É um problema de classificação binária (2 classes), portanto haverá dois neurônios na camada de saída. A rede será treinada por 20 épocas com uma taxa de aprendizado de 0,5, o que é alto porque estamos treinando para poucas iterações.

from math import exp
from random import seed
from random import random
# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
network = list()
hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
network.append(hidden_layer)
output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
network.append(output_layer)
return network
# Calculate neuron activation for an input
def activate(weights, inputs):
activation = weights[-1]
for i in range(len(weights)-1):
activation += weights[i] * inputs[i]
return activation
# Transfer neuron activation
def transfer(activation):
return 1.0 / (1.0 + exp(-activation))
# Forward propagate input to a network output
def forward_propagate(network, row):
inputs = row
for layer in network:
new_inputs = []
for neuron in layer:
activation = activate(neuron['weights'], inputs)
neuron['output'] = transfer(activation)
new_inputs.append(neuron['output'])
inputs = new_inputs
return inputs
# Calculate the derivative of an neuron output
def transfer_derivative(output):
return output * (1.0 - output)
# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
for i in reversed(range(len(network))):
layer = network[i]
errors = list()
if i != len(network)-1:
for j in range(len(layer)):
error = 0.0
for neuron in network[i + 1]:
error += (neuron['weights'][j] * neuron['delta'])
errors.append(error)
else:
for j in range(len(layer)):
neuron = layer[j]
errors.append(expected[j] - neuron['output'])
for j in range(len(layer)):
neuron = layer[j]
neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
# Update network weights with error
def update_weights(network, row, l_rate):
for i in range(len(network)):
inputs = row[:-1]
if i != 0:
inputs = [neuron['output'] for neuron in network[i - 1]]
for neuron in network[i]:
for j in range(len(inputs)):
neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
neuron['weights'][-1] += l_rate * neuron['delta']
# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):
for epoch in range(n_epoch):
sum_error = 0
for row in train:
outputs = forward_propagate(network, row)
expected = [0 for i in range(n_outputs)]
expected[row[-1]] = 1
sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])
backward_propagate_error(network, expected)
update_weights(network, row, l_rate)
print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
# Test training backprop algorithm
seed(1)
dataset = [[2.7810836,2.550537003,0],
[1.465489372,2.362125076,0],
[3.396561688,4.400293529,0],
[1.38807019,1.850220317,0],
[3.06407232,3.005305973,0],
[7.627531214,2.759262235,1],
[5.332441248,2.088626775,1],
[6.922596716,1.77106367,1],
[8.675418651,-0.242068655,1],
[7.673756466,3.508563011,1]]
n_inputs = len(dataset[0]) - 1
n_outputs = len(set([row[-1] for row in dataset]))
network = initialize_network(n_inputs, 2, n_outputs)
train_network(network, dataset, 0.5, 20, n_outputs)
for layer in network:
print(layer)

A execução do exemplo primeiro imprime o erro da soma ao quadrado a cada época de treinamento. Podemos ver uma tendência desse erro diminuindo a cada época.

Depois de treinada, a rede é impressa, mostrando os pesos aprendidos. Também na rede ainda estão os valores de saída e delta que podem ser ignorados. Poderíamos atualizar nossa função de treinamento para excluir esses dados, se quiséssemos.

>epoch=0, lrate=0.500, error=6.350
>epoch=1, lrate=0.500, error=5.531
>epoch=2, lrate=0.500, error=5.221
>epoch=3, lrate=0.500, error=4.951
>epoch=4, lrate=0.500, error=4.519
>epoch=5, lrate=0.500, error=4.173
>epoch=6, lrate=0.500, error=3.835
>epoch=7, lrate=0.500, error=3.506
>epoch=8, lrate=0.500, error=3.192
>epoch=9, lrate=0.500, error=2.898
>epoch=10, lrate=0.500, error=2.626
>epoch=11, lrate=0.500, error=2.377
>epoch=12, lrate=0.500, error=2.153
>epoch=13, lrate=0.500, error=1.953
>epoch=14, lrate=0.500, error=1.774
>epoch=15, lrate=0.500, error=1.614
>epoch=16, lrate=0.500, error=1.472
>epoch=17, lrate=0.500, error=1.346
>epoch=18, lrate=0.500, error=1.233
>epoch=19, lrate=0.500, error=1.132
[{'weights': [-1.4688375095432327, 1.850887325439514, 1.0858178629550297], 'output': 0.029980305604426185, 'delta': -0.0059546604162323625}, {'weights': [0.37711098142462157, -0.0625909894552989, 0.2765123702642716], 'output': 0.9456229000211323, 'delta': 0.0026279652850863837}]
[{'weights': [2.515394649397849, -0.3391927502445985, -0.9671565426390275], 'output': 0.23648794202357587, 'delta': -0.04270059278364587}, {'weights': [-2.5584149848484263, 1.0036422106209202, 0.42383086467582715], 'output': 0.7790535202438367, 'delta': 0.03803132596437354}]

Depois que uma rede é treinada, precisamos usá-la para fazer previsões.

5. Prever (Predict)

Fazer previsões com uma rede neural treinada é bastante fácil.

Já vimos como propagar adiante um padrão de entrada para obter uma saída. É tudo o que precisamos fazer para fazer uma previsão. Podemos usar os próprios valores de saída diretamente como a probabilidade de um padrão pertencente a cada classe de saída.

Pode ser mais útil transformar essa saída novamente em uma previsão de classe nítida. Podemos fazer isso selecionando o valor da classe com maior probabilidade. Isso também é chamado de função arg max .

Abaixo está uma função denominada prever () que implementa este procedimento. Retorna o índice na saída da rede que tem a maior probabilidade. Parte do princípio que valores de classe foram convertidos em números inteiros começando em 0.

# Make a prediction with a network
def predict(network, row):
outputs = forward_propagate(network, row)
return outputs.index(max(outputs))

Podemos juntar isso com o código acima para entrada de propagação direta e com nosso pequeno conjunto de dados artificial para testar as previsões com uma rede já treinada. O exemplo codifica uma rede treinada da etapa anterior.

O exemplo completo está listado abaixo.

from math import exp# Calculate neuron activation for an input
def activate(weights, inputs):
activation = weights[-1]
for i in range(len(weights)-1):
activation += weights[i] * inputs[i]
return activation
# Transfer neuron activation
def transfer(activation):
return 1.0 / (1.0 + exp(-activation))
# Forward propagate input to a network output
def forward_propagate(network, row):
inputs = row
for layer in network:
new_inputs = []
for neuron in layer:
activation = activate(neuron['weights'], inputs)
neuron['output'] = transfer(activation)
new_inputs.append(neuron['output'])
inputs = new_inputs
return inputs
# Make a prediction with a network
def predict(network, row):
outputs = forward_propagate(network, row)
return outputs.index(max(outputs))
# Test making predictions with the network
dataset = [[2.7810836,2.550537003,0],
[1.465489372,2.362125076,0],
[3.396561688,4.400293529,0],
[1.38807019,1.850220317,0],
[3.06407232,3.005305973,0],
[7.627531214,2.759262235,1],
[5.332441248,2.088626775,1],
[6.922596716,1.77106367,1],
[8.675418651,-0.242068655,1],
[7.673756466,3.508563011,1]]
network = [[{'weights': [-1.482313569067226, 1.8308790073202204, 1.078381922048799]}, {'weights': [0.23244990332399884, 0.3621998343835864, 0.40289821191094327]}],
[{'weights': [2.5001872433501404, 0.7887233511355132, -1.1026649757805829]}, {'weights': [-2.429350576245497, 0.8357651039198697, 1.0699217181280656]}]]
for row in dataset:
prediction = predict(network, row)
print('Expected=%d, Got=%d' % (row[-1], prediction))

A execução do exemplo imprime a saída esperada para cada registro no conjunto de dados de treinamento, seguida pela previsão precisa feita pela rede.

Isso mostra que a rede atinge 100% de acurácia (accuracy) nesse pequeno conjunto de dados.

Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1

Agora estamos prontos para aplicar nosso algoritmo de backpropagation a um conjunto de dados do mundo real.

6. Conjunto de Dados de Sementes de Trigo

Esta seção aplica o algoritmo Backpropagation ao conjunto de dados de sementes de trigo.

O primeiro passo é carregar o conjunto de dados e converter os dados carregados em números que podemos usar em nossa rede neural. Para isso, usaremos a função auxiliar load_csv () para carregar o arquivo, str_column_to_float () para converter números de string em flutuantes e str_column_to_int () para converter a coluna da classe em valores inteiros.

Os valores de entrada variam em escala e precisam ser normalizados para o intervalo de 0 e 1. Geralmente, é uma boa prática normalizar valores de entrada para o intervalo da função de transferência escolhida; nesse caso, a função sigmóide que gera valores entre 0 e 1 As funções auxiliar dataset_minmax() e normalize_dataset() foram usadas para normalizar os valores de entrada.

Avaliaremos o algoritmo usando a validação cruzada com dobras k com 5 dobras. Isso significa que 201/5 = 40,2 ou 40 registros estarão em cada dobra. Usaremos as funções auxiliar assessment_algorithm () para avaliar o algoritmo com validação cruzada e precision_metric () para calcular a precisão das previsões.

Uma nova função chamada back_propagation () foi desenvolvida para gerenciar o aplicativo do algoritmo Backpropagation, primeiro inicializando uma rede, treinando-a no conjunto de dados de treinamento e depois usando a rede treinada para fazer previsões em um conjunto de dados de teste.

O exemplo completo está listado abaixo.

# Backprop on the Seeds Dataset
from random import seed
from random import randrange
from random import random
from csv import reader
from math import exp
# Load a CSV file
def load_csv(filename):
dataset = list()
with open(filename, 'r') as file:
csv_reader = reader(file)
for row in csv_reader:
if not row:
continue
dataset.append(row)
return dataset
# Convert string column to float
def str_column_to_float(dataset, column):
for row in dataset:
row[column] = float(row[column].strip())
# Convert string column to integer
def str_column_to_int(dataset, column):
class_values = [row[column] for row in dataset]
unique = set(class_values)
lookup = dict()
for i, value in enumerate(unique):
lookup[value] = i
for row in dataset:
row[column] = lookup[row[column]]
return lookup
# Find the min and max values for each column
def dataset_minmax(dataset):
minmax = list()
stats = [[min(column), max(column)] for column in zip(*dataset)]
return stats
# Rescale dataset columns to the range 0-1
def normalize_dataset(dataset, minmax):
for row in dataset:
for i in range(len(row)-1):
row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0])
# Split a dataset into k folds
def cross_validation_split(dataset, n_folds):
dataset_split = list()
dataset_copy = list(dataset)
fold_size = int(len(dataset) / n_folds)
for i in range(n_folds):
fold = list()
while len(fold) < fold_size:
index = randrange(len(dataset_copy))
fold.append(dataset_copy.pop(index))
dataset_split.append(fold)
return dataset_split
# Calculate accuracy percentage
def accuracy_metric(actual, predicted):
correct = 0
for i in range(len(actual)):
if actual[i] == predicted[i]:
correct += 1
return correct / float(len(actual)) * 100.0
# Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):
folds = cross_validation_split(dataset, n_folds)
scores = list()
for fold in folds:
train_set = list(folds)
train_set.remove(fold)
train_set = sum(train_set, [])
test_set = list()
for row in fold:
row_copy = list(row)
test_set.append(row_copy)
row_copy[-1] = None
predicted = algorithm(train_set, test_set, *args)
actual = [row[-1] for row in fold]
accuracy = accuracy_metric(actual, predicted)
scores.append(accuracy)
return scores
# Calculate neuron activation for an input
def activate(weights, inputs):
activation = weights[-1]
for i in range(len(weights)-1):
activation += weights[i] * inputs[i]
return activation
# Transfer neuron activation
def transfer(activation):
return 1.0 / (1.0 + exp(-activation))
# Forward propagate input to a network output
def forward_propagate(network, row):
inputs = row
for layer in network:
new_inputs = []
for neuron in layer:
activation = activate(neuron['weights'], inputs)
neuron['output'] = transfer(activation)
new_inputs.append(neuron['output'])
inputs = new_inputs
return inputs
# Calculate the derivative of an neuron output
def transfer_derivative(output):
return output * (1.0 - output)
# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
for i in reversed(range(len(network))):
layer = network[i]
errors = list()
if i != len(network)-1:
for j in range(len(layer)):
error = 0.0
for neuron in network[i + 1]:
error += (neuron['weights'][j] * neuron['delta'])
errors.append(error)
else:
for j in range(len(layer)):
neuron = layer[j]
errors.append(expected[j] - neuron['output'])
for j in range(len(layer)):
neuron = layer[j]
neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
# Update network weights with error
def update_weights(network, row, l_rate):
for i in range(len(network)):
inputs = row[:-1]
if i != 0:
inputs = [neuron['output'] for neuron in network[i - 1]]
for neuron in network[i]:
for j in range(len(inputs)):
neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
neuron['weights'][-1] += l_rate * neuron['delta']
# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):
for epoch in range(n_epoch):
for row in train:
outputs = forward_propagate(network, row)
expected = [0 for i in range(n_outputs)]
expected[row[-1]] = 1
backward_propagate_error(network, expected)
update_weights(network, row, l_rate)
# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
network = list()
hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
network.append(hidden_layer)
output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
network.append(output_layer)
return network
# Make a prediction with a network
def predict(network, row):
outputs = forward_propagate(network, row)
return outputs.index(max(outputs))
# Backpropagation Algorithm With Stochastic Gradient Descent
def back_propagation(train, test, l_rate, n_epoch, n_hidden):
n_inputs = len(train[0]) - 1
n_outputs = len(set([row[-1] for row in train]))
network = initialize_network(n_inputs, n_hidden, n_outputs)
train_network(network, train, l_rate, n_epoch, n_outputs)
predictions = list()
for row in test:
prediction = predict(network, row)
predictions.append(prediction)
return(predictions)
# Test Backprop on Seeds dataset
seed(1)
# load and prepare data
filename = 'seeds_dataset.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])-1):
str_column_to_float(dataset, i)
# convert class column to integers
str_column_to_int(dataset, len(dataset[0])-1)
# normalize input variables
minmax = dataset_minmax(dataset)
normalize_dataset(dataset, minmax)
# evaluate algorithm
n_folds = 5
l_rate = 0.3
n_epoch = 500
n_hidden = 5
scores = evaluate_algorithm(dataset, back_propagation, n_folds, l_rate, n_epoch, n_hidden)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

Uma rede com 5 neurônios na camada oculta e 3 neurônios na camada de saída foi construída. A rede foi treinada por 500 épocas com uma taxa de aprendizado de 0,3. Esses parâmetros foram encontrados com um pouco de tentativa e erro, mas você pode fazer muito melhor.

A execução do exemplo imprime a precisão média da classificação em cada dobra, bem como o desempenho médio em todas as dobras.

Você pode ver que a retropropagação e a configuração escolhida alcançaram uma precisão de classificação média de cerca de 93%, o que é muito melhor que o algoritmo de regra zero, que teve uma precisão ligeiramente melhor que 28% de precisão.

Scores: [92.85714285714286, 92.85714285714286, 97.61904761904762, 92.85714285714286, 90.47619047619048]
Mean Accuracy: 93.333%

Sugestões de Melhorias e testes

  • Ajustar parâmetros do algoritmo . Tente redes maiores ou menores treinadas por mais ou menos. Veja se você pode obter um melhor desempenho no conjunto de dados de sementes.
  • Métodos adicionais . Experimente diferentes técnicas de inicialização de peso (como pequenos números aleatórios) e diferentes funções de transferência (como tanh).
  • Mais camadas . Adicione suporte para mais camadas ocultas, treinadas da mesma maneira que a camada oculta usada neste tutorial.
  • Regressão . Altere a rede para que haja apenas um neurônio na camada de saída e que um valor real seja previsto. Escolha um conjunto de dados de regressão para praticar. Uma função de transferência linear pode ser usada para neurônios na camada de saída ou os valores de saída do conjunto de dados escolhido podem ser redimensionados para valores entre 0 e 1.
  • Descida em gradiente em lote (Batch Gradient Descent) . Altere o procedimento de treinamento de online para descida em gradiente de lote e atualize os pesos somente no final de cada época.

--

--