Фаза 01 · урок 07

Теорема Байеса и статистическое мышление

Цель урока: Точность медицинского теста составляет 99%. Вы получили положительный результат. Какова вероятность того, что у вас действительно есть болезнь?

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
Математические основы
Чтение
17 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Проблема
  3. Концепция
  4. От совместной вероятности к Байесу
  5. Четыре составляющие
  6. Пример с медицинским тестом
  7. Пример со спам-фильтром
  8. Naive Bayes: допущение независимости
  9. Оценка максимального правдоподобия (MLE)
  10. Максимум апостериорной вероятности (MAP)
  11. Байесовский и частотный подходы: практическая разница
  12. Почему байесовское мышление важно для ML
  13. Соберите это
  14. Шаг 1: функция теоремы Байеса
  15. Шаг 2: классификатор Naive Bayes
  16. Шаг 3: обучите на данных о спаме
  17. Шаг 4: изучите выученные вероятности
  18. Используйте это
  19. Подготовьте результат
  20. Сопряжённые априорные распределения
  21. Последовательное байесовское обновление
  22. Связь с A/B-тестированием
  23. Упражнения
  24. Ключевые термины
  25. Дополнительные материалы

Вероятность — это то, чего вы ожидаете. Теорема Байеса — это то, чему вы учитесь.

Тип: Практика Язык: Python Предварительные требования: Фаза 1, урок 06 («Основы вероятности») Время: около 75 минут

Цели обучения

  • Применять теорему Байеса для вычисления апостериорных вероятностей на основе априорных вероятностей, правдоподобий и свидетельств.
  • Построить с нуля текстовый классификатор Naive Bayes со сглаживанием Лапласа и вычислениями в логарифмическом пространстве.
  • Сравнить оценки MLE и MAP и объяснить, как MAP соответствует L2-регуляризации.
  • Реализовать последовательное байесовское обновление, используя сопряжённые априорные распределения Beta-Binomial для A/B-тестирования.

Проблема

Точность медицинского теста составляет 99%. Вы получили положительный результат. Какова вероятность того, что у вас действительно есть болезнь?

Большинство людей скажет: 99%. Реальный ответ зависит от того, насколько редка болезнь. Если она встречается у 1 человека из 10 000, положительный результат даёт вам лишь примерно 1% вероятности быть больным. Остальные 99% положительных результатов — это ложные тревоги у здоровых людей.

Это не вопрос с подвохом. Это теорема Байеса. Каждый спам-фильтр, каждая медицинская диагностика, каждая модель машинного обучения, которая учитывает неопределённость, использует именно такое рассуждение. Вы начинаете с убеждения. Видите свидетельство. Обновляете его.

Если вы строите ML-системы, не понимая этого, вы будете неверно интерпретировать выходы моделей, задавать плохие пороги и выпускать чрезмерно уверенные прогнозы.

Концепция

От совместной вероятности к Байесу

Вы уже знаете из урока 06, что условная вероятность равна:

P(A|B) = P(A and B) / P(B)

А симметрично:

P(B|A) = P(A and B) / P(A)

У обоих выражений одинаковый числитель: P(A и B). Приравняйте их и преобразуйте:

P(A and B) = P(A|B) * P(B) = P(B|A) * P(A)

Следовательно:

P(A|B) = P(B|A) * P(A) / P(B)

Это теорема Байеса. Четыре величины, одно уравнение.

Четыре составляющие

Часть Название Что это означает
P(A|B) Апостериорная вероятность Ваше обновлённое убеждение об A после наблюдения свидетельства B
P(B|A) Правдоподобие Насколько вероятно свидетельство B, если A истинно
P(A) Априорная вероятность Ваше убеждение об A до наблюдения какого-либо свидетельства
P(B) Свидетельство Общая вероятность увидеть B при всех возможностях

Член-свидетельство P(B) действует как нормализатор. Его можно развернуть с помощью закона полной вероятности:

P(B) = P(B|A) * P(A) + P(B|not A) * P(not A)

Пример с медицинским тестом

Болезнь встречается у 1 человека из 10 000. Тест имеет точность 99% (выявляет 99% больных людей, даёт ложноположительный результат в 1% случаев).

P(sick)          = 0.0001     (prior: disease is rare)
P(positive|sick) = 0.99       (likelihood: test catches it)
P(positive|healthy) = 0.01    (false positive rate)

P(positive) = P(positive|sick) * P(sick) + P(positive|healthy) * P(healthy)
            = 0.99 * 0.0001 + 0.01 * 0.9999
            = 0.000099 + 0.009999
            = 0.010098

P(sick|positive) = P(positive|sick) * P(sick) / P(positive)
                 = 0.99 * 0.0001 / 0.010098
                 = 0.0098
                 = 0.98%

Меньше 1%. Априорная вероятность доминирует. Когда состояние редко, даже точные тесты дают преимущественно ложноположительные результаты. Поэтому врачи назначают подтверждающие тесты.

Пример со спам-фильтром

Вы получили электронное письмо со словом “lottery”. Это спам?

P(spam)                = 0.3      (30% of email is spam)
P("lottery"|spam)      = 0.05     (5% of spam emails contain "lottery")
P("lottery"|not spam)  = 0.001    (0.1% of legitimate emails contain "lottery")

P("lottery") = 0.05 * 0.3 + 0.001 * 0.7
             = 0.015 + 0.0007
             = 0.0157

P(spam|"lottery") = 0.05 * 0.3 / 0.0157
                  = 0.955
                  = 95.5%

Одно слово сдвигает вероятность с 30% до 95,5%. Настоящий спам-фильтр применяет Байеса одновременно к сотням слов.

Naive Bayes: допущение независимости

Naive Bayes распространяет этот подход на несколько признаков, предполагая, что все признаки условно независимы при заданном классе:

P(class | feature_1, feature_2, ..., feature_n)
  = P(class) * P(feature_1|class) * P(feature_2|class) * ... * P(feature_n|class)
    / P(feature_1, feature_2, ..., feature_n)

«Наивная» часть — это допущение независимости. В тексте появления слов не независимы (“New” и “York” коррелированы). Но на практике это допущение работает удивительно хорошо, потому что классификатору нужно лишь ранжировать классы, а не выдавать калиброванные вероятности.

Поскольку знаменатель одинаков для всех классов, его можно пропустить и просто сравнивать числители:

score(class) = P(class) * product of P(feature_i | class)

Выберите класс с наивысшей оценкой.

Оценка максимального правдоподобия (MLE)

Как получить P(feature|class) из обучающих данных? Подсчитать.

P("free"|spam) = (number of spam emails containing "free") / (total spam emails)

Это MLE: выбирайте значения параметров, при которых наблюдаемые данные наиболее вероятны. Вы максимизируете функцию правдоподобия, которая для дискретных подсчётов сводится к относительной частоте.

Проблема: если слово никогда не встречается в спаме во время обучения, MLE присваивает ему нулевую вероятность. Одно невиданное слово обнуляет всё произведение. Исправьте это сглаживанием Лапласа:

P(word|class) = (count(word, class) + 1) / (total_words_in_class + vocabulary_size)

Добавление 1 к каждому счётчику гарантирует, что вероятность никогда не будет нулевой.

Максимум апостериорной вероятности (MAP)

MLE спрашивает: какие параметры максимизируют P(data|parameters)?

MAP спрашивает: какие параметры максимизируют P(parameters|data)?

Согласно теореме Байеса:

P(parameters|data) proportional to P(data|parameters) * P(parameters)

MAP добавляет априорное распределение к самим параметрам. Если вы считаете, что параметры должны быть малыми, вы кодируете это как априорное распределение, штрафующее большие значения. Это идентично L2-регуляризации в ML. Штраф «гребня» в ridge-регрессии буквально является гауссовским априорным распределением весов.

Оценка Оптимизирует Эквивалент в ML
MLE P(data|params) Обучение без регуляризации
MAP P(data|params) * P(params) L2 / L1-регуляризация

Байесовский и частотный подходы: практическая разница

Частотники считают параметры фиксированными неизвестными величинами. Они спрашивают: «Если бы я повторил этот эксперимент много раз, что бы произошло?»

Байесовцы рассматривают параметры как распределения. Они спрашивают: «Учитывая то, что я наблюдал, во что я верю относительно параметров?»

Для построения ML-систем практическая разница такова:

Аспект Частотный подход Байесовский подход
Выход Точечная оценка Распределение значений
Неопределённость Доверительные интервалы (о процедуре) Байесовские доверительные интервалы (о параметре)
Мало данных Может переобучиться Априорная вероятность действует как регуляризация
Вычисления Обычно быстрее Часто требует сэмплирования (MCMC)

Большинство production ML использует частотный подход (SGD, точечные оценки). Байесовские методы особенно хороши, когда нужна калиброванная неопределённость (медицинские решения, критические для безопасности системы) или когда данных мало (few-shot learning, холодный старт).

Почему байесовское мышление важно для ML

Связь глубже, чем аналогия:

Априорные вероятности — это регуляризация. Гауссовское априорное распределение весов — это L2-регуляризация. Априорное распределение Лапласа — L1. Каждый раз, когда вы добавляете член регуляризации, вы делаете байесовское утверждение о том, какие значения параметров ожидаете.

Апостериорные вероятности — это неопределённость. Одна предсказанная вероятность ничего не говорит о том, насколько модель уверена в этой оценке. Байесовские методы дают распределение: «Я думаю, P(spam) находится между 0,8 и 0,95».

Байесовские обновления — это онлайн-обучение. Сегодняшняя апостериорная вероятность становится завтрашней априорной. Когда модель видит новые данные, она постепенно обновляет свои убеждения вместо того, чтобы переобучаться с нуля.

Сравнение моделей — байесовское. Байесовский информационный критерий (BIC), маргинальное правдоподобие и факторы Байеса используют байесовское рассуждение для выбора между моделями без переобучения.

bayes-update

Соберите это

Шаг 1: функция теоремы Байеса

def bayes(prior, likelihood, false_positive_rate):
    evidence = likelihood * prior + false_positive_rate * (1 - prior)
    posterior = likelihood * prior / evidence
    return posterior

result = bayes(prior=0.0001, likelihood=0.99, false_positive_rate=0.01)
print(f"P(sick|positive) = {result:.4f}")

Шаг 2: классификатор Naive Bayes

import math
from collections import defaultdict

class NaiveBayes:
    def __init__(self, smoothing=1.0):
        self.smoothing = smoothing
        self.class_counts = defaultdict(int)
        self.word_counts = defaultdict(lambda: defaultdict(int))
        self.class_word_totals = defaultdict(int)
        self.vocab = set()

    def train(self, documents, labels):
        for doc, label in zip(documents, labels):
            self.class_counts[label] += 1
            words = doc.lower().split()
            for word in words:
                self.word_counts[label][word] += 1
                self.class_word_totals[label] += 1
                self.vocab.add(word)

    def predict(self, document):
        words = document.lower().split()
        total_docs = sum(self.class_counts.values())
        vocab_size = len(self.vocab)
        best_class = None
        best_score = float("-inf")
        for cls in self.class_counts:
            score = math.log(self.class_counts[cls] / total_docs)
            for word in words:
                count = self.word_counts[cls].get(word, 0)
                total = self.class_word_totals[cls]
                score += math.log((count + self.smoothing) / (total + self.smoothing * vocab_size))
            if score > best_score:
                best_score = score
                best_class = cls
        return best_class

Логарифмы вероятностей предотвращают потерю значимости. Умножение множества малых вероятностей создаёт числа, слишком маленькие для чисел с плавающей точкой. Суммирование логарифмов вероятностей численно устойчиво и математически эквивалентно.

Шаг 3: обучите на данных о спаме

train_docs = [
    "win free money now",
    "free lottery ticket winner",
    "claim your prize today free",
    "urgent offer free cash",
    "congratulations you won free",
    "meeting tomorrow at noon",
    "project update attached",
    "can we schedule a call",
    "quarterly report review",
    "lunch on thursday sounds good",
    "team standup notes attached",
    "please review the pull request",
]

train_labels = [
    "spam", "spam", "spam", "spam", "spam",
    "ham", "ham", "ham", "ham", "ham", "ham", "ham",
]

classifier = NaiveBayes()
classifier.train(train_docs, train_labels)

test_messages = [
    "free money waiting for you",
    "meeting rescheduled to friday",
    "you won a free prize",
    "please review the attached report",
]

for msg in test_messages:
    print(f"  '{msg}' -> {classifier.predict(msg)}")

Шаг 4: изучите выученные вероятности

def show_top_words(classifier, cls, n=5):
    vocab_size = len(classifier.vocab)
    total = classifier.class_word_totals[cls]
    probs = {}
    for word in classifier.vocab:
        count = classifier.word_counts[cls].get(word, 0)
        probs[word] = (count + classifier.smoothing) / (total + classifier.smoothing * vocab_size)
    sorted_words = sorted(probs.items(), key=lambda x: x[1], reverse=True)
    for word, prob in sorted_words[:n]:
        print(f"    {word}: {prob:.4f}")

print("\nTop spam words:")
show_top_words(classifier, "spam")
print("\nTop ham words:")
show_top_words(classifier, "ham")

Используйте это

Scikit-learn поставляет готовые для production реализации naive Bayes:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report

vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(train_docs)
clf = MultinomialNB()
clf.fit(X_train, train_labels)

X_test = vectorizer.transform(test_messages)
predictions = clf.predict(X_test)
for msg, pred in zip(test_messages, predictions):
    print(f"  '{msg}' -> {pred}")

Тот же алгоритм. CountVectorizer обрабатывает токенизацию и построение словаря. MultinomialNB внутренне обрабатывает сглаживание и логарифмы вероятностей. Ваша версия с нуля делает то же самое в 40 строках.

Подготовьте результат

Построенный здесь класс NaiveBayes демонстрирует полный конвейер: токенизацию, оценивание вероятностей со сглаживанием Лапласа, предсказание в логарифмическом пространстве. Код в code/bayes.py запускается от начала до конца без зависимостей, кроме стандартной библиотеки Python.

Сопряжённые априорные распределения

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

Правдоподобие Сопряжённое априорное распределение Апостериорное распределение Пример
Бернулли Бета(a, b) Бета(a + успехи, b + неудачи) Оценка смещения монеты
Нормальное (известная дисперсия) Нормальное(mu_0, sigma_0) Нормальное(взвешенное среднее, меньшая дисперсия) Калибровка датчика
Пуассоновское Гамма(a, b) Гамма(a + сумма счётчиков, b + n) Моделирование интенсивности поступлений
Мультиномиальное Дирихле(alpha) Дирихле(alpha + счётчики) Тематическое моделирование, языковые модели

Почему это важно: без сопряжённых априорных распределений вам потребуется сэмплирование методом Монте-Карло или вариационный вывод, чтобы приблизить апостериорное распределение. С сопряжёнными априорными распределениями вы просто обновляете два числа.

Бета-распределение — наиболее распространённое на практике сопряжённое априорное распределение. Beta(a, b) представляет ваше убеждение о параметре вероятности. Его среднее равно a/(a+b). Чем больше a+b, тем более концентрировано (уверенно) распределение.

Частные случаи априорного Beta-распределения:

  • Beta(1, 1) = равномерное. У вас нет мнения о параметре.
  • Beta(10, 10) = пик в 0,5. Вы твёрдо считаете, что параметр близок к 0,5.
  • Beta(1, 10) = смещено к 0. Вы считаете параметр малым.

Правило обновления предельно простое:

Prior:     Beta(a, b)
Data:      s successes, f failures
Posterior: Beta(a + s, b + f)

Никаких интегралов. Никакого сэмплирования. Только сложение.

Последовательное байесовское обновление

Байесовский вывод по своей природе последователен. Сегодняшняя апостериорная вероятность становится завтрашней априорной. Так реальные системы обучаются постепенно без повторной обработки всех исторических данных.

Конкретный пример: оценка того, честна ли монета.

День 1: данных пока нет. Начните с Beta(1, 1) — равномерного априорного распределения. У вас нет мнения.

  • Среднее априорного распределения: 0,5
  • Априорное распределение плоское на [0, 1]

День 2: наблюдайте 7 орлов, 3 решки. Апостериорное распределение = Beta(1 + 7, 1 + 3) = Beta(8, 4)

  • Среднее апостериорного распределения: 8/12 = 0,667
  • Свидетельства указывают, что монета смещена в сторону орлов

День 3: наблюдайте ещё 5 орлов, 5 решек. Используйте вчерашнее апостериорное распределение как сегодняшнее априорное. Апостериорное распределение = Beta(8 + 5, 4 + 5) = Beta(13, 9)

  • Среднее апостериорного распределения: 13/22 = 0,591
  • Сбалансированные новые данные потянули оценку обратно к 0,5

Диаграмма к уроку «Теорема Байеса и статистическое мышление»

Порядок наблюдений не имеет значения. Beta(1,1), обновлённое сразу всеми 12 орлами и 8 решками, даёт Beta(13, 9) — тот же результат. Последовательное обновление и пакетное обновление математически эквивалентны. Но последовательное обновление позволяет принимать решения на каждом шаге, не сохраняя исходные данные.

Это основа онлайн-обучения в production ML-системах. Thompson sampling для бандитов, инкрементальные рекомендательные системы и потоковые детекторы аномалий используют этот шаблон.

Связь с A/B-тестированием

A/B-тестирование — это байесовский вывод под прикрытием.

Настройка: вы тестируете два цвета кнопки. Вариант A (синий) и вариант B (зелёный). Вы хотите узнать, какой из них получает больше кликов.

Байесовский A/B-тест:

  1. Априорное распределение. Начните с Beta(1, 1) для обоих вариантов. Нет априорного предпочтения.
  2. Данные. Вариант A: 50 кликов из 1000 просмотров. Вариант B: 65 кликов из 1000 просмотров.
  3. Апостериорные распределения.
    • A: Beta(1 + 50, 1 + 950) = Beta(51, 951). Среднее = 0,051
    • B: Beta(1 + 65, 1 + 935) = Beta(66, 936). Среднее = 0,066
  4. Решение. Вычислите P(B > A) — вероятность того, что истинная конверсия B выше, чем у A.

Аналитически вычислить P(B > A) сложно. Но метод Монте-Карло делает это тривиальным:

1. Draw 100,000 samples from Beta(51, 951)  -> samples_A
2. Draw 100,000 samples from Beta(66, 936)  -> samples_B
3. P(B > A) = fraction of samples where B > A

Если P(B > A) > 0,95, вы выпускаете вариант B. Если она находится между 0,05 и 0,95, продолжаете собирать данные. Если P(B > A) < 0,05, выпускаете вариант A.

Преимущества по сравнению с частотным A/B-тестированием:

  • Вы получаете прямое вероятностное утверждение: «вероятность того, что B лучше, составляет 97%».
  • Нет путаницы с p-value. Нет оговорки «не удалось отвергнуть нулевую гипотезу».
  • Можно проверять результаты в любое время, не увеличивая частоту ложноположительных результатов (нет «проблемы подглядывания»).
  • Можно учесть априорные знания (например, предыдущие тесты указывают, что конверсия обычно составляет 3–8%).
Аспект Частотное A/B Байесовское A/B
Выход p-value P(B > A)
Интерпретация «Насколько удивительны эти данные, если A=B?» «Насколько вероятно, что B лучше A?»
Ранняя остановка Увеличивает ложноположительные результаты Безопасна в любой момент (при удачно выбранном априорном распределении и правильно заданной модели)
Априорные знания Не используются Кодируются как априорное Beta-распределение
Правило решения p < 0.05 P(B > A) > порог

Упражнения

  1. Несколько тестов. Пациент дважды получает положительный результат независимых тестов (оба имеют точность 99%, распространённость болезни — 1 на 10 000). Какова P(sick) после обоих тестов? Используйте апостериорную вероятность после первого теста как априорную вероятность для второго.

  2. Влияние сглаживания. Запустите классификатор спама со значениями сглаживания 0.01, 0.1, 1.0 и 10.0. Как меняются вероятности верхних слов? Что происходит при smoothing=0 и слове, которое встречается только в ham?

  3. Добавьте признаки. Расширьте класс NaiveBayes так, чтобы наряду с подсчётами слов он также использовал длину сообщения (короткое/длинное) как признак. Оцените P(short|spam) и P(short|ham) по обучающим данным и включите это в оценку предсказания.

  4. MAP вручную. По наблюдаемым данным (7 орлов в 10 подбрасываниях монеты) вычислите MAP-оценку смещения, используя априорное Beta(2,2)-распределение. Сравните её с MLE-оценкой (7/10).

Ключевые термины

Термин Что обычно говорят Что это на самом деле означает
Априорная вероятность «Моё начальное предположение» P(гипотеза) до наблюдения свидетельств. В ML: член регуляризации.
Правдоподобие «Насколько хорошо подходят данные» P(свидетельство|гипотеза). Насколько вероятны наблюдаемые данные при конкретной гипотезе.
Апостериорная вероятность «Моё обновлённое убеждение» P(гипотеза|свидетельство). Априорная вероятность, умноженная на правдоподобие и затем нормализованная.
Свидетельство «Нормализующая константа» P(данные) по всем гипотезам. Гарантирует, что сумма апостериорной вероятности равна 1.
Naive Bayes «Тот простой текстовый классификатор» Классификатор, предполагающий, что признаки независимы при заданном классе. Хорошо работает, несмотря на ложность предположения.
Сглаживание Лапласа «Сглаживание с добавлением единицы» Добавление малого счётчика к каждому признаку для предотвращения нулевых вероятностей из-за невиданных данных.
MLE «Просто используйте частоты» Выбор параметров, максимизирующих P(данные|параметры). Без априорного распределения. Может переобучиться на малых данных.
MAP «MLE с априорным распределением» Выбор параметров, максимизирующих P(данные|параметры) * P(параметры). Эквивалент регуляризованного MLE.
Логарифм вероятности «Работайте в логарифмическом пространстве» Использование log(P) вместо P, чтобы избежать потери значимости с плавающей точкой при умножении множества малых чисел.
Ложноположительный результат «Неверная тревога» Тест говорит «положительный», но истинное состояние отрицательно. Вызывает ошибку базовой частоты.

Дополнительные материалы


Источник: Bayes’ Theorem & Statistical Thinking — оригинал Навигация: назад: 01.06 — Вероятность и распределения · Фаза 1 — Математические основы · Полный каталог · далее: 01.08 — Оптимизация: семейство градиентного спуска.