01:实现拓扑排序( python3.9 自带拓扑排序)
① 选择一个没有输入的节点,如果有多个没有输入的接点就任选一个;
② 在图中删去第一步选择的节点,把选择的节点作为我们的访问顺序节点;
③ 检查图是否为空,如果不为空,跳到①;
"""
图的节点与节点的值keys&values
"""
simple_graph = {
'a' = [1,2]
'b' = [2,3]
}
list(simple_graph.values())
#['a','b']
list(simple_graph.keys())
#[[1,2],[2,3]]
#如何将列表合并
reduce(lambda a, b: a + b, list(simple_graph.values()))
#a,b为形参,冒号后面的为函数表达式,表达式后面为形参的取值范围
#[1,2,2,3]
"""
包含a列表但不包含b列表
包含b列表但不包含a列表
"""
list_a = [1,2,3]
list_b = [2,3,4]
#利用集合的交并补
set(list_b) - set(list_a)
#{4}
set(list_b) + set(list_a)
#{1,2,3,4}
图graph.items() 即是将图graph转变为字典dict。
遍历图people_relationship,分别用两个变量将字典dict_items中的keys和values取出来
def topologic(graph):
"""graph:dict
{
x:[linear],
k:[linear],
b:[linear],
linear:[sigmoid],
sigmoid:[loss],
y:[loss],
}
"""
sorted_node = []
while graph:
all_nodes_have_inputs = list(lambda a, b: a + b, list(graph.values()))
all_nodes_have_outputs = list(graph.keys())
all_nodes_only_have_outputs_no_inputs = set(all_nodes_have_outputs) - set(all_nodes_have_inputs)
#仅有输出无输入的的节点
if all_nodes_only_have_outputs_no_inputs:
node = random.choice(list(all_nodes_only_have_outputs_no_inputs))
#任意选择一个节点node
sorted_node.append(node)
#将节点node添加到sorted_node变量中
if len(graph) == 1: sorted_node += graph[node]
#因为pop会将节点及其值都扔掉,会导致最后一个没有输出的节点被扔掉
#因此如果图的长度为1,比如sigmoid:[loss],为了加上最后一个节点loss,取graph的node的值
graph.pop(node)
#将选择好的节点node在图中删去
for _ , links in graph.items():#用_和links两变量遍历graph
if node in links: links.remove(node)
else:
raise TypeError('this graph has circle, which cannot get topological order')
#没有找到只有输出没有输入的节点,给个报错
return sirted_node
#测试一下拓扑排序的读取节点顺序函数
x,k,b,linear,sigmoid,y,loss = 'x','k','b','linear','sigmoid','y','loss'
test_graph = {
x:[linear],
k:[linear],
b:[linear],
linear:[sigmoid],
sigmoid:[loss],
y:[loss],
}
topologica(test_graph)
#按照拓扑排序的顺序读取节点顺序
#['b','k','x','y','linear','sigmoid','loss']
#['x','k','y','b','linear','sigmoid','loss']
#每一次都不一样
抽象节点,每个node都有一个inputs和一个outputs
__init__函数
# Node类:将节点列写出来
#两个下划线开头的函数是声明该属性为私有,不能在类的外部被使用或访问。
#而__init__函数(方法)支持带参数类的初始化,也可为声明该类的属性(类中的变量)。
#__init__函数(方法)的第一个参数必须为self,self是指实例本身,后续参数为自己定义。
#__init__()方法又被称为构造器(constructor)。
#本段代码将节点列写出来了
#能够根据输入节点,得出输出是什么
#e.g node_x.outputs为node_linear
class Node:
def __init__(self, inputs[], name=None, is_trainable=False):
self.inputs = inputs
self.outputs = []
self.name = name #方便可视化节点
self.value = None #用于存值
self.gradients = dict() #存储loss对某个值的偏导
self.is_trainable = is_trainable #默认值为false,只有少数几个是需要被训练的
#加入这段代码,就不必在输入参数中写outputs
for node in inputs:#告诉了这个node的输入,其实就告诉了这个节点的输出
node.outputs.append(self)#输出就是节点本身
def forward(self):
print('I am {}, I calculate myself value!!!'.format(self.name))
#理顺反向传播结构的代码
#def backward(self):
# for n in self.inputs:#遍历输入,self为本节点,n为本节点的输入
# print('get ∂{}/∂{}'.format(self.name, n.name))
# #输出对输入的偏导
def backward(self):
pass
#将对象转化为供解释器读取的形式
def __repr__(self):
return 'Node: {}'.format(self.name)
#告诉输入将节点列写出来
node_x = Placeholder(name = 'x')
#node_x = Node(inputs = Node, output = [node_linear])
#这种写法比较冗余,只要告诉node_x的inputs,那么node_x其实就是inputs的输出
#node_x = Placeholder(inputs = Node),没有输入给个默认值就行了
node_y = Placeholder(name = 'y')
node_k = Placeholder(name = 'k', is_trainable = True)
node_b = Placeholder(name = 'b', is_trainable = True)
node_linear = Linear(inputs = [node_x, node_k, node_y] , name = 'linear')
node_sigmoid = Sigmoid(inputs = [node_linear] , name = 'sigmoid')
node_loss = Loss(inputs = [node_sigmoid, node_y] , name = 'loss')
#Placeholder 继承自 Node类:用于进行人工赋值
#Placeholder一开始没有值,需要我们赋值,因此叫做一个占位符
class Placeholder(Node):#继承Node
def __init__(self, name=None, is_trainable=False):#所有的placeholder没有输入,只要一个name
Node.__init__(self, name=name, is_trainable=is_trainable)
def forward(self):
print('I am {}, I was assigned by human!!!'.formate(self.name))
def backward(self):
print('I got myself gradients: {]'.format(self.outputs[0].gradients[self]))
#直接读取即可,已经分别从Loss, Sigmoid, Linear类中求得并存储
#将对象转化为供解释器读取的形式
def __repr__(self):
return 'Placeholder: {}'.format(self.name)
#Linear
Linear = kx+b
```python
#Linear:kx+b
class Linear(Node):#继承Node
def __init__(self, x, k, b, name=None):#上一个输入为node_x,node_k,node_b节点
Node.__init__(self, inputs=[x, k, b], name=name)
def forward(self):
x, k, b = self.inputs[0], self.inputs[1], self.inputs[2]
#先从inputs里获取x, k, b
#同时也是Linear一个向后传值的过程
self.value = k.value * x.value + b.value
print('I am {}, I calculate myself value : {} by MYSELF!!!'.format(self.name, self.value))
def backward(self):
self.gradients[self.inputs[0]] = '*'join([self.outputs[0].gradients[self],
'∂{}/∂{}'.format(self.name, self.inputs[0].name)])
self.gradients[self.inputs[1]] = '*'join([self.outputs[0].gradients[self],
'∂{}/∂{}'.format(self.name, self.inputs[1].name)])
#self为linear,gradients为loss对某个参数求偏导,某个参数self.inputs[1]为k,即为loss对k求偏导
#self.outputs[0].gradients[self]为linear的输出sigmoid的gradients,就是为了求得(loss对linear的偏导),self.outputs[0]只是因为此偏导在sigmoid之下求得的
self.gradients[self.inputs[2]] = '*'join([self.outputs[0].gradients[self],
'∂{}/∂{}'.format(self.name, self.inputs[2].name)])
#将偏导公式加入
x, k, b = self.inputs[0], self.inputs[1], self.inputs[2]
self.gradients[self.inputs[0]] = self.outputs[0].gradients[self] * k.value
self.gradients[self.inputs[1]] = self.outputs[0].gradients[self] * x.value
self.gradients[self.inputs[2]] = self.outputs[0].gradients[self] * 1
print('self.gradients[self.inputs[0]] |{}.format(self.gradients[self.inputs[0]])
print('self.gradients[self.inputs[1]] |{}.format(self.gradients[self.inputs[1]])
print('self.gradients[self.inputs[2]] |{}.format(self.gradients[self.inputs[2]])
#将对象转化为供解释器读取的形式
def __repr__(self):
return 'Linear: {}'.format(self.name)
```
#Sigmoid
sigmoid的偏导 = sigmoid(1 - sigmoid)
```python
#Sigmoid:1/1+(e)^-x
class Linear(Node):#继承Node
def __init__(self, x, name=None):#上一个输入为node_Linear节点,用x代表
Node.__init__(self, inputs=[x], name=name)
def _sigmoid(self, x):
return 1 / ( 1 + np.exp(-x) )
def forward(self):
x = self.inputs[0]
#先从inputs里获取x
self.value = self._sigmoid(x.value)
print('I am {}, I calculate myself value : {} by MYSELF!!!'.format(self.name, self.value))
def backward(self):
x = self.inputs[0]
#self.gradients[self.inputs[0]] = '*'.join([self.outputs[0].gradients[self],
'∂{}/∂{}'.format(self.name, self.inputs[1].name)])
self.gradients[self.inputs[0]] = self.outputs[0].gradients[self] * (self._sigmoid(x.value) * (1 - self._sigmoid(x.value)))
#self为Sigmoid,gradients为loss对某个值求偏导,某个值[self.inputs[0]]为linear, self.gradients[self.inputs[0]]代表loss对linear的的偏导
#self.output[0]sigmoid输出为loss,loss的loss对sigmoid的偏导(即loss对yhat的偏导,yhat就是sigmoid)
#'*'.join,用'*'穿插在两个文本之间
print('self.gradients[self.inputs[0]] |{}.format(self.gradients[self.inputs[0]])
#将对象转化为供解释器读取的形式
def __repr__(self):
return 'Linear: {}'.format(self.name)
```
#Loss:MES
```Python
#Loss:MES
class Loss(Node):#继承Node
def __init__(self, y, yhat name=None):#上一个输入为node_Linear节点,用x代表
Node.__init__(self, inputs=[y, yhat], name=name)
def _sigmoid(self, x):
return 1 / ( 1 + np.exp(-x) )
def forward(self):
y = self.inputs[0]
yhat = self.inputs[1]
#先从inputs里获取y,yhat
self.value = np.mean((y.value - yhat.value)**2)
print('I am {}, I calculate myself value : {} by MYSELF!!!'.format(self.name, self.value))
def backward(self):
#self.gradients[self.inputs[0]] = '∂{}/∂{}'.format(self.name, self.inputs[0].name)
#self.gradients[self.inputs[1]] = '∂{}/∂{}'.format(self.name, self.inputs[1].name)
#self为Loss,gradients为loss对某个值求偏导,某个值[self.inputs[0]]为y, [self.inputs[1]]为yhat
self.gradients[self.inputs[0]] = 2 * np.mean(y.value - yhat.value)
self.gradients[self.inputs[1]] = - 2 * np.mean(y.value - yhat.value)
print('self.gradients[self.inputs[0]] |{}.format(self.gradients[self.inputs[0]])
print('self.gradients[self.inputs[1]] |{}.format(self.gradients[self.inputs[1]])
#将对象转化为供解释器读取的形式
def __repr__(self):
return 'Loss: {}'.format(self.name)
```
```python
#Loss:MES
class Loss(Node):#继承Node
def __init__(self, y, yhat name=None):#上一个输入为node_Linear节点,用x代表
Node.__init__(self, inputs=[y, yhat], name=name)
def _sigmoid(self, x):
return 1 / ( 1 + np.exp(-x) )
def forward(self):
y = self.inputs[0]
yhat = self.inputs[1]
#先从inputs里获取y,yhat
self.value = np.mean((y.value - yhat.value)**2)
print('I am {}, I calculate myself value : {} by MYSELF!!!'.format(self.name, self.value))
def backward(self):
self.gradients[self.inputs[0]] = '∂{}/∂{}'.format(self.name, self.inputs[0].name)
self.gradients[self.inputs[1]] = '∂{}/∂{}'.format(self.name, self.inputs[1].name)
#self为Loss,gradients为loss对某个值求偏导,某个值[self.inputs[0]]为y, [self.inputs[1]]为yhat即sigmoid
print('self.gradients[self.inputs[0]] |{}.format(self.gradients[self.inputs[0]])
print('self.gradients[self.inputs[1]] |{}.format(self.gradients[self.inputs[1]])
#将对象转化为供解释器读取的形式
def __repr__(self):
return 'Loss: {}'.format(self.name)
```
# convert_feed_dict_to_graph() 函数将字典转换为图
#节点连接成图,先检测边沿节点(边沿节点的值是需要被补充的,因此命名为need_expand)
#如何将节点像串珠子一样串起来,变成一张图?
#即将对应关系列写出来
#need_expand = [node_x,node_y,node_k,node_b]#需要被补充的外层节点need_expand
#喂值
feed_dict = {
node_x: 3,
node_y: random.random()
node_k: random.random()
node_b: 0.38
}
from collections import defaultdict
def convert_feed_dict_to_graph(feed_dict):
need_expand = [n for n in feed_dict]#用n遍历feed_dict,将遍历的值放在n变量中
computing_graph = defaultdict(list)
while need_expand: #当需要被补充的节点need_expand不为空的时候
n = need_expand.pop(0)
#我们从need_expand中取出一个点
if n in computing_graph : continue
#如果这个点已经在图中了,就重新循环
#如果这个点不在图中,就加进去
if isinstance(n, Placeholder): n.value = feed_dict[n]
#如果n是Placeholder类型的,那么就将feed_dict[n]里的值给n.value
for m in n.outputs:
computing_graph[n].append(m)
need_expand.append(m)
#写到这里已经将这些节点变成了一个图
sorted_nodes = topologic(convert_feed_dict_to_graph(feed_dict))
#对得到的图进行拓扑排序,按照节点的边缘程度排序
#经过拓扑排序,后续节点就一定能在后面计算
#[Node: b, Node: x, Node: y, Node: k, Node: linear, Node: sigmoid, Node: ]
# from collections import defaultdict 语句的用法
原文链接:https://blog.csdn.net/qq_36134437/article/details/103052650
使用普通字典,当关键字不在字典里时,会发生报错。这时defaultdict就能派上用场,defaultdict的作用在于,字典里的key不存在但被查找时,返回的不是keyError而是一个默认值,默认值是工厂函数的默认值。
#如何使用defaultdict
defaultdict接受一个工厂函数作为参数,如下来构造:
dict =defaultdict( factory_function)
这个factory_function可以是list、set、str等等,作用是当key不存在时,返回的是工厂函数的默认值,比如list对应[ ],str对应的是空字符串,set对应set( ),int对应0,如下举例:
02:模拟神经网络的计算过程
① 前向传播forward
# Feedforward
for node in sorted_nodes:
node.forword()
#没有添加Placeholder、Linear、Sigmoid、Loss类之前的前向传播,不具有计算能力
I am x,I was assigned by human!!!
I am y,I was assigned by human!!!
I am k,I was assigned by human!!!
I am b,I was assigned by human!!!
I am linear,I am calculate myself value by MYSELF!!!
I am sigmoid,I am calculate myself value by MYSELF!!!
I am loss,I am calculate myself value by MYSELF!!!
#添加了Placeholder、Linear、Sigmoid、Loss类后的前向传播,具有赋值与计算能力
I am x,I was assigned value:3 by human!!!
I am k,I was assigned value:0.5482045137858927by human!!!
I am y,I was assigned value:0.23009493812298676 by human!!!
I am b,I was assigned value:0.38 by human!!!
I am linear,I calculate myself value:2.0246135413576782 by myself!!!
I am sigmoid,I calculate myself value:0.8833572171043678 by myself!!!
I am loss,I calculate myself value:0.42675160513994764 by myself!!!
② 后向传播backward
# Backward
for node in sorted_nodes [ : : -1]:
#节点的反向
print('I am: {}'.format(node.name))
node.backward()
#def backward(self):
# for n in self.inputs:#遍历输入,self为本节点,n为本节点的输入
# print('get ∂{}/∂{}'.format(self.name, n.name))
# 输出对输入的偏导I am: loss
get ∂loss / ∂y
get ∂loss / ∂sigmoid
I am: sigmoid
get ∂sigmoid / ∂linear
I am: linear
get ∂linear / ∂x
get ∂linear / ∂k
get ∂linear / ∂b
I am: k
I am: x
I am: y
I am: b
#在每一类里面编写backward函数
#先整理出文字表达式
I am: loss
self.gradients[self.inputs[0]] | ∂loss / ∂y
self.gradients[self.inputs[1]] | ∂loss / ∂sigmoid
I am: sigmoid
self.gradients[self.inputs[0]] | ∂loss / ∂sigmoid * ∂sigmoid / ∂linear
I am: linear
self.gradients[self.inputs[0]] | ∂loss / ∂sigmoid * ∂sigmoid / ∂linear * ∂linear / ∂x
self.gradients[self.inputs[1]] | ∂loss / ∂sigmoid * ∂sigmoid / ∂linear * ∂linear / ∂k
self.gradients[self.inputs[2]] | ∂loss / ∂sigmoid * ∂sigmoid / ∂linear * ∂linear / ∂b
I am: b
I got myself gradients: ∂loss / ∂sigmoid * ∂sigmoid / ∂linear * ∂linear / ∂b
I am: y
I got myself gradients: ∂loss / ∂y
I am: x
I got myself gradients: ∂loss / ∂sigmoid * ∂sigmoid / ∂linear * ∂linear / ∂x
I am: k
I got myself gradients: ∂loss / ∂sigmoid * ∂sigmoid / ∂linear * ∂linear / ∂k
#将数值公式代入后,能求出Loss对各节点的公式
③ 优化更新optimize
#optimaize
learning_rate = 1e-1
for node in sorted_nodes:
if node.is_trainable:
#对k和b的值进行更新,将k和b的is_trainable属性设置为true
node.value = node.value + -1 * node.gradients[node] * learning_rate
cmp = 'large' if node.gradients[node] > 0 else 'small'
#梯度大于0时,说明值过大
print("{}’ value is too {}, I need update myself".format(node.name))
```python
#Feedforward 求值
def forward(graph_sorted_nodes):
for node in graph_sorted_nodes:
node.forward()
if isinstance(node,Loss):
print('loss value:{}',format(node.value))
#Backward Propagation 求导
def backward(graph_sorted_nodes):
for node in sorted_nodes[::-1]:
print('\nI am: {}'.format(node.name))
node.backward()
#epoch 一轮为先前向传播再反向传播
def run_one_epoch(graph_sorted_nodes):
forward(graph_sorted_nodes)
backward(graph_sorted_nodes)
#Optimize 更新
def optimize(graph_nodes, learning_rate=1e-3):
for node in graph_nodes:
if node.is_trainable:
node.value = node.value + -1*node.gradients[node] * learning_rate
cmp = 'large' if node.grad.gradients[node] >0 else 'small'
print("{}’ value is too {}, I need update myself".format(node.name, cmp))
```
一次完整的求值-求导-再更新
run_one_epoch(sorted_nodes)
optimize(sorted_nodes)
loss_history = []
for _ in range(100):
run_one_epoch(sorted_nodes)
__loss_node = sorted_nodes[-1]#loss节点是最后一个节点
assert isinstance(loss_node, Loss)#确保__loss_node 是Loss类
loss_history.append(__loss_node.value)#将loss值加入到loss_history中,用于画loss随着每个epoch变化的函数图
optimize(sorted_nodes, learning_rate=1e-1)
import matplotlib.pyplot as plt
plt.plot(loss_history)
④ 拟合效果
参数更新完成之后,看一下拟合的yhat的效果
sigmoid(sorted_notes[1].value * sorted_nodes[3].value + sorted_nodes[0].value)
#结果:0.6876006253543884
sorted_nodes[2].value
#结果:0.687366459078083
03:如何处理多维数据
① 数据用numpy矩阵运算;
② 多维数据的拟合结果比一维数据好很多,那是因为参考的维度多了,误差更小,结果更精确;
③ 多维版本的拟合函数会很复杂,但拟合函数可以选取两个显著特征+yhat构建一个三维函数图;
e.g 在波士顿房价问题中,RM(卧室数量)和LSTAT(犯罪率)是两个显著特征
随着RM的增加,predicted-Price是上升的;随着% of lower state的上升房价是下降的
04:如何发布程序
发布源代码到python中,由此,可以想TensorFlow等包一样进行pip调用
按照说明书来发布:
https://packaging.python.org/tutorials/packaging-projects/
packaging_tutorial/
├── LICENSE # 发布代码时,告诉别人这个代码的权限(收费/免费)
├── pyproject.toml
├── README.md # 用于说明这个程序怎么用,能干什么,如何联系作者
├── setup.cfg
├── setup.py # 设置工具,说明版本关系,以及如何pip installs work
├── src/
│ └── nn/ # 和神经元有关的代码
│ └── __init__.py
│ └── core.py
│ └── utils/
│ └── __init__.py
│ └── utilities.py # 工具:拓扑排序,前向传播,后向传播
└── __init__.py
附:
python编程小技巧
变量命名时不区分list和tuple,就用名称+s即可
name_list = ['a' , 'b'] #如果是个列表
def parse_name(names):
print(names)
parse_name(name_list)
#['a' , 'b']
name_list = ('a' , 'b') #如果是个元组
def parse_name(names):
print(names)
parse_name(name_list)
#('a' , 'b')
#没必要严格区分,因此一般使用names
names = ('a' , 'b') #如果是个元组
def parse_name(name):
print(name)
parse_name(names)
#('a' , 'b')