1A.e - TD noté, 21 février 2017¶
Solution du TD noté, celui-ci présente un algorithme pour calculer les coefficients d’une régression quantile et par extension d’une médiane dans un espace à plusieurs dimensions.
Précision : dans tout l’énoncé, la transposée d’une matrice est notée \(X' = X^{T}\). La plupart du temps \(X\) et \(Y\) désignent des vecteurs colonnes. \(\beta\) désigne un vecteur ligne, \(W\) une matrice diagonale.
Exercice 1¶
Q1¶
A l’aide du module random, générer un ensemble de points aléatoires.
[1]:
import random
def ensemble_aleatoire(n):
res = [random.randint(0, 100) for i in range(n)]
res[0] = 1000
return res
ens = ensemble_aleatoire(10)
ens
[1]:
[1000, 15, 65, 44, 95, 17, 43, 44, 88, 88]
Q2¶
La médiane d’un ensemble de points \(\left\{X_1, ..., X_n\right\}\) est une valeur \(X_M\) telle que :
Autrement dit, il y a autant de valeurs inférieures que supérieures à \(X_M\). On obtient cette valeur en triant les éléments par ordre croissant et en prenant celui du milieu.
[2]:
def mediane(ensemble):
tri = list(sorted(ensemble))
return tri[len(tri) // 2]
mediane(ens)
[2]:
65
Q3¶
Lorsque le nombre de points est pair, la médiane peut être n’importe quelle valeur dans un intervalle. Modifier votre fonction de façon à ce que la fonction précédente retourne le milieu de la fonction.
[3]:
def mediane(ensemble):
tri = list(sorted(ensemble))
if len(tri) % 2 == 0:
m = len(tri) // 2
return (tri[m] + tri[m - 1]) / 2
else:
return tri[len(tri) // 2]
mediane(ens)
[3]:
54.5
Q4¶
Pour un ensemble de points \(E=\left\{X_1, ..., X_n\right\}\), on considère la fonction suivante :
On suppose que la médiane \(X_M\) de l’ensemble \(E\) n’appartient pas à \(E\) : \(X_M \notin E\). Que vaut \(f'(X_M)\) ? On acceptera le fait que la médiane est le seul point dans ce cas.
Par définition de la médiane, \(f'(X_M)=0\). En triant les éléments, on montre que la \(f'(x) = 0 \Longleftrightarrow x=X_m\).
Q5¶
On suppose qu’on dispose d’un ensemble d’observations \(\left(X_i, Y_i\right)\) avec \(X_i, Y_i \in \mathbb{R}\). La régression linéaire consiste une relation linéaire \(Y_i = a X_i + b + \epsilon_i\) qui minimise la variance du bruit. On pose :
On cherche \(a, b\) tels que :
La fonction est dérivable et on trouve :
Il suffit alors d’annuler les dérivées. On résoud un système d’équations linéaires. On note :
Finalement :
Lorsqu’on a plusieurs dimensions pour \(X\), on écrit le problème d’optimisation, on cherche les coefficients \(\beta^*\) qui minimisent :
La solution est : \(\beta^* = (X'X)^{-1}X'Y\).
Ecrire une fonction qui calcule ce vecteur optimal.
[4]:
from numpy.linalg import inv
def regression_lineaire(X, Y):
t = X.T
return inv(t @ X) @ t @ Y
import numpy
X = numpy.array(ens).reshape((len(ens), 1))
regression_lineaire(
X, X + 1
) # un essai pour vérifier que la valeur n'est pas aberrante
[4]:
array([[1.00144835]])
Q6¶
Ecrire une fonction qui transforme un vecteur en une matrice diagonale.
[5]:
def matrice_diagonale(W):
return numpy.diag(W)
matrice_diagonale([1, 2, 3])
[5]:
array([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
Q7¶
On considère maintenant que chaque observation est pondérée par un poids \(w_i\). On veut maintenant trouver le vecteur \(\beta\) qui minimise :
Où \(W=diag(w_1, ..., w_n)\) est la matrice diagonale. La solution est :
Ecrire une fonction qui calcule la solution de la régression pondérée. La fonction ravel est utile.
[6]:
def regression_lineaire_ponderee(X, Y, W):
if len(W.shape) == 1 or W.shape[0] != W.shape[1]:
# c'est un vecteur
W = matrice_diagonale(W.ravel())
wx = W @ X
xt = X.T
return inv(xt @ wx) @ xt @ W @ Y
X = numpy.array(sorted(ens)).reshape((len(ens), 1))
Y = X.copy()
Y[0] = max(X)
W = numpy.ones(len(ens))
W[0] = 0
regression_lineaire_ponderee(X, Y, W), regression_lineaire(X, Y)
[6]:
(array([[1.]]), array([[1.01427573]]))
Q8¶
Ecrire une fonction qui calcule les quantités suivantes (fonctions maximum, reciprocal).
[7]:
def calcule_z(X, beta, Y, W, delta=0.0001):
epsilon = numpy.abs(Y - X @ beta)
return numpy.reciprocal(numpy.maximum(epsilon, numpy.ones(epsilon.shape) * delta))
calcule_z(X * 1.0, numpy.array([[1.01]]), Y, W)
[7]:
array([[1.01538305e-03],
[5.88235294e+00],
[2.32558140e+00],
[2.27272727e+00],
[2.27272727e+00],
[1.53846154e+00],
[1.13636364e+00],
[1.13636364e+00],
[1.05263158e+00],
[1.00000000e-01]])
Q9¶
On souhaite coder l’algorithme suivant :
\(w_i^{(1)} = 1\)
\(\beta_{(t)} = (X'W^{(t)}X)^{-1}X'W^{(t)}Y\)
\(w_i^{(t+1)} = \frac{1}{\max\left( \delta, \left|y_i - X_i \beta^{(t)}\right|\right)}\)
\(t = t+1\)
Retour à l’étape 2.
[8]:
def algorithm(X, Y, delta=0.0001):
W = numpy.ones(X.shape[0])
for i in range(0, 10):
beta = regression_lineaire_ponderee(X, Y, W)
W = calcule_z(X, beta, Y, W, delta=delta)
E = numpy.abs(Y - X @ beta).sum()
print(i, E, beta)
return beta
X = numpy.random.rand(10, 1)
Y = X * 2 + numpy.random.rand()
Y[0] = Y[0] + 100
algorithm(X, Y)
0 173.8971776057001 [[24.09487215]]
1 113.8229016016727 [[6.16379991]]
2 102.31983967417062 [[2.73034639]]
3 100.39132855434099 [[2.15204012]]
4 100.24844521352179 [[2.10443138]]
5 100.10481587527799 [[2.05258271]]
6 100.0856692693878 [[2.04297748]]
7 100.08332794594425 [[2.04131809]]
8 100.0800296132712 [[2.03867545]]
9 100.07493795090468 [[2.03459599]]
[8]:
array([[2.03459599]])
[9]:
regression_lineaire(X, Y)
[9]:
array([[24.09487215]])
Q10¶
[10]:
ens = ensemble_aleatoire(10)
Y = numpy.empty((len(ens), 1))
Y[:, 0] = ens
X = numpy.ones((len(ens), 1))
mediane(ens)
[10]:
51.0
[11]:
Y.mean(axis=0)
[11]:
array([136.7])
[12]:
regression_lineaire(X, Y)
[12]:
array([[136.7]])
[13]:
algorithm(X, Y)
0 1726.6000000000004 [[136.7]]
1 1168.2374836735025 [[58.61874184]]
2 1163.7683647305184 [[56.38418237]]
3 1161.0 [[54.75688683]]
4 1161.0 [[54.75688683]]
5 1161.0 [[54.75688683]]
6 1161.0 [[54.75688683]]
7 1161.0 [[54.75688683]]
8 1161.0 [[54.75688683]]
9 1161.0 [[54.75688683]]
[13]:
array([[54.75688683]])
[14]:
mediane(ens)
[14]:
51.0
[15]:
list(sorted(ens))
[15]:
[3, 7, 22, 24, 47, 55, 62, 70, 77, 1000]
La régression linéaire égale la moyenne, l’algorithme s’approche de la médiane.
Quelques explications et démonstrations¶
Cet énoncé est inspiré de Iteratively reweighted least squares. Cet algorithme permet notamment d’étendre la notion de médiane à des espaces vectoriels de plusieurs dimensions. On peut détermine un point \(X_M\) qui minimise la quantité :
Nous reprenons l’algorithme décrit ci-dessus :
\(w_i^{(1)} = 1\)
\(\beta_{(t)} = (X'W^{(t)}X)^{-1}X'W^{(t)}Y\)
\(w_i^{(t+1)} = \frac{1}{\max\left( \delta, \left|y_i - X_i \beta^{(t)}\right|\right)}\)
\(t = t+1\)
Retour à l’étape 2.
L’erreur quadratique pondéré est :
Si \(w_i = \frac{1}{\left|y_i - X_i \beta\right|}\), on remarque que :
On retombe sur l’erreur en valeur absolue optimisée par la régression quantile. Comme l’étape 2 consiste à trouver les coefficients \(\beta\) qui minimise \(E_2(\beta, W^{(t)})\), par construction, il ressort que :
La suite \(t \rightarrow E_1(\beta^{(t)})\) est suite décroissant est minorée par 0. Elle converge donc vers un minimum. Or la fonction \(\beta \rightarrow E_1(\beta)\) est une fonction convexe. Elle n’admet qu’un seul minimum (mais pas nécessaire un seul point atteignant ce minimum). L’algorithme converge donc vers la médiane. Le paramètre \(\delta\) est là pour éviter les erreurs de divisions par zéros et les approximations de calcul faites par l’ordinateur.
Quelques commentaires sur le code¶
Le symbol @ a été introduit par Python 3.5 et est équivalent à la fonction numpy.dot. Les dimensions des matrices posent souvent quelques problèmes.
[16]:
import numpy
y = numpy.array([1, 2, 3])
M = numpy.array([[3, 4], [6, 7], [3, 3]])
M.shape, y.shape
[16]:
((3, 2), (3,))
[17]:
try:
M @ y
except Exception as e:
print(e)
matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 3 is different from 2)
Par défaut, numpy considère un vecteur de taille (3,)
comme un vecteur ligne (3,1)
. Donc l’expression suivante va marcher :
[18]:
y @ M
[18]:
array([24, 27])
Ou :
[19]:
M.T @ y
[19]:
array([24, 27])
[ ]: