Régression linéaire par morceaux#
Le paragraphe Régression linéaire
étudie le lien entre le coefficient
et la corrélation pour finalement illustrer
une façon de réaliser une régression linéaire par
morceaux. L’algorithme s’appuie sur un arbre
de régression pour découper en morceaux ce qui
n’est pas le plus satisfaisant car l’arbre
cherche à découper en segment en approximant
la variable à régresser Y par une constante sur chaque
morceaux et non une droite.
On peut se poser la question de comment faire
pour construire un algorithme qui découpe en approximant
Y par une droite et non une constante. Le plus dur
n’est pas de le faire mais de le faire efficacement.
Et pour comprendre là où je veux vous emmener, il faudra
un peu de mathématiques.
Une implémentation de ce type de méthode est proposée dans la pull request Model trees (M5P and co) qui répond à au problème posée dans Model trees (M5P) et originellement implémentée dans Building Model Trees. Cette dernière implémentation réestime les modèles comme l’implémentation décrite au paragraphe Implémentation naïve d’une régression linéaire par morceaux mais étendue à tout type de modèle.
Exploration#
Problème et regréssion linéaire dans un espace à une dimension#
Tout d’abord, une petite illustration du problème avec la classe PiecewiseRegression implémentée selon l’API de scikit-learn.

Cette régression par morceaux est obtenue grâce à un arbre
de décision. Celui-ci trie le nuage de points
par ordre croissant selon les X, soit
.
L’arbre coupe en deux lorsque la différence des erreurs quadratiques est
maximale, erreur quadratique obtenue en approximant Y par sa moyenne
sur l’intervalle considéré. On note l’erreur quadratique :
La dernière ligne applique la formule
qui est facile à redémontrer.
L’algorithme de l’arbre de décision coupe un intervalle en
deux et détermine l’indice k qui minimise la différence :
L’arbre de décision optimise la construction d’une fonction en escalier qui représente au mieux le nuage de points, les traits verts sur le graphe suivant, alors qu’il faudrait choisir une erreur quadratique qui corresponde aux traits oranges.

Il suffirait donc de remplacer l’erreur E par celle obtenue
par une régression linéaire. Mais si c’était aussi simple,
l’implémentation de sklearn.tree.DecisionTreeRegressor
la proposerait. Alors pourquoi ?
La raison principale est que cela coûte trop cher en
temps de calcul. Pour trouver l’indice k, il faut calculer
toutes les erreurs
, ce qui
coûte très cher lorsque cette erreur est celle d’une régression
linéaire parce qu’il est difficile de simplifier la différence :
Arbre de régression constante
On s’intéresse au terme dans le cas
le nuage de points est représenté par une constante sur chaque segment.
C’est l’hypothèse faite par l’algorithme classique de construction
d’un arbre de régression (segments verts sur le premier dessin) :
On en déduit que :
On voit que cette formule ne fait intervenir que ,
elle est donc très rapide à calculer et c’est pour cela qu’apprendre un arbre
de décision peut s’apprendre en un temps raisonnable. Cela repose sur la possibilité
de calculer le critère optimisé par récurrence. On voit également que ces formules
ne font pas intervenir X, elles sont donc généralisables au cas
multidimensionnel. Il suffira de trier les couples
selon chaque dimension et déterminer le meilleur seuil de coupure
d’abord sur chacune des dimensions puis de prendre le meilleur
de ces seuils sur toutes les dimensions. Le problème est résolu.
Le notebook Custom Criterion for DecisionTreeRegressor
implémente une version pas efficace du critère
MSE
et compare la vitesse d’exécution avec l’implémentation de scikit-learn.
Il implémente ensuite le calcul rapide de scikit-learn pour
montrer qu’on obtient un temps comparable.
Le résultat est sans équivoque. La version rapide n’implémente pas
mais plutôt les sommes
,
dans un sens
et dans l’autre. En gros,
le code stocke les séries des numérateurs et des dénominateurs
pour les diviser au dernier moment.
Arbre de régression linéaire
Le cas d’une régression est plus complexe. Prenons d’abord le cas où il n’y a qu’un seule dimension, il faut d’abord optimiser le problème :
On dérive pour aboutir au système d’équations suivant :
Ce qui aboutit à :
Pour construire un algorithme rapide pour apprendre un arbre de décision
avec cette fonction de coût, il faut pouvoir calculer
en fonction de
ou d’autres quantités intermédiaires qui ne font pas intervenir
les valeurs
. D’après ce qui précède,
cela paraît tout-à-fait possible. Mais dans le
cas multidimensionnel,
il faut déterminer le vecteur A qui minimise
ce qui donne
. Si on note
la matrice
M tronquée pour ne garder que ses k premières lignes, il faudrait pouvoir
calculer rapidement :
La documentation de sklearn.tree.DecisionTreeRegressor
ne mentionne que deux critères pour apprendre un arbre de décision
de régression, MSE pour
sklearn.metrics.mean_squared_error et MAE pour
sklearn.metrics.mean_absolute_error. Les autres critères n’ont
probablement pas été envisagés. L’article [Acharya2016] étudie la possibilité
de ne pas calculer la matrice pour tous les k.
Le paragraphe Streaming Linear Regression utilise
le fait que la matrice A est la solution d’un problème d’optimisation
quadratique et propose un algorithme de mise à jour de la matrice A
(cas unidimensionnel). Cet exposé va un peu plus loin
pour proposer une version qui ne calcule pas de matrices inverses.
Implémentation naïve d’une régression linéaire par morceaux#
On part du cas général qui écrit la solution d’une régression
linéaire comme étant la matrice
et on adapte l’implémentation de scikit-learn pour
optimiser l’erreur quadratique obtenue. Ce n’est pas simple mais
pas impossible. Il faut entrer dans du code cython et, pour
éviter de réécrire une fonction qui multiplie et inverse une matrice,
on peut utiliser la librairie LAPACK. Je ne vais pas plus loin
ici car cela serait un peu hors sujet mais ce n’était pas une partie
de plaisir. Cela donne :
piecewise_tree_regression_criterion_linear.pyx
C’est illustré toujours par le notebook
DecisionTreeRegressor optimized for Linear Regression.
Aparté sur la continuité de la régression linéaire par morceaux#
Approcher la fonction quand x et y
sont réels est un problème facile, trop facile… A voir le dessin,
précédent, il est naturel de vouloir recoller les morceaux lorsqu’on
passe d’un segment à l’autre. Il s’agit d’une optimisation sous contrainte.
Il est possible également d’ajouter une contrainte de régularisation
qui tient compte de cela. On exprime cela comme suit avec une régression
linéaire à deux morceaux.
Le cas multidimensionnel est loin d’être aussi simple. Avec une dimension, chaque zone a deux voisines. En deux dimensions, chaque zone peut en avoir plus de deux. La figure suivante montre une division de l’espace dans laquelle la zone centrale a cinq voisins.

Peut-on facilement approcher une fonction
par un plan en trois dimensions ? A moins que tous les sommets soient
déjà dans le même plan, c’est impossible. La zone en question n’est
peut-être même pas convexe. Une régression linéaire par morceaux
et continue en plusieurs dimensions n’est pas un problème facile.
Cela n’empêche pas pour autant d’influencer la détermination de chaque
morceaux avec une contrainte du type de celle évoquée plus haut
mais pour écrire la contrainte lorsque les zones sont construites
à partir des feuilles d’un arbre de décision, il faut déterminer
quelles sont les feuilles voisines.
Et ça c’est un problème intéressant !
Régression linéaire et corrélation#
On reprend le calcul multidimensionnel mais on s’intéresse au
cas où la matrice est diagonale qui correspond au cas
où les variables
ne sont pas corrélées.
Si
,
la matrice
s’exprime plus simplement
.
On en déduit que :
Cette expression donne un indice sur la résolution d’une régression linéaire
pour laquelle les variables sont corrélées. Il suffit d’appliquer d’abord une
ACP
(Analyse en Composantes Principales) et de calculer les coefficients
associés à des valeurs propres non nulles. On écrit alors
où la matrice P vérifie
.
Idée de l’algorithme#
On s’intéresser d’abord à la recherche d’un meilleur point de coupure.
Pour ce faire, les éléments sont triés le plus souvent
selon l’ordre défini par une dimension. On note E l’erreur de prédiction
sur cette échantillon
.
On définit ensuite
.
D’après cette notation,
. La construction de l’arbre
de décision passe par la détermination de
qui vérifie :
Autrement dit, on cherche le point de coupure qui maximise la différence entre la prédiction obtenue avec deux régressions linéaires plutôt qu’une. On sait qu’il existe une matrice P qui vérifie :
Où est une matrice
diagonale. On a posé
,
donc
.
On peut réécrire le problème
de régression comme ceci :
Comme :
Avec . C’est la même régression
après un changement de repère et on la résoud de la même manière :
La notation désigne la ligne i et
désigne la colonne.
On en déduit que le coefficient de la régression
est égal à :
On en déduit que :
Algorithme A1 : Arbre de décision optimisé pour les régressions linéaires
On dipose qu’un nuage de points avec
et
. Les points sont
triés selon une dimension. On note X la matrice composée
des lignes
et le vecteur colonne