Température et échantillonnage : pourquoi la même question donne des réponses différentes
Récupérer les vraies probabilités des tokens candidats du modèle, implémenter soi-même la température et l'échantillonnage top_p avec numpy, mesurer avec l'API l'effet de la température sur la diversité des réponses, et expliquer pourquoi une température de 0 ne garantit pas non plus un résultat identique.
- Environ 40 minutes
- Niveau : Débutant
- Testé : 2026-09-14 deepseek-flash, numpy 2.5
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
Posez deux fois la même question à un grand modèle, et les réponses diffèrent souvent. Pour rédiger un texte publicitaire, c'est une bonne chose : vous pouvez demander plusieurs versions et choisir. Mais si vous écrivez un programme qui « extrait le nom de la partie A d'un contrat », et qu'il extrait aujourd'hui « Société XX SARL » et demain « Société XX », c'est très gênant.
Le bouton qui contrôle cela s'appelle la température (temperature). Cette leçon explique d'abord d'où vient l'aléatoire, puis implémente la température à la main, et enfin observe son effet avec de vrais appels d'API.
D'où vient l'aléatoire
La leçon précédente l'a montré : à chaque étape, le modèle fournit une distribution de probabilités, avec telle probabilité que le prochain token soit « 爬山 » (faire de la randonnée), telle probabilité qu'il soit « 图书馆 » (la bibliothèque)… puis un programme appelé échantillonneur tire un token selon cette distribution. Le modèle lui-même calcule presque la même distribution à chaque fois ; l'aléatoire vient de ce tirage au sort.
Cette distribution se voit directement. En demandant à deepseek-flash de continuer « 周末我打算去 » (ce week-end, je compte aller…) avec le paramètre logprobs activé (voir la leçon précédente), les 10 premiers candidats du premier token sont :
| Candidat | Probabilité |
|---|---|
| 爬山 (faire de la randonnée) | 68,8 % |
| 图书馆 (la bibliothèque) | 14,9 % |
| 公园 (le parc) | 8,0 % |
| 山里 (à la montagne) | 2,4 % |
| 郊 (ban-, début de « banlieue ») | 1,6 % |
| 超市 (le supermarché) | 1,6 % |
| 逛街 (faire les boutiques) | 1,1 % |
| 逛 (flâner) | 0,6 % |
| 书店 (la librairie) | 0,3 % |
| 露营 (camper) | 0,1 % |
La pratique la plus courante n'est pas de toujours choisir le plus probable, mais de tirer selon les probabilités : « 爬山 » dans 68,8 % des cas, « 图书馆 » dans 14,9 %, et parfois même « 露营 ».
Dans mes expériences, j'ai rencontré un cas très parlant. En demandant au modèle de continuer « 我今天中午吃了 » (à midi, j'ai mangé…), le premier token le plus probable était « 一碗 » (un bol de, 70,8 %), suivi de « 我今天 » (aujourd'hui je, 27,8 %). Cette fois-là, le tirage a justement donné le second, et toute la réponse a consisté à répéter la phrase d'origine. Un token peu judicieux tiré à une étape, chaque étape suivante construit dessus, et la réponse part dans une autre direction. C'est aussi pourquoi, quand une réponse est mauvaise, la régénérer suffit souvent.
Ce que fait la température
La température sert, avant le tirage, à rendre la distribution plus pointue ou plus plate.
Concrètement : on prend le logarithme de la probabilité de chaque candidat, on le divise par la température, puis on retransforme en probabilités (étape appelée softmax). En formule : nouvelle_proba_i ∝ exp(log(p_i) / T), ce qui revient à élever p_i à la puissance 1/T puis à normaliser.
- Température inférieure à 1 : les grandes probabilités grandissent, les petites rapetissent, la distribution devient pointue, le résultat plus stable.
- Température égale à 1 : la distribution ne change pas.
- Température supérieure à 1 : la distribution s'aplatit, les candidats mal classés ont plus de chances, le résultat est plus varié mais dérape plus facilement.
- Température tendant vers 0 : on choisit toujours le plus probable ; c'est le décodage glouton.
Faisons-le à la main avec la vraie distribution ci-dessus :
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))
Résultat :
原始分布(温度 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 次
À la température 0,5, « 爬山 » passe de 69 % à 94 %, et les candidats suivants n'ont presque plus aucune chance. À 1,5, « 爬山 » tombe à environ la moitié, et « 露营 » passe de 1 tirage sur 1000 à 7.
Attention : il ne s'agit ici que du premier token. Une réponse compte des dizaines ou des centaines de tokens, avec un tirage à chaque étape. Les différences s'accumulent, et à température élevée, deux réponses complètes peuvent être très différentes.
top_p : couper la longue traîne
Un autre paramètre courant est top_p, appelé aussi échantillonnage par noyau (nucleus sampling). Il ne change pas les probabilités relatives : il trie les candidats par probabilité décroissante, les additionne depuis le début, s'arrête dès que la somme atteint top_p, jette tout le reste, puis tire parmi ceux qui restent.
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 次
Les trois premiers candidats dépassent tout juste 90 % à eux trois ; on ne tire donc que parmi eux, et « 露营 » ou « 超市 » n'apparaissent plus jamais. L'avantage est de bloquer les tokens très improbables qui, une fois tirés, feraient dérailler la réponse.
En général, on ne règle que l'un des deux, température ou top_p ; en réglant les deux, on a du mal à savoir lequel a eu de l'effet.
Mesure avec l'API
Ce qui précède est une simulation locale. Qu'en est-il avec de vrais appels ? J'ai demandé à deepseek-flash d'écrire une métaphore sur l'automne, 20 fois pour chacune de trois températures :
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}")
Mon résultat (le vôtre sera différent) :
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× 秋天是把金色小提琴,风一拉就落叶。
(Seules les 5 réponses les plus fréquentes par température sont affichées.)
De la température 0 à 1,3, le nombre de réponses différentes sur 20 passe de 2 à 15. À 1,3 apparaissent des tournures inédites comme « 秋天是把金色小提琴,风一拉就落叶 » (l'automne est un violon doré ; dès que le vent joue, les feuilles tombent).
Deux phénomènes méritent d'être relevés.
Même à température 0, le résultat n'est pas toujours identique. En théorie, une température de 0 choisit toujours le token le plus probable, et le résultat est entièrement déterminé. En pratique, deux réponses différentes sont apparues sur 20. La cause est côté serveur : votre requête est calculée par lots avec celles d'autres utilisateurs, et selon l'ordre des opérations en virgule flottante sur le GPU, le résultat diffère très légèrement. D'ordinaire, peu importe ; mais quand deux candidats ont des probabilités très proches (c'est sans doute le cas entre « 写满离别的 » et « 缓缓飘落的 »), cette infime différence suffit à inverser le choix. Et dès qu'un token différent est choisi à une étape, toute la suite change. Ne comptez donc pas sur une température de 0 pour une reproductibilité à cent pour cent. Un programme qui a besoin d'une sortie stable doit la vérifier lui-même, plutôt que parier que le modèle répondra toujours pareil.
Quand le modèle est très sûr de lui, monter la température ne sert à rien. J'ai aussi essayé de faire « trouver un nom pour une petite boutique de café artisanal » : 5 fois à la température 1,5, 5 fois « 豆语咖啡 » (Café Parole de Grain). Mon hypothèse : la probabilité du premier token « 豆语 » est déjà proche de 100 %, et même en aplatissant la distribution, il garde une domination écrasante. Pour obtenir des résultats variés, plutôt que de monter la température, demandez directement dans le prompt « donne 10 noms de styles différents ».
En mode réflexion, la température n'a pas d'effet
La documentation de DeepSeek le précise : en mode réflexion, le paramètre temperature n'a aucun effet (le définir ne provoque pas d'erreur, il est simplement ignoré), et une valeur de top_p inférieure à 0,95 est automatiquement relevée à 0,95. C'est pourquoi le code ci-dessus désactive la réflexion.
Autrement dit : pour contrôler la sortie avec la température, il faut désactiver le mode réflexion. Et inversement, avec la réflexion activée, ne comptez pas sur la température pour stabiliser la sortie.
Quelle valeur choisir
Valeurs recommandées officiellement par DeepSeek (septembre 2026) :
| Usage | Température |
|---|---|
| Écrire du code, résoudre des problèmes de maths | 0,0 |
| Nettoyage et analyse de données | 1,0 |
| Conversation courante | 1,3 |
| Traduction | 1,3 |
| Écriture créative, poésie | 1,5 |
La température par défaut de DeepSeek est 1,0. Ce tableau ne vaut que pour DeepSeek : les modèles d'autres fournisseurs réagissent différemment à la température, et la même valeur peut avoir un effet très différent.
Ma pratique : pour les tâches à réponse unique, comme extraire, classer ou reformater, température à 0 ; pour écrire, nommer, faire du brainstorming, la valeur par défaut ou plus ; en cas de doute, la valeur par défaut d'abord, puis ajuster si le résultat ne convient pas.
Exercices
- Passez la température de
sampling.pyà 0,1 et à 3,0, et voyez ce que deviennent les tirages. À 3,0, combien de fois « 露营 » est-il tiré ? - Passez
top_pà 0,7 et à 0,99, et voyez combien de candidats restent. - Avec
temperature_api.py, changez de question : faites traduire « The quick brown fox jumps over the lazy dog » en chinois, 20 fois à la température 0 et 20 fois à 1,3, et comptez les traductions différentes. Pour la traduction, l'effet de la température est-il plus fort ou plus faible que pour les métaphores ? Réfléchissez à pourquoi. - Avec le paramètre
logprobsde la leçon précédente, regardez les candidats et probabilités du premier token quand on demande au modèle de « trouver un nom pour une petite boutique de café artisanal ». Cela confirme-t-il mon hypothèse sur le phénomène « 豆语咖啡 » ?
Auto-test
1. Pourquoi les réponses du modèle sont-elles plus stables quand on baisse la température ?
La température agit sur la distribution de probabilités des candidats à chaque étape : plus elle est basse, plus les candidats probables le deviennent et plus les improbables s'effacent, la distribution est plus « pointue ». Le tirage donne presque toujours le même token, et la réponse est naturellement plus stable. Quand la température tend vers 0, on choisit à chaque étape le candidat le plus probable.
2. Avec une température de 0, vous posez 20 fois la même question et obtenez tout de même deux réponses différentes. Est-ce normal ?
Oui. Quand le serveur traite les requêtes par lots, l'ordre des opérations en virgule flottante change, ce qui fait varier très légèrement les probabilités. Quand deux candidats sont très proches, cette différence suffit à inverser le choix, et toute la suite de la sortie change. On ne peut donc pas compter sur une température de 0 pour un résultat parfaitement reproductible.
3. Sur DeepSeek, avec le mode réflexion activé, vous passez la température de 1,0 à 0 et la sortie n'est pas plus stable. Pourquoi ?
En mode réflexion, DeepSeek ignore le paramètre temperature ; le définir n'a aucun effet. Pour contrôler la sortie avec la température, il faut désactiver la réflexion avec extra_body={"thinking": {"type": "disabled"}}.
Questions et discussion
Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.
Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.
Chargement de la discussion…