Faza 03 · lecția 04

Funcții de activare

Scopul lecției: Fără neliniaritate, rețeaua dumneavoastră cu 100 de straturi este doar o înmulțire matricială sofisticată. Activările sunt porțile care permit rețelelor neurale să gândească în curbe.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
Fundamentele învățării profunde
Lectură
25 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. De ce este necesară neliniaritatea
  5. Sigmoida
  6. Tangenta hiperbolică
  7. ReLU: momentul de cotitură
  8. Leaky ReLU
  9. GELU: o opțiune modernă frecventă
  10. Swish / SiLU
  11. Softmax: activarea de ieșire
  12. Compararea formelor
  13. Compararea fluxului gradientului
  14. Ce activare să folosiți și când
  15. Construiți
  16. Pasul 1: implementarea tuturor funcțiilor de activare și a derivatelor
  17. Pasul 2: vizualizarea zonelor în care gradienții se sting
  18. Pasul 3: experimentul gradienților care dispar
  19. Pasul 4: detectorul de neuroni morți
  20. Pasul 5: compararea antrenării — sigmoidă, ReLU și GELU
  21. Folosiți
  22. Livrați
  23. Exerciții
  24. Termeni-cheie
  25. Lecturi suplimentare

Fără neliniaritate, rețeaua dumneavoastră cu 100 de straturi este doar o înmulțire matricială sofisticată. Activările sunt porțile care permit rețelelor neurale să gândească în curbe.

Tip: Construire Limbaje: Python Cerințe preliminare: Lecția 03.03 — Retropropagarea Durată: ~75 de minute

Obiective de învățare

  • Să implementați de la zero funcțiile sigmoidă, tangentă hiperbolică, ReLU, Leaky ReLU, GELU, Swish și softmax; să calculați derivatele scalare unde sunt definite și să explicați Jacobianul softmax.
  • Să diagnosticați problema gradienților care dispar măsurând amplitudinea activărilor prin cel puțin zece straturi cu activări diferite.
  • Să detectați neuronii morți într-o rețea ReLU și să explicați de ce GELU reduce acest mod de eșec.
  • Să selectați funcția de activare potrivită unei arhitecturi date — transformator, CNN, RNN sau strat de ieșire.

Problema

Suprapuneți două transformări: y = W2(W1x + b1) + b2. Dezvoltați expresia: y = W2W1x + W2b1 + b2. Aceasta este doar y = Ax + c — o singură transformare afină. Indiferent câte straturi de acest fel suprapuneți, rezultatul se reduce la o singură înmulțire matricială urmată de adăugarea unui bias. Rețeaua cu 100 de straturi are aceeași putere de reprezentare ca un singur strat afin.

Aceasta nu este doar o curiozitate teoretică. Înseamnă că o rețea profundă formată exclusiv din asemenea transformări nu poate învăța XOR, nu poate clasifica un set de date în spirală și nu poate reprezenta frontierele neliniare necesare multor sarcini de recunoaștere. Fără funcții de activare, profunzimea este o iluzie.

Funcțiile de activare rup această structură afină. Ele deformează ieșirea fiecărui strat printr-o funcție neliniară, oferind rețelei capacitatea de a curba frontierele de decizie, de a aproxima funcții arbitrare și de a învăța efectiv. Însă alegerea nepotrivită poate favoriza gradienți care se micșorează spre zero — sigmoida în rețele profunde —, gradienți care explodează sau neuroni care devin permanent inactivi — ReLU cu biasuri negative mari. Alegerea funcției de activare influențează direct dinamica învățării.

Notă tehnică a traducerii: Expresiile Wx + b sunt transformări afine, nu liniare atunci când b != 0. În plus, explozia gradienților nu este cauzată doar de faptul că o activare este nelimitată; ea depinde de produsul Jacobienelor, de ponderi, inițializare, normalizare și arhitectură.

Conceptul

De ce este necesară neliniaritatea

Înmulțirea matricială se compune. Înmulțirea unui vector mai întâi cu matricea A, apoi cu matricea B, este identică cu înmulțirea lui cu matricea produs BA. Prin urmare, suprapunerea a zece straturi afine este echivalentă matematic cu un singur strat afin având o matrice și un bias compuse. Toți acei parametri și toată acea profunzime nu extind familia funcțiilor reprezentabile. Aveți nevoie de ceva care să rupă lanțul. Acesta este rolul funcțiilor de activare.

Iată demonstrația. Un strat afin calculează f(x) = Wx + b. Suprapuneți două:

Stratul 1: h = W1 * x + b1
Stratul 2: y = W2 * h + b2

Înlocuiți h:

y = W2 * (W1 * x + b1) + b2
y = (W2 * W1) * x + (W2 * b1 + b2)
y = A * x + c

Un singur strat. Introduceți între straturi o activare neliniară g():

h = g(W1 * x + b1)
y = W2 * h + b2

Acum reducerea nu mai este posibilă. Expresia W2 * g(W1 * x + b1) + b2 nu se poate reduce la o singură transformare afină. Rețeaua poate reprezenta funcții neliniare. Fiecare strat suplimentar însoțit de o activare poate mări capacitatea de reprezentare.

Sigmoida

Funcția de activare clasică a rețelelor neurale:

sigmoid(x) = 1 / (1 + e^(-x))

Intervalul ieșirii: (0, 1). Este netedă, diferențiabilă și mapează orice număr real la o valoare asemănătoare unei probabilități.

Derivata:

sigmoid'(x) = sigmoid(x) * (1 - sigmoid(x))

Valoarea maximă a acestei derivate este 0,25 și apare la x = 0. În retropropagare, factorii locali se înmulțesc prin straturi. Zece derivate sigmoidale pot contribui cu cel mult factorul 0.25 de zece ori:

0.25^10 = 0.000000953674

Mai puțin de o milionime din semnalul inițial. Aceasta este problema gradienților care dispar. Gradienții din straturile timpurii pot deveni atât de mici încât ponderile aproape nu se mai actualizează. Rețeaua poate părea să învețe — pierderea globală poate scădea datorită ajustărilor din straturile ulterioare —, dar primele straturi rămân aproape înghețate. Rețelele sigmoidale profunde sunt greu de antrenat.

O problemă suplimentară este că ieșirile sigmoidei sunt întotdeauna pozitive, între 0 și 1. În anumite configurații, componentele actualizării unei ponderi pot avea semne corelate, ceea ce produce un traseu în zigzag pentru coborârea gradientului.

Notă tehnică a traducerii: Formularea originală conform căreia gradienții ponderilor au „întotdeauna același semn” este prea generală. Semnul depinde și de gradientul sosit din aval și de intrările neuronului. Ieșirile necentrate pot corela direcțiile actualizărilor, dar nu impun universal același semn tuturor gradienților.

Tangenta hiperbolică

Versiunea centrată a sigmoidei:

tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))

Intervalul ieșirii: (-1, 1). Este centrată în jurul lui zero, ceea ce poate reduce problema deplasării în zigzag.

Derivata:

tanh'(x) = 1 - tanh(x)^2

Derivata maximă este 1,0 la x = 0 — de patru ori mai mare decât pentru sigmoidă. Problema gradienților care dispar există însă în continuare. Pentru intrări pozitive sau negative mari, derivata tinde spre zero. Zece straturi pot totuși comprima gradientul, chiar dacă mai puțin agresiv în zona centrală.

Notă tehnică a traducerii: Centrarea ieșirii în jurul lui zero poate îmbunătăți condiționarea actualizărilor, dar nu „elimină” în mod garantat traseele în zigzag; optimizarea depinde de întregul model și de distribuția datelor.

ReLU: momentul de cotitură

Unitatea liniară rectificată. Popularizată pentru învățarea profundă de Nair și Hinton în 2010 — funcțiile rectificate au antecedente mai vechi —, aceasta a schimbat substanțial practica domeniului.

relu(x) = max(0, x)

Intervalul ieșirii: [0, infinit). Derivata este extrem de simplă:

relu'(x) = 1  dacă x > 0
            0  dacă x <= 0

Pentru o intrare locală pozitivă, derivata activării nu micșorează gradientul: factorul este exact 1. Această proprietate a contribuit la antrenarea rețelelor profunde, deoarece nu introduce pe ramura pozitivă atenuarea sigmoidalei.

Există însă un mod de eșec: problema neuronului mort. Dacă intrarea ponderată a unui neuron rămâne negativă pentru toate exemplele relevante — din cauza unui bias negativ mare sau a unei inițializări nefavorabile —, ieșirea sa este întotdeauna zero, gradientul local este întotdeauna zero și parametrii lui nu se mai actualizează prin acea cale. Neuronul rămâne inactiv.

Notă tehnică a traducerii: Derivata locală 1 pe ramura pozitivă nu garantează că gradientul complet nu dispare; acesta este înmulțit și cu ponderile și Jacobienii celorlalte operații. Nici procentul de 10–40% de neuroni morți menționat în original nu este o constantă generală: rata depinde de date, inițializare, rata de învățare, biasuri și arhitectură.

Leaky ReLU

Cea mai simplă soluție pentru neuronii morți:

leaky_relu(x) = x         dacă x > 0
                alpha * x dacă x <= 0

alpha este o constantă mică, de obicei 0,01. Latura negativă are o pantă mică în loc de zero, astfel încât neuronii inactivi primesc în continuare un semnal de gradient și se pot recupera.

GELU: o opțiune modernă frecventă

Unitatea liniară cu eroare gaussiană, introdusă de Hendrycks și Gimpel în 2016. GELU este utilizată în BERT, în primele arhitecturi GPT și în numeroase transformatoare.

gelu(x) = x * Phi(x)

Phi(x) este funcția de distribuție cumulativă a distribuției normale standard. Aproximarea folosită frecvent în practică:

gelu(x) ~= 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))

GELU este netedă peste tot și permite valori negative mici, spre deosebire de ReLU, care le taie ferm la zero. Are și o interpretare probabilistă: fiecare intrare este ponderată cu probabilitatea corespunzătoare funcției cumulative gaussiene. Această poartă netedă poate oferi un flux al gradientului mai favorabil și poate evita starea strict blocată a ramurii negative ReLU.

Notă tehnică a traducerii: GELU nu este „valoarea implicită” a tuturor transformatoarelor moderne și nu depășește universal ReLU; multe arhitecturi folosesc SiLU/Swish sau variante cu porți. De asemenea, GELU are derivata care tinde spre zero pentru intrări foarte negative, deci formularea că elimină „în întregime” orice neuron inactiv este prea puternică. Avantajul este că nu are o semiaxă întreagă cu derivată exact zero, precum ReLU.

Swish / SiLU

O activare cu poartă proprie, identificată ca Swish de Ramachandran și colaboratorii în 2017 prin căutare automată:

swish(x) = x * sigmoid(x)

Swish este, formal, x * sigmoid(x). Lucrarea Google a regăsit-o prin căutarea automată în spațiul funcțiilor de activare — o rețea neurală proiectând componente ale rețelelor neurale.

La fel ca GELU, este netedă, nemonotonă și permite valori negative mici. Diferența este subtilă: Swish folosește sigmoida drept poartă, iar GELU folosește funcția de distribuție cumulativă gaussiană. În practică, performanța relativă depinde de arhitectură, date și regimul de antrenare. Swish/SiLU este folosită în EfficientNet și în modele de viziune, iar GELU este frecventă în modelele lingvistice.

Notă tehnică a traducerii: Funcția x * sigmoid(x) fusese publicată independent sub numele SiLU de Elfwing, Uchibe și Doya înainte ca lucrarea despre căutarea automată să o numească Swish. De aceea este mai precis să spunem că Swish a fost identificată și denumită în acel studiu, nu că forma matematică a fost descoperită pentru prima dată acolo.

Softmax: activarea de ieșire

Nu este folosită în mod obișnuit în straturile ascunse. Softmax transformă un vector de scoruri brute — logiți — într-o distribuție de probabilitate.

softmax(x_i) = e^(x_i) / sum(e^(x_j) pentru toți j)

Fiecare ieșire este între 0 și 1, iar toate ieșirile însumează 1. Acest lucru o face activarea finală standard pentru clasificarea cu mai multe clase reciproc exclusive. Logitul cel mai mare primește probabilitatea cea mai mare, dar, spre deosebire de argmax, softmax este diferențiabilă și păstrează informații despre încrederea relativă.

Compararea formelor

Диаграмма к уроку «Funcții de activare»

Compararea fluxului gradientului

Диаграмма к уроку «Funcții de activare»

Notă tehnică a traducerii: Valorile numerice din această diagramă sunt strict ilustrative, nu rezultate universale. Gradientul observat la un strat depinde de ponderi, intrări, pierdere, normalizare și toți Jacobienii de pe traseu; nu poate fi dedus doar din numele activării.

Ce activare să folosiți și când

Диаграмма к уроку «Funcții de activare»

Notă tehnică a traducerii: Diagrama oferă puncte de pornire, nu reguli absolute. De exemplu, clasificarea multietichetă folosește de obicei câte o sigmoidă pe clasă, iar activarea de ieșire pentru regresie depinde de domeniul țintei. Alegerea trebuie verificată împreună cu funcția de pierdere și arhitectura.

softmax-temperature

Construiți

Pasul 1: implementarea tuturor funcțiilor de activare și a derivatelor

Fiecare funcție primește un singur număr în virgulă mobilă și întoarce un număr în virgulă mobilă. Fiecare funcție de derivare primește aceeași intrare și întoarce gradientul.

import math

def sigmoid(x):
    x = max(-500, min(500, x))
    return 1.0 / (1.0 + math.exp(-x))

def sigmoid_derivative(x):
    s = sigmoid(x)
    return s * (1 - s)

def tanh_act(x):
    return math.tanh(x)

def tanh_derivative(x):
    t = math.tanh(x)
    return 1 - t * t

def relu(x):
    return max(0.0, x)

def relu_derivative(x):
    return 1.0 if x > 0 else 0.0

def leaky_relu(x, alpha=0.01):
    return x if x > 0 else alpha * x

def leaky_relu_derivative(x, alpha=0.01):
    return 1.0 if x > 0 else alpha

def gelu(x):
    return 0.5 * x * (1 + math.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * x ** 3)))

def gelu_derivative(x):
    phi = 0.5 * (1 + math.erf(x / math.sqrt(2)))
    pdf = math.exp(-0.5 * x * x) / math.sqrt(2 * math.pi)
    return phi + x * pdf

def swish(x):
    return x * sigmoid(x)

def swish_derivative(x):
    s = sigmoid(x)
    return s + x * s * (1 - s)

def softmax(xs):
    max_x = max(xs)
    exps = [math.exp(x - max_x) for x in xs]
    total = sum(exps)
    return [e / total for e in exps]

Notă tehnică a traducerii: Codul gelu implementează aproximarea cu tanh, dar gelu_derivative implementează derivata formei exacte x * Phi(x). Cele două nu sunt derivate exacte una a celeilalte. Blocul este păstrat neschimbat față de sursă; pentru verificări de gradient, folosiți aceeași variantă GELU atât în propagarea înainte, cât și în derivată.

Notă tehnică a traducerii: Softmax este o funcție vectorială, astfel încât derivata sa este un Jacobian, nu o derivată scalară; codul nu implementează acest Jacobian. De asemenea, limitarea intrării din sigmoid la [-500, 500] schimbă funcția, iar sigmoid_derivative și swish_derivative nu aplică derivata limitării, care este zero în afara intervalului. Aceste funcții sunt inconsistente pentru intrări limitate; utilizați implementări stabile dintr-o bibliotecă sau reprezentați limitarea în graful de diferențiere.

Pasul 2: vizualizarea zonelor în care gradienții se sting

Calculați gradientul în 100 de puncte echidistante în intervalul semiînchis [-5, 5). Afișați o histogramă textuală care arată unde gradientul fiecărei activări este aproape de zero.

def gradient_scan(name, derivative_fn, start=-5, end=5, n=100):
    step = (end - start) / n
    near_zero = 0
    healthy = 0
    for i in range(n):
        x = start + i * step
        g = derivative_fn(x)
        if abs(g) < 0.01:
            near_zero += 1
        else:
            healthy += 1
    pct_dead = near_zero / n * 100
    print(f"{name:15s}: {healthy:3d} healthy, {near_zero:3d} near-zero ({pct_dead:.0f}% dead zone)")

gradient_scan("Sigmoid", sigmoid_derivative)
gradient_scan("Tanh", tanh_derivative)
gradient_scan("ReLU", relu_derivative)
gradient_scan("Leaky ReLU", leaky_relu_derivative)
gradient_scan("GELU", gelu_derivative)
gradient_scan("Swish", swish_derivative)

Notă tehnică a traducerii: Scriptul măsoară proporția punctelor din intervalul ales unde derivata locală este sub pragul 0,01. Eticheta engleză dead zone nu înseamnă că acel procent de neuroni este mort în timpul antrenării; moartea unui neuron presupune inactivitate persistentă pe distribuția datelor și pe parcursul actualizărilor.

Pasul 3: experimentul gradienților care dispar

Propagați înainte un semnal prin N straturi folosind sigmoidă și ReLU. Măsurați cum se schimbă amplitudinea activării.

import random

def vanishing_gradient_experiment(activation_fn, name, n_layers=10, n_inputs=5):
    random.seed(42)
    values = [random.gauss(0, 1) for _ in range(n_inputs)]

    print(f"\n{name} through {n_layers} layers:")
    for layer in range(n_layers):
        weights = [random.gauss(0, 1) for _ in range(n_inputs)]
        z = sum(w * v for w, v in zip(weights, values))
        activated = activation_fn(z)
        magnitude = abs(activated)
        bar = "#" * int(magnitude * 20)
        print(f"  Layer {layer+1:2d}: magnitude = {magnitude:.6f} {bar}")
        values = [activated] * n_inputs

vanishing_gradient_experiment(sigmoid, "Sigmoid")
vanishing_gradient_experiment(relu, "ReLU")
vanishing_gradient_experiment(gelu, "GELU")

Notă tehnică a traducerii: În pofida numelui funcției, acest experiment execută numai propagarea înainte și măsoară amplitudinea activărilor, nu gradienții. Un diagnostic al dispariției gradientului trebuie să definească o pierdere, să execute propagarea înapoi și să măsoare derivatele pe straturi. Ponderile necalibrate și reutilizarea aceleiași activări în toate intrările fac rezultatul de aici doar demonstrativ.

Pasul 4: detectorul de neuroni morți

Creați o rețea ReLU, propagați prin ea intrări aleatoare și numărați neuronii care nu se activează niciodată.

def dead_neuron_detector(n_inputs=5, hidden_size=20, n_samples=1000):
    random.seed(0)
    weights = [[random.gauss(0, 1) for _ in range(n_inputs)] for _ in range(hidden_size)]
    biases = [random.gauss(0, 1) for _ in range(hidden_size)]

    fire_counts = [0] * hidden_size

    for _ in range(n_samples):
        inputs = [random.gauss(0, 1) for _ in range(n_inputs)]
        for neuron_idx in range(hidden_size):
            z = sum(w * x for w, x in zip(weights[neuron_idx], inputs)) + biases[neuron_idx]
            if relu(z) > 0:
                fire_counts[neuron_idx] += 1

    dead = sum(1 for c in fire_counts if c == 0)
    rarely_fire = sum(1 for c in fire_counts if 0 < c < n_samples * 0.05)
    healthy = hidden_size - dead - rarely_fire

    print(f"\nDead Neuron Report ({hidden_size} neurons, {n_samples} samples):")
    print(f"  Dead (never fired):     {dead}")
    print(f"  Barely alive (<5%):     {rarely_fire}")
    print(f"  Healthy:                {healthy}")
    print(f"  Dead neuron rate:       {dead/hidden_size*100:.1f}%")

    for i, c in enumerate(fire_counts):
        status = "DEAD" if c == 0 else "WEAK" if c < n_samples * 0.05 else "OK"
        bar = "#" * (c * 40 // n_samples)
        print(f"  Neuron {i:2d}: {c:4d}/{n_samples} fires [{status:4s}] {bar}")

dead_neuron_detector()

Notă tehnică a traducerii: Detectorul examinează un singur strat aleator, fără antrenare, pe un eșantion finit de intrări gaussiene. El detectează neuroni care nu s-au activat pe acel eșantion, nu demonstrează că au „murit” în timpul optimizării sau că vor rămâne inactivi pe distribuția reală.

Pasul 5: compararea antrenării — sigmoidă, ReLU și GELU

Antrenați aceeași rețea cu două straturi pe setul de date circular — punctele din interiorul unui cerc aparțin clasei 1, iar cele din exterior clasei 0 — folosind trei activări diferite. Comparați viteza convergenței.

def make_circle_data(n=200, seed=42):
    random.seed(seed)
    data = []
    for _ in range(n):
        x = random.uniform(-2, 2)
        y = random.uniform(-2, 2)
        label = 1.0 if x * x + y * y < 1.5 else 0.0
        data.append(([x, y], label))
    return data


class ActivationNetwork:
    def __init__(self, activation_fn, activation_deriv, hidden_size=8, lr=0.1):
        random.seed(0)
        self.act = activation_fn
        self.act_d = activation_deriv
        self.lr = lr
        self.hidden_size = hidden_size

        self.w1 = [[random.gauss(0, 0.5) for _ in range(2)] for _ in range(hidden_size)]
        self.b1 = [0.0] * hidden_size
        self.w2 = [random.gauss(0, 0.5) for _ in range(hidden_size)]
        self.b2 = 0.0

    def forward(self, x):
        self.x = x
        self.z1 = []
        self.h = []
        for i in range(self.hidden_size):
            z = self.w1[i][0] * x[0] + self.w1[i][1] * x[1] + self.b1[i]
            self.z1.append(z)
            self.h.append(self.act(z))

        self.z2 = sum(self.w2[i] * self.h[i] for i in range(self.hidden_size)) + self.b2
        self.out = sigmoid(self.z2)
        return self.out

    def backward(self, target):
        error = self.out - target
        d_out = error * self.out * (1 - self.out)

        for i in range(self.hidden_size):
            d_h = d_out * self.w2[i] * self.act_d(self.z1[i])
            self.w2[i] -= self.lr * d_out * self.h[i]
            for j in range(2):
                self.w1[i][j] -= self.lr * d_h * self.x[j]
            self.b1[i] -= self.lr * d_h
        self.b2 -= self.lr * d_out

    def train(self, data, epochs=200):
        losses = []
        for epoch in range(epochs):
            total_loss = 0
            correct = 0
            for x, y in data:
                pred = self.forward(x)
                self.backward(y)
                total_loss += (pred - y) ** 2
                if (pred >= 0.5) == (y >= 0.5):
                    correct += 1
            avg_loss = total_loss / len(data)
            accuracy = correct / len(data) * 100
            losses.append(avg_loss)
            if epoch % 50 == 0 or epoch == epochs - 1:
                print(f"    Epoch {epoch:3d}: loss={avg_loss:.4f}, accuracy={accuracy:.1f}%")
        return losses


data = make_circle_data()

configs = [
    ("Sigmoid", sigmoid, sigmoid_derivative),
    ("ReLU", relu, relu_derivative),
    ("GELU", gelu, gelu_derivative),
]

results = {}
for name, act_fn, act_d_fn in configs:
    print(f"\n=== Training with {name} ===")
    net = ActivationNetwork(act_fn, act_d_fn, hidden_size=8, lr=0.1)
    losses = net.train(data, epochs=200)
    results[name] = losses

print("\n=== Final Loss Comparison ===")
for name, losses in results.items():
    print(f"  {name:10s}: start={losses[0]:.4f} -> end={losses[-1]:.4f} (improvement: {(1 - losses[-1]/losses[0])*100:.1f}%)")

Notă tehnică a traducerii: Compararea GELU folosește perechea inconsistentă dintre aproximarea cu tanh și derivata formei exacte, semnalată la pasul 1. d_out omite factorul 2 al pierderii afișate (pred - y)^2, astfel că actualizarea corespunde pierderii pătratice înjumătățite 0.5 * (pred - y)^2; direcția gradientului rămâne aceeași, dar scara ratei efective de învățare diferă. În plus, pierderea și acuratețea sunt măsurate pe datele de antrenare, iar predicțiile unei epoci provin din stări succesive ale modelului, deoarece ponderile se actualizează după fiecare exemplu. Pentru o comparație solidă, evaluați după epocă un model fix pe un set separat și repetați cu mai multe seeduri.

Folosiți

PyTorch oferă toate aceste activări atât sub formă funcțională, cât și sub formă de module:

import torch
import torch.nn as nn
import torch.nn.functional as F

x = torch.randn(4, 10)

relu_out = F.relu(x)
gelu_out = F.gelu(x)
sigmoid_out = torch.sigmoid(x)
swish_out = F.silu(x)

logits = torch.randn(4, 5)
probs = F.softmax(logits, dim=1)

model = nn.Sequential(
    nn.Linear(10, 64),
    nn.GELU(),
    nn.Linear(64, 32),
    nn.GELU(),
    nn.Linear(32, 5),
)

Straturi ascunse într-un transformator: începeți prin a lua în considerare GELU. Straturi ascunse într-un CNN: ReLU. Pentru clasificare cu clase reciproc exclusive, softmax transformă logiturile în probabilități la inferență. Pentru regresie fără constrângeri, folosiți ieșire liniară. Pentru clasificare binară sau multietichetă, sigmoida transformă logiturile în probabilități. Acestea sunt valori implicite rezonabile, pe care trebuie să le schimbați când aveți dovezi experimentale sau cerințe arhitecturale.

Notă tehnică a traducerii: Modelul afișat se termină cu nn.Linear, deci produce logits. În antrenare, CrossEntropyLoss primește logits multiclasă și aplică intern LogSoftmax, iar BCEWithLogitsLoss combină stabil sigmoida cu pierderea binară; nu aplicați în prealabil softmax, respectiv sigmoidă, înaintea acestor pierderi fusionate.

RNN-urile și LSTM-urile clasice folosesc tanh pentru starea ascunsă și sigmoidă pentru porți. Dacă neuronii devin inactivi într-o rețea ReLU, puteți încerca GELU, Leaky ReLU, o inițializare mai potrivită sau o rată de învățare diferită și apoi compara rezultatele.

Notă tehnică a traducerii: Recomandarea originală de a înlocui automat ReLU cu GELU și de a evita Leaky ReLU nu este universală. Alegerea activării este o decizie empirică legată de arhitectură, costul calculului, inițializare și date; GELU nu garantează un flux al gradientului superior în orice model.

Livrați

Această lecție produce:

  • outputs/prompt-activation-selector.md — un prompt reutilizabil care vă ajută să alegeți funcția de activare potrivită oricărei arhitecturi.

Exerciții

  1. Implementați ReLU parametrică — PReLU —, în care panta negativă alpha este un parametru antrenabil. Antrenați-o pe setul de date circular și comparați-o cu Leaky ReLU având pantă fixă.

  2. Rulați experimentul dispariției semnalului cu 50 de straturi în loc de 10. Reprezentați amplitudinea activării la fiecare strat pentru sigmoidă, tanh, ReLU și GELU. La ce strat ajunge efectiv la zero semnalul fiecărei activări? Pentru a studia gradientul propriu-zis, extindeți experimentul cu o pierdere și o propagare înapoi.

  3. Implementați ELU — unitatea liniară exponențială: elu(x) = x dacă x > 0, respectiv alpha * (e^x - 1) dacă x <= 0. Comparați rata neuronilor inactivi cu cea a ReLU în aceeași rețea.

  4. Construiți un „monitor al sănătății gradienților” care rulează în timpul antrenării: la fiecare epocă, calculați amplitudinea medie a gradientului din fiecare strat. Afișați un avertisment când gradientul oricărui strat scade sub 0,001 sau depășește 100.

  5. Modificați comparația antrenării pentru a folosi setul de date XOR din lecția 01 în locul cercurilor. Care activare converge cel mai repede pe XOR? De ce diferă rezultatul de cel obținut pentru cercuri?

Termeni-cheie

Termen Ce spun oamenii Ce înseamnă de fapt
Funcție de activare „Partea neliniară” Funcție aplicată ieșirii fiecărui neuron, care rupe structura afină și permite rețelei să învețe mapări neliniare
Gradient care dispare „Gradienții dispar în rețelele profunde” Gradienții se pot micșora exponențial prin produse repetate de Jacobieni, făcând straturile timpurii greu de antrenat
Gradient care explodează „Gradienții scapă de sub control” Gradienții pot crește exponențial atunci când multiplicatorii efectivi ai Jacobienelor depășesc repetat 1, ceea ce destabilizează antrenarea
Neuron mort „Un neuron care a încetat să învețe” Neuron ReLU a cărui preactivare rămâne negativă pentru datele relevante, producând ieșire și gradient local zero
Sigmoidă „Comprimă valorile între 0 și 1” Funcția logistică 1/(1+e^-x), importantă istoric, dar predispusă la gradienți care dispar în rețele profunde
ReLU „Taie valorile negative la zero” max(0, x) — activare care are derivata 1 pe ramura pozitivă și a facilitat antrenarea multor rețele profunde
GELU „Activarea transformatoarelor” Unitatea liniară cu eroare gaussiană, o activare netedă care ponderază intrările prin funcția de distribuție cumulativă normală
Swish/SiLU „ReLU cu poartă proprie” x * sigmoid(x), funcție regăsită prin căutare automată sub numele Swish și folosită în EfficientNet
Softmax „Transformă scorurile în probabilități” Normalizează un vector de logiți într-o distribuție de probabilitate ale cărei valori însumează 1
Leaky ReLU „ReLU care nu moare” max(alpha*x, x), unde alpha este mic, de exemplu 0,01; permite gradienți mici pe ramura negativă
Saturare „Partea plată a sigmoidei” Regiuni în care derivata unei activări tinde spre zero și atenuează fluxul gradientului
Logit „Scorul brut dinaintea softmax” Ieșirea nenormalizată a stratului final, înaintea aplicării softmax sau a sigmoidei

Lecturi suplimentare

  • Nair și Hinton, „Rectified Linear Units Improve Restricted Boltzmann Machines” (2010) — lucrarea care a popularizat unitățile rectificate în învățarea profundă.
  • Hendrycks și Gimpel, „Gaussian Error Linear Units (GELUs)” (2016) — a introdus funcția de activare devenită frecventă în transformatoare.
  • Ramachandran și colaboratorii, „Searching for Activation Functions” (2017) — a folosit căutarea automată pentru a identifica și denumi Swish, arătând că proiectarea activărilor poate fi automatizată.
  • Glorot și Bengio, „Understanding the difficulty of training deep feedforward neural networks” (2010) — lucrarea care a analizat dispariția și explozia gradienților și a propus inițializarea Xavier.
  • Goodfellow, Bengio și Courville, „Deep Learning”, capitolul 6.3 — tratare riguroasă a unităților ascunse și a funcțiilor de activare.

Sursă: Originalul în limba engleză

Navigare: ← Lecția 03.03 — Retropropagarea de la zero · Faza 3 — Fundamentele învățării profunde · Catalog complet · Lecția 03.05 — Funcții de pierdere →