神经网络系列---计算图基本原理

news2024/9/23 3:25:26

文章目录

  • 计算图
    • 符号微分
      • 符号微分的步骤
      • 示例
      • 符号微分在计算图中的使用
      • 总结
    • 数值微分
      • 前向差分法
      • 中心差分法
      • 数值微分的使用
      • 注意事项
      • 总结
    • 自动微分
      • 1. 基本原理
      • 2. 主要类型
      • 3. 计算图
      • 4. 应用
      • 5. 工具和库
      • 6. 优点和缺点
    • 计算图
      • 1. **计算图的建立**
      • 2. **前向传播**
      • 3. **反向传播**
      • 4. **链式法则和梯度计算**
      • 5. **优点**
      • 例子:
        • 步骤1: 定义变量和运算
        • 步骤2: 创建节点
        • 步骤3: 创建边
        • 步骤4: 执行前向传播
        • 步骤5: (可选)执行反向传播


计算图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

符号微分

在这里插入图片描述

符号微分(Symbolic Differentiation)是一种使用数学表达式来表示微分或导数的技术。与数值微分不同,符号微分不是通过逼近来计算导数,而是直接处理数学表达式,得到一个精确的表达式,表示该函数的导数。

符号微分的步骤

  1. 表达式定义:首先,你需要一个表达式,例如一个多项式或任何可以微分的数学函数。

  2. 应用微分规则:然后,你会应用一系列微分规则(如乘积规则、商规则、链式法则等)来对表达式进行微分。

  3. 简化表达式:最后,你可能需要使用一些代数技巧来简化得到的导数表达式。

示例

假设你有一个函数 f ( x ) = x 2 + 3 x + 2 f(x) = x^2 + 3x + 2 f(x)=x2+3x+2,并且你想要找到其导数。通过符号微分,你可以应用基本的导数规则:

  • d d x x n = n ⋅ x n − 1 \frac{d}{dx} x^n = n \cdot x^{n-1} dxdxn=nxn1
  • d d x c = 0 \frac{d}{dx} c = 0 dxdc=0 (其中 c c c 是常数)
  • 线性函数的导数等于其系数

这样,你可以找到 f ( x ) f(x) f(x)的导数:

f ′ ( x ) = 2 ⋅ x 1 + 3 ⋅ 1 + 0 = 2 x + 3 f'(x) = 2 \cdot x^1 + 3 \cdot 1 + 0 = 2x + 3 f(x)=2x1+31+0=2x+3

符号微分在计算图中的使用

在深度学习中,符号微分经常与计算图结合使用:

  1. 前向传播:通过计算图表示模型的前向计算。
  2. 符号微分:应用符号微分来表示每个操作的局部导数。
  3. 反向传播:使用链式法则和计算图中的局部导数来计算整个模型的梯度。

这个过程允许深度学习框架自动计算模型的梯度,这是训练神经网络时所必需的。

总结

符号微分提供了一种精确、直接的方式来计算导数。在深度学习和其他科学计算应用中,通过结合计算图,符号微分使得自动求导和梯度下降优化变得可行和高效。不过,对于非常复杂的表达式,符号微分可能导致表达式膨胀,从而增加了计算复杂性。因此,有时可能会结合使用符号微分和数值微分方法。

数值微分

在这里插入图片描述

数值微分是一种近似计算函数导数的技术。与符号微分不同,数值微分不是通过解析地处理数学表达式来找到导数的精确形式,而是使用函数在特定点的数值来估计导数。

前向差分法

最简单的数值微分方法之一是前向差分法。假设你想要计算函数 f ( x ) f(x) f(x)在点 x x x处的导数,你可以使用以下公式:

f ′ ( x ) ≈ f ( x + h ) − f ( x ) h f'(x) \approx \frac{f(x + h) - f(x)}{h} f(x)hf(x+h)f(x)

其中 h h h是一个非常小的正数,称为步长。这个公式给出了在 x x x附近的函数的斜率的近似值。

中心差分法

前向差分方法的一个问题是,它可能不是非常精确,特别是当 h h h相对较大时。更精确的方法是中心差分法,使用以下公式:

f ′ ( x ) ≈ f ( x + h ) − f ( x − h ) 2 h f'(x) \approx \frac{f(x + h) - f(x - h)}{2h} f(x)2hf(x+h)f(xh)

中心差分通过计算 f ( x ) f(x) f(x) x x x附近的两点的平均斜率,通常提供了更好的近似。

数值微分的使用

数值微分用于许多不同的应用领域,包括:

  • 解析解不可用:当函数的导数很难或不可能解析地找到时,可以使用数值微分。

  • 深度学习的梯度检查:在深度学习中,数值微分通常用于梯度检查,以确保使用符号微分(或自动微分)计算的梯度是正确的。

  • 科学和工程应用:数值微分用于许多科学和工程应用,其中需要近似导数,但可能没有解析解。

注意事项

  • 选择步长:选择合适的步长 h h h是一个关键问题。太大的步长可能会导致近似不精确,而太小的步长可能会导致数值不稳定。

  • 数值不稳定:当涉及极小或极大的值时,数值微分可能会出现问题,因为计算机浮点数的精度有限。

  • 计算成本:数值微分通常比符号微分更慢,因为它需要多次评估函数。

总结

数值微分是一种有用的工具,特别是当解析解不可用或难以获得时。它提供了一种灵活而实用的方法来近似导数,但必须谨慎选择参数并注意可能的数值问题。在深度学习和其他领域,它通常与符号微分或自动微分结合使用。

自动微分

视频链接:【前向微分和正向微分怎么理解?
自动微分(Automatic Differentiation,简称AD)是一种高效计算函数导数(或梯度)的技术。它不同于数值微分和符号微分,因为它可以提供更高的数值稳定性和计算效率。下面是自动微分的更多细节:

1. 基本原理

自动微分基于链式法则进行,它通过计算机程序来逐步计算和追踪函数的局部导数。基本的想法是将复杂函数分解为一系列简单的元素函数(例如加法、乘法等),并依次计算这些函数的导数。

2. 主要类型

自动微分可以分为两种主要类型:

  1. 前向模式(Forward Mode)

在前向模式中,我们从输入向量开始,然后通过每一个操作前进,计算每一步的局部导数和全局雅可比矩阵的相应部分。给定一个函数 f : R n → R m f: \mathbb{R}^n \rightarrow \mathbb{R}^m f:RnRm,前向模式特别适合 n ≪ m n \ll m nm 的情况。

  1. 反向模式(Reverse Mode)

在反向模式中,我们首先进行一次正向传递来计算函数的输出,然后从输出向后进行一次传递来计算梯度或雅可比矩阵的每一部分。反向模式特别适合于 n ≫ m n \gg m nm 的情况,这也是为什么它被广泛应用于神经网络和深度学习,因为我们通常有许多输入和少量输出(例如损失函数)。

3. 计算图

自动微分常常依赖于一个计算图来表示和跟踪函数的计算过程。计算图是一种图形数据结构,其中每个节点代表一个操作,每个边代表数据流。

4. 应用

自动微分被广泛应用于各种领域,特别是在机器学习和优化问题中。它是训练神经网络时所用的反向传播算法的核心。

5. 工具和库

现有许多库和框架支持自动微分,如TensorFlow、PyTorch等,它们提供了方便的API来实现和使用自动微分技术。

6. 优点和缺点

  • 优点
    • 高数值稳定性:比数值微分更稳定。
    • 高效:特别是反向模式,它可以高效地计算梯度,尤其是对于有大量输入和少量输出的函数。
  • 缺点
    • 内存消耗:反向模式可能需要大量的内存来存储中间结果。
    • 实现复杂性:实现一个自动微分系统可能是非常复杂和技术性的。

计算图

自动微分通常是在计算图的基础上实现的。在计算图中,一个复杂函数被分解为多个简单的操作,这些操作被组织为一个有向图。现在,让我们更详细地了解自动微分和计算图之间的关系:

1. 计算图的建立

如我们前面所述,首先我们需要建立一个计算图,代表我们的函数。这涉及将函数分解为更简单的操作和变量,然后以有向图的形式表示这些操作和变量。

2. 前向传播

在计算图中,我们从输入变量开始,然后按照图中的顺序进行操作,直到我们计算出输出。这就是所谓的前向传播。

3. 反向传播

反向传播是自动微分的核心。在这一步,我们从输出开始,然后向后计算每一步的局部导数(或梯度)。这通常涉及应用链式法则,这是一种从输出向输入反向传播导数的方法。

4. 链式法则和梯度计算

通过使用链式法则,我们可以计算从输出到任何中间变量或输入的梯度。通过这种方式,我们可以得到我们想要的所有导数,而不是仅仅是输出相对于输入的导数。

5. 优点

  • 精确度:自动微分可以提供与解析解几乎相同的精确度。
  • 效率:它通常比数值微分方法更快、更稳定,尤其是对于具有许多变量的复杂函数。

例子:

使用给定的函数 f ( x , y ) = log ⁡ ( x ) + x ⋅ y − sin ⁡ ( y ) f(x, y) = \log(x) + x \cdot y - \sin(y) f(x,y)=log(x)+xysin(y),我们可以构建一个计算图,将该函数分解为多个基本操作。下面是计算图的创建步骤:

步骤1: 定义变量和运算

首先,我们识别并定义所有的基本变量和运算:

  1. 变量: x x x, y y y
  2. 运算:
    • log ⁡ ( x ) \log(x) log(x)
    • x ⋅ y x \cdot y xy
    • sin ⁡ ( y ) \sin(y) sin(y)
    • 加法和减法来组合上述结果
步骤2: 创建节点

然后,为每个变量和运算创建节点:

  1. 节点1(变量): x x x
  2. 节点2(变量): y y y
  3. 节点3(运算): log ⁡ ( x ) \log(x) log(x)
  4. 节点4(运算): x ⋅ y x \cdot y xy
  5. 节点5(运算): sin ⁡ ( y ) \sin(y) sin(y)
  6. 节点6(运算): log ⁡ ( x ) + x ⋅ y \log(x) + x \cdot y log(x)+xy
  7. 节点7(运算): log ⁡ ( x ) + x ⋅ y − sin ⁡ ( y ) \log(x) + x \cdot y - \sin(y) log(x)+xysin(y) (这是最终的输出节点)
步骤3: 创建边

接着,我们连接相应的边来形成有向图:

  1. 从节点1到节点3(表示 log ⁡ ( x ) \log(x) log(x) 的输入是 x x x
  2. 从节点1到节点4(表示 x ⋅ y x \cdot y xy 的一个输入是 x x x
  3. 从节点2到节点4(表示 x ⋅ y x \cdot y xy 的另一个输入是 y y y
  4. 从节点2到节点5(表示 sin ⁡ ( y ) \sin(y) sin(y) 的输入是 y y y
  5. 从节点3和节点4到节点6(表示他们的结果被加在一起)
  6. 从节点6和节点5到节点7(表示前者的结果减去后者的结果来得到最终输出)
步骤4: 执行前向传播

现在你可以执行前向传播来计算输出,按照操作的顺序一步步前进,直到达到输出节点。

步骤5: (可选)执行反向传播

如果你还打算进行自动微分,你可以实施反向传播算法来计算相对于 x x x y y y 的偏导数。

代码实现:

///
/// \brief The Variable class
/// 自动微分
class Variable
{
public:
	//保存值
	double value;
	//保存梯度
	double grad;
	//当前的梯度是否启用
	bool isEnableGrad;

	Variable(double v = 0.0,bool requires_grad=true,double g=0.0) : value(v), isEnableGrad(requires_grad),grad(g){}



	std::function<void()> backpropFunc;
	std::vector<std::shared_ptr<Variable>> parents;

	//设置求导函数
	void setBackprop(const std::function<void()>& func)
	{
		backpropFunc = func;
	}

	//添加组成当前节点的节点
	void addParent(const std::shared_ptr<Variable>& parent)
	{
		parents.push_back(parent);
	}
	//反向传播
	void backward()
	{
		if (backpropFunc)
			backpropFunc();
		for (auto& parent : parents)
		{
			parent->backward();
		}
	}


};


//加法
inline std::shared_ptr<Variable> operator+(const std::shared_ptr<Variable>& a, const std::shared_ptr<Variable>& b)
{
	auto result = std::make_shared<Variable>(a->value + b->value);
	result->setBackprop([=]() {
		if(a->isEnableGrad)a->grad += result->grad;
		if (b->isEnableGrad)b->grad += result->grad;

	});
	result->addParent(a);
	result->addParent(b);

	return result;
}
//减法
inline std::shared_ptr<Variable> operator-(const std::shared_ptr<Variable>& a, const std::shared_ptr<Variable>& b)
{
	auto result = std::make_shared<Variable>(a->value - b->value);
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad += result->grad;
		if (b->isEnableGrad)b->grad -= result->grad;
	});
	result->addParent(a);
	result->addParent(b);
	return result;
}

//乘法
inline std::shared_ptr<Variable> operator*(const std::shared_ptr<Variable>& a, const std::shared_ptr<Variable>& b)
{
	auto result = std::make_shared<Variable>(a->value * b->value);
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad += b->value * result->grad;
		if (b->isEnableGrad)b->grad += a->value * result->grad;
	});
	result->addParent(a);
	result->addParent(b);
	return result;
}
// 除法
inline std::shared_ptr<Variable> operator/(const std::shared_ptr<Variable>& a, const std::shared_ptr<Variable>& b)
{
	if (b->value == 0.0) {
		std::cerr << "Error: Division by zero!" << std::endl;
		exit(1);
	}
	auto result = std::make_shared<Variable>(a->value / b->value);
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad += (1.0 / b->value) * result->grad;
		if (b->isEnableGrad)b->grad -= (a->value / (b->value * b->value)) * result->grad;
	});
	result->addParent(a);
	result->addParent(b);
	return result;
}
// sin
inline std::shared_ptr<Variable> sin(const std::shared_ptr<Variable>& a)
{
	auto result = std::make_shared<Variable>(std::sin(a->value));
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad += std::cos(a->value) * result->grad;
	});
	result->addParent(a);
	return result;
}
// cos
inline std::shared_ptr<Variable> cos(const std::shared_ptr<Variable>& a)
{
	auto result = std::make_shared<Variable>(std::cos(a->value));
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad -= std::sin(a->value) * result->grad;  // 注意这里是减号,因为cos的导数是-sin
	});
	result->addParent(a);

	return result;
}

// log
inline std::shared_ptr<Variable> log(const std::shared_ptr<Variable>& a)
{
	if (a->value <= 0.0) {
		std::cerr << "Error: Logarithm of non-positive number!" << std::endl;
		exit(1);
	}
	auto result = std::make_shared<Variable>(std::log(a->value));
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad += (1.0 / a->value) * result->grad;  // 导数为 1/x
	});
	result->addParent(a);

	return result;
}

// exp
inline std::shared_ptr<Variable> exp(const std::shared_ptr<Variable>& a)
{
	auto result = std::make_shared<Variable>(std::exp(a->value));
	result->setBackprop([=]() {
		if (a->isEnableGrad)a->grad += std::exp(a->value) * result->grad;  // 导数为 e^x
	});
	result->addParent(a);

	return result;
}



int main() {

		auto x = std::make_shared<Variable>(2.0);  // 创建一个初值为2的变量x
		auto y = std::make_shared<Variable>(5.0,false);  // false 不计算y的梯度

		auto g = log(x) + x * y - sin(y);
		g->grad = 1.0;  // df/df = 1
		g->backward();
		std::cout << "df/dx = " << x->grad << std::endl;
		std::cout << "df/dy = " << y->grad << std::endl;

	

	return 0;
}

在这里插入图片描述在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1468089.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IT廉连看——C语言——循环语句

IT廉连看——C语言——循环语句 循环语句分为三种&#xff1a; while for do while 一、while循环 我们已经掌握了&#xff0c;if语句&#xff1a; if(条件)语句; 当条件满足的情况下&#xff0c;if语句后的语句执行&#xff0c;否则不执行。 但是这个语句只会执行一次…

调度和管制机制

目录 1 调度机制 分组按优先级排队 公平排队 FQ (Fair Queuing) 加权公平排队 WFQ (Weighted Fair Queuing) WFQ 与 FIFO 的比较 2 管制机制 漏桶管制器 (leaky bucket policer) 3 漏桶机制与加权公平排队相结合 调度和管制机制是使互联网能够提供服务质量的重要措施。…

K线实战分析系列之三:吞没形态

K线实战分析系列之三&#xff1a;吞没形态 一、吞没形态二、看涨吞没形态三、看跌吞没形态四、吞没形态判别标准 一、吞没形态 两根或两根以上的K线形成的组合形态&#xff0c;吞没形态就是一种主要的反转形态。 这个形态由两根K线组成&#xff0c;前短后长&#xff0c;一阴一…

软考-中级-系统集成2023年综合知识(三)

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 软考中级专栏回顾 专栏…

Raspbian命令行RTSP/RTP服务

Raspbian命令行RTSP/RTP服务 1. 源由2. Raspbian摄像头2.1 命令行启动RTP摄像头2.2 命令行启动RTSP摄像头 3. 示例3.1 测试RTP摄像头3.2 测试RTSP摄像头3.3 QGroundControl测试3.3.1 RTSP配置3.3.2 RTP配置 4. 总结5. 参考资料 1. 源由 鉴于实际测试发现RTP协议下&#xff0c;…

K8S部署Java项目 pod报错 logs日志内容:no main manifest attribute, in app.jar

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

责任链模式与spring容器的搭配应用

背景 有个需求&#xff0c;原先只涉及到一种A情况设备的筛选&#xff0c;每次筛选会经过多个流程&#xff0c;比如先a功能&#xff0c;a功能通过再筛选b功能&#xff0c;然后再筛选c功能&#xff0c;以此类推。现在新增了另外一种B情况的筛选&#xff0c;B情况同样需要A情况的筛…

NestJS入门7:增加异常过滤器

前文参考&#xff1a; NestJS入门1 NestJS入门2&#xff1a;创建模块 NestJS入门3&#xff1a;不同请求方式前后端写法 NestJS入门4&#xff1a;MySQL typeorm 增删改查 NestJS入门5&#xff1a;加入Swagger NestJS入门6&#xff1a;日志中间件 本文代码基于上一篇文章《…

数字化转型导师坚鹏:政府数字化流程管理

政府数字化流程管理 课程背景&#xff1a; 很多政府存在以下问题&#xff1a; 不清楚数字化对流程有什么影响&#xff1f; 不知道政府业流程如何进行优化&#xff1f; 不知道政府业流程优化的具体案例&#xff1f; 课程特色&#xff1a; 有实战案例 有原创观点 …

【大厂AI课学习笔记NO.52】2.3深度学习开发任务实例(5)需求采集考虑维度

今天来学习&#xff0c;怎么做需求分析&#xff0c;如何明确数据采集需求。 我把自己考试通过的学习笔记&#xff0c;都分享到这里了&#xff0c;另外还有一个比较全的思维脑图&#xff0c;我导出为JPG文件了。下载地址在这里&#xff1a;https://download.csdn.net/download/g…

【C++私房菜】面向对象中的多态

文章目录 一、多态二、对象的静态类型和动态类型三、虚函数和纯虚函数1、虚函数2、虚析构函数3、抽象基类和纯虚函数4、多态的原理 四、重载、覆盖(重写)、隐藏(重定义)的对比 一、多态 OOP的核心思想是多态性(polymorphism)。多态性这个词源自希腊语&#xff0c;其含义是“多…

免费多域名证书,最多支持保护250个域名

随着企业规模扩大和多元化发展&#xff0c;拥有多个域名的需求变得普遍&#xff0c;此时&#xff0c;多域名SSL证书应运而生&#xff0c;并且这一类型的证书已经发展到能够安全地支持多达250个不同域名的加密需求。 多域名SSL证书&#xff0c;也称为SAN&#xff08;Subject Alt…

【《高性能 MySQL》摘录】第 2 章 MySQL基准测试

文章目录 2.1 为什么需要基准测试2.2 基准测试的策略2.2.1 测试何种指标 2.3 基准测试方法2.3.1 设计和规划基准测试2.3.2 基准测试应该运行多长时间2.3.3 获取系统性能和状态2.3.4 获得准确的测试结果2.3.5 运行基准测试并分析结果2.3.6 绘图的重要性 2.4 基准测试工具…

[面试] 什么是死锁? 如何解决死锁?

什么是死锁 死锁&#xff0c;简单来说就是两个或者多个的线程在执行的过程中&#xff0c;争夺同一个共享资源造成的相互等待的现象。如果没有外部干预线程会一直阻塞下去. 导致死锁的原因 互斥条件&#xff0c;共享资源 X 和 Y 只能被一个线程占用; 请求和保持条件&#xf…

【第七天】C++模板探秘:函数模板、类模板以及类型转换的深入解析

一、模板的概述 c面向对象编程思想&#xff1a;封装、继承、多态 c泛型编程思想&#xff1a;模板 模板的分类&#xff1a;函数模板、类模板 函数模板&#xff08;类模板&#xff09;&#xff1a;将功能相同&#xff0c;类型不同的函数&#xff08;类&#xff09;的类型抽象成虚…

Java+Vue:宠物猫认养系统的未来之路

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

网购商城系统源码 积分兑换商城系统源码 独立后台附教程

应用介绍 本文来自&#xff1a;网购商城系统源码 积分兑换商城系统源码 独立后台附教程 - 源码1688 简介&#xff1a; 网购商城系统源码 积分兑换商城系统源码 独立后台附教程 测试环境&#xff1a;NginxPHP7.0MySQL5.6thinkphp伪静态 图片&#xff1a;

进程与线程之进程的理解

首先对堆栈等进程运行过程中的内存有了更深层次的理解&#xff1a; 我们之前了解到&#xff0c;程序在运行中存在堆栈&#xff0c;字符串常量区代码区。 现在我们提出虚拟内存的概念&#xff1a;程序在运行的过程中开辟0~4G的虚拟空间使用MUU映射单元映射到物理地址上 简而言…

板块二 JSP和JSTL:第四节 EL表达式 来自【汤米尼克的JAVAEE全套教程专栏】

板块二 JSP和JSTL&#xff1a;第四节 EL表达式 一、什么是表达式语言二、表达式取值&#xff08;1&#xff09;访问JSP四大作用域&#xff08;2&#xff09;访问List和Map&#xff08;3&#xff09;访问JavaBean 三、 EL的各种运算符&#xff08;1&#xff09;.和[ ]运算符&…

Jmeter基础(3) 发起一次请求

目录 Jmeter 一次请求添加线程组添加HTTP请求添加监听器 Jmeter 一次请求 用Jmeter进行一次请求的过程&#xff0c;需要几个步骤呢&#xff1f; 1、添加线程组2、添加HTTP请求3、添加监听器&#xff0c;查看结果树 现在就打开jmeter看下如何创建一个请求吧 添加线程组 用来…