31 KiB
31 KiB
In [1]:
import autograd.numpy as np # We need to use this numpy wrapper to make automatic differentiation work later
from autograd import grad, elementwise_grad
from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score
# Defining some activation functions
def ReLU(z):
return np.where(z > 0, z, 0)
# Derivative of the ReLU function
def ReLU_der(z):
return np.where(z > 0, 1, 0)
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def mse(predict, target):
return np.mean((predict - target) ** 2)In [2]:
def feed_forward_one_layer(W, b, x):
z = ...
a = ...
return a
def cost_one_layer(W, b, x, target):
predict = feed_forward_one_layer(W, b, x)
return mse(predict, target)
x = np.random.rand(2)
target = np.random.rand(3)
W = ...
b = ...In [3]:
autograd_one_layer = grad(cost_one_layer, [0, 1])
W_g, b_g = autograd_one_layer(W, b, x, target)
print(W_g, b_g)[0;31m---------------------------------------------------------------------------[0m
[0;31mKeyError[0m Traceback (most recent call last)
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/tracer.py:118[0m, in [0;36mnew_box[0;34m(value, trace, node)[0m
[1;32m 117[0m [38;5;28;01mtry[39;00m:
[0;32m--> 118[0m [38;5;28;01mreturn[39;00m [43mbox_type_mappings[49m[43m[[49m[38;5;28;43mtype[39;49m[43m([49m[43mvalue[49m[43m)[49m[43m][49m(value, trace, node)
[1;32m 119[0m [38;5;28;01mexcept[39;00m [38;5;167;01mKeyError[39;00m:
[0;31mKeyError[0m: <class 'ellipsis'>
During handling of the above exception, another exception occurred:
[0;31mTypeError[0m Traceback (most recent call last)
Cell [0;32mIn[3], line 2[0m
[1;32m 1[0m autograd_one_layer [38;5;241m=[39m grad(cost_one_layer, [[38;5;241m0[39m, [38;5;241m1[39m])
[0;32m----> 2[0m W_g, b_g [38;5;241m=[39m [43mautograd_one_layer[49m[43m([49m[43mW[49m[43m,[49m[43m [49m[43mb[49m[43m,[49m[43m [49m[43mx[49m[43m,[49m[43m [49m[43mtarget[49m[43m)[49m
[1;32m 3[0m [38;5;28mprint[39m(W_g, b_g)
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/wrap_util.py:20[0m, in [0;36munary_to_nary.<locals>.nary_operator.<locals>.nary_f[0;34m(*args, **kwargs)[0m
[1;32m 18[0m [38;5;28;01melse[39;00m:
[1;32m 19[0m x [38;5;241m=[39m [38;5;28mtuple[39m(args[i] [38;5;28;01mfor[39;00m i [38;5;129;01min[39;00m argnum)
[0;32m---> 20[0m [38;5;28;01mreturn[39;00m [43munary_operator[49m[43m([49m[43munary_f[49m[43m,[49m[43m [49m[43mx[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[43mnary_op_args[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mnary_op_kwargs[49m[43m)[49m
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/differential_operators.py:28[0m, in [0;36mgrad[0;34m(fun, x)[0m
[1;32m 21[0m [38;5;129m@unary_to_nary[39m
[1;32m 22[0m [38;5;28;01mdef[39;00m [38;5;21mgrad[39m(fun, x):
[1;32m 23[0m [38;5;250m [39m[38;5;124;03m"""[39;00m
[1;32m 24[0m [38;5;124;03m Returns a function which computes the gradient of `fun` with respect to[39;00m
[1;32m 25[0m [38;5;124;03m positional argument number `argnum`. The returned function takes the same[39;00m
[1;32m 26[0m [38;5;124;03m arguments as `fun`, but returns the gradient instead. The function `fun`[39;00m
[1;32m 27[0m [38;5;124;03m should be scalar-valued. The gradient has the same type as the argument."""[39;00m
[0;32m---> 28[0m vjp, ans [38;5;241m=[39m [43m_make_vjp[49m[43m([49m[43mfun[49m[43m,[49m[43m [49m[43mx[49m[43m)[49m
[1;32m 29[0m [38;5;28;01mif[39;00m [38;5;129;01mnot[39;00m vspace(ans)[38;5;241m.[39msize [38;5;241m==[39m [38;5;241m1[39m:
[1;32m 30[0m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m([38;5;124m"[39m[38;5;124mGrad only applies to real scalar-output functions. [39m[38;5;124m"[39m
[1;32m 31[0m [38;5;124m"[39m[38;5;124mTry jacobian, elementwise_grad or holomorphic_grad.[39m[38;5;124m"[39m)
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/core.py:10[0m, in [0;36mmake_vjp[0;34m(fun, x)[0m
[1;32m 8[0m [38;5;28;01mdef[39;00m [38;5;21mmake_vjp[39m(fun, x):
[1;32m 9[0m start_node [38;5;241m=[39m VJPNode[38;5;241m.[39mnew_root()
[0;32m---> 10[0m end_value, end_node [38;5;241m=[39m [43mtrace[49m[43m([49m[43mstart_node[49m[43m,[49m[43m [49m[43mfun[49m[43m,[49m[43m [49m[43mx[49m[43m)[49m
[1;32m 11[0m [38;5;28;01mif[39;00m end_node [38;5;129;01mis[39;00m [38;5;28;01mNone[39;00m:
[1;32m 12[0m [38;5;28;01mdef[39;00m [38;5;21mvjp[39m(g): [38;5;28;01mreturn[39;00m vspace(x)[38;5;241m.[39mzeros()
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/tracer.py:10[0m, in [0;36mtrace[0;34m(start_node, fun, x)[0m
[1;32m 8[0m [38;5;28;01mwith[39;00m trace_stack[38;5;241m.[39mnew_trace() [38;5;28;01mas[39;00m t:
[1;32m 9[0m start_box [38;5;241m=[39m new_box(x, t, start_node)
[0;32m---> 10[0m end_box [38;5;241m=[39m [43mfun[49m[43m([49m[43mstart_box[49m[43m)[49m
[1;32m 11[0m [38;5;28;01mif[39;00m isbox(end_box) [38;5;129;01mand[39;00m end_box[38;5;241m.[39m_trace [38;5;241m==[39m start_box[38;5;241m.[39m_trace:
[1;32m 12[0m [38;5;28;01mreturn[39;00m end_box[38;5;241m.[39m_value, end_box[38;5;241m.[39m_node
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/wrap_util.py:14[0m, in [0;36munary_to_nary.<locals>.nary_operator.<locals>.nary_f.<locals>.unary_f[0;34m(x)[0m
[1;32m 12[0m subargs [38;5;241m=[39m subvals(args, [(argnum, x)])
[1;32m 13[0m [38;5;28;01melse[39;00m:
[0;32m---> 14[0m subargs [38;5;241m=[39m [43msubvals[49m[43m([49m[43margs[49m[43m,[49m[43m [49m[38;5;28;43mzip[39;49m[43m([49m[43margnum[49m[43m,[49m[43m [49m[43mx[49m[43m)[49m[43m)[49m
[1;32m 15[0m [38;5;28;01mreturn[39;00m fun([38;5;241m*[39msubargs, [38;5;241m*[39m[38;5;241m*[39mkwargs)
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/util.py:6[0m, in [0;36msubvals[0;34m(x, ivs)[0m
[1;32m 4[0m [38;5;28;01mdef[39;00m [38;5;21msubvals[39m(x, ivs):
[1;32m 5[0m x_ [38;5;241m=[39m [38;5;28mlist[39m(x)
[0;32m----> 6[0m [38;5;28;01mfor[39;00m i, v [38;5;129;01min[39;00m ivs:
[1;32m 7[0m x_[i] [38;5;241m=[39m v
[1;32m 8[0m [38;5;28;01mreturn[39;00m [38;5;28mtuple[39m(x_)
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/tracer.py:46[0m, in [0;36mprimitive.<locals>.f_wrapped[0;34m(*args, **kwargs)[0m
[1;32m 44[0m ans [38;5;241m=[39m f_wrapped([38;5;241m*[39margvals, [38;5;241m*[39m[38;5;241m*[39mkwargs)
[1;32m 45[0m node [38;5;241m=[39m node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
[0;32m---> 46[0m [38;5;28;01mreturn[39;00m [43mnew_box[49m[43m([49m[43mans[49m[43m,[49m[43m [49m[43mtrace[49m[43m,[49m[43m [49m[43mnode[49m[43m)[49m
[1;32m 47[0m [38;5;28;01melse[39;00m:
[1;32m 48[0m [38;5;28;01mreturn[39;00m f_raw([38;5;241m*[39margs, [38;5;241m*[39m[38;5;241m*[39mkwargs)
File [0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/autograd/tracer.py:120[0m, in [0;36mnew_box[0;34m(value, trace, node)[0m
[1;32m 118[0m [38;5;28;01mreturn[39;00m box_type_mappings[[38;5;28mtype[39m(value)](value, trace, node)
[1;32m 119[0m [38;5;28;01mexcept[39;00m [38;5;167;01mKeyError[39;00m:
[0;32m--> 120[0m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m([38;5;124m"[39m[38;5;124mCan[39m[38;5;124m'[39m[38;5;124mt differentiate w.r.t. type [39m[38;5;132;01m{}[39;00m[38;5;124m"[39m[38;5;241m.[39mformat([38;5;28mtype[39m(value)))
[0;31mTypeError[0m: Can't differentiate w.r.t. type <class 'ellipsis'>In [ ]:
z = W @ x + b
a = sigmoid(z)
predict = a
def mse_der(predict, target):
return ...
print(mse_der(predict, target))
cost_autograd = grad(mse, 0)
print(cost_autograd(predict, target))In [ ]:
def sigmoid_der(z):
return ...
print(sigmoid_der(z))
sigmoid_autograd = elementwise_grad(sigmoid, 0)
print(sigmoid_autograd(z))In [54]:
dC_da = ...
dC_dz = ...In [ ]:
dC_da = ...
dC_dz = ...
dC_dW = ...
dC_db = ...
print(dC_dW, dC_db)In [ ]:
W_g, b_g = autograd_one_layer(W, b, x, target)
print(W_g, b_g)In [59]:
x = np.random.rand(2)
target = np.random.rand(4)
W1 = np.random.rand(3, 2)
b1 = np.random.rand(3)
W2 = np.random.rand(4, 3)
b2 = np.random.rand(4)
layers = [(W1, b1), (W2, b2)]In [60]:
z1 = W1 @ x + b1
a1 = sigmoid(z1)
z2 = W2 @ a1 + b2
a2 = sigmoid(z2)In [61]:
dC_da2 = ...
dC_dz2 = ...
dC_dW2 = ...
dC_db2 = ...In [ ]:
In [63]:
dC_da1 = ...
dC_dz1 = ...
dC_dW1 = ...
dC_db1 = ...In [ ]:
print(dC_dW1, dC_db1)
print(dC_dW2, dC_db2)In [67]:
def feed_forward_two_layers(layers, x):
W1, b1 = layers[0]
z1 = W1 @ x + b1
a1 = sigmoid(z1)
W2, b2 = layers[1]
z2 = W2 @ a1 + b2
a2 = sigmoid(z2)
return a2In [ ]:
def cost_two_layers(layers, x, target):
predict = feed_forward_two_layers(layers, x)
return mse(predict, target)
grad_two_layers = grad(cost_two_layers, 0)
grad_two_layers(layers, x, target)In [4]:
def create_layers(network_input_size, layer_output_sizes):
layers = []
i_size = network_input_size
for layer_output_size in layer_output_sizes:
W = np.random.randn(layer_output_size, i_size)
b = np.random.randn(layer_output_size)
layers.append((W, b))
i_size = layer_output_size
return layers
def feed_forward(input, layers, activation_funcs):
a = input
for (W, b), activation_func in zip(layers, activation_funcs):
z = W @ a + b
a = activation_func(z)
return a
def cost(layers, input, activation_funcs, target):
predict = feed_forward(input, layers, activation_funcs)
return mse(predict, target)In [5]:
def feed_forward_saver(input, layers, activation_funcs):
layer_inputs = []
zs = []
a = input
for (W, b), activation_func in zip(layers, activation_funcs):
layer_inputs.append(a)
z = W @ a + b
a = activation_func(z)
zs.append(z)
return layer_inputs, zs, aIn [ ]:
def backpropagation(
input, layers, activation_funcs, target, activation_ders, cost_der=mse_der
):
layer_inputs, zs, predict = feed_forward_saver(input, layers, activation_funcs)
layer_grads = [() for layer in layers]
# We loop over the layers, from the last to the first
for i in reversed(range(len(layers))):
layer_input, z, activation_der = layer_inputs[i], zs[i], activation_ders[i]
if i == len(layers) - 1:
# For last layer we use cost derivative as dC_da(L) can be computed directly
dC_da = ...
else:
# For other layers we build on previous z derivative, as dC_da(i) = dC_dz(i+1) * dz(i+1)_da(i)
(W, b) = layers[i + 1]
dC_da = ...
dC_dz = ...
dC_dW = ...
dC_db = ...
layer_grads[i] = (dC_dW, dC_db)
return layer_gradsIn [ ]:
network_input_size = 2
layer_output_sizes = [3, 4]
activation_funcs = [sigmoid, ReLU]
activation_ders = [sigmoid_der, ReLU_der]
layers = create_layers(network_input_size, layer_output_sizes)
x = np.random.rand(network_input_size)
target = np.random.rand(4)In [ ]:
layer_grads = backpropagation(x, layers, activation_funcs, target, activation_ders)
print(layer_grads)In [ ]:
cost_grad = grad(cost, 0)
cost_grad(layers, x, [sigmoid, ReLU], target)In [ ]:
class NeuralNetwork:
def __init__(
self,
network_input_size,
layer_output_sizes,
activation_funcs,
activation_ders,
cost_fun,
cost_der,
):
pass
def predict(self, inputs):
# Simple feed forward pass
pass
def cost(self, inputs, targets):
pass
def _feed_forward_saver(self, inputs):
pass
def compute_gradient(self, inputs, targets):
pass
def update_weights(self, layer_grads):
pass
# These last two methods are not needed in the project, but they can be nice to have! The first one has a layers parameter so that you can use autograd on it
def autograd_compliant_predict(self, layers, inputs):
pass
def autograd_gradient(self, inputs, targets):
pass