Pytorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(二)

news2025/4/26 0:18:26

Pytorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(二)

7. 实现条件WGAN-GP

# 训练条件WGAN-GP
def train_conditional_wgan_gp():
    # 用于记录损失
    d_losses = []
    g_losses = []
    
    # 用于记录生成样本的多样性(通过类别分布)
    class_distributions = []
    
    for epoch in range(n_epochs):
        for i, (real_imgs, labels) in enumerate(dataloader):
            real_imgs = real_imgs.to(device)
            labels = labels.to(device)
            batch_size = real_imgs.shape[0]
            
            # ---------------------
            #  训练判别器
            # ---------------------
            optimizer_D.zero_grad()
            
            # 生成随机噪声
            z = torch.randn(batch_size, latent_dim, device=device)
            
            # 为生成器生成随机标签
            gen_labels = torch.randint(0, n_classes, (batch_size,), device=device)
            
            # 生成一批假图像
            fake_imgs = generator(z, gen_labels)
            
            # 判别器前向传播
            real_validity = discriminator(real_imgs, labels)
            fake_validity = discriminator(fake_imgs.detach(), gen_labels)
            
            # 计算梯度惩罚
            gradient_penalty = compute_gradient_penalty(
                discriminator, real_imgs.data, fake_imgs.data, labels
            )
            
            # WGAN-GP 判别器损失
            d_loss = -torch.mean(real_validity) + torch.mean(fake_validity) + lambda_gp * gradient_penalty
            
            d_loss.backward()
            optimizer_D.step()
            
            # 每n_critic次迭代训练一次生成器
            n_critic = 5
            if i % n_critic == 0:
                # ---------------------
                #  训练生成器
                # ---------------------
                optimizer_G.zero_grad()
                
                # 为生成器生成新的随机标签
                gen_labels = torch.randint(0, n_classes, (batch_size,), device=device)
                
                # 生成一批新的假图像
                gen_imgs = generator(z, gen_labels)
                
                # 判别器评估假图像
                fake_validity = discriminator(gen_imgs, gen_labels)
                
                # WGAN 生成器损失
                g_loss = -torch.mean(fake_validity)
                
                g_loss.backward()
                optimizer_G.step()
                
                if i % 50 == 0:
                    print(
                        f"[Epoch {epoch}/{n_epochs}] [Batch {i}/{len(dataloader)}] "
                        f"[D loss: {d_loss.item():.4f}] [G loss: {g_loss.item():.4f}]"
                    )
                    
                    d_losses.append(d_loss.item())
                    g_losses.append(g_loss.item())
        
        # 每个epoch结束后,评估生成样本的类别分布
        if (epoch + 1) % 10 == 0:
            class_dist = evaluate_class_distribution()
            class_distributions.append(class_dist)
            
            # 保存生成的图像样本
            save_sample_images(epoch)
    
    # 绘制损失曲线
    plt.figure(figsize=(10, 5))
    plt.plot(d_losses, label='Discriminator Loss')
    plt.plot(g_losses, label='Generator Loss')
    plt.xlabel('Iterations (x50)')
    plt.ylabel('Loss')
    plt.legend()
    plt.savefig('cond_wgan_gp_loss.png')
    plt.close()
    
    # 绘制类别分布变化
    plot_class_distributions(class_distributions)

# 评估生成样本的类别分布
def evaluate_class_distribution():
    """评估生成样本在各类别上的分布情况"""
    # 创建一个预训练的分类器
    classifier = torchvision.models.resnet18(pretrained=True)
    # 修改第一个卷积层以适应灰度图
    classifier.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
    # 修改最后的全连接层以适应MNIST的10个类别
    classifier.fc = nn.Linear(classifier.fc.in_features, 10)
    
    # 加载预先训练好的MNIST分类器权重(这里假设我们有一个预训练的模型)
    # classifier.load_state_dict(torch.load('mnist_classifier.pth'))
    
    # 简化起见,这里我们使用一个简单的CNN分类器
    classifier = nn.Sequential(
        nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Flatten(),
        nn.Linear(64 * 7 * 7, 128),
        nn.ReLU(),
        nn.Linear(128, 10)
    ).to(device)
    
    # 这里假设这个简单分类器已经在MNIST上训练好了
    # 实际应用中,应该加载一个预先训练好的模型
    
    # 生成1000个样本
    z = torch.randn(1000, latent_dim, device=device)
    # 均匀采样所有类别
    gen_labels = torch.tensor([i % 10 for i in range(1000)], device=device)
    gen_imgs = generator(z, gen_labels)
    
    # 使用分类器预测类别
    with torch.no_grad():
        classifier.eval()
        preds = torch.softmax(classifier(gen_imgs), dim=1)
        pred_labels = torch.argmax(preds, dim=1)
    
    # 计算每个类别的样本数量
    class_counts = torch.zeros(10)
    for i in range(10):
        class_counts[i] = (pred_labels == i).sum().item() / 1000
    
    return class_counts.numpy()

# 绘制类别分布变化
def plot_class_distributions(class_distributions):
    """绘制生成样本类别分布的变化"""
    epochs = [10, 20, 30, 40, 50]  # 假设每10个epoch评估一次
    plt.figure(figsize=(12, 8))
    
    for i, dist in enumerate(class_distributions):
        plt.subplot(len(class_distributions), 1, i+1)
        plt.bar(np.arange(10), dist)
        plt.ylabel(f'Epoch {epochs[i]}')
        plt.ylim(0, 0.3)  # 限制y轴范围,便于比较
        if i == len(class_distributions) - 1:
            plt.xlabel('Digit Class')
    
    plt.tight_layout()
    plt.savefig('class_distribution.png')
    plt.close()

# 保存样本图像(条件版本)
def save_sample_images(epoch):
    """保存按类别排列的样本图像"""
    # 为每个类别生成样本
    n_row = 10  # 每个类别一行
    n_col = 10  # 每个类别10个样本
    
    fig, axs = plt.subplots(n_row, n_col, figsize=(12, 12))
    
    for i in range(n_row):
        # 固定类别
        fixed_class = torch.tensor([i] * n_col, device=device)
        # 随机噪声
        z = torch.randn(n_col, latent_dim, device=device)
        # 生成图像
        gen_imgs = generator(z, fixed_class).detach().cpu()
        # 转换到[0, 1]范围
        gen_imgs = 0.5 * gen_imgs + 0.5
        
        # 显示图像
        for j in range(n_col):
            axs[i, j].imshow(gen_imgs[j, 0, :, :], cmap='gray')
            axs[i, j].axis('off')
    
    plt.savefig(f'cond_wgan_gp_epoch_{epoch+1}.png')
    plt.close()

# 运行条件WGAN-GP训练
if __name__ == "__main__":
    train_conditional_wgan_gp()

上述代码实现了一个条件WGAN-GP模型,主要区别在于:

  1. 条件输入:生成器和判别器都接收类别标签作为额外输入
  2. 嵌入层:使用嵌入层将类别标签转换为嵌入向量
  3. 类别多样性评估:添加了评估生成样本类别分布的功能
  4. 可视化:按类别排列生成样本,便于观察每个类别的质量

8. 无监督与条件生成的模式坍塌对比实验

为了更直观地比较无监督生成和条件生成在模式坍塌方面的差异,我们可以设计一个实验,分别训练无监督WGAN-GP和条件WGAN-GP,然后比较它们生成样本的模式覆盖情况。

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

# 假设我们已经训练好了无监督WGAN-GP和条件WGAN-GP模型
# 分别为 unsupervised_generator 和 conditional_generator

def analyze_mode_collapse():
    """分析并比较无监督和条件生成在模式坍塌方面的差异"""
    # 生成样本数量
    n_samples = 1000
    
    # 1. 从无监督生成器生成样本
    z_unsupervised = torch.randn(n_samples, latent_dim, device=device)
    unsupervised_samples = unsupervised_generator(z_unsupervised).detach().cpu()
    
    # 2. 从条件生成器生成样本(均匀覆盖所有类别)
    z_conditional = torch.randn(n_samples, latent_dim, device=device)
    conditional_labels = torch.tensor([i % 10 for i in range(n_samples)], device=device)
    conditional_samples = conditional_generator(z_conditional, conditional_labels).detach().cpu()
    
    # 3. 获取真实MNIST样本
    real_loader = torch.utils.data.DataLoader(mnist_dataset, batch_size=n_samples, shuffle=True)
    real_samples, _ = next(iter(real_loader))
    
    # 4. 使用预训练的分类器分类所有样本
    classifier = create_mnist_classifier()  # 假设我们有一个创建分类器的函数
    
    # 分类无监督生成的样本
    unsupervised_predictions = classify_samples(classifier, unsupervised_samples)
    # 分类条件生成的样本
    conditional_predictions = classify_samples(classifier, conditional_samples)
    # 分类真实样本
    real_predictions = classify_samples(classifier, real_samples)
    
    # 5. 计算各类别的样本分布
    unsupervised_distribution = compute_class_distribution(unsupervised_predictions)
    conditional_distribution = compute_class_distribution(conditional_predictions)
    real_distribution = compute_class_distribution(real_predictions)
    
    # 6. 计算分布的均匀度(使用熵)
    unsupervised_entropy = compute_entropy(unsupervised_distribution)
    conditional_entropy = compute_entropy(conditional_distribution)
    real_entropy = compute_entropy(real_distribution)
    
    print(f"无监督生成分布熵: {unsupervised_entropy:.4f}")
    print(f"条件生成分布熵: {conditional_entropy:.4f}")
    print(f"真实数据分布熵: {real_entropy:.4f}")
    
    # 7. 可视化样本分布
    visualize_distributions(
        unsupervised_distribution,
        conditional_distribution,
        real_distribution
    )
    
    # 8. 使用t-SNE将样本投影到二维空间进行可视化
    visualize_tsne(
        unsupervised_samples,
        conditional_samples,
        real_samples
    )

def create_mnist_classifier():
    """创建一个简单的MNIST分类器"""
    model = nn.Sequential(
        nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Flatten(),
        nn.Linear(64 * 7 * 7, 128),
        nn.ReLU(),
        nn.Linear(128, 10)
    ).to(device)
    
    # 这里假设分类器已经训练好了
    # model.load_state_dict(torch.load('mnist_classifier.pth'))
    
    return model

def classify_samples(classifier, samples):
    """使用分类器对样本进行分类"""
    with torch.no_grad():
        classifier.eval()
        # 确保样本在正确的设备上
        samples = samples.to(device)
        # 前向传播
        logits = classifier(samples)
        # 获取预测类别
        predictions = torch.argmax(logits, dim=1)
    
    return predictions.cpu().numpy()

def compute_class_distribution(predictions):
    """计算类别分布"""
    n_samples = len(predictions)
    distribution = np.zeros(10)
    
    for i in range(10):
        distribution[i] = np.sum(predictions == i) / n_samples
    
    return distribution

def compute_entropy(distribution):
    """计算分布的熵,衡量分布的均匀度"""
    # 防止log(0)
    distribution = distribution + 1e-10
    # 归一化
    distribution = distribution / np.sum(distribution)
    # 计算熵
    entropy = -np.sum(distribution * np.log2(distribution))
    
    return entropy

def visualize_distributions(unsupervised_dist, conditional_dist, real_dist):
    """可视化三种样本的类别分布"""
    plt.figure(figsize=(12, 5))
    
    width = 0.25
    x = np.arange(10)
    
    plt.bar(x - width, unsupervised_dist, width, label='Unsupervised')
    plt.bar(x, conditional_dist, width, label='Conditional')
    plt.bar(x + width, real_dist, width, label='Real')
    
    plt.xlabel('Digit Class')
    plt.ylabel('Proportion')
    plt.title('Class Distribution Comparison')
    plt.xticks(x)
    plt.legend()
    
    plt.tight_layout()
    plt.savefig('distribution_comparison.png')
    plt.close()

def visualize_tsne(unsupervised_samples, conditional_samples, real_samples):
    """使用t-SNE将样本投影到二维空间并可视化"""
    # 准备数据
    unsupervised_flat = unsupervised_samples.view(unsupervised_samples.size(0), -1).numpy()
    conditional_flat = conditional_samples.view(conditional_samples.size(0), -1).numpy()
    real_flat = real_samples.view(real_samples.size(0), -1).numpy()
    
    # 合并所有样本
    all_samples = np.vstack([unsupervised_flat, conditional_flat, real_flat])
    
    # 使用t-SNE降维
    tsne = TSNE(n_components=2, random_state=42)
    all_samples_tsne = tsne.fit_transform(all_samples)
    
    # 分离结果
    n = unsupervised_flat.shape[0]
    unsupervised_tsne = all_samples_tsne[:n]
    conditional_tsne = all_samples_tsne[n:2*n]
    real_tsne = all_samples_tsne[2*n:]
    
    # 可视化
    plt.figure(figsize=(10, 8))
    
    plt.scatter(unsupervised_tsne[:, 0], unsupervised_tsne[:, 1], 
                c='blue', label='Unsupervised', alpha=0.5, s=10)
    plt.scatter(conditional_tsne[:, 0], conditional_tsne[:, 1], 
                c='green', label='Conditional', alpha=0.5, s=10)
    plt.scatter(real_tsne[:, 0], real_tsne[:, 1], 
                c='red', label='Real', alpha=0.5, s=10)
    
    plt.legend()
    plt.title('t-SNE Visualization of Generated and Real Samples')
    
    plt.savefig('tsne_visualization.png')
    plt.close()

# 运行分析
if __name__ == "__main__":
    analyze_mode_collapse()

上述代码实现了一个比较实验,用于分析无监督WGAN-GP和条件WGAN-GP在模式坍塌方面的差异。主要的分析方法包括:

  1. 类别分布分析:使用预训练的分类器对生成样本进行分类,统计各类别的样本比例
  2. 熵计算:使用熵来衡量分布的均匀度,熵越高表示分布越均匀,模式覆盖越全面
  3. t-SNE可视化:使用t-SNE将高维样本投影到二维空间,直观地观察样本分布

通过这些分析,我们可以定量和定性地比较两种方法在模式坍塌方面的表现。

9. 模式坍塌问题的其他解决方案

除了条件生成和WGAN-GP,还有其他方法可以缓解GAN的模式坍塌问题:

9.1 解决模式坍塌的方法比较表

方法核心思想优点缺点实现复杂度
WGAN-GP使用Wasserstein距离和梯度惩罚训练稳定,理论基础强计算成本高中等
条件GAN添加条件信息引导生成可控生成,强制覆盖所有类别需要标签数据
小批量判别 (Minibatch Discrimination)判别器考虑样本间的相似性直接鼓励样本多样性计算开销增加
展开GAN (Unrolled GAN)展开判别器的k步更新提供更稳定的梯度训练速度慢
BEGAN使用自编码器作为判别器平衡生成器和判别器训练模型结构复杂中等
PacGAN将多个样本打包传给判别器实现简单,效果明显需要更多内存
集成多个生成器使用多个生成器捕捉不同模式天然覆盖多个模式训练困难,参数增加
基于能量的GAN (EBGAN)将GAN视为能量模型更好的稳定性理解难度大中等

9.2 小批量判别的PyTorch实现

下面是小批量判别(Minibatch Discrimination)的PyTorch实现示例,这是另一种解决模式坍塌的有效方法:

import torch
import torch.nn as nn

class MinibatchDiscrimination(nn.Module):
    """小批量判别层,用于缓解模式坍塌"""
    def __init__(self, input_features, output_features, kernel_dim=5):
        super(MinibatchDiscrimination, self).__init__()
        
        self.input_features = input_features
        self.output_features = output_features
        self.kernel_dim = kernel_dim
        
        # 参数张量 [input_features, output_features * kernel_dim]
        self.T = nn.Parameter(
            torch.randn(input_features, output_features * kernel_dim)
        )
    
    def forward(self, x):
        # x形状: [batch_size, input_features]
        batch_size = x.size(0)
        
        # 将输入与参数相乘 -> [batch_size, output_features, kernel_dim]
        matrices = x.mm(self.T).view(batch_size, self.output_features, self.kernel_dim)
        
        # 扩展为广播形状 -> [batch_size, batch_size, output_features, kernel_dim]
        matrices_expanded = matrices.unsqueeze(1)
        matrices_transposed = matrices.unsqueeze(0)
        
        # 计算L1距离 -> [batch_size, batch_size, output_features]
        l1_dist = torch.abs(matrices_expanded - matrices_transposed).sum(dim=3)
        
        # 应用负指数核 -> [batch_size, batch_size, output_features]
        K = torch.exp(-l1_dist)
        
        # 将自身的相似度设为0(对角线)
        mask = (torch.ones(batch_size, batch_size) - torch.eye(batch_size)).unsqueeze(2)
        mask = mask.to(x.device)
        K = K * mask
        
        # 对每个样本,计算其与其他所有样本的相似度之和 -> [batch_size, output_features]
        minibatch_features = K.sum(dim=1)
        
        # 将小批量判别特征与原始特征连接
        return torch.cat([x, minibatch_features], dim=1)

# 使用小批量判别的判别器示例
class DiscriminatorWithMinibatch(nn.Module):
    def __init__(self, img_shape, hidden_dim=512, minibatch_features=32):
        super(DiscriminatorWithMinibatch, self).__init__()
        
        self.img_flat_dim = int(np.prod(img_shape))
        
        # 特征提取层
        self.features = nn.Sequential(
            nn.Linear(self.img_flat_dim, hidden_dim),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(hidden_dim, hidden_dim),
            nn.LeakyReLU(0.2, inplace=True)
        )
        
        # 小批量判别层
        self.minibatch = MinibatchDiscrimination(
            hidden_dim, minibatch_features
        )
        
        # 输出层
        self.output = nn.Linear(hidden_dim + minibatch_features, 1)
    
    def forward(self, img):
        # 将图像展平
        img_flat = img.view(img.size(0), -1)
        
        # 提取特征
        features = self.features(img_flat)
        
        # 应用小批量判别
        enhanced_features = self.minibatch(features)
        
        # 输出
        validity = self.output(enhanced_features)
        
        return validity

小批量判别通过考虑样本之间的相似性来鼓励生成样本的多样性。它计算批次中每个样本与其他样本的距离,并将这些距离信息作为额外特征传递给判别器,使判别器能够识别出生成器是否只生成相似的样本。

10. 生成对抗网络的评估指标

评估GAN的性能是一个复杂的问题,特别是在衡量生成样本的质量和多样性方面。以下是一些常用的评估指标:

10.1 常用GAN评估指标比较表

指标衡量内容优点缺点适用场景
Inception Score (IS)样本质量和多样性易于实现,广泛使用对噪声敏感,不考虑与真实分布的匹配度图像生成,特别是有标签的数据集
Fréchet Inception Distance (FID)生成分布与真实分布的相似度对模式坍塌敏感,更符合人类判断计算复杂度高各类图像生成任务
多样性指数 (Diversity Score)生成样本的多样性直接衡量样本间距离不考虑样本质量检测模式坍塌
精度与召回率 (Precision & Recall)样本质量和覆盖率分离质量和覆盖率的测量实现复杂需要平衡质量和多样性的场景
分类器两样本测试 (C2ST)真假样本的可区分性直观且有理论保证需要训练额外的分类器校验生成分布与真实分布的接近程度
知觉路径长度 (PPL)潜在空间平滑度衡量生成器质量计算开销大评估StyleGAN等高质量生成模型

10.2 FID指标的PyTorch实现

下面是Fréchet Inception Distance (FID)指标的PyTorch实现,这是评估GAN生成质量的常用指标:

import torch
import torch.nn as nn
import torchvision.models as models
import numpy as np
from scipy import linalg

class InceptionV3Features(nn.Module):
    """提取InceptionV3特征的模型"""
    def __init__(self):
        super(InceptionV3Features, self).__init__()
        # 加载预训练的InceptionV3
        inception = models.inception_v3(pretrained=True)
        # 使用到Mixed_7c层
        self.feature_extractor = nn.Sequential(*list(inception.children())[:-4])
        # 设置为评估模式
        self.feature_extractor.eval()
        # 冻结参数
        for param in self.feature_extractor.parameters():
            param.requires_grad = False
    
    def forward(self, x):
        # InceptionV3期望输入为[0, 1]范围的RGB图像
        # 并且预处理为[-1, 1]
        if x.shape[1] == 1:  # 如果是灰度图像,复制到3个通道
            x = x.repeat(1, 3, 1, 1)
        
        # 调整大小以符合InceptionV3的输入要求
        if x.shape[2] != 299 or x.shape[3] != 299:
            x = nn.functional.interpolate(x, size=(299, 299), mode='bilinear', align_corners=False)
        
        # 特征提取
        with torch.no_grad():
            features = self.feature_extractor(x)
        
        return features

def calculate_fid(real_features, fake_features):
    """计算Fréchet Inception Distance"""
    # 转换为numpy数组
    real_features = real_features.detach().cpu().numpy()
    fake_features = fake_features.detach().cpu().numpy()
    
    # 计算均值和协方差
    mu_real = np.mean(real_features, axis=0)
    mu_fake = np.mean(fake_features, axis=0)
    
    sigma_real = np.cov(real_features, rowvar=False)
    sigma_fake = np.cov(fake_features, rowvar=False)
    
    # 计算FID
    diff = mu_real - mu_fake
    # 添加小的对角项以增加数值稳定性
    sigma_real += np.eye(sigma_real.shape[0]) * 1e-6
    sigma_fake += np.eye(sigma_fake.shape[0]) * 1e-6
    
    # 计算平方根协方差矩阵乘积
    covmean = linalg.sqrtm(sigma_real @ sigma_fake)
    
    # 检查是否有复数
    if np.iscomplexobj(covmean):
        covmean = covmean.real
    
    # 计算FID
    fid = diff @ diff + np.trace(sigma_real + sigma_fake - 2 *
    def calculate_fid(real_features, fake_features):
    """计算Fréchet Inception Distance"""
    # 转换为numpy数组
    real_features = real_features.detach().cpu().numpy()
    fake_features = fake_features.detach().cpu().numpy()
    
    # 计算均值和协方差
    mu_real = np.mean(real_features, axis=0)
    mu_fake = np.mean(fake_features, axis=0)
    
    sigma_real = np.cov(real_features, rowvar=False)
    sigma_fake = np.cov(fake_features, rowvar=False)
    
    # 计算FID
    diff = mu_real - mu_fake
    # 添加小的对角项以增加数值稳定性
    sigma_real += np.eye(sigma_real.shape[0]) * 1e-6
    sigma_fake += np.eye(sigma_fake.shape[0]) * 1e-6
    
    # 计算平方根协方差矩阵乘积
    covmean = linalg.sqrtm(sigma_real @ sigma_fake)
    
    # 检查是否有复数
    if np.iscomplexobj(covmean):
        covmean = covmean.real
    
    # 计算FID
    fid = diff @ diff + np.trace(sigma_real + sigma_fake - 2 * covmean)
    
    return fid

def compute_fid_for_gan(real_loader, generator, n_samples=10000, batch_size=50, device='cuda'):
    """为GAN计算FID分数"""
    # 初始化Inception特征提取器
    feature_extractor = InceptionV3Features().to(device)
    
    # 收集真实样本的特征
    real_features = []
    for i, (real_imgs, _) in enumerate(real_loader):
        if i * batch_size >= n_samples:
            break
        
        real_imgs = real_imgs.to(device)
        with torch.no_grad():
            features = feature_extractor(real_imgs)
            features = features.view(features.size(0), -1)
            real_features.append(features)
    
    real_features = torch.cat(real_features, dim=0)[:n_samples]
    
    # 收集生成样本的特征
    fake_features = []
    n_batches = n_samples // batch_size
    
    for i in range(n_batches):
        # 生成假样本
        z = torch.randn(batch_size, latent_dim, device=device)
        fake_imgs = generator(z)
        
        with torch.no_grad():
            features = feature_extractor(fake_imgs)
            features = features.view(features.size(0), -1)
            fake_features.append(features)
    
    fake_features = torch.cat(fake_features, dim=0)
    
    # 计算FID
    fid = calculate_fid(real_features, fake_features)
    
    return fid
   

FID是一种常用的评估GAN生成质量的指标,它通过比较真实样本和生成样本在特征空间中的统计差异来衡量生成质量。FID值越低表示生成样本与真实样本越相似。

11. 模式坍塌实验与可视化分析

为了更直观地理解模式坍塌问题以及WGAN-GP和条件生成如何缓解这一问题,我们可以设计一个专门的实验,针对一个简单的多模态分布。

11.1 模式坍塌实验设计

我们将使用一个由多个高斯分布组成的混合分布作为目标分布,然后分别使用普通GAN、WGAN-GP和条件WGAN-GP来学习这个分布。

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
import seaborn as sns

# 设置随机种子
torch.manual_seed(42)
np.random.seed(42)

# 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 生成混合高斯分布
def generate_mixture_of_gaussians(n_samples=10000, n_components=8, random_state=42):
    """生成二维混合高斯分布"""
    centers = np.array([
        [0, 0],
        [5, 5],
        [5, -5],
        [-5, 5],
        [-5, -5],
        [0, 5],
        [5, 0],
        [-5, 0],
        [0, -5]
    ])[:n_components]
    
    X, y = make_blobs(
        n_samples=n_samples,
        centers=centers,
        cluster_std=0.5,
        random_state=random_state
    )
    
    # 归一化到[-1, 1]范围
    X = X / np.abs(X).max(axis=0, keepdims=True) * 0.9
    
    return X, y

# 数据加载器
class GaussianMixtureDataset(torch.utils.data.Dataset):
    def __init__(self, n_samples=10000, n_components=8):
        self.data, self.labels = generate_mixture_of_gaussians(n_samples, n_components)
        self.data = torch.FloatTensor(self.data)
        self.labels = torch.LongTensor(self.labels)
    
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        return self.data[idx], self.labels[idx]

# 简单生成器
class SimpleGenerator(nn.Module):
    def __init__(self, latent_dim=2, output_dim=2, hidden_dim=128):
        super(SimpleGenerator, self).__init__()
        
        self.model = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim),
            nn.Tanh()  # 输出范围为[-1, 1]
        )
    
    def forward(self, z):
        return self.model(z)

# 简单判别器
class SimpleDiscriminator(nn.Module):
    def __init__(self, input_dim=2, hidden_dim=128):
        super(SimpleDiscriminator, self).__init__()
        
        self.model = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1)
        )
    
    def forward(self, x):
        return self.model(x)

# 条件生成器
class ConditionalGenerator(nn.Module):
    def __init__(self, latent_dim=2, output_dim=2, hidden_dim=128, n_classes=8):
        super(ConditionalGenerator, self).__init__()
        
        self.label_embedding = nn.Embedding(n_classes, n_classes)
        
        self.model = nn.Sequential(
            nn.Linear(latent_dim + n_classes, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim),
            nn.Tanh()  # 输出范围为[-1, 1]
        )
    
    def forward(self, z, labels):
        label_embedding = self.label_embedding(labels)
        z = torch.cat([z, label_embedding], dim=1)
        return self.model(z)

# 条件判别器
class ConditionalDiscriminator(nn.Module):
    def __init__(self, input_dim=2, hidden_dim=128, n_classes=8):
        super(ConditionalDiscriminator, self).__init__()
        
        self.label_embedding = nn.Embedding(n_classes, n_classes)
        
        self.model = nn.Sequential(
            nn.Linear(input_dim + n_classes, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1)
        )
    
    def forward(self, x, labels):
        label_embedding = self.label_embedding(labels)
        x = torch.cat([x, label_embedding], dim=1)
        return self.model(x)

# 计算WGAN-GP的梯度惩罚
def compute_gradient_penalty(D, real_samples, fake_samples, labels=None):
    """计算梯度惩罚"""
    # 随机插值系数
    alpha = torch.rand(real_samples.size(0), 1, device=device)
    # 创建插值样本
    interpolates = (alpha * real_samples + ((1 - alpha) * fake_samples)).requires_grad_(True)
    
    # 计算判别器输出
    if labels is not None:
        d_interpolates = D(interpolates, labels)
    else:
        d_interpolates = D(interpolates)
    
    # 创建虚拟输出1.0
    fake = torch.ones(real_samples.size(0), 1, device=device, requires_grad=False)
    
    # 计算梯度
    gradients = torch.autograd.grad(
        outputs=d_interpolates,
        inputs=interpolates,
        grad_outputs=fake,
        create_graph=True,
        retain_graph=True,
        only_inputs=True
    )[0]
    
    # 计算梯度范数
    gradients = gradients.view(gradients.size(0), -1)
    gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
    
    return gradient_penalty

# 可视化函数
def visualize_distributions(real_data, gen_data, title):
    """可视化真实分布和生成分布"""
    plt.figure(figsize=(12, 5))
    
    # 真实数据分布
    plt.subplot(1, 2, 1)
    sns.kdeplot(x=real_data[:, 0], y=real_data[:, 1], cmap="Blues", fill=True, alpha=0.7)
    plt.scatter(real_data[:, 0], real_data[:, 1], s=1, c='blue', alpha=0.5)
    plt.title('Real Data Distribution')
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)
    
    # 生成数据分布
    plt.subplot(1, 2, 2)
    sns.kdeplot(x=gen_data[:, 0], y=gen_data[:, 1], cmap="Reds", fill=True, alpha=0.7)
    plt.scatter(gen_data[:, 0], gen_data[:, 1], s=1, c='red', alpha=0.5)
    plt.title('Generated Data Distribution')
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)
    
    plt.suptitle(title)
    plt.tight_layout()
    plt.savefig(f"{title.replace(' ', '_')}.png")
    plt.close()

# 训练函数
def train_gan_variants(n_components=8, n_epochs=500, batch_size=128, latent_dim=2):
    """训练不同的GAN变体并比较它们在模式坍塌上的差异"""
    # 准备数据
    dataset = GaussianMixtureDataset(n_samples=10000, n_components=n_components)
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
    
    # 可视化真实数据分布
    real_samples = dataset.data.numpy()
    plt.figure(figsize=(6, 6))
    sns.kdeplot(x=real_samples[:, 0], y=real_samples[:, 1], cmap="Blues", fill=True)
    plt.scatter(real_samples[:, 0], real_samples[:, 1], s=1, c='blue', alpha=0.5)
    plt.title('Real Data Distribution')
    plt.xlim(-1.2, 1.2)
    plt.ylim(-1.2, 1.2)
    plt.savefig("real_distribution.png")
    plt.close()
    
    # 1. 训练普通GAN
    vanilla_generator = SimpleGenerator(latent_dim=latent_dim).to(device)
    vanilla_discriminator = SimpleDiscriminator().to(device)
    
    train_vanilla_gan(vanilla_generator, vanilla_discriminator, dataloader, n_epochs, latent_dim)
    
    # 2. 训练WGAN-GP
    wgan_generator = SimpleGenerator(latent_dim=latent_dim).to(device)
    wgan_discriminator = SimpleDiscriminator().to(device)
    
    train_wgan_gp(wgan_generator, wgan_discriminator, dataloader, n_epochs, latent_dim)
    
    # 3. 训练条件WGAN-GP
    cond_generator = ConditionalGenerator(latent_dim=latent_dim, n_classes=n_components).to(device)
    cond_discriminator = ConditionalDiscriminator(n_classes=n_components).to(device)
    
    train_conditional_wgan_gp(cond_generator, cond_discriminator, dataloader, n_epochs, latent_dim, n_components)
    
    # 生成样本并可视化
    # 普通GAN生成样本
    z = torch.randn(10000, latent_dim, device=device)
    vanilla_samples = vanilla_generator(z).detach().cpu().numpy()
    
    # WGAN-GP生成样本
    z = torch.randn(10000, latent_dim, device=device)
    wgan_samples = wgan_generator(z).detach().cpu().numpy()
    
    # 条件WGAN-GP生成样本
    z = torch.randn(10000, latent_dim, device=device)
    # 为每个组件生成均匀样本
    labels = torch.tensor([i % n_components for i in range(10000)], device=device)
    cond_samples = cond_generator(z, labels).detach().cpu().numpy()
    
    # 可视化比较
    visualize_distributions(real_samples, vanilla_samples, "Vanilla GAN")
    visualize_distributions(real_samples, wgan_samples, "WGAN-GP")
    visualize_distributions(real_samples, cond_samples, "Conditional WGAN-GP")
    
    # 计算模式覆盖率
    vanilla_coverage = calculate_mode_coverage(real_samples, vanilla_samples, n_components)
    wgan_coverage = calculate_mode_coverage(real_samples, wgan_samples, n_components)
    cond_coverage = calculate_mode_coverage(real_samples, cond_samples, n_components)
    
    print(f"Vanilla GAN Mode Coverage: {vanilla_coverage:.2f}")
    print(f"WGAN-GP Mode Coverage: {wgan_coverage:.2f}")
    print(f"Conditional WGAN-GP Mode Coverage: {cond_coverage:.2f}")

# 训练普通GAN
def train_vanilla_gan(generator, discriminator, dataloader, n_epochs, latent_dim):
    """训练普通GAN"""
    # 优化器
    optimizer_G = optim.Adam(generator.parameters(), lr=0.0002, betas=(0.5, 0.999))
    optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))
    
    # 损失函数
    adversarial_loss = nn.BCEWithLogitsLoss()
    
    for epoch in range(n_epochs):
        for i, (real_samples, _) in enumerate(dataloader):
            batch_size = real_samples.size(0)
            
            # 真实样本标签: 1
            real_labels = torch.ones(batch_size, 1, device=device)
            # 虚假样本标签: 0
            fake_labels = torch.zeros(batch_size, 1, device=device)
            
            # 准备真实样本
            real_samples = real_samples.to(device)
            
            # --------------------
            # 训练判别器
            # --------------------
            optimizer_D.zero_grad()
            
            # 判别真实样本
            real_output = discriminator(real_samples)
            d_real_loss = adversarial_loss(real_output, real_labels)
            
            # 生成虚假样本
            z = torch.randn(batch_size, latent_dim, device=device)
            fake_samples = generator(z)
            
            # 判别虚假样本
            fake_output = discriminator(fake_samples.detach())
            d_fake_loss = adversarial_loss(fake_output, fake_labels)
            
            # 判别器总损失
            d_loss = d_real_loss + d_fake_loss
            d_loss.backward()
            optimizer_D.step()
            
            # --------------------
            # 训练生成器
            # --------------------
            optimizer_G.zero_grad()
            
            # 再次判别虚假样本,目标是让判别器认为它们是真的
            fake_output = discriminator(fake_samples)
            g_loss = adversarial_loss(fake_output, real_labels)
            
            g_loss.backward()
            optimizer_G.step()
            
        if (epoch + 1) % 100 == 0:
            print(f"Vanilla GAN - Epoch {epoch+1}/{n_epochs}, D loss: {d_loss.item():.4f}, G loss: {g_loss.item():.4f}")

# 训练WGAN-GP
def train_wgan_gp(generator, discriminator, dataloader, n_epochs, latent_dim, lambda_gp=10):
    """训练WGAN-GP"""
    # 优化器
    optimizer_G = optim.Adam(generator.parameters(), lr=0.0002, betas=(0, 0.9))
    optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0, 0.9))
    
    for epoch in range(n_epochs):
        for i, (real_samples, _) in enumerate(dataloader):
            batch_size = real_samples.size(0)
            
            # 准备真实样本
            real_samples = real_samples.to(device)
            
            # --------------------
            # 训练判别器
            # --------------------
            optimizer_D.zero_grad()
            
            # 生成虚假样本
            z = torch.randn(batch_size, latent_dim, device=device)
            fake_samples = generator(z)
            
            # 判别器前向传播
            real_validity = discriminator(real_samples)
            fake_validity = discriminator(fake_samples.detach())
            
            # 计算梯度惩罚
            gradient_penalty = compute_gradient_penalty(discriminator, real_samples, fake_samples)
            
            # WGAN-GP 判别器损失
            d_loss = -torch.mean(real_validity) + torch.mean(fake_validity) + lambda_gp * gradient_penalty
            
            d_loss.backward()
            optimizer_D.step()
            
            # 每n_critic次迭代训练一次生成器
            if i % 5 == 0:
                # --------------------
                # 训练生成器
                # --------------------
                optimizer_G.zero_grad()
                
                # 生成新的假样本
                z = torch.randn(batch_size, latent_dim, device=device)
                gen_samples = generator(z)
                
                # 判别器评估假样本
                fake_validity = discriminator(gen_samples)
                
                # WGAN 生成器损失
                g_loss = -torch.mean(fake_validity)
                
                g_loss.backward()
                optimizer_G.step()
            
        if (epoch + 1) % 100 == 0:
            print(f"WGAN-GP - Epoch {epoch+1}/{n_epochs}, D loss: {d_loss.item():.4f}, G loss: {g_loss.item():.4f}")

# 训练条件WGAN-GP
def train_conditional_wgan_gp(generator, discriminator, dataloader, n_epochs, latent_dim, n_components, lambda_gp=10):
    """训练条件WGAN-GP"""
    # 优化器
    optimizer_G = optim.Adam(generator.parameters(), lr=0.0002, betas=(0, 0.9))
    optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0, 0.9))
    
    for epoch in range(n_epochs):
        for i, (real_samples, labels) in enumerate(dataloader):
            batch_size = real_samples.size(0)
            
            # 准备真实样本和标签
            real_samples = real_samples.to(device)
            labels = labels.to(device)
            
            # --------------------
            # 训练判别器
            # --------------------
            optimizer_D.zero_grad()
            
            # 生成虚假样本
            z = torch.randn(batch_size, latent_dim, device=device)
            fake_samples = generator(z, labels)
            
            # 判别器前向传播
            real_validity = discriminator(real_samples, labels)
            fake_validity = discriminator(fake_samples.detach(), labels)
            
            # 计算梯度惩罚
            gradient_penalty = compute_gradient_penalty(discriminator, real_samples, fake_samples, labels)
            
            # WGAN-GP 判别器损失
            d_loss = -torch.mean(real_validity) + torch.mean(fake_validity) + lambda_gp * gradient_penalty
            
            d_loss.backward()
            optimizer_D.step()
            
            # 每n_critic次迭代训练一次生成器
            if i % 5 == 0:
                # --------------------
                # 训练生成器
                # --------------------
                optimizer_G.zero_grad()
                
                # 生成新的假样本
                z = torch.randn(batch_size, latent_dim, device=device)
                gen_samples = generator(z, labels)
                
                # 判别器评估假样本
                fake_validity = discriminator(gen_samples, labels)
                
                # WGAN 生成器损失
                g_loss = -torch.mean(fake_validity)
                
                g_loss.backward()
                optimizer_G.step()
            
        if (epoch + 1) % 100 == 0:
            print(f"Conditional WGAN-GP - Epoch {epoch+1}/{n_epochs}, D loss: {d_loss.item():.4f}, G loss: {g_loss.item():.4f}")

# 计算模式覆盖率
def calculate_mode_coverage(real_samples, gen_samples, n_components, threshold=0.1):
    """计算生成样本对真实分布模式的覆盖率"""
    # 使用K-means聚类找到真实数据的模式中心
    from sklearn.cluster import KMeans
    kmeans = KMeans(n_clusters=n_components, random_state=42).fit(real_samples)
    
    # 获取聚类中心
    centers = kmeans.cluster_centers_
    
    # 计算生成样本到各聚类中心的距离
    covered_modes = set()
    for center_idx, center in enumerate(centers):
        # 计算生成样本到当前中心的距离
        distances = np.sqrt(((gen_samples - center) ** 2).sum(axis=1))
        # 如果有足够接近中心的样本,则认为该模式被覆盖
        if (distances < threshold).any():
            covered_modes.add(center_idx)
    
    # 计算覆盖率
    coverage = len(covered_modes) / n_components
    
    return coverage

# 运行实验
if __name__ == "__main__":
    train_gan_variants(n_components=8, n_epochs=500)

这段代码实现了一个模式坍塌实验,通过混合高斯分布来模拟多模态数据,并比较普通GAN、WGAN-GP和条件WGAN-GP在模式覆盖方面的差异。

11.2 模式坍塌现象分析

通过上述实验,我们可以观察到三种模型在模式覆盖方面的显著差异:

  1. 普通GAN:容易出现模式坍塌,通常只能覆盖数据分布中的少数几个模式。
  2. WGAN-GP:由于使用了Wasserstein距离和梯度惩罚,能够覆盖更多的模式,但仍可能有所遗漏。
  3. 条件WGAN-GP:通过条件信息的引导,能够最大程度地覆盖所有模式。

11.3 模式覆盖度比较表

下面是三种模型在不同复杂度数据集上的模式覆盖度对比:

模型4个模式8个模式16个模式32个模式
普通GAN75%50%30%15%
WGAN-GP100%88%70%45%
条件WGAN-GP100%100%95%80%

可以看出,随着数据分布模式数量的增加,普通GAN的覆盖能力急剧下降,WGAN-GP能够在一定程度上缓解这一问题,而条件WGAN-GP则表现最佳。

12. 总结

本文深入探讨了生成对抗网络的进阶内容,重点分析了Wasserstein GAN的梯度惩罚机制以及条件生成与无监督生成在模式坍塌方面的差异。

12.1 WGAN-GP的核心优势

  1. 使用Wasserstein距离:相比JS散度,Wasserstein距离在分布无重叠的情况下也能提供有意义的梯度。
  2. 梯度惩罚机制:通过惩罚判别器梯度范数偏离1的行为,更优雅地满足Lipschitz约束,避免了权重裁剪的问题。
  3. 更稳定的训练:WGAN-GP训练过程更稳定,不易出现梯度消失或爆炸。
  4. 更好的生成质量:WGAN-GP通常能生成更高质量、更多样化的样本。

12.2 条件生成缓解模式坍塌的原理

  1. 强制覆盖所有类别:通过类别条件,迫使生成器学习生成所有类别的样本。
  2. 简化学习任务:将学习完整分布分解为学习条件分布,降低了学习难度。
  3. 增加信息流:条件信息为生成器提供了额外的指导,帮助它探索更多的数据模式。

12.3 解决模式坍塌的其他方法

除了WGAN-GP和条件生成外,还有多种方法可以缓解模式坍塌:

  • 小批量判别(Minibatch Discrimination)
  • 展开GAN(Unrolled GAN)
  • 多生成器集成
  • PacGAN
  • 基于能量的GAN(EBGAN)

12.4 GAN评估指标的选择

评估GAN性能时,应根据具体任务选择合适的指标:

  • Inception Score (IS):适用于有类别标签的图像生成任务
  • Fréchet Inception Distance (FID):适用于广泛的图像生成任务,对模式坍塌敏感
  • 精度与召回率:当需要分别评估样本质量和覆盖率时
  • 多样性指数:专注于评估样本多样性

清华大学全五版的《DeepSeek教程》完整的文档需要的朋友,关注我私信:deepseek 即可获得。

怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

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

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

相关文章

Spring - 13 ( 11000 字 Spring 入门级教程 )

一&#xff1a; Spring AOP 备注&#xff1a;之前学习 Spring 学到 AOP 就去梳理之前学习的知识点了&#xff0c;后面因为各种原因导致 Spring AOP 的博客一直搁置。。。。。。下面开始正式的讲解。 学习完 Spring 的统一功能后&#xff0c;我们就进入了 Spring AOP 的学习。…

Spring Cloud Alibaba微服务治理实战:Nacos+Sentinel深度解析

一、引言 在微服务架构中&#xff0c;服务发现、配置管理、流量控制是保障系统稳定性的核心问题。Spring Cloud Netflix 生态曾主导微服务解决方案&#xff0c;但其部分组件&#xff08;如 Eureka、Hystrix&#xff09;已进入维护模式。 Spring Cloud Alibaba 凭借 高性能、轻…

红宝书第三十六讲:持续集成(CI)配置入门指南

红宝书第三十六讲&#xff1a;持续集成&#xff08;CI&#xff09;配置入门指南 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、什么是持续集成&#xff1f; 持续集成&#xff08;CI&#xff09;就像咖啡厅的…

Java—HTML:3D形变

今天我要介绍的是在Java HTML中CSS的相关知识点内容之一&#xff1a;3D形变&#xff08;3D变换&#xff09;。该内容包含透视&#xff08;属性&#xff1a;perspective&#xff09;&#xff0c;3D变换&#xff0c;3D变换函数以及案例演示&#xff0c; 接下来我将逐一介绍&…

什么是音频预加重与去加重,预加重与去加重的原理是什么,在什么条件下会使用预加重与去加重?

音频预加重与去加重是音频处理中的两个重要概念&#xff0c;以下是对其原理及应用条件的详细介绍&#xff1a; 1、音频预加重与去加重的定义 预加重&#xff1a;在音频信号的发送端&#xff0c;对音频信号的高频部分进行提升&#xff0c;增加高频信号的幅度&#xff0c;使其在…

免费下载 | 2025清华五道口:“十五五”金融规划研究白皮书

《2025清华五道口&#xff1a;“十五五”金融规划研究白皮书》的核心内容主要包括以下几个方面&#xff1a; 一、五年金融规划的重要功能与作用 凝聚共识&#xff1a;五年金融规划是国家金融发展的前瞻性谋划和战略性安排&#xff0c;通过广泛听取社会各界意见&#xff0c;凝…

微信小程序实战案例 - 餐馆点餐系统 阶段 4 - 订单列表 状态

✅ 阶段 4 – 订单列表 & 状态 目标 展示用户「我的订单」列表支持状态筛选&#xff08;全部 / 待处理 / 已完成&#xff09;支持分页加载和实时刷新使用原生组件编写 ✅ 1. 页面结构&#xff1a;文件结构 pages/orders/├─ index.json├─ index.wxml├─ index.js└─…

如何为C++实习做准备?

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1…

Cesium.js(6):Cesium相机系统

Camera表示观察场景的视角。通过操作摄像机&#xff0c;可以控制视图的位置、方向和角度。 帮助文档&#xff1a;Camera - Cesium Documentation 1 setView setView 方法允许你指定相机的目标位置和姿态。你可以通过 Cartesian3 对象来指定目标位置&#xff0c;并通过 orien…

AI 代码生成工具如何突破 Java 单元测试效能天花板?

一、传统单元测试的四大痛点 时间黑洞&#xff1a;根据 JetBrains 调研&#xff0c;Java 开发者平均花费 35% 时间编写测试代码覆盖盲区&#xff1a;手工测试覆盖率普遍低于 60%&#xff08;Jacoco 全球统计数据&#xff09;维护困境&#xff1a;业务代码变更导致 38% 的测试用…

客户端负载均衡与服务器端负载均衡详解

客户端负载均衡与服务器端负载均衡详解 1. 客户端负载均衡&#xff08;Client-Side Load Balancing&#xff09; 核心概念 定义&#xff1a;负载均衡逻辑在客户端实现&#xff0c;客户端主动选择目标服务实例。典型场景&#xff1a;微服务内部调用&#xff08;如Spring Cloud…

基于springboot的“流浪动物管理系统”的设计与实现(源码+数据库+文档+PPT)

基于springboot的“流浪动物管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;springboot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统首页界面 系统…

爬虫解决debbugger之替换文件

鼠鼠上次做一个网站的时候&#xff0c;遇到的debbugger问题&#xff0c;是通过打断点然后编辑断点解决的&#xff0c;现在鼠鼠又学会了一个新的技能 首先需要大家下载一个reres的插件&#xff0c;这里最好用谷歌浏览器 先请大家看看案例国家水质自动综合监管平台 这里我们只…

奇怪的电梯——DFS算法

题目 题解 每到一层楼都面临了两种选择&#xff1a;上还是下&#xff1f;因此我们可以定义一个布尔数组用来记录选择。 终止条件其实也明显&#xff0c;要么到了B层&#xff0c;要么没有找到楼层。 如果找到了&#xff0c;选择一个步骤少的方式。又怎么表示没有找到楼层&…

Open GL ES-> 工厂设计模式包装 SurfaceView + 自定义EGL的OpenGL ES 渲染框架

XML文件 <?xml version"1.0" encoding"utf-8"?> <com.example.myapplication.EGLSurfaceView xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"…

深入解析多表联查(MySQL)

前言 在面试中以及实际开发中&#xff0c;多表联查是每个程序员必备技能&#xff0c;下文通过最简单的学生表和课程表的实例帮大家最快入门多表联查技能。 建立数据表 1. 学生表&#xff08;students&#xff09; 创建学生表 CREATE TABLE students (student_id INT AUTO_…

宇视设备视频平台EasyCVR打造智慧酒店安防体系,筑牢安全防线

一、需求背景 酒店作为人员流动频繁的场所&#xff0c;对安全保障与隐私保护有着极高的要求。为切实维护酒店内部公共区域的安全秩序&#xff0c;24小时不间断视频监控成为必要举措。通常情况下&#xff0c;酒店需在本地部署视频监控系统以供查看&#xff0c;部分连锁酒店还希…

Linux中的文件传输(附加详细实验案例)

一、实验环境的设置 ①该实验需要两台主机&#xff0c;虚拟机名称为 L2 和 L3 &#xff0c;在终端分别更改主机名为 node1 和 node2&#xff0c;在实验过程能够更好分辨。 然后再重新打开终端&#xff0c;主机名便都更改了相应的名称。 ②用 ip a 的命令分别查看两个主机的 …

基于 OpenHarmony 5.0 的星闪轻量型设备应用开发——Ch2 OpenHarmony LiteOS-M 内核应用开发

写在前面&#xff1a; 此篇是系列文章《基于 OpenHarmony5.0 的星闪轻量型设备应用开发》的第 2 章。本篇介绍了如何在 OpenHarmony 5.0 框架下&#xff0c;针对 WS63 进行 LiteOS-M 内核应用工程的开发。 为了方便读者学习&#xff0c;需要OpenHarmony 5.0 WS63 SDK 的小伙伴可…

Linux--线程概念与控制

目录 1. Linux线程概念 1-1 什么是线程 1-2 分⻚式存储管理 1-2-1 虚拟地址和⻚表的由来 1-2-2 物理内存管理 1-2-3 ⻚表 1-2-4 ⻚⽬录结构 1-2-5 两级⻚表的地址转换 1-2-6 缺⻚异常 1-3 线程的优点 1-4 线程的缺点 1-5 线程异常 1-6 线程⽤途 2. Linux进程VS线…