建模杂谈系列234 基于图的程序改造

news2024/11/13 19:15:50

说明

为了进一步提升程序设计与运维的可靠性,我觉得(目前看来)只有依赖图的结构。

提升主要包含如下方面:

  • 1 程序结构的简洁性:节点和边
  • 2 程序执行的可视化:交通图(红、黄、绿)
  • 3 程序支持的逻辑复杂性。子图嵌套。

其实这种方法在很多AI工具(例如KNIME)都已经使用了,似乎这也是唯一可行的方法。之所以要自己开发,还是基于一条假设:现成的工具永远无法实现你最重要的20%需求。

抛开一些可视化效果不说,目前的图工具又有什么特别棒的地方呢?

内容

1 想法

通过结构上的设计来确保万无一失,而不是依赖主观意愿的不出错

子图。子图是最常见的管理单位,一个子图一定具有的结构是Input、Core和Output。Core也可以称为子图核心,从运行上可以认为是一个服务,或者是一个以固定的定时程序来替代(间歇性服务)。

子图是一个合理的功能划分,通常不超过10个节点,这个规模属于人能容易看清楚,而工作量大约为几个人天的规模。

子图在某种程度上也可以认为是一个大号的节点。节点会包含某种处理和存储,简单的时候,可以理解为节点“run"一个函数,然后根据input(s)处理为output(s)。当处理比较复杂的时候,那么就是一个子图,里面有若干个节点的处理流程。所以子图同样需要类似input(s)处理为output(s), 同样,子图也有run方法,但是子图的run会按顺序(BFS)若干节点的run。

在实际运行时,最小的单位应该就是子图,因为子图有核心,可以改变结构,最重要的可以进行常态服务。如果某块工作只有一个节点,那么这个节点也应该注册到某个子图。

关联操作。之所以要将节点注册到子图,最大的原因是这些操作(结果)间存在关联,将它们放在一起执行是合适的:产生的中间结果可以在一次图会话中暂存,可以切进去反复调试、修改。

子图模板与运行子图。我们基于某项具体的任务或者业务进行(逻辑)处理上的设计,所形成的图称为子图模板,例如实体识别的处理是一个子图模板,当用于业务A的时候,启动一个子图称为运行子图,运行子图是一个子图模板的副本,但挂载了与业务相关的资源(例如磁盘、CPU、GPU),并保存了相应的执行信息,例如日志。

子图最终是以嵌套的方式构成更复杂的结构的。因为子图和节点的结构(input和output)以及调用方法都是固定的,所以最终可以视为都是某一张子图的运行。这样做的好处是,同样所有的结构都是高度相似的。当然也有坏处,就是我们无法得知一张子图到底有多"深",这会带来运行时间和效率的不确定性。所以在设计每一个子图的时候,要让其容纳足够的逻辑复杂性。随着计算机性能的提升,处理复杂性并维持逻辑稳定性显然是更重要的。只要确保每次子图的执行时间是可行的,那么通过分布式系统可以同时计算大量的任务。

最终,一个大的问题变为了一个子图设计问题,这确保了各个组件间自动的关联。子图和节点有着同样的结构,这样在开发时又是高度一致,并且简洁的。

2 尝试

首先,先确定本地的图工具为Networkx。这是一个比较经典的网络工具包,本身可以进行一些常见的图计算。这里,我主要利用这个包本身的网络构造方法,未来可能还会用一些路径算法,来计算最小距离之类的。当本地的开发成熟后,网络信息将以Json形式同步到Mongo和Neo4j,从而实现全局的存储、应用和搜索(图查询)。

下面定义了一个网络,是一个数据处理的流程(相对粗粒度)。

  • 1 可以感觉到,定义到6个节点时,开始略微觉得有点烦,但还可以接受。我觉得这个就是一个子图合适的尺寸。一般的任务可以大致以3层子图来进行规划,这样容纳的就是6**3 ~ 216个节点。
  • 2 每个节点的属性实际上就是一个标准字典,可以容纳函数。但是要进行Json保存的时候就会有问题(函数无法Json序列化)。
# ===============  例子

import networkx as nx
import matplotlib.pyplot as plt

# =======================>  图的定义
# Create a directed graph
G = nx.DiGraph()

def hello():
    print('This is Node Running ...')

G.add_node(1)
G.nodes[1]['name'] = 'M'
G.nodes[1]['description'] = '数据1'
G.nodes[1]['run'] =  hello

G.add_node(2)
G.nodes[2]['name'] = 'C'
G.nodes[2]['description'] = '数据2'
G.nodes[2]['run'] =  hello

G.add_node(3)
G.nodes[3]['name'] = 'MergeData'
G.nodes[3]['description'] = '合并数据'
G.nodes[3]['run'] = hello

G.add_edge(1,3)
G.add_edge(2,3)

G.add_node(4)
G.nodes[4]['name'] = 'FeatureHorizonal'
G.nodes[4]['description'] = '特征处理(横向)'
G.nodes[4]['run'] = hello

G.add_edge(3,4)

G.add_node(5)
G.nodes[5]['name'] = 'ImbalanceSample'
G.nodes[5]['description'] = '不等比采样'
G.nodes[5]['run'] = hello

G.add_edge(4,5)

G.add_node(6)
G.nodes[6]['name'] = 'FeatureVertical'
G.nodes[6]['description'] = '特征处理(纵向)'
G.nodes[6]['run'] = hello

G.add_edge(5,6)

# =======================>  图的绘画
# 获取节点标签属性
node_labels = nx.get_node_attributes(G, "name")

# pos = nx.shell_layout(G)
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=False,  node_size=1000, font_size=12, font_color='black', arrows=True)
# 绘制节点标签
_ = nx.draw_networkx_labels(G, pos, labels=node_labels)

这个画的图不是上面的网络,只是证明可以很容易给不同的节点标色,这个在可视化上很重要。
在这里插入图片描述

2.1 BFS

在任何一个子图中,比较重要的就是节点的执行顺序。简单的来说,我们可以认为节点是按层分布的,按照顺序去执行这些层的节点就可以了。所以,首先通过nx实现子图的BFS。

按上面的想法,一个子图中的节点可能是一个节点,也可能是一张子图,但是从执行上,我们可以都当做是节点。只不过在真实执行时,如果对应的节点是子图类型,那么在其内部会再进行展开。对于任何一个子图,总是需要先通过BFS确定内部节点或子图的执行顺序

  • 1 在图中选取入度为0的节点,作为第一层
  • 2 遍历第一层中的节点,选择以这些节点作为起点的边,边的另一端就是第二层几点
  • 3 循环以致图中无节点
# 查看图中节点的入度
G.in_degree()

# 获取入度为 0 的节点列表
nodes_with_in_degree_zero = [node for node, in_degree in G.in_degree() if in_degree == 0]

layer_dict = {}

# 初始化节点
init_node_list = [node for node, in_degree in G.in_degree() if in_degree == 0]
layer_dict[0] = init_node_list

# 节点的入度字典
in_degree_dict = dict(G.in_degree())
all_nodes = set(G.nodes)
travel_nodes = set(init_node_list)

# 迭代节点
for i in range(1,10):
    last_layer_nodes = layer_dict[i-1]
    layer_dict[i] = []
    for last_node in last_layer_nodes:
        out_nodes = list(G.successors(last_node))
        if len(out_nodes):
            for out_node in out_nodes:
                out_node_degree = in_degree_dict[out_node]
                out_node_degree1 = out_node_degree-1
                if out_node_degree1 == 0:
                    layer_dict[i].append(out_node)
                    travel_nodes.add(out_node)
                else:
                    in_degree_dict[out_node] = out_node_degree1
    gap_set = all_nodes - travel_nodes
    if len(gap_set) ==0:
        break

# 打印观察
for l in sorted(list(layer_dict.keys())):
#     print(l)
    for n in layer_dict[l]:
#         print(n)
        G.nodes[n]['run']()

代码中,构造了一个空的字典,然后选择入度为0的节点作为第0层。然后进行若干次(层)的搜索,每次搜索都遍历上一层的所有节点,假设这个节点为n。

对所有的n,都列出其后续节点,然后遍历到n时进行入度减1处理。当这个后续节点的入度为0时,说明节点所有的依赖都被满足,那么将节点放入本层。

在每层的搜索完成时,都将所有节点减去已遍历完成的节点,如果差集为0,那么中断BFS的继续迭代。

2.2 保存为json

通过nx自带的包,就可以把图的json信息导出,同时也可以通过载入json来恢复这个图。之所以选择json是因为这种格式比较容易通过网络传播,也容易存储在redis或者mongo中。

from networkx.readwrite import json_graph
# 保存图
data = json_graph.node_link_data(G)

3 设计

首先明确这个的设计目标是什么。由于复杂的逻辑处理,以及各种可能性的探索,我希望能够通过这个工具来进行大规模的探索,在合适的时候可以轻易的转入生产服务态,从中受益。

从一般人工智能的角度出发,这个设计要能够支持学习(Learn)与变换(Transform)模式

从图的角度想象,学习与变换过程的网络是极其相似(重合的),所以可以把图分为两层,底层是相同的变换过程,而上层是学习层,构成变换所需的元数据节点。

在运行时,由最外层的子图负责服务。子图核心会定时的启动轮询。如果是文件模式,那么决定是否进行流通是根据输入输出文件夹的文件差;如果是数据库或者服务模式,那么输入输出就是缓冲数据。

这样input节点的运行状态就是「正常|绿色」,如果是生产态子图就会开始基于已有的BFS开始执行,如果是开发态,子图就会开始BFS然后执行。

每次传播都是基于每一层的节点一次执行,最理想的情况是全部的运行状态都是「正常|绿色」,如果某个节点类型是子图,那么就会下钻到这个子图执行,然后返回。子图节点的状态由其内部节点的运行状态决定(例如有一个红色,那么就该节点就为红色)。所以可视化查看时,也可能需要层层下钻。

整个子图的数据都是可json的,这也意味着节点的方法将以文本的方式声明,存储在一个专门的对象中。同时,在节点运行过程中的数据,也将以共享工作空间的方式挂在某个对象上。

  • 1 子图。包含了整个处理所有的相关结构和元数据。
  • 2 方法对象。通过名称和参数来声明的处理。
  • 3 数据对象。子图的所有共享数据。数据只能在同一子图中共享,如果需要跨子图共享,就需要通过子图的数据入口进行声明与对接。

4 Wrapped Up

本次只是原型设计的一次探索,在短时间内暂时不会真正全面实施,所以到这里进行一个打包。

  • 1 封装方法。将BFS的过程抽象为函数。未来这个方法会多次用到,通过BFS,我们确定了合法的节点执行顺序。
# 输入一个nx图,给出BFS层级字典
def BFS(some_G,max_depth = 100):
    layer_dict = {}

    # 初始化节点
    init_node_list = [node for node, in_degree in some_G.in_degree() if in_degree == 0]
    layer_dict[0] = init_node_list    

    # 节点的入度字典
    in_degree_dict = dict(some_G.in_degree())
    all_nodes = set(some_G.nodes)
    travel_nodes = set(init_node_list)

    # 迭代节点
    for i in range(1,max_depth):
        last_layer_nodes = layer_dict[i-1]
        layer_dict[i] = []
        for last_node in last_layer_nodes:
            out_nodes = list(some_G.successors(last_node))
            if len(out_nodes):
                for out_node in out_nodes:
                    out_node_degree = in_degree_dict[out_node]
                    out_node_degree1 = out_node_degree-1
                    if out_node_degree1 == 0:
                        layer_dict[i].append(out_node)
                        travel_nodes.add(out_node)
                    else:
                        in_degree_dict[out_node] = out_node_degree1
        gap_set = all_nodes - travel_nodes
        if len(gap_set) ==0:
            break
    return layer_dict

BFS(G)
{0: [1, 2], 1: [3], 2: [4], 3: [5], 4: [6]}

可以看到,这个图是一个Y型的结构。 (1,2)->(3)->(4)->(5)->(6)

  • 2 三个功能结构。

子图保留的数据全部为可json的状态,包含了图的(多层)结构,学习层学到的元数据节点。

数据对象 将运行中的子图数据进行保存/暂存,在图会话期间,这些数据将以默认的方式进行共享(同一个子图内),或者显示的传递给子图共享。

方法对象 方法对象主要只是借用了对象这种形式,将函数及其参数进行了形式上的剥离,只是将方法统一的绑定在某个对象上。使得所有的操作变得参数形式化,这样容易被智能代理调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/931944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Multisim软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Multisim软件是一款电路仿真和设计软件,由美国国家仪器公司(National Instruments)开发。它提供了一个交互式的图形界面,使用户能够轻松地构建和仿真电路。以下是Multisim软件的详…

什么是devos勒索病毒,中招之后该怎么办?勒索病毒解密,数据恢复

Devos勒索病毒是一种比较常见的勒索病毒病毒,它利用加密技术来锁定用户的文件,并要求支付赎金才能解锁。这种病毒已经引起了全球范围内的关注,也给众多的企业主和个人造成了不可估量的损失。 Devos勒索病毒的起源尚不清楚,但它的攻…

无涯教程-分类算法 - 逻辑回归

逻辑回归是一种监督学习分类算法,用于预测目标变量的概率,目标或因变量的性质是二分法,这意味着将只有两种可能的类。 简而言之,因变量本质上是二进制的,其数据编码为1(代表成功/是)或0(代表失败/否)。 在数学上&…

Linux保存退出和不保存退出命令

Vim编辑器 vim 要编辑的文件输入i进入编辑模式保存退出: 按Esc键退出insert模式,然后输入冒号(:),输入wq!可以保存并退出. 不保存退出: 按Esc键退出insert模式,然后输入冒号(:),输入q!可以不保存并退出。…

Autosar存储入门系列03_Autosar中NVM状态机及存储调用逻辑

本文框架 0.前言1. NVM状态机介绍2. NVM读/写基本逻辑2.1 NVM读操作2.2 NVM写操作2.2.1 实时写2.2.2 下电写 2.3 NVM写入注意事项 0.前言 本系列是Autosar存储入门系列,希望能从学习者的角度把存储相关的知识点梳理一遍,这个过程中如果大家觉得有讲得不…

图文并茂:Python Tkinter从入门到高级实战全解析

目录 介绍什么是Tkinter?准备工作第一个Tkinter程序界面布局事件处理补充知识点 文本输入框复选框和单选框列表框弹出对话框 综合案例:待办事项列表总结 介绍 欢迎来到本篇文章,我们将带您深入了解如何在Python中使用Tkinter库来创建图形用…

弯道超车必做好题集锦二(C语言选择题)

前言: 编程想要学的好,刷题少不了,我们不仅要多刷题,还要刷好题!为此我开启了一个弯道超车必做好题锦集的系列,每篇大约10题左右。此为第二篇选择题篇,该系列会不定期更新,后续还会…

【网络云盘客户端】——项目简介

项目简介 网络云盘客户端时基于QT/C框架实现了一个网络云盘客户端软件,主要功能包括用户的注册,登录,显示用户的个人文件列表,以及文件的上传,下载,删除,共享文件。 登录界面 主窗口界面 文件…

Postman中参数区别及使用说明

一、Params与Body 二者区别在于请求参数在http协议中位置不一样。Params 它会将参数放入url中以?区分以&拼接Body则是将请求参数放在请求体中 后端接受数据: 二、body中不同格式 2.1 multipart/form-data key - value 格式输入,主要特点是可以上…

Proteus软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Proteus软件是一款电路设计和仿真的综合性软件,由Labcenter公司开发。它提供了一个交互式的图形界面,用户可以在其中构建电路、仿真结果并实时观察仿真结果。 1、Proteus的历史和演变 Proteus软件最初于…

Matlab图像处理-加法运算

加法运算 图像加法运算的一个应用是将一幅图像的内容叠加到另一幅图像上,生成叠加图像效果,或给图像中每个像素叠加常数改变图像的亮度。 在MATLAB图像处理工具箱中提供的函数imadd()可实现两幅图像的相加或者一幅图像和常量的相加。 程序代码 I1 i…

C++学习记录——이십유 C++11(2)

文章目录 1、类的新功能1、移动构造和移动赋值2、default、delete 2、可变参数模板3、STL容器的emplace 1、类的新功能 1、移动构造和移动赋值 逐成员按字节拷贝就是浅拷贝。一个类中,如果达成默认移动构造的要求,那么传右值就会使用移动构造了&#xf…

022-从零搭建微服务-短信服务(二)

写在最前 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 源码地址(后端):https://gitee.com/csps/mingyue 源码地址(前端):https://gitee.com/csps…

Docker镜像的私有定制之nginx

一、背景 机器上已有nginx的可执行文件,但它是基于官方源码进行修改过的,可模块的源码一时找不到。另外,每次都基于源码去构建,对于Nginx部署也是麻烦。 所以,我们想要改为docker容器化部署nginx。 操作系统是centos…

STL-常用容器-map/ multimap容器(二叉树-红黑树)

1 map基本概念 简介: Map是一种关联容器,它通过将键和值成对存储,实现了快速的键值查找。在Map中,每个键都是唯一的,而值可以重复。Map容器内部使用平衡二叉树(通常是红黑树)的数据结构来实现高…

HodlSoftware-免费在线PDF工具箱 加解密PDF 集成隐私保护功能

HodlSoftware是什么 HodlSoftware是一款免费在线PDF工具箱,集合编辑 PDF 的简单功能,可以对PDF进行加解密、优化压缩PDF、PDF 合并、PDF旋转、PDF页面移除和分割PDF等操作,而且工具集成隐私保护功能,文件只在浏览器本地完成&…

OpenCV基础知识(8)— 图形检测

前言:Hello大家好,我是小哥谈。图形检测是计算机视觉的一项重要功能。通过图形检测可以分析图像中可能存在的形状,然后对这些形状进行描绘,例如搜索并绘制图像的边缘,定位图像的位置,判断图像中有没有直线、…

【面向大一新生IT技术社群招新啦,不来瞅瞅?】

个人名片: 🐼作者简介:一名大三在校生 🐻‍❄️个人主页:落798. 🐼个人WeChat:落798. 🕊️系列专栏:【零基础学java】 ----- 【重识c语言】 ---- 【计算机网络】—【Spri…

Fortran 微分方程求解 --ODEPACK

最近涉及到使用Fortran对微分方程求解,我们知道MATLAB已有内置的函数,比如ode家族,ode15s,对应着不同的求解办法。通过查看odepack的官方文档,我尝试使用了dlsode求解刚性和非刚性常微分方程组。 首先是github网址&am…