【PyTorch实战演练】使用CelebA数据集训练DCGAN(深度卷积生成对抗网络)并生成人脸(附完整代码)

文章目录

      • 0. 前言
      • 1. CelebA数据集
        • 1.1 核心特性与规模
        • 1.2 应用与用途
        • 1.3 获取方式
        • 1.4 数据预处理
      • 2. DCGAN的模型构建
        • 2.1 生成器模型
        • 2.2 判别器模型
      • 3. DCGAN的模型训练(重点)
        • 3.1 训练参数
        • 3.2 模型参数初始化
        • 3.3 训练过程
      • 4. 结果展示
        • 4.1 loss值变化过程
        • 4.2 生成图像演化过程
      • 5. 完整代码

0. 前言

按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

本文基于PyTorch构建DCGAN(深度卷积生成对抗网络,Deep Convolutional Generative Adversarial Network),并使用CelebA数据集进行训练,最终使用训练好的生成网络生成人脸数据(图像),完整代码附在文章末尾。

本文的重点是基于PyTorch代码讲解DCGAN模型的构建及训练过程,对于GAN的理论部分将直接跳过,如果对于GAN的基础理论以及转置卷积不太了解,强烈建议先读下以下2篇相关内容:

  • 【硬核科普】一文读懂生成对抗网络GAN
  • 【PyTorch单点知识】深入理解与应用转置卷积ConvTranspose2d模块

本文的写作思路及部分代码借鉴了《PyTorch教程:21个项目玩转PyTorch实战》(北京大学出版社),这真是一本非常不错的书!以及参考了DCGAN的论文 UNSUPERVISED REPRESENTATION LEARNINGWITH DEEP CONVOLUTIONAL GENERATIVE ADVERSARIAL NETWORKS(下面说明以“原文”代表)。

最终DCGAN的生成器通过逐渐学习人脸特征,生成人脸的过程如下:
在这里插入图片描述

我怀着比较激动的心情在写此篇博客,因为2年前(2022)我就花费了较多时间(debug真的令人血压升高)写了一篇DCGAN相关的博客——基于Pytorch用GAN生成手写数字实例(附代码),但是结果不是特别理想:
在这里插入图片描述
在总结了相关经验后终于搞出了正文中的这个比较像样的结果。因为我是使用笔记本电脑(Nvidia RTX 4060 laptop)训练的网络模型,输出图像只能勉强调整到64×64大小。
虽然清晰度较差,但也能明显看出是一位金发美女。

1. CelebA数据集

CelebA 数据集(全称 Large-scale CelebFaces Attributes Dataset)是由香港中文大学多媒体实验室开发并公开提供的一个大规模名人面部属性数据集:
在这里插入图片描述

1.1 核心特性与规模
  • 包含图片数量: CelebA 数据集包含了超过 202,599 张名人面部图像,对应约 10,177 个不同的名人个体。
  • 多样性和复杂性: 数据集中的人脸图像具有丰富的姿态变化和复杂的背景环境,这为模型训练提供了极具挑战性的多样性和现实性,有助于提升模型在真实场景下的泛化能力。
  • 属性标注(本文不会用到): 每张图片均附带了详尽的属性标注,共计 40 个不同的面部属性。
  • 额外信息(本文不会用到): 除了属性标注外,CelebA 还提供了每张图片的人脸检测边界框(bounding box, bbox)、5个关键点(landmarks)的坐标位置,这对于进行精确的人脸对齐和定位等任务非常有用。
1.2 应用与用途

CelebA 数据集由于其规模大、标注详尽且具有多样性,被广泛应用于计算机视觉领域的各种研究和应用开发,主要包括:

  • 人脸识别与属性识别: 用于训练模型识别和分类人脸图像中的特定属性,如性别、发型、表情等。
  • 人脸合成与编辑(本文应用!): 作为训练数据,用于生成新的逼真人脸图像或修改现有图像的特定属性,如使用 GAN(如 DCGAN)进行人脸生成或风格迁移。
  • 人脸检测与对齐: 利用提供的边界框和关键点信息训练或评估人脸检测和关键点定位算法。
  • 深度学习模型验证与基准测试: 作为标准数据集,用于评估新提出的深度学习模型在人脸属性识别、生成或检测任务上的性能。
1.3 获取方式

CelebA 数据集可以从其官方网站直接下载,或者通过官方提供的百度网盘链接获取。由于数据集较大,下载可能需要一定时间。官方网址为:Large-scale CelebFaces Attributes (CelebA) Dataset

1.4 数据预处理

使用ImageFolderDataLoader进行数据预处理:

dataset = torchvision.datasets.ImageFolder(root='Img', transform=transforms.Compose([transforms.Resize(64),transforms.CenterCrop(64),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
]))
# print(len(dataset))  输出202599,总计20万个图像
dataloader = torch.utils.data.DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4, drop_last = True)

如果对预处理方式不太熟悉可以看下往期博客对于这些模块的介绍:ImageFolder、DataLoader、transforms。

2. DCGAN的模型构建

2.1 生成器模型

生成器是一个转置卷积神经网络,由CBR×4+CT串联而成,具体代码如下:

CBR = ConvTranspose+BatchNorm+ReLu
CT = ConvTranspose+Tanh

class Gnet(nn.Module):def __init__(self):super().__init__()self.net = nn.Sequential(nn.ConvTranspose2d(in_channels=noise_size, out_channels=64 * 8, kernel_size=4, stride=1, padding=0,bias=False),nn.BatchNorm2d(64 * 8),nn.ReLU(),# 输出特征图尺寸[64*8, 4, 4]nn.ConvTranspose2d(in_channels=64 * 8, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 4),nn.ReLU(),# 输出特征图尺寸[64*4, 8, 8]nn.ConvTranspose2d(in_channels=64 * 4, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 2),nn.ReLU(),# 输出特征图尺寸[64*2, 16, 16]nn.ConvTranspose2d(in_channels=64 * 2, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64),nn.ReLU(),# 输出特征图尺寸[64, 32, 32]nn.ConvTranspose2d(in_channels=64, out_channels=3, kernel_size=4, stride=2, padding=1, bias=False),nn.Tanh()# 输出特征图尺寸[3, 64, 64])def forward(self, x):return self.net(x)

这里有个细节需要说明下:生成器网络模型最终的激活函数为Tanh(),我原本认为这样并不好,因为Tanh()的取值范围为[-1, 1],这样会导致输出有负数。而负数是无法作为像素值绘图的(因为plt模块接受的像素范围为整数[0, 255]或浮点数[0, 1]),负数的像素值在绘图时会被舍弃。
但是当我尝试把最终的激活函数改为Sigmoid(),训练模型过程中奇怪的现象出现了:生成器和判别器的loss很快收敛为0。这是错误的现象,因为在前文GAN的理论中介绍过:当达到纳什均衡时,判别器的理论输出应该是0.5。判别器的loss必定是一个波动值,不会收敛到0。
对于仅更改最后的激活函数就造成这个现象的机理我还是没太想明白……

2.2 判别器模型

判别器是一个卷积神经元网络,由CL+CBL×3+CS模块构成,具体代码如下:

CL = Conv+LeakyReLu
CBL = Conv+BatchNorm+LeakyReLu
CS = Conv+Sigmoid

class Dnet(nn.Module):def __init__(self):super().__init__()self.net = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 2),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64 * 2, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 4),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64 * 4, out_channels=64 * 8, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 8),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64 * 8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=False),nn.Sigmoid())def forward(self, x):return self.net(x)

3. DCGAN的模型训练(重点)

3.1 训练参数
  • 损失函数:选用BCE(Binary Cross-Entropy,二元交叉熵);
loss = nn.BCELoss()
  • 优化器:生成器和判别器均选用Adams;
opt_G = torch.optim.Adam(G_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))  # 在Adam优化器中,beta1 和 beta2 是两个关键的超参数,分别用于控制梯度一阶动量和二阶动量的衰减速度,进而影响优化器对模型参数的更新方式。
opt_D = torch.optim.Adam(D_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))
  • 迭代次数:epoch设定为20,iteration=202599/128,取整后为1582。
3.2 模型参数初始化

这是非常关键的一步!DCGAN的论文建议需要把权重初始化为均值为0标准差为0.02

原文如下:在这里插入图片描述

代码实现方式为:

def weights_init(m):classname = m.__class__.__name__if classname.find('Conv') != -1:print("init:%s"%classname)nn.init.normal_(m.weight.data, 0.0, 0.02)elif classname.find('BatchNorm') != -1:print("init:%s"%classname)nn.init.normal_(m.weight.data, 1.0, 0.02)nn.init.constant_(m.bias.data, 0)

这里的.apply()函数是PyTorch中的nn.Module类提供的一个方法,用于递归地将指定函数应用到模块及其所有子模块的所有参数或缓冲区上,确保模型在训练开始前具有合理的初始权重分布,有助于模型的收敛和性能表现。

3.3 训练过程

首先需要设定真标签和假标签:

true_label = torch.ones(128).to(device)  #128为batch_size
false_label = torch.zeros(128).to(device)

DCGAN的训练过程即是生成器和判别器的交替训练过程:

  • 判别器训练:

    1. 从真实数据集中抽取一批真实样本。
    2. 生成器G生成一批假样本,使用同样的批次大小,输入为采样的随机噪声。
    3. 将真实样本和生成的假样本混合,组成一个批次的训练数据,并附上对应的标签(真实样本为“真”,生成样本为“假”)。损失值为: l o s s D = B C E ( D ( x d a t a ) , true-label ) + B C E ( D ( G ( z ) ) , false-label ) loss_D = BCE(D(x_{data}), \text{true-label})+ BCE(D(G(z)), \text{false-label}) lossD=BCE(D(xdata),true-label)+BCE(D(G(z)),false-label) x d a t a x_{data} xdata为真实样本, z z z为符合某种分布的随机向量。
    4. 利用这些数据和标签训练判别器D,目标是最大限度地提高D正确区分真实数据和假数据的能力。
  • 生成器训练:

    1. 冻结判别器D的参数,使其在本阶段不更新。
    2. 从噪声分布中再次采样一批新的随机向量作为G的输入。
    3. 生成器G根据这些输入生成假样本,并将它们传递给判别器D。
    4. 此时,G试图欺骗D,即让D认为这些生成的样本是真实的。为此,G的训练目标是最大化D对这些假样本判断为“真”的概率。通常,这等同于最小化D给出的“假”概率,即D的输出值。训练过程中可能使用负的D的输出(或相应的损失函数)作为G的损失来更新G的权重。损失值为: l o s s G = B C E ( D ( G ( z ) ) , true-label ) loss_G = BCE(D(G(z)), \text{true-label}) lossG=BCE(D(G(z)),true-label)

注意:判别器的训练是每隔k步才进行一次的,因为相比于生成器,判别器更容易训练。

4. 结果展示

4.1 loss值变化过程

在这里插入图片描述
其中红色为生成器的loss,绿色为判别器的loss,横坐标为迭代次数iteration。

4.2 生成图像演化过程

我们可以把0~19个epoch的训练权重结果都保存下来:
在这里插入图片描述

随机选取一个batch的4个图像观察生成器从epoch0到epoch20生成图像的演化过程:
在这里插入图片描述

5. 完整代码

  • 训练部分:

文件名:DCGAN_main_revise1,后面验证部分会用到。

import torch
import torchvision
import torch.nn as nn
from torchvision import transforms
import random
from tqdm import tqdm
import matplotlib.pyplot as pltrandom.seed(666)   #设定随机种子
torch.manual_seed(666)dataset = torchvision.datasets.ImageFolder(root='Img', transform=transforms.Compose([transforms.Resize(64),transforms.CenterCrop(64),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
]))
# print(len(dataset))  输出202599,总计20万个图像
dataloader = torch.utils.data.DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4, drop_last = True)noise_size = 100class Gnet(nn.Module):def __init__(self):super().__init__()self.net = nn.Sequential(nn.ConvTranspose2d(in_channels=noise_size, out_channels=64 * 8, kernel_size=4, stride=1, padding=0,bias=False),nn.BatchNorm2d(64 * 8),nn.ReLU(),nn.ConvTranspose2d(in_channels=64 * 8, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 4),nn.ReLU(),nn.ConvTranspose2d(in_channels=64 * 4, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 2),nn.ReLU(),nn.ConvTranspose2d(in_channels=64 * 2, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64),nn.ReLU(),nn.ConvTranspose2d(in_channels=64, out_channels=3, kernel_size=4, stride=2, padding=1, bias=False),nn.Tanh())def forward(self, x):return self.net(x)class Dnet(nn.Module):def __init__(self):super().__init__()self.net = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 2),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64 * 2, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 4),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64 * 4, out_channels=64 * 8, kernel_size=4, stride=2, padding=1, bias=False),nn.BatchNorm2d(64 * 8),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(in_channels=64 * 8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=False),nn.Sigmoid())def forward(self, x):return self.net(x)loss = nn.BCELoss()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
G_NET = Gnet().to(device)
D_NET = Dnet().to(device)opt_G = torch.optim.Adam(G_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))  # 在Adam优化器中,beta1 和 beta2 是两个关键的超参数,分别用于控制梯度一阶动量和二阶动量的衰减速度,进而影响优化器对模型参数的更新方式。
opt_D = torch.optim.Adam(D_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))true_label = torch.ones(128).to(device)
false_label = torch.zeros(128).to(device)def weights_init(m):classname = m.__class__.__name__if classname.find('Conv') != -1:print("init:%s"%classname)nn.init.normal_(m.weight.data, 0.0, 0.02)elif classname.find('BatchNorm') != -1:print("init:%s"%classname)nn.init.normal_(m.weight.data, 1.0, 0.02)nn.init.constant_(m.bias.data, 0)if __name__ == '__main__':G_NET.apply(weights_init)  # 进行权重初始化D_NET.apply(weights_init)epoch = 20for e in tqdm(range(epoch)):for i, (image, label) in enumerate(dataloader):  # image的维度为torch.Size([128, 3, 64, 64])G_NET.zero_grad()noise = torch.randn(128, noise_size, 1, 1, device=device)  # 产生正态分布的噪声fake = G_NET(noise) # 使用生成器产生假数据output = D_NET(fake).view(-1)loss_G = loss(output,true_label)loss_G.backward()loss_G_mean = output.mean().item()opt_G.step()if i%2 == 0:D_NET.zero_grad() #梯度清0real_image = image.to(device)output_real = D_NET(real_image).view(-1)loss_D_real = loss(output_real, true_label)    # 计算真实数据的损失output_fake = D_NET(fake.detach()).view(-1) # 使用判别器,此时将它们标记为假数据loss_D_fake = loss(output_fake, false_label)loss_D = loss_D_real + loss_D_fakeloss_D_mean = loss_D.mean().item()loss_D.backward()opt_D.step()# 输出训练状态if i % 50 == 0:print("epoch:%i/[%i] iter:%i/[1550]"%(e,epoch,i),'|',"loss_D:%f"%loss_D_mean,'|',"loss_G:%f"%loss_G_mean)iter = e*1550+iplt.scatter(iter,loss_G_mean, c='r',s = 5)plt.scatter(iter,loss_D_mean, c = 'g',s = 5)torch.save(obj=G_NET.state_dict(),f='weight/G_Net_parameter_%i_epoch.pth'%e)torch.save(obj=D_NET.state_dict(), f='weight/D_Net_parameter_%i_epoch.pth' % e)plt.show()
  • 验证部分:
from DCGAN_main_revise1 import Gnet
import torch
import matplotlib.pyplot as plt
import randomrandom.seed(888)   #设定随机种子
torch.manual_seed(888)Gnet_test = Gnet()
noise = torch.randn(128, 100, 1, 1)for i in range(20):Gnet_test.load_state_dict(torch.load('weight/G_Net_parameter_%i_epoch.pth'%i))output = Gnet_test(noise)output = output.detach()concate1 = torch.concat((output[0].permute(1,2,0),output[2].permute(1,2,0)),dim=1)concate2 = torch.concat((output[7].permute(1, 2, 0), output[6].permute(1, 2, 0)), dim=1)concate3 = torch.concat((concate1, concate2), dim=0)plt.imshow(concate3)plt.savefig('output_images/composed_%i'%i)

以上,终于写完了~

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

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

相关文章

Linux —— 进程间通信

目录 一、进程间通信的介绍二、管道三、匿名管道四、命名管道五、system V进程间通信 一、进程间通信的介绍 1.进程间通信的概念 进程通信(Interprocess communication),简称:IPC; 本来进程之间是相互独立的。但是…

Elasticsearch的基本使用

Elasticsearch的基本使用 1.基本概念1.1 文档和字段1.2 索引和映射1.3 mysql与elasticsearch对比 2.索引库2.1 es中mapping映射属性2.2.es中索引库的增删改查 3.文档3.1 新增文档3.2 查询文档3.3 删除文档3.4 修改文档3.4.1 全量修改3.4.2 增量修改3.5 总结 4.DSL查询语法4.1 D…

【LLM第三篇】名词解释:RLHF——chatgpt的功臣

RLHF (Reinforcement Learning from Human Feedback) ,直译为:“来自人类反馈的强化学习”。RLHF是一种结合了强化学习和人类反馈的机器学习方法,主要用于训练大模型以执行复杂的任务,尤其是当这些任务难以通过传统的奖励函数来精…

CCF-Csp算法能力认证, 202303-1重复局面(C++)含解析

前言 推荐书目,在这里推荐那一本《算法笔记》(胡明),需要PDF的话,链接如下 「链接:https://pan.xunlei.com/s/VNvz4BUFYqnx8kJ4BI4v1ywPA1?pwd6vdq# 提取码:6vdq”复制这段内容后打开手机迅雷…

大语言模型LLM入门篇

大模型席卷全球,彷佛得模型者得天下。对于IT行业来说,以后可能没有各种软件了,只有各种各样的智体(Agent)调用各种各样的API。在这种大势下,笔者也阅读了很多大模型相关的资料,和很多新手一样&a…

深圳CPDA|如何利用数据分析改进业务流程,提高效率?

在当今数字化时代,数据已经成为企业决策和优化的关键资源。通过有效地收集、分析和应用数据,企业可以深入了解其业务流程中的瓶颈和问题,从而改进流程,提高效率。本文将探讨如何利用数据分析改进业务流程,并提高效率。…

Vue3+vite优化基础架构(3)--- 优化vue-i18n国际化配置

Vue3vite优化基础架构(3)--- 优化vue-i18n国际化配置 说明全部页面进行中英文使用测试中英文切换对ElementPlus里面的所有组件进行中英文切换 说明 这里记录下自己在Vue3vite的项目增加全局中英文切换按钮对页面进行中英文切换及同时对ElementPlus里面的…

练习题(2024/5/9)

1删除二叉搜索树中的节点 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 一般来说,删除节点可分为…

融知财经:期货在哪里可以交易?期货交易有哪些交易规则?

作为当前金融市场的一种投资方式,期货只适合一些投资者,比如想获得高收益的投资者,因为期货的风险系数很高。但是很多投资者还不知道期货的意思,在一个固定的交易场所,期货是买卖标准化商品或金融资产的远期合约的交易…

RK3568 学习笔记 : u-boot 下通过设置 env ethact 设置当前工作的以太网设备

前言 正点原子 :RK3568 开发板 atompi-ca1 默认有两个网口,通过 u-boot mii 命令,可以查看 网口信息 > mii device MII devices: ethernetfe010000 ethernetfe2a0000 Current device: ethernetfe010000u-boot 下的以太网,不同…

HA-MAc,透明质酸-甲基丙烯酸酯可用于制备具有交联能力的透明质酸基材料

【基本信息】 Hyaluronate Methacrylate(甲基丙烯酸酯化透明质酸,简称HA-MAc)是一种重要的生物材料 中文名称:甲基丙烯酸酯化透明质酸、透明质酸-甲基丙烯酸酯 英文名称:Hyaluronate Methacrylate、HA-MAc 分子量&…

python代码自动生成器原理 python 生成器原理

python生成器原理剖析 函数的调用满足“后进先出”的原则,也就是说,最后被调用的函数应该第一个返回,函数的递归调用就是一个经典的例子。显然,内存中以“后进先出”"方式处理数据的栈段是最适合用于实现函数调用的载体&…

使用Maven对Scala独立应用程序进行编译打包

一、 安装Maven 1.解压,移动安装包 sudo tar -zxf ~/apache-maven-3.9.6-bin.tar.gz -C /usr/local/ cd /usr/local/ sudo mv apache-maven-3.9.6/ ./maven-3.9.6 sudo chown -R qiangzi ./maven-3.9.6 二、Scala应用程序代码 1.在终端中执行如下命令创建一个文…

【C++】C++11--- lambda表达式

目录 Lambda表达式概述 Lambda表达式语法定义 Lambda表达式参数详解 Lambda捕获列表 捕获列表总结 Lambda参数列表 可变规则mutable lambda表达式原理 Lambda表达式概述 当对自定义类型的数据集合进行排序时,需要根据自定义类型的不同属性去实现不同的排序方…

百病之源,根在肝脏!4种养肝法,助您对症养肝,越养越健康~

如今生活节奏比较快,人们的身体和精神都承受着巨大的压力,熬夜加班、喝酒应酬、通宵上网等,这些习惯都在悄悄损耗我们的肝脏,使得大家长期处于亚健康的边缘! 中医讲,百病之源,根在肝脏。肝不好…

二总线,替代传统485总线通讯,主站设计

二总线通信设计专栏 《二总线,替代传统485总线通讯,选型及应用-CSDN博客》《二总线,替代传统485总线通讯,低成本直流载波方案实现及原理-CSDN博客》《二总线,替代传统485总线通讯,调试避坑指南之最大的电流…

深度学习:基于TensorFlow 和 Keras,使用神经网络回归模型预测 IPL 分数

前言 系列专栏:机器学习:高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目,每个项目都处理一组不同的问题,包括监督和无监督学习、分类、回归和聚类,而且涉及创建深度学…

Cesium 问题:billboard 加载未出来

文章目录 问题分析问题 接上篇 Cesium 展示——图标的依比例和不依比例缩放,使用加载 billboard 时,怀疑是路径的原因导致未加载成功 分析 原先

怎样把excel表格转换成图片格式?学会这3个Excel小技巧,表格操作不求人,工作效率翻倍

一,前言 excel是办公必备的表格处理软件,每个表格都包含大量的数据和函数逻辑关系,牵一发而动全身。传输excel表格时可以将文件转换成图片或者pdf,这样有利于传输,而且不会改变表格原有的格式。那么怎样才能把excel转…