从零构建深度学习推理框架-3 手写算子relu

Relu介绍:

f(x) = \left\{\begin{matrix}x , x>thresh & & \\0,x<thresh & & \end{matrix}\right.

 relu是一个非线性激活函数,可以避免梯度消失,过拟合等情况。我们一般将thresh设为0。

operator类:

#ifndef KUIPER_COURSE_INCLUDE_OPS_OP_HPP_
#define KUIPER_COURSE_INCLUDE_OPS_OP_HPP_
namespace kuiper_infer {
enum class OpType {
  kOperatorUnknown = -1,
  kOperatorRelu = 0,
};

class Operator {
 public:
  OpType op_type_ = OpType::kOperatorUnknown; //不是一个具体节点 制定为unknown

  virtual ~Operator() = default; //

  explicit Operator(OpType op_type);
};

这里的  kOperatorUnknown = -1 , kOperatorRelu = 0分别是他们的代号

operator是一个父类,我们的relu就要继承于这个父类

class ReluOperator : public Operator {
 public:
  ~ReluOperator() override = default;

  explicit ReluOperator(float thresh);

  void set_thresh(float thresh);

  float get_thresh() const;

 private:
  // 需要传递到reluLayer中,怎么传递?
  float thresh_ = 0.f; // 用于过滤tensor<float>值当中大于thresh的部分
  // relu存的变量只有thresh
  // stride padding kernel_size 这些是到时候convOperator需要的
  // operator起到了属性存储、变量的作用
  // operator所有子类不负责具体运算
  // 具体运算由另外一个类Layer类负责
  // y =x  , if x >=0 y = 0 if x < 0

};

 operator起到了属性存储、变量的作用
 operator所有子类不负责具体运算
 具体运算由另外一个类Layer类负责

layer类:

class Layer {
 public:
  explicit Layer(const std::string &layer_name);

  virtual void Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
                        std::vector<std::shared_ptr<Tensor<float>>> &outputs);
  // reluLayer中 inputs 等于 x , outputs 等于 y= x,if x>0
  // 计算得到的结果放在y当中,x是输入,放在inputs中

  virtual ~Layer() = default;
 private:
  std::string layer_name_; //relu layer "relu"
};

父类只保留了一个layer_name属性和两个方法。

具体的在relu_layer这个class中

class ReluLayer : public Layer {
 public:
  ~ReluLayer() override = default;

  // 通过这里,把relu_op中的thresh告知给relu layer, 因为计算的时候要用到
  explicit ReluLayer(const std::shared_ptr<Operator> &op);

  // 执行relu 操作的具体函数Forwards
  void Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
                std::vector<std::shared_ptr<Tensor<float>>> &outputs) override;

  // 下节的内容,不用管
  static std::shared_ptr<Layer> CreateInstance(const std::shared_ptr<Operator> &op);

 private:
  std::unique_ptr<ReluOperator> op_;
};

具体的方法实现:

ReluLayer::ReluLayer(const std::shared_ptr<Operator> &op) : Layer("Relu") {
  CHECK(op->op_type_ == OpType::kOperatorRelu) << "Operator has a wrong type: " << int(op->op_type_);
  // dynamic_cast是什么意思? 就是判断一下op指针是不是指向一个relu_op类的指针
  // 这边的op不是ReluOperator类型的指针,就报错
  // 我们这里只接受ReluOperator类型的指针
  // 父类指针必须指向子类ReluOperator类型的指针
  // 为什么不讲构造函数设置为const std::shared_ptr<ReluOperator> &op?
  // 为了接口统一,具体下节会说到
  ReluOperator *relu_op = dynamic_cast<ReluOperator *>(op.get());

  CHECK(relu_op != nullptr) << "Relu operator is empty";
  // 一个op实例和一个layer 一一对应 这里relu op对一个relu layer
  // 对应关系
  this->op_ = std::make_unique<ReluOperator>(relu_op->get_thresh());
}

void ReluLayer::Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
                         std::vector<std::shared_ptr<Tensor<float>>> &outputs) {
  // relu 操作在哪里,这里!
  // 我需要该节点信息的时候 直接这么做
  // 实行了属性存储和运算过程的分离!!!!!!!!!!!!!!!!!!!!!!!!
  //x就是inputs y = outputs
  CHECK(this->op_ != nullptr);
  CHECK(this->op_->op_type_ == OpType::kOperatorRelu);

  const uint32_t batch_size = inputs.size(); //一批x,放在vec当中,理解为batchsize数量的tensor,需要进行relu操作
  for (int i = 0; i < batch_size; ++i) {

    CHECK(!inputs.at(i)->empty());
    const std::shared_ptr<Tensor<float>> &input_data = inputs.at(i); //取出批次当中的一个张量

    //对张量中的每一个元素进行运算,进行relu运算
    input_data->data().transform([&](float value) {
      // 对张良中的没一个元素进行运算
      // 从operator中得到存储的属性
      float thresh = op_->get_thresh();
      //x >= thresh
      if (value >= thresh) {
        return value; // return x
      } else {
        // x<= thresh return 0.f;
        return 0.f;
      }
    });

    // 把结果y放在outputs中
    outputs.push_back(input_data);
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值