Вопрос или проблема
Версии кода:
python == 3.8
tensorflow == 2.2.0
tensorflow-addons == 0.11.2
Недавно я использовал функцию фокусировки потерь из дополнения tensorflow для одной из моих моделей. Чтобы лучше понять/демонстрировать, что происходит, я пытался воспроизвести некоторые графики из оригинальной статьи – в частности, рисунок 4, который показывает график накопленной суммы потерь среди меньшинства и большинства отдельно с использованием различных гамм на сходимой модели.
def calcCSP(pred, Y_test, g, label):
true = np.asarray(Y_test)
idsPos = np.where(true == 1)
idsNeg = np.where(true == 0)
pPos = pred[idsPos]
yPos = true[idsPos]
pNeg = pred[idsNeg]
yNeg = true[idsNeg]
if label == 1:
p = pPos
y = yPos
title = "Распределение положительных потерь"
else:
p = pNeg
y = yNeg
title = "Распределение отрицательных потерь"
p = tf.convert_to_tensor(p)
y = tf.cast(y, tf.float32)
fl = tfa.losses.SigmoidFocalCrossEntropy(alpha=PARAMS['alpha'], gamma=g)
loss = fl(y, p)
x = np.sort(loss)
# Нормализованные данные
x = x/sum(x)
cdf = np.cumsum(x)
n = len(x)
share_of_population = np.arange(1, n + 1) / n
cdf_materials = {"shares": share_of_population,
"cusum": cdf}
return cdf_materials
cdfListPos = []
cdfListNeg = []
gammas = [0, 2, 4, 6, 8]
for g in gammas:
# потребуется сохранить все это
cdf_matPos = calcCDF(preds, y_test, g, 1)
cdf_matNeg = calcCDF(preds, y_test, g, 0)
cdfListPos.append(cdf_matPos)
cdfListNeg.append(cdf_matNeg)
posplot = plt.figure()
for i in range(len(gammas)):
plt.plot(cdfListPos[i]['shares'], cdfListPos[i]['cusum'], label= r'$\gamma$ = ' + str(gammas[i]))
plt.title('CSP положительных точек')
plt.ylabel('Накопленные нормализованные потери')
plt.legend()
negplot = plt.figure()
for i in range(len(gammas)):
plt.plot(cdfListNeg[i]['shares'], cdfListNeg[i]['cusum'], label=r'$\gamma$ = ' + str(gammas[i]))
plt.title('CSP отрицательных точек')
plt.ylabel('Накопленные нормализованные потери')
plt.legend()
Мне удалось нормально запустить мой код на меньших наборах данных, однако, когда набор данных становится слишком большим, у меня, похоже, заканчивается память.
W tensorflow/core/common_runtime/bfc_allocator.cc:434] Аллокатор (mklcpu) исчерпал память, пытаясь выделить 4.47GiB (округлено до 4804430848)
Текущий отчет о выделениях памяти следует.
Я опустил отчет ради краткости, но могу добавить его, если необходимо. Я не уверен, как обойти это. Я пробовал использовать tf.split, чтобы разделить предсказанные и истинные значения на более мелкие тензоры, затем передать их в функцию потерь отдельно и объединить, но тестирование на меньшем наборе данных показывает, что результаты не совпадают при сравнении с полными версиями. Честно говоря, я не знаю, как лучше всего обойти это, и буду открыт для любых рекомендаций.
Один из вариантов – использовать уменьшение точности. Вместо y = tf.cast(y, tf.float32)
используйте y = tf.cast(y, tf.float16)
или y = tf.cast(y, tf.bfloat16)
Ответ или решение
Проблема нехватки памяти при вычислении потерь с использованием SigmoidFocalCrossEntropy: Подробный анализ и решения
В последние годы множество разработчиков и исследователей в области глубокого обучения прибегают к использованию функций потерь, таких как SigmoidFocalCrossEntropy из библиотеки TensorFlow Addons. Однако, как показывает ваш опыт, подобные функции могут оказать значительное влияние на использование памяти, особенно когда речь идет о больших датасетах. Давайте более детально рассмотрим проблему нехватки памяти и варианты ее решения.
Природа проблемы
Ваше сообщение о нехватке памяти свидетельствует о том, что во время выполнения операции выделения памяти (в данном случае, 4.47 GiB) система не в состоянии удовлетворить запрос. Обычно это происходит при обработке больших тензоров или массивов, что, безусловно, является случаем с вашими данными.
Для понимания ситуации важно учитывать, что TensorFlow выделяет память динамически, что иногда приводит к тому, что система "не успевает" обработать определенные размеры данных, что и вызывает ошибку. Ваш код, работающий с меньшими датасетами, может и не сталкиваться с этой проблемой, но как только размер данных увеличивается, наложенные ограничения системы начинают оказывать влияние.
Оптимизация использования памяти
1. Уменьшение размерности данных
Одним из простейших подходов является уменьшение размерности входных данных. Вы можете попробовать уменьшить разрешение изображений (если это необходимо) или использовать выборку данных, оставляя только наиболее информативные.
2. Снижение точности данных
Как вы правильно заметили, использование более низкой точности чисел может значительно сократить объем занимаемой памяти. Переход от tf.float32
к tf.float16
или tf.bfloat16
может уменьшить объем памяти примерно в два раза без заметного ухудшения результатов. Пример применения:
y = tf.cast(y, tf.float16) # уменьшение точности
Этот метод также может ускорить вычисления на поддерживаемом оборудовании.
3. Пакетная обработка данных
Ранее вы пробовали использовать tf.split
, но столкнулись с различиями в результате. Вместо этого, вы можете обрабатывать данные в пакетах (batch processing). Это позволит разбить ваш датасет на более мелкие части и обработать их последовательно, сохраняя при этом согласованность результатов. Например:
batch_size = 1024 # размер пакета
for start in range(0, len(pred), batch_size):
end = min(start + batch_size, len(pred))
# вычисляйте потери только для части данных
loss_batch = fl(y[start:end], p[start:end])
4. Использование градиентного накопления
Если обработка в пакетах вызовет скачки в градиенте, вы можете рассмотреть использование градиентного накопления. Вместо непосредственного обновления параметров модели после каждого мини-пакета, вы можете накапливать градиенты на протяжении нескольких шагов и обновлять их всего раз.
Заключение
Использование функций потерь, таких как SigmoidFocalCrossEntropy
, может создавать большие нагрузки на память при работе с большими датасетами. Решение данной проблемы требует применения различных стратегий, таких как уменьшение размерности данных, снижение точности чисел, использование пакетной обработки данных и градиентного накопления.
Внедрение этих подходов позволит вам избежать проблем с нехваткой памяти и добиться лучших результатов в ваших модельных расчетах. Не забывайте тестировать каждое изменение на меньших объемах данных, чтобы гарантировать, что вы не ухудшаете качество модели.