简单神经网络的求梯度介绍
程序员文章站
2022-05-21 22:40:13
...
先构造一个如下图所示的简单神经网络
图中的直线均表示从左到右的箭头。
我们的目标是求字典型变量grads,grads['W']表示损失函数关于权重W的梯度,grads['b']表示损失函数关于偏置b的梯度。如下图所示
我们构造一个神经网络类simpleNet来实现求梯度的的目标,代码如下所示
import sys,os
sys.path.append(os.pardir)
import numpy as np
from ch03.softmax import softmax
from cross_entropy_error import cross_entropy_error
from numerical_gradient import numerical_gradient
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 利用高斯分布初始化2x3大小的权重数组
self.b = np.zeros(3) # 初始化长度为且元素全为0的一维偏置数组
def predict(self, x): # 推理函数
z = np.dot(x,self.W) + self.b # 计算加权和z
y = softmax(z) # 将z通过输出层的**函数转化得到推理结果y
return y
def loss(self, x, t): # 损失函数
y = self.predict(x) # 求得推理结果y
loss = cross_entropy_error(y,t) # 调用交叉熵误差函数求出损失函数值
return loss
def gradient(self, x, t): # 梯度函数
FX = lambda X: self.loss(x,t) # 创建匿名函数FX,入口参数为X,函数体内容实际上就是return self.loss(x,t)
# 我们要求的是损失函数关于权重参数的梯度。由于已实现的求梯度函数numerical_gradient(f,x)要求
# f的入口参数显示包含x,而已经实现的损失函数loss没有将权重参数作为入口参数,所以构造了形式损失函数FX。
# 之后便可以将FX和相应权重参数X作为numerical_gradient的入口参数了。
grads = {} # 定义字典型变量grads
grads['W'] = numerical_gradient(FX, self.W) # 求损失函数关于W的梯度
grads['b'] = numerical_gradient(FX, self.b) # 求损失函数关于b的梯度
return grads
network = simpleNet() # 创建简单神经网络对象network
x = np.array([0.6,0.9]) # 设定输入参数x和正确解标签t
t = np.array([0,0,1])
print(network.W) # 打印权重参数W和偏置参数b
print(network.b)
grads = network.gradient(x,t) # 求W和b的梯度,并打印
print(grads['W'])
print(grads['b'])
上边所用的求梯度函数numerical_gradient与之前讲的稍稍有所不同,多考虑了维度的变化(权重参数W现在是二维的)。代码如下所示
import numpy as np
# 计算梯度的函数
def numerical_gradient_1d(f,x): # 入口参数为待求导函数f和变量数组x (所求梯度就是一个由函数f关于各个变量的偏导数组成的数组)
h = 1e-4 # 定义微小值h为0.0001(便于直接使用定义求偏导数)
grad = np.zeros_like(x) # 生成和x形状相同且所有元素都为0的数组grad
for idx in range(x.size): # 依次取每一个变量的数组下标
tmp_val = x[idx] # 记录第idx个元素的数值,存放在tmp_val变量中
x[idx] = tmp_val + h # 将x[idx]修改为tmp_val+h
fxh1 = f(x) # 对当前x数组求f函数值,得到函数值fxh1(表示f(x+h),实际上这里的x+h表示仅x[idx]加了h后得到的数组)
x[idx] = tmp_val - h # 将x[idx]修改为tmp_val-h
fxh2 = f(x) # 对当前x数组求f函数值,得到函数值fxh2(表示f(x-h),实际上这里的x-h表示仅x[idx]减了h后得到的数组)
grad[idx] = (fxh1 - fxh2)/(2*h) # 求得x[idx]的偏导数,放入梯度数组第idx单元
x[idx] = tmp_val # 将x[idx]恢复原值,为求下一个变量的偏导数做准备
return grad
def numerical_gradient(f, X):
if X.ndim == 1: # 权重参数为一维时,直接调用一维梯度函数numerical_gradient
return numerical_gradient_1d(f, X)
else: # 权重参数大于一维时
grad = np.zeros_like(X) # 定义一个与权重矩阵形状相同的梯度矩阵grad
for idx, x in enumerate(X): # 逐行调用一维梯度函数numerical_gradient,求出梯度矩阵grad
grad[idx] = numerical_gradient_1d(f, x)
return grad
运行结果如下所示
这样就求出了我们所需要的梯度,为接下来的更新梯度等操作做了准备。
# 本博客参考了《深度学习入门——基于Python的理论与实现》(斋藤康毅著,陆宇杰译),特在此声明。