├── comp_graph.png
├── grad
├── __init__.py
├── variable.py
└── gate.py
├── README.md
└── nn.ipynb
/comp_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlisaLC/GraDino/HEAD/comp_graph.png
--------------------------------------------------------------------------------
/grad/__init__.py:
--------------------------------------------------------------------------------
1 | from . import variable as vp
2 | from .variable import Variable as vn
3 |
4 | class no_grad:
5 | def __init__(self):
6 | pass
7 |
8 | def __enter__(self):
9 | vp.is_grad_enabled = False
10 |
11 | def __exit__(self, exc_type, exc_value, traceback):
12 | vp.is_grad_enabled = True
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraDino
2 | an autograd package in python. GraDino variables can be used in lists, tuples or even `numpy` arrays!
3 | gradients are calculated using backpropagation. examples of linear regression and XOR neural network is in the `nn.ipynb` notebook.
4 | ## Basic Usage
5 | a `Variable` can be defined using a `float` or `int` datatype. every operation on a `Variable` results in another `Variable`. finally when `backward` method is called on a `Variable`. every `Variable`'s gradient is calculated using backpropagation and stored in `grad` property.
6 | ```python
7 | import grad
8 | from grad.variable import Variable as vn
9 |
10 | x = vn(1.0, requires_grad=True)
11 | y = vn(2.0, requires_grad=True)
12 | z = (x - y) ** 2
13 | z.backward()
14 | print(x.grad, y.grad) # -2.0 2.0
15 | ````
16 |
17 | ## Numpy Arrays
18 | an autograding array can be defined using `array` function in the module. it can work with lists and numpy arryas. to extract the gradients we can use `array_grad` function to extract the gradients.
19 | ```python
20 | import grad
21 | from grad import variable as vp
22 |
23 | x = vp.array(np.random.randn(3, 10))
24 | y = vp.array(np.random.randn(10, 3))
25 | z = np.mean((x - y.T) ** 2)
26 | z.backward()
27 | print(vp.array_grad(x))
28 | print(vp.array_grad(y))
29 | ```
30 | ## Gradient Calculation Techniques
31 | * if we want to define a variable where there is no need for backpropagation we can set `requires_grad` attribute to `False`.
32 | * to zero the calculated gradients after applying optimization in each iteration, `zero_grad` method should be called on each `Variable`. to apply this in an array we can use `array_zero_grad` function.
33 | * to disable computation graph during optimization, `with vp.no_grad():` can be used.
34 | * `zero_grad` can be called on the final product to reset the gradients recursively.
35 | ```python
36 | import grad
37 | from grad import variable as vp
38 |
39 | x = vn(1.0, requires_grad=True)
40 | y = vn(2.0, requires_grad=True)
41 | z = (x - y) ** 2
42 | z.backward()
43 | print(x.grad, y.grad) # -2.0 2.0
44 | z.zero_grad()
45 | print(x.grad, y.grad) # 0.0 0.0
46 | ```
47 | ## Computational Graph Drawing
48 | to draw the computational graph of a `Variable` we can use `draw_graph` function. it uses `graphviz` library to draw the graph. the graph is returned as a `graphviz.Digraph` object.
49 | ```python
50 | import grad
51 | from grad import variable as vp
52 |
53 | x = vn(1.0, requires_grad=True)
54 | y = vn(2.0, requires_grad=True)
55 | z = (x - y) ** 2
56 | z.backward()
57 | g = z.draw_graph()
58 | g.view()
59 | ```
60 |
61 |
62 |
63 |
64 | ## Higher Order Derivatives
65 | to calculate higher order derivatives we can use `backward` method multiple times. the gradients are accumulated in `grad` property. `make_graph` argument on `backward` must be set to `True` to make the computational graph. after each `backward` we `zero_grad` to reset the gradients.
66 | ```python
67 | import grad
68 | from grad import variable as vp
69 |
70 | x = vn(17.0, requires_grad=True)
71 | y = vn(13.0, requires_grad=True)
72 | z = (x - y) ** 2
73 | z.backward(make_graph=True)
74 | print(x.grad, y.grad) # 8.0 -8.0
75 | x_grad = x.grad # 2 * (x - y)
76 | z.zero_grad()
77 | x_grad.backward()
78 | print(x.grad, y.grad) # 2.0 -2.0
79 | ```
80 | when `make_graph` is set to `True` the computational graph is made and stored in `graph` property of `Variable`. we can use `draw_graph` method to draw the graph.
--------------------------------------------------------------------------------
/grad/variable.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from . import gate as g
3 | import graphviz
4 |
5 | is_grad_enabled = True
6 |
7 |
8 | class Variable:
9 | def __init__(self, data, gate=None, requires_grad=True):
10 | if isinstance(data, Variable):
11 | self = data
12 | return
13 | elif isinstance(data, (int, np.uint8, np.uint16, np.uint32, np.uint64, np.int8, np.int16, np.int32, np.int64)):
14 | data = int(data)
15 | elif isinstance(data, (float, np.float16, np.float32, np.float64)):
16 | data = float(data)
17 | else:
18 | raise TypeError('expected int or float, got ' +
19 | str(type(data)) + " instead")
20 | self.data = data
21 | self.grad = 0
22 | if gate is None or not is_grad_enabled:
23 | self.gate = g.Identity()
24 | else:
25 | self.gate = gate
26 | self.requires_grad = requires_grad
27 | self.is_graph = False
28 |
29 | def __repr__(self):
30 | return f"{self.data}"
31 |
32 | def __int__(self):
33 | return int(self.data)
34 |
35 | def __float__(self):
36 | return float(self.data)
37 |
38 | def __str__(self):
39 | return str(self.data)
40 |
41 | def __format__(self, *args, **kwargs):
42 | return self.data.__format__(*args, **kwargs)
43 |
44 | def backward(self, grad=None, make_graph=False):
45 | if not self.requires_grad or not is_grad_enabled:
46 | return
47 | if grad is None:
48 | if make_graph:
49 | grad = Variable(1, requires_grad=False)
50 | else:
51 | grad = 1
52 | self.grad += grad
53 | self.gate.backward(grad=grad, make_graph=make_graph)
54 |
55 | def draw_graph(self, graph=None):
56 | if graph is None:
57 | graph = graphviz.Digraph()
58 | if not self.is_graph:
59 | if self.gate is not None and self.gate.name == 'identity' and self.requires_grad:
60 | graph.node(str(id(self)), f'{self.data:.4g}', style='filled', fillcolor='lightblue')
61 | else:
62 | graph.node(str(id(self)), f'{self.data:.4g}')
63 | self.is_graph = True
64 | if self.gate is not None and self.gate.name != 'identity':
65 | graph.node(str(id(self.gate)), self.gate.name, style='filled', fillcolor='lightgreen')
66 | label = None
67 | if self.requires_grad:
68 | label = f'{self.grad:.4g}'
69 | graph.edge(str(id(self.gate)), str(id(self)), label=label)
70 | self.gate.draw_graph(graph)
71 | return graph
72 |
73 | def clear_graph(self):
74 | self.is_graph = False
75 | self.gate.clear_graph()
76 |
77 |
78 | def zero_grad(self):
79 | if not self.requires_grad or not is_grad_enabled:
80 | return
81 | self.grad = 0
82 | self.gate.zero_grad()
83 |
84 | def __eq__(self, other):
85 | if not isinstance(other, Variable):
86 | other = Variable(other, requires_grad=False)
87 | return self.data == other.data
88 |
89 | def __lt__(self, other):
90 | if not isinstance(other, Variable):
91 | other = Variable(other, requires_grad=False)
92 | return self.data < other.data
93 |
94 | def __gt__(self, other):
95 | if not isinstance(other, Variable):
96 | other = Variable(other, requires_grad=False)
97 | return self.data > other.data
98 |
99 | def __le__(self, other):
100 | if not isinstance(other, Variable):
101 | other = Variable(other, requires_grad=False)
102 | return self.data <= other.data
103 |
104 | def __ge__(self, other):
105 | if not isinstance(other, Variable):
106 | other = Variable(other, requires_grad=False)
107 | return self.data >= other.data
108 |
109 | def __ne__(self, other):
110 | if not isinstance(other, Variable):
111 | other = Variable(other, requires_grad=False)
112 | return self.data != other.data
113 |
114 | def __pos__(self):
115 | return self
116 |
117 | def __neg__(self):
118 | gate = g.Neg()
119 | return Variable(gate(self), gate=gate)
120 |
121 | def __abs__(self):
122 | gate = g.Abs()
123 | return Variable(gate(self), gate=gate)
124 |
125 | def __add__(self, other):
126 | if not isinstance(other, Variable):
127 | other = Variable(other, requires_grad=False)
128 | gate = g.Add()
129 | return Variable(gate(self, other), gate=gate)
130 |
131 | def __radd__(self, other):
132 | return self.__add__(other)
133 |
134 | def __sub__(self, other):
135 | if not isinstance(other, Variable):
136 | other = Variable(other, requires_grad=False)
137 | gate = g.Neg()
138 | other = Variable(gate(other), gate=gate)
139 | gate = g.Add()
140 | return Variable(gate(self, other), gate=gate)
141 |
142 | def __rsub__(self, other):
143 | if not isinstance(other, Variable):
144 | other = Variable(other, requires_grad=False)
145 | return other - self
146 |
147 | def __mul__(self, other):
148 | if not isinstance(other, Variable):
149 | other = Variable(other, requires_grad=False)
150 | gate = g.Mul()
151 | return Variable(gate(self, other), gate=gate)
152 |
153 | def __rmul__(self, other):
154 | return self.__mul__(other)
155 |
156 | def __truediv__(self, other):
157 | if not isinstance(other, Variable):
158 | other = Variable(other, requires_grad=False)
159 | gate = g.Div()
160 | return Variable(gate(self, other), gate=gate)
161 |
162 | def __rtruediv__(self, other):
163 | if not isinstance(other, Variable):
164 | other = Variable(other, requires_grad=False)
165 | return other / self
166 |
167 | def __pow__(self, other):
168 | if not isinstance(other, Variable):
169 | other = Variable(other, requires_grad=False)
170 | gate = g.Pow()
171 | return Variable(gate(self, other), gate=gate)
172 |
173 | def __rpow__(self, other):
174 | if not isinstance(other, Variable):
175 | other = Variable(other, requires_grad=False)
176 | return other ** self
177 |
178 | def sqrt(self):
179 | return self ** 0.5
180 |
181 | def sin(self):
182 | gate = g.Sin()
183 | return Variable(gate(self), gate=gate)
184 |
185 | def arcsin(self):
186 | gate = g.Asin()
187 | return Variable(gate(self), gate=gate)
188 |
189 | def sinh(self):
190 | gate = g.Sinh()
191 | return Variable(gate(self), gate=gate)
192 |
193 | def arcsinh(self):
194 | gate = g.Asinh()
195 | return Variable(gate(self), gate=gate)
196 |
197 | def cos(self):
198 | gate = g.Cos()
199 | return Variable(gate(self), gate=gate)
200 |
201 | def arccos(self):
202 | gate = g.Acos()
203 | return Variable(gate(self), gate=gate)
204 |
205 | def cosh(self):
206 | gate = g.Cosh()
207 | return Variable(gate(self), gate=gate)
208 |
209 | def arccosh(self):
210 | gate = g.Acosh()
211 | return Variable(gate(self), gate=gate)
212 |
213 | def tan(self):
214 | gate = g.Tan()
215 | return Variable(gate(self), gate=gate)
216 |
217 | def arctan(self):
218 | gate = g.Atan()
219 | return Variable(gate(self), gate=gate)
220 |
221 | def tanh(self):
222 | gate = g.Tanh()
223 | return Variable(gate(self), gate=gate)
224 |
225 | def arctanh(self):
226 | gate = g.Atanh()
227 | return Variable(gate(self), gate=gate)
228 |
229 | def exp(self):
230 | gate = g.Exp()
231 | return Variable(gate(self), gate=gate)
232 |
233 | def log(self):
234 | gate = g.Log()
235 | return Variable(gate(self), gate=gate)
236 |
237 | def conjugate(self):
238 | return self
239 |
240 |
241 | def array(data, requires_grad=True):
242 | if isinstance(data, Variable):
243 | return data
244 | if isinstance(data, (list, tuple)):
245 | return [array(x, requires_grad=requires_grad) for x in data]
246 | if isinstance(data, np.ndarray):
247 | return np.array([array(x, requires_grad=requires_grad) for x in data])
248 | return Variable(data, requires_grad=requires_grad)
249 |
250 |
251 | def array_grad(data):
252 | if isinstance(data, Variable):
253 | return data.grad
254 | if isinstance(data, (list, tuple)):
255 | return [array_grad(x) for x in data]
256 | if isinstance(data, np.ndarray):
257 | return np.array([array_grad(x) for x in data])
258 | return data
259 |
260 |
261 | def array_zero_grad(data):
262 | if isinstance(data, Variable):
263 | data.zero_grad()
264 | elif isinstance(data, (list, tuple)):
265 | for x in data:
266 | array_zero_grad(x)
267 | elif isinstance(data, np.ndarray):
268 | for x in data:
269 | array_zero_grad(x)
270 |
--------------------------------------------------------------------------------
/grad/gate.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import graphviz
3 |
4 |
5 | class Gate:
6 | def __init__(self, name):
7 | self.name = name
8 | self.vars = []
9 |
10 | def __repr__(self):
11 | return f"Gate({self.name})"
12 |
13 | def forward(self):
14 | raise NotImplementedError
15 |
16 | def backward(self, grad, make_graph=False):
17 | raise NotImplementedError
18 |
19 | def __call__(self, *args, **kwds):
20 | return self.forward(*args, **kwds)
21 |
22 | def draw_graph(self, graph):
23 | for var in self.vars:
24 | if not var.is_graph:
25 | if var.gate is not None and var.gate.name == 'identity' and var.requires_grad:
26 | graph.node(
27 | str(id(var)), f'{var.data:.4g}', style='filled', fillcolor='lightblue')
28 | else:
29 | graph.node(str(id(var)), f'{var.data:.4g}')
30 | var.is_graph = True
31 | label = None
32 | if var.requires_grad:
33 | label = f'{var.grad:.4g}'
34 | graph.edge(str(id(var)), str(id(self)), label=label)
35 | var.draw_graph(graph)
36 |
37 | def clear_graph(self):
38 | for var in self.vars:
39 | var.clear_graph()
40 |
41 | def zero_grad(self):
42 | for var in self.vars:
43 | var.zero_grad()
44 |
45 |
46 | class Identity(Gate):
47 | def __init__(self):
48 | super().__init__("identity")
49 |
50 | def forward(self, x):
51 | return x
52 |
53 | def backward(self, grad, make_graph=False):
54 | return grad
55 |
56 |
57 | class Add(Gate):
58 | def __init__(self):
59 | super().__init__("add")
60 |
61 | def forward(self, x, y):
62 | self.vars = [x, y]
63 | return x.data + y.data
64 |
65 | def backward(self, grad, make_graph=False):
66 | self.vars[0].backward(grad, make_graph=make_graph)
67 | self.vars[1].backward(grad, make_graph=make_graph)
68 |
69 |
70 | class Mul(Gate):
71 | def __init__(self):
72 | super().__init__("mul")
73 |
74 | def forward(self, x, y):
75 | self.vars = [x, y]
76 | return x.data * y.data
77 |
78 | def backward(self, grad, make_graph=False):
79 | var0, var1 = self.vars[0], self.vars[1]
80 | if not make_graph:
81 | var0, var1 = var0.data, var1.data
82 | self.vars[0].backward(grad * var1, make_graph=make_graph)
83 | self.vars[1].backward(grad * var0, make_graph=make_graph)
84 |
85 |
86 | class Neg(Gate):
87 | def __init__(self):
88 | super().__init__("neg")
89 |
90 | def forward(self, x):
91 | self.vars = [x]
92 | return -x.data
93 |
94 | def backward(self, grad, make_graph=False):
95 | self.vars[0].backward(-grad, make_graph=make_graph)
96 |
97 |
98 | class Abs(Gate):
99 | def __init__(self):
100 | super().__init__("abs")
101 |
102 | def forward(self, x):
103 | self.vars = [x]
104 | return abs(x.data)
105 |
106 | def backward(self, grad, make_graph=False):
107 | self.vars[0].backward(
108 | grad * (-1 if self.vars[0].data < 0 else 1), make_graph=make_graph)
109 |
110 |
111 | class Div(Gate):
112 | def __init__(self):
113 | super().__init__("div")
114 |
115 | def forward(self, x, y):
116 | self.vars = [x, y]
117 | return x.data / y.data
118 |
119 | def backward(self, grad, make_graph=False):
120 | var0, var1 = self.vars[0], self.vars[1]
121 | if not make_graph:
122 | var0, var1 = var0.data, var1.data
123 | if self.vars[0].requires_grad:
124 | self.vars[0].backward(grad / var1, make_graph=make_graph)
125 | if self.vars[1].requires_grad:
126 | self.vars[1].backward(-grad * var0 / var1 **
127 | 2, make_graph=make_graph)
128 |
129 |
130 | class Pow(Gate):
131 | def __init__(self):
132 | super().__init__("pow")
133 |
134 | def forward(self, x, y):
135 | self.vars = [x, y]
136 | return x.data ** y.data
137 |
138 | def backward(self, grad, make_graph=False):
139 | var0, var1 = self.vars[0], self.vars[1]
140 | if not make_graph:
141 | var0, var1 = var0.data, var1.data
142 | if self.vars[0].requires_grad:
143 | self.vars[0].backward(grad * var1 * var0 **
144 | (var1 - 1), make_graph=make_graph)
145 | if self.vars[1].requires_grad:
146 | self.vars[1].backward(grad * var0 ** var1 *
147 | np.log(var0), make_graph=make_graph)
148 |
149 |
150 | class Sin(Gate):
151 | def __init__(self):
152 | super().__init__("sin")
153 |
154 | def forward(self, x):
155 | self.vars = [x]
156 | return np.sin(x.data)
157 |
158 | def backward(self, grad, make_graph=False):
159 | var0 = self.vars[0]
160 | if not make_graph:
161 | var0 = var0.data
162 | self.vars[0].backward(grad * np.cos(var0), make_graph=make_graph)
163 |
164 |
165 | class Asin(Gate):
166 | def __init__(self):
167 | super().__init__("asin")
168 |
169 | def forward(self, x):
170 | self.vars = [x]
171 | return np.arcsin(x.data)
172 |
173 | def backward(self, grad, make_graph=False):
174 | var0 = self.vars[0]
175 | if not make_graph:
176 | var0 = var0.data
177 | self.vars[0].backward(
178 | grad / np.sqrt(1 - var0 ** 2), make_graph=make_graph)
179 |
180 |
181 | class Sinh(Gate):
182 | def __init__(self):
183 | super().__init__("sinh")
184 |
185 | def forward(self, x):
186 | self.vars = [x]
187 | return np.sinh(x.data)
188 |
189 | def backward(self, grad, make_graph=False):
190 | var0 = self.vars[0]
191 | if not make_graph:
192 | var0 = var0.data
193 | self.vars[0].backward(grad * np.cosh(var0), make_graph=make_graph)
194 |
195 |
196 | class Asinh(Gate):
197 | def __init__(self):
198 | super().__init__("asinh")
199 |
200 | def forward(self, x):
201 | self.vars = [x]
202 | return np.arcsinh(x.data)
203 |
204 | def backward(self, grad, make_graph=False):
205 | var0 = self.vars[0]
206 | if not make_graph:
207 | var0 = var0.data
208 | self.vars[0].backward(
209 | grad / np.sqrt(1 + var0 ** 2), make_graph=make_graph)
210 |
211 |
212 | class Cos(Gate):
213 | def __init__(self):
214 | super().__init__("cos")
215 |
216 | def forward(self, x):
217 | self.vars = [x]
218 | return np.cos(x.data)
219 |
220 | def backward(self, grad, make_graph=False):
221 | var0 = self.vars[0]
222 | if not make_graph:
223 | var0 = var0.data
224 | self.vars[0].backward(-grad * np.sin(var0), make_graph=make_graph)
225 |
226 |
227 | class Acos(Gate):
228 | def __init__(self):
229 | super().__init__("acos")
230 |
231 | def forward(self, x):
232 | self.vars = [x]
233 | return np.arccos(x.data)
234 |
235 | def backward(self, grad, make_graph=False):
236 | var0 = self.vars[0]
237 | if not make_graph:
238 | var0 = var0.data
239 | self.vars[0].backward(-grad / np.sqrt(1 - var0 **
240 | 2), make_graph=make_graph)
241 |
242 |
243 | class Cosh(Gate):
244 | def __init__(self):
245 | super().__init__("cosh")
246 |
247 | def forward(self, x):
248 | self.vars = [x]
249 | return np.cosh(x.data)
250 |
251 | def backward(self, grad, make_graph=False):
252 | var0 = self.vars[0]
253 | if not make_graph:
254 | var0 = var0.data
255 | self.vars[0].backward(grad * np.sinh(var0), make_graph=make_graph)
256 |
257 |
258 | class Acosh(Gate):
259 | def __init__(self):
260 | super().__init__("acosh")
261 |
262 | def forward(self, x):
263 | self.vars = [x]
264 | return np.arccosh(x.data)
265 |
266 | def backward(self, grad, make_graph=False):
267 | var0 = self.vars[0]
268 | if not make_graph:
269 | var0 = var0.data
270 | self.vars[0].backward(
271 | grad / np.sqrt(var0 ** 2 - 1), make_graph=make_graph)
272 |
273 |
274 | class Tan(Gate):
275 | def __init__(self):
276 | super().__init__("tan")
277 |
278 | def forward(self, x):
279 | self.vars = [x]
280 | return np.tan(x.data)
281 |
282 | def backward(self, grad, make_graph=False):
283 | var0 = self.vars[0]
284 | if not make_graph:
285 | var0 = var0.data
286 | self.vars[0].backward(grad / np.cos(var0) ** 2, make_graph=make_graph)
287 |
288 |
289 | class Atan(Gate):
290 | def __init__(self):
291 | super().__init__("atan")
292 |
293 | def forward(self, x):
294 | self.vars = [x]
295 | return np.arctan(x.data)
296 |
297 | def backward(self, grad, make_graph=False):
298 | var0 = self.vars[0]
299 | if not make_graph:
300 | var0 = var0.data
301 | self.vars[0].backward(grad / (1 + var0 ** 2), make_graph=make_graph)
302 |
303 |
304 | class Tanh(Gate):
305 | def __init__(self):
306 | super().__init__("tanh")
307 |
308 | def forward(self, x):
309 | self.vars = [x]
310 | return np.tanh(x.data)
311 |
312 | def backward(self, grad, make_graph=False):
313 | var0 = self.vars[0]
314 | if not make_graph:
315 | var0 = var0.data
316 | self.vars[0].backward(
317 | grad * (1 - np.tanh(var0) ** 2), make_graph=make_graph)
318 |
319 |
320 | class Atanh(Gate):
321 | def __init__(self):
322 | super().__init__("atanh")
323 |
324 | def forward(self, x):
325 | self.vars = [x]
326 | return np.arctanh(x.data)
327 |
328 | def backward(self, grad, make_graph=False):
329 | var0 = self.vars[0]
330 | if not make_graph:
331 | var0 = var0.data
332 | self.vars[0].backward(grad / (1 - var0 ** 2), make_graph=make_graph)
333 |
334 |
335 | class Exp(Gate):
336 | def __init__(self):
337 | super().__init__("exp")
338 |
339 | def forward(self, x):
340 | self.vars = [x]
341 | return np.exp(x.data)
342 |
343 | def backward(self, grad, make_graph=False):
344 | var0 = self.vars[0]
345 | if not make_graph:
346 | var0 = var0.data
347 | self.vars[0].backward(grad * np.exp(var0), make_graph=make_graph)
348 |
349 |
350 | class Log(Gate):
351 | def __init__(self):
352 | super().__init__("log")
353 |
354 | def forward(self, x):
355 | self.vars = [x]
356 | return np.log(x.data)
357 |
358 | def backward(self, grad, make_graph=False):
359 | var0 = self.vars[0]
360 | if not make_graph:
361 | var0 = var0.data
362 | self.vars[0].backward(grad / var0, make_graph=make_graph)
363 |
--------------------------------------------------------------------------------
/nn.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import numpy as np\n",
10 | "import grad\n",
11 | "from grad import variable as vp\n",
12 | "from grad.variable import Variable as vn"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": 2,
18 | "metadata": {},
19 | "outputs": [
20 | {
21 | "name": "stdout",
22 | "output_type": "stream",
23 | "text": [
24 | "8.0 -8.0\n",
25 | "2.0 -2.0\n"
26 | ]
27 | },
28 | {
29 | "data": {
30 | "text/plain": [
31 | "'x_grad.pdf'"
32 | ]
33 | },
34 | "execution_count": 2,
35 | "metadata": {},
36 | "output_type": "execute_result"
37 | }
38 | ],
39 | "source": [
40 | "x = vn(17.0, requires_grad=True)\n",
41 | "y = vn(13.0, requires_grad=True)\n",
42 | "z = (x - y) ** 2\n",
43 | "z.backward(make_graph=True)\n",
44 | "print(x.grad, y.grad)\n",
45 | "z.draw_graph().render('z')\n",
46 | "z.clear_graph()\n",
47 | "x_grad = x.grad\n",
48 | "z.zero_grad()\n",
49 | "x_grad.backward()\n",
50 | "print(x.grad, y.grad)\n",
51 | "x_grad.draw_graph().render('x_grad')"
52 | ]
53 | },
54 | {
55 | "attachments": {},
56 | "cell_type": "markdown",
57 | "metadata": {},
58 | "source": [
59 | "## Linear Regression"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": 3,
65 | "metadata": {},
66 | "outputs": [
67 | {
68 | "name": "stderr",
69 | "output_type": "stream",
70 | "text": [
71 | "100%|██████████| 300/300 [00:00<00:00, 2153.92it/s]\n"
72 | ]
73 | },
74 | {
75 | "data": {
76 | "image/png": "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X7Gurs66DXcZq0WLFokqlUrct2+feO3aNetUW1trXedO77vGxkYxKipKfPTRR0WdTid+9tlnYufOncVly5ZJ8ZTa1Z3G68KFC+Kf/vQn8fDhw2JhYaH4/vvvi7169RIfeugh6zbcZbyWLl0q7t+/XywsLBSPHTsmLl26VBQEQfziiy9EUXSs1xVLzT365z//KXbv3l1UKBRibGysmJOTI3UkhzB16lSxa9euokKhELt16yZOnTpVvHDhgnX5rVu3xGeffVbs2LGj6OPjI06cOFG8du2ahInt5+uvvxYBNJsSExNFUfzP17r/8Ic/iMHBwaJSqRTHjBkjnj17tsk2bty4IU6fPl3s0KGD6O/vLyYlJYnV1dUSPJv29XNjVVtbKz766KNi586dRU9PT7FHjx5icnJys39UuMtYtTROAMQNGzZY12nN++7SpUvi2LFjRW9vbzEoKEj8zW9+IzY0NNj52bS/O41XUVGR+NBDD4mBgYGiUqkU+/TpI7700kuiXq9vsh13GK+5c+eKPXr0EBUKhdi5c2dxzJgx1kIjio71uhJEURRte+yHiIiIyP54TQ0RERG5BJYaIiIicgksNUREROQSWGqIiIjIJbDUEBERkUtgqSEiIiKXwFJDRERELoGlhoiIiFwCSw0RERG5BJYaIiIicgksNUREROQSWGqIiIjIJfx/PiFSdr7y13gAAAAASUVORK5CYII=",
77 | "text/plain": [
78 | ""
79 | ]
80 | },
81 | "metadata": {},
82 | "output_type": "display_data"
83 | },
84 | {
85 | "name": "stdout",
86 | "output_type": "stream",
87 | "text": [
88 | "[[16.999999999999996 12.999999999999998 18.999999999999996\n",
89 | " 22.999999999999996]]\n"
90 | ]
91 | }
92 | ],
93 | "source": [
94 | "import matplotlib.pyplot as plt\n",
95 | "from tqdm import tqdm\n",
96 | "x = vn(2)\n",
97 | "y = vn(3)\n",
98 | "z = vn(4)\n",
99 | "a = np.array([[x, y, z]])\n",
100 | "W = vp.array(np.random.randn(4, 3))\n",
101 | "Y = vp.array(np.array([17, 13, 19, 23]), requires_grad=False)\n",
102 | "losses = []\n",
103 | "for i in tqdm(range(300)):\n",
104 | " b = a @ W.T\n",
105 | " loss = np.mean((Y - b) ** 2)\n",
106 | " losses.append(loss)\n",
107 | " loss.backward()\n",
108 | " with grad.no_grad():\n",
109 | " W = W - 0.01 * vp.array_grad(W)\n",
110 | " if i != 299:\n",
111 | " loss.zero_grad()\n",
112 | "loss.draw_graph().view()\n",
113 | "plt.plot(losses)\n",
114 | "plt.yscale('log')\n",
115 | "plt.show()\n",
116 | "print(a @ W.T)\n"
117 | ]
118 | },
119 | {
120 | "attachments": {},
121 | "cell_type": "markdown",
122 | "metadata": {},
123 | "source": [
124 | "## XOR Neural Net"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": 4,
130 | "metadata": {},
131 | "outputs": [
132 | {
133 | "name": "stderr",
134 | "output_type": "stream",
135 | "text": [
136 | "100%|██████████| 500/500 [02:13<00:00, 3.74it/s]\n"
137 | ]
138 | },
139 | {
140 | "name": "stdout",
141 | "output_type": "stream",
142 | "text": [
143 | "[[-0.9984565423358374]\n",
144 | " [0.9974001865275044]\n",
145 | " [0.9977095500300808]\n",
146 | " [-0.9985410651493936]]\n"
147 | ]
148 | },
149 | {
150 | "data": {
151 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi8AAAGdCAYAAADaPpOnAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAABBmElEQVR4nO3de3xT9eE+8OckaZLekrb03qaUO5RLC6UtRRGRKqLD4W14B6Y4sTJZnRtsX2F+f98NNy9DR6YbXtB5AXWCmwiC5a5c2kK5WC4FCi29pDeatGmbtMn5/dESrIC0NOnJ5Xm/XnlBTk5znhydfXby+ZyPIIqiCCIiIiIPIZM6ABEREVFPsLwQERGRR2F5ISIiIo/C8kJEREQeheWFiIiIPArLCxEREXkUlhciIiLyKCwvRERE5FEUUgdwNrvdjoqKCgQHB0MQBKnjEBERUTeIoojGxkbExsZCJvvxayteV14qKiqg0+mkjkFERETXoKysDPHx8T+6j9eVl+DgYAAdH16j0UichoiIiLrDZDJBp9M5fo//GK8rLxe+KtJoNCwvREREHqY7Qz44YJeIiIg8CssLEREReRSWFyIiIvIoLC9ERETkUVheiIiIyKOwvBAREZFHYXkhIiIij8LyQkRERB7FLcvLF198gWHDhmHIkCF48803pY5DREREbsTt7rDb3t6OnJwcbN26FVqtFqmpqbjzzjvRr18/qaMRERGRG3C7Ky/79u3DyJEjERcXh6CgIEyfPh2bNm2SOhYRERG5CaeXlx07dmDGjBmIjY2FIAhYt27dJfvo9XokJiZCrVYjIyMD+/btc7xWUVGBuLg4x/O4uDiUl5c7OyYRERF5KKeXF7PZjOTkZOj1+su+vmbNGuTk5GDp0qXYv38/kpOTMW3aNFRXVzs7ilM1trZh3nv5WL2vFAVnz+N0TRPOm61os9mljkZERORTnD7mZfr06Zg+ffoVX3/llVcwb948zJ07FwDwxhtvYP369Xj77bexaNEixMbGdrnSUl5ejvT09Cu+n8VigcVicTw3mUxO+BSX2na8BpuLDNhcZLjkNbWfDEEqP2jUCgSpFQhWKxCkUiBY7QeN2g+RGhWiNWpEadSI1qoRG6KGSiF3SU4iIiJv16cDdq1WKwoKCrB48WLHNplMhqysLOzevRsAkJ6ejiNHjqC8vBxarRYbNmzAc889d8X3XLZsGZ5//nmXZ0/RheCZm4di+4kaGBpb0WBuQ6OlHQDQ2mZHa5sFtU2Wq7xLB7lMwIDwQAyLDsbwqGCMTwzDuP4hLDRERETd0Kflpba2FjabDVFRUV22R0VF4dixYx2BFAq8/PLLmDJlCux2O37zm9/86EyjxYsXIycnx/HcZDJBp9M5PbsuLAALpg7BgqlDHNvabHaYLe1obL3waEPTheeWjufGljZUmyyoMrbCYGpFlakVzVYbTlY34WR1E9ajEkDH1ZuMAf1w59g4TBsZDX8liwwREdHluN1UaQC44447cMcdd3RrX5VKBZVK5eJEl+cnlyEkQImQAGW3f0YURRhMFhyrMuGEoRFHyk349lQdapss2H6iBttP1CBIpcCdY+PwxI2DEBfi78JPQERE5Hn6tLyEh4dDLpfDYOg6bsRgMCA6OrpX763X66HX62Gz2Xr1Pq4mCAKitR1jX24cFgmgo9CcMDRh45EqfLq/DGX1LfjXnrNYnVeKBzP6I+eWodCo/SROTkRE5B769D4vSqUSqampyM3NdWyz2+3Izc1FZmZmr947OzsbRUVFyMvL623MPicIAoZFB+PprCHY/usp+PCxDEwc1A9tNhGrvj2DqS9vx9eXGShMRETki5x+5aWpqQknT550PC8pKUFhYSHCwsKQkJCAnJwczJ49G+PHj0d6ejqWL18Os9nsmH3k62QyARMHh2Pi4HDsLK7Bks+/Q0mtGY+9l4/Hrh+A304fDj+5291bkIiIqM8IoiiKznzDbdu2YcqUKZdsnz17NlatWgUAWLFiBV588UVUVVUhJSUFr732GjIyMnp13O9/bXTixAkYjUZoNJpevac7aG2z4cWvjuOtXSUAgBuGRuD1B8chUOWWw5WIiIiuiclkglar7dbvb6eXF6n15MN7ko1HqvCrNYVoabNhdJwW/3o0vUcDhYmIiNxZT35/8/sHD3HrqGh89PgEhAUqcbjciNlv70Nja5vUsYiIiPqc15QXvV6PpKQkpKWlSR3FZVJ0IVjTWWAOnjPi0VX5sLS79+wqIiIiZ+PXRh7oSLkR96/cg8bWdtw1Lg4v35sMQRCkjkVERHTN+LWRlxsVp8XfHxwHuUzAZ/vLsXLnaakjERER9RmWFw81aUgElvwkCQDw543HkX+mXuJEREREfYPlxYM9ktkfM1NiYbOLeHp1IRqarVJHIiIicjmvKS++MGD3hwRBwP/dORqJ/QJQ3tCC/1l3ROpIRERELscBu17g0LkG3Pn3b2Gzi/jnw6m4ZWTv1okiIiLqaxyw62PGxIdg3qSBAIDnPj8CYwvv/0JERN6L5cVLLMwaggHhgTCYLHhhw1Gp4xAREbkMy4uXUPvJ8cJdowEAH+0rw57TdRInIiIicg2vKS++OGD3hzIG9sMDGQkAgOf/WwSb3auGMxEREQHggF2vc95sxY0vbYOxpQ1/unO0o8wQERG5Mw7Y9WGhgUoszBoCAHhp03EO3iUiIq/D8uKFHprQH4Mjg1BvtuJvucVSxyEiInIqlhcv5CeX4bnOpQNWfXsGZ+vMEiciIiJyHpYXLzV5aARuGBqBdruIv24+IXUcIiIip/Ga8sLZRpf6zbRhAIDPD1bgWJVJ4jRERETO4TXlJTs7G0VFRcjLy5M6itsYFafF7aNjIIrAS1/x6gsREXkHrykvdHk5twyFXCbg66MGFJw9L3UcIiKiXmN58XKDIoJwz7h4AMDLm45LnIaIiKj3WF58wC+zhsBPLuDbU3UoOFsvdRwiIqJeYXnxAXEh/rgntePqy2u5JyVOQ0RE1DssLz5i/uTBkMsEbD9Rg4NlDVLHISIiumZeU144VfrHJfQLwE9TYgEAK7by6gsREXkuLszoQ07VNCHrle0QRWDD05MwIobnh4iI3AMXZqTLGhQRhNtHxwAAVmzh1RciIvJMLC8+5qmbBgMAvjxSiZPVjRKnISIi6jmWFx8zPFqDaSOjIIrAP3ecljoOERFRj7G8+KBfTB4EAFh3oALVplaJ0xAREfUMy4sPGpcQivH9Q2G12bHq2zNSxyEiIuoRlhcf9fgNAwEA7+85C7OlXeI0RERE3cfy4qOyRkRhQHggTK3t+Di/TOo4RERE3cby4qNkMgGPTRoAAHhrVwnabXaJExEREXUPy4sPu3tcPPoFKnHufAs2HKmSOg4REVG3eE154fIAPaf2k+ORzEQAHdOmvexmy0RE5KW8prxkZ2ejqKgIeXl5UkfxKA9n9odKIcPhciP2ltRLHYeIiOiqvKa80LUJC1Ti3vHxAHjTOiIi8gwsL4THrh8IQQC2HKtGSa1Z6jhEREQ/iuWFkBgeiKnDIwEA7/KmdURE5OZYXggAMGdix7TpT/LL0NjaJnEaIiKiK2N5IQDAdYP7YXBkEMxWGz4tOCd1HCIioitieSEAgCAImDMxEUDHV0d2O6dNExGRe2J5IYe7xsUhWK3AmbpmbD9RI3UcIiKiy2J5IYcApQL3pekAAO9w4C4REbkplhfq4pHMRAgCsONEDU5WN0kdh4iI6BIsL9SFLiwAWSOiAHDaNBERuSeWF7rE3M6Bu//efw7GFk6bJiIi9+KW5eXOO+9EaGgo7rnnHqmj+KTMQf0wLCoYzVYbPskvkzoOERFRF25ZXp5++mm89957UsfwWYIgYM51iQCA93afhY3TpomIyI24ZXm58cYbERwcLHUMnzYzJQ5afz+U1jdj67FqqeMQERE59Li87NixAzNmzEBsbCwEQcC6desu2Uev1yMxMRFqtRoZGRnYt2+fM7JSH/JXyh3Tpt/bc1biNERERBf1uLyYzWYkJydDr9df9vU1a9YgJycHS5cuxf79+5GcnIxp06ahuvri/3tPSUnBqFGjLnlUVFRc+ychp3sgI8ExbfoMV5smIiI3oejpD0yfPh3Tp0+/4uuvvPIK5s2bh7lz5wIA3njjDaxfvx5vv/02Fi1aBAAoLCy8trSXYbFYYLFYHM9NJpPT3tvX9e8XiMlDI7DteA0+2HsWv789SepIREREzh3zYrVaUVBQgKysrIsHkMmQlZWF3bt3O/NQDsuWLYNWq3U8dDqdS47jqx7J7A8A+Dj/HFrbbBKnISIicnJ5qa2thc1mQ1RUVJftUVFRqKqq6vb7ZGVl4d5778WXX36J+Pj4Hy0+ixcvhtFodDzKyji115kmD41EfKg/jC1t+M9Bfq1HRETS6/HXRn3h66+/7va+KpUKKpXKhWl8m1wm4MGM/vjzxmN4f89Z/Gw8r2wREZG0nHrlJTw8HHK5HAaDoct2g8GA6OhoZx7qEnq9HklJSUhLS3PpcXzRrDQdlAoZDp0z4mBZg9RxiIjIxzm1vCiVSqSmpiI3N9exzW63Izc3F5mZmc481CWys7NRVFSEvLw8lx7HF4UFKvGT0TEAOm5aR0REJKUel5empiYUFhY6ZgyVlJSgsLAQpaWlAICcnBysXLkS7777Lo4ePYr58+fDbDY7Zh+RZ3qoc+Dufw9V4LzZKnEaIiLyZT0e85Kfn48pU6Y4nufk5AAAZs+ejVWrVmHWrFmoqanBkiVLUFVVhZSUFGzcuPGSQbzOptfrodfrYbNxRowrjNWFYFScBkfKTfikoAyP3zBI6khEROSjBFEUvWrhGpPJBK1WC6PRCI1GI3Ucr7ImrxS//fdhJIQFYNuvb4RMJkgdiYiIvERPfn+75dpG5J7uSI6DRq1AaX0zthfXSB2HiIh8FMsLdZu/Uo57O6dKv8+Bu0REJBGvKS+cKt03HsxIAABsOV6NsvpmidMQEZEv8prywqnSfWNgRBAmDQmHKAIf7C2VOg4REfkgrykv1HcennBhvaMyrndERER9juWFeuym4ZGI1apRb7biy8OVUschIiIf4zXlhWNe+o5CLsMDnWNf/rWHA3eJiKhveU154ZiXvjUrLQF+cgEHShtwpNwodRwiIvIhXlNeqG9FBKswfVTHekfv8+oLERH1IZYXumYPd653tK6wHMbmNonTEBGRr2B5oWs2vn8ohkcHo7XNjk8KyqSOQ0REPsJrygsH7PY9QRAcV18+2FsKu92rlskiIiI35TXlhQN2pTEzJQ7BKgVKas3YdbJW6jhEROQDvKa8kDQCVQrcnRoPAHiP6x0REVEfYHmhXnuo8467W44ZcO481zsiIiLXYnmhXhscGYTrBveDXQQ+5HpHRETkYiwv5BQPT0gEAKzJK4OlnesdERGR63hNeeFsI2lljYhEjFaNOrMVGw5XSR2HiIi8mNeUF842kpZCLsMD6R3rHb23+4y0YYiIyKt5TXkh6c1K18FPLmA/1zsiIiIXYnkhp4kMVuPWzvWO/sVp00RE5CIsL+RUj3Tecffzg1zviIiIXIPlhZyK6x0REZGrsbyQU31/vaP395zlekdEROR0LC/kdBfWOzpT18z1joiIyOm8przwPi/ug+sdERGRKwmiKHrVdX2TyQStVguj0QiNRiN1HJ91qqYJU1/eDpkA7PjNFMSHBkgdiYiI3FhPfn97zZUXci+DIi6ud/QB1zsiIiInYnkhl+F6R0RE5AosL+QyF9Y7qjdb8eXhSqnjEBGRl2B5IZfput4RB+4SEZFzsLyQS92XngA/uYADXO+IiIichOWFXCoiWIXpXO+IiIiciOWFXO5hrndEREROxPJCLvf99Y7W5HPaNBER9Q7LC7mcIAiYMzERQMfAXRvXOyIiol7wmvLC5QHc28yxcQgJ8MO58y34+qhB6jhEROTBvKa8ZGdno6ioCHl5eVJHoctQ+8lxf+e06Xe+KZE4DREReTKvKS/k/h6e0B9ymYA9p+txtNIkdRwiIvJQLC/UZ2JD/HHryGgAwKpvzkgbhoiIPBbLC/WpudclAgDWFZaj3myVNgwREXkklhfqU6n9QzEqTgNLux2r8zhtmoiIeo7lhfqUIAiYO3EAgI477rbZ7BInIiIiT8PyQn3uJ8kxCA9SotLYik3fcdo0ERH1DMsL9TmVQo4HMjqWDOC0aSIi6imWF5LEQxkJUMgE5J89j8PnuNo0ERF1H8sLSSJSo8btYzpWm37nW159ISKi7mN5IcnMva5j4O4XBytR02iROA0REXkKlheSTIouBCm6EFhtdny4l9OmiYioe9yuvJSVleHGG29EUlISxowZg08++UTqSORCP7++c9r0njNobbNJnIaIiDyB25UXhUKB5cuXo6ioCJs2bcLChQthNpuljkUuMn1UNGK1atQ2WfF5YbnUcYiIyAO4XXmJiYlBSkoKACA6Ohrh4eGor6+XNhS5jJ9c5hj78ubOEoiiKHEiIiJydz0uLzt27MCMGTMQGxsLQRCwbt26S/bR6/VITEyEWq1GRkYG9u3bd03hCgoKYLPZoNPprunnyTPMStchSKVAcXUTtp2okToOERG5uR6XF7PZjOTkZOj1+su+vmbNGuTk5GDp0qXYv38/kpOTMW3aNFRXVzv2SUlJwahRoy55VFRUOPapr6/HI488gn/+85/X8LHIk2jUfrgvraOgvrnztMRpiIjI3QliL67TC4KAtWvXYubMmY5tGRkZSEtLw4oVKwAAdrsdOp0OCxYswKJFi7r1vhaLBTfffDPmzZuHhx9++Kr7WiwXp9maTCbodDoYjUZoNJqefyiSRHlDC274y1bY7CLW//J6jIzVSh2JiIj6kMlkglar7dbvb6eOebFarSgoKEBWVtbFA8hkyMrKwu7du7v1HqIoYs6cObjpppuuWlwAYNmyZdBqtY4Hv2LyTHEh/rhtdMdN697ayZvWERHRlTm1vNTW1sJmsyEqKqrL9qioKFRVVXXrPb755husWbMG69atQ0pKClJSUnD48OEr7r948WIYjUbHo6ysrFefgaQzb1LHwN3/HKxAlbFV4jREROSuFFIH+KHrr78edru92/urVCqoVCro9Xro9XrYbLxXiKcaEx+C9AFh2FdSj1XfnsGi6cOljkRERG7IqVdewsPDIZfLYTAYumw3GAyIjo525qEukZ2djaKiIuTl5bn0OORa8yYNBAB8uPcszJZ2idMQEZE7cmp5USqVSE1NRW5urmOb3W5Hbm4uMjMznXko8lJTh0diYHggTK3t+DifXwESEdGlelxempqaUFhYiMLCQgBASUkJCgsLUVrasTZNTk4OVq5ciXfffRdHjx7F/PnzYTabMXfuXKcGJ+8kkwmOJQPe/qYENjtvWkdERF31eMxLfn4+pkyZ4niek5MDAJg9ezZWrVqFWbNmoaamBkuWLEFVVRVSUlKwcePGSwbxOhvHvHiPu8fF4+VNx1FW34KvvqtyzEIiIiICenmfF3fUk3ni5L5e2XQcr205iRRdCNY+ORGCIEgdiYiIXEiy+7wQOcvDmYlQKmQoLGvAvhKubUVERBd5TXnR6/VISkpCWlqa1FHICSKCVbg3NR4A8PdtpyROQ0RE7oRfG5HbKq1rxo0vbYVdBJcMICLycvzaiLxCQr8A/GRMLADgdV59ISKiTiwv5Nbm3zgIAPDl4UqcqTVLnIaIiNyB15QXjnnxTiNiNJgyLAJ2EfjHjtNSxyEiIjfAMS/k9vLO1OPeN3ZDKZdh12+nIFKjljoSERE5Gce8kFdJSwzD+P6hsNrseOubEqnjEBGRxFheyCM8OaVj7MsHe0phbGmTOA0REUmJ5YU8wpRhkRgeHYwmSzve33NW6jhERCQhrykvHLDr3QRBcMw8entXCVqsXMOKiMhXeU15yc7ORlFREfLy8qSOQi5y++gY6ML8UWe2YnVeqdRxiIhIIl5TXsj7KeQyPDG54+rLG9tPobWNV1+IiHwRywt5lHtS4xGjVcNgsuCTgnNSxyEiIgmwvJBHUSnkjrEvr289CWu7XeJERETU11heyOP8bLwOkcEqVBhb8SmvvhAR+RyvKS+cbeQ71H5yx9gX/daTaLPx6gsRkS/xmvLC2Ua+5YGMBIQHqVDe0IK1+8uljkNERH3Ia8oL+ZaOqy8DAQArtp5EO6++EBH5DJYX8lgPZCSgX6ASpfXN+LywQuo4RETUR1heyGMFKBWYdwOvvhAR+RqWF/JoD0/oj9AAP5TUmvHFoUqp4xARUR9geSGPFqhS4LFJHVdfXsst5tUXIiIfwPJCHu+RzI6rL6drzVh7gDOPiIi8ndeUF97nxXcFq/0c9315NbeYd90lIvJyXlNeeJ8X3/ZIZiIiglU4d74Fa/LLpI5DREQu5DXlhXybv1KOp6YMBgCs2FLMFaeJiLwYywt5jfvSdYgL8YfBZMH7e85KHYeIiFyE5YW8hkohxy+ndlx9+fu2U2iytEuciIiIXIHlhbzK3ePiMSA8EPVmK1Z9UyJ1HCIicgGWF/IqCrkMC7OGAAD+seM0jM1tEiciIiJnY3khrzNjTCyGRQWjsbUdK3eeljoOERE5GcsLeR2ZTEDOLUMBAG9/U4LaJovEiYiIyJlYXsgr3ZIUheR4LZqtNryWWyx1HCIiciKWF/JKgiDgt9OHAwA+3FuKklqzxImIiMhZvKa8cHkA+qGJg8IxZVgE2u0iXvzqmNRxiIjISQRRFEWpQziTyWSCVquF0WiERqOROg5J7HhVI6a/ugN2EfjsyYkYlxAqdSQiIrqMnvz+9porL0SXMyw6GPekxgMAln15FF7W1YmIfBLLC3m9X908FGo/GfLOnMfmIoPUcYiIqJdYXsjrxWj98ej1AwAAf954DO02u8SJiIioN1heyCf8YvIghAUqcarGjI/zz0kdh4iIeoHlhXyCRu2HBTd1LNr4169PwMxFG4mIPBbLC/mMBzP6IyEsADWNFi4bQETkwVheyGcoFTL85tZhAIA3tp9CRUOLxImIiOhasLyQT7l9dAzSEkPR2mbHnzfyxnVERJ6I5YV8iiAIWDpjJAQB+LywAgVn66WOREREPcTyQj5nVJwWP0vVAQCe/28R7HbeuI6IyJOwvJBP+vW0YQhSKXDonBH/3s+p00REnsTtyktDQwPGjx+PlJQUjBo1CitXrpQ6EnmhiGCVY+r0X746jiZOnSYi8hhuV16Cg4OxY8cOFBYWYu/evfjTn/6Euro6qWORF5pzXSIS+3VMndZvPSl1HCIi6ia3Ky9yuRwBAQEAAIvFAlEUuZgeuYRKIcfvb08CALy1swSldc0SJyIiou7ocXnZsWMHZsyYgdjYWAiCgHXr1l2yj16vR2JiItRqNTIyMrBv374eHaOhoQHJycmIj4/Hs88+i/Dw8J7GJOqWrBGRmDQkHFabHf+3vkjqOERE1A09Li9msxnJycnQ6/WXfX3NmjXIycnB0qVLsX//fiQnJ2PatGmorq527HNhPMsPHxUVFQCAkJAQHDx4ECUlJfjwww9hMHAlYHINQRDw3E+SIJcJ2FRkwPYTNVJHIiKiqxDEXnwnIwgC1q5di5kzZzq2ZWRkIC0tDStWrAAA2O126HQ6LFiwAIsWLerxMZ588kncdNNNuOeeey77usVigcVicTw3mUzQ6XQwGo3QaDQ9Ph75pv/9bxHe/qYEif0CsHHhDVD7yaWORETkU0wmE7Rabbd+fzt1zIvVakVBQQGysrIuHkAmQ1ZWFnbv3t2t9zAYDGhsbAQAGI1G7NixA8OGDbvi/suWLYNWq3U8dDpd7z4E+aRf3TwEkcEqnKlrxj+2c90jIiJ35tTyUltbC5vNhqioqC7bo6KiUFVV1a33OHv2LCZNmoTk5GRMmjQJCxYswOjRo6+4/+LFi2E0Gh2PsrKyXn0G8k3Baj8895OOwbv6bSdxts4scSIiIroShdQBfig9PR2FhYXd3l+lUkGlUrkuEPmMn4yJwcf5ZdhZXIsln3+HVXPTIAiC1LGIiOgHnHrlJTw8HHK5/JIBtgaDAdHR0c481CX0ej2SkpKQlpbm0uOQ9xIEAc/fMRJKuQzbT9Tgq++6d7WQiIj6llPLi1KpRGpqKnJzcx3b7HY7cnNzkZmZ6cxDXSI7OxtFRUXIy8tz6XHIuw2MCMITkwcC6Fj3yMw77xIRuZ0el5empiYUFhY6vtopKSlBYWEhSktLAQA5OTlYuXIl3n33XRw9ehTz58+H2WzG3LlznRqcyFWenDIYujB/VBpb8VpusdRxiIjoB3pcXvLz8zF27FiMHTsWQEdZGTt2LJYsWQIAmDVrFl566SUsWbIEKSkpKCwsxMaNGy8ZxOts/NqInEXtJ8f/3jEKAPDWrhIcr2qUOBEREX1fr+7z4o56Mk+c6Mf84l/5+Oo7A8YmhODTJyZCLuPgXSIiV5HsPi9E3uQPd4xEkEqBA6UNeG/3GanjEBFRJ5YXoiuI0fpj0fThAIC/bDyOsnou3EhE5A68prxwzAu5wgPpCUgfEIaWNht+t/YwVzgnInIDHPNCdBWna5pw66s7YW234+V7k3F3arzUkYiIvA7HvBA50cCIICzMGgIA+H/ri1DbZLnKTxARkSuxvBB1w7xJA5EUo0FDcxv+8J/vpI5DROTTvKa8cMwLuZKfXIa/3DMGcpmALw5VYnOR4eo/RERELsExL0Q9sGzDUfxj+2lEaVTYtHAytAF+UkciIvIKHPNC5CK/yhqKgeGBMJgsWPqfI1LHISLySSwvRD2g9pPj5Z8lQyYA6worsOFwpdSRiIh8DssLUQ+NTQjFkzcOBgD8bu1h1DRy9hERUV/ymvLCAbvUl345dQhGxGhwvrkNiz/jzeuIiPoSB+wSXaOjlSbcsWIX2mwiXrxnDO4dr5M6EhGRx+KAXaI+MCJGg1/dPBQA8L//LUJ5Q4vEiYiIfAPLC1Ev/OKGQRiXEIJGSzue/eQg7HavupBJROSWWF6IekEuE/Dyz1Lg7yfHt6fq8PY3JVJHIiLyeiwvRL00IDwQv799BADgzxuP4Ui5UeJERETezWvKC2cbkZQezEjALUlRaLOJ+OVHB2C2tEsdiYjIa3G2EZGTnDdbMf3VnagyteJn4+Pxl3uSpY5EROQxONuISAKhgUr8dVYKBAH4OP8cvjhUIXUkIiKvxPJC5ESZg/rhqSkdd99d/NlhlNU3S5yIiMj7sLwQOdnTU4d0TJ9ubcfTqw+g3WaXOhIRkVdheSFyMoVchlfvG4tglQL7Sxvwam6x1JGIiLwKywuRC+jCAvCnu0YDAFZsPYltx6slTkRE5D28prxwqjS5mxnJsXhoQgJEEVi4ppDLBxAROQmnShO5kKXdhnvf2I1D54xI1oXg419MgEohlzoWEZHb4VRpIjehUsihf2ActP5+OFjWgD+uPyp1JCIij8fyQuRiurAA/HVWxw3r3tt9Fp8XlkuciIjIs7G8EPWBm4ZHdbn/S7GhUeJERESei+WFqI/86uahmDioH5qtNsz/YD+auP4REdE1YXkh6iNymYBX7xuLKI0KJ6ub8Ks1hbDbvWq8PBFRn2B5IepDEcEqvP5QKpRyGTYXGbCcN7AjIuoxlheiPjYuIdRxA7vXcoux4XClxImIiDwLywuRBO5JjcfPrxsAAMj5+CCOVpokTkRE5DlYXogk8rvbhmPSkHC0tNkw77181JutUkciIvIIXlNeuDwAeRqFXIa/3T8W/fsF4Nz5Fjz5QQHauAI1EdFVcXkAIomdMDTiTv03MFtteCAjAX+cOQqCIEgdi4ioT3F5ACIPMjQqGK/eNxaCAHy4txQrd56WOhIRkVtjeSFyA1lJUfif25MAAH/68hi+5AwkIqIrYnkhchM/vy4RszP7AwB+taYQBWfPS5yIiMg9sbwQuQlBELBkxkhkjYiEpd2Oee/l42ydWepYRERuh+WFyI1cWEJgVJwG9WYr5r6Th4ZmTqEmIvo+lhciNxOoUuDt2WmI1apxutaMx98rQGubTepYRERug+WFyA1FatR4e24aglUK7DtTj19+dADtvAcMEREAlhcitzU8WoN/PjIeSoUMm4oM+P3aI/Cy2zIREV0TlhciN5Y5qB/+dv9YyARgTX4Z/vLVcakjERFJjuWFyM1NGxmNZZ2rUL++7RTe5E3siMjHsbwQeYBZaQn47a3DAQD/t/4o/l1wTuJERETScdvy0tzcjP79++PXv/611FGI3MITkwdi3qQBAIDf/PsQNn1XJXEiIiJpuG15+eMf/4gJEyZIHYPIbQiCgN/dNgL3pMbDZheR/eF+bD1WLXUsIqI+55blpbi4GMeOHcP06dOljkLkVgRBwAt3jcbto2PQZhPxi/cLsLO4RupYRER9qsflZceOHZgxYwZiY2MhCALWrVt3yT56vR6JiYlQq9XIyMjAvn37enSMX//611i2bFlPoxH5BIVchuX3peCWpChY2+147N187D5VJ3UsIqI+0+PyYjabkZycDL1ef9nX16xZg5ycHCxduhT79+9HcnIypk2bhurqi5e3U1JSMGrUqEseFRUV+PzzzzF06FAMHTr02j8VkZfzk8uw4oFxuGl4xzpIj76bh7wz9VLHIiLqE4LYi7teCYKAtWvXYubMmY5tGRkZSEtLw4oVKwAAdrsdOp0OCxYswKJFi676nosXL8b7778PuVyOpqYmtLW14ZlnnsGSJUsuu7/FYoHFYnE8N5lM0Ol0MBqN0Gg01/rRiDxCa5sN897Lx87iWgSpFPjXo+kYmxAqdSwioh4zmUzQarXd+v3t1DEvVqsVBQUFyMrKungAmQxZWVnYvXt3t95j2bJlKCsrw5kzZ/DSSy9h3rx5VywuF/bXarWOh06n6/XnIPIUaj85/vnweGQO7IcmSzseeWsf8nkFhoi8nFPLS21tLWw2G6Kiorpsj4qKQlWVa6Z1Ll68GEaj0fEoKytzyXGI3JW/Uo43Z49HxoAwNFra8cjb+zgGhoi8mkLqAD9mzpw5V91HpVJBpVK5PgyRGwtUKbBqbjoe/1fHV0hz3tmHlY+Mxw1DI6SORkTkdE698hIeHg65XA6DwdBlu8FgQHR0tDMPdQm9Xo+kpCSkpaW59DhE7spfKcfKR8Y7BvE+9m4+vi4yXP0HiYg8jFPLi1KpRGpqKnJzcx3b7HY7cnNzkZmZ6cxDXSI7OxtFRUXIy8tz6XGI3JnaT443HkrFrSOjYbXZ8cT7BdhwuFLqWERETtXj8tLU1ITCwkIUFhYCAEpKSlBYWIjS0lIAQE5ODlauXIl3330XR48exfz582E2mzF37lynBieiy1MqZFjxwFjckRyLdruIpz46gI/zORaMiLxHj8e85OfnY8qUKY7nOTk5AIDZs2dj1apVmDVrFmpqarBkyRJUVVUhJSUFGzduvGQQr7Pp9Xro9XrYbDaXHofIEyjkMvx1VgrUfjJ8nH8Ov/n0EOrNVvzihoEQBEHqeEREvdKr+7y4o57MEyfydqIo4oWNx/CP7acBAI9dPwC/u20EZDIWGCJyL5Ld54WI3IsgCFg8fQR+f9sIAMCbu0rwzCcH0WazS5yMiOjasbwQ+YB5NwzEy/cmQy4TsPZAOea9l49ma7vUsYiIronXlBdOlSb6cXenxuPNR8ZD7SfDtuM1uP+fe1Dd2Cp1LCKiHuOYFyIfU3D2PB59Nw8NzW2IC/HHO3PTMDQqWOpYROTjOOaFiK4otX8o1j55HQaEB6K8oQV3//1b7CyukToWEVG3sbwQ+aAB4YH4bP5EpCd2rIc05508fLSvVOpYRETd4jXlhWNeiHomNFCJfz2WjpkpsbDZRSz+7DCWbTgKm92rvkkmIi/EMS9EPk4URbyaW4zlXxcDAG4aHonl96VAo/aTOBkR+RKOeSGibhMEAQuzhuLV+1KgUsiw5Vg1Zq74Bierm6SORkR0WSwvRAQA+GlKHD59YiJitWqcrjXjTv03yD3KVamJyP2wvBCRw+h4Lf6z4HrHQN7H3svH33KL4WXfLhORh/Oa8sIBu0TOER6kwvuPZeDhCf0hisDLm0/g8X8VwNjSJnU0IiIAHLBLRD9i9b5SLPn8O1htdujC/PH3B1IxOl4rdSwi8kIcsEtETnFfegL+PX8idGH+KKtvwd2vf4t/7TnLr5GISFIsL0T0o0bHa/HFU5OQNSIKVpsdz607goVrCmG2cGFHIpIGywsRXZU2wA8rH0nF724bDrlMwOeFFZixYheOlBuljkZEPojlhYi6RRAEPH7DIKx+fAKiNWqcrjHjzr9/g39sPwU778pLRH3Ia8oLZxsR9Y20xDBseHoSpo2MQptNxLINx/DQW3tRZWyVOhoR+QjONiKiayKKItbkleH5/xahpc0Grb8f/nz3aNw6KkbqaETkgTjbiIhcThAE3JeegC9+eT1Gx2lhbGnDE+/vx28/PYQmDuYlIhdieSGiXhkUEYR/z5+IJyYPgiAAa/LLMO2vO7CruFbqaETkpVheiKjXlAoZFk0fjg8fm4D4UH+UN7Tgobf2YvFnh9HYyjvzEpFzsbwQkdNkDuqHrxbegEcy+wMAPtpXiluX78TO4hqJkxGRN2F5ISKnClQp8L8/HYWP5k2ALqzjKszDb+3Don8fgolXYYjICVheiMglMgf1w8anb8CciYkAgNV5Zch6eTu+OFTB5QWIqFe8przwPi9E7idQpcAf7hiJ1Y9PwIDwQFQ3WvDUhwcw5508lNY1Sx2PiDwU7/NCRH2itc2G17edwuvbTsFqs0OlkOGXU4dg3qSBUCq85v9HEdE14n1eiMjtqP3k+NXNQ7Fx4SRcN7gfLO12vPjVcdz22k7sOV0ndTwi8iAsL0TUpwZGBOH9RzOwfFYKwoOUOFndhPv+uQfZH+7HufP8KomIro7lhYj6nCAImDk2Drk5N+KhCQmQCcD6Q5WY+vJ2vLLpOJqtvEMvEV0Zx7wQkeSOVprw/H+/w57T9QCAaI0ai6YPx09TYiEIgsTpiKgv9OT3N8sLEbkFURTx1XdV+L/1R3HufAsAYFxCCH532wiMTwyTOB0RuRrLC8sLkcdqbbPhrV0l0G89iWarDQCQNSIKv7l1GIZGBUucjohcheWF5YXI4xlMrVj+dTE+zi+DzS5CJgB3j4vHr24eitgQf6njEZGTsbywvBB5jZPVTXjpq+PY+F0VgI5FIOdMTMT8yYMQGqiUOB0ROQvLC8sLkdfZX3oeL2w4hn0lHYN6A5VyzLkuEfMmDURIAEsMkafzyfKi1+uh1+ths9lw4sQJlhciLySKIrYdr8FfvjqOo5UmAECQSoE5ExPx2KQBLDFEHswny8sFvPJC5P3sdhGbjxqw/OviLiVm7nWJePR6lhgiT8TywvJC5BPsdhGbigxY/vUJHKtqBNDxddL96Ql4dNIAxGg5sJfIU7C8sLwQ+ZSOElOF5V8XO0qMn1zAT1Pi8MTkgRgcySnWRO6O5YXlhcgniaKIbSdq8Ma2U9jbObAX6LhPzPwbByK1P292R+SuWF5YXoh83oHS83hj+ylsKjLgwn/lxvcPxdzrBmDayCgo5FzajcidsLywvBBRp1M1Tfjn9tP47MA5tNk6/nMXo1XjoQn9cX96AsJ4rxgit8DywvJCRD9gMLXigz1n8cHeUtSZrQA6bng3MyUWsycmYmSsVuKERL6N5YXlhYiuoLXNhvWHKrHq2zM4XG50bE9PDMODExIwbWQ01H5yCRMS+SaWF5YXIroKURSxv/Q8Vn17FhsOV6Ld3vGfwpAAP9w1Nh73p+swhAtBEvUZlheWFyLqgSpjK9bklWFNXikqjK2O7an9Q3F/egJuHx0DfyWvxhC5EssLywsRXQObXcSO4hp8tLcUuceqYeu8GhOsVuAnY2Jx17g4jO8fCkEQJE5K5H1YXlheiKiXqk2t+KTgHFbnlaKsvsWxPSEsADPHxuGusXFIDA+UMCGRd/H48pKYmAiNRgOZTIbQ0FBs3bq12z/L8kJEzmS3i9hzug6fHSjHhsOVMFttjtfGJYTgrnHxuH10DEI55ZqoV7yivBw5cgRBQUE9/lmWFyJylWZrOzYXGfDv/eXYVVyDzm+VoJAJuG5wOG4fHYNbRkZxYUiia8DywvJCRC5WbWrF54UV+OxAuWNla6CjyFw/pLPIJEVDG+AnYUoiz9GT3989vj/2jh07MGPGDMTGxkIQBKxbt+6SffR6PRITE6FWq5GRkYF9+/b16BiCIGDy5MlIS0vDBx980NOIREQuF6lRY94NA7Hh6UnIfWYynrl5KIZHB6PdLmLb8Ro8++khjP/jZvx8VR4+zi9DXZNF6shEXkPR0x8wm81ITk7Gz3/+c9x1112XvL5mzRrk5OTgjTfeQEZGBpYvX45p06bh+PHjiIyMBACkpKSgvb39kp/dtGkTYmNjsWvXLsTFxaGyshJZWVkYPXo0xowZcw0fj4jI9QZFBGHB1CFYMHUITlY34cvDlVh/qBLHDY3YcqwaW45VQyZ0TL3OGhGFm5OiMDCi51eWiahDr742EgQBa9euxcyZMx3bMjIykJaWhhUrVgAA7HY7dDodFixYgEWLFvX4GM8++yxGjhyJOXPmXPZ1i8UCi+Xi/6MxmUzQ6XT82oiIJFdsaMT6w5XYXGTAdxWmLq8NjAjEzUlRuHlEFMYmhEIu4/Rr8m09+dqox1defozVakVBQQEWL17s2CaTyZCVlYXdu3d36z3MZjPsdjuCg4PR1NSELVu24Gc/+9kV91+2bBmef/75XmcnInK2IVHBWBgVjIVZQ1He0ILcowZsLjJgz+k6nK4x4x/bT+Mf208jNMAPk4ZE4IahEbhhSDgiNWqpoxO5NaeWl9raWthsNkRFRXXZHhUVhWPHjnXrPQwGA+68804AgM1mw7x585CWlnbF/RcvXoycnBzH8wtXXoiI3ElciD8eyUzEI5mJMLW2YceJGmwuMmDrsWqcb27Dfw5W4D8HKwAAI2I0uGFoOCYPiUBqYihUCt7dl+j7nFpenGHgwIE4ePBgt/dXqVRQqVTQ6/XQ6/Ww2WxX/yEiIglp1H74yZhY/GRMLNpsdhwobcCOEzXYUVyDw+VGHK004WilCf/YfhoBSjkmDOyHSUPCkTmoH4ZGBkPGr5jIxzm1vISHh0Mul8NgMHTZbjAYEB0d7cxDXSI7OxvZ2dmO78yIiDyBn1yG9AFhSB8Qhl9PG4a6Jgt2nazF9hM12Flci5pGi2PQLwCEBSqRMSAMmYP6YcLAfhgSGcTlCsjnOLW8KJVKpKamIjc31zGI1263Izc3F0899ZQzD0VE5JX6Banw05Q4/DQlDqIo4mhlI3YU1+Cbk7XIP3Me9WYrNhypwoYjVR37ByoxYWA/TBjUDxMGhGFQRBCvzJDX63F5aWpqwsmTJx3PS0pKUFhYiLCwMCQkJCAnJwezZ8/G+PHjkZ6ejuXLl8NsNmPu3LlODU5E5O0EQUBSrAZJsRo8MXkQrO12HC5vwJ7T9dh9qg75Z+tRZ7Zi/eFKrD9cCQDQ+vthXEIIUvuHIrV/GJJ1WgQo3W6EAFGv9Hiq9LZt2zBlypRLts+ePRurVq0CAKxYsQIvvvgiqqqqkJKSgtdeew0ZGRlOCXwl3x/zcuLECU6VJiKvZ2234+C5Buw5VYfdp+twoLQBLW1dx/3JZQKSYjRI7R+Kcf1DMS4hBHEh/vyqidyOxy8P0BtcHoCIfFWbzY5jlY3IP1uPgrPnsf/seVQYWy/ZLzxIidFxWoyOD0FyvBaj47WIDOb0bJIWywvLCxERAKCioQUFZ887HkcrTWi3X/qf/RitGqPjtEjWhXQUmzgtV8qmPuWT5YVfGxERXV1rmw1HK004dM7Y+WjAyZomXO43QbRGjRExwRgRo8HwGA2SYoKR2C8QCnmPl8UjuiqfLC8X8MoLEVHPmC3tOFJuxOFyIw52Fpqzdc2X3VelkGFYdDCGR3eUmhExGgyPDkZIAK/SUO+wvLC8EBH1SmNrG45XNeJopQlFlY04VmXC8apGNFsvfyPQ8CAlBkcGdTwigjAkKhiDI4MQGazi4GDqFpYXlhciIqez20WU1jc77gB8tLPcnDvfcsWfCVYrvldogjAgPAgDwgOgCwvgsgfUhU+WF455ISKShtnSjtM1ZhRXN+JkdROKq5twqroJZ+rMuMzYYACAIACxWn8MCA9E/34BnX8GYkB4AOJDA6D2Y7HxNT5ZXi7glRciIvdgabfhTG1zZ6HpKDYltWacrWtGk6X9ij93odgkhgdAFxqA+FB/xH/vz8hgFe8i7IV68vubt10kIiKXUCnkGBYdjGHRwQBiHNtFUURtkxVn68yOMlNSZ8bZOjPO1HYUm/KGFpQ3tACou+R9/eQCYkP8O8pMSGepCesoNnEh/ogMVnFGlJdjeSEioj4lCAIiglWICFZhfGJYl9dEUUSd2YoztWacqWvGufPNOHe+xfFnpbEVbTYRZ+uaO2dEXVpuZAIQHqRCjFaNKI2640+t+nvP/RGtUcNfya+mPBXLCxERuQ1BEBAepEJ40KXFBgDabXYYGi04V3+h1FwsNucamlHZ0Ip2u4jqRguqGy0AjFc8ltbfz1FoIjvLVHiQylGsLvxdo1ZwxpSb8Zry8v0Bu0RE5J0UchniQvwRF+KPy62YZ7eLqDVbYDBaUGlsgcHUikpjK6pMraj63p/NVhuMLW0wtrThWFXjjx5TqZAhIkiF8GAVIoKUHeWm83l4kAqhAUqEBSoRGuiH0AAl/PiVlctxwC4REfkUURRham13FBuDsRU1TRbUNHY+miyo7fyzsfXKA4uvJFitQGiAEqGBSoQF+HX+2fk8UNnxWoBfZ+FRQqP2g1LBwsMBu0RERFcgCAK0/n7Q+vthaFTwj+7b2mZDbWexqW2yOgrOxW0WnG+24nxzGxqarbCLQGNrOxpb21Faf/m7FF+Ov58cWn8/aPwVHX+q/Tqfdz7UCsdzx+sBHduDVL73tRbLCxER0RWo/eSd07QDrrqv3S7C1NqGerMV55utqDe34bzZivpma8efndvPN1/c3tDcBgBoabOhpc2GKlPPM8oEIEjVUWKC1AoEdv49WK1AoLJj24XXA6+w/cLfVQqZRxQhlhciIiInkMkEhAQoe7TOk80uorG1DaaWdhhb2mBq7RiHY+ocj3PxebtjjI6p9eLrbTYRdhEwtbbD1Nr+Y+OTu0UhExCglCNAqUCASn7x70o5ApUK+CvlCFTKMTQ6GA9m9O/dwXqTU7IjExER+Tj5NRSeC0RRRGubHcaWNjRZ2mG2tKPpwqO1HWZrx9dXl9ve1Hpxm9lic9w0sN0uXixCP2Ly0AiWF2fgbCMiIvIlgiDAXyl3yv1q7HYRzW02NLW2o9najmarDWZLO5rbbGi22C5us7ajxWpDQtjVv0ZzJc42IiIiIsn15Pc352YRERGRR2F5ISIiIo/C8kJEREQeheWFiIiIPArLCxEREXkUlhciIiLyKF5TXvR6PZKSkpCWliZ1FCIiInIh3ueFiIiIJMf7vBAREZHXYnkhIiIij8LyQkRERB6F5YWIiIg8CssLEREReRSF1AGc7cLkKZPJJHESIiIi6q4Lv7e7Mwna68pLY2MjAECn00mchIiIiHqqsbERWq32R/fxuvu82O12VFRUIDg4GIIgOPW9TSYTdDodysrKeA8ZF+J57hs8z32H57pv8Dz3HVeca1EU0djYiNjYWMhkPz6qxeuuvMhkMsTHx7v0GBqNhv/D6AM8z32D57nv8Fz3DZ7nvuPsc321Ky4XcMAuEREReRSWFyIiIvIoLC89oFKpsHTpUqhUKqmjeDWe577B89x3eK77Bs9z35H6XHvdgF0iIiLybrzyQkRERB6F5YWIiIg8CssLEREReRSWFyIiIvIoLC/dpNfrkZiYCLVajYyMDOzbt0/qSB5nx44dmDFjBmJjYyEIAtatW9fldVEUsWTJEsTExMDf3x9ZWVkoLi7usk99fT0efPBBaDQahISE4NFHH0VTU1Mffgr3tmzZMqSlpSE4OBiRkZGYOXMmjh8/3mWf1tZWZGdno1+/fggKCsLdd98Ng8HQZZ/S0lLcfvvtCAgIQGRkJJ599lm0t7f35Udxe6+//jrGjBnjuElXZmYmNmzY4Hid59k1XnjhBQiCgIULFzq28Vw7xx/+8AcIgtDlMXz4cMfrbnWeRbqq1atXi0qlUnz77bfF7777Tpw3b54YEhIiGgwGqaN5lC+//FL8/e9/L3722WciAHHt2rVdXn/hhRdErVYrrlu3Tjx48KB4xx13iAMGDBBbWloc+9x6661icnKyuGfPHnHnzp3i4MGDxfvvv7+PP4n7mjZtmvjOO++IR44cEQsLC8XbbrtNTEhIEJuamhz7PPHEE6JOpxNzc3PF/Px8ccKECeLEiRMdr7e3t4ujRo0Ss7KyxAMHDohffvmlGB4eLi5evFiKj+S2/vOf/4jr168XT5w4IR4/flz83e9+J/r5+YlHjhwRRZHn2RX27dsnJiYmimPGjBGffvppx3aea+dYunSpOHLkSLGystLxqKmpcbzuTueZ5aUb0tPTxezsbMdzm80mxsbGisuWLZMwlWf7YXmx2+1idHS0+OKLLzq2NTQ0iCqVSvzoo49EURTFoqIiEYCYl5fn2GfDhg2iIAhieXl5n2X3JNXV1SIAcfv27aIodpxTPz8/8ZNPPnHsc/ToURGAuHv3blEUO0qmTCYTq6qqHPu8/vrrokajES0WS99+AA8TGhoqvvnmmzzPLtDY2CgOGTJE3Lx5szh58mRHeeG5dp6lS5eKycnJl33N3c4zvza6CqvVioKCAmRlZTm2yWQyZGVlYffu3RIm8y4lJSWoqqrqcp61Wi0yMjIc53n37t0ICQnB+PHjHftkZWVBJpNh7969fZ7ZExiNRgBAWFgYAKCgoABtbW1dzvPw4cORkJDQ5TyPHj0aUVFRjn2mTZsGk8mE7777rg/Tew6bzYbVq1fDbDYjMzOT59kFsrOzcfvtt3c5pwD/nXa24uJixMbGYuDAgXjwwQdRWloKwP3Os9ctzOhstbW1sNlsXf5hAEBUVBSOHTsmUSrvU1VVBQCXPc8XXquqqkJkZGSX1xUKBcLCwhz70EV2ux0LFy7Eddddh1GjRgHoOIdKpRIhISFd9v3heb7cP4cLr9FFhw8fRmZmJlpbWxEUFIS1a9ciKSkJhYWFPM9OtHr1auzfvx95eXmXvMZ/p50nIyMDq1atwrBhw1BZWYnnn38ekyZNwpEjR9zuPLO8EHmp7OxsHDlyBLt27ZI6itcaNmwYCgsLYTQa8emnn2L27NnYvn271LG8SllZGZ5++mls3rwZarVa6jhebfr06Y6/jxkzBhkZGejfvz8+/vhj+Pv7S5jsUvza6CrCw8Mhl8svGVFtMBgQHR0tUSrvc+Fc/th5jo6ORnV1dZfX29vbUV9fz38WP/DUU0/hiy++wNatWxEfH+/YHh0dDavVioaGhi77//A8X+6fw4XX6CKlUonBgwcjNTUVy5YtQ3JyMl599VWeZycqKChAdXU1xo0bB4VCAYVCge3bt+O1116DQqFAVFQUz7WLhISEYOjQoTh58qTb/TvN8nIVSqUSqampyM3NdWyz2+3Izc1FZmamhMm8y4ABAxAdHd3lPJtMJuzdu9dxnjMzM9HQ0ICCggLHPlu2bIHdbkdGRkafZ3ZHoijiqaeewtq1a7FlyxYMGDCgy+upqanw8/Prcp6PHz+O0tLSLuf58OHDXYri5s2bodFokJSU1DcfxEPZ7XZYLBaeZyeaOnUqDh8+jMLCQsdj/PjxePDBBx1/57l2jaamJpw6dQoxMTHu9++0U4f/eqnVq1eLKpVKXLVqlVhUVCQ+/vjjYkhISJcR1XR1jY2N4oEDB8QDBw6IAMRXXnlFPHDggHj27FlRFDumSoeEhIiff/65eOjQIfGnP/3pZadKjx07Vty7d6+4a9cucciQIZwq/T3z588XtVqtuG3bti7THZubmx37PPHEE2JCQoK4ZcsWMT8/X8zMzBQzMzMdr1+Y7njLLbeIhYWF4saNG8WIiAhOK/2BRYsWidu3bxdLSkrEQ4cOiYsWLRIFQRA3bdokiiLPsyt9f7aRKPJcO8szzzwjbtu2TSwpKRG/+eYbMSsrSwwPDxerq6tFUXSv88zy0k1/+9vfxISEBFGpVIrp6eninj17pI7kcbZu3SoCuOQxe/ZsURQ7pks/99xzYlRUlKhSqcSpU6eKx48f7/IedXV14v333y8GBQWJGo1GnDt3rtjY2CjBp3FPlzu/AMR33nnHsU9LS4v45JNPiqGhoWJAQIB45513ipWVlV3e58yZM+L06dNFf39/MTw8XHzmmWfEtra2Pv407u3nP/+52L9/f1GpVIoRERHi1KlTHcVFFHmeXemH5YXn2jlmzZolxsTEiEqlUoyLixNnzZolnjx50vG6O51nQRRF0bnXcoiIiIhch2NeiIiIyKOwvBAREZFHYXkhIiIij8LyQkRERB6F5YWIiIg8CssLEREReRSWFyIiIvIoLC9ERETkUVheiIiIyKOwvBAREZFHYXkhIiIij8LyQkRERB7l/wOF8vn/IEsWXAAAAABJRU5ErkJggg==",
152 | "text/plain": [
153 | ""
154 | ]
155 | },
156 | "metadata": {},
157 | "output_type": "display_data"
158 | }
159 | ],
160 | "source": [
161 | "x = vp.array(np.array([[-1, -1], [-1, 1], [1, -1], [1, 1]]), requires_grad=False)\n",
162 | "y = vp.array(np.array([[-1], [1], [1], [-1]]), requires_grad=False)\n",
163 | "W_1 = vp.array(np.random.uniform(-1./np.sqrt(2),1./np.sqrt(2),(64, 2)))\n",
164 | "b_1 = vp.array(np.zeros(64))\n",
165 | "W_2 = vp.array(np.random.uniform(-1./np.sqrt(64),1./np.sqrt(64),(16, 64)))\n",
166 | "b_2 = vp.array(np.zeros(16))\n",
167 | "W_3 = vp.array(np.random.uniform(-1./np.sqrt(16),1./np.sqrt(16),(1, 16)))\n",
168 | "b_3 = vp.array(np.zeros(1))\n",
169 | "losses = []\n",
170 | "lr = 0.1\n",
171 | "for i in tqdm(range(500)):\n",
172 | " f_1 = np.tanh(x @ W_1.T + b_1)\n",
173 | " f_2 = np.tanh(f_1 @ W_2.T + b_2)\n",
174 | " z_3 = f_2 @ W_3.T + b_3\n",
175 | " loss = np.mean((z_3 - y) ** 2)\n",
176 | " losses.append(loss)\n",
177 | " loss.backward()\n",
178 | " with grad.no_grad():\n",
179 | " W_1 = W_1 - lr * vp.array_grad(W_1)\n",
180 | " b_1 = b_1 - lr * vp.array_grad(b_1)\n",
181 | " W_2 = W_2 - lr * vp.array_grad(W_2)\n",
182 | " b_2 = b_2 - lr * vp.array_grad(b_2)\n",
183 | " W_3 = W_3 - lr * vp.array_grad(W_3)\n",
184 | " b_3 = b_3 - lr * vp.array_grad(b_3)\n",
185 | " loss.zero_grad()\n",
186 | " lr *= 0.99\n",
187 | "print(z_3)\n",
188 | "plt.plot(losses)\n",
189 | "plt.yscale('log')"
190 | ]
191 | }
192 | ],
193 | "metadata": {
194 | "kernelspec": {
195 | "display_name": "Python 3",
196 | "language": "python",
197 | "name": "python3"
198 | },
199 | "language_info": {
200 | "codemirror_mode": {
201 | "name": "ipython",
202 | "version": 3
203 | },
204 | "file_extension": ".py",
205 | "mimetype": "text/x-python",
206 | "name": "python",
207 | "nbconvert_exporter": "python",
208 | "pygments_lexer": "ipython3",
209 | "version": "3.9.13"
210 | },
211 | "orig_nbformat": 4
212 | },
213 | "nbformat": 4,
214 | "nbformat_minor": 2
215 | }
216 |
--------------------------------------------------------------------------------