pytensor-ml 0.0.1__tar.gz → 0.0.2__tar.gz
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.
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/PKG-INFO +1 -1
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pyproject.toml +4 -1
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml/__init__.py +3 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml/_version.py +3 -3
- pytensor_ml-0.0.2/pytensor_ml/layers.py +103 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml/model.py +11 -5
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml/optimizers.py +45 -10
- pytensor_ml-0.0.2/pytensor_ml/pytensorf.py +95 -0
- pytensor_ml-0.0.2/pytensor_ml/rewriting/__init__.py +0 -0
- pytensor_ml-0.0.2/pytensor_ml/rewriting/layers.py +37 -0
- pytensor_ml-0.0.2/pytensor_ml/util.py +49 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml.egg-info/PKG-INFO +1 -1
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml.egg-info/SOURCES.txt +5 -1
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml.egg-info/requires.txt +1 -0
- pytensor_ml-0.0.1/pytensor_ml/layers.py +0 -53
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/LICENSE +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/README.md +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml/activations.py +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml/loss.py +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml.egg-info/dependency_links.txt +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/pytensor_ml.egg-info/top_level.txt +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/setup.cfg +0 -0
- {pytensor_ml-0.0.1 → pytensor_ml-0.0.2}/setup.py +0 -0
|
@@ -33,7 +33,8 @@ keywords = [
|
|
|
33
33
|
|
|
34
34
|
dependencies = [
|
|
35
35
|
"pytensor",
|
|
36
|
-
"numpy"
|
|
36
|
+
"numpy",
|
|
37
|
+
"pymc"
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
[project.optional-dependencies]
|
|
@@ -96,6 +97,8 @@ ignore = [
|
|
|
96
97
|
"D415",
|
|
97
98
|
"D417",
|
|
98
99
|
]
|
|
100
|
+
fixable = ["ALL"]
|
|
101
|
+
|
|
99
102
|
|
|
100
103
|
[tool.ruff.lint.isort]
|
|
101
104
|
lines-between-types = 1
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
4
|
from pytensor_ml._version import get_versions
|
|
5
|
+
from pytensor_ml.pytensorf import function
|
|
5
6
|
|
|
6
7
|
_log = logging.getLogger(__name__)
|
|
7
8
|
|
|
@@ -13,3 +14,5 @@ if not logging.root.handlers:
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
__version__ = get_versions()["version"]
|
|
17
|
+
|
|
18
|
+
__all__ = ["function"]
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "2025-01-26T00:24:12+0800",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.0.
|
|
14
|
+
"full-revisionid": "75eda5beff184dd32a4fe21444ec961a27c8eca3",
|
|
15
|
+
"version": "0.0.2"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytensor.tensor as pt
|
|
6
|
+
import pytensor.tensor.random as ptr
|
|
7
|
+
|
|
8
|
+
from pytensor import config
|
|
9
|
+
from pytensor.compile.builders import OpFromGraph
|
|
10
|
+
from pytensor.compile.sharedvalue import shared
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def shape_to_str(shape):
|
|
14
|
+
inner = ",".join([str(st_dim) if st_dim is not None else "?" for st_dim in shape])
|
|
15
|
+
return f"({inner})"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Layer(ABC):
|
|
19
|
+
def __call__(self, x: pt.TensorLike) -> pt.TensorLike: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LinearLayer(OpFromGraph): ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Linear(Layer):
|
|
26
|
+
__props__ = ("name", "n_in", "n_out")
|
|
27
|
+
|
|
28
|
+
def __init__(self, name: str | None, n_in: int, n_out: int):
|
|
29
|
+
self.name = name if name else "Linear"
|
|
30
|
+
self.n_in = n_in
|
|
31
|
+
self.n_out = n_out
|
|
32
|
+
|
|
33
|
+
self.W = pt.tensor(f"{self.name}_W", shape=(n_in, self.n_out))
|
|
34
|
+
self.b = pt.tensor(f"{self.name}_b", shape=(self.n_out,))
|
|
35
|
+
|
|
36
|
+
def __call__(self, X: pt.TensorLike) -> pt.TensorLike:
|
|
37
|
+
X = pt.as_tensor(X)
|
|
38
|
+
|
|
39
|
+
init_st_shape = shape_to_str(X.type.shape)
|
|
40
|
+
|
|
41
|
+
res = X @ self.W + self.b
|
|
42
|
+
|
|
43
|
+
final_st_shape = shape_to_str(res.type.shape)
|
|
44
|
+
|
|
45
|
+
ofg = LinearLayer(
|
|
46
|
+
inputs=[X, self.W, self.b],
|
|
47
|
+
outputs=[res],
|
|
48
|
+
inline=True,
|
|
49
|
+
name=f"{self.name}[{init_st_shape} -> {final_st_shape}]",
|
|
50
|
+
)
|
|
51
|
+
out = ofg(X, self.W, self.b)
|
|
52
|
+
out.name = f"{self.name}_output"
|
|
53
|
+
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DropoutLayer(OpFromGraph): ...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Dropout(Layer):
|
|
61
|
+
__props__ = ("name", "p")
|
|
62
|
+
|
|
63
|
+
def __init__(self, name: str | None, p: float = 0.5):
|
|
64
|
+
if p < 0.0 or p > 1.0:
|
|
65
|
+
raise ValueError(f"Dropout probability has to be between 0 and 1, but got {p}")
|
|
66
|
+
self.name = name if name else "Dropout"
|
|
67
|
+
self.p = p
|
|
68
|
+
self.rng = shared(np.random.default_rng())
|
|
69
|
+
|
|
70
|
+
def __call__(self, X: pt.TensorLike) -> pt.TensorLike:
|
|
71
|
+
X = pt.as_tensor(X)
|
|
72
|
+
new_rng, mask = ptr.bernoulli(p=1 - self.p, size=X.shape, rng=self.rng).owner.outputs
|
|
73
|
+
mask = mask.astype(config.floatX)
|
|
74
|
+
|
|
75
|
+
X_masked = DropoutLayer(
|
|
76
|
+
inputs=[X, mask], outputs=[pt.where(mask, ift=X / (1 - self.p), iff=0)], inline=True
|
|
77
|
+
)(X, mask)
|
|
78
|
+
X_masked.name = f"{self.name}[p = {self.p}]"
|
|
79
|
+
|
|
80
|
+
return X_masked
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def Input(name: str, shape: tuple[int]) -> pt.TensorLike:
|
|
84
|
+
if not all(isinstance(dim, int) for dim in shape):
|
|
85
|
+
raise ValueError("All dimensions must be integers")
|
|
86
|
+
|
|
87
|
+
return pt.tensor(name=name, shape=shape)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def Sequential(*layers: Callable) -> Callable:
|
|
91
|
+
def forward(x: pt.TensorLike) -> pt.TensorLike:
|
|
92
|
+
for layer in layers:
|
|
93
|
+
x = layer(x)
|
|
94
|
+
return x
|
|
95
|
+
|
|
96
|
+
return forward
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
Squeeze = pt.squeeze
|
|
100
|
+
Concatenate = pt.concatenate
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
__all__ = ["Concatenate", "Input", "Linear", "Sequential", "Squeeze"]
|
|
@@ -5,13 +5,14 @@ from typing import Literal, cast
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
7
|
from pytensor import config
|
|
8
|
-
from pytensor.compile.function import function
|
|
9
8
|
from pytensor.compile.sharedvalue import SharedVariable
|
|
10
9
|
from pytensor.graph import graph_inputs
|
|
11
10
|
from pytensor.graph.basic import Constant
|
|
12
11
|
from pytensor.printing import debugprint
|
|
13
12
|
from pytensor.tensor import TensorVariable
|
|
14
13
|
|
|
14
|
+
from pytensor_ml.pytensorf import function, rewrite_for_prediction
|
|
15
|
+
|
|
15
16
|
InitializationSchemes = Literal["zeros", "xavier_uniform", "xavier_normal"]
|
|
16
17
|
|
|
17
18
|
|
|
@@ -24,14 +25,15 @@ def required_graph_inputs(tensor: TensorVariable) -> Generator[TensorVariable, N
|
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class Model:
|
|
27
|
-
def __init__(self, X: TensorVariable, y: TensorVariable):
|
|
28
|
+
def __init__(self, X: TensorVariable, y: TensorVariable, compile_kwargs: dict | None = None):
|
|
28
29
|
self.X = X
|
|
29
30
|
self.y = y
|
|
30
31
|
|
|
32
|
+
self._compile_kwargs = compile_kwargs if compile_kwargs else {}
|
|
31
33
|
self._weight_values: list[np.ndarray[float]] | None = None
|
|
32
34
|
self._predict_fn = None
|
|
33
35
|
|
|
34
|
-
def
|
|
36
|
+
def initialize_weights(
|
|
35
37
|
self,
|
|
36
38
|
scheme: InitializationSchemes = "xavier_normal",
|
|
37
39
|
random_seed: int | str | np.random.Generator | None = None,
|
|
@@ -55,10 +57,14 @@ class Model:
|
|
|
55
57
|
|
|
56
58
|
def predict(self, X_values: np.ndarray) -> np.ndarray:
|
|
57
59
|
if self._predict_fn is None:
|
|
58
|
-
f = function(
|
|
60
|
+
f = function(
|
|
61
|
+
[*self.weights, self.X],
|
|
62
|
+
rewrite_for_prediction(self.y),
|
|
63
|
+
**self._compile_kwargs,
|
|
64
|
+
)
|
|
59
65
|
self._predict_fn = partial(f, *self.weight_values)
|
|
60
66
|
|
|
61
|
-
return self._predict_fn(X_values)
|
|
67
|
+
return cast(np.ndarray, self._predict_fn(X_values))
|
|
62
68
|
|
|
63
69
|
def __str__(self):
|
|
64
70
|
return debugprint(self.y, file="str")
|
|
@@ -4,12 +4,12 @@ from collections.abc import Callable, Sequence
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
6
|
from pytensor import config
|
|
7
|
-
from pytensor.compile.function import function
|
|
8
7
|
from pytensor.compile.function.types import Function
|
|
9
8
|
from pytensor.gradient import grad
|
|
10
9
|
from pytensor.tensor import TensorLike, TensorVariable, sqrt, tensor
|
|
11
10
|
|
|
12
11
|
from pytensor_ml.model import Model
|
|
12
|
+
from pytensor_ml.pytensorf import function, rewrite_pregrad
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Optimizer(ABC):
|
|
@@ -19,6 +19,7 @@ class Optimizer(ABC):
|
|
|
19
19
|
loss_fn: Callable,
|
|
20
20
|
ndim_out: int = 1,
|
|
21
21
|
optimizer_weights: list[TensorVariable] | None = None,
|
|
22
|
+
compile_kwargs: dict | None = None,
|
|
22
23
|
):
|
|
23
24
|
self.model = model
|
|
24
25
|
self.loss_fn = loss_fn
|
|
@@ -26,7 +27,7 @@ class Optimizer(ABC):
|
|
|
26
27
|
self.optimizer_weights = optimizer_weights if optimizer_weights is not None else []
|
|
27
28
|
self._optimizer_weights_values = self._initialize_weights()
|
|
28
29
|
|
|
29
|
-
self.update_fn = self.build_update_fn()
|
|
30
|
+
self.update_fn = self.build_update_fn(compile_kwargs)
|
|
30
31
|
|
|
31
32
|
@abstractmethod
|
|
32
33
|
def update_parameters(self, params: Sequence[TensorVariable], loss: TensorVariable): ...
|
|
@@ -51,7 +52,7 @@ class Optimizer(ABC):
|
|
|
51
52
|
n_params = len(self.model.weights)
|
|
52
53
|
return all_weights[:n_params], all_weights[n_params:]
|
|
53
54
|
|
|
54
|
-
def build_update_fn(self) -> Function:
|
|
55
|
+
def build_update_fn(self, compile_kwargs) -> Function:
|
|
55
56
|
"""
|
|
56
57
|
Compile a function to update model weights
|
|
57
58
|
|
|
@@ -59,12 +60,17 @@ class Optimizer(ABC):
|
|
|
59
60
|
values. By default, the functions also returns the loss value. To return additional diagnostics, this method
|
|
60
61
|
should be overloaded.
|
|
61
62
|
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
compile_kwargs: dict
|
|
66
|
+
Keyword arguments passed to pytensor.function
|
|
67
|
+
|
|
62
68
|
Returns
|
|
63
69
|
-------
|
|
64
70
|
update_fn: Function
|
|
65
71
|
A function that updates the model weights given a batch of data, targets, and current weights.
|
|
66
72
|
"""
|
|
67
|
-
|
|
73
|
+
compile_kwargs = compile_kwargs if compile_kwargs else {}
|
|
68
74
|
x, y_hat = self.model.X, self.model.y
|
|
69
75
|
|
|
70
76
|
label_slice = (slice(None),) * self.ndim_out + (0,) * (y_hat.ndim - self.ndim_out)
|
|
@@ -74,9 +80,16 @@ class Optimizer(ABC):
|
|
|
74
80
|
optimizer_weights = self.optimizer_weights
|
|
75
81
|
|
|
76
82
|
loss = self.loss_fn(y, y_hat)
|
|
83
|
+
loss = rewrite_pregrad(loss)
|
|
77
84
|
|
|
78
85
|
all_weights = self.update_parameters(weights + optimizer_weights, loss)
|
|
79
|
-
fn = function(
|
|
86
|
+
fn = function(
|
|
87
|
+
[x, y, *weights, *optimizer_weights],
|
|
88
|
+
[*all_weights, loss],
|
|
89
|
+
**compile_kwargs,
|
|
90
|
+
)
|
|
91
|
+
fn.trust_input = True
|
|
92
|
+
|
|
80
93
|
return fn
|
|
81
94
|
|
|
82
95
|
def step(self, x_values, y_values) -> np.ndarray:
|
|
@@ -90,7 +103,6 @@ class Optimizer(ABC):
|
|
|
90
103
|
*new_weights, loss_values = self.update_fn(
|
|
91
104
|
x_values, y_values, *self.model.weight_values, *self.optimizer_weights_values
|
|
92
105
|
)
|
|
93
|
-
self.update_fn.trust_input = True
|
|
94
106
|
|
|
95
107
|
self.model.weight_values, self.optimizer_weights_values = self._split_weights(new_weights)
|
|
96
108
|
|
|
@@ -98,9 +110,17 @@ class Optimizer(ABC):
|
|
|
98
110
|
|
|
99
111
|
|
|
100
112
|
class SGD(Optimizer):
|
|
101
|
-
def __init__(
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
model,
|
|
116
|
+
loss_fn,
|
|
117
|
+
*,
|
|
118
|
+
ndim_out: int = 1,
|
|
119
|
+
learning_rate: TensorLike = 0.01,
|
|
120
|
+
compile_kwargs: dict | None = None,
|
|
121
|
+
):
|
|
102
122
|
self.learning_rate = learning_rate
|
|
103
|
-
super().__init__(model, loss_fn, ndim_out=ndim_out)
|
|
123
|
+
super().__init__(model, loss_fn, ndim_out=ndim_out, compile_kwargs=compile_kwargs)
|
|
104
124
|
|
|
105
125
|
def update_parameters(
|
|
106
126
|
self, params: list[TensorVariable], loss: TensorVariable
|
|
@@ -122,17 +142,25 @@ class ADAGrad(Optimizer):
|
|
|
122
142
|
ndim_out: int = 1,
|
|
123
143
|
learning_rate: TensorLike = 0.01,
|
|
124
144
|
epsilon: TensorLike = 1e-8,
|
|
145
|
+
compile_kwargs: dict | None = None,
|
|
125
146
|
):
|
|
126
147
|
self.learning_rate = learning_rate
|
|
127
148
|
self.epsilon = epsilon
|
|
128
149
|
g2_weights = [param.type() for param in model.weights]
|
|
129
150
|
|
|
130
|
-
super().__init__(
|
|
151
|
+
super().__init__(
|
|
152
|
+
model,
|
|
153
|
+
loss_fn,
|
|
154
|
+
ndim_out=ndim_out,
|
|
155
|
+
optimizer_weights=g2_weights,
|
|
156
|
+
compile_kwargs=compile_kwargs,
|
|
157
|
+
)
|
|
131
158
|
|
|
132
159
|
def update_parameters(
|
|
133
160
|
self, weights: list[TensorVariable], loss: TensorVariable
|
|
134
161
|
) -> list[TensorVariable]:
|
|
135
162
|
weights, optimizer_weights = self._split_weights(weights)
|
|
163
|
+
|
|
136
164
|
grads = grad(loss, weights)
|
|
137
165
|
|
|
138
166
|
new_weights = []
|
|
@@ -158,6 +186,7 @@ class Adam(Optimizer):
|
|
|
158
186
|
beta1: TensorLike = 0.9,
|
|
159
187
|
beta2: TensorLike = 0.999,
|
|
160
188
|
epsilon: TensorLike = 1e-8,
|
|
189
|
+
compile_kwargs: dict | None = None,
|
|
161
190
|
):
|
|
162
191
|
self.learning_rate = learning_rate
|
|
163
192
|
self.beta1 = beta1
|
|
@@ -169,7 +198,13 @@ class Adam(Optimizer):
|
|
|
169
198
|
t = tensor("t", shape=(1,), dtype=config.floatX)
|
|
170
199
|
|
|
171
200
|
optimizer_weights = m_weights + v_weights + [t]
|
|
172
|
-
super().__init__(
|
|
201
|
+
super().__init__(
|
|
202
|
+
model,
|
|
203
|
+
loss_fn,
|
|
204
|
+
ndim_out=ndim_out,
|
|
205
|
+
optimizer_weights=optimizer_weights,
|
|
206
|
+
compile_kwargs=compile_kwargs,
|
|
207
|
+
)
|
|
173
208
|
|
|
174
209
|
def update_parameters(
|
|
175
210
|
self, weights: list[TensorVariable], loss: TensorVariable
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
3
|
+
import pytensor
|
|
4
|
+
|
|
5
|
+
from pymc.pytensorf import SeedSequenceSeed, collect_default_updates, reseed_rngs
|
|
6
|
+
from pytensor import Mode
|
|
7
|
+
from pytensor.compile import Function, SharedVariable, get_mode
|
|
8
|
+
from pytensor.graph import FunctionGraph, RewriteDatabaseQuery, rewrite_graph
|
|
9
|
+
from pytensor.tensor.variable import Variable
|
|
10
|
+
|
|
11
|
+
from pytensor_ml.rewriting.layers import predict_db
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def atleast_list(x):
|
|
15
|
+
if not isinstance(x, list | tuple):
|
|
16
|
+
return [x]
|
|
17
|
+
return x
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def function(
|
|
21
|
+
inputs,
|
|
22
|
+
outputs,
|
|
23
|
+
random_seed: SeedSequenceSeed = None,
|
|
24
|
+
mode=None,
|
|
25
|
+
**kwargs,
|
|
26
|
+
) -> Function:
|
|
27
|
+
"""
|
|
28
|
+
Compile a Pytensor function, including specialized rewrites.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
inputs: list of TensorVariables, optional
|
|
33
|
+
Inputs of the compiled PyTensor function
|
|
34
|
+
outputs: list of TensorVariables, optional
|
|
35
|
+
Outputs of the compiled PyTensor function
|
|
36
|
+
random_seed: int, array-like of int or SeedSequence, optional
|
|
37
|
+
Seed used to override any RandomState/Generator shared variables in the graph.
|
|
38
|
+
If not specified, the value of original shared variables will still be overwritten.
|
|
39
|
+
mode: optional
|
|
40
|
+
PyTensor mode used to compile the function
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
pytensor_function: Function
|
|
45
|
+
Compiled function
|
|
46
|
+
"""
|
|
47
|
+
rng_updates = collect_default_updates(
|
|
48
|
+
inputs=[inp.variable if isinstance(inp, pytensor.In) else inp for inp in inputs],
|
|
49
|
+
outputs=[
|
|
50
|
+
out.variable if isinstance(out, pytensor.Out) else out for out in atleast_list(outputs)
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if rng_updates:
|
|
55
|
+
rngs = cast(list[SharedVariable], list(rng_updates))
|
|
56
|
+
reseed_rngs(rngs, random_seed)
|
|
57
|
+
|
|
58
|
+
mode = get_mode(mode)
|
|
59
|
+
opt_qry = mode.provided_optimizer.including("random_make_inplace")
|
|
60
|
+
mode = Mode(linker=mode.linker, optimizer=opt_qry)
|
|
61
|
+
pytensor_function = pytensor.function(
|
|
62
|
+
inputs,
|
|
63
|
+
outputs,
|
|
64
|
+
updates={**rng_updates, **kwargs.pop("updates", {})},
|
|
65
|
+
mode=mode,
|
|
66
|
+
**kwargs,
|
|
67
|
+
)
|
|
68
|
+
return pytensor_function
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def rewrite_pregrad(graph):
|
|
72
|
+
"""Apply simplifying or stabilizing rewrites to graph that are safe to use pre-grad."""
|
|
73
|
+
return rewrite_graph(graph, include=("canonicalize", "stabilize"))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def rewrite_for_prediction(graph):
|
|
77
|
+
"""Apply rewrites to specialize a graph for forward passes (e.g. removing Dropout layers)"""
|
|
78
|
+
if isinstance(graph, FunctionGraph):
|
|
79
|
+
fgraph = graph
|
|
80
|
+
else:
|
|
81
|
+
outputs = [graph] if isinstance(graph, Variable) else graph
|
|
82
|
+
fgraph = FunctionGraph(outputs=outputs, clone=False)
|
|
83
|
+
|
|
84
|
+
rewriter = predict_db.query(RewriteDatabaseQuery(include=["basic"]))
|
|
85
|
+
rewriter.rewrite(fgraph)
|
|
86
|
+
|
|
87
|
+
if isinstance(graph, FunctionGraph):
|
|
88
|
+
return fgraph
|
|
89
|
+
if isinstance(graph, Variable):
|
|
90
|
+
return fgraph.outputs[0]
|
|
91
|
+
|
|
92
|
+
return fgraph.outputs
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = ["function"]
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pytensor.graph.basic import Apply
|
|
2
|
+
from pytensor.graph.fg import FunctionGraph
|
|
3
|
+
from pytensor.graph.rewriting.basic import node_rewriter
|
|
4
|
+
from pytensor.graph.rewriting.db import EquilibriumDB
|
|
5
|
+
from pytensor.tensor.variable import Variable
|
|
6
|
+
|
|
7
|
+
from pytensor_ml.layers import DropoutLayer
|
|
8
|
+
|
|
9
|
+
predict_db = EquilibriumDB()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@node_rewriter([DropoutLayer])
|
|
13
|
+
def remove_dropout_for_prediction(fgraph: FunctionGraph, node: Apply) -> list[Variable] | None:
|
|
14
|
+
"""
|
|
15
|
+
Set dropout probability to zero for all dropout layers.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
fgraph: FunctionGraph
|
|
20
|
+
Graph being rewritten
|
|
21
|
+
node: Node
|
|
22
|
+
Node being rewritten
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
X: Variable
|
|
27
|
+
The input to the dropout layer, removing the dropout from the graph
|
|
28
|
+
"""
|
|
29
|
+
X, rng = node.inputs
|
|
30
|
+
return [X]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
predict_db.register(
|
|
34
|
+
"remove_dropout_for_prediction",
|
|
35
|
+
remove_dropout_for_prediction,
|
|
36
|
+
"basic",
|
|
37
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pytensor import config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DataLoader:
|
|
7
|
+
def __init__(self, X, y, batch_size=64, dtype=None):
|
|
8
|
+
if dtype is None:
|
|
9
|
+
dtype = config.floatX
|
|
10
|
+
self.rng = np.random.default_rng()
|
|
11
|
+
|
|
12
|
+
self.X = X.astype(dtype)
|
|
13
|
+
self.y = y.astype(dtype)
|
|
14
|
+
self.n = X.shape[0]
|
|
15
|
+
|
|
16
|
+
self.batch_size = batch_size
|
|
17
|
+
self.cursor = 0
|
|
18
|
+
self.indices = np.arange(len(X)).astype("int32")
|
|
19
|
+
self.reset()
|
|
20
|
+
|
|
21
|
+
def shuffle(self):
|
|
22
|
+
self.rng.shuffle(self.indices)
|
|
23
|
+
|
|
24
|
+
def move_cursor(self):
|
|
25
|
+
start, stop = self.cursor, self.cursor + self.batch_size
|
|
26
|
+
|
|
27
|
+
has_remainder, stop = divmod(stop, self.n)
|
|
28
|
+
|
|
29
|
+
idx = slice(start, None if has_remainder > 0 else stop)
|
|
30
|
+
indices = self.indices[idx]
|
|
31
|
+
|
|
32
|
+
if has_remainder > 0:
|
|
33
|
+
excess = self.batch_size - indices.shape[0]
|
|
34
|
+
self.shuffle()
|
|
35
|
+
self.epoch += 1
|
|
36
|
+
indices = np.r_[indices, self.indices[:excess]]
|
|
37
|
+
|
|
38
|
+
self.cursor = (self.cursor + self.batch_size) % self.n
|
|
39
|
+
|
|
40
|
+
return indices
|
|
41
|
+
|
|
42
|
+
def reset(self):
|
|
43
|
+
self.cursor = 0
|
|
44
|
+
self.epoch = 0
|
|
45
|
+
self.shuffle()
|
|
46
|
+
|
|
47
|
+
def __call__(self):
|
|
48
|
+
idx = self.move_cursor()
|
|
49
|
+
return self.X[idx], self.y[idx]
|
|
@@ -9,8 +9,12 @@ pytensor_ml/layers.py
|
|
|
9
9
|
pytensor_ml/loss.py
|
|
10
10
|
pytensor_ml/model.py
|
|
11
11
|
pytensor_ml/optimizers.py
|
|
12
|
+
pytensor_ml/pytensorf.py
|
|
13
|
+
pytensor_ml/util.py
|
|
12
14
|
pytensor_ml.egg-info/PKG-INFO
|
|
13
15
|
pytensor_ml.egg-info/SOURCES.txt
|
|
14
16
|
pytensor_ml.egg-info/dependency_links.txt
|
|
15
17
|
pytensor_ml.egg-info/requires.txt
|
|
16
|
-
pytensor_ml.egg-info/top_level.txt
|
|
18
|
+
pytensor_ml.egg-info/top_level.txt
|
|
19
|
+
pytensor_ml/rewriting/__init__.py
|
|
20
|
+
pytensor_ml/rewriting/layers.py
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
|
|
4
|
-
from pytensor.tensor import TensorLike, as_tensor, concatenate, squeeze, tensor
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Layer(ABC):
|
|
8
|
-
def __call__(self, x: TensorLike) -> TensorLike: ...
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Linear(Layer):
|
|
12
|
-
__props__ = ("name", "n_out")
|
|
13
|
-
|
|
14
|
-
def __init__(self, name: str | None, n_in: int, n_out: int):
|
|
15
|
-
self.name = name
|
|
16
|
-
self.n_in = n_in
|
|
17
|
-
self.n_out = n_out
|
|
18
|
-
|
|
19
|
-
self.W = tensor(f"{self.name}_W", shape=(n_in, self.n_out))
|
|
20
|
-
self.b = tensor(f"{self.name}_b", shape=(self.n_out,))
|
|
21
|
-
|
|
22
|
-
def __call__(self, X: TensorLike) -> TensorLike:
|
|
23
|
-
X = as_tensor(X)
|
|
24
|
-
|
|
25
|
-
init_st_shape = [st_dim if st_dim is not None else "?" for st_dim in X.type.shape]
|
|
26
|
-
X = X @ self.W + self.b
|
|
27
|
-
final_st_shape = [st_dim if st_dim is not None else "?" for st_dim in X.type.shape]
|
|
28
|
-
X.name = f"{self.__class__.__name__}[{init_st_shape} -> {final_st_shape}]"
|
|
29
|
-
|
|
30
|
-
return X
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def Input(name: str, shape: tuple[int]) -> TensorLike:
|
|
34
|
-
if not all(isinstance(dim, int) for dim in shape):
|
|
35
|
-
raise ValueError("All dimensions must be integers")
|
|
36
|
-
|
|
37
|
-
return tensor(name=name, shape=shape)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def Sequential(*layers: Callable) -> Callable:
|
|
41
|
-
def forward(x: TensorLike) -> TensorLike:
|
|
42
|
-
for layer in layers:
|
|
43
|
-
x = layer(x)
|
|
44
|
-
return x
|
|
45
|
-
|
|
46
|
-
return forward
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Squeeze = squeeze
|
|
50
|
-
Concatenate = concatenate
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
__all__ = ["Concatenate", "Input", "Linear", "Sequential", "Squeeze"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|