0x00 сводка
Мы рассмотрим распределенные оптимизаторы в следующих нескольких статьях. Эта серия разделена на три статьи, а именно краеугольную статью, параллельный оптимизатор данных в DP/DDP/Horovod и распределенный оптимизатор PyTorch, которые постепенно углубляются.
Эта статья является краеугольной. В этой статье вы можете узнать о структуре модели, основных принципах оптимизатора, взаимодействии между ними, о том, как оптимизировать и обновлять модель и т. д., что закладывает основу для последующий поуровневый анализ.
Другие статьи о распространении PyTorch:
[Анализ исходного кода] Распространение PyTorch (1) ------ история и обзор
[Анализ исходного кода] Как PyTorch использует GPU
[Анализ исходного кода] Распределенный PyTorch (2) ----- DataParallel (включен)
[Анализ исходного кода] Распределенный PyTorch (3) ----- DataParallel (ниже)
[Анализ исходного кода] Распределенный PyTorch (7) ----- Группа процессов DistributedDataParallel
[Анализ исходного кода] Распределенный PyTorch (8) -------- Бумага DistributedDataParallel
[Анализ исходного кода] Распределенный PyTorch (9) ----- Инициализация DistributedDataParallel
[Анализ исходного кода] PyTorch, распространяемый Autograd (1) ---- дизайн
[Анализ исходного кода] PyTorch, распространяемый Autograd (2) ---- Фонд RPC
[Анализ исходного кода] PyTorch, распространяемый Autograd (3) ---- контекстно-зависимый
[Анализ исходного кода] PyTorch распространяет Автоград (4) ---- как врезаться в движок
[Анализ исходного кода] PyTorch, распространяемый Autograd (5) ---- движок (включен)
[Анализ исходного кода] PyTorch, распространяемый Autograd (6) ---- Engine (ниже)
Для лучшего объяснения код в этой статье будет соответственно упрощен в соответствии с конкретной ситуацией.
0x01 Начиная с проблемы
Изображение ниже взято из статьи Kuaishou Gossip. На изображении показано сравнение между нативным процессом обучения и DDP/Horovod. Ваниль выше — это нативный процесс обучения, а часть U соответствует процессу оптимизатора. Основная функция обычного оптимизатора — оптимизировать и обновлять текущие параметры модели в соответствии с градиентом:w.data -= w.grad * lr
.
1.1 Пример
Давайте используем пример, чтобы увидеть, как тренироваться.
class ToyModel(nn.Module):
def __init__(self):
super(ToyModel, self).__init__()
self.net1 = nn.Linear(10, 10)
self.relu = nn.ReLU()
self.net2 = nn.Linear(10, 5)
def forward(self, x):
return self.net2(self.relu(self.net1(x)))
net = ToyModel()
optimizer = optim.SGD(params=net.parameters(), lr = 1)
optimizer.zero_grad()
input = torch.randn(10,10)
outputs = net(input)
outputs.backward(outputs)
optimizer.step()
Приблизительная обратная расчетная схема приведена ниже.
1.2 Проблемы
Поскольку у нас уже есть другой опыт, такой как предыдущий механизм анализа, мы объединили знания, полученные ранее, чтобы разобраться с несколькими проблемными моментами, чтобы направлять наш анализ.Мы следуем: Создайте оптимизатор на основе параметров модели ---> Механизм вычисляет градиенты -- ---> Оптимизатор оптимизирует параметры ---> Оптимизатор обновляет модель в таком порядке для анализа. Мы знаем, что движок autograd вычисляет градиент, поэтому возникает проблема:
-
Создайте оптимизатор из параметров модели
- использовать
optimizer = optim.SGD(params=net.parameters(), lr = 1)
Создан таким образом, что кажется, что params присваивается внутренней переменной-члену оптимизатора (скажем, она называется параметрами). -
- Модель состоит из двух Линейных, как эти слои обновляют параметры?
- использовать
-
Движок вычисляет градиент
- Как я могу гарантировать, что Linear может вычислять градиенты?
-
- Как для модели рассчитанный градиент соответствует линейному параметру? Где накапливаются эти рассчитанные движком градиенты?
-
Оптимизатор оптимизирует параметры:
-
- Вызовите step для оптимизации, а целью оптимизации является внутренняя переменная-член self.parameters оптимизатора.
-
-
Оптимизатор обновляет модель:
-
- Как отразить обновление цели оптимизации (self.parameters) в обновлении параметров модели (например, Linear)?
-
Цифры и вопросительные знаки на рисунке ниже соответствуют четырем вопросам выше.
+-------------------------------------------+ +------------------+
|ToyModel | | Engine |
| | forward / backward | |
| Linear(10, 10)+--> ReLU +--> Linear(10, 5)| +----------------> | Compute gradient |
| | | + |
+-------------------+-----------------------+ | | |
| | | |
1 ??? | parameters() +------------------+
| |
| | gradient
| ^ |
| | v
| | 4 ??? 2 ???
| |
+------------------------------------------+
|SGD | | |
| | | |
| v + |
| |
^ +---------------> self.parameters +---------------->
| | | |
| | | |
| +------------------------------------------+ |
| |
<---------------------------------------------------+ v
3 step()
Мы должны анализировать шаг за шагом.
0x01 Построение модели
Поскольку оптимизатор должен оптимизировать параметры обновленной модели, мы сначала вводим соответствующую информацию о модели.
1.1 Module
Если вы определяете модель в PyTorch, вам обычно необходимо наследовать nn.Module.
import torch
import torch.nn as nn
import torch.nn.functional as F
class ToyModel(nn.Module):
def __init__(self):
super(ToyModel, self).__init__()
self.net1 = nn.Linear(10, 10)
self.relu = nn.ReLU()
self.net2 = nn.Linear(10, 5)
def forward(self, x):
return self.net2(self.relu(self.net1(x)))
Модуль определяется следующим образом:
class Module:
r"""Base class for all neural network modules.
Your models should also subclass this class.
Modules can also contain other Modules, allowing to nest them in
a tree structure. You can assign the submodules as regular attributes::
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5)
self.conv2 = nn.Conv2d(20, 20, 5)
def forward(self, x):
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))
Submodules assigned in this way will be registered, and will have their
parameters converted too when you call :meth:`to`, etc.
:ivar training: Boolean represents whether this module is in training or
evaluation mode.
:vartype training: bool
"""
dump_patches: bool = False
_version: int = 1
training: bool
_is_full_backward_hook: Optional[bool]
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._is_full_backward_hook = None
self._forward_hooks = OrderedDict()
self._forward_pre_hooks = OrderedDict()
self._state_dict_hooks = OrderedDict()
self._load_state_dict_pre_hooks = OrderedDict()
self._modules = OrderedDict()
1.2 Переменные-члены
Внутри модуля есть следующие важные переменные, которые можно условно разделить на следующие три категории.
базовый тип:
-
_parameters
: Весовые параметры типа тензор, используемые для прямого и обратного распространения, сохранение модели заключается в сохранении этих параметров. Используйте функцию parameters() для рекурсивного получения всех параметров модели, но следует отметить, что функция parameters() возвращает итератор. -
_buffers
: переменные, в которых хранятся некоторые несетевые параметры, которые необходимо сохранить, например, running_mean для BN. -
_modules
: Сохраняется переменная типа Module.При переходе к параметрам модели PyTorch реализует ее путем рекурсивного обхода всех _modules.
Типы вычислительной корреляции:
Расчет модели производится в следующем порядке:
_backward_hooks ----> forward ----> _forward_hooks ----> _backward_hooks
детали следующим образом:
-
_forward_pre_hooks : запускать перед форвардом без изменения входных параметров форварда.
-
_forward_hooks : запускать после форварда без изменения входов и выходов форварда.
-
_backward_hooks : запускать после обратного без изменения обратных входов и выходов.
сохранение/загрузка связанных:
Следующее относится к сохранению: PyTorch использует следующее для сохранения torch.save(cn.state_dict()...) и использования load_state_dict(state_dict) для загрузки.
- _load_state_dict_pre_hooks: действия, которые вы хотите выполнить при вызове _load_from_state_dict для загрузки модели.
- _state_dict_hooks: при вызове
state_dict
Действие, которое вы хотите выполнить при вызове метода.
Удельное время работы следующее:
net = {ToyModel}
T_destination = {TypeVar} ~T_destination
dump_patches = {bool} False
net1 = {Linear} Linear(in_features=10, out_features=10, bias=True)
net2 = {Linear} Linear(in_features=10, out_features=5, bias=True)
relu = {ReLU} ReLU()
training = {bool} True
_backward_hooks = {OrderedDict: 0} OrderedDict()
_buffers = {OrderedDict: 0} OrderedDict()
_forward_hooks = {OrderedDict: 0} OrderedDict()
_forward_pre_hooks = {OrderedDict: 0} OrderedDict()
_is_full_backward_hook = {NoneType} None
_load_state_dict_pre_hooks = {OrderedDict: 0} OrderedDict()
_modules = {OrderedDict: 3} OrderedDict([('net1', Linear(in_features=10, out_features=10, bias=True)), ('relu', ReLU()), ('net2', Linear(in_features=10, out_features=5, bias=True))])
_non_persistent_buffers_set = {set: 0} set()
_parameters = {OrderedDict: 0} OrderedDict()
_state_dict_hooks = {OrderedDict: 0} OrderedDict()
_version = {int} 1
1.3 _parameters
Оптимизатор оптимизирует _parameters, поэтому нам нужен особый взгляд.
1.3.1 Сборка
Давайте сначала посмотрим на характеристики генерации: require_grad=True. Параметры заданы таким образом, а это значит, что Parameter нужно вычислить градиент.
Поскольку тензоры не нужно различать по умолчанию, атрибут require_grad по умолчанию имеет значение False.Если для атрибута require_grad узла установлено значение True, это означает, что его необходимо различать, и все узлы, которые зависят от него, требуют_grad имеют значение True.
class Parameter(torch.Tensor):
r"""A kind of Tensor that is to be considered a module parameter.
Parameters are :class:`~torch.Tensor` subclasses, that have a
very special property when used with :class:`Module` s - when they're
assigned as Module attributes they are automatically added to the list of
its parameters, and will appear e.g. in :meth:`~Module.parameters` iterator.
Assigning a Tensor doesn't have such effect. This is because one might
want to cache some temporary state, like last hidden state of the RNN, in
the model. If there was no such class as :class:`Parameter`, these
temporaries would get registered too.
Args:
data (Tensor): parameter tensor.
requires_grad (bool, optional): if the parameter requires gradient. See
:ref:`locally-disable-grad-doc` for more details. Default: `True`
"""
def __new__(cls, data=None, requires_grad=True): # 需要计算梯度
if data is None:
data = torch.tensor([])
return torch.Tensor._make_subclass(cls, data, requires_grad)
1.3.2 Классификация
Если члены класса являются производными от класса Parameter, то nn.Module использует механизм __setattr__, чтобы приписать их к _parameters. Например, вес и смещение Linear.
def __setattr__(self, name: str, value: Union[Tensor, 'Module']) -> None:
# 省略 .....
params = self.__dict__.get('_parameters')
if isinstance(value, Parameter):
remove_from(self.__dict__, self._buffers, self._modules, self._non_persistent_buffers_set)
self.register_parameter(name, value) #
def register_parameter(self, name: str, param: Optional[Parameter]) -> None:
r"""Adds a parameter to the module.
The parameter can be accessed as an attribute using given name.
Args:
name (string): name of the parameter. The parameter can be accessed
from this module using the given name
param (Parameter): parameter to be added to the module.
"""
# 省略各种校验
if param is None:
self._parameters[name] = None
elif not isinstance(param, Parameter):
raise TypeError("cannot assign '{}' object to parameter '{}' "
"(torch.nn.Parameter or None required)"
.format(torch.typename(param), name))
elif param.grad_fn:
raise ValueError(
"Cannot assign non-leaf Tensor to parameter '{0}'. Model "
"parameters must be created explicitly. To express '{0}' "
"as a function of another Tensor, compute the value in "
"the forward() method.".format(name))
else:
self._parameters[name] = param # 这里添加了
1.3.3 Получить
Мы не можем получить переменную _parameters напрямую, мы можем получить ее только через метод параметров, который возвращает итератор.
Например:
for param in net.parameters():
print(type(param), param.size())
вывод:
<class 'torch.nn.parameter.Parameter'> torch.Size([10, 10])
<class 'torch.nn.parameter.Parameter'> torch.Size([10])
<class 'torch.nn.parameter.Parameter'> torch.Size([5, 10])
<class 'torch.nn.parameter.Parameter'> torch.Size([5])
Код параметров выглядит следующим образом.
def parameters(self, recurse: bool = True) -> Iterator[Parameter]:
r"""Returns an iterator over module parameters.
This is typically passed to an optimizer.
Args:
recurse (bool): if True, then yields parameters of this module
and all submodules. Otherwise, yields only parameters that
are direct members of this module.
Yields:
Parameter: module parameter
Example::
>>> for param in model.parameters():
>>> print(type(param), param.size())
<class 'torch.Tensor'> (20L,)
<class 'torch.Tensor'> (20L, 1L, 5L, 5L)
"""
for name, param in self.named_parameters(recurse=recurse):
yield param
Давайте взглянем на named_parameters, ядром которого является module._parameters.items(), который возвращает проходимый массив кортежей в виде списка.
def named_parameters(self, prefix: str = '', recurse: bool = True) -> Iterator[Tuple[str, Parameter]]:
r"""Returns an iterator over module parameters, yielding both the
name of the parameter as well as the parameter itself.
Args:
prefix (str): prefix to prepend to all parameter names.
recurse (bool): if True, then yields parameters of this module
and all submodules. Otherwise, yields only parameters that
are direct members of this module.
Yields:
(string, Parameter): Tuple containing the name and parameter
Example::
>>> for name, param in self.named_parameters():
>>> if name in ['bias']:
>>> print(param.size())
"""
gen = self._named_members(
lambda module: module._parameters.items(),
prefix=prefix, recurse=recurse)
for elem in gen:
yield elem
Следует отметить, что в настоящее время у нас есть два ключевых знания:
- Параметр required_grad=True в конструкторе параметров. Этот параметр означает, что Parameter должен рассчитывать градиент по умолчанию.
- Получается методом параметров, который возвращает итератор.
Так что предыдущую фигуру можно расширить.Теперь параметры SGD — это итератор, указывающий на ToyModel._parameters, а это значит, чтоОптимизатор фактически оптимизирует _параметры ToyModel напрямую.. Таким образом, мы можем удалить знак вопроса, соответствующий 4) на исходном рисунке.
+-------------------------------------------+ +------------------+
|ToyModel | | Engine |
| | forward / backward | |
| Linear(10, 10)+--> ReLU +--> Linear(10, 5)| +----------------> | Compute gradient |
| | | + |
| para_iterator = parameters() | | | |
| + ^ | | | |
| | | | +------------------+
+-------------------------------------------+ |
| | | gradient
| | |
1 ??? | | 4 update v
| | 2 ???
| |
+----------------------------------------------------------------+
|SGD | | |
| | | |
| v | |
| + |
^ +--------> self.parameters = para_iterator(ToyModel._parameters) --------->
| | | |
| | | |
| +----------------------------------------------------------------+ |
| |
<-------------------------------------------------------------------------+ v
3 step()
1.4 Linear
Torch.nn.Linear может реализовать линейное преобразование входных данных, которое обычно используется для установки полносвязного слоя.
1.4.1 Использование
Пример использования torch.nn.Linear в PyTorch выглядит следующим образом.
input = torch.randn(2,3)
linear = nn.Linear(3,4)
out = linear(input)
print(out)
# 输出结果如下
tensor([[-0.6938, 0.0543, -1.4393, -0.3554],
[-0.4653, -0.2421, -0.8236, -0.1872]], grad_fn=<AddmmBackward>)
1.4.2 Определения
Линейный конкретно определяется следующим образом, как видите, его параметры в основном
- собственный вес = Параметр().
- self.bias = Параметр().
Как видно спереди, при генерации параметра параметр required_grad=True указывает, что вес и смещение необходимы для расчета градиента.
class Linear(Module):
r"""Applies a linear transformation to the incoming data: :math:`y = xA^T + b`
This module supports :ref:`TensorFloat32<tf32_on_ampere>`.
Args:
in_features: size of each input sample
out_features: size of each output sample
bias: If set to ``False``, the layer will not learn an additive bias.
Default: ``True``
Shape:
- Input: :math:`(N, *, H_{in})` where :math:`*` means any number of
additional dimensions and :math:`H_{in} = \text{in\_features}`
- Output: :math:`(N, *, H_{out})` where all but the last dimension
are the same shape as the input and :math:`H_{out} = \text{out\_features}`.
Attributes:
weight: the learnable weights of the module of shape
:math:`(\text{out\_features}, \text{in\_features})`. The values are
initialized from :math:`\mathcal{U}(-\sqrt{k}, \sqrt{k})`, where
:math:`k = \frac{1}{\text{in\_features}}`
bias: the learnable bias of the module of shape :math:`(\text{out\_features})`.
If :attr:`bias` is ``True``, the values are initialized from
:math:`\mathcal{U}(-\sqrt{k}, \sqrt{k})` where
:math:`k = \frac{1}{\text{in\_features}}`
Examples::
>>> m = nn.Linear(20, 30)
>>> input = torch.randn(128, 20)
>>> output = m(input)
>>> print(output.size())
torch.Size([128, 30])
"""
__constants__ = ['in_features', 'out_features']
in_features: int
out_features: int
weight: Tensor
def __init__(self, in_features: int, out_features: int, bias: bool = True,
device=None, dtype=None) -> None:
factory_kwargs = {'device': device, 'dtype': dtype}
super(Linear, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))
if bias:
self.bias = Parameter(torch.empty(out_features, **factory_kwargs))
else:
self.register_parameter('bias', None)
self.reset_parameters()
def reset_parameters(self) -> None:
init.kaiming_uniform_(self.weight, a=math.sqrt(5))
if self.bias is not None:
fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight)
bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
init.uniform_(self.bias, -bound, bound)
def forward(self, input: Tensor) -> Tensor:
return F.linear(input, self.weight, self.bias)
def extra_repr(self) -> str:
return 'in_features={}, out_features={}, bias={}'.format(
self.in_features, self.out_features, self.bias is not None
)
1.4.3 Объяснение
Из предыдущей простой схемы вычислений мы можем знать, что обратный расчет torch.nn.Linear — это AddmmBackward.
struct TORCH_API AddmmBackward : public TraceableFunction {
using TraceableFunction::TraceableFunction;
variable_list apply(variable_list&& grads) override;
std::string name() const override { return "AddmmBackward"; }
void release_variables() override {
std::lock_guard<std::mutex> lock(mutex_);
mat2_.reset_data();
mat1_.reset_data();
}
std::vector<int64_t> mat1_sizes;
std::vector<int64_t> mat1_strides;
SavedVariable mat2_;
at::Scalar alpha;
SavedVariable mat1_;
std::vector<int64_t> mat2_sizes;
std::vector<int64_t> mat2_strides;
at::Scalar beta;
};
Мы нашли определение addmm из кода, и в его комментарии говорится, что это операция умножения матриц.
def addmm(mat: Tensor, mat1: Tensor, mat2: Tensor,
beta: float = 1., alpha: float = 1.) -> Tensor:
r"""
This function does exact same thing as :func:`torch.addmm` in the forward,
except that it supports backward for sparse matrix :attr:`mat1`. :attr:`mat1`
need to have `sparse_dim = 2`. Note that the gradients of :attr:`mat1` is a
coalesced sparse tensor.
Args:
mat (Tensor): a dense matrix to be added
mat1 (Tensor): a sparse matrix to be multiplied
mat2 (Tensor): a dense matrix to be multiplied
beta (Number, optional): multiplier for :attr:`mat` (:math:`\beta`)
alpha (Number, optional): multiplier for :math:`mat1 @ mat2` (:math:`\alpha`)
"""
return torch._sparse_addmm(mat, mat1, mat2, beta=beta, alpha=alpha)
Теперь мы можем продолжить расширение.
- Вес и смещение в Linear относятся к типу Parameter.
- Параметр required_grad=True в конструкторе параметров. Этот параметр означает, что Parameter должен рассчитывать градиент по умолчанию.
- Таким образом, вес и смещение Linear требуют, чтобы движок вычислял его градиент.
- ToyModel's
_parameters
Переменные-члены получаются с помощью метода параметров, который возвращает итератор.- Этот итератор используется в качестве параметра для построения оптимизатора SGD.
- Параметры оптимизатора SGD теперь представляют собой итератор, указывающий на ToyModel._parameters. это означаетОптимизатор фактически оптимизирует _параметры ToyModel напрямую., например, это параметры полносвязного слоя, а графику соответствуют стрелки, указывающие на параметры(), выдаваемые двумя Linear.
+--------------------------------------------------+ +------------------+
| ToyModel | | Engine |
| +-------------------+ +------------+ |forward / backward | |
| | Linear(10, 10) +--> ReLU +-->+Linear(10,5)| +-----------------> | Compute gradient |
| | | | | | | + |
| | weight=Parameter | | weight | | | | |
| | +----------+ | | | | | |
| | bias=Parameter | | | bias | | +------------------+
| | | | | | | |
| +-------------------+ | +--+---------+ | 2 | gradient
| | | | |
| | | | v
| v v | ???
| para_iterator = parameters() |
| + ^ |
| | | |
| | | |
+--------------------------------------------------+
| |
1 ??? | | 4 update
| |
| |
+----------------------------------------------------------------+
|SGD | | |
| | | |
| v | |
| + |
^ +--------> self.parameters = para_iterator(ToyModel._parameters) +-------->
| | | |
| | | |
| +----------------------------------------------------------------+ |
| |
<-------------------------------------------------------------------------+ v
3 step()
0x02 Базовый класс оптимизатора
Optimizer
является базовым классом для всех оптимизаторов и имеет следующие основные общедоступные методы:
- add_param_group : добавить группу обучаемых параметров.
- шаг: выполнить операцию обновления параметра.
- zero_grad : Обнуление градиента из предыдущей итерации перед обратным распространением градиента.
- state_dict : возвращает параметры и состояния, представленные структурой dict.
- load_state_dict : загрузить параметры и состояние, представленные структурой dict.
2.1 Инициализация
В функции инициализации оптимизатора выполняются следующие операции:
- Параметры инициализации включают в себя: обучаемые параметры (params) и гиперпараметры (по умолчанию).
- Сохраните глобальные параметры (гиперпараметры), такие как lr, momentun и т. д., в self.defaults.
- Сохраните текущее состояние оптимизатора в self.state.
- Сохраните все переменные для оптимизации в self.param_groups.
class Optimizer(object):
def __init__(self, params, defaults):
torch._C._log_api_usage_once("python.optimizer")
self.defaults = defaults # 保存 lr, momentun 等全局参数
self._hook_for_profile()
if isinstance(params, torch.Tensor): # params必须是字典或者tensors
raise TypeError("params argument given to the optimizer should be "
"an iterable of Tensors or dicts, but got " +
torch.typename(params))
self.state = defaultdict(dict) # 保存优化器当前状态
self.param_groups = [] # 所有待优化的参数,其每一项是一个字典,对应一组待优化参数和其他相关参数
param_groups = list(params) # 需要被优化的变量,是__init__ 传入的参数
if len(param_groups) == 0:
raise ValueError("optimizer got an empty parameter list")
if not isinstance(param_groups[0], dict):
# 将参数转换为字典
param_groups = [{'params': param_groups}] # param_groups 是一个列表,其中一项是字典形式,优化变量被保存在其中。
for param_group in param_groups:
self.add_param_group(param_group) # 把param_groups所有项都加到self.param_groups之中
2.2 Добавьте переменные для оптимизации
add_param_group используется в приведенном выше коде, мы рассмотрим эту функцию далее.
add_param_group Добавить обучаемые параметры для разных групп. Код выглядит следующим образом (большая часть проверочного кода опущена). в,param_groups
Цель состоит в том, чтобы получить доступ к оптимизируемым переменным по принципу ключ-значение, что особенно полезно при точной настройке.
def add_param_group(self, param_group):
r"""Add a param group to the :class:`Optimizer` s `param_groups`.
This can be useful when fine tuning a pre-trained network as frozen layers can be made
trainable and added to the :class:`Optimizer` as training progresses.
Args:
param_group (dict): Specifies what Tensors should be optimized along with group
specific optimization options.
"""
assert isinstance(param_group, dict), "param group must be a dict"
params = param_group['params'] # 得到待优化的变量
if isinstance(params, torch.Tensor):
param_group['params'] = [params] # 构建一个列表,其中就是待优化的变量
elif isinstance(params, set):
raise TypeError('optimizer parameters need to be organized in ordered collections, but '
'the ordering of tensors in sets will change between runs. Please use a list instead.')
else:
param_group['params'] = list(params)
# 省略校验,比如必须是tensor类型,而且是叶子节点
for name, default in self.defaults.items(): # 缺省参数也加入到 param_group 之中
if default is required and name not in param_group:
raise ValueError("parameter group didn't specify a value of required optimization parameter " +
name)
else:
param_group.setdefault(name, default) # 所有组都设置同样的缺省参数(超参数)
# 用set来去重
params = param_group['params']
param_set = set()
for group in self.param_groups:
param_set.update(set(group['params']))
# 更新自身的参数组中
self.param_groups.append(param_group) # 加入到param_groups
2.3 Примеры переменных, которые необходимо оптимизировать
Давайте воспользуемся следующим кодом для вывода param_groups и посмотрим.
net = nn.Linear(3, 3)
nn.init.constant_(net.weight, val=10)
nn.init.constant_(net.bias, val=5)
optimizer = optim.SGD(net.parameters(), lr=0.025)
print(optimizer.param_groups)
Результат выглядит следующим образом: первые 3 x 3 — это весовая матрица для сети, а 1 x 3 — матрица смещения.
[
{'params':
[
Parameter containing: # 权重矩阵
tensor([[10., 10., 10.],
[10., 10., 10.],
[10., 10., 10.]], requires_grad=True),
Parameter containing: # 偏置矩阵
tensor([5., 5., 5.], requires_grad=True)
],
'lr': 0.025,
'momentum': 0,
'dampening': 0,
'weight_decay': 0,
'nesterov': False
}
]
2.4 Состояние оптимизатора
2.4.1 Определения
PyTorch state_dict — это объект словаря Python.
-
Для модели state_dict установит отношение сопоставления между каждым слоем и параметрами (такими как веса и смещения), которые необходимо изучить в процессе обучения.В state_dict модели будут сохранены только те слои, параметры которых можно обучить, например сверточные слои, линейные слои и т. д.
-
Для оптимизатора state_dict — это его информация о состоянии, которая включает в себя два набора информации:
- state : словарь, содержащий текущее состояние оптимизатора (то есть последняя кэшированная переменная, вычисленная во время обновления переменной).
- Ключ словаря является индексом кеша.
- Значением словаря также является словарь, ключ — имя переменной кэша, а значение — соответствующий тензор.
- param_groups : словарь, содержащий все группы параметров.
- state : словарь, содержащий текущее состояние оптимизатора (то есть последняя кэшированная переменная, вычисленная во время обновления переменной).
def state_dict(self):
r"""Returns the state of the optimizer as a :class:`dict`.
It contains two entries:
* state - a dict holding current optimization state. Its content
differs between optimizer classes.
* param_groups - a dict containing all parameter groups
"""
# Save order indices instead of Tensors
param_mappings = {}
start_index = 0
def pack_group(group):
nonlocal start_index
# 'params'采用不同规则
packed = {k: v for k, v in group.items() if k != 'params'}
param_mappings.update({id(p): i for i, p in enumerate(group['params'], start_index)
if id(p) not in param_mappings})
# 保存了参数的id,而并非参数的值
packed['params'] = [param_mappings[id(p)] for p in group['params']]
start_index += len(packed['params'])
return packed
# 对self.param_groups进行遍历,进行pack
param_groups = [pack_group(g) for g in self.param_groups]
# 将state中的所有Tensor替换为相应的 use order indices
# Remap state to use order indices as keys
packed_state = {(param_mappings[id(k)] if isinstance(k, torch.Tensor) else k): v
for k, v in self.state.items()}
return { # 返回字典形式
'state': packed_state, # 状态
'param_groups': param_groups, # 待优化的参数
}
2.4.2 Пример 1
Мы добавили следующий оператор печати в примере 1, чтобы увидеть внутренние переменные оптимизатора:
# print model's state_dict
print('Model.state_dict:')
for param_tensor in model.state_dict():
print(param_tensor, '\t', model.state_dict()[param_tensor].size())
# print optimizer's state_dict
print('Optimizer,s state_dict:')
for var_name in optimizer.state_dict():
print(var_name, '\t', optimizer.state_dict()[var_name])
Результат выглядит следующим образом:
Model.state_dict:
net1.weight torch.Size([10, 10])
net1.bias torch.Size([10])
net1.weight torch.Size([10, 10])
net2.bias torch.Size([5])
Optimizer,s state_dict:
state {}
param_groups [{'lr': 0.001, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0, 1, 2, 3]}]
2.4.3 Пример 2
Пример 2 использует SGD для оптимизации функции.
from math import pi
import torch.optim
x = torch.tensor([pi/2,pi/3],requires_grad=True)
optimizer = torch.optim.SGD([x,],lr=0.2,momentum=0.5)
for step in range(11):
if step:
optimizer.zero_grad()
f.backward()
optimizer.step()
for var_name in optimizer.state_dict():
print(var_name, '\t', optimizer.state_dict()[var_name])
f=-((x.sin()**3).sum())**3
Выходные результаты следующие, и можно увидеть процесс оптимизации.
state {0: {'momentum_buffer': tensor([ 1.0704e-06, -9.1831e+00])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([-1.2757e-06, -4.0070e+00])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([-3.4580e-07, -4.7366e-01])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([7.3855e-07, 1.3584e+00])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([7.2726e-07, 1.6619e+00])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([-3.1580e-07, 8.4152e-01])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([2.3738e-07, 5.8072e-01])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([5.2412e-07, 8.4104e-01])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([-5.1160e-07, 1.9660e+00])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
state {0: {'momentum_buffer': tensor([4.9517e-07, 7.2053e+00])}}
param_groups [{'lr': 0.2, 'momentum': 0.5, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}]
Давайте обновим его и подтвердим, что имя переменной-члена внутри SGDparam_groups, что является целью оптимизации оптимизатора, который указывает на итератор ToyModel._parameters .
+-------------------------------------------------+ +------------------+
|ToyModel | | Engine |
| +------------------+ +------------+ |forward / backward | |
| |Linear(10, 10) +--> ReLU +-->+Linear(10,5)| +-----------------> | Compute gradient |
| | | | | | | + |
| | weight=Parameter| | weight | | | | |
| | +-----------+ | bias | | | | |
| | bias=Parameter | | +--+---------+ | +------------------+
| | | | | | |
| +------------------+ | | | 2 | gradient
| v v | |
| self._parameters | v
| + | ???
| | |
| | |
| v |
| para_iterator = parameters() |
| + ^ |
| | | |
| | | |
+-------------------------------------------------+
| |
1 ??? | | 4 update
| |
+----------------------------------------------------------------+
|SGD | | |
| | | |
| v | |
| + |
^ +-------> self.param_groups = para_iterator(ToyModel._parameters) -------->
| | | |
| | | |
| +----------------------------------------------------------------+ |
| |
<-------------------------------------------------------------------------+ v
3 step()
0x03 SGD
Рассмотрим подробнее оптимизатор, использующий SGD. SGD (стохастический градиентный спуск) — это стохастический градиентный спуск, пакетная версия градиентного спуска. Для обучающего набора данных он разделен на n пакетов, каждый пакет содержит m выборок. Каждое обновление использует пакет данных, а не весь обучающий набор.
3.1 Определения
SGD определяется следующим образом, в основном для проверки и установки значений по умолчанию.
class SGD(Optimizer):
def __init__(self, params, lr=required, momentum=0, dampening=0,
weight_decay=0, nesterov=False):
if lr is not required and lr < 0.0:
raise ValueError("Invalid learning rate: {}".format(lr))
if momentum < 0.0:
raise ValueError("Invalid momentum value: {}".format(momentum))
if weight_decay < 0.0:
raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
weight_decay=weight_decay, nesterov=nesterov)
if nesterov and (momentum <= 0 or dampening != 0):
raise ValueError("Nesterov momentum requires a momentum and zero dampening")
super(SGD, self).__init__(params, defaults)
def __setstate__(self, state):
super(SGD, self).__setstate__(state)
for group in self.param_groups:
group.setdefault('nesterov', False)
3.2 Анализ
Как видно из комментариев, SGD реализует алгоритм стохастического градиентного спуска (опционально с импульсом). Нестеровский импульс основан на[On the importance of initialization and momentum in deep learning](http://www.cs.toronto.edu/%7Ehinton/absps/momentum.pdf)
.алгоритм.
Пример использования следующий:
Example:
>>> optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
>>> optimizer.zero_grad()
>>> loss_fn(model(input), target).backward()
>>> optimizer.step()
Реализация PyTorch SGD с Momentum/Nesterov отличается от реализации Sutskever и др. и других фреймворков.
Например, PyTorch использует следующий метод для реализации особого случая Momentum:
Другие фреймворки используют:
3.3 step
Функция пошагового метода заключается в оптимизации переменных с помощью определенного алгоритма. Этот метод в основном завершает обновление параметров модели.
@torch.no_grad()
def step(self, closure=None):
"""Performs a single optimization step.
Args:
closure (callable, optional): A closure that reevaluates the model
and returns the loss.
"""
# 使用 closure 重新计算loss
loss = None
if closure is not None:
with torch.enable_grad():
loss = closure()
# 使用计算得到的梯度更新变量
# self.param_groups 就是我们传入的参数列表
for group in self.param_groups: # 每一个group是一个dict, 其包含每组参数所需的必要参数
params_with_grad = []
d_p_list = []
momentum_buffer_list = []
# 本组参数更新所必需的设置
weight_decay = group['weight_decay']
momentum = group['momentum']
dampening = group['dampening']
nesterov = group['nesterov']
lr = group['lr']
for p in group['params']: # 遍历本组所有需要更新的参数
if p.grad is not None:
params_with_grad.append(p)
d_p_list.append(p.grad)
state = self.state[p]
if 'momentum_buffer' not in state:
momentum_buffer_list.append(None)
else:
momentum_buffer_list.append(state['momentum_buffer'])
F.sgd(params_with_grad,
d_p_list,
momentum_buffer_list,
weight_decay=weight_decay,
momentum=momentum,
lr=lr,
dampening=dampening,
nesterov=nesterov)
# update momentum_buffers in state
for p, momentum_buffer in zip(params_with_grad, momentum_buffer_list):
state = self.state[p]
state['momentum_buffer'] = momentum_buffer
return loss
Функция sgd выглядит следующим образом:
def sgd(params: List[Tensor],
d_p_list: List[Tensor],
momentum_buffer_list: List[Optional[Tensor]],
*,
weight_decay: float,
momentum: float,
lr: float,
dampening: float,
nesterov: bool):
r"""Functional API that performs SGD algorithm computation.
See :class:`~torch.optim.SGD` for details.
"""
for i, param in enumerate(params):
d_p = d_p_list[i]
# 正则化及动量累积
if weight_decay != 0:
d_p = d_p.add(param, alpha=weight_decay)
if momentum != 0:
buf = momentum_buffer_list[i]
if buf is None:
# 历史更新量
buf = torch.clone(d_p).detach()
momentum_buffer_list[i] = buf
else:
# 通过buf更新了self.state
buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
if nesterov:
d_p = d_p.add(buf, alpha=momentum)
else:
d_p = buf
# 更新当前组学习参数 w.data -= w.grad*lr
param.add_(d_p, alpha=-lr) # add_ 会更改对象数值
3.4 Анализ переменных
Далее мы проанализируем глобальные параметры следующим образом.
3.4.1 lr
Это скорость обучения, известная концепция.
3.4.2 dampening
демпфирование действует на частную производную и используется для корректировки текущего веса градиента в импульсе SGD.
Соответствующая формула выглядит следующим образом:
Соответствующий код:
buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
3.4.3 weight_decay
weight_decay — это штрафной коэффициент L2, который изменяет частную производную со значением текущего обучаемого параметра p.
Частная производная обучаемого параметра p, который необходимо обновить, равна
Соответствующий код:
if weight_decay != 0:
d_p = d_p.add(param, alpha=weight_decay)
3.4.4 nesterov
Включить ли моментум Nesterov из исходного кода pytorch, когда для Nesterov установлено значение True, импульс и v_t снова используются на основе v_t, полученного выше.
if (nesterov) {
d_p = d_p.add(buf, momentum);
} else {
d_p = buf;
}
3.4.5 Momentum
Импульс: Из физики, переводится как импульс или импульс. Функция состоит в том, чтобы объединить последнее обновление с текущим градиентом, чтобы оптимизировать обновление текущего веса.
Причина введения заключается в том, что веса инициализации обучающей сети могут быть неподходящими, в результате чего в процессе обучения возникает локальный минимум, а глобальный оптимум не найден.
Введение импульса может в определенной степени решить эту проблему. Импульс имитирует инерцию движущегося объекта, представляя кумулятивный эффект силы во времени. При обновлении направление предыдущего обновления в определенной степени сохраняется, а направление обновления корректируется в сочетании с текущим градиентом. Чем больше импульс, тем больше энергии преобразуется в потенциальную энергию, что может повысить стабильность, а также обеспечить более быстрое обучение, так что с большей вероятностью он избавится от локальной вогнутой области и войдет в глобальную вогнутую область.
Первоначальная формула обновления веса выглядит следующим образом:
Здесь w — вес, Lr — скорость обучения, а dw — производная от w.
Формула обновления веса после введения импульса выглядит следующим образом:
Здесь импульс — это импульс, а v — скорость. Эта формула означает добавление произведения v и импульса из последнего обновления. Когда направление этого градиентного спуска -Lr * dw совпадает с направлением последнего обновления v, последнее обновление v может играть роль положительного ускорения. Когда направление этого градиентного спуска -Lr * dw противоположно направлению последнего обновления v, последнее обновление v может играть роль замедления.
Код соответствует следующему:
if momentum != 0:
buf = momentum_buffer_list[i]
if buf is None:
buf = torch.clone(d_p).detach()
momentum_buffer_list[i] = buf
else:
buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
if nesterov:
d_p = d_p.add(buf, alpha=momentum)
else:
d_p = buf
0x04 Визуализация
4.1 Текущие проблемы
На данный момент у нас все еще есть несколько нерешенных проблем, которые подчеркнуты ниже.
-
Создайте оптимизатор из параметров модели
-
- использовать
optimizer = optim.SGD(params=net.parameters(), lr = 1)
Создан таким образом, что кажется, что params присваивается внутренней переменной-члену оптимизатора (скажем, она называется параметрами).
- использовать
- Модель включает два полносвязных слоя Линейный, как эти слои обновляют параметры? ? ?
- Вес и смещение в Linear относятся к типу Parameter.
- Параметр required_grad=True в конструкторе параметров. Этот параметр означает, что Parameter должен рассчитывать градиент по умолчанию.
- Таким образом, вес и смещение Linear требуют, чтобы движок вычислял его градиент.
- ToyModel's
_parameters
Переменные-члены получаются с помощью метода параметров, который возвращает итератор.- Этот итератор используется в качестве параметра для построения оптимизатора SGD.
- Параметры оптимизатора SGD теперь представляют собой итератор, указывающий на ToyModel._parameters. это означаетОптимизатор фактически оптимизирует _параметры ToyModel напрямую..
-
-
Движок вычисляет градиент
- Как я могу гарантировать, что Linear может вычислять градиенты?
- И вес, и смещение имеют тип параметра, а градиенты должны рассчитываться по умолчанию.
- 2) Как для модели рассчитанный градиент соответствует линейным параметрам? Где накапливаются эти рассчитанные движком градиенты? ? ?
- Как я могу гарантировать, что Linear может вычислять градиенты?
-
Оптимизатор оптимизирует параметры:
-
- Вызовите step для оптимизации, а целью оптимизации является внутренняя переменная-член self.parameters оптимизатора.
- self.parameters — это итератор, указывающий на ToyModel._parameters. это означаетОптимизатор фактически оптимизирует _параметры ToyModel напрямую..
-
-
Оптимизатор обновляет модель:
-
- Обновление цели оптимизации (self.parameters) фактически воздействует непосредственно на параметры модели (такие как Linear).
-
Распечатаем выходные данные и увидим, что next_functions на самом деле три, что указывает на то, что предыдущая легенда нами упрощена, и нам нужно сделать дальнейшую визуализацию.
outputs = {Tensor: 10}
T = {Tensor: 5}
data = {Tensor: 10}
device = {device} cpu
dtype = {dtype} torch.float32
grad = {NoneType} None
grad_fn = {AddmmBackward}
metadata = {dict: 0} {}
next_functions = {tuple: 3}
0 = {tuple: 2} (<AccumulateGrad object at 0x7f9c3e3bd588>, 0)
1 = {tuple: 2} (<ReluBackward0 object at 0x7f9c3e5178d0>, 0)
2 = {tuple: 2} (<TBackward object at 0x7f9c3e517908>, 0)
__len__ = {int} 3
requires_grad = {bool} True
is_cuda = {bool} False
is_leaf = {bool} False
is_meta = {bool} False
is_mkldnn = {bool} False
is_mlc = {bool} False
is_quantized = {bool} False
is_sparse = {bool} False
is_sparse_csr = {bool} False
is_vulkan = {bool} False
is_xpu = {bool} False
layout = {layout} torch.strided
name = {NoneType} None
names = {tuple: 2} (None, None)
ndim = {int} 2
output_nr = {int} 0
requires_grad = {bool} True
4.2 Сеть визуализации PyTorchViz
Мы используем PyTorchViz для демонстрации сети.
Сначала установите библиотеку:
pip install torchviz
Затем добавляем визуализацию кода, используем функцию визуализацииmake_dot()
чтобы получить объект рисования. После запуска папка данных в том же корневом каталоге кода создаст.gv
файл и.png
документ,.gv
Файл представляет собой код скрипта для инструмента Graphviz для создания изображения,.png
да.gv
Изображение, созданное компиляцией файла. По умолчанию программа автоматически открывает файлы .png.
import torch
import torch.nn as nn
import torch.optim as optim
from torchviz import make_dot
class ToyModel(nn.Module):
def __init__(self):
super(ToyModel, self).__init__()
self.net1 = nn.Linear(10, 10)
self.relu = nn.ReLU()
self.net2 = nn.Linear(10, 5)
def forward(self, x):
return self.net2(self.relu(self.net1(x)))
net = ToyModel()
print(net) # 顺便打印一下看看
optimizer = optim.SGD(params=net.parameters(), lr = 1)
optimizer.zero_grad()
input = torch.randn(10,10)
outputs = net(input)
outputs.backward(outputs)
optimizer.step()
NetVis = make_dot(outputs, params=dict(list(net.named_parameters()) + [('x', input)]))
NetVis.format = "bmp" # 文件格式
NetVis.directory = "data" # 文件生成的文件夹
NetVis.view() # 生成文件
вывод.
ToyModel(
(net1): Linear(in_features=10, out_features=10, bias=True)
(relu): ReLU()
(net2): Linear(in_features=10, out_features=5, bias=True)
)
Пример выглядит следующим образом:
Мы обнаружили, что предыдущий скетч игнорировал ключевое звено AccumulateGrad, и мы проанализируем его далее.
0x05 AccumulateGrad
5.1 Принцип
Давайте сначала обрисуем принципы PyTorch.
Концептуально autograd записывает вычислительный граф. В графе есть два типа узлов: листовые узлы и нелистовые узлы.
Узлы, созданные пользователем, называются листовыми узлами.,Например:
a=torch.tensor([1.0])
运行时变量为:
a = {Tensor: 1} tensor([1.])
T = {Tensor: 1} tensor([1.])
data = {Tensor: 1} tensor([1.])
device = {device} cpu
dtype = {dtype} torch.float32
grad = {NoneType} None
grad_fn = {NoneType} None
is_cuda = {bool} False
is_leaf = {bool} True
requires_grad = {bool} False
Но в настоящее время не может быть дифференцирован При создании тензора, если для require_grad установлено значение True, Pytorch знает, что тензор должен быть автоматически дифференцирован.
a=torch.tensor([1.0], requires_grad = True)
运行时变量为:
a = {Tensor: 1} tensor([1.], requires_grad=True)
T = {Tensor: 1} tensor([1.], grad_fn=<PermuteBackward>)
data = {Tensor: 1} tensor([1.])
device = {device} cpu
dtype = {dtype} torch.float32
grad = {NoneType} None
grad_fn = {NoneType} None
is_cuda = {bool} False
is_leaf = {bool} True
requires_grad = {bool} True
shape = {Size: 1} 1
PyTorch записывает историю операций каждого шага тензора, тем самым создавая концептуальный ориентированный ациклический граф, Листовые узлы ациклического графа являются входными тензорами модели, а его корни — выходными тензорами модели. Пользователю не нужно кодировать все пути выполнения графа, потому что пользователь запускает то, что он хочет дифференцировать позже. Проследив этот граф от корня до листа, пользователь может использовать правило вывода по цепочке для автоматического вычисления градиентов.
Внутри autograd представляет этот график как график объектов «Функция» или «Узел» (реальные выражения), которые можно оценить с помощью метода применения.
Во время обратного распространения механизм автоградации отслеживает граф от корневого узла (то есть выходного узла прямого распространения), так что градиент всех листовых узлов может быть рассчитан с использованием правила вывода цепочки. Каждая рабочая функция прямого распространения имеет соответствующую функцию обратного распространения, которая используется для вычисления градиента каждой переменной.
На обратном графе функция вычисления обратного распространения, соответствующая тензору листового узла, который необходимо дифференцировать, называется AccumulateGrad, и его градиент накапливается, и на производной этого тензора будут накапливаться множественные производные, например:
a=torch.tensor([5.0], requires_grad = True)
b = torch.tensor([3.0], requires_grad = True)
c = a + b
Соответствует:
В соответствии с нашим примером все экземпляры Linear явно определяются пользователем, и все они являются конечными узлами.
5.2 AccumulateGrad
5.2.1 Определения
Определяемый следующим образом, accumulationGrad на самом деле:
- Сначала накапливайте градиенты.
- Затем вызовите входящую функцию update_grad, чтобы обновить градиент.
struct TORCH_API AccumulateGrad : public Node {
explicit AccumulateGrad(Variable variable_);
variable_list apply(variable_list&& grads) override;
static at::Tensor callHooks(
const Variable& variable,
at::Tensor new_grad) {
for (auto& hook : impl::hooks(variable)) {
new_grad = (*hook)({new_grad})[0];
}
return new_grad;
}
template <typename T>
static void accumulateGrad(
const Variable& variable,
at::Tensor& variable_grad,
const at::Tensor& new_grad,
size_t num_expected_refs,
const T& update_grad) { // 传入的更新梯度函数
if (!variable_grad.defined()) {
// 忽略
} else if (!GradMode::is_enabled()) {
if (variable_grad.is_sparse() && !new_grad.is_sparse()) {
auto result = new_grad + variable_grad;
update_grad(std::move(result));
} else if (!at::inplaceIsVmapCompatible(variable_grad, new_grad)) {
auto result = variable_grad + new_grad;
update_grad(std::move(result));
} else {
variable_grad += new_grad; // 进行累积
}
} else {
at::Tensor result;
if (variable_grad.is_sparse() && !new_grad.is_sparse()) {
// CPU backend throws an error on sparse + dense, so prefer dense + sparse here.
result = new_grad + variable_grad; // 进行累积
} else {
// Assumes operator+ result typically matches strides of first arg,
// and hopes variable_grad was originally created obeying layout contract.
result = variable_grad + new_grad; // 进行累积
}
update_grad(std::move(result));
}
}
Variable variable;
};
5.2.2 apply
При вызове применения есть два предостережения:
- Входящая функция обновления { grad = std::move(grad_update); } для обновления градиента.
- mutable_grad получает переменную-член градиента тензора.
Tensor& mutable_grad() const {
return impl_->mutable_grad();
}
/// Accesses the gradient `Variable` of this `Variable`.
Variable& mutable_grad() override {
return grad_;
}
Конкретный код выглядит следующим образом:
auto AccumulateGrad::apply(variable_list&& grads) -> variable_list {
check_input_variables("AccumulateGrad", grads, 1, 0);
if (!grads[0].defined())
return {};
if (variable.grad_fn())
throw std::logic_error(
"leaf variable has been moved into the graph interior");
if (!variable.requires_grad())
return {};
at::Tensor new_grad = callHooks(variable, std::move(grads[0]));
std::lock_guard<std::mutex> lock(mutex_);
at::Tensor& grad = variable.mutable_grad(); // 得到变量的mutable_grad
accumulateGrad(
variable,
grad,
new_grad,
1 + !post_hooks().empty() /* num_expected_refs */,
[&grad](at::Tensor&& grad_update) { grad = std::move(grad_update); });
return variable_list();
}
Конкретная логика блок-схемы выглядит следующим образом:
AccumulateGrad Tensor AutogradMeta
+ + +
| | |
| | |
| | |
v | |
apply(update_grad) | |
+ | |
| | |
| | |
| | |
v | |
accumulateGrad | |
+ | |
| | |
| result = variable_grad + new_grad | |
| | |
v result v v
update_grad +----------------------------> mutable_grad +---> grad_
Или следующим образом, для листового тензора AccumulateGrad будет вызываться для накопления градиентов во время обратного вычисления, а затем обновляться до grad_ листового тензора:
+----------------------------------------------+ +-------------------------+
|Tensor | |TensorImpl |
| | | |
| | bridge | |
| <TensorImpl, UndefinedTensorImpl> impl_ +-----------> | autograd_meta_ +---------+
| | | | |
| | | | |
+----------------------------------------------+ +-------------------------+ |
|
|
|
+-------------------------+ |
| AutogradMeta | <-----------------------------------------------------------+
| |
| |
| | +------------------------------------------------+
| | | AccumulateGrad |
| grad_fn_ +--------------------> | |
| | | |
| | | apply(grads) { |
| | | |
| grad_accumulator_ | | accumulateGrad(new_grad) { |
| | | |
| | | result = variable_grad + new_grad |
| | update | |
| grad_ <--------------------------------+ update_grad(result) |
| | | |
| | | } |
| | | } |
| | | |
| | | |
+-------------------------+ +------------------------------------------------+
Теперь мы знаем, что градиенты накапливаются на grad_ листовых узлах, но как эти градиенты обновляют параметры модели?
5.3 Комбинирование оптимизаторов
Вернемся к ступенчатой функции SGD, выделим только ключевые части, мы видим, что она получает градиент параметров в модели, а затем обновляет параметры модели.
@torch.no_grad()
def step(self, closure=None):
# 使用 closure 重新计算loss
# 使用计算得到的梯度更新变量
# self.param_groups 就是我们传入的参数列表
for group in self.param_groups: # 每一个group是一个dict, 其包含每组参数所需的必要参数
for p in group['params']: # 遍历本组所有需要更新的参数
if p.grad is not None: # 获取到模型参数的梯度
params_with_grad.append(p) # 利用梯度进行优化
d_p_list.append(p.grad)
# momentum 相关
F.sgd(params_with_grad, # 更新当前组学习参数 w.data -= w.grad*lr,使用 param.add_(d_p, alpha=-lr) 来更新参数
d_p_list,
momentum_buffer_list,
weight_decay=weight_decay,
momentum=momentum,
lr=lr,
dampening=dampening,
nesterov=nesterov)
# update momentum_buffers in state
return loss
0x06 Сводка
Подведем итоги по порядку построения оптимизатора на основе параметров модели ---> движок вычисляет градиент ---> оптимизатор оптимизирует параметры ---> оптимизатор обновляет модель.
-
Создайте оптимизатор из параметров модели
-
- использовать
optimizer = optim.SGD(params=net.parameters(), lr = 1)
Создайте так, чтобы params присваивались внутренней переменной-члену оптимизатора param_groups.
- использовать
- Модель состоит из двух Линейных, как эти слои обновляют параметры?
- Вес и смещение в Linear относятся к типу Parameter.
- Параметр required_grad=True в конструкторе параметров. Этот параметр означает, что Parameter должен рассчитывать градиент по умолчанию.
- Таким образом, вес и смещение Linear требуют, чтобы движок вычислял его градиент.
- вес, смещение добавляются к ToyModel's
_parameters
в переменных-членах.
- ToyModel's
_parameters
Переменные-члены получаются с помощью метода параметров, который возвращает итератор.- Используйте этот итератор в качестве аргумента для построения оптимизатора SGD.
- Параметры оптимизатора SGD теперь представляют собой итератор, указывающий на ToyModel._parameters. это означаетОптимизатор фактически оптимизирует _параметры ToyModel напрямую..
- Таким образом, оптимизатор должен напрямую оптимизировать и обновлять вес и смещение Linear. По сути, оптимизатор — это просто набор кодов, конкретные оптимизации необходимо указать при построении, он также может оптимизировать параметры модели или оптимизировать другие переменные, указанные пользователем.
- Вес и смещение в Linear относятся к типу Parameter.
-
-
Движок вычисляет градиент
- Как я могу гарантировать, что Linear может вычислять градиенты?
- И вес, и смещение имеют тип параметра, а градиенты должны рассчитываться по умолчанию.
-
- Итак, рассчитайте вес, градиент смещения.
- Как для модели рассчитанный градиент соответствует линейному параметру? Где накапливаются эти рассчитанные движком градиенты?
- В соответствии с нашим примером все экземпляры Linear явно определяются пользователем, поэтому все они являются конечными узлами.
-
- Листовой узел накапливает градиент в тензоре параметров модели через AccumulateGrad.
autograd_meta_.grad_
среди.
- Листовой узел накапливает градиент в тензоре параметров модели через AccumulateGrad.
- Как я могу гарантировать, что Linear может вычислять градиенты?
-
Оптимизатор оптимизирует параметры:
-
- Вызовите step для оптимизации, а целью оптимизации является внутренняя переменная-член self.parameters оптимизатора.
- self.parameters — это итератор, указывающий на ToyModel._parameters. это означаетОптимизатор фактически оптимизирует _параметры ToyModel напрямую..
-
-
Оптимизатор обновляет модель:
-
- Обновление цели оптимизации (self.parameters) фактически воздействует непосредственно на параметры модели (такие как линейный вес, смещение).
-
Как показано на рисунке:
+---------------------------------------------------------------------+
| ToyModel |
| +---------------------------------+ +------------+ | +------------------+
| | Linear(10, 10) +------> ReLU +-->+Linear(10,5)| | | Engine |
| | | | | |forward / backward | |
| | weight=Parameter | | weight | +-----------------> | Compute gradient |
| | +---------------+ | bias | | | + |
| | +----------------------------+ | | +--+---------+ | | | |
| | | bias=Parameter | | | | | | | |
| | | | | | | | +------------------+
| | | | | | | | 3 accumulate |
| | | autograd_meta_.grad_ <----------------------------------------------------+ 2 | gradient
| | | | | | | | | |
| | | data | | | | | | v
| | | | | v v | |
| | | | | self._parameters | | +------------------+
| | +----------------------------+ | + | | | AccumulateGrad |
| +---------------------------------+ | | | | |
| | | | | |
| v | 5 update -----------+ apply() |
| para_iterator = parameters() <----------------+ | |
| + | | | |
| | | | +------------------+
| | | |
+---------------------------------------------------------------------+ |
1 | |
| |
+---------------------------------------------------------------------------+
| SGD | | |
| | | |
| v + |
| 4 step() |
^-------------> self.param_groups = para_iterator(ToyModel._parameters) +---------------->
| | | |
| | | |
| +---------------------------------------------------------------------------+ |
| |
<--------------------------------------------------------------------------------------+ v
Телефон такой:
На этом анализ обычного оптимизатора завершен, в следующей главе мы проанализируем оптимизатор, работающий с параллельными данными.
0xEE Личная информация
★★★★★★Думая о жизни и технологиях★★★★★★
Публичный аккаунт WeChat:мысли Росси
ссылка 0xFF
Чтение исходного кода torch.optim.optimizer и гибкое использование
чтение исходного кода pytorch (2) принцип оптимизатора
Сводка и сравнение различных методов оптимизации (sgd/momentum/Nesterov/adagrad/adadelta)
[Оптимизатор] Алгоритм оптимизатора и реализация PyTorch (1): нестираемый SGD
Возьмите optim.SGD в качестве примера, чтобы представить оптимизатор pytorch.
Использование torch.optim в pytorch для оптимизации нейронной сети и выбора оптимизатора - pytorch
Подробный оптимизатор pytorch: SGD
Подробное объяснение использования addmm() и addmm_() в Pytorch