Pytorch实现线性回归

Posted WXiujie123456

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pytorch实现线性回归相关的知识,希望对你有一定的参考价值。

向前计算

对于Pytorch中的一个tensor,如果设置它的属性.requires_grad为TRUE,那么它将会追踪对于该向量的所有操作。或者可以理解为,这个tensor是一个参数,后续会被计算梯度,更新该参数。

计算过程

假设有以下条件(1/4表示求均值,xi中有4个数),使用torch完成其向前计算的过程。

如果x为参数,需要对齐进行梯度的计算和更新

那么,在最开始随机设置x的值的过程中,需要设置的requires_grad属性为True,其默认值为False

import torch
x=torch.ones(2,2,requires_grad=**True**)
print(x)
y=x+2
print(y)
z=y*y*3
print(z)
out=z.mean()
print(out)

运行结果:

tensor([[1., 1.],

​ [1., 1.]], requires_grad=True)

tensor([[3., 3.],

​ [3., 3.]], grad_fn=)

tensor([[27., 27.],

​ [27., 27.]], grad_fn=)

tensor(27., grad_fn=)

从上述代码可以看出:

x的requires_grad属性为True

之后的每次计算都会修改其grad_fn属性,用来记录做过的操作,通过这个函数和grad_fn能够组成一个和前一小节类似的计算图

requires_grad和grad_fn

import torch
a=torch.randn(2,2)
print(a)
a=((a*3)/(a-1))
print(a)
print(a.requires_grad)
a.requires_grad=**True
# a.requires_grad(True)#**语法错误
print(a.requires_grad)
b=(a*a).sum()
print(b)
print(b.grad_fn)
with torch.no_grad():
   c=(a*a).sum()
 print(c.requires_grad)

运行结果:

tensor([[ 0.4826, 1.3892],

​ [-0.5649, 1.9469]])

tensor([[-2.7978, 10.7076],

​ [ 1.0829, 6.1681]])

False

True

tensor(161.6989, grad_fn=)

<SumBackward0 object at 0x0000021F7F307FD0>

False

在此处,为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad()中,则它下面的代码块不会被追踪。这在评估模型时特别有用,因为模型可能具有requires_grad=True的可训练参数,但是我们不需要在此过程中对他们进行梯度计算。

梯度计算

对于“向前计算”那一节的out而言,我们可以使用backward方法来进行反向传播,计算梯度。out.backward(),此时就能求出out对x的倒数,调用x.grad能够获取导数值,得到:

tensor([[4.5000, 4.5000],

​ [4.5000, 4.5000]])

因为

当xi=1时其值为4.5

附上面的完整代码:

x=torch.ones(2,2,requires_grad=True)
print(x)
y=x+2
print(y)
z=y*y*3
print(z)
out=z.mean()
print(out)
out.backward()#不可省略,使用了backward方法之后才可使用x.grad
print(x.grad)

注意:在输出为一个标量的情况下,我们调用输出tensor的backward()方法,但是在数据是一个向量的话,调用backward()还需要传入其他参数。

很多时候我们的损失函数是一个标量,所以此处不介绍损失为向量的情况

loss.backward()就是根据损失函数,对参数(require_grad=True)计算梯度,并且把它累加保存到x.grad,因为还并未更新其梯度,故每次反向传播之前需要把梯度置为0。

此外还需注意:

第一, 关于tensor.data:

在tensor的require_grad=False时,tensor.data和tensor等价

require_grad=True时,tensor.data仅仅是获取tensor中的数据

第二, 关于tensor.numpy()

require_grad=True不能直接转化,需要使用tensor.detach().numpy()

手动实现线性回归

假设我们的基础模型是y=wx+b,其中w和b均是参数,我们使用y=3x+0.8来构造数据x、y,所以最后通过模型应该能够得出w和b应该接近3和0.8

  1. 准备数据

  2. 计算预测值

  3. 计算损失,把参数的梯度置为0,进行反向传播

  4. 更新参数

话不多说,直接上代码:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import torch
import numpy as np
from matplotlib import pyplot as plt

#准备x,y的数值
x = torch.rand([50,1])#50行一列,x相当于采样点,在0-1间随机选50个点,越多越准确
y_true = 3*x + 0.8 #按照计算公式y的准确值

#通过模型计算y_predict
w = torch.rand(1,requires_grad=True)#随机产生一个w,并追踪梯度
b = torch.rand(1,requires_grad=True)##随机产生一个b,并追踪梯度
# y_predict = torch.matmul(x,w)+b#做乘法,相当于x*w+b

#计算损失,损失函数也可以替换成其他的损失函数
def loss_fn(y_true,y_predict):
    loss=(y_predict-y_true).pow(2).mean()#做差,平方,再取平均值
    for i in [w,b]:#每次梯度都要归零
        if i.grad is not None:
            i.grad.data.zero_()#加下划线就地修改
    loss.backward() #反向传播
    return loss.data

#优化w和b的值,使之接近真实数据3和0.8
def optimize(learning_rate):
    w.data = w.data - learning_rate * w.grad.data
    b.data -= learning_rate * b.grad.data

#通过循环,反向传播,更新参数
for i in range(3000):
   # #计算预测值
    y_predict = x * w + b
    #计算损失,把参数的梯度置为0,进行反向传播
    loss=loss_fn(y_true,y_predict)

    if i%500 == 0:
        print(i,loss.data,w.data,b.data)
    #更新参数w和b
    optimize(0.01)

#绘制图像,观测训练结束的预测值和真实值
predict=x*w + b
#plt绘制图像的数据需要是numpy类型的,不能是torch类型
plt.plot(x.numpy(),y_true.numpy(),c='r') #以点阵图的形式绘制
plt.scatter(x.numpy(),predict.detach().numpy())#以直线绘制,y_predeict是带有梯度的torch类型,所以需要使用.detach()方法或者.data属性获得不含梯度的张量数据
plt.show()

以上代码是用函数封装的,也可以写成线性的:

import numpy
import torch
from matplotlib import pyplot as plt

x = torch.rand([50])
y = 3*x + 0.8

w = torch.rand(1,requires_grad=True)
b = torch.rand(1,requires_grad=True)

learning_rate=0.01

for i in range(10000):
    y_predict = w * x + b
    loss = (y - y_predict).pow(2).mean()
    for j in [w,b]:
        if j.grad is not None:
            j.grad.data.zero_()
    loss.backward()
    w.data -= learning_rate * w.grad.data
    b.data -= learning_rate * b.grad.data

y_predict = w * x + b
plt.scatter(x.numpy(),y_predict.detach().numpy(),c='r')
plt.plot(x.numpy(),y.numpy())
plt.show()

Pytorch完成模型常用的API

在前一部分,我们手动实现了通过torch的相关方法完成反向传播和参数更新。但是在Pytorch中预设了一些更加灵活简单的对象,让我们的模型更加简单,让我们来构造模型、定义损失、优化损失等

nn.Mudule

nn.Mudule是torch.nn提供的一个类,是Pytorch中我们自定义网络中的一个基类,在这个类中定义了很多有用的方法。

在我们自定义网络时,有两个方法需要特别注意:

  • __init__()需要调用super方法,继承定义我们的网络的向量计算的过程
  • forward方法必须实现,用来定义我们的网络的向前计算的过程

用前面的y = wx + b的模型举例如下:

from torch import nn
class Lr(nn.Module):
    def __init__(self):
        super(Lr,self).__init__()
        self.linear = nn.Linear(1,1)

    def forward(self,x):
        out = self.linear(x)
        return out

注意:

1.nn.Linear为torch预定义好的线性模型,也被称为全链接层

class torch.nn.Linear(in_features,out_features,bias = True )

对传入数据应用线性变换:y = A x+ b

参数:

  • in_features - 每个输入样本的大小
  • out_features - 每个输出样本的大小
  • bias - 如果设置为False,则图层不会学习附加偏差。默认值:True

2.nn.Mudule定义了__call__方法,实现的就是调用forward方法,即Lr的实例,能够直接被传入参数调用,实际上调用的是forward方法并传入参数。

#实例化模型
model = Lr()
#传入参数,计算结果
predict = model(x) 

优化器类optimizer

优化器(optimizer),可以理解为torch为我们封装的用来进行更新参数的方法,比如常见的随机梯度下降(stochastic gradient descent,SGD)

优化器类都是由torch.optim提供的,例如:

  • torch.optim.SGD(参数,学习率)
  • torch.optim.Adam(参数,学习率)

注意:

参数可以使用model.parameters()来获取,获取模型中所有requires_grad = True的参数

优化类的使用方法:

  • 实例化
  • 将所有参数的梯度置为零
  • 反向传播计算梯度
  • 更新参数值

示例如下:

optimizer = optim.SGD(model.parameters(),lr=1e-3) #实例化优化器
optimizer.zero_grad()#梯度归零
loss.backward()#计算梯度
optimizer.step()#更新梯

损失函数

前面的例子是一个回归问题,torch中也预测了很多损失函数:

  • 均方误差:nn.MSELoss(),常用于分类问题
  • 交叉熵损失:nn.CrossEntropyLoss(),常用于逻辑回归

使用方法:

#实例化模型,loss和优化器
model = Lr()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(),lr=1e-3)#实例化优化器
#训练模型
for i in range(30000):
   out = model(x) #获取预测值
   loss = criterion(y,out)#计算损失
   optimizer.zero_grad()#梯度归零
   loss.backward()#计算梯度
   optimizer.step()#更新梯度

线性回归完整代码

import torch
from torch import nn
from torch import optim
import numpy as np
from matplotlib import pyplot as plt

#定义数据
x = torch.rand([50,1])
y = x * 3 + 0.8

#定义模型
class Lr(nn.Module):
    def __init__(self):
        super(Lr,self).__init__()
        self.linear = nn.Linear(1,1)

    def forward(self,x):
        out = self.linear(x)
        return out

#实例化模型,loss和优化器
model = Lr()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(),lr=1e-3)#实例化优化器
#训练模型
for i in range(30000):
    out = model(x) #获取预测值
    loss = criterion(y,out)#计算损失
    optimizer.zero_grad()#梯度归零
    loss.backward()#计算梯度
    optimizer.step()#更新梯度

#评估模型
model.eval()  #设置模型为评估模式,即预测模式
predict = model(x) #传入参数,计算结果
predict = predict.data.numpy()
plt.scatter(x.data.numpy(),y.data.numpy(),c='r')
plt.plot(x.data.numpy(),predict)
plt.show()

输出如下:

注意:

  • model.eval()表示设置模型为评估模式,即为估测模式
  • model.train(model=True)表示设置模式为训练模式

在当前的线性回归中,上述并无区别

但是在一些其他模型中,训练的参数和预测的参数会不相同,到时候就需要具体告诉程序我们选择哪种模式。

在GPU上运行代码

当模型太大,或者参数太多的情况下,为了加快训练速度,经常会使用GPU来进行训练

此时我们的代码需要稍作调整:

  1. 判断GPU是否可用 torch.cuda.is_available()
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
  1. 把模型参数和input数据转化为cuda的支持类型
x = torch.rand([50,1]).to(device)
model = Lr().to(device)
  1. 在GPU上计算结果也为cuda的数据类型,需要转化为numpy或者torch的cpu的tensor类型

predict = predict.cpu().detach().numpy()

detach()的效果和data的相似,但是detach()是深拷贝,data是取值,是浅拷贝。

修改后代码如下:

import torch
from torch import nn
# import torch.nn as nn
from torch import optim
import numpy as np
from matplotlib import pyplot as plt
#定义一个device对象
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
#定义数据
x = torch.rand([50,1]).to(device)
y = x * 3 + 0.8

#定义模型
class Lr(nn.Module):
    def __init__(self):
        super(Lr,self).__init__()
        self.linear = nn.Linear(1,1)

    def forward(self,x):
        out = self.linear(x)
        return out

#实例化模型,loss和优化器
model = Lr().to(device)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(),lr=0.001)#实例化优化器
#训练模型
for i in range(30000):
    out = model(x) #获取预测值
    loss = criterion(y,out)#计算损失
    optimizer.zero_grad()#梯度归零
    loss.backward()#计算梯度
    optimizer.step()#更新梯度

#评估模型
model.eval()  #设置模型为评估模式,即预测模式
predict = model(x) #传入参数,计算结果
predict = predict.cpu().detach().numpy()
plt.scatter(x.data.numpy(),y.data.numpy(),c='r')
plt.plot(x.data.numpy(),predict)
plt.show()

(其实我的电脑很辣鸡,并不支持GPU运行,o(╥﹏╥)o)

更多Pytorch知识梳理,请参考: pytorch学习笔记

有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。

以上是关于Pytorch实现线性回归的主要内容,如果未能解决你的问题,请参考以下文章

PyTorch学习笔记 8. 实现线性回归模型

PyTorch学习笔记 8. 实现线性回归模型

Pytorch实战1:线性回归(Linear Regresion)

线性回归的pytorch代码

PyTorch深度学习:用PyTorch实现线性回归

PyTorch 完全入门指南!从线性回归逻辑回归到图像分类