Code:https://github.com/LeiWang1999/AICS-Course/tree/master/Code/5.1.Eager.tensorflow
静态图和动态图的区别在于,静态图需要我们事先定义好一张计算图并进行编译,之后在运行的过程中我们是对同一张计算图重复运算,不能更改计算图的内容。而动态图则在使用时创建。
静态图只需要编译一次,重复使用,这在部署上很实用,比如可以在磁盘中序列化,保存整个网络的结构,可以重载,而动态图则需要重复之前的代码。但编写静态图的程序需要使用特定的语法,增加了学习的成本,动态图可以直接使用Python语法,并且在调试过程中方便Debug。
单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
首先从官方的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()。该函数是将序列转化为经过填充以后的一个新序列,等等。
在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
在nn_ops.cc
中有conv算子的注册流程,其中包括了一系列conv算子,例如Conv2D、Conv2DBackpropInput、FusedConv等等。
算子注册的流程依然可以见:https://www.tensorflow.org/guide/create_op?hl=zh-cn
在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, ¶ms_));
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
这个训练方法(不了解了,不应该对训练出来的模型直接做稀疏化吗?