In [30]:
import numpy as np

miniflow.py


In [2]:
class Node(object):
    def __init__(self, inbound_nodes=[]):
        self.inbound_nodes = inbound_nodes
        self.outbound_nodes = []
        self.value = None
        for n in self.inbound_nodes:
            n.outbound_nodes.append(self)
        
    def forward(self):
        raise NotImplemented

In [3]:
class Input(Node):
    def __init__(self):
        Node.__init__(self)
    def forward(self, value=None):
        if value is not None:
            self.value = value

In [10]:
class Add(Node):
    #def __init__(self, x, y):
    #    Node.__init__(self, [x, y])
    def __init__(self, *inputs):################################ Learn this method
        Node.__init__(self, inputs)
    def forward(self):
        #self.value = self.inbound_nodes[0].value + self.inbound_nodes[1].value
        self.value = 0
        for i in range(len(self.inbound_nodes)):
            self.value += self.inbound_nodes[i].value

In [41]:
"""
class Linear(Node):
    def __init__(self, inputs, weights, bias):
        Node.__init__(self, [inputs, weights, bias])
        
    def forward(self):
        self.value = self.inbound_nodes[2].value
        for i in range(len(self.inbound_nodes[0].value)):
            self.value += self.inbound_nodes[0].value[i] * self.inbound_nodes[1].value[i]
        
        #Solution

        inputs = self.inbound_nodes[0].value
        weights = self.inbound_nodes[1].value
        bias = self.inbound_nodes[2]
        self.value = bias.value
        for x, w in zip(inputs, weights):
            self.value += x * w
        """
class Linear(Node):
    def __init__(self, X, W, b):
        Node.__init__(self, [X, W, b])
    def forward(self):
        num_example = len(self.inbound_nodes[0].value)
        B = self.inbound_nodes[2].value
        for i in range(num_example - 1):
            B = np.vstack((B, self.inbound_nodes[2].value))
        self.value = B
        self.value += np.dot(self.inbound_nodes[0].value, self.inbound_nodes[1].value)
    """
    # Solution
    def forward(self):
        X = self.inbound_nodes[0].value
        W = self.inbound_nodes[1].value
        b = self.inbound_nodes[2].value
        self.value = np.dot(X, W) + b
    """

In [45]:
class Sigmoid(Node):
    def __init__(self, node):
        Node.__init__(self, [node])
    def _sigmoid(self, x):
        return 1.0 / (1.0 + np.exp(-x))
    def forward(self):
        self.value = self._sigmoid(self.inbound_nodes[0].value)

In [17]:
class Mul(Node):
    def __init__(self, *inputs):
        Node.__init__(self, inputs)
        
    def forward(self):
        self.value = 1
        for i in range(len(self.inbound_nodes)):
            self.value *= self.inbound_nodes[i].value

In [ ]:
class MSE(node):
    def __init__(self, y, a):
        Node.__init__(self, [y, a])
    
    def forward(self):
        y = self.inbound_nodes[0].value.reshape(-1, 1)
        a = self.inbound_nodes[1].value.reshape(-1, 1)
        num = len(self.inbound_nodes[0].value)
        self.value = np.sum(np.square(y - a)) / num

In [5]:
def topological_sort(feed_dict):
    """
    Sort generic nodes in topological order using Kahn's Algorithm.

    `feed_dict`: A dictionary where the key is a `Input` node and the value is the respective value feed to that node.

    Returns a list of sorted nodes.
    """

    input_nodes = [n for n in feed_dict.keys()]

    G = {}
    nodes = [n for n in input_nodes]
    while len(nodes) > 0:
        n = nodes.pop(0)
        if n not in G:
            G[n] = {'in': set(), 'out': set()}
        for m in n.outbound_nodes:
            if m not in G:
                G[m] = {'in': set(), 'out': set()}
            G[n]['out'].add(m)
            G[m]['in'].add(n)
            nodes.append(m)

    L = []
    S = set(input_nodes)
    while len(S) > 0:
        n = S.pop()

        if isinstance(n, Input):
            n.value = feed_dict[n]

        L.append(n)
        for m in n.outbound_nodes:
            G[n]['out'].remove(m)
            G[m]['in'].remove(n)
            # if no other incoming edges add to S
            if len(G[m]['in']) == 0:
                S.add(m)
    return L


def forward_pass(output_node, sorted_nodes):
    """
    Performs a forward pass through a list of sorted nodes.

    Arguments:

        `output_node`: A node in the graph, should be the output node (have no outgoing edges).
        `sorted_nodes`: A topologically sorted list of nodes.

    Returns the output Node's value
    """

    for n in sorted_nodes:
        n.forward()

    return output_node.value

nn.py

This script builds and runs a graph with miniflow


In [18]:
# Multiplication
x, y, z = Input(), Input(), Input()
f = Mul(x, y, z)
feed_dict = {x: 10, y: 5, z: 8}

sorted_nodes = topological_sort(feed_dict)
#print(sorted_nodes)
output = forward_pass(f, sorted_nodes)
print("{} + {} + {} = {} (according to miniflow)".format(feed_dict[x], feed_dict[y], feed_dict[z], output))


10 + 5 + 8 = 400 (according to miniflow)

In [29]:
# Linear Combination
inputs, weights, bias = Input(), Input(), Input()
f = Linear(inputs, weights, bias)
feed_dict = {
    inputs: [6, 14, 3],
    weights: [0.5, 0.25, 1.4],
    bias: 2
}
graph = topological_sort(feed_dict)
output = forward_pass(f, graph)
print(output)


12.7

In [42]:
# Linear for Matrix
X, W, b = Input(), Input(), Input()
f = Linear(X, W, b)
X_ = np.array([[-1., -2.], [-1, -2]])
W_ = np.array([[2., -3], [2., -3]])
b_ = np.array([-3., -5])
feed_dict = {X: X_, W: W_, b: b_}
graph = topological_sort(feed_dict)
output = forward_pass(f, graph)
print(output)


[[-9.  4.]
 [-9.  4.]]

In [46]:
# Test Sigmoid
X, W, b = Input(), Input(), Input()
f = Linear(X, W, b)
g = Sigmoid(f)
X_ = np.array([[-1., -2.], [-1, -2]])
W_ = np.array([[2., -3], [2., -3]])
b_ = np.array([-3., -5])
feed_dict = {X: X_, W: W_, b: b_}
graph = topological_sort(feed_dict)
output = forward_pass(g, graph)
print(output)


[[  1.23394576e-04   9.82013790e-01]
 [  1.23394576e-04   9.82013790e-01]]

Gradient Descent


In [57]:
def gradient_descent_update(x, gradx, learning_rate):
    x -= learning_rate * gradx
    return x

In [ ]:
import random
def f(x):
    return x**2 + 5
def df(x):
    return 2 * x
x = random.randint(0, 10000)
learning_rate = 0.1
epochs = 100
for i in range(epochs + 1):
    cost = f(x)
    gradx = df(x)
    print("EPOCH {}: Cost = {:.3f}, x = {:.3f}".format(i, cost, gradx))
    x = gradient_descent_update(x, gradx, learning_rate)

In [ ]: