一元线性回归

线性模型试图学得一个通过属性的线性组合来进行预测的函数。

使用向量形式可表示为

f(x)=wTx+bf(x)=w^{T}x+b

这里需要注意的是,线性模型可以是用曲线拟合样本,但是分类的决策边界一定是直线的,例如logistics模型。

原理

线性回归试图学得
f(xi)=wxi+bf(x_i) = wx_i + b, 使得f(xi)yif(x_i) \approx y_i

这里引入均方误差**(MSE)**来对回归任务的性能进行度量,通过让MSE最小使预测值尽可能接近真实值。如公式2所示

(w,b)=argminw,b12mi=1m(wxi+byi)2(w^*,b^*) = arg\min_{w,b}\frac{1}{2m}\sum_{i=1}^{m}\big( wx_i+b-y_{i}\big)^2

均方误差有非常好的几何意义。它对应了"欧氏距离" (Euclidean distance)。基于均方误差最小化来进行模型求解的方法称为"最小二乘法" 。

在线性回归中,最小二乘法就是试图找到一条直线,使所有样本到直线上的欧氏距离之和最小。

E(w,b)w=i=1m2(wxi+byi)xi=2(wi=1mxi2i=1m(yib)xi)\frac{\partial E(w,b)}{\partial w} =\sum_{i=1}^m2(wx_i+b-y_i)x_i\\ \qquad\qquad\,\,=2\left(w\sum_{i=1}^mx_i^2-\sum_{i=1}^m(y_i-b)x_i\right)

E(w,b)b=i=1m2(wxi+byi)=2(wi=1mxi+mbi=1myi)\frac{\partial E(w,b)}{\partial b} = \sum_{i=1}^m2(wx_i+b-y_i)\\ \qquad\qquad=2\left(w\sum_{i=1}^mx_i+mb-\sum_{i=1}^my_i \right)

我们对参数wbw\text{和}b求导,即式(3)(4),然后使导数为0求得最优解。

实现

这里我们使用租金预测的数据集举例(关于数据集的情况可以查看回归综述的文章):

1
2
3
4
# x表示房屋大小,y表示租金
x_data = np.array([[1.0], [2.0], [3.0]])
y_data = np.array([[2.0], [4.0], [6.0]])
# 我们需要拟合直线并预测x=4时y的值

使用sklearn进行一元线性回归:

1
2
3
4
5
6
7
8
9
10
from sklearn import linear_model #可以直接调用sklearn中的linear_model模块进行线性回归。
import numpy as np
model = linear_model.LinearRegression()
x_data = np.array([[1.0], [2.0], [3.0]])
y_data = np.array([[2.0], [4.0], [6.0]])
model.fit(x_data, y_data)
print("w=",model.intercept_[0]) #截距
print("b=",model.coef_[0][0]) #线性模型的系数
a = model.predict([[12]])
print("预测结果为:{:.5f}".format(model.predict([[4.0]])[0][0]))

输出情况:

1
2
3
w= 1.3322676295501878e-15
b= 1.9999999999999993
预测结果为:8.00000

使用Pytorch进行一元线性回归

步骤

使用Pytorch构建线性模型主要分为以下4步,这4步也是未来构建复杂模型的基础。

  1. 处理数据集
  2. 使用Class定义模型
  3. 定义损失和优化器
  4. 进行迭代(前向,反向,更新)

代码

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
import torch

# 准备数据集(如果数据集复杂需要构建data类)
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])

# 构建模型
class LinearModel(torch.nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
#这是对继承自父类的属性进行初始化。而且是用父类的初始化方法来初始化继承的属性。
#也就是说,子类继承了父类的所有属性和方法。我们在构建模型时需要继承父类的属性和方法初始化。
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
#如果子类中没有实现会报错raise NotImplementedError
y_pred = self.linear(x)
#这里直接对对象进行传值调用,是因为在Module类中实现了__call__()方法,且在该方法中完成对 forward的调用
return y_pred
#这里没有反向及梯度更新的函数,因为子类继承了Module,知道了前向,torch会自动构建计算图
model = LinearModel()

# 定义损失函数和优化器
criterion = torch.nn.MSELoss(reduction="sum")
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 进行迭代(training cycle)
for epoch in range(1000):
y_pred = model(x_data) #前向传播
loss = criterion(y_pred, y_data) #计算损失,为了便于查看
if epoch%100 == 0:
print(epoch, loss.item())
optimizer.zero_grad() #梯度会累积,在反向传播之前需要将梯度清0
loss.backward() #反向传播
optimizer.step() #更新梯度
print('w = ', model.linear.weight.item())
print('b = ', model.linear.bias.item())
x_test = torch.Tensor([[4.0]])
y_test = model(x_test)
print('y_pred = ', y_test.data)

输出情况:

0 45.2210693359375
100 0.16480454802513123
200 0.0387517511844635
300 0.00911199301481247
400 0.0021425874438136816
500 0.0005037992377765477
600 0.00011845995322801173
700 2.7853518986376002e-05
800 6.548188139277045e-06
900 1.5404962141474243e-06
w =  1.9995964765548706
b =  0.0009174233418889344
y_pred =  tensor([[7.9993]])

上述代码构建了一个完整的线性模型,并进行回归预测任务。这里有几个需要注意的地方:

  1. 构建模型类时需要继承torch.nn.Module.这个类是 PyTorch 中所有 neural network module 的基类,
    自己创建的网络模型都是这个类的子类。
  2. 在子类中要实现init和forward这两个函数,其余函数可以暂不实现。
  3. 反向传播前要对梯度进行清0,否则梯度会累积

参考

  • 周志华-《机器学习》
  • B站-《PyTorch深度学习实践》