【论文复现】Graph Attention Networks图注意力神经网络

图注意力神经网络

  • 前言
  • 一、论文解读
    • 1.1 模型架构
    • 1.2 数学推导
  • 二、代码复现
    • 2.1 数据准备
      • 2.1.1 数据转化
      • 2.1.2 创建数据集
    • 2.2 模型构建
      • 2.2.1 参数设置
      • 2.2.2 模型代码
      • 2.2.3 pytorch官方GAT源码实现
    • 2.3 模型训练
  • 三、结果展示
    • 3.1 复现结果
    • 3.2 论文结果
  • 四、代码细节
  • 代码链接

前言

这篇论文提出的核心方法就是在计算一个节点的输出的时候考虑与其相邻的节点对当前节点的影响,同时也将节点对自己的影响考虑在内,为了方便后续解释中将Graph Attention Networks简化成GAT来表述,阅读原版论文有困难可以看一下Aleksa大神录制的讲解视频辅助理解,我在看完视频以后对第多头注意力下的结果输出部分茅塞顿开,同时对自己看论文的时候的一些理解也更加深刻了;另外还有B站上的GAT代码讲解视频的讲解很详细,相比于pytorch给出的源码更加容易理解,本次是笔者的论文复现入门,代码部分采用的是Vscode中的Jupyter代码块的方式,抛弃了Pycharm的繁重以及自建库的调用,直接用逐行运行的方式进行,小白友好。

一、论文解读

1.1 模型架构

在这里插入图片描述

  • 左边的单层网络展示了如何根据输入数据(图类型的数据,有节点和边的信息),计算出节点之间的注意力系数, α i j \alpha_{ij} αij( j j j节点对 i i i节点的重要程度),这里计算的时候我们只关心每个节点的入边不关心出边。公式如下:

e i j = a ( W h ⃗ i , W h ⃗ j ) e_{ij}=a(\mathbf{W}\vec{h}_i,\mathbf{W}\vec{h}_j) eij=a(Wh i,Wh j)

  • 首先通过这一步计算节点与其相邻节点的注意力系数
  • 这里的 a a a的作用是将拼接以后的高维特征映射到一个实数上

这一步计算出来的 e i j e_{ij} eij是相邻节点之间的注意力系数,这一点在代码层面有细节就是先计算所有节点之间的注意力系数然后再挑出来相邻的节点的注意力系数。

α i j = exp ⁡ ( LeakyReLU ( a ⃗ T [ W h ⃗ i ∥ W h ⃗ j ] ) ) ∑ k ∈ N i exp ⁡ ( LeakyReLU ( a ⃗ T [ W h ⃗ i ∥ W h ⃗ k ] ) ) \alpha_{ij}=\frac{\exp\left(\text{LeakyReLU}\left(\vec{\mathbf{a}}^T[\mathbf{W}\vec{h}_i\|\mathbf{W}\vec{h}_j]\right)\right)}{\sum_{k\in\mathcal{N}_i}\exp\left(\text{LeakyReLU}\left(\vec{\mathbf{a}}^T[\mathbf{W}\vec{h}_i\|\mathbf{W}\vec{h}_k]\right)\right)} αij=kNiexp(LeakyReLU(a T[Wh iWh k]))exp(LeakyReLU(a T[Wh iWh j]))

  • α \alpha α是一个形状为2F’*1的权重向量,作用如上
  • W是一个形状为F’*F的权重矩阵
  • h ⃗ i \vec{h}_i h i是当前节点的特征列向量 h ⃗ j \vec{h}_j h j是与当前处理的节点相邻的节点的特征列向量,形状都是F*1
  • ||表示将两个矩阵拼接起来
  • 最后计算以后用sigmoid函数进行归一化,统一所有自注意力系数的评价标准

这个公式实现的是将注意力系数从一个值转换成一个能够用来评价的概率值 α i j \alpha_{ij} αij,同时这个值也能用于后续更新模型参数。
最后的单层结构的输出求解公式如下两种情况:

  1. 第一种对应的是在最后一层输出之前的隐藏层还在计算注意力的时候需要将多头注意力拼接起来形成更加高维抽象的特征向量。

h ⃗ i ′ = ∏ k = 1 K σ ( ∑ j ∈ N i α i j k W k h ⃗ j ) \vec{h}_i'=\prod\limits_{k=1}^K\sigma\left(\sum\limits_{j\in\mathcal{N}_i}\alpha_{ij}^k\mathbf{W}^k\vec{h}_j\right) h i=k=1Kσ(jNiαijkWkh j)

  1. 第二情况是当进行到最后一层输出的时候直接求平均值然后输出每一种类别对应的概率值。

h ⃗ i ′ = σ ( 1 K ∑ k = 1 K ∑ j ∈ N i α i j k W k h ⃗ j ) \vec{h}_i^{\prime}=\sigma\left(\frac1K\sum_{k=1}^K\sum_{j\in\mathcal{N}_i}\alpha_{ij}^k\mathbf{W}^k\vec{h}_j\right) h i=σ(K1k=1KjNiαijkWkh j)

  • 右边的网络架构展示了GAT的工作原理,在模型的隐藏层,当前节点会计算对其他节点的注意力系数,同时这里引入了多头注意力机制的概念,也就是在计算当前层的输出的时候,当前层会“看一看”当前层的其他节点(论文中说的是与之相邻的第一跳节点,也就是第一层)来最终决定当前的输出(通过在训练的时候改变权重矩阵W来实现) h ⃗ i ′ \vec{h}_i' h i,具体的实现形式参考Transformer架构,或者可以参考3B1B最近的注意力机制视频。比较形象地展示多头注意力如何工作的有一张图:
    在这里插入图片描述
    这里的黄色箭头就很形象地展示了多头注意力。

1.2 数学推导

在这里插入图片描述
我简单写了一个手写版本的推导,可以看到最后成功输出了对应每个节点所注意的特征。

二、代码复现

2.1 数据准备

首先是导入数据:

# cora数据集导入
cora_path = '../dataset/cora/cora/cora.content'
cora = pd.read_csv(cora_path, sep='\t', header=None)
cora_cites = pd.read_csv('../dataset/cora/cora/cora.cites', sep='\t', header=None)
print('cora_cites:\n',cora_cites.head())
# print('cora:\n',cora.head())
# 将编号转换成0-2707,方便进行下下标索引
labelencoder = LabelEncoder()
cora[0] = labelencoder.fit_transform(cora[0])
print('cora:\n',cora.head())

2.1.1 数据转化

由于原始数据中都是点集,这里需要将数据转换成模型更加容易处理的图状结构,并生成相对应的邻接矩阵,另外由于最后的的标签是字符串,如果要导入模型需要用one-hot(热独)编码进行转化。

标签one-hot编码:

# 定义辅助函数热独编码,这里的labels是一个列表
def encode_onehot(labels):classes = sorted(list(set(labels))) # set去重,sorted按照从小到大排序classes_dict = {c: np.identity(len(classes))[i, :] for i, c in enumerate(classes)} # 生成热独编码字典 {类别:热独编码}labels_onehot = np.array(list(map(classes_dict.get, labels)), dtype=np.int32) # map函数,对labels中的每个元素进行热独编码,get函数单独使用,返回字典中key对应的valuereturn labels_onehot # 返回热独编码后的标签,shape为(N,C)

生成图结构:

# 生成图结构
idx = np.array(idx_features_labels[:, 0], dtype=np.int32) # 提取索引
idx_map = {j: i for i, j in enumerate(idx)} # 生成索引字典,{原索引:新索引},enumerate将列表转换为索引-值对
# 生成边信息
edges_unordered = np.genfromtxt(cora_cites_path, dtype=np.int32) # 读取边信息,shape=(5429,2)
edges = np.array(list(map(idx_map.get, edges_unordered.flatten())), dtype=np.int32).reshape(edges_unordered.shape) # 生成重新编号的边信息,shape=(5429,2)

生成邻接矩阵:

adj = sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])), shape=(labels.shape[0], labels.shape[0]), dtype=np.float32) # 生成邻接矩阵的稀疏矩阵,np.ones(edges.shape[0])表示矩阵中所有非零元素的值,edges[:, 0]和edges[:, 1]分别表示这些非零元素的行索引和列索引。
adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj) # 对称归一化邻接矩阵,因为无向图,所以邻接矩阵应该是对称的,这段代码将adj更新为adj和adj.T中的较大值

2.1.2 创建数据集

# 加载数据
idx_features_labels = np.genfromtxt(cora_content_path, dtype=np.dtype(str)) # 读取数据
# 从数据中提取特征
features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.float32) # 提取特征
# 提取标签
labels = encode_onehot(idx_features_labels[:, -1]) # 提取标签,-1表示最后一列,同时进行热独编码

2.2 模型构建

2.2.1 参数设置

在这里插入图片描述
首先是Transductive Learning的部分,蓝色的部分是可以适用于Cora数据集和Citeseer数据集的模型参数;绿色部分是为了Pubmed数据集修改的参数:

  • 第一层:
    • 多头注意力 K = 8
    • 输出维度 F’ = 8
    • 激活函数用 ELU
  • 第二层:
    • 一个单注意力层
    • 输出只有一个attention head,如果是Pubmed则修改为8个
  • 训练参数:
    • λ \lambda λ = 0.0005,如果是Pubmed则修改成0.001
    • p p p = 0.6

在这里插入图片描述
之后是Inductive Learning的部分,定义了三层的GAT模型,同时给出了参数设置:

  • 前两层参数定义:
    • 多头注意力 K = 4
    • 输出维度 F’ = 256
    • 激活函数 Func = ELU
  • 后一层参数定义
    • 多头注意力 K = 6
    • 使用求平均的方法
    • 用sigmoid函数做激活函数
  • 训练参数:
    • 数据量够大,不需要dropout
    • batch:2

2.2.2 模型代码

单层的模型部分

class GSingleLayer(nn.Module):def __init__(self, in_features, out_features, dropout, alpha, concat = True): # in_features是输入特征维度,out_features是输出特征维度,dropout是dropout的概率,alpha是leakyrelu的参数,concat是否要拼接super(GSingleLayer, self).__init__()self.dropout = dropout # 在数据量较少的时候,可以使用dropout防止过拟合self.in_features = in_features # 输入特征维度Fself.out_features = out_features # 输出特征维度F'self.concat = concat # 是否要拼接,对于中间层,需要拼接因为这里使用的是多头注意力,但是对于最后一层就不需要拼接直接计算平均值self.alpha = alpha # leakyrelu的参数# 定义权重矩阵self.W = nn.Parameter(torch.empty(size=(in_features, out_features))) # 定义权重矩阵,shape=[F,F'],这里跟论文中的不一样是后面在将数据导入模型的时候纬度是[N,F]。这里要相应进行调整# 用Xavier方法初始化权重矩阵nn.init.xavier_uniform_(self.W.data, gain=1.414) # 使用Xavier方法初始化权重矩阵# 定义权重向量aself.a = nn.Parameter(torch.empty(size=(2*out_features, 1))) # 定义权重向量a,shape=[2F',1]# 用Xavier方法初始化权重向量ann.init.xavier_uniform_(self.a.data, gain=1.414)# 定义leakyrelu激活函数self.leakyrelu = nn.LeakyReLU(self.alpha)# 定义在这一层节点会进行什么操作# h就是论文中的输入特征集合,adj就是邻接矩阵def forward(self, h, adj):# 首先是e的计算,矩阵的使用用高维的避免了循环遍历Wh = torch.mm(h, self.W) # shape [N, F']Wh1 = torch.matmul(Wh,self.a[:self.out_features, :]) # shape [N, 1], 这里将a拆开相当于后面将两个拼接起来Wh2 = torch.matmul(Wh,self.a[self.out_features:, :]) # shape [N, 1],这里一定要记住不能少了后面这个冒号!!!!!e = self.leakyrelu(Wh1 + Wh2.T) # shape [N, N],同时这里使用了广播相加# 计算注意力系数azero_vec = -9e15*torch.ones_like(e) # 生成负无穷矩阵,方便后续经过softmax函数直接变成0attention = torch.where(adj > 0, e, zero_vec) # 前面计算了所有节点之间的相关性系数,这里筛选出有边关系的保留attention,剩下的不保留attention = F.softmax(attention, dim = 1)attention = F.dropout(attention, self.dropout, training = self.training)h_prime = torch.matmul(attention, Wh) # 将所有的attention和Wh相乘得到当前层的输出if self.concat: # 如果是拼接的话return F.elu(h_prime)else:return h_prime

模型网络架构部分

class GAT_Network(nn.Module):def __init__(self, nfeature, nhidden, nclass, dropout, alpha, nheads): # nfeature是输入特征的维度,nhidden是隐藏层的维度,nclass是输出的类别数,dropout是dropout的概率,alpha是leakyrelu的参数,nheads是多头注意力的头数super(GAT_Network, self).__init__()self.dropout = dropoutself.nheads = nheads # 定义多个注意力层self.attentions = [GSingleLayer(nfeature, nhidden, dropout, alpha) for _ in range(nheads)] # 定义多个注意力层,输出是[nhidden, nheads]for i, attention in enumerate(self.attentions):# 将多个注意力层加入到模型中,并行计算self.add_module('attention_{}'.format(i), attention)# 定义输出层,输出层的输入维度就是上一层[nhidden, nheads],由于这里是最后一层,根据论文中说的就不进行拼接了self.out_att = GSingleLayer(nhidden * nheads, nclass, dropout, alpha, concat = False)# 定义在网络架构中信息的传递规则def forward(self, x, adj):x = F.dropout(x, self.dropout, training = self.training) # dropoutx = torch.cat([att(x, adj) for att in self.attentions], dim = 1) # 将多个注意力层的输出拼接起来x = F.dropout(x, self.dropout, training = self.training)x = F.elu(self.out_att(x, adj)) # 用elu激活函数return F.log_softmax(x, dim = 1) # softmax将输出转换为概率分布

2.2.3 pytorch官方GAT源码实现

class GATConv(MessagePassing):def __init__(self, in_channels: Union[int, Tuple[int, int]],out_channels: int, heads: int = 1, concat: bool = True,negative_slope: float = 0.2, dropout: float = 0.,add_self_loops: bool = True, bias: bool = True, **kwargs):kwargs.setdefault('aggr', 'add')super(GATConv, self).__init__(node_dim=0, **kwargs)self.in_channels = in_channelsself.out_channels = out_channelsself.heads = headsself.concat = concatself.negative_slope = negative_slopeself.dropout = dropoutself.add_self_loops = add_self_loopsif isinstance(in_channels, int):self.lin_l = Linear(in_channels, heads * out_channels, bias=False)self.lin_r = self.lin_lelse:self.lin_l = Linear(in_channels[0], heads * out_channels, False)self.lin_r = Linear(in_channels[1], heads * out_channels, False)self.att_l = Parameter(torch.Tensor(1, heads, out_channels))self.att_r = Parameter(torch.Tensor(1, heads, out_channels))if bias and concat:self.bias = Parameter(torch.Tensor(heads * out_channels))elif bias and not concat:self.bias = Parameter(torch.Tensor(out_channels))else:self.register_parameter('bias', None)self._alpha = Noneself.reset_parameters()def reset_parameters(self):glorot(self.lin_l.weight)glorot(self.lin_r.weight)glorot(self.att_l)glorot(self.att_r)zeros(self.bias)def forward(self, x: Union[Tensor, OptPairTensor], edge_index: Adj,size: Size = None, return_attention_weights=None):H, C = self.heads, self.out_channelsx_l: OptTensor = Nonex_r: OptTensor = Nonealpha_l: OptTensor = Nonealpha_r: OptTensor = Noneif isinstance(x, Tensor):assert x.dim() == 2, 'Static graphs not supported in `GATConv`.'x_l = x_r = self.lin_l(x).view(-1, H, C)alpha_l = (x_l * self.att_l).sum(dim=-1)alpha_r = (x_r * self.att_r).sum(dim=-1)else:x_l, x_r = x[0], x[1]assert x[0].dim() == 2, 'Static graphs not supported in `GATConv`.'x_l = self.lin_l(x_l).view(-1, H, C)alpha_l = (x_l * self.att_l).sum(dim=-1)if x_r is not None:x_r = self.lin_r(x_r).view(-1, H, C)alpha_r = (x_r * self.att_r).sum(dim=-1)assert x_l is not Noneassert alpha_l is not Noneif self.add_self_loops:if isinstance(edge_index, Tensor):num_nodes = x_l.size(0)if x_r is not None:num_nodes = min(num_nodes, x_r.size(0))if size is not None:num_nodes = min(size[0], size[1])edge_index, _ = remove_self_loops(edge_index)edge_index, _ = add_self_loops(edge_index, num_nodes=num_nodes)elif isinstance(edge_index, SparseTensor):edge_index = set_diag(edge_index)# propagate_type: (x: OptPairTensor, alpha: OptPairTensor)out = self.propagate(edge_index, x=(x_l, x_r),alpha=(alpha_l, alpha_r), size=size)alpha = self._alphaself._alpha = Noneif self.concat:out = out.view(-1, self.heads * self.out_channels)else:out = out.mean(dim=1)if self.bias is not None:out += self.biasif isinstance(return_attention_weights, bool):assert alpha is not Noneif isinstance(edge_index, Tensor):return out, (edge_index, alpha)elif isinstance(edge_index, SparseTensor):return out, edge_index.set_value(alpha, layout='coo')else:return outdef message(self, x_j: Tensor, alpha_j: Tensor, alpha_i: OptTensor,index: Tensor, ptr: OptTensor,size_i: Optional[int]) -> Tensor:alpha = alpha_j if alpha_i is None else alpha_j + alpha_ialpha = F.leaky_relu(alpha, self.negative_slope)alpha = softmax(alpha, index, ptr, size_i)self._alpha = alphaalpha = F.dropout(alpha, p=self.dropout, training=self.training)return x_j * alpha.unsqueeze(-1)def __repr__(self):return '{}({}, {}, heads={})'.format(self.__class__.__name__,self.in_channels,self.out_channels, self.heads)

这个源码看起来还是有点费劲的,跟原论文的匹配程度不是非常高。

2.3 模型训练

在这里插入图片描述
论文中给出了训练的参数设置如下:

  • 初始化:使用Glorot初始化方法来初始化模型参数比如 W W W权重矩阵和 α \alpha α权重向量。
  • 优化器:使用Adam SGD优化器
  • 学习率:Pubmed是0.01,其他数据集是0.005
  • 训练轮次:100

模型参数定义:

# 设置随机种子,保证实验的可重复性
seed = 72
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
# 定义训练参数序列
param = {"epochs": 200, # "Number of epochs to train."'lr': 0.005,  # "Initial learning rate."'weight_decay': 5e-4,  # "Weight decay (L2 loss on parameters)."'hidden': 8,  # "Number of hidden units."'nb_heads': 8,  # "Number of head attentions."'dropout': 0.6,  # "Dropout rate (1 - keep probability)."'alpha': 0.2,  # "Alpha for the leaky_relu."'patience': 100,    # "Patience"'fastmode': False  # "Activate fast mode (i.e. validate during training)."}

定义模型:

model = GAT_Network(nfeature = features.shape[1],nhidden = param["hidden"],nclass = labels.max().item() + 1,dropout = param["dropout"],alpha = param["alpha"],nheads = param["nb_heads"]
)
optimizer = optim.Adam(model.parameters(), # 模型参数lr = param["lr"],weight_decay = param["weight_decay"]
)

训练代码:

def train(epoch):t = time.time()# 训练模型model.train() # 训练模式optimizer.zero_grad() # 梯度清零output = model(features, adj) # 前向传播loss_train = F.nll_loss(output[idx_train], labels[idx_train]) # 计算损失acc_train = accuracy(output[idx_train], labels[idx_train]) # 计算准确率loss_train.backward() # 反向传播optimizer.step() # 更新参数if not param["fastmode"]: # 如果不是快速模式,验证集和测试集也要进行训练model.eval()output = model(features, adj)loss_val = F.nll_loss(output[idx_val], labels[idx_val])acc_val = accuracy(output[idx_val], labels[idx_val])print('Epoch: {:04d}'.format(epoch+1),'loss_train: {:.4f}'.format(loss_train.item()),'acc_train: {:.4f}'.format(acc_train.item()),'loss_val: {:.4f}'.format(loss_val.item()),'acc_val: {:.4f}'.format(acc_val.item()),'time: {:.4f}s'.format(time.time() - t))return loss_val.data.item()

测试代码:

def test():model.eval() # 测试模式output = model(features, adj) # 前向传播loss_test = F.nll_loss(output[idx_test], labels[idx_test]) # 计算损失acc_test = accuracy(output[idx_test], labels[idx_test]) # 计算准确率print("Test set results:","loss= {:.4f}".format(loss_test.item()),"accuracy= {:.4f}".format(acc_test.item()))

三、结果展示

3.1 复现结果

在这里插入图片描述
这里跑出来的结果符合官网的要求,哈哈哈哈哈,同时也能看到loss_val是越来越小了,我的显卡是8G显存的3060,一共跑了20秒就出结果了。

3.2 论文结果

在这里插入图片描述
这个是推理(Transductive)的准确度评价表格,评价模型在已知训练集的情况下在测试集下的表现情况,从图结构中直观看就是知道一个节点的信息就可以推测出与之相连的一级节点的信息。
在这里插入图片描述
这个是归纳式学习(Inductive)的准确度评价表格,评价经过现有数据训练的模型在遇到新数据的表现情况,在论文中就是通过论文引用训练的数据去预测蛋白质相关的信息。

四、代码细节

  1. torch中的forward函数
    在创建一个继承自nn.module的类的时候,后续用到model()函数的时候模型会自动调用forward函数进行前向传播的方法。
  2. torch中的dot mm mv matmul的区别
  • dot:用于处理两个一维向量的点积
  • mm:用于二维矩阵的相乘
  • mv:用于计算一维向量和二维矩阵的乘积,注意中间的维度要对应。
  • matmul:用于处理高维度矩阵的乘法,如果维度不同则会采用广播的方法,这个最常用。
  1. 将数据升维可以避免循环
arr1 = [1, 2, 3]
arr2 = [4, 5, 6]
result = []
for i in range(len(arr1)):for j in range(len(arr2)):result.append(arr1[i] * arr2[j])

比如上面这段代码要求每个元素的乘积,可以通过变成二维数组进行计算如下:

import numpy as nparr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr1_2d = np.tile(arr1, (len(arr2), 1))
arr2_2d = np.tile(arr2, (len(arr1), 1)).T
result = arr1_2d * arr2_2d

这样就避免了循环,由于显卡可以并行计算这里换成用矩阵进行乘法可以大大提升计算速度。

  1. where函数简化判断
attention = torch.where(adj_nodes > 0, e, zero_vec) # 将邻接矩阵中的0位置的值替换成-9e15,这样在softmax后就会变成0

这行代码表示如果adi_nodes中的张量大于零张量,那么就选取e张量中对应的张量,否则选择zero_vec中对应的张量。

  1. 对从文件中读取的信息重新编码
np.array(list(map(classes_dict.get, labels)), dtype=np.int32)

这里map会将get函数会对classes_dict中的每个元素获取对应下标并作为labels中每个元素的对应索引,之后转换成list。

  1. 生成对称矩阵的快捷方法
adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)

这段代码的功能跟adj[i,j]=max(adj[i,j],adj[j,i])是一样的,但是不用循环遍历了

代码链接

我所有涉及到的代码都在这个Github链接直接点开ipynb文件,然后可以跟着conda环境搭建教程用文件夹里面的requirements.txt配置一下conda环境然后直接点击run all就可以看到结果了。
在这里插入图片描述

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

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

相关文章

【Python项目】基于opencv的的【疲劳检测系统】

技术简介:使用Python技术、OpenCV图像处理库、MYSQL数据库等实现。 系统简介:用户可以通过登录系统平台实现实时的人脸照片的拍摄和上传,结合上传图像的内容进行后台的图像预处理和运算分析,用户可以通过照片分析界面查看到当前检…

数学中的极值

在数学领域中,极值是一个重要的概念,它不仅在纯数学理论研究中占据核心地位,还在实际应用中发挥着巨大作用。从微积分的基本定理到优化问题的求解,从物理学的能量守恒到经济学的边际分析,极值理论无处不在。本文将详细…

【MySQL】第一次作业

【MySQL】第一次作业 1、在官网下载安装包2、解压安装包,创建一个dev_soft文件夹,解压到里面。3、创建一个数据库db_classes4、创建一行表db_hero5、将四大名著中的常见人物插入这个英雄表 写一篇博客,在window系统安装MySQL将本机的MySQL一定…

求解亲和数

【问题描述】 古希腊数学家毕达哥拉斯在自然数研究中发现,220的所有真约数(即不是自身 的约数)之和为: 1245101120224455110284。而284的所有真约数为1、2、4、71、142,加起来恰好为220。人 们对这样的数感到很惊奇&am…

Spring Cloud架构进化实操:Eureka、Apollo、OpenFeign、Ribbon、Zuul组件

文章目录 前言一、引出二、服务注册与发现2.1 创建Eureka注册中心2.1.1 引入pom依赖2.1.2 配置yaml2.1.3 启动服务21.4 测试访问 2.2 创建服务提供者2.2.1 配置yaml2.2.2 启动服务2.2.3 测试访问 2.3 创建服务消费者2.3.1 服务提供者接口2.3.2 服务消费者调用接口 三、负载均衡…

用户中心(优化)

文章目录 功能扩充管理员修改用户信息管理员删除用户管理员添加用户添加个人主页,可以完善个人信息(上传头像没有实现)添加默认头像打造一个所有用户可发帖的页面前端页面,√后端建表,接口,√前后端联调√ …

W801学习笔记二十:宋词学习应用

前三章完成了唐诗的应用,本章将实现宋词的学习应用。 宋词与唐诗的区别不大,马上开始。 1、我们需要参考前面唐诗的方式,把宋词文本下载下来,并进行格式整理。 W801学习笔记十七:古诗学习应用——上 2、在菜单中添加…

[论文阅读]Adversarial Autoencoders(aae)和代码

In this paper, we propose the “adversarial autoencoder” (AAE), which is a probabilistic autoencoder that uses the recently proposed generative adversarial networks (GAN) to perform variational inference by matching the aggregated posterior of the hidden …

proxmox宿主机安装桌面

装完proxmox启动后一般进入shell界面,之后都是另外一台电脑连接web管理等操作,一直用起来还好。不过这样需要另外一台电脑连接管理操作,有时候调试时毕竟还是会有些不方便,就想能不能在宿主机上装个桌面做这类事,今天用…

python数据分析——大数据和云计算

大数据和云计算 前言一、大数据二、大数据定义三、数据存储单位四、大数据存储技术五、大数据应用技术六、大数据特征七、数据容量八、数据类型的多样性8.1结构化数据8.2半结构化数据8.3非结构化数据 九、获取数据的速度十、可变性十一、真实性十二、复杂性十三、价值十四、云计…

传输控制协议TCP

一、TCP简介 TCP是面向连接的: TCP连接只能有两个端点,TCP连接是点对点的; TCP提供可靠交互的服务; TCP提供全双工通信。 面向字节流: TCP中的“流”指的是流入或流出进程的字节序列; 虽然应用程序和…

Mac基于Docker-ubuntu构建c/c++编译环境

编译环境安装和使用被充分验证,如有期望补充的内容欢迎留言评论。 目录 前言 Docker desktop下载安装 修改镜像源 选择ubuntu镜像 docker容器启动 参数说明: 宿主机与docker容器文件共享 宿主机与docker容器拷贝文件 为 Ubuntu 配置 ssh、vim、make 相关工…

Flyway使用教程

Flyway使用教程 背景: 在开发环境对多个不同版本的分支进行开发,如果此时涉及到多张表结构修改(比如新增字段),而在测试环境时却忘了整理SQL 脚本给测试人员执行,就会导致出现 bug,从而影响测试…

Android selinux权限

一.SE 概述 SELinux 是由美国NSA(国安局)和 SCC 开发的 Linux的一个扩张强制访问控制安全模块。原先是在Fluke上开发的,2000年以 GNU GPL 发布。从 fedora core 2开始, 2.6内核的版本都支持SELinux。 在 SELinux 出现之前&#…

PHP ASCII码的字符串用mb_convert_encoding 转utf-8之后不生效

检测数据类型是ascii,转码之后再检测还是utf-8没生效 private function toUTF8($str){$encode mb_detect_encoding($str, array("ASCII",UTF-8,"GB2312","GBK",BIG5,LATIN1));if ($encode ! UTF-8) {$str1 mb_convert_encoding($str, UTF-8, …

抓包证书安装到安卓7.0+手机

前言: 首先理解一下,这个不只是证书到浏览器,而是抓包证书到安卓7.0+手机上的文章; 还有一点区分,在浏览器上装的证书,只是让抓包工具可以抓取手机浏览器的包,而不是抓取手机app上的包; 如果你的证书只是简单的在浏览器下进行安装,那么你的手机app是走不了代理网络的…

【微服务】服务保护(通过Sentinel解决雪崩问题)

Sentinel解决雪崩问题 雪崩问题服务保护方案服务降级保护 服务保护技术SentinelFallback服务熔断 雪崩问题 在微服务调用链中如果有一个服务的问题导致整条链上的服务都不可用,称为雪崩 原因 微服务之间的相互调用,服务提供者出现故障服务的消费者没有…

【微服务】配置管理

Nacos配置管理 配置管理配置共享配置热更新 配置管理 将微服务集群中常用,经常变化的配置都写到一个独立的配置文件微服务中进行统一管理 配置共享 在Nacos的界面当中进行配置管理,在配置列表中添加配置 比如各个服务中的jdbc的连接配置: …

HTML/CSS1

1.前置说明 请点这里 2.img元素 格式&#xff1a; <img src"图片地址" alt"占位文字" width"图片宽度" height"图片高度">其中alt是当图片加载失败时显示的文字 而且不同内核的浏览器显示出来的占位文字的效果也是不尽相同的…

【Android学习】简单的登录页面和业务逻辑实现

实现功能 1 登录页&#xff1a;密码登录和验证码登录 2 忘记密码页&#xff1a;修改密码 3 页面基础逻辑 java代码 基础页面 XML login_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.and…