/
githubmirror
/
faceswap
Обзор
Документация
Войти
/
githubmirror
/
faceswap
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v2.2.0
tools/model/model.py
285 строк
10 KB
torzdf
Replace restore tool with model tool
28 июн 2022, 16:27
28 июн 2022, 16:27
eb3c612
Код
Авторство
О чём код?
#!/usr/bin/env python3 """ Tool to restore models from backup """ import logging import os import sys from typing import Any, Tuple, TYPE_CHECKING, Union import numpy as np import tensorflow as tf from lib.model.backup_restore import Backup from lib.utils import get_backend # Import the following libs for custom objects from lib.model import initializers, layers, normalization # noqa # pylint:disable=unused-import from plugins.train.model._base.model import _Inference if get_backend() == "amd": import keras else: from tensorflow import keras if TYPE_CHECKING: import argparse logger = logging.getLogger(__name__) # pylint: disable=invalid-name class Model(): # pylint:disable=too-few-public-methods """ Tool to perform actions on a model file. Parameters ---------- :class:`argparse.Namespace` The command line arguments calling the model tool """ def __init__(self, arguments: 'argparse.Namespace') -> None: logger.debug("Initializing %s: (arguments: '%s'", self.__class__.__name__, arguments) self._configure_tensorflow() self._model_dir = self._check_folder(arguments.model_dir) self._job = self._get_job(arguments) @classmethod def _configure_tensorflow(cls) -> None: """ Disable eager execution and force Tensorflow into CPU mode. """ if get_backend() == "amd": return tf.config.set_visible_devices([], device_type="GPU") tf.compat.v1.disable_eager_execution() @classmethod def _get_job(cls, arguments: "argparse.Namespace") -> Any: """ Get the correct object that holds the selected job. Parameters ---------- arguments: :class:`argparse.Namespace` The command line arguments received for the Model tool which will be used to initiate the selected job Returns ------- class The object that will perform the selected job """ jobs = {"inference": Inference, "nan-scan": NaNScan, "restore": Restore} return jobs[arguments.job](arguments) @classmethod def _check_folder(cls, model_dir: str) -> str: """ Check that the passed in model folder exists and contains a valid model. If the passed in value fails any checks, process exits. Parameters ---------- model_dir: str The model folder to be checked Returns ------- str The confirmed location of the model folder. """ if not os.path.exists(model_dir): logger.error("Model folder does not exist: '%s'", model_dir) sys.exit(1) chkfiles = [fname for fname in os.listdir(model_dir) if fname.endswith(".h5") and not os.path.splitext(fname)[0].endswith("_inference")] if not chkfiles: logger.error("Could not find a model in the supplied folder: '%s'", model_dir) sys.exit(1) if len(chkfiles) > 1: logger.error("More than one model file found in the model folder: '%s'", model_dir) sys.exit(1) model_name = os.path.splitext(chkfiles[0])[0].title() logger.info("%s Model found", model_name) return model_dir def process(self) -> None: """ Call the selected model job.""" self._job.process() class Inference(): # pylint:disable=too-few-public-methods """ Save an inference model from a trained Faceswap model. Parameters ---------- :class:`argparse.Namespace` The command line arguments calling the model tool """ def __init__(self, arguments: "argparse.Namespace") -> None: self._switch = arguments.swap_model self._format = arguments.format self._input_file, self._output_file = self._get_output_file(arguments.model_dir) def _get_output_file(self, model_dir: str) -> Tuple[str, str]: """ Obtain the full path for the output model file/folder Parameters ---------- model_dir: str The full path to the folder containing the Faceswap trained model .h5 file Returns ------- str The full path to the source model file str The full path to the inference model save location """ model_name = next(fname for fname in os.listdir(model_dir) if fname.endswith(".h5")) in_path = os.path.join(model_dir, model_name) logger.debug("Model input path: '%s'", in_path) model_name = f"{os.path.splitext(model_name)[0]}_inference" model_name = f"{model_name}.h5" if self._format == "h5" else model_name out_path = os.path.join(model_dir, model_name) logger.debug("Inference output path: '%s'", out_path) return in_path, out_path def process(self) -> None: """ Run the inference model creation process. """ logger.info("Loading model '%s'", self._input_file) model = keras.models.load_model(self._input_file, compile=False) logger.info("Creating inference model...") inference = _Inference(model, self._switch).model logger.info("Saving to: '%s'", self._output_file) inference.save(self._output_file) class NaNScan(): # pylint:disable=too-few-public-methods """ Tool to scan for NaN and Infs in model weights. Parameters ---------- :class:`argparse.Namespace` The command line arguments calling the model tool """ def __init__(self, arguments: "argparse.Namespace") -> None: logger.debug("Initializing %s: (arguments: '%s'", self.__class__.__name__, arguments) self._model_file = self._get_model_filename(arguments.model_dir) @classmethod def _get_model_filename(cls, model_dir: str) -> str: """ Obtain the full path the model's .h5 file. Parameters ---------- model_dir: str The full path to the folder containing the model file Returns ------- str The full path to the saved model file """ model_file = next(fname for fname in os.listdir(model_dir) if fname.endswith(".h5")) return os.path.join(model_dir, model_file) def _parse_weights(self, layer: Union[keras.models.Model, keras.layers.Layer]) -> dict: """ Recursively pass through sub-models to scan layer weights""" weights = layer.get_weights() logger.debug("Processing weights for layer '%s', length: '%s'", layer.name, len(weights)) if not weights: logger.debug("Skipping layer with no weights: %s", layer.name) return {} if hasattr(layer, "layers"): # Must be a submodel retval = {} for lyr in layer.layers: info = self._parse_weights(lyr) if not info: continue retval[lyr.name] = info return retval nans = sum(np.count_nonzero(np.isnan(w)) for w in weights) infs = sum(np.count_nonzero(np.isinf(w)) for w in weights) if nans + infs == 0: return {} return dict(nans=nans, infs=infs) def _parse_output(self, errors: dict, indent: int = 0) -> None: """ Parse the output of the errors dictionary and print a pretty summary. Parameters ---------- errors: dict The nested dictionary of errors found when parsing the weights indent: int, optional How far should the current printed line be indented. Default: `0` """ for key, val in errors.items(): logline = f"|{'--' * indent} " logline += key.ljust(50 - len(logline)) if isinstance(val, dict) and "nans" not in val: logger.info(logline) self._parse_output(val, indent + 1) elif isinstance(val, dict) and "nans" in val: logline += f"nans: {val['nans']}, infs: {val['infs']}" logger.info(logline.ljust(30)) def process(self) -> None: """ Scan the loaded model for NaNs and Infs and output summary. """ logger.info("Loading model...") model = keras.models.load_model(self._model_file, compile=False) logger.info("Parsing weights for invalid values...") errors = self._parse_weights(model) if not errors: logger.info("No invalid values found in model: '%s'", self._model_file) sys.exit(1) logger.info("Invalid values found in model: %s", self._model_file) self._parse_output(errors) class Restore(): # pylint:disable=too-few-public-methods """ Restore a model from backup. Parameters ---------- :class:`argparse.Namespace` The command line arguments calling the model tool """ def __init__(self, arguments: "argparse.Namespace") -> None: logger.debug("Initializing %s: (arguments: '%s'", self.__class__.__name__, arguments) self._model_dir = arguments.model_dir self._model_name = self._get_model_name() def process(self) -> None: """ Perform the Restore process """ logger.info("Starting Model Restore...") backup = Backup(self._model_dir, self._model_name) backup.restore() logger.info("Completed Model Restore") def _get_model_name(self) -> str: """ Additional checks to make sure that a backup exists in the model location. """ bkfiles = [fname for fname in os.listdir(self._model_dir) if fname.endswith(".bk")] if not bkfiles: logger.error("Could not find any backup files in the supplied folder: '%s'", self._model_dir) sys.exit(1) logger.verbose("Backup files: %s)", bkfiles) # type:ignore model_name = next(fname for fname in bkfiles if fname.endswith(".h5.bk")) return model_name[:-6]