基于gcn的半监督分类代码整理2

一、说明

对论文《Semi-Supervised Classification with Graph Convolutional Network》的代码整理。

第一部分主要说明了数据预处理和初始化等工作,这节主要说明gcn和mlp模型建模以及数据训练过程,以下是笔记和代码逻辑的整理。

注:本人是初学者,如发现哪里写的不对或不妥当,欢迎随时和我交流,一起进步!

二、代码

1、model.py文件

会引用layers.py和metrics.py文件里的函数,我直接放在一起讲了

(1)首先定义一个抽象的模型类Model(),它提供了初始化、构建、训练和报错神经网络的功能和接口,可以用来构建各种类型的神经网络模型。下面我会一一详细介绍,这种方法值得学习。

class Model(object):

首先是模型的初始化方法,接收传入的关键字参数,在此定义允许传入的关键字参数,然后检查传入的关键字是否被允许。之后定义模型名称、配置日志记录、变量字典、占位字典、层列表、激活函数列表、输入输出、损失和正确率、优化器和优化操作

    def __init__(self, **kwargs):allowed_kwargs = {'name', 'logging'} # 定义了允许传入的关键字参数for kwarg in kwargs.keys():# 对传入的关键字进行检查,确保属于被允许的关键字assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwargname = kwargs.get('name')if not name:name = self.__class__.__name__.lower() # 使用类名的小写作为模型名称self.name = namelogging = kwargs.get('logging', False)self.logging = logging # 日志记录的配置self.vars = {} # 变量字典self.placeholders = {} # 占位字典self.layers = [] # 层列表self.activations = [] # 激活函数列表self.inputs = Noneself.outputs = Noneself.loss = 0self.accuracy = 0self.optimizer = None # 优化器self.opt_op = None # 优化操作

然后是定义了一个抽象方法_build,会在子类中写模型的具体结构;

一个build()方法,是对_build()的包装。首先创建变量作用域,调用_build()方法构建具体的模型结构;构建序列层模型,将模型输入添加到激活函数中,遍历每层,对每层进行前向传播计算,得到隐藏层输出,将其添加到激活函数列表中,将最后一个激活函数输出作为最终输出;获取模型变量存储到字典中;调用loss和accurary函数,这些需要在子类中定义;使用优化器最小化模型损失。

    # 定义了一个抽象方法_build,在子类中构建模型具体结构def _build(self):raise NotImplementedError# 对_build()方法的包装def build(self):# 创建变量作用域,调用_build方法构建模型具体结构with tf.compat.v1.variable_scope(self.name):self._build()# 构建序列层模型self.activations.append(self.inputs)for layer in self.layers:hidden = layer(self.activations[-1]) # 对每层进行前向传播计算,得到隐藏层输出self.activations.append(hidden) # 添加到激活函数列表中self.outputs = self.activations[-1] # 将最后一个激活函数的输出作为最终输出# 获取模型所有变量,存储到变量字典中variables = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.GLOBAL_VARIABLES, scope=self.name)self.vars = {var.name: var for var in variables}# 在子类中定义self._loss()self._accuracy()# 使用优化器最小化模型损失self.opt_op = self.optimizer.minimize(self.loss)

接下来是三个空函数,需要在子类中个性化定义

    def predict(self):passdef _loss(self):raise NotImplementedErrordef _accuracy(self):raise NotImplementedError

最后是一个保存模型变量的函数save()和加载模型变量的函数load()

save()函数中首先保存模型所有变量到saver,然后保存到指定路径save_path,保存.ckpt结构

laod()函数首先保存模型变量到saver,然后读取模型路径到save_path,并加载模型变量到会话中

    def save(self, sess=None):if not sess:raise AttributeError("TensorFlow session not provided.")saver = tf.train.Checkpoint(self.vars) # 保存模型的所有变量# 保存模型变量到指定路径save_path = saver.save(sess, "tmp/%s.ckpt" % self.name)print("Model saved in file: %s" % save_path)def load(self, sess=None):if not sess:raise AttributeError("TensorFlow session not provided.")saver = tf.train.Checkpoint(self.vars)save_path = "tmp/%s.ckpt" % self.name # 指定模型路径# 从指定路径加载模型变量到会话中saver.restore(sess, save_path)print("Model restored from file: %s" % save_path)

(2)MLP模型

继承了刚刚的Model类

class MLP(Model):

首先定义模型的初始化方法,是调用了父类的初始化方法,设置输入是features,输入维度是input_dim,输出维度是labels的第二个维度大小,优化器是Adam,调用父类的构建模型的方法self.build()

    # 定义了初始化方法def __init__(self, placeholders, input_dim, **kwargs):# 调用父类model的初始化方法super(MLP, self).__init__(**kwargs)self.inputs = placeholders['features']self.input_dim = input_dim# 获取标签占位符的形状信息,并将其第二个维度的大小赋值给 self.output_dimself.output_dim = placeholders['labels'].get_shape().as_list()[1]self.placeholders = placeholdersself.optimizer = tf.keras.optimizers.Adam(learning_rate=FLAGS.learning_rate)self.build()

接着定义模型的损失函数_loss(),首先遍历第一层的所有变量,计算权重衰减损失,添加到总损失中,然后将交叉熵损失添加到总损失中。

计算交叉熵损失的函数masked_softmax_cross_entropy()在metrics.py文件中,这里考虑了标签的掩码。传入预测值 outpus、真实标签labels、标签掩码labels_mask,先计算预测值和真实值之间的softmax交叉熵损失,然后对mask进行归一化操作,与loss逐元素相乘,得到经掩码处理过的损失,再返回损失的均值。

# 定义_loss(),用于计算模型的损失。
# 包括权重衰减损失(L2正则化)和交叉熵损失(使用softmax_cross_entropy函数计算)
def _loss(self):# 遍历第一层的所有变量的值,将权重衰减损失添加到总损失中for var in self.layers[0].vars.values():self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var)# 将交叉熵损失添加到总损失中,考虑了标签掩码self.loss += masked_softmax_cross_entropy(self.outputs, self.placeholders['labels'],self.placeholders['labels_mask'])def masked_softmax_cross_entropy(preds, labels, mask):# 计算预测值pred和labels的softmax交叉熵损失loss = tf.nn.softmax_cross_entropy_with_logits(logits=preds, labels=labels)mask = tf.cast(mask, dtype=tf.float32) #变成float32类型# 进行归一化操作,除以平均值,保证掩码总和为1mask /= tf.reduce_mean(mask)# 逐元素相乘将掩码应用到损失上loss *= maskreturn tf.reduce_mean(loss)

然后是计算正确率的函数_accurary(),调用了metrics.py文件的函数masked_accuracy(),传入的也是这三个参数,首先根据索引分别对preds和labels的样本类别逐个比较,返回bool类型,然后就是对mask实行归一化,并与正确率逐元素相乘,最后返回平均值。

def _accuracy(self):self.accuracy = masked_accuracy(self.outputs, self.placeholders['labels'], self.placeholders['labels_mask'])
def masked_accuracy(preds, labels, mask):# tf.argmax(preds, 1):返回预测值 preds 中每个样本预测结果最大概率的类别索引# tf.argmax(labels, 1)返回与labels中每个样本真实类别的索引# tf.equal()比较对应元素是否相等,返回布尔类型的张量correct_prediction = tf.equal(tf.argmax(preds, 1), tf.argmax(labels, 1))accuracy_all = tf.cast(correct_prediction, tf.float32)mask = tf.cast(mask, dtype=tf.float32)mask /= tf.reduce_mean(mask) # 归一化accuracy_all *= mask # 考虑索引return tf.reduce_mean(accuracy_all)

接下来调用父类的_build()函数建立模型,包括两个全连接层。

Dense()类在layers.py文件中,见第三节

    # MLP模型包括两个全连接层(Dense层)# 第一个全连接层的输出维度为FLAGS.hidden1,激活函数为ReLU# 第二个全连接层的输出维度为输出维度(labels的维度),激活函数为线性函数def _build(self):self.layers.append(Dense(input_dim=self.input_dim,output_dim=FLAGS.hidden1,placeholders=self.placeholders,act=tf.nn.relu, # relu激活函数dropout=True,sparse_inputs=True,logging=self.logging))self.layers.append(Dense(input_dim=FLAGS.hidden1,output_dim=self.output_dim,placeholders=self.placeholders,act=lambda x: x, # 线性激活函数dropout=True,logging=self.logging))

最后是predict()函数,返回outputsj经过softmax函数处理后的结果,用于进行分类预测。

    def predict(self):return tf.nn.softmax(self.outputs)

(3)GCN()类

继承了Model父类,包括__init__()函数、_loss()函数、_accuracy()函数、_build()函数、predict()函数,其中只有_build()函数不一样,包括两个图卷积层,

    # 用于构建GCN模型的具体结构。# 该GCN模型包括两个图卷积层(GraphConvolution层),# 第一个图卷积层的输出维度为FLAGS.hidden1,激活函数为ReLU,# 第二个图卷积层的输出维度为输出维度(labels的维度),激活函数为线性函数def _build(self):self.layers.append(GraphConvolution(input_dim=self.input_dim,output_dim=FLAGS.hidden1,placeholders=self.placeholders,act=tf.nn.relu,dropout=True,sparse_inputs=True,logging=self.logging))self.layers.append(GraphConvolution(input_dim=FLAGS.hidden1,output_dim=self.output_dim,placeholders=self.placeholders,act=lambda x: x,dropout=True,logging=self.logging))

2、Layer.py文件

layer.py文件也跟刚才一样,首先建立一个抽象类Layer,然后再建立全连接层Dense和GraphConvolution继承自Layer类。

(1)Layer类

class Layer(object):

首先是__init__()函数,和model类的很类似,检查传入的参数、指定名字、指定变量、指定日志的配置方式,设置稀疏矩阵的默认是false

    def __init__(self, **kwargs):allowed_kwargs = {'name', 'logging'}for kwarg in kwargs.keys():assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwargname = kwargs.get('name')if not name:layer = self.__class__.__name__.lower()name = layer + '_' + str(get_layer_uid(layer))self.name = nameself.vars = {}logging = kwargs.get('logging', False)self.logging = loggingself.sparse_inputs = False

接着是_call()函数和__call__()函数,_call函数现在是空壳,__call__()函数接受inputs作为输入,使用tf.name_scope()函数创建命名空间,如果启用了日志&输入不是稀疏矩阵,就将输入的直方图添加到日志中;输出是调用_call()函数得到输出,如果启用了日志,就将输出的直方图添加到日志中,并返回输出。

    def _call(self, inputs):return inputsdef __call__(self, inputs):with tf.name_scope(self.name):if self.logging and not self.sparse_inputs:tf.summary.histogram(self.name + '/inputs', inputs)outputs = self._call(inputs)if self.logging:tf.summary.histogram(self.name + '/outputs', outputs)return outputs

最后是_log_vars()函数,对于传入的所有变量,依次将每个变量的直方图摘要添加到日志中。

    def _log_vars(self):for var in self.vars:tf.summary.histogram(self.name + '/vars/' + var, self.vars[var])

(2)全连接层Dense()

Dense()类继承自Layer()类,重写__init__()和_call()函数。

class Dense(Layer):

首先是重写__init__()函数,传入输入维度、输出维度、占位符、丢弃率、输入是否稀疏矩阵、激活函数、偏置项bias、输入特征是否为空featureless、参数。调用父类Layer()的初始化方法,设置dropout,设置一系列参数;处理稀疏输入;使用tf.compat.v1.variable_scope()函数创建变量作用域,创建权重weights和偏置bias添加到变量中,使用glorot()和zeros()方法分别初始化。

    def __init__(self, input_dim, output_dim, placeholders, dropout=0., sparse_inputs=False,act=tf.nn.relu, bias=False, featureless=False, **kwargs):super(Dense, self).__init__(**kwargs)if dropout:self.dropout = placeholders['dropout']else:self.dropout = 0.self.act = actself.sparse_inputs = sparse_inputsself.featureless = featurelessself.bias = bias# helper variable for sparse dropout# 处理稀疏输入self.num_features_nonzero = placeholders['num_features_nonzero']# 使用变量作用域,创建权重weights和偏置biaswith tf.compat.v1.variable_scope(self.name + '_vars'):self.vars['weights'] = glorot([input_dim, output_dim],name='weights')if self.bias:self.vars['bias'] = zeros([output_dim], name='bias')if self.logging:self._log_vars() #日志记录权重和偏置摘要

然后是重写_call()函数,功能是实现dropout和矩阵乘得到输出。

首先判断输入x是否是稀疏矩阵,是就调用稀疏矩阵的丢弃函数sparse_dropout(),不是就直接执行tf.nn.dropout()。然后执行y=wx+b,先将x与权重weight执行矩阵乘,在dot()函数中,稀疏矩阵使用tf.compat.v1.sparse_tensor_dense_matmul()函数实现矩阵乘,普通矩阵使用tf.matmul()函数;再与偏置项bias相加,返回output。

    def _call(self, inputs):x = inputs# dropoutif self.sparse_inputs:x = sparse_dropout(x, 1-self.dropout, self.num_features_nonzero)else:x = tf.nn.dropout(x, 1-self.dropout)# transformoutput = dot(x, self.vars['weights'], sparse=self.sparse_inputs)# biasif self.bias:output += self.vars['bias']return self.act(output)
def sparse_dropout(x, keep_prob, noise_shape):random_tensor = keep_prob# 加上随机生成的[0,1]的均匀分布矩阵random_tensor += tf.random.uniform(noise_shape)# 先向下取整:>1的变成1;  <1的变成0# 再取布尔值:1→true,0→false,这样在统计上保证keep_prob的被保留dropout_mask = tf.cast(tf.floor(random_tensor), dtype=tf.bool)# 相同位置如果是true的被保留,是false的被丢弃(设为0)pre_out = tf.sparse.retain(x, dropout_mask)return pre_out * (1./keep_prob)def dot(x, y, sparse=False):"""矩阵乘(稀疏矩阵 vs 常规)."""if sparse:res = tf.compat.v1.sparse_tensor_dense_matmul(x, y)else:res = tf.matmul(x, y)return res

(3)图卷积层GraphConvolution()

GraphConvolution()类继承自Layer()类,重写__init__()和_call()函数。

class GraphConvolution(Layer):

首先是__init__()函数,与mlp类的初始化函数不同之处在于,它导入了placeholders['support'],即经处理过的图数据,以及创建weight和bias的方式:创建作用域后,首先读取support的长度,然后根据长度i分别创建i个weight,维度是[input_dim, output_dim],并使用glorot()函数进行初始化;但只需创建并初始化一个bias。

        self.support = placeholders['support'] # 是图数据# 处理稀疏矩阵,是noise_shapeself.num_features_nonzero = placeholders['num_features_nonzero']# variable_scope是创建作用域with tf.compat.v1.variable_scope(self.name + '_vars'):for i in range(len(self.support)): # len()=1self.vars['weights_' + str(i)] = glorot([input_dim, output_dim],name='weights_' + str(i))if self.bias:self.vars['bias'] = zeros([output_dim], name='bias')

然后是_call()函数,此处只解释卷积操作:首先遍历support里的元素,如果有节点特征,就将特征(稀疏矩阵)和权重进行矩阵乘得到pre_sup,如果节点无特征,直接将权重值赋给pre_sup。然后执行图卷积操作,将矩阵和节点特征进行卷积,即support[i]和pre_sup进行矩阵乘,然后得到的结果进行累加tf.add_n(),得到输出。

        # 卷积操作!!!supports = list()for i in range(len(self.support)):if not self.featureless: # 节点有特征,将特征和权重相乘pre_sup = dot(x, self.vars['weights_' + str(i)],sparse=self.sparse_inputs)else: # 节点无特征,将权重值赋给pre_suppre_sup = self.vars['weights_' + str(i)]# 图卷积操作:将矩阵和节点特征进行卷积support = dot(self.support[i], pre_sup, sparse=True)supports.append(support)output = tf.add_n(supports) #元素累加

3、train.py文件:

首先着重说明一下support变量,在模型选择代码中,support = [preprocess_adj(adj)],相关的函数已在1中说明,此处直接解释函数功能,打印support的值为

[(array([[   0,    0],[ 633,    0],[1862,    0],...,[1473, 2707],[2706, 2707],[2707, 2707]]), array([0.25     , 0.25     , 0.2236068, ..., 0.2      , 0.2      ,0.2      ]), (2708, 2708))]

可以看出support是一个元组,包含了三个部分:

①一个二维数组,每个元素表示稀疏矩阵的非零元素的坐标 (row_index, col_index)。  

②一个一维数组,包含了稀疏矩阵中每个非零元素的数值。

③一个元组,表示稀疏矩阵的形状。


此处接着第一节继续说明代码功能。

(1)建立模型、初始化全局变量

# Create model
model = model_func(placeholders, input_dim=features[2][1], logging=True)
with tf.compat.v1.Session() as sess:# 初始化全局变量sess.run(tf.compat.v1.initializers.global_variables())

model_func即前面提到的GCN和MLP,在model.py文件里(见下面)。

(2)训练模型

训练epochs轮,每轮循环时,首先根据训练集数据创建feed dictionary,更新placeholders里的变量对应的数据;然后指定优化器model.opt_op,使用sess.run()运行函数,计算loss和accuracy;然后运行验证集,得到损失、正确率和所用时间,并将损失添加到cost_val中;打印每个epoch的训练和验证结果。进行早停判断:首先确保模型至少训练了一定轮再判断,然后判断当前验证集的损失是否大于最近几次验证集损失的均值,大于说明当前一轮的损失过大,可能出现了过拟合,执行早停。

# Train model
for epoch in range(args.epochs):t = time.time()# 训练集:创建 feed dictionary,更新占位符变量对应的数据feed_dict = construct_feed_dict(features, support, y_train, train_mask, placeholders)feed_dict.update({placeholders['dropout']: args.dropout})# Training step,指定优化器,计算loss和accuracyouts = sess.run([model.opt_op, model.loss, model.accuracy], feed_dict=feed_dict)# 验证集:y_val val_maskcost, acc, duration = evaluate(features, support, y_val, val_mask, placeholders)cost_val.append(cost)# 打印每个epoch的训练和验证结果print("Epoch:", '%04d' % (epoch + 1), "train_loss=", "{:.5f}".format(outs[1]),"train_acc=", "{:.5f}".format(outs[2]), "val_loss=", "{:.5f}".format(cost),"val_acc=", "{:.5f}".format(acc), "time=", "{:.5f}".format(time.time() - t))# 早停判断:# 第一个>:确保模型至少训练了一定轮,再判断早停# 第二个>:判断当前的验证集损失是否>最近args.early_stopping次验证集损失的均值,说明最近一次损失过大,可能是过拟合if epoch > args.early_stopping and cost_val[-1] > np.mean(cost_val[-(args.early_stopping+1):-1]):print("Early stopping...")break

(3)测试集

还有y_test和test_mask没有运行,首先执行evaluate()函数,返回cost、accuracy和时间,然后直接输出

# Testing
test_cost, test_acc, test_duration = evaluate(features, support, y_test, test_mask, placeholders)
print("Test set results:", "cost=", "{:.5f}".format(test_cost),"accuracy=", "{:.5f}".format(test_acc), "time=", "{:.5f}".format(test_duration))

三、笔记

1、学习建模方法,首先建立抽象模型类,定义几个空壳函数,然后建立子类继承自抽象模型类,在子类里重写函数。子类模型中构建时,建立多层时也可以首先建立抽象层,然后建立子类层重写方法。

2、归一化操作:除以平均值,保证掩码总和为1

其中tf.reduce_mean()是求平均值的意思

    mask /= tf.reduce_mean(mask)

3、将Tebnsorflow1.x的代码改成2.x的,大部分都是加上compat.v1

例如 tf.variable_scope →→  tf.compat.v1.variable_scope

4、矩阵乘

常规的矩阵乘:res = tf.matmul(x, y)

稀疏矩阵相乘:res = tf.compat.v1.sparse_tensor_dense_matmul(x, y)

5、元素累加

output = tf.add_n(supports) #元素累加

6、早停判断

    # 早停判断:# 第一个>:确保模型至少训练了一定轮,再判断早停# 第二个>:判断当前的验证集损失是否>最近args.early_stopping次验证集损失的均值,说明最近一次损失过大,可能是过拟合if epoch > args.early_stopping and cost_val[-1] > np.mean(cost_val[-(args.early_stopping+1):-1]):print("Early stopping...")break

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

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

相关文章

Android启动时间分析

在Android启动过程中,“NHLOS” 和 “LK” 是两个与启动时间相关的术语,它们分别指的是: 各阶段时间 I Minidump: Enabled with max number of regions 200 I KPI : Bootloader start count = 59264 I KPI : Bootloader end count = 101746 I KPI : Boo…

【Spring Boot】spring boot环境搭建

1、环境准备 JDK安装&#xff1a;确保安装了Java Development Kit (JDK) 1.8或更高版本。JDK是Java编程的基础&#xff0c;Spring Boot项目需要它来编译和运行。Maven或Gradle安装&#xff1a;选择并安装Maven或Gradle作为项目构建工具。Maven通过pom.xml文件来管理项目的依赖…

Hbase/Hive连接数过多导致阻塞问题处理

HBase库连接数过多造成库堵塞 netstat -aoe&#xff08;netstat -tunap| grep 50070&#xff09;查看主机端口运行状态&#xff0c;可以看到有很多的CLOSE_WAIT。 Hiveserver2连接数过多造成库堵塞 netstat -aoe&#xff08;netstat -tunap| grep 10000&#xff09;查看主机端口…

如何在Java中使用JSON:解析与生成

如何在Java中使用JSON&#xff1a;解析与生成 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨在Java中如何使用JSON进行解析和生成&#xff…

Centos7网络配置(设置固定ip)

文章目录 1进入虚拟机设置选中【网络适配器】选择【NAT模式】2 进入windows【控制面板\网络和 Internet\网络和共享中心\更改适配器设置】设置网络状态。3 设置VM的【虚拟网络编辑器】4 设置系统网卡5 设置虚拟机固定IP 刚安装完系统&#xff0c;有的人尤其没有勾选自动网络配置…

Qt/C++开发经验小技巧296-300

国内站点&#xff1a;https://gitee.com/feiyangqingyun 国际站点&#xff1a;https://github.com/feiyangqingyun 官方店&#xff1a;https://shop114595942.taobao.com// 公众号&#xff1a;Qt实战&#xff0c;各种开源作品、经验整理、项目实战技巧&#xff0c;专注Qt/C软件…

弹出解锁登陆密钥环对话框提示解决方法

可能原因及角&#xff1a;&#xff08;重启生效&#xff09; 原因一&#xff1a;设置自动登录&#xff0c;取消自动登陆后 执行sudo rm -rf ~/.local/share/keyrings/* 命令删除配置文件 原因二&#xff1a;系统缺少依赖文件&#xff0c;执行 sudo apt-get update &&…

MeowBot:ESP32 语音控制宠物猫 DIY 教程——玩转语音识别与 MQTT 智能家居控制 (附代码解析)

摘要: 本文将手把手教你打造一只名为 MeowBot 的智能宠物猫&#xff01;它不仅可以通过舵机灵动地打招呼&#xff0c;还能听懂你的语音指令&#xff0c;帮你控制智能家居设备。让我们一起开启这段充满乐趣的 DIY 之旅吧&#xff01; 关键词: ESP32、语音识别、MQTT、智能家居、…

SpringSecurity中文文档(Servlet RememberMe)

Remember-Me Authentication Remember-me 或持久登录身份验证指的是网站能够在会话之间记住主体的标识。这通常是通过向浏览器发送 Cookie 来完成的&#xff0c;Cookie 将在以后的会话中被检测到&#xff0c;并导致自动登录的发生。Spring Security 为这些操作提供了必要的钩子…

RS232、RS485与RS422初步学习

目录 电平 传输方式 共模和差模干扰 ps&#xff1a;双绞线 485总线结构 ps&#xff1a;终端电阻 RS232接口&#xff08;DB9&#xff09; 优缺点 RS232优缺点 RS485较RS232的优点 为什么RS232还在使用&#xff1f; 电平 RS232、RS485与RS422的电平 区间逻辑备注RS232…

巧用Fiddler中的Comments提升接口测试效率

有没有同学在使用Fiddler时跟我遇到了同样的问题&#xff0c;就是想给某个抓包的请求进行注释&#xff01;&#xff01;&#xff01;但是奇怪的是&#xff0c;根本没有Comments相关信息呀&#xff1f; 设置Comments 设置Comments非常容易&#xff0c;选中一个请求&#xff0c…

基于ESP32 IDF的WebServer实现以及OTA固件升级实现记录(一)

webserver即运行在esp32上的web服务&#xff0c;相当于esp32作为web服务器&#xff0c;它可以处理web浏览器等客户端的http相关请求&#xff08;常见的get/post/put等http方法&#xff09;。 ota即在线固件升级&#xff0c;idf已经提供了丰富的官方ota示例&#xff0c;不过主要…

mybatis多数据源操作

最近项目&#xff0c;一个后端代码被两个前端页面调用&#xff0c;代码数据库都冗余了&#xff0c;写着很难受感觉跟吃狗屎一样。因此决定先把数据库分开&#xff0c;然后之后把项目分成多模块化得。 想把数据库分开其实就只是需要实现多数据源操作&#xff0c;我考虑得不多&a…

DSPy的BootstrapFewShot

这是官方文档 官方文档给示例数据的量划分了10/50/300等档位&#xff0c;对应BootstrapFewShot/BootstrapFewShotWithRandomSearch/MIPRO. 我们以10条示例数据example为例&#xff0c;即选用BootstrapFewShot DSPy认为用原始数据直接做few-shot效果不好的原因是&#xff1a;…

Golang-context理解

golang-context笔记整理 golang为何设计context&#xff1f;代码上理解原理空context类cancelCtx类.withcancelctx方法 timerCtx类valueCtx类 golang为何设计context&#xff1f; 有并发特性的语言中&#xff0c;都会有一种说法&#xff1a;创建异步线程或者携程的时候&#x…

【TS】TypeScript 入门指南:强大的JavaScript超集

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript 入门指南&#xff1a;强大的JavaScript超集一、TypeScript 简介1.1 …

Windows页面错误(Page Fault)写几种c++会导致,此问题的例子

在C中&#xff0c;直接导致Windows页面错误&#xff08;Page Fault&#xff09;的情景较少直接由编程错误引发&#xff0c;页面错误更多是由操作系统在内存管理和虚拟内存机制中处理的。不过&#xff0c;某些编程错误可能导致访问违规&#xff0c;进而间接引起操作系统报告页面…

SpringBoot实现图片添加水印(完整)

提示&#xff1a;昨天不是写了一个类似与图片添加水印的版本吗,今天来写一个带数据库&#xff0c;并且可以完整访问的版本 文章目录 目录 文章目录 引入库 配置文件 数据库配置 字段配置 索引配置 数据库表语句 启动文件 前端代码 整体代码目录 配置类AppConfig Contro…

通俗大白话理解Docker

什么是Docker Docker本质上是一种容器化技术&#xff0c;用于将应用程序及其所有依赖打包到一个标准化的单元中。这些单元&#xff08;容器&#xff09;可以在任何运行Docker的机器上运行。每个容器是相互隔离的&#xff0c;具有自己的文件系统、网络和进程空间。 以下是大白话…

gin框架中form, uri 2种类型的数据绑定到自定义结构体, 绑定数据默认值设置方法, 时间格式绑定和格式设置 详解

gin框架中可以非常方便的将http请求的form数据&#xff0c;和路由参数中的uri 数据通过反射的方式将我们自定义在结构体中的TAG字段和请求参数中同名的数据进行绑定。 要完成数据的绑定&#xff0c; 首先我们需要定义一个结构体和需要绑定的结构体字段&#xff0c;并在结构体字…