TGViewer
Channel Public Channel
Унарный код || прунинг

Унарный код || прунинг

@k_p_d_b

Subscribers
360
Photos
50
Videos
0
Links
38
Recent Posts 18 shown
Post #122 205
🔵Понятие непрерывности

Формально можно описать через эпсилон-дельта, а именно это буквально спецификация устойчивости модели к шуму во входных данных. Непрерывность функции f в точке x означает, что небольшое изменение входа даёт небольшое изменение выхода. Это же лежит в основе Lipschitz-непрерывности, которую явно контролируют при обучении GAN и при защите от adversarial атак.
def is_continuous_at(f, x, delta, eps, step=1e-4):
n = int(delta / step)
for i in range(-n, n + 1):
dx = i * step
if abs(f(x + dx) - f(x)) >= eps:
return False
return True

def relu(x):
return max(0.0, x)

def step_fn(x):
return 1.0 if x >= 0 else -1.0

print(is_continuous_at(relu, 0.0, 0.01, 0.1))
print(is_continuous_at(step_fn, 0.0, 0.01, 0.1))

ReLU непрерывна в нуле, хоть и не гладкая, но маленький сдвиг входа даёт маленький сдвиг выхода. Функция знака разрывна, именно поэтому её нельзя использовать как активацию при обучении градиентным спуском, то есть производная либо ноль, либо не определена, градиент не течёт. Это основная причина, почему перцептрон со ступенчатой активацией не обучается через обратное распределение ошибок, а требует другого подхода к обучению.
import numpy as np

def model_output(x, weights):
return np.tanh(x @ weights)

weights = np.array([0.5, -0.3])
x0 = np.array([1.0, 2.0])
noise = np.random.normal(0, 0.01, size=(20, 2))

outputs = np.array([model_output(x0 + n, weights) for n in noise])
print(outputs.std())

Малый разброс outputs.std() при малом шуме на входе, является эмпирической проверкой непрерывности модели в данной точке. Если разброс выхода непропорционально велик относительно шума на входе, модель ведёт себя как разрывная функция локально.


🔵Компактность и связность

# гомеоморфизма не будет, так как я не хочу в 100500й раз рассказывать про кружки и бублики, но если кому то интересно, то можно посмотреть всякого рода обзоры доказательства гипотезы Пуанкаре(к примеру от малого мехмата)

Связность - это гарантия, что из любой точки области можно добраться в любую другую, не выходя за её пределы. В кластеризации это является критерием качества, то есть если один кластер на самом деле состоит из двух несвязных областей плотности, значит алгоритм ошибся с числом кластеров или с метрикой.
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons

X, _ = make_moons(n_samples=200, noise=0.05, random_state=0)

db = DBSCAN(eps=0.3, min_samples=5).fit(X)
print(len(set(db.labels_)) - (1 if -1 in db.labels_ else 0))

DBSCAN строит топологию на данных через открытые eps-шары и находит связные компоненты объединения этих шаров, - то есть кластеры.
Изменение эпсилон меняет топологию, то есть слишком маленький eps рвёт один кластер на много несвязных кусков, слишком большой - склеивает разные кластеры в один связный.

Компактность, это гарантия существования максимума и минимума на множестве. Инженерно: если пространство состояний компактно, любая непрерывная функция потерь на нём достигает минимума - оптимизация гарантированно не уходит в бесконечность. Некомпактное (неограниченное) пространство параметров - это одна из основных причин расходимости градиентного спуска без регуляризации.

def loss(x):
return x ** 2 - 10 * x

def grad_descent(x0, lr, steps, bound=None):
x = x0
for _ in range(steps):
grad = 2 * x - 10
x = x - lr * grad
if bound is not None:
x = np.clip(x, -bound, bound)
return x

Без clip (без компактификации допустимой области) вторая траектория расходится. С компактным ограничением, все остаётся управляемым.

P.S
Это последний сугубо теоретический пост для базы, так как дальше пойдёт уже более практически интересная теория
  • ❤ 3
  • 🤯 3
Post #121 152
Открытые множества и непрерывность

🔵Открытые шары и окрестности

По сути это означает слово похожий. Если x - это эмбеддинг объекта, то окрестность x - это шар радиуса эпсилон вокруг него в пространстве признаков: всё, что внутри, модель или аналитик считает тем же.
import numpy as np

def in_neighborhood(x, center, eps):
return np.linalg.norm(x - center) < eps

embeddings = np.array([[2.01, 0.5], [2.05, 0.6], [1.98, 0.4], [5.30, 3.1], [1.70, 0.9]])
center = np.array([2.0, 0.5])
eps = 0.3
mask = np.array([in_neighborhood(e, center, eps) for e in embeddings])
print(embeddings[mask])

Открытое множество - объединение таких окрестностей. На практике это зона точной классификации, тт есть область признакового пространства, где модель предсказывает класс с высокой вероятностью и не колеблется. Граница туда не входит, так как на ней predict_proba равна ровно 0.5, и это чувствительная к шуму зона.
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=200, n_features=2, n_redundant=0, random_state=0)
clf = LogisticRegression().fit(X, y)

def confident_region(x, clf, margin=0.1):
proba = clf.predict_proba([x])[0]
return abs(proba[1] - 0.5) > margin

print(confident_region(X[0], clf))

🔵Топологическое пространство (интуитивно)

Как уже говорилось, что метрика(в этом случае можно сказать расстояние) - это избыточная информация. В реальности достаточно знать только, какие множества считаются открытыми, то есть какие точки в принципе можно отделить друг от друга окрестностями. Сухо это можно описать примерно так: пространство X с семейством подмножеств T называется топологическим, если T замкнуто относительно объединений (любых) и пересечений (конечных), и содержит пустое множество и само X. Это семейство T и есть топология, то есть множество того, что можно считать открытой областью, а всё остальное из этого множества выводится: замкнутые множества(то есть дополнения открытых), окрестность точки(то есть любое открытое множество, её содержащее) и граница(то что не попало ни туда, ни туда).

Главное, что полезно запомнить, это что одно и то же множество точек можно наполнить разными топологиями, и от этого меняется, что такое рядом и непрерывно. Метрика (например ранее разобранное евклидово расстояние), это только один из способов породить топологию через открытые шары. Часто, есть наборы, где данные часто живут в пространстве, где евклидова метрика физически бессмысленна (например пиксели картинки или эмбеддинги), но топологическая структура, к примеру какие точки соседи, а какие нет - всё равно нужна для обучения и должна строиться отдельно(но об этом был один из первых постов цикла, так что пойдём дальше)
  • ❤ 3
  • 👍 1
Post #120 163
Всем привет, давно постов не было, так что сегодня будет новый TDA пост, но сейчас хотел спросить, может быть, у кого-нибудь есть FIX/FAST/FLEX + ITCH/OUCH/OMD(HKEX OMD-D) pcap дамп(без сливов NDA) с колокации TradFi(LD4, LD5, NY4, FR2 от Equinix или CME(cme sbe), JPX Arrowhead, HKEX, SGX co-loc, SSE/SZSE), нужно для личного ресерча. Буду очень благодарен, если кто-то сможет поделиться, так как у самого нет доступа для записи
Лс: @K_I_17_R_A
  • 💘 2
  • 🤯 1
Post #119 294
🔵Влияние выбора метрики на TDA

Персистентная гомология формально определяется относительно фиксированной метрики, то есть смена метрики меняет фильтрацию и, как следствие, persistence diagram. На синтетике эффект можно показать через такой код:

import gudhi as gd

np.random.seed(0)
n = 200
X = np.random.randn(n, 10)
X[:, 0] *= 100
def persistence_h1(X, metric_matrix):
    rips = gd.RipsComplex(distance_matrix=metric_matrix.tolist(),
                          max_edge_length=np.inf)
    st = rips.create_simplex_tree(max_dimension=2)
    diag = st.persistence()
    return [(b, d) for dim, (b, d) in diag if dim == 1 and d != float('inf')]


from scipy.spatial.distance import squareform, pdist

D_l2 = squareform(pdist(X, metric='euclidean'))
D_l1 = squareform(pdist(X, metric='cityblock'))
D_linf = squareform(pdist(X, metric='chebyshev'))
for name, D in [('L2', D_l2), ('L1', D_l1), ('Linf', D_linf)]:
    h1 = persistence_h1(X, D)
    total_persistence = sum(d - b for b, d in h1)

Разница особенно ощущается, когда одна из координат имеет существенно больший масштаб: L_2 доминируется этой координатой и теряет структуру в остальных, L_inf полностью её игнорирует на низких значениях эпсилон, L_1 занимает промежуточное положение. На практике это означает, что предварительная нормализация признаков крайне критична, так как persistence diagram, построенная на ненормализованных данных, отражает скорее распределение масштабов координат, чем структуру многообразия(см прошлый пост)



Такие же эффекты можно заметить при применении TDA к специфическим типам данных: для текстов и косинусного расстояния на эмбедингах даёт принципиально другие диаграмы, чем евклидово расстояние; для последовательностей расстояние редактирования выделяет структуры, которые невидимы в координатном представлении, для временных рядов DTW показывет периодичности и режимы, которые попросту теряются при последовательном L_2.

#TDA101
  • 👍 1
Post #118 226
🔵Dynamic Time Warping(DTW)

DTW определяет расстояние между временными рядами, учитывая сжатия и растяжения временной оси. Формально говоря, для рядов x = (x_1, ..., x_n) и y = (y_1, ..., y_m) рассматриваются монотонные пути pi в решётке {1, ..., n} * {1, ..., m}, начинающиеся в (1, 1) и заканчивающиеся в (n, m), и минимизируется суммарная стоимость(см фото).

DTW можно реализовать примерно так(dynamic programming btw):

def dtw(x, y):
    n, m = len(x), len(y)
    D = np.full((n + 1, m + 1), np.inf)
    D[0, 0] = 0
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            cost = abs(x[i - 1] - y[j - 1])
            D[i, j] = cost + min(D[i - 1, j], D[i, j - 1], D[i - 1, j - 1])
    return D[n, m]
x = np.sin(np.linspace(0, 2 * np.pi, 100))
y = np.sin(np.linspace(0, 2 * np.pi, 80) + 0.3)


DTW не удовлетворяет неравенству треугольника и официально вообще не считается метрикой, что конечно ограничивает её применение во многих структурах и теоретически в TDA. Существуют исправленные версии, типа Lower Bound Keogh и soft-DTW Cuturi-Blondel, которые обеспечивают либо метрические свойства, либо дифференцируемость для использования в loss-функциях.


На практике, DTW применим к таким областям как speech recognition(разные говорящие, произносят слова с разной скоростью), в анализе мед данных(ЭКГ), а так же в анализе фин временных рядов.

#TDA101
Post #117 159
Для дискретных последовательностей (строки символов, последовательности ДНК, ну или временные ряды после HIPPO(если кто то еще помнит, то первые посты в канале были посвещены как раз SSM и дискретизации рядов через HIPPO)) расстояние Левенштейна определяется как минимальное число операций вставки, удаления или замены символа, необходимых для преобразования одной последовательности в другую. Функция удовлетворяет всем аксиомам метрики и вычисляется динамическим программированием за O(|s| * |t|):

def levenshtein(s, t):

    m, n = len(s), len(t)

    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(m + 1):

        dp[i][0] = i

    for j in range(n + 1):

        dp[0][j] = j

    for i in range(1, m + 1):

        for j in range(1, n + 1):

            cost = 0 if s[i - 1] == t[j - 1] else 1

            dp[i][j] = min(

                dp[i - 1][j] + 1,

                dp[i][j - 1] + 1,

                dp[i - 1][j - 1] + cost,

            )

    return dp[m][n]


Варианты edit distance включают расстояние Дамерау-Левенштейна с дополнительной операцией транспозиции, расстояние Хэмминга для последовательностей одинаковой длины с разрешённой только операцией замены, и weighted edit distance с переменной стоимостью операций. Для биоинформатики применяется Needleman-Wunsch с матрицами замещения, отражающими биологическую вероятность мутации.

Edit distance применима в TDA для построения комплексов на пространстве последовательностей - например, для анализа разнообразия репертуара T-cell receptor или сравнения протеомов.

#TDA101
Post #115 132
🔵Косинусное сходство

В задачах типа NLP используеться косинусное сходство(см фото), функция принимает значения в [-1, 1] и измеряет угол между векторами без учёта их длины. Прямое использование cos(x, y) в качестве расстояния не совсем работает: значение возрастает при увеличении сходства, а аксиома неотрицательности не удовлетворяется.

Стандартное преобразование в косинусном расстоянии(см второе фото), даёт неотрицательную симметричную функцию, обращающуюся в ноль при y = ax для a > 0. Это псевдометрика, а не метрика: разные векторы с одинаковым направлением неразличимы.


Неравенство треугольника для d_cos в общем случае тоже не выполняется, собственно метрикой является angular distance arccos(cos(x, y)) / pi. На практике косинусное расстояние применяется в условиях, где векторы предварительно нормированы на единичную сферу: в этом случае d_cos монотонно связана с евклидовым расстоянием через |x - y|_2^2 = 2 - 2 cos(x, y), и для целей ранжирования различие несущественно.

#TDA101
Post #113 132
🔵Понятие и аксиомы метрики

Для начала приведу просто определение, которые мы можете найти в большинстве ресурсов и статей по теме:
Функция d: X *X -> R называется метрикой на множестве X, если для любых x, y, z в X выполняются условия на фото.

Первое условие включает неотрицательность и невырожденность, второе - симметрию, третье - неравенство треугольника. Функция, удовлетворяющая всем условиям кроме невырожденности (то есть допускающая d(x, y) = 0 при x != y), называется псевдометрикой. Нарушение неравенства треугольника превращает функцию в premetric или dissimilarity и делает неприменимыми алгоритмы, опирающиеся на свойство треугольника - например, метрические индексы, BallTree, и большинство теоретических гарантий persistent homology.

🔵Нормы L_p

Семейство расстояний L_p для векторов x, y -> R^n определяется как показано на втором фото. При p = 1 получается манхэттенское расстояние, при p = 2 - евклидово, при p -> inf - расстояние Чебышёва d_inf(x, y) = max_i |x_i - y_i|. Условие p >= 1 необходимо для выполнения неравенства треугольника; при 0 < p < 10 функция метрикой не является.

import numpy as np
def lp_distance(x, y, p):
    if np.isinf(p):
        return np.max(np.abs(x - y))
    return np.sum(np.abs(x - y) p) (1 / p)
x = np.array([1.0, 2.0, 3.0])
y = np.array([4.0, 0.0, 1.0])
for p in [1, 2, np.inf]:
    print(f"L{p}: {lp_distance(x, y, p):.4f}")


Выбор p определяет чувствительность к выбросам и устойчивость к шуму. L_1 менее чувствителен к большим компонентам разности и используеться в задачах с тяжёлыми tails распределений, например в robust regression. L_2 удобен для гауссова шума и связан с максимальным правдоподобием при нормальной модели ошибок. L_inf выделяет максимальное покомпонентное отклонение и используется в кейсах с ограничениями на худший случай, в частности в adversarial robustness.

Влияние выбора p на соседей можно промоделировать таким кодом.

from sklearn.neighbors import NearestNeighbors
np.random.seed(0)
X = np.random.randn(1000, 50)
query = np.random.randn(1, 50)
for p in [1, 2, np.inf]:
    metric = 'chebyshev' if np.isinf(p) else 'minkowski'
    kwargs = {} if np.isinf(p) else {'p': p}
    nn = NearestNeighbors(n_neighbors=5, metric=metric, **kwargs).fit(X)
    dist, idx = nn.kneighbors(query)
    print(f"L{p}: nearest indices = {idx[0]}")


В высокоразмерных пространствах различия между метриками усиливаются: при n -> inf соотношение между максимальным и минимальным расстоянием от фиксированной точки до случайных наблюдений сжимается, причём для меньших p эффект менее выражен (Aggarwal, Hinneburg, Keim, 2001). Это даёт основания предпочитать L_1 или дробные L_p в задачах с десятками и сотнями измерений.

#TDA101
Post #112 134
С небольшим опозданием, но выпускаю второй пост из серии TDA101. Сегодня хотелось бы чуть углубиться в теорию, и описать такое понятие, как метрические пространства.

Понятие расстояния лежит в основе большинства алгоритмов машинного обучения: k-ближайших соседей, кластеризации, методов снижения размерности, gradient-based оптимизации в loss-landscape, а также topological data analysis. Как уже говорилось в прошлом посте, евклидово расстояние удобно математически, но дает представление только одного частного случая в широком классе функций, которые удовлетворяют понятию метрики(снова повтор предыдущего поста, как уже говорилось ранее, выбор метрики определяет, какие пары будут считаться близкими).

Сегодняшний пост будет чуть более сухим, так как сегодня я хотел бы формализировать само понятие метрики, основные классы метрик - нормы L_p, cosine similarity, edit distance, dynamic time warping, а так же проанализировать влияние выбора метрики на результаты persistent homology.

#TDA101
  • 👍 1
Post #111 202
Так же напоминаю, что всегда буду рад ответить на ваши вопросы в комментариях или в группе канала.
  • 🔥 3
Post #110 212
🔵Дополнительная информация

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

В телекоме(user -> eNODEb -> S-GW), я себе представляю два варианта применения(тут сразу надо сделать дисклеймер, так как тут будет очень много терминологии которая возможно будет непонятна большей части аудитории из за сугубо телеком терминов, если вы хотите узнать об этом больше, то всегда можете почитать документацию по non-3GPP конекту с EPC нодами, в частности их диаграммы): это либо анализ в control plane(S1-MME, GTP-C) или в user plane(GTP-U через S1-U и S5).

В первом варианте, поток сигнальных сообщений между eNodeB, MME, S-GW, P-GW: Attach, Bearer Setup, Handover, Paging. Признаки на сообщение: тип процедуры, IMSI hash, временные интервалы между сообщениями процедуры, размер payload, sequence number gaps. Нормальные процедуры формируют устойчивые петли в state-space (Attach -> Authentication -> Security Mode -> ESM Info -> Default барьер), что отлично реализуется через persistent homology. Атаки типа signaling storms или четещ IMSI кэтчеры +GTP packets(к примеру для получения доступа к EPC ноде через ePDG) нарушают эти петли или создают новые.

Во втором случае, сет фичей ближе к классическому network IDS: размер пакета, IAT, TEID-статистики, QCI, distribution по APN. К аномалия так же относятся data exfiltration через бэкдор-APN, abnormal handover patterns, baseband attacks типа IMSI leak через RRC.

С трейдами интереснее, допустим что есть какой то трейдер, который получает поток ITCH/OUCH/FIX-сообщений. К фичам относятся типы сообщений (Add, Modify, Delete, Trade), частоты по типам, message size distribution, sequence gaps, gateway latency jitter. Топологические аномалии здесь - это в основном баги в инфре: проблемы с feed handler, gateway congestion, exchange-side disruptions, network reordering. Это классический мониторинг приложений, по этому TDA здесь реально полезен, тк message flow имеет сложную структуру связностей.

Вторым вариантом является анализ LOB динамики. Это уже не просто анализ трафика от биржи к трейдеру, а анализ контента сообщений(не путать с DPI). Здесь фичи это состояние orderbook: n уровней bid/ask, объёмы, queue position changes, trade imbalance, order-to-trade ratio. К ананомалиям относятся spoofing, layering, momentum ignition, quote stuffing.

Топологически это интересно, так как все эти манипуляции имеют крайне понятные сигнатуры в эволюции ордербуке.

Но тут вопросом является задержка, если мы рассматриваем варианты применения в hft, то бюджет на inline детект примерно равен паре микросекунд. В теории, вычисление комплекса m = 256 входит в эти рамки на FPGA, если использовать агрегированные окна(допустим в снапшоты ордербука каждый 100 мс), но невозможно на tick by tick данных. По этому мне кажется, что логичнее это использовать в проде либо как post trade анализ(для поиска toxic flow) или как feature generation для downstream моделей, то есть мы используем персистентность на скользящем окне как входные данные для обычной модели, которая и будет применять сами решения.
  • 🤩 1
Post #109 149
🔵Применение в детектирование аномалий в сетевом трафике

Ну и теперь пример из реальной жизни, вообще это пример реализуем к любому сетевому трафику, мы может говорить об трафике на S-GW от пользователя к eNODEb, или о торговых данных(спорно, так как вопрос целостности и наличия аномалий здесь вопрос вторичный) и так далее.

Сетевой трафик можно представить как потоком многомерных наблюдений: размер пакета, межпакетный интервал, флаги TCP, размер окна, статистики source/destination port. Нормальная динамика трафика концентрируется на низкоразмерном многообразии в пространстве признаков; атаки типа port scan, DDoS, exfiltration проявляются как точки или подмножества, нарушающие топологическую структуру.

Вот offline-pipeline для проверки концепции на дампах трафика:

import numpy as np
import gudhi as gd
from gudhi.wasserstein import wasserstein_distance

def extract_features(packets):
return np.column_stack([
packets['size'],
packets['iat'],
packets['tcp_flags'],
packets['window'],
])

def reference_diagram(baseline_features, landmarks):
wc = gd.EuclideanWitnessComplex(landmarks=landmarks, witnesses=baseline_features)
st = wc.create_simplex_tree(max_alpha_square=1.0, limit_dimension=2)
diag = st.persistence()
return np.array([(b, d) for dim, (b, d) in diag
if dim == 1 and d != float('inf')])

def score_window(window_features, landmarks, ref_diag):
wc = gd.EuclideanWitnessComplex(landmarks=landmarks, witnesses=window_features)
st = wc.create_simplex_tree(max_alpha_square=1.0, limit_dimension=2)
diag = st.persistence()
test = np.array([(b, d) for dim, (b, d) in diag
if dim == 1 and d != float('inf')])
if len(test) == 0 or len(ref_diag) == 0:
return float('inf')
return wasserstein_distance(ref_diag, test, order=2)

def detect(stream, landmarks, ref_diag, window_size, threshold):
buffer = []
for pkt in stream:
buffer.append(pkt)
if len(buffer) >= window_size:
features = extract_features(buffer[-window_size:])
score = score_window(features, landmarks, ref_diag)
if score > threshold:
yield buffer[-1]['timestamp'], score



Бюджет задержки для inline IDS - около десятков микросекунд на пакет. Вычисление Vietoris-Rips на окне 10^5 точек в этот бюджет явно не укладывается; witness complex с m = 256 landmarks обеспечивает требуемую пропускную способность на устройствах типа Xilinx Alveo U280 при тактовой частоте 300 МГц. В продакшене этот пайплайн нужно заменить потоковым обновлением через StreamingWitnessComplex и аппаратным persistence engine.

В открытом доступе есть несколько реализаций комплексов, в частности в библиотеке CGAL(а именно в модуле TDA) + в том же GUHDI.
  • ❤ 1
  • ❤‍🔥 1
Post #108 135
🔵Streaming и инкрементальные обновления

В реальности, при потоковой обработке невозможно(ну точнее возможно конечно, но просто нелогично из за огромных затрат на вычисления) полностью перестроить комплекс при новом наблюдении.
Наверное многие уже догадались, что здесь так же будет использоваться скользящее окно: фиксируется окно длиной T последних наблюдений, при выходе наблюдения за границу окна выполняется откат соответствующих включений симплексов.
Логика обновления может выглядеть примерно так:

from collections import deque

class StreamingWitnessComplex:
def __init__(self, landmarks, window_size, epsilon):
self.landmarks = landmarks
self.window = deque(maxlen=window_size)
self.epsilon = epsilon
self.simplex_witnesses = {}

def update(self, point):
if len(self.window) == self.window.maxlen:
old = self.window[0]
self._retract_witness(old)

self.window.append(point)
self._add_witness(point)

def _add_witness(self, point):
dists = np.linalg.norm(self.landmarks - point, axis=1)
order = np.argsort(dists)
k_nearest = order[:3]
for i in range(len(k_nearest)):
for j in range(i + 1, len(k_nearest)):
edge = tuple(sorted([k_nearest[i], k_nearest[j]]))
if dists[k_nearest[j]] <= dists[order[2]] + self.epsilon:
self.simplex_witnesses.setdefault(edge, set()).add(id(point))

def _retract_witness(self, point):
pid = id(point)
for edge in list(self.simplex_witnesses):
self.simplex_witnesses[edge].discard(pid)
if not self.simplex_witnesses[edge]:
del self.simplex_witnesses[edge]


Алгоритм инкрементального persistent homology основан на vineyards Cohen-Steiner-Edelsbrunner-Morozov(я честно не могу это нормально перевести на русский) - отслеживании изменений persistence diagram при непрерывной деформации фильтрации. Применительно к нашей логике каждый такт состоит из трёх фаз, а именно из insertion новых симплексов, образованных свежим наблюдением + deletion симплексов, потерявших свидетелей + restoration через swap-операции.
В железе все это выполняется по очереди, то есть пока persistence computation block обрабатывает прошлое окно, distance computation unit и witness identification logic unit начинают обрабатывать новое.
Post #107 123
🔵Аппаратная архитектура

Теперь вернёмся к более практическим вопросам, в именно в тому, как такая архитектура может выглядеть на реальном FPGA.

В моей версии декодер состоит из следующих блоков.

Landmark memory хранит координаты m landmarks в формате fixed-point. Для m = 256, размерности признаков d = 16, ширины слова 16 бит требуется 256*16*16 = 64 Kбит, что укладывается в BRAM-блоки большинства устройств без обращения к внешней памяти.

Distance computation unit вычисляет d(w, l_i) для всех landmarks параллельно. При евклидовой метрике каждый PE состоит из вычитателя, умножителя и аккумулятора. Для m = 256 на платах типа Versal уходит так же 256 DSP-блоков.

Witness identification logic выполняет top-k выборку расстояний для каждого наблюдения и проверяет на условие включения симплекса.

Simplex memory хранит текущий комплекс в sparse CSR/CSC представлении, совместимом с алгоритмом boundary matrix reduction.

Persistence computation block реализует boundary matrix reduction. Алгоритм Twist Edelsbrunner-Harer использует столбцовую параллелизацию, что означает, что независимые столбцы редуцируются одновременно. На FPGA это пул воркеров с общей очередью пивотов.
  • ❤‍🔥 1
Post #106 119
🔵Landmark selection

Качество witness complex напрямую зависит от выбора landmarks. На FPGA реализуются три стратегии.
Random selection через reservoir sampling Виттера. Подходит для потока неизвестной длины + минимальная стоимость вычислений:

def reservoir_sample(stream, m):
landmarks = []
for i, point in enumerate(stream):
if i < m:
landmarks.append(point)
else:
j = np.random.randint(0, i + 1)
if j < m:
landmarks[j] = point
return np.array(landmarks)


В аппаратной реализации генератор индексов — LFSR, обновление landmarks выполняется в один такт на наблюдение.
MaxMin selection даёт равномерное покрытие многообразия:

def maxmin_landmarks(X, m, seed=0):
n = len(X)
rng = np.random.default_rng(seed)
landmarks = [rng.integers(n)]
min_dist = np.linalg.norm(X - X[landmarks[0]], axis=1)

for _ in range(m - 1):
next_idx = np.argmax(min_dist)
landmarks.append(next_idx)
new_dist = np.linalg.norm(X - X[next_idx], axis=1)
min_dist = np.minimum(min_dist, new_dist)

return np.array(landmarks)


Аппаратная реализация - систолический массив: каждая строка PE хранит координаты одного landmark + обрабатывает входящие наблюдения и обновляет вектор минимальных расстояний; глобальное дерево компараторов находит argmax.
epsilon-net selection для online добавления:

def epsilon_net_online(stream, epsilon):
landmarks = []
for point in stream:
if not landmarks:
landmarks.append(point)
continue
dists = np.linalg.norm(np.array(landmarks) - point, axis=1)
if dists.min() > epsilon:
landmarks.append(point)
return np.array(landmarks)


Этот вариант не требует общей оптимизации; обновление выполняется потоково. Для real-time применений более рабочим вариантом будет комбинация: epsilon-net для онлайнового добавления с проверкой через MaxMin pass.
Post #105 134
🔵Witness complex на практике

Идея конструкции: вместо построения симплексов на всём облаке X из n точек выбирается подмножество lanlandmark размера m <= n, симплексы строятся только на landmarks, а остальные точки выступают в роли «свидетелей»(не знаю как иначе переформулировать), определяющих, какой симплекс включить в комплекс.
Пример на синтетике через GUDHI:

import numpy as np
import gudhi as gd
import time

np.random.seed(0)
n = 5000
theta = np.linspace(0, 2 * np.pi, n)
X = np.column_stack([np.cos(theta), np.sin(theta)]) + np.random.normal(0, 0.05, (n, 2))

t0 = time.time()
rips = gd.RipsComplex(points=X, max_edge_length=0.5)
st_rips = rips.create_simplex_tree(max_dimension=2)
st_rips.persistence()
t_rips = time.time() - t0

m = 100
landmark_idx = np.random.choice(n, size=m, replace=False)
landmarks = X[landmark_idx]

t0 = time.time()
witness = gd.EuclideanWitnessComplex(landmarks=landmarks, witnesses=X)
st_wit = witness.create_simplex_tree(max_alpha_square=0.25, limit_dimension=2)
st_wit.persistence()
t_wit = time.time() - t0


На облаке n = 5000 точек witness complex с m = 100 landmarks даёт ускорение в 50-100 раз при сохранении основной топологической структуры (одна устойчивая петля H1, соответствующая окружности).
Lazy witness complex добавляет параметр v регулирующий жёсткость остальных нод: при v=0 свидетелем считается точка, которая удовлетворяет условию близости ко всем вершинам симплекса; при v>0 условие ослабляется на расстояние до (v+1)-й по близости landmark. В реальности v=2 даёт хороший баланс между чувствительностью и устойчивостью к шуму.
Post #104 150
Как уже ни раз говорилось, TDA крайне дорого и долго считать на обычном железе, и основная проблема тут в построении симплициальных комплексов. Для примера так же возьмем Vietoris-Rips complex на n точках содержит до binom(n, k+1) симплексов размерности k, что приводит к кубической сложности уже для вычисления одномерных гомологий, что в свою очередь делает невозможной реализацию в задачах, где задержка должна быть в рамках миллисекунд.
В таких условиях сама по себе реализация этих вычислений на FPGA особых плюсов не дает, а так же требует более сложный подбор структур данных, чтобы они соответствовали и согласовывались с ограничениям параллельного хранилища и пропускной способности памяти.
Но все таки решение есть, а именно Witness complex. В основе он представляет собой конструкцию, которая аппроксимирует облака точек на основе небольшого подмножества landmark-точек. Из за чего размер комплекса теперь определяется числом landmarks, а не общим числом точек, что существенно упрощает работу с ним и делает его основным кандидатом для работы с real time TDA.
И в этом после я хотел в рассказать каким образом можно выбирать landmarks точки, а так же поговорю о возможных способах представления данных для работы с TDA в условиях ограниченной памяти + пример применения алгоритма в детекте аномалий в сетевом трафике.
Post #103 216
Так же напоминаю, что у канала есть чат(https://t.me/kpdb_chatty) и если у вас возникают какие то вопросы по постам, то я буду рад на них там ответить
  • 🔥 5
Older posts →

About this channel

How can I read @k_p_d_b without a Telegram account?
TGViewer shows the public web preview Telegram publishes for Унарный код || прунинг: recent posts, photos, videos and the subscriber count, with no app, login or account.
How many subscribers does Унарный код || прунинг have?
Унарный код || прунинг (@k_p_d_b) has 360 subscribers on Telegram, refreshed roughly every 30 minutes.
Does Унарный код || прунинг know I viewed it here?
No. Public channel previews carry no viewer identity, and TGViewer has no accounts or tracking of what you look up.
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →