机器学习中的一些简单数学原理
线性回归
对于线性模型,其预测模型可以写为:
y
^
=
θ
0
+
θ
1
x
1
+
θ
2
x
2
+
⋯
+
θ
n
x
n
\hat{y}=\theta_0+\theta_1x_1+\theta_2x_2+\cdots+\theta_nx_n
y^=θ0+θ1x1+θ2x2+⋯+θnxn
其中:
- y ^ \hat{y} y^是预测值
- n是特征的数量
- x i x_i xi是第i个特征值
- θ j \theta_j θj是第j个模型参数(包括偏置项 θ 0 \theta_0 θ0以及特征权重 θ 0 , θ 1 , ⋯ , θ n \theta_0,\theta_1,\cdots,\theta_n θ0,θ1,⋯,θn)
如果我们使用向量形式,则可以写为:
y
^
=
h
θ
(
X
)
=
θ
T
⋅
X
\hat{y}=h_\theta(\bf{X})=\bf{\theta^T\cdot X}
y^=hθ(X)=θT⋅X
其中:
- θ \theta θ是模型的参数向量,包括偏置项 θ 0 \theta_0 θ0以及特征权重 θ 1 \theta_1 θ1到 θ n \theta_n θn。
- x \bf{x} x是实例的特征向量,包括从 x 0 x_0 x0到 x n x_n xn, x 0 x_0 x0永远是0。
- θ T ⋅ x \bf{\theta^T\cdot x} θT⋅x是二者的点积
- h θ h_\theta hθ是使用模型参数 θ \theta θ的假设
在使用线性模型的时候,我们评判其好坏的时候往往使用均方误差(MSE): M S E ( X , θ ) = 1 m ∑ i = 1 m ( θ T ⋅ X ( i ) − y ( i ) ) 2 MSE(\bf{X},\theta)=\frac{1}{m}\sum^m_{i=1}(\bf{\theta^T\cdot X^{(i)}-y^{(i)}})^2 MSE(X,θ)=m1i=1∑m(θT⋅X(i)−y(i))2
标准方程
为了得到其最小值,可以直接使用闭式解:
θ
^
=
(
X
T
⋅
X
)
−
1
⋅
X
T
⋅
y
\hat{\theta}=\bf{(X^T\cdot X)^{-1}\cdot X^T\cdot y}
θ^=(XT⋅X)−1⋅XT⋅y
其中:
- θ ^ \hat{\theta} θ^是使成本函数最小的 θ \theta θ值
- y \bf{y} y是包含 y ( 1 ) y^{(1)} y(1)到 y ( m ) y^{(m)} y(m)的目标值向量
对于 X T ⋅ X X^T\cdot X XT⋅X纬矩阵相乘,复杂度在 o ( n 2.4 ) o(n^{2.4}) o(n2.4)到 o ( n 3 ) {o(n^{3})} o(n3)之间,当特征数量n非常大的时候,使用闭式解则会非常慢。但是对于实例数量m来说,其复杂度为 o ( m ) o(m) o(m),因此非常适合大规模数据集的训练。
梯度下降
关于梯度下降,已经是老生常谈,这里做一下要点的笔记。
关于每一轮迭代,梯度下降的大小:
θ
(
n
e
x
t
s
t
e
p
)
=
θ
−
η
∇
θ
M
S
E
(
θ
)
\theta^{(next step)}=\theta-\eta\nabla_\theta MSE(\theta)
θ(nextstep)=θ−η∇θMSE(θ)
对于学习率:
- 学习率太高可能会跳过最小值导致算法发散,误差反而越来越大。
- 如果学习率太低,则需要耗费很长的时间才能收敛。
对于迭代次数:
- 如果迭代次数太少,则可能离最优解较远的时候就停止了训练
- 若果迭代次数过多,达到最优解后仍然后仍然会进行无意义的训练,浪费时间
为了避免迭代次数过多或者过少,可以设置一个容差( ϵ \epsilon ϵ),当成本函数低于容差的时候停止训练
对于成本函数:
- 若成本函数有起伏,则训练的过程中可能收敛到局部最小
- 若成本函数为凸函数,则不存在以上情况
- 在训练之前对数据缩放可以使得训练更加快速
批量梯度下降
批量梯度下降的含义就是计算所有数据在一个点上的平均梯度,根据这个平均梯度来改变参数的数值,其表达式为:
∇
θ
M
S
E
(
θ
)
=
(
∂
∂
θ
0
M
S
E
(
θ
)
∂
∂
θ
1
M
S
E
(
θ
)
⋮
∂
∂
θ
n
M
S
E
(
θ
)
)
=
2
m
X
T
⋅
(
X
⋅
θ
−
y
)
\nabla_{\theta}MSE(\theta)=\begin{pmatrix}{\frac{\partial}{\partial\theta_0}MSE(\theta)}\\ \frac{\partial}{\partial\theta_1}MSE(\theta)\\\vdots\\ \frac{\partial}{\partial\theta_n}MSE(\theta)\end{pmatrix}=\frac{2}{m}\bf{X^T}\cdot(\bf{X\cdot \theta-y})
∇θMSE(θ)=⎝⎜⎜⎜⎛∂θ0∂MSE(θ)∂θ1∂MSE(θ)⋮∂θn∂MSE(θ)⎠⎟⎟⎟⎞=m2XT⋅(X⋅θ−y)
这种情况下,面对非常庞大的数据集,则会花费非常多的时间,但是对于特征值比较多的情况下,使用这种方法则比闭式解快得多。
随机梯度下降
批量梯度下降使用的是一个点上每一个数据集的梯度的平均,而随机梯度下降则是另外一个极端,它是通过选择一个随机的实例在该点处计算梯度值(而不是所有数据的梯度值的平均)。
这种方法适用于成本函数不规则的时候,随机梯度下降可以帮助模型跳出极小值,缺点则是有可能也无法收敛到最优处。为了解决这个问题,可以将学习率最开始设置的较大,并随着训练的迭代慢慢减小,这种方法称为模拟退火。
在sklearn中,可以使用SGDRegressor类来用SGD完成线性回归:
from sklearn.linear_model import SGDRegressor
import numpy as np
X = 2 * np.random.rand(100,1)
y = 4 + 3 * X + np.random.randn(100,1)
# 方程为y = 4 + 3x 但是引入了噪声
sgd_reg = SGDRegressor(max_iter=50,penalty=None,eta0=0.1)
#迭代次数50,低版本的为n_iter,没有正则项,学习率为0.1,
sgd_reg.fit(X,y.ravel())
sgd_reg.intercept_,sgd_reg.coef_
# 截距(array([4.22962816]), 斜率array([2.96515406]))
小批量梯度下降
小批量梯度下降介于随机梯度下降和批量梯度下降之间,其相比于随机梯度下降,优点在于其可以在使用图形处理器的时候得到一个性能的提升。
相比于小批量梯度下降,一方面其可以停到一个更加小的值,另一方面也更难从局部极小值逃脱。相比于批量梯度下降,其收敛更快(但是很难收敛到最小值),批量梯度下降收敛很慢但是能最终停在极小值上。
下面是各类算法的一个比较:
算法 | m(训练集)很大 | 是否支持核外 | n(特征量)很大 | 超参数 | 是否需要缩放 | sklearn |
---|---|---|---|---|---|---|
标准方程 | 快 | 否 | 慢 | 0 | 否 | LinearRegression |
批量梯度下降 | 慢 | 否 | 快 | 2 | 是 | n/a |
随机梯度下降 | 快 | 是 | 快 | ≥2 | 是 | SGDRegressor |
小批量梯度下降 | 快 | 是 | 快 | ≥2 | 是 | n/a |
多项式回归
对于一个非线性方程,比如说二次方程,如:
y
=
0.5
⋅
x
2
+
x
+
2
+
噪
声
y = 0.5\cdot x^2+x+2+噪声
y=0.5⋅x2+x+2+噪声
我们如何进行拟合呢?我们可以将特征值x进行平方,作为一个新的特征,之后再进行一个多元线性拟合即可,代码如下:
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
import numpy as np
m = 100
np.random.seed(42)
X = 6 * np.random.rand(m,1) - 3
y = 0.5 * X**2 + X + 2 + np.random.randn(m,1)
# y = 0.5X^2 + X + 2 + 噪声
ploy_features = PolynomialFeatures(degree=2,include_bias=False)
# 若为True(默认),则会在添加一列1放在矩阵的第一列
X_ploy = ploy_features.fit_transform(X)
lin_reg = LinearRegression()
lin_reg.fit(X_ploy,y)
lin_reg.intercept_,lin_reg.coef_
# 截距(array([1.78134581]),一次项和二次项系数 array([[0.93366893, 0.56456263]]))
另外对于PloynormialFeatures这个转换器,若输入的数值为多列,比如两列,分别为a和b,degree为2,则会多输出3项,分别是 a 2 a^2 a2、 b 2 b^2 b2和 a b ab ab。
学习曲线
我们之前讲过,可以使用循环交叉验证来判断模型是否产生了过度拟合或者拟合不足:
- 若训练集上的表现良好,验证集上的精度很差,则可以说发生了过拟合。
- 若训练集和验证集上的结果都很差这说明拟合不足。
另外就是画出学习曲线,下面分别是是线性模型和10阶多项式模型对之前的二次多项式进行拟合的学习曲线:
线性拟合:
10阶多项式拟合:
对于第一张图,其训练集和测试集上的精度都停留在了一个非常接近且非常高的数值上,这就是典型的拟合不足,对于解决拟合不足,可以使用以下三种方法:
- 使用更多的示例。
- 使用更加复杂的模型。
- 添加更多的特征。
对于第二张图,可以看出其精度在训练集上非常低,而在测试集上较高,这是典型的过拟合。另外,该模型会随着训练集的增加,两条学习曲线将更加接近。对于过拟合,可以使用以下方法解决:
- 使用更多的示例。
- 简化模型
方差/偏差权衡
机器学习中往往存在三种误差:
- 偏差:源于模型的错误假设,比如对模型过度简化。高偏差的模型可能导致对数据的拟合不足。
- 方差:由于对数据微小的变化过度敏感导致的,复杂的模型可能具有一个较高的方差。
- 不可避免的误差:由于数据本身中夹杂的噪声所致。
当增加模型的复杂度时,偏差会减小方差会增大,反之使用较简单的模型时,可能会使得偏差增大方差减小。
正则线性模型
减少过度拟合的方法之一就是对模型正则化,即对模型做出约束。毕竟模型越简单模型就越不容易过拟合。原理就是对成本函数中加入正则项。这里需要注意的是,正则项只需要加入到训练时的成本函数中,当训练完毕时,我们使用未加入正则项的成本函数来评判模型的好坏。另外,大多数正则化模型对输入的特征非常敏感,所以在进行训练之前应该进行模型的正则化。
岭回归
也叫做吉洪诺夫正则化。岭回归的成本函数为:
J
(
θ
)
=
M
S
E
(
θ
)
+
α
1
2
∑
i
=
1
n
θ
i
2
J(\theta)=MSE(\theta)+\alpha\frac{1}{2}\sum^n_{i=1}\theta^2_i
J(θ)=MSE(θ)+α21i=1∑nθi2
其中α是正则化程度,当α=0的时候岭回归就是线性模型。
岭回归的闭式解为:
θ
^
=
(
X
T
⋅
X
+
α
A
)
−
1
⋅
X
T
⋅
y
\hat{\theta}=(\bf{X^T\cdot X+\alpha A})^{-1}\cdot \bf{X^T}\cdot y
θ^=(XT⋅X+αA)−1⋅XT⋅y
下面是使用闭式解来对之前给出的二次多项式的拟合:
from sklearn.linear_model import Ridge
ridge_reg = Ridge(alpha=1,solver="cholesky")
ridge_reg.fit(X,y)
ridge_reg.predict([[1.5]])
# array([[4.82497007]])
当然也可以使用随机梯度下降:
sgd_reg = SGDRegressor(penalty="l2")
#l2代表加入一个正则项,形式为l2范数的平方的一半,即岭回归
sgd_reg.fit(X,y.ravel())
sgd_reg.predict([[1.5]])
# array([4.80094839])
套索回归(Lasso)
也称为最小绝对收缩和选择算子回归。他增加的正则项是权重的l1范数:
J
(
θ
)
=
M
S
E
(
θ
)
+
α
∑
i
=
1
n
∣
θ
i
∣
J(\theta)=MSE(\theta)+\alpha\sum^n_{i=1}|\theta_i|
J(θ)=MSE(θ)+αi=1∑n∣θi∣
在套索回归的情况下,他倾向于消除掉最不重要的特征的权重(置零)。另外Lasso成本函数在
θ
i
=
0
\theta_i=0
θi=0的时候是不可微的,此时使用次梯度向量g代替。也可以将其作为整个套索回归的成本函数的梯度下降,公式如下:
g
(
θ
,
J
)
=
∇
θ
M
S
E
(
θ
)
+
α
(
s
i
g
n
(
θ
1
)
s
i
g
n
(
θ
2
)
⋮
s
i
g
n
(
θ
n
)
)
g(\theta,J)=\nabla_\theta MSE(\theta)+\alpha \begin{pmatrix}sign(\theta_1)\\sign(\theta_2)\\\vdots\\sign(\theta_n)\end{pmatrix}
g(θ,J)=∇θMSE(θ)+α⎝⎜⎜⎜⎛sign(θ1)sign(θ2)⋮sign(θn)⎠⎟⎟⎟⎞
s
i
g
n
(
θ
)
sign(\theta)
sign(θ)的定义为:
s
i
g
n
(
θ
)
=
{
−
1
(
θ
<
0
)
0
(
θ
=
0
)
1
(
θ
>
0
)
sign(\theta)=\begin{cases}-1(\theta<0)\\0(\theta=0)\\1(\theta>0)\end{cases}
sign(θ)=⎩⎪⎨⎪⎧−1(θ<0)0(θ=0)1(θ>0)
下面是使用Lasso回归对二次项进行拟合:
from sklearn.linear_model import Lasso
#也可以使用SGDRegressor,penalty设为"l1"
lasso_reg = Lasso(alpha=0.1)
lasso_reg.fit(X,y)
lasso_reg.predict([[1.5]])
# array([4.77621741])
弹性网络
弹性网络是套索回归和岭回归的这种,其成本函数为:
J
(
θ
)
=
M
S
E
(
θ
)
+
r
α
∑
i
=
1
n
∣
θ
i
∣
+
1
−
r
2
α
∑
i
=
1
n
θ
i
2
J(\theta)=MSE(\theta)+r\alpha\sum^n_{i=1}|\theta_i|+\frac{1-r}{2}\alpha\sum^n_{i=1}\theta^2_i
J(θ)=MSE(θ)+rαi=1∑n∣θi∣+21−rαi=1∑nθi2
当r=0的时候,即为岭回归,当r=1的时候,则变为套索回归。
一般情况下我们默认使用岭回归,但是当有作用的特征只有少数几个的时候可以使用弹性网络或者套索回归,另外一般情况下弹性网络的性能优于套索回归,因为当特征数超过训练实例数量,或者几个特征强相关的情况下,套索回归会变得不稳定。
下面是一个弹性网络的小例子:
from sklearn.linear_model import ElasticNet
elastic_net = ElasticNet(alpha=0.1,l1_ratio=0.5)
#li_ratio对应的是混合比例r
elastic_net.fit(X,y)
elastic_net.predict([[1.5]])
早起停止法
这种正则化方法限制的是训练的次数。即在训练过程中,当预测误差(RMSE)达到最小的时候,停止训练,因为继续在训练集上训练会导致过拟合,预测误差升高。
对于小批量梯度下降和随机梯度下降,由于误差曲线波动比较大,因此可以在最小误差产生后等待一段时间,若不出现更小的误差,则将其回滚到验证误差最低的位置。
下面是早期停止法的一个实现:
from sklearn.base import clone
sgd_reg = SGDRegressor(n_iter=1,warm_start=True,penalty=None,
learning_rate="constant",eta0=0.0005)
#迭代1次,warm_start=True表示会从停下的地方继续训练,学习率为0.0005
minimum_val_error = float("inf")
#初始的最小值
best_epoch = None
#最佳训练步数
best_model = None
#最佳模型
for epoch in range(1000):
sgd_reg.fit(X_train_ploy_scaled,y_train)
y_val_predict = sgd_reg.predict(x_val_poly_scaled)
val_error = mean_squared_error(y_val_predict,y_val)
if val_error < minimum_val_error:
minimum_val_error = val_error
best_epoch = epoch
best_model = clone(sgd_reg)
逻辑回归
逻辑回归被广泛用于估算某一个实例属于某一个类别的概率,它会输出属于这个类别的概率,若概率大于50%,则便定位正类,否则标记为负类。
概率估算
逻辑回归第一步和线性回归相同,都是计算输入特征的加权和,第二部则是将加权和结果带入到一个函数中:
p
^
=
h
θ
(
x
)
=
σ
(
θ
T
⋅
x
)
\hat{p}=h_\theta(\bf x)=\sigma(\theta^T \cdot \bf x)
p^=hθ(x)=σ(θT⋅x)
其中
σ
(
)
\sigma()
σ()成为sigmoid函数,其输出的结果是0~1之间的一个数字,具体定义如下:
σ
(
t
)
=
1
1
+
e
x
p
(
−
t
)
\sigma(t)=\frac{1}{1+exp(-t)}
σ(t)=1+exp(−t)1
从这个函数可以看出,当进行加权和运算之后,若结果为正,则判定为正类,若结果为负,则判定为负类。
训练成本函数
逻辑回归的成本函数如下:
J
(
θ
)
=
−
1
m
∑
i
=
1
m
[
y
(
i
)
l
o
g
(
p
^
(
i
)
)
+
(
1
−
y
(
i
)
)
l
o
g
(
1
−
p
^
(
i
)
)
]
J(\theta)=-\frac{1}{m}\sum^m_{i=1}[y^{(i)}log(\hat{p}^{(i)})+(1-y^{(i)})log(1-\hat{p}^{(i)})]
J(θ)=−m1i=1∑m[y(i)log(p^(i))+(1−y(i))log(1−p^(i))]
其中y为判定的结果,当为正类y=1,当为负类y=0。这样设计成本函数的意义在于为了使其得出的概率远离50%(输出一个模棱两可的结果)。另外这个成本函数不存在闭式解,但是其是一个凸函数,可以通过梯度下降等方法找到全局最小值。
下面是逻辑回归成本函数的导数:
∂
∂
θ
J
(
θ
)
=
1
m
∑
i
=
1
m
(
σ
(
θ
T
⋅
x
(
i
)
)
−
y
(
i
)
)
x
j
(
i
)
\frac{\partial}{\partial\theta}J(\bf{\theta})=\it\frac{1}{m}\sum^m_{i=1}(\sigma(\theta^T\cdot\bf x^{(i)})-\it{y^{(i)}})x_j^{(i)}
∂θ∂J(θ)=m1i=1∑m(σ(θT⋅x(i))−y(i))xj(i)
它的导数和成本函数MSE的倒数非常类似。
决策边界
为了说明问题,文章中使用了鸢尾植物数据集。该数据集中包含四种鸢尾花,以及他们相应的萼片以及花瓣的长度。在这里我们使用逻辑回归来训练一个二分类器来判断是否属于Virginics鸢尾花。首先只选取花瓣宽度作为特征,并通过绘图的方式查看当花瓣长度在0~3cm之间时模型给出的预测结果:
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
iris = datasets.load_iris()
X = iris["data"][:,3:]
#取最后一个特征(花瓣宽度)
y= (iris["target"] == 2).astype(np.int)
#若为鸢尾花则为1,否则为0
log_reg = LogisticRegression()
log_reg.fit(X,y)
X_new = np.linspace(0,3,1000).reshape(-1,1)
y_proba = log_reg.predict_proba(X_new)
plt.plot(X_new,y_proba[:,1],"g-",label="Iris-Virginica")
plt.plot(X_new,y_proba[:,0],"b--",label="Not Iris-Virginica")
plt.xlim([0,3])
plt.ylim([0,1])
plt.xlabel("probability")
plt.ylabel("petal width (cm)")
plt.legend()
可以看出,当花瓣宽度在1.6cm的时候,模型给出的两种可能性相等,这就是决策边界。
对于sklearn,逻辑回归默认使用的是l2惩罚函数来进行正则化,当然也可以选择l1作为惩罚函数。
Softmax回归
Softmax回归可以看做逻辑回归的推广,用于多分类的任务。对于Softmax回归,每一个类别都有一个相应的得分
s
k
(
x
)
s_k(\bf x)
sk(x),计算出得分之后带入softmax函数(也成为归一化函数),估算出属于每一个类别的概率。对于每一个分类的得分,可以写为:
s
k
(
x
)
=
θ
i
T
⋅
x
s_k(\bf x)=\it \theta^T_i \cdot \bf x
sk(x)=θiT⋅x
其中
θ
k
T
\theta^T_k
θkT对应于每一个类别的权重。
softmax函数的定义为:
p
^
k
=
σ
(
s
(
x
)
)
k
=
e
x
p
(
s
k
(
x
)
)
∑
j
=
1
k
e
x
p
(
s
j
(
x
)
)
\hat p_k=\sigma(\bf s(x))_k=\it\frac{exp(s_k(\bf x))}{\sum^k_{j=1}exp(s_j(\bf x))}
p^k=σ(s(x))k=∑j=1kexp(sj(x))exp(sk(x))
从softmax函数可以看出来,某一个示例在某类别中得分越高则属于该该类别的可能性越大。另外softmax不能用于多输出任务。
对于softmax回归,其使用的成本函数为交叉熵函数:
J
(
Θ
)
=
1
m
∑
i
=
1
m
∑
k
=
1
k
y
k
(
i
)
l
o
g
(
p
^
k
(
i
)
)
\bf J(\Theta)=\it \frac{1}{m}\sum^m_{i=1}\sum^k_{k=1}y_k^{(i)}log(\hat p_k^{(i)})
J(Θ)=m1i=1∑mk=1∑kyk(i)log(p^k(i))
其中
Θ
\bf\Theta
Θ是所有类别权重向量
θ
k
\theta_k
θk组成的矩阵。
可以看出,若softmax模型给出的分类概率较低,则成本函数会变高(受到惩罚)。当只有两个类别的时候,交叉熵成本函数退化为log损失函数。
下面是softmax关于
θ
k
\theta _k
θk的权重:
∇
θ
k
J
(
Θ
)
=
1
m
∑
i
=
1
m
(
p
^
k
(
i
)
−
y
k
(
i
)
)
x
(
i
)
\nabla_{\theta_k}\bf J(\Theta)=\it\frac{1}{m}\it\sum ^m_{i=1}(\hat p_k^{(i)}-y_k^{(i)})\bf x\it ^{(i)}
∇θkJ(Θ)=m1i=1∑m(p^k(i)−yk(i))x(i)
有了上面这个式子我们就可以通过梯度下降的方法对softmax回归模型进行训练。下面是一个softmax多分类器的例子:
X = iris["data"][:,(2,3)]
#取两个特征(花瓣长度和花瓣宽度)
y= iris["target"]
softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs",C=10)
#multi_class="multinomial"表示切换为softmax回归,solver="lbfgs"为指定的支持softmax回归的求解器
#C相当于正则化中的α,不同的是其越大表示正则化程度越低。
softmax_reg.fit(X,y)
softmax_reg.predict([[5,2]])
# array([2])
softmax_reg.predict_proba([[5,2]])
# array([[6.38014896e-07, 5.74929995e-02, 9.42506362e-01]])