Introduction à la Recherche Opérationelle


M2 Statistique Science des Données (SSD)
Anatoli Juditsky
Franck Iutzeler

1- Problème de régression parcimonieuse et Dantzig Selector

On considère le modèle de régression normale $$ y=X\theta+\sigma\xi,\;\;\xi\sim \mathcal{N}(0, I_m),$$ où $X\in \mathbb{R}^{m\times n}$ et $y\in \mathbb{R}^m$ sont les observables, et $\theta\in \mathbb{R}^n$ est le paramètre inconnu.

L'estimateur de $\theta$ de ``Dantzig Selector'' (cf. Candes, E., Tao, T. (2007). The Dantzig selector: Statistical estimation when $p$ is much larger than $n$. The Annals of Statistics, 2313-2351) peut être utilisé pour estimer $\theta$ dans le cas d'un modèle surparamétré, quand la dimension $n$ de $\theta$ est supérieure a la dimension de l'observation $y$.

Dans ce cas l'estimateur ${\theta}_{DS}$ s'écrit comme une solution du probleme d'optimisation $$\min_{\theta\in \mathbb{R}^n} \|\theta\|_1,~~~\;\mbox{sous contrainte}\;\|X^T(X\theta-y)\|_\infty\leq \kappa\sigma,$$ où $\kappa>0$ est un hyper-paramètre.

La valeur de $\kappa$, préconisée dans la literature, est $c q_{\mathcal{N}}\left(1-{\alpha\over 2m}\right)$, où $\alpha\in (0,1)$ est le niveau de risque choisi (par ex. $\alpha=.05$) et $q_\mathcal{N}(p)$ est la $p$-quantile de la normale standardisée, et $c=\max_j\|[X]_j\|_2=\max_j\sqrt{[X]_j^T[X]_j}$ est la norme maximale de colonne de la matrice $X$.

Votre objectif dans cet exercice sera d'implementer l'estimateur ${\theta}_{DS}$ en utilisant CVXPY.

Question: Vérifier que le problème et les contraintes peuvent se formuler via des fonctions disponibles pour CVXPY.

2- Un petit exemple

In [1]:
import cvxpy as cp
import numpy as np
import pandas as pd
In [2]:
# Exemple jouet
n = 5
m = 3
sigma = 0.1

X = np.random.randn(m,n)
theta_true = np.array([1,0,0,0,5])
xi = np.random.randn(m)
y = np.dot(X,theta_true) + sigma*xi
In [3]:
X
Out[3]:
array([[-0.4456058 , -0.8915184 , -1.17766982,  0.09269125,  0.27165414],
       [ 1.69530856,  0.62879196,  1.33810631, -0.55994919, -0.69678998],
       [-0.97057836, -0.34218544, -0.34819598,  0.7295977 ,  0.58125393]])
In [4]:
y
Out[4]:
array([ 1.05545834, -1.76967046,  2.00878244])
In [5]:
theta_true
Out[5]:
array([1, 0, 0, 0, 5])

Question: Trouver l'estimateur $\theta_{DS}$ à partir de $X$ et $y$ par résolution du problème d'optimisation via CVXPY avec $\kappa$ fixé à 0.2.

In [6]:
theta = cp.Variable(n) 
In [7]:
F  = cp.norm1(theta)
objective = cp.Minimize(F)
In [8]:
kappa = 0.2
contrainte  = cp.norm_inf( X.T@( X @ theta - y ) ) <= kappa*sigma
In [9]:
problem = cp.Problem(objective , constraints = [contrainte])
In [10]:
problem.solve(verbose=True,solver="ECOS")
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +0.000e+00  -2.000e-02  +4e+01  4e-01  1e+00  1e+00  2e+00    ---    ---    1  1  - |  -  - 
 1  +9.433e-01  +1.417e+00  +1e+01  1e-01  2e-01  7e-01  7e-01  0.7436  1e-01   0  0  0 |  0  0
 2  +1.502e+00  +1.657e+00  +4e+00  3e-02  5e-02  2e-01  2e-01  0.8197  1e-01   0  1  0 |  0  0
 3  +2.957e+00  +3.594e+00  +2e+00  1e-01  8e-02  8e-01  1e-01  0.7632  5e-01   0  0  0 |  0  0
 4  +2.871e+00  +2.855e+00  +5e-01  1e-02  1e-02  6e-03  2e-02  0.9547  2e-01   0  0  0 |  0  0
 5  +3.076e+00  +3.074e+00  +6e-02  1e-03  1e-03  8e-04  3e-03  0.8904  1e-02   1  0  0 |  0  0
 6  +3.129e+00  +3.129e+00  +3e-03  5e-05  6e-05  1e-04  2e-04  0.9607  2e-02   1  1  1 |  0  0
 7  +3.131e+00  +3.131e+00  +7e-05  1e-06  1e-06  3e-06  3e-06  0.9806  2e-04   1  0  0 |  0  0
 8  +3.131e+00  +3.131e+00  +7e-07  1e-08  1e-08  3e-08  3e-08  0.9890  1e-04   1  1  0 |  0  0
 9  +3.131e+00  +3.131e+00  +8e-09  1e-10  1e-10  3e-10  4e-10  0.9890  1e-04   1  0  0 |  0  0

OPTIMAL (within feastol=1.4e-10, reltol=2.6e-09, abstol=8.0e-09).
Runtime: 0.000505 seconds.

Out[10]:
3.13097684407165
In [11]:
problem.status
Out[11]:
'optimal'
In [12]:
theta_ds= theta.value
theta_ds
Out[12]:
array([-1.48067945e-02, -9.01999749e-01, -6.54722210e-09,  2.21417029e+00,
        1.69190242e-09])
In [13]:
print("True \t Dantzig selector")
for i in range(len(theta_true)):
    print('{} \t {:6.3f}'.format(theta_true[i],theta_ds[i]))
True 	 Dantzig selector
1 	 -0.015
0 	 -0.902
0 	 -0.000
0 	  2.214
5 	  0.000

3- Fonction "Dantzig Selector"

Question: Écrivez une fonction DSelect qui fait appel a CVXPY pour calculer l'estimation ${\theta}_{DS}$.

Cette fonction doit sortir un tuple avec les elements

  • coef, vecteur des coefficients de regression
  • resid, vecteur $y-X{\theta}_{DS}$ de résidus
  • status, le statut de sortie du solver

L'appel à cette fonction devra être:

DSelect(X, y, sigma = 1, c = 1, verb = False)

  • X et y sont les observables
  • sigma est une estimation de $\sigma$
  • c est le paramètre réel tel que la valeur de $\kappa$ dans {DS} est $ \kappa=c\,q_{\mathcal{N}}\left(1-{\alpha\over 2m}\right).$
In [14]:
import scipy.stats

def DSelect(X, y, sigma = 1, c = 1, verb = False):
    

    m,n = X.shape
     
    alpha = 0.05
    kappa = c*scipy.stats.norm.ppf(1-alpha/m)

    theta = cp.Variable(n) 
    # Function
    F = cp.norm1(theta)
    objective = cp.Minimize(F)

    # Constraints
    contrainte  = cp.norm_inf( X.T@( X @ theta - y ) ) <= kappa*sigma
    
    problem = cp.Problem(objective , constraints = [contrainte])
    
    problem.solve(verbose=verb,solver="ECOS")

    theta_ds = theta.value
    
    residual =  y - np.dot(X,theta_ds)
    sol_status = problem.status
    
    return theta_ds,residual,sol_status

Test quand on connait le vrai theta

In [15]:
n = 5
m = 5
X = np.random.randn(m,n)
theta_true = np.random.randn(n)
sigma = 0.005

y = np.dot(X,theta_true) + sigma*np.random.randn(m)

Avec la valeur de sigma prise ci-dessus, vous devriez pouvoir appeler votre fonctions comme ci-dessous.

In [16]:
f = DSelect(X, y, sigma=sigma)

Question: Testez votre fonction sur cet exemple jouet. En particulier, comparez la valeur de theta_ds à la vraie valeur theta_true.

In [17]:
print("True \t Dantzig selector")
for i in range(len(theta_true)):
    print('{:6.3f} \t {:6.3f}'.format(theta_true[i],f[0][i]))
True 	 Dantzig selector
-0.577 	 -0.511
-0.012 	 -0.060
-0.071 	 -0.072
 1.491 	  1.448
-0.143 	 -0.164
In [ ]:
 

Example du papier de Candes/Tao

In [18]:
import random

n = 256
m = 72
S = 8

S_set = random.sample(range(n),k=S)

X = np.random.randn(m,n)

theta_true = np.zeros(n)
theta_true[S_set] = np.random.randn(S)

sigma = 1/3.0*np.sqrt(S/m)
y = np.dot(X,theta_true) + sigma*np.random.randn(m)
In [19]:
f = DSelect(X, y, sigma= sigma)

Question: Testez votre fonction sur l'exemple du papier. Comparez la valeur de theta_ds à la vraie valeur theta_true. Essayer de changer sigma.

In [20]:
f = DSelect(X, y, sigma= sigma)
In [21]:
np.linalg.norm(theta_true-f[0],1)
Out[21]:
0.6082715941976763

Support des solutions

La valeur cherchée theta_true est très parcimonieuse (elle contient beaucoup de zéros). Les seules valeurs non-null sont aux positions suivantes.

In [22]:
S_set
Out[22]:
[225, 43, 175, 120, 177, 100, 30, 247]
In [23]:
theta_true
Out[23]:
array([ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
       -1.14743855,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  1.30897485,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.05910713,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.4613149 ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.29501964,  0.        ,  0.34930288,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
       -0.29683591,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.95623021,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ])

Question: Comparez le nombre et l'emplacement des zéros dans theta_ds à ceux dans theta_true. Essayez de changer c.

In [24]:
f[0]
Out[24]:
array([-3.53677187e-12, -5.95687850e-13, -6.57739012e-13, -2.03893922e-12,
       -9.00852842e-03,  3.41466381e-12,  2.92870232e-12,  1.36810813e-02,
       -1.70625577e-11,  1.73333948e-12,  1.94014948e-12, -8.03842762e-04,
        9.97731959e-13, -2.43526945e-13,  3.45841703e-12,  4.57633019e-12,
       -4.38034144e-03, -1.93463006e-11,  2.79860716e-12,  1.85256294e-12,
       -3.82465219e-12,  1.65144124e-02,  2.83928086e-03, -2.92889363e-12,
       -3.11753412e-13, -1.49132483e-02,  4.25901077e-12, -1.09106430e-11,
       -7.31365062e-12,  3.23966859e-12, -1.12329085e+00,  5.77460094e-12,
        2.27128942e-12, -1.37008013e-12,  1.83192196e-12,  1.01641049e-12,
        9.08047342e-12,  3.74389669e-11,  2.71847581e-12, -2.10897362e-12,
        8.74716634e-03, -2.84162884e-12, -4.10438753e-12,  1.25140207e+00,
       -1.48801015e-02, -2.09319789e-12,  1.72349858e-12,  1.44769669e-12,
       -1.29769706e-12,  8.27464636e-13, -3.01378067e-12,  6.41085195e-05,
        7.83152787e-03,  4.10577462e-11, -2.65053266e-11, -1.37772044e-11,
        8.97237324e-12, -3.45448352e-14,  6.28299917e-12, -2.12445788e-14,
       -1.09823017e-02,  1.23770530e-12, -6.26728083e-03,  5.19247660e-14,
       -6.03927223e-05, -4.67238792e-03,  2.50966399e-12, -4.48524107e-10,
        4.76057955e-13,  1.66897226e-14, -2.60789485e-12,  1.69937985e-02,
       -1.47967305e-12, -1.08358163e-12,  1.62148276e-12, -1.82832045e-12,
        2.27364346e-12, -7.72462730e-13,  2.91030599e-12,  7.90618179e-12,
       -6.13333056e-13, -3.32539618e-12, -9.32608362e-13,  2.78042188e-12,
        2.45277572e-03,  2.36827891e-13,  2.06711241e-12, -6.74987935e-12,
        9.44621260e-11,  1.86784879e-12,  1.00930037e-11,  3.65017031e-13,
        3.89013267e-12,  4.98453086e-12,  2.78039449e-12, -1.27356194e-02,
        1.68983421e-12, -2.51855218e-12, -1.21002997e-03, -1.06105978e-12,
        9.58779524e-03, -6.38391201e-03,  2.18958729e-12, -2.76319709e-12,
       -3.22982889e-03, -6.92579573e-14,  4.34590065e-03,  5.04960089e-13,
        1.83966154e-12, -8.43602736e-13,  3.79077951e-12, -1.65216037e-03,
        1.99800941e-12, -2.30576522e-13,  2.37879454e-12,  2.88516394e-03,
       -2.08571324e-11, -8.00697290e-12, -4.51491096e-03, -1.11483691e-02,
        4.39580520e-01,  5.15484895e-12, -4.77026656e-12, -4.27174717e-12,
       -3.89303770e-12, -2.78309072e-12, -1.01646383e-12,  1.38388164e-12,
        6.27855544e-12, -4.58699483e-12,  1.56597061e-02,  7.85587176e-14,
       -2.85457115e-12,  1.77360891e-13,  2.61943974e-03, -2.09785387e-12,
        3.87363940e-13,  7.01634870e-13,  2.38449151e-12,  2.16695160e-14,
       -7.64787268e-13,  2.55112802e-12,  2.71973610e-12,  4.68593525e-13,
       -3.51138463e-12, -2.80039986e-12, -3.43711652e-13,  2.68183679e-12,
       -2.00693253e-12,  3.74404305e-12,  5.60238781e-13, -3.16907577e-12,
        1.81811130e-12, -1.02154918e-02,  5.77707470e-13, -1.32779243e-12,
        1.23469426e-03,  5.30627153e-13,  2.64539685e-03,  6.79975616e-12,
       -1.73954083e-12, -2.43179823e-12,  1.32240846e-12,  4.15691064e-12,
       -5.61631388e-03, -9.58418959e-03, -7.04709014e-11, -8.06013764e-13,
        9.25600943e-13,  2.14625145e-13, -1.79944755e-12,  8.97198504e-03,
       -2.53321035e-11,  6.57518520e-12, -4.73239903e-13,  3.09024819e-01,
       -1.87405844e-12,  3.08647653e-01,  7.17542203e-03,  6.67588917e-12,
       -3.16646576e-14,  5.39194738e-13, -1.67218913e-14, -1.57045708e-12,
        2.92958923e-12, -7.19771004e-12, -6.55525537e-04, -1.19858289e-02,
        1.57858598e-12, -2.42891009e-12, -2.30292984e-12, -1.55730234e-11,
        6.86076791e-03, -1.06562454e-12,  1.18052088e-13,  2.00034821e-12,
        1.97590853e-11, -8.64444619e-03, -3.64691630e-12,  4.10022642e-12,
        1.19538233e-11, -4.00612479e-03, -8.39399618e-13,  5.05646529e-04,
       -8.10428717e-03, -1.85060326e-11,  5.16306308e-12,  1.80424721e-03,
       -1.68304390e-12, -3.18013659e-12,  1.15564388e-12, -4.58723645e-12,
        7.50916045e-13, -5.14133819e-03,  1.07991241e-11,  1.23541087e-12,
        4.11622748e-13, -3.74389854e-11,  6.94598634e-13, -1.34151230e-12,
        1.64747053e-03, -1.18579049e-12,  1.52237575e-02, -3.19880260e-12,
       -9.02383640e-13, -3.08584260e-01, -1.19860295e-02,  1.56423234e-12,
       -9.86794602e-13, -2.72771437e-12, -1.80028023e-02,  2.43328975e-12,
        1.47985617e-03, -2.12589776e-12, -1.18242162e-12,  4.87470225e-12,
        1.48703943e-02, -2.73688430e-12,  2.23476697e-12, -9.77286744e-03,
        6.01882938e-13,  1.23907675e-12,  6.54917991e-12,  8.43138715e-03,
       -2.89575943e-13, -6.40715435e-13, -4.73453874e-03,  9.48119984e-01,
       -1.23981389e-12, -3.57179590e-13, -1.40968060e-10, -9.81145512e-12,
       -9.90263761e-12, -2.61423867e-12,  2.71320024e-12,  1.51454412e-12])
In [25]:
import matplotlib.pyplot as plt

plt.plot(theta_true,'ro',label='Vrai theta')
plt.plot(f[0],'b*',label='Dantzig Selector')
plt.legend();
In [ ]: