• 动手学深度学习 | 线性回归+基础优化算法 | 06


    线性回归

    线性回归是机器学习中最基础的模型,也是后面我们理解所有模型的一个基础。

    之所以在深度学习中讲解线性模型,是因为它可以看作是一个单层神经网络(输出层可以不看做一个层,将权重和输入层看作一层)。

    训练数据当然是越多越好,但是也会受限于很多事情,房子售卖数据非常有限。所有我们有很多技术来处理,当你的数据不够的时候怎么办?之后会有非常多的算法来探讨这个问题。

    评估模型在每个数据上的损失,求均值,就可以得到损失函数的一个结果。

    上面损失函数采用的均方误差,也就是第二范数。

    最小化损失来学习参数:我们的目标就是找到一个(w,b)是的 (argmin_{loss}(X,y,w,b))

    当然因为是线性模型,所有是有显示解的(就是可以直接求解)。

    当然线性方程也是唯一一个有最优解的模型,后面的模型都不会有最优解了。

    基础优化算法

    当一个模型没有显示解的时候,应该怎么办呢?首先会挑选一个参数的随机初始值,记作(w_0),然后随后不断地去更新(w_0),去接近我们的最优解。

    (w_{t-1}):之前的参数,(eta):之前的参数。

    我们来直观理解一下,这是一个二次函数的等高线,黄线方向就是负梯度方向。

    学习率不能太小,太小的话会要“走很多步”,计算梯度是一件很贵的事情。

    学习率太大的话,会一直震荡。

    学习率不能太大,也不能太小,后面会有一系列的教程,教大家如何选择学习率。

    深度学习一般都是使用小批量随机梯度下降

    每次计算梯度,我们要对损失函数求导,损失函数是对我们所有样本的一个平均损失,所以意味着求一次梯度,我们要把所有的样本重新计算一遍,这是很贵的一件事情,计算一次可能需要几个小时,我们训练过程可能有几百步,几千步的样子,这样的话代价太大了。

    那么一个近似的办法怎么做呢?我们近似的话,我们可以随机采用b个样本,用它的平均,来近似整个样本的平均。当b很大的时候,近似的比较精确,当b比较小的时候,近似的不那么精确。b比较小的话,计算是比较容易的,因为计算复杂度和样本数量是线性相关的。所以b是批量大小,是另外一个重要的超参数。

    同样的,批量大小不能太大,也不能太小。

    太小,每次计算都是几个样本的梯度,很难以并行,之后我们会利用GPU来计算,GPU的话动不动就几百上千个核,如果批量太小,那么就不能很好的利用GPU。

    太大,内存和批量大小是成正比的,特别是用GPU的话,内存是一个很大的瓶颈。还有一个极端的例子,如果样本太大,有很多相似的样本,其实这就是浪费了计算。

    后面也会教大家如何计算这个批量的大小。

    梯度下降就是不断沿着梯度的反方向来进行模型的求解,它的好处就是不用知道显示解是什么样子,我只要知道不断的怎么求导数就行了。

    小批量随机梯度下降是深度学习默认的求解方法,虽然还有更好的,但是一般来说,它是最稳定的,也是最简单的,所以我们通常使用它。其中批量大小学习率是两个非常重要的超参数。

    当然优化算法是一个非常大的方向,后面会在讨论,当然这个小批量随机梯度下降算法已经够后面用几个星期了。

    线性回归的从零开始实现

    所谓的从零开始实现,就是我们不使用任何的深度学习框架,而且只使用一些最简单的在tensor上面的计算,这样的好处是可以帮助大家从底层理解每个模块是如何具体实现的。

    当然实际应用中,我们不会真的从零开始,但确实一个很好的教学工具。

    image-20210918212407282

    操纵总结

    # 人工生成数据集
    def syntheic_data(w,b,num_examples):
    	X = torch.normal(0,1,(num_examples,len(w)))
    	y = torch.manmul(X,w) + b
    	y += torch.normal(0,0.001,y.shape) # 加入噪声
    	return X,y.reshape(-1,1)
    
    ture_w = torch.tensor([2,-3.4])
    ture_b = 4.2
    features,labels = syntheic_data(true_w,true_b,1000)
    
    # 批量读取数据的生成器
    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]
    
    # 定义 初始化模型参数
    w = torch.normal(0,0.01,size=(2,1),requires_grad=True)
    b = torch.zeros(1,requires_grad=True)
    
    # 定义模型
    def linreg(X,w,b)
    	return torch.manmul(X,w)+b
    
    # 定义损失函数
    def squared_loss(y_hat,y):
    	# 这是是没有求平均的
    	return (y_hat-y.reshape(y_hat.shape))**2 / 2
    
    # 定义优化算法
    def sgd(params,lr,batch_size);
    	with torch.no_grad():
    		for param in parms:
    			param -= lr*parm.grad/batch_size
    			param.grad_zero_()
    
    # 训练过程
    lr=0.03
    num_epochs = 3
    net = linreg
    loss = squared_loss
    batch_size = 10
    
    for epoch in num_epochs:
    	for X,y in data_iter(batch_size,features,labels)
    		l = loss(net(X,w,b),y)
    		l.sum().backward()
    		sgd([w,b],lr,batch_size)
    	with torch.grad():
    		train_l = loss(net(features,w,b),labels)
    		print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
    

    线性回归的简洁实现

    所谓的简洁实现,就是使用Pytorch中提供的工具,来让实现更加的简单。

    操作总结

    import numpy as np 
    import torch from torch.utils 
    import data from d2l 
    import torch as d2l 
    
    true_w = torch.tensor([2, -3.4])
    true_b = 4.2
    
    feature,labels = d2l.syntheic_data(true_w,true_b,1000) # 人工构造数据
    
    # 使用框架生成 data_iter
    def load_array(data_arrays,batch_size,is_train=True):
    	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)
    
    # 网络模型定义
    from torch import nn
    net = nn.Sequential(nn.Linear(2,1)) # w,b参数都封装在里面
    
    # 初始化模型参数
    net[0].weight.data.normal_(0, 0.01),net[0].bias.data.fill_(0)
    
    # loss_fn
    loss = nn.MSELoss()
    
    # 实例化SGD
    trainer = torch.optim.SGD(net.parameters(), lr=0.03)
    
    # 训练代码
    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() # BP计算梯度
    		l.step() # 更新参数
    	l = loss(net(features),labels)
    	print(f'epoch {epoch + 1}, loss {l:f}')
    
    

    QA

    1. 有没有什么比较好用的云平台?

    google colab、kaggle上的notebook

    1. 为什么使用平方损失而不是绝对差值呢?

    我们后面会讲平方损失和绝对值差值?其实二者的区别不大,最早大家用平方损失函数,是因为绝对差值是一个不可导的函数。

    1. 损失为什么求平均?

    因为是batch_size个样本计算的损失,所以要除以batch_size。当然如果没有在对loss除batch_size,也可以对学习率除batch_size,其实都是一样的。

    1. 线性回归的loss是不是通常都是mse?

    是的,通常都是这样子的

    1. 不管是gd还是sgd,怎么找到合适的学习率?

    说实话,这个就是靠经验进行调整的。

    1. batch_size是否会最终影响模型结果?batch_size过小是否可能导致最终累计的梯度计算不准确?

    其实batch_size小点是好的,大了反而不行。这个可能是比较反自觉的。

    我们后面会讲到丢弃发dropout的时候,batch_size小,也就是在同样的计算下,也就是扫数据扫10遍,batch_size小,其实对收敛越好。为什么?SGD其实实际上是给模型带来了噪音,采样越小,实际上噪音越多,比如100w张图片,每次只采样2张图片,那么噪音是会很大的,和真实的方向就会差很远。

    但是噪音对神经网络是件好事情,因为现在的深度神经网络都太复杂了,一定的噪音使得你不会走偏,(大家说你教小孩的时候不要一直夸他,糙一点,不见得对小孩是一件坏事情,可以更加鲁棒),可以使得模型更加鲁棒,对各种噪音的容忍度越来越好,整个模型的泛化性就会更好。

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

    对的,因为梯度是线性的,所以一个一个梯度求,等价于一批梯度求和在取均值。因为梯度是一个线性关系,是可以这么进行操作的。

    1. 随机梯度下降中的“随机”是指的批量大小是随机的吗?

    批量大小是固定的,随机指的是随机对样本进行抽样。

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

    需要的,但是正则项一般不放在损失函数中,我们把(l_2)损失函数和(loss)是分开的。

    后面会讲到正则项,但其实没有太多用,我们还有其他很多很多的方法来做正则。

    1. detach()是什么作用?

    可以理解成把变量从计算图中抽取出来,就不要计算梯度。

    1. 这样的data_iter写法,每次都把所有的输出load进去, 如果数据多的话,最后内存会爆掉吧?有什么办法吗?

    是的,这样内存会爆掉。

    如果数据有100个G的话,这样直接load进内存当然是不对的。但是这本教材中包括一般的数据集都不会有那么大,一般服务器的内存几十个G还是有的,10G、20G的数据直接load进去问题也不大 。

    当然一般都是将数据放在硬盘上,然后在一点一点从硬盘中进行读取,如果担心效率问题,可以一次从硬盘中多读取几个批次的数据进入内存。

    1. 如果样本大小不是batch_size的整数倍,那需要随机剔除多余的样本吗?

    这是一个细节的问题,假设样本数量为100,但是batch_size取的是60。

    • 最常见的做法就是拿到一个小一点的样本,也就是大小为40。
    • 还有就是可以直接丢弃最后这个40的样本
    • 可以和下一个epoch要20个样本
    1. 优化算法里 /batch_size 但是最后一个batch里的样本个数没有这么多?

    是的,这里是我们偷懒了,实际上还是需要进行一个特判。

    真实有多少个,就应该除于多大的batch_size。

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

    理论上,SGD要收敛,那么就是要不断的把学习率变小变小变小,但实际上有很多其他的方法,学习率不做衰减的话,其实也问题不大。

    后面会讲一种方法,会根据梯度的大小来调整学习率,所以不做衰减其实问题也不大。

    1. 这里没有进行收敛的判断吗?直接认为设置epoch的大小吗?
    • 最简单的判断收敛的方法就是看两个epoch之间的loss差别如果在1%的时候,那么就可以认为是收敛了。

    • 或者可以有一个验证数据集,如果验证数据集的精度没有增加了,那么也可以认为是收敛了。

    其实如果算力是支持的话,多跑几次epoch是没有关系的,就算是loss没有下降,可能在进行微调。理解为读书读了10遍,在读一遍其实也没有关系。

    1. 定义网路有一定要手动设置参数初始值吗?

    其实是不用的,网络有自己的参数的默认值。

    这里手动设置w和b的初始值是为了和之前的从零开始实现线性回归对应上。

    后面就不会手动设置初始值了,也设置不过来(太多参数了)。

    1. 外层forloop中最后一行l=loss(net(),labels)就是为了print吗?这里梯度要不要清零呢?

    是的,就是为了print。

    这里不用清零梯度,因为这里只是forward,而没有backward去更新参数。

  • 相关阅读:
    InetAddress.getLocalHost().getHostAddress() 问题
    解决:Element ‘dependency‘ cannot have character [children], because the type‘s content type is elementon
    生成二维码下载二维码
    Spring boot 如何获得客户端 ip 地址以及根据主机名获得 ip 地址
    无法打开到主机的连接。 在端口 23: 连接失败
    在vue的项目中怎样修改浏览器窗口的 logo
    解决:SqlSession[xxx] was not registered for synchronization because synchronization is not active
    解决:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题
    [docker]在本地/内部配置部署docker镜像仓库
    1
  • 原文地址:https://www.cnblogs.com/Rowry/p/15310866.html
Copyright © 2020-2023  润新知