【深度学习实战】从零开始深度学习(二):多层全连接神经网络与MNIST手写数字分类

参考资料

  1. 廖星宇《深度学习入门之PyTorch》
  2. PyTorch官方文档
  3. 其他参考资料在文中以超链接的方式给出

目录

    • 0. 写在前面
    • 1. PyTorch基础
      • 1.1 张量(Tensor)
      • 1.2 变量(Variable)
      • 1.3 数据集(Dataset)
      • 1.4 模组(nn.Module)
      • 1.5 优化(torch.optim)
      • 1.6 模型保存与加载
    • 2. 案例实践:多层全连接神经网络实现 MNIST 手写数字分类
      • 2.1 定义简单三层全连接神经网络
      • 2.2 改进网络——增加激活函数
      • 2.3 再改进一下网络——添加批标准化
      • 2.4 训练网络
      • 2.5 三个神经网络模型的比较

0. 写在前面

多层全连接神经网络是深度学习各种复杂神经网络的基础,同时可以借用多层全连接神经网络,对PyTorch的一些基础概念进行一些了解。

关于神经网络的一些知识,廖星宇老师在《深度学习入门之PyTorc》的第二章和第三章都讲的很详细,廖老师也列出了很多参考资料:

学习内容 参考资料
python (1)《笨方法学Python》(Learn Python the Hard Way)。这本书面向零基础的python学者,通过一系列简单的例子快速入门Python的基本操作。(2)廖雪峰的Python入门,这个系列教程可以更全面地学习Python,对于机器学习方向的同学来说掌握前几张的Python基础即可。(3)Edx: Introduction to Computer Science and Programming Using Python,这是MIT的公开课,以Python作为入门语言,简洁、全面地讲述了计算机科学的内容,适合更进一步的学习。
线性代数 (1)《线性代数应该这样学》(Linear Algebra done right)(2)MIT的线性代数公开课(B站上就有视频:【公开课】麻省理工学院公开课——线性代数)这门课程建议在学习之前先对线性代数的知识体系有个基础的了解,老师讲课的思路比较跳跃,如果一点基础都没有的同学可能会觉得比较难。(3)Coding the Matrix
机器学习基础 (1)吴恩达的机器学习入门课程([中英字幕]吴恩达机器学习系列课程)(2)林轩田的机器学习基石和机器学习技法(林轩田机器学习基石(国语),林轩田机器学习技法(Machine Learning Techniques))(3)Udacity 的机器学习纳米学位(4)周志华著的 《 机器学习 》(5)李航著的《统计学习方法 》 这本书真的特别好,对理解一些模型和理论有很大的帮助。(6) Pattern Recognition and Machine Learning
深度学习 (1)Udacity 的两个深度学习课程 (2)Coursera 的 Neural 入{etworks for Machine Learning (3)Stanford 的 cs231n (4)Stanford 的 cs224n

1. PyTorch基础

1.1 张量(Tensor)

张量(Tensor)是PyTorch里面最基本的操作对象,可以和numpy的ndarray相互转换;它们的区别在于前者可以在GPU上运行,而后者只能在CPU上运行。可以通过下面这样的方式来定义一个三行两列给定元素的矩阵:

a = torch.Tensor(([2,3], [4,8], [7,9]))
print('a is {}'.format(a))
print('a size is {}'.format(a.size()))
'''
out:
a is tensor([[2., 3.],
        [4., 8.],
        [7., 9.]])
a size is torch.Size([3, 2])
'''

可以像操作numpy一样用索引来改变张量的值:

a[0,1] = 100
print('a is changed to {}'.format(a))
'''
out:
a is changed to tensor([[  2., 100.],
        [  4.,   8.],
        [  7.,   9.]])
'''

也可以实现Tensor与ndarray之间的转换:

numpy_a = a.numpy()
print('conver to numpy is \n {}'.format(numpy_a))
'''
out:
conver to numpy is 
 [[  2. 100.]
 [  4.   8.]
 [  7.   9.]]
'''

import numpy as np
b = np.array([[2,3], [4,5]])
torch_b = torch.from_numpy(b)
print('from numpy to torch.Tensor is {}'.format(torch_b))
'''
out:
from numpy to torch.Tensor is tensor([[2, 3],
        [4, 5]], dtype=torch.int32)
'''

torch.Tensor 默认的是 torch.FloatTensor 数据类型,也可以定义我们想要的数据类型:

a = torch.LongTensor(([2,3], [4,8], [7,9]))
print('a is {}'.format(a))
'''
out:
a is tensor([[2, 3],
        [4, 8],
        [7, 9]])
'''

同样可以创建全为0的张量或者随机创建张量:

a = torch.zeros((3,2))
print('a is {}'.format(a))
'''
out:
a is tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])
'''
a = torch.randn((3,2))
print('a is {}'.format(a))
'''
out:
a is tensor([[ 0.9284,  0.4900],
        [ 0.3578, -1.0652],
        [ 0.5255, -1.2100]])
'''

1.2 变量(Variable)

变量(Variable)是PyTorch里面一个比较特殊的概念,其与Tensor没有本质上的区别,想让一个Tensor变成Variable,只需要执行 torch.autograd.Variable(x) 就可以了。但不同的是,Variable 由三个重要的属性构成:data,grad,grad_fn。可以通过data取得Variable里面存储的Tensor值,grad表示的是这个Variable的反向传播梯度,grad_fn表示的是得到这个Variable的操作(加减乘除等)。

X = Variable(torch.Tensor ( [1) ) , requìres_grad=True) 

构建一个Variable,需要指明一个参数requìres_grad,这个参数默认为False,当被设置为True时,表示需要对这个Variable求梯度。

1.3 数据集(Dataset)

数据读取和预处理是深度学习问题的基础性的一步。PyTorch提供了很多工具可以帮助实现:

  • 继承和重写torch.utils.data.Dataset,例如:
import torch.utils.data.dataset as dataset
import pandas as pd

class myDataset(dataset):
    def __init__(self, csv_file, txt_file, root_dir, other_file):
        self.csv_data = pd.read_csv(csv_file)
        with open(txt_file, 'r') as f:
            data_list = f.readlines()
        self.txt_data = data_list
        self.root_dir = root_dir
    
    def __len__(self):
        return len(self.csv_data)
    
    def __getitem__(self, idx):
        data = (self.csv_data(idx), self.txt_data[idx])
        return data
  • 通过 torch.utils.data.DataLoader 进行多线程读取数据
    可以参考PyTorch源码解读(一)torch.utils.data.DataLoader,在案例中的应用可以参考上一篇文章《简单易懂的深度学习(一):利用PyTorch开始深度学习》中的3.3节。
  • torchvision 这个包中还有有关于计算机视觉的数据读取类:ImageFolder ,主要功能是处理图片
    具体的使用可以参考文章《简单易懂的深度学习(一):利用PyTorch开始深度学习》中的3.2节

1.4 模组(nn.Module)

nn.Module是利用PyTorch建立神经网络的核心工具之一,神经网络中的层、损失函数都在这个包里面。所有模型的构建都是从nn.Module这个类继承来的。

1.5 优化(torch.optim)

优化是调整模型中参数更新的一种策略。一般来说,优化算法分为两大类:

  • 一阶优化算法
    最常用的一阶优化算法就是梯度下降。
  • 二阶优化算法
    二阶优化算法使用的是二阶导数,但是计算成本太高。torch.optim包提供了各种优化算法的实现,如随机梯度下降,以及添加动量的随机棉度下降,自适应学习率等。例如:
optimizer = torch.optim.SGD(model.parameters() , lr=0.01 , momentum=0.9 ) 
# 将模型的参数作为需要更新的参数传入优化器,设定学习率是 0.01 ,动量是 0.9 的随机梯度下降
optimizer.zeros() # 在优化之前需要先将梯度归零
loss.backward() # 反向传播,自动求导得到每个参数的梯度
optimizer.step() # 以通过梯度做一步参数更新

1.6 模型保存与加载

PyTorch里面提供了两种模型的保存方式,对应的也有两种模型的加载方式。
第一种是保存整个模型的结构信息和参数信息,保存的对象是模型 model;在网络较大的时候加载的时间比较长,同时存储空间也比较大;

# 保存
torch.save(model, './model/pth')
# 加载
load_model = torch.load('model.pth')

第二种是保存模型的参数,保存的对象是模型的状态 model.state dict()

# 保存
torch.save(model.state_dict(), './model_state.pth')
# 加载
model.load_state_dic(torch.load('model_state.pth'))

2. 案例实践:多层全连接神经网络实现 MNIST 手写数字分类

2.1 定义简单三层全连接神经网络

import torch.nn as nn

class simpleNet(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(simpleNet, self).__init__()
        self.layer1 = nn.Linear(in_dim, n_hidden_1)
        self.layer2 = nn.Linear(n_hidden_1, n_hidden_2)
        self.layer3 = nn.Linear(n_hidden_2, out_dim)
        
    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

上面这个就是三层全连接神经网络架构的定义,输入参数包括:输入维度,输入的维度、第一层网络的神经元个数、第二层网络神经元的个数,以及第三层网络(输出层)神经元的个数。
全连接神经网络如下图所示:
【深度学习实战】从零开始深度学习(二):多层全连接神经网络与MNIST手写数字分类_第1张图片

2.2 改进网络——增加激活函数

class Activation_Net(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(Activation_Net, self).__init__()
        self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1),
                                    nn.ReLU(True))
        self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2),
                                    nn.ReLU(True))
        self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
    
    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

只需要在每层网络的输出部分添加激活函数就可以了,利用 nn.Sequential() 将网络的层组合到一起作为 self.layer。

2.3 再改进一下网络——添加批标准化

class Batch_Net(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(Batch_Net, self).__init__()
        self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1),
                                    nn.BatchNorm1d(n_hidden_1),
                                    nn.ReLU(True))
        self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2),
                                    nn.BatchNorm1d(n_hidden_2),
                                    nn.ReLU(True))
        self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
    
    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

同样使用 nn.Sequential( )将 nn. BatchNorm1d ()组合到网络层中。注意批标准化一般放在全连接层的后面、非线性层(激活函数)的前面。BatchNorm就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布的。

2.4 训练网络

首先导入需要的包,net是上面三个网络模型文件

import torch
from torch import nn, optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

import net

接着,设置模型的一些超参数:

# 设置超参数
batch_size = 64
learning_rate = 1e-2
num_epoches = 20

定义预处理方式:

#数据预处理
data_tf = transforms.Compose([transforms.ToTensor(),
                              transforms.Normalize([0.5], [0.5])])

torchvision.transforms提供了很多图片的预处理方法。这里的transforms.ToTensor()将图片转换成PyTorch中处理的Tensor对象,在转换的过程中PyTorch自动将图片标准化了;transforms.Normalize()需要传入两个参数,第一个参数市均值,第二个参数是方差,做的处理就是减均值,再除以方差。transforms.Compose()将各种预处理操作组合在一起。

注意这里由于是灰度图片,所以只有一个通道——transforms.Normalize([0.5], [0.5])。如果是彩色图片,则有三个通道,那么需要用transforms.Normalize([a,b,c], [d,e,f])来表示每个通道对应的均值和方差。

下面是下载数据集,读入数据。使用torch.utils.data.DataLoader 建立一个数据迭代器,传入数据集和 batch_size , 通过 shuffle=True 来表示每次迭代数据的时候是否将数据打乱。

#下载训练集-MNIST手写数字训练集
train_dataset = datasets.MNIST(root="./data", train=True, transform=data_tf, download=True)
test_dataset = datasets.MNIST(root="./data", train=False, transform=data_tf)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

接下来,导入网络,定义损失函数和优化方法:

model = net.simpleNet(28*28, 300, 100, 10)
if torch.cuda.is_available():
    model = model.cuda()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr = learning_rate)

这里首先构建了一个简单网络,网络的输出层有10个神经元(因为识别手写数字是个多分类问题,共有0-9这10个数字)。

下面就可以开始训练网络了:

# 训练模型
def train_model(model, criterion, optimizer, num_epoches):
    for epoch in range(num_epoches):
        print('epoch {}/{}'.format(epoch, num_epoches-1))
        print('-'*10)
        ## training------------------
#        model.train()
        train_loss = 0.0
        train_acc = 0.0
        # 获取数据输入和标签,封装成变量
        for data in train_loader: #获得一个batch样本
            img, label = data # 获得图片和标签
            img = img.view(img.size(0), -1) #将图片进行img的转换
            if torch.cuda.is_available():
                img = Variable(img).cuda()
                label = Variable(label).cuda()
            else:
                img, label = Variable(img), Variable(label)
            # 梯度参数清零
            optimizer.zero_grad()
            # 前向
            out = model(img) # 等价于 out = model.forward(img)
            loss = criterion(out, label)
            _, preds = torch.max(out.data, 1)
            # 反向传播
            loss.backward()
            optimizer.step()
            # 统计
            train_loss += loss.item()
            train_correct = torch.sum(preds == label.data)
            train_acc += train_correct
        print('Train Loss: {:.6f}, Acc: {:.6f}'.format(train_loss/(len(train_loader)), train_acc/(len(train_loader))))
        
        ## evaluation-------------
        model.eval()
        eval_loss = 0.0
        eval_acc = 0.0
        for data in test_loader:
            img, label = data
            img = img.view(img.size(0), -1)
            if torch.cuda.is_available():
                with torch.no_grad():
                    img = Variable(img).cuda()
                    label = Variable(label).cuda()
            else:
                img = Variable(img, volatile = True)
                label = Variable(label, volatile = True)
            out = model(img)
            loss = criterion(out, label)
            eval_loss += loss.item()
            _, preds = torch.max(out.data, 1)
            num_correct = torch.sum(preds == label.data)
            eval_acc += num_correct
        print('Test Loss:{:.6f}, Acc: {:.6f}'.format(eval_loss/(len(test_loader)), eval_acc/(len(test_loader))))

这里的view()函数的功能与reshape类似,用来转换size大小。view()函数作用是将一个多行的Tensor,拼接成一行。案例可以看这里:PyTorch中view()函数

import torch
 
a = torch.Tensor(2,3)
print(a)
# tensor([[0.0000, 0.0000, 0.0000],
#        [0.0000, 0.0000, 0.0000]])
 
print(a.view(1,-1))
# tensor([[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])

最后运行训练模型的函数,就可以得到训练的网络啦:

train_model(model, criterion, optimizer, num_epoches) 

2.5 三个神经网络模型的比较

分别对前面创建的三个模型进行训练20轮,得到的准确率如下:

模型 准确率(%)(epoch=20)
简单全连接(SimpleNet) 58.57
改进网络——增加激活函数(Acctivation_Net) 61.78
再改进一下网络——添加批标准化(Batch_Net ) 62.50

你可能感兴趣的:(深度学习,神经网络,机器学习,人工智能,python)