Binary cross-entropy was the very first loss function I truly understood on a gut level, mostly because I spent a weekend building a spam classifier from scratch and had to implement it by hand instead of just calling a library function. That exercise taught me more than any textbook chapter could. In this article, I’ll walk you through binary cross-entropy loss the same way I eventually understood it — starting from plain probability, moving through the math, and ending with real code and practical tips I wish someone had told me earlier.
The Problem Binary Cross-Entropy Solves
Whenever I’m building a model for a binary classification problem — spam vs. not spam, fraud vs. not fraud, cat vs. not cat — I need a way to measure how wrong my model’s predicted probabilities are compared to the actual labels. Binary cross-entropy (BCE), also called log loss, is the standard way to do this.
The key idea is simple: I want to punish confident wrong predictions much more harshly than I punish uncertain wrong predictions.
The Mathematical Definition
For a single training example with true label $y \in {0, 1}$ and predicted probability $\hat{y} \in (0, 1)$ (usually the output of a sigmoid activation), binary cross-entropy is defined as:
$$\mathcal{L}(y, \hat{y}) = -\left[y \log(\hat{y}) + (1-y)\log(1-\hat{y})\right]$$
For a full dataset of $N$ examples, I average this over all samples:
$$\mathcal{L} = -\frac{1}{N}\sum_{i=1}^{N}\left[y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)\right]$$
Breaking Down What’s Happening
Let’s consider both cases separately:
- If $y = 1$: the loss simplifies to $-\log(\hat{y})$. As $\hat{y} \to 1$, the loss goes to 0. As $\hat{y} \to 0$, the loss goes to $\infty$.
- If $y = 0$: the loss simplifies to $-\log(1-\hat{y})$. As $\hat{y} \to 0$, the loss goes to 0. As $\hat{y} \to 1$, the loss goes to $\infty$.
This is exactly the “harsh punishment for confident wrong answers” behavior I mentioned earlier. If the true label is 1 and my model predicts 0.99, the loss is tiny. But if my model confidently predicts 0.01 when the true label is 1, the loss explodes.
A Worked Numerical Example
Suppose I have three examples:
| Example | True Label $y$ | Predicted $\hat{y}$ | Loss |
|---|---|---|---|
| 1 | 1 | 0.9 | $-\log(0.9) = 0.105$ |
| 2 | 0 | 0.2 | $-\log(0.8) = 0.223$ |
| 3 | 1 | 0.1 | $-\log(0.1) = 2.303$ |
Average loss:
$$\mathcal{L} = \frac{0.105 + 0.223 + 2.303}{3} = \frac{2.631}{3} \approx 0.877$$
Notice how example 3 — a confidently wrong prediction — dominates the total loss. That’s the whole point of using log loss instead of something simpler like absolute error.
Why the Sigmoid + BCE Combination Works So Well
Binary cross-entropy is almost always paired with a sigmoid activation function at the output layer:
$$\hat{y} = \sigma(z) = \frac{1}{1 + e^{-z}}$$
This pairing is elegant mathematically because when I compute the gradient of the loss with respect to the pre-activation logit $z$, it simplifies beautifully:
$$\frac{\partial \mathcal{L}}{\partial z} = \hat{y} – y$$
This clean gradient is one of the best-kept secrets in deep learning — it means the error signal flowing backward is simply the difference between prediction and truth, with no messy derivative terms from the sigmoid function itself getting in the way. This helps avoid the vanishing gradient problems that would occur if I paired sigmoid with, say, mean squared error.
Visualizing the Pipeline
flowchart TD
A[Input Features] --> B[Neural Network Layers]
B --> C[Output Logit z]
C --> D["Sigmoid Activation: y-hat = sigma(z)"]
D --> E[Predicted Probability y-hat]
F[True Label y] --> G[Binary Cross-Entropy Loss]
E --> G
G --> H[Gradient: y-hat minus y]
H --> I[Backpropagation to Update Weights]
Code Example: Binary Cross-Entropy in PyTorch and TensorFlow
PyTorch
import torch
import torch.nn as nn
# Predicted probabilities (after sigmoid) and true labels
y_pred = torch.tensor([0.9, 0.2, 0.1], requires_grad=True)
y_true = torch.tensor([1.0, 0.0, 1.0])
bce_loss = nn.BCELoss()
loss = bce_loss(y_pred, y_true)
print(f"BCE Loss: {loss.item():.4f}")
# In practice, use BCEWithLogitsLoss for numerical stability
# (it combines sigmoid + BCE internally)
logits = torch.tensor([2.2, -1.4, -2.2], requires_grad=True)
bce_logits_loss = nn.BCEWithLogitsLoss()
loss2 = bce_logits_loss(logits, y_true)
print(f"BCE with Logits Loss: {loss2.item():.4f}")
TensorFlow / Keras
import tensorflow as tf
y_true = tf.constant([1.0, 0.0, 1.0])
y_pred = tf.constant([0.9, 0.2, 0.1])
bce = tf.keras.losses.BinaryCrossentropy()
loss = bce(y_true, y_pred)
print(f"BCE Loss: {loss.numpy():.4f}")
# Model compilation example
model = tf.keras.Sequential([
tf.keras.layers.Dense(16, activation='relu', input_shape=(10,)),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
Why Use BCEWithLogitsLoss Instead of Separate Sigmoid + BCE?
I learned this the hard way after running into NaN losses during training. If $\hat{y}$ becomes exactly 0 or 1 due to floating-point rounding, $\log(\hat{y})$ or $\log(1-\hat{y})$ becomes $-\infty$. Combined loss functions like BCEWithLogitsLoss in PyTorch or setting from_logits=True in TensorFlow use the log-sum-exp trick internally to avoid this instability, computing:
$$\mathcal{L} = \max(z, 0) – z \cdot y + \log(1 + e^{-|z|})$$
This is mathematically equivalent to the sigmoid + BCE formula but far more numerically stable.
Advantages of Binary Cross-Entropy
- Probabilistically grounded: It’s derived directly from maximum likelihood estimation for a Bernoulli distribution.
- Well-behaved gradients: When paired with sigmoid, gradients are clean and don’t vanish the way they might with MSE.
- Penalizes confident mistakes heavily: This pushes the model to be well-calibrated rather than just accurate.
- Differentiable everywhere (within the open interval $(0,1)$), making it ideal for gradient-based optimization.
Disadvantages and Limitations
- Sensitive to class imbalance: If 95% of my examples are negative, the model can achieve low average loss by just predicting negative most of the time. This is often addressed with weighted BCE or focal loss.
- Numerical instability at extremes: As discussed above, values very close to 0 or 1 can cause instability if not handled carefully.
- Assumes independent binary outcomes: For multi-label problems, BCE is applied per-label independently, which is fine, but it doesn’t model any correlation between labels.
Weighted Binary Cross-Entropy for Imbalanced Data
$$\mathcal{L}{weighted} = -\frac{1}{N}\sum{i=1}^{N}\left[w_1 \cdot y_i \log(\hat{y}_i) + w_0 \cdot (1-y_i)\log(1-\hat{y}_i)\right]$$
Here $w_1$ and $w_0$ are weights I can tune based on class frequency, giving more importance to the minority class.
Binary Cross-Entropy vs Other Loss Functions
| Loss Function | Use Case | Output Range | Notes |
|---|---|---|---|
| Binary Cross-Entropy | Binary classification | $\hat{y} \in (0,1)$ | Pairs with sigmoid |
| Categorical Cross-Entropy | Multi-class classification | Softmax output | Pairs with softmax |
| Hinge Loss | SVM-style classification | $\hat{y} \in \mathbb{R}$ | Margin-based, less probabilistic |
| Mean Squared Error | Regression | $\hat{y} \in \mathbb{R}$ | Not ideal for classification due to poor gradient behavior |
| Focal Loss | Imbalanced classification | $\hat{y} \in (0,1)$ | Down-weights easy examples |
Real-World Use Cases
- Spam detection — classifying emails as spam or not spam.
- Medical diagnosis — predicting the presence or absence of a disease from patient data.
- Fraud detection — flagging transactions as fraudulent or legitimate.
- Sentiment analysis — classifying text as positive or negative sentiment.
- Multi-label image tagging — using BCE independently per label when an image can have multiple tags (e.g., “outdoor,” “sunny,” “mountain”).
- Customer churn prediction — predicting whether a customer will churn or stay.
Best Practices
- Always use logits-based loss functions (
BCEWithLogitsLoss,from_logits=True) for numerical stability instead of manually applying sigmoid then BCE. - Address class imbalance using class weights, focal loss, or resampling techniques.
- Monitor calibration, not just accuracy — a well-calibrated model’s predicted probabilities should match observed frequencies.
- Clip predictions to avoid $\log(0)$ if you must compute BCE manually (e.g., clip to $[\epsilon, 1-\epsilon]$).
- Use appropriate evaluation metrics alongside loss, such as precision, recall, F1-score, and AUC-ROC, especially for imbalanced datasets.
Summary
Binary cross-entropy loss is the workhorse loss function for binary classification problems. It’s derived from the negative log-likelihood of a Bernoulli distribution, pairs elegantly with the sigmoid activation function to produce clean gradients, and heavily penalizes confidently wrong predictions. While it has some limitations around class imbalance and numerical stability, these are well-understood problems with established solutions like weighted loss and logits-based implementations. Understanding BCE deeply is foundational to understanding almost every other classification loss function that builds on it.
References
- Goodfellow, I., Bengio, Y., & Courville, A. (2016). “Deep Learning.” MIT Press.
- PyTorch Documentation: https://pytorch.org/docs/stable/generated/torch.nn.BCEWithLogitsLoss.html
- TensorFlow Documentation: https://www.tensorflow.org/api_docs/python/tf/keras/losses/BinaryCrossentropy
- Lin, T.Y., et al. (2017). “Focal Loss for Dense Object Detection.” arXiv:1708.02002.