Slim模型部署多GPU

1、多GPU原理

    单GPU时,思路很简单,前向、后向都在一个GPU上进行,模型参数更新时只涉及一个GPU。多GPU时,有模型并行和数据并行两种情况。模型并行指模型的不同部分在不同GPU上运行。数据并行指不同GPU上训练数据不同,但模型是同一个(相当于是同一个模型的副本)。TensorFlow支持的是数据并行。数据并行的原理:CPU负责梯度平均和参数更新,在GPU上训练模型的副本。多GPU并行计算的过程:

      1)模型副本定义在指定的GPU/CPU上;
      2)对于每一个GPU, 都是从CPU获得数据,前向传播进行计算,得到loss,并计算出梯度;
      3)CPU接到GPU的梯度,取平均值,然后进行梯度更新。

    这个在tf的实现思路如下:
    模型参数保存在一个指定gpu/cpu上,模型参数的副本在不同gpu上,每次训练,提供batch_size*gpu_num数据,并等量拆分成多个batch,分别送入不同GPU。前向在不同gpu上进行,模型参数更新时,将多个GPU后向计算得到的梯度数据进行平均,并在指定GPU/CPU上利用梯度数据更新模型参数。假设有两个GPU(gpu0,gpu1),模型参数实际存放在cpu0上,实际一次训练过程如下图所示:

2、model_deploy.py文件及其用法

    为了能让一个Slim模型在多个GPU上训练更加容易,这个模块提供了一系列帮助函数,比如create_clones()、optimize_clones()、deploy()、gather_clone_loss()、_add_gradients_summaries()、_sum_clones_gradients()等,该模块位于:https://github.com/tensorflow/models/blob/master/research/slim/deployment/model_deploy.py

详细步骤:

(1)创建DeploymentConfig对象:config = model_deploy.DeploymentConfig()

    Deployment类定义的源码如下:

class DeploymentConfig(object):
   ''' 这个配置类描述了如何将一个模型部署在多个单机的多个GPU上,在每个单机上,模型将挥被复制num_clones次
   '''
  def __init__(self,num_clones=1, clone_on_cpu=False,
               replica_id=0, num_replicas=1,num_ps_tasks=0,
               worker_job_name='worker',ps_job_name='ps'):

    参数:
      num_clones : 每个单机部署多少个clone(即部署在多少个GPU)
      clone_on_cpu : 如果为True,则单机中的每个clone将被放在CPU中
      replica_id :   整数,模型所部署的单机的索引,通常是0.  
      num_replicas: 使用多少个单机,通常为1,表示单机部署。此时`worker_device`, `num_ps_tasks`和 `ps_device`这几个参数将被忽略。
      num_ps_tasks: ‘ps’作业(分布式作业)使用多少个单机,如果为0表示不使用单机
      worker_job_name: 默认为“worker”
      ps_job_name:默认为'ps'

    if num_replicas > 1:
      if num_ps_tasks < 1:
        raise ValueError('When using replicas num_ps_tasks must be positive')
    if num_replicas > 1 or num_ps_tasks > 0:
      if not worker_job_name:
        raise ValueError('Must specify worker_job_name when using replicas')
      if not ps_job_name:
        raise ValueError('Must specify ps_job_name when using parameter server')
    if replica_id >= num_replicas:
      raise ValueError('replica_id must be less than num_replicas')
    self._num_clones = num_clones
    self._clone_on_cpu = clone_on_cpu
    self._replica_id = replica_id
    self._num_replicas = num_replicas
    self._num_ps_tasks = num_ps_tasks
    self._ps_device = '/job:' + ps_job_name if num_ps_tasks > 0 else ''
    self._worker_device = '/job:' + worker_job_name if num_ps_tasks > 0 else ''

  @property
  def num_clones(self):
    return self._num_clones
  @property
  def clone_on_cpu(self):
    return self._clone_on_cpu
  @property
  def replica_id(self):
    return self._replica_id
  @property
  def num_replicas(self):
    return self._num_replicas
  @property
  def num_ps_tasks(self):
    return self._num_ps_tasks
  @property
  def ps_device(self):
    return self._ps_device
  @property
  def worker_device(self):
    return self._worker_device

  def caching_device(self):
    """缓存变量的设备    
    Returns:
      如果不需要被缓存则返回None,否则返回设备号
    """
    if self._num_ps_tasks > 0:
      return lambda op: op.device
    else:
      return None

  def clone_device(self, clone_index):
    """根据索引号返回用来创建克隆的设备号
    Args:
      clone_index: 克隆的索引值
    Returns:
       tf.device()的一个合适的值.
    """
    if clone_index >= self._num_clones:
      raise ValueError('clone_index must be less than num_clones')
    device = ''
    if self._num_ps_tasks > 0:
      device += self._worker_device
    if self._clone_on_cpu:
      device += '/device:CPU:0'
    else:
      device += '/device:GPU:%d' % clone_index
    return device

  def clone_scope(self, clone_index):
    """根据索引号返回所创建的克隆的scope名字
    Args:
      clone_index: 克隆的索引号
    Returns:
      tf.name_scope()的一个合适值.
    """
    if clone_index >= self._num_clones:
      raise ValueError('clone_index must be less than num_clones')
    scope = ''
    if self._num_clones > 1:
      scope = 'clone_%d' % clone_index
    return scope

  def optimizer_device(self):
    """模型参数更新的设备,单机时为CPU
    Returns:
      tf.device()的一个合适值
    """
    if self._num_ps_tasks > 0 or self._num_clones > 0:
      return self._worker_device + '/device:CPU:0'
    else:
      return ''

  def inputs_device(self):
    """建立输入的设备,即读取数据的设备,单机时为CPU
    Returns:
      tf.device()的一个合适值
    """
    device = ''
    if self._num_ps_tasks > 0:
      device += self._worker_device
    device += '/device:CPU:0'
    return device

  def variables_device(self):
    """创建模型变量的设备,单机时为CPU
    Returns:
      `tf.device()的一个合适值
    """
    device = ''
    if self._num_ps_tasks > 0:
      device += self._ps_device
    device += '/device:CPU:0'

    class _PSDeviceChooser(object):
      """Slim device chooser for variables when using PS."""

      def __init__(self, device, tasks):
        self._device = device
        self._tasks = tasks
        self._task = 0

      def choose(self, op):
        if op.device:
          return op.device
        node_def = op if isinstance(op, tf.NodeDef) else op.node_def
        if node_def.op.startswith('Variable'):
          t = self._task
          self._task = (self._task + 1) % self._tasks
          d = '%s/task:%d' % (self._device, t)
          return d
        else:
          return op.device

    if not self._num_ps_tasks:
      return device
    else:
      chooser = _PSDeviceChooser(device, self._num_ps_tasks)
      return chooser.choose

(2) 获取数据集的输入数据

  # 定义输入
  with tf.device(config.inputs_device()):
    images, labels = LoadData(...)
    inputs_queue = slim.data.prefetch_queue((images, labels))

(3) 创建模型并clone到多个GPU

with tf.device(config.variables_device()):
      global_step = tf.train.get_or_create_global_step()

      # 定义模型并创建多GPU克隆
      model_fn = build_model
      model_args = ....

      # 创建多个克隆值,config对象、创造模型的函数以及函数的参数
      clones = model_deploy.create_clones(config, model_fn, args=model_args)

其中 create_clones()的源码如下:

Clone = collections.namedtuple('Clone',
                               ['outputs', # Whatever model_fn() returned.
                                'scope',   # The scope used to create it.
                                'device',  # The device used to create.
                               ])

def create_clones(config, model_fn, args=None, kwargs=None):
  """
  返回值是一个命名元组Clone的列表,Clone包装了model_fn函数的返回值、scope和device。
  注意,这里假设被model_fn创建的任何损失被加入到tf.GraphKeys.LOSSES collection中。为了恢复losses、summaries和updata_op,可以使用:
    losses = tf.get_collection(tf.GraphKeys.LOSSES, clone.scope)
    summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, clone.scope)
    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, clone.scope)
  model_fn函数被调用了config.num_clones次,被克隆到多个GPU。
  Args:
    config: DeploymentConfig对象
    model_fn: 一个创建模型的函数,调用方式为:model_fn(*args, **kwargs)
    args: 传递给model_fn的可选的参数列表
    kwargs: 可选参数列表
  Returns:
    命名元组Clone的一个列表
  """
  clones = []
  args = args or []
  kwargs = kwargs or {}
  with slim.arg_scope([slim.model_variable, slim.variable],
                      device=config.variables_device()):
    # 创建克隆
    for i in range(0, config.num_clones):
      with tf.name_scope(config.clone_scope(i)) as clone_scope:
        clone_device = config.clone_device(i)
        with tf.device(clone_device):
          with tf.variable_scope(tf.get_variable_scope(),reuse=True if i > 0 else None):
            outputs = model_fn(*args, **kwargs)
          clones.append(Clone(outputs, clone_scope, clone_device))
  return clones

(4) 设置学习率和创建优化器

with tf.device(config.optimizer_device()):
      learning_rate = ...
      optimizer = tf.train.MomentumOptimizer(learning_rate, momentum)

(5) 计算总的损失计算梯度并进行权重更新

with tf.device(config.variables_device()):
      # 计算总共的损失和每个变量的梯度
      total_loss, grads_and_vars = model_deploy.optimize_clones(clones, optimizer)

      # 创建梯度更新操作
      grad_updates = optimizer.apply_gradients(grads_and_vars, global_step=global_step)

 optimize_clones()方法的源码定义如下:

def _gather_clone_loss(clone, num_clones, regularization_losses):
  sum_loss = None
  # Individual components of the loss that will need summaries.
  clone_loss = None
  regularization_loss = None
  # Compute and aggregate losses on the clone device.
  with tf.device(clone.device):
    all_losses = []
    clone_losses = tf.get_collection(tf.GraphKeys.LOSSES, clone.scope)
    if clone_losses:
      clone_loss = tf.add_n(clone_losses, name='clone_loss')
      if num_clones > 1:
        clone_loss = tf.div(clone_loss, 1.0 * num_clones,
                            name='scaled_clone_loss')
      all_losses.append(clone_loss)
    if regularization_losses:
      regularization_loss = tf.add_n(regularization_losses,
                                     name='regularization_loss')
      all_losses.append(regularization_loss)
    if all_losses:
      sum_loss = tf.add_n(all_losses)
  # Add the summaries out of the clone device block.
  if clone_loss is not None:
    tf.summary.scalar('/'.join(filter(None,['Losses', clone.scope, 'clone_loss'])),
                      clone_loss)
  if regularization_loss is not None:
    tf.summary.scalar('Losses/regularization_loss', regularization_loss)
  return sum_loss

def _optimize_clone(optimizer, clone, num_clones, regularization_losses,
                    **kwargs): 
  sum_loss = _gather_clone_loss(clone, num_clones, regularization_losses)
  clone_grad = None
  if sum_loss is not None:
    with tf.device(clone.device):
      clone_grad = optimizer.compute_gradients(sum_loss, **kwargs)
  return sum_loss, clone_grad

def optimize_clones(clones, optimizer,regularization_losses=None,
                    **kwargs):
  Args:
   clones:由create_clones()函数创建的Clones列表
   optimizer: 一个 `Optimizer`对象
   regularization_losses: (可选)正则化损失列表,如果为None,则可以从tf.GraphKeys.REGULARIZATION_LOSSES获取。如果传入[],则表示忽略正则化损失。
   **kwargs: 可选参数
  Returns:
    一个元素:(total_loss, grads_and_vars).
     - total_loss: 一个Tensor,包含每个Clone的平均损失(包括正则化损失)。
     - grads_and_vars: 一个元组(gradient, variable)列表,包含每个变量总的梯度值。

  """
  grads_and_vars = []
  clones_losses = []
  num_clones = len(clones)
  if regularization_losses is None:
    regularization_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
  for clone in clones:
    with tf.name_scope(clone.scope):
      clone_loss, clone_grad = _optimize_clone(
          optimizer, clone, num_clones, regularization_losses, **kwargs)
      if clone_loss is not None:
        clones_losses.append(clone_loss)
        grads_and_vars.append(clone_grad)
      # Only use regularization_losses for the first clone
      regularization_losses = None
  # Compute the total_loss summing all the clones_losses.
  total_loss = tf.add_n(clones_losses, name='total_loss')
  # Sum the gradients across clones.
  grads_and_vars = _sum_clones_gradients(grads_and_vars)
  return total_loss, grads_and_vars

(6) 开始训练:slim.learning.train() 

  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: gsconv slim-neck 是一种新型的神经网络结构,它可以有效地减轻模型的复杂度,同时提升精度。 一般情况下,神经网络的复杂度越高,模型就越庞大、计算量就越大,同时也越容易出现过拟合的问题。针对这个问题,gsconv slim-neck 结构主要通过两种方式来降低神经网络的复杂度: 一是采用深度可分离卷积层代替传统的卷积层,减少神经网络的参数量和计算量,从而提高模型的训练和运行效率。 二是在神经网络的设计中引入了一种“瓶颈结构”,即在模型的中间位置设置一个较小的通道,保证了更加有效地信息传递和特征提取,同时也带来了更小的计算和存储开销。 此外,gsconv slim-neck 还采用了立体空洞卷积等高阶方法,进一步提高了模型的特征提取能力和准确性,并在训练数据规模较小的情况下表现出了更强的鲁棒性。 综上,gsconv slim-neck 在保证模型准确性的基础上,有效地减轻了模型的复杂度,提高了神经网络的效率,为神经网络的设计和应用提供了新的思路和方法。 ### 回答2: gsconv slim-neck是一种神经网络结构优化方法,旨在减轻模型的复杂度和提高精度。它采用了两种优化策略:gsconv和slim-neck。通过这两种策略的结合运用,可以使神经网络模型更加轻量化和高效,有效避免过拟合等问题,从而提高模型的性能表现。 首先,gsconv(Global Softmax Convolution)是一种结合全局softmax和卷积操作的卷积神经网络(CNN)模块,它可以有效地将特征图的维度降低。具体来说,在该模块中,全局softmax操作会将每个特征图通道的所有值归一化,从而得到每个通道上的注意力分布权重。然后,将该权重作为卷积核来执行卷积操作,将特征图的维度降低到1,从而减少模型中的参数数量和计算复杂度,提高模型的计算效率。 其次,slim-neck是一种细颈设计(Bottlenect)结构,可以有效地缩小神经网络模型的通道数,减少网络所需的存储空间和计算资源,并且有利于控制过拟合。在该结构中,通过将一部分卷积层的输出通道数降到较低的水平来压缩特征图,然后再使用较小的通道数进行卷积操作,从而减少了模型的参数数量和计算量。 综合gsconv和slim-neck策略,gsconv slim-neck 能够在保持精度不变的情况下,大幅度减小神经网络模型的复杂度。通过在CNN模型的不同层次中结合这两种优化策略,可以提高模型的泛化能力和准确性,并且在实际应用中具有更优的性能表现。因此,gsconv slim-neck 作为一种高效的神经网络优化方法,在图像识别、语音识别、自然语言处理等领域中具有广泛的应用前景。 ### 回答3: gsconv slim-neck是一种卷积神经网络模型压缩技术,旨在减轻模型的复杂度同时提升精度。相对于传统的模型压缩技术,如权重剪枝和量化,gsconv slim-neck技术可以更好地完成这个任务。 gsconv slim-neck技术的主要原理是使用一种特殊的卷积操作方式,即group sparse convolution,来减少模型中神经元的数量并提高精度。该操作将神经元分为多个group,其中每个group包含一些神经元,这些神经元权重之间是高度相关的。在进行卷积操作时,只有少数group参与计算,因此模型的复杂度得以减少。 与此同时,gsconv slim-neck技术还使用了一种称为neck卷积的操作,可以将一个大的卷积核分解为多个小的卷积核,从而进一步提高了模型的精度。这种操作类似于一种特殊的卷积神经网络结构——Inception,但是更加高效。 总之,gsconv slim-neck技术通过使用group sparse convolution和neck卷积两种技术手段,成功地实现了模型压缩和精度提升的目标。这项技术在计算机视觉等领域得到了广泛的应用,可以为实际应用场景提供更加高效和准确的神经网络模型

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值