MLP 多层感知机

Posted by UUQ on 2025-01-15
Estimated Reading Time 6 Minutes
Words 1.5k In Total
Viewed Times

多层感知机 MLP

线性模型表征能力有限。

MLP是在一层的基础上,增加全连接层,并增加激活函数(activation function),引入非线性

通用近似原理:universal approximation theorm

直观理解:一个有至少一个隐藏层的神经网络,具有线性输出层具有挤压性质的激活函数(sigmoid、logit),那么只要有足够多的隐藏单元,就可以以任意精度近似一个函数。

更具体准确的数学定义和表达这里暂时略过,有需要再来学

激活函数

一般是非线性的,激活函数$\sigma$ 的输入是神经网络某线性层的输出Y,输出是下一层的输入。常见的有 ReLU、sigmoid、tanh

ReLU

负数值归零处理
$$
ReLU(x)=\max(x,0)
$$
但是会在某些情况下让梯度消失

sigmoid

一类输出挤压到 (0,1) 范围内的函数,
$$
sigmoid(x)=\frac{1}{1+\exp(-x)}
$$

tanh

范围挤压到 (-1, 1)
$$
tanh(x)=\frac{1-\exp(-2x)}{1+\exp(-2x)}
$$

maxout

原来只有一个w和b,但是现在这一层有多个,输出最大的那一个。 来源于ReLU,很好的弥补了0那部分很多的空白,但是参数多,需要更多学习。
$$
maxout(x) = \max(\omega_i x_i + b_i)
$$

Pytorch实操 MNIST

1. 导入数据集

MNIST是经典图片分类的入门数据集,已经内置在torchvision中的dataloader里,可以直接导入

1
2
3
4
5
6
7
8
9
10
11
12
from torch.utils.data import DataLoader, random_split
train_size = int(0.8*len(train_dataset))
valid_size = len(train_dataset) - train_size
train_dataset, valid_dataset = random_split(train_dataset, [train_size, valid_size])

train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
valid_loader = DataLoader(dataset=valid_dataset, batch_size=64, shuffle=False)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

for data, target in train_loader:
print(data.shape, target.shape)
break

2. 定义MLP

这里需要注意,我按照多分类问题的思路,一开始在最外面增加了softmax层,结果跑出来效果还不如不加不加,后来发现是使用了crossentropyloss,相当于已经有了softmax。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.fc1 = nn.Linear(28*28, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 10)
# self.out = nn.Softmax(dim=1)

def forward(self, x):
x= x.view(-1, 28*28)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
# x = self.out(x)
return x

criterion = nn.CrossEntropyLoss() #交叉熵,正是这里使用交叉熵作为criterion,所以实际上已经进行了softmax计算
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9) #随机梯度下降, lr是学习率

3. 训练

使用train_loader实现小batch数据的输入和loss计算,使用验证集验证

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
def train(model, train_loader, valid_loader, criterion, optimizer, num_epochs):
total_loss = 0
model.train()
model.to(device)
for epoch in range(num_epochs):
total_loss = 0
for data, target in train_loader:
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
total_loss += loss.item()
# print(f'Epoch [{epoch+1}/{num_epochs}], Batch Loss: {loss.item()}')
print(f'Epoch [{epoch+1}/{num_epochs}], Total Loss: {total_loss/(len(train_loader))}')
# 使用验证集进行验证
model.eval()
valid_loss = 0
with torch.no_grad():
for data, target in valid_loader:
data, target = data.to(device), target.to(device)
output = model(data)
loss = criterion(output, target)
valid_loss += loss.item()

return total_loss/len(train_loader), valid_loss / len(valid_loader)

1
2
3
4
5
6
7
8
9
10
best_loss = 100000
best_model = None
for lr in [0.01, 0.001, 0.005, 0.0001, 0.1]:
model = MLP()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9)
train_loss, valid_loss = train(model, train_loader, valid_loader, criterion, optimizer, num_epochs)
print(f'\n\nLearning rate: {lr}, loss : {valid_loss}')
if valid_loss < best_loss:
best_loss = valid_loss
best_model = model

4. 评估

使用test集进行测试,禁用梯度计算(显然没必要计算,节省显存),并使用eval模式

1
2
3
4
5
6
7
8
9
10
11
with torch.no_grad():
correct = 0
total = 0
for data, target in test_loader:
data, target = data.to(device), target.to(device)
outputs = best_model(data)
_, predicted = torch.max(outputs.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f'Accuracy of the network on the 10000 test images: {100 * correct / total} %')
# 最终用0.01的lr,达到了loss 0.08,测试集acc 97.8%

权重衰减 weight decay

实际上就是L2正则化,增加一个L2范数的惩罚项,限制模型复杂度、让参数都比较小,防止过拟合,二分之一是为了求导方便。
$$
\frac{1}{2}\lambda \cdot ||\pmb{w}||^2
$$

Dropout 丢弃参数

训练的时候,为了增加网络输入输出之间的平滑度,在每层随机增加噪声。在标准的dropout过程中,每个中间的值v有p的概率被设为0,为保持期望不变,其他情况则设为$\frac{v}{1-p}$

一般测试的时候不用dropout,dropout可以看成一个层,nn.Dropout(dropoutP),其中dropoutP是概率

从MLP到神经网络

这里也放了一些对cs231n课程的学习理解

MLP可以说是比较直白的人工神经网络(ANN,顺带一提,是诺贝尔2024物理学奖),用的是全连接层。而神经网络,或者说在深度学习中的神经网络,可能会层数更深、参数量更大,另外层与层之间的连接方式也不再都是全连接FC,而是更加有效率的连接方式。

为什么还要更深

这里算是我个人学习的一些经验心得,简单总结一下,可能日后才能真正理解。

既然有了通用近似定理(UAT),说至少有一个隐层、有线性输出层和类sigmoid激活函数,就可以任意近似,那么为什么还要用多层网络?

首先就是神经网络的工程属性,理论上正确,但是实践上不一定好用,实践证明多层就是好用,it works,那么肯定就要用,至于为什么,用了再研究。

其次,理论一些来讲,多层的神经网络有助于学习特征,非线性能力更强,以及参数可以在层之间共享,减少总体参数量、降低过拟合风险。

另外更大的网络,貌似优化的时候所达到的局部极小值的,效果真的好;小网络就不一定了。

玩一玩 https://cs.stanford.edu/people/karpathy/convnetjs/demo/classify2d.html


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !