File size: 6,209 Bytes
9751df6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env python3
"""Mathe-Diagramme fuer die HuggingFace-Seite.

Zwei Bilder:
  1. MATH-500 je Variante mit 95-Prozent-Intervall — zeigt, dass die
     Varianten sich nicht unterscheiden
  2. Trefferquote nach Schwierigkeitsstufe — zeigt, dass der Benchmark
     sehr wohl differenziert, nur eben nicht zwischen diesen Modellen

Bild 2 ist das eigentlich interessante: eine einzelne Prozentzahl verschweigt,
dass die Modelle auf Stufe 1 bei knapp 90 Prozent liegen und auf Stufe 5 bei
gut der Haelfte.
"""
import glob
import json
import math
import os
import sys

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

sys.path.insert(0, "/mnt/models/qwen38-prep")
from diagramm import HELL, DUNKEL, NAMEN, stil

PREP = "/mnt/models/qwen38-prep"


def lade():
    d = {}
    for p in glob.glob(os.path.join(PREP, "mathe-math500-neubewertet", "*.json")):
        k = os.path.splitext(os.path.basename(p))[0]
        if k in NAMEN or k in ("q36-35b-a3b", "gemma4-12b"):
            d[k] = json.load(open(p))
    return d


def kurzname(k, j):
    if k in NAMEN:
        return NAMEN[k][0]
    return j["modell"].split(" (")[0]


def ist38(k):
    return NAMEN[k][1] if k in NAMEN else False


def bild_quoten(d, f, ziel):
    zeilen = sorted(
        ((kurzname(k, j), ist38(k), j["quote"], j["bestanden"], j["n"],
          j.get("am_limit", 0))
         for k, j in d.items()), key=lambda z: z[2])
    if not zeilen:
        return False

    fig, ax = plt.subplots(figsize=(8.6, 0.55 * len(zeilen) + 2.0), dpi=170)
    fig.patch.set_facecolor(f["surface"])
    y = list(range(len(zeilen)))
    ax.barh(y, [z[2] for z in zeilen], height=0.58, zorder=3,
            color=[f["akzent"] if z[1] else f["grau"] for z in zeilen])
    hoechst = max(z[2] for z in zeilen)
    for i, z in enumerate(zeilen):
        p = z[3] / z[4]
        ci = 1.96 * math.sqrt(p * (1 - p) / z[4]) * 100
        ax.plot([z[2] - ci, z[2] + ci], [i, i], color=f["ink2"],
                linewidth=1.2, zorder=5)
        for x in (z[2] - ci, z[2] + ci):
            ax.plot([x, x], [i - 0.13, i + 0.13], color=f["ink2"],
                    linewidth=1.2, zorder=5)
        ax.text(z[2] + ci + hoechst * 0.02, i, f"{z[2]:.1f} %", va="center",
                ha="left", color=f["ink"], fontsize=10, fontweight="600")
        ax.text(hoechst * 0.025, i, f"{z[3]}/{z[4]}", va="center", ha="left",
                color=f["surface"], fontsize=8.5, zorder=6)
    ax.set_yticks(y)
    ax.set_yticklabels([z[0] for z in zeilen], color=f["ink"], fontsize=10)
    ax.set_xlim(0, hoechst * 1.30)
    stil(ax, f, "MATH-500  ·  gelöste Aufgaben",
         "Anteil gelöst in Prozent  ·  Strich = 95-Prozent-Intervall")
    abgeschnitten = sum(z[5] for z in zeilen) / len(zeilen)
    fig.text(0.012, 0.015,
             f"Alle Intervalle überlappen.  Im Schnitt {abgeschnitten:.0f} von "
             f"{zeilen[0][4]} Antworten liefen ins Token-Limit und zählen als "
             f"falsch — die absoluten Werte liegen daher zu niedrig.",
             color=f["muted"], fontsize=8)
    fig.tight_layout(rect=(0, 0.05, 1, 1))
    fig.savefig(ziel, facecolor=f["surface"])
    plt.close(fig)
    return True


def bild_stufen(d, f, ziel):
    """Trefferquote nach Schwierigkeitsstufe. Hier trennt der Benchmark."""
    stufen = sorted({s for j in d.values() for s in j.get("nach_level", {})})
    if not stufen:
        return False
    fig, ax = plt.subplots(figsize=(8.6, 5.2), dpi=170)
    fig.patch.set_facecolor(f["surface"])
    # Farbe traegt die Generation, Strichart und Markerform die Variante.
    # Farbe allein reicht hier nicht: mehrere Qwen3.8-Varianten teilen sich
    # denselben Blauton und waeren in einem Liniendiagramm nicht trennbar.
    striche = ["-", "--", ":", "-."]
    marker = ["o", "s", "^", "D"]
    zaehler = {True: 0, False: 0}
    for k, j in sorted(d.items()):
        nl = j.get("nach_level", {})
        xs, ys = [], []
        for s in stufen:
            if s in nl and nl[s][1]:
                xs.append(int(s))
                ys.append(nl[s][0] / nl[s][1] * 100)
        if not xs:
            continue
        g = ist38(k)
        i = zaehler[g] % 4
        zaehler[g] += 1
        ax.plot(xs, ys, marker=marker[i], markersize=7, linewidth=2,
                linestyle=striche[i],
                color=f["akzent"] if g else f["grau"],
                markeredgecolor=f["surface"], markeredgewidth=1.5,
                label=kurzname(k, j), zorder=3)
    ax.set_facecolor(f["surface"])
    ax.set_title("MATH-500 nach Schwierigkeitsstufe", color=f["ink"],
                 fontsize=12.5, pad=12, loc="left", fontweight="600")
    ax.set_xlabel("Schwierigkeitsstufe (1 = leicht, 5 = Wettbewerbsniveau)",
                  color=f["ink2"], fontsize=9.5)
    ax.set_ylabel("gelöst in Prozent", color=f["ink2"], fontsize=9.5)
    ax.set_xticks([int(s) for s in stufen])
    ax.tick_params(colors=f["muted"], labelsize=9.5, length=0)
    for s in ("top", "right"):
        ax.spines[s].set_visible(False)
    for s in ("left", "bottom"):
        ax.spines[s].set_color(f["achse"])
    ax.grid(True, color=f["grid"], linewidth=1)
    ax.set_axisbelow(True)
    ax.set_ylim(0, 100)
    leg = ax.legend(frameon=False, fontsize=9, loc="lower left")
    for t in leg.get_texts():
        t.set_color(f["ink2"])
    fig.text(0.012, 0.015,
             "Der Benchmark trennt sauber über die Stufen — nur eben nicht "
             "zwischen diesen Varianten.  Blau: Qwen3.8, Grau: übrige",
             color=f["muted"], fontsize=8)
    fig.tight_layout(rect=(0, 0.045, 1, 1))
    fig.savefig(ziel, facecolor=f["surface"])
    plt.close(fig)
    return True


def main():
    d = lade()
    print(f"MATH-500: {len(d)} Modelle")
    if not d:
        return 1
    ziel = os.path.join(PREP, "bilder")
    os.makedirs(ziel, exist_ok=True)
    for name, f in (("hell", HELL), ("dunkel", DUNKEL)):
        for bau, datei in ((bild_quoten, f"mathe-{name}.png"),
                           (bild_stufen, f"mathe-stufen-{name}.png")):
            if bau(d, f, os.path.join(ziel, datei)):
                print(f"  {datei}")
    return 0


if __name__ == "__main__":
    sys.exit(main())