|
1 | | -from typing import Dict |
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from typing import Dict, Any, Optional, Union |
| 4 | +import os |
2 | 5 |
|
3 | 6 | import torch |
4 | 7 | import torch.nn as nn |
|
18 | 21 | ) |
19 | 22 | from .base_model import BaseModel |
20 | 23 |
|
| 24 | + |
| 25 | +def _iter_text_vectors( |
| 26 | + path: str, |
| 27 | + embedding_dim: int, |
| 28 | + wanted_tokens: set[str], |
| 29 | + encoding: str = "utf-8", |
| 30 | +) -> Dict[str, torch.Tensor]: |
| 31 | + """Loads word vectors from a text file (e.g., GloVe) for a subset of tokens. |
| 32 | +
|
| 33 | + Expected format: one token per line followed by embedding_dim floats. |
| 34 | +
|
| 35 | + This function reads the file line-by-line and only retains vectors for |
| 36 | + tokens present in `wanted_tokens`. |
| 37 | + """ |
| 38 | + |
| 39 | + if not os.path.exists(path): |
| 40 | + raise FileNotFoundError(f"pretrained embedding file not found: {path}") |
| 41 | + |
| 42 | + vectors: Dict[str, torch.Tensor] = {} |
| 43 | + with open(path, "r", encoding=encoding) as f: |
| 44 | + for line in f: |
| 45 | + line = line.strip() |
| 46 | + if not line: |
| 47 | + continue |
| 48 | + parts = line.split() |
| 49 | + # token + embedding_dim values |
| 50 | + if len(parts) < embedding_dim + 1: |
| 51 | + continue |
| 52 | + token = parts[0] |
| 53 | + if token not in wanted_tokens: |
| 54 | + continue |
| 55 | + try: |
| 56 | + vec = torch.tensor( |
| 57 | + [float(x) for x in parts[1 : embedding_dim + 1]], |
| 58 | + dtype=torch.float, |
| 59 | + ) |
| 60 | + except ValueError: |
| 61 | + continue |
| 62 | + vectors[token] = vec |
| 63 | + return vectors |
| 64 | + |
| 65 | + |
| 66 | +def init_embedding_with_pretrained( |
| 67 | + embedding: nn.Embedding, |
| 68 | + code_vocab: Dict[Any, int], |
| 69 | + pretrained_path: str, |
| 70 | + embedding_dim: int, |
| 71 | + pad_token: str = "<pad>", |
| 72 | + unk_token: str = "<unk>", |
| 73 | + normalize: bool = False, |
| 74 | + freeze: bool = False, |
| 75 | +) -> int: |
| 76 | + """Initializes an nn.Embedding from a pretrained text-vector file. |
| 77 | +
|
| 78 | + Tokens not found in the pretrained file are left as the module's existing |
| 79 | + random initialization. |
| 80 | +
|
| 81 | + Returns: |
| 82 | + int: number of tokens successfully initialized from the file. |
| 83 | + """ |
| 84 | + |
| 85 | + # Build wanted token set (stringified) |
| 86 | + vocab_tokens = {str(t) for t in code_vocab.keys()} |
| 87 | + vectors = _iter_text_vectors(pretrained_path, embedding_dim, vocab_tokens) |
| 88 | + |
| 89 | + loaded = 0 |
| 90 | + with torch.no_grad(): |
| 91 | + for tok, idx in code_vocab.items(): |
| 92 | + tok_s = str(tok) |
| 93 | + if tok_s in vectors: |
| 94 | + vec = vectors[tok_s] |
| 95 | + if normalize: |
| 96 | + vec = vec / (vec.norm(p=2) + 1e-12) |
| 97 | + embedding.weight[idx].copy_(vec) |
| 98 | + loaded += 1 |
| 99 | + |
| 100 | + # Ensure pad row is zero |
| 101 | + if pad_token in code_vocab: |
| 102 | + embedding.weight[code_vocab[pad_token]].zero_() |
| 103 | + # If embedding has a padding_idx, keep it consistent |
| 104 | + if embedding.padding_idx is not None: |
| 105 | + embedding.weight[embedding.padding_idx].zero_() |
| 106 | + |
| 107 | + if freeze: |
| 108 | + embedding.weight.requires_grad_(False) |
| 109 | + |
| 110 | + return loaded |
| 111 | + |
21 | 112 | class EmbeddingModel(BaseModel): |
22 | 113 | """ |
23 | 114 | EmbeddingModel is responsible for creating embedding layers for different types of input data. |
@@ -46,7 +137,14 @@ class EmbeddingModel(BaseModel): |
46 | 137 | - MultiHotProcessor: nn.Linear over multi-hot vector |
47 | 138 | """ |
48 | 139 |
|
49 | | - def __init__(self, dataset: SampleDataset, embedding_dim: int = 128): |
| 140 | + def __init__( |
| 141 | + self, |
| 142 | + dataset: SampleDataset, |
| 143 | + embedding_dim: int = 128, |
| 144 | + pretrained_emb_path: Optional[Union[str, Dict[str, str]]] = None, |
| 145 | + freeze_pretrained: bool = False, |
| 146 | + normalize_pretrained: bool = False, |
| 147 | + ): |
50 | 148 | super().__init__(dataset) |
51 | 149 | self.embedding_dim = embedding_dim |
52 | 150 | self.embedding_layers = nn.ModuleDict() |
@@ -81,6 +179,22 @@ def __init__(self, dataset: SampleDataset, embedding_dim: int = 128): |
81 | 179 | padding_idx=0, |
82 | 180 | ) |
83 | 181 |
|
| 182 | + # Optional pretrained initialization (e.g., GloVe). |
| 183 | + if pretrained_emb_path is not None: |
| 184 | + if isinstance(pretrained_emb_path, str): |
| 185 | + path = pretrained_emb_path |
| 186 | + else: |
| 187 | + path = pretrained_emb_path.get(field_name) |
| 188 | + if path: |
| 189 | + init_embedding_with_pretrained( |
| 190 | + self.embedding_layers[field_name], |
| 191 | + processor.code_vocab, |
| 192 | + path, |
| 193 | + embedding_dim=embedding_dim, |
| 194 | + normalize=normalize_pretrained, |
| 195 | + freeze=freeze_pretrained, |
| 196 | + ) |
| 197 | + |
84 | 198 | # Numeric features (including deep nested floats) -> nn.Linear over last dim |
85 | 199 | elif isinstance( |
86 | 200 | processor, |
|
0 commit comments