本篇文章将对mxnet的Dropout操作进行详细说明, 源码见src/operator/dropout-inl.h. 现将源码dropout-inl.h.及注释贴上. 源码的注释都是笔者自己写的, 有分析不对的地方网各位读者加以指正. 只把层的参数部分, 前向传播和反向传播部分贴上.

/*!* Copyright (c) 2015 by Contributors* \file dropout-inl.h* \brief* \author
*/#ifndef MXNET_OPERATOR_DROPOUT_INL_H_
#define MXNET_OPERATOR_DROPOUT_INL_H_ // 定义宏 MXNET_OPERATOR_DROPOUT_INL_H_.
#include <dmlc/logging.h>
#include <dmlc/parameter.h>
#include <mxnet/operator.h>
#include <map>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
#include "./operator_common.h" // src/operator下, mxnet的层一些常用的属性.
#include "./mshadow_op.h" // src/operator下, 定义了一些结构体. 这些结构体用来接收数据实现某些层的前向输出和反向输出, 如激活函数
// 层有softplus, softplus_grad. 一个计算前向的输出, 一个计算反向的输出. #if defined(USE_STATIC_MKL) && defined(_OPENMP)
#include <omp.h> // OpenMP头文件.
#include <sched.h>#include <mkl_vml_functions.h>
#include <mkl_vsl.h> // MKL的一些头文件.
#endif  // USE_MKL && _OPENMP // 是否使用MKL和OPENMP. 在make mxnet的时候, BLAS库使用的是OpenBLAS, 并不是MKL.
// 即 USE_BLAS = openblas, 所以USE_STATIC_MKL = NONE; 而且, USE_NNPACK = 0; USE_MKL2017 = 0; USE_OPENMP = 1.
// defined(USE_STATIC_MKL) && defined(_OPENMP)即: 是否定义了宏 USE_STATIC_MKL 和 _OPENMP. namespace dropout {
enum DropoutOpInputs {kData}; // Dropout层的输入, 只有数据kData.
enum DropoutOpOutputs {kOut, kMask}; // Dropout层的输出有两个: 输出数据kOut和kMask. 0和1.
enum DropoutOpForwardResource {kRandom}; // Dropout层前向传播资源, kRandom.
/*
有些操作需要额外的内存作为工作空间进行计算, 比如说BatchNormBackward. 这种情况下,
系统最好可以对这部分内存进行管理, 这样系统可以做一些优化, 比如说内存的重复利用.
struct ResourceRequest {enum Type {kRandom,  // get an mshadow::Random<xpu> objectkTempSpace,  // request temporay space};Type type;
};
*/
}  // namespace dropoutnamespace mxnet {
namespace op {#if defined(USE_STATIC_MKL) && defined(_OPENMP)
static void bernoulli_generate(int n, double p, int* r) {int seed = 17 + rand_r() % 4096;int nthr = omp_get_max_threads();
# pragma omp parallel num_threads(nthr){const int ithr = omp_get_thread_num();const int avg_amount = (n + nthr - 1) / nthr;const int my_offset = ithr * avg_amount;const int my_amount = std::min(my_offset + avg_amount, n) - my_offset;if (my_amount > 0) {VSLStreamStatePtr stream;vslNewStream(&stream, VSL_BRNG_MCG31, seed);vslSkipAheadStream(stream, my_offset);viRngBernoulli(VSL_RNG_METHOD_BERNOULLI_ICDF, stream, my_amount,r + my_offset, p);vslDeleteStream(&stream);}}
}
#endif  // USE_MKL && _OPENMP // 是否使用MKL和OPENMP.struct DropoutParam : public dmlc::Parameter<DropoutParam> { // Dropout层的参数设置和描述. float p; // p是在训练过程中激活/抑制结点状态的概率值. DMLC_DECLARE_PARAMETER(DropoutParam) {DMLC_DECLARE_FIELD(p).set_default(0.5) // p的默认值是0.5(set_default).  .set_range(0, 1) // set_range设置参数p的变化范围, 是0-1.  .describe("Fraction of the input that gets dropped out at training time"); // describe描述参数的用途, 字符串. }
};  // struct DropoutParamtemplate<typename xpu, typename DType>
class DropoutOp : public Operator { // Dropout操作类DropoutOp. 模板类这有两个模板参数xpu(cpu or gpu)和DType(float). public:explicit DropoutOp(DropoutParam param) {// C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的. param是参数类// 的对象, 利用param来访问Dropout的参数p. this->pkeep_ = 1.0f - param.p; // pkeep_是real_t型的变量. real_t定义见dmlc-core/include/dmlc/data.h.// typedef float real_t; // 另外data.h中还有index_t的定义: typedef unsigned index_t; the unsigned integer type // pkeep_ = 1.0f - p. }virtual void Forward(const OpContext &ctx,const std::vector<TBlob> &in_data,const std::vector<OpReqType> &req,const std::vector<TBlob> &out_data,const std::vector<TBlob> &aux_states) {/*前向操作, 虚函数. 函数的实现在类中定义. 不需要返回值. 本层为第 l 层. in_data: 本层输入data, 只有上层的输入.req: 数据操作模式. out_data: 本层输出, out. 在训练的时候本层输出有两个.  */using namespace mshadow;using namespace mshadow::expr;CHECK_EQ(in_data.size(), 1); // in_data容器大小为1, 即Dropout层的输入参数只有数据. if (ctx.is_train) {CHECK_EQ(out_data.size(), 2);}/*ctx是OpContext结构体定义的成员. OpContext结构体定义见include/mxnet/operator.h. 利用ctx成员访问结构变量is_train:int is_train; // operator是在进行 train 还是 test (is_train); */Stream<xpu> *s = ctx.get_stream<xpu>(); // operator在哪个device上运行Tensor<xpu, 2, DType> data = in_data[dropout::kData].FlatTo2D<xpu, DType>(s);Tensor<xpu, 2, DType> out = out_data[dropout::kOut].FlatTo2D<xpu, DType>(s);/*将in_data[dropout::kData]输入数据利用FlatTo2D拉成2维的张量data; 本层(第l层)的输入. 定义out_data[dropout::kOut]输出数据利用FlatTo2D拉成2维的张量out. 本层(第l层)的输出. */if (ctx.is_train) { // 网络在训练阶段. Tensor<xpu, 2, DType> mask = out_data[dropout::kMask].FlatTo2D<xpu, DType>(s);/*网络在训练阶段时, out_data容器的大小是2. 一个数本层的输出数据, 一个是kMask. 义out_data[dropout::kMask]输出数据利用FlatTo2D拉成2维的张量mask. mask可以这样理解, 在Dropout层, 对输入结点多加了一道概率流程:原来结点的输入值是 yi^(l), 加上概率之后变为 ri^(l) * yi^(l), ri^(l) ~ Bernoulli(p). 即Dropout层就可以看做是对网络的输入数据data加上了一个概率值. 因为 ri^(l) ~ Bernoulli(p), 即0-1分布, 所以该结点可能激活可能抑制, 也因此减小了网络的规模, 但是网络的实际参数数目是不变的. (改变的是输入的data, 并不是连接的参数. 以一定的概率抑制/激活该结点). 因此, mask扮演的就是 ri^(l) 的角色, 即网络第l层的每个结点的概率值, 得到了mask后, 再和本层(第l层)的输入数据data进行相乘即可.   */#if defined(USE_STATIC_MKL) && defined(_OPENMP) // USE_MKL && _OPENMP // 使用MKL和OPENMP.DType* outptr = out.dptr_;DType* dataptr = data.dptr_;int* maskptr = reinterpret_cast<int*>(mask.dptr_);int count = mask.shape_[0]*mask.shape_[1];bernoulli_generate(count, this->pkeep_, maskptr);#pragma omp parallel for // OPENMP并行 for (int i = 0; i < count; ++i) {outptr[i] = dataptr[i] * maskptr[i];}
#else // 不使用MKL和OPENMP. Random<xpu> *prnd = ctx.requested[dropout::kRandom].get_random<xpu, real_t>(s);/*OpContext: 结构体, 定义在include/mxnet/operator.h中, 该结构体可以记录操作在前向和后向传播中的信息. ctx是结构体OpContext定义的对象, requested是OPContext结构体下的函数:// brief Resources requested by the operatorstd::vector<Resource> requested; // 用来返回操作所需的资源. ctx.requested返回的是一个向量容器, 我们需要的只是kRandom的资源配置, 即一个随机操作资源. ctx.requested[dropout::kRandom]就是一个Resource的对象. 再调用get_random函数.Resource结构体是mxnet操作所需资源结构体, 和NDArray类似. NDArray是一个多维的数组对象.get_random函数定义见: include/mxnet/resource.h下: get_random函数是定义在Resource结构体下的函数: template<typename xpu, typename DType>inline mshadow::Random<xpu, DType>* get_random(mshadow::Stream<xpu> *stream) get_random是随机数生成器. stream是device流; 返回一个随机数生成器, 类型是 mshadow::Random<xpu, DType>* . real_t即float, 即DType.利用ctx获取kRandom所需的资源对象, 在调用get_random得到一个随机数生成器, *prnd即是一个随机数生成器. *prnd是在device s下, real_t类型的随机数生成器.   */mask = tcast<DType>(F<mshadow_op::threshold>(prnd->uniform(mask.shape_), pkeep_) * (1.0f / pkeep_));/*mask扮演的就是 ri^(l) 的角色, 即网络第l层的每个结点的概率值. 现在来获取mask的值. mask是一个2维的张量, 即矩阵. 因为data是2维的张量, 所以mask也是2维的张量.均匀采样, 采样概率是结点状态抑制的概率, 根据这个概率来抑制连接, 然后把mask全部除以pkeep_. 因此这里Dropout的概率值p是结点是抑制状态的概率值, 1-p即激活状态. 根据Dropout的理论知识, 在test/predict时, 每个weight要激活状态的概率值, 即1-p. 这样把mask全部除以pkeep_, 在test/predict的时候就不需要乘以 1-p 了. 首先来看F<mshadow_op::threshold>(prnd->uniform(mask.shape_), pkeep_):mshadow用于表达式操作的类(DotExp, BinaryMapExp, UnaryMapExp):BinaryMapExp(二叉图)是双目运算的表达式类, 在BinaryMapExp类下有F函数F< OP >(lhs, rhs)描述了一个双目运算;DotExp是做点乘的类, 其中最常用的就是dot函数;UnaryMapExp类是单目运算符的表达式类, 在UnaryMapExp类下有F函数.这里, F<mshadow_op::threshold>(prnd->uniform(mask.shape_), pkeep_)是一个双目运算符. F< OP >(lhs, rhs)中的OP就是操作符,即lhs和rhs做什么运算, 这里OP是mshadow_op::threshold, mshadow_op::threshold是定义在src/operator/mshadow_op.h下:threshold操作是mshadow_op.h下的结构体, threshold是用来获取Bernoulli mask的. 即threshold就是专门来做Dropout的.threshold操作如下: 传入参数a和b, 返回 a < b ? DType(1.0f) : DType(0.0f). 这里a是prnd->uniform(mask.shape_), b是pkeep_即结点抑制状态概率. prnd->uniform(mask.shape_)是均匀采样, uniform函数定义见: mshadow/mshadow/random.h 143行. uniform在类class Random<cpu, DType>下, 而prnd是Random类的对象, 所以可以引用uniform函数.   template<int dim>inline expr::ReshapeExp<Tensor<cpu, 1, DType>, DType, dim, 1> uniform(Shape<dim> shape). shape是Tensor的shape, 这里即mask.shape_, shape_即代表一个Tensor的shape. dim是tensor的维数, 这里是2, 即张量的维数是2. uniform函数是[0, 1]的均匀分布, 在[0, 1]间为1, 其余为0. 将 prnd->uniform(mask.shape_) 输出一下:其类型是mshadow::expr::ReshapeExp<mshadow::Tensor<mshadow::cpu, 1, float>, float, 2, 1>.   prnd->uniform(mask.shape_)是一个1维的张量, 因此可以当做标量使用, 因此, a是prnd->uniform(mask.shape_), 即a可以是一个标量. ----------------------------------------------------------------------------------------------------------------------- tcast操作, 该函数定义见: mshadow/mshadow/expression.h 108行. template<typename DstDType, typename SrcDType, typename EType, int etype>inline TypecastExp<DstDType, SrcDType, EType, (etype|type::kMapper)> tcast(const Exp<EType, SrcDType, etype> &exp){...}.建立一个标量表达式. */       Assign(out, req[dropout::kOut], data * mask);/*Assign赋值操作, out是本层(第l层)的输出, req是数据操作模式, exp是data * mask. exp即在数据上加了一道概率程序, 将结点的数据值和概率值相乘. 概率服从伯努利分布, 即0-1分布. */
#endif  // USE_MKL && _OPENMP} else {Assign(out, req[dropout::kOut], F<mshadow_op::identity>(data));/*如果不是训练阶段, 就不需要mask了, 因为在训练阶段生成mask的时候, 部除以pkeep_了, 因此在test/predict阶段, 网络的weight就不需要再乘 1 - p了. 因此, exp就是data.F<mshadow_op::identity>(data)是一个单目运算符, 运算符是mshadow_op::identity, identity这个结构体实现的操作是输入DType a,返回DType a. 即输入等于输出. 将data赋值给本层(第l层)输出out. */}}virtual void Backward(const OpContext &ctx,const std::vector<TBlob> &out_grad,const std::vector<TBlob> &in_data,const std::vector<TBlob> &out_data,const std::vector<OpReqType> &req,const std::vector<TBlob> &in_grad,const std::vector<TBlob> &aux_states) {/*Dropout层(第l层)没有权重和偏置, 因此要计算的是损失J关在Dropout层(第l层)的残差.!!!!!!!!!!!!!!!!梯度可以看做是损失J关于层参数的导数, 残差可以看做是损失J关于层输入的导数!!!!!!!!!!!!!!!!!!!!!!!!!!!! in_grad输出残差参数, 向量容器, 每个元素的类型是TBlob. 本层(第l层)的.out_grad输入残差参数, 向量容器, 每个元素的类型是TBlob. 上一层(第l + 1层)的残差, 计算本层的残差. in_data输入参数, 向量容器, 每个元素的类型是TBlob. 本层(第l层)的输入.  out_data输出参数, 向量容器, 每个元素的类型是TBlob. 本层(第l层)的输出.  req: 数据操作模式, 向量数组. 元素类型是OpReqType.*/using namespace mshadow;using namespace mshadow::expr;CHECK_EQ(out_grad.size(), 1);CHECK_EQ(in_grad.size(), 1);/*Dropout层(第l层)的out_grad, in_grad容器大小为1. 即只有输入的残差(第l + 1层)的残差, 输出残差(第l层的残差).  */Stream<xpu> *s = ctx.get_stream<xpu>();Tensor<xpu, 2, DType> grad = out_grad[dropout::kOut].FlatTo2D<xpu, DType>(s);Tensor<xpu, 2, DType> mask = out_data[dropout::kMask].FlatTo2D<xpu, DType>(s);Tensor<xpu, 2, DType> gdata = in_grad[dropout::kData].FlatTo2D<xpu, DType>(s);/*Dropout为第l层. 将第l + 1层的残差out_grad[0]利用FlatTo2D函数拉成2维的张量. 即残差和数据是一样的, 是2维的. grad.将第l层的输出out_data[1]利用FlatTo2D函数拉成2维的张量. mask. out_data容器大小为2, 即一个是本层的输出out_data[0], 一个是Dropout层的mask out_data[1]. 定义本层(第l层)的残差是2维的张量. gdata.  */#if defined(USE_STATIC_MKL) && defined(_OPENMP)DType* ingradptr = gdata.dptr_;DType* outgradptr = grad.dptr_;int* maskptr = reinterpret_cast<int*>(mask.dptr_);int count = mask.shape_[0]*mask.shape_[1];#pragma omp parallel forfor (int i = 0; i < count; ++i) {ingradptr[i] = outgradptr[i] * maskptr[i];}
#else  // USE_MKL && _OPENMP 使用MKL和OPENMP. 本质和不使用MKL和OPENMP时的反向操作是一样的, 只是使用MKL和OPENMP时, 先用:/*DType* 定义float*的数组ingradptr(本层残差), outgradptr(上一层残差)和int*的数组maskptr(本层mask). 然后:ingradptr[i] = outgradptr[i] * maskptr[i]; conut = mask.shape_[0]*mask.shape_[1];即mask矩阵的高度和宽度乘积.            */ Assign(gdata, req[dropout::kData], grad * mask);/*不使用MKL和OPENMP时, 本层(第l层)的残差gdata = grad * mask, 即上一层(第l + 1层)的残差 * 本层(第l层)的mask.  */
#endif  // USE_MKL && _OPENMP 不使用MKL和OPENMP.  }private:real_t pkeep_;
};  // class DropoutOp

Operators in MXNet-Dropout相关推荐

  1. mxnet dropout 层阅读和测试

    (1)mxnet 测试 dropout的层函数: mxnet.symbol.Dropout(data=None, p=_Null, mode=_Null, axes=_Null, name=None, ...

  2. CNN结构:MXNet设计和实现简介

    对原文有大量修改,如有疑惑,请移步原文. 参考链接:MXNet设计和实现简介 文章翻译于:https://mxnet.incubator.apache.org/architecture/index.h ...

  3. 【MXNet学习16】在MXNet中使用Dropout

    1.Dropout层应该加在什么地方 做分类的时候,Dropout 层一般加在全连接层后(输出层的全连接层后不用drop了),防止过拟合,提升模型泛化能力,而很少见到卷积层后接Dropout (原因主 ...

  4. 深度学习框架哪家强?MXNet称霸CNN、RNN和情感分析,TensorFlow仅擅长推断特征提取

    深度学习框架哪家强:TensorFlow?Caffe?MXNet?Keras?PyTorch?对于这几大框架在运行各项深度任务时的性能差异如何,各位读者不免会有所好奇. 微软数据科学家Ilia Kar ...

  5. MXNet学习:试用卷积-训练CIFAR-10数据集

    第一次用卷积,看的别人的模型跑的CIFAR-10,不过吐槽一下...我觉着我的965m加速之后比我的cpu算起来没快多少..正确率64%的样子,没达到模型里说的75%,不知道问题出在哪里 import ...

  6. Mxnet TensorRT

    Optimizing Deep Learning Computation Graphs with TensorRT CUDA9.0或9.2 Pascal或更新架构的显卡 下载并安装TensorRT库 ...

  7. PaddlePaddle, TensorFlow, MXNet, Caffe2 , PyTorch五大深度学习框架2017-10最新评测

    前言 本文将是2017下半年以来,最新也是最全的一个深度学习框架评测.这里的评测并不是简单的使用评测,我们将用这五个框架共同完成一个深度学习任务,从框架使用的易用性.训练的速度.数据预处理的繁琐程度, ...

  8. 分布式机器学习框架:MxNet

    MxNet官网: http://mxnet.readthedocs.io/en/latest/ 前言: caffe是很优秀的dl平台.影响了后面很多相关框架. cxxnet借鉴了很多caffe的思想. ...

  9. mxnet(gluon)—— 模型、数据集、损失函数、优化子等类、接口大全

    1. 数据集 dataset_train = gluon.data.ArrayDataset(X_train, y_train) data_iter = gluon.data.DataLoader(d ...

  10. 偏差与方差、L1正则化、L2正则化、dropout正则化、神经网络调优、批标准化Batch Normalization(BN层)、Early Stopping、数据增强

    日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) 3.2 深度学习正则化 3.2.1 偏差与方差 3.2.1.1 ...

最新文章

  1. 深入理解GBDT多分类算法
  2. 阿里云ESC搭建SVN服务端
  3. 上传一份个人学习struts2的历程笔记
  4. Spring Cloud Alibaba 新版本发布:众多期待内容整合打包加入!
  5. java访问map_java.map使用
  6. const的用法,特别是用在函数前面与后面的区别
  7. python调用dll函数_从Python调用DLL函数
  8. Programming WCF Services翻译笔记(五)
  9. css3动画事件—webkitAnimationEnd
  10. mysql php状态函数_mysql_stat()查询MySQL服务器当前系统状态
  11. mysql线程缓存和表缓存
  12. MySQL (13)---查询数据
  13. Python 字符串与二进制串的相互转换
  14. 红帽子linux改ip命令,Linux系统下图形界面更改IP地址
  15. 开课吧:深入了解人工智能在金融行业中的应用
  16. 大一计算机ppt知识点,大一计算机总复习_图文.ppt
  17. WEB页面打印--打印指定区域,页面预览,页面设置
  18. 《大象:thinking in uml 》(第二版) 3章 UML核心元素 4-7节 边界、业务实体、包、分析类
  19. Android WiFi 连接 (Connect,no Internet,带叉)
  20. 图像处理:实现图片镜像(基于python)

热门文章

  1. 软编码Flv 到Mp4 容器(五) fmp4 ftyp box 和moovmvhd box详解
  2. 门这边、门那边的2个世界...
  3. H5游戏框架phaser实例二(附上传到服务器的教程)
  4. Java常用设计模式之装饰者模式
  5. IFE页面结构语义化
  6. IBM RSA 建模:第 8 章可重用模型
  7. 文艺中年高晓松成“岛主” 上万册图书免费看
  8. Ubuntu 设置 OpenDNS
  9. GPS时间系统的转换
  10. HTTP协议6-HTTP内容类型