GraphSAGE (SAmple and aggreGatE)知识总结

news2024/9/25 9:35:53

1.前置知识

inductive和transductive
模型训练:
Transductive learning在训练过程中已经用到测试集数据(不带标签)中的信息,而Inductive learning仅仅只用到训练集中数据的信息。
模型预测:
Transductive learning只能预测在其训练过程中所用到的样本(Specific --> Specific),而Inductive learning,只要样本特征属于同样的欧拉空间,即可进行预测(Specific --> Gerneral)
模型复用性:
当有新样本时,Transductive learning需要重新进行训练;Inductive Leaning则不需要。
模型计算量:
显而易见,Transductive Leaning是需要更大的计算量的,即使其有时候确实能够取得相比Inductive learning更好的效果。

2.论文公式

  • 生成节点嵌入的公式:
更新过程:
(1)为了更新红色节点,首先在第一层(k=1)我们会将蓝色节点的信息聚合到红色节点上,将绿色节点的信息聚合到蓝色节点上。 所有的节点都有了新的包含邻居节点的embedding。
(2)在第二层(k=2)红色节点的embedding被再次更新,不过这次用的是更新后的蓝色节点embedding,这样就保证了红色节点更新后的embedding包括蓝色和绿色节点的信息。这样,每个节点又有了新的embedding向量,且包含更多的信息。
简而言之就是聚合K层采样过的邻居的特征再拼接上自己的特征,用来更新自己新的特征。
  • 聚合函数:
1.mean:
2.pool:
  • 小批量向前传播公式:
和上面的算法1一样,只不过是1-7行添加了一个采样的过程,而 不是选取所有的邻居节点了。

3.论文导图

4.核心代码

从中心节点开始,逐层向外寻找邻居节点然后采样,然后继续
##导入训练的batch节点
        lower_layer_nodes = list(nodes_batch)
        ##聚合节点的层
        nodes_batch_layers = [(lower_layer_nodes,)]  //存储每一层的节点

        # 每一层的graph sage
        for i in range(self.num_layers):
        ##获得邻接节点
            lower_samp_neighs, lower_layer_nodes_dict, lower_layer_nodes = self._get_unique_neighs_list(lower_layer_nodes)
        ##形成layer2(最外层节点),layer1(中间层节点),layer center(中心节点),即
            nodes_batch_layers.insert(0, (lower_layer_nodes, lower_samp_neighs, lower_layer_nodes_dict))
##获得节点的邻居
    def _get_unique_neighs_list(self, nodes, num_sample=10):
        _set = set
        ##获取每个节点的邻居节点
        to_neighs = [self.adj_lists[int(node)] for node in nodes]

        ##进行采样
        if not num_sample is None:
            _sample = random.sample
        ##如果邻居节点个数大于等于十个,则随机取十个,如果邻居节点个数小于十个则全取
            samp_neighs = [_set(_sample(to_neigh, num_sample)) if len(to_neigh) >= num_sample else to_neigh for to_neigh in to_neighs]
        else:
            samp_neighs = to_neighs
        ##将采样的邻居节点和本身的中心节点结合得到节点所属邻居的集合
        samp_neighs = [samp_neigh | set([nodes[i]]) for i, samp_neigh in enumerate(samp_neighs)]

        ##进行去重操作
        _unique_nodes_list = list(set.union(*samp_neighs))

        ##进行重新排列
        i = list(range(len(_unique_nodes_list)))

        ##对字典进行重新编号
        unique_nodes = dict(list(zip(_unique_nodes_list, i)))

        ##返回所有节点的邻居,返回所有节点的编号,返回所有节点的列表
        return samp_neighs, unique_nodes, _unique_nodes_list
从次外层向里聚集
##将节点特征赋予到变量pre_hidden_embs
        pre_hidden_embs = self.raw_features
        ##nodes_batch_layers=[layer2,layer1,layer_center]
        for index in range(1, self.num_layers+1):
        ##以layer1节点作为中心节点
            nb = nodes_batch_layers[index][0]
        ##取中心节点对应的上层(layer2)的邻居节点
            pre_neighs = nodes_batch_layers[index-1]

        ##聚合函数进行聚合得到中心节点特征
            aggregate_feats = self.aggregate(nb, pre_hidden_embs, pre_neighs)
            sage_layer = getattr(self, 'sage_layer'+str(index))

        ##如果层数大于1
            if index > 1:
                nb = self._nodes_map(nb, pre_hidden_embs, pre_neighs)
        ##利用中心节点表征和聚合后的中心节点特征进行拼接然后连一个可学习的权重参数
            cur_hidden_embs = sage_layer(self_feats=pre_hidden_embs[nb],
                                        aggregate_feats=aggregate_feats)
            pre_hidden_embs = cur_hidden_embs
        return pre_hidden_embs  //graphsage层最后返回一个聚合后的节点嵌入,作为分类器的输入。
def aggregate(self, nodes, pre_hidden_embs, pre_neighs, num_sample=10):
        ##取出最外层邻居节点(layer2)
        unique_nodes_list, samp_neighs, unique_nodes = pre_neighs
        assert len(nodes) == len(samp_neighs)

        ##判断邻居节点是否包含了中心节点
        indicator = [(nodes[i] in samp_neighs[i]) for i in range(len(samp_neighs))]
        assert (False not in indicator)

        ##将邻居节点包含中心节点的部分去掉
        if not self.gcn:
            samp_neighs = [(samp_neighs[i]-set([nodes[i]])) for i in range(len(samp_neighs))

        ##判断如果涉及到所有中心节点,保留原先矩阵,如果不涉及所有中心,保留部分矩阵
        if len(pre_hidden_embs) == len(unique_nodes):
            embed_matrix = pre_hidden_embs
        else:
            embed_matrix = pre_hidden_embs[torch.LongTensor(unique_nodes_list)]

        ##以邻接节点为行,以中心节点为列构建邻接矩阵
        mask = torch.zeros(len(samp_neighs), len(unique_nodes))
        column_indices = [unique_nodes[n] for samp_neigh in samp_neighs for n in samp_neigh]
        row_indices = [i for i in range(len(samp_neighs)) for j in range(len(samp_neighs[i]))]

        ##将其有链接的地方记为1
        mask[row_indices, column_indices] = 1

        ##选择平均的方式进行聚合
        if self.agg_func == 'MEAN':
        ##按行求和得到每个中心节点的连接的邻居节点的个数
            num_neigh = mask.sum(1, keepdim=True)
        ##按行进行归一化操作
            mask = mask.div(num_neigh).to(embed_matrix.device)
        ##矩阵相乘,相当于聚合周围临界信息并求和
            aggregate_feats = mask.mm(embed_matrix)


        elif self.agg_func == 'MAX':
            # print(mask)
            indexs = [x.nonzero() for x in mask==1]
            aggregate_feats = []
            # self.dc.logger.info('5')
            for feat in [embed_matrix[x.squeeze()] for x in indexs]:
                if len(feat.size()) == 1:
                    aggregate_feats.append(feat.view(1, -1))
                else:
                    aggregate_feats.append(torch.max(feat,0)[0].view(1, -1))
            aggregate_feats = torch.cat(aggregate_feats, 0)

        # self.dc.logger.info('6')
       
        return aggregate_feats
  • 分类器把 经过graphsage层采样聚集后的特征作为输入,一共有labels类,得到一个概率。然后经过有监督/无监督loss函数训练。
##定义分类器
class Classification(nn.Module):
    def __init__(self, emb_size, num_classes):
        super(Classification, self).__init__()
        #self.weight = nn.Parameter(torch.FloatTensor(emb_size, num_classes))
        self.layer = nn.Sequential(
                                nn.Linear(emb_size, num_classes)      
                                #nn.ReLU())
        self.init_params()
    def init_params(self):
        for param in self.parameters():
            if len(param.size()) == 2:
                nn.init.xavier_uniform_(param)

    def forward(self, embeds):
        logists = torch.log_softmax(self.layer(embeds), 1)
        return logists
## 分类器训练
def train_classification(dataCenter, graphSage, classification, ds, device, max_vali_f1, name, epochs=800):
    print('Training Classification ...')
    c_optimizer = torch.optim.SGD(classification.parameters(), lr=0.5)
    # train classification, detached from the current graph
    #classification.init_params()
    b_sz = 50
    train_nodes = getattr(dataCenter, ds+'_train')
    labels = getattr(dataCenter, ds+'_labels')
    features = get_gnn_embeddings(graphSage, dataCenter, ds)  #得到之前采样聚集后的节点特征
    for epoch in range(epochs):
        train_nodes = shuffle(train_nodes)
        batches = math.ceil(len(train_nodes) / b_sz)
        visited_nodes = set()
        for index in range(batches):
            nodes_batch = train_nodes[index*b_sz:(index+1)*b_sz]
            visited_nodes |= set(nodes_batch)
            labels_batch = labels[nodes_batch]
            embs_batch = features[nodes_batch]

            logists = classification(embs_batch)
            loss = -torch.sum(logists[range(logists.size(0)), labels_batch], 0)
            loss /= len(nodes_batch)
            loss.backward()
            nn.utils.clip_grad_norm_(classification.parameters(), 5)
            c_optimizer.step()
            c_optimizer.zero_grad()

        max_vali_f1 = evaluate(dataCenter, ds, graphSage, classification, device, max_vali_f1, name, epoch)  ##evaluate函数得到。。
    return classification, max_vali_f1
  • 无监督loss
class UnsupervisedLoss(object):
    """docstring for UnsupervisedLoss"""
    def __init__(self, adj_lists, train_nodes, device):
        super(UnsupervisedLoss, self).__init__()
        self.Q = 10
        self.N_WALKS = 6
        self.WALK_LEN = 1
        self.N_WALK_LEN = 5
        self.MARGIN = 3
        self.adj_lists = adj_lists
        self.train_nodes = train_nodes
        self.device = device

        self.target_nodes = None
        self.positive_pairs = []
        self.negtive_pairs = []
        self.node_positive_pairs = {}
        self.node_negtive_pairs = {}
        self.unique_nodes_batch = []

    def get_loss_sage(self, embeddings, nodes):
    ## 确保输入的嵌入(embeddings)和唯一节点批次(unique_nodes_batch)的长度相同
        assert len(embeddings) == len(self.unique_nodes_batch)
        assert False not in [nodes[i]==self.unique_nodes_batch[i] for i in range(len(nodes))]
    ##建立节点和嵌入列表的索引的映射关系
        node2index = {n:i for i,n in enumerate(self.unique_nodes_batch)}
    
    ##对每个节点,计算它的正负样本对的分数
        nodes_score = []
        assert len(self.node_positive_pairs) == len(self.node_negtive_pairs)
        for node in self.node_positive_pairs:
            pps = self.node_positive_pairs[node]
            nps = self.node_negtive_pairs[node]
            if len(pps) == 0 or len(nps) == 0:
                continue
        ## 负样本对的损失,使用余弦相似度和对数sigmoid函数
            # Q * Exception(negative score)
            indexs = [list(x) for x in zip(*nps)]
            node_indexs = [node2index[x] for x in indexs[0]]
            neighb_indexs = [node2index[x] for x in indexs[1]]
            neg_score = F.cosine_similarity(embeddings[node_indexs], embeddings[neighb_indexs])
            neg_score = self.Q*torch.mean(torch.log(torch.sigmoid(-neg_score)), 0)
            #print(neg_score)
        
        ## 正样本对的损失,同样使用余弦相似度和对数sigmoid函数。
            # multiple positive score
            indexs = [list(x) for x in zip(*pps)]
            node_indexs = [node2index[x] for x in indexs[0]]
            neighb_indexs = [node2index[x] for x in indexs[1]]
            pos_score = F.cosine_similarity(embeddings[node_indexs], embeddings[neighb_indexs])
            pos_score = torch.log(torch.sigmoid(pos_score))
            #print(pos_score)

            nodes_score.append(torch.mean(- pos_score - neg_score).view(1,-1))
                
        loss = torch.mean(torch.cat(nodes_score, 0))
        return loss
    
##边缘损失(margin loss)
    ##这个方法与get_loss_sage类似,但在计算损失时使用了边缘损失
    def get_loss_margin(self, embeddings, nodes):
        assert len(embeddings) == len(self.unique_nodes_batch)
        assert False not in [nodes[i]==self.unique_nodes_batch[i] for i in range(len(nodes))]
        node2index = {n:i for i,n in enumerate(self.unique_nodes_batch)}

        nodes_score = []
        assert len(self.node_positive_pairs) == len(self.node_negtive_pairs)
        for node in self.node_positive_pairs:
            pps = self.node_positive_pairs[node]
            nps = self.node_negtive_pairs[node]
            if len(pps) == 0 or len(nps) == 0:
                continue
        ##正样本对的最小得分
            indexs = [list(x) for x in zip(*pps)]
            node_indexs = [node2index[x] for x in indexs[0]]
            neighb_indexs = [node2index[x] for x in indexs[1]]
            pos_score = F.cosine_similarity(embeddings[node_indexs], embeddings[neighb_indexs])
            pos_score, _ = torch.min(torch.log(torch.sigmoid(pos_score)), 0)
        ## 负样本对的最大得分
            indexs = [list(x) for x in zip(*nps)]
            node_indexs = [node2index[x] for x in indexs[0]]
            neighb_indexs = [node2index[x] for x in indexs[1]]
            neg_score = F.cosine_similarity(embeddings[node_indexs], embeddings[neighb_indexs])
            neg_score, _ = torch.max(torch.log(torch.sigmoid(neg_score)), 0)
            
        ##计算它们之间的差异,加上一个边缘值
            nodes_score.append(torch.max(torch.tensor(0.0).to(self.device), neg_score-pos_score+self.MARGIN).view(1,-1))
            # nodes_score.append((-pos_score - neg_score).view(1,-1))

        loss = torch.mean(torch.cat(nodes_score, 0),0)

        # loss = -torch.log(torch.sigmoid(pos_score))-4*torch.log(torch.sigmoid(-neg_score))
        return loss

    def extend_nodes(self, nodes, num_neg=6):
    ##清空当前的正负样本对和它们的映射
        self.positive_pairs = []
        self.node_positive_pairs = {}
        self.negtive_pairs = []
        self.node_negtive_pairs = {}
    ##设置目标节点为传入的节点
        self.target_nodes = nodes
    ##方法来生成正负样本对。
        self.get_positive_nodes(nodes)
        # print(self.positive_pairs)
        self.get_negtive_nodes(nodes, num_neg)
        # print(self.negtive_pairs)
    ##所有正负样本对中出现的唯一节点
        self.unique_nodes_batch = list(set([i for x in self.positive_pairs for i in x]) | set([i for x in self.negtive_pairs for i in x]))
        assert set(self.target_nodes) < set(self.unique_nodes_batch)
        return self.unique_nodes_batch
    ##正样本生成——_run_random_walks方法生成正样本对
    def get_positive_nodes(self, nodes):
        return self._run_random_walks(nodes)
    ## 每个节点生成指定数量的负样本。这些负样本是从不是节点邻居的节点中随机选取的
    def get_negtive_nodes(self, nodes, num_neg):
        for node in nodes:
            neighbors = set([node])
            frontier = set([node])
            for i in range(self.N_WALK_LEN):
                current = set()
                for outer in frontier:
                    current |= self.adj_lists[int(outer)]
                frontier = current - neighbors
                neighbors |= current
            far_nodes = set(self.train_nodes) - neighbors
            neg_samples = random.sample(far_nodes, num_neg) if num_neg < len(far_nodes) else far_nodes
            self.negtive_pairs.extend([(node, neg_node) for neg_node in neg_samples])
            self.node_negtive_pairs[node] = [(node, neg_node) for neg_node in neg_samples]
        return self.negtive_pairs
    ##随机游走函数
    def _run_random_walks(self, nodes):
        for node in nodes:
            if len(self.adj_lists[int(node)]) == 0:
                continue
            cur_pairs = []
        ##对于传入的每个节点,进行指定次数的随机游走。
            for i in range(self.N_WALKS):
                curr_node = node
                ##对于每次随机游走进行指定长度并且每次随机游走都选择邻居节点作为下一个节点
                for j in range(self.WALK_LEN):
                    neighs = self.adj_lists[int(curr_node)]
                    next_node = random.choice(list(neighs))
                    # self co-occurrences are useless
                    ##如果选中的邻居节点不是原始节点且在训练节点集中,将其作为正样本对添加到列表中
                    if next_node != node and next_node in self.train_nodes:
                        self.positive_pairs.append((node,next_node))
                        cur_pairs.append((node,next_node))
                    curr_node = next_node

            self.node_positive_pairs[node] = cur_pairs
        return self.positive_pairs
正负样本定义
什么是正负样本?事实上,在目标检测领域正负样本的定义策略是不断变化的。正负样本是在训练过程中计算损失用的,而在预测过程和验证过程是没有这个概念的。许多人在看相关目标检测的论文时,常常误以为正样本就是我们手动标注的GT,其实不然。
首先, 正样本是待检测的目标,比如检测人脸时,人脸是正样本,非人脸则是负样本,比如旁边的树呀花呀之类的其他东西;其次,正负样本都是针对于算法经过处理生成的框(如:计算宽高比、交并比、样本扩充等)而言,而非原始的GT数据。
随机游走算法的基本思想是:
从一个或一系列顶点开始遍历一张图。在任意一个顶点,遍历者将以概率1-a游走到这个顶点的邻居顶点,以概率a随机跳跃到图中的任何一个顶点,称a为跳转发生概率,每次游走后得出一个概率分布,该概率分布刻画了图中每一个顶点被访问到的概率。用这个概率分布作为下一次游走的输入并反复迭代这一过程。当满足一定前提条件时,这个概率分布会趋于收敛。收敛后,即可以得到一个平稳的概率分布。

5.整体总结一下

GraphSAGE是一种能利用顶点的属性信息高效 产生未知顶点embedding的一种归纳式(inductive)学习的框架。
主要步骤:
(1)对邻居随机采样
(2)使用聚合函数将采样的邻居节点的Embedding进行聚合,用于更新节点的embedding。
(3)根据更新后的embedding预测节点的标签。

6.核心思想 / 贡献

只要有边就能聚合信息,改进gcn,不再需要邻接矩阵
与gcn不同的消息传递方法,不依赖邻接矩阵,而是 边索引
mini batch的使用,改进gcn的全图训练。

7.问:如何生成未知节点(测试集)embedding的?

答:假如在这个图里面,存在新生成的节点,例如社交网络或者蛋白质结构rna结构之类的情况。用之前的图训练好的模型, 根据之前训练好的参数和新节点的特征,再用上面的流程,生成新节点的embedding。

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

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

相关文章

6.前端怎么做一个验证码和JWT,使用mockjs模拟后端

流程图 创建一个发起请求 创建一个方法 getCaptchaImg() {this.$axios.get(/captcha).then(res > {console.log(res);this.loginForm.token res.data.data.tokenthis.captchaImg res.data.data.captchaImgconsole.log(this.captchaImg)})}, captchaImg: "", 创…

【数据结构】排序基本概念、插入排序、希尔排序(详解)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…

java学习--泛型

前言 当我们将dog类放入集合List中想要遍历通过一下手段可实现遍历名字和年龄&#xff0c;但是当我们要加入一个新的Cat类时&#xff0c;他并不会报错&#xff0c;只有编译了才会报错&#xff0c;因为在这一步的时候注定了只能是Dog类&#xff0c;但这是非常不方便的 此时我们…

哦吼,新模型?文生图领域的新模型FLUX.1(附模型下载网盘地址和详细使用方法)

&#x1f3a1;背景 Black Forest Labs 是由 Stable Diffusion 原班人马成立的公司&#xff0c;致力于研发优质的多模态模型并开源。该公司由多位前 Stability AI 研究员组成&#xff0c;包括 Robin Rombach 在内的团队成员&#xff0c;他们在图像和视频生成领域有着杰出的贡献…

取消订单业务

文章目录 概要整体架构流程技术细节小结 概要 取消订单是电子商务、外卖平台、在线零售等多个行业中常见的业务需求之一。这项功能允许消费者或商家取消已下的订单&#xff0c;通常是因为各种原因&#xff08;如商品缺货、配送问题、支付问题等&#xff09;。 需求分析以及接…

【课程总结】day19(中):Transformer架构及注意力机制了解

前言 本章内容&#xff0c;我们将从注意力的基础概念入手&#xff0c;结合Transformer架构&#xff0c;由宏观理解其运行流程&#xff0c;然后逐步深入了解多头注意力、多头掩码注意力、融合注意力等概念及作用。 注意力机制&#xff08;Attension&#xff09; 背景 深度学…

如何在立创EDA的PCB电路板导入logo图案

1、首先制作好logo图案&#xff0c;一般为公司logo图标&#xff0c;如下图 2、打开立创EDA的PCB文件&#xff0c;如下图 3、将PCB的图层切换到丝印层&#xff1a; 4、然后选择EDA菜单栏的放置---图片&#xff1a; 5、进入后点击选择图片&#xff0c;将logo图片导入&#xff0c;…

人生低谷来撸C#--022 winfrom 和WPF

1、简单介绍 标题其实是写错了&#xff0c;是winform,不是winfrom&#xff0c;如果再准确点&#xff0c;应该是 WinForms&#xff08;复数形式&#xff09;&#xff0c;它代表的是 Windows Forms 技术&#xff0c;用于在 .NET Framework 中创建桌面应用程序的用户界面。在 Vis…

数据结构——八大排序

一.排序的概念和其应用 1.1排序的概念 排序&#xff1a;排列或排序是将一组数据按照一定的规则或顺序重新组织的过程&#xff0c;数据既可以被组织成递增顺序&#xff08;升序&#xff09;&#xff0c;或者递减顺序&#xff08;降序&#xff09;。稳定性&#xff1a;假定在待…

Prometheus监控的搭建(ansible安装——超详细)

目录 1.各组件功能介绍 2.安装批量部署工具ansbile 3.执行服务器 4.各服务器间做免密 5.下载安装包 5.1Prometheus的下载的下载地址 5.2exporter的下载地址 5.3grafana的下载地址 6.编辑ansible需要的配置文件 7.编写ansible文件 8.验证执行结果 今天和大家分享一下…

网站在线查询工具箱源码分享

终极网络工具系统”(SAAS)&#xff0c;是一款功能强大的PHP脚本在线查询工具。本版集合了超过470种快速且易用的Web工具&#xff0c;为日常任务处理和开发人员提供了极大的便利。作为一款综合性的网络工具系统&#xff0c;66toolkit不仅满足了用户的基本网络需求&#xff0c;更…

Java面试题 -- 为什么重写equals就一定要重写hashcode方法

在回答这个问题之前我们先要了解equals与hascode方法的本质是做什么的 1. equals方法 public boolean equals(Object obj) {return (this obj);}我们可以看到equals在不重写的情况下是使用判断地址值是否相同 所以默认的 equals 的逻辑就是判断的双方是否引用了一个对象&am…

【EI会议征稿】第四届高性能计算与通信工程国际学术会议(HPCCE 2024)

出版出版 【SPIE出版 | 往届会后3个月内完成EI检索】 第四届高性能计算与通信工程国际学术会议(HPCCE 2024) 2024 4th International Conference on High Performance Computing and Communication 第四届高性能计算与通信工程国际学术会议&#xff08;HPCCE 2024&#xf…

使用Chainlit接入通义千问快速实现一个自然语言转sql语言的智能体

文本到 SQL 让我们构建一个简单的应用程序&#xff0c;帮助用户使用自然语言创建 SQL 查询。 最终结果预览 ​ 先决条件 此示例有额外的依赖项。你可以使用以下命令安装它们&#xff1a; pip install chainlit openai​ 导入 应用程序 from openai import AsyncOpenAI…

扩展------零拷贝技术(Mmap,SendFile)

什么是零拷贝 零拷贝&#xff08;Zero-Copy&#xff09;是一种计算机操作技术&#xff0c;旨在减少数据在内存之间的拷贝次数&#xff0c;以提高数据传输的效率和性能。 传统的IO模式&#xff1a; 模拟网络传输数据运行过程&#xff1a; 用户态read()发起系统调用&#xff0c…

Flink中上游DataStream到下游DataStream的内置分区策略及自定义分区策略

目录 全局分区器GlobalPartitioner 广播分区器BroadcastPartitioner 哈希分区器BinaryHashPartitioner 轮询分区器RebalancePartitioner 重缩放分区器RescalePartitioner 随机分区器ShufflePartitioner 转发分区器ForwardPartitioner 键组分区器KeyGroupStreamPartitio…

力扣SQL50 第二高的薪水 ifnull() 分页

Problem: 176. 第二高的薪水 &#x1f468;‍&#x1f3eb; 参考题解 Code select ifNull((select distinct salaryfrom employeeorder by salary desclimit 1,1),null) as SecondHighestSalary

【Python数据结构与算法】分治----汉诺塔问题

题目&#xff1a;汉诺塔问题 描述 古代有一个梵塔&#xff0c;塔内有三个座A、B、C&#xff0c;A座上有n个盘子&#xff0c;盘子大小不等&#xff0c;大的在下&#xff0c;小的在上。三个座都可以用来放盘子。有一个和尚想把这n个盘子从A座移到C座&#xff0c;但每次只能允许移…

AWS SES 认证策略设置全攻略:轻松掌握简单步骤!

最近&#xff0c;我有机会设置 Amazon Simple Email Service&#xff08;以下简称&#xff1a;SES&#xff09;的认证策略&#xff0c;所以这次写下来作为备忘。 前言 Amazon Simple Email Service&#xff08;SES&#xff09;是一项通过 API 端点或 SMTP 接口进行邮件发送的服…

MySQL:VIEW视图

概述 MySQL 视图&#xff08;View&#xff09;是一种虚拟存在的表&#xff0c;同真实表一样&#xff0c;视图也由列和行构成&#xff0c;但视图并不实际存在于数据库中。行和列的数据来自于定义视图的查询中所使用的表&#xff0c;并且是在使用视图时动态生成的。 数据库中只…