从零到有,用基础的函数与算法实现一个线性回归模型
1. 构造人造数据集。
使用线性模型参数 和噪声项 生成数据集及其标签:
import random
import torch
from d2l import torch as d2l
# 构造人造数据集
# 假设给定了w, b和数据个数n
def synthetic_data(w, b, num_examples):
# 生成 y = Xw + b + 噪声
# X是均值为0,方差为1,n行w列(w的元素数)的张量
X = torch.normal(0, 1, (num_examples, len(w)))
# y等于X乘以w再加b
y = torch.matmul(X, w) + b
# 引入噪音
y += torch.normal(0, 0.01, y.shape)
# 将X,y作为列向量返回
return X, y.reshape((-1, 1))
# 定义真实的w和真实的b
true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 通过函数生成特征与标注
features, labels = synthetic_data(true_w, true_b, 1000)
2. 数据集的呈现。
features 中的每一行都包含一个二维数据样本,labels 中的每一行都包含一维标签值(标量)。
3. 生成小批量数据集。
(1) 首先,函数 data_iter 接受三个参数:batch_size(批次大小)、features(特征数据)和labels(标签数据)。
(2) 根据特征数据计算样本数,然后创建一个包含所有样本索引的列表 indices。
(3) 接下来,使用random.shuffle函数对样本索引进行随机打乱,以实现随机读取样本的目的。
(4) 然后,使用for循环以batch_size为步长遍历样本索引列表。在每次迭代中,生成一个批次的样本索引batch_indices,使用torch.tensor将其转换为张量。
(5) 最后,使用生成的批次样本索引batch_indices从特征数据和标签数据中获取对应的批次数据,并通过yield语句返回。
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 样本随机读取,没有特定吮吸
random.shuffle(indices)
# 使用for循环将批次一个个分成多份
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i:min(i + batch_size, num_examples)]
)
yield features[batch_indices], labels[batch_indices]
4. 初始化模型
输入维度为2,因此初始化w为长度=2的向量,初始化为均值=0,方差为0.01的正态分布。定义梯度下降为True,后续利用梯度下降算法优化w的值。
初始化b为数值为0的标量,定义梯度下降为True,后续利用梯度下降优化b的值。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
def linreg(X, w, b):
# 线性回归模型
return torch.matmul(X, w) + b
5. 定义损失函数
# 定义损失函数
# y_hat为预测值,y为真实值
def squared_loss(y_hat, y):
# 均方损失
return (y_hat - y.reshape(y_hat.shape))**2 / 2
# 这里没有做均值,直接进行了相加
6. 定义优化算法
# 定义优化算法
# params是包含w和b的list参数,lr是学习率,batch_size是批量数
def sgd(params, lr, batch_size):
# 小批量随机梯度下降
# 在更新参数时不会进行梯度计算和记录。因为我们只关心参数的更新,而不需要计算梯度
with torch.no_grad():
for param in params:
# 这里除以batch_size是因为上面的损失函数没有做均值
param -= lr * param.grad / batch_size
# 手动将梯度设置为0
param.grad.zero_()
7. 训练过程
# 训练过程
# 手动设置学习率
lr = 1
# 将数据扫三遍
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) # X和y的小批量损失
# 由于l的形状是(batch_size, 1), 而不是一个标量。
# 对损失做求和,求和后调用backward计算损失对模型参数的梯度
l.sum().backward()
# 梯度算法更新w和b的值
sgd([w, b], lr, batch_size)
# 注意的问题:batch_size可能无法被样本数量整除,在梯度算法里可能会多除
with torch.no_grad():
# 将整个数据的预测与labels传入loss,做损失对比,进行模型预测的评估
train_1 = loss(net(features, w, b), labels)
print(f'epoch {epoch+1}, loss {float(train_1.mean()):f}')