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

通过参数限制值的选择性范围来控制模型容量

min ℓ(w,b)\ell(w,b)ℓ(w,b) subject to ∣∣w∣∣2≤θ||w||^2 \le \theta∣∣w∣∣2≤θ

  • 通常不限制偏移b(限制和限制都差不多)
  • 小的θ\thetaθ意味着更强的正则项
使用均方范数作为柔性限制
  • 对每个θ\thetaθ都可以找到λ\lambdaλ使得之前的目标函数等价于下面
    minℓ(w,b)+λ2∣∣w∣∣2\ell(w, b) + \frac{\lambda}{2}||w||^2ℓ(w,b)+2λ​∣∣w∣∣2
    • 可以通过拉格朗日乘子来证明
  • 超参数λ\lambdaλ控制了正则项的重要程度
    • λ=0\lambda = 0λ=0: 无作用
    • λ→∞,w∗→0\lambda \rightarrow \infty, w^* \rightarrow 0λ→∞,w∗→0

其中minℓ(w,b)\ell(w, b)ℓ(w,b)代表了之前的损失函数, λ2∣∣w∣∣2\frac{\lambda}{2}||w||^22λ​∣∣w∣∣2控制值不会特别大, 其中λ\lambdaλ是超参数, 如果不希望模型太复杂可以通过增加λ\lambdaλ来解决

演示最优解的影响

w∗=argminℓ(w,b)+λ2∣∣w∣∣2w˘∗=argminℓ(e,b) w^* = argmin \ell(w, b) + \frac{\lambda}{2}||w||^2 \\ \breve{w}^* = argmin\ell(e, b) w∗=argminℓ(w,b)+2λ​∣∣w∣∣2w˘∗=argminℓ(e,b)
在这里插入图片描述

  • w˘∗\breve{w}^*w˘∗是之前的最优解
  • ℓ\ellℓ(绿色的线)
  • 黄色的线是损失线
  • 其中优化过程w˘∗\breve{w}^*w˘∗会不断地往w∗w^*w∗靠近, 在w∗w^*w∗位置形成平衡点
  • 优化过程使得最优解往原点走, 可以使得模型复杂度变低

其中, 我们希望优化ℓ\ellℓ(绿色的线)

参数更新法则

计算梯度
∂∂w(ℓ(w,b)+λ2∣∣w∣∣2)=∂ℓ(w,b)∂w+λw \frac{\partial}{\partial{w}}(\ell(w,b) +\frac{\lambda}{2}||w||^2)=\frac{\partial{\ell(w,b)}}{\partial{w}}+\lambda{w} ∂w∂​(ℓ(w,b)+2λ​∣∣w∣∣2)=∂w∂ℓ(w,b)​+λw
时间t更新参数
wt+1=(1−ηλ)wt−η∂ℓ(wt,bt)∂wt w_{t+1} = (1 - \eta\lambda)w_t - \eta\frac{\partial\ell(w_t,b_t)}{\partial{w_t}} wt+1​=(1−ηλ)wt​−η∂wt​∂ℓ(wt​,bt​)​
通常ηλ<1\eta\lambda \lt1ηλ<1, 在深度学习中通常叫做权重衰退

代码实现

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

# y = 0.05 + sum^d_{i=1}0.01x_i+ where(0, 0.01)
# 20个训练样本, 方便实验过拟合, 0.05是偏移量, x_i是输入, 均值为0, 方差为1
n_train, n_test, num_inputs, batch_Size = 20, 100, 200, 5
true_new, 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=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()
	net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
	num_epochs, lr = 100, 0.003
	animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='loss', xlim=[5, num_epochs], legend=['train', 'test'])
	for epoch in range(num_epochs):
		for X, y in train_iter:
			with troch.enable_grad():
				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, d2l.evaluate_loss(net, test_iter))))
	print("w的L2范数是", torch.norm(w).item())

train(lambd=0)  # w的l2范数12.56
train(lambd=3)  # w的l2范数0.35

简洁实现

def train_concise(wd):
	net = nn.Sequential(nn.Linear(num_inputs, 1))
	for param in net.parameters():
		param.data.normal_()
	loss = nn.MSELoss()
	num_epochs, lr = 100, 0.003
	trainer = torch.optim.SGD([
		{
			"params": net[0].weight,
			"weight_decay": wd  # 在算法内部实现lambd的计算
		}, {
			"param": net[0].bias
		}
	}], lr=lr)
	animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='loss', xlim=[5, num_epochs], legend=['train', 'test'])
	for epoch in range(num_epochs):
		for X, y in train_iter:
			with torch.enable_grad():
				trainer.zero_grad()
				l = loss(net(X), Y)
			L.backward()
			trainer.step()
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐