目录

  • 基本概念
  • 训练集、测试集、验证集
  • 损失函数
  • 优化算法
  • 线性回归的神经网络及代码实现
  • 线性回归模型的从零开始的实现
  • 线性回归模型使用pytorch的简洁实现
  • softmax回归的神经网络及代码实现
  • 获取Fashion-MNIST训练集和读取数据
  • 多层感知机
  • 多层感知机pytorch实现


基本概念

训练集、测试集、验证集

训练集是用来训练模型内参数的数据集。
验证集用于在训练过程中检验模型的状态,收敛情况。验证集通常用于调整超参数,根据几组模型验证集上的表现决定哪组超参数拥有最好的性能。同时验证集在训练过程中还可以用来监控模型是否发生过拟合,一般来说验证集表现稳定后,若继续训练,训练集表现还会继续上升,但是验证集会出现不升反降的情况,这样一般就发生了过拟合。所以验证集也用来判断何时停止训练,训练集会被多次使用以调参
测试集用来评价模型泛化能力,即之前模型使用验证集确定了超参数,使用训练集调整了参数,最后使用一个从没有见过的数据集来判断这个模型是否Work,测试集只使用一次。

训练集比作学生的课本,学生根据课本里的内容来掌握知识;验证集比作作业,通过作业可以知道不同学生学习情况、进步的速度快慢;测试集比作考试,考的题是平常都没有见过,考察学生举一反三的能力。
一般来说,我们把数据分成这样的三份:训练集(60%),验证集(20%),测试集(20%)。验证集不是必须的。

损失函数

损失函数用于衡量模型输出的估计值与真实值之间的误差,通常会选取一个非负数作为误差,且数值越小表示误差越小,一个常用的选择是平方函数。

优化算法

当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。
小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)BB,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

线性回归输出是一个连续值,因此适用于回归问题;softmax回归的输出是离散值,因此适用于分类问题,两者都是单层神经网络。

线性回归的神经网络及代码实现

python 分训练集测试集 pytorch训练集和验证集_深度学习

def stop(self):
        # stop the timer and record time into a list
        self.times.append(time.time() - self.start_time) #.append用于在self.times列表中添加一个元素
        return self.times[-1]

这是一个计时器的定义片段,.append()用于在类中self.times 列表添加一个元素。

线性回归模型的从零开始的实现

w.requires_grad_(requires_grad=True) 
b.requires_grad_(requires_grad=True) #权重和偏差的初始化
# training
for epoch in range(num_epochs):  # training repeats num_epochs times
    # in each epoch, all the samples in dataset will be used once
    
    # X is the feature and y is the label of a batch sample
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  
        # calculate the gradient of batch sample loss 
        l.backward()  
        # using small batch random gradient descent to iter model parameters
        sgd([w, b], lr, batch_size)  
        # reset parameter gradient
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))

对一个数据将其属性.requires_grad设置为True,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用对结果数据调用.backward()来完成所有梯度计算,对被求导数据调用如b.grad获得以上一系列运算中关于改变量的梯度,也即此Tensor的梯度将累积到.grad属性中。需要注意的是,每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播前需要将梯度清零,操作如b.grad.data.zero_()

线性回归模型使用pytorch的简洁实现

import torch
from torch import nn
import numpy as np
torch.manual_seed(1)

print(torch.__version__)
torch.set_default_tensor_type('torch.FloatTensor')

torch.manual_seed(1)用于设计随机初始化种子的,要知道神经网络都需要初始化,那么怎么初始化才能保证每次初始化之后都相同,这时候使用同样的随机初始化种子就可以保证。
读取数据集

import torch.utils.data as Data

batch_size = 10

# combine featues and labels of dataset
dataset = Data.TensorDataset(features, labels)

# put dataset into DataLoader
data_iter = Data.DataLoader(
    dataset=dataset,            # torch TensorDataset format
    batch_size=batch_size,      # mini batch size
    shuffle=True,               # whether shuffle the data or not
    num_workers=2,              # read data in multithreading
)

torch.utils.data是pytorch用于实现数据自由读取的一个包,Data.TensorDataset用于把数据放在数据库中,Data.DataLoader用于从数据库中每次抽出batch size个样本进行训练,shuffle=True表示抽取数据是随机的。
定义模型

class LinearNet(nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()      # call father function to init 
        self.linear = nn.Linear(n_feature, 1)  # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)`

    def forward(self, x):
        y = self.linear(x)
        return y
    
net = LinearNet(num_inputs)
print(net)

首先,关于python类的一些相关知识可以查看:python类,而本例用的是一个继承类的方法,class LinearNet(nn.Module):表明线性网络是继承自nn.Module这个父类,并且在子类中如果重写了__init__ 时,要继承父类的构造方法,可以使用 super关键字,如super(子类,self).__init__(参数1,参数2,....),详情见子类继承父类初始化
如果有多层网络叠加,可以使用.Sequential()来定义模型,如下:

# ways to init a multilayer network
# method one
net = nn.Sequential(
    nn.Linear(num_inputs, 1)
    # other layers can be added here
    )

# method two
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......

# method three
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
          ('linear', nn.Linear(num_inputs, 1))
          # ......
        ]))

初始化模型参数

from torch.nn import init

init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0)  # or you can use `net[0].bias.data.fill_(0)` to modify it directly

初始化模型参数详情见pytorch nn.init参数初始化方法
定义损失函数和优化函数

loss = nn.MSELoss()    # nn built-in squared loss function
                       # function prototype: `torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')`

import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.03)   # built-in random gradient descent function
print(optimizer)  # function prototype: `torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)`

训练

num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # reset gradient, equal to net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))

训练步骤为:确定超参数(训练周期为3,优化学习率为0.03),从数据库中提取批次数据,网络输出,计算损失函数,优化器梯度清零,反向求导,优化参数。

softmax回归的神经网络及代码实现

python 分训练集测试集 pytorch训练集和验证集_线性回归_02


既然分类问题需要得到离散的预测输出,一个简单的办法是将输出值python 分训练集测试集 pytorch训练集和验证集_线性回归_03当作预测类别是python 分训练集测试集 pytorch训练集和验证集_损失函数_04的置信度,并将值最大的输出所对应的类作为预测输出,即输出 python 分训练集测试集 pytorch训练集和验证集_初始化_05。例如,如果python 分训练集测试集 pytorch训练集和验证集_深度学习_06分别为python 分训练集测试集 pytorch训练集和验证集_损失函数_07,由于python 分训练集测试集 pytorch训练集和验证集_深度学习_08最大,那么预测类别为2。

那么直接使用输出层的输出有两个问题:

1. 一方面,由于输出层的输出值的范围不确定,我们难以直观上判断这些值的意义。例如,刚才举的例子中的输出值10表示“很置信”图像类别为猫,因为该输出值是其他两类的输出值的100倍。但如果python 分训练集测试集 pytorch训练集和验证集_深度学习_09,那么输出值10却又表示图像类别为猫的概率很低。

2. 另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。

因此,引入softmax函数可以解决以上问题它通过下式将输出值变换成值为正且和为1的概率分布:

python 分训练集测试集 pytorch训练集和验证集_深度学习_10

其中

python 分训练集测试集 pytorch训练集和验证集_初始化_11

容易看出python 分训练集测试集 pytorch训练集和验证集_深度学习_12python 分训练集测试集 pytorch训练集和验证集_初始化_13,因此python 分训练集测试集 pytorch训练集和验证集_损失函数_14是一个合法的概率分布。这时候,如果python 分训练集测试集 pytorch训练集和验证集_深度学习_15,不管python 分训练集测试集 pytorch训练集和验证集_线性回归_16python 分训练集测试集 pytorch训练集和验证集_python 分训练集测试集_17的值是多少,我们都知道图像类别为猫的概率是80%。此外,我们注意到

python 分训练集测试集 pytorch训练集和验证集_损失函数_18

因此softmax运算不改变预测类别输出。
此处采用交叉熵损失函数,假设训练数据集的样本数为python 分训练集测试集 pytorch训练集和验证集_深度学习_19,交叉熵损失函数定义为
python 分训练集测试集 pytorch训练集和验证集_深度学习_20

其中python 分训练集测试集 pytorch训练集和验证集_线性回归_21代表模型参数。同样地,如果每个样本只有一个标签,那么交叉熵损失可以简写成python 分训练集测试集 pytorch训练集和验证集_初始化_22。从另一个角度来看,我们知道最小化python 分训练集测试集 pytorch训练集和验证集_深度学习_23等价于最大化python 分训练集测试集 pytorch训练集和验证集_深度学习_24,即最小化交叉熵损失函数等价于最大化训练数据集所有标签类别的联合预测概率。

获取Fashion-MNIST训练集和读取数据

这里会使用torchvision包,它是服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision主要由以下几部分构成:

  1. torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
  2. torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;
  3. torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
  4. torchvision.utils: 其他的一些有用的方法。
    get dataset
mnist_train = torchvision.datasets.FashionMNIST(root='/home/kesci/input/FashionMNIST2065', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='/home/kesci/input/FashionMNIST2065', train=False, download=True, transform=transforms.ToTensor())

class torchvision.datasets.FashionMNIST(root, train=True, transform=None, target_transform=None, download=False)

  • root(string)– 数据集的根目录,其中存放processed/training.pt和processed/test.pt文件。
  • train(bool, 可选)– 如果设置为True,从training.pt创建数据集,否则从test.pt创建。
  • download(bool, 可选)– 如果设置为True,从互联网下载数据并放到root文件夹下。如果root目录下已经存在数据,不会再次下载。
  • transform(可被调用 , 可选)– 一种函数或变换,输入PIL图片,返回变换之后的数据。如:transforms.RandomCrop。
  • target_transform(可被调用 , 可选)– 一种函数或变换,输入目标,进行变换。

其余均类似线性网络进行训练操作,不过训练过程中需要将python 分训练集测试集 pytorch训练集和验证集_深度学习_25的图像展平为python 分训练集测试集 pytorch训练集和验证集_初始化_26的数据向量进行训练。
训练过程中有个函数需要了解一下:view()函数,以二维数组为例:
a.view(-1)表示将数组a的数据展成一个行向量,a.view(-1,n)表示先确定python 分训练集测试集 pytorch训练集和验证集_深度学习_27有n列,-1处代表对应计算出的行数,同理,a.view(n,-1)表示先确定python 分训练集测试集 pytorch训练集和验证集_深度学习_27有n行,-1处代表对应计算出的列数。

多层感知机

python 分训练集测试集 pytorch训练集和验证集_线性回归_29


如果多层感知机的每层都是仿射变换的话,虽然神经网络引入了隐藏层,却依然等价于一个单层神经网络,即便再添加更多的隐藏层,以上设计依然只能与仅含输出层的单层神经网络等价,因此需要在隐藏层引入激活函数(非线性)

多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。以单隐藏层为例并沿用本节之前定义的符号,多层感知机按以下方式计算输出:

python 分训练集测试集 pytorch训练集和验证集_损失函数_30

其中python 分训练集测试集 pytorch训练集和验证集_python 分训练集测试集_31表示激活函数。

多层感知机pytorch实现

与线性回归和softmax回归最大的区别在于网络的模型:

num_inputs, num_outputs, num_hiddens = 784, 10, 256
    
net = nn.Sequential(
        d2l.FlattenLayer(),
        nn.Linear(num_inputs, num_hiddens),
        nn.ReLU(),
        nn.Linear(num_hiddens, num_outputs), 
        )
    
for params in net.parameters():
    init.normal_(params, mean=0, std=0.01)

训练

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')
loss = torch.nn.CrossEntropyLoss()

optimizer = torch.optim.SGD(net.parameters(), lr=0.5)

num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)