从零开始的线性回归模型

从零开始的线性回归模型

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import random
import torch
import matplotlib.pyplot as plt
## 线性回归方程
# y = Xw + b + c
# X为1000x2的矩阵
# w = [2.-3.4]T
# b = 4.2
# c是为捕获特征和标签时的潜在观测误差,这里我们认为标准假设成立,即c服从均值为0的正态分布
# 为简化问题,标准差设为0.01
# torch.normal(mean,std,size=):返回一个从单独的正态分布中提取的随机数张量,这些正态分布的平均值和标准差是给定的。
# mean均值,std标准差,表现形式为张量,size是返回的张量的大小(如果张量是矩阵nxm的话,size=(n,m))
# torch.matmul()类似于torch.mm()矩阵-矩阵乘法

## 生成给定了真实权重w和真实偏移值b的函数y = Xw + b + c的数据集
# 说明该数据集是真实数据,是一个训练集train set
def synthetic_data(w,b,num_examples):
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X,w) + b
c = torch.normal(0, 0.01, y.shape)
y += c
return X, y.reshape((-1, 1))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
# print(features.shape, labels.shape)
# torch.Size([1000, 2]) torch.Size([1000, 1])
# features有1000行,每行包含一个二维数据样本(说明有两个特征值),labels有1000行,每行包含一维标签值(一个标量)

#用散点图来观察features的第二列(第二个特征)与labels的线性关系
# plt.scatter(features[:, 1], labels)
# plt.show()

## 小批量随机读取数据集
# 该函数能打乱数据集中的样本,并以小批量的方式获取数据
# data_iter函数,用来接收批量大小,特征矩阵,标签向量作为输入,生成大小为batch_size的小批量,每个小批量包含一组特征和标签
# 最终目的是为了:小批量随机梯度下降
def data_iter(batch_size,features,labels):
num_examples = len(features) # len(features) == 1000
indices = list(range(num_examples)) # indices是一个list,里面包含0-1000个数字
random.shuffle(indices) # 将indices内的数乱序存放
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)])
# min()确保右边界 i+batch_size 不会超出num_examples
# i从0开始,每次循环 i += batch_size,直到 i 超出num_examples则结束循环
# 每次取出范围为 [i,min(i+batch_size, num_examples)]内的数,
# 并作为向量(一维张量torch.tensor)形式,赋给batch_indices
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


#------------------------------------------------------------------------------------------------
# 上面的部分为(创造数据集)和(定义小批量读取数据集的方法)
#------------------------------------------------------------------------------------------------
# 接下来为正式的解决问题
#------------------------------------------------------------------------------------------------

## 定义线性回归模型,为了降低难度,这里将噪声c设置为0
def linreg(X,w,b):
return torch.matmul(X,w) + b

## 初始化权重w和偏移值b
# requires_grad=True,通过自动求导来计算梯度
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
# 从均值0,标准差0.01的正态分布中采样随机数来初始权重
b = torch.zeros(1, requires_grad=True) #初始化为标量0

## 定义损失函数(使用常见的"平方损失函数")
def squared_loss(y_hat,y): # y_hat是带^号的y,(表示估计值),而y是数据集中实际的值
return (y_hat - y.reshape(y_hat.shape))**2 / 2

## 定义优化算法(小批量随机梯度下降算法) 在优化算法中使用data_iter()得到的小批量数据集
# 该函数接收模型参数集合params,学习速率lr,批量大小batch_size
# 小批量随机梯度下降-small batch random gradient descent(SGD)
def sgd(params,lr,batch_size):
with torch.no_grad(): # 表示张量的计算过程中无需计算梯度
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_() #pytorch会积累梯度,所以每轮循环结束时要清空梯度

# torch.no_grad
# 在该模块下,所有计算得出的tensor的requires_grad都自动设置为false
# 即使一个tensor(命名为x)的requires_grad=True,在with torch.no_grad计算下,由x得到的新tensor(命名为w)的
# requires_grad也为False,且grad_fn也为None,即不会对w求导
# x = torch.randn(10,5,requires_grad=True)
# y = torch.randn(10,5,requires_grad=True)
# z = torch.randn(10,5,requires_grad=True)
# with torch.no_grad():
# w = x + y + z
# print(w.requires_grad) # False
# print(w.grad_fn) # None
# print(w.requires_grad) # False
# print(w.grad_fn) # None

## 整体流程
# 初始化参数
# 重复,直到完成
# 读取一小批量训练样本,通过模型来获取一组预测,
# 通过预测,计算完损失后,开始反向传播,存储每个参数的梯度,
# 最后调用优化算法sgd来更新模型的参数(w,b)

#------------------------------------------------------------------------------------------------
# 上面为设定正确的线性回归方程,并根据该方程生成训练集数据,
# 构建读取数据方式,构建线性回归模型,损失函数,小批量随机梯度下降优化算法,
#-------------------------------------------------------------------------------------------------
# 下面实际训练过程
#-------------------------------------------------------------------------------------------------

lr = 0.03 # 学习率
num_epochs = 3 # 迭代周期个数
# 在每个迭代周期中,我们用data_iter函数遍历整个数据集,并将训练数据集中的所有样本都使用一次(假设样本数能被批量大小整除)
net = linreg # 线性回归模型,也可以说是net(网络)
loss = squared_loss # 损失值
batch_size = 10 #设置每个小批量的大小为batch_size

for epoch in range(num_epochs):
for X, y in data_iter(batch_size,features,labels):
l = loss(net(X,w,b),y) # 计算求出来的“小批量数据集”的(估计值y_hat)和(实际的y真实值)之间的损失
# l的形状为(batch_size,1),需要将l的所有元素加和,并一次计算关于['w','b']的梯度
l.sum().backward()
sgd([w,b],lr,batch_size) # 使用参数的梯度来更新梯度
# 第二个for循环用来得到小批量数据集X,y,并带入参数w,b,求出l损失值,
# 并通过l.sum.backward来求出参数w,b的梯度
# 将含梯度的参数w,b带入sgd()算法中,更新得到新的参数w,b
with torch.no_grad():
train_l = loss(net(features,w,b),labels)
# train_l == 计算构造的线性回归模型的(估计值y_hat)与(实际的y真实值)之间的损失,与l不同
# l是(小批量数据集Xw+b+c)与(小批量y)的损失,
# train_l是(整体全部数据集featuresw+b)与(全部数据对应的labels)的
print(f'epoch {epoch+1}, loss {float(train_l.mean()):f}')

print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}')

##过程
# 1.生成数据集(此步骤在真实情况中一般不需要考虑,数据集是从外部收集得到的),需要自己构建一个真实的线性回归方程
# 生成的数据集由features(x)和labels(y)构成,features是1000x2的矩阵,而labels是1000x1的矩阵(向量)
# 2.读取数据(因为使用的优化算法为“小批量随机梯度下降算法(sgd)“,所以需要小批量随机读取数据)
# 3.构建线性回归模型y=Xw+b(这里为了简单,将噪声c设置为0)
# 4.构建计算平方损失的损失函数(该函数计算"小批量"的结果值l,通过自动求导,可以求出”小批量”的参数[w,b]的梯度
# 并将带梯度的参数[w,b]用于优化算法(sgd)中,与计算"featurs"的结果值train_l不同)
# 5.构建优化算法”小批量随机梯度下降算法“(sgd),将带梯度的参数[w,b],batch_size喂入sgd算法中,从而更新[w,b]参数值
# 6.共有三次迭代周期,每个周期内以batch_size使用训练数据集的所有样本(假设样本数能整除批量大小(batch_size))
# 每次迭代结束后,计算labels与features之间的损失值train_l,也就是最终的损失值


本博客所有文章除特别声明外,转载请注明出处!