机器学习03-线性回归

二次信任 提交于 2019-11-28 08:35:01

1. 问题背景

波士顿房价数据集包含506条波士顿的城镇信息,每一条城镇信息都包含了14个属性的值,希望从该数据集找到城镇房价中位数与其它13属性之间存在的关系或规律,使得给出波士顿的一个城镇的前13个属性的值,就能够预测出该城镇的房价中位数。

这是一个典型的可以用线性回归(linear regression)算法解决的问题。线性回归算法是一种回归算法,属于监督学习算法,它使用的数据集既有特征又有标记,样本标记和预测结果都是连续值。

2. 数据表示

2.1. 特征向量

使用用特征向量表示数据集中的一个样本,一个样本的特征向量包含了该样本所有特征(feature)的值,但不包含需要预测的那一个属性的值:

x(i)=(x1(i)x2(i)xn(i)) \boldsymbol{{x}^{(i)}} = \begin{pmatrix} x_{1}^{(i)} \\ x_{2}^{(i)} \\ \vdots \\ x_{n}^{(i)} \end{pmatrix}

  1. x(i)\boldsymbol{{x}^{(i)}}是第ii个样本(sample)的特征向量。
  2. xj(i)x_{j}^{(i)}是第ii个样本的第jj个特征(feature)的值。
  3. nn是特征的数量,一个特征向量包含了一个样本的nn个特征的值,在波士顿房价数据集中,n=13n = 13

粗体的字母表示矩阵或向量,包含多个数值;未加粗的字母表示标量,表示单个数值。

2.2. 特征矩阵

所有样本的特征向量组成了特征矩阵:

X=(x1(1)x2(1)xn(1)x1(2)x2(2)x2(n)x1(m)x2(m)xn(m))=(x(1)Tx(2)Tx(m)T) \boldsymbol{X} = \begin{pmatrix} x_{1}^{(1)} & x_{2}^{(1)} & \ldots & x_{n}^{(1)} \\ x_{1}^{(2)} & x_{2}^{(2)} & \ldots & x_{2}^{(n)} \\ \vdots & \vdots & \ddots & \vdots \\ x_{1}^{(m)} & x_{2}^{(m)} & \ldots & x_{n}^{(m)} \end{pmatrix} = \begin{pmatrix} {\boldsymbol{{x}^{(1)}}}^{T} \\ {\boldsymbol{{x}^{(2)}}}^{T} \\ \vdots \\ {\boldsymbol{{x}^{(m)}}}^{T} \end{pmatrix}

  1. X\boldsymbol{X}是特征矩阵,每一行的值由一个样本的特征向量的值组成。
  2. mm是样本的数量。
  3. nn是特征的数量。
  4. x(i)T=(x1(i)x2(i)xn(i)){\boldsymbol{{x}^{(i)}}}^{T} = \begin{pmatrix} x_{1}^{(i)} & x_{2}^{(i)} & \ldots & x_{n}^{(i)} \end{pmatrix}是第ii个样本的特征向量的行向量形式。

向量通常是指列向量,加转置符号T^{T}的向量表示行向量。

2.3. 样本标记

样本标记是需要预测的样本的属性的值:

y(i) y^{(i)}

所有样本的标记组成了样本标记的向量:

y=(y(1)y(2)y(m)) \boldsymbol{y} = \begin{pmatrix} y^{(1)} \\ y^{(2)} \\ \vdots \\ y^{(m)} \end{pmatrix}

3. 模型

对于一个样本:

y^(i)=hw,b(x(i))=wTx(i)+b=w1x1(i)+w2x2(i)++wnxn(i)+b \begin{aligned} \hat{y}^{(i)} = & h_{\boldsymbol{w}, b}(\boldsymbol{x^{(i)}}) \\ = & \boldsymbol{w}^{T} \boldsymbol{x^{(i)}} + b \\ = & w_{1} x_{1}^{(i)} + w_{2} x_{2}^{(i)} + \ldots + w_{n} x_{n}^{(i)} + b \end{aligned}

对于所有样本:

y^=hw,b(X)=Xw+b \begin{aligned} \boldsymbol{\hat{y}} = & h_{\boldsymbol{w}, b}(\boldsymbol{X}) \\ = & \boldsymbol{X} \boldsymbol{w} + b \end{aligned}

  1. y^(i)\hat{y}^{(i)}是模型(model)根据第ii个样本的特征向量预测出的值。
  2. y^\boldsymbol{\hat{y}}是模型根据所有样本的特征矩阵预测出的值,包含每一个样本的预测值。
  3. hw,bh_{\boldsymbol{w}, b}是模型的数学表示,在上面的公式中相当于一个函数名,w\boldsymbol{{w}}bb相当于函数的常量,x(i)\boldsymbol{x^{(i)}}X\boldsymbol{X}相当于函数的自变量。
  4. w=(w0w1wn)T\boldsymbol{w}= \begin{pmatrix} w_{0} & w_{1} & \ldots & w_{n} \end{pmatrix}^{T}是权重(weight),bb是偏置项(bias),w\boldsymbol{w}bb是需要训练学习的模型参数(parameters)。
  5. 从直观上理解,需要预测的值应该与样本的特征有关,给每个特征一个权重,表示每个特征对预测结果的影响程度,偏置项则是考虑特征以外的影响。

利用向量化的方法,可以一次性对所有样本的特征矩阵进行处理。NumPy内部的并行机制能够很高效地执行多维数组的运算,比自己写循环要快很多。

代码实现:

"""
inputs:  type=numpy.ndarray, shape=(num_samples, num_features)
weights: type=numpy.ndarray, shape=(num_features, 1)
bias:    type=float
outputs: type=numpy.ndarray, shape=(num_samples, 1)
"""

outputs = inputs @ weights + bias

这里没有使用数学符号命名代码中的变量,主要是考虑到可读性和Python编程规范。很多人在实现机器学习算法时使用数学符号命名变量也是可以的,但是在算法比较复杂时,使用数学符号命名变量会导致代码不易理解,同时大写字母开头的变量名违背了Python的变量命名规范。

4. 训练

4.1. 损失函数和代价函数

损失函数(loss function)的值是模型在一个单独的样本上的误差。

线性回归常用的损失函数是平方误差函数(squared error function):

L(y^(i),y(i))=12(y^(i)y(i))2 L(\hat{y}^{(i)}, y^{(i)}) = \frac{1}{2} (\hat{y}^{(i)} - y^{(i)})^{2}

代价函数(cost function)的值是模型在所有样本上的误差的均值,代价函数以w\boldsymbol{w}bb作为自变量。

线性回归的代价函数是所有样本的平方误差的均值:

J(w,b)=12mi=1m(y^(i)y(i))2 J(\boldsymbol{w}, b) = \frac{1}{2m} \sum_{i = 1}^{m} (\hat{y}^{(i)} - y^{(i)})^{2}

很多人甚至是学术界中,通常不使用损失函数,而是将代价函数称为损失函数。两者的概念通常不必过分细究。

代码实现(2种方式):

"""
outputs:     type=numpy.ndarray, shape=(num_samples, 1)
labels:      type=numpy.ndarray, shape=(num_samples, 1)
num_samples: type=int
loss:        type=float
diff:        type=numpy.ndarray, shape=(num_samples, 1)
"""

# implementation 1
loss = 0.5 / num_samples * np.sum((outputs - labels) ** 2)

# implementation 2
diff = outputs - labels
loss = 0.5 / num_samples * diff.T @ diff

Python中通常使用小写字母和单词间加下划线的形式命名变量和函数,可以使用一些约定俗成的缩写命名变量和函数,不违背Python编程规范。与许多编程语言推荐使用的驼峰命名法不同。不管采用哪种命名方式,只要没有语法错误就可以,但建议遵循每种编程语言各自的编程规范,每种语言的主流IDE基本都支持编程规范检查。

4.2. 优化目标

代价函数的值越小,代表模型的预测越接近于实际情况。为了使模型表现更好,需要不断降低代价函数的值,优化目标(optimization object)即为求解模型的常量w\boldsymbol{w}bb使得J(w,b)J(\boldsymbol{w}, b)最小化。

4.3. 梯度下降

通常使用梯度下降(gradient descent)求解w\boldsymbol{w}bb

w:=wαwJ(w,b)b:=bαbJ(w,b) \begin{aligned} \boldsymbol{w} & := \boldsymbol{w} - \alpha \frac{\partial}{\partial \boldsymbol{w}} J(\boldsymbol{w}, b) \\ b & := b - \alpha \frac{\partial}{\partial b} J(\boldsymbol{w}, b) \end{aligned}

  1. α\alpha是学习率(learning rate),表示参数沿梯度下降的幅度大小。
  2. wJ(w,b)\frac{\partial}{\partial \boldsymbol{w}} J(\boldsymbol{w}, b)JJw\boldsymbol{w}上的偏导:
    wJ(w,b)=1mXT(y^y) \frac{\partial}{\partial \boldsymbol{w}} J(\boldsymbol{w}, b) = \frac{1}{m} \boldsymbol{X}^{T} (\boldsymbol{\hat{y}} - \boldsymbol{y})
  3. bJ(w,b)\frac{\partial}{\partial b} J(\boldsymbol{w}, b)JJbb上的偏导:
    bJ(w,b)=1mi=1m(y^y) \frac{\partial}{\partial b} J(\boldsymbol{w}, b) = \frac{1}{m} \sum_{i = 1}^{m} (\boldsymbol{\hat{y}} - \boldsymbol{y})
  4. 线性回归中使用梯度下降的优点:在特征数n很大时效果较好。
  5. 线性回归中使用梯度下降的缺点:需要选取学习率;需要多次迭代。

求偏导的过程:

w1J(w1,b)=w1(12mi=1m(y^(i)y(i))2)=w1(12m[(y^(1)y(1))2+...+(y^(m)y(m))2])=1m(y^(1)y(1))w1(y^(1)y(1))+...+1m(y^(m)y(m))w1(y^(m)y(m))=1m(y^(1)y(1))w1(w1x1(1)+wnxn(1)+by(1))+...+1m(y^(m)y(m))w1(w1x1(m)+wnxn(m)+by(m))=1m[(y^(1)y(1))x1(1)+...+(y^(m)y(m))x1(m)] \begin{aligned} \frac{\partial}{\partial w_{1}} J (w_{1}, b) = & \frac{\partial}{\partial w_{1}} (\frac{1}{2m} \sum_{i = 1}^{m} (\hat{y}^{(i)} - y^{(i)})^{2}) \\ = & \frac{\partial}{\partial w_{1}} (\frac{1}{2m} [(\hat{y}^{(1)}- y^{(1)})^{2} + ... + (\hat{y}^{(m)} - y^{(m)})^{2}]) \\ = & \frac{1}{m} (\hat{y}^{(1)} - y^{(1)}) \frac{\partial}{\partial w_{1}} (\hat{y}^{(1)} - y^{(1)}) + ... \\ & + \frac{1}{m} (\hat{y}^{(m)} - y^{(m)}) \frac{\partial}{\partial w_{1}} (\hat{y}^{(m)} - y^{(m)}) \\ = & \frac{1}{m} (\hat{y}^{(1)} - y^{(1)}) \frac{\partial}{\partial w_{1}} (w_{1} x_{1}^{(1)} \ldots + w_{n} x_{n}^{(1)} + b - y^{(1)}) + ... \\ & + \frac{1}{m} (\hat{y}^{(m)} - y^{(m)}) \frac{\partial}{\partial w_{1}} (w_{1} x_{1}^{(m)} \ldots + w_{n} x_{n}^{(m)} + b - y^{(m)}) \\ = & \frac{1}{m} [(\hat{y}^{(1)} - y^{(1)}) x_{1}^{(1)} + ... + (\hat{y}^{(m)} - y^{(m)}) x_{1}^{(m)}] \end{aligned}

wJ(w,b)=(w1J(w1,b)w2J(w2,b)wnJ(wn,b))=1m((y^(1)y(1))x1(1)+...+(y^(m)y(m))x1(m)(y^(1)y(1))x2(1)+...+(y^(m)y(m))x2(m)(y^(1)y(1))xn(1)+...+(y^(m)y(m))xn(m))=1mXT(y^y) \begin{aligned} \frac{\partial}{\partial \boldsymbol{w}} J(\boldsymbol{w}, b) = & \begin{pmatrix} \frac{\partial}{\partial w_{1}} J (w_{1}, b) \\ \frac{\partial}{\partial w_{2}} J (w_{2}, b) \\ \vdots \\ \frac{\partial}{\partial w_{n}} J (w_{n}, b) \end{pmatrix} \\ = &\frac{1}{m} \begin{pmatrix} (\hat{y}^{(1)} - y^{(1)}) x_{1}^{(1)} + ... + (\hat{y}^{(m)} - y^{(m)}) x_{1}^{(m)} \\ (\hat{y}^{(1)} - y^{(1)}) x_{2}^{(1)} + ... + (\hat{y}^{(m)} - y^{(m)}) x_{2}^{(m)} \\ \vdots \\ (\hat{y}^{(1)} - y^{(1)}) x_{n}^{(1)} + ... + (\hat{y}^{(m)} - y^{(m)}) x_{n}^{(m)} \end{pmatrix} \\ = & \frac{1}{m} \boldsymbol{X}^{T} (\boldsymbol{\hat{y}} - \boldsymbol{y}) \end{aligned}

上式最后一步可以反推回去验证。

bJ(w1,b)=b(12mi=1m(y^(i)y(i))2)=b(12m[(y^(1)y(1))2+...+(y^(m)y(m))2])=1m(y^(1)y(1))b(y^(1)y(1))+...+1m(y^(m)y(m))b(y^(m)y(m))=1m(y^(1)y(1))b(w1x1(1)+wnxn(1)+by(1))+...+1m(y^(m)y(m))b(w1x1(m)+wnxn(m)+by(m))=1m[(y^(1)y(1))+...+(y^(m)y(m))]=1mi=1m(y^y) \begin{aligned} \frac{\partial}{\partial b} J (w_{1}, b) = & \frac{\partial}{\partial b} (\frac{1}{2m} \sum_{i = 1}^{m} (\hat{y}^{(i)} - y^{(i)})^{2}) \\ = & \frac{\partial}{\partial b} (\frac{1}{2m} [(\hat{y}^{(1)}- y^{(1)})^{2} + ... + (\hat{y}^{(m)} - y^{(m)})^{2}]) \\ = & \frac{1}{m} (\hat{y}^{(1)} - y^{(1)}) \frac{\partial}{\partial b} (\hat{y}^{(1)} - y^{(1)}) + ... + \frac{1}{m} (\hat{y}^{(m)} - y^{(m)}) \frac{\partial}{\partial b} (\hat{y}^{(m)} - y^{(m)}) \\ = & \frac{1}{m} (\hat{y}^{(1)} - y^{(1)}) \frac{\partial}{\partial b} (w_{1} x_{1}^{(1)} \ldots + w_{n} x_{n}^{(1)} + b - y^{(1)}) + ... \\ & + \frac{1}{m} (\hat{y}^{(m)} - y^{(m)}) \frac{\partial}{\partial b} (w_{1} x_{1}^{(m)} \ldots + w_{n} x_{n}^{(m)} + b - y^{(m)}) \\ = & \frac{1}{m} [(\hat{y}^{(1)} - y^{(1)}) + ... + (\hat{y}^{(m)} - y^{(m)})] \\ = & \frac{1}{m} \sum_{i = 1}^{m} (\boldsymbol{\hat{y}} - \boldsymbol{y}) \end{aligned}

代码实现:

"""
outputs:       type=numpy.ndarray, shape=(num_samples, 1)
labels:        type=numpy.ndarray, shape=(num_samples, 1)
inputs:        type=numpy.ndarray, shape=(num_samples, num_features)
num_samples:   type=int
learning_rate: type=float
weights:       type=numpy.ndarray, shape=(num_features, 1)
bias:          type=float
"""

weights -= learning_rate / num_samples * inputs.T @ (outputs - labels)
bias -= learning_rate / num_samples * np.sum(outputs - labels)

5. 正规方程

线性回归模型的模型参数还可以使用正规方程(regulation equation,也称闭式解(closed-form solution))求解。

对代价函数求模型参数的导数,令导数为零,解出的模型参数为最优解:

w^=(XTX)1XTy \boldsymbol{\hat{w}} = (\boldsymbol{X}^{T} \boldsymbol{X})^{-1} \boldsymbol{X}^{T} \boldsymbol{y}

  1. w^=(w;b)\boldsymbol{\hat{w}} = (\boldsymbol{w}; b)w\boldsymbol{w}bb的拼接。
  2. X\boldsymbol{X}不同于之前公式中的X\boldsymbol{X}。为了方便运算,在最右边多加一列11,此时:
    X=(x1(1)x2(1)xn(1)1x1(2)x2(2)x2(n)11x1(m)x2(m)xn(m)1) \boldsymbol{X} = \begin{pmatrix} x_{1}^{(1)} & x_{2}^{(1)} & \ldots & x_{n}^{(1)} & 1 \\ x_{1}^{(2)} & x_{2}^{(2)} & \ldots & x_{2}^{(n)} & 1 \\ \vdots & \vdots & \ddots & \vdots & 1 \\ x_{1}^{(m)} & x_{2}^{(m)} & \ldots & x_{n}^{(m)} & 1 \end{pmatrix}
  3. 线性回归的代价函数,即平方误差函数,总是一个凸函数,只有一个全局最优解。
  4. 使用正规方程不需要使用特征缩放。
  5. 线性回归中使用正规方程的优点:不需要选取学习率;不需要多次迭代的训练。
  6. 线性回归中使用正规方程的缺点: 求矩阵的逆时间复杂度为O(n3)O(n^{3}),当特征数n很大时非常耗时;有些矩阵不可逆。
  7. 如果矩阵X\boldsymbol{X}中有两个特征的值可以通过一个线性方程联系起来,那么XTX\boldsymbol{X}^{T} \boldsymbol{X}不可逆。解决方法是删除冗余的特征。
  8. 如果样本数mm小于特征数nn,有时候会使XTX\boldsymbol{X}^{T} \boldsymbol{X}不可逆。解决方法是删除一些特征,或是使用正则化。

求闭式解的过程:

J(w^)=12mi=1m(y^(i)y(i))2=12m(y^y)T(y^y)=12m(Xw^y)T(Xw^y) \begin{aligned} J(\boldsymbol{\hat{w}}) = & \frac{1}{2m} \sum_{i = 1}^{m} (\hat{y}^{(i)} - y^{(i)})^{2} \\ = & \frac{1}{2m} (\boldsymbol{\hat{y}} - \boldsymbol{y})^{T} (\boldsymbol{\hat{y}} - \boldsymbol{y}) \\ = & \frac{1}{2m} (\boldsymbol{X} \boldsymbol{\hat{w}} - \boldsymbol{y})^{T} (\boldsymbol{X} \boldsymbol{\hat{w}} - \boldsymbol{y}) \end{aligned}

JJw^\boldsymbol{\hat{w}}求偏导:

$$
\begin{aligned}

\frac{\partial}{\partial \boldsymbol{\hat{w}}} J(\boldsymbol{\hat{w}}) = \frac{1}{m} \boldsymbol{X}^{T} (\boldsymbol{X} \boldsymbol{\hat{w}} - \boldsymbol{y})

\end{aligned}
$$

w^J(w^)=0\frac{\partial}{\partial \boldsymbol{\hat{w}}} J(\boldsymbol{\hat{w}}) = 0解得w^\boldsymbol{\hat{w}}

XT(Xw^y)=0XTXw^=XTyw^=(XTX)1XTy \begin{aligned} \boldsymbol{X}^{T} (\boldsymbol{X} \boldsymbol{\hat{w}} - \boldsymbol{y}) = & 0 \\ \boldsymbol{X}^{T} \boldsymbol{X} \boldsymbol{\hat{w}} = & \boldsymbol{X}^{T} \boldsymbol{y} \\ \boldsymbol{\hat{w}} = & (\boldsymbol{X}^{T} \boldsymbol{X})^{-1} \boldsymbol{X}^{T} \boldsymbol{y} \end{aligned}

代码实现:

"""
labels:      type=numpy.ndarray, shape=(num_samples, 1)
inputs:      type=numpy.ndarray, shape=(num_samples, num_features)
inputs(new): type=numpy.ndarray, shape=(num_samples, num_features + 1)
inputs_t:    type=numpy.ndarray, shape=(num_features + 1, num_samples)
params:      type=numpy.ndarray, shape=(num_features + 1, 1)
"""

inputs = np.concatenate((inputs, np.ones((inputs.shape[0], 1))), axis=1)
inputs_t = inputs.T
params = np.linalg.inv(inputs_t @ inputs) @ inputs_t @ labels

6. 波士顿房价求解

使用线性回归对其波士顿房价问题求解。代码可以从我的GitHub克隆或下载,提供了.py.ipynb两个版本。

在终端或控制台运行时,先运行命令export PYTHONPATH=xxx/machine-learning/将项目目录添加到PYTHONPATH,修改xxx为项目的目录。

training: 100%|███████████████████████| 100/100 [00:00<00:00, 18099.18it/s]
loss on training set after training 100 epochs: 34.51055400910424
loss on test set after training 100 epochs: 35.1717284870999
loss on training set by regulation equation: 9.979109907119023
loss on test set by regulation equation: 13.597982883439103

收敛曲线图:

收敛曲线

训练结果:

训练结果

正规方程的结果:

正规方程结果

目前来看,使用正规方程的结果要好一些,但梯度下降的方法还可以改善。

7. 参考

  1. sklearn波士顿房价数据集
  2. 网易云课堂-吴恩达机器学习视频
  3. 机器学习/周志华著. --北京: 清华大学出版社,2016(2016.2重印)

本文内容纯属个人学习过程中的笔记和思考,不作为详细的教程。见解尚浅,承蒙斧正。

Like a Swift, like a Python.
——落空开关,a fallthrough switch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!