参考https://blog.csdn.net/m0_45521766/article/details/126621549?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167532896916782429722417%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167532896916782429722417&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-126621549-null-null.142^v72^control_1,201^v4^add_ask&utm_term=%E6%9D%8E%E6%B2%90%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E4%BB%A3%E7%A0%81%E8%AE%B2%E8%A7%A3&spm=1018.2226.3001.4187
一、线性归回的实现
1.线性模型的构造及输出
构造线性模型及人造数据集
输出结果
可视化
代码实现:
构造线性模型及人造数据集:
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w,b,num_examples):
"""生成 y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w)))
"""随机数均值为0,方差为1,一共有n行,列数与w的长度相等,表示有多少个features"""
y = torch.matmul(X, w) + b
"""求y"""
y += torch.normal(0, 0.01, y.shape)
"""加入了一个噪音,均值为0,方差为0.01,形状和y相同"""
return X, y.reshape((-1, 1))
"""X, y做成列向量返回 y.reshape((-1,1))将y转化成1列"""
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
生成随机数torch.normal()
生成函数torch.matmul()
将x,y做成列向量返回y.reshape(-1,1)
理解:函数返回了x,y即为features,labels,此函数定义了features和labels的关系。
输出结果:
print('features:', features[0], '\nlabel:', labels[0])
# 运行结果
features: tensor([-0.7228, 0.3353])
label: tensor([1.6056])
理解:这里只输出了一组features,labels的值进行观察
可视化显示:
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), #detach()分离出数值,不再含有梯度
labels.detach().numpy(), 1); #scatter()函数的最后一个1是绘制点直径大小
#把feature的第一列和labels绘出来,是有线性相关的性质
#显示结果如下图:
2.取出小批量数据重新构造数据集
梯度下降是通过不断沿着反梯度方向更新参数求解,小批量随机梯度下降是深度学习默认的求解算法
实现:构造一个函数,接受所有数据最终输出一定的小批量。
# 读取小批量的函数
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): # 每次从0开始到num_examples,每次跳batch_size个大小
batch_indices = torch.tensor(
indices[i:min(i + batch_size, num_examples)]) # 把batch_size的index找出来,因为可能会超出我们的样本个数,所以最后如果没有拿满的话,会取出最小值,所以使用min
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
从数字转为标号list(range(num_examples))
将标号打乱random.shuffle(indices)
理解:找到features的个数后标号,并将原来的顺序打乱,遍历所有的序号,按照设定好的小批量取值,取出样本个数规定的值,最终利用yield将取出的值暂存,最终得到的结果的X是[10,2]向量,y的结果是[10,1]向量。
3.定义初始化的模型参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True) # w:size为2行1列,随机初始化成均值为0,方差为0.01的正态分布,requires=true是指需要计算梯度
b = torch.zeros(1, requires_grad=True) #对于偏差来说直接为0,1表示为一个标量,因为也需要进行更新所以为True
#广播机制:当我们用一个向量加一个标量时,标量会被加到每一个分量上
理解:随机生成w和b,对模型进行初始化,为接下来的逼近真实值做准备
4.定义模型
def linreg(X, w, b):
"""线性回归模型。"""
return torch.matmul(X, w) + b #矩阵乘以向量再加上偏差
5.定义损失函数
def squared_loss(y_hat, y): #y_hat是预测值,y是真实值
"""均方损失。"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2 #按元素做减法,按元素做平方,再除以2 (这里没有做均方)
6.定义优化算法
def sgd(params, lr, batch_size): # 优化算法是sgd,他的输入是:params给定所有的参数,这个是一个list包含了w和b,lr是学习率,和batch_size大小
"""小批量随机梯度下降。"""
with torch.no_grad(): # 这里更新的时候不需要参与梯度计算所以是no_grad
for param in params: # 对于参数中的每一个参数,可能是w可能是b
param -= lr * param.grad / batch_size # 参数减去learning rate乘以他的梯度(梯度会存在.grad中)。上面的损失函数中没有求均值,所以这里除以了batch_size求均值,因为乘法对于梯度是一个线性的关系,所以除以在上面损失函数那里定义和这里是一样的效果
param.grad.zero_() # 把梯度设置为0,因为pytorch不会自动的设置梯度为0,需要手动,下次计算梯度的时候就不会与这次相关了
# 我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size)来归一化步长,这样步长大小就不会取决于我们对批量大小的选择
# 更新数据时不需要求导
# pytorch 会不断累加变量的梯度,所以每更新一次参数,就要让其对应的梯度清零
理解:使用小批量随机梯度下降作为优化算法,计算每一个参数(w,b)的梯度更新,这里除以了batch_size因为在上面求损失函数的时候没有除。在一开始的更新中不需要参与梯度计算所以是no_grad,在最后将梯度设置为0,因为pytorch 不会自动清零,梯度会随着变量更新累加,所以让对应梯度为0.
7.训练过程
lr = 0.03 # 首先指定一些超参数:学习率为0.03
num_epochs = 3 # epoch为3表示把整个数据扫3遍
net = linreg # network为linreg前面定义的线性回归模型
loss = squared_loss # loss为均方损失
for epoch in range(num_epochs): # 训练的过程基本是两层for循环(loop),第一次for循环是对数据扫一遍
for X, y in data_iter(batch_size, features, labels): # 对于每一次拿出一个批量大小的X和y
l = loss(net(X, w, b), y) # 把X,w,b放进network中进行预测,把预测的y和真实的y来做损失,则损失就是一个长为批量大小的一个向量,是X和y的小批量损失
#l(loss)的形状是('batch_size',1),而不是一个标量
l.sum().backward() #对loss求和然后算梯度。计算关于['w','b']的梯度
sgd([w, b], lr, batch_size) #算完梯度之后就可以访问梯度了,使用sgd对w和b进行更新。使用参数的梯度对参数进行更新
#对数据扫完一遍之后来评价一下进度,这块是不需要计算梯度的,所以放在no_grad里面
with torch.no_grad():
train_l = loss(net(features, w, b), labels) #把整个features,整个数据传进去计算他的预测和真实的labels做一下损失,然后print
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
# 求和本身是让l(即loss)以标量的形式表现出来(通过sum()转化标量之后在求梯度)(一般都是对一个标量进行求导,所以我们先对y进行求和再求导:见前面自动求导笔记)。
# 不求和是向量,梯度算下来就是变成矩阵了,形状没有办法对应
# 求梯度是对于l中每一个分量都是单独求的,l(loss)是一个向量每个元素表示一个样本的误差除以批量数
# 如果没有no_grad,在评估模型的时候进行梯度优化过程了
#运行结果如下:
epoch 1, loss 0.047467
epoch 2, loss 0.000196
epoch 3, loss 0.000047
理解:一开始的synthetic_data没有用上,使用了linreg做线性回归模型。两层循环,外层是重复,内层的步骤为:拿出小批量——计算损失——损失求和求梯度——对梯度做优化
8.比较真实参数和通过训练学到的参数来评估训练的成功程度
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
# 运行结果如下:
w的估计误差: tensor([ 0.0003, -0.0010], grad_fn=)
b的估计误差: tensor([0.0010], grad_fn=)
二、线性回归的简洁实现
1. 通过使用深度学习框架来简洁的实现 线性回归模型 生成数据集
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
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
# 构造一个真实的w和b,然后通过人工数据合成函数生成我们需要的features和labels
理解:真是的w和b及通过定义函数算出来的特征和标签。
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))
# 假设我们已经有features和labels了,我们把他做成一个List传到tensor的dataset里面,把我们的X和y传进去,得到pytorch的一个dataset,(也就是说dataset里面是由两部分组成,features和labels)
# dataset里面拿到数据集之后我们可以调用dataloader函数每次从里面随机挑选batch_size个样本出来,shuffle是指是否需要随机去打乱顺序,如果是train则是需要的
# 构造了一个data_iter(可迭代对象)之后,然后用iter()转成python的一个迭代器,再通过next()函数得到一个X和y
# TensorDataset:把输入的两类数据进行一一对应,一个*表示解包(见python书的P175,一个*表示使用一个已经存在的列表作为可变参数)
# DataLoader:构建可迭代的数据装载器
# enumerate:返回值有两个,一个是序号,一个是数据(包含训练数据和标签)
#运行结果如下:
[tensor([[-1.6881, 0.5505],
[-0.5636, -0.4053],
[-0.5162, -1.8817],
[-2.0505, -1.2795],
[-0.3089, -0.7559],
[-1.0222, 0.6298],
[-1.4214, -1.4331],
[-0.3923, 0.3843],
[-0.5184, 0.5693],
[-1.1609, 1.0831]]),
tensor([[-1.0346e+00],
[ 4.4640e+00],
[ 9.5674e+00],
[ 4.4545e+00],
[ 6.1507e+00],
[-4.5305e-04],
[ 6.2074e+00],
[ 2.1123e+00],
[ 1.2354e+00],
[-1.8010e+00]])]
理解:将已经得到的features和labels传入dataset里形成数据集,调用dataloader和规定batch_size每次从数据集中随机挑选固定个数的样本,使用iter()转成python的迭代器,通过next()得到X和y的值。
3. 使用框架的预定义好的层
from torch import nn # nn是neural network的缩写,里面有大量定义好的层
net = nn.Sequential(nn.Linear(2, 1))
# 线性回归用的是nn里面的线性层(或者说是全连接层),它唯一要指定的是输入和输出的维度是多少,此处的输入维度是2,输出维度是1
# 线性回归就是简单的单层神经网络,为了以后的方便,放进一个Sequential的容器里面,可以理解为一个list of layers把层按顺序一个一个放在一起
# Sequential是一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数
# Sequential 是一个容器,里面可以放置任何层,不一定是线性层
理解:使用定义好的神经网络的层并放在容器里。
4. 初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
# 上面定义的net就是只有一个layer,可以通过索引[0]访问到layer,然后用.weight访问到他的w,用.data访问真实data,normal_表示使用正态分布来替换到data的值,使用的是均值为0方差为0.01来替换
# 偏差直接设置为0
#运行结果如下:
tensor([0.])
将神经网络的单层初始化为0,使用.weight.data.normal_(0, 0.01)将w初始化,用.data.fill_(0)将偏差初始化。
5. 计算均方误差使用的是MSELoss类,也称平方L2范数
loss = nn.MSELoss() #在nn中均方误差叫MSELoss
6. 实例化SGD实例
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
# SGD至少传入两个参数。net.parameters里面就包括了所有的参数,w和b;指定学习率0.03
# Optimize.Stochastic Gradient Descent (随机梯度下降法) optim是指optimize优化,sgd是优化的一种方法
# L1范数是算数差,L2范数是算平方差
理解:直接使用随机梯度下降的方法进行优化。
7.训练过程
num_epochs = 3 # 迭代3个周期
for epoch in range(num_epochs):
for X, y in data_iter: # 在data_iter里面一次一次的把minibatch(小批量)拿出来放进net里面
l = loss(net(X), y) # net()这里本身带了模型参数,不需要把w和b放进去了,net(X)是预测值,y是真实值,拿到预测值和真实值做Loss
trainer.zero_grad() # 梯度清零
l.backward() # 计算反向传播,这里pytorch已经做了sum就不需要在做sum了(loss是一个张量,求sum之后是标量)
trainer.step() # 有了梯度之后调用step()函数来进行一次模型的更新。调用step函数,从而分别更新权重和偏差
l = loss(net(features), labels) # 当扫完一遍数据之后,把所有的feature放进network中,和所有的Label作一次Loss
print(f'epoch {epoch + 1}, loss {l:f}') # {l:f} 是指打印l,格式是浮点型
# 运行结果如下:
epoch 1, loss 0.000288
epoch 2, loss 0.000094
epoch 3, loss 0.000093