多层感知机 MLP
多层感知机 MLP
引言
多层感知机(Multi-Layer Perception)由感知机推广而来,最主要的特点是具有多个神经元层,因此也叫深度神经网络(DNN)。
感知机由两层神经元组成,输入层接收外界输入信号后传递给输出层,输出层是M-P神经元。也称为阈值逻辑单元(threshold logic unit)
一个感知机的模型示意图如下:
感知机只具有输入层和输出层,通过对输入信号进行加权,使用激活功能,产生输出。可以表示为
u
=
∑
i
=
1
n
w
i
x
i
+
b
u = \sum_{i=1}^n w_ix_i + b
u=i=1∑nwixi+b
y
=
s
i
g
n
(
u
)
=
{
+
1
,
u
>
0
−
1
,
u
≤
0
y = sign(u) = \begin{cases} +1, \quad u>0 \\ -1, \quad u\leq0 \end{cases}
y=sign(u)={+1,u>0−1,u≤0
很显然,感知机仅仅只是一个线性的模型,如果遇到非线性数据就很难进行分类(感知机无法解决异或问题),那么可以通过加深神经元的网络层次来进行拟合,理论上来说,多层网络可以拟合任何复杂的函数。
人工智能奠基人之一的Marvin Minsky与1969年出版了《感知机》一书,书中指出,单层神经网络无法解决非线性问题,而多层神经网络的训练算法尚看不到希望,这个论断直接使神经网络研究进入了“冰河期”,这就是神经网络的第一次低谷。直到后来BP算法的走红,才掀起了神经网络的第二次高潮。
多层感知机能够解决异或问题主要是通过升维,原本的样本是二维的,但中间层有两个节点,每个样本通过中间层的结点计算得到两个值,那么就对原来的样本进行了升维操作。
采用多层感知机后:
随着维度的增高,原本线性不可分的样本,就可以变得可分。引用自知乎答主Yiwen
原理
通过矩阵
X
∈
R
n
×
d
\mathbf{X} \in \mathbb{R}^{n \times d}
X∈Rn×d
来表示
n
n
n个样本的小批量,
其中每个样本具有
d
d
d个输入特征。
对于具有
h
h
h个隐藏单元的单隐藏层多层感知机,
用
H
∈
R
n
×
h
\mathbf{H} \in \mathbb{R}^{n \times h}
H∈Rn×h表示隐藏层的输出,
称为隐藏表示(hidden representations)。
在数学或代码中,
H
\mathbf{H}
H也被称为隐藏层变量(hidden-layer variable)
或隐藏变量(hidden variable)。
因为隐藏层和输出层都是全连接的,
所以我们有隐藏层权重
W
(
1
)
∈
R
d
×
h
\mathbf{W}^{(1)} \in \mathbb{R}^{d \times h}
W(1)∈Rd×h
和隐藏层偏置
b
(
1
)
∈
R
1
×
h
\mathbf{b}^{(1)} \in \mathbb{R}^{1 \times h}
b(1)∈R1×h
以及输出层权重
W
(
2
)
∈
R
h
×
q
\mathbf{W}^{(2)} \in \mathbb{R}^{h \times q}
W(2)∈Rh×q
和输出层偏置
b
(
2
)
∈
R
1
×
q
\mathbf{b}^{(2)} \in \mathbb{R}^{1 \times q}
b(2)∈R1×q。
形式上,我们按如下方式计算单隐藏层多层感知机的输出
O
∈
R
n
×
q
\mathbf{O} \in \mathbb{R}^{n \times q}
O∈Rn×q:
H = X W ( 1 ) + b ( 1 ) , O = H W ( 2 ) + b ( 2 ) . \begin{aligned} \mathbf{H} & = \mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)}, \\ \mathbf{O} & = \mathbf{H}\mathbf{W}^{(2)} + \mathbf{b}^{(2)}. \end{aligned} HO=XW(1)+b(1),=HW(2)+b(2).
注意在添加隐藏层之后,模型现在需要跟踪和更新额外的参数。
可我们能从中得到什么好处呢?在上面定义的模型里,我们没有好处!
原因很简单:上面的隐藏单元由输入的仿射函数给出,
而输出(softmax操作前)只是隐藏单元的仿射函数。
仿射函数的仿射函数本身就是仿射函数,
但是我们之前的线性模型已经能够表示任何仿射函数。
我们可以证明这一等价性,即对于任意权重值,
我们只需合并隐藏层,便可产生具有参数
W
=
W
(
1
)
W
(
2
)
\mathbf{W} = \mathbf{W}^{(1)}\mathbf{W}^{(2)}
W=W(1)W(2)
和
b
=
b
(
1
)
W
(
2
)
+
b
(
2
)
\mathbf{b} = \mathbf{b}^{(1)} \mathbf{W}^{(2)} + \mathbf{b}^{(2)}
b=b(1)W(2)+b(2)
的等价单层模型:
O = ( X W ( 1 ) + b ( 1 ) ) W ( 2 ) + b ( 2 ) = X W ( 1 ) W ( 2 ) + b ( 1 ) W ( 2 ) + b ( 2 ) = X W + b . \mathbf{O} = (\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)})\mathbf{W}^{(2)} + \mathbf{b}^{(2)} = \mathbf{X} \mathbf{W}^{(1)}\mathbf{W}^{(2)} + \mathbf{b}^{(1)} \mathbf{W}^{(2)} + \mathbf{b}^{(2)} = \mathbf{X} \mathbf{W} + \mathbf{b}. O=(XW(1)+b(1))W(2)+b(2)=XW(1)W(2)+b(1)W(2)+b(2)=XW+b.
为了发挥多层架构的潜力,
我们还需要一个额外的关键要素:
在仿射变换之后对每个隐藏单元应用非线性的激活函数(activation function)
σ
\sigma
σ。
激活函数的输出(例如,
σ
(
⋅
)
\sigma(\cdot)
σ(⋅))被称为活性值(activations)。
一般来说,有了激活函数,就不可能再将我们的多层感知机退化成线性模型:
H = σ ( X W ( 1 ) + b ( 1 ) ) , O = H W ( 2 ) + b ( 2 ) . \begin{aligned} \mathbf{H} & = \sigma(\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)}), \\ \mathbf{O} & = \mathbf{H}\mathbf{W}^{(2)} + \mathbf{b}^{(2)}.\\ \end{aligned} HO=σ(XW(1)+b(1)),=HW(2)+b(2).
由于
X
\mathbf{X}
X中的每一行对应于小批量中的一个样本,
出于记号习惯的考量,
我们定义非线性函数
σ
\sigma
σ也以按行的方式作用于其输入,
即一次计算一个样本。
为了构建更通用的多层感知机,
我们可以继续堆叠这样的隐藏层,
例如
H
(
1
)
=
σ
1
(
X
W
(
1
)
+
b
(
1
)
)
\mathbf{H}^{(1)} = \sigma_1(\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)})
H(1)=σ1(XW(1)+b(1))和
H
(
2
)
=
σ
2
(
H
(
1
)
W
(
2
)
+
b
(
2
)
)
\mathbf{H}^{(2)} = \sigma_2(\mathbf{H}^{(1)} \mathbf{W}^{(2)} + \mathbf{b}^{(2)})
H(2)=σ2(H(1)W(2)+b(2)),
一层叠一层,从而产生更有表达能力的模型。
通用近似定理
多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用,
这些神经元依赖于每个输入的值。
我们可以很容易地设计隐藏节点来执行任意计算。
例如,在一对输入上进行基本逻辑操作,多层感知机是通用近似器。
即使是网络只有一个隐藏层,给定足够的神经元和正确的权重,
我们可以对任意函数建模,尽管实际中学习该函数是很困难的。
神经网络有点像C语言。
C语言和任何其他现代编程语言一样,能够表达任何可计算的程序。
但实际上,想出一个符合规范的程序才是最困难的部分。
而且,虽然一个单隐层网络能学习任何函数,
但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。
事实上,通过使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数。
算法优化原理
针对样本,误差可以定义为
L
o
s
s
=
(
y
−
y
^
)
2
Loss = (y-\hat y )^2
Loss=(y−y^)2
带入
y
^
\hat y
y^有
L
o
s
s
=
(
y
−
(
W
T
X
+
b
)
)
2
Loss = (y - (W^TX + b))^2
Loss=(y−(WTX+b))2
如果训练集中有N个样本,需要将误差累加,并使得误差最小化
m
i
n
∑
i
=
1
N
L
o
s
s
i
min \sum_{i=1}^N Loss_i
mini=1∑NLossi
待优化变量为W,b,为无约束问题,可以通过梯度下降算法进行求解。
基本迭代公式为:
θ
i
=
θ
i
−
α
∂
∂
θ
i
J
(
θ
0
,
θ
1
.
.
.
,
θ
n
)
\theta_i = \theta_i - \alpha\frac{\partial}{\partial \theta_i}J(\theta_0,\theta_1...,\theta_n)
θi=θi−α∂θi∂J(θ0,θ1...,θn)
代码
import torch
from torchvision import datasets
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
#一次取出的训练样本数
batch_size = 16
# epoch 的数目
n_epochs = 10
# 模型保存位置
path = "./saved_model/MLP.bin"
#读取数据
train_data = datasets.MNIST(root="./data", train=True, download=True,transform=transforms.ToTensor())
test_data = datasets.MNIST(root="./data", train=False, download=True, transform=transforms.ToTensor())
#创建数据加载器
train_loader = torch.utils.data.DataLoader(train_data, batch_size = batch_size, num_workers = 0)
test_loader = torch.utils.data.DataLoader(test_data, batch_size = batch_size, num_workers = 0)
class MLP(nn.Module):
def __init__(self):
#继承自父类
super(MLP, self).__init__()
#创建一个三层的网络
#输入的28*28为图片大小,输出的10为数字的类别数
hidden_first = 512
hidden_second = 512
self.first = nn.Linear(in_features=28*28, out_features=hidden_first)
self.second = nn.Linear(in_features=hidden_first, out_features=hidden_second)
self.third = nn.Linear(in_features=hidden_second, out_features=10)
def forward(self, data):
#先将图片数据转化为1*784的张量
data = data.view(-1, 28*28)
data = F.relu(self.first(data))
data = F.relu((self.second(data)))
data = F.log_softmax(self.third(data), dim = 1)
return data
def train():
# 定义损失函数和优化器
lossfunc = torch.nn.CrossEntropyLoss().cuda()
#lossfunc = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model.parameters(), lr=0.01)
# 开始训练
for epoch in range(n_epochs):
train_loss = 0.0
for data, target in train_loader:
optimizer.zero_grad()
#将数据放至GPU并计算输出
data = data.cuda()
target = target.cuda()
output = model(data)
#计算误差
loss = lossfunc(output, target)
#反向传播
loss.backward()
#将参数更新至网络中
optimizer.step()
#计算误差
train_loss += loss.item() * data.size(0)
train_loss = train_loss / len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch + 1, train_loss))
# 每遍历一遍数据集,测试一下准确率
test()
#最后将模型保存
torch.save(model, path)
#测试程序
def test():
correct = 0
total = 0
with torch.no_grad(): # 训练集中不需要反向传播
for data in test_loader:
images, labels = data
#将数据放到GPU上
images = images.cuda()
labels = labels.cuda()
output = model(images)
_, predicted = torch.max(output.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the test images: %d %%' % (
100 * correct / total))
return 100.0 * correct / total
try:
model = torch.load(path).cuda()
test()
except FileNotFoundError:
model = MLP()
# 将模型放到GPU上
model = model.cuda()
train()
参考
AD稳稳CSDN博客
柚子味的羊CSDN博客
知乎博主我在开水团做运筹
知乎博主HappyWang
李沐——动手学深度学习
code