Nous avons implémenté un auto-encodeur et une analyse des composants principaux comme méthode de compression de dimension. En tant que manuels, ["Deep Learning"](https://www.amazon.co.jp/ Deep Learning-Machine Learning Professional Series-Okaya-Takayuki / dp / 4061529021) et ["First Pattern Recognition"](https: / /www.amazon.co.jp/ La première reconnaissance de formes-Hirai-Yuzo / dp / 4627849710) a été utilisée.
Structure de cet article
** auto encoder ** est un réseau neuronal qui utilise l'entrée comme données d'entraînement et acquiert des fonctionnalités qui représentent bien les données. Il est classé comme apprentissage non supervisé qui n'utilise pas les données des enseignants et est utilisé pour le pré-apprentissage des réseaux de neurones et la détermination des valeurs initiales. Maintenant, passons à l'explication de l'auto-encodeur. Considérez un réseau à trois couches comme indiqué dans la figure ci-dessous. La fonction d'activation est la fonction de mappage constante $ g (a) = a $.
La valeur de l'unité dans la couche intermédiaire $ z_j $ et la valeur de l'unité dans la couche de sortie $ y_k $ sont obtenues en propageant l'entrée vers l'avant.
\begin{align}
& z_j = \sum_i w_{ji} x_i \\
& \\
& y_k = \sum_j \tilde w_{kj} z_j
\end{align}
Le vecteur avec les valeurs des unités dans la couche du milieu est $ \ boldsymbol z $, et le vecteur avec les valeurs des unités dans la couche de sortie est $ \ boldsymbol y $, Si la matrice avec $ w_ {ji} $ comme composant $ (\ j, i ) $ est $ \ boldsymbol W $, elle peut être exprimée comme suit.
\begin{align}
& \boldsymbol z = \boldsymbol W \boldsymbol x \\
& \\
& \boldsymbol y = \boldsymbol{\tilde W} \boldsymbol z
\end{align}
La conversion vers la couche intermédiaire $ \ boldsymbol z $ est appelée ** encoding **, et la conversion vers la couche de sortie $ \ boldsymbol y $ est appelée ** déchiffrement **. L'auto-encodeur entraîne le $ \ boldsymbol y $ déchiffré pour qu'il soit proche de l'entrée $ \ boldsymbol x $. Si le nombre d'unités dans la couche intermédiaire est inférieur à la dimension d'entrée et que l'entrée peut être reproduite, la dimension peut être compressée. Pour une méthode d'apprentissage détaillée, reportez-vous à "Implémentation d'un réseau neuronal avec python". Puisque la fonction d'activation est une fonction de mappage constante, la valeur différentielle est toujours $ 1 $, ce qui est facile à calculer.
** L'analyse en composantes principales (ACP) ** est une transformation linéaire des données d'apprentissage $ \ boldsymbol x_i = (x_ {i1}, ..., x_ {id}) ^ T $ dans le sens de la dispersion maximale. C'est la méthode à trouver. Une matrice de données constituée de $ N $ data est $ \ boldsymbol X = (\ boldsymbol x_1, ..., \ boldsymbol x_N) ^ T $, et un vecteur moyen est $ \ boldsymbol {\ bar x} = (\ bar x_1, ..., \ bar x_d) ^ T $. La matrice de données $ \ boldsymbol {\ bar X} $, qui est la matrice de données moins le vecteur moyen, et la matrice de covariance $ \ boldsymbol \ Sigma $ sont les suivantes.
\begin{align}
& \boldsymbol{\bar X} = (\boldsymbol x_1 - \boldsymbol{\bar x}, ..., \boldsymbol x_N - \boldsymbol{\bar x})^T \\
& \\
& \boldsymbol \Sigma = Var\bigl\{\boldsymbol{\bar X}\bigr\} = \frac{1}{N} \boldsymbol{\bar X}^T \boldsymbol{\bar X}
\end{align}
Si la matrice de données $ \ boldsymbol {\ bar X} $ est convertie linéairement par un certain vecteur de coefficient $ \ boldsymbol a_j $ et $ \ boldsymbol s_j $ est utilisé, la distribution des données converties est la suivante.
\begin{align}
& \boldsymbol s_j = (s_{1j}, ..., s_{Nj})^T \\
& \\
& Var\bigl\{\boldsymbol s_j \bigr\} \varpropto \boldsymbol s_j^T \boldsymbol s_j = \bigl(\boldsymbol{\bar X} \boldsymbol a_j \bigr)^T \boldsymbol{\bar X} \boldsymbol a_j = \boldsymbol a_j^T \boldsymbol{\bar X}^T \boldsymbol{\bar X} \boldsymbol a_j \varpropto \boldsymbol a_j^T Var\bigl\{\boldsymbol{\bar X}\bigr\} \boldsymbol a_j
\end{align}
Le vecteur de projection $ \ boldsymbol a_j $ qui maximise cette variance est obtenu en maximisant la fonction de Lagrange avec la norme contrainte à $ 1 $.
\begin{align}
& E(\boldsymbol a_j) = \boldsymbol a_j^T Var\bigl\{\boldsymbol{\bar X}\bigr\} \boldsymbol a_j - \lambda (\boldsymbol a_j^T \boldsymbol a_j - 1) \\
& \\
& \frac{\partial E(\boldsymbol a_j)}{\partial \boldsymbol a_j} = 2 Var\bigl\{\boldsymbol{\bar X}\bigr\} \boldsymbol a_j - 2 \lambda \boldsymbol a_j = 0 \\
& \\
& Var\bigl\{\boldsymbol{\bar X}\bigr\} \boldsymbol a_j = \lambda \boldsymbol a_j \tag{*}
\end{align}
L'équation ($ \ ast $) montre qu'en résolvant le problème des valeurs propres pour la matrice de covariance des données d'origine, le vecteur de projection $ \ boldsymbol a_j $ qui maximise la variance peut être obtenu. Soit la valeur propre obtenue en résolvant l'équation ($ \ ast $) $ \ lambda_1 \ geq ... \ geq \ lambda_d $, et le vecteur propre correspondant $ \ boldsymbol a_1, ..., \ boldsymbol a_d $. Puisque la matrice de covariance est une matrice symétrique réelle, les vecteurs propres sont orthogonaux les uns aux autres.
\boldsymbol a_i^T \boldsymbol a_j = \delta_{ij} = \begin{cases}
1 \ ( \ i = j \ ) \\
\\
0 \ ( \ i \ne j \ )
\end{cases}
La quantité caractéristique transformée linéairement par le vecteur propre correspondant à la valeur propre maximale est la composante principale $ 1 $, La quantité de caractéristiques transformée linéairement par le vecteur propre correspondant à la valeur propre $ k $ th est appelée la composante principale $ k $. Le montant de dispersion totale $ V_ {total} $, le taux de cotisation de la composante principale $ k $ $ c_k $ et le taux de cotisation cumulatif $ r_k $ jusqu'à la composante principale $ k $ sont les suivants.
\begin{align}
& V_{total} = \sum_{i = 1}^d \lambda_i \\
& \\
& c_k = \frac{\lambda_k}{V_{total}} \\
& \\
& r_k = \frac{\sum_{i = 1}^k \lambda_i}{V_{total}}
\end{align}
Sélectionnez $ D_y $ vecteurs propres de $ \ boldsymbol \ Sigma $ dans l'ordre décroissant des valeurs propres, et laissez la matrice qui les stocke en tant que vecteurs de ligne être $ \ boldsymbol U_ {D_y} $. Si le nombre de dimensions des données d'entrée est $ D_x $, les dimensions seront compressées lorsque $ D_y <D_x $. $ \ boldsymbol U_ {D_y} $ et $ \ boldsymbol {\ bar x} $ sont les solutions au problème de minimisation suivant $ (\ boldsymbol \ Gamma, \ boldsymbol \ xi) = (\ boldsymbol U_ {D_y}, \ boldsymbol C'est {\ bar x}) $.
\min_{\boldsymbol \Gamma, \boldsymbol \xi} \sum_{n = 1}^N \bigl\| \ (\boldsymbol x_n - \boldsymbol \xi) - \boldsymbol \Gamma^T \boldsymbol \Gamma(\boldsymbol x_n - \boldsymbol \xi) \ \bigr\|^2 \tag{**}
Dans l'analyse en composantes principales, chaque élément de données dans l'espace dimensionnel $ D_x $ peut être interprété comme donnant le sous-espace dimensionnel $ D_y $ qui représente le mieux la distance la moins carrée.
$ \ Boldsymbol W = \ boldsymbol \ Gamma, \ boldsymbol b = - \ boldsymbol \ Gamma \ boldsymbol \ xi, \ boldsymbol {\ tilde W} = \ boldsymbol \ Gamma ^ T, \ Si boldsymbol {\ tilte b} = \ boldsymbol \ xi
Je l'ai implémenté comme suit. L'auto-encodeur et l'analyse des composants principaux sont répertoriés séparément.
auto encoder
La dimension de compression est traitée comme 50 $. Avec un taux d'apprentissage de $ \ varepsilon = 0,00001 $, nous tournons 10000 $ à l'époque.
auto_encoder.py
import numpy
class AutoEncoder:
def __init__(self, n_input, n_hidden):
self.hidden_weight = numpy.random.randn(n_hidden, n_input + 1)
self.output_weight = numpy.random.randn(n_input, n_hidden + 1)
def fit(self, X, epsilon, epoch):
self.error = numpy.zeros(epoch)
N = X.shape[0]
for epo in range(epoch):
print u'epoch: %d' % epo
for x in X:
self.__update_weight(x, epsilon)
self.error[epo] = self.__calc_error(X)
def encode(self, X):
Z = numpy.zeros((X.shape[0], self.hidden_weight.shape[0]))
Y = numpy.zeros(X.shape)
for (i, x) in enumerate(X):
z, y = self.__forward(x)
Z[i, :] = z
Y[i, :] = y
return (Z, Y)
def __forward(self, x):
z = self.hidden_weight.dot(numpy.hstack((1, x)))
y = self.output_weight.dot(numpy.hstack((1, z)))
return (z, y)
def __update_weight(self, x, epsilon):
z, y = self.__forward(x)
# update output_weight
output_delta = y - x
self.output_weight -= epsilon * output_delta.reshape(-1, 1) * numpy.hstack((1, z))
# update hidden_weight
hidden_delta = self.output_weight[:, 1:].T.dot(output_delta)
self.hidden_weight -= epsilon * hidden_delta.reshape(-1, 1) * numpy.hstack((1, x))
def __calc_error(self, X):
N = X.shape[0]
err = 0.0
for x in X:
_, y = self.__forward(x)
err += (y - x).dot(y - x) / 2.0
return err
main.py
import numpy
import cv2
from sklearn.datasets import fetch_mldata
from auto_encoder import AutoEncoder
if __name__ == '__main__':
print 'read data...'
mnist = fetch_mldata('MNIST original', data_home = '.')
_max = mnist.data[:, :-1].max()
X = mnist.data[:, :-1] * 1.0 / _max
input_size = X.shape[1]
hidden_size = 50
epsilon = 0.00001
epoch = 10000
stride = 50
print 'auto encoder init...'
auto = AutoEncoder(input_size, hidden_size)
print 'train...'
auto.fit(X[::stride], epsilon, epoch)
print 'encode...'
Z, Y = auto.encode(X[::stride])
for (i, y) in enumerate(Y * _max):
cv2.imwrite('result/%04d.png' % i, y.reshape(28, 28))
PCA
La dimension de compression est traitée comme 50 $. Le vecteur de projection et l'image décodée sont enregistrés.
pca.py
import numpy
class PCA:
def __init__(self, n_components):
self.n_components = n_components
def fit(self, X):
self.bar = self.__bar(X)
self.cov = self.__cov()
self.lamda, self.A, self.ccr = self.__solve_eigen_porblem()
self.S = self.__transformation()
def decode(self):
return self.S.dot(self.A.T)
def __bar(self, X):
return X - X.mean(axis = 0)
def __cov(self):
return numpy.cov(self.bar, rowvar = 0)
def __solve_eigen_porblem(self):
_lamda, _A = numpy.linalg.eig(self.cov)
lamda = _lamda[:self.n_components]
A = _A[:, :self.n_components]
ccr = lamda.sum() / _lamda.sum()
return (lamda, A, ccr)
def __transformation(self):
return self.bar.dot(self.A)
main.py
import cv2
from matplotlib import pyplot
from sklearn.datasets import fetch_mldata
import os
from pca import PCA
if __name__ == '__main__':
print 'read data...'
mnist = fetch_mldata('MNIST original', data_home = '.')
X = mnist.data
n_components = 50
p_dir = 'projection/'
d_dir = 'decoded/'
print 'train...'
pca = PCA(n_components)
pca.fit(X)
print 'cumulative contribution ratio: %f' % pca.ccr
print 'decode...'
Xhat = pca.decode()
print 'save projection vector...'
if not os.path.exists(p_dir):
os.mkdir(p_dir)
for (i, a) in enumerate(pca.A.T):
fig, ax = pyplot.subplots()
heatmap = ax.pcolor(a.reshape(28, 28)[:, ::-1], cmap = pyplot.cm.RdYlBu)
pyplot.savefig(p_dir + '%04d.png' % i, dpi = 25)
pyplot.clf()
print 'save decoded images...'
if not os.path.exists(d_dir):
os.mkdir(d_dir)
for (i, xhat) in enumerate(Xhat[::50]):
cv2.imwrite(d_dir + '%04d.png' % i, xhat.reshape(28, 28))
n_components = 30
pca = PCA(n_components)
pca.fit(X)
Xhat = pca.A.dot(pca.S.T)
for (i, xhat) in enumerate(Xhat.T):
cv2.imwrite('result/%04d.png' % i, xhat.reshape(28, 28))
Les résultats suivants ont été obtenus.
auto encoder
Image déchiffrée
Erreur
J'ai choisi une belle image pour l'image décodée. La quantité d'entités compressée à une dimension de 50 $ peut être décodée en une dimension de 784 $. Dans le graphique d'erreur, l'axe horizontal correspond au nombre d'époques et l'axe vertical correspond à la valeur d'erreur. La différence entre les chiffres de l'erreur est trop grande, le logarithme est donc pris. Ce n'est pas une erreur exacte, mais vous pouvez voir qu'elle diminue de manière monotone. ..
PCA
Vecteur de projection visualisé
Image déchiffrée
Pour le vecteur de projection, 10 vecteurs correspondants ont été sélectionnés par ordre décroissant de valeur propre. Le vecteur de projection en haut à gauche se projette sur la première composante principale avec la plus grande dispersion. L'image décodée donne de bons résultats ainsi que l'auto-encodeur.
Nous avons pu compresser les dimensions par auto-encodeur et analyse des composants principaux.