• Nenhum resultado encontrado

Implementação de rede neural do zero

N/A
N/A
Protected

Academic year: 2021

Share "Implementação de rede neural do zero"

Copied!
25
0
0

Texto

(1)

Implementação de rede neural “do zero”

Nesta apostila, vamos implementar uma rede neural desde o início, sem usar uma implementação pronta. Os exemplos utilizando as funções de rede neural de OpenCV estão nas apostilas "aprendi-zagem" e "mle_avancada".

Vamos utilizar o material de dois sites:

1) Introdutória: http://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/ 2) Avançada: http://neuralnetworksanddeeplearning.com/

Para acompanhar esta apostila, devem olhar ao mesmo tempo o material correspondente desses dois sites.

(2)

O site:

http://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/ [haewin/GoodReader/Algoritmos/deep/stepbystep.pdf]

apresenta a simulação numérica de uma rede neural muito simples. Vamos compreender como fun-ciona, fazendo a implementação em C++.

Pesos iniciais da rede [mattmazur.com]

Numa rede neural "normal", cada neurônio tem um bias. No exemplo do site, há um único "bias" para todos os neurônios de uma camada de rede.

Os nós i1 e i2 (input) não fazem nada. Só pegam as entradas e passam para os nós da camada es-condida (h1 e h2).

Os nós h1, h2 (hidden), o1 e o2 (output) calculam a média ponderada das entradas pelos pesos wi, mais o bias. Depois, aplicam a função de ativação sigmóide. Considere o nó h1:

neth1 = w1*i1 + w2*i2 + b1*1 outh1 = sigmoid(neth1)

Funções "logistic" e "sigmoid" são a mesma coisa: f(x)= 1 / ( 1 + exp(-x) )

(3)

Outra função de ativação bastante usada é tangente hiperbólico:

A função custo ou função erro é calculada: C=Etotal=

12(target−output )2

Vamos calcular a saída de rede neural para entrada (0.05, 0.10). A saída desejada para essa entrada é (0.01, 0.99). Vamos também calcular os erros.

net_h1 = w1*i1+w2*i2+b1*1;

net_h1 = 0.15*0.05+0.2*0.1+0.35*1 = 0.3775 out_h1 = logistic(net_h1);

out_h1 = 0.59327; out_h2 = 0.596884;

net_o1 = w5*out_h1+w6*out_h2+b2*1;

net_o1 = 0.4*0.593+0.45*0.597+0.6*1 = 1.10591; out_o1 = logistic(net_o1);

out_o1 = 0.751365 out_o2 = 0.772928

err_o1 = 0.5 * elev2(target_o1-out_o1);

err_o1 = 0.5 * elev2(0.01-0.7513) = 0.274811; err_o2 = 0.02356

err_total = err_o1+err_o2; err_total = 0.298371

(4)

//step01.cpp

#include <cekeikon.h> double i1=0.05, i2=0.10;

double w1=0.15, w2=0.20, w3=0.25, w4=0.30, b1=0.35; double net_h1, net_h2, out_h1, out_h2;

double w5=0.40, w6=0.45, w7=0.50, w8=0.55, b2=0.60; double net_o1, net_o2, out_o1, out_o2;

double target_o1=0.01, target_o2=0.99; // saida desejada double err_o1,err_o2,err_total;

double logistic(double x) { return 1 / ( 1 + exp(-x) ); }

void forward() {

net_h1=w1*i1+w2*i2+b1*1; xprint(net_h1); out_h1=logistic(net_h1); xprint(out_h1); net_h2=w3*i1+w4*i2+b1*1; xprint(net_h2); out_h2=logistic(net_h2); xprint(out_h2); net_o1=w5*out_h1+w6*out_h2+b2*1; xprint(net_o1); out_o1=logistic(net_o1); xprint(out_o1); net_o2=w7*out_h1+w8*out_h2+b2*1; xprint(net_o2); out_o2=logistic(net_o2); xprint(out_o2);

err_o1 = 0.5 * elev2(target_o1-out_o1); xprint(err_o1); err_o2 = 0.5 * elev2(target_o2-out_o2); xprint(err_o2); err_total = err_o1+err_o2; xprint(err_total);

} int main() { forward(); } ~/haepi/deep/redeneural/stepbystep $ step01 net_h1 = 0.3775 out_h1 = 0.59327 net_h2 = 0.3925 out_h2 = 0.596884 net_o1 = 1.10591 out_o1 = 0.751365 net_o2 = 1.22492 out_o2 = 0.772928 err_o1 = 0.274811 err_o2 = 0.02356 err_total = 0.298371

(5)

backpropagation [mattmazur.com]

No backpropagation, queremos diminuir o erro ou o custo (diferença entre a saída da rede e a saída desejada). Para isso, devemos calcular as derivadas parciais da função custo C (ou erro E) em rela-ção a cada peso e cada bias. Depois, movemos "um pouco" cada peso e cada bias no sentido contrá-rio à sua derivada parcial para diminuir o erro. Para especificar "um pouco", utiliza-se a taxa de aprendizagem (learning rate) η. Learning rate é denotada por α no tiny_dnn.

(6)

//step02.cpp

#include <cekeikon.h> double i1=0.05, i2=0.10;

double w1=0.15, w2=0.20, w3=0.25, w4=0.30, b1=0.35; double net_h1, net_h2, out_h1, out_h2;

double w5=0.40, w6=0.45, w7=0.50, w8=0.55, b2=0.60; double net_o1, net_o2, out_o1, out_o2;

double target_o1=0.01, target_o2=0.99; // saida desejada double err_o1, err_o2, err_total;

double w1p, w2p, w3p, w4p, w5p, w6p, w7p, w8p; double logistic(double x) {

return 1 / ( 1 + exp(-x) ); }

void forward() {

net_h1=w1*i1+w2*i2+b1*1; xprint(net_h1);

out_h1=logistic(net_h1); xprint(out_h1);

net_h2=w3*i1+w4*i2+b1*1; xprint(net_h2);

out_h2=logistic(net_h2); xprint(out_h2);

net_o1=w5*out_h1+w6*out_h2+b2*1; xprint(net_o1);

out_o1=logistic(net_o1); xprint(out_o1);

net_o2=w7*out_h1+w8*out_h2+b2*1; xprint(net_o2);

out_o2=logistic(net_o2); xprint(out_o2);

err_o1 = 0.5 * elev2(target_o1-out_o1); xprint(err_o1);

err_o2 = 0.5 * elev2(target_o2-out_o2); xprint(err_o2);

err_total = err_o1+err_o2; xprint(err_total); }

void backward() {

double eta=0.5;

double derr_dw5=-(target_o1-out_o1)*out_o1*(1-out_o1)*out_h1; xprint(derr_dw5);

w5p = w5 - eta*derr_dw5; xprint(w5p);

double derr_dw6=-(target_o1-out_o1)*out_o1*(1-out_o1)*out_h2; xprint(derr_dw6);

w6p = w6 - eta*derr_dw6; xprint(w6p);

double derr_dw7=-(target_o2-out_o2)*out_o2*(1-out_o2)*out_h1; xprint(derr_dw7);

w7p = w7 - eta*derr_dw7; xprint(w7p);

double derr_dw8=-(target_o2-out_o2)*out_o2*(1-out_o2)*out_h2; xprint(derr_dw8);

w8p = w8 - eta*derr_dw8; xprint(w8p); } int main() { forward(); backward(); } Saída: ~/haepi/deep/redeneural/stepbystep $ step02 net_h1 = 0.3775 out_h1 = 0.59327 net_h2 = 0.3925 out_h2 = 0.596884 net_o1 = 1.10591 out_o1 = 0.751365 net_o2 = 1.22492 out_o2 = 0.772928 err_o1 = 0.274811 err_o2 = 0.02356 err_total = 0.298371 derr_dw5 = 0.082167 w5p = 0.358916 derr_dw6 = 0.0826676 w6p = 0.408666 derr_dw7 = -0.0226025 w7p = 0.511301 derr_dw8 = -0.0227402 w8p = 0.56137

(7)

Livro online sobre rede neural e deep learning: http://neuralnetworksanddeeplearning.com/

[haewin/GoodReader/Algoritmos/deep/neural_network_and_deep_learning.pdf]

traz uma descrição mais detalhada de como funciona uma rede neural. Esse livro traz os exemplos em Python. Vamos traduzir esses programas para C++ e verificar que os resultados são praticamen-te iguais aos das implementações em Python. Isto certificará que aprendemos corretamenpraticamen-te os con-ceitos apresentados no livro. Os exemplos abaixo seguirão esse livro.

O programa rede1 abaixo treina rede neural de 3 camadas com número de neurônios (2,2,2) para que a saída P=(P1, P2) para a entrada X=(0.05, 0.1) fique cada vez mais semelhante à saída ideal Y=(0.01, 0.99) - é o mesmo exemplo que estávamos tentando implementar antes.

Inicializa pesos w e biases b com função gaussiana de média zero e desvio-padrão um. Repete 40 vezes:

1) Feed-forward. 2) Calcula erro. 3) Feed-backward.

4) Update weights and biases (gradient descent)

Diferentemente do exemplo anterior, aqui cada neurônio tem um bias. Cada aresta tem um weight.

Cada neurônio que não seja de entrada possui função de ativação sigmoide na saída. Quando tem somente uma amostra de treinamento, função custo ou função erro é:

C(w , b)=‖y(x)−a‖2 x = amostra de entrada y = saída ideal

a = saída gerada pelo algoritmo ∇ C(w1,..., wK, b1,..., bL)=

(

∂C ∂ w1 , ..., ∂C ∂ wK , ∂C ∂ b1..., ∂ C ∂ bL,

)

T wk→ w'k=wk−η∂C ∂wk bl→b 'l=bl−η∂C ∂ bl

(8)

//rede1.cpp

//Treina rede para que quando a entrada for X=(0.05, 0.1) //a saida seja Y=(0.01, 0.99)

#include <cekeikon.h> float logistic(float x) {

// Funcao logistic

return 1 / ( 1 + exp(-x) ); }

Mat_<float> logistic(Mat_<float> x) {

// Calcula funcao logistic de cada elemento da matriz

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=logistic(x(i));

return y; }

float Dlogistic(float x) {

// Derivada da funcao logistic

float z=logistic(x);

return z*(1-z); }

Mat_<float> Dlogistic(Mat_<float> x) {

// Calcula derivada da funcao logistic para cada elemento da matriz

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=Dlogistic(x(i));

return y; }

class RedeNeural {

Mat_<int> nNeuronio;

vector< Mat_<float> > w; // pesos - um para cada aresta

vector< Mat_<float> > b; // bias - um para cada neurônio exceto entrada

public:

RedeNeural(Mat_<int> _nNeuronio);

void train(Mat_<float> x, Mat_<float> y); };

RedeNeural::RedeNeural(Mat_<int> _nNeuronio) {

// Cria redeneural com o numero de neuronios em cada camada

// especificada pelo vetor _nNeuronio

// _nNeuronio deve ter uma linha

// e numero de colunas igual a numero de camadas da rede

RNG r(7); r.next();

nNeuronio=_nNeuronio.clone();

w.resize(nNeuronio.total()); // nao existe w[0]

for (unsigned i=1; i<w.size(); i++) {

w[i].create(nNeuronio(i),nNeuronio(i-1));

r.fill(w[i],RNG::NORMAL,0.0,1.0);

}

b.resize(nNeuronio.total()); // nao existe b[0]

for (unsigned i=1; i<b.size(); i++) {

b[i].create(nNeuronio(i),1);

r.fill(b[i],RNG::NORMAL,0.0,1.0);

} }

void RedeNeural::train(Mat_<float> x, Mat_<float> y) {

// Treina rede neural para que a saida de x concorde com y

vector< Mat_<float> > a(nNeuronio.total()); // valores dos neurônios antes de sigmoide

// a[0] = x

vector< Mat_<float> > z(nNeuronio.total()); // valores dos neurônios depois de sigmoide

// Nao existe z[0]

vector< Mat_<float> > delta(nNeuronio.total());

for (unsigned passo=0; passo<40; passo++) {

//======== feed forward =================

a[0]=x.clone();

for (unsigned i=1; i<a.size(); i++) {

z[i]=w[i]*a[i-1]+b[i];

a[i]=logistic(z[i]);

}

//========= calcula erro ========================

int L=a.size()-1;

printf("passo=%-5d erro=%9.6f\n",passo,norm(a[L]-y));

//cout << "Erro: " << norm(a[L]-y) << endl;

(9)

multiply( a[L]-y, Dlogistic(z[L]), delta[L]); // (BP1)

for (unsigned i=L-1; i>0; i--)

multiply( w[i+1].t()*delta[i+1], Dlogistic(z[i]), delta[i]); // (BP2)

//============== update weights and biases (cap 2, BP3 e BP4) ==================

float eta=3.0; for (unsigned i=L; i>0; i--) { w[i] = w[i] - eta * delta[i]*a[i-1].t(); // (BP4) b[i] = b[i] - eta * delta[i]; // (BP3) } } } int main() {

Mat_<float> x = ( Mat_<float>(2,1) << 0.05, 0.1 );

Mat_<float> y = ( Mat_<float>(2,1) << 0.01, 0.99 );

Mat_<int> v = (Mat_<int>(1,3) << 2,2,2);

RedeNeural ind(v);

ind.train(x,y); }

O vetor de matrizes w armazena os pesos das arestas. O vetor de vetores b armazena os bias dos neurônios.

Os elementos w[0] e b[0] não existem (começam a ser usados a partir do índice 1).

O vetor de vetores z armazena os valores de saída dos neurônios, antes de calcular a função de ati-vação sigmóide.

O vetor de vetores a armazena os valores de saída dos neurônios, após calcular a função de ativação sigmóide. a[0] contém os valores de entrada da rede neural.

(10)

Os erros L2 obtidos são: passo=0 erro= 0.686175 passo=1 erro= 0.523090 passo=2 erro= 0.390845 passo=3 erro= 0.311366 passo=4 erro= 0.262869 ... passo=32 erro= 0.075270 passo=33 erro= 0.073844 passo=34 erro= 0.072485 passo=35 erro= 0.071187 passo=36 erro= 0.069946 passo=37 erro= 0.068759 passo=38 erro= 0.067620 passo=39 erro= 0.066528

Note que o erro diminui com o número de iterações. Backpropagation:

Equações do livro:

De [neuralnetworksanddeeplearning.com]. Equações adaptadas para o programa:

δL =(aL

− y )⊙σ ' (zL

) (BP1) L é a última camada.

Multiplicação de Hadamard (elemento a elemento). σ’ é a derivada do sigmoide.

δl

=((wl−1)T

δl+1)⊙σ ' (zl

) (BP2)

l é camada interior de rede neural (exceto a última). wl=wl−ηδl(al−1)T (BP4)

η é a taxa de aprendizagem bl=bl

(11)

Cada amostra de treinamento gera um erro. É possível calcular as derivadas parciais dessa função erro em relação à cada peso e bias. Backpropagation minimiza o erro fazendo pesos e bias andar no sentido contrário às derivadas parciais.

Quando tem duas ou mais amostras de treinamento, é necessário calcular média da função custo para todas as amostras. E calcular as derivadas parciais (em relação à w e b) dessa média da função custo. C(w , b)= 1 2 n

x‖y (x)−a‖ 2 x = amostra de entrada y = saída ideal

a = saída gerada pelo algoritmo n = número de amostras

Dw[l] é a somatória para todas as amostras de treinamento: (Dw)l

=

(x, y)δl

(al−1)T (BP5)

Db[l] é a somatória para todas as amostras de treinamento: (Db)

l

=

(x , y)δl (BP6)

O programa rede3 abaixo treina rede neural de 3 camadas com número de neurônios (2,2,2) para que a saída P=(P1, P2) para a entrada X=(0.9, 0.1) seja Y=(0.1, 0.9) e a saída para entrada X=(0.1, 0.9) seja Y=(0.9, 0.1).

Repete 200 vezes:

Repete para cada uma das duas amostras: 1) Feed-forward.

2) Calcula erro. 3) Feed-backward.

4) Update weights and biases (gradient descent), tirando a média dos dois gradientes (para as duas amostras de treinamento). Para a entrada: 0.9, 0.1, 0.1, 0.9, 0.8, 0.0, 0.2, 0.9 A saída obtida é: [0.096133143, 0.8931331; 0.90120441, 0.10131229; 0.12444679, 0.8717249;

(12)

//rede3.cpp

//Treina rede neural de 3 camadas com número de neurônios (2,2,2) //para que a saída para a entrada X=(0.9, 0.1) seja Y=(0.1, 0.9) //e a saída para entrada X=(0.1, 0.9) seja Y=(0.9, 0.1).

#include <cekeikon.h> float logistic(float x) {

return 1 / ( 1 + exp(-x) ); }

Mat_<float> logistic(Mat_<float> x) {

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=logistic(x(i));

return y; }

float Dlogistic(float x) {

float z=logistic(x);

return z*(1-z); }

Mat_<float> Dlogistic(Mat_<float> x) {

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=Dlogistic(x(i));

return y; }

class RedeNeural {

Mat_<int> nNeuronio;

vector< Mat_<float> > w;

vector< Mat_<float> > b;

public:

RedeNeural(Mat_<int> _nNeuronio);

void train(Mat_<float> ax, Mat_<float> ay);

Mat_<float> predict(Mat_<float> qx); };

RedeNeural::RedeNeural(Mat_<int> _nNeuronio) {

RNG r(18); r.next();

nNeuronio=_nNeuronio.clone();

w.resize(nNeuronio.total()); // nao existe w[0]

for (unsigned i=1; i<w.size(); i++) {

w[i].create(nNeuronio(i),nNeuronio(i-1));

r.fill(w[i],RNG::NORMAL,0.0,1.0);

}

b.resize(nNeuronio.total()); // nao existe b[0]

for (unsigned i=1; i<b.size(); i++) {

b[i].create(nNeuronio(i),1);

r.fill(b[i],RNG::NORMAL,0.0,1.0);

} }

void RedeNeural::train(Mat_<float> ax, Mat_<float> ay) {

int L=nNeuronio.total()-1; // Indice da ultima camada da rede

if (ax.cols!=nNeuronio(0)) erro("Erro: Dimensao entrada");

if (ay.cols!=nNeuronio(L)) erro("Erro: Dimensao saida");

if (ax.rows!=ay.rows) erro("Erro: Amostras ax != amostras ay");

int N=ax.rows; // Numero de amostras

vector< Mat_<float> > a(nNeuronio.total());

vector< Mat_<float> > z(nNeuronio.total());

vector< Mat_<float> > delta(nNeuronio.total());

vector< Mat_<float> > Dw(nNeuronio.total());

vector< Mat_<float> > Db(nNeuronio.total());

for (unsigned passo=0; passo<200; passo++) {

for (unsigned s=0; s<N; s++) {

//======== feed forward =================

a[0]=ax.row(s).t();

for (unsigned i=1; i<a.size(); i++) {

z[i]=w[i]*a[i-1]+b[i];

a[i]=logistic(z[i]);

}

//========== feed backward ======================

multiply( a[L]-ay.row(s).t(), Dlogistic(z[L]), delta[L]); // (BP1)

for (unsigned i=L-1; i>0; i--) {

multiply( Mat_<float>(w[i+1].t()*delta[i+1]), Dlogistic(z[i]), delta[i]); // (BP2)

(13)

for (unsigned i=L; i>0; i--) { // (BP5) (BP6) if (s==0) { Dw[i] = delta[i]*a[i-1].t(); Db[i] = delta[i]; } else { Dw[i] += delta[i]*a[i-1].t(); Db[i] += delta[i]; } } }

//============== update weights and biases ==================

float eta=3.0; for (unsigned i=L; i>0; i--) { w[i] = w[i] - (eta/N) * Dw[i]; // (BP4) b[i] = b[i] - (eta/N) * Db[i]; // (BP3) } } }

Mat_<float> RedeNeural::predict(Mat_<float> qx) {

if (qx.cols!=nNeuronio(0)) erro("Erro: Dimensao entrada");

int L=nNeuronio.total()-1; // Indice da ultima camada da rede

int N=qx.rows; // Numero de amostras

Mat_<float> qp(N,nNeuronio(L));

vector< Mat_<float> > a(nNeuronio.total());

vector< Mat_<float> > z(nNeuronio.total());

for (unsigned s=0; s<N; s++) {

a[0]=qx.row(s).t();

for (unsigned i=1; i<a.size(); i++) {

z[i]=w[i]*a[i-1]+b[i]; a[i]=logistic(z[i]); } qp.row(s)=a[L].t(); } return qp; } int main() {

Mat_<float> ax = ( Mat_<float>(2,2) << 0.9, 0.1, 0.1, 0.9 );

Mat_<float> ay = ( Mat_<float>(2,2) << 0.1, 0.9, 0.9, 0.1 );

Mat_<int> v = (Mat_<int>(1,3) << 2,2,2);

RedeNeural ind(v);

ind.train(ax,ay);

Mat_<float> qx = ( Mat_<float>(4,2) <<

0.9, 0.1,

0.1, 0.9,

0.8, 0.0,

0.2, 0.9 );

Mat_<float> qp=ind.predict(qx);

cout << qp << endl; }

(14)

Stochastic gradient descent (SGD): Algoritmo de backpropagation faz:

wk→ w'k=wk−η∂C ∂wk bl→b 'l=bl−η∂C

∂ bl

onde o custo C é o erro médio L2 para todas as amostras de treinamento.

Para calcular gradiente ∇ C , é necessário calcular o gradiente ∇ Cx com respeito a cada amostra de treinamento x e tirar média delas: ∇ C=1

n(

x∇ Cx) . Quando a quantidade de amostras de treinamento é grande, demora muito tempo para calcular e fazer uma única descida de gradiente.

No stochastic gradient descent, estima-se o gradiente ∇ C calculando ∇ Cx para um pequeno número de amostras de treinamento x escolhidos aleatoriamente. Estas amostras de treinamento escolhidos aleatoriamente são chamados de mini-batch.

Um epoch de treinamento é quando usamos todas as amostras de treinamento (divididos em mini-batches) para fazer descida de gradiente.

Exemplo: Suponha que queiramos treinar uma rede neural para reconhecer dígitos manuscritos usando o banco de imagens MNIST que possui 60000 amostras de treinamento. Treinar 30 epochs com mini-batch m=10 significa:

1) Dividir aleatoriamente 60000 amostras em 6000 mini-batches com 10 amostras cada.

2) Aplicar descida de gradiente estocástico usando cada mini-batch de 10 amostras, até usar todos os 6000 mini-batches.

3) Repetir 30 vezes (o número de epochs) os passos 1 e 2.

No site: https://www.quora.com/What-are-the-meanings-of-batch-size-mini-batch-iterations-and-epoch-in-neural-networks há boa explicação dada por Pankaj Malhotra sobre estes conceitos. Copio abaixo:

"Gradient descent is an iterative algorithm which computes the gradient of a function and uses it to update the parameters of the function in order to find a maximum or minimum value of the function. In case of Neural Networks, the function to be optimized (minimized) is the loss function, and the parameters are the weights and biases in the network.

Number of iterations (n): The number of times the gradient is estimated and the parameters of the neural network are updated using a batch of training instances. The batch size B is the number of training instances used in one iteration.

When the total number of training instances (N) is large, a small number of training instances (B<<N) which constitute a mini-batch can be used in one iteration to estimate the gradient of the loss function and update the parameters of the neural network.

It takes n (=N/B) iterations to use the entire training data once. This constitutes an epoch. So, the total number of times the parameters get updated is (N/B)*E, where E is the number of epochs. Three modes of gradient descent:

(15)

Batch mode: N=B, one epoch is same as one iteration.

Mini-batch mode: 1<B<N, one epoch consists of N/B iterations. Stochastic mode: B=1, one epoch takes N iterations."

AdaGrad (adaptive gradient algorithm) é um stochastic gradient descent modificado com taxa de aprendizagem η adaptativo.

(16)

O programa rede7 abaixo treina rede neural de 3 camadas com número de neurônios (nlado*nlado, 30 , 10) para reconhecer os dígitos do banco de dados MNIST.

Usa mini-batches de tamanho m=10, P=30 epochs e taxa de aprendizagem η=3. Obtém taxa de erro 4.04% usando nlado=12 e erro 4.58% usando nlado=28.

Não inverte as cores do MNIST (fundo=preto letras=branco). Usa bounding-box: MNIST mnist(nlado,false,true);

//rede7.cpp

//Classifica MNIST usando mini-batch m=10 e P=30 epochs //Obtem taxa de erro 4.04% usando nlado=12

#include <cekeikon.h>

class RedeNeural {

float logistic(float x); // Funcao logistic

Mat_<float> logistic(Mat_<float> x); // Calcula funcao logistic de cada elemento da matriz

float Dlogistic(float x); // Derivada da funcao logistic

Mat_<float> Dlogistic(Mat_<float> x); // Calcula derivada da logistic para cada elemento da matriz

void trocaLinhas(Mat_<float>& a, int i, int j);

void embaralhaLinhas(Mat_<float>& ax, Mat_<float>& ay, int semente);

Mat_<int> nNeuronio; // Numero de neuronios em cada camada

vector< Mat_<float> > w; // weight

vector< Mat_<float> > b; // bias public:

RedeNeural(Mat_<int> _nNeuronio); // construtor

void train(Mat_<float> ax, Mat_<float> ay); // treina

Mat_<float> predict(Mat_<float> qx); // faz previsao };

float RedeNeural::logistic(float x) {

return 1 / ( 1 + exp(-x) ); }

Mat_<float> RedeNeural::logistic(Mat_<float> x) {

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=logistic(x(i));

return y; }

float RedeNeural::Dlogistic(float x) {

float z=logistic(x);

return z*(1-z); }

Mat_<float> RedeNeural::Dlogistic(Mat_<float> x) {

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=Dlogistic(x(i));

return y; }

void RedeNeural::trocaLinhas(Mat_<float>& a, int i, int j) {

for (unsigned c=0; c<a.cols; c++) {

swap(a(i,c),a(j,c));

} }

void RedeNeural::embaralhaLinhas(Mat_<float>& ax, Mat_<float>& ay, int semente) {

if (ax.rows!=ay.rows) erro("Erro: Amostras ax != amostras ay");

RNG rng(semente); rng.next();

for (unsigned i=0; i<teto(ax.rows,2); i++) {

unsigned j=rng.uniform(i+1,ax.rows);

trocaLinhas(ax,i,j);

trocaLinhas(ay,i,j);

} }

RedeNeural::RedeNeural(Mat_<int> _nNeuronio) {

(17)

nNeuronio=_nNeuronio.clone();

w.resize(nNeuronio.total()); // nao existe w[0]

for (unsigned i=1; i<w.size(); i++) {

w[i].create(nNeuronio(i),nNeuronio(i-1));

r.fill(w[i],RNG::NORMAL,0.0,1.0);

}

b.resize(nNeuronio.total()); // nao existe b[0]

for (unsigned i=1; i<b.size(); i++) {

b[i].create(nNeuronio(i),1);

r.fill(b[i],RNG::NORMAL,0.0,1.0);

} }

void RedeNeural::train(Mat_<float> ax, Mat_<float> ay) {

int L=nNeuronio.total(); // Numero de camadas da rede

if (ax.cols!=nNeuronio(0)) erro("Erro: Dimensao entrada");

if (ay.cols!=nNeuronio(L-1)) erro("Erro: Dimensao saida");

if (ax.rows!=ay.rows) erro("Erro: Amostras ax != amostras ay");

int N=ax.rows; // Numero de amostras

int M=10; // tamanho dos mini-batches

int K=N/M; // numero de mini-batches 60000/10=6000

int P=30; // numero de epochs

vector< Mat_<float> > a(L);

vector< Mat_<float> > z(L);

vector< Mat_<float> > delta(L);

vector< Mat_<float> > Dw(L);

vector< Mat_<float> > Db(L);

for (unsigned p=0; p<P; p++) { // para cada um dos 30 epochs

printf("Epoch %d\n",p);

embaralhaLinhas(ax,ay,p+7);

for (unsigned k=0; k<K; k++) { // para cada um dos 6000 mini-batches

for (unsigned m=0; m<M; m++) { // para cada uma das 10 amostras do mini-batch

int s=k*M+m;

//======== feed forward =================

a[0]=ax.row(s).t();

for (unsigned i=1; i<a.size(); i++) {

z[i]=w[i]*a[i-1]+b[i];

a[i]=logistic(z[i]);

}

//========== feed backward ======================

multiply( a[L-1]-ay.row(s).t(), Dlogistic(z[L-1]), delta[L-1]);

for (unsigned i=L-2; i>0; i--) {

multiply( Mat_<float>(w[i+1].t()*delta[i+1]), Dlogistic(z[i]), delta[i]);

}

for (unsigned i=L-1; i>0; i--) {

if (m==0) { //epoch 0, sample 0 Dw[i] = delta[i]*a[i-1].t(); Db[i] = delta[i]; } else { Dw[i] += delta[i]*a[i-1].t(); Db[i] += delta[i]; } } } // cada sample

//============== update weights and biases (para cada mini-batch) ==================

float eta=3.0;

(18)

for (unsigned s=0; s<N; s++) {

a[0]=qx.row(s).t();

for (unsigned i=1; i<a.size(); i++) {

z[i]=w[i]*a[i-1]+b[i]; a[i]=logistic(z[i]); } qp.row(s)=a[L-1].t(); } return qp; } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< int main(int argc, char** argv) {

if (argc!=2) erro("ann nlado");

int nlado;

convArg(nlado,argv[1]);

if (nlado<2) erro("Erro: nlado fora do intervalo valido");

MNIST mnist(nlado,false,true);

mnist.le("c:/haebase/mnist");

// Converte ay com rotulos 0..9 para ay2 com 10 saidas

Mat_<float> ay2(mnist.na,10); ay2.setTo(0.0);

for (unsigned i=0; i<mnist.ay.rows; i++) {

int j=int(mnist.ay(i));

assert(0<=j && j<10);

ay2(i,j)=1.0;

}

Mat_<int> v = (Mat_<int>(1,3) << nlado*nlado, 30, 10);

RedeNeural ind(v);

ind.train(mnist.ax,ay2);

Mat_<float> saida=ind.predict(mnist.qx);

for (int l=0; l<mnist.nq; l++) {

mnist.qp(l)=argMax(saida.row(l));

}

printf("Erros=%10.2f\%\n",100.0*mnist.contaErros()/mnist.nq); }

(19)

O programa rede-ocv abaixo treina a classe rede neural do OpenCV 2.X de 3 camadas com número de neurônios (nlado*nlado, 30 , 10) para reconhecer os dígitos do banco de dados MNIST.

Obtém taxa de erro 8.65% usando nlado=12. Note que a nossa implementação obtém taxa de erro bem menor (4.04%) nas mesmas condições. Podemos concluir que a implementação de rede neural do OpenCV não é muito boa.

Não inverte as cores do MNIST (fundo=preto letras=branco). Usa bounding-box.

MNIST mnist(nlado,false,true);

//rede-ocv.cpp

//Classifica MNIST usando rede neural do opencv 2.X //Obtem taxa de erro 8.65% usando nlado=12 e leva 25s

#include <cekeikon.h>

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< int main(int argc, char** argv) {

if (argc!=2) erro("ann nlado");

int nlado;

convArg(nlado,argv[1]);

if (nlado<2) erro("Erro: nlado fora do intervalo valido");

MNIST mnist(nlado,false,true);

mnist.le("c:/haebase/mnist");

// Converte ay com rotulos 0..9 para ay2 com 10 saidas

Mat_<float> ay2(mnist.na,10); ay2.setTo(0.0);

for (unsigned i=0; i<mnist.ay.rows; i++) {

int j=int(mnist.ay(i));

assert(0<=j && j<10);

ay2(i,j)=1.0;

}

Mat_<int> v = (Mat_<int>(1,3) << nlado*nlado, 30, 10);

CvANN_MLP ind(v);

ind.train(mnist.ax,ay2,Mat());

Mat_<float> saida; ind.predict(mnist.qx,saida);

for (int l=0; l<mnist.nq; l++) {

mnist.qp(l)=argMax(saida.row(l));

}

printf("Erros=%10.2f\%\n",100.0*mnist.contaErros()/mnist.nq); }

(20)

Taxas de erros obtidos. Os dois constantes booleanos indicam inverte_cores / bounding-box.

Erro true/false true/true false/true false/false

rede7 nlado=12 epochs=10 11.37% 5.91% 4.42% 26s 4.49% rede7 nlado=10 epochs=30 4.12% rede7 nlado=12 epochs=30 4.62% 68s 4.04%68s rede7 nlado=16 epochs=30 4.21% rede7 nlado=20 epochs=30 4.13% 120s rede-ocv nlado=12 8.26% 22.86s 8.65% 25s rede-ocv nlado=20 8.71%65s

Rede7 é a minha implementação. Usa mini-batches m=10. Rede-ocv usa a implementação de rede neural do opencv 2.X.

A minha implementação comete bem menos erros que a implementação do opencv e tem velocida-de semelhante (usando 10 epochs).

Meu programa usando 30 neurônios na camada escondida (rede7) e 30 epochs:

nlado 4 6 8 10 12 14 16 20 28

erro 6.47% 4.49% 4.17% 4.12% 4.04% 4.50% 4.21% 4.13% 4.58%

tempo

(treino+teste) 41s 48s 53s 61s 68s 79s 90s 119s 200s

Meu programa usando 100 neurônios na camada escondida e 30 epochs:

nlado 4 6 8 10 12 14 16 20 28

erro 2.71% 12.16%

tempo

(treino+teste) ?? 700

Observação: Aumentando o número de neurônios da camada escondida de 30 para 100 neurônios, o erro cai para nlado=12 mas aumenta para nlado=28. Provavelmente, temos um "overfitting". Resol-veremos este problema com regularização.

(21)

Regularização L2 cria penalidade para pesos grandes. A função custo torna-se: C=Co+ λ

2 n

w 2

onde Co é a função custo original (função de erro), λ é o parâmtro de regularização, n é o núme-ro de amostras de treinamento e w são os pesos da redes. Intuitivamente, o efeito de regularização é fazer com que rede com pesos pequenos tenha menor custo. Backpropagation vai tentar minimizar o erro e ao mesmo tempo a soma dos pesos.

Para fazer regularização, o livro faz a seguinte modificação em Python: network.py (sem regularização):

self.weights = [w-(eta/len(mini_batch))*nw

for w, nw in zip(self.weights, nabla_w)]

network2.py (com regularização):

self.weights = [(1-eta*(lmbda/n))*w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)]

Faremos a mesma modificação em C++:

rede7.cpp (sem regularização): M é tamanho de mini-batch

w[i] = w[i] - (eta/M) * Dw[i];

rede8.cpp (com regularização): M é tamanho de mini-batch

w[i] = (1-(eta*lmbda/N))*w[i] - (eta/M) * Dw[i];

lmbda (λ) é parâmetro de regularização.

Meu programa (rede8) usando 100 neurônios na camada escondida e 30 epochs com regularização: Lmbda=5, eta=0.5.

nlado 4 6 8 10 12 14 16 20 28

erro 3.53% 2.55%

tempo

(treino+teste) 189s 747s

Repare que o erro diminuiu substancialmente para nlado=28 (de 12% para 2,5%). A minha imple-mentação C++ comete erro semelhante ao relatado no livro para impleimple-mentação em Python.

(22)

Erros obtidos para diferentes parâmetros com regularização: nlado=28 eta=0.5 hidden neurons=100 inicialização=G(0,1) rede8

lmbda 5 4 2

erro 2.55% 2.26% 2.34%

tempo 747s 734s ??

nlado=12 eta=0.5 hidden neurons=100 inicialização=G(0,1) rede8

lmbda 5 4 2 1 0.5

erro 3.53% 3.13% 2.49% 2.30%

tempo 189s 191s ??

nlado=12 hidden neurons=100 inicialização=G(0,1) lmbda=1 rede8

eta 0.1 0.4 0.5 1.0 1.3 2.0

erro 13.89% 2.35% 2.30% 2.04% 2.24% 2.41%

nlado=12 eta=0.5 hidden neurons=100 inicialização de pesos=G(0,1/n) rede9

lmbda 5 4 2 1

erro 3.38% 2.82%

tempo

Esta inicialização de pesos não diminui o erro.

Com stochastic gradient descent e regularização, chegamos ao erro da ordem de 2%, usando rede neural com 3 camadas (uma camada escondida).

(23)

//rede8.cpp

//Classifica MNIST usando mini-batch m=10 e P=30 epochs //Usa rede neural com 100 neuronios na camada escondida. //Usa regularizacao com lmbda=1

//Usando eta=1, chega-se a 2.04% de erro.

#include <cekeikon.h> class RedeNeural {

float logistic(float x); // Funcao logistic

Mat_<float> logistic(Mat_<float> x); // Calcula funcao logistic de cada elemento da matriz

float Dlogistic(float x); // Derivada da funcao logistic

Mat_<float> Dlogistic(Mat_<float> x); // Calcula derivada da funcao logistic para cada elemento da matriz

void trocaLinhas(Mat_<float>& a, int i, int j);

void embaralhaLinhas(Mat_<float>& ax, Mat_<float>& ay, int semente);

Mat_<int> nNeuronio; // Numero de neuronios em cada camada

vector< Mat_<float> > w; // weight

vector< Mat_<float> > b; // bias

public:

RedeNeural(Mat_<int> _nNeuronio); // construtor

void train(Mat_<float> ax, Mat_<float> ay); // treina

Mat_<float> predict(Mat_<float> qx); // faz previsao };

float RedeNeural::logistic(float x) {

return 1 / ( 1 + exp(-x) ); }

Mat_<float> RedeNeural::logistic(Mat_<float> x) {

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=logistic(x(i));

return y; }

float RedeNeural::Dlogistic(float x) {

float z=logistic(x);

return z*(1-z); }

Mat_<float> RedeNeural::Dlogistic(Mat_<float> x) {

Mat_<float> y(x.size());

for (unsigned i=0; i<x.total(); i++)

y(i)=Dlogistic(x(i));

return y; }

void RedeNeural::trocaLinhas(Mat_<float>& a, int i, int j) {

for (unsigned c=0; c<a.cols; c++) {

swap(a(i,c),a(j,c));

} }

void RedeNeural::embaralhaLinhas(Mat_<float>& ax, Mat_<float>& ay, int semente) {

if (ax.rows!=ay.rows) erro("Erro: Amostras ax != amostras ay");

RNG rng(semente); rng.next();

for (unsigned i=0; i<teto(ax.rows,2); i++) {

unsigned j=rng.uniform(i+1,ax.rows);

(24)

r.fill(b[i],RNG::NORMAL,0.0,1.0);

} }

void RedeNeural::train(Mat_<float> ax, Mat_<float> ay) {

int L=nNeuronio.total(); // Numero de camadas da rede

if (ax.cols!=nNeuronio(0)) erro("Erro: Dimensao entrada");

if (ay.cols!=nNeuronio(L-1)) erro("Erro: Dimensao saida");

if (ax.rows!=ay.rows) erro("Erro: Amostras ax != amostras ay");

int N=ax.rows; // Numero de amostras

int M=10; // tamanho dos mini-batches

int K=N/M; // numero de mini-batches 60000/10=6000

int P=30; // numero de epochs

//int P=10; // numero de epochs

vector< Mat_<float> > a(L);

vector< Mat_<float> > z(L);

vector< Mat_<float> > delta(L);

vector< Mat_<float> > Dw(L);

vector< Mat_<float> > Db(L);

for (unsigned p=0; p<P; p++) { // para cada um dos 10 ou 30 epochs

printf("Epoch %d\n",p);

embaralhaLinhas(ax,ay,p+7);

for (unsigned k=0; k<K; k++) { // para cada um dos 6000 mini-batches

for (unsigned m=0; m<M; m++) { // para cada uma das 10 amostras do mini-batch

int s=k*M+m;

//======== feed forward =================

a[0]=ax.row(s).t();

for (unsigned i=1; i<a.size(); i++) {

z[i]=w[i]*a[i-1]+b[i];

a[i]=logistic(z[i]);

}

//========= calcula erro ========================

// cout << "Sample " << s << " erro: " << norm(a[L-1]-ay.row(s).t()) << endl;

//========== feed backward ======================

multiply( a[L-1]-ay.row(s).t(), Dlogistic(z[L-1]), delta[L-1]);

for (unsigned i=L-2; i>0; i--) {

multiply( Mat_<float>(w[i+1].t()*delta[i+1]), Dlogistic(z[i]), delta[i]);

} for (unsigned i=L-1; i>0; i--) { if (m==0) { //epoch 0, sample 0 Dw[i] = delta[i]*a[i-1].t(); Db[i] = delta[i]; } else { Dw[i] += delta[i]*a[i-1].t(); Db[i] += delta[i]; } } } // cada sample

//============== update weights and biases ==================

//float eta=3.0;

float eta=1.0;

float lmbda=1.0;

for (unsigned i=L-1; i>0; i--) {

//w[i] = w[i] - (eta/M) * Dw[i];

w[i] = (1-(eta*lmbda/N))*w[i] - (eta/M)*Dw[i];

b[i] = b[i] - (eta/M) * Db[i];

}

} // cada um dos 6000 mini-batches

} // cada um dos 10 epochs

}

Mat_<float> RedeNeural::predict(Mat_<float> qx) {

if (qx.cols!=nNeuronio(0)) erro("Erro: Dimensao entrada");

int L=nNeuronio.total(); // Numero de camadas da rede

int N=qx.rows; // Numero de amostras

Mat_<float> qp(N,nNeuronio(L-1));

vector< Mat_<float> > a(L);

vector< Mat_<float> > z(L);

for (unsigned s=0; s<N; s++) {

(25)

for (unsigned i=1; i<a.size(); i++) { z[i]=w[i]*a[i-1]+b[i]; a[i]=logistic(z[i]); } qp.row(s)=a[L-1].t(); } return qp; } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

int main(int argc, char** argv) {

if (argc!=2) erro("ann nlado");

int nlado;

convArg(nlado,argv[1]);

if (nlado<2) erro("Erro: nlado fora do intervalo valido");

MNIST mnist(nlado,false,true);

mnist.le("c:/haebase/mnist");

// Converte ay com rotulos 0..9 para ay2 com 10 saidas

Mat_<float> ay2(mnist.na,10); ay2.setTo(0.0);

for (unsigned i=0; i<mnist.ay.rows; i++) {

int j=int(mnist.ay(i));

assert(0<=j && j<10);

ay2(i,j)=1.0;

}

Mat_<int> v = (Mat_<int>(1,3) << nlado*nlado, 100, 10);

RedeNeural ind(v);

ind.train(mnist.ax,ay2);

Mat_<float> saida=ind.predict(mnist.qx);

for (int l=0; l<mnist.nq; l++) {

mnist.qp(l)=argMax(saida.row(l));

}

printf("Erros=%10.2f\%\n",100.0*mnist.contaErros()/mnist.nq); }

Referências

Documentos relacionados

A COMISSÃO PERMANENTE DE CONCURSO, TORNA PÚBLICO PONTO DE PROVA, HORÁRIO, ENSALAMENTO E ORDEM DE APRESENTAÇÃO PARA A PROVA DIDÁTICA DO EDITAL Nº 047/UFFS/2017 – PROCESSO SELETIVO

A pedagogia hospitalar é um ramo da educação que proporciona á criança e ao adolescente hospitalizado uma recuperação mais aliviada, por meio de atividades

De seguida, vamos adaptar a nossa demonstrac¸ ˜ao da f ´ormula de M ¨untz, partindo de outras transformadas aritm ´eticas diferentes da transformada de M ¨obius, para dedu-

3) A produção de mais de 7 milhões de mudas de árvores nativas, as quais estão sendo utilizadas na recuperação de fontes, matas ciliares, áreas degradadas e formação de bosques

de movimento e da gravidade. Não existe trabalho  °C. Não existe trabalho durante o processo. Assinale a alternativa que indica a transferência de calor por que indica a

• Mochila Ataque 20/30L - É a melhor opção para os passeios, pois tem tamanho ideal para levar água, toalha, snacks, máquina fotográfica, protetor solar, lanche de trilha e

Por esta razão, os programas educativos da Nova SBE Executive Education procuram cada vez mais preparar as pessoas e organiza- ções para enfrentar estes medos, trazendo

Gramsci (2004) assevera que os intelectuais são, na sociedade, representantes de classes, eles intervêm nos episódios da cultura, o indivíduo organiza e sustenta