Lesson 12. 前言
尽管深度学习也属于机器学习范畴,基本的建模理念和机器学习类似,比如需要确定目标函数和损失函数、找到合适的优化算法对参数进行求解等等,但利用PyTorch在进行建模的过程却和和最大的机器学习库Scikit-Learn中定义的机器学习建模方法有较大的差别。
- 在数据读取过程中,PyTorch需要将数据先封装在一个Dataset的子类里面,然后再用DataLoader进行装载,然后才能带入训练,而sklearn则可以直接读取Pandas中存储的面板数据进行建模;
- 在模型调用的过程,PyTorch需要先创建一个Module的子类去定义模型基本结构,然后才能实例化这个模型进行训练,并且训练过程中优化算法也是某个类的实例化结果,在训练过程中需要实用这个类的诸多方法来将梯度清零或者更新神经元之间连接的权重。相比之下sklearn则简单的多,只需要在实例化模型的过程中定义好超参数的取值,然后实用fit方法进行训练即可。
那为何PyTorch的整个实现过程看似会更加复杂?归根结底,还是和深度学习建模的特殊性有关。
类似PyTorch的这种,看似对初学者略显复杂的建模流程,实际上都是为了能够更好的满足深度学习建模的一般情况:针对非结构化数据、在超大规模的数据集上进行模型训练。
- PyTorch在读取数据的过程中需要使用Dataset和DataLoader数据进行封装和加载,其实是为了能够实现数据的迭代式存储和映射式存储,也就是通过生成数据的生成器或者保存数据的映射关系,来避免数据的重复存储
- 如进行小批量数据划分时,PyTorch并未真正意义上的把数据进行切分然后单独存储,而是创建了每个“小批”数据和原数据的映射关系(或者说“小批”数据的索引值),然后借助这种映射关系,在实际需要使用这些数据的时候在对其进行提取。因为当进行海量数据处理时,划分多个数据集进行额外的存储显然是不合适的。
但不管怎样,这样的一个建模流程还是给很多初学者造成一定的学习难度,然而熟练掌握深度学习建模流程、熟练使用基本函数和类却是后续学习的基础,因此,在正式进入到下个阶段之前,也就是正式进入到深度学习优化算法学习之前,我们需要进行一段时间的强化练习,通过对此前介绍的基础神经网络进行手动建模实现和调库实现,强化代码能力。
此外,在前几节课的学习当中我们也发现,PyTorch作为新兴的深度学习计算框架,在某些功能实现上还显得不够完善,比如此前我们看到的将“概率”结果划为类别判别的过程、准确率计算过程等等,PyTorch中都没有提供原生的函数作为支持,因此我们需要手动编写此类实用函数。外加模型训练过程本身也可以封装在函数内,因此本节我们也将手动编写PyTorch实际应用中的实用函数作为nn.functional的补充。
另外,为了在后续的优化算法部分课程中更好的观察模型不同优化算法能够起到的作用,本节课程还将介绍数据集创建函数、模型可视化工具TensorBoard安装和实用方法;同时,虽然是建模练习,但可能会涉及一定规模的运算,因此我们还将在本节还将介绍模型的GPU运行方法。
# 导包
import random
import matplotlib as mpl
from matplotlib import pyplot
import matplotlib.pyplot as plt
import numpy as np
import torch
from torch import nn,optim
import torch.nn.functional as F
from torch.utils.data import Dataset,TensorDataset,DataLoader
# 自定义模块
from torchLearning import *
# 导入以下包从而使得可以在jupyter中的一个cell输出多个结果
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
Lesson 12.1 数据集创建函数
1、回归类数据集手动创建方法
回归类模型的数据,特征和标签都是连续型数值。(1)生成两个特征、存在偏差,自变量和因变量存在线性关系的数据集(2)生成非线性关系的数据集,此处我们创建满足 y = x 2 + 1 y=x^2+1 y=x2+1规律的数据集。
- 线性关系数据集:满足y = 2 * x1 - x2 + 1
# 回归类数据集 手动创建
# 数据生成
num_inputs = 2 # 特征
num_examples = 1000 # 样本
# 线性方程
torch.random.manual_seed(420)
w_true = torch.tensor([2,-1]).reshape(-1,1).float()
b_true = torch.tensor(1).float()
# 线性关系计算真实标签值
features = torch.randn(num_examples, num_inputs)
labels_true = torch.mm(features, w_true) + b_true
# 添加噪声
labels = labels_true + torch.randn(size = labels_true.shape) * 0.01 # delta = 0.01, 控制扰动项大小,扰动项越大,线性关系越弱
# 可视化展示
plt.subplot(221)
plt.scatter(features[:, 0], labels) # 第一个特征和标签的关系
plt.subplot(222)
plt.plot(features[:, 1], labels, 'ro') # 第二个特征和标签的关系
- 非线性关系数据集:y=x**2+1
# 设置随机数种子
torch.manual_seed(420)
num_inputs = 2 # 两个特征
num_examples = 1000 # 总共一千条数据
# 线性方程系数
w_true = torch.tensor(2.)
b_true = torch.tensor(1.)
# 特征和标签取值
features = torch.randn(num_examples, num_inputs)
labels_true = torch.pow(features, 2) * w_true + b_true
labels = labels_true + torch.randn(size = labels_true.shape) * 0.1
# 可视化展示
plt.scatter(features, labels)
2、回归类数据集创建函数
为了方便后续使用,我们将上述过程封装在一个函数内。
# 回归类数据集创建函数
# 默认数据集:生成1000个样本,y=2*x1-x2+1,噪声项为0.01
def tensorGenReg(num_examples = 1000, w = [2, -1, 1], bias = True, delta = 0.01, deg = 1):
"""回归类数据集创建函数。
:param num_examples: 创建数据集的数据量
:param w: 包括截距的(如果存在)特征系数向量
:param bias:是否需要截距
:param delta:扰动项取值
:param deg:方程次数
:return: 生成的特征张和标签张量
"""
if bias == True:
num_inputs = len(w)-1 # 特征张量
features_true = torch.randn(num_examples, num_inputs) # 不包含全是1的列的特征张量
w_true = torch.tensor(w[:-1]).reshape(-1, 1).float() # 自变量系数
b_true = torch.tensor(w[-1]).float() # 截距
if num_inputs == 1: # 若输入特征只有1个,则不能使用矩阵乘法
labels_true = torch.pow(features_true, deg) * w_true + b_true
else:
labels_true = torch.mm(torch.pow(features_true, deg), w_true) + b_true
features = torch.cat((features_true, torch.ones(len(features_true), 1)), 1) # 在特征张量的最后添加一列全是1的列
labels = labels_true + torch.randn(size = labels_true.shape) * delta
else:
num_inputs = len(w)
features = torch.randn(num_examples, num_inputs)
w_true = torch.tensor(w).reshape(-1, 1).float()
if num_inputs == 1:
labels_true = torch.pow(features, deg) * w_true
else:
labels_true = torch.mm(torch.pow(features, deg), w_true)
labels = labels_true + torch.randn(size = labels_true.shape) * delta
return features, labels
注:上述函数无法创建带有交叉项的方程
- 测试函数性能
(1)线性关系,噪声项
# 生成1000个样本,y=2*x1-x2+1,噪声项为0.01
features, labels = tensorGenReg(num_examples = 1000, w = [2, -1, 1], bias = True, delta = 0.01, deg = 1)
# 生成1000个样本,y=2*x1-x2+1,噪声项为2
features2, labels2 = tensorGenReg(num_examples = 1000, w = [2, -1, 1], bias = True, delta = 2, deg = 1)
# 可视化展示
# 噪声项为0.01
plt.subplot(221)
plt.scatter(features[:, 0], labels) # 第一个特征和标签的关系
plt.subplot(222)
plt.plot(features[:, 1], labels, 'ro') # 第二个特征和标签的关系
# 噪声项为2
plt.subplot(223)
plt.scatter(features2[:, 0], labels2) # 第一个特征和标签的关系
plt.subplot(224)
plt.plot(features2[:, 1], labels2, 'ro') # 第二个特征和标签的关系
(2)二阶非线性关系
# 生成1000个样本,y=2*x1**2-x2**2+1,二阶非线性关系(两个特征)
features, labels = tensorGenReg(num_examples = 1000, w = [2, -1, 1], bias = True, delta = 0.01, deg = 2)
# 生成1000个样本,y= x1**2 ,二阶非线性关系(一个特征,无偏置b)
features3, labels3 = tensorGenReg(num_examples = 1000, w = [1], bias = False, delta = 0.01, deg = 2)
# 可视化展示
plt.subplot(221)
plt.scatter(features[:, 0], labels) # 第一个特征x1和标签的关系
plt.subplot(222)
plt.plot(features[:, 1], labels, 'ro') # 第二个特征x2和标签的关系
plt.subplot(223)
plt.plot(features3, labels3, 'yo') # y= x1**2 关系
3、分类数据集手动创建方法
和回归模型的数据不同,分类模型数据的标签是离散值。
- 拥有两个特征的三分类的数据集
尝试创建一个拥有两个特征的三分类的数据集,每个类别包含500条数据,并且第一个类别的两个特征都服从均值为4、标准差为2的正态分布,第二个类别的两个特征都服从均值为-2、标准差为2的正态分布,第三个类别的两个特征都服从均值为-6、标准差为2的正态分布,创建过程如下:
# 设置随机数种子
torch.manual_seed(420)
# 创建初始标记值
num_inputs = 2 # 特征数目
num_examples = 500 # 样本数目
# 创建自变量簇 x
data0 = torch.normal(4, 2, size=(num_examples, num_inputs)) # 类别1:Mean,Std(4,2)的正态分布
data1 = torch.normal(-2, 2, size=(num_examples, num_inputs)) # 类别2:Mean,Std(-2,2)的正态分布
data2 = torch.normal(-6, 2, size=(num_examples, num_inputs)) # 类别3:Mean,Std(-6,2)的正态分布
# 创建标签 y
label0 = torch.zeros(500)
label1 = torch.ones(500)
label2 = torch.full_like(label1, 2)
# 合并生成最终数据
features = torch.cat((data0, data1, data2)).float()
labels = torch.cat((label0, label1, label2)).long().reshape(-1, 1) # 分类问题默认标签类型为整型
# 可视化展示
plt.scatter(features[:, 0], features[:, 1], c = labels)
4、分类数据集创建函数
同样,我们将上述创建分类函数的过程封装为一个函数。(均值,方差)变量可以控制数据整体离散程度,也就是后续建模分类的难以程度。如果每个分类数据集中心点较近(均值越小)、且每个类别的点内部方差较大(方差越大),则数据集整体离散程度较高(离散越高),反之离散程度较低。
# 分类数据集创建函数
# 默认数据集:特征2个,三分类,方差及均值为4,2,500样本。
def tensorGenCla(num_examples = 500, num_inputs = 2, num_class = 3, deg_dispersion = [4, 2], bias = False):
"""分类数据集创建函数。
:param num_examples: 每个类别的数据数量
:param num_inputs: 数据集特征数量
:param num_class:数据集标签类别总数
:param deg_dispersion:数据分布离散程度参数,需要输入一个列表,其中第一个参数表示每个类别数组均值的参考、第二个参数表示随机数组标准差。
:param bias:建立模型逻辑回归模型时是否带入截距
:return: 生成的特征张量和标签张量,其中特征张量是浮点型二维数组,标签张量是长正型二维数组。
"""
cluster_l = torch.empty(num_examples, 1) # 每一类标签张量的形状
mean_ = deg_dispersion[0] # 每一类特征张量的均值的参考值
std_ = deg_dispersion[1] # 每一类特征张量的方差
lf = [] # 用于存储每一类特征张量的列表容器
ll =