Skip to main content

ML-алгоритмы с нуля на NumPy: LinearRegression, LogisticRegression, MNISTNeuralNetwork

Project description

numl — ML-алгоритмы с нуля на NumPy

Учебная реализация трёх фундаментальных алгоритмов машинного обучения без использования sklearn, PyTorch или TensorFlow.
Написана с целью закрепить теорию на практике.

LinearRegression · LogisticRegression · MNISTNeuralNetwork

Структура проекта

numl/
├── numl/
│   ├── base.py        # BaseModel: цикл SGD, ранняя остановка
│   ├── linear.py      # LinearRegression (SGD / GD / normal equation)
│   ├── logistic.py    # LogisticRegression (SGD / GD)
│   ├── neural.py      # MNISTNeuralNetwork 784→128→64→10 (backprop)
│   └── __init__.py
├── tests/
│   ├── test_linear.py    # 22 теста
│   ├── test_logistic.py  # 21 тест
│   └── test_neural.py    # 34 теста
├── examples/
│   ├── california_housing.ipynb   # LinearRegression на реальных данных
│   ├── breast_cancer.ipynb        # LogisticRegression + анализ порога
│   └── mnist_neural.ipynb         # NeuralNetwork на MNIST
└── requirements.txt

Математика

Ниже — формулы, которые реализованы в коде. Каждый раздел содержит ссылку на соответствующий файл.

Линейная регрессия linear.py

Модель: $\hat{y} = \mathbf{w}^\top \mathbf{x} + b$

Функция потерь (MSE):

$$L = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2$$

Градиент и шаг обновления:

$$\frac{\partial L}{\partial \mathbf{w}} = \frac{1}{m} \mathbf{X}^\top (\hat{\mathbf{y}} - \mathbf{y}), \qquad \mathbf{w} \leftarrow \mathbf{w} - \alpha \cdot \frac{\partial L}{\partial \mathbf{w}}$$

Аналитическое решение (нормальное уравнение):

$$\boldsymbol{\theta} = (\mathbf{X}^\top \mathbf{X})^{-1} \mathbf{X}^\top \mathbf{y}$$

Реализовано через np.linalg.lstsq для численной устойчивости при вырожденных матрицах.


Логистическая регрессия logistic.py

Функция активации — сигмоид:

$$\hat{y} = \sigma(z) = \frac{1}{1 + e^{-z}}, \quad z = \mathbf{w}^\top \mathbf{x} + b$$

Функция потерь (Binary Cross-Entropy, выводится из MLE):

$$L = -\frac{1}{n} \sum_{i=1}^{n} \left[ y_i \log \hat{y}_i + (1 - y_i) \log (1 - \hat{y}_i) \right]$$

После упрощения через цепное правило градиент принимает ту же форму, что и в линейной регрессии — это следствие выпуклости BCE:

$$\frac{\partial L}{\partial \mathbf{w}} = \frac{1}{m} \mathbf{X}^\top (\hat{\mathbf{y}} - \mathbf{y})$$

Благодаря этому LogisticRegression и LinearRegression используют один и тот же цикл обучения в BaseModel, переопределяя только _activation() и _compute_loss().

Почему norm_eq запрещён: BCE невыпукла относительно $\mathbf{w}$ в замкнутой форме — аналитического решения не существует, только итеративная оптимизация.


Нейронная сеть neural.py

Архитектура:

Вход (784) → Dense(128, ReLU) → Dense(64, ReLU) → Dense(10, Softmax)

Прямой проход для слоя $l$:

$$\mathbf{z}^{[l]} = \mathbf{W}^{[l]} \mathbf{a}^{[l-1]} + \mathbf{b}^{[l]}, \qquad \mathbf{a}^{[l]} = g^{[l]}(\mathbf{z}^{[l]})$$

Функция потерь (Categorical Cross-Entropy):

$$L = -\frac{1}{m} \sum_{i=1}^{m} \sum_{k=1}^{K} y_{ik} \log \hat{y}_{ik}$$

Обратный проход (цепное правило):

$$\boldsymbol{\delta}^{[3]} = \hat{\mathbf{Y}} - \mathbf{Y} \quad \text{(softmax + CCE упрощаются)}$$

$$\boldsymbol{\delta}^{[l]} = \left(\mathbf{W}^{[l+1]\top} \boldsymbol{\delta}^{[l+1]}\right) \odot \text{ReLU}'(\mathbf{z}^{[l]})$$

$$\frac{\partial L}{\partial \mathbf{W}^{[l]}} = \frac{1}{m} \boldsymbol{\delta}^{[l]} \mathbf{a}^{[l-1]\top}, \qquad \frac{\partial L}{\partial \mathbf{b}^{[l]}} = \frac{1}{m} \sum \boldsymbol{\delta}^{[l]}$$

Инициализация весов по He (для ReLU-сетей):

$$\mathbf{W}^{[l]} \sim \mathcal{N}!\left(0,\ \frac{2}{n^{[l-1]}}\right)$$


Быстрый старт

git clone https://github.com/KarIes-ss/numl.git
cd numl
pip install -r requirements.txt
from numl import LinearRegression, LogisticRegression, MNISTNeuralNetwork

LinearRegression

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from numl import LinearRegression

X, y = fetch_california_housing(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)

# Три режима обучения
model = LinearRegression(method="norm_eq").fit(X_train, y_train)
print(f"R² = {model.score(X_test, y_test):.4f}")

model = LinearRegression(method="sgd", lr=0.1, epochs=300).fit(X_train, y_train)
model.plot_loss()

LogisticRegression

from sklearn.datasets import load_breast_cancer
from numl import LogisticRegression

X, y = load_breast_cancer(return_X_y=True)
# ... предобработка ...

model = LogisticRegression(lr=0.1, epochs=500, threshold=0.5)
model.fit(X_train, y_train)

proba  = model.predict(X_test)        # вероятности ∈ (0, 1)
labels = model.predict_class(X_test)  # метки {0, 1}
print(f"Accuracy = {model.score(X_test, y_test):.4f}")

MNISTNeuralNetwork

from numl import MNISTNeuralNetwork

# Ожидается X нормализованный в [0, 1], y ∈ {0, …, 9}
model = MNISTNeuralNetwork(hidden1=128, hidden2=64, lr=0.01, epochs=20)
model.fit(X_train, y_train)
model.plot_history()

preds = model.predict(X_test)
print(f"Accuracy = {model.score(X_test, y_test):.4f}")

Запуск тестов

# Установка зависимостей (только numpy и pytest)
pip install -r requirements.txt

# Все тесты
python -m pytest tests/ -v

# Один файл
python -m pytest tests/test_linear.py -v

# Конкретный класс или тест
python -m pytest tests/test_logistic.py::TestLogisticNormEqForbidden -v
python -m pytest tests/test_neural.py::TestActivations::test_softmax_numerical_stability -v

Покрытие: 77 тестов, ~1.6 сек.

Файл Тестов Что проверяется
test_linear.py 22 Три метода обучения, R², ранняя остановка, нормальное уравнение
test_logistic.py 21 BCE, сигмоид, порог классификации, запрет norm_eq
test_neural.py 34 Формы тензоров, softmax, backprop, сходимость, predict_proba

Тесты написаны на pytest с использованием фикстур и параметризации (@pytest.mark.parametrize). Покрывается как поведение публичного API, так и внутренняя математика (_activation, _compute_loss, _relu_grad).


Архитектурные решения

Шаблонный метод (Template Method) в BaseModel

Весь цикл обучения (SGD, ранняя остановка, история потерь) реализован единожды в BaseModel.fit(). Подклассы переопределяют только два метода:

def _activation(self, z): ...   # sigmoid / identity
def _compute_loss(self, y, y_pred): ...  # BCE / MSE

Это устраняет дублирование и позволяет добавить новую модель (например, SVMRegression) без изменения цикла обучения.

Численная стабильность

  • Сигмоид: np.clip(z, -500, 500) предотвращает exp(-z) overflow
  • Softmax: вычитание max(z) по столбцам перед exp (стандартный трюк log-sum-exp)
  • BCE и CCE: eps = 1e-9 защищает от log(0) на граничных предсказаниях

Нормальное уравнение через lstsq

Вместо явного обращения $(\mathbf{X}^\top\mathbf{X})^{-1}$ используется np.linalg.lstsq, которая применяет SVD-разложение — устойчива при мультиколлинеарности и вырожденных матрицах.


Результаты на реальных данных

Датасет Модель Метрика Значение
California Housing LinearRegression (norm_eq) ~0.606
Breast Cancer Wisconsin LogisticRegression (sgd) Accuracy ~0.982
MNIST MNISTNeuralNetwork (128→64) Accuracy ~0.970

Зависимости

numpy>=1.24
matplotlib>=3.7
pytest>=7.0        # только для тестов
scikit-learn>=1.3  # только для примеров (загрузка датасетов)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

numl-0.1.0.tar.gz (25.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

numl-0.1.0-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file numl-0.1.0.tar.gz.

File metadata

  • Download URL: numl-0.1.0.tar.gz
  • Upload date:
  • Size: 25.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for numl-0.1.0.tar.gz
Algorithm Hash digest
SHA256 82b33e122e9e1163832908b4d58ec404add2f31bf05bf58950dcd5ebfedf376e
MD5 c90261451c907e3e2d00fca1dcb8b17d
BLAKE2b-256 b602beb9ecc801d2d96e51a6dd4c9112909d3e0dab37af16965fe7cd44ee5a67

See more details on using hashes here.

File details

Details for the file numl-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: numl-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for numl-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2bb983f79d22d12072e440c4998fc7960b85b1944fa669247b22dd56299ed8bc
MD5 a977e021c262b5441e3de6cc10109cd6
BLAKE2b-256 2c91b11bfc0fbfbf8eb9a5d483b93a14fce2ebd0481bdf1be5c5b492e3280ff3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page