《联邦学习实战—杨强》之使用Python从零开始实现一个简单的横向联邦学习模型

news/2024/4/20 19:28:48/文章来源:https://blog.csdn.net/Waldocsdn/article/details/128096142

文章目录

  • 前言
  • 环境准备
  • 完整代码
  • 配置文件(conf.json)
  • 获取数据集(datasets.py)
  • 获取PyTorch中自带深度学习网络预训练模型(models.py)
  • 客户端(client.py)
  • 服务端(server.py)
  • main.py
  • 运行
  • 知识点补充
    • argparse基本用法
    • tensor.copy_()
    • [Python dict() 函数](https://www.runoob.com/python/python-func-dict.html)
    • 什么是状态字典:state_dict?
    • view_as()函数

前言

本文通过阅读《联邦学习实战—杨强》中第3章“用Python实现横向联邦图像分类”入门横向联邦。

核心思想: 使用Python在本地模拟多个客户端,然后由服务器统一管理进行联邦学习,客户端在本地用自己的数据对模型进行训练,服务器将训练结果聚合更新模型并分发给客户端,客户端继续训练。

用横向联邦学习来实现对cifar10图像数据集的分类,模型使用的是ResNet-18。方式为在本地以循环的方式来模拟。

多多调试来理解代码

环境准备

本实验基于Python实现,使用机器学习库PyTorch。

  • Python、PyTorch
  • 编译器使用PyCharm
  • 数据集:cifar10
  • 模型:ResNet-18

基本流程:

  1. 服务器按配置文件生成初始化模型,客户端按照自己的ID将数据集横向不重叠切割
  2. 服务器将全局模型发送给客户端
  3. 客户端接收全局模型(来自服务器)并通过本地多次迭代计算本地参数差值返回给服务器
  4. 服务器聚合各个客户端差值更新模型,再评估当前模型性能
  5. 如果性能未达标,则重复2过程,否则结束

CIFAR10数据集:
在这里插入图片描述
每个批处理文件都包含一个包含以下元素的字典,加起来有6万张图片:

  • 数据 —— 60000x3072(32x32x3)的uint8 numpy数组。数组的每一行存储一个 32x32 彩色图像。前 1024 个条目包含红色通道值,接下来的 1024 个条目包含绿色通道值,最后 1024 个条目包含蓝色通道值。图像以行优先顺序存储,因此数组的前 32 个条目是图像第一行的红色通道值。
  • labels —— 0-9 范围内的 60000 个数字的列表。索引i处的数字表示数组数据中第i个图像的标签。

完整代码

链接: https://pan.baidu.com/s/1PPCd_YB6w537OYif9TQZPg?pwd=s987 提取码: s987

配置文件(conf.json)

联邦训练配置: 一共10台客户端设备(no_models=10),每一轮任意挑选其中的5台参与训练(k=5),每一次本地训练迭代次数为3次(local_epochs=3),全局迭代次数为20次(global_epochs=20)

  • 训练的客户端数量(no_models): 每一轮的选代,服务端会首先从所有的客户端中挑选部分客户端进行本地训练。每一次迭代只选取部分客户端参与,并不会影响全局收敛的效果,且能够提升训练的效率。
  • 全局迭代次数(global_epochs): 即服务端和客户端的通信次数。通常会设置一个最大的全局迭代次数,但在训练过程中,只要模型满足收敛的条件,那么训练也可以提前终止。
  • 本地模型的选代次数(local_epochs):即每一个客户端在进行本地模型训练时的迭代次数。每一个客户端的本地模型的迭代次数可以相同,也可以不同。
  • 本地训练相关的算法配置:本地模型进行训练时的参数设置,如学习率(lr)、训练
    样本大小、使用的优化算法等。
  • 模型信息:即当前任务我们使用的模型结构。在本案例中,我们使用 ResNet-18 图像分类模型。
  • 数据信息:联邦学习训练的数据。在本案例中,我们将使用 cifar10 数据集。为了模拟横向建模,数据集将按样本维度,切分为多份不重叠的数据(本文将5万张训练集图片数据切分为10份,分给10个客户端),每一份(5000张图片)放置在每一个客户端中作为本地训练数据。
  • 其他的配置信息,比如可能使用到的加密方案、是否使用差分隐私、模型是否需要检查点文件(checkpoint )、模型聚合的策略等,都可以根据实际需要自行添加或者修改。

我们将上面的信息以json 格式记录在配置文件中以便修改,如下所示。

conf.json代码:

{"model_name" : "resnet18","no_models" : 10,"type" : "cifar","global_epochs" : 20,"local_epochs" : 3,"k" : 5,"batch_size" : 32,"lr" : 0.001,"momentum" : 0.0001,"lambda" : 0.1
}

获取数据集(datasets.py)

import torch 
from torchvision import datasets, transforms
# 获取数据集
def get_dataset(dir, name):if name=='mnist':# root(这里是dir): 数据路径# train参数表示是否是训练集(True)或者测试集(False)# download=true表示从互联网上下载数据集并把数据集放在root路径中# transform:图像类型的转换# 将图片转化为张量对象。通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,并除以255使得所有像素的数值均在 0 到 1 之间train_dataset = datasets.MNIST(dir, train=True, download=True, transform=transforms.ToTensor())eval_dataset = datasets.MNIST(dir, train=False, transform=transforms.ToTensor())elif name=='cifar':# transforms.Compose(图片预处理步骤)是将多个transform组合起来使用(由transform构成的列表)transform_train = transforms.Compose([transforms.RandomCrop(32, padding=4), # 从图片中随机裁剪出尺寸为size的图片。size: 所需裁剪出的图片尺寸。padding: 设置填充大小,当为a时,上下左右均填充a个像素。transforms.RandomHorizontalFlip(), # 水平翻转(左右翻转图像通常不会改变对象的类别。这是最早且最广泛使用的图像增广方法)transforms.ToTensor(), # 将图片转化为张量对象。通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,并除以255使得所有像素的数值均在 0 到 1 之间# transforms.Normalize: 标准化图像的每个通道。第一个参数: 均值; 第二个参数: 方差# 对于RGB(红、绿、蓝)颜色通道,我们分别标准化每个通道。具体而言,该通道的每个值减去该通道的平均值,然后将结果除以该通道的标准差。transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),])transform_test = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),])train_dataset = datasets.CIFAR10(dir, train=True, download=True,transform=transform_train)eval_dataset = datasets.CIFAR10(dir, train=False, transform=transform_test)return train_dataset, eval_dataset

获取PyTorch中自带深度学习网络预训练模型(models.py)

import torch
from torchvision import modelsdef get_model(name="vgg16", pretrained=True):# 卷积神经网络的训练是耗时的,很多场合不可能每次都从随机初始化参数开始训练网络。# pytorch中自带几种常用的深度学习网络预训练模型,如VGG、ResNet等。往往为了加快学习的进度,在训练的初期我们直接加载pre-train模型中预先训练好的参数if name == "resnet18":# 使用在ImageNet数据集上预训练的ResNet-18作为源模型。指定pretrained=True自动下载预训练的模型参数model = models.resnet18(pretrained=pretrained) elif name == "resnet50":# 使用在ImageNet数据集上预训练的ResNet-50作为源模型。指定pretrained=True自动下载预训练的模型参数model = models.resnet50(pretrained=pretrained)	elif name == "densenet121":model = models.densenet121(pretrained=pretrained)		elif name == "alexnet":model = models.alexnet(pretrained=pretrained)elif name == "vgg16":model = models.vgg16(pretrained=pretrained)elif name == "vgg19":model = models.vgg19(pretrained=pretrained)elif name == "inception_v3":model = models.inception_v3(pretrained=pretrained)elif name == "googlenet":		model = models.googlenet(pretrained=pretrained)if torch.cuda.is_available():return model.cuda()else:return model 

客户端(client.py)

横向联邦学习的客户端的主要功能是接收服务端的下发指令和全局模型(参数),并利用本地数据进行局部模型训练

# 在项目文件夹下创建client.py文件,客户端的主要功能是接受服务器传来的全局模型,并利用本地数据对模型进行训练后返回"差值",包括构造函数、本地训练函数。
import models, torch, copy# 客户端类
class Client(object): # clients.append(Client(conf, server.global_model, train_datasets, c))# 构造函数def __init__(self, conf, model, train_dataset, id = -1):# 读取配置文件self.conf = conf# 根据配置文件获取客户端本地模型(一般由服务器传输)self.local_model = models.get_model(self.conf["model_name"])# 客户端IDself.client_id = id# 客户端本地数据集self.train_dataset = train_dataset# 按ID对数据集集合进行拆分all_range = list(range(len(self.train_dataset))) # all_range列表: 0~49999data_len = int(len(self.train_dataset) / self.conf['no_models']) # 50000/总客户端数量=data_len:5000train_indices = all_range[id * data_len: (id + 1) * data_len] # 将切分数据集,每一份有5000张图片# 若id为0: [0:5000]; 若id为1: [5000, 10000]; 若id为2: [10000,15000],......; 若id为9: [45000, 50000]self.train_loader = torch.utils.data.DataLoader(self.train_dataset, batch_size=conf["batch_size"], sampler=torch.utils.data.sampler.SubsetRandomSampler(train_indices))# SubsetRandomSampler用来打乱数据# 模型本地训练函数def local_train(self, model):# 客户端获取服务器的模型,然后通过部分本地数据集进行训练for name, param in model.state_dict().items():# 用服务器下发的全局模型参数(weight、bias)覆盖本地模型参数(weight、bias)。# 本文服务端、客户端采用的都是ResNet-18模型,所以其实参数时一样的,但是按照代码完整性,还是覆盖一下。self.local_model.state_dict()[name].copy_(param.clone())#print(id(model))# 定义最优化函数器用户本地模型训练optimizer = torch.optim.SGD(self.local_model.parameters(), lr=self.conf['lr'],momentum=self.conf['momentum'])#print(id(self.local_model))# 本地训练模型# 设置开启模型训练,如果模型中有BN层(Batch Normalization)和Dropout,需要在训练时添加local_model.train(),在测试时添加local_model.eval()self.local_model.train()# 开始训练模型,本文每个客户端都是本地训练3次for e in range(self.conf["local_epochs"]):# 每次(共3次)本地训练训练5000张图片,每次下面的循环需要循环157次for batch_id, batch in enumerate(self.train_loader): # self.train_loader有5000张图片,分成了5000/32=157份data, target = batchif torch.cuda.is_available(): # 如果可以的话加载到gpudata = data.cuda()target = target.cuda()optimizer.zero_grad() # 在计算参数的梯度之前,通常需要清零梯度信息。清零模型参数的梯度值output = self.local_model(data) # # 训练预测loss = torch.nn.functional.cross_entropy(output, target) # 计算损失函数cross_entropy交叉熵误差loss.backward() # 利用自动求导函数loss.backward()求出模型中所有参数的梯度信息"loss对权重或偏置求导",这些梯度会自动保存在每个张量的grad成员变量中optimizer.step() # 根据梯度下降算法更新参数。w'=w-lr*grad; b'=b-lr*gradprint(f'客户端: {self.client_id} 的', "Epoch %d done." % e)# 创建差值字典(结构与模型参数同规格),用于记录差值diff = dict()# 此时self.local_model.state_dict()和model.state_dict()不一样了for name, data in self.local_model.state_dict().items(): # ResNet-18有122个层需要更新参数,所以这里执行122次循环(通过调试理解)# 计算训练后与训练前的差值diff[name] = (data - model.state_dict()[name])#print(diff[name])print("客户端 %d 本地训练结束" % self.client_id)#客户端返回差值return diff

服务端(server.py)

横向联邦学习的服务端的主要功能是将被选择的客户端上传的本地模型进行模型聚合(使用FedAvg算法)

import models, torch# 服务器类
class Server(object):def __init__(self, conf, eval_dataset): # 定义构造函数self.conf = conf # 将配置信息拷贝到服务端中self.global_model = models.get_model(self.conf["model_name"]) # 按照配置中的模型信息获取模型,返回类型为model(nn.Module)# self.eval_loader有 313 份测试集batch(32张图片为一份batch)self.eval_loader = torch.utils.data.DataLoader(eval_dataset, batch_size=self.conf["batch_size"], shuffle=True)# 模型聚合函数# weight_accumulator 存储了每个客户端上传参数的变化值def model_aggregate(self, weight_accumulator):# 遍历服务器的全局模型for name, data in self.global_model.state_dict().items(): # ResNet-18有122个层需要更新参数,所以这里执行122次循环(通过调试理解)# 书第44页FedAvg算法公式的右半部分update_per_layer = weight_accumulator[name] * self.conf["lambda"]# 累加if data.type() != update_per_layer.type():# 因为update_per_layer的type是floatTensor,所以将其转换为模型的LongTensor(损失精度)# 这里就是整个书44页的FedAvg算法公式,结果为聚合之后的全局模型的weight、biasdata.add_(update_per_layer.to(torch.int64))else:data.add_(update_per_layer)# 模型评估函数def model_eval(self):# 开启模型评估模式self.global_model.eval()total_loss = 0.0correct = 0dataset_size = 0# 遍历评估数据集合for batch_id, batch in enumerate(self.eval_loader): # self.eval_loader有 313 份测试集batch(32张图片为一份batch)data, target = batch# 获取所有样本总量大小dataset_size += data.size()[0] # data.size()=torch.Size([32, 3, 32, 32])if torch.cuda.is_available(): # 如果可以的话存储到gpudata = data.cuda()target = target.cuda()# 加载到模型中训练output = self.global_model(data) # output的shape为(32, 1000)# 聚合所有损失 cross_entropy 交叉熵函数计算损失total_loss += torch.nn.functional.cross_entropy(output, target,reduction='sum').item() # sum up batch loss# 获取最大的对数概率的索引值,即在所有预测结果中选择可能性最大的作为最终结果# 解读output.data.max(1): 首先output的shape是(32, 1000)。通过output的第二个维度找到每行最大值(共32个),返回是值和索引(通过调试理解)# 解读output.data.max(1)[1]: 提取出"索引"pred = output.data.max(1)[1]  # get the index of the max log-probability# 统计预测结果与真实标签的匹配个数correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()# 计算准确率acc = 100.0 * (float(correct) / float(dataset_size))# 计算总损失值total_l = total_loss / dataset_sizereturn acc, total_l

server.py涉及的调试截图:

  1. output.data.max(1)
    解读output.data.max(1): 首先output的shape是(32, 1000)。通过output的第二个维度找到每行最大值(共32个),返回是值和索引(通过调试理解)
    在这里插入图片描述
  2. 解读output.data.max(1)[1]: 提取出"索引"
    在这里插入图片描述
    验证一下:
    在这里插入图片描述

main.py

import argparse, json
import datetime
import os
import logging
import torch, randomfrom server import *
from client import *
import models, datasetsif __name__ == '__main__':# 设置命令行程序parser = argparse.ArgumentParser(description='Federated Learning')parser.add_argument('-c', '--conf', dest='conf') # argparse默认的变量名是--或-后面的字符串,# 但是你也可以通过dest=xxx来设置参数的变量名,然后在代码中用args.xxx来获取参数的值。args = parser.parse_args()# 终端执行python main.py -c ./utils/conf.json。此时,args.conf = ./utils/conf.json# 这里我使用文件路径地址替换了"arg.conf",可以直接运行main.pywith open('./utils/conf.json', 'r') as f: # args.confconf = json.load(f)	# conf = {"model_name" : "resnet18","no_models" : 10,"type" : "cifar",# "global_epochs" : 20,"local_epochs" : 3,"k" : 5,"batch_size" : 32,# "lr" : 0.001,"momentum" : 0.0001,"lambda" : 0.1}# eval_datasets是"字典",键为"数据"——一个 10000x3072(32*32*3=3072)的uint8 numpy数组,值为"labels" -- 0-9 范围内的 10000 个数字的列表。# conf中的 "type" : "cifar"train_datasets, eval_datasets = datasets.get_dataset("./data/", conf["type"])server = Server(conf, eval_datasets) # Server类实例化。self.eval_loader有 10000/32=313 份测试集batch(32张图片为一份batch)clients = [] # 定义客户端列表# clients列表里面存放10个客户端类的实例for c in range(conf["no_models"]): # c = 0~9。"c"是客户端的id号clients.append(Client(conf, server.global_model, train_datasets, c))print("\n\n")# 全局训练for e in range(conf["global_epochs"]): # e = 0~19print(f"当前是第 {e} 次大循环Global Epoch")# 每次训练从clients列表中随机抽取k个进行训练。candidates也是列表,里面有5个客户端实例candidates = random.sample(clients, conf["k"])print("selected clients are: ")for c in candidates:print('client_id: ', c.client_id)# 累加参数变化。把每一个大循环(global_epochs)的差值加起来weight_accumulator = {}# 初始化空模型参数weight_accumulatorfor name, params in server.global_model.state_dict().items():# 这里务必调试理解!!!# name分别为: 'conv1.weight'、'bn1.weight'、'bn1.bias'等等(调试看看);# params分别为对应各个name的参数值weight_accumulator[name] = torch.zeros_like(params) # 生成一个和参数矩阵大小相同的零矩阵# 遍历选中的客户端,每个客户端本地进行训练for c in candidates:diff = c.local_train(server.global_model)# 根据客户端返回的参数差值字典更新总体权重for name, params in server.global_model.state_dict().items(): # ResNet-18有122个层需要更新参数,所以这里执行122次循环(通过调试理解)weight_accumulator[name].add_(diff[name])# 模型参数聚合server.model_aggregate(weight_accumulator) # 执行完这行代码后,模型全局参数就更新了# 模型评估acc, loss = server.model_eval()print("Epoch %d, acc: %f, loss: %f\n" % (e, acc, loss))

main.py涉及的调试截图:

  1. 初始化candidates列表(从clients列表里随机选择5个)
    在这里插入图片描述

  2. server.global_model.state_dict().items():
    在这里插入图片描述

  3. 初始化字典weight_accumulator
    weight_accumulator[name] = torch.zeros_like(params) # 生成一个和参数矩阵大小相同的零矩阵
    在这里插入图片描述

运行

在PyCharm里直接运行main.py便可

知识点补充

argparse基本用法

  1. 文章 1
  2. 文章 2

tensor.copy_()

tensor.copy_(src)
将src中的元素复制到tensor中并返回这个tensor; 两个tensor应该有相同shape

例子:

x = torch.tensor([[1,2], [3,4], [5,6]])
y = torch.rand((3,2))
print(y)
y.copy_(x)
print(y)

输出:

tensor([[0.1604, 0.0176],[0.3737, 0.2009],[0.1438, 0.8394]])tensor([[1., 2.],[3., 4.],[5., 6.]])
[Finished in 1.9s]

Python dict() 函数

什么是状态字典:state_dict?

在PyTorch中,torch.nn.Module模型的可学习参数(即权重和偏差)包含在模型的参数中,(使用model.parameters()可以进行访问)。 state_dict是Python字典对象,它将每一层映射到其参数张量。注意,只有具有可学习参数的层(如卷积层,线性层等)的模型才具有state_dict这一项。目标优化torch.optim也有state_dict属性,它包含有关优化器的状态信息,以及使用的超参数。

因为state_dict的对象是Python字典,所以它们可以很容易的保存、更新、修改和恢复,为PyTorch模型和优化器添加了大量模块。

下面通过从简单模型训练一个分类器中来了解一下state_dict的使用。

# 定义模型
class TheModelClass(nn.Module):def __init__(self):super(TheModelClass, self).__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.pool = nn.MaxPool2d(2, 2)self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16 * 5 * 5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, 10)def forward(self, x):x = self.pool(F.relu(self.conv1(x)))x = self.pool(F.relu(self.conv2(x)))x = x.view(-1, 16 * 5 * 5)x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return x# 初始化模型
model = TheModelClass()# 初始化优化器
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)# 打印模型的状态字典
print("Model's state_dict:")
for param_tensor in model.state_dict():print(param_tensor, "\t", model.state_dict()[param_tensor].size())# 打印优化器的状态字典
print("Optimizer's state_dict:")
for var_name in optimizer.state_dict():print(var_name, "\t", optimizer.state_dict()[var_name])

输出:

Model's state_dict:
conv1.weight     torch.Size([6, 3, 5, 5])
conv1.bias   torch.Size([6])
conv2.weight     torch.Size([16, 6, 5, 5])
conv2.bias   torch.Size([16])
fc1.weight   torch.Size([120, 400])
fc1.bias     torch.Size([120])
fc2.weight   torch.Size([84, 120])
fc2.bias     torch.Size([84])
fc3.weight   torch.Size([10, 84])
fc3.bias     torch.Size([10])Optimizer's state_dict:
state    {}
param_groups     [{'lr': 0.001, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [4675713712, 4675713784, 4675714000, 4675714072, 4675714216, 4675714288, 4675714432, 4675714504, 4675714648, 4675714720]}]

view_as()函数

函数定义:view_as(tensor) [参数为一个Tensor张量]

该函数的作用是将调用函数的变量,转变为同参数tensor同样的形状。

例如:

data1 = [[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 0], [10, 11]]]
t1 = torch.Tensor(data1).long()  # size=2,3,2
data2 = [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 0], [10, 11]]]
t2 = torch.Tensor(data2).long()
print(t1.size())
print(t2.size())
print("-------view_as()-------")
t2=t2.view_as(t1)
print(t2)
print(t2.size())

输出结果:
在这里插入图片描述
可以看出经过view_as()操作后,t2 Tensor转变为了与t1 相同的形状。(需要重新对t2赋值,这是因为不是进行的原地操作)

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

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

相关文章

域自适应——Bidirectional Learning for Domain Adaptation of Semantic Segmentation

论文题目:Bidirectional Learning for Domain Adaptation of Semantic Segmentation 本文的域位移是针对虚拟数据和真实数据之间的。 本文的贡献是: 提出了一种语义分割的双向学习系统 ,其是一个学习分割适应模型和图像翻译模型的闭环学习系…

nVisual部署之nginx配置说明

Nginx 是一个高性能的HTTP和反向代理web服务器,因此nvisual在部署前端包时便采用了它作为服务器,版本使用1.14.1以上。在默认的配置下,还需要向nginx各模块添加配置才能达到生产需要。 接下来,从http模块开始,再到ser…

谷粒商城1.项目简介和项目环境预搭建(项目概述和环境搭建代码)

一.商城项目总体架构 从讲课篇看 从分块来看 项目知识概述 二.环境搭建代码 1.项目架构 建立父工程 pom文件 <description>聚合服务</description><packaging>pom</packaging><modules><module>gulimall-coupon</module><mo…

第七章 贝叶斯分类器(上)

7.1 贝叶斯决策论 贝叶斯决策论是概率框架下实施决策的基本方法。 假设有N种可能的类别标记&#xff0c;即y{c1,c2,…,cn}&#xff0c;λij是将一个真实标记为cj的样本误分类为ci所产生的损失。基于后验概率P(ci | x)可获得将样本x分类为ci所产生的期望损失&#xff0c;即在样…

从电商到超市,美团的零售之变

从上海回到湖南长沙县的时候&#xff0c;何靓做好了过“苦日子”的准备。作为一个湖南人&#xff0c;她知道县城往往意味着没有星巴克和喜茶&#xff0c;意味着仅有的一两座电影院环境不太好&#xff0c;意味着每天晚上九点后连便利店都大门紧闭。 但在真正回到这“半个故土”…

品牌投资与形象全面升级 | 快来认识全新的 Go 旅城通票

近日&#xff0c;Go 旅城通票&#xff08;Go City&#xff09;品牌全面升级&#xff0c;旨在提高旅游爱好者对品牌的认知。从新冠疫情大流行中阴霾中走出来的 Go 旅城通票复苏势头强劲&#xff0c;专注于技术提升&#xff0c;使命是协助旅游爱好者无论到世界各地的哪一个城市畅…

人工智能前沿——未来AI技术的五大应用领域

关注“PandaCVer”公众号 >>>深度学习Tricks&#xff0c;第一时间送达<<< 目录 一、航空航天 二、医疗保健 三、建筑行业 四、能源领域 五、供应链 关于YOLO算法改进及论文投稿可关注并留言博主的CSDN/QQ >>>一起交流&#xff01;互相学习&…

【千瓜行研】2022年11.11小红书保健品行业数据研报

2022年双十一营销盛会已落下帷幕&#xff0c;小红书平台保健品行业流量连续3年持续走高&#xff0c;热度破亿&#xff01; 本期「千瓜行研」重磅推出《2022年11.11保健品行业数据研报&#xff08;小红书平台&#xff09;》&#xff08;文末附完整版下载&#xff09;&#xff0c…

Android 创建桌面组件Widget——构建应用微件(二)

Android 创建桌面组件Widget——构建应用微件&#xff08;二&#xff09;Android 创建桌面组件Widget——构建应用微件&#xff08;二&#xff09;概览使用 AppWidgetProvider 类接收应用微件广播 Intent固定应用微件设置预览图片完整代码Android 创建桌面组件Widget——构建应…

LVS-DR模式单网段和多网段案例实现

1 实验环境&#xff1a; 一台&#xff1a;客户端 eth0:仅主机 192.168.10.8/24 GW:192.168.10.18一台&#xff1a;ROUTER eth0 :NAT 192.168.100.18/24 eth1: 仅主机 192.168.10.18/24 启用 IP_FORWARD一台&#xff1a;LVS eth0:NAT:DIP:192.168.100.48/24 GW:192.168.100.18两…

大数据必学Java基础(一百零八):过滤器的生命周期

文章目录 过滤器的生命周期 一、构造方法 二、初始化方法 三、拦截请求方法

【AI学习笔记】TensorFlow GPU版本的安装(超详细)

安装步骤&#xff1a;1. 确认显卡是否支持CUDA2. 安装CUDA3. 安装cuDNN3.1 安装 cudnn3.2 将cudnn64_8.dll存放的位置加入Path环境变量4. 安装TensorFlow GPU版本4.1 在Anaconda建立TensorFlow GPU虚拟环境4.2 安装Tensorflow-gpu4.3 安装Keras总结1. 确认显卡是否支持CUDA 在…

供应荧光染料AF532 活性酯,AF532-NHS,CAS:477876-64-5

一&#xff1a;产品描述 1、名称 AF5 532酯 AF532-NHS AF532 活性酯 Alexa Fluor 532 AF532 NHS ester 2、CAS编号&#xff1a;477876-64-5 3、分子式&#xff1a;C34H33N3O11S2 4、分子量&#xff1a;723.77 5、质量控制&#xff1a;95% 6、储存&#xff1a; -20…

Arduino开发实例-DIY酒精浓度检测计

DIY酒精浓度检测计 在本文中,将详细介绍如何创建一个简单的酒精检测器。 它可以在各种应用领域中使用。市场上有许多先进的酒精传感器,价格合理,但我们在这里使用一些基本的微控制器来制作这个项目,如 Arduino、LED、蜂鸣器和 MQ3 酒精传感器。 1、MQ-3传感器介绍 MQ-3传…

A*算法-Python实现

好久没有在CSDN上发文章了&#xff0c;快一年了吧。这两天重新登录了一下&#xff0c;不看不知道&#xff0c;一看吓一跳&#xff0c;没想到访问量快13万了。 之前写博客的时候&#xff0c;想着把一些有用的东西写下来&#xff0c;一方面是当做笔记了&#xff0c;免得以后忘记…

5G无线技术基础自学系列 | SU-MIMO原理

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 通过多天线技术支持单用户在上下行数据…

Vue3框架中路由的使用和局部刷新的功能(第十一课)

使用vue-router的步骤:p第一步&#xff1a;创建路由需要映射的组件&#xff08;打算显示的页面&#xff09;&#xff1b;p第二步&#xff1a;通过createRouter创建路由对象&#xff0c;并且传入routes和history模式&#xff1b;配置路由映射: 组件和路径映射关系的routes数组&a…

DFL3:软件版本的选择和安装详解

这本是一个简单的问题&#xff0c;但是对于新手而言&#xff0c;所有问题&#xff0c;总是说的越清楚越仔细越好。我之所以这么说&#xff0c;肯定是有人问了。所以我就专门开一篇文章来说一说&#xff0c;软件版本的异同&#xff0c;以及如何选择。针对不同的语言&#xff0c;…

mysql相关基础知识篇(五)

1.MySQL 事务的四大特性说一下&#xff1f; 原子性&#xff1a;事务作为一个整体被执行&#xff0c;包含在其中的对数据库的操作要么全部被执行&#xff0c;要么都不执行。一致性&#xff1a;指在事务开始之前和事务结束以后&#xff0c;数据不会被破坏&#xff0c;假如 A 账户…

(2)点云库处理学习——剔除点云值

1、主要参考 1.1参考地址 (1) 点云离群点剔除 — open3d python_Coding的叶子的博客-CSDN博客_离群点去除 (2) open3d之点云异常值去除&#xff08;笔记5&#xff09;_Satellite_H的博客-CSDN博客 (3)斯坦福经典兔子的点云数据下载地址 下载地址 Model : Bunny 1.2兔子…