segmodels-keras 0.1.0.dev8__tar.gz → 0.1.0.dev9__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.
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/PKG-INFO +1 -1
- segmodels_keras-0.1.0.dev9/segmodels_keras/_compat.py +123 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/_version.py +1 -1
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/_weights.py +2 -1
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/inception_resnet_v2.py +4 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/inception_v3.py +4 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/resnet_18_34.py +35 -37
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/fpn.py +3 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/linknet.py +3 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/pspnet.py +3 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/unet.py +4 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/PKG-INFO +1 -1
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/tests/test_utils.py +84 -0
- segmodels_keras-0.1.0.dev8/segmodels_keras/_compat.py +0 -4
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/LICENSE +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/MANIFEST.in +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/README.rst +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/pyproject.toml +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/__init__.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/__init__.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/_common_blocks.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/backbones_factory.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/resnet_common.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/__init__.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/functional.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/objects.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/losses.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/metrics.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/__init__.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/_common_blocks.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/_utils.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/utils.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/SOURCES.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/dependency_links.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/requires.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/top_level.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/setup.cfg +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/setup.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/tests/test_backbones.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/tests/test_metrics.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/tests/test_models.py +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import keras
|
|
5
|
+
from packaging.version import parse as parse_version
|
|
6
|
+
|
|
7
|
+
KERAS_GTE_3 = parse_version(keras.__version__) >= parse_version("3.0.0")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _is_keras_v3_weights_hdf5(filepath):
|
|
11
|
+
try:
|
|
12
|
+
import h5py # noqa: PLC0415
|
|
13
|
+
except ImportError:
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
with h5py.File(filepath, "r") as handle:
|
|
18
|
+
return "layers" in handle and "vars" in handle
|
|
19
|
+
except OSError:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_sorted_hdf5_var_keys(vars_group):
|
|
24
|
+
return sorted(vars_group.keys(), key=int)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_keras_v3_weighted_layers(handle):
|
|
28
|
+
import h5py # noqa: PLC0415
|
|
29
|
+
|
|
30
|
+
weighted_layers = []
|
|
31
|
+
for layer_name in handle["layers"].keys():
|
|
32
|
+
layer_group = handle["layers"][layer_name]
|
|
33
|
+
vars_group = layer_group.get("vars")
|
|
34
|
+
if vars_group is None or not vars_group.keys():
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
signature = tuple(
|
|
38
|
+
tuple(vars_group[key].shape)
|
|
39
|
+
for key in _get_sorted_hdf5_var_keys(vars_group)
|
|
40
|
+
)
|
|
41
|
+
weighted_layers.append(
|
|
42
|
+
{
|
|
43
|
+
"name": layer_name,
|
|
44
|
+
"addr": h5py.h5o.get_info(layer_group.id).addr,
|
|
45
|
+
"signature": signature,
|
|
46
|
+
"vars_group": vars_group,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
weighted_layers.sort(key=lambda layer: layer["addr"])
|
|
51
|
+
return weighted_layers
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _load_keras_v3_weights_hdf5(model, filepath):
|
|
55
|
+
import h5py # noqa: PLC0415
|
|
56
|
+
|
|
57
|
+
with h5py.File(filepath, "r") as handle:
|
|
58
|
+
saved_layers = _get_keras_v3_weighted_layers(handle)
|
|
59
|
+
saved_layers_by_signature = defaultdict(list)
|
|
60
|
+
for saved_layer in saved_layers:
|
|
61
|
+
saved_layers_by_signature[saved_layer["signature"]].append(saved_layer)
|
|
62
|
+
|
|
63
|
+
loaded_layers = 0
|
|
64
|
+
|
|
65
|
+
for layer in model.layers:
|
|
66
|
+
if not layer.weights:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
signature = tuple(tuple(variable.shape) for variable in layer.weights)
|
|
70
|
+
if not saved_layers_by_signature[signature]:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
"Unable to locate matching Keras v3 HDF5 weights for layer "
|
|
73
|
+
f"'{layer.name}' with shapes {list(signature)}."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
vars_group = saved_layers_by_signature[signature].pop(0)["vars_group"]
|
|
77
|
+
weight_values = [
|
|
78
|
+
vars_group[key][()] for key in _get_sorted_hdf5_var_keys(vars_group)
|
|
79
|
+
]
|
|
80
|
+
layer.set_weights(weight_values)
|
|
81
|
+
loaded_layers += 1
|
|
82
|
+
|
|
83
|
+
if loaded_layers == 0:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
"No matching weighted layers found in Keras v3 HDF5 weights file: "
|
|
86
|
+
f"{filepath}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_weights(model, filepath):
|
|
91
|
+
"""Load weights from an HDF5 file into a Keras model.
|
|
92
|
+
|
|
93
|
+
TensorFlow/Keras 2.10 and 2.11 can only read the legacy HDF5 format
|
|
94
|
+
(``layer_names`` attribute at the root). Keras 3 switched to a newer
|
|
95
|
+
layout where weights are stored under ``layers/<name>/vars/<index>``.
|
|
96
|
+
When a file saved with Keras 3 is opened by the TF 2.10/2.11 loader the
|
|
97
|
+
error "found 0 saved layers" is raised because the legacy reader finds no
|
|
98
|
+
``layer_names`` attribute.
|
|
99
|
+
|
|
100
|
+
This wrapper calls ``model.load_weights`` first. If that raises the
|
|
101
|
+
"found 0 saved layers" ValueError *and* the file is detected as a Keras 3
|
|
102
|
+
weights HDF5 file, it falls back to a manual reader that matches each
|
|
103
|
+
model layer to its saved counterpart by weight-shape signature and file
|
|
104
|
+
creation order.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
model: Keras model whose weights should be restored.
|
|
108
|
+
filepath (str | Path): Path to a ``.h5`` or ``.hdf5`` weights file.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
model.load_weights(filepath)
|
|
112
|
+
except ValueError as ex:
|
|
113
|
+
filepath = str(filepath)
|
|
114
|
+
message = str(ex)
|
|
115
|
+
is_hdf5 = Path(filepath).suffix in {".h5", ".hdf5"}
|
|
116
|
+
if (
|
|
117
|
+
not is_hdf5
|
|
118
|
+
or "found 0 saved layers" not in message
|
|
119
|
+
or not _is_keras_v3_weights_hdf5(filepath)
|
|
120
|
+
):
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
_load_keras_v3_weights_hdf5(model, filepath)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__versioninfo__ = (0, 1, 0, "
|
|
1
|
+
__versioninfo__ = (0, 1, 0, "dev9")
|
|
2
2
|
__version__ = ".".join(map(str, __versioninfo__))
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/_weights.py
RENAMED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Source: https://github.com/qubvel/classification_models
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from segmodels_keras._compat import load_weights
|
|
6
7
|
from keras.utils import get_file
|
|
7
8
|
|
|
8
9
|
__all__ = ["load_model_weights"]
|
|
@@ -35,7 +36,7 @@ def load_model_weights(model, model_name, dataset, classes, include_top):
|
|
|
35
36
|
md5_hash=weights["md5"],
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
load_weights(model, weights_path)
|
|
39
40
|
|
|
40
41
|
else:
|
|
41
42
|
raise ValueError(
|
|
@@ -38,6 +38,8 @@ from keras import backend, layers, models
|
|
|
38
38
|
from keras.applications import imagenet_utils
|
|
39
39
|
from keras.utils import get_file, get_source_inputs
|
|
40
40
|
|
|
41
|
+
from segmodels_keras._compat import load_weights
|
|
42
|
+
|
|
41
43
|
from .resnet_common import _obtain_input_shape
|
|
42
44
|
|
|
43
45
|
BASE_WEIGHT_URL = (
|
|
@@ -261,9 +263,9 @@ def InceptionResNetV2(
|
|
|
261
263
|
cache_subdir="models",
|
|
262
264
|
file_hash="d19885ff4a710c122648d3b5c3b684e4",
|
|
263
265
|
)
|
|
264
|
-
|
|
266
|
+
load_weights(model, weights_path)
|
|
265
267
|
elif weights is not None:
|
|
266
|
-
|
|
268
|
+
load_weights(model, weights)
|
|
267
269
|
|
|
268
270
|
return model
|
|
269
271
|
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/inception_v3.py
RENAMED
|
@@ -14,6 +14,8 @@ from keras import backend, layers, models
|
|
|
14
14
|
from keras import utils as keras_utils
|
|
15
15
|
from keras.applications import imagenet_utils
|
|
16
16
|
|
|
17
|
+
from segmodels_keras._compat import load_weights
|
|
18
|
+
|
|
17
19
|
WEIGHTS_PATH = (
|
|
18
20
|
"https://github.com/fchollet/deep-learning-models/"
|
|
19
21
|
"releases/download/v0.5/"
|
|
@@ -361,9 +363,9 @@ def InceptionV3(
|
|
|
361
363
|
cache_subdir="models",
|
|
362
364
|
file_hash="bcbd6486424b2319ff4ef7d526e38f63",
|
|
363
365
|
)
|
|
364
|
-
|
|
366
|
+
load_weights(model, weights_path)
|
|
365
367
|
elif weights is not None:
|
|
366
|
-
|
|
368
|
+
load_weights(model, weights)
|
|
367
369
|
|
|
368
370
|
return model
|
|
369
371
|
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/resnet_18_34.py
RENAMED
|
@@ -14,27 +14,22 @@ closely so that the two can be maintained together:
|
|
|
14
14
|
Layer naming follows the same ``conv{N}_block{M}_*`` convention used by
|
|
15
15
|
keras.applications, so models built here share the same weight-loading patterns.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
the
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
weights.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
is actually ``momentum=0.1``, but after inversion we use ``momentum=0.9`` here.
|
|
34
|
-
- The single bottom convolutional layer gets ``use_bias=False`` (like all convolutional
|
|
35
|
-
layers in the model just like the version in torchvision and in segmentation_models.
|
|
36
|
-
In the keras.applications ResNet(50+) implementation ``use_bias=True`` is used for the
|
|
37
|
-
single bottom convolutional layer.
|
|
17
|
+
Notes:
|
|
18
|
+
- The resulting resnet18 and resnet34 are not exactly the same as the version in
|
|
19
|
+
https://github.com/qubvel/classification_models and hence is also different than
|
|
20
|
+
the segmentation models in https://github.com/qubvel/segmentation_models.
|
|
21
|
+
There are some differences in the bottom and top layers of the resnet, e.g. because
|
|
22
|
+
the classification_models version applies also post-activation after the last
|
|
23
|
+
residual block.
|
|
24
|
+
- The implementation is also ~ compatible with torchvision ResNet18/34 pretrained
|
|
25
|
+
weights based on a (rough) test. There are some small differences, but they don't
|
|
26
|
+
seem to influance the results significantly:
|
|
27
|
+
- BatchNormalization in torchvision uses ``momentum=0.9`` and ``epsilon=1e-5``
|
|
28
|
+
instead of the keras.applications defaults of ``momentum=0.99`` and
|
|
29
|
+
``epsilon=1.001e-5`` used here.
|
|
30
|
+
Note that momentum is only used during training. In addition, the value in
|
|
31
|
+
pytorch is inverted compared to the tf.keras implementation, so the default in
|
|
32
|
+
pytorch is actually ``momentum=0.1``, but inverted this gives ``momentum=0.9``.
|
|
38
33
|
"""
|
|
39
34
|
|
|
40
35
|
import os
|
|
@@ -43,8 +38,13 @@ from keras import backend, layers, models
|
|
|
43
38
|
from keras.applications import imagenet_utils
|
|
44
39
|
from keras.utils import get_source_inputs
|
|
45
40
|
|
|
41
|
+
from segmodels_keras._compat import load_weights
|
|
42
|
+
|
|
46
43
|
from .resnet_common import _obtain_input_shape
|
|
47
44
|
|
|
45
|
+
momentum = 0.99
|
|
46
|
+
epsilon = 1.001e-5
|
|
47
|
+
|
|
48
48
|
|
|
49
49
|
def ResNet(
|
|
50
50
|
stack_fn,
|
|
@@ -141,7 +141,7 @@ def ResNet(
|
|
|
141
141
|
# The batchnormalization parameters have been changed to be compatible with the
|
|
142
142
|
# torchvision ResNet18/34 pretrained weights.
|
|
143
143
|
x = layers.BatchNormalization(
|
|
144
|
-
axis=bn_axis, momentum=
|
|
144
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name="conv1_bn"
|
|
145
145
|
)(x)
|
|
146
146
|
x = layers.Activation("relu", name="conv1_relu")(x)
|
|
147
147
|
|
|
@@ -154,7 +154,7 @@ def ResNet(
|
|
|
154
154
|
# The batchnormalization parameters have been changed to be compatible with the
|
|
155
155
|
# torchvision ResNet18/34 pretrained weights.
|
|
156
156
|
x = layers.BatchNormalization(
|
|
157
|
-
axis=bn_axis, momentum=
|
|
157
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name="post_bn"
|
|
158
158
|
)(x)
|
|
159
159
|
x = layers.Activation("relu", name="post_relu")(x)
|
|
160
160
|
|
|
@@ -184,7 +184,7 @@ def ResNet(
|
|
|
184
184
|
model = models.Model(inputs, x, name=name)
|
|
185
185
|
|
|
186
186
|
if weights is not None and os.path.exists(weights):
|
|
187
|
-
|
|
187
|
+
load_weights(model, weights)
|
|
188
188
|
|
|
189
189
|
return model
|
|
190
190
|
|
|
@@ -226,7 +226,7 @@ def residual_basicblock(
|
|
|
226
226
|
filters, 1, strides=stride, use_bias=False, name=f"{name}_0_conv"
|
|
227
227
|
)(x)
|
|
228
228
|
shortcut = layers.BatchNormalization(
|
|
229
|
-
axis=bn_axis, momentum=
|
|
229
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name=f"{name}_0_bn"
|
|
230
230
|
)(shortcut)
|
|
231
231
|
else:
|
|
232
232
|
shortcut = x
|
|
@@ -236,14 +236,14 @@ def residual_basicblock(
|
|
|
236
236
|
filters, kernel_size, strides=stride, use_bias=False, name=f"{name}_1_conv"
|
|
237
237
|
)(x)
|
|
238
238
|
x = layers.BatchNormalization(
|
|
239
|
-
axis=bn_axis, momentum=
|
|
239
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name=f"{name}_1_bn"
|
|
240
240
|
)(x)
|
|
241
241
|
x = layers.Activation("relu", name=f"{name}_1_relu")(x)
|
|
242
242
|
|
|
243
243
|
x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name=f"{name}_2_pad")(x)
|
|
244
244
|
x = layers.Conv2D(filters, kernel_size, use_bias=False, name=f"{name}_2_conv")(x)
|
|
245
245
|
x = layers.BatchNormalization(
|
|
246
|
-
axis=bn_axis, momentum=
|
|
246
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name=f"{name}_2_bn"
|
|
247
247
|
)(x)
|
|
248
248
|
|
|
249
249
|
x = layers.Add(name=f"{name}_add")([shortcut, x])
|
|
@@ -354,15 +354,13 @@ def ResNet34(
|
|
|
354
354
|
)
|
|
355
355
|
|
|
356
356
|
|
|
357
|
-
def preprocess_input(x,
|
|
358
|
-
"""
|
|
359
|
-
import numpy as np
|
|
357
|
+
def preprocess_input(x, data_format=None):
|
|
358
|
+
"""Preprocesses a numpy array encoding a batch of images.
|
|
360
359
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
std = np.array([52.17, 45.38, 44, 39.69, 79.3], dtype="float32")
|
|
360
|
+
Args:
|
|
361
|
+
x: a 4D numpy array consists of RGB values within [0, 255].
|
|
364
362
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return (x
|
|
363
|
+
Returns:
|
|
364
|
+
Preprocessed array.
|
|
365
|
+
"""
|
|
366
|
+
return imagenet_utils.preprocess_input(x, data_format=data_format, mode="tf")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from keras import backend, layers, models
|
|
2
2
|
from keras import utils as keras_utils
|
|
3
3
|
|
|
4
|
+
from .._compat import load_weights
|
|
4
5
|
from ..backbones.backbones_factory import Backbones
|
|
5
6
|
from ._common_blocks import Conv2dBn
|
|
6
7
|
from ._utils import freeze_model
|
|
@@ -177,7 +178,7 @@ def build_fpn(
|
|
|
177
178
|
# load weights without top if path provided
|
|
178
179
|
if weights_notop is not None:
|
|
179
180
|
model_notop = models.Model(input_, x)
|
|
180
|
-
|
|
181
|
+
load_weights(model_notop, weights_notop)
|
|
181
182
|
if freeze_notop:
|
|
182
183
|
freeze_model(model_notop)
|
|
183
184
|
x = model_notop.output
|
|
@@ -291,6 +292,6 @@ def FPN(
|
|
|
291
292
|
|
|
292
293
|
# loading model weights
|
|
293
294
|
if weights is not None:
|
|
294
|
-
|
|
295
|
+
load_weights(model, weights)
|
|
295
296
|
|
|
296
297
|
return model
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from keras import backend, layers, models
|
|
2
2
|
from keras import utils as keras_utils
|
|
3
3
|
|
|
4
|
+
from .._compat import load_weights
|
|
4
5
|
from ..backbones.backbones_factory import Backbones
|
|
5
6
|
from ._common_blocks import Conv2dBn
|
|
6
7
|
from ._utils import freeze_model
|
|
@@ -174,7 +175,7 @@ def build_linknet(
|
|
|
174
175
|
# load weights without top if path provided
|
|
175
176
|
if weights_notop is not None:
|
|
176
177
|
model_notop = models.Model(input_, x)
|
|
177
|
-
|
|
178
|
+
load_weights(model_notop, weights_notop)
|
|
178
179
|
if freeze_notop:
|
|
179
180
|
freeze_model(model_notop)
|
|
180
181
|
x = model_notop.output
|
|
@@ -300,6 +301,6 @@ def Linknet(
|
|
|
300
301
|
|
|
301
302
|
# loading model weights
|
|
302
303
|
if weights is not None:
|
|
303
|
-
|
|
304
|
+
load_weights(model, weights)
|
|
304
305
|
|
|
305
306
|
return model
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from keras import backend, layers, models
|
|
2
2
|
from keras import utils as keras_utils
|
|
3
3
|
|
|
4
|
+
from .._compat import load_weights
|
|
4
5
|
from ..backbones.backbones_factory import Backbones
|
|
5
6
|
from ._common_blocks import Conv2dBn
|
|
6
7
|
from ._utils import freeze_model
|
|
@@ -152,7 +153,7 @@ def build_psp(
|
|
|
152
153
|
# load weights without top if path provided
|
|
153
154
|
if weights_notop is not None:
|
|
154
155
|
model_notop = models.Model(input_, x)
|
|
155
|
-
|
|
156
|
+
load_weights(model_notop, weights_notop)
|
|
156
157
|
if freeze_notop:
|
|
157
158
|
freeze_model(model_notop)
|
|
158
159
|
x = model_notop.output
|
|
@@ -276,6 +277,6 @@ def PSPNet(
|
|
|
276
277
|
|
|
277
278
|
# loading model weights
|
|
278
279
|
if weights is not None:
|
|
279
|
-
|
|
280
|
+
load_weights(model, weights)
|
|
280
281
|
|
|
281
282
|
return model
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from keras import backend, layers, models
|
|
2
2
|
from keras import utils as keras_utils
|
|
3
3
|
|
|
4
|
+
from .._compat import load_weights
|
|
4
5
|
from ..backbones.backbones_factory import Backbones
|
|
5
6
|
from ._common_blocks import Conv2dBn
|
|
6
7
|
from ._utils import freeze_model
|
|
@@ -146,7 +147,8 @@ def build_unet(
|
|
|
146
147
|
# load weights without top if path provided
|
|
147
148
|
if weights_notop is not None:
|
|
148
149
|
model_notop = models.Model(input_, x)
|
|
149
|
-
|
|
150
|
+
load_weights(model_notop, weights_notop)
|
|
151
|
+
|
|
150
152
|
if freeze_notop:
|
|
151
153
|
freeze_model(model_notop)
|
|
152
154
|
x = model_notop.output
|
|
@@ -271,6 +273,6 @@ def Unet(
|
|
|
271
273
|
|
|
272
274
|
# loading model weights
|
|
273
275
|
if weights is not None:
|
|
274
|
-
|
|
276
|
+
load_weights(model, weights)
|
|
275
277
|
|
|
276
278
|
return model
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import h5py
|
|
1
2
|
import numpy as np
|
|
2
3
|
import pytest
|
|
3
4
|
|
|
4
5
|
import segmodels_keras as sm
|
|
5
6
|
from segmodels_keras import Unet
|
|
7
|
+
from segmodels_keras._compat import _load_keras_v3_weights_hdf5
|
|
6
8
|
from segmodels_keras.utils import set_regularization
|
|
7
9
|
|
|
8
10
|
if sm.framework() == sm._TF_KERAS_FRAMEWORK_NAME:
|
|
@@ -146,5 +148,87 @@ def test_save_model_weights_notop_invalid_decoder():
|
|
|
146
148
|
)
|
|
147
149
|
|
|
148
150
|
|
|
151
|
+
def test_load_keras_v3_style_weights_hdf5(tmp_path):
|
|
152
|
+
"""Normal case: distinct weight-shape signatures per layer."""
|
|
153
|
+
inputs = keras.layers.Input(shape=(4,), name="input")
|
|
154
|
+
x = keras.layers.Dense(3, use_bias=True, name="dense")(inputs)
|
|
155
|
+
x = keras.layers.BatchNormalization(scale=False, name="batch_norm")(x)
|
|
156
|
+
outputs = keras.layers.Dense(2, use_bias=False, name="head")(x)
|
|
157
|
+
model = keras.models.Model(inputs, outputs, name="functional")
|
|
158
|
+
|
|
159
|
+
expected_weights = [weight.copy() for weight in model.get_weights()]
|
|
160
|
+
path = tmp_path / "keras_v3_style.weights.h5"
|
|
161
|
+
|
|
162
|
+
with h5py.File(path, "w") as handle:
|
|
163
|
+
layers_group = handle.create_group("layers")
|
|
164
|
+
handle.create_group("vars").attrs["name"] = model.name
|
|
165
|
+
|
|
166
|
+
for layer in model.layers:
|
|
167
|
+
layer_group = layers_group.create_group(layer.name)
|
|
168
|
+
vars_group = layer_group.create_group("vars")
|
|
169
|
+
for index, value in enumerate(layer.get_weights()):
|
|
170
|
+
vars_group.create_dataset(str(index), data=value)
|
|
171
|
+
|
|
172
|
+
zero_weights = [np.zeros_like(weight) for weight in expected_weights]
|
|
173
|
+
model.set_weights(zero_weights)
|
|
174
|
+
|
|
175
|
+
_load_keras_v3_weights_hdf5(model, path)
|
|
176
|
+
|
|
177
|
+
for actual, expected in zip(model.get_weights(), expected_weights, strict=True):
|
|
178
|
+
np.testing.assert_allclose(actual, expected)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_load_keras_v3_style_weights_hdf5_duplicate_signatures(tmp_path):
|
|
182
|
+
"""Layers with identical weight-shape signatures are matched in save order.
|
|
183
|
+
|
|
184
|
+
This gave an error for Keras v3 HDF5 weight files of an inceptionresnetv2 backbone.
|
|
185
|
+
"""
|
|
186
|
+
# All three Dense layers share the same shape signature: [(4, 3)].
|
|
187
|
+
# The fallback must pop each candidate in file-write order so that
|
|
188
|
+
# branch_a, branch_b, and branch_c each receive their own distinct values.
|
|
189
|
+
inputs = keras.layers.Input(shape=(4,), name="input")
|
|
190
|
+
branch_a = keras.layers.Dense(3, use_bias=False, name="branch_a")(inputs)
|
|
191
|
+
branch_b = keras.layers.Dense(3, use_bias=False, name="branch_b")(inputs)
|
|
192
|
+
branch_c = keras.layers.Dense(3, use_bias=False, name="branch_c")(inputs)
|
|
193
|
+
concat_axis = 1
|
|
194
|
+
outputs = keras.layers.Concatenate(axis=concat_axis, name="concat")(
|
|
195
|
+
[branch_a, branch_b, branch_c]
|
|
196
|
+
)
|
|
197
|
+
model = keras.models.Model(inputs, outputs, name="functional")
|
|
198
|
+
|
|
199
|
+
# Assign distinct sentinel values to each dense layer so we can verify
|
|
200
|
+
# that the right values end up in the right layer after loading.
|
|
201
|
+
dense_layers = [model.get_layer(n) for n in ("branch_a", "branch_b", "branch_c")]
|
|
202
|
+
original_values = []
|
|
203
|
+
for i, layer in enumerate(dense_layers):
|
|
204
|
+
sentinel = np.full((4, 3), fill_value=float(i + 1), dtype=np.float32)
|
|
205
|
+
layer.set_weights([sentinel])
|
|
206
|
+
original_values.append(sentinel.copy())
|
|
207
|
+
|
|
208
|
+
path = tmp_path / "dup_signatures.weights.h5"
|
|
209
|
+
with h5py.File(path, "w") as handle:
|
|
210
|
+
layers_group = handle.create_group("layers")
|
|
211
|
+
handle.create_group("vars").attrs["name"] = model.name
|
|
212
|
+
for layer in model.layers:
|
|
213
|
+
layer_group = layers_group.create_group(layer.name)
|
|
214
|
+
vars_group = layer_group.create_group("vars")
|
|
215
|
+
for index, value in enumerate(layer.get_weights()):
|
|
216
|
+
vars_group.create_dataset(str(index), data=value)
|
|
217
|
+
|
|
218
|
+
# Zero out all weights before loading.
|
|
219
|
+
for layer in dense_layers:
|
|
220
|
+
layer.set_weights([np.zeros((4, 3), dtype=np.float32)])
|
|
221
|
+
|
|
222
|
+
_load_keras_v3_weights_hdf5(model, path)
|
|
223
|
+
|
|
224
|
+
for layer, expected in zip(dense_layers, original_values):
|
|
225
|
+
actual = layer.get_weights()[0]
|
|
226
|
+
np.testing.assert_allclose(
|
|
227
|
+
actual,
|
|
228
|
+
expected,
|
|
229
|
+
err_msg=f"Layer '{layer.name}' got wrong weights after loading.",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
149
233
|
if __name__ == "__main__":
|
|
150
234
|
pytest.main([__file__])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/resnet_common.py
RENAMED
|
File without changes
|
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/functional.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/__init__.py
RENAMED
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/_common_blocks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/requires.txt
RENAMED
|
File without changes
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|