zoukankan      html  css  js  c++  java
  • 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)
    
    
  • 相关阅读:
    HDU 2089 不要62
    HDU 5038 Grade(分级)
    FZU 2105 Digits Count(位数计算)
    FZU 2218 Simple String Problem(简单字符串问题)
    FZU 2221 RunningMan(跑男)
    FZU 2216 The Longest Straight(最长直道)
    FZU 2212 Super Mobile Charger(超级充电宝)
    FZU 2219 StarCraft(星际争霸)
    FZU 2213 Common Tangents(公切线)
    FZU 2215 Simple Polynomial Problem(简单多项式问题)
  • 原文地址:https://www.cnblogs.com/wzdszh/p/14109119.html
Copyright © 2011-2022 走看看