深度学习4月22笔记
1、过拟合与欠拟合
在训练深层神经网络时,由于模型参数较多,在数据量不足时很容易过拟合。而正则化技术主要就是用于防止过拟合,提升模型的泛化能力(对新数据表现良好)和鲁棒性(对异常数据表现良好)。
1. 概念认知
这里我们简单的回顾下过拟合和欠拟合的基本概念~
1.1 过拟合
过拟合是指模型对训练数据拟合能力很强并表现很好,但在测试数据上表现较差。
过拟合常见原因有:
-
数据量不足:当训练数据较少时,模型可能会过度学习数据中的噪声和细节。
-
模型太复杂:如果模型很复杂,也会过度学习训练数据中的细节和噪声。
-
正则化强度不足:如果正则化强度不足,可能会导致模型过度学习训练数据中的细节和噪声
1.2 欠拟合
欠拟合是由于模型学习能力不足,无法充分捕捉数据中的复杂关系。
1.3 如何判断
那如何判断一个错误的结果是过拟合还是欠拟合呢?
过拟合
训练误差低,但验证时误差高。模型在训练数据上表现很好,但在验证数据上表现不佳,说明模型可能过度拟合了训练数据中的噪声或特定模式。
欠拟合
训练误差和测试误差都高。模型在训练数据和测试数据上的表现都不好,说明模型可能太简单,无法捕捉到数据中的复杂模式。
2. 解决欠拟合
欠拟合的解决思路比较直接:
-
增加模型复杂度:引入更多的参数、增加神经网络的层数或节点数量,使模型能够捕捉到数据中的复杂模式。
-
增加特征:通过特征工程添加更多有意义的特征,使模型能够更好地理解数据。
-
减少正则化强度:适当减小 L1、L2 正则化强度,允许模型有更多自由度来拟合数据。
-
训练更长时间:如果是因为训练不足导致的欠拟合,可以增加训练的轮数或时间.
避免模型参数过大是防止过拟合的关键步骤之一。
模型的复杂度主要由权重w决定,而不是偏置b。偏置只是对模型输出的平移,不会导致模型过度拟合数据。
怎么控制权重w,使w在比较小的范围内?
考虑损失函数,损失函数的目的是使预测值与真实值无限接近,如果在原来的损失函数上添加一个非0的变量
其中f(w)是关于权重w的函数,f(w)>0
要使L1变小,就要使L变小的同时,也要使f(w)变小。从而控制权重w在较小的范围内。
2正则化
L2 正则化通过在损失函数中添加权重参数的平方和来实现,目标是惩罚过大的参数值
设损失函数为 L(\theta),其中 \theta 表示权重参数,加入L2正则化后的损失函数表示为:
其中:
-
是原始损失函数(比如均方误差、交叉熵等)。
-
是正则化强度,控制正则化的力度。
-
是模型的第 i 个权重参数。
-
是所有权重参数的平方和,称为 L2 正则化项。
L2 正则化会惩罚权重参数过大的情况,通过参数平方值对损失函数进行约束。
为什么是?
假设没有1/2,则对L2 正则化项的梯度为:
,会引入一个额外的系数 2,使梯度计算和更新公式变得复杂。
添加1/2后,对的梯度为:
梯度更新
在 L2 正则化下,梯度更新时,不仅要考虑原始损失函数的梯度,还要考虑正则化项的影响。更新规则为:
其中:
-
是学习率。
-
是损失函数关于参数
的梯度。
-
是 L2 正则化项的梯度,对应的是参数值本身的衰减。
很明显,参数越大惩罚力度就越大,从而让参数逐渐趋向于较小值,避免出现过大的参数。
权重就会趋向于较小的值
早停
早停是一种在训练过程中监控模型在验证集上的表现,并在验证误差不再改善时停止训练的技术。这样可避免训练过度,防止模型过拟合。pytorch没有现成的API,需要自己写代码实现。
早停法的实现步骤
-
将数据集分为训练集和验证集。
-
在训练过程中,定期(例如每个 epoch)在验证集上评估模型的性能(如损失或准确率)。
-
记录验证集的最佳性能(如最低损失或最高准确率)。
-
如果验证集的性能在连续若干次评估中没有改善(即达到预设的“耐心值”),则停止训练。
-
返回验证集性能最佳时的模型参数。
早停法的关键参数
-
耐心值(Patience):
-
允许验证集性能不提升的连续次数。
-
例如,如果耐心值为 5,则当验证集损失连续 5 次没有下降时,停止训练。
-
-
最小改善值(Min Delta):
-
定义“性能提升”的最小阈值。
-
例如,如果验证集损失的变化小于该值,则认为性能没有提升。
-
-
恢复最佳权重(Restore Best Weights):
-
是否在早停时恢复验证集性能最佳时的模型权重。
-
import torch
import torch.nn as nn
from torch import optim
from torch.utils.data import Dataset, DataLoader, TensorDataset
from sklearn.model_selection import train_test_split
import numpy as np# 1.定义一个简单的神经网络
class simple_net(nn.Module):def __init__(self):super(simple_net, self).__init__()self.fc1 = nn.Linear(20, 10)self.fc2 = nn.Linear(10, 1)self.relu = nn.ReLU()def forward(self, x):x = self.relu(self.fc1(x))x = self.fc2(x)return x# 早停法类
class Early_Stopping():def __init__(self, patience=10, min_delta=0.0):self.patience = patienceself.min_delta = min_deltaself.counter = 0self.best_loss = Noneself.early_stop = Falsedef __call__(self, val_loss):# 如果best_loss 为空,,把损失赋值给最佳if self.best_loss is None:self.best_loss = val_loss# 新损失减少大于最少损失,耐心加一elif val_loss > self.best_loss - self.min_delta:self.counter += 1# 耐心大于等于设定的阈值,把flag置为trueif self.counter >= self.patience:self.early_stop = True# 其他情况,更新最佳损失,计数器置0else:self.best_loss = val_lossself.counter = 0# 生成随机数据
np.random.seed(0)
torch.manual_seed(0)x = torch.randn(1000, 20)
y = torch.randn(1000, 1)x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)
# 转换为张量# 创建Dataloader
train_set = TensorDataset(x_train, y_train)
train_loader = DataLoader(train_set, batch_size=32, shuffle=True)
test_set = TensorDataset(x_test, y_test)
test_loader = DataLoader(test_set, batch_size=32, shuffle=True)# 初始化模型、损失函数和优化器
model = simple_net()
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 初始化早停
early_stopping = Early_Stopping(patience=10, min_delta=0.001)# 循环次数epochs
epochs = 100
train_losses = []
test_losses = []
for epoch in range(epochs):model.train()epoch_loss = 0for x_batch, y_batch in train_loader:y_pred = model(x_batch)loss = criterion(y_pred, y_batch)optimizer.zero_grad()loss.backward()optimizer.step()epoch_loss += loss.item()train_losses.append(epoch_loss/len(train_loader))# 验证阶段model.eval()epoch_test_loss = 0for x_batch, y_batch in test_loader:y_pred = model(x_batch)loss = criterion(y_pred, y_batch)epoch_test_loss += loss.item()test_losses.append(epoch_test_loss/len(test_loader))if epoch % 5 == 0:print(f'Epoch {epoch}: train_Loss {train_losses[-1]:.4f} test_Loss {test_losses[-1]:.4f}')# 早停法检测early_stopping(test_losses[-1])if early_stopping.early_stop:print('Early stopping')breakprint('Training complete')