泛化
(1)泛化能力
就是模型的拟合程度,一般来说对于泛化能力,我们采取三种级别进行衡量,“欠拟合”,“正常拟合”,“过拟合”。
(2)泛化差错
泛化差错分为三类:“偏差差错”(bias),“方差差错”(variance),“噪声”(noise)。Bias属于由于网络结构缺陷而产生的,网络越复杂,Bias越小,如果Bias过大,证明出现欠拟合。Variance是由于训练数据和测试数据的差异而产生的,对于固定的训练数据,网络越简单,Variance越小,Variance过大,证明出现过拟合。而噪声属于不可消除部分。
总的来说:高偏差
→
\rightarrow
→欠拟合,高方差
→
\rightarrow
→过拟合,以模型复杂度来作为自变量,差错作为因变量,画出图像如下:
泛化能力的判断
我们可以通过绘制学习曲线和验证曲线来判断当前是否出现高Bias(欠拟),还是高Variance(过拟)。我们在训练中,一般将数据集分为训练集(train set),验证集(validation set),测试集(test set)。
- 训练集负责权重的计算
- 验证集用于判断是否出现欠拟合过拟合,以此作为标准来控制网络结构和控制模型的复杂程度的参数
- 而测试集作为评价最终的结构,我们绘制学习曲线需要训练集和验证集,不需要测试集。
一般来说,遵循622比例分配一个数据集。(60%,20%,20%)
学习曲线和验证曲线在上的表现(差错Loss和训练数据量的关系):
解决方法
总结一下步骤:
- step 1:根据训练曲线和验证曲线来判断当前模型泛化能力,是属于“过”还是“欠”
- step 2:根据泛化能力做出对模型调整
【 Loss → \rightarrow →泛化能力 → \rightarrow →模型复杂度调整】 → \rightarrow →Loss → \rightarrow →…循环下去
实验:根据模拟数据进行一次网络结构调整
本实验基于Pytorch
(1)模拟生成数据集
samples_size=[1000],labels_size=[1000]
import torch
from debug import ptf_tensor
torch.manual_seed(seed=0) #固定随机数种子
sample_sum=1000 #生成1000个样本
features=torch.rand(sample_sum,2)*12-6 #size[1000,2]
noises=torch.randn(sample_sum) #size[1000]
def himmelblau(x):
return (x[:,0]**2+x[:,1]-11)**2+(x[:,0]+x[:,1]**2-7)**2
hims=himmelblau(features)*0.01 #size[1000]
labels=hims+noises #size[1000]
ptf_tensor(features,'features')
ptf_tensor(noises,'noise')
ptf_tensor(hims,'hims')
ptf_tensor(labels,'labels')
(2)数据集的真实噪声率(真实的均方误差)
60%训练,20%验证,20%测试
计算数据集的真实噪声率:
train_sum, validate_sum, test_sum =600, 200, 200
#计算出这些数据的噪声率
train_mse=(noises[:train_sum]**2).mean()
validate_mse=(noises[train_sum:-test_sum]**2).mean()
test_mse=(noises[-test_sum:]**2).mean()
print('Train set:{:g}\nValidate set:{:g}\nTest set:{:g}'.format(train_mse,validate_mse,test_mse))
(3)打造网络结构
开始我们考虑使用一个:3层神经网络,前两个是隐含层,每层有2个神经元,最后一层是输出层有一个神经元。
import torch.nn as nn
hidden_features=[2,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))
net=nn.Sequential(*layers) # 从列表变成一个神经网络
print(net) #输出网络结构
(4)训练迭代
使用自己假设的网络结构如下进行迭代训练:
Sequential(
(0): Linear(in_features=2, out_features=2, bias=True)
(1): Sigmoid()
(2): Linear(in_features=2, 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_sum=100 #所有的训练的样本数量 =100,200,300,400,500,600
n_iter=100000 # 最大的迭代次数
for n_iter in range(n_iter):
ouputs=net(features)
preds=ouputs.squeeze()
loss_train=criterion(preds[:train_entry_sum],labels[:train_entry_sum])
loss_validate=criterion(preds[train_sum:-test_sum],labels[train_sum:-test_sum])
if n_iter % 1000 == 0 :
print('#step {},Trian MSE:{},Validate MSE:{}'.format(n_iter,loss_train,loss_validate))
optimizer.zero_grad()
loss_train.backward()
optimizer.step()
运行以上的代码,输出:
我们真实的MSE(均方误差)和我们迭代计算的MSE(均方误差)
如下表:
训练条目数量 | Train_MSE | Validate_MSE |
---|---|---|
100 | 3.00 | 4.00 |
200 | 2.70 | 3.40 |
300 | 3.20 | 3.14 |
400 | 3.00 | 3.18 |
500 | 3.38 | 3.21 |
600 | 3.26 | 3.24 |
画出散点图:
由图可以看到,非常明显高偏差,我们需要增加模型的复杂度,我们在神经元个数,我们有两个隐含层,一个输出层,我们应该对隐含层进行修改,我们尝试把2 → \rightarrow → 6,把原来每层2个神经元改成6个神经元。,然后重新训练,得到如下输出:
Train set:0.918333
Validate set:0.902182
Test set:0.978382
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)
)
#step 0,Trian MSE:18.523536682128906,Validate MSE:24.195621490478516
....
#step 98000,Trian MSE:0.44830459356307983,Validate MSE:2.7131242752075195
#step 99000,Trian MSE:0.4482784867286682,Validate MSE:2.7131223678588867
我们可以理解成:真实的MSE就是我们目标的Loss。
现在我们调节变量train_entry_sum
=100,200,300,400,500,600,然后计算当step=9000时,Train MSE 和Validate MSE的值然后描绘一个X=train_entry_sum; Y=MSELOSS
的函数图像。
每层6个神经元的训练LOSS:
训练条目数量 | Train_MSE | Validate_MSE |
---|---|---|
100 | 0.45 | 2.71 |
200 | 0.77 | 1.21 |
300 | 0.79 | 1.11 |
400 | 0.94 | 1.14 |
500 | 1.00 | 1.21 |
600 | 0.94 | 1.05 |
结果:
由图可见,已经比较接近我们的目标MSE了,说明调整后的网络结构:
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 numpy as np
import matplotlib.pyplot as plt
x=np.array([100,200,300,400,500,600])
y=np.array([0.45,0.77,0.79,0.94,1.00,0.94])
y1=np.array([2.71,1.21,1.11,1.14,1.21,1.05])
y2=np.array([0.95,0.95,0.95,0.95,0.95,0.95])
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
ax.plot(x,y,'m.-.',label='trainMSE',color='blue')
ax.plot(x,y1,'m.-.',label='ValidateMSE',color='green')
ax.plot(x,y2,'c*-',label='StandardMSE',color='red')
ax.legend()
plt.show()