PyTorch学习记录002-神经网络

代码中的变量定义皆是同前面笔记相同

1.PyTorch的nn模块

  神经网络的结构与原理相信大家已经非常熟悉,这里不再赘述。
  PyTorch有一个专门用于神经网络的完整子模块:torch.nn。该子模块包含创建各种神经网络体系结构所需的构建块。这些构建块在PyTorch术语中称为module(模块),在其他框架中称为layer(层)。
  PyTorch模块都是从基类nn.Module继承而来的Python类。模块可以具有一个或多个参数(Parameter)实例作为属性,这些参数就是在训练过程中需要优化的张量(在笔记1的线性模型中即w和b)。模块还可以具有一个或多个子模块(nn.Module的子类)属性,并且也可以追踪其参数。
  可以找到一个名为nn.Linear的nn.Module子类,它对其输入进行仿射变换(通过参数属性weight和bias);它就相当于之前在笔记1中的温度计实验中实现的方法。现在,从上次中断的地方开始,将之前的代码转换为使用nn的形式。
import torch.nn as nn

linear_model = nn.Linear(1, 1) # 参数: input size, output size, bias(默认True)
linear_model(t_un_val) # t_un_val为切分出的验证集,并经过了normalization
  nn.Linear的构造函数接受三个参数:输入特征的数量,输出特征的数量以及线性模型是否包含偏差(此处默认为True)。例如,如果在输入中同时使用了温度和气压,则在其中输入具有两个特征输入和而输出只有一个特征。
  PyTorch的nn.Module及其子类被设计为可以同时处理多个样本。为了容纳多个样本,模型希望输入的第0维为这个批次中的样本数目。 因此,假设你需要对10个样本运行nn.Linear,则可以创建大小为 B x Nin 的输入张量,其中 B 是批次的大小,而 Nin 是输入特征的数量,然后在模型中同时运行:
x = torch.ones(10, 1)
linear_model(x)
  下图显示了批处理图像数据的类似的情况。输入尺寸为 BxCxHxW,其中批处理大小(batch size)B为3(狗、鸟和汽车的图像),每张图像通道数C为3(红色,绿色和蓝色),高度 H 和宽度 W 的像素数未指定。

  输出是大小为 B x Nout 的张量,其中 Nout 是输出特征的数量——在此例中为四个。
  进行此批处理的原因是多方面的。一个主要动机是以使我们充分利用执行计算的计算资源。GPU是高度并行化的,因此在小型模型上的单个输入将使大多数计算单元处于空闲状态。通过提供成批的输入,可以将计算分散到其他闲置的计算单元上,这意味着成批的结果就像单个结果一样能够很快地返回。另一个好处是,某些高级模型将使用整个批次的统计信息,而当批次大小较大时那些统计数据将变得更准确。
  回到之前的线性模型,需要将尺寸为 B 的输入reshape为 B x Nin,其中Nin为1。
t_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c).unsqueeze(1) # 增加一维
t_u = torch.tensor(t_u).unsqueeze(1) # 
  将之前的手工模型替换为nn.Linear(1,1),然后将线性模型参数传递给优化器:
linear_model = nn.Linear(1, 1)
optimizer = optim.SGD(
    linear_model.parameters(), # 获取任何nn.Module或其子模块的参数列表
    lr=1e-2)
  我们为优化器提供了一个require_grad = True张量列表。所有参数都是用这种方式定义的,因为它们需要通过梯度下降进行优化。调用raining_loss.backward()时,grad将累积在图的叶节点上,这些节点正是传递给优化器的参数。
  调用optimizer.step()时,优化器将循环访问每个参数,并按与存储在其grad属性中的值成比例的量对其进行更改。
  现在你不再明确地将params传递给model,因为model本身在内部保存有Parameters。
  下面来看看修改后的完整代码:
import torch
import torch.optim as optim
import torch.nn as nn


def training_loop(n_epochs, optimizer, model, loss_fn,
                  t_u_train, t_u_val, t_c_train, t_c_val):
    for epoch in range(1, n_epochs+1):
        t_p_train = model(t_u_train)
        loss_train = loss_fn(t_p_train, t_c_train)
        
        t_p_val = model(t_u_val)
        loss_val = loss_fn(t_p_val, t_c_val)
        
        optimizer.zero_grad()
        loss_train.backward()
        optimizer.step()
        
        if epoch == 1 or epoch % 1000 == 0:
            print('Epoch %d, Training loss %.4f, Validation loss %.4f' % (
                    epoch, float(loss_train), float(loss_val)))
            

if __name__ == "__main__":
    t_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0] #实际结果(摄氏度)
    t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4] #温度计读取的数值
    t_c = torch.tensor(t_c).unsqueeze(1) # 将数据增加一维
    t_u = torch.tensor(t_u).unsqueeze(1) 
    # 划分训练集与测试集
    n_samples = t_u.shape[0]
    n_val = int(0.2 * n_samples)  #切出20%为测试集   
    shuffled_indices = torch.randperm(n_samples)
    train_indices = shuffled_indices[:-n_val]
    val_indices = shuffled_indices[-n_val:]
    train_t_u = t_u[train_indices]
    train_t_c = t_c[train_indices]    
    val_t_u = t_u[val_indices]
    val_t_c = t_c[val_indices] 
    t_un_train = 0.1 * train_t_u
    t_un_val = 0.1 * val_t_u
	# 模型
    linear_model = nn.Linear(1, 1)# 参数:input size, output size, bias(默认True)
	# 优化器
    optimizer = optim.SGD(
        linear_model.parameters(),
        lr = 1e-2)
	# 训练循环
    training_loop(
        n_epochs = 3000,
        optimizer = optimizer,
        model = linear_model,
        loss_fn = nn.MSELoss(), # 不再使用自己定义的loss函数
        t_u_train = t_un_train,
        t_u_val = t_un_val,
        t_c_train = train_t_c,
        t_c_val = val_t_c)
  还差最后一个步骤:用神经网络代替线性模型作为近似函数。
  nn提供了一种通过nn.Sequential容器串联模块的简单方法:
seq_model = nn.Sequential(
            nn.Linear(1, 13),
            nn.Tanh(),
            nn.Linear(13, 1))
  该模型将1个输入特征散开为13个隐藏特征,然后将通过tanh激活函数,最后将得到的13个数字线性组合为1个输出特征。
  有关nn.Modules参数的一些注意事项:当你检查由几个子模块组成的模型的参数时,可以方便地通过其名称识别参数。这个方法叫做named_parameters:
for name, param in seq_model.named_parameters():
    print(name, param.shape)

输出:

0.weight torch.Size([13, 1])
0.bias torch.Size([13])
2.weight torch.Size([1, 13])
2.bias torch.Size([1])
  这些都是优化器所需的参数张量。同样,在调用model.backward()之后,所有参数都将被计算其grad,然后优化器会在调用optimizer.step()期间更新参数的值,这与之前的线性模型没有太大不同。毕竟这两个模型都是可微分的模型,可以通过梯度下降进行训练。

  实际上,Sequential中每个模块的名称都是该模块在参数中出现的顺序。有趣的是,Sequential还可以接受OrderedDict作为参数,这样就可以给Sequential的每个模块命名:
from collections import OrderedDict
seq_model = nn.Sequential(OrderedDict([
        ('hidden_linear', nn.Linear(1, 8)),
        ('hidden_activation'), nn.Tanh(),
        ('output_linear', nn.Linear(8, 1))
]))
for name, param in seq_model.named_parameters():
    	print(name, param.shape)

输出:

hidden_linear.weight torch.Size([8, 1])
hidden_linear.bias torch.Size([8])
output_linear.weight torch.Size([1, 8])
output_linear.bias torch.Size([1])
  你还可以通过访问子模块来访问特定的参数,就像它们是属性一样:
seq_model.output_linear.bias

输出:

Parameter containing:
tensor([-0.1786], requires_grad=True)
  该代码对于检查参数或其梯度(例如在训练期间监视梯度)很有用。
  最后画出输入数据(圆形),期望输出(叉号)和显示样本之间行为的连续曲线

完整代码如下:

import torch
import torch.optim as optim
import torch.nn as nn
from collections import OrderedDict
from matplotlib import pyplot as plt

def training_loop(n_epochs, optimizer, model, loss_fn,
                  t_u_train, t_u_val, t_c_train, t_c_val):
    for epoch in range(1, n_epochs+1):
        t_p_train = model(t_u_train)
        loss_train = loss_fn(t_p_train, t_c_train)
        
        t_p_val = model(t_u_val)
        loss_val = loss_fn(t_p_val, t_c_val)
        
        optimizer.zero_grad()
        loss_train.backward()
        optimizer.step()
        
        if epoch == 1 or epoch % 1000 == 0:
            print('Epoch %d, Training loss %.4f, Validation loss %.4f' % (
                    epoch, float(loss_train), float(loss_val)))            
if __name__ == "__main__":
    # 实际结果(摄氏度)
    t_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
    # 温度计读取的数值(单位未知)
    t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
    t_c = torch.tensor(t_c).unsqueeze(1) # 将数据增加一维
    t_u = torch.tensor(t_u).unsqueeze(1)  
    # 划分训练集与测试集
    n_samples = t_u.shape[0]
    n_val = int(0.2 * n_samples)    
    shuffled_indices = torch.randperm(n_samples)
    train_indices = shuffled_indices[:-n_val]
    val_indices = shuffled_indices[-n_val:]
    train_t_u = t_u[train_indices]
    train_t_c = t_c[train_indices]    
    val_t_u = t_u[val_indices]
    val_t_c = t_c[val_indices] 
    t_un_train = 0.1 * train_t_u
    t_un_val = 0.1 * val_t_u    
    seq_model = nn.Sequential(OrderedDict([
        ('hidden_linear', nn.Linear(1, 8)),
        ('hidden_activation', nn.Tanh()),
        ('output_linear', nn.Linear(8, 1))
    ]))

    optimizer = optim.SGD(
        seq_model.parameters(),
        lr = 1e-3)

    training_loop(
        n_epochs = 5000,
        optimizer = optimizer,
        model = seq_model,
        loss_fn = nn.MSELoss(), # 不再使用自己定义的loss
        t_u_train = t_un_train,
        t_u_val = t_un_val,
        t_c_train = train_t_c,
        t_c_val = val_t_c)

    print('output', seq_model(t_un_val))
    print('answer', val_t_c)
    # 打印出隐藏层线性部分的权重的梯度
    print('hidder', seq_model.hidden_linear.weight.grad)
    # 绘图
    t_range = torch.arange(20., 90.).unsqueeze(1)
    fig = plt.figure(dpi = 100)
    plt.xlabel("Fahrenheit")
    plt.ylabel("Celsius")
    plt.plot(t_u.numpy(), t_c.numpy(), 'o')
    plt.plot(t_range.numpy(), seq_model(0.1 * t_range).detach().numpy(), 'c-')
    plt.plot(t_u.numpy(), seq_model(0.1 * t_u).detach().numpy(), 'kx')
    plt.show()

2.nn的子类

  对于更大,更复杂的项目,你需要将nn.Sequential放在一边,转而使用可以带来更大灵活性的东西:将nn.Module子类化。
  要实现nn.Module的子类,至少需要定义一个forward()函数,该函数将接收模型输入并返回输出。如果你使用的是torch中的操作,那么autograd会自动处理反向传递。
  在第一节中,我们已经使用了nn.Sequential来实现一个神经网络,并且用有序字典而不是列表作为输入为每一层添加标签以作改进。
  进一步,可以自己定义nn.Module的子类来完全控制输入数据的处理方式。
import torch
import torch.optim as optim
import torch.nn as nn

from collections import OrderedDict
from matplotlib import pyplot as plt
class SubclassModel(nn.Module):
    def __init__(self):
        # super()用于调用父类的方法,可用来解决多重继承问题。
        super().__init__()
        self.hidden_linear = nn.Linear(1, 13)
        self.output_linear = nn.Linear(13, 1)
        # 需要定义一个forward()函数,该函数将接收模型输入并返回输出
    def forward(self, input):
        hidden_t = self.hidden_linear(input)
        activated_t = torch.tanh(hidden_t) # nn.Tanh对应的函数
        output_t = self.output_linear(activated_t)
        return output_t

def training_loop(n_epochs, optimizer, model, loss_fn,
                  t_u_train, t_u_val, t_c_train, t_c_val):
    for epoch in range(1, n_epochs+1):
        t_p_train = model(t_u_train)
        loss_train = loss_fn(t_p_train, t_c_train)
        t_p_val = model(t_u_val)
        loss_val = loss_fn(t_p_val, t_c_val)
        optimizer.zero_grad()
        loss_train.backward()
        optimizer.step()
        if epoch == 1 or epoch % 1000 == 0:
            print('Epoch %d, Training loss %.4f, Validation loss %.4f' % (
                    epoch, float(loss_train), float(loss_val)))

if __name__ == "__main__":
    # 实际结果(摄氏度)
    t_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
    # 温度计读取的数值(单位未知)
    t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
    t_c = torch.tensor(t_c).unsqueeze(1) # 将数据增加一维
    t_u = torch.tensor(t_u).unsqueeze(1) 
    # 划分训练集与测试集
    n_samples = t_u.shape[0]
    n_val = int(0.2 * n_samples)    
    shuffled_indices = torch.randperm(n_samples)
    train_indices = shuffled_indices[:-n_val]
    val_indices = shuffled_indices[-n_val:]
    train_t_u = t_u[train_indices]
    train_t_c = t_c[train_indices]    
    val_t_u = t_u[val_indices]
    val_t_c = t_c[val_indices] 
    t_un_train = 0.1 * train_t_u
    t_un_val = 0.1 * val_t_u
    subclass_model = SubclassModel()    
    optimizer = optim.SGD(
        subclass_model.parameters(),
        lr = 1e-3)
    training_loop(
        n_epochs = 5000,
        optimizer = optimizer,
        model = subclass_model,
        loss_fn = nn.MSELoss(), # 不再使用自己定义的loss
        t_u_train = t_un_train,
        t_u_val = t_un_val,
        t_c_train = train_t_c,
        t_c_val = val_t_c)

posted @ 2020-12-09 15:56  wzdszh  阅读(149)  评论(0编辑  收藏  举报