Pages

Funções de ativação para redes Neurais

Tuesday, December 30, 2008

Nos posts anteriores, discutiu-se sobre as redes neurais diretas (feedFoward) Perceptron e Adaline e suas limitações em relação à solução de problemas não linearmente separáveis. Se é necessário resolver este tipo de problema, então precisa-se de uma rede neural mais sofisticada, isto é, precisa-se de uma rede neural com mais camadas de neurônios. Entretanto, se realmente for utilizar desse artifício, como treinamos este tipo de rede multi-camadas ?

Até agora, foram utilizados apenas erros observados pelas saídas dos neurônios através do ajustes sucessivos dos pesos entre a camada de entrada e a de saída (2 camadas apenas). Mas, se for adicionada mais uma camada intermediaria entre elas, uma camada escondida ?

Bom, de toda maneira ainda é necessário ter o processo de retro-alimentação do erro de volta à rede neural, de forma que possam ser ajustados os pesos. E aí está o problema.


Multi Layer Perceptron


Atualmente, o neurônio ou está "ligado" ou "desligado", o que significa que a sua função de ativação é controlada por um limiar que determina a mudança dos estados do neurônio.

Threshold Function

def function(output):
if output >= 0:
return 1
else:
return -1

Plotando o gráfico representando a função acima, pode-se ver as linhas vermelha (representando a sua derivada) e a linha azul (representando a função).


Threshold Graph


Quaisquer ajustes feitos nos pesos que alimentam a camada escondida com uso desta função terão efeitos não tão eficientes e limitados, devido a sua descontinuidade acentuada . É necessário trocar a função de limiar (threeshold function) por algo melhor, que tenha uma transição mais suave. Pode-se utilizar então, a função sigmóide representada pela equação abaixo:


Sigmoid Function


def sigmoid(output):
return 2/ (1 + math.exp(-2 * output))

E sua derivada:

f'(x) = 1 - f(x)2


def sigmoidDerivative(output):

x = sigmoid(output)

return 1 - (math.pow(x,2))



Plotando os gráficos das funções acima representadas, temos:

Sigmoid Graph

Pode-se observar que a transição agora entre os valores 1 e -1 é mais suave e contínua e sua derivada tende ao infinito. Esta função é muito popular entre diversos desenvolvedores de redes neurais, porém alguns utilizam uma versão modificada deste. Variando os limites da função de 0 até 1 (em vez de -1 a 1) tem o benefício de ser computacionalmente menos custoso e interessante quando aplicado em redes neurais de grande dimensão.


Para isso, utiliza-se a função:


Sigmoid Function

def sigmoid(output):
return 2/ (1 + math.exp(-output))

E sua derivada:

f'(x) = f(x)(1 - f(x))


def sigmoidDerivative(output):

x = sigmoid(output)

return x * ( 1 - x)



Plotando-se o gráfico, obtemos:


Sigmoid Graph

No próximo post, será utilizada esta função sigmóide como função de ativação no desenvolvimento e aplicação de uma rede neural Perceptron de múltiplas camadas (Multi Layer Perceptron - MLP) treinada com o algoritmo Back Propagation.

[Artigo]: Introduzindo Redes Neurais e Adaline

Sunday, December 21, 2008

Olá a todos,


Nesse post irei apresentar a outra rede neural direta e pioneira bastante conhecida além do Perceptron: A Adaline. Seguirei a mesma linha utilizada no tutorial anterior com implementações desenvolvidas em python.

Apresentando a Adaline

A Adaline (Adaptative Linear Neuron) foi desenvolvida por Widrow e Hoff em 1959. Foi criada anos depois do aparecimento do Perceptron e é um clássico modelo de neurônio que permite saídas tanto diretas quanto contínuas e pode ser usado para tarefas de classificação e regressão.

Para estas tarefas, o Adaline apresenta uma regra de aprendizado sofisticado, a Regra Delta, que se trata de um método de regressão linear que diminuía a cada exemplo a distância entre a saída obtida e a desejada através de adaptações graduais dos pesos do Perceptron.

Mas o que seria Regressão linear ?

A regressão consiste na busca por uma função que represente, de forma aproximada o comportamento apresentado pelo fenômeno em estudo. A forma mais conhecida de regressão é a linear, por exemplo, uma reta que minimiza o erro médio entre todos os valores considerados.

O gráfico abaixo ilustra um exemplo de um problema de regressão linear em duas dimensões. A busca pela "melhor" reta que melhor represente o conjunto de dados pode ser obtida através da rede Neural Adaline, o qual será explicado nos próximos tópicos.



Problema de regressão de linear

Treinamento

O Adaline é similar ao Perceptron, com diferença apenas pelo seu algoritmo de treinamento. Enquanto o Perceptron ajusta os pesos somente quando um padrão é classificado incorretamente, o Adaline utiliza a regra Delta para minimizar o erro médio (MSE) após cada padrão ser apresentado, ajustando os pesos proporcionalmente ao erro.

A regra Delta foi projetada para eliminar a deficiência do algoritmo de treinamento do perceptron quando são apresentados dados não linearmente separáveis. Ela simplesmente converge até um valor desejado aproximado, onde a função tem taxa de variação máxima. Para isso, ela utiliza de um algoritmo de gradiente descendente, com a intenção de diminuir o valor da função de erro. Este algoritmo pode ser visto como uma "caminhada" no domínio da função do erro , em que casa passo é feito no sentido oposto ao gradiente da função no ponto atual.

Imagine um ADALINE com apenas uma entrada x. Neste caso o neurônio terá apenas 2 pesos, w0 e w1. Logo, a função de erro será bidimensional. No figura abaixo é apresentado o
gráfico da função de erro para 50 padrões de treinamento criados aleatoriamente.


O algoritmo de treinamento do Adaline trabalha tentando minimizar o erro das saídas em relação aos valores desejados di pertencentes ao conjunto de treinamento. A função de custo a ser minimizada é a soma dos erros quadráticos descrita na equação:


Para uma condição inicial qualquer w(0)=wi deseja-se obter a direção do ajuste a ser aplicado no vetor de pesos de forma a caminhar em direção à solução ótima. Para a superfície de erro definida pela equação acima, a direção do ajuste no tempo t pode ser obtida pelo gradiente da função de custo no ponto w(t). Segundo esta regra, o ajuste deve ser feito em direção contrária ao vetor gradiente no ponto w(t). A figura abaixo ilustra o conceito apresentado acima. Os pesos iniciais começam a caminhar (convergir) em direção à solução ótima que seria quando o erro for o mínimo possível.



Baseado nesses conceitos a equação de atualização dos pesos pode ser definida por:

wi   wi +  (d - y)xi
b b +  (d - y)


Veremos agora através de implementação pelo código como esse algoritmo funciona. Utilizaremos o exemplo demonstrado na figura acima do problema de regressão linear, onde dado um conjunto de pontos, precisamos definir a melhor reta (y = ax + b) que represente esse que represente, de forma aproximada o comportamento apresentado pelo fenômeno estudado.

Para resolvermos esse problema nós precisamos de uma rede representada pela figura abaixo, da mesma maneira que o Perceptron com apenas algumas diferenças na saída do neurônio, onde a saída agora é passada por uma função de ativação linear em vez de uma função degrau (1 ou -1).

single layer perceptron


Como vocês podem observar, cada nó de entrada (input) está diretamente conectado ao nó de saída (output). Ajustando os valores dos pesos (weight) que conectam tais nós , a rede é capaz de aprender.

Veremos abaixo como isso funciona.

Um algoritmo simples

Segue o código-fonte relacionado ao neurônio. Há uma pequena diferença em relação ao Perceptron, em relação ao cálculo do erro global, que é realizado após o treinamento da rede e o surgimento de uma nova variável que é o limiar de erro (biasError), já que as saídas são contínuas no Adaline.




##################################################
# #
# Copyright 2008 -Marcel Pinheiro Caraciolo- #
# Email: caraciol@gmail.com #
# #
# -- Adaline Neural Net snippet code #
# -- Version: 0.1 - 12/12/2008 #
##################################################

#Snippet Neuron

from random import random

class Neuron:


def __init__(self,data,learningRate = 0.0000002, biasError = 0.01, bias = 1):
self._learning_rate = learningRate
#Threshold representing the bias (b of the equation y=ax+b)
self._biasError = biasError
self._bias = bias
self._input,self._output = data
#Randomise weights (Put one more entry for the weights vector to the bias).
self._weight = map(lambda x: x*random(), [0] * (len(self._input)+1))
#print self._weight
self._y = None
self._global_error = 1.0


def train(self,data):
self._input,self._output = data
#Append the bias into the input vector.
#bug in python @todo: FIX IT.
if len(self._input) == 1:
self._input.append(self._bias)

#Calculate output.
self._y = self._calculateOutput()
#Calculate error.
if self._error() > self._biasError:
#Update weights.
self._adjustWeight()



def calculateGlobalError(self,data):
self._input,self._output = data
#Append the bias into the input vector.
#bug in python @todo: FIX IT.
if len(self._input) == 1:
self._input.append(self._bias)
#Calculate output.
self._y = self._calculateOutput()
#Calculate the local error.
localError = abs(self._error())
if localError > self._biasError:
#Calculate the Global Error
self._global_error += localError

def execute(self,input):
self._input = input
#Calculate output.
self._y = self._calculateOutput()
return self._y

def _calculateOutput(self):
#return (self._weight[0] * self._input[0] + self._weight[1])
return sum(map(lambda x,y: x*y, self._input,self._weight))

def _error(self):
return self._output - self._y

def _adjustWeight(self):
self._weight = map(lambda x,y: x + (y*self._learning_rate*self._error()),self._weight,self._input)
#self._weight[0] += self._input[0] * self._learning_rate * self._error()
#self._weight[1] += self._learning_rate * self._error()

def getGlobalError(self):
return self._global_error

def getWeights(self):
return self._weight

def resetGlobalError(self):
self._global_error = 0.0


O código seguinte refere-se ao Adaline, que é o que irá apresentar os exemplos ao neurônio:


##################################################
# #
# Copyright 2008 -Marcel Pinheiro Caraciolo- #
# Email: caraciol@gmail.com #
# #
# -- Adaline Neural Net snippet code #
# -- Version: 0.1 - 28/12/2008 #
##################################################


#Snippet Adaline

from Neuron import *


class Adaline:

def __init__(self,inputs,iterations=100):
self._inputs = inputs
self._refresh_inputs = list(inputs)
self._iterations = iterations
self._iteration = 0
self._neuron = Neuron(self._getRandInput())
self._train()


def _train(self):
globalError = self._neuron.getGlobalError()
while self._iteration <= self._iterations and globalError != 0.0:
self._neuron.resetGlobalError()
self._refresh_inputs = list(self._inputs)
for i in range(len(self._refresh_inputs)):
self._neuron.train(self._getRandInput())
self._refresh_inputs = list(self._inputs)
for i in range(len(self._refresh_inputs)):
self._neuron.calculateGlobalError(self._getRandInput())
globalError = self._neuron.getGlobalError()/len(self._inputs)
print "Iteration %d Error: %f" % (self._iteration, globalError)
self._iteration += 1


def _getRandInput(self):
return self._refresh_inputs.pop()


def execute(self,input):
return self._neuron.execute(input)


def arange(self,start,stop=None,step=None):
if stop is None:
stop = float(start)
start = 0.0
if step is None:
step = 1.0
cur = float(start)
while cur <= stop:
yield cur
cur+=step

def getCoefficients(self):
return self._neuron.getWeights()



Execute as classes acima usando:


#main Logic

#Load sample input patterns

inputs = [ [[0.4],0.7], [[0.9],1.0], [[1.5],0.8], [[2.3],0.9],
[[2.9],1.4], [[3.1],2.1], [[3.7],2.4] ]


adaline = Adaline(inputs,351000)

#Display network generalization
print "Y = %f * x + %f " %(adaline.getCoefficients()[0],adaline.getCoefficients()[1])


A saída da rede após o treinamento é exibida conforme abaixo. Pode-se observar que o erro global vai reduzindo ao decorrer das iteraçôes. Embora não se tenha obtido um erro global igual 0.0, o treinamento foi interrompido pelo numero máximo de iterações que foi colocado como parâmetro (351000).



(...)
Iteration 350974 Error: 0.305201
Iteration 350975 Error: 0.305201
Iteration 350976 Error: 0.305201
Iteration 350977 Error: 0.305200
Iteration 350978 Error: 0.305200
Iteration 350979 Error: 0.305200
Iteration 350980 Error: 0.305200
Iteration 350981 Error: 0.305200
Iteration 350982 Error: 0.305200
Iteration 350983 Error: 0.305200
Iteration 350984 Error: 0.305200
Iteration 350985 Error: 0.305200
Iteration 350986 Error: 0.305199
Iteration 350987 Error: 0.305199
Iteration 350988 Error: 0.305199
Iteration 350989 Error: 0.305199
Iteration 350990 Error: 0.305199
Iteration 350991 Error: 0.305199
Iteration 350992 Error: 0.305199
Iteration 350993 Error: 0.305199
Iteration 350994 Error: 0.305199
Iteration 350995 Error: 0.305199
Iteration 350996 Error: 0.305198
Iteration 350997 Error: 0.305198
Iteration 350998 Error: 0.305198
Iteration 350999 Error: 0.305198
Iteration 351000 Error: 0.305198

Final Result:
Y = 0.531989 * x + 0.218829


Plotando o gráfico com a equação da reta obtida pelo treinamento da rede, observamos abaixo a reta (de cor marrom) que mais se aproxima do conjunto de valores de entrada da rede, provando a sua generalização e a capacidade de resolver problemas de regressão linear.



Download dos códigos aqui.

[Artigo]: Introduzindo Redes Neurais e Perceptron

Olá a todos,



Nesse primeiro artigo, estarei escrevendo sobre as Redes Neurais e algumas implementações simples desenvolvidas em Python.

Apresentando as Redes Neurais

O que são? - “São sistemas de processamento de sinais ou de informações, compostos por um grande número de processadores elementares chamados neurônios artificiais, operando de forma paralela e distribuída, de modo a resolver um determinado problema físico/computacional.”

Esquema! - (X1, X2, …, Xn) * (W1, W2, …, Wn) — F(x) — Y

Isso significa que uma Rede Neural, de um neurônio apenas, é um combinador linear que passa por uma função de ativação e dá um resultado Y, o mais interessante é que podemos programá-la para que ela dê o resultado que você deseje!

Com isso podemos fazer, dentre outras coisas, aproximadores de funções, associadores, classificação de padrões, predição futura, controle de sistemas, filtragem de sinais, compressão de dados, datamining, etc.

Redes Neurais podem ser de vários tipos, nesta parte básica vamos ver as mais simples, as Redes Neurais supervisionadas, que são treinadas (ou programadas) usando um conjunto de exemplos conhecidos, com atributos e respostas, e desta forma a Rede Neural “se acostuma” em dar os resultados desejados e passa a responder de acordo.

Programar uma Rede Neural é ajustar os valores do vetor W, de todos os neurônios, para que determinada entrada X, quando processada, resulte num valor Y desejado.

Eu irei começar com a mais simples redes neurais de todas, a Perceptron com apenas uma camada de neurônios e trabalhar sobre as diferentes arquiteturas e técnicas de aprendizado que eventualmente irão convergir para redes que podem prever tendências dos preços das ações de uma bolsa de valores, reconhecimento de padrões, processamento de linguagem natural, etc.


O Perceptron é o ancião de todas as redes neurais. Ela foi criada em 1957 nos laboratórios das forças militares por Frank Rosenblatt. O Perceptron é o mais simples tipo de rede neural diretas (Feedfoward) , conhecido como classificador linear. Isto significa que os tipos de problemas solucionados por esta rede neural devem ser linearmente separáveis. O que significa isso ? O gráfico abaixo ilustra facilmente o que ser um problema linearmente/não-linearmente separáveis em duas dimensões.

linearly separable linearly non-separable

Lineamente Separável.

Não linearmente separável.



A linha verde representa a separação entre duas classes de dados que uma rede está tentando classificar. Em três dimensões, isto seria representado por um plano, e em 4 dimensões ou mais por um hiper-plano. Há outros tipos de redes de neurais que pdoem solucionar problemas linearmente e não linearmente separáveis, que serão discutidas em futuros posts.

Para tentar resolver este problema, nós precisamos de uma rede representada pela figura abaixo.

single layer perceptron


Como vocês podem observar, cada nó de entrada (input) está diretamente conectado ao nó de saída (output). Ajustando os valores dos pesos (weight) que conectam tais nós , a rede é capaz de aprender.

Em uma simples demonstração, irei usar o conjunto de dados plotados no primeiro gráfico aqui apresentado como os dados de treinamento (conjunto de dados para treinamento). O conjunto de dados de treinamento será usado repetidamente como entrada para os nós de entrada (input) da rede neural, fazendo com que os pesos se ajustem até a rede neural atinja o desempenho e objetivo desejado, que é classificar um conjunto de dados linearmente separáveis.

Uma vez que a rede neural foi treinada, a aplicação irá acessar uma base de dados (diferente dos dados de treinamento) e irá provar que a rede neural aprendeu corretamente e tem a capacidade de generalização; ela pode encontrar respostas corretas mesmos quandos os dados de entrada estão incompletos ou quando a relação entre a entrada e a saída não é concreta.

O código abaixo mostra como isso foi realizado.

Um algoritmo simples

Este descreve apenas o funcionamento de um neurônio.



##################################################
# #
# Copyright 2008 Marcel Pinheiro Caraciolo #
# #
# #
# -- Perceptron Neural Net snippet code #
# -- Version: 0.1 - 12/12/2008 #
##################################################

#Snippet Neuron

from random import random

class Neuron:

def __init__(self,data,learningRate = 0.1):
self._learning_rate = learningRate
self._input,self._output = data
#Randomise weights.
self._weight = map(lambda x: x*random(), [1] * len(self._input))
#print self._weight
self._y = None
self._global_error = 0.0


def train(self,data):
self._input,self._output = data
#Calculate output.
self._y = self._sign(self._sum())
#Calculate error.
if self._error() != 0:
#Update weights.
self._adjustWeight()
#Convert error to absolute value.
self._global_error += abs(self._error())

def execute(self,input):
self._input = input
#Calculate output.
self._y = self._sign(self._sum())
return self._y


def _sum(self):
return sum(map(lambda x,y: x*y, self._input,self._weight))

def _sign(self,output):
if output < 0:
y = -1
else:
y = 1
return y

def _error(self):
return self._output - self._y

def _adjustWeight(self):
self._weight = map(lambda x,y: x + (y*self._learning_rate*self._error()),self._weight,self._input)

def getGlobalError(self):
return self._global_error

def resetGlobalError(self):
self._global_error = 0



E para executar o perceptron, que é o que irá apresentar os exemplos ao neurônio, temos:



##################################################
# #
# Copyright 2008 -Marcel Pinheiro Caraciolo- #
# #
# #
# -- Perceptron Neural Net snippet code #
# -- Version: 0.1 - 12/12/2008 #
##################################################


#Snippet Perceptron

from Neuron import *


class Perceptron:

def __init__(self,inputs,iterations=100):
self._inputs = inputs
self._refresh_inputs = list(inputs)
self._iterations = iterations
self._iteration = 0
self._neuron = Neuron(self._getRandInput())
self._train()


def _train(self):
while self._iteration <= self._iterations:
self._neuron.resetGlobalError()
self._refresh_inputs = list(self._inputs)
for i in range(len(self._refresh_inputs)):
self._neuron.train(self._getRandInput())
print "Iteration %d Error: %f" % (self._iteration, self._neuron.getGlobalError())
self._iteration += 1
if(self._neuron.getGlobalError() == 0.0):
break


def _getRandInput(self):
return self._refresh_inputs.pop()


def execute(self,input):
return self._neuron.execute(input)


def arange(self,start,stop=None,step=None):
if stop is None:
stop = float(start)
start = 0.0
if step is None:
step = 1.0
cur = float(start)
while cur <= stop:
yield cur
cur+=step

Execute as classes acima usando:




#main Logic

#Load sample input patterns
inputs = [ [[0.72, 0.82], -1], [[0.91, -0.69], -1],
[[0.46, 0.80], -1], [[0.03, 0.93], -1],
[[0.12, 0.25], -1], [[0.96, 0.47], -1],
[[0.8, -0.75], -1], [[0.46, 0.98], -1],
[[0.66, 0.24], -1], [[0.72, -0.15], -1],
[[0.35, 0.01], -1], [[-0.16, 0.84], -1],
[[-0.04, 0.68], -1], [[-0.11, 0.1], 1],
[[0.31, -0.96], 1], [[0.0, -0.26], 1],
[[-0.43, -0.65], 1], [[0.57, -0.97], 1],
[[-0.47, -0.03], 1], [[-0.72, -0.64], 1],
[[-0.57, 0.15], 1], [[-0.25, -0.43], 1],
[[0.47, -0.88], 1], [[-0.12, -0.9], 1],
[[-0.58, 0.62], 1], [[-0.48, 0.05], 1],
[[-0.79, -0.92], 1], [[-0.42, -0.09], 1],
[[-0.76, 0.65], 1], [[-0.77, -0.76], 1]]


perceptron = Perceptron(inputs,1000)

#Display network generalization
print ""
print "X, Y, Output"
for i in perceptron.arange(-1,1,0.5):
for j in perceptron.arange(-1,1,0.5):
#Calculate output.
result = perceptron.execute(list((i,j)))
if result == 1:
result = "Blue"
else:
result = "Red"
print "%f %f %s" % (i,j,result)


A saída da rede após o treinamento é exibida conforme abaixo, o que prova que a generalização foi atingida. Cada par de coordenadas (x,y) quando apresentadas à rede, retorna a classificação esperada, isto é, vermelho ou azul.


(...)
X, Y, Output
-1.000000 -1.000000 Blue
-1.000000 -0.500000 Blue
-1.000000 0.000000 Blue
-1.000000 0.500000 Blue
-1.000000 1.000000 Blue
-0.500000 -1.000000 Blue
-0.500000 -0.500000 Blue
-0.500000 0.000000 Blue
-0.500000 0.500000 Blue
-0.500000 1.000000 Red
0.000000 -1.000000 Blue
0.000000 -0.500000 Blue
0.000000 0.000000 Blue
0.000000 0.500000 Red
0.000000 1.000000 Red
0.500000 -1.000000 Blue
0.500000 -0.500000 Red
0.500000 0.000000 Red
0.500000 0.500000 Red
0.500000 1.000000 Red
1.000000 -1.000000 Red
1.000000 -0.500000 Red
1.000000 0.000000 Red
1.000000 0.500000 Red
1.000000 1.000000 Red


Analise o objeto criado, seus métodos e pesos, verifique que com o método “execute” você poderá testar se as respostas estão sendo dadas corretamente.


Além de problemas triviais como este de classificação de 2 dimensões , o Perceptron pode realizar uma análise mais poderosa em mais dimensões. As regras genéricas aqui apresentadas são também aplicáveis.


No próximo post, eu falarei mais sobre a Adaline, que é outro tipo de rede neural feedfoward como o Perceptron.

Download dos códigos aqui.

Primeiro Post: Boas Vindas

--


Onde está a chave para busca de todo o conhecimento ?


Olá a todos,

Sejam bem vindos ao blog AiMotion, acrônimo para Artificial Intelligence in Motion. O objetivo deste blog é de divulgar e trazer aos interessados um pouco dos avanços e notícias do campo de inteligência artificial e seus vários ramos e também tutoriais e artigos com implementações para introduzir os que estão começando agora a estudar esse campo tão imenso.

Os autores deste blog são eu : Marcel Pinheiro Caraciolo e Luis Soares. Ambos mestrandos do CIN/UFPE na área de inteligência artificial. Este também foi outro fator que nos levou a criação deste blog, que é poder tornar pública nossas experiências e conhecimentos adquiridos durante essa passagem na academia.

Prentedemos não tornar este blog apenas como um ponto de encontro de especialistas e pequisadores na área e sim tornar um local para iniciantes, profissionais, curiosos que se interessam ou querem aprender mais sobre Inteligência Artificial na prática como funciona e encontrar artigos e fatos que explorem ou alavanquem o estímulo de indagar e discutir cada vez mais sobre esse ramo de pesquisa.

Então, sejam bem vindos!

O blog está ainda em caráter de desenvolvimento e pretendemos aos poucos melhorar o layout do mesmo.

Atenciosamente,

Marcel Pinheiro Caraciolo.