Зоопарка нет

Как мы проверили, есть ли у зон MACD разные «виды» — и получили отрицательный ответ

Вопрос

Первая же кластеризация зон дала всего два грубых типа — «трендовая» и «пила». Ожидание было другим: зоопарк разнообразных видов, у каждого своя внутренняя механика.

Отсюда вопрос, который легко задать и трудно ответить честно:

Два типа — это потолок наших входов (мы описали зону слишком бедно) или потолок самих данных (разных видов там просто нет)?

Разница не академическая. Если видов много, продукт — каталог: определи вид, возьми его статистику. Если это континуум, каталог будет самообманом, а продукт — условные распределения.

Почему мы не стали просто перебирать кластеризаторы

Соблазн был очевидный: прогнать десяток алгоритмов и посмотреть, где картинка красивее. Так делать нельзя — любой партиционный метод обязан что-нибудь разделить. Дай ему облако точек без всякой структуры, попроси четыре кластера — он вернёт четыре кластера. Красота картинки в этом случае измеряет упорство алгоритма, а не устройство данных.

Поэтому сначала — гейт: проверить формально, есть ли многомодальность вообще и вдоль каких осей. И только если она есть, выбирать k и алгоритм.

Метод

Пять способов описать одну и ту же зону — от бедного к богатому:

что это
R0 8 ручных скаляров — исходный базовый набор
R1 15 обогащённых скаляров: позиционный профиль импульса и отката, квантили глубин, длительности ног, форма горба
R2 функциональный анализ формы кривой (масштаб убран)
R3 то же для амплитуды — контраст: ось размера
R4 сигнатуры пути 3-го порядка

И батарея проверок, каждая из которых считается до всякой кластеризации: Hopkins, dip-тест Хартигана по осям и по распределению попарных расстояний, кривая BIC, и главное — prediction strength: разделение, найденное на одной половине данных, проверяется на другой. Воспроизводится ли оно вообще.

Что получилось

показать код, которым посчитано
import plotly.graph_objects as go

battery = study("Z1a/battery.json")
NAMES = {"R0_v0scalars": "R0 · 8 скаляров", "R1_enriched": "R1 · обогащённые",
         "R2_FPCA_shape": "R2 · форма", "R3_FPCA_amp": "R3 · амплитуда",
         "R4_signatures": "R4 · сигнатуры"}
t = _theme("light")
COLORS = [t["muted"], t["fan"], t["median"], t["actual"], t["green"]]

fig = go.Figure()
for (row, color) in zip(battery, COLORS):
    ps = row["pred_strength"]
    ks = sorted(int(k) for k in ps)
    fig.add_trace(go.Scatter(x=ks, y=[ps[str(k)] for k in ks], mode="lines+markers",
                             name=NAMES[row["repr"]], line=dict(color=color, width=2.2)))
fig.add_hline(y=0.8, line=dict(color=t["muted"], width=1, dash="dash"))
_layout(fig, t, xtitle="число кластеров k", ytitle="prediction strength", height=380,
        title="Воспроизводится ли разбиение на независимых данных")
fig.update_xaxes(dtick=1)
show(fig)
Figure 1: Prediction strength: воспроизводится ли разбиение на независимых данных. Пунктир — общепринятый порог 0.8.

Читается это так: на k=2 держатся все пять представлений, а дальше кривые обрушиваются. Исключение — представления формы и амплитуды, у которых воспроизводимость дотягивает до k=3 и k=4. Но обогащение входов (R0 → R1, восемь скаляров против пятнадцати) картину не меняет: если бы дело было в бедности описания, именно здесь и появились бы новые воспроизводимые разделения.

Многомодальности нет ни по одной оси

Figure 2: Dip-тест Хартигана. Ниже пунктира — распределение значимо не унимодально. Ось значений логарифмическая.

Один-единственный столбик уходит под порог — расстояния в пространстве амплитуды. То есть многомодальность в данных всё-таки есть, и она ровно одна: по размеру зоны. Большие зоны отличаются от маленьких. Это не «виды», это шкала.

Проверка чужим методом

Партиционные методы обязаны делить всё — значит они могли бы размазать плотные острова по вынужденным разделам. Поэтому вердикт проверялся методом с другой посылкой: плотностной кластеризацией, которая делить не обязана и имеет право сказать «здесь шум».

Figure 3: Доля точек, помеченных шумом. 1.00 означает: ни одной области, достаточно плотной, чтобы засеять кластер.

Во всех пространствах и при всех параметрах — всё шум. Это учебная сигнатура континуума.

Зелёный столбик справа важнее остальных: это позитивный контроль. Тот же алгоритм, с той же настройкой, запущен на трёх заведомо разделённых облаках той же размерности — и нашёл их. Без этого контроля результат ничего не стоил бы: «ничего не найдено» и «искать не умеет» выглядят одинаково.

Дополнительно метод прогнали на самых мягких настройках, какие имеют смысл. Результат: один гигантский блоб и пылинка из пяти точек. Не острова.

Что оказалось ложной многомодальностью

Отдельная часть работы — проверить признаки поштучно и понять, что именно даёт «многомодальный» сигнал там, где он появляется.

Table 1
признак p dip-теста различных значений что это на самом деле
n_peaks 0.0004 8 integer-count
cnt_b2 0.0073 2170 continuous

Из всех признаков значимо не унимодальны единицы, и по крайней мере один — артефакт счётчика: у признака восемь различных значений на две с лишним тысячи зон, потому что это целое число пиков. Dip-тест видит дискретность и честно сообщает о многомодальности, но это многомодальность шкалы измерения, а не структуры данных.

Ещё один признак оказался вырожденной константой: он всегда равен единице, потому что свинги чередуются по построению алгоритма разметки. Такой признак не несёт информации вообще, и его удалили.

Информационный критерий на всех пяти представлениях уверенно тянет к шести-восьми компонентам. Соблазнительно прочитать это как «видов много».

Так читать нельзя. На континууме BIC монотонно любит больше компонент: каждая новая гауссиана чуть лучше облегает непрерывное облако, и выигрыш никогда не обнуляется, он лишь становится всё меньше. Смотреть надо на выигрыш на точку данных — он здесь исчезающе мал.

Именно так родилось раннее «оптимальное k=5», от которого пришлось отказаться.

Вердикт

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

Обогащение описания зоны — с восьми признаков до пятнадцати, плюс функциональная форма, плюс сигнатуры пути — этого не изменило. Значит потолок был не в наших входах, а в данных.

Что это изменило в работе

Гейт был поставлен именно затем, чтобы отменять планы, и он их отменил:

  • каталог видов не строится — строить его значило бы продавать бины континуума как биологию;
  • порядок работ развернулся: продукт стал приоритетнее таксономии;
  • меню кластеризаторов перецелено: их роль теперь не «найти зоопарк», а очертить принципиальные бины непрерывных осей — для языка и коммуникации, а не для механики;
  • изделием стало распределение, а не класс: ансамбль сценариев продолжения с честной мерой доверия — то, что показано на главной.

Отрицательный результат здесь — самый полезный из возможных: он стоил одного прогона батареи и сэкономил ветку разработки, которая выглядела очевидной.