# Кейс 1. Оценка стоимости квартиры по параметрам

Этот кейс – классика, но не учебная абстракция. В таком виде он реально используется во внутренних сервисах агентств недвижимости, банков и девелоперов. Его просто объяснить бизнесу, он интуитивно понятен и при этом отлично показывает, как работает линейная модель на практике.

Мы попробуем решить задачу двумя способами: сначала"чистым" PHP, чтобы было видно всю математику, а затем с помощью готовой библиотеки.

#### Суть кейса

Есть квартира, описанная набором числовых параметров. По этим параметрам нужно оценить её стоимость.

Мы используем следующие признаки (можно и больше, но для этого примера ограничимся только нижеуказанными):

* площадь квартиры в квадратных метрах
* этаж
* расстояние до центра города в километрах
* возраст дома в годах

Цель – предсказать цену квартиры в рублях.

С точки зрения машинного обучения это обычная задача регрессии: по вектору признаков нужно предсказать одно числовое значение.

#### Почему это хороший кейс

Во-первых, его легко обсуждать с бизнесом. Любой человек интуитивно понимает, что цена квартиры растёт с увеличением площади и падает с удалением от центра.

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

В-третьих, этот кейс позволяет интерпретировать веса модели. Например:

"+$1000  за каждый дополнительный квадратный метр" или "−$500 за каждый километр от центра". Для бизнеса это огромный плюс по сравнению с "чёрным ящиком".

#### Формализация задачи

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

$$
\mathbf{x} = (x\_1, x\_2, x\_3, x\_4)
$$

Где:

* $$x\_1$$ – площадь
* $$x\_2$$ – этаж
* $$x\_3$$ – расстояние до центра
* $$x\_4$$ – возраст дома

Модель линейной регрессии имеет вид:

$$
\hat{y} = w\_1 x\_1 + w\_2 x\_2 + w\_3 x\_3 + w\_4 x\_4 + b
$$

Здесь $$\hat{y}$$ – предсказанная цена квартиры.

#### Реализация на чистом PHP

Начнём с варианта без библиотек. Это полезно не для продакшена, а для понимания.

Мы будем использовать градиентный спуск, матрицу признаков $$X$$ размером $$N \times 4$$ и вектор весов $$w$$ длины 4. [Bias](https://apphp.gitbook.io/ai-for-php-developers/vvedenie/glossarii#bias-smeshenie) добавим как дополнительный признак со значением 1.

**Подготовка данных**

```php
// X: [площадь, этаж, расстояние до центра, возраст дома, bias]
$X = [
    [50, 3, 5, 10, 1],
    [70, 10, 3, 5, 1],
    [40, 2, 8, 30, 1],
];

// Цена в долларах
$y = [
    66_000,
    95_000,
    45_000,
];
```

Теперь каждый объект – это вектор из пяти элементов, где последний отвечает за bias.

**Вспомогательные функции**

Эта функция вычисляет скалярное произведение двух векторов. Функция перемножает соответствующие элементы массивов `$a` и `$b` и возвращает сумму этих произведений.

Предполагается, что: массивы имеют одинаковую длину, элементы массивов являются числами и индексы в массивах совпадают.

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

**Обучение модели**

Обучение линейной модели методом градиентного спуска. Используется функция потерь MSE (Mean Squared Error). На каждой эпохе вычисляются градиенты по всем весам и веса обновляются в направлении уменьшения ошибки.

```php
$weights = array_fill(0, 5, 0.0);
$learningRate = 0.000001;
$epochs = 5000;

$n = count($X);
$m = count($weights);

for ($epoch = 0; $epoch < $epochs; $epoch++) {
    $gradients = array_fill(0, $m, 0.0);

    for ($i = 0; $i < $n; $i++) {
        $prediction = dotProduct($weights, $X[$i]);
        $error = $y[$i] - $prediction;

        for ($j = 0; $j < $m; $j++) {
            $gradients[$j] += -2 * $X[$i][$j] * $error;
        }
    }

    for ($j = 0; $j < $m; $j++) {
        $weights[$j] -= $learningRate * ($gradients[$j] / $n);
    }
}
```

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

**Предсказание цены**

```php
$newApartment = [60, 5, 4, 12, 1];
$predictedPrice = dotProduct($weights, $newApartment);

echo "Оценка стоимости: $" . number_format($predictedPrice) . "\n";

// Результат: 
// Оценка стоимости: $78,374
```

**Плюсы и минусы подхода**

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

Минусы тоже очевидны. Нет масштабирования признаков (признаки имеют разные порядки величин, из-за чего градиентный спуск обучается медленно, нестабильно или вообще не сходится). На больших датасетах такой код будет работать медленно. Поддержка и развитие такого решения в продакшене – отдельная задача.

#### Реализация с библиотекой RubixML

Теперь реализуем тот же подход, но в формате, характерном для реальных production-проектов. Воспользуемся линейной регрессией с L2-регуляризацией (Ridge Regression). В этом случае библиотека самостоятельно решает задачу оптимизации и аналитически вычисляет веса модели.

{% hint style="warning" %}
Важно: в отличие от самописной реализации, ML-библиотеки, такие как RubixML, предоставляют готовые и хорошо протестированные алгоритмы, включающие нормализацию признаков, этапы предобработки данных и численно устойчивые методы обучения. В production-окружении такие модели, как правило, оказываются более стабильными, воспроизводимыми и точными по сравнению с упрощённой реализацией линейной регрессии "с нуля".
{% endhint %}

```php
use Rubix\ML\Datasets\Labeled;
use Rubix\ML\Datasets\Unlabeled;
use Rubix\ML\Regressors\Ridge;

// Данные: [площадь, этаж, расстояние до центра, возраст дома]
$samples = [
    [50, 3, 5, 10],
    [70, 10, 3, 5],
    [40, 2, 8, 30],
];

$targets = [
    66_000,
    95_000,
    45_000,
];

// Создаём датасет
$dataset = new Labeled($samples, $targets);

// Создаём модель линейной регрессии
// При alpha = 1e-6 Ridge регрессия эквивалентна линейной регрессии
$regression = new Ridge(1e-6);

// Обучаем модель
$regression->train($dataset);

// Делаем предсказание для новой квартиры

$newApartment = [60, 5, 4, 12];

// Ridge::predict ожидает Dataset и возвращает массив предсказаний
$dataset = new Unlabeled([$newApartment]);
$predictions = $regression->predict($dataset);
$predictedPrice = $predictions[0];

echo "Оценка стоимости: $" . number_format($predictedPrice) . "\n";

// Результат: 
// Оценка стоимости: $78,016
```

**Интерпретация весов**

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

#### Выводы по кейсу

Этот кейс показывает, что машинное обучение – это не обязательно сложные модели и GPU. Часто достаточно простой линейной регрессии, чтобы получить полезный, объяснимый и применимый результат.

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

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

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