1.26.过拟合、欠拟合及其解决方案
1.26.1.过拟合、欠拟合的概念
1.26.1.1.训练误差和泛化误差
1.26.1.2.验证数据集与K-fold验证
1.26.1.3.过拟合和欠拟合
1.26.1.4.导致过拟合和欠拟合的关键因素
1.26.2.L2正则化
1.26.3.drop out
1.27.Pytorch过拟合&欠拟合
1.27.1.理想化情况
1.27.1.1.场景一:线性模型—房价预测
1.27.1.2.场景二:非线性模型—GPA
1.27.2.真实情况
1.27.3.模型学习能力
1.27.4.欠拟合与过拟合
1.28.Train-Val-Test划分
1.28.1.1.How to detect(如何检测)
1.28.1.2.Splitting(划分训练集和测试集)
1.28.1.3.for example
1.28.1.4.test while train
1.28.1.5.train test trade-off
1.28.1.6.for others judge
1.28.1.7.train-val-test
1.28.1.8.K-fold cross-validation
1.28.1.9.k-fold cross validation
1.26.过拟合、欠拟合及其解决方案
1.26.1.过拟合、欠拟合的概念
1.26.1.1.训练误差和泛化误差
前者指模型在训练数据集上表现出的误差,后者模型在任意一个测试数据样本上表现的误差的期望,并常常通过测试数据集上的误差来近似。
1.26.1.2.验证数据集与K-fold验证
预留一部分在训练数据集和测试数据集以外的数据来进行模型选择,这部分数据被称为验证数据集,简称验证集(validation set)。在K折交叉验证中,把原始训练数据集分割成K个不重合的子数据集,然后做K次模型训练和验证。每一次,使用一个子数据集验证模型,并使用其他K-1个子数据集来训练模型。在这K次训练和验证中,每次用来验证模型的子数据集都不同。最后,对这K次训练误差和验证误差分别求平均。
1.26.1.3.过拟合和欠拟合
欠拟合:模型无法得到较低的训练误差。
过拟合:模型的训练误差远小于它在测试数据集上的误差。
1.26.1.4.导致过拟合和欠拟合的关键因素
数据集大小:影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。一般来说,如果训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。此外,泛化误差不会随训练数据集里样本数量增加而减小。因此,在计算资源允许的范围之内,通常希望训练数据集大一些,特别是在模型复杂度较高时,例如层数较多的深度学习模型。
模型复杂程度:
1.26.2.L2正则化
1.26.3.drop out
1.27.Pytorch过拟合&欠拟合
1.27.1.理想化情况
1.27.1.1.场景一:线性模型—房价预测
横坐标是房屋面积,纵坐标是房屋价格。可以看出这两者呈现线性关系。
1.27.1.2.场景二:非线性模型—GPA
老师给学生打分时,往往大部分学生一般的成绩,成绩差或成绩好的则占少数。因此总体上趋近于高斯模型,呈现非线性关系。
1.27.2.真实情况
从以上两个例子中,我们都已经提前知道了问题中的具体模型,不知道的只有模型的参数,如期望,方差。然而在现实生活中,大部分要解决的问题是不知道具体模型的。如手写数字识别,我们既不知道它的模型,也不知道里面需要包含什么参数。那么如何解决这个问题呢?
另外我们还要考虑另一个因素的影响:噪声。并不是所有的数据集都是严格按照模型的。如房价关系的线性模型,房价与面积在呈线性关系的同时往往有些许误差。我们可以认为误差服从高斯分布,因此为了尽量减少误差的影响,我们需要大量的数据集来训练模型。同时,如果数据集太少,可能被误认为不满足线性关系,而采用其他关系进行预测。
1.27.3.模型学习能力
我们对利用数据训练模型时,往往会选择不同的模型,可能一次方,二次方。。。到n次方,不同类型,不同次方的模型,次方越高,模型波动越大越复杂。
那么如何衡量不同模型的学习能力?
可以看出,只有一个参数的常数函数,学习能力最差,输出的是固定值,随着次方增加,参数越来越多,学习能力越来越强。
最近的深度学习网络层次越来越深,参数也越来越大,也从侧面反映出模型的model capacity越来越强。
1.27.4.欠拟合与过拟合
接下来我们观察两种情况:
情况一:欠拟合
我们用的模型复杂度小于真实数据模型的复杂度,就好像利用线性模型去预测非线性模型的GPA函数。这种情况会造成模型的表达能力不够,无法表达真实数据模型,这种情况叫做欠拟合。
再举一个WGAN的例子,早期的WGAN对模型加了许多约束,导致生成的数据表达能力不够强,上面三幅图无法像下面三幅图一样表达许多精美的图片。
那么欠拟合如何体现出来,我们如何发现模型出现欠拟合?
训练集准确度低
测试集准确度低
情景二:过拟合
我们训练出的模型为了拟合所有数据变得过分复杂,为了降低loss,模型趋近于每一个点,训练模型复杂度大于真实数据集复杂度,这样会导致在训练集上的效果非常好,但是在测试集上的效果不好,这种情况叫做过拟合。
那么过拟合如何体现出来,我们如何发现模型出现过拟合?
上面的图分别表示:欠拟合,适配良好,过拟合
1.28.Train-Val-Test划分
1.28.1.1.How to detect(如何检测)
1.28.1.2.Splitting(划分训练集和测试集)
合理的 Train/Test 集划分会有效地减少 under-fitting 和 over-fitting 现象
以数字识别为例,正常一个数据集我们要划分出来训练部分和测试部分,如下图所示:
左侧橘色部分作为训练部分,神经网路在该区域内不停地学习,将特征转入到函数中,学习好后得到一个函数模型。随后将上图右面白色区域的测试部分导入到该模型中,进行accuracy和loss的验证
通过不断地测试,查看模型是否调整到了一个最佳的参数,及结果是否发生了over-fitting现象。
1.28.1.3.for example
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
batch_size=200
learning_rate=0.01
epochs=10
# 使用 train=True 或 train=False来进行数据集的划分
# train=True时为训练集,相反即为测试集
train_db = datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
# 训练-测试代码写法
# 一般使用DataLoader函数来让机器学习或测试
train_loader = torch.utils.data.DataLoader(
train_db,
batch_size=batch_size, shuffle=True)
test_db = datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
test_loader = torch.utils.data.DataLoader(test_db,
batch_size=batch_size, shuffle=True)
1.28.1.4.test while train
1.28.1.5.train test trade-off
1.28.1.6.for others judge
其实正常情况下除了Train Set和挑选最佳参数的Test Set外,一般还要有Validation Set。
Val Set代替Test Set的功能,而Test Set则要交给客户,进行实际验证,正常情况下Test Set是不加入到测试中的
说个很具体的场景,就比方说 Kaggle 竞赛中,比赛的主办方给你训练的数据集,一般我们拿来都会自己分成 Train Set 和 Val Set 两部分,用 Train Set 训练,Val Set 挑选最佳参数,训练好以后提交模型,此时主办方会用它自己的数据集,即 Test Set 去测试你的模型,得到一个 Score。
从上面的过程能看出,Val Set可以理解为是从Train Set找拆出来的一部分,而与Test Set没有关系
# 这里注意,正常情况下数据集是要有validation(验证)集的,若没有设置,即将test和val集合并为一个了
# 这里注意,正常情况下数据集是要有validation(验证)集的,若没有设置,即将test和val集合并为一个了
print('train:', len(train_db), 'test:', len(test_db))
# 首先先查看train和test数据集的数量,看看是否满足预订的分配目标
# 随机分配法将数据分为50k和10k的数量
train_db, val_db = torch.utils.data.random_split(train_db, [50000, 10000])
print('db1:', len(train_db), 'db2:', len(val_db))
train_loader = torch.utils.data.DataLoader(
train_db,
batch_size=batch_size, shuffle=True)
val_loader = torch.utils.data.DataLoader(
val_db,
batch_size=batch_size, shuffle=True)
1.28.1.7.train-val-test
1.28.1.8.K-fold cross-validation
1.28.1.9.k-fold cross validation
hold-out(留出法)
直接将数据集划分为两个互斥的集合
但是这种方法,只有新划分的训练集会参与到backward过程中
K-fold cross-validation(K拆交叉验证)
即把数据分成K份,每次拿出一份作为验证集,剩下k-1份作为训练集,重新K次。最后平均K次的结果,作为误差评估的结果。
这种方法可以将验证集充分利用起来,从而使得每个数据集都可以参与backward过程中。
不过这种方法对模型的提升有限,毕竟数据集并没有增加。
1.30.Regularization
为了解决torch.optim优化器只能实现L2正则化以及惩罚网络中的所有参数的缺陷,这里实现类似于TensorFlow正则化的方法。
1.30.1.自定义正则化Regularization类
这里封装成一个实现正则化的Regularization类,各个方法都给出了注释。
# -*- coding: UTF-8 -*-
import torch
# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# device='cuda'
print("--------device:{}".format(device))
print("--------Pytorch version:{}".format(torch.__version__))
class Regularization(torch.nn.Module):
def __init__(self, model, weight_decay, p=2):
'''
:param model 模型
:param weight_decay: 正则化参数
:param p: 范数计算中的幂指数值,默认求2范数,当p=0为L2正则化,p=1为L1正则化
'''
super(Regularization, self).__init__()
if weight_decay <= 0:
print("param weight_decay can not <=0")
exit(0)
self.model = model
self.weight_decay = weight_decay
self.p = p
self.weight_list = self.get_weight(model)
self.weight_info(self.weight_list)
def to(self, device):
'''
指定运行模式
:param device: cuda or cpu
:return:
'''
self.device = device
super().to(device)
return self
def get_weight(self, model):
'''
获得模型的权重列表
:param model:
:return:
'''
weight_list = []
for name, param in model.named_parameters():
if 'weight' in name:
weight = (name, param)
weight_list.append(weight)
return weight_list
def regularization_loss(self, weight_list, weight_decay, p=2):
'''
计算张量范数
:param weight_list:
:param weight_decay:
:param p: 范数计算中的幂指数值,默认求2范数
:return:
'''
reg_loss = 0
for name, w in weight_list:
l2_reg = torch.norm(w, p=p)
reg_loss = reg_loss + l2_reg
reg_loss = weight_decay * reg_loss
return reg_loss
def weight_info(self, weight_list):
'''
打印权重列表信息
:param weight_list:
:return:
'''
print("-------regularization weight--------")
for name, w in weight_list:
print(name)
print("------------------------------------")
def forward(self, model):
# 获得最新的权重
self.weight_list = self.get_weight(model)
reg_loss = self.regularization_loss(self.weight_list, self.weight_decay, p=self.p)
return reg_loss
1.30.2.Regularization使用方法
使用方法很简单,就当一个普通Pytorch模块来使用,例如:
# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("------device:{}".format(device))
print("------Pytorch version:{}".format(torch.__version__))
weight_decay = 100.0 #正则化参数
learning_rate=0.01
model = Regularization().to(device)
# 初始化正则化
if weight_decay > 0:
reg_loss = Regularization(model, weight_decay, p=2).to(device)
else:
print("no regularization")
# CrossEntropyLoss=softmax+cross entropy
criterion = nn.CrossEntropyLoss().to(device)
# 不需要指定参数weight_decay
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# train
batch_train_data = ...
batch_train_label = ...
out = model(batch_train_data)
# loss and regularization
loss = criterion(input=out, target=batch_train_label)
if weight_decay > 0:
loss = loss + reg_loss(model)
total_loss = loss.item()
# backprop
# 清除当前所有的累积梯度
optimizer.zero_grad()
total_loss.backward()
optimizer.step()
训练时输出的 loss和Accuracy信息:
(1)当weight_decay=0.0时,未使用正则化
(2)当weight_decay=10.0时,使用正则化
(3)当weight_decay=10000.0时,使用正则化
对比torch.optim优化器的实现L2正则化方法,这种Regularization类的方法也同样达到正则化的效果,并且与TensorFlow类似,loss把正则化的损失也计算了。
此外更改参数p,如当p=0表示L2正则化,p=1表示L1正则化。