diff --git a/skills/rai-discovery/SKILL.md b/skills/rai-discovery/SKILL.md index 478c3fe..3c2122a 100644 --- a/skills/rai-discovery/SKILL.md +++ b/skills/rai-discovery/SKILL.md @@ -386,7 +386,12 @@ Each suggestion includes a `reasoners` field — an ordered list specifying the } ``` -After discovery, use the appropriate formulation skill for the chosen reasoner type. +After discovery, route to the appropriate skill based on the `reasoners` tag: + +- **prescriptive** → `rai-prescriptive-problem-formulation` +- **graph** → `rai-graph-analysis` +- **rules** → `rai-rules-authoring` +- **predictive** → `rai-predictive-modeling` --- diff --git a/skills/rai-graph-analysis/SKILL.md b/skills/rai-graph-analysis/SKILL.md index 724a800..6edf34d 100644 --- a/skills/rai-graph-analysis/SKILL.md +++ b/skills/rai-graph-analysis/SKILL.md @@ -37,6 +37,7 @@ description: Graph algorithm selection and execution on PyRel v1 models. Covers - Ontology design decisions (concept modeling, data mapping) — see `rai-ontology-design` - Optimization formulation (variables, constraints, objectives) — see `rai-prescriptive-problem-formulation` - Business rule authoring (validation, classification, alerting) — see `rai-rules-authoring` +- GNN graph construction for predictive pipelines — see `rai-predictive-modeling` **Overview (process steps):** 1. Study the existing model — understand base definitions, coding conventions, and what's already wired diff --git a/skills/rai-predictive-management/SKILL.md b/skills/rai-predictive-management/SKILL.md new file mode 100644 index 0000000..502ff12 --- /dev/null +++ b/skills/rai-predictive-management/SKILL.md @@ -0,0 +1,194 @@ +--- +name: rai-predictive-management +description: Register trained GNN models to Snowflake Model Registry and load previously trained models for inference. Use after training with rai-predictive-training, or when loading a saved model by registry key or run ID. +--- + +# Predictive Management + + +## Summary + +**What:** Encodes the model lifecycle workflow — registering trained GNN models and loading them for later inference. + +**When to use:** +- Saving a trained model to Snowflake Model Registry +- Loading a previously registered model by name and version +- Loading a model by run ID +- Understanding the train-register-load multi-session workflow + +**When NOT to use:** +- Defining concepts, building graphs, or configuring features — see `rai-predictive-modeling` +- Training a model or generating predictions — see `rai-predictive-training` + +**Overview:** +1. Register a trained model with `gnn.register_model()` +2. Load a model by registry key or run ID with `GNN(...).load()` +3. Generate predictions with the loaded model + +--- + +## Quick Reference + +**Register:** +```python +gnn.register_model( + model_database="DB", + model_schema="MODEL_REGISTRY", + model_name="fraud_detector", + version_name="v1", +) +``` + +**Load by registry key:** +```python +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + model_database="DB", model_schema="MODEL_REGISTRY", + model_name="fraud_detector", version_name="v1", +) +gnn.load() +``` + +**Load by run ID:** +```python +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + model_run_id="01c2d9a0-0711-c54d-000a-1dc707f7a1e6", +) +gnn.load() +``` + +**What to include vs. omit when loading:** + +| Include | Omit | +|---------|------| +| `database`, `schema` | `train`, `validation` | +| `exp_database`, `exp_schema` | `task_type`, `eval_metric` | +| `graph`, `pt` | hyperparameters (`device`, `n_epochs`, etc.) | +| model identifier (registry key or run ID) | | + +--- + +## Register a Model + +After `gnn.fit()` completes, register the model to the Snowflake Model Registry: + +```python +gnn.register_model( + model_database="DB", + model_schema="MODEL_REGISTRY", + model_name="fraud_detector", + version_name="v1", + comment="Initial training run", # optional +) +``` + +The combination of `(model_database, model_schema, model_name, version_name)` uniquely identifies a registered model. + +--- + +## Load a Model + +### By Registry Key + +```python +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + model_database="DB", + model_schema="MODEL_REGISTRY", + model_name="fraud_detector", + version_name="v1", +) +gnn.load() + +User.predictions = gnn.predictions(domain=Test) +``` + +### By Run ID + +```python +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + model_run_id="01c2d9a0-0711-c54d-000a-1dc707f7a1e6", +) +gnn.load() + +User.predictions = gnn.predictions(domain=Test) +``` + +After `gnn.load()`, use `gnn.predictions(domain=Test)` exactly as after `gnn.fit()` — the prediction workflow is the same (see `rai-predictive-training`). + +--- + +## Train-Register-Load Workflow + +A typical multi-session workflow: + +### Session 1: Train and Register + +```python +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + train=Train, validation=Val, + task_type="binary_classification", eval_metric="roc_auc", + has_time_column=True, + device="cuda", n_epochs=5, +) +gnn.fit() +gnn.register_model( + model_database="DB", model_schema="MODEL_REGISTRY", + model_name="fraud_detector", version_name="v1", +) +``` + +### Session 2: Load and Predict + +Rebuild the graph and PropertyTransformer with the same structure as training, then load: + +```python +# Rebuild graph and pt (same as training session) +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +# ... define edges ... +pt = PropertyTransformer(...) + +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + model_database="DB", model_schema="MODEL_REGISTRY", + model_name="fraud_detector", version_name="v1", +) +gnn.load() +User.predictions = gnn.predictions(domain=Test) +``` + +--- + +## Common Pitfalls + +| Mistake | Cause | Fix | +|---------|-------|-----| +| Calling `register_model()` before `fit()` | Model must be trained first — `RuntimeError`, no weights to serialize | Always call `gnn.fit()` before `gnn.register_model()` | +| Omitting `graph` or `pt` when loading | Loaded models still need the graph structure — `RuntimeError` | Provide the same `graph` and `pt` used during training | +| Passing `train`, `validation`, or hyperparameters when loading | These are training-only parameters — ignored or unexpected behavior | Omit `train`, `validation`, `task_type`, `eval_metric`, and all hyperparameters | +| Reusing the same `(name, version)` tuple | Registry keys must be unique — `RegistryError` | Use a new `version_name` for each registration | + +--- + +## Examples + +| Pattern | Description | File | +|---------|-------------|------| +| Register and load | Complete train-register-load workflow across sessions | [examples/register_and_load.py](examples/register_and_load.py) | diff --git a/skills/rai-predictive-management/examples/register_and_load.py b/skills/rai-predictive-management/examples/register_and_load.py new file mode 100644 index 0000000..2dce118 --- /dev/null +++ b/skills/rai-predictive-management/examples/register_and_load.py @@ -0,0 +1,52 @@ +""" +GNN Model Management — Register and Load Workflow +================================================== +Demonstrates the train-register-load pattern across sessions. + +Session 1: Train a model and register it to Snowflake Model Registry. +Session 2: Load the registered model and generate predictions. +""" + +# ── Session 1: Train and Register ─────────────────────────────────────────── +# Assumes data model from `rai-predictive-modeling`: +# gnn_graph, pt, Train, Val, Test, User + +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + train=Train, validation=Val, + task_type="binary_classification", eval_metric="roc_auc", + has_time_column=True, + device="cuda", n_epochs=5, +) +gnn.fit() + +gnn.register_model( + model_database="DB", + model_schema="MODEL_REGISTRY", + model_name="fraud_detector", + version_name="v1", + comment="Initial training run", +) + + +# ── Session 2: Load and Predict ───────────────────────────────────────────── +# REQUIRED: Rebuild the same graph and PT structure used during training +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +Edge = gnn_graph.Edge +# ... define edges (same as training session) ... +pt = PropertyTransformer(...) # same config as training session + +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + model_database="DB", + model_schema="MODEL_REGISTRY", + model_name="fraud_detector", + version_name="v1", +) +gnn.load() + +User.predictions = gnn.predictions(domain=Test) diff --git a/skills/rai-predictive-modeling/SKILL.md b/skills/rai-predictive-modeling/SKILL.md new file mode 100644 index 0000000..f3dd6b9 --- /dev/null +++ b/skills/rai-predictive-modeling/SKILL.md @@ -0,0 +1,333 @@ +--- +name: rai-predictive-modeling +description: Build GNN data models — concepts, Snowflake data loading, task relationships, graph edges, and PropertyTransformer features. +--- + +# Predictive Modeling + + +## Summary + +**What:** Encodes the data modeling workflow for GNN pipelines — from imports through graph construction and feature configuration. + +**When to use:** +- Defining domain concepts (entity types) and their primary keys +- Loading data from Snowflake tables into concepts +- Setting up train/validation/test task relationships +- Building a graph with edges between concepts +- Configuring PropertyTransformer feature annotations + +**When NOT to use:** +- Training a GNN model or generating predictions — see `rai-predictive-training` +- Registering or loading saved models — see `rai-predictive-management` +- Running graph algorithms (centrality, community, etc.) — see `rai-graph-analysis` + +**Overview:** +1. Imports and Model setup +2. Define concepts (graph entities + task tables) +3. Populate concepts from Snowflake +4. Define task relationships (train/val/test splits) +5. Build graph and define edges +6. Configure PropertyTransformer (optional) + +--- + +## Quick Reference + +**Imports:** +```python +from relationalai.semantics import Model, select, define, Integer, String, Any +from relationalai.semantics.reasoners.graph import Graph +from relationalai.semantics.reasoners.predictive import GNN, PropertyTransformer +``` + +Additional type imports as needed: `Date`, `DateTime`, `Float`. + +**Model setup:** +```python +model = Model("") +Concept, Table, Relationship = model.Concept, model.Table, model.Relationship +``` + +**Concept patterns:** + +| Pattern | Code | +|---------|------| +| Single PK | `User = Concept("User", identify_by={"user_id": Integer})` | +| Composite PK | `Class = Concept("Class", identify_by={"courseid": Integer, "year": Integer})` | +| No PK | `Transaction = Concept("Transaction")` | +| Task table | `train_table_concept = Concept("TrainTable")` | + +**Relationship arity rules:** + +| Task Type | Train/Val template | Test template | +|-----------|-------------------|---------------| +| classification (no time) | `f"{Source} has {Any:label}"` | `f"{Source}"` | +| classification (with time) | `f"{Source} at {Any:ts} has {Any:label}"` | `f"{Source} at {Any:ts}"` | +| regression (no time) | `f"{Source} has {Any:value}"` | `f"{Source}"` | +| regression (with time) | `f"{Source} at {Any:ts} has {Any:value}"` | `f"{Source} at {Any:ts}"` | +| link_prediction | `f"{Source} has {Target}"` | `f"{Source}"` | +| repeated_link_prediction | `f"{Source} at {Any:ts} has {Target}"` | `f"{Source} at {Any:ts}"` | + +**Graph init:** +```python +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +Edge = gnn_graph.Edge +``` + +--- + +## Imports and Model Setup + +Every GNN pipeline starts with these imports: + +```python +from relationalai.semantics import Model, select, define, Integer, String, Any +from relationalai.semantics.reasoners.graph import Graph +from relationalai.semantics.reasoners.predictive import GNN, PropertyTransformer +``` + +Add type imports based on your concept primary keys and data: +- `Integer` — integer primary keys +- `String` — string primary keys +- `Any` — flexible types in Relationship templates +- `Date`, `DateTime` — temporal fields +- `Float` — float values + +Unpack the DSL primitives from the Model: + +```python +model = Model("") +Concept, Table, Relationship = model.Concept, model.Table, model.Relationship +``` + +--- + +## Define and Populate Concepts + +### Graph Concepts + +Graph concepts represent domain entities. Define with `identify_by` for primary keys: + +```python +Customer = Concept("Customer", identify_by={"customer_id": Integer}) +Article = Concept("Article", identify_by={"article_id": Integer}) +Transaction = Concept("Transaction") # no PK — identity from data source +``` + +Populate from Snowflake using fully qualified table names: + +```python +define(Customer.new(Table("DB.SCHEMA.CUSTOMERS").to_schema())) +define(Article.new(Table("DB.SCHEMA.ARTICLES").to_schema())) +define(Transaction.new(Table("DB.SCHEMA.TRANSACTIONS").to_schema())) +``` + +### Task Table Concepts + +Task table concepts hold train/validation/test split data. They have no `identify_by`: + +```python +train_table_concept = Concept("TrainTable") +val_table_concept = Concept("ValidationTable") +test_table_concept = Concept("TestTable") + +define(train_table_concept.new(Table("DB.SCHEMA.TRAIN").to_schema())) +define(val_table_concept.new(Table("DB.SCHEMA.VAL").to_schema())) +define(test_table_concept.new(Table("DB.SCHEMA.TEST").to_schema())) +``` + +--- + +## Task Relationships + +Relationships encode the task structure using a template string. The template has three parts: +- **Head** = source concept (the concept being predicted on) +- **"at" clause** = optional timestamp field +- **"has" clause** = label (classification/regression) or target concept (link prediction) + +### Node Classification (with time) + +```python +Train = Relationship(f"{User} at {Any:timestamp} has {Any:target}") +define(Train(User, train_table_concept.timestamp, train_table_concept.target)).where( + User.user_id == train_table_concept.user +) + +Val = Relationship(f"{User} at {Any:timestamp} has {Any:target}") +define(Val(User, val_table_concept.timestamp, val_table_concept.target)).where( + User.user_id == val_table_concept.user +) + +Test = Relationship(f"{User} at {Any:timestamp}") +define(Test(User, test_table_concept.timestamp)).where( + User.user_id == test_table_concept.user +) +``` + +### Node Classification (no time) + +```python +Train = Relationship(f"{User} has {Any:target}") +Val = Relationship(f"{User} has {Any:target}") +Test = Relationship(f"{User}") +``` + +### Node Regression (with time) + +```python +Train = Relationship(f"{Article} at {Any:timestamp} has {Any:sales}") +define(Train(Article, train_table_concept.timestamp, train_table_concept.sales)).where( + Article.a_article_id == train_table_concept.article_id +) + +Val = Relationship(f"{Article} at {Any:timestamp} has {Any:sales}") +define(Val(Article, val_table_concept.timestamp, val_table_concept.sales)).where( + Article.a_article_id == val_table_concept.article_id +) + +Test = Relationship(f"{Article} at {Any:timestamp}") +define(Test(Article, test_table_concept.timestamp)).where( + Article.a_article_id == test_table_concept.article_id +) +``` + +> **Link prediction has two task types — do not choose for the user.** +> `link_prediction` (static, no timestamps) and `repeated_link_prediction` (temporal, with timestamps) serve different data shapes. The correct choice depends on whether the user's data has temporal ordering. Present both options and ask the user which applies to their data. + +### Link Prediction (with time / repeated_link_prediction) + +```python +Train = Relationship(f"{Customer} at {Any:timestamp} has {Article}") +define(Train(Customer, train_table_concept.timestamp, Article)).where( + Customer.customer_id == train_table_concept.customer_id, + Article.article_id == train_table_concept.article_id, +) + +Val = Relationship(f"{Customer} at {Any:timestamp} has {Article}") +define(Val(Customer, val_table_concept.timestamp, Article)).where( + Customer.customer_id == val_table_concept.customer_id, + Article.article_id == val_table_concept.article_id, +) + +Test = Relationship(f"{Customer} at {Any:timestamp}") +define(Test(Customer, test_table_concept.timestamp)).where( + Customer.customer_id == test_table_concept.customer_id, +) +``` + +### Link Prediction (no time) + +```python +Train = Relationship(f"{Customer} has {Article}") +Val = Relationship(f"{Customer} has {Article}") +Test = Relationship(f"{Customer}") +``` + +--- + +## Graph and Edges + +Create the graph with standard defaults and define edges via field equality: + +```python +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +Edge = gnn_graph.Edge + +define(Edge.new(src=Transaction, dst=Customer)).where( + Transaction.customer_id == Customer.customer_id) +define(Edge.new(src=Transaction, dst=Article)).where( + Transaction.article_id == Article.article_id) +``` + +### Self-Referential Edges + +When both sides of an edge are the same concept, use `.ref()`: + +```python +PostRef = Post.ref() +define(Edge.new(src=Post, dst=PostRef)).where( + PostRef.parent_id == Post.id) +``` + +### Multiple Typed Edges Between Same Pair + +```python +BB1Edge = Concept("BB1Edge", extends=[Edge]) +BB2Edge = Concept("BB2Edge", extends=[Edge]) + +Bref = B.ref() +define(BB1Edge.new(src=B, dst=Bref)).where(B.field1 == Bref.id) +define(BB2Edge.new(src=B, dst=Bref)).where(B.field2 == Bref.id) +``` + +--- + +## Feature Configuration + +`PropertyTransformer` annotates concept fields with their semantic types. Organize fields by concept, then combine: + +```python +# User features +category_user = [User.locale, User.gender] +datetime_user = [User.joinedAt] +continuous_user = [User.birthyear] + +# Event features +category_event = [Event.city, Event.state, Event.country] +datetime_event = [Event.start_time] + +pt = PropertyTransformer( + category=[*category_user, *category_event], + datetime=[*datetime_user, *datetime_event], + continuous=[*continuous_user], + time_col=[Event.start_time], +) +``` + +### Feature Type Guidelines + +| Data type | Annotation | +|-----------|-----------| +| Boolean flags, enum/status codes | `category` | +| Ages, prices, ratings | `continuous` | +| Free-form text, names, descriptions | `text` | +| Dates, timestamps | `datetime` | + +PropertyTransformer is optional — omitting it auto-infers all field types. For production, explicit annotation is recommended. Use `drop` to exclude fields or entire concepts: `drop=[Customer, Article.COLOUR_GROUP_CODE]`. + +For the full feature type reference including drop patterns, see [references/property-transformer-types.md](references/property-transformer-types.md). + +--- + +## Common Pitfalls + +| Mistake | Cause | Fix | +|---------|-------|-----| +| Concept name is plural (e.g. "Customers") | Naming convention — inconsistent concept references | Use singular names: `Concept("Customer")` | +| Task table concept has `identify_by` | Task tables don't need primary keys — causes unexpected join behavior | Use plain `Concept("TrainTable")` with no `identify_by` | +| Snowflake table name not fully qualified | Missing database or schema prefix — `TableNotFoundError` | Use `"DATABASE.SCHEMA.TABLE"` format | +| Test Relationship includes label/target | Test data should not contain the answer — data leakage, meaningless results | Omit the "has" clause: `f"{Source}"` or `f"{Source} at {Any:ts}"` | +| Positional args in `define(Train(...))` don't match template | Template and population call must align — runtime error or silent wrong column binding | Match the order: source, [timestamp], [label/target] | +| Self-referential edge without `.ref()` | Same concept on both sides creates ambiguity — runtime error | Use `PostRef = Post.ref()` for the destination | +| `time_col` fields not in `datetime` list | Both lists must include the field — time column not encoded as temporal feature | Add time columns to both `datetime=[...]` and `time_col=[...]` | +| Task table concept used in edge definition | Only graph concepts participate in edges — invalid graph structure | Edges connect domain entities, not task tables | +| Missing type import | e.g. using `Date` without importing it — `NameError` | Add missing types to the import line | + +--- + +## Examples + +| Pattern | Description | File | +|---------|-------------|------| +| Node classification | Binary classification with temporal features (User/Event/EventAttendee) | [examples/node_classification_snowflake.py](examples/node_classification_snowflake.py) | +| Node regression | Regression predicting article sales on H&M data (Customer/Article/Transaction) | [examples/node_regression_snowflake.py](examples/node_regression_snowflake.py) | +| Link prediction | Repeated link prediction on H&M data (Customer/Article/Transaction) | [examples/link_prediction_snowflake.py](examples/link_prediction_snowflake.py) | + +--- + +## Reference files + +| Reference | Description | File | +|-----------|-------------|------| +| PropertyTransformer types | Full feature type reference, drop patterns, and guidelines | [references/property-transformer-types.md](references/property-transformer-types.md) | diff --git a/skills/rai-predictive-modeling/examples/link_prediction_snowflake.py b/skills/rai-predictive-modeling/examples/link_prediction_snowflake.py new file mode 100644 index 0000000..c159409 --- /dev/null +++ b/skills/rai-predictive-modeling/examples/link_prediction_snowflake.py @@ -0,0 +1,86 @@ +""" +GNN Link Prediction — Data Modeling (Phases 1-6) +================================================= +Repeated link prediction on H&M customer-article data from Snowflake. +Demonstrates: concepts, population, task relationships, graph, and features. + +For training and prediction, see `rai-predictive-training`. +""" + +# ── Phase 1: Imports & Model Setup ────────────────────────────────────────── +from relationalai.semantics import Model, select, define, Integer, Date, Any +from relationalai.semantics.reasoners.graph import Graph +from relationalai.semantics.reasoners.predictive import GNN, PropertyTransformer + +model = Model("gnn_link_prediction_example") +Concept, Table, Relationship = model.Concept, model.Table, model.Relationship + +# ── Phase 2: Define Concepts ──────────────────────────────────────────────── +# graph concepts +Customer = Concept("Customer", identify_by={"C_customer_id": Integer}) +Article = Concept("Article", identify_by={"A_article_id": Integer}) +Transaction = Concept("Transaction") + +# task table concepts +train_table_concept = Concept("TrainTable") +val_table_concept = Concept("ValidationTable") +test_table_concept = Concept("TestTable") + +# ── Phase 3: Populate Concepts (from Snowflake) ───────────────────────────── +define(Customer.new(Table("DB.SCHEMA.CUSTOMERS").to_schema())) +define(Article.new(Table("DB.SCHEMA.ARTICLES").to_schema())) +define(Transaction.new(Table("DB.SCHEMA.TRANSACTIONS_DEDUP").to_schema())) + +define(train_table_concept.new(Table("DB.SCHEMA.TRAIN_LINK").to_schema())) +define(val_table_concept.new(Table("DB.SCHEMA.VALIDATION_LINK").to_schema())) +define(test_table_concept.new(Table("DB.SCHEMA.TEST_LINK").to_schema())) + +# ── Phase 4: Setup Task Relationships ───────────────────────────────────────── +Train = Relationship(f"{Customer} at {Any:timestamp} has {Article}") +define(Train(Customer, train_table_concept.timestamp, Article)).where( + Customer.C_customer_id == train_table_concept.customer_id, + Article.a_article_id == train_table_concept.article_id, +) + +Val = Relationship(f"{Customer} at {Any:timestamp} has {Article}") +define(Val(Customer, val_table_concept.timestamp, Article)).where( + Customer.C_customer_id == val_table_concept.customer_id, + Article.a_article_id == val_table_concept.article_id, +) + +Test = Relationship(f"{Customer} at {Any:timestamp}") +define(Test(Customer, test_table_concept.timestamp)).where( + Customer.C_customer_id == test_table_concept.customer_id, +) + +# ── Phase 5: Build Graph & Edges ──────────────────────────────────────────── +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +Edge = gnn_graph.Edge + +define(Edge.new(src=Transaction, dst=Customer)).where( + Transaction.t_customer_id == Customer.C_customer_id) +define(Edge.new(src=Transaction, dst=Article)).where( + Transaction.t_article_id == Article.a_article_id) + +# ── Phase 6: Configure PropertyTransformer ────────────────────────────────── +# Customer features +category_customer = [Customer.FN, Customer.ACTIVE, Customer.POSTAL_CODE, + Customer.CLUB_MEMBER_STATUS, Customer.FASHION_NEWS_FREQUENCY] +continuous_customer = [Customer.AGE] + +# Article features +category_article = [Article.PRODUCT_CODE] +text_article = [Article.PROD_NAME] + +# Transaction features +category_transaction = [Transaction.SALES_CHANNEL_ID] +continuous_transaction = [Transaction.PRICE] +datetime_transaction = [Transaction.T_DAT] + +pt = PropertyTransformer( + category=[*category_customer, *category_article, *category_transaction], + continuous=[*continuous_customer, *continuous_transaction], + datetime=[*datetime_transaction], + text=[*text_article], + time_col=[Transaction.T_DAT], +) diff --git a/skills/rai-predictive-modeling/examples/node_classification_snowflake.py b/skills/rai-predictive-modeling/examples/node_classification_snowflake.py new file mode 100644 index 0000000..0b03d45 --- /dev/null +++ b/skills/rai-predictive-modeling/examples/node_classification_snowflake.py @@ -0,0 +1,82 @@ +""" +GNN Node Classification — Data Modeling (Phases 1-6) +===================================================== +Binary classification on user data from Snowflake with temporal features. +Demonstrates: concepts, population, task relationships, graph, and features. + +For training and prediction, see `rai-predictive-training`. +""" + +# ── Phase 1: Imports & Model Setup ────────────────────────────────────────── +from relationalai.semantics import Model, select, define, Integer, String, Any +from relationalai.semantics.reasoners.graph import Graph +from relationalai.semantics.reasoners.predictive import GNN, PropertyTransformer + +model = Model("gnn_node_classification_example") +Concept, Table, Relationship = model.Concept, model.Table, model.Relationship + +# ── Phase 2: Define Concepts ──────────────────────────────────────────────── +# graph concepts +User = Concept("User", identify_by={"user_id": Integer}) +Event = Concept("Event", identify_by={"event_id": Integer}) +EventAttendee = Concept("EventAttendee") + +# task table concepts +train_table_concept = Concept("TrainTable") +val_table_concept = Concept("ValidationTable") +test_table_concept = Concept("TestTable") + +# ── Phase 3: Populate Concepts (from Snowflake) ───────────────────────────── +define(User.new(Table("DB.SCHEMA.USERS").to_schema())) +define(Event.new(Table("DB.SCHEMA.EVENTS").to_schema())) +define(EventAttendee.new(Table("DB.SCHEMA.EVENT_ATTENDEES").to_schema())) + +define(train_table_concept.new(Table("DB.SCHEMA.TRAIN").to_schema())) +define(val_table_concept.new(Table("DB.SCHEMA.VAL").to_schema())) +define(test_table_concept.new(Table("DB.SCHEMA.TEST").to_schema())) + +# ── Phase 4: Setup Task Relationships ───────────────────────────────────────── +Train = Relationship(f"{User} at {Any:timestamp} has {Any:target}") +define(Train(User, train_table_concept.timestamp, train_table_concept.target)).where( + User.user_id == train_table_concept.user +) + +Val = Relationship(f"{User} at {Any:timestamp} has {Any:target}") +define(Val(User, val_table_concept.timestamp, val_table_concept.target)).where( + User.user_id == val_table_concept.user +) + +Test = Relationship(f"{User} at {Any:timestamp}") +define(Test(User, test_table_concept.timestamp)).where( + User.user_id == test_table_concept.user +) + +# ── Phase 5: Build Graph & Edges ──────────────────────────────────────────── +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +Edge = gnn_graph.Edge + +define(Edge.new(src=Event, dst=User)).where( + Event.user_id == User.user_id) +define(Edge.new(src=EventAttendee, dst=Event)).where( + EventAttendee.event == Event.event_id) +define(Edge.new(src=EventAttendee, dst=User)).where( + EventAttendee.user_id == User.user_id) + +# ── Phase 6: Configure PropertyTransformer ────────────────────────────────── +category_user = [User.locale, User.gender] +datetime_user = [User.joinedAt] +continuous_user = [User.birthyear] + +category_event = [Event.city, Event.state, Event.zip, Event.country] +datetime_event = [Event.start_time] +continuous_event = [Event.lat, Event.lng] + +category_event_attendee = [EventAttendee.status] +datetime_event_attendee = [EventAttendee.start_time] + +pt = PropertyTransformer( + category=[*category_user, *category_event, *category_event_attendee], + datetime=[*datetime_user, *datetime_event, *datetime_event_attendee], + continuous=[*continuous_user, *continuous_event], + time_col=[Event.start_time, EventAttendee.start_time], +) diff --git a/skills/rai-predictive-modeling/examples/node_regression_snowflake.py b/skills/rai-predictive-modeling/examples/node_regression_snowflake.py new file mode 100644 index 0000000..854267a --- /dev/null +++ b/skills/rai-predictive-modeling/examples/node_regression_snowflake.py @@ -0,0 +1,84 @@ +""" +GNN Node Regression — Data Modeling (Phases 1-6) +================================================= +Regression predicting article sales from H&M data in Snowflake. +Demonstrates: concepts, population, task relationships, graph, and features. + +For training and prediction, see `rai-predictive-training`. +""" + +# ── Phase 1: Imports & Model Setup ────────────────────────────────────────── +from relationalai.semantics import Model, select, define, Integer, Any +from relationalai.semantics.reasoners.graph import Graph +from relationalai.semantics.reasoners.predictive import GNN, PropertyTransformer + +model = Model("gnn_node_regression_example") +Concept, Table, Relationship = model.Concept, model.Table, model.Relationship + +# ── Phase 2: Define Concepts ──────────────────────────────────────────────── +# graph concepts +Customer = Concept("Customer", identify_by={"C_customer_id": Integer}) +Article = Concept("Article", identify_by={"A_article_id": Integer}) +Transaction = Concept("Transaction") + +# task table concepts +train_table_concept = Concept("TrainTable") +val_table_concept = Concept("ValidationTable") +test_table_concept = Concept("TestTable") + +# ── Phase 3: Populate Concepts (from Snowflake) ───────────────────────────── +define(Customer.new(Table("DB.SCHEMA.CUSTOMERS").to_schema())) +define(Article.new(Table("DB.SCHEMA.ARTICLES").to_schema())) +define(Transaction.new(Table("DB.SCHEMA.TRANSACTIONS_DEDUP").to_schema())) + +define(train_table_concept.new(Table("DB.SCHEMA.TRAIN").to_schema())) +define(val_table_concept.new(Table("DB.SCHEMA.VAL").to_schema())) +define(test_table_concept.new(Table("DB.SCHEMA.TEST").to_schema())) + +# ── Phase 4: Setup Task Relationships ───────────────────────────────────────── +Train = Relationship(f"{Article} at {Any:timestamp} has {Any:sales}") +define(Train(Article, train_table_concept.timestamp, train_table_concept.sales)).where( + Article.a_article_id == train_table_concept.article_id +) + +Val = Relationship(f"{Article} at {Any:timestamp} has {Any:sales}") +define(Val(Article, val_table_concept.timestamp, val_table_concept.sales)).where( + Article.a_article_id == val_table_concept.article_id +) + +Test = Relationship(f"{Article} at {Any:timestamp}") +define(Test(Article, test_table_concept.timestamp)).where( + Article.a_article_id == test_table_concept.article_id +) + +# ── Phase 5: Build Graph & Edges ──────────────────────────────────────────── +gnn_graph = Graph(model, directed=True, weighted=False, aggregator="sum") +Edge = gnn_graph.Edge + +define(Edge.new(src=Transaction, dst=Customer)).where( + Transaction.t_customer_id == Customer.C_customer_id) +define(Edge.new(src=Transaction, dst=Article)).where( + Transaction.t_article_id == Article.a_article_id) + +# ── Phase 6: Configure PropertyTransformer ────────────────────────────────── +# Customer features +category_customer = [Customer.FN, Customer.ACTIVE, Customer.POSTAL_CODE, + Customer.CLUB_MEMBER_STATUS, Customer.FASHION_NEWS_FREQUENCY] +continuous_customer = [Customer.AGE] + +# Article features +category_article = [Article.PRODUCT_CODE] +text_article = [Article.PROD_NAME] + +# Transaction features +category_transaction = [Transaction.SALES_CHANNEL_ID] +continuous_transaction = [Transaction.PRICE] +datetime_transaction = [Transaction.T_DAT] + +pt = PropertyTransformer( + category=[*category_customer, *category_article, *category_transaction], + continuous=[*continuous_customer, *continuous_transaction], + datetime=[*datetime_transaction], + text=[*text_article], + time_col=[Transaction.T_DAT], +) diff --git a/skills/rai-predictive-modeling/references/property-transformer-types.md b/skills/rai-predictive-modeling/references/property-transformer-types.md new file mode 100644 index 0000000..f902105 --- /dev/null +++ b/skills/rai-predictive-modeling/references/property-transformer-types.md @@ -0,0 +1,66 @@ +# PropertyTransformer Feature Types + +The `PropertyTransformer` class specifies how concept fields are transformed into GNN-compatible features. + +## Feature Types + +| Type | PropertyTransformer kwarg | Description | Example fields | +|------|--------------------------|-------------|----------------| +| Category | `category=[...]` | Discrete categorical values (int or string) | Gender, product code, membership status | +| Continuous | `continuous=[...]` | Numeric continuous values (float) | Age, price, rating | +| Text | `text=[...]` | Text strings (embedded via language model) | Product name, description, comment | +| Datetime | `datetime=[...]` | Timestamps or dates | Transaction date, creation date | +| Integer | `integer=[...]` | Integer values (kept as integers, not categorical) | Explicit integer IDs | +| Drop | `drop=[...]` | Exclude field from model entirely | Foreign keys, sensitive data, redundant IDs | + +## Special Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `time_col` | list or single | Time column(s) for temporal models. Must NOT appear in `drop`. | + +## Usage + +```python +from relationalai.semantics.reasoners.predictive import PropertyTransformer + +pt = PropertyTransformer( + category=[Customer.gender, Article.PRODUCT_CODE, Transaction.SALES_CHANNEL_ID], + continuous=[Customer.age, Transaction.PRICE], + text=[Article.PROD_NAME], + datetime=[Transaction.T_DAT], + drop=[Customer, Article.GRAPHICAL_APPEARANCE_NAME], + time_col=[Transaction.T_DAT], +) +``` + +## Drop Patterns + +### Drop specific fields +```python +drop=[Article.GRAPHICAL_APPEARANCE_NAME, Article.COLOUR_GROUP_CODE] +``` + +### Drop all fields of a concept (identifier columns) +```python +drop=[Customer] # drops all Customer fields (including primary key) +``` + +### Mixed: drop entire concept + specific fields from another +```python +drop=[Customer, Article.GRAPHICAL_APPEARANCE_NAME, Article.COLOUR_GROUP_CODE] +``` + +## Default Behavior + +Fields not mentioned in any category are auto-inferred by the GNN engine (equivalent to the `Infer` embedding type). This is usually fine for most fields, but explicitly annotating them improves reproducibility. + +## Guidelines + +- **Primary key / identifier fields**: Usually `drop` (they don't carry predictive signal) +- **Foreign key join columns**: Usually `drop` (the graph structure captures the relationship) +- **Numeric IDs that encode meaning** (e.g. product_code): Use `category` +- **Free-form text**: Use `text` +- **Dates/timestamps**: Use `datetime`. If it's the temporal ordering column, also add to `time_col` +- **Boolean flags**: Use `category` +- **Continuous measurements**: Use `continuous` diff --git a/skills/rai-predictive-training/SKILL.md b/skills/rai-predictive-training/SKILL.md new file mode 100644 index 0000000..1b8213b --- /dev/null +++ b/skills/rai-predictive-training/SKILL.md @@ -0,0 +1,279 @@ +--- +name: rai-predictive-training +description: Configure and train GNN models with hyperparameters, generate predictions, and inspect results. Use after building the data model with rai-predictive-modeling, when ready to run training or evaluate predictions. +--- + +# Predictive Training + + +## Summary + +**What:** Encodes the training and evaluation workflow for GNN pipelines — configuring the GNN estimator, running training, and generating predictions. + +**When to use:** +- Configuring a GNN constructor with task type, metric, and hyperparameters +- Running `gnn.fit()` to train a model +- Generating predictions with `gnn.predictions(domain=Test)` +- Inspecting or exporting prediction results + +**When NOT to use:** +- Defining concepts, loading data, or building the graph — see `rai-predictive-modeling` +- Registering or loading saved models — see `rai-predictive-management` + +**Overview:** +1. Configure the GNN estimator (database, graph, task type, metric, hyperparameters) +2. Train with `gnn.fit()` +3. Generate predictions with `gnn.predictions(domain=Test)` +4. Inspect or export results + +--- + +## Quick Reference + +**GNN constructor (node classification):** +```python +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, pt=pt, + train=Train, validation=Val, + task_type="binary_classification", + eval_metric="roc_auc", + has_time_column=True, + device="cuda", n_epochs=5, +) +gnn.fit() +``` + +**Default metrics per task type:** + +| Task Type | Suggested Metric | +|-----------|-----------------| +| binary_classification | `roc_auc` | +| multiclass_classification | `accuracy` | +| multilabel_classification | `multilabel_auprc_macro` | +| regression | `rmse` | +| link_prediction | `link_prediction_precision@5` | +| repeated_link_prediction | `link_prediction_precision@5` | + +**Prediction attributes:** + +| Task Type | Attributes | +|-----------|-----------| +| classification | `.probs`, `.predicted_labels` | +| regression | `.predicted_value` | +| link prediction | `.rank`, `.scores`, `.predicted_` | + +--- + +## GNN Constructor + +The `GNN` constructor takes data locations, graph structure, task configuration, and hyperparameters. + +### Required Parameters + +| Parameter | Description | +|-----------|-------------| +| `database`, `schema` | Snowflake location of source data tables | +| `exp_database`, `exp_schema` | Snowflake location for experiment artifacts | +| `graph` | Graph object with edges defined (from `rai-predictive-modeling`) | +| `train`, `validation` | Relationship objects for train and validation splits | +| `task_type` | One of: `binary_classification`, `multiclass_classification`, `multilabel_classification`, `regression`, `link_prediction`, `repeated_link_prediction` | +| `eval_metric` | Evaluation metric compatible with the task type | + +### Optional Parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `pt` | None | PropertyTransformer (omit for auto-inference) | +| `has_time_column` | False | Set `True` when Relationships use the "at" keyword | +| `stream_logs` | True | Stream training logs to console | + +### Node Classification Example + +```python +gnn = GNN( + database="MY_DB", schema="MY_SCHEMA", + exp_database="MY_DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + train=Train, + validation=Val, + task_type="binary_classification", + eval_metric="roc_auc", + device="cuda", + n_epochs=5, + lr=0.005, +) +gnn.fit() +``` + +### Node Regression Example (temporal) + +```python +gnn = GNN( + database="MY_DB", schema="MY_SCHEMA", + exp_database="MY_DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + train=Train, + validation=Val, + task_type="regression", + eval_metric="rmse", + has_time_column=True, + device="cuda", + n_epochs=5, + lr=0.005, + head_layers=2, +) +gnn.fit() +``` + +### Link Prediction Example (temporal) + +```python +gnn = GNN( + database="MY_DB", schema="MY_SCHEMA", + exp_database="MY_DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + train=Train, + validation=Val, + task_type="repeated_link_prediction", + eval_metric="link_prediction_precision@5", + has_time_column=True, + device="cuda", + n_epochs=5, + lr=0.005, + head_layers=2, + num_negative=20, + label_smoothing=True, +) +gnn.fit() +``` + +### Common Hyperparameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `device` | `"cuda"` | `"cuda"` (GPU) or `"cpu"` | +| `n_epochs` | 5 | Number of training epochs | +| `lr` | 0.005 | Learning rate | +| `train_batch_size` | 256 | Training batch size | + +For link prediction, also consider: `head_layers=2`, `num_negative=20`, `label_smoothing=True`. + +For all hyperparameters, see [references/hyperparameters.md](references/hyperparameters.md). + +--- + +## Training + +Call `gnn.fit()` to start training. This executes the following stages: +1. Prepare dataset (load data from Snowflake) +2. Configure trainer (set up model architecture) +3. Submit training job (run on compute) + +### Detecting `has_time_column` + +If the Train Relationship template contains the "at" keyword (e.g. `f"{User} at {Any:timestamp} ..."`), set `has_time_column=True` in the GNN constructor. + +--- + +## Predictions + +After training, generate predictions on the test set: + +```python +Source.predictions = gnn.predictions(domain=Test) +``` + +### Classification (binary, multiclass, multilabel) + +```python +User.predictions = gnn.predictions(domain=Test) + +select( + User.user_id, + User.predictions.probs, + User.predictions.predicted_labels, +).where(User.predictions).inspect() +``` + +### Regression + +```python +Unit.predictions = gnn.predictions(domain=Test) + +select( + Unit.unit_id, + Unit.predictions.predicted_value, +).where(Unit.predictions).inspect() +``` + +### Link Prediction + +```python +Customer.predictions = gnn.predictions(domain=Test) + +select( + Customer.customer_id, + Article.article_id, + Customer.predictions.rank, + Customer.predictions.scores, +).where( + Customer.predictions.predicted_article == Article, +).inspect() +``` + +The `predicted_` attribute name is derived from the target concept name, always lowercase: +- Target `Article` -> `.predicted_article` +- Target `Product` -> `.predicted_product` + +### As DataFrame + +Replace `.inspect()` with `.to_df()` to get a pandas DataFrame: + +```python +df = select( + User.user_id, + User.predictions.probs, + User.predictions.predicted_labels, +).where(User.predictions).to_df() +``` + +For the full prediction attributes reference, see [references/prediction-attributes.md](references/prediction-attributes.md). + +--- + +## Common Pitfalls + +| Mistake | Cause | Fix | +|---------|-------|-----| +| Passing `select(...)` fragments to `train=` or `validation=` | GNN expects Relationship objects — `TypeError` | Use `train=Train` with the Relationship object directly | +| Missing `has_time_column=True` | Relationships use "at" keyword but flag not set — temporal ordering ignored, degraded accuracy | Set `has_time_column=True` when templates contain "at" | +| Treating hyperparameters as named GNN parameters | Hyperparameters are `**kwargs` — `TypeError` | Pass them directly: `GNN(..., device="cuda", n_epochs=5)` | +| Expecting `gnn.predictions()` to return results | Predictions are assigned to a concept attribute — returns `None` | Use `Source.predictions = gnn.predictions(domain=Test)` | +| Using `.predicted_Article` (uppercase) | Attribute name is always lowercase — `AttributeError` | Use `.predicted_article` regardless of concept casing | +| Passing wrong object to `domain=` | Must be the Test Relationship — `TypeError` or wrong prediction scope | Use `domain=Test` with the Relationship object | +| Invalid task_type/metric combination | Not all metrics work with all task types — `ValueError` | Check the valid pairs in [references/task-types-and-metrics.md](references/task-types-and-metrics.md) | + +--- + +## Examples + +| Pattern | Description | File | +|---------|-------------|------| +| Node classification | Binary classification training + prediction (User/Event) | [examples/train_node_classification.py](examples/train_node_classification.py) | +| Node regression | Regression training + prediction for article sales (Article) | [examples/train_node_regression.py](examples/train_node_regression.py) | +| Link prediction | Repeated link prediction training + prediction (Customer/Article) | [examples/train_link_prediction.py](examples/train_link_prediction.py) | + +--- + +## Reference files + +| Reference | Description | File | +|-----------|-------------|------| +| Task types and metrics | All valid (task_type, eval_metric) combinations | [references/task-types-and-metrics.md](references/task-types-and-metrics.md) | +| Hyperparameters | Full hyperparameter table with types, defaults, and descriptions | [references/hyperparameters.md](references/hyperparameters.md) | +| Prediction attributes | Prediction attributes by task type with usage examples | [references/prediction-attributes.md](references/prediction-attributes.md) | diff --git a/skills/rai-predictive-training/examples/train_link_prediction.py b/skills/rai-predictive-training/examples/train_link_prediction.py new file mode 100644 index 0000000..84ec184 --- /dev/null +++ b/skills/rai-predictive-training/examples/train_link_prediction.py @@ -0,0 +1,45 @@ +""" +GNN Link Prediction — Training & Prediction (Phases 7-8) +========================================================= +Repeated link prediction training and prediction on H&M data. + +Assumes data model from `rai-predictive-modeling`. +See: examples/link_prediction_snowflake.py for the full data model. +Required variables: gnn_graph, pt, Train, Val, Test, Customer, Article +""" + +# ── Phase 7: Train GNN ────────────────────────────────────────────────────── +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + train=Train, + validation=Val, + task_type="repeated_link_prediction", + eval_metric="link_prediction_precision@5", + has_time_column=True, + device="cuda", + n_epochs=5, + train_batch_size=256, + lr=0.005, + head_layers=2, + num_negative=20, + label_smoothing=True, +) +gnn.fit() + +# ── Phase 8: Predict & Inspect ────────────────────────────────────────────── +Customer.predictions = gnn.predictions(domain=Test) + +select( + Customer.c_customer_id, + Customer.age, + Article.a_article_id, + Customer.predictions.rank, + Customer.predictions.scores, +).where( + Customer.predictions.predicted_article == Article, + Customer.age < 50, + Customer.age > 20, +).inspect() diff --git a/skills/rai-predictive-training/examples/train_node_classification.py b/skills/rai-predictive-training/examples/train_node_classification.py new file mode 100644 index 0000000..ead6acc --- /dev/null +++ b/skills/rai-predictive-training/examples/train_node_classification.py @@ -0,0 +1,42 @@ +""" +GNN Node Classification — Training & Prediction (Phases 7-8) +============================================================= +Binary classification training and prediction on user data. + +Assumes data model from `rai-predictive-modeling`. +See: examples/node_classification_snowflake.py for the full data model. +Required variables: gnn_graph, pt, Train, Val, Test, User +""" + +# ── Phase 7: Train GNN ────────────────────────────────────────────────────── +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + train=Train, + validation=Val, + task_type="binary_classification", + eval_metric="roc_auc", + has_time_column=True, + device="cuda", + n_epochs=5, +) +gnn.fit() + +# ── Phase 8: Predict & Inspect ────────────────────────────────────────────── +User.predictions = gnn.predictions(domain=Test) + +select( + User.user_id, + User.predictions.probs, + User.predictions.predicted_labels, +).where(User.predictions).inspect() + +df = select( + User.user_id, + User.predictions.probs, + User.predictions.predicted_labels, +).where(User.predictions).to_df() + +print(f"Predictions: {len(df)} rows, {len(df.dropna())} after dropping NaNs") diff --git a/skills/rai-predictive-training/examples/train_node_regression.py b/skills/rai-predictive-training/examples/train_node_regression.py new file mode 100644 index 0000000..14352cf --- /dev/null +++ b/skills/rai-predictive-training/examples/train_node_regression.py @@ -0,0 +1,43 @@ +""" +GNN Node Regression — Training & Prediction (Phases 7-8) +========================================================= +Regression training predicting article sales. + +Assumes data model from `rai-predictive-modeling`. +See: examples/node_regression_snowflake.py for the full data model. +Required variables: gnn_graph, pt, Train, Val, Test, Article +""" + +# ── Phase 7: Train GNN ────────────────────────────────────────────────────── +gnn = GNN( + database="DB", schema="SCHEMA", + exp_database="DB", exp_schema="EXPERIMENTS", + graph=gnn_graph, + pt=pt, + train=Train, + validation=Val, + task_type="regression", + eval_metric="rmse", + has_time_column=True, + device="cuda", + n_epochs=5, + train_batch_size=256, + lr=0.005, + head_layers=2, +) +gnn.fit() + +# ── Phase 8: Predict & Inspect ────────────────────────────────────────────── +Article.predictions = gnn.predictions(domain=Test) + +select( + Article.a_article_id, + Article.predictions.predicted_value, +).where(Article.predictions).inspect() + +df = select( + Article.a_article_id, + Article.predictions.predicted_value, +).where(Article.predictions).to_df() + +print(f"Predictions: {len(df)} rows, {len(df.dropna())} after dropping NaNs") diff --git a/skills/rai-predictive-training/references/hyperparameters.md b/skills/rai-predictive-training/references/hyperparameters.md new file mode 100644 index 0000000..13da305 --- /dev/null +++ b/skills/rai-predictive-training/references/hyperparameters.md @@ -0,0 +1,77 @@ +# GNN Hyperparameters + +Hyperparameters are passed as `**train_params` kwargs to the `GNN(...)` constructor. + +## Common Hyperparameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `device` | str | `"cuda"` | Compute device: `"cuda"` (GPU) or `"cpu"` | +| `n_epochs` | int | 5 | Number of training epochs | +| `lr` | float | 0.005 | Learning rate | +| `train_batch_size` | int | 256 | Training batch size | +| `head_layers` | int | 2 | Number of prediction head layers | +| `seed` | int | - | Random seed for reproducibility | +| `channels` | int | 64 | Hidden channel dimension | + +## Link Prediction Hyperparameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `num_negative` | int | 20 | Number of negative samples per positive | +| `label_smoothing` | bool | True | Apply label smoothing during training | + +## Advanced Hyperparameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `temporal_strategy` | str | - | Temporal modeling strategy (e.g. `"last"`) | +| `text_embedder` | str | - | Text embedding model (e.g. `"model2vec-potion-base-4M"`) | +| `max_iters` | int | - | Maximum training iterations | + +## GNN Constructor Operational Flags + +These are named parameters on `GNN(...)`, not train_params: + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `test_batch_size` | int | None | Batch size for prediction/inference | +| `stream_logs` | bool | True | Stream training logs to console | +| `extract_embeddings` | bool | False | Extract node embeddings during prediction | +| `use_current_time` | bool | True | Use current time for temporal models | + +## Example Configurations + +### Node Classification (small dataset) +```python +train_params = {"device": "cpu", "n_epochs": 10, "seed": 42} +``` + +### Node Classification (large dataset) +```python +train_params = {"device": "cuda", "n_epochs": 5, "lr": 0.005, "train_batch_size": 256} +``` + +### Link Prediction +```python +train_params = { + "device": "cuda", + "n_epochs": 5, + "train_batch_size": 256, + "lr": 0.005, + "head_layers": 2, + "num_negative": 20, + "label_smoothing": True, +} +``` + +### Regression with Temporal Data +```python +train_params = { + "device": "cuda", + "n_epochs": 5, + "channels": 64, + "head_layers": 2, + "temporal_strategy": "last", +} +``` diff --git a/skills/rai-predictive-training/references/prediction-attributes.md b/skills/rai-predictive-training/references/prediction-attributes.md new file mode 100644 index 0000000..92491a8 --- /dev/null +++ b/skills/rai-predictive-training/references/prediction-attributes.md @@ -0,0 +1,67 @@ +# Prediction Attributes by Task Type + +After calling `gnn.predictions(domain=Test)`, the prediction results are attached to the source concept (head of the Relationship) and accessed via `select(...)`. + +## Classification (binary, multiclass, multilabel) + +| Attribute | Type | Description | +|-----------|------|-------------| +| `Source.predictions.probs` | float/array | Probability distribution over classes | +| `Source.predictions.predicted_labels` | int/str | Predicted class label (argmax of probs) | + +```python +Source.predictions = gnn.predictions(domain=Test) +select( + Source.id, + Source.predictions.probs, + Source.predictions.predicted_labels, +).where(Source.predictions).inspect() +``` + +## Regression + +| Attribute | Type | Description | +|-----------|------|-------------| +| `Source.predictions.predicted_value` | float | Predicted continuous value | + +```python +Source.predictions = gnn.predictions(domain=Test) +select( + Source.id, + Source.predictions.predicted_value, +).where(Source.predictions).inspect() +``` + +## Link Prediction (link_prediction, repeated_link_prediction) + +| Attribute | Type | Description | +|-----------|------|-------------| +| `Source.predictions.rank` | int | Ranking position (1, 2, 3, ...) | +| `Source.predictions.scores` | float | Relevance/similarity score | +| `Source.predictions.predicted_` | reference | Predicted target concept instance | + +The `predicted_` attribute name is derived from the target concept in the Relationship template. For example, if the Relationship tail is `Article`, the attribute is `predicted_article`. + +```python +Source.predictions = gnn.predictions(domain=Test) +select( + Source.source_id, + Target.target_id, + Source.predictions.rank, + Source.predictions.scores, +).where( + Source.predictions.predicted_article == Target, +).inspect() +``` + +## Using `.to_df()` Instead of `.inspect()` + +Replace `.inspect()` with `.to_df()` to get a pandas DataFrame: + +```python +df = select( + Source.id, + Source.predictions.probs, + Source.predictions.predicted_labels, +).where(Source.predictions).to_df() +``` diff --git a/skills/rai-predictive-training/references/task-types-and-metrics.md b/skills/rai-predictive-training/references/task-types-and-metrics.md new file mode 100644 index 0000000..ab3ed05 --- /dev/null +++ b/skills/rai-predictive-training/references/task-types-and-metrics.md @@ -0,0 +1,68 @@ +# Task Types and Evaluation Metrics + +Valid `(task_type, eval_metric)` combinations for the GNN constructor. + +## Binary Classification + +| task_type | eval_metric | +|-----------|-------------| +| `"binary_classification"` | `"accuracy"` | +| `"binary_classification"` | `"f1"` | +| `"binary_classification"` | `"roc_auc"` | +| `"binary_classification"` | `"average_precision"` | + +## Multiclass Classification + +| task_type | eval_metric | +|-----------|-------------| +| `"multiclass_classification"` | `"accuracy"` | +| `"multiclass_classification"` | `"macro_f1"` | +| `"multiclass_classification"` | `"micro_f1"` | + +## Multilabel Classification + +| task_type | eval_metric | +|-----------|-------------| +| `"multilabel_classification"` | `"multilabel_auprc_micro"` | +| `"multilabel_classification"` | `"multilabel_auroc_micro"` | +| `"multilabel_classification"` | `"multilabel_precision_micro"` | +| `"multilabel_classification"` | `"multilabel_auprc_macro"` | +| `"multilabel_classification"` | `"multilabel_auroc_macro"` | +| `"multilabel_classification"` | `"multilabel_precision_macro"` | + +## Regression + +| task_type | eval_metric | +|-----------|-------------| +| `"regression"` | `"r2"` | +| `"regression"` | `"mae"` | +| `"regression"` | `"rmse"` | + +## Link Prediction + +| task_type | eval_metric | +|-----------|-------------| +| `"link_prediction"` | `"link_prediction_precision@k"` | +| `"link_prediction"` | `"link_prediction_recall@k"` | +| `"link_prediction"` | `"link_prediction_map@k"` | + +## Repeated Link Prediction (temporal) + +| task_type | eval_metric | +|-----------|-------------| +| `"repeated_link_prediction"` | `"link_prediction_precision@k"` | +| `"repeated_link_prediction"` | `"link_prediction_recall@k"` | +| `"repeated_link_prediction"` | `"link_prediction_map@k"` | + +Replace `@k` with the desired top-k value, e.g. `"link_prediction_precision@5"`. + +## Task Type Summary + +| Task Type | has_time_column | Train Relationship template | Test Relationship template | +|-----------|-----------------|---------------------------|--------------------------| +| binary_classification | optional | `f"{Source} has {Any:label}"` | `f"{Source}"` | +| multiclass_classification | optional | `f"{Source} has {Any:label}"` | `f"{Source}"` | +| multilabel_classification | optional | `f"{Source} has {Any:label}"` | `f"{Source}"` | +| regression | optional | `f"{Source} has {Any:value}"` | `f"{Source}"` | +| link_prediction | False | `f"{Source} has {Target}"` | `f"{Source}"` | +| repeated_link_prediction | True | `f"{Source} at {Any:ts} has {Target}"` | `f"{Source} at {Any:ts}"` |