Metadata-Version: 2.4
Name: myml-algorithms
Version: 0.1.1
Summary: Custom NumPy implementations of LinearRegression, LogisticRegression, and KMeans
License-Expression: MIT
Keywords: machine learning,linear regression,logistic regression,kmeans,numpy
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Education
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.21
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: scikit-learn>=1.0; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# myml

**myml** is a lightweight Python library providing clean, from-scratch NumPy implementations of three foundational machine learning algorithms:

- `LinearRegression` — Ordinary Least Squares via the Normal Equation
- `LogisticRegression` — Binary classification via Gradient Descent
- `KMeans` — Unsupervised clustering via Lloyd's algorithm

> **Note**: This is an educational library. The implementations are intentionally faithful to the mathematical foundations and are not intended to replace production-grade libraries such as [scikit-learn](https://scikit-learn.org/). Use scikit-learn when you need performance, robustness, and full-featured APIs.

---

## Why this library exists

Most ML libraries abstract away the implementation. `myml` exposes it — you can read the actual source code of any model with a single attribute access: `LinearRegression.code`.

This makes `myml` ideal for:

- Students learning how ML algorithms work under the hood
- Educators demonstrating algorithms in live code sessions
- Anyone who wants to understand what happens inside `fit()` and `predict()`

---

## Installation

You can install the library directly from a pre-built wheel:

```bash
pip install myml-0.1.1-py3-none-any.whl
```

Then:

```python
from myml import LinearRegression
from myml import LogisticRegression
from myml import KMeans
```

## Installation in Anaconda

The same wheel can be installed on a compatible server-side Conda/Anaconda Python environment.

Example:

```bash
conda create -n myml-env python=3.11 -y
conda activate myml-env
pip install myml-0.1.1-py3-none-any.whl
```

Then:

```python
from myml import KMeans

model = KMeans(n_clusters=3)
```

---

## Usage

### LinearRegression

Fits a linear model using the **Normal Equation** (closed-form solution):

$$\theta = (X^T X)^{-1} X^T y$$

```python
import numpy as np
from myml import LinearRegression

X = np.array([[1], [2], [3], [4], [5]], dtype=float)
y = np.array([2, 4, 6, 8, 10], dtype=float)

model = LinearRegression()
model.fit(X, y)

print(model.coef_)        # [2.]
print(model.intercept_)   # ~0.0
print(model.predict([[6]]))  # [12.]
```

**API:**

| Method / Attribute | Description |
|--------------------|-------------|
| `fit(X, y)` | Fit model using the Normal Equation |
| `predict(X)` | Predict continuous output values |
| `coef_` | Fitted coefficients (weights) |
| `intercept_` | Fitted intercept (bias) |
| `code` | Source code of the class |

---

### LogisticRegression

Binary classifier using **sigmoid activation** and **gradient descent**:

$$\sigma(z) = \frac{1}{1 + e^{-z}}$$

Weights are updated per epoch:

$$w \leftarrow w - \alpha \cdot \frac{X^T(\hat{y} - y)}{n}$$

$$b \leftarrow b - \alpha \cdot \frac{\sum(\hat{y} - y)}{n}$$

```python
import numpy as np
from myml import LogisticRegression

X = np.array([[0.1], [0.4], [0.7], [1.0], [1.5], [2.0]])
y = np.array([0, 0, 0, 1, 1, 1])

model = LogisticRegression(learning_rate=0.01, epochs=10000)
model.fit(X, y)

print(model.weights)
print(model.bias)
print(model.predict(X))
print(model.predict_proba(X))
print(model.score(X, y))
```

**API:**

| Method / Attribute | Description |
|--------------------|-------------|
| `fit(X, y)` | Train with gradient descent |
| `predict(X)` | Binary predictions (0 or 1) |
| `predict_proba(X)` | Sigmoid probabilities |
| `score(X, y)` | Accuracy (fraction correct) |
| `weights` | Fitted weight vector |
| `bias` | Fitted bias scalar |
| `lr` | Learning rate (alias of `learning_rate`) |
| `epochs` | Number of training iterations |
| `code` | Source code of the class |

**Iris example:**

```python
from sklearn.datasets import load_iris
from myml import LogisticRegression

iris = load_iris()
X = iris.data
y = (iris.target == 0).astype(int)

model = LogisticRegression(learning_rate=0.01, epochs=10000)
model.fit(X, y)

print(model.weights)
print(model.bias)
print(model.score(X, y))
```

---

### KMeans

Unsupervised clustering using **Lloyd's algorithm**:

1. Initialize `k` centroids randomly from the data (seeded with `np.random.seed(42)`)
2. Assign each point to its nearest centroid (Euclidean distance)
3. Recompute centroids as cluster means
4. Repeat until convergence (`np.allclose`)

```python
import numpy as np
from myml import KMeans

X = np.array([
    [1.0, 2.0], [1.5, 1.8], [5.0, 8.0],
    [8.0, 8.0], [1.0, 0.6], [9.0, 11.0],
])

model = KMeans(n_clusters=2)
model.fit(X)

print(model.centroids)
print(model.labels_)
print(model.predict([[5.0, 5.0]]))
```

**API:**

| Method / Attribute | Description |
|--------------------|-------------|
| `fit(X)` | Fit cluster centroids |
| `predict(X)` | Assign new points to clusters |
| `centroids` | Final centroid positions |
| `labels_` | Cluster label for each training point |
| `k` | Number of clusters (alias of `n_clusters`) |
| `max_iters` | Maximum iterations |
| `code` | Source code of the class |

**Iris example:**

```python
from sklearn.datasets import load_iris
from myml import KMeans

iris = load_iris()
X = iris.data

model = KMeans(n_clusters=3)
model.fit(X)

print(model.centroids)
print(model.labels_)
```

---

## The `.code` Feature

Every model class exposes its own source code as a class-level attribute:

```python
from myml import LinearRegression, LogisticRegression, KMeans

print(LinearRegression.code)
print(LogisticRegression.code)
print(KMeans.code)
```

The source is retrieved via Python's `inspect.getsource()` — it reflects the actual implementation being executed, not a hardcoded string. This works from source installs and from installed wheels (since wheels include `.py` files).

---

## Mathematical Background

### Linear Regression (Normal Equation)

Given design matrix $X_b = [1 \mid X]$:

$$\theta = (X_b^T X_b)^+ X_b^T y$$

The pseudoinverse (`np.linalg.pinv`) is used for numerical stability.

### Logistic Regression (Gradient Descent)

For binary classification with sigmoid:

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

Gradients:

$$\nabla_w = \frac{X^T(\hat{y} - y)}{n}, \quad \nabla_b = \frac{\sum(\hat{y}-y)}{n}$$

### KMeans (Lloyd's Algorithm)

Euclidean distance assignment:

$$d(x, c_i) = \|x - c_i\|_2$$

Centroid update:

$$c_i = \frac{1}{|S_i|} \sum_{x \in S_i} x$$

---

## Testing

```bash
pip install myml[dev]
pytest tests/ -v
```

---

## Limitations

- `LinearRegression` uses the Normal Equation: $O(n^3)$ time, not suitable for very large datasets
- `LogisticRegression` does not include regularization, early stopping, or multi-class support
- `KMeans` uses a fixed `np.random.seed(42)` inside `fit()` — results are always deterministic but the seed is not configurable
- No input validation beyond NumPy array conversion
- Not optimized for production performance — use [scikit-learn](https://scikit-learn.org/) for production workloads

---

## License

MIT — see [LICENSE](LICENSE).
