使用Pytorch实现线性回归

当所求变量与其影响因素之间存在线性关系时,可以使用线性回归来建模处理此类问题。在实际生活中常见的有预测房屋价格、气温、销售额等连续值的问题。

以房屋价格预测为例,房价取决于多个因素,这里只考虑面积房龄两个因素,并假设房价与两个因素之间为线性关系。

设房屋价格为y,房屋面积为x_1,房龄为x_2,模型预测得到的价格为\hat{y}。则三个变量之间的关系可以表示为:        

\hat{y} = x_1w_1 + x_2w_2 + b

w_1w_2是权重,b为偏差,三个参数均为标量。预测得到的\hat{y}和真实价格y之间允许存在一定误差。我们需要找到合适的参数值,使得误差尽可能小。

损失函数

两个变量之间的误差值可以用误差平方和来衡量,位于[0, 1]之间,数值越小效果越好。第i个样本误差可以表示为:

l^{(i)}(w_1, w_2, b) = \frac{1}{2}(\hat{y}^{(i)}-y^{(i)})^2

常数\frac{1}{2}使得对平方项求导后常数系数为1,在形式上可以简单一些。给定训练集,误差只与模型参数有关。

在机器学习中,将衡量误差的函数称为损失函数(loss function),用训练集中所有样本误差的平均来衡量模型预测质量,在这里可以表示为:

l(w_1,w_2,b) = \frac{1}{n}\textstyle\sum_{i=1}^nl^{(i)}(w_1, w_2,b) = \frac{1}{n}\textstyle\sum_{i=1}^n\frac{1}{2}(x_1^{(i)}w_1+x_2^{(i)}w_2+b-y^{(i)})^2

训练模型时,我们希望找到一组模型参数w_1^*,w_2^*,b^*使得训练样本平均损失最小:

w_1^*,w_2^*,b^*=arg minl(w_1, w_2, b)

根据平方损失定义线性回归的损失函数,代码如下:

def squared_loss(y_hat, y):
    # pytorch 中的 MSELoss没有除以2
    # 返回值为向量
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

Pytorch在nn模块提供了各种损失函数,并将这些损失函数定义为 nn.Module 的子类。

class L1Loss(_Loss):……
class NLLLoss(_WeightedLoss):……
class GaussianNLLLoss(_Loss):……
class MSELoss(_Loss):……

 这里可以直接使用MSELoss均方误差损失作为模型的损失函数。

loss = nn.MSELoss()

 优化算法

当确定模型训练的参数后,就可以得到预测结果\hat{y}与真实值y之间的误差,但我们不能保证随意选择的参数可以使得模型性能很好甚至最小,所以模型中的参数值需要不断的调整,但如果是人为改变参数值,这无疑是一个费时费力不讨好的工作。在深度学习中,通常使用小批量随机梯度下降(mini-batch stochastic gradient desent)方法来改变参数值:如可以先随机选取一组参数的初始值,然后对参数多次迭代,使每次迭代都可能降低损失函数的值。每次迭代中,先随机均匀采样一定数目训练数据样本B(mini-batch),然后求样本B的平均损失有关模型参数的导数(梯度),参数在一个迭代的减小量可以表示为:

w_1 = w_1 - \frac{\eta}{|B|}\sum_{i\epsilon{B}}\frac{\partial{l^{(i)}(w_1,w_2,b)}}{\partial{w_1}}=w_1-\frac{\eta}{|B|}\sum_{i\epsilon{B}}x_1^{(i)}(x_1^{(i)}w_1+x_2^{(i)}w_2+b-y^{(i)})

w_2 = w_2 - \frac{\eta}{|B|}\sum_{i\epsilon{B}}\frac{\partial{l^{(i)}(w_1,w_2,b)}}{\partial{w_2}}=w_2-\frac{\eta}{|B|}\sum_{i\epsilon{B}}x_2^{(i)}(x_1^{(i)}w_1+x_2^{(i)}w_2+b-y^{(i)})

 b = b - \frac{\eta}{|B|}\sum_{i\epsilon{B}}\frac{\partial{l^{(i)}(w_1,w_2,b)}}{\partial{b}}=b-\frac{\eta}{|B|}\sum_{i\epsilon{B}}(x_1^{(i)}w_1+x_2^{(i)}w_2+b-y^{(i)})

|B|表示样本个数,如模型训练时设置batch\_size = 10,则 |B| = 10\eta称为学习率。两个参数都为超参,是人为设定而非模型训练学出来的。少数情况下,超参也可通过模型训练得出。

优化算法可以定义为:

def sgd(params, lr, batch_size):
    for param in params:
        # 更改 param 时用的是param.data
        # 这里自动求梯度模块计算得到的梯度是一个批量样本的梯度和,将它除以批量大小得到平均值
        param.data -= lr * param.grad / batch_size

 Pytorch的optim模块提供了很多常用的优化算法如SGD、Adam和RMSProp等,不需要我们自己实现梯度下降算法。学习率为0.03的小批量随机梯度下降算法可以表示为:

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

读取数据

训练模型时,需要每次取出样本量为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)])
        yield features.index_select(0, j), labels.index_select(0, j)

pytorch提供了data包来读取数据,上述代码使用data包处理代码量就少很多:

import torch.utils.data as Data  
dataset = Data.TensorDataset(features, labels)
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)

模型定义 

线性回归矢量计算表达式实现:

def linreg(X, w, b):
    # 使用 mm 函数做矩阵乘法
    return torch.mm(X, w) + b

 实际使用时,最常见的做法是继承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)

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

训练模型

在训练时,会有多次迭代改变模型中的参数,也就是会有epoch个迭代周期。每个迭代周期中,会使用训练集中的所有样本,但每次只会取batch\_size大小的样本,样本包括影响因素(即特征)和变量真实值(即标签)。根据参数w_1,w_2,b以及输入样本计算得出预测值\hat{y},计算损失函数loss,得出loss函数分别对w_1,w_2,b的梯度,使用优化算法更新参数。多次训练后,比较学习到的参数和真实参数,应该很接近。

epoch 1, loss 0.000203
epoch 2, loss 0.000073
epoch 3, loss 0.000066
[2, -3.4] Parameter containing:
tensor([[ 2.0001, -3.3991]], requires_grad=True)
4.2 Parameter containing:
tensor([4.2008], requires_grad=True)

训练采用的数据集通过线性回归模型真实权重w = [2,-3.4]和偏差b = 4.2,以及一个随机噪声项\epsilon来生成。

lr = 0.03    #学习率
num_epochs = 3    #epoch 迭代周期
net = linreg    # 采用线性回归模型
loss = squared_loss    # squared_loss为平方和误差函数

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()))

使用pytorch,可以省去对损失函数、优化函数和数据读取的自定义实现。

    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))
            # 梯度清零,等价于net.zero_grad()
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
        print('epoch %d, loss %f' % (epoch, l.item()))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值