Для работы с библиотекой Synaptic необходимо создать архитектуру нейронной сети. Базовые элементы библиотеки включают NeuralNetwork, Layer, Trainer, а также вспомогательные классы для различных видов сетей (Perceptron, LSTM и др.).
const synaptic = require('synaptic');
const { Layer, Network, Trainer } = synaptic;
Для задачи предсказания следующего символа чаще всего используют рекуррентные сети, в частности LSTM (Long Short-Term Memory), так как они хорошо справляются с последовательными данными.
const inputLayer = new Layer(10); // размер входного вектора зависит от кодировки символов
const hiddenLayer = new Layer(20);
const outputLayer = new Layer(10); // размер выхода равен количеству уникальных символов
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
const myNetwork = new Network({
input: inputLayer,
hidden: [hiddenLayer],
output: outputLayer
});
Перед подачей данных на вход нейронной сети символы необходимо закодировать в числовой вид. Чаще всего используется one-hot encoding — каждому символу соответствует вектор с длиной, равной количеству уникальных символов в наборе данных, где на позиции символа ставится 1, а остальные элементы — 0.
function encodeChar(char, charset) {
return charset.map(c => (c === char ? 1 : 0));
}
function decodeVector(vector, charset) {
const index = vector.indexOf(Math.max(...vector));
return charset[index];
}
Для предсказания следующего символа необходимо подготовить последовательности фиксированной длины, где каждый элемент — закодированный символ, а целевой выход — следующий символ в последовательности.
Данные подаются в виде массива объектов с полями input и
output. Каждое input — последовательность
символов в виде one-hot векторов, а output — one-hot вектор
следующего символа.
const sequenceLength = 5;
const trainingSet = [];
for (let i = 0; i < text.length - sequenceLength; i++) {
const inputSeq = text.slice(i, i + sequenceLength).split('').map(c => encodeChar(c, charset));
const outputChar = encodeChar(text[i + sequenceLength], charset);
trainingSet.push({
input: [].concat(...inputSeq),
output: outputChar
});
}
Объединение элементов inputSeq в один массив необходимо,
так как Synaptic ожидает плоский вектор на входе сети,
а не массив массивов.
Для обучения используется класс Trainer, который
предоставляет удобные методы, включая train(), с поддержкой
пакетного обучения, итераций и
динамического контроля ошибки.
const trainer = new Trainer(myNetwork);
trainer.train(trainingSet, {
rate: 0.1, // скорость обучения
iterations: 2000, // количество итераций
error: 0.01, // целевая ошибка
shuffle: true,
log: 100, // вывод прогресса каждые 100 итераций
cost: Trainer.cost.CROSS_ENTROPY
});
Для задач предсказания последовательностей рекомендуется использовать cross-entropy как функцию ошибки, так как она более точно отражает отклонение вероятностного распределения предсказанных символов от реальных.
После обучения сети можно использовать её для предсказания следующего символа. Для этого необходимо:
activate.function predictNext(network, inputSeq, charset) {
const inputVector = [].concat(...inputSeq.map(c => encodeChar(c, charset)));
const outputVector = network.activate(inputVector);
return decodeVector(outputVector, charset);
}
Для генерации текста длиной N символов можно
последовательно добавлять предсказанный символ в конец
последовательности, сдвигая окно входной последовательности:
let seed = 'Нача'; // исходная последовательность
for (let i = 0; i < 50; i++) {
const inputSeq = seed.slice(-sequenceLength).split('');
const nextChar = predictNext(myNetwork, inputSeq, charset);
seed += nextChar;
}
console.log(seed);