Ga naar hoofdinhoud

Forward propagation in neurale netwerken: een complete gids

Leer hoe forward propagation werkt in neurale netwerken, van wiskundige basis tot praktische implementatie in Python. Beheers dit essentiële deep learning-concept met codevoorbeelden en visualisaties.
· 12 min lezen

Verken met AI

Neurale netwerken hebben kunstmatige intelligentie getransformeerd en voeden alles van beeldherkenning tot natural language processing. In het hart van deze krachtige modellen ligt een fundamenteel proces: forward propagation. Deze gids verkent dit kernconcept, van basisprincipes tot praktische implementatie. 

Zoek je een hands-on gids bij veel van de concepten die we hier behandelen? Bekijk dan zeker onze Deep Learning in Python skill track.

Wat is forward propagation?

Forward propagation is het proces waarbij een neuraal netwerk inputdata omzet in voorspellingen of outputs. Zie het als de “denk”-fase van een neuraal netwerk — bij een input (zoals een afbeelding of tekst) verwerkt het netwerk die informatie laag voor laag om een resultaat te produceren.

Technisch gezien is het de sequentiële berekening die data van de inputlaag via verborgen lagen naar de outputlaag brengt. Onderweg wordt de data getransformeerd door gewogen verbindingen en activatiefuncties, waardoor het netwerk complexe patronen kan vastleggen.

Waarom inzicht in forward propagation belangrijk is

Begrijpen van forward propagation is cruciaal om meerdere redenen:

  1. Basis voor leren: Je kunt niet begrijpen hoe neurale netwerken leren zonder eerst te begrijpen hoe ze voorspellingen maken. Forward propagation is de voorwaarde om backpropagation te doorgronden, het algoritme dat leren mogelijk maakt.
  2. Debuggen en optimaliseren: Als neurale netwerken slecht presteren, helpt inzicht in de datastroom door het netwerk je problemen te vinden en te verhelpen.
  3. Modelontwerp: Effectief architectuurontwerp vereist begrip van hoe informatie zich verspreidt door verschillende laagconfiguraties.
  4. Efficiënt uitrollen: Het optimaliseren van forward propagation is essentieel voor deployment in omgevingen met beperkte resources.

Wat je in deze gids leert

Aan het einde van deze uitgebreide gids kun je:

  • De wiskunde achter berekeningen in neurale netwerken begrijpen
  • Forward propagation from scratch implementeren met NumPy
  • Moderne frameworks zoals TensorFlow en PyTorch gebruiken voor efficiënte forward propagation
  • Visualiseren hoe informatie transformeert terwijl het door een netwerk beweegt
  • Forward propagation verbinden met het bredere leerproces in neurale netwerken
  • Een volledig werkend voorbeeld op echte data implementeren

Vereisten

Om het meeste uit deze gids te halen, heb je idealiter:

  • Basiskennis van programmeren in Python
  • Bekendheid met fundamentele wiskundige concepten (vectoren, matrices en basisanalyse)
  • Enige ervaring met machine learning-concepten

Moet je je basis versterken? Overweeg dan deze resources:

Zelfs zonder uitgebreide voorkennis bouwt deze gids de concepten stap voor stap op, zodat neurale netwerken toegankelijk zijn voor gemotiveerde leerders. Laten we erin duiken!

Fundamenten van forward propagation

Om forward propagation te begrijpen, beginnen we bij de fundamentele bouwstenen. We starten met de kleinste rekeneenheid in neurale netwerken en bouwen geleidelijk op naar complexere structuren.

De enkele neuron: waar het allemaal begint

De reis van het neurale netwerk begint met een fascinerende parallel met de biologie. Net zoals de menselijke hersenen uit miljarden onderling verbonden neuronen bestaan, zijn kunstmatige neurale netwerken opgebouwd uit wiskundige modellen geïnspireerd op deze biologische cellen.

Een schets van een vereenvoudigde neuron met enkele hoofdstructuren aangeduid.

Bron: Deep Learning - Visual Approach

Een biologisch neuron ontvangt signalen van andere neuronen via dendrieten, verwerkt deze signalen in het cellichaam en verstuurt vervolgens het resultaat via het axon naar andere neuronen. In ons computationele model spiegelen we dit proces met:

  • Inputs: De signalen die de neuron binnenkomen (zoals dendrieten signalen ontvangen).
  • Gewichten: Het belang dat aan elke input wordt toegekend (analoog aan de sterkte van synaptische verbindingen).
  • Bias: Een extra parameter die helpt bepalen of de neuron activeert of inactief blijft, los van de inputs.
  • Activatiefunctie: Bepaalt of en hoe sterk de neuron “vuurt” op basis van zijn inputs (vergelijkbaar met het cellichaam dat beslist of er een actiepotentiaal wordt gegenereerd).
  • Output: Het signaal dat naar de volgende laag wordt gestuurd (zoals het axon dat naar andere neuronen uitzendt).

Laten we een enkele neuron visualiseren om dit concreet te maken:

Een enkele neuron in een neuraal netwerk met inputs, gewichten, bias, activatiefunctie en somoperatie gevisualiseerd.

Bron: Deep Learning - Visual Approach

Deze eenvoudige rekeneenheid vormt de basis van zelfs de meest complexe neurale netwerken. Maar hoe zet een neuron zijn inputs precies om in een output? Daar komt de wiskunde bij kijken.

Wiskundige basis van een enkele neuron

De werking van een neuron is te beschrijven met een eenvoudige vergelijking:

Een vergelijking die het forward propagation-proces op hoog niveau weergeeft

Uiteengezet:

  • x is onze inputvector [x_1, x_2, x_3, …]
  • w is onze gewichtenvector [w_1, w_2, …, w_n]
  • b is de bias (een enkele scalaire waarde)
  • z is de gewogen som plus bias (vaak “pre-activatie” of “logit” genoemd)
  • f is de activatiefunctie
  • a is de uiteindelijke output van de neuron

Laten we een concreet voorbeeld met echte getallen doorlopen. Stel, we hebben een neuron met drie inputs:

Inputs: x = [2, 5, -1]
Weights: w = [0.5, -1, 2]
Bias: b = 0.5

Eerst berekenen we de gewogen som plus bias:

Een voorbeeldberekening volgens de formule van forward propagation

Vervolgens passen we een activatiefunctie toe. Laten we de populaire ReLU (Rectified Linear Unit)-functie gebruiken, gedefinieerd als:

De formule van de ReLU-activatiefunctie toegepast op een voorbeeldwaarde

Met een negatieve pre-activatiewaarde geeft onze ReLU-neuron 0 terug, wat betekent dat hij voor deze specifieke input niet “vuurt”.

De activatiefunctie is cruciaal omdat die niet-lineariteit introduceert in het netwerk. Zonder activatiefuncties zouden neurale netwerken, ongeacht het aantal lagen, alleen lineaire relaties kunnen leren. Veelgebruikte activatiefuncties zijn onder meer:

  • Sigmoid: De formule voor de sigmoid-activatiefunctie— Geeft waarden tussen 0 en 1, handig voor binaire classificatie
  • ReLU: Formule van de ReLU-activatiefunctie— Eenvoudig, efficiënt en helpt tegen het verdwijnende-gradiëntprobleem
  • Tanh: Formule van de tanh-activatiefunctie — Geeft waarden tussen -1 en 1, vaak gebruikt in verborgen lagen

Elke activatiefunctie heeft zijn sterke punten en toepassingen; daar komen we later op terug bij de implementatie van ons neurale netwerk.

Van neuronen naar lagen

Individuele neuronen zijn krachtig, maar de echte kracht van neurale netwerken komt naar voren wanneer neuronen in lagen worden georganiseerd. Een laag is simpelweg een verzameling neuronen die inputs parallel verwerken. Meestal zijn er drie type lagen in een neuraal netwerk:

  1. Inputlaag: ontvangt de ruwe data en geeft die door aan de volgende laag
  2. Verborgen lagen: verwerken informatie uit voorgaande lagen
  3. Outputlaag: produceert de uiteindelijke voorspelling of classificatie

Met meerdere neuronen in een laag, die elk dezelfde inputs ontvangen maar met verschillende gewichten en biases, kunnen we dit efficiënt representeren met matrixbewerkingen. Zo werkt dat.

Stel, we hebben een laag met 3 inputwaarden en 4 neuronen. Elke neuron heeft zijn eigen set gewichten en een bias:

Inputs: x = [x₁, x₂, x₃]
Weights for neuron 1: w₁ = [w₁₁, w₁₂, w₁₃]
Weights for neuron 2: w₂ = [w₂₁, w₂₂, w₂₃]
Weights for neuron 3: w₃ = [w₃₁, w₃₂, w₃₃]
Weights for neuron 4: w₄ = [w₄₁, w₄₂, w₄₃]
Biases: b = [b₁, b₂, b₃, b₄]

We kunnen deze gewichten in een matrix W organiseren:

Gewichtsmatrix W als onderdeel van het forward propagation-proces

Nu kunnen we alle pre-activaties van de neuronen in één keer berekenen met één matrixvermenigvuldiging:

Pre-activaties van neuronen berekenen met matrixvermenigvuldiging

Waarbij:

  • X onze inputvector is [x₁, x₂, x₃]
  • W^T de getransponeerde is van onze gewichtsmatrix
  • B onze biasvector is [b₁, b₂, b₃, b₄]
  • Z de resulterende vector van pre-activaties is [z₁, z₂, z₃, z₄]

Daarna passen we de activatiefunctie elementgewijs toe om onze uiteindelijke outputs te krijgen:

Activatiefunctie toepassen op de vector van pre-activaties

Deze matrixrepresentatie is niet alleen wiskundig elegant; het is ook computationeel efficiënt. Moderne hardware (vooral GPU’s) is geoptimaliseerd voor matrixoperaties, waardoor deze aanpak veel sneller is dan de output van elke neuron afzonderlijk berekenen.

Het vermogen om deze lagen te stapelen — waarbij de output van de ene laag de input van de volgende wordt — geeft neurale netwerken hun opmerkelijke capaciteit om complexe patronen te leren. Met deze bouwstenen verbonden, zijn we klaar om te bekijken hoe forward propagation werkt in een volledig neuraal netwerk.

Forward propagation door een compleet netwerk

Nu we individuele neuronen en lagen begrijpen, kijken we hoe forward propagation werkt door een volledig neuraal netwerk. Hier komt de echte kracht van deep learning naar voren.

Meerdere lagen: het complete plaatje

Een volledig neuraal netwerk bestaat uit een inputlaag, één of meer verborgen lagen en een outputlaag. De term “deep” in deep learning verwijst naar netwerken met meerdere verborgen lagen. Elke laag transformeert de data op steeds abstracter niveau, zodat het netwerk complexe representaties kan leren.

Neem een eenvoudig neuraal netwerk met:

  • Een inputlaag met 4 features
  • Twee verborgen lagen met respectievelijk 3 en 2 neuronen
  • Een outputlaag met 3 neuronen (bijvoorbeeld voor multi-class-classificatie)

Visueel ziet dit netwerk er zo uit:

Een eenvoudige neurale netwerkarchitectuur

Terwijl data door dit netwerk stroomt, doen we bij elke laag een reeks berekeningen. Als we noteren:

  • Inputvector als X = [x_1, x_2, x_3, x_4]
  • Gewichtsmatrices als (voor de verborgen laag) en (voor de outputlaag)
  • Biasvectoren als en
  • Activatiefuncties als f1 (voor de verborgen laag) en f2 (voor de outputlaag)

Dan kunnen we de forward propagation door het hele netwerk uitdrukken als:

1. Pre-activaties van de verborgen laag berekenen: 

Pre-activaties van de verborgen laag berekenen

2. Activatiefunctie toepassen om outputs van de verborgen laag te krijgen: 

Activatiefunctie toepassen om outputs van de verborgen laag te verkrijgen

3. Pre-activaties van de outputlaag berekenen: 

Pre-activaties van de outputlaag berekenen

4. Activatiefunctie toepassen om de uiteindelijke outputs te krijgen:

Activatiefunctie toepassen om de uiteindelijke outputs te krijgen

De uiteindelijke output A^[2] vertegenwoordigt de voorspelling van het netwerk. Bij classificatieproblemen zijn dit waarschijnlijkheden per klasse; bij regressie de voorspelde waarden.

Verschillende lagen gebruiken vaak verschillende activatiefuncties. Verborgen lagen gebruiken bijvoorbeeld vaak ReLU, terwijl outputlagen kunnen gebruiken:

  • Sigmoid voor binaire classificatie
  • Softmax voor multi-class-classificatie
  • Lineair (geen activatie) voor regressie

Het mooie van deze meerlagige structuur is dat elke laag andere aspecten van de data kan leren representeren. Vroege lagen detecteren doorgaans eenvoudige features, terwijl diepere lagen deze combineren tot complexere patronen. Deze hiërarchische manier van leren maakt neurale netwerken zo krachtig voor complexe taken zoals beeld- en spraakherkenning.

Forward propagation-algoritme

Laten we het forward propagation-proces formaliseren tot een algoritme. Voor een neuraal netwerk met L lagen volgt forward propagation deze stappen:

# Pseudocode for forward propagation
def forward_propagation(X, parameters):
   """
   X: Input data (batch_size, n_features)
   parameters: Dictionary containing weights and biases for each layer
   Returns: The final output and all intermediate activations
   """
   # Store all activations for later use (e.g., in backpropagation)
   activations = {'A0': X}  # A0 is the input
  
   # Loop through L-1 layers (excluding the output layer)
   for l in range(1, L):
       # Get previous activation
       A_prev = activations[f'A{l-1}']
      
       # Get weights and biases for current layer
       W = parameters[f'W{l}']
       b = parameters[f'b{l}']
      
       # Compute pre-activation
       Z = np.dot(A_prev, W.T) + b
      
       # Apply activation function (e.g., ReLU for hidden layers)
       A = relu(Z)
      
       # Store values for later use
       activations[f'Z{l}'] = Z
       activations[f'A{l}'] = A
  
   # Compute output layer (layer L)
   A_prev = activations[f'A{L-1}']
   W = parameters[f'W{L}']
   b = parameters[f'b{L}']
  
   # Compute pre-activation for output layer
   Z = np.dot(A_prev, W.T) + b
  
   # Apply output activation function (depends on the task)
   if task == 'binary_classification':
       A = sigmoid(Z)
   elif task == 'multiclass_classification':
       A = softmax(Z)
   elif task == 'regression':
       A = Z  # Linear activation
  
   # Store output layer values
   activations[f'Z{L}'] = Z
   activations[f'A{L}'] = A
  
   return A, activations

Dit algoritme benadrukt enkele belangrijke aspecten van forward propagation:

  1. Sequentiële verwerking: Informatie stroomt strikt voorwaarts, laag voor laag.
  2. Opslaan van activaties: We slaan niet alleen de uiteindelijke output op, maar ook alle tussentijdse activaties, die cruciaal zijn voor het trainen van het netwerk met backpropagation.
  3. Taakafhankelijke output: De activatiefunctie voor de outputlaag wordt gekozen op basis van het specifieke probleem (classificatie versus regressie).

Het forward propagation-algoritme is opmerkelijk eenvoudig, maar stelt neurale netwerken in staat om zeer complexe functies te benaderen. Gecombineerd met correct trainen via backpropagation, leert het netwerk van data en maakt het steeds nauwkeurigere voorspellingen.

Laten we een specifiek voorbeeld doorlopen om dit verder te verankeren. Neem input X = [0.5, -0.2, 0.1] door ons voorbeeldnetwerk met:

  • Gewichten W^[1] (4×3-matrix) en biases b^[1] (vector van 4 waarden) voor de verborgen laag
  • Gewichten W^[2] (2×4-matrix) en biases b^[2] (vector van 2 waarden) voor de outputlaag

Voor de eenvoud nemen we alle gewichten 0,1 en alle biases 0:

X = [0.5, -0.2, 0.1]
W[1] = [[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]
b[1] = [0, 0, 0, 0]
W[2] = [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]
b[2] = [0, 0]

Volgens ons algoritme:

  • Eerste stap:

Pre-activaties van de verborgen laag berekenen

  • Tweede stap met ReLU: 

Activatiefunctie toepassen om outputs van de verborgen laag te verkrijgen

  • Derde stap:

Pre-activaties van de verborgen laag berekenen

  • Vierde stap met sigmoid: 

Pre-activaties van de outputlaag berekenen

Dit levert onze uiteindelijke voorspelling op. In een binaire classificatiecontext duiden waarden rond 0,5 op onzekerheid tussen de twee klassen.

In de volgende sectie implementeren we forward propagation in Python om deze berekeningen in actie te zien.

Forward propagation implementeren in Python

Nu we de theorie achter forward propagation begrijpen, zetten we dit om in de praktijk met een implementatie in Python. We beginnen met een “from scratch”-implementatie met alleen NumPy en bekijken daarna hoe moderne deep learning-frameworks dit proces vereenvoudigen.

Vanuit de basis met NumPy

NumPy biedt efficiënte array-bewerkingen waarmee we de matrixberekeningen kunnen implementeren die we hebben besproken. Laten we een eenvoudige neural-network-klasse bouwen die forward propagation door meerdere lagen uitvoert.

Eerst importeren we de benodigde libraries:

import numpy as np
import matplotlib.pyplot as plt

# For reproducibility
np.random.seed(42)

Nu definiëren we activatiefuncties die we in ons netwerk gebruiken:

def relu(Z):
   """ReLU activation function: max(0, Z)"""
   return np.maximum(0, Z)

def sigmoid(Z):
   """Sigmoid activation function: 1/(1 + e^(-Z))"""
   return 1 / (1 + np.exp(-Z))

def softmax(Z):
   """Softmax activation function for multi-class classification"""
   # Subtract max for numerical stability (prevents overflow)
   exp_Z = np.exp(Z - np.max(Z, axis=1, keepdims=True))
   return exp_Z / np.sum(exp_Z, axis=1, keepdims=True)

Vervolgens maken we een klasse voor ons neurale netwerk:

class NeuralNetwork:
   def __init__(self, layer_dims, activations):
       """
       Initialize a neural network with specified layer dimensions and activations
      
       Parameters:
       - layer_dims: List of integers representing the number of neurons in each layer
                    (including input and output layers)
       - activations: List of activation functions for each layer (excluding input layer)
       """
       self.L = len(layer_dims) - 1  # Number of layers (excluding input layer)
       self.layer_dims = layer_dims
       self.activations = activations
       self.parameters = {}
      
       # Initialize parameters (weights and biases)
       self.initialize_parameters()
  
   def initialize_parameters(self):
       """Initialize weights and biases with small random values"""
       for l in range(1, self.L + 1):
           # He initialization for weights - helps with training deep networks
           self.parameters[f'W{l}'] = np.random.randn(self.layer_dims[l], self.layer_dims[l-1]) * np.sqrt(2 / self.layer_dims[l-1])
           self.parameters[f'b{l}'] = np.zeros((self.layer_dims[l], 1))
  
   def forward_propagation(self, X):
       """
       Perform forward propagation through the network
      
       Parameters:
       - X: Input data (n_features, batch_size)
      
       Returns:
       - AL: Output of the network
       - caches: Dictionary containing all activations and pre-activations
       """
       caches = {}
       A = X  # Input layer activation
       caches['A0'] = X
      
       # Process through L-1 layers (excluding output layer)
       for l in range(1, self.L):
           A_prev = A
          
           # Get weights and biases for current layer
           W = self.parameters[f'W{l}']
           b = self.parameters[f'b{l}']
          
           # Forward propagation for current layer
           Z = np.dot(W, A_prev) + b
          
           # Apply activation function
           activation_function = self.activations[l-1]
           if activation_function == "relu":
               A = relu(Z)
           elif activation_function == "sigmoid":
               A = sigmoid(Z)
          
           # Store values for backpropagation
           caches[f'Z{l}'] = Z
           caches[f'A{l}'] = A
      
       # Output layer
       W = self.parameters[f'W{self.L}']
       b = self.parameters[f'b{self.L}']
       Z = np.dot(W, A) + b
      
       # Apply output activation function
       activation_function = self.activations[self.L-1]
       if activation_function == "sigmoid":
           AL = sigmoid(Z)
       elif activation_function == "softmax":
           AL = softmax(Z)
       elif activation_function == "linear":
           AL = Z  # No activation for regression
      
       # Store output layer values
       caches[f'Z{self.L}'] = Z
       caches[f'A{self.L}'] = AL
      
       return AL, caches

De klasse NeuralNetwork die we hierboven hebben geïmplementeerd, biedt een compleet raamwerk om een neuraal netwerk met aanpasbare architectuur te maken en te gebruiken. De belangrijkste onderdelen:

  1. Initialisatie: Het netwerk wordt geconfigureerd met gespecificeerde laagaantallen en activatiefuncties. Het initialiseert automatisch gewichten en biases met de He-initialisatie voor betere trainingsprestaties.
  2. Forward propagation: De methode forward_propagation die we zojuist hebben geïmplementeerd, is het hart van de voorspellingscapaciteit van het netwerk. Ze:
  • Neemt inputdata en stuurt die door elke laag
  • Past bij elke laag de juiste activatiefunctie toe
  • Slaat tussentijdse waarden op in caches voor later gebruik bij backpropagation
  • Geeft zowel de uiteindelijke output als alle gecachete waarden terug

3. Activatiefuncties: Het netwerk ondersteunt meerdere activatiefuncties, waaronder ReLU, sigmoid en softmax, zodat het verschillende probleemtypen (regressie of classificatie) aankan.

4. Flexibele architectuur: De implementatie laat netwerken van willekeurige diepte en breedte toe, geschikt voor een breed scala aan machine learning-taken.

Deze implementatie volgt het standaardpatroon waarbij data voorwaarts door het netwerk stroomt, en elke laag de data transformeert voordat die wordt doorgegeven aan de volgende laag.

Laten we onze implementatie testen met een klein voorbeeldnetwerk:

# Create a sample network
# Input layer: 3 features
# Hidden layer 1: 4 neurons with ReLU activation
# Output layer: 2 neurons with sigmoid activation (binary classification)
layer_dims = [3, 4, 2]
activations = ["relu", "sigmoid"]
nn = NeuralNetwork(layer_dims, activations)

# Create sample input data - 3 features for 5 examples
X = np.random.randn(3, 5)

# Perform forward propagation
output, caches = nn.forward_propagation(X)

print(f"Input shape: {X.shape}")
print(f"Output shape: {output.shape}")
print(f"Output values:\n{output}")

Output:

Input shape: (3, 5)
Output shape: (2, 5)
Output values:
[[0.00386784 0.54343014 0.39661893 0.5        0.51056934]
[0.11877049 0.32541093 0.44840699 0.5        0.45586633]]

Laten we ook visualiseren hoe de data transformeert terwijl die door het netwerk stroomt:

def visualize_activations(caches, example_idx=0):
   """Visualize the activations for a single example through the network"""
   plt.figure(figsize=(15, 6))
  
   # Plot input
   plt.subplot(1, 3, 1)
   plt.bar(range(caches['A0'].shape[0]), caches['A0'][:, example_idx])
   plt.title('Input Layer')
   plt.xlabel('Feature')
   plt.ylabel('Value')
  
   # Plot hidden layer activation
   plt.subplot(1, 3, 2)
   plt.bar(range(caches['A1'].shape[0]), caches['A1'][:, example_idx])
   plt.title('Hidden Layer (ReLU Activation)')
   plt.xlabel('Neuron')
   plt.ylabel('Activation')
  
   # Plot output layer
   plt.subplot(1, 3, 3)
   plt.bar(range(caches['A2'].shape[0]), caches['A2'][:, example_idx])
   plt.title('Output Layer (Sigmoid Activation)')
   plt.xlabel('Neuron')
   plt.ylabel('Activation')
  
   plt.tight_layout()
   plt.show()

# Visualize the first example
visualize_activations(caches)

Deze visualisatie helpt ons begrijpen hoe de inputdata transformeert terwijl die door een neuraal netwerk gaat.

Deze visualisatie helpt ons begrijpen hoe de inputdata transformeert terwijl die door het netwerk gaat. We zien dat:

  1. De inputfeatures positief of negatief kunnen zijn
  2. Na ReLU in de verborgen laag alle activaties niet-negatief zijn
  3. De sigmoid in de outputlaag waarden tussen 0 en 1 begrenst

Onze “from scratch”-implementatie laat de kernprincipes van forward propagation zien, maar moderne deep learning-frameworks bieden efficiëntere en flexibelere tools om neurale netwerken te bouwen.

Gebruik van deep learning-frameworks

Nu implementeren we hetzelfde neurale netwerk met populaire deep learning-frameworks: TensorFlow en PyTorch. Deze frameworks optimaliseren prestaties en bieden hogere abstractielagen, waardoor het bouwen van complexe modellen eenvoudiger wordt.

Eerst de TensorFlow/Keras-implementatie:

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input

# For reproducibility
tf.random.set_seed(42)

# Create the same network architecture
tf_model = Sequential([
   Input(shape=(3,)),  # Input layer with 3 features
   Dense(4, activation='relu'),  # Hidden layer with 4 neurons
   Dense(2, activation='sigmoid')  # Output layer with 2 neurons
])

tf_model.summary()

Een voorbeeld van een TensorFlow-modeloverzicht

# Sample input data with the shape expected by Keras
X_tf = np.random.randn(5, 3)  # 5 examples, 3 features

# Forward propagation in TensorFlow
tf_output = tf_model.predict(X_tf)

print(f"TensorFlow output shape: {tf_output.shape}")
print(f"TensorFlow output values:\n{tf_output}")
TensorFlow output shape: (5, 2)
TensorFlow output values:
[[0.6855308  0.7139542 ]
[0.4952355  0.5231934 ]
[0.50174904 0.5198488 ]
[0.44331825 0.6860926 ]
[0.6624589  0.5385444 ]]

We hebben ons neurale netwerk met TensorFlow/Keras geïmplementeerd en succesvol forward propagation uitgevoerd op voorbeelddata. De modelsamenvatting toont onze architectuur met een inputlaag (3 features), een verborgen laag met 4 neuronen en ReLU, en een outputlaag met 2 neuronen en sigmoid. In totaal heeft het model 26 trainable parameters.

De resultaten tonen de outputvorm (5, 2) die overeenkomt met onze 5 inputvoorbeelden, elk met 2 outputwaarden. Door de sigmoidactivatie in de outputlaag liggen deze outputs tussen 0 en 1.

Vervolgens implementeren we dezelfde architectuur met PyTorch, een ander populair framework, om de aanpakken te vergelijken.

import torch
import torch.nn as nn

# For reproducibility
torch.manual_seed(42)

# Create a network in PyTorch
class PyTorchNN(nn.Module):
   def __init__(self):
       super(PyTorchNN, self).__init__()
       self.hidden = nn.Linear(3, 4)  # 3 inputs, 4 hidden neurons
       self.relu = nn.ReLU()
       self.output = nn.Linear(4, 2)  # 4 inputs from hidden, 2 outputs
       self.sigmoid = nn.Sigmoid()
  
   def forward(self, x):
       x = self.relu(self.hidden(x))
       x = self.sigmoid(self.output(x))
       return x

# Instantiate the model
torch_model = PyTorchNN()

# Print model structure
print(torch_model)

Output:

PyTorchNN(
 (hidden): Linear(in_features=3, out_features=4, bias=True)
 (relu): ReLU()
 (output): Linear(in_features=4, out_features=2, bias=True)
 (sigmoid): Sigmoid()
)

```python
# Sample input data
X_torch = torch.randn(5, 3)  # 5 examples, 3 features

# Forward propagation in PyTorch
torch_output = torch_model(X_torch)

print(f"PyTorch output shape: {torch_output.shape}")
print(f"PyTorch output values:\n{torch_output}")

Output:

PyTorch output shape: torch.Size([5, 2])
PyTorch output values:
tensor([[0.4516, 0.4116],
       [0.4289, 0.4267],
       [0.4278, 0.4172],
       [0.3771, 0.4321],
       [0.5769, 0.3328]], grad_fn=<SigmoidBackward0>)

Laten we onze implementaties vergelijken:

# Compare dimensions and structure of outputs
print("\nComparison of implementations:")
print(f"NumPy output shape: {output.shape}")
print(f"TensorFlow output shape: {tf_output.shape}")
print(f"PyTorch output shape: {torch_output.shape}")

Output:

Comparison of implementations:
NumPy output shape: (2, 5)
TensorFlow output shape: (5, 2)
PyTorch output shape: torch.Size([5, 2])

De belangrijkste verschillen tussen onze implementaties:

  1. Structuur en leesbaarheid:
  • NumPy: expliciete implementatie van elke stap geeft volledige controle en transparantie
  • TensorFlow/Keras: hoge abstractie verbergt details maar maakt code eenvoudiger
  • PyTorch: objectgeoriënteerde aanpak met expliciete forward-methode
  1. Prestaties:
  • TensorFlow en PyTorch optimaliseren operaties voor GPU-versnelling
  • Beide frameworks implementeren efficiënte gradiëntberekening voor training
  • Frameworks optimaliseren geheugengebruik voor grote netwerken
  1. Uitbreidbaarheid:
  • Frameworks bieden kant-en-klare componenten (lagen, optimizers, enz.)
  • Ingebouwde tools voor opslaan/laden van modellen, visualisatie en deployment
  • Uitgebreide community-ondersteuning en documentatie

Voor eenvoudige netwerken volstaat onze NumPy-implementatie, maar naarmate de complexiteit toeneemt, worden frameworks essentieel. Ze verzorgen low-level optimalisaties en bieden tools voor de volledige ML-levenscyclus, van ontwikkeling tot deployment. Wil je meer leren over deze frameworks? Bekijk dan onze aparte gidsen:

In de volgende sectie bouwen we een volledig werkend voorbeeld dat forward propagation toepast op een realistisch probleem.

Voorbeeld van forward propagation

Nu we forward propagation zowel from scratch als met populaire frameworks hebben geïmplementeerd, bekijken we het in actie op een realistisch probleem. We bouwen een volledig voorbeeld en verkennen hoe je het proces optimaliseert voor betere prestaties.

Volledig werkend voorbeeld

Laten we onze kennis toepassen op een klassieke ML-taak: handgeschreven cijferherkenning met de MNIST-dataset. Deze dataset bevat 70.000 afbeeldingen van handgeschreven cijfers (0–9), elk 28x28 pixels.

Eerst laden en prepareren we de data:

from tensorflow.keras.datasets import mnist
import numpy as np
import matplotlib.pyplot as plt

# Load MNIST dataset
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# Normalize pixel values to range [0, 1]
X_train = X_train / 255.0
X_test = X_test / 255.0

# Reshape images to vectors (flatten 28x28 to 784)
X_train_flat = X_train.reshape(X_train.shape[0], -1).T  # Shape: (784, 60000)
X_test_flat = X_test.reshape(X_test.shape[0], -1).T     # Shape: (784, 10000)

# Convert labels to one-hot encoding
def one_hot_encode(y, num_classes=10):
   one_hot = np.zeros((num_classes, y.size))
   one_hot[y, np.arange(y.size)] = 1
   return one_hot

y_train_one_hot = one_hot_encode(y_train)  # Shape: (10, 60000)
y_test_one_hot = one_hot_encode(y_test)    # Shape: (10, 10000)

# Display sample images
plt.figure(figsize=(10, 5))
for i in range(5):
   plt.subplot(1, 5, i+1)
   plt.imshow(X_train[i], cmap='gray')
   plt.title(f"Label: {y_train[i]}")
   plt.axis('off')
plt.tight_layout()
plt.show()

Output van een neuraal netwerk voor cijferclassificatie

Nu bouwen we een neuraal netwerk voor deze taak met onze NumPy-implementatie. We maken een netwerk met:

  • Inputlaag: 784 neuronen (28x28 pixels)
  • Verborgen laag: 128 neuronen met ReLU
  • Outputlaag: 10 neuronen met softmax (voor 10 cijfers)
# Define our network architecture
layer_dims = [784, 128, 10]
activations = ["relu", "softmax"]
nn = NeuralNetwork(layer_dims, activations)

# Take a small batch for demonstration
batch_size = 64
batch_indices = np.random.choice(X_train_flat.shape[1], batch_size, replace=False)
X_batch = X_train_flat[:, batch_indices]
y_batch = y_train_one_hot[:, batch_indices]

# Perform forward propagation
output, caches = nn.forward_propagation(X_batch)

# Compute accuracy
predictions = np.argmax(output, axis=0)
true_labels = np.argmax(y_batch, axis=0)
accuracy = np.mean(predictions == true_labels)

print(f"Batch accuracy: {accuracy:.4f}")

Out: Batch accuracy: 0.0781

We behalen 7% nauwkeurigheid, wat nog slechter is dan willekeurig raden. Dat is verwacht, want we voeren alleen forward propagation uit — er is geen sprake van leren.

Laten we nu visualiseren hoe het netwerk een enkele afbeelding laag voor laag verwerkt:

def visualize_network_processing(nn, image, label, caches):
   """Visualize network processing for a single image"""
   plt.figure(figsize=(15, 10))
  
   # Plot original image
   plt.subplot(2, 3, 1)
   plt.imshow(image.reshape(28, 28), cmap='gray')
   plt.title(f"Input Image (Digit: {label})")
   plt.axis('off')
  
   # Plot flattened input (first 100 values)
   plt.subplot(2, 3, 2)
   plt.bar(range(100), image.flatten()[:100])
   plt.title("Input Layer (first 100 pixels)")
   plt.xlabel("Pixel Index")
   plt.ylabel("Pixel Value")
  
   # Plot hidden layer activations (first 50 neurons)
   plt.subplot(2, 3, 3)
   hidden_activations = caches['A1'][:50, 0]
   plt.bar(range(len(hidden_activations)), hidden_activations)
   plt.title("Hidden Layer Activations (first 50 neurons)")
   plt.xlabel("Neuron Index")
   plt.ylabel("Activation")
  
   # Plot output layer activations
   plt.subplot(2, 3, 4)
   output_activations = caches['A2'][:, 0]
   plt.bar(range(10), output_activations)
   plt.xticks(range(10))
   plt.title("Output Layer Activations (Softmax)")
   plt.xlabel("Digit Class")
   plt.ylabel("Probability")
  
   # Plot prediction vs actual
   plt.subplot(2, 3, 5)
   prediction = np.argmax(output_activations)
   plt.bar(['Actual', 'Predicted'], [label, prediction], color=['blue', 'orange'])
   plt.title(f"Prediction: {prediction}, Actual: {label}")
   plt.ylabel("Digit")
  
   plt.tight_layout()
   plt.show()

# Visualize forward propagation for the first image in our batch
image_idx = 0
image = X_batch[:, image_idx].reshape(784, 1)
label = true_labels[image_idx]

visualize_network_processing(nn, image, label, caches)

Een diagram dat laat zien hoe een afbeelding van het cijfer 4 door een voorbeeldnetwerk stroomt

Deze visualisatie toont hoe informatie door ons netwerk stroomt:

  1. Inputlaag: de platgeslagen beeldpixels (waarden tussen 0 en 1)
  2. Verborgen laag: de door ReLU geactiveerde neuronen vangen verschillende kenmerken van het cijfer
  3. Outputlaag: de softmax-activatie geeft een kansverdeling over de 10 mogelijke cijfers

De meest actieve outputneuron komt overeen met de voorspelling van het netwerk. Zelfs met willekeurige gewichten (omdat we nog niet hebben getraind) zien we hoe forward propagation inputdata omzet in een voorspelling.

Voor een completer voorbeeld zouden we dit netwerk trainen met backpropagation om gewichten en biases aan te passen en zo de voorspellingen te verbeteren. Maar zelfs zonder training laat dit voorbeeld het forward propagation-proces op echte data zien.

Forward propagation optimaliseren

Als neurale netwerken groter worden en datasets omvangrijker, wordt het optimaliseren van forward propagation cruciaal. Enkele strategieën voor meer efficiëntie:

1. Batchverwerking

In plaats van één voorbeeld per keer te verwerken, berekenen we forward propagation op batches tegelijk. Dit benut de parallelle verwerkingsmogelijkheden van moderne hardware.

2. Vectorisatie

Matrixbewerkingen in plaats van lussen versnellen de berekening aanzienlijk. Daarom gebruikten we NumPy’s array-bewerkingen.

3. Geheugenbeheer

Bij grote netwerken is zorgvuldig omgaan met geheugen noodzakelijk. Technieken zijn onder andere:

  • Geheugen hergebruiken voor tussentijdse activaties.
  • Activaties laag voor laag berekenen zonder alle tussenresultaten op te slaan.
  • Lagere precisie gebruiken (bijv. 16-bit floats in plaats van 32-bit).

4. GPU-versnelling

Grafische kaarten blinken uit in matrixoperaties. Moderne frameworks kunnen berekeningen naadloos naar GPU’s verplaatsen voor aanzienlijke versnellingen:

# TensorFlow example of GPU acceleration
import tensorflow as tf

# Check for available GPUs
print("GPUs Available:", tf.config.list_physical_devices('GPU'))

# TensorFlow automatically uses available GPUs
with tf.device('/GPU:0'):  # Explicitly specify GPU if multiple are available
   # Computation runs on GPU if available
   result = tf_model.predict(X_tf)

5. Gespecialiseerde hardware

Naast GPU’s is er hardware zoals Google’s Tensor Processing Units (TPU’s), speciaal ontworpen voor operaties met neurale netwerken.

6. Algoritmische optimalisaties

Bepaalde activatiefuncties en netwerkarchitecturen zijn ontworpen voor rekenefficiëntie:

  • ReLU is sneller te berekenen dan sigmoid of tanh.
  • Depthwise separable convoluties verlagen de rekencapaciteit t.o.v. standaardconvoluties.
  • Netwerkpruning verwijdert overbodige verbindingen en reduceert rekenwerk.

7. Frameworkoptimalisaties

Deep learning-frameworks implementeren tal van low-level optimalisaties:

  • Gefuseerde operaties combineren meerdere stappen in één geoptimaliseerde kernel.
  • Auto-tuning kiest het snelste algoritme voor specifieke hardware.
  • JIT-compilatie (Just-In-Time) genereert geoptimaliseerde code voor specifieke netwerkconfiguraties.

Naarmate netwerken dieper en breder worden, wordt optimalisatie van forward propagation steeds belangrijker. Moderne architecturen zoals ResNets, Transformers en EfficientNets bevatten ontwerpskeuzes die forward propagation efficiënter maken met behoud of verbetering van de nauwkeurigheid.

In de volgende sectie bekijken we hoe forward propagation samenhangt met backward propagation (backpropagation), zodat het leerproces van neurale netwerken compleet wordt.

De relatie met backpropagation

We hebben forward propagation nu grondig verkend, maar dit is slechts de helft van het verhaal. Laten we kort bekijken hoe forward propagation samenwerkt met backpropagation, het algoritme dat neurale netwerken laat leren.

Forward en backward propagation verbinden

Forward propagation en backpropagation werken als complementaire processen in neurale netwerken:

  • Forward propagation neemt inputs en produceert voorspellingen, van links naar rechts door het netwerk.
  • Backpropagation neemt fouten en produceert gewichtsupdates, van rechts naar links door het netwerk.

Deze twee processen zijn onlosmakelijk in het leerproces. Backpropagation kan niet plaatsvinden zonder eerst forward propagation, want het heeft nodig:

  1. De activaties van elke laag (die we in onze caches hebben opgeslagen).
  2. De uiteindelijke voorspelling om te vergelijken met het echte label.
  3. De netwerkarchitectuur en gewichten die tijdens forward propagation zijn gebruikt.

Zie forward propagation als het netwerk dat zijn beste gok doet op basis van de huidige kennis, terwijl backpropagation die kennis verfijnt op basis van fouten.

De leercyclus

Het volledige leerproces volgt een cyclisch patroon:

  1. Forward propagation: het netwerk verwerkt de inputdata en doet voorspellingen.
  2. Loss-berekening: de voorspellingen worden vergeleken met de echte doelen om de fout te berekenen.
  3. Backward propagation: de fout wordt teruggepropageerd door het netwerk om te bepalen hoe elk gewicht bijdroeg aan de fout.
  4. Gewichtsupdate: de gewichten worden aangepast om de fout te verkleinen.
  5. Herhalen: het proces gaat door met nieuwe data tot het netwerk goed presteert.

De kracht hiervan is dat forward propagation de context levert die backpropagation nodig heeft. Tijdens forward propagation maakt het netwerk niet alleen voorspellingen, maar houdt het alle tussentijdse waarden en beslissingen bij. Backpropagation gebruikt deze informatie om gerichte aanpassingen te doen.

Het mooie is dat complex leren ontstaat uit deze twee relatief eenvoudige processen die samenwerken. Forward propagation is rechttoe rechtaan — matrixvermenigvuldigingen en activatiefuncties die sequentieel worden toegepast. Backpropagation is complexer, maar volgt direct uit principes van de calculus.

Samen stellen ze neurale netwerken in staat vrijwel elk patroon te leren, mits voldoende data en rekenkracht. Deze leercapaciteit heeft doorbraken mogelijk gemaakt in computervisie, natural language processing, games en talloze andere domeinen.

Een grondig begrip van forward propagation, zoals we in dit artikel hebben opgebouwd, vormt de basis om het volledige leerproces van neurale netwerken te begrijpen. Wil je dieper duiken in het leeraspect? Onze gids over backpropagation biedt een gedetailleerde verkenning van hoe netwerken van hun fouten leren.

Conclusie en volgende stappen

Forward propagation is het fundamentele proces dat neurale netwerken in staat stelt inputs om te zetten in voorspellingen. Zoals we hebben gezien, omvat het sequentiële matrixvermenigvuldigingen en activatiefuncties die data stap voor stap door de lagen van het netwerk transformeren. Inzicht hierin is cruciaal, of je nu netwerken from scratch implementeert, moderne frameworks gebruikt of modelprestaties troubleshoott. 

Door forward propagation te beheersen, zet je een grote stap richting het bouwen en begrijpen van deep learning-modellen die complexe problemen in diverse domeinen aankunnen.

Ga verder met je deep learning-reis met DataCamp’s uitgebreide resources: 

Voor een gestructureerd leerpad biedt de Deep Learning in Python-track alles wat je nodig hebt om bekwaam te worden in het bouwen en deployen van neurale netwerken voor toepassingen in de echte wereld.

Forward Propagation FAQ's

Wat is het verschil tussen forward propagation en backpropagation?

Forward propagation is het proces waarbij data door een neuraal netwerk van input naar output stroomt om voorspellingen te maken. Backpropagation beweegt in de omgekeerde richting en berekent gradiënten om gewichten bij te werken op basis van voorspellingsfouten. Ze werken samen tijdens training: forward propagation maakt voorspellingen, backpropagation helpt het netwerk te leren van fouten.

Waarom hebben we activatiefuncties nodig bij forward propagation?

Activatiefuncties brengen niet-lineariteit in neurale netwerken, waardoor ze complexe patronen kunnen leren die verder gaan dan eenvoudige lineaire relaties. Zonder activatiefuncties zou een neuraal netwerk, ongeacht het aantal lagen, zich gedragen als één lineaire laag en daardoor sterk beperkt zijn in wat het uit data kan leren.

Hoe wordt forward propagation efficiënt geïmplementeerd in moderne frameworks?

Moderne frameworks optimaliseren forward propagation via batchverwerking, GPU-versnelling, gevectoriseerde operaties, geheugentechnieken en gespecialiseerde algoritmen. TensorFlow en PyTorch verzorgen deze optimalisaties automatisch, zodat je complexe netwerken kunt bouwen zonder de low-level details te beheren.

Kan forward propagation worden gebruikt zonder het netwerk te trainen?

Ja, zodra een neuraal netwerk getraind is, wordt bij inferentie (voorspellen op nieuwe data) alleen forward propagation gebruikt. Daarom is inzicht in forward propagation cruciaal voor deployment, aangezien training met backpropagation alleen tijdens de ontwikkelfase nodig is.

Hoe beïnvloedt de keuze van activatiefunctie forward propagation?

Verschillende activatiefuncties beïnvloeden zowel het leervermogen van het netwerk als de rekenefficiëntie. ReLU is sneller te berekenen en helpt verdwijnende gradiënten voorkomen, sigmoid is nuttig voor binaire classificatie-outputs en softmax normaliseert outputs voor multi-class-problemen. De keuze beïnvloedt zowel welke patronen het netwerk kan leren als hoe snel het data verwerkt.

Bexruz (Bex) Tuychiev

Auteur

Bexruz (Bex) Tuychiev

Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn. 

Onderwerpen

Gerelateerd