Lesson 13.1 深度学习建模目标与性能评估理论

  从Lesson 13起,我们讲开始系统介绍深度学习建模理论,以及建模过程中的优化方法。

二、机器学习目标与模型评估方法

  在了解深度学习基本模型的概念与实现方法后,接下来,我们将详细探讨深度学习模型优化的常用方法。从上一课的实验中不难发现,要把一个模型“建好”已是不容易,而要想办法把模型的效果进行提升,如果没有基础理论支持和方法论工具,优化过程无异于盲人摸象。因此,本节课将从建模的根本目标出发,围绕模型优化的基本概念和核心理论进行全面梳理,并在此基础之上介绍相关实践方法,逐渐拨开模型优化面前的迷雾。
  我们经常会对模型的好坏优劣进行评估,Lesson 12中我们也使用准确率、MSE等指标评估建模结果,看起来模型评估是围绕某项指标在进行评估,指标好模型就好,指标不好模型就不好,其实并不完全如此。要了解模型的性能其实并不简单,固然我们会使用某些指标去进行模型评估,但其实指标也只是我们了解模型性能的途径而不是模型性能本身。而要真实、深刻的评判模型性能,就必须首先了解机器学习的建模目标,并在此基础之上熟悉我们判断模型是否能够完成目标的一些方法,当然,只有真实了解的模型性能,我们才能进一步考虑如何提升模型性能。因此,在正式讲解模型优化方法之前,我们需要花些时间讨论机器学习算法的建模目标、机器学习算法为了能够达到目标的一般思路,以及评估模型性能的手段,也就是模型评估指标。
  无论是机器学习还是传统的统计分析模型,核心使命就是探索数字规律,而有监督学习则是希望在探索数字规律的基础上进一步对未来进行预测,当然,在数字的世界,这个预测未来,也就是预测未来某项事件的某项数值指标,如某地区未来患病人次、具备某种数字特征的图片上的动物是哪一类,此处的未来也并非指绝对意义上的以后的时间,而是在模型训练阶段暂时未接触到的数据。正是因为模型有了在未知标签情况下进行预判的能力,有监督学习才有了存在的价值,但我们知道,基本上所有的模型,都只能从以往的历史经验当中进行学习,也就是在以往的、已经知道的数据集上进行训练(如上述利用已知数据集进行模型训练,如利用过往股票数据训练时间序列模型),这里的核心矛盾在于,在以往的数据中提取出来的经验(也就是模型),怎么证明能够在接下来的数据中也具备一定的预测能力呢?或者说,要怎么训练模型,才能让模型在未知的数据集上也拥有良好的表现呢?
  目的相同,但在具体的实现方法上,传统的数理统计分析建模和机器学习采用了不同的解决方案。
  首先,在统计分析领域,我们会假设现在的数据和未来的数据其实都属于某个存在但不可获得的总体,也就是说,现在和未来的数据都是从某个总体中抽样而来的,都是这个总体的样本。而正式因为这些数据属于同一个总体,因此具备某些相同的规律,而现在挖掘到的数据规律也就在某些程度上可以应用到未来的数据当中去,不过呢,不同抽样的样本之间也会有个体之间的区别,另外模型本身也无法完全捕获规律,而这些就是误差的来源。
  虽然样本和总体的概念是统计学概念,但样本和总体的概念所假设的前后数据的“局部规律一致性”,却是所有机器学习建模的基础。试想一下,如果获取到的数据前后描绘的不是一件事情,那么模型训练也就毫无价值(比如拿着A股走势预测的时间序列预测某地区下个季度患病人次)。因此,无论是机器学习所强调的从业务角度出发,要确保前后数据描述的一致性,还是统计分析所强调的样本和总体的概念,都是建模的基础。
  在有了假设基础之后,统计分析就会利用一系列的数学方法和数理统计工具去推导总体的基本规律,也就是变量的分布规律和一些统计量的取值,由于这个过程是通过已知的样本去推断未知的总体,因此会有大量的“估计”和“检验”,在确定了总体的基本分布规律之后,才能够进一步使用统计分析模型构建模型(这也就是为什么在数理统计分析领域,构建线性回归模型需要先进行一系列的检验和变换的原因),当然,这些模型都是在总体规律基础之上、根据样本具体的数值进行的建模,我们自然有理由相信这些模型对接下来仍然是从总体中抽样而来的样本还是会具备一定的预测能力,这也就是我们对统计分析模型“信心”的来源。简单来说,就是我们通过样本推断总体的规律,然后结合总体的规律和样本的数值构建模型,由于模型也描绘了总体规律,所以模型对接下来从总体当中抽样而来的数据也会有不错的预测效果,这个过程我们可以通过下图来进行表示。
  而对于机器学习来说,并没有借助“样本-总体”的基本理论,而是简单的采用了一种后验的方法来判别模型有效性,前面说到,我们假设前后获取的数据拥有规律一致性,但数据彼此之间又略有不同,为了能够在捕捉规律的同时又能考虑到“略有不同”所带来的误差,机器学习会把当前能获取到的数据划分成训练集(trainSet)和测试集(testSet),在训练集上构建模型,然后带入测试集的数据,观测在测试集上模型预测结果和真实结果之间的差异。这个过程其实就是在模拟获取到真实数据之后模型预测的情况,此前说到,模型能够在未知标签的数据集上进行预测,就是模型的核心价值,此时的测试集就是用于模拟未来的未知标签的数据集。如果模型能够在测试集上有不错的预测效果,我们就“简单粗暴”的认为模型可以在真实的未来获取的未知数据集上有不错的表现。其一般过程可以由下图表示。

  虽然对比起数理统计分析,机器学习的证明模型有效性的过程更加“简单”,毕竟只要一次“模拟”成功,我们就认为模型对未来的数据也拥有判别效力,但这种“简单”的处理方式却非常实用,可以说,这是一种经过长期实践被证明的行之有效的方法。这也是为什么机器学习很多时候也被认为是实证类的方法,而在以后的学习中,我们也将了解到,机器学习有很多方法都是“经验总结的结果”。相比数理统计分析,确实没有“那么严谨”,但更易于理解的理论和更通用的方法,却使得机器学习可以在更为广泛的应用场景中发挥作用。(当然,负面影响却是,机器学习在曾经的很长一段时间内并不是主流的算法。)
  据此,我们称模型在训练集上误差称为训练误差,在测试集上的误差称为泛化误差,不过毕竟在测试集上进行测试还只是模拟演习,我们采用模型的泛化能力来描述模型在未知数据上的判别能力,当然泛化能力无法准确衡量(未知的数据还未到来,到来的数据都变成了已知数据),我们只能通过模型在训练集和测试集上的表现,判别模型泛化能力,当然,就像此前说的一样,最基本的,我们会通过模型在测试集上的表现来判断模型的泛化能力。

三、手动实现训练集和测试集切分

  接下来我们开始实践模型评估过程,首先是对训练集和测试集的划分,我们尝试创建一个切分训练集和测试集的函数。

def data_split(features, labels, rate=0.7):"""训练集和测试集切分函数:param features: 输入的特征张量:param labels:输入的标签张量:param rate:训练集占所有数据的比例:return Xtrain, Xtest, ytrain, ytest:返回特征张量的训练集、测试集,以及标签张量的训练集、测试集 """num_examples = len(features)                              # 总数据量indices = list(range(num_examples))                       # 数据集行索引random.shuffle(indices)                                   # 乱序调整                     num_train = int(num_examples * rate)                      # 训练集数量 indices_train = torch.tensor(indices[: num_train])        # 在已经乱序的的indices中挑出前num_train数量的行索引值indices_test = torch.tensor(indices[num_train: ])         Xtrain = features[indices_train]                          # 训练集特征ytrain = labels[indices_train]                            # 训练集标签Xtest = features[indices_test]                            # 测试集特征ytest = labels[indices_test]                              # 测试集标签return Xtrain, Xtest, ytrain, ytest

  一般来说,训练集和测试集可以按照8:2或7:3比例进行划分。在进行数据划分的过程中,如果测试集划分数据过多,参与模型训练的数据就会相应减少,而训练数据不足则会导致模型无法正常训练、损失函数无法收敛、模型过拟合等问题,但如果反过来测试集划分数据过少,则无法代表一般数据情况测试模型是否对未知数据也有很好的预测作用。因此,根据经验,我们一般来说会按照8:2或7:3比例进行划分。
  看到这里,相信肯定有小伙伴觉得根据所谓的“经验”来定数据集划分比例不太严谨,有没有一种方法能够“精准”的确定什么划分比例最佳呢?例如通过类似最小二乘法或者梯度下降这类优化算法来计算划分比例?各位同学可以尝试着进行思考,并给出自己的答案。课程中将在下一节介绍参数和超参数时给出详细解答。
  值得一提的是,在机器学习领域,充斥着大量的“经验之谈”或者“约定俗成”的规则,一方面这些经验为建模提供了诸多便捷、也节省了很多算力,但另一方面,通过经验来决定影响模型效果的一些“超参数”取值的不严谨的做法,也被数理统计分析流派所诟病。

接下来,测试函数性能

f = torch.arange(10)                # 创建特征0-9
f
#tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
l = torch.arange(1, 11)             # 创建标签1-10,保持和特征+1的关系
l
#tensor([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
data_split(f, l)
#(tensor([1, 6, 4, 5, 8, 3, 0]),
# tensor([9, 7, 2]),
# tensor([2, 7, 5, 6, 9, 4, 1]),
# tensor([10,  8,  3]))

接下来,还是在上一节课的内容上,尝试带入训练集进行建模,利用测试集评估模型建模效果

# 设置随机数种子
torch.manual_seed(420)   # 生成回归类数据集
features, labels = tensorGenReg()
#<torch._C.Generator at 0x26f6196e570>
features
#tensor([[-0.0070,  0.5044,  1.0000],
#        [ 0.6704, -0.3829,  1.0000],
#        [ 0.0302,  0.3826,  1.0000],
#        ...,
#        [-0.9164, -0.6087,  1.0000],
#        [ 0.7815,  1.2865,  1.0000],
#        [ 1.4819,  1.1390,  1.0000]])
torch.manual_seed(420) # 初始化数据
features, labels = tensorGenReg()# 切分训练集和测试集
Xtrain, Xtest, ytrain, ytest = data_split(features, labels)# 初始化核心参数
batch_size = 10                                # 小批的数量
lr = 0.03                                      # 学习率
num_epochs = 5                                 # 训练过程遍历几次数据
w = torch.zeros(3, 1, requires_grad = True)    # 随机设置初始权重# 参与训练的模型方程
net = linreg                                   # 使用回归方程
loss = MSE_loss                                # 均方误差的一半作为损失函数# 模型训练过程
for epoch in range(num_epochs):for X, y in data_iter(batch_size, Xtrain, ytrain):l = loss(net(X, w), y)l.backward()sgd(w, lr)

查看训练结果

w
#tensor([[ 2.0002],
#        [-1.0002],
#        [ 0.9996]], requires_grad=True)

查看模型在训练集、测试集上的MSE

MSE_loss(torch.mm(Xtrain, w), ytrain)
#tensor(0.0001, grad_fn=<DivBackward0>)
MSE_loss(torch.mm(Xtest, w), ytest)
#tensor(9.9141e-05, grad_fn=<DivBackward0>)

至此,我们就完成了一整个从数据集划分,到训练集训练,再到测试集上测试模型性能的一整个流程。

四、Dataset和DataLoader基本使用方法与数据集切分函数

  接下来,我们尝试使用PyTorch原生库来实现上述功能,不过这个实现过程略显复杂,首先我们需要了解Dataset和DataLoader的基本使用方法。

1.Dataset和DataLoader的基本使用方法

  • random_split随机切分函数
      首先,在PyTorch的torch.utils.data中,提供了random_split函数可用于数据集切分。
from torch.utils.data import random_split

简单测试函数功能

t = torch.arange(12).reshape(4, 3)
t
#tensor([[ 0,  1,  2],
#        [ 3,  4,  5],
#        [ 6,  7,  8],
#        [ 9, 10, 11]])
random_split(t, [2, 2])         # 输入切分的每部分数据集数量
#[<torch.utils.data.dataset.Subset at 0x15872bd9b08>,
# <torch.utils.data.dataset.Subset at 0x15872bd9c48>]

根据生成结果可知,random_split函数其实生成了生成器切分结果的生成器,并不是和此前定义的函数一样,直接切分数据后返回。当然这也符合utils.data模块主要生成映射式和迭代式对象的一般规定。

train, test = random_split(t, [2, 2])

使用print函数查看生成器内容

for tr, te in random_split(t, [2, 2]):print(tr, te)
#tensor([ 9, 10, 11]) tensor([0, 1, 2])
#tensor([3, 4, 5]) tensor([6, 7, 8])
  • Dataset和Dataloader

  由于在大多数调库建模过程中,我们都是先通过创建Dataset的子类并将数据保存为该子类类型,然后再使用DataLoader进行数据载入,因此更为通用的做法是先利用Dataset和DatasetLoader这两个类进行数据的读取、预处理和载入,然后再使用random_split函数进行切分。
  再次强调,Dataset类主要负责数据类的生成,在PyTorch中,所有数据集都是Dataset的子类;而DatasetLoader类则是加载模型训练的接口,二者基本使用流程如下:

from torch.utils.data import Dataset
from torch.utils.data import DataLoader
  • 创建数据类

  根据此前描述,PyTorch中所有的数据都是Dataset的子类,换而言之就是在使用PyTorch建模训练数据时,需要创建一个和数据集对应的类来表示该数据集,此前我们使用的TensorDataset函数其实就是一个简单的类型转化函数,将数据统一转化为“TensorDataset”类然后带入模型进行计算。

features, labels = tensorGenReg(bias=False)
features
#tensor([[ 0.5846,  0.4064, -0.7022],
#        [ 0.5943,  0.5927,  0.8111],
#        [-0.4947,  0.2168,  0.0981],
#        ...,
#        [-0.4568, -1.1319, -1.7560],
#        [-1.3112, -0.9356,  1.5156],
#        [-1.0726,  0.8814, -1.6092]])
data = TensorDataset(features, labels)
data
#<torch.utils.data.dataset.TensorDataset at 0x17be64eeb48>

而TensorDataset其实使用面较窄,最直接的限制就是该函数只能将张量类型转化为TensorDataset类

TensorDataset([1,2], 1)
#AttributeError: 'list' object has no attribute 'size'
TensorDataset?
#Init signature: TensorDataset(*args, **kwds)
#Docstring:
#Dataset wrapping tensors.#Each sample will be retrieved by indexing tensors along the first dimension.#Arguments:
#    *tensors (Tensor): tensors that have the same size of the first dimension.
#File:           d:\users\asus\anaconda3\lib\site-packages\torch\utils\data\dataset.py
#Type:           type
#Subclasses:

更加通用的数据读取方法则是手动创建一个继承自torch.utils.data.dataset的数据类,用来作为当前数据的表示。例如Lesson 11中的乳腺癌数据,通过如下方式进行读取

from sklearn.datasets import load_breast_cancer as LBC
data = LBC()

简单查看data数据集

data.data          # 返回数据集的特征数组
#array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01,
#        1.189e-01],
#       [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01,
#        8.902e-02],
#       [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01,
#        8.758e-02],
#       ...,
#       [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01,
#        7.820e-02],
#       [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01,
#        1.240e-01],
#       [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01,
#        7.039e-02]])
data.target        # 返回数据集的标签数组
#array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
#       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
#       0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0,
#       . . . . . .
#       1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
#       1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
#       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1])
len(data.data)     # 返回数据集总个数
#569

  接下来,创建一个用于表示该数据集的Dataset的子类。在创建Dataset的子类过程中,必须要重写__getitem__方法和__len__方法,其中__getitem__方法返回输入索引后对应的特征和标签,而__len__方法则返回数据集的总数据个数。当然,在必须要进行的__init__初始化过程中,我们也可输入可代表数据集基本属性的相关内容,包括数据集的特征、标签、大小等等,视情况而定。

class LBCDataset(Dataset):def __init__(self,data):                       # 创建该类时需要输入sklearn导入的数据集self.features = data.data                  # features属性返回数据集特征self.labels = data.target                  # labels属性返回数据集标签self.lens = len(data.data)                 # lens属性返回数据集大小def __getitem__(self, index):# 调用该方法时需要输入index数值,方法最终返回index对应的特征和标签return self.features[index,:],self.labels[index]    def __len__(self):# 调用该方法不需要输入额外参数,方法最终返回数据集大小return self.lensdata = LBC()
LBC_data = LBCDataset(data)LBC_data.features
#array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01,
#        1.189e-01],
#       [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01,
#        8.902e-02],
#       [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01,
#        8.758e-02],
#       ...,
#       [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01,
#        7.820e-02],
#       [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01,
#        1.240e-01],
#       [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01,
#        7.039e-02]])
LBC_data.lens
#569
# 查看第三条数据
LBC_data.__getitem__(2)
#(array([1.969e+01, 2.125e+01, 1.300e+02, 1.203e+03, 1.096e-01, 1.599e-01,
#        1.974e-01, 1.279e-01, 2.069e-01, 5.999e-02, 7.456e-01, 7.869e-01,
#        4.585e+00, 9.403e+01, 6.150e-03, 4.006e-02, 3.832e-02, 2.058e-02,
#        2.250e-02, 4.571e-03, 2.357e+01, 2.553e+01, 1.525e+02, 1.709e+03,
#        1.444e-01, 4.245e-01, 4.504e-01, 2.430e-01, 3.613e-01, 8.758e-02]),
# 0)
LBC_data.features[2]
#array([1.969e+01, 2.125e+01, 1.300e+02, 1.203e+03, 1.096e-01, 1.599e-01,
#       1.974e-01, 1.279e-01, 2.069e-01, 5.999e-02, 7.456e-01, 7.869e-01,
#       4.585e+00, 9.403e+01, 6.150e-03, 4.006e-02, 3.832e-02, 2.058e-02,
#       2.250e-02, 4.571e-03, 2.357e+01, 2.553e+01, 1.525e+02, 1.709e+03,
#       1.444e-01, 4.245e-01, 4.504e-01, 2.430e-01, 3.613e-01, 8.758e-02])
LBC_data.labels[2]
#0

封装好的数据可以直接进行索引,并且能够返回实体结果

LBC_data[1]
#(array([2.057e+01, 1.777e+01, 1.329e+02, 1.326e+03, 8.474e-02, 7.864e-02,
#        8.690e-02, 7.017e-02, 1.812e-01, 5.667e-02, 5.435e-01, 7.339e-01,
#        3.398e+00, 7.408e+01, 5.225e-03, 1.308e-02, 1.860e-02, 1.340e-02,
#        1.389e-02, 3.532e-03, 2.499e+01, 2.341e+01, 1.588e+02, 1.956e+03,
#        1.238e-01, 1.866e-01, 2.416e-01, 1.860e-01, 2.750e-01, 8.902e-02]),
# 0)
LBC_data.__getitem__(1)
#(array([2.057e+01, 1.777e+01, 1.329e+02, 1.326e+03, 8.474e-02, 7.864e-02,
#        8.690e-02, 7.017e-02, 1.812e-01, 5.667e-02, 5.435e-01, 7.339e-01,
#        3.398e+00, 7.408e+01, 5.225e-03, 1.308e-02, 1.860e-02, 1.340e-02,
#        1.389e-02, 3.532e-03, 2.499e+01, 2.341e+01, 1.588e+02, 1.956e+03,
#        1.238e-01, 1.866e-01, 2.416e-01, 1.860e-01, 2.750e-01, 8.902e-02]),
# 0)
LBC_data[:]
#(array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01,
#         1.189e-01],
#        [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01,
#         8.902e-02],
#        [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01,
#         8.758e-02],
#        ...,
#        [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01,
#         7.820e-02],
#        [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01,
#         1.240e-01],
#        [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01,
#         7.039e-02]]),
# array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
#        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
#        0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0,
#        1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0,
#        . . . . . .
#        1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1,
#        1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
#        1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
#        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1]))

另外,我们可以使用random_split方法对其进行切分

# 确定训练集、测试集大小,此处以7:3划分训练集和测试集
num_train = int(LBC_data.lens * 0.7)
num_test = LBC_data.lens - num_trainnum_train            # 训练集个数
num_test             # 测试集个数
#398
#171
LBC_train, LBC_test = random_split(LBC_data, [num_train, num_test])

注,此时切分的结果是一个映射式的对象,只有dataset和indices两个属性,其中dataset属性用于查看原数据集对象,indices属性用于查看切分后数据集的每一条数据的index(序号)。

LBC_train?
#Type:        Subset
#String form: <torch.utils.data.dataset.Subset object at 0x0000017B8803D648>
#Length:      398
#File:        d:\users\asus\anaconda3\lib\site-packages\torch\utils\data\dataset.py
#Docstring:
#Subset of a dataset at specified indices.
#
#Arguments:
#    dataset (Dataset): The whole Dataset
#    indices (sequence): Indices in the whole set selected for subset
LBC_train.dataset
#<__main__.LBCDataset at 0x7fb8ac3adb50>

通过切分结果还原原始数据集

LBC_train.dataset == LBC_data      # 还原原数据集
#True

在原始数据集中查找切分数据集

LBC_train.indices[:10]             # 抽取的训练集数据的index
#[384, 475, 64, 490, 496, 136, 123, 300, 175, 342]

当然,无论是迭代式生成数据还是映射式生成数据,都可以使用print查看数据

for i in LBC_train:print(i)break
#(array([1.328e+01, 1.372e+01, 8.579e+01, 5.418e+02, 8.363e-02, 8.575e-02,
#       5.077e-02, 2.864e-02, 1.617e-01, 5.594e-02, 1.833e-01, 5.308e-01,
#       1.592e+00, 1.526e+01, 4.271e-03, 2.073e-02, 2.828e-02, 8.468e-03,
#       1.461e-02, 2.613e-03, 1.424e+01, 1.737e+01, 9.659e+01, 6.237e+02,
#       1.166e-01, 2.685e-01, 2.866e-01, 9.173e-02, 2.736e-01, 7.320e-02]), 1)
LBC_data.__getitem__(384)           # 验证是否是LBC_train的第一条数据
#(array([1.328e+01, 1.372e+01, 8.579e+01, 5.418e+02, 8.363e-02, 8.575e-02,
#        5.077e-02, 2.864e-02, 1.617e-01, 5.594e-02, 1.833e-01, 5.308e-01,
#        1.592e+00, 1.526e+01, 4.271e-03, 2.073e-02, 2.828e-02, 8.468e-03,
#        1.461e-02, 2.613e-03, 1.424e+01, 1.737e+01, 9.659e+01, 6.237e+02,
#        1.166e-01, 2.685e-01, 2.866e-01, 9.173e-02, 2.736e-01, 7.320e-02]),
# 1)
LBC_data[LBC_train.indices][1]
#array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1,
#       1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1,
#       1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1,
#       1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
#       1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
#       1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
#       0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
#       1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1,
#       0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1,
#       1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1,
#       1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1,
#       1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0,
#       1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1,
#       0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0,
#       1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
#       0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,
#       1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1,
#       1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
#       0, 1])

还是需要强调,虽然PyTorch的数据表示形式会略显复杂,但这是应对复杂大规模数据计算之必须,面对海量、非结构化数据,我们很难去查看一条条数据,而只能通过一些数据集的特性来探索数据信息。

  然后使用DataLoader函数进行数据转化,由一般数据状态转化为“可建模”的状态。所谓“可建模”状态,指的是经过DataLoader处理的数据,不仅包含数据原始的数据信息,还包含数据处理方法信息,如调用几个线程进行训练、分多少批次等,DataLoader常用参数如下:

  • batch_size:每次迭代输入多少数据,如果是小批量梯度下降,则输入的数据量就是小批量迭代过程中“小批”的数量
  • shuffle:是否需要先打乱顺序然后再进行小批量的切分,一般训练集需要乱序,而测试集乱序没有意义
  • num_worker:启动多少线程进行计算

其他更多参数,将随着我们介绍的深入逐步进行介绍

DataLoader?train_loader = DataLoader(LBC_train, batch_size=10, shuffle=True)test_loader = DataLoader(LBC_test, batch_size=10, shuffle=False)

此处需要注意,对于测试集来说,数据装载并不是一定要进行的,如果测试集只是用于检测模型效果,有时可以不用装载直接带入计算。

同样,经过DataLoader处理后的数据也可以使用dataset属性查看原数据

train_loader.dataset
#<torch.utils.data.dataset.Subset at 0x15819156e88>
LBC_train
#<torch.utils.data.dataset.Subset at 0x15819156e88>
train_loader.dataset == LBC_train
#True

这里值得一提的是,市面上有很多教材在介绍PyTorch深度学习建模过程中的数据集划分过程,会推荐使用scikit-learn中的train_test_split函数。该函数是可以非常便捷的完成数据集切分,但这种做法只能用于单机运行的数据,并且切分之后还要调用Dataset、DataLoader模块进行数据封装和加载,切分过程看似简单,但其实会额外占用非常多的存储空间和计算资源,当进行超大规模数据训练时,所造成的影响会非常明显(当然,也有可能由于数据规模过大,本地无法运行)。因此,为了更好的适应深度学习真实应用场景,在使用包括数据切分等常用函数时,函数使用优先级是

Pytorch原生函数和类>依据张量及其常用方法手动创建的函数>Scikit-Learn函数

2.建模及评估过程

  接下来,我们尝试通过调库实现完整的数据切分、训练、查看建模结果一整个流程。

  • 数据准备过程
# 生成数据
features, labels = tensorGenReg()
features = features[:, :-1]                                  # 剔除最后全是1的列# 创建一个针对手动创建数据的数据类
class GenData(Dataset):def __init__(self, features, labels):           # 创建该类时需要输入的数据集self.features = features                    # features属性返回数据集特征self.labels = labels                        # labels属性返回数据集标签self.lens = len(features)                   # lens属性返回数据集大小def __getitem__(self, index):# 调用该方法时需要输入index数值,方法最终返回index对应的特征和标签return self.features[index,:],self.labels[index]    def __len__(self):# 调用该方法不需要输入额外参数,方法最终返回数据集大小return self.lens# 实例化对象/数据封装
data = GenData(features, labels)# 切分数据集
num_train = int(data.lens * 0.7)
num_test = data.lens - num_train
data_train, data_test = random_split(data, [num_train, num_test])# 加载数据
train_loader = DataLoader(data_train, batch_size=10, shuffle=True)
test_loader = DataLoader(data_test, batch_size=10, shuffle=False)
  • 构建模型
# 初始化核心参数
batch_size = 10                                # 小批的数量
lr = 0.03                                      # 学习率
num_epochs = 3                                 # 训练过程遍历几次数据# Stage 1.定义模型
class LR(nn.Module):def __init__(self, in_features=2, out_features=1):       # 定义模型的点线结构super(LR, self).__init__()self.linear = nn.Linear(in_features, out_features)def forward(self, x):                                    # 定义模型的正向传播规则out = self.linear(x)             return out# 实例化模型
LR_model = LR()# Stage 2.定义损失函数
criterion = nn.MSELoss()# Stage 3.定义优化方法
optimizer = optim.SGD(LR_model.parameters(), lr = 0.03)# Stage 4.模型训练与测试
def fit(net, criterion, optimizer, batchdata, epochs=3):for epoch  in range(epochs):for X, y in batchdata:yhat = net.forward(X)loss = criterion(yhat, y)optimizer.zero_grad()loss.backward()optimizer.step()
  • 模型训练与测试
fit(net = LR_model,criterion = criterion,optimizer = optimizer,batchdata = train_loader,epochs = num_epochs
)# 查看训练模型
LR_model
#LR(
#  (linear): Linear(in_features=2, out_features=1, bias=True)
#)
# 查看模型参数
list(LR_model.parameters())
#[Parameter containing:
# tensor([[ 1.9997, -0.9996]], requires_grad=True),
# Parameter containing:
# tensor([1.0005], requires_grad=True)]

查看模型在训练集上表现,首先我们可以通过dataset和indices方法还原训练数据集

data_train.indices            # 返回训练集索引
#[705,
# 223,
# 553,
# 386,
# ...
data[data_train.indices]      # 返回训练集
#(tensor([[ 1.1406,  0.4082],
#         [ 0.4877,  0.8226],
#         [-0.2876,  0.8771],
#         ...,
#         [ 2.0659,  0.6420],
#         [-0.5856,  1.1246],
#         [-0.3630, -1.5988]]),
# tensor([[ 2.8812e+00],
#         [ 1.1609e+00],
#         [-4.6634e-01],
#         [ 3.9260e-01],
#         ........
data[data_train.indices][0]      # 返回训练集的特征
#tensor([[ 1.1406,  0.4082],
#        [ 0.4877,  0.8226],
#        [-0.2876,  0.8771],
#        ...,
#        [ 2.0659,  0.6420],
#        [-0.5856,  1.1246],
#        [-0.3630, -1.5988]])
# 计算训练集MSE
F.mse_loss(LR_model(data[data_train.indices][0]), data[data_train.indices][1])
#tensor(9.4741e-05, grad_fn=<MseLossBackward>)
# 计算测试集MSE
F.mse_loss(LR_model(data[data_test.indices][0]), data[data_test.indices][1])
#tensor(0.0001, grad_fn=<MseLossBackward>)

至此,即完成了整个从数据集切分到模型训练,再到查看模型在不同数据集上表现的全过程。

五、实用函数补充

  结合上述过程,我们可以补充一些实用函数,方便简化后续建模流程。

  • 数据封装、切分和加载函数

  该函数可以直接将输入的特征和标签直接进行封装、切分和加载。该函数可以直接处理此前定义的数据生成器创建的数据。

def split_loader(features, labels, batch_size=10, rate=0.7):"""数据封装、切分和加载函数::param features:输入的特征 :param labels: 数据集标签张量:param batch_size:数据加载时的每一个小批数据量 :param rate: 训练集数据占比:return:加载好的训练集和测试集"""data = GenData(features, labels) num_train = int(data.lens * 0.7)num_test = data.lens - num_traindata_train, data_test = random_split(data, [num_train, num_test])train_loader = DataLoader(data_train, batch_size=batch_size, shuffle=True)test_loader = DataLoader(data_test, batch_size=batch_size, shuffle=False)return(train_loader, test_loader)

测试函数性能

# 设置随机数种子
torch.manual_seed(420)   # 创建数据集
features, labels = tensorGenReg()
features = features[:, :-1]  # 进行数据加载
train_loader, test_loader = split_loader(features, labels)
#<torch._C.Generator at 0x17be1691530>
# 查看第一条训练集数据
train_loader.dataset[0]
#(tensor([-1.4463, -0.6221]), tensor([-1.2863]))
len(train_loader.dataset[:][0])
#700
  • 模型训练函数

  模型训练函数并不是新的函数,此处正式对其进行定义并写入自定义模块中,方便后续调用。

def fit(net, criterion, optimizer, batchdata, epochs=3, cla=False):"""模型训练函数:param net:待训练的模型 :param criterion: 损失函数:param optimizer:优化算法:param batchdata: 训练数据集:param cla: 是否是分类问题:param epochs: 遍历数据次数"""for epoch  in range(epochs):for X, y in batchdata:if cla == True:y = y.flatten().long()          # 如果是分类问题,需要对y进行整数转化yhat = net.forward(X)loss = criterion(yhat, y)optimizer.zero_grad()loss.backward()optimizer.step()
  • MSE计算函数

  接下来,我们借助F.mse_loss,定义一个可以直接根据模型输出结果和加载后的数据计算MSE的函数。

def mse_cal(data_loader, net):"""mse计算函数:param data_loader:加载好的数据:param net: 模型:return:根据输入的数据,输出其MSE计算结果"""data = data_loader.dataset                # 还原Dataset类X = data[:][0]                            # 还原数据的特征y = data[:][1]                            # 还原数据的标签yhat = net(X)return F.mse_loss(yhat, y)train_loader.dataset[:][0]
#tensor([[-1.4463, -0.6221],
#        [-0.4742, -0.2939],
#        [ 1.9870,  0.1949],
#        ...,
#        [-1.6366, -2.1399],
#        [-1.8178, -1.4618],
#        [ 0.2646,  2.3555]])

接下来,测试函数性能。借助上述建模实验中构建的回归模型,测试函数能否顺利执行。

# 设置随机数种子
torch.manual_seed(420)   # 实例化模型
LR_model = LR()# Stage 2.定义损失函数
criterion = nn.MSELoss()# Stage 3.定义优化方法
optimizer = optim.SGD(LR_model.parameters(), lr = 0.03)# Stage 4.训练模型
fit(net = LR_model,criterion = criterion,optimizer = optimizer,batchdata = train_loader,epochs = 3
)
#<torch._C.Generator at 0x17be1691530>
LR_model
#LR(
#  (linear): Linear(in_features=2, out_features=1, bias=True)
#)
mse_cal(train_loader, LR_model)           # 计算训练误差
#tensor(0.0001, grad_fn=<MseLossBackward>)
mse_cal(test_loader, LR_model)            # 计算测试误差
#tensor(8.8412e-05, grad_fn=<MseLossBackward>)

和F.mse_loss对比

F.mse_loss(LR_model(train_loader.dataset[:][0]), train_loader.dataset[:][1])
#tensor(0.0001, grad_fn=<MseLossBackward>)
F.mse_loss(LR_model(test_loader.dataset[:][0]), test_loader.dataset[:][1])
#tensor(8.8412e-05, grad_fn=<MseLossBackward>)
  • 准确率计算函数

  类似的,定义一个分类问题的准确率计算函数,同样要求输入是加载后的数据集和训练完成的模型。

def accuracy_cal(data_loader, net):"""准确率:param data_loader:加载好的数据:param net: 模型:return:根据输入的数据,输出其准确率计算结果"""data = data_loader.dataset                # 还原Dataset类X = data[:][0]                            # 还原数据的特征y = data[:][1]                            # 还原数据的标签zhat = net(X)                             # 默认是分类问题,并且输出结果是未经softmax转化的结果soft_z = F.softmax(zhat, 1)                  # 进行softmax转化acc_bool = torch.argmax(soft_z, 1).flatten() == y.flatten()acc = torch.mean(acc_bool.float())return acc
t = torch.arange(9).reshape(3, 3).float()
t
#tensor([[0., 1., 2.],
#        [3., 4., 5.],
#        [6., 7., 8.]])
F.softmax(t, 1)
#tensor([[0.0900, 0.2447, 0.6652],
#        [0.0900, 0.2447, 0.6652],
#        [0.0900, 0.2447, 0.6652]])

接下来,测试函数性能:

# 设置随机数种子
torch.manual_seed(420)   # 创建分类数据集
features, labels = tensorGenCla()# 进行数据加载
train_loader, test_loader = split_loader(features, labels)class softmaxR(nn.Module):def __init__(self, in_features=2, out_features=3, bias=False):       # 定义模型的点线结构super(softmaxR, self).__init__()self.linear = nn.Linear(in_features, out_features)def forward(self, x):                                    # 定义模型的正向传播规则out = self.linear(x)             return out# 实例化模型和
softmax_model = softmaxR()# 定义损失函数
criterion = nn.CrossEntropyLoss()# 定义优化算法
optimizer = optim.SGD(softmax_model.parameters(), lr = lr)# 执行模型训练
fit(net = softmax_model, criterion = criterion, optimizer = optimizer, batchdata = train_loader, epochs = num_epochs, cla=True)accuracy_cal(train_loader, softmax_model)
#tensor(0.8571)
accuracy_cal(test_loader, softmax_model)
#tensor(0.8533)

至此,完成本阶段实用函数的添加。

Lesson 13.1 深度学习建模目标与性能评估理论相关推荐

  1. Lesson 12.1 深度学习建模实验中数据集生成函数的创建与使用

    Lesson 12.1 深度学习建模实验中数据集生成函数的创建与使用   为了方便后续练习的展开,我们尝试自己创建一个数据生成器,用于自主生成一些符合某些条件.具备某些特性的数据集.相比于传统的机器学 ...

  2. 一文搞定深度学习建模预测全流程(Python)

    作者 | 泳鱼 来源 | 算法进阶 本文详细地梳理及实现了深度学习模型构建及预测的全流程,代码示例基于python及神经网络库keras,通过设计一个深度神经网络模型做波士顿房价预测.主要依赖的Pyt ...

  3. 深度学习建模预测全流程(Python)!

    本文详细地梳理及实现了深度学习模型构建及预测的全流程,代码示例基于python及神经网络库keras,通过设计一个深度神经网络模型做波士顿房价预测.主要依赖的Python库有:keras.scikit ...

  4. 一文深度学习建模预测全流程(Python)

    本文详细地梳理及实现了深度学习模型构建及预测的全流程,代码示例基于python及神经网络库keras,通过设计一个深度神经网络模型做波士顿房价预测.主要依赖的Python库有:keras.scikit ...

  5. 深度学习之目标检测综述

      这里是自己这几天读过的几篇论文的笔记,美其名曰为"综述".   当年AlexNet 掀起 CNN 网络热潮,深度学习也逐渐被应用到目标检测(object detection)中 ...

  6. 【深度学习】目标检测之YOLOv3算法

    [深度学习]目标检测之YOLOv3算法 YOLO系列目标检测算法官方代码 YOLOv3 网络结构 anchor的编解码 损失函数 binary cross-entropy loss AP(Averag ...

  7. 综述 | 基于深度学习的目标检测算法

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:计算机视觉life 导读:目标检测(Object Det ...

  8. 基于深度学习的目标检测的研究进展2

    普通的深度学习监督算法主要是用来做分类,如图1(1)所示,分类的目标是要识别出图中所示是一只猫.而在ILSVRC(ImageNet Large Scale Visual Recognition Cha ...

  9. AI公开课:19.05.29 浣军-百度大数据实验室主任《AutoDL 自动化深度学习建模的算法和应用》课堂笔记以及个人感悟

    AI公开课:19.05.29 浣军 百度大数据实验室主任<AutoDL 自动化深度学习建模的算法和应用>课堂笔记以及个人感悟 导读        浣军博士,汉族,1975年出生于江苏苏州, ...

最新文章

  1. Android开发者必备的42个链接
  2. SQL语句执行顺序以及oracle基本查询优化
  3. 6-18 23:50 day15
  4. 数据结构--------------静态表的希尔排序
  5. 为何不精通C? 03 深入剖析声明
  6. 园子里关于罗辑树与视觉树的文章
  7. 【王道考研计算机网络】—速率相关的性能指标
  8. scala编程第16章学习笔记(3)——List类的高阶方法
  9. ActivityGroup和TabActiviy的差异性?
  10. 2022-渗透测试-口令破解-几款暴力破解和字典生成的工具
  11. 支付路由适配服务技术实现
  12. 【转】CC2530/2531/2533对比
  13. css 设置层级关系,css层级关系怎么设置
  14. 联想电脑拯救者y7000触摸屏失灵的修复方法
  15. python练习实例——水仙花数判断
  16. Levy distribution(列维分布)和Levy fligt(列维飞行)
  17. 总结 db visualizer连接GBase8s数据库报-908错误解决方法
  18. 年货小史:最土年货中藏着最真实的烟火气
  19. ccxt php系统,CCXT中文开发手册
  20. 流媒体基础-RTP协议

热门文章

  1. php教程数据库操作,PHP实现对文本数据库的常用操作方法实例演示_PHP教程
  2. cpu序列号能告诉别人嘛_微信这个开关不删除,别人手机能随意登录你的微信,学会告诉家人...
  3. MVC模型构建管理系统
  4. lnmp中怎么运行ngin和mysql_安装LNMP(Nginx+Mysql+PHP)
  5. Pytorch学习-torch.max()和min()深度解析
  6. linux下使用 du查看某个文件或目录占用磁盘空间的大小
  7. git push代码到远程新分支
  8. Leetcode 18. 四数之和 (每日一题 20211011)
  9. 重温强化学习之无模型学习方法:TD(lambda)
  10. 文巾解题 1816. 截断句子