Normalização de Dados: Escalonamento de Características

Antes de tudo, feliz Ano Novo! Agradecemos à plataforma CSDN por ser um espaço acolhedor para troca técnica. Neste Ano da Serpente, dseejamos crescimento contínuo, eventos vibrantes e que mais desenvolvedores superem barreiras técnicas, explorando todo o potencial no novo ano.

O que é Normalização de Dados?

Durante a preparação de dados, escalonar características — como normalização ou padronização — é essencial. Métricas distintas frequentemente possuem unidades e mangitudes diferentes, o que pode distorcer análises. Para tornar os dados comparáveis e eliminar viés dimensional, aplicamos transformações que ajustam todos os valores a uma mesma escala, facilitando comparações e melhorando a qualidade das decisões baseadas em dados.

Vantagens da Normalização

  • Convergência mais rápida em modelos de aprendizado: Quando variáveis estão em escalas muito diferentes, superfícies de perda se tornam alongadas, dificultando a descida do gradiente. Normalizar equaliza as escalas, resultando em caminhos de otimização mais curtos e estáveis.
  • Remoção de unidades físicas (adimensionalização): Transforma dados em valores relativos entre 0 e 1, independentemente da unidade original — útil para comparação direta entre atributos heterogêneos.

Exemplo Visual: Superfície de Perda sem e com Normalização

import numpy as np
import matplotlib.pyplot as plt

# Gera dados sintéticos com escalas diferentes
feat_a = np.random.rand(100) * 10      # Pequena escala
feat_b = np.random.rand(100) * 1000    # Grande escala
target = 15 + 20*feat_a + 0.1*feat_b + np.random.rand(100)*5

# Monta matriz de design
X_raw = np.column_stack([np.ones(100), feat_a, feat_b])

# Função de custo MSE
def compute_mse(weights, X, y_true):
    preds = X @ weights
    return np.mean((y_true - preds)**2) / 2

# Espaço de parâmetros para plotagem
w_range_1 = np.linspace(-10, 50, 80)
w_range_2 = np.linspace(-0.3, 0.5, 80)
W1, W2 = np.meshgrid(w_range_1, w_range_2)
loss_surface = np.zeros_like(W1)

for i in range(W1.shape[0]):
    for j in range(W1.shape[1]):
        w_vec = np.array([0, W1[i,j], W2[i,j]])
        loss_surface[i,j] = compute_mse(w_vec, X_raw, target)

plt.figure(figsize=(7,5))
plt.contour(W1, W2, loss_surface, levels=40, cmap='plasma')
plt.colorbar(label='Perda MSE')
plt.xlabel('Peso W₁')
plt.ylabel('Peso W₂')
plt.title('Superfície de Perda (Dados Originais)')
plt.show()

Ao normalizar as entradas usando Min-Max, os pesos convergem em intervalos similares, evitando problemas numéricos e oscilações indesejadas:

# Normalização Min-Max
norm_func = lambda x: (x - x.min()) / (x.max() - x.min())
feat_a_norm = norm_func(feat_a) * 100
feat_b_norm = norm_func(feat_b) * 100

X_norm = np.column_stack([np.ones(100), feat_a_norm, feat_b_norm])

# Recalcula superfície com dados normalizados
loss_norm = np.zeros_like(W1)
w_grid_small = np.linspace(-2, 5, 80)
W1s, W2s = np.meshgrid(w_grid_small, w_grid_small)

for i in range(W1s.shape[0]):
    for j in range(W1s.shape[1]):
        w_vec = np.array([0, W1s[i,j], W2s[i,j]])
        loss_norm[i,j] = compute_mse(w_vec, X_norm, target)

plt.figure(figsize=(7,5))
plt.contour(W1s, W2s, loss_norm, levels=40, cmap='inferno')
plt.colorbar(label='Perda MSE')
plt.xlabel('Peso W₁ (normalizado)')
plt.ylabel('Peso W₂ (normalizado)')
plt.title('Superfície de Perda (Dados Normalizados)')
plt.show()

Métodos Comuns de Normalização

1. Normalização Min-Max

Transforma dados para o intervalo [0,1]:

x_scaled = (x - x_min) / (x_max - x_min)

Para mapear para outro intervalo [a,b]:

x_scaled = a + (x - x_min) * (b - a) / (x_max - x_min)

2. Padronização Z-Score

Centraliza em média zero e desvio padrão unitário:

z = (x - μ) / σ

def z_score_transform(arr):
    mean_val = np.mean(arr)
    std_val = np.std(arr)
    return (arr - mean_val) / std_val

sample = np.array([10, 20, 30, 40, 50])
print("Original:", sample)
print("Z-Score:", z_score_transform(sample))

3. Escalonamento Decimal

Move a vírgula decimal até que todos os valores fiquem entre [-1,1]:

def decimal_scale(data):
    max_abs = np.max(np.abs(data))
    scale_power = len(str(int(max_abs)))
    divisor = 10 ** scale_power
    return data / divisor

values = np.array([1234, -5678, 91011])
print("Original:", values)
print("Escalonado:", decimal_scale(values))

4. RobustScaler (Baseado em Mediana e IQR)

Resistente a outliers, usa quartis em vez de média/desvio:

x_scaled = (x - mediana) / (Q3 - Q1)

def robust_transform(data, q_low=25, q_high=75):
    q25 = np.percentile(data, q_low)
    q75 = np.percentile(data, q_high)
    median = np.median(data)
    iqr = q75 - q25
    if iqr == 0:
        iqr = 1  # Evita divisão por zero
    return (data - median) / iqr

noisy_data = np.array([1, 2, 3, 4, 1000])  # Com outlier
print("Original:", noisy_data)
print("RobustScaler:", robust_transform(noisy_data))

Implementação com Bibliotecas

O scikit-learn oferece escalonadores prontos:

from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler

data = np.random.rand(100, 1) * 500

scalers = {
    'MinMax': MinMaxScaler(),
    'Standard': StandardScaler(),
    'Robust': RobustScaler()
}

fig, axes = plt.subplots(1, 4, figsize=(14, 3))
axes[0].hist(data, bins=20, color='gray', alpha=0.7)
axes[0].set_title('Original')

for idx, (name, scaler) in enumerate(scalers.items(), 1):
    scaled = scaler.fit_transform(data)
    axes[idx].hist(scaled, bins=20, color='steelblue', alpha=0.7)
    axes[idx].set_title(name)

plt.tight_layout()
plt.show()

Tags: NumPy scikit-learn normalização-de-dados

Publicado em 10-2 13:42