# Кейс 1. Логистическая регрессия для оттока клиентов

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

Именно поэтому логистическая регрессия здесь подходит особенно хорошо.

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

Построить простую модель, которая по поведению пользователя оценивает вероятность его ухода, и понять, как эта вероятность формируется из признаков.

Важно сразу отметить: цель кейса не в точности предсказаний. Данные игрушечные, модель упрощена. Задача – увидеть механику логистической регрессии в действии и связать формулу с практическим смыслом.

#### Сценарий и признаки

Каждый пользователь описывается тремя числовыми признаками:

* количество входов в систему
* средняя длительность сессии
* количество дней с момента регистрации

Формально один объект выглядит так:

```
x = [logins, avgSession, daysFromSignup]
```

Целевая переменная y принимает значение 1, если пользователь ушел, и 0, если он остался.

Мы хотим по вектору $$x$$ получить вероятность ухода.

#### Логистическая регрессия в чистом PHP

В самом простом виде логистическая регрессия – это линейная комбинация признаков, к которой применяется сигмоида. Никакой магии.

Сначала определим вспомогательные функции.

Сигмоида превращает любое число в вероятность от 0 до 1:

```php
function sigmoid(float $z): float {
    return 1.0 / (1.0 + exp(-$z));
}
```

Скалярное произведение – это линейная часть модели:

```php
function dot(array $a, array $b): float {
    $sum = 0.0;
    foreach ($a as $i => $v) {
        $sum += $v * $b[$i];
    }
    return $sum;
}
```

Подготовим небольшой датасет:

```php
$X = [
    [1.0, 5.2, 30.0],
    [12.0, 15.1, 400.0],
    [3.0, 4.8, 60.0],
    [20.0, 25.0, 800.0],
];

$y = [1, 0, 1, 0];
```

Инициализируем веса и смещение:

```php
$weights = [0.0, 0.0, 0.0];
$bias = 0.0;

$learningRate = 0.01;
$epochs = 1000;
```

Обучение происходит с помощью градиентного спуска по log loss. Внутри цикла нет ничего неожиданного: считаем предсказание, ошибку и двигаем веса в сторону уменьшения ошибки.

```php
for ($epoch = 0; $epoch < $epochs; $epoch++) {
    foreach ($X as $i => $x) {
        $z = dot($weights, $x) + $bias;
        $p = sigmoid($z);

        // градиент log loss
        $error = $p - $y[$i];

        foreach ($weights as $j => $w) {
            $weights[$j] -= $learningRate * $error * $x[$j];
        }

        $bias -= $learningRate * $error;
    }
}
```

Теперь можно сделать прогноз для нового пользователя:

```php
$newUser = [5.0, 7.0, 120.0];
$probability = sigmoid(dot($weights, $newUser) + $bias);

echo "Вероятность ухода: " . round($probability, 3) . "\n";

// Результат
// Вероятность ухода: 0.994
```

Этот пример намеренно прост. Он показывает ключевую идею: логистическая регрессия – это линейная модель плюс сигмоида и правильная функция потерь. Всё остальное – детали реализации.

<details>

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

```php
function sigmoid(float $z): float {
    return 1.0 / (1.0 + exp(-$z));
}

function dot(array $a, array $b): float {
    $sum = 0.0;

    foreach ($a as $i => $v) {
        $sum += $v * $b[$i];
    }

    return $sum;
}

$X = [
    [1.0, 5.2, 30.0],
    [12.0, 15.1, 400.0],
    [3.0, 4.8, 60.0],
    [20.0, 25.0, 800.0],
];

$y = [1, 0, 1, 0];

$weights = [0.0, 0.0, 0.0];
$bias = 0.0;

$learningRate = 0.01;
$epochs = 1000;

for ($epoch = 0; $epoch < $epochs; $epoch++) {
    foreach ($X as $i => $x) {
        $z = dot($weights, $x) + $bias;
        $p = sigmoid($z);

        $error = $p - $y[$i];

        foreach ($weights as $j => $w) {
            $weights[$j] -= $learningRate * $error * $x[$j];
        }

        $bias -= $learningRate * $error;
    }
}

$newUser = [5.0, 7.0, 120.0];
$probability = sigmoid(dot($weights, $newUser) + $bias);

echo "Вероятность ухода: " . round($probability, 3) . PHP_EOL;
```

</details>

#### Тот же кейс с RubixML

В реальных проектах редко пишут логистическую регрессию вручную. Гораздо удобнее использовать готовые библиотеки.

Тот же самый кейс с RubixML выглядит заметно короче:

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

$samples = [
    [1, 5.2, 30],
    [12, 15.1, 400],
    [3, 4.8, 60],
    [20, 25.0, 800],
];

$labels = ['churn', 'stay', 'churn', 'stay'];

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

$classifier = new LogisticRegression();
$classifier->train($dataset);

$newUser = [5, 7, 120];
$dataset = new Unlabeled([$newUser]);
$probabilities = $classifier->proba($dataset);

$probaRow = $probabilities[0];
$churnProbability = $probaRow['churn'];

echo 'Вероятность ухода: ' . number_format($churnProbability, 3) . PHP_EOL;

// Результат
// on 0 до 1, например 0.136 (может сильно колебаться, так как мало данных для обучения)
```

RubixML скрывает детали оптимизации, но концептуально это та же самая модель: линейная комбинация признаков, сигмоида и decision boundary.

#### Визуализация decision boundary для кейса оттока

Чтобы сделать поведение модели наглядным, полезно временно упростить задачу и оставить только два признака. Например:

* количество входов за последний месяц,
* количество дней с момента регистрации.

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

В этом случае decision boundary логистической регрессии задается уравнением:

$$
w₁x₁ + w₂x₂ + b = 0
$$

Геометрически это прямая, которая делит пространство на две области. По одну сторону находятся пользователи с вероятностью ухода больше 0.5, по другую – с вероятностью меньше 0.5.

Важно подчеркнуть: хотя граница линейна, вероятность меняется плавно. Чем дальше точка от границы, тем увереннее модель в своем прогнозе.

<div align="left"><figure><img src="https://4057452061-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjIcWF5FKKxpY4JporyGd%2Fuploads%2F4AqRlMEnkAn3CRwhzCZj%2F14.4-churn-decision-boundary.png?alt=media&#x26;token=bf364c8f-1f7c-434c-a76b-50158ba98f69" alt="" width="563"><figcaption><p>14.4 Граница принятия решения об оттоке</p></figcaption></figure></div>

Такая визуализация хорошо иллюстрирует ключевую идею логистической регрессии: модель линейно разделяет пространство признаков, но результат интерпретируется через вероятность, а не через жесткое правило.

В более высоких размерностях decision boundary перестает быть линией, но логика остается той же – это гиперплоскость в пространстве признаков.

#### Выводы

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

Мы можем смотреть на веса признаков, менять порог принятия решения, объяснять поведение модели бизнесу и принимать решения не "в лоб", а с учетом риска. Именно за это логистическую регрессию до сих пор любят – несмотря на все более сложные модели.

{% hint style="info" %}
Чтобы самостоятельно протестировать этот код, воспользуйтесь [онлайн-демонстрацией](https://aiwithphp.org/books/ai-for-php-developers/examples/part-3/logistic-regression) для его запуска.
{% endhint %}
