mathai 2.5.2__tar.gz → 2.5.4__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.
- {mathai-2.5.2 → mathai-2.5.4}/PKG-INFO +3 -1
- {mathai-2.5.2 → mathai-2.5.4}/mathai/__init__.py +1 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/ml.py +1 -1
- mathai-2.5.4/mathai/mldirect.py +1017 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai.egg-info/PKG-INFO +3 -1
- {mathai-2.5.2 → mathai-2.5.4}/mathai.egg-info/SOURCES.txt +2 -0
- mathai-2.5.4/mathai.egg-info/requires.txt +1 -0
- {mathai-2.5.2 → mathai-2.5.4}/setup.py +4 -1
- {mathai-2.5.2 → mathai-2.5.4}/README.md +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/apart.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/base.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/benchmark.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/decision.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/diff.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/electronic.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/expand.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/factor.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/formula_applier.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/formula_compiler.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/fraction.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/graphics.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/integrate.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/inverse.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/lambda_calculus.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/limit.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/linear.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/logic.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/matrix.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/mlscalar.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/ncert_data.txt +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/ncert_data_optimized.txt +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/ode.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/parser.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/pde.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/printeq.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/qm.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/simplify.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/square_root.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/statistics.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/test.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/tool.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/trig.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai/univariate_inequality.py +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai.egg-info/dependency_links.txt +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/mathai.egg-info/top_level.txt +0 -0
- {mathai-2.5.2 → mathai-2.5.4}/setup.cfg +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mathai
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.4
|
|
4
4
|
Summary: Mathematics solving Ai tailored to NCERT
|
|
5
5
|
Home-page: https://github.com/infinity390/mathai
|
|
6
6
|
Requires-Python: >=3.7
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
8
9
|
Dynamic: description
|
|
9
10
|
Dynamic: description-content-type
|
|
10
11
|
Dynamic: home-page
|
|
12
|
+
Dynamic: requires-dist
|
|
11
13
|
Dynamic: requires-python
|
|
12
14
|
Dynamic: summary
|
|
13
15
|
|
|
@@ -675,7 +675,7 @@ class NeuralNetwork:
|
|
|
675
675
|
A_list = A_list + flatten_list(item)
|
|
676
676
|
exec(s, env2)
|
|
677
677
|
return env2["fx"](A_list,flatten_list(given_x))
|
|
678
|
-
def train(self, train_x, train_y, learning_rate, epoch, batch_size=1):
|
|
678
|
+
def train(self, train_x, train_y, learning_rate=12, epoch=10000, batch_size=1):
|
|
679
679
|
global hadamard, zeros, transpose, matadd, sigmoid, identity, power, vec, diag, flatten,\
|
|
680
680
|
matmul, reshape, kronecker, commutation, broadcast, im2col, conv, col2im
|
|
681
681
|
print("gradients calculated")
|
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
from .formula_compiler import formula_list_compiler
|
|
2
|
+
import itertools
|
|
3
|
+
from .parser import parse, remove_extra_brackets
|
|
4
|
+
from .simplify import simplify, addition_node_mat
|
|
5
|
+
from .ml import diff_matrix_matrix
|
|
6
|
+
from .base import *
|
|
7
|
+
import random
|
|
8
|
+
import copy
|
|
9
|
+
import math
|
|
10
|
+
import numpy as np
|
|
11
|
+
class TreeNode2:
|
|
12
|
+
count = 0
|
|
13
|
+
count_x = 0
|
|
14
|
+
count_y = 0
|
|
15
|
+
def __new__(cls, name=None, children=None):
|
|
16
|
+
if name is None:
|
|
17
|
+
return super().__new__(cls)
|
|
18
|
+
if children and len(children) == 2:
|
|
19
|
+
c1, c2 = children[0], children[1]
|
|
20
|
+
if hasattr(c1, 'name') and c1.name.startswith("d_") and \
|
|
21
|
+
hasattr(c2, 'name') and c2.name.startswith("d_"):
|
|
22
|
+
try:
|
|
23
|
+
val1 = int(c1.name[2:])
|
|
24
|
+
val2 = int(c2.name[2:])
|
|
25
|
+
res = None
|
|
26
|
+
if name == "f_add":
|
|
27
|
+
res = val1 + val2
|
|
28
|
+
elif name == "f_sub":
|
|
29
|
+
res = val1 - val2
|
|
30
|
+
elif name == "f_mul":
|
|
31
|
+
res = val1 * val2
|
|
32
|
+
elif name == "f_pow":
|
|
33
|
+
if val2 >= 0:
|
|
34
|
+
res = val1 ** val2
|
|
35
|
+
else:
|
|
36
|
+
if val1 == 1:
|
|
37
|
+
res = 1
|
|
38
|
+
elif val1 == -1:
|
|
39
|
+
res = 1 if val2 % 2 == 0 else -1
|
|
40
|
+
if res is not None:
|
|
41
|
+
obj = super().__new__(cls)
|
|
42
|
+
obj.name = f"d_{res}"
|
|
43
|
+
obj.children = []
|
|
44
|
+
return obj
|
|
45
|
+
except ValueError:
|
|
46
|
+
pass
|
|
47
|
+
if name == "f_mul" and children and any(hasattr(child, 'name') and child.name == "d_0" for child in children):
|
|
48
|
+
obj = super().__new__(cls)
|
|
49
|
+
obj.name = "d_0"
|
|
50
|
+
obj.children = []
|
|
51
|
+
return obj
|
|
52
|
+
if name == "f_mul" and children and len(children) == 2:
|
|
53
|
+
if hasattr(children[0], 'name') and children[0].name == "d_1":
|
|
54
|
+
return children[1]
|
|
55
|
+
if hasattr(children[1], 'name') and children[1].name == "d_1":
|
|
56
|
+
return children[0]
|
|
57
|
+
if name == "f_add" and children and len(children) == 2:
|
|
58
|
+
if hasattr(children[0], 'name') and children[0].name == "d_0":
|
|
59
|
+
return children[1]
|
|
60
|
+
if hasattr(children[1], 'name') and children[1].name == "d_0":
|
|
61
|
+
return children[0]
|
|
62
|
+
return super().__new__(cls)
|
|
63
|
+
def __init__(self, name=None, children=None):
|
|
64
|
+
if hasattr(self, 'name'):
|
|
65
|
+
return
|
|
66
|
+
if name is None:
|
|
67
|
+
return
|
|
68
|
+
self.name = name
|
|
69
|
+
self.children = children
|
|
70
|
+
self.compare = None
|
|
71
|
+
def normal(self):
|
|
72
|
+
commutative={"f_add","f_mul"}
|
|
73
|
+
stack=[(self,False)]
|
|
74
|
+
while stack:
|
|
75
|
+
node,visited=stack.pop()
|
|
76
|
+
if visited:
|
|
77
|
+
if node.children:
|
|
78
|
+
if node.name in commutative:
|
|
79
|
+
node.children.sort(key=lambda c:c.compare)
|
|
80
|
+
node.compare=(node.name,*(c.compare for c in node.children))
|
|
81
|
+
else:
|
|
82
|
+
node.compare=(node.name,)
|
|
83
|
+
else:
|
|
84
|
+
stack.append((node,True))
|
|
85
|
+
stack.extend((c,False) for c in node.children)
|
|
86
|
+
def __repr__(self):
|
|
87
|
+
action_stack = [(self, False)]
|
|
88
|
+
value_stack = []
|
|
89
|
+
while action_stack:
|
|
90
|
+
node, children_processed = action_stack.pop()
|
|
91
|
+
if node.name.startswith("v_"):
|
|
92
|
+
var_idx = node.name.split("_")[1]
|
|
93
|
+
value_stack.append(f"A[{var_idx}]")
|
|
94
|
+
continue
|
|
95
|
+
if node.name.startswith("x_"):
|
|
96
|
+
var_idx = node.name.split("_")[1]
|
|
97
|
+
value_stack.append(f"X[{var_idx}]")
|
|
98
|
+
continue
|
|
99
|
+
if node.name.startswith("y_"):
|
|
100
|
+
var_idx = node.name.split("_")[1]
|
|
101
|
+
value_stack.append(f"Y[{var_idx}]")
|
|
102
|
+
continue
|
|
103
|
+
if node.name.startswith("w_"):
|
|
104
|
+
value_stack.append(node.name.split("_")[1])
|
|
105
|
+
continue
|
|
106
|
+
if node.name.startswith("d_"):
|
|
107
|
+
value_stack.append(node.name.split("_")[1])
|
|
108
|
+
continue
|
|
109
|
+
if not children_processed:
|
|
110
|
+
action_stack.append((node, True))
|
|
111
|
+
for child in reversed(node.children):
|
|
112
|
+
action_stack.append((child, False))
|
|
113
|
+
continue
|
|
114
|
+
num_children = len(node.children)
|
|
115
|
+
child_strs = []
|
|
116
|
+
for _ in range(num_children):
|
|
117
|
+
child_strs.append(value_stack.pop())
|
|
118
|
+
child_strs.reverse()
|
|
119
|
+
if node.name == "f_add":
|
|
120
|
+
value_stack.append(f"({'+'.join(child_strs)})")
|
|
121
|
+
elif node.name == "f_mul":
|
|
122
|
+
value_stack.append(f"({'*'.join(child_strs)})")
|
|
123
|
+
elif node.name == "f_pow":
|
|
124
|
+
value_stack.append(f"({'**'.join(child_strs)})")
|
|
125
|
+
elif node.name == "f_div":
|
|
126
|
+
value_stack.append(f"({'/'.join(child_strs)})")
|
|
127
|
+
elif node.name == "f_exp":
|
|
128
|
+
value_stack.append(f"exp({child_strs[0]})")
|
|
129
|
+
elif node.name == "f_neg":
|
|
130
|
+
value_stack.append(f"(-{child_strs[0]})")
|
|
131
|
+
elif node.name == "f_sigmoid":
|
|
132
|
+
value_stack.append(f"sigmoid({child_strs[0]})")
|
|
133
|
+
else:
|
|
134
|
+
value_stack.append(f"{node.name}({','.join(child_strs)})")
|
|
135
|
+
return value_stack[0]
|
|
136
|
+
def zeros(*shape):
|
|
137
|
+
if len(shape) == 0:
|
|
138
|
+
return TreeNode2("d_0",[])
|
|
139
|
+
return [zeros(*shape[1:]) for _ in range(shape[0])]
|
|
140
|
+
def flatten(A):
|
|
141
|
+
B = len(A)
|
|
142
|
+
H = len(A[0])
|
|
143
|
+
W = len(A[0][0])
|
|
144
|
+
C = len(A[0][0][0])
|
|
145
|
+
out = []
|
|
146
|
+
for b in range(B):
|
|
147
|
+
row = []
|
|
148
|
+
for i in range(H):
|
|
149
|
+
for j in range(W):
|
|
150
|
+
for c in range(C):
|
|
151
|
+
row.append(A[b][i][j][c])
|
|
152
|
+
out.append(row)
|
|
153
|
+
return out
|
|
154
|
+
def conv(X, K):
|
|
155
|
+
B = len(X)
|
|
156
|
+
H = len(X[0])
|
|
157
|
+
W = len(X[0][0])
|
|
158
|
+
C = len(X[0][0][0])
|
|
159
|
+
P = len(K[0])
|
|
160
|
+
OH = H - P + 1
|
|
161
|
+
OW = W - P + 1
|
|
162
|
+
out = []
|
|
163
|
+
for b in range(B):
|
|
164
|
+
batch = []
|
|
165
|
+
for i in range(OH):
|
|
166
|
+
row = []
|
|
167
|
+
for j in range(OW):
|
|
168
|
+
s = TreeNode2("d_0",[])
|
|
169
|
+
for u in range(P):
|
|
170
|
+
for v in range(P):
|
|
171
|
+
for c in range(C):
|
|
172
|
+
s = TreeNode2("f_add", [s, TreeNode2("f_mul",[X[b][i + u][j + v][c] , K[0][u][v][c]])])
|
|
173
|
+
row.append([s])
|
|
174
|
+
batch.append(row)
|
|
175
|
+
out.append(batch)
|
|
176
|
+
return out
|
|
177
|
+
def im2col(image, kernel):
|
|
178
|
+
B = len(image)
|
|
179
|
+
H = len(image[0])
|
|
180
|
+
W = len(image[0][0])
|
|
181
|
+
C = len(image[0][0][0])
|
|
182
|
+
Kh = len(kernel[0])
|
|
183
|
+
Kw = len(kernel[0][0])
|
|
184
|
+
out_h = H - Kh + 1
|
|
185
|
+
out_w = W - Kw + 1
|
|
186
|
+
P = Kh * Kw * C
|
|
187
|
+
cols = []
|
|
188
|
+
for b in range(B):
|
|
189
|
+
for i in range(out_h):
|
|
190
|
+
for j in range(out_w):
|
|
191
|
+
row = [TreeNode2("d_0",[]) for _ in range(B * P)]
|
|
192
|
+
idx = b * P
|
|
193
|
+
for r in range(Kh):
|
|
194
|
+
for c in range(Kw):
|
|
195
|
+
for ch in range(C):
|
|
196
|
+
row[idx] = image[b][i+r][j+c][ch]
|
|
197
|
+
idx += 1
|
|
198
|
+
cols.append(row)
|
|
199
|
+
return cols
|
|
200
|
+
def col2im(kernel, image):
|
|
201
|
+
B = len(image)
|
|
202
|
+
H = len(image[0])
|
|
203
|
+
W = len(image[0][0])
|
|
204
|
+
C = len(image[0][0][0])
|
|
205
|
+
K = len(kernel)
|
|
206
|
+
Kh = len(kernel[0])
|
|
207
|
+
Kw = len(kernel[0][0])
|
|
208
|
+
assert K == 1 or K == B
|
|
209
|
+
OH = H - Kh + 1
|
|
210
|
+
OW = W - Kw + 1
|
|
211
|
+
rows = B * OH * OW
|
|
212
|
+
cols = B * H * W * C
|
|
213
|
+
J = [[TreeNode2("d_0",[]) for _ in range(cols)] for _ in range(rows)]
|
|
214
|
+
row = 0
|
|
215
|
+
for b in range(B):
|
|
216
|
+
kb = 0 if K == 1 else b
|
|
217
|
+
for i in range(OH):
|
|
218
|
+
for j in range(OW):
|
|
219
|
+
for u in range(Kh):
|
|
220
|
+
for v in range(Kw):
|
|
221
|
+
for ch in range(C):
|
|
222
|
+
col = (
|
|
223
|
+
(((b * H + (i + u)) * W + (j + v)) * C)
|
|
224
|
+
+ ch
|
|
225
|
+
)
|
|
226
|
+
J[row][col] = kernel[kb][u][v][ch]
|
|
227
|
+
row += 1
|
|
228
|
+
return J
|
|
229
|
+
def commutation(m, n):
|
|
230
|
+
size = m * n
|
|
231
|
+
K = [[TreeNode2("d_0", []) for _ in range(size)] for _ in range(size)]
|
|
232
|
+
for i in range(m):
|
|
233
|
+
for j in range(n):
|
|
234
|
+
src_idx = j * m + i
|
|
235
|
+
dest_idx = i * n + j
|
|
236
|
+
K[dest_idx][src_idx] = TreeNode2("d_1",[])
|
|
237
|
+
return K
|
|
238
|
+
def hadamard_h(A, B):
|
|
239
|
+
if isinstance(A, list) and isinstance(B, list):
|
|
240
|
+
assert len(A) == len(B)
|
|
241
|
+
return [hadamard_h(a, b) for a, b in zip(A, B)]
|
|
242
|
+
return TreeNode2("f_mul",[A,B])
|
|
243
|
+
def hadamard(*lst_prod):
|
|
244
|
+
result = lst_prod[0]
|
|
245
|
+
for x in lst_prod[1:]:
|
|
246
|
+
if isinstance(result, list) and isinstance(x, list):
|
|
247
|
+
result = hadamard_h(result, x)
|
|
248
|
+
elif isinstance(result, list):
|
|
249
|
+
result = apply(result, lambda y: TreeNode2("f_mul",[y,x]))
|
|
250
|
+
elif isinstance(x, list):
|
|
251
|
+
result = apply(x, lambda y: TreeNode2("f_mul",[result,y]))
|
|
252
|
+
else:
|
|
253
|
+
result = TreeNode2("f_mul",[result,x])
|
|
254
|
+
return result
|
|
255
|
+
def matadd_h(A, B):
|
|
256
|
+
if isinstance(A, list) and isinstance(B, list):
|
|
257
|
+
assert len(A) == len(B)
|
|
258
|
+
return [matadd_h(a, b) for a, b in zip(A, B)]
|
|
259
|
+
return TreeNode2("f_add",[A,B])
|
|
260
|
+
def matadd(*lst_prod):
|
|
261
|
+
result = lst_prod[0]
|
|
262
|
+
for x in lst_prod[1:]:
|
|
263
|
+
if isinstance(result, list) and isinstance(x, list):
|
|
264
|
+
result = matadd_h(result, x)
|
|
265
|
+
elif isinstance(result, list):
|
|
266
|
+
result = apply(result, lambda y: TreeNode2("f_add",[y,x]))
|
|
267
|
+
elif isinstance(x, list):
|
|
268
|
+
result = apply(x, lambda y: TreeNode2("f_add",[result,y]))
|
|
269
|
+
else:
|
|
270
|
+
result = TreeNode2("f_add",[result,x])
|
|
271
|
+
return result
|
|
272
|
+
def matmul_h(A, B):
|
|
273
|
+
assert len(A[0])==len(B)
|
|
274
|
+
rows = len(A)
|
|
275
|
+
inner = len(A[0])
|
|
276
|
+
cols = len(B[0])
|
|
277
|
+
C = []
|
|
278
|
+
for i in range(rows):
|
|
279
|
+
row = []
|
|
280
|
+
for j in range(cols):
|
|
281
|
+
expr = TreeNode2("d_0",[])
|
|
282
|
+
for k in range(inner):
|
|
283
|
+
left = A[i][k]
|
|
284
|
+
right = B[k][j]
|
|
285
|
+
expr = TreeNode2("f_add",[expr,TreeNode2("f_mul",[left,right])])
|
|
286
|
+
row.append(expr)
|
|
287
|
+
C.append(row)
|
|
288
|
+
return C
|
|
289
|
+
def depth(x):
|
|
290
|
+
d = 0
|
|
291
|
+
while isinstance(x, list):
|
|
292
|
+
d += 1
|
|
293
|
+
x = x[0]
|
|
294
|
+
return d
|
|
295
|
+
def matmul(*lst_prod):
|
|
296
|
+
result = lst_prod[0]
|
|
297
|
+
for index, x in enumerate(lst_prod[1:]):
|
|
298
|
+
if not isinstance(result, list) or not isinstance(x, list):
|
|
299
|
+
if isinstance(result, list):
|
|
300
|
+
result = apply(result, lambda y: TreeNode2("f_mul",[y,x]))
|
|
301
|
+
elif isinstance(x, list):
|
|
302
|
+
result = apply(x, lambda y: TreeNode2("f_mul",[result,y]))
|
|
303
|
+
else:
|
|
304
|
+
result = TreeNode2("f_mul",[result,x])
|
|
305
|
+
continue
|
|
306
|
+
r1 = depth(result)
|
|
307
|
+
r2 = depth(x)
|
|
308
|
+
if r1 == 2 and r2 == 2:
|
|
309
|
+
result = matmul_h(result, x)
|
|
310
|
+
elif r1 == 3 and r2 == 2:
|
|
311
|
+
result = [matmul_h(A, x) for A in result]
|
|
312
|
+
elif r1 == 2 and r2 == 3:
|
|
313
|
+
result = [matmul_h(result, B) for B in x]
|
|
314
|
+
elif r1 == 3 and r2 == 3:
|
|
315
|
+
if len(result) == 1:
|
|
316
|
+
result = [matmul_h(result[0], B) for B in x]
|
|
317
|
+
elif len(x) == 1:
|
|
318
|
+
result = [matmul_h(A, x[0]) for A in result]
|
|
319
|
+
else:
|
|
320
|
+
assert len(result) == len(x)
|
|
321
|
+
result = [
|
|
322
|
+
matmul_h(A, B)
|
|
323
|
+
for A, B in zip(result, x)
|
|
324
|
+
]
|
|
325
|
+
else:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
f"Unsupported matmul ranks ({r1}, {r2})"
|
|
328
|
+
)
|
|
329
|
+
return result
|
|
330
|
+
def kronecker_h(A, B):
|
|
331
|
+
m = len(A)
|
|
332
|
+
n = len(A[0])
|
|
333
|
+
p = len(B)
|
|
334
|
+
q = len(B[0])
|
|
335
|
+
result = [[TreeNode2("d_0", []) for _ in range(n * q)] for _ in range(m * p)]
|
|
336
|
+
for j in range(n):
|
|
337
|
+
for c in range(q):
|
|
338
|
+
col_target = (j*q)+c
|
|
339
|
+
for i in range(m):
|
|
340
|
+
for r in range(p):
|
|
341
|
+
row_target = (i*p)+r
|
|
342
|
+
result[row_target][col_target] = TreeNode2("f_mul",[A[i][j],B[r][c]])
|
|
343
|
+
return result
|
|
344
|
+
def kronecker(*lst_prod):
|
|
345
|
+
result = lst_prod[0]
|
|
346
|
+
for x in lst_prod[1:]:
|
|
347
|
+
result = kronecker_h(result, x)
|
|
348
|
+
return result
|
|
349
|
+
def transpose(A):
|
|
350
|
+
rows = len(A)
|
|
351
|
+
cols = len(A[0])
|
|
352
|
+
return [[A[i][j] for i in range(rows)] for j in range(cols)]
|
|
353
|
+
def apply(arr, fx):
|
|
354
|
+
if isinstance(arr, list):
|
|
355
|
+
return [apply(item, fx) for item in arr]
|
|
356
|
+
return fx(arr)
|
|
357
|
+
def sigmoid(arg):
|
|
358
|
+
def fx_apply(x):
|
|
359
|
+
return TreeNode2("f_sigmoid",[x])
|
|
360
|
+
return apply(arg, fx_apply)
|
|
361
|
+
def identity(size):
|
|
362
|
+
arr = zeros(size,size)
|
|
363
|
+
for i in range(size):
|
|
364
|
+
for j in range(size):
|
|
365
|
+
if i == j:
|
|
366
|
+
arr[i][j] = TreeNode2("d_1",[])
|
|
367
|
+
return arr
|
|
368
|
+
def vec(A):
|
|
369
|
+
out = []
|
|
370
|
+
def visit(x):
|
|
371
|
+
if isinstance(x, list):
|
|
372
|
+
for y in x:
|
|
373
|
+
visit(y)
|
|
374
|
+
else:
|
|
375
|
+
out.append([x])
|
|
376
|
+
visit(A)
|
|
377
|
+
return out
|
|
378
|
+
def diag(v):
|
|
379
|
+
if len(v) > 0 and isinstance(v[0], list):
|
|
380
|
+
if len(v[0]) != 1:
|
|
381
|
+
raise ValueError("diag expects a column vector.")
|
|
382
|
+
v = [x[0] for x in v]
|
|
383
|
+
for x in v:
|
|
384
|
+
if isinstance(x, list):
|
|
385
|
+
raise ValueError("diag received a tensor instead of a vector.")
|
|
386
|
+
n = len(v)
|
|
387
|
+
result = [[TreeNode2("d_0", []) for _ in range(n)] for _ in range(n)]
|
|
388
|
+
for i in range(n):
|
|
389
|
+
result[i][i] = v[i]
|
|
390
|
+
return result
|
|
391
|
+
def broadcast(M, rows):
|
|
392
|
+
if not isinstance(M, list):
|
|
393
|
+
raise ValueError("Cannot broadcast a scalar.")
|
|
394
|
+
r = len(M)
|
|
395
|
+
if r == rows:
|
|
396
|
+
return M
|
|
397
|
+
if r != 1:
|
|
398
|
+
raise ValueError(f"Cannot broadcast first dimension {r} to {rows}")
|
|
399
|
+
return [copy.deepcopy(M[0]) for _ in range(rows)]
|
|
400
|
+
def reshape(data, *shape):
|
|
401
|
+
def get_shape(x):
|
|
402
|
+
if not isinstance(x, list):
|
|
403
|
+
return []
|
|
404
|
+
return [len(x)] + get_shape(x[0])
|
|
405
|
+
in_shape = get_shape(data)
|
|
406
|
+
def get_element(x, indices):
|
|
407
|
+
for idx in indices:
|
|
408
|
+
x = x[idx]
|
|
409
|
+
return x
|
|
410
|
+
flat = []
|
|
411
|
+
if in_shape:
|
|
412
|
+
num_elements = 1
|
|
413
|
+
for s in in_shape:
|
|
414
|
+
num_elements *= s
|
|
415
|
+
for i in range(num_elements):
|
|
416
|
+
indices = [0] * len(in_shape)
|
|
417
|
+
rem = i
|
|
418
|
+
for d in reversed(range(len(in_shape))):
|
|
419
|
+
indices[d] = rem % in_shape[d]
|
|
420
|
+
rem //= in_shape[d]
|
|
421
|
+
flat.append(get_element(data, indices))
|
|
422
|
+
else:
|
|
423
|
+
flat = [data]
|
|
424
|
+
total = 1
|
|
425
|
+
for s in shape:
|
|
426
|
+
total *= s
|
|
427
|
+
if len(flat) != total:
|
|
428
|
+
raise ValueError(f"Cannot reshape: incompatible dimensions. Total size {len(flat)} vs {total}")
|
|
429
|
+
def make_empty(shp):
|
|
430
|
+
if len(shp) == 1:
|
|
431
|
+
return [None] * shp[0]
|
|
432
|
+
return [make_empty(shp[1:]) for _ in range(shp[0])]
|
|
433
|
+
out_structure = make_empty(shape)
|
|
434
|
+
def set_element(x, indices, val):
|
|
435
|
+
for idx in indices[:-1]:
|
|
436
|
+
x = x[idx]
|
|
437
|
+
x[indices[-1]] = val
|
|
438
|
+
for i in range(total):
|
|
439
|
+
indices = [0] * len(shape)
|
|
440
|
+
rem = i
|
|
441
|
+
for d in reversed(range(len(shape))):
|
|
442
|
+
indices[d] = rem % shape[d]
|
|
443
|
+
rem //= shape[d]
|
|
444
|
+
set_element(out_structure, indices, flat[i])
|
|
445
|
+
return out_structure
|
|
446
|
+
def gen2(eq, w):
|
|
447
|
+
def from_treenode(eq,keep_integer=False):
|
|
448
|
+
nonlocal w
|
|
449
|
+
alter = {"f_wadd":"matadd", "f_transpose":"transpose", "f_diag":"diag", "f_identity":"identity","f_wmul":"matmul",\
|
|
450
|
+
"f_hadamard":"hadamard","f_commutation":"commutation","f_kronecker":"kronecker",\
|
|
451
|
+
"f_conv":"conv", "f_im2col":"im2col","f_col2im":"col2im", "f_flatten":"flatten",\
|
|
452
|
+
"f_sigmoid":"sigmoid", "f_vec":"vec"\
|
|
453
|
+
}
|
|
454
|
+
if eq.name in alter.keys():
|
|
455
|
+
return alter[eq.name]+"("+",".join([from_treenode(child,keep_integer) for child in eq.children])+")"
|
|
456
|
+
if eq.name in ["f_reshape", "f_broadcast"]:
|
|
457
|
+
return eq.name[2:]+"("+",".join([from_treenode(eq.children[0])]+\
|
|
458
|
+
[from_treenode(child,True) for child in eq.children[1:]])+")"
|
|
459
|
+
if eq.name in ["f_zeros","f_len"]:
|
|
460
|
+
return eq.name[2:]+"("+",".join([from_treenode(child,True) for child in eq.children])+")"
|
|
461
|
+
alter2 = ["f_add", "f_pow", "f_mul"]
|
|
462
|
+
if eq.name in alter2:
|
|
463
|
+
return f"TreeNode2('{eq.name}',["+",".join([from_treenode(child,keep_integer) for child in eq.children])+"])"
|
|
464
|
+
if not keep_integer and eq.name in [parse("m").name, parse("n").name]:
|
|
465
|
+
return "TreeNode2(f'd_{m}',[])"
|
|
466
|
+
if eq.name == "f_index":
|
|
467
|
+
return from_treenode(eq.children[0],keep_integer)+"["+str(eq.children[1])+"]"
|
|
468
|
+
if eq in w:
|
|
469
|
+
return f"w[{w.index(eq)}]"
|
|
470
|
+
if not keep_integer and eq.name.startswith("d_"):
|
|
471
|
+
return f"TreeNode2('d_{eq.name[2:]}',[])"
|
|
472
|
+
return str(eq)
|
|
473
|
+
return from_treenode(eq)
|
|
474
|
+
def gpu_var(*shape):
|
|
475
|
+
if len(shape) == 0:
|
|
476
|
+
TreeNode2.count += 1
|
|
477
|
+
return TreeNode2(f"v_{TreeNode2.count-1}", [])
|
|
478
|
+
return [gpu_var(*shape[1:]) for _ in range(shape[0])]
|
|
479
|
+
def gpu_var_x(*shape):
|
|
480
|
+
if len(shape) == 0:
|
|
481
|
+
TreeNode2.count_x += 1
|
|
482
|
+
return TreeNode2(f"x_{TreeNode2.count_x-1}", [])
|
|
483
|
+
return [gpu_var_x(*shape[1:]) for _ in range(shape[0])]
|
|
484
|
+
def gpu_var_y(*shape):
|
|
485
|
+
if len(shape) == 0:
|
|
486
|
+
TreeNode2.count_y += 1
|
|
487
|
+
return TreeNode2(f"y_{TreeNode2.count_y-1}", [])
|
|
488
|
+
return [gpu_var_y(*shape[1:]) for _ in range(shape[0])]
|
|
489
|
+
def gpu_var_write(content,*shape):
|
|
490
|
+
def gpu_var_helper(*shape):
|
|
491
|
+
if len(shape) == 0:
|
|
492
|
+
return content.pop(0)
|
|
493
|
+
return [gpu_var_helper(*shape[1:]) for _ in range(shape[0])]
|
|
494
|
+
return gpu_var_helper(*shape)
|
|
495
|
+
def flatten_list(nested_list):
|
|
496
|
+
flat = []
|
|
497
|
+
def _traverse(items):
|
|
498
|
+
for item in items:
|
|
499
|
+
if isinstance(item, list):
|
|
500
|
+
_traverse(item)
|
|
501
|
+
else:
|
|
502
|
+
flat.append(item)
|
|
503
|
+
_traverse(nested_list)
|
|
504
|
+
return flat
|
|
505
|
+
def shape(nested_list):
|
|
506
|
+
if not isinstance(nested_list, list):
|
|
507
|
+
return []
|
|
508
|
+
return [len(nested_list)] + shape(nested_list[0])
|
|
509
|
+
def flatten_list2(nested_list):
|
|
510
|
+
flat = []
|
|
511
|
+
def _traverse(items):
|
|
512
|
+
for item in items:
|
|
513
|
+
if isinstance(item, list):
|
|
514
|
+
_traverse(item)
|
|
515
|
+
else:
|
|
516
|
+
flat.append(item)
|
|
517
|
+
output = []
|
|
518
|
+
for item in nested_list:
|
|
519
|
+
flat = []
|
|
520
|
+
_traverse(item)
|
|
521
|
+
output += flat
|
|
522
|
+
return output
|
|
523
|
+
|
|
524
|
+
def _t_const(k):
|
|
525
|
+
return TreeNode2(f"d_{k}", [])
|
|
526
|
+
def _t_add(terms):
|
|
527
|
+
terms = [t for t in terms if t.name != "d_0"]
|
|
528
|
+
if not terms:
|
|
529
|
+
return _t_const(0)
|
|
530
|
+
r = terms[0]
|
|
531
|
+
for t in terms[1:]:
|
|
532
|
+
r = TreeNode2("f_add", [r, t])
|
|
533
|
+
return r
|
|
534
|
+
def _t_mul(factors):
|
|
535
|
+
r = factors[0]
|
|
536
|
+
for f in factors[1:]:
|
|
537
|
+
r = TreeNode2("f_mul", [r, f])
|
|
538
|
+
return r
|
|
539
|
+
def _diff_rule(node, dch, var):
|
|
540
|
+
name = node.name
|
|
541
|
+
ch = node.children or []
|
|
542
|
+
if not ch:
|
|
543
|
+
return _t_const(1) if name == var else _t_const(0)
|
|
544
|
+
if name == "f_add":
|
|
545
|
+
return _t_add(dch)
|
|
546
|
+
if name == "f_mul":
|
|
547
|
+
terms = []
|
|
548
|
+
for i in range(len(ch)):
|
|
549
|
+
if dch[i].name == "d_0":
|
|
550
|
+
continue
|
|
551
|
+
terms.append(_t_mul([dch[i]] + ch[:i] + ch[i+1:]))
|
|
552
|
+
return _t_add(terms)
|
|
553
|
+
if name == "f_pow":
|
|
554
|
+
base, expo = ch
|
|
555
|
+
if not expo.name.startswith("d_"):
|
|
556
|
+
raise NotImplementedError("derivative of a pow with a non-constant exponent is not supported")
|
|
557
|
+
k = int(expo.name[2:])
|
|
558
|
+
if k == 0 or dch[0].name == "d_0":
|
|
559
|
+
return _t_const(0)
|
|
560
|
+
if k == 1:
|
|
561
|
+
return dch[0]
|
|
562
|
+
return _t_mul([_t_const(k), TreeNode2("f_pow", [base, _t_const(k-1)]), dch[0]])
|
|
563
|
+
if name == "f_sigmoid":
|
|
564
|
+
if dch[0].name == "d_0":
|
|
565
|
+
return _t_const(0)
|
|
566
|
+
one_minus = TreeNode2("f_add", [_t_const(1), TreeNode2("f_mul", [_t_const(-1), node])])
|
|
567
|
+
return _t_mul([node, one_minus, dch[0]])
|
|
568
|
+
if name == "f_exp":
|
|
569
|
+
if dch[0].name == "d_0":
|
|
570
|
+
return _t_const(0)
|
|
571
|
+
return _t_mul([node, dch[0]])
|
|
572
|
+
if name == "f_neg":
|
|
573
|
+
return _t_mul([_t_const(-1), dch[0]])
|
|
574
|
+
if name == "f_div":
|
|
575
|
+
a, b = ch
|
|
576
|
+
t1 = _t_mul([dch[0], TreeNode2("f_pow", [b, _t_const(-1)])])
|
|
577
|
+
t2 = _t_mul([_t_const(-1), a, dch[1], TreeNode2("f_pow", [b, _t_const(-2)])])
|
|
578
|
+
return _t_add([t1, t2])
|
|
579
|
+
raise NotImplementedError(f"no derivative rule for {name}")
|
|
580
|
+
def diff_node(root, var, memo):
|
|
581
|
+
stack = [root]
|
|
582
|
+
while stack:
|
|
583
|
+
node = stack[-1]
|
|
584
|
+
if id(node) in memo:
|
|
585
|
+
stack.pop()
|
|
586
|
+
continue
|
|
587
|
+
kids = node.children or []
|
|
588
|
+
pending = [c for c in kids if id(c) not in memo]
|
|
589
|
+
if pending:
|
|
590
|
+
stack.extend(pending)
|
|
591
|
+
continue
|
|
592
|
+
stack.pop()
|
|
593
|
+
memo[id(node)] = _diff_rule(node, [memo[id(c)] for c in kids], var)
|
|
594
|
+
return memo[id(root)]
|
|
595
|
+
|
|
596
|
+
_DAG_OPS = {"f_add":"add", "f_mul":"mul", "f_pow":"pow", "f_div":"div",
|
|
597
|
+
"f_exp":"exp", "f_neg":"neg", "f_sigmoid":"sigmoid"}
|
|
598
|
+
def compile_dag(roots):
|
|
599
|
+
index = {}
|
|
600
|
+
prog = []
|
|
601
|
+
for root in roots:
|
|
602
|
+
stack = [root]
|
|
603
|
+
while stack:
|
|
604
|
+
node = stack[-1]
|
|
605
|
+
if id(node) in index:
|
|
606
|
+
stack.pop()
|
|
607
|
+
continue
|
|
608
|
+
kids = node.children or []
|
|
609
|
+
pending = [c for c in kids if id(c) not in index]
|
|
610
|
+
if pending:
|
|
611
|
+
stack.extend(pending)
|
|
612
|
+
continue
|
|
613
|
+
stack.pop()
|
|
614
|
+
name = node.name
|
|
615
|
+
if not kids:
|
|
616
|
+
if name.startswith("d_"):
|
|
617
|
+
entry = ("const", float(name[2:]))
|
|
618
|
+
elif name.startswith("v_"):
|
|
619
|
+
entry = ("A", int(name[2:]))
|
|
620
|
+
elif name.startswith("x_"):
|
|
621
|
+
entry = ("X", int(name[2:]))
|
|
622
|
+
elif name.startswith("y_"):
|
|
623
|
+
entry = ("Y", int(name[2:]))
|
|
624
|
+
else:
|
|
625
|
+
raise ValueError(f"cannot evaluate leaf {name}")
|
|
626
|
+
else:
|
|
627
|
+
if name not in _DAG_OPS:
|
|
628
|
+
raise ValueError(f"cannot evaluate node {name}")
|
|
629
|
+
entry = (_DAG_OPS[name], tuple(index[id(c)] for c in kids))
|
|
630
|
+
index[id(node)] = len(prog)
|
|
631
|
+
prog.append(entry)
|
|
632
|
+
return prog, [index[id(r)] for r in roots]
|
|
633
|
+
def sigmoid_num(x):
|
|
634
|
+
return 0.5 * (1.0 + np.tanh(0.5 * x))
|
|
635
|
+
def run_dag(prog, outs, A, X, Y):
|
|
636
|
+
vals = [0.0] * len(prog)
|
|
637
|
+
with np.errstate(all="ignore"):
|
|
638
|
+
for i, (op, arg) in enumerate(prog):
|
|
639
|
+
if op == "const":
|
|
640
|
+
vals[i] = arg
|
|
641
|
+
elif op == "A":
|
|
642
|
+
vals[i] = A[arg]
|
|
643
|
+
elif op == "X":
|
|
644
|
+
vals[i] = X[arg]
|
|
645
|
+
elif op == "Y":
|
|
646
|
+
vals[i] = Y[arg]
|
|
647
|
+
elif op == "add":
|
|
648
|
+
s = 0.0
|
|
649
|
+
for k in arg:
|
|
650
|
+
s += vals[k]
|
|
651
|
+
vals[i] = s
|
|
652
|
+
elif op == "mul":
|
|
653
|
+
p = 1.0
|
|
654
|
+
for k in arg:
|
|
655
|
+
p *= vals[k]
|
|
656
|
+
vals[i] = p
|
|
657
|
+
elif op == "pow":
|
|
658
|
+
vals[i] = np.power(vals[arg[0]], vals[arg[1]])
|
|
659
|
+
elif op == "div":
|
|
660
|
+
vals[i] = np.divide(vals[arg[0]], vals[arg[1]])
|
|
661
|
+
elif op == "exp":
|
|
662
|
+
vals[i] = np.exp(vals[arg[0]])
|
|
663
|
+
elif op == "neg":
|
|
664
|
+
vals[i] = -vals[arg[0]]
|
|
665
|
+
elif op == "sigmoid":
|
|
666
|
+
vals[i] = sigmoid_num(vals[arg[0]])
|
|
667
|
+
return [float(vals[k]) for k in outs]
|
|
668
|
+
|
|
669
|
+
class NeuralNetworkDirect:
|
|
670
|
+
def __init__(self, struct):
|
|
671
|
+
self.struct = struct
|
|
672
|
+
self.var_list = [tree_form(f"v_-{i}") for i in range(1,26+1-4)]
|
|
673
|
+
self.o = None
|
|
674
|
+
self.lst_w = None
|
|
675
|
+
self.gradient = None
|
|
676
|
+
self.weight_equation = None
|
|
677
|
+
self.hessian_equation = None
|
|
678
|
+
self.critical_points = None
|
|
679
|
+
self.critical_point_info = None
|
|
680
|
+
self.learn = None
|
|
681
|
+
self.learn_sym = None
|
|
682
|
+
self.learn_var = None
|
|
683
|
+
self.loss = None
|
|
684
|
+
self.model_type = None
|
|
685
|
+
self.init_mat = lambda *args: gpu_var(*args)
|
|
686
|
+
self._eq_key = None
|
|
687
|
+
self._flat_grad = None
|
|
688
|
+
self._grad_prog = None
|
|
689
|
+
self._grad_outs = None
|
|
690
|
+
self._hess_prog = None
|
|
691
|
+
self._hess_outs = None
|
|
692
|
+
self.loss_equation = None
|
|
693
|
+
self._loss_prog = None
|
|
694
|
+
self._loss_outs = None
|
|
695
|
+
self.minima = None
|
|
696
|
+
self.num_gradients = 0
|
|
697
|
+
self.diff_matrix_count = 0
|
|
698
|
+
self.weight_equation_count = 0
|
|
699
|
+
TreeNode2.count = 0
|
|
700
|
+
TreeNode2.count_x = 0
|
|
701
|
+
TreeNode2.count_y = 0
|
|
702
|
+
def model(self, t="image"):
|
|
703
|
+
self.model_type = t
|
|
704
|
+
if t == "image":
|
|
705
|
+
return self.model_image()
|
|
706
|
+
def model_image(self):
|
|
707
|
+
lst_z = []
|
|
708
|
+
lst_w = []
|
|
709
|
+
x = parse("X")
|
|
710
|
+
y = parse("Y")
|
|
711
|
+
lst_z.append(x)
|
|
712
|
+
flattening_needed = False
|
|
713
|
+
for i in range(1,len(self.struct)):
|
|
714
|
+
if self.struct[i]["type"] == "convolution":
|
|
715
|
+
flattening_needed = True
|
|
716
|
+
lst_w.append(self.var_list.pop(0))
|
|
717
|
+
eq = TreeNode("f_broadcast", [lst_w[-1], parse("m")])
|
|
718
|
+
tmp = TreeNode("f_conv", [lst_z[-1], eq])
|
|
719
|
+
tmp = replace(self.struct[i]["activation"], parse("Z"), tmp)
|
|
720
|
+
lst_z[-1] = tmp
|
|
721
|
+
elif self.struct[i]["type"] == "dense":
|
|
722
|
+
lst_w.append(self.var_list.pop(0))
|
|
723
|
+
lst_w.append(self.var_list.pop(0))
|
|
724
|
+
if flattening_needed:
|
|
725
|
+
flattening_needed = False
|
|
726
|
+
lst_z[-1] = lst_z[-1].fx("flatten")
|
|
727
|
+
eq = TreeNode("f_wmul", [lst_z[-1], lst_w[-2]])
|
|
728
|
+
eq = TreeNode("f_wadd", [eq, TreeNode("f_broadcast", [lst_w[-1], parse("m")])])
|
|
729
|
+
lst_z.append(eq)
|
|
730
|
+
tmp = replace(self.struct[i]["activation"], parse("Z"), lst_z[-1])
|
|
731
|
+
lst_z[-1] = tmp
|
|
732
|
+
self.lst_w = lst_w
|
|
733
|
+
self.o = lst_z[-1]
|
|
734
|
+
eq = TreeNode("f_hadamard", [tree_form("d_-1"), y])
|
|
735
|
+
eq = TreeNode("f_wadd", [self.o, eq])
|
|
736
|
+
eq = eq.fx("vec")
|
|
737
|
+
eq = TreeNode("f_wmul", [eq.fx("transpose"), eq])
|
|
738
|
+
L = TreeNode("f_hadamard", [eq, (parse("m") * tree_form("d_2")) ** tree_form("d_-1")])
|
|
739
|
+
self.loss = L
|
|
740
|
+
gradient = []
|
|
741
|
+
index = 0
|
|
742
|
+
self.learn = []
|
|
743
|
+
for i in range(1,len(self.struct)):
|
|
744
|
+
if self.struct[i]["type"] == "dense":
|
|
745
|
+
prev = None
|
|
746
|
+
if self.struct[i-1]["type"] == "convolution":
|
|
747
|
+
a,b,c = self.struct[0]["dim"]
|
|
748
|
+
for j in range(1,i):
|
|
749
|
+
d,e,_ = self.struct[j]["dim"]
|
|
750
|
+
a,b = a-d + 1, b-e + 1
|
|
751
|
+
prev = a*b
|
|
752
|
+
else:
|
|
753
|
+
prev = self.struct[i-1]["dim"][0]
|
|
754
|
+
curr = self.struct[i]["dim"][0]
|
|
755
|
+
self.learn.append(self.init_mat(prev, curr))
|
|
756
|
+
self.learn.append(self.init_mat(1, curr))
|
|
757
|
+
elif self.struct[i]["type"] == "convolution":
|
|
758
|
+
self.learn.append(self.init_mat(*([1]+self.struct[i]["dim"])))
|
|
759
|
+
self.learn_sym = self.learn
|
|
760
|
+
self.num_gradients = len(lst_w)
|
|
761
|
+
self.diff_matrix_count = 0
|
|
762
|
+
print(f"gradients to calculate: {self.num_gradients}")
|
|
763
|
+
for j in range(len(lst_w)):
|
|
764
|
+
item = lst_w[j]
|
|
765
|
+
tmp = TreeNode("f_pdif", [L, item.fx("vec")])
|
|
766
|
+
tmp = diff_matrix_matrix(tmp)
|
|
767
|
+
self.diff_matrix_count += 1
|
|
768
|
+
print(f"diff_matrix_matrix {self.diff_matrix_count}/{self.num_gradients} done")
|
|
769
|
+
d = []
|
|
770
|
+
k = self.learn[j]
|
|
771
|
+
while isinstance(k, list):
|
|
772
|
+
d.append(len(k))
|
|
773
|
+
k = k[0]
|
|
774
|
+
tmp = TreeNode("f_reshape", [tmp]+[tree_form(f"d_{item}") for item in d])
|
|
775
|
+
gradient.append(tmp)
|
|
776
|
+
self.gradient = gradient
|
|
777
|
+
return self
|
|
778
|
+
def predict(self, given_x):
|
|
779
|
+
global hadamard, zeros, transpose, matadd, sigmoid, identity, vec, diag, flatten,\
|
|
780
|
+
matmul, reshape, kronecker, commutation, broadcast, im2col, conv, col2im, extract_values, shape
|
|
781
|
+
TreeNode2.count = 0
|
|
782
|
+
TreeNode2.count_x = 0
|
|
783
|
+
TreeNode2.count_y = 0
|
|
784
|
+
learn2 = []
|
|
785
|
+
for item in self.learn:
|
|
786
|
+
learn2.append(gpu_var(*shape(item)))
|
|
787
|
+
given_x = [given_x]
|
|
788
|
+
given2 = gpu_var_x(*shape(given_x))
|
|
789
|
+
env = {
|
|
790
|
+
"w": learn2,
|
|
791
|
+
"m": 1,
|
|
792
|
+
"identity":identity,
|
|
793
|
+
"X": given2,
|
|
794
|
+
"zeros":zeros,
|
|
795
|
+
"hadamard": hadamard,
|
|
796
|
+
"sigmoid":sigmoid,
|
|
797
|
+
"matadd": matadd,
|
|
798
|
+
"diag":diag,
|
|
799
|
+
"vec":vec,
|
|
800
|
+
"matmul":matmul,
|
|
801
|
+
"reshape":reshape,
|
|
802
|
+
"kronecker":kronecker,
|
|
803
|
+
"commutation":commutation,
|
|
804
|
+
"transpose":transpose,
|
|
805
|
+
"broadcast":broadcast,
|
|
806
|
+
"conv":conv,
|
|
807
|
+
"im2col":im2col,
|
|
808
|
+
"col2im": col2im,
|
|
809
|
+
"flatten":flatten
|
|
810
|
+
}
|
|
811
|
+
if self.model_type == "image":
|
|
812
|
+
tmp = gen2(self.o, self.lst_w)
|
|
813
|
+
eq = eval(tmp, {}, env)[0]
|
|
814
|
+
s = "fx = lambda A,X: "+str(eq)
|
|
815
|
+
env2 = {
|
|
816
|
+
"exp": np.exp,
|
|
817
|
+
"sigmoid": sigmoid_num
|
|
818
|
+
}
|
|
819
|
+
A_list = []
|
|
820
|
+
for item in self.learn:
|
|
821
|
+
A_list = A_list + flatten_list(item)
|
|
822
|
+
exec(s, env2)
|
|
823
|
+
return env2["fx"](A_list,flatten_list(given_x))
|
|
824
|
+
def build_equations(self, train_x, train_y, batch_size):
|
|
825
|
+
key = (batch_size, tuple(shape(train_x[:batch_size])), tuple(shape(train_y[:batch_size])))
|
|
826
|
+
if self._eq_key == key and self.weight_equation is not None:
|
|
827
|
+
return
|
|
828
|
+
TreeNode2.count_x = 0
|
|
829
|
+
TreeNode2.count_y = 0
|
|
830
|
+
data_x = gpu_var_x(*shape(train_x[:batch_size]))
|
|
831
|
+
data_y = gpu_var_y(*shape(train_y[:batch_size]))
|
|
832
|
+
env = {
|
|
833
|
+
"identity":identity,
|
|
834
|
+
"sigmoid":sigmoid,
|
|
835
|
+
"matadd": matadd,
|
|
836
|
+
"zeros":zeros,
|
|
837
|
+
"hadamard": hadamard,
|
|
838
|
+
"diag":diag,
|
|
839
|
+
"vec":vec,
|
|
840
|
+
"matmul":matmul,
|
|
841
|
+
"reshape":reshape,
|
|
842
|
+
"kronecker":kronecker,
|
|
843
|
+
"commutation":commutation,
|
|
844
|
+
"transpose":transpose,
|
|
845
|
+
"broadcast":broadcast,
|
|
846
|
+
"conv":conv,
|
|
847
|
+
"im2col":im2col,
|
|
848
|
+
"col2im":col2im,
|
|
849
|
+
"flatten":flatten,
|
|
850
|
+
"TreeNode2":TreeNode2,
|
|
851
|
+
"m":batch_size,
|
|
852
|
+
"w":self.learn_sym,
|
|
853
|
+
"X":data_x,
|
|
854
|
+
"Y":data_y
|
|
855
|
+
}
|
|
856
|
+
self.weight_equation_count = 0
|
|
857
|
+
print(f"gradients: {self.num_gradients}, diff_matrix_matrix done: {self.diff_matrix_count}/{self.num_gradients}")
|
|
858
|
+
weight_equation = []
|
|
859
|
+
for g in self.gradient:
|
|
860
|
+
weight_equation.append(eval(gen2(g, self.lst_w), {}, env))
|
|
861
|
+
self.weight_equation_count += 1
|
|
862
|
+
print(f"weight equation {self.weight_equation_count}/{self.num_gradients} done")
|
|
863
|
+
self.weight_equation = weight_equation
|
|
864
|
+
self._flat_grad = flatten_list(self.weight_equation)
|
|
865
|
+
n_weights = len(flatten_list(self.learn_sym))
|
|
866
|
+
assert len(self._flat_grad) == n_weights, "gradient size does not match number of weights"
|
|
867
|
+
self._grad_prog, self._grad_outs = compile_dag(self._flat_grad)
|
|
868
|
+
loss_flat = flatten_list(eval(gen2(self.loss, self.lst_w), {}, env))
|
|
869
|
+
assert len(loss_flat) == 1, "loss must be a scalar"
|
|
870
|
+
self.loss_equation = loss_flat[0]
|
|
871
|
+
self._loss_prog, self._loss_outs = compile_dag([self.loss_equation])
|
|
872
|
+
self.hessian_equation = None
|
|
873
|
+
self._hess_prog = None
|
|
874
|
+
self._hess_outs = None
|
|
875
|
+
self.critical_points = None
|
|
876
|
+
self.critical_point_info = None
|
|
877
|
+
self._eq_key = key
|
|
878
|
+
def _eval_gradient(self, A, X, Y):
|
|
879
|
+
return run_dag(self._grad_prog, self._grad_outs, A, X, Y)
|
|
880
|
+
def loss_at(self, A, X, Y):
|
|
881
|
+
return run_dag(self._loss_prog, self._loss_outs, A, X, Y)[0]
|
|
882
|
+
def _unflatten(self, flat):
|
|
883
|
+
it = iter(flat)
|
|
884
|
+
def build(shp):
|
|
885
|
+
if not shp:
|
|
886
|
+
return next(it)
|
|
887
|
+
return [build(shp[1:]) for _ in range(shp[0])]
|
|
888
|
+
return [build(shape(item)) for item in self.learn_sym]
|
|
889
|
+
def build_hessian(self):
|
|
890
|
+
if self._flat_grad is None:
|
|
891
|
+
raise RuntimeError("call build_equations / find_critical_points / train first")
|
|
892
|
+
flat = self._flat_grad
|
|
893
|
+
N = len(flat)
|
|
894
|
+
H = [[None] * N for _ in range(N)]
|
|
895
|
+
step = max(1, N // 10)
|
|
896
|
+
print(f"building hessian: {N} x {N}")
|
|
897
|
+
for j in range(N):
|
|
898
|
+
if j % step == 0:
|
|
899
|
+
print(f"hessian column {j}/{N}")
|
|
900
|
+
memo = {}
|
|
901
|
+
var = f"v_{j}"
|
|
902
|
+
for i in range(j + 1):
|
|
903
|
+
H[i][j] = diff_node(flat[i], var, memo)
|
|
904
|
+
H[j][i] = H[i][j]
|
|
905
|
+
self.hessian_equation = H
|
|
906
|
+
roots = [H[i][j] for j in range(N) for i in range(j + 1)]
|
|
907
|
+
self._hess_prog, self._hess_outs = compile_dag(roots)
|
|
908
|
+
return H
|
|
909
|
+
def hessian_at(self, A, X, Y):
|
|
910
|
+
if self._hess_prog is None:
|
|
911
|
+
raise RuntimeError("call build_hessian() first")
|
|
912
|
+
vals = run_dag(self._hess_prog, self._hess_outs, A, X, Y)
|
|
913
|
+
N = len(self._flat_grad)
|
|
914
|
+
Hn = np.empty((N, N))
|
|
915
|
+
k = 0
|
|
916
|
+
for j in range(N):
|
|
917
|
+
for i in range(j + 1):
|
|
918
|
+
Hn[i, j] = Hn[j, i] = vals[k]
|
|
919
|
+
k += 1
|
|
920
|
+
return Hn
|
|
921
|
+
def _newton(self, x, X, Y, grad_tol, max_iter):
|
|
922
|
+
g = np.array(self._eval_gradient(x.tolist(), X, Y))
|
|
923
|
+
for it in range(max_iter):
|
|
924
|
+
if not np.all(np.isfinite(g)):
|
|
925
|
+
return x, it, False
|
|
926
|
+
gnorm = np.linalg.norm(g, np.inf)
|
|
927
|
+
if gnorm < grad_tol:
|
|
928
|
+
return x, it, True
|
|
929
|
+
Hn = self.hessian_at(x.tolist(), X, Y)
|
|
930
|
+
if not np.all(np.isfinite(Hn)):
|
|
931
|
+
return x, it, False
|
|
932
|
+
try:
|
|
933
|
+
dx = np.linalg.solve(Hn, -g)
|
|
934
|
+
except np.linalg.LinAlgError:
|
|
935
|
+
dx = np.linalg.lstsq(Hn, -g, rcond=None)[0]
|
|
936
|
+
t = 1.0
|
|
937
|
+
while True:
|
|
938
|
+
x_new = x + t * dx
|
|
939
|
+
g_new = np.array(self._eval_gradient(x_new.tolist(), X, Y))
|
|
940
|
+
if np.all(np.isfinite(g_new)) and np.linalg.norm(g_new, np.inf) < gnorm:
|
|
941
|
+
break
|
|
942
|
+
t *= 0.5
|
|
943
|
+
if t < 1e-10:
|
|
944
|
+
return x, it, False
|
|
945
|
+
x, g = x_new, g_new
|
|
946
|
+
return x, max_iter, bool(np.linalg.norm(g, np.inf) < grad_tol)
|
|
947
|
+
def find_critical_points(self, train_x, train_y, n_starts=50, init_scale=1.0, seed=None,
|
|
948
|
+
grad_tol=1e-8, dedup_tol=1e-5, max_iter=100):
|
|
949
|
+
batch_size = len(train_x)
|
|
950
|
+
self.build_equations(train_x, train_y, batch_size)
|
|
951
|
+
if self._hess_prog is None:
|
|
952
|
+
self.build_hessian()
|
|
953
|
+
X = flatten_list(train_x)
|
|
954
|
+
Y = flatten_list(train_y)
|
|
955
|
+
N = len(self._flat_grad)
|
|
956
|
+
rng = np.random.default_rng(seed)
|
|
957
|
+
found = []
|
|
958
|
+
for s in range(n_starts):
|
|
959
|
+
x0 = rng.normal(0.0, init_scale, N)
|
|
960
|
+
x, iters, ok = self._newton(x0, X, Y, grad_tol, max_iter)
|
|
961
|
+
print(f"newton start {s+1}/{n_starts}: {'converged' if ok else 'failed'} after {iters} iterations")
|
|
962
|
+
if not ok:
|
|
963
|
+
continue
|
|
964
|
+
if any(np.linalg.norm(x - p) <= dedup_tol * (1.0 + np.linalg.norm(p)) for p in found):
|
|
965
|
+
continue
|
|
966
|
+
found.append(x)
|
|
967
|
+
self.critical_points = found
|
|
968
|
+
return found
|
|
969
|
+
def classify_critical_points(self, train_x, train_y, points=None, eig_tol=1e-6):
|
|
970
|
+
batch_size = len(train_x)
|
|
971
|
+
self.build_equations(train_x, train_y, batch_size)
|
|
972
|
+
if self._hess_prog is None:
|
|
973
|
+
self.build_hessian()
|
|
974
|
+
if points is None:
|
|
975
|
+
points = self.critical_points
|
|
976
|
+
X = flatten_list(train_x)
|
|
977
|
+
Y = flatten_list(train_y)
|
|
978
|
+
results = []
|
|
979
|
+
for p in points:
|
|
980
|
+
Hn = self.hessian_at(np.asarray(p).tolist(), X, Y)
|
|
981
|
+
eig = np.linalg.eigvalsh(Hn)
|
|
982
|
+
thr = eig_tol * max(1.0, float(np.max(np.abs(eig))))
|
|
983
|
+
if eig.min() > thr:
|
|
984
|
+
kind = "local minimum"
|
|
985
|
+
elif eig.max() < -thr:
|
|
986
|
+
kind = "local maximum"
|
|
987
|
+
elif eig.min() < -thr and eig.max() > thr:
|
|
988
|
+
kind = "saddle point"
|
|
989
|
+
else:
|
|
990
|
+
kind = "inconclusive (singular Hessian)"
|
|
991
|
+
grad_norm = float(np.linalg.norm(self._eval_gradient(np.asarray(p).tolist(), X, Y), np.inf))
|
|
992
|
+
results.append({"point": np.asarray(p), "type": kind, "eigenvalues": eig,
|
|
993
|
+
"grad_norm": grad_norm,
|
|
994
|
+
"loss": self.loss_at(np.asarray(p).tolist(), X, Y)})
|
|
995
|
+
self.critical_point_info = results
|
|
996
|
+
return results
|
|
997
|
+
def analyze_critical_points(self, train_x, train_y, **find_kwargs):
|
|
998
|
+
points = self.find_critical_points(train_x, train_y, **find_kwargs)
|
|
999
|
+
return self.classify_critical_points(train_x, train_y, points)
|
|
1000
|
+
def train(self, train_x, train_y, check_hessian=False, **find_kwargs):
|
|
1001
|
+
points = self.find_critical_points(train_x, train_y, **find_kwargs)
|
|
1002
|
+
if not points:
|
|
1003
|
+
raise RuntimeError("no critical point found; try a larger n_starts or a different init_scale")
|
|
1004
|
+
if check_hessian:
|
|
1005
|
+
results = [r for r in self.classify_critical_points(train_x, train_y, points)
|
|
1006
|
+
if r["type"] == "local minimum"]
|
|
1007
|
+
else:
|
|
1008
|
+
X = flatten_list(train_x)
|
|
1009
|
+
Y = flatten_list(train_y)
|
|
1010
|
+
results = [{"point": p, "loss": self.loss_at(p.tolist(), X, Y)} for p in points]
|
|
1011
|
+
results.sort(key=lambda r: r["loss"])
|
|
1012
|
+
print(f"{len(points)} critical points found, {len(results)} kept")
|
|
1013
|
+
if not results:
|
|
1014
|
+
raise RuntimeError("no local minimum passed the Hessian test")
|
|
1015
|
+
self.minima = results
|
|
1016
|
+
self.learn = self._unflatten(results[0]["point"].tolist())
|
|
1017
|
+
return results
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mathai
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.4
|
|
4
4
|
Summary: Mathematics solving Ai tailored to NCERT
|
|
5
5
|
Home-page: https://github.com/infinity390/mathai
|
|
6
6
|
Requires-Python: >=3.7
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
8
9
|
Dynamic: description
|
|
9
10
|
Dynamic: description-content-type
|
|
10
11
|
Dynamic: home-page
|
|
12
|
+
Dynamic: requires-dist
|
|
11
13
|
Dynamic: requires-python
|
|
12
14
|
Dynamic: summary
|
|
13
15
|
|
|
@@ -22,6 +22,7 @@ mathai/linear.py
|
|
|
22
22
|
mathai/logic.py
|
|
23
23
|
mathai/matrix.py
|
|
24
24
|
mathai/ml.py
|
|
25
|
+
mathai/mldirect.py
|
|
25
26
|
mathai/mlscalar.py
|
|
26
27
|
mathai/ncert_data.txt
|
|
27
28
|
mathai/ncert_data_optimized.txt
|
|
@@ -40,4 +41,5 @@ mathai/univariate_inequality.py
|
|
|
40
41
|
mathai.egg-info/PKG-INFO
|
|
41
42
|
mathai.egg-info/SOURCES.txt
|
|
42
43
|
mathai.egg-info/dependency_links.txt
|
|
44
|
+
mathai.egg-info/requires.txt
|
|
43
45
|
mathai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy
|
|
@@ -3,12 +3,15 @@ from setuptools import setup, find_packages
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
5
|
name="mathai",
|
|
6
|
-
version="2.5.
|
|
6
|
+
version="2.5.4",
|
|
7
7
|
description="Mathematics solving Ai tailored to NCERT",
|
|
8
8
|
long_description=Path("README.md").read_text(encoding="utf-8"),
|
|
9
9
|
long_description_content_type="text/markdown",
|
|
10
10
|
url="https://github.com/infinity390/mathai",
|
|
11
11
|
packages=find_packages(),
|
|
12
|
+
install_requires=[
|
|
13
|
+
"numpy",
|
|
14
|
+
],
|
|
12
15
|
include_package_data=True,
|
|
13
16
|
package_data={
|
|
14
17
|
"mathai": ["ncert_data.txt", "ncert_data_optimized.txt"],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|