Pytorch实现线性回归demo

关于线性回归

即对于几组input和target的值用模型进行线性的拟合。比如对于房子的不同地理位置大小等因素决定的房价。线性的模型就是对于一组input输入得出与target相近的out。

过程

较为普式的把输入对象的属性整合为一组集合 [ x 1 j , x 2 j , x 3 j , . . . , x n j ] [x_1^j,x_2^j,x_3^j,...,x_n^j] [x1j​,x2j​,x3j​,...,xnj​],对应的target为 y j y^j yj。

然后对于一个 j j j给出预测的out(target的prediction):
h θ ( x ) = θ 0 x 0 + θ 1 x 1 + θ 2 x 2 + . . . + θ n x n h_{\theta}( x) = \theta_0x_0+\theta_1x_1+\theta_2x_2 +...+\theta_nx_n hθ​(x)=θ0​x0​+θ1​x1​+θ2​x2​+...+θn​xn​

h θ ( x ) = [ θ 0 . . . θ n ] × [ x 0 . . . x n ] = θ T X h_\theta(x) = \begin{bmatrix} \theta_0 &... & \theta_n \end{bmatrix} \times \begin{bmatrix} x_0 \\ ... \\ x_n \end{bmatrix} = \theta^TX hθ​(x)=[θ0​​...​θn​​]×⎣⎡​x0​...xn​​⎦⎤​=θTX
所以开始,先制造数据,在线性函数周围制造噪点(以标量函数举例),然后定义LinearRegression模型。

import torch
import matplotlib.pyplot as plt
from torch import nn
from torch.autograd import Variable

# 对于-1到1进行x取样,并增加一维
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1) # 0为行扩展,1为列扩展
#print(x)
# 定义y,并在准确值的周围制造random噪点
y= 3 * x + 10 + torch.rand(x.size())

# 绘制散点图
plt.scatter(x.data.numpy(),y.data.numpy())
plt.show()

# 定义输入层和输出层均为一维的模型
class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1,1) # 输入输出层的维度都为1
    def forward(self,x):
        out = self.linear(x)
        return out

# print(LinearRegression())
model = LinearRegression()

定义好model后开始选择损失函数和优化器。

这里选择均方误差的损失函数,默认返回loss的mean值。

然后选择优化器,选择常用的SGD随机梯度下降训练优化,并在参数中选择lr步长。

# 定义损失函数(均方误差)和优化函数(梯度下降)
criterion = nn.MSELoss() # loss function 返回mean
# 构建optimizer对象,SGD随机梯度下降训练优化
optimizer = torch.optim.SGD(model.parameters(),lr = 1e-2)

然后开始模型训练的迭代,将x和y转化为Variable,分别为inputs和targets。

如何训练呢。

首先向前传播:将inputs放入model,得到out的预测值,然后就是利用out和targets计算loss。

然后需要将loss降到最低,就要用梯度下降的方法求其最小值。也就是:

损失函数:
J ( θ ) = 1 2 m ∑ i = 0 m ( h θ ( x i ) − y i ) 2 J(\theta) = \frac{1}{2m}\sum_{i = 0}^{m}(h_{\theta}(x^i)-y^i)^2 J(θ)=2m1​i=0∑m​(hθ​(xi)−yi)2

那么要找到让其最小的 θ \theta θ,梯度下降的方法使用如下公式:

θ j ′ = θ j − η ∂ J ( θ ) ∂ θ j \theta_j^{'} =\theta_j-\eta \frac{\partial J(\theta) }{\partial \theta_j} θj′​=θj​−η∂θj​∂J(θ)​
也就是让 θ \theta θ沿着梯度的方向走,一直到计算前后的 θ \theta θ值相等或者小于某个阈值。就找到了相应的极值。

对于步长 η \eta η,不宜太大,会导致错过最小值。也不宜太小,容易陷入局部极小值。

但是在计算.backward()求梯度前,要记得将梯度清零,不然会累加。

也就是向后传播:

# 向后传播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if (epoch + 1)%20 == 0:
        print('Epoch[{}/{}], Loss:{:.6f}'.format(epoch + 1,num_epoch,loss.item()))

此处提一嘴.backward(),此处由于是标量的损失函数,所以可以直接求导,不需要参数。而如果损失函数(模型函数)是一个矢量tensor,需要在里面加入一个于函数同维的tensor。

至于为什么,下面来解释:

举一个例子:

这种情况一般出现于,target为一个多维值的情况。

y i = w i 1 x 1 + w i 2 x 2 y_i = w_{i1}x_1+w_{i2}x_2 yi​=wi1​x1​+wi2​x2​

也就是:

[ y 1 y 2 ] = [ w 11 w 12 w 21 w 22 ] [ x 1 x 2 ] \begin{bmatrix} y_1\\ y_2 \end{bmatrix} = \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} [y1​y2​​]=[w11​w21​​w12​w22​​][x1​x2​​]

然后定义一个与y维度相同的参数v:
[ v 1 v 2 ] \begin{bmatrix} v_1 \\ v_2 \end{bmatrix} [v1​v2​​]

然后执行y.backward(v)

那么:

x . g r a d = [ ∂ y ∂ x 1 ∂ y ∂ x 2 ] = [ ∂ y 1 ∂ x 1 v 1 + ∂ y 2 ∂ x 1 v 2 ∂ y 1 ∂ x 2 v 1 + ∂ y 2 ∂ x 2 v 2 ] = [ w 11 v 1 + w 12 v 2 w 11 v 2 + w 12 v 1 ] x.grad = \begin{bmatrix} \frac{\partial y }{\partial x_1} \\ \frac{\partial y }{\partial x_2} \end{bmatrix}=\begin{bmatrix} \frac{\partial y_1 }{\partial x_1} v_1 + \frac{\partial y_2 }{\partial x_1} v_2\\ \frac{\partial y_1 }{\partial x_2} v_1 + \frac{\partial y_2 }{\partial x_2} v_2 \end{bmatrix}=\begin{bmatrix} w_{11}v_1 + w_{12}v_2\\ w_{11}v_2 + w_{12}v_1 \end{bmatrix} x.grad=[∂x1​∂y​∂x2​∂y​​]=[∂x1​∂y1​​v1​+∂x1​∂y2​​v2​∂x2​∂y1​​v1​+∂x2​∂y2​​v2​​]=[w11​v1​+w12​v2​w11​v2​+w12​v1​​]

因为在pytorch中,tensor对tensor求导不允许,对于这种函数target为tensor的情况,可以理解为定义了一个复合函数:
L = ∑ i = 1 n v i y i L=\sum_{i=1}^{n}v_iy_i L=∑i=1n​vi​yi​,这样对x求偏导就是一个scaler而不是tensor对x求偏导了,无非使用链式求导法则现对y求偏导,再对x求偏导。而v就可以看作 L L L的每一项的系数。

最后对模型进行评估:

model.eval() # 设为评估模式

# 测试
if torch.cuda.is_available():
    predict = model(Variable(x).cuda())
    predict = predict.data.cpu().numpy()
else:
    predict = model(Variable(x))
    predict = predict.data.numpy() # 转化为numpy

plt.plot(x.numpy(), y.numpy(),'ro',label="Original Data")
plt.plot(x.numpy(),predict,label="Fitting Line")
plt.show()

Pytorch实现线性回归demo
整体代码:

# -*- coding: utf-8 -*-
"""
Spyder Editor

linear regression demo
"""

# target model:y = 3x + 10

import torch
import matplotlib.pyplot as plt
from torch import nn
from torch.autograd import Variable

# 对于-1到1进行x取样,并增加一维
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1) # 0为行扩展,1为列扩展
#print(x)
# 定义y,并在准确值的周围制造random噪点
y= 3 * x + 10 + torch.rand(x.size())

# 绘制散点图
plt.scatter(x.data.numpy(),y.data.numpy())
plt.show()

# 定义输入层和输出层均为一维的模型
class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1,1) # 输入输出层的维度都为1
    def forward(self,x):
        out = self.linear(x)
        return out

# print(LinearRegression())
model = LinearRegression()

# 定义损失函数(均方误差)和优化函数(梯度下降)
criterion = nn.MSELoss() # loss function 返回mean
# 构建optimizer对象,SGD随机梯度下降训练优化
optimizer = torch.optim.SGD(model.parameters(),lr = 1e-2)

# 模型训练
num_epoch = 1000 # 迭代次数1000
for epoch in range(num_epoch):
    if torch.cuda.is_available():
        inputs = Variable(x).cuda()
        targets = Variable(y).cuda()
    else:
        inputs = Variable(x)
        targets = Variable(y)
    
    # 向前传播
    out = model(inputs) # 放入模型
    loss = criterion(out, targets) # 计算loss,参数均为torch in variable
    
    # 向后传播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    # 进度条
    if (epoch + 1)%20 == 0:
        print('Epoch[{}/{}], Loss:{:.6f}'.format(epoch + 1,num_epoch,loss.item()))

model.eval() # 设为评估模式

# 测试
if torch.cuda.is_available():
    predict = model(Variable(x).cuda())
    predict = predict.data.cpu().numpy()
else:
    predict = model(Variable(x))
    predict = predict.data.numpy() # 转化为numpy

plt.plot(x.numpy(), y.numpy(),'ro',label="Original Data")
plt.plot(x.numpy(),predict,label="Fitting Line")
plt.show()
    
上一篇:深度学习的数学 (5)偏导数


下一篇:矩阵求导