Re:从0开始的手写线性回归

Re:从0开始的手写线性回归

只使用tensorautograd完成简单的线性回归全过程。

一些小细节

  • pycharm要求函数之间空两行
  • 函数内部变量尽量不要与外部重名,参考:

https://www.cnblogs.com/shengulong/p/10171386.html

https://www.twle.cn/t/649

【目前还不完全理解,以及不确定是否需要解决】

代码注解

生成数据集

1
2
3
4
5
6
def synthesis_data(w, b, number):
X = torch.rand(number, len(w))
noise = torch.normal(0, 0.01, size=(number, 1))
y = torch.matmul(X, w) + noise
y += b
return X, y
  • y+=b一句需要单独成行,否则会出错

  • 采用reshape(x, y)可以改变tensor的大小,如果参数选择-1则可以仅输入行或者列,自动计算另一个参数。书中使用的reshape(-1, 1)可以确定列数为1,自动计算行数。

生成后画出特征的两个维度和标签的三维散点图:

Figure_1

随机读取小批量数据

1
2
3
4
5
6
7
8
# iter:迭代器
def data_iter(batch_size, features, labels):
total_number = len(labels)
indices = list(range(total_number))
random.shuffle(indices)
for i in range(0, total_number-batch_size, batch_size):
batch_indices = indices[i:i+batch_size-1]
yield features[batch_indices, :], labels[batch_indices]
  • 学习这种随机取出小批量数据的方法!
  • 合理大小的小批量可以利⽤GPU硬件的优势,每个样本都可以并行地进行模型计算、损失函数计算、梯度计算。结果是GPU可以在处理几百个样本时,所花费的时间不比处理⼀个样本时多太多。
  • 实际上深度学习内置的iter效率高得多

初始化模型参数

在这里模型参数指权重w和偏置b。

1
2
3
4
5
def initialize_parameter(mu, sigma):
w = torch.normal(mu, sigma, size=(2, 1), requires_grad=True)
# w = torch.zeros(size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
return w, b
  • 参数w使用了正态分布进行初始化,其实也可以直接以0作为初始值【那为什么要指定初始值啊】
  • 注意不要漏掉requires_grad=True

模型

1
2
def linear_regression(w, b, X):
return torch.matmul(X, w) + b

损失函数

1
2
3
def squared_loss(labels, targets):
# return 0.5 * (labels - targets) ** 2
return 0.5 * (labels - targets.reshape(labels.shape)) ** 2
  • 这里使用了reshape(),是一种安全且规范的写法,保证了两个向量确实是按我们期望的方式都作为列项链进行相减。值得认可!

优化算法(小批量随机梯度下降)

1
2
3
4
5
def sgd(params, learning_rate, batch_size):
with torch.no_grad():
for param in params:
param -= learning_rate * param.grad / batch_size
param.grad.zero_()

【关于自动求导的部分待填坑】

训练过程

参数设置

1
2
3
4
5
6
7
# 初始化模型参数
w, b = initialize_parameter(0, 0.01)
learning_rate = 0.03
batch_size = 10
net = linear_regression
num_epochs = 30
loss = squared_loss
  • 注意这两句:

    1
    2
    net = linear_regression
    loss = squared_loss

    是一种规范的写法,值得认可!

训练过程

1
2
3
4
5
6
7
8
for epoch in range(num_epochs):
for features_sample, labels_sample in data_iter(batch_size, features, labels):
loss_list = loss(net(w, b, features_sample), labels_sample)
loss_list.sum().backward()
sgd([w, b], learning_rate, batch_size)
with torch.no_grad():
train_loss = loss(net(w, b, features), labels)
print(f'epoch {epoch + 1}, loss {float(train_loss.mean()):f}')

整个算法步骤按上一节所述如下:

  • 初始化模型参数
  • 随机抽取小批量样本,在负梯度方向更新参数
  • 不断迭代

【关于自动求导的部分待填坑】

训练效果

这里如果num_epochs设为3,我的代码效果不好,因此增大到30了。

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
26
27
28
29
30
31
32
epoch 1, loss 0.602139
epoch 2, loss 0.400999
epoch 3, loss 0.274957
epoch 4, loss 0.188473
epoch 5, loss 0.130619
epoch 6, loss 0.091479
epoch 7, loss 0.064301
epoch 8, loss 0.045488
epoch 9, loss 0.032430
epoch 10, loss 0.023281
epoch 11, loss 0.016996
epoch 12, loss 0.012337
epoch 13, loss 0.009028
epoch 14, loss 0.006587
epoch 15, loss 0.004879
epoch 16, loss 0.003635
epoch 17, loss 0.002709
epoch 18, loss 0.002028
epoch 19, loss 0.001535
epoch 20, loss 0.001165
epoch 21, loss 0.000886
epoch 22, loss 0.000677
epoch 23, loss 0.000522
epoch 24, loss 0.000404
epoch 25, loss 0.000316
epoch 26, loss 0.000250
epoch 27, loss 0.000202
epoch 28, loss 0.000164
epoch 29, loss 0.000136
epoch 30, loss 0.000114
loss of w: tensor([[-0.0262], [-0.0319]], grad_fn=<SubBackward0>)
loss of b: tensor([0.0309], grad_fn=<RsubBackward1>)

训练效果:

训练效果

调API的线性回归

思想与上面完全一致,但是代码精简很多。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import torch
from torch.utils import data
from torch import nn


def synthesis_data(w, b, number):
X = torch.rand(number, len(w))
noise = torch.normal(0, 0.01, size=(number, 1))
y = torch.matmul(X, w) + noise
y += b
return X, y

# dataloader 还不理解
def load_array(data_array, batch_size, is_train=True):
dataset = data.TensorDataset(*data_array)
return data.DataLoader(dataset, batch_size, shuffle=is_train)




if __name__ == '__main__':
# 生成数据集
w_true = torch.tensor([2, -3.4]).reshape(2, 1)
b_true = 4.2
n = 1000
features, labels = synthesis_data(w_true, b_true, n)

batch_size = 10
data_iter = load_array((features, labels), batch_size)
# print(next(iter(data_iter)))

net = nn.Sequential(nn.Linear(2, 1))
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
# print(net[0])

loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

num_epochs = 30
for epoch in range(1, num_epochs+1):
for X, y in data_iter:
l = loss(net(X), y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch{epoch}, loss{l:.4f}')

w = net[0].weight.data
b = net[0].bias.data
print(f'error of w= {w-w_true}')
print(f'error of b= {b-b_true}')

仍然有一样的问题,书中训练3个epoch效果就很好,我需要训练到10个左右才能达到相同效果


Re:从0开始的手写线性回归
http://example.com/2022/12/17/Re-从0开始的手写线性回归/
作者
Thunderbolt
发布于
2022年12月17日
许可协议