1.梯度:
上一篇中我们按变量分别计算了x0和x1的偏导数,但是我们希望的是一起计算x0和x1的偏导数。比如说,我们希望求得x0=3、x1=4时的偏导数。
$$\left( \frac{\partial f}{\partial x_0}\text{ ,}\frac{\partial f}{\partial x_1} \right) $$
这样由全部变量的偏导数汇总而成的向量就称为“梯度(gradient)”。
import numpy as np
# 定义函数
def function(x):
return x[0]**2 + x[1]**2
# 等同于下面的方法
# return np.sum(x**2)
# 定义梯度求解函数
def numerical_gradient(f,x):
h = 1e-4
# 生成一个和x形状一样的数组
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
# 计算f(x+h)
x[idx] =tmp_val + h
f_xh1 = f(x)
# 计算f(x+h)
x[idx] =tmp_val - h
f_xh2 = f(x)
grad[idx] = (f_xh1 - f_xh2) / (2*h)
# 还原值
# x[idx] = tmp_val
return grad
# 求解函数在x0=3、x1=4处的梯度
print(numerical_gradient(function,np.array([3.0,4.0])))
运行得到结果
[6. 8.]
这里之所以显示为[6. 8.]
,是因为在输出numpy数组时,数值会被显示成“易读”的形式。
为了更形象解释,我们将函数的梯度画图表达。
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
def _numerical_gradient_no_batch(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
return grad
def numerical_gradient(f, X):
if X.ndim == 1:
return _numerical_gradient_no_batch(f, X)
else:
grad = np.zeros_like(X)
for idx, x in enumerate(X):
grad[idx] = _numerical_gradient_no_batch(f, x)
return grad
def function_2(x):
if x.ndim == 1:
return np.sum(x**2)
else:
return np.sum(x**2, axis=1)
def tangent_line(f, x):
d = numerical_gradient(f, x)
print(d)
y = f(x) - d*x
return lambda t: d*t + y
if __name__ == '__main__':
x0 = np.arange(-2, 2.5, 0.25)
x1 = np.arange(-2, 2.5, 0.25)
X, Y = np.meshgrid(x0, x1)
X = X.flatten()
Y = Y.flatten()
grad = numerical_gradient(function_2, np.array([X, Y]) )
plt.figure()
plt.quiver(X, Y, -grad[0], -grad[1], angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")
plt.xlim([-2, 2])
plt.ylim([-2, 2])
plt.xlabel('x0')
plt.ylabel('x1')
plt.grid()
plt.legend()
plt.draw()
plt.show()
运行得到结果:

不过,需要注意的是,图中的元素是元素值为负梯度的向量(也就是后续提到的梯度法中变量的更新方向)。
可以从图中看出,函数的梯度呈现为有向向量(箭头),并且所有箭头都指向函数的最小值处,并且离最小值处越远,箭头越长。
梯度指示的方向就是各点处的函数值减小最多的方向。
2.梯度法:
神经网络的主要目标就是寻找使得损失函数最小时的最优参数,通过使用梯度来寻找函数最小值或者尽可能小的值(因为梯度表示的是各点处的函数值减小最多的方向,但是没办法保证梯度所指的方向就是函数的最小值)的方法就是梯度法。
梯度法(gradient method)其实就是通过不断的沿着梯度的方向前进,逐渐减小函数值的过程。
根据求解问题不同,梯度法的叫法也不相同。寻找最小值的梯度法称为梯度下降法(gradient descent method),而寻找最大值的梯度法称为梯度上升法(gradient ascent method)。但是可以通过反转损失函数的符号,使得两者变成一样的问题,也就是寻找最小值。
一般来说,神经网络中,梯度法主要是指梯度下降法。
用数学式子表达梯度法如下所示:
$$
x_0=x_0-\eta \frac{\partial f}{\partial x_0}
$$
$$
x_1=x_1-\eta \frac{\partial f}{\partial x_1}
$$
其中η表示更新量,即在多大程度上改变参数,在神经网络中称为学习率(learning rate),在神经网络中学习率需要不断调整从而找打合适的取值。
我们现在通过代码实现用梯度法求解函数的最小值。
import numpy as np
# 定义函数
def function(x):
return x[0]**2 + x[1]**2
# 等同于下面的方法
# return np.sum(x**2)
# 定义梯度求解函数
def numerical_gradient(f,x):
h = 1e-4
# 生成一个和x形状一样的数组
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
# 计算f(x+h)
x[idx] =tmp_val + h
f_xh1 = f(x)
# 计算f(x+h)
x[idx] =tmp_val - h
f_xh2 = f(x)
grad[idx] = (f_xh1 - f_xh2) / (2*h)
# 还原值
x[idx] = tmp_val
return grad
# 定义实现梯度下降法函数
def gradient_descent(f, init_x, lr, step_num):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f,x)
x -= lr*grad
return x
# 使用梯度法求函数的最小值
print(gradient_descent(function,init_x=np.array([-3.0,4.0]),lr=0.1,step_num=100))
运行得到结果
[-6.11110793e-10 8.14814391e-10]
可以看到结果已经非常接近(0,0)了,所以说通过梯度法基本得到了正确的结果。
下面通过代码用图来表示梯度法的更新过程。
import numpy as np
import matplotlib.pylab as plt
# 定义梯度求解函数
def numerical_gradient(f,x):
h = 1e-4
# 生成一个和x形状一样的数组
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
# 计算f(x+h)
x[idx] =tmp_val + h
f_xh1 = f(x)
# 计算f(x+h)
x[idx] =tmp_val - h
f_xh2 = f(x)
grad[idx] = (f_xh1 - f_xh2) / (2*h)
# 还原值
x[idx] = tmp_val
return grad
# 定义实现梯度下降法函数
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
# 保存每次更新得到的值
x_history = []
for i in range(step_num):
x_history.append(x.copy())
grad = numerical_gradient(f, x)
x -= lr * grad
return x, np.array(x_history)
# 定义函数
def function_2(x):
return x[0] ** 2 + x[1] ** 2
init_x = np.array([-3.0, 4.0])
lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)
print(x)
print(x_history)
# 画图
plt.plot( [-5, 5], [0,0], '--b')
plt.plot( [0,0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')
plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()
运行得到结果

可以看出,通过梯度法,函数的取值逐渐向最小值处靠近。
3.神经网络的梯度:
神经网络的梯度是指损失函数关于权重参数的梯度,例如,有一个形状为2×3的权重W
的神经网络,损失函数用L
表示,此时,神经网络的梯度用数学表达式表示如下:

下面用一个简单的神经网络来实现求解梯度的过程。
import numpy as np
# 定义softmax函数
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # 溢出对策
return np.exp(x) / np.sum(np.exp(x))
# 定义交叉熵误差函数
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size:
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
# 定义梯度求解函数
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
# 定义类
class simpleNet:
def __init__(self):
# 对W进行初始化
self.W = np.random.randn(2, 3)
# 定义预测函数
def predict(self, x):
return np.dot(x, self.W)
# 定义损失函数
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t)
return loss
# 给定输入x的值
x = np.array([0.6, 0.9])
# 给定正确的标签
t = np.array([0, 0, 1])
net = simpleNet()
print("W初始化值为:", net.W)
# 计算梯度,先定义一个计算损失函数的新函数f
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
print("求得的梯度为:", dW)
运行得到结果
W初始化值为: [[ 0.86595961 0.87388942 -1.2888172 ]
[-0.66973145 -1.86272037 0.85993859]]
求得的梯度为: [[ 0.24682956 0.0847551 -0.33158467]
[ 0.37024435 0.12713265 -0.497377 ]]
通过上述代码我们求得了梯度,然后利用求得的梯度,来更新权重参数即可。
Reference:
《Deep Learning from Scratch》