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

说明

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

提升主要包含如下方面:

  • 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的状态,包含了图的(多层)结构,学习层学到的元数据节点。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构—循环队列(环形队列)

循环队列(环形队列) 循环队列的概念及结构循环队列的实现 循环队列的概念及结构 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。…

用Cmake build OpenCV后,在VS中查看OpenCV源码的方法(环境VS2022+openCV4.8.0) Part II

用Cmake build OpenCV后,在VS中查看OpenCV源码的方法 Part II 用Cmake build OpenCV后,在VS中查看OpenCV源码的方法(环境VS2022openCV4.8.0) Part I_松下J27的博客-CSDN博客 在上一篇文章中,我用cmake成功的生成了ope…

设计模式三原则

1.1单一职责原则 C 面向对象三大特性之一的封装指的就是将单一事物抽象出来组合成一个类,所以我们在设计类的时候每个类中处理的是单一事物而不是某些事物的集合。 设计模式中所谓的单一职责原则,就是对一个类而言,应该仅有一个引起它变化的原…

我的128天创作纪念日-东离与糖宝

文章目录 机缘收获日常成就憧憬 不知不觉我也迎来了自己的128天创作纪念日,一起来看看我有什么想对大家说的吧 机缘 我的写博客之旅始于参加了代码随想录算法训练营。在训练营期间,代码随想录作者卡尔建议我们坚持每天写博客记录刷题学习的进度和心得体…

【LeetCode-中等题】240. 搜索二维矩阵 II

文章目录 题目方法一:暴力双for查找方法二:二分查找,对每二维数组进行拆分,一行一行的进行二分查找方法三:列倒序Z字形查找 题目 方法一:暴力双for查找 public boolean searchMatrix(int[][] matrix, int …

Java版B/S架构 智慧工地源码,PC、移动、数据可视化智慧大屏端源码

智慧工地是什么?智慧工地主要围绕绿色施工、安全管控、劳务管理、智能管理、集成总控等方面,帮助工地解决运营、管理方面各个难点痛点。在互联网的加持下促进项目现场管理的创新与发展,实现工程管理人员与工程施工现场的整合,构建…

系统架构师---软件重用、基于架构的软件设计、软件模型

目录 软件重用 构件技术 基于架构的软件设计 ABSD方法与生命周期 抽象功能需求 用例 抽象的质量和业务需求 架构选项 质量场景 约束 基于架构的软件开发模型 架构需求 需求获取 标识构件 需求评审 架构设计 架构文档 架构复审 架构实现 架构演化 前言&…

什么是响应式设计(Responsive Design)?如何实现一个响应式网页?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式设计(Responsive Design)⭐ 如何实现一个响应式网页?1. 弹性网格布局2. 媒体查询3. 弹性图像和媒体4. 流式布局5. 优化导航6. 测试和调整7. 图片优化8. 字体优化9. 渐进增强10. 面向移动优先11. …

一、Kafka概述

目录 1.1 定义1.2 消息队列1、传统消息队列的应用场景2、消息队列的两种模式 1.3 Kafka的基础架构 1.1 定义 Kafka传 统定义:Kafka是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。 K…

6、Spring_Junit与JdbcTemplate整合

Spring 整合 1.Spring 整合 Junit 1.1新建项目结构 1.2导入依赖 导入 junit 与 Spring 依赖 <!-- 添加 spring 依赖--> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version…

数据结构数组栈的实现

Hello&#xff0c;今天我们来实现一下数组栈&#xff0c;学完这个我们又更进一步了。 一、栈 栈的概念 栈是一种特殊的线性表&#xff0c;它只允许在固定的一端进行插入和删除元素的操作。 进行数据的插入和删除只在栈顶实现&#xff0c;另一端就是栈底。 栈的元素是后进先出。…

前端高频面试题 js中堆和栈的区别和浏览器的垃圾回收机制

一、 栈(stack)和 堆(heap) 栈(stack)&#xff1a;是栈内存的简称&#xff0c;栈是自动分配相对固定大小的内存空间&#xff0c;并由系统自动释放&#xff0c;栈数据结构遵循FILO&#xff08;first in last out&#xff09;先进后出的原则&#xff0c;较为经典的就是乒乓球盒结…

验证码服务(使用提供好的项目)

1、先生成一个指定位数的验证码&#xff0c;根据需要可能是数字、数字字母组合或文字。 2、根据生成的验证码生成一个图片并返回给页面 3、给生成的验证码分配一个key&#xff0c;将key和验证码一同存入缓存。这个key和图片一同返回给页面。 4、用户输入验证码&#xff0c;连…

iPhone 15 Pro与谷歌Pixel 7 Pro:哪款相机手机更好?

考虑到苹果最近将更多高级功能转移到iPhone Pro设备上的趋势,今年秋天iPhone 15 Pro与谷歌Pixel 7 Pro的对决将是一场特别有趣的对决。去年发布的iPhone 14 Pro确实发生了这种情况,有传言称iPhone 15 Pro再次受到了苹果的大部分关注。 预计iPhone 15系列会有一些变化,例如切…

新版Jadx 加载dex报错 jadx.plugins.input.dex.DexException:Bad checksum 解决方法

本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!新版Jadx(1.6+) 加载dex报错 jadx.plugins.input.dex.DexException:Bad checksum 解决方法 环境 win10J…

QT5.12.12通过ODBC连接到GBase 8s数据库(CentOS)

本示例使用的环境如下&#xff1a; 硬件平台&#xff1a;x86_64&#xff08;amd64&#xff09;操作系统&#xff1a;CentOS 7.8 2003数据库版本&#xff08;含CSDK&#xff09;&#xff1a;GBase 8s V8.8 3.0.0_1 为什么使用QT 5.12.10&#xff1f;该版本包含QODBC。 1&#…

二进制数间按位逻辑运算按位逻辑与、逻辑或运算bitwise_and()bitwise_or()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 二进制数间按位逻辑运算 按位逻辑与、逻辑或运算 bitwise_and() bitwise_or() [太阳]选择题 下列代码最后一次输出的结果是&#xff1f; import numpy as np a, b 3, 8 print("…

Diffusion Models for Image Restoration and Enhancement – A Comprehensive Survey

图像恢复与增强的扩散模型综述 论文链接&#xff1a;https://arxiv.org/abs/2308.09388 项目地址&#xff1a;https://github.com/lixinustc/Awesome-diffusion-model-for-image-processing/ Abstract 图像恢复(IR)一直是低水平视觉领域不可或缺的一项具有挑战性的任务&…

【golang】for语句和switch语句

使用携带range子句的for语句时需要注意哪些细节&#xff1f; numbers1 : []int{1, 2, 3, 4, 5, 6} for i : range numbers1 {if i 3 {numbers1[i] | i} } fmt.Println(numbers1)这段代码执行后会打印出什么内容&#xff1f; 答案&#xff1a;[1 2 3 7 5 6] 当for语句被执行…

C++避坑——most vexing parse问题

1."坑"的问题是什么&#xff1f; 先看一段代码&#xff1a; class Functor { public:void operator()(){std::cout << "我是线程的初始函数" << std::endl;} };int main() {std::thread t(Functor());// 强制高速编译器这是一个构造函数!t.j…