Logistic Regression - Student Assignment¶
Total Points: 8 (4 parts × 2 points each)
Prehľad výpočtu logistickej regresie:¶
1. Inicializovať váhy (w) a bias (b) ako nuly alebo (malé) náhodné hodnoty
2. Pre každú trénovaciu iteráciu:
a) Vypočet lineárnej kombinácie: z = X · w + b
b) Aplikovanie sigmoidu pre výpočet pravdepodobností: σ(z) = 1 / (1 + e^(-z))
c) Výpočet straty (Binary Cross-Entropy):
L = -1/n × Σ [y × log(σ(z)) + (1-y) × log(1-σ(z))]
d) Výpočet gradientu:
dw = 1/n × X^T · (σ(z) - y)
db = 1/n × Σ(σ(z) - y)
e) Aktualizácia váh:
w = w - α × dw
b = b - α × db
4. Tvorba predikcií: y = 1 ak σ(z) ≥ 0.5, inak 0
Vzorce:¶
Sigmoid Function: $\sigma(z) = \frac{1}{1 + e^{-z}}$
Binary Cross-Entropy Loss: $L = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\sigma(z_i)) + (1-y_i) \log(1-\sigma(z_i))]$
dw: gradient váh
db: gradient biasu
$\alpha$: rýchlosť učenia
$n$: počet trénovacích záznamov
Setup - Required Imports¶
import numpy as np
from typing import Tuple
Part 1: Sigmoid Function (2 points)¶
Implementujte sigmoid pre vstupnú funkciu z.
Vzorec: $\sigma(z) = \frac{1}{1 + e^{-z}}$
Premenné:
- $\sigma(z)$ - Výstupná pravdepodobnosť (medzi 0 a 1)
- $z$ - Výsledok vstupnej funkcie
- $e$ - Eulerovo číslo (~2.71828)
Vstupy:
z: Pole ľubovoľného rozmeru. Obsahuje výstupy lineárnej kombinácie hodnôt.
Výstup:
result: Pole rovnakého rozmeru akoz, v ktorom je na každú hodnotu vstupného poľa aplikovaný sigmoid
Hints:
- Použite
np.exp(-z)pre výpočet $e^{-z}$ (funguje samostatne pre každý prvok poľa) - výpočet pre celé pole v 1 riadku kódu!
def sigmoid(z: np.ndarray) -> np.ndarray:
# Sample return value for z = 0: 0.5
# Sample return value for z = np.array([0, 2, -2]): array([0.5, 0.88079708, 0.11920292])
"""
Compute the sigmoid of z.
Args:
z: A numpy array of any shape
Returns:
result: np.ndarray - Sigmoid applied element-wise
"""
result = np.zeros_like(z, dtype=float)
# TODO: Implement sigmoid function
# Your code here
return result
Part 2: Predict Probabilities (2 points)¶
Implementujte funkciu, ktorá vypočíta pravdepodobnosti pre binárnu klasifikáciu
Vzorec: $\sigma(X \cdot w + b) = \frac{1}{1 + e^{-(X \cdot w + b)}}$
Premenné:
- $\sigma(X \cdot w + b)$ - predikované pravdepodobnosti (Pole veľkosti [počet záznamov])
- $X$ - matica atribútov (Dvojrozmerné pole veľkosti [počet záznamov[počet atribútov]])
- $w$ - váhy (Pole veľkosti [počet atribútov])
- $b$ - bias
- $\sigma$ - sigmoid funkcia
Vstupy:
X: Dvojrozmerné pole vo formáte[[atr1, atr2, ...], [atr1, atr2, ...], ...]w: Pole váh vo formáte[w1, w2, ...]b: Bias
Výstup:
probabilities: Predikované pravdepodobnosti (Pole veľkosti [počet záznamov])
Hints:
Použite
np.dot(X, w)pre výpočet $X \cdot w$Ku výsledku $X \cdot w$ môžete jednoducho pripočítať
b(numpy to pripočíta každému záznamu v poli) - výsledok potom môžete priamo vložiť do funkciesigmoid()z predošlej časti
def predict_proba(X: np.ndarray, w: np.ndarray, b: float) -> np.ndarray:
# Sample return value for X=[[1,2],[3,4]], w=[0.5,0.5], b=0: array([0.81757448, 0.97068777])
"""
Compute predicted probabilities using logistic regression.
Args:
X: Feature matrix of shape (m, n)
w: Weight vector of shape (n,)
b: Bias term (scalar)
Returns:
probabilities: np.ndarray - Predicted probabilities of shape (m,)
"""
probabilities = np.zeros(X.shape[0])
# TODO: Implement probability prediction
# Your code here
return probabilities
Part 3: Compute Binary Cross-Entropy Loss (2 points)¶
Implementujte funkciu, ktorá vypočíta hodnotu stratovej funkcie (pre binárnu krížovú entrópiu).
Vzorec: $L = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\sigma(z_i)) + (1-y_i) \log(1-\sigma(z_i))]$
Variables:
- $L$ - hodnota straty
- $n$ - počet trénovacích záznamov
- $y_i$ - skutočná trieda záznamu $i$ (0 alebo 1)
- $\sigma(z_i)$ - predikovaná pravdepodobnosť pre záznam $i$
Vstupy:
y: Skutočné triedy záznamov (Pole veľkosti [počet záznamov] obsahujúce hodnoty 0 alebo 1)y_pred: Predikované pravdepodobnosti (Pole veľkosti [počet záznamov] obsahujúce hodnoty medzi 0 a 1)
Výstup:
loss: Hodnota straty (float)
Hints:
Použite
np.log(x)pre výpočet logaritmu (funguje samostatne pre každý prvok poľa)Použite
np.mean(x)pre výpočet priemeru (nahradí vo vzorci $\frac{1}{n}\sum{x}$)Použite
np.clip(y_pred, 1e-15, 1-1e-15), aby ste sa vyhli errorom v prípadoch, kde nastane log(0)
def compute_loss(y: np.ndarray, y_pred: np.ndarray) -> float:
# Sample return value for y=[1,0,1,0], y_pred=[0.9,0.1,0.8,0.2]: ~0.164
"""
Compute binary cross-entropy loss.
Args:
y: Actual labels of shape (m,)
y_pred: Predicted probabilities of shape (m,)
Returns:
loss: float - The binary cross-entropy loss
"""
loss = 0.0
# TODO: Implement binary cross-entropy loss
# Your code here
return loss
Part 4: Gradient Descent Update (2 points)¶
Implementujte funkciu, ktorá vypočíta gradienty a vykoná jeden krok gradientového zostupu.
Vzorce pre výpočet gradientov:
- Weight gradient: $dw = \frac{1}{n} X^T \cdot (\sigma(z) - y)$
- Bias gradient: $db = \frac{1}{n} \sum_{i=1}^{n} (\sigma(z_i) - y_i)$
Aktualizácia váh:
- $w_{new} = w - \alpha \cdot dw$
- $b_{new} = b - \alpha \cdot db$
Premenné:
- $X$ - matica atribútov (Dvojrozmerné pole veľkosti [počet záznamov[počet atribútov]])
- $y$ - skutočné triedy záznamov (Pole veľkosti [počet záznamov] obsahujúce hodnoty 0 alebo 1)
- $\sigma(z)$ - predikované pravdepodobnosti (Pole veľkosti [počet záznamov] obsahujúce hodnoty medzi 0 a 1)
- $w$ - váhy (Pole veľkosti [počet atribútov])
- $b$ - bias
- $\alpha$ - rýchlosť učenia
- $n$ - počet záznamov
- $X^T$ - transponovaná matica X
Vstupy:
X: Dvojrozmerné pole vo formáte[[atr1, atr2, ...], [atr1, atr2, ...], ...]y: Pole skutočných označení vo formáte[trieda1, trieda2, ...]w: Pole váh vo formáte[w1, w2, ...]b: Biaslearning_rate: Rýchlosť učenia ($\alpha$)
Výstup:
w_new: Pole aktualizovaných váh vo formáte [$w1_{new}$, $w2_{new}$, ...]b_new: Aktualizovaný bias
Hints:
Pre výpočet predikcii použite vašu funkciu
predict_proba(X, w, b)Použite
X.shape[0]pre získanie počtu záznamov $n$Použite
X.Tpre získanie transponovanej matice X ($X^T$)Chyba: $\sigma(z_i) - y_i$
Použite
np.sum(chyba)pre výpočet gradientu biasuPoužite
np.dot(X.T, chyba)pre výpočet gradientu váh
def gradient_descent_step(X: np.ndarray, y: np.ndarray, w: np.ndarray, b: float,
learning_rate: float) -> Tuple[np.ndarray, float]:
# Sample: After one step, weights and bias should move toward minimizing the loss
"""
Perform one step of gradient descent.
Args:
X: Feature matrix of shape (m, n)
y: Actual labels of shape (m,)
w: Current weight vector of shape (n,)
b: Current bias term (scalar)
learning_rate: Learning rate α
Returns:
w_new: np.ndarray - Updated weight vector
b_new: float - Updated bias term
"""
w_new = w.copy()
b_new = b
# TODO: Implement gradient descent step
# 1. Get predictions using predict_proba
# 2. Compute gradients for w and b
# 3. Update w and b using the learning rate
# Your code here
return w_new, b_new
Testing Dataset¶
Cieľom je klasifikovať záznamy medzi triedy 0 a 1 na základe dvoch vstupných atribútov.
# Features: 2 features (X1, X2)
# Target: Binary labels (0 or 1)
X_test = np.array([
[0.5, 1.5],
[1.0, 1.0],
[1.5, 0.5],
[3.0, 0.5],
[2.0, 2.0],
[1.0, 2.5],
[2.5, 3.0],
[3.5, 2.5],
[3.0, 3.5],
[4.0, 4.0]
])
y_test = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
# Initial weights and bias
w_init = np.array([0.0, 0.0])
b_init = 0.0
print(f"Dataset shape: X = {X_test.shape}, y = {y_test.shape}")
print(f"Number of class 0: {np.sum(y_test == 0)}")
print(f"Number of class 1: {np.sum(y_test == 1)}")
print(f"Initial weights: {w_init}")
print(f"Initial bias: {b_init}")
Test Your Implementation¶
# Test Part 1: Sigmoid
test_z = np.array([-2, -1, 0, 1, 2])
sigmoid_result = sigmoid(test_z)
print("Part 1 - Sigmoid Function:")
print(f"Input: {test_z}")
print(f"Your result: {sigmoid_result}")
print(f"Expected: [0.1192, 0.2689, 0.5000, 0.7311, 0.8808]")
print()
# Test Part 2: Predict Probabilities
test_X = np.array([[1, 2], [3, 4], [5, 6]])
test_w = np.array([0.5, -0.5])
test_b = 0.0
proba_result = predict_proba(test_X, test_w, test_b)
print("Part 2 - Predict Probabilities:")
print(f"Input X shape: {test_X.shape}, w: {test_w}, b: {test_b}")
print(f"Your result: {proba_result}")
print(f"Expected: [0.3775, 0.3775, 0.3775]")
print()
# Test Part 3: Compute Loss
test_y_true = np.array([1, 0, 1, 0])
test_y_pred = np.array([0.9, 0.1, 0.8, 0.2])
loss_result = compute_loss(test_y_true, test_y_pred)
print("Part 3 - Compute Loss:")
print(f"True labels: {test_y_true}")
print(f"Predicted probabilities: {test_y_pred}")
print(f"Your loss: {loss_result:.4f}")
print(f"Expected: ~0.1643")
print()
# Test Part 4: Gradient Descent Step
learning_rate = 0.1
w_new, b_new = gradient_descent_step(X_test, y_test, w_init, b_init, learning_rate)
print("Part 4 - Gradient Descent Step:")
print(f"Initial weights: {w_init}, bias: {b_init}")
print(f"Learning rate: {learning_rate}")
print(f"Updated weights: {w_new}")
print(f"Updated bias: {b_new:.4f}")
print()
print("After several iterations, the model should learn to separate the classes.")
print("Weights should move in a direction that reduces the loss.")
# Full Training Loop Test (Optional)
# Run multiple iterations to see if your model learns
def train_model(X, y, w, b, learning_rate, num_iterations):
"""Train the logistic regression model."""
for i in range(num_iterations):
w, b = gradient_descent_step(X, y, w, b, learning_rate)
if (i + 1) % 100 == 0:
y_pred = predict_proba(X, w, b)
loss = compute_loss(y, y_pred)
print(f"Iteration {i+1}: Loss = {loss:.4f}")
return w, b
# Train for 500 iterations
print("Training for 500 iterations...")
w_trained, b_trained = train_model(X_test, y_test, w_init.copy(), b_init, 0.5, 500)
print(f"\nFinal weights: {w_trained}")
print(f"Final bias: {b_trained:.4f}")
# Test predictions
final_probs = predict_proba(X_test, w_trained, b_trained)
final_preds = (final_probs >= 0.5).astype(int)
print(f"\nPredicted classes: {final_preds}")
print(f"Actual classes: {y_test}")
print(f"Accuracy: {np.mean(final_preds == y_test) * 100:.1f}%")