Skip to content

Latest commit

 

History

History
233 lines (174 loc) · 12.6 KB

5.编程框架机理.md

File metadata and controls

233 lines (174 loc) · 12.6 KB

5.1 请调研学习Eager API的使用。使用Eager API实现两个数的加法和矩阵乘法。

TensorFlor Eager

Code:https://github.com/LeiWang1999/AICS-Course/tree/master/Code/5.1.Eager.tensorflow

5.2 现有常见的编程框架的执行模式分为静态图模式和动态图模式,说明这两种执行模式各有什么优缺点。

静态图和动态图的区别在于,静态图需要我们事先定义好一张计算图并进行编译,之后在运行的过程中我们是对同一张计算图重复运算,不能更改计算图的内容。而动态图则在使用时创建。

静态图只需要编译一次,重复使用,这在部署上很实用,比如可以在磁盘中序列化,保存整个网络的结构,可以重载,而动态图则需要重复之前的代码。但编写静态图的程序需要使用特定的语法,增加了学习的成本,动态图可以直接使用Python语法,并且在调试过程中方便Debug。

5.3 使用GPU计算时,试分析在单机单卡、单机多卡、多机多卡的设备下训练卷积神经网络流程上的区别。其中哪些步骤是可以并行的,哪些步骤是必须串行的?

单GPU训练 一般代码比较简单,并且能满足我们的基本需求,通常做法是设定变量CUDA_VISIBLE_DEVICES的值为某一块GPU来Mask我们机器上的GPU设备,虽然有时当我们忘了设定该变量时程序会自动占用所有的GPU资源,但如果没有相应的代码去分配掌控GPU资源的使用的话,程序还是只会利用到第一张卡的计算资源,其他的资源则仅是占用浪费状态。

多GPU训练 则可以从两个方面提升我们模型训练的上限:1. 超过单卡显存上限的模型大小, 2. 更大的Batch Size和更快训练速度。相应的,目前各大主流框架的多GPU训练一般存在两种模式:

  • 模型并行 :分布式系统中的不同GPU负责网络模型的不同部分,进而可以 构建超过单卡显存容量大小的模型 。比如,可以将神经网络的不同层分配到不同的GPU设备,或者将不同的参数变量分配到不同的GPU设备。
  • 数据并行 :不同的 GPU设备有同一模型的多个副本,将数据分片并分配到每个GPU上,然后将所有GPU的计算结果按照某种方式合并,进而可以增加训练数据的Batch Size

多机多卡相比较于单机多卡,其使得模型训练的上限进一步突破。一般我们一台服务器只支持8张GPU卡,而采用分布式的多机多卡训练方式,可以将几十甚至几百台服务器调度起来一起训练一个模型。

但相比于单机多卡,多机多卡分布式训练方式的配置更复杂一些,不仅要保证多台机器之间是可以互相通信的,还需要配置不同机器之间的角色以及不同机器之间梯度传递。

摘录自:https://zhuanlan.zhihu.com/p/70312627

5.4 查看TensorFlow源码,在python/keras中,查找关于ImageNet数据集数据预处理相关的代码,学习几种常用的数据预处理方法,并列举出keras里实现的数据预处理方法。

首先从官方的repo里pull v1的代码,这里我用的1.15:

git clone https://github.com/tensorflow/tensorflow.git --branch v1.15.0 --depth 1

之后我们发现,其代码在applications/imagenet_utils.py

@keras_export('keras.applications.imagenet_utils.preprocess_input')
@keras_modules_injection
def preprocess_input(*args, **kwargs):
  return imagenet_utils.preprocess_input(*args, **kwargs)

调用是keras的api:

def preprocess_input(x, data_format=None, mode='caffe', **kwargs):
    """Preprocesses a tensor or Numpy array encoding a batch of images.

    # Arguments
        x: Input Numpy or symbolic tensor, 3D or 4D.
            The preprocessed data is written over the input data
            if the data types are compatible. To avoid this
            behaviour, `numpy.copy(x)` can be used.
        data_format: Data format of the image tensor/array.
        mode: One of "caffe", "tf" or "torch".
            - caffe: will convert the images from RGB to BGR,
                then will zero-center each color channel with
                respect to the ImageNet dataset,
                without scaling.
            - tf: will scale pixels between -1 and 1,
                sample-wise.
            - torch: will scale pixels between 0 and 1 and then
                will normalize each channel with respect to the
                ImageNet dataset.

    # Returns
        Preprocessed tensor or Numpy array.

    # Raises
        ValueError: In case of unknown `data_format` argument.
    """
    backend, _, _, _ = get_submodules_from_kwargs(kwargs)

    if data_format is None:
        data_format = backend.image_data_format()
    if data_format not in {'channels_first', 'channels_last'}:
        raise ValueError('Unknown data_format ' + str(data_format))

    if isinstance(x, np.ndarray):
        return _preprocess_numpy_input(x, data_format=data_format,
                                       mode=mode, **kwargs)
    else:
        return _preprocess_symbolic_input(x, data_format=data_format,
                                          mode=mode, **kwargs)

对于图像的预处理方法,在习题4.6里已经进行过学习了,而在keras/preprocessing目录下,有分别针对image、sequence、text的预处理方案,例如:RNN网络容易出现反向传播过程中的梯度问题。主要原因是我们通常给RNN的参数为有限的序列。为了实现的简便,keras只能接受长度相同的序列输入。因此如果目前序列长度参差不齐,这时需要使用pad_sequences()。该函数是将序列转化为经过填充以后的一个新序列,等等。

5.5 查看TensorFlow源码,在python/ops中,查找涉及注册sin算子梯度计算和maxpool算子梯度计算的代码,查看相关文件里注册其他算子的代码,学习了解注册Python层算子。

math_grad.py里可以找到sin算子梯度计算的代码:

@ops.RegisterGradient("Sin")
def _SinGrad(op, grad):
  """Returns grad * cos(x)."""
  x = op.inputs[0]
  with ops.control_dependencies([grad]):
    x = math_ops.conj(x)
    return grad * math_ops.cos(x)

而在nn_grad.py里,有maxpool的梯度计算注册。

至于算子注册,官方文档里有一页:https://www.tensorflow.org/guide/create_op?hl=zh-cn

5.6 查看TensorFlow源码,在core/ops中,查找涉及conv算子的代码,请简述算子注册的流程。

nn_ops.cc中有conv算子的注册流程,其中包括了一系列conv算子,例如Conv2D、Conv2DBackpropInput、FusedConv等等。

算子注册的流程依然可以见:https://www.tensorflow.org/guide/create_op?hl=zh-cn

5.7 查看TensorFlow源码,在core/kernel中,查找涉及conv算子的代码,请简述卷积的具体实现。

conv_ops.cc里,我们看到如下的函数:

template <typename Device, typename T>
class Conv2DOp : public BinaryOp<T> {
 public:
  explicit Conv2DOp(OpKernelConstruction* context) : BinaryOp<T>(context) {
    // 这边做条件检查和验证参数
    OP_REQUIRES_OK(context, InitConv2DParameters(context, &params_));

    OP_REQUIRES_OK(context, context->GetAttr("use_cudnn_on_gpu", &use_cudnn_));
    use_cudnn_ &= CanUseCudnn();
    cudnn_use_autotune_ = CudnnUseAutotune();
  }

  void Compute(OpKernelContext* context) override {
    // Input tensor is of the following dimensions:
    // [ batch, in_rows, in_cols, in_depth ]
    // 获取输入的参数
    const Tensor& input = context->input(0);

    // Input filter is of the following dimensions:
    // [ filter_rows, filter_cols, in_depth, out_depth]
    const Tensor& filter = context->input(1);

    Conv2DDimensions dimensions;
    OP_REQUIRES_OK(context,
                   ComputeConv2DDimension(params_, input, filter, &dimensions));

    TensorShape out_shape = ShapeFromFormat(
        params_.data_format, dimensions.batch, dimensions.out_rows,
        dimensions.out_cols, dimensions.out_depth);

    // Output tensor is of the following dimensions:
    // [ in_batch, out_rows, out_cols, out_depth ]
    Tensor* output = nullptr;
    OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &output));

    VLOG(2) << "Conv2D: in_depth = " << dimensions.in_depth
            << ", patch_depth = " << dimensions.patch_depth
            << ", input_cols = " << dimensions.input_cols
            << ", filter_cols = " << dimensions.filter_cols
            << ", input_rows = " << dimensions.input_rows
            << ", filter_rows = " << dimensions.filter_rows
            << ", stride_rows = " << dimensions.stride_rows
            << ", stride_cols = " << dimensions.stride_cols
            << ", dilation_rows = " << dimensions.dilation_rows
            << ", dilation_cols = " << dimensions.dilation_cols
            << ", out_depth = " << dimensions.out_depth;

    // If there is nothing to compute, return.
    if (out_shape.num_elements() == 0) {
      return;
    }

    // 检测是否是指定平台
#ifdef TENSORFLOW_USE_LIBXSMM_CONVOLUTIONS
    if (params_.padding != EXPLICIT &&
        LaunchXsmmConvOp<Device, T>::Run(
            context, input, filter, dimensions.batch, dimensions.input_rows,
            dimensions.input_cols, dimensions.in_depth, dimensions.filter_rows,
            dimensions.filter_cols, dimensions.pad_rows_before,
            dimensions.pad_cols_before, dimensions.out_rows,
            dimensions.out_cols, dimensions.out_depth, dimensions.dilation_rows,
            dimensions.dilation_cols, dimensions.stride_rows,
            dimensions.stride_cols, output, params_.data_format)) {
      return;
    }
#endif

    // 进行运算
    if (params_.padding != EXPLICIT &&
        LaunchDeepConvOp<Device, T>::Run(
            context, input, filter, dimensions.batch, dimensions.input_rows,
            dimensions.input_cols, dimensions.in_depth, dimensions.filter_rows,
            dimensions.filter_cols, dimensions.pad_rows_before,
            dimensions.pad_cols_before, dimensions.out_rows,
            dimensions.out_cols, dimensions.out_depth, dimensions.dilation_rows,
            dimensions.dilation_cols, dimensions.stride_rows,
            dimensions.stride_cols, output, params_.data_format)) {
      return;
    }

    launcher_(context, use_cudnn_, cudnn_use_autotune_, input, filter,
              dimensions.dilation_rows, dimensions.dilation_cols,
              dimensions.stride_rows, dimensions.stride_cols, params_.padding,
              params_.explicit_paddings, output, params_.data_format);
  }

 private:
  Conv2DParameters params_;
  bool use_cudnn_;
  bool cudnn_use_autotune_;

  LaunchConv2DOp<Device, T> launcher_;

  TF_DISALLOW_COPY_AND_ASSIGN(Conv2DOp);
};

5.8 TensorFlow使用SWIG(Simplified Wrapper and Interface Generator),使得Python语言能调用底层C/C++的接口。学习了解SWIG的基本原理,并在源码中找到和SWIG有关的部分。请列出SWIG的一个使用实例。

可以在这里参考到SWIG的使用方法和基本原理:https://www.cnblogs.com/xuruilong100/tag/SWIG%203%20中文手册/

例如,在tensorflow/python目录下的tensorflow.i文件,引入了各个模块的SWIG接口文件。

5.9 现在常用的几种机器学习框架均支持混合精度(Mixed Precision)训练方法,该方法采用半精度浮点做正向传播计算,使用单精度浮点做反向传播计算,在训练时需要同时存储半精度和单精度两份数据。调研了解Mixed Precision的具体实现方法,并借鉴此思想,简述如何实现稀疏卷积神经网络模型的训练。注:稀疏卷积神经网络采用稠密矩阵或者稀疏矩阵方法存储均可。

一文搞懂神经网络混合精度训练:https://zhuanlan.zhihu.com/p/84219777

这个训练方法(不了解了,不应该对训练出来的模型直接做稀疏化吗?

5.10 使用TF_CPP_MIN_VLOG_LEVEL环境变量,设置级别为3。试运行课本中数据流图剪枝相关程序,查看并分析输出日志。

5.11 试分析算子融合比非融合提高计算效率的原因。在常见的分类网络中,算子融合对具有哪些特征的网络带来的加速比更大?

5.12 在MNIST数据集上,不使用常见的机器学习框架,可以借助Numpy等计算库,实现一个三层的全连接网络的预测与训练。进一步地,使用习题5.9中的方法,实现一个稀疏全连接网络的训练,建议每一层的稀疏度为50%,且稀疏度可以随着训练过程从0%逐渐到达50%。注:卷积层和全连接层的稀疏度指权重中0元素的占比。