TD/TP 1 : Introduction au Machine Learning

TD

ex1_1.png

ex1_2et3.png

ex2.png

ex3_1et2.png

ex3_3.png

ex3_final.jpg

Questions posées


1) Question : (cf ex1 q1)

Quel est l'intérêt de prendre l'esperance des risques ? En effet, les risques semblent déterministes...

Réponse : $$\mathcal{R}(f) = \mathbb{E}_{(X,Y) \sim P}[l(f(X), Y)]$$

$$\eta(x) = \mathbb{E}[Y|X=x]$$

Si $f^*(x) = \mathbb{1}\{\eta(x) \geq \frac{1}{2}\}$ alors $R(f^*)$ est bien déterministe

Cependant si $\hat{f}$ dépend des v.a $\mathcal{D}_n = \{(X_1, Y_1), \cdots, (X_n, Y_n)\}$, ce qui est souvent le cas, alors le risque (conditionnel) peut s'écrire

$$ \mathcal{R}(\hat{f}) = \mathcal{R}(\hat{f}(\mathcal{D}_n)) = \mathbb{E}_{(X,Y) \sim P}[\ l(\hat f(\mathcal{D}_n, X), Y) \ |\ \mathcal{D}_n] $$

Nous appelons alors le risque moyen

$$ \mathbb{E}[\mathcal{R}(\hat{f})] = \mathbb{E}[\ l(\hat f(\mathcal{D}_n, X), Y) \ ] $$

2) Question : (cf ex2)

Au moment de l'IPP en primitivant $\pi(\theta | x)$ on passe d'une intégration sur $\theta$ a une intégration sur une autre variable $y$, pourquoi ?

Réponse :

Ici, $y$ << correspond à $\theta$ la variable locale>>. Au fait, il y a $\theta$ signifie deux notations ...

  • $\theta$ une variable locale (qui se trouve seulement dans l'intégrale mais qui n'existe pas en dehors) et
  • $\theta$ la variable aléaroire suivant $\pi$

Afin de ne pas écrire $P^\pi(\theta < \theta | x)$, j'ai changé la variable locale $\theta$ en $y$.

TP

Partie 1 : Introduction à Python, Numpy, etc.

In [6]:
import numpy as np
import matplotlib.pyplot as plt

Q1.

In [7]:
Z = np.zeros((8,8))
Z[1::2,::2] = 1
Z[::2,1::2] = 1
Z
Out[7]:
array([[0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0.],
       [0., 1., 0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0., 1., 0.]])

Q2.

In [8]:
M = np.arange(1,21)
M = M.reshape((5,4))
M = M.transpose()
print(M)
[[ 1  5  9 13 17]
 [ 2  6 10 14 18]
 [ 3  7 11 15 19]
 [ 4  8 12 16 20]]
In [9]:
print(M[1:,:][:,[1,4,2]])
[[ 6 18 10]
 [ 7 19 11]
 [ 8 20 12]]

Q3.

In [10]:
m = 15
x = 2*np.random.random_sample(m)-1

Q4.

In [11]:
def g(x):
    return 1.5*x**3 -  x**2 - .75*x + 1 
y = g(x) + .05*np.random.randn(m)

Q5.

In [12]:
plt.plot(x,y,'bo')
plt.show()

Q6.

In [13]:
m_test = 30
x_test = 2*np.random.random_sample(m_test)-1
y_test = g(x_test) + .05*np.random.randn(m_test)

Q7.

In [15]:
from sklearn.linear_model import LinearRegression
f = LinearRegression()

X = x[:,np.newaxis]
X_test = x_test[:,np.newaxis]

f.fit(X,y)

xplot = np.linspace(-1,1,500).reshape(-1,1)
plt.plot(x,y,'bo')
plt.plot(xplot,f.predict(xplot))
plt.show()

Q8.

In [16]:
print(sum((y-f.predict(X))**2)/m)
print(sum((y_test-f.predict(X_test))**2)/m_test)
0.0723641397174936
0.15573443914884566

Q9.

In [30]:
from sklearn.preprocessing import PolynomialFeatures
psi = PolynomialFeatures(2,include_bias=False).fit_transform

f = LinearRegression().fit(psi(X),y)

plt.plot(x,y,'bo')
plt.plot(xplot,f.predict(psi(xplot)))
plt.show()

print(sum((y-f.predict(psi(X)))**2)/m)
print(sum((y_test-f.predict(psi(X_test)))**2)/m_test)
0.04864477318742404
0.060256486665932044

On observe une nette amélioration par rapport à la regression linéaire

Q10.

In [23]:
def reg_poly(n):
    psi = PolynomialFeatures(n,include_bias=False).fit_transform
    return LinearRegression().fit(psi(X),y), psi

plt.plot(x,y,'bo')
poly = {} 
psi = {}
for n in [3,4,13,14]:
    poly[n], psi[n] = reg_poly(n)
    plt.plot(xplot,poly[n].predict(psi[n](xplot)))
plt.axis([-1, 1, -1.5, 1.5])
plt.legend(['données','n = 3', 'n = 4', 'n = 13', 'n = 14'])
plt.show()

print(poly[3].intercept_)
print(poly[3].coef_)
1.0715759937747686
[-0.84007261 -1.17039339  1.74706394]
In [22]:
def reg_poly(n):
    psi = PolynomialFeatures(n,include_bias=True).fit_transform
    return LinearRegression().fit(psi(X),y), psi

plt.plot(x,y,'bo')
poly = {} 
psi = {}
for n in [3,4,13,14]:
    poly[n], psi[n] = reg_poly(n)
    plt.plot(xplot,poly[n].predict(psi[n](xplot)))
plt.axis([-1, 1, -1.5, 1.5])
plt.legend(['données','n = 3', 'n = 4', 'n = 13', 'n = 14'])
plt.show()

print(poly[3].intercept_)
print(poly[3].coef_)
1.0715759937747686
[ 0.         -0.84007261 -1.17039339  1.74706394]

Pour n=3, ces coefficients sont similaires à ceux de la fonction g

Pour n=14, il existe un polynôme qui passe par chacun des 15 points (polynôme d'interpolation de Lagrange). Ce polynôme minimise évidemment le risque empirique, c'est donc celui donné par la minimisation du risque empirique

In [34]:
mean_train_error = []
mean_test_error = []
n_max = 14 
for n in range(1,n_max+1):
    f, psi = reg_poly(n)
    mean_train_error.append(sum((f.predict(psi(X))-y)**2)/m)
    mean_test_error.append(sum((f.predict(psi(X_test))-y_test)**2)/m_test)

plt.plot(range(1,n_max+1),mean_train_error)
plt.plot(range(1,n_max+1),mean_test_error)
plt.axis([0, n_max, 0, .2])
plt.show()

Plus n est élevé, plus les polyômes obtenus sont complexes et plus l'erreur d'apprentissage est faible.

L'erreur de test décroît d'abord. Puis, lorsque n grand, il croît: il y a alors sur-apprentissage. La grande complexité des polynôme permet de mieux coller aux points d'apprentissage, mais ne permet pas une bonne généralisation à de nouveaux points.

Q11.

In [35]:
from sklearn.linear_model import Lasso

def reg_poly_lasso(n, alpha=1):
    psi = PolynomialFeatures(n,include_bias=False).fit_transform
    return Lasso(alpha).fit(psi(X),y), psi
In [36]:
for alpha in [10**a for a in range(-6,1)]:
    f, psi = reg_poly_lasso(14,alpha)
    plt.plot(x,y,'bo')
    plt.plot(xplot,f.predict(psi(xplot)))
    plt.axis([-1, 1, -1.5, 1.5])
    print('Coefficients:',f.coef_)
plt.show()
Coefficients: [-0.64803503 -1.19324628  0.93763319  1.07099377 -0.02410153 -0.52579166
  0.38253428 -0.7655917   0.61660386 -0.58977517  0.61748816 -0.3829227
  0.52207339 -0.22135245]
Coefficients: [-0.63990803 -1.1357819   0.82663399  0.90755527  0.24208361 -0.48828249
  0.39648116 -0.80754577  0.58080969 -0.52922576  0.43612767 -0.14400592
  0.15073703  0.        ]
Coefficients: [-0.62143234 -0.83110795  0.5132018  -0.          1.29719959 -0.3671598
  0.         -0.19744133  0.         -0.          0.         -0.
  0.         -0.        ]
Coefficients: [-0.61734245 -0.89991996  0.87598964 -0.16127648  0.61289367 -0.
  0.         -0.          0.         -0.          0.         -0.
  0.         -0.        ]
Coefficients: [-0.16960355 -0.65594633  0.         -0.          0.53416889 -0.
  0.          0.          0.          0.          0.          0.
  0.          0.        ]
Coefficients: [-0. -0. -0. -0. -0. -0.  0. -0.  0. -0.  0. -0.  0. -0.]
Coefficients: [-0. -0. -0. -0. -0. -0.  0. -0.  0. -0.  0. -0.  0. -0.]
/Users/Faugon/opt/anaconda3/envs/machine-learning/lib/python3.6/site-packages/sklearn/linear_model/_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.01182704979202215, tolerance: 0.000105576220851152
  positive)
/Users/Faugon/opt/anaconda3/envs/machine-learning/lib/python3.6/site-packages/sklearn/linear_model/_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.005411478374081265, tolerance: 0.000105576220851152
  positive)
/Users/Faugon/opt/anaconda3/envs/machine-learning/lib/python3.6/site-packages/sklearn/linear_model/_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.0007780023463562109, tolerance: 0.000105576220851152
  positive)

Pour des valeurs de alpha assez grands, l'algorithme produit des polynômes ayant beaucoup de coefficients nuls et de faible degré. Cela limite la complexité des polynômes et donc le sur-apprentissage.

Q12.

In [37]:
mean_train_error = []
mean_test_error = []
nonzero_coefficients = []
degree = []
alpha_range = [10**a for a in range(-6,1)]

for alpha in alpha_range:
    f, psi = reg_poly_lasso(14,alpha)
    mean_train_error.append(sum((f.predict(psi(X))-y)**2)/m)
    mean_test_error.append(sum((f.predict(psi(X_test))-y_test)**2)/m_test)

plt.plot(np.log10(alpha_range),mean_train_error)
plt.plot(np.log10(alpha_range),mean_test_error)
plt.axis([-6,0,0,0.5])
plt.show()
/Users/Faugon/opt/anaconda3/envs/machine-learning/lib/python3.6/site-packages/sklearn/linear_model/_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.01182704979202215, tolerance: 0.000105576220851152
  positive)
/Users/Faugon/opt/anaconda3/envs/machine-learning/lib/python3.6/site-packages/sklearn/linear_model/_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.005411478374081265, tolerance: 0.000105576220851152
  positive)
/Users/Faugon/opt/anaconda3/envs/machine-learning/lib/python3.6/site-packages/sklearn/linear_model/_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.0007780023463562109, tolerance: 0.000105576220851152
  positive)

Le paramètre alpha joue un rôle similaire à n dans la question 10 (mais en sens inverse).

Lorsque alpha décroît, les polynômes obtenus sont de plus en plus complexes, et l'erreur d'apprentissage diminue. Mais quand alpha est assez petit, l'erreur de test augmente à nouveau: il y a alors sur-apprentissage.

Partie 2 : K-Nearest Neighbors / K-plus proche voisins

In [36]:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

iris = sns.load_dataset("iris")
iris.head()
Out[36]:
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
In [37]:
sns.set()
sns.pairplot(iris, hue="species");
plt.show()

Q13.

Les deux variables explicatives qui semblent le mieux séparer les espèces sont sepal_with et petal_length

In [49]:
X = iris.values[:,1:3]
y = iris.values[:,4]
In [50]:
X_train = X[:90]
y_train = y[:90]
X_test = X[90:]
y_test = y[90:]

Q14.

Les données X et y n'étaient pas mélangées: les espèces sont regroupées. En particulier, l'échantillon de test ne contient quasiment que des espèces virginica, alors que l'échantillon d'apprentissage ne contient aucune espèce virginica. Cela fausserait donc et l'apprentissage et le test. Il faut donc au préalable mélanger le jeu de données.

In [51]:
from sklearn.utils import shuffle
X, y = shuffle(X,y)

X_train = X[:90]
y_train = y[:90]
X_test = X[90:]
y_test = y[90:]
In [52]:
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
print(knn.score(X_train,y_train))
print(knn.score(X_test,y_test))
0.9666666666666667
0.9166666666666666

Q15.

Le score empirique mesure la proportion de bonnes prédictions, alors que le risque empirique mesure la proportion de mauvaises prédictions (lorsqu la fonction de perte considérée est la perte 0-1). Donc: score = 1 - risque empirique

Q16.

In [53]:
knn = {}
score_train = []
score_test = []

k_range = range(1,21)

for k in k_range:
    knn[k] =  KNeighborsClassifier(n_neighbors=k)
    knn[k].fit(X_train,y_train)
    score_train.append(knn[k].score(X_train,y_train))
    score_test.append(knn[k].score(X_test,y_test))

plt.plot(list(k_range),score_train)
plt.plot(list(k_range),score_test)
plt.show()

La valeur k=6 semble ici donner le meilleur score de test.

Q17.

In [55]:
def best_knn_score(X,y):
    k_range=range(1,21)
    score_test = []
    X, y = shuffle(X,y)
    X_train = X[:90]
    y_train = y[:90]
    X_test = X[90:]
    y_test = y[90:]
    for k in k_range:
        knn =  KNeighborsClassifier(n_neighbors=k)
        knn.fit(X_train,y_train)
        score_test.append(knn.score(X_test,y_test))
    return max(score_test)

print(best_knn_score(X,y))
print(best_knn_score(X,y))
print(best_knn_score(X,y))
0.95
0.9833333333333333
0.9166666666666666

Le résultat est différent à chaque fois. Cela est dû à au mélange aléatoire des données par shuffle(X,y).

Q18.

In [56]:
def best_knn_score_avg(X,y):
    scores = []
    for i in range(100):
        scores.append(best_knn_score(X,y))
    return np.mean(scores)

print(best_knn_score_avg(X,y))
0.9598333333333333

Q19.

In [57]:
X_ = X.copy()
X_[:,0] = X_[:,0]/100

print(best_knn_score_avg(X_,y))
0.9606666666666667

Le score reste similaire

In [58]:
X_ = X.copy()
X_[:,1] = X_[:,1]/100

print(best_knn_score_avg(X_,y))
0.8293333333333333

Le score est cette fois-ci détérioré.

Q20.

In [59]:
from sklearn import preprocessing
X_scaled = preprocessing.scale(X)

print(best_knn_score_avg(X_scaled,y))
0.9398333333333335

En l'occurence, cela n'améliore pas le score, il est même légèrement détérioré.

Q21.

In [60]:
X = iris.values[:,0:4]
y = iris.values[:,4]
print(best_knn_score_avg(X,y))
0.9806666666666666

Le score est meilleur.