一、前文回顾
什么是梯度,简而言之就是对多元函数的每个参数求偏导数,然后以向量的形式写出来,这就是梯度。例如
它对于参数x1的偏导数为2*x1,参数x2的偏导数为2*x2,那么它的梯度为向量(2*x1,2*x2)。对于该函数在点(2,3)的具体梯度grad f(2,3)=(4,6)。
那么梯度代表的意义是什么?就是该函数在该点变化最快的方向。或者说沿着这个梯度方向可以最快找到函数的最小值(或者最大值)。
二、梯度下降
神经网络所谓的自主学习,其实就是一个寻找最佳权重参数值的过程。或者说寻找损失函数最小值的过程。在一个复杂的损失函数(高维度)中寻找最小值光靠眼睛看肯定是不行的,而这时候梯度就好比一个指南针一样,告诉了我们该往哪个方向走才有可能达到最小值。既然我们都有指南针了,那就走一步看一眼指南针,岂不是很快就可以找到最小值了?没错,理论就是这么简单,而这个方法就叫梯度下降法。
三、实现梯度下降法
梯度下降法就是从初始地点开始,沿着梯度方向前进一小段距离,然后再计算新位置的梯度,并且继续沿着梯度方向走,计算梯度→走一步→计算梯度→走一步,就这样边走边看最终就会达到函数最小值。
理论都懂了,接下来就是用python实现了。
def gradient_descent(f, init_x, lr=0.01, step_num=100):
"""
f为损失函数,
int_x代表初期值,
lr代表学习率,也就是每一步走多远,
step_num是走多少步
"""
x = init_x
x_history = []
#
for i in range(step_num):
x_history.append(x.copy())
# 计算梯度(调用上一节的求梯度方法)
grad = _numerical_gradient_no_batch(f, x)
# grad = numerical_gradient(f, x)
# 更新参数(走一步)
x -= lr * grad
return x, np.array(x_history)
注意在这里引入两个新的概念:学习率和步数。学习率其实就是每一步你要走多远,这个参数的设定主要凭经验和不断的实验。设定太大的话,步子迈太大容易扯到D,设定太小的话,步子迈太小又显得太娘,半天都挪不动半米远。
步数也很好理解,就是总共走多少步就停下来,设定太大的话,你可能会很早就到最小值了,但是因为步数还没走完就只能原地踏步浪费很多时间。步数太小的话,还没到最小值因为步数走完了就停下来了。所以这个值也是凭经验和不断实验来设定的。
我们用一开始的函数作为例子,看一下梯度下降法是怎么一步步找到最小值的。
# 定义函数
def f(x):
return x[0] ** 2 + x[1] ** 2
if __name__ == "__main__":
init_x = np.array([-3.0, 4.0])
# 设定学习率,也就是步长
lr = 0.1
# 设定步数,走100步就停
step_num = 100
# 按照梯度下降法找最小值
x, x_history = gradient_descent(f, init_x, lr=lr, step_num=step_num)
print(x)
# 用图形来展示过程
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()
执行一下看看效果
这里为了更直观的展示梯度下降法的过程,没有使用三维图,而是用二维图来展示。我们可以看到每一个实心圆点就代表走过的每一步,从轨迹来看最终走到了最低点(0,0)处(严格来讲是无限接近于最小值处),这时候在这个最小值处的参数值(-6.11110793e-10 ,8.14814391e-10)便是我们想要的最佳权重参数。
四、随机梯度下降法(SGD)
我们以前在实现小D识数字的时候说过,为了加快运行速度,采用批量处理法,也就是每一次从所有样本随机抽取一个批次的样本进行推理。而随机梯度下降法(Stochastic Gradient Descent)就是随机抽取一个批次的样本,然后一次性算出每一个样本的梯度。
五、神经网络的梯度
随机梯度下降法我们已经完全掌握了,接下来看看梯度在神经网络中怎么运用。
我们知道在神经网络中权重参数是用矩阵来表示的,比如
而相对于损失函数E,它的梯度相应的就是
可以看出和前面讲的其实一样,也是对于损失函数的每个参数求偏导数,然后用矩阵表示出来,比如第一行第一个元素代表的是权重参数w11稍微变化一丢丢,损失函数E相应的发生多少变化。
接下来用Python来实现一下或许更容易懂。
# 定义一个简单的神经网络类
class simpleNet:
def __init__(self):
# 初期化权重参数,2行3列的矩阵,代表该神经网络有2个输入,3个输出,无中间层
self.W = np.random.randn(2, 3)
print("初期权重参数:", self.W)
def predict(self, x):
# 推理 W*x+B过程(此处忽略B)
return np.dot(x, self.W)
# 求损失
def loss(self, x, t):
# 先推理
z = self.predict(x)
# softmax函数进行分类
y = softmax(z)
# 用交叉熵损失函数求损失
loss = cross_entropy_error(y, t)
return loss
# 如果参数是高维数组,求梯度函数需要相应修改
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
# 使用迭代数组访问x
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
# 循环取出数组x的每一个元素
idx = it.multi_index
tmp_val = x[idx]
x[idx] = 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
然后我们通过这个简易的神经网络来算一下梯度
if __name__ == "__main__":
# 输入神经元赋值,两个神经元,一个0.6,另一个0.9
x = np.array([0.6, 0.9])
# 输出神经元的正确标签[0,0,1]
t = np.array([0, 0, 1])
# 生成神经网络
net = simpleNet()
# def f(W):
# return net.loss(x, t)
# 定义损失函数
f = lambda w: net.loss(x, t)
# 求损失函数的梯度
dW = numerical_gradient(f, net.W)
# 输出梯度
print("梯度:", dW)
以下为输出:
初期权重参数: [[-0.44699123 -0.64247416 -0.17642992]
[-0.68196722 -0.13443141 -0.48333021]]
梯度:[[ 0.15535118 0.22614597 -0.38149715]
[ 0.23302677 0.33921896 -0.57224572]]
可以看出来梯度也是一个2*3的数组,其中第一行第一列A就是对于损失函数,权重参数W11的偏导数,也就是当参数W11变化h时,损失函数的增加量为0.15h。而第一行第三列的梯度为-0.38,表示当参数W13变化h时,损失函数减少量为0.38h。因为我们要寻找损失函数的最小值,所以W11需要往负方向(参数需要不断减小)更新,而W13需要往正方向(参数需要不断增加)更新,又因为W13的梯度大于W11,所以表示参数W13的贡献更大。
六、总结
这节学习了什么是梯度,梯度代表的意义,以及通过梯度下降法调整参数,下一节把这些零零散散的归纳起来,完整系统的看看神经网络是怎么进行自主学习的。
Comments