豆瓣9.4!《深度学习入门:基于Python的理论与实现》学习笔记(2)

2020 年 8 月 11 日 深度学习自然语言处理

点击上方,选择星标置顶,每天给你送干货

阅读大概需要20分钟

跟随小博主,每天进步一丢丢


作者:云不见
链接:https://blog.csdn.net/Walk_OnTheRoad/article/details/107837003
编辑:王萌 澳门城市大学(深度学习冲鸭公众号)

此笔记是基于《深度学习入门》这本书的重点知识汇总。
接着《深度学习入门:基于Python的理论与实现》学习笔记(1)继续学习笔记的记载。

目录:
梯度
神经网络的梯度
学习算法的实现步骤:
    mini-batch的实现(为了提高学习的效率!)
利用误差反向传播法求梯度(高速)
    加法节点的反向传播
    乘法节点的反向传播
    log对数的反向传播
    指数e的反向传播
激活函数层反向传播的实现
    1、Relu层的反向传播
    2、sigmoid函数的反向传播
    Affine层的反向传播
    3、Softmax-with-Loss层的反向传播
误差反向传播法的实现
使用误差反向传播法的神经网络的学习

本文重点知识点:
  • 梯度
  • 神经网络计算梯度的实现(利用数值微分)
  • 学习算法的实现步骤
  • 利用误差反向传播法求梯度(更高速)
  • 激活函数层反向传播的实现
  • 利用误差反向传播法实现两层神经网络的学习

如有细节处没有写到的,请继续精读《深度学习入门:基于Python的理论与实现》,对小白来说真的是非常好的深度学习的入门书籍,通俗易懂。(书中的例子主要是基于CV的)





梯度





为什么要计算梯度?
  • 寻找函数的最小值

前文我们已经说过,用损失函数来描述神经网络的好坏。损失函数越小,神经网络越好。

那么如何去寻找这个损失函数的最小值讷?也就是说如何找一个函数的最小值。
  • 通过数值微分计算梯度。梯度下降的地方,就是函数在变小的地方。直到梯度下降为0,这个函数的局部最小值也就找到啦。

这种方法就叫梯度下降法(SGD):寻找最小值的梯度法。

图 数学式表示梯度法

学习率lr (η):梯度下降的速度。需要自己设定,不能太大,不能太小。

Python来实现数值微分计算梯度的梯度下降法:
   
   
     
def gradient_descent(f, init_x, lr=0.01, step_num=100): # step_num为更新次数 x = init_x for i in range(step_num): grad = numerical_gradient(f, x) x -= lr * grad     return x

缺点:
但是通过数值微分计算梯度比较费时间,之后介绍的误差反向传播法会更高效。




神经网络的梯度





即求损失函数L关于权重参数的梯度

图 损失函数L关于权重参数w的数值微分法计算梯度

以下是利用数值微分求梯度,实现简单神经网络的代码:
   
   
     
import sys, os sys.path.append(os.pardir) import numpy as np from common.functions import softmax, cross_entropy_error from common.gradient import numerical_gradient class simpleNet: def __init__(self): 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




学习算法的实现步骤





也就是梯度下降法逐渐更新函数的过程

  • 前提
    神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。

神经网络的学习分成下面4个步骤。

  • 步骤1(mini-batch)
    从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们的目标是减小mini-batch的损失函数的值。

  • 步骤2(计算梯度)
    为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。

  • 步骤3(更新参数)
    将权重参数沿梯度方向进行微小更新。

  • 步骤4(重复)
    重复步骤1、步骤2、步骤3。

2层神经网络的实现代码:
   
   
     
import sys, os sys.path.append(os.pardir) from common.functions import * from common.gradient import numerical_gradient class TwoLayerNet: def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01): # 1、初始化权重 self.params = {} self.params['W1'] = weight_init_std * \ np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * \ np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) # 2、神经网络的推理过程(已知权重参数,进行前向传播) def predict(self, x): W1, W2 = self.params['W1'], self.params['W2'] b1, b2 = self.params['b1'], self.params['b2'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 y = softmax(a2) return y # 3、损失函数的选择 # x:输入数据, t:监督数据 def loss(self, x, t): y = self.predict(x) return cross_entropy_error(y, t) # 4、网络的识别精度,判断网络的精确度 def accuracy(self, x, t): # y = self.predict(x) y = np.argmax(y, axis=1) t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy # 利用数值微分求梯度 # x:输入数据, t:监督数据 def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_gradient(loss_W, self.params['W1']) grads['b1'] = numerical_gradient(loss_W, self.params['b1']) grads['W2'] = numerical_gradient(loss_W, self.params['W2']) grads['b2'] = numerical_gradient(loss_W, self.params['b2'])         return grads

mini-batch的实现(为了提高学习的效率!)



就是从训练数据中随机选择一部分数据(称为mini-batch),再以这些mini-batch为对象,使用梯度法更新参数的过程。
   
   
     
import numpy as np from dataset.mnist import load_mnist from two_layer_net import TwoLayerNet
# 加载输入训练数据 (x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_hot_ laobel = True) train_loss_list = []
# 超参数 iters_num = 10000 # 使用随机梯度下降法(SGD)更新参数、循环10000次 train_size = x_train.shape[0] batch_size = 100 # 随机选择100笔mini-batch数据 learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) for i in range(iters_num): # 获取mini-batch batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 计算梯度 grad = network.numerical_gradient(x_batch, t_batch) # grad = network.gradient(x_batch, t_batch) # 计算梯度的高速版!也就是接下来会说的反向传播法 # 更新参数 for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] # 记录学习过程,不断拟合的过程 loss = network.loss(x_batch, t_batch)     train_loss_list.append(loss)

图 记录损失函数不断拟合的过程

通过数值微分计算梯度比较费时间,误差反向传播法更高效。




利用误差反向传播法求梯度(高速)





计算图:将计算过程用图形表示出来。就像流程图一样。由节点,输入输出和箭头来表示他们之间的关系。

图 基于计算图来求解买苹果需要多少钱的问题

图中所示就是一个简单的计算图,每个圈圈相当于一个节点,通过加减乘除等运算来表示输入之间的关系;箭头上的数字表示输入的权重。比如输入为“苹果”,价格(权重)为100元一个;还有一条输入是“苹果的个数”,个数(权重)为两个,得到第一个乘法节点的输出为200;以此类推加上消费税输入,经过多个节点计算,得到最终买苹果的价格(输出)。

图 计算图的反向传播

反向传播,顾名思义,由输出传回输入,也就是说已知输出,求出输入。

上图反向传播的计算顺序:
  • 将信号 E × 节点的局部导数(偏导数),再传递给下一节点

对于简单层:

  加法节点的反向传





加法节点的反向传播原封不动的传给下一个节点。

  乘法节点的反向传播






乘法节点的反向传播:反向输入的偏导乘以另一条正向输入的值

  log对数的反向传播





  指数e的反向传播








激活函数层反向传播的实现





   1、 Relu 层的反向 传播






Relu层反向传播的代码实现:
   
   
     
class Relu: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout         return dx

总结:ReLU层像电路中的开关
  • 正向传播时,有电流通过的话,开关设为ON;没有电流通过,开关设为OFF。
  • 反向传播时,开关为ON,电流会直接通过;开关为OFF,则不会有电流通过

  2、sigmoid函数的反向传播






最终得到Sigmoid的反向传播:


已知sigmoid的y=1/(1+exp(-x)),带入式子化简一下得到下式,即为sigmoid层的反向传播:


用Python实现反向传播的Sigmoid层
   
   
     
#(实现的代码在 common/layers.py中) class Sigmoid: def __init__(self): self.out = None def forward(self, x): out = 1 / (1 + np.exp(-x)) self.out = out return out # 正向传播时将输出 y 保存在了实例变量 out中 def backward(self, dout): dx = dout * (1.0 - self.out) * self.out         return dx

  Affine层的反向传播



Affine仿射变换: y = xw+b。 就是神经网络的正向传播中进行的矩阵的乘积运算

  • 包括一次线性变换和一次平移,分别对应神经网络的加权和运算与加偏置运算,将进行该仿射变换的处理实现为“Affine层”



其中:W^T的T表示转置。
这里还要注意矩阵的形状,为什么要注意矩阵的形状呢?因为矩阵的乘积运算要求对应维度的元素个数保持一致,所以也就能推导出上式的乘积顺序为什么一个在前,一个在后了。因为只有这样才能实现矩阵乘法,得到正确的矩阵形状。

Affine层的反向传播的实现:
   
   
     
class Affine: def __init__(self, W, b): self.W = W self.b = b self.x = None self.dW = None self.db = None def forward(self, x): self.x = x out = np.dot(x, self.W) + self.b return out def backward(self, dout): # 反向传播 dx = np.dot(dout, self.W.T) self.dW = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0)         return dx

  3、Softmax-with-Loss层的反向传播




中间的过程推导很长,在书中的附录A中可参考,但是这里我们只需要记住这个看起来特别简单的结论即可。

Softmax-with-Loss层的反向传播代码实现
   
   
     
class SoftmaxWithLoss: def __init__(self): self.loss = None # 损失 self.y = None # softmax的输出 self.t = None # 监督数据(one-hot vector) def forward(self, x, t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size # 反向传播         return dx




误差反向传播法的实现





通过像组装乐高积木一样组装每一层,可以最终构建神经网络。

接下来可以通过组装已经实现的层来构建神经网络。

神经网络学习的步骤如下所示(又出现了,这个很重要):

  • 前提
    神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习。神经网络的学习分为下面4个步骤。
  • 步骤1(mini-batch)
    从训练数据中随机选择一部分数据。
  • 步骤2(计算梯度)
    计算损失函数关于各个权重参数的梯度。(计算梯度:数值微分法和误差反向传播法)
  • 步骤3(更新参数)
    将权重参数沿梯度方向进行微小的更新。
  • 步骤4(重复)
    重复步骤1、步骤2、步骤3。

和需要花费较多时间的数值微分不同,误差反向传播法可以快速高效地计算梯度。

对应误差反向传播法的两层神经网络的实现
   
   
     
import sys, os sys.path.append(os.pardir) import numpy as np from common.layers import * from common.gradient import numerical_gradient from collections import OrderedDict class TwoLayerNet: def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01): # 进行属性的初始化 # 参数从头开始依次是输入层的神经元数、隐藏层的神经元数、输出层的神经元数、初始化权重时的高斯分布的规模 # 初始化权重 self.params = {} self.params['W1'] = weight_init_std * \ np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * \ np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) # 生成所需要的层 self.layers = OrderedDict() # 有序字典 self.layers['Affine1'] = \ # w1*x + b1 Affine(self.params['W1'], self.params['b1']) self.layers['Relu1'] = Relu() self.layers['Affine2'] = \ # w2*x + b2 Affine(self.params['W2'], self.params['b2']) self.lastLayer = SoftmaxWithLoss() #SoftmaxWithLoss层 def predict(self, x): # 前向传播 for layer in self.layers.values(): x = layer.forward(x) return x # 计算损失函数的值。 # x:输入数据, t:监督数据 def loss(self, x, t): y = self.predict(x) return self.lastLayer.forward(y, t) # 计算识别精度 def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) if t.ndim != 1 : t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy # x:输入数据, t:监督数据 # 数值微分法计算梯度 def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_gradient(loss_W, self.params['W1']) grads['b1'] = numerical_gradient(loss_W, self.params['b1']) grads['W2'] = numerical_gradient(loss_W, self.params['W2']) grads['b2'] = numerical_gradient(loss_W, self.params['b2']) return grads # 利用误差反向传播法计算关于权重参数的梯度 def gradient(self, x, t): # forward self.loss(x, t) # backward dout = 1 dout = self.lastLayer.backward(dout) layers = list(self.layers.values()) layers.reverse() # 反转列表的顺序,反向传播只需要按照相反的顺序条用各层即可 for layer in layers: dout = layer.backward(dout) # 设定 grads = {} grads['W1'] = self.layers['Affine1'].dW grads['b1'] = self.layers['Affine1'].db grads['W2'] = self.layers['Affine2'].dW grads['b2'] = self.layers['Affine2'].db         return grads

以上做的的事情仅仅是以正确的顺序连接各层,再按顺序(或者逆序)调用各层。

如果想另外构建一个神经网络(比如5层、10层、20层……的大的神经网络)时,只需像组装乐高积木那样添加必要的层就可以了。

数值微分的实现简单,因此,一般情况下不太容易出错。而误差反向传播法的实现很复杂,容易出错。

如何检查误差反向传播法梯度算对没有?:

  • 可以利用梯度确认(gradient check),确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致。

梯度确认的代码实现如下所示:
   
   
     
# (源代码在 ch05/gradient_check.py中)。 import sys, os sys.path.append(os.pardir) import numpy as np from dataset.mnist import load_mnist from two_layer_net import TwoLayerNet # 读入数据 (x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_ hot_label = True) network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) x_batch = x_train[:3] t_batch = t_train[:3] grad_numerical = network.numerical_gradient(x_batch, t_batch) grad_backprop = network.gradient(x_batch, t_batch) # 求各个权重的绝对误差的平均值 for key in grad_numerical.keys(): diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )     print(key + ":" + str(diff))




使用误差反向传播法的神经网络的学习





这个学习过程中只是求梯度的方法与之前不同。
   
   
     
import sys, os sys.path.append(os.pardir) import numpy as np from dataset.mnist import load_mnist from two_layer_net import TwoLayerNet # 读入数据 (x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_hot_label=True) network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) iters_num = 10000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] train_acc_list = [] test_acc_list = [] iter_per_epoch = max(train_size / batch_size, 1) for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 通过误差反向传播法求梯度 grad = network.gradient(x_batch, t_batch) # 更新 for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, t_batch) train_loss_list.append(loss) if i % iter_per_epoch == 0: train_acc = network.accuracy(x_train, t_train) test_acc = network.accuracy(x_test, t_test) train_acc_list.append(train_acc) test_acc_list.append(test_acc)         print(train_acc, test_acc)




总结





我们学过的层有 ReLU 层、Softmax-with-Loss 层、Affine 层、Softmax 层等,这些层中实现了 forward和 backward方法,通过将数据正向和反向地传播,可以高效地计算权重参数的梯度。

通过使用层进行模块化,神经网络中可以自由地组装层,轻松构建出自己喜欢的网络。

注:如有细节处没有写到的,请继续精读《深度学习入门》,对小白来说真的是非常通俗易懂的深度学习入门书籍。(书中的例子主要是基于CV的)



今天因为你的点赞,让我元气满满
登录查看更多
0

相关内容

反向传播一词严格来说仅指用于计算梯度的算法,而不是指如何使用梯度。但是该术语通常被宽松地指整个学习算法,包括如何使用梯度,例如通过随机梯度下降。反向传播将增量计算概括为增量规则中的增量规则,该规则是反向传播的单层版本,然后通过自动微分进行广义化,其中反向传播是反向累积(或“反向模式”)的特例。 在机器学习中,反向传播(backprop)是一种广泛用于训练前馈神经网络以进行监督学习的算法。对于其他人工神经网络(ANN)都存在反向传播的一般化–一类算法,通常称为“反向传播”。反向传播算法的工作原理是,通过链规则计算损失函数相对于每个权重的梯度,一次计算一层,从最后一层开始向后迭代,以避免链规则中中间项的冗余计算。
《常微分方程》笔记,419页pdf
专知会员服务
71+阅读 · 2020年8月2日
专知会员服务
118+阅读 · 2020年7月22日
《深度学习》圣经花书的数学推导、原理与Python代码实现
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
【开源书】PyTorch深度学习起步,零基础入门(附pdf下载)
专知会员服务
110+阅读 · 2019年10月26日
GitHub超2.7万星,最全Python入门算法来了
新智元
5+阅读 · 2019年4月28日
从零开始深度学习:利用numpy手写一个感知机
数萃大数据
10+阅读 · 2018年6月10日
PyTorch:60分钟入门学习
全球人工智能
13+阅读 · 2018年5月18日
基于Numpy实现神经网络:反向传播
论智
5+阅读 · 2018年3月21日
干货|代码原理教你搞懂SGD随机梯度下降、BGD、MBGD
机器学习研究会
12+阅读 · 2017年11月25日
神经网络bp算法推导
统计学习与视觉计算组
11+阅读 · 2017年11月17日
蒙特卡罗方法入门
算法与数学之美
7+阅读 · 2017年9月26日
神经网络理论基础及 Python 实现
Python开发者
6+阅读 · 2017年7月15日
Arxiv
13+阅读 · 2020年4月12日
A Modern Introduction to Online Learning
Arxiv
20+阅读 · 2019年12月31日
Arxiv
3+阅读 · 2018年10月18日
Arxiv
8+阅读 · 2018年5月15日
Arxiv
11+阅读 · 2018年4月25日
VIP会员
相关VIP内容
《常微分方程》笔记,419页pdf
专知会员服务
71+阅读 · 2020年8月2日
专知会员服务
118+阅读 · 2020年7月22日
《深度学习》圣经花书的数学推导、原理与Python代码实现
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
【开源书】PyTorch深度学习起步,零基础入门(附pdf下载)
专知会员服务
110+阅读 · 2019年10月26日
相关资讯
GitHub超2.7万星,最全Python入门算法来了
新智元
5+阅读 · 2019年4月28日
从零开始深度学习:利用numpy手写一个感知机
数萃大数据
10+阅读 · 2018年6月10日
PyTorch:60分钟入门学习
全球人工智能
13+阅读 · 2018年5月18日
基于Numpy实现神经网络:反向传播
论智
5+阅读 · 2018年3月21日
干货|代码原理教你搞懂SGD随机梯度下降、BGD、MBGD
机器学习研究会
12+阅读 · 2017年11月25日
神经网络bp算法推导
统计学习与视觉计算组
11+阅读 · 2017年11月17日
蒙特卡罗方法入门
算法与数学之美
7+阅读 · 2017年9月26日
神经网络理论基础及 Python 实现
Python开发者
6+阅读 · 2017年7月15日
相关论文
Arxiv
13+阅读 · 2020年4月12日
A Modern Introduction to Online Learning
Arxiv
20+阅读 · 2019年12月31日
Arxiv
3+阅读 · 2018年10月18日
Arxiv
8+阅读 · 2018年5月15日
Arxiv
11+阅读 · 2018年4月25日
Top
微信扫码咨询专知VIP会员