Инициализация весов нейронной сети является критически важным этапом при построении и обучении модели. В библиотеке Synaptic она играет особую роль, поскольку от правильного выбора начальных значений весов зависит скорость сходимости сети и её способность к обучению.
По умолчанию Synaptic использует случайную инициализацию весов в диапазоне от -1 до 1. Каждый нейрон создаётся с случайными весами, что предотвращает симметрию между нейронами и позволяет обучению начинаться с разнообразных состояний:
const { Layer } = require('synaptic');
let inputLayer = new Layer(3);
let hiddenLayer = new Layer(4);
let outputLayer = new Layer(2);
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
В этом примере веса между слоями будут случайными. Такой подход подходит для большинства простых сетей, однако не всегда обеспечивает оптимальную сходимость, особенно для глубоких моделей.
Для более точного контроля над стартовыми весами можно использовать метод set каждого соединения, задавая конкретные значения:
hiddenLayer.list.forEach(neuron => {
neuron.connections.inputs.forEach(conn => {
conn.weight = Math.random() * 0.2 - 0.1; // диапазон от -0.1 до 0.1
});
});
Ключевой момент: меньший диапазон начальных весов снижает вероятность того, что сигналы будут насыщаться функциями активации, такими как сигмоида или tanh, и предотвращает «затухающие градиенты» на старте обучения.
Иногда более эффективной оказывается инициализация весов по нормальному распределению с математическим ожиданием 0 и небольшой дисперсией. Это позволяет распределить значения вокруг нуля, создавая симметрично «случайные» веса, что особенно полезно для скрытых слоёв.
function normalRandom(mean = 0, stdDev = 0.1) {
let u = 0, v = 0;
while(u === 0) u = Math.random();
while(v === 0) v = Math.random();
return mean + stdDev * Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
}
hiddenLayer.list.forEach(neuron => {
neuron.connections.inputs.forEach(conn => {
conn.weight = normalRandom();
});
});
Нейроны Synaptic поддерживают смещения, которые также требуют начальных значений. Обычно их инициализируют нулём или небольшими случайными значениями. Пример установки смещений:
hiddenLayer.list.forEach(neuron => {
neuron.bias = Math.random() * 0.2 - 0.1;
});
Правильная инициализация смещений помогает ускорить обучение и снижает вероятность того, что нейроны будут полностью «выключены» на старте.
Для многослойных перцептронов или рекуррентных сетей важно контролировать распределение весов на каждом уровне отдельно. Например, скрытые слои могут получать веса с небольшим стандартным отклонением, а выходные слои — чуть более широкий диапазон, чтобы начальные выходы были разнообразными:
function initializeLayer(layer, stdDev) {
layer.list.forEach(neuron => {
neuron.connections.inputs.forEach(conn => {
conn.weight = normalRandom(0, stdDev);
});
neuron.bias = normalRandom(0, stdDev);
});
}
initializeLayer(hiddenLayer, 0.1);
initializeLayer(outputLayer, 0.3);
Такая практика снижает риск того, что сеть будет застревать в локальных минимумах при первом обновлении весов.
Synaptic позволяет создавать собственные методы инициализации, расширяя стандартные функции. Можно создать класс, который наследует нейроны и автоматически задаёт веса по выбранной стратегии. Это упрощает повторное использование конфигураций для различных проектов и экспериментов.
Ключевые моменты:
Инициализация весов — фундаментальная часть настройки нейросети, напрямую влияющая на её способность быстро и эффективно обучаться, а также на стабильность процесса градиентного спуска.