CoolFace
Apppublic

MLBench/ReaLens

sourceHugging Facemitupdated 8mo agoView on Hugging Face
0likes
base_model.py307 linesDownload Raw Back to models
1import os2import torch3import torch.distributed as dist4from pathlib import Path5from collections import OrderedDict6from abc import ABC, abstractmethod7from . import networks8 9 10class BaseModel(ABC):11    """This class is an abstract base class (ABC) for models.12    To create a subclass, you need to implement the following five functions:13        -- <__init__>:                      initialize the class; first call BaseModel.__init__(self, opt).14        -- <set_input>:                     unpack data from dataset and apply preprocessing.15        -- <forward>:                       produce intermediate results.16        -- <optimize_parameters>:           calculate losses, gradients, and update network weights.17        -- <modify_commandline_options>:    (optionally) add model-specific options and set default options.18    """19 20    def __init__(self, opt):21        """Initialize the BaseModel class.22 23        Parameters:24            opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions25 26        When creating your custom class, you need to implement your own initialization.27        In this function, you should first call <BaseModel.__init__(self, opt)>28        Then, you need to define four lists:29            -- self.loss_names (str list):          specify the training losses that you want to plot and save.30            -- self.model_names (str list):         define networks used in our training.31            -- self.visual_names (str list):        specify the images that you want to display and save.32            -- self.optimizers (optimizer list):    define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example.33        """34        self.opt = opt35        self.isTrain = opt.isTrain36        self.save_dir = Path(opt.checkpoints_dir) / opt.name  # save all the checkpoints to save_dir37        self.device = opt.device38        # with [scale_width], input images might have different sizes, which hurts the performance of cudnn.benchmark.39        if opt.preprocess != "scale_width":40            torch.backends.cudnn.benchmark = True41        self.loss_names = []42        self.model_names = []43        self.visual_names = []44        self.optimizers = []45        self.image_paths = []46        self.metric = 0  # used for learning rate policy 'plateau'47 48    @staticmethod49    def modify_commandline_options(parser, is_train):50        """Add new model-specific options, and rewrite default values for existing options.51 52        Parameters:53            parser          -- original option parser54            is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options.55 56        Returns:57            the modified parser.58        """59        return parser60 61    @abstractmethod62    def set_input(self, input):63        """Unpack input data from the dataloader and perform necessary pre-processing steps.64 65        Parameters:66            input (dict): includes the data itself and its metadata information.67        """68        pass69 70    @abstractmethod71    def forward(self):72        """Run forward pass; called by both functions <optimize_parameters> and <test>."""73        pass74 75    @abstractmethod76    def optimize_parameters(self):77        """Calculate losses, gradients, and update network weights; called in every training iteration"""78        pass79 80    def setup(self, opt):81        """Load and print networks; create schedulers82 83        Parameters:84            opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions85        """86        # Initialize all networks and load if needed87        for name in self.model_names:88            if isinstance(name, str):89                net = getattr(self, "net" + name)90                net = networks.init_net(net, opt.init_type, opt.init_gain)91 92                # Load networks if needed93                if not self.isTrain or opt.continue_train:94                    load_suffix = f"iter_{opt.load_iter}" if opt.load_iter > 0 else opt.epoch95                    load_filename = f"{load_suffix}_net_{name}.pth"96                    load_path = self.save_dir / load_filename97 98                    if isinstance(net, torch.nn.parallel.DistributedDataParallel):99                        net = net.module100                    print(f"loading the model from {load_path}")101 102                    state_dict = torch.load(load_path, map_location=str(self.device), weights_only=True)103 104                    if hasattr(state_dict, "_metadata"):105                        del state_dict._metadata106 107                    # patch InstanceNorm checkpoints108                    for key in list(state_dict.keys()):109                        self.__patch_instance_norm_state_dict(state_dict, net, key.split("."))110                    net.load_state_dict(state_dict)111 112                # Move network to device113                net.to(self.device)114 115                # Wrap networks with DDP after loading116                if dist.is_initialized():117                    # Check if using syncbatch normalization for DDP118                    if self.opt.norm == "syncbatch":119                        raise ValueError(f"For distributed training, opt.norm must be 'syncbatch' or 'inst', but got '{self.opt.norm}'. " "Please set --norm syncbatch for multi-GPU training.")120 121                    net = torch.nn.parallel.DistributedDataParallel(net, device_ids=[self.device.index])122                    # Sync all processes after DDP wrapping123                    dist.barrier()124 125                setattr(self, "net" + name, net)126 127        self.print_networks(opt.verbose)128 129        if self.isTrain:130            self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers]131 132    def eval(self):133        """Make models eval mode during test time"""134        for name in self.model_names:135            if isinstance(name, str):136                net = getattr(self, "net" + name)137                net.eval()138 139    def test(self):140        """Forward function used in test time.141 142        This function wraps <forward> function in no_grad() so we don't save intermediate steps for backprop143        It also calls <compute_visuals> to produce additional visualization results144        """145        with torch.no_grad():146            self.forward()147            self.compute_visuals()148 149    def compute_visuals(self):150        """Calculate additional output images for visdom and HTML visualization"""151        pass152 153    def get_image_paths(self):154        """Return image paths that are used to load current data"""155        return self.image_paths156 157    def update_learning_rate(self):158        """Update learning rates for all the networks; called at the end of every epoch"""159        old_lr = self.optimizers[0].param_groups[0]["lr"]160        for scheduler in self.schedulers:161            if self.opt.lr_policy == "plateau":162                scheduler.step(self.metric)163            else:164                scheduler.step()165 166        lr = self.optimizers[0].param_groups[0]["lr"]167        print(f"learning rate {old_lr:.7f} -> {lr:.7f}")168 169    def get_current_visuals(self):170        """Return visualization images. train.py will display these images with visdom, and save the images to a HTML"""171        visual_ret = OrderedDict()172        for name in self.visual_names:173            if isinstance(name, str):174                visual_ret[name] = getattr(self, name)175        return visual_ret176 177    def get_current_losses(self):178        """Return traning losses / errors. train.py will print out these errors on console, and save them to a file"""179        errors_ret = OrderedDict()180        for name in self.loss_names:181            if isinstance(name, str):182                errors_ret[name] = float(getattr(self, "loss_" + name))  # float(...) works for both scalar tensor and float number183        return errors_ret184 185    def save_networks(self, epoch):186        """Save all the networks to the disk, unwrapping them first."""187 188        # Only allow the main process (rank 0) to save the checkpoint189        if not dist.is_initialized() or dist.get_rank() == 0:190            for name in self.model_names:191                if isinstance(name, str):192                    save_filename = f"{epoch}_net_{name}.pth"193                    save_path = self.save_dir / save_filename194                    net = getattr(self, "net" + name)195 196                    # 1. First, unwrap from DDP if it exists197                    if hasattr(net, "module"):198                        model_to_save = net.module199                    else:200                        model_to_save = net201 202                    # 2. Second, unwrap from torch.compile if it exists203                    if hasattr(model_to_save, "_orig_mod"):204                        model_to_save = model_to_save._orig_mod205 206                    # 3. Save the final, clean state_dict207                    torch.save(model_to_save.state_dict(), save_path)208 209    def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0):210        """Fix InstanceNorm checkpoints incompatibility (prior to 0.4)"""211        key = keys[i]212        if i + 1 == len(keys):  # at the end, pointing to a parameter/buffer213            if module.__class__.__name__.startswith("InstanceNorm") and (key == "running_mean" or key == "running_var"):214                if getattr(module, key) is None:215                    state_dict.pop(".".join(keys))216            if module.__class__.__name__.startswith("InstanceNorm") and (key == "num_batches_tracked"):217                state_dict.pop(".".join(keys))218        else:219            self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1)220 221    def load_networks(self, epoch):222        """Load all networks from the disk for DDP."""223 224        for name in self.model_names:225            if isinstance(name, str):226                load_filename = f"{epoch}_net_{name}.pth"227                load_path = self.save_dir / load_filename228                net = getattr(self, "net" + name)229 230                if isinstance(net, torch.nn.parallel.DistributedDataParallel):231                    net = net.module232                print(f"loading the model from {load_path}")233 234                state_dict = torch.load(load_path, map_location=str(self.device), weights_only=True)235 236                if hasattr(state_dict, "_metadata"):237                    del state_dict._metadata238 239                # patch InstanceNorm checkpoints240                for key in list(state_dict.keys()):241                    self.__patch_instance_norm_state_dict(state_dict, net, key.split("."))242                net.load_state_dict(state_dict)243 244        # Add a barrier to sync all processes before continuing245        if dist.is_initialized():246            dist.barrier()247 248    def print_networks(self, verbose):249        """Print the total number of parameters in the network and (if verbose) network architecture250 251        Parameters:252            verbose (bool) -- if verbose: print the network architecture253        """254        print("---------- Networks initialized -------------")255        for name in self.model_names:256            if isinstance(name, str):257                net = getattr(self, "net" + name)258                num_params = 0259                for param in net.parameters():260                    num_params += param.numel()261                if verbose:262                    print(net)263                print(f"[Network {name}] Total number of parameters : {num_params / 1e6:.3f} M")264        print("-----------------------------------------------")265 266    def set_requires_grad(self, nets, requires_grad=False):267        """Set requies_grad=Fasle for all the networks to avoid unnecessary computations268        Parameters:269            nets (network list)   -- a list of networks270            requires_grad (bool)  -- whether the networks require gradients or not271        """272        if not isinstance(nets, list):273            nets = [nets]274        for net in nets:275            if net is not None:276                for param in net.parameters():277                    param.requires_grad = requires_grad278 279    def init_networks(self, init_type="normal", init_gain=0.02):280        """Initialize all networks: 1. move to device; 2. initialize weights281 282        Parameters:283            init_type (str) -- initialization method: normal | xavier | kaiming | orthogonal284            init_gain (float) -- scaling factor for normal, xavier and orthogonal285        """286        import os287 288        for name in self.model_names:289            if isinstance(name, str):290                net = getattr(self, "net" + name)291 292                # Move to device293                if torch.cuda.is_available():294                    if "LOCAL_RANK" in os.environ:295                        local_rank = int(os.environ["LOCAL_RANK"])296                        net.to(local_rank)297                        print(f"Initialized network {name} with device cuda:{local_rank}")298                    else:299                        net.to(0)300                        print(f"Initialized network {name} with device cuda:0")301                else:302                    net.to("cpu")303                    print(f"Initialized network {name} with device cpu")304 305                # Initialize weights using networks function306                networks.init_weights(net, init_type, init_gain)307