深度学习中线性回归实现及详解(基于pytorch)

1,546 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

1.线性回归的基本要素

1.1 模型定义

以简单的房屋价格预测来作为线性回归的基本要素,目标是售出价格(元)。设房屋的面积为x1,房龄为x2,售出价格为y。我们需要建立基于输入x1和x2来计算输出y的表达式,也就是模型。

image.png 其中,w1和w2是权重,b是偏差,且均为标量。它们是线性回归模型的参数。模型输出的结果是线性回归对真实价格y的预测和估计。通常会允许存在一定的误差。

1.2 模型训练

然后我们就需要数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小。这个过程叫做模型训练。其中包含三个要素:

(1)训练数据

训练的数据应该用真实数据,这些数据一般就被称为训练数据集 ,这里我们将一栋房屋作为一个样本(sample),真实价格叫标签(label),用来预测标签的两个因素叫特征(feature)。

(2)损失函数

在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。它在评估索引为i的样本误差的表达式为:

image.png

可以看出,误差越小表示预测价格与真实价格越相近。这里使用的平方误差函数也称为平方损失。

(3)优化算法

在求数值解的优化算法中,我们通常使用小批量随机梯度下降。它的算法思路:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch), 然后求小批量中数据样本的平均损失有关模型参数的导数(梯度) ,最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

2.代码实现线性回归

2.1 环境配置要求

Pytorch1.12.1
Python3.8
Numpy 1.21库
Cuda11.3

image.png

2.2环境导入

image.png

import matplotlib_inline#用于作图
import torch#导入pytorch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random

2.3生成数据集

image.png

num_inputs=2               #输入特征数为2,
num_examples=1000          #设置训练样本数为1000
true_w=[2,-3.4]            #设置模型的真实权重分别为2和-3.4
true_b=4.2                 #设置偏差值为4.2
features=torch.from_numpy(np.random.normal(0,1,(num_examples,num_inputs)))#就是torch.from_numpy()方法把数组转换成张量,且二者共享内存.
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()))
print(features[0],labels[0])

注意,features 的每一行是一个长度为2的向量,而labels的每一行是一个长度为1的向量(标量)。

image.png 这里代码进行了改进,改进之后不再报红了。

def use_svg_display():
    #用矢量图进行显示
    matplotlib_inline.backend_inline.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);

通过生成第二个特征features[;, 1]和标签labels 的散点图,可以更直观地观察两者间的线性关系。

2.4读取数据

image.png

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

我们读取第一个小批量数据样本并打印。每个批量的特征形状为(10, 2),分别对应批量大小和输入个数;标签形状为批量大小。

2.5初始化模型参数

image.png

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

这里会在最后有一个报错,所以float32改成了float64。

2.6定义模型

使用mm函数做矩阵乘法 image.png

def linreg(X, w,b):
     return torch.mm(X,w) + b

2.7定义损失函数

image.png

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

这里使用平方损失来定义线性回归的损失函数,把真实值y变为y_hat的形状。

2.8定义优化算法

image.png

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

这里使用小批量随机梯度算法进行优化

2.9训练模型

image.png

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
    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()))

训练后查看生成的参数

image.png

print(true_w, '\n', w)
print(true_b, '\n', b)

2.10可能出现的bug

出现:RuntimeError: expected scalar type Double but found Float报错

image.png 这是因为double类型和float类型发生了冲突,将dtype的32改成64问题就可以解除。