ch08: ResNet中的add层(为表达式增加操作数)

72 阅读2分钟

项目地址
课程地址
个人完成的作业地址
作者原文
ch07中我们成功将一个表达式解析为树结构,这节将其转化为逆波兰表达式,并增加操作数。让表达式能够成功运算。

逆波兰表达式

上节解析的结构为树状,打印时候是中序。
逆波兰表达式是后序遍历。解析结构如下

add(@1,@2)        逆波兰表达式1 2 add
add(mul(@0,@1),@2)逆波兰表达式0 1 mul 2 add

根据逆波兰表达式进行计算:
遇到操作数,将数字入栈。
遇到操作符,从栈中弹出两个数字,再将计算结果入栈。

// 逆波兰  后序遍历
void ReversePolish(const std::shared_ptr<TokenNode>&root_node, std::vector<std::shared_ptr<TokenNode>>& reverse_polish){
    if(root_node!=nullptr){
        ReversePolish(root_node->left, reverse_polish);
        ReversePolish(root_node->right, reverse_polish);\
        reverse_polish.push_back(root_node);
    }
}

现在的generate函数与上节相比,增加转换为逆波兰表达式部分。
上节返回std::shared_ptr<TokenNode>
这节返回std::vector<std::shared_ptr<TokenNode>>

std::vector<std::shared_ptr<TokenNode>> ExpressionParser::Generate() {
    if (this->tokens_.empty()) {
        this->Tokenizer(true);
    }
    int index = 0;
    std::shared_ptr<TokenNode> root = Generate_(index);
    
    //以下新增 
    CHECK(root != nullptr);
    CHECK(index == tokens_.size() - 1);

    std::vector<std::shared_ptr<TokenNode>> reverse_polish;
    ReversePolish(root, reverse_polish);  // 逆波兰表达式
    return reverse_polish;
}

算子expression_op

上节虽然增加了解析表达式的功能,但没有增加专门的表达式算子,表达式运算层。

class ExpressionOp:public Operator{
    public:
        explicit ExpressionOp(const std::string &expr);
        std::vector<std::shared_ptr<TokenNode>> Generate();
    private:
        std::shared_ptr<ExpressionParser> parser_;
        std::vector<std::shared_ptr<TokenNode>> nodes_;
        std::string expr_;
};

运算层expression_layer

class ExpressionLayer:public Layer{
    public:
        explicit ExpressionLayer(const std::shared_ptr<Operator> &op);
        void Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
            std::vector<std::shared_ptr<Tensor<float>>> &outputs) override;
    private:
        std::unique_ptr<ExpressionOp> op_;
};

Forwards函数

// 首先检查输入是否为空
CHECK(!inputs.empty());

// 初始化outputs数组中的元素.
const uint32_t batch_size = outputs.size();
CHECK(batch_size != 0);
for (uint32_t i = 0; i < batch_size; ++i) {
    CHECK(outputs.at(i) != nullptr && !outputs.at(i)->empty());
    outputs.at(i)->Fill(0.f);
}

CHECK(this->op_!=nullptr&&this->op_->op_type_==OpType::kOperatorExpression);
// 用于存储计算结果
// std::vector<std::shared_ptr<Tensor<float>>>   一个batch放在一个vector里面
std::stack<std::vector<std::shared_ptr<Tensor<float>>>> op_stack;

// 生成逆波兰表达式
const std::vector<std::shared_ptr<TokenNode>> &token_nodes = this->op_->Generate();  

// 遍历节点
for (const auto &token_node : token_nodes) {
// 1. 如果遇到数字类型,从inputs取batchsize个数字,拼成一个操作数
// 因为tensor最多三维,如果想表达一个batch的tensor,必须用vector装载

 if (token_node->num_index >= 0) {   // 操作数
    uint32_t start_pos = token_node->num_index * batch_size;
    std::vector<std::shared_ptr<Tensor<float>>> input_token_nodes;
    for (uint32_t i = 0; i < batch_size; ++i) {
        CHECK(i + start_pos < inputs.size());
        input_token_nodes.push_back(inputs.at(i + start_pos));
    }
    op_stack.push(input_token_nodes);

// 2. 如果遇到运算符
const int32_t op = token_node->num_index;  // 操作符
// 先检查栈中临时结果是否大于等于2(运算数够不够用)
CHECK(op_stack.size() >= 2) << "The number of operand is less than two";

// 取数字
std::vector<std::shared_ptr<Tensor<float>>> input_node1 = op_stack.top();
CHECK(input_node1.size() == batch_size);
op_stack.pop();

std::vector<std::shared_ptr<Tensor<float>>> input_node2 = op_stack.top();
CHECK(input_node2.size() == batch_size);
op_stack.pop();

 // 临时计算结果
std::vector<std::shared_ptr<Tensor<float>>> output_token_nodes(batch_size);
for (uint32_t i = 0; i < batch_size; ++i) {
    if (op == -int(TokenType::TokenAdd)) {
        output_token_nodes.at(i) = ftensor::ElementAdd(input_node1.at(i), input_node2.at(i));
    } else if (op == -int(TokenType::TokenMul)) {
        output_token_nodes.at(i) = ftensor::ElementMultiply(input_node1.at(i), input_node2.at(i));
    } else {
        LOG(FATAL) << "Unknown operator type: " << op;
    }
}
// 计算结果入栈
op_stack.push(output_token_nodes);

// 检查最终结果
// 最后栈中应该只有一个值
CHECK(op_stack.size() == 1);
std::vector<std::shared_ptr<Tensor<float>>> output_node = op_stack.top();
op_stack.pop();
for (int i = 0; i < batch_size; ++i) {
CHECK(outputs.at(i) != nullptr && !outputs.at(i)->empty());
    outputs.at(i) = output_node.at(i);
}

TEST

没有用注册机制

const std::string &expr = "add(mul(@0,@1),@2)";
std::shared_ptr<ExpressionOp> expression_op = std::make_shared<ExpressionOp>(expr);
ExpressionLayer layer(expression_op);

由于generate返回结果变化,shownode函数也需要变化

static void ShowNodes(const std::vector<std::shared_ptr<kuiper_infer::TokenNode>> &nodes) {
    for(std::shared_ptr<kuiper_infer::TokenNode> node:nodes){
        if (node->num_index < 0) {
            if (node->num_index == -int(kuiper_infer::TokenType::TokenAdd)) {
              LOG(INFO) << "ADD";
            } else if (node->num_index == -int(kuiper_infer::TokenType::TokenMul)) {
              LOG(INFO) << "MUL";
            }
        else if (node->num_index == -int(kuiper_infer::TokenType::TokenDiv)) {
              LOG(INFO) << "Div";
            }
        } else {
            LOG(INFO) << "NUM: " << node->num_index;
          }
}
}

逆波兰表达式解析结果 7.3.png layer.Forwards(inputs, outputs)带操作数的运算结果 8.1.png 目前通过的test数为18 8.2.png