AI出题工具的答案位置:96道生成题里只有5道把答案放在D

🔧 AI工具 2026-11-10 约 20 分钟阅读

让生成器自己安排答案位置,96道题里D只出现5次、B出现37次,每题都选B能拿38.5%。在提示里要求均分,总数确实平了(卡方0.67),代价是六批里三批原样返回ABCDABCD。正确做法是生成后在代码里均分洗牌,检查最终序列,并验证答案文本跟着选项一起挪。

📜 目录

二十道选择题,几秒钟就能生成。检查正确答案落在哪个位置,只要一行算术;没人做这一步,卷子就带着自己的答案纹路进教室。学生对这种纹路的敏感程度,比老师藏它的能力强。

下面这组数是144道题:六段短文、一个模型、2026年11月10日的三次运行。它只回答一个问题——让生成器自己决定答案位置,四个字母会不会被摊平。

跑了什么

六段各约120词的短文,都是为这次测试写的:水循环、罗马道路施工、光合作用的光反应、古腾堡印刷机、潮汐、弗莱明那块被霉菌污染的培养皿。每段各发一次接口调用,要求写八道四选项题、恰有一个正确答案,以JSON返回、选项标A到D。前两次用这份普通提示,温度取默认值。第三次在提示里加一句:正确答案在四个位置上均匀分布,八道题每个字母恰好两个,且同一个字母不得连续两次。同样的短文、同样的模型、同一个解析器。

模型走的是deepseek-chat的OpenAI兼容接口,返回里模型名带的是deepseek-flash。只有返回四个互不相同的选项文本、且答案是一个A到D的字母,这道题才计入。144道全部通过这一关,所以下面的计数里没有悄悄丢掉的题。

运行题数ABCD卡方(自由度3)p
普通提示,第一次48101817312.170.007
普通提示,第二次48101917214.830.002
普通提示,两次合计96203734526.92<0.00001
均分提示48121412100.670.88

D几乎没用,两次运行咬得很紧

普通提示下生成的96道题里,只有五道把答案放在D。均匀分布应该是24道。每题都选B能拿到96道里的37道,38.5%,瞎猜是25%。换成一份20道题的卷子,一个字不读能对7.7道,瞎猜对5道。两次运行来自各自独立的接口调用,每个字母上的差异都不超过一道,说明这是模型在这类任务上的稳定倾向,不是某一批抽坏了。

拆到单批看,比总数更难看。十二批各八道题里,有八批从头到尾没有出现过D。最偏的一批,八道题里有五道的答案在B。学生连续遇到同一个工具出的两份卷子,看到的是同一个形状,规律就是这样被看出来的。

要求均分确实有效,代价是换成一个更糟的规律

第三次运行听话了。A十二道、B十四道、C十二道、D十道,卡方0.67,p=0.88,整份题在位置分布上已经查不出东西。再看单批:六批里有三批原样返回ABCDABCD,八道题一个不差。用「答案从起点开始跟随ABCD循环的比例」来量,普通提示的两次是31%和33%,正好是抛硬币的水平;均分提示那次是73%,六批里三批是完美循环。

这个交换才是要记住的部分。均分提示把一种温和的偏斜——瞎选B能多拿13个百分点——换成了一个确定性循环,看出规律的学生可以一路对到8道全中。提示被逐字执行了,连「不得连续两次」这一条都满足,因为四字母循环本来就不会出现相邻重复。句子照做了,意图没做到。

在代码里修,然后验证修对了

生成之后再洗牌是可靠的做法。有两个细节,每个都要花一次运行才学得会。

细节一:逐题独立随机洗牌不能把一份卷子摊平。拿普通提示生成的32道题做样本,答案分布是5/13/11/3。给每道题的四个选项独立洗牌、用固定种子,结果是5/13/7/7,卡方4.5,p=0.21,还出现连续四个相同字母。随机在期望上无偏,落到32道题这样的小样本上,照样一眼看得出偏,而这份文件是要发到学生手上的。

细节二:均分要靠发牌拿到,而「不连续重复」必须在最终序列上检查。按等量的字母牌发下去,再把每道题的答案挪到它被分到的位置。我第一版在牌堆上检查了不连续重复,然后通过一个乱序的下标把字母发出去,打印出来的序列里仍然有连续三个相同字母。检查的对象应该是最终顺序,不是发牌前的牌堆。

同一份32道题的导出文件,脚本输出原样如下:

before  n=32 a=5 b=13 c=11 d=3 chi2=8.50 p=0.037 run=3
after   n=32 a=8 b=8 c=8 d=8 chi2=0.00 p=1.000 run=2
key text changed on 0 items

最后一行才是关键。把四个选项换个位置很容易,难的是让答案跟着它自己的文本一起走,洗牌毁掉一份导出,通常就毁在这一步。脚本会重新读一遍每一行,比较洗牌前后被标为答案的那个选项文本,因为分布看着完美、答案却挪错了位置,比不洗牌更糟。单独一份八道题的导出,同一个脚本把1/3/2/2变成2/2/2/2。

脚本

只用标准库,Python 3.8以上。导出成CSV,表头stem,a,b,c,d,answer,answer填小写字母,然后运行python3 fix_keys.py bank.csv。结果写在输入文件旁边的bank.fixed.csv,并把洗牌前后的分布打印出来。种子固定为20261110,同一份导出每次跑出同一个文件;同一份导出要给另一个班用时,改一下种子。

import csv, hashlib, math, random, sys, collections
L = "abcd"

def chi2_p(x):  # p-value for 3 degrees of freedom
    return math.erfc(math.sqrt(x / 2)) + math.sqrt(2 * x / math.pi) * math.exp(-x / 2)

def longest_run(s):
    best = cur = 1
    for a, b in zip(s, s[1:]):
        cur = cur + 1 if a == b else 1
        best = max(best, cur)
    return best if s else 0

def report(rows, tag):
    seq = "".join(r[5] for r in rows)
    c = collections.Counter(seq); n = len(seq); e = n / 4.0
    cnt = [c[k] for k in L]
    chi = sum((o - e) ** 2 / e for o in cnt)
    print("%-7s n=%d a=%d b=%d c=%d d=%d chi2=%.2f p=%.3f run=%d"
          % (tag, n, *cnt, chi, chi2_p(chi), longest_run(seq)))

def main(path, seed=20261110):
    rows = []
    for r in csv.DictReader(open(path, encoding="utf-8-sig", newline="")):
        opts = [(r.get(k) or "").strip() for k in L]
        key = (r.get("answer") or "").strip().lower()
        if all(opts) and key in L:
            rows.append([r["stem"].strip()] + opts + [key])
    report(rows, "before")

    rng = random.Random(seed)
    deck = [L[i % 4] for i in range(len(rows))]
    for _ in range(500):
        rng.shuffle(deck)
        if longest_run("".join(deck)) <= 2:
            break

    out = []
    for row, target in zip(rows, deck):
        rest = [o for i, o in enumerate(row[1:5]) if i != L.index(row[5])]
        random.Random(int(hashlib.sha256(row[0].encode()).hexdigest()[:8], 16)).shuffle(rest)
        new = rest[:]; new.insert(L.index(target), row[1 + L.index(row[5])])
        out.append([row[0]] + new + [target])
    report(out, "after")
    print("key text changed on %d items" % sum(
        row[1 + L.index(row[5])] != new[1 + L.index(new[5])] for row, new in zip(rows, out)))

    with open(path.replace(".csv", ".fixed.csv"), "w", encoding="utf-8", newline="") as f:
        w = csv.writer(f); w.writerow(["stem", "a", "b", "c", "d", "answer"]); w.writerows(out)

if __name__ == "__main__":
    main(sys.argv[1])

这查不出什么

一个模型家族、一天的生成、英文短文。托管式的出题工具可能在服务端已经洗过牌,那样导出的分布本来就是平的,上面这条检查十秒钟就能确认。答案标得对不对、题目有没有歧义、难度合不合适,这里一概说不了,每一项都要单独过一遍。48道题这一批也粗:若生成器本来无偏,48道题里出现三次或更少D的概率约为千分之一,一批异常足以提出问题,不足以定论,所以先比了两次运行再下结论。

如果真正担心的是答案能不能靠选项的形状猜出来,而不是靠位置,那条检查在这篇:28.6%的题能靠选项长度蒙对。若想把每道题的三个干扰项当成对照组来用,是这篇:15186道真题上的答案泄漏体检。还在挑工具的话,九个工具的免费边界和导出限制在这篇:九个免费出题工具各自能给到什么。

带两个数进下一份导出:96道题里只有5道把答案放在D;一旦要求均分,一半的批次会变成ABCDABCD。先看序列,再把卷子发出去。

相关文章

延伸阅读 · 含推广链接

跑通原型和长期挂着跑是两件事。按量计费起一台,用完释放,不用为闲置的机器付整月。

看按量计费价格 →

相关文章
2026-08-16
2026 香港最佳 AI 图像生成器:设计师与广告公司的 7 款工具
2026-07-27
向量数据库对比
2026-08-01
教师用AI:8个免费工具省一半备课时间
2026-08-28
2026 最佳 AI 庭院设计工具:6 款开挖前先规划后院的免费工具
2026-08-16
2026 华沙最佳 AI 图像生成器:游戏开发工作室的 6 款工具
2026-08-11
LLM评估平台2026:Langfuse、Arize Phoenix与开源Evals对比

本站文章由编辑人工撰写,收录的工具均经过实测或公开资料核验。文中链接指向工具官网或 GitHub 仓库,仅作信息参考,不构成付费推广。

💬 评论 (0)

暂无评论,来说两句吧~

登录后评论