torch-geopooling 1.0.0rc3__cp312-cp312-macosx_11_0_arm64.whl → 1.1.0__cp312-cp312-macosx_11_0_arm64.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.
Potentially problematic release.
This version of torch-geopooling might be problematic. Click here for more details.
- torch_geopooling/_C.cpython-312-darwin.so +0 -0
- torch_geopooling/__bind__/python_module.cc +7 -4
- torch_geopooling/__init__.py +3 -0
- torch_geopooling/functional/__init__.py +2 -0
- torch_geopooling/functional/embedding.py +85 -0
- torch_geopooling/functional/embedding_test.py +31 -0
- torch_geopooling/{functional.py → functional/pooling.py} +5 -3
- torch_geopooling/{functional_test.py → functional/pooling_test.py} +16 -1
- torch_geopooling/nn/__init__.py +2 -0
- torch_geopooling/nn/embedding.py +89 -0
- torch_geopooling/nn/embedding_test.py +48 -0
- torch_geopooling/{nn.py → nn/pooling.py} +6 -1
- torch_geopooling/nn/pooling_test.py +158 -0
- torch_geopooling/py.typed +0 -0
- {torch_geopooling-1.0.0rc3.dist-info → torch_geopooling-1.1.0.dist-info}/METADATA +10 -6
- torch_geopooling-1.1.0.dist-info/RECORD +24 -0
- {torch_geopooling-1.0.0rc3.dist-info → torch_geopooling-1.1.0.dist-info}/WHEEL +1 -1
- torch_geopooling/nn_test.py +0 -85
- torch_geopooling-1.0.0rc3.dist-info/RECORD +0 -17
- {torch_geopooling-1.0.0rc3.dist-info → torch_geopooling-1.1.0.dist-info}/LICENSE +0 -0
- {torch_geopooling-1.0.0rc3.dist-info → torch_geopooling-1.1.0.dist-info}/dependency_links.txt +0 -0
- {torch_geopooling-1.0.0rc3.dist-info → torch_geopooling-1.1.0.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -11,14 +11,17 @@ namespace torch_geopooling {
|
|
|
11
11
|
|
|
12
12
|
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
|
|
13
13
|
{
|
|
14
|
-
m.def("
|
|
15
|
-
m.def("
|
|
14
|
+
m.def("avg_quad_pool2d", &avg_quad_pool2d);
|
|
15
|
+
m.def("avg_quad_pool2d_backward", &avg_quad_pool2d_backward);
|
|
16
16
|
|
|
17
17
|
m.def("max_quad_pool2d", &max_quad_pool2d);
|
|
18
18
|
m.def("max_quad_pool2d_backward", &max_quad_pool2d_backward);
|
|
19
19
|
|
|
20
|
-
m.def("
|
|
21
|
-
m.def("
|
|
20
|
+
m.def("quad_pool2d", &quad_pool2d);
|
|
21
|
+
m.def("quad_pool2d_backward", &quad_pool2d_backward);
|
|
22
|
+
|
|
23
|
+
m.def("embedding2d", &embedding2d);
|
|
24
|
+
m.def("embedding2d_backward", &embedding2d_backward);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
|
torch_geopooling/__init__.py
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copyright (C) 2024, Yakau Bubnou
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
from typing import Optional, Tuple
|
|
17
|
+
|
|
18
|
+
from torch import Tensor, autograd
|
|
19
|
+
from torch.autograd.function import FunctionCtx
|
|
20
|
+
|
|
21
|
+
import torch_geopooling._C as _C
|
|
22
|
+
from torch_geopooling.tiling import ExteriorTuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["embedding2d"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Function(autograd.Function):
|
|
29
|
+
@staticmethod
|
|
30
|
+
def forward(
|
|
31
|
+
input: Tensor,
|
|
32
|
+
weight: Tensor,
|
|
33
|
+
padding: Tuple[int, int],
|
|
34
|
+
exterior: ExteriorTuple,
|
|
35
|
+
) -> Tensor:
|
|
36
|
+
return _C.embedding2d(input, weight, padding, exterior)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def setup_context(ctx: FunctionCtx, inputs: Tuple, outputs: Tuple) -> None:
|
|
40
|
+
input, weight, padding, exterior = inputs
|
|
41
|
+
|
|
42
|
+
ctx.save_for_backward(input, weight)
|
|
43
|
+
ctx.padding = padding
|
|
44
|
+
ctx.exterior = exterior
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def backward(ctx: FunctionCtx, grad: Tensor) -> Tuple[Optional[Tensor], ...]:
|
|
48
|
+
input, weight = ctx.saved_tensors
|
|
49
|
+
grad_weight = _C.embedding2d_backward(grad, input, weight, ctx.padding, ctx.exterior)
|
|
50
|
+
return None, grad_weight, None, None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def embedding2d(
|
|
54
|
+
input: Tensor,
|
|
55
|
+
weight: Tensor,
|
|
56
|
+
*,
|
|
57
|
+
padding: Tuple[int, int] = (0, 0),
|
|
58
|
+
exterior: ExteriorTuple,
|
|
59
|
+
) -> Tensor:
|
|
60
|
+
"""
|
|
61
|
+
Retrieves spatial embeddings from a fixed-size lookup table based on 2D coordinates.
|
|
62
|
+
|
|
63
|
+
This function accepts a list of (x, y) coordinates and retrieves the corresponding
|
|
64
|
+
spatial embeddings from a provided embedding matrix. The embeddings are selected
|
|
65
|
+
based on the input coordinates, with an optional padding to include neighboring cells.
|
|
66
|
+
See :class:`torch_geopooling.nn.Embedding2d` for more details.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
input: A list of 2D coordinates where each coordinate is represented as a tuple (x, y),
|
|
70
|
+
where x is the longitude and y is the latitude.
|
|
71
|
+
weight: A 3D tensor representing the embedding matrix. The first dimension corresponds to
|
|
72
|
+
the maximum possible bucket for the x coordinate, the second dimension corresponds to
|
|
73
|
+
the maximum possible bucket for the y coordinate, and the third dimension corresponds
|
|
74
|
+
to the embedding size.
|
|
75
|
+
padding: The size of the neighborhood to query. Default is 0, meaning only the embedding
|
|
76
|
+
for the exact input coordinate is retrieved.
|
|
77
|
+
exterior: The geometric boundary of the learning space, specified as a tuple (X, Y, W, H),
|
|
78
|
+
where X and Y represent the origin, and W and H represent the width and height of the
|
|
79
|
+
space, respectively.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Tensor: The retrieved spatial embeddings corresponding to the input coordinates.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
return Function.apply(input, weight, padding, exterior)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (C) 2024, Yakau Bubnou
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
import torch
|
|
17
|
+
from torch_geopooling.functional.embedding import embedding2d
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_embedding2d() -> None:
|
|
21
|
+
input = torch.rand((100, 2), dtype=torch.float64) * 10.0
|
|
22
|
+
weight = torch.rand((1024, 1024, 3), dtype=torch.float64)
|
|
23
|
+
|
|
24
|
+
result = embedding2d(
|
|
25
|
+
input,
|
|
26
|
+
weight,
|
|
27
|
+
padding=(3, 2),
|
|
28
|
+
exterior=(-10.0, -10.0, 20.0, 20.0),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert result.size() == torch.Size([100, 7, 5, 3])
|
|
@@ -271,10 +271,12 @@ class AdaptiveFunction(autograd.Function):
|
|
|
271
271
|
weight, input = ctx.saved_tensors
|
|
272
272
|
tiles, w = cls.sparse_ravel(weight)
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
grad_weight_dense = cls.backward_impl(
|
|
275
|
+
grad_values, tiles, w, input, ctx.exterior, *ctx.params
|
|
276
|
+
) # type: ignore
|
|
277
|
+
grad_weight_sparse = cls.sparse_unravel(tiles, grad_weight_dense, size=weight.size())
|
|
276
278
|
|
|
277
|
-
return
|
|
279
|
+
return grad_weight_sparse.coalesce(), None, None, None, None
|
|
278
280
|
|
|
279
281
|
@classmethod
|
|
280
282
|
def func(
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
import pytest
|
|
17
17
|
import torch
|
|
18
18
|
|
|
19
|
-
from torch_geopooling.functional import (
|
|
19
|
+
from torch_geopooling.functional.pooling import (
|
|
20
|
+
AdaptiveFunction,
|
|
20
21
|
adaptive_quad_pool2d,
|
|
21
22
|
adaptive_avg_quad_pool2d,
|
|
22
23
|
adaptive_max_quad_pool2d,
|
|
@@ -26,6 +27,20 @@ from torch_geopooling.functional import (
|
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
def test_adaptive_function_ravel() -> None:
|
|
31
|
+
size = (2, 2, 2, 1)
|
|
32
|
+
tiles = torch.tensor([[0, 0, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=torch.int64)
|
|
33
|
+
|
|
34
|
+
weight = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]], dtype=torch.float64)
|
|
35
|
+
|
|
36
|
+
sparse = AdaptiveFunction.sparse_unravel(tiles, weight, size=size)
|
|
37
|
+
torch.testing.assert_close(sparse.to_dense().to_sparse_coo(), sparse)
|
|
38
|
+
|
|
39
|
+
tiles_out, weight_out = AdaptiveFunction.sparse_ravel(sparse)
|
|
40
|
+
torch.testing.assert_close(tiles_out, tiles)
|
|
41
|
+
torch.testing.assert_close(weight_out, weight)
|
|
42
|
+
|
|
43
|
+
|
|
29
44
|
@pytest.mark.parametrize(
|
|
30
45
|
"function",
|
|
31
46
|
[
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Copyright (C) 2024, Yakau Bubnou
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
from typing import Union, Tuple, cast
|
|
17
|
+
|
|
18
|
+
import torch
|
|
19
|
+
from torch import Tensor, nn
|
|
20
|
+
|
|
21
|
+
from torch_geopooling import functional as F
|
|
22
|
+
from torch_geopooling.tiling import Exterior, ExteriorTuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Embedding2d",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_Exterior = Union[Exterior, ExteriorTuple]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Embedding2d(nn.Module):
|
|
34
|
+
"""
|
|
35
|
+
Retrieves spatial embeddings from a fixed-size lookup table based on 2D coordinates.
|
|
36
|
+
|
|
37
|
+
This module accepts a tensor of (x, y) coordinates and retrieves the corresponding
|
|
38
|
+
spatial embeddings from a provided embedding matrix. The embeddings are selected
|
|
39
|
+
based on the input coordinates, with an optional padding to include neighboring cells.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
manifold: The size of the 2-dimensional embedding in a form (W, H, N), where
|
|
43
|
+
W is a width, H is a height, and N is a feature dimension of the embedding.
|
|
44
|
+
padding: The size of the neighborhood to query. Default is 0, meaning only the embedding
|
|
45
|
+
for the exact input coordinate is retrieved.
|
|
46
|
+
exterior: The geometric boundary of the learning space, specified as a tuple (X, Y, W, H),
|
|
47
|
+
where X and Y represent the origin, and W and H represent the width and height of the
|
|
48
|
+
space, respectively.
|
|
49
|
+
|
|
50
|
+
Shape:
|
|
51
|
+
- Input: :math:`(*, 2)`, where 2 comprises x and y coordinates.
|
|
52
|
+
- Output: :math:`(*, X_{out}, Y_{out}, N)`, where * is the input shape, \
|
|
53
|
+
:math:`N = \\text{manifold[2]}`, and
|
|
54
|
+
|
|
55
|
+
:math:`X_{out} = \\text{padding}[0] \\times 2 + 1`
|
|
56
|
+
|
|
57
|
+
:math:`Y_{out} = \\text{padding}[1] \\times 2 + 1`
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
|
|
61
|
+
>>> # Create an embedding of EPSG:4326 rectangle into 1024x1024 embedding
|
|
62
|
+
>>> # with 3 features in each cell.
|
|
63
|
+
>>> embedding = nn.Embedding2d(
|
|
64
|
+
... (1024, 1024, 3),
|
|
65
|
+
... exterior=(-180.0, -90.0, 360.0, 180.0),
|
|
66
|
+
... padding=(2, 2),
|
|
67
|
+
... )
|
|
68
|
+
>>> input = torch.rand((100, 2), dtype=torch.float64) * 60.0
|
|
69
|
+
>>> output = embedding(input)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
manifold: Tuple[int, int, int],
|
|
75
|
+
exterior: _Exterior,
|
|
76
|
+
padding: Tuple[int, int] = (0, 0),
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__()
|
|
79
|
+
self.exterior = cast(ExteriorTuple, tuple(map(float, exterior)))
|
|
80
|
+
self.padding = padding
|
|
81
|
+
|
|
82
|
+
self.weight = nn.Parameter(torch.empty(manifold, dtype=torch.float64))
|
|
83
|
+
nn.init.zeros_(self.weight)
|
|
84
|
+
|
|
85
|
+
def extra_repr(self) -> str:
|
|
86
|
+
return "{manifold}, exterior={exterior}, padding={padding}".format(**self.__dict__)
|
|
87
|
+
|
|
88
|
+
def forward(self, input: Tensor) -> Tensor:
|
|
89
|
+
return F.embedding2d(input, self.weight, exterior=self.exterior, padding=self.padding)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright (C) 2024, Yakau Bubnou
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
import torch
|
|
18
|
+
from torch import nn
|
|
19
|
+
from torch.optim import SGD
|
|
20
|
+
|
|
21
|
+
from torch_geopooling.nn.embedding import Embedding2d
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_embedding2d_optimize() -> None:
|
|
25
|
+
embedding = Embedding2d(
|
|
26
|
+
(2, 2, 1),
|
|
27
|
+
padding=(0, 0),
|
|
28
|
+
exterior=(-180.0, -90.0, 360.0, 180.0),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
x_true = torch.tensor(
|
|
32
|
+
[[90.0, 45.0], [90.0, -45.0], [-90.0, -45.0], [-90.0, 45.0]], dtype=torch.float64
|
|
33
|
+
)
|
|
34
|
+
y_true = torch.tensor([[10.0], [20.0], [30.0], [40.0]], dtype=torch.float64)
|
|
35
|
+
|
|
36
|
+
optim = SGD(embedding.parameters(), lr=0.1)
|
|
37
|
+
loss_fn = nn.L1Loss()
|
|
38
|
+
|
|
39
|
+
for i in range(10000):
|
|
40
|
+
optim.zero_grad()
|
|
41
|
+
|
|
42
|
+
y_pred = embedding(x_true)
|
|
43
|
+
loss = loss_fn(y_pred[:, 0, 0, :], y_true)
|
|
44
|
+
loss.backward()
|
|
45
|
+
|
|
46
|
+
optim.step()
|
|
47
|
+
|
|
48
|
+
assert pytest.approx(0.0, abs=1e-1) == loss.detach().item()
|
|
@@ -91,7 +91,12 @@ class _AdaptiveQuadPool(nn.Module):
|
|
|
91
91
|
def initialize_parameters(self) -> None:
|
|
92
92
|
# The weight for adaptive operation should be sparse, since training operation
|
|
93
93
|
# results in a dynamic change of the underlying quadtree.
|
|
94
|
-
weight_size = (
|
|
94
|
+
weight_size = (
|
|
95
|
+
self.max_depth + 1,
|
|
96
|
+
1 << self.max_depth,
|
|
97
|
+
1 << self.max_depth,
|
|
98
|
+
self.feature_dim,
|
|
99
|
+
)
|
|
95
100
|
self.weight = nn.Parameter(torch.sparse_coo_tensor(size=weight_size, dtype=torch.float64))
|
|
96
101
|
|
|
97
102
|
@property
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Copyright (C) 2024, Yakau Bubnou
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
from typing import Type
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
import torch
|
|
20
|
+
from shapely.geometry import Polygon
|
|
21
|
+
from torch import nn
|
|
22
|
+
from torch.optim import SGD
|
|
23
|
+
from torch.nn import L1Loss
|
|
24
|
+
|
|
25
|
+
from torch_geopooling.nn.pooling import (
|
|
26
|
+
AdaptiveAvgQuadPool2d,
|
|
27
|
+
AdaptiveMaxQuadPool2d,
|
|
28
|
+
AdaptiveQuadPool2d,
|
|
29
|
+
AvgQuadPool2d,
|
|
30
|
+
MaxQuadPool2d,
|
|
31
|
+
QuadPool2d,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize(
|
|
36
|
+
"module_class",
|
|
37
|
+
[
|
|
38
|
+
AdaptiveQuadPool2d,
|
|
39
|
+
AdaptiveMaxQuadPool2d,
|
|
40
|
+
AdaptiveAvgQuadPool2d,
|
|
41
|
+
],
|
|
42
|
+
ids=["id", "max", "avg"],
|
|
43
|
+
)
|
|
44
|
+
def test_adaptive_quad_pool2d_gradient(module_class: Type[nn.Module]) -> None:
|
|
45
|
+
pool = module_class(5, (-180, -90, 360, 180))
|
|
46
|
+
|
|
47
|
+
input = torch.rand((100, 2), dtype=torch.float64) * 90
|
|
48
|
+
y = pool(input)
|
|
49
|
+
|
|
50
|
+
assert pool.weight.grad is None
|
|
51
|
+
|
|
52
|
+
loss_fn = L1Loss()
|
|
53
|
+
loss = loss_fn(y, torch.ones_like(y))
|
|
54
|
+
loss.backward()
|
|
55
|
+
|
|
56
|
+
assert pool.weight.grad is not None
|
|
57
|
+
assert pool.weight.grad.sum().item() == pytest.approx(-1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_adaptive_quad_pool2d_optimize() -> None:
|
|
61
|
+
pool = AdaptiveQuadPool2d(1, (-180, -90, 360, 180), max_depth=1)
|
|
62
|
+
|
|
63
|
+
# Input coordinates are simply centers of the level-1 quads.
|
|
64
|
+
x_true = torch.tensor(
|
|
65
|
+
[[90.0, 45.0], [90.0, -45.0], [-90.0, -45.0], [-90.0, 45.0]], dtype=torch.float64
|
|
66
|
+
)
|
|
67
|
+
y_true = torch.tensor([[10.0], [20.0], [30.0], [40.0]], dtype=torch.float64)
|
|
68
|
+
y_tile = [[1, 1, 1], [1, 1, 0], [1, 0, 0], [1, 0, 1]]
|
|
69
|
+
|
|
70
|
+
optim = SGD(pool.parameters(), lr=0.01)
|
|
71
|
+
loss_fn = nn.L1Loss()
|
|
72
|
+
|
|
73
|
+
for i in range(20000):
|
|
74
|
+
optim.zero_grad()
|
|
75
|
+
|
|
76
|
+
y_pred = pool(x_true)
|
|
77
|
+
loss = loss_fn(y_pred, y_true)
|
|
78
|
+
loss.backward()
|
|
79
|
+
|
|
80
|
+
optim.step()
|
|
81
|
+
|
|
82
|
+
# Ensure that model converged with a small loss.
|
|
83
|
+
assert pytest.approx(0.0, abs=1e-1) == loss.detach().item()
|
|
84
|
+
|
|
85
|
+
# Ensure that weights that pooling operation learned are the same as in the
|
|
86
|
+
# target matrix (y_true).
|
|
87
|
+
weight = pool.weight.to_dense()
|
|
88
|
+
|
|
89
|
+
for i, tile in enumerate(y_tile):
|
|
90
|
+
z, x, y = tile
|
|
91
|
+
expect_weight = y_true[i].item()
|
|
92
|
+
actual_weight = weight[z, x, y].detach().item()
|
|
93
|
+
|
|
94
|
+
assert pytest.approx(expect_weight, abs=1e-1) == actual_weight, f"tile {tile} is wrong"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.parametrize(
|
|
98
|
+
"module_class",
|
|
99
|
+
[
|
|
100
|
+
QuadPool2d,
|
|
101
|
+
MaxQuadPool2d,
|
|
102
|
+
AvgQuadPool2d,
|
|
103
|
+
],
|
|
104
|
+
ids=["id", "max", "avg"],
|
|
105
|
+
)
|
|
106
|
+
def test_quad_pool2d_gradient(module_class: Type[nn.Module]) -> None:
|
|
107
|
+
poly = Polygon([(0.0, 0.0), (1.0, 0.0), (1.0, 1.1), (0.0, 1.0)])
|
|
108
|
+
exterior = (0.0, 0.0, 1.0, 1.0)
|
|
109
|
+
|
|
110
|
+
pool = module_class(4, poly, exterior, max_depth=5)
|
|
111
|
+
assert pool.weight.size() == torch.Size([pool.tiles.size(0), 4])
|
|
112
|
+
|
|
113
|
+
input = torch.rand((100, 2), dtype=torch.float64)
|
|
114
|
+
y = pool(input)
|
|
115
|
+
|
|
116
|
+
assert pool.weight.grad is None
|
|
117
|
+
|
|
118
|
+
loss_fn = L1Loss()
|
|
119
|
+
loss = loss_fn(y, torch.ones_like(y))
|
|
120
|
+
loss.backward()
|
|
121
|
+
|
|
122
|
+
assert pool.weight.grad is not None
|
|
123
|
+
assert pool.weight.grad.sum().item() == pytest.approx(-1)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_quad_pool2d_optimize() -> None:
|
|
127
|
+
poly = Polygon([(-180, -90), (-180, 90), (180, 90), (180, -90)])
|
|
128
|
+
pool = QuadPool2d(1, poly, (-180, -90, 360, 180), max_depth=1)
|
|
129
|
+
|
|
130
|
+
x_true = torch.tensor(
|
|
131
|
+
[[90.0, 45.0], [90.0, -45.0], [-90.0, -45.0], [-90.0, 45.0]], dtype=torch.float64
|
|
132
|
+
)
|
|
133
|
+
y_true = torch.tensor([[10.0], [20.0], [30.0], [40.0]], dtype=torch.float64)
|
|
134
|
+
y_tile = [(1, 1, 1), (1, 1, 0), (1, 0, 0), (1, 0, 1)]
|
|
135
|
+
|
|
136
|
+
optim = SGD(pool.parameters(), lr=0.01)
|
|
137
|
+
loss_fn = nn.L1Loss()
|
|
138
|
+
|
|
139
|
+
for i in range(20000):
|
|
140
|
+
optim.zero_grad()
|
|
141
|
+
|
|
142
|
+
y_pred = pool(x_true)
|
|
143
|
+
loss = loss_fn(y_pred, y_true)
|
|
144
|
+
loss.backward()
|
|
145
|
+
|
|
146
|
+
optim.step()
|
|
147
|
+
|
|
148
|
+
# Ensure that model converged with a small loss.
|
|
149
|
+
assert pytest.approx(0.0, abs=1e-1) == loss.detach().item()
|
|
150
|
+
|
|
151
|
+
actual_tiles = {}
|
|
152
|
+
for i in range(pool.tiles.size(0)):
|
|
153
|
+
tile = tuple(pool.tiles[i].detach().tolist())
|
|
154
|
+
actual_tiles[tile] = pool.weight[i, 0].detach().item()
|
|
155
|
+
|
|
156
|
+
for tile, expect_weight in zip(y_tile, y_true[:, 0].tolist()):
|
|
157
|
+
actual_weight = actual_tiles[tile]
|
|
158
|
+
assert pytest.approx(expect_weight, abs=1e-1) == actual_weight, f"tile {tile} is wrong"
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: torch-geopooling
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: The geospatial pooling modules for neural networks in PyTorch
|
|
5
5
|
Author-email: Yakau Bubnou <girokompass@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -680,7 +680,7 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
|
680
680
|
|
|
681
681
|
Project-URL: homepage, https://github.com/ybubnov/torch_geopooling
|
|
682
682
|
Project-URL: source, https://github.com/ybubnov/torch_geopooling
|
|
683
|
-
Classifier: Development Status ::
|
|
683
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
684
684
|
Classifier: Intended Audience :: Developers
|
|
685
685
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
686
686
|
Classifier: Topic :: Scientific/Engineering
|
|
@@ -699,17 +699,21 @@ Classifier: Operating System :: MacOS
|
|
|
699
699
|
Requires-Python: >=3.9
|
|
700
700
|
Description-Content-Type: text/markdown
|
|
701
701
|
License-File: LICENSE
|
|
702
|
-
Requires-Dist: shapely
|
|
703
|
-
Requires-Dist: torch
|
|
702
|
+
Requires-Dist: shapely>=2.0.0
|
|
703
|
+
Requires-Dist: torch<2.4.0,>=2.3.0
|
|
704
704
|
Provides-Extra: test
|
|
705
|
-
Requires-Dist: pytest
|
|
706
|
-
Requires-Dist: numpy
|
|
705
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
706
|
+
Requires-Dist: numpy<2.0.0,>=1.4.0; extra == "test"
|
|
707
707
|
|
|
708
708
|
# Torch Geopooling - The geospatial pooling library for PyTorch
|
|
709
709
|
|
|
710
710
|
The Torch Geopooling library is an extension for PyTorch library that provide extra layers for
|
|
711
711
|
building geospatial neural networks.
|
|
712
712
|
|
|
713
|
+
Here is an example of how you can use modules from Torch Geopooling library to train neural
|
|
714
|
+
networks predicting geospatial features:
|
|
715
|
+

|
|
716
|
+
|
|
713
717
|
## Installation
|
|
714
718
|
|
|
715
719
|
The library is distributed as PyPI package, to install that package, execute the following
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
torch_geopooling/_C.cpython-312-darwin.so,sha256=ATpxmHPRqaaAX9SvVua1sCc0Yj4Z0LgTzsMz03Qb0R0,563992
|
|
2
|
+
torch_geopooling/__init__.py,sha256=0o5Lnb5ZcEIRNY9AljwW1ESEmQid79rcbBsDBoMT8aM,60
|
|
3
|
+
torch_geopooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
torch_geopooling/return_types.py,sha256=QhIKf_qxpppzuZzUrT-VacfbvZ6zKmSRu-WZOqL8wuE,898
|
|
5
|
+
torch_geopooling/tiling.py,sha256=f1g0Gf__5zIOafZzDkwO62t9OqMDMBEYVtM2HXB5X0Y,2553
|
|
6
|
+
torch_geopooling/transforms.py,sha256=NHPBgx1L4-1745xNO_PvLuvbMH6DWi2rs1ViuRyBQAY,3635
|
|
7
|
+
torch_geopooling/transforms_test.py,sha256=Cqp4VVObBVrc8HQ7uxkpe1EsQ5NxXrCI77BiUqngLXw,1196
|
|
8
|
+
torch_geopooling/__bind__/python_module.cc,sha256=LLI363riung_4LI5ugN7Pm93iuoHDRF7n4Y4RlkzLYk,675
|
|
9
|
+
torch_geopooling/functional/__init__.py,sha256=BCcS22yPWITm37M-EPD-PK6WrS7grikDR8uZdoO8sZ4,118
|
|
10
|
+
torch_geopooling/functional/embedding.py,sha256=ER0KB-G7SoCsWJuNEqXRC_XcLdhqaCpn5OJPpUF9pLc,3337
|
|
11
|
+
torch_geopooling/functional/embedding_test.py,sha256=B5a1MP0rynX01a1RmmTJHZxMi4Y68cQoblw-kQawHJM,1094
|
|
12
|
+
torch_geopooling/functional/pooling.py,sha256=MuJfPW_RC4slgzWBO2ngQIBTzlZXeW7DBqV3ooIOSw4,12060
|
|
13
|
+
torch_geopooling/functional/pooling_test.py,sha256=qBT_WVbwyANolwg7-rE7KimF-bAEDB44guBl4OQuQhE,3005
|
|
14
|
+
torch_geopooling/nn/__init__.py,sha256=fWrhfQu0_KzW7VUesk31Sy3n3Om2c5g5dGA3vrLcCg4,102
|
|
15
|
+
torch_geopooling/nn/embedding.py,sha256=u0H1e_sqFG8ALmnfLpbFwKr_yw-z7MNExmB9-uE0SF0,3317
|
|
16
|
+
torch_geopooling/nn/embedding_test.py,sha256=msUeidL9Xkf3AKCPGQH9SJAhfkS7dyqYRzsH88Pt1uY,1505
|
|
17
|
+
torch_geopooling/nn/pooling.py,sha256=_NnGB45dWTZy4MQokFfmJL3as-EPVU3liwOuAmWbv1Y,14219
|
|
18
|
+
torch_geopooling/nn/pooling_test.py,sha256=AqnkpaQ6LcQxh5bWkuDuBAJ2XiBc-C2zVuUp5hynzBM,4794
|
|
19
|
+
torch_geopooling-1.1.0.dist-info/LICENSE,sha256=jLc4eyvG8hqxH4AdKAjK2DwXoP68qjHiGfq8eP5ubBI,35069
|
|
20
|
+
torch_geopooling-1.1.0.dist-info/METADATA,sha256=SnR-sEl9o7dGaskBb2gk0CNU6AydBsta4ZcxFSpuFu8,44371
|
|
21
|
+
torch_geopooling-1.1.0.dist-info/WHEEL,sha256=h9jBNgvnuEaix45NgESHvfNcOPGGNEywrbP9Un7hZlk,110
|
|
22
|
+
torch_geopooling-1.1.0.dist-info/dependency_links.txt,sha256=JqLDcYHtEaQB51V72n3gAJvRd36bpoPk9qgTbot-Lx4,37
|
|
23
|
+
torch_geopooling-1.1.0.dist-info/top_level.txt,sha256=3geTL2nsLvybdtr1psWIE6h63B1LuyIIyWWv0rDafTk,17
|
|
24
|
+
torch_geopooling-1.1.0.dist-info/RECORD,,
|
torch_geopooling/nn_test.py
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Copyright (C) 2024, Yakau Bubnou
|
|
2
|
-
#
|
|
3
|
-
# This program is free software: you can redistribute it and/or modify
|
|
4
|
-
# it under the terms of the GNU General Public License as published by
|
|
5
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
-
# (at your option) any later version.
|
|
7
|
-
#
|
|
8
|
-
# This program is distributed in the hope that it will be useful,
|
|
9
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
-
# GNU General Public License for more details.
|
|
12
|
-
#
|
|
13
|
-
# You should have received a copy of the GNU General Public License
|
|
14
|
-
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
-
|
|
16
|
-
from typing import Type
|
|
17
|
-
|
|
18
|
-
import pytest
|
|
19
|
-
import torch
|
|
20
|
-
from shapely.geometry import Polygon
|
|
21
|
-
from torch import nn
|
|
22
|
-
from torch.nn import L1Loss
|
|
23
|
-
|
|
24
|
-
from torch_geopooling.nn import (
|
|
25
|
-
AdaptiveAvgQuadPool2d,
|
|
26
|
-
AdaptiveMaxQuadPool2d,
|
|
27
|
-
AdaptiveQuadPool2d,
|
|
28
|
-
AvgQuadPool2d,
|
|
29
|
-
MaxQuadPool2d,
|
|
30
|
-
QuadPool2d,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@pytest.mark.parametrize(
|
|
35
|
-
"module_class",
|
|
36
|
-
[
|
|
37
|
-
AdaptiveQuadPool2d,
|
|
38
|
-
AdaptiveMaxQuadPool2d,
|
|
39
|
-
AdaptiveAvgQuadPool2d,
|
|
40
|
-
],
|
|
41
|
-
ids=["id", "max", "avg"],
|
|
42
|
-
)
|
|
43
|
-
def test_adaptive_quad_pool2d_gradient(module_class: Type[nn.Module]) -> None:
|
|
44
|
-
pool = module_class(5, (-180, -90, 360, 180))
|
|
45
|
-
|
|
46
|
-
input = torch.rand((100, 2), dtype=torch.float64) * 90
|
|
47
|
-
y = pool(input)
|
|
48
|
-
|
|
49
|
-
assert pool.weight.grad is None
|
|
50
|
-
|
|
51
|
-
loss_fn = L1Loss()
|
|
52
|
-
loss = loss_fn(y, torch.ones_like(y))
|
|
53
|
-
loss.backward()
|
|
54
|
-
|
|
55
|
-
assert pool.weight.grad is not None
|
|
56
|
-
assert pool.weight.grad.sum().item() == pytest.approx(-1)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@pytest.mark.parametrize(
|
|
60
|
-
"module_class",
|
|
61
|
-
[
|
|
62
|
-
QuadPool2d,
|
|
63
|
-
MaxQuadPool2d,
|
|
64
|
-
AvgQuadPool2d,
|
|
65
|
-
],
|
|
66
|
-
ids=["id", "max", "avg"],
|
|
67
|
-
)
|
|
68
|
-
def test_quad_pool2d_gradient(module_class: Type[nn.Module]) -> None:
|
|
69
|
-
poly = Polygon([(0.0, 0.0), (1.0, 0.0), (1.0, 1.1), (0.0, 1.0)])
|
|
70
|
-
exterior = (0.0, 0.0, 1.0, 1.0)
|
|
71
|
-
|
|
72
|
-
pool = module_class(4, poly, exterior, max_depth=5)
|
|
73
|
-
assert pool.weight.size() == torch.Size([pool.tiles.size(0), 4])
|
|
74
|
-
|
|
75
|
-
input = torch.rand((100, 2), dtype=torch.float64)
|
|
76
|
-
y = pool(input)
|
|
77
|
-
|
|
78
|
-
assert pool.weight.grad is None
|
|
79
|
-
|
|
80
|
-
loss_fn = L1Loss()
|
|
81
|
-
loss = loss_fn(y, torch.ones_like(y))
|
|
82
|
-
loss.backward()
|
|
83
|
-
|
|
84
|
-
assert pool.weight.grad is not None
|
|
85
|
-
assert pool.weight.grad.sum().item() == pytest.approx(-1)
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
torch_geopooling/_C.cpython-312-darwin.so,sha256=x7_fRV02UM4cE_Vk7d5-Adb1V002ZQyEMhZc7o9HPd4,519528
|
|
2
|
-
torch_geopooling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
torch_geopooling/functional.py,sha256=rZrh30K4IlxG3lGQ1_C8wrL7MKS9yIT-NSEkcM21A_k,12028
|
|
4
|
-
torch_geopooling/functional_test.py,sha256=WZ7GePj1f8jp8cxNrnrDoHXnJeEeBUNVnFySPgEDl2I,2406
|
|
5
|
-
torch_geopooling/nn.py,sha256=A_pozkqIfjhmOL7BDvTV2bJBv7nXD3Wi8nAlbl_rSEs,14156
|
|
6
|
-
torch_geopooling/nn_test.py,sha256=4Aj3CcI8KG0parOyruAHZCCnC51CvSacTZ0AxTs16Gc,2359
|
|
7
|
-
torch_geopooling/return_types.py,sha256=QhIKf_qxpppzuZzUrT-VacfbvZ6zKmSRu-WZOqL8wuE,898
|
|
8
|
-
torch_geopooling/tiling.py,sha256=f1g0Gf__5zIOafZzDkwO62t9OqMDMBEYVtM2HXB5X0Y,2553
|
|
9
|
-
torch_geopooling/transforms.py,sha256=NHPBgx1L4-1745xNO_PvLuvbMH6DWi2rs1ViuRyBQAY,3635
|
|
10
|
-
torch_geopooling/transforms_test.py,sha256=Cqp4VVObBVrc8HQ7uxkpe1EsQ5NxXrCI77BiUqngLXw,1196
|
|
11
|
-
torch_geopooling/__bind__/python_module.cc,sha256=5K04tRT62XTVsZhY2EE4r4bBTz4mA4IwEkw5adPzo58,576
|
|
12
|
-
torch_geopooling-1.0.0rc3.dist-info/LICENSE,sha256=jLc4eyvG8hqxH4AdKAjK2DwXoP68qjHiGfq8eP5ubBI,35069
|
|
13
|
-
torch_geopooling-1.0.0rc3.dist-info/METADATA,sha256=e81OTHBrYbB3X4ZdjIKovz-ch5tEywdIMf4_oNNq8co,44199
|
|
14
|
-
torch_geopooling-1.0.0rc3.dist-info/WHEEL,sha256=Vo9YTsjXxZ5SWdH4n69oS5jU3YTIi3eHk0n-aUcTtlw,110
|
|
15
|
-
torch_geopooling-1.0.0rc3.dist-info/dependency_links.txt,sha256=JqLDcYHtEaQB51V72n3gAJvRd36bpoPk9qgTbot-Lx4,37
|
|
16
|
-
torch_geopooling-1.0.0rc3.dist-info/top_level.txt,sha256=3geTL2nsLvybdtr1psWIE6h63B1LuyIIyWWv0rDafTk,17
|
|
17
|
-
torch_geopooling-1.0.0rc3.dist-info/RECORD,,
|
|
File without changes
|
{torch_geopooling-1.0.0rc3.dist-info → torch_geopooling-1.1.0.dist-info}/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|