【问题标题】:Constraint the sum of coefficients with scikit learn linear model用scikit学习线性模型约束系数总和
【发布时间】:2017-12-01 02:06:39
【问题描述】:

我正在做一个有 1000 个 coefs 的 LassoCV。 Statsmodels 似乎无法处理这么多的系数。所以我正在使用scikit学习。 Statsmodel 允许 .fit_constrained("coef1 + coef2...=1")。这将系数的总和限制为 = 1。我需要在 Scikit 中执行此操作。我也将截距保持为零。

from sklearn.linear_model import LassoCV

LassoCVmodel = LassoCV(fit_intercept=False)
LassoCVmodel.fit(x,y)

任何帮助将不胜感激。

【问题讨论】:

  • 不支持。您要么需要从头开始编写一些东西,要么修改 sklearn,要么使用其他一些库。
  • 我可以使用 sklearn.model_selection.GridSearchCV 和 fit_params 吗?
  • 没有。这没有任何意义。
  • 好的,谢谢@sascha
  • 如果这个任务对你很重要,你可以很容易地在 cvxpy 中表述这个问题,并且解决的速度应该足够快(尽管你没有指定你有多少样本)。

标签: python machine-learning scikit-learn regression sklearn-pandas


【解决方案1】:

我很惊讶之前没有人在 cmets 中说明过这一点,但我认为您的问题陈述中存在概念上的误解。

让我们从 Lasso Estimator 的定义开始,例如 Hastie、Tibshirani 和 Wainwright 在 Statistical Learning with Sparsity The Lasso and Generalizations 中给出的: p>

给定 N 个预测-响应对 {(xi,yi)} 的集合, lasso 为最小二乘找到拟合系数 (β0,βi) 具有附加约束的优化问题,即 L1 范数 系数向量的βi小于等于t

其中系数向量的 L1-范数是所有系数的大小之和。 如果你的系数都是正数,这正好解决了你的问题。

现在,这个 t 和 scikit-learn 中使用的 alpha 参数之间有什么关系?好吧,事实证明,通过拉格朗日对偶,t 的每个值与alpha 的值之间存在一一对应关系。

这意味着当您使用LassoCV 时,由于您使用的是alpha 的值范围,因此根据定义,您使用的是所有系数总和的允许值范围!

总而言之,所有系数之和等于 1 的条件相当于使用 Lasso 获取特定值 alpha

【讨论】:

    【解决方案2】:

    如 cmets 中所述:文档和来源并未表明 sklearn 支持此功能!

    我刚刚尝试了使用现成的凸优化求解器的替代方案。这只是一种简单的类似原型的方法,它可能不适合您的(未完全定义的)任务(样本大小?)。

    一些cmets:

    • 实施/模型制定很容易
    • 这个问题比我想象的更难解决
      • 求解器 ECOS 遇到一般问题
      • 求解器 SCS 达到良好的准确度(与 sklearn 相比更差)
      • 但是:调整迭代以提高精度会破坏求解器
        • 问题对于 SCS 来说是不可行的!
      • 基于 SCS + bigM 的公式(约束在目标内作为惩罚项发布)看起来可用;但可能需要调整
      • 仅测试了开源求解器,而商业求解器可能要好得多

    进一步尝试:

    • 解决巨大的问题(与稳健性和准确性相比,性能变得更重要),(加速)投影随机梯度方法看起来很有前景

    代码

    """ data """
    from time import perf_counter as pc
    import numpy as np
    from sklearn import datasets
    diabetes = datasets.load_diabetes()
    A = diabetes.data
    y = diabetes.target
    alpha=0.1
    
    print('Problem-size: ', A.shape)
    
    def obj(x):  # following sklearn's definition from user-guide!
        return (1. / (2*A.shape[0])) * np.square(np.linalg.norm(A.dot(x) - y, 2)) + alpha * np.linalg.norm(x, 1)
    
    
    """ sklearn """
    print('\nsklearn classic l1')
    from sklearn import linear_model
    clf = linear_model.Lasso(alpha=alpha, fit_intercept=False)
    t0 = pc()
    clf.fit(A, y)
    print('used (secs): ', pc() - t0)
    print(obj(clf.coef_))
    print('sum x: ', np.sum(clf.coef_))
    
    """ cvxpy """
    print('\ncvxpy + scs classic l1')
    from cvxpy import *
    x = Variable(A.shape[1])
    objective = Minimize((1. / (2*A.shape[0])) * sum_squares(A*x - y) + alpha * norm(x, 1))
    problem = Problem(objective, [])
    t0 = pc()
    problem.solve(solver=SCS, use_indirect=False, max_iters=10000, verbose=False)
    print('used (secs): ', pc() - t0)
    print(obj(x.value.flat))
    print('sum x: ', np.sum(x.value.flat))
    
    """ cvxpy -> sum x == 1 """
    print('\ncvxpy + scs sum == 1 / 1st approach')
    objective = Minimize((1. / (2*A.shape[0])) * sum_squares(A*x - y))
    constraints = [sum(x) == 1]
    problem = Problem(objective, constraints)
    t0 = pc()
    problem.solve(solver=SCS, use_indirect=False, max_iters=10000, verbose=False)
    print('used (secs): ', pc() - t0)
    print(obj(x.value.flat))
    print('sum x: ', np.sum(x.value.flat))
    
    """ cvxpy approach 2 -> sum x == 1 """
    print('\ncvxpy + scs sum == 1 / 2nd approach')
    M = 1e6
    objective = Minimize((1. / (2*A.shape[0])) * sum_squares(A*x - y) + M*(sum(x) - 1))
    constraints = [sum(x) == 1]
    problem = Problem(objective, constraints)
    t0 = pc()
    problem.solve(solver=SCS, use_indirect=False, max_iters=10000, verbose=False)
    print('used (secs): ', pc() - t0)
    print(obj(x.value.flat))
    print('sum x: ', np.sum(x.value.flat))
    

    输出

    Problem-size:  (442, 10)
    
    sklearn classic l1
    used (secs):  0.001451024380348898
    13201.3508496
    sum x:  891.78869298
    
    cvxpy + scs classic l1
    used (secs):  0.011165673357417458
    13203.6549995
    sum x:  872.520510561
    
    cvxpy + scs sum == 1 / 1st approach
    used (secs):  0.15350853891775978
    13400.1272148
    sum x:  -8.43795102327
    
    cvxpy + scs sum == 1 / 2nd approach
    used (secs):  0.012579569383536493
    13397.2932976
    sum x:  1.01207061047
    

    编辑

    只是为了好玩,我使用加速投影梯度的方法实现了一个缓慢的非优化原型求解器(代码中的注释!)。

    尽管这里的行为很慢(因为没有优化),但对于大问题(因为它是一阶方法),这个应该可以更好地扩展。应该有很大的潜力!

    警告: 对某些人来说可能被视为高级数值优化 :-)

    编辑 2:我忘记在投影上添加非负约束(sum(x) == 1 如果 x 可以是非负的,则没有多大意义!) .这使解决变得更加困难(数值问题),很明显,应该使用其中一种快速的专用投影(我现在太懒了;我认为 n*log n 算法可用)。再说一遍:这个 APG 求解器是一个原型,还不能用于实际任务。

    代码

    """ accelerated pg  -> sum x == 1 """
    def solve_pg(A, b, momentum=0.9, maxiter=1000):
        """ remarks:
                algorithm: accelerated projected gradient
                projection: proj on probability-simplex
                    -> naive and slow using cvxpy + ecos
                line-search: armijo-rule along projection-arc (Bertsekas book)
                    -> suffers from slow projection
                stopping-criterion: naive
                gradient-calculation: precomputes AtA
                    -> not needed and not recommended for huge sparse data!
        """
    
        M, N = A.shape
        x = np.zeros(N)
    
        AtA = A.T.dot(A)
        Atb = A.T.dot(b)
    
        stop_count = 0
    
        # projection helper
        x_ = Variable(N)
        v_ = Parameter(N)
        objective_ =  Minimize(0.5 * square(norm(x_ - v_, 2)))
        constraints_ = [sum(x_) == 1]
        problem_ = Problem(objective_, constraints_)
    
        def gradient(x):
            return AtA.dot(x) - Atb
    
        def obj(x):
            return 0.5 * np.linalg.norm(A.dot(x) - b)**2
    
        it = 0
        while True:
            grad = gradient(x)
    
            # line search
            alpha = 1
            beta = 0.5
            sigma=1e-2
            old_obj = obj(x)
            while True:
                new_x = x - alpha * grad
                new_obj = obj(new_x)
                if old_obj - new_obj >= sigma * grad.dot(x - new_x):
                    break
                else:
                    alpha *= beta
    
            x_old = x[:]
            x = x - alpha*grad
    
            # projection
            v_.value = x
            problem_.solve()
            x = np.array(x_.value.flat)
    
            y = x + momentum * (x - x_old)
    
            if np.abs(old_obj - obj(x)) < 1e-2:
                stop_count += 1
            else:
                stop_count = 0
    
            if stop_count == 3:
                print('early-stopping @ it: ', it)
                return x
    
            it += 1
    
            if it == maxiter:
                return x
    
    
    print('\n acc pg')
    t0 = pc()
    x = solve_pg(A, y)
    print('used (secs): ', pc() - t0)
    print(obj(x))
    print('sum x: ', np.sum(x))
    

    输出

    acc pg
    early-stopping @ it:  367
    used (secs):  0.7714511330487027
    13396.8642379
    sum x:  1.00000000002
    

    【讨论】:

    • 哇,这非常有用。感谢您抽出宝贵时间。
    猜你喜欢
    • 2021-01-27
    • 2014-04-18
    • 2013-05-14
    • 2013-10-24
    • 2019-07-18
    • 1970-01-01
    • 2017-01-04
    • 2012-12-16
    • 2016-10-01
    相关资源
    最近更新 更多