Normalizarea datelor
Normalizarea transformă valorile datelor într-un interval standard, de obicei între 0 și 1. Este un pas esențial pentru mulți algoritmi de machine learning care sunt sensibili la scala datelor.
De ce avem nevoie de normalizare?
Imaginează-ți că vrei să prezici dacă un client va cumpăra un produs, pe baza vârstei și salariului:
import pandas as pd
df = pd.DataFrame({
'vârstă': [25, 35, 45, 55], # Interval: 25-55
'salariu': [3000, 5000, 7000, 9000], # Interval: 3000-9000
'ani_experiență': [2, 5, 10, 15] # Interval: 2-15
})
Problema e că salariul are valori mult mai mari decât vârsta sau experiența. Fără normalizare, algoritmii bazați pe distanțe (cum ar fi KNN) vor considera că diferența de salariu e mult mai importantă decât diferența de vârstă, doar pentru că numerele sunt mai mari.
Algoritmi afectați de scale diferite:
- KNN - calculează distanțe, deci valorile mari domină
- Rețele neurale - convergență lentă dacă inputurile au scale diferite
- SVM - performanță slabă pe date nescalate
- Regresia liniară - coeficienții devin greu de interpretat
Metoda 1: Min-Max Normalization
Transformă toate valorile în intervalul [0, 1]. Valoarea minimă devine 0, valoarea maximă devine 1.
Formula: $X_{norm} = \frac{X - X_{min}}{X_{max} - X_{min}}$
import numpy as np
from sklearn.preprocessing import MinMaxScaler
# Date originale
X = np.array([[25, 3000], [35, 5000], [45, 7000], [55, 9000]])
# Varianta manuală
X_min = X.min(axis=0)
X_max = X.max(axis=0)
X_norm = (X - X_min) / (X_max - X_min)
print(X_norm)
# Cu sklearn (recomandat)
scaler = MinMaxScaler()
X_norm = scaler.fit_transform(X)
print(X_norm)
Rezultat:
[[0. 0. ]
[0.333 0.333]
[0.667 0.667]
[1. 1. ]]
Acum ambele coloane au valori între 0 și 1, și niciuna nu domină calculele.
Interval personalizat
Dacă vrei un alt interval (de exemplu, -1 la 1):
scaler = MinMaxScaler(feature_range=(-1, 1))
X_norm = scaler.fit_transform(X)
Metoda 2: Max Absolute Scaling
Împarte fiecare valoare la valoarea maximă absolută din acea coloană. Rezultatul e în intervalul [-1, 1].
Formula: $X_{norm} = \frac{X}{|X_{max}|}$
from sklearn.preprocessing import MaxAbsScaler
scaler = MaxAbsScaler()
X_norm = scaler.fit_transform(X)
Când e utilă: Pentru date sparse (cu multe zerouri) sau date care pot fi negative. Păstrează zerourile ca zerouri și semnul valorilor.
Metoda 3: Normalizarea L1 și L2
Aceste metode normalizează fiecare rând (nu fiecare coloană) astfel încât să aibă o "lungime" unitară.
Norma L1 - suma valorilor absolute devine 1
from sklearn.preprocessing import Normalizer
import numpy as np
normalizer = Normalizer(norm='l1')
X_norm = normalizer.fit_transform(X)
# Verificare: suma absolută pe fiecare rând = 1
print(np.abs(X_norm).sum(axis=1)) # [1. 1. 1. 1.]
Norma L2 - lungimea vectorului devine 1
normalizer = Normalizer(norm='l2')
X_norm = normalizer.fit_transform(X)
# Verificare: norma euclidiană pe fiecare rând = 1
print(np.linalg.norm(X_norm, axis=1)) # [1. 1. 1. 1.]
Când e utilă: În clasificarea textului (TF-IDF), unde vrei să compari documente indiferent de lungimea lor.
Vizualizare: Cum arată datele înainte și după
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler, MaxAbsScaler
# Date cu distribuție exponențială
np.random.seed(42)
data = np.random.exponential(scale=2, size=(100, 1))
fig, axes = plt.subplots(1, 4, figsize=(15, 4))
# Original
axes[0].hist(data, bins=20, edgecolor='black')
axes[0].set_title('Original')
axes[0].set_xlabel(f'Range: [{data.min():.1f}, {data.max():.1f}]')
# Min-Max
scaler_mm = MinMaxScaler()
data_mm = scaler_mm.fit_transform(data)
axes[1].hist(data_mm, bins=20, edgecolor='black')
axes[1].set_title('Min-Max [0,1]')
# Standardizare (Z-score)
scaler_std = StandardScaler()
data_std = scaler_std.fit_transform(data)
axes[2].hist(data_std, bins=20, edgecolor='black')
axes[2].set_title('Standardizare (Z-score)')
# Max Absolute
scaler_max = MaxAbsScaler()
data_max = scaler_max.fit_transform(data)
axes[3].hist(data_max, bins=20, edgecolor='black')
axes[3].set_title('Max Absolute')
plt.tight_layout()
plt.show()
Observă că forma distribuției rămâne aceeași - normalizarea doar schimbă scala, nu distribuția.
Ce algoritmi au nevoie de normalizare?
| Algoritm | Necesită normalizare? | De ce? |
|---|---|---|
| KNN | Da | Calculează distanțe între puncte |
| SVM | Da | Sensibil la scale diferite |
| Rețele neurale | Da | Convergență mai rapidă |
| K-Means | Da | Calculează distanțe |
| PCA | Da | Variația depinde de scală |
| Regresie liniară | Opțional | Ajută la interpretarea coeficienților |
| Decision Trees | Nu | Bazat pe reguli, nu pe distanțe |
| Random Forest | Nu | Bazat pe arbori |
| XGBoost | Nu | Bazat pe arbori |
Regulă simplă: Dacă algoritmul calculează distanțe sau folosește gradient descent, normalizează datele.
Utilizare corectă în pipeline
O greșeală comună este să normalizezi toate datele înainte de a le împărți în train/test. Problema: informații din setul de test "se scurg" în procesul de normalizare (știi min/max-ul întregului set).
Greșit - data leakage
# ❌ NU așa
scaler = MinMaxScaler()
X_normalized = scaler.fit_transform(X) # Folosește info din test!
X_train, X_test = train_test_split(X_normalized)
Corect - fit pe train, transform pe test
# ✅ Așa e corect
X_train, X_test, y_train, y_test = train_test_split(X, y)
scaler = MinMaxScaler()
X_train_norm = scaler.fit_transform(X_train) # Învață din train
X_test_norm = scaler.transform(X_test) # Aplică pe test
Și mai bine - folosește Pipeline
Pipeline-ul gestionează automat această separare:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
pipeline = Pipeline([
('normalizer', MinMaxScaler()),
('classifier', KNeighborsClassifier(n_neighbors=5))
])
pipeline.fit(X_train, y_train)
accuracy = pipeline.score(X_test, y_test)
Inversarea normalizării
După ce obții predicții, uneori vrei să le transformi înapoi la scala originală:
scaler = MinMaxScaler()
X_norm = scaler.fit_transform(X)
# ... antrenezi modelul, obții predicții ...
# Inversare la scala originală
X_original = scaler.inverse_transform(X_norm)
Exemplu complet
import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
# Date
df = pd.DataFrame({
'vârstă': [25, 30, 35, 40, 45, 50, 55, 60],
'salariu': [3000, 3500, 4500, 5000, 6000, 7000, 8000, 9000],
'cumpără': [0, 0, 0, 1, 1, 1, 1, 1]
})
X = df[['vârstă', 'salariu']].values
y = df['cumpără'].values
# Split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
# Fără normalizare
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
acc_fara = accuracy_score(y_test, knn.predict(X_test))
print(f"Acuratețe fără normalizare: {acc_fara:.2f}")
# Cu normalizare
scaler = MinMaxScaler()
X_train_norm = scaler.fit_transform(X_train)
X_test_norm = scaler.transform(X_test)
knn_norm = KNeighborsClassifier(n_neighbors=3)
knn_norm.fit(X_train_norm, y_train)
acc_cu = accuracy_score(y_test, knn_norm.predict(X_test_norm))
print(f"Acuratețe cu normalizare: {acc_cu:.2f}")
Normalizare vs Standardizare
Sunt două tehnici diferite pentru același scop: aducerea datelor la o scală comună.
| Aspect | Normalizare (Min-Max) | Standardizare (Z-score) |
|---|---|---|
| Interval rezultat | [0, 1] sau personalizat | Medie 0, deviație standard 1 |
| Sensibilă la outlieri | Da (foarte) | Mai puțin |
| Când o folosesc | Rețele neurale, imagini | Date cu distribuție normală, PCA |
Regulă simplă: - Folosește Min-Max când ai nevoie de valori într-un interval fix (ex: inputuri pentru rețele neurale) - Folosește Standardizare când datele au o distribuție aproximativ normală sau când ai outlieri