text: import os
  import torch
  import torch.backends.cudnn as cudnn
  import torch.nn as nn
  import torch.nn.functional as F
  import logging
  import time
  import math
  
  logger = logging.getLogger(__name__)
  
  def init_torch_seeds(seed=0):
      torch.manual_seed(seed)
  
      # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html
      if seed == 0:  # slower, more reproducible
          cudnn.deterministic = True
          cudnn.benchmark = False
      else:  # faster, less reproducible
          cudnn.deterministic = False
          cudnn.benchmark = True
  
  def select_device(device='', batch_size=None):
      # device = 'cpu' or '0' or '0,1,2,3'
      cpu_request = device.lower() == 'cpu'
      if device and not cpu_request:  # if device requested other than 'cpu'
          os.environ['CUDA_VISIBLE_DEVICES'] = device  # set environment variable
          assert torch.cuda.is_available(), 'CUDA unavailable, invalid device %s requested' % device  # check availablity
  
      cuda = False if cpu_request else torch.cuda.is_available()
      if cuda:
          c = 1024 ** 2  # bytes to MB
          ng = torch.cuda.device_count()
          if ng > 1 and batch_size:  # check that batch_size is compatible with device_count
              assert batch_size % ng == 0, 'batch-size %g not multiple of GPU count %g' % (batch_size, ng)
          x = [torch.cuda.get_device_properties(i) for i in range(ng)]
          s = 'Using CUDA '
          for i in range(0, ng):
              if i == 1:
                  s = ' ' * len(s)
              logger.info("%sdevice%g _CudaDeviceProperties(name='%s', total_memory=%dMB)" %
                          (s, i, x[i].name, x[i].total_memory / c))
      else:
          logger.info('Using CPU')
  
      logger.info('')  # skip a line
      return torch.device('cuda:0' if cuda else 'cpu')
  
  def time_synchronized():
      torch.cuda.synchronize() if torch.cuda.is_available() else None
      return time.time()
  
  def fuse_conv_and_bn(conv, bn):
      # Fuse convolution and batchnorm layers https://tehnokv.com/posts/fusing-batchnorm-and-conv/
  
      # init
      fusedconv = nn.Conv2d(conv.in_channels,
                            conv.out_channels,
                            kernel_size=conv.kernel_size,
                            stride=conv.stride,
                            padding=conv.padding,
                            groups=conv.groups,
                            bias=True).requires_grad_(False).to(conv.weight.device)
  
      # prepare filters
      w_conv = conv.weight.clone().view(conv.out_channels, -1)
      w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var)))
      fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size()))
  
      # prepare spatial bias
      b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias
      b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps))
      fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn)
  
      return fusedconv
  
  def model_info(model, verbose=False):
      # Plots a line-by-line description of a PyTorch model
      n_p = sum(x.numel() for x in model.parameters())  # number parameters
      n_g = sum(x.numel() for x in model.parameters() if x.requires_grad)  # number gradients
      if verbose:
          print('%5s %40s %9s %12s %20s %10s %10s' % ('layer', 'name', 'gradient', 'parameters', 'shape', 'mu', 'sigma'))
          for i, (name, p) in enumerate(model.named_parameters()):
              name = name.replace('module_list.', '')
              print('%5g %40s %9s %12g %20s %10.3g %10.3g' %
                    (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std()))
  
      try:  # FLOPS
          from thop import profile
          flops = profile(deepcopy(model), inputs=(torch.zeros(1, 3, 64, 64),), verbose=False)[0] / 1E9 * 2
          fs = ', %.1f GFLOPS' % (flops * 100)  # 640x640 FLOPS
      except:
          fs = ''
  
      logger.info(
          'Model Summary: %g layers, %g parameters, %g gradients%s' % (len(list(model.parameters())), n_p, n_g, fs))
  
  def scale_img(img, ratio=1.0, same_shape=False):  # img(16,3,256,416), r=ratio
      # scales img(bs,3,y,x) by ratio
      if ratio == 1.0:
          return img
      else:
          h, w = img.shape[2:]
          s = (int(h * ratio), int(w * ratio))  # new size
          img = F.interpolate(img, size=s, mode='bilinear', align_corners=False)  # resize
          if not same_shape:  # pad/crop img
              gs = 32  # (pixels) grid size
              h, w = [math.ceil(x * ratio / gs) * gs for x in (h, w)]
          return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447)  # value = imagenet mean
  
  def initialize_weights(model):
      for m in model.modules():
          t = type(m)
          if t is nn.Conv2d:
              pass  # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
          elif t is nn.BatchNorm2d:
              m.eps = 1e-3
              m.momentum = 0.03
          elif t in [nn.LeakyReLU, nn.ReLU, nn.ReLU6]:
              m.inplace = True
  
  
  def intersect_dicts(da, db, exclude=()):
      # Dictionary intersection of matching keys and shapes, omitting 'exclude' keys, using da values
      return {k: v for k, v in da.items() if k in db and not any(x in k for x in exclude) and v.shape == db[k].shape}
  