QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5713|回复: 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
    8 D! ?' f+ F$ a# A
    深度卷积生成对抗网络DCGAN——生成手写数字图片
    " Z% K$ z, H! b/ c8 v$ ^: k前言
    4 p9 p! ]6 k$ |' _& j, h本文使用深度卷积生成对抗网络(DCGAN)生成手写数字图片,代码使用Keras API与tf.GradientTape 编写的,其中tf.GradientTrape是训练模型时用到的。
    6 c% E5 c/ C+ h, W  L! _/ X  s: h, s4 F7 M9 f* b# ~

    ' t6 @! s$ f) W1 @8 V 本文用到imageio 库来生成gif图片,如果没有安装的,需要安装下:, J+ s- r5 V4 u3 b/ k

    ( [( r9 ]  T( W( R( \6 }

    " [6 k8 Y" \: ?/ A: F- r- c! ~# 用于生成 GIF 图片6 ]2 S- S9 [7 ~! x; X8 C/ e
    pip install -q imageio8 j! A& J& l- ~6 H) W
    目录/ g. i7 B, V6 u; o" ~3 A

    & }9 w( P& |! ?$ Y3 t! _

    ; M' d& p4 P" J) f8 _; n9 j# D, v前言: G7 h0 F5 r1 h& c+ n2 X2 f7 @! W6 Q
    5 z/ Q9 b* B: z9 p
    - c4 O1 y/ v3 I  v, Q
    一、什么是生成对抗网络?2 G. q# I# q5 _
    % U3 R3 p; I* ~. b7 W2 I

    , w, T9 e' X$ `3 D& {二、加载数据集) p7 Y8 P; F0 D/ {! k
    2 i+ Y6 z; G9 _  u- j

    + p2 j/ {! X- C# e4 A6 v9 Z7 m三、创建模型
    ( Q6 Z0 `  _4 H" S( w: {9 W
    ) Y! ]9 H' x' s+ y5 R, j
      b- M5 f( J) K3 Z) h6 I
    3.1 生成器
    # `: p6 w" T7 |2 ]; X( _* T* q  D- o+ O3 A; W
    ! w% X. A2 _% a4 X/ I! n' x
    3.1 判别器
    5 R1 Y( s% B/ z% l
      A  G0 K) L+ f
      j3 j  X  f" {) x) c- p
    四、定义损失函数和优化器
    + [0 Q7 {/ `% u6 `1 g  d+ C2 g) u! y/ c' v7 H

    $ ?* D9 M  ?. Y6 R, y3 v/ q4.1 生成器的损失和优化器. e- \7 u. W: {& r7 b5 B( O/ l" X

    ( K1 n: D; ^2 O1 O& E/ x8 J

    4 ]. y0 w5 e  `" V, j) m& }, U5 N2 y4.2 判别器的损失和优化器) n9 A& J! L* A8 e" Q( s( X; j5 f

    8 o/ W3 I# L# B# u1 X4 y
    8 F! u$ }6 b6 O( d$ g
    五、训练模型5 m( v2 D. j! I9 M! m/ W3 L

    % \+ O5 h2 f/ E8 T+ w  J
    . t9 Z' i8 R+ v& r) P
    5.1 保存检查点$ z, k7 h0 {: i5 |# V; i  R
    4 L# w3 O- p$ n: f; ~& Y  @

    5 ?8 Q" l6 L/ f6 E5 p$ p5.2 定义训练过程
    + K0 Q9 E, b8 c7 b. ~" H
      w0 h. ^# J0 V" k+ F6 Z3 [

    ) C0 U% h+ ~' X; }* U! m5.3 训练模型# s- h/ Z* F7 s

    9 Y% j/ s  @9 d8 P! |' M; h" T

    7 ?$ G  r9 I7 W& s6 Y. y六、评估模型
    - g3 `  D, U' I/ c0 V5 b
    * s3 X' D1 S1 W3 v. n! w- V
    . b3 p2 _8 M* v/ z8 T( Z6 F& E
    一、什么是生成对抗网络?
    " R3 i; l! }! e+ S4 n" [生成对抗网络(GAN),包含生成器和判别器,两个模型通过对抗过程同时训练。
    . \" n( f6 [4 G2 L9 y9 S" y" P, }  K' ?' g0 p" [
    % V9 E+ V% ^5 L* K
    生成器,可以理解为“艺术家、创造者”,它学习创造看起来真实的图像。
    3 l. D( [+ _3 M
    / M( y" y' o: \
    / D. k- ~' m7 l& k
    判别器,可以理解为“艺术评论家、审核者”,它学习区分真假图像。
    8 l% x2 q0 z! T' y: A# u7 a3 X" J0 m7 ~& w% E
    # Y, s7 D. U% ?$ |0 ~# W) s
    训练过程中,生成器在生成逼真图像方便逐渐变强,而判别器在辨别这些图像的能力上逐渐变强。
    ) j2 G0 i; ?" `) E# m: l, r  t; j
    4 ~' \. t* o/ F  V2 {8 ~* z
    当判别器不能再区分真实图片和伪造图片时,训练过程达到平衡。  D; G  V9 r0 z/ x: I8 {' M
    & L% G: D6 a4 m+ e" u, z( \" a) U

    - p7 t; }" ^( T) d本文,在MNIST数据集上演示了该过程。随着训练的进行,生成器所生成的一系列图片,越来越像真实的手写数字。
    ; |' P" f3 v! K5 G% T- p5 h$ [3 w5 l7 p5 b% m6 q5 g6 Y
    " g' p: v" q3 @$ H# A
    二、加载数据集
    $ K. \! j- ?) G/ C7 X& h" q6 B使用MNIST数据,来训练生成器和判别器。生成器将生成类似于MNIST数据集的手写数字。+ ~, O6 e, e& p$ j3 h; Z6 W

    5 l5 L/ v# y! y/ i- O' A
    ' J& k$ j, X7 A6 ?+ e
    (train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
    . c6 f4 Z! `/ K: v9 j - Z# H- D* W4 [9 S4 i8 b$ H
    train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')0 G1 [% c& ~) Y2 V1 m
    train_images = (train_images - 127.5) / 127.5 # 将图片标准化到 [-1, 1] 区间内, I! N/ {# @) Y  J0 B- ~7 V6 M
    8 [8 Y9 ?  O  P& [
    BUFFER_SIZE = 600002 s9 M4 x0 \# t
    BATCH_SIZE = 2567 d$ ?& a# X( l1 x+ r
    6 {& d; _; p! Z! w% i# C$ v" x0 h
    # 批量化和打乱数据  l* h& g5 F* m8 R1 \
    train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
    5 p9 t( @# W8 i  F9 R三、创建模型
    8 [$ f& C# f1 G/ b$ c0 N主要创建两个模型,一个是生成器,另一个是判别器。
    2 H7 `8 B) q+ j  ]7 z
    . B3 Z6 V  \+ ?4 g: r

    $ q5 Z5 F! U9 X: f: A: }. T" {3.1 生成器& n5 r: S7 W3 t( ]! j7 K
    生成器使用 tf.keras.layers.Conv2DTranspose 层,来从随机噪声中产生图片。
    # k8 r6 ]% {1 S
    7 ?+ N, i- w7 B: S

    : A/ ^: ^8 N$ q& U- Z然后把从随机噪声中产生图片,作为输入数据,输入到Dense层,开始。
    ) P$ a9 i$ U9 r, p, s- B5 X; c, i
    + N+ H! G' `! y! r
    后面,经过多次上采样,达到所预期 28x28x1 的图片尺寸。9 w1 Y( A& a! z& f4 u
    . Z. J" ~% |/ |0 P  O, N+ d
    ! j1 Y; N1 w+ C3 L0 q( M/ e2 |2 }
    def make_generator_model():
    ( m! o% k1 G. N3 U    model = tf.keras.Sequential()
    $ g/ u8 l) B3 T    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    6 x; ?% a* V3 ?4 a" @" Q    model.add(layers.BatchNormalization())8 |9 r( n9 o9 d3 W
        model.add(layers.LeakyReLU())! P/ A! |* b' r, c, c6 a+ ?) K7 h

      \. k  x* o- x) c    model.add(layers.Reshape((7, 7, 256)))  K. n- Y) D8 e$ y* Y* r
        assert model.output_shape == (None, 7, 7, 256) # 注意:batch size 没有限制
    7 }0 R9 |# G0 W7 j& T 4 z1 _: d) b- [. p  S
        model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))$ M% l+ k4 H, E, Q( Z- t
        assert model.output_shape == (None, 7, 7, 128)
    6 r1 l! P" y+ L( \' O    model.add(layers.BatchNormalization())
    4 ~3 x+ b: |" t, S5 Y& e    model.add(layers.LeakyReLU())
      |, D* j$ m9 F3 y! W* m
    0 N) t& R* U% |; M    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))2 I' b- C( |1 U/ j# g( c' d
        assert model.output_shape == (None, 14, 14, 64)$ B' x) K: n3 P5 |; C- G
        model.add(layers.BatchNormalization())
    3 @5 V- v% V$ B8 T! b/ Y1 O    model.add(layers.LeakyReLU())
    * D  }+ Y1 z7 Z' n 7 H7 g2 i. h3 }  F+ Q# U
        model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))7 a- k( ?' ~; \
        assert model.output_shape == (None, 28, 28, 1)
    & c9 i) x2 E9 ]" |: w$ {- q $ z1 `. _: c9 i. ^% ~( h/ N8 ~0 E
        return model0 |$ i; R. X7 k: w
    用tf.keras.utils.plot_model( ),看一下模型结构- F: C' ]3 t- a
    ) Q+ A1 R/ p) W( w
    6 w! L2 B/ V; r' K/ |' b. k
    6 z% U, y- U+ n5 z2 }
    0 r8 {/ s# g6 m+ A0 P

      k! b1 o! i% Y* H2 O: l8 |4 T) F用summary(),看一下模型结构和参数% I- f8 d* W2 i8 G! m7 S
    ( {  h, o& v5 v6 I! F
    3 r# F' N& _; U, w+ U
    6 d0 j5 e" f& k

    : l9 x0 B8 q4 }, z# V4 D) Z; t. m" E% i$ v
    0 C" J3 A3 K8 L# M5 A8 _
    使用尚未训练的生成器,创建一张图片,这时的图片是随机噪声中产生。
    ( f/ D2 O) Y" w2 `/ S' a& N( {' d1 u- k4 y8 P) C$ s

    / R* x6 e+ ]' y* dgenerator = make_generator_model()# O( E) K- h; A  U
    & P9 G5 l2 `7 K/ F5 v
    noise = tf.random.normal([1, 100])
    : c3 }* D7 z0 F, q4 Bgenerated_image = generator(noise, training=False)7 g% _$ L* W! q1 I' J

    7 E1 y& q8 K, M- @) n+ i6 ~plt.imshow(generated_image[0, :, :, 0], cmap='gray')
    . V0 e* b- y# J, ]( g! _: x
    . P; P# k' }  I# W
    8 f' m! }1 d, k5 `$ A( I

    ! Q+ y5 u2 Q1 c! h7 g

    8 f. X2 J+ ~3 `! S8 e3.1 判别器
    6 \4 n, Z2 w9 x! R, H# f( }# |判别器是基于 CNN卷积神经网络 的图片分类器。& f8 R1 I7 h, s4 Z( w0 h% X
    # T6 A$ X; U9 W

    " B% p" g" P2 b( `) c# Rdef make_discriminator_model():5 ^* F( Q, i! s1 I
        model = tf.keras.Sequential()7 X& `: [/ A/ Y3 M
        model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',8 m- T/ ?. N/ c- h
                                         input_shape=[28, 28, 1]))2 t9 E, F! E% G& X) k
        model.add(layers.LeakyReLU())9 ^( A3 C  }1 J& I9 G1 N' x: ?
        model.add(layers.Dropout(0.3))
    & {+ l# L: }# \. s9 F; Z ; ?* j' I1 \. B& U. h
        model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))! R1 Q  k9 c/ x2 ]( y
        model.add(layers.LeakyReLU())
    - Q4 Y$ m9 S5 h6 U% a6 ?: l    model.add(layers.Dropout(0.3))7 d' h1 @" O& r1 I$ U, u0 V
    ( q) X' a7 Q6 F. x+ e% O
        model.add(layers.Flatten())
    % K7 l8 s& E$ v& p6 Z    model.add(layers.Dense(1))
    ) H+ Y! \- D1 w7 P7 P- i
    7 M/ Q7 m( F  [% y    return model
    6 J' X, k$ X; R' b8 |; N9 v+ y9 }( W用tf.keras.utils.plot_model( ),看一下模型结构1 V" k* d3 I7 }0 s  J" r
    0 Q5 N5 m9 b- F
    ! Y( y9 o6 Z, {) m4 U& v

    1 o6 N! o& [. E" ^) c3 u
    6 I+ V8 D. C0 P( a3 r: @  ]

    0 i+ [% O! l  S. c$ v& a2 A( ^

    ; `8 g, _# h9 I6 I7 }9 P7 a; T用summary(),看一下模型结构和参数
    ( N$ R" T& W* m. S* |6 Y8 O- u7 W3 D& r, |
    8 z' S" B0 w, {8 R$ d
    ; Z2 M3 u. a$ P+ s  L, b3 H
    : p) K& [. `2 E9 w# c$ _! M2 P
    " M% h  o6 c. Y; |" a$ E3 Q

    5 [8 s6 R2 z0 x, _. T( ]7 y9 n四、定义损失函数和优化器1 t+ p; ~1 T8 \# J0 o9 g% x! Y: q
    由于有两个模型,一个是生成器,另一个是判别器;所以要分别为两个模型定义损失函数和优化器。+ k6 x& H4 R! C) L# s
    ! h: E$ _3 Y1 Q: D0 ?: e

    % X2 s0 E. o4 n$ S) [2 r/ |! S* s首先定义一个辅助函数,用于计算交叉熵损失的,这个两个模型通用。
    5 \( _8 g9 _! L; J0 X' B
    2 l, J7 |  k1 f2 y5 c

    1 J& P* R/ z1 }% [# 该方法返回计算交叉熵损失的辅助函数" A  v4 W- Z" ^5 E- J+ T+ j' E
    cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
    ( N' ]; l6 C3 t4.1 生成器的损失和优化器1 W! G3 i7 ]+ }% h, u& x
    1)生成器损失: j$ G$ |' J6 j) n8 Q+ i
    ( [0 r/ t* |0 y9 K4 X. \+ A
    / b" o( N4 }4 O9 g, |0 s
    生成器损失,是量化其欺骗判别器的能力;如果生成器表现良好,判别器将会把伪造图片判断为真实图片(或1)。
    . k2 ?. m2 I& C. X  l- l% S! d! `$ E9 c

    6 M4 L! T$ ?# A! T4 D/ q7 Z这里我们将把判别器在生成图片上的判断结果,与一个值全为1的数组进行对比。$ c/ |8 S8 A) N% {% q. _* v
    $ H+ I; w" e( W, I$ y9 _4 s" E
    7 j' q# Q- h+ ?0 Q4 E9 ?* n% S
    def generator_loss(fake_output):; |1 E& S: o5 M* I. L
        return cross_entropy(tf.ones_like(fake_output), fake_output). ?: F# ^* f! O( q- X0 k$ Z: R
    2)生成器优化器
    ) w( H1 y3 {3 ]1 ?5 q( {; |5 F6 V$ j) V% U3 t5 ?
    ; V% n. z% {8 z& B; `, F
    generator_optimizer = tf.keras.optimizers.Adam(1e-4)# {, d" t, z0 s1 d- b. p
    4.2 判别器的损失和优化器; @1 ^1 i+ u8 X8 w
    1)判别器损失
    + a8 O4 l- o8 j
    : G- V& @5 a) d: f) Q: f( Z& r
    ( b, I0 g0 q" p$ E
    判别器损失,是量化判断真伪图片的能力。它将判别器对真实图片的预测值,与全值为1的数组进行对比;将判别器对伪造(生成的)图片的预测值,与全值为0的数组进行对比。
    0 z" S$ h! _* k1 b) G; A) m+ H6 l! C9 q
    9 h( ?" p0 K& V  G" m1 l+ P
    def discriminator_loss(real_output, fake_output):
    8 u' I2 r2 z( R& {7 C    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    ) ~9 c! u) v! l0 t6 r0 C$ r    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    6 e# r9 @( k5 p6 M6 `/ g5 @! s$ H    total_loss = real_loss + fake_loss
    8 r# b' m" B: d) C2 t/ ~8 h! n    return total_loss8 M. P; W+ E! i& u
    2)判别器优化器
    / ?$ Q( G5 {! L1 f' u. C) Z5 E$ l0 g5 M+ o
    # k0 `0 J: t( `: p& T- [
    discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)( X# R2 _2 q4 \( b* u% D, \* I: d5 a
    五、训练模型1 B6 u1 [9 P% v
    5.1 保存检查点" S) V4 J6 }- ?0 m
    保存检查点,能帮助保存和恢复模型,在长时间训练任务被中断的情况下比较有帮助。  g5 y% E  e) ^' g) p0 W& v

    $ C: Y3 _: I1 |" K6 ~( Y* w" V

    & ~  d+ @" T% S2 Y; ]4 P8 ^  zcheckpoint_dir = './training_checkpoints'
    5 r2 `8 ?- h9 ~( \) I8 M" |" k! lcheckpoint_prefix = os.path.join(checkpoint_dir, "ckpt")8 {! }1 h7 _% a9 I; y
    checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,; x- J: ]/ w6 l6 j2 D3 ^4 R
                                     discriminator_optimizer=discriminator_optimizer,
    % A1 _! _9 V8 i6 y- _3 l3 C0 i( p                                 generator=generator,  O$ P) y4 q' P- c4 \
                                     discriminator=discriminator)1 X8 \  K( @! c# m
    5.2 定义训练过程6 }3 N. `! s/ z* H
    EPOCHS = 50
    ! t5 ?# K8 u0 w0 [7 v) B1 h' W8 Lnoise_dim = 100
    ' A+ [# Z3 D0 B: ynum_examples_to_generate = 16
    ) a; E: C6 }9 u6 f! s# j
    8 l; x  Z" K9 D  C' m+ M
    ) _  `3 o# Z% g1 b  I  T$ J) t# 我们将重复使用该种子(因此在动画 GIF 中更容易可视化进度)- }5 F' j5 M5 F/ `/ u( z
    seed = tf.random.normal([num_examples_to_generate, noise_dim])$ {1 ~$ \' u6 @5 X
    训练过程中,在生成器接收到一个“随机噪声中产生的图片”作为输入开始。
    4 B3 i( ~( x5 f. Z$ U9 J3 d9 c7 u; o1 T3 n0 g" a. `
    4 d- I) t' l9 Z+ J1 _0 M
    判别器随后被用于区分真实图片(训练集的)和伪造图片(生成器生成的)。* M0 h$ A4 G' @) u, @. d; n$ c

    " ^' i  h5 e) @- [
    $ e& d3 W1 d2 x) b
    两个模型都计算损失函数,并且分别计算梯度用于更新生成器与判别器。
    , K+ Z& u* r& c  W
    , _+ H; P4 m8 I2 c. T

    6 Y+ g, U/ o, f6 `5 W' I# 注意 `tf.function` 的使用
      a1 g, |: x. p; v2 y4 I) U# 该注解使函数被“编译”
    1 b6 Z& K3 T) o) T3 A  W9 _@tf.function$ I% d) B1 }' b+ P8 |) J6 Z& f
    def train_step(images):: m# E: q1 a6 `9 Y+ b
        noise = tf.random.normal([BATCH_SIZE, noise_dim])% ]- ]* `/ L- G# T4 h0 F/ g' ~5 X& |: \
    / n4 |) m! O. L0 c8 ^# d. D+ s' O
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:/ K6 m  _3 l1 q, N% \# j
          generated_images = generator(noise, training=True)( [4 B6 g5 }4 Z" R2 J6 b

    + I: h8 ^* P3 `! M# e- z      real_output = discriminator(images, training=True)# M- n) M, \- M0 F
          fake_output = discriminator(generated_images, training=True): t# k  F4 ^( f$ u

    8 h7 ~$ I" v/ N" S% Q7 }  \/ r      gen_loss = generator_loss(fake_output)
    8 L, A$ O, W/ l- [( v8 O, `8 r& {      disc_loss = discriminator_loss(real_output, fake_output)! w! l4 m  X. Q% S, z
    6 m1 w0 h5 N4 G$ F7 j( ], l. V
        gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    4 A$ P; g+ Z2 |- j    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)& J0 L9 {: X. Q  Q& G
    0 y6 z' B$ e+ r
        generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    8 r* w: U5 ^  E    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))* F6 j! a, y; [

    0 ]: J' x' Q$ zdef train(dataset, epochs):! r# d( w8 S5 e: t2 J9 A  M
      for epoch in range(epochs):! q" z& J6 p& q6 p" S
        start = time.time()8 O" a) b6 j2 C% @3 T6 @

    8 X) [& Q% @5 w9 b- U. O    for image_batch in dataset:
    3 f8 ]# Q2 v. Y; r  R      train_step(image_batch)% C7 j( P. a7 _  q- i; ^

    8 F: E$ X! X% Z# o1 ?% }$ |    # 继续进行时为 GIF 生成图像
    " t: [5 c( d8 E6 q; ~  L' \    display.clear_output(wait=True)
    - C% P( H: U- E2 O7 ?: S    generate_and_save_images(generator,
    5 f" H  @% l! Y& t! F                             epoch + 1,( [# B1 d9 N9 Z. F, k2 l9 W
                                 seed)8 x8 D) D. x+ }: @4 Q
    2 i3 r! {" a+ l
        # 每 15 个 epoch 保存一次模型
    $ G8 Y: I6 Y# S2 P1 [+ a    if (epoch + 1) % 15 == 0:
    + Q3 }0 |% O5 a8 p) D      checkpoint.save(file_prefix = checkpoint_prefix)
    ! F  g$ u) |1 G8 c" h' v
    5 S; ]% c  R, z  X) F    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))3 U: R6 q) ~  S
      V8 h% e& R/ M6 e" T  ?2 [) x+ I
      # 最后一个 epoch 结束后生成图片
    - ^2 O7 K! W4 u, |  display.clear_output(wait=True)& g4 `! u7 o0 u) P
      generate_and_save_images(generator,
    " Z: \) s. W) O7 H2 {                           epochs,
    0 }: L3 x2 b1 U$ F. d- s  C                           seed)
    4 A! r# _( E6 W7 c, g) R
    7 c' Y2 P) @3 g$ _# 生成与保存图片
    . H, O, z- }. s. Tdef generate_and_save_images(model, epoch, test_input):' J4 y/ R7 [. f# p
      # 注意 training` 设定为 False
      c; ]7 \7 b  C8 ]$ y; V  # 因此,所有层都在推理模式下运行(batchnorm)。
    ' P; n5 Q( c% d" T0 _2 [  predictions = model(test_input, training=False)
    7 W& N7 e9 w  v/ r$ L7 Y8 O . F) i+ W2 I  G, V
      fig = plt.figure(figsize=(4,4))
    ' [( V1 z1 U. F2 A% f! J( ]
    0 d- U2 h/ n: `3 s9 s' {2 _+ i  for i in range(predictions.shape[0]):
    ( O' W: C4 a$ E4 \. e& a      plt.subplot(4, 4, i+1)) W6 S3 S. Z4 c1 m3 [, N+ i
          plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')2 h7 I8 J$ ]- S9 ~5 p, M( x* I! c8 J
          plt.axis('off')- f( R0 v/ [' G0 f! {1 L
    : C/ l/ T$ O5 I0 K8 m5 `* `
      plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))9 R- U" A2 Z* [. o
      plt.show()0 W1 C# U- O/ }- k
    5.3 训练模型( K- k+ n& H9 f( @$ d
    调用上面定义的train()函数,来同时训练生成器和判别器。8 n! ?- y9 j- Y' I# F) X% `

    . P. w3 p* u% `% d7 b7 e3 D
    1 H: n) b( P, D# b6 D0 p$ \
    注意,训练GAN可能比较难的;生成器和判别器不能互相压制对方,需要两种达到平衡,它们用相似的学习率训练。" Y- }, N/ X1 _& o, c
    # J6 f8 w6 W* p3 x( O, L
    % A6 d, T; w6 V; g
    %%time* e+ y, U/ @6 V
    train(train_dataset, EPOCHS)
    ) b1 u& I. Y* q1 ?- o2 k( s. o在刚开始训练时,生成的图片看起来很像随机噪声,随着训练过程的进行,生成的数字越来越真实。训练大约50轮后,生成器生成的图片看起来很像MNIST数字了。) P4 W, R* C) V  q: _4 q/ c9 `
    ' ^& W4 h% o, Y9 l4 p8 N6 H8 ?2 t

    ( z1 ~" W9 T- M( }& s' r+ _训练了15轮的效果:
    8 `/ ]7 J. S# L) \3 L/ m/ ^0 s( N$ J/ Q, s9 [! Z" @
    , Q; O% e) F  A8 E  Y
    % ~* R$ `: x2 [, e& u

    1 O, m5 W, y! f! g1 p" J: r
    ! s5 P' x$ B' C1 D* L

    6 y2 o) B& f: t* ]; |. X* {训练了30轮的效果:
    0 ^6 C  v1 |) n! q. f' p9 B* ?
    7 U& v5 Q6 w' H

    # u1 }- q$ l4 Y" n
    $ ]! [; d1 b+ p' Z  o! p+ K
      `  h! [. z& V  c

    - D; @/ e4 S+ n4 D" G
    4 w. u" x! e" k, ~$ m! p
    训练过程:$ Y# O, K6 {/ y6 Y% ^
    8 m' k8 }: K; M4 ]) y* k3 A) O

    4 h4 M! \; C* e7 I9 v( V' x( ?1 R# O
    : L! l# u- p0 r& k

    ' _9 Y. e" m# x0 c! Z2 x
    8 U  D* U% p  J- t) a; i
    恢复最新的检查点& X* a& m" b8 n) e! l4 L
    , l3 E, X4 f  S$ M1 m
    ! n$ L) h- A  r* i. g
    checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))8 {0 H% R  p  Q  i6 {' |* x; `% c, s
    六、评估模型
    * I- D# Z* |3 l5 ]5 ^这里通过直接查看生成的图片,来看模型的效果。使用训练过程中生成的图片,通过imageio生成动态gif。- @( }8 V, b, i$ s- @4 o

    " i9 b. H7 ^/ w; y9 T) n
    - g1 M0 F% K, L
    # 使用 epoch 数生成单张图片
    ) M' n' q8 Q& b+ `5 K- k7 edef display_image(epoch_no):
    3 k9 p& b" y; M( s- [! E  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))* Z6 L$ E  \; K7 Z
    2 I; D% F! r) C# W7 ~$ G
    display_image(EPOCHS)
    # P3 `5 d) I8 @# n. a3 zanim_file = 'dcgan.gif'9 q) f' @# q9 I8 K" m3 ]" ^6 c
    # f+ s4 f4 _1 \1 @$ m
    with imageio.get_writer(anim_file, mode='I') as writer:
    * K3 Y# v# N/ t% l: k% o& x  filenames = glob.glob('image*.png')
    " L& I4 @1 m) v$ v9 @. e  filenames = sorted(filenames)
    ! @. ~" F0 o8 |3 N1 m( o2 P  last = -13 w8 V8 R3 b* d4 B; r
      for i,filename in enumerate(filenames):$ L7 G: z. ]  V! p7 j4 S" G
        frame = 2*(i**0.5)
    + ]+ Z" `- K8 h0 i; p& _  i0 G    if round(frame) > round(last):2 {3 U0 C, P) M- f. S" s
          last = frame
    8 g. G% X5 z; ?3 k! b3 k8 [. u    else:
    % H2 D  W5 x2 }0 e" r6 J7 s/ A7 X      continue: I9 i0 O+ Z9 p: {3 }0 V( \0 j
        image = imageio.imread(filename)
    $ P6 b4 P- B% l0 i    writer.append_data(image)3 p+ C" r) \& s" f/ r& x
      image = imageio.imread(filename)4 |7 I- H$ K" Z; @) q8 N$ ?2 s9 |
      writer.append_data(image)
    6 k* v: V$ {" |  v' W3 I' ^# k
    9 a% Y5 S7 {2 Q, I# X, p/ Vimport IPython$ F6 J) y' L% v3 b5 g5 _
    if IPython.version_info > (6,2,0,''):* k/ u+ ]( m2 r; B2 Z( g  t0 j( {, w! h
      display.Image(filename=anim_file): `7 _- i+ u% m

    ) z' F7 [: {* A$ X$ Z
    0 n% T9 k; _3 W. d5 l* Z
    3 e$ s) W6 d  E$ x

    # {7 @2 `5 [( N/ b完整代码:. y! f: M- B/ j( O
    7 K* g# Q4 \5 B- T: M

    ) |' s: K! X3 ^$ aimport tensorflow as tf2 G& i0 D2 d' e* |
    import glob
    " M5 O4 U9 m& u& T- W2 Fimport imageio
    ; T0 L0 Y, _) W% j0 g6 p$ limport matplotlib.pyplot as plt# v- P5 `% U. [  @' K
    import numpy as np
    , |* g6 f- d1 q1 Cimport os
    6 M) t" D$ b9 v) Y; iimport PIL
    $ u7 W. R( n8 o2 O0 }6 Q: hfrom tensorflow.keras import layers1 Z( S" F  q+ t' [" `2 L' d
    import time; v7 V4 @7 d3 Z7 t. w
    & }% d" N& z/ H% c" ~( U
    from IPython import display! Q- i3 h0 E  ?) |' ?1 D
    6 k9 {) R! T3 Z! S; x3 B
    (train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
    5 J& k, F2 k% h  n ' V2 e+ x- C! l- S8 c7 S
    train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')# D6 @$ t( i. a( [0 P
    train_images = (train_images - 127.5) / 127.5 # 将图片标准化到 [-1, 1] 区间内
    ! }# \; o; K4 O  I
    : }3 [5 l) T# Z7 N9 r  |* F) z$ sBUFFER_SIZE = 600001 r% B# X4 L, m0 R
    BATCH_SIZE = 256
    ; ?7 H7 c. K% J- |) z9 r2 h0 o" [ / l" B, `$ @0 s2 G
    # 批量化和打乱数据
    / c' E6 I+ t. \/ }5 D7 ]$ ]train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
    # {# h: Y, h* p$ X9 A
    , K! X0 P. v# i( d+ z$ N# 创建模型--生成器
    ; @1 [  @3 B9 v! x- `  Jdef make_generator_model():
    / ]9 t5 H2 Y3 f. c$ Z) D3 D; a$ q( j    model = tf.keras.Sequential()6 {9 ?) L' F+ L0 |3 h3 u, |' ?
        model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    ! \- `$ V4 B; G& a, r7 b# I    model.add(layers.BatchNormalization())* u0 M# [5 f. K3 h
        model.add(layers.LeakyReLU())
    0 M- b. N1 K! [5 V6 K ( s) H# m& U6 f5 H* I
        model.add(layers.Reshape((7, 7, 256))); b0 ~# I7 V( }, h4 T- a' z
        assert model.output_shape == (None, 7, 7, 256) # 注意:batch size 没有限制5 L9 }2 b$ |" {" l$ x

    ! F/ d+ d" a0 r9 R9 J. l4 q3 f9 a    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False)): m  z* x$ C5 \% c" M
        assert model.output_shape == (None, 7, 7, 128)
    0 v4 T) p) k/ g7 O  x1 I2 c& \    model.add(layers.BatchNormalization())# Q. ^! N$ x9 l0 i! v' H
        model.add(layers.LeakyReLU())2 n& K, _" L/ C. D/ z. _2 H/ }

    4 L4 ]; l7 X% K) Z# O# m    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))2 H& F1 i* \6 t  P
        assert model.output_shape == (None, 14, 14, 64)
    # f( z3 ]' u. J6 G6 S5 z0 J    model.add(layers.BatchNormalization())/ N7 Z) M. a: V5 q1 b% ~
        model.add(layers.LeakyReLU())0 w8 ^6 D* ^" ]4 ^+ q$ @

    + T0 `. @8 a/ X    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))1 {. ^: i. o1 j9 B: ]1 K" I! V
        assert model.output_shape == (None, 28, 28, 1)
    % ^0 w1 B) h, f+ s7 l- c$ `
    - w, _% k5 P+ b; K1 t- q, b" K  l1 Y    return model. Z3 k2 j  g2 o* a" P0 u
    0 j$ _( P5 L  n
    # 使用尚未训练的生成器,创建一张图片,这时的图片是随机噪声中产生。
    / A5 T$ ]* Q/ j( n; ?+ P: P3 Jgenerator = make_generator_model()
    8 `+ L9 W1 j" u7 v
      M. o0 j5 Z! w: s9 K& knoise = tf.random.normal([1, 100])
    . d: e8 }+ b/ K# N; l( t: Q/ z9 ^' b" sgenerated_image = generator(noise, training=False)6 N. c" |& \4 \6 g; a4 r  }

    0 P) I4 E, I- q. q9 ]1 Mplt.imshow(generated_image[0, :, :, 0], cmap='gray')+ H& {; v* e+ L2 T, B! R' J  O% m
    tf.keras.utils.plot_model(generator)
    0 ?  ]9 b/ W/ j) G  w6 ` 5 |" j4 B: h9 P5 }  T
    # 判别器9 Q! A4 j9 `# i
    def make_discriminator_model():- \# }, M4 y) p4 P& w: P
        model = tf.keras.Sequential()2 @9 T* Y  W: M& Z0 Z( H2 [
        model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
    / Q" _% ^$ g2 A' A" [7 p                                     input_shape=[28, 28, 1]))
    5 N: s3 _, k3 ~6 q& k    model.add(layers.LeakyReLU())
    ' y6 c" {. ?1 \* Q5 r% G- ?' h    model.add(layers.Dropout(0.3))
    - [6 @/ c$ ~- T7 }
    8 \" Q: f. }" ?; S( k# @1 H    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))" I: c# ]0 [3 d8 Y3 R* `! K
        model.add(layers.LeakyReLU())2 w2 p3 l9 j) E5 U+ D
        model.add(layers.Dropout(0.3))
    * E9 K3 r. i2 e, j* n5 b4 {
    / l0 b. D( m: I6 t% W# [- j    model.add(layers.Flatten())
    ( n2 Z; i# C$ V5 F1 Z    model.add(layers.Dense(1))
    ( J; K( x7 |8 c: S3 z) j  Q6 Q
    & l  k' A- d, v: z8 N& n    return model  `( S3 c" j# O8 p
    4 m; E5 }! }  l8 |! [  r- X
    # 使用(尚未训练的)判别器来对图片的真伪进行判断。模型将被训练为为真实图片输出正值,为伪造图片输出负值。! c9 j# ?% h% V2 D! {, \
    discriminator = make_discriminator_model()! \& u, v; u# O% s% X
    decision = discriminator(generated_image)! d: ?: u0 I, ^7 w
    print (decision)
    ; q! f& ?9 g! Y" _. o3 m
    . R+ ?6 c  s6 a; P5 {# 首先定义一个辅助函数,用于计算交叉熵损失的,这个两个模型通用。
    + Y$ h! S9 [  F* F+ d% ?8 Jcross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
      S5 S2 k% F" Y0 v3 A, p# p
    8 \- O9 x; x; Q% r$ w# U* L# 生成器的损失和优化器3 y7 a$ ~' \( H: N, n$ A
    def generator_loss(fake_output):/ k+ c5 w+ N7 q! ~2 F
        return cross_entropy(tf.ones_like(fake_output), fake_output)
    ; Q! ?4 t- Y8 X, l* y- e7 w- hgenerator_optimizer = tf.keras.optimizers.Adam(1e-4)
    5 F3 F( ]3 t' y* j- a6 y 2 I( a- d7 Q& \6 l- n9 u3 g' k9 e' m
    # 判别器的损失和优化器
    " |; C) @1 N# pdef discriminator_loss(real_output, fake_output):& r( f5 V8 o8 o8 Y# R7 I5 x
        real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    $ F5 B* P2 c5 Z    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    & f) A7 [# G( }# L. T    total_loss = real_loss + fake_loss) O7 ?* G0 i# K4 a( W8 K; F
        return total_loss
    ) L+ Z5 t  T4 z1 H8 K: wdiscriminator_optimizer = tf.keras.optimizers.Adam(1e-4)& E" p3 i* j% u6 K. E1 c) w

    % S) C0 P9 c+ J" z* S; f# 保存检查点
    ; Z* |% k7 J0 L6 @( [) ucheckpoint_dir = './training_checkpoints'
    . M' ?, F6 h9 D0 A# g' p( Ucheckpoint_prefix = os.path.join(checkpoint_dir, "ckpt")/ @; g) e) f* Y# r# \$ L9 q" p( N
    checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
    ' a$ T' |9 M: k                                 discriminator_optimizer=discriminator_optimizer,2 H# f# Q3 M8 B$ }
                                     generator=generator,
    4 K+ p9 z$ w( F/ V% Q                                 discriminator=discriminator)* Y/ i+ s7 ]$ p, n0 A# F

    + y6 L4 I$ w9 C) c! T! x) b# 定义训练过程+ k/ J  [3 z/ O1 G' G
    EPOCHS = 50
    2 k% f4 ^1 n" ^# Wnoise_dim = 100+ V- O* i: a# B, q! |. e
    num_examples_to_generate = 16
    ! ^/ h" c1 P# G# j/ P4 c7 p9 d0 j   c  \( F* h* Z. r
    # 我们将重复使用该种子(因此在动画 GIF 中更容易可视化进度)
    ; r6 T( w- a. Wseed = tf.random.normal([num_examples_to_generate, noise_dim])
    6 A5 ]  o/ i4 @4 U$ `& |
    ) c7 O' y" X# Z( I/ L# 注意 `tf.function` 的使用
    0 t1 G& g' j1 r9 G+ r% C6 Z# 该注解使函数被“编译”1 I& V$ q0 s4 J3 r
    @tf.function
    $ v, T+ z/ J2 {, |/ p8 P/ }def train_step(images):
    8 I0 ^  Q9 S2 V. O. M    noise = tf.random.normal([BATCH_SIZE, noise_dim])3 o) L& w! C. B( A4 r
    . }$ z6 G+ R5 h8 d+ N. w/ c
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:" u' N+ S+ g2 U
          generated_images = generator(noise, training=True)
    7 B, n4 w1 B# ?* T+ z- X7 G + V( H8 R: m' Q0 {- {
          real_output = discriminator(images, training=True)
    8 w% r: O% F' v* F: Y( Y      fake_output = discriminator(generated_images, training=True)
    5 Z) B% F* @9 x% N) f& j: a2 O1 ? : M3 v' U  |3 r
          gen_loss = generator_loss(fake_output)3 D- n7 D; c7 N  V9 k; q
          disc_loss = discriminator_loss(real_output, fake_output)! [5 C' ?3 E  ~8 C8 N  W  a, ^$ \+ X

    6 C% o3 z  F; C9 Q3 J  r# E    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables); Z+ p- S# v& k& O# S
        gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
    $ l3 p/ G! P' j1 b2 S
    5 z$ b% G* N7 f1 T$ }) N2 ?    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))+ K& R" e0 L3 o% l
        discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
    0 `1 o1 H6 }2 p: G+ H2 g
    * h1 f0 s* s. rdef train(dataset, epochs):
    % w6 f/ G- f4 h; e) H  for epoch in range(epochs):0 Z9 y( Y, _, r
        start = time.time()2 g9 e' W7 R* d7 L' Q( d3 t* M

    7 A; C. W  ]3 }  a; H9 ]& f    for image_batch in dataset:: A/ i) p* M* \) i( ]6 q
          train_step(image_batch)3 n* F, p# `, @* Y

    1 z- _9 j9 Y+ l/ F; U; s    # 继续进行时为 GIF 生成图像
    . l  o8 c8 {' R; ?8 L  y    display.clear_output(wait=True)4 i3 ~+ s6 l) O0 I
        generate_and_save_images(generator,
    ! m8 S- N% I7 |                             epoch + 1,5 G2 x; s8 ~3 w& @% J3 o. R  i
                                 seed)% z; e# X8 _3 U/ V6 x1 Q

    / \. [5 c2 U: x5 \# G6 m) i    # 每 15 个 epoch 保存一次模型9 x# |4 ?- }* K* v; z# X
        if (epoch + 1) % 15 == 0:  i* s* k& t" D6 P. l
          checkpoint.save(file_prefix = checkpoint_prefix)" _0 a+ V; K3 ]2 @+ J2 A3 f& W

    ; p7 k5 V" i+ }# }' [9 H/ ~, \    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))
    0 {0 N! K3 f* n" X0 a: M, I0 O! y
    3 ?7 P: j/ H6 W) M# w5 s  # 最后一个 epoch 结束后生成图片$ g, P  U. ~# I# J" s' K& v
      display.clear_output(wait=True)5 }7 s7 S$ e2 `0 b* v
      generate_and_save_images(generator,! _7 j' _' a- m: r, n
                               epochs,
    ( _8 }  |* F( Z' M, @                           seed)
    5 m" @4 C5 e  e
    : f" O" L  e/ D! A# 生成与保存图片& P/ w' d# _# a+ y( V4 p
    def generate_and_save_images(model, epoch, test_input):
    4 B* u% J- j. b2 ]$ {  ^  # 注意 training` 设定为 False
    ' q  b5 y4 y6 a) ]% c+ U7 h5 L( [  # 因此,所有层都在推理模式下运行(batchnorm)。
    - I& y- J; O$ O; i  predictions = model(test_input, training=False)
    ! w) Y* x" X" R( r7 X7 R" a ! p( B! i$ Q, W! Z, u- `' q
      fig = plt.figure(figsize=(4,4))/ C' i, D- ^, y- Z- g
    : y. _3 V+ r" U- G7 F- @: x* @+ z
      for i in range(predictions.shape[0]):  F; P/ @0 e$ S: p$ y, ?
          plt.subplot(4, 4, i+1); _9 m, D. l+ p' a! O/ z
          plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
    - M2 @: \! q, h/ P4 O9 `  f      plt.axis('off')) k7 k4 J7 L# [8 ~" _
      R! G! a1 z1 N/ J0 d: ~
      plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))- W" D) f& g+ X7 e* M0 ^9 q
      plt.show()
    . l& }# u' @1 t/ d
    - J2 }) I! ^4 l! X# }5 W$ G# 训练模型
    $ |, f9 T, h# `train(train_dataset, EPOCHS)
    7 f6 z) r7 Z7 b- Y4 {
    $ J! }4 C0 H1 T1 w$ u# 恢复最新的检查点
    1 w* n9 v7 H. P' e) Ncheckpoint.restore(tf.train.latest_checkpoint(checkpoint_dir)). y/ R9 j) c' `( Q: ?. Z
    ' g) S4 I) ?( c7 s  V7 Y! Q$ W9 f
    # 评估模型
    ' J) T, h9 G3 o" Y0 @$ o' r& K# 使用 epoch 数生成单张图片
    + I, W/ K' v. y2 k- I3 adef display_image(epoch_no):0 X! L) x# o/ O
      return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))( j( e4 e. g) j7 C# |) N2 F

    8 V- M0 ^2 k* L) j6 hdisplay_image(EPOCHS)
    % X- \0 b2 s; ~* N  u! R; S/ z+ [, V" _
    7 _1 [' W5 k' h! Wanim_file = 'dcgan.gif'
      s' g# w, f5 v, N3 I* r
    , e% G2 J6 S- B) }$ S/ P! J$ u$ ]with imageio.get_writer(anim_file, mode='I') as writer:! b4 f; g& r# I" i" [  W. l
      filenames = glob.glob('image*.png')& ?2 E9 V# D* i; G
      filenames = sorted(filenames)
    : Y* t4 k- x: E0 M  last = -18 O9 |; ~. {) f' K2 @
      for i,filename in enumerate(filenames):! _, L4 G+ U+ ?9 G9 D1 _" G
        frame = 2*(i**0.5). ?1 \" M6 q! b  v. z+ ?6 ~- k
        if round(frame) > round(last):
    + g1 ~; f/ j7 X; Z# j: S      last = frame# G4 n5 `3 O$ c" o4 Z( u
        else:
    . [) @0 C; I: W. ^      continue* _! `  f2 P3 ?  q6 y
        image = imageio.imread(filename)2 [5 n/ R& r3 F
        writer.append_data(image)
    : R6 A- ~7 l/ I8 b( o2 b' ~  image = imageio.imread(filename)' H% F% q& [- \, w2 ], y
      writer.append_data(image)6 ^4 z. W6 E" J/ M, U" X
    & A; M" ~- f  i6 p  a+ e6 ]  V% G
    import IPython
    2 |' l6 W7 E  D0 c% B  A  Jif IPython.version_info > (6,2,0,''):2 j. u0 Q6 D, s: E7 V8 ?* a
      display.Image(filename=anim_file)
    / @5 k% P& }; p, u+ ^" q& a参考:https://www.tensorflow.org/tutorials/generative/dcgan/ L) j# i3 f' Z" J
    ————————————————* F: {& c8 [% X( [: i2 `1 b5 J0 D
    版权声明:本文为CSDN博主「一颗小树x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。3 A9 v+ d' Y0 }2 `, h1 U+ t
    原文链接:https://blog.csdn.net/qq_41204464/article/details/118279111% v- @( c) x( |8 O+ A

    4 a- z) m# S4 K" T) P) K, F" b$ K& i% M/ F9 B- h; G
    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-6-15 09:00 , Processed in 0.456804 second(s), 51 queries .

    回顶部