saturncube 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- saturncube/__init__.py +69 -0
- saturncube/_docs.py +89 -0
- saturncube/attention.py +451 -0
- saturncube/base.py +396 -0
- saturncube/classical.py +207 -0
- saturncube/codegen.py +184 -0
- saturncube/cognition.py +124 -0
- saturncube/data.py +1045 -0
- saturncube/deeprl.py +629 -0
- saturncube/evaluation.py +499 -0
- saturncube/experiment.py +220 -0
- saturncube/graph.py +694 -0
- saturncube/layers.py +369 -0
- saturncube/memory.py +359 -0
- saturncube/meta.py +237 -0
- saturncube/metric.py +293 -0
- saturncube/optim.py +314 -0
- saturncube/pycode.py +180 -0
- saturncube/retrieval.py +213 -0
- saturncube/rl.py +412 -0
- saturncube/rules.py +376 -0
- saturncube/search.py +130 -0
- saturncube/sequence.py +588 -0
- saturncube/spiking.py +242 -0
- saturncube/strategies.py +171 -0
- saturncube/symbolic.py +212 -0
- saturncube/text.py +349 -0
- saturncube/unsupervised.py +578 -0
- saturncube/vector.py +200 -0
- saturncube/verify.py +143 -0
- saturncube/vision.py +73 -0
- saturncube-0.7.0.dist-info/METADATA +714 -0
- saturncube-0.7.0.dist-info/RECORD +36 -0
- saturncube-0.7.0.dist-info/WHEEL +5 -0
- saturncube-0.7.0.dist-info/licenses/LICENSE +21 -0
- saturncube-0.7.0.dist-info/top_level.txt +1 -0
saturncube/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""saturncube: one fit/predict/evaluate interface across many model families."""
|
|
2
|
+
from .base import BaseModel, REGISTRY, available, create, register, warmup
|
|
3
|
+
from .vector import (MLP, DeepFeedforward, ResNet, DenseNet, Highway, SqueezeExcitation,
|
|
4
|
+
MixtureOfExperts, GatedMixtureOfExperts, Hypernetwork, ImplicitNetwork,
|
|
5
|
+
DeepEquilibrium, KAN, RBF, ExtremeLearningMachine, RandomNeuralNetwork)
|
|
6
|
+
|
|
7
|
+
from .rules import (FeedbackAlignment, DirectFeedbackAlignment, ForwardGradient, LocalErrorLearning,
|
|
8
|
+
SyntheticGradients, EquilibriumPropagation, ContrastiveHebbian, PredictiveCoding)
|
|
9
|
+
from .optim import OPTIMIZERS, Lion, LAMB, make_optimizer
|
|
10
|
+
|
|
11
|
+
from .unsupervised import (Autoencoder, DenoisingAutoencoder, VAE, MaskedPrediction, Contrastive,
|
|
12
|
+
BYOL, DeepClustering, HebbianLearner, TemporalContrastive, LatentPrediction,
|
|
13
|
+
EnergyBasedModel)
|
|
14
|
+
from .strategies import EWC, LwF, Replay, Distillation, distill, query
|
|
15
|
+
from .rl import (GridWorld, PotentialShaping, QLearning, SARSA, ExpectedSARSA, MonteCarloControl,
|
|
16
|
+
BernoulliBandit, EpsilonGreedyBandit, UCB1, ThompsonBandit, LinUCB, run_bandit,
|
|
17
|
+
OneHotObs, PointMass, SARSALambda, DynaQ, MaxEntIRL)
|
|
18
|
+
from .deeprl import (DQN, DoubleDQN, DuelingDQN, PrioritizedDQN, REINFORCE, ActorCritic, A2C, PPO, TRPO, SAC,
|
|
19
|
+
ModelBasedMPC, PreferenceRewardModel, LearnedRewardEnv, ReplayBuffer)
|
|
20
|
+
|
|
21
|
+
from .sequence import (SimpleRNN, ElmanNetwork, LSTM, GRU, BidirectionalRNN, BidirectionalLSTM,
|
|
22
|
+
BidirectionalGRU, ContinuousTimeRNN, NeuralStateSpace, S4, Mamba, PlasticRNN,
|
|
23
|
+
JordanNetwork, EchoStateNetwork, LiquidStateMachine, RTRLRNN)
|
|
24
|
+
|
|
25
|
+
from .attention import (MultiHeadAttention, LinearAttention, TransformerLayer, TransformerEncoder,
|
|
26
|
+
TransformerDecoder, LinearAttentionTransformer, LocalAttentionTransformer,
|
|
27
|
+
SparseAttentionTransformer, MultiQueryTransformer, GroupedQueryTransformer,
|
|
28
|
+
UniversalTransformer, MemoryAugmentedTransformer, Perceiver, SSMAttentionHybrid,
|
|
29
|
+
RecurrentAttention, RetrievalAugmented, EncoderDecoderTransformer)
|
|
30
|
+
|
|
31
|
+
from .memory import (HopfieldNetwork, ModernHopfield, AssociativeMemory, ContentAddressableMemory,
|
|
32
|
+
ProductKeyMemory, ProductKeyMemoryNet, MemoryNetwork, KeyValueMemoryNetwork,
|
|
33
|
+
NeuralTuringMachine, DifferentiableNeuralComputer)
|
|
34
|
+
|
|
35
|
+
from .metric import (Siamese, TripletNetwork, TwinTower, ContrastiveEmbedding, PrototypicalNetwork,
|
|
36
|
+
RelationNetwork, MatchingNetwork)
|
|
37
|
+
from .meta import (Task, SineTasks, GaussianTasks, MAML, Reptile, transfer, fit_curriculum, fit_self_paced,
|
|
38
|
+
GenerativeReplay)
|
|
39
|
+
|
|
40
|
+
from .graph import (Graph, Tree, GraphConvolutionalNetwork, GraphAttentionNetwork, GraphSAGE,
|
|
41
|
+
GraphIsomorphismNetwork, MessagePassingNetwork, RelationalGCN, GraphTransformer,
|
|
42
|
+
GatedGraphNetwork, RecurrentGraphNetwork, HypergraphNeuralNetwork, ProgramGraphNetwork,
|
|
43
|
+
TreeLSTM, TreeStructuredNetwork, PointerNetwork, PointerGenerator)
|
|
44
|
+
|
|
45
|
+
from .pycode import check_python, run_py, PythonSandbox, python_facts, ExecutionError
|
|
46
|
+
from .search import GeneticAlgorithm, SuccessiveHalving, RandomSearch, SimulatedAnnealing
|
|
47
|
+
from .symbolic import (walk, tree_size, to_python, ParseError, StepLimitExceeded, EnumerativeSynthesizer, Datalog,
|
|
48
|
+
REACHABILITY_RULES)
|
|
49
|
+
|
|
50
|
+
from .retrieval import BM25Index, HashingEmbedder, VectorStore, CaseLibrary, tokenize_text
|
|
51
|
+
from .cognition import SM2Scheduler, InterleavedPractice, RevisionLoop, MetacognitiveMonitor
|
|
52
|
+
from .verify import (UnitTests, TestReport, check_property, differential_test, ints, lists, IntGen,
|
|
53
|
+
ListGen, PropertyResult)
|
|
54
|
+
|
|
55
|
+
from .codegen import GrammarGenerator, PY_GRAMMAR, TemplateFiller, TEMPLATES, BPETokenizer
|
|
56
|
+
from .experiment import (ArchitectureSpec, RandomArchitectureGenerator, MODULE_KINDS, validate, ablate_module,
|
|
57
|
+
ablate_edge, execute, latin_hypercube, full_factorial, bootstrap_ci, permutation_test,
|
|
58
|
+
holm_correction)
|
|
59
|
+
from .classical import DecisionTree, RandomForest, KMeans, NaiveBayes
|
|
60
|
+
|
|
61
|
+
from .vision import CNN, CNNNet
|
|
62
|
+
from .spiking import SpikingNetwork, SelfOrganizingMap, LIFLayer
|
|
63
|
+
|
|
64
|
+
from .data import (load_dataset, Dataset, Prepared, Pipeline, TabularPreprocessor, Report, auto_model, AutoResult,
|
|
65
|
+
load_pipeline, check_arrays, split_indices)
|
|
66
|
+
from .evaluation import benchmark, BenchmarkResult
|
|
67
|
+
from .text import TextVectorizer, TokenEncoder, TextModel
|
|
68
|
+
|
|
69
|
+
__version__ = "0.7.0"
|
saturncube/_docs.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Builds the README's list of registered entries from the registry, so the two cannot drift apart.
|
|
2
|
+
|
|
3
|
+
Run ``python scripts/update_readme.py`` after adding or changing a registered class; the test suite fails if the
|
|
4
|
+
README block no longer matches (``python scripts/update_readme.py --check`` does the same from the command line).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from collections import Counter
|
|
10
|
+
|
|
11
|
+
from .base import REGISTRY, BaseModel, available
|
|
12
|
+
|
|
13
|
+
START = "<!-- registry-table:start -->"
|
|
14
|
+
END = "<!-- registry-table:end -->"
|
|
15
|
+
|
|
16
|
+
# Input conventions, only for families where one sentence is accurate for every entry.
|
|
17
|
+
FAMILY_NOTES = {
|
|
18
|
+
"vector": "Input: 2-D array `(n_samples, n_features)`.",
|
|
19
|
+
"classical": "Pure NumPy; input: 2-D array `(n_samples, n_features)`.",
|
|
20
|
+
"sequence": "Input: `(n, T, d)` floats, or integer tokens `(n, T)` with `vocab_size=`.",
|
|
21
|
+
"vision": "Input: images as `(n, C, H, W)` or `(n, H, W)`.",
|
|
22
|
+
"graph": "Input: `Graph` / `Tree` containers (dense adjacency, so small and medium graphs).",
|
|
23
|
+
"metric": "Few-shot: `predict(X, support=(X_support, y_support))` works on unseen classes.",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
FAMILY_ORDER = ["vector", "vision", "bio", "sequence", "memory", "metric", "graph", "unsupervised", "classical",
|
|
27
|
+
"agent", "bandit", "search", "symbolic", "cognition", "verification", "codegen", "experiment"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def kind_of(cls):
|
|
31
|
+
"""model: shared fit/predict/evaluate/save interface; classical: NumPy learner with fit/predict;
|
|
32
|
+
agent: learns by interacting with an environment or bandit; tool: a plain Python helper class."""
|
|
33
|
+
if issubclass(cls, BaseModel):
|
|
34
|
+
return "model"
|
|
35
|
+
if cls.family == "classical":
|
|
36
|
+
return "classical"
|
|
37
|
+
if cls.family in ("agent", "bandit"):
|
|
38
|
+
return "agent"
|
|
39
|
+
return "tool"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def summary_of(cls):
|
|
43
|
+
"""First sentence of the class's OWN docstring (never an inherited one)."""
|
|
44
|
+
doc = (cls.__dict__.get("__doc__") or "").strip()
|
|
45
|
+
if not doc:
|
|
46
|
+
return ""
|
|
47
|
+
para = " ".join(doc.split("\n\n")[0].split())
|
|
48
|
+
m = re.search(r"(?<=[a-z0-9\)`])\. (?=[A-Z])", para)
|
|
49
|
+
if m and m.start() >= 25:
|
|
50
|
+
para = para[:m.start()]
|
|
51
|
+
return para.rstrip(".").replace("|", "\\|")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def registry_markdown():
|
|
55
|
+
names = available()
|
|
56
|
+
kinds = Counter(kind_of(REGISTRY[n]) for n in names)
|
|
57
|
+
out = ["%d registered entries (`sc.available()`): %d models, %d classical learners, %d agents and bandits, "
|
|
58
|
+
"%d tools." % (len(names), kinds["model"], kinds["classical"], kinds["agent"], kinds["tool"]),
|
|
59
|
+
"",
|
|
60
|
+
"`model` entries share one `fit` / `predict` / `evaluate` / `save` / `load` interface and expose "
|
|
61
|
+
"`.module`. `classical` entries have `fit` / `predict` only. `agent` entries learn from an "
|
|
62
|
+
"environment or bandit instead of a dataset. `tool` entries are plain Python classes with their "
|
|
63
|
+
"own methods. Build any of them with `sc.create(name, **kwargs)`.", ""]
|
|
64
|
+
families = [f for f in FAMILY_ORDER if any(REGISTRY[n].family == f for n in names)]
|
|
65
|
+
families += sorted({REGISTRY[n].family for n in names} - set(families)) # a new family still shows up
|
|
66
|
+
for fam in families:
|
|
67
|
+
members = [n for n in names if REGISTRY[n].family == fam]
|
|
68
|
+
out.append("### %s (%d)" % (fam, len(members)))
|
|
69
|
+
out.append("")
|
|
70
|
+
if fam in FAMILY_NOTES:
|
|
71
|
+
out += [FAMILY_NOTES[fam], ""]
|
|
72
|
+
out += ["| Registry name | Class | Kind | Description |", "|---|---|---|---|"]
|
|
73
|
+
for n in members:
|
|
74
|
+
cls = REGISTRY[n]
|
|
75
|
+
out.append("| `%s` | `%s` | %s | %s |" % (n, cls.__name__, kind_of(cls), summary_of(cls)))
|
|
76
|
+
out.append("")
|
|
77
|
+
return "\n".join(out).rstrip() + "\n"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def render_block():
|
|
81
|
+
return START + "\n" + registry_markdown() + END
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def splice(readme_text):
|
|
85
|
+
"""README text with the generated block replaced (or raises if the markers are missing)."""
|
|
86
|
+
i, j = readme_text.find(START), readme_text.find(END)
|
|
87
|
+
if i < 0 or j < i:
|
|
88
|
+
raise ValueError("README is missing the %s ... %s markers" % (START, END))
|
|
89
|
+
return readme_text[:i] + render_block() + readme_text[j + len(END):]
|
saturncube/attention.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Attention and Transformer family (A3)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
import torch.nn as nn
|
|
9
|
+
import torch.nn.functional as F
|
|
10
|
+
|
|
11
|
+
from .base import register
|
|
12
|
+
from .sequence import Core, MambaBlock, SequenceModel, SeqNet, _CoreModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def sinusoid(T, d, device):
|
|
16
|
+
pos = torch.arange(T, device=device).float()[:, None]
|
|
17
|
+
div = torch.exp(-math.log(10000.0) * torch.arange(0, d, 2, device=device).float() / d)
|
|
18
|
+
pe = torch.zeros(T, d, device=device)
|
|
19
|
+
pe[:, 0::2], pe[:, 1::2] = torch.sin(pos * div), torch.cos(pos * div)[:, : d // 2]
|
|
20
|
+
return pe
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def make_mask(T, causal=False, window=None, stride=None, device=None):
|
|
24
|
+
"""Boolean (T, T) mask, True = may attend. window -> local band; stride -> every
|
|
25
|
+
stride-th earlier/later position as well (sparse, Child et al.)."""
|
|
26
|
+
i = torch.arange(T, device=device)[:, None]
|
|
27
|
+
j = torch.arange(T, device=device)[None, :]
|
|
28
|
+
allowed = torch.ones(T, T, dtype=torch.bool, device=device)
|
|
29
|
+
if window is not None or stride is not None:
|
|
30
|
+
allowed = (i - j).abs() <= window if window is not None else torch.zeros_like(allowed)
|
|
31
|
+
if stride:
|
|
32
|
+
allowed = allowed | ((i - j) % stride == 0)
|
|
33
|
+
return allowed & (j <= i) if causal else allowed
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MultiHeadAttention(nn.Module):
|
|
37
|
+
"""Multi-head (n_kv_heads=n_heads), multi-query (1) or grouped-query (1 < g < heads)
|
|
38
|
+
attention. Pass `context` for cross-attention."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, d, n_heads=4, n_kv_heads=None):
|
|
41
|
+
super().__init__()
|
|
42
|
+
kv = n_kv_heads or n_heads
|
|
43
|
+
if d % n_heads or n_heads % kv:
|
|
44
|
+
raise ValueError("d must divide by n_heads and n_heads by n_kv_heads")
|
|
45
|
+
self.h, self.kv, self.dh = n_heads, kv, d // n_heads
|
|
46
|
+
self.q, self.k, self.v = nn.Linear(d, d), nn.Linear(d, kv * self.dh), nn.Linear(d, kv * self.dh)
|
|
47
|
+
self.o = nn.Linear(d, d)
|
|
48
|
+
|
|
49
|
+
def forward(self, x, context=None, mask=None):
|
|
50
|
+
ctx = x if context is None else context
|
|
51
|
+
B, Tq, _ = x.shape
|
|
52
|
+
Tk = ctx.shape[1]
|
|
53
|
+
q = self.q(x).view(B, Tq, self.h, self.dh).transpose(1, 2)
|
|
54
|
+
k = self.k(ctx).view(B, Tk, self.kv, self.dh).transpose(1, 2)
|
|
55
|
+
v = self.v(ctx).view(B, Tk, self.kv, self.dh).transpose(1, 2)
|
|
56
|
+
if self.kv != self.h:
|
|
57
|
+
k, v = k.repeat_interleave(self.h // self.kv, 1), v.repeat_interleave(self.h // self.kv, 1)
|
|
58
|
+
y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask)
|
|
59
|
+
return self.o(y.transpose(1, 2).reshape(B, Tq, -1))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LinearAttention(nn.Module):
|
|
63
|
+
"""Linear attention (Katharopoulos et al.): softmax replaced by a positive feature map,
|
|
64
|
+
so cost is linear in sequence length. causal=True uses running sums."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, d, n_heads=4, causal=False):
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.h, self.dh, self.causal = n_heads, d // n_heads, causal
|
|
69
|
+
self.q, self.k, self.v, self.o = (nn.Linear(d, d) for _ in range(4))
|
|
70
|
+
|
|
71
|
+
def forward(self, x, context=None, mask=None):
|
|
72
|
+
B, T, _ = x.shape
|
|
73
|
+
split = lambda t: t.view(B, T, self.h, self.dh).transpose(1, 2)
|
|
74
|
+
q, k, v = (split(f(x)) for f in (self.q, self.k, self.v))
|
|
75
|
+
q, k = F.elu(q) + 1, F.elu(k) + 1
|
|
76
|
+
if self.causal:
|
|
77
|
+
S = torch.cumsum(torch.einsum("bhtd,bhte->bhtde", k, v), dim=2)
|
|
78
|
+
z = torch.cumsum(k, dim=2)
|
|
79
|
+
y = torch.einsum("bhtd,bhtde->bhte", q, S) / (torch.einsum("bhtd,bhtd->bht", q, z)[..., None] + 1e-6)
|
|
80
|
+
else:
|
|
81
|
+
y = torch.einsum("bhtd,bhde->bhte", q, torch.einsum("bhtd,bhte->bhde", k, v))
|
|
82
|
+
y = y / (torch.einsum("bhtd,bhd->bht", q, k.sum(2))[..., None] + 1e-6)
|
|
83
|
+
return self.o(y.transpose(1, 2).reshape(B, T, -1))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TransformerLayer(nn.Module):
|
|
87
|
+
"""Pre-norm layer: self-attention (full / local / sparse / linear), optional
|
|
88
|
+
cross-attention to `context`, feed-forward."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, d, n_heads=4, n_kv_heads=None, ff_mult=2, attention="full", window=None,
|
|
91
|
+
stride=None, causal=False, cross=False, dropout=0.0):
|
|
92
|
+
super().__init__()
|
|
93
|
+
if attention == "local" and window is None:
|
|
94
|
+
raise ValueError("attention='local' needs window=")
|
|
95
|
+
if attention == "sparse" and (window is None or stride is None):
|
|
96
|
+
raise ValueError("attention='sparse' needs window= and stride=")
|
|
97
|
+
self.attention, self.causal = attention, causal
|
|
98
|
+
self.window = window if attention in ("local", "sparse") else None
|
|
99
|
+
self.stride = stride if attention == "sparse" else None
|
|
100
|
+
self.attn = (LinearAttention(d, n_heads, causal) if attention == "linear"
|
|
101
|
+
else MultiHeadAttention(d, n_heads, n_kv_heads))
|
|
102
|
+
self.cross = MultiHeadAttention(d, n_heads, n_kv_heads) if cross else None
|
|
103
|
+
self.ln1, self.ln2, self.lnc = nn.LayerNorm(d), nn.LayerNorm(d), nn.LayerNorm(d)
|
|
104
|
+
self.ff = nn.Sequential(nn.Linear(d, ff_mult * d), nn.GELU(), nn.Linear(ff_mult * d, d))
|
|
105
|
+
self.drop = nn.Dropout(dropout)
|
|
106
|
+
|
|
107
|
+
def forward(self, x, context=None, mask=None):
|
|
108
|
+
if mask is None and self.attention != "linear" and (self.causal or self.window or self.stride):
|
|
109
|
+
mask = make_mask(x.shape[1], self.causal, self.window, self.stride, x.device)
|
|
110
|
+
x = x + self.drop(self.attn(self.ln1(x), mask=mask))
|
|
111
|
+
if self.cross is not None and context is not None:
|
|
112
|
+
x = x + self.drop(self.cross(self.lnc(x), context=context))
|
|
113
|
+
return x + self.drop(self.ff(self.ln2(x)))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ----------------------------------------------------------------------- cores ---
|
|
117
|
+
class TransformerCore(Core):
|
|
118
|
+
def __init__(self, d, n_layers=2, n_heads=4, n_kv_heads=None, attention="full", window=None,
|
|
119
|
+
stride=None, causal=False, dropout=0.0):
|
|
120
|
+
super().__init__()
|
|
121
|
+
self.layers = nn.ModuleList([TransformerLayer(d, n_heads, n_kv_heads, 2, attention, window,
|
|
122
|
+
stride, causal, False, dropout) for _ in range(n_layers)])
|
|
123
|
+
self.ln, self.out_dim = nn.LayerNorm(d), d
|
|
124
|
+
|
|
125
|
+
def forward(self, x):
|
|
126
|
+
x = x + sinusoid(x.shape[1], x.shape[2], x.device)
|
|
127
|
+
for layer in self.layers:
|
|
128
|
+
x = layer(x)
|
|
129
|
+
return self.ln(x)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class UniversalCore(Core):
|
|
133
|
+
"""Universal Transformer: one layer applied repeatedly (shared weights, depth recurrence)
|
|
134
|
+
with a per-step embedding. Fixed number of steps (no adaptive halting)."""
|
|
135
|
+
|
|
136
|
+
def __init__(self, d, n_steps=4, n_heads=4, causal=False):
|
|
137
|
+
super().__init__()
|
|
138
|
+
self.layer = TransformerLayer(d, n_heads, causal=causal)
|
|
139
|
+
self.step_emb = nn.Parameter(torch.randn(n_steps, d) * 0.1)
|
|
140
|
+
self.ln, self.out_dim = nn.LayerNorm(d), d
|
|
141
|
+
|
|
142
|
+
def forward(self, x):
|
|
143
|
+
x = x + sinusoid(x.shape[1], x.shape[2], x.device)
|
|
144
|
+
for e in self.step_emb:
|
|
145
|
+
x = self.layer(x + e)
|
|
146
|
+
return self.ln(x)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class MemoryCore(Core):
|
|
150
|
+
"""Memory-augmented Transformer: learned memory slots are prepended and take part in
|
|
151
|
+
attention in every layer, giving the model a persistent scratchpad."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, d, n_mem=8, n_layers=2, n_heads=4, causal=False):
|
|
154
|
+
super().__init__()
|
|
155
|
+
self.mem = nn.Parameter(torch.randn(n_mem, d) * 0.1)
|
|
156
|
+
self.layers = nn.ModuleList([TransformerLayer(d, n_heads) for _ in range(n_layers)])
|
|
157
|
+
self.ln, self.out_dim, self.n_mem, self.causal = nn.LayerNorm(d), d, n_mem, causal
|
|
158
|
+
|
|
159
|
+
def forward(self, x):
|
|
160
|
+
B, T, d = x.shape
|
|
161
|
+
n = self.n_mem
|
|
162
|
+
x = torch.cat([self.mem.expand(B, -1, -1), x + sinusoid(T, d, x.device)], 1)
|
|
163
|
+
mask = None
|
|
164
|
+
if self.causal:
|
|
165
|
+
mask = torch.zeros(n + T, n + T, dtype=torch.bool, device=x.device)
|
|
166
|
+
mask[:, :n] = True
|
|
167
|
+
mask[n:, n:] = make_mask(T, True, device=x.device)
|
|
168
|
+
for layer in self.layers:
|
|
169
|
+
x = layer(x, mask=mask)
|
|
170
|
+
return self.ln(x[:, n:])
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class PerceiverCore(Core):
|
|
174
|
+
"""Perceiver: a small latent array cross-attends to the (possibly long) input, then
|
|
175
|
+
processes it with self-attention. Cost does not grow quadratically with input length."""
|
|
176
|
+
no_all = True
|
|
177
|
+
|
|
178
|
+
def __init__(self, d, n_latents=8, n_layers=2, n_heads=4):
|
|
179
|
+
super().__init__()
|
|
180
|
+
self.latents = nn.Parameter(torch.randn(n_latents, d) * 0.1)
|
|
181
|
+
self.cross, self.ln_l, self.ln_i = MultiHeadAttention(d, n_heads), nn.LayerNorm(d), nn.LayerNorm(d)
|
|
182
|
+
self.ff, self.ln_f = nn.Sequential(nn.Linear(d, 2 * d), nn.GELU(), nn.Linear(2 * d, d)), nn.LayerNorm(d)
|
|
183
|
+
self.layers = nn.ModuleList([TransformerLayer(d, n_heads) for _ in range(n_layers)])
|
|
184
|
+
self.out_dim = d
|
|
185
|
+
|
|
186
|
+
def forward(self, x):
|
|
187
|
+
x = self.ln_i(x + sinusoid(x.shape[1], x.shape[2], x.device))
|
|
188
|
+
lat = self.latents.expand(x.shape[0], -1, -1)
|
|
189
|
+
lat = lat + self.cross(self.ln_l(lat), context=x)
|
|
190
|
+
lat = lat + self.ff(self.ln_f(lat))
|
|
191
|
+
for layer in self.layers:
|
|
192
|
+
lat = layer(lat)
|
|
193
|
+
return lat
|
|
194
|
+
|
|
195
|
+
def pool(self, y, mode):
|
|
196
|
+
return y.mean(1)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class HybridCore(Core):
|
|
200
|
+
"""State-space / attention hybrid: Mamba-style blocks alternate with attention layers."""
|
|
201
|
+
|
|
202
|
+
def __init__(self, d, n_layers=2, n_heads=4, state_dim=8, causal=False):
|
|
203
|
+
super().__init__()
|
|
204
|
+
self.ssm = nn.ModuleList([MambaBlock(d, state_dim) for _ in range(n_layers)])
|
|
205
|
+
self.norms = nn.ModuleList([nn.LayerNorm(d) for _ in range(n_layers)])
|
|
206
|
+
self.attn = nn.ModuleList([TransformerLayer(d, n_heads, causal=causal) for _ in range(n_layers)])
|
|
207
|
+
self.out_dim = d
|
|
208
|
+
|
|
209
|
+
def forward(self, x):
|
|
210
|
+
for s, n, a in zip(self.ssm, self.norms, self.attn):
|
|
211
|
+
x = a(x + s(n(x)))
|
|
212
|
+
return x
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class RecurrentAttentionCore(Core):
|
|
216
|
+
"""Recurrent attention: a GRU controller repeatedly queries the whole input
|
|
217
|
+
('glimpses'), reading a context vector each step."""
|
|
218
|
+
no_all = True
|
|
219
|
+
|
|
220
|
+
def __init__(self, d, n_glimpses=4, n_heads=4):
|
|
221
|
+
super().__init__()
|
|
222
|
+
self.attn, self.cell = MultiHeadAttention(d, n_heads), nn.GRUCell(d, d)
|
|
223
|
+
self.n_glimpses, self.out_dim, self.d = n_glimpses, d, d
|
|
224
|
+
|
|
225
|
+
def forward(self, x):
|
|
226
|
+
x = x + sinusoid(x.shape[1], x.shape[2], x.device)
|
|
227
|
+
h, hs = torch.zeros(x.shape[0], self.d, device=x.device), []
|
|
228
|
+
for _ in range(self.n_glimpses):
|
|
229
|
+
h = self.cell(self.attn(h.unsqueeze(1), context=x).squeeze(1), h)
|
|
230
|
+
hs.append(h)
|
|
231
|
+
return torch.stack(hs, 1)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# ---------------------------------------------------------------------- models ---
|
|
235
|
+
class _AttnModel(_CoreModel):
|
|
236
|
+
attention, n_kv_heads_default, causal_default = "full", None, False
|
|
237
|
+
|
|
238
|
+
def __init__(self, hidden=32, n_layers=2, n_heads=4, n_kv_heads=None, window=None, stride=None,
|
|
239
|
+
causal=None, dropout=0.0, **kw):
|
|
240
|
+
kw.setdefault("readout", "mean")
|
|
241
|
+
super().__init__(hidden=hidden, **kw)
|
|
242
|
+
self.n_layers, self.n_heads, self.dropout = n_layers, n_heads, dropout
|
|
243
|
+
self.n_kv_heads = n_kv_heads or self.n_kv_heads_default
|
|
244
|
+
self.window, self.stride = window, stride
|
|
245
|
+
self.causal = self.causal_default if causal is None else causal
|
|
246
|
+
|
|
247
|
+
def make_core(self):
|
|
248
|
+
return TransformerCore(self.hidden, self.n_layers, self.n_heads, self.n_kv_heads, self.attention,
|
|
249
|
+
self.window, self.stride, self.causal, self.dropout)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@register("transformer_encoder", family="sequence")
|
|
253
|
+
class TransformerEncoder(_AttnModel):
|
|
254
|
+
"""Transformer encoder: bidirectional self-attention with a mean-pooled readout over the sequence."""
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@register("transformer_decoder", family="sequence")
|
|
259
|
+
class TransformerDecoder(_AttnModel):
|
|
260
|
+
"""Causal Transformer; use fit(tokens) with vocab_size for language modelling."""
|
|
261
|
+
causal_default = True
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@register("linear_attention", family="sequence")
|
|
265
|
+
class LinearAttentionTransformer(_AttnModel):
|
|
266
|
+
"""Transformer with linear attention (positive feature map instead of softmax), linear in sequence length."""
|
|
267
|
+
attention = "linear"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@register("local_attention", family="sequence")
|
|
271
|
+
class LocalAttentionTransformer(_AttnModel):
|
|
272
|
+
"""Transformer whose attention is restricted to a local window (`window`)."""
|
|
273
|
+
attention = "local"
|
|
274
|
+
|
|
275
|
+
def __init__(self, window=3, **kw):
|
|
276
|
+
super().__init__(window=window, **kw)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@register("sparse_attention", family="sequence")
|
|
280
|
+
class SparseAttentionTransformer(_AttnModel):
|
|
281
|
+
"""Transformer with sparse attention: a local window plus every `stride`-th position."""
|
|
282
|
+
attention = "sparse"
|
|
283
|
+
|
|
284
|
+
def __init__(self, window=2, stride=4, **kw):
|
|
285
|
+
super().__init__(window=window, stride=stride, **kw)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@register("multi_query", family="sequence")
|
|
289
|
+
class MultiQueryTransformer(_AttnModel):
|
|
290
|
+
"""Transformer with multi-query attention: all query heads share one key/value head."""
|
|
291
|
+
n_kv_heads_default = 1
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@register("grouped_query", family="sequence")
|
|
295
|
+
class GroupedQueryTransformer(_AttnModel):
|
|
296
|
+
"""Transformer with grouped-query attention: query heads share `n_kv_heads` key/value heads (2 by default)."""
|
|
297
|
+
n_kv_heads_default = 2
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class _OwnCore(_CoreModel):
|
|
301
|
+
def __init__(self, hidden=32, n_layers=2, n_heads=4, causal=False, **kw):
|
|
302
|
+
kw.setdefault("readout", "mean")
|
|
303
|
+
super().__init__(hidden=hidden, **kw)
|
|
304
|
+
self.n_layers, self.n_heads, self.causal = n_layers, n_heads, causal
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@register("universal_transformer", family="sequence")
|
|
308
|
+
class UniversalTransformer(_OwnCore):
|
|
309
|
+
"""Universal Transformer: one shared layer applied repeatedly (`n_steps`)."""
|
|
310
|
+
def __init__(self, n_steps=4, **kw):
|
|
311
|
+
super().__init__(**kw)
|
|
312
|
+
self.n_steps = n_steps
|
|
313
|
+
|
|
314
|
+
def make_core(self):
|
|
315
|
+
return UniversalCore(self.hidden, self.n_steps, self.n_heads, self.causal)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@register("memory_transformer", family="sequence")
|
|
319
|
+
class MemoryAugmentedTransformer(_OwnCore):
|
|
320
|
+
"""Memory-augmented Transformer: learned memory slots (`n_memory`) take part in attention."""
|
|
321
|
+
def __init__(self, n_memory=8, **kw):
|
|
322
|
+
super().__init__(**kw)
|
|
323
|
+
self.n_memory = n_memory
|
|
324
|
+
|
|
325
|
+
def make_core(self):
|
|
326
|
+
return MemoryCore(self.hidden, self.n_memory, self.n_layers, self.n_heads, self.causal)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@register("perceiver", family="sequence")
|
|
330
|
+
class Perceiver(_OwnCore):
|
|
331
|
+
"""Perceiver: a small latent array (`n_latents`) cross-attends to the input."""
|
|
332
|
+
def __init__(self, n_latents=8, **kw):
|
|
333
|
+
super().__init__(**kw)
|
|
334
|
+
self.n_latents = n_latents
|
|
335
|
+
|
|
336
|
+
def make_core(self):
|
|
337
|
+
return PerceiverCore(self.hidden, self.n_latents, self.n_layers, self.n_heads)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@register("ssm_attention_hybrid", family="sequence")
|
|
341
|
+
class SSMAttentionHybrid(_OwnCore):
|
|
342
|
+
"""Hybrid of Mamba-style state-space blocks and attention layers."""
|
|
343
|
+
def make_core(self):
|
|
344
|
+
return HybridCore(self.hidden, self.n_layers, self.n_heads, causal=self.causal)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@register("recurrent_attention", family="sequence")
|
|
348
|
+
class RecurrentAttention(_OwnCore):
|
|
349
|
+
"""Recurrent attention: a GRU controller repeatedly queries the whole input (`n_glimpses`)."""
|
|
350
|
+
def __init__(self, n_glimpses=4, **kw):
|
|
351
|
+
super().__init__(**kw)
|
|
352
|
+
self.n_glimpses = n_glimpses
|
|
353
|
+
|
|
354
|
+
def make_core(self):
|
|
355
|
+
return RecurrentAttentionCore(self.hidden, self.n_glimpses, self.n_heads)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# ------------------------------------------------------------ retrieval-augmented ---
|
|
359
|
+
class RetrievalNet(nn.Module):
|
|
360
|
+
"""The input sequence (mean-pooled) is a query into an external key/value knowledge
|
|
361
|
+
base; the top-k entries are retrieved by cosine similarity and read by cross-attention."""
|
|
362
|
+
|
|
363
|
+
def __init__(self, d_in, hidden, d_out, keys, values, k, n_heads):
|
|
364
|
+
super().__init__()
|
|
365
|
+
self.register_buffer("keys", F.normalize(torch.as_tensor(keys, dtype=torch.float32), dim=1))
|
|
366
|
+
self.register_buffer("values", torch.as_tensor(values, dtype=torch.float32))
|
|
367
|
+
self.q_embed = nn.Linear(d_in, hidden)
|
|
368
|
+
self.v_embed = nn.Linear(self.values.shape[1], hidden)
|
|
369
|
+
self.read = MultiHeadAttention(hidden, n_heads)
|
|
370
|
+
self.head = nn.Sequential(nn.Linear(2 * hidden, hidden), nn.GELU(), nn.Linear(hidden, d_out))
|
|
371
|
+
self.k = min(k, len(self.keys))
|
|
372
|
+
|
|
373
|
+
def forward(self, x):
|
|
374
|
+
q = x.mean(1)
|
|
375
|
+
idx = (F.normalize(q, dim=1) @ self.keys.T).topk(self.k, dim=1).indices # (B, k)
|
|
376
|
+
retrieved = self.v_embed(self.values[idx]) # (B, k, hidden)
|
|
377
|
+
qe = self.q_embed(q).unsqueeze(1)
|
|
378
|
+
return self.head(torch.cat([qe, self.read(qe, context=retrieved)], -1).squeeze(1))
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@register("retrieval_augmented", family="sequence")
|
|
382
|
+
class RetrievalAugmented(SequenceModel):
|
|
383
|
+
"""Retrieval-augmented model. `knowledge=(keys, values)`: keys (M, d_in) live in the same
|
|
384
|
+
space as the (unnormalised) input features, values (M, d_v) are what the model may read."""
|
|
385
|
+
def __init__(self, knowledge, hidden=32, k=3, n_heads=4, **kw):
|
|
386
|
+
kw.setdefault("normalize", False)
|
|
387
|
+
super().__init__(hidden=hidden, **kw)
|
|
388
|
+
self.knowledge, self.k, self.n_heads = knowledge, k, n_heads
|
|
389
|
+
|
|
390
|
+
def build(self, d_in, d_out):
|
|
391
|
+
keys, values = self.knowledge
|
|
392
|
+
return RetrievalNet(d_in, self.hidden, d_out, keys, values, self.k, self.n_heads)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# --------------------------------------------------------- encoder-decoder model ---
|
|
396
|
+
class Seq2SeqNet(nn.Module):
|
|
397
|
+
def __init__(self, vocab, d=32, n_layers=2, n_heads=4):
|
|
398
|
+
super().__init__()
|
|
399
|
+
self.src_emb, self.tgt_emb = nn.Embedding(vocab, d), nn.Embedding(vocab + 1, d)
|
|
400
|
+
self.enc = TransformerCore(d, n_layers, n_heads)
|
|
401
|
+
self.dec = nn.ModuleList([TransformerLayer(d, n_heads, causal=True, cross=True) for _ in range(n_layers)])
|
|
402
|
+
self.ln, self.head, self.bos = nn.LayerNorm(d), nn.Linear(d, vocab), vocab
|
|
403
|
+
|
|
404
|
+
def forward(self, src, tgt_in):
|
|
405
|
+
mem = self.enc(self.src_emb(src))
|
|
406
|
+
x = self.tgt_emb(tgt_in)
|
|
407
|
+
x = x + sinusoid(x.shape[1], x.shape[2], x.device)
|
|
408
|
+
for layer in self.dec:
|
|
409
|
+
x = layer(x, context=mem)
|
|
410
|
+
return self.head(self.ln(x))
|
|
411
|
+
|
|
412
|
+
@torch.no_grad()
|
|
413
|
+
def decode(self, src, length):
|
|
414
|
+
tgt = torch.full((len(src), 1), self.bos, device=src.device)
|
|
415
|
+
for _ in range(length):
|
|
416
|
+
tgt = torch.cat([tgt, self(src, tgt)[:, -1].argmax(-1, keepdim=True)], 1)
|
|
417
|
+
return tgt[:, 1:]
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@register("encoder_decoder", family="sequence")
|
|
421
|
+
class EncoderDecoderTransformer(SequenceModel):
|
|
422
|
+
"""Sequence-to-sequence Transformer over integer tokens: fit(src, tgt), predict(src)."""
|
|
423
|
+
def __init__(self, vocab_size, hidden=32, n_layers=2, n_heads=4, **kw):
|
|
424
|
+
super().__init__(hidden=hidden, vocab_size=vocab_size, **kw)
|
|
425
|
+
self.n_layers, self.n_heads, self.tgt_len = n_layers, n_heads, None
|
|
426
|
+
|
|
427
|
+
def build(self, d_in, d_out):
|
|
428
|
+
return Seq2SeqNet(self.vocab_size, self.hidden, self.n_layers, self.n_heads)
|
|
429
|
+
|
|
430
|
+
def fit(self, X, y, epochs=None, val_data=None):
|
|
431
|
+
y = np.asarray(y)
|
|
432
|
+
self.tgt_len, self.task = y.shape[1], "classification"
|
|
433
|
+
self._declared_classes = np.arange(self.vocab_size)
|
|
434
|
+
return super().fit(X, y, epochs=epochs)
|
|
435
|
+
|
|
436
|
+
def _train_batch(self, m, opt, xb, yb):
|
|
437
|
+
tgt_in = torch.cat([torch.full((len(yb), 1), m.bos, device=yb.device), yb[:, :-1]], 1)
|
|
438
|
+
opt.zero_grad()
|
|
439
|
+
loss = self._loss(m(xb, tgt_in), yb)
|
|
440
|
+
loss.backward()
|
|
441
|
+
opt.step()
|
|
442
|
+
return loss.item()
|
|
443
|
+
|
|
444
|
+
def predict(self, X):
|
|
445
|
+
self._check_fitted()
|
|
446
|
+
self._module.eval()
|
|
447
|
+
return self._module.decode(self._norm(self._as_x(X)), self.tgt_len).cpu().numpy()
|
|
448
|
+
|
|
449
|
+
def evaluate(self, X, y):
|
|
450
|
+
pred, y = self.predict(X), np.asarray(y)
|
|
451
|
+
return {"token_accuracy": float((pred == y).mean()), "sequence_accuracy": float((pred == y).all(1).mean())}
|