No history yet

Архитектура тензоров и Autograd

Тензоры: строительные блоки PyTorch

В основе PyTorch лежит структура данных, называемая тензором. Думайте о нем как о многомерном массиве, похожем на ndarray в NumPy, но с одним ключевым преимуществом: тензоры могут использовать мощность графических процессоров (GPU) для значительного ускорения вычислений. Это делает их идеальными для задач глубокого обучения, которые требуют огромного количества математических операций.

Каждый тензор имеет несколько важных атрибутов, которые определяют, как он хранится и обрабатывается:

  • dtype: Определяет тип данных, хранящихся в тензоре, например, torch.float32 (числа с плавающей запятой) или torch.int64 (целые числа). Выбор правильного типа данных важен для точности и производительности.
  • device: Указывает, где хранится тензор — на центральном процессоре (CPU) или на графическом процессоре (GPU), например, cuda для NVIDIA или mps для Apple Silicon. Вычисления на GPU выполняются на порядки быстрее.
  • layout: Описывает, как тензор организован в памяти. Стандартным является torch.strided, но для специфических задач могут использоваться и другие, например, torch.sparse_coo для разреженных тензоров.
import torch

# Проверяем, доступен ли GPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Используемое устройство: {device}')

# Создаем тензор на CPU
x_cpu = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
print(f'Тензор на CPU: {x_cpu.device}')

# Перемещаем его на GPU
x_gpu = x_cpu.to(device)
print(f'Тензор на GPU: {x_gpu.device})

Динамические графы и Autograd

Центральной особенностью PyTorch является его механизм автоматического дифференцирования, называемый Autograd. Когда вы выполняете операции с тензорами, для которых включено отслеживание градиентов, PyTorch на лету строит динамический граф вычислений. Этот граф отслеживает все шаги, которые привели к конечному результату.

В отличие от статических графов, которые определяются до выполнения вычислений, динамические графы создаются по мере выполнения кода. Это дает огромную гибкость. Вы можете использовать стандартные управляющие конструкции Python, такие как циклы for и условные операторы if, и PyTorch корректно построит граф для вычисления градиентов. Каждый проход через вашу модель может создавать новый граф, что идеально подходит для моделей с изменяющейся архитектурой, например, рекуррентных нейронных сетей.

Этот граф является основой для вычисления градиентов — процесса, известного как обратное распространение ошибки. Когда вы вызываете метод .backward() на конечном тензоре (который обычно представляет собой скалярное значение ошибки), Autograd проходит по графу в обратном направлении и вычисляет градиенты для всех тензоров, участвовавших в вычислениях.

Отслеживание градиентов

Чтобы Autograd начал отслеживать операции, нужно явно указать это для входных тензоров, установив их атрибут requires_grad=True. Эти тензоры называются листовыми (leaf tensors). У тензоров есть несколько ключевых атрибутов, связанных с Autograd:

АтрибутОписание
requires_gradЛогическое значение (True/False), указывающее, нужно ли вычислять градиент для этого тензора.
gradХранит значение вычисленного градиента после вызова .backward(). Изначально None.
grad_fnСсылка на функцию, которая создала тензор. У листовых тензоров, созданных пользователем, этот атрибут равен None.
is_leafTrue, если тензор был создан пользователем, а не в результате операции. Градиенты накапливаются только в листовых тензорах.

Давайте посмотрим на простой пример. Пусть у нас есть вычисление z=(ax+b)2z = (a \cdot x + b)^2.

# Создаем листовые тензоры, для которых нужны градиенты
a = torch.tensor(2.0, requires_grad=True)
x = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)

# Выполняем операции
y = a * x + b
z = y**2

# z теперь имеет grad_fn, так как является результатом операции
print(f"z.grad_fn: {z.grad_fn}")

# Вычисляем градиенты
z.backward()

# Градиенты теперь хранятся в атрибуте .grad листовых тензоров
print(f"Градиент для a (dz/da): {a.grad}")
print(f"Градиент для x (dz/dx): {x.grad}")
print(f"Градиент для b (dz/db): {b.grad}")

Важно: метод .backward() можно вызывать только для скалярных тензоров (содержащих один элемент). Если у вас тензор с несколькими элементами, нужно передать в .backward() градиентный тензор того же размера.

Эффективное управление памятью

Отслеживание градиентов требует дополнительных вычислений и памяти, так как PyTorch должен хранить граф и промежуточные значения для обратного прохода. Во время инференса (оценки модели), когда градиенты не нужны, это становится излишней нагрузкой.

Lesson image

Для отключения отслеживания градиентов PyTorch предоставляет два контекстных менеджера: torch.no_grad() и torch.inference_mode(). Использование любого из них значительно ускоряет вычисления и снижает потребление памяти.

x = torch.randn(100, 100, requires_grad=True)

# Вне контекстного менеджера отслеживание включено
print(f"x.requires_grad до no_grad: {x.requires_grad}")

with torch.no_grad():
    y = x * 2
    # Все операции внутри блока не отслеживаются
    print(f"y.requires_grad внутри no_grad: {y.requires_grad}")

# После выхода из блока отслеживание для x остается включенным
print(f"x.requires_grad после no_grad: {x.requires_grad}")

# torch.inference_mode() - более новый и быстрый вариант
with torch.inference_mode():
    z = x * 2
    print(f"z.requires_grad внутри inference_mode: {z.requires_grad}")

Понимание тензоров и работы Autograd является ключевым для эффективного использования PyTorch. Эти инструменты обеспечивают необходимую гибкость и производительность для построения и обучения даже самых сложных нейронных сетей.

Quiz Questions 1/5

Какое ключевое преимущество тензоров PyTorch по сравнению с массивами NumPy?

Quiz Questions 2/5

Как называется механизм автоматического дифференцирования в PyTorch?