上一个策略中,我们尝试的是随机权重,然后找到随机权重中最好的一批(Loss最小的那一批权重值)。
其实不需要随机寻找权重,因为我们可以直接计算出最好的方向,也就是从数学上计算出最陡峭的方向。这个方向就是损失函数的梯度(gradient)。
对一维函数进行求导,当函数包含多个参数的时候,我们称导数为偏导数。而梯度就是在每个维度上偏导数所形成的向量。
计算梯度有两种方法:一种是缓慢的近似方法,即数值梯度法,其实现相对来说比较简单;另一种方法是分析梯度法,虽然其计算迅速,结果精确,但是实现时容易出错,且需要使用微分。
本节主要针对数值梯度法进行讲解: 如果是对上述公式编写一 个Python函数的话,则可以写成:
def eval_numerical_gradient(f,x):
h = 0.00001
return (f(x+h)-f(x))/h
上述实现中,该函数接受了两个参数,即“函数f”和参数x。 实践考量: 注意在数学公式中,h的取值是趋近于0的,然而在实际中,用一个很小的数值(比 如例子中的1e-5)来代替就足够了。
在数值计算不出错的理想前提下,应尽可能地使用较小的h。还有,实际中使用中心差值公式(centered difference formula)效果较好。实现代码具体如下:
def eval_numerical_gradient(f,x):
h = 0.00001
return (f(x+h)-f(x-h))/ (2*h)
1.梯度 之前的例子中,我们只计 算了一个变量的导数,如果存在多个x(比如:x0,x1,x2,x3,…,xn)的偏导数,则由全部变量的偏导数汇总而成的向量即为梯度(gradient),实现代码具体如下:
def numerical_gradient(f,x):
h = 0.00001
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
#f(x+h)的计算
x[idx] = tmp_val + h
fxh1 = f(x)
#f(x-h)的计算
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val
return grad
函数numerical_gradient(f,x)中的实现看上去比较复杂,其实就是针对x中的每一个值都去做一下单个的eval_numerical_gradient运算 罢了。
其中,np.zeros_like(x)会生成一个形状与x相同且所有元素都为0的数组。
2.梯度下降法
之前详细介绍过,这里只是稍微补充一下,虽然梯度的方向并不一定指向函数的最小值(可能存在局部最小值的可能性,因而没有找到全局最小值),但的确是在沿着它的方向尽可能地减少函数的值。
下面我们利用Python语言来实现梯度下降法,实现代码具体如下:
def gradient_descent(f,init_x,lr=0.01,step_num=100):
x =init_x
for i in range(step_num):
grad = numerical_gradient(f,x)
x -= lr*grad
return x
其中,参数f是要进行最优化的函数,init_x是初始值,lr表示learning_rate(其是个超参数,需要自己调整),step_num代表梯度下降 法的重复数。
3.神经网络的梯度下降法 神经网络的学习也要求梯度,这里的梯度所代表的是损失函数中关于权重以及偏移量(bias)的梯度。比如一个形状为2*2的权重为W的神经网络,损失函数用L表示,那么对于:
其梯度表示为: 的元素由各个元素关于W的偏导数构成。对于每一个偏导数,其表示的意义是,当每个 W稍微变化的时候,损失函数L会发生多大的变化(这里的和W的形状是相同的。)
4.补充概念:
Np.nditer import numpy as np
arr1 = np.arange(0,30,5).reshape(2,3)
it = np.nditer(arr1, flags=['multi_index'],op_flags=['readwrite'])
while not it.finished:
print(it.multi_index)
it.iternext()
输出如下: (0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2) flags=['multi_index']
表示对a进行多重索引,具体解释请看下面的示例代码。 上述代码段中参数的解释如下。 ·op_flags=['readwrite']表示 不仅可以对a进行read(读取),还可以write(写入),即相当于在创建这个迭代器的时候,我们就规定好了其具有哪些权限。
·print(it.multi_index)表示输出元素的索引,可以看到输出的结果都是index。
·it.iternext()表示进入下一次迭代,如果不加这一条语句的话,输出的结果就会一直都是(0,0)。
5.讲了这么多的知识点,读 者可能会觉得对于一些概念还不是那么理解,所以本节打算再讲解一个小案例,将上述知识点做一个贯穿以帮助大家理解。
案例的背景如下:输入一个X(人工识别这个X的图像为狗),让机器自动判断该图像的分类,其中,图像为三分类(类别分别为鸡、猫、狗),真实标签的分类为y=[0,0,1](标签已经转为one-hot类型,代表是狗)。
假设我们有一个数据集X,X赋值为[[0.6,0.9]](已经将肉眼 识别的狗的图片转为了矩阵),从代码中能够看到X的形状为(1,2),代表的是1行2列:
import numpy as np
X = np.array([[0.6,0.9]])
print(X.shape)
接着我们来定义一个简单的神经网络,在__init__初始化方法里,我们初始化了一个符合高斯分布的W矩阵,其大小为(2,3),并且为了保证效果的可靠性,我们设置了ran dom.seed(0)方法以保证每一次随机的W都是一致的;
另外在Forward方法里,我们实现了前向传播;在Loss方法里,我们主要的实现思路是通过Forward方法得到预测值,经过Softmax方法转为相加之和为1的概率矩阵,之后再通过cross_entropy_error方法计算损失值Loss。
现在大家都了解了我们的目的就是通过W的调整(利用梯度下降法)使得Loss值不断减少。详细代码如下:
class simpleNet:
def __init__(self):
np.random.seed(0)
self.W = np.random.randn(2,3)
def forward(self,x):
return np.dot(x,self.W)
def loss(self,x,y):
z = self.forward(x)
p = _softmax(z)
loss = cross_entropy_error(p,y)
return loss Softmax
以及cross_entropy_error的实现方式分别在以后进行了详细讲解,如果读者有疑问请翻至对应章节阅读学习。
接着,我们初始化一下简单神经网络,然后输出看下目前随机的W分别是哪些值,实现代码如下:
net = simpleNet()
print(net.W)
X= np.array([[0.6,0.9]])
p = net.predict(X)
print('预测值为:',p)
print('预测的类别为: ',np.argmax(p)) 因为W是随机的,所以读者的输出结果与这里的输出结果可能会不一致,这里的输出结果仅供参考,我们可以发现,结果中预测类别是0对应的类别是鸡,很明显预测错误。
[[ 1.76405235 0.40015721 0.97873798]
[ 2.2408932 1.86755799
-0.97727788]]
预测值为: [[ 3.07523529 1.92089652 -0.2923073 ]]
预测的类别为: 0 下面我们进一步看一下此时的损失值Loss: y = np.array([0,0,1]) #输入正确类别
print(net.loss(x,y)) 计算一下损失值,我们会发现Loss非常大,其值为15.706416957363151,这个就需要基于数值微分的梯度下降法 。
来进行优化了!详细代码如下:
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
def gradient_descent(f,init_x,lr=0.01,step_num=1000):
x =init_x
for i in range(step_num):
grad = numerical_gradient(f,x)
x -= lr*grad
return x
f = lambda w: net.loss(x,y)
dw = gradient_descent(f,net.W)
#需要更新的主要是W
print(dw) 上述代码比较简单,而且之前陆陆续续已经讲解过了,这里就不再赘述了,我们此时输出dw观察下,通过梯度下降之后的dw的值具体如下:
[[-4.09592461e-02 -8.44400784e-01 4.02830757e+00]
[-4.66624189e-01 7.21000464e-04 3.59707650e+00]]
最后我们来验证下,通过计算出来的dw值,我们重新计算下损失值以及预测的类别,可以发现我们的损失值降低了,并且类别也预测正确了,具体代码如下:
print('损失值变为: ',cross_entropy_error(_softmax (np.dot(x,dw)),y))
print('预测类别为: ',np.argmax(np.dot(x,dw)))
上述代码输出结果如下: 损失值变为: 0.06991990559251195
预测类别为: 2
希望通过这个案例,读者对前文的知识点能有一个直观的了解。
网友评论