Интерпретация выходных тензоров

Keras.js представляет собой библиотеку для выполнения предобученных моделей Keras непосредственно в браузере с использованием JavaScript. После загрузки модели и выполнения инференса перед разработчиком возникает задача корректной интерпретации выходных тензоров, полученных от модели. Это критический этап, от которого зависит правильность обработки результатов и их последующее применение.


Структура выходного тензора

Каждая модель Keras имеет определённый выходной слой, который формирует тензор с фиксированной размерностью. Типичный вид выходного тензора зависит от задачи:

  • Классификация: выходной тензор обычно одномерный массив размерности (num_classes), где каждый элемент соответствует вероятности принадлежности к классу.
  • Регрессия: выходной тензор может быть одномерным или многомерным массивом с числовыми значениями, соответствующими предсказанным величинам.
  • Сегментация и детекция объектов: выходные тензоры часто имеют более сложную структуру (batch_size, height, width, channels) или (num_boxes, 4 + num_classes).

Keras.js возвращает тензор как объект Float32Array, иногда вместе с дополнительными метаданными, такими как форма тензора (shape). Это означает, что для интерпретации нужно учитывать не только массив значений, но и его размерности.


Извлечение значений из тензора

После выполнения модели через model.predict() результат необходимо декодировать в удобный для работы формат:

const outputData = model.predict(inputData);
const shape = outputData.shape;
const values = outputData.data; // Float32Array
  • shape позволяет понять структуру тензора.
  • values содержит линейный массив всех элементов тензора.

Для многомерных тензоров требуется резайпинг или индексация, чтобы получить отдельные значения или срезы:

function getElement(values, shape, indices) {
    let offset = 0;
    let stride = 1;
    for (let i = shape.length - 1; i >= 0; i--) {
        offset += indices[i] * stride;
        stride *= shape[i];
    }
    return values[offset];
}

Этот метод позволяет безопасно извлекать элементы без необходимости вручную рассчитывать смещения в линейном массиве.


Интерпретация вероятностей

Для задач классификации чаще всего выходной слой — softmax, что даёт вероятность каждого класса. Основные шаги обработки:

  1. Преобразование линейного массива в массив вероятностей:

    const probabilities = Array.from(values);
  2. Поиск индекса максимальной вероятности:

    const maxIndex = probabilities.indexOf(Math.max(...probabilities));
    const predictedClass = classLabels[maxIndex];
  3. Для многоклассовой классификации с несколькими метками (multi-label) значения выходного тензора можно интерпретировать как вероятности каждой метки независимо. Пороговое значение часто задаётся вручную, например, 0.5:

const activeLabels = probabilities.map((p, i) => p > 0.5 ? classLabels[i] : null).filter(Boolean);

Работа с многомерными выходами

В задачах сегментации, генерации текста или временных рядов выходной тензор имеет несколько измерений. Правильная интерпретация требует учёта осей:

  • Для изображений (batch, height, width, channels) важно учитывать, что элементы идут в порядке HWC, а не всегда привычном CHW.
  • Для последовательностей (batch, timesteps, features) каждая временная точка соответствует отдельному набору предсказанных значений.

Пример доступа к конкретному пикселю сегментации:

const pixelValue = getElement(outputData.data, outputData.shape, [0, y, x, 2]);

Здесь [0, y, x, 2] — это [номер изображения, строка, столбец, канал].


Масштабирование и обратное преобразование

Многие модели требуют нормализации входных данных, поэтому интерпретация выходов иногда также нуждается в обратном преобразовании. Примеры:

  • Для регрессии на диапазоне [0, 1] возвращаемые значения нужно масштабировать обратно:

    const originalValue = outputValue * (max - min) + min;
  • Для изображений, где модель возвращает пиксели [0, 1], преобразование к [0, 255] выполняется так:

    const pixel = Math.round(value * 255);

Это важно для корректного отображения или дальнейшей обработки.


Оптимизация работы с тензорами

  • Избегать повторного копирования массива Float32Array без необходимости.
  • Использовать typed arrays и работать с ними напрямую для быстрого доступа.
  • Для больших выходных тензоров полезно применять срезы и батчинг, чтобы не перегружать память браузера.

Примеры практических интерпретаций

  1. Классификация цифр MNIST:

    const predictedClass = outputData.data.indexOf(Math.max(...outputData.data));
  2. Сегментация лица:

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const classIndex = argMax(outputData.data, [0, y, x, channels]);
            mask[y][x] = classIndex;
        }
    }
  3. Регрессия температуры:

    const temperature = outputData.data[0] * scaleFactor + offset;

Каждый пример демонстрирует разные методы извлечения и интерпретации, исходя из типа задачи и формы выходного тензора.


Вывод

Интерпретация выходных тензоров в Keras.js требует внимания к размерности, типу данных и контексту задачи. Линейные массивы Float32Array нужно корректно индексировать, преобразовывать к вероятностям или числовым величинам и учитывать масштабирование. Грамотная работа с выходными тензорами обеспечивает точность предсказаний и корректное использование результатов модели в браузере.