Оглавление
Введение
Представьте: пациент приходит на приём. Врач выслушивает жалобы и назначает обследование. Температура, общий анализ крови, рентген грудной клетки, УЗИ, мазок из горла – стандартная карточка. Часть из этого действительно нужна. Часть – назначается по привычке, «чтобы не пропустить».
Теперь вопрос: можно ли математически доказать какие симптомы несут реальную информацию о диагнозе, а какие – просто "шум"? Можно ли взять таблицу пациентов и получить на выходе точный ответ – вот эти три признака обязательны, этот четвёртый заменяем, а пятый почти бесполезен?
Оказывается, можно. И для этого не нужны нейросети, градиентный бустинг или сотни тысяч записей. Достаточно бинарной матрицы, красивой идеи из теории множеств и нескольких строк кода.
Сразу сделаю честную оговорку: я не врач и не претендую на медицинскую точность. Таблица пациентов в этой статье составлена по общедоступным представлениям из интернета – какие симптомы обычно сопровождают грипп, бронхит, пневмонию. Настоящие врачи составят такую матрицу куда точнее – на основе реальной клинической практики, статистики, протоколов. Моя цель другая: показать как математика может помочь врачу структурировать знания и найти в них закономерности. Не заменить клиническое мышление – а дать ему инструмент.
Медицина здесь – удобный и понятный пример. Тот же подход работает в промышленной диагностике, в анализе анкет, в задачах отбора признаков для машинного обучения, в системах поддержки принятия решений. Математика универсальна – меняется только предметная область.
В этой статье мы возьмём таблицу из 12 пациентов и 7 симптомов, переберём все возможные комбинации признаков, найдём те наборы которые позволяют однозначно поставить диагноз – и посчитаем вектор значимости каждого симптома. Реализацию сделаем в среде Engee на языке Julia.
Немного истории: Павлак и грубые множества
Чтобы понять откуда берётся наш алгоритм – нужно сделать небольшой экскурс в историю.
В 1982 году польский математик и информатик Здислав Павлак (Zdzisław Pawlak) опубликовал работу «Rough Sets». Это была короткая статья – меньше двадцати страниц. Но она дала начало целому направлению в анализе данных, которое сегодня насчитывает тысячи публикаций и десятки практических приложений.

Здислав родился в 1926 году в городе Лодзи. Он был одновременно математиком, логиком и пионером информатики в Польше – участвовал в создании одного из первых польских компьютеров в 1950-х годах. Но главным его вкладом в мировую науку стала именно теория грубых множеств, опубликованная когда ему было уже 56 лет.
Проблема которую он решал
До Павлака задача работы с неточными и неполными знаниями решалась двумя основными способами.
Первый – теория вероятностей. Если мы не уверены – присвоим вероятность. Грипп с вероятностью 0.7, ОРВИ с вероятностью 0.3. Подход работает, но требует численных оценок которых часто просто нет.
Второй – нечёткая логика Заде (1965). Вместо чёткого «да/нет» – степень принадлежности от 0 до 1. Температура «немного высокая» – это 0.6, «очень высокая» – это 0.95. Тоже работает, но требует экспертного задания функций принадлежности.
Павлак предложил принципиально иной подход: не размывать границы понятий и не вводить вероятности, а честно признать что некоторые объекты при имеющихся данных неразличимы – и работать с этим фактом напрямую.
Ключевая идея
Представьте что у вас есть таблица. Строки – объекты (пациенты). Столбцы – признаки (симптомы). Два объекта неразличимы относительно некоторого набора признаков если они совпадают по всем признакам из этого набора.
Например если мы смотрим только на температуру и кашель – то пациент с [температура=1, кашель=1] и другой пациент с [температура=1, кашель=1] неразличимы, даже если у них разные одышка и насморк.
Все объекты разбиваются на классы неразличимости. Внутри класса – объекты которых мы не можем отличить друг от друга при данном наборе признаков. Это называется отношением неразличимости.
Теперь появляется понятие нижнего и верхнего приближения множества. Нижнее приближение – это объекты которые мы точно можем отнести к нужному классу. Верхнее – объекты которые возможно к нему относятся. Разница между ними – это «граница неопределённости», та самая «грубость» в названии теории.
Павлак показал что с этими приближениями можно работать строго математически – выводить правила, находить зависимости, сокращать описания объектов. И всё это без вероятностей и без нечёткости.
Почему это важно
Теория грубых множеств привлекательна по нескольким причинам:
Не требует дополнительных знаний. Для вероятностного подхода нужны априорные распределения. Для нечёткой логики – функции принадлежности. Для грубых множеств нужна только сама таблица данных.
Интерпретируемость. Результат – конкретные наборы признаков и правила. Не «веса нейросети», а «для постановки диагноза достаточно проверить температуру и слабость».
Работает с малыми данными. Нейросетям нужны тысячи примеров. Грубые множества работают с таблицами в десятки строк.
Сегодня теория Павлака живёт в медицинской диагностике, в системах управления производством, в анализе финансовых рисков и в предобработке данных для машинного обучения. Но начиналось всё с простой таблицы на бумаге.
Ключевые понятия: что такое редукт?
Прежде чем переходить к коду – разберём три ключевых понятия. Без них результат будет набором цифр без смысла.
Согласованность таблицы
Таблица согласована если в ней нет двух объектов с одинаковыми условными признаками но разными целевыми классами.
Простой пример. Есть два пациента:
Температура | Кашель | Диагноз | |
|---|---|---|---|
Пациент А | 1 | 1 | Грипп |
Пациент Б | 1 | 1 | ОРВИ |
Таблица несогласована: одинаковые симптомы – разные диагнозы. Врач смотрит на одну и ту же картину и не может решить что именно у пациента. Значит этих двух симптомов недостаточно – нужно добавить ещё.
Если же:
Температура | Кашель | Насморк | Диагноз | |
|---|---|---|---|---|
Пациент А | 1 | 1 | 1 | Грипп |
Пациент Б | 1 | 1 | 0 | ОРВИ |
Теперь таблица согласована: добавили насморк – и пациенты стали различимы. Одинаковых строк с разными диагнозами нет.
Суперредукт
Суперредукт – любой согласованный набор признаков. Это может быть и три признака, и пять, и все семь. Главное условие – согласованность.
Полный набор всех симптомов – всегда суперредукт (если исходная таблица согласована). Но нас интересует не любой согласованный набор, а минимальный.
Редукт
Редукт – это минимальный суперредукт. Набор признаков который:
согласован – не порождает конфликтов в классификации;
минимален – если убрать любой один признак из набора, согласованность нарушится.
Это принципиально: редукт не просто «хороший» набор признаков – это набор в котором каждый элемент необходим. Лишних нет.
У одной таблицы может быть несколько редуктов. Это нормально и даже интересно: существуют разные минимальные способы описать объекты достаточно для классификации. Иногда можно использовать симптомы 1, 2, 5 – а иногда вместо симптома 2 подойдёт симптом 6.
Вектор значимости
Когда редуктов несколько – возникает вопрос: какой выбрать? И более интересный вопрос: какие признаки самые важные в целом?
Ответ – вектор значимости. Для каждого признака считаем в какой доле всех валидных наборов (редуктов и суперредуктов) он присутствует. Признак который входит во все наборы – обязателен. Признак который входит в половину – заменяем. Признак который почти нигде не нужен – слабый.
Формально:
где V – множество всех валидных (согласованных) наборов признаков.
Именно этот вектор мы и будем вычислять.
Постановка задачи
Итак, у нас есть таблица 12 пациентов. Для каждого зафиксировано наличие или отсутствие 7 симптомов (1 – есть, 0 – нет) и поставлен диагноз врачом.
Повторю оговорку: данные составлены по общим представлениям, а не по медицинским справочникам. Реальный врач с реальной базой данных составит эту матрицу точнее – и получит более содержательные результаты. Математика та же самая.
Таблица пациентов
Температура | Кашель | Одышка | Боль в горле | Слабость | Насморк | Головная боль | Диагноз | |
|---|---|---|---|---|---|---|---|---|
Пациент 1 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | Грипп |
Пациент 2 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | Пневмония |
Пациент 3 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | ОРВИ |
Пациент 4 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | Бронхит |
Пациент 5 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | Бронхит |
Пациент 6 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | Фарингит |
Пациент 7 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | Грипп |
Пациент 8 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | ОРВИ |
Пациент 9 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | Пневмония |
Пациент 10 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | Фарингит |
Пациент 11 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | Грипп |
Пациент 12 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | ОРВИ |
Диагноз кодируется числом: 1 = Грипп, 2 = Пневмония, 3 = ОРВИ, 4 = Бронхит, 5 = Фарингит.
Как читать эту таблицу
Каждая строка – один пациент. Каждый столбец – один симптом. Единица означает «симптом есть», ноль – «симптома нет». Диагноз – то что врач поставил по совокупности всех данных.
Несколько наблюдений которые важны для понимания задачи:
Пациенты 4 и 5 – оба с бронхитом, но симптомы разные. Пациент 4: температура, кашель, одышка – без слабости. Пациент 5: кашель, одышка, слабость – без температуры. Это нормально. Одна болезнь может проявляться по-разному у разных людей. Алгоритм это учитывает: он не требует чтобы пациенты с одинаковым диагнозом были неразличимы. Он требует только чтобы пациенты с одинаковыми симптомами имели одинаковый диагноз.
Пациенты 1, 7, 11 – все грипп, но симптомы разные. Три варианта одной болезни. Алгоритм воспринимает их как три отдельных объекта – и это правильно.
Полный набор из 7 симптомов – согласован. Это легко проверить вручную: нет двух строк с одинаковым набором нулей и единиц. Значит исходная задача корректно поставлена и у неё есть решение.
Формальная постановка
Дано:
Матрица M размером 12×7 – симптомы пациентов;
Вектор меток L длиной 12 – диагнозы.
Найти:
Все согласованные подмножества столбцов матрицы M;
Вектор значимости каждого столбца.
Алгоритм: как это работает?
Идея алгоритма прямолинейная – полный перебор с проверкой. Это не самый быстрый подход (об ограничениях поговорим позже), но самый прозрачный: видно каждый шаг, легко проверить результат.
Шаг 1. Подсчёт числа комбинаций
При n симптомах количество непустых собственных подмножеств равно:
Вычитаем 2 потому что нас не интересуют два крайних случая: пустое множество (ноль симптомов – диагноз поставить невозможно) и полное множество (все симптомы – это исходная таблица, она заведомо согласована и не несёт нового знания).
При наших 7 симптомах: 27−2=126 комбинаций. Компьютер справится с ними за доли секунды.
Для наглядности: эти 126 комбинаций распределяются так:
Убираем столбцов | Остаётся столбцов | Комбинаций |
|---|---|---|
1 | 6 | 7 |
2 | 5 | 21 |
3 | 4 | 35 |
4 | 3 | 35 |
5 | 2 | 21 |
6 | 1 | 7 |
Итого | 126 |
Шаг 2. Построение подматрицы
Для каждой комбинации берём только те столбцы которые в неё вошли. Получаем подматрицу – уменьшенное представление таблицы пациентов.
Шаг 3. Проверка согласованности
Это главный шаг. Алгоритм:
Находим все уникальные строки подматрицы – это уникальные «профили симптомов».
Для каждого профиля собираем всех пациентов у которых он совпал.
Смотрим – у всех в группе одинаковый диагноз?
Если нашли группу с разными диагнозами – конфликт, набор не годится.
Если прошли все группы без конфликтов – набор согласован.
Примеры: валидный набор
Убрали «Температуру» (столбец 1). Остались: «Кашель», «Одышка», «Боль в горле», «Слабость», «Насморк», «Головная боль».
Смотрим на пациентов 1 и 7 – оба Грипп:
Кашель | Одышка | Боль в горле | Слабость | Насморк | Гол. боль | |
|---|---|---|---|---|---|---|
Пациент 1 | 0 | 0 | 1 | 1 | 1 | 0 |
Пациент 7 | 0 | 0 | 0 | 1 | 1 | 1 |
Строки разные – они различимы даже без температуры!
Проверяем все 12 пациентов – нигде нет двух строк с одинаковыми симптомами и разными диагнозами. Набор валиден!
Примеры: невалидный набор
Убрали «Кашель», «Одышку», «Насморк» и «Головную боль». Остались: «Температура», «Боль в горле», «Слабость».
Температура | Боль в горле | Слабость | Диагноз | |
|---|---|---|---|---|
Пациент 1 | 1 | 1 | 1 | Грипп |
Пациент 2 | 1 | 1 | 1 | Пневмония |
Одинаковые симптомы – разные диагнозы. Конфликт!
Логика понятна: если убрать слишком много симптомов, разные болезни начинают «сливаться» – врач не может их различить.
Шаг 4. Подсчёт вектора значимости
Собрали все валидные наборы. Теперь для каждого симптома считаем в скольких из них он присутствует и делим на общее число валидных наборов:
Результат – число от 0 до 1. Чем ближе к 1 – тем важнее симптом.
Реализация в Engee
Всю описанную математику мы реализовали в среде Engee на языке Julia. Engee – это облачная среда динамического моделирования, доступная бесплатно прямо в браузере. Никакой установки, никаких зависимостей – открыл и считаешь. Именно поэтому порог входа минимальный: если вы знакомы с основами программирования, разобраться в коде не составит труда.
Структура кода
Скрипт разбит на четыре логических блока. Разберём каждый подробно.
Блок 1. Входные данные
using Combinatorics M = [ 1 0 0 1 1 1 0; # Пациент 1 — Грипп 1 1 1 1 1 0 0; # Пациент 2 — Пневмония 1 0 0 1 0 1 1; # Пациент 3 — ОРВИ 1 1 1 0 0 0 0; # Пациент 4 — Бронхит 0 1 1 0 1 0 0; # Пациент 5 — Бронхит 0 1 0 1 0 0 1; # Пациент 6 — Фарингит 1 0 0 0 1 1 1; # Пациент 7 — Грипп 0 0 0 1 0 1 1; # Пациент 8 — ОРВИ 1 1 1 0 1 0 0; # Пациент 9 — Пневмония 0 1 0 1 1 0 1; # Пациент 10 — Фарингит 1 1 0 0 1 0 1; # Пациент 11 — Грипп 0 0 0 0 1 1 0; # Пациент 12 — ОРВИ ] labels = [1, 2, 3, 4, 4, 5, 1, 3, 2, 5, 1, 3] symptom_names = [ "Температура", "Кашель", "Одышка", "Боль в горле", "Слабость", "Насморк", "Головная боль" ] n_rows, n_cols = size(M)
Первая строка – using Combinatorics: подключает стандартный пакет Julia для работы с комбинаторными объектами. Он даёт функциюcombinations(1:n, k),которая автоматически генерирует все сочетания изnпоk.
Все данные собраны в самом начале файла – намеренно. Хотите поменять задачу: другие болезни, другие симптомы, другое число пациентов – меняете только M, labels и symptom_names. Остальной код не трогаете. Это называется разделением данных и логики.
Диагнозы закодированы числами, а не строками. «Грипп», «ОРВИ» оставлены в комментариях для читаемости – но в вычислениях используются числа: сравнивать 1 == 1 быстрее и надёжнее чем сравнивать "Грипп" == "Грипп".
Блок 2. Функция проверки согласованности
function is_consistent(sub::Matrix{Int}, labels::Vector{Int}) unique_rows = unique(eachrow(sub)) for urow in unique_rows mask = [collect(row) == collect(urow) for row in eachrow(sub)] group_labels = labels[mask] if length(unique(group_labels)) > 1 return false end end return true end
Это сердце всего алгоритма. Функция принимает подматрицу симптомов и вектор диагнозов, возвращает true если набор согласован и false если найден конфликт.
Разберём построчно.
unique(eachrow(sub)) – итератор по строкам матрицы, из которых выбираются неповторяющиеся. Каждая уникальная строка – это уникальный «профиль симптомов». Если два пациента имеют одинаковый профиль – они попадут в одну группу.
mask = [collect(row) == collect(urow) for row in eachrow(sub)] – булев вектор длиной 12. Истина на позиции i означает что профиль i- го пациента совпадает с текущим проверяемым профилем. collect здесь необходим: eachrow возвращает представления (views), а не обычные массивы, и прямое сравнение без collect может дать неожиданный результат.
group_labels = labels[mask] – диагнозы всех пациентов из текущей группы.
length(unique(group_labels)) > 1 – если в группе больше одного уникального диагноза, значит нашли конфликт: одинаковые симптомы – разные диагнозы.
Одна деталь заслуживает отдельного внимания:
return false
Как только найден первый конфликт – функция немедленно возвращаетfalseи не проверяет остальные группы. Это ранняя остановка: зачем продолжать, если набор уже отбракован? При 126 комбинациях это незаметно, но при тысячах – даёт ощутимый выигрыш в скорости.
Блок 3. Основной перебор
valid_col_sets = Vector{Vector{Int}}() actual_total = 0 expected_total = 2^n_cols - 2 println("=" ^ 57) println(" Пациентов : $n_rows") println(" Симптомов : $n_cols") println(" Комбинаций для перебора: 2^$n_cols - 2 = $expected_total") println("=" ^ 57) for n_remove in 1 : n_cols - 1 for combo in combinations(1:n_cols, n_remove) actual_total += 1 cols_kept = setdiff(1:n_cols, combo) sub = M[:, cols_kept] if is_consistent(sub, labels) push!(valid_col_sets, cols_kept) end end end println("\nПеребрано комбинаций : $actual_total") if actual_total == expected_total println("Все варианты перебраны ✓") else println("ОШИБКА: пропущено $(expected_total - actual_total) вариантов ✗") end println("Валидных наборов : $(length(valid_col_sets))\n")
Два вложенных цикла. Внешний идёт по количеству удаляемых столбцов – от одного до n_cols - 1. Внутренний перебирает все комбинации через combinations – элегантно и без лишнего кода.
setdiff(1:n_cols, combo) возвращает индексы столбцов которые остаются после удаления. Например если убираем столбцы [2, 5] из семи – остаются [1, 3, 4, 6, 7].
M[:, cols_kept]– срез матрицы по нужным столбцам. Все строки, только выбранные столбцы.
push!(valid_col_sets, cols_kept)– добавляем индексы валидного набора в список. Именно индексы, а не саму подматрицу: они занимают меньше памяти и содержат всё необходимое для финального подсчёта.
После перебора – контрольная проверка: сравниваем ожидаемое и реальное число комбинаций. Если цифры совпали – ничего не пропущено. Простая но надёжная страховка от ошибки в логике цикла.
Блок 4. Вектор значимости и вывод
counts = zeros(Int, n_cols) n_valid = length(valid_col_sets) for cols in valid_col_sets for c in cols counts[c] += 1 end end result_vector = counts ./ n_valid println("Вектор значимости симптомов:") println(" " * "─" ^ 58) println(" $(rpad("Симптом", 16)) | $(rpad("Доля", 8)) | Дробь | Важность") println(" " * "─" ^ 58) for i in 1:n_cols bar_len = round(Int, result_vector[i] * 20) bar = "█" ^ bar_len * "░" ^ (20 - bar_len) println(" $(rpad(symptom_names[i], 16)) | " * "$(rpad(round(result_vector[i], digits=4), 8)) | " * "$(lpad(counts[i], 2))/$n_valid | $bar") end println("\nИтоговый вектор:") println(round.(result_vector, digits=4))
counts = zeros(Int, n_cols)– массив из семи нулей, по одному на каждый симптом. Будем накапливать в него счётчики.
Двойной цикл проходит по всем валидным наборам и для каждого симптома в наборе увеличивает его счётчик на единицу. После цикла counts[i] содержит число валидных наборов в которых симптом i присутствует.
counts ./ n_valid– поэлементное деление на количество валидных наборов. Точка перед / в Julia означает векторизованную операцию: делим каждый элемент массива, а не массив целиком.
Результат запуска
После запуска консоль выводит:
========================================================= Пациентов : 12 Симптомов : 7 Комбинаций для перебора: 2^7 - 2 = 126 ========================================================= Перебрано комбинаций : 126 Все варианты перебраны ✓ Валидных наборов : 17 Вектор значимости симптомов: ────────────────────────────────────────────────────────── Симптом | Доля | Дробь | Важность ────────────────────────────────────────────────────────── Температура | 1.0 | 17/17 | ████████████████████ Кашель | 0.6471 | 11/17 | █████████████░░░░░░░ Одышка | 0.6471 | 11/17 | █████████████░░░░░░░ Боль в горле | 0.4706 | 8/17 | █████████░░░░░░░░░░░ Слабость | 1.0 | 17/17 | ████████████████████ Насморк | 0.6471 | 11/17 | █████████████░░░░░░░ Головная боль | 0.6471 | 11/17 | █████████████░░░░░░░ Итоговый вектор: [1.0, 0.6471, 0.6471, 0.4706, 1.0, 0.6471, 0.6471]
Результат и интерпретация
Алгоритм нашёл 17 валидных наборов симптомов из 126 возможных. Разберём что это означает на практике – по каждому симптому отдельно, а потом сделаем общий вывод.
Температура – 1.0 (17/17)
Температура присутствует абсолютно во всех 17 валидных наборах. Это строгий математический результат: не существует ни одного согласованного набора симптомов который обходился бы без неё.
Почему? Посмотрим на матрицу внимательно. Температура делит всех пациентов на две большие группы:
Есть температура: Грипп (пациенты 1, 7, 11), Пневмония (2, 9), ОРВИ (3), Бронхит (4)
Нет температуры: Бронхит (5), Фарингит (6, 10), ОРВИ (8, 12)
Это фундаментальное разделение. Без него пациенты из разных групп начинают «смешиваться» – и никакая комбинация оставшихся симптомов не может надёжно их разграничить. Температура – это первый и самый грубый фильтр в нашей таблице.
Слабость – 1.0 (17/17)
Аналогичная ситуация. Слабость тоже входит во все 17 валидных наборов – убрать её невозможно.
Её роль другая: там где температура уже разделила группы, слабость разграничивает внутри них. Например среди пациентов с температурой – у пациента 4 (Бронхит) слабости нет, у пациента 2 (Пневмония) есть. Без этого признака они могут слиться при определённых комбинациях остальных симптомов.
Интересно что два обязательных признака – температура и слабость: не самые очевидные с медицинской точки зрения. Слабость есть при очень многих болезнях, и интуитивно кажется что она должна быть малоинформативной. Но математика говорит обратное: именно в контексте этой конкретной таблицы она незаменима.
Кашель, Одышка, Насморк, Головная боль – 0.647 (11/17)
Все четыре симптома получили одинаковую оценку – 11 из 17 валидных наборов. Это не случайное совпадение.
Эти четыре признака взаимозаменяемы в определённой мере. Существуют валидные наборы которые включают любые три из них – и при этом остаются согласованными. То есть иногда можно не проверять кашель если есть одышка, насморк и головная боль. Или не проверять насморк если есть кашель, одышка и головная боль.
Именно поэтому каждый из них отсутствует ровно в 6 валидных наборах из 17 – в тех случаях когда трое оставшихся справляются без него.
Для практики это означает: если пациент по какой-то причине не может описать один из этих симптомов – алгоритм не сломается. Есть запасные варианты.
Боль в горле – 0.471 (8/17)
Наименее информативный симптом. Меньше половины валидных наборов его включают – значит в большинстве случаев он просто не нужен.
Причина хорошо видна в таблице: боль в горле присутствует у пациентов с Гриппом (1, 2), ОРВИ (3, 8) и Фарингитом (6, 10) одновременно. Она плохо разграничивает диагнозы – есть и тут, и там. Остальные симптомы справляются с различением лучше, поэтому боль в горле легко исключается из набора без потери согласованности.
Это, пожалуй, самый неожиданный результат. Боль в горле – один из первых симптомов о которых спрашивает врач. А математика говорит: в контексте данной таблицы он наименее важен.
Итоговая картина
Сведём всё в одну таблицу:
Симптом | Значимость | Роль |
|---|---|---|
Температура | 1.00 | Обязателен – первичный фильтр |
Слабость | 1.00 | Обязателен – уточняющий фильтр |
Кашель | 0.65 | Важен, но заменяем |
Одышка | 0.65 | Важен, но заменяем |
Насморк | 0.65 | Важен, но заменяем |
Головная боль | 0.65 | Важен, но заменяем |
Боль в горле | 0.47 | Наименее информативен |
Если переформулировать практически:
Минимальный набор симптомов для однозначной постановки диагноза по данной таблице – это температура и слабость плюс любые три из четырёх: кашель, одышка, насморк, головная боль. Боль в горле при этом можно не проверять вовсе.
Важная оговорка
Повторю то что сказал во введении – но теперь это важнее чем когда-либо.
Эти выводы справедливы только для данной конкретной таблицы. Мы работаем с 12 пациентами составленными по общим представлениям из интернета. Реальная клиническая картина сложнее: болезни протекают по-разному, симптомы перекрываются иначе, выборка из 12 человек статистически ничтожна.
Настоящий врач с реальной базой данных – скажем, 500 пациентов с подтверждёнными диагнозами – получит совершенно другие числа. И они будут куда более содержательными. Математический метод тот же самый. Качество результата определяется качеством данных.
Именно в этом и есть главная идея статьи: метод работает, инструмент готов. Осталось подставить правильные данные.
Заключение
Мы прошли путь от простого вопроса – какие симптомы реально важны: до математически обоснованного ответа с конкретными числами и работающим кодом.
Главный вывод: за задачей отбора признаков стоит красивая и строгая математика, которой уже больше сорока лет. Теория грубых множеств Павлака, опубликованная в 1982 году, даёт инструмент который работает без нейросетей, без больших данных и без сложных вычислений – только таблица, логика и перебор.
Результат получился неожиданным даже для такой простой таблицы: температура и слабость оказались обязательными признаками, а боль в горле – наименее информативным симптомом, хотя интуиция подсказывала бы обратное. Именно в этом и есть ценность формального метода: он не доверяет интуиции, он считает.
Повторю оговорку которую делал на протяжении всей статьи: наша таблица составлена по общим представлениям, а не по клинической практике. Настоящий врач с реальной базой данных получит другие числа – и они будут куда содержательнее. Математика та же самая. Качество результата определяется качеством данных. Именно поэтому метод имеет смысл не сам по себе, а как инструмент в руках специалиста – врача, инженера, аналитика – который понимает предметную область и умеет правильно составить таблицу.
Если тема заинтересовала – следующий шаг это матрица различимости Павлака. Это более элегантный математический способ найти все редукты без полного перебора: вместо 2n−2 проверок строится одна матрица размером n×n из которой редукты извлекаются алгебраически. При большом числе признаков это принципиально быстрее. А ещё дальше –жадные и генетические алгоритмы поиска редуктов, которые работают даже когда полный перебор невозможен.
И напоследок: медицина здесь была лишь удобным примером. Та же матрица, тот же код, те же формулы – работают для диагностики промышленного оборудования, для анализа анкет, для отбора признаков перед обучением классификатора. Математика универсальна. Осталось только найти свою таблицу.
