Neural Network (NN) - Student Assignment¶
Total Points: 8 (4 parts x 2 points each)
Prehľad základných krokov neurónovej siete¶
1. Prijať vstupné atribúty: X
2. Vypočítať skrytú vrstvu: X · W_hidden + b_hidden
3. Aplikovať aktivačnú funkciu ReLU
4. Vypočítať výstupnú vrstvu
5. Aplikovať sigmoid a získať pravdepodobnosť
V tomto cvičení sa zameriame na doprednú neurónovú sieť so vstupnou vrstvou, skrytou vrstvou a výstupným neurónom.
Zapamätajte si hlavné pojmy:
- Input layer: vstupné atribúty, ktoré pošleme do siete
- Hidden layer: vrstva, ktorá kombinuje vstupy pomocou váh a biasov
- Activation function: funkcia, ktorá upraví surové skóre neurónu
- Output layer: finálna vrstva, ktorá vráti predikciu alebo pravdepodobnosť
- Forward pass: prechod dát od vstupu až po výstup
Náš príklad používa tri emailové atribúty podľa kľúčových slov v tomto poradí: [free, win, offer]. Hodnota 1 znamená, že slovo je prítomné, a 0 znamená, že prítomné nie je.
Setup - Required Imports¶
import numpy as np
from numpy.random import default_rng
Part 1: Activation Functions (2 points)¶
Implementujte obe aktivačné funkcie, ktoré používa táto neurónová sieť.
ReLU¶
Pravidlo: záporné hodnoty sa zmenia na 0, kladné hodnoty zostanú nezmenené.
Sigmoid¶
Vzorec:
$$\sigma(z) = \frac{1}{1 + e^{-z}}$$
Výstup sigmoidu je vždy medzi 0 a 1, preto je vhodný vtedy, keď má výstupná vrstva vyzerať ako pravdepodobnosť.
Vstup:
z: NumPy pole surových skóre neurónov
Výstup:
- NumPy pole, kde je aktivačná funkcia aplikovaná po prvkoch
def relu(z: np.ndarray) -> np.ndarray:
# Sample return value for [-2.0, 0.0, 3.0]: [0.0, 0.0, 3.0]
"""
Apply ReLU activation element by element.
Args:
z: Raw neuron scores
Returns:
activated: np.ndarray - ReLU output
"""
activated = np.zeros_like(z, dtype=float)
# TODO: Implement ReLU activation
# Your code here
return activated
def sigmoid(z: np.ndarray) -> np.ndarray:
# Sample return value for [0.0]: [0.5]
"""
Apply sigmoid activation element by element.
Args:
z: Raw neuron scores
Returns:
probabilities: np.ndarray - Sigmoid output
"""
probabilities = np.zeros_like(z, dtype=float)
# TODO: Implement sigmoid activation
# Your code here
return probabilities
Part 2: Hidden Layer Forward Pass (2 points)¶
Implementujte jednu skrytú vrstvu.
Skrytá vrstva prijme vstupné atribúty, skombinuje ich s váhami a biasmi a následne aplikuje ReLU.
Kroky:
- Vypočítajte skóre skrytej vrstvy:
X · W_hidden + b_hidden - Aplikujte
relu(...)
Vstupy:
X: vstupná matica s tvarom(počet_príkladov, počet_atribútov)W_hidden: váhy skrytej vrstvy s tvarom(počet_atribútov, počet_skrytých_neurónov)b_hidden: biasy skrytej vrstvy s tvarom(počet_skrytých_neurónov,)
Výstup:
hidden_output: aktivované hodnoty skrytej vrstvy s tvarom(počet_príkladov, počet_skrytých_neurónov)
def hidden_layer_forward(X: np.ndarray, W_hidden: np.ndarray, b_hidden: np.ndarray) -> np.ndarray:
# Sample output shape for 4 emails and 2 hidden neurons: (4, 2)
"""
Compute the activated output of one hidden layer.
Args:
X: Input feature matrix
W_hidden: Hidden-layer weight matrix
b_hidden: Hidden-layer bias vector
Returns:
hidden_output: np.ndarray - ReLU-activated hidden values
"""
hidden_output = np.zeros((X.shape[0], W_hidden.shape[1]), dtype=float)
# TODO: Compute the hidden layer forward pass
# Your code here
return hidden_output
Part 3: Create a Neural Network (2 points)¶
Vytvorte malú neurónovú sieť ako slovník. Slovník má byť použiteľný vo funkcii pre dopredný prechod v časti 4.
Použite túto štruktúru:
{
"input_size": 3,
"num_hidden_layers": 1,
"hidden_layer_size": 2,
"output_size": 1,
"layers": [
{
"W": np.array([
[0.01, -0.02],
[0.03, 0.04],
[-0.01, 0.02],
]),
"b": np.array([0.0, 0.0]),
"activation": "relu",
},
{
"W": np.array([
[0.05],
[-0.03],
]),
"b": np.array([0.0]),
"activation": "sigmoid",
},
],
}
Pravidlá:
- vytvorte
num_hidden_layersskrytých vrstiev, - každá skrytá vrstva má
hidden_layer_sizeneurónov a aktiváciu"relu", - finálna výstupná vrstva má
output_sizeneurónov a aktiváciu"sigmoid", - každá vrstva ukladá maticu váh
Wa vektor biasovbako NumPy polia (np.ndarray), - biasy inicializujte nulami,
- váhy inicializujte malými nenulovými náhodnými hodnotami, napríklad
rng.normal(0, 0.1, size=...).
Tip: default_rng(random_state) vytvorí opakovateľný generátor náhodných čísel. Funkcia default_rng je importovaná v sekcii potrebných importov.
def create_neural_network(
input_size: int,
num_hidden_layers: int,
hidden_layer_size: int,
output_size: int,
random_state: int = 12,
) -> dict:
# Sample call: create_neural_network(3, 1, 2, 1)
"""
Create a small feedforward neural network stored as a dictionary.
Args:
input_size: Number of input features
num_hidden_layers: Number of hidden layers
hidden_layer_size: Number of neurons in each hidden layer
output_size: Number of output neurons
random_state: Seed for repeatable random initialization
Returns:
network: dict - Neural network description
"""
network = {}
# TODO: Create a dict like in the example, as well as hidden layers and the output layer
# Your code here
return network
Part 4: Full Network Forward Pass (2 points)¶
Použite slovník siete z časti 3 a vypočítajte celý dopredný prechod.
Kroky:
- Začnite s
current_output = X - Pre každú vrstvu v
network["layers"]:- vypočítajte surové skóre:
current_output @ layer["W"] + layer["b"] - aplikujte aktiváciu vrstvy (
relualebosigmoid)
- vypočítajte surové skóre:
- Vráťte finálny výstup
Vstupy:
X: vstupná matica atribútovnetwork: návratová hodnota zcreate_neural_network(...)
Výstup:
probabilities: finálny výstup siete pre každý vstupný riadok
Ak má sieť jeden výstupný neurón, vráťte 1D pole s tvarom (počet_príkladov,). Ak má viac výstupných neurónov, vráťte 2D pole.
def neural_network_forward(X: np.ndarray, network: dict) -> np.ndarray:
# Sample return shape for 4 emails and 1 output neuron: (4,)
"""
Compute probabilities using a small neural network dictionary.
Args:
X: Input feature matrix
network: Dictionary returned by create_neural_network
Returns:
probabilities: np.ndarray - Final network output
"""
probabilities = np.zeros(X.shape[0], dtype=float)
# TODO: Compute the full network forward pass
# Your code here
return probabilities
Testing Dataset¶
feature_names = ["free", "win", "offer"]
X_demo = np.array([
[1.0, 0.0, 1.0], # free + offer
[0.0, 1.0, 0.0], # win
[1.0, 1.0, 1.0], # all keywords
[0.0, 0.0, 0.0], # no keywords
])
# Two hidden neurons. Each column contains weights for one hidden neuron.
W_hidden_demo = np.array([
[0.5, 0.4],
[-0.2, 0.1],
[0.3, -0.5],
])
b_hidden_demo = np.array([0.0, 0.0])
# One output neuron stored as a 2D matrix because networks can have more outputs.
W_output_demo = np.array([[0.7], [0.2]])
b_output_demo = np.array([0.0])
manual_network_demo = {
"input_size": 3,
"num_hidden_layers": 1,
"hidden_layer_size": 2,
"output_size": 1,
"layers": [
{"W": W_hidden_demo, "b": b_hidden_demo, "activation": "relu"},
{"W": W_output_demo, "b": b_output_demo, "activation": "sigmoid"},
],
}
# Tiny training dataset for the final demo.
X_train = np.array([
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 0.0],
[1.0, 1.0, 1.0],
])
# Label 1 means the email looks suspicious because it has at least two keywords.
y_train = np.array([0, 0, 0, 0, 1, 1, 1, 1], dtype=float)
Test your implementation¶
# Test Part 1: Activation Functions
activation_input = np.array([-2.0, 0.0, 3.0])
relu_result = relu(activation_input)
sigmoid_result = sigmoid(np.array([0.0, 2.0, -2.0]))
print("Part 1 - Activation Functions:")
print(f"ReLU result: {relu_result}")
print("ReLU expected: [0. 0. 3.]")
print(f"Sigmoid result: {sigmoid_result}")
print("Sigmoid expected approximately: [0.5 0.88079708 0.11920292]")
print()
# Test Part 2: Hidden Layer
hidden_result = hidden_layer_forward(X_demo[:1], W_hidden_demo, b_hidden_demo)
print("Part 2 - Hidden Layer Forward Pass:")
print(f"Input email features: {X_demo[:1]}")
print(f"Your result: {hidden_result}")
print("Expected: [[0.8 0. ]]")
print()
# Test Part 3: Create Neural Network
network = create_neural_network(3, 1, 2, 1, random_state=12)
print("Part 3 - Create Neural Network:")
print(f"Number of layers: {len(network['layers'])}")
for i, layer in enumerate(network["layers"]):
print(f"Layer {i + 1}: W type = {type(layer['W']).__name__}, W shape = {np.asarray(layer['W']).shape}, b type = {type(layer['b']).__name__}, b shape = {np.asarray(layer['b']).shape}, activation = {layer['activation']}")
print("Expected: 2 layers with W and b as np.ndarray values")
print("Expected shapes: hidden W (3, 2), hidden b (2,), output W (2, 1), output b (1,)")
print()
# Test Part 4: Full Network
probabilities = neural_network_forward(X_demo, manual_network_demo)
predictions = (np.asarray(probabilities) >= 0.5).astype(int)
print("Part 4 - Full Network Forward Pass:")
print(f"Your probabilities: {probabilities}")
print(f"Your 0/1 predictions: {predictions}")
print("First email expected probability approximately: 0.63645254")
print()
Training Demo (not graded)¶
Táto krátka ukážka trénuje sieť s jednou skrytou vrstvou. Tréningovú funkciu nemusíte implementovať kvôli bodom; je tu len na to, aby ste videli, že ten istý dopredný prechod sa opakuje veľakrát, zatiaľ čo sa váhy postupne upravujú.
def binary_cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
y_pred = np.clip(np.asarray(y_pred, dtype=float), 1e-8, 1 - 1e-8)
y_true = np.asarray(y_true, dtype=float)
return float(-np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)))
def train_demo_network(network: dict, X: np.ndarray, y: np.ndarray, epochs: int = 2000, learning_rate: float = 0.8) -> list:
hidden_layer = network["layers"][0]
output_layer = network["layers"][-1]
y_column = y.reshape(-1, 1)
losses = []
for epoch in range(epochs + 1):
hidden_scores = X @ hidden_layer["W"] + hidden_layer["b"]
hidden_output = relu(hidden_scores)
output_scores = hidden_output @ output_layer["W"] + output_layer["b"]
probabilities = sigmoid(output_scores)
if epoch % 400 == 0:
losses.append(binary_cross_entropy(y, probabilities.reshape(-1)))
output_error = probabilities - y_column
grad_W_output = hidden_output.T @ output_error / len(X)
grad_b_output = np.mean(output_error, axis=0)
hidden_error = (output_error @ output_layer["W"].T) * (hidden_scores > 0)
grad_W_hidden = X.T @ hidden_error / len(X)
grad_b_hidden = np.mean(hidden_error, axis=0)
output_layer["W"] -= learning_rate * grad_W_output
output_layer["b"] -= learning_rate * grad_b_output
hidden_layer["W"] -= learning_rate * grad_W_hidden
hidden_layer["b"] -= learning_rate * grad_b_hidden
return losses
try:
demo_network = create_neural_network(3, 1, 4, 1, random_state=12)
if len(demo_network.get("layers", [])) < 2:
raise ValueError("create_neural_network must create at least one hidden layer and one output layer")
before_training = neural_network_forward(X_train, demo_network)
losses = train_demo_network(demo_network, X_train, y_train)
after_training = neural_network_forward(X_train, demo_network)
print("Training demo:")
print(f"Loss snapshots: {np.round(losses, 4)}")
print(f"Predictions before training: {np.round(before_training, 3)}")
print(f"Predictions after training: {np.round(after_training, 3)}")
print(f"Expected labels: {y_train.astype(int)}")
except Exception as e:
print("Training demo could not run yet. Complete Parts 1-4 first.")
print(f"Reason: {e}")