PyG-节点分类+链接预测+异常检测示例

news2024/11/16 23:50:38

引言

图神经网络(Graph Neural Networks)是一种针对图结构数据(如社交图、网络安全网络或分子表示)设计的机器学习算法。它在过去几年里发展迅速,被用于许多不同的应用程序。在这篇文章中我们将回顾GNN的基础知识,然后使用Pytorch Geometric解决一些常见的主要问题,并讨论一些算法和代码的细节。

常见的图神经网络应用

GNN可以用来解决各种与图相关的机器学习问题:

  • 节点的分类:预测节点的类别或标签。例如,在网络安全中检测网络中的欺诈实体可能是一个节点分类问题。

  • 链接预测:预测节点之间是否存在潜在的链接(边)。例如,社交网络服务根据网络数据建议可能的朋友联系。

  • 图分类:将图形本身划分为不同的类别。比如通过观察一个化合物的图结构来确定它是有毒的还是无毒的。

  • 社区检测:将节点划分为集群。比如在社交图中寻找不同的社区。

  • 异常检测:以无监督的方式在图中查找离群节点。如果没有标签,可以使用这种方法。

在这篇文章中,我们将回顾节点分类、链接预测和异常检测的相关知识和用Pytorch Geometric代码实现这三个算法。

图卷积

图神经网络在过去的几年里发展迅速,并且有许多的变体。在这些GNN变体中图卷积网络可能是最流行和最基本的算法。在本节中我们将对其进行回顾和介绍。

图卷积是一种基于图结构提取/汇总节点信息的有效方法。它是卷积神经网络卷积运算的一个变体,卷积神经网络通常用于解决图像问题。

在图像中,像素在网格中按结构排序,卷积操作中的过滤器或卷积核(权重矩阵)以预先确定的步幅在图像上滑动。像素的邻域由过滤器大小决定(下图中过滤器大小为3 x 3,蓝色过滤器中的8个灰色像素为邻域),过滤器中的加权像素值被聚合为单个值。卷积运算的输出比输入图像的尺寸小,但对输入图像有更高层次的视图,这对预测图像问题很有用,比如图像分类。

在图中,节点以非结构化的方式排序,节点之间的邻域大小不同。图卷积取给定节点(下图中的红色节点)及其邻居(蓝圈内的灰色节点)的节点特征的平均值,计算更新后的节点表示值。通过这种卷积操作,节点表示捕获局部的图信息。

下图显示了更多关于图卷积操作的细节。邻居节点(蓝色)的节点特征和目标节点(红色)的节点特征被平均。然后与权重向量(W)相乘,其输出更新目标节点的节点特征(更新后的节点值也称为节点嵌入)。

对于那些相关的节点,节点特征使用度矩阵的逆进行归一化,然后再聚合而不是简单的平均(原始论文公式8中提出)

这个卷积操作中需要注意的一点是,图卷积的数量决定了节点特征被聚合到每个节点的步数。在下图中,第一个卷积将蓝色节点的特征聚合到橙色节点中,第二个卷积将绿色节点的特征合并到橙色节点中

可以看到卷积的数量决定了聚合的节点特征有多远

在接下来的几节中,我们实现GCN。但是在深入研究它们之前,先熟悉一下将要使用的数据集。

Cora - 基准数据集

Cora数据集是一个论文引用网络数据,包含2708篇科学论文。图中的每个节点代表一篇论文,如果一篇论文引用另一篇论文,则有节点间有一条边相连。

我们使用PyG (Pytorch Geometric)来实现GCN, GCN是GNN的流行库之一。Cora数据集也可以使用PyG模块加载:

 from torch_geometric.datasets import Planetoid
 
 dataset = Planetoid(root='/tmp/Cora', name='Cora')
 graph = dataset[0]
Cora数据集来源于Pytorch Geometric的“ Automating the Construction of Internet Portals with Machine Learning”论文。

节点特征和边缘信息如下所示。节点特征是 1433 个词向量,表示每个出版物中的词不存在 (0) 或存在 (1)。边在邻接列表中表示。

每个节点都是七个类别中的一个,这将就是分类的目标标签

利用NetworkX库可以实现图数据的可视化。节点颜色代表不同的类。

import random
 from torch_geometric.utils import to_networkx
 import networkx as nx
 
 def convert_to_networkx(graph, n_sample=None):
 
    g = to_networkx(graph, node_attrs=["x"])
    y = graph.y.numpy()
 
    if n_sample is not None:
        sampled_nodes = random.sample(g.nodes, n_sample)
        g = g.subgraph(sampled_nodes)
        y = y[sampled_nodes]
 
    return g, y
 
 
 def plot_graph(g, y):
 
    plt.figure(figsize=(9, 7))
    nx.draw_spring(g, node_size=30, arrows=False, node_color=y)
    plt.show()
     
     
 g, y = convert_to_networkx(graph, n_sample=1000)
 plot_graph(g, y)

节点分类

对于节点分类问题,可以使用PyG中的RandomNodeSplit模块将节点分为train、valid和test(我替换数据中的原始分割掩码,因为它的训练集太小了)。

 import torch_geometric.transforms as T
 
 split = T.RandomNodeSplit(num_val=0.1, num_test=0.2)
 graph = split(graph)

数据分割标记会被写入到图对象的掩码属性中(参见下图),而不是图本身。这些掩码用于训练损失计算和模型评估,而图卷积使用整个图数据。

节点分类基线模型(MLP)

在构建GCN之前,只使用节点特征来训练MLP(多层感知器,即前馈神经网络)来创建一个基线性能。该模型忽略节点连接(或图结构),并试图仅使用词向量对节点标签进行分类。模型类如下所示。它有两个隐藏层(Linear),带有ReLU激活,后面是一个输出层。

 import torch.nn as nn
 
 class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.layers = nn.Sequential(
        nn.Linear(dataset.num_node_features, 64),
        nn.ReLU(),
        nn.Linear(64, 32),
        nn.ReLU(),
        nn.Linear(32, dataset.num_classes)
        )
 
    def forward(self, data):
        x = data.x # only using node features (x)
        output = self.layers(x)
        return output

我们用一个普通的Pytorch训练/验证流程来定义训练和评估函数。

 def train_node_classifier(model, graph, optimizer, criterion, n_epochs=200):
 
    for epoch in range(1, n_epochs + 1):
        model.train()
        optimizer.zero_grad()
        out = model(graph)
        loss = criterion(out[graph.train_mask], graph.y[graph.train_mask])
        loss.backward()
        optimizer.step()
 
        pred = out.argmax(dim=1)
        acc = eval_node_classifier(model, graph, graph.val_mask)
 
        if epoch % 10 == 0:
            print(f'Epoch: {epoch:03d}, Train Loss: {loss:.3f}, Val Acc: {acc:.3f}')
 
    return model
 
 
 def eval_node_classifier(model, graph, mask):
 
    model.eval()
    pred = model(graph).argmax(dim=1)
    correct = (pred[mask] == graph.y[mask]).sum()
    acc = int(correct) / int(mask.sum())
 
    return acc
   
   
 mlp = MLP().to(device)
 optimizer_mlp = torch.optim.Adam(mlp.parameters(), lr=0.01, weight_decay=5e-4)
 criterion = nn.CrossEntropyLoss()
 mlp = train_node_classifier(mlp, graph, optimizer_mlp, criterion, n_epochs=150)
 
 test_acc = eval_node_classifier(mlp, graph, graph.test_mask)
 print(f'Test Acc: {test_acc:.3f}')

该模型的测试精度为73.2%。

GCN进行节点分类

接下来,我们将对GCN进行训练并将其性能与MLP进行比较。这里使用的是一个非常简单的模型,有两个图卷积层和它们之间的ReLU激活。此设置与论文原文相同(公式9)。

 from torch_geometric.nn import GCNConv
 import torch.nn.functional as F
 
 class GCN(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = GCNConv(dataset.num_node_features, 16)
        self.conv2 = GCNConv(16, dataset.num_classes)
 
    def forward(self, data):
        x, edge_index = data.x, data.edge_index
 
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        output = self.conv2(x, edge_index)
 
        return output
     
     
 gcn = GCN().to(device)
 optimizer_gcn = torch.optim.Adam(gcn.parameters(), lr=0.01, weight_decay=5e-4)
 criterion = nn.CrossEntropyLoss()
 gcn = train_node_classifier(gcn, graph, optimizer_gcn, criterion)
 
 test_acc = eval_node_classifier(gcn, graph, graph.test_mask)
 print(f'Test Acc: {test_acc:.3f}')

该模型的测试集准确度为88.0%。从MLP中获得了大约15%的精度提高。

链接预测

链接预测比节点分类更复杂,因为我们需要使用节点嵌入对边缘进行预测。预测步骤大致如下:

  • 编码器通过处理具有两个卷积层的图来创建节点嵌入。

  • 在原始图上随机添加负链接。这使得模型任务变为对原始边的正链接和新增边的负链接进行二元分类。

  • 解码器使用节点嵌入对所有边(包括负链接)进行链接预测(二元分类)。它从每条边上的一对节点计算节点嵌入的点积。然后聚合整个嵌入维度的值,并在每条边上创建一个表示边存在概率的值。

该模型结构来源于变分图自动编码器中原始的链接预测实现。代码改编自PyG repo中的代码示例,并基于Graph Auto-Encoders实现

from sklearn.metrics import roc_auc_score
 from torch_geometric.utils import negative_sampling
 
 
 class Net(torch.nn.Module):
    def __init__(self, in_channels, hidden_channels, out_channels):
        super().__init__()
        self.conv1 = GCNConv(in_channels, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, out_channels)
 
    def encode(self, x, edge_index):
        x = self.conv1(x, edge_index).relu()
        return self.conv2(x, edge_index)
 
    def decode(self, z, edge_label_index):
        return (z[edge_label_index[0]] * z[edge_label_index[1]]).sum(
            dim=-1
        ) # product of a pair of nodes on each edge
 
    def decode_all(self, z):
        prob_adj = z @ z.t()
        return (prob_adj > 0).nonzero(as_tuple=False).t()
     
 
 def train_link_predictor(
    model, train_data, val_data, optimizer, criterion, n_epochs=100
 ):
 
    for epoch in range(1, n_epochs + 1):
 
        model.train()
        optimizer.zero_grad()
        z = model.encode(train_data.x, train_data.edge_index)
 
        # sampling training negatives for every training epoch
        neg_edge_index = negative_sampling(
            edge_index=train_data.edge_index, num_nodes=train_data.num_nodes,
            num_neg_samples=train_data.edge_label_index.size(1), method='sparse')
 
        edge_label_index = torch.cat(
            [train_data.edge_label_index, neg_edge_index],
            dim=-1,
        )
        edge_label = torch.cat([
            train_data.edge_label,
            train_data.edge_label.new_zeros(neg_edge_index.size(1))
        ], dim=0)
 
        out = model.decode(z, edge_label_index).view(-1)
        loss = criterion(out, edge_label)
        loss.backward()
        optimizer.step()
 
        val_auc = eval_link_predictor(model, val_data)
 
        if epoch % 10 == 0:
            print(f"Epoch: {epoch:03d}, Train Loss: {loss:.3f}, Val AUC: {val_auc:.3f}")
 
    return model
 
 
 @torch.no_grad()
 def eval_link_predictor(model, data):
 
    model.eval()
    z = model.encode(data.x, data.edge_index)
    out = model.decode(z, data.edge_label_index).view(-1).sigmoid()
 
    return roc_auc_score(data.edge_label.cpu().numpy(), out.cpu().numpy())

对于这个链接预测任务,我们希望将链接/边随机分割为训练数据、有效数据和测试数据。我们可以使用PyG的RandomLinkSplit模块来做到这一点。

 import torch_geometric.transforms as T
 
 split = T.RandomLinkSplit(
    num_val=0.05,
    num_test=0.1,
    is_undirected=True,
    add_negative_train_samples=False,
    neg_sampling_ratio=1.0,
 )
 train_data, val_data, test_data = split(graph)

输出数据如下所示。

这个输出数据有以下3点需要注意:

1、在edge_index上执行分割,这样训练和验证分割不包括来自验证和测试分割的边(即只有来自训练分割的边),而测试分割不包括来自测试分割的边。这是因为编码器使用edge_index和x来创建节点嵌入,这种方式确保了在对验证/测试数据进行预测时,节点嵌入上没有目标泄漏。

2、向每个分割数据添加两个新属性(edge_label和edge_label_index)。它们是与每次分割相对应的边标签和边索引。Edge_label_index将用于解码器进行预测,edge_label将用于模型评估。

3、向val_data和test_data添加与正链接相同数量的负链接(neg_sampling_ratio=1.0)。它们被添加到edge_label和edge_label_index属性中,但没有添加到edge_index中,因为我们不希望在编码器(或节点嵌入创建)上使用负链接。我们没有向这里的训练集添加负链接(设置add_negative_train_samples=False),因为会在上面的train_link_predictor的训练循环中添加它们。训练过程中的这种随机化应该会使模型更健壮。

下图总结了如何对编码器和解码器执行边缘分割(每个阶段使用彩色边缘)。

我们现在可以用下面的代码来训练和评估模型。

 model = Net(dataset.num_features, 128, 64).to(device)
 optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)
 criterion = torch.nn.BCEWithLogitsLoss()
 model = train_link_predictor(model, train_data, val_data, optimizer, criterion)
 
 test_auc = eval_link_predictor(model, test_data)
 print(f"Test: {test_auc:.3f}")

该模型的测试AUC为92.5%。

异常检测

再次使用Cora数据集进行异常检测任务,但它与前面的数据集略有不同:我们需要合成注入异常值。数据集有两种不同类型的异常值:

  • 结构异常

  • 密集连接的节点,而不是稀疏连接的规则节点

  • 上下文的异常值

  • 属性与相邻节点显著不同的节点

对于这个异常检测任务,需要使用的是PyGOD库,它是建立在PyG之上的一个图异常值检测库。可以通过PyGOD模块加载已经进行了异常值注入的Cora数据集。

 from pygod.utils import load_data
 
 graph = load_data('inj_cora')

下面的代码显示了异常值类型分布。

 Counter(graph.y.tolist())
 
 #Counter({0: 2570, 1: 68, 2: 68, 3: 2})

0:正常,1:仅上下文异常,2:结构异常,3:上下文和结构都异常

如果你对这些异常值是如何注入的感兴趣,可以查看关于异常值生成器模块的PyGOD文档,该文档解释了操作细节。这里我们需要注意的是标签y将只用于模型评估,而不是用于训练标签,因为我们正在训练一个无监督的模型。

为了检测这些异常点,我们训练了DOMINANT(Deep Anomaly Detection on Attributed Networks)模型。它是一个具有图卷积层的自编码器网络,其重构误差将是节点异常评分。该模型遵循以下步骤进行预测。

  • 属性网络编码器使用三个图卷积层来处理输入图,从而创建节点嵌入。

  • 结构重构解码器利用学习到的节点嵌入重构原始图边(即邻接矩阵)。它从每个可能的节点对计算节点嵌入的点积,在每个节点对上创建一个表示边缘存在的概率评分。

  • 属性重构解码器使用获得的节点嵌入重构原始节点属性。它有一个图卷积层来预测属性值。

  • 最后一步将上述两种解码器的重构误差在每个节点上进行加权平均合并,合并后的误差即为最终的误差/损失。这些最终的误差也是节点的异常评分。

from pygod.models import DOMINANT
 from sklearn.metrics import roc_auc_score, average_precision_score
 
 def train_anomaly_detector(model, graph):
    return model.fit(graph)
 
 def eval_anomaly_detector(model, graph):
 
    outlier_scores = model.decision_function(graph)
    auc = roc_auc_score(graph.y.numpy(), outlier_scores)
    ap = average_precision_score(graph.y.numpy(), outlier_scores)
    print(f'AUC Score: {auc:.3f}')
    print(f'AP Score: {ap:.3f}')
 
 
 graph.y = graph.y.bool()
 model = DOMINANT()
 model = train_anomaly_detector(model, graph)
 eval_anomaly_detector(model, graph)

模型的AUC为84.1%,平均精度为20.8%。这种差异很可能是由于目标不平衡造成的。由于这是一个无监督的模型,我们不可能期望得到一个非常精确的模型,但可以在原始论文中看到,它仍然优于任何其他流行的异常检测算法。

引用

本文的源代码可以在colab找到:

https://colab.research.google.com/drive/1Ksca_p4XrZjeN0A6jT5aYN6ARvwFVSbY?usp=sharing

引用如下:

  • Benjamin Sanchez-Lengeling et al., A Gentle Introduction to Graph Neural Networks

  • Ameya Daigavane et al., Understanding Convolutions on Graphs

  • Francesco Casalegno, Graph Convolutional Networks — Deep Learning on Graphs

  • Thomas N. Kipf, Max Welling, Semi-Supervised Classification with Graph Convolutional Networks (2017), ICLR 2017

  • Thomas N. Kipf, Max Welling, Variational Graph Auto-Encoders (2016)

  • Kaize Ding et al., Deep Anomaly Detection on Attributed Networks (2019)

  • Kay Liu et al., Benchmarking Node Outlier Detection on Graphs (2022)

作者:Tomonori Masui

转载:Deephub Imba

使用PyG进行图神经网络的节点分类、链路预测和异常检测

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

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

相关文章

idea本地debug调试DATAX插件运行

datax官方github地址:GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。 接触datax是看重他的数据采集功能和可以扩展插件的功能,根据官方说明,DATAX是以python来调用插件的运行json配置,但对于二次开发插件的…

nacos权限区分

背景 nacos的默认是不进行分配权限的,那么这样就带来了一个问题,如果多项目共同使用一个nacos,可以带了一个情况是开发人员误操作,把其他项目的nacos配置文件更改或者删除。那么如何解决这个问题呢?就是把nacos进行分…

网络知识详解之:网络攻击与安全防护

网络知识详解之:网络攻击与安全防护 计算机网络相关知识体系详解 网络知识详解之:TCP连接原理详解网络知识详解之:HTTP协议基础网络知识详解之:HTTPS通信原理剖析(对称、非对称加密、数字签名、数字证书)…

Kotlin 1.8.0 现已发布,有那些新特性?

文章目录**如何安装 Kotlin 1.8.0****如果您遇到任何问题****更多文章和视频**结语Kotlin 1.8.0 版本现已发布,以下是其部分最大亮点: JVM 的新实验性功能:递归复制或删除目录内容提升了 kotlin-reflect 性能新的-Xdebug编译器选项&#xff…

PCB铺铜技巧如何铺铜经验总结

🏡《总目录》 目录1, 什么是铺铜2,铺铜的好处3,如何铺铜4,铺铜的经验原则5,铺铜的注意事项1, 什么是铺铜 铺铜是指在PCB电气层添加整块的铜皮;铺铜包括电源层铺铜,地层铺…

openofdm03:Frequency Offset Correction

载波频率偏移(Carrier Frequency Offset,CFO):发射机和接收机本振频率之差,会造成接收到的I/Q采样值的相位旋转。This symptom of this offset is a phase rotation of incoming I/Q samples (time domain). The CFO c…

基于语义分割实现人脸图像的皱纹检测定位与分割

前言 人脸皱纹主要区分有额纹、川字纹、眼下纹、法令纹、嘴角纹,眼角纹等,在美颜相机,智能医美等于应用领域里,需要对人脸皱纹进行检测、定位、分割,测量等。 传统图像算法的皱纹检测 1.传统算法的皱纹检测可参考《…

仿京东PC网页商品详情的放大镜效果(原理+代码)

实现效果 黑色只不过是转gif出问题而已 准备工作 1. 访问该网址,理解我们要弄的放大镜效果,鼠标经过商品图片,显示一个黄色的放大选区,右边显示该选区的大图。 2. 获取商品图片和商品大图 【摩托罗拉moto X30 Pro】摩托罗拉mot…

protocol-buffers 基础(一)

protocol-buffers 官网 Github 一、概述 协议缓冲区(protocol-buffers)是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据。 协议缓冲区提供了一种与语言无关、与平台无关、可扩展的机制,用于以向前兼容和向后兼…

【深度学习笔记】全卷积网络FCN及 ROI Pooling 和 ROI Align 的区别

问题 FCN 是语义分割的开山之作,没理由不了解。最近发现学习最快的方式还是看别人的教学视频,很多时候博客看一大推,没几个能讲地明白的,确实啊,视频都得用四五十分钟的东西,一个博客得写得多详细才能覆盖到方方面面呀,以后还是先借助视频来学习,然后再根据源码进一步…

leetcode 1669. 合并两个链表【python3一次遍历方法详细过程描述】

题目 给你两个链表list1和list2,它们包含的元素分别为n个和m个。请你将list1中下标从a到b的全部节点都删除,并将list2接在被删除节点的位置。 下图中蓝色边和节点展示了操作后的结果: 请你返回结果链表的头指针。 示例1: 输入&…

网络原理(TCP/IP五层协议)(二)

目录TCP 中的一些主要机制1.确认应答机制(安全机制)2.超时重传机制(安全机制)3.连接管理机制(安全机制)如何建立连接:三次握手如何断开连接:四次挥手TCP 中的一些主要机制 TCP对数据传输提供的管控机制,主要体现在两个方面:安全和…

C++右值引用相关

能够取地址值、有名字的就是左值,不能取地址值、没名字的就是右值。 移动语义 将内存的所有权从一个对象转换到另一个对象,高效的移动来替换效率低下的赋值。 完美转发 定义一个函数模板,该函数模板可以接受任意类型参数,然后将参…

即时通讯开发之TCP/IP中的IP协议、ARP协议

IP 协议是 TCP/IP 协议的核心,所有的 TCP,UDP,IMCP,IGCP 的数据都以 IP 数据格式传输。要注意的是,IP 不是可靠的协议,这是 说,IP 协议没有提供一种数据未传达以后的处理机制--这被认为是上层协议--TCP 或 UDP 要做的事情。所以这也就出现了 TCP 是一个可靠的协议,而 UDP 就没有…

前端开发:JS中关于八皇后算法的使用

前言 在前端开发过程中,关于算法的使用也是非常常见的操作,尤其是处理一些复杂的业务场景,还有就是前端获取到后端返回的复杂结构的数据,所以说前端开发中处处都有算法使用的场景。开发者从接触编程开发开始,就与算法脱…

Java switch case语句详解

if…else 语句可以用来描述一个“二岔路口”,我们只能选择其中一条路来继续走,然而生活中经常会碰到“多岔路口”的情况。switch 语句提供了 if 语句的一个变通形式,可以从多个语句块中选择其中的一个执行。switch 语句格式switch 语句是 Jav…

44物体检测算法:R-CNN,SSD,YOLO【动手学深度学习v2】

44物体检测算法:R-CNN,SSD,YOLO【动手学深度学习v2】 深度学习学习笔记 学习视频:https://www.bilibili.com/video/BV1if4y147hS/?spm_id_from333.337.search-card.all.click&vd_source75dce036dc8244310435eaf03de4e330 目标…

使用Springboot 2.7+Websocket+js实现服务端消息实时推送

文章目录诉求相关技术相关步骤项目创建编写相关配置消息逻辑处理使用JavaScript实现Websocket的前端功能功能测试诉求 模拟服务端主动推送消息给客户端,同时展示客户端发送给客户端的消息以及服务端推送给客户的消息。 相关技术 Springboot(2.7.0)Websocketjavascr…

Elasticsearch7.8.0版本入门——JavaAPI操作(文档操作)

目录一、pom文件依赖二、创建实体对象类三、文档操作代码示例3.1、创建文档代码示例3.2、修改文档代码示例3.3、查询文档代码示例3.4、删除文档代码示例一、pom文件依赖 引入相关依赖 <!-- elasticsearch 依赖 --> <dependency><groupId>org.elasticsearch&l…

【Pytorch】自定义autograd函数,使用graphviz画出计算图

使用pytorch.autograd.Function构建一个自动求导层1. 手工设计一个线性运算层2. 使用pytorch.autograd.Function编码实现3. graphviz进行可视化1. 手工设计一个线性运算层 设输入为x\bold{x}x&#xff0c;参数为w\bold{w}w和b\bold{b}b&#xff0c;运算如下&#xff1a; yw⊙x…