Create model.py
Browse files
model.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import re
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
from torch import Tensor
|
| 8 |
+
from jaxtyping import Float
|
| 9 |
+
from huggingface_hub import PyTorchModelHubMixin
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Model(nn.Module, PyTorchModelHubMixin, repo_url="https://huggingface.co/TimWalter/RAM/new/main", license="mit",):
|
| 13 |
+
"""
|
| 14 |
+
RAM model that predicts reachability from a modified Denavit-Hartenberg morphology parametrisation and a pose.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
@classmethod
|
| 18 |
+
def from_id(cls, model_id: int):
|
| 19 |
+
"""
|
| 20 |
+
Instantiate a model from its wandb ID.
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
model_id: Wandb ID of the model.
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
model: Instantiated model.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
model_dir = Path(__file__).parent.parent / "trained_models"
|
| 30 |
+
pattern = rf"{model_id}-[a-z]+-[a-z]+"
|
| 31 |
+
folder = next((f for f in model_dir.iterdir() if re.match(pattern, f.name)), None)
|
| 32 |
+
metadata_path = model_dir / folder / 'metadata.json'
|
| 33 |
+
metadata = json.load(open(metadata_path, 'r'))
|
| 34 |
+
|
| 35 |
+
model = cls(**metadata["hyperparameter"])
|
| 36 |
+
model_folder = Path(str(model_dir / folder))
|
| 37 |
+
prime = model_folder / "model.pth"
|
| 38 |
+
|
| 39 |
+
if prime.exists():
|
| 40 |
+
old_state_dict =torch.load(prime)
|
| 41 |
+
else:
|
| 42 |
+
old_state_dict =torch.load(model_folder / "checkpoint.pth")
|
| 43 |
+
new_state_dict = {}
|
| 44 |
+
# 2. Map legacy state dict keys to your new flattened structure
|
| 45 |
+
for key, value in old_state_dict.items():
|
| 46 |
+
new_key = key
|
| 47 |
+
if key.startswith("encoder.lstm."):
|
| 48 |
+
new_key = key.replace("encoder.lstm.", "encoder.")
|
| 49 |
+
elif key.startswith("decoder.model."):
|
| 50 |
+
new_key = key.replace("decoder.model.", "decoder.")
|
| 51 |
+
|
| 52 |
+
new_state_dict[new_key] = value
|
| 53 |
+
|
| 54 |
+
# Load the corrected state dict into the model
|
| 55 |
+
model.load_state_dict(new_state_dict)
|
| 56 |
+
|
| 57 |
+
return model
|
| 58 |
+
|
| 59 |
+
def __init__(self,
|
| 60 |
+
dim_encoding: int = 128,
|
| 61 |
+
num_encoder_layers: int = 1,
|
| 62 |
+
drop_prob: float = 0.0,
|
| 63 |
+
dim_decoder: int = 1792,
|
| 64 |
+
num_decoder_layer: int = 8):
|
| 65 |
+
"""
|
| 66 |
+
Initialise the model.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
dim_encoding: The dimension of the latent morphology encoding.
|
| 70 |
+
num_encoder_layers: Number of LSTM layers.
|
| 71 |
+
drop_prob: Dropout probability of the LSTM.
|
| 72 |
+
dim_decoder: Hidden dimension of the MLP.
|
| 73 |
+
num_decoder_layer: Number of layers of the MLP.
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
super().__init__()
|
| 77 |
+
self.encoder = nn.LSTM(3, dim_encoding, num_encoder_layers, dropout=drop_prob, batch_first=True, bias=False)
|
| 78 |
+
self.decoder = nn.Sequential(
|
| 79 |
+
nn.Linear(9 + dim_encoding, dim_decoder),
|
| 80 |
+
nn.ReLU(),
|
| 81 |
+
*[nn.Sequential(nn.Linear(dim_decoder, dim_decoder), nn.ReLU())
|
| 82 |
+
for _ in range(num_decoder_layer)],
|
| 83 |
+
nn.Linear(dim_decoder, 1)
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
def forward(self, morph: Float[Tensor, "batch seq 3"], pose: Float[Tensor, "batch 9"]) -> Float[Tensor, "batch"]:
|
| 87 |
+
"""
|
| 88 |
+
Predict reachability.
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
morph: Morphology description.
|
| 92 |
+
pose: Pose as vector encoded.
|
| 93 |
+
Returns:
|
| 94 |
+
Reachability logit.
|
| 95 |
+
"""
|
| 96 |
+
latent = self.encoder(morph)[1][0][-1]
|
| 97 |
+
logit = self.decoder(torch.cat([pose, latent], dim=-1)).squeeze(-1)
|
| 98 |
+
return logit
|
| 99 |
+
|
| 100 |
+
@torch.inference_mode()
|
| 101 |
+
def predict(self, *args, **kwargs):
|
| 102 |
+
"""
|
| 103 |
+
forward with inference mode
|
| 104 |
+
"""
|
| 105 |
+
return self.forward(*args, **kwargs)
|