pytorch-13_1 深度学习之数据准备

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])
  • 数据集的两个属性:datasetindices,前者用于查看切分前的原数据集对象,后者用于切分后的数据集索引。
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)
  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白白白飘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值