Download StarNetPyTorch/convert_tf2_h5.py from MilanMagik/AstroStarDetect: direct link, hf CLI and curl.
- Browser
- Download file 5.08 kB
-
https://huggingface.co/MilanMagik/AstroStarDetect/resolve/main/StarNetPyTorch/convert_tf2_h5.py
- Command line
-
hf download hf://MilanMagik/AstroStarDetect/StarNetPyTorch/convert_tf2_h5.py
-
curl -L -o convert_tf2_h5.py https://huggingface.co/MilanMagik/AstroStarDetect/resolve/main/StarNetPyTorch/convert_tf2_h5.py
5.08 kB
| import argparse | |
| from pathlib import Path | |
| import h5py | |
| import numpy as np | |
| import torch | |
| from model import Generator | |
| def _decode_names(values): | |
| return [value.decode("utf-8") if isinstance(value, bytes) else str(value) for value in values] | |
| def _read_layer_weights(layer_group): | |
| weight_names = _decode_names(layer_group.attrs.get("weight_names", [])) | |
| weights = {} | |
| for weight_name in weight_names: | |
| short_name = weight_name.rsplit("/", 1)[-1].split(":", 1)[0] | |
| try: | |
| weights[short_name] = np.asarray(layer_group[weight_name]) | |
| except KeyError: | |
| relative_name = weight_name.split("/", 1)[-1] | |
| weights[short_name] = np.asarray(layer_group[relative_name]) | |
| return weights | |
| def _load_keras_layers(h5_path): | |
| layers = [] | |
| with h5py.File(h5_path, "r") as handle: | |
| root = handle | |
| if "layer_names" not in root.attrs and "model_weights" in root: | |
| root = root["model_weights"] | |
| layer_names = _decode_names(root.attrs.get("layer_names", [])) | |
| if not layer_names: | |
| raise ValueError( | |
| "Unsupported H5 layout: no Keras layer_names attribute was found. " | |
| "Use a TensorFlow/Keras save_weights H5 file." | |
| ) | |
| for layer_name in layer_names: | |
| layer_group = root[layer_name] | |
| weights = _read_layer_weights(layer_group) | |
| if weights: | |
| layers.append((layer_name, weights)) | |
| return layers | |
| def _copy_conv(state, torch_name, weights, transpose=False): | |
| required = {"kernel", "bias"} | |
| if not required.issubset(weights): | |
| raise ValueError("Layer %s is missing kernel or bias" % torch_name) | |
| kernel = torch.from_numpy(weights["kernel"]).permute(3, 2, 0, 1).contiguous() | |
| if transpose: | |
| state[torch_name + ".weight"] = kernel | |
| else: | |
| state[torch_name + ".weight"] = kernel | |
| state[torch_name + ".bias"] = torch.from_numpy(weights["bias"]) | |
| def _copy_batch_norm(state, torch_name, weights): | |
| names = { | |
| "weight": "gamma", | |
| "bias": "beta", | |
| "running_mean": "moving_mean", | |
| "running_var": "moving_variance", | |
| } | |
| missing = [source for source in names.values() if source not in weights] | |
| if missing: | |
| raise ValueError("Layer %s is missing: %s" % (torch_name, ", ".join(missing))) | |
| for target, source in names.items(): | |
| state[torch_name + "." + target] = torch.from_numpy(weights[source]) | |
| def convert(input_path, output_path, mode): | |
| input_channels = 3 if mode == "RGB" else 1 | |
| layers = _load_keras_layers(input_path) | |
| conv_layers = [weights for name, weights in layers if name.startswith("conv2d") and "transpose" not in name] | |
| deconv_layers = [weights for name, weights in layers if name.startswith("conv2d_transpose")] | |
| batch_norm_layers = [weights for name, weights in layers if name.startswith("batch_normalization")] | |
| if len(conv_layers) != 8 or len(deconv_layers) != 8 or len(batch_norm_layers) != 14: | |
| raise ValueError( | |
| "Unexpected generator layout: found %d Conv2D, %d Conv2DTranspose, and " | |
| "%d BatchNormalization layers; expected 8, 8, and 14." | |
| % (len(conv_layers), len(deconv_layers), len(batch_norm_layers)) | |
| ) | |
| generator = Generator(input_channels=input_channels, batch_norm_eps=1e-3) | |
| state = generator.state_dict() | |
| for index, weights in enumerate(conv_layers): | |
| _copy_conv(state, "down_convs.%d" % index, weights) | |
| for index, weights in enumerate(deconv_layers): | |
| _copy_conv(state, "up_convs.%d" % index, weights, transpose=True) | |
| for index, weights in enumerate(batch_norm_layers[:7]): | |
| _copy_batch_norm(state, "down_bns.%d" % index, weights) | |
| for index, weights in enumerate(batch_norm_layers[7:]): | |
| _copy_batch_norm(state, "up_bns.%d" % index, weights) | |
| generator.load_state_dict(state) | |
| output_path = Path(output_path) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| torch.save( | |
| { | |
| "state_dict": generator.state_dict(), | |
| "mode": mode, | |
| "source": str(Path(input_path)), | |
| }, | |
| output_path, | |
| ) | |
| return output_path | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Convert StarNet TF2 Keras generator H5 weights") | |
| parser.add_argument("input", help="Keras generator H5 file, such as weights_G_Greyscale.h5") | |
| parser.add_argument( | |
| "output", | |
| nargs="?", | |
| default=None, | |
| help="Output .pth file; defaults to the input filename with a .pth suffix", | |
| ) | |
| parser.add_argument("--mode", choices=("RGB", "Greyscale"), default="Greyscale") | |
| args = parser.parse_args() | |
| output = args.output or str(Path(args.input).with_suffix(".pth")) | |
| output_path = convert(args.input, output, args.mode) | |
| print("Saved PyTorch weights: %s" % output_path) | |
| if __name__ == "__main__": | |
| main() | |