Pas à pas sur la théorie, l'implémentation en python et l'analyse à l'aide de scikit-learn sur l'algorithme précédemment repris dans "Classification of Machine Learning" J'étudierai avec. Je l'écris pour un apprentissage personnel, alors j'aimerais que vous oubliez toute erreur.
Cette fois, en résumé de l'édition de régression linéaire, je résumerai l'approximation linéaire en utilisant le noyau gaussien, et les régularisations L1 (Lasso) et L2 (Ridge) qui suppriment le surentraînement et lui.
Les sites suivants ont été mentionnés cette fois. Merci beaucoup.
Plus tôt, quand j'ai écrit sur Généralisation de la régression linéaire, j'ai écrit que "la fonction de base peut être n'importe quoi". Même dans la régression proprement dite, il n'est pas toujours possible de faire une approximation avec une ligne droite, et il est également nécessaire de faire une approximation telle qu'une fonction polymorphe ou une fonction triangulaire.
Par exemple, considérons $ y = -xsin (x) + \ epsilon $ avec du bruit ajouté à $ y = -xsin (x) $.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
fig, ax = plt.subplots()
t = np.linspace(0, 2*np.pi, 100)
y1 = - t * np.sin(t)
n=40
np.random.seed(seed=2020)
x = 2*np.pi * np.random.rand(n)
y2 = - x * np.sin(x) + 0.4 * np.random.normal(loc=0.0, scale=1.0, size=n)
ax.scatter(x, y2, color='blue', label="sample(with noise)")
ax.plot(t, y1, color='green', label="-x * sin(x)")
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=11)
plt.show()
Les points bleus sont des échantillons avec du bruit et les lignes vertes pleines indiquent les fonctions attendues. Le bruit a été ajouté avec des nombres aléatoires, mais la valeur de départ des nombres aléatoires a été fixée de manière à ce que le même résultat soit obtenu à chaque fois.
Cette fois, il est normal de connaître la fonction objectif, mais nous devons estimer à partir de l'état où nous ne savons pas si l'échantillon est un polynôme ou une autre fonction.
Le noyau gaussien est défini comme suit:
k(\boldsymbol{x}, \boldsymbol{x'})=\exp(-\beta||\boldsymbol{x}-\boldsymbol{x'}||^2)
Cette fonction a la forme suivante, et en déplaçant ou en modifiant la taille du centre de cette fonction, elle revient à la chaîne de données cible.
Maintenant la fonction souhaitée
f({\bf x})=\sum_{i=1}^{N} \alpha k({\bf x}^{(i)}, {\bf x}')
Et. Soit la valeur de l'échantillon $ \ hat {y} $
\hat{y} =
\left(\begin{matrix}
k({\bf x}^{(1)}, {\bf x}) & k({\bf x}^{(2)}, {\bf x}) & \cdots & k({\bf x}^{(N)}, {\bf x})
\end{matrix}\right)
\left(\begin{matrix}
\alpha_0 \\
\alpha_1 \\
\vdots \\
\alpha_N
\end{matrix}\right)
Suivant,
{\bf y} =
\left(\begin{matrix}
y^{(1)} \\
y^{(2)} \\
\vdots \\
y^{(N)}
\end{matrix}\right)
,\;
K=\left(\begin{matrix}
k({\bf x}^{(1)}, {\bf x}^{(1)}) & k({\bf x}^{(2)}, {\bf x}^{(1)}) & \cdots & k({\bf x}^{(N)}, {\bf x}^{(1)}) \\
k({\bf x}^{(1)}, {\bf x}^{(2)}) & k({\bf x}^{(2)}, {\bf x}^{(2)}) & & \vdots \\
\vdots & & \ddots & \vdots \\
k({\bf x}^{(1)}, {\bf x}^{(N)}) & \cdots & \cdots & k({\bf x}^{(N)}, {\bf x}^{(N)})
\end{matrix}\right)
,\;
{\bf \alpha}
=
\left(\begin{matrix}
\alpha^{(1)} \\
\alpha^{(2)} \\
\vdots \\
\alpha^{(N)}
\end{matrix}\right)
Il sera développé comme ça (je suis désolé pour cette étrange copie). Ici, $ K $ est appelé ** matrice gramme **. L'erreur carrée $ L $ entre $ f ({\ bf x}) $ et $ \ hat {y} $ est
L=({\bf y}- K {\bf \alpha})^{\mathrm{T}}({\bf y}- K {\bf \alpha})
Et en résolvant ceci pour $$ alpha
J'ai implémenté la classe KernelRegression en utilisant la formule ci-dessus telle quelle.
class KernelRegression: #Pas de régularisation
def __init__(self, beta=1):
self.alpha = np.array([])
self.beta = beta
def _kernel(self, xi, xj):
return np.exp(- self.beta * np.sum((xi - xj)**2))
def gram_matrix(self, X):
N = X.shape[0]
K = np.zeros((N, N))
for i in range(N):
for j in range(N):
K[i][j] = self._kernel(X[i], X[j])
K[j][i] = K[i][j]
return K
def fit(self, X, Y):
K = self.gram_matrix(X)
self.alpha = np.linalg.inv(K)@Y
def predict(self, X, x):
Y = 0
for i in range(len(X)):
Y += self.alpha[i] * self._kernel(X[i], x)
return Y
Faisons une approximation en utilisant le premier échantillon.
model = KernelRegression(beta=20)
model.fit(x, y2)
xp = np.linspace(0, 2*np.pi, 100)
yp = np.zeros(len(xp))
for i in range(len(xp)):
yp[i] = model.predict(x, xp[i])
fig, ax = plt.subplots()
plt.ylim(-3,6)
ax.plot(xp, yp, color='purple', label="estimate curve")
ax.scatter(x, y2, color='blue', label="sample(with noise)")
ax.plot(t, y1, color='green', label="-x*sin(x)")
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=11)
plt.show()
Il semble qu'il passe sur l'échantillon donné, mais c'est compliqué. C'est complètement différent de la courbe verte.
Comme indiqué ci-dessus, le fait que le résultat d'apprentissage soit fidèle aux données de l'enseignant mais éloigné de la réalité est appelé ** surapprentissage **. Est-ce similaire à un test de mathématiques que vous pouvez résoudre si vous rencontrez le même problème qu'un manuel, mais que vous ne pouvez pas le résoudre dès que vous le arrangez un peu?
Les modèles surentraînés sont également qualifiés de médiocres ** performances de généralisation **. La précision de la prédiction est faible. Pour éviter cela, nous avons besoin du concept de ** régularisation **.
Dans l'exemple ci-dessus, le paramètre $ \ bf {\ alpha} $ est devenu extrêmement grand, résultant en une courbe violemment violente pour forcer les points d'un échantillon donné à passer. Après cela, il semble que cela se produise souvent lorsque le nombre de données est extrêmement petit ou qu'il y a de nombreuses variables.
Pour éviter cela, lors du calcul de $ \ alpha $, cela peut être réalisé en limitant la plage de $ \ alpha $ en ajoutant ** terme de régularisation ** à l'erreur carrée de la régression du noyau. La régularisation L1 (Lasso) et la régularisation L2 (Ridge) sont célèbres dans l'apprentissage automatique. Le mélange des deux s'appelle la régularisation Elastic Net.
Commencez par L2 au lieu de L1.
Plus tôt
L=({\bf y}- K {\bf \alpha})^{\mathrm{T}}({\bf y}- K {\bf \alpha})+\lambda||{\bf \alpha}||_2^2
Optimiser. Le plus grand $ \ lambda $, le plus restreint $ \ alpha $, résultant en une courbe plus douce (?). Si cela est partiellement différencié par $ \ alpha $ et mis à 0, et résolu pour $ \ alpha $,
{\bf \alpha} = (K+\lambda I_N)^{-1}{\bf y}
Ce sera. Ceci est facile à changer car tout ce que vous avez à faire est d'ajouter la matrice unitaire à la matrice gramme et de calculer la matrice inverse.
J'ai changé la partie de la classe KernelRegression qui demande $ \ alpha $. Le reste est presque le même.
class KernelRegression: #Régularisation L2
def __init__(self, beta=1, lam=0.5):
self.alpha = np.array([])
self.beta = beta
self.lam = lam
def _kernel(self, xi, xj):
return np.exp(- self.beta * np.sum((xi - xj)**2))
def gram_matrix(self, X):
N = X.shape[0]
K = np.zeros((N, N))
for i in range(N):
for j in range(N):
K[i][j] = self._kernel(X[i], X[j])
K[j][i] = K[i][j]
return K
def fit(self, X, Y):
K = self.gram_matrix(X)
self.alpha = np.linalg.inv(K + self.lam * np.eye(X.shape[0]))@Y
def predict(self, X, x):
Y = 0
for i in range(len(X)):
Y += self.alpha[i] * self._kernel(X[i], x)
return Y
Essayez de faire une approximation en utilisant le même échantillon que s'il n'était pas régularisé. Les valeurs de $ \ beta $ et $ \ lambda $ ont été décidées de manière appropriée.
model = KernelRegression(beta=0.3, lam=0.1)
model.fit(x, y2)
xp = np.linspace(0, 2*np.pi, 100)
yp = np.zeros(len(xp))
for i in range(len(xp)):
yp[i] = model.predict(x, xp[i])
fig, ax = plt.subplots()
plt.ylim(-3,6)
ax.plot(xp, yp, color='purple', label="estimate curve")
ax.scatter(x, y2, color='blue', label="sample(with noise)")
ax.plot(t, y1, color='green', label="-x*sin(x)")
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=11)
plt.show()
Cette fois, c'était un match parfait. En fait, la forme change en dehors de la plage affichée, mais vous pouvez voir qu'une courbe d'approximation assez proche peut être dessinée dans une plage spécifique.
$ \ Beta $ et $ \ lambda $ sont appelés ** hyperparamètres **, et vous devez en fait les optimiser en les changeant petit à petit pour minimiser la valeur de la fonction de perte.
En régularisation L2, comme terme de régularisation,$\lambda ||{\bf \alpha}||^2_{2}
Trouver une solution est un peu compliqué car le terme de régularisation ne peut pas être différencié dans la régularisation L1. Il semble que scikit-learn soit implémenté par une méthode appelée Coordinate Descent, mais je ne la comprends pas encore, alors j'aimerais utiliser scikit-learn docilement dans cet article.
La régularisation L1 permet à une partie significative du paramètre $ \ alpha $ d'être nulle (obtenant une solution clairsemée), ce qui permet de supprimer les paramètres indésirables.
Je vais l'implémenter docilement avec scicit-learn.
from sklearn.metrics.pairwise import rbf_kernel
from sklearn.linear_model import Lasso
kx = rbf_kernel(x.reshape(-1,1), x.reshape(-1,1))
KX = rbf_kernel(xp.reshape(-1,1), x.reshape(-1,1))
clf = Lasso(alpha=0.01, max_iter=10000)
clf.fit(kx, y2)
yp = clf.predict(KX)
fig, ax = plt.subplots()
plt.ylim(-3,6)
ax.plot(xp, yp, color='purple', label="estimate curve")
ax.scatter(x, y2, color='blue', label="sample(with noise)")
ax.plot(t, y1, color='green', label="-x*sin(x)")
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=11)
plt.show()
print("coef: ", clf.coef_)
coef: [-0. 0. 0. -0. -0.79703436 -0.
-0. -0. 0. -0. -0. 3.35731837
0. -0. -0.77644607 0. -0. 0.
-0. -0.19590705 0. -0. 0. -0.
0. 0. -0. -0. -0. 0.
-0. 0. -0. -0. 0. 0.58052561
0.3688375 0. -0. 1.75380012]
Ceci est également très similaire à la fonction d'origine. Vous pouvez également voir qu'une partie significative du paramètre (coef) est 0.
Elasticnet La régularisation L1 et la régularisation L2 ajoutent un terme de régularisation à la fonction de perte, mais chacune peut être considérée indépendamment et vous pouvez décider laquelle et dans quelle mesure adopter, ce qui s'appelle ** ElasticNet **. Je vais. En fait, l'implémentation scikit-learn a défini les paramètres ElasticNet comme Lasso et Ridge.
Toute fonction peut être sélectionnée comme fonction de base dans la régression linéaire. Puisque le noyau gaussien (non limité à) a un degré de liberté élevé et a tendance à être surappris, il est possible de créer un modèle de régression avec des performances de généralisation plus élevées en le limitant par régularisation L1 ou régularisation L2.
J'ai résumé la série de régression plusieurs fois, mais pour vous aider à comprendre le contenu de la régression dans la feuille de triche scikit-learn N'est-ce pas?
Tsukkomi est le bienvenu car je l'ai résumé de manière appropriée.
Recommended Posts