Il s'agit d'une compilation de ce que l'auteur a écrit au lieu d'un mémo, dont l'auteur n'a pas encore saisi le tableau d'ensemble. Je vais résumer le contenu du tutoriel Pytorch + le contenu examiné en α.
Cette fois, c'est le chapitre 1.
Découvrez l'installation et les couches de base de CNN.
Lorsqu'il est calculé dans une grande dimension telle qu'une matrice 32 × 32 Il est difficile d'imaginer quel type de calcul est effectué J'ai calculé le code réel avec un exemple simple et j'ai essayé d'inclure des formules. Nous visons une compréhension intuitive.
https://pytorch.org/
Sélectionnez votre environnement PC à partir de l'URL ci-dessus et exécutez la commande [Exécuter cette commande].
Sortons la matrice de la même manière que numpy. https://pytorch.org/tutorials/beginner/blitz/tensor_tutorial.html#sphx-glr-beginner-blitz-tensor-tutorial-py
test.py
x = torch.empty(5, 3)
print(x)
#Résultat d'exécution
tensor([[1.9349e-19, 4.5445e+30, 4.7429e+30],
[7.1354e+31, 7.1118e-04, 1.7444e+28],
[7.3909e+22, 4.5828e+30, 3.2483e+33],
[1.9690e-19, 6.8589e+22, 1.3340e+31],
[1.1708e-19, 7.2128e+22, 9.2216e+29]])
Cela ressemble à un engourdissant.
En définissant $ requires_grad $, il semble que le gradient à un certain point puisse être obtenu.
test.py
x = torch.tensor([1.0, 2.0], requires_grad=True)
À titre d'exemple, trouvons le gradient pour la fonction des deux variables suivantes.
f(x,y)=2x + y^2\\
La formule du dégradé est la suivante.
\frac
{\partial f}
{\partial x}
=
2\\
\frac
{\partial f}
{\partial y}
=
2y\\
D'après l'équation ci-dessus, on peut voir que le gradient dans la direction $ x $ est déterminé par 2 et le gradient dans la direction $ y $ est déterminé par 2y. Le gradient au point (x, y) = (1,2) est (2,4) de ce qui suit.
\frac
{\partial f}
{\partial x}
=
2\\
\frac
{\partial f}
{\partial y}
=
2*2=4\\
C'est require_grad qui fait le calcul. Voici ce que j'ai fait avec pytorch:
test.py
from __future__ import print_function
import torch
#Préparation du point de calcul
z = torch.tensor([1.0, 2.0], requires_grad=True)
# f(z)Préparation de
f = z[0]*2 + z[1]**2
#Effectuer la différenciation
f.backward()
print(z.grad)
#Résultat d'exécution
tensor([2., 4.])
https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html Le réseau sera défini dans la section suivante du tutoriel.
test.py
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 1 input image channel, 6 output channels, 3x3 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 6 * 6, 120) # 6*6 from image dimension
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
net.zero_grad()
out.backward(torch.randn(1, 10))
Même si vous le lancez soudainement, ce sera "?", Alors jetons un coup d'œil au contenu un par un.
test.py
class Net(nn.Module):
def __init__(self):
#Définissez chaque couche
def forward(self, x):
#Effectuer le traitement pour chaque couche
En héritant de la classe nn.Module Il semble que vous puissiez définir librement la configuration du réseau.
Définissez chaque couche au moment où la classe est initialisée par init, Appelez la couche définie par forward pour appeler le traitement principal de CNN.
En tant que flux réel, Forward est exécuté lorsque l'instance de la classe définie par vous-même est créée.
Le traitement principal est le contenu de traitement de l'avant. Démontons la source de l'échantillon et examinons chaque couche une par une. (Je vais sauter l'explication de la fonction d'activation)
test.py
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
↓↓↓↓↓↓↓↓↓↓↓↓↓
max_pool2d :Couche de regroupement
Conv2d :Couche pliante
Linear :Couche entièrement connectée
Essayez d'exécuter max_pool2d seul.
test.py
import torch
import torch.nn as nn
import numpy as np
import torch.nn.functional as F
class Test_Pooling(nn.Module):
def __init__(self):
super(Test_Pooling, self).__init__()
def forward(self, x):
print("Before")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
x = F.max_pool2d(x, (2, 2))
print("After")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
return x
net = Test_Pooling()
#contribution
nparr = np.array([1,2,3,4]).astype(np.float32).reshape(2,2)
nparr = np.block([[nparr,nparr],[nparr,nparr]]).reshape(1,1,4,4)
input = torch.from_numpy(nparr).clone()
#production
out = net(input)
#Résultat d'exécution
Before
size : torch.Size([1, 1, 4, 4])
data :
[[[[1. 2. 1. 2.]
[3. 4. 3. 4.]
[1. 2. 1. 2.]
[3. 4. 3. 4.]]]]
After
size : torch.Size([1, 1, 2, 2])
data :
[[[[4. 4.]
[4. 4.]]]]
Les données d'entrée sont les suivantes.
input = \begin{pmatrix}
1 & 2 & 1 & 2\\
3 & 4 & 3 & 4\\
1 & 2 & 1 & 2\\
3 & 4 & 3 & 4\\
\end{pmatrix}\\
L'opération de max_pool2d est dans la plage de (2,2) spécifiée par l'argument. Extrait la valeur maximale et renvoie la valeur sous forme de matrice. S'il est appliqué à la matrice d'entrée ci-dessus, il sera exécuté pour 1, 2, 3 et 4 sous-matrice. En conséquence, (2,2) dans lequel quatre 4 sont alignés est sorti.
La mise en commun a deux objectifs principaux.
1.Réduction de dimension
2.Assurer l'invariance du mouvement et de la rotation
L'un est la réduction des dimensions. Comme vous pouvez le voir, les 16 nombres ont été réduits à 4. Si vous envisagez de traiter des centaines d'images, vous pouvez vous attendre à raccourcir le traitement plusieurs fois à la fois. (Étant donné que la quantité réduite d'informations est perdue, La quantité de mise en commun pouvant être effectuée dépend de l'élément. )
L'autre est immuable. Bien sûr, cela peut se produire si l'image d'entrée est tournée ou mal alignée. La mise en commun semble être quelque chose qui peut garantir dans une certaine mesure, même dans de tels cas.
Par exemple, un tableau de pixels dans une image en niveaux de gris D'une part, $ (0,0,0,0,1,2,3,4,5,1,2) $, L'autre est $ (0,1,2,3,4,5,1,2,3,4,5) $ Considérant le cas où une image montre la même image mais est décalée latéralement.
Si la mise en commun maximale est effectuée dans la plage horizontale longue de (1,12), (5) est affiché dans les deux images, et "les deux affichent la même valeur numérique", c'est-à-dire Il sera possible de trouver des indices sur la fonction quel que soit le mouvement de l'image.
(..., j'ai écrit que j'ai enquêté, mais quel que soit le but de 1, Quelle efficacité peut-on attendre de 2? J'ai l'impression que l'image est un peu peu fiable. (De toute façon, si les points sont dans le désordre))
Organisez l'image du pliage et quel type de calcul est réellement effectué.
Qu'est-ce que le «pliage»? N'ayez pas peur de mal comprendre, en pliant C'est une reconnaissance qu'il est fait pour souligner et extraire la partie caractéristique.
Par exemple, un filtre 5x5 appelé filtre laplacien L'intégration par pliage produit une image avec des bords accentués, comme dans l'image ci-dessus. Les caractéristiques de la partie de bord sont mises en valeur et extraites. (Référence: https://desktop.arcgis.com/ja/arcmap/10.3/manage-data/raster-and-images/convolution-function.htm)
En ce qui concerne le type de calcul effectué en tant que formule de calcul, Il est difficile de comprendre s'il s'agit d'un traitement vocal, mais il est facile de comprendre visuellement s'il s'agit d'une image. (↓)
Dans le cas de convolution de la zone d'image, pour les pixels de l'image cible (matrice bleue), Comme le montre la vidéo ci-dessus, le "noyau" (matrice verte) est appliqué afin de calculer.
En guise de calcul, dans l'exemple de l'image ci-dessus, Appliquez le noyau autour du pixel 1 de (2,2) dans [données d'entrée].
(1*2)+(2*0)+(3*1)+(0*0)+(1*1)+(2*2)+(3*1)+(0*0)+(1*2)=15
Et ajoutez ceux qui sont dans la même position multipliés les uns par les autres. Dans l'exemple illustré, si vous essayez d'installer un filtre 3x3 sans espace, 4x4 [données de sortie] est calculé.
Donc, tout en jouant avec la source basée sur ceux-ci, Jetons un coup d'œil au calcul.
test.py
class Test_Conv(nn.Module):
kernel_filter = None
def __init__(self):
super(Test_Conv, self).__init__()
# self.conv = nn.Conv2d(1, 1, 3)
ksize = 4
self.conv = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=4,
bias=False)
self.kernel_filter = self.conv.weight.data.numpy().reshape(ksize,ksize)
def forward(self, x):
print("Before")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
print("Calc Self Conv")
x_np = x.to('cpu').detach().numpy().copy().reshape(4,4)
calc_conv = 0 ;
for col in range(self.kernel_filter.shape[0]):
for row in range(self.kernel_filter.shape[1]):
calc_conv += self.kernel_filter[row][col] * x_np[row][col]
print("kernel filter :")
print(self.kernel_filter )
print("data : \n",calc_conv)
print("\n")
x = self.conv(x)
print("After")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
return x
net = Test_Conv()
#contribution
nparr = np.array([1,2,3,4]).astype(np.float32).reshape(2,2)
nparr = np.block([[nparr,nparr],[nparr,nparr]]).reshape(1,1,4,4)
input = torch.from_numpy(nparr).clone()
#production
out = net(input)
exit()
Avec un filtre laplacien qui extrait les arêtes, J'appliquais un filtre de noyau fixe.
Cependant, le filtre du noyau généré automatiquement par Conv2d de pytorch est Il semble que la valeur change aléatoirement à chaque exécution. (Il n'a pas été étudié quel type de calcul et quel type d'intention conv2d calcule ce filtre. N'est-il pas possible de régler le filtre intentionnellement? )
À propos, le résultat de sortie est le suivant.
#Résultat d'exécution
Before
size : torch.Size([1, 1, 4, 4])
data :
[[[[1. 2. 1. 2.]
[3. 4. 3. 4.]
[1. 2. 1. 2.]
[3. 4. 3. 4.]]]]
Calc Self Conv
kernel filter :
[[-0.03335193 -0.05553913 0.10690624 -0.0219309 ]
[-0.02052614 0.23662615 -0.07596081 -0.04400161]
[ 0.19031712 -0.06902602 -0.24611491 -0.06604707]
[-0.05149609 -0.08155683 0.06496871 -0.15480098]]
data :
-0.8313058316707611
After
size : torch.Size([1, 1, 1, 1])
data :
[[[[-0.8313058]]]]
[Dara] de [Before] est Des données d'entrée. C'est la procession bleue de l'animation précédente.
Et après]. [dara] est le résultat calculé dans la couche de convolution. Il dit [-0.8313058].
Ensuite, jetons un œil à [Calc Self Conv]. Ici, j'ai essayé le calcul ci-dessus à la main. Puisque kernel_size = 4 a été spécifié lors de la déclaration de Conv2d, Vous pouvez voir que le filtre du noyau, qui est la base du calcul, produit une matrice 4x4. Il s'agit de la matrice verte de l'animation précédente.
Maintenant, comparons le résultat du calcul manuel avec le résultat de conv2d. En regardant [données] dans [Calc Self Conv] et [After], Vous pouvez voir que les chiffres sont presque les mêmes.
Avec une telle image, la couche convolutionnelle est calculée. Notez que cet exemple est assez simple pour simplifier le calcul, donc Je vais creuser un peu plus la prochaine fois.
J'étudierai un peu plus. J'ai en quelque sorte compris quel genre de calcul faisait la convolution. Ensuite, examinons de plus près les paramètres impliqués lors du calcul réel.
Les paramètres suivants sont principalement présents dans conv2d.
test.py
in_channels
out_channels
kernel_size
stride
padding
bias
Pour donner une explication intuitive Regardons le code réel un par un.
in_channels Définissez le nombre de dimensions par données dans in_channels.
data :
[[[[0. 1. 0. 1.]
[0. 0. 0. 0.]
[0. 1. 0. 1.]
[0. 0. 0. 0.]]]]
Prenons les données ci-dessus à titre d'exemple. En termes d'images, ces données sont une image en échelle de gris, Le nombre de canaux (nombre de couleurs) n'est qu'une donnée. Pour ces données, définissez "1". "3" pour les données RVB 3D, Si vous avez une position et RVB comme un groupe de points, entrez "6".
out_channels Le nombre de dimensions des données de sortie est sorti vers out_channels. Seul le nombre de filtres de noyau spécifié ici sera généré. Le nombre spécifié de résultats d'application de filtre sera renvoyé.
En d'autres termes, plus il y a de dimensions ici, plus Les caractéristiques sont extraites par des filtres créés pour le nombre de dimensions, Les éléments présentant diverses caractéristiques peuvent être extraits en tant que quantité.
Par exemple Filtrer pour extraire les caractéristiques des animaux, Des filtres qui extraient les caractéristiques humaines, Un filtre qui extrait les caractéristiques de la tasse, Et ici, vous déclarez le nombre de filtres qui extraient diverses fonctionnalités.
(Si vous n'écoutez que cette explication, plus vous en avez, mieux cela semble. Intuitivement, si vous extrayez les fonctionnalités trop finement, Même si vous pouvez faire des classifications détaillées comme M. A et M. B, Je pense que des problèmes tels que l'incapacité de classer en étant humain sont susceptibles de survenir. (Image sans pratique personnelle))
stride la foulée fait référence à la largeur du mouvement lors de l'écriture d'un filtre. Dans cette image, la foulée est "1" car elle se déplace un par un.
Voyons ce mouvement un peu plus dans le code.
test.py
class Test_Conv2(nn.Module):
kernel_filter = None
def __init__(self):
super(Test_Conv2, self).__init__()
self.conv = nn.Conv2d(
in_channels=1,
out_channels=2,
kernel_size=2,
stride=2,
padding=0,
bias=False)
self.kernel_filter = self.conv.weight.data.numpy()
def forward(self, x):
print("Before")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
print("Calc Self Conv")
print("kernel filter :")
print(self.kernel_filter )
print("\n")
x = self.conv(x)
print("After")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
return x
net = Test_Conv2()
#contribution
nparr = np.array([0,1,0,0]).astype(np.float32).reshape(2,2)
nparr = np.block([[nparr,nparr],[nparr,nparr]]).reshape(1,1,4,4)
input = torch.from_numpy(nparr).clone()
#production
out = net(input)
exit()
#Résultat d'exécution
Before
size : torch.Size([1, 1, 4, 4])
data :
[[[[0. 1. 0. 1.]
[0. 0. 0. 0.]
[0. 1. 0. 1.]
[0. 0. 0. 0.]]]]
Calc Self Conv
kernel filter :
[[[[-0.07809174 -0.39049476]
[-0.00448102 -0.09000683]]]
[[[ 0.03750324 0.12070286]
[-0.06378353 0.22772777]]]]
After
size : torch.Size([1, 2, 2, 2])
data :
[[[[-0.39049476 -0.39049476]
[-0.39049476 -0.39049476]]
[[ 0.12070286 0.12070286]
[ 0.12070286 0.12070286]]]]
J'ai mis la foulée à "2". Puisqu'il se décale de deux, comme un calcul, [Données] sur [Avant] Avec [filtre du noyau] de [Calc Self Conv] Il sera multiplié.
input = \begin{pmatrix}
0 & 1 \\
0 & 0
\end{pmatrix}\\\\
filter = \begin{pmatrix}
-0.07809174 & -0.39049476 \\
-0.00448102 & -0.09000683
\end{pmatrix}\\
Est-ce vraiment par deux? Vérifiez le résultat de l'exécution.
Les données d'entrée sont une matrice dans laquelle la sous-matrice [d'entrée] ci-dessus est alignée. Si vous essayez d'appliquer le [filtre] de (2,2) à ces données d'entrée, Il sera multiplié 4 fois pour qu'il corresponde exactement à la sous-matrice de [input].
output = \begin{pmatrix}
-0.39049476 & -0.39049476 \\
-0.39049476 & -0.39049476
\end{pmatrix}\\
Donc, Comme la convolution de la sous-matrice [d'entrée] et de la matrice [filtre] est effectuée 4 fois, La valeur numérique du filtre où la sous-matrice de [entrée] est la seule [1] est sortie quatre fois. Par conséquent, la convolution est calculée comme un résultat comme [sortie].
De ce qui précède, Je pense que vous pouvez voir comment les pixels sont décalés de deux et le pliage est effectué.
padding Je n'ai rien dit de particulier, mais en parcourant le filtre, J'ai expliqué en supposant que le filtre est déplacé dans la plage qui correspond à l'image.
Dans cette image, près du centre du pixel dans l'image bleue, Vous filtrez autour de (1,1) (1,2) (2,1) (2,2).
Lorsque j'essaye d'appliquer le filtre à la position supérieure gauche de (0,0), On peut s'attendre à ce que la partie supérieure ne se chevauche pas et qu'il ne sera pas possible de calculer.
La partie centrale de l'image est également filtrée Le bord de l'image n'est pas filtré, En d'autres termes, il s'agit d'un état dans lequel les caractéristiques de la partie de bord n'ont pas été extraites.
Ainsi, en ajoutant une colonne / ligne virtuelle de [0] aux deux extrémités et en calculant, L'idée du «remplissage» est de calculer également la partie de bord.
bias Il ajoute simplement des valeurs à tous les éléments de la sortie.
Est-il utilisé pour déterminer la supériorité ou l'infériorité d'importance de chaque filtre?
Ou inversement, quelque chose qui ne peut ramasser que de petites fonctionnalités Y a-t-il quelque chose comme l'agrandir pour le traiter comme n'importe quelle autre fonctionnalité?
La dimension du résultat de sortie de la convolution change en fonction du paramètre. Voyons comment les dimensions changent en fonction des paramètres de conv2d.
test.py
in_channels
out_channels
kernel_size
stride
padding
bias
Exemple 1 Données d'entrée = (dimension = 1, (4 × 4))) / taille du noyau = 2 / padding = 0
in_channels=1\\
out_channels=1\\
kernel_size=2\\
stride=2\\
padding=0\\
bias=0\\
\\
\\
input = \begin{pmatrix}
0 & 1 & 0 & 1\\
0 & 0 & 0 & 0\\
0 & 1 & 0 & 1\\
0 & 0 & 0 & 0
\end{pmatrix}\\\\
filter = \begin{pmatrix}
-0.07809174 & -0.39049476 \\
-0.00448102 & -0.09000683
\end{pmatrix}\\
Dans ce cas, le filtre de (2,2) prend les données d'entrée de (4,4). Déplacez-vous deux par deux et avancez exactement. Le résultat de sortie est 2x2.
Exemple 2 Données d'entrée = (dimension = 1, (4 × 4))) / taille du noyau = 3 / padding = 0
in_channels=1\\
out_channels=1\\
kernel_size=3\\
stride=2\\
padding=0\\
bias=0\\
\\
\\
input = \begin{pmatrix}
0 & 1 & 0 & 1\\
0 & 0 & 0 & 0\\
0 & 1 & 0 & 1\\
0 & 0 & 0 & 0
\end{pmatrix}\\\\
filter = \begin{pmatrix}
-0.41127872
\end{pmatrix}\\
Dans ce cas, le filtre (3,3) déplace les données d'entrée de (4,4) de deux. Comme il est calculé à partir du coin supérieur gauche, les données d'entrée sont centrées sur la position (1,1). Le filtre est plié.
Puisque la foulée est de 2 à partir de là, si vous passez de 2 sur le côté, La position centrale du calcul suivant est (1,3). Parce que c'est à l'extrême droite Tous les filtres ne correspondent pas.
Puisqu'il se décale de 2 dans la direction verticale, En conséquence, même si l'opération de foulée est répétée, elle ne sera pliée qu'une seule fois. Par conséquent, il n'y a qu'un seul résultat.
Exemple 2 Données d'entrée = (dimension = 1, (4 × 4))) / taille du noyau = 3 / padding = 1
in_channels=1\\
out_channels=1\\
kernel_size=3\\
stride=2\\
padding=1\\
bias=0\\
\\
\\
input = \begin{pmatrix}
0 & 0 & 0 & 0 & 0 & 0\\
0 & 0 & 1 & 0 & 1 & 0\\
0 & 0 & 0 & 0 & 0 & 0\\
0 & 0 & 1 & 0 & 1 & 0\\
0 & 0 & 0 & 0 & 0 & 0\\
0 & 0 & 0 & 0 & 0 & 0\\
\end{pmatrix}\\\\
filter = \begin{pmatrix}
0.08725476 & 0.4106578 \\
0.08725476 & 0.4106578 \\
\end{pmatrix}\\
Les 0 ont été remplis d'une ligne / colonne à chaque extrémité. Basé sur le coin supérieur gauche, si vous calculez à la position où le filtre 3x3 s'adapte, Il est calculé 4 fois autour de (1,1) (1,3) (3,1) (3,3). Par conséquent, le résultat de sortie est 2x2.
Couche entièrement connectée.
C'est très simple à faire, Diverses caractéristiques extraites par la couche de pliage, etc. Mettons-le ensemble? Je fais ça.
Si vous le dites dans une image sans crainte de malentendu "Carré rouge". Prenons le cas où vous essayez de faire la distinction entre «carré bleu» et «autre que cela».
Même si vous extrayez uniquement les caractéristiques du carré, vous ne pouvez pas faire la distinction entre le rouge et le bleu. Vous devez également faire correspondre les caractéristiques de couleur.
Par conséquent, il est nécessaire de combiner les caractéristiques du carré, les caractéristiques de la couleur et de multiples caractéristiques. Je le reconnais comme une couche entièrement connectée.
y = xA^T \\
Le calcul que je fais est simple, Appliquez la transformation linéaire $ A $ à l'entrée $ x $ et Calculez simplement $ y $ comme sortie.
y = xA^T \\
⇔\\
\begin{pmatrix}
y_1 \\
y_2 \\
\end{pmatrix}
=
\begin{pmatrix}
A_{00} & A_{01} \\
A_{10} & A_{11} \\
\end{pmatrix}
\begin{pmatrix}
x_1 \\
x_2 \\
\end{pmatrix}\\
⇔\\
\begin{pmatrix}
y_1 = A_{00} x_1 + A_{01}x_2\\
y_2 = A_{10} x_1 + A_{11}x_2 \\
\end{pmatrix}\\
Je vais essayer une explication un peu plus intuitive. La formule ci-dessus est le développement de la formule précédente pour les petites données bidimensionnelles. Les données d'entrée $ x $ sont une valeur numérique obtenue en extrayant des caractéristiques d'une image. La formule a des valeurs de $ x_1 $ et $ x_2 $, Apprenez cela de l'exemple précédent, "Résultats de l'extraction de caractéristiques carrées à partir d'images" "Résultats de l'extraction des caractéristiques de couleur des images" ça ira.
Comme mentionné ci-dessus, chacun seul Je ne sais pas si c'est un carré rouge ou un carré bleu. Vous devez également voir le résultat.
Sur cette base, en regardant la formule finale, Vous pouvez voir que $ x_1 $ et $ x_2 $ sont intégrés.
Pour le moment, je vois l'opération dans le code.
test.py
class Test_Linear(nn.Module):
fc_filter = None
def __init__(self):
super(Test_Linear, self).__init__()
self.conv = nn.Conv2d(
in_channels=1,
out_channels=2,
kernel_size=4,
stride=2,
padding=0)
self.kernel_filter = self.conv.weight.data.numpy()
self.fc = nn.Linear(in_features=1,
out_features=1,
bias=False)
self.fc_filter = self.fc.weight.data.numpy()
print(self.fc_filter)
def forward(self, x):
nparr = np.array( [[[[1.0]],[[100.0]]]]).astype(np.float32)
input = torch.from_numpy(nparr).clone()
x = input
print("Before")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
x = self.fc(x)
print("After Linear")
print("size : \t",x.size())
print("data : \n",x.to('cpu').detach().numpy().copy())
print("\n")
return x
net = Test_Linear()
#contribution
nparr = np.array([0,1,0,0]).astype(np.float32).reshape(2,2)
nparr = np.block([[nparr,nparr],[nparr,nparr]]).reshape(1,1,4,4)
input = torch.from_numpy(nparr).clone()
#production
out = net(input)
exit()
#Résultat d'exécution
[[0.04909718]]
Before
size : torch.Size([1, 2, 1, 1])
data :
[[[[ 1.]]
[[100.]]]]
After Linear
size : torch.Size([1, 2, 1, 1])
data :
[[[[0.04909718]]
[[4.909718 ]]]]
C'est incroyablement simple.
Au fait, comme out_features = 2, Si vous augmentez la dimension de sortie,
#Résultat d'exécution
[[-0.5130856]
[ 0.6920992]]
Before
size : torch.Size([1, 2, 1, 1])
data :
[[[[ 1.]]
[[100.]]]]
After Linear
size : torch.Size([1, 2, 1, 2])
data :
[[[[ -0.5130856 0.6920992]]
[[-51.30856 69.20992 ]]]]
Encore une dimension de A,
https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html La configuration du réseau dans la source de l'échantillon pytorch, Si vous analysez le flux grossièrement,
Avec le contenu jusqu'à présent, nous avons pu acquérir les fonctionnalités numériquement. Cependant, cela seul ne peut pas effectuer le traitement d'identification.
Si vous utilisez le contenu jusqu'à présent, Vous pouvez obtenir les caractéristiques de M. A et M. B, Si quelqu'un qui ne connaît pas A ou B arrive Il est nécessaire de juger si la caractéristique de la personne non identifiée est A ou B.
Les fonctionnalités peuvent déjà être extraites à l'aide des réseaux décrits jusqu'à présent. Essayons de nous identifier à un exemple simple.
test.py
class Test_Conv(nn.Module):
kernel_filter = None
def __init__(self):
super(Test_Conv, self).__init__()
# self.conv = nn.Conv2d(1, 1, 3)
ksize = 4
self.conv = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=4,
bias=False)
self.kernel_filter = self.conv.weight.data.numpy().reshape(ksize,ksize)
def forward(self, x):
x = self.conv(x)
return x
net = Test_Conv()
#contribution
nparr = np.array([0,1,0,0]).astype(np.float32).reshape(2,2)
nparr = np.block([[nparr,nparr],[nparr,nparr]]).reshape(1,1,4,4)
input = torch.from_numpy(nparr).clone()
print("*****Phase d'apprentissage*****")
print("Données d'entrée pour l'apprentissage du réseau")
print(input)
print("\n")
#production
out = net(input)
#Entrée cible#Entrez les mêmes données
out_target1 = net(input)
criterion = nn.MSELoss()
loss = criterion(out, out_target1)
print("*****Phase d'évaluation*****")
print("Entrez les mêmes données")
print("input:")
print(input)
print("Évaluation",loss)
print("\n")
#Entrée cible#Entrez des données légèrement différentes
nparr2 = np.array([0,2,0,0]).astype(np.float32).reshape(2,2)
nparr2 = np.block([[nparr2,nparr2],[nparr2,nparr2]]).reshape(1,1,4,4)
input2 = torch.from_numpy(nparr2).clone()
out_target2 = net(input2)
criterion = nn.MSELoss()
loss = criterion(out, out_target2)
print("Entrez des données légèrement différentes")
print("input:")
print(input2)
print("Évaluation",loss)
print("\n")
#Entrée cible#Entrez des données complètement différentes
nparr3 = np.array([10,122,1000,200]).astype(np.float32).reshape(2,2)
nparr3 = np.block([[nparr3,nparr3],[nparr3,nparr3]]).reshape(1,1,4,4)
input3 = torch.from_numpy(nparr3).clone()
out_target3 = net(input3)
criterion = nn.MSELoss()
loss = criterion(out, out_target3)
print("Entrez des données complètement différentes")
print("input:")
print(input3)
print("Évaluation",loss)
print("\n")
#Résultat d'exécution
*****Phase d'apprentissage*****
Données d'entrée pour l'apprentissage du réseau
tensor([[[[0., 1., 0., 1.],
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 0., 0., 0.]]]])
*****Phase d'évaluation*****
Entrez les mêmes données
input:
tensor([[[[0., 1., 0., 1.],
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 0., 0., 0.]]]])
Tenseur d'évaluation(0., grad_fn=<MseLossBackward>)
Entrez des données légèrement différentes
input:
tensor([[[[0., 2., 0., 2.],
[0., 0., 0., 0.],
[0., 2., 0., 2.],
[0., 0., 0., 0.]]]])
Tenseur d'évaluation(0.4581, grad_fn=<MseLossBackward>)
Entrez des données complètement différentes
input:
tensor([[[[ 10., 122., 10., 122.],
[1000., 200., 1000., 200.],
[ 10., 122., 10., 122.],
[1000., 200., 1000., 200.]]]])
Tenseur d'évaluation(58437.6680, grad_fn=<MseLossBackward>)
Je n'ai rien à expliquer,
out = net(input)
out_target3 = net(input3)
Saisissez chaque donnée d'entrée dans le réseau créé pour calculer les caractéristiques, (Out, out_target3 contient la matrice calculée par conv2d.)
#Définir la méthode d'évaluation
criterion = nn.MSELoss()
#Exécution de l'évaluation
loss = criterion(out, out_target3)
La méthode d'évaluation est définie (ici, l'erreur quadratique moyenne) et évaluée. Si les entités sont proches, la valeur sera proche de 0, Plus la fonction est éloignée, plus la valeur est élevée.
Recommended Posts