结合前面文章介绍的知识,用非线性函数搭建神经网络,用优化器和损失函数来计算权重。通过本章,你将能够从头搭建一个较为复杂的神经网络,实现较为复杂的功能。
前馈神经网络Feed-Forward Neutral Network
定义:各神经元从输入层开始,接收前一层输入,并输出到下一层,直到输出层,中间没有反馈,这样的网络称作前馈神经网络。
前馈神经网络是最简单的神经网络类型。
一般情况下,最后一层称为输出层(output layer),中间的称为隐层(hidden layer).神经网络中每一层的神经元常常使用相同的逐元素非线性运算。所以可以进一步把每一层神经网络分为线性运算层和非线性运算层。这两个层分别叫做“线性层”和“激活层”。
前馈神经网络可以包含下面类型的层:
- 线性层(linear layer)和卷积层(convolution layer)。这两种层对输入进行线性运算,层内维护着线性运算的权重。
- 激活层(activation layer)。该层对数据进行非线性运算,可以是逐元素非线性运算也可以是其他类型的非线性运算。
- 归一化层(normalization layer)。根据输入的均值和方差对数据进行归一化处理。
- 池化层(pooling layer)和视觉层(vision layer)。这两种层与数据重采样有关,包括数据下采样(隔几个数据取1个数据)、上采样(把一个数据复制出很多份)和重新排序。
- 丢弃层(dropout layer)。在输入中随机选择一些输出。
- 补齐层(padding layer)。采用循环补齐等方法让输入变多。
使用torch.nn.Sequential搭建前馈神经网络
from torch.nn import Linear,ReLU,Sequential
net=Sequential(
Linear(4,2),
ReLU(),
Linear(2,1),
ReLU()
)
print(net)
运行结果:
Sequential(
(0): Linear(in_features=4, out_features=2, bias=True)
(1): ReLU()
(2): Linear(in_features=2, out_features=1, bias=True)
(3): ReLU()
)
除了利用torch.nn.Sequentiao搭建前馈神经网络,还可以使用torch.nn.Module来搭建,将在后续章节介绍。
全连接层和全连接神经网络
前馈神经网络是最简单的神经网络类型,全连接神经网络是最简单的前馈神经网络类型。
全连接层(Fully Connected layer):虽有输出和该层的输入都连接。
全连接神经网络(Fully Connected neural network):仅由全连接层组成的前馈神经网络。
pytorch可以使用torch.nn.Sequential搭建前馈神经网络,所以我们同样也可以使用它来搭建全连接神经网络。注意,我们要在搭建时向其传递表示线性运算的torch.nn.Linear和表示非线性运算的激活函数。
非线性激活
分为两类:基于逐元素非线性运算的激活层和多元素组合运算的激活层。
基于逐元素非线性运算的激活层:使用激活函数对张量进行逐元素运算。如ReLU
多元素组合运算的激活层:利用多个元素的值联合计算,i.e.,softmax()
逐元素激活
- S形激活:把[-∞,+∞]数据映射到一个有限区间中,例如torch.nn.Sigmoid。 但当输入的绝对值较大,可能导致梯度=0
- 单侧激活:正值保持不变,负值压缩到一个区间.例如Relu
- 皱缩激活:比较少用。
非逐元素激活
都是和softmax()函数有关。
- torch.nn.Softmax:只对张量的某一维做softmax,构造实例时需要指定维度。
- torch.nn.Softmax2d:只对4维张量做运算,且softmax()函数作用在第一维(维度从0开始)。
- torch.nn.LogSoftmax:在1. 的基础上求自然对数ln(). 得到的都是负值,同样需要指定维度。
- 非逐元素激活相当于进行了多值判决,从多个特征中选择了一个特征。
网络架构的选择
确定网络有几层?每层几个神经元?神经元的激活函数?
欠拟合:网络复杂性不够。
过拟合:网络复杂性过大。
欠拟合和过拟合都会导致神经网络在新数据上的性能(泛化能力)变差。又称为泛化误差(generalization error).它分为三类:
偏差差错:越大说明网络复杂度不行,欠拟合了。
方差差错:复杂度太高了,过拟合了
噪声:模型无法消除的部分。
训练集、验证集、测试集
如何判断欠拟合(高偏差差错)与过拟合(高方差差错)?
将数据分成三份:训练、验证、测试
训练集:训练并计算权重。
验证集:判断是否出现欠拟合或过拟合,确定网络架构或控制模型复杂程度的参数。
测试集:评价最终效果。
比例:6:2:2 (一般情况下)
学习曲线和验证曲线:横轴为训练样本数量,纵轴为差错。
学习曲线随着样本增多,差错可能会变大,验证曲线随着样本增多,可能会降低。(都不超过最优性能的情况下)
欠拟合 | 过拟合 | |
---|---|---|
误差来源 | 偏差差错 | 方差差错 |
模型复杂度 | 过低 | 过高 |
学习曲线和验证曲线特征 | 收敛到比较大的差错值 | 两个曲线之间差别很大 |
解决方案 | 增加模型复杂度 | 减小模型复杂度或增加训练集 |
基于全连接网络的非线性回归实例
生成有噪声数据:
import torch
torch.manual_seed(seed=0)
sample_num=1000
features=torch.rand(sample_num,2)*12-6
'''
torch.rand(sample_num, 2):
torch.rand() 函数生成一个给定形状的张量,其元素值在 [0, 1) 区间内均匀分布的随机数。
sample_num 是张量的行数,2 是列数。这意味着每个样本有两个特征,总共有 sample_num 个样本。
*12:
将 torch.rand(sample_num, 2) 生成的每个元素乘以 12。由于原始的随机数范围是 [0, 1),乘以 12 后,新的范围变为 [0, 12)。
-6:
从上一步的结果中减去 6。这个操作会进一步调整每个元素的范围。由于原先范围是 [0, 12),减去 6 后,新的范围变为 [-6, 6)。
'''
noises=torch.randn(sample_num)
# torch.randn() 生成的是一个具有标准正态分布的随机数的张量。这个函数生成的随机数均值为 0,标准差为 1,这是标准正态分布的特性。
def himmelblau(x):
return(x[:,0]**2+x[:,1]-11)**2+(x[:,0]+x[:,1]**2-7)**2
hims=himmelblau(features)*0.01
labels=hims+noises
分割数据集并查看数据集上的噪声大小:
train_num,validate_num,test_num=600,200,200
train_mse=(noises[:train_num]**2).mean()
validate_mse=(noises[train_num:-test_num]**2).mean()
test_mse=(noises[-test_num:]**2).mean()
print('真实:训练集MSE={:g},验证集={:g},测试集={:g}'.format(train_mse,validate_mse,test_mse))
确定网络架构:
先构建一个网络,然后通过差错在通过减小或增大网络来判断是否出现过拟合与欠拟合,然后去调整网络架构。
import torch.nn as nn
hidden_features=[6,2]
layers=[nn.Linear(2,hidden_features[0])]
for idx,hidden_feature in enumerate(hidden_features):
layers.append(nn.Sigmoid())
next_hidden_feature=hidden_features[idx+1] if idx+1<len(hidden_features) else 1
layers.append(nn.Linear(hidden_feature,next_hidden_feature))
# *layers 将列表 layers 中的所有元素展开,并将它们作为单独的参数传递给 nn.Sequential。
net=nn.Sequential(*layers)
print("神经网络为{}".format(net))
输出的网络架构:
Sequential(
(0): Linear(in_features=2, out_features=6, bias=True)
(1): Sigmoid()
(2): Linear(in_features=6, out_features=2, bias=True)
(3): Sigmoid()
(4): Linear(in_features=2, out_features=1, bias=True)
)
训练模型,并计算训练误差和验证误差:
import torch.optim
optimizer=torch.optim.Adam(net.parameters())
criterion=nn.MSELoss()
train_entry_num=600#选择训练样本数
n_iter=100000#最大迭代次数
for step in range(n_iter):
outputs=net(features)
preds=outputs.squeeze()
loss_train=criterion(preds[:train_entry_num],labels[:train_entry_num])
loss_validate=criterion(preds[train_num:-test_num],labels[train_num:-test_num])
if step%10000==0:
print("#{}训练集MSE{:g},验证集MSE{:g}".format(step,loss_train,loss_validate))
optimizer.zero_grad()
loss_train.backward()
optimizer.step()
print("训练集MSE{:g},验证集MSE{:g}".format(loss_train,loss_validate))
测试性能:
outputs=net(features)
preds=outputs.squeeze()
loss=criterion(preds[-test_num:],labels[-test_num:])
print(loss)