ReLU层的设计思路
ReLU的定义
由于ReLU是不带参数的,因此可通过继承Layer类,定义ReluLayer类来实现ReLU激活函数的神经网络层。
具体来说,该类包括以下几个成员函数:
-
构造函数
ReluLayer() : Layer("Relu")
,用于创建一个ReluLayer对象,并设置该层的名称为"Relu"。 -
InferStatus Forward(const std::vector<std::shared_ptr<Tensor<float>>> &inputs, std::vector<std::shared_ptr<Tensor<float>>> &outputs) override
,重载虚函数Forward(),实现输入tensor的元素作用ReLU函数后输出tensor的功能。 -
static ParseParameterAttrStatus GetInstance(const std::shared_ptr<RuntimeOperator> &op, std::shared_ptr<Layer> &relu_layer)
,该静态函数为工厂方法,用于生成一个ReluLayer对象,并保存在relu_layer中。
在这个ReluLayer类中,采取了静态的GetInstance函数生成对象的方式,这样能够允许在不创建对象实例的情况下调用对象的静态方法,提高了代码框架的灵活性和可拓展性。
同时,为了保证神经网络框架内部部分的较强的理解和优化能力,通常在类实现的前后端分离中(RuntimeOperator),会根据前端输入的描述来实例化对应类型的后端运算符,从而构建出对应的Layer对象,提高了系统可靠性和效率。
ReLU的前向传播实现
Forward函数首先检查输入是否为空,以及输入tensor和输出tensor向量的尺寸是否匹配。
然后,对于每个输入tensor,使用OpenMP多线程并行处理,并对每个元素应用ReLU激活函数。
需要注意的是,ReLU函数的实现是通过lambda表达式实现的,即Transform([](float val) { return val > 0. ? val : 0.; })
,它将输入tensor中每个小于等于0的数据点都设置为0,而大于0的数据点保持不变。
算法的实现性能也有所保证,因为OpenMP多线程能够充分利用CPU多核心心性能进行计算。
最后,将每个输出tensor存储到相应的输出tensor向量中,并返回一个InferStatus类型的结果表示函数执行的状态。
ReLU的注册
ReLU的注册是通过ReluLayer类中的一个静态函数GetInstance以及定义一个全局变量kReluGetInstance来实现的,即LayerRegistererWrapper kReluGetInstance("nn.ReLU", ReluLayer::GetInstance);
。
GetInstance函数是ReluLayer类的一个工厂方法,用于创建一个ReluLayer对象,并将指针保存在参数relu_layer
中。
- 在该函数中,首先检查输入的op对象是否为nullptr。
- 然后,使用
std::make_shared
函数创建一个新的ReluLayer对象,并将其保存到relu_layer
指针中。 - 最后返回一个枚举类型ParseParameterAttrStatus,表明该函数执行成功。
而变量kReluGetInstance是一个全局变量,类型为LayerRegistererWrapper。
- 该变量的作用是将ReluLayer::GetInstance函数注册到神经网络框架的层注册表(registry)中。
- 在程序运行时,当需要实例化一个名为"nn.ReLU"的ReLU层时,就会调用ReluLayer::GetInstance函数来创建相应的层对象。
阅读的代码
- source
- layer
- details
- relu.hpp
- relu.cpp
- details
- layer