一定不要错过!一定不要错过!一定不要错过!重要的事情说 3 遍。本篇文章中,我将手把手教你写一个功能完整的深度学习框架 demo,本文将满足你对 Pytorch、Tensorflow、Paddle 中神秘的 C++ 后端的所有好奇心。
为了在有限的篇幅中把深度学习框架讲明白,我们以一个简单的例子开始:
组网结构:超级简单的 FC(全连接),Loss(损失函数)采用 MSE(最小均方误差)
为了进一步简化,输入输出张量纬度均设为 1,也就是全为标量
上式中,是输入数据,是模型输出,是参数。
给定一条训练样本
上述公式中的就是神经网络中的 loss,则该最优化问题可抽象为:
如何求解呢?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
}
Op 就可以简单理解为函数符号化,对于每个 Op(函数),我们需要指定输入是什么,输出是什么,所以很显然想到用 string 类型的名字去描述。在 Op 运行时,只要按名字找到实际的数据即可。
由 python 前端接口提供,具体可参考本系列专题之前的文章。
当然,本文为了让大家理解神经网络框架的基本原理,所以非常简化。实际框架比这要复杂得多(功能更加完善、训练和推理的区别、静态图和动态图、cpu/gpu 等异构硬件、自动微分、序列化等等),但本质思想上是完全一样的。感兴趣的同学,可以去深入研究下 Pytorch、Paddle、Tensorflow 的源码吧。
之后的文章,我将重点介绍下深度学习分布式技术的方方面面,欢迎关注。
更多内容,也请关注我同名知乎账号『自由技艺』
页面更新:2024-03-07
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号