pytorch深度学习实战lesson9

时间:2022-11-10 11:55:33

第九课 线性回归

理论部分

线性回归

案例:美国房价预测

pytorch深度学习实战lesson9

系统估价和自己实际付的钱要保持差不多的关系才能使自己赚到,那怎么样才能赚到呢,那就得有一个量良好的预估手段。

下面做出两个假设:

pytorch深度学习实战lesson9

pytorch深度学习实战lesson9

线性模型可以看做是单层的神经网络

pytorch深度学习实战lesson9

每个箭头代表一个权重,输出是o1,输入是xn

神经网络其实源于神经科学,下图是真实的神经元:

pytorch深度学习实战lesson9

有了模型之后,就要对其进行预测了,放在上面的案例里面讲也就是比较房价的真实值和预估值。

pytorch深度学习实战lesson9

这个相当于是神经网络里面的损失函数。

有了模型和损失后,下一步就是参数的学习。

可以收集一些数据点来决定参数值(权重和偏差),比如过去六个月卖的房子,这些数据就被称为是训练数据,通常来讲,训练数据越多越好。

pytorch深度学习实战lesson9

X是列向量,每个x一排一排的排好然后做转置。然后y是列向量有n个样本,每个y是实数样本。

pytorch深度学习实战lesson9

目标是找到一个w和b使得损失函数的结果最小。

pytorch深度学习实战lesson9

总结

1、线性回归是对n维输入的加权,外加偏差。

2、使用平方损失来衡量预测值和真实值的差异。

3、线性回归有显示解。

4、线性回归可以看做是单层神经网络。

基础优化方法

优化方法最常用的是梯度下降算法。

pytorch深度学习实战lesson9

这里的学习率不能太大也不能太小,因为太小的话耗费资源很多,太大的话就错过最优值了。

pytorch深度学习实战lesson9

pytorch深度学习实战lesson9

pytorch深度学习实战lesson9

总结:

1、梯度下降通过不断沿着反梯度方向更新参数求解

2、小批量随机梯度下降是深度学习默认的求解算法

3、两个重要的超参数是批量大小和学习率

实践部分

线性回归从零开始实现:

#¥¥¥¥¥<<<<<+++++-----线性回归从零开始实现-----+++++>>>>>¥¥¥¥¥#
#从零开始实现整个方法,包括流水线、模型、损失函数和小批量随机梯度下降优化器
#%matplotlib_inline                                 #用于随机梯度下降和初始化权重
import matplotlib.pyplot as plt
import torch
import random
from d2l import torch as d2l
#根据带有噪声的线性模型构造一个人造数据集,我们使用线性模型参数w=[2,-3,4]T、b=4.2和噪声e生成数据集及其标签:
#y=Xw+b+e
def synthetic_data(w,b,num_examples):               #构造人造数据集,num_examples是要生成的样本数量
    '''生成 y=Xw+b+e'''
    X=torch.normal(0,1,(num_examples,len(w)))       #构造x是方差为零均值为1的随机数,它的大小是样本数,它的列数是w的长度
    y=torch.matmul(X,w)+b                           #构造y
    y+=torch.normal(0,0.01,y.shape)                 #又让y加上了一个噪音。噪音是方差为0,均值为0.01,形状与y一样
    return X,y.reshape((-1,1))                      #把x和y做出列向量返回

true_w=torch.tensor([2,-3.4])                       #定义真实的w和真实的b
true_b=4.2
features,labels=synthetic_data(true_w,true_b,1000)  #然后通过函数计算出特征和标注

#看一下训练样本的样子以及样本分布图
print('features:',features[0],'\nlable:',labels[0]) #输出:features: tensor([0.4727, 0.1651]) lable: tensor([4.5845])
                                                    #可见,第零个样本是长为2的一个向量,其标号是标量
print('########################################################')
d2l.set_figsize()
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(),1)#detach的作用是在pytorch的一些版本中,
                                                                            # 需要从计算图中detach处来才能转到numpy中
plt.show()                                          #画完后我们可以看到是个线性相关的样本集
#定义一个data_iter 函数, 该函数接收批量大小、
def data_iter(batch_size, features, labels):
                                                    # 特征矩阵和标签向量作为输入,生成大小为batch_size的小批量
    num_examples = len(features)
    indices = list(range(num_examples))             #生成每个样本的索引(每个样本是随机读取的,没有特定顺序)
    random.shuffle(indices)                         #打乱下标
    for i in range(0, num_examples, batch_size):    #从0开始到num_examples,每次走batch_size个
        batch_indices = torch.tensor(
indices[i:min(i +batch_size, num_examples)])        #这里要获取批量索引,把索引存入张量中,后面的min操作是防止超过总量,所以要取最小的
        yield (features[batch_indices],             #通过indices生成特征和标号。yield相当于是Python中的重复操作,
               labels[batch_indices])               #每次都生成一个x一个y,生成完后再调用函数,直到全部完成为止。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):#给我一些样本标号,每次随机选取b个样本返回出来
    print(X, '\n', y)
    break
print('########################################################')
#定义初始化模型参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)#w是2*1的,2表示特征,1表示标签,0表示方差,0.02表示均值
b = torch.zeros(1, requires_grad=True)              #b初始赋为零,1表示它是1维的标量
                                                    #w和b的requires_grad都是true表示它们要计算梯度,实时更新
#定义模型
def linreg(X, w, b):                                #-----线性回归模型
    return torch.matmul(X, w) + b
#定义损失函数
def squared_loss(y_hat, y):                         #-----均方损失
    return (y_hat - y.reshape(y_hat.shape))**2 / 2  #y_hat是预测值,y是真实值,要保证两者形状统一,差先按元素平方然后按元素除二
#定义优化算法
def sgd(params, lr, batch_size):                    #-----小批量随机梯度下降,params是个列表,里面包含w和b
    with torch.no_grad():                           #它不需要计算梯度
        for param in params:                        #每一个w和b
            param -= lr * param.grad / batch_size   #param=param-lr*param.grad/batch_size除以batch_size的操作是求均值操作,
                                                    #由于是线性关系,所以在此求均值也可。
            param.grad.zero_()                      #梯度置零
#开始训练
#先定义超参数
lr = 0.03                                           #学习率
num_epochs = 3                                      #把整个数据扫三遍,也就是进行三轮训练
net = linreg                                        #模型就是之前定义的linreg
loss = squared_loss                                 #损失还是之前的定义的
for epoch in range(num_epochs):                     #每一次对数据扫一遍
    for X, y in data_iter(batch_size, features, labels):#每次拿出一组x和y
        l = loss(net(X, w, b), y)                   #把预测的y和真实的y做损失,出来的损失是长为批量大小的向量
        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.mean()):f}')
print('########################################################')
#比较真实参数和通过训练学到的参数来评估训练的成功程度
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

#可以实验一下梯度很大或梯度很小时的情况,看看其损失有什么变化
#梯度很大时
'''#先定义超参数
lr = 10                                           #学习率
num_epochs = 10                                      #把整个数据扫三遍,也就是进行三轮训练
net = linreg                                        #模型就是之前定义的linreg
loss = squared_loss                                 #损失还是之前的定义的
for epoch in range(num_epochs):                     #每一次对数据扫一遍
    for X, y in data_iter(batch_size, features, labels):#每次拿出一组x和y
        l = loss(net(X, w, b), y)                   #把预测的y和真实的y做损失,出来的损失是长为批量大小的向量
        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.mean()):f}')'''
#梯度很小时,可以通过增加学习轮次得到好一些的损失值,但是成本有点大
'''#梯度很大时
#先定义超参数
lr = 0.001                                           #学习率
num_epochs = 3                                      #把整个数据扫三遍,也就是进行三轮训练
net = linreg                                        #模型就是之前定义的linreg
loss = squared_loss                                 #损失还是之前的定义的
for epoch in range(num_epochs):                     #每一次对数据扫一遍
    for X, y in data_iter(batch_size, features, labels):#每次拿出一组x和y
        l = loss(net(X, w, b), y)                   #把预测的y和真实的y做损失,出来的损失是长为批量大小的向量
        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.mean()):f}')'''

features: tensor([ 0.3840, -0.3376]) 
lable: tensor([6.1121])
########################################################
tensor([[-1.1147e+00, -8.3294e-01],
        [-6.0542e-04,  2.1474e-02],
        [ 9.3246e-01,  1.4499e+00],
        [ 4.7537e-01, -2.9177e-01],
        [ 1.8271e+00,  1.3387e-01],
        [-3.8728e-01, -1.7065e+00],
        [-2.3963e+00, -1.5596e-01],
        [-1.7679e+00, -6.3735e-01],
        [ 4.8884e-01,  1.1035e+00],
        [-6.9350e-01,  2.9550e-01]]) 
 tensor([[ 4.7953],
        [ 4.1320],
        [ 1.1461],
        [ 6.1476],
        [ 7.3970],
        [ 9.2280],
        [-0.0416],
        [ 2.8342],
        [ 1.4362],
        [ 1.8041]])
########################################################
epoch 1, loss 0.026386
epoch 2, loss 0.000093
epoch 3, loss 0.000046
########################################################
w的估计误差: tensor([0.0001, 0.0004], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0007], grad_fn=<RsubBackward1>)

线性回归简洁实现:

#所谓的简洁实现就是通过使用深度学习框架来简洁地实现 线性回归模型 生成数据集
import numpy as np
import torch
from torch.utils import data                                #处理数据的模块
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])                            #真实的w
true_b = 4.2                                                #真实的b
features, labels = d2l.synthetic_data(true_w, true_b, 1000) #生成特征和标签
#调用框架中现有的API来读取数据
def load_array(data_arrays, batch_size, is_train=True):
    """构造一个PyTorch数据迭代器。"""
    dataset = data.TensorDataset(*data_arrays)              #先将x,y变成dataset数据集
    return data.DataLoader(dataset, batch_size,             #然后调用dataloader,每次挑b个样本出来
                           shuffle=is_train)                #如果在训练过程中的话就要随机打乱顺序

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

next(iter(data_iter))                                       #得到一个x和一个y
#使用框架的预定义好的层
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))                        #要指定输入输出维度,linear是个线性层,
                                                            # 把他放在sequential里的话就相当于把每层给罗列起来了
#初始化模型参数
net[0].weight.data.normal_(0, 0.01)                         #把权重初始化
net[0].bias.data.fill_(0)                                   #把偏重初始化
#计算均方误差使用的是MSELoss类,也称为平方 L2 范数
loss = nn.MSELoss()                                         #均方误差的调用
#实例化 SGD 实例
trainer = torch.optim.SGD(net.parameters(), lr=0.03)        #梯度下降的调用,要传入所有参数(w,b),和学习参数
#训练过程代码与我们从零开始实现时所做的非常相似
num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X), y)          #net自己带模型参数了
        trainer.zero_grad()          #先梯度清零
        l.backward()                 #pytorch已经做sum了
        trainer.step()               #模型更新
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')
#比较生成数据集的真实参数和通过有限数据训练获得的模型参数
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

epoch 1, loss 0.000369
epoch 2, loss 0.000104
epoch 3, loss 0.000105
w的估计误差: tensor([-0.0004, -0.0009])
b的估计误差: tensor([-5.7220e-06])