ML05-Ridge回归

本主题讲解一个特殊的改进型(优化型)线性回归:

  1. 从矩阵奇异值问题,解释Ridge回归对奇异值问题的解决;
  2. Ridge回归的sklearn与numpy实现;
  3. Ridge回归的数学基础;
  4. Rifge回归的数据集标准的意义与标准化实现;
  5. Ridge回归的特征选型的意义与过拟合问题的解决;

一、线性回归中的奇异值问题

1. 矩阵奇异的问题

  在线性回归中,最小二乘法(OLS:Ordinary Least Squares)依赖一个条件是可逆的。有一种情况下很容易不可逆的(奇异矩阵),就是特征比样本还多的时候。
  怎么确保矩阵(其实是方阵) 是可逆的,从而解决计算问题?

二、Ridge回归

  如果在上面的线性回归中,调整sigma参数,很容易出现矩阵奇异。为了解决矩阵奇异,在局部加权基础上,对局部系数调整,使得矩阵不发生奇异。
  系数调整方式是:
  
  
  因为单位矩阵形象的称呼为岭回归。

1.1 奇异值分解

  假设是一个阶矩阵,其中的元素全部属于实数域或复数域。则存在一个分解使得:
  
  其中是阶酉矩阵;是半正定阶对角矩阵;而,即的共轭转置,是阶酉矩阵。对角线上的元素,其中即为的奇异值。
  这样的分解就称作的奇异值分解。
  如果奇异值由大而小排列。如此便能由唯一确定了。(虽然和仍然不能确定)

  1. 酉矩阵
      n阶复方阵U的n个列向量是U空间的一个标准正交基,则U是酉矩阵(Unitary Matrix)。
      酉矩阵是正交矩阵往复数域上的推广。
      若的复数矩阵 满足:
      
      其中为的共轭转置, 为 阶单位矩阵,则 称为酉矩阵。

  2. 共轭转置
      下面使用例子来说明共轭转置矩阵的定义。

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]]
  1. 奇异值分解的例子
      下面使用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]]
  1. 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回归实现的公式:
    |-

  1. 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.]]

  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]]
  1. 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()
Ridge回归numpy实现的可视化
1.4 Ridge回归的sklearn实现
  1. 考虑截距的情况
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]
Ridge回归sklearn实现可视化

比较上面的运算,结果基本上差不多,尽管求解算法我们没有关注。

  1. 不考虑截距的情况
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
sklean算法中不考虑截距的情况
1.5. 关于的分析
  1. 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。

  1. 使用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()
鸢尾花4个特征的权重与λ的变化关系

  上图明显看见有三个特征的加权系数,随着的变化,在逐步趋近于0。当样本数目在50个的时候,基本上对特征的加权系数没有影响;如果把样本数量改成5个,明显,有三个特征的加权系数趋于0,从而在决策的时候,这三个特征的影响基本上就消失。
  所以的作用不仅仅是解决奇异的问题,还可以根据样本的数量域特征的数量,影响到决策的特征数量的多少。

1.6 Ridge回归中数据集标准化的必要性

  从上面可以看得出来,在Ridge回归中,如果样本数偏少,某些特征在决策中基本上没有作用,为了防止某些重要特征最后参与不了决策,所以需要对数据集进行标准化。标准化的目的是所有数据站在统一起跑线上,这样避免某些重要特征因为数据本身偏小,在训练中容易被丢弃。

  数据集标准化也称中心化,就是将数据的均值调整到0,标准差调整为1;计算过程很简单就是将所有数据减去平均值后再除以标准差。

  数据标准化公式:
    |-

  标准化数据集的均值为0:
    |-
  标准化数据集的方差为1:
    |-\sigma ^2 = \dfrac{1}{n} \sum \limits _{i=1} ^ n (x_i^ \prime - \mu)^2 = \dfrac{1}{n} \sum \limits _{i=1} ^ n (x_i^ \prime)^2 = \dfrac{1}{n} \sum \limits _{i=1} ^ n (\dfrac{x_i - \mu}{\sigma})^2= \dfrac{ \dfrac{1}{n} \sum \limits _{i=1}^{n} (x_i- \mu )^2}{\sigma ^ 2} =\dfrac{\sigma^2}{\sigma^2}=1

  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]]
  1. 数据集标准化实现代码
      数据标准化包含输入与输出;
% 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

  1. 标准化数据集训练
% 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()
鸢尾花4个特征的权重系数在λ很大的时候趋于0的效果

  标准化的数据集,在回归上,特征会尽量参与决策,但权重系数明显反应了特征的决策贡献。
  如果要确定哪个是最合适的,需要做交叉验证,得到较好的模型。

  1. 交叉验证

  交叉验证(Cross Validation),有的时候也称作循环估计(Rotation Estimation),是一种统计学上将数据样本切割成较小子集的实用方法,该理论是由Seymour Geisser提出的。
  在给定的建模样本中,拿出大部分样本进行建模型,留小部分样本用刚建立的模型进行预报,并求这小部分样本的预报误差,记录它们的平方加和。这个过程一直进行,直到所有的样本都被预报了一次而且仅被预报一次。把每个样本的预报误差平方加和,称为PRESS(predicted Error Sum of Squares)。

1.7 Ridge的数学基础

  添加一个项,需要有一定的数学理论与机器学习理论支持。
  Ridge回归的求解中增加这一项,我们可以从机器学习的角度返回损失函数的定义,甚至可以返回到决策模型以及损失模型的建模上。

  Ridge回归的损失函数模型是:
    |-
    其中是的每个元素。

  如果返回到损失模型的建立(就是从误差的概率模型,到最大似然函数的推导),我们可以看得出实际是概率的加权平衡:
  对似然函数求对数:
    
    
    
    

  大家可以从早器的最大似然函数建模可以看得出来,实际是对概率做的平衡(就是先验概率与后验概率)。

  1. 线性回归的概率模型(Bayes概率模型)

  假设线性分布的决策模型是:
    |-
  假设线性分布的误差模型是:
    |-

  用数学术语的描述就是:
     |-,则
  从而得到上述最大似然估计函数。

  1. Ridge回归的概率模型(Bayes概率模型)
      假设线性分布的决策模型是:
        |-
      假设线性分布的误差模型是:
        |-
      使用数学术语描述为:
         |-,且
         |
         |-注意这里使用了误差的先验概率模型,其中权重系数使用后验概率模型(也称高斯选样模型)。

  下面可以使用简单的概率推导一下:
    |-
    |-

  下面可以根据上述概率得到极大似然函数:
    
    
    l(W)= k\ log\ \dfrac{1}{\sqrt{2 \pi } \tau }- \dfrac{1}{\tau^2}\ \dfrac{1}{2} \sum \limits_{j=1}^{k}(w_j)^2 \ + \ m\ log\ \dfrac{1}{ \sqrt{2\pi} \sigma } - \dfrac{1}{ \sigma^2}\ \dfrac{1}{2} \sum \limits_{i=1}^{m}(y^{(i)} - x^{ (i) } W)^2

  不考虑两个常数项,并且记:,我们可以取损失函数为:
    |-

  现在我们看,为什么越大,则权重系数就会都趋于0,因为权重系数服从正态分布,越大,就是正态分布的方差越小,从而权重系数都趋于均值0。

注意: 根据上述推理,肯定必须大于0,他实际代表的是方差的倒数。

  1. Ridge损失函数的其他几何解释
      目前还存在Ridge回归损失函数的其他解释,但这些解释都是基于直观的解释,没有严密的数学推理作为保证,我本人觉得是不具备说服力的,这里不详细介绍。

  2. 过拟合问题的解决
      因为Ridge回归,可以解决特征过多的问题,从而可以解决因为特征过多导致的过拟合问题。


  如果Ridge回归(岭回归)理解的比较好的话,LASSO回归就容易理解了。

你可能感兴趣的:(ML05-Ridge回归)