函数表达式:
函数图像:
通过该图,可以看出对于sigmoid激活函数的一下几个特点:
1.当输入大于零时,输出一定大于0.5
2.当输入超过6时,输出将会无限接近于1,该函数的辨别功能将会失效
3.函数的阈值在0,1之间
4.具有良好的对称性
根据上述几个特点,我们可以初步总结sigmoid函数的应用场景:sigmoid的输出在0和1之间,我们在二分类任务中,采用sigmoid的输出的事件概率,也就是当输出满足满足某一概率条件我们将其划分分类,所以sigmoid函数适合作为二分类问题的输出层。
下面通过几个角度更加深入的理解sigmoid函数:
一.通过生活中的实例解释sigmoid激活函数:
题目:已知某样本实验者的身高,体重,每日锻炼时长,猜测实验者的性别。
首先根据sigmoid的特性,即当输入大于6时函数将会基本失效,则我们设定评判身高体重和每日锻炼时长的最高分为6,这里先估计某人的身高为5,体重为4.4,腰围为4,锻炼时长为4.3
然后设置身高的权重为0.3,体重的权重为4.4,腰围的权重为0.2,锻炼时长的权重为0.2
对于阈值,设置0到0.6为女生,0.6到1为男生
通过如上计算,可以得到结果为0.96,则判断该人很有可能是男生。
但是经过笔者在多次计算后发现之前说的通过分值代替身高体重的方法输入的数值还是太大了,如果输入的数大部分在3左右,则最后得出基本都是在0.9以上,很难有较好的判别效果分类,所以下述中,使用厘米计算身高,吨计算体重,小时计算锻炼时长,先简化放弃腰围,再次计算。
在上述计算中,通过第一次计算将阈值分界点从0.5改到了0.62,于是判断出了性别。
通过上述实验可以看出,sigmoid激活函数适合于在输出层作为二分类实验,以输出结果。
二.通过bp神经网络代码实验sigmoid激活函数
sigmoid函数代码定义:
def sigmoid(x):
return 1 / (1 + np.exp(-x))
使用sigmoid激活函数实现bp神经网络的完整代码,该神经网络是通过前面的跳高成绩去预测下一次跳高是成功还是失败,其中1代表成功0代表失败:
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def main():
# 14条数据
data = np.array([
[1, 0, 0, 1, 0, 1, 1, 1],
[0, 0, 1, 1, 0, 0, 1, 0],
[1, 1, 0, 1, 1, 1, 0, 1],
[0, 1, 0, 1, 0, 0, 1, 1],
[1, 0, 1, 1, 0, 1, 1, 1],
[1, 1, 0, 0, 1, 1, 1, 0],
[0, 0, 0, 1, 0, 0, 1, 1],
[1, 0, 1, 1, 0, 1, 1, 1],
[1, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 1],
[1, 0, 0, 1, 0, 1, 1, 0],
[0, 0, 1, 1, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 1],
[0, 1, 0, 1, 0, 1, 1, 1]])
print("原始数据:\n", data)
# 十四条数据的跳高成绩
highJump = np.array(
[0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0])
print("十四条数据的跳高成绩:\n", highJump)
# 第十五条数据的输入
data15 = np.array([0, 1, 0, 1, 1, 0, 1, 0])
print("第十五条数据的输入:\n", data15)
# 设置输入层与隐藏层之间的权值和阈值
wInput = np.random.random(size=(6, 8)) / 10
print("输入层与隐藏层之间的六组权值:\n", wInput)
bInput = np.random.random(size=(6, 8)) / 10
print("输入层与隐藏层之间的六组阈值:\n", bInput)
# 设置隐藏层与输出层之间的权值和阈值
wOutput = np.random.random(size=6) / 10
print("隐藏层与输出层之间的一组权值", wOutput)
bOutput = np.random.random(size=6) / 10
print("隐藏层与输出层之间的一组阈值", bOutput)
loss = 2
count = 0
while loss > 1.7489:
count = count + 1
loss = 0
outputNode = []
for i in range(0, 14):
# 正向传播
# 计算隐藏层节点输入
hide = []
for j in range(0, 6):
hideNode = 0
for k in range(0, 8):
hideNode = data[i, k] * wInput[j, k] + \
bInput[j, k] + hideNode
#print(hideNode)
hideNode = sigmoid(hideNode) # 激活函数
hide.append(hideNode)
hide = np.array(hide)
# print("隐藏层结点", hide)
output = 0
for j in range(0, 6):
output = hide[j] * wOutput[j] + bOutput[j] + output
output = sigmoid(output)
outputNode.append(output)
# print("输出层结点", output)
loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
outputNode = np.array(outputNode)
# 反向传播
# print("隐藏层结点", hide)
for i in range(0, 14):
# 隐藏层与输出层之间权值阈值更新
wOutputLoss = []
for j in range(0, 6):
wOutputLoss.append((outputNode[i] - highJump[i]) *
outputNode[i] * (1 - outputNode[i])
* hide[j])
wOutputLoss = np.array(wOutputLoss)
# print("wOutputLoss", wOutputLoss)
bOutputLoss = []
for j in range(0, 6):
bOutputLoss.append((outputNode[i] - highJump[i]) *
outputNode[i] * (1 - outputNode[i]))
bOutputLoss = np.array(bOutputLoss)
# print("bOutputLoss", bOutputLoss)
for j in range(0, 6):
wOutput[j] = wOutput[j] - 0.1 * wOutputLoss[j]
bOutput[j] = bOutput[j] - 0.1 * bOutputLoss[j]
# print("隐藏层与输出层更新后权值和阈值", wOutput, bOutput)
# 输入层与隐藏层之间权值更新
wInputLoss = np.ones((6, 8)) * 0
for j in range(0, 6):
for k in range(0, 8):
wInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
outputNode[i] *
(1 - outputNode[i]) * wOutput[j]
* hide[j] * (1 - hide[j]) * data[i][k])
wInputLoss = np.array(wInputLoss)
# print("wIutputLoss", wInputLoss)
bInputLoss = np.ones((6, 8)) * 0
for j in range(0, 6):
for k in range(0, 8):
bInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
outputNode[i] * (1 - outputNode[i]) *
wOutput[j] * hide[j] * (1 - hide[j]))
bInputLoss = np.array(bInputLoss)
#print("bIutputLoss", bInputLoss)
for j in range(0, 6):
for k in range(0, 8):
wInput[j][k] = wInput[j][k] - 0.1 * wInputLoss[j][k]
bInput[j][k] = bInput[j][k] - 0.1 * bInputLoss[j][k]
#print("输入层与隐藏层之间更新后的权值和阈值", wInput, bInput)
print("输出", output)
print("学习前的loss", loss)
loss = 0
for i in range(0, 14):
# 正向传播
# 计算隐藏层节点输入
hide = []
for j in range(0, 6):
hideNode = 0
for k in range(0, 8):
hideNode = data[i, k] * wInput[j, k] + \
bInput[j, k] + hideNode
hideNode = sigmoid(hideNode) # 激活函数
hide.append(hideNode)
hide = np.array(hide)
output = 0
for j in range(0, 6):
output = hide[j] * wOutput[j] + bOutput[j] + output
output = sigmoid(output)
loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
print("输出", output)
print("学习后的loss", loss)
# 预测
hide = []
for j in range(0, 6):
hideNode = 0
for k in range(0, 8):
hideNode = data15[k] * wInput[j, k] + \
bInput[j, k] + hideNode
hideNode = sigmoid(hideNode) # 激活函数
hide.append(hideNode)
hide = np.array(hide)
output = 0
for j in range(0, 6):
output = hide[j] * wOutput[j] + bOutput[j] + output
output = sigmoid(output)
print(output)
print(loss)
print(count)
if __name__ == '__main__':
main()
该bp神经网络包括了带有八个神经元(也就是原始数据中的八个01成绩)的输入层,带有六个神经元的隐藏层和一个神经元的输出层。
运行代码后得到结果:
上图是该网络首次完成学习前后的输出以及损失
上图是在第297次学习前后的输出以及损失并输出了最终的输出和损失,可以看到最后输出小于0.5,预测在下一次测试中较为有可能失败的。
函数表达式:
函数图像:
Tanh函数也称为双曲正切函数,取值范围为[-1,1]。Tanh函数是 sigmoid 的变形,Tanh函数是 0 均值的,因此实际应用中 Tanh 会比 sigmoid 更好。
通过上图,结合sigmoid函数可以得到tanh函数的几个特点:
1.双曲正切函数,tanh函数输出以0为中心,区间为[−1,1],tanh可以想象成两个sigmoid函数放在一起,性能要高于sigmoid函数
2.收敛速度相对于Sigmoid更快
3.但是仍然存在梯度饱和与exp计算的问题。
依据上述特点,可以初步总结tanh函数的应用场景:由于tanh是sigmoid的改进版,所以sigmoid函数的应用场景tanh也都可以用,而且tanh性能更加优异,所以在不确定激活函数的时候使用tanh进行尝试的优先级仅仅在relu之后,不同于sigmoid函数适用于输出层的特性,tanh函数还可以应用在隐层和更多的情况。
和之前一样下面通过两个角度更加输入的理解tanh函数
一.通过生活中的实例解释tanh激活函数:
使用之前的题目:已知某样本实验者的身高,体重,每日锻炼时长,猜测实验者的性别。
在上述阈值中可以判断出性别,tanh函数可以使用在sigmoid适用的场景。
二.通过bp神经网络代码实验tanh激活函数
tanh函数定义代码:
def tanh(x):
return np.sinh(x)/np.cosh(x)
使用上文中的跳高预测代码,稍作修改得到如下代码:
import numpy as np
def tanh(x):
return np.sinh(x)/np.cosh(x)
def main():
# 14条数据
data = np.array([
[1, 0, 0, 1, 0, 1, 1, 1],
[0, 0, 1, 1, 0, 0, 1, 0],
[1, 1, 0, 1, 1, 1, 0, 1],
[0, 1, 0, 1, 0, 0, 1, 1],
[1, 0, 1, 1, 0, 1, 1, 1],
[1, 1, 0, 0, 1, 1, 1, 0],
[0, 0, 0, 1, 0, 0, 1, 1],
[1, 0, 1, 1, 0, 1, 1, 1],
[1, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 1],
[1, 0, 0, 1, 0, 1, 1, 0],
[0, 0, 1, 1, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 1],
[0, 1, 0, 1, 0, 1, 1, 1]])
print("原始数据:\n", data)
# 十四条数据的跳高成绩
highJump = np.array(
[0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0])
print("十四条数据的跳高成绩:\n", highJump)
# 第十五条数据的输入
data15 = np.array([0, 1, 0, 1, 1, 0, 1, 0])
print("第十五条数据的输入:\n", data15)
# 设置输入层与隐藏层之间的权值和阈值
wInput = np.random.random(size=(6, 8)) / 10
print("输入层与隐藏层之间的六组权值:\n", wInput)
bInput = np.random.random(size=(6, 8)) / 10
print("输入层与隐藏层之间的六组阈值:\n", bInput)
# 设置隐藏层与输出层之间的权值和阈值
wOutput = np.random.random(size=6) / 10
print("隐藏层与输出层之间的一组权值", wOutput)
bOutput = np.random.random(size=6) / 10
print("隐藏层与输出层之间的一组阈值", bOutput)
loss = 2
count = 0
while loss < 2.1:
count = count + 1
loss = 0
outputNode = []
for i in range(0, 14):
# 正向传播
# 计算隐藏层节点输入
hide = []
for j in range(0, 6):
hideNode = 0
for k in range(0, 8):
hideNode = data[i, k] * wInput[j, k] + \
bInput[j, k] + hideNode
#print(hideNode)
hideNode = tanh(hideNode) # 激活函数
hide.append(hideNode)
hide = np.array(hide)
# print("隐藏层结点", hide)
output = 0
for j in range(0, 6):
output = hide[j] * wOutput[j] + bOutput[j] + output
output = tanh(output)
outputNode.append(output)
# print("输出层结点", output)
loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
outputNode = np.array(outputNode)
# 反向传播
# print("隐藏层结点", hide)
for i in range(0, 14):
# 隐藏层与输出层之间权值阈值更新
wOutputLoss = []
for j in range(0, 6):
wOutputLoss.append((outputNode[i] - highJump[i]) *
outputNode[i] * (1 - outputNode[i])
* hide[j])
wOutputLoss = np.array(wOutputLoss)
# print("wOutputLoss", wOutputLoss)
bOutputLoss = []
for j in range(0, 6):
bOutputLoss.append((outputNode[i] - highJump[i]) *
outputNode[i] * (1 - outputNode[i]))
bOutputLoss = np.array(bOutputLoss)
# print("bOutputLoss", bOutputLoss)
for j in range(0, 6):
wOutput[j] = wOutput[j] - 0.1 * wOutputLoss[j]
bOutput[j] = bOutput[j] - 0.1 * bOutputLoss[j]
# print("隐藏层与输出层更新后权值和阈值", wOutput, bOutput)
# 输入层与隐藏层之间权值更新
wInputLoss = np.ones((6, 8)) * 0
for j in range(0, 6):
for k in range(0, 8):
wInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
outputNode[i] *
(1 - outputNode[i]) * wOutput[j]
* hide[j] * (1 - hide[j]) * data[i][k])
wInputLoss = np.array(wInputLoss)
# print("wIutputLoss", wInputLoss)
bInputLoss = np.ones((6, 8)) * 0
for j in range(0, 6):
for k in range(0, 8):
bInputLoss[j][k] = ((outputNode[i] - highJump[i]) *
outputNode[i] * (1 - outputNode[i]) *
wOutput[j] * hide[j] * (1 - hide[j]))
bInputLoss = np.array(bInputLoss)
#print("bIutputLoss", bInputLoss)
for j in range(0, 6):
for k in range(0, 8):
wInput[j][k] = wInput[j][k] - 0.1 * wInputLoss[j][k]
bInput[j][k] = bInput[j][k] - 0.1 * bInputLoss[j][k]
#print("输入层与隐藏层之间更新后的权值和阈值", wInput, bInput)
print("输出", output)
print("学习前的loss", loss)
loss = 0
for i in range(0, 14):
# 正向传播
# 计算隐藏层节点输入
hide = []
for j in range(0, 6):
hideNode = 0
for k in range(0, 8):
hideNode = data[i, k] * wInput[j, k] + \
bInput[j, k] + hideNode
hideNode = tanh(hideNode) # 激活函数
hide.append(hideNode)
hide = np.array(hide)
output = 0
for j in range(0, 6):
output = hide[j] * wOutput[j] + bOutput[j] + output
output = tanh(output)
loss = ((output - highJump[i]) * (output - highJump[i])) / 2 + loss
print("输出", output)
print("学习后的loss", loss)
# 预测
hide = []
for j in range(0, 6):
hideNode = 0
for k in range(0, 8):
hideNode = data15[k] * wInput[j, k] + \
bInput[j, k] + hideNode
hideNode = tanh(hideNode) # 激活函数
hide.append(hideNode)
hide = np.array(hide)
output = 0
for j in range(0, 6):
output = hide[j] * wOutput[j] + bOutput[j] + output
output = tanh(output)
print(output)
print(loss)
print(count)
if __name__ == '__main__':
main()
上述代码和之前的bp神经网络有着相同的层,每层有相同的神经元,仅对loss结果和激活函数进行了修改。
上述代码在一开始的运行结果如图:
结果多次学习后的代码如图:
可见学习了506次之后,学习后的损失值反而更大了,这里可能是由于过拟合之类的问题造成的,
不过可以初步判定该段代码相对于sigmoid函数不是很适合tanh激活函数,也可以看到在该段代码中将tanh用于输出层的效果不如sigmoid。
relu函数:
relu函数图像:
relu函数的使用情况:
1.二分类问题中除了输出层外全部用relu
2.在不确定的情况下一般优先考虑relu
3.relu中负数会直接变为0
4.遇到死的神经元也会使用relu
relu函数的特点:
整流线性单元(Rectified linear unit,ReLU)是现代神经网络中最常用的激活函数,大多数前馈神经网络默认使用的激活函数。
优点:
1.使用ReLU的SGD算法的收敛速度比 sigmoid 和 tanh 快。
2. 在x>0区域上,不会出现梯度饱和、梯度消失的问题。
3. 计算复杂度低,不需要进行指数运算,只要一个阈值就可以得到激活值。
缺点:
ReLU的输出不是0均值(将每个像素的值减去训练集上所有像素值的平均值,比如已计算得所有像素点的平均值为128,所以减去128后,现在的像素值域即为[-128,127],即满足均值为零。)的。
relu函数的例子笔者就不再算一次了,基本算法思路和代码和上文一样。