2024年7月

feat-cgroup-v2.png

本文为从零开始写 Docker 系列第十九篇,添加对 cgroup v2 的支持。


完整代码见:
https://github.com/lixd/mydocker
欢迎 Star

推荐阅读以下文章对 docker 基本实现有一个大致认识:


开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic


注意:需要使用 root 用户

1. 概述

本篇主要添加对 cgroup v2 的支持,自动识别当前系统 cgroup 版本。

2. 实现

判断 cgroup 版本

通过下面这条命令来查看当前系统使用的 Cgroups V1 还是 V2

stat -fc %T /sys/fs/cgroup/

如果输出是
cgroup2fs
那就是 V2,就像这样

root@tezn:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

如果输出是
tmpfs
那就是 V1,就像这样

[root@docker cgroup]# stat -fc %T /sys/fs/cgroup/
tmpfs

Go 实现如下:

const (
	unifiedMountpoint = "/sys/fs/cgroup"
)

var (
	isUnifiedOnce sync.Once
	isUnified     bool
)

// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
func IsCgroup2UnifiedMode() bool {
	isUnifiedOnce.Do(func() {
		var st unix.Statfs_t
		err := unix.Statfs(unifiedMountpoint, &st)
		if err != nil && os.IsNotExist(err) {
			// For rootless containers, sweep it under the rug.
			isUnified = false
			return
		}
		isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
	})
	return isUnified
}

cgroup v2 支持

使用 cgroup v2 过程和 v1 基本一致

  • 1)创建子 cgroup
  • 2)配置 cpu、memory 等 Subsystem
  • 3)配置需要限制的进程

创建子 cgroup

创建子 cgroup,则是在 cgroup 根目录下创建子目录即可,对 cgroup v2 来说,根目录就是
/sys/fs/cgroup

const UnifiedMountpoint = "/sys/fs/cgroup"

// getCgroupPath 找到cgroup在文件系统中的绝对路径
/*
实际就是将根目录和cgroup名称拼接成一个路径。
如果指定了自动创建,就先检测一下是否存在,如果对应的目录不存在,则说明cgroup不存在,这里就给创建一个
*/
func getCgroupPath(cgroupPath string, autoCreate bool) (string, error) {
	// 不需要自动创建就直接返回
	cgroupRoot := UnifiedMountpoint
	absPath := path.Join(cgroupRoot, cgroupPath)
	if !autoCreate {
		return absPath, nil
	}
	// 指定自动创建时才判断是否存在
	_, err := os.Stat(absPath)
	// 只有不存在才创建
	if err != nil && os.IsNotExist(err) {
		err = os.Mkdir(absPath, constant.Perm0755)
		return absPath, err
	}
	return absPath, errors.Wrap(err, "create cgroup")
}

配置 Subsystem

以 cpu 为例,只需要在 cpu.max 中添加具体限制即可,就像这样:

echo 5000 10000 > cpu.max

含义是在10000的CPU时间周期内,有5000是分配给本cgroup的,也就是本cgroup管理的进程在单核CPU上的使用率不会超过50%

具体实现如下:

func (s *CpuSubSystem) Set(cgroupPath string, res *resource.ResourceConfig) error {
	if res.CpuCfsQuota == 0 {
		return nil
	}
	subCgroupPath, err := getCgroupPath(cgroupPath, true)
	if err != nil {
		return err
	}

	// cpu.cfs_period_us & cpu.cfs_quota_us 控制的是CPU使用时间,单位是微秒,比如每1秒钟,这个进程只能使用200ms,相当于只能用20%的CPU
	// v2 中直接将 cpu.cfs_period_us & cpu.cfs_quota_us 统一记录到 cpu.max 中,比如 5000 10000 这样就是限制使用 50% cpu
	if res.CpuCfsQuota != 0 {
		// cpu.cfs_quota_us 则根据用户传递的参数来控制,比如参数为20,就是限制为20%CPU,所以把cpu.cfs_quota_us设置为cpu.cfs_period_us的20%就行
		// 这里只是简单的计算了下,并没有处理一些特殊情况,比如负数什么的
		if err = os.WriteFile(path.Join(subCgroupPath, "cpu.max"), []byte(fmt.Sprintf("%s %s", strconv.Itoa(PeriodDefault/Percent*res.CpuCfsQuota), PeriodDefault)), constant.Perm0644); err != nil {
			return fmt.Errorf("set cgroup cpu share fail %v", err)
		}
	}
	return nil
}

配置需要限制的进程

只需要将 pid 写入 cgroup.procs 即可

echo 1033 > cgroup.procs

Go 实现如下:

func (s *CpuSubSystem) Apply(cgroupPath string, pid int) error {
	return applyCgroup(pid, cgroupPath)
}

func applyCgroup(pid int, cgroupPath string) error {
	subCgroupPath, err := getCgroupPath(cgroupPath, true)
	if err != nil {
		return errors.Wrapf(err, "get cgroup %s", cgroupPath)
	}
	if err = os.WriteFile(path.Join(subCgroupPath, "cgroup.procs"), []byte(strconv.Itoa(pid)),
		constant.Perm0644); err != nil {
		return fmt.Errorf("set cgroup proc fail %v", err)
	}
	return nil
}

移除

删除 cgroup 下的子目录即可移除

func (s *CpuSubSystem) Remove(cgroupPath string) error {
	subCgroupPath, err := getCgroupPath(cgroupPath, false)
	if err != nil {
		return err
	}
	return os.RemoveAll(subCgroupPath)
}

兼容V1和V2

只需要在创建 CgroupManager 时判断当前系统 cgroup 版本即可

func NewCgroupManager(path string) CgroupManager {
	if IsCgroup2UnifiedMode() {
		log.Infof("use cgroup v2")
		return NewCgroupManagerV2(path)
	}
	log.Infof("use cgroup v1")
	return NewCgroupManagerV1(path)
}

3. 测试

cgroup v1

到 cgroup v1 环境进行测试

root@mydocker:~/mydocker# ./mydocker run -mem 10m -cpu 10 -it -name cgroupv1 busybox /bin/sh
{"level":"info","msg":"createTty true","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"resConf:\u0026{10m 10 }","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"lower:/var/lib/mydocker/overlay2/3845479957/lower image.tar:/var/lib/mydocker/image/busybox.tar","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/var/lib/mydocker/overlay2/3845479957/lower,upperdir=/var/lib/mydocker/overlay2/3845479957/upper,workdir=/var/lib/mydocker/overlay2/3845479957/work /var/lib/mydocker/overlay2/3845479957/merged]","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"use cgroup v1","time":"2024-04-14T13:23:19+08:00"}
{"level":"error","msg":"apply subsystem:cpuset err:set cgroup proc fail write /sys/fs/cgroup/cpuset/mydocker-cgroup/tasks: no space left on device","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"init come on","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"Current location is /var/lib/mydocker/overlay2/3845479957/merged","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-04-14T13:23:19+08:00"}

根据日志可知,当前使用的时 cgroup v1

{"level":"info","msg":"use cgroup v1","time":"2024-04-14T13:23:19+08:00"}

执行以下命令测试memory分配

yes > /dev/null

可以看到,过会就被 OOM Kill 了

/ # yes > /dev/null
Killed

执行以下命令 跑满 cpu

while : ; do : ; done &

确实被限制到 10%了

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND              
1212 root      20   0    1332     68      4 R   9.9   0.0   0:02.30 sh  

cgroup v2

到 cgroup v2 环境进行测试,或者参考以下步骤切换到 v2 版本。

切换到 cgroup v2

你还可以通过修改内核 cmdline 引导参数在你的 Linux 发行版上手动启用 cgroup v2。

如果你的发行版使用 GRUB,则应在
/etc/default/grub
下的
GRUB_CMDLINE_LINUX
中添加
systemd.unified_cgroup_hierarchy=1
, 然后执行
sudo update-grub

编辑 grub 配置

vi /etc/default/grub

内容大概是这样的:

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

对最后一行
GRUB_CMDLINE_LINUX
进行修改

GRUB_CMDLINE_LINUX="quiet splash systemd.unified_cgroup_hierarchy=1"

然后执行以下命令更新 GRUB 配置

sudo update-grub

最后查看一下启动参数,确认配置修改上了

cat /boot/grub/grub.cfg | grep "systemd.unified_cgroup_hierarchy=1"

然后就是重启

reboot

重启后查看,不出意外切换到 cgroups v2 了

root@cgroupv2:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

测试

./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh
root@mydocker:~/mydocker# ./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh
{"level":"info","msg":"createTty true","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"resConf:\u0026{10m 10 }","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"lower:/var/lib/mydocker/overlay2/3526930704/lower image.tar:/var/lib/mydocker/image/busybox.tar","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/var/lib/mydocker/overlay2/3526930704/lower,upperdir=/var/lib/mydocker/overlay2/3526930704/upper,workdir=/var/lib/mydocker/overlay2/3526930704/work /var/lib/mydocker/overlay2/3526930704/merged]","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"use cgroup v2","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"init come on","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"Current location is /var/lib/mydocker/overlay2/3526930704/merged","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-04-14T13:26:32+08:00"}

根据日志可知,当前使用的时 cgroup v2

{"level":"info","msg":"use cgroup v2","time":"2024-04-14T13:26:32+08:00"}

执行同样的测试,效果一致,说明 cgroup v2 使用正常。

执行以下命令测试memory分配

yes > /dev/null

可以看到,过会就被 OOM Kill 了

/ # yes > /dev/null
Killed

执行以下命令 跑满 cpu

while : ; do : ; done &

确实被限制到 10%了

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND              
1212 root      20   0    1332     68      4 R   9.9   0.0   0:02.30 sh  

4. 小结

本文主要为 mydocker 添加了 cgroup v2 的支持,根据系统 cgroup 版本自适应切换。


完整代码见:
https://github.com/lixd/mydocker
欢迎关注~


【从零开始写 Docker 系列】
持续更新中,搜索公众号【
探索云原生
】订阅,阅读更多文章。


相关代码见
feat-cgroup-v2
分支,测试脚本如下:

需要提前在 /var/lib/mydocker/image 目录准备好 busybox.tar 文件,具体见第四篇第二节。

# 克隆代码
git clone -b feat-cgroup-v2 https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 
./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh

1 ACGAN基本原理

1.2 ACGAN模型解释

ACGAN相对于CGAN使的判别器不仅可以判别真假,也可以判别类别 。通过对生成数据类别的判断,判别器可以更好地传递loss函数使得生成器能够更加准确地找到label对应的噪声分布,通过下图告诉了我们ACGAN与CGAN的异同之处 :

1721649945655

对于CGAN和ACGAN,生成器输入均为潜在矢量及其标签,输出是属于输入类标签的伪造数据。对于CGAN,判别器的输入是数据(包含假的或真实的数据)及其标签, 输出是图像属于真实数据的概率。对于ACGAN,判别器的输入是数据,而输出是该图像属于真实数据的概率以及其类别概率。

在ACGAN中,对于生成器来说有两个输入,一个是标签的分类数据c,另一个是随机数据z,得到生成数据为
X_{fake}=G(photo/gif.gif)
;对于判别器,产生跨域标签和源数据的概率分布
1721652818004

1.2 ACGAN损失函数

对于判别器而言,即希望分类正确,有希望能正确分辨数据的真假;对于生成器而言,也希望分类正确,但希望判别器不能正确分辨真假。因此在训练判别器的时候,我们希望LSE+LCS最大化;在训练生成器的时候,我们希望LCS-LSE最大化。

  • logP(SR = real | Xreal)
    表示鉴别器将真实样本源正确分类为真实样本的对数似然;
  • logP(SR = fake | Xfake)
    表示鉴别器正确地将假样本的来源分类为假样本的对数似然
  • E[.]
    表示所有样本的平均值
  • logP(CS = CS | Xreal)
    表示鉴别器正确分类真实样本的对数似然
  • logP(CS = CS | Xfake)
    表示鉴别器正确分类具有正确类别标签的假样本的对数似然

判别器
的损失函数 = LSE + LCS;
生成器
的损失函数 = LCS - LSE

  • LSE测量鉴别器正确区分样本是真还是假的程度。这有助于鉴别器熟练地识别来源(真实的或生成的)。
  • LCS确保生成的样本不仅看起来真实,而且携带正确的类信息。它引导生成器在不同的类中产生多样化和现实的样本。

2 ACGAN pytorch代码实现

完整代码链接:
https://github.com/znxlwm/pytorch-generative-model-collections/tree/master

(但是这个代码我训练的时候损失函数也对应的上,得到的图片是黑乎乎的一片,也不知道是什么原因,如果知道的师傅可以麻烦告知一下吗?(感谢))

这个代码在训练ACGAN模型的时候加载数据集的时候会出现问题,因为我使用的是minist数据集,所以应该改为单通道的:

1721782164361

import utils, torch, time, os, pickle
import numpy as np
import torch.nn as nn
import torch.optim as optim
from dataloader import dataloader

class generator(nn.Module):
    # Network Architecture is exactly same as in infoGAN (https://arxiv.org/abs/1606.03657)
    # Architecture : FC1024_BR-FC7x7x128_BR-(64)4dc2s_BR-(1)4dc2s_S
    def __init__(self, input_dim=100, output_dim=1, input_size=32, class_num=10):
        super(generator, self).__init__()
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.input_size = input_size
        self.class_num = class_num

        self.fc = nn.Sequential(
            nn.Linear(self.input_dim + self.class_num, 1024),
            nn.BatchNorm1d(1024),
            nn.ReLU(),
            nn.Linear(1024, 128 * (self.input_size // 4) * (self.input_size // 4)),
            nn.BatchNorm1d(128 * (self.input_size // 4) * (self.input_size // 4)),
            nn.ReLU(),
        )
        self.deconv = nn.Sequential(
            nn.ConvTranspose2d(128, 64, 4, 2, 1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.ConvTranspose2d(64, self.output_dim, 4, 2, 1),
            nn.Tanh(),
        )
        utils.initialize_weights(self)

    def forward(self, input, label):
        x = torch.cat([input, label], 1)
        x = self.fc(x)
        x = x.view(-1, 128, (self.input_size // 4), (self.input_size // 4))
        x = self.deconv(x)

        return x

class discriminator(nn.Module):
    # Network Architecture is exactly same as in infoGAN (https://arxiv.org/abs/1606.03657)
    # Architecture : (64)4c2s-(128)4c2s_BL-FC1024_BL-FC1_S
    def __init__(self, input_dim=1, output_dim=1, input_size=32, class_num=10):
        super(discriminator, self).__init__()
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.input_size = input_size
        self.class_num = class_num

        self.conv = nn.Sequential(
            nn.Conv2d(self.input_dim, 64, 4, 2, 1),
            nn.LeakyReLU(0.2),
            nn.Conv2d(64, 128, 4, 2, 1),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2),
        )
        self.fc1 = nn.Sequential(
            nn.Linear(128 * (self.input_size // 4) * (self.input_size // 4), 1024),
            nn.BatchNorm1d(1024),
            nn.LeakyReLU(0.2),
        )
        self.dc = nn.Sequential(
            nn.Linear(1024, self.output_dim),
            nn.Sigmoid(),
        )
        self.cl = nn.Sequential(
            nn.Linear(1024, self.class_num),
        )
        utils.initialize_weights(self)

    def forward(self, input):
        x = self.conv(input)
        x = x.view(-1, 128 * (self.input_size // 4) * (self.input_size // 4))
        x = self.fc1(x)
        d = self.dc(x)
        c = self.cl(x)

        return d, c

class ACGAN(object):
    def __init__(self, args):
        # parameters
        self.epoch = args.epoch
        self.sample_num = 100
        self.batch_size = args.batch_size
        self.save_dir = args.save_dir
        self.result_dir = args.result_dir
        self.dataset = args.dataset
        self.log_dir = args.log_dir
        self.gpu_mode = args.gpu_mode
        self.model_name = args.gan_type
        self.input_size = args.input_size    # 输入图像的尺寸
        self.z_dim = 62    # 潜在向量维度
        self.class_num = 10
        self.sample_num = self.class_num ** 2   # 总样本的数量

        # load dataset
        self.data_loader = dataloader(self.dataset, self.input_size, self.batch_size)   # 加载数据集
        data = self.data_loader.__iter__().__next__()[0]    # 获得第一个批次的数据,data 的形状通常是 (batch_size, channels, height, width)


        # networks init
        self.G = generator(input_dim=self.z_dim, output_dim=data.shape[1], input_size=self.input_size)
        self.D = discriminator(input_dim=data.shape[1], output_dim=1, input_size=self.input_size)
        self.G_optimizer = optim.Adam(self.G.parameters(), lr=args.lrG, betas=(args.beta1, args.beta2))
        self.D_optimizer = optim.Adam(self.D.parameters(), lr=args.lrD, betas=(args.beta1, args.beta2))

        # 查看是否启用了gpu模式
        if self.gpu_mode:
            self.G.cuda()
            self.D.cuda()
            self.BCE_loss = nn.BCELoss().cuda()     # 将交叉熵损失加载到GPU
            self.CE_loss = nn.CrossEntropyLoss().cuda()     # 将二元交叉熵损失加载到GPU
        else:
            self.BCE_loss = nn.BCELoss()
            self.CE_loss = nn.CrossEntropyLoss()

        print('---------- Networks architecture -------------')
        utils.print_network(self.G)
        utils.print_network(self.D)
        print('-----------------------------------------------')

        # fixed noise & condition
        # 为每个类别生成潜在向量(latent vector)z,并确保同一类别的所有样本共享相同的潜在向量
        self.sample_z_ = torch.zeros((self.sample_num, self.z_dim))
        for i in range(self.class_num):
            self.sample_z_[i*self.class_num] = torch.rand(1, self.z_dim)     # 为每一个类别随机生成潜在变量
            for j in range(1, self.class_num):
                self.sample_z_[i*self.class_num + j] = self.sample_z_[i*self.class_num]     # 同一类别的样本共享相同的潜在变量

        # 为每个样本创造标签向量
        temp = torch.zeros((self.class_num, 1))     # 10*1
        for i in range(self.class_num):
            temp[i, 0] = i

        temp_y = torch.zeros((self.sample_num, 1))
        for i in range(self.class_num):
            temp_y[i*self.class_num: (i+1)*self.class_num] = temp   # 给每个样本赋予相同的标签

        # 编码one-hot
        self.sample_y_ = torch.zeros((self.sample_num, self.class_num)).scatter_(1, temp_y.type(torch.LongTensor), 1)
        if self.gpu_mode:
            self.sample_z_, self.sample_y_ = self.sample_z_.cuda(), self.sample_y_.cuda()

    # 用于训练模型
    def train(self):
        self.train_hist = {}
        self.train_hist['D_loss'] = []
        self.train_hist['G_loss'] = []
        self.train_hist['per_epoch_time'] = []
        self.train_hist['total_time'] = []

        self.y_real_, self.y_fake_ = torch.ones(self.batch_size, 1), torch.zeros(self.batch_size, 1)
        if self.gpu_mode:
            self.y_real_, self.y_fake_ = self.y_real_.cuda(), self.y_fake_.cuda()

        self.D.train()
        print('training start!!')
        start_time = time.time()
        for epoch in range(self.epoch):
            self.G.train()
            epoch_start_time = time.time()
            for iter, (x_, y_) in enumerate(self.data_loader):
                if iter == self.data_loader.dataset.__len__() // self.batch_size:
                    break
                z_ = torch.rand((self.batch_size, self.z_dim))
                y_vec_ = torch.zeros((self.batch_size, self.class_num)).scatter_(1, y_.type(torch.LongTensor).unsqueeze(1), 1)
                if self.gpu_mode:
                    x_, z_, y_vec_ = x_.cuda(), z_.cuda(), y_vec_.cuda()

                # update D network
                self.D_optimizer.zero_grad()    # 梯度清0

                D_real, C_real = self.D(x_)     # 获取判别器的预测结果
                D_real_loss = self.BCE_loss(D_real, self.y_real_)
                C_real_loss = self.CE_loss(C_real, torch.max(y_vec_, 1)[1])

                G_ = self.G(z_, y_vec_)     # 生成伪造数据
                D_fake, C_fake = self.D(G_)
                D_fake_loss = self.BCE_loss(D_fake, self.y_fake_)
                C_fake_loss = self.CE_loss(C_fake, torch.max(y_vec_, 1)[1])

                D_loss = D_real_loss + C_real_loss + D_fake_loss + C_fake_loss
                self.train_hist['D_loss'].append(D_loss.item())

                D_loss.backward()
                self.D_optimizer.step()     # 更新判别器权重

                # update G network
                self.G_optimizer.zero_grad()

                G_ = self.G(z_, y_vec_)
                D_fake, C_fake = self.D(G_)

                G_loss = self.BCE_loss(D_fake, self.y_real_)
                C_fake_loss = self.CE_loss(C_fake, torch.max(y_vec_, 1)[1])

                G_loss += C_fake_loss
                self.train_hist['G_loss'].append(G_loss.item())

                G_loss.backward()
                self.G_optimizer.step()

                # 打印训练信息
                if ((iter + 1) % 100) == 0:
                    print("Epoch: [%2d] [%4d/%4d] D_loss: %.8f, G_loss: %.8f" %
                          ((epoch + 1), (iter + 1), self.data_loader.dataset.__len__() // self.batch_size, D_loss.item(), G_loss.item()))
                # 每一轮训练结束-------------
            self.train_hist['per_epoch_time'].append(time.time() - epoch_start_time)
            with torch.no_grad():   # 结束进行梯度运算
                self.visualize_results((epoch+1))
        # 每一epoch训练结束-------------

        self.train_hist['total_time'].append(time.time() - start_time)
        print("Avg one epoch time: %.2f, total %d epochs time: %.2f" % (np.mean(self.train_hist['per_epoch_time']),
                                                                        self.epoch, self.train_hist['total_time'][0]))
        print("Training finish!... save training results")

        self.save()     # 保存训练历史
        utils.generate_animation(self.result_dir + '/' + self.dataset + '/' + self.model_name + '/' + self.model_name,
                                 self.epoch)
        utils.loss_plot(self.train_hist, os.path.join(self.save_dir, self.dataset, self.model_name), self.model_name)

    # 用于可视化生成的图像
    def visualize_results(self, epoch, fix=True):
        self.G.eval()

        if not os.path.exists(self.result_dir + '/' + self.dataset + '/' + self.model_name):
            os.makedirs(self.result_dir + '/' + self.dataset + '/' + self.model_name)

        image_frame_dim = int(np.floor(np.sqrt(self.sample_num)))

        if fix:
            """ fixed noise """
            samples = self.G(self.sample_z_, self.sample_y_)
        else:
            """ random noise """
            sample_y_ = torch.zeros(self.batch_size, self.class_num).scatter_(1, torch.randint(0, self.class_num - 1, (self.batch_size, 1)).type(torch.LongTensor), 1)
            sample_z_ = torch.rand((self.batch_size, self.z_dim))
            if self.gpu_mode:
                sample_z_, sample_y_ = sample_z_.cuda(), sample_y_.cuda()

            samples = self.G(sample_z_, sample_y_)

        if self.gpu_mode:
            samples = samples.cpu().data.numpy().transpose(0, 2, 3, 1)
        else:
            samples = samples.data.numpy().transpose(0, 2, 3, 1)

        samples = (samples + 1) / 2
        utils.save_images(samples[:image_frame_dim * image_frame_dim, :, :, :], [image_frame_dim, image_frame_dim],
                          self.result_dir + '/' + self.dataset + '/' + self.model_name + '/' + self.model_name + '_epoch%03d' % epoch + '.png')

    # 用于保存模型和训练历史
    def save(self):
        save_dir = os.path.join(self.save_dir, self.dataset, self.model_name)

        if not os.path.exists(save_dir):
            os.makedirs(save_dir)

        torch.save(self.G.state_dict(), os.path.join(save_dir, self.model_name + '_G.pkl'))
        torch.save(self.D.state_dict(), os.path.join(save_dir, self.model_name + '_D.pkl'))

        with open(os.path.join(save_dir, self.model_name + '_history.pkl'), 'wb') as f:
            pickle.dump(self.train_hist, f)

    # 用于加载模型和训练历史
    def load(self):
        save_dir = os.path.join(self.save_dir, self.dataset, self.model_name)

        self.G.load_state_dict(torch.load(os.path.join(save_dir, self.model_name + '_G.pkl')))
        self.D.load_state_dict(torch.load(os.path.join(save_dir, self.model_name + '_D.pkl')))

由于上一个代码训练有问题,因此我训练的是以下代码:

完整代码链接:
https://github.com/ChenKaiXuSan/ACGAN-PyTorch

模型结构:

# %%
'''
acgan structure.
the network model architecture from the paper [ACGAN](https://arxiv.org/abs/1610.09585)
'''
import torch
import torch.nn as nn

import numpy as np
from torch.nn.modules.activation import Sigmoid
# %%
class Generator(nn.Module):
    '''
    pure Generator structure

    '''    
    def __init__(self, image_size=64, z_dim=100, conv_dim=64, channels = 1, n_classes=10):
        
        super(Generator, self).__init__()
        self.imsize = image_size
        self.channels = channels
        self.z_dim = z_dim
        self.n_classes = n_classes

        self.label_embedding = nn.Embedding(self.n_classes, self.z_dim)
        self.linear = nn.Linear(self.z_dim, 768)

        self.deconv1 = nn.Sequential(
            nn.ConvTranspose2d(768, 384, 4, 1, 0, bias=False),
            nn.BatchNorm2d(384),
            nn.ReLU(True)
        )

        self.deconv2 = nn.Sequential(
            nn.ConvTranspose2d(384, 256, 4, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(True)
        )

        self.deconv3 = nn.Sequential(
            nn.ConvTranspose2d(256, 192, 4, 2, 1, bias=False),
            nn.BatchNorm2d(192),
            nn.ReLU(True),
        )
        
        self.deconv4 = nn.Sequential(
            nn.ConvTranspose2d(192, 64, 4, 2, 1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(True)
        )

        self.last = nn.Sequential(
            nn.ConvTranspose2d(64, self.channels, 4, 2, 1, bias=False),
            nn.Tanh()
        )

    def forward(self, z, labels):
        label_emb = self.label_embedding(labels)
        gen_input = torch.mul(label_emb, z)

        out = self.linear(gen_input)
        out = out.view(-1, 768, 1, 1)

        out = self.deconv1(out)
        out = self.deconv2(out)
        out = self.deconv3(out)
        out = self.deconv4(out)
        
        out = self.last(out) # (*, c, 64, 64)

        return out

# %%
class Discriminator(nn.Module):
    '''
    pure discriminator structure

    '''
    def __init__(self, image_size = 64, conv_dim = 64, channels = 1, n_classes = 10):
        super(Discriminator, self).__init__()
        self.imsize = image_size
        self.channels = channels
        self.n_classes = n_classes

        # (*, c, 64, 64)
        self.conv1 = nn.Sequential(
            nn.Conv2d(self.channels, 16, 3, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.5, inplace=False)
        )

        # (*, 64, 32, 32)
        self.conv2 = nn.Sequential(
            nn.Conv2d(16, 32, 3, 1, 1, bias=False),
            nn.BatchNorm2d(32),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.5, inplace=False)
        )

        # (*, 128, 16, 16)
        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 3, 2, 1, bias=False),
            nn.BatchNorm2d(64),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.5, inplace=False)
        )
        
        # (*, 256, 8, 8)
        self.conv4 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.5, inplace=False)
        )

        self.conv5 = nn.Sequential(
            nn.Conv2d(128, 256, 3, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.5, inplace=False)
        )

        self.conv6 = nn.Sequential(
            nn.Conv2d(256, 512, 3, 1, 1, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.5, inplace=False)
        )

        # output layers
        # (*, 512, 8, 8)
        # dis fc
        self.last_adv = nn.Sequential(
            nn.Linear(8*8*512, 1),
            # nn.Sigmoid()
            )
        # aux classifier fc 
        self.last_aux = nn.Sequential(
            nn.Linear(8*8*512, self.n_classes),
            nn.Softmax(dim=1)
        )

    def forward(self, input):
        out = self.conv1(input)
        out = self.conv2(out)
        out = self.conv3(out)
        out = self.conv4(out)
        out = self.conv5(out)
        out = self.conv6(out)

        flat = out.view(input.size(0), -1)

        fc_dis = self.last_adv(flat)
        fc_aux = self.last_aux(flat)

        return fc_dis.squeeze(), fc_aux

数据加载:

# %%
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals


import torch
import torchvision.transforms as transform
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
# %%
def getdDataset(opt):

    if opt.dataset == 'mnist':
        dst = datasets.MNIST(
            # 相对路径,以调用的文件位置为准——因为我不是每次都想下载数据,因为很多数据是重复的
            root='D:\\ProfessionStudy\\AI\\data',
            train=True,
            download=True,
            transform=transform.Compose(
                [transform.Resize(opt.img_size), transform.ToTensor(), transform.Normalize([0.5], [0.5])]
            )
        )
    elif opt.dataset == 'fashion':
        dst = datasets.FashionMNIST(
            root='D:\\ProfessionStudy\\AI\\data',
            train=True,
            download=True,
            # split='mnist',
            transform=transform.Compose(
                [transform.Resize(opt.img_size), transform.ToTensor(), transform.Normalize([0.5], [0.5])]
            )
        )
    elif opt.dataset == 'cifar10':
        dst = datasets.CIFAR10(
            root='D:\\ProfessionStudy\\AI\\data',
            train=True,
            download=True,
            transform=transform.Compose(
                [transform.Resize(opt.img_size), transform.ToTensor(), transform.Normalize([0.5], [0.5])]
            )
        )

    dataloader = DataLoader(
        dst,
        batch_size=opt.batch_size, 
        shuffle=True,
    )

    return dataloader

# %%
from torchvision.utils import make_grid
import matplotlib.pyplot as plt
import numpy as np

if __name__ == "__main__":
    class opt:
        dataroot = '../../data'
        dataset = 'mnist'
        img_size = 32
        batch_size = 10

    dataloader = getdDataset(opt)
    for i, (imgs, labels) in enumerate(dataloader):
        print(i, imgs.shape, labels.shape)
        print(labels)

        img = imgs[0]
        img = img.numpy()
        img = make_grid(imgs, normalize=True).numpy()
        img = np.transpose(img, (1, 2, 0))

        plt.imshow(img)
        plt.show()
        plt.close()
        break
# %%

训练过程:

# %% 
"""
wgan with different loss function, used the pure dcgan structure.
"""
import os 
import time
import torch
import datetime

import torch.nn as nn 
import torchvision
from torchvision.utils import save_image

from models.acgan import Generator, Discriminator
from utils.utils import *

# %%
class Trainer_acgan(object):
    def __init__(self, data_loader, config):
        super(Trainer_acgan, self).__init__()

        # data loader 
        self.data_loader = data_loader

        # exact model and loss 
        self.model = config.model

        # model hyper-parameters
        self.imsize = config.img_size 
        self.g_num = config.g_num
        self.z_dim = config.z_dim
        self.channels = config.channels
        self.n_classes = config.n_classes
        self.g_conv_dim = config.g_conv_dim
        self.d_conv_dim = config.d_conv_dim

        self.epochs = config.epochs
        self.batch_size = config.batch_size
        self.num_workers = config.num_workers 
        self.g_lr = config.g_lr
        self.d_lr = config.d_lr 
        self.beta1 = config.beta1
        self.beta2 = config.beta2
        self.pretrained_model = config.pretrained_model

        self.dataset = config.dataset 
        self.use_tensorboard = config.use_tensorboard
        # path
        self.image_path = config.dataroot 
        self.log_path = config.log_path
        self.sample_path = config.sample_path
        self.log_step = config.log_step
        self.sample_step = config.sample_step
        self.version = config.version

        # path with version
        self.log_path = os.path.join(config.log_path, self.version)
        self.sample_path = os.path.join(config.sample_path, self.version)

        if self.use_tensorboard:
            self.build_tensorboard()

        self.build_model()

    def train(self):
        '''
        Training
        '''

        # fixed input for debugging 用于每个epoch训练完成生成器后,用来测试其性能的
        fixed_z = tensor2var(torch.randn(self.batch_size, self.z_dim)) # (*, 100)
        fixed_labels = tensor2var(torch.randint(0, self.n_classes, (self.batch_size,), dtype=torch.long))
        # fixed_labels = to_LongTensor(np.array([num for _ in range(self.n_classes) for num in range(self.n_classes)]))

        for epoch in range(self.epochs):
            # start time
            start_time = time.time()

            for i, (real_images, labels) in enumerate(self.data_loader):

                # configure input 
                real_images = tensor2var(real_images)
                labels = tensor2var(labels)
                
                # adversarial ground truths;valid 和 fake 是用于计算判别器损失的对抗性标签。
                valid = tensor2var(torch.full((real_images.size(0),), 0.9)) # (*, )
                fake = tensor2var(torch.full((real_images.size(0),), 0.0)) #(*, )
                
                # ==================== Train D 训练判别器 ==================
                self.D.train()
                self.G.train()

                self.D.zero_grad()

                # 计算真实数据损失
                dis_out_real, aux_out_real = self.D(real_images)

                d_loss_real = self.adversarial_loss_sigmoid(dis_out_real, valid) + self.aux_loss(aux_out_real, labels)

                # noise z for generator
                # 随机初始化假数据和标签
                z = tensor2var(torch.randn(real_images.size(0), self.z_dim)) # *, 100
                gen_labels = tensor2var(torch.randint(0, self.n_classes, (real_images.size(0),), dtype=torch.long))

                # 生成假数据和标签
                fake_images = self.G(z, gen_labels) # (*, c, 64, 64)
                dis_out_fake, aux_out_fake = self.D(fake_images) # (*,)
                # 计算假数据的损失
                d_loss_fake = self.adversarial_loss_sigmoid(dis_out_fake, fake) + self.aux_loss(aux_out_fake, gen_labels)

                # total d loss
                d_loss = d_loss_real + d_loss_fake

                d_loss.backward()
                # update D
                self.d_optimizer.step()

                # calculate dis accuracy
                d_acc = compute_acc(aux_out_real, aux_out_fake, labels, gen_labels)

                # train the generator every 5 steps 每五步训练一次生成器
                if i % self.g_num == 0:

                    # =================== Train G and gumbel =====================
                    self.G.zero_grad()
                    # create random noise 
                    fake_images = self.G(z, gen_labels)

                    # compute loss with fake images 
                    dis_out_fake, aux_out_fake = self.D(fake_images) # batch x n

                    g_loss_fake = self.adversarial_loss_sigmoid(dis_out_fake, valid) + self.aux_loss(aux_out_fake, gen_labels)

                    g_loss_fake.backward()
                    # update G
                    self.g_optimizer.step()
            # 每个epoch训练完成-------------------------------------------------------------------------------------------

            # log to the tensorboard
            self.logger.add_scalar('d_loss', d_loss.data, epoch)
            self.logger.add_scalar('g_loss_fake', g_loss_fake.data, epoch)
            # end one epoch

            # print out log info
            if (epoch) % self.log_step == 0:
                elapsed = time.time() - start_time
                elapsed = str(datetime.timedelta(seconds=elapsed))
                print("Elapsed [{}], G_step [{}/{}], D_step[{}/{}], d_loss: {:.4f}, g_loss: {:.4f}, Acc: {:.4f}"
                    .format(elapsed, epoch, self.epochs, epoch,
                            self.epochs, d_loss.item(), g_loss_fake.item(), d_acc))

            # sample images 
            if (epoch) % self.sample_step == 0:
                self.G.eval()
                # save real image
                save_sample(self.sample_path + '/real_images/', real_images, epoch)
                
                with torch.no_grad():
                    fake_images = self.G(fixed_z, fixed_labels)
                    # save fake image 
                    save_sample(self.sample_path + '/fake_images/', fake_images, epoch)
                    
                # sample sample one images
                save_sample_one_image(self.sample_path, real_images, fake_images, epoch)
        # 所有epoch训练完成-----------------------------------------------------------------------------------------------

    # 建立训练模型
    def build_model(self):

        self.G = Generator(image_size = self.imsize, z_dim = self.z_dim, conv_dim = self.g_conv_dim, channels = self.channels).cuda()
        self.D = Discriminator(image_size = self.imsize, conv_dim = self.d_conv_dim, channels = self.channels).cuda()

        # apply the weights_init to randomly initialize all weights
        # to mean=0, stdev=0.2
        self.G.apply(weights_init)
        self.D.apply(weights_init)
        
        # optimizer 
        self.g_optimizer = torch.optim.Adam(self.G.parameters(), self.g_lr, [self.beta1, self.beta2])
        self.d_optimizer = torch.optim.Adam(self.D.parameters(), self.d_lr, [self.beta1, self.beta2])

        # for orignal gan loss function
        self.adversarial_loss_sigmoid = nn.BCEWithLogitsLoss().cuda()
        self.aux_loss = nn.CrossEntropyLoss().cuda()

        # print networks
        print(self.G)
        print(self.D)

    # 日志记录
    def build_tensorboard(self):
        from torch.utils.tensorboard import SummaryWriter
        self.logger = SummaryWriter(self.log_path)

    def save_image_tensorboard(self, images, text, step):
        if step % 100 == 0:
            img_grid = torchvision.utils.make_grid(images, nrow=8)

            self.logger.add_image(text + str(step), img_grid, step)
            self.logger.close()

额外知识

什么是对数似然函数?

概率:在
给定参数值
的情况下,概率用于
描述未来出现某种情况的观测数据
的可信度。
似然:在
给定观测数据
的情况下,似然用于
描述参数值
的可信度。
极大似然估计:在
给定观测数据
的情况下,某个
参数值
有多个取值可能,但是如果存在某个
参数值
,使其对应的
似然值
最大,那就说明
这个值
就是该参数最可信的
参数值

对数似然函数
极大似然估计的求解方法,往往是对参数θ求导,然后找到导函数为0时对应的参数值,根据函数的单调性,找到极大似然估计时对应的参数θ。

但是在实际问题中,对于大批量的样本(大量的观测结果),其概率值是由很多项相乘组成的式子,对于参数θ的求导,是一个很复杂的问题,于是我们一个直观的想法,就是把它转成对数函数,累乘就变成了累加,即似然函数也就变成了
对数似然函数

对数似然函数的的主要作用,就是用来定义某个机器学习模型的损失函数
,线性回归或者逻辑回归中都可以用到,然后我们再根据
梯度下降/上升法
求解损失函数的最优解,取得最优解时对应的参数
θ
,就是我们机器学习模型想要学习的参数 。

参考:

ACGAN(Auxiliary Classifier GAN)详解与实现(tensorflow2.x实现)-CSDN博客

一天一GAN-day4-ACGAN - 知乎 (zhihu.com)

GAN生成对抗网络-ACGAN原理与基本实现-条件生成对抗网络05 - gemoumou - 博客园 (cnblogs.com)

[
生成对抗网络GAN入门指南](9)ACGAN: Conditional Image Synthesis with Auxiliary Classifier GANs-CSDN博客

【For非数学专业】通俗理解似然函数、概率、极大似然估计和对数似然_对数似然估计-CSDN博客

https://github.com/znxlwm/pytorch-generative-model-collections/tree/master

相关文章

数据库系列:MySQL慢查询分析和性能优化
数据库系列:MySQL索引优化总结(综合版)
数据库系列:高并发下的数据字段变更
数据库系列:覆盖索引和规避回表
数据库系列:数据库高可用及无损扩容
数据库系列:使用高区分度索引列提升性能
数据库系列:前缀索引和索引长度的取舍
数据库系列:MySQL引擎MyISAM和InnoDB的比较
数据库系列:InnoDB下实现高并发控制
数据库系列:事务的4种隔离级别
数据库系列:RR和RC下,快照读的区别
数据库系列:MySQL InnoDB锁机制介绍
数据库系列:MySQL不同操作分别用什么锁?
数据库系列:业内主流MySQL数据中间件梳理

1 背景

前段时间面试新员工,跟候选人沟通起来分页性能问题,正好之前遇到过这类问题,就拿出来再讨论下!

2 分析

分页性能问题,特别是在数据量大的情况下,是一个常见的问题。通常,当我们使用类似
LIMIT

OFFSET
的SQL语句进行分页时,性能问题尤其明显。这是因为随着
OFFSET
的增加,数据库需要跳过更多的行才能获取到需要的数据,这导致了查询时间的增加。

我们在查看前几页的时候,发现速度非常快,比如
limit 200,25
,瞬间就出来了。但是越往后,速度就越慢,特别是百万条之后,卡到不行,那这个是什么原理呢。先看一下我们翻页翻到后面时,查询的sql是怎样的:

1 select * from t_name where c_name1='xxx' order by c_name2 limit 2000000,25;

这种查询的慢,其实是因为limit后面的偏移量太大导致的。比如像上面的
limit 2000000,25
,这个等同于数据库要扫描出
2000025
条数据,然后再丢弃前面的
20000000
条数据,返回剩下
25
条数据给用户,这种取法明显不合理。

image

在《高性能MySQL》第六章:查询性能优化,对这个问题有过详细说明:

分页操作通常会使用limit加上偏移量的办法实现,同时再加上合适的order by子句。但这会出现一个常见问题:当偏移量非常大的时候,它会导致MySQL扫描大量不需要的行然后再抛弃掉。

3 优化

以下是一些优化分页性能的策略:
1. 使用索引+子查询优化

确保你的查询涉及的列(尤其是用于排序和过滤的列)都被索引,没有索引的列会导致数据库进行全表扫描,这会大大降低查询性能。
确保有索引之后,可以在索引树中找到开始位置的 id值,再根据找到的id值查询行数据。

[SQL]
SELECT a.empno,a.empname,a.job,a.sal,b.depno,b.depname
from emp a left join dep b on a.depno = b.depno
where a.id >= (select id from emp order by id limit 100,1)
order by a.id limit 25;
受影响的行: 0
时间: 0.106s

2. 使用更有效的分页技术

考虑使用基于游标或键的分页而不是基于
OFFSET
的分页。例如,如果你正在根据时间戳或ID排序,你可以记住上一页最后一个条目的时间戳或ID,并从那里开始下一页的查询。

记住上次查找结果的主键位置,避免使用偏移量 offset

[SQL]
SELECT a.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
from emp a left join dep b on a.depno = b.depno
where a.id > 100 order by a.id limit 25;
受影响的行: 0
时间: 0.001s

[SQL]
SELECT a.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
from emp a left join dep b on a.depno = b.depno
where a.id > 4800000
order by a.id limit 25;
受影响的行: 0
时间: 0.000s

3. 减少返回的数据量

只选择需要的列,而不是使用
SELECT *
, 减少数据量可以显著提高查询速度。
这个好理解,获取数据,越精简越好,千万别都fetch回来,MySQL准入规范也是这么定的。

4. 分区表

对于非常大的表,考虑使用分区技术。通过将数据分布到不同的分区,可以提高查询性能,因为查询可以在更小的数据集上操作。

5. 使用缓存

对于经常访问的页面,可以考虑使用缓存技术,如Redis或Memcached,来存储查询结果。这样,对于相同的查询请求,可以直接从缓存中获取结果,而不是每次都查询数据库。

6. 考虑物理设计
数据库的物理设计,如硬盘的速度和类型(SSD vs HDD),服务器的内存大小,也会影响查询性能。

4 总结

通过实施上述策略,你可以显著提高数据库分页的性能,尤其是在处理大量数据时,每种方法都有其适用场景,因此我们需要根据具体需求和数据库环境来选择合适的优化策略。

技术背景

在前面一篇
博客
中,我们介绍过在Linux平台下安装和使用免费版本的PyMol。其实同样的这个免费版在Windows平台上(这里以win11为例)也是支持的。

安装流程

这个免费版本的PyMol依赖于Conda,因此首先需要访问
conda官网
下载一个miniconda到本地进行安装,这部分配置都是通过交互界面完成的。安装结束后,可以在win11系统下打开anaconda专属命令行窗口:

需要注意的是,这里如果打开的是默认的终端窗口,有可能使用不了conda。确认conda命令可用之后,输入如下命令安装PyMol:

$ conda install -c conda-forge pymol-open-source

使用方法

在完成上述安装流程之后,免费版的PyMol会在指定目录下(如
\conda\miniconda\Scripts
生成一个
pymol.bat
的脚本文件,如果是收费版的可能会有一个exe可执行文件。虽然是bat文件,但是我们依然可用设置其为默认的打开方式:选中一个
pdb文件
或者其他需要用pymol来可视化的文件,选择默认打开方式,找到这个bat文件。这样以后在windows系统下双击pdb文件,就会默认使用pymol来打开。

常用指令

这里介绍一些比较简单的有可能用到的pymol指令

设置球体大小

在球模型视图下,可以用指令设置球体大小:

set sphere_scale, 0.1, mol_input

设置球体透明度

在球模型视图下,可以设置球体的透明度:

set sphere_transparency, 0.5, mol_input

设置表面透明度

在表面模型下,可以设置表面的透明度:

set transparency, 0.5, mol_input

计算质心

可以选中一些原子然后计算其质心:

centerofmass sele

或者也可以在pymol中直接使用一个python的指令来计算某一个输入分子的质心:

print (cmd.centerofmass(f'mol_input'))

平移分子

可以输入一个数组,对选定的分子平移指定的长度;

translate [20, 20, 20], mol_input

批量计算脚本

pymol支持一个py脚本文件的输入,例如我们可以用脚本来批量计算多个分子的质心:

import pymol
from pymol import cmd

def main():
    pymol.finish_launching()

    objectlist = ['mol_input_{}'.format(i) for i in range(15)]

    for obj_name in objectlist:
        center = cmd.centerofmass(obj_name)
        print('{},'.format(center))
main()

然后在pymol界面上选择
File->Run Script
运行该脚本即可。

总结概要

接上一篇介绍Linux下安装和使用免费版本的PyMol之后,这里再介绍一下Windows系统下的安装方法。同时在本文中列举了一些在PyMol中有可能使用到的脚本指令,例如设置球体模型的大小、设置表面模型的透明度、平移分子和批量执行脚本等操作。

版权声明

本文首发链接为:
https://www.cnblogs.com/dechinphy/p/win-pymol.html

作者ID:DechinPhy

更多原著文章:
https://www.cnblogs.com/dechinphy/

请博主喝咖啡:
https://www.cnblogs.com/dechinphy/gallery/image/379634.html

Visual Studio 订阅(无论是专业版还是企业版)提供的不仅仅是软件使用权;这是一个全面的工具包,旨在显著提高您的开发能力和职业发展。这些订阅每年可以为您节省数千美元,提供各种服务,从每月用于云实验的 Azure 积分,到免费访问 Pluralsight 和 LinkedIn Learning 等高级培训平台。您还可以享受 Azure Dev/Test 定价的折扣,并在 Visual Studio LIVE 等专业活动中节省大量费用!

在我们探索三个令人兴奋的新福利的细节之前,请确保您充分利用了 Visual Studio 订阅。如果已经有一段时间了,请登录 my.visualstudio.com 来查看并激活您的福利,为您配备所有必要的工具和培训,以便在快节奏的技术环境中脱颖而出。

Visual Studio 订阅者的独家新学习路径

发掘专门为付费 Visual Studio 订阅者设计的特殊机会,使您始终处于快速发展的软件开发领域的最前沿。随着像 copilots 这样的人工智能工具的迅速出现,保持良好的技能不仅仅是一种选择,而是势在必行。

这三个新的独家优势旨在提高您的技能,并确保您能够充分利用 Visual Studio、.NET 和 Azure 中的最新进展,帮助您的企业保持竞争优势。利用这些独家优惠,每年节省超过 1500 美元,支持您对技术和开发实践最新进展的了解。以下是最新消息:

Dometrain Pro:全面访问高级课程

概述:通过 Dometrain Pro 提升您的技术专长。无限制地访问超过380小时的高清内容,涵盖从基础编程到高级开发技术的所有内容,所有内容都是为了帮助您跟上行业变化的步伐。

关键福利:在您订阅 Dometrain Pro for Visual Studio 时享受50%的折扣,允许您在一年内访问超过36个综合课程。这是一个很好的资源,可以让您保持最新状态,并加深您的知识库。

通过 DevPass 商业版参加 Tim Corey 的培训

概述:专为开发人员在所有职业阶段,DevPass 商业版提供有针对性的,现实世界的培训。该计划以著名的 Tim Corey 为特色,包括直接适用于日常开发工作和现代软件实践中即将面临的挑战的课程。

关键福利:利用 DevPass Business 的50%折扣,解锁所有28门课程和超过320小时的 HD 全年培训,确保您的技能保持敏锐和相关。

Visual Studio LIVE!活动:独家订阅者折扣

概述:在 Visual Studio LIVE 中参与最新的软件开发!活动,可在现场和虚拟。这些专业发展聚会聚焦前沿技术和实际应用,特别强调人工智能和云计算的进步。获得能够改变您和您的企业如何利用新产品和功能来加速开发并在市场中提升您的解决方案的见解。

关键福利:确保注册的独家折扣,让您以通常成本的一小部分获得行业专家的宝贵见解。这是您了解新特性、工具和策略的门户,可以帮助您的商业合作伙伴实现更多目标。

使用您的福利

释放 Visual Studio 订阅的全部潜力非常简单,并且可以显著增强您的开发体验。以下是您今天就可以开始利用这些机会的方法:

访问 Visual Studio 订阅门户

步骤1:浏览 my.visualstudio.com。这个门户是您通过订阅获得所有福利的门户。

步骤2:使用您的 Visual Studio 凭证登录。如果您不确定您的账户详细信息,请咨询您公司的订阅管理员。

了解您的订阅级别

社区版:社区版是个人开发人员的理想选择,它提供了对基本工具和服务的访问,以启动您的开发项目。但是,请注意,不包括获得高级福利的权利。

专业版:为专业开发人员设计,此订阅层包含更全面的工具和服务。作为专业版订阅者,您将发现许多旨在提高您的生产力和拓宽您的技能的福利。

企业版:最广泛的订阅等级,为旨在充分利用 Visual Studio 功能的团队和组织量身定制。企业版订阅者享有独家特权,包括可以对您的业务成果产生重大影响的高级工具、服务和培训机会。

有些福利是为付费订阅级别保留的,其中包括: Visual Studio Enterprise (Standard), Visual Studio Enterprise subscription with GitHub Enterprise, Visual Studio Enterprise (Annual cloud), Visual Studio Professional (Standard), Visual Studio Professional subscription with GitHub Enterprise 和 Visual Studio Professional (Annual cloud).

探索您的福利

每个订阅等级提供独特的好处,在访问性和范围方面差异很大。登录后,花时间探索与您的订阅级别相关的特定福利。从软件许可证和 Azure 信用额度到培训模块和合作伙伴优惠,每种福利都是精心设计的,以支持您开发工作的不同方面。

今天就行动起来

我们鼓励所有的订阅用户今天登录并探索他们的福利,同时您也在考虑它。通过充分了解和利用您的优势,您不仅可以确保您与最新的技术发展保持同步,还可以确保您在项目和职业中具有竞争优势。

根据订阅类型,访问 my.visualstudio.com 查看完整的福利列表。

原文链接:https://devblogs.microsoft.com/visualstudio/new-visual-studio-benefits-2024/