深度学习训练营之DCGAN网络学习
深度学习训练营之DCGAN网络学习
- 原文链接
- 环境介绍
- DCGAN简单介绍
- 生成器(Generator)
- 判别器(Discriminator)
- 对抗训练
- 前置工作
- 导入第三方库
- 导入数据
- 数据查看
- 定义模型
- 初始化权重
- 定义生成器generator
- 定义判别器
- 模型训练
- 定义参数
- 模型训练
- 结果可视化
原文链接
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍦 参考文章:365天深度学习训练营-第G2周:深度学习训练营之DCGAN网络学习
- 🍖 原作者:K同学啊|接辅导、项目定制
环境介绍
- 语言环境:Python3.11.4
- 编译器:jupyter notebook
- 深度学习环境:TensorFlow2
DCGAN简单介绍
DCGAN(Deep Convolutional Generative Adversarial Network)是一种基于生成对抗网络(GAN)的深度学习模型,用于生成逼真的图像。它通过将生成器和判别器两个网络相互对抗地训练,以实现生成高质量图像的目标。
DCGAN 的核心思想是使用卷积神经网络(CNN)作为生成器和判别器的网络结构。下面是 DCGAN 的一般工作原理:
生成器(Generator)
生成器接受一个随机噪声向量作为输入,并使用反卷积层(或称为转置卷积层)将其逐渐放大和转换为图像。
通过层层上采样处理和卷积操作,生成器逐渐学习到将低分辨率噪声向量转化为高分辨率逼真图像的映射。
生成器的目标是尽可能接近真实图像的分布,从而生成看起来真实的图像。
判别器(Discriminator)
判别器是一个二分类的CNN网络,用于区分真实图像和生成器生成的假图像。
判别器接受输入图像并输出一个概率,表示输入图像是真实图像的概率。
判别器通过对真实图像分配较高的概率值,并对生成器生成的假图像分配较低的概率值,来辨别真实和假的图像。
对抗训练
DCGAN 的核心是通过对抗训练生成器和判别器来提升它们的性能(属于是无监督的学习)。
在训练过程中,生成器试图生成逼真的图像以欺骗判别器,而判别器则努力区分真实和生成的图像。
这里就可以理解为生成器通过尽可能地生成逼近于真实图片的图像来尝试骗过判别器,而判别器就是通过尽可能地将假图片和真图片进行区分,当两种之间发生冲突的时候,就会进行进一步的优化,直到达到平衡,在后续的代码当中我们也可以看到生成器和判别器之间的网络价格正好是相反的
生成器和判别器相互对抗地进行训练,通过最小化生成器生成图像被判别为假的概率(对抗损失)和最大化真实图像被判别为真的概率(真实损失)来优化网络。
通过反复训练生成器和判别器,并使它们相互对抗地提升,最终可以得到一个生成器能够生成高质量逼真图像的模型。
前置工作
导入第三方库
import torch,random,os
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
manualSeed=999#随机数种子
print("Random Seed:",manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
torch.use_deterministic_algorithms(True)
999
导入数据
导入数据并设置超参数
dataroot="./DCGAN/"
# 数据集和上一周的一样,所以就放在一起了
batch_size=128
image_size=64
nz=100 #z潜在的向量大小(生成器generator的尺寸)
ngf=64 #生成器中的特征图大小
ndf=64
num_epochs=50
lr=0.00002
beta1=0.5
print(dataroot)
数据查看
进行数据的导入,
-
用
ImageFolder
类来创建数据集对象, -
Transforms.Compose
组合成一系列的图像变换操作来对图像进行预处理 -
DataLoder
类来创建一个数据加载器的对象 -
Matplotlib
库来绘制这些图像
dataset=dset.ImageFolder(root=dataroot,transform=transforms.Compose([transforms.Resize(image_size),transforms.CenterCrop(image_size),transforms.ToTensor(),#转换成张量transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),]))
dataloader=torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=True,num_workers=5#使用多个线程加载数据的工作进程数)
device=torch.device=("cuda:0"if (torch.cuda.is_available())else "cpu")
print("使用的设备为 " +device)real_batch=next(iter(dataloader))
plt.figure(figsize=(8,8))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:24],padding=2,normalize=True).cpu(),(1,2,0)))
定义模型
初始化权重
def weights_init(m):#获取当前层类名classname=m.__class__.__name__#包含conv,表示当前层是卷积层if classname.find('Conv')!=-1:#j均值设为0.0,标准差为0.02nn.init.normal_(m.weight.data,0.0,0.02)#直接在张量上进行参数初始化elif classname.find('BatchNorm')!=-1:nn.init.normal_(m.weight.data,1.0,0.02)nn.init.constant_(m.bias.data,0)
定义生成器generator
class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()## 模型中间块儿self.main=nn.Sequential(nn.ConvTranspose2d(nz,ngf*8,4,1,0,bias=False),nn.BatchNorm2d(ngf*8),nn.ReLU(True),nn.ConvTranspose2d(ngf*8,ngf*4,4,2,1,bias=False),nn.BatchNorm2d(ngf*4),nn.ReLU(True),nn.ConvTranspose2d(ngf*4,ngf*2,4,2,1,bias=False),nn.BatchNorm2d(ngf*2),nn.ReLU(True),nn.ConvTranspose2d(ngf*2,ngf,4,2,1,bias=False),nn.BatchNorm2d(ngf),nn.ReLU(True),nn.ConvTranspose2d(ngf,3,4,2,1,bias=False),nn.Tanh()#Tanh激活函数)def forward(self, input): return self.main(input)
#创建生成器
netG=Generator().to(device)
netG.apply(weights_init)
print(netG)
大家可以注意一下这个网络的架构,会和后面的判别器是相反的
定义判别器
class Discriminator(nn.Module):def __init__(self):super(Discriminator,self).__init__()self.main=nn.Sequential(nn.Conv2d(3,ndf,4,2,1,bias=False),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf,ndf*2,4,2,1,bias=False),nn.BatchNorm2d(ndf*2),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf*2,ndf*4,4,2,1,bias=False),nn.BatchNorm2d(ndf*4),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf*4,ndf*8,4,2,1,bias=False),nn.BatchNorm2d(ndf*8),nn.LeakyReLU(0.2,inplace=True),nn.Conv2d(ndf*8,1,4,1,0,bias=False),nn.Sigmoid()#Sigmoid激活函数)def forward(self, input):return self.main(input)
#创建判别器
netD=Discriminator().to(device)
netD.apply(weights_init)#weights_init初始化所有权重
print(netD)
模型训练
定义参数
criterion=nn.BCELoss()
fixed_noise=torch.randn(64,nz,1,1,device=device)
real_label=1.#1表示真实
fake_label=0.#0表示虚假生成#设置优化器
optimizerD=optim.Adam(netD.parameters(),lr=lr,betas=(beta1,0.999))
optimizerG=optim.Adam(netG.parameters(),lr=lr,betas=(beta1,0.999))
模型训练
img_list=[]#用存储生成的图像列表
G_losses=[]
D_losses=[]
iters=0#迭代次数
print("开始训练Starting Training Loop..")
for epoch in range(num_epochs):#dataloader中的每个batchfor i,data in enumerate(dataloader,0):#####最大化log(D(x))+log(1-D(G(z)))####netD.zero_grad()#清除判别器网络的梯度 real_cpu=data[0].to(device)b_size=real_cpu.size(0)label=torch.full((b_size,),real_label,dtype=torch.float,device=device) #输入判别器进行前向传播output=netD(real_cpu).view(-1)errD_real=criterion(output,label)errD_real.backward()D_x=output.mean().item()#计算批判别器对真实图像样本的输出平均值'''使用生成图像样本进行训练'''noise=torch.randn(b_size,nz,1,1,device=device)fake=netG(noise)label.fill_(fake_label)output=netD(fake.detach()).view(-1)errD_fake=criterion(output,label)errD_fake.backward()D_G_z1=output.mean().item()errD=errD_fake+errD_realoptimizerD.step()'''更新生成网络'''netG.zero_grad()label.fill_(real_label)output=netD(fake).view(-1)errG=criterion(output,label)errG.backward()D_G_z2=output.mean().item()optimizerG.step()if i % 400 == 0:print('[%d/%d][%d/%d]\tLoss_D:%.4f\tLoss_G:%.4f\tD(x):%.4f\tD(G(z)):%.4f / %.4f'% (epoch, num_epochs, i, len(dataloader), errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))#保存损失值G_losses.append(errG.item())D_losses.append(errD.item())#保存固定噪声上的输出来检查生成器的性能 if(iters%500==0)or((epoch==num_epochs-1)and(i==len(dataloader)-1)):with torch.no_grad():fake=netG(fixed_noise).detach().cpu()img_list.append(vutils.make_grid(fake,padding=2,normalize=True))iters+=1
开始训练Starting Training Loop..
[0/50][0/36] Loss_D:1.3728 Loss_G:1.0315 D(x):0.6877 D(G(z)):0.5443 / 0.4221
[1/50][0/36] Loss_D:0.3502 Loss_G:2.3366 D(x):0.9120 D(G(z)):0.1921 / 0.1283
[2/50][0/36] Loss_D:0.1925 Loss_G:3.2138 D(x):0.9384 D(G(z)):0.0957 / 0.0582
[3/50][0/36] Loss_D:0.1281 Loss_G:3.6822 D(x):0.9570 D(G(z)):0.0674 / 0.0370
[4/50][0/36] Loss_D:0.1669 Loss_G:4.0574 D(x):0.9308 D(G(z)):0.0563 / 0.0262
[5/50][0/36] Loss_D:0.1337 Loss_G:4.2146 D(x):0.9428 D(G(z)):0.0551 / 0.0209
[6/50][0/36] Loss_D:0.0729 Loss_G:4.5967 D(x):0.9696 D(G(z)):0.0344 / 0.0138
[7/50][0/36] Loss_D:0.0770 Loss_G:4.6592 D(x):0.9747 D(G(z)):0.0344 / 0.0133
[8/50][0/36] Loss_D:0.0932 Loss_G:4.8994 D(x):0.9742 D(G(z)):0.0303 / 0.0105
[9/50][0/36] Loss_D:0.0790 Loss_G:5.0675 D(x):0.9819 D(G(z)):0.0269 / 0.0083
[10/50][0/36] Loss_D:0.0496 Loss_G:5.0618 D(x):0.9807 D(G(z)):0.0278 / 0.0085
[11/50][0/36] Loss_D:0.0452 Loss_G:5.2256 D(x):0.9800 D(G(z)):0.0221 / 0.0069
[12/50][0/36] Loss_D:0.0332 Loss_G:5.4038 D(x):0.9833 D(G(z)):0.0148 / 0.0058
[13/50][0/36] Loss_D:0.0370 Loss_G:5.2032 D(x):0.9815 D(G(z)):0.0171 / 0.0064
[14/50][0/36] Loss_D:0.0326 Loss_G:5.5015 D(x):0.9838 D(G(z)):0.0149 / 0.0053
[15/50][0/36] Loss_D:0.0368 Loss_G:5.4651 D(x):0.9872 D(G(z)):0.0162 / 0.0055
[16/50][0/36] Loss_D:0.0349 Loss_G:5.6891 D(x):0.9849 D(G(z)):0.0186 / 0.0047
[17/50][0/36] Loss_D:0.0214 Loss_G:5.5402 D(x):0.9925 D(G(z)):0.0133 / 0.0048
[18/50][0/36] Loss_D:0.0216 Loss_G:5.6668 D(x):0.9912 D(G(z)):0.0123 / 0.0041
[19/50][0/36] Loss_D:0.0219 Loss_G:5.6475 D(x):0.9919 D(G(z)):0.0132 / 0.0046
[20/50][0/36] Loss_D:0.0165 Loss_G:5.7313 D(x):0.9956 D(G(z)):0.0118 / 0.0040
[21/50][0/36] Loss_D:0.0203 Loss_G:5.7859 D(x):0.9939 D(G(z)):0.0138 / 0.0040
[22/50][0/36] Loss_D:0.0266 Loss_G:5.7094 D(x):0.9850 D(G(z)):0.0104 / 0.0040
[23/50][0/36] Loss_D:0.0207 Loss_G:5.7429 D(x):0.9899 D(G(z)):0.0101 / 0.0038
...
[46/50][0/36] Loss_D:0.0100 Loss_G:6.6160 D(x):0.9945 D(G(z)):0.0044 / 0.0024
[47/50][0/36] Loss_D:0.0114 Loss_G:7.1434 D(x):0.9927 D(G(z)):0.0025 / 0.0017
[48/50][0/36] Loss_D:0.0039 Loss_G:7.2856 D(x):0.9980 D(G(z)):0.0019 / 0.0012
[49/50][0/36] Loss_D:0.0198 Loss_G:6.2926 D(x):0.9882 D(G(z)):0.0048 / 0.0029
结果可视化
plt.figure(figsize=(10, 5))
plt.title('Generator and Discriminator Loss During Training')
plt.plot(G_losses, label='G')
plt.plot(D_losses, label='D')
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.legend()
plt.show()
阿哲,训练效果好差,不知道是不是硬件的问题
fig = plt.figure(figsize=(8, 8))
plt.axis('off')ims = [[plt.imshow(np.transpose(i, (1, 2, 0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)HTML(ani.to_jshtml())
相关文章:

深度学习训练营之DCGAN网络学习
深度学习训练营之DCGAN网络学习 原文链接环境介绍DCGAN简单介绍生成器(Generator)判别器(Discriminator)对抗训练 前置工作导入第三方库导入数据数据查看 定义模型初始化权重定义生成器generator定义判别器 模型训练定义参数模型训…...

自定义MVC增删改查
目录 mymvcdemo是自定义mvc框架的使用示例 1.1 实体类 1.2 dao方法 1.3 写Service / biz 三层架构 1.4 建action 相当于selvert 1.5 con连接MySQL 8.0 版本 1.6 配置文件 XML 1.7 主界面布局 1.8 增加界面布局 1.9 写tld配置文件 2.0 注意架包 我是已经打包好的 mymv…...

RabbitMQ 教程 | 第2章 RabbitMQ 入门
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的编码爱好者 大家好,我是 DevO…...
双网卡如何配置DNS?我是一个仅主机模式配置静态(static)IP、一个NET或桥接(dhcp获取)
目录 一、所有主机初始化 二、135、136服务器,部署DNS调度服务器 1、更改主机主从DNS服务器的主机名称 2、安装bind软件、修改主配置文件 3、修改区域配置文件 4、修改数据文件 5、启动named服务、修改网卡信息 6、解析 7、双网卡的话记得注释以下内容、注…...
Android10: 动态隐藏导航栏和状态栏总结
(1)全屏相关设置 //(1)主题添加 <item name"android:windowFullscreen">true</item>//(2)setContentView之前添加 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCRE…...

roop 视频换脸
roop: one click face swap. 只用一张人脸图片,就能完成视频换脸。 项目地址: https://github.com/s0md3v/roopColab 部署: https://github.com/dream80/roop_colab 本文是本地部署的实践记录。 环境基础 OS: Ubuntu 22.04.2 LTSKernel: 5…...

Java类集框架(一)
目录 1.Collection集合接口 2.List 接口 (常用子类 ArrayList ,LinkedList,Vector) 3.Set 集合 接口(常用子类 HashSet LinkedHashSet,TreeSet) 4.集合输出(iterator , Enumeration) 1.Collection集合接口 Collection是集合中最大父接口,在接口中定义了核心的…...

Jsp+Ssh+Mysql实现的简单的企业物资信息管理系统项目源码附带视频指导运行教程
由jspssh(springstruts2mysql)实现的企业物资信息管理系统,系统功能比较简单,实现了基本的管理员、操作员等用户管理、物品分类管理、物品管理、入库管理、出库管理、库存预警、客户管理、供应商管理等基本功能需要的可以联系我分…...

【Spring】深究SpringBoot自动装配原理
文章目录 前言1、main入口2、SpringBootApplication3、EnableAutoConfiguration4、AutoConfigurationImportSelector4.1、selectImports()4.2、getAutoConfigurationEntry()4.3、getCandidateConfigurations()4.4、loadFactoryNames() 5、META-INF/spring.factories6、总结 前言…...

阿里云负载均衡SLB网络型NLB负载均衡架构性能详解
阿里云网络型负载均衡NLB是阿里云推出的新一代四层负载均衡,支持超高性能和自动弹性能力,单实例可以达到1亿并发连接,帮您轻松应对高并发业务。网络型负载均衡NLB具有超强性能、自动弹性伸缩、高可用、TCPSSL卸载、多场景流量分发和丰富的高级…...
JavaScript学习 -- SM4算法应用实例
SM4算法,也被称为国密算法,是中国公布的一种高效且安全的对称加密算法。在JavaScript中,我们可以通过使用CryptoJS库来实现SM4算法的加密和解密。本篇博客将为您介绍如何在JavaScript中使用SM4算法,并提供一个实际的案例。 首先&…...

【JVM】什么是双亲委派机制
文章目录 1、类加载机制2、双亲委派模型2.1、介绍2.2、为什么需要双亲委派2.3、源码解析 3、破坏双亲委派3.1、介绍3.2、破坏实现3.3、破坏双亲委派的例子 4、线程上下文类加载器 1、类加载机制 类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全…...

网络安全 Day24-select高级用法和多表连接
select高级用法和多表连接 1. select 多子句单表高级实践1.1 select 多子句高级语法1.2 聚合函数1.3 group by 实践1.4 having 筛选1.5 order by 排序1.6 limit 2. 多表连接 1. select 多子句单表高级实践 1.1 select 多子句高级语法 where 和 having 区别是后者是分组后进行…...

JUC并发编程之volatile详解
目录 1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用场景 送书活动 1. volatile 在并发编程中,多线程操作共享的变量时&a…...

swing布局详解
1. 布局管理器接口 (1)说明 布局管理器接口为LayoutManager和LayoutManager2,LayoutManager2是LayoutManager的子类。 (2)常用方法 方法描述LayoutManageraddLayoutComponent(String name, Component comp) removeL…...
el-table某一列嵌套使用el-popover,使用click触发,导致页面下拉框组件无法触发弹框关闭(解决办法)
在弹框触发的方法里加上document.body.click() 即可 尝试了很多其他的方法都没用,只有这个解决了 完整代码: <el-select change"sourceChange" clearable ><el-optionv-for"option in list1":key"option.code":…...

正泰电力携手图扑:VR 变电站事故追忆反演
VR(Virtual Reality,虚拟现实)技术作为近年来快速发展的一项新技术,具有广泛的应用前景,支持融合人工智能、机器学习、大数据等技术,实现更加智能化、个性化的应用。在电力能源领域,VR 技术在高性能计算机和专有设备支…...
报错 -bash: wget: command not found
1、报错 -bash: wget: command not found 可以重装 wget 工具: 卸载 wget 工具 yum remove wget下载 wget 工具 yum -y install wget最后尝试 wget “url” 又OK了,一般是原来的wget初始化有文件损坏造成的。...
HashMap扩容和Redis中Dict 扩容
扩容时机: Hash Map:要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子) Dict: 当每次新增键值对的时 , 会检测 负载因子(LoadFactor) , 判断以…...

【Redis】内存数据库Redis进阶(Redis持久化)
目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...