Session #5: Logistic regression - sigmoid functions, gradient descent, Python
Free University Berlin
2026-04-26
Game plan
Logistic regression shares some characteristics with neural networks including the weighted sum and activation function (for single neurons) and gradient-descent training loop. Studying logistic regression therefore serves as a good preparation for understanding neural networks.
Logistic Regression (LogReg) is one of the most common machine learning algorithms. It can be used to predict the probability of an event occurring based on a given labeled data set.
We will go over the algorithm in detail, because it is an excellent preparation for understanding neural networks.
1. Feature Representation
For each input \(x\), we have a vector: \(\mathbf{x} = [x_1, x_2, \ldots, x_n]\)
2. Classification Function
Computes the estimated class using the Sigmoid function: \(\sigma(z) = \frac{1}{1 + e^{-z}}\)
3. Objective Function
A Loss function (Cross-Entropy) to measure how well the model is performing ( cross-entropy loss function).
4. Optimization
Gradient Descent: The method used to minimize the loss and find the best weights.
5. Prediction
The learned model can be used to classify new data points.
For each input \(x\), we create a vector of features: \(\mathbf{x} = [x_1, x_2, \ldots, x_n]\).
Example
In the Python exercise for this lecture, we will generate two dimensional points with overlapping Gaussian distributions, but in general, the features can also be created (e.g., by counting positive and negative words for sentiment analysis).
Weights
LogReg learns a vector of a vector of weights and a bias term to solve the classification task
Equation 1 can be represented identically using dot product notation (where the bold font indicates a vector of weights (\(\mathbf{w}\)) and inputs (\(\mathbf{x}\)))
\[ z_i = \mathbf{w}\cdot \mathbf{x}_i + b \]
This is identical to the sums we will see at individual neurons in neural networks
Sigmoid function
To create a probability, we transform z through the sigmoid function, \(\sigma(z)\)
\[ \sigma(z) = \frac{1}{1+e^{-z}} = \frac{1}{1+\exp(-z)} \]
\[ \begin{align*} 1−\sigma(x) &=& 1- \frac{1}{1+e^{-x}}\\ &=& \frac{1+e^{-x} - 1}{1+e^{-x}} \\ &=& \frac{e^{-x}}{1+e^{-x}} \cdot \frac{e^{x}}{e^{x}}\\ &=& \frac{1}{1+e^{x}} \\ &=& \sigma(-x) \\ \end{align*} \]
\[ \begin{align*} \sigma(\text{logit}(p)) &= \frac{1}{1 + e^{-\left[ \ln \left( \frac{p}{1-p} \right) \right]}} &&\bullet \text{Substitute Logit into Sigmoid } z \\ &= \frac{1}{1 + e^{\left[ \ln \left( \frac{1-p}{p} \right) \right]}} && \bullet\text{Apply } -\ln(A) = \ln(1/A) \\ &= \frac{1}{1 + \frac{1-p}{p}} && \bullet\text{Identity: } e^{\ln(x)} = x \\ &= \frac{1}{\frac{p + 1 - p}{p}} && \\ &= \frac{1}{1/p} && \bullet\text{Simplify} \\ &= p && \end{align*} \]
\[ \begin{array}{c c c c c} \mathbf{x}& \longrightarrow & \sigma(z)=\sigma(\mathbf{w}\cdot\mathbf{x}+b) & \longrightarrow & \hat{y}\\ \text{\small Input features} & & \text{\small Output probability} & & \text{\small Prediction of class label} \end{array} \]
Classification Decision Rule
\[ \mathrm{classification}(x) = \begin{cases} 1 & \text{if }P(y = 1|x) > 0.5 \\ 0 & \text{otherwise} \end{cases} \]
A good model will minimize the difference between our prediction \(\hat{y}\) and the ground truth \(y\)
this distance is defined by the loss function (also known as the cost function).
The same loss function is commonly used for logistic regression and for neural networks, the cross-entropy loss.
Recall that our classifier output is \(\hat{y} = \sigma(\mathbf{w}\cdot\mathbf{x} + b)\)
For logistic regression with binary classification, \(y\) can be either 0 or 1, but \(\hat{y}\) is a probability that ranges from 0 to 1.
We measure the difference with the loss function \(\mathcal{L}(\hat{y}, y)\)
As \(\log \sigma(z) \rightarrow 1\), the loss approaches zero
Using \(\hat{y}=\sigma(z)\), we can concisely write the loss function as: \[ \log p(y|x) = \log[\hat{y}^y(1-\hat{y})^{1-y}] = y\log\hat{y} + (1-y)\log(1-\hat{y}) \]
Note this simplifies to either \(\log\hat{y}\) or \(\log(1-\hat{y})\) depending on whether \(y\) is 0 or 1.
Since it is convenient to minimize the function, we multiply it by -1: The result is known as the cross-entropy loss
\[ \mathcal{L}_{CE}(\hat{y}, y) = - y\log\hat{y} - (1-y)\log(1-\hat{y}) \]
\[ \mathcal{L}_{CE}(\hat{y}, y) = - y\log\sigma(\mathbf{w}\cdot \mathbf{x} + b) - (1-y)\log(1-\sigma(\mathbf{w}\cdot \mathbf{x} + b)) \]
\[ \mathcal{L}_{CE}(\hat{y}, y) = - y\log\sigma(\mathbf{Xw} + b) - (1-y)\log(1-\sigma(\mathbf{Xw} + b)) \]
\[ \mathcal{L}_{CE}(\hat{y}, y) = - 1\log 1 = 0 \]
Optimization Goal
To create a high-performing classifier, we minimize the Cost Function \(J(\theta)\), which is the average loss over the entire training set of \(m\) examples.
\[ J(\theta) = \frac{1}{m} \sum_{i=1}^{m} \mathcal{L}(\hat{y}^{(i)}, y^{(i)}) \]
\[ \hat{\theta} = \arg\min_{\theta} J(\theta) \]
Basic idea of gradient descent
The gradient descent algorithm finds the gradient of the loss function at the current point and moves the parameters in the opposite direction.
Given a learning rate \(\eta\) and the gradient of the weights and biases:
Each iteration of the GD processes Updates the weights and biases of each layer \[ \begin{eqnarray*} \mathbf{W} &\leftarrow &\mathbf{W} - \eta\Delta \mathbf{W} \\ \mathbf{b} &\leftarrow &\mathbf{b} - \eta\Delta \mathbf{b} \end{eqnarray*} \]
We will explain how the gradient is calculated and then return to the mechanics of the gradient descent algorithm.
Another commonly encountered notation is the gradient, which by convention is represented as a column vector
\[ \nabla f(\mathbf{x}) = \begin{bmatrix} \dfrac{\partial f}{\partial \mathbf{x}} \end{bmatrix}^T = \begin{bmatrix} \dfrac{\partial f}{\partial x_1}\\ \dfrac{\partial f}{\partial x_2} \\ \cdots \\ \dfrac{\partial f}{\partial x_n} \end{bmatrix} \tag{2}\]
Note
We need to update each component of the weight vector \(\mathbf{w}\), e.g., \(w_j\) based on the firstderivative of the loss \(\mathcal{L}=\mathcal{L}_{CE}(\hat{y}, y)\) with respect to the component.
\[ \frac{\partial \mathcal{L}}{\partial w_j} = \frac{\partial \mathcal{L}}{\partial \sigma_i} \cdot \frac{\partial \sigma_i}{\partial w_j} = \frac{\partial \mathcal{L}}{\partial \sigma_i} \cdot \frac{\partial \sigma_i}{\partial z_i} \cdot \frac{\partial z_i}{\partial w_j} \]
If \(f\) and \(g\) are differentiable functions, then \[ \left(f(g(x))\right)^{\prime} = f^{\prime}(g(x))\cdot g^{\prime}(x) \]
The chain rule can also be written as follows. If \(y=f(u)\) and \(u=g(x)\), then \[ \dfrac{dy}{dx} =\dfrac{dy}{du}\dfrac{du}{dx} \]
Examples
\[ \frac{\partial \mathcal{L}}{\partial w_j} = \underbrace{\frac{\partial \mathcal{L}}{\partial \sigma_i}}_{\text{Part A}} \cdot \underbrace{\frac{\partial \sigma_i}{\partial z_i}}_{\text{Part B}} \cdot \underbrace{\frac{\partial z_i}{\partial w_j}}_{\text{Part C}} \]
\[ \begin{align*} \frac{\partial \mathcal{L}}{\partial \hat{y}} & = -\frac{\partial }{\partial \hat{y}} [y \ln \hat{y} + (1-y) \ln(1-\hat{y})] && \bullet \text{Using } \hat{y}=\sigma(wx+b) \\ & = -y \frac{\partial }{\partial \hat{y}}\ln \hat{y} - (1-y)\frac{\partial }{\partial \hat{y}}\ln(1-\hat{y}) && \bullet \text{Move constants out of derivative}\\ & = -\frac{y}{\hat{y}} - (1-y) \frac{-1}{1-\hat{y}} && \bullet \text{Recall } \frac{d \ln x}{dx} = \frac{1}{x} \\ & = -\frac{y}{\hat{y}} + \frac{1-y}{1-\hat{y}} && \bullet \text{Simplify signs} \\ & = \frac{-y(1-\hat{y}) + \hat{y}(1-y)}{\hat{y}(1-\hat{y})} && \bullet \text{Common denominator} \\ & = \frac{-y + y\hat{y} + \hat{y} - y\hat{y}}{\hat{y}(1-\hat{y})} && \bullet \text{Expand numerator} \\ & = \frac{\hat{y} - y}{\hat{y}(1-\hat{y})} && \bullet \text{Final result} \end{align*} \]
\[ \begin{align*} \dfrac{d\sigma(x)}{dx} & = \dfrac{d}{dx} \dfrac{1}{1+e^{-x}} \\ & = \dfrac{-1}{(1+e^{-x})^2}\cdot (-e^{-x}) && \bullet \text{chain rule} \\ & = \dfrac{e^{-x}}{(1+e^{-x})^2} && \bullet \text{simplify}\\ & = \dfrac{1}{1+e^{-x}} \cdot \dfrac{e^{-x}}{1+e^{-x}} && \bullet \text{rearrange}\\ & = \sigma(x) \cdot \dfrac{e^{-x}}{1+e^{-x}} && \bullet \text{definition of } \sigma(x)\\ & = \sigma(x) \cdot \dfrac{1 + e^{-x} - 1}{1+e^{-x}} && \bullet \text{add and subtract 1}\\ & = \sigma(x) \cdot \left( \dfrac{1 + e^{-x} }{1+e^{-x}} -\dfrac{1}{1+e^{-x}} \right) && \bullet \text{rearrange}\\ & = \sigma(x) \cdot \left( 1 -\sigma(x) \right) && \bullet \text{simplify using definition of } \sigma(x) \end{align*} \]
\[ \frac{d\sigma(x)}{dx} = \sigma(x)(1-\sigma(x)) \]
The Since \(z = w_1x_1 + w_2x_2 + \dots + b\), the derivative with respect to \(w_j\) is simply the feature it is multiplied by: \[ \frac{\partial z}{\partial w_j} = x_j \]
\[ \frac{\partial \mathcal{L}}{\partial w_j} = \underbrace{\left[ \frac{\hat{y} - y}{\hat{y}(1-\hat{y})} \right]}_{\text{Part A}} \cdot \underbrace{\left[ \hat{y}(1-\hat{y}) \right]}_{\text{Part B}} \cdot \underbrace{x_j}_{\text{Part C}} \]
\[ \begin{align*} \frac{\partial \mathcal{L}}{\partial w_j} &= (\hat{y} - y)x_j \end{align*} \]
Intuition
The gradient is a product of the error and the magnitude of the input.
\[ \frac{\partial \mathcal{L}}{\partial b} = (\hat{y} - y) \]
Update the weights and biases of each layer \[ \begin{eqnarray*} \mathbf{W} &\leftarrow &\mathbf{W} - \eta\Delta \mathbf{W} \\ \mathbf{b} &\leftarrow &\mathbf{b} - \eta\Delta \mathbf{b} \end{eqnarray*} \]
Gradient descent is used in a variety of settings outside of back prop. Let’s do one simple example – finding the minimum of a function
\[ f(x) = 7x^2 + 3x-9 \]
\[ \begin{eqnarray*} \dfrac{df}{dx} (7x^2 + 3x-9) &=& 14x +3 &=& 0 \\ x &=& - \dfrac{3}{14} \end{eqnarray*} \]
\[ x \leftarrow x-\eta (14x +3 ) \]
import numpy as np
import matplotlib.pylab as plt
def f(x):
return 7*x**2 + 3*x - 9
def d(x):
return 14*x + 3
# first guess
eta = 0.03
# plot
x = np.linspace(-1, 1.5, 1000)
plt.figure(figsize=(3, 3))
plt.plot(x, f(x))
t = 1
for i in range(15):
plt.plot(t, f(t), marker='o', color='r')
t = t - eta*d(t)
print(f"Solution: {t:.4f} (-3/14={-3/14:.4f})")
plt.axvline(x=t, color='blue', linestyle='--', linewidth=0.8)Solution: -0.2139 (-3/14=-0.2143)
Final Solution: -0.0199 (Target: -0.2143)
\[ \mathcal{L} = L(\Theta; \mathbf{x},y) \]
If the following is the derivative rule for one example \[ \frac{\partial \mathcal{L}}{\partial w_j} = (\sigma(z_i) - y_i)x_{i,j} \] then the following is the cost across a batch with m examples \[ \frac{\partial C}{\partial w_j} = \frac{1}{m}\sum_{i=1}^m(\sigma(z_i) - y_i)x_{i,j} \] - The update rule for \(w_j\) is thus \[ w_j \leftarrow w_j - \eta \frac{\partial C}{\partial w_j} \]
\[ \begin{array}{ll} \hline \mathbf{Algorithm:} & \text{Gradient Descent Update} \\ \hline 1: & \text{Initialize } \mathbf{w} \in \mathbb{R}^d, b \in \mathbb{R} \\ 2: & \textbf{for } i \leftarrow 1 \text{ to } \text{iterations } \textbf{do} \\ 3: & \quad \hat{\mathbf{y}} \leftarrow \sigma(\mathbf{Xw} + b) \\ 4: & \quad \mathbf{w} \leftarrow \mathbf{w} - \eta \frac{1}{m} \mathbf{X}^T(\hat{\mathbf{y}} - \mathbf{y}) \\ 5: & \textbf{end for} \\ \hline \end{array} \]
Sigmoid function
It is convenient to stack many examples into a matrix
\[ \mathbf{\hat{y}} = \sigma(\mathbf{Xw} + b) \]
\[ \underbrace{\hat{\mathbf{y}}}_{m \times 1} = \sigma \left( \underbrace{\mathbf{X}}_{m \times n} \cdot \underbrace{\mathbf{w}}_{n \times 1} + b \right) \]
\[ \mathbf{X} = \begin{pmatrix} x_{1,1} & x_{1,2} & \cdots & x_{1,n} \\ x_{2,1} & x_{2,2} & \cdots & x_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ x_{m,1} & x_{m,2} & \cdots & x_{m,n} \end{pmatrix} \]
\[ \begin{align*} \sigma(z) & = \frac{1}{1+e^{-z}} \\ \mathrm{softmax}(z_i) & = \frac{e^{z_i}}{\sum_{j=1}^K e^{-z_j}} \end{align*} \]
\[ \sigma(z) = \frac{1}{1+e^{-z}} = \frac{1}{1+\exp(-z)} \]
def cost_function(
X: npt.NDArray,
y: npt.NDArray,
w: npt.NDArray,
b: float
) -> float:
m = len(y) # Define m based on the number of labels
cost_sum = 0.0
for i in range(m):
# np.dot between vectors returns a scalar, b is a float
z = float(np.dot(w, X[i]) + b)
g = sigmoid(z)
cost_sum += -y[i] * np.log(g) - (1 - y[i]) * np.log(1 - g)
return (1 / m) * cost_sumdef gradient_function(
X: npt.NDArray,
y: npt.NDArray,
w: npt.NDArray,
b: float
) -> typing.Tuple[float, npt.NDArray]:
m, n = X.shape # m = examples, n = features
grad_w = np.zeros(n)
grad_b = 0.0
for i in range(m):
# Calculate prediction error for the i-th example
z = float(np.dot(w, X[i]) + b)
g = sigmoid(z)
error = g - y[i]
# Accumulate bias gradient
grad_b += error
# Accumulate weight gradients for each feature j
for j in range(n):
grad_w[j] += error * X[i, j]
# Average the gradients over all examples
grad_b = (1 / m) * grad_b
grad_w = (1 / m) * grad_w
return grad_b, grad_w\[ \frac{\partial C}{\partial w_j} = \frac{1}{m}\sum_{i=1}^m(\sigma(z_i) - y_i)x_{i,j} \quad\text{ and }\quad \frac{\partial C}{\partial b} = \frac{1}{m}\sum_{i=1}^m(\sigma(z_i) - y_i) \]
def gradient_descent(X: npt.NDArray,
y: npt.NDArray,
alpha: float,
iterations: int
) -> typing.Tuple[npt.NDArray, float]:
w = np.zeros(n)
b = 0
for i in range(iterations):
grad_b, grad_w = gradient_function(X, y, w, b)
w = w - alpha * grad_w
b = b - alpha * grad_b
if i % 1000 == 0:
print(f"Iteration {i}: Cost {cost_function(X, y, w, b)}")
return w, b\[ w_j \leftarrow w_j - \eta \frac{\partial C}{\partial w_j} \quad\text{ and }\quad b \leftarrow b - \eta \frac{\partial C}{\partial b} \]
\[ pred(i) = \begin{cases} 1& \text{if }\sigma(z_i) >= 0.5 \\ 0& \text{if }\sigma(z_i) < 0.5 \\ \end{cases} \]
Iteration 0: Cost 0.6863856844505929
Iteration 1000: Cost 0.24893304312617473
Iteration 2000: Cost 0.19455725857907238
Iteration 3000: Cost 0.17109379033874833
Iteration 4000: Cost 0.15808906650231236
Iteration 5000: Cost 0.1498628871067555
Iteration 6000: Cost 0.14421883627612167
Iteration 7000: Cost 0.14012775640570907
Iteration 8000: Cost 0.13704269179323025
Iteration 9000: Cost 0.13464584006217875
training accuracy: 95.60%Plotting this, we get
“With four parameters I can fit an elephant and with five I can make him wiggle his trunk.” — John von Neumann
Model fit can be assessed using the difference between predictions and reality:
a technique used to enhance the generalization ability of machine learning models by adding a penalty term to the loss function.
\[ Y = \beta_0 + \beta_1X_1 + \ldots + \beta_pX_p \]
Shrinkage
Shrinkage (also known as regularization) has the effect of reducing variance.
We add an L2 penalty term to the standard cross-entropy loss function:
\[ J(\mathbf{w}) = -\frac{1}{m} \sum_{i=1}^{m} \left[ y^{(i)} \log(a^{(i)}) + (1 - y^{(i)}) \log(1 - a^{(i)}) \right] + \frac{\lambda}{2m} \|\mathbf{w}\|_2^2 \tag{3}\]
Where the L2 norm penalty is defined as: \[ \|\mathbf{w}\|_2^2 = \sum_{j=1}^{n} w_j^2 \]
🔑 Key Parameters
To train our regularized model, we need the gradient of the objective function \(J(\mathbf{w})\), which combines our standard cross-entropy loss \(\mathcal{L}\) and the L2 penalty.
Using the sum rule for differentiation, we take the derivative of both parts with respect to weight \(w_j\):
\[ \frac{\partial J(\mathbf{w})}{\partial w_j} = \underbrace{\frac{\partial \mathcal{L}}{\partial w_j}}_{\text{Standard Gradient}} + \underbrace{\frac{\partial}{\partial w_j} \left( \frac{\lambda}{2m} \sum_{k=1}^{n} w_k^2 \right)}_{\text{L2 Derivative}} \tag{4}\]
Evaluating the derivative of the penalty term (where \(\frac{\partial}{\partial w_j}(w_j^2) = 2w_j\)), we get:
\[ \frac{\partial J(\mathbf{w})}{\partial w_j} = \underbrace{(\hat{y} - y)x_j}_{\text{Prediction Error}} + \underbrace{\frac{\lambda}{m} w_j}_{\text{Weight Penalty}} \tag{5}\]
When we update our weights (\(w_j \leftarrow w_j - \alpha \frac{\partial J}{\partial w_j}\)) using Equation 5, the L2 term (i.e., weight penalty) alters the step:
The Sum of Convex Functions is Convex
The above total cost function \(J(\mathbf{w})\) is composed of two distinct components \[J(\mathbf{w}) = \mathcal{L}_{\text{cross-entropy}}(\mathbf{w}) + \mathcal{L}_{\text{L2}}(\mathbf{w})\]
THe Standard negative log-likelihood (cross-entropy loss) for logistic regression is convex.
L2 Penalty (\(\frac{\lambda}{2m}\|\mathbf{w}\|_2^2\)) is a quadratic function (“bowl shaped”), which is strictly convex.
Thus logistic regression with L2 penalty has a unique solution
We still need to decide which value to use for \(\lambda\), which is a hyperparameter.
The optimal value of \(\lambda\) can be estimated with \(k\)-Fold Cross-Validation
HOMEWORK You will receive code for “do-it-yourself” logisitic regression in Python and be askeed to add L2 regularization.
🏠 Home | bioonto-llm