Нечеткое ассамблирование нейросетей для классификации на Python
Для ансамблирования нейросетей обычно используют простые методы, например, в задаче классификации, выбирают класс, за который проголосовало большинство моделей. Но что если рассматривать моделей, как экспертов, для которых мы можем оценить уровень точности? В этом посте я расскажу о своем подходе Fuzzy Vote, который использует нечеткую логику для объединения предсказаний моделей. Метод написан с помощью библиотеки fuzzyops, доступной через pypi. В библиотеке реализованы различные методы работы с нечеткими числами, поддерживаются вычисления на CUDA.
Идея метода
Каждая модель рассматривается как эксперт, который предсказывает вероятность принадлежности к классу и имеет определенную степень доверия. Эту информацию можно отразить через нечеткое число, в котором центр - это вероятность, ширина - неопределенность и высота - степень доверия.
Далее каждая модель "голосует" нечетким числом, числа агрегируются, и полученное число дефаззифицируется в одно значение. Полученное четкое число используется для классификации.
Генерация треугольного и гауссового нечеткого числа:
from fuzzyops.fuzzy_numbers import Domain, FuzzyNumber
def build_triangular(domain, centre, width, height):
a, b, c = centre - width/2, centre, centre + width/2
fn = domain.create_number("triangular", a, b, c)
return fn * height
def build_gauss(domain, centre, sigma, height):
fn = domain.create_number("gauss", sigma, centre)
return fn * height
Агрегация одного примера:
def aggregate_sample(probs, accs, mf_type="gauss", scale_w=1.0, gamma=1.0, defuzz="cgrav"):
domain = Domain((0.0, 1.0, 0.005), method="minimax")
fnums = []
for p, acc in zip(probs, accs):
height = acc ** gamma
width = max(0.02, (1.0 - acc) * scale_w)
if mf_type == "tri":
fnums.append(build_triangular(domain, p, width, height))
else:
sigma = width / 3.0
fnums.append(build_gauss(domain, p, sigma, height))
agg = sum(fnums[1:], start=fnums[0])
return float(agg.defuzz(defuzz))
Агрегация всей выборки и оценка:
import numpy as np
from sklearn.metrics import roc_auc_score
def evaluate_fuzzy(probs_mat, y_true, acc_vec, **kwargs):
scores = np.array([
aggregate_sample(row, acc_vec, **kwargs)
for row in probs_mat
])
scores = (scores - scores.min()) / (scores.max() - scores.min() + 1e-12)
return roc_auc_score(y_true, scores)
Как это сработало в задаче классификации пневмонии
Я обучил три модели (VGG19, ResNet50, DenseNet121) на датасете Chest X-Ray Pneumonia, взяв предобученные веса и переобучив классификатор на одну эпоху.

Метод Fuzzy-Vote дал лучшую точность, чем любая отдельная модель или простой majority vote метод. По ROC-AUC он не обошёл VGG19, но обошёл остальные методы, включая дискретный ансамбль. При этом метод не требует сложных архитектур или переобучения: он просто работает поверх уже полученных вероятностей.
Fuzzy-Vote — это простой, но гибкий способ агрегации предсказаний с учетом точности и уверенности каждой модели. Особенно полезен в случаях, когда:
модели сильно различаются по качеству
обычный majority vote даёт просадку
хочется объединить разные модели без дополнительного обучения
Но метод еще требует доработки, он не учитывает, например, confusion matrix каждой модели, чтобы учесть ошибки разного рода. Библиотека fuzzyops позволяет реализовать метод с минимумом кода и достаточно гибкой настройкой.
С полным кодом тренировки моделей и агрегирования можно ознакомиться по ссылке.