relu算子的介绍
relu是一种非线性激活函数,它的特点有运算简单,不会在梯度处出现梯度消失的情况,而且它在一定程度上能够防止深度学习模型在训练中发生的过拟合现象。relu的公式表达如下所示,「如果对于深度学习基本概念不了解的同学,可以将relu当作一个公式进行对待,可以不用深究其背后的含义。」
我们今天的任务就是来完成这个公式中的操作,「值得注意的是,在我们的项目中,x和y可以理解为我们在第二、第三节中实现的张量类(tensor).」
operator类
operator类就是我们在第一节中说过的计算图中「节点」的概念,计算图的另外一个概念是数据流图,如果同学们忘记了这个概念,可以重新重新翻看第一节课程。
在我们的代码中我们先定义一个「operator」类,它是一个父类,其余的operator,包括我们本节要实现的reluoperator都是其派生类,「operator中会存放节点相关的参数。」例如在「convoperator」中就会存放初始化卷积算子所需要的stride, padding, kernel_size等信息,本节的「reluoperator」就会带有「thresh」值信息。
我们从下方的代码中来了解operator类和reluoperator类,它们是父子关系,operator是基类,optype记录operator的类型。
enum class optype { koperatorunknown = -1, koperatorrelu = 0,};class operator { public: optype koptype = optype::koperatorunknown; virtual ~operator() = default; explicit operator(optype op_type);};
reluoperator实现:
class reluoperator : public operator { public: ~reluoperator() override = default; explicit reluoperator(float thresh); void set_thresh(float thresh); float get_thresh() const; private: float thresh_ = 0.f;};
layer类
我们会在operator类中存放从「计算图结构文件」得到的信息,例如在reluoperator中存放的thresh值作为一个参数就是我们从计算图结构文件中得到的,计算图相关的概念我们已经在第一节中讲过。
下一步我们需要根据reluoperator类去完成relulayer的初始化,「他们的区别在于reluoperator负责存放从计算图中得到的节点信息,不负责计算」,而relulayer则「负责具体的计算操作」,同样,所有的layer类有一个公共父类layer. 我们可以从下方的代码中来了解两者的关系。
class layer { public: explicit layer(const std::string &layer_name); virtual void forwards(const std::vector
&inputs, std::vector &outputs); virtual ~layer() = default; private: std::string layer_name_;};
其中layer的forwards方法是具体的执行函数,负责将输入的inputs中的数据,进行relu运算并存放到对应的outputs中。
class relulayer : public layer { public: ~relulayer() override = default; explicit relulayer(const std::shared_ptr &op); void forwards(const std::vector &inputs, std::vector &outputs) override; private: std::shared_ptr op_;};
这是集成于layer的relulayer类,我们可以看到其中有一个op成员,是一个reluoperator指针,「这个指针中负责存放relulayer计算时所需要用到的一些参数」。此处op_存放的参数比较简单,只有reluoperator中的thresh参数。
我们再看看是怎么使用reluoperator去初始化relulayer的,先通过统一接口传入operator类,再转换为对应的reluoperator指针,最后再通过指针中存放的信息去初始化「op_」.
relulayer::relulayer(const std::shared_ptr &op) : layer(relu) { check(op->koptype == optype::koperatorrelu); reluoperator *relu_op = dynamic_cast(op.get()); check(relu_op != nullptr); this->op_ = std::make_shared(relu_op->get_thresh());}
我们来看一下具体relulayer的forwards过程,它在执行具体的计算,完成relu函数描述的功能。
void relulayer::forwards(const std::vector &inputs, std::vector &outputs) { check(this->op_ != nullptr); check(this->op_->koptype == optype::koperatorrelu); const uint32_t batch_size = inputs.size(); for (int i = 0; i empty()); const std::shared_ptr& input_data = inputs.at(i); input_data->data().transform([&](float value) { float thresh = op_->get_thresh(); if (value >= thresh) { return value; } else { return 0.f; } }); outputs.push_back(input_data); }}
在for循环中,首先读取输入input_data, 再对input_data使用armadillo自带的transform按照我们给定的thresh过滤其中的元素,如果「value」的值大于thresh则不变,如果小于thresh就返回0.
最后,我们写一个测试函数来验证我们以上的两个类,节点op类,计算层layer类的正确性。
先判断forwards返回的outputs是否已经保存了relu层的输出,输出大小应该assert为1. 随后再进行比对,我们应该知道在thresh等于0的情况下,第一个输出index(0)和第二个输出index(1)应该是0,第三个输出应该是3.f.
test(test_layer, forward_relu) { using namespace kuiper_infer; float thresh = 0.f; std::shared_ptr relu_op = std::make_shared(thresh); std::shared_ptr input = std::make_shared(1, 1, 3); input->index(0) = -1.f; input->index(1) = -2.f; input->index(2) = 3.f; std::vector inputs; std::vector outputs; inputs.push_back(input); relulayer layer(relu_op); layer.forwards(inputs, outputs); assert_eq(outputs.size(), 1); for (int i = 0; i index(0), 0.f); assert_eq(outputs.at(i)->index(1), 0.f); assert_eq(outputs.at(i)->index(2), 3.f); }}
本期代码仓库位置
git clone https://gitee.com/fssssss/kuipercourse.gitgit checkout fouth
二极管内部由什么构成?
箱式变压器送电操作流程和注意事项
iPhone15系列发布时间
多点监视报警器的原理
索尼将以11.75亿美元购买动漫流媒体服务Crunchyroll
Relu算子的介绍
币币交易,场外OTC交易所开发源中瑞胡大帅
剑桥工程师打造自主式蔬菜采摘机器人,成功率令人惊讶
高通骁龙6系5G移动平台的相关终端预计将于2020年下半年实现商用
Vivado/Vitis 2022.2详细安装步骤
PCB元器件摆放的小技巧分享
浅谈节能隧道烘箱的发展前景
5G手机问世时间点 主要取决于手机的芯片平台的成熟度
我们到底有多少理由选择售价为2799元的HomePod?
TE Connectivity推新型Sliver跨接式连接器 支持PCIe Gen 5高速数据传输
具有能量收集功能的传感器才是设计成功的关键
小米10将全线采用LPDDR5你期待吗
浅谈耦合线的定义
70迈智能后视镜评测 799元的厚道定价感动人心
HSPA技术能否将网络升级至HSPA+