Como vimos, redes neurais podem ser impactadas pelo fenômeno do sobre-ajuste. Nesta interação, abordaremos uma técnica, a regularização, que busca forçar redes neurais a criarem mapeamentos mais suaves.
Ao fim desta lição, o aluno será capaz de:
A regularização é um processo que busca penalizar determinadas configurações de sistemas que são claramente inadequados a um problema. Assim, além de minimizar o erro ($E = ||\boldsymbol y_e - \boldsymbol y||^2.$), o processo de treinamento também buscará minimizar uma determinada função do conjunto de parâmetros $\theta$ da rede. É comum que essa função seja a norma L1 ou L2 dos pesos da rede.
Numa rede regularizada, a função-objetivo, que deve ser minimizada, é dada por: $$E + \lambda f(\theta),$$ onde $\lambda$ é um parâmetro que determina o quão regular a rede deverá ser. Se definirmos $f(\theta) = ||\theta||_1$, então a função de retro-propagação poderá ser re-definida, na forma:
In [32]:
# Inicializacao
%matplotlib inline
import numpy as np
from matplotlib import pyplot as plt
def nova_mlp(entradas, saidas, camadas):
lista_de_camadas = [entradas] + camadas + [saidas]
pesos = []
for i in xrange(len(lista_de_camadas)-1):
pesos.append(np.random.random((lista_de_camadas[i+1], lista_de_camadas[i])))
return pesos
def ff_mlp(entradas, pesos):
s = entradas
for i in xrange(len(pesos)-1):
s = np.tanh(np.dot(pesos[i],s))
s = np.dot(pesos[-1],s)
return s
def backpropagation_step(entradas, saidas, pesos, regular=0.01, passo=0.01):
derivadas = []
resultados_intermediarios = [entradas]
s = entradas
for i in xrange(len(pesos)-1):
s = np.tanh(np.dot(pesos[i],s))
resultados_intermediarios.append(s)
s = np.dot(pesos[-1],s)
resultados_intermediarios.append(s)
# Derivada do erro em relacao a saida estimada
dedye = (resultados_intermediarios[-1] - saidas)
# Derivada em relacao a camada de saida linear
dedb = np.dot(dedye, resultados_intermediarios[-2].T)
# Para cada camada nao-linear, calcula a nova derivada na forma:
deda = dedye
for i in range(len(pesos)-2, -1, -1):
linear = np.dot(pesos[i], resultados_intermediarios[i])
flz = (1-np.tanh(linear)**2)
deda = np.dot(pesos[i+1].T, deda) # deriv_front
derivada = np.dot(deda * flz, resultados_intermediarios[i].T)
derivadas.insert (0, derivada)
derivadas.append(dedb)
# Executa um passo na direcao contraria da derivada e adiciona minimizacao da
# norma L1 ponderada por lambda
for i in xrange(len(derivadas)):
n = np.linalg.norm(derivadas[i])
pesos[i] -= passo * (derivadas[i]/n + (regular * np.sign(pesos[i])))
return pesos
def erro(y, y_e):
return np.sum((y-y_e)**2)
In [56]:
# Usando 30 amostras aleatoriamente escolhidas (30% do total) para o aprendizado
# e bias nas entradas
import random
x = np.linspace(-3, 3, num=100)
y_ = x**2
y = y_ + np.random.normal(0, 0.5, 100) # Criando uma parabola com ruido
x.shape = (1, x.size)
y.shape = (1, y.size)
x_treino = x[:,0:30]
x_teste = x[:,30:100]
y_treino = y[:,0:30]
y_teste = y[:,30:100]
x.shape = (x.size)
y.shape = (y.size)
train_array = np.zeros((x.size)).astype(bool)
test_array = np.ones((x.size)).astype(bool)
while np.sum(train_array) < 30:
n = int(random.random() * x.size)
test_array[n] = False
train_array[n] = True
x_treino = x[train_array]
x_teste = x[test_array]
y_treino = y[train_array]
y_teste = y[test_array]
x_treino.shape = (1,x_treino.size)
y_treino.shape = (1,y_treino.size)
x_teste.shape = (1,x_teste.size)
y_teste.shape = (1,y_teste.size)
x_treino2 = np.vstack((x_treino, np.ones(x_treino.size)))
x_teste2 = np.vstack((x_teste, np.ones(x_teste.size)))
mlp0 = nova_mlp(entradas=2, saidas=1, camadas=[10])
# Processo de treinamento
n_passos = 2000
eqm_treino = np.zeros((n_passos+1))
eqm_treino[0] = erro(y_treino, ff_mlp(x_treino2, mlp0))
eqm_teste = np.zeros((n_passos+1))
eqm_teste[0] = erro(y_teste, ff_mlp(x_teste2, mlp0))
for i in xrange(n_passos):
mlp0 = backpropagation_step(x_treino2, y_treino, mlp0, regular=0.3)
eqm_treino[i+1] = erro(y_treino, ff_mlp(x_treino2, mlp0))
eqm_teste[i+1] = erro(y_teste, ff_mlp(x_teste2, mlp0))
print "EQM final:", eqm_teste[-1]
plt.figure();
plt.plot(range(n_passos+1), eqm_treino);
plt.plot(range(n_passos+1), eqm_teste);
plt.ylabel('EQM');
plt.xlabel('Passos');
plt.title('EQM nos conjuntos de treino e teste');
plt.figure();
plt.plot(x.T, y.T);
plt.plot(x_treino.T, ff_mlp(x_treino2, mlp0).T);
plt.plot(x_teste.T, ff_mlp(x_teste2, mlp0).T);
plt.ylabel('Y');
plt.xlabel('X');
plt.title('Aproximacao nos conjuntos de teste e treino');
Modifique a quantidade de ruído adicionada à parábola (determinada pela sua variância em np.random.normal()). A cada nova quantidade de ruído, verifique o que acontece quando os níveis de regularização ($\lambda$) são muito altos, muito baixos e tente achar valores que parecem adequados.
Qual é a relação, observada nos exemplos deste caderno, entre a regularização e a capacidade de aproximação universal?
A norma L1 está ligada à esparsidade dos parâmetros da rede. Escreva um pequeno texto (de não mais que 500 palavras) discutindo o que significa esparsidade.
Utilize a rede neural regularizada num problema de classificação ou de regressão à sua escolha. Durante esse procedimento, investigue o impacto da regularização no problema em questão, variando $\lambda$ desde valores muito baixos até valores muito altos.
Nesta interação e na anterior (regressão) observamos os seguintes comportamentos:
Tendo isso em vista, considere o seguinte procedimento para definir um critério de parada para a otimização da rede:
Esse procedimento é válido? Apresente argumentos sustentando sua afirmação.
In [ ]: