← All tutorials

Uncertainty Weighting

Uncertainty Weighting learns each task’s loss weight from how noisy the task is. Noisier tasks get smaller weights.

On this page

1 Hand-tuned loss weights 2 Loss weighting without gradients 3 Task uncertainty 4 The uncertainty-weighted loss 5 One training step 6 Learned uncertainties during training 7 Implementation in PyTorch 8 When weighting is enough 9 References

Uncertainty Weighting changes only the loss. Each task’s loss is multiplied by a weight that depends on a learned noise level σi, and a penalty term stops every noise level from growing without limit. Where GradNorm sets the weights from training speed, Uncertainty Weighting sets them from each task’s learned noise level.

1 Hand-tuned loss weights

The paper that introduced the method, by Kendall et al. (2018), tests it on scene understanding with Cityscapes, a dataset of street scenes. A regression task predicts a number or vector per pixel; a classification task picks a label per pixel.

camera imageshared networkDepthregression, distance per pixelInstance segmentationregression, vector to object centerSemantic segmentationclassification, label per pixel
Figure 1. Three losses in different units and scales share one network.

The paper shows that performance depends strongly on the loss weights, and a grid search over weights is expensive. Its answer is to learn one number per task, the task uncertainty, as a parameter during training, and derive the loss weight from it.

2 Loss weighting without gradients

Like GradNorm, Uncertainty Weighting is a loss-weighting method. Unlike GradNorm, it never measures a gradient. It treats each task’s output as a probability distribution over answers, and uses one rule from probabilistic modeling: a loss that is inherently noisier should count less.

3 Task uncertainty

Some tasks have answers that are fully determined by the input. Others have a floor of error that no model can beat, because the information is not in the image; exact per-pixel depth is an example. The paper models this with one noise level σi per task, the same for every input, called homoscedastic task uncertainty. A larger σi means a noisier task. Pushing hard on a noisy loss partly fits the noise, so the loss weight falls with 1/σi2 (1/(2σi2) for a regression task, Section 4).

Low-noise tasknoise 1×true valuerelative weight 1Noisy tasknoise 2×answersrelative weight ¼
Figure 2. Twice the noise σ gives a quarter of the weight, since the weight is 1/(2σ2).

4 The uncertainty-weighted loss

The running example has two tasks on NYUv2 indoor photos, seg (semantic segmentation, a classification task) and depth (meters per pixel, a regression task). Its numbers are illustrative.

segdepth
σi0.701.50
weight2.0410.222
loss Li1.20.9
weighted loss plus log σi2.0920.605
Each task learns its own noise level σ, a noisier task’s loss counts for less, and a log term stops σ from growing forever. The first line is for regression tasks, the second for classification tasks.
L = Σi (Li2σi² + log σi)
The total loss L sums over all K tasks, counted by i. Li is task i’s loss and σi (sigma) its learned noise level, so the loss weight is 12σi²: twice the noise, a quarter of the weight. On its own this term would push every σi to infinity, where every loss costs nothing. The penalty log σi grows with σi and stops that; below σi = 1 it is negative. For this regression line the two terms balance where σi² equals Li, so the learned noise level follows the size of the task’s loss.
L ≈ Σi (Liσi² + log σi)
A classification task has no factor 2 in the denominator, so at the same σi its loss weighs twice as much as a regression loss. The ≈ is there because the paper derives this line with an approximation that is exact at σi = 1. The penalty is the same, so without the 2 the two terms balance where σi² equals 2Li.
So the loss weights itself by how noisy each task is: seg’s σ of 0.70 gives it weight 2.041, and depth’s 1.50 gives it 0.222. Training then moves seg’s σ toward its balance near 1.55, where σ² = 2 × 1.2, so seg’s weight falls, and depth’s toward about 0.95, where σ² = 0.9.

Share of the weighted losses, log σ penalties left out

Semantics, σ = 0.70, Instance, σ = 1.50, Depth Total loss L (every loss 1):

Figure 3. Depth’s σ moves the bar (each task’s share) and the curve (total loss L).

Figure 3 uses the paper’s three Cityscapes tasks with illustrative noise levels, not the running example; depth’s σ starts at 2.40, a weight of 0.087.

5 One training step

The weighted losses and penalties are summed into one total, and one backward pass gives gradients to the network and the noise levels together (code in Section 7).

The walkthrough in Figure 4 uses the paper’s three Cityscapes tasks with every loss at 1.00 and illustrative noise levels; the penalties log σ are −0.36, 0.41 and 0.88.

taskσweight
semantics0.702.04
instance1.500.22
depth2.400.09

Figure 4. One step on three Cityscapes tasks with illustrative noise levels.

One training step adds a weighted loss and a log penalty per task, then moves both the network and each σ.
Each bracket is one task’s term of the uncertainty-weighted loss in Section 4. Semantics is classification, so its loss is divided by σsem²; instance and depth are regression, so theirs are divided by 2σ². A larger σ gives a smaller weight, and the penalty log σ grows with σ, so σ rises only when that lowers the total.
So each training step updates every task’s weight from its noise level. Each σ moves by gradient descent on its own bracket, toward the value where that bracket’s slope is zero. Semantics has loss 1.00 and σ = 0.70, below its balance where σ² = 2 × 1.00, so a descent step raises σsem and its weight 2.04 falls. Instance at σ = 1.50 and depth at 2.40 sit above their balance where σ² = 1.00, so their σ falls and their weights 0.22 and 0.09 rise.
Lsem, σsem the semantics task’s loss and noise levelLi task i’s lossσi its learned noise levellog σi the penalty on a large σiL the total lossΣi sum over instance and depth

6 Learned uncertainties during training

Each σ2 falls steeply early in training and then drifts down slowly with its loss. The noisiest loss, instance segmentation, keeps by far the largest σ2.

Column (a): semantic segmentation loss on top and its learned task uncertainty below, over 100,000 training iterations
Figure 5. Semantics loss (top) and σ² (bottom).
Column (b): instance segmentation loss on top and its learned task uncertainty below, over 100,000 training iterations
Figure 6. Instance loss (top) and σ² (bottom).
Column (c): depth regression loss on top and its learned task uncertainty below, over 100,000 training iterations
Figure 7. Depth loss (top) and σ² (bottom).

Figures 5 to 7 are crops of the paper’s Figure 7 (arXiv:1705.07115), captioned “Learning task uncertainty. …”

instance segmentationabout 20depthabout 3semantic segmentationunder 0.10.010.1110100final σ², log scale
Figure 8. Final σ² per task, read from the axes of the paper’s Figure 7.

7 Implementation in PyTorch

The running example again, written with s = log σ²; each value follows from its σ (0.70 for seg, 1.50 for depth) by this section’s formulas.

segdepth
s = log σ²log 0.49 = −0.713log 2.25 = 0.811
weight from se0.713 = 2.041e−0.811 ÷ 2 = 0.222
The code learns s = log σ² instead of σ, so the weight stays positive and training stays stable; both terms of the loss of Section 4 rewrite in si.
si = log σi²
This keeps the σ steps of Section 5 stable. si is the log of the squared noise level. The paper learns it because this is “more numerically stable” than learning σi and avoids division by zero: any real si gives a positive σi².
12σi² = e−si2
The regression weight is exp(-s) divided by 2. In the code, 1 + REG supplies the 2 for regression tasks only; REG is written for this page’s three tasks (semantics, instance, depth, in that order) and marks the regression ones.
log σi = si2
The penalty is s / 2 in the code. A larger start value of s starts every weight smaller, and a smaller one starts them larger; the paper found the end result insensitive to it.
So the code’s two terms are the loss of Section 4 rewritten in s: seg’s s of −0.713 gives back its weight 2.041 and depth’s 0.811 gives 0.222. In the code, seg is the semantics row (REG 0) and depth the depth row (REG 1).

The code runs once per training step on that step’s batch, with losses as batch means, as in the paper.

import torch
import torch.nn as nn
from torch.optim import Adam

# s[i] = log sigma_i^2
s = nn.Parameter(torch.zeros(K))
params = list(model.parameters())
params.append(s)
opt = Adam(params, lr=lr)
# 1 marks a regression task
REG = torch.tensor([0., 1., 1.])

def uw_step(losses):
    L = torch.stack(losses)
    w = torch.exp(-s) / (1 + REG)
    total = (w * L + s / 2).sum()
    opt.zero_grad()
    total.backward()
    opt.step()
  1. 1The state kept across steps is K numbers, one si per task, trained by the same optimizer as the network. Leave s out of the optimizer and the weights never change.
  2. 2Marks which tasks use the regression line of the loss in Section 4.
  3. 3Weighted losses plus penalties, a few extra operations on K numbers: line 1 of that loss for regression tasks, line 2 for classification. Each σi² settles near its task’s loss for a regression task and near twice it for a classification task, so a summed loss would make σ grow with the batch size. The total goes negative only once the losses are small: at balance a regression term is ½ + ½ ln L, which is below zero only when L is under 0.37 (e to the power −1).
  4. 4One backward pass and one step for the network and s together, step 4 in Figure 4, with no extra backward pass. The weight multiplies the whole task loss, so the heads also get gradients scaled by it (Section 3 of Gradient Balancing Inside the Training Loop compares methods that edit only the shared gradients).
SettingSource value
Start value of s0 (σ = 1); the noise levels settle early in training
Learning rate for sThe paper uses no separate rate and trains everything with SGD at 2.5×10−3 with polynomial decay (Appendix A); the GradNorm paper used 0.025 with Adam for its uncertainty-weighting baseline
Training length50,000 iterations at 128×256 for the comparison runs; about 100,000 iterations in the runs of Figure 5, read from its axis
Final loss weightsAbout 87 : 1 : 0.16 for semantics, depth and instance under this page’s loss (full-size model); the paper reports 43 : 1 : 0.16 from its σ2 values 0.075, 3.25 and 20.4, using 1/σ2 for every task

There is no official code. LibMTL ships a version (--weighting UW) that uses the same s = log σ2 form with the regression factor ½ for every task.

1 · Forwardeach task computes its loss2 · Backwardper-task gradients, shared layers3 · Balancecombine the task gradients4 · Clipcap the gradient's length5 · Optimizer stepupdate the network's parameters6 · Logrecord what this step didloss-weighting methods act heregradient methods act here
Figure 9. Weights set in the loss reach every gradient after backward.

The paper uses weight decay 10−4 and does not say whether s is excluded. Decay pulls s toward 0, which pulls every weight toward its start value. Gradient Balancing Inside the Training Loop covers how the noise levels interact with the optimizer in Sections 9.2 and 9.3.

8 When weighting is enough

GradNorm and Uncertainty Weighting both change only the loss weights. Both beat fixed weights in their papers.

PaperComparisonResult
Uncertainty WeightingThree Cityscapes tasks at 128×256, against equal weights and the best fixed weights from a grid searchBetter on all three tasks
GradNormNYUv2, against 100 networks with random fixed weightsThe best of the 100 fell short of GradNorm at the same training step

Because it changes only the loss weights, Uncertainty Weighting can be combined with a gradient method. The weights handle scale; a method such as PCGrad or CAGrad then handles direction.

Neither weighting method fixes conflicting gradients, gradients that point against each other. Uncertainty Weighting never looks at gradients, and GradNorm measures only their norms.

Two methods learn each task’s loss weight (GradNorm and Uncertainty Weighting). Four methods edit the task gradients (PCGrad, GradVac, CAGrad and MGDA).

MethodThe question it answers
GradNormHow much weight should each task's loss get so all tasks train at a similar rate? (learned during training from how fast each loss falls)
Uncertainty Weighting (this page)How much weight should each task's loss get, given how noisy that task's targets are? (the noise level is learned along with the model)
PCGradHow should a task gradient be edited when it conflicts with another task's gradient?
GradVacHow should each pair of task gradients be moved toward a target cosine, a running average of that pair's past cosines?
CAGradHow can a step raise the smallest per-task improvement while staying near the average gradient?
MGDAWhich convex combination of task gradients lowers every task's loss at once, and when does no such combination exist?

When the weights are right but the gradients still conflict, PCGrad edits the directions.

9 References

  1. Kendall, A., et al. (2018). Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics. CVPR. arXiv:1705.07115.
  2. Silberman, N., et al. (2012). Indoor Segmentation and Support Inference from RGBD Images. ECCV.
  3. Chen, Z., et al. (2018). GradNorm: Gradient Normalization for Adaptive Loss Balancing in Deep Multitask Networks. ICML. arXiv:1711.02257.