STATISTICS GUIDE · PYTHON

Logistic regression gradient descent in Python

Follow the learning process from a first guess to better class probabilities. The example uses a short NumPy program so you can see every update instead of hiding the steps inside a library.

Beginner-friendly code walkthroughDocs checked October 30, 2026

Quick answer: gradient descent repeatedly adjusts the model’s weights to reduce logistic loss. This guide uses full-batch gradient descent: every update uses all eight training examples.

Four training stages: initialize weights, predict probabilities, calculate log loss and its gradient, then update weights.
Infographic: the training loopEach pass measures the current error and nudges the weights in a direction that should reduce it.
Log loss decreases over 3,000 full-batch gradient updates on the eight-example dataset used in this guide.
Loss from this guide’s dataThe chart is calculated from the same eight rows and learning rate shown in the code.

What gradient descent does

A logistic model first calculates a score, often written z = b + w₁x₁ + …. A sigmoid function changes that score into a probability from 0 to 1. The model compares those probabilities with the known 0/1 answers using log loss.

Gradient descent calculates how each weight affects that loss. It then changes the weights a little. If the learning rate is well chosen, repeated updates make the training loss smaller. The goal is not merely to make the training loss tiny; the model also needs to work on new cases.

The update rule in plain English

For a data matrix X, predicted probabilities p, target labels y, and n rows, the full-batch gradient for logistic loss is:

gradient = Xᵀ(p − y) / n

Then the program applies new weights = old weights − learning rate × gradient. The minus sign moves the weights downhill on the loss curve. This simple form assumes binary labels and the usual logistic loss; regularization adds another term.

Worked Python example

The input values are scaled practice scores. The labels deliberately overlap: the same range contains both 0 and 1 outcomes, so a finite logistic fit is possible without perfectly separating the classes.

import numpy as np

# One scaled feature and a binary target.
x = np.array([-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0])
y = np.array([0, 0, 1, 0, 1, 1, 0, 1], dtype=float)

# First column is the intercept; second column is the feature.
X = np.column_stack([np.ones(len(x)), x])
weights = np.zeros(X.shape[1])
learning_rate = 0.1
loss_history = []

def sigmoid(z):
    z = np.clip(z, -30, 30)  # avoid overflow in exp()
    return 1 / (1 + np.exp(-z))

def record_loss(probability):
    probability = np.clip(probability, 1e-12, 1 - 1e-12)
    return -np.mean(y * np.log(probability) + (1 - y) * np.log(1 - probability))

loss_history.append(record_loss(sigmoid(X @ weights)))  # loss before updates
for step in range(1, 3001):
    probability = sigmoid(X @ weights)
    gradient = X.T @ (probability - y) / len(y)
    weights -= learning_rate * gradient
    if step % 100 == 0:
        loss_history.append(record_loss(sigmoid(X @ weights)))

new_x = np.array([-0.25, 0.75])
new_X = np.column_stack([np.ones(len(new_x)), new_x])
new_probability = sigmoid(new_X @ weights)

print("intercept, slope:", weights)
print("probability of class 1:", new_probability)
print("final training log loss:", loss_history[-1])

The first weight is the intercept and the second is the slope for the scaled input. The final numbers are probabilities, not certain answers. The chart above records the loss every 100 updates from this exact example.

For a production model, use a train/validation split, inspect class balance and compare suitable metrics. The simple loop omits regularization and stopping rules to make the update visible.

Common problems and how to spot them

scikit-learn’s SGDClassifier(loss="log_loss") fits logistic regression with stochastic gradient descent. It updates from individual samples rather than calculating one full-data gradient at each step, so its path and final values can differ from this teaching loop.

Frequently asked questions

Is this batch gradient descent or stochastic gradient descent?

This code uses full-batch gradient descent because each update uses every training row. Stochastic gradient descent updates from one row at a time; mini-batch methods use a small group of rows.

Why does logistic regression need a sigmoid function?

The linear score can be any number. The sigmoid maps it to a value between 0 and 1, which the model uses as a probability estimate for the positive class.

What learning rate should I choose?

There is no one value that works for every dataset. A value that is too high can make loss jump around; one that is too low can make learning slow. Scale features, plot loss and tune using validation data.

Does lower training loss prove that the model is good?

No. Training loss measures fit to the rows used for learning. Check validation or test data to estimate how the model may perform on new cases.

Sources and next steps