22. 深度学习 - 自动求导

在这里插入图片描述

Hi,你好。我是茶桁。

咱们接着上节课内容继续讲,我们上节课已经了解了拓朴排序的原理,并且简单的模拟实现了。我们这节课就来开始将其中的内容变成具体的计算过程。

linear, sigmoidloss这三个函数的值具体该如何计算呢?

我们现在似乎大脑已经有了一个起比较模糊的印象,可以通过它的输入来计算它的点。

让我们先把最初的父类Node改造一下:


class Node():def __init__(self, inputs=[], name=None):...self.value = None...

然后再复制出一个,和Placeholder一样,我们需要继承Node,并且改写这个方法自己独有的内容:

class Linear(Node):def __init__(self, x, k, b, name=None):Node.__init__(self, inputs=[x, k, b], name=name)def forward(self):x, k, b = self.inputs[0], self.inputs[1], self.inputs[2]self.value = k.value * x.value + b.valueprint('我是{}, 我没有人类爸爸,需要自己计算结果{}'.format(self.name, self.value))...

我们新定义的这个类叫Linear, 它会接收x, k, b。它继承了Node。这个里面的forward该如何计算呢? 我们需要每一个节点都需要一个值,一个变量,因为我们初始化的时候接收的x,k,b都赋值到了inputs里,这里我们将其取出来就行了,然后就是线性方程的公式k*x+b,赋值到它自己的value上。

然后接着呢,就轮到Sigmoid了,一样的,我们定义一个子类来继承Node:

class Sigmoid(Node):def __init__(self, x, name=None):Node.__init__(self, inputs=[x], name=name)self.x = self.inputs[0]def _sigmoid(self, x):return 1/(1+np.exp(-x))def forward(self):self.value = self._sigmoid(self.x.value)print('我是{}, 我自己计算了结果{}'.format(self.name, self.value))...

Sigmoid函数只接收一个参数,就是x,其公式为1/(1+e^{-x}),我们在这里定义一个新的方法来计算,然后在forward里把传入的x取出来,再将其送到这个方法里进行计算,最后将结果返回给它自己的value。

那下面自然是Loss函数了,方式也是一模一样:

class Loss(Node):def __init__(self, y, yhat, name=None):Node.__init__(self, inputs = [y, yhat], name=name)self.y = self.inputs[0]self.yhat = self.inputs[1]def forward(self):y_v = np.array(self.y.value)yhat_v = np.array(self.y_hat.value)self.value = np.mean((y.value - yhat.value) ** 2)print('我是{}, 我自己计算了结果{}'.format(self.name, self.value))...

那我们这里定义成Loss其实并不确切,因为我们虽然喊它是损失函数,但是其实损失函数的种类也非常多。而这里,我们用的MSE。所以我们应该定义为MSE,不过为了避免歧义,这里还是沿用Loss好了。

定义完类之后,我们参数调用的类名也就需要改一下了:

...
node_linear = Linear(x=node_x, k=node_k, b=node_b, name='linear')
node_sigmoid = Sigmoid(x=node_linear, name='sigmoid')
node_loss = Loss(y=node_y, yhat=node_sigmoid, name='loss')

好,这个时候我们基本完成了,计算之前让我们先看一下sorted_node:

sorted_node---
[Placeholder: y,Placeholder: k,Placeholder: x,Placeholder: b,Linear: Linear,Sigmoid: Sigmoid,MSE: Loss]

没有问题,我们现在可以模拟神经网络的计算过程了:

for node in sorted_nodes:node.forward()---
我是x, 我已经被人类爸爸赋值为3
我是b, 我已经被人类爸爸赋值为0.3737660632429008
我是k, 我已经被人类爸爸赋值为0.35915077292816744
我是y, 我已经被人类爸爸赋值为0.6087876106387002
我是Linear, 我没有人类爸爸,需要自己计算结果1.4512183820274032
我是Sigmoid, 我没有人类爸爸,需要自己计算结果0.8101858733432837
我是Loss, 我没有人类爸爸,需要自己计算结果0.04056126022042443

咱们这个整个过程就像是数学老师推公式一样,因为这个比较复杂。你不了解这个过程就求解不出来。

这就是为什么我一直坚持要手写代码的原因。c+v大法确实好,但是肯定是学的不够深刻。表面的东西懂了,但是更具体的为什么不清楚。

我们可以看到,我们现在已经将Linear、Sigmoid和Loss都将值计算出来了。那我们现在已经实现了从x到loss的前向传播

现在我们有了loss,那就又要回到我们之前机器学习要做的事情了,就是将损失函数loss的值降低。

之前咱们讲过,要将loss的值减小,那我们就需要求它的偏导,我们前面课程的求导公式这个时候就需要拿过来了。

然后我们需要做的事情并不是完成求导就好了,而是要实现「链式求导」。

那从Loss开始反向传播的时候该做些什么?先让我们把“口号”喊出来:

class Node:def __init__(...):......def backward(self):for n in self.inputs:print('获取∂{} / ∂{}'.format(self.name, n.name))

这样修改一下Node, 然后在其中假如一个反向传播的方法,将口号喊出来。

然后我们来看一下口号喊的如何,用[::-1]来实现反向获取:

for node in sorted_nodes[::-1]:node.backward()---
获取∂Loss / ∂y
获取∂Loss / ∂Sigmoid
获取∂Sigmoid / ∂Linear
获取∂Linear / ∂x
获取∂Linear / ∂k
获取∂Linear / ∂b

这样看着似乎不是太直观,我们再将node的名称加上去来看就明白很多:

for node in sorted_nodes[::-1]:print(node.name)node.backward()
---
Loss
获取∂Loss / ∂y
获取∂Loss / ∂Sigmoid
Sigmoid
获取∂Sigmoid / ∂Linear
Linear
获取∂Linear / ∂x
获取∂Linear / ∂k
获取∂Linear / ∂b
...

最后的k, y, x, b我就用…代替了,主要是函数。

那我们就清楚的看到,Loss获取了两个偏导,然后传到了Sigmoid, Sigmoid获取到一个,再传到Linear,获取了三个。那现在其实我们只要把这些值能乘起来就可以了。我们要计算步骤都有了,只需要把它乘起来就行了。

我们先是需要一个变量,用于存储Loss对某个值的偏导

class Node:def __init__(...):...self.gradients = dict()...

然后我们倒着来看, 先来看Loss:

class Loss(Node):...def backward(self):self.gradients[self.inputs[0]] = '∂{}/∂{}'.format(self.name, self.inputs[0].name)self.gradients[self.inputs[1]] = '∂{}/∂{}'.format(self.name, self.inputs[1].name)print('[0]: {}'.format(self.gradients[self.inputs[0]]))print('[1]: {}'.format(self.gradients[self.inputs[1]]))

眼尖的小伙伴应该看出来了,我现在依然还是现在里面进行「喊口号」的动作。主要是先来看一下过程。

刚才每个node都有一个gradients,它代表的是对某个节点的偏导。

现在这个节点self就是loss,然后我们self.inputs[0]就是y, self.inputs[1]就是yhat, 也就是node_sigmoid。那么我们现在这个self.gradients[self.inputs[n]]其实就分别是∂loss/∂y∂loss/∂yhat,我们把对的值分别赋值给它们。

然后我们再来看Sigmoid:

class Sigmoid(Node):...def backward(self):self.gradients[self.inputs[0]] = '∂{}/∂{}'.format(self.name, self.inputs[0].name)print('[0]: {}'.format(self.gradients[self.inputs[0]]))

我们依次来看哈,这个时候的self就是Sigmoid了,这个时候的sigmoid.inputs[0]应该是Linear对吧,然后我们整个self.gradients[self.inputs[0]]自然就应该是∂sigmoid/∂linear

我们继续,这个时候self.outputs[0]就是loss, loss.gradients[self]那自然就应该是输出过来的∂loss/∂sigmoid,然后呢,我们需要将这两个部分乘起来:

def backward(self):self.gradients[self.inputs[0]] = '*'.join([self.outputs[0].gradients[self], '∂{}/∂{}'.format(self.name, self.inputs[0].name)])print('[0]: {}'.format(self.gradients[self.inputs[0]]))

接着,我们就需要来看看Linear了:

def backward(self):self.gradients[self.inputs[0]] = '*'.join([self.outputs[0].gradients[self], '∂{}/∂{}'.format(self.name, self.inputs[0].name)])self.gradients[self.inputs[1]] = '*'.join([self.outputs[0].gradients[self], '∂{}/∂{}'.format(self.name, self.inputs[1].name)])self.gradients[self.inputs[2]] = '*'.join([self.outputs[0].gradients[self], '∂{}/∂{}'.format(self.name, self.inputs[2].name)])print('[0]: {}'.format(self.gradients[self.inputs[0]]))print('[1]: {}'.format(self.gradients[self.inputs[1]]))print('[2]: {}'.format(self.gradients[self.inputs[2]]))

和上面的分析一样,我们先来看三个inputs[n]的部分,self在这里是linear了,这里的self.inputs[n]分别应该是x, k, b对吧,那么它们就应该分别是linear.gradients[x], linear.gradients[k]linear.gradients[b], 也就是∂linear/∂x,∂linear/∂k, ∂linear/∂b

那反过来,outputs就应该反向来找,那么self.outputs[0]这会儿就应该是sigmoid。sigmoid.gradients[self]就是前一个输出过来的∂loss/∂sigmoid * ∂sigmoid/∂linear, 那后面以此的[1]和[2]我们也就应该明白了。

然后后面分别是∂linear/∂x,∂linear/∂k, ∂linear/∂b。一样,我们将它们用乘号连接起来。

公式就应该是:

∂ l o s s ∂ s i g m o i d ⋅ ∂ s i g m o i d ∂ l i n e a r ⋅ ∂ l i n e a r ∂ x ∂ l o s s ∂ s i g m o i d ⋅ ∂ s i g m o i d ∂ l i n e a r ⋅ ∂ l i n e a r ∂ k ∂ l o s s ∂ s i g m o i d ⋅ ∂ s i g m o i d ∂ l i n e a r ⋅ ∂ l i n e a r ∂ b \begin{align*} \frac{\partial loss}{\partial sigmoid} \cdot \frac{\partial sigmoid}{\partial linear} \cdot \frac{\partial linear}{\partial x} \\ \frac{\partial loss}{\partial sigmoid} \cdot \frac{\partial sigmoid}{\partial linear} \cdot \frac{\partial linear}{\partial k} \\ \frac{\partial loss}{\partial sigmoid} \cdot \frac{\partial sigmoid}{\partial linear} \cdot \frac{\partial linear}{\partial b} \\ \end{align*} sigmoidlosslinearsigmoidxlinearsigmoidlosslinearsigmoidklinearsigmoidlosslinearsigmoidblinear

那同理,我们还需要写一下Placeholder

def Placeholder(Node):...def backward(self):print('我获取了我自己的gradients: {}'.format(self.outputs[0].gradients[self]))...

好,我们来看下我们模拟的情况如何,看看它们是否都如期喊口号了, 结合我们之前的前向传播的结果,我们一起来看:

for node in sorted_nodes:node.forward()for node in sorted_nodes[::-1]:print('\n{}'.format(node.name))node.backward()---
Loss
[0]: ∂Loss/∂y
[1]: ∂Loss/∂SigmoidSigmoid
[0]: ∂Loss/∂Sigmoid*∂Sigmoid/∂LinearLinear
[0]: ∂Loss/∂Sigmoid*∂Sigmoid/∂Linear*∂Linear/∂x
[1]: ∂Loss/∂Sigmoid*∂Sigmoid/∂Linear*∂Linear/∂k
[2]: ∂Loss/∂Sigmoid*∂Sigmoid/∂Linear*∂Linear/∂bk
我获取了我自己的gradients: ∂Loss/∂Sigmoid*∂Sigmoid/∂Linear*∂Linear/∂kb
我获取了我自己的gradients: ∂Loss/∂Sigmoid*∂Sigmoid/∂Linear*∂Linear/∂bx
我获取了我自己的gradients: ∂Loss/∂Sigmoid*∂Sigmoid/∂Linear*∂Linear/∂xy
我获取了我自己的gradients: ∂Loss/∂y

好,观察下来没问题,那我们现在还剩下最后一步。就是将这些口号替换成真正的计算的值, 其实很简单,就是将我们之前学习过并写过的函数替换进去就可以了:

class Linear(Node):...def backward(self):x, k, b = self.inputs[0], self.inputs[1], self.inputs[2]self.gradients[self.inputs[0]] = self.outputs[0].gradients[self] * k.valueself.gradients[self.inputs[1]] = self.outputs[0].gradients[self] * x.valueself.gradients[self.inputs[2]] = self.outputs[0].gradients[self] * 1...class Sigmoid(Node):...def backward(self):self.value = self._sigmoid(self.x.value)self.gradients[self.inputs[0]] = self.outputs[0].gradients[self] * self.value * (1 - self.value)...class Loss(Node):...def backward(self):y_v = self.y.valueyhat_v = self.y_hat.valueself.gradients[self.inputs[0]] = 2*np.mean(y_v - yhat_v)self.gradients[self.inputs[1]] = -2*np.mean(y_v - yhat_v)

那我们来看下真正计算的结果是怎样的:

for node in sorted_nodes[::-1]:print('\n{}'.format(node.name))node.backward()---
Loss
∂Loss/∂y: -0.402796525409167
∂Loss/∂Sigmoid: 0.402796525409167Sigmoid
∂Sigmoid/∂Linear: 0.06194395247945269Linear
∂Linear/∂x: 0.02224721841122111
∂Linear/∂k: 0.18583185743835806
∂Linear/∂b: 0.06194395247945269y
gradients: -0.402796525409167k
gradients: 0.18583185743835806b
gradients: 0.06194395247945269x
gradients: 0.02224721841122111

好,到这里,我们就实现了前向传播和反向传播,让程序自动计算出了它们的偏导值。

不过我们整个动作还没有结束,就是我们需要将loss降低到最小才可以。

那我们下节课,就来完成这一步。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第14届蓝桥杯青少组python试题解析:23年5月省赛

选择题 T1. 执行以下代码&#xff0c;输出结果是&#xff08;&#xff09;。 lst "abc" print(lstlst)abcabc abc lstlst abcabc T2. 执行以下代码&#xff0c;输出的结果是&#xff08;&#xff09;。 age {16,18,17} print(type(sorted(age)))<class set&…

vscode 推送本地新项目到gitee

一、gitee新建仓库 1、填好相关信息后点击创建 2、创建完成后复制 https&#xff0c;稍后要将本地项目与此关联 3、选择添加远程存储库 4、输入仓库地址&#xff0c;选择从URL添加远程存储仓库 5、输入仓库名称&#xff0c;确保仓库名一致

【SQL server】数据库、数据表的创建

创建数据库 --如果存在就删除 --所有的数据库都存在sys.databases当中 if exists(select * from sys.databases where name DBTEST)drop database DBTEST--创建数据库 else create database DBTEST on --数据文件 (nameDBTEST,--逻辑名称 字符串用单引号filenameD:\DATA\DBT…

esp-01刷固件/下载软件到内部单片机的方法

此文章为转载&#xff0c;非原创 一、准备 需要准备三个东西&#xff1a; 1.esp模块。ESP-01 和 ESP-01s 的引脚及 flash 容量基本完全兼容&#xff0c;只是内部硬件设计粗糙与否的区别&#xff0c;所以理论上都适用。 2.官方固件。此部分可以从安信可官方教程中下载&#xff0…

计算机网络——物理层-信道的极限容量(奈奎斯特公式、香农公式)

目录 介绍 奈氏准则 香农公式 介绍 信号在传输过程中&#xff0c;会受到各种因素的影响。 如图所示&#xff0c;这是一个数字信号。 当它通过实际的信道后&#xff0c;波形会产生失真&#xff1b;当失真不严重时&#xff0c;在输出端还可根据已失真的波形还原出发送的码元…

深度学习之基于YoloV5血红细胞检测识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习已经在许多领域中得到了广泛的应用&#xff0c;包括医疗健康领域。其中&#xff0c;YOLO&#xff08;You O…

SpringMVC 进阶

SpringMVC 进阶 一、拦截器 SpringMVC 中 Interceptor 拦截器的主要作⽤是拦截⽤⼾的请求并进⾏相应的处理。⽐如通过它来进⾏权限验证&#xff0c;或者是来判断⽤⼾是否登陆等操作。对于 SpringMVC 拦截器的定义⽅式有两种&#xff1a; 实现接⼝&#xff1a;org.springfram…

【Linux网络】从原理到实操,感受PXE无人值守自动化高效批量网络安装系统

一、PXE网络批量装机的介绍 1、常见的三种系统安装方式 2、回顾系统安装的过程&#xff0c;了解系统安装的必要条件 3、什么是pxe 4、搭建pxe的原理 5、Linux的光盘镜像中的isolinux中的相关文件学习 二、关于实现PXE无人值守装机的四大文件与五个软件的对应关系详解 5个…

超详细~25考研规划~感恩现在努力的你!!!

25考研规划 俄语&#xff0c;翻译过来叫我爱你 考试时间 第一天 8.30-11.30政治——100分 2.00-5.00英语——100分 第二天 8.30-11.30数学——150分 2.00-5.00专业课——150分 1.什么是25考研 将在2024年12月参加考研&#xff0c;2025年本科毕业&#xff0c;9月读研究…

探索Scrapy中间件:自定义Selenium中间件实例解析

简介 Scrapy是一个强大的Python爬虫框架&#xff0c;可用于从网站上抓取数据。本教程将指导你创建自己的Scrapy爬虫。其中&#xff0c;中间件是其重要特性之一&#xff0c;允许开发者在爬取过程中拦截和处理请求与响应&#xff0c;实现个性化的爬虫行为。 本篇博客将深入探讨…

SpringCloud 微服务全栈体系(十四)

第十一章 分布式搜索引擎 elasticsearch 四、RestAPI ES 官方提供了各种不同语言的客户端&#xff0c;用来操作 ES。这些客户端的本质就是组装 DSL 语句&#xff0c;通过 http 请求发送给 ES。官方文档地址&#xff1a;https://www.elastic.co/guide/en/elasticsearch/client/…

Python 自动化(十八)admin后台管理

admin后台管理 什么是admin后台管理 django提供了比较完善的后台数据库的接口&#xff0c;可供开发过程中调用和测试使用 django会搜集所有已注册的模型类&#xff0c;为这些模型类提供数据管理界面&#xff0c;供开发使用 admin配置步骤 创建后台管理账号 该账号为管理后…

程序员带你入门人工智能

随着人工智能技术的飞速发展&#xff0c;越来越多的程序员开始关注并学习人工智能。作为程序员&#xff0c;我们可能会对如何开始了解人工智能感到困惑。今天&#xff0c;我将向大家介绍一些如何通过自学了解人工智能的经验和方法&#xff0c;帮助大家更好地入门这个充满挑战和…

李沐的学习Pytorch环境配置

https://github.com/Miraclelucy/dive_into_deep_learning/tree/main 上面是别人的笔记 可以学一下。 如果没有梯子&#xff0c;按照清华源配置 清华源conda配置 最好下载 1.11版本torch那一套 然后装d2l版本可以装 pip install d2l0.17.6然后可以用 http://localhost:8889/…

学习笔记6——垃圾回收

学习笔记系列开头惯例发布一些寻亲消息 链接&#xff1a;https://baobeihuijia.com/bbhj/contents/3/190801.html java垃圾回收&#xff08;stop the world&#xff09; 专注于堆和方法区的垃圾回收&#xff0c;年轻代&#xff0c;老年代&#xff0c;永久代判断对象是否还存…

Linux shell编程学习笔记26:stty(set tty)

之前我们探讨了Linux中的tty&#xff0c;tty命令的主要功能是显示当前使用的终端名称。 如果我们想进一步对tty进行设置&#xff0c;就要用到stty。 stty的功能&#xff1a;显示和修改终端特性&#xff08;Print or change terminal characteristics&#xff09;。 1 stty -…

关于新能源汽车的英语翻译

近年来&#xff0c;随着全球对环保和可持续发展的重视&#xff0c;新能源汽车已经成为汽车产业的重要发展方向。各国政府和企业都在加大投入&#xff0c;推动新能源汽车的技术研发和产业化发展&#xff0c;进而促进了新能源汽车翻译的需求不断提升 。那么&#xff0c;关于新能源…

智慧城市安全监控的新利器

在传统的城市管理中&#xff0c;井盖的监控一直是一个难题&#xff0c;而井盖异动传感器的出现为这一问题提供了有效的解决方案。它具有体积小、重量轻、安装方便等特点&#xff0c;可以灵活地应用于各种类型的井盖&#xff0c;实现对城市基础设施的全方位监控。 智能井盖监测终…

Android图片涂鸦,Kotlin(1)

Android图片涂鸦&#xff0c;Kotlin&#xff08;1&#xff09; import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Path import android.graphics.PointF import android.…

RobotFramework之用例执行时添加命令行参数(十三)

学习目录 引言 标签tag 设置变量 随机执行顺序 设置监听器 输出日志目录和文件 引言 Robot Framework 提供了许多命令行选项&#xff0c;可用于控制测试用例的执行方式以及生成的输出。本节介绍一些常用的选项语法。 标签tag 之前文章我们介绍过&#xff0c;在测试套件…