心得
在本课程的心得前,先来讲一点课程的学习操作上的经验,也许聪明的学生第一科就知道了,我是操作了好几课程后总结出来的。
一、先看下图:
图左边有一个蓝色的条状图标,指示当前准备运行的位置。
图的上端的工具栏,有个播放按钮,点击后,执行当前位置的代码。有的是阅览。有的是代码框,是需要运行的。运行后有结果的。
二、再看下图
代码框前面有星号,表明,此代码尚未运行。
三、再看下图
点击运行后,要等待一段时间。。。一定要等。。。等前面的星星没有了,变成数字了,表明这段代码执行结束。代码框的下面有执行结果。这个执行结果,在教程打开的初始状态是没有的。
四、进入本课程的心得:
今天这个课程主要通过函数的执行,来讲几个函数。这些函数是神经网络的训练主要使用的反向传播算法。很奇妙啊,算法又出来了。算法用在神经元的传播之间。其实一点也不奇怪。神经网络不是凭空产生的。一定是有基础衍生的。这个基础其实就是传统的过程算法。就像微积分,一定需要加减乘除。
然后,可以发现,这些函数,处理的变量很多都是张量类型的。所以,把张量看成一种类型的变量,它也是有其处理的函数的。只是这个变量的组成成分复杂一点。
打卡截图:
函数式自动微分
神经网络的训练主要使用反向传播算法,模型预测值(logits)与正确标签(label)送入损失函数(loss function)获得loss,然后进行反向传播计算,求得梯度(gradients),最终更新至模型参数(parameters)。自动微分能够计算可导函数在某点处的导数值,是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算,该功能对用户屏蔽了大量的求导细节和过程,大大降低了框架的使用门槛。
MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口grad
和value_and_grad
。下面我们使用一个简单的单层线性变换模型进行介绍。
[1]:
%%capture captured_output
# 实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
[2]:
import numpy as np
import mindspore
from mindspore import nn
from mindspore import ops
from mindspore import Tensor, Parameter
函数与计算图
计算图是用图论语言表示数学函数的一种方式,也是深度学习框架表达神经网络模型的统一方法。我们将根据下面的计算图构造计算函数和神经网络。
在这个模型中,𝑥𝑥为输入,𝑦𝑦为正确值,𝑤𝑤和𝑏𝑏是我们需要优化的参数。
[3]:
x = ops.ones(5, mindspore.float32) # input tensor
y = ops.zeros(3, mindspore.float32) # expected output
w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w') # weight
b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b') # bias
我们根据计算图描述的计算过程,构造计算函数。 其中,binary_cross_entropy_with_logits 是一个损失函数,计算预测值和目标值之间的二值交叉熵损失。
[4]:
def function(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss
执行计算函数,可以获得计算的loss值。
[5]:
loss = function(x, y, w, b)
print(loss)
0.29692236
微分函数与梯度计算
为了优化模型参数,需要求参数对loss的导数:∂loss∂𝑤∂loss∂𝑤和∂loss∂𝑏∂loss∂𝑏,此时我们调用mindspore.grad
函数,来获得function
的微分函数。
这里使用了grad
函数的两个入参,分别为:
fn
:待求导的函数。grad_position
:指定求导输入位置的索引。
由于我们对𝑤𝑤和𝑏𝑏求导,因此配置其在function
入参对应的位置(2, 3)
。
使用
grad
获得微分函数是一种函数变换,即输入为函数,输出也为函数。
[6]:
grad_fn = mindspore.grad(function, (2, 3))
执行微分函数,即可获得𝑤𝑤、𝑏𝑏对应的梯度。
[7]:
grads = grad_fn(x, y, w, b)
print(grads)
(Tensor(shape=[5, 3], dtype=Float32, value= [[ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]]), Tensor(shape=[3], dtype=Float32, value= [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]))
Stop Gradient
通常情况下,求导时会求loss对参数的导数,因此函数的输出只有loss一项。当我们希望函数输出多项时,微分函数会求所有输出项对参数的导数。此时如果想实现对某个输出项的梯度截断,或消除某个Tensor对梯度的影响,需要用到Stop Gradient操作。
这里我们将function
改为同时输出loss和z的function_with_logits
,获得微分函数并执行。
[8]:
def function_with_logits(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss, z
[9]:
grad_fn = mindspore.grad(function_with_logits, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
(Tensor(shape=[5, 3], dtype=Float32, value= [[ 1.11649573e+00, 1.12105942e+00, 1.00315404e+00], [ 1.11649573e+00, 1.12105942e+00, 1.00315404e+00], [ 1.11649573e+00, 1.12105942e+00, 1.00315404e+00], [ 1.11649573e+00, 1.12105942e+00, 1.00315404e+00], [ 1.11649573e+00, 1.12105942e+00, 1.00315404e+00]]), Tensor(shape=[3], dtype=Float32, value= [ 1.11649573e+00, 1.12105942e+00, 1.00315404e+00]))
可以看到求得𝑤𝑤、𝑏𝑏对应的梯度值发生了变化。此时如果想要屏蔽掉z对梯度的影响,即仍只求参数对loss的导数,可以使用ops.stop_gradient
接口,将梯度在此处截断。我们将function
实现加入stop_gradient
,并执行。
[10]:
def function_stop_gradient(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss, ops.stop_gradient(z)
[11]:
grad_fn = mindspore.grad(function_stop_gradient, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
(Tensor(shape=[5, 3], dtype=Float32, value= [[ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]]), Tensor(shape=[3], dtype=Float32, value= [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]))
可以看到,求得𝑤𝑤、𝑏𝑏对应的梯度值与初始function
求得的梯度值一致。
Auxiliary data
Auxiliary data意为辅助数据,是函数除第一个输出项外的其他输出。通常我们会将函数的loss设置为函数的第一个输出,其他的输出即为辅助数据。
grad
和value_and_grad
提供has_aux
参数,当其设置为True
时,可以自动实现前文手动添加stop_gradient
的功能,满足返回辅助数据的同时不影响梯度计算的效果。
下面仍使用function_with_logits
,配置has_aux=True
,并执行。
[12]:
grad_fn = mindspore.grad(function_with_logits, (2, 3), has_aux=True)
[13]:
grads, (z,) = grad_fn(x, y, w, b)
print(grads, z)
(Tensor(shape=[5, 3], dtype=Float32, value= [[ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]]), Tensor(shape=[3], dtype=Float32, value= [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03])) [-0.62129426 -0.5615954 -4.650951 ]
可以看到,求得𝑤𝑤、𝑏𝑏对应的梯度值与初始function
求得的梯度值一致,同时z能够作为微分函数的输出返回。
神经网络梯度计算
前述章节主要根据计算图对应的函数介绍了MindSpore的函数式自动微分,但我们的神经网络构造是继承自面向对象编程范式的nn.Cell
。接下来我们通过Cell
构造同样的神经网络,利用函数式自动微分来实现反向传播。
首先我们继承nn.Cell
构造单层线性变换神经网络。这里我们直接使用前文的𝑤𝑤、𝑏𝑏作为模型参数,使用mindspore.Parameter
进行包装后,作为内部属性,并在construct
内实现相同的Tensor操作。
[14]:
# Define model
class Network(nn.Cell):
def __init__(self):
super().__init__()
self.w = w
self.b = b
def construct(self, x):
z = ops.matmul(x, self.w) + self.b
return z
接下来我们实例化模型和损失函数。
[15]:
# Instantiate model
model = Network()
# Instantiate loss function
loss_fn = nn.BCEWithLogitsLoss()
完成后,由于需要使用函数式自动微分,需要将神经网络和损失函数的调用封装为一个前向计算函数。
[16]:
# Define forward function
def forward_fn(x, y):
z = model(x)
loss = loss_fn(z, y)
return loss
完成后,我们使用value_and_grad
接口获得微分函数,用于计算梯度。
由于使用Cell封装神经网络模型,模型参数为Cell的内部属性,此时我们不需要使用grad_position
指定对函数输入求导,因此将其配置为None
。对模型参数求导时,我们使用weights
参数,使用model.trainable_params()
方法从Cell中取出可以求导的参数。
[17]:
grad_fn = mindspore.value_and_grad(forward_fn, None, weights=model.trainable_params())
[18]:
loss, grads = grad_fn(x, y)
print(grads)
(Tensor(shape=[5, 3], dtype=Float32, value= [[ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03], [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]]), Tensor(shape=[3], dtype=Float32, value= [ 1.16495721e-01, 1.21059470e-01, 3.15404218e-03]))
执行微分函数,可以看到梯度值和前文function
求得的梯度值一致。
[19]:
import time
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),'guojun0718')
2024-07-13 14:09:08 guojun0718
[ ]: