はじめに
こんにちは。
LLM開発コンペチームRAMENに参加したのデータ班メンバーです。
プロジェクトの前半では、主にSeedの収集を担当していましたが、
後半からは評価班が出してくれたHLE(Humanity’s Last Exam)の正誤表をもとに、
評価結果を分析しました。
この記事では、私が実際にColab上で行った分析コードとともに、
初心者としてどのように気づきを得ていったのかを紹介します。
誤答分析の目的
LLM開発において、HLEで更に高得点を取るためにはどうすればよいか、
初心者なりに考えた結果、思い出したのは受験勉強でした。
ー「なぜ間違えたのか」を知り、
 また間違えないように工夫することでスコアを上げるー
これをLLM開発にも応用できるんじゃないかと。
誤答の種類によって、取り得る対策がまったく違います。
・計算問題の誤り → 計算ミス?定義や定理、公式などの理解不足?
・知識問題の誤り → 知識を得る/整理する?
・回答は正しいが形式が違う → 回答前に指定フォーマットを確認する?
どの問題でなぜ間違えたかが分からないと、
Seed改善やプロンプト修正の方向性を誤ります。
どんなタイプの誤答が多いのか、初心者なりに分析してみました。
Step 1. 評価ログをColabで分析
評価班が出してくれたHLEの評価結果はJSON形式でした。
以下コード(抜粋)で分析してみました。
# 正解が単一英字(A–Eなど)→MC、モデル回答が一文字A–E以外なら違反
def is_mc(ans):
    return bool(re.fullmatch(r"[A-Za-z]", str(ans).strip())) if ans is not None else False
df["IsMC"] = df["CorrectAnswer"].map(is_mc)
df["FormatViolation"] = False
df.loc[df["IsMC"], "FormatViolation"] = ~df.loc[df["IsMC"], "ModelAnswer"].astype(str)\
    .str.strip().str.upper().str.fullmatch(r"[A-E]")
acc_mc  = (df.loc[df["IsMC"], "IsCorrect"]  == "yes").mean()*100 if df["IsMC"].any() else np.nan
acc_nmc = (df.loc[~df["IsMC"], "IsCorrect"] == "yes").mean()*100 if (~df["IsMC"]).any() else np.nan
print("\nAccuracy by format:")
print("MC:", f"{acc_mc:.2f}%")
print("Non-MC:", f"{acc_nmc:.2f}%")
print("Format violations (MC only):", int(df["FormatViolation"].sum()))
plt.figure()
plt.bar(["Non-MC","MC"], [acc_nmc, acc_mc])
plt.ylabel("Accuracy (%)")
plt.title("Accuracy by Question Format")
plt.show()
#  問題タイプ分類(ヒューリスティック)
def problem_type(ca, ma):
    s = str(ca or "")
    if re.fullmatch(r"[A-Za-z]", s.strip()):
        return "MC"
    if re.fullmatch(r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", s.strip()):
        return "PureNumeric"
    if re.match(r"^\s*[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?\s*[a-zA-Zµ%/·\*\^\-\_]+", s):
        return "UnitNumeric"
    if any(tok in s for tok in ["\\frac","\\int","\\sum","_{","}\\","$","^"]):
        return "Symbolic"
    if re.fullmatch(r"[A-Za-z][A-Za-z0-9]{1,5}", s.strip()):
        return "Code"
    return "OpenText"
df["Type"] = df.apply(lambda r: problem_type(r["CorrectAnswer"], r["ModelAnswer"]), axis=1)
type_acc = df.groupby("Type")["IsCorrect"].apply(lambda x: (x=="yes").mean()*100).sort_values(ascending=False)
print("\nAccuracy by Type (%):\n", type_acc)
fmt_viol_by_type = df.groupby("Type")["FormatViolation"].sum()
print("\nFormat violations by Type:\n", fmt_viol_by_type)
Qwen3-235B-A22Bの評価結果を分析したところ、
Format violations (MC only): 122/222
となり、“フォーマットミス”が多く検出されました。
Step 2. 目視確認して分かった落とし穴
誤答だけでなく、正答もいくつか見てみると、判定結果は以下の通りでした。
| 正答 | モデルの回答 | 判定 | 
|---|---|---|
| 4 | The answer is 4. | ✅(形式違いだが正解扱い) | 
| A | The answer is B | ❌(本来は“選択ミス”だが、誤って“フォーマットミス”に分類していた) | 
| Mars | Mars. | ✅(形式違いだが正解) | 
つまり、
"The answer is 4." のような出力は正解扱いになっていた一方で、
正答がAなのに「The answer is B」と出力したケースは、
本来は「選択ミス」であるにも関わらず、“フォーマットミス”として誤分類していました。
Step 3. 質問文から誤答分析
フォーマットミスの項目を除き、誤答タイプを再定義する必要があったため、
思い切って考え方を変え、質問文を分類することで誤答タイプを推測してみました。
| classify_question | 内容 | 
|---|---|
| Calculation | 数字・数式が多ければ計算問題 | 
| Knowledge | 固有名詞(大文字開始の単語が多い)や "Act", "Author", "Law" などが多ければ知識問題 | 
| Reasoning | Yes/No, True/False, 正誤判定なら推論問題 | 
| Language | 言語・翻訳系 | 
以下、コードを抜粋して載せます。
#judge=="yes" → 正解 と仮定
df["IsCorrect"] = df["judge"].apply(lambda x: True if str(x).lower()=="yes" else False)
#全体統計
total = len(df)
correct = df["IsCorrect"].sum()
#category列を "Math/Algebra" のように "/" 区切りにして細分類
df["MainCategory"] = df["category"].apply(lambda x: str(x).split("/")[0])
df["SubCategory"] = df["category"].apply(lambda x: str(x).split("/")[1] if "/" in str(x) else "General")
def classify_question(text):
    """問題文の性質を簡易分類"""
    if pd.isna(text):
        return "Unknown"
    #数字・数式が多ければ計算問題
    if re.search(r"\d|=|\+|\-|\*|/|log|sin|cos|tan|√|∑|π", text, re.IGNORECASE):
        return "Calculation"
    #固有名詞(大文字開始の単語が多い)や "Act","Author" などが多ければ知識問題
    if len(re.findall(r"\b[A-Z][a-z]+\b", text)) >= 3 or re.search(r"Act|War|Author|King|Law|Novel|State", text, re.IGNORECASE):
        return "Knowledge"
    #Yes/No, True/False, 正誤判定なら推論問題
    if re.search(r"Yes|No|True|False|correct|incorrect", text, re.IGNORECASE):
        return "Reasoning"
    #言語・翻訳系
    if re.search(r"translate|pronounce|language|word|spelling", text, re.IGNORECASE):
        return "Language"
    return "General"
#新しい列を追加
df["ProblemType"] = df["question"].apply(classify_question)
#誤答のみ集計
wrong_df = df[~df["IsCorrect"]]
summary_type = (
    wrong_df.groupby(["MainCategory","SubCategory","ProblemType"])
    .size()
    .reset_index(name="WrongCount")
    .sort_values(["MainCategory","SubCategory","WrongCount"], ascending=[True,True,False])
)
display(summary_type)
分析してみると結果は以下のようになりました。
| MainCategory | SubCategory | ProblemType | WrongCount | |
|---|---|---|---|---|
| 0 | Biology | Medicine | Calculation | 10 | 
| 1 | Chemistry | General | Calculation | 6 | 
| 2 | Computer Science | AI | Calculation | 11 | 
| 3 | Engineering | General | Calculation | 2 | 
| 4 | Humanities | Social Science | Calculation | 7 | 
| 5 | Humanities | Social Science | Knowledge | 3 | 
| 6 | Math | General | Calculation | 34 | 
| 7 | Math | General | Reasoning | 1 | 
| 8 | Other | General | Calculation | 7 | 
| 9 | Other | General | Knowledge | 2 | 
| 10 | Physics | General | Calculation | 9 | 
| 11 | Physics | General | Knowledge | 1 | 
これをもとに、Seed改善やプロンプト修正の議論ができたかな…?と思います。
学びと改善ポイント
評価結果の分析を通して得た学びは以下の通りです。
・目視確認の重要性
・誤答タイプ(間違える問題の傾向)を分析することでLLM強化の方向性がわかるかも
一方で、初心者の分析なのでまだまだ改善の余地があるかと思います。
例えば、問題分類基準の見直し・精緻化やCoTに基づく誤答分析など、挙げればきりがないですね…。
まとめ
| ポイント | 内容 | 
|---|---|
| 目的 | 評価結果から誤答傾向を把握し、Seed改善に繋げる | 
| 課題 | フォーマットと選択ミスを混同していた | 
| 対処 | どのような問題を間違えたかに着目してみた | 
| 成果 | 実質的な誤答問題の把握とその対策に集中できた | 
| 学び | 評価結果の目視確認をすることが重要 | 
同じようにHLE評価ログを扱っている方は、
ぜひ一度、本当に“フォーマットミス”かどうか、目視で見直してみてくださいー!
使用環境
・Google Colab(無料)
本プロジェクトは、国立研究開発法人新エネルギー・産業技術総合開発機構(以下「NEDO」)の「日本語版医療特化型LLMの社会実装に向けた安全性検証・実証」における基盤モデルの開発プロジェクトの一環として行われます。
