线性回归
线性模型
此处箭头即权重
衡量预估质量
训练数据
参数学习
预测损失计算:每个实际y减去每个预测y(<x,w>-b)插值的平方求和除以2n算出预测损失。
利用最小化损失学习参数:找到最合适的向量w和b使得损失最小。
显示解
线性模型的损失是一个凸函数,通常有最优解,最优解需满足单数为0即可
将变量向量X设为X = [ X ,1 ],参数向量W设为 W = [ W , b ],
线性回归总结
基础优化算法
η为学习率(步长,是一个超参,需要人为指定的值),乘以变化梯度,表示延梯度方向将增加的损失函数值,η表示沿梯度方向每次都多远。
梯度:就是沿函数值增加最大的方向,负梯度是下降最大的方向
不能太小:因为计算梯度是一件代价很大的操作,如果η太小,想要达到最好效果计算梯度的次数就会显著增加,付出极大的计算资源。
不能太大:如果η太大,可能会一步迈过最佳参数值,会使得一直震荡,不能得到很好的结果。
用整个样本b的平均来近似整个数据的损失,样本b越大,近似越好。
总结
QA:
-
之所以使用平方损失而不用绝对差值,是因为绝对差值又是不可导。
-
损失要求平均,是损失太大,如果损失不除以n,那么求梯度的时候就要除以n
-
线性回归损失函数通常都是MSE
-
batch_size越小对收敛越好,因为随机梯度下降实际是带来了噪音,采样样本越小,噪音越多,但是噪音对当前的神经网络是有益的。
-
理论上学习率和批量大小对收敛的影响不大
-
每个参数更新时减去的梯度是batch_size中每个样本对应参数梯度求和后的均值
-
随机梯度下降,采样是随机的,批量大小是一样的
-
真实的损失函数是一个很复杂的曲面
-
detach()就是把数据从计算梯度的计算图中拿出来,不参与计算梯度
-
SGD收敛的话,学习率η需要逐步衰减
-
每个batch计算的时候,需要把梯度清零,如果不清零的话,PyTorch会在之前的梯度上做累加
编写实现线性回归
%matplotlib inline
import torch
import random #要随机梯度下降和初始化权重
from d2l import torch as d2l
import numpy
# 数据张量生成
def synthetic_data(w,b,num_examples):
#生成y = wx + b
#生成x是均值为0方差为1的张量,(num_examples,len(w))指的是张量的形状
x = torch.normal(0,1,(num_examples,len(w)))
#设计y,matmul是矩阵乘法
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)
print("features:",features[0],'\nlabels:',labels[0])
#该函数接受 批量大小,特征矩阵和标签向量作为输入,生成大小为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会返回一个x,y batch_indices是一个随机标号
yield features[batch_indices],labels[batch_indices]
'''带有yield的函数在Python中被称之为generator(生成器),也就是说,
当你调用这个函数的时候,函数内部的代码并不立即执行 ,
这个函数只是返回一个生成器(Generator Iterator)。'''
batch_size = 10
# data_iter是一个生成器,每次调用会在上一个状态的基础上再次调用
for x,y in data_iter(batch_size,features,labels):
print(x,'\n',y)
# break是因为只取了一个小批量
break
# 定义初始化模型参数
w = torch.normal(0,0.01,size = (2,1),requires_grad = True)
b = torch.zeros(1,requires_grad = True)
#定义线性回归模型 linear regression
def linreg(X,w,b):
# 线性回归模型
return torch.matmul(X,w)+b
#定义损失函数 y_hat 预测值, y 真实值
def squared_loss(y_hat,y):
'''均方损失,统一 y 的形状'''
return (y_hat - y.reshape(y_hat.shape))**2 / 2
#定义优化算法
'''params里面包含了w,b lr为学习率'''
def sgd(params,lr,batch_size):
'''小批量梯度下降'''
with torch.no_grad():
for param in params:
# 之前求损失函数没有计算均值,所以这里除以batch_size, w,b都需要计算梯度
param -= lr*param.grad/batch_size
# 梯度手动置为0
param.grad.zero_()
#训练过程
'''指定超参'''
lr = 0.03 #学习率
num_epochs = 3 #扫描数据次数
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for x , y in data_iter(batch_size,features,labels):
l = loss(net(x,w,b),y) #预测和实际的小批量损失
#因为 l 的形状是(batch_size,1),是一个长为批量大小的向量,而不是一个标量。
#并以此计算关于 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.mean()):f}')
print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}')
PyTorch实现线性回归
%matplotlib inline
import torch
import random #要随机梯度下降和初始化权重
from d2l import torch as d2l
import numpy
from torch.utils import data
# 数据生成
def synthetic_data(w,b,num_examples):
#生成y = wx + b
#生成x是均值为0方差为1的张量,(num_examples,len(w))指的是张量的形状
x = torch.normal(0,1,(num_examples,len(w)))
#设计y,matmul是矩阵乘法
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)
print("features:",features[0],'\nlabels:',labels[0])
# 调用框架的API读取数据
def load_array(data_arrays,batch_size,is_train=True):
"""构造一个PyTorch数据迭代器"""
"""
TensorDataset 可以用来对 tensor 进行打包,包装成dataset。
就好像 python 中的 zip 功能。
该类通过每一个 tensor 的第一个维度进行索引。
TensorDataset 中的参数必须是 tensor。
"""
# dataset = torch.utils.data.TensorDataset(*data_arrays)
"""
* 表示解包操作,表示将一个包含多个元素的元组、列表、集合等数据结构
解压为多个独立的元素。接受任意多个参数并将其放在一个元组中。
"""
dataset = data.TensorDataset(*data_arrays)
# return torch.utils.data.DataLoader(dataset,batch_size,shuffle=is_train)
"""
使用DataLoader函数每次从dataset中随机挑选batch_size个样本,
shuffle指是否随机打乱顺序
"""
return data.DataLoader(dataset,batch_size,shuffle=is_train)
batch_size = 10
data_iter = load_array((features,labels),batch_size)
#把函数调用转成一个迭代器
next(iter(data_iter))
# 使用框架预定义好的层
from torch import nn
# Sequential 可以理解为将一个一个的层按顺序放在一起,初始化模型
net = nn.Sequential(nn.Linear(2,1))
"""
net通过第1层访问线性模型,weight访问w,
data就是真是的数据,narmal指的是用正态分布替代data的值
"""
net[0].weight.data.normal_(0,0.01)
"""bias值得是偏差"""
net[0].bias.data.fill_(0)
# 计算均方误差使用 MSELoss 类,也称平方L2范数
loss = nn.MSELoss()
# 实例化SGD实例, net.parameters 里面包含了所有参数
trainer = torch.optim.SGD(net.parameters(),lr=0.03)
# 训练模块
num_epochs = 3
for epoch in range(num_epochs):
#拿出小批量进行训练
for x,y in data_iter:
# net本身带有模型参数,就不需要 w,b
l = loss(net(x),y)
# trainer是优化器,先把梯度清零
trainer.zero_grad()
# 梯度清零过后进行梯度的前向传播
l.backward()
# 有梯度之后,调用setp函数对模型进行更新
trainer.step()
# 把整个data放入模型计算损失
l = loss(net(features),labels)
print(f'epoch{epoch+1},loss:{l:f}')