Работа с переменной длиной последовательностей: маскирование

Многие задачи машинного обучения, особенно в области обработки естественного языка (NLP) и временных рядов, требуют работы с последовательностями различной длины. Для корректной работы моделей важно учитывать реальную длину последовательностей и игнорировать лишние элементы, добавленные для выравнивания батчей. Этот процесс называется маскированием.

Паддинг и необходимость маскирования

В TensorFlow.js последовательности часто приводятся к одинаковой длине с помощью padding. Например, при работе с предложениями, каждое слово может быть закодировано как число (индекс в словаре). Чтобы объединить предложения в батч, более короткие последовательности дополняются специальным токеном (например, 0):

const sequences = [
  [1, 2, 3],
  [4, 5],
  [6]
];

// Применяем padding до длины 3
const paddedSequences = [
  [1, 2, 3],
  [4, 5, 0],
  [6, 0, 0]
];

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

Маскирование в TensorFlow.js

TensorFlow.js поддерживает masking через слой tf.layers.masking. Маска указывает, какие элементы последовательности следует игнорировать в последующих слоях, таких как LSTM или GRU.

const model = tf.sequential();

model.add(tf.layers.masking({
  maskValue: 0, // Значение, которое будет игнорироваться
  inputShape: [null, 1] // null позволяет работать с переменной длиной последовательности
}));

model.add(tf.layers.lstm({
  units: 16,
  returnSequences: true
}));

model.add(tf.layers.dense({ units: 1, activation: 'sigmoid' }));

Ключевые моменты:

  • maskValue задаёт значение, которое не будет учитываться моделью.
  • inputShape с null позволяет обрабатывать последовательности любой длины.
  • Маскирование передается автоматически во все совместимые рекуррентные слои (LSTM, GRU, SimpleRNN).

Автоматическое маскирование с Embedding

Если используется слой Embedding для преобразования индексов слов в векторы, маскирование можно включить сразу:

model.add(tf.layers.embedding({
  inputDim: 10000, // размер словаря
  outputDim: 64,   // размер вектора признаков
  maskZero: true,  // автоматически создает маску для нулевых токенов
  inputLength: null
}));
  • Опция maskZero: true создаёт маску для всех нулевых элементов.
  • Этот подход устраняет необходимость добавлять слой Masking отдельно.

Применение маски при обучении

При обучении модели с маскированными последовательностями:

const xs = tf.tensor3d([
  [[1], [2], [3]],
  [[4], [5], [0]],
  [[6], [0], [0]]
]);

const ys = tf.tensor2d([
  [1],
  [0],
  [1]
]);

model.compile({
  optimizer: 'adam',
  loss: 'binaryCrossentropy',
  metrics: ['accuracy']
});

await model.fit(xs, ys, {
  epochs: 10,
  batchSize: 2
});

Маска предотвращает влияние нулевых элементов на обучение, обеспечивая корректную работу градиентов и точность модели.

Взаимодействие маски с рекуррентными слоями

Маскирование распространяется через рекуррентные слои автоматически. Например, если LSTM получает маску, то:

  • На каждом временном шаге, где маска равна 0, вычисления скрытого состояния пропускаются.
  • Градиенты на маскированных шагах не обновляются.
  • При returnSequences: true маска сохраняется для всех последующих слоев.

Маскирование в пользовательских слоях

Можно использовать маску и в собственных слоях через аргумент mask в методе call. Например:

class MyLayer extends tf.layers.Layer {
  constructor() {
    super({});
  }

  computeMask(inputs, mask) {
    return mask; // передаем маску дальше
  }

  call(inputs, kwargs) {
    const mask = kwargs['mask'];
    // можно использовать mask для игнорирования padded элементов
    return tf.mul(inputs, tf.cast(mask, 'float32'));
  }
}

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

Практические рекомендации

  • Всегда использовать маскирование при работе с последовательностями разной длины.
  • При применении Embedding устанавливать maskZero: true для автоматического управления маской.
  • Проверять совместимость маски с кастомными слоями.
  • Следить, чтобы слои, не поддерживающие маску, не изменяли тензоры с padded значениями до рекуррентных слоев.

Маскирование является фундаментальным инструментом для работы с последовательностями в TensorFlow.js, обеспечивая корректность обучения и предсказаний при работе с данными переменной длины.