mist-1.8B-85f24xkj-u298 / modeling_mist_finetuned.py
anoushka2000's picture
Upload folder using huggingface_hub
8758b06 verified
Raw
History Blame Contribute Delete
28.1 kB
from datasets import IterableDataset
from pathlib import Path
from smirk import SmirkTokenizerFast
from torch import nn
from torch.masked import MaskedTensor, masked_tensor
from transformers import (
AutoConfig,
AutoModel,
AutoTokenizer,
DataCollatorWithPadding,
PreTrainedModel,
PretrainedConfig,
)
from typing import Any, Callable, Optional, Union
from typing import Any, Dict, List, Optional
import json
import logging
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
AutoTokenizer.register("SmirkTokenizer", fast_tokenizer_class=SmirkTokenizerFast)
MODEL_TYPE_ALIASES = {}
def build_encoder(enc_dict: Dict[str, Any]):
mtype = enc_dict.get("model_type")
if mtype:
base = MODEL_TYPE_ALIASES.get(mtype, mtype)
cfg_cls = AutoConfig.for_model(base)
enc_cfg = cfg_cls.from_dict(enc_dict)
elif enc_dict.get("_name_or_path"):
enc_cfg = AutoConfig.from_pretrained(enc_dict["_name_or_path"])
else:
raise KeyError("encoder config missing 'model_type' or '_name_or_path'")
if hasattr(enc_cfg, "add_pooling_layer"):
enc_cfg.add_pooling_layer = False
return AutoModel.from_config(enc_cfg)
class MISTFinetunedConfig(PretrainedConfig):
"""HF config for a single-task MIST wrapper."""
model_type = "mist_finetuned"
def __init__(
self,
encoder: Optional[Dict[str, Any]] = None,
task_network: Optional[Dict[str, Any]] = None,
transform: Optional[Dict[str, Any]] = None,
channels: Optional[List[Dict[str, Any]]] = None,
tokenizer_class: Optional[str] = "SmirkTokenizer",
**kwargs,
):
super().__init__(**kwargs)
self.encoder = encoder or {}
self.task_network = task_network or {}
self.transform = transform or {}
self.channels = channels
self.tokenizer_class = tokenizer_class
class MISTFinetuned(PreTrainedModel):
config_class = MISTFinetunedConfig
def __init__(self, config: MISTFinetunedConfig):
super().__init__(config)
self.encoder = build_encoder_from_dict(config.encoder)
tn = config.task_network
self.task_network = PredictionTaskHead(
embed_dim=tn["embed_dim"],
output_size=tn["output_size"],
dropout=tn["dropout"],
)
self.transform = AbstractNormalizer.get(
config.transform["class"], config.transform["num_outputs"]
)
self.channels = config.channels
self.tokenizer = None
self.post_init()
@classmethod
def from_components(
cls,
encoder: PreTrainedModel,
task_network: nn.Module,
transform: Any,
tokenizer: Optional[Any] = None,
channels: Optional[List[Dict[str, Any]]] = None,
) -> "MISTFinetuned":
cfg = MISTFinetunedConfig(
encoder=encoder.config.to_dict(),
task_network={
"embed_dim": encoder.config.hidden_size,
"output_size": task_network.final.out_features,
"dropout": task_network.dropout1.p,
},
transform=transform.to_config(),
channels=channels,
tokenizer_class=(
getattr(tokenizer, "__class__", type("T", (), {})).__name__
if tokenizer
else "SmirkTokenizer"
),
)
model = cls(cfg)
# load component weights
model.encoder.load_state_dict(encoder.state_dict(), strict=False)
model.task_network.load_state_dict(task_network.state_dict())
model.transform.load_state_dict(transform.state_dict())
model.tokenizer = tokenizer
return model
def forward(self, input_ids, attention_mask=None):
hs = self.encoder(input_ids, attention_mask=attention_mask).last_hidden_state
y = self.task_network(hs)
return self.transform.forward(y)
def _resolve_tokenizer(self, tokenizer):
if tokenizer is not None:
return tokenizer
if getattr(self, "tokenizer", None) is not None:
return self.tokenizer
try:
return AutoTokenizer.from_pretrained(
self.name_or_path, use_fast=True, trust_remote_code=True
)
except Exception:
return AutoTokenizer.from_pretrained(
self.config._name_or_path, use_fast=True, trust_remote_code=True
)
def embed(self, smi: List[str], tokenizer=None):
tok = self._resolve_tokenizer(tokenizer)
batch = tok(smi)
batch = DataCollatorWithPadding(tok)(batch)
input_ids = batch["input_ids"].to(self.device)
attention_mask = batch["attention_mask"].to(self.device)
with torch.inference_mode():
hs = self.encoder(
input_ids, attention_mask=attention_mask
).last_hidden_state[:, 0, :]
return hs.to("cpu")
def predict(self, smi: List[str], return_dict: bool = True, tokenizer=None):
tok = self._resolve_tokenizer(tokenizer)
batch = tok(smi)
collate_fn = DataCollatorWithPadding(tok)
batch = collate_fn(batch)
batch = {
"input_ids": batch["input_ids"].to(self.encoder.device),
"attention_mask": batch["attention_mask"].to(self.encoder.device),
}
with torch.inference_mode():
out = self(**batch).cpu()
if self.channels is None or not return_dict:
return out
return annotate_prediction(out, maybe_get_annotated_channels(self.channels))
def save_pretrained(self, save_directory, **kwargs):
super().save_pretrained(save_directory, **kwargs)
if getattr(self, "tokenizer", None) is not None:
self.tokenizer.save_pretrained(save_directory)
def maybe_get_annotated_channels(channels: List[Any]):
for chn in channels:
if isinstance(chn, str):
yield {"name": chn, "description": None, "unit": None}
else:
yield chn
def annotate_prediction(
y: torch.Tensor, channels: List[Dict[str, str]]
) -> Dict[str, Dict[str, Any]]:
out: Dict[str, Dict[str, Any]] = {}
for idx, chn in enumerate(channels):
channel_info = {f: v for f, v in chn.items() if f != "name"}
out[chn["name"]] = {"value": y[:, idx], **channel_info}
return out
def build_encoder_from_dict(enc_dict):
if "model_type" in enc_dict:
cfg_cls = AutoConfig.for_model(enc_dict["model_type"])
enc_cfg = cfg_cls.from_dict(enc_dict, strict=False)
elif "_name_or_path" in enc_dict:
enc_cfg = AutoConfig.from_pretrained(enc_dict["_name_or_path"], strict=False)
else:
raise KeyError("Encoder config is missing 'model_type' and '_name_or_path.")
# Ensure pooling layer is disabled to match saved checkpoints
if hasattr(enc_cfg, "add_pooling_layer"):
enc_cfg.add_pooling_layer = False
return AutoModel.from_config(enc_cfg)
class MISTMultiTaskConfig(PretrainedConfig):
"""HuggingFace config for a multi-task MIST wrapper."""
model_type = "mist_multitask"
def __init__(
self,
encoder: Optional[Dict[str, Any]] = None,
task_networks: Optional[List[Dict[str, Any]]] = None,
transforms: Optional[List[Dict[str, Any]]] = None,
channels: Optional[List[Dict[str, Any]]] = None,
tokenizer_class: Optional[str] = "SmirkTokenizer",
**kwargs,
):
super().__init__(**kwargs)
self.encoder = encoder or {}
self.task_networks = task_networks or []
self.transforms = transforms or []
self.channels = channels
self.tokenizer_class = tokenizer_class
class MISTMultiTask(PreTrainedModel):
config_class = MISTMultiTaskConfig
def __init__(self, config: MISTMultiTaskConfig):
super().__init__(config)
self.encoder = build_encoder_from_dict(config.encoder)
self.task_networks = nn.ModuleList(
[
PredictionTaskHead(
embed_dim=tn["embed_dim"],
output_size=tn["output_size"],
dropout=tn["dropout"],
)
for tn in config.task_networks
]
)
self.transforms = nn.ModuleList(
[
AbstractNormalizer.get(tf_cfg["class"], tf_cfg["num_outputs"])
for tf_cfg in config.transforms
]
)
assert len(self.task_networks) == len(
self.transforms
), "task_networks and transforms must align"
self.channels = config.channels
self.tokenizer = None
self.post_init()
@classmethod
def from_components(
cls,
encoder: PreTrainedModel,
task_networks: List[nn.Module],
transforms: List[Any],
tokenizer: Optional[Any] = None,
channels: Optional[List[Dict[str, Any]]] = None,
) -> "MISTMultiTask":
cfg = MISTMultiTaskConfig(
encoder=encoder.config.to_dict(),
task_networks=[
{
"embed_dim": encoder.config.hidden_size,
"output_size": tn.final.out_features,
"dropout": tn.dropout1.p,
}
for tn in task_networks
],
transforms=[tf.to_config() for tf in transforms],
channels=channels,
tokenizer_class=(
getattr(tokenizer, "__class__", type("T", (), {})).__name__
if tokenizer
else "SmirkTokenizer"
),
)
model = cls(cfg)
model.encoder.load_state_dict(encoder.state_dict(), strict=False)
for dst, src in zip(model.task_networks, task_networks):
dst.load_state_dict(src.state_dict())
for dst, src in zip(model.transforms, transforms):
dst.load_state_dict(src.state_dict())
model.tokenizer = tokenizer
return model
def forward(self, input_ids, attention_mask=None):
hs = self.encoder(input_ids, attention_mask=attention_mask).last_hidden_state
outs = []
for tn, tf in zip(self.task_networks, self.transforms):
outs.append(tf.forward(tn(hs)))
return torch.cat(outs, dim=-1)
def _resolve_tokenizer(self, tokenizer):
if tokenizer is not None:
return tokenizer
if getattr(self, "tokenizer", None) is not None:
return self.tokenizer
try:
return AutoTokenizer.from_pretrained(
self.name_or_path, use_fast=True, trust_remote_code=True
)
except Exception:
return AutoTokenizer.from_pretrained(
self.config._name_or_path, use_fast=True, trust_remote_code=True
)
def predict(self, smi: List[str], tokenizer=None):
tok = self._resolve_tokenizer(tokenizer)
batch = tok(smi)
batch = DataCollatorWithPadding(tok)(batch)
inputs = {k: v.to(self.device) for k, v in batch.items()}
with torch.inference_mode():
out = self(**inputs).cpu()
if self.channels is None:
return out
return annotate_prediction(out, maybe_get_annotated_channels(self.channels))
def embed(self, smi: List[str], tokenizer=None):
tok = self._resolve_tokenizer(tokenizer)
batch = tok(smi)
batch = DataCollatorWithPadding(tok)(batch)
input_ids = batch["input_ids"].to(self.device)
attention_mask = batch["attention_mask"].to(self.device)
with torch.inference_mode():
hs = self.encoder(
input_ids, attention_mask=attention_mask
).last_hidden_state[:, 0, :]
return hs.to("cpu")
def save_pretrained(self, save_directory, **kwargs):
super().save_pretrained(save_directory, **kwargs)
if getattr(self, "tokenizer", None) is not None:
self.tokenizer.save_pretrained(save_directory)
class PredictionTaskHead(nn.Module):
def __init__(
self, embed_dim: int, output_size: int = 1, dropout: float = 0.2
) -> None:
super().__init__()
self.desc_skip_connection = True
self.fc1 = nn.Linear(embed_dim, embed_dim)
self.dropout1 = nn.Dropout(dropout)
self.relu1 = nn.GELU()
self.fc2 = nn.Linear(embed_dim, embed_dim)
self.dropout2 = nn.Dropout(dropout)
self.relu2 = nn.GELU()
self.final = nn.Linear(embed_dim, output_size)
def forward(self, emb):
if emb.ndim > 2:
emb = emb[:, 0, :]
x_out = self.fc1(emb)
x_out = self.dropout1(x_out)
x_out = self.relu1(x_out)
if self.desc_skip_connection is True:
x_out = x_out + emb
z = self.fc2(x_out)
z = self.dropout2(z)
z = self.relu2(z)
if self.desc_skip_connection is True:
z = self.final(z + x_out)
else:
z = self.final(z)
return z
class AbstractNormalizer(torch.nn.Module):
def __init__(self, num_outputs: Optional[int] = None):
super().__init__()
self.num_outputs = num_outputs
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Remove normalization"""
raise NotImplementedError
def inverse(self, x: torch.Tensor) -> torch.Tensor:
"""Apply normalization"""
raise NotImplementedError
def _fit(self, x: MaskedTensor) -> dict:
"""Fit the normalization parameters"""
raise NotImplementedError
def to_config(self) -> dict:
return {"class": self.__class__.__name__, "num_outputs": self.num_outputs}
def leader_fit(self, ds, rank: int, broadcast: Callable):
state = None
if rank == 0:
state = self.fit(ds)
state = broadcast(state)
self.load_state_dict(state)
def fit(self, ds, name: str = "target") -> dict:
"""Fit the normalization parameters on dataset"""
if isinstance(ds, IterableDataset):
target = []
mask = []
for x in ds:
target.append(x[name])
mask.append(x[f"{name}_mask"])
target = torch.stack(target)
mask = torch.stack(mask)
else:
target = torch.stack([torch.tensor(x) for x in ds[name]])
mask = torch.stack([torch.tensor(x) for x in ds[f"{name}_mask"]])
# Use masked tensor to compute normalization parameters
target = masked_tensor(target, mask)
state = self._fit(target)
return state
@classmethod
def get(
cls, transform: Optional[Union[list[str], str]], num_outputs: int
) -> "AbstractNormalizer":
if isinstance(transform, list):
assert len(transform) == num_outputs
return ChannelWiseTransform([cls.get(t, 1) for t in transform])
elif transform in ["standardize", Standardize.__name__]:
return Standardize(num_outputs)
elif transform in ["power_transform", PowerTransform.__name__]:
return PowerTransform(num_outputs)
elif transform in ["log_transform", LogTransform.__name__]:
return LogTransform(num_outputs)
elif transform in ["max_scale", MaxScaleTransform.__name__]:
return MaxScaleTransform(num_outputs)
else:
return IdentityTransform()
class Standardize(AbstractNormalizer):
def __init__(self, num_outputs: int, eps: float = 1e-8):
super().__init__(num_outputs)
self.register_buffer("mean", torch.zeros(num_outputs))
self.register_buffer("std", torch.zeros(num_outputs))
self.eps = float(eps)
assert 0 <= self.eps
def forward(self, x: torch.Tensor) -> torch.Tensor:
return (self.std * x) + self.mean
def inverse(self, x: torch.Tensor) -> torch.Tensor:
return (x - self.mean) / self.std
def fit(self, ds, name: str = "target") -> dict:
num_outputs = self.num_outputs
assert num_outputs is not None
mean = torch.zeros(num_outputs)
m2 = torch.zeros(num_outputs)
n = torch.zeros(num_outputs, dtype=torch.int)
for row in ds:
target = torch.tensor(row[name])
mask = torch.tensor(row[f"{name}_mask"])
x = masked_tensor(target, mask)
n += mask.view(-1, num_outputs).sum(0)
xs = x.view(-1, num_outputs).sum(0)
delta = xs - mean
# Only update masked values
mean += (delta / n).get_data().masked_fill(~delta.get_mask(), 0)
delta2 = xs - mean
m2 += (delta * delta2).get_data().masked_fill(~delta.get_mask(), 0)
self.mean = mean.to(self.mean)
self.std = (m2 / n).sqrt().to(self.std) + self.eps
self.mean[self.mean.isnan()] = 0
self.std[self.std.isnan()] = 1
logging.debug("Fitted %s", self.state_dict())
return self.state_dict()
def _fit(self, target: MaskedTensor) -> dict:
self.mean = target.mean(0).get_data().to(self.mean)
self.std = target.std(0).get_data().to(self.std) + self.eps
return self.state_dict()
def load_state_dict(self, state_dict: dict[str, Any], strict: bool = True, assign: bool = False):
# Handle legacy case where keys have "transform." prefix
if "transform.mean" in state_dict:
state_dict = state_dict.copy() # Don't modify original
state_dict["mean"] = state_dict.pop("transform.mean")
state_dict["std"] = state_dict.pop("transform.std")
if assign:
# Manually assign buffers when assign=True
for key, value in state_dict.items():
if key in ['mean', 'std']:
# Use register_buffer to properly replace the buffer
self.register_buffer(key, value)
result = None # No incompatible keys when we do it manually
else:
result = super().load_state_dict(state_dict, strict=strict, assign=False)
logging.debug(f" After loading: mean={self.mean}, std={self.std}")
return result
class TokenTaskHead(nn.Module):
def __init__(
self, embed_dim: int, output_size: int = 1, dropout: float = 0.2
) -> None:
super().__init__()
self.layers = nn.Sequential(
nn.Linear(embed_dim, embed_dim),
nn.Dropout(dropout),
nn.GELU(),
nn.Linear(embed_dim, embed_dim),
nn.Dropout(dropout),
nn.GELU(),
nn.Linear(embed_dim, output_size),
)
def forward(self, emb):
return self.layers(emb)
class TokenPairwiseDistance(nn.Module):
def __init__(
self,
embed_dim: int,
dropout: float = 0.2,
num_attention_heads: int = 1,
num_layers: int = 1,
activation: str = "relu",
ff_ratio: int = 2,
) -> None:
super().__init__()
enc_layer = nn.TransformerEncoderLayer(
d_model=embed_dim,
nhead=num_attention_heads,
dim_feedforward=ff_ratio * embed_dim,
dropout=dropout,
batch_first=True,
norm_first=True,
)
self.interaction = nn.TransformerEncoder(enc_layer, num_layers)
self.pairwise_distance = PairwiseMLP(embed_dim, dropout)
self.distance1 = nn.Sequential(
nn.Linear(embed_dim, embed_dim), nn.Dropout(dropout), nn.GELU()
)
self.distance2 = nn.Linear(embed_dim, 1)
def forward(self, hs: torch.Tensor) -> torch.Tensor:
hs = self.interaction(hs)
with torch.autocast("cuda", dtype=torch.float32):
pw_dist = self.pairwise_distance(hs)
d = self.distance1(pw_dist) + pw_dist
d = self.distance2(d).squeeze(-1)
return F.relu(F.elu(d) + 1)
class BiPairwiseBlock(nn.Module):
def __init__(self, d_model: int, bias: bool = True, device=None, dtype=None):
super().__init__()
factory_kwargs = {"device": device, "dtype": dtype}
self.bi_weight = nn.Parameter(torch.empty((d_model, d_model), **factory_kwargs))
self.lin_weight = nn.Parameter(
torch.empty((d_model, d_model), **factory_kwargs)
)
if bias:
self.bias = nn.Parameter(torch.empty(d_model, **factory_kwargs))
else:
self.register_parameter("bias", None)
self.reset_parameters()
# Gradient hook to enforce symmetry
self.bi_weight.register_hook(lambda grad: 0.5 * (grad + grad.T))
def reset_parameters(self):
nn.init.xavier_normal_(self.lin_weight, gain=nn.init.calculate_gain("relu"))
nn.init.xavier_normal_(self.bi_weight, gain=nn.init.calculate_gain("relu"))
with torch.no_grad():
self.bi_weight.copy_(0.5 * (self.bi_weight + self.bi_weight.T))
if self.bias is not None:
bound = 1 / math.sqrt(self.bias.size(0))
nn.init.uniform_(self.bias, -bound, bound)
def forward(self, x: torch.Tensor):
y_bi = torch.einsum("...ld,df,...rf->...lrf", x, self.bi_weight, x)
y_bi = 0.5 * (y_bi + y_bi.transpose(-3, -2)) # Enforce symmetry
x_linear = x.unsqueeze(-2) + x.unsqueeze(-3)
return y_bi + F.linear(x_linear, self.lin_weight, self.bias)
class PairwiseMLP(nn.Module):
def __init__(
self,
d_model: int,
dropout: float = 0.2,
device=None,
dtype=None,
) -> None:
super().__init__()
self.mlp = nn.Sequential(
nn.Linear(2 * d_model, d_model),
nn.Dropout(dropout),
nn.GELU(),
nn.Linear(d_model, d_model),
nn.GELU(),
)
def forward(self, x: torch.Tensor):
_, N, _ = x.shape
x_l = x.unsqueeze(-2).expand(-1, N, N, -1)
x_r = x.unsqueeze(-3).expand(-1, N, N, -1)
x_pw = torch.cat([x_l, x_r], dim=-1)
y = self.mlp(x_pw)
return 0.5 * (y + y.transpose(1, 2))
class ChannelWiseTransform(AbstractNormalizer):
def __init__(self, transforms: list[AbstractNormalizer]):
super().__init__(len(transforms))
self.transforms = torch.nn.ModuleList(transforms)
def to_config(self) -> dict:
return {
"class": [t.__class__.__name__ for t in self.transforms],
"num_outputs": self.num_outputs,
}
def inverse(self, x: torch.Tensor) -> torch.Tensor:
return torch.cat(
[
transform.inverse(x[:, [idx]])
for idx, transform in enumerate(self.transforms)
],
dim=1,
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return torch.cat(
[
transform.forward(x[:, [idx]])
for idx, transform in enumerate(self.transforms)
],
dim=1,
)
def _fit(self, x: MaskedTensor) -> dict:
for idx, transform in enumerate(self.transforms):
transform._fit(x[:, [idx]])
return self.state_dict()
class LogTransform(Standardize):
def forward(self, x: torch.Tensor) -> torch.Tensor:
return torch.exp(super().forward(x))
def inverse(self, x: torch.Tensor) -> torch.Tensor:
return super().inverse(torch.log(x))
def _fit(self, target: MaskedTensor) -> dict:
return super()._fit(torch.log(target))
class PowerTransform(AbstractNormalizer):
"""
Apply a power transform (Yeo-Johnson) featurewise to make data more Gaussian-like.
Followed by applying a zero-mean, unit-variance normalization to the
transformed output to rescale targets to [-1, 1].
"""
def __init__(self, num_outputs, eps: float = 1e-8):
super().__init__(num_outputs)
self.num_outputs = num_outputs
self.register_buffer("lmbdas", torch.zeros(num_outputs))
self.register_buffer("mean", torch.zeros(num_outputs))
self.register_buffer("std", torch.zeros(num_outputs))
self.eps = float(eps)
assert 0 <= self.eps
def _yeo_johnson_transform(self, x, lmbda):
"""
Return transformed input x following Yeo-Johnson transform with
parameter lambda.
Adapted from
https://github.com/scikit-learn/scikit-learn/blob/fbb32eae5/sklearn/preprocessing/_data.py#L3354
"""
x_out = x.clone()
eps = torch.finfo(x.dtype).eps
pos = x >= 0 # binary mask
# when x >= 0
if abs(lmbda) < eps:
x_out[pos] = torch.log1p(x[pos])
else: # lmbda != 0
x_out[pos] = (torch.pow(x[pos] + 1, lmbda) - 1) / lmbda
# when x < 0
if abs(lmbda - 2) > eps:
x_out[~pos] = -(torch.pow(-x[~pos] + 1, 2 - lmbda) - 1) / (2 - lmbda)
else: # lmbda == 2
x_out[~pos] = -torch.log1p(-x[~pos])
return x_out
def _yeo_johnson_inverse_transform(self, x, lmbda):
"""
Return inverse-transformed input x following Yeo-Johnson inverse
transform with parameter lambda.
Adapted from
https://github.com/scikit-learn/scikit-learn/blob/fbb32eae5/sklearn/preprocessing/_data.py#L3383
"""
x_out = x.clone()
pos = x >= 0
eps = torch.finfo(x.dtype).eps
# when x >= 0
if abs(lmbda) < eps: # lmbda == 0
x_out[pos] = torch.exp(x[pos]) - 1
else: # lmbda != 0
x_out[pos] = torch.pow(x[pos] * lmbda + 1, 1 / lmbda) - 1
# when x < 0
if abs(lmbda - 2) > eps: # lmbda != 2
x_out[~pos] = 1 - torch.pow(-(2 - lmbda) * x[~pos] + 1, 1 / (2 - lmbda))
else: # lmbda == 2
x_out[~pos] = 1 - torch.exp(-x[~pos])
return x_out
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Undo standardization
x = (self.std * x) + self.mean
x_out = torch.zeros_like(x)
for i in range(self.num_outputs):
x_out[:, i] = self._yeo_johnson_inverse_transform(x[:, i], self.lmbdas[i])
return x_out
def inverse(self, x: torch.Tensor) -> torch.Tensor:
x_out = torch.zeros_like(x)
for i in range(self.num_outputs):
x_out[:, i] = self._yeo_johnson_transform(x[:, i], self.lmbdas[i])
# Standardization
x_out = (x_out - self.mean) / self.std
return x_out
def _fit(self, target: MaskedTensor) -> dict:
# Fit Yeo-Johnson lambdas
from sklearn.preprocessing import (
PowerTransformer as _PowerTransformer, # noqa: F811
)
transformer = _PowerTransformer(method="yeo-johnson", standardize=False)
target = torch.tensor(transformer.fit_transform(target.get_data().numpy()))
self.lmbdas = torch.tensor(transformer.lambdas_)
# Fit standardization scaling
self.mean = target.mean(0).to(self.mean)
self.std = target.std(0).to(self.std) + self.eps
return self.state_dict()
class MaxScaleTransform(AbstractNormalizer):
"""
Divide by maximum value in training dataset.
"""
def __init__(self, mx: int, eps: float = 1e-8):
super().__init__(1)
self.num_outputs = 1
self.max = mx
self.eps = float(eps)
assert 0 <= self.eps
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Undo standardization
x_out = self.max * x
return x_out
def inverse(self, x: torch.Tensor) -> torch.Tensor:
x_out = x / self.max
return x_out
def _fit(self, target: MaskedTensor) -> dict:
return self.state_dict()
class IdentityTransform(AbstractNormalizer):
def inverse(self, x: torch.Tensor) -> torch.Tensor:
return x
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x
def _fit(self, x: MaskedTensor) -> dict:
return self.state_dict()