CoolFace
Apppublic

S0L009/Luna-GNN-Scorer-InferenceAPI

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
pipelines.py530 linesDownload Raw Back to root
1"""2Helpful model training, testing, and inference functions.3"""4 5import torch6import torch.nn as nn7from torch_geometric.data import Data8 9import numpy as np10import pickle as pkl11from typing import Callable, Optional12 13from data import RegenerativeData, Neo4jAggregator14from model import DirWeighedGNN, MultiheadScoring, flexible_device15from loss import masked_mse, unmasked_mse16 17 18def inference_step(19    score_model: nn.Module,20    embeddings: torch.Tensor,21    edge_index: torch.Tensor,22    edge_weights: torch.Tensor,23    categorical_src_indices: list[torch.Tensor],24    categorical_trg_indices: list[torch.Tensor],25    gnn_model: Optional[nn.Module] = None,26    all_pairs: bool = False,27    training: bool = True28) -> list[torch.Tensor]:29    """30    Performs an inference step to compute predicted scores using a scoring model.31 32    Parameters33    ----------34    score_model : nn.Module35        The scoring model used to compute scores based on embeddings.36    embeddings : torch.Tensor37        The input vertex embeddings.38    edge_index : torch.Tensor39        Tensor containing edge indices that define the graph structure40        in COO format.41    edge_weights : torch.Tensor42        Weights associated with each edge in the graph.43    categorical_src_indices : list[torch.Tensor]44        Tensor containing source vertex indices for each category.45    categorical_trg_indices : list[torch.Tensor]46        Tensor containing target vertex indices for each category.47    gnn_model : nn.Module, optional48        The GNN model to extract refinined vertex embeddings, by default None.49    all_pairs : bool, optional50        Flag to indicate whether to compute scores for all pairs, by default False.51    training : bool, optional52        Flag to indicate if the model is in training mode, by default True.53 54    Returns55    -------56    list[torch.Tensor]57        The list of predicted scores for each category.58    """59 60    if gnn_model is not None:61        embeddings = gnn_model(62            x=embeddings,63            edge_index=edge_index,64            edge_weights=edge_weights,65            training=training66        )67    68    pred_scores = score_model(69        embeddings=embeddings,70        categorical_src_indices=categorical_src_indices,71        categorical_trg_indices=categorical_trg_indices,72        all_pairs=all_pairs,73        training=training74    )75    76    return pred_scores77 78 79def loss_step(80    data: RegenerativeData,81    score_model: nn.Module,82    criterion: Callable,83    gnn_model: Optional[nn.Module] = None,84    training: bool = True85) -> torch.Tensor:86    """87    Computes the model prediction loss on input data.88 89    Parameters90    ----------91    data: RegenerativeData92        A `RegenerativeData` object generated by a `GraphAggregator`93        when called with `generate_graph_data`.94    score_model: nn.Module95        The scoring model based on vertex embeddings.96    criterion: Callable97        The loss function.98    gnn_model: nn.Module, optional99        The GNN model to extract vertex embeddings. If not provided,100        the `score_model` is expected to use graph embeddings.101        Defaults to None.102    training: bool, optional103        Whether or not the loss is computed for model training.104        Defaults to True.105 106    Returns107    -------108    loss: torch.Tensor109        The loss for the input data.110    """111    pred_scores = inference_step(112        score_model=score_model,113        embeddings=data.x,114        edge_index=data.edge_index,115        edge_weights=data.masked_edge_attr,116        categorical_src_indices=data.sources,117        categorical_trg_indices=data.targets,118        gnn_model=gnn_model,119        all_pairs=False,120        training=training121    )122    123    losses = [criterion(pred, y, mask)124              for pred, y, mask125              in zip(pred_scores, data.y, data.mask)]126    loss = sum(losses) / len(losses)127    128    return loss129 130 131def train_scores_predictor(132    data: RegenerativeData,133    score_model: nn.Module,134    optim: torch.optim.Optimizer,135    criterion: Callable,136    steps: int,137    gnn_model: nn.Module = None,138    val_data: list[RegenerativeData] = [],139    val_criteria: list[Callable] = [],140    val_names: list[str] = [],141    early_stop: Callable = None,142    print_every: int = None,143    verbose: bool = True,144    seed: int = 142937145) -> nn.Module | tuple[nn.Module, nn.Module]:146    """147    Train the scoring predictor model.148 149    Parameters150    ----------151    data: RegenerativeData152        The training data.153    score_model: nn.Module154        The scoring model used to turn embeddings into scores.155    optim: torch.optim.Optimizer156        The optimizer to use for training.157    criterion: Callable158        The loss function.159    steps: int160        The number of training steps.161    gnn_model: nn.Module, optional162        The GNN model used to extract embeddings, by default None.163    val_data: list[RegenerativeData], optional164        List of validation datasets, by default [].165    val_criteria: list[Callable], optional166        The validation loss functions, by default []. If [],167        will use `criterion` for all validation datasets.168    val_names: list[str], optional169        The names of the validation datasets, by default [].170    early_stop: Callable, optional171        The early stopping criterion. If None, the training will run to completion.172        The default is None.173        174        The criterion should accept a dictionary object and return True175        if early stopping should be triggered. The dictionary contains the values of176        various metrics throughout the training process. Specifically, let ``name``177        be the name of any metric name (where ``name`` is either 'loss' or a name in178        ``val_names``), then the dictionary will contain the following keys:179        180        * ``name``: A (step, value) tuple of the current step and the current value181          of the metric.182        183        * ``name_low``: A (step, value) tuple of the lowest value of the metric and184          the step.185        186        * ``name_high``: A (step, value) tuple of the highest value of the metric187          and the step.188        189        * ``name_hist``: A list of all values of the metric, indexed by step.190        191        Example::192 193            def early_stop(metrics):194                # stop if the `val` metric has increased from its195                # lowest value by more than 0.1196                return metrics['val'][1] - metrics['val_low'][1] > 0.1197    198    print_every: int, optional199        How often to print the loss, by default None. If None,200        will print every 10% of the steps.201    verbose: bool, optional202        Whether to print messages during training, by default True.203    seed: int, optional204        The seed to use for random number generation, by default 142937.205 206    Returns207    -------208    float or tuple[float, float]209        The training loss, and optionally the validation loss if provided.210    """    211    torch.manual_seed(seed)212    213    if print_every is None:214        print_every = max(1, steps // 10)215    216    if not val_criteria:217        val_criteria = [criterion for _ in range(len(val_data))]218    219    if not val_names:220        val_names = [f'Val-{i}' for i in range(len(val_data))]221    222    if early_stop is None:223        early_stop = lambda x: False224    225    metrics = {}226    for name in ['loss'] + val_names:227        metrics = metrics | {228            f'{name}': (-1, None),229            f'{name}_low': (-1, float('inf')),230            f'{name}_high': (-1, -float('inf')),231            f'{name}_hist': [],232        }233    234    def update_metric(name: str, step: int, value: float):235        metrics[f'{name}'] = (step, value)236        if metrics[f'{name}_low'][1] > value:237            metrics[f'{name}_low'] = (step, value)238        if metrics[f'{name}_high'][1] < value:239            metrics[f'{name}_high'] = (step, value)240        metrics[f'{name}_hist'].append(value)241    242    for step in range(steps):243        data.regenerate()244        optim.zero_grad()245        loss = loss_step(246            data=data,247            gnn_model=gnn_model,248            score_model=score_model,249            criterion=criterion,250            training=True251        )252        loss.backward()253        optim.step()254        255        if verbose and step % print_every == 0:256            print(f"[{step:4d}]  Loss: {loss:.5f}", end='    ')257        258        loss = loss.detach().cpu().item()259        update_metric('loss', step, loss)260        261        with torch.no_grad():            262            for nm, vd, cr in zip(val_names, val_data, val_criteria):263                vd.regenerate()264                val_loss = loss_step(265                    data=vd,266                    gnn_model=gnn_model,267                    score_model=score_model,268                    criterion=cr,269                    training=False270                )271                val_loss = val_loss.detach().cpu().item()272                update_metric(nm, step, val_loss)273                274                if verbose and step % print_every == 0:275                    print(f"{nm}: {val_loss:.5f}", end='    ')276        277        if verbose and step % print_every == 0:278            print()279        280        if step > 0 and early_stop(metrics):281            if verbose:282                print('Stopping early...')283            break284    285    return metrics286 287 288def predict_scores(289    data: RegenerativeData|Data,290    score_model: nn.Module,291    src_indices: torch.Tensor = None,292    trg_indices: torch.Tensor = None,293    gnn_model: nn.Module = None,294    all_pairs: bool = True295) -> list[torch.Tensor]:296    """297    Compute scores for the given data using.298 299    Parameters300    ----------301    data: RegenerativeData|Data302        The data to compute scores for.303    score_model: nn.Module304        The scoring model used to turn embeddings into scores.305    src_indices: torch.Tensor, optional306        The source vertex indices for each category. Defaults to None307        and reads from `data.sources`.308    trg_indices: torch.Tensor, optional309        The target vertex indices for each category. Defaults to None310        and reads from `data.targets`.311    gnn_model: nn.Module, optional312        The GNN model used to extract embeddings, by default None.313    all_pairs: bool, optional314        Whether to compute scores for all pairs of source and target315        vertices, by default True.316 317    Returns318    -------319    scores: list[torch.Tensor]320        The predicted scores.321    """322    if src_indices is None:323        src_indices = data.sources324    if trg_indices is None:325        trg_indices = data.targets326    327    with torch.no_grad():328        scores = inference_step(329            score_model=score_model,330            embeddings=data.x,331            edge_index=data.edge_index,332            edge_weights=data.edge_attr,333            categorical_src_indices=src_indices,334            categorical_trg_indices=trg_indices,335            gnn_model=gnn_model,336            all_pairs=all_pairs,337            training=False338        )339    340    return scores341 342def get_query_matrices(343    data: RegenerativeData|Data,344    score_model: nn.Module,345    src_indices: torch.Tensor = None,346    trg_indices: torch.Tensor = None,347    gnn_model: nn.Module = None,348    all_pairs: bool = True349) -> list[torch.Tensor]:350    """351    Compute scores for the given data using.352 353    Parameters354    ----------355    data: RegenerativeData|Data356        The data to compute scores for.357    score_model: nn.Module358        The scoring model used to turn embeddings into scores.359    src_indices: torch.Tensor, optional360        The source vertex indices for each category. Defaults to None361        and reads from `data.sources`.362    trg_indices: torch.Tensor, optional363        The target vertex indices for each category. Defaults to None364        and reads from `data.targets`.365    gnn_model: nn.Module, optional366        The GNN model used to extract embeddings, by default None.367    all_pairs: bool, optional368        Whether to compute scores for all pairs of source and target369        vertices, by default True.370 371    Returns372    -------373    scores: list[torch.Tensor]374        The predicted scores.375    """376    if src_indices is None:377        src_indices = data.sources378    if trg_indices is None:379        trg_indices = data.targets380    381    with torch.no_grad():382        if gnn_model is not None:383            embeddings = gnn_model(384                x=data.x,385                edge_index=data.edge_index,386                edge_weights=data.edge_attr,387                training=False388            )389        390        queries = score_model(391            embeddings=embeddings,392            categorical_src_indices=src_indices,   # user393            categorical_trg_indices=trg_indices,   # categories394            all_pairs=all_pairs,395            training=False,396            just_query=True397        )398    399    obj = {400        'queries': queries,401        'embeddings': embeddings402    }403    torch.save(obj, 'query_cache.pt')404    405    return obj406 407def neo4j_training_pipeline(408    db_path: str,409    graph_steps: int = 100,410    raw_steps: int = 100411):412    agg = Neo4jAggregator(db_path)413    agg.collect()414    device = flexible_device()415    416    masked_data = agg.generate_graph_data(417        device=device,418        prob_masked=0.6,419    )420 421    unmasked_data = agg.generate_graph_data(422        device=device,423        prob_masked=0.0424    )425    426    DEMB = agg.vertex_attrs[0].shape[0]427    DMODEL = 512428    429    gnn_model = DirWeighedGNN(430        in_channels=DEMB,431        hidden_channels=1024,432        out_channels=DEMB433    ).to(device)434 435    score_model = MultiheadScoring(436        nheads=agg.category_count,437        demb=DEMB,438        dmodel=DMODEL439    ).to(device)440 441    raw_score_model = MultiheadScoring(442        nheads=agg.category_count,443        demb=DEMB,444        dmodel=DMODEL445    ).to(device)446    447    optim_graph = torch.optim.Adam(448        list(gnn_model.parameters()) + list(score_model.parameters()),449        lr=1e-3450    )451    452    train_scores_predictor(453        data=masked_data,454        score_model=score_model,455        gnn_model=gnn_model,456        optim=optim_graph,457        criterion=masked_mse,458        steps=100459    )460 461    optim_raw = torch.optim.Adam(462        raw_score_model.parameters(),463        lr=1e-3464    )465    466    train_scores_predictor(467        data=masked_data,468        score_model=score_model,469        gnn_model=gnn_model,470        optim=optim_graph,471        criterion=masked_mse,472        steps=graph_steps473    )474    475    train_scores_predictor(476        data=unmasked_data,477        score_model=raw_score_model,478        optim=optim_raw,479        criterion=unmasked_mse,480        steps=raw_steps481    )482    483    torch.save(484        {485            'gnn': gnn_model.state_dict(),486            'score_model': score_model.state_dict(),487            'raw_score_model': raw_score_model.state_dict(),488        },489        'models.pt'490    )491    492    torch.save(493        {494            'optim_graph': optim_graph.state_dict(),495            'optim_raw': optim_raw.state_dict()496        },497        'optims.pt'498    )499 500def get_cached_scores(501    cache_path: str,502    user_id: str,503    category: str,504    target_ids: list[str],505):506    return np.random.random()507 508    with open(cache_path, 'rb') as f:509        pass510        obj = pkl.load(f)511    512    def score(user_id, target_id):513        user = obj.id2vertex[user_id]514        target = obj.id2vertex[target_id]515        category = obj.id2category[category]516        return obj.scores[user][category][target]517    518    return {519        trg_id: score(user_id, trg_id)520        for trg_id in target_ids521    }522 523 524def recommend_users(525    venue_id: str,526    user_id: str,527    num_users: int = 10528):529    ...530