【深度学习】【PaddlePaddle】DAY 3 - 构建神经网络模型(二)

Chapter 2 构建神经网络模型

2.2 代码实现-构建神经网络模型

(4)训练过程

上述计算过程描述了如何构建神经网络,通过神经网络完成预测值和损失函数的计算。接下来介绍如何求解参数w和b的数值,这个过程也称为模型训练过程。训练过程是深度学习模型的关键要素之一,其目标是让定义的损失函数Loss尽可能的小,也就是说找到一个参数解w和b,使得损失函数取得极小值。

总结来说,计算损失函数由三个部分组成:真实样本的特征x、真实样本的输出y和参数。由于真实样本是确定的,因此损失函数为以参数为自变量的一个函数。

右图假设横轴自变量为参数,纵轴因变量为y,当曲线达到最低点时斜率为0,即导数为0。导数为0的点为函数的极值点。

理论上,Loss函数对参数w和参数b分别取导数,令导数为0,将样本数据(x,y)带入上面的方程组中即可求解出w和b的值,能求解出参数w和b。

但实际上应用,导函数很可能为不可逆的函数或有不可逆的倾向,即由x求y容易,由y求x难。因此可以采用摸索的形式找到最低点,这种方法为梯度下降法。

在这里插入图片描述

1)梯度下降法

训练的关键是找到一组(w,b),使得损失函数L取极小值。我们先看一下损失函数LLL只随两个参数w5​、w9变化时的简单情形,方便展示3D模型,启发寻解的思路。

L=L(w5​,w9​)

这里我们将w0,w1,…,w12​中除w5​、w9​之外的参数和b都固定下来,可以用图画出L(w5​,w9​)的形式。

net = Network(13)
losses = []
#设定取值范围,只画出参数w5和w9在区间[-160, 160]的曲线部分,以及包含损失函数的极值

w5 = (-160.0, 160.0, 1.0)     #np.arange()函数,返回一个有终点和起点的固定步长的排列
w9 = np.arange(-160.0, 160.0, 1.0)
losses = np.zeros([len(w5), len(w9)])      #返回来一个给定形状和类型的用0填充的数组

##双层循环计算设定区域内每个参数取值所对应的Loss
for i in range(len(w5)):
    for j in range(len(w9)):
        net.w[5] = w5[i]
        net.w[9] = w9[j]
        z = net.forward(x)
        loss = net.loss(z, y)
        losses[i, j] = loss

#使用matplotlib将两个变量和对应的Loss作3D图
#x轴y轴为w5和w9,z轴为loss值
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()    #创建画板
ax = Axes3D(fig)    #绘制3D图形

w5, w9 = np.meshgrid(w5, w9)    #x轴y轴每隔一个点进行打点,

ax.plot_surface(w5, w9, losses, rstride=1, cstride=1, cmap='rainbow')    #用彩虹图绘制,指定xyz轴,指定行列跨度,设置颜色映射
plt.show()

在这里插入图片描述
从图中可以看出有些区域的函数值明显比周围的点小。选择w5和w9来画图,因为选择这两个参数的时候,可比较直观的从损失函数的曲面图上发现极值点的存在。

观察上述曲线呈现出“圆滑”的坡度,这正是我们选择以均方误差作为损失函数的原因之一。下图呈现了只有一个参数维度时,均方误差和绝对值误差(只将每个样本的误差累加,不做平方处理)的损失函数曲线图。

在这里插入图片描述由此可见,均方误差表现的“圆滑”的坡度有两个好处:

  • 曲线的最低点是可导的。
  • 越接近最低点,曲线的坡度逐渐放缓,有助于通过当前的梯度来判断接近最低点的程度(是否逐渐减少步长,以免错过最低点)。

而绝对值误差是不具备这两个特性的,这也是损失函数的设计不仅仅要考虑“合理性”,还要追求“易解性”的原因。

现在我们要找出一组[w5,w9] 的值,使得损失函数最小,实现梯度下降法的方案如下:

  • 步骤1:随机的选一组初始值,例如:[w5,w9] =[−100.0,−100.0]
  • 步骤2:选取下一个点[w5’,w9’],使得L(w5’,w9’)<L(w5,w9)
  • 步骤3:重复步骤2,直到损失函数几乎不再下降。

如何选择[w5’,w9’]是至关重要的,第一要保证L是下降的,第二要使得下降的趋势尽可能的快。
微积分的基础知识告诉我们,沿着梯度的反方向,是函数值下降最快的方向。函数在某一个点的梯度方向是曲线切线的方向,即曲线斜率最大的方向;但梯度方向是向上的,所以下降最快的是梯度的反方向

2)计算梯度

改写损失函数的计算方法。为了使梯度计算更加简洁,引入因子 1 2 \frac{1}{2} 21,定义损失函数如下:
在这里插入图片描述其中zi是网络对第i个样本的预测值:
在这里插入图片描述由梯度的定义,就是对变量取偏导:
在这里插入图片描述可以计算出L对w和b的偏导数:

在这里插入图片描述从导数的计算过程可以看出,因子 1 2 \frac{1}{2} 21被消掉了,这是因为二次函数求导的时候会产生因子2,这也是我们将损失函数改写的原因。

下面我们考虑只有一个样本的情况下,计算梯度:
在这里插入图片描述
可以计算出:
在这里插入图片描述可以计算出L对w和b的偏导数:
在这里插入图片描述只有一个样本时,通过具体的程序查看每个变量的数据和维度。

x1 = x[0]     #x特征变量值
y1 = y[0]     #y真实值
z1 = net.forward(x1)    #z预测值
print('x1 {}, shape {}'.format(x1, x1.shape))
print('y1 {}, shape {}'.format(y1, y1.shape))
print('z1 {}, shape {}'.format(z1, z1.shape))

输出

x1 [-0.02146321  0.03767327 -0.28552309 -0.08663366  0.01289726  0.04634817
  0.00795597 -0.00765794 -0.25172191 -0.11881188 -0.29002528  0.0519112
 -0.17590923], shape (13,)
y1 [-0.00390539], shape (1,)
z1 [-12.05947643], shape (1,)

按上面的公式,当只有一个样本时,可以计算某个wj​,比如w0​的梯度。

gradient_w0 = (z1 - y1) * x1[0]
print('gradient_w0 {}'.format(gradient_w0))
gradient_w0 [0.25875126]

同样我们可以计算w1​的梯度。

gradient_w1 = (z1 - y1) * x1[1]
print('gradient_w1 {}'.format(gradient_w1))
gradient_w1 [-0.45417275]

依次计算w2​的梯度。

gradient_w2= (z1 - y1) * x1[2]
print('gradient_w1 {}'.format(gradient_w2))
gradient_w2 [3.44214394]
3)使用Numpy进行梯度计算

基于Numpy广播机制(对向量和矩阵计算如同对1个单一变量计算一样),可以更快速的实现梯度计算。

  • step1 计算1个样本13个参数的梯度
    计算梯度的代码中直接用(z1−y1)⋅x1,得到的是一个13维的向量,每个分量分别代表该维度的梯度。
gradient_w = (z1 - y1) * x1     #这里x1是13维的向量,梯度也是向量
print('gradient_w_by_sample1 {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))

输出

gradient_w_by_sample1 [ 0.25875126 -0.45417275  3.44214394  1.04441828 -0.15548386 -0.55875363
 -0.09591377  0.09232085  3.03465138  1.43234507  3.49642036 -0.62581917
  2.12068622], gradient.shape (13,)

输入数据中有多个样本,每个样本都对梯度有贡献。如上代码计算了只有样本1时的梯度值,同样的计算方法也可以计算样本2和样本3对梯度的贡献。

x2 = x[1]
y2 = y[1]
z2 = net.forward(x2)
gradient_w = (z2 - y2) * x2
print('gradient_w_by_sample2 {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))

输出

gradient_w_by_sample2 [ 0.7329239   4.91417754  3.33394253  2.9912385   4.45673435 -0.58146277
 -5.14623287 -2.4894594   7.19011988  7.99471607  0.83100061 -1.79236081
  2.11028056], gradient.shape (13,)
x3 = x[2]
y3 = y[2]
z3 = net.forward(x3)
gradient_w = (z3 - y3) * x3
print('gradient_w_by_sample3 {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))

输出

gradient_w_by_sample3 [ 0.25138584  1.68549775  1.14349809  1.02595515  1.5286008  -1.93302947
  0.4058236  -0.85385157  2.46611579  2.74208162  0.28502219 -0.46695229
  2.39363651], gradient.shape (13,)

有404个样本,计算13个参数的梯度,可以写双层for循环计算一个样本所有参数的梯度和所有样本对参数的梯度,然后再作平均。实际上可以用Numpy矩阵操作来简化运算。

# 注意这里是一次取出3个样本的数据,不是取出第3个样本
x3samples = x[0:3]
y3samples = y[0:3]
z3samples = net.forward(x3samples)

print('x {}, shape {}'.format(x3samples, x3samples.shape))
print('y {}, shape {}'.format(y3samples, y3samples.shape))
print('z {}, shape {}'.format(z3samples, z3samples.shape))

输出

x [[-0.02146321  0.03767327 -0.28552309 -0.08663366  0.01289726  0.04634817
   0.00795597 -0.00765794 -0.25172191 -0.11881188 -0.29002528  0.0519112
  -0.17590923]
 [-0.02122729 -0.14232673 -0.09655922 -0.08663366 -0.12907805  0.0168406
   0.14904763  0.0721009  -0.20824365 -0.23154675 -0.02406783  0.0519112
  -0.06111894]
 [-0.02122751 -0.14232673 -0.09655922 -0.08663366 -0.12907805  0.1632288
  -0.03426854  0.0721009  -0.20824365 -0.23154675 -0.02406783  0.03943037
  -0.20212336]], shape (3, 13)
y [[-0.00390539]
 [-0.05723872]
 [ 0.23387239]], shape (3, 1)
z [[-12.05947643]
 [-34.58467747]
 [-11.60858134]], shape (3, 1)

上面的x3samples, y3samples, z3samples的第一维大小均为3,表示有3个样本,为3×13的矩阵。下面计算这3个样本对梯度的贡献。

radient_w = (z3samples - y3samples) * x3samples
print('gradient_w {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))

输出

gradient_w [[ 0.25875126 -0.45417275  3.44214394  1.04441828 -0.15548386 -0.55875363
  -0.09591377  0.09232085  3.03465138  1.43234507  3.49642036 -0.62581917
   2.12068622]
 [ 0.7329239   4.91417754  3.33394253  2.9912385   4.45673435 -0.58146277
  -5.14623287 -2.4894594   7.19011988  7.99471607  0.83100061 -1.79236081
   2.11028056]
 [ 0.25138584  1.68549775  1.14349809  1.02595515  1.5286008  -1.93302947
   0.4058236  -0.85385157  2.46611579  2.74208162  0.28502219 -0.46695229
   2.39363651]], gradient.shape (3, 13)

此处可见,计算梯度gradient_w的维度是3×133 ,并且其第1行与上面第1个样本计算的梯度gradient_w_by_sample1一致,第2行与上面第2个样本计算的梯度gradient_w_by_sample2一致,第3行与上面第3个样本计算的梯度gradient_w_by_sample3一致。这里使用矩阵操作,可以更加方便的对3个样本分别计算各自对梯度的贡献。

那么对于有N个样本的情形,我们可以直接使用如下方式计算出所有样本对梯度的贡献,这就是使用Numpy库广播功能带来的便捷。 小结一下这里使用Numpy库的广播功能:

  • 一方面可以扩展参数的维度,代替for循环来计算1个样本对从w0​到w12的所有参数的梯度。
  • 另一方面可以扩展样本的维度,代替for循环来计算样本0到样本403对参数的梯度。

因此,利用Numpy库的广播功能对梯度的计算如下:

z = net.forward(x)
gradient_w = (z - y) * x
print('gradient_w shape {}'.format(gradient_w.shape))
print(gradient_w)

输出

gradient_w shape (404, 13)
[[  0.25875126  -0.45417275   3.44214394 ...   3.49642036  -0.62581917
    2.12068622]
 [  0.7329239    4.91417754   3.33394253 ...   0.83100061  -1.79236081
    2.11028056]
 [  0.25138584   1.68549775   1.14349809 ...   0.28502219  -0.46695229
    2.39363651]
 ...
 [ 14.70025543 -15.10890735  36.23258734 ...  24.54882966   5.51071122
   26.26098922]
 [  9.29832217 -15.33146159  36.76629344 ...  24.91043398  -1.27564923
   26.61808955]
 [ 19.55115919 -10.8177237   25.94192351 ...  17.5765494    3.94557661
   17.64891012]]

上面gradient_w的每一行代表了一个样本对梯度的贡献。根据梯度的计算公式,总梯度是对每个样本对梯度贡献的平均值。
在这里插入图片描述我们也可以使用Numpy的均值函数来完成此过程:

# axis = 0 表示把每一行做相加然后再除以总的行数
gradient_w = np.mean(gradient_w, axis=0)
print('gradient_w ', gradient_w.shape)
print('w ', net.w.shape)
print(gradient_w)
print(net.w)
gradient_w  (13,)
w  (13, 1)
[ 1.59697064 -0.92928123  4.72726926  1.65712204  4.96176389  1.18068454
  4.55846519 -3.37770889  9.57465893 10.29870662  1.3900257  -0.30152215
  1.09276043]
[[ 1.76405235e+00]
 [ 4.00157208e-01]
 [ 9.78737984e-01]
 [ 2.24089320e+00]
 [ 1.86755799e+00]
 [ 1.59000000e+02]
 [ 9.50088418e-01]
 [-1.51357208e-01]
 [-1.03218852e-01]
 [ 1.59000000e+02]
 [ 1.44043571e-01]
 [ 1.45427351e+00]
 [ 7.61037725e-01]]

我们使用Numpy的矩阵操作方便地完成了gradient的计算,但引入了一个问题,gradient_w的形状是(13,),而www的维度是(13, 1)。导致该问题的原因是使用np.mean函数时消除了第0维。为了加减乘除等计算方便,gradient_w和w必须保持一致的形状。因此我们将gradient_w的维度也设置为(13,1),代码如下:

gradient_w = gradient_w[:, np.newaxis]    #np.newaxis的作用是在这一位置增加一个一维
print('gradient_w shape', gradient_w.shape)

综合上面的剖析,计算w的梯度的代码如下所示。

z = net.forward(x)
gradient_w = (z - y) * x
gradient_w = np.mean(gradient_w, axis=0)
gradient_w = gradient_w[:, np.newaxis]
gradient_w

输出

array([[ 1.59697064],
       [-0.92928123],
       [ 4.72726926],
       [ 1.65712204],
       [ 4.96176389],
       [ 1.18068454],
       [ 4.55846519],
       [-3.37770889],
       [ 9.57465893],
       [10.29870662],
       [ 1.3900257 ],
       [-0.30152215],
       [ 1.09276043]])

上述代码非常简洁地完成了w的梯度计算。同样,计算b的梯度的代码也是类似的原理。

gradient_b = (z - y)
gradient_b = np.mean(gradient_b)
# 此处b是一个数值,所以可以直接用np.mean得到一个标量
gradient_b

输出

-1.0918438870293816e-13

将上面计算w和b的梯度的过程,写成Network类的gradient函数,实现方法如下所示。
定义:

class Network(object):
    def __init__(self, num_of_weights):
        # 随机产生w的初始值
        # 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
        np.random.seed(0)
        self.w = np.random.randn(num
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值