GCN-图卷积神经网络算法简单实现(含python代码)

news2025/7/12 11:41:23

本文是就实现GCN算法模型进行的代码介绍,上一篇文章是GCN算法的原理和模型介绍。

代码中用到的Cora数据集:

链接:https://pan.baidu.com/s/1SbqIOtysKqHKZ7C50DM_eA 
提取码:pfny 

文章目录

目的

一、数据集介绍

二、实现过程讲解

三、代码实现和结果分析

1. 导入包

2. 数据准备¶

3. 图卷积层定义

4. GCN图卷积神经网络模型定义

5. 模型训练

5.1 超参数定义,包含学习率、正则化系数等。

5.2 定义模型:

5.3 定义训练和测试函数,进行训练

6. 可视化


目的

本次实验的目的是将论文分类,通过模型训练,利用已经分好类的训练集,将论文通过GCN算法分为7类。


一、数据集介绍

数据集我选用的是GCN常用的Cora数据集,实验的目标就是通过对构造出来的两层GCN模型进行训练,实现对数据集样本节点的分类

Cora数据集下载地址:https://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz

个人不建议用python的dgl包中的Cora数据,总是报错。

Cora数据集由关于机器学习方面的论文组成。 这些论文分为以下七个类别之一:

1.基于案例

2.遗传算法

3.神经网络

4.概率方法

5.强化学习

6.规则学习

7.理论

这些论文都是经过筛选的,在最终的数据集中,每篇论文引用或被至少一篇其他论文引用。整个语料库中有2708篇论文。

在词干堵塞和去除词尾后,只剩下1433个唯一的单词。文档频率小于10的所有单词都被删除。

即Cora数据集包含2708个顶点, 5429条边,每个顶点包含1433个特征,共有7个类别。

并且Cora已经把训练集和测试集的数据都划分好了,直接按照文件名读取数据即可,如

文件ind.cora.x => 训练实例的特征向量;ind.cora.y => 训练实例的标签,独热编码

ind.cora.tx => 测试实例的特征向量;ind.cora.ty => 测试实例的标签,独热编码

二、实现过程讲解

结合我最后做的代码实现,给大家先举一个引文网络的简单实例,方便大家了解处理过程。

其中每个节点代表一篇研究论文,同时边代表的是引用关系。

我们在这里有一个预处理步骤。在这里我们不使用原始论文作为特征,而是将论文转换成向量(通过使用NLP嵌入,例如tf-idf)。

假设我们使用average()函数(实际上GCN内部的传递函数肯定不是平均值,这里只是方便理解)。我们将对所有的节点进行同样的获取特征向量的操作。最后,我们将这些计算得到的平均值输入到神经网络中。

让我们考虑下绿色节点。首先,我们得到它的所有邻居的特征值,包括自身节点,接着取平均值。最后通过神经网络返回一个结果向量并将此作为最终结果。请注意,在GCN中,我们仅仅使用一个全连接层。在这个例子中,我们得到2维向量作为输出(全连接层的2个节点)。

全连接网络的作用就是对上一层得到的向量做乘法,最终降低其维度,然后输入到softmax层中得到对应的每个类别的得分。

在实际操作中,我们肯定是使用比average函数更复杂的聚合函数,也就是上面讲的那个传播函数。

我们还可以将更多的层叠加在一起,以获得更深的GCN。其中每一层的输出会被视为下一层的输入。

2层GCN的例子:第一层的输出是第二层的输入。

那么两层的GCN就可以在降维的同时,通过层间传播的公式获取到二阶邻居节点的特征:

 在节点分类问题中,实际上在输入的邻接矩阵和每个节点的特征中,既包含了节点间的联系情况,也包含了节点自身的特征。

通过GCN的卷积层就可以实现降维,想要聚成几类就降成几维。

三、代码实现和结果分析

1. 导入包

import itertools
import os
import os.path as osp
import pickle
import urllib
from collections import namedtuple
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import scipy.sparse as sp
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.init as init
import torch.optim as optim
import matplotlib.pyplot as plt
%matplotlib inline

2. 数据准备¶

Data = namedtuple('Data', ['x', 'y', 'adjacency',
                           'train_mask', 'val_mask', 'test_mask'])


def tensor_from_numpy(x, device):
    return torch.from_numpy(x).to(device)


class CoraData(object):
    filenames = ["ind.cora.{}".format(name) for name in
                 ['x', 'tx', 'allx', 'y', 'ty', 'ally', 'graph', 'test.index']]

    def __init__(self, data_root="./data", rebuild=False):
        """Cora数据,包括数据下载,处理,加载等功能
        当数据的缓存文件存在时,将使用缓存文件,否则将下载、进行处理,并缓存到磁盘

        处理之后的数据可以通过属性 .data 获得,它将返回一个数据对象,包括如下几部分:
            * x: 节点的特征,维度为 2708 * 1433,类型为 np.ndarray
            * y: 节点的标签,总共包括7个类别,类型为 np.ndarray
            * adjacency: 邻接矩阵,维度为 2708 * 2708,类型为 scipy.sparse.coo.coo_matrix
            * train_mask: 训练集掩码向量,维度为 2708,当节点属于训练集时,相应位置为True,否则False
            * val_mask: 验证集掩码向量,维度为 2708,当节点属于验证集时,相应位置为True,否则False
            * test_mask: 测试集掩码向量,维度为 2708,当节点属于测试集时,相应位置为True,否则False

        Args:
        -------
            data_root: string, optional
                存放数据的目录,原始数据路径: ../data/cora
                缓存数据路径: {data_root}/ch5_cached.pkl
            rebuild: boolean, optional
                是否需要重新构建数据集,当设为True时,如果存在缓存数据也会重建数据

        """
        self.data_root = data_root #数据存放的路径
        save_file = osp.join(self.data_root, "ch5_cached.pkl")
        if osp.exists(save_file) and not rebuild:
            print("Using Cached file: {}".format(save_file))
            self._data = pickle.load(open(save_file, "rb"))
        else:
            self._data = self.process_data()
            with open(save_file, "wb") as f:
                pickle.dump(self.data, f)
            print("Cached file: {}".format(save_file))
    
    @property
    def data(self):
        """返回Data数据对象,包括x, y, adjacency, train_mask, val_mask, test_mask"""
        return self._data

    def process_data(self):
        """
        处理数据,得到节点特征和标签,邻接矩阵,训练集、验证集以及测试集
        引用自:https://github.com/rusty1s/pytorch_geometric
        """
        print("Process data ...")
        _, tx, allx, y, ty, ally, graph, test_index = [self.read_data(
            osp.join(self.data_root, name)) for name in self.filenames]
        train_index = np.arange(y.shape[0])
        val_index = np.arange(y.shape[0], y.shape[0] + 500)
        sorted_test_index = sorted(test_index)

        x = np.concatenate((allx, tx), axis=0)                #节点特征
        y = np.concatenate((ally, ty), axis=0).argmax(axis=1) #标签

        x[test_index] = x[sorted_test_index]
        y[test_index] = y[sorted_test_index]
        num_nodes = x.shape[0]

        train_mask = np.zeros(num_nodes, dtype=np.bool) #训练集
        val_mask = np.zeros(num_nodes, dtype=np.bool)   #验证集
        test_mask = np.zeros(num_nodes, dtype=np.bool)  #测试集
        train_mask[train_index] = True
        val_mask[val_index] = True
        test_mask[test_index] = True
        
        
        """"构建邻接矩阵"""
        adjacency = self.build_adjacency(graph)
        print("Node's feature shape: ", x.shape)
        print("Node's label shape: ", y.shape)
        print("Adjacency's shape: ", adjacency.shape)
        print("Number of training nodes: ", train_mask.sum())
        print("Number of validation nodes: ", val_mask.sum())
        print("Number of test nodes: ", test_mask.sum())

        return Data(x=x, y=y, adjacency=adjacency,
                    train_mask=train_mask, val_mask=val_mask, test_mask=test_mask)

    @staticmethod
    def build_adjacency(adj_dict):
        """根据邻接表创建邻接矩阵"""
        edge_index = []
        num_nodes = len(adj_dict)
        for src, dst in adj_dict.items():
            edge_index.extend([src, v] for v in dst)
            edge_index.extend([v, src] for v in dst)
        # 去除重复的边
        edge_index = list(k for k, _ in itertools.groupby(sorted(edge_index)))
        edge_index = np.asarray(edge_index)
        adjacency = sp.coo_matrix((np.ones(len(edge_index)), 
                                   (edge_index[:, 0], edge_index[:, 1])),
                    shape=(num_nodes, num_nodes), dtype="float32")
        return adjacency

    @staticmethod
    def read_data(path):
        """使用不同的方式读取原始数据以进一步处理"""
        name = osp.basename(path)
        if name == "ind.cora.test.index":
            out = np.genfromtxt(path, dtype="int64")
            return out
        else:
            out = pickle.load(open(path, "rb"), encoding="latin1")
            out = out.toarray() if hasattr(out, "toarray") else out
            return out

    @staticmethod
    def normalization(adjacency):
        """计算 H=D^-0.5 * (A+I) * D^-0.5"""
        adjacency += sp.eye(adjacency.shape[0])    # 增加自连接
        degree = np.array(adjacency.sum(1))
        d_hat = sp.diags(np.power(degree, -0.5).flatten())
        return d_hat.dot(adjacency).dot(d_hat).tocoo()

3. 图卷积层定义

class GraphConvolution(nn.Module):
    def __init__(self, input_dim, output_dim, use_bias=True):
        """图卷积:H*X*\theta

        Args:
        ----------
            input_dim: int
                节点输入特征的维度
            output_dim: int
                输出特征维度
            use_bias : bool, optional
                是否使用偏置
        """
        super(GraphConvolution, self).__init__()
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.use_bias = use_bias
        self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))
        if self.use_bias:
            self.bias = nn.Parameter(torch.Tensor(output_dim))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters() #初始化w

    def reset_parameters(self):
        init.kaiming_uniform_(self.weight) 
        #init.kaiming_uniform_神经网络权重初始化,神经网络要优化一个非常复杂的非线性模型,而且基本没有全局最优解,
        #初始化在其中扮演着非常重要的作用,尤其在没有BN等技术的早期,它直接影响模型能否收敛。
        
        if self.use_bias:
            init.zeros_(self.bias)

    def forward(self, adjacency, input_feature):
        """邻接矩阵是稀疏矩阵,因此在计算时使用稀疏矩阵乘法
    
        Args: 
        -------
            adjacency: torch.sparse.FloatTensor
                邻接矩阵
            input_feature: torch.Tensor
                输入特征
        """
        support = torch.mm(input_feature, self.weight)
        output = torch.sparse.mm(adjacency, support)
        if self.use_bias:
            output += self.bias
        return output

    def __repr__(self):
        return self.__class__.__name__ + ' (' \
            + str(self.input_dim) + ' -> ' \
            + str(self.output_dim) + ')'

4. GCN图卷积神经网络模型定义

有了数据和GCN层,就可以构建模型进行训练了。
定义一个两层的GCN,其中输入的维度为1433,隐藏层维度设为16,最后一层GCN将输出维度变为类别数7,激活函数使用的是ReLU。

class GcnNet(nn.Module):
    """
    定义一个包含两层GraphConvolution的模型
    """
    def __init__(self, input_dim=1433):
        super(GcnNet, self).__init__()
        self.gcn1 = GraphConvolution(input_dim, 16)
        self.gcn2 = GraphConvolution(16, 7)
    
    def forward(self, adjacency, feature):
        h = F.relu(self.gcn1(adjacency, feature))
        logits = self.gcn2(adjacency, h)
        return logits

 

5. 模型训练

5.1 超参数定义,包含学习率、正则化系数等。

LEARNING_RATE = 0.1 #学习率 学习率过小→ →→收敛过慢,学习率过大→ →→错过局部最优;
WEIGHT_DACAY = 5e-4 #正则化系数 weight_dacay,解决过拟合问题
EPOCHS = 200        #完整遍历训练集的次数
DEVICE = "cuda" if torch.cuda.is_available() else "cpu" #指定设备,如果当前显卡忙于其他工作,可以设置为 DEVICE = "cpu",使用cpu运行

为什么要训练200轮呢,因为我们最开始是不知道边的权重的,需要通过模型训练出来合适的权重,也就是公式中的W。

# 加载数据,并转换为torch.Tensor
dataset = CoraData().data
node_feature = dataset.x / dataset.x.sum(1, keepdims=True)  # 归一化数据,使得每一行和为1
tensor_x = tensor_from_numpy(node_feature, DEVICE)
tensor_y = tensor_from_numpy(dataset.y, DEVICE)
tensor_train_mask = tensor_from_numpy(dataset.train_mask, DEVICE)
tensor_val_mask = tensor_from_numpy(dataset.val_mask, DEVICE)
tensor_test_mask = tensor_from_numpy(dataset.test_mask, DEVICE)
normalize_adjacency = CoraData.normalization(dataset.adjacency)   # 规范化邻接矩阵

num_nodes, input_dim = node_feature.shape
indices = torch.from_numpy(np.asarray([normalize_adjacency.row, 
                                       normalize_adjacency.col]).astype('int64')).long()
values = torch.from_numpy(normalize_adjacency.data.astype(np.float32))
tensor_adjacency = torch.sparse.FloatTensor(indices, values, 
                                            (num_nodes, num_nodes)).to(DEVICE)

5.2 定义模型:

# 模型定义:Model, Loss, Optimizer
model = GcnNet(input_dim).to(DEVICE)
criterion = nn.CrossEntropyLoss().to(DEVICE) #nn.CrossEntropyLoss()函数计算交叉熵损失
optimizer = optim.Adam(model.parameters(), 
                       lr=LEARNING_RATE, 
                       weight_decay=WEIGHT_DACAY)

其中在定义模型时,还顺手定义了criterion,即在训练过程中可以用nn.CrossEntropyLoss()函数计算交叉熵损失:

 

5.3 定义训练和测试函数,进行训练

# 训练主体函数
def train():
    loss_history = []
    val_acc_history = []
    model.train()
    train_y = tensor_y[tensor_train_mask]
    
    for epoch in range(EPOCHS):
        # 共进行200次训练
        logits = model(tensor_adjacency, tensor_x)  # 前向传播
        #其中logits是模型输出,tensor_adjacency, tensor_x分别是邻接矩阵和节点特征。
        
        train_mask_logits = logits[tensor_train_mask]   # 只选择训练节点进行监督
        loss = criterion(train_mask_logits, train_y)    # 计算损失值,目的是优化模型,获得更科学的权重W
        optimizer.zero_grad()
        loss.backward()     # 反向传播计算参数的梯度
        optimizer.step()    # 使用优化方法进行梯度更新
        train_acc, _, _ = test(tensor_train_mask)     # 计算当前模型训练集上的准确率
        val_acc, _, _ = test(tensor_val_mask)     # 计算当前模型在验证集上的准确率
        
        # 记录训练过程中损失值和准确率的变化,用于画图
        loss_history.append(loss.item())
        val_acc_history.append(val_acc.item())
        print("Epoch {:03d}: Loss {:.4f}, TrainAcc {:.4}, ValAcc {:.4f}".format(
            epoch, loss.item(), train_acc.item(), val_acc.item()))
    
    return loss_history, val_acc_history


# 测试函数
def test(mask):
    model.eval()  # 表示将模型转变为evaluation(测试)模式,这样就可以排除BN和Dropout对测试的干扰
    
    with torch.no_grad():  # 显著减少显存占用
        logits = model(tensor_adjacency, tensor_x) #(N,16)->(N,7) N节点数
        test_mask_logits = logits[mask]  # 矩阵形状和mask一样
        
        predict_y = test_mask_logits.max(1)[1]  # 返回每一行的最大值中索引(返回最大元素在各行的列索引)
        accuarcy = torch.eq(predict_y, tensor_y[mask]).float().mean()
    return accuarcy, test_mask_logits.cpu().numpy(), tensor_y[mask].cpu().numpy()

 

使用上述代码进行模型训练,可以看到如下代码所示的日志输出:

loss, val_acc = train()
test_acc, test_logits, test_label = test(tensor_test_mask)
print("Test accuarcy: ", test_acc.item())#item()返回的是一个浮点型数据,测试集准确率

 

其中Epoch为训练轮数;loss是损失值;TrainAcc训练集准确率;ValAcc测试集上的准确率;

 

6. 可视化

将损失值和验证集准确率的变化趋势可视化:

损失函数用来测度模型的输出值和真实因变量值之间的差异

def plot_loss_with_acc(loss_history, val_acc_history):
    fig = plt.figure()
    # 坐标系ax1画曲线1
    ax1 = fig.add_subplot(111)  # 指的是将plot界面分成1行1列,此子图占据从左到右从上到下的1位置
    ax1.plot(range(len(loss_history)), loss_history,
             c=np.array([255, 71, 90]) / 255.)  # c为颜色
    plt.ylabel('Loss')
    
    # 坐标系ax2画曲线2
    ax2 = fig.add_subplot(111, sharex=ax1, frameon=False)  # 其本质就是添加坐标系,设置共享ax1的x轴,ax2背景透明
    ax2.plot(range(len(val_acc_history)), val_acc_history,
             c=np.array([79, 179, 255]) / 255.)
    ax2.yaxis.tick_right()  # 开启右边的y坐标
    
    ax2.yaxis.set_label_position("right")
    plt.ylabel('ValAcc')
    
    plt.xlabel('Epoch')
    plt.title('Training Loss & Validation Accuracy')
    plt.show()

plot_loss_with_acc(loss, val_acc)

 

可以看到红线代表的损失值随着训练次数的增加越来越小,蓝线代表的模型准确率越来越高。

将最后一层得到的输出进行TSNE降维,(TSNE)t分布随机邻域嵌入 是一种用于探索高维数据的非线性降维算法。

它将多维数据映射到适合于人类观察的两个或多个维度。

得到如下图所示的分类结果:

绘制测试数据的TSNE降维图:

from sklearn.manifold import TSNE
tsne = TSNE()
out = tsne.fit_transform(test_logits)
fig = plt.figure()
for i in range(7):
    indices = test_label == i
    x, y = out[indices].T
    plt.scatter(x, y, label=str(i))
plt.legend()

 

根据上述结果:我们通过图卷积神经网络算法,可以成功将论文集划分为较为鲜明的7类,这与论文集原本的种类划分基本一致,效果还是较为可观的。

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

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

相关文章

[闪存 2.1] 闪存芯片物理结构与_SLC/MLC/TLC/QLC

固态硬盘(Solid State Drives),简称SSD。它是一种电脑存储设备,由闪存(FLASH)、 闪存控制器、高速缓存(DRAM)组成。这是是固态硬盘的三个基本部件,对性能有关键影响。 闪存芯片简介闪存颗粒是固态

MySQL数据库的基础语法总结(1)

MySql一.数据库,数据表的基本操作1.数据库的基本操作2. 数据表的基本操作2.1 数据库的数据类型2.1.1 整数类型2.1.2 浮点数类型和定点数类型2.1.3 字符串类型2.1.4 日期与时间类型2.2 数据表的基本操作2.2.1 创建一个数据表2.2.2 查看数据表2.2.3 查看表的基本信息的MySQL指令2…

设计模式(上)

目录 1.设计模式概述 1.什么是设计模式 2.学习设计模式的意义 3.23种设计模式 4.七大设计原则 2.创建者模式 1.单例模式 2.工厂模式 3.抽象工厂模式 4.建造者模式 5.原型模式 3.结构型模式 1.适配器模式 2.桥接模式 3.代理模式 4.组合模式 5.装饰模式 6.享元…

[架构之路-24]:目标系统 - 系统软件 - C语言的结构与程序的工作原理 - 程序控制、函数调用栈、函数调用性能优化

目录 前言: 第1章 结构化程序与分层编程系统 1.1 计算机软硬件模型 1.2 程序的经典框架:算法数据结构 1.3 程序的结构化框架 1.4 程序的层次模型 1.5 程序设计的层次模型 第2章 C语言概述 2.1 C语言本质 2.2 C语言关键字 2.3 C语言在编程语言的…

js算法 字母大小写转换

题目:输入字符串将大写转换成小写,小写转换成大写? js字母大小写转换方法: 1、转换成大写:toUpperCase() 2、转换成小写:toLowerCase()方法一:把输入的字符串转成数组用split(&…

chatgpt 无法登录报错Access denied、OpenAl‘s services are not available in yourcountry. (error=unsupported )

一、主要解决有账号无法登录问题,共包含两种情况。 1、Oops! OpenAls services are not available in yourcountry.(errorunsupported country) 2、Access denied You do not have access to chat.openai.com. The site owner may have set restrictions that p…

Linux命令之nano命令

一、nano命令简介 nano是一个小型、免费、友好的编辑器,旨在取代非免费Pine包中的默认编辑器Pico。nano不仅复制了Pico的外观,还实现了Pico中一些缺失(或默认禁用)的功能,例如“搜索和替换”和“转到行号和列号”。nan…

Element Plus的el-tree-select组件,懒加载 + 数据回显

目录一、背景说明二、使用1. dom2.methods三、回显一、背景说明 技术:Vue3 Element Plus需求:在选择组织机构时以树结构下拉展示。用到组件:TreeSelect 树形选择组件(el-tree-select) 官网文档地址: ht…

Vue如何启动项目

文章目录 文章目录 前言 一、首先了解vue-cli脚手架 二、安装脚手架 三、启动项目 总结 前言 废话就不多说了,直接进入正题 一、首先了解vue-cli脚手架 vue-cli 是一个基于 Vue.js 进行快速开发的完整系统 vue-cli是 基于 webpack 构建 vue 前端模块工程环境webpa…

Vue.js介绍

目录 一、 Vue.js是什么 二、 Vue.js优点 三、 前提条件 四、 安装Vue 1. 直接用 script 标签 引入 2. 命令行工具 (CLI) 3. 其他方法或细节应用 五、声明式渲染 六、 理解什么是MVVM MVVM模式的组成部分 MVVM优点 一、 Vue.js是什么 Vue.js (vue读音 /vjuː/&am…

【CSS扩展】VUE如何使用或修改element plus中自带的CSS全局变量来定义样式

目录 一、CSS声明全局变量 二、使用el plus 和 el ui的自带样式 1、element plus—— var.scss位置 2、element ui—— var.scss位置 三、修改el plus 和 el ui中的自定义样式变量(方法一致) 本萌新最近在写网页时使用到了element plus中自带的CSS全…

Vue中如何解决跨域问题

跨域 跨域报错是前端开发中非常经典的一个错误,报错如下 Access to XMLHttpRequest at ...... from origin ...... has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource. 跨域错误源自于浏览器的同源…

《中秋佳节倍思亲》——2022年这场中秋

中秋佳夜,圆的是月,满的是情,但一人终究扛下离愁 作为一个杭漂者,虽不及北漂般艰难困苦,但也举步维艰啊! 或许,这对我而言,正是一个别样的中秋,也正是一个值得我一生纪念…

多端统一开发解决方案---Taro

Taro 一套代码,多端运行,释放双手的摸鱼神器 文章目录Taro 一套代码,多端运行,释放双手的摸鱼神器1. 简介2. 准备工作2.1 安装及使用2.1.1 开发者工具2.1.2 tarojs2.1安装tarojs工具3. Taro 使用4.限制5.路由跳转汇总6.注意事项7.…

vue3:安装配置sass

目录 前言: 1. 安装sass 2. 新建style目录,存放scss文件 3. main.ts 4. vite.config.ts 5. Test.vue 前言: 对于前端开发人员来说,css预处理的语言已经是家常便饭了,如sass,less等等,那么…

深度学习实战 1 YOLOv5结合BiFPN

目录 1. BiFPN论文简介 2. 在Common.py中添加定义模块(Concat) 3. 将类名加入进去,修改yolo.py 4. 修改train.py 5. 修改配置文件yolov5.yaml 1. BiFPN论文简介 论文《EfficientDet: Scalable and Efficient Object Detection》地址:https://arxiv…

微信小程序云开发的具体使用教程

小程序云开发介绍 云开发官方文档 一个小程序在开发时,除了考虑界面功能逻辑外,还需要后端的数据支持。而为了获得后端的数据支持,开发者需要提前考虑服务器、存储和数据库等需求,并且会花费时间精力在部署应用、依赖上。因此官方…

【JavaScript 逆向】网易易盾滑块逆向分析

声明本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!案例目标验证码:aHR0cHM6Ly9kdW4uMTYzLmNvbS90cmlhbC9qaWdzYXc以上均做了脱敏处理,Base64 编码及解码方式:…

MongoDB数据库性能监控详解

目录一、MongoDB启动超慢1、启动日常卡住,根本不用为了截屏而快速操作,MongoDB启动真的超级慢~~2、启动MongoDB配置服务器,间歇性失败。3、查看MongoDB日志,分析“MongoDB启动慢”的原因。4、耗时“一小时”,MongoDB启…

color ui

color ui——组件使用 组件类型 ColorUI Vant webapp ColorUI 下载地址 Git地址 官网地址 简介 ColorUI是一个css库!!!在你引入样式后可以根据class来调用组件,一些含有交互的操作我也有简单写,可以为你开发提供一…