[Apprenez en faisant! Développement Deep Learning par PyTorch](https://www.amazon.co.jp/%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%AA%E3%81%8C% E3% 82% 89% E5% AD% A6% E3% 81% B6% EF% BC% 81PyTorch% E3% 81% AB% E3% 82% 88% E3% 82% 8B% E7% 99% BA% E5% B1% 95% E3% 83% 87% E3% 82% A3% E3% 83% BC% E3% 83% 97% E3% 83% A9% E3% 83% BC% E3% 83% 8B% E3% 83% B3% E3% 82% B0-% E5% B0% 8F% E5% B7% 9D-% E9% 9B% 84% E5% A4% AA% E9% 83% 8E-ebook / dp / B07VPDVNKW) Il y avait une telle description dans 1-3 transfert d'apprentissage. (Vous pouvez voir tout le code sur Author GitHub)
1-3_transfer_learning.ipynb
#Importation de package
import glob
import os.path as osp
import random
import numpy as np
import json
from PIL import Image
from tqdm import tqdm
import matplotlib.pyplot as plt
%matplotlib inline
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import torchvision
from torchvision import models, transforms
(Omission)
#Paramètres de la fonction de perte
criterion = nn.CrossEntropyLoss()
(Omission)
def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):
#boucle d'époque
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch+1, num_epochs))
print('-------------')
#Boucle d'apprentissage et de vérification pour chaque époque
for phase in ['train', 'val']:
if phase == 'train':
net.train() #Mettre le modèle en mode entraînement
else:
net.eval() #Mettre le modèle en mode validation
epoch_loss = 0.0 #somme des pertes d'époque
epoch_corrects = 0 #Nombre de bonnes réponses pour l'époque
#Époque pour vérifier les performances de vérification lorsque non appris=0 formation omise
if (epoch == 0) and (phase == 'train'):
continue
#Boucle pour récupérer un mini-lot à partir du chargeur de données
for inputs, labels in tqdm(dataloaders_dict[phase]):
#Initialiser l'optimiseur
optimizer.zero_grad()
#Calcul à terme
with torch.set_grad_enabled(phase == 'train'):
outputs = net(inputs)
loss = criterion(outputs, labels) #Calculer la perte
_, preds = torch.max(outputs, 1) #Étiquette de prédiction
#Propagation du dos pendant l'entraînement
if phase == 'train':
loss.backward()
optimizer.step()
#Calcul des résultats d'italation
#Mettre à jour la perte totale
epoch_loss += loss.item() * inputs.size(0)
#Mise à jour du nombre total de bonnes réponses
epoch_corrects += torch.sum(preds == labels.data)
#Afficher la perte et le taux de réponse correct pour chaque époque
epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
epoch_acc = epoch_corrects.double(
) / len(dataloaders_dict[phase].dataset)
print('{} Loss: {:.4f} Acc: {:.4f}'.format(
phase, epoch_loss, epoch_acc))
Je voudrais que vous prêtiez attention ici aux ** critères **. Il est défini comme une instance de nn.CrossEntropyLoss () comme suit:
1-3_transfer_learning.ipynb
criterion = nn.CrossEntropyLoss()
Et je traite les ** critères ** comme une fonction.
1-3_transfer_learning.ipynb
loss = criterion(outputs, labels)
Cependant, lorsque je vérifie le code source de torch.nn.CrossEntropyLoss, il n'y a pas de description de __call__ method
**!
Alors ** pourquoi pouvez-vous traiter une instance de CrossEntropyLoss () comme une fonction? ** **
Le but de cet article est de résoudre ce mystère.
Voir ici pour savoir pourquoi la présence ou l'absence de la «méthode call» est importante.
Le début du Code source de la classe CrossEntropyLoss
est écrit comme suit.
Python:torch.nn.modules.loss
class CrossEntropyLoss(_WeightedLoss):
Tout d'abord, qu'est-ce que cela signifie de mettre quelque chose entre parenthèses lors de la définition de class
en Python?
Ceci est appelé ** héritage de classe **, et est utilisé lors de l'appel d'une fonction ou d'une méthode définie dans une autre classe telle quelle. (L'exemple spécifique suivant est cité ici)
#Héritage
class MyClass:
def hello(self):
print("Hello")
class MyClass2(MyClass):
def world(self):
print("World")
a = MyClass2()
a.hello() # Hello
a.world() # World
La mise en garde ici est que si une classe parent et une classe enfant ont une méthode avec le même nom défini, la méthode de la classe enfant sera écrasée. C'est ce qu'on appelle un remplacement.
#passer outre
class MyClass:
def hello(self):
print("Hello")
class MyClass2(MyClass):
def hello(self): #Classe des parents bonjour()Méthode d'écrasement
print("HELLO")
a = MyClass2()
a.hello() # HELLO
Et je veux utiliser la méthode définie dans la classe parent pour la méthode de la classe enfant! Vous pouvez utiliser la fonction super ()
quand vous y pensez.
class MyClass1:
def __init__(self):
self.val1 = 123
class MyClass2(MyClass1):
def __init__(self):
super().__init__()
self.val2 = 456
a = MyClass2()
print(a.val1) # 123
print(a.val2) # 456
Pour revenir à l'histoire, la classe CrossEntropyLoss
hérite de la classe _WeightedLoss
. Au fait, si vous vérifiez un peu plus le code de CrossEntropyLoss,
Python:torch.nn.modules.loss
class CrossEntropyLoss(_WeightedLoss):
__constants__ = ['ignore_index', 'reduction']
ignore_index: int
def __init__(self, weight: Optional[Tensor] = None, size_average=None, ignore_index: int = -100,
reduce=None, reduction: str = 'mean') -> None:
super(CrossEntropyLoss, self).__init__(weight, size_average, reduce, reduction)
self.ignore_index = ignore_index
def forward(self, input: Tensor, target: Tensor) -> Tensor:
return F.cross_entropy(input, target, weight=self.weight,
ignore_index=self.ignore_index, reduction=self.reduction)
La description est un peu différente de l'exemple ci-dessus car elle est super (CrossEntropyLoss, self)
, mais [Python official](https://docs.python.org/ja/3/library/functions.html?highlight Si vous faites référence à = super # super), vous pouvez voir que les significations des deux sont exactement les mêmes.
Du fonctionnaire
class C(B):
def method(self, arg):
super().method(arg) # This does the same thing as:
# super(C, self).method(arg)
Jetons maintenant un œil à la description de la classe _WeitedLoss
.
Python:torch.nn.modules.loss
class _WeightedLoss(_Loss):
De là, nous pouvons voir que _WeitedLoss
hérite de _Loss
.
Jetons maintenant un œil à la description de la classe _WeitedLoss
.
Python:torch.nn.modules.loss
class _Loss(Module):
De là, nous pouvons voir que _Loss
hérite de Module
.
Jetons un œil à la description de la classe Module
.
Python:torch.nn.modules.module
class Module:
Module
n'hérite de rien! Vérifions donc à partir du contenu de Module
.
torch.nn.Module
La _Loss class
hérite de la __init__ method
de la Module class, donc vérifiez ceci uniquement. Je vais essayer.
Python:torch.nn.modules.module
#Remarque:Tous les codes ne sont pas répertoriés
from collections import OrderedDict, namedtuple
class Module:
_version: int = 1
training: bool
dump_patches: bool = False
def __init__(self):
"""
Initializes internal Module state, shared by both nn.Module and ScriptModule.
"""
torch._C._log_api_usage_once("python.nn_module")
self.training = True
self._parameters = OrderedDict()
self._buffers = OrderedDict()
self._non_persistent_buffers_set = set()
self._backward_hooks = OrderedDict()
self._forward_hooks = OrderedDict()
self._forward_pre_hooks = OrderedDict()
self._state_dict_hooks = OrderedDict()
self._load_state_dict_pre_hooks = OrderedDict()
self._modules = OrderedDict()
Vous pouvez voir que de nombreux ʻOrderedDict () sont définis ici. Pour plus d'informations sur ʻOrederedDict ()
, veuillez vous référer à here. En termes simples, comme son nom l'indique, **" Un dict vide qui est ordonné **. En d'autres termes, cette classe définit simplement un grand nombre de dictionnaires vides.
Et, en fait, la méthode __call__
en question est définie ici!
Python:torch.nn.modules.module
def _call_impl(self, *input, **kwargs):
for hook in itertools.chain(
_global_forward_pre_hooks.values(),
self._forward_pre_hooks.values()):
result = hook(self, input)
if result is not None:
if not isinstance(result, tuple):
result = (result,)
input = result
if torch._C._get_tracing_state():
result = self._slow_forward(*input, **kwargs)
else:
result = self.forward(*input, **kwargs)
for hook in itertools.chain(
_global_forward_hooks.values(),
self._forward_hooks.values()):
hook_result = hook(self, input, result)
if hook_result is not None:
result = hook_result
if (len(self._backward_hooks) > 0) or (len(_global_backward_hooks) > 0):
var = result
while not isinstance(var, torch.Tensor):
if isinstance(var, dict):
var = next((v for v in var.values() if isinstance(v, torch.Tensor)))
else:
var = var[0]
grad_fn = var.grad_fn
if grad_fn is not None:
for hook in itertools.chain(
_global_backward_hooks.values(),
self._backward_hooks.values()):
wrapper = functools.partial(hook, self)
functools.update_wrapper(wrapper, hook)
grad_fn.register_hook(wrapper)
return result
__call__ : Callable[..., Any] = _call_impl
La dernière ligne __call__: Callable [..., Any] = _call_impl
définit le contenu de __call__
sur _call_impl
, donc si vous appelez l'instance comme une fonction, la fonction ci-dessus sera exécutée. Si vous ne comprenez pas la signification de «Callable [..., Any]», vous pouvez vous référer à ici. En outre, ce deux-points est une annotation de fonction, voir ici pour plus de détails. En termes simples, il "écrit simplement une expression qui sert d'annotation dans l'argument ou la valeur de retour de la fonction".
Je vais suivre la signification de ce code dans cet article.
En plus de ce qui précède, certaines méthodes sont définies dans la Classe de module
, donc vérifiez si nécessaire.
torch.nn._Loss
La _Loss class
hérite de la __init__ method
de la _Loss class. Je vais vérifier.
Python:torch.nn.modules.loss
class _Loss(Module):
reduction: str
def __init__(self, size_average=None, reduce=None, reduction: str = 'mean') -> None:
super(_Loss, self).__init__()
if size_average is not None or reduce is not None:
self.reduction = _Reduction.legacy_get_string(size_average, reduce)
else:
self.reduction = reduction
Ici, vous pouvez voir que nous introduisons une nouvelle self.reduction
. Et cette valeur semble dépendre des valeurs de «size_average» et de «reduction».
torch.nn.__WeightedLoss
La méthode __init__
de la classe _WeightedLoss est héritée de la classe CrossEntropyLoss
. Je vais vérifier.
Python:torch.nn.modules.loss
class _WeightedLoss(_Loss):
def __init__(self, weight: Optional[Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean') -> None:
super(_WeightedLoss, self).__init__(size_average, reduce, reduction)
self.register_buffer('weight', weight)
Ici, ʻOptional [Tensor] est spécifié dans l'annotation de fonction de
weight`. L'explication de ici est facile à comprendre. En termes simples, «poids» signifie que soit le «type Tensor» soit le «type Aucun» peuvent être inclus.
Revenons au sujet principal. Il y a une nouvelle fonction appelée self.register_buffer
ici, qui est une fonction définie dans la classe Module
. Voici le code source.
Python:torch.nn.modules.module
forward: Callable[..., Any] = _forward_unimplemented
def register_buffer(self, name: str, tensor: Optional[Tensor], persistent: bool = True) -> None:
r"""Adds a buffer to the module.
This is typically used to register a buffer that should not to be
considered a model parameter. For example, BatchNorm's ``running_mean``
is not a parameter, but is part of the module's state. Buffers, by
default, are persistent and will be saved alongside parameters. This
behavior can be changed by setting :attr:`persistent` to ``False``. The
only difference between a persistent buffer and a non-persistent buffer
is that the latter will not be a part of this module's
:attr:`state_dict`.
Buffers can be accessed as attributes using given names.
Args:
name (string): name of the buffer. The buffer can be accessed
from this module using the given name
tensor (Tensor): buffer to be registered.
persistent (bool): whether the buffer is part of this module's
:attr:`state_dict`.
Example::
>>> self.register_buffer('running_mean', torch.zeros(num_features))
"""
if persistent is False and isinstance(self, torch.jit.ScriptModule):
raise RuntimeError("ScriptModule does not support non-persistent buffers")
if '_buffers' not in self.__dict__:
raise AttributeError(
"cannot assign buffer before Module.__init__() call")
elif not isinstance(name, torch._six.string_classes):
raise TypeError("buffer name should be a string. "
"Got {}".format(torch.typename(name)))
elif '.' in name:
raise KeyError("buffer name can't contain \".\"")
elif name == '':
raise KeyError("buffer name can't be empty string \"\"")
elif hasattr(self, name) and name not in self._buffers:
raise KeyError("attribute '{}' already exists".format(name))
elif tensor is not None and not isinstance(tensor, torch.Tensor):
raise TypeError("cannot assign '{}' object to buffer '{}' "
"(torch Tensor or None required)"
.format(torch.typename(tensor), name))
else:
self._buffers[name] = tensor
if persistent:
self._non_persistent_buffers_set.discard(name)
else:
self._non_persistent_buffers_set.add(name)
C'est un code assez long, mais la moitié supérieure est l'explication du code, et la partie ci-dessus ʻelse de ʻif instruction
ne définit l'erreur, donc l'explication est omise. Et dans ʻelse, vous mettez des éléments dans
self._buffersde
dict type. En d'autres termes, en définissant la
classe WeightedLoss`, nous avons:
self._buffer = {'weight': weight} #Le poids à droite est de type Tensor ou None
torch.nn.CrossEntropyLoss Enfin, je suis revenu à la question. Voici le code source. Il y a un long commentaire, mais je vais tous les citer.
Python:torch.nn.modules.loss
class CrossEntropyLoss(_WeightedLoss):
r"""This criterion combines :func:`nn.LogSoftmax` and :func:`nn.NLLLoss` in one single class.
It is useful when training a classification problem with `C` classes.
If provided, the optional argument :attr:`weight` should be a 1D `Tensor`
assigning weight to each of the classes.
This is particularly useful when you have an unbalanced training set.
The `input` is expected to contain raw, unnormalized scores for each class.
`input` has to be a Tensor of size either :math:`(minibatch, C)` or
:math:`(minibatch, C, d_1, d_2, ..., d_K)`
with :math:`K \geq 1` for the `K`-dimensional case (described later).
This criterion expects a class index in the range :math:`[0, C-1]` as the
`target` for each value of a 1D tensor of size `minibatch`; if `ignore_index`
is specified, this criterion also accepts this class index (this index may not
necessarily be in the class range).
The loss can be described as:
.. math::
\text{loss}(x, class) = -\log\left(\frac{\exp(x[class])}{\sum_j \exp(x[j])}\right)
= -x[class] + \log\left(\sum_j \exp(x[j])\right)
or in the case of the :attr:`weight` argument being specified:
.. math::
\text{loss}(x, class) = weight[class] \left(-x[class] + \log\left(\sum_j \exp(x[j])\right)\right)
The losses are averaged across observations for each minibatch. If the
:attr:`weight` argument is specified then this is a weighted average:
.. math::
\text{loss} = \frac{\sum^{N}_{i=1} loss(i, class[i])}{\sum^{N}_{i=1} weight[class[i]]}
Can also be used for higher dimension inputs, such as 2D images, by providing
an input of size :math:`(minibatch, C, d_1, d_2, ..., d_K)` with :math:`K \geq 1`,
where :math:`K` is the number of dimensions, and a target of appropriate shape
(see below).
Args:
weight (Tensor, optional): a manual rescaling weight given to each class.
If given, has to be a Tensor of size `C`
size_average (bool, optional): Deprecated (see :attr:`reduction`). By default,
the losses are averaged over each loss element in the batch. Note that for
some losses, there are multiple elements per sample. If the field :attr:`size_average`
is set to ``False``, the losses are instead summed for each minibatch. Ignored
when reduce is ``False``. Default: ``True``
ignore_index (int, optional): Specifies a target value that is ignored
and does not contribute to the input gradient. When :attr:`size_average` is
``True``, the loss is averaged over non-ignored targets.
reduce (bool, optional): Deprecated (see :attr:`reduction`). By default, the
losses are averaged or summed over observations for each minibatch depending
on :attr:`size_average`. When :attr:`reduce` is ``False``, returns a loss per
batch element instead and ignores :attr:`size_average`. Default: ``True``
reduction (string, optional): Specifies the reduction to apply to the output:
``'none'`` | ``'mean'`` | ``'sum'``. ``'none'``: no reduction will
be applied, ``'mean'``: the weighted mean of the output is taken,
``'sum'``: the output will be summed. Note: :attr:`size_average`
and :attr:`reduce` are in the process of being deprecated, and in
the meantime, specifying either of those two args will override
:attr:`reduction`. Default: ``'mean'``
Shape:
- Input: :math:`(N, C)` where `C = number of classes`, or
:math:`(N, C, d_1, d_2, ..., d_K)` with :math:`K \geq 1`
in the case of `K`-dimensional loss.
- Target: :math:`(N)` where each value is :math:`0 \leq \text{targets}[i] \leq C-1`, or
:math:`(N, d_1, d_2, ..., d_K)` with :math:`K \geq 1` in the case of
K-dimensional loss.
- Output: scalar.
If :attr:`reduction` is ``'none'``, then the same size as the target:
:math:`(N)`, or
:math:`(N, d_1, d_2, ..., d_K)` with :math:`K \geq 1` in the case
of K-dimensional loss.
Examples::
>>> loss = nn.CrossEntropyLoss()
>>> input = torch.randn(3, 5, requires_grad=True)
>>> target = torch.empty(3, dtype=torch.long).random_(5)
>>> output = loss(input, target)
>>> output.backward()
"""
__constants__ = ['ignore_index', 'reduction']
ignore_index: int
def __init__(self, weight: Optional[Tensor] = None, size_average=None, ignore_index: int = -100,
reduce=None, reduction: str = 'mean') -> None:
super(CrossEntropyLoss, self).__init__(weight, size_average, reduce, reduction)
self.ignore_index = ignore_index
def forward(self, input: Tensor, target: Tensor) -> Tensor:
return F.cross_entropy(input, target, weight=self.weight,
ignore_index=self.ignore_index, reduction=self.reduction)
Tout d'abord, dans la méthode «init», une nouvelle variable appelée «self.ignore_index» a été ajoutée. Et une fonction appelée forward ()
est également définie. Cependant, la méthode __call__
n'a pas été définie depuis la classe Module
. Par conséquent, la «méthode call» de la «classe Module» était l'identité que l'instance de la «classe CrossEntropyLoss» était utilisée comme une fonction.
Recommended Posts