Передача модели между главным потоком и Worker

При работе с нейронными сетями в браузере часто возникает необходимость использовать Web Workers для выполнения вычислительно интенсивных операций в отдельном потоке, чтобы не блокировать пользовательский интерфейс. Библиотека Synaptic предоставляет гибкие средства для создания и обучения нейронных сетей, однако объекты сети нельзя напрямую передавать между потоками из-за ограничений сериализации JavaScript. Необходим подход, который позволяет сериализовать модель, передать её в Worker и восстановить там для дальнейшего использования.

Сериализация и десериализация модели

Synaptic предоставляет методы toJSON() и fromJSON(), которые позволяют преобразовать объект сети в сериализуемый формат и восстановить его.

// Создание сети
const { Layer, Network } = require('synaptic');

const inputLayer = new Layer(2);
const hiddenLayer = new Layer(3);
const outputLayer = new Layer(1);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

const network = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

// Сериализация сети в JSON
const networkJSON = network.toJSON();

Сериализация превращает структуру сети, веса и смещения в объект, который можно безопасно передавать через postMessage в Worker:

worker.postMessage({ type: 'loadNetwork', networkData: networkJSON });

Внутри Worker сеть восстанавливается с помощью fromJSON:

importScripts('synaptic.js');

let workerNetwork = null;

self.onmess age = function(event) {
    if (event.data.type === 'loadNetwork') {
        const { Network } = synaptic;
        workerNetwork = Network.fromJSON(event.data.networkData);
    }
};

Передача данных между потоками

Существует два основных подхода к обмену данными между главным потоком и Worker:

  1. Обмен объектами через postMessage Работает для сериализуемых объектов: массивов, объектов, JSON. Для моделей Synaptic это основной метод.

  2. Использование Transferable Objects Для больших массивов данных, например входных сигналов или батчей обучающих примеров, эффективнее передавать ArrayBuffer. Это предотвращает копирование и ускоряет обмен данными:

const inputArray = new Float32Array([0.1, 0.9]);
worker.postMessage(inputArray.buffer, [inputArray.buffer]);

В Worker можно получить данные обратно, обернув буфер в TypedArray:

self.onmess age = function(event) {
    const inputs = new Float32Array(event.data);
    const output = workerNetwork.activate(inputs);
    self.postMessage(output);
};

Обновление сети и передача весов обратно

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

// В Worker после обучения
const updatedNetworkJSON = workerNetwork.toJSON();
self.postMessage({ type: 'updateNetwork', networkData: updatedNetworkJSON });

В главном потоке:

worker.onmess age = function(event) {
    if (event.data.type === 'updateNetwork') {
        network = Network.fromJSON(event.data.networkData);
    }
};

Такой подход позволяет полностью сохранять состояние сети между потоками, включая структуру, веса и смещения, что особенно важно для долгих обучающих сессий.

Особенности и рекомендации

  • Объекты Synaptic (слои, проекции, сети) нельзя передавать напрямую через postMessage без сериализации. Любая попытка передачи сложных объектов приведёт к ошибкам или потере данных.
  • JSON-сериализация не поддерживает методы класса напрямую, поэтому после fromJSON необходимо использовать объект Synaptic, чтобы методы сети были доступны.
  • Передача больших моделей может создавать нагрузку на основной поток. Для ускорения можно использовать структурированные клонирования и Transferable Objects для массивов данных.
  • Асинхронная обработка в Worker позволяет одновременно обучать и использовать модель без блокировки UI, особенно при больших батчах входных данных.

Пример полной передачи модели и работы с Worker

Главный поток:

const worker = new Worker('worker.js');

// Отправка модели в Worker
worker.postMessage({ type: 'loadNetwork', networkData: network.toJSON() });

// Отправка входных данных
worker.postMessage({ type: 'activate', inputs: [0.5, 0.7] });

worker.onmess age = function(event) {
    if (event.data.type === 'result') {
        console.log('Результат из Worker:', event.data.output);
    }
};

Worker:

importScripts('synaptic.js');

let network = null;

self.onmess age = function(event) {
    const data = event.data;
    const { Network } = synaptic;

    if (data.type === 'loadNetwork') {
        network = Network.fromJSON(data.networkData);
    }

    if (data.type === 'activate' && network) {
        const output = network.activate(data.inputs);
        self.postMessage({ type: 'result', output });
    }
};

Эта схема обеспечивает полноценное взаимодействие между потоками, безопасное хранение модели и её обновление, а также эффективное использование вычислительных ресурсов браузера при работе с Synaptic.