Softmax
Turn any list of scores into probabilities.
Create a free account Try part of it here
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
- From scores to probabilities
- Worked example: three scores by hand
- Only the gaps matter
- The overflow trap and the max trick
- Temperature
- 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:
- Make every score positive. Raise the number (about 2.718) to the power of each score. You met in the exponentials lesson. A power of is always positive, and a bigger score always gives a bigger result.
- 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 . Each is one score. The small number beside it says which option the score belongs to. The letter is how many options there are, so the last score is . The bold means the whole list at once. The symbol is a capital Greek sigma. It means "add these all up".
Here is the option you're asking about. The letter counts through every option, from 1 up to , to build the total.
In plain words: the probability of option is to the power of its score, divided by the total of 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())
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.
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: 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])
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.
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 . Adding it to a score multiplies that score's power of by :
Read it as: " to the power plus equals to the , times to the ." Every power of gets multiplied by the same . That includes the one on top of the fraction and every one in the total underneath. So appears on the top and the bottom, and it cancels. For example, adding 10 multiplies every power by , about 22,026, top and bottom alike.
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]))
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.
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. 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 . Every other score becomes negative. 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.
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 , 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.
Here is the probability of option .
In plain words: divide every score by the temperature, then do softmax as usual.
Take the worked example's scores [2, 1, 0] again:
- leaves the scores alone. The probabilities are 0.665, 0.245 and 0.090.
- doubles the scores to [4, 2, 0]. The gaps grow, and "cat" jumps to 0.867.
- halves the scores to [1, 0.5, 0]. The gaps shrink, giving 0.506, 0.307 and 0.186.
So the general rule is:
- below 1 stretches the gaps. The top option gets even more probability. The output is sharper and more predictable.
- above 1 shrinks the gaps. The probabilities even out. The output is flatter and more varied.
- As 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.
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.
| Place | Logits | Softmax gives |
|---|---|---|
| A classifier's output | one score per class | P(class) |
| A language model's output | one score per vocabulary token (~100k) | P(next token) |
| Attention in a transformer | query · key for each other token | how 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 and the other the score 0. The first option's probability is then the sigmoid of . For , both come to 0.881.
Go deeper Softmax with two options is the sigmoid
Read it as: "with scores and 0, the first probability is to the , over to the plus 1. That equals the sigmoid of ." The 1 is , the second option's share. Divide the top and bottom by and you get , 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
Try a quiz question
A chatbot's answers are repetitive and bland. Which temperature change makes them more varied?