图神经网络实战(7)——图卷积网络(Graph Convolutional Network, GCN)详解与实现

图神经网络实战(7)——图卷积网络详解与实现

    • 前言
    • 1. 图卷积层
    • 2. 比较 GCN 和 GNN
      • 2.1 数据集分析
      • 2.2 实现 GCN 架构
    • 小结
    • 系列链接

前言

图卷积网络 (Graph Convolutional Network, GCN) 架构由 KipfWelling2017 年提出,其理念是创建一种适用于图的高效卷积神经网络 (Convolutional Neural Networks, CNN)。更准确地说,它是图信号处理中图卷积操作的近似,由于其易用性,GCN 已成为最受欢迎的图神经网络 (Graph Neural Networks, GNN) 之一,是处理图数据时创建基线模型的首选架构。
在本节中,我们将讨论 Vanilla GNN 架构的局限性,这有助于我们理解 GCN 的核心思想。并详细介绍 GCN 的工作原理,解释为什么 GCNVanilla GNN 性能更好,通过使用 PyTorch Geometric 在 Cora 和 Facebook Page-Page 数据集上实现 GCN 来验证其性能。

1. 图卷积层

与表格或图像数据不同,图数据中节点的邻居数量并不总是相同。例如,在下图中,节点 13 个邻居,而节点 2 只有 1 个:

图数据

但是,观察图神经网络 (Graph Neural Networks, GNN) 层就会发现,邻居数量的差异并不会导致计算的复杂化。GNN 层由一个简单的求和公式组成,没有任何归一化系数,计算节点 i i i 的嵌入方法如下:
h i = ∑ j ∈ N i x j W T h_i=\sum_{j\in \mathcal N_i}x_jW^T hi=jNixjWT
假设节点 11,000 个邻居,而节点 2 只有 1 个邻居,那么 h 1 h_1 h1 嵌入的值将远远大于 h 2 h_2 h2 嵌入的值。这样便会出现一个问题,当我们要对这些嵌入进行比较时,如果它们的值相差过大,如何进行有意义的比较?
一个简单的解决方案是将嵌入除以邻居数量,用 deg ⁡ ( A ) \deg(A) deg(A) 表示节点的度,因此 GNN 层公式可以更新为:
h i = 1 deg ⁡ ( i ) ∑ j ∈ N i x j W T h_i=\frac 1{\deg(i)}\sum_{j\in \mathcal N_i}x_jW^T hi=deg(i)1jNixjWT
那么如何将其转化为矩阵乘法呢?首先回顾普通 GNN 层的计算公式:
H = A ~ T X W T H=\tilde A^TXW^T H=A~TXWT
其中, A ~ = A + I \tilde A=A+I A~=A+I。公式中缺少的是一个能为我们提供归一化系数 1 deg ⁡ ( A ) \frac 1 {\deg(A)} deg(A)1 的矩阵,可以利用度矩阵 D D D 来计算每个节点的邻居数量。上示图像中的图的度矩阵如下:
D = [ 3 0 0 0 0 1 0 0 0 0 2 0 0 0 0 2 ] D=\left[\begin{array}{c} 3 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 2 & 0\\ 0 & 0 & 0 & 2\\ \end{array}\right] D= 3000010000200002
使用 NumPy 表示以上矩阵:

import numpy as npD = np.array([[3, 0, 0, 0],[0, 1, 0, 0],[0, 0, 2, 0],[0, 0, 0, 2]
])

根据定义, D D D 给出了每个节点的度 deg ⁡ ( i ) \deg(i) deg(i) 。因此,根据度矩阵的逆矩阵 D − 1 D^{-1} D1 可以直接得到归一化系数 1 deg ⁡ ( A ) \frac 1 {\deg(A)} deg(A)1

可以使用 numpy.linalg.inv() 函数计算矩阵的逆:

print(np.linalg.inv(D))
'''输出如下
[[0.33333333 0.         0.         0.        ][0.         1.         0.         0.        ][0.         0.         0.5        0.        ][0.         0.         0.         0.5       ]]
'''

为了更加精确,在图中添加了自循环,用 A ~ = A + I \tilde A=A+I A~=A+I 表示。同样,我们也需要在度矩阵中加入自循环,即 D ~ = D + I \tilde D= D+I D~=D+I ,因此最终所需的矩阵为 D ~ − 1 = ( D + I ) − 1 \tilde D^{-1} = (D+I)^{-1} D~1=(D+I)1

NumPy 中,可以使用函数 numpy.identity(n) 快速创建指定维度 n 的单位矩阵 I I I

print(np.linalg.inv(D + np.identity(4)))
'''输出如下
[[0.25       0.         0.         0.        ][0.         0.5        0.         0.        ][0.         0.         0.33333333 0.        ][0.         0.         0.         0.33333333]]
'''

得到归一化系数矩阵后,有两种应用方式:

  • D ~ − 1 A ~ X W T \tilde D^{-1}\tilde AXW^T D~1A~XWT 会对每一行特征进行归一化处理。
  • A ~ D ~ − 1 X W T \tilde A \tilde D^{-1}XW^T A~D~1XWT 会对每一列特征进行归一化处理。

接下来,通过计算 D ~ − 1 A ~ \tilde D^{-1}\tilde A D~1A~ A ~ D ~ − 1 \tilde A \tilde D^{-1} A~D~1 进行验证:

D ~ − 1 A ~ = [ 1 4 0 0 0 0 1 2 0 0 0 0 1 3 0 0 0 0 1 3 ] ⋅ [ 1 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 ] = [ 1 4 1 4 1 4 1 4 1 2 1 2 0 0 1 3 0 1 3 1 3 1 3 0 1 3 1 3 ] A ~ D ~ − 1 = [ 1 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 ] ⋅ [ 1 4 0 0 0 0 1 2 0 0 0 0 1 3 0 0 0 0 1 3 ] = [ 1 4 1 2 1 3 1 3 1 4 1 2 0 0 1 4 0 1 3 1 3 1 4 0 1 3 1 3 ] \tilde D^{-1}\tilde A=\left[\begin{array}{c} \frac 14 & 0 & 0 & 0\\ 0 & \frac 12 & 0 & 0\\ 0 & 0 & \frac 13 & 0\\ 0 & 0 & 0 & \frac 13\\ \end{array}\right] \cdot \left[\begin{array}{c} 1 & 1 & 1 & 1\\ 1 & 1 & 0 & 0\\ 1 & 0 & 1 & 1\\ 1 & 0 & 1 &1\\ \end{array}\right]=\left[\begin{array}{c} \frac 14 & \frac 14 & \frac 14 & \frac 14\\ \frac 12 & \frac 12 & 0 & 0\\ \frac 13 & 0 & \frac 13 & \frac 13\\ \frac 13 & 0 & \frac 13 & \frac 13\\ \end{array}\right]\\ \tilde A \tilde D^{-1}=\left[\begin{array}{c} 1 & 1 & 1 & 1\\ 1 & 1 & 0 & 0\\ 1 & 0 & 1 & 1\\ 1 & 0 & 1 &1\\ \end{array}\right] \cdot \left[\begin{array}{c} \frac 14 & 0 & 0 & 0\\ 0 & \frac 12 & 0 & 0\\ 0 & 0 & \frac 13 & 0\\ 0 & 0 & 0 & \frac 13\\ \end{array}\right]=\left[\begin{array}{c} \frac 14 & \frac 12 & \frac 13 & \frac 13\\ \frac 14 & \frac 12 & 0 & 0\\ \frac 14 & 0 & \frac 13 & \frac 13\\ \frac 14 & 0 & \frac 13 & \frac 13\\ \end{array}\right] D~1A~= 41000021000031000031 1111110010111011 = 4121313141210041031314103131 A~D~1= 1111110010111011 41000021000031000031 = 4141414121210031031313103131

在第一种情况下,每一行的和都等于 1;在第二种情况下,每一列的和都等于 1。矩阵乘法可以使用 numpy.matmul() 函数执行,或使用 Python 内置的矩阵乘法运算符 @。定义邻接矩阵并使用 @ 操作符计算矩阵乘法:

A = np.array([[1, 1, 1, 1],[1, 1, 0, 0],[1, 0, 1, 1],[1, 0, 1, 1]
])
print(np.linalg.inv(D + np.identity(4)) @ A)
print('------------------------------')
print(A @ np.linalg.inv(D + np.identity(4)))
'''输出如下
[[0.25       0.25       0.25       0.25      ][0.5        0.5        0.         0.        ][0.33333333 0.         0.33333333 0.33333333][0.33333333 0.         0.33333333 0.33333333]]
------------------------------
[[0.25       0.5        0.33333333 0.33333333][0.25       0.5        0.         0.        ][0.25       0.         0.33333333 0.33333333][0.25       0.         0.33333333 0.33333333]]
'''

得到的结果与手动计算的矩阵乘法相同。那么,在实践中我们应该使用哪种应用方式?第一种方案似乎看起来合理,因为它能很好地对相邻节点特征进行归一化处理。
KipfWelling 提出,具有多个邻居的节点的特征很容易传播,而与之相反,孤立节点的特征不容易传播。在 GCN 论文中,作者提出了一种混合归一化方法来平衡这种影响。在实践中,使用以下公式为邻居较少的节点分配更高的权重:
H = D ~ − 1 2 A ~ T D ~ − 1 2 X W T H=\tilde D^{-\frac 12}\tilde A^T\tilde D^{-\frac 12}XW^T H=D~21A~TD~21XWT
就单个嵌入而言,上式可以写为:
h i = ∑ j ∈ N i 1 deg ⁡ ( i ) deg ⁡ ( j ) x j W T h_i=\sum_{j\in \mathcal N_i}\frac 1{\sqrt {\deg(i)}\sqrt {\deg(j)}}x_jW^T hi=jNideg(i) deg(j) 1xjWT
这就是实现原始图卷积层的数学公式。与普通的 GNN 层一样,我们可以通过堆叠图卷积层创建 GCN。接下来,使用 PyTorch Geometric 实现一个 GCN 模型,并验证其性能是否优于原始图神经网络模型。

2. 比较 GCN 和 GNN

我们已经证明了 vanilla GNN 性能优于 Node2Vec 模型,接下来,我们将其与 GCN 进行比较,比较它们在 Cora 和 Facebook Page-Page 数据集上的表现。
与普通 GNN 相比,GCN 的主要特点是通过考虑节点度来权衡其特征。在构建模型之前,我们首先计算这两个数据集中的节点度,这与 GCN 的性能直接相关。
根据我们对 GCN 架构的了解,可以猜测当节点度差异较大时,它的性能会更好。如果每个节点都有相同数量的邻居,那么无论使用哪种归一化方式,架构之间都是等价的: deg ⁡ ( i ) deg ⁡ ( i ) = deg ⁡ ( i ) \sqrt {\deg(i)} \sqrt {\deg(i)}= \deg (i) deg(i) deg(i) =deg(i)

2.1 数据集分析

(1)PyTorch Geometric 中导入 Planetoid 类,为了可视化节点度,同时导入两个附加类( degree 用于获取每个节点的邻居数,Counter 用于计算每个度数的节点数)和 matplotlib 库:

import torch
from torch_geometric.datasets import Planetoid
from torch_geometric.utils import degree
from collections import Counter
import matplotlib.pyplot as plt

(2) 导入 Cora 数据集,并将图存储在 data 中:

dataset = Planetoid(root=".", name="Cora")
data = dataset[0]

(3) 计算图中每个节点的邻居数:

degrees = degree(data.edge_index[0]).numpy()

(4) 为了生成更自然的可视化效果,统计具有相同度的节点数量:

numbers = Counter(degrees)

(5) 使用条形图来绘制统计结果:

fig, ax = plt.subplots()
ax.set_xlabel('Node degree')
ax.set_ylabel('Number of nodes')
plt.bar(numbers.keys(), numbers.values())
plt.show()

节点度分布

从上图中可以看出,图中的度分布近似指数分布,从 1 个邻居( 485 个节点)到 168 个邻居( 1 个节点)不等,这种不平衡的数据集正是归一化处理的用武之地。

(6)Facebook Page-Page 数据集上重复同样的过程:

from torch_geometric.datasets import FacebookPagePage# Import dataset from PyTorch Geometric
dataset = FacebookPagePage(root=".")
data = dataset[0]# Create masks
data.train_mask = range(18000)
data.val_mask = range(18001, 20000)
data.test_mask = range(20001, 22470)# Get list of degrees for each node
degrees = degree(data.edge_index[0]).numpy()# Count the number of nodes for each degree
numbers = Counter(degrees)# Bar plot
fig, ax = plt.subplots()
ax.set_xlabel('Node degree')
ax.set_ylabel('Number of nodes')
plt.bar(numbers.keys(), numbers.values())
plt.show()

节点度分布

Facebook Page-Page 数据集的图的节点度分布看起来更加失衡,邻居数量从 1709 不等。出于同样的原因,Facebook Page-Page 数据集也是应用 GCN 的合适实例。

2.2 实现 GCN 架构

我们可以从零开始实现 GCN 层,但这里我们无需再从头造轮子,PyTorch Geometric 已经内置了 GCN 层,首先在 Cora 数据集上实现 GCN 架构。

(1)PyTorch Geometric 中导入 GCN 层,并导入 PyTorch

import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConvdataset = Planetoid(root=".", name="Cora")
data = dataset[0]

(2) 创建函数 accuracy() 计算模型准确率:

def accuracy(y_pred, y_true):"""Calculate accuracy."""return torch.sum(y_pred == y_true) / len(y_true)

(3) 创建 GCN 类,其中 __init__() 函数接受三个参数作为输入:输入维度 dim_in、隐藏维度 dim_h 和输出维度 dim_out

class GCN(torch.nn.Module):"""Graph Convolutional Network"""def __init__(self, dim_in, dim_h, dim_out):super().__init__()self.gcn1 = GCNConv(dim_in, dim_h)self.gcn2 = GCNConv(dim_h, dim_out)

(4) forward() 方法使用两个 GCN 层,并对分类结果应用 log_softmax 函数:

    def forward(self, x, edge_index):h = self.gcn1(x, edge_index)h = torch.relu(h)h = self.gcn2(h, edge_index)return F.log_softmax(h, dim=1)

(5) fit() 方法与 Vanilla GNN 相同,为了更好的比较,使用具有相同参数的 Adam 优化器,其中学习率 lr0.1L2 正则化 weight_decay0.0005

    def fit(self, data, epochs):criterion = torch.nn.CrossEntropyLoss()optimizer = torch.optim.Adam(self.parameters(),lr=0.01,weight_decay=5e-4)self.train()for epoch in range(epochs+1):optimizer.zero_grad()out = self(data.x, data.edge_index)loss = criterion(out[data.train_mask], data.y[data.train_mask])acc = accuracy(out[data.train_mask].argmax(dim=1),data.y[data.train_mask])loss.backward()optimizer.step()if(epoch % 20 == 0):val_loss = criterion(out[data.val_mask], data.y[data.val_mask])val_acc = accuracy(out[data.val_mask].argmax(dim=1),data.y[data.val_mask])print(f'Epoch {epoch:>3} | Train Loss: {loss:.3f} | Train Acc:'f' {acc*100:>5.2f}% | Val Loss: {val_loss:.2f} | 'f'Val Acc: {val_acc*100:.2f}%')

(6) 编写 test() 方法:

    @torch.no_grad()def test(self, data):self.eval()out = self(data.x, data.edge_index)acc = accuracy(out.argmax(dim=1)[data.test_mask], data.y[data.test_mask])return acc

(7) 实例化模型并训练 100epoch

# Create the Vanilla GNN model
gcn = GCN(dataset.num_features, 16, dataset.num_classes)
print(gcn)# Train
gcn.fit(data, epochs=100)

训练过程中的输出结果如下:

训练过程

(8) 最后,在测试集上对模型进行评估:

acc = gcn.test(data)
print(f'\nGCN test accuracy: {acc*100:.2f}%\n')# GCN test accuracy: 80.30%

重复此实验 100 次,模型的平均准确率为 80.26%(±0.59%),明显 vanilla GNN 模型的平均准确率 74.99%(±1.60%)

(9) 将同样的模型应用于 Facebook Page-Page 数据集,其平均准确率可以达到 91.78%(±0.31%),同样比 vanilla GNN 的结果( 84.91%(±1.88%) )高出很多:

# Load Facebook Page-Page
dataset = FacebookPagePage(root=".")
data = dataset[0]
data.train_mask = range(18000)
data.val_mask = range(18001, 20000)
data.test_mask = range(20001, 22470)# Train GCN
gcn = GCN(dataset.num_features, 16, dataset.num_classes)
print(gcn)
gcn.fit(data, epochs=100)
acc = gcn.test(data)
print(f'\nGCN test accuracy: {acc*100:.2f}%\n')

模型训练过程

下表总结了不同模型在不同数据集上的准确率和标准差:

MLPGNNGCN
Cora53.47%(±1.95%)74.99%(±1.60%)80.26%(±0.59%)
Facebook75.22%(±0.39%)84.91%(±1.88%)91.78%(±0.31%)

我们可以将这些性能提升归因于这两个数据集中节点度的分布的不平衡性。通过对特征进行归一化处理,并考虑中心节点及其邻居的数量,GCN 的灵活性得到了极大的提升,可以很好地处理各种类型的图。但节点分类远不是 GCN 的唯一应用,在之后的学习中,我们将看到 GCN 模型的更多新颖应用。

小结

在本节中,我们改进了 vanilla GNN 层,使其能够正确归一化节点特征,这一改进引入了图卷积网络 (Graph Convolutional Network, GCN) 层和混合归一化。在 CoraFacebook Page-Page 数据集上,我们对比了 GCN 架构与 Node2Vecvanilla GNN 之间的性能差异。由于采用了归一化处理,GCN 在这两个数据集中都具有较高的准确率。

系列链接

图神经网络实战(1)——图神经网络(Graph Neural Networks, GNN)基础
图神经网络实战(2)——图论基础
图神经网络实战(3)——基于DeepWalk创建节点表示
图神经网络实战(4)——基于Node2Vec改进嵌入质量
图神经网络实战(5)——常用图数据集
图神经网络实战(6)——使用PyTorch构建图神经网络

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

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

相关文章

基于springboot+vue+Mysql的教学视频点播系统

开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…

idea使用docker将Java项目生成镜像并使用

1:开启docker 远程访问 使用 vim 编辑docker服务配置文件 vim /lib/systemd/system/docker.service [Service] Typenotify # the default is not to use systemd for cgroups because the delegate issues still # exists and systemd currently does not suppor…

操作系统内功篇:内存管理之虚拟内存

一 虚拟内存 在这种情况下,要想在内存中同时运行两个程序是不可能的。如果第一个程序在 2000 的位置写入一个新的值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,这两个程序会立刻崩溃。 …

最简单的 AAC 音频码流解析程序

最简单的 AAC 音频码流解析程序 最简单的 AAC 音频码流解析程序原理源程序运行结果下载链接参考 最简单的 AAC 音频码流解析程序 参考雷霄骅博士的文章:视音频数据处理入门:AAC音频码流解析 本文中的程序是一个AAC码流解析程序。该程序可以从AAC码流中…

Linux(05) Debian 系统修改主机名

查看主机名 方法1:hostname hostname 方法2:cat etc/hostname cat /etc/hostname 如果在创建Linux系统的时候忘记修改主机名,可以采用以下的方式来修改主机名称。 修改主机名 注意,在linux中下划线“_”可能是无效的字符&…

数据结构(初阶)第一节:数据结构概论

本篇文章是对数据结构概念的纯理论介绍,希望系统了解数据结构概念的友友可以看看,对概念要求不高的友友稍做了解后移步下一节: 数据结构(初阶)第二节:顺序表-CSDN博客 正文 目录 正文 1.数据结构的相关概…

qqqqqqq

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和…

Polardb MySQL 产品架构及特性

一、产品概述; 1、产品族 参考:https://edu.aliyun.com/course/3121700/lesson/341900000?spma2cwt.28120015.3121700.6.166d71c1wwp2px 2、polardb mysql架构优势 1)大容量高弹性:最大支持存储100T,最高超1000核CPU&#xff0…

open Gauss 数据库-03 openGauss数据库维护管理指导手册

发文章是为了证明自己真的掌握了一个知识,同时给他人带来帮助,如有问题,欢迎指正,祝大家万事胜意! 目录 前言 openGauss数据库维护管理 1 操作系统参数检查 1.1 实验介绍 1.2 场景设置及操作步骤 2 openGauss 运…

认识什么是Webpack

目录 1. 认识Webpack 1.1. 什么是Webpack?(定义) 1.2. 使用Webpack 1.2.1. 需求 1.2.2. 步骤 1.3. 入口和出口默认值 1.3.1. 需求代码如下 2. 修改Webpack打包入口和出口 2.1. 步骤: 2.2. 注意 3. Webpack自动生成html文件 3.1.…

D-迷恋网游(遇到过的题,做个笔记)

我的代码&#xff1a; #include <iostream> using namespace std; int main() {int a, b, c; //a表示内向&#xff0c;b表示外向&#xff0c;c表示无所谓cin >> a >> b >> c; //读入数 if (b % 3 0 || 3-b % 3 < c) //如果外向的人能够3人组成…

真·面试题总结——JVM虚拟机

JVM虚拟机 JVM虚拟机规范与实现 JVM虚拟机规范 JVM虚拟机实现 JVM的常见实现 JVM虚拟机物理架构 JVM虚拟机的运转流程 JVM类加载过程 JVM类加载器及类加载器类型 JVM类加载器双亲委派机制 JVM运行时数据区的内存模型 JVM运行时数据区的内存模型&#xff1a;程序计数器…

蓝桥杯第八届c++大学B组详解

目录 1.购物单 2.等差素数列 3.承压计算 4.方格分割 5.日期问题 6.包子凑数 7.全球变暖 8.k倍区间 1.购物单 题目解析&#xff1a;就是将折扣字符串转化为数字&#xff0c;进行相加求和。 #include<iostream> #include<string> #include<cmath> usin…

vue2 列表一般不使用索引删除的原因

在 Vue 中使用索引来删除列表项可能会导致一系列问题&#xff0c;尤其是在处理动态列表时。以下是一些可能的问题和相应的例子&#xff1a; 1. 数据不一致问题 当你使用索引来删除列表中的某个项时&#xff0c;如果列表中的其他项发生了变化&#xff08;比如新增或重新排序&a…

编译时提示存在多个默认构造函数的错误怎么解决呢?

c程序中&#xff0c;如果编译器提升存在多个默认构造函数怎么解决呢&#xff1f; class Date { public:Date(){_year 1900;_month 1;_day 1;}Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;} private:int _year;int _month;int _day…

chromium源码学习-调试日志 LOG

在学习 chromium 源码时&#xff0c;我们经常需要增加调试日志&#xff0c;常见的用法一般是 LOG(INFO) << "调试信息";其中 INFO 代表当前这条日志的级别&#xff0c;使用的时候就是输入 INFO 就行。接下来我们在探索下这个宏背后的内容。 一、基本用法 LO…

读所罗门的密码笔记08_共生思想(下)

1. 机器判断 1.1. 在生活的各个领域&#xff0c;机器正在我们无意识的情况下做出更多的决定 1.1.1. 我们看到的新闻会塑造我们的观点和行动&#xff0c;它们是根据我们过去行为中所表达的倾向&#xff0c;或者其他同类人的行为而生成的 1.2. …

K-均值聚类算法

K-均值聚类算法是一种常用的无监督学习算法&#xff0c;用于将数据集分成 K 个簇。该算法的主要思想是通过迭代的方式将数据点分配到离它们最近的簇中&#xff0c;并更新簇的中心点&#xff0c;直到满足某个停止条件为止。 以下是 K-均值聚类算法的基本步骤&#xff1a; 初始化…

【热门话题】WebKit架构简介

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 WebKit架构简介一、引言二、WebKit概览1. 起源与发展2. 模块化设计 三、WebCore…

软考高级架构师:流水线的概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…