# Кейс 2. Спам-фильтр на словах (Bernoulli Naive Bayes)

Этот кейс – классика машинного обучения. Именно на задаче фильтрации спама наивный Байес десятилетиями показывал удивительно хорошую практическую эффективность. И что особенно важно – здесь максимально ясно видно, почему он работает, несмотря на наивное предположение о независимости признаков.

#### Цель кейса

Показать, как:

* текст превращается в признаки
* каждое слово становится независимым доказательством
* логарифмы превращают произведение вероятностей в линейную сумму
* простейшая модель решает реальную задачу

Здесь мы используем вариант Bernoulli Naive Bayes – модель, где важен не счет слов, а сам факт их присутствия.

#### Сценарий

Мы строим примитивный спам-фильтр.

Есть два класса:

* spam
* ham (обычное письмо)

Модель учитывает только одно:

> слово либо есть в письме, либо его нет.

Частота появления слова внутри письма не имеет значения. Это и есть ключевая особенность Bernoulli-подхода.

#### Обучающая выборка

```php
$emails = [
    ['text' => ['free', 'win'], 'class' => 'spam'],
    ['text' => ['free'],        'class' => 'spam'],
    ['text' => ['meeting'],     'class' => 'ham'],
    ['text' => ['project'],     'class' => 'ham'],
];
```

Каждое письмо – это набор слов.

Никакой грамматики, порядка слов или контекста мы не учитываем.

#### Шаг 1. Подсчет априорных вероятностей

```php
$classes = [];
$totalDocs = count($emails);

foreach ($emails as $email) {
    $class = $email['class'];
    $classes[$class] = ($classes[$class] ?? 0) + 1;
}
```

Мы получаем $$P(spam)$$ и $$P(ham)$$.

Если спама больше – модель будет к нему априори склоняться.

#### Шаг 2. Подсчет условных вероятностей слов

```php
$wordCounts = [];

foreach ($emails as $email) {
    $class = $email['class'];

    foreach ($email['text'] as $word) {
        $wordCounts[$class][$word] = ($wordCounts[$class][$word] ?? 0) + 1;
    }
}
```

Здесь мы оцениваем: $$P(word \mid class)$$

Например:

* $$P("free" \mid spam)$$
* $$P("meeting" \mid ham)$$

Каждое слово рассматривается отдельно – это и есть предположение условной независимости.

#### Шаг 3. Классификация нового письма

Допустим, пришло письмо:

```php
$input = ['free', 'meeting'];
```

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

Теперь считаем логарифмы вероятностей:

```php
$scores = [];

foreach ($classes as $class => $count) {
    $logProb = log($count / $totalDocs);

    foreach ($input as $word) {
        $wordCount = $wordCounts[$class][$word] ?? 1;
        $logProb += log($wordCount / $count);
    }

    $scores[$class] = $logProb;
}

arsort($scores);
print_r($scores);
```

Модель:

1. Берет априорную вероятность класса.
2. Добавляет вклад каждого слова.
3. Выбирает класс с максимальным значением.

#### Что здесь важно понять

**1. Каждое слово – независимое доказательство**

Слово "free" голосует за spam.

Слово "meeting" голосует за ham.

Итог – сумма этих голосов в лог-пространстве. Это буквально раздел "Голосование признаков" из теоретической главы.

**2. Произведение превращается в линейную модель**

Формально:&#x20;

$$
P(C \mid \text{words}) \propto P(C) \cdot \prod\_{i} P(\text{word}\_i \mid C)
$$

После логарифмирования:

$$
\log P(C \mid \text{words})  = \log P(C) + \sum\_{i} \log P(\text{word}\_i \mid C)
$$

Это уже линейная сумма. Именно поэтому наивный Байес часто ведет себя как линейный классификатор.

**3. Почему модель работает несмотря на наивность**

В реальности слова зависимы:

* "free" и "win" часто встречаются вместе
* "meeting" и "project" коррелируют

Но для классификации нам важна не идеальная оценка вероятностей, а правильное сравнение классов. Ошибки из-за зависимостей часто распределяются симметрично и не меняют итоговый выбор.

#### Ограничения текущей реализации

В этом примере:

* нет сглаживания (Laplace smoothing)
* не учитывается отсутствие слов
* не используется словарь фиксированной размерности
* данные крайне малы

Это демонстрационная версия, цель которой – показать механику.

<details>

<summary>Кейс 2. Полный пример кода на чистом PHP</summary>

```php
$emails = [
    ['text' => ['free', 'win'], 'class' => 'spam'],
    ['text' => ['free'],        'class' => 'spam'],
    ['text' => ['meeting'],     'class' => 'ham'],
    ['text' => ['project'],     'class' => 'ham'],
];

$classes = [];
$totalDocs = count($emails);

foreach ($emails as $email) {
    $class = $email['class'];
    $classes[$class] = ($classes[$class] ?? 0) + 1;
}

$wordCounts = [];

foreach ($emails as $email) {
    $class = $email['class'];

    foreach ($email['text'] as $word) {
        $wordCounts[$class][$word] = ($wordCounts[$class][$word] ?? 0) + 1;
    }
}

$input = ['free', 'meeting'];

$scores = [];

foreach ($classes as $class => $count) {
    $logProb = log($count / $totalDocs);

    foreach ($input as $word) {
        $wordCount = $wordCounts[$class][$word] ?? 1;
        $logProb += log($wordCount / $count);
    }

    $scores[$class] = $logProb;
}

arsort($scores);
print_r($scores);
```

</details>

### Та же задача с использованием RubixML

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

```php
use Rubix\ML\Classifiers\NaiveBayes;
use Rubix\ML\Datasets\Labeled;

// free, win, meeting
$samples = [
    [1, 1, 0], 
    [1, 0, 0],
    [0, 0, 1],
    [0, 0, 0],
];

$labels = ['spam', 'spam', 'ham', 'ham'];

$dataset = new Labeled($samples, $labels);

$model = new NaiveBayes();
$model->train($dataset);

$prediction = $model->predict([[1, 0, 1]]);
print_r($prediction);
```

Здесь:

* каждое слово стало бинарным признаком
* 1 означает присутствие
* 0 – отсутствие
* RubixML автоматически считает частоты и применяет формулу Байеса

С точки зрения логики – ничего не изменилось. Изменился только уровень абстракции.

#### Выводы

Этот кейс показывает три фундаментальные вещи.

Во-первых, наивный Байес идеально подходит для текстовых задач, где признаки естественно бинарны.

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

В-третьих, простота модели – не недостаток, а преимущество. Именно благодаря этой простоте наивный Байес десятилетиями оставался одним из базовых инструментов в задачах фильтрации спама.

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