diff --git a/CHANGELOG.md b/CHANGELOG.md index 79035b7b..6760a158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ see the [GitHub releases / tags](https://github.com/BindsNET/bindsnet/releases). ## [Unreleased] +### Results changes (read this before upgrading) +- `Conv1dConnection`, `Conv2dConnection`, `Conv3dConnection` and `MeanFieldConnection` + now clamp a `w` passed to the constructor into `[wmin, wmax]`, as `Connection` does. + Before, the check was reversed (`==` instead of `!=`): a given `w` was clamped only + when a bound was infinite, which does nothing, so with finite bounds it was never + clamped. Affects code that passes these classes weights outside finite bounds; their + starting weights are now clamped. No built-in model or example does this, and the + golden-output tests are unchanged. + ### Added - Contributor rules for AI-assisted work (`CONTRIBUTING.md`, "AI-assisted contributions"): AI tools are welcome, a human must have read and run every change, diff --git a/bindsnet/network/topology.py b/bindsnet/network/topology.py index 328277b7..087937f5 100644 --- a/bindsnet/network/topology.py +++ b/bindsnet/network/topology.py @@ -880,7 +880,7 @@ def __init__( w += self.wmin w = w.to(dtype=w_dtype) else: - if (self.wmin == -inf).any() or (self.wmax == inf).any(): + if (self.wmin != -inf).any() or (self.wmax != inf).any(): w = torch.clamp(w, self.wmin, self.wmax) w = self.cast_dtype_if_needed(w, w_dtype) @@ -1041,7 +1041,7 @@ def __init__( w += self.wmin w = w.to(dtype=w_dtype) else: - if (self.wmin == -inf).any() or (self.wmax == inf).any(): + if (self.wmin != -inf).any() or (self.wmax != inf).any(): w = torch.clamp(w, self.wmin, self.wmax) w = self.cast_dtype_if_needed(w, w_dtype) @@ -1223,7 +1223,7 @@ def __init__( w += self.wmin w = w.to(dtype=w_dtype) else: - if (self.wmin == -inf).any() or (self.wmax == inf).any(): + if (self.wmin != -inf).any() or (self.wmax != inf).any(): w = torch.clamp(w, self.wmin, self.wmax) w = self.cast_dtype_if_needed(w, w_dtype) @@ -2268,7 +2268,7 @@ def __init__( w = self.wmin + ((torch.randn(1)[0] + 1) / 10) * (self.wmax - self.wmin) w = w.to(dtype=w_dtype) else: - if (self.wmin == -np.inf).any() or (self.wmax == np.inf).any(): + if (self.wmin != -np.inf).any() or (self.wmax != np.inf).any(): w = torch.clamp(w, self.wmin, self.wmax) w = self.cast_dtype_if_needed(w, w_dtype) self.w = Parameter(w, requires_grad=False) diff --git a/test/network/test_connections.py b/test/network/test_connections.py index 6e62da73..bc5466d1 100644 --- a/test/network/test_connections.py +++ b/test/network/test_connections.py @@ -1065,3 +1065,48 @@ def test_local_connection_nd_random_weights_within_per_weight_bounds(name): **kwargs, ) assert ((conn.w >= 0) & (conn.w <= wmax)).all() + + +def _given_w_out_of_bounds(): + """Constructors of the connection types whose given w is checked against finite bounds.""" + return { + "Conv1dConnection": lambda: Conv1dConnection( + Input(shape=(1, 6)), + LIFNodes(shape=(2, 4)), + kernel_size=3, + w=torch.linspace(-1.0, 2.0, 6).view(2, 1, 3), + wmin=0.0, + wmax=1.0, + ), + "Conv2dConnection": lambda: Conv2dConnection( + Input(shape=(1, 6, 6)), + LIFNodes(shape=(2, 4, 4)), + kernel_size=3, + w=torch.linspace(-1.0, 2.0, 18).view(2, 1, 3, 3), + wmin=0.0, + wmax=1.0, + ), + "Conv3dConnection": lambda: Conv3dConnection( + Input(shape=(1, 4, 4, 4)), + LIFNodes(shape=(2, 2, 2, 2)), + kernel_size=3, + w=torch.linspace(-1.0, 2.0, 54).view(2, 1, 3, 3, 3), + wmin=0.0, + wmax=1.0, + ), + "MeanFieldConnection": lambda: MeanFieldConnection( + Input(n=4), LIFNodes(n=3), w=torch.tensor(2.0), wmin=0.0, wmax=1.0 + ), + } + + +@pytest.mark.parametrize("name", list(_given_w_out_of_bounds())) +def test_given_weights_are_clamped_to_finite_bounds(name): + """A w passed to the constructor is clamped into [wmin, wmax], as Connection does. + + Caught: Conv1d/2d/3dConnection and MeanFieldConnection clamped a given w only when + a bound was infinite (== instead of !=), so with finite bounds it was never clamped. + """ + conn = _given_w_out_of_bounds()[name]() + assert conn.w.min() >= 0.0 + assert conn.w.max() <= 1.0