FatimahEmadEldin's picture
Import from FatimahEmadEldin/KnowledgeGraphEval-2026-AdaptNER-Experiments
ea1a330 verified
Raw History Blame Contribute Delete
3.77 kB
# -*- coding: utf-8 -*-
"""Ensemble AdaptNER predictions by weighted majority vote per entity-type column.
Each input is a Konooz submission file: token + 21 space-separated B/I/O columns
(one channel per entity type), sentences separated by blank lines. All inputs must
share the same tokens in the same order. For every token and every one of the 21
columns, we take a weighted vote over the models' tags ({O, B-TYPE, I-TYPE}) and
emit the highest-weight tag (ties break toward the earlier/higher-weight model).
Usage
-----
python ensemble_predictions.py \
--model FILE1.txt:0.74 --model FILE2.txt:0.72 --model FILE3.txt:0.71 \
--out namaa_pred_ensemble.txt --zip submission_ensemble.zip
Weights are optional (default 1.0 each -> plain majority vote). Pass them after a
colon; the natural choice is each model's Konooz-dev micro-F1.
"""
from __future__ import annotations
import argparse
import io
import os
import zipfile
from collections import defaultdict
N_COLS = 21
def read_rows(path):
"""Return list of rows; each row is None (blank line) or [token, *21tags]."""
rows = []
for line in io.open(path, encoding="utf-8"):
s = line.rstrip("\n")
if not s.strip():
rows.append(None)
else:
rows.append(s.split())
return rows
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--model", action="append", required=True,
help="path[:weight] of a prediction file; repeatable")
ap.add_argument("--out", default="namaa_pred_ensemble.txt")
ap.add_argument("--zip", dest="zip_path", default="")
ap.add_argument("--mode", choices=["vote", "union"], default="vote",
help="vote = weighted majority per column; union = any non-O wins (max recall)")
args = ap.parse_args()
models, weights = [], []
for spec in args.model:
if ":" in spec and spec.rsplit(":", 1)[1].replace(".", "", 1).isdigit():
path, w = spec.rsplit(":", 1); w = float(w)
else:
path, w = spec, 1.0
models.append(read_rows(path)); weights.append(w)
print(f"[in] {os.path.basename(path)} weight={w}")
n = len(models[0])
for i, m in enumerate(models):
if len(m) != n:
raise ValueError(f"file {i} has {len(m)} lines, expected {n} (inputs must align)")
out_rows = []
for r in range(n):
if models[0][r] is None:
out_rows.append(None); continue
token = models[0][r][0]
cols = []
for c in range(N_COLS):
score = defaultdict(float)
for m, w in zip(models, weights):
tag = m[r][1 + c]
if args.mode == "union":
# any B/I outweighs O
score[tag] += (w + 10.0 if tag != "O" else w)
else:
score[tag] += w
# pick max-weight tag; ties -> earlier model order via stable max on (weight, -firstseen)
best = max(score.items(), key=lambda kv: kv[1])[0]
cols.append(best)
out_rows.append([token, *cols])
with io.open(args.out, "w", encoding="utf-8", newline="\n") as f:
for row in out_rows:
f.write("" if row is None else " ".join(row))
f.write("\n")
n_ent = sum(1 for row in out_rows if row for t in row[1:] if t.startswith("B-"))
print(f"[out] {args.out} (entities: {n_ent})")
if args.zip_path:
with zipfile.ZipFile(args.zip_path, "w", zipfile.ZIP_DEFLATED) as z:
z.write(args.out, arcname=os.path.basename(args.out))
print(f"[zip] {args.zip_path}")
if __name__ == "__main__":
main()