Index Ai Academy
Phase 2 · Math, when you need it · Information

Softmax

Turn any list of scores into probabilities.

About 25 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

A model often ends with a list of raw scores, one per option. A review sorter might give "great" 2.1, "good" 1.3 and "bad" −0.4. Those scores aren't probabilities yet. One is negative, and they don't add up to anything useful. Softmax turns them into probabilities. Every one is positive, and together they add up to 1. Softmax sits at the end of every language model, which gives one score to each token it knows. It also sits inside every transformer, where it decides how much attention each word pays to the others. The temperature setting in AI chat tools is a dial on this exact function.

What you'll be able to do

  • Turn a list of scores (logits) into probabilities with softmax
  • Implement softmax so it never overflows
  • Explain how temperature sharpens or flattens the distribution
  • Recognize softmax at the output of classifiers, language models and attention

What it covers

  1. From scores to probabilities
  2. Worked example: three scores by hand
  3. Only the gaps matter
  4. The overflow trap and the max trick
  5. Temperature
  6. Where softmax appears

From scores to probabilities

Picture a talent show with four acts. The judges give each act a score. Some scores are high, and one is below zero. Now you want to say how likely each act is to win. You need numbers that are all positive and that add up to 1 (that is, 100%).

Dividing each score by the total doesn't work. A negative score would give a negative chance, which makes no sense. Softmax fixes this in two steps:

  1. Make every score positive. Raise the number ee (about 2.718) to the power of each score. You met ee in the exponentials lesson. A power of ee is always positive, and a bigger score always gives a bigger result.
  2. Divide by the total. Add up all the results. Then divide each one by that total. Now they add up to 1.

The raw scores going in have a name: logits. A model's last layer produces logits, and softmax turns them into probabilities.

The formula

Write the list of scores as z1,z2,…z_1, z_2, \ldots. Each zz is one score. The small number beside it says which option the score belongs to. The letter KK is how many options there are, so the last score is zKz_K. The bold z\mathbf{z} means the whole list at once. The symbol Σ\Sigma is a capital Greek sigma. It means "add these all up".

softmax⁡(z)i=ezi∑j=1Kezj\operatorname{softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}

Here ii is the option you're asking about. The letter jj counts through every option, from 1 up to KK, to build the total.

In plain words: the probability of option ii is ee to the power of its score, divided by the total of ee to the power of every score.

In NumPy, np.exp(z) does step 1 for the whole list at once. Then .sum() gives the total for step 2.

import numpy as np

z = np.array([2.0, 1.0, 0.1, -1.0])
p = np.exp(z) / np.exp(z).sum()
print(p.round(3), p.sum())
It runs right here in your browser, with nothing to install.

The four scores become 0.638, 0.235, 0.095 and 0.032. The second number printed is their total. It comes to 1, give or take a tiny rounding error in the last digit.

Four things are always true about the result:

  • Every output is between 0 and 1, and they all add up to 1. That makes a valid set of probabilities, called a distribution.
  • The order stays the same. The highest score gets the highest probability.
  • It's soft. The winner doesn't take everything. The bigger the gaps between scores, the more lopsided the probabilities. (The name means a soft version of picking the top score.)
  • Only the gaps between scores matter. Add the same number to every score and nothing changes. You'll see why below.
Quick check
A model gives the scores [5.0, 2.0, -1.0]. Which statement about the softmax output is true?
  1. The third option gets a negative probability
  2. The first option gets a probability of exactly 1
  3. The three probabilities add up to 6
  4. The first option gets the highest probability, and the three add up to 1
Show the answer

The first option gets the highest probability, and the three add up to 1

Softmax keeps the order and always gives positive numbers that add up to 1. The first option gets most of the probability, but not all of it.

Worked example

Worked example: three scores by hand

A model choosing the next word gives three scores: "cat" 2, "dog" 1 and "car" 0. Turn them into probabilities, one step at a time.

import math

scores = [2.0, 1.0, 0.0]                # cat, dog, car
powers = [math.exp(z) for z in scores]  # step 1: e to the power of each score
total = sum(powers)                     # step 2: add them up
probs = [p / total for p in powers]     # step 3: divide by the total
print([round(p, 3) for p in powers], round(total, 3))
print([round(p, 3) for p in probs])
In the lesson you can change this and run it.

Step 1: e² is about 7.389, e¹ is 2.718 and e⁰ is 1. All three are positive. Step 2: the total is 11.107. That sum(...) line is the Σ in the formula. Step 3: 7.389 ÷ 11.107 is 0.665, so "cat" gets about 66.5%. "dog" gets 24.5% and "car" gets 9%. Together they make 1.

Look at the gaps. "cat" scored only 1 more than "dog", but its probability is almost three times bigger. Each extra point of score multiplies the share by about 2.718.

Try it now

Drag a score up and watch probability flow toward it from the others. Then move the temperature.

Only the gaps matter

Shift every score by the same amount and softmax gives the same answer. The scores [1, 3] and [11, 13] both give 0.119 and 0.881.

Think of a race. If every runner's time had 10 seconds added, the gaps between them would stay the same. So would the order they finished in. Softmax only looks at those gaps.

Go deeper Why adding a number cancels out

Call the number you add cc. Adding it to a score multiplies that score's power of ee by ece^c:

ez+c=ec⋅eze^{z + c} = e^c \cdot e^{z}

Read it as: "ee to the power zz plus cc equals ee to the cc, times ee to the zz." Every power of ee gets multiplied by the same ece^c. That includes the one on top of the fraction and every one in the total underneath. So ece^c appears on the top and the bottom, and it cancels. For example, adding 10 multiplies every power by e10e^{10}, about 22,026, top and bottom alike.

Quick check
What does this print?
import math
def softmax(xs):
    e = [math.exp(x) for x in xs]
    return [round(v / sum(e), 3) for v in e]
print(softmax([1, 3]) == softmax([11, 13]))
  1. False
  2. None
  3. True
  4. 2
Show the answer

True

softmax([11, 13]) has every score 10 higher than softmax([1, 3]), and that cancels in the division, so the lists match and == gives True. Adding the same number to every score changes nothing. Only the gaps matter.

Quick check
You add 10 to every logit before applying softmax. What happens to the probabilities?
  1. They all increase
  2. The largest one becomes 1
  3. They no longer sum to 1
  4. Nothing: softmax only depends on differences between logits
Show the answer

Nothing: softmax only depends on differences between logits

e^(z+10) = e^10 · e^z, and the e^10 cancels in the ratio.

The overflow trap and the max trick

A computer stores each number in a fixed amount of space. e1000e^{1000} is far too big to fit. It overflows: the computer gives up and calls it infinity. Real logits can be large, so this does happen. Infinity divided by infinity isn't a number, so NumPy prints nan ("not a number").

The fix uses the rule you just saw. Only the gaps matter, so subtract the largest score from every score first. The biggest score becomes 0, and e0=1e^0 = 1. Every other score becomes negative. ee to a negative power is a small number between 0 and 1. Nothing overflows, and the answer is unchanged.

import numpy as np

z = np.array([1000.0, 1001.0, 1002.0])
naive = np.exp(z) / np.exp(z).sum()          # overflow: inf / inf
stable = np.exp(z - z.max()) / np.exp(z - z.max()).sum()
print("naive: ", naive)
print("stable:", stable.round(4))

The naive line gives nan for every option. NumPy may also print an overflow warning. The stable line gives 0.09, 0.2447 and 0.6652. Those are the probabilities for the scores [0, 1, 2]. They're also the worked example's numbers in reverse order. Every real softmax implementation uses this trick.

Quick check
You use the max trick on the scores [5, 8, 6]. Which numbers go into the exponential (e to the power)?
  1. [0, 3, 1]
  2. [-8, -8, -8]
  3. [5, 8, 6]
  4. [-3, 0, -2]
Show the answer

[-3, 0, -2]

Subtract the largest score, 8, from each one: 5 - 8 = -3, 8 - 8 = 0 and 6 - 8 = -2. The biggest power is then e^0 = 1, so nothing overflows.

Temperature

Temperature is a dial for how bold the choices are. You divide every score by a number TT, the temperature, before softmax.

Dividing by a number below 1 spreads the scores further apart. Dividing by a number above 1 squeezes them together. And bigger gaps give more lopsided probabilities.

pi=ezi/T∑jezj/Tp_i = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}

Here pip_i is the probability of option ii.

In plain words: divide every score by the temperature, then do softmax as usual.

Take the worked example's scores [2, 1, 0] again:

  • T=1T = 1 leaves the scores alone. The probabilities are 0.665, 0.245 and 0.090.
  • T=0.5T = 0.5 doubles the scores to [4, 2, 0]. The gaps grow, and "cat" jumps to 0.867.
  • T=2T = 2 halves the scores to [1, 0.5, 0]. The gaps shrink, giving 0.506, 0.307 and 0.186.

So the general rule is:

  • TT below 1 stretches the gaps. The top option gets even more probability. The output is sharper and more predictable.
  • TT above 1 shrinks the gaps. The probabilities even out. The output is flatter and more varied.
  • As TT gets close to 0, the top option takes nearly all the probability. That comes close to always picking the top option, which is called greedy choice.

Temperature never changes the order. The top score stays on top at every temperature.

The code below defines a reusable softmax. It applies the temperature and the max trick.

import numpy as np

def softmax(z, T=1.0):
    s = np.asarray(z) / T
    e = np.exp(s - s.max())
    return e / e.sum()

z = [2.0, 1.0, 0.1, -1.0]
for T in [0.3, 1.0, 3.0]:
    print(f"T={T}: {softmax(z, T).round(3)}")

At T=0.3 the top option gets 0.964. At T=3 the four probabilities sit much closer together: 0.382, 0.274, 0.203 and 0.141.

Quick check
You lower the temperature from 1 to 0.3 for the scores [2, 1, 0.1, -1]. What happens?
  1. The top option's probability rises from about 0.64 to about 0.96
  2. The order of the options changes
  3. The probabilities no longer add up to 1
  4. Nothing: temperature only matters above 1
Show the answer

The top option's probability rises from about 0.64 to about 0.96

Dividing by 0.3 stretches the gaps, so the top option pulls further ahead. The order and the total of 1 never change.

Where softmax appears

Anywhere a model must share out 100% between options, you'll find softmax.

PlaceLogitsSoftmax gives
A classifier's outputone score per classP(class)
A language model's outputone score per vocabulary token (~100k)P(next token)
Attention in a transformerquery · key for each other tokenhow much to attend to each token

A classifier sorts things into fixed groups, such as "spam" or "not spam". A language model scores every token it knows to pick the next one. Attention comes in Phase 5. For now, know that each word gives every other word a score, and softmax turns those scores into shares of attention.

With only two options, softmax is the S-curve (the sigmoid) from the functions and graphs lesson. Give one option the score zz and the other the score 0. The first option's probability is then the sigmoid of zz. For z=2z = 2, both come to 0.881.

Go deeper Softmax with two options is the sigmoid

softmax⁡([z,0])1=ezez+1=σ(z)\operatorname{softmax}([z, 0])_1 = \frac{e^z}{e^z + 1} = \sigma(z)

Read it as: "with scores zz and 0, the first probability is ee to the zz, over ee to the zz plus 1. That equals the sigmoid of zz." The 1 is e0e^0, the second option's share. Divide the top and bottom by eze^z and you get 1/(1+e−z)1 / (1 + e^{-z}), the sigmoid's usual form.

import math

z = 2.0
print(round(math.exp(z) / (math.exp(z) + math.exp(0)), 3))
print(round(1 / (1 + math.exp(-z)), 3))

Both lines print 0.881.

Practice what you just read

  • 5 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

A chatbot's answers are repetitive and bland. Which temperature change makes them more varied?

  1. Raise the temperature above 1
  2. Lower it toward 0
  3. Set it to exactly 1
  4. Temperature doesn't affect variety

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson