通过使用深度学习框架来简洁地实现 线性回归模型 生成数据集
import numpy as np
import torch
from torch.utils import data # 从torch.utils中引入一些处理数据的模块
from d2l import torch as d2ltrue_w = torch.tensor([2,-3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
调用框架中现有的API来读取数据
# 假设我们已经有了features和labels,我们可以把它们作为一个list传到TensorDataset里面
# 会得到一个Pytorch的一个dataset
# dataset拿到数据集之后,我们可以调用data.DataLoader这个函数,每一次从里面随机的挑选batch_size个样本
# shuffle表示是不是要随机去打乱它的顺序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))
# 先把data_iter转成python iter,通过next函数来得到一个X和一个y
控制台输出:
[tensor([[ 0.7835, 2.1334],[ 1.2522, 0.8810],[ 2.7299, -0.8198],[-0.7226, 0.1075],[ 0.1661, 3.0323],[ 1.5635, 0.4247],[-0.1221, 0.3311],[ 0.3448, 0.8430],[-0.9672, 1.3701],[ 0.8360, 2.6205]]),tensor([[-1.4739],[ 3.7078],[12.4481],[ 2.4049],[-5.7701],[ 5.8812],[ 2.8200],[ 2.0200],[-2.3958],[-3.0526]])]
使用框架预定义好的层
# nn是神经网络的缩写
from torch import nn
# nn 中定义了大量的定义好的层 对我们的线性回归来说 等价于它的线性层或者说全连接层net = nn.Sequential(nn.Linear(2,1)) # 唯一需要指定的是 输入的维度是多少 输出的维度是多少
# 输入维度:每个样本它的特征个数,即为输入维度# 一般直接用nn.Linear就行(线性回归就是一个简单的单层神经网络),
# 但是我们为了后面的方便,把它放到一个容器Sequential里面
# 可以理解容器Sequential为 list of layers 我们把层按顺序 一个一个的放到一起
在这段代码中,nn.Linear(2,1)
创建了一个线性层(或称为全连接层),这是神经网络中最基本的组成单元之一。
nn.Linear
需要两个主要的参数:输入维度和输出维度,这两个参数决定了层的结构。
输入维度(Input Dimension)
- 输入维度指的是每个输入数据向量的大小或长度。在神经网络中,每个样本通常被表示为一个向量,向量中的每个元素对应一个特征。因此,输入维度就是每个样本的特征数量。
- 例如,
nn.Linear(2,1)
中的2
表示每个输入样本有2
个特征。如果你的数据是二维空间中的点,那么每个点由两个坐标值表示(例如,x 和 y),因此输入维度是2
。
输出维度(Output Dimension)
- 输出维度指的是经过这一层计算后输出数据向量的大小或长度。输出维度取决于你希望这一层神经网络输出多少个数值。
- 在
nn.Linear(2,1)
中,1
表示这个线性层的输出是一个单一的值。这常见于执行回归任务(如线性回归)时,你可能只需要预测一个连续值(比如房价),因此输出维度是1
。
nn.Sequential
容器
nn.Sequential
是一个容器,用于按顺序包装多个层。它允许你将多个层组合成一个模块,这样数据就可以依次通过这些层进行处理。在这个例子中,虽然只有一个 nn.Linear(2,1) 层,将其放入 nn.Sequential 中可能看起来多此一举,但这种做法为添加更多层提供了灵活性,便于后续模型的扩展。
初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
# 因为我是放在容器里,也就它一个模型,所以可以用索引0访问到
# weight w
# bias b
# data 真实data
# normal_ 使用正态分布来填充data的值 均值为0,标准差为0.01
计算均方误差使用的是MSELoss
类,也称为平方 L 2 L_{2} L2范数
loss = nn.MSELoss()
实例化SGD实例
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
# net.parameters() w和b
那么相比从0开始实现,那里的除以batch_size
这里怎么没有了呢?
在PyTorch中,损失函数的默认行为对于批处理的数据是计算并返回批处理中所有样本损失的平均值。因此,当你在训练循环中调用loss(net(X), y)
时,得到的损失l
实际上已经是当前批次内所有样本损失的平均值。
这意味着,即使在代码中没有显式地除以批大小(batch_size
)来计算平均误差,这个计算过程实际上已经在损失函数内部自动完成了。这是为了简化训练过程,并使代码更加简洁。
例如,如果你使用的是torch.nn.CrossEntropyLoss
或torch.nn.MSELoss
作为你的损失函数,这些函数默认就是计算批次中所有样本损失的平均值。如果你希望损失函数返回批次中所有样本损失的总和,而不是平均值,你可以在初始化损失函数时通过设置reduction='sum'
参数来实现。默认情况下,reduction
参数的值是'mean'
,即计算平均值。
总结来说,你看不到代码中显式地除以batch_size
来计算平均误差的原因是因为这一计算步骤已经被内嵌在了损失函数中。
训练过程代码与我们从零开始实现时所做的非常相似
num_epochs = 3
for epoch in range(num_epochs):for X, y in data_iter: # 依次拿出小批次l = loss(net(X) ,y) # net本身自带模型参数了,不需要显示的去写w和b了,# 而且这里算的确实已经是平均值,相当于上一节从0开始里面,除以batch_size的操作,它这里是放在计算损失函数里面已经做了trainer.zero_grad() # 梯度清零 防止梯度累加l.backward() # 这里pytorch 自动会计算sum,不需要我们自己再去手动计算sumtrainer.step() # 模型更新l = loss(net(features), labels) # 扫完一遍数据之后,把整个特征矩阵带进去,评估效果,算损失值print(f'epoch {epoch + 1}, loss {l:f}')
控制台输出:
epoch 1, loss 0.000251
epoch 2, loss 0.000111
epoch 3, loss 0.000110
我们可以发现,随着轮次的增大,损失值在降低。