Добавить в корзинуПозвонить
Найти в Дзене
Один Rust не п...Rust

Конечный автомат с ML

t.me/oneRustnoqRust Для чего нужна данная статья? : Отказаться от GPU, tch, PyTorch для максимально быстрой компиляции, интерпретируемости, онлайн-обучения, уменьшения объёма бинарника, ясности причины перехода. Создать адаптивный парсер и лексический анализатор логов. Зачем Вам это уметь? : Реализовать переходы полностью через ML (Нейронная сеть заменяет таблицу переходов). С использованием дерева решений из библиотеки smartcore и онлайн-обучения. Получаем вектор длины 2: [state_val, signal_val].
Дерево решений работает только с числами → это обязательный шаг. Это нужно, потому что DecisionTreeClassifier работает с целыми метками классов. Это нужно, потому что DecisionTreeClassifier работает с целыми метками классов. Почему переобучаем каждый раз?
smartcore не поддерживает инкрементальное обучение деревьев, поэтому самый простой и надёжный способ — собрать все примеры и обучить дерево заново. Для десятков-сотен примеров это мгновенно. Это классический детерминированный FSM, но таблиц
Оглавление
GitHub - nicktretyakov/interpretable_ml_fsm
ML на RUST без заморочек

t.me/oneRustnoqRust

Для чего нужна данная статья? :

Отказаться от GPU, tch, PyTorch для максимально быстрой компиляции, интерпретируемости, онлайн-обучения, уменьшения объёма бинарника, ясности причины перехода.

Создать адаптивный парсер и лексический анализатор логов.

Зачем Вам это уметь? :

Реализовать переходы полностью через ML (Нейронная сеть заменяет таблицу переходов). С использованием дерева решений из библиотеки smartcore и онлайн-обучения.

Общая архитектура

  • tree — обученное дерево решений, которое по текущему состоянию + входному сигналу предсказывает следующее состояние.
  • examples_* — хранят все примеры, на которых мы обучали дерево (нужны для переобучения).
  • current_state — состояние автомата в данный момент.
  • rng — нужен только для стохастических (вероятностных) переходов.

Состояния и сигналы

  • TextSegment + ] → InvalidSegment
  • InvalidSegment + ] → TextSegment
  • любой другой символ → остаёмся в текущем состоянии

Кодирование в числовые признаки

Получаем вектор длины 2: [state_val, signal_val].
Дерево решений работает только с числами → это обязательный шаг.

  • TextSegment ↔ 0
  • InvalidSegment ↔ 1

Это нужно, потому что DecisionTreeClassifier работает с целыми метками классов.

Преобразование состояний ↔ меток классов

  • TextSegment ↔ 0
  • InvalidSegment ↔ 1

Это нужно, потому что DecisionTreeClassifier работает с целыми метками классов.

Онлайн-обучение

  1. Кодируем пару (current, signal) → вектор признаков.
  2. Преобразуем correct_next в метку класса.
  3. Добавляем их в examples_x и examples_y.
  4. Вызываем retrain() — полностью переобучаем дерево на всех накопленных примерах.

Почему переобучаем каждый раз?
smartcore не поддерживает инкрементальное обучение деревьев, поэтому самый простой и надёжный способ — собрать все примеры и обучить дерево заново. Для десятков-сотен примеров это мгновенно.

Детерминированный переход

  1. Кодируем текущее состояние + сигнал → один вектор-признак.
  2. Оборачиваем его в DenseMatrix (требование smartcore).
  3. tree.predict(...) → получаем метку класса (0 или 1).
  4. Преобразуем метку обратно в State.
  5. Обновляем current_state и печатаем переход.

Это классический детерминированный FSM, но таблица переходов хранится внутри дерева.

Стохастический переход

В smartcore 0.3.2 у дерева нет метода predict_proba, поэтому мы делаем простую, но корректную имитацию:

  • После predict мы знаем, какой класс дерево выбрало с уверенностью 100 %.
    Создаём вектор вероятностей [p0, p1]:
  • если предсказан класс 0 → [1.0, 0.0]
  • если предсказан класс 1 → [0.0, 1.0]

Затем:

  • генерируем случайное число sample ~ U(0,1)
  • выбираем класс по кумулятивной вероятности
  • переходим в соответствующее состояние

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

Сохранение и загрузка модели

  • Используем bincode — очень быстрый бинарный формат.
  • Поле rng помечено #[serde(skip)] — его нельзя сериализовать, поэтому при загрузке создаём новый генератор (rand::rng()).
  • Всё остальное (дерево, накопленные примеры, текущее состояние) сохраняется и восстанавливается идеально.