LIJINGHAI111's picture
upload model.py
ad405d2 verified
Raw History Blame Contribute Delete
4.32 kB
"""DeepSets 编码器 + LSTM 解码器(方案 B 自包含分发包的平铺依赖副本)。"""
import torch
import torch.nn as nn
from vocab import Vocab
import config
class DeepSetsEncoder(nn.Module):
def __init__(self, input_dim: int = None):
super().__init__()
dims = config.ENCODER_MLP_DIMS
H = config.ENCODER_OUT_DIM
layers = []
in_dim = input_dim if input_dim is not None else 0
for out_dim in dims:
layers.append(nn.Linear(in_dim, out_dim))
layers.append(nn.ReLU())
in_dim = out_dim
self.phi = nn.Sequential(*layers)
rho_layers = [
nn.Linear(H * 2, H),
nn.ReLU(),
nn.Linear(H, H),
]
self.rho = nn.Sequential(*rho_layers)
def forward(self, X, y, n_samples, var_counts=None):
"""
X: [B, N_max, D_MAX] (padded, 0 for missing samples and missing vars)
y: [B, N_max]
n_samples: [B] 每个 batch 的真实样本数
var_counts: [B] 每个样本的真实变量维度(未使用,保留兼容签名)
"""
B, N_max, D = X.shape
v = torch.cat([X, y.unsqueeze(-1)], dim=-1)
h = self.phi(v) # [B, N_max, H]
# 只对样本维做 mask(padding 的行置零 / 置 -inf),broadcast 到 H 维
sample_mask = torch.arange(N_max, device=X.device).unsqueeze(0) < n_samples.unsqueeze(1)
sample_mask = sample_mask.unsqueeze(-1).float() # [B, N_max, 1]
h = h * sample_mask
h_mean = h.sum(dim=1) / n_samples.unsqueeze(-1).float().clamp(min=1)
h_max = h.masked_fill(sample_mask == 0, float("-inf")).max(dim=1).values
z = torch.cat([h_mean, h_max], dim=-1)
z = self.rho(z)
return z
class ExpressionLSTM(nn.Module):
def __init__(self, vocab_size: int):
super().__init__()
self.embedding = nn.Embedding(vocab_size, config.LSTM_EMBEDDING_DIM)
self.lstm = nn.LSTM(
input_size=config.LSTM_EMBEDDING_DIM,
hidden_size=config.LSTM_HIDDEN_DIM,
num_layers=config.LSTM_NUM_LAYERS,
batch_first=True,
dropout=config.DROPOUT if config.LSTM_NUM_LAYERS > 1 else 0,
)
self.proj = nn.Linear(config.LSTM_HIDDEN_DIM, vocab_size)
def forward(self, token_ids, hidden, cell):
"""
token_ids: [B, T]
hidden, cell: [1, B, H]
returns: logits [B, T, V], hidden, cell
"""
embedded = self.embedding(token_ids)
output, (hidden, cell) = self.lstm(embedded, (hidden, cell))
logits = self.proj(output)
return logits, hidden, cell
class SymbolicRegressionModel(nn.Module):
def __init__(self, vocab: Vocab):
super().__init__()
self.vocab = vocab
self.encoder = DeepSetsEncoder(input_dim=config.D_MAX + 1)
self.lstm = ExpressionLSTM(vocab.vocab_size)
def forward(self, batch):
X = batch["X"]
y = batch["y"]
n_samples = batch["n_samples"]
token_ids = batch["token_ids"]
var_counts = batch["var_counts"]
z = self.encoder(X, y, n_samples, var_counts)
hidden = z.unsqueeze(0)
cell = z.unsqueeze(0)
logits, _, _ = self.lstm(token_ids[:, :-1], hidden, cell)
return logits
@torch.no_grad()
def generate(self, X, y, n_samples, var_counts=None, max_len: int = None):
self.eval()
if max_len is None:
max_len = config.T_MAX
B = X.size(0)
if var_counts is None:
var_counts = torch.full((B,), X.size(-1), device=X.device)
z = self.encoder(X, y, n_samples, var_counts)
hidden = z.unsqueeze(0)
cell = z.unsqueeze(0)
token = torch.full((B, 1), fill_value=self.vocab.sos_id, device=X.device)
generated = [token]
for _ in range(max_len):
embedded = self.lstm.embedding(token)
output, (hidden, cell) = self.lstm.lstm(embedded, (hidden, cell))
logits = self.lstm.proj(output)
token = logits.argmax(dim=-1)
generated.append(token)
if (token == self.vocab.eos_id).all():
break
all_tokens = torch.cat(generated, dim=1)
return all_tokens