zoukankan      html  css  js  c++  java
  • 从头学pytorch(三) 线性回归

    关于什么是线性回归,不多做介绍了.可以参考我以前的博客https://www.cnblogs.com/sdu20112013/p/10186516.html

    实现线性回归

    分为以下几个部分:

    • 生成数据集
    • 读取数据
    • 初始化模型参数
    • 定义模型
    • 定义损失函数
    • 定义优化算法
    • 训练模型

    生成数据集

    我们构造一个简单的人工训练数据集,它可以使我们能够直观比较学到的参数和真实的模型参数的区别。设训练数据集样本数为1000,输入个数(特征数)为2。给定随机生成的批量样本特征 (oldsymbol{X} in mathbb{R}^{1000 imes 2}),我们使用线性回归模型真实权重 (oldsymbol{w} = [2, -3.4]^ op) 和偏差 (b = 4.2),以及一个随机噪声项 (epsilon) 来生成标签

    [oldsymbol{y} = oldsymbol{X}oldsymbol{w} + b + epsilon ]

    其中噪声项 (epsilon) 服从均值为0、标准差为0.01的正态分布。噪声代表了数据集中无意义的干扰。

    %matplotlib inline
    import torch
    from IPython import display
    from matplotlib import pyplot as plt
    import numpy as np
    import random
    
    num_inputs = 2
    num_examples = 1000
    true_w = [2, -3.4]
    true_b = 4.2
    features = torch.from_numpy(np.random.normal(0, 1, (num_examples, num_inputs)))
    print(type(features),features.shape)
    labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
    print(type(labels),labels.shape)
    labels += torch.from_numpy(np.random.normal(0, 0.01, size=labels.size()))
    
    def use_svg_display():
        # 用矢量图显示
        display.set_matplotlib_formats('svg')
    
    def set_figsize(figsize=(3.5, 2.5)):
        use_svg_display()
        # 设置图的尺寸
        plt.rcParams['figure.figsize'] = figsize
        
    set_figsize()
    plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
    

    读取数据

    每次读取batch_size个样本.注意乱序读取.以使得每个batch的样本多样性足够丰富.

    def data_iter(batch_size, features, labels):
        num_examples = len(features)
        #print(num_examples)
        indices = list(range(num_examples))
        random.shuffle(indices)  # 样本的读取顺序是随机的
        #print(indices)
        for i in range(0, num_examples, batch_size):
            j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 最后一次可能不足一个batch
            #print(j)
            yield  features.index_select(0, j), labels.index_select(0, j)
            
    batch_size = 10
    
    for X, y in data_iter(batch_size, features, labels):
        #print(X, y)
        #break
        pass
    

    关于yiled用法参考:https://www.cnblogs.com/sdu20112013/p/11216584.html中yield部分.
    关于torch的index_select用法参考:https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch/#torchindex_select

    features是[1000,2]的Tensor。所以features.index_select(0, j)即在第0维度上对索引为j的输入进行切片.也即选取第j(j为一个长度为batch_size的tensor)个样本.

    初始化模型参数

    权重值有2个.所以我们初始化一个shape为[2,1]的Tensor.我们将其随机初始化为符合均值0,标准差0.01的正态分布随机数,bias初始化为0.

    w=torch.from_numpy(np.random.normal(0,0.01,(num_inputs,1)))
    b = torch.zeros(1, dtype=torch.float64)
    print(w.dtype,b.dtype)
    

    ndarray的类型是float64,所以w的类型是float64,在生成b的时候我们指定dtype=float64.

    之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True

    w.requires_grad_(requires_grad=True)
    b.requires_grad_(requires_grad=True) 
    

    定义模型

    下面是线性回归的矢量计算表达式的实现。我们使用mm函数做矩阵乘法。
    在我们的例子中,X是[1000,2]的矩阵,w是[2,1]的矩阵,相乘得到[1000,1]的矩阵.

    def linreg(X, w, b):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
        return torch.mm(X, w) + b
    

    定义损失函数

    我们使用平方损失来定义线性回归的损失函数。在实现中,我们需要把真实值y变形成预测值y_hat的形状。以下函数返回的结果也将和y_hat的形状相同。

    def squared_loss(y_hat, y):
        # 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
        return (y_hat - y.view(y_hat.size())) ** 2 / 2
    

    定义优化算法

    以下的sgd函数实现了上一节中介绍的小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。均值反映了平均而言,对单个样本,朝着哪个梯度方向去更新参数可以使得loss最小

    def sgd(params, lr, batch_size):
        for param in params:
            param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
    

    这里的params传入的即w,b

    训练模型

    我们创建一个循环,每次传入batch_size个样本,计算损失.反向传播,计算w,b的梯度,然后更新w,b.循环往复.注意每次方向传播后清空梯度. 以及l是一个向量. 调用.sum()将其转换为标量,再计算梯度.
    一个epoch即所有样本均计算一次损失.
    代码如下:

    lr = 0.03
    num_epochs = 3
    net = linreg
    loss = squared_loss
    batch_size=10
    
    for epoch in range(num_epochs):
        for X,y in data_iter(batch_size,features,labels):
            l = loss(linreg(X,w,b),y).sum()
            l.backward()
            sgd([w,b],lr,batch_size)
    
            w.grad.data.zero_()
            b.grad.data.zero_()
        train_l = loss(net(features,w,b),labels)
        print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
    
    print(true_w,'
    ',w)
    print(true_b,'
    ',b)
    

    输出如下:

    epoch 1, loss 0.051109
    epoch 2, loss 0.000217
    epoch 3, loss 0.000049
    [2, -3.4] 
     tensor([[ 1.9996],
            [-3.3993]], dtype=torch.float64, requires_grad=True)
    4.2 
     tensor([4.1995], dtype=torch.float64, requires_grad=True)
    

    可以看到得到的w和b都已经非常接近true_w,true_b了.


    之前我们是手写代码构建模型,创建损失函数,定义随机梯度下降等等.用pytorch里提供的类和函数,可以更方便地实现线性回归.

    线性回归的简洁实现

    生成数据集

    与前面没有区别.

    num_inputs = 2
    num_examples = 1000
    true_w = [2, -3.4]
    true_b = 4.2
    features = torch.from_numpy(np.random.normal(0, 1, (num_examples, num_inputs)))
    labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
    labels += torch.from_numpy(np.random.normal(0, 0.01, size=labels.size()))
    

    数据读取

    用torch.utils.data模块,主要使用TensorDataset类和DataLoader类

    import torch.utils.data as Data
    batch_size=10
    dataset = Data.TensorDataset(features,labels)
    data_iter = Data.DataLoader(dataset,batch_size=batch_size,shuffle=True)
    for X,y in data_iter:
        print(X,y)
        break
    

    创建网络结构

    在上一节从零开始的实现中,我们需要定义模型参数,并使用它们一步步描述模型是怎样计算的。当模型结构变得更复杂时,这些步骤将变得更繁琐。其实,PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用PyTorch更简洁地定义线性回归。

    首先,导入torch.nn模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。之前我们已经用过了autograd,而nn就是利用autograd来定义模型。nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module实现一个线性回归模型。

    class LinearNet(nn.Module):
        def __init__(self, n_feature):
            super(LinearNet, self).__init__()
            self.linear = nn.Linear(n_feature, 1)
        # forward 定义前向传播
        def forward(self, x):
            y = self.linear(x)
            return y
        
    net = LinearNet(num_inputs)
    print(net) # 使用print可以打印出网络的结构
    

    输出:

    LinearNet(
      (linear): Linear(in_features=2, out_features=1, bias=True)
    )
    

    事实上我们还可以用nn.Sequential来更加方便地搭建网络,Sequential是一个有序的容器,网络层将按照在传入Sequential的顺序依次被添加到计算图中。

    # 写法一
    net = nn.Sequential(
        nn.Linear(num_inputs, 1)
        # 此处还可以传入其他层
        )
    
    # 写法二
    net = nn.Sequential()
    net.add_module('linear', nn.Linear(num_inputs, 1))
    # net.add_module ......
    
    # 写法三
    from collections import OrderedDict
    net = nn.Sequential(OrderedDict([
              ('linear', nn.Linear(num_inputs, 1))
              # ......
            ]))
    
    print(net)
    print(net[0])
    

    输出:

    Sequential(
      (linear): Linear(in_features=2, out_features=1, bias=True)
    )
    Linear(in_features=2, out_features=1, bias=True)
    

    可以通过net.parameters()来查看模型所有的可学习参数,此函数将返回一个生成器。

    for param in net.parameters():
        print(param)
    

    输出:

    Parameter containing:
    tensor([[-0.2956, -0.2817]], requires_grad=True)
    Parameter containing:
    tensor([-0.1443], requires_grad=True)
    

    作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。因此,线性回归的输出层又叫全连接层。

    注意:torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。

    初始化模型参数

    在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了多种参数初始化方法。这里的initinitializer的缩写形式。我们通过init.normal_将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。偏差会初始化为零。

    from torch.nn import init
    
    init.normal_(net[0].weight, mean=0, std=0.01)
    init.constant_(net[0].bias, val=0)  # 也可以直接修改bias的data: net[0].bias.data.fill_(0)
    

    定义优化算法

    同样,我们也无须自己实现小批量随机梯度下降算法。torch.optim模块提供了很多常用的优化算法比如SGD、Adam和RMSProp等。下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。

    import torch.optim as optim
    
    optimizer = optim.SGD(net.parameters(), lr=0.03)
    print(optimizer)
    

    输出:

    SGD (
    Parameter Group 0
        dampening: 0
        lr: 0.03
        momentum: 0
        nesterov: False
        weight_decay: 0
    )
    

    我们还可以为不同子网络设置不同的学习率,这在finetune时经常用到。例:

    optimizer =optim.SGD([
                    # 如果对某个参数不指定学习率,就使用最外层的默认学习率
                    {'params': net.subnet1.parameters()}, # lr=0.03
                    {'params': net.subnet2.parameters(), 'lr': 0.01}
                ], lr=0.03)
    

    有时候我们不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。

    • 一种是修改optimizer.param_groups中对应的学习率
    # 调整学习率
    for param_group in optimizer.param_groups:
        param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
    
    • 另一种是更简单也是较为推荐的做法——新建优化器,由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer。但是后者对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。

    训练

    所有的optimizer都实现了step()方法,这个方法会更新所有的参数。它能按两种方式来使用:

    • optimizer.step()
      这是大多数optimizer所支持的简化版本。一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数。
    for input, target in dataset:
        optimizer.zero_grad()
        output = model(input)
        loss = loss_fn(output, target)
        loss.backward()
        optimizer.step()
    
    • optimizer.step(closure)
      一些优化算法例如Conjugate Gradient和LBFGS需要重复多次计算函数,因此你需要传入一个闭包去允许它们重新计算你的模型。这个闭包应当清空梯度, 计算损失,然后返回。
    for input, target in dataset:
        def closure():
            optimizer.zero_grad()
            output = model(input)
            loss = loss_fn(output, target)
            loss.backward()
            return loss
        optimizer.step(closure)
    

    具体参考https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-optim/

    num_epochs = 3
    for epoch in range(1, num_epochs + 1):
        for X, y in data_iter:
            output = net(X)
            l = loss(output, y.view(-1, 1))
            optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
            l.backward()
            optimizer.step()
        print('epoch %d, loss: %f' % (epoch, l.item()))
    
    dense = net[0]
    print(true_w, dense.weight)
    print(true_b, dense.bias)
    

    输出:

    epoch 1, loss: 0.000227
    epoch 2, loss: 0.000160
    epoch 3, loss: 0.000136
    [2, -3.4] Parameter containing:
    tensor([[ 2.0007, -3.4010]], requires_grad=True)
    4.2 Parameter containing:
    tensor([4.1998], requires_grad=True)
    

    总结:

    • 使用PyTorch可以更简洁地实现模型。
    • torch.utils.data模块提供了有关数据处理的工具,torch.nn模块定义了大量神经网络的层,torch.nn.init模块定义了各种初始化方法,torch.optim模块提供了模型参数优化的各种方法。
  • 相关阅读:
    小技巧与阅读感悟
    Android常见漏洞介绍(1):Intent Scheme URL 攻击
    利用Mallet工具自动挖掘文本Topic
    Xie Tao的几个小建议PPT:关于读Paper,如何与他人交流,赶Deadline
    Python解析DNS数据包
    iOS 横竖屏适配 笔记
    多工程联编,cocopods的使用
    Call to unavailable function 'system': not available on iOS
    xxx referenced from: xxx in xxx.o
    YYModel学习总结YYClassInfo(1)
  • 原文地址:https://www.cnblogs.com/sdu20112013/p/12070716.html
Copyright © 2011-2022 走看看