# Core Предоставляет класс смеси распределений, а так же дескриптор, позволяющий реализовывать свои распределения. (Решил его вынести сюда а не в Distributions) ```{image} ../_static/core.png :alt: Select Parameters :width: 800px :align: center ``` ## Классы ### Parameter Класс, реализующий протокол дескриптора Python для управления атрибутами-параметрами в классах распределений. Этот класс решает две ключевые задачи: 1. **Валидация:** Гарантирует, что параметр всегда имеет корректное значение (например, `scale` для нормального распределения должен быть больше нуля). 2. **Фиксация:** Позволяет "замораживать" параметры, чтобы они не менялись в процессе оптимизации. - **Атрибуты** - **\- invariant: Callable[[float], bool]** Функция-предикат, которая проверяет корректность присваиваемого значения. Должна возвращать `True`, если значение допустимо, и `False` в противном случае. По умолчанию принимает любое значение (`lambda x: True`). - **\- error_message: str** Текст ошибки, которая будет вызвана, если значение не прошло проверку инварианта. - **\- public_name: str** Публичное имя атрибута, как оно задано в классе-владельце (например, `loc` или `scale`). Это имя устанавливается автоматически при создании класса через метод `__set_name__`. - **\- private_name: str** Приватное имя, используемое для хранения значения внутри экземпляра класса-владельца (например, _loc или _scale). Использование отдельного приватного имени необходимо, чтобы избежать бесконечной рекурсии при вызове getattr и setattr внутри дескриптора. - **Методы** - **\- \_\_set_name\_\_(self, owner, name)** Магический метод, который автоматически вызывается интерпретатором Python при создании класса-владельца. Он "сообщает" дескриптору имя, под которым он был присвоен атрибуту класса (например, `name` будет равно `"scale"`). - **\- \_\_get\_\_(self, instance, owner) -> float** Метод для получения значения атрибута. Когда мы обращаемся к `distribution.scale`, вызывается этот метод, который возвращает значение из `distribution._scale`. - **\- \_\_set\_\_self, instance, value)** Метод для установки нового значения. Это ключевой метод, где происходит вся логика: 1. Проверяет, не является ли этот параметр "зафиксированным" (не входит ли его `public_name` в множество `instance._fixed_params`). 2. Если не зафиксирован, проверяет новое `value` с помощью функции `self.invariant`. 3. Если обе проверки пройдены, сохраняет `value` в `instance` под приватным именем (`self.private_name`). - **Альтернативы** В прошлой версии архитектуры параметры хранились в объекте распределения в виде списка и без имён. Такой подход приводил ко многим проблемам: - Внешний пользователь работал с параметрами как со списком, поэтому не мог знать о том, какой параметр закодирован в массиве под индексом 0 или под индексом 1, об этом знал только разработчик. - Логика преобразования параметров к внутреннему и внешнему виду лежала в самом объекте распределения, и проводилась с помощью специальных методов. Дескриптор решает эту проблему, инкапсулируя эту логику внутри себя. - Если хранить параметры в таком виде, то неудобно поддерживать заморозку и разморозку параметров, т.к. это происходит по индексам, а не по именам, и для конечного пользователя все так же является непонятным. ### MixtureModel Класс смеси распределений. - **Атрибуты** - **\+ n_components: int** Количество компонент в смеси. - **\+ components: list[Distribution]** Компоненты смеси. - **\+ weights: ndarray** Веса компонент. - **\- logits: ndarray** Логиты. Необходимы для численной стабильности и соблюдения инварианта для весов. Можно оптимизировать их напрямую. - **Методы** - **\+ add_component(component: int, weight: float)** Добавляет новую компоненту и назначает ей выбранный вес. Остальные веса пересчитываются согласно их пропорциям. - **\+ remove_component(component_idx: int)** Удаляет компоненту из смеси по ее индексу. Остальные веса пересчитываются согласно их пропорциям так, чтобы соблюдался инвариант. - **+ pdf(X: ArrayLike): ndarray** Функция плотности. - **\+ lpdf(X: ArrayLike): ndarray** Логарифм функции плотности. - **\+ loglikelihood(X: ArrayLike): ndarray** Логарифм правдоподобия. - **\+ generate(size: int): ndarray** Сэмплирование выборки размера `size`. ## Различные диаграммы ### Пример взаимодействия ContinuousDistribution и дескриптора Parameter ```{mermaid} sequenceDiagram participant User participant Distribution as "экземпляр
Exponential" participant Parameter as "дескриптор
Parameter ('rate')" participant Invariant as "lambda s: s > 0" User->>Distribution: dist.rate = 2.0 activate Distribution Distribution->>Parameter: __set__(dist, 2.0) activate Parameter Parameter-->>Distribution: является ли 'rate' в dist._fixed_params? Distribution-->>Parameter: Нет Parameter->>Invariant: invariant(2.0) activate Invariant Invariant-->>Parameter: True deactivate Invariant Parameter->>Distribution: setattr(dist, "_rate", 2.0) note right of Parameter: Значение корректно и
параметр не зафиксирован.
Обновляем приватное поле. deactivate Parameter deactivate Distribution User->>Distribution: dist.rate = -1.0 activate Distribution Distribution->>Parameter: __set__(dist, -1.0) activate Parameter Parameter-->>Distribution: является ли 'rate' в dist._fixed_params? Distribution-->>Parameter: Нет Parameter->>Invariant: invariant(-1.0) activate Invariant Invariant-->>Parameter: False deactivate Invariant Parameter-->>User: raise ValueError deactivate Parameter deactivate Distribution ```