网络知识 娱乐 【机器学习实战】从零开始深度学习(通过GPU服务器进行深度学习)

【机器学习实战】从零开始深度学习(通过GPU服务器进行深度学习)

注:如需查看算法直接看《三》

一·利用PyTorch开始深度学习
  • 0 写在前面
  • 1 神经网络的组成部分
    • 1.1 层
    • 1.2 非线性激活函数
  • 2 利用Pytorch构建深度学习框架
    • 2.1 数据预处理与特征工程
    • 2.2 如何决定要使用的层?
    • 2.3 损失函数
    • 2.4 优化器的选择
    • 2.5 评估机器学习模型
    • 2.6 模型的选择
  • 3 案例实践——猫狗图像分类
    • 3.1 数据集的建立(训练集+验证集)
    • 3.2 数据预处理(图片数据转换成PyTorch张量)
    • 3.3 批量加载PyTorch张量
    • 3.4 构建网络架构
    • 3.5 训练模型

二·多层全连接神经网络与MNIST手写数字分类

  • 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 写在前面
  • 1. 卷积神经网络
    • 1.1 三个重要的思想
    • 1.2 卷积神经网络的主要结构
      • 1.2.1 卷积层(Convolution Layer)
      • 1.2.2 池化层
      • 1.2.3 全连接层
      • 1.2.4 卷积神经网络的基本形式
  • 2. 构建CNN模型架构——熟悉PyTorch的卷积模块
    • 2.1 卷积层nn.Conv2d()
    • 2.2 池化层
    • 2.3 扁平化操作(view函数)
  • 3. 一些卷积神经网络的案例
  • 4. 案例应用一:使用CNN实现MNIST手写数字分类
  • 5. 案例应用二:使用CNN重新实现猫狗图片分类
  • 6. 迁移学习
    • 6.1 提取模型中的层结构
    • 6.2 提取参数及自定义初始化
  • 7. 案例应用三:再一次猫狗分类——迁移学习,从VGG16模型开始
    • 7.1 创建和探索VGG16模型
    • 7.2 微调VGG模型
    • 7.3 设置优化器和损失函数
    • 7.6 训练VGG16模型
    • 7.7 改进模型泛化能力的小技巧
  • 8. 案例应用四:计算预卷积特征——再改进一下我们对猫狗图片分类的训练框架

四·生成对抗网络——深度学习中的非监督学习问题

1. 生成模型(Generative Model)

  • 1.1 自编码器(Autoencoder)

  • 2. 生成对抗网络(Generative Adversarial Networks,GAN)
    • 2.1 生成对抗网络模型概述
    • 2.2 生成对抗网络的数学原理
      • 2.2.1 预备知识
      • 2.2.2 生成对抗网络的数学原理

      3 【案例一】利用PyTorch实现GAN【生成新的图片】

  • 3.1 模型构建
  • 3.2 损失函数和优化器
  • 3.3 训练模型
  • 3.4 采用不同的loss函数
  • 3.5 使用更复杂的卷积神经网络

《之一》

0 写在前面

0.1. 利用GPU加速深度学习   疫情期间没有办法用实验室的电脑来跑模型,用领取的腾讯云实例来弄刚刚好。发现如果没有GPU来跑的话真的是太慢了,非常推荐利用GPU加速深度学习的训练速度。     如果采用GPU的话,训练函数train_model(*)中数据的输入要改变一下,也就是需要将数据放在GPU上  

inputs, labels = Variable(inputs).cuda(), Variable(labels).cuda()

  另外,使用GPU训练可能会导致GPU内存不足的情况(CUDA out of memory),有一个办法就是将batch_size的值调小(其中一个原因是GPU没有办法一下子处理打包过来的那么多图片)。batch_size调小之后面临的问题自然就是训练的速度变慢。   0.2. 监控你的显存占用情况   在训练的过程中可以随时监控自己的显存占用情况,输入下面这个命令就可以:  

C:Program FilesNVIDIA CorporationNVSMI>nvidia-smi

  得到的结果如下:  

0.3. optimal.step()和scheduler.step() 如果有更新到PyTorch 1.1.0之后的版本,就会出现“Detected call of lr_scheduler.step() beforeoptimizer.step().”这样的错误。建议自行百度解决。

1 神经网络的组成部分

抽象出底层的运算并训练深度学习算法的过程如下图所示  

1.1 层

  层(Layer)是神经网络的基本组成,线性层是其中最重要的一种。在pytorch里面,线性层只需要一行代码就可以实现:  

from torch.nn import Linear
myLayer = Linear(in_features = 10, out_features = 5, bias = True)

  上面这行代码的作用在于对输入数据进行一个线性变换 y=Wx+b   其中,in_features是输入数据的维度,out_features是输出数据的维度,bias是“b”的值,默认为True;如果bias=False,则b=0。   例子:  

import torch
from torch.nn import Linear
m = Linear(20, 30)
inp = torch.randn(128, 20)
out = m(inp)
print(out.size())
# output:torch.Size([128, 30])

  线性层Linear可以查询两个训练参数:W和b  

# 查询weight
w = m.weight
# 查询bias
b = m.bias

1.2 非线性激活函数

  我们知道,神经网络每一层的的输出应该是 z=g(Wx+b),其中g(*)为非线性的激活函数。Pytorch里面也提供了一些非线性的激活函数可以使用。  

f(x)=max(0, x)

线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元,是一种人工神经网络中常用的激活函数(activation function),通常指代以斜坡函数及其变种为代表的非线性函数。  

2 利用Pytorch构建深度学习框架

  在PyTorch中,所有的网络都实现为类,因此,神经网络MyFirstNetwork被创建为PyTorch类nn.Module的子类(从nn.Module继承),并实现init和forward方法。super方法用于将子类的参数传给父类。   在init方法中,初始化层,这里是构建了两个线性层。 在forward方法中,把数据传入init方法中初始化的层,并返回最终的输出。非线性层经常被forward函数直接调用,有些时候也可以在init方法中实现。  

2.1 数据预处理与特征工程

  • 特征提取 深度学习算法可以使用大量的数据自己学习出特征,不再使用手动的特征工程。

2.2 如何决定要使用的层?

2.3 损失函数

  定义好了网络架构,还剩下了最重要的两步——评估和优化。 评估神经网络通常会利用损失函数,一般来说,损失函数越小,模型越好。损失函数的梯度可以对模型的参数进行优化。   PyTorch提供了一些可用的损失函数:  

  在PyTorch里面使用这些损失函数,只要调用相应的函数就可以了:  

  这里以交叉熵Cross-entropy loss为例。

2.4 优化器的选择

2.5 评估机器学习模型

2.6 模型的选择

  要使模型能够工作,有三个选择至关重要:  

  • 最后一层的选择(激活函数的选择)和损失函数的选择 对于不同的机器学习问题,激活函数的选择和损失函数的选择可以概括成下表:
torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

MultiStepLR:与StepLR类似,只不过步长是以列表的形式给出。  

torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)

ExponentialLR:每一轮都将学习率乘上gamma值。  

torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.1)

ReduceLROnPlateau:这是最常用的学习率改变策略之一。当特定的度量指标,如训练损失、验证损失或者准确率不再变化时,学习率就会改变。通常会将学习率的原始值降低为原来的1/2~1/10。  

3 案例实践——猫狗图像分类

  光说不练假把式,我们下面就通过一个案例来巩固一下学习到的东西吧。   数据集:Dogs vs Cats。数据集有个文件夹,一个是train(训练数据集),一个是test(测试集)。在训练集中,有猫和狗的照片各12500张,每一张都通过文件名打标签:  

3.1 数据集的建立(训练集+验证集)

  首先是数据的分类,将数据集分为训练集(training set)和验证集(validation set)。  

import os
import numpy as np

'''
数据集的分配
'''
path = 'train'
# 读取文件夹内的所有文件
files= os.listdir(path) #得到文件夹下的所有文件名称
print(f'Total no of images {len(files)}')
no_of_images = len(files)
# 创建验证集的随机文件索引
shuffle = np.random.permutation(no_of_images)
# 将训练数据集进行划分,2000个样本归入验证集,剩下的样本归入训练集
validation_index = shuffle[:2000]
train_index = shuffle[2000:]
# 创建验证集和训练集文件夹
os.mkdir(os.path.join(path, 'validation'))
os.mkdir(os.path.join(path, 'training'))
for t in ['training', 'validation']:
    for folder in ['dog', 'cat']:
        os.mkdir(os.path.join(path, t, folder))
# 将图片的一小部分复制到validation文件夹      
for i in validation_index:
    folder = files[i].split('/')[-1].split('.')[0]
    image = files[i].split('/')[-1]
    os.rename(os.path.join(path, files[i]), os.path.join(path, 'validation', folder, image))
# 将剩下的图片复制到training文件夹
for i in train_index:
    folder = files[i].split('/')[-1].split('.')[0]
    image = files[i].split('/')[-1]
    os.rename(os.path.join(path, files[i]), os.path.join(path, 'training', folder, image))

  上面的代码将数据集中的样本图片分成了训练集(23000个样本)和验证集(2000个样本),并在相应的目录底下创建了对应的类别文件夹(cat和dog)  

3.2 数据预处理(图片数据转换成PyTorch张量)

  数据预处理的目的是将图片加载成PyTorch张量。PyTorch的torchvision.datasets包提供了一个名为ImageFolder的工具类,可以用于加载图片以及相应的标签。  

  PyTorch在transforms模块中提供了很多工具函数,可以用于完成这些预处理的步骤。  

在train对象中,保留了所有图片以及相应的标签:  

print(train.class_to_idx)
# out: {'cat': 0, 'dog': 1}
print(train.classes)
# out: ['cat', 'dog']

  同样,可以对得到的张量进行再次变形并将值反归一化,就可以得到相应的图片:  

# 对张量进行可视化
def imshow(inp):
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)

imshow(train[50][0])

  输出图片:  

3.3 批量加载PyTorch张量

下面的代码将前面的train数据集和valid数据集转换到数据加载器(data loader)中:  

import torch 
# 按批加载Pytorch张量
train_data_gen = torch.utils.data.DataLoader(train, batch_size = 64, num_workers = 3)
valid_data_gen = torch.utils.data.DataLoader(valid, batch_size = 64, num_workers = 3)
dataset_sizes = {'train':len(train_data_gen.dataset),'valid':len(valid_data_gen.dataset)}
dataloaders = {'train':train_data_gen,'valid':valid_data_gen}

  这里的num_workers用于多线程处理的设置。如果在运行代码的时候碰到下面这个错误代码:  

[Errno 32] Broken pipe

  那么可以参考这个原因: 把上面的train_data_gen和valid_data_gen改成下面的形式就可以了:  

train_data_gen = torch.utils.data.DataLoader(train, batch_size = 64)
valid_data_gen = torch.utils.data.DataLoader(valid, batch_size = 64)

  关于DataLoader的我给出了一些参数的介绍:  

3.4 构建网络架构

  对于计算机视觉中的大多数案例,我们可以使用已有的不同架构来解决实际的问题。torchvision.models模块里面提供了很多现成的应用:  

import torchvision.models as models
resnet18 = models.resnet18()
alexnet = models.alexnet()
vgg16 = models.vgg16()
squeezenet = models.squeezenet1_0()
densenet = models.densenet161()
inception = models.inception_v3()
googlenet = models.googlenet()
shufflenet = models.shufflenet_v2_x1_0()
mobilenet = models.mobilenet_v2()
resnext50_32x4d = models.resnext50_32x4d()
wide_resnet50_2 = models.wide_resnet50_2()
mnasnet = models.mnasnet1_0()

  在这里我们使用ResNet架构来解决。  

# 构建网络架构
import torchvision.models as models
import torch.nn as nn
import torch.optim as op
model_ft = models.resnet18(pretrained = True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)

  在这里,model_fit = models.resnet18(pretrained = True)创建了算法的实例,实例是PyTorch层的集合。ResNet架构是一个层的集合.同时,可以在这里预下载好ResNet-18模型,模型放在“C:UsersAdministrator.torchmodels”文件夹下面。  

pretrained (bool) – If True, returns a model pre-trained on ImageNet

# 检查是否可以在GPU上运行
if torch.cuda.is_available():
    model_ft = model_ft.cuda()

  接下来建立损失函数和基于SGD的优化器  

# 损失函数和优化器
learning_rate = 0.001
criterion = nn.CrossEntropyLoss()
optimizer_ft = op.SGD(model_ft.parameters(), lr = learning_rate, momentum = 0.9)
exp_lr_scheduler = op.lr_scheduler.StepLR(optimizer_ft, step_size = 7, gamma = 0.1)

  StepLR函数帮助动态修改学习率。在scheduler的step_size表示scheduler.step()每调用step_size次,对应的学习率就会按照策略调整一次。  

3.5 训练模型

  我们首先来看一下训练模型的整体代码,再详细进行解读。下面的train_model函数获取模型输入,并通过多轮训练调优算法的权重,降低损失函数:  

import time
from torch.autograd import Variable
# 训练模型
def train_model(model, criterion, optimizer, scheduler, num_epochs = 25):
    since = time.time()
    
    best_model_wts = model.state_dict()
    best_acc = 0.0
    
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs-1))
        print('-'*10)
        
        # 每轮都有训练和验证的阶段
        for phase in ['train', 'valid']:
            if phase == 'train':
                scheduler.step()
                model.train(True) # 模型设置为训练模式
            else:
                model.train(False) # 模型设置为评估模式
            
            running_loss = 0.0
            running_correct = 0
            
            # 在数据上迭代
            for data in dataloaders[phase]:
                # 获取输入
                inputs, labels = data
                # 封装成变量
                inputs, labels = Variable(inputs), Variable(labels)
                # 梯度参数清零
                optimizer.zero_grad()
                # 前向
                outputs = model(inputs)
                _, preds = torch.max(outputs.data, 1)
                loss = criterion(outputs, labels)
                # 只在训练阶段反向优化
                if phase == 'train':
                    loss.backward()
                    optimizer.step()
                # 统计
                running_loss += loss.item()
                running_correct += torch.sum(preds == labels.data)
            
            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_correct / dataset_sizes[phase]
            
            print('{} Loss: (:.4f) Acc: (:.4f)'.format(phase, epoch_loss, epoch_acc))
            
            # 深度复刻模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = model.state_dict()
                
        print()
    
    time_elapsed = time.time() - since
    print('Training Complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    
    #加载最优权重
    model.load_state_dict(best_model_wts)
    return model

  上述函数主要实现以下四个功能:  

  • 传入图片并计算损失;
  • 在训练阶段反向传播,在验证/测试阶段不调整权重;
  • 每轮训练中的损失值跨批次累加
  • 存储最优模型并打印验证准确率。

  输入参数:  

  • model:构建好的神经网络框架
  • criterion:损失函数
  • optimizer:构建的优化器
  • scheduler:学习率的修改
  • num_epochs = 25:循环次数
model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=2)

  代码要点:  

《之二》

0. 写在前面

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

学习内容

参考资料

python

对于机器学习方向的同学来说掌握Python基础即可。Edx: Introduction to Computer Science and Programming Using Python,这是公开课,以Python作为入门语言,简洁、全面地讲述了计算机科学的内容,适合更进一步的学习。

线性代数

MIT的线性代数公开课这门课程建议在学习之前先对线性代数的知识体系有个基础的了解,老师讲课的思路比较跳跃,如果一点基础都没有的同学可能会觉得比较难。Coding the Matrix

机器学习基础

(1)吴恩达的机器学习入门课程(2)林轩田的机器学习基石和机器学习技法(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 进行多线程读取数据
  • torchvision 这个包中还有有关于计算机视觉的数据读取类:ImageFolder ,主要功能是处理图片

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

  上面这个就是三层全连接神经网络架构的定义,输入参数包括:输入维度,输入的维度、第一层网络的神经元个数、第二层网络神经元的个数,以及第三层网络(输出层)神经元的个数。 全连接神经网络如下图所示:

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轮,得到的准确率如下:  

《之三》

1. 卷积神经网络

  关于卷积神经网络的一些理论知识

1.1 三个重要的思想

  卷积神经网络有三个非常重要的思想,这些思想也是为什么CNN能够真正起作用的原因。   1.局部性 对于一个图片而言,要对图片进行分类,就要获取图片的特征;通常情况下,这些特征不是由整张图片决定的,而是由一些局部区域来决定的。比如下面这张图片,通过鸟喙就可以判断这张图片是一张关于鸟的图片,那么分类根据的特征就是鸟喙这个区域的特征。  

  2.相同性 对于不同的图片,如果它们具有相同的特征,即使这些特征出现在图片的不同位置,也可以用同样的检测模式去检测不同图片的相同特征。   3.不变性 对一张大图片进行采样(图像采样方法),图片的性质基本保持不变。  

1.2 卷积神经网络的主要结构

  上图:全连接神经网络   卷积神经网络和全连接神经网络是相似的,都是由一些神经元构成,这些神经元有需要学习的参数,通过网络输入最后输出结构,并通过损失函数来优化网络中的参数。   然而,如果采用全连接神经网络去处理图片,当处理比较大的彩色图片时i,神经网络的参数增加的特别快,效率特别低。   而卷积神经网络的处理过程,不同于一般的全连接神经网络,卷积神经网络的层结构是不同的(如下图)。  

卷积神经网络是一个3D容量的神经元,神经元是以三个维度来排列的:宽度、高度和深度。卷积神经网络中的主要层结构有三个:卷积层、池化层和全连接层,通过堆叠这些层结构形成一个完整的卷积神经网络。卷积神经网络将原始图片转化成最后的类别得分,其中一些层包含参数,一些层没有包含参数,比如卷积层和全连接层拥有参数,而激活层和池化层不含参数。这些参数通过梯度下降法来更新,最后使模型尽可能正确地识别出图片类别。  

1.2.1 卷积层(Convolution Layer)

  卷积层是卷积神经网络的核心,大多数计算都是在卷积层中进行的。   概述   卷积神经网络的参数是由一些可学习的滤波器集合构成,每个滤波器在空间上(宽度和高度)都比较小,但是深度和输入数据的深度保持一致。在前向传播的时候,让每个滤波器都在输入数据的宽度和高度上滑动(卷积),然后计算整个滤波器和输入数据任意一处的内积。   滤波器可以视为二维数字矩阵。卷积操作可以看成以下四个步骤:  

  1. 在图像的某个位置上覆盖滤波器;
  2. 将滤波器中的值与图像中的对应像素的值相乘;
  3. 把上面的乘积加起来,得到的和是输出图像中目标像素的值;
  4. 对图像的所有位置重复此操作。

  在卷积层中还有一个重要的概念——感受野(receptive field)。与神经元连接的空间大小叫做神经元的感受野,它的大小是人为设置的一个超参数。   在滑动滤波器的时候,需要设置步长限制,步长就是滤波器一次移动的像素格的个数。输出图片的尺寸可以由公式 (W-F+2P)/S+1 来计算。其中W表示输入数据的大小,F表示卷积层中神经元的感受野尺寸,S表示步长,P表示边界填充0的数量。步长的设置不能使上述公式计算的结果为非整数。   举个例子来说明,图片像素中的数字表示像素格的亮度(步长=1):  

  卷积有助于我们找到特定的局部图像特征(如边缘),用在后面的网络中。比如上面这个滤波器(索伯滤波器,Sobel filter)就可以对图片进行如下所示的处理,这个滤波器的作用就是输出图像中更亮的像素表示原始图像中存在的边缘。  

  我们可以看到,上面4X4的图片通过3X3的滤波器,就变成了2X2的图片。为了解决这个问题,可以再图片的像素矩阵周围填充0像素:  

  最后,总结一下卷积层的一些性质: (1)输入数据体的尺寸是W1×H1×D1。 (2)有4个超参数:滤波器数量K,滤波器空间尺寸F,滑动步长S,零填充的数量P。 (3)输出数据体的尺寸为W2×H2×D2,其中W2=(W1-F+2P)/S+1,H2=(H1-F+2P)/S+1,D2=K。 (4)由于参数共享,每个滤波器包含的权重数目为F×F×D1,卷积层一共有F×F×D1×K个权重和K个偏置。 (5)在输出体数据中,第d个深度切片(空间尺寸是W2×H2),用第d个滤波器和输入数据进行有效卷积运算的结果,再加上第d个偏置。   对于卷积神经网络的一些超参数,常见的设置是F=3,S=1,P=1。  

1.2.2 池化层

  通常会在卷积层之间周期性插入一个池化层,其作用是逐渐降低数据体的空间尺寸,这样就能够减少网络中参数的数量,减少计算资源耗费,同时也能有效地控制过拟合。   池化一般通过简单的最大值、最小值或平均值操作完成。以下是池大小为2的最大池层的示例。除了最大值池化外,还有一些其他的池化函数,比如平均池化,或者L2范数池化。在实际中证明,在卷积层之间引入最大池化的效果是最好的,而平均池化一般放在卷积神经网络的最后一层。

1.2.3 全连接层

1.2.4 卷积神经网络的基本形式

 

2. 构建CNN模型架构——熟悉PyTorch的卷积模块

  整体架构如下:  

import torch.nn as nn
import torch.nn.functional as F

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        layer1 = nn.Sequential()
        layer1.add_module('conv1', nn.Conv2d(3, 32, 3, 1, padding = 1))
        layer1.add_module('relu1', nn.ReLU(True))
        layer1.add_module('pool1', nn.MaxPool2d(2,2))
        self.conv1 = layer1
        
        layer2 = nn.Sequential()
        layer2.add_module('conv2', nn.Conv2d(32, 64, 3, 1, padding = 1))
        layer2.add_module('relu2', nn.ReLU(True))
        layer2.add_module('pool2', nn.MaxPool2d(2,2))
        self.conv2 = layer2
        
        layer3 = nn.Sequential()
        layer3.add_module('conv3', nn.Conv2d(64, 128, 3, 1, padding = 1))
        layer3.add_module('relu3', nn.ReLU(True))
        layer3.add_module('pool3', nn.MaxPool2d(2,2))
        self.conv3 = layer3
        
        layer4 = nn.Sequential()
        layer4.add_module('fc1', nn.Linear(2048, 512))
        layer4.add_module('fc_relu1', nn.ReLU(True))
        layer4.add_module('fc2', nn.Linear(512, 64))
        layer4.add_module('fc_relu2', nn.ReLU(True))
        layer4.add_module('fc3', nn.Linear(64, 10))
        self.fc = layer4
    
    def forward(self, x):
        conv1 = self.conv1(x)
        conv2 = self.conv2(conv1)
        conv3 = self.conv3(conv2)
        fc_input = conv3.view(conv3.size(0), -1)
        fc_out = self.fc(fc_input)
        return fc_out

2.1 卷积层nn.Conv2d()

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')

 

2.2 池化层

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

  kernel_size、stride、padding、dilation的参数含义和卷积层的一样。 其他参数(一般情况下下面这两个参数都不会设置): return_indices:表示是否返回最大值所处的下标。默认为False。 ceil_mode:表示使用一些方格代替层结构,默认为False。   PyTorch也提供了其他的池化层,在官方文档里面可以找

返回顶部