欢迎光临散文网 会员登陆 & 注册

08 线性回归 + 基础优化算法【动手学深度学习v2】

2021-11-16 13:31 作者:如果我是泡橘子  | 我要投稿

线性回归


房价预测


线性模型

  • b:标量偏差


  • 单层神经网络:带权重的层只有一个(输出层)


真实的神经元


衡量预估质量

  • 平方损失:衡量真实值与预测值之间的误差
  • 1 / 2:主要是为了求导的时候方便抵消平方的导数所产生的系数2


训练数据


参数学习

  • 1 / 2:来自损失函数
  • 1 / n:求平均
  • 最小损失函数来学习参数


显示解

  • 因为是线性模型,所以是有显示解的,并且损失是一个凸函数
  • 凸函数的最优解满足梯度等于0



总结


  • 线性回归是对n维输入的加权,外加偏差(对输出值的预估)
  • 使用平方损失来衡量预测值和真实值的差异
  • 线性回归有显示解(一般来说,模型都没有显示解,有显示解的模型过于简单,复杂度有限,很难衡量复杂的数据)
  • 线性回归可以看做是单层神经网络(最简单的神经网络)





基础优化算法


梯度下降

  • η:标量,表示学习率,代表沿着负梯度方向一次走多远,即步长。他是一个超参数(需要人为的指定值)
  • 学习率的选择不能太小,也不能太大(太小会导致计算量大,求解时间长;太大的话或导致函数值振荡,并没有真正的下降)
  • l:损失函数
  • 圆圈代表函数值的等高线,每个圆圈上的点处的函数值相等
  • 梯度:使得函数值增加最快的方向,负梯度就是使函数下降最快的方向,如图中黄线所示



小批量随机梯度下降

  • 梯度下降时,每次计算梯度,要对整个损失函数求导,损失函数是对所有样本的平均损失,所以每求一次梯度,要对整个样的本的损失函数进行重新计算,计算量大且耗费时间长,代价太大
  • 用 b 个样本的平均损失来近似所有样本的平均损失,当 b 足够大的时候,能够保证一定的精确度
  • 批量大小的选择



总结


  • 梯度下降通过不断沿着负梯度方向更新参数求解(好处是不需要知道显示解是什么,只需要不断的求导就可以了)
  • 小批量随机梯度下降是深度学习默认的求解算法(虽然还有更稳定的,但是他是最稳定、最简单的)
  • 两个重要的超参数是批量大小和学习率





线性回归的实现


1、导入所需要的包


%matplotlib inline

import random

import torch

from d2l import torch as d2l

  • random:导入random包用于随机初始化权重
  • d2l:将用过的或者实现过的算法放在d2l的包里面
  • matplotlib inline:在plot的时候默认是嵌入到matplotlib中
  • 报错 No module named 'matplotlib' :在命令行中使用 pip install matplotlib 安装 matplotlib 包即可
  • 报错 No module named 'd2l' :在命令行中使用 pip install d2l 安装 d2l 包即可,安装完成之后可能需要重新打开程序才能生效



2、根据带有噪声的线性模型构造一个人造的数据集


  • 构造人造数据集的好处是知道真实的 w 和 b
  • X = torch.normal(0,1,(num_examples,len(w))):X 是一个均值为 0 ,方差为 1 的随机数,他的行数等于样本数,列数等于 w 的长度
  • y += torch.normal(0,0.01,y.shape):给 y 加上了一个均值为 0 ,方差为 0.01 形状和 y 相同的噪声
  • return X,y.reshape((-1,1)):最后把 X 和 y 做成一个列向量返回
  • true_w:真实的 w
  • true_b:真实的 b
  • features,labels = synthetic_data(true_w,true_b,1000),根据函数来生成特征和标注


def synthetic_data(w,b,num_examples):

    """生成 y = Xw + b + 噪声"""

    X = torch.normal(0,1,(num_examples,len(w)))

    y = torch.matmul(X,w) + b

    y += torch.normal(0,0.01,y.shape)

    return X,y.reshape((-1,1))


true_w = torch.tensor([2,-3.4])

true_b = 4.2

features,labels = synthetic_data(true_w,true_b,1000)



3、feature中每一行都包含一个二维数据样本,labels中的每一行都包含一维标签值


print('features:',features[0],'\nlabels',labels[0])

输出:

features: tensor([0.1605, 0.2305])

labels tensor([3.7450])


d2l.set_figsize()

d2l.plt.scatter(features[:1].detach().numpy(),labels.detach().numpy(),1);

输出:

  • detach():在pytorch的一些版本中,需要从计算图中detach出来才能转到numpy中去



4、实现一个函数读取小批量


定义一个data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为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):

        batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)])

        yield features[batch_indices],labels[batch_indices]


batch_size = 10


for X,y in data_iter(batch_size,features,labels):

    print(X,'\n',y)

    break

  • batch_size:批量大小
  • num_examples:样本数
  • random.shuffle(indices):将下标打乱,实现对样本的随机访问
  • for i in range(0,num_examples,batch_size):从 0 开始到样本数结束,每次跳批量大小
  • yield features[batch_indices],labels[batch_indices]:通过indices,每次产生随机顺序的特征和其对应的随即顺序标号
  • yield是python中的一个迭代器

输出:

tensor([[-0.3785, -0.5235],

[ 0.0674, 0.6504],

[-0.9654, 1.3349],

[ 0.6241, -0.1228],

[-1.4924, -1.4087],

[ 0.7791, -0.5478],

[-0.6711, -0.5625],

[-0.0553, -0.1775],

[-0.7085, 0.6464],

[ 2.4625, -1.1113]])

tensor([[ 5.2227],

[ 2.1381],

[-2.2681],

[ 5.8792],

[ 6.0258],

[ 7.6395],

[ 4.7813],

[ 4.6846],

[ 0.5918],

[12.8976]])



5、定义初始化模型参数


w = torch.normal(0,0.01,size=(2,1),requires_grad=True)

b = torch.zeros(1,requires_grad=True)



6、定义模型


def linreg(X,w,b):

    """线性回归模型"""

    return torch.matmul(X,w) + b



7、定义损失函数


def squared_loss(y_hat,y):

    """均方损失"""

    return (y_hat - y.reshape(y_hat.shape))**2 / 2

  • y_hat:预测值
  • y:真实值
  • 虽然 y_hat 和 y 元素个数是一样的,但是可能他们一个是行向量一个是列向量,因此需要使用reshape进行统一



8、定义优化算法


def sgd(params,lr,batch_size):

    """小批量梯度下降"""

    with torch.no_grad():

        for param in params:

            param -= lr * param.grad / batch_size

            param.grad.zero_()

  • params:给定的所有参数,包含 w 和 b ,他是一个list
  • lr:学习率
  • param.grad.zero_():手动将梯度设置成 0 ,在下一次计算梯度的时候就不会和上一次相关了



9、训练过程


lr = 0.03

num_epochs = 3

net = linreg

loss = squared_loss


for epoch in range(num_epoch):

    for X,y in data_iter(batch_size,features,labels):

        l = loss(net(X,w,b),y) # X 和 y 的小批量损失

        # 因为 l 的形状是(batch_size,1),而不是一个标量。l 中的所有元素被加到一起求梯度

        # 并以此计算关于[w,b]的梯度

        l.sum().backward()

        sgd([w,b],lr,batch_size) # 使用参数的梯度更新

    with torch.no_grad():

        train_l = loss(net(features,w,b),labels)

        print(f'epoch{epoch + 1},loss{float(train_l.):f}')

  • num_epoch=3:将整个数据扫描三遍
  • net:之前定义的模型
  • loss:均方损失
  • 每一次对数据扫描一遍,扫描的时候拿出一定批量的X和y

输出:

epoch1,loss0.034571

epoch2,loss0.000130

epoch3,loss0.000049



10、比较真实参数和通过训练学到的参数来评估训练的成功程度


print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')

print(f'b的估计误差:{true_b - b}')

输出:

w的估计误差:tensor([ 0.0003, -0.0004], grad_fn=<SubBackward0>)

b的估计误差:tensor([-7.7724e-05], grad_fn=<RsubBackward1>)





线性回归的实现(简洁版)


使用深度学习框架来简洁的实现线性回归模型生成数据集,使用pytorch的nn的moudule提供的一些数据预处理的模块来使实现更加简单


1、导包


import numpy as np

import torch

import torch.utils.data as data

from d2l import torch as d2l


true_w = torch.tensor([2,-3.4])

true_b = 4.2

features,labels = d2l.synthetic_data(true_w,true_b,1000)

  • 这里沐神导包的时候写的是 import torch.utils as data ,我在运行的时候会报错 AttributeError: module 'torch.utils' has no attribute 'TensorDataset' ,当我改成 import torch.utils.data as data 这种方式来进行导包之后就能够继续运行了



2、调用框架中现有的API来读取数据


def load_array(data_arrays,batch_size,is_train=True):

    """构造一个PyTorch的数据迭代器"""

    dataset = data.TensorDataset(*data_arrays)

    return data.DataLoader(dataset,batch_size,shuffle=is_train)


batch_size = 10

data_iter = load_array((features,labels),batch_size)


next(iter(data_iter))

输出:

[tensor([[-0.8249, 1.9358],

[-0.9235, -0.2573],

[-1.6639, -1.0040],

[ 0.5258, 1.1980],

[-0.4612, -0.8816],

[ 2.1845, -1.4419],

[-0.0317, 1.7666],

[-0.0510, -0.9682],

[-1.8175, 0.0168],

[ 0.5344, 0.5456]]),

tensor([[-4.0278],

[ 3.2316],

[ 4.2774],

[ 1.1869],

[ 6.2739],

[13.4859],

[-1.8532],

[ 7.3907],

[ 0.5091],

[ 3.4219]])]



3、使用框架的预定义好的层


from torch import nn

net = nn.Sequential(nn.Linear(2,1))

  • nn 是神经网络的缩写
  • nn.Linear(2,1):输入的维度是2,输出的维度是1
  • sequential:可以理解成是层的排列,将一层一层的神经网络进行排列在一起



4、初始化模型参数


net[0].weight.data.normal_(0,0.01)

net[0].bias.data.fill_(0)

  • normal_:使用正态分布来替换掉data的值(均值为0,方差为0.01)
  • fill_:将偏差直接设置为0

输出:

tensor([0.])



5、计算均方误差使用的是MESLoss类,也称为L2范数


loss = nn.MSELoss()



6、实例化SGD实例


trainer = torch.optim.SGD(net.parameters(),lr=0.03)



7、训练过程


和之前的训练过程是一样的


num_epochs = 3

for epoch in range(num_epochs):

    for X,y in data_iter:

        l = loss(net(X),y)

        trainer.zero_grad()

        l.backward()

        trainer.step()

    l = loss(net(features),labels)

    print(f'epoch {epoch + 1}, loss {l:f}')

  • trainer.step():调用step函数来对模型进行更新

输出:

epoch 1, loss 0.000304

epoch 2, loss 0.000100

epoch 3, loss 0.000100





Q&A


1、我想请问可以在Google colab上安装d2l库进行环境配置吗?或者老师推荐一些对学生党比较便宜的平台


QA P5 - 00:06




2、为啥使用平方损失而不是绝对插值呢?


QA P5 - 01:16




3、损失为什么要求平均?


QA P5 - 02:00




4、线性回归损失函数是不是通常都是mse?


QA P5 - 03:37




5、关于神经网络,我们是通过误差反馈修改参数,但是神经元没有反馈误差这一说,请问这一点是人为推导的吗?


QA P5 - 03:50




6、物理实验中经常使用 n-1 代替 n 求误差,请问这里求误差也能用 n-1 代替n吗?


QA P5 - 04:54




7、老师,不管是gd还是sgd怎么找到合适的学习率?有什么好的方法吗?


QA P5 - 05:16




8、batchsize是否会最终影响模型结果?batchsize过小是否可能导致最终累积的梯度计算不准确?


QA P5 - 06:26




9、在训练过程中,过拟合和欠拟合情况下,学习率和批次该如何进行调整呢?有什么常见的策略吗?


QA P5 - 07:45




10、针对batchsize大小的数据集进行网络训练的时候,网络中每个参数更新时的减去的梯度是batchsize中每个样本对应参数梯度求和后取得的平均值吗?


QA P5 - 08:25




11、随机梯度下降中的“随机”是值得批量大小是随机的吗?


QA P5 - 08:53




12、在深度学习上,设置损失函数的时候,需要考虑正则吗?


QA P5 - 09:19




13、为什么机器学习优化算法都采取梯度下降(一阶导算法),而不是采用牛顿法(二阶导算法),收敛速度更快,一般都能算出一阶导,二阶导也应该能算?


QA P5 - 09:44




14、学习率怎么除N,设置学习率的时候吗?


QA P5 - 12:57




15、detach()是什么作用?


QA P5 - 13:17




16、detach和pytorch中的用法一样吗?梯度为什么要去掉,这里的梯度是怎么产生的?


QA P5 - 13:44




17、这样的data-iter写法,每次都把所有输入load进去,如果数据较多的话,最后内存会爆炸吧?有什么好的办法吗?


QA P5 - 14:06




18、这里的indices为什么要转化成tensor,直接用列表不行吗?


QA P5 - 15:42




19、每次都是随机取出一部分,怎么保证最后所有数据都被拿过了?


QA P5 - 15:51




20、这里使用生成器生成数据有什么优势呢,相比return?


QA P5 - 16:41




21、如果样本大小不是批量数的整数倍,那么需要随机剔除多余的样本吗?


QA P5 - 17:12




22、优化算法里+batchsize但是最后一个batch里的样本个数没有这么多呀?


QA P5 - 18:08





23、这里学习率不做衰减吗?有什么好的学习率衰减方法吗?


QA P5 - 18:56




24、老师请问这里面是没有进行收敛判断吗?直接人为设置epoch的大小吗?


QA P5 - 19:42




25、本质上我们为什么要用SGD,是因为大部分的实际loss太复杂,推到不出导数为0的解吗?只能逐个batch去逼近?


QA P5 - 21:30




26、请问 w 为什么要随机初始化,不能用相同的值呢?


QA P5 - 22:19




27、实际中有时候网络会输出nan,nan到底是怎么出现的?为什么不是inf?数值稳定性不是会导致inf吗?


QA P5 - 22:41




28、定义网络层后一定要手动设置参数初始值吗?


QA P5 - 23:03




29、l.baxkward()这里是调用pytorch自定义的back propogation吗?


QA P5 - 23:27




30、外层for循环中最后一行l=loss(net(),labels)就是为了print吗?这里梯度要不要清零呢?会不会有什么影响?


QA P5 - 23:31




31、每个batch计算的时候,为什么要把梯度先清零?


QA P5 - 23:46




32、学习率设置过大会不会导致梯度爆炸(数值为NAN),但从数学理解上,如若上一次根据偏导数乘以学习率调整参数,就算向相反的方向在调,下一次不是就可以纠正过来吗?为什么还会爆炸的呢?


QA P5 - 24:00






----end----

08 线性回归 + 基础优化算法【动手学深度学习v2】的评论 (共 条)

分享到微博请遵守国家法律