Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions bindsnet/network/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
45 changes: 45 additions & 0 deletions test/network/test_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading