常用的激活函数
在神经网络中,激活函数决定一个节点从一组给定输入的输出,而非线性激活函数允许网络复制复杂的非线性行为。由于大多数神经网络使用某种形式的梯度下降进行优化,激活函数必须是可微的此外,复杂的激活函数可能会产生关于渐变消失和爆炸的问题。因此,神经网络倾向于使用一些选定的激活函数(sigmoid, ReLU和它们的变种)
Sigmoid
Sigmoid函数是一个有着优美S形曲线的数学函数,在逻辑回归、人工神经网络中有着广泛的应用。
Sigmoid数学形式:
函数图:
sigmoid函数连续,光滑,严格单调,以(0,0.5)中心对称,是一个非常良好的阈值函数。
当x趋近负无穷时,y趋近于0;趋近于正无穷时,y趋近于1;x=0时,y=0.5。
代码示例:
import torch
import torch.nn as nn
m = nn.Sigmoid()
input = torch.randn(2)
output = m(input)
PReLU(Parameteric Rectified Linear Unit)
代码:
import torch
import torch.nn as nn
m = nn.PReLU()
input = torch.randn(2)
output = m(input)
GELU(Gaussian Error Linear Units)
import torch
import torch.nn as nn
m = nn.GELU()
input = torch.randn(2)
output = m(input)
Softmax
将各个输出节点的输出值范围映射到[0, 1],并且约束各个输出节点的输出值的和为1的函数。
SoftPlus
import torch
import torch.nn as nn
m = nn.Softplus()
input = torch.randn(2)
output = m(input)
常用的基础模型
MLP (Muti-Layer Perceptron)
MLP包括包括三层:输入层、隐藏层和输出层,MLP神经网络不同层之间是全连接的(全连接的意思就是:上一层的任何一个神经元与下一层的所有神经元都有连接)
MLP中包括权重、偏置和激活函数,这三个是构成MLP的基础
- 权重(weight): 下一层的每个神经元和上一层的每个神经元之间都有权重值,当前神经元的值是由上一层所有神经元和其对应的权重的线性加权求和而得到的
- 偏置(bias): 权重加权求和完之后再加上bias来得到当前神经元的值, 权重+偏置构成线性函数,即
- 激活函数(activation function): 起非线性映射的作用,其可将神经元的输出幅度限制在一定范围内,一般限制在(-1
1)或(01)之间。
代码示例:
class MLP(torch.nn.Module):
def __init__(self, in_dim, h1_dim, h2_dim, out_dim):
super(MLP, self).__init__()
self.fc1 = torch.nn.Linear(in_dim, h1_dim) # 第一个隐含层
self.fc2 = torch.nn.Linear(h1_dim, h2_dim) # 第二个隐含层
self.fc3 = torch.nn.Linear(h2_dim, out_dim) # 输出层
def forward(self, din):
# 前向传播, 输入值:din, 返回值 dout
dout = F.relu(self.fc1(din))
dout = F.relu(self.fc2(dout))
dout = F.softmax(self.fc3(dout), dim=1) # 输出层
return dout
BP(Back Propagation)
反向传播(英语:Backpropagation,缩写为BP)指的是计算神经网络参数梯度的方法。总的来说,反向传播依据微积分中的链式法则,沿着从输出层到输入层的顺序,依次计算并存储目标函数有关神经网络各层的中间变量以及参数的梯度。然后用计算出来的梯度和learning rate来更新各层的参数
# 计算predict 和 label 之间的loss值
loss = F.mse_loss(pre, ground_truth)
# 反向传播
loss.backward()
# 更新权值
w = w - lr * w.grad
b = b - lr * b.grad
CNN (Convolutional Neural Network)
卷积 运算
卷积层得名于卷积(convolution)运算,在二维卷积层中,一个二维输入数组和一个二维核(kernel)数组通过互相关运算输出一个二维数组。如图所示,输入是一个高和宽均为3的二维数组。我们将该数组的形状记为(3,3), 核数组的高和宽分别为2。该数组在卷积计算中又称卷积核或过滤器(filter)。卷积核窗口(又称卷积窗口)的形状取决于卷积核的高和宽,即(2,2)。图中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:。
填充(padding)
填充(padding)是指在输入高和宽的两侧填充元素(通常是0元素)。图中在原输入高和宽的两侧分别添加了值为0的元素,使得输入高和宽从3变成了5,并导致输出高和宽由2增加到4。图中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:0×0+0×1+0×2+0×3=00×0+0×1+0×2+0×3=0。
步幅(stride)
刚才的例子里,在高和宽两个方向上步幅均为1。我们也可以使用更大步幅。下图展示了在高上步幅为3、在宽上步幅为2的二维互相关运算。可以看到,输出第一列第二个元素时,卷积窗口向下滑动了3行,而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右滑动2列时,由于输入元素无法填满窗口,无结果输出。图中的阴影部分为输出元素及其计算所使用的输入和核数组元素:。
池化(pooling)
池化层每次对输入数据的一个固定形状窗口(又称池化窗口)中的元素计算输出。不同于卷积层里计算输入和核的互相关性,池化层直接计算池化窗口内元素的最大值或者平均值。该运算也分别叫做最大池化或平均池化。在二维最大池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当池化窗口滑动到某一位置时,窗口中的输入子数组的最大值即输出数组中相应位置的元素。
这里把取最大换成取平均的话就可以达到平均池化的效果
# 计算predict 和 label 之间的loss值
loss = F.mse_loss(pre, ground_truth)
# 反向传播
loss.backward()
# 更新权值
w = w - lr * w.grad
b = b - lr * b.grad
# LeNet卷积神经网络代码
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
nn.Sigmoid(),
nn.MaxPool2d(2, 2), # kernel_size, stride
nn.Conv2d(6, 16, 5),
nn.Sigmoid(),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential
nn.Linear(16*4*4, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1)) # flatten
return output
LSTM (Long Short-Term Memory)
LSTM 中引入了3个门,即输入门(input gate)、遗忘门(forget gate)和输出门(output gate),以及与隐藏状态形状相同的记忆细胞(某些文献把记忆细胞当成一种特殊的隐藏状态),从而记录额外的信息。
class LSTMCell(torch.nn.Module):
def __init__(self, input_size, hidden_size):
super().__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.ft = torch.nn.Linear(input_size+hidden_size, hidden_size)
self.it = torch.nn.Linear(input_size+hidden_size, hidden_size)
self.zt = torch.nn.Linear(input_size+hidden_size, hidden_size)
self.ot = torch.nn.Linear(input_size+hidden_size, hidden_size)
def forward(self, x, state):
'''
:param x: [B, input_size]
:return: h[B, hidden_size], c[B, hidden_size]
'''
h, c = state # h(t-1), c(t-1)
input = torch.cat([x,h],dim=1) # [B, input_size+hidden_size]
ft = self.ft(input).sigmoid()
it = self.it(input).sigmoid()
zt = self.zt(input).tanh()
ot = self.ot(input).sigmoid()
ct = ft * c + it * zt
ht = ot * ct.tanh()
return ht, ct
class LSTM(torch.nn.Module):
def __init__(self, input_size, hidden_size):
super().__init__()
self.cell = LSTMCell(input_size, hidden_size)
def forward(self, x, state=None):
'''
:param x: [B, T, C]
'''
if state is None:
h = torch.zeros(x.shape[0], self.cell.hidden_size,
device=x.device)
c = torch.zeros(x.shape[0], self.cell.hidden_size,
device=x.device)
else:
h, c = state
inputs = x.unbind(1)
outputs = []
for i in range(len(inputs)):
h, c = self.cell(inputs[i], (h, c))
outputs.append(h)
return torch.stack(outputs, dim=1), (h,c)
GRU(Gated Recurrent Unit)
GRU(Gate Recurrent Unit)是循环神经网络(Recurrent Neural Network, RNN)的一种。和LSTM(Long-Short Term Memory)一样,也是为了解决长期记忆和反向传播中的梯度等问题而提出来的。
相比LSTM,使用GRU能够达到相当的效果,并且相比之下更容易进行训练,能够很大程度上提高训练效率,因此很多时候会更倾向于使用GRU。
def gru(inputs, state, params):
W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
Z = torch.sigmoid(torch.matmul(X, W_xz) + torch.matmul(H, W_hz) + b_z)
R = torch.sigmoid(torch.matmul(X, W_xr) + torch.matmul(H, W_hr) + b_r)
H_tilda = torch.tanh(torch.matmul(X, W_xh) + torch.matmul(R * H, W_hh) + b_h)
H = Z * H + (1 - Z) * H_tilda
Y = torch.matmul(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H,)