Центральным для всех нейронных сетей в PyTorch является пакет autograd
.
Пакет autograd
обеспечивает автоматическое разграничение всех операций на тензорах. Это определяемый-по-исполнению (define-by-run) фреймворк, что означает, что ваш backprop (обратное распространение в нейронной сети) определяется тем, как выполняется ваш код, и что каждая отдельная итерация может отличаться.
Рассмотрим это в более простых терминах на нескольких примерах.
Тензор
torch.Tensor
является центральным классом пакета. Если вы установите его атрибут .require_grad
равным True
, тогда он начнет отслеживать все операции над ним. Когда вы закончите вычисления, вы можете вызвать .backward()
и получить все градиенты вычисленные автоматически. Градиент для этого тензора будет накапливается в атрибуте .grad
.
Чтобы остановить отслеживание тензора в истории вычислений, вы можете вызвать .detach()
.
Чтобы предотвратить отслеживание истории (и использование памяти), вы также можете обернуть блок кода
в with torch.no_grad():
. Это может быть особенно полезно при оценке модели, потому что модель может иметь обучаемые параметры с require_grad = True, но для которых нам не нужны градиенты.
Есть еще один класс, который очень важен для autograd реализации - Function
.
Tensor
и Function
связаны между собой и создают ациклический граф, который кодирует полную историю вычислений. Каждый тензор имеет .grad_fn
атрибут, который ссылается на Function
, которая создала
Tensor
(за исключением тензоров, созданных пользователем - их grad_fn = None
).
Если вы хотите вычислить производные, вы можете вызвать .backward()
на Tensor
. Если Tensor
является скаляром (то есть он содержит один элемент данных), вам не нужно указывать какие-либо аргументы для backward()
, однако, если в нем больше элементов, вам нужно указать gradient
аргумент, который является тензором подходящей формы.
import torch
Создаем тензор и задаем requires_grad=True, чтобы отслеживать вычисления с ним:
x = torch.ones(2, 2, requires_grad=True)
print(x)
Вывод:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
Выполняем операцию на тензоре:
y = x + 2
print(y)
Вывод:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y был создан как результат операции, поэтому он имеет grad_fn.
print(y.grad_fn)
Вывод:
<AddBackward0 object at 0x7fef6f982438>
Выполняем еще несколько операций на y:
z = y * y * 3
out = z.mean()
print(z, out)
Вывод:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>)
tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_( ... ) изменяет существующий requires_grad флаг тензора на месте (in-place). input флаг равен False, если не задан:
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
Вывод:
False
True
<SumBackward0 object at 0x7fe1db427dd8>
Градиенты
Вызовем backprop теперь. Поскольку вывод содержит единственный скаляр, out.backward() эквивалентен ut.backward(torch.tensor(1.)).
out.backward()
Напечатаем градиенты d(out)/dx
print(x.grad)
Вывод:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
В общем говоря, torch.autograd - это механизм для вычисления Jacobian-vector произведения.
Рассмотрим пример Jacobian-vector произведения:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
Вывод:
tensor([ 840.8677, 613.5138, -778.9942], grad_fn=<MulBackward0>)
Теперь в этом случае y больше не скаляр. torch.autograd не может вычислить полный Jacobian напрямую, но если мы просто хотим Jacobian-vector произведение, просто передаем вектор в backward как аргумент:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
Вывод:
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])
Вы можете также остановить autograd от отслежвания истории на тензорах с .requires_grad=True с помощью обертывания блока кода в with torch.no_grad():
print(x.requires_grad)
print((x 2).requires_grad)
with torch.no_grad():
print((x 2).requires_grad)
Вывод:
True
True
False
Читайте также другие статьи в блоге: