[Lerne beim Machen! Development Deep Learning von 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) Es gab eine solche Beschreibung in 1-3 Transferlernen. (Sie können den gesamten Code unter [Author GitHub] sehen (https://github.com/YutaroOgawa/pytorch_advanced/blob/master/1_image_classification/1-3_transfer_learning.ipynb))
1-3_transfer_learning.ipynb
#Paketimport
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
(Unterlassung)
#Einstellungen der Verlustfunktion
criterion = nn.CrossEntropyLoss()
(Unterlassung)
def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):
#Epochenschleife
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch+1, num_epochs))
print('-------------')
#Lern- und Verifizierungsschleife für jede Epoche
for phase in ['train', 'val']:
if phase == 'train':
net.train() #Versetzen Sie das Modell in den Trainingsmodus
else:
net.eval() #Versetzen Sie das Modell in den Validierungsmodus
epoch_loss = 0.0 #Epochenverlustsumme
epoch_corrects = 0 #Anzahl der richtigen Antworten für die Epoche
#Epoche, um die Überprüfungsleistung zu überprüfen, wenn Sie nicht gelernt haben=0 Training weggelassen
if (epoch == 0) and (phase == 'train'):
continue
#Schleife zum Abrufen des Mini-Batch vom Datenlader
for inputs, labels in tqdm(dataloaders_dict[phase]):
#Optimierer initialisieren
optimizer.zero_grad()
#Vorwärtsberechnung
with torch.set_grad_enabled(phase == 'train'):
outputs = net(inputs)
loss = criterion(outputs, labels) #Verlust berechnen
_, preds = torch.max(outputs, 1) #Etikett vorhersagen
#Rückenausbreitung während des Trainings
if phase == 'train':
loss.backward()
optimizer.step()
#Berechnung der Kursivationsergebnisse
#Totalverlust aktualisieren
epoch_loss += loss.item() * inputs.size(0)
#Die Gesamtzahl der richtigen Antworten wurde aktualisiert
epoch_corrects += torch.sum(preds == labels.data)
#Anzeigeverlust und korrekte Antwortrate für jede Epoche
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))
Ich möchte, dass Sie hier auf ** Kriterien ** achten. Es ist wie folgt als Instanz von nn.CrossEntropyLoss () definiert:
1-3_transfer_learning.ipynb
criterion = nn.CrossEntropyLoss()
Und der Autor behandelt ** Kriterien ** wie eine Funktion.
1-3_transfer_learning.ipynb
loss = criterion(outputs, labels)
Wenn ich jedoch den Quellcode von torch.nn.CrossEntropyLoss überprüfe, gibt es keine Beschreibung der Methode call **! ** Warum können Sie eine Instanz von CrossEntropyLoss () wie eine Funktion behandeln? ** **. Der Zweck dieses Artikels ist es, dieses Rätsel zu lösen. Unter hier erfahren Sie, warum das Vorhandensein oder Fehlen der Methode "call" wichtig ist.
Der Anfang des Quellcodes der "CrossEntropyLoss-Klasse" lautet wie folgt.
Python:torch.nn.modules.loss
class CrossEntropyLoss(_WeightedLoss):
Was bedeutet es zunächst, etwas in Klammern zu setzen, wenn "Klasse" in Python definiert wird? Dies wird als ** Klassenvererbung ** bezeichnet und beim Aufrufen einer Funktion oder Methode verwendet, die in einer anderen Klasse so definiert ist, wie sie ist. (Das folgende spezifische Beispiel wird aus [hier] zitiert (https://murashun.jp/blog/20200113-63.html))
#Erbe
class MyClass:
def hello(self):
print("Hello")
class MyClass2(MyClass):
def world(self):
print("World")
a = MyClass2()
a.hello() # Hello
a.world() # World
Die Einschränkung hierbei ist, dass die Methode der untergeordneten Klasse überschrieben wird, wenn für eine übergeordnete und eine untergeordnete Klasse eine Methode mit demselben Namen definiert ist. Dies wird als Override bezeichnet.
#überschreiben
class MyClass:
def hello(self):
print("Hello")
class MyClass2(MyClass):
def hello(self): #Elternklasse Hallo()Methode überschreiben
print("HELLO")
a = MyClass2()
a.hello() # HELLO
Und ich möchte die in der übergeordneten Klasse definierte Methode für die Methode der untergeordneten Klasse verwenden! Sie können die Funktion super ()
verwenden, wenn Sie darüber nachdenken.
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
Zurück zur Geschichte: Die "CrossEntropyLoss-Klasse" erbt von der "_WeightedLoss-Klasse". Übrigens, wenn Sie den Code von CrossEntropyLoss etwas genauer überprüfen,
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)
Die Beschreibung unterscheidet sich ein wenig vom obigen Beispiel, da sie "super (CrossEntropyLoss, self)" ist, aber [Python offiziell](https://docs.python.org/ja/3/library/functions.html?highlight Wenn Sie sich auf = super # super) beziehen, können Sie sehen, dass die Bedeutungen von beiden genau gleich sind.
Vom Beamten
class C(B):
def method(self, arg):
super().method(arg) # This does the same thing as:
# super(C, self).method(arg)
Schauen wir uns nun die Beschreibung der _WeitedLoss-Klasse
an.
Python:torch.nn.modules.loss
class _WeightedLoss(_Loss):
Daraus können wir ersehen, dass "_WeitedLoss" von "_Loss" erbt.
Schauen wir uns nun die Beschreibung der _WeitedLoss-Klasse
an.
Python:torch.nn.modules.loss
class _Loss(Module):
Daraus können wir ersehen, dass _Loss
von Module
erbt.
Werfen wir einen Blick auf die Beschreibung der Modulklasse
.
Python:torch.nn.modules.module
class Module:
Modul
erbt nichts! Schauen wir uns also den Inhalt von Module
an.
torch.nn.Module
Die _Loss-Klasse
erbt die __init__-Methode
der Modulklasse. Überprüfen Sie dies daher nur. Ich werde versuchen.
Python:torch.nn.modules.module
#Hinweis:Nicht alle Codes sind aufgeführt
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()
Sie können sehen, dass hier viele "OrderedDict ()" definiert sind. Weitere Informationen zu "OrederedDict ()" finden Sie unter hier. Einfach ausgedrückt, wie der Name schon sagt, ** " Ein leeres Diktat, das bestellt wird **. Mit anderen Worten, diese Klasse definiert nur viele leere Wörterbücher.
Und tatsächlich ist hier die fragliche "call" -Methode definiert!
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
Die letzte Zeile __call__: Callable [..., Any] = _call_impl
setzt den Inhalt von __call__
auf _call_impl
. Wenn Sie also die Instanz wie eine Funktion aufrufen, wird die obige Funktion ausgeführt. Wenn Sie die Bedeutung von "Callable [..., Any]" nicht verstehen, können Sie auf [hier] verweisen (https://qiita.com/KtheS/items/7a2bec2a94cf3587df14). Dieser Doppelpunkt ist auch eine Funktionsanmerkung. Weitere Informationen finden Sie unter hier. Einfach ausgedrückt, schreibt es einfach "einen Ausdruck, der als Anmerkung in das Argument oder den Rückgabewert der Funktion dient".
Ich werde der Bedeutung dieses Codes in diesem Artikel folgen.
Darüber hinaus sind einige Methoden in der Modulklasse
definiert. Überprüfen Sie daher gegebenenfalls.
torch.nn._Loss
Die _WeightedLoss-Klasse
erbt die __init__-Methode
der _Loss-Klasse. Ich werde es prüfen.
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
Hier können Sie sehen, dass wir eine neue "Selbstreduktion" einführen. Und dieser Wert scheint von den Werten von "size_average" und "reduct" abzuhängen.
torch.nn.__WeightedLoss
Die "init" -Methode der _WeightedLoss-Klasse wird von der "CrossEntropyLoss-Klasse" geerbt. Ich werde es prüfen.
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)
Hier wird "Optional [Tensor]" in der Funktionsanmerkung "Gewicht" angegeben. Die Erklärung von hier ist leicht zu verstehen. Einfach ausgedrückt bedeutet "Gewicht", dass entweder "Tensortyp" oder "Keine Typ" eingeschlossen werden kann.
Kommen wir zurück zum Hauptthema. Hier gibt es eine neue Funktion namens "self.register_buffer", eine Funktion, die in der Klasse "Module" definiert ist. Unten ist der Quellcode.
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)
Es ist ein ziemlich langer Code, aber die obere Hälfte ist die Erklärung des Codes, und der Teil über dem "else" der "if-Anweisung" ist nur die Fehlereinstellung, daher wird die Erklärung weggelassen. Und in "else" setzen Sie Elemente in "self._buffers" vom Typ "dict". Mit anderen Worten, durch Definieren der WeightedLoss-Klasse
haben wir:
self._buffer = {'weight': weight} #Das Gewicht rechts ist vom Typ Tensor oder vom Typ None
torch.nn.CrossEntropyLoss Schließlich bin ich auf die Frage zurückgekommen. Unten ist der Quellcode. Es gibt einen langen Kommentar, aber ich werde sie alle zitieren.
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)
Zunächst wurde in der Methode init eine neue Variable namens self.ignore_index hinzugefügt. Außerdem wird eine Funktion namens "forward ()" definiert. Die __call__-Methode
wurde jedoch seit der Modulklasse
nicht mehr definiert. Daher war die __call__-Methode
der Modulklasse
die Identität, dass die Instanz der CrossEntropyLoss-Klasse
wie eine Funktion verwendet wurde.
Recommended Posts