线性回归的从零开始实现

  • 我们将从零开始整个方法,包括数据流水线、模型、损失函数和小批量随机梯度下降优化器
1
2
3
4
5
6
7
import random
import torch
from d2l import torch as d2l

# 根据带有噪声的线性模型构造一个人造数据集。我们使用的模型参数有
# 权重w=[2,-3.4]T 、 偏差b=4.2 和噪声项c生成的数据集及其标签
# y=wX+b+c X是随机数,是标准正态分布

数据流水线,数据集

  • 权重:输入值的重要性,输入值和权重相乘送到下一层神经网络,权重的调节可以使用反向传播算法实现。
  • 偏差:输出的偏移量,它是不变的,通常为常数。
  • 噪声:是指随机的、不可预测的数据误差。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 制作含有噪声的数据集
def synthetic_data(w, b, num_examples):
"""生成 y = wX + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w))) # 生成一个均值为0,方差为1的标准正态分布,行数是n个样本,列数是权重w的长度
# X是一个1000行,2列的矩阵
Y = torch.matmul(X, w)+b # 矩阵相乘,再加b
Y += torch.normal(0, 0.01, Y.shape) # 加入一个随机噪音,一个均值为0,方差为0.01的正态分布,行列数与Y相同
return X, Y.reshape((-1, 1)) # Y转成列向量返回,-1是自动计算缺失的维度大小


true_w = torch.tensor([2, -3.4]) # 张量,权重w
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000) # 生成特征:X真实值,标签:Y:,1000个样本

# features中每一行都包含一个二维数据样本,labels中每一行都包含一维标签值(一个标量)
# print('features:', features[0], '\n label:', labels[0])

d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
# features为x轴,labels为y轴,选择features中的第1列,第1列负相关,递减;第0列正相关,递增
# detach()分离出数值,不在含有梯度
# scatter()中的最后一个1是绘制点的直径大小
# d2l.plt.show() # 展示图片

模型

1
2
3
4
5
6
7
8
9
# 定义初始化模型参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True) # 均值为0,方差为0,01,两行一列,可以含有梯度
b = torch.zeros(1, requires_grad=True) # 只包含一个元素的张量

# 定义模型
def linreg(X, w, b):
"""线性回归模型 """
return torch.matmul(X, w) + b # net=Xw+b

1
2
3
4
5
# 定义损失函数
def squared_loss(y_hat, y):
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape))**2/2 # 这里除以二是为了求梯度时去掉系数

小批量随机梯度下降优化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 小批量梯度下降
# 定义一个data_iter函数,该函数接收批量大小、特征矩阵和标签作为输入,生成大小为batch_size的小批量
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples)) # range表示0~num-1,表示含有0~num-1的list
random.shuffle(indices) # 将源列表中的元素(下标)打乱
for i in range(0, num_examples, batch_size): # 从0开始,到num,每次跳batch_size个大小
batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)]) # 截取batch_size大小的张量
yield features[batch_indices], labels[batch_indices] # yield是迭代器


batch_size = 10
for X, Y in data_iter(batch_size, features, labels):
# print(X, '\n', Y)
break


# 定义优化算法
def sgd(params, lr, batch_size): # 梯度更新。参数params包含w,b,lr是学习率,小批量步长
"""小批量随机梯度下降算法"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size # 梯度下降公式,这里除以batch_size是求平均
param.grad.zero_()

训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 训练过程
lr = 0.035 # 学习率
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.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}') # :f是默认格式化,默认6位小数


# 比较真实参数和通过训练得到的参数来评估训练的成功程度
print(f'w的真实误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的真实误差:{true_b - b}')