Buckets:
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| from __future__ import annotations | |
| import math | |
| import random | |
| from dataclasses import dataclass | |
| from typing import Dict, List, Sequence, Tuple | |
| import numpy as np | |
| import torch | |
| from tqdm.auto import tqdm | |
| from agent_rec.config import POS_TOPK, POS_TOPK_BY_PART | |
| from agent_rec.data import build_training_pairs, stratified_train_valid_split | |
| from agent_rec.graph_utils import GraphFeaturePack, build_graph_features | |
| from agent_rec.models.graph import bpr_loss | |
| from agent_rec.run_common import build_pos_pairs, cache_key_from_meta, load_or_build_training_cache, shared_cache_dir | |
| class GraphDataPack: | |
| train_qids: List[str] | |
| valid_qids: List[str] | |
| pairs_idx_np: np.ndarray | |
| interactions: List[Tuple[int, int]] | |
| feature_pack: GraphFeaturePack | |
| rankings_train: Dict[str, List[str]] | |
| def prepare_graph_data( | |
| *, | |
| args, | |
| boot, | |
| use_model_content_vector: bool, | |
| use_tool_content_vector: bool, | |
| max_features: int, | |
| ) -> GraphDataPack: | |
| bundle = boot.bundle | |
| tools = boot.tools | |
| all_agents = bundle.all_agents | |
| all_questions = bundle.all_questions | |
| all_rankings = bundle.all_rankings | |
| qid_to_part = bundle.qid_to_part | |
| q_ids = boot.q_ids | |
| a_ids = boot.a_ids | |
| qid2idx = boot.qid2idx | |
| aid2idx = boot.aid2idx | |
| qids_in_rank = boot.qids_in_rank | |
| data_sig = boot.data_sig | |
| want_meta = { | |
| "data_sig": data_sig, | |
| "pos_topk_by_part": POS_TOPK_BY_PART, | |
| "neg_per_pos": int(args.neg_per_pos), | |
| "rng_seed_pairs": int(args.rng_seed_pairs), | |
| "split_seed": int(args.split_seed), | |
| "valid_ratio": float(args.valid_ratio), | |
| } | |
| training_cache_dir = shared_cache_dir(args.data_root, "training", f"{data_sig}_{cache_key_from_meta(want_meta)}") | |
| def build_cache(): | |
| train_qids, valid_qids = stratified_train_valid_split( | |
| qids_in_rank, qid_to_part=qid_to_part, valid_ratio=args.valid_ratio, seed=args.split_seed | |
| ) | |
| print(f"[split] train={len(train_qids)} valid={len(valid_qids)}") | |
| rankings_train = {qid: all_rankings[qid] for qid in train_qids} | |
| pairs = build_training_pairs( | |
| rankings_train, | |
| a_ids, | |
| qid_to_part=qid_to_part, | |
| pos_topk_by_part=POS_TOPK_BY_PART, | |
| pos_topk_default=POS_TOPK, | |
| neg_per_pos=args.neg_per_pos, | |
| rng_seed=args.rng_seed_pairs, | |
| ) | |
| pairs_idx = [(qid2idx[q], aid2idx[p], aid2idx[n]) for (q, p, n) in pairs] | |
| pairs_idx_np = np.array(pairs_idx, dtype=np.int64) | |
| return train_qids, valid_qids, pairs_idx_np | |
| train_qids, valid_qids, pairs_idx_np = load_or_build_training_cache( | |
| training_cache_dir, | |
| args.rebuild_training_cache, | |
| want_meta, | |
| build_cache, | |
| ) | |
| rankings_train = {qid: all_rankings[qid] for qid in train_qids} | |
| pos_pairs = build_pos_pairs( | |
| rankings_train, | |
| qid_to_part=qid_to_part, | |
| pos_topk_by_part=POS_TOPK_BY_PART, | |
| pos_topk_default=POS_TOPK, | |
| rng_seed=args.rng_seed_pairs, | |
| ) | |
| interactions = [(qid2idx[q], aid2idx[a]) for q, a in pos_pairs] | |
| feature_pack = build_graph_features( | |
| all_agents, | |
| all_questions, | |
| tools or {}, | |
| q_ids=q_ids, | |
| a_ids=a_ids, | |
| max_features=max_features, | |
| use_model_content_vector=use_model_content_vector, | |
| use_tool_content_vector=use_tool_content_vector, | |
| ) | |
| return GraphDataPack( | |
| train_qids=train_qids, | |
| valid_qids=valid_qids, | |
| pairs_idx_np=pairs_idx_np, | |
| interactions=interactions, | |
| feature_pack=feature_pack, | |
| rankings_train=rankings_train, | |
| ) | |
| def train_graph_bpr( | |
| model: torch.nn.Module, | |
| pairs: Sequence[Tuple[int, int, int]], | |
| *, | |
| batch_size: int, | |
| epochs: int, | |
| device: torch.device, | |
| optimizer: torch.optim.Optimizer, | |
| ) -> None: | |
| num_pairs = len(pairs) | |
| num_batches = math.ceil(num_pairs / batch_size) | |
| print(f"Training pairs: {num_pairs}, batches/epoch: {num_batches}") | |
| for epoch in range(1, epochs + 1): | |
| random.shuffle(pairs) | |
| total_loss = 0.0 | |
| pbar = tqdm(range(num_batches), desc=f"Epoch {epoch}/{epochs}", leave=True, dynamic_ncols=True) | |
| model.train() | |
| for b in pbar: | |
| batch = pairs[b * batch_size : (b + 1) * batch_size] | |
| if not batch: | |
| continue | |
| q_idx = torch.tensor([t[0] for t in batch], dtype=torch.long, device=device) | |
| pos_idx = torch.tensor([t[1] for t in batch], dtype=torch.long, device=device) | |
| neg_idx = torch.tensor([t[2] for t in batch], dtype=torch.long, device=device) | |
| out = model(q_idx, pos_idx, neg_idx) | |
| cl_loss = torch.tensor(0.0, device=device) | |
| if isinstance(out, tuple) and len(out) == 3: | |
| pos, neg, cl_loss = out | |
| else: | |
| pos, neg = out | |
| loss = bpr_loss(pos, neg) | |
| if hasattr(model, "cl_weight"): | |
| loss = loss + float(getattr(model, "cl_weight", 0.0)) * cl_loss | |
| optimizer.zero_grad() | |
| loss.backward() | |
| optimizer.step() | |
| total_loss += float(loss.item()) | |
| postfix = { | |
| "batch_loss": f"{loss.item():.4f}", | |
| "avg_loss": f"{(total_loss / (b + 1)):.4f}", | |
| } | |
| if cl_loss.item() != 0.0: | |
| postfix["cl_loss"] = f"{cl_loss.item():.4f}" | |
| pbar.set_postfix(postfix) | |
| print(f"Epoch {epoch}/{epochs} - loss: {(total_loss / num_batches if num_batches else 0.0):.4f}") | |
Xet Storage Details
- Size:
- 5.66 kB
- Xet hash:
- 522fb80919387a71da4cbb5f96cfd49a9591d2c0c52f924dcf35b30eba429691
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.