从零开始写多层感知机
多层感知机
本节中,我们将以多层感知机(multilayer perceptron,简称 MLP)为例,介绍多层神经网络的概念。
import sys
sys.path.insert(0,'..')
import gluonbook as gb
batch_size = 256
train_data, test_data = gb.load_data_fashion_mnist(batch_size)
隐藏层
多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer)。隐藏层位于输入层和输出层之间。
具体来说,给定一个小批量样本 ,其批量大小为 ,输入个数为 。假设多层感知机只有一个隐藏层,其中隐藏单元个数为 。记隐藏层的输出(也称为隐藏层变量或隐藏变量)为 ,我们有 。因为隐藏层和输出层均是全连接层,我们知道隐藏层的权重参数和偏差参数分别为 和 ,以及输出层的权重和偏差参数分别为 和 。
我们先来看一个简单的输出 的计算方法:
也就是我们将这两个全连接层放置在一起,隐藏全连接层的输入直接进入输出全连接层。但如果我们将两个式子联立起来,就会发现
这样等价与我们创建一个单层神经网络,它的输出层权重参数是 ,且偏差参数为 。这样,不管使用多少隐藏层,其效果等同于只有输出层的单层神经网络。
初始化
对于W1,W2参数的设置,一开始不太理解,只是简单地记忆为
W1,W2 : inputs --> hidden --->outputs
因此W1,W2的形状应该为(inputs, hidden),(hidden, outputs)
之后通过print
数据在网络中的形状后才对网络运作有所理解(net函数中解释)
from mxnet import ndarray as nd
num_inputs = 28 * 28
num_outputs = 10
num_hidden = 256
weight_scale = .01
W1 = nd.random_normal(shape=(num_inputs, num_hidden), scale=weight_scale)
b1 = nd.zeros(num_hidden)
#print(W1.shape)
W2 = nd.random_normal(shape=(num_hidden, num_outputs), scale=weight_scale)
b2 = nd.zeros(num_outputs)
params = [W1, b1, W2, b2]
for param in params:
param.attach_grad()
(784, 256)
激活函数
上述问题的根源在于全连接层只是对数据做线性变换(准确叫仿射变换(affine transformation))。但多个线性变换的叠加仍然是一个线性变化。解决问题的一个方法是引入非线性变换,例如对隐藏变量先作用一个按元素的非线性函数后再输入到后面的层中。这个非线性函数被叫做激活函数(activation function)。下面我们介绍几个常见的激活函数。
ReLU 函数
ReLU(rectified linear unit)函数提供了一个很简单的非线性变换。给定元素 ,该函数定义为
可以看出,ReLU 函数只保留正数元素,并将负数元素清零。为了直观地观察这一非线性变换,我们先定义一个绘图函数xyplot
。
def relu(X):
return nd.maximum(X, 0)
定义模型
对应上面讲到的初始化参数,W1的形状为(num_inputs, hidden),在net
中将数据X
的形状转为(-1(不确定),num_inputs),与W1
相乘,将得出的结果传入激活函数Relu:
X:(-1(不确定),num_inputs) * W1:(num_inputs, hidden) --> (-1(不确定),hidden)
这里只需要用到一些简单的矩阵相乘的知识,如果不记得的话,只要记住两个二维矩阵,只要是(X,num)(num, Y)形式就能得出结果(X, Y),但如果是(num, Y)(X, num)就不能相乘(详情见线性代数)
def net(X):
#print(X.shape)
X = X.reshape((-1, num_inputs))
#print(X.shape)
h1 = relu(nd.dot(X, W1) + b1)
output = nd.dot(h1, W2) + b2
return output
Softmax和交叉熵损失函数
from mxnet import gluon
softmax_cross_entropy = gluon.loss.SoftmaxCrossEntropyLoss()
#哇,这个函数贼难背
#计算softmax交叉熵损失(底层代码另做解释) 大体是先做softmax处理 然后再求loss值
训练
一开始,我不太理解batch_size
的作用,心想如果规定了每次训练数据的数目,会不会对结果有影响,如果只是想识别一张图片会不会报错
然后,我尝试输出output的形状:print(output.shape)
得出了output.shape = (batch_size, num_ouputs)
因此batch_size
只是作为一个每次训练迭代的一个单位数据长度而已(一张图片就为1),对训练无影响
from mxnet import autograd as autograd
from gluonbook import utils
learning_rate = .5
for epoch in range(5): #迭代五次
train_loss = 0. #初始化训练损失值
train_acc = 0. #初始化训练准确率
for data, label in train_data: #从训练数据迭代器中每次选取一组数据和标签进行训练
with autograd.record():
output = net(data) #输出值为net(data)
#print("output")
#print(output.shape)
loss = softmax_cross_entropy(output, label)
#将output值置入softmax函数,然后与label做比较,得出损失值loss
loss.backward()
#对loss求导
utils.sgd(params, learning_rate, batch_size)
#(改)对参数进行sgd优化,为了使学习率不那么敏感,学习率除以每一组数据的数量
train_loss += nd.mean(loss).asscalar()
#loss的结构为数组,因此对数组的元素求平均,并且转为浮点数
train_acc += gb.accuracy(output, label)
#计算训练集的准确率
test_acc = gb.evaluate_accuracy(test_data, net)
#计算测试集的准确率
print("Eopch %d. Loss: %f, Train acc %f, Test acc %f" % (
epoch, train_loss/len(train_data),train_acc/len(train_data),test_acc))
Eopch 0. Loss: 0.237514, Train acc 0.911215, Test acc 0.891500
Eopch 1. Loss: 0.235197, Train acc 0.912578, Test acc 0.893800
Eopch 2. Loss: 0.229723, Train acc 0.913470, Test acc 0.887900
Eopch 3. Loss: 0.226010, Train acc 0.916329, Test acc 0.891500
Eopch 4. Loss: 0.221937, Train acc 0.917852, Test acc 0.894700
多层感知机 --- 使用Gluon
定义模型
现在使用Gluon来搭建多层感知机,更为简单方便
将使用Flatten层与Dense层搭建简单的多层网络如下:
from mxnet import gluon
net = gluon.nn.Sequential() #串型网络 按顺序排列
with net.name_scope():
net.add(gluon.nn.Flatten()) #将输入展平为二维
net.add(gluon.nn.Dense(256, activation="relu")) #隐藏层(全连接),256为num_output
net.add(gluon.nn.Dense(128, activation="relu"))
net.add(gluon.nn.Dense(10))#output层
print(net)
net.initialize()
net.collect_params()
Sequential(
(0): Flatten
(1): Dense(None -> 256, Activation(relu))
(2): Dense(None -> 128, Activation(relu))
(3): Dense(None -> 10, linear)
)
sequential3_ (
Parameter sequential3_dense0_weight (shape=(256, 0), dtype=float32)
Parameter sequential3_dense0_bias (shape=(256,), dtype=float32)
Parameter sequential3_dense1_weight (shape=(128, 0), dtype=float32)
Parameter sequential3_dense1_bias (shape=(128,), dtype=float32)
Parameter sequential3_dense2_weight (shape=(10, 0), dtype=float32)
Parameter sequential3_dense2_bias (shape=(10,), dtype=float32)
)
读取数据并训练
from mxnet import ndarray as nd
from mxnet import autograd
from gluonbook import utils
batch_size = 256
train_data, test_data = utils.load_data_fashion_mnist(batch_size)
softmax_cross_entropy = gluon.loss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.5} )
#设置训练器:
#net.collect_params()为网络中的权重和参数
#sgd为优化函数
#以字典的形式输入学习率
for epoch in range(5):
train_loss = 0.
train_acc = 0.
for data, label in train_data:
with autograd.record(): #设置训练范围(向前传播)
output = net(data)
loss = softmax_cross_entropy(output, label)
loss.backward()
trainer.step(batch_size)#每次读取大小为batch_size的数据训练
train_loss += nd.mean(loss).asscalar()
train_acc += utils.accuracy(output, label)
test_acc = utils.evaluate_accuracy(test_data, net)
print("Epoch %d. Loss: %f, Train acc %f, Test acc %f" % (
epoch, train_loss/len(train_data), train_acc/len(train_data),test_acc))
Epoch 0. Loss: 0.841238, Train acc 0.688143, Test acc 0.825200
Epoch 1. Loss: 0.483838, Train acc 0.820107, Test acc 0.847700
Epoch 2. Loss: 0.421176, Train acc 0.843467, Test acc 0.859700
Epoch 3. Loss: 0.382882, Train acc 0.858245, Test acc 0.862000
Epoch 4. Loss: 0.357187, Train acc 0.867880, Test acc 0.877500