动手学习Pytorch:过拟合欠拟合篇

基本概念

训练误差:模型在训练数据集上计算得到的误差

泛化误差:模型在原始分布中抽取的无限多的样本上的误差

生成数据集

根据这个函数来生成数据集

生成数据集的顺序如下:

生成原始数据x->计算x的多个次方->除以(n-1)!->跟系数点乘得到最后的结果

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])

features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
for i in range(max_degree):
    poly_features[:, i] /= math.gamma(i + 1)  # gamma(n)=(n-1)!
# labels的维度:(n_train+n_test,)

labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)

#转换成tensor类型的数据
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
    torch.float32) for x in [true_w, features, poly_features, labels]]

#展示一下前两个数据
features[:2], poly_features[:2, :], labels[:2]

训练与测试

def evaluate_loss(net,data_iter,loss):
    metric = d2l.Accumulator(2)
    for X,y in data_iter:
        out = net(X)
        y = y.reshape(out.shape)
        l = loss(out,y)
        metric.add(l.sum(),y.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, is_train=False)

    #设置优化器与绘图工具
    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=['train', 'test'])

    #开始训练
    for epoch in range(num_epochs):
        d2l.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/2!,x^3/3!
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
      labels[:n_train], labels[n_train:])

可以发现结果跟真实结果相近

而当只选取前两个维度的时候

# 从多项式特征中选择前2个维度,即1和x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
      labels[:n_train], labels[n_train:])

可以发现loss值始终降不下去,训练跟测试的损失都偏高

而当选取更高维度的线性层对它进行拟合时

# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=1500)

会发现虽然在训练集上取得很低的loss,但是对于测试的时候还是有很高的损失,说明过度拟合了

解决方法

对于过拟合问题,

在线性回归中,如果模型参数量过大,但是样本不够,可能会出现

在神经网络中,模型更看重与不同特征之间的关联,可能会过度强化关联,导致过拟合

究其根本,

一类过拟合是由于样本不足,但参数过大,导致模型受到噪声干扰严重

一类是过度挖掘特征之间的关联,强制赋予关联

对于前者,我们使用正则化技术,通过权重衰退来缓解噪声干扰

对于后者,使用dropout技术,在训练过程中随机撤销某些参数,增强模型的鲁棒性

权重衰减

解决过拟合的思路:由于模型的参数过多,而样本数据比较小,模型就可能会一些微小扰动非常敏感,这导致了模型的过拟合,所以为了解决这个问题,就是让模型对某些特征上的数据不那么敏感,就可以采用缩小权重的方法来解决,这就称为权重衰减,有助于模型学习到更一般的特征,减少噪声的干扰

在损失函数中再加上对权重的惩罚项之和,这样如果模型过于复杂,就会控制权重,让模型有更好的泛化能力

在原先我们介绍过L1跟L2范数,都是对权重的一种描述类型

在这里我们选取的是L2范数,二者区别如下:

我们的目标是想要模型的权重在大量特征上均匀分布,这样会在针对单一变量中的观测误差更小

而L1范数会让某一部分的特征被突出,不符合当前场景,更适合特征选择

接下来针对下面这个多项式进行代码实现

#预设真实参数,并生成数据集
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)

#初始化参数
def init_params():
    w = torch.normal(0,1,size=(num_inputs,1),requires_grad=1)
    b = torch.zeros(1,requires_grad=1)
    return [w,b]

#定义惩罚项,L2范数
def L2_penalty(w):
    return torch.sum(w.power(2))/2

def train(lambd):
    #初始化参数
    w,b = init_params()
    #定义模型与损失,lambda X:定义了一个匿名函数,输入是X
    net , loss =  lambda X: d2l.linreg(X,w,b), d2l.square_loss

    #初始化训练轮次与学习率,并制定绘图方式
    num_epochs,lr = 100,0.03
    animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                            xlim=[5, num_epochs], legend=['train', 'test'])

    for epoch in range(num_epochs):
        for X,y in train_iter:
            #定义带有惩罚项的损失
            l = loss(net(X),y) + lambd * L2_penalty(w)
            l.sum().backward()
            d2l.sgd([w,b],lr,batch_size)

        #每个五个绘制标点
        if (epoch + 1) % 5 == 0:
            animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
                                     d2l.evaluate_loss(net, test_iter, loss)))
    print('w的L2范数是:', torch.norm(w).item())

首先先对不使用惩罚项的损失函数进行训练

可以发现,虽然训练损失下降了,但是测试损失并没有改变,所以模型很可能出现了过拟合的现象

接着引入惩罚项,可以发现虽然训练损失没有原来低,但是测试损失明显下降,正则化达到效果

接着结合Pytorch库里面对上面的代码进行简洁实现

深度学习框架将权重衰退添加到优化器中,这样方便跟任何损失函数结合使用

def train_concise(wd):
    #定义网络
    net = nn.Sequential(nn.Linear(num_inputs,1))
    #初始化参数
    for param in net.parameters():
        param.data.normal_()
    #定义损失
    loss = nn.MSEloss(reduction = 'None')
    num_epochs,lr = 100,0.03
    #定义优化器
    trainer = torch.optim.SGD([
        {"params":net[0].weight,'weight_decay':wd},
        {"params":net[0].bias}]
        lr = lr
    )
    #绘图工具
    animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                             xlim=[5, num_epochs], legend=['train', 'test'])
    #训练
    for epoch in range(num_epochs):
        for X,y in train_iter:
            trainer.zero_grad()
            l = loss(net(X),y)
            l.mean().backward()
            trainer.step()
            if (epoch + 1) % 5 == 0:
                animator.add(epoch + 1,
                         (d2l.evaluate_loss(net, train_iter, loss),
                          d2l.evaluate_loss(net, test_iter, loss)))
    print('w的L2范数:',net[0].weight.norm().item())

同样达到先前的效果

不过在这里并没有讲的很详细,权重衰减是如何配合损失函数一起使用的

在优化器中实现权重衰减而不是直接修改损失函数有以下优点:

  1. 效率高:在优化器中直接对梯度进行调整,无需显式计算正则化项。
  2. 灵活性:可以对不同参数(如权重和偏置)设置不同的正则化规则。例如,上述代码中,权重 net[0].weight 有正则化,而偏置 net[0].bias 没有。

暂退法 dropout

使用dropout的目的:

(1) 减少过拟合
  • 问题: 神经网络往往拥有大量参数,在训练时可能过度拟合训练数据,学习到噪声或特定数据模式,导致泛化能力下降。
  • Dropout 的作用
    • 通过随机丢弃部分神经元,迫使网络学习冗余和通用的特征,而非过度依赖某些神经元。
    • 提高模型对噪声的鲁棒性。

(2) 增强网络的鲁棒性
  • 问题: 如果网络的某些神经元对输出结果影响过大,模型可能会对小的输入变化非常敏感。
  • Dropout 的作用
    • 模拟多个不同的网络架构(类似于模型集成),使模型更加鲁棒。
    • 防止模型对某些特定神经元的过度依赖。

(3) 减少参数之间的相互依赖
  • 问题: 神经元之间可能形成某种依赖关系,影响模型学习更好的特征。
  • Dropout 的作用
    • 打破这种依赖关系,促进模型参数独立地学习。

比如在MLP模型中使用dropout的效果如下:

def dropout(X,dropout):
    assert 0 <= dropout <=1
    #全部丢弃
    if dropout == 1:
        return torch.zeros_like(X)
    #全部保留
    if dropout == 0:
        return X
    #生成随机掩码
    mask = (torch.rand(X.shape) > dropout).float()

    #应用掩码并进行缩放
    return mask * X / (1.0 - dropout)

这里虽然说是以dropout概率丢弃输入X中的元素,但是实际上在小样本上不一定是这样的

因为在真实实现中,是使用rand随机生成的,然后去跟dropout判断

比如在下面的实际实现中,16个元素就别丢弃了11个,但是随着样本增加,会逼近dropout的概率

接下来,我们要实现对具有两个隐藏层的MLP进行dropout操作

#初始化参数
num_inputs,num_outputs,num_hiddens1,num_hiddens2 = 784, 10, 256, 256

dropout1 ,dropout2 = 0.2,0.5

#构建模型
class Net(nn.Module):
    def __init__(self,num_inputs,num_outputs,num_hiddens1,num_hiddens2,is_training =True):
        super(Net,self).__init__()
        self.num_inputs = num_inputs
        self.training = istraining
        self.lin1 = nn.Linear(num_inputs,num_hiddens1)
        self.lin2 = nn.Linear(num_hiddens1,num_hiddens2)
        self.lin3 = nn.Linear(num_hiddens2,num_outputs)
        self.relu = nn.ReLU()

    def forward(self,X):
        H1 = self.relu(self.lin1(X))
        if self.training:
            H1 = dropout(H1,dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training:
            H2 = dropout(H2,dropout2)
        out = self.lin3(H2)
        return out

net = Net(num_inputs,num_outputs,num_hiddens1,num_hiddens2)

简介实现,由于深度学习框架里面已经实现了dropout层,所以直接使用即可

#定义网络结构
net = nn.Sequential(
    nn.Flatten(),
    nn.Linear(784,256),
    nn.ReLU(),
    nn.Dropout(dropout1),
    nn.Linear(256,256),
    nn.ReLU(),
    nn.Dropout(dropout2),
    nn.Linear(256,10)
)

def init_weight(m):
    if type(m)==nn.Linear:
        nn.init.normal_(m.weight,std = 0.1)

net.apply(init_weights);

trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

小结:

dropout在前向传播的过程中,计算每一内部层的同时会丢弃一些神经元

可以避免过拟合,通常和控制权重向量的维数和大小结合使用

将活性值h替换为具有期望值h的随机变量,就是dropout实现里面是用随机变量,而非是固定的值

它仅在训练期间使用,不会应用在测试期间

要安装Docker并安装插件,可以按照以下步骤进行操作: 1. 首先,安装Docker。可以按照官方文档提供的步骤进行安装,或者使用适合您操作系统的包管理器进行安装。 2. 安装Docker Compose插件。可以使用以下方法安装: 2.1 下载指定版本的docker-compose文件: curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 2.2 赋予docker-compose文件执行权限: chmod +x /usr/local/bin/docker-compose 2.3 验证安装是否成功: docker-compose --version 3. 在安装插件之前,可以测试端口是否已被占用,以避免编排过程中出错。可以使用以下命令安装netstat并查看端口号是否被占用: yum -y install net-tools netstat -npl | grep 3306 现在,您已经安装了Docker并安装了Docker Compose插件,可以继续进行其他操作,例如上传docker-compose.yml文件到服务器,并在服务器上安装MySQL容器。可以参考Docker的官方文档或其他资源来了解如何使用Docker和Docker Compose进行容器的安装和配置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Docker安装docker-compose插件](https://blog.youkuaiyun.com/qq_50661854/article/details/124453329)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Docker安装MySQL docker安装mysql 完整详细教程](https://blog.youkuaiyun.com/qq_40739917/article/details/130891879)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值