第九课 线性回归
理论部分
线性回归
案例:美国房价预测
系统估价和自己实际付的钱要保持差不多的关系才能使自己赚到,那怎么样才能赚到呢,那就得有一个量良好的预估手段。
下面做出两个假设:
线性模型可以看做是单层的神经网络
每个箭头代表一个权重,输出是o1,输入是xn
神经网络其实源于神经科学,下图是真实的神经元:
有了模型之后,就要对其进行预测了,放在上面的案例里面讲也就是比较房价的真实值和预估值。
这个相当于是神经网络里面的损失函数。
有了模型和损失后,下一步就是参数的学习。
可以收集一些数据点来决定参数值(权重和偏差),比如过去六个月卖的房子,这些数据就被称为是训练数据,通常来讲,训练数据越多越好。
X是列向量,每个x一排一排的排好然后做转置。然后y是列向量有n个样本,每个y是实数样本。
目标是找到一个w和b使得损失函数的结果最小。
总结
1、线性回归是对n维输入的加权,外加偏差。
2、使用平方损失来衡量预测值和真实值的差异。
3、线性回归有显示解。
4、线性回归可以看做是单层神经网络。
基础优化方法
优化方法最常用的是梯度下降算法。
这里的学习率不能太大也不能太小,因为太小的话耗费资源很多,太大的话就错过最优值了。
总结:
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])