23. 深度学习 - 多维向量自动求导

茶桁的AI秘籍 核心能力 23

Hi, 你好。我是茶桁。

前面几节课中,我们从最初的理解神经网络,到讲解函数,多层神经网络,拓朴排序以及自动求导。 可以说,最难的部分已经过去了,这节课到了我们来收尾的阶段,没错,生长了这么久,终于到迎接成果的时候了。

好,让我们开始。

我们还是用上一节课的代码:21.ipynb

我们上一节课中,实现了自动计算的部分。

for node in sorted_nodes[::-1]:print('\n{}'.format(node.name))node.backward()

结果我就不打印了,节省篇幅。

那我们到这一步之后,咱们就已经获得了偏导,现在要考虑的问题就是去更新它,去优化它的值。

learning_rate = 1e-5for node in sorted_nodes:node.value = node.value + -1 * node.gradients[node] * learning_rate

node的值去更新,就应该等于它本身的值加上一个-1乘以它的偏导在乘以一个learning_rate, 我们对这个是不是已经很熟悉了?我们从第8节线性回归的时候就一直在接触这个公式。

只不过在这个地方,x, y的值也要更新吗? 它们的值是不应该去更新的,那要更新的应该是k, b的值。

那么在这个地方该怎么办呢?其实很简单,我们添加一个判断就可以了:

for node in sorted_nodes:if node.is_trainable:node.value = node.value + -1 * node.gradients[node] * learning_rate

然后我们给之前定义的类上加一个变量用于判断。

class Node:def __init__(..., is_trainable=False):...self.is_trainable = is_trainable

在这里我们默认是不可以训练的,只有少数的一些是需要训练的。

然后我们在初始化的部分把这个定义的值加上:

node_k = Placeholder(name='k', is_trainable=True)
node_b = Placeholder(name='b', is_trainable=True)

对了,我们还需要将Placeholder做些改变:

class Placeholder(Node):def __init__(..., is_trainable=False):Node.__init__(.., is_trainable=is_trainable)......

这就意味着,运行for循环的时候只有k和b的值会更新,我们再加几句话:

for node in sorted_nodes:if node.is_trainable:...cmp = 'large' if node.gradients[node] > 0 else 'small'print('{}的值{},需要更新。'.format(node.name, cmp))---
k的值small,需要更新。
b的值small,需要更新。

我们现在将forward, backward和optimize的三个循环封装乘三个方法:

def forward(graph_sorted_nodes):# Forwardfor node in sorted_nodes:node.forward()def backward(graph_sorted_nodes):# Backwardfor node in sorted_nodes[::-1]:print('\n{}'.format(node.name))node.backward()def optimize(graph_sorted_nodes, learning_rate=1e-3):# optimizefor node in sorted_nodes:if node.is_trainable:node.value = node.value + -1 * node.gradients[node] * learning_ratecmp = 'large' if node.gradients[node] > 0 else 'small'print('{}的值{},需要更新。'.format(node.name, cmp))

然后我们再来定义一个epoch方法,将forward和backward放进去一起执行:

def run_one_epoch(graph_sorted_nodes):forward(graph_sorted_nodes)backward(graph_sorted_nodes)

这样,我们完成一次完整的求值-求导-更新,就可以写成这样:

run_one_epoch(sorted_nodes)
optimize(sorted_nodes)

为了更好的观察,我们将所有的print都删掉,然后在backward方法中写一个观察loss的打印函数:

def backward(graph_sorted_nodes):# Backwardfor node in sorted_nodes[::-1]:if isinstance(node, Loss):print('loss value: {}'.format(node.value))node.backward()

然后我们来对刚才完整的过程做个循环:

# 完整的一次求值-求导-更新:
for _ in range(10):run_one_epoch(sorted_nodes)optimize(sorted_nodes, learning_rate=1e-1)---
loss value: 0.12023025149136042
loss value: 0.11090709486917472
loss value: 0.10118818479676453
loss value: 0.09120180962480523
loss value: 0.08111466190584131
loss value: 0.0711246044819575
loss value: 0.061446239826641165
loss value: 0.05229053883349982
loss value: 0.043842158831920566
loss value: 0.036239620745126

可以看到loss在一点点的下降。当然,这样循环10次我们还能观察出来,但是我们如果要成百上千次的去计算它,这样可就不行了, 那我们需要将history存下来,然后用图来显示出来:

loss_history = []
for _ in range(100):..._loss_node = sorted_nodes[-1]assert isinstance(_loss_node, Loss)loss_history.append(_loss_node.value)optimize(sorted_nodes, learning_rate=1e-1)plt.plot(loss_history)

Alt text

我们现在可以验证一下,我们拟合的yhat和真实的y之间差距有多大,首先我们当然是要获取到每个值的下标,然后用sigmoid函数来算一下:

sorted_nodes---
[k, y, x, b, Linear, Sigmoid, Loss]

通过下标来进行计算, k是0, x是2, b是3, y是1:

def sigmoid(x):return 1/(1+np.exp(-x))# k*x+b
sigmoid_x = sorted_nodes[0].value * sorted_nodes[2].value + sorted_nodes[3].value
print(sigmoid(sigmoid_x))# y
print(sorted_nodes[1].value)---
0.891165479601981
0.8988713384533658

可以看到,非常的接近。那说明我们拟合的情况还是不错的。

好,这里总结一下,就是我们有了拓朴排序,就能向前去计算它的值,通过向前计算的值就可以向后计算它的值。那现在其实我们已经完成了一个mini的深度学习框架的核心内容,咱们能够定义节点,能够前向传播运算,能够反向传播运算,能更新梯度了。

那接下来是不是就结束了呢?很遗憾,并没有,接着咱们还要考虑如何处理多维数据。咱们现在看到的数据都是x、k、b的输入,也就是都是一维的。

然而咱们真实世界中大多数场景下其实都是多维度的,其实都是多维数组。那么多维数组的还需要更新些什么,和现在有什么区别呢?

我们来接着往后看,因为基本上写法和现在这些几乎完全一样,那我也就不这么细致的讲了。

为了和之前代码做一个区分,所以我将多维向量计算的代码从新开了个文件,放在了23.ipynb里,小伙伴可以去下载到本地研习。

那么多维和现在最大的区别在哪里呢?就在于计算的时候,我们就要用到矩阵运算了。只是值变成了矩阵,运算变成的了矩阵运算。好,我们从Node开始来改动它,没什么变化的地方我就直接用...来省略了:

class Node:def __init__(self, input=[]):...def forward(self):raise NotImplementeddef backward(self):raise NotImplementedclass Placeholder(Node):def __init__(self):Node.__init__(self)def forward(self, value=None):...def backward(self):self.gradients = {self:0}for n in self.outputs:grad_cost = n.gradients[self]self.gradients[self] = grad_cost * 1class Linear(Node):def __init__(self, x, k, b):...def forward(self):...def backward(self):self.gradients = {n: np.zeros_like(n.value) for n in self.inputs}for n in self.outputs:grad_cost = n.gradients[self]self.gradients[self.inputs[0]] = np.dot(grad_cost, self.inputs[1].value.T)self.gradients[self.inputs[1]] = np.dot(self.inputs[0].value.T, grad_cost)self.gradients[self.inputs[2]] = np.sum(grad_cost, axis=0, keepdims=False)class Sigmoid(Node):def __init__(self, node):Node.__init__(self, [node])def _sigmoid(self, x):...def forward(self):...def backward(self):self.partial = self._sigmoid(self.x) * (1 - self._sigmoid(self.x))self.gradients = {n: np.zeros_like(n.value) for n in self.inputs}for n in self.outputs:grad_cost = n.gradients[self]  self.gradients[self.inputs[0]] = grad_cost * self.partialclass MSE(Node): # 也就是之前的Loss类def __init__(self, y, a):Node.__init__(self, [y, a])def forward(self):y = self.inputs[0].value.reshape(-1, 1)a = self.inputs[1].value.reshape(-1, 1)assert(y.shape == a.shape)self.m = self.inputs[0].value.shape[0]self.diff = y - aself.value = np.mean(self.diff**2)def backward(self):self.gradients[self.inputs[0]] = (2 / self.m) * self.diffself.gradients[self.inputs[1]] = (-2 / self.m) * self.diff

类完成之后,我们还有一些其他的方法:

def forward_and_backward(graph): # run_one_epochfor n in graph:n.forward()for n in  graph[::-1]:n.backward()
def toplogic(graph):...
def convert_feed_dict_to_graph(feed_dict):...
# 将sorted_nodes赋值从新定义了一个方法
def topological_sort_feed_dict(feed_dict):graph = convert_feed_dict_to_graph(feed_dict)return toplogic(graph)def optimize(trainables, learning_rate=1e-2):for node in trainables:node.value += -1 * learning_rate * node.gradients[node]

这样就完成了。可以发现基本上代码没有什么变动,变化比较大的都是各个类中的backward方法,因为要将其变成使用矩阵运算。

我们来尝试着用一下这个多维算法,我们还是用波士顿房价的那个数据来做一下尝试:

X_ = data['data']
y_ = data['target']# Normalize data
X_ = (X_ - np.mean(X_, axis=0)) / np.std(X_, axis=0)n_features = X_.shape[1]
n_hidden = 10
W1_ = np.random.randn(n_features, n_hidden)
b1_ = np.zeros(n_hidden)
W2_ = np.random.randn(n_hidden, 1)
b2_ = np.zeros(1)# Neural network
X, y = Placeholder(), Placeholder()
W1, b1 = Placeholder(), Placeholder()
W2, b2 = Placeholder(), Placeholder()l1 = Linear(X, W1, b1)
s1 = Sigmoid(l1)
l2 = Linear(s1, W2, b2)
cost = MSE(y, l2)feed_dict = {X: X_,y: y_,W1: W1_,b1: b1_,W2: W2_,b2: b2_
}epochs = 5000
# Total number of examples
m = X_.shape[0]
batch_size = 16
steps_per_epoch = m // batch_sizegraph = topological_sort_feed_dict(feed_dict)
trainables = [W1, b1, W2, b2]print("Total number of examples = {}".format(m))

我们在中间定义了l1, s1, l2, cost, 分别来实例化四个类。然后我们就需要根据数据来进行迭代计算了,定义一个losses来保存历史数据:

losses = []epochs = 100for i in range(epochs):loss = 0for j in range(steps_per_epoch):# Step 1X_batch, y_batch = resample(X_, y_, n_samples=batch_size)X.value = X_batchy.value = y_batch# Step 2forward_and_backward(graph) # set output node not important.# Step 3rate = 1e-2optimize(trainables, rate)loss += graph[-1].valueif i % 100 == 0: print("Epoch: {}, Loss: {:.3f}".format(i+1, loss/steps_per_epoch))losses.append(loss/steps_per_epoch)---
Epoch: 1, Loss: 194.170
...
Epoch: 4901, Loss: 3.137

可以看到它loss下降的非常快,还记得咱们刚开始的时候在训练波士顿房价数据的时候,那个loss下降到多少? 最低是不是就下降到在第一节课的时候我们的lose最多下降到了多少47.34对吧?那现在呢?直接下降到了3,这是为什么? 因为我们的维度多了,维度多了它就准确了。这说明什么? 说明大家去谈恋爱的时候,不要盯着对象的一个方面,多方面考察,才能知道这个人是否合适。

好,现在看起来效果是很好,但是我们想知道到底拟合出来的什么函数,那怎么办?咱们把这个维度降低成三维空间就可以看了。

现在咱们这个波士顿的所有数据实际上是一个15维的数据,15维的数据你根本看不了,咱们现在只要把x这个里边取一点值,在这个里边稍微把值给它变一下。

X_ = dataframe[['RM', 'LSTAT']]
y_ = data['target']

在咱们之前的课程中对其进行计算的时候就分析过,RM和LSTAT是影响最大的两个特征,我们还是来用这个。然后我们将刚才的代码从新运行一遍:

losses = []for i in tqdm_notebook(range(epochs)):...---
Epoch: 1, Loss: 150.122
...
Epoch: 4901, Loss: 16.181

这次下降的就没上次好了。

现在我们可视化一下这个三维空间来看看:

from mpl_toolkits.mplot3d import Axes3Dpredicate_results = []
for rm, ls in X_.values:X.value = np.array([[rm, ls]])forward_and_backward(graph)predicate_results.append(graph[-2].value[0][0])%matplotlib widgetfig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')X_ = dataframe[['RM', 'LSTAT']].values[:, 0]
Y_ = dataframe[['RM', 'LSTAT']].values[:, 1]Z = predicate_resultsrm_and_lstp_price = ax.plot_trisurf(X_, Y_, Z, color='green')ax.set_xlabel('RM')
ax.set_ylabel('% of lower state')
ax.set_zlabel('Predicated-Price')

然后我们就能看到一个数据的三维图形,因为我们开启了widget, 所以可以进行拖动。

Alt text

Alt text

从图形上看,确实符合房间越多,低收入人群越少,房价越高的特性。

那现在计算机确实帮我们自动的去找到了一个函数,这个函数到底怎么设置咱们都不用关心,它自动就给你求解出来,这个就是深度学习的意义。咱们经过这一系列写出来的东西其实就已经能够做到。

我觉得这个真的有一种数学之美,它从最简单的东西出发,最后做成了这样一个复杂的东西。确实很深其,并且还都在我们的掌握之中。

好,大家下来以后记得要多多自己敲代码,多分析其中的一些过程和原理。

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

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

相关文章

算法通关村第十二关-白银挑战字符串经典题目

大家好我是苏麟 , 今天带来字符串相关的题目 . 大纲 反转问题字符串反转K个一组反转仅仅反转字母反转字符串中的单词 反转问题 字符串反转 描述 : 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s的形式给出。 题目 : LeetCode 344. 反转…

webshell之扩展免杀

由于很多企业为了防止源码泄露,都会使用加密扩展将代码进行加密,那么我们就可以就将计就计,将webshell也利用扩展加密,将特征消除,从而达到免杀的效果 1.php-beast 扩展地址 下载dll,并添加至ext中 在php…

MySQL中自增id用完怎么办?

MySQL中自增id用完怎么办? MySQL里有很多自增的id,每个自增id都是定义了初始值,然后不停地往上加步长。虽然自然数是没有上限的,但是在计算机里,只要定义了表示这个数的字节长度,那它就有上限。比如&#…

python数据结构与算法-15_堆与堆排序

堆(heap) 前面我们讲了两种使用分治和递归解决排序问题的归并排序和快速排序,中间又穿插了一把树和二叉树, 本章我们开始介绍另一种有用的数据结构堆(heap), 以及借助堆来实现的堆排序,相比前两种排序算法要稍难实现一些。 最后我…

Linux开发工具(含gdb调试教程)

文章目录 Linux开发工具(含gdb调试教程)1、Linux 软件包管理器 yum2、Linux开发工具2.1、Linux编辑器 -- vim的使用2.1.1、vim的基本概念2.1.2、vim的基本操作2.1.3、vim正常模式命令集2.1.4、vim末行模式命令集 2.2、vim简单配置 3、Linux编译器 -- gcc…

redis之cluster集群

1、redis-cluster集群:redis3.0引入的分布式存储方案 2、集群:由多个node节点组成,redis数据分布在这些节点之中 (1)在集群之中也分主节点和从节点 (2)自带哨兵模式 3、redis-cluster集群的…

腾讯云 小程序 SDK对象存储 COS使用记录,原生小程序写法。

最近做了一个项目,需求是上传文档,文档类型多种,图片,视频,文件,doc,xls,zip,txt 等等,而且文档类型可能是大文件,可能得上百兆,甚至超过1G。 腾讯云文档地址:https://c…

PC端页面进去先出现加载效果

自定义指令v-loading&#xff0c;只需要绑定Boolean即可 v-loading“loading” <el-table :data"list" border style"width: 100%" v-loading"loading"><el-table-column align"center" label"序号" width"5…

开发板启动进入系统以后再挂载 NFS 文件系统, 这里的NFS文件系统是根据正点原子教程制作的ubuntu_rootfs

如果是想开发板启动进入系统以后再挂载 NFS 文件系统&#xff0c;开发板启动进入文件系统&#xff0c;开发板和 ubuntu 能互相 ping 通&#xff0c;在开发板文件系统下新建一个目录 you&#xff0c;然后执行如下指令进行挂载&#xff1a; mkdir mi mount -t nfs -o nolock,nfsv…

日本it就职培训机构,日本IT行业的三种类型

日本的IT产业一直保持增长趋势&#xff0c;市场规模逐年增加&#xff0c;在日本所有产业中占据很大比例。由于日本老龄化严重&#xff0c;日本国内的IT人才无法满足需求&#xff0c;为缓解这一问题&#xff0c;日本将引进外国优秀IT人才作为一项国策&#xff0c;日本IT行业不仅…

Leetcode1410. HTML 实体解析器

Every day a Leetcode 题目来源&#xff1a;1410. HTML 实体解析器 解法1&#xff1a;模拟 遍历字符串 text&#xff0c;每次遇到 ’&‘&#xff0c;就判断以下情况&#xff1a; 双引号&#xff1a;字符实体为 &quot; &#xff0c;对应的字符是 " 。单引号&a…

振弦式土压力计在岩土工程安全监测应用的方案

振弦式土压力计在岩土工程安全监测应用的方案 振弦式土压力计是一种常见的土压力测量仪器&#xff0c;其原理是利用振弦在土中传播的速度与土的应力状态有关的特点测量土压力。在岩土工程安全监测中&#xff0c;振弦式土压力计可以应用于以下方面&#xff1a; 1. 地下连续墙和…

某资产管理机构: IAST提升安全水平,保障资产管理水平稳健增长

某资产管理机构是国内首批成立的资产管理公司之一&#xff0c;坚持“科技金融”、“数字金融”战略&#xff0c;以客户为中心&#xff0c;聚焦用户体验与业务协同&#xff0c;着力推进营销数字化进程和大数据平台建设&#xff0c;助力资产管理高质量发展。 数字科技推动工作效率…

面试题:Java 对象不使用时,为什么要赋值 null ?

文章目录 前言示例代码运行时栈典型的运行时栈Java的栈优化提醒 GC一瞥提醒 JVM的“BUG”总结 前言 最近&#xff0c;许多Java开发者都在讨论说&#xff0c;“不使用的对象应手动赋值为null“ 这句话&#xff0c;而且好多开发者一直信奉着这句话&#xff1b;问其原因&#xff…

【Flask使用】全知识md文档,4大部分60页第3篇:Flask模板使用和案例

本文的主要内容&#xff1a;flask视图&路由、虚拟环境安装、路由各种定义、状态保持、cookie、session、模板基本使用、过滤器&自定义过滤器、模板代码复用&#xff1a;宏、继承/包含、模板中特有变量和函数、Flask-WTF 表单、CSRF、数据库操作、ORM、Flask-SQLAlchemy…

nvm切换版本之后npm用不了

原因是 nvm只给你安了对应的node没给你安装对应的node版本的npm 解决办法如下 1找到你安装的node版本号 然后去官网下载对应的版本包 这个网址就是node官网的版本列表 Index of /download/release/ 2下载后解压 把根目录这俩复制到自己的nvm安装目录下 还有那个node_modul…

Java【XML 配置文件解析】

前言 最近考试周忙得要死&#xff0c;但我却不紧不慢&#xff0c;还有三天复习时间&#xff0c;考试科目几乎都还没学呢。今天更新一个算是工具类-XML文件的解析&#xff0c;感觉还是挺有用的&#xff0c;之后可以融进自己的项目里。 XML 配置文件解析 0、导入依赖 有点像我…

企业软件定制开发的优势|app小程序网站搭建

企业软件定制开发的优势|app小程序网站搭建 企业软件定制开发是一种根据企业特定需求开发定制化软件的服务。相比于购买现成的软件产品&#xff0c;企业软件定制开发具有许多优势。 1.企业软件定制开发可以满足企业独特需求。每个企业都有自己独特的业务流程和需求&#xff0c;…

在 Redis 中使用 JSON 文档:命令行界面(CLI)和 Navicat 集成

Redis&#xff0c;因其极高的性能而闻名&#xff0c;是一款多功能的 NoSQL 数据库&#xff0c;擅长处理键值对。虽然 Redis主要用于处理简单数据结构&#xff0c;但是同样支持更多复杂的数据类型&#xff0c;如列表、集合甚至是 JSON 文件。在本文&#xff0c;我们将深入到 Red…

SAP LU04记账更改通知单创建转储单报错:L3094 记帐修改没有份存在

解决办法&#xff1a; 使用事务码LU02&#xff0c;修改过账更改状态&#xff0c;将过账更改状态改为U&#xff0c;强制关闭 1. LU04 查找记账更改通知单号 2. 事务码LU02修改状态 这个时候再用LU04去查看的时候&#xff0c;就不会再显示了