之前介绍的模型本质上都是线性模型,然而现实世界中很多真实的问题不都是线性可分的,即无法使用一条直线、平面或者超平面分割不同的类别,其中典型的例子是异或问题(Exclusive OR,XOR),即假设输入为和,如果它们相同,即当、或、时,输出;如果它们不相同,即当、或、时,输出。此时,无法使用线性分类器恰当地将输入划分到正确的类别。
多层感知器(Multi-layer Perceptron,MLP)是解决线性不可分问题的一种解决方案。
多层感知器指的是堆叠多层线性分类器,并在中间层(也叫隐含层,Hidden layer)增加非线性激活函数。例如,可以设计如下的多层感知器:
式中,ReLU(Rectified Linear Unit)是一种非线性激活函数,其定义为当某一项输入小于0时,输出为0;否则输出相应的输入值。即:
和分别表示第层感知器的权重和偏置项。
如果将相应的参数进行如下的设置:、、,,即可解决异或问题。
那么,该网络是如何解决异或问题的呢?其主要通过两个关键的技术,即增加了一个含两个节点的隐含层()以及引入非线性激活函数(ReLU)。通过设置恰当的参数值,将在原始输入空间中线性不可分的问题映射到新的隐含层空间,使其在该空间内线性可分。
原空间内和两个点,分别被映射到和;而和两个点,都被映射到。此时就可以使用一条直线将两类点分割,即成功转换为线性可分问题。
下图4-6展示了更一般的多层感知器,其中引入了更多的隐含层(没有画出非线性激活函数),并将输出层设置为多类分类层(使用Softmax函数)。输入层和输出层的大小一般是固定的,与输入数据的维度以及所处理问题的类别相对应,而隐含层的大小、层数和激活函数的类型等需要根据经验以及实验结果设置。它们又被称为超参数(Hyper-parameter)。
一般来讲,隐含层越大、层数越多,即模型的参数越多、容量越大,多层感知器的表达能力就越强,但是此时较难优化网络的参数。而如果隐含层太小、层数过少,则模型的表达能力不足。为了在模型容量和学习难度中间寻找到一个平衡点,需要根据不同的问题和数据,通过调参过程寻找合适的超参数组合。
模型实现:
1.神经网络层与激活函数
上面介绍了从简单的线性回归到复杂的多层感知器等多种神经网络模型,接下来介绍如何使用PyTorch实现这些模型。实际上,使用第3章介绍的PyTorch提供的基本张量存储及运算功能,就可以实现这些模型,但是这种实现方式不但难度高,而且容易出错。因此,PyTorch将常用的神经网络模型封装到了torch.nn包内,从而可以方便灵活地加以调用。如通过以下代码,就可以创建一个线性映射模型(也叫线性层)。
from torch import nn
linear = nn.Linear(in_features,out_features)
代码中的in_features是输入特征的数目,out_features是输出特征的数目。可以使用该线性映射层实现线性回归模型,只要将输出特征的数据设置为1即可。当实际调用线性层时,可以一次性输入多个样例,一般叫作一个批次(Batch),并同时获得每个样例的输出。所以,如果输入张量的形状是(batch,in_features),则输出的张量的形状是(batch,out_features)。采用批次操作的好处是可以充分利用GPU等硬件的多核并行计算能力,大幅提高计算的效率。具体示例如下:
linear = nn.Linear(32,2) # 输入是32维,输出是2维
inputs = torch.rand(3,32) # 创建一个形状为(3,32)的随机张量,3为批次大小
outputs = linear(inputs)
print(outputs)
运行结果:
Sigmoid、Softmax等各种激活函数包含在torch.nn.functional中,实现对输入按元素进行非线性运算,调用方式如下:
我的本机上使用的函数functional,会提示这个,所以改成了如下代码:
activation = torch.sigmoid(outputs)
print(activation)
activation = torch.softmax(outputs,dim=1)
print(activation)
activation = torch.relu(outputs)
print(activation)
除了Sigmoid,Softmax和ReLU函数,PyTorch还提供了tanh等多种激活函数。
2.自定义神经网络模型
通过对上文介绍的神经网络层以及激活函数进行组合,就可以搭建更复杂的神经网络模型。在PyTorch中构建一个自定义神经网络模型非常简单,就是从torch.nn中的Module类派生一个子类。并实现构造函数和forword函数。其中,构造函数定义了模型所需的成员对象,如构成该模型的各层,并对其中的参数进行初始化等。而forword函数用来实现该模块的前向过程,即对输入进行逐层的处理,从而得到最终的输出结果。
下面以多层感知器模型为例,介绍如何自定义一个神经网络模型,其代码如下:
import torch
from torch import nn
from torch.nn import functional as F
class MLP(nn.Module):
def __init__(self,input_dim,hidden_dim,num_class):
super(MLP,self).__init__()
# 线性变换:输入层->隐含层
self.linear1 = nn.Linear(input_dim,hidden_dim)
# 使用ReLU激活函数
self.activate = torch.relu
# 线性变换:隐含层->输出层
self.linear2 = nn.Linear(hidden_dim,num_class)
def forward(self,inputs):
hidden = self.linear1(inputs)
activation = self.activate(hidden)
outputs = self.linear2(activation)
props = torch.softmax(outputs,dim=1)
return props
mlp = MLP(input_dim=4,hidden_dim=5,num_class=2)
inputs = torch.rand(3,4)
# 输入形状为(3,4)的张量,其中3表示有3个输入,4表示每个输入的维度
props = mlp(inputs)