动手深度学习-模型选择+权重衰退
模型选择
训练误差:模拟在训练数据上的数据
泛化误差:模型在新数据上的误差
训练误差就类似平时考试的分数,泛化误差就是实际考试的能力
验证数据集:用来评估模型好坏的数据集
测试数据集:只用一次的数据集,不能反过来再更新超参数
给定一个模型的种类主要用俩种因素
- 参数的个数
- 参数的选择范围
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)