动手深度学习-模型选择+权重衰退

模型选择

训练误差:模拟在训练数据上的数据

泛化误差:模型在新数据上的误差

训练误差就类似平时考试的分数,泛化误差就是实际考试的能力

验证数据集:用来评估模型好坏的数据集

测试数据集:只用一次的数据集,不能反过来再更新超参数

给定一个模型的种类主要用俩种因素

  • 参数的个数
  • 参数的选择范围

vc维:对于一个分类模型,vc等于一个最大的数据集大小,不管给定标号,都存在一个模型来对它进行完美分类。

线性分类的vc维

2维输入的感知机,vc维=3:能够分类任何三个点,但不是四个

支持n维输入的感知机vc维是N+1,一些多层感知机的vc维$O(Nlog_2N)$

但vc维衡量不是很准确,计算深度学习模型的vc维很困难

数据复杂度

有多个因素

  • 样本个数
  • 每个样本元素个数
  • 时间空间特性
  • 多样性

综上模型容量需要匹配模型复杂度,否则可能导致过拟合和欠拟合

权重衰退

使用均方范数作为硬性限制

通过参数的选择范围来控制模型的容量,权重衰退就是控制值的范围来进行的。

$$ \min \ell(w,b) \quad \text{subject to} \quad ||w||^2 \leq \theta $$

简单来说就是w中所有值的平方和小于一个数

  • 通常不限制偏移项b(限不限制都差不多)
  • 更小的theta意味着更强的正则项

使用均方范数作为柔性限制

对于每个$\theta$都可以找到$\lambda$使得之前的目标函数等价于下面

$$ \min \ell(w, b) + \frac{\lambda}{2} ||w||^2 $$

就是找到最小化loss+右边式子的值

可以通过拉格朗日乘子来证明

超参数$\lambda$控制了这则项的重要程度

$\lambda=0:无作用,\lambda \to \infty ,w^\ast \to 0$

我们通常更多的使用柔性限制,这里arg是达到最小时候的参数集合

他可能会在黄点形成一个平衡点(这里的等高线密度并不重要)

$$ \frac{\partial}{\partial \mathbf w}\left(\ell(\mathbf w,b) + \frac{\lambda}{2}||\mathbf w||^2\right) = \frac{\partial \ell(\mathbf w,b)}{\partial \mathbf w} + \lambda \mathbf w $$

时间t更新参数,更具学习率进行下一轮的更新

$$ \mathbf w_{t+1} = (1-\eta\lambda)\mathbf w_t - \eta\frac{\partial l(\mathbf w_t, b_t)}{\partial \mathbf w_t} $$

通常$\eta\lambda<1$再深度学习中叫做衰退权重

import torch
from torch import nn
from d2l import torch as d2l

n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
# 用于创建一个特定形状的张量,并用数值1填充这个张量的所有元素
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():
    # 这个size是维度
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]


# 定义l2范数乘法
def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2


def train(lambd):
    w, b = init_params()
    w, b = init_params()
    # python的lambda表达式,其他俩个参数是被冻结了
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
    num_epochs, lr = 100, 0.003
    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)
    #torch.norm() 函数用于计算输入张量 w 的范数(norm)
    print('w的L2范数是', torch.norm(w).item())

train(lambd=4)

在简单写的时候在可以直接使用torch.optim.SGD来实现。weight_decay来实现的

因为在小范围取参数,模型的空间就会变小

权重衰退的效果是有限的

丢弃法(dropout)

一个好的模型需要输入数据的鲁棒性

使用有噪音的数据等价于Tihonov正则

在机器学习中,正则项(Regularization Term)是一种技术,用于防止模型过拟合(Overfitting),即模型在训练数据上表现得过于优秀,以至于它失去了对新数据的泛化能力。正则项通过在损失函数中添加一个额外的惩罚项来实现这一目标,这个惩罚项与模型参数的大小有关。

丢弃法,在层之间加入噪音

对x加入噪音得到的x',我们希望

$$ E[\mathbf x']=\mathbf x $$

丢弃法对于每个元素进行如下扰动,

$$ x'_i=\begin{cases} 0 & \text{with probability } p \\ \frac{x_i}{1-p} & \text{otherise} \end{cases} $$

简单来说就是一定概率做出变化,一定概率不变

丢弃法通常作用在隐藏层全连接的输出上。

顺序是算权重后,激活之后,执行dropout函数丢弃,然后在进入下一轮迭代

推理中的丢弃法

正则想只在训练中使用:他们影响模型参数的更新

在推理过程中,丢弃法直接返回输入,他可能会随机激活神经元

h=droupout(h)

这样也能保证确定性是输出net.train()和net.eval()就是控制这个

丢弃法类似一个正则项,丢弃的概率是控制模型复杂度的超参数

在Python中,特别是在NumPy和PyTorch这样的科学计算库中,“广播机制”(Broadcasting)指的是数组运算时自动调整形状以进行兼容性操作的能力。广播机制允许形状不同的数组进行算术运算,只要这些数组在某些维度上是可兼容的

代码

import torch
from torch import nn
from d2l import torch as d2l
from softmax_regression_2 import train_ch3


# dropout函数随机丢弃,传入0.5 就有百分之50%的概率进行丢弃
def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    if dropout == 1:
        # 创建了和X相同的矩阵
        return torch.zeros_like(X)
    if dropout == 0:
        return X

    # 如果大于dropout就是1 小于的话就是0,这里是直接采样了
    mask = (torch.rand(X.shape) > dropout).float()
    # mask不是0,就是1的矩阵,这样有些元素会被丢弃,而有些元素将会被保留 具体被丢弃和被保留的成都取决于传入的dropout值
    # 这里这么做的原因,是生成一个矩阵进行惩罚,相比于直接把一部分元素设置为0有更好的性能
    return mask * X / (1.0 - dropout)


# 这个函数生成一个一维的张量,包含了从0开始到15(不包含16)的整数序列
X = torch.arange(16, dtype=torch.float32).reshape((2, 8))

print(dropout_layer(X, 0.5))
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 = is_training
        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.reshape((-1, self.num_inputs))))
        # 如果当前是在进行训练的话,就进行丢弃
        if self.training == True:
            H1 = dropout_layer(H1, dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training == True:
            H2 = dropout_layer(H2, dropout2)
        out = self.lin3(H2)
        return out


net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
# 这里使用了256个神经元,这对于小规模数据集的神经网络来说,是相对复杂的,容易出现过拟合现象,但是这里使用了dropout过拟合的出现
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss(reduction='none')
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 这里是返回模型中所有可学习的参数gpt
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

简单写法

import torch
from torch import nn
from d2l import torch as d2l
from softmax_regression_2 import train_ch3

dropout1, dropout2 = 0.2, 0.5
net = nn.Sequential(
    nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),
    nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(),
    nn.Dropout(dropout1), nn.Linear(256, 10)
)

num_epochs, lr, batch_size = 10, 0.5, 256


# 设置权重初始化
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)


net.apply(init_weights)

trainer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss(reduction='none')
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
Last modification:July 17, 2024
如果觉得我的文章对你有用,请随意赞赏