03 抽象提取所学内容,合并为通用框架

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

\operatorname{loss} =\frac{1}{n}(y-\hat{y})^{2}

```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')

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值