Inicialização de Pesos
O que é inicialização de pesos?
Inicialização de pesos (weight initialization) é o processo de escolher os valores iniciais dos parâmetros treináveis (trainable parameters) de uma rede neural (neural network) (pesos (weights) e, às vezes, vieses (biases)) antes do início do treinamento. Embora o treinamento atualize esses parâmetros via Retropropagação (Backpropagation) e um otimizador (optimizer) como a Descida do Gradiente (Gradient Descent), a distribuição inicial afeta fortemente:
- se os sinais (ativações (activations)) se propagam pela profundidade sem explodir ou se extinguir
- se os gradientes (gradients) permanecem utilizáveis (evitar explosão/desaparecimento)
- quão rapidamente a otimização converge e quão estável ela é
- se neurônios diferentes aprendem características diferentes (quebra de simetria (symmetry breaking))
A inicialização é especialmente importante em redes profundas, arquiteturas residuais (residual architectures) e modelos de atenção (attention models), onde pequenos problemas numéricos podem se acumular ao longo de muitas camadas.
Por que a inicialização importa
1) Quebra de simetria: por que não inicializar tudo com zero?
Se todos os pesos em uma camada começam iguais (por exemplo, todos zero), então todos os neurônios nessa camada produzem a mesma saída para qualquer entrada, recebem o mesmo gradiente e permanecem idênticos para sempre. Isso é um problema de simetria: o modelo não consegue aprender características diversas.
- Pesos quase nunca devem ser inicializados com todos os valores zero (exceto em construções especiais, como certos escalares residuais com portas).
- Vieses muitas vezes podem ser zero sem problemas de simetria, porque a simetria é causada principalmente por pesos iguais.
2) Propagação estável do sinal no sentido direto (forward)
Considere uma camada linear (linear layer):
[ \mathbf{y} = \mathbf{W}\mathbf{x} + \mathbf{b} ]
Se as entradas de W forem grandes demais, a variância (variance) das ativações tende a crescer com a profundidade, levando à saturação (para sigmoid/tanh) ou a ativações enormes (para a unidade linear retificada (ReLU)). Se forem pequenas demais, as ativações encolhem em direção a zero e se tornam pouco informativas.
O objetivo geralmente é escolher uma inicialização tal que a variância das ativações permaneça aproximadamente constante à medida que a profundidade aumenta.
3) Propagação estável do gradiente no sentido reverso (backward)
O treinamento depende de gradientes fluindo para trás através de muitas camadas. Se cada camada escala o gradiente, em média, um pouco acima de 1, a norma do gradiente pode explodir exponencialmente com a profundidade; um pouco abaixo de 1, ele pode desaparecer.
Esquemas modernos de inicialização buscam manter:
- a variância das ativações no forward estável
- a variância dos gradientes no backward estável
Isso está intimamente conectado ao fenômeno de “gradientes que desaparecem/explodem (vanishing/exploding gradients)” em Redes Neurais (Neural Networks) profundas.
4) Condicionamento e velocidade de otimização
Mesmo que os gradientes não desapareçam/explodam completamente, uma inicialização ruim pode levar a Jacobianos/Hessianos mal condicionados. Na prática, isso se parece com:
- treinamento muito lento
- alta sensibilidade à taxa de aprendizado
- picos instáveis de loss no início do treinamento
Uma boa inicialização melhora o condicionamento efetivo do problema de otimização inicial.
A ideia central: preservação da variância (variance preservation) (intuição)
Uma análise simplificada comum assume:
- as entradas de uma camada são i.i.d., com média zero e alguma variância
- os pesos são i.i.d., independentes das entradas
- as não linearidades (nonlinearities) se comportam “bem” em esperança
Para um neurônio com (n) entradas (fan-in), se os pesos têm variância (\mathrm{Var}(w)) e as entradas têm variância (\mathrm{Var}(x)), então, aproximadamente:
[ \mathrm{Var}(y) \approx n \cdot \mathrm{Var}(w) \cdot \mathrm{Var}(x) ]
Para manter (\mathrm{Var}(y) \approx \mathrm{Var}(x)), queremos:
[ n \cdot \mathrm{Var}(w) \approx 1 \quad \Rightarrow \quad \mathrm{Var}(w) \approx \frac{1}{n} ]
Não linearidades mudam esse cenário. Para ReLU, cerca de metade das ativações é zero, então a variância é reduzida; isso motiva uma variância de peso maior (aproximadamente um fator 2).
Essa intuição sustenta a maioria dos esquemas populares de inicialização.
Esquemas comuns de inicialização de pesos
Inicialização zero (zero initialization) (principalmente um alerta)
- Pesos = 0: ruim para quase todas as camadas ocultas (sem quebra de simetria).
- Vieses = 0: muitas vezes é aceitável.
- Camada de saída: às vezes usa-se intencionalmente uma inicialização pequena para começar perto de um prior razoável (por exemplo, pontuações antes da softmax (logits) próximas de zero), mas ainda assim não com todos os pesos idênticos entre unidades.
Normal/uniforme aleatória pequena (baseline legado)
Historicamente, usava-se algo como (w \sim \mathcal{N}(0, 0.01)). Isso pode funcionar para redes rasas, mas é pouco confiável para redes profundas, porque não escala com a largura da camada (número de entradas (fan-in)/número de saídas (fan-out)).
A prática moderna usa esquemas escalados por fan.
Inicialização Xavier / Glorot (Xavier / Glorot initialization)
Mais indicada para: tanh, sigmoid logística (e muitas vezes aceitável para camadas lineares ou ativações do tipo GELU na prática).
A inicialização de Glorot escolhe a variância dos pesos com base tanto no fan-in quanto no fan-out:
- Glorot normal: [ w \sim \mathcal{N}\left(0, \frac{2}{\text{fan_in} + \text{fan_out}}\right) ]
- Glorot uniforme: [ w \sim U\left(-\sqrt{\frac{6}{\text{fan_in} + \text{fan_out}}},\ \sqrt{\frac{6}{\text{fan_in} + \text{fan_out}}}\right) ]
Por que fan-out também? Para equilibrar a preservação de variância tanto das ativações no forward quanto dos gradientes no backward.
Inicialização He / Kaiming (He / Kaiming initialization)
Mais indicada para: ReLU e ativações do tipo ReLU (ReLU com vazamento (LeakyReLU), variantes de unidade linear exponencial (ELU) dependendo do ganho (gain)).
A inicialização de He foca no fan-in:
- Kaiming (He) normal: [ w \sim \mathcal{N}\left(0, \frac{2}{\text{fan_in}}\right) ]
- Kaiming (He) uniforme: [ w \sim U\left(-\sqrt{\frac{6}{\text{fan_in}}},\ \sqrt{\frac{6}{\text{fan_in}}}\right) ]
O fator 2 compensa o efeito “metade das unidades é zero” da ReLU (sob suposições comuns).
A maioria das CNNs e MLPs profundas com ativações da família ReLU usa Kaiming por padrão.
Inicialização LeCun (LeCun initialization) (comum com SELU)
Mais indicada para: unidade exponencial linear escalada (SELU; Scaled Exponential Linear Unit) em redes auto-normalizantes (self-normalizing networks).
- LeCun normal: [ w \sim \mathcal{N}\left(0, \frac{1}{\text{fan_in}}\right) ]
Redes SELU dependem de uma interação específica entre ativação e inicialização; desviar do esquema recomendado pode quebrar o comportamento auto-normalizante.
Inicialização ortogonal (orthogonal initialization)
A inicialização ortogonal amostra uma matriz de pesos com colunas/linhas ortonormais (dependendo da forma), às vezes escalada por um ganho.
Por que ajuda:
- Matrizes ortogonais preservam normas de vetores (no caso linear).
- Isso pode melhorar o fluxo de gradientes em redes lineares profundas e costuma ser útil em RNNs.
Na prática:
- Para matrizes não quadradas, frameworks constroem uma matriz aleatória e fazem uma fatoração QR.
- Frequentemente combinada com um ganho dependendo da ativação (por exemplo, (\sqrt{2}) para ReLU).
A inicialização ortogonal é uma boa escolha quando você se preocupa com estabilidade dinâmica, especialmente para pesos recorrentes.
Inicialização identidade / quase identidade (identity / near-identity initialization) (especializada)
Algumas arquiteturas se beneficiam de inicializar certas transformações próximas da identidade, por exemplo:
- algumas transições recorrentes
- ramos residuais (efetivamente começam como “não fazer nada”)
Em redes residuais, uma ideia relacionada é inicializar a última camada em um ramo residual com zero, para que cada bloco comece como um mapeamento identidade (isso é diferente de “todos os pesos zero em todo lugar”).
Normal truncada (truncated normal) (comum em Transformers)
Implementações de Transformer (Transformer) em larga escala às vezes usam uma normal truncada com um desvio padrão fixo pequeno (por exemplo, 0.02), especialmente para vetores de incorporação (embeddings) e matrizes de projeção (projection matrices). Essa prática é em parte histórica e em parte empírica; a estabilidade frequentemente vem da estrutura residual e da normalização (comumente Normalização por Camada (Layer Normalization)).
Dito isso, muitas bibliotecas ainda usam escalonamento no estilo Xavier para projeções de atenção/MLP, e ambos podem funcionar dependendo da arquitetura exata e do posicionamento da normalização.
fan_in e fan_out (detalhe importante)
As fórmulas de inicialização dependem de:
- fan_in: número de conexões de entrada (fan-in) para um neurônio
- fan_out: número de conexões de saída (fan-out) de um neurônio
Para uma camada densa com formato de pesos (out_features, in_features):
fan_in = in_featuresfan_out = out_features
Para uma convolução com formato de pesos (out_channels, in_channels, kH, kW):
fan_in = in_channels * kH * kWfan_out = out_channels * kH * kW
Frameworks normalmente calculam isso corretamente, mas vale a pena saber ao implementar camadas customizadas.
Inicialização de vieses: o que fazer na prática
Vieses normalmente não afetam a propagação de variância tão fortemente quanto os pesos, mas podem importar.
Práticas comuns:
- A maioria das camadas:
bias = 0é aceitável. - Redes ReLU: às vezes um viés positivo pequeno (por exemplo, 0.01) foi usado historicamente para reduzir “ReLUs mortas (dead ReLUs)”, mas com boa inicialização, normalização e otimizadores modernos isso é menos comum.
- Parâmetros de Normalização em Lote (Batch Normalization; BatchNorm)/Normalização por Camada (LayerNorm):
- escala (gamma/weight) geralmente inicializada em 1
- deslocamento (beta/bias) inicializado em 0
- Viés da porta de esquecimento (forget gate) em memória de longo curto prazo (LSTM): frequentemente inicializado em 1 (ou um valor positivo) para incentivar o “lembrar” no início do treinamento.
Diretrizes práticas por função de ativação
ReLU / LeakyReLU
- Use inicialização He/Kaiming com a configuração correta de não linearidade (ganho).
- Se estiver usando LeakyReLU com inclinação negativa
a, use um ganho que leveaem conta (frameworks podem fazer isso automaticamente).
tanh / sigmoid
- Use Xavier/Glorot.
- Essas ativações saturam facilmente; uma inicialização grande demais empurra as pré-ativações para saturação, causando gradientes que desaparecem.
GELU / SiLU (Swish)
- Comuns em Transformers e MLPs modernas.
- Xavier costuma ser aceitável; muitos repositórios usam normal truncada com desvio padrão pequeno, mais um projeto com normalização/estrutura residual.
- Se o treinamento estiver instável, tente Xavier (ou Xavier escalado) e garanta que a escala de normalização/residual esteja correta.
SELU
- Use LeCun normal e siga as suposições da SELU (por exemplo, AlphaDropout em vez de dropout padrão) se o objetivo for comportamento auto-normalizante.
Diretrizes práticas por arquitetura
MLPs (redes totalmente conectadas)
- Família ReLU: Kaiming.
- tanh: Xavier.
- Se forem muito profundas e o treinamento for sensível, considere:
- adicionar normalização (Normalização em Lote ou Normalização por Camada)
- usar conexões residuais
- inicialização ortogonal em algumas camadas
CNNs
- A inicialização Kaiming é o padrão de trabalho para CNNs ReLU.
- Garanta que o fan-in seja calculado incluindo o tamanho do kernel (o padrão do framework normalmente está correto).
- Para CNNs residuais muito profundas, é comum:
- usar Kaiming para a maioria das convoluções
- inicializar a escala da última BN em cada ramo residual com 0 (para que cada bloco comece como algo próximo de identidade), o que frequentemente estabiliza o treinamento inicial
RNNs / LSTMs / GRUs
RNNs são particularmente sensíveis à propagação de gradientes ao longo do tempo.
Receita comum:
- Pesos de entrada-para-oculto: Xavier ou Kaiming dependendo da ativação.
- Pesos oculto-para-oculto (recorrentes): ortogonal é frequentemente benéfico.
- Viés da porta de esquecimento em LSTM: definir como 1.
Mesmo com boa inicialização, considere abordagens com portas e normalização se as sequências forem longas.
Transformers
Transformers tipicamente dependem de:
- conexões residuais
- normalização (frequentemente pré-normalização (pre-norm) com Normalização por Camada) para manter a estabilidade.
Práticas comuns:
- Projeções lineares (Q/K/V, projeção de saída, MLP): Xavier-like ou normal truncada com desvio padrão pequeno.
- Vetores de incorporação: normal pequena/normal truncada.
- Se você observar divergência cedo, verifique:
- posicionamento da normalização (pré-LN vs pós-LN (post-LN))
- convenções de escala residual (residual scaling)
- aquecimento da taxa de aprendizado (learning rate warmup)
- se o desvio padrão da inicialização está grande demais para a sua profundidade
Para Transformers mais profundos, algumas implementações também escalonam as saídas dos ramos residuais (mudança em nível de arquitetura), em vez de depender apenas da inicialização.
Inicialização e normalização: como interagem
Camadas de normalização reduzem a sensibilidade à inicialização, mas não a eliminam.
- Com Normalização em Lote, a escala das ativações no forward é parcialmente controlada, então redes frequentemente treinam mesmo com uma inicialização um pouco “errada”. Ainda assim, Kaiming/Xavier continua recomendado.
- Com Normalização por Camada e Normalização por Grupo (Group Normalization), a escala é normalizada por exemplo (ou por grupo), o que ajuda na estabilidade, especialmente quando as estatísticas do batch são ruidosas. A inicialização ainda importa para:
- dinâmica de otimização inicial
- escala do ramo residual
- escala dos logits de atenção (em Transformers)
Um ponto prático: use inicialização padrão escalada por fan mesmo ao usar normalização, a menos que você tenha um motivo forte para não fazê-lo.
Exemplos práticos de código (PyTorch)
Usando inicializadores embutidos corretamente
import torch
import torch.nn as nn
import torch.nn.init as init
class MLP(nn.Module):
def __init__(self, d_in, d_hidden, d_out):
super().__init__()
self.fc1 = nn.Linear(d_in, d_hidden)
self.act = nn.ReLU()
self.fc2 = nn.Linear(d_hidden, d_out)
self.reset_parameters()
def reset_parameters(self):
# He/Kaiming for ReLU layers
init.kaiming_normal_(self.fc1.weight, nonlinearity="relu")
init.zeros_(self.fc1.bias)
# Output layer: depends on task; Xavier is a common neutral choice
init.xavier_normal_(self.fc2.weight)
init.zeros_(self.fc2.bias)
def forward(self, x):
return self.fc2(self.act(self.fc1(x)))
Exemplo de convolução (fan-in inclui a área do kernel)
class ConvNet(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(3, 64, kernel_size=3, padding=1)
self.act = nn.ReLU()
self.reset_parameters()
def reset_parameters(self):
init.kaiming_normal_(self.conv.weight, nonlinearity="relu")
init.zeros_(self.conv.bias)
def forward(self, x):
return self.act(self.conv(x))
Truque do viés da porta de esquecimento em LSTM
O PyTorch armazena vieses de LSTM concatenados para as portas. Um padrão comum:
lstm = nn.LSTM(input_size=128, hidden_size=256, num_layers=1)
for name, param in lstm.named_parameters():
if "weight_ih" in name:
init.xavier_uniform_(param)
elif "weight_hh" in name:
init.orthogonal_(param)
elif "bias" in name:
nn.init.zeros_(param)
# Set forget gate bias to 1
# LSTM gate order in PyTorch: input, forget, cell, output
hidden_size = lstm.hidden_size
param.data[hidden_size:2*hidden_size].fill_(1.0)
Depuração e diagnósticos: como perceber que a inicialização está te prejudicando
Sintomas de inicialização ruim frequentemente aparecem nas primeiras poucas centenas de passos:
- A loss vira
NaNrapidamente (frequentemente por explosão de ativações/gradientes) - A loss não diminui em nada (frequentemente por desaparecimento de gradientes ou ativações saturadas)
- Histogramas de ativação viram todos zeros (ReLUs mortas) ou extremamente grandes
- Normas de gradiente ficam perto de zero nas camadas iniciais
Verificações úteis:
- registrar normas de gradiente por camada (por exemplo, valores retornados de
torch.nn.utils.clip_grad_norm_, ou hooks) - monitorar estatísticas das ativações (média/desvio padrão) em algumas profundidades
- reduzir temporariamente a profundidade para ver se a instabilidade escala com o número de camadas
- verificar se você não está sobrescrevendo acidentalmente padrões do framework (por exemplo, um
reset_parameterscustomizado)
Problemas de inicialização também podem ser confundidos com:
- taxa de aprendizado grande demais
- falta de aquecimento (warmup) (comum em Transformers)
- estouro em precisão mista (mixed precision) sem escalonamento de gradiente (gradient scaling)
- posicionamento incorreto da normalização
Recomendações “cheat sheet”
- CNN/MLP ReLU/LeakyReLU: He/Kaiming (fan-in), bias = 0
- tanh/sigmoid: Xavier/Glorot (fan-in + fan-out)
- SELU: LeCun normal (fan-in), seguir convenções da SELU
- Pesos recorrentes em RNN/LSTM: ortogonal frequentemente ajuda; viés de esquecimento em LSTM = 1
- Redes residuais: considere inicializar com zero a última escala de BN (ou a última camada no ramo residual) para que os blocos comecem próximos de identidade
- Transformers: Xavier-like ou normal pequena/normal truncada + convenções corretas de Normalização por Camada/residual; cuidado com profundidade
Principais conclusões
A inicialização de pesos não é apenas uma formalidade — é uma escolha deliberada de projeto que ajuda a manter propagação estável de sinais e gradientes, permitindo que redes profundas treinem com eficiência. Inicializações modernas escaladas por fan (Xavier/Glorot, He/Kaiming, LeCun) e abordagens conscientes da estrutura (ortogonal, inícios residuais tipo identidade) são ferramentas simples e de alto impacto. Mesmo ao usar camadas de normalização como Normalização em Lote, Normalização por Camada ou Normalização por Grupo, uma boa inicialização continua sendo uma parte importante de uma receita estável de aprendizado profundo (deep learning).