QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5755|回复: 0
打印 上一主题 下一主题

深度卷积生成对抗网络DCGAN——生成手写数字图片

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2021-6-28 11:54 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    & l2 a1 E. P' U9 _; R5 @& ^
    深度卷积生成对抗网络DCGAN——生成手写数字图片5 c6 j( I3 u* Z  ]# D: N
    前言
    , H- R: H. _' @: h' P本文使用深度卷积生成对抗网络(DCGAN)生成手写数字图片,代码使用Keras API与tf.GradientTape 编写的,其中tf.GradientTrape是训练模型时用到的。2 `5 n* M; J& ~

    4 M5 E" x  A3 I  ?  |

    ; T  R# |& t7 x9 |" G# R 本文用到imageio 库来生成gif图片,如果没有安装的,需要安装下:
    ; O- g/ Y5 K5 P+ \2 j! e% s' P# t: ?& D
    7 T$ |& V3 x" @2 `" j1 y0 F
    ' l2 o* {* g5 g* V
    # 用于生成 GIF 图片
    & V1 M. S5 \* U9 F: D) Cpip install -q imageio
      I8 z, d2 n( J" ~2 c8 y6 [目录2 I  V  W/ W) r7 l4 Z% v& H( y" [
    - \/ p  }7 G) i# X6 s& `

    & _4 p2 v$ G; J2 @  K) X前言( D- E. y3 r  a+ A3 T2 A' r

    9 H! n0 \" e# s4 L% p! Q3 Q% o( J
    5 O( w: W: r( c: ^( V" G( O( e
    一、什么是生成对抗网络?7 B# }# O  a8 R  V/ S; Y1 x6 w
    2 X2 x3 g1 s5 R8 z0 b
    : ~! v$ o! N8 [; \/ n, R
    二、加载数据集( E+ M! Y# |) B

      n* s" a1 W. H. o1 ~
    & U. O: {9 y, _  H
    三、创建模型- H5 A4 v- r1 n+ H! }9 i! W
    " o1 r; [& a6 A/ z7 I; A

    6 s) i8 c) ]6 R9 p7 N, N( m$ F3.1 生成器
    0 `& {& S$ c, ^  @8 k
    # Z7 f6 L" Q4 U. V

      c* a: U: p7 A/ z; L3.1 判别器
    # i$ J% b/ `5 z( W1 x& |5 s: J- Z7 l. {( i; Z/ [2 g

    ( M* n. B8 R, W4 S! P# E四、定义损失函数和优化器" P( ^$ \! j3 p% Y3 f1 C. G; o
    5 s& b$ a( \8 Z: k' m
    , }1 u$ l- S8 |) t$ z) k' K& O
    4.1 生成器的损失和优化器& U8 n7 k9 p. ~

    ; n# y* d: C4 C; |; R5 X$ K

    ( C) F% z" ^' e% G# X5 r4.2 判别器的损失和优化器/ @2 h# A' B7 w4 u& M

    ! i+ ?; C4 E7 ]

    ; I# j! o6 C) @; Q五、训练模型) ^5 r# [: ^  L: ?, k

    ' q7 k8 o7 \0 g" d/ J% H
    9 ]. v& r) `" l8 v6 T+ l% I, }
    5.1 保存检查点
      y- ?" {  O1 L+ o0 W: X, ]
    ( M4 l9 o7 G5 G  ]
    4 Q8 _4 I% H+ A. ]
    5.2 定义训练过程
    4 n  y& l8 r' n
    / {( O9 p+ j. d, h7 D
    9 Q& n% E( Z% ?) G0 s8 }! ~
    5.3 训练模型
    , G# f# z+ K5 Q$ G6 j5 Y* t. B  D4 Y# r" ~

    9 S* O0 L6 s/ W6 o六、评估模型! Q$ ?  q+ x7 h6 d: ?2 {
    8 C& u! Z* B" D4 I5 p

    ; r9 k# _8 a' J: a- ?一、什么是生成对抗网络?6 L4 _4 z+ ^9 ]: E: ~
    生成对抗网络(GAN),包含生成器和判别器,两个模型通过对抗过程同时训练。  o6 _/ R/ C* ]4 d) P2 e0 Q

      J: o9 [! ^1 W( w

    % E" S' u6 j" D7 W生成器,可以理解为“艺术家、创造者”,它学习创造看起来真实的图像。! ~3 b/ f" Q& B% ]2 Y2 B6 Y+ |

    : O2 Y1 B: T2 {' J* F- F0 G

    * ?7 F/ |" G' A7 I3 K! k/ Z判别器,可以理解为“艺术评论家、审核者”,它学习区分真假图像。
    . z2 h5 q- x8 _) a( f. f9 v7 l% V, m6 `
    - P3 P0 h) r- j# B# L5 C( z: u
    训练过程中,生成器在生成逼真图像方便逐渐变强,而判别器在辨别这些图像的能力上逐渐变强。) b/ f1 e2 M5 H$ C2 ]; M2 X

    5 a- l+ U" x, h+ {5 v' F2 [3 y" W$ V

    + H4 P2 D; p: w4 `( u当判别器不能再区分真实图片和伪造图片时,训练过程达到平衡。
    ! }: m% V# ~. M6 h! H  f. V4 L& O2 Z( k' b( P. g4 m' O
    / s$ W9 y2 c$ k, J  Y! f$ z0 {
    本文,在MNIST数据集上演示了该过程。随着训练的进行,生成器所生成的一系列图片,越来越像真实的手写数字。3 q5 T( ]: @# J5 K- l/ E8 g
    1 |: }- W. U" Z% P- o" s4 o

    6 E  p4 _+ y: J4 {- x' F二、加载数据集' B" ^* s. n4 e
    使用MNIST数据,来训练生成器和判别器。生成器将生成类似于MNIST数据集的手写数字。
    5 t7 p1 l- v8 Z8 o: l  l& c8 f8 Q% w2 V) d! R- i2 z' v- v

    + }6 v, f- Q5 C) Z8 K/ t(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()0 G+ X. u; r) }! }- ]$ J1 x

    * ?, k# _4 e- _4 Z9 N2 l* A$ Ktrain_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')) w& K  W1 b0 e/ e6 f9 S/ T  r
    train_images = (train_images - 127.5) / 127.5 # 将图片标准化到 [-1, 1] 区间内
    1 i' M# m  {- p! @; ~# m - o" Y$ W# s- q+ f
    BUFFER_SIZE = 60000$ `* k$ Y9 g# p8 V' n9 ~5 W
    BATCH_SIZE = 256& N0 R6 }) |. b
    / X& L- x! U# s( y
    # 批量化和打乱数据: k3 n0 d* _5 |' {: I3 B3 m) P+ [
    train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
    . X9 [! M, @, T6 O三、创建模型2 Q5 w! L- }% M5 [9 v( \
    主要创建两个模型,一个是生成器,另一个是判别器。1 |, a) X5 X3 R+ t
    - {- @! e4 z) V' l/ x$ f1 L
    / G3 b8 A6 C+ }- \* F9 v8 j
    3.1 生成器8 I5 j9 i$ s7 t- H1 N. ^5 T" R
    生成器使用 tf.keras.layers.Conv2DTranspose 层,来从随机噪声中产生图片。4 e7 W* {' r) p, ~* X# C) v6 v: L0 m- G

    ' I+ U6 G! j+ T0 `" \

    " o- H8 i; e/ R8 @2 [; y然后把从随机噪声中产生图片,作为输入数据,输入到Dense层,开始。( ~- _( C0 D( B9 o
    7 B  D, ?% }% h$ O7 v" j

    / d+ l% f" X; P/ l/ T' }后面,经过多次上采样,达到所预期 28x28x1 的图片尺寸。; G8 R9 t' s1 B. {" a

    % R: B" Q5 L" \1 A
    . |+ e2 l; o( I. i1 ?! Q9 V
    def make_generator_model():
    + S  V9 c7 J- c  ^. i/ {( X    model = tf.keras.Sequential()' N: X  @# H( O2 k. }7 o3 U
        model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    4 t3 i+ n  @8 R  p5 ?, X" H    model.add(layers.BatchNormalization())3 p& g0 y/ A' l! O) `) o; G
        model.add(layers.LeakyReLU())# b/ `+ N* `6 M! M# z
    ( V) C# T% `1 O( @5 W/ {8 }
        model.add(layers.Reshape((7, 7, 256)))
    / F( o1 ~; L" `4 w1 m    assert model.output_shape == (None, 7, 7, 256) # 注意:batch size 没有限制4 [. x- l! m1 _

    " r& Z$ h" N! m( V) v3 a+ A0 ]    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))- r& T5 o  a9 w; H3 d. S" s$ x. L9 q" @
        assert model.output_shape == (None, 7, 7, 128)# D& t; |6 u+ k0 t) N
        model.add(layers.BatchNormalization())7 z% g! g- i. d9 |0 k( c
        model.add(layers.LeakyReLU())
    " Y( v9 u3 t1 _/ s% j4 g
    - t* Y# X$ s( ^$ [5 m* R    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
    1 E; H/ g+ q8 z- |( |( L    assert model.output_shape == (None, 14, 14, 64)$ J3 v9 b$ _/ r: j6 c
        model.add(layers.BatchNormalization())  d* H' W' Y; t. k
        model.add(layers.LeakyReLU())
    / }/ o+ _! n' k/ d& d 2 @% a5 |( p% W# ~3 [% Y/ {" h
        model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))% i0 K/ Z4 V5 U& j1 M* J- ~
        assert model.output_shape == (None, 28, 28, 1)
    6 b* `# \! R) h" j# P
      t0 @4 k+ _/ p" o    return model
    # ], \- d4 V+ K% Z0 M用tf.keras.utils.plot_model( ),看一下模型结构
    $ O$ c0 k' }& E: `* @0 x4 p0 L9 o* B! Y' h  a# I( {

    - d5 h2 M- W; K9 w9 I& x
      l( s% l& `) S" h/ B3 N% ~) O& [$ ?% M: k8 h, |$ a! U3 ~* x
    5 V8 ~; X' e/ V- f) Y8 X( Q, b: [
    用summary(),看一下模型结构和参数
    8 Q" ~& ~7 n' b) W
    ( y& C/ O0 Z$ y+ I+ @/ p

    8 |; b  z3 Y& ]. Z( L1 b2 s# J' H: Y0 q3 ^& H) h
    $ c. W: d" t  I' {

    4 I9 Q' G$ Q% F" q
    ( `  d& R) }, n( w) r! f1 O, [" \
    使用尚未训练的生成器,创建一张图片,这时的图片是随机噪声中产生。
    $ [! v/ S: w& }; y, z* [) A
    4 d0 x2 e2 `* v

    / Q( S) `9 M3 _generator = make_generator_model(): z# _$ U" z3 F8 ]1 D) s' {: Y
    - x) K5 J7 h/ b$ q% }3 m/ p
    noise = tf.random.normal([1, 100])
    " h) f7 W( Z  m9 b: ygenerated_image = generator(noise, training=False)
    ! m- i8 p2 w; E* h. Y# D1 U5 J
    # o' R; |# D' |' x* }7 nplt.imshow(generated_image[0, :, :, 0], cmap='gray')
    * W' H( D) w. J$ P% Y3 S
    ) z- o  q9 h7 B/ P4 v6 x6 Z
    ) y& r+ ?. N2 |- G4 N

    6 n5 p5 N9 i' [; W, D, ]% }
      M4 ?' m2 H1 N6 o/ H6 o9 @
    3.1 判别器
    / ?$ z7 L% a) ?- N- ~. Q判别器是基于 CNN卷积神经网络 的图片分类器。
    # U5 J1 F6 n" R% |0 Q
    ! o' {: d3 Y/ Y1 L

      \% M; D9 M0 H: G7 `def make_discriminator_model():
    $ q9 p. y( b+ ^    model = tf.keras.Sequential()* ?( T, V5 q) w; s- X: c1 K
        model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',; n% r3 f' m3 n
                                         input_shape=[28, 28, 1]))' ~5 Q' }# u# l5 Y8 x
        model.add(layers.LeakyReLU())
    ' p2 h; C  g3 M- W2 G6 i    model.add(layers.Dropout(0.3))
    6 _5 Y( `" R6 J" U2 n0 S9 e# X3 Q0 s' ] " a4 J( n8 F) P3 b5 L# q% o+ J
        model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    : V/ o5 a9 u7 M    model.add(layers.LeakyReLU())& ~1 K! D) a+ T; \2 g8 @
        model.add(layers.Dropout(0.3))( |: p1 I3 ^, i. c" D3 e& x& ~5 s0 }

    ( A. `) C; z1 U: Q    model.add(layers.Flatten())# Z. k4 ?+ s, T/ M# O' f
        model.add(layers.Dense(1))
    1 ?9 c9 ]3 a+ T, l4 a! g7 W- R ' ^2 V# B/ \8 x; N  [3 b9 D! p
        return model6 c4 A# q% S9 V, I' t9 W% U" L
    用tf.keras.utils.plot_model( ),看一下模型结构( ~2 m# ?/ U$ N* n- Y6 m- u' z

    8 i1 s. c) i4 D0 Z, n

    ) {. g* b' ?5 J3 w% x4 O; x5 C- _2 V4 e
    - Y& c# y, F. `8 ^4 I8 J# p/ s% _
    $ K3 E9 Y1 w2 O; L  Z" M1 p% j

    * h* V' f% R8 y5 k) m/ o用summary(),看一下模型结构和参数
    % l6 E1 q& w! R" N) U
    : q  m" k9 \8 o: m' ]" M4 T# r' E

    7 q2 B- L9 h; V4 D' d8 k
    : \) \: B3 H: b6 y8 p9 N) k: y6 ^
    , W, Q# H: ~$ l" X7 {8 q  W8 a
    & K) R7 w  @( [6 y
    * c' j/ S4 {. L, P. g* m
    四、定义损失函数和优化器6 D' v' X0 S/ T" x: o1 ]+ N
    由于有两个模型,一个是生成器,另一个是判别器;所以要分别为两个模型定义损失函数和优化器。
    2 G  @) {0 k8 f) F& O7 ?2 w- |0 @" W' K  P3 }& b- `

    " v/ y. z/ p' y, T首先定义一个辅助函数,用于计算交叉熵损失的,这个两个模型通用。9 o0 k0 y3 i  N) \7 e  ?
    * n1 `- e" P" z7 P0 O4 H6 I
    : K) b/ M# ?" Y4 c: l  W* Y
    # 该方法返回计算交叉熵损失的辅助函数: a9 Z/ r+ x) t) C
    cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)" Z; d% t# I$ [% L
    4.1 生成器的损失和优化器
    % L; u5 h+ p6 g5 `4 n1)生成器损失3 n6 e" I; h$ C1 Q6 O0 b

    7 O# P7 F7 A$ }/ A6 y- F

    , c3 G5 t, _$ ?, g( |/ |生成器损失,是量化其欺骗判别器的能力;如果生成器表现良好,判别器将会把伪造图片判断为真实图片(或1)。
    0 {  j4 H$ w% x: V
    , L8 X' B+ @8 B

    $ y( x& Q" a1 u5 W这里我们将把判别器在生成图片上的判断结果,与一个值全为1的数组进行对比。
    & \3 w) Q, A) V( s  Q' [/ ^$ ]% G- I  Y8 X& K" ?
    # ~* Y; h* {$ G) y3 @1 A
    def generator_loss(fake_output):4 v3 M7 y$ h5 p% W- m/ _
        return cross_entropy(tf.ones_like(fake_output), fake_output)* T2 g7 b& j  l* T0 |
    2)生成器优化器7 g' ]1 N/ D  |

    ( w3 K" [+ ?$ D0 D/ C. V

    4 x7 t" c; r6 a1 Sgenerator_optimizer = tf.keras.optimizers.Adam(1e-4)
    6 U* w7 \8 \8 V7 G# q6 `4.2 判别器的损失和优化器
    0 H5 X% c! i1 o4 b1)判别器损失
    6 u' u" ]& X8 f: C$ ~- o: {/ n% |$ z$ F$ y5 `' e4 L+ f
    % y% ^, \# \/ ]; c: Q' ^0 I
    判别器损失,是量化判断真伪图片的能力。它将判别器对真实图片的预测值,与全值为1的数组进行对比;将判别器对伪造(生成的)图片的预测值,与全值为0的数组进行对比。
    & x( Y! c7 `% g+ L( G7 @0 r; a# ~+ [# z" `) N2 a) i
    $ X1 R6 Q# `. P" m
    def discriminator_loss(real_output, fake_output):
    " B  {% @, n5 v; A2 s  a/ u9 `    real_loss = cross_entropy(tf.ones_like(real_output), real_output)5 V/ L* ?# ~  `& r
        fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output). m) ]1 ^  M* l4 Z" u' y
        total_loss = real_loss + fake_loss# Y: U( ~$ e; v3 s2 ^
        return total_loss0 ^, [3 U* }6 b% N3 l1 o$ ?* i- t
    2)判别器优化器
    / \1 p7 h6 U3 u6 a+ G" H' }; w$ }) G4 d# I
    ! x3 g/ [" O- i( g1 q5 b
    discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)
    " l0 E0 M  O& [# [五、训练模型1 J+ e1 |" j) G  \3 ]9 M
    5.1 保存检查点
    4 ^$ E, E& p3 @# ?; b保存检查点,能帮助保存和恢复模型,在长时间训练任务被中断的情况下比较有帮助。
    % }2 u, W+ m. z1 D, S. U. d. a
    3 e0 c% {* b' c

    : c7 L  w2 q0 @* ?checkpoint_dir = './training_checkpoints'( w) w3 F) i: M: o; {1 F
    checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
    0 P0 ^0 N8 N1 P# Kcheckpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,3 a) P9 q+ g# @' M6 i8 o
                                     discriminator_optimizer=discriminator_optimizer,
    5 U4 F0 q# r, H: h) |; l1 M3 K0 i# L& q                                 generator=generator,+ t' X" Q5 V: ^  B
                                     discriminator=discriminator)
    8 R& A0 M% Q& t0 V' F- B+ c& `) ^% p5.2 定义训练过程
    7 c5 Y2 z8 f2 e* G' Q, l7 f$ |3 O3 eEPOCHS = 50; b0 k) p, G- _6 ^* R8 @
    noise_dim = 100% r- I- p, R3 s, n* E- c1 @
    num_examples_to_generate = 16$ N4 j4 {8 s/ A6 V

    ) D/ h% M8 |6 e  j % j  F; W) f. p6 i# B/ n
    # 我们将重复使用该种子(因此在动画 GIF 中更容易可视化进度)
    4 R- W/ n/ N1 |seed = tf.random.normal([num_examples_to_generate, noise_dim])! c) }( p: C* w" w! \/ }
    训练过程中,在生成器接收到一个“随机噪声中产生的图片”作为输入开始。5 j+ e4 q! S9 B
      ]$ i& Y0 l/ _& \

    * o8 G/ ^$ N* N$ A6 [# r7 t判别器随后被用于区分真实图片(训练集的)和伪造图片(生成器生成的)。
    3 c2 x" P8 c6 K  G+ M, Y, h) h

    7 ]7 s! ~! {' O两个模型都计算损失函数,并且分别计算梯度用于更新生成器与判别器。
    7 k; c/ |' s+ K  Y& e
    4 ~( J; c- X2 @' m/ n) Q# A( A- W5 H
    4 E4 C: H* C  h) `
    # 注意 `tf.function` 的使用1 L% _* e8 e7 a- P  ]2 n5 r
    # 该注解使函数被“编译”# x9 U# o+ `& u2 f1 [
    @tf.function4 H1 Q: s! }. O; i3 E% a* R
    def train_step(images):9 ]! v: H- j- H# h# ]
        noise = tf.random.normal([BATCH_SIZE, noise_dim])9 x( b6 |' `+ p6 V; L
    - ~; ~. G+ N) f% v
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:9 y" e# ~3 `, z) F* f- \
          generated_images = generator(noise, training=True)
    - X  d- X: \; q) A
    ) H, E/ j3 N2 o2 T3 X      real_output = discriminator(images, training=True)
    ! ~* v) e+ ?, r; Z6 ?1 e: g7 U" P      fake_output = discriminator(generated_images, training=True)
      a2 x: G3 L  a, @
    - _! X2 G) ?% ]% E# _1 L5 Y      gen_loss = generator_loss(fake_output)
    1 M2 b+ @/ v/ g* Q) K( q      disc_loss = discriminator_loss(real_output, fake_output)
    , ?6 v6 B7 Z4 ^: ?$ }$ t0 J+ }   [- v9 `  V1 r( s
        gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)7 T' M5 I) r) B' |/ ?: J; f. }1 p
        gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
    5 X& p$ ~( U$ B) d, W8 `; b* Y ( Q4 b7 P3 f3 o
        generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))6 F4 s( a: k' E( j
        discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables)), z4 _! R, A3 Y. d

    9 A' G6 z6 r' C) O# E: K  x1 idef train(dataset, epochs):6 i. g  ^1 }% E
      for epoch in range(epochs):
    & w, ?2 i& T# U3 B! d: I    start = time.time()1 ~  z0 x# F: f/ g/ q" k, z
      J* W. K( q$ t
        for image_batch in dataset:4 j+ B+ z, E4 k! B1 \9 s$ E
          train_step(image_batch)
    5 v5 B4 }- G2 L2 j5 U9 j6 ]9 w8 W
    9 F) Z7 Z+ l' J# C) W: D    # 继续进行时为 GIF 生成图像4 s: l. A' }, s9 W8 ~
        display.clear_output(wait=True)
    " V8 B/ c) }0 D# ]; W6 D, N6 q! S    generate_and_save_images(generator,  N3 K# R1 l% f. }; D( I
                                 epoch + 1,; z, i3 H9 ?# z4 E3 z& ~! g
                                 seed): a3 G6 }* \& t4 j
    9 u: Q6 c1 R9 v  H! b4 R0 f; w0 F
        # 每 15 个 epoch 保存一次模型
    9 F" k. S$ x8 h    if (epoch + 1) % 15 == 0:/ W  g" ?  y8 D$ m# V- T
          checkpoint.save(file_prefix = checkpoint_prefix)
    : A/ q3 e2 f' Y6 e" g# B9 E: p " m/ Q" K+ y' }. r( d
        print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))
    : f4 @: v5 F2 V2 L7 l ' w+ @* N. `0 E7 ]: s, G1 G
      # 最后一个 epoch 结束后生成图片
    7 S. X8 }  l, ?  T: G  display.clear_output(wait=True)
    , Q1 M2 G! G& }! G) o3 w, z  generate_and_save_images(generator,
    * ^3 g- x) `$ u, [9 P                           epochs,; o* C1 N* t! l) X6 Y' o
                               seed)
      r( ^  ~/ `* T# j" y
    6 R( c' T4 b7 g# 生成与保存图片
    5 S& h5 W$ X# _6 Mdef generate_and_save_images(model, epoch, test_input):' ?( u  P  m% X! Q7 m6 X" @
      # 注意 training` 设定为 False
    * H4 q& N) r# W" l2 x  # 因此,所有层都在推理模式下运行(batchnorm)。: d* O7 I1 D) v4 G7 e
      predictions = model(test_input, training=False)& r& s) A$ h* m
    , t+ R+ J5 i- }& n7 m  \
      fig = plt.figure(figsize=(4,4))
    + I! ~! {- T/ e. F 8 ?& F" S# W3 `4 I/ U# P
      for i in range(predictions.shape[0]):
    7 L+ k9 X1 t+ b& J/ ^3 Q4 m      plt.subplot(4, 4, i+1)
    9 i+ c% d0 }: _2 v+ ?( ^; ?      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
    7 ?5 z# }  `9 [$ g% m# R      plt.axis('off')
    4 o# k# i- l5 E' m 4 t4 h% F+ i! i) r
      plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
    . U) H5 T# O3 f: ]  plt.show()
    # c: n4 q  `) l8 |- v5.3 训练模型
    6 G' m$ w! k# E调用上面定义的train()函数,来同时训练生成器和判别器。
    , d. v1 C. O, c" f$ Y3 i3 k6 J
    ' Q3 A* P$ ^4 O- ?5 t9 I
    ) x6 \0 d6 o) y# W" N
    注意,训练GAN可能比较难的;生成器和判别器不能互相压制对方,需要两种达到平衡,它们用相似的学习率训练。
    % {; v; N: O& T
    % |; m4 G% R; E% H, G7 P4 I# s

    ! i/ k9 V# F! j9 w/ {, Q%%time
    & N0 ^! V1 ]$ v3 z; ntrain(train_dataset, EPOCHS)
    : u( N# I' J' x  d9 m在刚开始训练时,生成的图片看起来很像随机噪声,随着训练过程的进行,生成的数字越来越真实。训练大约50轮后,生成器生成的图片看起来很像MNIST数字了。
    , g6 H& ?! C+ R8 n+ b4 t
    4 g, O4 u( s/ R" E" [

    7 R/ S4 y" }  K9 r* |" `5 C+ W. g训练了15轮的效果:% f8 \) e( s# a& X* U2 C5 G

    * |5 y+ X) [+ D" Q
    , a# x" B4 B. H  o  E9 t# p

    7 y; r# {: q/ {- X3 H  `
    * o. M( s3 ]) e2 r; h
    3 @. E) u1 G9 N7 r6 j

    0 t7 H8 G+ A$ s% Y( E& W9 M训练了30轮的效果:9 i0 L) N+ C* I4 o6 ]
    5 F( J. z. u4 x
    5 I9 x6 t  u: g2 W6 C& A; e) j9 l
    + o9 T" }- B6 g8 n$ O: C) P2 L

    & [8 I8 H# c. X0 @! M
    ( _; x& ~8 K* U3 ~' R! `

    $ ?" ]6 n1 X' I6 N& W. U) r3 y训练过程:
    4 x1 p& j5 R  }: c& j# W& [' A3 w. }/ J( r
    ! |$ t, k7 x! y

    ; A. e  q5 R2 j1 ~8 O, M

    2 T" V' \+ ^/ u8 ?' k( k+ E- ~4 r2 j0 d( R

    8 t# s, E8 M" z/ ]% L恢复最新的检查点
    ; b, S/ b- C# }8 @4 ~- U2 D5 U; g  `! a) b$ u

    1 N8 g+ x: h' \; l" V. Mcheckpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))# A/ b& X0 r3 i& k* c
    六、评估模型6 P3 s( s# _8 [8 j* K: W
    这里通过直接查看生成的图片,来看模型的效果。使用训练过程中生成的图片,通过imageio生成动态gif。% V+ {8 g# Q" y3 ]7 Z
    5 l) d1 {$ ]- o# _( n

    , ?0 F0 a  }$ E8 v6 F: o# 使用 epoch 数生成单张图片
    ( \, ^1 G) P  Y, Cdef display_image(epoch_no):
    * E$ ~: x8 M) S) {+ ]  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
    ! H  {' [7 D0 E  ^9 n, w& ?+ d/ F 4 B' N' U$ f" F( A% U5 P% `
    display_image(EPOCHS)
    3 T* f. y9 V3 O, \7 @0 z% uanim_file = 'dcgan.gif'
    - Y6 Y; F/ M! k0 i$ C8 C5 s# o
    ! w2 d) ~2 Q) z4 `$ B% R; Z' {with imageio.get_writer(anim_file, mode='I') as writer:* o' y. p4 \8 I
      filenames = glob.glob('image*.png')) ^9 }( l& P# Y$ l7 a6 v. Z, }
      filenames = sorted(filenames); E! A1 w4 \, p9 C/ k! k$ ~, K
      last = -1- O: c0 e; M! K' ^% {5 W
      for i,filename in enumerate(filenames):
    9 j6 K! T1 i9 _! K    frame = 2*(i**0.5)+ n5 b! t7 ~& G" c) Y( I. }+ R6 X( B
        if round(frame) > round(last):" B; {5 D! z. k0 V
          last = frame! J! n& Z) ?) I4 g
        else:9 |* e6 q0 I  Q! o0 G
          continue
    7 T  W# @" C8 A2 R/ l    image = imageio.imread(filename)
    ( p8 ]7 L9 |. ?7 P    writer.append_data(image)
    ) o; j. h9 R' A: c0 U: p+ h  image = imageio.imread(filename)
    4 ]1 n* V$ M  V( n  writer.append_data(image)! Y6 H3 p: U( \

    9 P/ ]& V" i$ O7 o1 Y2 Himport IPython
    * F; J6 |2 |$ `: Z+ E, Iif IPython.version_info > (6,2,0,''):+ o  b9 W9 h, P- i, m) G+ H/ g
      display.Image(filename=anim_file)4 J& S. \8 o, j# N, t
    ! @- z% W3 \/ @; a9 a

    , z! P5 S* E: H6 p: q1 L2 d9 ?- P: T! |
    : Z& `. @# ^( @1 t8 e
    完整代码:* e- \& Y; X$ J* p2 P
    3 r6 C; R5 g: I/ I# L" C) B
    8 s! N+ H- I3 r4 y! h  w& h
    import tensorflow as tf$ c- k# w& `+ i7 q1 N% l$ d
    import glob' [6 F3 P6 V9 d1 S5 m& m* W
    import imageio2 u4 o7 S8 X' D# x) p3 T" }
    import matplotlib.pyplot as plt7 z: V  A, {$ M6 P, a# T9 P% f
    import numpy as np
    4 @" H; f! H$ _5 P7 T! gimport os
    9 l$ \& v$ S& M9 P8 z; E% Pimport PIL
    + j0 S" _$ P% J/ @- T8 P+ yfrom tensorflow.keras import layers
    - ]- L: I/ S# T: }import time5 l! r2 p: o+ J$ B: S2 H/ {
    / \+ r" k: v) t/ y
    from IPython import display8 w+ f0 X: a0 M

    6 }4 S- w: s" v  Y9 z  y(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
    % P8 {: d. D- h( N" b# H% l9 V 6 Q  e/ r5 m8 b' e: ~) t( J, U
    train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')+ ]& Z/ {& x" i$ z* c+ u( }& ?
    train_images = (train_images - 127.5) / 127.5 # 将图片标准化到 [-1, 1] 区间内. F! H/ ]8 J& _# A7 E( }

    : j1 }" z1 a; ?4 k7 P- aBUFFER_SIZE = 60000. X5 o1 p; `" n' x+ Q0 i$ [
    BATCH_SIZE = 256
    ) p! y9 `* Z3 `
    2 p+ I9 ?& C$ d4 e, D: f# 批量化和打乱数据
    8 y' h$ p* Q" \5 Y7 etrain_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)& [9 F: [0 S4 w$ ~, j3 J0 c. W
    ' U( I/ I* J6 y; S, O' z
    # 创建模型--生成器: B  T! F, q  a! L
    def make_generator_model():' r2 Q5 x( k- A- y8 ]: l
        model = tf.keras.Sequential()
    - U! i! x" F' l: \2 ^0 i    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    7 p1 v+ f( V, Q7 x( l5 o3 j9 M    model.add(layers.BatchNormalization())
    - E# L6 e8 k( O  O8 ^' i) ?1 V/ d    model.add(layers.LeakyReLU())$ U: H4 U' H) k9 Y8 q
    1 @8 X9 ~; j' Z0 S3 c
        model.add(layers.Reshape((7, 7, 256)))
    + ]8 X/ _# n& {3 }; ?: I$ t  I    assert model.output_shape == (None, 7, 7, 256) # 注意:batch size 没有限制5 E$ t0 @2 K" r6 k! `5 C9 s
      x5 m& ]% J- n1 m8 W% a
        model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))! y$ f2 I) t: X
        assert model.output_shape == (None, 7, 7, 128)2 c( g( A" o9 U- o! ?
        model.add(layers.BatchNormalization())5 V7 l1 k* Z9 L8 T
        model.add(layers.LeakyReLU())1 ^* a' Y$ v1 t. ^: N* X
    ( {) Q6 p# g3 j  v
        model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))  _% H" c& I/ L+ R& u, @2 G
        assert model.output_shape == (None, 14, 14, 64)2 {/ C/ ?& [5 v, B
        model.add(layers.BatchNormalization())0 Z% ^8 d5 y8 _
        model.add(layers.LeakyReLU())5 R2 E# C* g9 S4 E
    1 F7 W& b( Z% F9 o# E2 L
        model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
    : w8 R0 c/ c2 [0 E1 N    assert model.output_shape == (None, 28, 28, 1)
    + o' F7 A' I, C' g7 v6 z; B) |
    9 C: e& H: q% N- [6 R& ^% ?    return model
      ?5 Y8 V* z3 |2 ]8 ~ 5 ]' I- }: B7 H" H  c& {8 O
    # 使用尚未训练的生成器,创建一张图片,这时的图片是随机噪声中产生。/ {9 u! b9 U; q
    generator = make_generator_model()6 X0 b$ S, z8 H) ]% n, @) O

    , H; A. w. G/ P6 G( A/ O  nnoise = tf.random.normal([1, 100])
    3 a, [; S# e1 @% p+ o) A( J; g7 ygenerated_image = generator(noise, training=False)
    6 \( T& y6 x: g  [* N, }7 A
    4 c; r6 ~7 L& s$ [: v! L, xplt.imshow(generated_image[0, :, :, 0], cmap='gray')
    - d" e5 @) h6 J6 qtf.keras.utils.plot_model(generator)
    3 r+ y* M5 ~& S' Y* g2 k( t 2 x: V4 t" C5 M8 H, T1 _& A( u
    # 判别器0 }0 }! A: e. F- i
    def make_discriminator_model():
    . {$ r5 u6 w, `3 j. N8 s    model = tf.keras.Sequential()
    # d& ^$ d, p& I$ j    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
    . |% ~% \5 a0 D9 M- |                                     input_shape=[28, 28, 1]))
    1 f* K# A5 T3 J  `1 b$ n    model.add(layers.LeakyReLU())
    ; n/ A; J, g' G1 n  v+ G    model.add(layers.Dropout(0.3))
    9 n4 ?' Q( V: A# `
    7 g/ {' `) d8 i    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    - A1 @( Z8 E3 k) \+ m    model.add(layers.LeakyReLU())
    6 I0 X, W( j2 r! f7 F* z# W3 H    model.add(layers.Dropout(0.3))
    1 O+ r1 e3 N( l' V; p" c % n) u0 w! P7 a5 b, F
        model.add(layers.Flatten())
    ) y: `& l0 G% b( K% K, Q) N0 l: X: |7 _    model.add(layers.Dense(1))
    5 _6 Q7 U+ T, \( G' ? , s, P2 _) L9 \- N& x6 N  X# H& e
        return model; A% @3 h% L4 b& w
    ; `, Z. Q2 R7 `' X
    # 使用(尚未训练的)判别器来对图片的真伪进行判断。模型将被训练为为真实图片输出正值,为伪造图片输出负值。  I2 f- R- W& {$ O
    discriminator = make_discriminator_model(); F0 E/ \/ O" e4 e
    decision = discriminator(generated_image)
    5 N* {- G3 C9 K: _6 J6 jprint (decision)3 D6 @: W$ y( Q

    ; w5 Z) i5 v  t( F% R# 首先定义一个辅助函数,用于计算交叉熵损失的,这个两个模型通用。
    % ?, c, |2 ]% \9 pcross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)# I  F' w2 x  A5 y& K- s
    - U- o' T8 _$ y4 R  O6 a+ j' k
    # 生成器的损失和优化器
    8 v) U' C3 ^8 e. Vdef generator_loss(fake_output):
    - t* U, Y$ T) |' V* f    return cross_entropy(tf.ones_like(fake_output), fake_output)
    / q. S; A2 P$ G* U( K7 r. @/ ugenerator_optimizer = tf.keras.optimizers.Adam(1e-4)
    ! R7 m6 G1 g- T6 C ! o9 w3 {' Z0 S( `
    # 判别器的损失和优化器
    3 d4 H, P; o7 ?+ {  h) S% Vdef discriminator_loss(real_output, fake_output):
    9 q6 d1 E3 b! h" b$ L0 `    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    ) @# a$ g. @3 H7 {9 @1 j    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    ( }+ D% z" u7 q) z, ]' z    total_loss = real_loss + fake_loss9 T% D) I& y" q5 Z- E
        return total_loss
    % D" S" ?. P+ o. X6 e: x6 Rdiscriminator_optimizer = tf.keras.optimizers.Adam(1e-4)
      S- _5 D5 S4 V! ]9 p
    , m" {! D$ M" k% w# 保存检查点
    8 {0 E7 F1 c' `checkpoint_dir = './training_checkpoints'. H& j! u, {& K( x6 J
    checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
    + W( L, {+ s. x6 {' e$ b# Dcheckpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
    8 O+ J1 \1 x3 v+ @/ y                                 discriminator_optimizer=discriminator_optimizer,4 ^# Z8 h8 V. G2 {
                                     generator=generator,+ W1 o$ ]' _  k4 A0 ]4 l' i6 Z& W
                                     discriminator=discriminator)' O6 p3 U% q7 Q9 u9 g2 d

      s/ `$ Y% |# {9 G5 a$ _4 ^# 定义训练过程2 o  @" d8 b" X+ g2 v
    EPOCHS = 502 U: B" ~# Q; U& P/ O
    noise_dim = 100
    $ G& C0 @5 V8 i6 ]6 [3 A) n/ ynum_examples_to_generate = 164 Q5 g9 {9 x, H: r

    1 @' q7 L  {  p7 z/ k# 我们将重复使用该种子(因此在动画 GIF 中更容易可视化进度)4 m: v( U# E. B# H. Z
    seed = tf.random.normal([num_examples_to_generate, noise_dim])
    ; }' g6 N) j! U, S% _
    2 Q8 q0 |8 w! t0 \( w0 [5 w3 ]# 注意 `tf.function` 的使用
    $ C) l  ~% b$ W7 f& C! v# 该注解使函数被“编译”
    5 F1 m" L+ t/ ?4 W8 n@tf.function
    % `! j: f, c3 F% P) udef train_step(images):
    ( C" U4 M) \" o4 P9 X    noise = tf.random.normal([BATCH_SIZE, noise_dim])8 [9 I9 R5 T( `7 B# k, I
    & r& P+ z6 m* j
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
    ; J! E  a& T* ?, z3 M. |3 q. O      generated_images = generator(noise, training=True)6 J0 C+ a5 d) U2 W' s

    % V% r$ g& ~+ J" t; X: a      real_output = discriminator(images, training=True)' ]! E, S6 r" V1 Y
          fake_output = discriminator(generated_images, training=True), \4 r: B% e  s3 P0 A% M, V
    % \* f3 {. F0 j. h8 |8 y
          gen_loss = generator_loss(fake_output)1 b0 C" C* f: X6 c' A9 I
          disc_loss = discriminator_loss(real_output, fake_output). e( v) @) K; k: f8 C% y# @

    2 L. ]# H/ n! |8 m5 \    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    & V1 M) L% ?/ M& J* p    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)- j  E7 N% ?  r

    6 r2 t; d  ~  W* t6 R  A    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    , v! F  |6 N# b+ G! T( f! ?  j    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
    0 l4 N4 ^2 O3 Q5 C: @ $ l2 P% v* k" z& @! f% `& i% |
    def train(dataset, epochs):' y0 z# j' R% e
      for epoch in range(epochs):
    4 S2 p/ Z9 h. X( t0 M5 {% n    start = time.time()
    + p6 }3 ]( Q5 Q2 p( Y
    ; ?4 ^& ~! o; z% l( M    for image_batch in dataset:) Z& D4 Z: S- e$ w# s6 ?+ p0 ~
          train_step(image_batch)
    " r* v( f8 k% o5 f/ J- c+ M
    ( R9 u4 |. m: K. U* @) P    # 继续进行时为 GIF 生成图像3 f" J) D2 Y5 n3 d% h9 L& F
        display.clear_output(wait=True)
    # L: P2 |' p! h    generate_and_save_images(generator,
    . @- t! i$ Q) x* x+ _$ |                             epoch + 1,
    ) L, }3 R, s) L1 A                             seed)6 @6 P; i0 s& N& @, F& n7 m
    + {3 i- `: J" k2 D6 S( G2 e
        # 每 15 个 epoch 保存一次模型  d  a* X" T* L3 q0 p
        if (epoch + 1) % 15 == 0:
    . Q5 i8 A) K# L$ b* Z+ b3 H! u      checkpoint.save(file_prefix = checkpoint_prefix)
      R. ?0 s0 B0 x6 Y8 E. E: _3 C , B+ B2 U4 ^5 {7 `+ C' e: _* p. f
        print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))2 Z( A3 t$ ^- _0 X. Y3 x8 Z

    / L1 c5 [# c4 W% z  # 最后一个 epoch 结束后生成图片
    + d8 M, m- f: N9 k  display.clear_output(wait=True)
    * {/ ]' w6 _: P0 \, C6 k  generate_and_save_images(generator,2 U8 N! S6 v  u+ c: ]% R5 Y# I& x
                               epochs,
    - P7 K! m% }( j& w% e7 Y/ {! A  \) \1 t                           seed)6 V" A- y" s" l3 E3 I4 O9 ?2 b! e
    6 i, P+ v  H, v% Y- G
    # 生成与保存图片
    . U3 g, b# R/ o6 g0 ]+ E) pdef generate_and_save_images(model, epoch, test_input):
    7 B: b1 K7 v1 ^+ {# K# s3 @- ]+ b  # 注意 training` 设定为 False  P) g# L2 Y# w* s
      # 因此,所有层都在推理模式下运行(batchnorm)。
    8 g% U' @2 ~# W  predictions = model(test_input, training=False)& m) G) r" b5 ~! u: n) h, G/ ?

    ( m: T' D, e; V, j$ y9 M; s  fig = plt.figure(figsize=(4,4))
    6 g( K: o  r( E  p) ^2 ~' Q" a3 Y9 e
    ( B% {; B7 H( d1 l- ]  for i in range(predictions.shape[0]):
    8 J, e* u8 L0 S8 g" x( K      plt.subplot(4, 4, i+1)
    * i$ m, R& E. I2 @4 ]      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')+ _& w; ^. b  z; N/ {7 b- J
          plt.axis('off')* s, v+ o' T! u: u. c( E: S& R
    5 }5 ?" P8 s1 D& m" T7 w
      plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
    $ b$ Q& d/ ^  ~7 R, j  S5 `  plt.show()
    6 U3 h. r6 h! n, S. z . X3 z1 l1 y* w9 o/ ?$ o0 A5 B1 W
    # 训练模型6 c- d' p0 l8 T( j: Q$ s  f6 N( D; u
    train(train_dataset, EPOCHS)
    4 _9 R6 n8 V# a4 w2 ~- V4 {$ o4 V
    4 _  f0 S' s7 c% q# 恢复最新的检查点, h7 Q; @) |3 Y8 |* c) u8 \, _8 p
    checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))6 r0 Y) V7 d" A- ^) f* {
    - {% [6 g9 o$ |& V- V. ^- ?& d, P
    # 评估模型6 Y. {9 N$ W3 `$ Z
    # 使用 epoch 数生成单张图片
    " x$ N0 u/ X) }; Kdef display_image(epoch_no):
    ) W! Z3 _7 E5 p6 K. U8 M  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))9 t, E) D' `) ~& y$ z" h- Q

    ( w* P1 R2 |& Y9 Q, q+ Gdisplay_image(EPOCHS)2 [* y# v4 y& i7 |
    2 C1 ~9 N! I. ]1 H2 Q/ ^  t$ n
    anim_file = 'dcgan.gif'
    + m; B' d2 |$ s0 G 7 _- x! f) C* e0 U
    with imageio.get_writer(anim_file, mode='I') as writer:
    1 O9 J6 K8 e7 E- ?! F2 c" `  filenames = glob.glob('image*.png')1 m2 O+ U3 n( s5 j! i: m9 ^4 B
      filenames = sorted(filenames)
    ' A4 f( N( \' v# {/ @. b4 C5 p4 g- ]  last = -1
    ' e+ B, V1 s+ Q) F/ I  for i,filename in enumerate(filenames):* E8 @0 ^% d' _* _
        frame = 2*(i**0.5)2 I! F2 i5 q( V; O( b# ^
        if round(frame) > round(last):
    . ^  V! K$ y: q* H  V. c! \/ C4 ?      last = frame$ ^4 y3 r/ g( w1 z) g
        else:
    * i; p( M+ q' Q' k      continue* N! {$ L3 |% c
        image = imageio.imread(filename)# f' I" z" }  {0 U# ~
        writer.append_data(image)
    $ z( }1 j0 {' u6 ?7 k  image = imageio.imread(filename)% C4 ~4 o% i* f9 V3 @9 a9 l
      writer.append_data(image)
    ( k9 p& j  O0 B+ e 1 C  L; B' s3 j# w3 }$ `2 Y2 `
    import IPython# g* j: @1 d* j3 }" q$ ?+ r4 J
    if IPython.version_info > (6,2,0,''):5 M1 n9 |  ~6 x
      display.Image(filename=anim_file)5 X: I1 Z# ~$ w4 U( R' P! ^! `+ {
    参考:https://www.tensorflow.org/tutorials/generative/dcgan+ a4 w  _; H, C" C- m
    ————————————————
    ; r# Y; P5 v5 Q3 C" u  I版权声明:本文为CSDN博主「一颗小树x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    , Z8 K# w6 R: U. i/ o2 x原文链接:https://blog.csdn.net/qq_41204464/article/details/118279111) m( x( t) S8 i9 r. r
    9 m6 n- e8 K$ Z& ^" v
    1 ~8 G% s) F. B; z
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-7-30 11:30 , Processed in 0.648707 second(s), 51 queries .

    回顶部