Download modeling_ftt.py from thinkhong/RAIC: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/thinkhong/RAIC/resolve/main/modeling_ftt.py
- Command line
-
hf download hf://thinkhong/RAIC/modeling_ftt.py
-
curl -L -o modeling_ftt.py https://huggingface.co/thinkhong/RAIC/resolve/main/modeling_ftt.py
16 kB
| """ | |
| FTT (Feature Tokenizer Transformer) for tabular UK Biobank data. | |
| A DistilBERT-style transformer adapted to tabular inputs: each row is | |
| treated as a "sentence" whose "tokens" are its features. Continuous | |
| features are embedded via a per-feature learned weight vector scaled by | |
| the (min-max normalized) value; categorical features are embedded via a | |
| per-value lookup table. A separate embedding table encodes which feature | |
| slot ("variable") each token corresponds to, functioning like a position | |
| embedding. | |
| This file is loaded automatically via `trust_remote_code=True` through the | |
| `auto_map` entries in config.json. | |
| """ | |
| from typing import Optional, Tuple, Union | |
| from transformers.models.distilbert.modeling_distilbert import ( | |
| Embeddings, | |
| DistilBertModel, | |
| Transformer, | |
| DistilBertForMaskedLM, | |
| DistilBertForSequenceClassification, | |
| ) | |
| from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask_for_sdpa | |
| from transformers import PretrainedConfig | |
| from transformers.modeling_outputs import BaseModelOutput, MaskedLMOutput, SequenceClassifierOutput | |
| from transformers.utils import logging | |
| from transformers.activations import get_activation | |
| import torch | |
| from torch import nn | |
| from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss | |
| import torch.nn.functional as F | |
| logger = logging.get_logger(__name__) | |
| class FTTConfig(PretrainedConfig): | |
| """Config for the FTT tabular transformer (DistilBERT-style hyperparameters).""" | |
| model_type = "ftt" | |
| def __init__( | |
| self, | |
| vocab_size: int = 512, | |
| var_weights_size: int = 512, | |
| var_bias_size: int = 512, | |
| max_position_embeddings: int = 256, | |
| sinusoidal_pos_embds: bool = False, | |
| n_layers: int = 8, | |
| n_heads: int = 16, | |
| dim: int = 128, | |
| hidden_dim: int = 1024, | |
| dropout: float = 0.1, | |
| attention_dropout: float = 0.1, | |
| activation: str = "gelu", | |
| initializer_range: float = 0.02, | |
| qa_dropout: float = 0.1, | |
| seq_classif_dropout: float = 0.2, | |
| tie_weights_: bool = True, | |
| pad_token_id: int = 3, | |
| **kwargs, | |
| ): | |
| self.vocab_size = vocab_size | |
| self.var_weights_size = var_weights_size | |
| self.var_bias_size = var_bias_size | |
| self.max_position_embeddings = max_position_embeddings | |
| self.sinusoidal_pos_embds = sinusoidal_pos_embds | |
| self.n_layers = n_layers | |
| self.num_hidden_layers = n_layers | |
| self.n_heads = n_heads | |
| self.dim = dim | |
| self.hidden_dim = hidden_dim | |
| self.dropout = dropout | |
| self.attention_dropout = attention_dropout | |
| self.activation = activation | |
| self.initializer_range = initializer_range | |
| self.qa_dropout = qa_dropout | |
| self.seq_classif_dropout = seq_classif_dropout | |
| self.tie_weights_ = tie_weights_ | |
| super().__init__(pad_token_id=pad_token_id, **kwargs) | |
| class TableEmbeddings(Embeddings): | |
| def __init__(self, config: PretrainedConfig): | |
| super().__init__(config) | |
| self.variable_weights_embeddings = nn.Embedding(config.var_weights_size, config.dim, padding_idx=config.pad_token_id) | |
| self.variable_bias_embeddings = nn.Embedding(config.max_position_embeddings, config.dim, padding_idx=config.pad_token_id) | |
| if config.sinusoidal_pos_embds: | |
| raise NotImplementedError("We don't use sinusoidal position embeddings in gene embedding") | |
| self.LayerNorm = nn.LayerNorm(config.dim, eps=1e-12) | |
| self.dropout = nn.Dropout(config.dropout) | |
| self.config = config | |
| def forward( | |
| self, | |
| num_input_val: Optional[torch.Tensor] = None, | |
| num_input_ids: Optional[torch.Tensor] = None, | |
| cat_input_ids: Optional[torch.Tensor] = None, | |
| num_variable_ids: Optional[torch.Tensor] = None, | |
| cat_variable_ids: Optional[torch.Tensor] = None, | |
| input_embeds: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| if num_input_ids is None and cat_input_ids is None: | |
| raise ValueError("any num_input_ids or cat_input_ids is required") | |
| if num_input_ids is not None: | |
| num_embed = self.variable_weights_embeddings(num_input_ids) | |
| num_embed = torch.einsum("bl,bld->bld", num_input_val, num_embed) | |
| if cat_input_ids is not None: | |
| cat_embed = self.variable_weights_embeddings(cat_input_ids) | |
| if num_input_val is None and cat_input_ids is not None: | |
| weights_embeddings = cat_embed | |
| bias_embeddings = self.variable_bias_embeddings(cat_variable_ids) | |
| elif num_input_val is not None and cat_input_ids is None: | |
| weights_embeddings = num_embed | |
| bias_embeddings = self.variable_bias_embeddings(num_variable_ids) | |
| else: | |
| weights_embeddings = torch.cat([num_embed, cat_embed], dim=1) | |
| bias_embeddings = self.variable_bias_embeddings(torch.cat([num_variable_ids, cat_variable_ids], dim=1)) | |
| embeddings = weights_embeddings + bias_embeddings | |
| embeddings = self.LayerNorm(embeddings) | |
| embeddings = self.dropout(embeddings) | |
| return embeddings | |
| class FTTModel(DistilBertModel): | |
| config_class = FTTConfig | |
| def __init__(self, config: PretrainedConfig): | |
| super().__init__(config) | |
| self.embeddings = TableEmbeddings(config) | |
| self.transformer = Transformer(config) | |
| self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" | |
| self.post_init() | |
| def forward( | |
| self, | |
| num_input_val: Optional[torch.Tensor] = None, | |
| num_input_ids: Optional[torch.Tensor] = None, | |
| cat_input_ids: Optional[torch.Tensor] = None, | |
| num_variable_ids: Optional[torch.Tensor] = None, | |
| cat_variable_ids: Optional[torch.Tensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| head_mask: Optional[torch.Tensor] = None, | |
| inputs_embeds: Optional[torch.Tensor] = None, | |
| output_attentions: Optional[bool] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| ) -> Union[BaseModelOutput, Tuple[torch.Tensor, ...]]: | |
| output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions | |
| output_hidden_states = ( | |
| output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states | |
| ) | |
| return_dict = return_dict if return_dict is not None else self.config.use_return_dict | |
| if inputs_embeds is not None: | |
| raise ValueError("inputs_embeds is not supported") | |
| elif num_input_val is None and cat_input_ids is None: | |
| raise ValueError("any num_input_ids or cat_input_ids is required") | |
| if num_input_val is not None and cat_input_ids is not None: | |
| input_shape = torch.tensor(num_input_val.size()) | |
| input_shape[1] = input_shape[1] + cat_input_ids.size(1) | |
| embeddings = self.embeddings( | |
| num_input_val, | |
| num_input_ids, | |
| cat_input_ids, | |
| num_variable_ids, | |
| cat_variable_ids, | |
| ) | |
| device = embeddings.device | |
| head_mask_is_none = head_mask is None | |
| head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers) | |
| if self._use_flash_attention_2: | |
| attention_mask = attention_mask if (attention_mask is not None and 0 in attention_mask) else None | |
| else: | |
| if attention_mask is None: | |
| attention_mask = torch.ones(input_shape, device=device) | |
| if self._use_sdpa and head_mask_is_none and not output_attentions: | |
| attention_mask = _prepare_4d_attention_mask_for_sdpa( | |
| attention_mask, embeddings.dtype, tgt_len=input_shape[1] | |
| ) | |
| return self.transformer( | |
| x=embeddings, | |
| attn_mask=attention_mask, | |
| head_mask=head_mask, | |
| output_attentions=output_attentions, | |
| output_hidden_states=output_hidden_states, | |
| return_dict=return_dict, | |
| ) | |
| class IgnoreMaskingMSELoss(nn.Module): | |
| def __init__(self, ignore_index=-100): | |
| super(IgnoreMaskingMSELoss, self).__init__() | |
| self.ignore_index = ignore_index | |
| def forward(self, prediction, target): | |
| mask = target != self.ignore_index | |
| filtered_pred = prediction[mask] | |
| filtered_target = target[mask] | |
| return F.mse_loss(filtered_pred, filtered_target) | |
| class FTTForMaskedLM(DistilBertForMaskedLM): | |
| config_class = FTTConfig | |
| _tied_weights_keys = ["vocab_projector.weight"] | |
| def __init__(self, config: PretrainedConfig): | |
| super().__init__(config) | |
| self.activation = get_activation(config.activation) | |
| self.distilbert = FTTModel(config) | |
| self.vocab_transform = nn.Linear(config.dim, config.dim) | |
| self.vocab_layer_norm = nn.LayerNorm(config.dim, eps=1e-12) | |
| self.vocab_projector = nn.Linear(config.dim, config.vocab_size) | |
| self.num_projector = nn.Linear(config.dim, 1) | |
| self.post_init() | |
| self.mlm_loss_fct = nn.CrossEntropyLoss() | |
| self.mlm_loss_mse = IgnoreMaskingMSELoss() | |
| def forward( | |
| self, | |
| num_input_val: Optional[torch.Tensor] = None, | |
| num_input_ids: Optional[torch.Tensor] = None, | |
| cat_input_ids: Optional[torch.Tensor] = None, | |
| num_variable_ids: Optional[torch.Tensor] = None, | |
| cat_variable_ids: Optional[torch.Tensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| head_mask: Optional[torch.Tensor] = None, | |
| inputs_embeds: Optional[torch.Tensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| output_attentions: Optional[bool] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| ) -> Union[MaskedLMOutput, Tuple[torch.Tensor, ...]]: | |
| return_dict = return_dict if return_dict is not None else self.config.use_return_dict | |
| dlbrt_output = self.distilbert( | |
| num_input_val=num_input_val, | |
| num_input_ids=num_input_ids, | |
| cat_input_ids=cat_input_ids, | |
| num_variable_ids=num_variable_ids, | |
| cat_variable_ids=cat_variable_ids, | |
| attention_mask=attention_mask, | |
| head_mask=head_mask, | |
| inputs_embeds=inputs_embeds, | |
| output_attentions=output_attentions, | |
| output_hidden_states=output_hidden_states, | |
| return_dict=return_dict, | |
| ) | |
| hidden_states = dlbrt_output[0] | |
| prediction_logits = self.vocab_transform(hidden_states) | |
| prediction_logits = self.activation(prediction_logits) | |
| prediction_logits = self.vocab_layer_norm(prediction_logits) | |
| continuous_prediction_logits = self.num_projector(prediction_logits[:, : num_variable_ids.size(1), :]) | |
| continuous_prediction_logits = continuous_prediction_logits.squeeze(-1) | |
| categorical_prediction_logits = self.vocab_projector(prediction_logits[:, num_variable_ids.size(1) :, :]) | |
| mlm_loss = None | |
| if labels is not None: | |
| continuous_labels = labels[:, : num_variable_ids.size(1)] | |
| continuous_mlm_loss = self.mlm_loss_mse(continuous_prediction_logits, continuous_labels) | |
| categorical_labels = labels[:, num_variable_ids.size(1) :].type(torch.LongTensor).to(device=self.distilbert.device) | |
| categorical_mlm_loss = self.mlm_loss_fct( | |
| categorical_prediction_logits.view(-1, categorical_prediction_logits.size(-1)), | |
| categorical_labels.reshape(-1), | |
| ) | |
| mlm_loss = continuous_mlm_loss + categorical_mlm_loss | |
| else: | |
| return { | |
| "continuous_prediction_logits": continuous_prediction_logits, | |
| "categorical_prediction_logits": categorical_prediction_logits, | |
| } | |
| return MaskedLMOutput( | |
| loss=mlm_loss, | |
| logits=prediction_logits, | |
| hidden_states=dlbrt_output.hidden_states, | |
| attentions=dlbrt_output.attentions, | |
| ) | |
| class FTTForSequenceClassification(DistilBertForSequenceClassification): | |
| config_class = FTTConfig | |
| def __init__(self, config: PretrainedConfig): | |
| super().__init__(config) | |
| self.num_labels = config.num_labels | |
| self.config = config | |
| self.distilbert = FTTModel(config) | |
| self.pre_classifier = nn.Linear(config.dim, config.dim) | |
| self.classifier = nn.Linear(config.dim, config.num_labels) | |
| self.dropout = nn.Dropout(config.seq_classif_dropout) | |
| self.sigmoid = nn.Sigmoid() | |
| self.post_init() | |
| def forward( | |
| self, | |
| num_input_val: Optional[torch.Tensor] = None, | |
| num_input_ids: Optional[torch.Tensor] = None, | |
| cat_input_ids: Optional[torch.Tensor] = None, | |
| num_variable_ids: Optional[torch.Tensor] = None, | |
| cat_variable_ids: Optional[torch.Tensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| head_mask: Optional[torch.Tensor] = None, | |
| inputs_embeds: Optional[torch.Tensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| output_attentions: Optional[bool] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| ) -> Union[SequenceClassifierOutput, Tuple[torch.Tensor, ...]]: | |
| return_dict = return_dict if return_dict is not None else self.config.use_return_dict | |
| distilbert_output = self.distilbert( | |
| num_input_val=num_input_val, | |
| num_input_ids=num_input_ids, | |
| cat_input_ids=cat_input_ids, | |
| num_variable_ids=num_variable_ids, | |
| cat_variable_ids=cat_variable_ids, | |
| attention_mask=attention_mask, | |
| head_mask=head_mask, | |
| inputs_embeds=inputs_embeds, | |
| output_attentions=output_attentions, | |
| output_hidden_states=output_hidden_states, | |
| return_dict=return_dict, | |
| ) | |
| hidden_state = distilbert_output[0] | |
| pooled_output = hidden_state[:, 0] | |
| pooled_output = self.pre_classifier(pooled_output) | |
| pooled_output = nn.ReLU()(pooled_output) | |
| pooled_output = self.dropout(pooled_output) | |
| logits = self.classifier(pooled_output) | |
| loss = None | |
| if labels is not None: | |
| if self.config.problem_type is None: | |
| if self.num_labels == 1: | |
| self.config.problem_type = "regression" | |
| elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int): | |
| self.config.problem_type = "single_label_classification" | |
| else: | |
| self.config.problem_type = "multi_label_classification" | |
| if self.config.problem_type == "regression": | |
| labels = labels.float() | |
| loss_fct = MSELoss() | |
| if self.num_labels == 1: | |
| loss = loss_fct(logits.squeeze(), labels.squeeze()) | |
| else: | |
| loss = loss_fct(logits, labels) | |
| elif self.config.problem_type == "single_label_classification": | |
| loss_fct = CrossEntropyLoss() | |
| loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) | |
| elif self.config.problem_type == "multi_label_classification": | |
| labels = labels.float() | |
| loss_fct = BCEWithLogitsLoss() | |
| loss = loss_fct(logits.squeeze(), labels) | |
| if not return_dict: | |
| output = (logits,) + distilbert_output[1:] | |
| return ((loss,) + output) if loss is not None else output | |
| return SequenceClassifierOutput( | |
| loss=loss, | |
| logits=logits, | |
| hidden_states=distilbert_output.hidden_states, | |
| attentions=distilbert_output.attentions, | |
| ) | |