👋大家好,我是一名正在学习机器学习的小白,最近在写一些关于机器学习基础概念的文章。如果你也对机器学习感兴趣,或者想要了解一些基础知识,欢迎来关注我哦!🤗
梯度下降法
- 定义代价函数
- 选择起始点
- 计算梯度
- 按学习率前进
批量梯度下降
批量梯度下降法是最原始的形式,它是指在每一次迭代时使用所有样本来进行梯度的更新
优点:
(1)一次迭代是对所有样本进行计算,此时利用矩阵进行操作,实现了并行。
(2)由全数据集确定的方向能够更好地代表样本总体,从而更准确地朝向极值所在的方向。当目标函数为凸函数时,BGD一 定能够得到全局最优。
缺点:
(1)当样本数目 m 很大时,每迭代一步都需要对所有样本计算,训练过程会很慢。
随机梯度下降
随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。使得训练速度加快
优点:
(1)由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数,这样每一轮参数 的更新速度大大加快。
缺点:
(1)准确度下降。由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。
(2)可能会收敛到局部最优,由于单个样本并不能代表全体样本的趋势。
(3)不易于并行实现。
解释一下为什么SGD收敛速度比BGD要快:
答:这里我们假设有30W个样本,对于BGD而言,每次迭代需要计算30W个样本才能对参数进行一次更新,需要求得最小值可能需要多次迭代(假设这里是10);而对于SGD,每次更新参数只需要一个样本,因此若使用这30W个样本进行参数更新,则参数会被更新(迭代)30W次,而这期间,SGD就能保证能够收敛到一个合适的最小值上了。也就是说,在收敛时,BGD计算了10×30W10×30W 次,而SGD只计算了 1×30W1×30W 次。
小批量梯度下降
小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代 使用 batch_size个样本来对参数进行更新
优点:
(1)通过矩阵运算,每次在一个batch上优化神经网络参数并不会比单个数据慢太多。
(2)每次 使用一个batch可以大大减小收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。(比如上 例中的30W,设置batch_size=100时,需要迭代3000次,远小于SGD的30W次)
(3)可实现并行化。
缺点:
(1)batch_size的不当选择可能会带来一些问题。
batcha_size的选择带来的影响:
(1)在合理地范围内,增大batch_size的好处:
a. 内存利用率提高了,大矩阵乘法的并行化效率提高。
b. 跑完一次 epoch(全数据集)所需的迭代次数减少,对于相同数据量的处理速度进一步加快。
c. 在一定范围内,一般来说 Batch_Size 越大,其确定的下降方向越准,引起训练震荡越小。
(2)盲目增大batch_size的坏处:
a. 内存利用率提高了,但是内存容量可能撑不住了。
b. 跑完一次 epoch(全数据集)所需的迭代次数减少,要想达到相同的精度,其所花费的时间大大增加了,从而对参数 的修正也就显得更加缓慢。
c. Batch_Size 增大到一定程度,其确定的下降方向已经基本不再变化。
1 、实验目的
理解多层神经网络的结构和原理,掌握反向传播算法对神经元的训练过程,了解反向传播公式。通过构建 BP 网络实例,熟悉前馈网络的原理及结构。
2、背景知识
误差反向传播算法即 BP 算法,是一种适合于多层神经网络的学习算法。其建立在梯度下降方法的基础之上,主要由激励传播和权重更新两个环节组成,经过反复迭代更新、修正权值从而输出预期的结果。
BP 算法整体上可以分成正向传播和反向传播,原理如下:
正向传播过程:信息经过输入层到达隐含层,再经过多个隐含层的处理后到达输出层。
反向传播过程:比较输出结果和正确结果,将误差作为一个目标函数进行反向传播:每一层依次求对权值的偏导数,构成目标函数对权值的梯度,网络权重再依次完成更新调整。依此往复、直到输出达到目标值完成训练。该算法可以总结为:利用输出误差推算前一层的误差,再用推算误差算出更前一层的误差,直到计算出所有层的误差估计。
1986 年,Hinton 在论文《Learning Representations by Back-propagating Errors》中首次系统地描述了如何利用 BP 算法来训练神经网络。从此,BP 算法开始占据有监督神经网络算法的核心地位。它是迄今最成功的神经网络学习算法之一,现实任务中使用神经网络时,大多是在使用 BP 算法进行训练。为了说明 BP 算法的过程,本实验使用一个公司招聘的例子:
假设有一个公司,其人员招聘由 5个人组成的人事管理部门负责,如下图所示:其中张三、李四等人是应聘者,他们向该部门投递简历,简历包括两类数据:学习成绩和社会实践得分,人事部门有三个层级,一科长根据应聘者的学习成绩和实践得分评估其智商,二科长根据同样的资料评估其情商;一处长根据两个科长提供的智商、情商评分,评估应聘者的工作能力,二处长评估工作态度;最后由总裁汇总两位处长的意见,得出最终结论,即是否招收该应聘者。
该模型等价于一个形状为(2,2,2,1)的前馈神经网络,输入层、隐藏层 1、隐藏层 2、输出层各自包含 2、2、2、1 个节点,如下图所示。
3、示例代码
import numpy as np
import matplotlib.pyplot as plt
# 输入数据1行2列,这里只有张三的数据
X = np.array([[1, 0.1]])
# X = np.array([[1,0.1],
# [0.1,1],
# [0.1,0.1],
# [1,1]])
# 标签,也叫真值,1行1列,张三的真值:一定录用
T = np.array([[1]])
# T = np.array([[1],
# [0],
# [0],
# [1]])
# 定义一个2隐层的神经网络:2-2-2-1
# 输入层2个神经元,隐藏1层2个神经元,隐藏2层2个神经元,输出层1个神经元
# 输入层到隐藏层1的权值初始化,2行2列
W1 = np.array([[0.8, 0.2],
[0.2, 0.8]])
# 隐藏层1到隐藏层2的权值初始化,2行2列
W2 = np.array([[0.5, 0.0],
[0.5, 1.0]])
# 隐藏层2到输出层的权值初始化,2行1列
W3 = np.array([[0.5],
[0.5]])
# 初始化偏置值
# 隐藏层1的2个神经元偏置
b1 = np.array([[-1, 0.3]])
# 隐藏层2的2个神经元偏置
b2 = np.array([[0.1, -0.1]])
# 输出层的1个神经元偏置
b3 = np.array([[-0.6]])
# 学习率设置
lr = 0.1
# 定义训练周期数10000
epochs = 10000
# 每训练1000次计算一次loss值 # 定义测试周期数
report = 1000
# 将所有样本分组,每组大小为
batch_size = 1
# 定义sigmoid函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 定义sigmoid函数导数
def dsigmoid(x):
return x * (1 - x)
# 更新权值和偏置值
def update():
global batch_X, batch_T, W1, W2, W3, lr, b1, b2, b3
# 隐藏层1输出
Z1 = np.dot(batch_X, W1) + b1
A1 = sigmoid(Z1)
# 隐藏层2输出
Z2 = (np.dot(A1, W2) + b2)
A2 = sigmoid(Z2)
# 输出层输出
Z3 = (np.dot(A2, W3) + b3)
A3 = sigmoid(Z3)
# 求输出层的误差
delta_A3 = (batch_T - A3)
delta_Z3 = delta_A3 * dsigmoid(A3)
# 利用输出层的误差,求出三个偏导(即隐藏层2到输出层的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W3 = A2.T.dot(delta_Z3) / batch_X.shape[0]
delta_B3 = np.sum(delta_Z3, axis=0) / batch_X.shape[0]
# 求隐藏层2的误差
delta_A2 = delta_Z3.dot(W3.T)
delta_Z2 = delta_A2 * dsigmoid(A2)
# 利用隐藏层2的误差,求出三个偏导(即隐藏层1到隐藏层2的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W2 = A1.T.dot(delta_Z2) / batch_X.shape[0]
delta_B2 = np.sum(delta_Z2, axis=0) / batch_X.shape[0]
# 求隐藏层1的误差
delta_A1 = delta_Z2.dot(W2.T)
delta_Z1 = delta_A1 * dsigmoid(A1)
# 利用隐藏层1的误差,求出三个偏导(即输入层到隐藏层1的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W1 = batch_X.T.dot(delta_Z1) / batch_X.shape[0]
delta_B1 = np.sum(delta_Z1, axis=0) / batch_X.shape[0]
# 更新权值
W3 = W3 + lr * delta_W3
W2 = W2 + lr * delta_W2
W1 = W1 + lr * delta_W1
# 改变偏置值
b3 = b3 + lr * delta_B3
b2 = b2 + lr * delta_B2
b1 = b1 + lr * delta_B1
# 定义空list用于保存loss
loss = []
batch_X = []
batch_T = []
max_batch = X.shape[0] // batch_size
# 训练模型
for idx_epoch in range(epochs):
for idx_batch in range(max_batch):
# 更新权值
batch_X = X[idx_batch * batch_size:(idx_batch + 1) * batch_size, :]
batch_T = T[idx_batch * batch_size:(idx_batch + 1) * batch_size, :]
update()
# 每训练5000次计算一次loss值
if idx_epoch % report == 0:
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
# 计算loss值
print('A3:', A3)
print('epochs:', idx_epoch, 'loss:', np.mean(np.square(T - A3) / 2))
# 保存loss值
loss.append(np.mean(np.square(T - A3) / 2))
# 画图训练周期数与loss的关系图
plt.plot(range(0, epochs, report), loss)
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
print('output:')
print(A3)
# 因为最终的分类只有0和1,所以我们可以把
# 大于等于0.5的值归为1类,小于0.5的值归为0类
def predict(x):
if x >= 0.5:
return 1
else:
return 0
# map会根据提供的函数对指定序列做映射
# 相当于依次把A3中的值放到predict函数中计算
# 然后打印出结果
print('predict:')
for i in map(predict, A3):
print(i)
4、实验内容
请回答下列问题:
1)如果去掉总裁这一层,相应张三的样本修改为(1.0,0.1,1.0,1.0),分别对应张三的学习成绩、张三的实践成绩、张三的工作能力真值、张三的工作态度真值,代码应该如何修改?
import numpy as np
import matplotlib.pyplot as plt
# 输入数据1行2列,这里只有张三的数据
X = np.array([[1, 0.1]])
# 标签,也叫真值,1行1列,张三的真值:一定录用
T = np.array([[1],
[1]])
# 输入层到隐藏层1的权值初始化,2行2列
W1 = np.array([[0.8, 0.2],
[0.2, 0.8]])
# 隐藏层1到输出层的权值初始化,2行2列
W2 = np.array([[0.5, 0.0],
[0.5, 1.0]])
# 初始化偏置值
# 隐藏层1的2个神经元偏置
b1 = np.array([[-1, 0.3]])
# 输出层的2个神经元偏置
b2 = np.array([[0.1, -0.1]])
# 学习率设置
lr = 0.1
# 定义训练周期数10000
epochs = 10000
# 每训练1000次计算一次loss值 # 定义测试周期数
report = 1000
# 将所有样本分组,每组大小为
batch_size = 1
# 定义sigmoid函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 定义sigmoid函数导数
def dsigmoid(x):
return x * (1 - x)
# 更新权值和偏置值
def update():
global batch_X, batch_T, W1, W2, lr, b1, b2
# 隐藏层1输出
Z1 = np.dot(batch_X, W1) + b1
A1 = sigmoid(Z1)
# 输出层
Z2 = (np.dot(A1, W2) + b2)
A2 = sigmoid(Z2)
# 求输出层的误差
delta_A2 = (batch_T - A2)
delta_Z2 = delta_A2 * dsigmoid(A2)
# 利用输出层的误差,求出三个偏导(即隐藏层1到输出层的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W2 = A1.T.dot(delta_Z2) / batch_X.shape[0]
delta_B2 = np.sum(delta_Z2, axis=0) / batch_X.shape[0]
# 求隐藏层1的误差
delta_A1 = delta_Z2.dot(W2.T)
delta_Z1 = delta_A1 * dsigmoid(A1)
# 利用隐藏层1的误差,求出三个偏导(即输入层到隐藏层1的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W1 = batch_X.T.dot(delta_Z1) / batch_X.shape[0]
delta_B1 = np.sum(delta_Z1, axis=0) / batch_X.shape[0]
# 更新权值
W2 = W2 + lr * delta_W2
W1 = W1 + lr * delta_W1
# 改变偏置值
b2 = b2 + lr * delta_B2
b1 = b1 + lr * delta_B1
# 定义空list用于保存loss
loss = []
batch_X = []
batch_T = []
max_batch = X.shape[0] // batch_size
# 训练模型
for idx_epoch in range(epochs):
for idx_batch in range(max_batch):
# 更新权值
batch_X = X[idx_batch * batch_size:(idx_batch + 1) * batch_size, :]
batch_T = T[idx_batch * batch_size:(idx_batch + 1) * batch_size, :]
update()
# 每训练5000次计算一次loss值
if idx_epoch % report == 0:
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 输出层输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 计算loss值
print('A2:', A2)
print('epochs:', idx_epoch, 'loss:', np.mean(np.square(T - A2) / 2)) # 各样本损失函数均值
# 保存loss值
loss.append(np.mean(np.square(T - A2) / 2))
# 画图训练周期数与loss的关系图
plt.plot(range(0, epochs, report), loss)
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 输出层输出
A2 = sigmoid(np.dot(A1, W2) + b2)
print('output:')
print(A2)
# 因为最终的分类只有0和1,所以我们可以把
# 大于等于0.5的值归为1类,小于0.5的值归为0类
def predict(x):
if x.all() >= 0.5:
return 1
else:
return 0
# map会根据提供的函数对指定序列做映射
# 相当于依次把A2中的值放到predict函数中计算
# 然后打印出结果
print('predict:')
for i in map(predict, A2):
print(i)
2)如果增加一个样本,李四(0.1,1.0,0),分别对应李四的学习成绩,李四的实践成绩,李四被招聘可能性的真值,代码应该如何修改?此时是一个样本计算一次偏导、更新一次权值,还是两个样本一起计算一次偏导、更新一次权值?(提示:注意 batch_size 的作用)
import numpy as np
import matplotlib.pyplot as plt
# 输入数据1行2列,这里有张三、李四的数据
X = np.array([[1, 0.1],
[0.1, 1]])
# 标签,也叫真值
T = np.array([[1],
[0]])
# 定义一个2隐层的神经网络:2-2-2-1
# 输入层2个神经元,隐藏1层2个神经元,隐藏2层2个神经元,输出层1个神经元
# 输入层到隐藏层1的权值初始化,2行2列
W1 = np.array([[0.8, 0.2],
[0.2, 0.8]])
# 隐藏层1到隐藏层2的权值初始化,2行2列
W2 = np.array([[0.5, 0.0],
[0.5, 1.0]])
# 隐藏层2到输出层的权值初始化,2行1列
W3 = np.array([[0.5],
[0.5]])
# 初始化偏置值
# 隐藏层1的2个神经元偏置
b1 = np.array([[-1, 0.3]])
# 隐藏层2的2个神经元偏置
b2 = np.array([[0.1, -0.1]])
# 输出层的1个神经元偏置
b3 = np.array([[-0.6]])
# 学习率设置
lr = 0.1
# 定义训练周期数10000
epochs = 10000
# 每训练1000次计算一次loss值 # 定义测试周期数
report = 1000
# 将所有样本分组,每组大小为
batch_size = 1
# 定义sigmoid函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 定义sigmoid函数导数
def dsigmoid(x):
return x * (1 - x)
# 更新权值和偏置值
def update():
global batch_X, batch_T, W1, W2, W3, lr, b1, b2, b3
# 隐藏层1输出
Z1 = np.dot(batch_X, W1) + b1
A1 = sigmoid(Z1)
# 隐藏层2输出
Z2 = (np.dot(A1, W2) + b2)
A2 = sigmoid(Z2)
# 输出层输出
Z3 = (np.dot(A2, W3) + b3)
A3 = sigmoid(Z3)
# 求输出层的误差
delta_A3 = (batch_T - A3)
delta_Z3 = delta_A3 * dsigmoid(A3)
# 利用输出层的误差,求出三个偏导(即隐藏层2到输出层的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W3 = A2.T.dot(delta_Z3) / batch_X.shape[0]
delta_B3 = np.sum(delta_Z3, axis=0) / batch_X.shape[0]
# 求隐藏层2的误差
delta_A2 = delta_Z3.dot(W3.T)
delta_Z2 = delta_A2 * dsigmoid(A2)
# 利用隐藏层2的误差,求出三个偏导(即隐藏层1到隐藏层2的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W2 = A1.T.dot(delta_Z2) / batch_X.shape[0]
delta_B2 = np.sum(delta_Z2, axis=0) / batch_X.shape[0]
# 求隐藏层1的误差
delta_A1 = delta_Z2.dot(W2.T)
delta_Z1 = delta_A1 * dsigmoid(A1)
# 利用隐藏层1的误差,求出三个偏导(即输入层到隐藏层1的权值改变) # 由于一次计算了多个样本,所以需要求平均
delta_W1 = batch_X.T.dot(delta_Z1) / batch_X.shape[0]
delta_B1 = np.sum(delta_Z1, axis=0) / batch_X.shape[0]
# 更新权值
W3 = W3 + lr * delta_W3
W2 = W2 + lr * delta_W2
W1 = W1 + lr * delta_W1
# 改变偏置值
b3 = b3 + lr * delta_B3
b2 = b2 + lr * delta_B2
b1 = b1 + lr * delta_B1
# 定义空list用于保存loss
loss = []
batch_X = []
batch_T = []
max_batch = X.shape[0] // batch_size
# 训练模型
for idx_epoch in range(epochs):
for idx_batch in range(max_batch):
# 更新权值
batch_X = X[idx_batch * batch_size:(idx_batch + 1) * batch_size, :] # 矩阵切片操作,取需要的行与列
batch_T = T[idx_batch * batch_size:(idx_batch + 1) * batch_size, :]
update()
# 每训练5000次计算一次loss值
if idx_epoch % report == 0:
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
# 计算loss值
print('A3:', A3)
print('epochs:', idx_epoch, 'loss:', np.mean(np.square(T - A3) / 2))
# 保存loss值
loss.append(np.mean(np.square(T - A3) / 2))
# 画图训练周期数与loss的关系图
plt.plot(range(0, epochs, report), loss)
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
print('output:')
print(A3)
# 因为最终的分类只有0和1,所以我们可以把
# 大于等于0.5的值归为1类,小于0.5的值归为0类
def predict(x):
if x >= 0.5:
return 1
else:
return 0
# map会根据提供的函数对指定序列做映射
# 相当于依次把A3中的值放到predict函数中计算
# 然后打印出结果
print('predict:')
for i in map(predict, A3):
print(i)
3)样本为张三[1,0.1,1]、李四[0.1,1,0]、王五[0.1,0.1,0]、赵六[1,1,1],请利用 batch_size 实现教材 279 页提到的“批量梯度下降”、“随机梯度下降”和“小批量梯度下降”,请注意“随机梯度下降”和“小批量梯度下降”要体现随机性。
三种梯度下降的区别在于每次更新权值的样本规模不同,修改每次迭代的样本规模即可
批量梯度下降
# 训练模型
for idx_epoch in range(epochs):
# 更新权值
batch_X = X # 矩阵切片操作,取需要的行与列
batch_T = T
update()
# 每训练5000次计算一次loss值
if idx_epoch % report == 0:
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
# 计算loss值
print('A3:', A3)
print('epochs:', idx_epoch, 'loss:', np.mean(np.square(T - A3) / 2))
# 保存loss值
loss.append(np.mean(np.square(T - A3) / 2))
小批量梯度下降
batch_size = 2 # 小批量梯度下降,这里每次迭代样本容量为2
# 训练模型
for idx_epoch in range(epochs):
for idx_batch in range(max_batch):
# 更新权值
batch_X = X[idx_batch * batch_size:(idx_batch + 1) * batch_size, :] # 矩阵切片操作,取需要的行与列
batch_T = T[idx_batch * batch_size:(idx_batch + 1) * batch_size, :]
update()
# 每训练5000次计算一次loss值
if idx_epoch % report == 0:
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
# 计算loss值
print('A3:', A3)
print('epochs:', idx_epoch, 'loss:', np.mean(np.square(T - A3) / 2))
# 保存loss值
loss.append(np.mean(np.square(T - A3) / 2))
随机梯度下降
# 训练模型
for idx_epoch in range(epochs):
randomNum = random.randint(X.shape[0]) # 生成一个n到m之间的随机数, 随机梯度下降
# 更新权值
batch_X = X[randomNum:(randomNum + 1), :]
batch_T = T[randomNum:(randomNum + 1), :]
update()
# 每训练5000次计算一次loss值
if idx_epoch % report == 0:
# 隐藏层1输出
A1 = sigmoid(np.dot(X, W1) + b1)
# 隐藏层2输出
A2 = sigmoid(np.dot(A1, W2) + b2)
# 输出层输出
A3 = sigmoid(np.dot(A2, W3) + b3)
# 计算loss值
print('A3:', A3)
print('epochs:', idx_epoch, 'loss:', np.mean(np.square(T - A3) / 2))
# 保存loss值
loss.append(np.mean(np.square(T - A3) / 2))
随机梯度下降每次画出的训练周期数与loss的关系图都不一样,因为每次迭代都是随机选取一个样本来更新权值