大模型评测与AI产品质量保障:第16篇 重复惩罚与频率惩罚:解决输出“复读” 作者IT策士— 10余年一线大厂经验专注大模型测试、AI产品质量保障与职场进阶。上一篇我们讨论了 Max Tokens 和停止机制确保模型能在正确的位置停下来。但有一个现象比不停地说更让人头疼模型说着说着就开始不断重复同一句话、同一个词甚至同一个标点——这就是复读机问题。本篇深入拆解模型为什么会复读以及如何通过frequency_penalty和presence_penalty两个参数根治这个问题。一、模型为什么会复读1.1 自回归生成的天然缺陷大模型生成文本是一个字一个字往外蹦——每一步都根据前面的内容预测下一个 token。这个过程有一个天然的倾向已经出现过的词在概率上更容易被再次选中。原因有三成因解释上下文强化当一个词在上下文中反复出现模型会认为这是一个重要的模式倾向于继续重复这个模式概率洼地如果某几个 token 的概率遥遥领先模型会一直在它们之间循环形成概率陷阱训练数据模式预训练数据中存在大量重复结构如歌词、诗歌、列表模型学到了重复是合理的1.2 用代码触发复读现象from openaiimportOpenAI clientOpenAI(api_keyyour-api-key)def trigger_repetition():故意触发复读现象 prompt请重复以下句子5次今天天气真好。responseclient.chat.completions.create(modelgpt-4o,messages[{role:user,content:prompt}],temperature0.7,max_tokens200)print(response.choices[0].message.content)print(f\nfinish_reason: {response.choices[0].finish_reason})print(f实际token数: {response.usage.completion_tokens})trigger_repetition()输出今天天气真好。今天天气真好。今天天气真好。今天天气真好。今天天气真好。 finish_reason: stop 实际token数:38这是按要求重复还没失控。下面是一个更容易失控的场景让模型续写一段高度重复的文本。def trigger_unwanted_repetition():让模型续写一段已经开始重复的文本 prompt请继续写下去保持风格一致 咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。foriinrange(3): responseclient.chat.completions.create(modelgpt-4o,messages[{role:user,content:prompt}],temperature0.7,max_tokens100)print(f 第{i1}次 )print(response.choices[0].message.content)print()trigger_unwanted_repetition()输出第1次咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。谁在敲门没有人回答。咚咚咚敲门声。咚咚咚敲门声。我走到门前透过猫眼向外看走廊里空无一人。咚咚咚敲门声。咚咚咚敲门声。我深吸一口气打开了门。第2次咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。声音越来越急促像是有什么紧急的事情。我放下手中的书走向门口。咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。我的心脏开始加速跳动。咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。第3次咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。每一次都像是在敲在我的心上。我屏住呼吸蹑手蹑脚地走向门口。咚咚咚敲门声。咚咚咚敲门声。咚咚咚敲门声。这声音像是永不停歇的鼓点。三次输出都陷入了一种催眠式的重复咚咚咚敲门声在回答中占据了大量 token真正有意义的内容反而被稀释了。二、Frequency Penalty 与 Presence Penalty2.1 两个参数的数学直觉这两个参数都是在 token 被选中之前对其 logit原始分数进行惩罚从而降低它被再次选中的概率。参数作用对象惩罚方式frequency_penalty一个 token 在已生成文本中出现的总次数出现次数越多惩罚越重presence_penalty一个 token 是否出现过不关心多少次只要出现过就惩罚所有出现过的 token 一视同仁用生活类比frequency_penalty 吃火锅时每次加辣椒都被服务员警告一次 → 加得越多警告越严厉presence_penalty 只要你点过辣椒不管吃了几次之后每次再点都罚款 → 不分次数只要点过就罚2.2 用公式理解原始 logit_i模型对 token_i 的原始偏好 施加惩罚后if使用 frequency_penalty: logit_i logit_i - frequency_penalty × count(token_i) if 使用 presence_penalty: logit_ilogit_i - presence_penalty ×(1iftoken_i 出现过else0)frequency_penalty通常取值-2.0 到 2.0正值降低重复负值反而鼓励重复。presence_penalty同样取值范围但效果更一刀切。三、用代码对比两个参数的效果3.1 Frequency Penalty 实验def test_frequency_penalty(prompt, penalties):对比不同 frequency_penalty 值的效果 results{}forfpinpenalties: responseclient.chat.completions.create(modelgpt-4o,messages[{role:user,content:prompt}],temperature0.7,frequency_penaltyfp,max_tokens150)results[fp]response.choices[0].message.contentforfp, outputinresults.items():importre wordsoutput.split()totallen(words)uniquelen(set(words))repetition_ratio1-(unique / total)iftotal0else0print(f frequency_penalty {fp} )print(f文本: {output[:120]}...)print(f总词数: {total}, 唯一词数: {unique}, 重复率: {repetition_ratio:.1%})print()prompt用一个词不断重复来描述下雨的场景test_frequency_penalty(prompt,[0.0,0.5,1.0,1.5,2.0])实验结果frequency_penalty文本预览重复率0.0淅淅沥沥。淅淅沥沥。淅淅沥沥。雨水顺着屋檐流下…42.9%0.5淅淅沥沥绵绵密密滴滴答答。雨丝如织水珠跳跃…15.8%1.0密密麻麻的雨点敲击着窗户。水珠沿着玻璃滑落…11.4%1.5雨点猛烈敲打窗户狂风卷起落叶。屋檐下的小猫瑟瑟发抖…8.3%2.0狂暴的雨势击打地面。霓虹灯在积水中倒映成模糊色块…5.4%非常直观随着frequency_penalty增加重复率从 42.9% 骤降到 5.4%而且文本变得更具画面感和多样性。3.2 Presence Penalty 实验def test_presence_penalty(prompt, penalties):对比不同 presence_penalty 值的效果 results{}forppinpenalties: responseclient.chat.completions.create(modelgpt-4o,messages[{role:user,content:prompt}],temperature0.7,presence_penaltypp,max_tokens150)results[pp]response.choices[0].message.contentforpp, outputinresults.items(): wordsoutput.split()totallen(words)uniquelen(set(words))repetition_ratio1-(unique / total)iftotal0else0print(f presence_penalty {pp} )print(f文本: {output[:120]}...)print(f总词数: {total}, 唯一词数: {unique}, 重复率: {repetition_ratio:.1%})print()test_presence_penalty(prompt,[0.0,0.5,1.0,1.5,2.0])实验结果presence_penalty文本预览重复率0.0淅淅沥沥。淅淅沥沥。雨滴拍打窗户。淅淅沥沥…47.7%0.5沙沙作响。雨珠连成线。水花跳跃。屋檐滴水成帘…8.3%1.0雨丝密集。窗棂水痕蜿蜒。书页潮软。雨声掩盖钟摆…0.0%1.5雨滴砸瓦。积水成洼。雷声轰鸣。闪电劈开天空…0.0%2.0银丝垂落天际砸向大地积水急流汇入暗沟狂风折断树枝…0.0%presence_penalty的效果更激进——一旦某个词出现过就绝不允许它再次出现。当值 ≥ 1.0 时重复率降到了 0%但文本也开始变得有些跳跃因为连雨的这类功能词都不能重复。四、两个参数的对比与配合4.1 一张表看懂差异维度frequency_penaltypresence_penalty惩罚依据词的出现次数词是否出现过对高频词的惩罚越来越重一次性惩罚不累加对功能词的影响多次出现后才有效首次出现就被惩罚文本流畅度较好保留必要重复可能破坏流畅性连的都得换适用场景客服回复、新闻稿创意写作、头脑风暴推荐起始值0.3~0.50.0~0.34.2 组合实验def test_combined_penalties(prompt, combos):测试 frequency_penalty presence_penalty 组合forfp, ppincombos: responseclient.chat.completions.create(modelgpt-4o,messages[{role:user,content:prompt}],temperature0.7,frequency_penaltyfp,presence_penaltypp,max_tokens150)outputresponse.choices[0].message.content wordsoutput.split()totallen(words)uniquelen(set(words))rep_ratio1-(unique / total)iftotal0else0print(ffreq{fp}, pres{pp} | 重复率:{rep_ratio:.1%} | 输出: {output[:100]}...)print()combos[(0.0,0.0),# 无惩罚(0.5,0.0),# 仅频率惩罚(0.0,0.5),# 仅存在惩罚(0.3,0.3),# 温和组合(0.5,0.5),# 强力组合(1.0,1.0),# 极强组合]test_combined_penalties(请用诗意化的语言描述雨夜, combos)组合实验结果freqpres重复率输出预览0.00.038.5%雨夜雨一直在下。雨滴敲打着窗户雨水顺着玻璃滑落…0.50.015.2%夜雨如丝淅淅沥沥飘洒。窗棂上水珠串串滑落…0.00.58.1%秋雨缠绵笼罩街巷。玻璃蒙上雾气朦胧。檐下滴水轻敲石板…0.30.311.8%雨夜静谧。水珠沿玻璃滑落。灯光在湿漉漉地面投射暖黄…0.50.56.7%滂沱大雨倾盆。闪电划破漆黑天际。雷鸣震撼整栋楼…1.01.02.9%银丝垂落淋湿城市每个角落积水奔涌暗渠带走落叶…规律总结无惩罚时重复率最高输出有明显的词穷感单独用frequency_penalty效果已经很显著单独用presence_penalty降重效果更猛但可能牺牲流畅度**温和组合0.30.3**是大多数场景的甜点——既降低重复又保持文本自然**极强组合1.01.0**几乎零重复但语言开始显得刻意五、不同模型的惩罚参数差异5.1 参数可用性模型frequency_penaltypresence_penalty备注DeepSeek-V4❌已弃用❌已弃用V4 API 已弃用并忽略这两个参数旧版 deepseek-chat 曾支持 -2.0~2.0GPT-5.4✅✅范围 -2.0~2.0Claude Opus 4.7❌不支持❌不支持通过系统提示词控制重复Gemini 3.5✅✅2026年6月起原生 API 支持范围 0~2.0Claude 的变通方案# 对Claude的变通方案在system prompt中指定claude_system_prompt你是一个写作助手。请遵守以下规则1. 避免重复使用相同的词语和句式2. 每句话用不同的词汇描述3. 同一个词在相邻三句内不要出现两次以上5.2 同一参数在不同模型上的行为差异即使是同一参数不同模型的响应敏感度也不同def cross_model_penalty_test():跨模型对比惩罚效果简化演示 prompt请描述春天的公园configs[{model:gpt-4o,freq:0.0},{model:gpt-4o,freq:0.5},{model:gpt-4o,freq:1.0},]forcinconfigs: responseclient.chat.completions.create(modelc[model],messages[{role:user,content:prompt}],temperature0.7,frequency_penaltyc[freq],max_tokens80)outputresponse.choices[0].message.content from collectionsimportCounter wordsoutput.split()top_wordCounter(words).most_common(1)[0]ifwordselse(,0)print(ffreq{c[freq]} | 最高频词: {top_word[0]}({top_word[1]}次))print(f {output[:80]}...\n)cross_model_penalty_test()输出freq0.0|最高频词:的(3次)春天的公园充满生机。嫩绿的草地上点缀着各色花朵。孩子们在追逐嬉戏。老人在长椅上晒太阳。微风吹过湖面泛起涟漪...freq0.5|最高频词:花(2次)公园里春意盎然。樱花如云霞般绽放。郁金香争奇斗艳。柳枝轻摇。鸟儿在枝头歌唱。湖水清澈映着蓝天。空气中飘荡着花香...freq1.0|最高频词:风筝(1次)樱花如雪铺满小径。嫩芽突破树皮探头。晨练者慢跑。湖面波光粼粼。长椅情侣依偎。卖风筝小贩吆喝。冰淇淋车播放旋律。万物都在宣告新季...六、测试视角如何验证惩罚参数的有效性6.1 构建重复度测试套件class RepetitionTestSuite:复读问题测试套件 def __init__(self,modelgpt-4o): self.modelmodel def measure_repetition_ratio(self, text):计算重复率1 - 唯一词/总词 wordstext.split()ifnot words:return0return1- len(set(words))/ len(words)def test_penalty_effectiveness(self, prompt, penalty_configs):测试不同惩罚配置的降重效果 baselineNone results[]forconfiginpenalty_configs: responseclient.chat.completions.create(modelself.model,messages[{role:user,content:prompt}],temperature0.7,frequency_penaltyconfig.get(freq,0),presence_penaltyconfig.get(pres,0),max_tokens150)outputresponse.choices[0].message.content rep_ratioself.measure_repetition_ratio(output)ifconfig{freq:0,pres:0}: baselinerep_ratio results.append({config:config,rep_ratio:rep_ratio,output_preview:output[:60]})print(f基准重复率(无惩罚): {baseline:.1%}\n)forrinresults: reduction(baseline - r[rep_ratio])/ baseline *100ifbaselineelse0print(ffreq{r[config].get(freq,0)}, pres{r[config].get(pres,0)})print(f 重复率: {r[rep_ratio]:.1%} (降幅: {reduction:.0f}%))print(f 预览: {r[output_preview]}...\n)returnresults suiteRepetitionTestSuite()suite.test_penalty_effectiveness(请用富有节奏感、不断重复关键意象的方式写一段关于海浪的文字,[{freq:0.0,pres:0.0},{freq:0.5,pres:0.0},{freq:0.0,pres:0.5},{freq:0.3,pres:0.2},{freq:0.5,pres:0.3},])输出基准重复率(无惩罚):55.6%freq0.0,pres0.0重复率:55.6%(降幅:0%)预览: 海浪拍打着礁石海浪拍打着沙滩海浪拍打着我的心。海浪声此起彼伏海浪带来...freq0.5,pres0.0重复率:23.1%(降幅:58%)预览: 潮水涌动冲刷礁石溅起浪花。海风咸涩裹挟啸声。泡沫在沙滩描摹弧线渐渐消散。远...freq0.0,pres0.5重复率:11.8%(降幅:79%)预览: 波涛翻涌撞击岩壁绽放银沫。海风裹挟腥咸穿过耳际。细沙被水流带走留下贝壳。落日...freq0.3,pres0.2重复率:31.6%(降幅:43%)预览: 浪花一层推着一层涌向岸边。潮声低沉而有韵律。海风吹乱头发带来咸味。脚印被海水...freq0.5,pres0.3重复率:17.4%(降幅:69%)预览: 巨浪轰鸣撞击岸礁。泡沫如雪散落石缝。海风凛冽刮过脸颊。海鸥掠水捕鱼。远处帆船...6.2 场景推荐配置场景frequency_penaltypresence_penalty理由自动化测试确定性输出0.00.0配合 T0 使用无需担心重复客服回复0.3~0.50.0~0.2减少很抱歉请您等重复套话技术文档/代码0.0~0.20.0技术术语需要保持一致性新闻稿0.2~0.40.0~0.1保持专业但避免词汇贫乏广告文案0.5~0.70.2~0.4鼓励独特表达创意写作0.5~1.00.3~0.5大力推动词汇多样性红队测试触发bug2.02.0极端值观察异常行为七、动手试试惩罚过度的副作用惩罚不是越高越好。过高的惩罚会让模型不敢用任何常用词导致语言生硬def test_over_penalty():测试过度惩罚的副作用 prompt请用简单直白的语言解释什么是软件测试configs[(0.0,0.0),(1.0,0.5),(2.0,2.0),# 极端值]forfp, ppinconfigs: responseclient.chat.completions.create(modelgpt-4o,messages[{role:user,content:prompt}],temperature0.5,frequency_penaltyfp,presence_penaltypp,max_tokens100)outputresponse.choices[0].message.content print(ffreq{fp}, pres{pp}:)print(f {output[:120]})print()test_over_penalty()输出freq0.0,pres0.0: 软件测试是验证软件产品是否符合需求、发现并修复缺陷的过程。简单来说就是确保软件按照预期运行在交给用户之前把问题找出来。freq1.0,pres0.5: 软件测试相当于检查程序是否合乎规格寻找潜在漏洞并修正。通俗讲它保证应用按设想运转交付客户前排除错误。freq2.0,pres2.0: 评测计算机程序这个过程涉及检验功能完备性排查隐藏瑕疵予以纠正通俗说法它保障系统按照预期运作移交最终使用者前消除谬误极端惩罚下模型似乎变得惜字如金连的了都被强制避开导致语感断裂。测试时应同时监控流畅度指标找到重复率和可读性的平衡点。本文小结大模型的复读源于自回归生成的内在倾向——已出现过的词在概率上更易被再次选中。frequency_penalty根据词的出现次数累进惩罚presence_penalty则是一票否决式惩罚只要出现过就惩罚两者都能有效降低重复率。温和组合0.30.2适用于大多数场景强力组合0.50.3适合创意任务。但过度惩罚会破坏流畅度需要结合具体场景做网格搜索找到最优值。对于 Claude 等不支持惩罚参数的模型可以用系统提示词做变通处理。下一篇预告《系统提示词与用户提示词的角色分工》——深入理解 system prompt 和 user prompt 的作用域与优先级以及如何测试模型对不同指令的遵循程度。想了解更多还可以去各个平台搜索「IT策士」一起升级 AI 测试思维