从0到1,实现你的第一个多层神经网络

1,962 阅读6分钟

好书分享:机器学习入门书籍

0.识别结果

如果有想亲自试一试的小伙伴,可以去我的公众号【拇指笔记】,后台回复"MLP"自取~

1. 多层感知机(MLP)

本节将以多层感知机为例,介绍多层神经网络的概念。

1.1 隐藏层

下图为一个多层感知机的神经网络图。

多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer)。如图所示的隐藏层一共有5个隐藏单元。由于输入层不涉及计算,因此这个多层感知机的层数为2。如图所示的多层感知机中的隐藏层和输出层都是全连接层。

对于一个只有一个隐藏层且隐藏单元个数为h的多层感知机,记它的输出为H。因为这个多层感知机中的隐藏层和输出层都是全连接层,所以可以设:隐藏层的权重参数和偏差参数分别为W_h和b_h,输出层的权重参数和偏差参数分别为W_o和b_o

由此我们可以得到单隐藏层神经网络输入、隐藏层输出和输出之间的关系

\begin{align}
H = XW_h+b_h
\\O = HW_o+b_o
\end{align}
\tag1

将两个式子联立起来,得到输入与输出之间的关系。

\begin{align}
O &= (XW_h+b_h)W_o+b_o
\\ &=XW_hW_o+b_hW_o+b_o
\end{align}
\tag2

由式(2)不难看出,这样的神经网络虽然引入了(一个或多个)隐藏层,却依然等价于一个单层神经网络。引起该问题的根源是全连接层,全连接层只是对数据进行仿射变换,而多个仿射变换叠加仍然为一个仿射变换。为了解决这样的问题,引入了非线性变换,即激活函数。

不了解激活函数的同学,戳这里

2. 实现多层感知机

这一节将使用多层感知机实现对Fashion-MNIST数据集的读取。

首先导入需要的库。

import torch
from torch import nn
import numpy as np
import sys
sys.path.append("..") 

import torchvision
from IPython import display
from time import time
import matplotlib.pyplot as plt
import torchvision.transforms as transforms
from time import time 

2.1 获取和读取数据

这一部分依然使用之前的Fashion-MNIST数据集。

batch_size = 256

mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',train=True,download=True,transform=transforms.ToTensor())
#获取训练集
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',train=False,download=True,transform=transforms.ToTensor())
#获取测试集

#生成迭代器
train_iter = torch.utils.data.DataLoader(mnist_train,batch_size=batch_size,shuffle = True,num_workers = 0)

test_iter = torch.utils.data.DataLoader(mnist_test,batch_size = batch_size,shuffle=False,num_workers=0)

2.2 定义并初始化模型参数

Fashion-MNIST数据集中的图像为28*28像素,也就是由784个特征值。Fashion-MNIST数据集一共有十个类别。因此模型需要784个输入,10个输出。假设隐藏单元为256(超参数,可调节)。

num_inputs,num_outputs,num_hiddens = 784,10,256

#隐藏层权重参数、隐藏层偏差参数、输出层权重参数和输出层偏差参数初始化。
w1 = torch.tensor(np.random.normal(0,0.01,(num_inputs,num_hiddens)),dtype = torch.float)
b1 = torch.zeros(num_hiddens,dtype = torch.float)
w2 = torch.tensor(np.random.normal(0,0.01,(num_hiddens,num_outputs)),dtype = torch.float)
b2 = torch.zeros(num_outputs,dtype = torch.float)

params = [w1,b1,w2,b2]

#对w1,w2,b1,b2开启自动求导
for param in params:
	param.requirers_grad_(requires_grad = True)

2.3 定义激活函数

def relu(X):
	return torch.max(input=X,other = torch.tensor(0.0))

2.4 定义模型

def net(X):
	X = X.view((-1,num_inputs))
    #将输入换形为列向量的形式
    H = relu(torch.matmul(X,W1)+b1)
    #计算出隐藏层的输出
    O = torch.matmul(H,W2)+b2
    #计算输出
    return O

2.5 定义损失函数

这里采用计算softmax运算和交叉熵数值稳定性更好的CrossEntroyLoss函数

loss = torch.nn.CrossEntropyLoss()

2.6 优化函数

使用小批量随机梯度下降算法。

def sgd(params,lr,batch_size):
    #lr:学习率,params:权重参数和偏差参数
    for param in params:
        param.data -= lr*param.grad/batch_size
        #.data是对数据备份进行操作,不改变数据本身。

2.7 计算分类准确率

计算准确率的原理:

我们把预测概率最大的类别作为输出类别,如果它与真实类别y一致,说明预测正确。分类准确率就是正确预测数量与总预测数量之比

首先我们需要得到预测的结果。

从一组预测概率(变量y_hat)中找出最大的概率对应的索引(索引即代表了类别)

#argmax(f(x))函数,对f(x)求最大值所对应的点x。我们令f(x)= dim=1,即可实现求所有行上的最大值对应的索引。
A = y_hat.argmax(dim=1)	
#最终输出结果为一个行数与y_hat相同的列向量

然后我们需要将得到的最大概率对应的类别与真实类别(y)比较,判断预测是否是正确的

B = (y_hat.argmax(dim=1)==y).float()
#由于y_hat.argmax(dim=1)==y得到的是ByteTensor型数据,所以我们通过.float()将其转换为浮点型Tensor()

最后我们需要计算分类准确率

我们知道y_hat的行数就对应着样本总数,所以,对B求平均值得到的就是分类准确率

(y_hat.argmax(dim=1)==y).float().mean()

上一步最终得到的数据为tensor(x)的形式,为了得到最终的pytorch number,需要对其进行下一步操作

(y_hat.argmax(dim=1)==y).float().mean().item()
#pytorch number的获取统一通过.item()实现

整理一下,得到计算分类准确率函数

def accuracy(y_hat,y):
    return (y_hat.argmax(dim=1).float().mean().item())

作为推广,该函数还可以评价模型net在数据集data_iter上的准确率。

def net_accurary(data_iter,net):
    right_sum,n = 0.0,0
    for X,y in data_iter:
    #从迭代器data_iter中获取X和y
        right_sum += (net(X).argmax(dim=1)==y).float().sum().item()
        #计算准确判断的数量
        n +=y.shape[0]
        #通过shape[0]获取y的零维度(列)的元素数量
    return right_sum/n

2.8 训练模型

在训练模型时,迭代周期数num_epochs、隐藏层神经单元数num_hiddens和学习率lr都是可以调节的超参数,通过调节超参数的值可以获得分类更准确的模型。

num_epochs,lr = 5,100

def train_MLP(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr ,optimizer,net_accurary):
    for epoch in range(num_epochs):
        #损失值、正确数量、总数 初始化。
        train_l_sum,train_right_sum,n= 0.0,0.0,0
        
        for X,y in train_iter:
            y_hat = net(X)
            l = loss(y_hat,y).sum()
            #数据集损失函数的值=每个样本的损失函数值的和。            
            optimizer.zero_grad()			#对优化函数梯度清零
            for param in params:
                param.grad.data.zero_()
            l.backward()	#对损失函数求梯度
            optimzer(params,lr,batch_size)
            
            train_l_sum += l.item()
            train_right_sum += (y_hat.argmax(dim=1) == y).sum().item()
            n += y.shape[0]
            
        test_acc = net_accuracy(test_iter, net)	#测试集的准确率
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f' % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
        
train_MLP(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr,sgd,net_accurary)

2.9 训练结果

可以看出,使用了多层感知机后,神经网络的识别准确率有了提升。且运行时间有所加长(5个学习周期运行了80秒)。

2.10 识别测试集

使用训练好的模型对测试集进行预测

做一个模型的最终目的当然不是训练了,所以来识别数据集试试。

#将样本的类别数字转换成文本
def get_Fashion_MNIST_labels(labels):
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                   'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels]
    #labels是一个列表,所以有了for循环获取这个列表对应的文本列表

#显示图像
def show_fashion_mnist(images,labels):
    display.set_matplotlib_formats('svg')
    #绘制矢量图
    _,figs = plt.subplots(1,len(images),figsize=(12,12))
    #设置添加子图的数量、大小
    for f,img,lbl in zip(figs,images,labels):
        f.imshow(img.view(28,28).numpy())
        f.set_title(lbl)
        f.axes.get_xaxis().set_visible(False)
        f.axes.get_yaxis().set_visible(False)
    plt.show()

#从测试集中获得样本和标签
X, y = iter(test_iter).next()

true_labels = get_Fashion_MNIST_labels(y.numpy())
pred_labels = get_Fashion_MNIST_labels(net(X).argmax(dim=1).numpy())

#将真实标签和预测得到的标签加入到图像上
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]

show_fashion_mnist(X[0:9], titles[0:9])

识别结果


写文不易,觉得有用的话加个关注呗,我的公众号【拇指笔记】,每天更新我的学习笔记~