深度学习Day-27:生成对抗网络(GAN)入门

  🍨 本文为:[🔗365天深度学习训练营] 中的学习记录博客
 🍖 原作者:[K同学啊 | 接辅导、项目定制]

要求:

  1. 了解什么是生成对抗网络
  2. 生成对抗网络结构是怎么样的
  3. 学习本文代码,并跑通代码
  4. 调用训练好的模型生成新图像(进阶)

一、 基础配置

  • 语言环境:Python3.8
  • 编译器选择:Pycharm
  • 深度学习环境:
    • torch==1.12.1+cu113
    • torchvision==0.13.1+cu113

二、 前期准备 

1. 定义超参数

import os
import numpy as np
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable
import torch.nn as nn
import torch## 创建文件夹
os.makedirs("./images/", exist_ok=True)  ## 记录训练过程的图片效果
os.makedirs("./save/", exist_ok=True)  ## 训练完成时模型保存的位置
os.makedirs("./datasets/mnist", exist_ok=True)  ## 下载数据集存放的位置## 超参数配置
n_epochs = 50
batch_size = 512
lr = 0.0002
b1 = 0.5
b2 = 0.999
n_cpu = 2
latent_dim = 100
img_size = 28
channels = 1
sample_interval = 500## 图像的尺寸:(1, 28, 28),  和图像的像素面积:(784)
img_shape = (channels, img_size, img_size)
img_area = np.prod(img_shape)## 设置cuda:(cuda:0)
cuda = True if torch.cuda.is_available() else False
print(cuda)

2.下载数据

运行下述代码:

## mnist数据集下载
mnist = datasets.MNIST(root='./datasets/', train=True, download=True, transform=transforms.Compose([transforms.Resize(img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]),
)

3. 配置数据

## 配置数据到加载器
dataloader = DataLoader(mnist,batch_size=batch_size,shuffle=True,
)

4.搭建模型

4.1.定义鉴别器

class Discriminator(nn.Module):def __init__(self):super(Discriminator, self).__init__()self.model = nn.Sequential(nn.Linear(img_area, 512),  # 输入特征数为784,输出为512nn.LeakyReLU(0.2, inplace=True),  # 进行非线性映射nn.Linear(512, 256),  # 输入特征数为512,输出为256nn.LeakyReLU(0.2, inplace=True),  # 进行非线性映射nn.Linear(256, 1),  # 输入特征数为256,输出为1nn.Sigmoid(),  # sigmoid是一个激活函数,二分类问题中可将实数映射到[0, 1],作为概率值, 多分类用softmax函数)def forward(self, img):img_flat = img.view(img.size(0), -1)  # 鉴别器输入是一个被view展开的(784)的一维图像:(64, 784)validity = self.model(img_flat)  # 通过鉴别器网络return validity  # 鉴别器返回的是一个[0, 1]间的概率

4.2.定义生成器

class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()## 模型中间块儿def block(in_feat, out_feat, normalize=True):  # block(in, out )layers = [nn.Linear(in_feat, out_feat)]  # 线性变换将输入映射到out维if normalize:layers.append(nn.BatchNorm1d(out_feat, 0.8))  # 正则化layers.append(nn.LeakyReLU(0.2, inplace=True))  # 非线性激活函数return layers## prod():返回给定轴上的数组元素的乘积:1*28*28=784self.model = nn.Sequential(*block(latent_dim, 128, normalize=False),  # 线性变化将输入映射 100 to 128, 正则化, LeakyReLU*block(128, 256),  # 线性变化将输入映射 128 to 256, 正则化, LeakyReLU*block(256, 512),  # 线性变化将输入映射 256 to 512, 正则化, LeakyReLU*block(512, 1024),  # 线性变化将输入映射 512 to 1024, 正则化, LeakyReLUnn.Linear(1024, img_area),  # 线性变化将输入映射 1024 to 784nn.Tanh()  # 将(784)的数据每一个都映射到[-1, 1]之间)## view():相当于numpy中的reshape,重新定义矩阵的形状:这里是reshape(64, 1, 28, 28)def forward(self, z):  # 输入的是(64, 100)的噪声数据imgs = self.model(z)  # 噪声数据通过生成器模型imgs = imgs.view(imgs.size(0), *img_shape)  # reshape成(64, 1, 28, 28)return imgs  # 输出为64张大小为(1, 28, 28)的图像

三、 训练模型 

1. 创建实例

## 创建生成器,判别器对象
generator = Generator()
discriminator = Discriminator()## 首先需要定义loss的度量方式  (二分类的交叉熵)
criterion = torch.nn.BCELoss()## 其次定义 优化函数,优化函数的学习率为0.0003
## betas:用于计算梯度以及梯度平方的运行平均值的系数
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(b1, b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))## 如果有显卡,都在cuda模式中运行
if torch.cuda.is_available():generator = generator.cuda()discriminator = discriminator.cuda()criterion = criterion.cuda()

2. 训练模型

for epoch in range(n_epochs):  # epoch:50for i, (imgs, _) in enumerate(dataloader):  # imgs:(64, 1, 28, 28)     _:label(64)imgs = imgs.view(imgs.size(0), -1)  # 将图片展开为28*28=784  imgs:(64, 784)real_img = Variable(imgs).cuda()  # 将tensor变成Variable放入计算图中,tensor变成variable之后才能进行反向传播求梯度real_label = Variable(torch.ones(imgs.size(0), 1)).cuda()  ## 定义真实的图片label为1fake_label = Variable(torch.zeros(imgs.size(0), 1)).cuda()  ## 定义假的图片的label为0real_out = discriminator(real_img)  # 将真实图片放入判别器中loss_real_D = criterion(real_out, real_label)  # 得到真实图片的lossreal_scores = real_out  # 得到真实图片的判别值,输出的值越接近1越好## 计算假的图片的损失## detach(): 从当前计算图中分离下来避免梯度传到G,因为G不用更新z = Variable(torch.randn(imgs.size(0), latent_dim)).cuda()  ## 随机生成一些噪声, 大小为(128, 100)fake_img = generator(z).detach()  ## 随机噪声放入生成网络中,生成一张假的图片。fake_out = discriminator(fake_img)  ## 判别器判断假的图片loss_fake_D = criterion(fake_out, fake_label)  ## 得到假的图片的lossfake_scores = fake_out## 损失函数和优化loss_D = loss_real_D + loss_fake_D  # 损失包括判真损失和判假损失optimizer_D.zero_grad()  # 在反向传播之前,先将梯度归0loss_D.backward()  # 将误差反向传播optimizer_D.step()  # 更新参数z = Variable(torch.randn(imgs.size(0), latent_dim)).cuda()  ## 得到随机噪声fake_img = generator(z)  ## 随机噪声输入到生成器中,得到一副假的图片output = discriminator(fake_img)  ## 经过判别器得到的结果## 损失函数和优化loss_G = criterion(output, real_label)  ## 得到的假的图片与真实的图片的label的lossoptimizer_G.zero_grad()  ## 梯度归0loss_G.backward()  ## 进行反向传播optimizer_G.step()  ## step()一般用在反向传播后面,用于更新生成网络的参数## 打印训练过程中的日志## item():取出单元素张量的元素值并返回该值,保持原元素类型不变if (i + 1) % 100 == 0:print("[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f] [D real: %f] [D fake: %f]"% (epoch, n_epochs, i, len(dataloader), loss_D.item(), loss_G.item(), real_scores.data.mean(),fake_scores.data.mean()))## 保存训练过程中的图像batches_done = epoch * len(dataloader) + iif batches_done % sample_interval == 0:save_image(fake_img.data[:25], "./images/%d.png" % batches_done, nrow=5, normalize=True)

得到如下输出:

 

[Epoch 0/50] [Batch 99/118] [D loss: 1.358119] [G loss: 0.854631] [D real: 0.828902] [D fake: 0.651636]
[Epoch 1/50] [Batch 99/118] [D loss: 1.133802] [G loss: 0.875813] [D real: 0.669196] [D fake: 0.512738]
[Epoch 2/50] [Batch 99/118] [D loss: 1.092905] [G loss: 0.928318] [D real: 0.596505] [D fake: 0.426318]
[Epoch 3/50] [Batch 99/118] [D loss: 1.170596] [G loss: 0.856016] [D real: 0.474676] [D fake: 0.308923]
[Epoch 4/50] [Batch 99/118] [D loss: 1.137071] [G loss: 1.499401] [D real: 0.748560] [D fake: 0.554068]
[Epoch 5/50] [Batch 99/118] [D loss: 1.107893] [G loss: 0.926190] [D real: 0.532273] [D fake: 0.344259]
[Epoch 6/50] [Batch 99/118] [D loss: 0.982400] [G loss: 1.729913] [D real: 0.745666] [D fake: 0.483679]
[Epoch 7/50] [Batch 99/118] [D loss: 0.939210] [G loss: 1.378411] [D real: 0.751248] [D fake: 0.463029]
[Epoch 8/50] [Batch 99/118] [D loss: 0.944242] [G loss: 1.400929] [D real: 0.684620] [D fake: 0.410933]
[Epoch 9/50] [Batch 99/118] [D loss: 1.319004] [G loss: 2.674909] [D real: 0.846857] [D fake: 0.665650]
[Epoch 10/50] [Batch 99/118] [D loss: 0.990556] [G loss: 1.723803] [D real: 0.706130] [D fake: 0.444029]
[Epoch 11/50] [Batch 99/118] [D loss: 0.954127] [G loss: 1.318436] [D real: 0.581514] [D fake: 0.247630]
[Epoch 12/50] [Batch 99/118] [D loss: 0.974208] [G loss: 1.197314] [D real: 0.622621] [D fake: 0.352450]
[Epoch 13/50] [Batch 99/118] [D loss: 0.975531] [G loss: 1.204608] [D real: 0.599850] [D fake: 0.319579]
[Epoch 14/50] [Batch 99/118] [D loss: 0.804716] [G loss: 1.438551] [D real: 0.697204] [D fake: 0.322416]
[Epoch 15/50] [Batch 99/118] [D loss: 0.974637] [G loss: 1.892112] [D real: 0.731586] [D fake: 0.457425]
[Epoch 16/50] [Batch 99/118] [D loss: 1.010618] [G loss: 1.973964] [D real: 0.773618] [D fake: 0.509037]
[Epoch 17/50] [Batch 99/118] [D loss: 1.039451] [G loss: 0.909313] [D real: 0.478101] [D fake: 0.154255]
[Epoch 18/50] [Batch 99/118] [D loss: 0.903574] [G loss: 1.223073] [D real: 0.581385] [D fake: 0.212841]
[Epoch 19/50] [Batch 99/118] [D loss: 0.980628] [G loss: 1.094885] [D real: 0.574129] [D fake: 0.278985]
[Epoch 20/50] [Batch 99/118] [D loss: 0.911209] [G loss: 1.193374] [D real: 0.607967] [D fake: 0.278935]
[Epoch 21/50] [Batch 99/118] [D loss: 1.162250] [G loss: 2.464992] [D real: 0.859242] [D fake: 0.614595]
[Epoch 22/50] [Batch 99/118] [D loss: 0.886364] [G loss: 1.460998] [D real: 0.680276] [D fake: 0.342425]
[Epoch 23/50] [Batch 99/118] [D loss: 0.745030] [G loss: 1.674799] [D real: 0.740911] [D fake: 0.318460]
[Epoch 24/50] [Batch 99/118] [D loss: 0.838446] [G loss: 1.577401] [D real: 0.719997] [D fake: 0.351009]
[Epoch 25/50] [Batch 99/118] [D loss: 0.787986] [G loss: 1.436635] [D real: 0.690589] [D fake: 0.275540]
[Epoch 26/50] [Batch 99/118] [D loss: 0.788522] [G loss: 1.810806] [D real: 0.750961] [D fake: 0.351278]
[Epoch 27/50] [Batch 99/118] [D loss: 0.753224] [G loss: 1.290582] [D real: 0.661438] [D fake: 0.182648]
[Epoch 28/50] [Batch 99/118] [D loss: 0.883731] [G loss: 1.140509] [D real: 0.588566] [D fake: 0.180261]
[Epoch 29/50] [Batch 99/118] [D loss: 0.903187] [G loss: 1.285718] [D real: 0.570227] [D fake: 0.154226]
[Epoch 30/50] [Batch 99/118] [D loss: 0.777330] [G loss: 1.491624] [D real: 0.698211] [D fake: 0.256135]
[Epoch 31/50] [Batch 99/118] [D loss: 1.068173] [G loss: 0.707813] [D real: 0.504247] [D fake: 0.173714]
[Epoch 32/50] [Batch 99/118] [D loss: 0.887443] [G loss: 1.093637] [D real: 0.608482] [D fake: 0.204210]
[Epoch 33/50] [Batch 99/118] [D loss: 0.900112] [G loss: 2.071476] [D real: 0.783667] [D fake: 0.428811]
[Epoch 34/50] [Batch 99/118] [D loss: 0.984621] [G loss: 2.191275] [D real: 0.865837] [D fake: 0.539177]
[Epoch 35/50] [Batch 99/118] [D loss: 0.811297] [G loss: 1.447091] [D real: 0.684229] [D fake: 0.278086]
[Epoch 36/50] [Batch 99/118] [D loss: 0.855922] [G loss: 1.334665] [D real: 0.586626] [D fake: 0.127038]
[Epoch 37/50] [Batch 99/118] [D loss: 0.790842] [G loss: 2.243885] [D real: 0.839149] [D fake: 0.423923]
[Epoch 38/50] [Batch 99/118] [D loss: 0.842804] [G loss: 1.557674] [D real: 0.702306] [D fake: 0.316344]
[Epoch 39/50] [Batch 99/118] [D loss: 0.700674] [G loss: 1.459940] [D real: 0.715495] [D fake: 0.239821]
[Epoch 40/50] [Batch 99/118] [D loss: 1.004132] [G loss: 2.275932] [D real: 0.782830] [D fake: 0.486044]
[Epoch 41/50] [Batch 99/118] [D loss: 0.893716] [G loss: 1.230096] [D real: 0.653264] [D fake: 0.297629]
[Epoch 42/50] [Batch 99/118] [D loss: 0.803412] [G loss: 1.524267] [D real: 0.698426] [D fake: 0.286330]
[Epoch 43/50] [Batch 99/118] [D loss: 0.964831] [G loss: 2.093666] [D real: 0.814177] [D fake: 0.483912]
[Epoch 44/50] [Batch 99/118] [D loss: 0.826310] [G loss: 1.482098] [D real: 0.705200] [D fake: 0.309380]
[Epoch 45/50] [Batch 99/118] [D loss: 0.862298] [G loss: 1.375562] [D real: 0.680220] [D fake: 0.309462]
[Epoch 46/50] [Batch 99/118] [D loss: 1.084897] [G loss: 0.787066] [D real: 0.508777] [D fake: 0.168071]
[Epoch 47/50] [Batch 99/118] [D loss: 1.214089] [G loss: 0.794297] [D real: 0.480335] [D fake: 0.182916]
[Epoch 48/50] [Batch 99/118] [D loss: 0.847767] [G loss: 1.546502] [D real: 0.701518] [D fake: 0.327755]
[Epoch 49/50] [Batch 99/118] [D loss: 1.136368] [G loss: 1.930105] [D real: 0.786755] [D fake: 0.547514]Process finished with exit code 0

3. 保存模型

torch.save(generator.state_dict(), './generator.pth')
torch.save(discriminator.state_dict(), './discriminator.pth')

四、理论基础

        生成对抗网络(Generative Adversarial Networks, GAN)并不指代某一个具体的神经网络,而是指一类基于博弈思想而设计的神经网络。GAN由两个分别被称为生成器(Generator)和判别器(Discriminator)的神经网络组成。其中,生成器从某种噪声分布中随机采样作为输入,输出与训练集中真实样本非常相似的人工样本;判别器的输入则为真实样本或人工样本,其目的是将人工样本与真实样本尽可能地区分出来。生成器和判别器交替运行,相互博弈,各自的能力都得到升。理想情况下,经过足够次数的博弈之后,判别器无法判断给定样本的真实性,即对于所有样本都输出50%真,50%假的判断。此时,生成器输出的人工样本已经逼真到使判别器无法分辨真假,停止博弈。这样就可以得到一个具有“伪造”真实样本能力的生成器。

1. 生成器

        GANs中,生成器 G 选取随机噪声 z 作为输入,通过生成器的不断拟合,最终输出一个和真实样本尺寸相同,分布相似的伪造样本G(z)。生成器的本质是一个使用生成式方法的模型,它对数据的分布假设和分布参数进行学习,然后根据学习到的模型重新采样出新的样本。
        从数学上来说,生成式方法对于给定的真实数据,首先需要对数据的显式变量或隐含变量做分布假设;然后再将真实数据输入到模型中对变量、参数进行训练;最后得到一个学习后的近似分布,这个分布可以用来生成新的数据。从机器学习的角度来说,模型不会去做分布假设,而是通过不断地学习真实数据,对模型进行修正,最后也可以得到一个学习后的模型来做样本生成任务。这种方法不同于数学方法,学习的过程对人类理解较不直观。

2. 判别器

        GANs中,判别器 D 对于输入的样本 x,输出一个[0,1]之间的概率数值D(x)。x 可能是来自于原始数据集中的真实样本 x,也可能是来自于生成器 G 的人工样本G(z)。通常约定,概率值D(x)越接近于1就代表此样本为真实样本的可能性更大;反之概率值越小则此样本为伪造样本的可能性越大。也就是说,这里的判别器是一个二分类的神经网络分类器,目的不是判定输入数据的原始类别,而是区分输入样本的真伪。可以注意到,不管在生成器还是判别器中,样本的类别信息都没有用到,也表明 GAN 是一个无监督的学习过程。

3. 基本原理

        GAN是博弈论和机器学习相结合的产物,研究者最初想要通过计算机完成自动生成数据的功能,例如通过训练某种算法模型,让某模型学习过一些苹果的图片后能自动生成苹果的图片,具备些功能的算法即认为具有生成功能。但是GAN不是第一个生成算法,而是以往的生成算法在衡量生成图片和真实图片的差距时采用均方误差作为损失函数,但是研究者发现有时均方误差一样的两张生成图片效果却截然不同,鉴于此不足Ian Goodfellow提出了GAN。

        如图1所示,GAN是由两个模型组成的:生成模型G和判别模型D。首先第一代生成模型1G的输入是随机噪声z,然后生成模型会生成一张初级照片,训练一代判别模型1D另其进行二分类操作,将生成的图片判别为0,而真实图片判别为1;为了欺瞒一代鉴别器,于是一代生成模型开始优化,然后它进阶成了二代,当它生成的数据成功欺瞒1D时,鉴别模型也会优化更新,进而升级为2D,按照同样的过程也会不断更新出N代的G和D。

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

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

相关文章

抖音开放平台API接口如何开发||抖音相关接口数据采集数据分析 【附实例】

抖音开放平台提供了多种接口,包括授权登录、用户信息、视频管理、评论互动、消息通知、数据分析等。 以下是开发抖音接口的一些步骤: 1. 注册开发者账号:在抖音开放平台上注册开发者账号,获取开发者身份认证。 2. 创建应用&…

Oracle VM VirtualBox 异常退出,如何解决??

🏆本文收录于《CSDN问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收…

【Linux】进程间通信 —— 管道与 System V 版本通信方式

目录 为什么有进程间通信?进程间通信的目的是什么? 管道 匿名管道 父子进程共享管道 命名管道 共享内存 概念 原理 共享内存和内存映射(文件映射)的区别 使用 消息队列 概念 使用 信号量 概念 使用 IPCS 命令 S…

Docker Compose方式部署Ruoyi-前后端分离版本

目录 一. 环境准备 二. 制作一个jdk8u202环境的镜像 三. 制作nginx镜像 四. 对项目文件做修改 五. 项目打包 1. 前端打包 2. 后端打包 六. 编写docker-compose.yml 一. 环境准备 主机名IP系统软件版本配置信息localhost192.168.226.25Rocky_linux9.4 git version 2.…

码农职场:一本专为IT行业求职者量身定制的指南

目录 写在前面 推荐图书 推荐理由 写在后面 写在前面 本期博主给大家推荐一本专为IT行业求职者量身定制的指南:《码农职场》。 推荐图书 https://item.jd.com/14716160.html 内容简介 这是一本专为广大IT 行业求职者量身定制的指南,提供了从职前…

黑马JavaWeb后端案例开发(包含所有知识点!!!)

目录 1.准备工作 环境搭建 开发规范 REST(REpresentation State Transfer),表述性状态转换,它是一种软件架构风格 注意事项 统一响应结果 2.部门管理功能 查询部门 删除部门 新增部门 RequestMapping 3.员工管理功能 分页查询 批…

单细胞|MEBOCOST·基于代谢物的细胞通讯预测(一)

import os,sys import scanpy as sc import pandas as pd import numpy as np from matplotlib import pyplot as plt import seaborn as sns from mebocost import mebocost 1. 创建 mebocost 对象 adata sc.read_h5ad(data/demo/raw_scRNA/demo_HNSC_200cell.h5ad) ## che…

开发无人带货直播插件

在当今快速发展的电商行业中,直播带货已成为推动销售增长的重要力量,然而,随着直播市场的日益饱和和消费者需求的不断变化,如何在保持直播互动性的同时,实现高效、低成本的运营成为许多商家关注的焦点。 无人带货直播…

springboot 微信消息推送 springboot sse推送

目录 一、springboot 微信消息推送 springboot sse推送 1、在 Spring 框架中实现 2、传统的 Servlet 中实现 一、springboot 微信消息推送 springboot sse推送 关于 SSE SSE 全程 Server Send Event,是 HTTP 协议中的一种,Content-Type 为 text/event…

Android 自定义圆形进度条样式

效果 代码 主要是设置属性indeterminateDrawable <ProgressBarandroid:id"id/iv_progress"android:layout_width"20dp"android:layout_height"20dp"android:layout_gravity"center"android:layout_marginStart"15dp"and…

清爽简洁!这可能是开源界功能最强大的项目开发管理系统

&#x1f482; 个人网站: IT知识小屋&#x1f91f; 版权: 本文由【IT学习日记】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 文章目录 写在前面项目简介项目特点设计思想技术栈项目展示项目获取 写在前…

❤️‍FlyFlow:新增表格布局表单

FlyFlow 介绍 官网地址&#xff1a;www.flyflow.cc ElementPlus演示网址&#xff1a;pro.flyflow.cc AntDesign演示网址&#xff1a;ant.flyflow.cc FlyFlow 借鉴了钉钉与飞书的界面设计理念&#xff0c;致力于打造一款用户友好、快速上手的工作流程工具。相较于传统的基于 …

21. Hibernate 性能之数据库连接池

1. 前言 从本节课程开始&#xff0c;和大家一起聊聊 Hibernate 中的性能问题&#xff0c;面对开发者&#xff0c;Hibernate 表现出卓越的数据库操作能力。 使用框架最大的优势就是带来操作的快捷、便利。同时&#xff0c;因为框架的封装性&#xff0c;其性能往往比原生开发要…

【PostGresql】---- pgSql 将列中合并字符串拆分为多行 实例代码

-- 将 AQY_ID,AQY 中的字符串拆分为多行 SELECT"ID","AQY_ID","AQY",UNNEST ( string_to_array( "AQY_ID", , ) ) AS "AQY_ID_1",UNNEST ( string_to_array( "AQY", , ) ) AS "AQY_1" FROM"JF_SGC…

30.jdk源码阅读之ReentrantReadWriteLock

1.写在前面 ReentrantReadWriteLock 是 Java 并发包中的一个读写锁实现&#xff0c;它允许多个读线程同时访问共享资源&#xff0c;但在写线程访问时&#xff0c;所有的读线程和其他写线程都会被阻塞。不知道大家在日常工作中这个类使用的多不多&#xff0c;对于它的底层实现有…

Tooltip 文字提示

在偶然维护前端开发时,遇到页面列表中某个字段内容太长,且该字段使用了组件显示,导致不能使用纯文本得那个省略号代替显示得css样式效果,如下 所以只能另辟溪路了, 1、最开始想到是使用横向滚动得效果来实现,但是实现后,感觉还是不太理想,因为用户注意不到你这里有滚动…

【基础篇】Docker 容器操作 FOUR

嘿&#xff0c;小伙伴们&#xff01;我是小竹笋&#xff0c;一名热爱创作的工程师。在上一篇文章中&#xff0c;我们探讨了 Docker 镜像管理的相关知识。今天&#xff0c;让我们一起深入了解一下 Docker 容器的操作吧&#xff01; &#x1f4e6; 运行、停止和删除容器 Docker…

一个私有化的中文笔记工具个人知识库,极空间Docker部署中文版『Trilium Notes』

一个私有化的中文笔记工具&个人知识库&#xff0c;极空间Docker部署中文版『Trilium Notes』 哈喽小伙伴们好&#xff0c;我是Stark-C~ 最近被很多小伙伴问到NAS上的笔记工具&#xff0c;虽说之前也出过Memos&#xff0c;刚开始用起来还不错&#xff0c;但是用了一段时间…

【JKI SMO】框架讲解(六)

接JKI SMO 框架讲解&#xff08;五&#xff09;&#xff0c;现在对代码进行一个扩展&#xff0c;当前代码仅有一路电压采集&#xff0c;现在需要扩展一路电流采集通道。 下面是对应的步骤&#xff1a; 1.打开项目&#xff0c;在工具里打开SMO Editor。 2.之前创建的SMO会自动加…

快速收集地图商户信息_百度|高德|腾讯|google

数字化营销中企业名录和商家电话号码的采集已成为营销人员日常工作的首要一环。地图平台以其海量的商家信息和实时更新的特性&#xff0c;成为我们获取数据的宝贵渠道。如何快速利用百度、高德、腾讯这三大地图平台高效采集商家联系方式是每个营销人员的必备技能。 我们整理了…