多层感知机(multilayer perceptron,简称 MLP),多层神经网络模型。

隐藏层

多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer)。隐藏层介于输入层和输出层之间。

包含单个隐藏层的多层感知机设计:

$$H = XW_h + b_h$$

$$O = HW_o + b_o$$

将隐藏层的输出直接作为输入层的输入。两个式子联立起来,得到:

$$O = (XW_h + b_h)W_o + b_o = XW_hW_0 + b_hW_o + b_o$$

可以看出,虽然引入了隐藏层,却依然等价与一个单层神经网络:其中输出层权重参数为$W_hW_o$,偏置参数为$b_hW_o + b_o$。即便添加再多的隐藏层,以上设计依然等价与一个单层神经网络。

激活函数

上述问题的根源在于全连接层只是对数据做仿射变换(affine transformation),而多个仿射变换的叠加依然是一个仿射变换。解决问题的一个方法是引入一个非线性变换,例如对隐藏层变量使用按元素操作的非线性函数进行变换,然后作为下一个全连接层的输入。这个非线性函数被称为激活函数(activation function)。

ReLU函数

ReLU(rectified linear unit)函数提供了一个很简单的非线性变换。给定元素$x$,该函数定义为:

$$ReLU(x) = max(x, 0)$$

ReLU函数只保留正数元素,并将负数元素清零。

当输入为负数时,ReLU函数的导数为0,输入为正时,ReLU的导数为1

绘制函数图像:

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
from mxnet import autograd, nd
from matplotlib import pyplot as plt

def xyplot(x_vals, y_vals, name):

plt.rcParams['figure.figsize'] = (5, 2.5)
plt.plot(x_vals.asnumpy(), y_vals.asnumpy())
plt.xlabel('x')
plt.ylabel(name + '(x)')
plt.show()

x = nd.arange(-8.0, 8.0, 0.1)
# print(x)

x.attach_grad()

with autograd.record():
y = x.relu()
# print(y)

# 绘制函数图像
xyplot(x, y, "relu")

# 绘制导数
y.backward()

xyplot(x, x.grad, "grad of relu")

结果如图所示:

relu

ReLU函数的导数:

grad_of_relu

Sigmoid函数

Sigmoid函数可以将元素的值变换到0和1之间:

$$sigmoid(x) = \frac{1}{1+\exp(-x)}$$

Sigmoid函数在早期神经网络中较为普遍,但是目前更多被简单的ReLU函数所替代。当输入接近0时,sigmoid函数接近线性变换。

绘制函数图像:

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
from mxnet import autograd, nd
from matplotlib import pyplot as plt

def xyplot(x_vals, y_vals, name):

plt.rcParams['figure.figsize'] = (5, 2.5)
plt.plot(x_vals.asnumpy(), y_vals.asnumpy())
plt.xlabel('x')
plt.ylabel(name + '(x)')
plt.show()

x = nd.arange(-8.0, 8.0, 0.1)
# print(x)

x.attach_grad()

with autograd.record():
y = x.sigmoid()

# 绘制函数图像
xyplot(x, y, 'sigmoid')

# 绘制导数图像
y.backward()
xyplot(x, x.grad, 'grad of sigmoid')

sigmoid函数图像:

sigmoid

sigmoid的导数:

grad_of_sigmoid

当输入为0时,sigmoid函数的导数达到最大值0.25;当输入越偏离0时,sigmoid函数的导数越接近0.

Tanh函数

Tanh(双曲正切)函数可以将元素的值变换到-1和1之间:
$$tanh(x) = \frac{1-\exp(-2x)}{1+\exp(-2x)}$$

当输入接近0时,tanh函数接近线性变换。虽然函数的形状和sigmoid函数的形状很相似,但是tanh函数在坐标系的原点上对称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from mxnet import autograd, nd
from matplotlib import pyplot as plt

def xyplot(x_vals, y_vals, name):

plt.rcParams['figure.figsize'] = (5, 2.5)
plt.plot(x_vals.asnumpy(), y_vals.asnumpy())
plt.xlabel('x')
plt.ylabel(name + '(x)')
plt.show()

x = nd.arange(-8.0, 8.0, 0.1)
# print(x)

x.attach_grad()
with autograd.record():
y = x.tanh()

# 绘制函数图像
xyplot(x, y, 'tanh')

# 绘制导数图像
y.backward()
xyplot(x, x.grad, 'grad of tanh')

tanh的函数图像:

tanh

tanh的导数:

grad_of_tanh

当输入为0时,tanh函数的导数达到最大值1;当输入偏离0时,tanh函数的导数越接近0.

多层感知机

多层感知机就是至少含有一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数变换。多层感知机的层数和各个隐藏层中隐藏单元个数都是超参数。由于引入了非线性因素,多层感知机的计算方式为:

$$H=\phi(XW_h+b_h)$$

$$O = HW_o +b_o$$

其中$\phi$表示激活函数。在分类问题中,可以对输出Osoftmax运算,并使用softmax回归中的交叉熵损失函数。在回归问题中,将输出层个数设置为1,并将输出$O$直接提供给线性回归中使用的平方损失函数。

多层感知机的实现

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
import gluonbook as gb
from mxnet import nd
from mxnet.gluon import loss as gloss

batch_size = 256
train_iter, test_iter = gb.load_data_fashion_mnist(batch_size)

# 定义模型参数
# 28*28
# 输入,输出, 隐藏单元数
num_inputs, num_outputs, num_hiddens = 784, 10, 256

W1 = nd.random.normal(scale=0.01, shape=(num_inputs, num_hiddens))
b1 = nd.zeros(shape=(num_hiddens))

W2 = nd.random.normal(scale=0.01, shape=(num_hiddens, num_outputs))
b2 = nd.zeros(shape=(num_outputs))

params = [W1, b1, W2, b2]

for param in params:
param.attach_grad()

# 定义激活函数
def relu(X):
return nd.maximum(X, 0)

# 定义模型
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(nd.dot(X, W1) + b1)

return nd.dot(H, W2) + b2

# 定义损失函数
loss = gloss.SoftmaxCrossEntropyLoss()


# 训练模型
num_epochs, lr = 5, 0.5
# softmax回归中实现
gb.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)

多层感知机的Gluon实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import gluonbook as gb
from mxnet import gluon, init
from mxnet.gluon import loss as gloss, nn


batch_size = 256
train_iter, test_iter = gb.load_data_fashion_mnist(batch_size)


# 定义模型
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'), nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))

# 损失函数
loss = gloss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.5})

num_epochs = 5

gb.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, trainer)