深入剖析回归(二)L1,L2正则项,梯度下降

时间:2024-03-18 16:34:27

一、回归问题的定义

回归是监督学习的一个重要问题,回归用于预测输入变量和输出变量之间的关系。回归模型是表示输入变量到输出变量之间映射的函数。回归问题的学习等价于函数拟合:使用一条函数曲线使其很好的拟合已知函数且很好的预测未知数据。
回归问题分为模型的学习和预测两个过程。基于给定的训练数据集构建一个模型,根据新的输入数据预测相应的输出。
回归问题按照输入变量的个数可以分为一元回归和多元回归;按照输入变量和输出变量之间关系的类型,可以分为线性回归和非线性回归。
 
一元回归:y = ax + b
深入剖析回归(二)L1,L2正则项,梯度下降
多元回归:
深入剖析回归(二)L1,L2正则项,梯度下降
深入剖析回归(二)L1,L2正则项,梯度下降

二、回归问题的求解

2.1解析解

2.1.1 最小二乘法

最小二乘法又称最小平方法,它通过最小化误差的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数据,并使得这些求得的数据与实际数据之间误差的平方和为最小。最小二乘法还可用于曲线拟合。

2.1.2利用极大似然估计解释最小二乘法

现在假设我们有m个样本,我们假设有:
深入剖析回归(二)L1,L2正则项,梯度下降
误差项是IID,根据中心极限定理,由于误差项是好多好多相互独立的因素影响的综合影响,我们有理由假设其服从高斯分布,又由于可以自己适配theta0,是的误差项的高斯分布均值为0,所以我们有
深入剖析回归(二)L1,L2正则项,梯度下降
所以我们有:
深入剖析回归(二)L1,L2正则项,梯度下降
也即:
深入剖析回归(二)L1,L2正则项,梯度下降
 表示在theta给定的时候,给我一个x,就给你一个y
那么我们可以写出似然函数:
深入剖析回归(二)L1,L2正则项,梯度下降
由极大似然估计的定义,我们需要L(theta)最大,那么我们怎么才能是的这个值最大呢?两边取对数对这个表达式进行化简如下:
深入剖析回归(二)L1,L2正则项,梯度下降
需要 l(theta)最大,也即最后一项的后半部分最小,也即:
目标函数(损失函数)
深入剖析回归(二)L1,L2正则项,梯度下降
所以,我们最后由极大似然估计推导得出,我们希望 J(theta) 最小,而这刚好就是最小二乘法做的工作。而回过头来我们发现,之所以最小二乘法有道理,是因为我们之前假设误差项服从高斯分布,假如我们假设它服从别的分布,那么最后的目标函数的形式也会相应变化。
好了,上边我们得到了有极大似然估计或者最小二乘法,我们的模型求解需要最小化目标函数J(theta),那么我们的theta到底怎么求解呢?有没有一个解析式可以表示theta?

2.1.3 theta的解析式的求解过程

深入剖析回归(二)L1,L2正则项,梯度下降
我们需要最小化目标函数,关心 theta 取什么值的时候,目标函数取得最小值,而目标函数连续,那么 theta 一定为 目标函数的驻点,所以我们求导寻找驻点。
求导可得:
深入剖析回归(二)L1,L2正则项,梯度下降
最终我们得到参数 theta 的解析式:
深入剖析回归(二)L1,L2正则项,梯度下降
 关于向量、矩阵求导知识参见http://www.cnblogs.com/futurehau/p/6105236.html
上述最后一步有一些问题,假如 X'X不可逆呢?
我们知道 X'X 是一个半正定矩阵,所以若X'X不可逆或为了防止过拟合,我们增加lambda扰动,得到
深入剖析回归(二)L1,L2正则项,梯度下降
从另一个角度来看,这相当与给我们的线性回归参数增加一个惩罚因子,这是必要的,我们数据是有干扰的,不正则的话有可能数据对于训练集拟合的特别好,但是对于新数据的预测误差很大。
深入剖析回归(二)L1,L2正则项,梯度下降

2.1.4正则化

L2-norm: (Ridge回归)
深入剖析回归(二)L1,L2正则项,梯度下降
L1-norm: (Lasso回归)
  深入剖析回归(二)L1,L2正则项,梯度下降
L1-norm 和 L2-norm都能防止过拟合,一般L2-norm的性能更好一些。L1-norm能够进行特选择对数据进行降维 产生稀疏模型,能够帮助我们去除某些特征,因此可以用于特征选择。
Elastic Net:
深入剖析回归(二)L1,L2正则项,梯度下降
L1-norm 和 L2-norm都能防止过拟合,一般L2-norm的效果更好一些。L1-norm能够产生稀疏模型,能够帮助我们去除某些特征,因此可以用于特征选择。
L1-norm 和 L2-norm的直观理解:摘自http://lib.csdn.net/article/machinelearning/42049
 

正则化(Regularization)

机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作1-norm2-norm,中文称作L1正则化L2正则化,或者L1范数L2范数
L1正则化和L2正则化可以看做是损失函数的惩罚项。所谓『惩罚』是指对损失函数中的某些参数做一些限制。对于线性回归模型,使用L1正则化的模型建叫做Lasso回归,使用L2正则化的模型叫做Ridge回归(岭回归)。下图是Python中Lasso回归的损失函数,式中加号后面一项α||w||1即为L1正则化项。
深入剖析回归(二)L1,L2正则项,梯度下降
下图是Python中Ridge回归的损失函数,式中加号后面一项α||w||22即为L2正则化项。
深入剖析回归(二)L1,L2正则项,梯度下降
一般回归分析中回归w表示特征的系数,从上式可以看到正则化项是对系数做了处理(限制)。L1正则化和L2正则化的说明如下:
  • L1正则化是指权值向量w中各个元素的绝对值之和,通常表示为||w||1
  • L2正则化是指权值向量w中各个元素的平方和然后再求平方根(可以看到Ridge回归的L2正则化项有平方符号),通常表示为||w||2
一般都会在正则化项之前添加一个系数,Python中用α表示,一些文章也用λ表示。这个系数需要用户指定。
那添加L1和L2正则化有什么用?下面是L1正则化和L2正则化的作用,这些表述可以在很多文章中找到。
  • L1正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择
  • L2正则化可以防止模型过拟合(overfitting);一定程度上,L1也可以防止过拟合

稀疏模型与特征选择

上面提到L1正则化有助于生成一个稀疏权值矩阵,进而可以用于特征选择。为什么要生成一个稀疏矩阵?
稀疏矩阵指的是很多元素为0,只有少数元素是非零值的矩阵,即得到的线性回归模型的大部分系数都是0. 通常机器学习中特征数量很多,例如文本处理时,如果将一个词组(term)作为一个特征,那么特征数量会达到上万个(bigram)。在预测或分类时,那么多特征显然难以选择,但是如果代入这些特征得到的模型是一个稀疏模型,表示只有少数特征对这个模型有贡献,绝大部分特征是没有贡献的,或者贡献微小(因为它们前面的系数是0或者是很小的值,即使去掉对模型也没有什么影响),此时我们就可以只关注系数是非零值的特征。这就是稀疏模型与特征选择的关系。

L1和L2正则化的直观理解

这部分内容将解释为什么L1正则化可以产生稀疏模型(L1是怎么让系数等于零的),以及为什么L2正则化可以防止过拟合

L1正则化和特征选择

假设有如下带L1正则化的损失函数: 
J=J0+αw|w|(1)

其中J0是原始的损失函数,加号后面的一项是L1正则化项,α是正则化系数。注意到L1正则化是权值的绝对值之和J是带有绝对值符号的函数,因此J是不完全可微的。机器学习的任务就是要通过一些方法(比如梯度下降)求出损失函数的最小值。当我们在原始损失函数J0后添加L1正则化项时,相当于对J0做了一个约束。令L=αw|w|,则J=J0+L,此时我们的任务变成L约束下求出J0取最小值的解。考虑二维的情况,即只有两个权值w1w2,此时L=|w1|+|w2|对于梯度下降法,求解J0的过程可以画出等值线,同时L1正则化的函数L也可以在w1w2的二维平面上画出来。如下图:
深入剖析回归(二)L1,L2正则项,梯度下降 
图1 L1正则化
图中等值线是J0的等值线,黑色方形是L函数的图形。在图中,当J0等值线与L图形首次相交的地方就是最优解。上图中J0LL的一个顶点处相交,这个顶点就是最优解。注意到这个顶点的值是(w1,w2)=(0,w)。可以直观想象,因为L函数有很多『突出的角』(二维情况下四个,多维情况下更多),J0与这些角接触的机率会远大于与L其它部位接触的机率,而在这些角上,会有很多权值等于0,这就是为什么L1正则化可以产生稀疏模型,进而可以用于特征选择。
而正则化前面的系数α,可以控制L图形的大小。α越小,L的图形越大(上图中的黑色方框);α越大,L的图形就越小,可以小到黑色方框只超出原点范围一点点,这是最优点的值(w1,w2)=(0,w)中的w可以取到很小的值。
类似,假设有如下带L2正则化的损失函数: 
J=J0+αww2(2)

同样可以画出他们在二维平面上的图形,如下:
深入剖析回归(二)L1,L2正则项,梯度下降 
图2 L2正则化
二维平面下L2正则化的函数图形是个圆,与方形相比,被磨去了棱角。因此J0L相交时使得w1w2等于零的机率小了许多,这就是为什么L2正则化不具有稀疏性的原因。

L2正则化和过拟合

拟合过程中通常都倾向于让权值尽可能小,最后构造一个所有参数都比较小的模型。因为一般认为参数值小的模型比较简单,能适应不同的数据集,也在一定程度上避免了过拟合现象。可以设想一下对于一个线性回归方程,若参数很大,那么只要数据偏移一点点,就会对结果造成很大的影响;但如果参数足够小,数据偏移得多一点也不会对结果造成什么影响,专业一点的说法是『抗扰动能力强』。
那为什么L2正则化可以获得值很小的参数?
以线性回归中的梯度下降法为例。假设要求的参数为θhθ(x)是我们的假设函数,那么线性回归的代价函数如下: 
J(θ)=12mi=1m(hθ(x(i))y(i))(3)

那么在梯度下降法中,最终用于迭代计算参数θ的迭代式为: 
θj:=θjα1mi=1m(hθ(x(i))y(i))x(i)j(4)

其中α是learning rate. 上式是没有添加L2正则化项的迭代公式,如果在原始代价函数之后添加L2正则化,则迭代公式会变成下面的样子: 
θj:=θj(1αλm)α1mi=1m(hθ(x(i))y(i))x(i)j(5)

其中λ就是正则化参数。从上式可以看到,与未添加L2正则化的迭代公式相比,每一次迭代,θj都要先乘以一个小于1的因子,从而使得θj不断减小,因此总得来看,θ是不断减小的。
最开始也提到L1正则化一定程度上也可以防止过拟合。之前做了解释,当L1的正则化系数很小时,得到的最优解会很小,可以达到和L2正则化类似的效果。

正则化参数的选择

L1正则化参数

通常越大的λ可以让代价函数在参数为0时取到最小值。下面是一个简单的例子,这个例子来自Quora上的问答。为了方便叙述,一些符号跟这篇帖子的符号保持一致。
假设有如下带L1正则化项的代价函数: 
F(x)=f(x)+λ||x||1

其中x是要估计的参数,相当于上文中提到的w以及θ. 注意到L1正则化在某些位置是不可导的,当λ足够大时可以使得F(x)x=0时取到最小值。如下图:
深入剖析回归(二)L1,L2正则项,梯度下降 
图3 L1正则化参数的选择
分别取λ=0.5λ=2,可以看到越大的λ越容易使F(x)x=0时取到最小值。

L2正则化参数

从公式5可以看到,λ越大,θj衰减得越快。另一个理解可以参考图2,λ越大,L2圆的半径越小,最后求得代价函数最值时各参数也会变得很小。

 2.2 梯度下降算法

我们在上边给出了最小二乘法求解线性回归的参数theta,实际python 的 numpy库就是使用的这种方法。
当然了,这需要我们的参数的维度不大,当维度大的时候,使用解析解就不适用了,这里讨论梯度下降算法。
深入剖析回归(二)L1,L2正则项,梯度下降

2.2.1梯度下降法步骤:

  初始化theta
  沿着负梯度方向迭代,更新后的theta使得J(theta)更小。
深入剖析回归(二)L1,L2正则项,梯度下降
  其中α:学习率 步长
  
梯度下系那个示意图如下:
深入剖析回归(二)L1,L2正则项,梯度下降
每次迭代找到一个更好的,最后得到一个局部最优解,不一定是全局最优,但是是堪用的。

2.2.2 具体实现

梯度方向:
深入剖析回归(二)L1,L2正则项,梯度下降
2.2.2.1 批量梯度下降算法:(BGD)
深入剖析回归(二)L1,L2正则项,梯度下降
由于在线性回归中,目标函数收敛而且为凸函数,是有一个极值点,所以局部最小值就是全局最小值。
深入剖析回归(二)L1,L2正则项,梯度下降
深入剖析回归(二)L1,L2正则项,梯度下降
2.2.2.2随机梯度下降算法:(SGD)
拿到一个样本就下降一次。实际中随机梯度下降算法其实是更适用的。出于一下亮点考虑:
1.由于噪声的存在,不能保证每个变量都让目标函数下降,但总的趋势是下降的。但是另一方面,由于噪声的存在,随机梯度下降算法往往有利于跳出局部最小值。
2.流式数据的处理
深入剖析回归(二)L1,L2正则项,梯度下降
2.2.2.3 mini-batch SGD(实际中都是简称为SGD)
拿到若干个样本的平均梯度之后在更新梯度方向。
深入剖析回归(二)L1,L2正则项,梯度下降深入剖析回归(二)L1,L2正则项,梯度下降
SGD随机梯度下降
如上图所示,一个批量梯度下降,一个随机梯度下降,最终效果其实是相近的。

今天先搞这么多吧,虽然是东拼西凑的,但是这些都是我自己理解过之后才去粘贴的
上个经典的干货:
#!/usr/bin/python
# -*- coding:utf-8 -*-

import numpy as np
from sklearn.linear_model import LinearRegression, RidgeCV, LassoCV, ElasticNetCV
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt
from sklearn.pipeline import Pipeline
from sklearn.exceptions import ConvergenceWarning
import matplotlib as mpl
import warnings

def xss(y, y_hat):
    y = y.ravel()
    y_hat = y_hat.ravel()
    # Version 1
    tss = ((y - np.average(y)) ** 2).sum()
    rss = ((y_hat - y) ** 2).sum()
    ess = ((y_hat - np.average(y)) ** 2).sum()
    r2 = 1 - rss / tss
    # print 'RSS:', rss, '\t ESS:', ess
    # print 'TSS:', tss, 'RSS + ESS = ', rss + ess
    tss_list.append(tss)
    rss_list.append(rss)
    ess_list.append(ess)
    ess_rss_list.append(rss + ess)
    # Version 2
    # tss = np.var(y)
    # rss = np.average((y_hat - y) ** 2)
    # r2 = 1 - rss / tss
    corr_coef = np.corrcoef(y, y_hat)[0, 1]
    return r2, corr_coef


if __name__ == "__main__":
    warnings.filterwarnings(action='ignore', category=ConvergenceWarning)
    np.random.seed(0)
    np.set_printoptions(linewidth=1000)
    N = 9
    x = np.linspace(0, 6, N) + np.random.randn(N)
    x = np.sort(x)
    y = x**2 - 4*x - 3 + np.random.randn(N)
    x.shape = -1, 1
    y.shape = -1, 1
    print("- - - - -")
    # 形如 1.00000000e+02  表示1.000*10^2 即1.00乘以10的2次幂
    print (np.logspace(-3, 2, 5))
    print(np.logspace(-2, 9, 5))
    print(np.logspace(0, 0, 5))
    print(np.logspace(0, 1, 5))
    print('- - - - - -')
    # 线性回归的目的是要得到输出向量Y和输入特征X之间的线性关系,求出线性回归系数θ,也就是Y=Xθ,
    # 其中Y的维度为mx1,X的维度为mxn,而θ的维度为nx1,m代表样本个数,n代表样本特征的维度

    # 损失函数:损失函数是用来评价模型的预测值f(x)与真实值Y的不一致程度,它是一个非负实值函数 通常用L(Y,f(x))表示,损失函数越小,模型的性能就越好
    # 正则化项:为了防止损失函数过拟合的问题,一般会在损失函数中加上正则化项,增加模型的泛化能力
    models = [Pipeline([
        ('poly', PolynomialFeatures()),
        # 损失函数:J(θ)=1/2(Xθ−Y)T(Xθ−Y) 优化方法:梯度下降和最小二乘法,scikit中采用最小二乘
        # 使用场景:只要数据线性相关,LinearRegression是我们的首选,如果发现拟合或者预测的不够好,再考虑其他的线性回归库
        ('linear', LinearRegression(fit_intercept=False))]),
        # Ridge回归(岭回归)损失函数的表达形式:J(θ)=1/2(Xθ−Y)T(Xθ−Y)+1/2α||θ||22(线性回归LineaRegression的损失函数+L2(2范式的正则化项))
        # a为超参数 alphas=np.logspace(-3, 2, 50) 从给定的超参数a中选择一个最优的,logspace用于创建等比数列 本例中 开始点为10的-3次幂,结束点10的2次幂,元素个数为
        # 50,并且从这50个数中选择一个最优的超参数
        # linspace创建等差数列
        # Ridge回归中超参数a和回归系数θ的关系,a越大,正则项惩罚的就越厉害,得到的回归系数θ就越小,最终趋近与0
        # 如果a越小,即正则化项越小,那么回归系数θ就越来越接近于普通的线性回归系数

        # 使用场景:只要数据线性相关,用LinearRegression拟合的不是很好,需要正则化,可以考虑使用RidgeCV回归,
        # 如何输入特征的维度很高,而且是稀疏线性关系的话, RidgeCV就不太合适,考虑使用Lasso回归类家族
        Pipeline([
            ('poly', PolynomialFeatures()),
            ('linear', RidgeCV(alphas=np.logspace(-3, 2, 50), fit_intercept=False))]),
        Pipeline([
            ('poly', PolynomialFeatures()),
            # 损失函数:J(θ)=1/2m(Xθ−Y)T(Xθ−Y)+α||θ||1 线性回归LineaRegression的损失函数+L1(1范式的正则化项))
            # Lasso回归可以使得一些特征的系数变小,甚至还使一些绝对值较小的系数直接变为0,从而增强模型的泛化能力
            # 使用场景:对于高纬的特征数据,尤其是线性关系是稀疏的,就采用Lasso回归,或者是要在一堆特征里面找出主要的特征,那么
            # Lasso回归更是首选了
            ('linear', LassoCV(alphas=np.logspace(-3, 2, 50), fit_intercept=False))]),
        Pipeline([
            ('poly', PolynomialFeatures()),
            # 损失函数:J(θ)=1/2m(Xθ−Y)T(Xθ−Y)+αρ||θ||1+α(1−ρ)/2||θ||22  其中α为正则化超参数,ρ为范数权重超参数
            # alphas=np.logspace(-3, 2, 50), l1_ratio=[.1, .5, .7, .9, .95, .99, 1] ElasticNetCV会从中选出最优的 a和p
            # ElasticNetCV类对超参数a和p使用交叉验证,帮助我们选择合适的a和p
            # 使用场景:ElasticNetCV类在我们发现用Lasso回归太过(太多特征被稀疏为0),而Ridge回归也正则化的不够(回归系数衰减太慢)的时候
            ('linear', ElasticNetCV(alphas=np.logspace(-3, 2, 50), l1_ratio=[.1, .5, .7, .9, .95, .99, 1],
                                    fit_intercept=False))])
    ]
    mpl.rcParams['font.sans-serif'] = [u'simHei']
    mpl.rcParams['axes.unicode_minus'] = False
    np.set_printoptions(suppress=True)

    plt.figure(figsize=(18, 12), facecolor='w')
    d_pool = np.arange(1, N, 1)  # 阶
    m = d_pool.size
    print("==================",m)
    clrs = []  # 颜色
    print(np.linspace(16711680, 255, m))
    for c in np.linspace(16711680, 255, m):
        clrs.append("#%06x" % int(c))

    line_width = np.linspace(5, 2, m)
    titles = '线性回归', u'Ridge回归', u'LASSO', u'ElasticNet'
    tss_list = []
    rss_list = []
    ess_list = []
    ess_rss_list = []
    for t in range(4):
        model = models[t]
        plt.subplot(2, 2, t+1)
        plt.plot(x, y, 'ro', ms=10, zorder=N)
        for i, d in enumerate(d_pool):
            model.set_params(poly__degree=d)
            model.fit(x, y.ravel())
            lin = model.get_params('linear')['linear']
            output = '%s:%d阶,系数为:' % (titles[t], d)
            if hasattr(lin, 'alpha_'):
                idx = output.find('系数')
                output = output[:idx] + ('alpha=%.6f,' % lin.alpha_) + output[idx:]
            if hasattr(lin, 'l1_ratio_'):   # 根据交叉验证结果,从输入l1_ratio(list)中选择的最优l1_ratio_(float)
                idx = output.find(u'系数')
                output = output[:idx] + (u'l1_ratio=%.6f,' % lin.l1_ratio_) + output[idx:]
            print (output, lin.coef_.ravel())
            x_hat = np.linspace(x.min(), x.max(), num=100)
            x_hat.shape = -1, 1
            y_hat = model.predict(x_hat)
            s = model.score(x, y)
            r2, corr_coef = xss(y, model.predict(x))
            # print 'R2和相关系数:', r2, corr_coef
            # print 'R2:', s, '\n'
            z = N - 1 if (d == 2) else 0
            label = '%d阶,$R^2$=%.3f' % (d, s)
            if hasattr(lin, 'l1_ratio_'):
                label += ',L1 ratio=%.2f' % lin.l1_ratio_
            plt.plot(x_hat, y_hat, color=clrs[i], lw=line_width[i], alpha=0.75, label=label, zorder=z)
        plt.legend(loc='upper left')
        plt.grid(True)
        plt.title(titles[t], fontsize=18)
        plt.xlabel('X', fontsize=16)
        plt.ylabel('Y', fontsize=16)
    plt.tight_layout(1, rect=(0, 0, 1, 0.95))
    plt.suptitle(u'多项式曲线拟合比较', fontsize=22)
    plt.show()

    y_max = max(max(tss_list), max(ess_rss_list)) * 1.05
    plt.figure(figsize=(9, 7), facecolor='w')
    t = np.arange(len(tss_list))
    plt.plot(t, tss_list, 'ro-', lw=2, label=u'TSS(Total Sum of Squares)')
    plt.plot(t, ess_list, 'mo-', lw=1, label=u'ESS(Explained Sum of Squares)')
    plt.plot(t, rss_list, 'bo-', lw=1, label=u'RSS(Residual Sum of Squares)')
    plt.plot(t, ess_rss_list, 'go-', lw=2, label=u'ESS+RSS')
    plt.ylim((0, y_max))
    plt.legend(loc='center right')
    plt.xlabel(u'实验:线性回归/Ridge/LASSO/Elastic Net', fontsize=15)
    plt.ylabel(u'XSS值', fontsize=15)
    plt.title(u'总平方和TSS=?', fontsize=18)
    plt.grid(True)
    plt.show()
加点小东西:
深入剖析回归(二)L1,L2正则项,梯度下降