genetic-classifier 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genetic_classifier-3.0.0/.github/workflows/publish.yml +37 -0
- genetic_classifier-3.0.0/.gitignore +12 -0
- genetic_classifier-3.0.0/LICENSE +21 -0
- genetic_classifier-3.0.0/PKG-INFO +298 -0
- genetic_classifier-3.0.0/README.md +277 -0
- genetic_classifier-3.0.0/docs/.gitignore +7 -0
- genetic_classifier-3.0.0/docs/README.md +34 -0
- genetic_classifier-3.0.0/docs/genetic_algorithm.md +197 -0
- genetic_classifier-3.0.0/docs/genetic_algorithm.tex +327 -0
- genetic_classifier-3.0.0/examples/.gitkeep +0 -0
- genetic_classifier-3.0.0/examples/diabetes_classification/diabetes_classification.ipynb +235 -0
- genetic_classifier-3.0.0/examples/diabetes_classification/diabetes_data.xlsx +0 -0
- genetic_classifier-3.0.0/examples/example_1_binary/binary_data_1.xlsx +0 -0
- genetic_classifier-3.0.0/examples/example_1_binary/binary_data_2.xlsx +0 -0
- genetic_classifier-3.0.0/examples/example_1_binary/binary_example.ipynb +558 -0
- genetic_classifier-3.0.0/examples/example_2_multiclass/multiclass_data.xlsx +0 -0
- genetic_classifier-3.0.0/examples/example_2_multiclass/multiclass_example.ipynb +607 -0
- genetic_classifier-3.0.0/examples/heart_disease_classification/heart.csv +919 -0
- genetic_classifier-3.0.0/examples/heart_disease_classification/heart_data.xlsx +0 -0
- genetic_classifier-3.0.0/examples/heart_disease_classification/heart_disease_classification.ipynb +1568 -0
- genetic_classifier-3.0.0/examples/mushroom_classification/mushroom_classification.ipynb +228 -0
- genetic_classifier-3.0.0/examples/mushroom_classification/mushroom_data.xlsx +0 -0
- genetic_classifier-3.0.0/examples/mushroom_classification/mushrooms.csv +8125 -0
- genetic_classifier-3.0.0/examples/template/template.xlsx +0 -0
- genetic_classifier-3.0.0/pyproject.toml +39 -0
- genetic_classifier-3.0.0/requirements.txt +3 -0
- genetic_classifier-3.0.0/src/genetic_classifier/__init__.py +10 -0
- genetic_classifier-3.0.0/src/genetic_classifier/core.py +339 -0
- genetic_classifier-3.0.0/src/genetic_classifier/creation.py +47 -0
- genetic_classifier-3.0.0/src/genetic_classifier/crossover.py +53 -0
- genetic_classifier-3.0.0/src/genetic_classifier/evaluation.py +233 -0
- genetic_classifier-3.0.0/src/genetic_classifier/fitness.py +194 -0
- genetic_classifier-3.0.0/src/genetic_classifier/mutation.py +53 -0
- genetic_classifier-3.0.0/src/genetic_classifier/reading.py +100 -0
- genetic_classifier-3.0.0/src/genetic_classifier/selection.py +91 -0
- genetic_classifier-3.0.0/src/genetic_classifier/split.py +110 -0
- genetic_classifier-3.0.0/src/genetic_classifier/update.py +84 -0
- genetic_classifier-3.0.0/tests/.gitkeep +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Publishes to PyPI when a version tag (e.g. v3.0.0) is pushed to GitHub,
|
|
2
|
+
# or manually from the Actions tab -> "Publish to PyPI" -> Run workflow.
|
|
3
|
+
# Uses Trusted Publishing: no token needed, PyPI trusts this repository.
|
|
4
|
+
name: Publish to PyPI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- run: python -m pip install build
|
|
20
|
+
- run: python -m build
|
|
21
|
+
- uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish:
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment: pypi
|
|
30
|
+
permissions:
|
|
31
|
+
id-token: write # required for Trusted Publishing
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/download-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fellipe Almassy and Matheus Gomes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: genetic-classifier
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Linear classifier (binary and ordinal) trained by a genetic algorithm, built with NumPy
|
|
5
|
+
Project-URL: Repository, https://github.com/FeAlmassy/Genetic-Classifier
|
|
6
|
+
Project-URL: Issues, https://github.com/FeAlmassy/Genetic-Classifier/issues
|
|
7
|
+
Author: Fellipe Almassy, Matheus Gomes
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: classification,evolutionary algorithm,genetic algorithm,machine learning,ordinal classification
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: numpy>=2.1
|
|
18
|
+
Requires-Dist: openpyxl>=3.1
|
|
19
|
+
Requires-Dist: pandas>=2.2
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Genetic Classifier
|
|
23
|
+
|
|
24
|
+
A Python library that trains a **linear classifier with a genetic algorithm**, written from scratch with NumPy.
|
|
25
|
+
|
|
26
|
+
Instead of using gradients (like logistic regression), the model **evolves** a population of solutions: the best ones reproduce, mutate and, generation after generation, the classification improves.
|
|
27
|
+
|
|
28
|
+
It works for:
|
|
29
|
+
- **Binary classification** (2 classes: 0 and 1)
|
|
30
|
+
- **Ordinal classification** (K ordered classes, e.g. bad < average < good)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
Each **chromosome** is a list of numbers: one **bias** and one **weight** per feature. With them the model computes a score for each sample:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
score = bias + weight₁·x₁ + weight₂·x₂ + ... + weightₙ·xₙ
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Binary:** score ≥ 0 → class 1, score < 0 → class 0
|
|
43
|
+
- **Ordinal:** the score is split into ranges by the **thresholds** (cut points), and each range is a class
|
|
44
|
+
|
|
45
|
+
### The fitness
|
|
46
|
+
|
|
47
|
+
The score of each chromosome (its fitness) is the **product of the recall of each class**:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
fitness = recall(class 0) × recall(class 1) × ... × recall(class K-1)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If the model gets every sample of a class wrong, the fitness becomes 0. This forces the model to get **every** class right, not only the most common one, which is useful when the classes are imbalanced.
|
|
54
|
+
|
|
55
|
+
### The algorithm cycle
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
1. create random population
|
|
59
|
+
│
|
|
60
|
+
▼
|
|
61
|
+
2. compute fitness ◄───────────────┐
|
|
62
|
+
│ │
|
|
63
|
+
▼ │
|
|
64
|
+
3. select two parents (roulette) │
|
|
65
|
+
│ │
|
|
66
|
+
▼ │
|
|
67
|
+
4. crossover (one cut point) │
|
|
68
|
+
│ │
|
|
69
|
+
▼ │
|
|
70
|
+
5. mutation │
|
|
71
|
+
│ │
|
|
72
|
+
▼ │
|
|
73
|
+
6. replace the worst with ─────────┘
|
|
74
|
+
the best offspring
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The cycle stops when it reaches the target fitness or runs out of generations.
|
|
78
|
+
|
|
79
|
+
| Step | What it does |
|
|
80
|
+
|---|---|
|
|
81
|
+
| **Creation** | Creates chromosomes with random genes between -1 and 1 |
|
|
82
|
+
| **Fitness** | Gives each chromosome a score from 0 to 1 |
|
|
83
|
+
| **Selection** | Draws two parents by roulette: the higher the fitness, the higher the chance |
|
|
84
|
+
| **Crossover** | The child takes the beginning of one parent and the end of the other, from a random cut point |
|
|
85
|
+
| **Mutation** | Replaces some genes of each child with new random values |
|
|
86
|
+
| **Update** | The worst of the population leave and the best offspring come in (elitism) |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
Requires **Python 3.10+**.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install genetic-classifier
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or straight from GitHub:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install git+https://github.com/FeAlmassy/Genetic-Classifier.git
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### For development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/FeAlmassy/Genetic-Classifier.git
|
|
108
|
+
cd Genetic-Classifier
|
|
109
|
+
python -m venv venv_ag
|
|
110
|
+
source venv_ag/bin/activate # on Windows: venv_ag\Scripts\activate
|
|
111
|
+
pip install -e . # editable mode: code changes apply right away
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Quick start
|
|
117
|
+
|
|
118
|
+
### Binary
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from genetic_classifier import train, predict, evaluate, read_excel, train_test_split
|
|
122
|
+
|
|
123
|
+
X, y = read_excel("examples/example_1_binary/binary_data_1.xlsx")
|
|
124
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, seed=0)
|
|
125
|
+
|
|
126
|
+
model = train(X_train, y_train, seed=0) # learns from the training set only
|
|
127
|
+
|
|
128
|
+
y_pred = predict(model, X_test) # classifies data the model has never seen
|
|
129
|
+
results = evaluate(y_test, y_pred) # prints accuracy, fitness, recall and confusion matrix
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> The default values of `train` already give good results in most cases. Only tune the parameters if you need to.
|
|
133
|
+
|
|
134
|
+
The functions of each step (`create_chromosomes`, `binary_fitness`, `select_parents_roulette`, `one_point_crossover`, `mutate`, `update_population`) can also be used on their own, imported from their modules (e.g. `from genetic_classifier.mutation import mutate`).
|
|
135
|
+
|
|
136
|
+
### Ordinal (several classes)
|
|
137
|
+
|
|
138
|
+
The classes must be numbers from `0` to `K-1`, ordered from lowest to highest. For K classes, pass K-1 thresholds in increasing order:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import numpy as np
|
|
142
|
+
|
|
143
|
+
thresholds = np.array([-1.5, -0.5, 0.5, 1.5]) # 4 cuts -> 5 classes
|
|
144
|
+
|
|
145
|
+
model = train(X, y, thresholds=thresholds, n_generations=2000)
|
|
146
|
+
y_pred = predict(model, X_new)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `train` parameters
|
|
150
|
+
|
|
151
|
+
| Parameter | Default | Description |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `X` | — | array (n_samples, n_features) with the data |
|
|
154
|
+
| `y` | — | vector with the class of each sample |
|
|
155
|
+
| `n_chromosomes` | `30` | population size |
|
|
156
|
+
| `n_mutated_genes` | `2` | how many genes change in each child |
|
|
157
|
+
| `n_offspring` | `16` | how many children per generation |
|
|
158
|
+
| `n_replacements` | `None` | how many of the worst in the population are swapped for the best children (at most the smaller of `n_chromosomes` and `n_offspring`). `None` = half of `n_offspring` |
|
|
159
|
+
| `n_generations` | `1000` | maximum number of generations |
|
|
160
|
+
| `target_fitness` | `1.0` | stops when the best fitness reaches this value (`1.0` = runs every generation unless it gets everything right) |
|
|
161
|
+
| `thresholds` | `None` | `None` = binary; array with K-1 cuts = ordinal |
|
|
162
|
+
| `gene_min` | `-1.0` | smallest gene value in the initial population |
|
|
163
|
+
| `gene_max` | `1.0` | largest gene value in the initial population |
|
|
164
|
+
| `seed` | `None` | random seed for reproducibility (`None` = different every run) |
|
|
165
|
+
| `verbose` | `True` | prints the best fitness and accuracy of every generation |
|
|
166
|
+
|
|
167
|
+
`train` returns a dictionary with `chromosome` (the best one found), `fitness`, `accuracy` (fraction right on the training data), `generation` (where it stopped), `thresholds` and `history` (lists `best`, `mean` and `accuracy` with the evolution per generation).
|
|
168
|
+
|
|
169
|
+
### Tuning the parameters
|
|
170
|
+
|
|
171
|
+
The defaults work well in most cases. If you need to tune them, keep in mind that the parameters affect each other:
|
|
172
|
+
|
|
173
|
+
- **Renewal rate = `n_replacements` ÷ `n_chromosomes`:** fraction of the population replaced every generation (default: 8 ÷ 30 ≈ 27%). Too low makes evolution slow; close to 100% the whole population becomes children of the same couple and loses diversity. That is why **increasing only the population usually makes things worse**: increase the replacements too, or leave it as `None`.
|
|
174
|
+
- **`n_offspring` × `n_replacements`:** only the best children enter the population. With more children than replacements, the worst children are discarded.
|
|
175
|
+
- **`n_mutated_genes` × number of features:** what matters is the fraction of the chromosome that changes. With few features, use `1`; with many, `2` or `3`.
|
|
176
|
+
- **Cost ≈ `n_generations` × (`n_chromosomes` + `n_offspring`) × n_samples.**
|
|
177
|
+
- **Explore × refine:** a large population and more mutation search for new solutions; high renewal and little mutation improve the ones already found.
|
|
178
|
+
|
|
179
|
+
To find out whether more generations would help, look at the end of `model["history"]["best"]`: if the fitness is still going up, the model has not finished.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Data format
|
|
184
|
+
|
|
185
|
+
The spreadsheet (`.xlsx` or `.csv`) must follow this layout:
|
|
186
|
+
|
|
187
|
+
| Lessons | X1 | X2 | ... | Xn | CLASS |
|
|
188
|
+
|---|---|---|---|---|---|
|
|
189
|
+
| Y001 | 1 | 0 | ... | 1 | 1 |
|
|
190
|
+
| Y002 | 0 | 1 | ... | 0 | 0 |
|
|
191
|
+
|
|
192
|
+
- **First column:** sample identifier (ignored)
|
|
193
|
+
- **Middle columns:** features
|
|
194
|
+
- **Last column:** class
|
|
195
|
+
|
|
196
|
+
To classify new data, which has no class column, use `has_labels=False`. In that case the function returns only `X`:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
X_new = read_excel("new_data.xlsx", has_labels=False)
|
|
200
|
+
y_pred = predict(model, X_new)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A ready-made template is in [`examples/template/template.xlsx`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/template/template.xlsx).
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Examples and results
|
|
208
|
+
|
|
209
|
+
| Notebook | Dataset | Classes | Test accuracy* |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| [`binary_example.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/example_1_binary/binary_example.ipynb) | `binary_data_1.xlsx` (539 samples, 18 features) | 2 | **87%** |
|
|
212
|
+
| [`multiclass_example.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/example_2_multiclass/multiclass_example.ipynb) | `multiclass_data.xlsx` (261 samples, 30 features) | 5 | **79%** |
|
|
213
|
+
| [`mushroom_classification.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/mushroom_classification/mushroom_classification.ipynb) | `mushroom_data.xlsx` (8124 samples, 117 features) | 2 | **100%** |
|
|
214
|
+
| [`heart_disease_classification.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/heart_disease_classification/heart_disease_classification.ipynb) | `heart_data.xlsx` (917 samples, 21 features) | 2 | **85%** |
|
|
215
|
+
| [`diabetes_classification.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/diabetes_classification/diabetes_classification.ipynb) | `diabetes_data.xlsx` (40000 samples, 21 features) | 3 | **58%**† |
|
|
216
|
+
|
|
217
|
+
\* Trained on 80% of the data and tested on the remaining 20%, which the model never saw, always with seed 0.
|
|
218
|
+
|
|
219
|
+
† Prediabetes is very rare in this dataset, so the accuracy alone is misleading: the notebook shows the recall of each class.
|
|
220
|
+
|
|
221
|
+
The binary notebook shows **one generation step by step**, calling each function in the order of the algorithm. The multiclass one shows how to choose the order of the classes when it is not known.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Project structure
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
Genetic-Classifier/
|
|
229
|
+
├── src/
|
|
230
|
+
│ └── genetic_classifier/
|
|
231
|
+
│ ├── __init__.py # public API: train, predict, evaluate, confusion_matrix, read_excel, read_csv, train_test_split
|
|
232
|
+
│ ├── core.py # main loop (train) and prediction (predict)
|
|
233
|
+
│ ├── evaluation.py # model evaluation (evaluate) and confusion matrix
|
|
234
|
+
│ ├── creation.py # 1. initial population
|
|
235
|
+
│ ├── fitness.py # 2. binary and ordinal fitness, roulette proportions
|
|
236
|
+
│ ├── selection.py # 3. roulette selection
|
|
237
|
+
│ ├── crossover.py # 4. one-point crossover
|
|
238
|
+
│ ├── mutation.py # 5. mutation
|
|
239
|
+
│ ├── update.py # 6. replacing the worst with the best offspring
|
|
240
|
+
│ ├── reading.py # reading .xlsx and .csv files
|
|
241
|
+
│ └── split.py # train/test split (stratified)
|
|
242
|
+
├── docs/ # how the algorithm works, with math and examples (Markdown and LaTeX)
|
|
243
|
+
├── examples/ # example notebooks and datasets
|
|
244
|
+
├── tests/ # automated tests (coming soon)
|
|
245
|
+
├── pyproject.toml # package configuration (name, version, dependencies)
|
|
246
|
+
├── LICENSE # MIT license
|
|
247
|
+
└── requirements.txt
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Each step of the algorithm lives in its own module, so you can swap one piece (for example, try another selection method) without touching the others. Every function checks its parameters and gives clear error messages.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Changelog
|
|
255
|
+
|
|
256
|
+
### 3.0.0
|
|
257
|
+
- **The whole library is now in English** and is published on PyPI as **`genetic-classifier`** (import `genetic_classifier`). The Portuguese version stays available as `algoritmo-genetico` 2.0.0.
|
|
258
|
+
- Every docstring follows the same format: what the function does, each parameter with its type, and what it returns with its type and a suggested variable name.
|
|
259
|
+
- The deprecated alias `rodar` was removed.
|
|
260
|
+
|
|
261
|
+
Migrating from `algoritmo-genetico`:
|
|
262
|
+
|
|
263
|
+
| 2.x (Portuguese) | 3.0 (English) |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `treinar`, `prever`, `avaliar`, `matriz_confusao` | `train`, `predict`, `evaluate`, `confusion_matrix` |
|
|
266
|
+
| `ler_excel`, `ler_csv` (`tem_classe`) | `read_excel`, `read_csv` (`has_labels`) |
|
|
267
|
+
| `dividir_treino_teste` (`proporcao_treino`, `estratificar`) | `train_test_split` (`train_size`, `stratify`) |
|
|
268
|
+
| `qtd_cromossomos`, `qtd_genes_mutados`, `qtd_filhos`, `qtd_substituicoes` | `n_chromosomes`, `n_mutated_genes`, `n_offspring`, `n_replacements` |
|
|
269
|
+
| `geracoes`, `fitness_alvo`, `minimo`, `maximo` | `n_generations`, `target_fitness`, `gene_min`, `gene_max` |
|
|
270
|
+
| `y_real`, `y_previsto`, `nomes_classes` | `y_true`, `y_pred`, `class_names` |
|
|
271
|
+
| keys `cromossomo`, `acuracia`, `geracao`, `historico` (`melhor`, `medio`) | keys `chromosome`, `accuracy`, `generation`, `history` (`best`, `mean`) |
|
|
272
|
+
| key `matriz` of `avaliar` | key `confusion_matrix` of `evaluate` |
|
|
273
|
+
| `criar_cromossomos`, `fitness_binario`, `fitness_ordinal`, `fitness_percentual` | `create_chromosomes`, `binary_fitness`, `ordinal_fitness`, `fitness_proportions` |
|
|
274
|
+
| `selecionar_pais_roleta`, `cruzar_um_ponto`, `mutar`, `atualizar_populacao` | `select_parents_roulette`, `one_point_crossover`, `mutate`, `update_population` |
|
|
275
|
+
|
|
276
|
+
### 2.0.0
|
|
277
|
+
- New **`evaluate`** function (`avaliar`), which prints and returns accuracy, fitness, recall per class and the confusion matrix. The matrix can also be obtained on its own with **`confusion_matrix`**.
|
|
278
|
+
- `read_excel` and `read_csv` got the **`has_labels`** parameter: with `has_labels=False` they read new data without the class column.
|
|
279
|
+
- New checks: empty cells, text in the features and text classes in ordinal mode now raise clear error messages instead of silently giving wrong results.
|
|
280
|
+
- Training no longer creates offspring in the last generation, since they would never be compared with the best chromosome.
|
|
281
|
+
|
|
282
|
+
### 1.1.0
|
|
283
|
+
- `rodar` was renamed to **`treinar`** (now `train`), pairing with `prever` (now `predict`).
|
|
284
|
+
- **New default values**, which give much better results without tuning: 30 chromosomes, 2 mutated genes, 16 offspring, replacements = half of the offspring, 1000 generations and target fitness 1.0.
|
|
285
|
+
- Training converts `X` to float internally, which makes it up to ~6x faster when the data is integer (as read by `read_csv`).
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
Distributed under the [MIT license](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/LICENSE): you can use, modify and redistribute it freely, as long as you keep the copyright notice.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Authors
|
|
296
|
+
|
|
297
|
+
- **Fellipe Almassy** — [GitHub](https://github.com/FeAlmassy)
|
|
298
|
+
- **Matheus Gomes** — [GitHub](https://github.com/matheos-hash)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Genetic Classifier
|
|
2
|
+
|
|
3
|
+
A Python library that trains a **linear classifier with a genetic algorithm**, written from scratch with NumPy.
|
|
4
|
+
|
|
5
|
+
Instead of using gradients (like logistic regression), the model **evolves** a population of solutions: the best ones reproduce, mutate and, generation after generation, the classification improves.
|
|
6
|
+
|
|
7
|
+
It works for:
|
|
8
|
+
- **Binary classification** (2 classes: 0 and 1)
|
|
9
|
+
- **Ordinal classification** (K ordered classes, e.g. bad < average < good)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
Each **chromosome** is a list of numbers: one **bias** and one **weight** per feature. With them the model computes a score for each sample:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
score = bias + weight₁·x₁ + weight₂·x₂ + ... + weightₙ·xₙ
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **Binary:** score ≥ 0 → class 1, score < 0 → class 0
|
|
22
|
+
- **Ordinal:** the score is split into ranges by the **thresholds** (cut points), and each range is a class
|
|
23
|
+
|
|
24
|
+
### The fitness
|
|
25
|
+
|
|
26
|
+
The score of each chromosome (its fitness) is the **product of the recall of each class**:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
fitness = recall(class 0) × recall(class 1) × ... × recall(class K-1)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If the model gets every sample of a class wrong, the fitness becomes 0. This forces the model to get **every** class right, not only the most common one, which is useful when the classes are imbalanced.
|
|
33
|
+
|
|
34
|
+
### The algorithm cycle
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
1. create random population
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
2. compute fitness ◄───────────────┐
|
|
41
|
+
│ │
|
|
42
|
+
▼ │
|
|
43
|
+
3. select two parents (roulette) │
|
|
44
|
+
│ │
|
|
45
|
+
▼ │
|
|
46
|
+
4. crossover (one cut point) │
|
|
47
|
+
│ │
|
|
48
|
+
▼ │
|
|
49
|
+
5. mutation │
|
|
50
|
+
│ │
|
|
51
|
+
▼ │
|
|
52
|
+
6. replace the worst with ─────────┘
|
|
53
|
+
the best offspring
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The cycle stops when it reaches the target fitness or runs out of generations.
|
|
57
|
+
|
|
58
|
+
| Step | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| **Creation** | Creates chromosomes with random genes between -1 and 1 |
|
|
61
|
+
| **Fitness** | Gives each chromosome a score from 0 to 1 |
|
|
62
|
+
| **Selection** | Draws two parents by roulette: the higher the fitness, the higher the chance |
|
|
63
|
+
| **Crossover** | The child takes the beginning of one parent and the end of the other, from a random cut point |
|
|
64
|
+
| **Mutation** | Replaces some genes of each child with new random values |
|
|
65
|
+
| **Update** | The worst of the population leave and the best offspring come in (elitism) |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
Requires **Python 3.10+**.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install genetic-classifier
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or straight from GitHub:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install git+https://github.com/FeAlmassy/Genetic-Classifier.git
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### For development
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/FeAlmassy/Genetic-Classifier.git
|
|
87
|
+
cd Genetic-Classifier
|
|
88
|
+
python -m venv venv_ag
|
|
89
|
+
source venv_ag/bin/activate # on Windows: venv_ag\Scripts\activate
|
|
90
|
+
pip install -e . # editable mode: code changes apply right away
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
### Binary
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from genetic_classifier import train, predict, evaluate, read_excel, train_test_split
|
|
101
|
+
|
|
102
|
+
X, y = read_excel("examples/example_1_binary/binary_data_1.xlsx")
|
|
103
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, seed=0)
|
|
104
|
+
|
|
105
|
+
model = train(X_train, y_train, seed=0) # learns from the training set only
|
|
106
|
+
|
|
107
|
+
y_pred = predict(model, X_test) # classifies data the model has never seen
|
|
108
|
+
results = evaluate(y_test, y_pred) # prints accuracy, fitness, recall and confusion matrix
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> The default values of `train` already give good results in most cases. Only tune the parameters if you need to.
|
|
112
|
+
|
|
113
|
+
The functions of each step (`create_chromosomes`, `binary_fitness`, `select_parents_roulette`, `one_point_crossover`, `mutate`, `update_population`) can also be used on their own, imported from their modules (e.g. `from genetic_classifier.mutation import mutate`).
|
|
114
|
+
|
|
115
|
+
### Ordinal (several classes)
|
|
116
|
+
|
|
117
|
+
The classes must be numbers from `0` to `K-1`, ordered from lowest to highest. For K classes, pass K-1 thresholds in increasing order:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import numpy as np
|
|
121
|
+
|
|
122
|
+
thresholds = np.array([-1.5, -0.5, 0.5, 1.5]) # 4 cuts -> 5 classes
|
|
123
|
+
|
|
124
|
+
model = train(X, y, thresholds=thresholds, n_generations=2000)
|
|
125
|
+
y_pred = predict(model, X_new)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `train` parameters
|
|
129
|
+
|
|
130
|
+
| Parameter | Default | Description |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `X` | — | array (n_samples, n_features) with the data |
|
|
133
|
+
| `y` | — | vector with the class of each sample |
|
|
134
|
+
| `n_chromosomes` | `30` | population size |
|
|
135
|
+
| `n_mutated_genes` | `2` | how many genes change in each child |
|
|
136
|
+
| `n_offspring` | `16` | how many children per generation |
|
|
137
|
+
| `n_replacements` | `None` | how many of the worst in the population are swapped for the best children (at most the smaller of `n_chromosomes` and `n_offspring`). `None` = half of `n_offspring` |
|
|
138
|
+
| `n_generations` | `1000` | maximum number of generations |
|
|
139
|
+
| `target_fitness` | `1.0` | stops when the best fitness reaches this value (`1.0` = runs every generation unless it gets everything right) |
|
|
140
|
+
| `thresholds` | `None` | `None` = binary; array with K-1 cuts = ordinal |
|
|
141
|
+
| `gene_min` | `-1.0` | smallest gene value in the initial population |
|
|
142
|
+
| `gene_max` | `1.0` | largest gene value in the initial population |
|
|
143
|
+
| `seed` | `None` | random seed for reproducibility (`None` = different every run) |
|
|
144
|
+
| `verbose` | `True` | prints the best fitness and accuracy of every generation |
|
|
145
|
+
|
|
146
|
+
`train` returns a dictionary with `chromosome` (the best one found), `fitness`, `accuracy` (fraction right on the training data), `generation` (where it stopped), `thresholds` and `history` (lists `best`, `mean` and `accuracy` with the evolution per generation).
|
|
147
|
+
|
|
148
|
+
### Tuning the parameters
|
|
149
|
+
|
|
150
|
+
The defaults work well in most cases. If you need to tune them, keep in mind that the parameters affect each other:
|
|
151
|
+
|
|
152
|
+
- **Renewal rate = `n_replacements` ÷ `n_chromosomes`:** fraction of the population replaced every generation (default: 8 ÷ 30 ≈ 27%). Too low makes evolution slow; close to 100% the whole population becomes children of the same couple and loses diversity. That is why **increasing only the population usually makes things worse**: increase the replacements too, or leave it as `None`.
|
|
153
|
+
- **`n_offspring` × `n_replacements`:** only the best children enter the population. With more children than replacements, the worst children are discarded.
|
|
154
|
+
- **`n_mutated_genes` × number of features:** what matters is the fraction of the chromosome that changes. With few features, use `1`; with many, `2` or `3`.
|
|
155
|
+
- **Cost ≈ `n_generations` × (`n_chromosomes` + `n_offspring`) × n_samples.**
|
|
156
|
+
- **Explore × refine:** a large population and more mutation search for new solutions; high renewal and little mutation improve the ones already found.
|
|
157
|
+
|
|
158
|
+
To find out whether more generations would help, look at the end of `model["history"]["best"]`: if the fitness is still going up, the model has not finished.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Data format
|
|
163
|
+
|
|
164
|
+
The spreadsheet (`.xlsx` or `.csv`) must follow this layout:
|
|
165
|
+
|
|
166
|
+
| Lessons | X1 | X2 | ... | Xn | CLASS |
|
|
167
|
+
|---|---|---|---|---|---|
|
|
168
|
+
| Y001 | 1 | 0 | ... | 1 | 1 |
|
|
169
|
+
| Y002 | 0 | 1 | ... | 0 | 0 |
|
|
170
|
+
|
|
171
|
+
- **First column:** sample identifier (ignored)
|
|
172
|
+
- **Middle columns:** features
|
|
173
|
+
- **Last column:** class
|
|
174
|
+
|
|
175
|
+
To classify new data, which has no class column, use `has_labels=False`. In that case the function returns only `X`:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
X_new = read_excel("new_data.xlsx", has_labels=False)
|
|
179
|
+
y_pred = predict(model, X_new)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
A ready-made template is in [`examples/template/template.xlsx`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/template/template.xlsx).
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Examples and results
|
|
187
|
+
|
|
188
|
+
| Notebook | Dataset | Classes | Test accuracy* |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| [`binary_example.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/example_1_binary/binary_example.ipynb) | `binary_data_1.xlsx` (539 samples, 18 features) | 2 | **87%** |
|
|
191
|
+
| [`multiclass_example.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/example_2_multiclass/multiclass_example.ipynb) | `multiclass_data.xlsx` (261 samples, 30 features) | 5 | **79%** |
|
|
192
|
+
| [`mushroom_classification.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/mushroom_classification/mushroom_classification.ipynb) | `mushroom_data.xlsx` (8124 samples, 117 features) | 2 | **100%** |
|
|
193
|
+
| [`heart_disease_classification.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/heart_disease_classification/heart_disease_classification.ipynb) | `heart_data.xlsx` (917 samples, 21 features) | 2 | **85%** |
|
|
194
|
+
| [`diabetes_classification.ipynb`](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/examples/diabetes_classification/diabetes_classification.ipynb) | `diabetes_data.xlsx` (40000 samples, 21 features) | 3 | **58%**† |
|
|
195
|
+
|
|
196
|
+
\* Trained on 80% of the data and tested on the remaining 20%, which the model never saw, always with seed 0.
|
|
197
|
+
|
|
198
|
+
† Prediabetes is very rare in this dataset, so the accuracy alone is misleading: the notebook shows the recall of each class.
|
|
199
|
+
|
|
200
|
+
The binary notebook shows **one generation step by step**, calling each function in the order of the algorithm. The multiclass one shows how to choose the order of the classes when it is not known.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Project structure
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Genetic-Classifier/
|
|
208
|
+
├── src/
|
|
209
|
+
│ └── genetic_classifier/
|
|
210
|
+
│ ├── __init__.py # public API: train, predict, evaluate, confusion_matrix, read_excel, read_csv, train_test_split
|
|
211
|
+
│ ├── core.py # main loop (train) and prediction (predict)
|
|
212
|
+
│ ├── evaluation.py # model evaluation (evaluate) and confusion matrix
|
|
213
|
+
│ ├── creation.py # 1. initial population
|
|
214
|
+
│ ├── fitness.py # 2. binary and ordinal fitness, roulette proportions
|
|
215
|
+
│ ├── selection.py # 3. roulette selection
|
|
216
|
+
│ ├── crossover.py # 4. one-point crossover
|
|
217
|
+
│ ├── mutation.py # 5. mutation
|
|
218
|
+
│ ├── update.py # 6. replacing the worst with the best offspring
|
|
219
|
+
│ ├── reading.py # reading .xlsx and .csv files
|
|
220
|
+
│ └── split.py # train/test split (stratified)
|
|
221
|
+
├── docs/ # how the algorithm works, with math and examples (Markdown and LaTeX)
|
|
222
|
+
├── examples/ # example notebooks and datasets
|
|
223
|
+
├── tests/ # automated tests (coming soon)
|
|
224
|
+
├── pyproject.toml # package configuration (name, version, dependencies)
|
|
225
|
+
├── LICENSE # MIT license
|
|
226
|
+
└── requirements.txt
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Each step of the algorithm lives in its own module, so you can swap one piece (for example, try another selection method) without touching the others. Every function checks its parameters and gives clear error messages.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Changelog
|
|
234
|
+
|
|
235
|
+
### 3.0.0
|
|
236
|
+
- **The whole library is now in English** and is published on PyPI as **`genetic-classifier`** (import `genetic_classifier`). The Portuguese version stays available as `algoritmo-genetico` 2.0.0.
|
|
237
|
+
- Every docstring follows the same format: what the function does, each parameter with its type, and what it returns with its type and a suggested variable name.
|
|
238
|
+
- The deprecated alias `rodar` was removed.
|
|
239
|
+
|
|
240
|
+
Migrating from `algoritmo-genetico`:
|
|
241
|
+
|
|
242
|
+
| 2.x (Portuguese) | 3.0 (English) |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `treinar`, `prever`, `avaliar`, `matriz_confusao` | `train`, `predict`, `evaluate`, `confusion_matrix` |
|
|
245
|
+
| `ler_excel`, `ler_csv` (`tem_classe`) | `read_excel`, `read_csv` (`has_labels`) |
|
|
246
|
+
| `dividir_treino_teste` (`proporcao_treino`, `estratificar`) | `train_test_split` (`train_size`, `stratify`) |
|
|
247
|
+
| `qtd_cromossomos`, `qtd_genes_mutados`, `qtd_filhos`, `qtd_substituicoes` | `n_chromosomes`, `n_mutated_genes`, `n_offspring`, `n_replacements` |
|
|
248
|
+
| `geracoes`, `fitness_alvo`, `minimo`, `maximo` | `n_generations`, `target_fitness`, `gene_min`, `gene_max` |
|
|
249
|
+
| `y_real`, `y_previsto`, `nomes_classes` | `y_true`, `y_pred`, `class_names` |
|
|
250
|
+
| keys `cromossomo`, `acuracia`, `geracao`, `historico` (`melhor`, `medio`) | keys `chromosome`, `accuracy`, `generation`, `history` (`best`, `mean`) |
|
|
251
|
+
| key `matriz` of `avaliar` | key `confusion_matrix` of `evaluate` |
|
|
252
|
+
| `criar_cromossomos`, `fitness_binario`, `fitness_ordinal`, `fitness_percentual` | `create_chromosomes`, `binary_fitness`, `ordinal_fitness`, `fitness_proportions` |
|
|
253
|
+
| `selecionar_pais_roleta`, `cruzar_um_ponto`, `mutar`, `atualizar_populacao` | `select_parents_roulette`, `one_point_crossover`, `mutate`, `update_population` |
|
|
254
|
+
|
|
255
|
+
### 2.0.0
|
|
256
|
+
- New **`evaluate`** function (`avaliar`), which prints and returns accuracy, fitness, recall per class and the confusion matrix. The matrix can also be obtained on its own with **`confusion_matrix`**.
|
|
257
|
+
- `read_excel` and `read_csv` got the **`has_labels`** parameter: with `has_labels=False` they read new data without the class column.
|
|
258
|
+
- New checks: empty cells, text in the features and text classes in ordinal mode now raise clear error messages instead of silently giving wrong results.
|
|
259
|
+
- Training no longer creates offspring in the last generation, since they would never be compared with the best chromosome.
|
|
260
|
+
|
|
261
|
+
### 1.1.0
|
|
262
|
+
- `rodar` was renamed to **`treinar`** (now `train`), pairing with `prever` (now `predict`).
|
|
263
|
+
- **New default values**, which give much better results without tuning: 30 chromosomes, 2 mutated genes, 16 offspring, replacements = half of the offspring, 1000 generations and target fitness 1.0.
|
|
264
|
+
- Training converts `X` to float internally, which makes it up to ~6x faster when the data is integer (as read by `read_csv`).
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
Distributed under the [MIT license](https://github.com/FeAlmassy/Genetic-Classifier/blob/main/LICENSE): you can use, modify and redistribute it freely, as long as you keep the copyright notice.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Authors
|
|
275
|
+
|
|
276
|
+
- **Fellipe Almassy** — [GitHub](https://github.com/FeAlmassy)
|
|
277
|
+
- **Matheus Gomes** — [GitHub](https://github.com/matheos-hash)
|