mini-torch-from-scratch 0.1.0__py3-none-any.whl
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.
- mini_torch/__init__.py +4 -0
- mini_torch/autodiff.py +144 -0
- mini_torch/nn.py +55 -0
- mini_torch_from_scratch-0.1.0.dist-info/METADATA +90 -0
- mini_torch_from_scratch-0.1.0.dist-info/RECORD +8 -0
- mini_torch_from_scratch-0.1.0.dist-info/WHEEL +5 -0
- mini_torch_from_scratch-0.1.0.dist-info/licenses/LICENSE +21 -0
- mini_torch_from_scratch-0.1.0.dist-info/top_level.txt +1 -0
mini_torch/__init__.py
ADDED
mini_torch/autodiff.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Nombre:
|
|
5
|
+
def __init__(self, h_val, _parent=(), _op=""):
|
|
6
|
+
self.h_val = h_val
|
|
7
|
+
self.b_val = 0.0
|
|
8
|
+
self._derive = lambda: None
|
|
9
|
+
self._parent = set(_parent)
|
|
10
|
+
self._op = _op
|
|
11
|
+
|
|
12
|
+
def __repr__(self):
|
|
13
|
+
return f"[h_val: {self.h_val}, b_val: {self.b_val}]"
|
|
14
|
+
|
|
15
|
+
def __add__(self, autre):
|
|
16
|
+
autre = autre if isinstance(autre, Nombre) else Nombre(h_val=autre)
|
|
17
|
+
somme = Nombre(
|
|
18
|
+
h_val=self.h_val + autre.h_val,
|
|
19
|
+
_parent=(self, autre),
|
|
20
|
+
_op="+",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def _derive():
|
|
24
|
+
self.b_val += somme.b_val
|
|
25
|
+
autre.b_val += somme.b_val
|
|
26
|
+
|
|
27
|
+
somme._derive = _derive
|
|
28
|
+
return somme
|
|
29
|
+
|
|
30
|
+
def __mul__(self, autre):
|
|
31
|
+
autre = autre if isinstance(autre, Nombre) else Nombre(h_val=autre)
|
|
32
|
+
produit = Nombre(
|
|
33
|
+
h_val=self.h_val * autre.h_val,
|
|
34
|
+
_parent=(self, autre),
|
|
35
|
+
_op="*",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def _derive():
|
|
39
|
+
self.b_val += autre.h_val * produit.b_val
|
|
40
|
+
autre.b_val += self.h_val * produit.b_val
|
|
41
|
+
|
|
42
|
+
produit._derive = _derive
|
|
43
|
+
return produit
|
|
44
|
+
|
|
45
|
+
def __pow__(self, autre):
|
|
46
|
+
if not isinstance(autre, (int, float)):
|
|
47
|
+
raise TypeError("L'exposant doit être un nombre.")
|
|
48
|
+
|
|
49
|
+
puissance = Nombre(
|
|
50
|
+
h_val=self.h_val**autre,
|
|
51
|
+
_parent=(self,),
|
|
52
|
+
_op=f"**{autre}",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def _derive():
|
|
56
|
+
self.b_val += autre * (self.h_val ** (autre - 1)) * puissance.b_val
|
|
57
|
+
|
|
58
|
+
puissance._derive = _derive
|
|
59
|
+
return puissance
|
|
60
|
+
|
|
61
|
+
def maximum(self, autre):
|
|
62
|
+
autre = autre if isinstance(autre, Nombre) else Nombre(h_val=autre)
|
|
63
|
+
max_val = Nombre(
|
|
64
|
+
h_val=max(self.h_val, autre.h_val),
|
|
65
|
+
_parent=(self, autre),
|
|
66
|
+
_op="max",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def _derive():
|
|
70
|
+
if self.h_val > autre.h_val:
|
|
71
|
+
self.b_val += max_val.b_val
|
|
72
|
+
elif autre.h_val > self.h_val:
|
|
73
|
+
autre.b_val += max_val.b_val
|
|
74
|
+
else:
|
|
75
|
+
self.b_val += 0.5 * max_val.b_val
|
|
76
|
+
autre.b_val += 0.5 * max_val.b_val
|
|
77
|
+
|
|
78
|
+
max_val._derive = _derive
|
|
79
|
+
return max_val
|
|
80
|
+
|
|
81
|
+
def __neg__(self):
|
|
82
|
+
return self * -1
|
|
83
|
+
|
|
84
|
+
def __sub__(self, autre):
|
|
85
|
+
return self + (-autre)
|
|
86
|
+
|
|
87
|
+
def __rsub__(self, autre):
|
|
88
|
+
return autre + (-self)
|
|
89
|
+
|
|
90
|
+
def __rmul__(self, autre):
|
|
91
|
+
return self * autre
|
|
92
|
+
|
|
93
|
+
def __radd__(self, autre):
|
|
94
|
+
return self + autre
|
|
95
|
+
|
|
96
|
+
def tanh(self):
|
|
97
|
+
t = math.tanh(self.h_val)
|
|
98
|
+
out = Nombre(h_val=t, _parent=(self,), _op="tanh")
|
|
99
|
+
|
|
100
|
+
def _derive():
|
|
101
|
+
self.b_val += (1 - t**2) * out.b_val
|
|
102
|
+
|
|
103
|
+
out._derive = _derive
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
def exp(self):
|
|
107
|
+
out = Nombre(
|
|
108
|
+
h_val=math.exp(self.h_val),
|
|
109
|
+
_parent=(self,),
|
|
110
|
+
_op="exp",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _derive():
|
|
114
|
+
self.b_val += out.h_val * out.b_val
|
|
115
|
+
|
|
116
|
+
out._derive = _derive
|
|
117
|
+
return out
|
|
118
|
+
|
|
119
|
+
def backward(self):
|
|
120
|
+
topo = []
|
|
121
|
+
seen = set()
|
|
122
|
+
|
|
123
|
+
def construire_graph(nbr):
|
|
124
|
+
if nbr not in seen:
|
|
125
|
+
seen.add(nbr)
|
|
126
|
+
|
|
127
|
+
for parent in nbr._parent:
|
|
128
|
+
construire_graph(parent)
|
|
129
|
+
|
|
130
|
+
topo.append(nbr)
|
|
131
|
+
|
|
132
|
+
construire_graph(self)
|
|
133
|
+
|
|
134
|
+
for nbr in topo:
|
|
135
|
+
if nbr._parent:
|
|
136
|
+
nbr.b_val = 0.0
|
|
137
|
+
|
|
138
|
+
self.b_val = 1.0
|
|
139
|
+
|
|
140
|
+
for nbr in reversed(topo):
|
|
141
|
+
nbr._derive()
|
|
142
|
+
|
|
143
|
+
def _retropropager(self):
|
|
144
|
+
self.backward()
|
mini_torch/nn.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .autodiff import Nombre
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Neuron:
|
|
5
|
+
def __init__(self, poids, biais):
|
|
6
|
+
self.poids = [p if isinstance(p, Nombre) else Nombre(p) for p in poids]
|
|
7
|
+
self.biais = biais if isinstance(biais, Nombre) else Nombre(biais)
|
|
8
|
+
|
|
9
|
+
def propager(self, entrees):
|
|
10
|
+
if len(entrees) != len(self.poids):
|
|
11
|
+
raise ValueError(
|
|
12
|
+
f"Le neurone attend {len(self.poids)} entrées, "
|
|
13
|
+
f"mais en a reçu {len(entrees)}."
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
z = self.biais
|
|
17
|
+
|
|
18
|
+
for x, w in zip(entrees, self.poids):
|
|
19
|
+
x = x if isinstance(x, Nombre) else Nombre(x)
|
|
20
|
+
z = z + x * w
|
|
21
|
+
|
|
22
|
+
return z.tanh()
|
|
23
|
+
|
|
24
|
+
def parameters(self):
|
|
25
|
+
return self.poids + [self.biais]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Couche:
|
|
29
|
+
def __init__(self, neurons):
|
|
30
|
+
self.neurons = neurons
|
|
31
|
+
|
|
32
|
+
def propager(self, caracteres):
|
|
33
|
+
return [neuron.propager(caracteres) for neuron in self.neurons]
|
|
34
|
+
|
|
35
|
+
def parameters(self):
|
|
36
|
+
params = []
|
|
37
|
+
for neuron in self.neurons:
|
|
38
|
+
params.extend(neuron.parameters())
|
|
39
|
+
return params
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MLP:
|
|
43
|
+
def __init__(self, couches):
|
|
44
|
+
self.couches = couches
|
|
45
|
+
|
|
46
|
+
def propager(self, entrees):
|
|
47
|
+
for couche_actuelle in self.couches:
|
|
48
|
+
entrees = couche_actuelle.propager(entrees)
|
|
49
|
+
return entrees
|
|
50
|
+
|
|
51
|
+
def parameters(self):
|
|
52
|
+
params = []
|
|
53
|
+
for couche_actuelle in self.couches:
|
|
54
|
+
params.extend(couche_actuelle.parameters())
|
|
55
|
+
return params
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mini-torch-from-scratch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A small neural network and automatic differentiation framework
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Mini Torch from Scratch
|
|
15
|
+
|
|
16
|
+
Un petit framework Python, sans dependance externe, pour l'autodiff scalaire et
|
|
17
|
+
les reseaux fully connected (MLP). Il ne fournit pas encore de couches CNN.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```powershell
|
|
22
|
+
python -m pip install mini-torch-from-scratch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Le nom du paquet sur PyPI est `mini-torch-from-scratch`. Le nom d'import Python
|
|
26
|
+
reste `mini_torch`.
|
|
27
|
+
|
|
28
|
+
## Exemple
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from mini_torch import Couche, MLP, Neuron, Nombre
|
|
32
|
+
|
|
33
|
+
reseau = MLP(
|
|
34
|
+
[
|
|
35
|
+
Couche([Neuron([0.5, -0.2], 0.1)]),
|
|
36
|
+
Couche([Neuron([0.8], 0.0)]),
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
sortie = reseau.propager([2.0, 1.0])[0]
|
|
41
|
+
perte = (sortie - 1.0) ** 2
|
|
42
|
+
perte.backward()
|
|
43
|
+
|
|
44
|
+
for parametre in reseau.parameters():
|
|
45
|
+
parametre.h_val -= 0.01 * parametre.b_val
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`Nombre.backward()` calcule les gradients et les accumule sur les feuilles du
|
|
49
|
+
graphe. Remets les gradients des parametres a zero avant une nouvelle etape
|
|
50
|
+
d'optimisation si tu ne veux pas les accumuler.
|
|
51
|
+
|
|
52
|
+
Pour voir un exemple d'entrainement complet depuis le depot :
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
python -m examples.entrainement
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Tests
|
|
59
|
+
|
|
60
|
+
Depuis la racine du depot :
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
python -m unittest discover -s tests
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Construire et publier sur PyPI
|
|
67
|
+
|
|
68
|
+
La publication n'est pas automatique. Pour publier toi-meme :
|
|
69
|
+
|
|
70
|
+
1. Remplace le titulaire fictif dans `LICENSE` par ton nom ou celui de ton
|
|
71
|
+
organisation, puis cree un compte PyPI et verifie que le nom
|
|
72
|
+
`mini-torch-from-scratch` est toujours disponible. Sa disponibilite peut
|
|
73
|
+
changer.
|
|
74
|
+
2. Depuis la racine du depot, construis et verifies les distributions :
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
python -m pip install --upgrade build twine
|
|
78
|
+
python -m build
|
|
79
|
+
python -m twine check dist/*
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. Configure tes identifiants PyPI selon la documentation officielle de Twine,
|
|
83
|
+
puis envoie les distributions :
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
python -m twine upload dist/*
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Ne place pas de jeton PyPI dans le code source ni dans le depot. Augmente la
|
|
90
|
+
version dans `pyproject.toml` avant chaque nouvelle publication.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
mini_torch/__init__.py,sha256=isATTvYziM-LW-yR-5BKKz-qx95Y2gQWhUv7XDOABxY,118
|
|
2
|
+
mini_torch/autodiff.py,sha256=Zn4bxlt0G749unlt3eR9aFdxbUu0G1bX2_HIGC-FZ9c,3695
|
|
3
|
+
mini_torch/nn.py,sha256=A97AS9mExiWO_4SWmMcGBNJXXPK0n5pJiQG63SDa7Vw,1526
|
|
4
|
+
mini_torch_from_scratch-0.1.0.dist-info/licenses/LICENSE,sha256=6YR8E1abkg_9lfZxJwAIJPjOMOH-J0HBO2LXtNwR6A8,1092
|
|
5
|
+
mini_torch_from_scratch-0.1.0.dist-info/METADATA,sha256=9bHMWx9beD8De6Bm7mSqxoSooCL-RUivu2V2R3kuYhU,2473
|
|
6
|
+
mini_torch_from_scratch-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
7
|
+
mini_torch_from_scratch-0.1.0.dist-info/top_level.txt,sha256=Tjy7Wli-5exd4kympiwVtQMpf-8564YZ-koIbi4Be4I,11
|
|
8
|
+
mini_torch_from_scratch-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Salma Izoughar
|
|
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 @@
|
|
|
1
|
+
mini_torch
|