动手学深度学习10 模型选择+过拟合和欠拟合
1. 模型选择
电子书:https://zh-v2.d2l.ai/chapter_multilayer-perceptrons/underfit-overfit.html
视频:https://www.bilibili.com/video/BV1kX4y1g7jp?p=1&vd_source=eb04c9a33e87ceba9c9a2e5f09752ef8
课件:https://courses.d2l.ai/zh-v2/assets/pdfs/part-0_14.pdf
2. 过拟合和欠拟合
目的:降低泛化误差 + 降低泛化误差和训练误差的gap
为了能让泛化误差往下降,不得不承受一定程度的过拟合,能够过拟合也是一种好事。
深度学习核心:首先模型要足够大+再用别的方法控制模型容量
该图X轴是模型容量,是很多个模型,不是一个模型。
输出层有k个预测的y_hat,每个y_hat包含m个w参数(由上一层的节点数决定)和一个bias,所以是(m+1)k
林轩田讲VC维–《机器学习基石》
3.代码
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
# 模拟创建数据集
max_degree = 20 # 多项式的最大阶数 特征的维度?
n_train, n_test = 100, 100 # 训练集和测试数据集的大小
true_w = np.zeros(max_degree) # 分配大量的空间
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6]) # 真实权重只给前4个特征赋值,后面16个特征仍旧是0,作为噪声
# np.random.normal:表示使用 NumPy 中的随机模块 random 中的正态分布函数 normal
features = np.random.normal(size=(n_train+n_test, 1)) # 生成服从正态分布的随机数数组 均值0标准差1的随机数
np.random.shuffle(features) # 用于对数组或列表进行随机重排(打乱顺序)。具体来说,它会将数组或列表中的元素按随机顺序重新排列,从而达到打乱数据集或样本顺序的效果。
# np.power() 是 NumPy 库中的一个函数,用于计算数组中元素的指数幂。具体来说,np.power(x, y) 将数组 x 中的每个元素按照指数 y 进行幂运算。
# 这个函数的功能是计算数组元素的指数幂,即x^y。其中,x 是底数数组,y 是指数数组。
# 如果 y 是一个标量,则对x中的每个元素进行相同的指数幂运算;
# 如果 y 是一个数组,则要求 x 和 y 的形状相同,对应位置的元素进行指数幂运算。
# x,y 可以是标量、数组或者其他可转换为数组的对象
# NumPy 库中的一个函数,用于创建一个等差数列的一维数组。
# 具体来说,np.arange(start, stop, step) 会生成一个从起始值 start 开始,到停止值 stop 之前(不包括 stop),步长为 step 的等差数列
# start:数列的起始值(包含在数列中)。
# stop:数列的结束值(不包含在数列中)。
# step:数列的步长,即相邻两个数之间的差值。
# 如果只提供一个参数,则默认起始值为 0,步长为 1;如果提供两个参数,则默认步长为1。
# np.arange() 函数生成的数列是左闭右开区间,即包含起始值而不包含结束值。
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
print(features.shape, np.arange(max_degree).reshape(1, -1).shape) # (200, 1) (1, 20)
print(poly_features.shape) # (200, 20)
print(poly_features[1, :])
for i in range(max_degree):
# [:, i] 表示选取矩阵的所有行,但只选取第 i 列。
poly_features[:, i] /= math.gamma(i+1) # gamma(n)=(n-1)!
# labels的维度:(n_train+n_test,)
# np.dot() 是 NumPy 库中用于计算两个数组的点积(内积)的函数。点积是两个向量相乘并求和的结果
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)
# Numpy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=torch.float32) for x in [true_w, features, poly_features, labels]]
print(features[:2], poly_features[:2, :], labels[:2])
# 模型训练和测试
def train_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第三章)"""
# 如果模型是用nn模组定义的
if isinstance(net, torch.nn.Module):
net.train() # 将模型设置为训练模式 告诉pytorch要计算梯度
# 训练损失总和、训练准确度总和、样本数
metric = d2l.Accumulator(3) # 三个参数需要累加的迭代器
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y) # 计算损失函数
# 如果优化器是pytorch内置的优化器
# 下面两个加的结果有什么区别
# print(float(l)*len(y), accuracy(y_hat,y), y.size().numel(),
# float(l.sum()), accuracy(y_hat, y), y.numel())
if isinstance(updater, torch.optim.Optimizer):
# 使用pytorch内置的优化器和损失函数
updater.zero_grad() # 1.梯度先置零
l.mean().backward() # 2.计算梯度
updater.step() # 3.调用step参数进行一次更新
# metric.add(float(l)*len(y), accuracy(y_hat,y), y.size().numel())
# 报错 ValueError: only one element tensors can be converted to Python scalars
else:
# 使用定制的优化器和损失函数
# 自己实现的l是一个向量
l.sum().backward()
updater(X.shape[0])
# metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
metric.add(float(l.sum()), d2l.accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
# 损失累加/总样本数 训练正确的/总样本数
return metric[0] / metric[2], metric[1] / metric[2]
# 损失函数
def evaluate_loss(net, data_iter, loss):
"""评估给定数据集上模型的损失"""
metric = d2l.Accumulator(2) # 损失函数的总和,样本数量
for X, y in data_iter:
out = net(X)
l = loss(out, y)
metric.add(l.sum(), l.numel())
return metric[0]/metric[1]
def train(train_features, test_features, train_labels, test_labels, num_epochs=400):
loss = nn.MSELoss(reduction="none")
input_shape = train_features.shape[-1]
# 不设置偏置值 因为已经在多项式中实现了它
net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
batch_size = min(10, train_labels.shape[0])
train_iter = d2l.load_array((train_features, train_labels.reshape(-1,1)), batch_size)
test_iter = d2l.load_array((test_features, test_labels.reshape(-1,1)), batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=0.01)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log', xlim=[1, num_epochs], ylim=[1e-3,1e2], legend=['trian', 'test'])
for epoch in range(num_epochs):
train_epoch_ch3(net, train_iter, loss, trainer)
if epoch == 0 or (epoch+1)%20 == 0:
animator.add(epoch+1, (evaluate_loss(net, train_iter, loss), evaluate_loss(net, test_iter, loss)))
print('weight', net[0].weight.data.numpy())
# 正常 从多项式特征中选择前4个维度,即1,x,x^2,x^3/3! # 真实的人工定义的权重就是4个值
print("normal")
train(poly_features[:n_train, :4], poly_features[n_train:, :4], labels[:n_train], labels[n_train:])
print("overfitting")
# 过拟合 从多项式特征中选择所有维度 -- 全部20个维度,即1,x,x^2,x^3***/20!
train(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:])
print('underfitting')
# 欠拟合 从多项式特征中选择前2个维度,即1,x/3!
train(poly_features[:n_train, :2], poly_features[n_train:, :2], labels[:n_train], labels[n_train:])
上面3图X轴是epoch,是同一个模型的不同epoch。epoch也是超参数。
4. QA
-
svm缺点:通过kernel匹配模型复杂度,计算困难,很难做到100w的数据量。神经网络可以做大数据量的计算。第二:svm能调的东西不多,可调性不好。
-
神经网络优点:神经网络本身是一种语言,一个个的小语句,不同layer是一个个不同的小工具,一句句的连起来,通过神经网络语言编程,来描述整个物体或者整个世界或要解决问题的理解。比较不太直观,但是可编程性好的框架。可以做到很大的数据集,卷积可以很好的做特征的提取。理论上单层隐藏层MLP能拟合所有的数据集,实际上训练不出来。所以要有一个比较好的结构,尽量帮助拟合。例如cnn本身就是MLP,只是对一些weight固定住了,告诉神经网络我觉得数据有空间信息,要这样去处理空间信息。RNN告诉有时序信息,要这样去训练。用神经网络来描述对问题的理解,帮助训练。试一下模型结构看看训练效果。【艺术】
-
模型剪枝、蒸馏可以把模型size变小,但是很少影响模型效果。从原始大模型变成小模型,肯定是比原始就训练一个小模型效果要好一些。
-
测试数据集跟验证数据集的分布很可能不一样。假设数据都是独立同分布的。数据集划分常用:30%测试集,70%训练集【其中20%做5折交叉验证】。
-
对于时间序列的数据,训练集和验证集自相关,那么测试集和验证集需要在训练集的时间之后。
-
验证集和训练集的数据清洗-异常处理、特征构建-标准化,是否要一起处理?
可以放在一起计算均值和方差,实际生产环境可能可以,对整体的数据分布更鲁棒一些;也可以分开计算。看实际应用,是否能拿到验证数据集的数据,拿不到只能用训练数据集计算。 -
实际生成,深度学习训练集很大,不需要做K折交叉验证,训练成本比较贵。
-
cross validation只是选择超参数,不能解决别的问题。
-
k折的k怎么确定:最重要一点要在自己能承受的计算成本里面。K越大效果越好,但是训练成本越高。K折交叉验证第一次氛围数据后,就确定分组了。也可以随机打乱做begging,这样是获取k个模型做平均预测。
-
模型参数是指训练的w b等模型训练中计算解决的参数;超参数是模型参数以外的我们能调整、可以选的参数都称为超参数,lr,线性模型还是多层感知机,模型有多少层,每层多大。
-
cross validation每块训练时获得的最终模型参数可能是不同的,每一块报告的也是平均精度,最后可以每一块都计算取平均。
统计意义:【大数定理】 -
偏差和方差的区别?
-
overfitting和underfitting现象可以告诉自己那个参数的效果比较好或坏,在范围内去调参。
-
如何有效设计超参数【优化 optuna】
网格搜索,所有遍历一次。贝叶斯方法计算次数多。【一百一千一万次效果才会好一点】
超参数设计:一靠自己的经验;二靠自己调参,判断测试结果;三靠随机,随机参数训练从中选一个效果比较好的。
Optuna 是一个用于超参数优化的自动化调参库,它专门用于优化机器学习模型的超参数,以提高模型的性能和效果。Optuna 提供了一种高效的方法来搜索超参数空间,以找到最佳的超参数组合,从而优化模型的性能并提高模型在测试数据上的表现。在深度学习中,模型的性能往往受到超参数的影响。例如,学习率、批量大小、层数、隐藏单元数量等超参数都会直接影响模型的训练速度、收敛性和泛化能力。通过使用 Optuna 进行超参数优化,可以更快速地找到最佳的超参数组合,从而提高深度学习模型的性能和效果。
总而言之,Optuna 在深度学习中表示一种用于自动化超参数优化的工具,能够帮助用户更高效地优化模型的性能,并提升模型的泛化能力和表现。
-
样本不平衡,二分类数据比例1:9,数据集不很大的话,最好验证集上两类数据分布差不多,避免模型偏好数据占比较大的那一类,也可加权重避免。
-
k折交叉验证目的就是为了确定超参数,一种最常见做法再用这个超参数训练一次模型;第二不再训练模型,找到K折里精度最好【或随便】的一折,选择该参数模型,代价就是模型少看了一些训练集;第三种把K个模型全部拿下来,所有模型都预测一次然后求均值,代价是计算成本高,但是增加了模型稳定性。
-
svm简单,不用咋调参,有数学理论,有人推,就是多层感知机要流行起来。深度学习虽没有理论,但是实际效果好,就更流行了。发展的问题。深度学习并没有改变机器学习核心的东西:要搞数据,避免overfitting,要看误差。
-
模型容量是指模型能够拟合函数的能力
-
深度学习一般特指神经网络这一块,有把随机森林做到神经网络里面的,但是问题是随机森林不是通过梯度下降计算的。一般是训练多个模型然后做投票。
-
K折训练k个模型,把K个模型全部拿下来融合,所有模型都预测一次然后求均值;训练模型初始化随机值,那就随机初始化n次训练n个模型,测试时再输出n个模型的平均值。
-
标号、标注、label。
-
深度学习模型不做正则化、不做限制可能输出的是无限VC维的算法。
-
vc维,是模型能记住多大的【多复杂的】数据集长什么样子。
-
同样模型结构+训练数据,只是随机初始化不同,最后集成都一定会好?
模型是统计模型,优化是数值优化。【统计学、优化】最后的模型=统计学模型【模型定义】+怎么做的优化的结果。统计学模型一样的基础上,随机初始化数值不一样,最后结果不一样。每个模型都有一定的偏移,但是方差【噪音】每次优化,做n个模型取均值降低方差,可能效果比较好。 -
实际训练数据是噪音越少越好,人工假造的数据可以稍微加一点噪声。
-
训练数据集是不平衡的,如果真实世界就是不平衡的,那可以做好主流的就可以;如果只是因为采样导致数据比较少,可以通过加权让数据集变平衡【最简单数据多复制几遍让训练样本均衡;也可以loss加权,小类别加大权重】。
-
验证集loss先下降再上升,说明是过拟合了。
练习题 https://blog.csdn.net/weixin_45496725/article/details/136445995