Vas0acc

Untitled

Nov 2nd, 2025
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.04 KB | Source Code | 0 0
  1. import os
  2. import torch
  3. import torch.optim as optim
  4. from torch import nn
  5. import math
  6. from transformers import AutoTokenizer
  7. from typing import Any
  8.  
  9. from huggingface_hub import hf_hub_download
  10. from safetensors.torch import load_file, save_file # For loading and saving the safetensors
  11.  
  12. WEIGHTS_DIR = "weights"
  13. WEIGHTS_PATH = os.path.join(WEIGHTS_DIR, "model.safetensors")
  14.  
  15. tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
  16.  
  17. class Config(object):
  18.     def __init__(self):
  19.         self.n_heads = 12
  20.         self.n_layers = 6
  21.         self.pad_token_id = tokenizer.pad_token_id
  22.         self.embedding_dim = 768
  23.         self.ffn_dim = 3072
  24.         self.max_position_embeddings = 512
  25.         self.vocab_size = 30522
  26.         self.eps = 1e-12
  27.  
  28.         self.attention_head_size = self.embedding_dim // self.n_heads
  29.         self.all_head_size = self.n_heads * self.attention_head_size
  30.  
  31.         self.n_classes = 1
  32.         self.device = 'cpu'
  33.  
  34. config = Config()
  35.  
  36. class DistilEmbeddings(nn.Module):
  37.     def __init__(self, config):
  38.         super().__init__()
  39.  
  40.         # Word and Position embeddings
  41.         self.word_embeddings = nn.Embedding(config.vocab_size, config.embedding_dim, padding_idx=config.pad_token_id)
  42.         self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.embedding_dim)
  43.  
  44.         # Layer Norm and Dropout
  45.         self.LayerNorm = nn.LayerNorm(config.embedding_dim, eps=config.eps)
  46.         self.dropout = nn.Dropout(0.1)
  47.  
  48.     def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
  49.         """
  50.        Parameters:
  51.            - input_ids (torch.Tensor):
  52.            torch.tensor(bs, max_seq_length) The token ids to embed.
  53.        Returns: torch.tensor(bs, max_seq_length, hidden_size) The embedded tokens (plus position embeddings and no segment embeddings for our task).
  54.        """
  55.  
  56.         # 1. Word Embeddings
  57.         input_embeds = self.word_embeddings(input_ids)  # (bs, max_seq_length, dim)
  58.         seq_length = input_embeds.size(1)
  59.  
  60.         # 2. Position Embeddings
  61.         position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device)  # (max_seq_length)
  62.         position_ids = position_ids.unsqueeze(0).expand_as(input_ids)  # (bs, max_seq_length)
  63.         position_embeddings = self.position_embeddings(position_ids)  # (bs, max_seq_length, dim)
  64.  
  65.         # 3. Combination
  66.         embeddings = input_embeds + position_embeddings
  67.  
  68.         # 4. Normalization
  69.         embeddings = self.LayerNorm(embeddings)  # (bs, max_seq_length, dim)
  70.  
  71.         return self.dropout(embeddings)
  72.  
  73. class MultiHeadSelfAttention(nn.Module):
  74.     def __init__(self, config):
  75.         super().__init__()
  76.  
  77.         self.heads = config.n_heads
  78.         self.head_dim = config.attention_head_size
  79.        
  80.         self.q = nn.Linear(config.embedding_dim, config.embedding_dim, bias=True)
  81.         self.k = nn.Linear(config.embedding_dim, config.embedding_dim, bias=True)
  82.         self.v = nn.Linear(config.embedding_dim, config.embedding_dim, bias=True)
  83.         self.out = nn.Linear(config.embedding_dim, config.embedding_dim, bias=True)
  84.         self.drop = nn.Dropout(0.1)
  85.  
  86.     def forward(self, x, attn_mask=None):
  87.             bs, s, embed_dim = x.shape
  88.             q = self.q(x).view(bs, s, self.heads, self.head_dim).transpose(1, 2) # (bs, n_heads, q_length, dim_per_head)
  89.             k = self.k(x).view(bs, s, self.heads, self.head_dim).transpose(1, 2)
  90.             v = self.v(x).view(bs, s, self.heads, self.head_dim).transpose(1, 2)
  91.             scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim) # (bs, n_heads, q_length, k_length)
  92.            
  93.             if attn_mask is not None:
  94.                 if attn_mask.dtype != torch.bool:
  95.                     attn_mask = attn_mask.bool()
  96.                 attn_mask = attn_mask.unsqueeze(1).unsqueeze(2)   # (B,1,1,S)
  97.                 scores = scores.masked_fill(~attn_mask, -1e9)
  98.                
  99.             weights = torch.softmax(scores, dim=-1) # (bs, n_heads, q_length, k_length)
  100.             weights = self.drop(weights)
  101.             context = torch.matmul(weights,v ) # (bs, n_heads, q_length, dim_per_head)
  102.             context = context.transpose(1, 2).contiguous().view(bs, s, embed_dim) # (bs, q_length, dim)
  103.             return self.drop(self.out(context))
  104.  
  105. class FeedForward(nn.Module):
  106.     def __init__(self, config):
  107.         super().__init__()
  108.  
  109.         self.fc1 = nn.Linear(config.embedding_dim, config.ffn_dim)
  110.         self.fc2 = nn.Linear(config.ffn_dim, config.embedding_dim)
  111.         self.drop = nn.Dropout(0.1)
  112.         self.act = nn.GELU()
  113.  
  114.     def forward(self, x):
  115.         x = self.fc1(x)
  116.         x = self.act(x)
  117.         x = self.drop(x)
  118.         x = self.fc2(x)
  119.         x = self.drop(x)
  120.         return x
  121.  
  122. class TransformerBlock(nn.Module):
  123.     def __init__(self, config):
  124.         super().__init__()
  125.  
  126.         self.attn = MultiHeadSelfAttention(config)
  127.         self.attn_ln = nn.LayerNorm(config.embedding_dim, eps=config.eps)
  128.         self.ffn = FeedForward(config)
  129.         self.ffn_ln = nn.LayerNorm(config.embedding_dim, eps=config.eps)
  130.  
  131.     def forward(self, x, attn_mask=None):
  132.         # 1. Attention Sub-layer (Input + Attention(Input) -> LayerNorm)
  133.         attn_output = self.attn(x, attn_mask)
  134.         x = self.attn_ln(x + attn_output)  # Residual + LayerNorm
  135.  
  136.         # 2. FFN Sub-layer (Input + FFN(Intermediate) -> LayerNorm)
  137.         ffn_output = self.ffn(x)
  138.         x = self.ffn_ln(x + ffn_output) # Residual + LayerNorm
  139.        
  140.         return x
  141.  
  142. class DistilBertEncoder(nn.Module):
  143.     def __init__(self, config):
  144.         super().__init__()
  145.        
  146.         self.config = config
  147.         self.emb = DistilEmbeddings(config)
  148.         self.layers = nn.ModuleList(
  149.             [TransformerBlock(config) for _ in range(config.n_layers)]
  150.         )
  151.  
  152.     def forward(self, input_ids, attn_mask=None):
  153.         x = self.emb(input_ids)
  154.         for blk in self.layers:
  155.             x = blk(x, attn_mask)
  156.         return x  # (bs, s, 768)
  157.  
  158. class Model(nn.Module):
  159.     def __init__(self, encoder, config):
  160.         super().__init__()
  161.  
  162.         self.encoder = encoder
  163.         self.drop = nn.Dropout(0.1)
  164.         self.out = nn.Linear(config.embedding_dim, config.n_classes)
  165.  
  166.     def forward(self, input_ids, attn_mask=None):
  167.         h = self.encoder(input_ids, attn_mask)
  168.         cls = h[:, 0, :] # CLS token
  169.        
  170.         # Output are raw scores/predictions, no final activation (like sigmoid) needed
  171.         # for the BCE loss.
  172.         return self.out(self.drop(cls)) # scores (bs, n_classes)
  173.  
  174. # --- WEIGHT MAPPING FUNCTION ---
  175. def map_hf_to_custom_keys(model: nn.Module, pretrained_state_dict: dict):
  176.     """
  177.    Maps keys from HuggingFace DistilBERT state_dict to our custom DistilBertEncoder.
  178.    """
  179.     mapped_weights = {}
  180.     config = model.config
  181.    
  182.     # Debug: print available HF keys (now removed for clarity)
  183.     # print("Available HF keys:", [k for k in pretrained_state_dict.keys() if 'embedding' in k])
  184.    
  185.     # 1. Embeddings Mapping
  186.     emb_mapping = {
  187.         'emb.word_embeddings': 'distilbert.embeddings.word_embeddings',
  188.         'emb.position_embeddings': 'distilbert.embeddings.position_embeddings',
  189.         'emb.LayerNorm': 'distilbert.embeddings.LayerNorm',
  190.     }
  191.    
  192.     for custom_prefix, hf_prefix in emb_mapping.items():
  193.         for suffix in ['.weight', '.bias']:
  194.             hf_key = hf_prefix + suffix
  195.             custom_key = custom_prefix + suffix
  196.            
  197.             if hf_key in pretrained_state_dict:
  198.                 mapped_weights[custom_key] = pretrained_state_dict[hf_key] # Save the weight/bias values under custom key
  199.  
  200.     # 2. Transformer Blocks Mapping (all 6 layers)
  201.     for i in range(config.n_layers):
  202.         # Attention
  203.         for hf_key, custom_key in [('q_lin', 'q'), ('k_lin', 'k'), ('v_lin', 'v'), ('out_lin', 'out')]:
  204.             for suffix in ['.weight', '.bias']:
  205.                 hf_full_key = f'distilbert.transformer.layer.{i}.attention.{hf_key}{suffix}'
  206.                 custom_full_key = f'layers.{i}.attn.{custom_key}{suffix}'
  207.                
  208.                 if hf_full_key in pretrained_state_dict:
  209.                     mapped_weights[custom_full_key] = pretrained_state_dict[hf_full_key] # Save the weight/bias values under custom key
  210.  
  211.         # FFN (FeedForward)
  212.         for hf_key, custom_key in [('lin1', 'fc1'), ('lin2', 'fc2')]:
  213.             for suffix in ['.weight', '.bias']:
  214.                 hf_full_key = f'distilbert.transformer.layer.{i}.ffn.{hf_key}{suffix}'
  215.                 custom_full_key = f'layers.{i}.ffn.{custom_key}{suffix}'
  216.                 if hf_full_key in pretrained_state_dict:
  217.                     mapped_weights[custom_full_key] = pretrained_state_dict[hf_full_key] # Save the weight/bias values under custom key
  218.  
  219.         # LayerNorms
  220.         for ln_name, hf_ln_prefix in [('attn_ln', 'sa_layer_norm'), ('ffn_ln', 'output_layer_norm')]:
  221.             for suffix in ['.weight', '.bias']:
  222.                 hf_full_key = f'distilbert.transformer.layer.{i}.{hf_ln_prefix}{suffix}'
  223.                 custom_full_key = f'layers.{i}.{ln_name}{suffix}'
  224.                 if hf_full_key in pretrained_state_dict:
  225.                     mapped_weights[custom_full_key] = pretrained_state_dict[hf_full_key] # Save the weight/bias values under custom key
  226.    
  227.     return mapped_weights
  228.  
  229. # --- WEIGHT INITIALIZATION FUNCTION ---
  230. def initialize_encoder_with_weights(encoder: nn.Module):
  231.     """
  232.    Initializes the custom encoder:
  233.    - Tries to load local safetensors from WEIGHTS_PATH
  234.    - If missing, downloads with huggingface_hub, saves to safetensors, then loads
  235.    - Maps HF keys -> our keys and loads with strict=False
  236.    """
  237.     os.makedirs(WEIGHTS_DIR, exist_ok=True)
  238.  
  239.     # 1) Try local safetensors first
  240.     pretrained_state_dict = None
  241.     if os.path.exists(WEIGHTS_PATH):
  242.         try:
  243.             print(f"[weights] Loading local safetensors: {WEIGHTS_PATH}")
  244.             pretrained_state_dict = load_file(WEIGHTS_PATH)  # dict[str, Tensor]
  245.         except Exception as e:
  246.             print(f"[weights][WARN] Could not load local file ({e}). Will try downloading.")
  247.  
  248.     # 2) Download with huggingface_hub if not found locally
  249.     if pretrained_state_dict is None:
  250.         try:
  251.             print("[weights] Downloading DistilBERT base uncased via huggingface_hub...")
  252.             # 'distilbert-base-uncased' stores weights in 'model.safetensors'
  253.             hf_file = hf_hub_download(
  254.                 repo_id="distilbert-base-uncased",
  255.                 filename="model.safetensors"
  256.             )
  257.             pretrained_state_dict = load_file(hf_file)  # HF keys -> Tensors
  258.  
  259.             # Persist for next runs
  260.             try:
  261.                 save_file(pretrained_state_dict, WEIGHTS_PATH)
  262.                 print(f"[weights] Saved to {WEIGHTS_PATH}")
  263.             except Exception as e_save:
  264.                 print(f"[weights][WARN] Could not cache weights to {WEIGHTS_PATH}: {e_save}")
  265.  
  266.         except Exception as e:
  267.             print(f"[weights][FATAL] Failed to obtain pretrained weights: {e}")
  268.             print("[weights] Aborting process.")
  269.             exit(0)
  270.  
  271.     # 3) Map keys and load
  272.     try:
  273.         mapped = map_hf_to_custom_keys(encoder, pretrained_state_dict)
  274.         incompatible = encoder.load_state_dict(mapped, strict=False) # Fill in what it can match, ignore the rest
  275.         if hasattr(incompatible, "missing_keys") and hasattr(incompatible, "unexpected_keys"):
  276.             print("[load] Missing keys:", incompatible.missing_keys)
  277.             print("[load] Unexpected keys:", incompatible.unexpected_keys)
  278.         print("[load] Pre-trained DistilBERT encoder weights successfully loaded.")
  279.     except Exception as e:
  280.         print(f"[load][FATAL] Loading mapped weights failed: {e}")
  281.         print("[load] Aborting process.")
  282.         exit(0)
  283.  
  284. # 1. INPUT DATA SIMULATOR (Will be replaced by our load_data function later)
  285. def data_simulator_generator(comments: list, tokenizer: Any, max_len: int, n_classes: int):
  286.     """
  287.    SIMULATOR: Yields tokenized data dictionaries and random regression labels (0.0 to 1.0).
  288.    This mimics the required output of the load_data generator.
  289.    """
  290.     for comment in comments:
  291.         # Simulate tokenization using the requested parameters
  292.         tokenized_output = tokenizer(
  293.             comment,
  294.             max_length=max_len,
  295.             padding='max_length',
  296.             truncation=True,
  297.             return_tensors='pt'
  298.         )
  299.        
  300.         # Extract inputs (x_i)
  301.         # Squeeze removes the batch dimension: (1, L) -> (L)
  302.         x_i = {k: v.squeeze(0) for k, v in tokenized_output.items()}
  303.        
  304.         # Simulate labels (y_i): 6 random toxicity scores (0.0 to 1.0)
  305.         y_i = torch.rand(n_classes)
  306.        
  307.         yield x_i, y_i
  308.  
  309. # 2. TRAINING LOOP PROTOTYPE
  310. def prototype_train_step(model, loss_fn, optimizer, data_gen):
  311.     """
  312.    Executes a single step to demonstrate the model's runnability.
  313.    """
  314.     model.train()
  315.    
  316.     # 1. Load Batch
  317.     try:
  318.         x, labels = next(data_gen)
  319.     except StopIteration:
  320.         print("Simulator exhausted.")
  321.         return None
  322.  
  323.     # Add batch dimension (B=1) for model input: (L) -> (1, L)
  324.     input_ids = x['input_ids'].unsqueeze(0)
  325.     attn_mask = x['attention_mask'].unsqueeze(0)
  326.     labels = labels.unsqueeze(0)
  327.  
  328.     # 2. Forward Pass
  329.     optimizer.zero_grad()
  330.     predictions = model(input_ids, attn_mask)
  331.  
  332.     # 3. Calculate Loss (MSE for regression)
  333.     loss = loss_fn(predictions, labels)
  334.  
  335.     # 4. Backward Pass and Update
  336.     loss.backward()
  337.     optimizer.step()
  338.    
  339.     print(f"Prototype training step completed (Regression/MSE). Loss: {loss.item():.6f}")
  340.     return loss.item()
  341.  
  342. # 3. EXECUTION OF PROTOTYPE
  343. # Simulate comments taken from train.csv
  344. SAMPLE_COMMENTS = [
  345.     "I hope you die a miserable death.",
  346.     "This movie was good.",
  347.     "Why don't you people just leave?",
  348.     "That is so stupid it makes me angry.",
  349.     "Wow I'm gonna be sick.",
  350. ]
  351.  
  352. # Initialize Data Simulator
  353. data_generator = data_simulator_generator(
  354.     comments=SAMPLE_COMMENTS,
  355.     tokenizer=tokenizer,
  356.     max_len=128,
  357.     n_classes=config.n_classes
  358. )
  359.  
  360. # Initialize Encoder and Load pretrained Weights
  361. encoder = DistilBertEncoder(config)
  362. initialize_encoder_with_weights(encoder)
  363.  
  364. # Initialize Model
  365. model = Model(encoder, config)
  366. # print(model)
  367.  
  368. # Initialize Loss (Binary Cross-Entropy with Logits) and Optimizer (Adam)
  369. loss_function = nn.BCEWithLogitsLoss()
  370. optimizer = optim.Adam(model.parameters(), lr=1e-4)
  371.  
  372. # Print Model Summary
  373. for name, param in model.named_parameters():
  374.     # print(f"{name:60s} {tuple(param.shape)}")
  375.     pass
  376.  
  377. # Print total and trainable parameters
  378. num_params = sum(p.numel() for p in model.parameters())
  379. trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
  380. print(f"Total params: {num_params:,}, Trainable: {trainable:,}")
  381.  
  382. # Run the Prototype Training Step
  383. print("--- Running Prototype Train Step ---")
  384. prototype_train_step(model, loss_function, optimizer, data_generator)
  385. print("------------------------------------")
Add Comment
Please, Sign In to add comment