QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5339|回复: 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

    0 y; E# j# X4 D: ]9 S0 V0 @深度卷积生成对抗网络DCGAN——生成手写数字图片
    , U! i/ R8 T, z% Q前言7 k  B0 _- o# b5 p# @6 \; J/ D
    本文使用深度卷积生成对抗网络(DCGAN)生成手写数字图片,代码使用Keras API与tf.GradientTape 编写的,其中tf.GradientTrape是训练模型时用到的。: h' n" C* ]) c: G5 x
    6 w# k' M) Y1 ^# Q0 b

    8 p& ^0 ?* k0 v9 Q$ {* W: o 本文用到imageio 库来生成gif图片,如果没有安装的,需要安装下:
    . Y# h- _( ]. ?- ?/ ^) E/ U( l3 W6 R( z2 o! ^( g. b5 f
    6 q. q( T6 H. Q  y, u
    # 用于生成 GIF 图片3 y- Z# n& g) z3 I+ L  z2 [
    pip install -q imageio
    / C5 H/ D, p* p! P* z: k目录3 X5 K/ G5 J" q8 o  K

    , a1 H3 U" G0 d; A+ t; C/ w  j
    # k7 Q9 `7 S* S8 d# _+ `
    前言/ x- g3 ~8 k3 N
    7 g, i+ t* ^7 D: l. E. p
    & H! y0 A# d* d1 b0 s
    一、什么是生成对抗网络?
    , }# ?5 k3 Y% [
    8 ~6 V% k# Q6 s; p) ~; Z! t. e9 K
    4 |$ G/ M, e/ L$ v
    二、加载数据集7 J( q$ `- T$ n4 P& O& I
    0 S$ ]# ~4 m8 V5 R* `, A. z
    / w. S% p) B" }7 J, @2 ~& ~
    三、创建模型0 n# x3 b' O. k$ l- B1 t; z4 ]. N% P

    ) _7 I6 h& c2 p4 N3 k
    8 _, k- A' a+ ^- b! T& I
    3.1 生成器% \& `- V0 X) u  n2 w+ S
    0 T  G& n; G- R8 {

    , ^# \7 M# x( ~. u4 `3.1 判别器
    1 Q% v1 s9 D6 f9 l- U+ z' a+ o3 i
    % A4 ~7 H% s6 H" a" C

    4 _* g1 s2 n% K& |3 v四、定义损失函数和优化器# C$ r% x! t/ B$ Z3 t$ [; t( _. ~

    9 ^0 X1 `4 q& ?1 Z9 Z# y6 j7 l
    8 k  s6 \6 V; O# }: c+ P6 q/ \
    4.1 生成器的损失和优化器1 r# w* C7 M' O2 U: o, V7 A2 p

      ~5 b7 y2 e, U* b" X* O

    9 J5 V7 A* P) _( [+ X: @4.2 判别器的损失和优化器
    + {' ~+ g/ i$ n4 l& \% z& C2 {, Z1 E# }- X3 Q6 g

    8 B" W( i3 w% H5 {; b4 C五、训练模型4 H! V2 H* n$ `; q! i! W, {
      }1 a( [$ W: [5 y8 Q/ L
    3 I& T( ?* `# V3 H/ z: ?. @# m
    5.1 保存检查点* }$ ^  E0 }$ X" J8 {0 W# f8 ?
    5 c, n9 r& q% |. L0 I" m* v

    ! g& E* S5 S9 A6 ]$ O5.2 定义训练过程
    5 @, ~+ t: S) \9 }" U
    . N! Y2 ]4 w! H2 O

    % W* m! a- ]0 @, V9 a, B1 j. H5.3 训练模型
    0 V7 q) x/ \3 D! N6 q
    5 `9 X7 \$ m" P6 T. |+ U: I  N; m% c8 m
    8 G7 }9 U1 z" e8 \9 _* `( F
    六、评估模型
    8 l9 e/ o4 P. W: G& o" l+ r- p! O
    2 L% h& l) @* v' D+ j. w' A
    . m/ T' R  h; Z* u
    一、什么是生成对抗网络?$ L1 V) C6 v+ H: r
    生成对抗网络(GAN),包含生成器和判别器,两个模型通过对抗过程同时训练。7 J+ H- t% e6 N' u

    1 g9 X' T( n! M) N1 ]8 @( z
      _, f, T( s' Q0 n+ |
    生成器,可以理解为“艺术家、创造者”,它学习创造看起来真实的图像。
    ' R8 b5 [3 w3 A) X! b3 m) J  @0 Z
    ' w* o; y8 ~( B6 o3 v
    + V3 h9 H$ u0 X  ^* f& E
    判别器,可以理解为“艺术评论家、审核者”,它学习区分真假图像。& v3 k0 p' G0 i
    , ?2 x& P7 B0 `7 J8 {$ |

    1 ^1 X: A+ z' [! m1 D$ W% c训练过程中,生成器在生成逼真图像方便逐渐变强,而判别器在辨别这些图像的能力上逐渐变强。
    3 @& r7 ?2 L% Z) D9 U: u0 X' |& c3 O) c# \' [, X
    9 N& [+ I* X3 R  }
    当判别器不能再区分真实图片和伪造图片时,训练过程达到平衡。  `* l4 d+ b- Z

    9 f; I* @# a( q2 f
    $ R; v( f0 `( b1 Y! l
    本文,在MNIST数据集上演示了该过程。随着训练的进行,生成器所生成的一系列图片,越来越像真实的手写数字。4 M4 L. |+ k9 ~; x; u' ^
    : ?+ l% ]! s* s  U% H

    % Z( @" u+ N& D+ _* y二、加载数据集
    1 ~2 Q/ A- ?# ^( @! r; U; Y; O使用MNIST数据,来训练生成器和判别器。生成器将生成类似于MNIST数据集的手写数字。
    ! x9 n' R) p: g/ D' T( ^- h  `5 @5 k) i, D1 A$ x5 Z. j* ~' t
    1 b& B* E6 n2 k2 F
    (train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()( \+ P/ h/ A; Y/ V  F# T5 h. P

    9 O# {8 y% T" x# g. Q& _5 f" Ytrain_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')4 q% `# I# }' y$ Y
    train_images = (train_images - 127.5) / 127.5 # 将图片标准化到 [-1, 1] 区间内( \  m7 y, X! s: L( l! E
      a1 l1 S. |" f. X
    BUFFER_SIZE = 60000  D" t1 s( K" X7 X6 e# ?& \
    BATCH_SIZE = 2566 D$ j6 g/ u  W0 r

    5 Q' J2 N. W/ v. f% J% _# 批量化和打乱数据3 Z4 \3 d9 `) L* ^5 r2 M
    train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
    $ m2 r+ w6 v1 i* D$ h* B# q* U三、创建模型5 |6 G/ K6 B' P; `, {. j' ]; M1 g
    主要创建两个模型,一个是生成器,另一个是判别器。
    , N& G1 @" f9 j9 E6 [1 ^
    % b0 R) g# p# U& O
    / @$ }' U" p) s% q0 d* c) K. ^6 C
    3.1 生成器' M* ^$ R; W6 Y" F3 ^# _
    生成器使用 tf.keras.layers.Conv2DTranspose 层,来从随机噪声中产生图片。
    0 M, l/ S+ K+ D7 D$ K7 R- D8 \9 g! t  ?) ?! W
    , S' ?6 |7 C  }: R8 z3 A
    然后把从随机噪声中产生图片,作为输入数据,输入到Dense层,开始。
    4 d! D* v* n$ S  A" N- }5 \
    ) Q' r+ X3 J" i8 i% x% @

    6 `7 J  z5 K# q1 A/ B8 f后面,经过多次上采样,达到所预期 28x28x1 的图片尺寸。  K- y" [1 ?7 }9 n. F- R. D

    : i' v: r% L) j! F5 R4 e$ q$ ?0 ?" S7 F

    $ _3 Z3 D0 ^& F: O- a% C- L/ b5 H9 Idef make_generator_model():5 y$ T% j5 r3 K" a
        model = tf.keras.Sequential()
    , d6 h% A  T# \3 z3 z0 W- a" N! Q    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    7 x+ j% g3 u8 z/ R6 V    model.add(layers.BatchNormalization())
    5 H$ `# ~9 F! H* k; k1 S. H    model.add(layers.LeakyReLU())4 B) D6 d( O& j; a, C5 T" i6 U
    $ u' S- m! r3 v- g
        model.add(layers.Reshape((7, 7, 256)))
    7 _- e: c  B) x/ \9 _3 i    assert model.output_shape == (None, 7, 7, 256) # 注意:batch size 没有限制
    8 f3 z7 e6 T7 B  I0 F3 A; X
    5 Q  u- W* H6 {    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
    0 L% C' z  ^, Q& N    assert model.output_shape == (None, 7, 7, 128), h( ?0 {9 r- f' ]; v: Z, C
        model.add(layers.BatchNormalization())' C4 o5 c0 K* l- c8 C; j5 K
        model.add(layers.LeakyReLU())" ^5 Q& [* G' M% T
    ; y( ]4 ~. G, p# b' G" ^% b9 v4 B
        model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False)). f8 T2 h/ h4 M' J
        assert model.output_shape == (None, 14, 14, 64)7 j; K  g; C) @
        model.add(layers.BatchNormalization())
    . e9 w! H' M5 {8 w+ a9 U    model.add(layers.LeakyReLU())
    : p5 Y0 r' ]9 W& h5 d* G
    : r& r1 J* @6 x  S9 B% x    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
    & V% s6 K3 a& Z- Q3 R$ H. P    assert model.output_shape == (None, 28, 28, 1)
    + n6 d; M* I! X/ x
    , m: I" O9 v. q- _, O4 {* `$ n    return model8 G7 q) f  i0 ]7 F9 O& o
    用tf.keras.utils.plot_model( ),看一下模型结构0 \5 g& }2 @& Q2 k, W

    8 b$ \; q, P4 G5 b: Q
    . j* Q0 K! `/ n2 Q8 i/ ]

    8 |; ?) c5 A/ E, f
    4 \7 q% U8 y2 M! H7 |3 A
    3 v% p4 a& z7 y/ y2 A
    用summary(),看一下模型结构和参数
    ) _8 _1 C# L, L. h- h5 g* P+ Z% \2 r" b: e8 D
    8 x: R2 V! C0 R5 T
    ' |  W9 Q- A) ^$ z

    ' A% g0 p% v( J1 X3 D. y! Q& K; J% ?  K0 Q8 H* q

    1 \) ^8 h9 B- U4 _% s/ b使用尚未训练的生成器,创建一张图片,这时的图片是随机噪声中产生。8 I+ f/ q/ R9 I' r5 s0 M) Y

    8 Z) c* L! R* C7 v$ h2 o0 @1 f
    " o! M7 o$ \1 e; b/ p
    generator = make_generator_model()3 A- O, w- }4 `0 Y; i

    % K0 r$ J/ a) D$ _+ pnoise = tf.random.normal([1, 100])- A- q5 s; C) e. y% {9 ~/ v) Q5 ~
    generated_image = generator(noise, training=False)! F8 k9 Z) @" x; i8 x/ C8 G
    & |2 y( }( @  d# C* c# k
    plt.imshow(generated_image[0, :, :, 0], cmap='gray')/ N/ ~: ?5 `4 o
    ) s" ?& i5 R$ C9 F7 i  }
    , s- f- G$ d$ H4 ~4 m

    9 x0 U/ c* c% z
    3 v" V5 I/ u) D5 ]9 C
    3.1 判别器) f& [/ x6 M2 c' N. N, [$ p
    判别器是基于 CNN卷积神经网络 的图片分类器。/ X8 P( }9 {. W8 @. z3 D  P- j

    6 b7 k) p9 Q4 Z6 y! i% }, {% U- h

    6 \! D, J, F" M+ edef make_discriminator_model():
    / l6 S  \" P8 C2 z    model = tf.keras.Sequential()
    " G$ ]. `5 Z; e    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
    1 f/ K6 F, G% d& y+ a# E                                     input_shape=[28, 28, 1]))
    2 n( J7 J& u; b4 C' ]    model.add(layers.LeakyReLU())
    8 `1 b0 R" {2 Q# l7 V2 r7 d/ e    model.add(layers.Dropout(0.3)), o' z) B: C2 z7 V  l/ S
    : }& x3 G+ O+ }1 q
        model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    4 y- n; X8 ~" H. S  p; U    model.add(layers.LeakyReLU())% k. S+ J3 t3 N
        model.add(layers.Dropout(0.3))6 {! F% c& I! R" h/ i6 I
    ( j7 V5 m; m8 j. |6 _" w3 K
        model.add(layers.Flatten())9 i  E! o( H; Y: Q
        model.add(layers.Dense(1))
    # W6 Z4 L$ g6 ?! V) G% z+ V* ?0 u
    * l) h- E3 N5 v3 Q& D6 e. ]) R0 v    return model1 Y) ~. f( {& V& E5 g
    用tf.keras.utils.plot_model( ),看一下模型结构
    ( r3 F/ g, ~- S, H0 E: \" E9 C5 \% g4 W4 [* ^
    $ Y6 f$ G- }: N$ w2 K

    , P% R4 L1 b6 f2 j. {2 O
    , Q3 U1 D- E, s7 [4 i+ H. G) i
    , E' I7 k+ Z: ~+ ~
      ]8 B1 Y$ H  _2 n. s2 y; \  t
    用summary(),看一下模型结构和参数4 x- J1 x. I6 o+ M

    ; q; P7 v7 ?: C1 S
    , G. |4 J) l4 x1 y. K
    - S) b* u: N- K2 |: w
    6 ?, s0 Y% A  L
    ; q* O5 f, z6 g' Z$ E6 [

    # m! a# n& m& K  H" y. A6 C6 h四、定义损失函数和优化器
    ! B; W( v* B3 Y/ f由于有两个模型,一个是生成器,另一个是判别器;所以要分别为两个模型定义损失函数和优化器。& Z) N* m  a4 J& }4 f

    7 P) V; F! F  `  x2 y/ v$ n+ x

    ' C, Y6 i5 f1 g首先定义一个辅助函数,用于计算交叉熵损失的,这个两个模型通用。
    # s' u! Y/ K9 W( r
    3 n) o  \+ g/ H

    * b+ V( o0 Y$ f# 该方法返回计算交叉熵损失的辅助函数
    5 Y% N# O2 X4 o9 ^% b- ocross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True); Q( n4 ?$ b- v3 h
    4.1 生成器的损失和优化器1 u7 s. g# s. Q/ I. l6 g5 `
    1)生成器损失
    & i; S) E) `7 W- `4 v2 F% B- n1 y5 }& Q, v* N, j* ], M6 Q0 ]
    * ~1 d' p4 `. C6 c( {
    生成器损失,是量化其欺骗判别器的能力;如果生成器表现良好,判别器将会把伪造图片判断为真实图片(或1)。- Z( G6 f. |; P7 ]* ~

    - O; y8 H2 b6 E% X2 p. F

    , p7 R8 h  {1 _0 X/ k7 |- B3 d这里我们将把判别器在生成图片上的判断结果,与一个值全为1的数组进行对比。( t+ ~/ `6 H$ Q7 M4 s

    # q/ n$ s5 H* {' `: P
    ( E" Y, ^+ ~7 F3 p; a3 O* T
    def generator_loss(fake_output):/ G. X7 k0 i( n, d, m
        return cross_entropy(tf.ones_like(fake_output), fake_output)
    : ^3 l: h: K* {2)生成器优化器
    ! |* d+ M  r  I
    * N$ U) w" L$ a7 y& L5 m
    ! w$ O$ X6 J/ ]) C9 Y' D
    generator_optimizer = tf.keras.optimizers.Adam(1e-4)3 D0 c+ o8 e/ {' ]0 E/ h
    4.2 判别器的损失和优化器
    # ~  O6 ]9 i( o" b1)判别器损失
    ! B& x2 Q) ~! q. O6 n$ U' |
    ( j; r) u, N$ p# ~0 [$ x2 f
    & F# p# L  J3 Z# B' ~# O7 J
    判别器损失,是量化判断真伪图片的能力。它将判别器对真实图片的预测值,与全值为1的数组进行对比;将判别器对伪造(生成的)图片的预测值,与全值为0的数组进行对比。5 E& S- O$ J' F. `5 J
    8 b) T/ N6 {: M6 L  i+ r; V3 V
    7 U/ T7 w/ x/ _5 k2 n* j
    def discriminator_loss(real_output, fake_output):& F$ [  Y6 g; }; m
        real_loss = cross_entropy(tf.ones_like(real_output), real_output)# ~9 E2 e( S/ q; y) @
        fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)9 g, f* U% r# g
        total_loss = real_loss + fake_loss7 O; g, Y2 W3 E3 w$ |
        return total_loss* Q" P& ~) P3 s$ W0 o: `
    2)判别器优化器! g. a% o6 _- l# M0 Q
    $ B% D. `! V4 B

    7 J' [6 s$ c7 |" @7 z! [9 wdiscriminator_optimizer = tf.keras.optimizers.Adam(1e-4)4 F- L4 p5 m1 v4 g- o! I
    五、训练模型
    * W4 A0 B0 d# K5.1 保存检查点8 W5 Z( R9 X1 i  S+ [# s
    保存检查点,能帮助保存和恢复模型,在长时间训练任务被中断的情况下比较有帮助。& x. `8 g# R% J1 i& L1 C$ p
    # A/ [6 _1 W( T& }
    ; n8 [8 h# q  v+ `
    checkpoint_dir = './training_checkpoints'" o% _- M1 ~5 U2 g
    checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
    ) [0 n# i; T9 X7 e4 A. h0 \, Lcheckpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,0 \! l& O! C0 p. S) J% G- d
                                     discriminator_optimizer=discriminator_optimizer,
    8 ~0 Q# e$ O! @4 ~% i                                 generator=generator,
    2 c6 h% B/ G: R5 I* @! N1 i                                 discriminator=discriminator)
    0 e; R" `: b. O0 {0 M8 W4 o5.2 定义训练过程
    / A. E; _9 p( v8 F6 JEPOCHS = 50# \8 B% G0 h; n  @
    noise_dim = 100
    & k7 e/ [2 J5 j* ]9 Anum_examples_to_generate = 16
    " u" }  Z1 l6 a 5 ]4 u$ X  w7 p) n
    : u4 B, D4 P9 p; d' d2 Z% g$ j
    # 我们将重复使用该种子(因此在动画 GIF 中更容易可视化进度)+ r4 h5 c, N2 o
    seed = tf.random.normal([num_examples_to_generate, noise_dim])2 E- r2 s5 ^9 |8 G+ V
    训练过程中,在生成器接收到一个“随机噪声中产生的图片”作为输入开始。6 G, u, J- B9 M3 s
    * y% W2 _, V% Q5 }. t. v4 X  ?/ @

    3 T, k( ], u; ~. _8 I判别器随后被用于区分真实图片(训练集的)和伪造图片(生成器生成的)。
    * {) }, t/ H4 Y) I' }% m( |
    5 H9 u1 T. M1 Q( a, y

    " @6 Z# v( b% }! O7 g两个模型都计算损失函数,并且分别计算梯度用于更新生成器与判别器。% m; ~3 C( Q5 t, [8 V4 v

    / t  ~  N" g9 I
    . P2 z! A% x3 J; n5 G0 Y
    # 注意 `tf.function` 的使用, g& M5 E+ b1 ?% R
    # 该注解使函数被“编译”  U9 \. I  ?5 C, N! y+ p
    @tf.function0 Q! X, g' P+ T! ]2 z
    def train_step(images):
    0 f: g) s# G, W. m! q# Z6 f' Q    noise = tf.random.normal([BATCH_SIZE, noise_dim])
    " ~+ k. H6 T3 L
    . z0 y: s& s- r  l+ a( O  ^    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:! b4 P" v8 Q" n
          generated_images = generator(noise, training=True)% K  L# g) g7 u9 C. B  u+ N

    ; {" e% A3 {  ?1 c3 l3 [9 S      real_output = discriminator(images, training=True), N: l6 i* X6 W6 t8 u# X6 I& T
          fake_output = discriminator(generated_images, training=True)
    1 w1 J) A2 d; x. d: T 3 b. f% L  t# _; F# P: w( v! c
          gen_loss = generator_loss(fake_output)% I9 F6 ^# G  A. I3 q5 C) C. \! U
          disc_loss = discriminator_loss(real_output, fake_output)( p* Z6 B6 g& E. c- f

    / |6 O" E) e  q    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    0 @" @" T0 ~- X" X, r0 A7 Z    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)& l8 r- x8 R  L  Y1 T* L

    8 U+ m+ N1 S8 @& Z4 w    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    3 S/ A" v1 {) S" {" ^    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
    , V3 G& L, g: |, x 7 K/ K. N1 O, Z4 h% ~
    def train(dataset, epochs):% P" x; ]4 d; f8 M( @& G
      for epoch in range(epochs):
    + p: m0 _) t- w4 l    start = time.time()
    & O9 Z/ q1 W' b1 K  }8 `( ]1 t3 d: ?
    $ X# ~( G$ P2 S# f' a    for image_batch in dataset:3 y' N4 u7 i( z. d( L3 e7 m
          train_step(image_batch)
    : ]. b0 p) T7 D" w' W& m8 O* }1 f1 w 6 r) \. r8 i1 l2 ?; y2 l  x5 T, q
        # 继续进行时为 GIF 生成图像! l8 ~; m8 m$ D- _' C; q+ C* U; x
        display.clear_output(wait=True)! w' n$ d" |+ k% S3 D4 m+ X
        generate_and_save_images(generator,
    : Q" [1 a& h% H0 t( _                             epoch + 1,3 L& v2 Z. d; x6 I
                                 seed)
    & I! E# ]1 W' S: q1 c' W9 Q 6 S! W  C" L$ z  T+ V
        # 每 15 个 epoch 保存一次模型5 y4 J1 s) C. K3 m0 P" W
        if (epoch + 1) % 15 == 0:
    ; q$ S  l. K1 J; Q" `  T4 J8 z      checkpoint.save(file_prefix = checkpoint_prefix)- w+ t0 o0 J, A8 F5 z

    9 A+ z- e4 D. y+ p! w( j' m    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))
    ( R: L$ h# @8 A; v1 c; u7 G / |5 }- U0 h( N, G4 o; j" [
      # 最后一个 epoch 结束后生成图片
    . @6 X$ U3 E/ o, d  display.clear_output(wait=True)
    4 c0 Z5 B/ I4 H6 h* B1 I* g! e5 z  generate_and_save_images(generator,7 Q: y# q  k/ k! {2 `
                               epochs,
    : V! F. @. {6 B9 @, [$ ^; y7 R                           seed)
    " Q% j- A0 ?# y( F
    8 C2 v1 O3 O; k, w  R. f& z" Q# 生成与保存图片
    2 o5 F3 V4 c* i/ @def generate_and_save_images(model, epoch, test_input):" e, f& F: h1 y% b) i: @3 ~
      # 注意 training` 设定为 False" t, Q4 d, d' F6 j: g$ {
      # 因此,所有层都在推理模式下运行(batchnorm)。6 p, ?! z& ]# o" g
      predictions = model(test_input, training=False)
    ) N' [8 D' u9 G4 Z+ ` ; @3 @( g9 q" r3 S9 _3 l6 J
      fig = plt.figure(figsize=(4,4))
    3 |, {& p* w. `0 p5 B  T 7 |1 V% h$ K% }2 x7 Y  n8 v2 j
      for i in range(predictions.shape[0]):( A* R( y+ u  f( @2 Y8 |# a
          plt.subplot(4, 4, i+1)
    2 j% h2 E6 j$ a- M3 _      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
    9 o& W7 a+ a8 [; L% N4 E* N      plt.axis('off')
    3 e8 A1 `& K5 Y. Z
    # B2 v4 p' s9 v! u: C* T1 ]; a  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))3 j% g! o, j$ N
      plt.show()  r1 @+ V+ U: Q- u& h
    5.3 训练模型
    - R! h. P+ j5 l4 e. z调用上面定义的train()函数,来同时训练生成器和判别器。
    9 F5 S1 l' Q, @( u  ^* v4 M1 |. S" @  ?. [: f# z2 x$ Y0 h
      F) t# q4 l6 [; E
    注意,训练GAN可能比较难的;生成器和判别器不能互相压制对方,需要两种达到平衡,它们用相似的学习率训练。1 {7 |5 E4 }0 G9 n5 _1 Z4 Y3 l

    + Z* a. i( c! ]6 _

    7 w" l0 r( d, p$ h9 n%%time+ i1 a" _4 Y4 a6 L3 K# C
    train(train_dataset, EPOCHS)2 a: N5 K4 ?7 f9 E. |2 y- S
    在刚开始训练时,生成的图片看起来很像随机噪声,随着训练过程的进行,生成的数字越来越真实。训练大约50轮后,生成器生成的图片看起来很像MNIST数字了。" J* Y) s$ p7 X5 ~: D" H' W
    3 G, G* A: w; W* Z% \! V8 ^6 }  H! u

    2 ^8 N) X% S6 |训练了15轮的效果:4 S( V  ]7 \3 r1 w
    6 b; D. i- N& l3 K8 p0 d* K

    : L: P* y% {- v$ [9 ^0 O( v# a5 _" K# J2 ]: C
    4 z9 P7 a, z5 T( Y+ W* L! r

    - _5 M9 c+ c2 l& M9 x( u* M* v$ s
    8 @' {7 z/ {. t# a* W7 q. F
    训练了30轮的效果:
    9 o; r: a' V( `# ~1 `& H& [7 g* d3 W

    2 a+ x% n7 J5 W3 Y0 ~
    " g/ t) F8 Q$ }4 d
    : H$ J6 v6 e: w! k3 l

    ! g4 M! w4 ^2 Q# i/ C; m
    9 B1 g" |! d3 a1 u1 s2 X
    训练过程:
    4 O% i4 S1 f5 a- k+ m- a
    7 L( y4 v# E) h2 _* _1 g

    : g& T+ I( n2 c. j' v) n" }
    , V! Q) i" s5 y/ R' b+ J
    + G5 T) a! S4 u0 s: |

    7 K5 E; j8 \: P  w- p: S
    ; {6 b0 \# [% R$ ]6 a
    恢复最新的检查点
    / O& i4 u# U: A( ^% q7 ^) Q: O) b% A( i- T  \

    # g: J  ?; F  @$ Fcheckpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
    ( [( d0 d6 O( K# `2 j. H六、评估模型
      r5 ~7 B4 Q# j) e: R' u这里通过直接查看生成的图片,来看模型的效果。使用训练过程中生成的图片,通过imageio生成动态gif。- t8 o7 L' B# g
    + m* g' Z1 O8 @! t, m+ i# L. _
    7 {8 i8 Z9 b8 |
    # 使用 epoch 数生成单张图片, i  n* R9 U! F! A: g- y
    def display_image(epoch_no):7 v0 ?. T% K1 q
      return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
    - q* G3 _. ^* u: ^) _# |, ]  J ( P( N; ]$ T. O
    display_image(EPOCHS)% O4 w2 i9 ]) V
    anim_file = 'dcgan.gif'' u6 l8 e/ t5 r6 D. ~  `% V8 E7 C

    5 |- j1 B' [8 c, l7 Xwith imageio.get_writer(anim_file, mode='I') as writer:
    , \" M# L+ Y/ z  filenames = glob.glob('image*.png')5 S: S* D( r/ \( x
      filenames = sorted(filenames)
    ; i" d; v# y+ _# O% o  last = -1
    " b+ Z% @5 C2 v  for i,filename in enumerate(filenames):
    ) A! p$ s# ]& Z& u9 f4 R" H3 c9 e  T    frame = 2*(i**0.5)2 T1 j0 z% c1 k4 k
        if round(frame) > round(last):7 c! [- k; T; o- z5 y
          last = frame
    # e+ y5 e& a% `. o* a    else:, L: q, K8 P/ h1 S# Z
          continue
    & ^& W" H* i$ u; H* ^8 X2 o# i0 Z    image = imageio.imread(filename)
    . l" z" a4 u/ ], W! Z9 R    writer.append_data(image)
    & A' ~% n1 x# N9 }4 ]  image = imageio.imread(filename)
    , Z0 w( E0 w! I; z6 h  writer.append_data(image)
    $ \+ @. x+ s" x6 h. G1 G
    : J! a+ ~& t( O4 Rimport IPython
    # E+ R; _/ l& x  t- p: A7 Pif IPython.version_info > (6,2,0,''):9 N. g! p, }2 e- Q' ?& H, W9 S- a
      display.Image(filename=anim_file)) @) a* @3 ?9 p4 O

    ) f& s* Y5 A& a$ A5 q

    0 q6 N2 D" @9 S& ^
    & J" s( ~( v  o* V

    6 `9 l, j* j7 v& [0 c# Z完整代码:
    # H, W0 C& L3 {9 j8 L: \2 W& h0 Z1 w$ A( B+ s4 P, {+ b5 D

    0 g, j/ u) Q2 bimport tensorflow as tf
    ( X0 Q! f8 n- l5 uimport glob$ v5 ?5 \. }# [
    import imageio& q# o: P+ h/ n" I# E" i
    import matplotlib.pyplot as plt
    5 N3 m. O# w: Mimport numpy as np
    ( Q) [2 m" [) B% ~import os
    9 j& d- B2 D  R! Z/ z1 U. aimport PIL7 C8 I4 @, M4 {: {! B0 }
    from tensorflow.keras import layers
    2 H3 S: `( |" F3 D9 S% Wimport time
    - ?) }; C7 H, e% I% c ! U7 ?$ h( @* B# u) B4 g
    from IPython import display
    % F+ D+ |- [, B" d: c3 S) e
    $ ?6 L* x9 P" ^) a(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data(), Q7 [* @7 P0 s+ y  W/ k6 G/ i

    & }1 Q9 O- t* V5 K2 Itrain_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
    - }# v* E9 O' z5 R& O* G- `8 Gtrain_images = (train_images - 127.5) / 127.5 # 将图片标准化到 [-1, 1] 区间内
    2 x1 O: h7 G9 V, |% u ' ~* o9 m6 b$ o% A0 n; D" _
    BUFFER_SIZE = 60000
    0 w$ K6 x' R/ ?  d4 o, G* f! PBATCH_SIZE = 256
    ( w1 m: B! L  Y. [" W ' H/ Z1 J0 }3 l+ q
    # 批量化和打乱数据7 p8 A8 f; Q& N1 O0 o5 g
    train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
    5 w) S5 a, U5 H5 `$ b + T$ R; A" ?( }- u' ~
    # 创建模型--生成器
    % @0 b1 P, m; ?, r1 ^def make_generator_model():
    4 z" r+ U0 D5 I    model = tf.keras.Sequential()
    4 k/ O9 y- k( W) b    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))8 r& c( U& U! t: P' M5 f% R, c1 N/ d
        model.add(layers.BatchNormalization())
    ' \7 s8 G7 {5 Y    model.add(layers.LeakyReLU())- U* ]: o% i# i# E
    " I  e! e& e4 S# e, e! Q
        model.add(layers.Reshape((7, 7, 256)))7 h) _' F8 R4 d% |- `+ \
        assert model.output_shape == (None, 7, 7, 256) # 注意:batch size 没有限制  n* x' S0 L: g- |3 L' {
    3 q, T9 e3 c; q  m$ D% Q
        model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))- [+ f/ ^( |/ o
        assert model.output_shape == (None, 7, 7, 128)
    : a# a( D1 }5 t4 q$ I    model.add(layers.BatchNormalization())
    2 k4 O& D4 q( [$ Z- O    model.add(layers.LeakyReLU())! l; E! G3 H) _3 k) c' G

    * C) ~7 O& c0 c+ P    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))0 ?% J7 g6 [* W1 i* Q9 F% `
        assert model.output_shape == (None, 14, 14, 64)# [+ u3 J& L1 S+ L, B- ^- L1 G
        model.add(layers.BatchNormalization())9 z( k2 e+ D; \3 B3 O
        model.add(layers.LeakyReLU())
    3 @$ x2 x7 A4 c  G 6 z) l7 E" k" C# p/ _: n
        model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))" T* g2 X# N8 c& w1 C1 B0 S
        assert model.output_shape == (None, 28, 28, 1)9 [. E8 x/ Z4 N; E6 J
    0 w- v, Y) u9 n( V0 D+ Y" T, c' s
        return model! p/ b* H6 C; V3 L- Y$ I

    1 {" s& B- q) K6 o0 ]# 使用尚未训练的生成器,创建一张图片,这时的图片是随机噪声中产生。: u% [& ?8 C- _: Q5 p4 n
    generator = make_generator_model()6 }; ~+ Y0 B% D' p; ^. S
    7 n2 ~# v+ r2 x/ \3 `
    noise = tf.random.normal([1, 100]); D$ m) d7 Q2 l4 Y$ o5 a2 l' y$ @
    generated_image = generator(noise, training=False)0 b7 L7 |- x8 T
    - x/ p3 y" |# W9 U. L& E
    plt.imshow(generated_image[0, :, :, 0], cmap='gray')
    / Y9 }# [' D" ftf.keras.utils.plot_model(generator)$ S8 D2 q  S1 Z  h" y5 i

    ' E0 Q/ o8 ]0 F- O2 \# 判别器! o; ]$ U5 b+ X6 P! A
    def make_discriminator_model():/ m( s" t6 \* \3 v) Y+ h# b
        model = tf.keras.Sequential()
    5 T( o, Q3 r. L) c9 R    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',$ X- u1 `! f. B: s
                                         input_shape=[28, 28, 1]))4 H% H( t3 w3 Q+ ?: K9 Q' w
        model.add(layers.LeakyReLU())
    - s/ h; z) }/ H! J# D9 n    model.add(layers.Dropout(0.3))
      \- j( N: c. d! ^. n
    1 @$ c) `$ O) u: Z4 l# A    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    9 W* X# _& z+ @! N+ P, J" m% J" r    model.add(layers.LeakyReLU()). b/ @! p0 @9 F3 \
        model.add(layers.Dropout(0.3))
    - @- d9 y  \& ^$ D' d   p+ H  g( s7 K. Y8 p9 |, X- Z
        model.add(layers.Flatten())- {! k: k, U+ |9 W# F
        model.add(layers.Dense(1))
    : ~  \' E: H! k7 m; b0 a7 z& y ; h, V0 Q( k  k9 ~+ S
        return model6 i+ w8 O- G$ w0 _, ^4 a: N

    7 |/ B$ u; E, r1 x# 使用(尚未训练的)判别器来对图片的真伪进行判断。模型将被训练为为真实图片输出正值,为伪造图片输出负值。
    " O" T& W. O3 K! Idiscriminator = make_discriminator_model()
      b& }/ j' H+ ]' u9 n# }' t3 tdecision = discriminator(generated_image)
    * n1 n; W. s) z0 O& y# }print (decision)
    # h9 O; c1 `' {2 u( D  n5 W8 L
    ; z9 m: b2 h; t, g2 w1 ?% ~6 u4 r# 首先定义一个辅助函数,用于计算交叉熵损失的,这个两个模型通用。
    6 D: b5 K& [2 Tcross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
    4 G. e5 g1 c) f- {
    * t: s8 p: I; P: d  a) D# 生成器的损失和优化器, C+ ~) _- w# {7 b8 e1 B' A
    def generator_loss(fake_output):4 _2 M7 d( A9 l& j8 {1 u
        return cross_entropy(tf.ones_like(fake_output), fake_output)
    : E# W1 N& o% |& j2 E" ngenerator_optimizer = tf.keras.optimizers.Adam(1e-4)
      m$ r: s) I9 g5 {' F6 T, A7 q0 S  l$ \2 n % w' Q4 U6 b5 X+ P8 b8 `9 `8 j
    # 判别器的损失和优化器
    1 S# A4 u7 v( zdef discriminator_loss(real_output, fake_output):) m$ a: U" z# r. _. R
        real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    : V2 S" @: X* C    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    $ O9 V( ?  S4 c' t: M    total_loss = real_loss + fake_loss
    & i' Y$ Y; o7 ?- |/ U    return total_loss
    3 a+ [0 n( b1 }* _discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)& y! l( c- n2 I4 _% x

    ( ]" S9 Z: Z. [# 保存检查点
    : o# L. Y2 O1 K" J; h+ U5 B5 `checkpoint_dir = './training_checkpoints'" z! t3 R2 a3 F
    checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")# ^! `  u% g' i; R8 D7 {1 Z
    checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
    ) E7 }$ r3 \0 o) h6 }" d                                 discriminator_optimizer=discriminator_optimizer,& t) t' m* E3 c8 x) Z3 m7 L6 B2 i/ t
                                     generator=generator,
    & K9 k1 _+ m! e# _: Q                                 discriminator=discriminator)
    ; W9 n( c. T' y
    4 s1 L0 ^5 o* k: x' V6 ]# 定义训练过程
    ' v/ n; C* h  g6 CEPOCHS = 503 P+ s. E  y) `' l! W- @7 @' D
    noise_dim = 100- d0 F* N! `0 {: a* V0 J* U# s; ?
    num_examples_to_generate = 16
    * l9 B# m: O, w' g; p9 C9 v+ r7 P
    $ K4 T. y  C. g" ]" l. t# 我们将重复使用该种子(因此在动画 GIF 中更容易可视化进度)
    * r, u7 {5 `' b" a, R' zseed = tf.random.normal([num_examples_to_generate, noise_dim])% P* a9 d, Q: L
    & j; R+ r5 @5 S+ w$ X
    # 注意 `tf.function` 的使用6 e# J* U& S# O0 f
    # 该注解使函数被“编译”! h; Z6 F: ~# X- ^% ]. |: R# W
    @tf.function
    ( w! m/ ~( h4 M/ \& @8 Udef train_step(images):
    , S) y# ?0 }. E( t* E    noise = tf.random.normal([BATCH_SIZE, noise_dim])  t% m1 m/ V3 s6 d0 K

    ) [) f5 D2 y: {- T5 N( g4 C    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:) T' B( i, b+ E' U) f. ]$ @2 M0 O; |; L% U
          generated_images = generator(noise, training=True)& H/ E& ~, d5 e% L3 [8 b
    1 x4 v  _! Q/ }# I8 J' N: M  u
          real_output = discriminator(images, training=True)
    ; E) g% H' A& U: d/ d; B      fake_output = discriminator(generated_images, training=True)
    0 b# e  `3 p/ C' }5 S: [ 5 o/ F* I" c. s5 O
          gen_loss = generator_loss(fake_output)
    2 ]+ [  ^) b. z6 W: `5 p% D      disc_loss = discriminator_loss(real_output, fake_output)
    0 f$ _! d$ y; v( J/ f4 V
    2 Z/ m+ I2 v/ x0 Y" V3 j    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    $ E5 s& j5 o4 @5 ?    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables), w! X7 ]0 r+ H2 a7 D3 g  r
    $ w! x1 J' e# l
        generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))+ K; z. j$ e' _7 P' f- o) V$ T: m: ^
        discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))) l8 z5 X4 v# x3 y  ^  k

    1 l" q8 d0 U. A# f6 Z  ^/ A$ d0 xdef train(dataset, epochs):! Q) Q$ N' F  V& u
      for epoch in range(epochs):0 G: T& i$ T# M# k
        start = time.time()/ Y8 x9 Y7 U% g
    " P  a1 E9 z( D; A2 P
        for image_batch in dataset:3 D0 j+ H4 J1 z0 X& \
          train_step(image_batch)# ~1 b) Z- Y, W. v: Z
    1 J4 `" M6 R: J7 Y- }6 e$ D+ k1 l
        # 继续进行时为 GIF 生成图像3 k3 S6 h: ^& ?7 j6 _
        display.clear_output(wait=True)
    1 O8 a8 X0 s$ y    generate_and_save_images(generator,
    # e3 y4 v' o0 J                             epoch + 1,
    6 w3 k7 H7 n6 P( t. @                             seed)
    / q( X' {0 {0 T2 t 3 }' b& h9 F7 d( w8 ?7 ^9 c
        # 每 15 个 epoch 保存一次模型
    ( q& G7 m9 Q: k2 W* [$ m9 r    if (epoch + 1) % 15 == 0:1 B' |2 j' A# z7 f% w% j
          checkpoint.save(file_prefix = checkpoint_prefix)
    3 I6 C% t! u% P5 j4 E7 o0 m$ @ , {9 i" [! K" g/ k' W4 H! T8 L
        print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))' s4 K7 T0 ^  X' |0 h6 n9 V. q

    0 d7 J& Q1 o- J/ ?! _7 R* j  # 最后一个 epoch 结束后生成图片1 @9 P; }. g/ b3 w1 @
      display.clear_output(wait=True)
    1 ]1 B6 F+ f$ [  generate_and_save_images(generator,8 }3 P. h* y2 P- ^2 H, X5 n% \
                               epochs,- P+ u+ J9 r  V- U3 n; o- I
                               seed)
    # Y6 b5 F) o- y$ D) ^8 Y5 H3 n
    $ v, a& z9 D7 g* r9 Z; q# h" Z6 I2 n# 生成与保存图片
    - l0 Y9 o# F" {5 t' f9 G: wdef generate_and_save_images(model, epoch, test_input):" \3 r% G" B) P7 i8 I9 c  ?
      # 注意 training` 设定为 False
    ) f8 ?7 Z" y* Q. X/ q( d1 S) C- W  # 因此,所有层都在推理模式下运行(batchnorm)。5 P* B' I* e; C$ N  T7 d; y
      predictions = model(test_input, training=False)
    & v' U7 g$ g" n, n2 h  Y
    $ r2 W7 y8 ]* I3 c2 F' I; `  fig = plt.figure(figsize=(4,4))" s! [$ a% l3 z. A, z, n
    1 E! x$ a5 S$ i+ V0 Q$ }- V5 l
      for i in range(predictions.shape[0]):
    ' K; n3 ]8 `. n      plt.subplot(4, 4, i+1)
    8 C0 [( n8 \; T: r0 F      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
    * O/ Y4 N4 A& U& C+ L      plt.axis('off')
    6 @+ Z; r, R; S. B0 g
    & [& ^6 b2 k+ k  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))7 Z% Y. l1 r* ]( }: N  m/ V, q
      plt.show()
    6 }9 m) M5 b* \- k7 M
    & I2 @3 L1 U8 p# 训练模型
    : p4 T# B% k. h- k$ G/ m  |train(train_dataset, EPOCHS). [1 R2 n) H- `
    , n9 d  Z/ r+ p; `1 U0 P
    # 恢复最新的检查点' z+ X5 W6 u9 i+ L
    checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir)). E3 g! `4 [/ g$ p! y! E7 v
    ' y: h3 T4 \. e9 ]( k
    # 评估模型
    % }; G( }  V+ b( X& }( [3 b# 使用 epoch 数生成单张图片1 k2 G$ V. `1 T/ _* e2 J- D3 j
    def display_image(epoch_no):
    ( c3 s2 ?, f( h7 `/ v2 q) H  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
    7 _. q# u8 T- D( z4 G, |6 Z; {7 X6 V
    ! W5 H- ~) V( O: t! ydisplay_image(EPOCHS)) z* K( u4 i3 d4 U6 t; K

    3 [6 @4 [" C0 {8 s3 X3 uanim_file = 'dcgan.gif'
    , a1 h$ y* `- v- T6 C. Z" [ 1 a+ v; G0 V) V6 x- k$ B- H+ O8 Q2 ~
    with imageio.get_writer(anim_file, mode='I') as writer:
    # u. e. M$ ]9 c3 {  |$ D  filenames = glob.glob('image*.png')* v0 |) f! `1 R; s5 U5 ~% x
      filenames = sorted(filenames)
    % a5 a- K1 T( w# m  last = -11 n! B+ C/ M$ i
      for i,filename in enumerate(filenames):0 ?: X6 q1 [9 w& F
        frame = 2*(i**0.5)
    : j) X  ^  p  S    if round(frame) > round(last):
    , E# q5 }* E, l( ]' {      last = frame
    - C' n) k2 Z* {' u: i' m3 ^8 I8 n    else:1 s' B/ W: L7 C% `
          continue
    1 q( ^/ B8 y) N    image = imageio.imread(filename)
    1 T8 r; O# @: n- `! X, ?3 b    writer.append_data(image)
    0 s' C5 t3 P' ]$ X% ]- A# a  image = imageio.imread(filename)
    , {, I( q: ~1 q, |  writer.append_data(image)
    + z& E1 a0 ?+ w* ?, c & m0 I0 `9 q7 v+ v- @
    import IPython
    % a# A$ S  ]7 ?if IPython.version_info > (6,2,0,''):  A  ^7 j5 m* K, k+ m
      display.Image(filename=anim_file)% I! Q: T  M6 s: c& r7 I1 }5 w
    参考:https://www.tensorflow.org/tutorials/generative/dcgan" M5 `' }8 p. c9 z- y1 O
    ————————————————2 T; e7 y4 f/ n+ m7 ]
    版权声明:本文为CSDN博主「一颗小树x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。/ B4 `5 N8 j) Z( \* @$ t3 q
    原文链接:https://blog.csdn.net/qq_41204464/article/details/118279111* U9 i! c  U! P% v# T  E6 I0 u, l
    6 E4 {  t# r( e. c  ^

    0 `* ?0 d' x7 t3 n, 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, 2025-8-2 04:02 , Processed in 0.604987 second(s), 50 queries .

    回顶部