pytorch Une introduction au pytorch par des débutants.
Lorsque vous utilisez un tel nouveau cadre, je pense que le meilleur raccourci est de regarder l'exemple et de rechercher sur Google les fonctions utilisées, de lire le document ou de jouer avec, donc c'est un mémo.
Si vous essayez de vous familiariser avec pytorch de la même manière, cet article vous fera gagner du temps. (Je suis content si tu le deviens.)
Donc, dans le code de cifar10-tutorial faisant CNN avec CIFAR10 Le décodage ou le travail sur Google est terminé.
L'installation de pytorch est super simple, et si vous cliquez sur votre environnement sur le site officiel, le code d'installation sera affiché. Dans le cas de mon environnement, c'était comme suit.
http://pytorch.org/
pip install http://download.pytorch.org/whl/cu80/torch-0.2.0.post3-cp36-cp36m-manylinux1_x86_64.whl
pip install torchvision
Jetons d'abord un œil à ce code qui charge et prépare les données.
import torch
import torchvision
import torchvision.transforms as transforms
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
torchvision est un package pour la vision par ordinateur de pytorch, et il semble qu'il contienne des fonctions de chargement de données et de prétraitement. ..
Utilisez transforms.Compose
pour configurer la fonction de prétraitement à exécuter après le chargement des données.
Comme son nom l'indique, «ToTensor ()» change le type de données en un tenseur appelé «torch.Tensor» défini par pytorch.
Puisque l'argument de transforms.Normalize
est torch.Tensor
, les fonctions seront exécutées dans l'ordre à partir du début de la liste.
[`` transforms.Normalize ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)
`](http://pytorch.org/docs/master/torchvision/transforms.html#torchvision.transforms. Dans Normaliser), le premier taple de l'argument représente la moyenne de chaque canal RVB et le second taple représente l'écart type. Normaliser en fonction de ces moyennes et écarts types.
En d'autres termes
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
Vous avez maintenant une fonction qui transforme les données en un type de tenseur pour pytorch et effectue la normalisation.
`` `Torchvision.datasets.CIFAR10``` est une classe de chargement de données CIFAR10 comme son nom l'indique.
Si download = True
, enregistrez les données à l'emplacement racine.
Pour MNIST et CIFAR10, les ensembles de données sont déjà divisés en formation et test. (Je pense que vous pouvez le mélanger et le diviser vous-même)
Dans CIFAR10, il y a 60000 images, dont 50000 pour la formation et 10000 pour les tests. Lorsque train = True
est défini, 50000, qui est censé être utilisé pour l'entraînement à l'avance, est chargé, et lorsque train = False
, 10000 données de test sont chargées.
Si vous passez une série de flux de prétraitement créés avec transforms.Compose
avec l'argument transform
, le prétraitement passé après le chargement sera exécuté.
DataLoader place sampler sur l'ensemble de données chargé. /_modules/torch/utils/data/sampler.html) Il s'agit d'une classe, qui est un objet d'échantillonnage de données. Si vous le vérifiez, l'échantillonneur est certainement attaché.
trainloader.sampler
# <torch.utils.data.sampler.RandomSampler at 0x7f9099e13ef0>
sampler avait apparemment un échantillonnage aléatoire, un échantillonnage séquentiel, un échantillonnage pondéré, etc. ..
Il y a un échantillonneur dans l'argument de DataLoader, donc si vous passez l'échantillonneur que vous avez défini ici J'ai un bon pressentiment, alors essayons-le.
Ici, afin de rendre le résultat facile à comprendre, nous pondérerons et n'échantillonnerons qu'une seule donnée. Étant donné que le nombre de données est petit et facile, je vais l'essayer avec des données de test.
import numpy as np
#Créez un vecteur de poids de 1 pour une seule image.
weights = np.zeros(10000) #Le nombre de données de test est de 10000
weights[300] = 1. #300 convient
num_samples = 4 #Nombre d'échantillons
#Essayez WeightedRandomSampler.
my_sampler = torch.utils.data.sampler.WeightedRandomSampler(weights, num_samples, replacement=True)
my_testloader = torch.utils.data.DataLoader(testset, batch_size=4,shuffle=False, num_workers=2, sampler=my_sampler)
my_testiter = iter(my_testloader)
images, labels = my_testiter.next()
#La fonction imshow sera expliquée par la suite, mais je l'utiliserai un peu plus tôt.
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
imshow(torchvision.utils.make_grid(images))
Oh, c'est juste une grenouille.
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# functions to show an image
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()
# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
img = img / 2 + 0.Il y a une non-normalisation dans le commentaire de la 5ème partie, mais je pense que la non-normalisation est un peu trompeuse. On dit que c'est normalisé.
Puisque l'entrée de [plt.imshow](https://matplotlib.org/devdocs/api/_as_gen/matplotlib.pyplot.imshow.html) est [0,1], elle est décalée en conséquence.
## 3.7. Qu'est-ce que img.numpy ()?
```python
type(img) # torch.FloatTensor
torch.Tensor est le type de tenseur manipulé par pytorch et est nommé "type de données d'élément + Tensor". .. Dans ce cas, puisqu'il s'agit d'un Float Tensor, il s'agit d'un float, c'est-à-dire d'une virgule flottante 32 bits.
Soit le ndarray retourné dans `` `Document``` et le tenseur d'origine partagent la même zone. Si vous le modifiez, l'autre sera également modifié.
Vérifions-le.
a = torch.FloatTensor([1])
b = a.numpy()
#Changer ndarray
b[0] = 2
#Le tenseur d'origine est également 2.
print("original tensor: ", a) # original tensor: 2
print("ndarray : ", b) # ndarray : [ 2.]
#Il fait référence à une autre zone de mémoire.
print(id(a)) # 140024484781832
print(id(b)) # 140024044621056
Oh, les changements de ndarray sont également reflétés dans le tenseur d'origine. Puisque les zones de mémoire réservées sont différentes, il semble qu'elles fonctionnent comme si elles partageaient virtuellement la même zone en conservant les mêmes valeurs.
npimg = img.numpy()
npimg2 = np.transpose(npimg, (1, 2, 0))
print(npimg.shape) # (3, 36, 138)
print(npimg2.shape) # (36, 138, 3)
En regardant la documentation, les arguments pour plt.imshow
sont alignés comme (n, m, RGB). Doit être.
Puisque npimg est à l'origine aligné avec (RVB, vertical, horizontal), il est trié dans l'ordre du deuxième argument de `` np.transpose ''.
dataiter = iter(trainloader)
print(type(trainloader))
# <class 'torch.utils.data.dataloader.DataLoader'>
print(type(dataiter))
# <class 'torch.utils.data.dataloader.DataLoaderIter'>
Le __iter__
défini dans DataLoader est appelé par iter () et renvoie DataLoaderIter
.
Contrairement à un itérateur normal, vous devez passer les données par incréments de batch_size, il semble donc qu'ils aient défini un itérateur dédié. (Code)
En conséquence, chaque fois que vous appelez dataiter.next ()
, le nième lot, le n + 1ème lot et les données répétées seront acquis.
img = torchvision.utils.make_grid(images)
print(type(images)) # <class 'torch.FloatTensor'>
print(images.size) # torch.Size([4, 3, 32, 32])
print(type(img)) # <class 'torch.FloatTensor'>
print(img.size) # torch.Size([3, 36, 138])
La documentation est torchvision.utils.make_grid.
La fonction make_grid
organise plusieurs images côte à côte.
L'argument de make_grid
est un tenseur à 4 dimensions, tandis que la valeur de retour est un tenseur à 3 dimensions. L'argument tenseur était un tenseur à 4 dimensions de [nombre d'images, RVB, vertical, horizontal], mais la dimension du nombre d'images a disparu.
Et comme le dit la documentation, par défaut
padding = 2```, donc 2 sont ajoutés au-dessus et en dessous à 36, et 2 est ajouté entre chaque image et aux deux extrémités, 32 * n + 2 * (n + 1) = 138 (n = 4) En d'autres termes, 32 vaut 138 pour l'horizontale.
Jetons un coup d'œil à ce code.
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
Variable
enveloppe torch.Tensor pour qu'il puisse contenir des données de gradient, etc. ..
Variable encapsule un tenseur, donc une étape est nécessaire lorsque vous souhaitez voir les données contenues dans Variable.
a = torch.FloatTensor([1.])
a2 = Variable(a)
print(type(a)) # <class 'torch.FloatTensor'>
print(type(a2)) # <class 'torch.autograd.variable.Variable'>
print(type(a2.data)) # <class 'torch.FloatTensor'>
print(a.numpy()) # [ 1.]
print(a2.data.numpy()) # [ 1.]
Comme indiqué ci-dessus, le tenseur est stocké dans «.data». (Référence)
À propos, les informations sur le gradient sont
print(a2.grad) # None
Il est enregistré ici. Il n'y a rien pour le moment, donc Aucun n'est inclus. Il semble que seuls "Aucun" ou "Variable" sont acceptés.
a2.grad = Variable(torch.FloatTensor([100]))
print(a2.grad) # Variable containing: 100
Je pense que c'est une bonne idée de le remplacer comme ça au verso. Et le fait que seule Variable soit acceptée signifie que les informations de gradient y seront également stockées.
a2.grad.grad = Variable(torch.FloatTensor([200]))
print(a2.grad.grad) # Variable containing: 200
Le dérivé supérieur est-il falsifié de cette manière? c'est intéressant.
class Net(nn.Module):
nn.Module La classe est une classe de base, et forward etc. sont définis ici, et lors de la création d'un modèle Il hérite de cela. (En fait, quelque chose de concret n'est pas défini dans forward, et il semble que l'on suppose qu'il sera attribué après l'héritage.)
L'argument de nn.Conv2d est À partir de la gauche, le nombre de canaux d'entrée, le nombre de canaux de sortie et la taille du noyau.
Au moment de la définition de ce modèle, une valeur aléatoire proche de 0 est placée dans le filtre, qui est un paramètre de la couche de convolution, et elle est préparée. Cela peut être confirmé comme suit.
conv1 = nn.Conv2d(3, 6, 5)
print(conv1.weight)
Parameter containing:
(0 ,0 ,.,.) =
-0.0011 -0.1120 0.0351 -0.0488 0.0323
-0.0529 -0.0126 0.1139 -0.0234 -0.0729
0.0384 -0.0263 -0.0903 0.1065 0.0702
0.0087 -0.0492 0.0519 0.0254 -0.0941
0.0351 -0.0556 -0.0279 -0.0641 -0.0790
(0 ,1 ,.,.) =
-0.0738 0.0853 0.0817 -0.1121 0.0463
-0.0266 0.0360 0.0215 -0.0997 -0.0559
0.0441 -0.0151 0.0309 -0.0026 0.0167
-0.0534 0.0699 -0.0295 -0.1043 -0.0614
-0.0820 -0.0549 -0.0654 -0.1144 0.0049
...
[torch.FloatTensor of size 6x3x5x5]
Puisque la taille du noyau est définie sur 5, le tenseur (nombre de canaux de sortie, RVB, taille du noyau, taille du noyau) est préparé. Au fait,
conv1 = nn.Conv2d(3, 6, (5,1))
print(conv1.weight)
Parameter containing:
(0 ,0 ,.,.) =
0.2339
-0.0756
0.0604
-0.0185
-0.0975
...
Il semble que vous puissiez faire autre chose que le carré.
En outre, ce paramètre est
type(conv1.weight)
# torch.nn.parameter.Parameter
Il est défini par l'objet Parameter comme celui-ci, et il s'agit d'une sous-classe de Variable
, et cela ressemble à un rapide coup d'œil. , La fonction d'affichage est définie, et si elle est Variable, elle sera affichée comme Variable contenant, mais dans Paramètre, elle sera affichée comme Paramètre contenant:.
nn.MaxPool2d est la couche de regroupement MAX. Les principaux arguments utilisés sont kernel_size, stride et padding.
Alors, plions et regroupons MAX l'image adaptative et voyons comment elle est convertie.
images, labels = dataiter.next()
print(images.size())
print(type(images))
image_plot = images[0][1].numpy()
plt.imshow(image_plot, cmap='Greys', interpolation='nearest')
plt.show()
#Définition du modèle
img_input = Variable(images)
conv = nn.Conv2d(3, 1, 3, padding=1)
pool = nn.MaxPool2d(3, padding=1, stride=1)
#vers l'avant
conv_output = conv(img_input)
pool_output = pool(conv_output)
print(pool_output.size())
#terrain
conv_plot = conv_output[0][0].data.numpy()
conv_plot
plt.imshow(conv_plot, cmap='Greys', interpolation='nearest')
plt.show()
pool_plot = pool_output[0][0].data.numpy()
plt.imshow(pool_plot, cmap='Greys', interpolation='nearest')
plt.show()
--Image originale C'est un cheval.
-Image après pliage Ici, comme la convolution n'a pas encore appris les paramètres du filtre, c'est un paramètre aléatoire proche de 0, et tous ont la même valeur, il semble donc qu'il n'y ait pas beaucoup de différence.
-Image après pliage et mise en commun MAX Vous pouvez voir que le pool MAX a un 3 * 3 MAX et est flou.
Ensuite, jetez un œil à ce code.
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
nn.CrossEntropyLoss () définit un objet comme étant la fonction objectif.
`` `torch.optim``` définit divers algorithmes d'optimisation. SGD, Adam, etc.
Parmi les algorithmes d'optimisation définis dans torch.optim
, ici optim.SGD est utilisé. Je vais.
Nous transmettons net.parameters ()
comme argument à ʻoptim.SGD.
net.parameters () semble renvoyer les paramètres définis dans le modèle (
torch.nn.parameter.Parameter`) en tant que générateur.
type(net.parameters())
# generator
type(net.parameters().__next__())
# torch.nn.parameter.Parameter
print(net.parameters().__next__())
Parameter containing:
(0 ,0 ,.,.) =
-0.0998 0.0035 -0.0438 -0.1150 -0.0435
0.0310 -0.0750 -0.0405 -0.0745 -0.1095
-0.0355 0.0065 -0.0225 0.0729 -0.1114
0.0708 -0.0170 -0.0253 0.1060 0.0557
0.1057 0.0873 0.0793 -0.0309 -0.0861
...
Si vous vérifiez l'objet détenu par l'optimiseur,
optimizer.__dict__
{'param_groups': [{'dampening': 0,
'lr': 0.001,
'momentum': 0.9,
'nesterov': False,
'params': [Parameter containing:
(0 ,0 ,.,.) =
0.0380 -0.1152 0.0761 0.0964 -0.0555
-0.0325 -0.0455 -0.0755 0.0413 -0.0589
0.0116 0.1136 -0.0992 -0.1149 -0.0414
-0.0611 0.0827 -0.0906 0.0631 0.0170
0.0903 -0.0816 -0.0690 0.0470 -0.0578
...
net.parameters ()
conserve tous les paramètres inclus dans le modèle, y compris mes paramètres.
Jetons un œil au [code] pour la formation (http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#train-the-network).
for epoch in range(2): # loop over the dataset multiple times
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
#Le deuxième argument est la position de départ, qui est 0, donc énumérer(trainloader)Pareil que
# https://docs.python.org/3/library/functions.html#enumerate
# get the inputs
inputs, labels = data
# wrap them in Variable
inputs, labels = Variable(inputs), Variable(labels)
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.data[0]
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
Comme nous l'avons confirmé précédemment, optimizer
contient tous les paramètres.
ʻOptimizer.zero_grad () ` initialise le grad de ces variables conservées. Il semble. Je pense que tout est Aucun.
outputs = net(inputs)
print(type(outputs))
# <class 'torch.autograd.variable.Variable'>
print(outputs.size())
# torch.Size([4, 10])
outputs
Variable containing:
-2.4825 -4.4286 2.2041 3.4353 2.0734 2.8198 1.9374 0.7751 -2.6798 -3.1932
-1.7512 -4.6657 2.7911 3.9570 0.7931 5.9005 -0.8023 2.9664 -4.3328 -3.2921
2.4015 2.8962 0.9330 -1.2107 -0.0525 -2.2119 -1.2474 -2.6026 -0.1120 0.4869
-1.3042 -2.7538 1.0985 -0.2462 3.7435 1.1724 -1.4233 6.6892 -3.8201 -2.3132
[torch.FloatTensor of size 4x10]
En le passant à net
, vous pouvez voir que la sortie finale via la fonction objectif est renvoyée.
Le commentaire dit avant + arrière + optimiser, mais je ne vois pas la méthode avant.
C'est en fait parce que CrossEntropyLoss appelle en avant avec call, ce qui signifie
loss = criterion(outputs, labels)
loss = criterion.forward(outputs, labels)
Les deux font la même chose. Donc «perte = critère (sorties, étiquettes)» est en avant.
loss
est un objet Variable.
type(loss)
# torch.autograd.variable.Variable
Variable.backward () se trouve à l'intérieur de cette [torch.autograd.backward ()](http: / /pytorch.org/docs/master/autograd.html#torch.autograd.backward) est appelée.
Ensuite, ce que fait ce «.backward ()» est de trouver le coefficient différentiel du paramètre contenu dans la fonction objectif. Essayons avec un exemple simple.
x = torch.autograd.Variable(torch.Tensor([3,4]), requires_grad=True)
# requires_grad=Vrai, vous dit que cette variable va différencier
print("x.grad : ", x.grad)
# None
#À ce stade, rien n'y est encore.
#Créez une fonction objective de manière appropriée.
y = x[0]**2 + 5*x[1] + x[0]*x[1]
# x[0]Dérivé de: 2*x[0] + x[1]
# x[0]Coefficient différentiel de: 2*3 + 4 = 10
# x[1]Dérivé de: 5 + x[0]
# x[1]Coefficient différentiel de: 5 + 3 = 8
y.backward()
# torch.autograd.backward(y)Mais c'est d'accord.
print("x.grad : ", x.grad)
# 10
# 8
# .zero_grad()au lieu de
x.grad = None
C'est le coefficient différentiel de la fonction objectif «y» au point de données d'entrée. Il convient de noter ici que vers l'arrière concerne la fonction de perte, donc y vers l'arrière doit être un scalaire.
Par exemple
y = x
y.backward()
# RuntimeError: grad can be implicitly created only for scalar outputs
Ensuite, je me fâche d'en faire un scalaire.
.step () met à jour les paramètres en fonction du gradient calculé par .backward ()
Le fera.
Regardons ça.
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
print(net.parameters().__next__())
Parameter containing:
(0 ,0 ,.,.) =
-0.0839 0.1434 -0.0371 -0.1394 -0.0277
Après l'avoir exécuté plusieurs fois (bien qu'il soit optimisé avec les mêmes données)
print(net.parameters().__next__())
Parameter containing:
(0 ,0 ,.,.) =
-0.0834 0.1436 -0.0371 -0.1389 -0.0276
Et ainsi, les paramètres sont mis à jour petit à petit.
Prédisez le modèle pour les données de test. Ce code.
correct = 0
total = 0
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
images, labels = data
#print("images type : ", type(images))
#print("images.shape : ", images.shape)
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i]
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
# Accuracy of plane : 51 %
# Accuracy of car : 54 %
# Accuracy of bird : 53 %
# Accuracy of cat : 33 %
# Accuracy of deer : 41 %
# Accuracy of dog : 50 %
# Accuracy of frog : 54 %
# Accuracy of horse : 65 %
# Accuracy of ship : 70 %
# Accuracy of truck : 67 %
Est-ce le seul que je ne connaisse pas? torch.squeeze Dans la dimension du tenseur, effacez celui de 1. Il semble que presser signifie presser.
Vous pouvez trouver de quel type de modèle il s'agit en regardant ~~ net.parameters
. ~~ (Corrigé le 27 octobre 2017)
Puisque le __repr__
de nn.Module est défini de sorte que le modèle soit affiché de manière facile à lire, vous pouvez approximativement comprendre de quel type de modèle il s'agit comme suit.
In [22]: net
Out[22]:
Net (
(conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
(pool): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear (400 -> 120)
(fc2): Linear (120 -> 84)
(fc3): Linear (84 -> 10)
)
Recommended Posts