一、什么是自动微分
自动微分基于链式法则,将复杂函数分解为基本的微分操作。它主要用于求解复杂函数的导数,尤其是对于高维、高阶导数的计算非常有用。在深度学习领域有着十分广泛的用途,减去了手动计算导数的痛苦工作
二、如何实现
这个就用比较简单的加减乘除作为示例, 有需要可以增加或者自定义函数
1.f(x) 形式
使用前向微分的形式
#include <iostream>
struct Node{
float value;
float grad;
Node() = default;
Node(float value);
Node(float value, float gard);
};
Node::Node(float value){
this->value = value;
this->grad = 0;
}
Node::Node(float value, float gard){
this->value = value;
this->grad = gard;
}
Node add(Node op0, Node op1){
return Node(op0.value + op1.value, op1.grad + op1.grad);
}
Node sub(Node op0, Node op1){
return Node(op0.value - op1.value, op1.grad - op1.grad);
}
Node mul(Node op0, Node op1){
return Node(op0.value * op1.value,
op0.grad * op1.value + op0.value * op1.grad);
}
Node div(Node op0, Node op1){
return Node(op0.value / op1.value,
(op0.grad * op1.value - op0.value * op1.grad)/(op1.value * op1.value));
}
int main(){
int val = 2;
Node x(val, 1);
Node revx = div(Node(1) ,x); // 1/x
std::cout<<"value:"<<revx.value<<"\ngard:"<<revx.grad;
}
2.f(x, y) 等多元变量形式
#include <iostream>
struct Node{
float value;
float grad_x;
float grad_y;
Node(float value, float grad_x, float gard_y):
value(value),grad_x(grad_x),grad_y(gard_y){}
};
Node add(Node a, Node b) {
return Node(a.value + b.value, a.grad_x + b.grad_x, a.grad_y + b.grad_y);
}
Node mul(Node a, Node b) {
return Node(a.value * b.value, a.value * b.grad_x + b.value * a.grad_x, a.value * b.grad_y + b.value * a.grad_y);
}
//f(x, y) = x^2 + y^2 + 2xy + 1
Node compute_function(Node x, Node y) {
Node x2 = mul(x, x); // x^2
Node y2 = mul(y, y); // y^2
Node xyd = mul(Node(2, 0, 0), mul(x, y));
Node sum = add(x2, add(y2, xyd));
Node result = add(sum, Node(1, 0, 0));
return result;
}
int main() {
Node x = Node(3.0, 1.0, 0.0);
Node y = Node(1.0, 0.0, 1.0);
Node result = compute_function(x, y);
std::cout<<"f(x, y) = "<<result.value<<std::endl;
std::cout<<"df/dx = "<<result.grad_x<<std::endl;
std::cout<<"df/dy = "<< result.grad_y<<std::endl;
return 0;
}
3. 不确定维度形式
问题来了,如果变量很多,而且路径不确定,前向微分算法对空间的占用会很大, 那么有没有什么可以记录需要指定变量梯度形式的方法呢?
这个相对来说比较复杂,前面的是正向微分,但是到这里是反向微分(确实反向微分更适合多元函数)
具体方法就是存路径,用一个比较简单的数据结构存储路径,在调用backward()
方法的时候按照路径计算需要求解的梯度
#include<iostream>
#include<vector>
namespace autograd{
enum Operator{ADD, MUL,SUB,DIV};
inline float operate(float vl, float vr, Operator op){
switch (op){
case ADD: return vl + vr;
case SUB: return vl - vr;
case MUL: return vl * vr;
case DIV: return vl / vr;
default: std::cerr << "Unsupported operator!" << std::endl; exit(-1);
}
}
struct BaseNode{
float value;
float grad;
BaseNode* org_id;
BaseNode():value(0),grad(0),org_id(this){}
BaseNode(float value, float gard=0):value(value), grad(gard), org_id(this){}
void accumulate(float DeltaGrad){
this->grad += DeltaGrad;
}
};
struct LinkNode: public BaseNode{
BaseNode* left_node;
BaseNode* right_node;
Operator opr;
LinkNode(BaseNode* l, BaseNode* r, Operator op):
left_node(l), right_node(r), opr(op){
this->org_id = this;
this->grad = 0;
this->value = operate(l->value, r->value, op);
this->tape_line.push_back(this);
}
void backward(){
this->zero_tape(true); //如果需要累计梯度就更改参数
this->grad = 1.0f;
for(auto it = tape_line.rbegin(); it!=tape_line.rend(); it++){
if((*it)->org_id == this) grad_backward((*it));
}
}
static void clear_tape(){
tape_line.clear();
}
private:
static std::vector<LinkNode*> tape_line;
void zero_tape(bool clear_leaf=false){
tape_line.back()->grad = 0.0f;
for(auto it = tape_line.rbegin(); it!=tape_line.rend(); it++){
(*it)->grad = 0.0f;
if(clear_leaf){
(*it)->left_node->grad = 0.0f;
(*it)->right_node->grad = 0.0f;
}
}
}
void grad_backward(LinkNode* t){
BaseNode* l = t->left_node;
BaseNode* r = t->right_node;
Operator op = t->opr;
l->org_id = r->org_id = t->org_id;
switch (op){
case ADD:l->accumulate(t->grad);r->accumulate(t->grad);return;
case SUB:l->accumulate(t->grad);r->accumulate(-t->grad);return;
case MUL:l->accumulate(t->grad * r->value);r->accumulate(t->grad * l->value);return;
case DIV:l->accumulate(t->grad / r->value);r->accumulate(-t->grad * l->value / (r->value * r->value));return;
default: std::cerr << "Unsupported operator!" << std::endl; exit(-1);
}
}
};
std::vector<LinkNode*> LinkNode::tape_line =
std::vector<LinkNode*>();
}
using namespace std;
using namespace autograd;
int main() {
BaseNode node1(5.0);
BaseNode node2(3.0);
LinkNode ln1(&node1, &node2, ADD);
LinkNode ln2(&ln1, &node2, MUL);
LinkNode ln3(&ln2, &node2, MUL);
ln3.backward();
cout<<node1.grad <<endl<<node2.grad << endl;
return 0;
}