"""Generate a submission from several random-walk feature blocks.""" from __future__ import annotations import argparse import importlib.util import sys from pathlib import Path import lightgbm as lgb import numpy as np import pandas as pd from gensim.models import Word2Vec def load_module(name: str, path: Path): spec = importlib.util.spec_from_file_location(name, path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None sys.modules[name] = module spec.loader.exec_module(module) return module def make_subs(root: Path, out_dir: Path, version: str, score: np.ndarray, ratios: list[float], thresholds: list[float]) -> None: known = np.load(root / "cached_scores" / "test_known_mask.npy").astype(bool) current_path = root / "validation_runs" / "dynamic_seed202" / "node2vec_deepwalk_submission" / "submission_content_mf_deepwalk_node2vec_lgb_th0.480000.csv" current = pd.read_csv(current_path)["Predicted"].to_numpy(np.int8) if current_path.exists() else None rows = [] for ratio in ratios: pred = np.zeros(len(score), dtype=np.int8) pred[np.argsort(score)[-int(round(len(score) * ratio)):]] = 1 pred[known] = 1 path = out_dir / f"submission_{version}_r{ratio:.3f}.csv" pd.DataFrame({"Index": np.arange(len(pred), dtype=np.int64), "Predicted": pred}).to_csv(path, index=False) changed = int((pred != current).sum()) if current is not None else -1 rows.append({"path": str(path), "rule": f"r{ratio:.3f}", "positive_ratio": float(pred.mean()), "changed_vs_current": changed}) print(path, int(pred.sum()), float(pred.mean()), "changed", changed) for th in thresholds: pred = (score >= th).astype(np.int8) pred[known] = 1 path = out_dir / f"submission_{version}_th{th:.6f}.csv" pd.DataFrame({"Index": np.arange(len(pred), dtype=np.int64), "Predicted": pred}).to_csv(path, index=False) changed = int((pred != current).sum()) if current is not None else -1 rows.append({"path": str(path), "rule": f"th{th:.6f}", "positive_ratio": float(pred.mean()), "changed_vs_current": changed}) print(path, int(pred.sum()), float(pred.mean()), "changed", changed) pd.DataFrame(rows).to_csv(out_dir / f"{version}_submission_summary.csv", index=False) def aggregate(blocks: list[np.ndarray]) -> np.ndarray: cos_stack = np.vstack([b[:, 1] for b in blocks]) dot_stack = np.vstack([b[:, 0] for b in blocks]) rank_stack = np.vstack([b[:, 10] for b in blocks]) agree = (rank_stack >= 0.5).sum(axis=0).astype(np.float32) return np.column_stack( [ cos_stack.mean(axis=0), cos_stack.std(axis=0), cos_stack.max(axis=0), cos_stack.min(axis=0), dot_stack.mean(axis=0), dot_stack.std(axis=0), rank_stack.mean(axis=0), rank_stack.std(axis=0), rank_stack.max(axis=0), rank_stack.min(axis=0), agree, ] ).astype(np.float32) def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--package-root", type=Path, default=Path(__file__).resolve().parents[1]) parser.add_argument("--split-seed", type=int, default=202) parser.add_argument("--main-val-score-file", type=Path, required=True) parser.add_argument("--versions", nargs="+", required=True) parser.add_argument("--threshold", type=float, default=0.5) parser.add_argument("--ratios", nargs="*", type=float, default=[0.498, 0.499, 0.500, 0.501, 0.502]) args = parser.parse_args() root = args.package_root stack = load_module("stack", root / "code" / "stack_rank_calibration.py") lgcn = load_module("lgcn", root / "code" / "train_val_lgcn_ensemble.py") post = load_module("post", root / "code" / "post95_ablation.py") gen = load_module("gen", root / "code" / "generate_post95_submission.py") extra = load_module("extra", root / "code" / "extra_score_sources_ablation.py") rw = load_module("rw", root / "code" / "randomwalk_systematic_ablation.py") cfgs = {c.version_name: c for c in rw.small_configs() + rw.graph_configs() + rw.extra_configs()} version_name = "rwens_" + "_".join(v.replace("_d128_l", "l").replace("_w10_win10", "") for v in args.versions) sys_dir = root / "validation_runs" / f"dynamic_seed{args.split_seed}" / "randomwalk_systematic" out_dir = root / "validation_runs" / f"dynamic_seed{args.split_seed}" / "randomwalk_ensemble_submission" out_dir.mkdir(parents=True, exist_ok=True) train_refs, pairs, y, X_val = rw.build_base_features(root, args.split_seed, args.main_val_score_file) val_blocks = [] for version in args.versions: cfg = cfgs[version] model = Word2Vec.load(str(sys_dir / "models" / f"{version}.model")) block, _ = rw.pair_feature_block(model, pairs, cfg, root, args.split_seed, train_refs) val_blocks.append(block) X_val = np.column_stack([X_val, *val_blocks, aggregate(val_blocks)]).astype(np.float32) print("fit", X_val.shape) clf = lgb.LGBMClassifier( n_estimators=1400, learning_rate=0.022, num_leaves=31, subsample=0.9, colsample_bytree=0.9, reg_lambda=6.0, min_child_samples=80, objective="binary", n_jobs=8, verbose=-1, random_state=202, ) clf.fit(X_val, y) test_pairs = np.array(gen.read_txt(root / "data_and_docs" / "bipartite_test_ann.txt"), dtype=np.int64) main_test = np.load(root / "validation_runs" / f"dynamic_seed{args.split_seed}" / "post95_test_scores" / "dyn202_l2d512_bpr_bigbatch_more" / "scores" / "test_vanilla_ensemble_mean.npy").astype(np.float32) full_refs = pd.DataFrame(gen.read_txt(root / "data_and_docs" / "bipartite_train_ann.txt"), columns=["source", "target"]) test_builder = stack.ExplicitGraphFeatures(root, full_refs) Xht = test_builder.transform(test_pairs) X_test = np.column_stack( [ stack.add_rank_features(test_pairs, main_test), Xht, post.negative_evidence_features(Xht, main_test), gen.topk_content_similarity_fast(root, test_pairs, test_builder), ] ).astype(np.float32) selected = [Path(x.strip()) for x in (root / "validation_runs" / f"dynamic_seed{args.split_seed}" / "post95_submission" / "selected_variant_val_scores.txt").read_text().splitlines() if x.strip()] test_scores = [] for p in selected: rel = p.resolve().relative_to(root / "validation_runs" / f"dynamic_seed{args.split_seed}") tp = root / "validation_runs" / f"dynamic_seed{args.split_seed}" / "post95_test_scores" / rel.parent / rel.name.replace("val_", "test_", 1) test_scores.append(np.load(tp).astype(np.float32)) X_test = np.column_stack([X_test, gen.variant_feature_matrix(post, test_scores)]).astype(np.float32) content_test = extra.content_mean_score(root, test_pairs, test_builder) mf_test = np.load(root / "validation_runs" / f"dynamic_seed{args.split_seed}" / "extra_bprmf_submission" / "test_mf_bpr_dynamic_s202_d256_e220.npy").astype(np.float32) Xct, _ = extra.score_to_features(content_test, "content_mean_cos", test_pairs) Xmt, _ = extra.score_to_features(mf_test, "mf_bpr", test_pairs) X_test = np.column_stack([X_test, Xct, Xmt]).astype(np.float32) test_blocks = [] for version in args.versions: cfg = cfgs[version] model = Word2Vec.load(str(sys_dir / "models" / f"{version}.model")) block, _ = rw.pair_feature_block(model, test_pairs, cfg, root, args.split_seed, full_refs) test_blocks.append(block) X_test = np.column_stack([X_test, *test_blocks, aggregate(test_blocks)]).astype(np.float32) print("predict", X_test.shape) score = clf.predict_proba(X_test)[:, 1].astype(np.float32) np.save(out_dir / f"test_{version_name}_pred.npy", score) make_subs(root, out_dir, version_name, score, args.ratios, [args.threshold, 0.48, 0.50]) if __name__ == "__main__": main()