文章目录
1、手动实现训练集和测试集的切分
1. data_split()函数
接下来我们开始实践模型评估过程,首先是对训练集和测试集的划分,我们尝试创建一个切分训练集和测试集的函数。
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
- 测试函数性能
features = torch.arange(10) # 创建特征0-9
features
labels = torch.arange(1, 11) # 创建标签1-10,保持和特征+1的关系
labels
data_split(features, labels)
- 实验结果:
#f
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
#l
tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
#fs
(tensor([2, 6, 3, 0, 7, 5, 9]),
tensor([1, 8, 4]),
#ls
tensor([ 3, 7, 4, 1, 8, 6, 10]),
tensor([2, 9, 5]))
2. 实践练习
尝试带入训练集进行建模,利用测试集评估模型建模效果
# 设置随机数种子
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) # 随机设置初始权重
# 2、模型构建
def linreg(X,w):
return torch.mm(X, w)
# 3、损失函数 mse
def MSE_loss(yhat, y):
total = y.numel()
sse = torch.sum((yhat.reshape(-1, 1) - y.reshape(-1, 1)) ** 2)
return sse / total
# 4、优化算法
def sgd(params, lr):
params.data -= lr * params.grad # (参数-学习率lr * 梯度)
params.grad.zero_()
# 5、训练模型
# 参与训练的模型方程
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
# 查看模型在训练集、测试集上的MSE
MSE_loss(torch.mm(Xtrain, w), ytrain)
MSE_loss(torch.mm(Xtest, w), ytest)
- 测试结果:
# w的值,方程的系数
tensor([[ 2.0008],
[-0.9999],
[ 1.0001]], requires_grad=True)
# 测试集 MSE
tensor(9.6096e-05, grad_fn=<DivBackward0>)
# 训练集 MSE
tensor(0.0001, grad_fn=<DivBackward0>)
至此,我们就完成了一整个从数据集划分,到训练集训练,再到测试集上测试模型性能的一整个流程。
2、Dataset和DataLoader
由于在大多数调库建模过程中,我们都是先通过创建Dataset的子类并将数据保存为该子类类型,然后再使用DataLoader进行数据载入,因此更为通用的做法是先利用Dataset和DatasetLoader这两个类进行数据的读取、预处理和载入,然后再使用random_split函数进行切分。
通用的数据处理流程
- 创建
Dataset
的子类并将数据保存为该子类类型- 使用
DataLoader
进行数据载入- 再使用
random_split
函数进行切分
1. random_split随机切分函数
首先,在PyTorch的torch.utils.data
中,提供了random_split
函数可用于数据集切分。
from torch.utils.data import random_split
# 简单测试函数功能
t = torch.arange(12).reshape(4, 3)
train, test = random_split(t, [3, 1]) # 切两份,分别3,1
len(train), len(test)
- 测试结果:
3, 1
根据生成结果可知,random_split
函数其实生成了生成器切分结果的生成器,并不是和此前定义的函数一样,直接切分数据后返回。当然这也符合utils.data模块主要生成映射式和迭代式对象的一般规定。
2. Dataset 封装数据
Dataset
类主要负责数据类的生成,在PyTorch中,所有数据集都是Dataset的子类;- 而
DatasetLoader
类则是加载模型训练的接口,二者基本使用流程如下:
根据此前描述,PyTorch中所有的数据都是Dataset的子类,使用PyTorch建模训练数据时,需要创建一个和数据集对应的类来表示该数据集。有两种方式: - 直接调用TensorDataset类创建
- 手动创建一个继承自torch.utils.data.dataset的数据类。
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
(1)TensorDataset类的数据集
此前我们使用的TensorDataset函数其实就是一个简单的类型转化函数,将数据统一转化为“TensorDataset”类然后带入模型进行计算。但是TensorDataset其实使用面较窄,该函数只能将张量类型转化为TensorDataset类。
from torch.utils.data import Dataset,TensorDataset
features, labels = tensorGenReg(bias=False)
data = TensorDataset(features, labels) # 生成TensorDataset类数据集
data
测试结果:
<torch.utils.data.dataset.TensorDataset at 0x2a94c912d00>
(2)自定义dataset数据类的数据集
- 查看乳腺癌数据集LBC的基本信息
from sklearn.datasets import load_breast_cancer as LBC
data = LBC()
# 简单查看data数据集
data.data # 返回数据集的特征数组
data.target # 返回数据集的标签数组
len(data.data) # 返回数据集总个数
- 手动创建数据集类
重写两个方法__getitem__
和__len__
,并在初始化__init__
方法中调用数据集的基本信息,如数据集的特征、标签、大小等。
# 自定义数据集类
class LBCDataset(Dataset):
def __init__(self,data): # 导入的数据集 data
self.features = data.data # 返回数据集特征
self.labels = data.target # 返回数据集标签
self.lens = len(data.data) # 返回数据集大小
def __getitem__(self, index):
# 返回index对应的特征和标签
return self.features[index,:],self.labels[index]
def __len__(self):
# 返回数据集大小
return self.lens
# 测试效果
from sklearn.datasets import load_breast_cancer as LBC
# 加载数据及读取
data = LBC()
LBC_data = LBCDataset(data) # 自定义的数据集类,读取数据
# 测试效果
LBC_data.features # 数据集的特征
LBC_data.lens # 数据集的大小
# 查看第三条数据
LBC_data.__getitem__(2)
LBC_data.features[2]
LBC_data.labels[2]
# 封装好的数据可以直接进行索引,并且能够返回实体结果
LBC_data[:]
- random_split 方法切分数据集
# 确定训练集、测试集大小,此处以7:3划分训练集和测试集
num_train = int(LBC_data.lens * 0.7)
num_test = LBC_data.lens - num_train
num_train # 训练集个数
num_test # 测试集个数
LBC_train, LBC_test = random_split(LBC_data, [num_train, num_test])
- 数据集的两个属性:
dataset
和indices
,前者用于查看切分前的原数据集对象,后者用于切分后的数据集索引。
LBC_train?
LBC_train.dataset # LBC_data数据集
# 通过切分结果还原原始数据集
LBC_train.dataset == LBC_data # 还原原数据集
# 在原始数据集中查找切分数据集
LBC_train.indices
LBC_train.indices[:10] # 训练集的index
# 当然,无论是迭代式生成数据还是映射式生成数据,都可以使用print查看数据
for i in LBC_train:
print(i)
break
LBC_data.__getitem__(384) # LBC_data的第384条数据
LBC_data[LBC_train.indices][1] # LBC_data的训练集的所有标签
3. DataLoader 数据加载
然后使用DataLoader函数进行数据转化,由一般数据状态转化为“可建模”的状态,包含数据原始的数据,数据处理方法,如调用几个线程进行训练、分多少批次等。
- batch_size:每次迭代输入多少数据,如果是小批量梯度下降,则输入的数据量就是小批量迭代过程中“小批”的数量
- shuffle:是否需要先打乱顺序然后再进行小批量的切分,一般训练集需要乱序,而测试集乱序没有意义
- num_worker:启动多少线程进行计算
train_loader = DataLoader(LBC_train, batch_size=10, shuffle=True)
test_loader = DataLoader(LBC_test, batch_size=10, shuffle=False)
这里值得一提的是,市面上有很多教材在介绍PyTorch深度学习建模过程中的数据集划分过程,会推荐使用scikit-learn中的
train_test_split
函数。该函数是可以非常便捷的完成数据集切分,但这种做法只能用于单机运行的数据,并且切分之后还要调用Dataset、DataLoader模块进行数据封装和加载,切分过程看似简单,但其实会额外占用非常多的存储空间和计算资源,当进行超大规模数据训练时,所造成的影响会非常明显(当然,也有可能由于数据规模过大,本地无法运行)。因此,为了更好的适应深度学习真实应用场景,在使用包括数据切分等常用函数时,函数使用优先级是
Pytorch原生函数和类 > 依据张量及其常用方法手动创建的函数 > Scikit-Learn函数
3、完整的建模与评估过程
接下来,我们尝试通过调库实现完整的数据切分、训练、查看建模结果一整个流程。
1. 数据准备
# 生成数据
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对应的特征和标签
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)
2. 构建模型
# 初始化核心参数
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()
criterion = nn.MSELoss()
optimizer = optim.SGD(LR_model.parameters(), lr = 0.03)
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()
3. 模型训练与测试
fit(net = LR_model,
criterion = criterion,
optimizer = optimizer,
batchdata = train_loader,
epochs = num_epochs
)
# 查看训练模型
LR_model
# 查看模型参数
list(LR_model.parameters())
# 查看模型在训练集上表现,首先我们可以通过dataset和indices方法还原训练数据集
data_train.indices # 训练集的索引
data[data_train.indices] # 训练集
data[data_train.indices][0] # 训练集的特征
# 计算训练集 MSE
F.mse_loss(LR_model(data[data_train.indices][0]), data[data_train.indices][1])
# 计算测试集 MSE
F.mse_loss(LR_model(data[data_test.indices][0]), data[data_test.indices][1])
4、自定义的实用函数
1. GenData 自定义数据集类
# 自定义数据集类,封装数据
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对应的特征和标签
return self.features[index,:],self.labels[index]
def __len__(self):
# 返回数据集大小
return self.lens
2. split_loader() 数据封装、切分和加载函数
def split_loader(features, labels, batch_size=10, rate=0.8):
"""数据封装、切分和加载函数:
:param features:输入的特征
:param labels: 数据集标签张量
:param batch_size:数据加载时的每一个小批数据量
:param rate: 训练集数据占比
:return:加载好的训练集和测试集
"""
data = GenData(features, labels)
num_train = int(data.lens * rate)
num_test = data.lens - num_train
data_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)
# 查看第一条训练集数据
train_loader.dataset[0]
len(train_loader.dataset[:][0])
3. fit() 模型训练函数
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()
4. 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)
- 测试函数性能。
借助上述建模实验中构建的回归模型,测试函数能否顺利执行。
# 设置随机数种子
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
)
LR_model
mse_cal(train_loader, LR_model) # 计算训练误差
mse_cal(test_loader, LR_model) # 计算测试误差
# 和F.mse_loss对比
F.mse_loss(LR_model(train_loader.dataset[:][0]), train_loader.dataset[:][1])
F.mse_loss(LR_model(test_loader.dataset[:][0]), test_loader.dataset[:][1])
测试结果:
LR(
(linear): Linear(in_features=2, out_features=1, bias=True)
)
# mse_cal函数的结果
tensor(0.0001, grad_fn=<MseLossBackward0>)
tensor(8.9603e-05, grad_fn=<MseLossBackward0>)
# 直接计算的结果
tensor(0.0001, grad_fn=<MseLossBackward0>)
tensor(8.9603e-05, grad_fn=<MseLossBackward0>)
5. 准确率计算函数
类似的,定义一个分类问题的准确率计算函数,同样要求输入是加载后的数据集和训练完成的模型。
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
- 测试函数性能
# 设置随机数种子
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)
accuracy_cal(test_loader, softmax_model)
测试结果:
tensor(0.8438)
tensor(0.8444)