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

Для работы с библиотекой 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 как функцию ошибки, так как она более точно отражает отклонение вероятностного распределения предсказанных символов от реальных.

Генерация текста

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

  1. Закодировать входную последовательность.
  2. Передать её в сеть методом activate.
  3. Декодировать выходной вектор в символ.
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);

Особенности настройки сети

  • Размер скрытого слоя влияет на способность сети запоминать контекст. Для коротких последовательностей достаточно 10–30 нейронов, для больших текстов — 50–100.
  • Длина входной последовательности (sequenceLength) задаёт “память” сети: слишком маленькая последовательность снижает точность, слишком длинная — увеличивает сложность обучения.
  • Скорость обучения (rate) нужно подбирать экспериментально, чтобы избежать застревания в локальных минимумах.
  • Для стабилизации работы сети можно использовать нормализацию входных данных, особенно если расширяется набор признаков (например, добавляются метки слов или частоты).

Советы по улучшению качества

  1. Добавление нескольких скрытых слоев помогает сети моделировать более сложные зависимости.
  2. Использование Dropout или регуляризации уменьшает переобучение, если обучающий текст мал.
  3. Для больших текстовых корпусов полезно разбивать текст на абзацы или предложения, чтобы сеть училась предсказывать символы в разнообразных контекстах.