Pytorch实现线性回归

0.速记

  • torch.utils.data模块提供了有关数据处理的工具
  • torch.nn模块定义了大量神经网络的层
  • torch.nn.init模块定义了各种初始化方法
  • torch.optim模块提供了很多常用的优化算法。

1.引入包

import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random #random模块用于生成随机数
%matplotlib inline 
  • %matplotlib inline:在使用jupyter notebook 或者 jupyter qtconsole的时候,才会经常用到%matplotlib。%matplotlib具体作用是当你调用matplotlib.pyplot的绘图函数plot()进行绘图的时候,或者生成一个figure画布的时候,可以直接在你的python console里面生成图像。

2.生成数据集

y = X w + b + ϵ y=Xw+b+ϵ y=Xw+b+ϵ

num_inputs = 2 #有两个特征
num_examples = 1000 #有1000个样本
true_w = [2, -3.4] #w的真实值
true_b = 4.2 #b的真实值
#随机生成1000个有2个特征的样本(tensor)
features = torch.randn(num_examples, num_inputs,type=torch.float32)        
#根据方程生成样本的标签
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
#为样本的标签添加一些噪声
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
                       dtype=torch.float32)
  • torch.randn()函数的第一个参数是*size,也就是说这个参数可以是一串整数,也可以是一个list或tuple,代表要创建tensor的size。在上面代码中,size就是1000x2。此函数返回的tensor中的数据符合标准正态分布,即均值为0,方差为1。
  • np.random.normal()有三个参数:loc,scale,size。loc代表mean,scale代表标准差,size是一个整数或一个tuple。返回值从从一个正态分布上采样的数据。

3.画出一个变量和y的图,观察它们的关系

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

# # 在../d2lzh_pytorch(包)里面添加上面两个函数后就可以这样导入
# import sys
# sys.path.append("..")
# from d2lzh_pytorch import * 

set_figsize()
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
  • display.set_matplotlib_formats():plt.show() 默认都是输出.png文件,图片只要稍微放大一点,就糊的不行。IPython 是 Python 的一个增强版本。它在下列方面有所增强:命名输入输出、使用系统命令(shell commands)、排错(debug)能力。用IPython模块的display类的set_matplotlib_formats函数,设置显示的图像类型为svg。
  • plt.rcParams[ ]:pylot使用rc配置文件来自定义图形的各种默认属性,称之为rc配置或rc参数。通过rc参数可以修改默认的属性,包括窗体大小、每英寸的点数、线条宽度、颜色、样式、坐标轴、坐标和网络属性、文本、字体等。rc参数存储在字典变量中,通过字典的方式进行访问。
  • figure.figsize:画板大小。
  • plt.scatter()的前几个参数分别是x、y、size、color、marker。

4.读取数据

(1)Python方法

在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:batch_size()它每次返回batch_size(批量大小)个随机样本的特征和标签。

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    random.shuffle(indices)  # 样本的读取顺序是随机的
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) 
        # 最后一次可能不足一个batch
        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
  • range():参数有start,end,step。计数从 start 开始。默认是从 0 开始。 计数到 stop 结束,但不包括 stop。步长,默认为1。
    range(10)        # 从 0 开始到 10
    #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    在上面代码的for循环中,range(0, num_examples, batch_size)返回的是[0,10,20…990]
  • torch.LongTensor():Torch定义了七种CPU tensor类型和八种GPU tensor类型。LongTensor指的是long int类型的Tensor。
  • indices[i:min(i+batch_size,1000):这里是一个切片操作,不包含最后一个索引。当i=0时,这里的选取范围是indices[0]~indices[9]
  • tensor.index_select():前三个参数为input,dim,index。返回输入Tensor指定维度上的index(index要求是LongTensor类型)对应的值。在上述代码中指选取 features 和 labels 中 j 对应的行(dim=0)。
  • yield
    • 通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象。它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。生成器是可以迭代的,但只可以读取它一次。因为用的时候才生成。
    • 简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 data_iter(batch_size, features, labels) 不会执行 data_iter函数,而是返回一个 iterable 对象!
    • 在 for 循环执行时,每次循环都会执行 data_iter 函数内部的代码,执行到 yield 时,data_iter 函数就返回一个迭代值,下次迭代时,代码从 yield 的下一条语句继续执行( i 变为下一个数),而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
    • 这样不断执行下去,就会按照批次,一批一批输出X,y。

(2)简洁:Pytorch方法

PyTorch提供了torch.utils.data包来读取数据。由于data常用作变量名,我们将导入的data模块用Data代替。在每一次迭代中,我们将随机读取包含10个数据样本的小批量。

import torch.utils.data as Data

batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 随机读取小批量
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)

for X, y in data_iter:
    print(X, y)
    break
  • Data.TensorDataset():每个样本都将通过索引第一维的张量来检索。
  • Data.DataLoader():参数很多,可以只记前三个。API Reference

5.初始化模型参数并定义模型

(1)Python方法:

我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True。

w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)

def linreg(X, w, b): 
    return torch.mm(X, w) + b 
    
net = linreg #模型函数的别名
  • torch.mm():做矩阵乘法。

(2)简洁:Pytorch方法:

  • 在上面的实现中,我们需要定义模型参数,并使用它们一步步描述模型是怎样计算的。当模型结构变得更复杂时,这些步骤将变得更繁琐。其实,PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用PyTorch更简洁地定义线性回归。
  • 首先,导入torch.nn模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。之前我们已经用过了autograd,而nn就是利用autograd来定义模型。
  • torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。
定义模型方法一: nn.Module
  • nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module实现一个线性回归模型。
import torch.nn as nn

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)
)
  • super():super() 函数是用于调用父类(超类)的一个方法。super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。第一个参数是类(本身),第二个参数一般是self。点后面调用的是父类的方法。
  • nn.Linear()有三个参数:in_features,每个输入样本的大小。out_features,每个输出样本的大小。bias,如果设置为False,则图层不会学习附加偏差。默认值: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)
查看模型所有的可学习参数:
for param in net.parameters():
    print(param)

输出:

Parameter containing:
tensor([[-0.0277,  0.2771]], requires_grad=True)
Parameter containing:
tensor([0.3395], requires_grad=True)
  • net.parameters():此函数将返回一个生成器
初始化模型参数:

在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了多种参数初始化方法。这里的init是initializer的缩写形式。

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)
  • init.normal_():将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。
  • init.constant_():偏差会初始化为零。
  • net[0].bias.data.fill_():net[0]这样根据下标访问子模块的写法只有当net是个ModuleList或者Sequential实例时才可以。

6.定义损失函数

(1)Python方法:

def squared_loss(y_hat, y): 
    # 注意这里返回的是向量, 另外, pytorch里的 MSELoss并没有除以 2
    return (y_hat - y.view(y_hat.size())) ** 2 / 2
    #我们需要把真实值y变形成预测值y_hat的形状。

loss = squared_loss #损失函数的别名

(2)简洁:Pytorch方法:

PyTorch在nn模块中提供了各种损失函数,这些损失函数可看作是一种特殊的层,PyTorch也将这些损失函数实现为nn.Module的子类。我们现在使用它提供的均方误差损失作为模型的损失函数。

loss = nn.MSELoss()

7.定义优化算法

小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:

  • 先选取一组模型参数的初始值,如随机选取;
  • 接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。
  • 在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B。
  • 然后求小批量中数据样本的平均损失有关模型参数的导数(梯度)。
  • 最后用此结果与预先设定的一个正数(学习率(learning rate))的乘积作为模型参数在本次迭代的减小量。
    在这里插入图片描述

(1)Python方法:

以下的sgd函数实现了小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。

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

(2)简洁:Pytorch方法:

torch.optim模块提供了很多常用的优化算法比如SGDAdamRMSProp等。

  • 下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。
import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
  • net.parameters()来自torch.nn.Module类。
  • optim.SGD()可以只记前两个参数。
  • 为不同子网络设置不同的学习率,这在finetune时经常用到。例:
optimizer =optim.SGD([
                # 如果对某个参数不指定学习率,就使用最外层的默认学习率
                {'params': net.subnet1.parameters()}, # lr=0.03
                {'params': net.subnet2.parameters(), 'lr': 0.01}
            ], lr=0.03)
  • 不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。一种是修改optimizer.param_groups中对应的学习率,另一种是更简单也是较为推荐的做法——新建优化器,由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer。但是后者对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。
# 调整学习率
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 学习率为之前的0.1倍

8.训练模型

(1)Python方法:

lr = 0.03
num_epochs = 3

for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
    # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。
    
    # X和y分别是小批量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  # l是有关小批量X和y的损失
        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, '\n', w)
print(true_b, '\n', b)

(2)简洁:Pytorch方法:

在使用Gluon训练模型时,我们通过调用optim实例的step函数来迭代模型参数。按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均。

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)
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值