本主题讲解一个特殊的改进型(优化型)线性回归:
- 从矩阵奇异值问题,解释Ridge回归对奇异值问题的解决;
- Ridge回归的sklearn与numpy实现;
- Ridge回归的数学基础;
- Rifge回归的数据集标准的意义与标准化实现;
- Ridge回归的特征选型的意义与过拟合问题的解决;
一、线性回归中的奇异值问题
1. 矩阵奇异的问题
在线性回归中,最小二乘法(OLS:Ordinary Least Squares)依赖一个条件是可逆的。有一种情况下很容易不可逆的(奇异矩阵),就是特征比样本还多的时候。
怎么确保矩阵(其实是方阵) 是可逆的,从而解决计算问题?
二、Ridge回归
如果在上面的线性回归中,调整sigma参数,很容易出现矩阵奇异。为了解决矩阵奇异,在局部加权基础上,对局部系数调整,使得矩阵不发生奇异。
系数调整方式是:
因为单位矩阵形象的称呼为岭回归。
1.1 奇异值分解
假设是一个阶矩阵,其中的元素全部属于实数域或复数域。则存在一个分解使得:
其中是阶酉矩阵;是半正定阶对角矩阵;而,即的共轭转置,是阶酉矩阵。对角线上的元素,其中即为的奇异值。
这样的分解就称作的奇异值分解。
如果奇异值由大而小排列。如此便能由唯一确定了。(虽然和仍然不能确定)
酉矩阵
n阶复方阵U的n个列向量是U空间的一个标准正交基,则U是酉矩阵(Unitary Matrix)。
酉矩阵是正交矩阵往复数域上的推广。
若的复数矩阵 满足:
其中为的共轭转置, 为 阶单位矩阵,则 称为酉矩阵。共轭转置
下面使用例子来说明共轭转置矩阵的定义。
import numpy as np
# 对实数域来讲就是转置矩阵
m1=np.matrix(
[
[1, 2, 3],
[4, 5, 6]
]
)
print(m1.H)
[[1 4]
[2 5]
[3 6]]
import numpy as np
# 对对复数域来说,需要对虚部进行变号
m2=np.matrix(
[
[1+2j, 2-3j, -3-4j],
[-4+5j, -5+6j, 6]
]
)
print(m2.H)
[[ 1.-2.j -4.-5.j]
[ 2.+3.j -5.-6.j]
[-3.+4.j 6.-0.j]]
- 奇异值分解的例子
下面使用numpy求解矩阵奇异值分解,这样比较基础的数学概念实现,大家可以暂时不用关注,可以自己私下补充相关的数学推导。
import numpy as np
# numpy.linalg.svd(a, full_matrices=True, compute_uv=True)
# 返回三个矩阵
m1=np.matrix(
[
[1, 2, 3],
[4, 5, 6]
]
)
u,s,vh=np.linalg.svd(m1)
print(u)
print(s)
print(vh)
# 其中s返回的奇异值,只是返回对角线上元素,而且安装降序排列。
# vh是分解的共轭转置矩阵
[[-0.3863177 -0.92236578]
[-0.92236578 0.3863177 ]]
[9.508032 0.77286964]
[[-0.42866713 -0.56630692 -0.7039467 ]
[ 0.80596391 0.11238241 -0.58119908]
[ 0.40824829 -0.81649658 0.40824829]]
- U矩阵的校验
import numpy as np
m=np.matrix(
[
[1, 2, 3],
[4, 5, 6]
]
)
u,s,vh=np.linalg.svd(m)
# 校验其中的U矩阵
re=u*u.H
print(re)
re=vh.H*vh
print(re)
# 注意在矩阵下支持内积运算符号*
re=np.matmul(u,u.H)
print(re)
[email protected]
print(re)
[[1. 0.]
[0. 1.]]
[[1.00000000e+00 1.66533454e-16 8.32667268e-17]
[1.66533454e-16 1.00000000e+00 5.55111512e-17]
[8.32667268e-17 5.55111512e-17 1.00000000e+00]]
[[1. 0.]
[0. 1.]]
[[1. 0.]
[0. 1.]]
1.2 Ridge回归的可逆数学推导
Ridge回归的求解公式:
如果分解为(考虑实数域):
则Ridge回归求解为
import numpy as np
# 对接矩阵与转置相乘的结果,实际是方阵,并且值是对角线上平方
m=np.matrix(
[
[2, 0, 0],
[0, 3, 0]
]
)
print(m*m.H)
print(m.H*m)
[[4 0]
[0 9]]
[[4 0 0]
[0 9 0]
[0 0 0]]
根据上面公式很容易知道,在取值合适的情况下, 可逆是成立的。 但是上面公式不能确保一定可逆,需要在合适的取值情况下才能满足可逆。
1.3 Ridge回归的numpy实现
Ridge回归实现的公式:
|-
- matrix与ndarray计算的方便性
import numpy as np
z=np.matrix([
[2,0,0],
[0,2,0],
[0,0,2]
])
print(z**(-1))
# 如果使用ndarray就没有这么方便的运算
I=np.eye(3)
print(I)
print(type(I))
[[0.5 0. 0. ]
[0. 0.5 0. ]
[0. 0. 0.5]]
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
- Ridge回归实现代码
import numpy as np
# 数据初始化
data=np.loadtxt('ex0.txt')
X_DATA=data[:,1]
Y_DATA=data[:,2]
# 数据格式化
X=np.zeros(shape=(X_DATA.shape[0], 2), dtype=np.float) # 考虑偏置项,在测试特征加一维
Y=Y_DATA.reshape(Y_DATA.shape[0], 1)
X[:, 0]=X_DATA
X[:, 1]=1
# 我们全部采用矩阵模式(这样可以使用矩阵更加方便的运算):
X=np.matrix(X)
Y=np.matrix(Y)
p_lambda=0.2
W=(X.T*X+p_lambda * np.eye(X.shape[1])).I*X.T*Y
print(W)
# 预测效果
predict=X*W
#print(predict)
[[1.69269 ]
[3.00602279]]
- Ridge回归可视化
% matplotlib inline
import numpy as np
# 数据初始化
data=np.loadtxt('ex0.txt')
X_DATA=data[:,1]
Y_DATA=data[:,2]
# 数据格式化
X=np.zeros(shape=(X_DATA.shape[0], 2), dtype=np.float) # 考虑偏置项,在测试特征加一维
Y=Y_DATA.reshape(Y_DATA.shape[0], 1)
X[:, 0]=X_DATA
X[:, 1]=1
# 我们全部采用矩阵模式(这样可以使用矩阵更加方便的运算):
X=np.matrix(X)
Y=np.matrix(Y)
p_lambda=0.00001 #可以改变p_lambda来看看训练结果
W=(X.T*X+p_lambda * np.eye(X.shape[1])).I*X.T*Y
# 预测效果
predict=X*W
# 可视化
figure=plt.figure('数据集可视化',figsize=(6,4))
ax=figure.add_axes([0.1,0.1,0.8,0.8],xlabel='X',ylabel='Y')
ax.set_xlim(-0.2,1.2)
ax.set_ylim(1,5.5)
ax.scatter(X_DATA,Y_DATA,label='不知名数据集',marker='.',color=(1,0,0,1))
ax.plot(X_DATA,predict,label='Ridge回归',color='b',linewidth=3)
ax.legend()
plt.show()
1.4 Ridge回归的sklearn实现
- 考虑截距的情况
from sklearn.linear_model import Ridge
'''
class sklearn.linear_model.Ridge(
alpha=1.0,
fit_intercept=True,
normalize=False,
copy_X=True,
max_iter=None,
tol=0.001,
solver=’auto’,
random_state=None)
'''
# Ridge回归
regression =Ridge(alpha=0.001,fit_intercept=True)
# 加载数据
data=np.loadtxt('ex0.txt')
X_DATA=data[:,1]
Y_DATA=data[:,2]
X_DATA=X_DATA.reshape(-1,1)
Y_DATA=Y_DATA.reshape(-1,1)
regression.fit(X_DATA,Y_DATA)
print('评估:',regression.score(X_DATA, Y_DATA))
# 斜率
print('斜率:',regression.coef_)
# 截距
print('截距:',regression.intercept_ )
predict=regression.predict(X_DATA)
# 可视化
figure=plt.figure('数据集可视化',figsize=(6,4))
ax=figure.add_axes([0.1,0.1,0.8,0.8],xlabel='X',ylabel='Y')
ax.set_xlim(-0.2,1.2)
ax.set_ylim(1,5.5)
ax.scatter(X_DATA,Y_DATA,label='不知名数据集',marker='.',color=(1,0,0,1))
ax.plot(X_DATA,predict,label='Ridge回归',color='b',linewidth=3)
ax.legend()
plt.show()
评估: 0.9731300856492625
斜率: [[1.69522337]]
截距: [3.00779172]
比较上面的运算,结果基本上差不多,尽管求解算法我们没有关注。
- 不考虑截距的情况
from sklearn.linear_model import Ridge
'''
class sklearn.linear_model.Ridge(
alpha=1.0,
fit_intercept=True,
normalize=False,
copy_X=True,
max_iter=None,
tol=0.001,
solver=’auto’,
random_state=None)
'''
# Ridge回归
regression =Ridge(alpha=0.001,fit_intercept=False)
# 加载数据
data=np.loadtxt('ex0.txt')
X_DATA=data[:,1]
Y_DATA=data[:,2]
X_DATA=X_DATA.reshape(-1,1)
Y_DATA=Y_DATA.reshape(-1,1)
regression.fit(X_DATA,Y_DATA)
print('评估:',regression.score(X_DATA, Y_DATA))
# 斜率
print('斜率:',regression.coef_)
# 截距
print('截距:',regression.intercept_ )
predict=regression.predict(X_DATA)
# 可视化
figure=plt.figure('数据集可视化',figsize=(6,4))
ax=figure.add_axes([0.1,0.1,0.8,0.8],xlabel='X',ylabel='Y')
ax.set_xlim(-0.2,1.2)
ax.set_ylim(0,5.5)
ax.scatter(X_DATA,Y_DATA,label='不知名数据集',marker='.',color=(1,0,0,1))
ax.plot(X_DATA,predict,label='Ridge回归',color='b',linewidth=3)
ax.legend()
plt.grid(True)
plt.show()
评估: -8.485203616736882
斜率: [[6.23058178]]
截距: 0.0
1.5. 关于的分析
- sklearn官方文档中的一个图
在sklearn官方文档有一个图说明了随着的变化,每个特征的加权系数的变化(这种变化称为岭迹,绘制的图也称岭迹图)。下面我们使用图示来说明。
% matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
'''
numpy.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None)
|-start: base ** start is the starting value of the sequence.
'''
# 构造了10*10的矩阵
col=np.arange(1,11)
col=np.matrix(col)
row=np.arange(0, 10)
row=np.matrix(row)
row=row.T
x = 1.0/(col+row)
y = np.ones(10)
n_alphas = 200
alphas = np.logspace(-10,-2,n_alphas)
clf = linear_model.Ridge(fit_intercept=False) #截距为不为0
# 加权系数
coefs = []
for a in alphas:
clf.set_params(alpha = a)
clf.fit(x,y)
coefs.append(clf.coef_)
ax = plt.gca()
ax.plot(alphas,coefs)
ax.set_xscale('log')
ax.set_xlim(ax.get_xlim()[::-1]) #坐标轴换一个方向
plt.grid(True)
plt.show()
上图明显看见有四个特征的加权系数,随着的变化,在逐步趋近于0。
- 使用4个特征的鸢尾花数据观察的变化产生的影响
% matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from sklearn import datasets
# 鸢尾花数据集
data,target=datasets.load_iris(return_X_y=True)
x=data[:5] #改变样本个数,观察效果非常明显
y=target[50:55]
# lambda变化200次,从10**-2到10**-10
n_alphas = 50
alphas = np.logspace(-10,-2,n_alphas)
regression =Ridge(fit_intercept=True)
# 加权系数
coefs = []
for a in alphas:
clf.set_params(alpha = a)
clf.fit(x,y)
coefs.append(clf.coef_)
figure=plt.figure('数据集可视化',figsize=(6,4))
ax=figure.add_axes([0.1,0.1,0.8,0.8],xlabel='X',ylabel='Y')
# plot自动遍历每列,按照不同的颜色绘制曲线
ax.plot(alphas,coefs)
ax.set_xscale('log') # 不按照等分模式计算x轴刻度,按照对数的指数来计算刻度
ax.set_xlim(ax.get_xlim()[::-1]) #坐标轴换一个方向
# 有网格线,更加容易观察
plt.grid(True)
plt.show()
上图明显看见有三个特征的加权系数,随着的变化,在逐步趋近于0。当样本数目在50个的时候,基本上对特征的加权系数没有影响;如果把样本数量改成5个,明显,有三个特征的加权系数趋于0,从而在决策的时候,这三个特征的影响基本上就消失。
所以的作用不仅仅是解决奇异的问题,还可以根据样本的数量域特征的数量,影响到决策的特征数量的多少。
1.6 Ridge回归中数据集标准化的必要性
从上面可以看得出来,在Ridge回归中,如果样本数偏少,某些特征在决策中基本上没有作用,为了防止某些重要特征最后参与不了决策,所以需要对数据集进行标准化。标准化的目的是所有数据站在统一起跑线上,这样避免某些重要特征因为数据本身偏小,在训练中容易被丢弃。
数据集标准化也称中心化,就是将数据的均值调整到0,标准差调整为1;计算过程很简单就是将所有数据减去平均值后再除以标准差。
数据标准化公式:
|-
标准化数据集的均值为0:
|-
标准化数据集的方差为1:
|-
- 计算均值,标准方差与数据标准化
import numpy as np
from sklearn import preprocessing
m=np.array(
[
[ 1,2,3,4],
[ 0,4,3,2],
[ 3,1,9,7]
],dtype=np.float64
)
pre_data_sk=preprocessing.scale(m)
print ("\n使用sklearn标准化结果:\n",pre_data_sk)
# 标准化
c_mean=m.mean(axis=0)
c_std=m.std(axis=0)
pre_data_mn=(m-c_mean)/c_std
print("\n使用numpy标准化结果:\n",pre_data_mn)
print('\n比较结果:')
print(pre_data_sk==pre_data_mn)
使用sklearn标准化结果:
[[-0.26726124 -0.26726124 -0.70710678 -0.16222142]
[-1.06904497 1.33630621 -0.70710678 -1.13554995]
[ 1.33630621 -1.06904497 1.41421356 1.29777137]]
使用numpy标准化结果:
[[-0.26726124 -0.26726124 -0.70710678 -0.16222142]
[-1.06904497 1.33630621 -0.70710678 -1.13554995]
[ 1.33630621 -1.06904497 1.41421356 1.29777137]]
比较结果:
[[ True True True True]
[ True True True True]
[ True True True True]]
- 数据集标准化实现代码
数据标准化包含输入与输出;
% matplotlib inline
import matplotlib.pyplot as plt
from sklearn import preprocessing
import numpy as np
X_DATA = np.loadtxt('ex2x.dat')
Y_DATA = np.loadtxt('ex2y.dat')
# 可视化
figure=plt.figure('数据集可视化',figsize=(10,4))
# 数据集标准化前
ax1=figure.add_axes([0.1,0.1,0.4,0.8],xlabel='年龄',ylabel='身高')
ax1.scatter(X_DATA,Y_DATA,label='原始数据集',marker='.',color=(0,0,1,1))
ax1.legend()
# 数据标准化-------------
STD_X=preprocessing.scale(X_DATA)
STD_Y=preprocessing.scale(Y_DATA)
# 数据集标准化后
ax2=figure.add_axes([0.6,0.1,0.4,0.8],xlabel='年龄',ylabel='身高')
ax2.scatter(STD_X,STD_Y,label='标准化数据集',marker='.',color=(1,0,1,1))
ax2.legend()
plt.grid(True)
plt.show()
标准化数据集以后,均值为0
- 标准化数据集训练
% matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from sklearn import datasets
from sklearn import preprocessing
# 鸢尾花数据集
data,target=datasets.load_iris(return_X_y=True)
x=data[:] #改变样本个数,观察效果非常明显
y=target[:]
x=x.astype(np.float64)
y=y.astype(np.float64)
x=preprocessing.scale(x)
y=preprocessing.scale(y)
# lambda变化200次,从10**-2到10**-10
n_alphas = 50
alphas = np.logspace(-10,4,n_alphas)
regression =Ridge(fit_intercept=True)
# 加权系数
coefs = []
for a in alphas:
clf.set_params(alpha = a)
clf.fit(x,y)
coefs.append(clf.coef_)
figure=plt.figure('数据集可视化',figsize=(6,4))
ax=figure.add_axes([0.1,0.1,0.8,0.8],xlabel='X',ylabel='Y')
# plot自动遍历每列,按照不同的颜色绘制曲线
ax.plot(alphas,coefs)
ax.set_xscale('log') # 不按照等分模式计算x轴刻度,按照对数的指数来计算刻度
ax.set_xlim(ax.get_xlim()[::-1]) #坐标轴换一个方向
# 有网格线,更加容易观察
plt.grid(True)
plt.show()
标准化的数据集,在回归上,特征会尽量参与决策,但权重系数明显反应了特征的决策贡献。
如果要确定哪个是最合适的,需要做交叉验证,得到较好的模型。
- 交叉验证
交叉验证(Cross Validation),有的时候也称作循环估计(Rotation Estimation),是一种统计学上将数据样本切割成较小子集的实用方法,该理论是由Seymour Geisser提出的。
在给定的建模样本中,拿出大部分样本进行建模型,留小部分样本用刚建立的模型进行预报,并求这小部分样本的预报误差,记录它们的平方加和。这个过程一直进行,直到所有的样本都被预报了一次而且仅被预报一次。把每个样本的预报误差平方加和,称为PRESS(predicted Error Sum of Squares)。
1.7 Ridge的数学基础
添加一个项,需要有一定的数学理论与机器学习理论支持。
Ridge回归的求解中增加这一项,我们可以从机器学习的角度返回损失函数的定义,甚至可以返回到决策模型以及损失模型的建模上。
Ridge回归的损失函数模型是:
|-
其中是的每个元素。
如果返回到损失模型的建立(就是从误差的概率模型,到最大似然函数的推导),我们可以看得出实际是概率的加权平衡:
对似然函数求对数:
大家可以从早器的最大似然函数建模可以看得出来,实际是对概率做的平衡(就是先验概率与后验概率)。
- 线性回归的概率模型(Bayes概率模型)
假设线性分布的决策模型是:
|-
假设线性分布的误差模型是:
|-
用数学术语的描述就是:
|-,则
从而得到上述最大似然估计函数。
- Ridge回归的概率模型(Bayes概率模型)
假设线性分布的决策模型是:
|-
假设线性分布的误差模型是:
|-
使用数学术语描述为:
|-,且
|
|-注意这里使用了误差的先验概率模型,其中权重系数使用后验概率模型(也称高斯选样模型)。
下面可以使用简单的概率推导一下:
|-
|-
下面可以根据上述概率得到极大似然函数:
不考虑两个常数项,并且记:,我们可以取损失函数为:
|-
现在我们看,为什么越大,则权重系数就会都趋于0,因为权重系数服从正态分布,越大,就是正态分布的方差越小,从而权重系数都趋于均值0。
注意: 根据上述推理,肯定必须大于0,他实际代表的是方差的倒数。
Ridge损失函数的其他几何解释
目前还存在Ridge回归损失函数的其他解释,但这些解释都是基于直观的解释,没有严密的数学推理作为保证,我本人觉得是不具备说服力的,这里不详细介绍。过拟合问题的解决
因为Ridge回归,可以解决特征过多的问题,从而可以解决因为特征过多导致的过拟合问题。
如果Ridge回归(岭回归)理解的比较好的话,LASSO回归就容易理解了。