Визуализация фильтров сверток

ConvNetJS — это чисто JavaScript-библиотека для построения и обучения нейронных сетей, включая сверточные нейронные сети (CNN). Одной из важных возможностей при работе со сверточными сетями является визуализация фильтров сверток. Эта визуализация позволяет понять, какие признаки сети обнаруживают на ранних и глубоких слоях.

Архитектура сверточного слоя

Сверточный слой состоит из набора фильтров (ядр свертки), каждый из которых представлен матрицей весов. При применении свертки к входному изображению фильтры извлекают локальные признаки — края, текстуры или более сложные паттерны на последующих слоях.

В ConvNetJS фильтры создаются объектами типа Vol с параметрами:

  • sx — ширина фильтра
  • sy — высота фильтра
  • depth — количество каналов (например, 3 для RGB)
  • w — массив весов фильтра
  • dw — массив градиентов весов (используется при обучении)

Получение весов фильтра

Для визуализации фильтров необходимо извлечь веса из сверточного слоя. Если сверточный слой conv определён в сети:

let layer = net.layers[convIndex]; // convIndex — индекс сверточного слоя
let filters = layer.filters; // массив фильтров

Каждый фильтр хранится как объект Vol, содержащий линейный массив весов w. Для преобразования его в двумерное изображение используется функция Vol.toNumbers() или простая итерация по индексам.

Преобразование весов в изображение

Вес каждого фильтра можно интерпретировать как пиксели. Для одноцветного (grayscale) изображения:

function filterToImage(filter) {
    let size = Math.sqrt(filter.w.length);
    let canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;
    let ctx = canvas.getContext('2d');
    let imgData = ctx.createImageData(size, size);
    for (let i = 0; i < filter.w.length; i++) {
        let value = Math.floor((filter.w[i] - Math.min(...filter.w)) / (Math.max(...filter.w) - Math.min(...filter.w)) * 255);
        imgData.data[i * 4 + 0] = value;
        imgData.data[i * 4 + 1] = value;
        imgData.data[i * 4 + 2] = value;
        imgData.data[i * 4 + 3] = 255;
    }
    ctx.putImageData(imgData, 0, 0);
    return canvas;
}

В этом коде выполняется нормализация весов фильтра к диапазону 0–255, что позволяет корректно отобразить как положительные, так и отрицательные значения.

Визуализация многоканальных фильтров

Для фильтров с глубиной больше 1 (например, RGB) каждый канал визуализируется отдельно или комбинируется:

function filterToRGBImage(filter) {
    let size = filter.sx;
    let canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;
    let ctx = canvas.getContext('2d');
    let imgData = ctx.createImageData(size, size);
    for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
            let idx = y * size + x;
            for (let c = 0; c < 3; c++) { // R, G, B
                let val = filter.w[idx + c * size * size];
                val = Math.floor((val - Math.min(...filter.w)) / (Math.max(...filter.w) - Math.min(...filter.w)) * 255);
                imgData.data[(idx) * 4 + c] = val;
            }
            imgData.data[idx * 4 + 3] = 255;
        }
    }
    ctx.putImageData(imgData, 0, 0);
    return canvas;
}

Этот метод полезен для первых слоев сети, где фильтры имеют три канала. В более глубоких слоях визуализация отдельных каналов позволяет выявлять сложные текстуры и паттерны.

Автоматическая отрисовка сетки фильтров

Для наглядного представления фильтров удобно создавать сетку изображений:

function drawFilterGrid(filters, containerId, perRow = 8) {
    let container = document.getElementById(containerId);
    container.innerHTML = '';
    filters.forEach((filter, index) => {
        let canvas = filterToImage(filter);
        canvas.style.margin = '2px';
        container.appendChild(canvas);
    });
}

Можно менять filterToImage на filterToRGBImage для цветных фильтров. Параметр perRow позволяет задавать количество фильтров в одной строке, формируя аккуратную визуальную сетку.

Визуализация активаций слоев

Кроме фильтров, полезно визуализировать активации слоев, показывая, какие области изображения вызывают отклик сети. Активации извлекаются через объект Vol:

let activations = layer.out_act; // Vol активаций

Каждое поле activations.w соответствует отклику нейрона на входное изображение. Аналогично фильтрам, активации можно нормализовать и отобразить в виде изображения для каждого канала.

Практические наблюдения

  • Фильтры первых слоев чаще всего выделяют границы и простые текстуры, что соответствует их роли в извлечении базовых признаков.
  • Фильтры глубинных слоев становятся абстрактными, комбинируя базовые паттерны в сложные формы.
  • Визуализация помогает отслеживать обучение, выявлять недообученные или “мертвые” фильтры.

Оптимизация производительности

При большом количестве фильтров рендеринг на канвасе может замедлять интерфейс. Рекомендуется:

  • Использовать offscreen canvas для предварительной отрисовки.
  • Ограничивать количество одновременно визуализируемых фильтров.
  • Кэшировать нормализованные данные фильтров, чтобы не пересчитывать их при каждой отрисовке.

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