Inicializando uma rede MLP (Keras)Importância1. Números aleatórios com distribuição uniformeDescriçãoEquações1. Uniforme Contínua2. Uniforme DiscretaHistograma TípicoImplementação no Keras2. Método de Inicialização de Xavier (Glorot, 2010)DescriçãoEquações1. Função de Densidade de Probabilidade (PDF)2. Média ($\mu$)3. Variância ($\sigma^2$)Histograma TípicoEmbasamento teórico3. Método de Inicialização de He (He et al, 2015)Funções de Ativação $\times$ VariânciaImplementação no TensorFlowImplementação no KerasMétodos Mais Recentes e Promissores?Método de LeCun (1998)Método LeCun com funções Sigmoidal e tanhPor que LeCun Normal ou LeCun Uniform funciona bem com Sigmoid/Tanh?Implementação no KerasQuando usar LeCun vs Xavier (Glorot) vs He?Estudos ComparativosDoc's TensorFlowCódigo Python para gráfico da Distribuição UniformeCódigo Python para gráficos da Distribuição Gaussiana
Quando você está trabalhando com redes neurais profundas, inicializar a rede com os pesos certos pode ser difícil de lidar porque as Redes Neurais Profundas sofrem de problemas chamados Gradientes de Desaparecer/Explodir. Portanto, precisamos que o sinal flua adequadamente em ambas as direções: na direção para frente ao fazer previsões e na direção inversa ao retropropagar gradientes. Não queremos que o sinal se apareça, nem queremos que ele exploda e sature (He, 2015).
Para inicializar os pesos de uma rede neural no Keras, você pode utilizar diferentes métodos de inicialização. Abaixo, apresento como implementar três abordagens específicas:
A distribuição uniforme é uma distribuição de probabilidade onde todos os valores dentro de um intervalo específico têm a mesma probabilidade de ocorrer. Ela pode ser contínua ou discreta.
O histograma de uma distribuição uniforme contínua é uma reta horizontal, indicando que todos os valores no intervalo têm a mesma frequência. Para a uniforme discreta, o histograma consiste em barras de mesma altura, cada uma representando um valor possível com a mesma probabilidade, por exemplo:
Código Python usado para gerar figura anterior: ver aqui.
Suponha que se queria inicializar os pesos da rede usando números aleatórios com distribuição uniforme na faixa [-0.1, 0.1].
Pode-se usar a função RandomUniform
para definir a faixa desejada:
xfrom tensorflow.keras import layers, initializers
model = keras.Sequential([
layers.Dense(64, activation='relu', kernel_initializer=initializers.RandomUniform(minval=-0.1, maxval=0.1), input_shape=(input_dim,)),
# Adicione mais camadas conforme necessário
])
Não sabemos nada sobre os dados, então não temos certeza de como atribuir os pesos que funcionariam num caso específico. Uma boa maneira é atribuir os pesos usando distribuição gaussiana.
A distribuição normal, também conhecida como distribuição Gaussiana, é uma das distribuições mais importantes em estatística. Ela é simétrica em torno da média, com a maioria dos valores concentrados próximos ao centro.
onde: é a média, é o desvio padrão.
Para uma distribuição normal padrão (, ), a média é 0.
Para uma distribuição normal padrão, a variância é 1.
O histograma de uma distribuição normal tem a forma de um sino, simétrico em torno da média. A maioria dos dados está concentrada próximo à média, e a frequência diminui à medida que os valores se afastam da média em ambas as direções.
Essa distribuição teria média zero e alguma variância finita (He, 2015).
Suponha que tenhamos uma contribuiçãoXComNcomponentes e um neurônio linear com pesos aleatórios que gera uma saída . A variância de pode ser desscrita como:
Sabemos que a variância de é:
Aqui, nós assumimos que e são todos distribuídos de forma idêntica e independente (distribuição gaussiana com média zero), então podemos trabalhar a variância de como sendo:
A variância da saída é a variância da entrada, mas é ponderada por . Portanto, se quisermos que a variação de seja igual à variância de , o termo deve ser igual a 1. Portanto, a variância do peso deve ser:
onde: número de neurônios de entrada num certo tensor.
Esta é a equação de inicialização de Xavier. Precisamos escolher os pesos de uma distribuição gaussiana com média zero e uma variância de: [[He, 2015]].
Usando o método de Glorot & Bengio (2010), também conhecido como Inicialização Xavier, este método é implementado no Keras como GlorotUniform
:
xxxxxxxxxx
from tensorflow.keras import layers, initializers
model = keras.Sequential([
layers.Dense(64, activation='tanh', kernel_initializer=initializers.GlorotUniform(), input_shape=(input_dim,)),
# Adicione mais camadas conforme necessário
])
Este método é especialmente eficaz para funções de ativação como a tangente hiperbólica (tanh) e a sigmoide.
Referência:
Xavier Glorot, Yoshua Bengio (2010) Understanding the difficulty of training deep feedforward neural networks, Proceedings of the Thirteenth International Conference on Artificial Intelligence and Statistics (AISTATS), PMLR 9:249-256, 2010. URL
Resumo: Considerando que antes de 2006 parece que as redes neurais multicamadas profundas não foram treinadas com sucesso, desde então vários algoritmos demonstraram treiná-las com sucesso, com resultados experimentais mostrando a superioridade de arquiteturas mais profundas versus menos profundas. Todos esses resultados experimentais foram obtidos com novos mecanismos de inicialização ou treinamento. Nosso objetivo aqui é entender melhor por que a descida de gradiente padrão da inicialização aleatória está indo tão mal com redes neurais profundas, para entender melhor esses recentes sucessos relativos e ajudar a projetar melhores algoritmos no futuro. Primeiro observamos a influência das funções de ativação não linear. Descobrimos que a ativação sigmóide logística é inadequada para redes profundas com inicialização aleatória por causa de seu valor médio, que pode levar especialmente a camada oculta superior à saturação. Surpreendentemente, descobrimos que as unidades saturadas podem sair da saturação sozinhas, embora lentamente, e explicando os platôs às vezes vistos ao treinar redes neurais. Descobrimos que uma nova não linearidade que satura menos muitas vezes pode ser benéfica. Finalmente, estudamos como as ativações e os gradientes variam entre as camadas e durante o treinamento, com a ideia de que o treinamento pode ser mais difícil quando os valores singulares do Jacobiano associados a cada camada estão longe de 1. Com base nessas considerações, propomos um novo esquema de inicialização que traz uma convergência substancialmente mais rápida.
xxxxxxxxxx
@InProceedings{pmlr-v9-glorot10a,
title = {Understanding the difficulty of training deep feedforward neural networks},
author = {Glorot, Xavier and Bengio, Yoshua},
booktitle = {Proceedings of the Thirteenth International Conference on Artificial Intelligence and Statistics},
pages = {249--256},
year = {2010},
editor = {Teh, Yee Whye and Titterington, Mike},
volume = {9},
series = {Proceedings of Machine Learning Research},
address = {Chia Laguna Resort, Sardinia, Italy},
month = {13--15 May},
publisher = {PMLR},
pdf = {http://proceedings.mlr.press/v9/glorot10a/glorot10a.pdf},
url = {https://proceedings.mlr.press/v9/glorot10a.html}
}
Glorot e Bengio consideraram a função de ativação sigmóide logística, que era a escolha padrão naquele momento para seu esquema de inicialização de peso. Mais tarde, a ativação sigmóide foi superada pelo ReLu, porque permitiu resolver o problema dos gradientes de desaparecimento/explosão (wanishing gradiet). No entanto, acontece que a inicialização de Xavier (Glorot) não é tão ideal para as funções ReLU. Consequentemente, surgiu uma nova técnica de inicialização, que aplicou a mesma ideia (equilíbrio da variância da ativação) a essa nova função de ativação e agora é frequentemente referida como inicialização. A estratégia de inicialização para a função de ativação ReLU e suas variantes às vezes é chamada de inicialização He. Há apenas um pequeno ajuste que precisamos fazer, que é multiplicar a variância dos pesos por 2! (He, 2015).
Função de Ativação | Distribuição Uniforme | Distribuição Normal |
---|---|---|
Tangente Hiperbólia (tanh) | ||
Sigmóide | ||
ReLU (e variantes) |
Ref: He, 2015.
No Tensorflow, a inicialização é implementada na função variance_scaling_initializer()
(que é, na verdade, um inicializador mais geral, mas, por padrão, executa a inicialização de He), enquanto o inicializador de Xavier é logicamente xavier_initializer()
.
Conhecido como Inicialização de He, é adequado para funções de ativação ReLU e suas variantes. No Keras, utilize HeNormal
:
xxxxxxxxxx
from tensorflow.keras import layers, initializers
model = keras.Sequential([
layers.Dense(64, activation='relu', kernel_initializer=initializers.HeNormal(), input_shape=(input_dim,)),
# Adicione mais camadas conforme necessário
])
Este método ajuda a evitar o problema do desaparecimento do gradiente (wanishing gradiet) em redes profundas.
Referência:
Uma abordagem mais recente é a Inicialização de LeCun, que é particularmente eficaz quando usada com a função de ativação selu
(Scaled Exponential Linear Unit). No Keras, pode ser implementada como:
xxxxxxxxxx
from tensorflow.keras import layers, initializers
model = keras.Sequential([
layers.Dense(64, activation='selu', kernel_initializer=initializers.LecunNormal(), input_shape=(input_dim,)),
# Adicione mais camadas conforme necessário
])
Referência
xxxxxxxxxx
@incollection{lecun1998efficient,
title = {Efficient BackProp},
author = {LeCun, Yann and Bottou, L{\'e}on and Orr, Genevieve B. and M{\"u}ller, Klaus-Robert},
booktitle = {Neural Networks: Tricks of the Trade},
pages = {9--50},
year = {1998},
publisher = {Springer},
address = {Berlin}
}
Este trabalho é amplamente citado na literatura de aprendizado de máquina e é considerado uma referência fundamental para técnicas de inicialização de pesos em redes neurais.
O método de inicialização de LeCun é eficaz para redes MLP (Multilayer Perceptron) que utilizam funções de ativação sigmoide ou tangente hiperbólica (tanh). Ele foi projetado para lidar com o problema da variação da escala dos gradientes à medida que a informação se propaga pela rede, especialmente quando se usa essas funções de ativação.
O método de LeCun ajusta a escala dos pesos com base no número de neurônios da camada anterior, usando a seguinte equação:
Onde:
Essas escalas ajudam a manter a variação dos sinais dentro de um intervalo controlado, evitando explosão ou desaparecimento do gradiente, um problema comum ao usar sigmoid ou tanh.
Para aplicar esse método no Keras, utilize os inicializadores LeCunNormal
ou LeCunUniform
:
xxxxxxxxxx
from tensorflow.keras import layers, initializers, Sequential
model = Sequential([
layers.Dense(64, activation='tanh', kernel_initializer=initializers.LecunNormal(), input_shape=(input_dim,)),
layers.Dense(32, activation='tanh', kernel_initializer=initializers.LecunNormal()),
layers.Dense(10, activation='softmax') # Camada de saída (exemplo para classificação)
])
Ou usando distribuição uniforme:
xxxxxxxxxx
model = Sequential([
layers.Dense(64, activation='sigmoid', kernel_initializer=initializers.LecunUniform(), input_shape=(input_dim,)),
layers.Dense(32, activation='sigmoid', kernel_initializer=initializers.LecunUniform()),
layers.Dense(10, activation='softmax') # Camada de saída
])
Método | Observações |
---|---|
LeCun | Melhor para sigmoid e tanh. |
Glorot (Xavier) | Também funciona bem para sigmoid e tanh, mas LeCun tende a ser um pouco mais estável. |
He | Melhor para ReLU e variantes (Leaky ReLU, ELU, etc.) |
Em resumo, se sua rede MLP utiliza sigmoid ou tanh, a inicialização de LeCun Normal/Uniform é uma excelente escolha! 🚀
Existem diversos estudos que comparam métodos de inicialização de pesos em redes neurais multicamadas. Esses estudos analisam o impacto de diferentes inicializações no desempenho e na convergência das redes. Para uma análise detalhada, consulte artigos acadêmicos e publicações especializadas que abordam comparações entre essas técnicas.
Ao escolher o método de inicialização, considere a arquitetura da sua rede e as funções de ativação utilizadas, pois a combinação adequada pode melhorar significativamente o desempenho do modelo.
Em TensorFlow > API > TensorFlow v2.16.1 > Python > tf.keras.initializers.VarianceScaling pode-se encontrar documentação à respeito de implementações e formas de inicializações usando Keras.
. Código show_dist_uniform.py:
xxxxxxxxxx
'''
Rotina para demonstrar distribuição uniforme
Fernando Passold, 21/03/2025
'''
import numpy as np
import matplotlib.pyplot as plt
# Parâmetros da distribuição uniforme
a = -0.5 # limite inferior
b = 0.5 # limite superior
num_samples = 50 # número de amostras
# k = np.linspace(0, num_samples-1, num_samples) # gerando vetor k correspondente ao index das amostras
k = np.arange(num_samples)
# print("10 primeiros valores:", k[0:10])
# Gerando amostras da distribuição uniforme
samples = np.random.uniform(a, b, num_samples)
# print("10 primeiros amostras:", samples[0:10])
# Calculando a média e a variância das amostras
mean = np.mean(samples) # média, μ
variance = np.var(samples) # variância, σ^2
desvio_padrao = np.sqrt(variance) # desvio padrão, σ
'''
Quando você escreve μ ± σ, está dizendo que, para uma distribuição normal
(Gaussiana), aproximadamente 68% dos dados estão dentro do intervalo
[μ−σ, μ+σ]. Isso ajuda a entender em torno de quais valores a média oscila.
'''
# Criando a janela gráfica com 1 linha e 2 colunas
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# Gráfico da esquerda: Valores das amostras em função do número da amostra
ax1.plot(k, samples, 'bo-', markersize=4, label='Amostras') # Marcadores das amostras
ax1.axhline(mean, color='m', linestyle='--', label=f'Média = {mean:.4f}') # Linha da média
ax1.set_title('Amostras Geradas')
ax1.set_xlabel('No. da Amostra')
ax1.set_ylabel('Valor da Amostra')
# ax1.legend()
ax1.grid(True)
# Adicionando texto com o valor da média
ax1.text(0.5, 0.06, f'Média = {mean:.4f} ± {desvio_padrao:.4f}', fontsize=12, ha='center', va='center',
transform=ax1.transAxes, color='black', alpha=1,
backgroundcolor=(1, 1, 1, 0.6)) # Texto com fundo semitransparente
# Gráfico da direita: Histograma da distribuição uniforme
ax2.hist(samples, bins=10, density=True, alpha=0.6, color='b', label=f'Dist. Uniforme [{a}, {b}]')
ax2.set_title('Histograma da Distribuição Uniforme')
ax2.set_xlabel('Valores')
ax2.set_ylabel('Densidade de Probabilidade')
ax2.legend()
ax2.grid(True)
# Adicionando texto com a média e a variância
ax2.text(0.5, 0.1, f'Média = {mean:.4f}\nVariância = {variance:.4f}', fontsize=12,
ha='center', va='center', transform=ax2.transAxes, color='black', alpha=1,
backgroundcolor=(1, 1, 1, 0.6)) # Texto com fundo semitransparente
# Ajustando o layout para evitar sobreposição
plt.tight_layout()
plt.show()
Comentários:
np.random.uniform
.plt.hist
, com density=True
para normalizar a área sob o histograma para 1.Código show_dist_gauss.py:
xxxxxxxxxx
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# Parâmetros da distribuição normal
mean = 0 # média nula
variances = [0.1, 0.5, 1, 3] # variâncias
x = np.linspace(-5, 5, 1000) # intervalo de valores para o eixo x
# Plotando as curvas de densidade para cada variância
plt.figure(figsize=(10, 6))
for var in variances:
std_dev = np.sqrt(var) # desvio padrão
pdf = norm.pdf(x, mean, std_dev) # função de densidade de probabilidade
plt.plot(x, pdf, label=f'Variância = {var}, Desvio Padrão = {std_dev:.2f}')
# Configurações do gráfico
plt.title('Curvas de Densidade da Distribuição Normal (Média = 0)')
plt.xlabel('Valores')
plt.ylabel('Densidade de Probabilidade')
plt.legend()
plt.grid(True)
plt.show()
Comentários:
scipy.stats.norm.pdf
para calcular a função de densidade de probabilidade (PDF).19/03/2025