05. 前向传播解决方案
下面是我的解决方案(我只显示了 Add
类的 forward
方法):
def forward(self):
x_value = self.inbound_nodes[0].value
y_value = self.inbound_nodes[1].value
self.value = x_value + y_value
虽然看起来简单,但是我还是想解释下我为何使用了 inbound_nodes
数组的 x_value
和 y_value
。我们先从 Node
的构造函数开始:
class Node(object):
def __init__(self, inbound_nodes=[]):
# Node(s) from which this Node receives values.
self.inbound_nodes = inbound_nodes
# Removed everything else for brevity.
当 Node
实例化时,inbound_nodes
被设置。
当然,你没有直接使用 Node
,而是使用了 Add
,Add
是 Node
的子类。Add
的构造函数负责将 inbound_nodes
传递给 Node
,发生在以下这段代码中:
class Add(Node):
def __init__(self, x, y):
Node.__init__(self, [x, y]) # calls Node's constructor
...
最后一个问题是为何 node.value
存储了输入的值。通过查看 Input
类找出原因。
class Input(Node):
...
# NOTE: Input node is the only node that may
# receive its value as an argument to forward().
#
# All other node implementations should calculate their
# values from the value of previous nodes, using
# self.inbound_nodes
#
# Example:
# val0 = self.inbound_nodes[0].value
def forward(self, value=None):
if value:
self.value = value
对于 Input
节点,forward
方法似乎设置了 value
。
但是其他节点子类并不是这种情况。对于这些子类,当你运行 topological_sort
时,value
被设置。请看以下代码:
def topological_sort(feed_dict):
...
if isinstance(n, Input):
n.value = feed_dict[n]
...
加法完成了!
继续使 MiniFlow
功能更强大吧。
额外挑战!
下面是不会评分的挑战,因为主要用于检验你的 Python 技能,而不是神经网络技能。
- 你能让
Add
接受任何数量的输入吗 ?例如Add(x, y, z)
。 - 你能创建可以将 n 个输入相乘的类
Mul
吗?
Start Quiz:
"""
No need to change anything here!
If all goes well, this should work after you
modify the Add class in miniflow.py.
"""
from miniflow import *
x, y, z = Input(), Input(), Input()
f = Add(x, y, z)
feed_dict = {x: 4, y: 5, z: 10}
graph = topological_sort(feed_dict)
output = forward_pass(f, graph)
# should output 19
print("{} + {} + {} = {} (according to miniflow)".format(feed_dict[x], feed_dict[y], feed_dict[z], output))
"""
Bonus Challenge!
Write your code in Add (scroll down).
"""
class Node(object):
def __init__(self, inbound_nodes=[]):
# Nodes from which this Node receives values
self.inbound_nodes = inbound_nodes
# Nodes to which this Node passes values
self.outbound_nodes = []
# A calculated value
self.value = None
# Add this node as an outbound node on its inputs.
for n in self.inbound_nodes:
n.outbound_nodes.append(self)
# These will be implemented in a subclass.
def forward(self):
"""
Forward propagation.
Compute the output value based on `inbound_nodes` and
store the result in self.value.
"""
raise NotImplemented
class Input(Node):
def __init__(self):
# An Input Node has no inbound nodes,
# so no need to pass anything to the Node instantiator
Node.__init__(self)
# NOTE: Input Node is the only Node where the value
# may be passed as an argument to forward().
#
# All other Node implementations should get the value
# of the previous nodes from self.inbound_nodes
#
# Example:
# val0 = self.inbound_nodes[0].value
def forward(self, value=None):
# Overwrite the value if one is passed in.
if value is not None:
self.value = value
"""
Can you augment the Add class so that it accepts
any number of nodes as input?
Hint: this may be useful:
https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists
"""
class Add(Node):
# You may need to change this...
def __init__(self, *inputs):
Node.__init__(self, inputs)
def forward(self):
"""
For reference, here's the old way from the last
quiz. You'll want to write code here.
"""
# x_value = self.inbound_nodes[0].value
# y_value = self.inbound_nodes[1].value
# self.value = x_value + y_value
def topological_sort(feed_dict):
"""
Sort the 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