Log-Loss (Cross-Entropy)¶
Summary¶
Log-loss (also called cross-entropy loss or logarithmic loss) is a scoring rule that measures the quality of probabilistic predictions. For binary outcomes, it is the negative average of log-probabilities assigned to the actual outcomes. It heavily penalizes confident wrong predictions — predicting 99% when the outcome is 0% scores much worse than predicting 51%.
Log-loss is the cost function used in logistic regression and is equivalent to the negative log-likelihood of the data under a Bernoulli model. Lower log-loss is better, with 0 being perfect.
In sports betting models, log-loss is used both as a training objective (to optimize probabilistic classifiers) and as a validation metric. Unlike Brier score which quadratically penalizes errors, log-loss penalizes exponentially — a prediction of 0.01 when the true outcome is 1 contributes -ln(0.01) = 4.6 to the loss, while Brier would contribute only 0.9801.
Key Concepts¶
- Cross-entropy: Log-loss is the cross-entropy between the predicted distribution and the true distribution. For binary: H(p,q) = -[y log(p) + (1-y) log(1-p)]
- Strictly proper: Like Brier score, log-loss is a strictly proper scoring rule — optimal when predicted probabilities match true probabilities
- Penalizes overconfidence: A prediction of 0.001 for an event that occurs is catastrophic (ln(0.001) = -6.9), much worse than predicting 0.3 (ln(0.3) = -1.2)
- Log-loss vs Brier: Log-loss is more sensitive to confident mistakes; Brier is more sensitive to calibration errors. For betting models, both should be tracked.
- Multi-class log-loss: For soccer 1X2 predictions, use categorical cross-entropy: LL = -Σ y_i log(p_i)
- Normalization: Log-loss can be normalized (divide by log(2)) to report in bits; or compared to baseline (climatology)
Formulas¶
Binary log-loss:
$$LL = -\frac{1}{N} \sum_{i=1}^{N} [y_i \log(p_i) + (1 - y_i) \log(1 - p_i)]$$
Where y_i = actual outcome (1 or 0), p_i = predicted probability.
Multi-class log-loss (categorical):
$$LL = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{ic} \log(p_{ic})$$
For soccer 1X2: C=3 classes (home win, draw, away win), y is one-hot encoded.
Relationship to Brier score: For binary events near 50%, log-loss and Brier are similar. Differences grow at the extremes.
Normalized log-loss:
$$NLL = \frac{LL}{LL_{\text{climatology}}}$$
Where climatology = -[p̄ log(p̄) + (1-p̄) log(1-p̄)] with p̄ = mean outcome rate.
Python Implementation¶
import numpy as np
from sklearn.metrics import log_loss
def log_loss_binary(predicted_probs, actual_outcomes):
"""
Calculate binary log-loss.
"""
eps = 1e-15 # clipping to avoid log(0)
p = np.clip(predicted_probs, eps, 1 - eps)
return -np.mean(actual_outcomes * np.log(p) + (1 - actual_outcomes) * np.log(1 - p))
def log_loss_multiclass(predicted_probs_matrix, actual_outcomes_onehot):
"""
Calculate multi-class log-loss (categorical cross-entropy).
For 1X2 soccer predictions.
Args:
predicted_probs_matrix: Nx3 array of predicted probabilities (rows sum to 1)
actual_outcomes_onehot: Nx3 array of one-hot actual outcomes
"""
eps = 1e-15
p = np.clip(predicted_probs_matrix, eps, 1 - eps)
# Normalize rows to sum to 1
p = p / p.sum(axis=1, keepdims=True)
return -np.mean(np.sum(actual_outcomes_onehot * np.log(p), axis=1))
def log_loss_skill_score(predicted_probs, actual_outcomes):
"""
Log-loss skill score vs. climatology baseline.
"""
ll_model = log_loss_binary(predicted_probs, actual_outcomes)
p_bar = np.mean(actual_outcomes)
p_base = np.full_like(actual_outcomes, p_bar, dtype=float)
ll_baseline = log_loss_binary(p_base, actual_outcomes)
return 1 - (ll_model / ll_baseline)
# Example: soccer 1X2 multi-class
# predicted: [home_prob, draw_prob, away_prob] for each match
predictions = np.array([
[0.55, 0.25, 0.20], # match 1: home predicted most likely
[0.30, 0.35, 0.35], # match 2: draw/away uncertain
[0.70, 0.20, 0.10], # match 3: home heavily favored
])
# outcomes: 1=home win, 2=draw, 3=away win
outcomes = [1, 3, 1] # match 1: home win, match 2: away win, match 3: home win
y_onehot = np.zeros((3, 3))
for i, outcome in enumerate(outcomes):
y_onehot[i, outcome - 1] = 1
ll = log_loss_multiclass(predictions, y_onehot)
print(f"Multi-class log-loss: {ll:.4f}")
Notes¶
- Log-loss is the primary training objective for neural network and gradient boosting sports prediction models — it directly optimizes probability calibration
- For betting model evaluation: track both log-loss (penalizes overconfident wrong predictions) and Brier score (penalizes calibration errors)
- Log-loss is more volatile than Brier — small sample sizes (64 World Cup matches) make it less reliable as a standalone metric
- Use log-loss for model selection during training; use Brier score + CLV for final model evaluation
- The Expected Calibration Error (ECE) metric complements both: bucket predictions by probability range, compute weighted |avg_pred - empirical_freq|, sum