mirror of
https://github.com/RYDE-WORK/lnp_ml.git
synced 2026-09-18 14:23:20 +08:00
增加特征重要性计算
This commit is contained in:
parent
4b75b7406d
commit
3b38727053
9
Makefile
9
Makefile
@ -126,10 +126,11 @@ train: requirements
|
||||
# INTERPRETABILITY #
|
||||
#################################################################################
|
||||
# 参数:
|
||||
# TASK 目标任务 (delivery, size, pdi, ee, biodist, toxic; 默认: delivery)
|
||||
# METHOD 方法 (ig, ablation, attention, all; 默认: all)
|
||||
# DATA 数据路径 (默认: data/processed/train.parquet)
|
||||
# MODEL 模型路径 (默认: models/model.pt)
|
||||
# TASK 目标任务 (delivery, size, pdi, ee, biodist, toxic, all; 默认: delivery)
|
||||
# 如果指定 'all',将依次计算所有 6 个任务
|
||||
# METHOD 方法 (ig, ablation, attention, all; 默认: ig)
|
||||
# DATA 数据路径 (默认: data/interim/internal.csv,即最终模型的全量训练数据)
|
||||
# MODEL 模型路径 (默认: models/final/model.pt)
|
||||
|
||||
TASK_FLAG = $(if $(TASK),--task $(TASK),)
|
||||
METHOD_FLAG = $(if $(METHOD),--method $(METHOD),)
|
||||
|
||||
@ -29,8 +29,8 @@ from torch.utils.data import DataLoader
|
||||
from loguru import logger
|
||||
from tqdm import tqdm
|
||||
|
||||
from lnp_ml.config import MODELS_DIR, PROCESSED_DATA_DIR, REPORTS_DIR
|
||||
from lnp_ml.dataset import LNPDataset, collate_fn
|
||||
from lnp_ml.config import MODELS_DIR, INTERIM_DATA_DIR, REPORTS_DIR
|
||||
from lnp_ml.dataset import LNPDataset, collate_fn, process_dataframe
|
||||
from lnp_ml.modeling.predict import load_model
|
||||
from lnp_ml.modeling.models import LNPModel, LNPModelWithoutMPNN
|
||||
|
||||
@ -332,12 +332,12 @@ def save_csv(
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Token-level Feature Importance")
|
||||
parser.add_argument("--model-path", type=str, default=str(MODELS_DIR / "model.pt"),
|
||||
parser.add_argument("--model-path", type=str, default=str(MODELS_DIR / "final" / "model.pt"),
|
||||
help="Path to trained model checkpoint")
|
||||
parser.add_argument("--data-path", type=str, default=str(PROCESSED_DATA_DIR / "train.parquet"),
|
||||
help="Path to data (parquet) for computing importance")
|
||||
parser.add_argument("--task", type=str, default="delivery", choices=TASKS,
|
||||
help="Target task for importance computation")
|
||||
parser.add_argument("--data-path", type=str, default=str(INTERIM_DATA_DIR / "internal.csv"),
|
||||
help="Path to data (.csv or .parquet) for computing importance")
|
||||
parser.add_argument("--task", type=str, default="all", choices=TASKS + ["all"],
|
||||
help="Target task for importance computation ('all' to run on all tasks)")
|
||||
parser.add_argument("--method", type=str, default="ig",
|
||||
choices=["ig", "ablation", "attention", "all"],
|
||||
help="Which method(s) to run")
|
||||
@ -363,7 +363,12 @@ def main() -> None:
|
||||
logger.info(f"Tokens ({len(token_names)}): {token_names}")
|
||||
|
||||
# ── Load data ──
|
||||
df = pd.read_parquet(args.data_path)
|
||||
data_path = Path(args.data_path)
|
||||
if data_path.suffix == ".csv":
|
||||
df = pd.read_csv(data_path)
|
||||
df = process_dataframe(df)
|
||||
else:
|
||||
df = pd.read_parquet(data_path)
|
||||
dataset = LNPDataset(df)
|
||||
loader = DataLoader(dataset, batch_size=args.batch_size, shuffle=False, collate_fn=collate_fn)
|
||||
logger.info(f"Samples: {len(dataset)}")
|
||||
@ -382,54 +387,62 @@ def main() -> None:
|
||||
else [args.method]
|
||||
)
|
||||
|
||||
results: Dict[str, np.ndarray] = {}
|
||||
# Determine tasks to process
|
||||
tasks_to_run = TASKS if args.task == "all" else [args.task]
|
||||
|
||||
for method in methods:
|
||||
for task in tasks_to_run:
|
||||
logger.info(f"\n{'#'*60}")
|
||||
logger.info(f"# Processing task: {task}")
|
||||
logger.info(f"{'#'*60}")
|
||||
|
||||
results: Dict[str, np.ndarray] = {}
|
||||
|
||||
for method in methods:
|
||||
logger.info(f"\n{'='*60}")
|
||||
logger.info(f"Computing: {method} (task={task})")
|
||||
logger.info(f"{'='*60}")
|
||||
|
||||
if method == "ig":
|
||||
imp = integrated_gradients_importance(
|
||||
model, all_tokens, device,
|
||||
task=task, batch_size=args.batch_size, n_steps=args.n_steps,
|
||||
)
|
||||
results["Integrated Gradients"] = imp
|
||||
|
||||
elif method == "ablation":
|
||||
imp = token_ablation_importance(
|
||||
model, all_tokens, device,
|
||||
task=task, batch_size=args.batch_size,
|
||||
)
|
||||
results["Token Ablation"] = imp
|
||||
|
||||
elif method == "attention":
|
||||
imp = fusion_attention_importance(
|
||||
model, all_tokens, device,
|
||||
batch_size=args.batch_size,
|
||||
)
|
||||
if imp is not None:
|
||||
results["Fusion Attention"] = imp
|
||||
|
||||
# ── Print summary ──
|
||||
logger.info(f"\n{'='*60}")
|
||||
logger.info(f"Computing: {method}")
|
||||
logger.info(f"Token Importance Summary (task={task})")
|
||||
logger.info(f"{'='*60}")
|
||||
for method_name, importance in results.items():
|
||||
normed = normalize(importance)
|
||||
order = np.argsort(-normed)
|
||||
logger.info(f"\n {method_name}:")
|
||||
for rank, idx in enumerate(order, 1):
|
||||
logger.info(f" {rank:>2d}. {token_names[idx]:<10s} {normed[idx]:.4f}")
|
||||
|
||||
if method == "ig":
|
||||
imp = integrated_gradients_importance(
|
||||
model, all_tokens, device,
|
||||
task=args.task, batch_size=args.batch_size, n_steps=args.n_steps,
|
||||
)
|
||||
results["Integrated Gradients"] = imp
|
||||
logger.info(f"\n Gate values (sigmoid):")
|
||||
for name, val in sorted(gv.items(), key=lambda x: -x[1]):
|
||||
logger.info(f" {name:<10s} {val:.4f}")
|
||||
|
||||
elif method == "ablation":
|
||||
imp = token_ablation_importance(
|
||||
model, all_tokens, device,
|
||||
task=args.task, batch_size=args.batch_size,
|
||||
)
|
||||
results["Token Ablation"] = imp
|
||||
|
||||
elif method == "attention":
|
||||
imp = fusion_attention_importance(
|
||||
model, all_tokens, device,
|
||||
batch_size=args.batch_size,
|
||||
)
|
||||
if imp is not None:
|
||||
results["Fusion Attention"] = imp
|
||||
|
||||
# ── Print summary ──
|
||||
logger.info(f"\n{'='*60}")
|
||||
logger.info(f"Token Importance Summary (task={args.task})")
|
||||
logger.info(f"{'='*60}")
|
||||
for method_name, importance in results.items():
|
||||
normed = normalize(importance)
|
||||
order = np.argsort(-normed)
|
||||
logger.info(f"\n {method_name}:")
|
||||
for rank, idx in enumerate(order, 1):
|
||||
logger.info(f" {rank:>2d}. {token_names[idx]:<10s} {normed[idx]:.4f}")
|
||||
|
||||
logger.info(f"\n Gate values (sigmoid):")
|
||||
for name, val in sorted(gv.items(), key=lambda x: -x[1]):
|
||||
logger.info(f" {name:<10s} {val:.4f}")
|
||||
|
||||
# ── Save results ──
|
||||
if results:
|
||||
plot_token_importance(results, token_names, args.task, out_dir)
|
||||
save_csv(results, token_names, args.task, out_dir, gate_vals=gv)
|
||||
# ── Save results ──
|
||||
if results:
|
||||
plot_token_importance(results, token_names, task, out_dir)
|
||||
save_csv(results, token_names, task, out_dir, gate_vals=gv)
|
||||
|
||||
logger.info("\nDone!")
|
||||
|
||||
|
||||
9
reports/feature_importance/token_importance_biodist.csv
Normal file
9
reports/feature_importance/token_importance_biodist.csv
Normal file
@ -0,0 +1,9 @@
|
||||
token,Integrated Gradients_raw,Integrated Gradients_normalized,gate_sigmoid
|
||||
desc,3.2013970842762367e-09,0.17906809140868585,0.5030443072319031
|
||||
mpnn,3.109777150387161e-09,0.17394338920379962,0.5024935007095337
|
||||
maccs,3.0657202874248063e-09,0.1714790968475434,0.5030479431152344
|
||||
morgan,3.020539287877718e-09,0.16895192663283606,0.5045571327209473
|
||||
help,1.937320535640997e-09,0.10836278088337024,0.49689680337905884
|
||||
comp,1.876732953403087e-09,0.10497385335304418,0.5007365345954895
|
||||
exp,1.6503372406931126e-09,0.09231055445232395,0.5002157688140869
|
||||
phys,1.6274562664095572e-11,0.0009103072183966515,0.49989768862724304
|
||||
|
500 Internal Server Error
Gitea Version: 1.23.8 |