动手学深度学习框架(4)- 手把手教你写一个功能完整的简易 Demo

0、前言

一定不要错过!一定不要错过!一定不要错过!重要的事情说 3 遍。本篇文章中,我将手把手教你写一个功能完整的深度学习框架 demo,本文将满足你对 Pytorch、Tensorflow、Paddle 中神秘的 C++ 后端的所有好奇心。

1、问题描述

为了在有限的篇幅中把深度学习框架讲明白,我们以一个简单的例子开始:

组网结构:超级简单的 FC(全连接),Loss(损失函数)采用 MSE(最小均方误差)

为了进一步简化,输入输出张量纬度均设为 1,也就是全为标量

上式中,是输入数据,是模型输出,是参数。

给定一条训练样本

2、数学推导

上述公式中的就是神经网络中的 loss,则该最优化问题可抽象为:

如何求解呢?3 板斧:反向梯度 + 链式求导 + 梯度更新

3、详细代码及解释

#include 
#include 
#include 
#include 
#include 
#include 
#include 

//自定义 Tensor 类型,这里数据成员非常简单,就是个标量,重载了基本数学运算符
class MyTensor {
public:
    uint32_t data;
public:
    MyTensor(){};
    MyTensor(uint32_t x) : data(x) {}
    MyTensor operator*(const MyTensor& a) {
        this->data = this->data * a.data;
        return *this;
    }
    MyTensor operator+(const MyTensor& a) {
        this->data = this->data + a.data;
        return *this;
    }
    MyTensor operator-(const MyTensor& a) {
        this->data = this->data - a.data;
        return *this;
    }
    MyTensor operator*(const int& a) {
        this->data = this->data * a;
        return *this;
    }
};

// Op 基类
class OpBase {
public:
    std::unordered_map inputs;
    std::unordered_map outputs;
    std::unordered_map labels;
public:
    virtual void Run() = 0;
};

// 乘法前向 Op
class MultipylyForward : public OpBase {
public:
    void Run() {
        MyTensor x = inputs["X"];
        MyTensor w = inputs["W"];
        MyTensor y1 = x * w;
        outputs["Y"] = y1;
    }
};

// 乘法反向 Op
class MultipylyBackward : public OpBase {
public:
    void Run() {
        MyTensor x = inputs["X"];
        outputs["Y"] = x;
    }
};

// 加法前向 Op
class AddForward : public OpBase {
public:
    void Run() {
        MyTensor x1 = inputs["X1"];
        MyTensor x2 = inputs["X2"];
        MyTensor y = x1 + x2;
        outputs["Y"] = y;
    }
};

// 加法反向 Op
class AddBackward : public OpBase {
public:
    void Run() {
        MyTensor x;
        x.data = 1;
        outputs["Y"] = x;
    }
};

// loss 前向 Op,这里选取 MSE 作为示例
class LossForward : public OpBase {
public:
    void Run() {
        MyTensor y = inputs["X"];
        MyTensor label = labels["Label"];
        MyTensor loss = (y - label) * (y - label);
        outputs["Y"] = loss;
    }
};

// loss 反向 Op
class LossBackward : public OpBase {
public:
    void Run() {
        MyTensor y = inputs["X"];
        MyTensor label = labels["Label"];
        outputs["Y"] = (y - label) + (y - label);
    }
};

// 梯度更新 Op
class UpdateGrad : public OpBase {
public:
    double lr = 0.1;
    std::unordered_map inputs;
    std::unordered_map outputs;
public:
    void Run() {
        MyTensor w = inputs["W"];
        MyTensor grad = inputs["Grad1"] * inputs["Grad2"] * inputs["Grad3"];  // 链式求导
        MyTensor lr;
        lr.data = this->lr;
        outputs["Y"] = w - lr * grad;
    }
};

int main() 
{
    //1. 用户自定义前向组网
    std::vector program{"Multiply", "Add", "Loss"};

    //2. 框架生成前向op + 自动补全反向OP + 插入梯度更新op
    std::vector ops{"multiply_forward", "add_forward", "loss_forward",
        "loss_backward", "Add_forward", "multiply_backward", "update_grad"};

    //3. 实例化 c++ 端 op 对象
    std::vector opClass {new MultipylyForward(), new AddForward(), new LossForward(),
        new LossBackward(), new AddBackward(), new MultipylyBackward(), new UpdateGrad()};

    //4. 框架根据用户组网,自动给每个op的输入赋值,这里仅以乘法前向op作个例子。一定要记住一点:框架中所有输入数据、
    //参数、模型中间输入、输出、以及每个参数的梯度都有一个 string 类型的名字,它的存在是为了给op输入赋值服务的
    opClass[0]->inputs["X"] = MyTensor(10);
    opClass[0]->inputs["W"] = MyTensor(20);
    for (auto op : opClass) {
        op->Run();
    }

    //5. 测试第1个op的输出
    std::cout << opClass[0]->outputs["Y"].data;  // 输出结果:200
}

3.0、框架实现 7 个算子(Op)

Op 就可以简单理解为函数符号化,对于每个 Op(函数),我们需要指定输入是什么,输出是什么,所以很显然想到用 string 类型的名字去描述。在 Op 运行时,只要按名字找到实际的数据即可。

动手学深度学习框架(4)- 手把手教你写一个功能完整的简易 Demo

3.1、用户描述组网信息

由 python 前端接口提供,具体可参考本系列专题之前的文章。

3.2、框架生成前向 Op(3 个) + 框架生成反向 Op(3 个) + 框架插入参数更新 Op(1 个)

3.3、框架运行 Op

4、总结

当然,本文为了让大家理解神经网络框架的基本原理,所以非常简化。实际框架比这要复杂得多(功能更加完善、训练和推理的区别、静态图和动态图、cpu/gpu 等异构硬件、自动微分、序列化等等),但本质思想上是完全一样的。感兴趣的同学,可以去深入研究下 Pytorch、Paddle、Tensorflow 的源码吧。

之后的文章,我将重点介绍下深度学习分布式技术的方方面面,欢迎关注。

更多内容,也请关注我同名知乎账号『自由技艺』

展开阅读全文

页面更新:2024-03-07

标签:链式   求导   框架   标量   神经网络   梯度   乘法   简易   深度   完整   参数   简单   功能   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top