赞
踩
凡是能解决模型泛化误差而不是训练误差的方法,都被称为正则化。
模型的泛化误差主要是由模型过拟合引起的,所以正则化的各种方法用于解决模型过拟合的问题。
L1和L2正则化的核心思想就是限制模型参数的取值范围。 模型取值范围大同样可以训练出一个泛化能力强的模型,那为什么要限制模型参数的取值范围呢?
模型取值范围大同样可以训练出一个泛化能力强的模型,但是出现过拟合的几率也大大提升了(可以选择的范围大,自然就选了一整套相互配合起来可以让损失最小的参数,但是这些参数有可能只是在迎合训练集)。另一方面,参数取得太大会放大输入模型的样本之中的噪声,让输出结果失真。
综上所述,无论是从参数取值范围大会提高过拟合几率的角度来看,还是从参数太大会放大噪声的角度来看,参数取值范围太大都是非常不利的,所以需要对范围进行限制。
明白了L1和L2正则化的核心思想就是限制模型参数的取值范围之后,来解决下一个问题:如何减小模型参数的取值范围?
首先,理解一下L1和L2正则化中的L1和L2是什么意思。L1和L2就是L1范数和L2范数。L1范数是我们非常熟悉的曼哈顿距离,L2范数也是非常熟悉的欧式距离。对于一个向量
ω
⃗
\vec{\omega}
ω
而言,其L1范数和L2范数分别是:
L
1
范数:
∣
∣
ω
⃗
∣
∣
1
=
∣
ω
1
∣
+
∣
ω
2
∣
+
…
+
∣
ω
n
∣
L
2
范数:
∣
∣
ω
⃗
∣
∣
2
=
ω
1
2
+
ω
2
2
+
…
+
ω
n
2
L1范数:||\vec{\omega}||_1=|\omega_1|+|\omega_2|+…+|\omega_n| \\ L2范数:||\vec{\omega}||_2=\sqrt{\omega_1^2+\omega_2^2+…+\omega_n^2}
L1范数:∣∣ω
∣∣1=∣ω1∣+∣ω2∣+…+∣ωn∣L2范数:∣∣ω
∣∣2=ω12+ω22+…+ωn2
在损失函数之中,在尾项之中加入L2正则项,为梯度下降加入减小权重的目标,就可以在减小损失的同时减小权重。假设原本的损失函数是
ι
(
ω
⃗
,
b
⃗
)
\iota(\vec{\omega},\vec{b})
ι(ω
,b
),改正之后的损失函数就是:
ι
′
(
ω
⃗
,
b
⃗
)
=
ι
(
ω
⃗
,
b
⃗
)
+
λ
2
L
2
2
(
w
⃗
)
\iota'(\vec{\omega},\vec{b})=\iota(\vec{\omega},\vec{b})+\frac{\lambda}{2} L_2^2(\vec{w})
ι′(ω
,b
)=ι(ω
,b
)+2λL22(w
)其中,
λ
\lambda
λ是一个超参数,用来控制正则项的惩罚力度。越大,则最终权重会越小。
L1范数和L2范数作为正则项的区别在于,L1范数可以带来稀疏性。从L1范数的图像之中可以看出,L1正则化之后的损失函数想要最小化,
ω
⃗
\vec{\omega}
ω
的取值相比起L2正则化更容易接近或者落在坐标轴上,这意味着会将某些权重的值设置为0或者接近于0,权重消失或者接近于消失,就是所谓“带来稀疏性”。
ι
′
(
ω
⃗
,
b
⃗
)
\iota'(\vec{\omega},\vec{b})
ι′(ω
,b
)对
ω
\omega
ω求梯度,之后利用梯度下降更新权重,可以得到:
ω
⃗
t
+
1
=
ω
⃗
t
−
η
(
∂
ι
(
ω
⃗
,
b
⃗
)
∂
ω
⃗
+
λ
ω
⃗
)
=
(
1
−
λ
η
)
ω
⃗
t
−
η
∂
ι
(
ω
⃗
,
b
⃗
)
∂
ω
⃗
t
\vec{\omega}_{t+1}=\vec{\omega}_t-\eta(\frac{\partial \iota(\vec{\omega},\vec{b})}{\partial \vec{\omega}}+\lambda\vec{\omega})=(1-\lambda\eta)\vec{\omega}_t-\eta\frac{\partial \iota(\vec{\omega},\vec{b})}{\partial \vec{\omega}_t}
ω
t+1=ω
t−η(∂ω
∂ι(ω
,b
)+λω
)=(1−λη)ω
t−η∂ω
t∂ι(ω
,b
)由于
λ
η
\lambda\eta
λη是小于1的,所以每一次梯度下降的时候,权重都会衰减。
故L2正则化也称为权重衰减。
在pytorch之中,进行权重衰减非常简单,只需要在梯度下降的函数中加入一个参数即可。如下:
# 梯度下降优化器(对权重w设置权重衰减。默认会对权重和偏置都做权重衰减,虽然偏置没有必要做权重衰减)
trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=1e-4)
训练一个模型的完整源码如下:
import torch import matplotlib.pyplot as plt import numpy as np from torch.utils import data from torchvision import datasets from torchvision import transforms from torch import nn def get_train_test_loader(image_size=28, train_batch_size=10, test_batch_size=10, num_workers=0, is_download=True): """ 获得训练数据生成器和验证数据生成器,这个数据集总共有10个类别(即10个标签) :param image_size: 图片的大小,取28 :param train_batch_size: 数据生成器的批量大小 :param num_workers: 数据生成器每次读取时调用的线程数量 :param is_download: 是否要下载数据集(如果还未下载设置为True) :return: 训练数据生成器和验证数据生成器 """ data_transform = transforms.Compose([ # 设置图片大小 transforms.Resize(image_size), # 转化为tensor张量 transforms.ToTensor() ]) train_data = datasets.FashionMNIST(root='../data', train=True, download=is_download, transform=data_transform) test_data = datasets.FashionMNIST(root='../data', train=False, download=is_download, transform=data_transform) train_loader = data.DataLoader(train_data, batch_size=train_batch_size, shuffle=True, num_workers=num_workers, drop_last=True) test_loader = data.DataLoader(test_data, batch_size=test_batch_size, shuffle=False, num_workers=num_workers, drop_last=True) return train_loader, test_loader def accuracy(y_hat, y): """模型训练完成后,判断预测结果的准确率""" if y_hat.shape[0] < 2 and y_hat.shape[1] < 2: raise ValueError("dimesion error") # 得到预测的y_hat每一行中最大概率所在的索引(索引即类别) y_hat = y_hat.argmax(axis=1) # 判断预测类别是否与实际类别相等 judge = y_hat.type(y.dtype) == y # 现在cmp是一个bool类型的向量,转成0和1,统计1的数量 return float(judge.type(y.dtype).sum()) / len(y) def init_weights(m): """将网络中每一个线性层的所有权重都利用标准差为0.01的正态分布进行初始化,b没有初始化,所以初始为0""" if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) # 学习率 lr = 0.03 # 批量大小 batch_size = 100 test_batch_size = 10000 # dropout概率 p = 0.5 # 最开始一个展平层用来给输入的x整形,接下来第一层是线性层,结果经过RuLU激活之后,进入下一个线性层,之后结果进行输出。隐藏层共有392个神经元 net = nn.Sequential(nn.Flatten(), nn.Linear(784, 392), nn.ReLU(), nn.Dropout(p), nn.Linear(392,100), nn.ReLU(), nn.Dropout(p), nn.Linear(100, 10)) # apply会将net中的每一层都作为参数进入init_weights,当发现是线性层,会对线性层的w自动初始化 net.apply(init_weights) # 损失函数是交叉熵函数,参数是y_hat和y,注意,会对传入的y_hat先进行一次softmax处理 loss = nn.CrossEntropyLoss() # 梯度下降优化器(对权重w设置权重衰减。默认会对权重和偏置都做权重衰减,虽然偏置没有必要做权重衰减) trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=1e-4) # 获取数据生成器以及数据 train_loader, test_loader = get_train_test_loader(train_batch_size=batch_size, test_batch_size=test_batch_size, is_download=False) train_loader_test, _ = get_train_test_loader(train_batch_size=60000, is_download=False) # 学习代数 num_epoch = 10 # 用来正常显示中文标签 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示负号 plt.rcParams['axes.unicode_minus'] = False # 交互模式 plt.ion() fig = plt.figure() ax = fig.add_subplot() ax.grid(True, axis="y") epoch_x, validate_accuracy_y, test_accuracy_y, loss_y = [], [], [], [] line_validate, = ax.plot(epoch_x,validate_accuracy_y,color='black',linestyle="--",label="训练集准确率") line_test, = ax.plot(epoch_x,test_accuracy_y,color='blue',label="测试集准确率") line_loss, = ax.plot(epoch_x,loss_y,color='red',label="损失函数") ax.set(xticks=np.arange(0,12,1),xlim=(0,11),yticks=np.arange(0,1.1,0.05),ylim=(0,1)) ax.legend() for i in range(num_epoch): epoch_x.append(i + 1) for x, y in train_loader: # 模型得到预测值 y_hat = net(x) # 损失函数 l = loss(y_hat, y) print(f"\r{batch_size}个批量样本损失为{l}", end="", flush=True) trainer.zero_grad() # 求偏导 l.backward() # 梯度下降 trainer.step() with torch.no_grad(): for x, y in train_loader_test: y_hat = net(x) l = loss(y_hat, y) loss_y.append(l) print(f"\n第{i}代,所有训练样本损失为{l}") validate_accuracy = accuracy(y_hat, y) print(f"验证集预测正确率:{validate_accuracy}") validate_accuracy_y.append(validate_accuracy) for x, y in test_loader: test_accuracy = accuracy(net(x), y) print(f"测试集预测正确率:{test_accuracy}") test_accuracy_y.append(test_accuracy) print("=" * 25) line_validate.set_data(epoch_x,validate_accuracy_y) line_test.set_data(epoch_x,test_accuracy_y) line_loss.set_data(epoch_x,loss_y) plt.draw() plt.pause(0.1) plt.ioff() plt.show()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。