MADlib——基于SQL的数据挖掘解决方案(16)——回归之弹性网络回归

2019-05-25 19:36:17 浏览数 (1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1433071

一、弹性网络回归简介

代码语言:txt复制
    要想理解弹性网络(Elastic Net)回归,正则化是必须要首先知道的,其次是岭回归和Lasso回归,知道了这些,弹性网络回归自然也就明白了。

1. 正则化

代码语言:txt复制
    假设利用最小二乘法来做线性回归,最小二乘法回归成功的条件是:
代码语言:txt复制
    即上面这个函数(损失函数)达到最小值,可得到最优的拟合参数θ。但是存在这样一种情况,如果我们用来拟合的自变量(特征变量)过多,而且特征变量之间存在很高的相关关系,比如下面这种情况:
代码语言:txt复制
    以上两个函数都可以很好的拟合数据,但右边的函数显然有过拟合的嫌疑。为了避免这种情况,有两种方法,第一种方法是舍掉 

这两个变量,这就是所谓的特征选择,舍弃无用的特征变量。可以人工选择,也可以利用算法来做。但有些时候我们可能并不希望舍弃数据,一方面特征选择有一定的不确定性,另一方面这个过程是比较繁琐的,这种时候我们可以采用第二种方法来解决这一问题:减小

的值,即正则化,保留所有特征变量,但减少变量参数的值。例如,要减小

的值,我们可以在损失函数的后面加上

代码语言:txt复制
    如此一来在最小化目标函数时,因为在 

前面乘了1000这样大的数字,导致

的值会非常的小,目标达成。这里我们有选择的让

的值变小,实际情况中,我们很难判断哪些特征变量需要正则化,所以一般情况下,我们是对所有的参数都正则化处理:

即目标函数设为

,其中:

是正则项,lambda(λ)为正则参数。需要注意的是,j是从1开始的,这意味着函数的常数项(

)并没有被正则化。所以lambda不能设的太大,否则会导致除了常数项外,所有的参数值都很小,因变量近似等于常数项,出现欠拟合现象。

2. 岭回归与Lasso回归

代码语言:txt复制
    岭回归(ridge regression)的目标函数就是上面介绍的 

。如果矩阵化的话,也写成:

即最小化损失函数

  • 惩罚函数

。通过修改λ的值控制惩罚项,λ值越高,惩罚越强,因此系数量级降低。

代码语言:txt复制
    Lasso回归和岭回归的区别在于惩罚项的不同:
代码语言:txt复制
    Lasso回归的惩罚项用的是绝对值(也称为L1正则化),而不是岭回归中的平方(L2正则化)。L2正则化并不具有产生稀疏解的能力,得到的系数仍然需要数据中的所有特征才能计算预测结果,从计算量上来说并没有得到改观。L1正则化能产生稀疏性,稀疏的解除了计算量上的好处之外,更重要的是更具有“可解释性”。
代码语言:txt复制
    假设有一个大的数据集,有10000个特征,其中一些特征是相关的。如果我们使用岭回归,它会保留所有特征,系数会收缩。但问题是模型同样复杂,因为还是有10000个特征,这会导致很差的模型性能。若使用Lasso回归,当我们有相关联的变量,它只会保留一个变量,将其它相关联的变量系数设置为0。这可能会导致一些信息的丢失,结果是模型精确度降低。
代码语言:txt复制
    实际上,弹性网络回归本质上就是岭回归和Lasso回归的组合,其目标函数为:
代码语言:txt复制
    可以通过调整 控制L1和L2惩罚项。弹性网络是一种使用L1和L2先验作为正则化矩阵的线性回归模型。这种组合用于只有很少的权重非零的稀疏模型,比如Lasso回归, 但是又能保持岭回归的正则化属性。
代码语言:txt复制
    当多个特征和另一个特征相关的时候弹性网络非常有用。Lasso 倾向于随机选择其中一个,而弹性网络更倾向于选择两个。在实践中,Lasso 和 Ridge 之间权衡的一个优势是它允许在循环过程(Under rotate)中继承 Ridge 的稳定性。

二、MADlib的弹性网络回归相关函数

1. 训练函数

(1) 语法

代码语言:javascript复制
elastic_net_train( tbl_source,  
                   tbl_result,  
                   col_dep_var,  
                   col_ind_var,  
                   regress_family,  
                   alpha,  
                   lambda_value,  
                   standardize,  
                   grouping_col,  
                   optimizer,  
                   optimizer_params,  
                   excluded,  
                   max_iter,  
                   tolerance  
                 )

(2) 参数

参数名称

数据类型

描述

tbl_source

TEXT

包含训练数据的源表名。

tbl_result

TEXT

包含模型的输出表名,输出表列如表2所示。

col_dep_var

TEXT

因变量表达式。col_dep_var和col_ind_var可以是有效的PostgreSQL表达式。例如,col_dep_var = 'log(y 1)'、 col_ind_var = 'array[exp(x1), x2, 1/(1 x3)]'。在二项回归情况下,可以使用布尔表达式,如col_dep_var = 'y < 0'。

col_ind_var

TEXT

自变量表达式。使用‘*’指定tbl_source中除以下描述中的列以外的所有列。如果col_dep_var是列名,它自动排除在自变量之外。如果col_dep_var是一个PostgreSQL表达式,表达式中用到的列名,只有显式出现在excluded参数中才被排除。因此,比较好的做法是将因变量表达式中所含的列名都添加到excluded参数的字符串中。

regress_family

TEXT

指定回归类型,有效值为‘gaussian’(‘linear’)线性回归或‘binomial’(‘logistic’)二项回归。

alpha

FLOAT8

弹性网络控制参数,取值范围是0, 1。1表示L1正则化,0表示L2正则化。该参数是超参,其值并不是自动从模型中学习得到而是手动设置。

lambda_value

FLOAT8

指定正则化参数,必须是正数。该参数也是超参数。

standardize(可选)

BOOLEAN

指定是否规范化数据,缺省值为TRUE。设置为TRUE通常能产生更好的结果和更快的收敛速度。

grouping_col(可选)

TEXT

缺省值为NULL。可以指定单列或逗号分隔的多列,将输入数据按列划分为离散组,每组执行一个回归。缺省不使用分组,全部数据生成单一模型。该参数当前不支持表达式。

optimizer(可选)

TEXT

优化器名称,可指定为‘fista’或‘igd’,缺省为‘fista’。

optimizer_params(可选)

TEXT

指定逗号分隔的优化参数,缺省为NULL。这些参数根据优化器参数的值而有所不同,后面将详细描述。

excluded(可选)

TEXT

逗号分隔的列名,缺省值为NULL。当col_ind_var入参为‘*’时,可从特征列中排除此参数中的列。

max_iter(可选)

INTEGER

缺省值为1000,指定允许的最大迭代次数。

tolerance

FLOAT8

缺省值为1e-6,指定停止迭代的条件。无论是‘fista’还是‘igd’优化器,如果两次连续迭代的对数似然值之差小于此参数值,或者迭代次数超过max_iter参数的限制,计算结束。

表1 elastic_net_train函数参数说明

列名

数据类型

描述

family

TEXT

回归类型,‘gaussian’或‘binomial’。

features

TEXT[]

传给算法的特征(自变量)数组。

features_selected

TEXT[]

算法选择的特征数组。

coef_nonzero

FLOAT8[]

选定特征的回归系数。

coef_all

FLOAT8[]

所有特征的回归系数。

intercept

FLOAT8

模型截距。所有自变量为0时,因变量的值。

log_likelihood

FLOAT8

算法产生的对数似然值。

standardize

BOOLEAN

如果数据已被规范化,将被设置为TRUE。

iteration_run

INTEGER

执行的迭代次数。

表2 elastic_net_train函数输出表列说明

2. 优化参数

代码语言:txt复制
    前面提到的optimizer_params参数可分为预热、交叉验证和优化三类。该参数值为逗号分隔的名值对字符串,字符串用$$括起来,其中所有命名参数都是可选的,并且具有“<param_name>= <value>”格式。

(1) 预热参数

代码语言:javascript复制
$$  
  warmup = <value>,  
  warmup_lambdas = <value>,  
  warmup_lambda_no = <value>,  
  warmup_tolerance = <value>  
$$
  • warmup:缺省值为FALSE。如果warmup设置为TRUE,使用一系列严格递减的lambda值,以用户希望计算的lambda值的结束。大的lambda值了给出一个稀疏解决方案,作为下一轮lambda解决方案的初始猜测。对于大数据集,这有时会加速整个计算过程,并且实际上可能比只计算单一lambda值还快。
  • warmup_lambdas:缺省为NULL。当warmup为TRUE时,设置lambda值。此参数为NULL时,lambda值将自动生成。
  • warmup_lambda_no:缺省值是15,指定预热使用的lambda值个数。如果warmup_lambdas为NULL,此参数值将被自动生成的lambda值的数目覆盖。
  • warmup_tolerance:预热时使用的容忍值,缺省与训练函数的tolerance参数相同。

(2) 交叉验证参数

代码语言:txt复制
    出于性能考虑,如果使用交叉验证,则预热被禁用。而且交叉验证不支持分组。
代码语言:javascript复制
$$  
  n_folds = <value>,  
  validation_result = <value>,  
  lambda_value = <value>,  
  n_lambdas = <value>,  
  alpha = <value>  
$$
代码语言:txt复制
    超参数(lambda和alpha)优化可以使用内置的交叉验证进行,这通过给n_folds参数分配一个大于1的整数来激活。交叉验证参数应该提供一个列表。例如,为了用L1范数进行正则化,并且使用集合{0.3, 0.4, 0.5}中的lambda值,则参数中应包含'lambda_value={0.3, 0.4,0.5}',注意这里‘{}’与‘[]’符合都是有效的。
  • n_folds:缺省值为0,指定折数(k)。为激活交叉验证,该值应大于1。如果该值大于2,每折用作一次验证集,而其它K - 1折形成训练集。
  • validation_result:缺省值为NULL,指定存储交叉验证结果的表名。只有此参数值非NULL时才会创建结果表,结果包括参数值与相应的平均误差。
  • lambda_value:缺省值为NULL,设置用于交叉验证的lambda值。缺省为NULL时,lambda值将自动生成。
  • n_lambdas:缺省值是15。交叉验证使用的lambda值个数。如果lambda_value参数中没有给出lambda值,会自动生成此参数给出的lambda集合。如果lambda_value参数值非空,此参数被提供的lambda值个数所覆盖。注意,如果只想交叉验证alpha而不是lambda,那么设置lambda_value为NULL,并且设置n_lambdas等于0。此时在下一个参数中指定的alpha值的集合上做交叉验证,所使用的lambda值将是训练函数调用中指定的值。
  • alpha:弹性网络控制参数,是提供给交叉验证的一个值列表。注意alpha值不会自动生成,如果没有指定,所使用的alpha值将是训练函数调用中指定的值。

(3) 优化参数

代码语言:txt复制
    FISTA优化器参数
代码语言:javascript复制
$$  
  max_stepsize = <value>,  
  eta = <value>,  
  use_active_set = <value>,  
  activeset_tolerance = <value>,  
  random_stepsize = <value>  
$$ 
  • max_stepsize:缺省值是4.0,指定初始回溯步长。在每次迭代中,算法首先尝试步长 = max_stepsize,如果它不起作用,则尝试小一些的步长,步长 = 步长/eta,其中eta必须大于1。使用大步长会显著加快计算速度,并使总的迭代次数最小化。一个仔细选择的步长可能使计算时间减少10倍以上。
  • eta:缺省值为2.0,必须大于1。
  • use_active_set:缺省值为FALSE,如果设置为TRUE,使用有效集法(active-set method)加快计算速度。通过围绕特征有效集(非0回归系数)进行迭代,将获得相当大的加速提升。经过一个完整的循环处理所有变量后,只迭代有效集直到收敛。如果下一个循环不改变有效集迭代结束,否则重复此过程。
  • activeset_tolerance:计算有效集时使用的容忍值,缺省与训练函数的tolerance参数值相同。
  • random_stepsize:缺省为FALSE,是否给步长增加一些随机性,有时这可以加速计算。
代码语言:txt复制
    IGD优化器参数
代码语言:javascript复制
$$  
    stepsize = <value>,  
    step_decay = <value>,  
    threshold = <value>,  
    parallel = <value>  
$$
  • stepsize:步长,缺省0.01。
  • step_decay:当前实际使用的步长是(上次步长)/exp(step_decay)。缺省值为0,意味着此时IGD中使用的步长为个常量。
  • threshold:缺省为1e-10。当系数非常小时,可以设置此参数为0。由于SGD的随机性,对于拟合系数,我们只能得到非常小的值。因此计算结束时需要阈值来筛选极小值,并把它们强制设为零。按下面的步骤实现:(1)将每个系数乘以相应特征的标准差;(2)计算重定系数绝对值的平均值;(3)用平均值除以每个重定系数,如果结果的绝对值小于threshold参数值,设置原始系数为0。
  • parallel:指定是否在多个段上执行并行计算,缺省为TRUE。

3. 预测函数

(1) 元组预测

代码语言:txt复制
    此预测函数对线性回归返回FLOAT类型的值,对二项回归返回布尔值。预测函数(elastic_net_gaussian_predict()和elastic_net_binomial_predict())语法如下:
代码语言:javascript复制
elastic_net_<family>_predict(  
                     coefficients,  
                     intercept,  
                     ind_var  
                   ) 
代码语言:txt复制
    参数:
  • coefficients:FLOAT8[]类型,拟合系数,通常指定为模型表的coef_all或coef_nonzero列。
  • intercept:FLOAT8[]类型,模型截距。
  • ind_var:FLOAT8[]类型,系数对应的自变量。对于coef_all,使用模型结果表中的features列。对于coef_nonzero使用模型结果表中的features_selected中的列。
代码语言:txt复制
    对于二项回归,elastic_net_binomial_prob()函数输出实例为真的概率:
代码语言:javascript复制
elastic_net_binomial_prob(  
                     coefficients,  
                     intercept,  
                     ind_var  
                   )  

(2) 表预测

代码语言:txt复制
    也可以使用另一个预测函数,将预测结果保存在一个表中,这在将弹性网络与普通交叉验证函数一同使用时很有用。语法如下:
代码语言:javascript复制
elastic_net_predict( tbl_model,  
                     tbl_new_sourcedata,  
                     col_id,  
                     tbl_predict  
                   )  
代码语言:txt复制
    参数:
  • tbl_model:TEXT类型,包含模型的训练函数输出表名。
  • tbl_new_sourcedata:TEXT类型,包含测试数据的表名。
  • col_id:TEXT类型,行ID列。
  • tbl_predict:TEXT类型,存储预测结果的表名。
代码语言:txt复制
    这里不需要指定“linear”或“logistic”回归类型,因为模型中已经包含此信息。

三、简单示例

1. 获取联机帮助

代码语言:javascript复制
select madlib.elastic_net_train();

2. 创建测试表并生成数据

代码语言:javascript复制
-- 房屋价格表  
drop table if exists houses;  
create table houses ( id int,           -- 逻辑主键  
                      tax int,          -- 税金  
                      bedroom int,      -- 卧室数  
                      bath float,       -- 卫生间数  
                      price int,        -- 价格  
                      size int,         -- 使用面积  
                      lot int,          -- 占地面积  
                      zipcode int       -- 邮编  
                      );  
insert into houses (id, tax, bedroom, bath, price, size, lot, zipcode) values  
(1  ,  590 ,       2 ,    1 ,  50000 ,  770 , 22100  , 94301),  
(2  , 1050 ,       3 ,    2 ,  85000 , 1410 , 12000  , 94301),  
(3  ,   20 ,       3 ,    1 ,  22500 , 1060 ,  3500  , 94301),  
(4  ,  870 ,       2 ,    2 ,  90000 , 1300 , 17500  , 94301),  
(5  , 1320 ,       3 ,    2 , 133000 , 1500 , 30000  , 94301),  
(6  , 1350 ,       2 ,    1 ,  90500 ,  820 , 25700  , 94301),  
(7  , 2790 ,       3 ,  2.5 , 260000 , 2130 , 25000  , 94301),  
(8  ,  680 ,       2 ,    1 , 142500 , 1170 , 22000  , 94301),  
(9  , 1840 ,       3 ,    2 , 160000 , 1500 , 19000  , 94301),  
(10 , 3680 ,       4 ,    2 , 240000 , 2790 , 20000  , 94301),  
(11 , 1660 ,       3 ,    1 ,  87000 , 1030 , 17500  , 94301),  
(12 , 1620 ,       3 ,    2 , 118600 , 1250 , 20000  , 94301),  
(13 , 3100 ,       3 ,    2 , 140000 , 1760 , 38000  , 94301),  
(14 , 2070 ,       2 ,    3 , 148000 , 1550 , 14000  , 94301),  
(15 ,  650 ,       3 ,  1.5 ,  65000 , 1450 , 12000  , 94301),  
(16 ,  770 ,       2 ,    2 ,  91000 , 1300 , 17500  , 76010),  
(17 , 1220 ,       3 ,    2 , 132300 , 1500 , 30000  , 76010),  
(18 , 1150 ,       2 ,    1 ,  91100 ,  820 , 25700  , 76010),  
(19 , 2690 ,       3 ,  2.5 , 260011 , 2130 , 25000  , 76010),  
(20 ,  780 ,       2 ,    1 , 141800 , 1170 , 22000  , 76010),  
(21 , 1910 ,       3 ,    2 , 160900 , 1500 , 19000  , 76010),  
(22 , 3600 ,       4 ,    2 , 239000 , 2790 , 20000  , 76010),  
(23 , 1600 ,       3 ,    1 ,  81010 , 1030 , 17500  , 76010),  
(24 , 1590 ,       3 ,    2 , 117910 , 1250 , 20000  , 76010),  
(25 , 3200 ,       3 ,    2 , 141100 , 1760 , 38000  , 76010),  
(26 , 2270 ,       2 ,    3 , 148011 , 1550 , 14000  , 76010),  
(27 ,  750 ,       3 ,  1.5 ,  66000 , 1450 , 12000  , 76010);  

3. 训练模型

代码语言:txt复制
    本次培训lambda值指定为1,alpha为0.5,实际并没有抑制过渡拟合。
代码语言:javascript复制
drop table if exists houses_en, houses_en_summary;  
select madlib.elastic_net_train( 'houses',                  -- 源表  
                                 'houses_en',               -- 结果表  
                                 'price',                   -- 因变量  
                                 'array[tax, bath, size]',  -- 自变量  
                                 'gaussian',                -- 回归类型  
                                 0.5,                       -- alpha值  
                                 1,                         -- lambda值  
                                 true,                      -- 标准化数据  
                                 null,                      -- 分组列  
                                 'fista',                   -- 优化器  
                                 '',                        -- 优化参数  
                                 null,                      -- 排除列  
                                 1000,                      -- 最大迭代次数  
                                 1e-6                       -- 容忍值  
                               );  

4. 查看结果模型

代码语言:javascript复制
x on  
select * from houses_en;
代码语言:txt复制
    结果:
代码语言:javascript复制
-[ RECORD 1 ]----- --------------------------------------------  
family            | gaussian  
features          | {tax,bath,size}  
features_selected | {tax,bath,size}  
coef_nonzero      | {19.1307716652,14331.3082101,40.4478161865}  
coef_all          | {19.1307716652,14331.3082101,40.4478161865}  
intercept         | 12944.5112825  
log_likelihood    | -741004029.774  
standardize       | t  
iteration_run     | 263  
代码语言:txt复制
    迭代了263次,函数收敛。

5. 使用预测函数评估残差

代码语言:javascript复制
x off  
select id, price, predict, price - predict as residual, round(abs((price - predict) / price * 100)::numeric,4)  as residual_pct  
from (  
    select  
        houses.*,  
        madlib.elastic_net_gaussian_predict(  
            m.coef_all,             -- 全部拟合系数  
            m.intercept,            -- 模型截距  
            array[tax,bath,size]    -- 系数对应的特征列  
            ) as predict  
    from houses, houses_en m) s  
order by id; 
代码语言:txt复制
    结果:
代码语言:javascript复制
 id | price  |     predict      |     residual     | residual_pct 
---- -------- ------------------ ------------------ --------------
  1 |  50000 |  69707.793238673 | -19707.793238673 |      39.4156
  2 |  85000 | 118725.858774125 | -33725.858774125 |      39.6775
  3 |  22500 |  70533.120083594 | -48033.120083594 |     213.4805
  4 |  90000 | 110833.060093874 | -20833.060093874 |      23.1478
  5 | 133000 | 127531.470580514 |   5468.529419486 |       4.1117
  6 |  90500 |   86269.57051355 |    4230.42948645 |       4.6745
  7 | 260000 | 188301.483230903 |  71698.516769097 |      27.5764
  8 | 142500 |  87608.689163141 |  54891.310836859 |      38.5202
  9 | 160000 | 137479.471846418 |  22520.528153582 |      14.0753
 10 | 240000 | 224857.774590971 |  15142.225409029 |       6.3093
 11 |  87000 | 100694.151128927 | -13694.151128927 |      15.7404
 12 | 118600 | 123158.748033449 |  -4558.748033449 |       3.8438
 13 | 140000 |  172100.67635306 |  -32100.67635306 |      22.9291
 14 | 148000 | 158233.248348839 | -10233.248348839 |       6.9144
 15 |  65000 | 105525.808650455 | -40525.808650455 |      62.3474
 16 |  91000 | 108919.982927354 | -17919.982927354 |      19.6923
 17 | 132300 | 125618.393413994 |   6681.606586006 |       5.0503
 18 |  91100 |   82443.41618051 |    8656.58381949 |       9.5023
 19 | 260011 | 186388.406064383 |  73622.593935617 |      28.3152
 20 | 141800 |  89521.766329661 |  52278.233670339 |      36.8676
 21 | 160900 | 138818.625862982 |  22081.374137018 |      13.7237
 22 | 239000 | 223327.312857755 |  15672.687142245 |       6.5576
 23 |  81010 |  99546.304829015 | -18536.304829015 |      22.8815
 24 | 117910 | 122584.824883493 |  -4674.824883493 |       3.9647
 25 | 141100 |  174013.75351958 |  -32913.75351958 |      23.3265
 26 | 148011 | 162059.402681879 | -14048.402681879 |       9.4915
 27 |  66000 | 107438.885816975 | -41438.885816975 |      62.7862
(27 rows)
代码语言:txt复制
    可以看到,预测与实际值之差还是比较大的。主要原因是我们没有大的特征集。弹性回归一般在有大数据集的时候工作得很好。

四、分组示例

1. 按邮编分组训练模型

代码语言:txt复制
    本次训练除了增加邮编分组列,其它参数与前一次训练相同。
代码语言:javascript复制
drop table if exists houses_en1, houses_en1_summary;  
select madlib.elastic_net_train( 'houses',                  -- 源表  
                                 'houses_en1',              -- 结果表  
                                 'price',                   -- 因变量  
                                 'array[tax, bath, size]',  -- 自变量  
                                 'gaussian',                -- 回归类型  
                                 0.5,                       -- alpha值  
                                 1,                         -- lambda值  
                                 true,                      -- 标准化数据  
                                 'zipcode',                 -- 分组列  
                                 'fista',                   -- 优化器  
                                 '',                        -- 优化参数  
                                 null,                      -- 排除列  
                                 1000,                      -- 最大迭代次数  
                                 1e-6                       -- 容忍值  
                               );  

2. 查看每个分组的模型

代码语言:javascript复制
x on  
select * from houses_en1; 
代码语言:txt复制
    结果:
代码语言:javascript复制
-[ RECORD 1 ]----- --------------------------------------------  
zipcode           | 94301  
family            | gaussian  
features          | {tax,bath,size}  
features_selected | {tax,bath,size}  
coef_nonzero      | {18.3256413727,15278.5402561,33.2539221347}  
coef_all          | {18.3256413727,15278.5402561,33.2539221347}  
intercept         | 23185.4057684  
log_likelihood    | -758812991.622  
standardize       | t  
iteration_run     | 1000  
-[ RECORD 2 ]----- --------------------------------------------  
zipcode           | 76010  
family            | gaussian  
features          | {tax,bath,size}  
features_selected | {tax,bath,size}  
coef_nonzero      | {12.715063895,11840.1947579,31.8858681578}  
coef_all          | {12.715063895,11840.1947579,31.8858681578}  
intercept         | 40625.1513799  
log_likelihood    | -830719826.169  
standardize       | t  
iteration_run     | 1000  
代码语言:txt复制
    每组生成一个模型,两组的迭代次数都达到1000。

3. 预测残差

代码语言:javascript复制
x off  
drop table if exists houses_en1_prediction;  
select madlib.elastic_net_predict(  
                'houses_en1',             -- 模型表  
                'houses',                 -- 数据表  
                'id',                     -- ID列  
                'houses_en1_prediction'   -- 预测结果表  
              );  
  
select  houses.id,  
        houses.price,  
        houses_en1_prediction.prediction,  
        houses.price - houses_en1_prediction.prediction as residual,  
        round(abs((houses.price - houses_en1_prediction.prediction)/houses.price*100)::numeric,4) as residual_pct  
from houses_en1_prediction, houses  
where houses.id = houses_en1_prediction.id order by id;  
代码语言:txt复制
    结果:
代码语言:javascript复制
 id | price  |    prediction    |     residual      | residual_pct 
---- -------- ------------------ ------------------- --------------
  1 |  50000 |  74881.594478112 |  -24881.594478112 |      49.7632
  2 |  85000 | 119872.439931862 |  -34872.439931862 |      41.0264
  3 |  22500 |  74079.616314736 |  -51579.616314736 |     229.2427
  4 |  90000 | 112915.893049959 |  -22915.893049959 |      25.4621
  5 | 133000 | 127813.216094614 |  5186.78390538601 |       3.8998
  6 |  90500 |  90471.778028099 |  28.2219719010027 |       0.0312
  7 | 260000 | 183341.149985394 |   76658.850014606 |      29.4842
  8 | 142500 |  89832.471055535 |   52667.528944465 |      36.9597
  9 | 160000 | 137342.549608418 |   22657.450391582 |      14.1609
 10 | 240000 | 213959.289287949 |   26040.710712051 |      10.8503
 11 |  87000 | 103136.050501923 |  -16136.050501923 |      18.5472
 12 | 118600 | 124997.427972749 | -6397.42797274899 |       5.3941
 13 | 140000 | 169078.877493042 |  -29078.877493042 |      20.7706
 14 | 148000 | 158498.683486974 |  -10498.683486974 |       7.0937
 15 |  65000 |  106233.07014012 |   -41233.07014012 |      63.4355
 16 |  91000 |  115547.76869999 |   -24547.76869999 |      26.9756
 17 | 132300 |   127646.7210843 |  4653.27891569999 |       3.5172
 18 |  91100 |  93234.081506446 |   -2134.081506446 |       2.3426
 19 | 260011 | 172346.059328314 |   87664.940671686 |      33.7159
 20 | 141800 |  99689.561720526 |   42110.438279474 |      29.6971
 21 | 160900 |  136420.11517185 |    24479.88482815 |      15.2143
 22 | 239000 | 199041.343077962 |   39958.656922038 |      16.7191
 23 |  81010 | 105651.892572334 |  -24641.892572334 |      30.4183
 24 | 117910 |    124379.827686 |      -6469.827686 |       5.4871
 25 | 141100 | 161112.873317428 |  -20012.873317428 |      14.1835
 26 | 148011 |  154432.02633984 |    -6421.02633984 |       4.3382
 27 |  66000 |  114156.25026681 |   -48156.25026681 |      72.9640
(27 rows)
代码语言:txt复制
    本次的预测结果同样较大。

五、比较coef_nonzero与coef_all

1. 用L1正则化和大的lambda值(30000)预测模型

代码语言:javascript复制
drop table if exists houses_en2, houses_en2_summary;  
select madlib.elastic_net_train( 'houses',                  -- 源表  
                                 'houses_en2',              -- 结果表  
                                 'price',                   -- 因变量  
                                 'array[tax, bath, size]',  -- 自变量  
                                 'gaussian',                -- 回归类型  
                                 1,                         -- alpha值  
                                 30000,                     -- lambda值  
                                 true,                      -- 标准化数据  
                                 null,                      -- 分组列  
                                 'fista',                   -- 优化器  
                                 '',                        -- 优化参数  
                                 null,                      -- 排除列  
                                 10000,                     -- 最大迭代次数  
                                 1e-6                       -- 容忍值  
                               ); 

2. 查看结果模型

代码语言:javascript复制
x on  
select * from houses_en2;
代码语言:txt复制
     结果:
代码语言:javascript复制
-[ RECORD 1 ]----- --------------------------------  
family            | gaussian  
features          | {tax,bath,size}  
features_selected | {tax,size}  
coef_nonzero      | {6.94744249834,29.7137297658}  
coef_all          | {6.94744249834,0,29.7137297658}  
intercept         | 74445.7039382  
log_likelihood    | -1635348585.07  
standardize       | t  
iteration_run     | 151
代码语言:txt复制
    可以看到,这次只迭代了151次。与前面的模型不同,由于lambda值足够大,features_selected中少了bath列,因为其系数已为0。

3. 预测残差

代码语言:javascript复制
x off  
select id, price, predict, price - predict as residual, round(abs((price - predict) / price * 100)::numeric,4)  as residual_pct  
from (  
    select  
        houses.*,  
        madlib.elastic_net_gaussian_predict(  
            m.coef_all,                   -- 全部拟合系数  
            m.intercept,                  -- 模型截距  
            array[tax,bath,size]          -- 特征列  
            ) as predict  
    from houses, houses_en2 m) s  
order by id; 

4. 用coef_nonzero加速预测

代码语言:txt复制
    可以用coef_nonzero加速预测函数评估残差。这需要检查模型结果表的feature_selected列,为预测函数提供正确的自变量集合。
代码语言:javascript复制
x off  
select id, price, predict, price - predict as residual, round(abs((price - predict) / price * 100)::numeric,4)  as residual_pct  
from (  
    select  
        houses.*,  
        madlib.elastic_net_gaussian_predict(  
            m.coef_nonzero,               -- 非0系数  
            m.intercept,                  -- 模型方差  
            array[tax,size]               -- 对应特征列  
            ) as predict  
    from houses, houses_en2 m) s  
order by id;  
代码语言:txt复制
    结果:
代码语言:javascript复制
 id | price  |     predict      |     residual      | residual_pct 
---- -------- ------------------ ------------------- --------------
  1 |  50000 | 101424.266931887 | -51424.2669318866 |     102.8485
  2 |  85000 | 123636.877531235 |  -38636.877531235 |      45.4552
  3 |  22500 | 106081.206339915 | -83581.2063399148 |     371.4720
  4 |  90000 | 119117.827607296 | -29117.8276072958 |      32.3531
  5 | 133000 | 128186.922684709 |   4813.0773152912 |       3.6189
  6 |  90500 | 108190.009718915 |  -17690.009718915 |      19.5470
  7 | 260000 | 157119.312909723 |  102880.687090277 |      39.5695
  8 | 142500 | 113935.028663057 |  28564.9713369428 |      20.0456
  9 | 160000 | 131799.592783846 |  28200.4072161544 |      17.6253
 10 | 240000 | 182913.598378673 |  57086.4016213268 |      23.7860
 11 |  87000 | 116583.600144218 | -29583.6001442184 |      34.0041
 12 | 118600 | 122842.722992761 |  -4242.7229927608 |       3.5773
 13 | 140000 | 148278.940070862 | -8278.94007086198 |       5.9135
 14 | 148000 | 134883.191046754 |  13116.8089532462 |       8.8627
 15 |  65000 | 122046.449722531 |  -57046.449722531 |      87.7638
 16 |  91000 | 118423.083357462 | -27423.0833574618 |      30.1353
 17 | 132300 | 127492.178434875 |  4807.82156512521 |       3.6340
 18 |  91100 | 106800.521219247 |  -15700.521219247 |      17.2344
 19 | 260011 | 156424.568659889 |  103586.431340111 |      39.8392
 20 | 141800 | 114629.772912891 |  27170.2270871088 |      19.1609
 21 | 160900 | 132285.913758729 |  28614.0862412706 |      17.7838
 22 | 239000 | 182357.802978806 |   56642.197021194 |      23.6997
 23 |  81010 | 116166.753594318 |  -35156.753594318 |      43.3980
 24 | 117910 | 122634.299717811 | -4724.29971781059 |       4.0067
 25 | 141100 | 148973.684320696 | -7873.68432069599 |       5.5802
 26 | 148011 | 136272.679546422 |  11738.3204535782 |       7.9307
 27 |  66000 | 122741.193972365 |  -56741.193972365 |      85.9715
(27 rows)
代码语言:txt复制
    虽然这次只用了两个自变量进行预测,但与前面一个查询的结果相同。同时可以看到,虽然结果模型少了一个特征,但预测误差比lambda=1时更大了,说明可能出现了拟合不足的情况。

六、交叉验证示例

1. 训练时使用交叉验证

代码语言:txt复制
    为了找出最佳的lambda值,这次使用3折交叉验证,自动生成lambda。这个训练函数将执行较长时间,因为弹性网络被调用15次。
代码语言:javascript复制
x on  
drop table if exists houses_en3, houses_en3_summary, houses_en3_cv;  
select madlib.elastic_net_train( 'houses',                  -- 源表  
                                 'houses_en3',              -- 结果表  
                                 'price',                   -- 因变量  
                                 'array[tax, bath, size]',  -- 自变量  
                                 'gaussian',                -- 回归类型  
                                 1,                         -- alpha值  
                                 1,                         -- lambda值  
                                 true,                      -- 标准化数据  
                                 null,                      -- 分组列  
                                 'fista',                   -- 优化器  
                                 $$ n_folds = 3,            -- 交叉验证参数  
                                    validation_result=houses_en3_cv,  
                                    n_lambdas = 5  
                                 $$,                         
                                 null,                      -- 排除列  
                                 10000,                     -- 最大迭代次数  
                                 1e-6                       -- 容忍值  
                               );  
  
select * from houses_en3;  
代码语言:txt复制
    结果:
代码语言:javascript复制
-[ RECORD 1 ]----- -------------------------------------------  
family            | gaussian  
features          | {tax,bath,size}  
features_selected | {tax,bath,size}  
coef_nonzero      | {22.933010813,9449.64725727,58.1847775893}  
coef_all          | {22.933010813,9449.64725727,58.1847775893}  
intercept         | -10794.876829  
log_likelihood    | -479245790.957  
standardize       | t  
iteration_run     | 157 

2. 查看交叉验证细节

代码语言:javascript复制
x off  
select round(lambda_value::numeric,4) lambda_value,  
       round(mean::numeric,4) mean,  
       round(std::numeric,4) std  
  from houses_en3_cv order by lambda_value desc; 
代码语言:txt复制
    结果:
代码语言:javascript复制
 lambda_value |       mean       |       std         
-------------- ------------------ -----------------  
  100000.0000 | -4128231363.7200 | 1221228181.7600  
    5623.4133 | -1222931198.7000 |  103345863.4470  
     316.2278 | -1144722373.6400 |   98220325.5374  
      17.7828 | -1142632269.8300 |  101332958.7120  
       1.0000 | -1142517820.0700 |  101527783.2400  
(5 rows) 
代码语言:txt复制
    可以看到,因为n_lambdas=5,所以交叉验证自动产生了5个lambda值,最后的1.0是我们在训练函数中指定的lambda值。当lambda=316时,标准差最小。下面用lambda=316进行训练生成模型,然后用这个模型进行预测,将结果与lambda=1时的模型预测比较。
代码语言:javascript复制
drop table if exists houses_en4, houses_en4_summary;  
select madlib.elastic_net_train( 'houses',                  -- 源表  
                                 'houses_en4',              -- 结果表  
                                 'price',                   -- 因变量  
                                 'array[tax, bath, size]',  -- 自变量  
                                 'gaussian',                -- 回归类型  
                                 1,                         -- alpha值  
                                 316,                       -- lambda值  
                                 true,                      -- 标准化数据  
                                 null,                      -- 分组列  
                                 'fista',                   -- 优化器  
                                 '',                        -- 优化参数  
                                 null,                      -- 排除列  
                                 10000,                     -- 最大迭代次数  
                                 1e-6                       -- 容忍值  
                               );  
  
x on  
select * from houses_en4;  
代码语言:txt复制
    结果:
代码语言:javascript复制
-[ RECORD 1 ]----- --------------------------------------------  
family            | gaussian  
features          | {tax,bath,size}  
features_selected | {tax,bath,size}  
coef_nonzero      | {22.7898939713,9142.85771283,57.9936622207}  
coef_all          | {22.7898939713,9142.85771283,57.9936622207}  
intercept         | -9730.60572809  
log_likelihood    | -497109765.778  
standardize       | t  
iteration_run     | 320  
代码语言:txt复制
    执行预测
代码语言:javascript复制
x off  
select id, price, predict, price - predict as residual, round(abs((price - predict) / price * 100)::numeric,4)  as residual_pct  
from (  
    select  
        houses.*,  
        madlib.elastic_net_gaussian_predict(  
            m.coef_all,             -- 全部拟合系数  
            m.intercept,            -- 模型截距  
            array[tax,bath,size]    -- 系数对应的特征列  
            ) as predict  
    from houses, houses_en4 m) s  
order by id;  
代码语言:txt复制
    结果:
代码语言:javascript复制
 id | price  |     predict      |     residual      | residual_pct   
---- -------- ------------------ ------------------- --------------  
  1 |  50000 |  57513.409337746 |   -7513.409337746 |      15.0268  
  2 |  85000 | 114255.562098622 |  -29255.562098622 |      34.4183  
  3 |  22500 |  61341.331818108 |  -38841.331818108 |     172.6281  
  4 |  90000 | 103774.078339511 |  -13774.078339511 |      15.3045  
  5 | 133000 | 125628.263070736 |  7371.73692926399 |       5.5427  
  6 |  90500 |  77733.411866969 |   12766.588133031 |      14.1067  
  7 | 260000 | 200236.843264003 |   59763.156735997 |      22.9858  
  8 | 142500 |  82761.964683443 |   59738.035316557 |      41.9214  
  9 | 160000 | 137479.007935812 |   22520.992064188 |      14.0756  
 10 | 240000 | 254224.237107707 |  -14224.237107707 |       5.9268  
 11 |  87000 |  96976.948064419 |   -9976.948064419 |      11.4678  
 12 | 118600 | 117966.815706951 |  633.184293048995 |       0.5339  
 13 | 140000 | 181272.626517032 |  -41272.626517032 |      29.4804  
 14 | 148000 | 154763.224373076 | -6763.22437307599 |       4.5697  
 15 |  65000 | 102887.922142515 |  -37887.922142515 |      58.2891  
 16 |  91000 | 101495.088942381 |  -10495.088942381 |      11.5331  
 17 | 132300 | 123349.273673606 |    8950.726326394 |       6.7655  
 18 |  91100 |  73175.433072709 |   17924.566927291 |      19.6757  
 19 | 260011 | 197957.853866873 |   62053.146133127 |      23.8656  
 20 | 141800 |  85040.954080573 |   56759.045919427 |      40.0275  
 21 | 160900 | 139074.300513803 |   21825.699486197 |      13.5648  
 22 | 239000 | 252401.045590003 |  -13401.045590003 |       5.6071  
 23 |  81010 |  95609.554426141 |  -14599.554426141 |      18.0219  
 24 | 117910 | 117283.118887812 |  626.881112187999 |       0.5317  
 25 | 141100 | 183551.615914162 |  -42451.615914162 |      30.0862  
 26 | 148011 | 159321.203167336 |  -11310.203167336 |       7.6415  
 27 |  66000 | 105166.911539645 |  -39166.911539645 |      59.3438  
(27 rows) 
代码语言:txt复制
    可以看到,本次预测的误差比lambda=1时小。
代码语言:txt复制
    MADlib强烈建议在使用大的max_iter参数在全数据集合上进行训练前,先使用小的max_iter参数在一个数据子集上运行elastic_net_train()函数。在跑全集前调整参数以获得最佳性能,然后再将最佳参数应用到全集训练上。

0 人点赞