Knowledge

正则化(或者称为惩罚项)

一般而言,正则化的通项为$$w^* = arg min \sum_i L(y_i, f(x_i;w))+ \lambda \Omega(w)$$
规则化函数Ω(w)也有很多种选择,一般是模型复杂度的单调递增函数,模型越复杂,规则化值就越大。比如,规则化项可以是模型参数向量的范数。然而,不同的选择对参数w的约束不同,取得的效果也不同,但我们在论文中常见的都聚集在:零范数、一范数、二范数、迹范数、Frobenius范数和核范数等等。

常见的优化是使用的L1与L2范数,这是为什么呢? ——> 因为这本质其实简化到了一个凸优化问题,凸优化问题是有解的,所以常见的就是L1和L2范数

参考文章

1
2
3
4
5
6
https://blog.csdn.net/zouxy09/article/details/24971995?ADUIN=189151916&ADSESSION=1451702742&ADTAG=CLIENT.QQ.5449_.0&ADPUBNO=26525

https://www.zhihu.com/question/38081976

【“L1和L2正则化”直观理解(之一),从拉格朗日乘数法角度进行理解-哔哩哔哩】 https://b23.tv/hzgdwPP

正则化的种类

参数范数惩罚

L0正则化L1 正则化

L0范数是指向量中非0的元素的个数。如果我们用L0范数来规则化一个参数矩阵W的话,就是希望W的大部分元素都是0。

L1正则化:也称为 Lasso 正则化,它通过在模型的损失函数中增加权重的 L1 范数(权重向量的绝对值之和)来实现正则化。L1 正则化倾向于产生稀疏权重矩阵,即将一些权重推向零,从而实现特征选择的效果。

L1的损失函数为:

LL1=Ldata+λi=1nwi1L_{L1} = L_{data} + \lambda \sum_{i=1}^n||w_i||_1

为啥我们常见的只有L1范数进行约束而不是使用L0范数进行约束,原因其实在上面已经说过

一是因为L0范数很难优化求解(NP难问题)。
二是L1范数是L0范数的最优凸近似,而且它比L0范数要容易优化求解。所以大家才把目光和万千宠爱转于L1范数。

总之一句话 L1范数和L0范数都可以实现稀疏,L1因具有比L0更好的优化求解特性而被广泛应用。

稀疏化的优点

1)特征选择(Feature Selection):
大家对稀疏规则化趋之若鹜的一个关键原因在于它能实现特征的自动选择。一般来说,xi的大部分元素(也就是特征)都是和最终的输出yi没有关系或者不提供任何信息的,在最小化目标函数的时候考虑xi这些额外的特征,虽然可以获得更小的训练误差,但在预测新的样本时,这些没用的信息反而会被考虑,从而干扰了对正确yi的预测。稀疏规则化算子的引入就是为了完成特征自动选择的光荣使命,它会学习地去掉这些没有信息的特征,也就是把这些特征对应的权重置为0。

2)可解释性(Interpretability):
另一个青睐于稀疏的理由是,模型更容易解释。例如患某种病的概率是y,然后我们收集到的数据x是1000维的,也就是我们需要寻找这1000种因素到底是怎么影响患上这种病的概率的。假设我们这个是个回归模型:y=w1x1+w2x2+…+w1000x1000+b(当然了,为了让y限定在[0,1]的范围,一般还得加个Logistic函数)。通过学习,如果最后学习到的w就只有很少的非零元素,例如只有5个非零的wi,那么我们就有理由相信,这些对应的特征在患病分析上面提供的信息是巨大的,决策性的。也就是说,患不患这种病只和这5个因素有关,那医生就好分析多了。但如果1000个wi都非0,医生面对这1000种因素,累觉不爱。

L2正则化

L2 正则化:也称为 Ridge 正则化,它通过在模型的损失函数中增加权重的 L2 范数(权重向量的平方和)来实现正则化。L2 正则化会使权重值变得较小,但不会直接导致权重稀疏,因此不具有特征选择的作用,但可以有效地控制模型的复杂度。

L2的损失函数为:

LL1=Ldata+λi=1nwi22L_{L1} = L_{data} + \lambda \sum_{i=1}^n||w_i||_2^2

实例

Elastic Net 正则化
1
A correction has been published: _Journal of the Royal Statistical Society Series B: Statistical Methodology_, Volume 67, Issue 5, November 2005, Page 768, [https://doi.org/10.1111/j.1467-9868.2005.00527.x](https://doi.org/10.1111/j.1467-9868.2005.00527.x)

Elastic Net 正则化:Elastic Net 是 L1 和 L2 正则化的组合,它在损失函数中同时使用 L1 和 L2 范数,可以综合两者的优点。即吸收了L1对参数的稀疏化,同时又吸收了L2对参数的放缩的特点。

因此其损失函数为二者惩罚项的组合:

LL1=Ldata+λ1i=1nwi1+λ2i=1nwi22L_{L1} = L_{data} + \lambda_1 \sum_{i=1}^n||w_i||_1 + \lambda_2 \sum_{i=1}^n||w_i||_2^2

Elastic Net优点
  1. 平衡L1和L2:通过调整( λ1\lambda_1)和( λ2\lambda_2 )的值,可以在L1和L2正则化之间进行权衡,以适应不同的数据特性。
  2. 特征选择:当( λ1\lambda_1 )较大时,弹性网倾向于进行特征选择,将不重要的特征权重置为零。
  3. 稳定性:当( λ2\lambda_2)较大时,弹性网倾向于保持权重较小,增加模型的稳定性。

Dropout

最大范数约束

注入噪声

在这个方法中,我们在反向传播对学习的权重进行更新的过程中,少量随机噪声被添加到更新后的权重中,以使其更鲁棒或对微小变化不敏感,这有助于模型构建更稳健的特征集,从而确保模型不会过度拟合训练数据。然而,作为正则化方式之一,这种方法并没有取得很好的效果。

提前停止

数据增强

几何变换类:翻转、旋转、缩放、裁剪、移位
颜色变换类:高斯噪声、模糊、颜色变换、擦除、填充等
多样本数据增强等高级增强方法

Homework

9.26

问题

用 matlab 或者 python 实现一个前馈网络,完成分类任务,只是选择一个分类评估指标结果。提交源码和实验结果。

解答

解释

借助识别手写数字的数据集,在 20 轮的训练后进行评估,借助 sklearn 的机器学习库,计算了准确率,精准率,F1指数,召回率以及混淆矩阵的值。

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import torch.nn.functional as F
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, precision_score, recall_score, f1_score

import torch.optim as optim

# 定义超参数
batch_size = 64

# 图像处理,标准化
# 由于标准化处理之后便于矩阵运算,有利于提高运算速率
# 单通道实现为多通道 28*28 -> 1*28*28, 同时进行标准化
# 0.1307, 0.3081都是均值与方差
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])

# 加载训练数据集
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)

# 加载测试数据集
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)

# 定义神经网络结构
class NET(torch.nn.Module):
def __init__(self):
super(NET, self).__init__()
self.n1 = torch.nn.Linear(784, 512)
self.n2 = torch.nn.Linear(512, 256)
self.n3 = torch.nn.Linear(256, 128)
self.n4 = torch.nn.Linear(128, 64)
self.n5 = torch.nn.Linear(64, 10)

def forward(self, x):
# 展平输入图像
x = x.view(-1, 784)
# 依次通过每一层,并使用ReLU激活函数
x = F.relu(self.n1(x))
x = F.relu(self.n2(x))
x = F.relu(self.n3(x))
x = F.relu(self.n4(x))
# 最后一层不使用激活函数,输出分类结果
return self.n5(x)


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = NET().to(device)
criterion = torch.nn.CrossEntropyLoss()
# momentum 是指的是冲量值,用来优化神经网络
optimization = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

def train(epoch):
# 设置模型为训练模式
model.train()
running_loss = 0.0
for batch_index, data in enumerate(train_loader, 0):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
optimization.zero_grad()
output = model(inputs)
loss = criterion(output, labels)
loss.backward()
optimization.step()
running_loss += loss.item()

if batch_index % 300 == 299:
print('[%d, %d] loss = %f' % (epoch + 1, batch_index + 1, running_loss / 300))
running_loss = 0.0

def test():
# 设置模型为评估模式
model.eval()
correct = 0
total = 0

all_labels = []
all_preds = []
# 关闭梯度计算
with torch.no_grad():
for data in test_loader:
inputs, labels = data
# 将输入和标签转移到相应设备(CPU或GPU)
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播
outputs = model(inputs)
# 获取预测结果
_, predicted = torch.max(outputs.data, 1)
# 由于是N * 1矩阵,那么size就是(N,1)的元组,我们可以直接选第一个元素表示这一个批次的数量

all_labels.extend(labels.cpu().numpy())
all_preds.extend(predicted.cpu().numpy())

total += labels.size(0)
# 计算正确预测的数量
correct += (predicted == labels).sum().item()

# 打印准确率
accuracy = accuracy_score(all_labels, all_preds)
print('Accuracy is %f%%' % (accuracy * 100))

# 精准率、召回率、F1值
precision = precision_score(all_labels, all_preds, average='weighted')
recall = recall_score(all_labels, all_preds, average='weighted')
f1 = f1_score(all_labels, all_preds, average='weighted')
print('Precision is %f%%' % (precision * 100))
print('Recall is %f%%' % (recall * 100))
print('F1 is %f%%' % (f1 * 100))

# 计算混淆矩阵
confu_matr = confusion_matrix(all_labels, all_preds)
print('Confusion Matrix is \n', confu_matr)

if __name__ == '__main__':
for epoch in range(20):
# 训练模型
train(epoch)
# 测试模型
print('=====Finished Training=====')
test()


实验结果

alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Accuracy is 98.410000%
Precision is 98.410806%
Recall is 98.410000%
F1 is 98.410144%
Confusion Matrix is
[[ 970 0 2 0 0 2 2 0 2 2]
[ 0 1128 1 1 0 1 2 0 2 0]
[ 3 1 1013 3 2 0 2 2 4 2]
[ 0 0 2 992 0 4 0 4 4 4]
[ 1 0 4 0 963 0 3 2 0 9]
[ 2 0 0 5 1 877 1 1 2 3]
[ 2 2 1 0 3 2 947 0 1 0]
[ 0 2 8 0 0 0 0 1011 3 4]
[ 0 1 3 5 1 1 2 3 953 5]
[ 4 2 0 1 8 4 1 2 0 987]]