【图神经网络】Pytorch图神经网络库——PyG异构图学习

news2024/12/29 10:12:02

PyG异构图学习

  • 举个例子
  • 创建异构图
    • Utility函数
  • 异构图Transformations
  • 创建异构图神经网络
    • 自动转换GNN模型
    • 使用异构卷积包装器
    • 部署现有的异构算子
  • 异构图采样
  • 参考资料

大量真实世界数据集存储为异构图,这促使Pytorch Geometric(PyG)中引入专门的功能。例如,推荐领域中的大多数图(如社交图)都是异构的,因为它们存储关于不同类型的实体及其不同类型的关系的信息。本文介绍如何将异构图映射到PyG,以及如何将它们用作图形神经网络模型的输入。

异构图具有不同类型的信息附加到节点和边上。因此,由于类型和维数的差异,单个节点或边特征张量不能包含整个图的所有节点或边特征。相反,需要为节点和边分别指定一组类型,每个类型都有自己的数据张量。由于数据结构的不同,消息传递公式也随之改变,允许以节点或边类型为条件计算消息和更新函数。

举个例子

作为一个示例,我们从OGB数据集看一下异构ogbn-mag网络:
OGBN-MAG网络
给定的异构图有1,939,743个节点,节点类型分为作者、论文、机构和研究领域四种。它还有21,111,007条边,属于以下四种类型之一:

  • writes(写作):作者写一篇特定的论文
  • affiliated with(附属于):作者附属于某一特定机构
  • cites(引用):一篇论文引用另一篇论文
  • has topic(有主题):一篇论文有一个特定研究领域的主题

这个图的任务是根据图中存储的信息推断出每一篇论文(会议或期刊)的发表地点。

创建异构图

首先,我们可以创建torch_geometric.data.HeteroData类型的数据对象,为每种类型分别定义节点特征张量边索引张量边特征张量

from torch_geometric.data import HeteroData

data = HeteroData() # 实例化一个空对象

# 初始化结点特征
data['paper'].x = ... # [num_papers, num_features_paper]
data['author'].x = ... # [num_authors, num_features_author]
data['institution'].x = ... # [num_institutions, num_features_institution]
data['field_of_study'].x = ... # [num_field, num_features_field]
# 初始化边索引
data['paper', 'cites', 'paper'].edge_index = ... # [2, num_edges_cites]
data['author', 'writes', 'paper'].edge_index = ... # [2, num_edges_writes]
data['author', 'affiliated_with', 'institution'].edge_index = ... # [2, num_edges_affiliated]
data['author', 'has_topic', 'institution'].edge_index = ... # [2, num_edges_topic]
# 初始化边特征
data['paper', 'cites', 'paper'].edge_attr = ... # [num_edges_cites, num_features_cites]
data['author', 'writes', 'paper'].edge_attr = ... # [num_edges_writes, num_features_writes]
data['author', 'affiliated_with', 'institution'].edge_attr = ... # [num_edges_affiliated, num_features_affiliated]
data['paper', 'has_topic', 'field_of_study'].edge_attr = ... # [num_edges_topic, num_features_topic]

节点或边张量将在第一次访问时自动创建,并由字符串类型的键索引。节点类型由单个字符串标识,而边类型由字符串的三元组 (source_node_type, edge_type, destination_node_type)标识:边类型标识符和边类型可以存在的两个节点类型。并且,数据对象允许每种类型有不同的特征维度。

包含按属性名而不是按节点或边类型分组的异构信息的字典可以通过data.{attribute_name}_dict直接访问,并作为输入到GNN模型中:

model = HeteroGNN(...)
# 下面是数据对象调用方式
output = model(data.x_dict, data.edge_index_dict, data.edge_attr_dict)

如果该数据集存在于PyG数据集列表中,则可以直接导入和使用。并且它将被下载到根目录并自动处理

from torch_geometric.datasets import OGB_MAG

dataset = OGB_MAG(root='./data', preprocess='metapath2vec')
data = dataset[0]

数据对象可以打印出来进行验证:

HeteroData(
  paper={
    x=[736389, 128],
    y=[736389],
    train_mask=[736389],
    val_mask=[736389],
    test_mask=[736389]
  },
  author={ x=[1134649, 128] },
  institution={ x=[8740, 128] },
  field_of_study={ x=[59965, 128] },
  (author, affiliated_with, institution)={ edge_index=[2, 1043998] },
  (author, writes, paper)={ edge_index=[2, 7145660] },
  (paper, cites, paper)={ edge_index=[2, 5416271] },
  (paper, has_topic, field_of_study)={ edge_index=[2, 7505078] }
)

注意:最初的ogbn mag网络只为“paper”节点提供特征。在OGB_MAG中,我们提供了下载其处理版本的选项,其中结构特征(从“metapath2vec”或“TransE”获得)被添加到无特征节点,这通常在向OGB排行榜提交的排名靠前的文件中进行。

Utility函数

torch_geometric.data.HeteroData类提供了许多有用的实用函数来修改和分析给定的图。例如,单个节点或边存储可以被单独索引:

paper_node_data = data['paper']
cites_edge_data = data['paper', 'cites', 'paper']

可以添加新的节点类型或张量,并删除它们:

data['paper'].year = ...    # Setting a new paper attribute
del data['field_of_study']  # Deleting 'field_of_study' node type
del data['has_topic']       # Deleting 'has_topic' edge type

可以访问数据对象的元数据,包含所有当前节点和边类型的信息:

node_types, edge_types = data.metadata()
print(node_types)
['paper', 'author', 'institution']
print(edge_types)
[('paper', 'cites', 'paper'),
('author', 'writes', 'paper'),
('author', 'affiliated_with', 'institution')]

数据对象可以像往常一样在设备之间传输:

data = data.to('cuda:0')
data = data.cpu()

还可以使用其他辅助函数来分析给定的图

data.has_isolated_nodes()
data.has_self_loops()
data.is_undirected()

并且可以通过to_homogeneous()将其转换为同构的“类型化”图,该图能够在不同类型之间保持特征的维数匹配:

homogeneous_data = data.to_homogeneous()
print(homogeneous_data)
Data(x=[1879778, 128], edge_index=[2, 13605929], edge_type=[13605929])

在这里,homogeneous_data.Edge_type表示一个边级向量,以整数形式保存每条边的边类型。

异构图Transformations

大多数用于预处理规则图的transformations也适用于异构图数据对象。

import torch_geometric.transforms as T

data = T.ToUndirected()(data)
data = T.AddSelfLoops()(data)
data = T.NormalizeFeatures()(data)

这里,ToUndirected()通过为图中的所有边添加反向边,将有向图转换为无向图。消息传递将在所有边的两个方向上执行。

对于所有类型为node_type的节点和所有现有形式的边类型('node_type', 'edge_type', 'node_type'),函数AddSelfLoops()将添加自循环边。因此,在消息传递期间,每个节点可能从自身接收一个或多个消息(每个适当的边类型接收一个消息)

NormalizeFeatures()的工作方式与同质情况类似,并将所有指定的特征归并为1。

创建异构图神经网络

标准消息传递GNN (MP-GNN)不能简单地应用于异构图数据,因为不同类型的节点特征和边特征由于特征类型的差异不能由相同的函数处理。避免这种情况的一种自然方法是为每种边类型分别实现消息和更新函数。在运行时,MP-GNN 算法需要在消息计算期间迭代边类型字典,在节点更新期间迭代节点类型字典
为了避免不必要的运行时开销,并尽可能简单地创建异构MP-GNN,Pytorch Geometric为用户提供了三种方法来创建异构图数据模型:

  1. 通过使用 torch_geometrici .nn.to_hetero()torch_geometrici .nn.to_hetero_with_bases()自动将一个同质GNN模型转换为一个异构的GNN模型;
  2. 使用PyGs包装器torch_geometric..nn.conv.HeteroCov为异构卷积定义不同类型的独立函数
  3. 部署现有的(或编写您自己的)异构 GNN 算子

下面将详细介绍每个方法:

自动转换GNN模型

Pytorch geometry允许使用内置函数 torch_geometrical .nn.to_hetero()torch_geometrical .nn.to_hetero_with_bases()自动将任何PyG GNN模型转换为异构输入图形的模型。

import torch_geometric.transforms as T
from torch_geometric.datasets import OGB_MAG
from torch_geometric.nn import SAGEConv, to_hetero

# 读取数据集 并自动预处理为无向图
dataset = OGB_MAG(root='./data', preprocess='metapath2vec', transform=T.ToUndirected())
data = dataset[0]

class GNN(torch.nn.Module):
    def __init__(self, hidden_channels, out_channels):
        super().__init__()
        self.conv1 = SAGEConv((-1, -1), hidden_channels)
        self.conv2 = SAGEConv((-1, -1), out_channels)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index).relu()
        x = self.conv2(x, edge_index)
        return x


model = GNN(hidden_channels=64, out_channels=dataset.num_classes)
model = to_hetero(model, data.metadata(), aggr='sum')

该过程采用一个现有的GNN模型,并复制消息函数以分别处理每个边类型,如下图所示。
同质图与异质图
因此,该模型现在期望将节点和边类型作为键作为输入参数的字典,而不是在同构图中使用的单张量。注意,我们将in_channels的元组传递给SAGEConv,以便允许在二分图中传递消息。

由于输入特性的数量和张量的大小因不同类型而异,PyG可以利用延迟初始化来初始化异构GNN中的参数(用-1表示in_channels参数)。这允许我们避免计算和跟踪计算图的所有张量大小。所有现有的PyG操作符都支持延迟初始化。我们可以通过调用一次来初始化模型的参数:

with torch.no_grad():  # Initialize lazy modules.
    out = model(data.x_dict, data.edge_index_dict)

to_hetero()to_hetero_with_bases()对于可以自动转换为异构体系结构的同构体系结构来说都非常灵活。举个例子:

from torch_geometric.nn import GATConv, Linear, to_hetero
# 定义模型 全部延迟初始化
class GAT(torch.nn.Module):
    def __init__(self, hidden_channels, out_channels):
        super().__init__()
        self.conv1 = GATConv((-1, -1), hidden_channels, add_self_loops=False)
        self.lin1 = Linear(-1, hidden_channels)
        self.conv2 = GATConv((-1, -1), out_channels, add_self_loops=False)
        self.lin2 = Linear(-1, out_channels)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index) + self.lin1(x)
        x = x.relu()
        x = self.conv2(x, edge_index) + self.lin2(x)
        return x
# 转换
model = GAT(hidden_channels=64, out_channels=dataset.num_classes)
model = to_hetero(model, data.metadata(), aggr='sum')

通常,可以使用如下方法训练(其实和torch中一样,需要注意的就是传入数据的字典表示):

def train():
    model.train()
    optimizer.zero_grad()
    out = model(data.x_dict, data.edge_index_dict)
    mask = data['paper'].train_mask
    loss = F.cross_entropy(out['paper'][mask], data['paper'].y[mask])
    loss.backward()
    optimizer.step()
    return float(loss)

使用异构卷积包装器

异构卷积卷积包装器 torch_geometric.nn.conv.HeteroConv允许定义自定义异构消息和更新函数,以从头开始为异构图构建任意MP-GNNs。虽然 to_hetero()自动转换器对所有边类型使用相同的操作,但包装器允许为不同的边类型定义不同的操作HeteroConv接受一个子模块字典作为输入,图数据中的每一种边类型都有一个。下面的示例演示如何应用它。

from torch_geometric.nn import HeteroConv, GCNConv, SAGEConv, GATConv, Linear

class HeteroGNN(torch.nn.Module):
    def __init__(self, hidden_channels, out_channels, num_layers):
        super().__init__()

        self.convs = torch.nn.ModuleList()
        for _ in range(num_layers):
            conv = HeteroConv({
                ('paper', 'cites', 'paper'): GCNConv(-1, hidden_channels),
                ('author', 'writes', 'paper'): SAGEConv((-1, -1), hidden_channels),
                ('paper', 'rev_writes', 'author'): GATConv((-1, -1), hidden_channels),
            }, aggr='sum')
            self.convs.append(conv)

        self.lin = Linear(hidden_channels, out_channels)

    def forward(self, x_dict, edge_index_dict):
	# 需要注意的就是这里需要传入字典
        for conv in self.convs:
            x_dict = conv(x_dict, edge_index_dict)
            x_dict = {key: x.relu() for key, x in x_dict.items()}
        return self.lin(x_dict['author'])

model = HeteroGNN(hidden_channels=64, out_channels=dataset.num_classes,
                  num_layers=2)

我们可以通过调用一次来初始化模型(更多关于惰性初始模式的细节请参阅这里)

with torch.no_grad():  # Initialize lazy modules.
     out = model(data.x_dict, data.edge_index_dict)

部署现有的异构算子

PyG提供了操作符(例如,torch_geometrical.nn.convt.hgtconv),这是专门为异构图设计的。这些操作符可以直接用于构建异构的GNN模型,如下例所示:

from torch_geometric.nn import HGTConv, Linear

class HGT(torch.nn.Module):
    def __init__(self, hidden_channels, out_channels, num_heads, num_layers):
        super().__init__()
	# 将各种类型的边使用线性层转换为同一个维度
        self.lin_dict = torch.nn.ModuleDict()
        for node_type in data.node_types:
            self.lin_dict[node_type] = Linear(-1, hidden_channels)

	# 堆叠多层异构卷积层 num_heads是使用了多头注意力机制 
        self.convs = torch.nn.ModuleList()
        for _ in range(num_layers):
            conv = HGTConv(hidden_channels, hidden_channels, data.metadata(),num_heads, group='sum')
            self.convs.append(conv)

        self.lin = Linear(hidden_channels, out_channels)

    def forward(self, x_dict, edge_index_dict):
        for node_type, x in x_dict.items():
            x_dict[node_type] = self.lin_dict[node_type](x).relu_()

        for conv in self.convs:
            x_dict = conv(x_dict, edge_index_dict)

        return self.lin(x_dict['author'])

model = HGT(hidden_channels=64, out_channels=dataset.num_classes,
            num_heads=2, num_layers=2)

with torch.no_grad():  # Initialize lazy modules.
     out = model(data.x_dict, data.edge_index_dict)

异构图采样

PyG为异构图形的采样提供了各种功能,例如在标准的torch_geometric.loader.NeighborLoader类或专用的异构图形采样器,如 torch_geometric.loader.HGTLoader。这对于大型异构图的高效表示学习特别有用,因为在这种情况下,处理完整数量的邻居的计算开销太大。对其他采样器(如 torch_geometrical.loader.ClusterLoader,torch_geometric.loader.GraphSAINTLoader)的异构图形支持很快就会加入。

总的来说,所有异构图形加载器都将生成一个 HeteroData对象作为输出,其中包含原始数据的一个子集,其主要不同之处是其采样过程的工作方式。因此,将训练过程从全批处理转换为小批处理只需要很小的代码更改。

使用 NeighborLoader进行邻居采样的工作原理如下:

import torch_geometric.transforms as T
from torch_geometric.datasets import OGB_MAG
from torch_geometric.loader import NeighborLoader

transform = T.ToUndirected()  # Add reverse edge types.
data = OGB_MAG(root='./data', preprocess='metapath2vec', transform=transform)[0]

train_loader = NeighborLoader(
    data,
    # Sample 15 neighbors for each node and each edge type for 2 iterations:
    num_neighbors=[15] * 2,
    # Use a batch size of 128 for sampling training nodes of type "paper":
    batch_size=128,
    input_nodes=('paper', data['paper'].train_mask),
)

batch = next(iter(train_loader))

NeighborLoader既适用于同构图,也适用于异构图。当在异构图中操作时,可以对单个边类型的采样邻居数量进行更细粒度的控制,但不是必需的,例如:

num_neighbors = {key: [15] * 2 for key in data.edge_types}

使用input _ node 参数,我们进一步指定节点的类型和索引,我们希望从这些节点中抽样本地邻域,例如根据data['paper'].train_mask标记为训练节点的所有“paper”节点。

打印Batch,然后产生以下输出:

HeteroData(
  paper={
    x=[20799, 256],
    y=[20799],
    train_mask=[20799],
    val_mask=[20799],
    test_mask=[20799],
    batch_size=128
  },
  author={ x=[4419, 128] },
  institution={ x=[302, 128] },
  field_of_study={ x=[2605, 128] },
  (author, affiliated_with, institution)={ edge_index=[2, 0] },
  (author, writes, paper)={ edge_index=[2, 5927] },
  (paper, cites, paper)={ edge_index=[2, 11829] },
  (paper, has_topic, field_of_study)={ edge_index=[2, 10573] },
  (institution, rev_affiliated_with, author)={ edge_index=[2, 829] },
  (paper, rev_writes, author)={ edge_index=[2, 5512] },
  (field_of_study, rev_has_topic, paper)={ edge_index=[2, 10499] }
)

batch共有28187个节点,用于计算128个“paper”节点的嵌入。被采样的节点总是根据它们被采样的顺序进行排序。因此,batch['paper'].batch_size节点表示原始的mini-batch节点集合,便于通过切片获得最终的输出嵌入。

在小批处理模式下训练我们的异构GNN模型与在全批处理模式下训练类似,只是我们通过 train_loader迭代生成的mini-batch,并基于单个mini-batch小批处优化模型参数:

def train():
    model.train()

    total_examples = total_loss = 0
    for batch in train_loader:
        optimizer.zero_grad()
        batch = batch.to('cuda:0')
        batch_size = batch['paper'].batch_size
        out = model(batch.x_dict, batch.edge_index_dict)
        loss = F.cross_entropy(out['paper'][:batch_size],
                               batch['paper'].y[:batch_size])
        loss.backward()
        optimizer.step()

        total_examples += batch_size
        total_loss += float(loss) * batch_size

    return total_loss / total_examples

重要的是,在损失计算过程中,我们只使用了前128个“paper”节点。我们基于batch['paper'].batch_size,对labels batch['paper'.y和输出 out['paper']来表示原始mini-batch的标签和输出。

参考资料

[1] HETEROGENEOUS GRAPH LEARNING

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

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

相关文章

Java项目:Springboot实现的一个简单博客管理系统

作者主页:源码空间站2022 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目为前后台管理系统,包括博主与游客两种角色; 博主角色包含以下功能: 博主登录,发博客,博主可以删除博客…

使用yolov5 v7.0进行实例分割

1. 介绍 U版yolov5最新推出了v7.0版本,新增了基于yolov5进行实例分割的代码。作者提到yolov5 v7.0实现的实例分割是超越了所有的SOTA模型的效果,是目前为止速度和精度最高的。 2. 代码的使用 2.1 Setup 克隆GitHub仓库,安装依赖项,检查PyTorch和GPU。 git clone http…

GEO芯片数据分析更新(补富集分析与WGCNA)

GEO数据挖掘,表达芯片分析 举例:王同学近期拟通过生物信息学相关软件与数据库来探讨女性非抽烟者的非小细胞肺癌预后相关的显著性基因及潜在的治疗靶点,他在NCBI上查询到了1套芯片数据GSE19804。请帮助他完成该项目的设计与分析。 上一篇博…

Linux系统基础——内核初始化

内核初始化 特此说明: 刘超的趣谈linux操作系统是比较重要的参考资料,本文大部分内容和所有图片来源于这个专栏。 1 背景知识 BootLoader阶段后,cpu从实模式转换成保护模式。有了更强的寻址能力后,内核也已经加载到内存了,系统内…

2. 做一个极简 UI 库之Toast 组件

效果 API 设计 先设计好了 API 写起来代码才不会犯迷糊 Toast(message: string; otherParams?: ToastParams): ToastReturninterface ToastParams {time?: number;appendTo?: string | HTMLElement;dangerouslyUseHTMLString?: boolean; }interface ToastReturn {close():v…

Node.js - Express

文章目录目标一、初识 Express1、Express 简介(1)什么是 Express(2)进一步理解 Express(3)Express 能做什么2、Express 的基本使用(1)安装(2)创建基本的 Web …

认识 Fuchsia OS

认识 Fuchsia OS 1 说明背景 1.1 基本信息 开发者: Google编程语言: C、C、Rust、Go、Python、Dart内核: Zircon运作状态: 当前源码模式: 开放源代码初始版本: 2016年8月15日支持的语言: 英语支持平台: ARM64、X86-64内核类别: 微内核 基于能力 实时操作系统许可证: BSD 3 c…

node.js+uni计算机毕设项目高校迎新管理小程序(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置: Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术: Express框架 Node.js Vue 等等组成,B/S模式 Vscode管理前后端分离等…

【2023 AR元宇宙过圣诞!】《Merry Meta Christmas》

啥也不说了,先看最终效果 3D场景资源、EasyAR_Plugin、图片与安卓App资源均已上传,点击该处下载 一、前言 圣诞节的真正含义是为了纪念耶稣诞生,象征着团圆美满,万物复苏,日子变得愈发美好 2021年是元宇宙的元年&…

UE5 狐獴演示Demo分析

1.特效的生成方式 1.1临时特效的生成:使用了已生成轨道临时创建该特效(不用在场景中放入该特效,而是临时创建即可)、系统生命周期轨道设置该特效的播放时长 1.2长期特效的生成:特效时长为该镜头片段长度 2.特效的类…

输出数组中每一行(列)中的最小值(最大值)numpy.amin()numpy.amax()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 输出数组中每一行(列)中的最小值(最大值) numpy.amin() numpy.amax() [太阳]选择题 对下面代码中np.amin(myList, 0)输出的结果为?…

java基于ssh的旅游系统

本项目主要发西安各个旅游景点和附近酒店信息的网站,用户可以根据旅游团一起旅游,可以也可以自驾游,还可以发布旅游活动等。 演示视频 https://www.bilibili.com/video/BV1wv411x7cg/?share_sourcecopy_web&vd_sourceed0f04fbb713154db…

【Vue】七、Vue-cli工程化开发

后端程序员的vue学习之路一、 Vue-cli安装Vue-cli1、安装node.js2、配置node.js环境变量3、 Npm仓库设置淘宝源4、全局安装 vue-cli5、创建vue应用程序1、 创建vue项目基础骨架:2、 运行项目:6、vue项目结构二、Vue.js项目运行逻辑分析1、 npm run dev命…

3.11.2、虚拟局域网 VLAN 实现机制

虚拟局域网 VLAN 技术是在交换机上实现的,需要交换机能够实现以下两大功能 能够处理带有 VLAN 标记的帧:IEEE 802.1Q 帧交换机的各端口支持不同的端口类型(帧的处理方式有所不同) 1、IEEE 802.1Q 帧 IEEE 802.1Q 帧&#xff08…

Java项目:SpringBoot美容院预约管理系统

作者主页:源码空间站2022 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本系统分为管理员与普通用户两种角色; 管理员角色包含以下功能: 登录,首页,新增管理员,管理员信息列表,网站用户信息列表…

node.js+uni计算机毕设项目基于微信小程序校园心理咨询(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置: Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术: Express框架 Node.js Vue 等等组成,B/S模式 Vscode管理前后端分离等…

RabbitMQ 第一天 基础 1 MQ的基本概念 1.1 MQ 概述 1.2 MQ的优势和 劣势 1.3 MQ的优势

RabbitMQ 【黑马程序员RabbitMQ全套教程,rabbitmq消息中间件到实战】 文章目录RabbitMQ第一天 基础1 MQ的基本概念1.1 MQ 概述1.1.1 MQ 概述1.1.2 小结1.2 MQ的优势和 劣势1.2.1 概述1.3 MQ的优势1.3.1 应用解耦1.3.2 异步提速1.3.3 削峰填谷1.3.4 小结第一天 基础…

【SpringMVC】SpringMVC模型数据+视图解析器

目录 一、模型数据-如何将数据存入request域 二、模型数据-如何将数据存入session域 三、ModelAttribute 四、视图解析器 相关文章 【SpringMVC】入门篇:带你了解SpringMVC的执行流程【SpringMVC】入门篇:带你了解SpringMVC的执行流程 【SpringMVC】使用…

springboot整合swagger

特别说明:本次项目整合基于idea进行的,如果使用Eclipse可能操作会略有不同,不过总的来说不影响。 springboot整合之如何选择版本及项目搭建 springboot整合之版本号统一管理 springboot整合mybatis-plusdurid数据库连接池 springboot整合…

node.js+uni计算机毕设项目儿童健康成长档案小程序(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置: Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术: Express框架 Node.js Vue 等等组成,B/S模式 Vscode管理前后端分离等…