Согласно онлайн-учебнику, в обучении усилителя половинной точности есть проблемы с наном, которые представляют собой не что иное, как следующие типы:
- При расчете убытка возникает ситуация деления на 0
- Потери слишком велики, и они оцениваются как inf с половинной точностью.
- Если в параметрах сети есть nan, то в результате операции также будет выведено nan (это скорее явление, чем причина. Появление nan в сети должно быть связано с появлением nan или inf ранее)
Но в целом есть три типа:
- Операционные ошибки, такие как x/0 при расчете убытков, вызывающие ошибки
- Числовое переполнение, результат операции выходит за пределы представления, например, вес и ввод в норме, а результат операции Nan или Inf. Например, если потеря слишком велика, она фактически превышает диапазон представления и становится инф.
- Проблема с градиентом, возможно проблема с возвратом градиента (не знаю)
0. Заключение
Позвольте мне сначала сказать о заключении, я использую обучение с половинной точностью amp, то есть тип данных float16 будет подмешиваться в середине, чтобы ускорить процесс обучения.
Но Нэн появляется в этой статье из-за float16, потому что максимальное значение, поддерживаемое float16, равно 65504, а моя модель включает матричное умножение (фактически операция q@k в трансформаторе). Среди них a∈[-38,40], b∈[-39,40] и умножение матриц a@b=c, c∈[-61408,inf]. Поскольку максимальное значение после операции матричного умножения a и b превышает максимальное представление float16, что приводит к появлению inf, поэтому окончательный результат появляется Nan.
1. Грубое позиционирование
Процесс обучения можно представить в виде следующего процесса:
1.1 Привязка к эпохе
Во-первых, видно, что потери на выходе в эпоху 4 в норме, а это означает, что процесс обучения 0~498iter в epoch4 нормальный, тогда проблема может появиться в 501 iter эпохи 4 499iter и epoch0~499iter.
1.2 найти его
Теперь нам нужно настроить таргетинг на конкретный итератор.
Об этом можно судить по методу дихотомии.В модели отладки 100, 300 и 499 итеров в эпоху = 5 раундов соответственно проверяют, являются ли потери нормальными, и так далее, чтобы найти конкретный итер.
У меня между iter161~162 эпохи = 5, потеря нормальна, когда iter = 161, и потеря Nan, когда iter = 162. Iter по-прежнему следует процессу, показанному на рисунке выше.Можно видеть, что проблема заключается не в чем ином, как в вычислении градиента и обновлении веса, когда iter=161, а также в прямой операции и расчете потерь, когда iter=162, в этих четырех местах.
1.3 Поиск конкретных шагов
При отладке сделайте паузу непосредственно перед прямой операцией эпохи = 5 и iter = 162.
Сначала посмотрите, в норме ли вес:
# 在iter=162的模型推理之前,检查权重是否存在异常值,比如Nan或inf
if epoch == 5:
if i == 162:
print(epoch, i)
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
# print grad check
v_n = []
v_v = []
v_g = []
for name, parameter in model.named_parameters():
v_n.append(name)
v_v.append(parameter.detach().cpu().numpy() if parameter is not None else [0])
v_g.append(parameter.grad.detach().cpu().numpy() if parameter.grad is not None else [0])
for j in range(len(v_n)):
if np.isnan(np.max(v_v[j]).item() - np.min(v_v[j]).item()) or np.isnan(
np.max(v_g[j]).item() - np.min(v_g[j]).item()):
color = bcolors.FAIL + '*'
else:
color = bcolors.OKGREEN + ' '
print('%svalue %s: %.3e ~ %.3e' % (color, v_n[j], np.min(v_v[j]).item(), np.max(v_v[j]).item()))
print('%sgrad %s: %.3e ~ %.3e' % (color, v_n[j], np.min(v_g[j]).item(), np.max(v_g[j]).item()))
outputs = model(images)
В ходе проверки доказано, что с весом проблем нет, поэтому проблема ограничивается прямыми рассуждениями и расчетом потерь iter=162.
Проверьте ввод и вывод
с помощью кода:
print(images.mean()) # 检查输入,正常
outputs = model(images)
print(outputs .mean()) # 检查输出,Nan
Отсюда мы знаем ситуацию: вес модели нормальный, вход модели нормальный, а выход модели Нан
2. Точное позиционирование
Здесь это легко сделать.С помощью pycharm мы шаг за шагом отлаживаем ввод и вывод каждой модели в модели, чтобы увидеть, какая часть модели появляется Nan или Inf, и, наконец, найти строку кода:
attn = (q @ k.transpose(-2, -1)) * self.scale
Этот код предназначен для реализации матричного умножения q и k, диапазоны их значений:
тензор | макс (приблизительное значение) | мин (приблизительное значение) |
---|---|---|
д | 38 | -37 |
к | 40 | -38 |
внимание | инф | -61408 |
Отсюда видно, что это простая проблема вычисления, и очень распространенная проблема - числовое переполнение.Учитывая, что я использую float16 половинной точности, максимальное значение при запросе равно 65504, поэтому весьма вероятно, что максимальное значение имеет переполнен. Для проверки мы можем преобразовать q и k в double (float64) перед вычислением, и мы можем обнаружить, что результат вычисления нормальный, и тип тоже float64. Это показывает, что это вызвано числовым переполнением.
3. Решения
Теперь известно, что моя причина - числовое переполнение.Один из способов - перехватить: установить inf или nan как константу, и я нормализую q и k до [-1,1] перед операцией, что гарантирует, что результат операции будет не быть слишком большим (нет причин, это безмозглая операция, и ее не рекомендуется изучать).