Skip to content

Commit 99a01f1

Browse files
Merge pull request #38 from LukasHedegaard/develop
Call-mode specific functions in `co.Lambda`
2 parents 75321a6 + 2760f93 commit 99a01f1

5 files changed

Lines changed: 92 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ From v1.0.0 and on, the project will adherence strictly to Semantic Versioning.
99

1010
## [Unreleased]
1111

12+
## [0.15.3]
13+
### Added
14+
- Call-mode specific functions in `co.Lambda`
15+
1216

1317
## [0.15.2]
1418
### Added

continual/closure.py

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,67 @@ class Lambda(CoModule, nn.Module):
1515
1616
Args:
1717
fn (Callable[[Tensor], Tensor]): Function to be called during forward.
18+
forward_only_fn (Callable[[Tensor], Tensor]): Function to be called only during `forward`. `fn` is used for the other call modes.
19+
forward_step_only_fn (Callable[[Tensor], Tensor]): Function to be called only during `forward_step`. `fn` is used for the other call modes.
20+
forward_steps_only_fn (Callable[[Tensor], Tensor]): Function to be called only during `forward_steps`. `fn` is used for the other call modes.
21+
forward_only_fn (Callable[[Tensor], Tensor]): Function to be called only during forward. `fn` is used for the other call modes.
1822
takes_time (bool, optional): If True, `fn` recieves all steps, if False, it received one step and no time dimension. Defaults to False.
1923
"""
2024

21-
def __init__(self, fn: Callable[[Tensor], Tensor], takes_time=False):
25+
def __init__(
26+
self,
27+
fn: Callable[[Tensor], Tensor] = None,
28+
forward_only_fn=None,
29+
forward_step_only_fn=None,
30+
forward_steps_only_fn=None,
31+
takes_time=False,
32+
):
2233
nn.Module.__init__(self)
23-
assert callable(fn), "The pased function should be callable."
34+
assert callable(fn) or all(
35+
callable(forward_only_fn),
36+
callable(forward_step_only_fn),
37+
callable(forward_steps_only_fn),
38+
), "Either fn or all of forward_only_fn, forward_step_only_fn, and forward_steps_only_fn should be callable."
39+
2440
self.fn = fn
25-
if not hasattr(self.fn, "__name__") and hasattr(self.fn, "__repr__"):
26-
self.fn.__name__ = self.fn.__repr__()
41+
self.forward_only_fn = forward_only_fn
42+
self.forward_step_only_fn = forward_step_only_fn
43+
self.forward_steps_only_fn = forward_steps_only_fn
2744
self.takes_time = takes_time
2845

46+
@staticmethod
47+
def build_from(
48+
fn: Callable[[Tensor], Tensor],
49+
forward_only_fn=None,
50+
forward_step_only_fn=None,
51+
forward_steps_only_fn=None,
52+
takes_time=False,
53+
) -> "Lambda":
54+
return Lambda(
55+
fn, forward_only_fn, forward_step_only_fn, forward_steps_only_fn, takes_time
56+
)
57+
2958
def __repr__(self) -> str:
30-
s = f"Lambda({function_repr(self.fn)}"
59+
s = "Lambda("
60+
if callable(self.fn):
61+
s += f"{function_repr(self.fn)}"
62+
if callable(self.forward_only_fn):
63+
if callable(self.fn):
64+
s += ", "
65+
s += f"{function_repr(self.forward_only_fn)}"
66+
if callable(self.forward_step_only_fn):
67+
s += f", {function_repr(self.forward_step_only_fn)}"
68+
if callable(self.forward_steps_only_fn):
69+
s += f", {function_repr(self.forward_steps_only_fn)}"
3170
if self.takes_time:
3271
s += ", takes_time=True"
3372
s += ")"
3473
return s
3574

3675
def forward(self, input: Tensor) -> Tensor:
76+
if self.forward_only_fn is not None:
77+
return self.forward_only_fn(input)
78+
3779
if self.takes_time:
3880
return self.fn(input)
3981

@@ -42,20 +84,27 @@ def forward(self, input: Tensor) -> Tensor:
4284
)
4385

4486
def forward_steps(self, input: Tensor, pad_end=False, update_state=True) -> Tensor:
45-
return self.forward(input)
87+
if self.forward_steps_only_fn is not None:
88+
return self.forward_steps_only_fn(input)
89+
90+
if self.takes_time:
91+
return self.fn(input)
92+
93+
return torch.stack(
94+
[self.fn(input[:, :, t]) for t in range(input.shape[2])], dim=2
95+
)
4696

4797
def forward_step(self, input: Tensor, update_state=True) -> Tensor:
98+
if self.forward_step_only_fn is not None:
99+
return self.forward_step_only_fn(input)
100+
48101
if self.takes_time:
49102
input = input.unsqueeze(dim=2)
50103
output = self.fn(input)
51104
if self.takes_time:
52105
output = output.squeeze(dim=2)
53106
return output
54107

55-
@staticmethod
56-
def build_from(fn: Callable[[Tensor], Tensor], takes_time=False) -> "Lambda":
57-
return Lambda(fn, takes_time)
58-
59108

60109
def _multiply(x: Tensor, factor: Union[float, int, Tensor]):
61110
return x * factor

continual/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ def num_from(tuple_or_num: Union[Number, Tuple[Number, ...]], dim=0) -> Number:
167167

168168

169169
def function_repr(fn):
170+
if fn is None:
171+
return ""
172+
173+
if isinstance(fn, nn.Module):
174+
fn.__name__ = fn.__repr__()
175+
170176
if isinstance(fn, partial):
171177
fn = fn.func
172178
s = fn.__name__

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def from_file(file_name: str = "requirements.txt", comment_char: str = "#"):
2525

2626
setup(
2727
name="continual-inference",
28-
version="0.15.2",
28+
version="0.15.3",
2929
description="Building blocks for Continual Inference Networks in PyTorch",
3030
long_description=long_description(),
3131
long_description_content_type="text/markdown",

tests/continual/test_closure.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,12 @@ def local_always42(x):
6363

6464
# Anonymous
6565
mod = Lambda.build_from(lambda x: torch.ones_like(x) * 42)
66-
assert torch.equal(target, mod(x))
66+
assert torch.equal(target, mod.forward_steps(x))
6767

6868
# Functor
6969
functor = torch.nn.Sigmoid()
7070
assert torch.equal(functor(x), Lambda(functor)(x))
7171

72-
# takes_time = False
7372
mod = Lambda.build_from(lambda x: torch.ones_like(x) * 42, takes_time=True)
7473
assert torch.equal(target, mod(x))
7574

@@ -83,6 +82,27 @@ def local_always42(x):
8382
assert modules[0][1].__repr__() == "Lambda(lambda x: x.view(x.shape[0], -1))"
8483

8584

85+
def test_lambda_call_specific_fns():
86+
x = torch.ones((1, 1, 2, 2))
87+
88+
mod = Lambda(
89+
fn=lambda x: x + 1,
90+
forward_only_fn=lambda x: x + 2,
91+
forward_step_only_fn=lambda x: x + 3,
92+
forward_steps_only_fn=lambda x: x + 4,
93+
)
94+
95+
assert torch.equal(x + 2, mod.forward(x))
96+
assert torch.equal(x.squeeze(2) + 3, mod.forward_step(x))
97+
assert torch.equal(x + 4, mod.forward_steps(x))
98+
99+
# __repr__
100+
assert (
101+
mod.__repr__()
102+
== "Lambda(lambda x: x + 1, lambda x: x + 2, lambda x: x + 3, lambda x: x + 4)"
103+
)
104+
105+
86106
def test_unity():
87107
x = torch.ones((1, 1, 2, 2))
88108
assert torch.equal(x, Unity()(x))

0 commit comments

Comments
 (0)