pytorch实战11:基于pytorch简单实现DCGAN

news2025/4/7 21:38:40

基于pytorch简单实现DCGAN

前言

​ 最近会把一些简单的CV领域的架构进行复现,完整的代码在最后。

本系列必须的基础

​ python基础知识、CNN原理知识、pytorch基础知识

本系列的目的

​ 一是帮助自己巩固知识点;

​ 二是自己实现一次,可以发现很多之前的不足;

​ 三是希望可以给大家一个参考。

参考资料

GitHub项目:
https://github.com/znxlwm/pytorch-MNIST-CelebA-GAN-DCGAN
文章:
https://zhuanlan.zhihu.com/p/48501100  --- 反卷积计算公式推导
https://blog.csdn.net/qq_41605740/article/details/127816320 --- BCELoss介绍
https://blog.csdn.net/m0_62128864/article/details/123935874 --- DCGAN实现

目录结构

文章目录

    • 基于pytorch简单实现DCGAN
      • 1. 前言
      • 2. 生成器和判别器实现
      • 3. 训练前的准备
      • 4. 实现训练过程
      • 5. 结果分析
      • 6. 总结

1. 前言

​ 前一篇基于pytorch实现了CGAN,但是效果不是很好。于是打算试试DCGAN,因为相比于CGAN,DCGAN更加强大,而且两者整体代码差不多,也就是架构采用的不同。

​ 必须一提的是,DCGAN不是D + CGAN,DCGAN是Deep Convolution GAN,而CGAN中的C是conditional。

​ 另外,前一篇,将代码分开写在不同文件夹中,感觉有点没必要,因为代码量其实不多。这里就直接放一个文件中了。

​ 我的目录结构:

---- DCGAN
	---- fake_images    	# 用于保存生成器生成图片的文件夹
	---- pytorch_dcgan.py   # 主要代码文件
---- data
	---- mnist    # MNIST数据集文件夹

2. 生成器和判别器实现

​ 首先,贴一张图,来自参考资料中GitHub项目的图片:

在这里插入图片描述

​ 这张图就是生成器和判别器的主要架构图。其中需要注意的几点如下:

  • 生成器全由反卷积实现,最终输出为图像大小
  • 由于MNIST图像为灰度图,因此维度只有1
  • 判别器最终输出一个值,并且使用sigmoid将值限定为0到1之间,表示这个输入图像为真实or假的概率值
  • 上图中绿色部分就是卷积核
  • 值得注意的是,作者似乎把原来28*28*1的MNIST图像转为了64*64*1,目的应该是为了方便生成器中反卷积的尺寸设计等

​ 一般来说,有了上面的图,实现起来就非常简单了。这里先来实现判别器,因为判别器没有用到反卷积,用的都是平常常见的东西:(看注释)

# 判别器
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            # 按照图片上的实现即可
            nn.Conv2d(1,128,kernel_size=4,stride=2,padding=1),
            # 这里0.2是图片中采取的值
            nn.LeakyReLU(0.2),
            nn.Conv2d(128,256,4,2,1),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2),
            nn.Conv2d(256, 512, 4, 2, 1),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2),
            nn.Conv2d(512, 1024, 4, 2, 1),
            nn.BatchNorm2d(1024),
            nn.LeakyReLU(0.2),
            # 最终输出1不要忘记即可
            nn.Conv2d(1024, 1, 4, 2, 0),
            # 加上一个sigmoid,控制输出到0-1
            nn.Sigmoid(),
        )

    def forward(self,x):
        result = self.model(x)
        return result

​ 接着,来实现生成器,由于生成器采用了反卷积,这里放一下反卷积常用的计算输出尺寸公式:

在这里插入图片描述

​ 以上图为例,第一个反卷积:(上图已经给出了输出尺寸、通道数、卷积核大小、步长,但是没有给出padding值,所以需要自己计算一下

输入尺寸为: 1*1,维度为100
卷积核:4*4,s=1
输出尺寸目标为: 4*4*1024

所以,输出通道数为1024
根据公式:4 = 1*(1-1)-2*p+4,得到p=0

​ 根据上述计算过程,第一个反卷积应该这么写:

nn.ConvTranspose2d(100,1024,4,1,0),
# 对应参数:输入通道数、输出通道数、卷积核大小、步长、padding值

​ 那么,依次计算,可以完成生成器的构建:

# 生成器
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            # 输入通道、输出通道、卷积核、步长、padding
            nn.ConvTranspose2d(100,1024,4,1,0),
            nn.BatchNorm2d(1024),
            nn.ReLU(),
            nn.ConvTranspose2d(1024, 512, 4, 2, 1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.ConvTranspose2d(512, 256, 4, 2, 1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.ConvTranspose2d(256, 128, 4, 2, 1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            # 最后,输出通道数为1,是因为为灰度图
            nn.ConvTranspose2d(128, 1, 4, 2, 1),
            nn.Tanh(),
        )

    def forward(self,x):
        result = self.model(x)
        return result

3. 训练前的准备

​ 现在来完成训练前的准备工作,即定义基本参数、优化器、损失函数等内容。

​ 首先,定义采用的设备:(单GPU)

# 设备
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

​ 然后,定义训练多少次,batch大小、初始学习率和损失函数:

# batch大小、epoch次数、初始学习率、损失函数
batch_size = 128
epoch = 200
lr = 0.002
loss = nn.BCELoss()

​ 这里,需要简单介绍一下BCELoss这个损失函数,该函数叫做二值交叉熵损失函数,一看就知道是用于二值分类的。感兴趣的可以看看参考资料中的介绍。

​ 接着,创建模型、定义优化器:

# 模型创建
D = Discriminator().to(device)
G = Generator().to(device)
# 优化器
optim_G = optim.Adam(G.parameters(),lr=lr,betas=(0.5, 0.999))
optim_D = optim.Adam(D.parameters(),lr=lr,betas=(0.5, 0.999))

​ 最后,定义一下数据加载器,由于我们用的是MNIST数据集,而pytorch官方已经实现了这个数据集的加载,所以可以很简单的实现。但是,注意的是,模型要求输入大小为64*64,所以还需要定义一下预处理方法:

# 预处理方法
transforms_func = transforms.Compose(
    [transforms.Resize(64), # 缩放到64*64
     transforms.ToTensor(), # 转为tensor
     transforms.Normalize(mean=0.5,std=0.5)] # 归一化处理
)
# 加载数据
dataset = MNIST('../data/mnist', # 确定自己加载的路径或者要加载保存的路径
                train=True,
                transform=transforms_func,
                download=False)  # 这个参数,根据自己有没有数据集确定
train_loader = DataLoader(dataset,batch_size=batch_size,shuffle=True,drop_last=True)

4. 实现训练过程

​ 训练过程,其实和CGAN训练过程一样,同时也是按照原始GAN定义的训练过程进行训练的,即先训练一步D、在训练G,重复上述训练过程即可。

​ 首先,定义一个循环:

# 开始训练
for e in range(epoch):
	pass

​ 那么,接着,需要定义调整学习率的方法,这里采取的思路是训练到指定epoch次数,调整一次学习率:

# 开始训练
for e in range(epoch):
    # 调整学习率
    if e+1 == 50:
        optim_G.param_groups[0]['lr'] /= 10
        optim_D.param_groups[0]['lr'] /= 10
        print('学习率改变')
    if e+1 == 100:
        optim_G.param_groups[0]['lr'] /= 10
        optim_D.param_groups[0]['lr'] /= 10
        print('学习率改变')

​ 然后,再定义一个循环,开始定义真正的训练过程:

# 开始训练
for e in range(epoch):
    # 调整学习率
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
		pass

​ 首先,需要定义两个变量,一个全为1,一个全为0,用于与判别器的输出计算损失值:

# 开始训练
for e in range(epoch):
    # 调整学习率
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 创建标签张量,一个全为1,一个全为0
        batch = batch_img.size()[0]
        label_real = torch.ones(batch, requires_grad=True)
        label_fake = torch.zeros(batch, requires_grad=True)

​ 然后,把这些值放入GPU中:

# 开始训练
for e in range(epoch):
    # 调整学习率
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 创建标签张量,一个全为1,一个全为0
        batch = batch_img.size()[0]
        label_real = torch.ones(batch, requires_grad=True)
        label_fake = torch.zeros(batch, requires_grad=True)
        # 放入GPU中
        batch_img, batch_label, label_fake, label_real = batch_img.to(device), batch_label.to(device), label_fake.to(
            device), label_real.to(device)

​ 下面,开始训练判别器D:

# 开始训练
for e in range(epoch):
    # 调整学习率
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 定义参数
        ......
        # 优化D
        optim_D.zero_grad()
        # 先喂真实图像
        d_result_real = D(batch_img).squeeze() # 把结果拉平
        d_real_loss = loss(d_result_real,label_real)
        # 再喂假的图像
        # 创建噪声向量
        z = torch.randn((batch,100),requires_grad=True).view(-1,100,1,1) # 展开为[batch,100,1,1]
        z = z.to(device)
        g_result = G(z)
        d_result_fake = D(g_result).squeeze()
        d_fake_loss = loss(d_result_fake,label_fake)
        all_loss = d_fake_loss + d_real_loss # 两个损失相加,同时反向传播
        all_loss.backward()
        optim_D.step()

​ 接着,训练G:

# 开始训练
for e in range(epoch):
    # 调整学习率
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 定义参数
        ......
        # 优化D
        ......
        # 开始训练G
        optim_G.zero_grad()
        # 噪声向量
        z = torch.randn((batch, 100), requires_grad=True).view(-1, 100, 1, 1)  # 展开为[batch,100,1,1]
        z = z.to(device)
        g_result = G(z)
        d_result = D(g_result).squeeze()
        g_loss = loss(d_result,label_real)
        g_loss.backward()
        optim_G.step()

​ 在完成了训练D、G后,打印本次训练的损失函数值:

# 开始训练
for e in range(epoch):
    # 调整学习率
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 定义参数
        ......
        # 优化D
        ......
        # 开始训练G
        ......
        # 每个batch训练完毕后,打印损失
        print('epoch {%d},batch {%d},g_loss:%.5f,d_loss:%.5f' % (e+1,i+1,g_loss.item(),all_loss.item()))

​ 而在完成每epoch的训练后,用生成器生成一张图片并保存下来,用于后期分析结果:

# 开始训练
for e in range(epoch):
    ......
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        ......
	# 训练一个epoch,用生成器生成图片,并保存
    z = torch.randn((5*5, 100), requires_grad=True).view(-1, 100, 1, 1)  # 展开为[batch,100,1,1]
    z = z.to(device)
    gen_imgs = G(z)
    # 路径自己改
    save_image(gen_imgs.data, "fake_images1/%d.png" % (e + 1), nrow=10, normalize=True)

完整的训练代码

# 开始训练
for e in range(epoch):
    # 调整学习率
    if e+1 == 50:
        optim_G.param_groups[0]['lr'] /= 10
        optim_D.param_groups[0]['lr'] /= 10
        print('学习率改变')
    if e+1 == 100:
        optim_G.param_groups[0]['lr'] /= 10
        optim_D.param_groups[0]['lr'] /= 10
        print('学习率改变')
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 创建标签张量,一个全为1,一个全为0
        batch = batch_img.size()[0]
        label_real = torch.ones(batch, requires_grad=True)
        label_fake = torch.zeros(batch, requires_grad=True)
        # 放入GPU中
        batch_img, batch_label, label_fake, label_real = batch_img.to(device), batch_label.to(device), label_fake.to(
            device), label_real.to(device)
        # 优化D
        optim_D.zero_grad()
        # 先喂真实图像
        d_result_real = D(batch_img).squeeze() # 把结果拉平
        d_real_loss = loss(d_result_real,label_real)
        # 再喂假的图像
        # 创建噪声向量
        z = torch.randn((batch,100),requires_grad=True).view(-1,100,1,1) # 展开为[batch,100,1,1]
        z = z.to(device)
        g_result = G(z)
        d_result_fake = D(g_result).squeeze()
        d_fake_loss = loss(d_result_fake,label_fake)
        all_loss = d_fake_loss + d_real_loss # 两个损失相加,同时反向传播
        all_loss.backward()
        optim_D.step()
        # 开始训练G
        optim_G.zero_grad()
        # 噪声向量
        z = torch.randn((batch, 100), requires_grad=True).view(-1, 100, 1, 1)  # 展开为[batch,100,1,1]
        z = z.to(device)
        g_result = G(z)
        d_result = D(g_result).squeeze()
        g_loss = loss(d_result,label_real)
        g_loss.backward()
        optim_G.step()

        # 每个batch训练完毕后,打印损失
        print('epoch {%d},batch {%d},g_loss:%.5f,d_loss:%.5f' % (e+1,i+1,g_loss.item(),all_loss.item()))
    # 训练一个epoch,用生成器生成图片,并保存
    z = torch.randn((5*5, 100), requires_grad=True).view(-1, 100, 1, 1)  # 展开为[batch,100,1,1]
    z = z.to(device)
    gen_imgs = G(z)
    # 路径自己改
    save_image(gen_imgs.data, "fake_images1/%d.png" % (e + 1), nrow=10, normalize=True)

5. 结果分析

​ 由于网络架构相比CGAN大了很多,因此我的个人电脑跑起来很慢,我计算了下,一个epoch大概需要5-8分钟才跑完,那么跑完目标的200个epoch需要16-26个小时左右。这是我个人没有办法接受的,而且电脑用了几年了,散热很垃圾。

​ 所以,我减少了epoch次数,改为了50个epoch,同时我将网络架构按比例缩小了,最终的运行结果如下图所示:

在这里插入图片描述

6. 总结

​ 虽然相比于CGAN的结果来说,DCGAN的结果却是更加好一点,这里的好我个人认为是结构上的胜利,因为DCGAN采取的结构更大,所以取得的结果比较好。即使训练的批次很小,结果也不错。

​ 但是,同样发下一个问题,就是训练仍然不稳定,有时候训练的好好的,但是突然生成器损失值会增大很多,如下图所示:
在这里插入图片描述

​ 综上,感觉想取得好结果,一是从结构上入手,二是尽量控制稳定性。

完整代码

# author: baiCai
# DCGAN --- MNIST --- pytorch

# 导包
import torch
from torch import nn
from torch.utils.data import DataLoader
from torch.nn import functional as F
from torch import optim
from torchvision.utils import save_image
from torchvision import transforms
from torchvision.datasets import MNIST

# 说明: 这里的d是后面添加的,用于控制模型的规模
# 生成器
class Generator(nn.Module):
    def __init__(self,d=128):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            # 输入通道、输出通道、卷积核、步长、padding
            nn.ConvTranspose2d(100,d*8,4,1,0),
            nn.BatchNorm2d(d*8),
            nn.ReLU(),
            nn.ConvTranspose2d(d*8, d*4, 4, 2, 1),
            nn.BatchNorm2d(d*4),
            nn.ReLU(),
            nn.ConvTranspose2d(d*4, d*2, 4, 2, 1),
            nn.BatchNorm2d(d*2),
            nn.ReLU(),
            nn.ConvTranspose2d(d*2, d, 4, 2, 1),
            nn.BatchNorm2d(d),
            nn.ReLU(),
            # 最后,输出通道数为1,是因为为灰度图
            nn.ConvTranspose2d(d, 1, 4, 2, 1),
            nn.Tanh(),
        )

    def forward(self,x):
        result = self.model(x)
        return result


# 判别器
class Discriminator(nn.Module):
    def __init__(self,d=128):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            # 按照图片上的实现即可
            nn.Conv2d(1,d,kernel_size=4,stride=2,padding=1),
            # 这里0.2是图片中采取的值
            nn.LeakyReLU(0.2),
            nn.Conv2d(d,d*2,4,2,1),
            nn.BatchNorm2d(d*2),
            nn.LeakyReLU(0.2),
            nn.Conv2d(d*2, d*4, 4, 2, 1),
            nn.BatchNorm2d(d*4),
            nn.LeakyReLU(0.2),
            nn.Conv2d(d*4, d*8, 4, 2, 1),
            nn.BatchNorm2d(d*8),
            nn.LeakyReLU(0.2),
            # 最终输出1不要忘记即可
            nn.Conv2d(d*8, 1, 4, 2, 0),
            # 加上一个sigmoid,控制输出到0-1
            nn.Sigmoid(),
        )

    def forward(self,x):
        result = self.model(x)
        return result


# 定义基本参数
# 设备
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
# batch大小、epoch次数、初始学习率、损失函数
batch_size = 128
epoch = 200
lr = 0.002
loss = nn.BCELoss()
# 模型创建
D = Discriminator().to(device)
G = Generator().to(device)
# 优化器
optim_G = optim.Adam(G.parameters(),lr=lr,betas=(0.5, 0.999))
optim_D = optim.Adam(D.parameters(),lr=lr,betas=(0.5, 0.999))
# 预处理方法
transforms_func = transforms.Compose(
    [transforms.Resize(64), # 缩放到64*64
     transforms.ToTensor(), # 转为tensor
     transforms.Normalize(mean=0.5,std=0.5)] # 归一化处理
)
# 加载数据
dataset = MNIST('../data/mnist', # 确定自己加载的路径或者要加载保存的路径
                train=True,
                transform=transforms_func,
                download=False)  # 这个参数,根据自己有没有数据集确定
train_loader = DataLoader(dataset,batch_size=batch_size,shuffle=True,drop_last=True)
# 开始训练
for e in range(epoch):
    # 调整学习率
    if e+1 == 50:
        optim_G.param_groups[0]['lr'] /= 10
        optim_D.param_groups[0]['lr'] /= 10
        print('学习率改变')
    if e+1 == 100:
        optim_G.param_groups[0]['lr'] /= 10
        optim_D.param_groups[0]['lr'] /= 10
        print('学习率改变')
    # 开始真正的训练
    for i,(batch_img,batch_label) in enumerate(train_loader):
        # 创建标签张量,一个全为1,一个全为0
        batch = batch_img.size()[0]
        label_real = torch.ones(batch, requires_grad=True)
        label_fake = torch.zeros(batch, requires_grad=True)
        # 放入GPU中
        batch_img, batch_label, label_fake, label_real = batch_img.to(device), batch_label.to(device), label_fake.to(
            device), label_real.to(device)
        # 优化D
        optim_D.zero_grad()
        # 先喂真实图像
        d_result_real = D(batch_img).squeeze() # 把结果拉平
        d_real_loss = loss(d_result_real,label_real)
        # 再喂假的图像
        # 创建噪声向量
        z = torch.randn((batch,100),requires_grad=True).view(-1,100,1,1) # 展开为[batch,100,1,1]
        z = z.to(device)
        g_result = G(z)
        d_result_fake = D(g_result).squeeze()
        d_fake_loss = loss(d_result_fake,label_fake)
        all_loss = d_fake_loss + d_real_loss # 两个损失相加,同时反向传播
        all_loss.backward()
        optim_D.step()
        # 开始训练G
        optim_G.zero_grad()
        # 噪声向量
        z = torch.randn((batch, 100), requires_grad=True).view(-1, 100, 1, 1)  # 展开为[batch,100,1,1]
        z = z.to(device)
        g_result = G(z)
        d_result = D(g_result).squeeze()
        g_loss = loss(d_result,label_real)
        g_loss.backward()
        optim_G.step()

        # 每个batch训练完毕后,打印损失
        print('epoch {%d},batch {%d},g_loss:%.5f,d_loss:%.5f' % (e+1,i+1,g_loss.item(),all_loss.item()))
    # 训练一个epoch,用生成器生成图片,并保存
    # 可以改变5*5,表示生成多数个图片
    z = torch.randn((5*5, 100), requires_grad=True).view(-1, 100, 1, 1)  # 展开为[batch,100,1,1]
    z = z.to(device)
    gen_imgs = G(z)
    # 路径自己改
    save_image(gen_imgs.data, "fake_images1/%d.png" % (e + 1), nrow=10, normalize=True)
# # 保存一下权值:是否保存取决于自己,另外需要注意路径哦
# torch.save(D.state_dict(),'./save_weights/D.pkl')
# torch.save(G.state_dict(),'./save_weights/G.pkl')

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

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

相关文章

ZED使用指南(五)Camera Controls

六、其他 1、相机控制 (1)选择视频模式 左右视频帧同步,以并排格式作为单个未压缩视频帧流式传输。 在ZED Explorer或者使用API可以改变视频的分辨率和帧率。 (2)选择输出视图 ZED能以不同的格式输出图像&#xf…

Android系统原理性问题分析 - Android Native程序的结构设计方式

Android核心原理 5.3 声明 在Android系统中经常会遇到一些系统原理性的问题,在此专栏中集中来讨论下。Android系统主要由Java和C/C两个世界构成,此篇分析处于Java世界和C/C世界的两个进程如何实现进程间通信的问题。此篇参考一些博客和书籍&#xff0c…

Go程序设计语言翻译问题(goroutine)

中文:Go程序设计语言 2017.1 英文:The Go Programming Language 2016 8.4.2. Pipelines 8.4.2管道章节 修正: 第一个管道应该改成通道,cannel是概念词汇 重要概念词汇不能混淆 来自chatGPT3.5: Go Pipelines和channel…

(kubernetes yaml文件|--dry-run导出yaml文件

kubernetes yaml文件|--dry-run导出yaml文件 YAML 语法格式:二 查看 api 资源版本标签三 写一个nignx.yaml文件demo四、编写service服务的资源清单详解k8s中的port五 用–dry-run命令生成yaml资源清单六 将现有的资源生成模板导出写yaml太累怎么办? YAML…

【Linux】1. Linux常见指令

专栏导读 🍁作者简介:余悸,在读本科生一枚,致力于 C方向学习。 🍁收录于 C 专栏,本专栏主要内容为 C 初阶、 C 进阶、 STL 详解等,持续更新中! 🍁相关专栏推荐&#xff1…

社会关系抽取赛题提交指南

社会关系抽取赛题提交指南 一、赛题背景 本次主要为大家介绍社会科学计算大赛的赛题之一---社会关系抽取。 1.技术发展 关系抽取(Relationship Extraction, RE)是信息抽取的一个重要任务,其目标是从文本中抽取实体之间的关系。RE技术发展历程主要有以下几个阶段:1. 规则与模…

初窥机器视觉与卷积神经网络

文章目录 1. 什么是卷积神经网络2. 卷积运算3. 池化层4. 卷积神经网络的整体架构 1. 什么是卷积神经网络 卷积神经网络(convolutional neural network,CNN)主要用于计算机视觉相关任务,但处理对象并不局限于图像,CNN在序列和语音等上的应用也…

2023年陕西省《网络建设与运维》技能大赛试题

项目简介: 某集团公司原在北京建立了总公司,后在成都建立了分公司,广东设立了办事处。集团设有产品、营销、法务、财务、人力5个部门,全网采用OSPF、RIP、ISIS、BGP路由协议进行互联互通。集团在北京建立两个数据中心,贵州建立异地灾备数据中心。 公司网络拓扑如图1所示,…

功能要进阶自动化测试,你必须要了解的十大自动化测试框架

什么是自动化测试框架? 自动化测试框架,即是应用于自动化测试所用的框架。按照框架的定义,自动化测试框架要么是提供可重用的基础自动化测试模块,如:selenium 、watir等,它们主要提供最基础的自动化测试…

【Python入门篇】——Python中循环语句(while循环的嵌套应用,嵌套案例)

作者简介: 辭七七,目前大一,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: Python入门,本专栏主要内容为Python的基础语法,Python中的选择循环语句…

监控系统经典架构详解

要了解一个监控系统那么我们就先要了解他的架构,看看监控系统是由哪些模块组成,各个模块是如何相互协调。我们将众多主流监控系统架构进行逻辑抽象和概括。 典型架构 从上图(监控系统经典架构图)来看,从左往右&#x…

Android RecyclerView实现吸顶动态效果

文章目录 一、ItemDecoration二、实现RecyclerView吸顶效果1、实现一个简单的RecyclerView2、通过ItemDecoration画分割线3、画出每个分组的组名4、实现吸顶效果 完整demo 链接:https://download.csdn.net/download/JasonXu94/87786702 一、ItemDecoration ItemDecoration 允…

python实现学生成绩管理程序,包含各科成绩的录入,计算各个学生的总分和平均分,统计各个科目的最高分、最低分和平均分。

一、编程题目 编程题目:使用python实现学生各科成绩的录入,计算各个学生的总分和平均分,统计各个科目的最高分、最低分和平均分。其中学生人数通过输入来决定,科目包括语文、数学和英语这三门课程。(保留一位小数&…

JavaEE 5 (4/28)

1.wait() 和notify() 调用wait后做的三件事 1.释放锁 2.等待其他线程通知 3.收到通知后重新上锁,继续执行 要想实用wait和notify就得搭配synchronized 获取锁 wait哪个对象就要针对哪个对象加锁 Java中线程是随机抢占式执行的,实际上线程的执行我们一定要有一个顺序. join可以…

阿里云服务器可以做什么?十大使用场景举例说明

使用阿里云服务器可以做什么?阿里云百科分享使用阿里云服务器常用的十大使用场景,说是十大场景实际上用途有很多,阿里云百科分享常见的云服务器使用场景,如本地搭建ChatGPT、个人网站或博客、运维测试、学习Linux、跑Python、小程…

【2023秋招】2023华为od-4.20三道题

2023大厂笔试模拟练习网站(含题解) www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据,挂载到我们的OJ上,供大家学习交流,体会笔试难度。现已录入200道互联网大厂模拟练习题&…

【刷题之路Ⅱ】LeetCode 622. 设计循环队列

LeetCode 622. 设计循环队列 一、题目描述二、解题1、方案1——数组实现,预留一个空判满1.1、成环思路1.2、初始化接口1.3、入队接口1.4、出队接口1.5、取队头接口1.6、取队尾接口1.7、判空接口1.8、判满接口1.9、释放接口 2、方案2——单向循环链表实现&#xff0c…

SpringBoot集成Oracle实战和坑

这里写目录标题 前言1.导包2. 配置文件:数据库信息辟谣 3.代码 问题更新 前言 前段时间搞了一个oracle的项目,耗费了很多时间,现在项目整体上线了,在此记录下实战过程以及遇到的坑,有需要的网友也可以直接拿去使用。 …

文本三剑客正则表达式1

文章目录 文本三剑客&正则表达式11 sort1.1 sort -f1.2 sort -b1.3 sort -n1.4 sort -r1.5 sort -u1.6 sort -t1.7 sort -k1.8 sort -o 2 uniq2.1 uniq -c2.2 uniq -u2.3 uniq -d 3 tr3.1tr -c3.2 tr -d3.3 tr -s :3.4 tr -t 4 cut4.1 cut -d4.2 cut -f4.3 cut -b4.4 cut -…

基于 SpringBoot+Vue 的家政服务管理平台

1. 背景 本系统主要是设计出家政服务管理平台,基于B/S构架,后台数据库采用了Mysql,可以使数据的查询和存储变得更加有效,可以确保家政服务管理的工作能够正常、高效的进行,从而提高工作的效率。总体的研究内容如下&am…