Track
Сколько раз вы видели значение функции потерь NaN при обучении глубокой нейронной сети?
После часов обучения кривая ошибки выглядит нормально, а затем внезапно взлетает в бесконечность. Чаще всего причина в взрывающихся градиентах — значениях градиента, которые во время обратного распространения становятся настолько большими, что обновления параметров становятся нестабильными, и модель «ломается». Эта проблема сильнее всего бьёт по рекуррентным сетям, но встречается и в трансформерах, и в глубоких полносвязных сетях.
Обрезка градиента решает это, ограничивая величину градиентов до того, как они попадут в оптимизатор. Это однострочная добавка в ваш цикл обучения, которая держит обновления в пределах разумного без каких‑либо изменений в модели.
В этой статье я разберу интуицию обрезки градиента, два основных метода, выбор порога и реализацию в PyTorch и TensorFlow.
Но что вообще такое loss в data science? Прочитайте нашу публикацию «Loss Function in Machine Learning», чтобы узнать.
Что такое обрезка градиента?
Обрезка градиента — это метод, который ограничивает величину градиентов во время обучения, чтобы предотвратить нестабильные обновления параметров.
Если градиент становится слишком большим, оптимизатор делает гигантский шаг в пространстве параметров и уводит веса в область, где loss «взрывается». Обрезка помогает, ограничивая размер этого шага до того, как он сможет навредить.
Важно, что обрезка градиента не затрагивает архитектуру модели. Вы не добавляете слои и не меняете функции активации. Меняется только процесс обучения: градиенты перехватываются между обратным распространением и шагом оптимизатора.
Это дёшево в использовании и легко убрать. Как вы увидите дальше, это буквально одна строка кода.
Как работает обрезка градиента
Механика проста. Операция обрезки располагается между обратным проходом и шагом оптимизатора и на каждой итерации повторяет четыре шага.
- Вычислите градиенты: выполните прямой проход, посчитайте функцию потерь и запустите обратное распространение. Здесь ничего не меняется: градиенты текут по сети как обычно.
- Проверьте величину градиента: измерьте, насколько велики градиенты. В зависимости от метода это может означать проверку отдельных значений или вычисление общей нормы по всем параметрам.
- Уменьшите градиенты, если они превышают порог: если величина превышает заданный предел, масштабируйте градиенты вниз. Если нет — оставьте как есть.
- Обновите параметры модели: передайте обрезанные градиенты оптимизатору и выполните обновление весов.
В большинстве случаев ваши градиенты остаются ниже порога, и обучение идёт так же, как без обрезки. Когда возникает всплеск, обрезка ловит его до того, как успеет среагировать оптимизатор.
Вот и всё.
Распространённые методы обрезки градиента
Есть два способа обрезать градиенты; разница в том, что именно вы измеряете и что масштабируете.
Обрезка по значению
Обрезка по значению по отдельности ограничивает каждый элемент градиента.
Вы выбираете диапазон, например [-1.0, 1.0], и любое значение градиента за его пределами округляется к ближайшей границе. Градиент 2.5 становится 1.0. Градиент -2.5 становится -1.0. Значения внутри диапазона не меняются.

Пример обрезки по значению
Привлекательность — в простоте. Тут нет математики сложнее операции min/max, и работает быстро.
Но у подхода есть недостаток. Обрезая отдельные значения, вы меняете направление векторa градиента. Если один компонент обрежется, а другие — нет, обновлённый вектор уже не указывает туда, куда «сказало» обратное распространение. В итоге оптимизатор делает шаг чуть в неправильном направлении.
Поэтому на практике обрезка по значению встречается реже.
Обрезка по норме
Обрезка по норме масштабирует весь вектор градиента, когда его общая величина превышает порог.
Вместо отдельных значений вычисляется норма всех градиентов вместе (обычно L2-норма) и сравнивается с максимумом. Если норма ниже порога — ничего не происходит. Если выше — каждый градиент домножается на один и тот же коэффициент, чтобы вернуть норму к пределу.

Пример обрезки по норме
Преимущество — сохранение направления. Поскольку каждый компонент уменьшается на один и тот же множитель, вектор градиента по‑прежнему указывает в исходном направлении. Вы лишь укорачиваете шаг, а не перенаправляете его.
Поэтому обрезка по норме стала стандартом. PyTorch clip_grad_norm_ и TensorFlow clipnorm реализуют именно этот метод, и большинство современных пайплайнов используют его по умолчанию.
Взрывающиеся градиенты и затухающие градиенты
Взрывающиеся и затухающие градиенты — частые проблемы в глубоком обучении, но обрезка решает только одну из них.
Взрывающиеся градиенты
Взрывающиеся градиенты возникают, когда значения градиента становятся слишком большими во время обратного распространения.
Обычно это проявляется в глубоких сетях или рекуррентных архитектурах, где градиенты умножаются через множество слоёв или шагов по времени. Если эти умножения складываются «не в ту сторону», величина градиента раздувается. Оптимизатор делает огромный шаг, веса прыгают к экстремальным значениям, и loss часто превращается в NaN или Inf.
Вы увидите это как внезапные пики функции потерь или неожиданную дивергенцию модели.
Затухающие градиенты
Затухающие градиенты — противоположная проблема. Значения градиента стремятся к нулю по мере распространения назад по сети.
Когда градиенты слишком малы, обновления весов становятся крошечными. Ранние слои перестают учиться, более глубокие слои учатся медленно, и обучение практически замирает. Кривая потерь выравнивается и не улучшается, даже спустя многие эпохи.
Именно из‑за этого RNN раньше плохо справлялись с длинными последовательностями до появления LSTM и GRU.
Где уместна обрезка градиента
Обрезка градиента решает проблему взрывающихся, а не затухающих градиентов.
Обрезка уменьшает слишком большие градиенты, но ничем не помогает, когда градиенты слишком малы. Для затухающих градиентов нужны лучшая инициализация весов, остаточные связи, пакетная нормализация или архитектуры, сохраняющие поток градиента.
Обрезка градиента по норме: подробности
Именно этот метод чаще всего ищут, когда говорят об обрезке градиента.
Процесс включает три шага. Сначала вычислите норму всех градиентов вместе. Затем сравните эту норму с выбранным порогом. Наконец, отмасштабируйте градиенты, если норма слишком велика.
Обычно используют L2‑норму: вы возводите каждое значение градиента в квадрат, суммируете и берёте квадратный корень. Если у вас есть градиенты g_1, g_2, ..., g_n по всем параметрам модели, L2‑норма равна:

Формула обрезки по норме
Получив норму, сравните её с порогом c. Если ||g|| <= c, градиенты проходят как есть. Если ||g|| > c, каждый градиент умножается на коэффициент c / ||g||. Новая норма становится ровно c.
Это важно, потому что каждый компонент уменьшается на одинаковый множитель. Относительные пропорции между значениями градиента сохраняются, следовательно, вектор по‑прежнему указывает в исходном направлении. Вы укорачиваете шаг оптимизатора, а не меняете направление.
Именно свойство сохранения направления делает обрезку по норме выбором по умолчанию. Обрезка по значению может «скрутить» вектор градиента в другую сторону. Обрезка по норме меняет только его длину.
PyTorch clip_grad_norm_ и TensorFlow clipnorm делают ровно это. Когда кто‑то говорит «я использую обрезку градиента», почти всегда имеется в виду обрезка по норме.
Выбор порога для обрезки градиента
Порог — это гиперпараметр, то есть универсального значения для всех моделей нет.
Если задать его слишком высоким, обрезка почти никогда не сработает. Ваши градиенты почти всегда ниже предела, поэтому «страховка» ничего не ловит. Обучение идёт так, словно обрезки нет, и вы всё равно увидите всплески потерь при взрывах градиентов.
Если задать слишком низким, вы будете обрезать чрезмерно. Каждый батч уменьшает свои градиенты, из‑за чего обновления весов становятся меньше нужного. Обучение замедляется, и модели требуется больше времени для сходимости, порой значительно больше.
Частая стартовая точка — 1.0, она хорошо подходит многим архитектурам. Значения между 0.5 и 5.0 покрывают большинство практических случаев.
Лучше мониторить нормы градиента во время обучения. Логируйте необрезанную норму на каждом шаге и смотрите на распределение. Если большинство норм около 0.3 с редкими всплесками до 50, установите порог чуть выше типичного диапазона, но заметно ниже всплесков — здесь подойдут 2.0 или 3.0.
Относитесь к нему как к любому гиперпараметру. Начните с 1.0, посмотрите на поведение и корректируйте по динамике обучения.
Обрезка градиента в рекуррентных нейронных сетях
Именно в RNN обрезка градиента впервые стала стандартной практикой.
Причина — в том, как RNN распространяют градиенты во времени. Backpropagation through time многократно перемножает одни и те же матрицы весов на множестве временных шагов, и эти повторные умножения могут разрастаться до огромных значений. Длинные последовательности усугубляют проблему.
LSTM и GRU снизили остроту проблемы благодаря механизмам «врат», но не устранили её. Обе архитектуры по‑прежнему выигрывают от обрезки, особенно при обучении на длинных последовательностях или с высокими скоростями обучения.
Для обучения RNN типичный дефолт — обрезка по норме с порогом между 1.0 и 5.0. Если вы используете PyTorch nn.LSTM или nn.GRU и loss «взрывается» при обучении, добавление clip_grad_norm_ — обычно первое, что стоит попробовать.
Обрезка градиента в современном глубоком обучении
Когда трансформеры сменили RNN, обрезка градиента никуда не делась.
Крупные языковые модели вроде GPT и BERT используют её при предобучении и дообучении. То же справедливо для vision‑трансформеров, диффузионных моделей и большинства глубоких архитектур со сотнями слоёв. Оптимизаторы Adam и AdamW, доминирующие в современном обучении, часто сочетаются с обрезкой по норме при порогах около 1.0.
Причина та же, что и для RNN. Глубокие сети умножают градиенты через множество слоёв, а большие batch‑размеры в сочетании с высокими скоростями обучения могут порождать редкие всплески градиента. Обрезка справляется с ними, не затрагивая обычные шаги обучения.
Большинство референсных реализаций включают обрезку по умолчанию. В Trainer от Hugging Face, PyTorch Lightning и DeepSpeed обрезка доступна как стандартная настройка. Если вы обучаете что‑то больше «игрушечной» модели, обрезка почти наверняка часть пайплайна.
Это однострочная вставка, которая почти ничего не стоит и не даёт запускам обучения «упасть» после часов вычислений. Поэтому она с нами до сих пор.
Обрезка градиента в PyTorch
В PyTorch обрезка градиента реализована одной функцией‑утилитой: torch.nn.utils.clip_grad_norm_.
Вызов обрезки располагается между loss.backward() и optimizer.step(). Сначала обратное распространение должно заполнить градиенты, затем при необходимости они обрезаются, затем оптимизатор применяет обновление. В другом месте это не сработает.
Вот полный исполняемый скрипт обучения: он обучает небольшой MLP на синтетических регрессионных данных с включённой обрезкой градиента:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(42)
# Synthetic regression data
n_samples = 1000
n_features = 20
inputs = torch.randn(n_samples, n_features)
targets = (inputs.sum(dim=1, keepdim=True) * 2.0 + torch.randn(n_samples, 1) * 0.1)
dataset = TensorDataset(inputs, targets)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
# Small feedforward network
model = nn.Sequential(
nn.Linear(n_features, 64),
nn.ReLU(),
nn.Linear(64, 64),
nn.ReLU(),
nn.Linear(64, 1),
)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.MSELoss()
# Training loop with gradient clipping
n_epochs = 5
max_grad_norm = 1.0
for epoch in range(n_epochs):
epoch_loss = 0.0
for batch_inputs, batch_targets in dataloader:
optimizer.zero_grad()
predictions = model(batch_inputs)
loss = loss_fn(predictions, batch_targets)
loss.backward()
# Gradient clipping
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=max_grad_norm)
optimizer.step()
epoch_loss += loss.item()
print(f"Epoch {epoch + 1}: loss = {epoch_loss / len(dataloader):.4f}")

Вывод PyTorch
Функция clip_grad_norm_ принимает два основных аргумента:
-
parameters: параметры модели, чьи градиенты вы хотите обрезать. Передайтеmodel.parameters(), чтобы охватить всю модель. -
max_norm: порог для нормы градиента. Значение1.0— распространённая стартовая точка.
Есть необязательный аргумент norm_type, по умолчанию 2.0 для L2‑нормы. Менять его требуется редко.
Завершающее подчёркивание в clip_grad_norm_ означает операцию «на месте». Функция изменяет градиенты непосредственно в атрибуте .grad каждого параметра, поэтому возвращаемое значение отслеживать не нужно. Она возвращает суммарную норму градиентов до обрезки — удобно для логирования.
Для обрезки по значению (вместо по норме) в PyTorch есть torch.nn.utils.clip_grad_value_:
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=0.5)
Но, как уже говорилось, вы вряд ли будете это использовать.
Вот и вся настройка. Две строки в ваш цикл обучения.
Обрезка градиента в TensorFlow
В TensorFlow обрезка выполняется на уровне оптимизатора, а не отдельной функцией.
При создании оптимизатора вы передаёте аргументы clipnorm или clipvalue. Оптимизатор применяет обрезку внутренне на каждом шаге, так что ваш цикл обучения менять не нужно.
Вот полностью рабочий пример с использованием Keras API на синтетических регрессионных данных:
import numpy as np
import tensorflow as tf
tf.random.set_seed(42)
np.random.seed(42)
# Synthetic regression data
n_samples = 1000
n_features = 20
x_train = np.random.randn(n_samples, n_features).astype(np.float32)
y_train = (x_train.sum(axis=1, keepdims=True) * 2.0
+ np.random.randn(n_samples, 1).astype(np.float32) * 0.1)
# Small feedforward network
model = tf.keras.Sequential([
tf.keras.layers.Dense(64, activation="relu", input_shape=(n_features,)),
tf.keras.layers.Dense(64, activation="relu"),
tf.keras.layers.Dense(1),
])
# Optimizer with gradient clipping by norm
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3, clipnorm=1.0)
model.compile(optimizer=optimizer, loss="mse")
model.fit(x_train, y_train, epochs=5, batch_size=32)

Вывод TensorFlow
Два аргумента делают разное:
-
clipnormобрезает по L2‑норме каждого тензора градиента. Если норма превышает порог, тензор пропорционально масштабируется вниз. -
clipvalueпоэлементно обрезает каждый градиент. Любое значение выше порога прижимается к порогу, ниже отрицательного порога — к отрицательному порогу.
Чтобы перейти от обрезки по норме к обрезке по значению, просто замените аргумент:
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3, clipvalue=0.5)
Оба аргумента работают со всеми оптимизаторами Keras: Adam, SGD, RMSprop, AdamW и другими. Есть также аргумент global_clipnorm, который обрезает по норме, вычисленной сразу по всем градиентам, а не по каждому тензору. Это ближе к поведению PyTorch по умолчанию.
Если вы пишете кастомный цикл обучения с tf.GradientTape, оптимизатор всё равно выполнит обрезку при вызове apply_gradients:
for epoch in range(5):
for batch_x, batch_y in zip(np.array_split(x_train, 32), np.array_split(y_train, 32)):
with tf.GradientTape() as tape:
predictions = model(batch_x, training=True)
loss = tf.reduce_mean(tf.square(predictions - batch_y))
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
В этом и разница между двумя фреймворками. В PyTorch обрезка в ваших руках, внутри цикла. В TensorFlow — внутри самого оптимизатора. Общая логика идентична.
Обрезка градиента и другие методы стабилизации
Обрезка градиента — не единственный способ стабилизировать обучение и не всегда правильный инструмент для задачи.
Другие техники решают смежные, но иные проблемы. Одни предотвращают разрастание градиентов изначально, другие мешают им затухать, третьи упрощают поверхность потерь для оптимизации. Вот несколько таких техник.
Пакетная нормализация
Пакетная нормализация нормализует активации внутри каждого мини‑батча во время обучения.
Она удерживает выходы слоёв в стабильном диапазоне, делая величины градиентов предсказуемее. Сети с batch norm терпимее к высоким скоростям обучения и сходятся быстрее, а также менее чувствительны к выбору инициализации весов.
Но batch norm напрямую не останавливает взрывы градиентов. Она сокращает их частоту, а не решает, что делать, когда они уже возникли. Поэтому многие модели сочетают batch norm с обрезкой градиента.
Остаточные связи
Остаточные связи добавляют «шорткаты», перескакивающие через один или несколько слоёв, что позволяет градиентам течь напрямую из поздних слоёв к ранним.
Это решает проблему затухающих градиентов в глубоких сетях. Без них обучение сетей глубже 20–30 слоёв затруднено, потому что градиенты стремятся к нулю по мере обратного распространения. С ними сети на сотни слоёв обучаются без проблем.
Остаточные связи нацелены на противоположный край проблемы, чем обрезка. Обрезка — для слишком больших градиентов. Residual‑связи — для слишком малых.
Аккуратная инициализация весов
Начальные значения весов задают стартовую величину активаций и градиентов. Неправильная инициализация может вызвать взрыв или затухание градиентов уже на первом шаге.
Методы вроде инициализации Ксавье и Хе масштабируют начальные веса в зависимости от размера слоя. Это стабилизирует дисперсии активаций по слоям в начале обучения и предотвращает многие проблемы с градиентами заранее.
Хорошая инициализация снижает вероятность, что вам понадобится обрезка, но не исключает её. Всплески градиента могут появляться и позже, особенно при высоких скоростях обучения или «необычных» батчах.
Как это сочетается
Перечисленные техники — не взаимоисключающие. Это взаимодополняющие инструменты, решающие разные части одной общей задачи.
Типичная современная настройка включает аккуратную инициализацию на старте, остаточные связи в архитектуре, пакетную (или слойную) нормализацию внутри сети и обрезку градиента как страховку во время оптимизации. Каждая решает свой сбойный сценарий, а вместе они делают глубокие сети обучаемыми.
Выводы
Обрезка градиента — одно из самых простых исправлений в глубоком обучении и средство от проблемы, которая способна «свести на нет» часы обучения одним шагом.
Хорошая новость: вам не нужно менять архитектуру модели или переписывать код обучения. Одна строка в PyTorch или один аргумент в TensorFlow — и обрезка градиента включена.
Лучше всего она работает как часть более широкой настройки. Сочетайте её с аккуратной инициализацией весов, остаточными связями и пакетной или слойной нормализацией — и у вас будет пайплайн обучения, устойчивый к нестабильности с разных сторон.
Если ваша функция потерь «взрывается», начните с обрезки. Если «затухает» — ищите другие методы. А если вы обучаете что‑то больше маленькой модели, включите обрезку по умолчанию — и забудьте о ней.
Обрезка градиента — лишь один из многих терминов, которые должен знать каждый инженер машинного обучения. Если хотите узнать остальные и быть готовыми к работе в 2026 году, запишитесь на наш трек «Machine Learning Engineer» уже сегодня.
Частые вопросы об обрезке градиента
Что такое обрезка градиента в глубоком обучении?
Обрезка градиента — это метод, который ограничивает величину градиентов при обучении нейросетей, чтобы предотвратить нестабильные обновления параметров. Когда градиенты во время обратного распространения становятся слишком большими, оптимизатор делает огромные шаги, уводящие веса в «плохие» области и вызывающие взрыв функции потерь. Обрезка ограничивает величину градиента до шага оптимизатора, так что обновления остаются в ограничениях, даже когда «сырые» градиенты дают всплески.
Когда мне использовать обрезку градиента?
Используйте обрезку градиента при нестабильном обучении, особенно если видите внезапные всплески loss, значения NaN или дивергенцию после часов обучения. Это стандартная практика для рекуррентных сетей, таких как LSTM и GRU, и любых глубоких архитектур, обучаемых с высокими скоростями обучения. Если кривая потерь выглядит здоровой, можно обойтись без неё, но добавить как страховку — не повредит.
В чём разница между обрезкой по значению и по норме?
Обрезка по значению по отдельности ограничивает каждый элемент градиента, из‑за чего может измениться направление вектора градиента. Обрезка по норме масштабирует весь вектор, когда его общая величина превышает порог, сохраняя исходное направление и укорачивая шаг. Обрезка по норме — стандартный выбор в современном глубоком обучении, потому что не искажает направление обновления.
Как выбрать хороший порог обрезки градиента?
Начните с 1.0 и корректируйте по тому, что видите во время обучения. Логируйте необрезанные нормы градиента по батчам и смотрите на распределение. Установите порог выше типичного диапазона норм, но заметно ниже всплесков, которые хотите поймать. Если задать слишком высоко — обрезка не будет срабатывать; слишком низко — излишне замедлите обучение.
Устраняет ли обрезка градиента и затухающие градиенты?
Нет. Обрезка градиента решает только проблему взрывающихся градиентов, когда значения становятся слишком большими. Затухающие градиенты — противоположная ситуация, когда значения стремятся к нулю и обучение практически останавливается. Для затухающих градиентов используйте лучшую инициализацию весов, остаточные связи, пакетную нормализацию или архитектуры вроде LSTM и GRU.