本文主要讲解了深度学习中常用的激活函数的各种形式以及如何利用 PyTorch 对其进行实现。
最后利用学到的激活函数,建立了一个简单的三层神经网络模型。
一、激活函数
- 1.Sigmoid函数
- 2.Tanh函数
- 3.ReLU函数
二、神经网络的建立
一、激活函数
激活函数是深度学习中一个很重要的概念。
在神经网络中,我们经常使用线性运算来解决线性问题。
但是日常生活中的大多数问题,都不是简单的线性问题。
为此,我们引入了激活函数来解决非线性的问题。
常见的激活函数有 Sigmoid
函数(又名 Logistic
函数)、tanh
函数(又名双曲正切函数
),ReLU
函数(又名线性修正单元函数
)等。
Sigmoid 函数是深度学习发展中最经典的且最先被使用的激活函数之一。
它的公式如下所示:
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z) = \frac{1}{1+e^{-z}}
σ(z)=1+e−z1
其中
z
z
z 表示函数的输入,
σ
\sigma
σ 表示函数的输出。
根据公式,我们可以画出相关的几何图像:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 手写 sigmoid 函数
def sigmoid(x): return 1 / (1 + np.exp(-x))
# 画图
y = np.linspace(-10, 10, 100)
plt.plot(y, sigmoid(y), 'b')
plt.grid(linestyle='--')
plt.xticks([-4, -3, -2, -1, 0, 1, 2, 3, 4])
plt.yticks([0, 0.5, 1])
plt.ylim(0, 1)
plt.xlim(-4, 4)
plt.show()
从图中可以看到,激活函数 Sigmoid 在定义域内处处可导。
但是,通过曲线的斜率,可以发现,当输入一个较小或较大的数时,该函数的导数会变得很小,梯度趋近于 0 。
举个例子,每一次的梯度值都减少 0.25,如果神经网络的隐含层过多,那么当梯度穿过多层后将变得非常接近于 0,即出现梯度消失的现象,进而造成模型无法收敛。
除了自定义 Sigmoid 函数之外,我们还可以通过 PyTorch 对其进行定义:
import torch
import torch.nn as nn
import torch.nn.functional as F
# pytorch 中有两种实现方法
x = torch.tensor([-1.0, 1.0, 2.0, 3.0])
output = torch.sigmoid(x)
print(output)
s = nn.Sigmoid()
output = s(x)
print(output)
输出结果如下:
tensor([0.2689, 0.7311, 0.8808, 0.9526])
tensor([0.2689, 0.7311, 0.8808, 0.9526])
2.Tanh函数
Tanh 是双曲函数中的双曲正切函数。
在数学中,双曲正切函数都是由双曲正弦函数和双曲余弦函数推导而来。
函数的具体形式如下:
t
a
n
h
(
x
)
=
e
x
−
e
−
x
e
x
+
e
−
x
tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}
tanh(x)=ex+e−xex−e−x
Tanh 的函数图像如下:
def tanh(x): return 2*sigmoid(2*x)-1
y = np.linspace(-10, 10, 100)
plt.plot(y, tanh(y), 'b')
plt.grid(linestyle='--')
plt.xlabel('X Axis')
plt.ylabel('Y Axis')
plt.xticks([-4, -3, -2, -1, 0, 1, 2, 3, 4])
plt.yticks([-1, 0, 1])
plt.ylim(-1, 1)
plt.xlim(-4, 4)
plt.show()
从上图可以看出,双曲正切函数和 Sigmoid 函数图像很相似,但是 Tanh 函数的范围为(-1,1),而 Sigmoid 函数的范围为 (0,1)。
同 Sigmoid 类似,PyTorch 中也有两种方式实现 tanh:
output = torch.tanh(x)
print(output)
t = nn.Tanh()
output = t(x)
print(output)
输出结果如下:
tensor([-0.7616, 0.7616, 0.9640, 0.9951])
tensor([-0.7616, 0.7616, 0.9640, 0.9951])
3.ReLU函数
双曲正切函数和 Sigmoid 函数相似,也存在着梯度消失现象。
且由于解析式中存在幂运算,计算机需要消耗大量的时间成本。
因此,为了解决梯度消失的问题,线性修正单元函数(Rectified Linear Units,简称ReLU)孕育而生。
ReLU 函数是目前最常用的激活函数之一。
公式如下所示:
f
(
x
)
=
{
0
x
<
0
x
x
≥
0
f(x)= \begin{cases} 0& \text{x $<$ 0}\ x& \text{x $\geq$ 0} \end{cases}
f(x)={0xx < 0x ≥ 0
其中
x
x
x 为常数。
可以看到,当
x
<
0
x<0
x<0 时,ReLU 全部取值为 0,梯度也为 0,减少了梯度的运算成本(这种现象称为硬饱和)。
当
x
≥
0
x\geq 0
x≥0 时,ReLU 的取值为
x
x
x, 梯度始终为一个固定的值,进而缓解了梯度消失的问题。
其函数图像如下所示:
def relu(x): return np.where(x >= 0, x, 0)
y = np.linspace(-10, 10, 1000)
plt.plot(y, relu(y), 'b')
plt.grid(linestyle='--')
plt.xticks([-3, -2, -1, 0, 1, 2, 3])
plt.yticks([0, 1, 2, 3])
plt.ylim(0, 3)
plt.xlim(-3, 3)
plt.show()
利用 PyTorch 中的 ReLU 函数处理输入数据:
output = torch.relu(x)
print(output)
relu = nn.ReLU()
output = relu(x)
print(output)
输出结果如下:
tensor([0., 1., 2., 3.])
tensor([0., 1., 2., 3.])
至此我们学习完了深度学习中常见的几种激活函数。
如果我们需要解决的是二分类问题,我们一般会将最后一层设置为 Sigmoid 函数层。
因为,从 Sigmoid 函数图像可以看出,该函数的范围为 (0,1),这样可以很好的表示概率值。
综上,考虑到每种激活函数的特性,我们得到了以下的规则
- 如果在神经网络内部(隐含层)需要使用激活函数,一般会使用
ReLU
函数或者ReLU
函数的改进来进行激活。 - 如果是二分类问题,那么会在神经网络的最后一层加上一个
Sigmoid
函数层。 - 如果是多分类问题,那么会在神经网络的最后一层加上一个
Softmax
函数层。
二、神经网络的建立
神经网络其实就是有很多各线性函数和非线性函数组成的复杂函数。
至此,我们已经学习完了线性函数和非线性函数的定义方式。
接下来,让我们根据这些知识点建立一个完整的神经网络模型。
在利用 PyTorch 建立神经网络模型时,需要注意下面几个点:
- 自定的神经网络类必须继承
nn.Module
。 - 自定义类中需要实现
__init__
和forward
函数。 __init__
: 定义网络的结构。forward
:定义数据在模型中的传播路径。
# 自定的神经网络类必须继承 `nn.Module`。
class NeuralNet(nn.Module):
# init 函数主要用于定义,神经网络中有那些结果
def __init__(self, input_size, hidden_size):
super(NeuralNet, self).__init__()
self.linear1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(hidden_size, 1)
self.sigmoid = nn.Sigmoid()
# 将输出传入网络模型
def forward(self, x):
out = self.linear1(x)
out = self.relu(out)
out = self.linear2(out)
out = self.sigmoid(out)
return out
# 测试代码
model = NeuralNet(10, 20)
model
输出结果如下:
NeuralNet(
(linear1): Linear(in_features=10, out_features=20, bias=True)
(relu): ReLU()
(linear2): Linear(in_features=20, out_features=1, bias=True)
(sigmoid): Sigmoid()
)
从结果可以看出,我们建立的神经网络结构为:连接层(线性函数层)
-> ReLU 层
-> 连接层
-> Sigmoid 函数层
。
如果中间有多个 ReLU 函数层,那么按照上面的思路,我们就需要定义 relu1,relu2 … 等多个变量。
当然在定义激活层时,我们可以将其放在 forward()
函数中,这样可以减少变量的定义,如下:
class NeuralNet(nn.Module):
def __init__(self, input_size, hidden_size):
super(NeuralNet, self).__init__()
self.linear1 = nn.Linear(input_size, hidden_size)
self.linear2 = nn.Linear(hidden_size, 1)
# 将激活函数层直接放到 forward 中
def forward(self, x):
out = torch.relu(self.linear1(x))
out = torch.sigmoid(self.linear2(out))
return out
# 测试代码
model = NeuralNet(10, 20)
model
输出结果如下:
NeuralNet(
(linear1): Linear(in_features=10, out_features=20, bias=True)
(linear2): Linear(in_features=20, out_features=1, bias=True)
)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)