0682ccc140
README: - processed/ tree now shows mcgill/ and user/ subdirs - --style user -> --style H1K0 in quick-start prefix example - pretrained.report.txt and finetuned.report.txt added to artifact tables architecture.md (-> v1.1): - remove stale music21 fallback mention from chord_parser section - fix ChordDataset: on-demand loading, not eager; remove non-existent make_dataloader from public interface - fix train function name: train_model -> train - update logging description: report goes to .report.txt, not stdout - note that scripts use max_seq_len=256 (sequences top out at 195 tokens) requirements.md (-> v1.1): - FT-12: update from unified script to pretrain.py + train.py pair Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
919 lines
56 KiB
Markdown
919 lines
56 KiB
Markdown
# Архитектура системы hamori
|
||
|
||
**Версия документа:** 1.0
|
||
**Дата:** 2026-05-19
|
||
|
||
Документ описывает архитектуру проекта _hamori_ — генератора гармонических
|
||
периодов: высокоуровневую структуру, потоки данных, состав модулей, ключевые
|
||
проектные решения и их обоснование, а также точки расширения.
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Высокоуровневая архитектура](#1-высокоуровневая-архитектура)
|
||
2. [Потоки данных](#2-потоки-данных)
|
||
3. [Состав модулей](#3-состав-модулей)
|
||
4. [Модель машинного обучения](#4-модель-машинного-обучения)
|
||
5. [Конвейер обучения](#5-конвейер-обучения)
|
||
6. [Конвейер инференса и оценки](#6-конвейер-инференса-и-оценки)
|
||
7. [Ключевые проектные решения](#7-ключевые-проектные-решения)
|
||
8. [Точки расширения](#8-точки-расширения)
|
||
|
||
---
|
||
|
||
## 1. Высокоуровневая архитектура
|
||
|
||
Система состоит из шести логических уровней.
|
||
|
||
**Уровень человекочитаемых данных.** Текстовые `.chord`-файлы лид-шит-нотации,
|
||
с которыми работает автор-композитор при ручной транскрипции. Каждый файл
|
||
описывает один гармонический период.
|
||
|
||
**Уровень парсинга и валидации.** Модули, преобразующие `.chord`-файлы в
|
||
структурированные внутренние представления и проверяющие их корректность.
|
||
|
||
**Уровень токенизации.** Модули, преобразующие структурированные представления
|
||
в последовательности целочисленных идентификаторов и обратно. Здесь же
|
||
выполняется нормализующая транспозиция в каноническую тональность.
|
||
|
||
**Уровень обучения и инференса.** Реализация нейросетевой модели, циклы
|
||
обучения и сэмплирования, работа с чекпоинтами.
|
||
|
||
**Уровень оценки.** Расчёт метрик, построение распределений, формирование
|
||
графических артефактов для отчёта.
|
||
|
||
**Уровень внешних адаптеров.** Конвертеры публичных корпусов в формат `.chord`,
|
||
экспорт периодов в MIDI.
|
||
|
||
Схема информационных связей между уровнями:
|
||
|
||
```
|
||
автор-композитор
|
||
│
|
||
▼
|
||
.chord-файлы (raw_user)
|
||
│
|
||
│
|
||
│ публичный корпус
|
||
│ │
|
||
│ ▼
|
||
│ внешний конвертер
|
||
│ │
|
||
│ ▼
|
||
│ .chord-файлы (raw_external)
|
||
│ │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
парсер + валидатор
|
||
│
|
||
▼
|
||
транспозиция в C/Am
|
||
│
|
||
▼
|
||
токенизатор
|
||
│
|
||
▼
|
||
.pt-файлы (processed)
|
||
│
|
||
┌──────────────────┼──────────────────┐
|
||
▼ ▼ ▼
|
||
train выборка val выборка holdout выборка
|
||
│ │ │
|
||
└────────┬─────────┘ │
|
||
▼ │
|
||
трансформер: pre-train + fine-tune │
|
||
│ │
|
||
▼ │
|
||
чекпоинты ◄───────────────────────┘
|
||
│ │
|
||
┌────────────────┼────────────────────────────┘
|
||
▼ ▼
|
||
инференс оценка
|
||
│ │
|
||
▼ ▼
|
||
.chord + MIDI метрики + графики + образцы
|
||
│ │
|
||
▼ ▼
|
||
автор-композитор отчёт
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Потоки данных
|
||
|
||
### 2.1 Поток подготовки собственного корпуса
|
||
|
||
```
|
||
DAW-проект (REAPER)
|
||
│
|
||
│ ручная транскрипция
|
||
▼
|
||
.chord-файл в data/raw_user/
|
||
│
|
||
│ валидация формата
|
||
│ (опционально: MIDI-санитарная проверка)
|
||
▼
|
||
.chord-файл прошёл проверку
|
||
│
|
||
│ скрипт prepare_data.py
|
||
│ ├── чтение
|
||
│ ├── транспозиция в C major / A minor
|
||
│ ├── токенизация
|
||
│ └── разбиение train / val
|
||
▼
|
||
.pt-файлы в data/processed/user/{train,val}/
|
||
```
|
||
|
||
### 2.2 Поток подготовки публичного корпуса
|
||
|
||
```
|
||
McGill Billboard (Harte-аннотации)
|
||
│
|
||
│ скрипт mcgill_to_chord.py
|
||
│ ├── парсинг Harte-нотации
|
||
│ ├── разрезание на периоды по секциям
|
||
│ ├── определение тональности
|
||
│ └── конвертация в .chord
|
||
▼
|
||
.chord-файлы в data/raw_external/mcgill_converted/
|
||
│
|
||
│ скрипт prepare_data.py
|
||
▼
|
||
.pt-файлы в data/processed/mcgill/{train,val}/
|
||
```
|
||
|
||
### 2.3 Поток обучения
|
||
|
||
```
|
||
data/processed/mcgill/ ◄── предобучение
|
||
│
|
||
▼
|
||
checkpoints/pretrained.pt
|
||
│
|
||
│ инициализация весов
|
||
▼
|
||
data/processed/user/ ◄── дообучение
|
||
│
|
||
▼
|
||
checkpoints/finetuned.pt
|
||
```
|
||
|
||
### 2.4 Поток инференса
|
||
|
||
```
|
||
пользовательские параметры (CLI)
|
||
│
|
||
│ построение prompt-токенов
|
||
│ ▼
|
||
prompt = <BOS> + метатокены + опциональный prefix
|
||
│
|
||
│ авторегрессионная генерация (top-p sampling)
|
||
▼
|
||
последовательность токенов до <EOS>
|
||
│
|
||
│ детокенизация
|
||
▼
|
||
ChordPeriod в канонической тональности C/Am
|
||
│
|
||
│ транспозиция в целевую тональность
|
||
▼
|
||
ChordPeriod в требуемой тональности
|
||
│
|
||
│ сериализация + MIDI-экспорт
|
||
▼
|
||
.chord и .mid файлы
|
||
```
|
||
|
||
### 2.5 Поток оценки
|
||
|
||
```
|
||
data/processed/holdout/
|
||
│
|
||
▼
|
||
вычисление перплексии для base и target
|
||
│
|
||
▼
|
||
извлечение признаков (типы аккордов, инверсии, интервалы корня)
|
||
│
|
||
▼
|
||
построение гистограмм и таблиц
|
||
│
|
||
▼
|
||
reports/figures/, reports/metrics.json
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Состав модулей
|
||
|
||
### 3.1 `src/chord_parser.py`
|
||
|
||
**Назначение.** Разбор отдельных аккордовых символов в строго типизированное
|
||
представление.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- `parse_chord_symbol(symbol: str) -> ChordTokens` — парсит строку вида `Am7`,
|
||
`Cmaj9`, `F/G`, `Bb7b9/D` в dataclass `ChordTokens(root, quality, extension, bass)`.
|
||
- `ChordParseError` — исключение, поднимаемое при невалидном символе.
|
||
|
||
**Ключевые соображения реализации.** Модуль не использует регулярные
|
||
выражения для парсинга качеств: вместо этого выполняется последовательное
|
||
распознавание из таблицы альтернативных написаний по принципу самого длинного
|
||
совпадения. Это упрощает добавление новых качеств в будущем и снижает риск
|
||
тонких ошибок с приоритетами совпадений.
|
||
|
||
Бемольные написания корня и баса нормализуются к диезной форме на этапе парсинга.
|
||
|
||
**Связи.** Используется модулем `tokenizer.py` для разбора аккордов внутри
|
||
периода. Не имеет зависимостей внутри проекта, кроме стандартной библиотеки
|
||
Python.
|
||
|
||
### 3.2 `src/tokenizer.py`
|
||
|
||
**Назначение.** Преобразование `.chord`-файлов в последовательности
|
||
целочисленных идентификаторов и обратно. Реализация словаря токенов.
|
||
Реализация нормализующей транспозиции.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- Константа `VOCAB: list[str]` — словарь токенов в порядке, описанном в
|
||
спецификации формата (85 токенов).
|
||
- Константа `TOKEN_TO_ID: dict[str, int]` — обратное отображение.
|
||
- Функция `parse_chord_file(path: Path) -> ChordPeriod` — парсинг
|
||
`.chord`-файла в структурированное представление.
|
||
- Функция `transpose_to_canonical(period: ChordPeriod) -> ChordPeriod` —
|
||
транспозиция мажорных периодов в C major, минорных в A minor.
|
||
- Функция `tokenize_period(period: ChordPeriod) -> list[int]` — последовательно
|
||
выполняет транспозицию и преобразование в токены.
|
||
- Функция `detokenize_to_period(token_ids: list[int]) -> ChordPeriod` —
|
||
обратная операция, возвращает период в канонической тональности.
|
||
- Функция `transpose_period(period, target_key) -> ChordPeriod` —
|
||
транспозиция в произвольную целевую тональность (используется на этапе
|
||
инференса для возврата результата в требуемую тональность).
|
||
- Исключение `ChordFormatError` — для ошибок формата файла.
|
||
|
||
**Ключевые соображения реализации.** Словарь токенов является константой
|
||
модуля; его изменение приводит к несовместимости с ранее обученными моделями,
|
||
поэтому любые изменения должны сопровождаться инкрементом версии спецификации
|
||
формата и переобучением моделей.
|
||
|
||
Транспозиция реализуется через расчёт интервала в полутонах между исходным и
|
||
целевым тонами, после чего к каждому корневому тону и бассу применяется
|
||
циклический сдвиг по 12-тоновой системе. Качество и расширения аккорда при
|
||
транспозиции не меняются.
|
||
|
||
**Связи.** Используется всеми остальными модулями для входа и выхода из
|
||
токенизированного пространства. Зависит от `chord_parser.py`.
|
||
|
||
### 3.3 `src/midi_export.py`
|
||
|
||
**Назначение.** Преобразование `.chord`-файлов в стандартные MIDI-файлы для
|
||
прослушивания в DAW и для использования сгенерированных периодов в
|
||
композиторской работе.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- `chord_file_to_midi(chord_path, midi_path, tempo=90)` — основная функция.
|
||
- `period_to_midi(period: ChordPeriod, midi_path, tempo=90)` — вариант,
|
||
принимающий уже распарсенный период.
|
||
|
||
**Ключевые соображения реализации.** MIDI-файл содержит два инструментальных
|
||
трека: трек аккордов и трек баса. Аккорды раскладываются в средней октаве
|
||
(C4–B5) тремя или четырьмя одновременными нотами, бас — в нижней октаве (C2–B2)
|
||
одной нотой. Длительности соответствуют длительностям удержания аккордов в
|
||
исходном `.chord`-файле.
|
||
|
||
Voicing внутри аккорда выполняется минимально — простое расположение нот в
|
||
тесном расположении от корня. Это не задача данного модуля и сознательно
|
||
оставлено простым.
|
||
|
||
**Связи.** Зависит от `tokenizer.py` (для парсинга `.chord`) и `pretty_midi`.
|
||
|
||
### 3.4 `src/dataset.py`
|
||
|
||
**Назначение.** Реализация PyTorch-датасета над предварительно
|
||
токенизированными `.pt`-файлами.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- Класс `ChordDataset(torch.utils.data.Dataset)`.
|
||
- Конструктор принимает путь к директории с `.pt`-файлами и максимальную
|
||
длину последовательности.
|
||
- `__getitem__` возвращает тензор токенов, обрезанный или дополненный
|
||
паддингом до максимальной длины.
|
||
|
||
**Ключевые соображения реализации.** При инициализации датасет собирает список
|
||
путей к `.pt`-файлам; сами тензоры загружаются с диска по запросу в
|
||
`__getitem__`. При текущем размере данных это не является узким местом.
|
||
|
||
Паддинг выполняется специальным токеном `<PAD>` с индексом 2 в словаре.
|
||
В функции потерь этот индекс игнорируется через параметр `ignore_index`.
|
||
|
||
### 3.5 `src/model.py`
|
||
|
||
**Назначение.** Определение нейросетевой архитектуры.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- Класс `ChordTransformer(nn.Module)` с параметрами конструктора:
|
||
`vocab_size`, `d_model`, `n_layers`, `n_heads`, `d_ff`, `max_seq_len`,
|
||
`dropout`.
|
||
|
||
**Архитектурные детали.** Декодер-only трансформер с pre-normalization
|
||
(нормализация перед остаточной связью, а не после). Эмбеддинги токенов и
|
||
позиционные эмбеддинги — обучаемые. Веса входного эмбеддинга и финальной
|
||
проекции на словарь связаны (tied weights), что сокращает число параметров
|
||
и стабилизирует обучение на малых данных.
|
||
|
||
Каждый блок трансформера состоит из:
|
||
|
||
- LayerNorm
|
||
- Causal multi-head self-attention с маскированием будущих позиций
|
||
- Residual connection
|
||
- LayerNorm
|
||
- Feedforward с активацией GELU
|
||
- Residual connection
|
||
|
||
После последнего блока — финальная LayerNorm и линейная проекция на размер
|
||
словаря.
|
||
|
||
**Связи.** Используется в модулях обучения и инференса.
|
||
|
||
### 3.6 `src/train.py`
|
||
|
||
**Назначение.** Логика обучения, общая для предобучения и дообучения.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- Функция `train(config: TrainConfig) -> Path` — основная точка
|
||
входа. Возвращает путь к лучшему чекпоинту.
|
||
- Dataclass `TrainConfig` с полями для всех гиперпараметров.
|
||
|
||
**Особенности.** Один общий цикл обучения параметризуется аргументом
|
||
`init_from`. Если этот аргумент задан, веса модели инициализируются из
|
||
указанного чекпоинта, иначе — случайно. Это позволяет использовать один и
|
||
тот же код для предобучения и дообучения, различающихся только параметрами
|
||
запуска (низкий learning rate, меньшее число эпох для дообучения).
|
||
|
||
Логирование: после каждой эпохи строка с номером эпохи, тренировочной
|
||
потерей, валидационной потерей и перплексией выводится через `logging.INFO`.
|
||
Параллельно строка добавляется в CSV-лог (`<output>.log.csv`). По окончании
|
||
прогона скрипты `scripts/pretrain.py` и `scripts/train.py` записывают сводный
|
||
отчёт с полной таблицей метрик в текстовый файл (`<output>.report.txt`).
|
||
Лучший по валидационной потере чекпоинт сохраняется отдельно.
|
||
|
||
Ранняя остановка: если валидационная потеря не улучшается на протяжении N
|
||
эпох (по умолчанию 5), обучение завершается досрочно.
|
||
|
||
### 3.7 `src/generate.py`
|
||
|
||
**Назначение.** Сэмплирование из обученной модели.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- Функция `generate_period(model, mode, time, subdivision, style, function,
|
||
key, prefix=None, temperature=1.0, top_p=0.9, max_tokens=300, seed=None)
|
||
-> ChordPeriod`.
|
||
|
||
**Ключевые соображения реализации.** Авторегрессионная генерация выполняется
|
||
по одному токену за раз. Для каждого шага:
|
||
|
||
1. Прогон последовательности через модель, получение распределения над
|
||
следующим токеном.
|
||
2. Деление логитов на температуру.
|
||
3. Применение nucleus sampling: оставляем минимальный по числу элементов
|
||
набор кандидатов с накопленной вероятностью не менее top_p.
|
||
4. Маскирование грамматически невалидных кандидатов (например, токена
|
||
расширения сразу после токена удержания).
|
||
5. Сэмплирование из оставшегося распределения.
|
||
6. Останов при появлении `<EOS>` или при достижении лимита токенов.
|
||
|
||
После завершения генерации последовательность детокенизируется, получившийся
|
||
период транспонируется из канонической тональности в целевую и возвращается
|
||
вызывающему.
|
||
|
||
### 3.8 `src/evaluate.py`
|
||
|
||
**Назначение.** Расчёт метрик качества и построение распределений.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- `compute_perplexity(model, dataloader) -> float`.
|
||
- `extract_features(period: ChordPeriod) -> dict` — извлекает гармонические
|
||
признаки периода: список типов качеств, доли инверсий, интервалы движения
|
||
корня, биграммы корней.
|
||
- `compare_distributions(baseline_features, target_features) -> dict` —
|
||
агрегирует признаки и формирует структуры для построения графиков.
|
||
- `plot_comparison(distributions, output_dir)` — рисует и сохраняет графики.
|
||
|
||
### 3.9 `src/external_converters/mcgill_to_chord.py`
|
||
|
||
**Назначение.** Конвертация аннотаций McGill Billboard Project в формат
|
||
`.chord`.
|
||
|
||
**Публичный интерфейс.**
|
||
|
||
- `convert_directory(input_dir, output_dir, log_path=None)` — конвертирует
|
||
все пьесы из исходной директории.
|
||
- `convert_song(song_dir, output_dir) -> list[Path]` — конвертирует одну
|
||
пьесу, возвращает список путей к созданным файлам периодов.
|
||
|
||
**Ключевые соображения реализации.** Harte-нотация McGill отличается от
|
||
формата проекта по ряду признаков: использует другие имена качеств, явно
|
||
указывает интервальный состав в скобках, имеет иную систему обозначения
|
||
длительностей. Конвертер реализует таблицу соответствий между Harte и форматом
|
||
проекта и приводит к ближайшему допустимому аккорду в случаях, когда точное
|
||
соответствие отсутствует.
|
||
|
||
Разрезание на периоды выполняется по разметке секций в исходных файлах
|
||
(`verse`, `chorus`, `bridge` и т.д.). Периоды длиной менее 4 или более 16
|
||
тактов пропускаются.
|
||
|
||
---
|
||
|
||
## 4. Модель машинного обучения
|
||
|
||
### 4.1 Выбор архитектуры
|
||
|
||
Архитектура декодер-only трансформера выбрана по следующим причинам.
|
||
|
||
**Соответствие задаче.** Гармоническая последовательность — это
|
||
последовательность дискретных символов с сильными локальными
|
||
зависимостями (соседние аккорды связаны функциональными отношениями) и
|
||
менее сильными глобальными зависимостями (начало и конец периода связаны
|
||
тонально). Self-attention отражает оба типа зависимостей естественным
|
||
образом.
|
||
|
||
**Совместимость со схемой предобучения + дообучения.** Архитектуры
|
||
семейства трансформеров — стандартный выбор для задач с малой целевой
|
||
выборкой и большим объёмом предобучающих данных.
|
||
|
||
**Простота реализации с нуля.** При выбранном масштабе модели (несколько
|
||
блоков, небольшая размерность) реализация умещается в нескольких сотнях
|
||
строк кода и не требует тяжёлых зависимостей.
|
||
|
||
Альтернатива в виде LSTM была рассмотрена и отвергнута на основании того,
|
||
что:
|
||
|
||
- При сопоставимом числе параметров трансформер обычно работает не хуже на
|
||
задачах с дискретными последовательностями.
|
||
- Параллелизация обучения трансформера эффективнее.
|
||
- Стандартное предобучение языковых моделей через next-token prediction
|
||
легче переносится на трансформер, чем на рекуррентные сети.
|
||
|
||
### 4.2 Параметры модели
|
||
|
||
Размер модели сознательно выбран небольшим — порядка одного-трёх миллионов
|
||
параметров. Это обусловлено объёмом обучающих данных: при тысячах примеров
|
||
крупная модель неизбежно переобучится, а компактная сохранит способность
|
||
к обобщению. Рекомендуемая конфигурация:
|
||
|
||
| Параметр | Значение |
|
||
| ---------------------------- | ------------------------------------------------------------------------------------------ |
|
||
| Число слоёв | 3 |
|
||
| Размерность модели (d_model) | 192 |
|
||
| Число голов внимания | 6 |
|
||
| Размерность FFN | 768 |
|
||
| Длина контекста | 512 токенов (256 в скриптах обучения — последовательности McGill не превышают 195 токенов) |
|
||
| Размер словаря | 85 |
|
||
| Dropout | 0.1 |
|
||
|
||
При необходимости конфигурация может быть пересмотрена в сторону уменьшения
|
||
(если модель не сходится) или увеличения (если результаты явно недостаточны
|
||
и есть запас времени на эксперимент).
|
||
|
||
### 4.3 Функция потерь и оптимизация
|
||
|
||
Стандартная кросс-энтропия с игнорированием `<PAD>`-токена. Оптимизатор —
|
||
AdamW. Расписание learning rate — косинусное снижение с линейным разогревом
|
||
на 5% от общего числа шагов.
|
||
|
||
**Предобучение.** Стартовый learning rate 3·10⁻⁴, 50 эпох (с возможностью
|
||
ранней остановки).
|
||
|
||
**Дообучение.** Стартовый learning rate 1·10⁻⁵, 15 эпох с ранней остановкой.
|
||
|
||
Двухпорядковая разница в learning rate между предобучением и дообучением —
|
||
ключевой приём для предотвращения катастрофического забывания: на этапе
|
||
дообучения веса модели изменяются медленно, что сохраняет общие
|
||
гармонические закономерности, выученные на крупном корпусе.
|
||
|
||
### 4.4 Генерация
|
||
|
||
Используется nucleus sampling (top-p) с температурой 1.0 по умолчанию.
|
||
Параметры регулируются на этапе инференса.
|
||
|
||
Beam search отвергнут на основании опыта генеративных задач: он склонен
|
||
порождать монотонные, многократно повторяющиеся последовательности, что
|
||
особенно нежелательно в задаче создания творческих идей.
|
||
|
||
---
|
||
|
||
## 5. Конвейер обучения
|
||
|
||
### 5.1 Подготовка данных
|
||
|
||
```
|
||
сырьё (.chord)
|
||
│
|
||
▼
|
||
парсинг и валидация
|
||
│
|
||
▼
|
||
транспозиция в каноническую тональность
|
||
│
|
||
▼
|
||
токенизация
|
||
│
|
||
▼
|
||
случайное разбиение на train/val (90/10)
|
||
│
|
||
▼
|
||
сохранение .pt-файлов
|
||
```
|
||
|
||
Разбиение train/val выполняется на уровне периодов, а не на уровне исходных
|
||
пьес. Для собственного корпуса это компромиссное решение: разбиение по
|
||
пьесам было бы методологически чище, но при 20–25 пьесах привело бы к
|
||
слишком высокой дисперсии валидационной потери. Holdout-выборка, в свою
|
||
очередь, специально формируется на уровне пьес, что обеспечивает честность
|
||
итоговой оценки.
|
||
|
||
### 5.2 Цикл предобучения
|
||
|
||
```
|
||
инициализация модели случайными весами
|
||
│
|
||
▼
|
||
для каждой эпохи (1..50):
|
||
│
|
||
├── проход по train: forward, loss, backward, optimizer step
|
||
│ │
|
||
│ ▼
|
||
│ агрегация train_loss за эпоху
|
||
│
|
||
├── проход по val (без градиентов): forward, loss
|
||
│ │
|
||
│ ▼
|
||
│ агрегация val_loss и val_perplexity
|
||
│
|
||
├── запись строки в CSV-лог
|
||
│
|
||
├── если val_loss улучшилась — сохранение чекпоинта
|
||
│
|
||
└── если val_loss не улучшалась 5 эпох подряд — выход
|
||
```
|
||
|
||
### 5.3 Цикл дообучения
|
||
|
||
Идентичен циклу предобучения по структуре, отличается:
|
||
|
||
- Инициализация модели из чекпоинта предобучения.
|
||
- Меньший learning rate.
|
||
- Меньшее максимальное число эпох (15).
|
||
- Опционально: меньший patience для ранней остановки.
|
||
|
||
### 5.4 Контроль качества обучения
|
||
|
||
В процессе обучения отслеживаются следующие признаки нормального хода:
|
||
|
||
- Train loss монотонно снижается.
|
||
- Val loss снижается синхронно с train loss до точки, после которой
|
||
начинается расхождение (типичное переобучение). Лучший чекпоинт
|
||
сохраняется до точки расхождения.
|
||
- Val perplexity на сошедшейся модели находится в диапазоне 2–6 для нашей
|
||
задачи. Существенно меньшие значения указывают на ошибку (например,
|
||
пересечение train и val выборок). Существенно большие — на плохую
|
||
сходимость или несоответствие модели данным.
|
||
|
||
Если эти признаки нарушаются, необходимо в первую очередь проверить
|
||
корректность подготовленных данных: токенизацию случайных файлов руками,
|
||
отсутствие пересечений между выборками, баланс распределения метаданных.
|
||
|
||
---
|
||
|
||
## 6. Конвейер инференса и оценки
|
||
|
||
### 6.1 Инференс
|
||
|
||
Подробное описание процесса генерации приведено в разделе 3.7. Ключевые
|
||
особенности:
|
||
|
||
- Все параметры запроса передаются через CLI-аргументы.
|
||
- Случайное зерно фиксируется, что обеспечивает воспроизводимость отдельных
|
||
семплов.
|
||
- Невалидные грамматические последовательности маскируются на каждом шаге
|
||
сэмплирования.
|
||
- Результат сразу сохраняется в двух форматах: `.chord` (для возможного
|
||
редактирования или подачи модели как затравки в дальнейшем) и MIDI (для
|
||
прослушивания).
|
||
|
||
### 6.2 Количественная оценка
|
||
|
||
**Перплексия** на отложенной выборке рассчитывается как экспонента средней
|
||
кросс-энтропии. Сравнение перплексий базовой и целевой моделей на одной
|
||
выборке показывает, насколько сильно дообучение сместило распределение
|
||
вероятностей модели в сторону распределения собственного корпуса автора.
|
||
|
||
Снижение перплексии на отложенной выборке после дообучения является
|
||
основным численным индикатором успеха проекта. Ожидаемая величина снижения —
|
||
от 10% до 50% относительно базовой модели.
|
||
|
||
### 6.3 Качественная оценка через распределения
|
||
|
||
Качественная сторона эффекта дообучения оценивается через сравнение
|
||
гистограмм по следующим признакам.
|
||
|
||
**Типы качеств аккордов.** Распределение по 18 базовым качествам. На малых
|
||
данных авторский стиль часто проявляется в смещении этого распределения:
|
||
например, повышенная частота больших септаккордов и нонаккордов или,
|
||
напротив, преобладание простых трезвучий.
|
||
|
||
**Доля инверсий.** Процент аккордов с явно указанным басом, отличным от
|
||
корня. Этот признак особенно характерен для индивидуального стиля и для
|
||
конкретных жанров (J-Pop, например, активно использует слэш-аккорды).
|
||
|
||
**Интервалы движения корня.** Распределение интервалов между корнями
|
||
соседних аккордов в полутонах. Например, доминирование интервала –5
|
||
полутонов (квинтовый ход вниз) характерно для барочной и классической
|
||
гармонии; преобладание интервалов –2, +2 — для более поп-ориентированных
|
||
стилей.
|
||
|
||
**Биграммы корней.** Частоты пар «текущий корень → следующий корень». Эти
|
||
биграммы захватывают функциональные предпочтения автора: например,
|
||
характерные переходы IV → V или V → vi.
|
||
|
||
Графики строятся как наложение двух гистограмм (baseline-распределение и
|
||
target-распределение) на одной координатной плоскости. Визуальный сдвиг
|
||
target относительно baseline — прямое подтверждение того, что дообучение
|
||
сработало.
|
||
|
||
### 6.4 Качественная оценка через прослушивание
|
||
|
||
Для отчёта формируются три специально подобранные («cherry-picked») пары
|
||
сгенерированных образцов: для каждой из выбранных гармонических затравок —
|
||
по одному примеру от базовой и от дообученной модели с одним и тем же
|
||
случайным зерном. Эти примеры конвертируются в MIDI и прилагаются к отчёту
|
||
(в виде ссылок и описаний).
|
||
|
||
Слепой listening-тест с привлечением сторонних слушателей не проводится из
|
||
соображений ограничения по времени.
|
||
|
||
---
|
||
|
||
## 7. Ключевые проектные решения
|
||
|
||
В этом разделе фиксируются проектные решения, принятые на этапе
|
||
проектирования, и обоснования к ним. Решения изложены в виде записей в
|
||
стиле Architectural Decision Records.
|
||
|
||
### 7.1 ПР-01. Юнит обработки — гармонический период, а не пьеса целиком
|
||
|
||
**Контекст.** Изначально рассматривался вариант обучения модели на целых
|
||
пьесах. При объёме собственного корпуса 20–25 пьес и средней длине каждой
|
||
40–100 тактов это давало бы датасет из 20–25 длинных последовательностей —
|
||
крайне малый объём для генеративной модели.
|
||
|
||
**Решение.** Единицей обработки и генерации является гармонический период —
|
||
замкнутая фраза 4–16 тактов. Из одной пьесы извлекается 4–8 периодов.
|
||
|
||
**Последствия.**
|
||
|
||
- Эффективный объём датасета увеличивается в 4–8 раз.
|
||
- Проблема обработки модуляций между секциями исчезает: внутри периода
|
||
модуляций нет.
|
||
- Длина обучающей последовательности становится меньшей и более однородной
|
||
(50–250 токенов вместо 500–1500), что упрощает обучение.
|
||
- Юнит хорошо соответствует реальному композиторскому воркфлоу: помощник
|
||
выдаёт идеи периодами, а не целыми пьесами.
|
||
|
||
### 7.2 ПР-02. Нормализующая транспозиция в C major / A minor
|
||
|
||
**Контекст.** Если каждый период хранится в исходной тональности,
|
||
функционально эквивалентные последовательности в разных тональностях
|
||
становятся для модели разными последовательностями. Это резко увеличивает
|
||
эффективное разнообразие данных в 12 раз и затрудняет обобщение.
|
||
|
||
**Решение.** Перед токенизацией все периоды транспонируются: мажорные — в
|
||
C major, минорные — в A minor. Тональность в словарь модели не входит.
|
||
На инференсе результат транспонируется обратно в требуемую тональность
|
||
постпроцессингом.
|
||
|
||
**Последствия.**
|
||
|
||
- Эффективное увеличение датасета в 12 раз.
|
||
- Сокращение словаря на 24 токена.
|
||
- Цвет конкретной тональности (характерное звучание Fis-dur против C-dur)
|
||
теряется. Это исполнительское свойство, не функционально-гармоническое,
|
||
и для задачи генерации прогрессий не релевантно.
|
||
- Внутренние модуляции и тонизации записываются обычными функциональными
|
||
аккордами и обрабатываются единообразно.
|
||
|
||
### 7.3 ПР-03. Факторизованная токенизация аккордов
|
||
|
||
**Контекст.** Каждый аккорд можно представить либо одним атомарным токеном
|
||
(`Cmaj7`, `Am7`, `F/G` как отдельные элементы словаря), либо разложенным
|
||
на несколько токенов (корень, качество, расширение, бас).
|
||
|
||
**Решение.** Каждый аккорд представляется ровно четырьмя токенами:
|
||
`ROOT_x`, `QUAL_x`, `EXT_x`, `BASS_x`. Словарь содержит 85 токенов против
|
||
нескольких сотен в случае атомарной токенизации.
|
||
|
||
**Последствия.**
|
||
|
||
- Существенно меньший словарь, легче обучаемый на малых данных.
|
||
- Модель видит общность между, например, всеми минорными септаккордами,
|
||
а не учит их как 12 несвязанных слов.
|
||
- Каждый аккорд занимает в последовательности четыре позиции вместо одной,
|
||
что увеличивает длину последовательности и нагрузку на attention. При
|
||
выбранной длине контекста 512 это не создаёт проблем.
|
||
- Появляется необходимость грамматического маскирования при генерации:
|
||
не любой токен может следовать за любым.
|
||
|
||
### 7.4 ПР-04. Двухстадийное обучение
|
||
|
||
**Контекст.** Прямое обучение модели на собственном корпусе автора
|
||
невозможно из-за крайне малого объёма данных.
|
||
|
||
**Решение.** Двухстадийная схема: предобучение на крупном публичном
|
||
корпусе (McGill Billboard Project) и последующее дообучение на собственном
|
||
корпусе с пониженным learning rate.
|
||
|
||
**Последствия.**
|
||
|
||
- Базовые гармонические закономерности (функциональная гармония,
|
||
стандартные каденции) выучиваются на этапе предобучения.
|
||
- Индивидуальный стиль автора подмешивается на этапе дообучения без
|
||
необходимости заново выучивать общие законы.
|
||
- Появляется естественная схема сравнения «до и после» дообучения для
|
||
отчёта.
|
||
- Существует риск катастрофического забывания на этапе дообучения, что
|
||
митигируется низким learning rate и небольшим числом эпох.
|
||
|
||
### 7.5 ПР-05. Минималистичная реализация без тяжёлых фреймворков
|
||
|
||
**Контекст.** Существует ряд готовых фреймворков для обучения трансформеров
|
||
(PyTorch Lightning, HuggingFace Trainer, fastai), которые скрывают
|
||
boilerplate кода тренировочного цикла.
|
||
|
||
**Решение.** Использовать чистый PyTorch с явным циклом обучения.
|
||
|
||
**Последствия.**
|
||
|
||
- Код полностью прозрачен и поддаётся пошаговой отладке, что важно для
|
||
учебного проекта.
|
||
- Снижается риск проблем с совместимостью версий и сложным поведением
|
||
фреймворков «из коробки».
|
||
- Объём кода тренировочного цикла остаётся небольшим (порядка двух сотен
|
||
строк).
|
||
- Теряется доступ к некоторым удобствам фреймворков (готовые callbacks,
|
||
логирование в TensorBoard и т.п.). Для масштабов проекта это
|
||
несущественно.
|
||
|
||
### 7.6 ПР-06. Ручная транскрипция собственного корпуса
|
||
|
||
**Контекст.** Альтернатива — автоматическое извлечение аккордов из аудио
|
||
с помощью библиотек вроде Chordino, librosa, или нейросетевых детекторов.
|
||
|
||
**Решение.** Транскрипция выполняется автором вручную, на основе
|
||
DAW-проектов с использованием абсолютного слуха.
|
||
|
||
**Последствия.**
|
||
|
||
- Качество транскрипции существенно выше автоматического: тонкие гармонические
|
||
решения, нестандартные расширения, точные инверсии — всё это передаётся
|
||
без потерь.
|
||
- Существенные временные затраты (10–15 часов). Это самая трудозатратная
|
||
часть проекта.
|
||
- Невозможность масштабирования на большой корпус. Для текущей задачи
|
||
(80–150 периодов) это приемлемо.
|
||
|
||
### 7.7 ПР-07. Английский язык в коде, русский — в документации и отчёте
|
||
|
||
**Контекст.** Учебное заведение требует оформления отчёта на русском
|
||
языке. С другой стороны, стандарты разработки и совместимость с
|
||
инструментами вроде Claude Code предполагают английский язык в коде.
|
||
|
||
**Решение.** Чёткое разделение по слоям:
|
||
|
||
- Код, идентификаторы, комментарии, сообщения логов, коммиты — английский.
|
||
- Документация (README, спецификация, требования, архитектура,
|
||
глоссарий) — русский.
|
||
- Итоговый отчёт — русский с оформлением по ГОСТу.
|
||
|
||
**Последствия.** Однозначность для всех участников разработки.
|
||
Двуязычность не создаёт неудобств, поскольку слои разделены.
|
||
|
||
---
|
||
|
||
## 8. Точки расширения
|
||
|
||
Перечисленные ниже направления развития проекта оставлены явно за рамками
|
||
текущей версии. Их реализация может рассматриваться в будущем.
|
||
|
||
### 8.1 Дообучение на корпусе японской поп-музыки
|
||
|
||
**Описание.** После защиты курсовой работы планируется собрать второй
|
||
авторский корпус — гармонические периоды из японских поп-песен (Royal Road
|
||
прогрессии, mu-аккорды, характерные секундовые надстройки, on-аккорды) — и
|
||
выполнить дополнительное дообучение модели на этом материале с тегом
|
||
`STYLE_jpop`.
|
||
|
||
**Что уже подготовлено для этого расширения.** В словаре токенов
|
||
зарезервирован токен `STYLE_jpop`. Формат `.chord` поддерживает любые
|
||
характерные для J-Pop приёмы (расширенные аккорды, инверсии, слэш-аккорды).
|
||
В шапке файла предусмотрено поле `style`.
|
||
|
||
**Что нужно дополнительно сделать.** Собрать и транскрибировать корпус
|
||
J-Pop периодов. Выполнить дообучение существующей модели на смешанном
|
||
корпусе (свой + J-Pop) или последовательное дообучение (свой → J-Pop).
|
||
Сравнить генерации с разными значениями стилевого conditioning.
|
||
|
||
### 8.2 Генерация мелодии
|
||
|
||
**Описание.** Расширение модели на генерацию монофонической мелодической
|
||
линии, привязанной к гармонической последовательности.
|
||
|
||
**Что нужно сделать.** Расширить формат `.chord` дополнительным полем для
|
||
мелодической линии (или ввести отдельный формат). Расширить словарь
|
||
токенов мелодическими токенами (вероятно, через раздельное представление
|
||
ступени, длительности, артикуляции). Архитектура модели может остаться
|
||
прежней.
|
||
|
||
**Сложность.** Существенная: задача мелодизации сложнее, чем гармонизации,
|
||
требует больше данных, имеет другие критерии оценки.
|
||
|
||
### 8.3 Voicing внутри аккорда
|
||
|
||
**Описание.** Автоматическое расположение нот внутри аккорда выше баса
|
||
с учётом голосоведения (минимизация суммарного движения голосов, запрет
|
||
параллельных квинт и октав, разрешение тяготеющих ступеней).
|
||
|
||
**Что нужно сделать.** Эта задача может быть решена rule-based методом без
|
||
машинного обучения. Простой алгоритм минимизации суммарного межаккордового
|
||
смещения голосов с дополнительными правилами укладывается в несколько
|
||
сотен строк кода.
|
||
|
||
**Сложность.** Низкая, выполнима за день-два после защиты курсовой.
|
||
|
||
### 8.4 Графический пользовательский интерфейс
|
||
|
||
**Описание.** Веб- или десктоп-приложение, позволяющее задавать параметры
|
||
генерации интерактивно, прослушивать результат прямо в браузере, сохранять
|
||
понравившиеся варианты.
|
||
|
||
**Что нужно сделать.** Любой современный веб-фреймворк (FastAPI на backend,
|
||
любой минимальный frontend) поверх существующего CLI. Воспроизведение
|
||
MIDI в браузере через `Tone.js` или подобные библиотеки.
|
||
|
||
**Сложность.** Невысокая по нынешним стандартам, но требует существенного
|
||
времени.
|
||
|
||
### 8.5 Интеграция с REAPER
|
||
|
||
**Описание.** Плагин или внешний инструмент, который при работе в REAPER
|
||
позволяет запрашивать генерацию следующего фрагмента прямо из проекта,
|
||
учитывая текущий гармонический контекст.
|
||
|
||
**Сложность.** REAPER предоставляет ReaScript для расширений на Lua и
|
||
Python. Реализация возможна, но требует погружения в API REAPER.
|
||
|
||
### 8.6 Обработка модуляций внутри периода
|
||
|
||
**Описание.** Текущая версия требует разрезания периодов по точке
|
||
модуляции. Альтернатива — введение inline-токена `MODULATE_<note>_<mode>`,
|
||
переключающего тонический центр в середине последовательности.
|
||
|
||
**Что нужно сделать.** Расширить словарь токенов на 24 модуляционных
|
||
токена. Дополнить парсер и токенизатор поддержкой inline-меток модуляции.
|
||
Накопить достаточное число обучающих примеров с модуляциями (что
|
||
проблематично при малом исходном корпусе).
|
||
|
||
**Сложность.** Средняя, основное ограничение — данные.
|
||
|
||
### 8.7 Поддержка большего числа альтераций в аккорде
|
||
|
||
**Описание.** Текущая версия поддерживает один слот расширения на аккорд.
|
||
Альтерированные доминанты с несколькими альтерациями одновременно
|
||
(`C7♯9♭13`) сворачиваются до одной альтерации.
|
||
|
||
**Что нужно сделать.** Перейти от единственного `EXT_x` токена к множеству
|
||
одновременных токенов расширений. Это требует пересмотра грамматики
|
||
последовательности и формата представления одного аккорда (теперь его
|
||
описание становится не четырёхтокеновым, а переменной длины).
|
||
|
||
**Сложность.** Средняя, в основном проектная — требуется аккуратное
|
||
обновление формата с инкрементом версии.
|
||
|
||
---
|
||
|
||
## 9. История изменений
|
||
|
||
- **1.1** (2026-05-20) — актуализация: убрана ссылка на music21, исправлено
|
||
описание ChordDataset (on-demand загрузка), исправлено имя функции train,
|
||
добавлено описание report-файла, уточнена рабочая длина контекста (256).
|
||
- **1.0** (2026-05-19) — первоначальная редакция документа.
|