4.4 PyTorch迭代过程中的评估

欢迎订阅本专栏:《PyTorch深度学习实践》
订阅地址:https://blog.csdn.net/sinat_33761963/category_9720080.html

  • 第二章:认识Tensor的类型、创建、存储、api等,打好Tensor的基础,是进行PyTorch深度学习实践的重中之重的基础。
  • 第三章:学习PyTorch如何读入各种外部数据
  • 第四章:利用PyTorch从头到尾创建、训练、评估一个模型,理解与熟悉PyTorch实现模型的每个步骤,用到的模块与方法。
  • 第五章:学习如何利用PyTorch提供的3种方法去创建各种模型结构。
  • 第六章:利用PyTorch实现简单与经典的模型全过程:简单二分类、手写字体识别、词向量的实现、自编码器实现。
  • 第七章利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。
  • 第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可视化、多个GPU并行计算。

在之前几节,我们在训练过程在打印出了每次epoch的损失,但这是训练集的损失,我们需要获得验证集上的损失才能做更公正的评估。

因此我们本节介绍将数据集分割为训练集与验证集,让模型在训练集上训练,在验证集上给出每次迭代的评估。

关于训练集、验证集、测试集,为何分割,如何分割的理论知识此处不做展开。这里只简单介绍在pytorch中如何进行训练中的评估。

4.4.1 分割数据集

先来看看分割数据集的步骤:

import torch
import torch.optim as optim

# x与y,还是老例子
x = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
y = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
x = torch.tensor(x)
y = torch.tensor(y)


n_sample = x.shape[0]  # 总样本数
n_val = int(0.2 * n_sample)

shuffled_indices = torch.randperm(n_sample) # 获取随机正整数

train_indices = shuffled_indices[: -n_val] # 训练集的索引
val_indices = shuffled_indices[-n_val:] # 验证集的索引

train_x = 0.1 * x[train_indices]  # 别忘了乘以0.1缩小x规模
train_y = y[train_indices]

val_x = 0.1 * x[val_indices]
val_y = y[val_indices]

4.4.2 训练与评估

现在我们用训练集进行训练模型,用验证集进行评估每次迭代后的损失。

# 构建模型函数
def model(x, w, b):
    return w*x + b

# 构建损失函数
def loss_fn(y_p, y):
    squared_diffs = (y_p - y) ** 2
    return squared_diffs.mean()

def training_loop(n_epochs, optimizer, params, train_x, val_x, train_y, val_y):
    for epoch in range(1, n_epochs+1):
        # 训练集的前向
        train_y_p = model(train_x, *params)
        train_loss = loss_fn(train_y_p, train_y)
        
        # 验证集的前向
        val_y_p = model(val_x, *params)
        val_loss = loss_fn(val_y_p, val_y)
        
        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()
        
        if epoch % 100 == 0:
            print('Epoch {}, Train Loss {}, Val Loss {}'.format(epoch, float(train_loss), float(val_loss)))
        
    return params

params = torch.tensor([1.0, 0.0], requires_grad=True)
learning_rate = 1e-2
optimizer = optim.SGD([params], lr=learning_rate)

training_loop(
    n_epochs = 1000,
    optimizer = optimizer,
    params = params,
    train_x = train_x,
    val_x = val_x,
    train_y = train_y,
    val_y = val_y
)
Epoch 100, Train Loss 39.48476028442383, Val Loss 82.94966888427734
Epoch 200, Train Loss 18.64116668701172, Val Loss 42.926963806152344
Epoch 300, Train Loss 12.16136646270752, Val Loss 27.725482940673828
Epoch 400, Train Loss 10.144084930419922, Val Loss 21.16741371154785
Epoch 500, Train Loss 9.51606559753418, Val Loss 18.106447219848633
Epoch 600, Train Loss 9.320556640625, Val Loss 16.584747314453125
Epoch 700, Train Loss 9.259687423706055, Val Loss 15.793700218200684
Epoch 800, Train Loss 9.240734100341797, Val Loss 15.370372772216797
Epoch 900, Train Loss 9.234842300415039, Val Loss 15.139771461486816
Epoch 1000, Train Loss 9.232999801635742, Val Loss 15.012871742248535





tensor([18.1260, 32.0774], requires_grad=True)

4.4.3 关闭梯度

上面的过程固然暂时没有问题。但是还存在一个隐患。

还记得初始化参数params时设置了requires_grad=True, 即在params上的所有后续操作都会被跟踪和记录,而在验证集上只是做了前向计算,并须需要计算和记录梯度,当params很小时这无所谓,但真实业务中往往回有几百万的参数,如果它们在验证集上的操作都被记录,会导致大量内存的消耗。

基于此,pytorch给出了对某些操作关闭梯度的功能:torch.no_grad,对以上training_loop代码做如下修改:

def training_loop(n_epochs, optimizer, params, train_x, val_x, train_y, val_y):
    for epoch in range(1, n_epochs+1):
        # 训练集的前向
        train_y_p = model(train_x, *params)
        train_loss = loss_fn(train_y_p, train_y)
        
        # 验证集的前向
        with torch.no_grad():  # 注意这里
            val_y_p = model(val_x, *params)
            val_loss = loss_fn(val_y_p, val_y)
            assert val_loss.requires_grad == False
        
        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()
        
        if epoch % 100 == 0:
            print('Epoch {}, Train Loss {}, Val Loss {}'.format(epoch, float(train_loss), float(val_loss)))
        
    return params

以上对不需要梯度的代码部分设置了特定的环境,同理,也可以对需要梯度的代码建立单独的环境:

def calc_forward(x, y, is_train):
    with torch.set_grad_enabled(is_train):
        y_p = model(x, *params)
        loss = loss_fn(y_p, y)
    return loss
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页