Temperatur und Sampling: warum dieselbe Frage jedes Mal anders beantwortet wird
Die echten Kandidaten-Wahrscheinlichkeiten eines Modells holen, Temperatur und top_p-Sampling mit numpy selbst implementieren, dann per API messen, wie die Temperatur die Vielfalt der Antworten beeinflusst, und erklären, warum auch Temperatur 0 keine identischen Ergebnisse garantiert.
- Etwa 40 Minuten
- Niveau: Einsteiger
- Getestet: 2026-09-14 deepseek-flash, numpy 2.5
Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.
Stellt man einem Sprachmodell dieselbe Frage zweimal, antwortet es oft unterschiedlich. Beim Schreiben von Werbetexten ist das gut: Du kannst dir ein paar Versionen geben lassen und eine auswählen. Baust du aber ein Programm, das „aus einem Vertrag den Namen der Partei A extrahiert“, und es liefert heute „某某有限公司“ (Soundso GmbH) und morgen „某某公司“ (Soundso-Firma), ist das sehr lästig.
Der Regler dafür heißt Temperatur (temperature). Diese Lektion klärt zuerst, woher die Zufälligkeit kommt, implementiert dann die Temperatur selbst im Code und zeigt schließlich mit echten API-Aufrufen ihre Wirkung.
Woher die Zufälligkeit kommt
Wie die letzte Lektion gezeigt hat, liefert das Modell bei jedem Schritt eine Wahrscheinlichkeitsverteilung: wie wahrscheinlich das nächste Token „爬山“ (wandern) ist, wie wahrscheinlich „图书馆“ (die Bibliothek) … Dann zieht ein Programm namens Sampler nach dieser Verteilung ein Token. Das Modell selbst berechnet jedes Mal fast dieselbe Verteilung; die Zufälligkeit steckt im Losziehen.
Die Verteilung kann man direkt sehen. Lass deepseek-flash „周末我打算去“ (am Wochenende habe ich vor, … zu gehen) fortsetzen und schalte den Parameter logprobs ein (wie das geht, zeigte die letzte Lektion); die Top-10-Kandidaten für das erste Token sind:
| Kandidat | Wahrscheinlichkeit |
|---|---|
| 爬山 (wandern) | 68,8 % |
| 图书馆 (in die Bibliothek) | 14,9 % |
| 公园 (in den Park) | 8,0 % |
| 山里 (in die Berge) | 2,4 % |
| 郊 („Vor-“ wie in Vorort) | 1,6 % |
| 超市 (in den Supermarkt) | 1,6 % |
| 逛街 (bummeln) | 1,1 % |
| 逛 (schlendern) | 0,6 % |
| 书店 (in die Buchhandlung) | 0,3 % |
| 露营 (zelten) | 0,1 % |
Üblich ist nicht, immer den wahrscheinlichsten Kandidaten zu nehmen, sondern nach Wahrscheinlichkeit zu ziehen: in 68,8 % der Fälle „爬山“, in 14,9 % „图书馆“ und gelegentlich auch „露营“.
Bei meinen Experimenten hatte ich einen sehr aufschlussreichen Fall. Beim Fortsetzen von „我今天中午吃了“ (heute Mittag habe ich … gegessen) war „一碗“ (eine Schüssel) mit 70,8 % der wahrscheinlichste Kandidat für das erste Token, danach „我今天“ (ich heute) mit 27,8 %. Ausgerechnet der zweite wurde gezogen, und die ganze Antwort wurde zu einer Wiederholung des ursprünglichen Satzes. Wurde einmal ein ungünstiges Token gezogen, bauen alle folgenden Schritte darauf auf, und die Antwort läuft in eine andere Richtung. Deshalb hilft es oft, eine schwache Antwort einfach neu erzeugen zu lassen.
Was die Temperatur tut
Die Temperatur macht die Verteilung vor dem Ziehen spitzer oder flacher.
Konkret: Man nimmt den Logarithmus jeder Kandidaten-Wahrscheinlichkeit, teilt durch die Temperatur und macht daraus wieder Wahrscheinlichkeiten (dieser Schritt heißt softmax). Als Formel neue_Wahrscheinlichkeit_i ∝ exp(log(p_i) / T), was dasselbe ist wie p_i hoch 1/T und dann normieren.
- Temperatur unter 1: Große Wahrscheinlichkeiten werden größer, kleine kleiner, die Verteilung wird spitzer, das Ergebnis stabiler.
- Temperatur gleich 1: Die Verteilung bleibt unverändert.
- Temperatur über 1: Die Verteilung wird flacher, hintere Kandidaten bekommen mehr Chancen, das Ergebnis wird vielfältiger, aber auch leichter abwegig.
- Temperatur gegen 0: Es wird immer der wahrscheinlichste Kandidat gewählt; das nennt man gierige Dekodierung.
Mit der echten Verteilung von oben einmal selbst ausprobieren:
import numpy as np
candidates = ["爬山", "图书馆", "公园", "山里", "郊", "超市", "逛街", "逛", "书店", "露营"]
probs = np.array([0.6879, 0.1489, 0.0797, 0.0244, 0.0158, 0.0157, 0.0107, 0.0062, 0.0029, 0.0013])
probs = probs / probs.sum() # 只取了前 10 个,重新归一化让它们加起来等于 1
def apply_temperature(p, t):
# 温度作用在对数概率上:先取对数,除以温度,再变回概率(softmax)
logits = np.log(p) / t
e = np.exp(logits - logits.max()) # 减去最大值是为了防止 exp 溢出,不影响结果
return e / e.sum()
def show(title, p, n=1000, seed=0):
rng = np.random.default_rng(seed)
draws = rng.choice(len(p), size=n, p=p)
counts = np.bincount(draws, minlength=len(p))
print(title)
for word, prob, count in zip(candidates, p, counts):
if prob > 0.0005 or count:
print(f" {word: <4} 概率 {prob:6.1%} 抽中 {count:4d} 次")
print()
show("原始分布(温度 1.0),抽 1000 次:", probs)
show("温度 0.5:", apply_temperature(probs, 0.5))
show("温度 1.5:", apply_temperature(probs, 1.5))
Ergebnis:
原始分布(温度 1.0),抽 1000 次:
爬山 概率 69.2% 抽中 673 次
图书馆 概率 15.0% 抽中 166 次
公园 概率 8.0% 抽中 83 次
山里 概率 2.5% 抽中 29 次
郊 概率 1.6% 抽中 11 次
超市 概率 1.6% 抽中 15 次
逛街 概率 1.1% 抽中 12 次
逛 概率 0.6% 抽中 8 次
书店 概率 0.3% 抽中 2 次
露营 概率 0.1% 抽中 1 次
温度 0.5:
爬山 概率 94.1% 抽中 944 次
图书馆 概率 4.4% 抽中 41 次
公园 概率 1.3% 抽中 14 次
山里 概率 0.1% 抽中 0 次
超市 概率 0.0% 抽中 1 次
温度 1.5:
爬山 概率 49.6% 抽中 467 次
图书馆 概率 17.9% 抽中 188 次
公园 概率 11.8% 抽中 127 次
山里 概率 5.4% 抽中 64 次
郊 概率 4.0% 抽中 40 次
超市 概率 4.0% 抽中 40 次
逛街 概率 3.1% 抽中 30 次
逛 概率 2.1% 抽中 21 次
书店 概率 1.3% 抽中 16 次
露营 概率 0.8% 抽中 7 次
Bei Temperatur 0,5 steigt „爬山“ von 69 % auf 94 %, die hinteren Kandidaten haben praktisch keine Chance mehr. Bei Temperatur 1,5 fällt „爬山“ auf etwa die Hälfte, und „露营“ wird statt einmal nun siebenmal von 1000 gezogen.
Beachte, dass es hier nur um das erste Token geht. Eine Antwort hat Dutzende bis Hunderte Tokens, und bei jedem Schritt wird so gezogen. Die Unterschiede pro Schritt summieren sich; bei hoher Temperatur unterscheiden sich ganze Antworten stark.
top_p: den langen Schwanz abschneiden
Ein weiterer gängiger Parameter ist top_p, auch Nucleus Sampling genannt. Er ändert nicht das Verhältnis der Wahrscheinlichkeiten, sondern sortiert die Kandidaten absteigend, summiert von oben, hört bei top_p auf, verwirft den Rest und zieht aus dem, was übrig bleibt.
def top_p_filter(p, top_p):
order = np.argsort(p)[::-1]
cumulative = np.cumsum(p[order])
# 保留累计概率刚好达到 top_p 的那几个,其余的概率清零
keep = order[: np.searchsorted(cumulative, top_p) + 1]
q = np.zeros_like(p)
q[keep] = p[keep]
return q / q.sum()
show("温度 1.0 + top_p 0.9:", top_p_filter(probs, 0.9))
温度 1.0 + top_p 0.9:
爬山 概率 75.1% 抽中 733 次
图书馆 概率 16.2% 抽中 183 次
公园 概率 8.7% 抽中 84 次
Die ersten drei Kandidaten ergeben zusammen knapp über 90 %, also wird nur unter ihnen gezogen; „露营“ und „超市“ tauchen nie mehr auf. Der Vorteil: Tokens mit sehr geringer Wahrscheinlichkeit, die eine Antwort entgleisen lassen könnten, wenn sie gezogen werden, werden ausgeschlossen.
Temperatur und top_p verstellt man meist nicht beide; stellt man an beiden, lässt sich schwer sagen, welcher Regler wirkt.
Mit der API messen
Oben war lokale Simulation. Wie ist es bei echten Aufrufen? Ich habe deepseek-flash einen bildhaften Vergleich über den Herbst schreiben lassen, bei drei Temperaturen je 20-mal:
import os
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL", "https://api.deepseek.com"),
)
MODEL = os.environ.get("LLM_MODEL", "deepseek-flash")
QUESTION = "用一句话写一个关于秋天的比喻,不超过十五个字,只输出这句话。"
def ask(temperature):
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": QUESTION}],
temperature=temperature,
# 思考模式下 temperature 不起作用,所以要关掉思考
extra_body={"thinking": {"type": "disabled"}},
)
return response.choices[0].message.content.strip()
for t in [0, 0.7, 1.3]:
# 10 个线程同时发请求,省点时间
with ThreadPoolExecutor(10) as pool:
answers = list(pool.map(ask, [t] * 20))
counts = Counter(answers)
print(f"temperature={t}: 20 次里有 {len(counts)} 种")
for text, n in counts.most_common(5):
print(f" {n:2d}× {text}")
Mein Ergebnis (deins wird anders sein):
temperature=0: 20 次里有 2 种
11× 秋天像一封写满离别的信。
9× 秋天像一封缓缓飘落的信。
temperature=0.7: 20 次里有 6 种
10× 秋天像一封写满离别的信。
4× 秋天像一封慢慢变黄的信。
3× 秋天像一封缓缓飘落的信。
1× 秋天像一封缓缓拆开的旧信。
1× 秋天像一封缓缓展开的旧信。
temperature=1.3: 20 次里有 15 种
4× 秋天像一封缓缓飘落的信。
2× 秋天像一封写满离别的信。
2× 秋天像一封慢慢变黄的信。
1× 秋天像一封写给大地的金色信笺。
1× 秋天是把金色小提琴,风一拉就落叶。
(Angezeigt sind je Temperatur nur die fünf häufigsten.)
Von Temperatur 0 bis 1,3 steigt die Zahl verschiedener Antworten unter 20 von 2 auf 15. Bei 1,3 taucht „秋天是把金色小提琴,风一拉就落叶“ (der Herbst ist eine goldene Geige; streicht der Wind, fallen die Blätter) auf, eine Formulierung, die vorher nie vorkam.
Zwei Beobachtungen verdienen eine Erwähnung.
Auch bei Temperatur 0 ist das Ergebnis nicht jedes Mal gleich. Theoretisch müsste Temperatur 0 immer das wahrscheinlichste Token wählen und ein völlig festes Ergebnis liefern. Tatsächlich gab es unter 20 Antworten zwei Varianten. Der Grund liegt beim Server: Deine Anfrage wird zusammen mit denen anderer im Batch berechnet, und je nach Reihenfolge der Gleitkommaoperationen auf der GPU ergeben sich winzige Unterschiede. Meist spielt das keine Rolle, aber wenn zwei Kandidaten fast gleich wahrscheinlich sind („写满离别的“ voller Abschied und „缓缓飘落的“ langsam herabschwebend waren wohl so ein Fall), reicht dieser Unterschied, um die Wahl umzukehren. Sobald bei einem Schritt ein anderes Token gewählt wird, ist alles Folgende anders. Verlass dich also nicht darauf, dass Temperatur 0 hundertprozentig reproduzierbar ist. Programme, die stabile Ausgaben brauchen, müssen im Programm prüfen, statt darauf zu wetten, dass das Modell jedes Mal gleich antwortet.
Wenn das Modell sehr sicher ist, hilft auch eine höhere Temperatur nicht. In einem anderen Versuch sollte das Modell „einem kleinen Laden für handgemachten Kaffee einen Namen geben“; bei Temperatur 1,5 fragte ich fünfmal, fünfmal kam „豆语咖啡“ (Bohnensprache-Kaffee). Meine Vermutung: Das erste Token „豆语“ hat schon eine Wahrscheinlichkeit nahe 100 %, und selbst bei flacherer Verteilung bleibt es weit vorn. Willst du vielfältige Ergebnisse, bitte im Prompt lieber direkt um „10 Namen in unterschiedlichen Stilen“, statt die Temperatur hochzudrehen.
Im Denkmodus wirkt die Temperatur nicht
Die Dokumentation von DeepSeek sagt klar: Im Denkmodus hat der Parameter temperature keine Wirkung (setzen gibt keinen Fehler, er wird nur ignoriert), und top_p-Werte unter 0,95 werden automatisch auf 0,95 angehoben. Deshalb ist in allen Codebeispielen oben das Denken ausgeschaltet.
Heißt: Wer die Ausgabe über die Temperatur steuern will, muss den Denkmodus ausschalten. Umgekehrt: Mit eingeschaltetem Denken darfst du nicht erwarten, dass eine niedrigere Temperatur die Ausgabe stabiler macht.
Welcher Wert
Die offiziellen Empfehlungen von DeepSeek (Stand September 2026):
| Anwendung | Temperatur |
|---|---|
| Programmieren, Matheaufgaben | 0,0 |
| Datenbereinigung, Datenanalyse | 1,0 |
| Alltagsgespräch | 1,3 |
| Übersetzen | 1,3 |
| Kreatives Schreiben, Gedichte | 1,5 |
Der Standard bei DeepSeek ist 1,0. Die Tabelle gilt nur für DeepSeek; Modelle anderer Anbieter reagieren unterschiedlich empfindlich auf die Temperatur, dieselbe Zahl kann ganz anders wirken.
Ich mache es so: Für Aufgaben mit eindeutiger Lösung wie Extrahieren, Klassifizieren oder Umformatieren Temperatur 0; für Schreiben, Namensfindung und Brainstorming den Standard oder höher; im Zweifel erst den Standard und nur bei Unzufriedenheit nachjustieren.
Übungen
- Setz die Temperatur in
sampling.pyauf 0,1 und 3,0 und schau, wie die Stichprobe aussieht. Wie oft wird „露营“ bei 3,0 gezogen? - Setz
top_pauf 0,7 und 0,99 und schau, wie viele Kandidaten übrig bleiben. - Nimm in
temperature_api.pyeine andere Aufgabe: das Modell soll „The quick brown fox jumps over the lazy dog“ ins Chinesische übersetzen, je 20-mal bei Temperatur 0 und 1,3; zähl die verschiedenen Übersetzungen. Ist der Einfluss der Temperatur beim Übersetzen größer oder kleiner als beim bildhaften Vergleich? Überleg, warum. - Schau mit dem
logprobs-Parameter aus der letzten Lektion nach, welche Kandidaten und Wahrscheinlichkeiten das erste Token hat, wenn das Modell „einem kleinen Laden für handgemachten Kaffee einen Namen geben“ soll. Bestätigt das meine Vermutung zum „豆语咖啡“-Phänomen?
Selbsttest
1. Warum werden die Antworten des Modells bei niedrigerer Temperatur stabiler?
Die Temperatur wirkt auf die Kandidatenverteilung jedes Schritts: Je niedriger sie ist, desto größer werden hohe Wahrscheinlichkeiten und desto kleiner niedrige, die Verteilung wird „spitzer“. Beim Ziehen trifft es fast immer dasselbe Token, die Antwort wird also stabiler. Geht die Temperatur gegen 0, wird bei jedem Schritt der wahrscheinlichste Kandidat gewählt.
2. Du hast die Temperatur auf 0 gesetzt und bei 20 Wiederholungen derselben Frage trotzdem zwei verschiedene Antworten bekommen. Ist das normal?
Ja. Wenn der Server Anfragen im Batch verarbeitet, ändert sich die Reihenfolge der Gleitkommaoperationen, was winzige Unterschiede in den Wahrscheinlichkeiten erzeugt. Liegen zwei Kandidaten sehr nah beieinander, genügt das, um die Wahl umzukehren, und alle folgenden Ausgaben unterscheiden sich. Temperatur 0 garantiert daher keine vollständig reproduzierbaren Ergebnisse.
3. Bei DeepSeek mit eingeschaltetem Denkmodus hast du die Temperatur von 1,0 auf 0 gesenkt, aber die Ausgabe wurde nicht stabiler. Warum?
DeepSeek ignoriert den Parameter temperature im Denkmodus; er hat keine Wirkung. Um die Ausgabe über die Temperatur zu steuern, musst du das Denken mit extra_body={"thinking": {"type": "disabled"}} ausschalten.