Python实现线性回归模型

从简单的线性回归模型中可以看到构建一个监督学习网络的基本步骤。下文摘自《动手学深度学习》

线性回归概念

线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常见,如预测房屋价格、气温、销售额等连续值的问题。

数学模型

$$ \hat{y}=x_{1} w_{1}+x_{2} w_{2}+b $$

其中$x_{1},x_{2}$是输入数据的特征,$w_{1},w_{2}$是特征的对应权重,$b$是偏置,这样就可以表示出一个简单的线性模型,用单层神经网络就可以实现线性模型。给定数据和标签,通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小,这个过程叫作模型训练(model training)。

模型训练的三个要素

1 . 训练数据集 我们通常收集一系列的真实数据,我们希望在这个数据上面寻找模型参数来使预测值$\hat{y}$与真实值(标签)$y$误差最小。在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),训练集的每一个数据的每个维度值叫作特征(feature)。特征用来表征样本的特点。 2 . 损失函数 用来衡量预测值和真实值的误差,一个常用的选择是平方函数: $$ \ell^{(i)}\left(w_{1}, w_{2}, b\right)=\frac{1}{2}\left(\hat{y}^{(i)}-y^{(i)}\right)^{2} $$ 在机器学习里,将衡量误差的函数称为损失函数(loss function)。这里使用的平方误差函数也称为平方损失(square loss)。

通常,我们用训练数据集中所有样本误差的平均来衡量模型预测的质量,即 $$ \ell(w_1, w_2, b) =\frac{1}{n} \sum_{i=1}^n \ell^{(i)}(w_1, w_2, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right)^2. $$ 在模型训练中,我们希望找出一组模型参数,记为$w_{1}^{*}, w_{2}^{*}, b^{*}$来使训练样本平均损失最小:

$$ w_{1}^{*},w_{2}^{*}, b^{*}=\underset{w_{1}, w_{2}, b}{\operatorname{argmin}} \ell\left(w_{1}, w_{2}, b\right) $$

3 . 优化算法 当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。

在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch),然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

在训练本节讨论的线性回归模型的过程中,模型的每个参数将作如下迭代:

$$ \begin{aligned} w_1 &\leftarrow w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \frac{ \partial \ell^{(i)}(w_1, w_2, b) }{\partial w_1} = w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_1^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\\ w_2 &\leftarrow w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \frac{ \partial \ell^{(i)}(w_1, w_2, b) }{\partial w_2} = w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_2^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\\ b &\leftarrow b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \frac{ \partial \ell^{(i)}(w_1, w_2, b) }{\partial b} = b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}\left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right). \end{aligned} $$

在上式中,$|\mathcal{B}|$代表每个小批量中的样本个数(批量大小,batch size),$\eta$称作学习率(learning rate)并取正数。需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学出的,因此叫作超参数(hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。

模型预测

模型训练完成后,我们将模型参数$w_1, w_2, b$在优化算法停止时的值分别记作$\hat{w}_1, \hat{w}_2, \hat{b}$。注意,这里我们得到的并不一定是最小化损失函数的最优解$w_{1}^{*}, w_{2}^{*}, b^{*}$,而是对最优解的一个近似。得到的优化算法停止时的值用于估算训练集以外的数据的结果,这里的估算也叫作模型预测、模型推断或模型测试。

python实现

训练一个线性回归模型的大致步骤为:

  • 数据预处理 处理输入数据集和标签的表示形式,训练的批大小

  • 定义网络模型 定义网络的结构,网络参数初始化等

  • 定义损失函数

  • 定义优化算法

  • 模型训练

代码如下:

 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
from IPython import display
from matplotlib import pyplot as plt
from mxnet import autograd, nd
import random

num_inputs = 2 # 特征维度
num_examples = 1000 # 特征数
batch_size = 10 # 批大小
true_w = [2, -3.4] #参数w
true_b = 4.2 #参数b
features = nd.random.normal(scale=1, shape=(num_examples, num_inputs)) #【1000*2】的ndarray,服从高斯分布,标准差为1
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b #标签 y
labels += nd.random.normal(scale=0.01, shape=labels.shape) #标签加噪声


def use_svg_display():
    # 用矢量图显示
    display.set_matplotlib_formats('svg')

def set_figsize(figsize=(3.5, 2.5)):
    use_svg_display()
    # 设置图的尺寸
    plt.rcParams['figure.figsize'] = figsize


# 本函数已保存在d2lzh包中方便以后使用
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    random.shuffle(indices)  # 样本的读取顺序是随机的
    for i in range(0, num_examples, batch_size): #每批次都依次从feature中读取batch_size的数据x
        j = nd.array(indices[i: min(i + batch_size, num_examples)]) #若不能整除,要防止越界
        yield features.take(j), labels.take(j)  # take函数根据索引返回对应元素

w = nd.random.normal(scale=0.01, shape=(num_inputs, 1)) #初始化权重
b = nd.zeros(shape=(1,)) #初始化偏置
w.attach_grad()
b.attach_grad()

def linreg(X, w, b):  # 本函数已保存在d2lzh包中方便以后使用
    return nd.dot(X, w) + b  #线性回归模型

def squared_loss(y_hat, y):  # 本函数已保存在d2lzh包中方便以后使用
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 #损失函数

def sgd(params, lr, batch_size):  # 本函数已保存在d2lzh包中方便以后使用
    for param in params:
        param[:] = param - lr * param.grad / batch_size  #小批量梯度下降

lr = 0.03 #学习率
num_epochs = 3 #迭代次数
net = linreg
loss = squared_loss



for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
    # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
    # 和y分别是小批量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        with autograd.record(): #记录梯度参数,默认是不记录的,所以要加上这句
            l = loss(net(X, w, b), y)  # l是有关小批量X和y的损失
        l.backward()  # 小批量的损失对模型参数求梯度,其实是 l.sum().backward()
        sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降迭代模型参数
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().asnumpy()))
    print('trained weight :',w.asnumpy())
    print('trained bias  :',b.asscalar())

set_figsize()
plt.scatter(features[:, 1].asnumpy(), labels.asnumpy(), 1);  # 加分号只显示图
plt.show()
updatedupdated2019-12-282019-12-28