├── .gitignore
├── LICENSE
├── README.md
├── requirements.txt
├── res
├── deeplearning-with-tensorflow-book.jpg
└── keras-dataset.png
└── src
├── ch01
├── 1.5.2-tf1.py
├── 1.5.2-tf2.py
├── 1.5.3-autograd.py
├── 1.5.3-cpu-gpu-time.svg
└── 1.5.3-cpu-gpu.py
├── ch02
├── 2.3-linear-model.py
├── data.csv
└── 模型训练MSE下降曲线.png
├── ch03
├── 3.8-non-linear-nn.py
└── MNIST数据集的训练误差曲线.png
├── ch04
├── 4.10-forward-prop.py
├── MNIST数据集的前向传播训练误差曲线.png
└── ch04-TensorFlow基础.ipynb
├── ch05
├── 5.8-mnist-tensorflow.py
├── MNIST测试准确率曲线.svg
├── MNIST训练误差曲线.svg
└── ch05-TensorFlow进阶.ipynb
├── ch06
├── 6.8-auto-mpg-efficency.py
├── MEA变化曲线.svg
├── ch06-神经网络.ipynb
└── 特征之间的两两分布.svg
├── ch07
├── 7.9-backward-prop.py
├── ch07-反向传播算法.ipynb
├── 数据集分布.svg
├── 网络测试准确率.svg
└── 训练误差曲线.svg
├── ch08
├── 8.2-model-assembly-training-and-testing.py
├── ch08-Keras高层接口.ipynb
├── checkpoint
├── model-savedmodel
│ ├── saved_model.pb
│ └── variables
│ │ ├── variables.data-00000-of-00002
│ │ ├── variables.data-00001-of-00002
│ │ └── variables.index
├── model.h5
├── weights.ckpt.data-00000-of-00002
├── weights.ckpt.data-00001-of-00002
├── weights.ckpt.index
└── 模型性能.png
├── ch09
├── 9.8-over-fitting-and-under-fitting.py
├── ch09-过拟合.ipynb
├── lena512color.png
├── lena512color.tiff
└── output_dir
│ ├── dropout
│ ├── Dropout_0.png
│ ├── Dropout_1.png
│ ├── Dropout_2.png
│ ├── Dropout_3.png
│ └── Dropout_4.png
│ ├── network_layers
│ ├── 网络容量_2.png
│ ├── 网络容量_3.png
│ ├── 网络容量_4.png
│ ├── 网络容量_5.png
│ └── 网络容量_6.png
│ ├── regularizers
│ ├── 正则化_0.001.svg
│ ├── 正则化_0.1.svg
│ ├── 正则化_0.12.svg
│ ├── 正则化_0.13.svg
│ ├── 正则化_1e-05.svg
│ ├── 正则化网络权值_0.001.svg
│ ├── 正则化网络权值_0.1.svg
│ ├── 正则化网络权值_0.12.svg
│ ├── 正则化网络权值_0.13.svg
│ └── 正则化网络权值_1e-05.svg
│ └── 月牙形状二分类数据集分布.svg
├── ch10
├── 10.10-cifar10-vgg13-compile.py
├── 10.10-cifar10-vgg13-output-1582916285.1641374.log
├── 10.10-cifar10-vgg13.py
├── 10.14-cifar10-resnet18.py
├── 10.4-LeNet-5.py
├── ch10-卷积神经网络.ipynb
├── cifar10-vgg13-accuracy.png
└── resnet.py
├── ch11
├── 11.11-sentiment-analysis-cell-GRU.py
├── 11.11-sentiment-analysis-cell-LSTM.py
├── 11.11-sentiment-analysis-layer-GRU.py
├── 11.11-sentiment-analysis-layer-LSTM.py
├── 11.12-sentiment-analysis-layer-LSTM-pretrain.py
├── 11.5-sentiment-analysis-RNN.py
└── ch11-循环神经网络.ipynb
├── ch12
├── 12.2-fashion-mnist-autoencoder.py
├── 12.5-fashion-mnist-vae.py
├── ae_images
│ └── rec_epoch_99.png
└── vae_images
│ ├── epoch_99_rec.png
│ └── epoch_99_sampled.png
├── ch13
├── 13.3-faces-dcgan.py
├── 13.8-faces-wgan-gp.py
├── dataset.py
└── gan.py
├── ch14
├── 14.1.2-cartpole-v1.py
├── 14.1.5-cartpole-policy.py
├── 14.1.5-cartpole-policy.svg
├── 14.3.6-cartpole-ppo.py
├── 14.3.6-cartpole-ppo.svg
├── 14.4.7-cartpole-dqn.py
└── 14.5.3-cartpole-a3c.py
└── ch15
├── pokemon.py
├── resnet.py
├── train_scratch_dense_net.py
├── train_transfer_dense_net.py
└── train_transfer_dense_net.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | /venv
2 | /.idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TensorFlow深度学习练习代码
2 | 龙龙(龙曲良)老师的《TensorFlow深度学习》是TensorFlow2.0入门教材之一。
3 | 本书共15章,大体上可分为4个部份:第1-3章为第1部分,主要介绍人工智能的初步认知,并引出相关问题;第4-5章为第2部分,主要介绍TensorFlow相关基础,为后续算法实现铺垫;第6-9章为第3部分,主要介绍神经网络的核心理论和共性知识,让读者理解深度学习的本质;第10-15章为模型算法应用部分,主要介绍常见的算法与模型,让读者能够学有所用。
4 | **申明:** 所有的代码都来源于《TensorFlow深度学习》,github地址:https://github.com/dragen1860/Deep-Learning-with-TensorFlow-book。
5 |
6 | ## 使用说明
7 | 1. 本练习代码是搭配龙龙老师的《TensorFlow深度学习》一书。
8 | 2. 关于本笔记中的练习代码,已经消缺了书中代码的错误,可以很方便地执行程序。
9 | 3. 关于书中的很多图,已经写好了生成数据图的代码,在对应的目录下也有数据图。
10 | 4. 关于书中很多用jupyter notebook写的代码示例,也在对应的目录下有对应章节的ipynb文档。
11 | 5. 关于python包的版本问题,请详见requirements.txt文件,笔者采用的tensorflow-gpu==2.0.0,可以使用cpu版本,但是运行会特别慢。
12 | 6. keras模型与数据下载地址:链接:https://pan.baidu.com/s/1Rt6KYWUAQ8MWKY9UVVDtmQ 提取码:wedp
13 | 7. 相关数据集和gym包,百度网盘的下载地址:链接:https://pan.baidu.com/s/1fZ748Xz3WrgQnIaxGsrZLQ,提取码:ea6u
14 |
15 |
16 | 使用windows平台的tensorflow,将keras中的datasets和models放入到C:\\Users\\{pcUserName}\\.keras路径下,其他的数据包,在对应的练习代码中有说明。
17 |
18 | ## 选用的《TensorFlow深度学习》版本
19 |
20 |
21 |
22 | > 书名:TensorFlow深度学习
23 | > 作者:龙龙老师
24 | > 版次:2019年12月05日测试版第2版
25 |
26 | 电子书(带书签-无水印版)的百度网盘地址:链接:https://pan.baidu.com/s/1CPXZSrqVTJWHc3cYXIYjNg,提取码:mrhw
27 |
28 | ## 主要贡献者(按首字母排名)
29 | [@胡锐锋-天国之影-Relph](https://github.com/Relph1119)
30 |
31 | ## 总结
32 | 本书总共用了16天(2020年2月14日-2020年3月1日)阅读完,对TensorFlow和Keras的使用有很大的收获,其中第11、13章和第15章的scratch训练,由于电脑的显卡不好,不能完成练习,但其他章节的练习均已完成。
33 |
34 | **注意:** 如果出现以下这个错误,说明显卡的显存太低,可以将代码和数据集放到Google Colab上面执行。
35 | > tensorflow.python.framework.errors_impl.ResourceExhaustedError: OOM when allocating tensor with shape[500,500,500] and type float on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc [Op:Sub] name: sub/
36 |
37 | ## LICENSE
38 | [GNU General Public License v3.0](https://github.com/relph1119/deeplearning-with-tensorflow-notes/blob/master/LICENSE)
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | absl-py==0.9.0
2 | astor==0.8.1
3 | attrs==19.3.0
4 | bleach==3.1.0
5 | cachetools==4.0.0
6 | certifi==2019.11.28
7 | chardet==3.0.4
8 | cloudpickle==1.3.0
9 | colorama==0.4.3
10 | cycler==0.10.0
11 | decorator==4.4.1
12 | defusedxml==0.6.0
13 | entrypoints==0.3
14 | future==0.18.2
15 | gast==0.2.2
16 | google-auth==1.11.1
17 | google-auth-oauthlib==0.4.1
18 | google-pasta==0.1.8
19 | grpcio==1.27.1
20 | -e egg=gym&subdirectory=venv\lib\site-packages\gym
21 | h5py==2.10.0
22 | idna==2.8
23 | imageio==2.8.0
24 | importlib-metadata==1.5.0
25 | ipykernel==5.1.4
26 | ipython==5.0.0
27 | ipython-genutils==0.2.0
28 | ipywidgets==7.5.1
29 | Jinja2==2.11.1
30 | joblib==0.14.1
31 | jsonschema==3.2.0
32 | jupyter==1.0.0
33 | jupyter-client==5.3.4
34 | jupyter-console==6.1.0
35 | jupyter-contrib-core==0.3.3
36 | jupyter-core==4.6.2
37 | jupyter-nbextensions-configurator==0.4.1
38 | Keras-Applications==1.0.8
39 | Keras-Preprocessing==1.1.0
40 | kiwisolver==1.1.0
41 | Markdown==3.2.1
42 | MarkupSafe==1.1.1
43 | matplotlib==3.1.3
44 | mistune==0.8.4
45 | nbconvert==5.6.1
46 | nbformat==5.0.4
47 | notebook==6.0.3
48 | numpy==1.18.1
49 | oauthlib==3.1.0
50 | opt-einsum==3.1.0
51 | pandas==1.0.1
52 | pandocfilters==1.4.2
53 | pickleshare==0.7.5
54 | Pillow==7.0.0
55 | prometheus-client==0.7.1
56 | prompt-toolkit==1.0.18
57 | protobuf==3.11.3
58 | pyasn1==0.4.8
59 | pyasn1-modules==0.2.8
60 | pyglet==1.5.0
61 | Pygments==2.5.2
62 | pyparsing==2.4.6
63 | pyrsistent==0.15.7
64 | python-dateutil==2.8.1
65 | pytz==2019.3
66 | pywin32==227
67 | pywinpty==0.5.7
68 | PyYAML==5.3
69 | pyzmq==18.1.1
70 | qtconsole==4.6.0
71 | requests==2.22.0
72 | requests-oauthlib==1.3.0
73 | rsa==4.0
74 | scikit-learn==0.22.1
75 | scipy==1.4.1
76 | seaborn==0.10.0
77 | Send2Trash==1.5.0
78 | simplegeneric==0.8.1
79 | six==1.14.0
80 | tensorboard==2.0.2
81 | tensorflow-estimator==2.0.1
82 | tensorflow-gpu==2.0.0
83 | termcolor==1.1.0
84 | terminado==0.8.3
85 | testpath==0.4.4
86 | tornado==6.0.3
87 | traitlets==4.3.3
88 | urllib3==1.25.8
89 | wcwidth==0.1.8
90 | webencodings==0.5.1
91 | Werkzeug==1.0.0
92 | widgetsnbextension==3.5.1
93 | win-unicode-console==0.5
94 | wrapt==1.11.2
95 | zipp==2.2.0
96 |
--------------------------------------------------------------------------------
/res/deeplearning-with-tensorflow-book.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/res/deeplearning-with-tensorflow-book.jpg
--------------------------------------------------------------------------------
/res/keras-dataset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/res/keras-dataset.png
--------------------------------------------------------------------------------
/src/ch01/1.5.2-tf1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 1.5.2-tf1.py
6 | @time: 2020/2/14 15:30
7 | @desc: 1.5.2 TensorFlow 1.x的代码
8 | """
9 |
10 | import tensorflow.compat.v1 as tf
11 | tf.disable_v2_behavior()
12 |
13 | # 1.创建计算图阶段,此处代码需要使用 tf 1.x 版本运行
14 | # 创建 2 个输入端子, 并指定类型和名字
15 | a_ph = tf.placeholder(tf.float32, name='variable_a')
16 | b_ph = tf.placeholder(tf.float32, name='variable_b')
17 |
18 | # 创建输出端子的运算操作,并命名
19 | c_op = tf.add(a_ph, b_ph, name='variable_c')
20 |
21 |
22 | # 2.运行计算图阶段,此处代码需要使用 tf 1.x 版本运行
23 | # 创建运行环境
24 | sess = tf.InteractiveSession()
25 | # 初始化步骤也需要作为操作运行
26 | init = tf.global_variables_initializer()
27 | sess.run(init) # 运行初始化操作,完成初始化
28 | # 运行输出端子,需要给输入端子赋值
29 | c_numpy = sess.run(c_op, feed_dict={a_ph: 2., b_ph: 4.})
30 | # 运算完输出端子才能得到数值类型的 c_numpy
31 | print('a+b=',c_numpy)
--------------------------------------------------------------------------------
/src/ch01/1.5.2-tf2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 1.5.2-tf2.py
6 | @time: 2020/2/14 15:34
7 | @desc: 1.5.2 TensorFlow 2的代码
8 | """
9 |
10 | import tensorflow as tf
11 | # 此处代码需要使用 tf 2 版本运行
12 | # 1.创建输入张量,并赋初始值
13 | a = tf.constant(2.)
14 | b = tf.constant(4.)
15 | # 2.直接计算, 并打印结果
16 |
17 | print('a+b=', a+b)
--------------------------------------------------------------------------------
/src/ch01/1.5.3-autograd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 1.5.3-autograd.py
6 | @time: 2020/2/14 18:02
7 | @desc: 1.5.3 功能演示-自动梯度的代码
8 | """
9 |
10 | import tensorflow as tf
11 |
12 | # 创建 4 个张量,并赋值
13 | a = tf.constant(1.)
14 | b = tf.constant(2.)
15 | c = tf.constant(3.)
16 | w = tf.constant(4.)
17 |
18 | with tf.GradientTape() as tape: # 构建梯度环境
19 | tape.watch([w]) # 将 w 加入梯度跟踪列表
20 | # 构建计算过程,函数表达式
21 | y = a * w ** 2 + b * w + c
22 |
23 | # 自动求导
24 | [dy_dw] = tape.gradient(y, [w])
25 | print(dy_dw) # 打印出导数
26 |
--------------------------------------------------------------------------------
/src/ch01/1.5.3-cpu-gpu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 1.5.3-cpu-gpu.py
6 | @time: 2020/2/14 17:56
7 | @desc: 1.5.3 功能演示-加速计算的代码
8 | """
9 |
10 | import matplotlib
11 | from matplotlib import pyplot as plt
12 |
13 | # Default parameters for plots
14 | matplotlib.rcParams['font.size'] = 20
15 | matplotlib.rcParams['figure.titlesize'] = 20
16 | matplotlib.rcParams['figure.figsize'] = [9, 7]
17 | matplotlib.rcParams['font.family'] = ['STKaiti']
18 | matplotlib.rcParams['axes.unicode_minus'] = False
19 |
20 | import tensorflow as tf
21 | import timeit
22 |
23 | # 设置 GPU 显存使用方式
24 | # 获取 GPU 设备列表
25 | gpus = tf.config.experimental.list_physical_devices('GPU')
26 | if gpus:
27 | try:
28 | # 设置 GPU 为增长式占用
29 | for gpu in gpus:
30 | tf.config.experimental.set_memory_growth(gpu, True)
31 | except RuntimeError as e:
32 | # 打印异常
33 | print(e)
34 |
35 | cpu_data = []
36 | gpu_data = []
37 | for n in range(8):
38 | n = 10 ** n
39 | # 创建在CPU上运算的2个矩阵
40 | with tf.device('/cpu:0'):
41 | cpu_a = tf.random.normal([1, n])
42 | cpu_b = tf.random.normal([n, 1])
43 | print(cpu_a.device, cpu_b.device)
44 | # 创建使用GPU运算的2个矩阵
45 | with tf.device('/gpu:0'):
46 | gpu_a = tf.random.normal([1, n])
47 | gpu_b = tf.random.normal([n, 1])
48 | print(gpu_a.device, gpu_b.device)
49 |
50 |
51 | def cpu_run():
52 | with tf.device('/cpu:0'):
53 | c = tf.matmul(cpu_a, cpu_b)
54 | return c
55 |
56 |
57 | def gpu_run():
58 | with tf.device('/gpu:0'):
59 | c = tf.matmul(gpu_a, gpu_b)
60 | return c
61 |
62 |
63 | # 第一次计算需要热身,避免将初始化阶段时间结算在内
64 | cpu_time = timeit.timeit(cpu_run, number=10)
65 | gpu_time = timeit.timeit(gpu_run, number=10)
66 | print('warmup:', cpu_time, gpu_time)
67 | # 正式计算10次,取平均时间
68 | cpu_time = timeit.timeit(cpu_run, number=10)
69 | gpu_time = timeit.timeit(gpu_run, number=10)
70 | print('run time:', cpu_time, gpu_time)
71 | cpu_data.append(cpu_time / 10)
72 | gpu_data.append(gpu_time / 10)
73 |
74 | del cpu_a, cpu_b, gpu_a, gpu_b
75 |
76 | x = [10 ** i for i in range(8)]
77 | cpu_data = [1000 * i for i in cpu_data]
78 | gpu_data = [1000 * i for i in gpu_data]
79 | plt.plot(x, cpu_data, 'C1')
80 | plt.plot(x, cpu_data, color='C1', marker='s', label='CPU')
81 | plt.plot(x, gpu_data, 'C0')
82 | plt.plot(x, gpu_data, color='C0', marker='^', label='GPU')
83 |
84 | plt.gca().set_xscale('log')
85 | plt.gca().set_yscale('log')
86 | plt.ylim([0, 100])
87 | plt.xlabel('矩阵大小n:(1xn)@(nx1)')
88 | plt.ylabel('运算时间(ms)')
89 | plt.legend()
90 | plt.savefig('1.5.3-cpu-gpu-time.svg')
91 |
--------------------------------------------------------------------------------
/src/ch02/2.3-linear-model.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 2.3-linear-model.py
6 | @time: 2020/2/14 19:13
7 | @desc: 2.3 线性模型实战的代码
8 | """
9 |
10 | import matplotlib.pyplot as plt
11 | import numpy as np
12 |
13 | # Default parameters for plots
14 | plt.rcParams['font.size'] = 16
15 | plt.rcParams['font.family'] = ['STKaiti']
16 | plt.rcParams['axes.unicode_minus'] = False
17 |
18 | # def load_data(seed):
19 | # np.random.seed(seed)
20 | # # 保存样本集的列表
21 | # data = []
22 | # # 循环采样 100 个点
23 | # for i in range(100):
24 | # # 随机采样输入 x
25 | # x = np.random.uniform(-10., 10.)
26 | # # 采样高斯噪声
27 | # eps = np.random.normal(0., 0.01)
28 | # # 得到模型的输出
29 | # y = 1.477 * x + 0.089 + eps
30 | # # 保存样本点
31 | # data.append([x, y])
32 | #
33 | # # 转换为 2D Numpy 数组
34 | # return np.array(data)
35 |
36 |
37 | def mse(b, w, points):
38 | # 根据当前的 w,b 参数计算均方差损失
39 | total_error = 0
40 |
41 | # 循环迭代所有点
42 | for i in range(0, len(points)):
43 | # 获得 i 号点的输入 x
44 | x = points[i, 0]
45 | # 获得 i 号点的输出 y
46 | y = points[i, 1]
47 | # 计算差的平方,并累加
48 | total_error += (y - (w * x + b)) ** 2
49 |
50 | # 将累加的误差求平均,得到均方差
51 | return total_error / float(len(points))
52 |
53 |
54 | def step_gradient(b_current, w_current, points, lr):
55 | # 计算误差函数在所有点上的导数,并更新 w,b
56 | b_gradient = 0
57 | w_gradient = 0
58 | # 总样本数
59 | m = float(len(points))
60 | for i in range(0, len(points)):
61 | x = points[i, 0]
62 | y = points[i, 1]
63 | # 误差函数对 b 的导数: grad_b = 2(wx+b-y),参考公式(2.3)
64 | b_gradient += (2 / m) * ((w_current * x + b_current) - y)
65 | # 误差函数对 w 的导数: grad_w = 2(wx+b-y)*x,参考公式(2.2)
66 | w_gradient += (2 / m) * x * ((w_current * x + b_current) - y)
67 |
68 | # 根据梯度下降算法更新 w',b',其中 lr 为学习率
69 | new_b = b_current - (lr * b_gradient)
70 | new_w = w_current - (lr * w_gradient)
71 |
72 | return [new_b, new_w]
73 |
74 |
75 | def gradient_descent(points, starting_b, starting_w, lr, num_iterations):
76 | losses = []
77 |
78 | # 循环更新 w,b 多次
79 | # b 的初始值
80 | b = starting_b
81 | # w 的初始值
82 | w = starting_w
83 | # 根据梯度下降算法更新多次
84 | for step in range(num_iterations):
85 | # 计算梯度并更新一次
86 | b, w = step_gradient(b, w, np.array(points), lr)
87 | # 计算当前的均方差,用于监控训练进度
88 | loss = mse(b, w, points)
89 | losses.append(loss)
90 | # 打印误差和实时的 w,b 值
91 | if step % 50 == 0:
92 | print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")
93 |
94 | # 返回最后一次的 w,b
95 | return [b, w], losses
96 |
97 |
98 | def main():
99 | # 加载训练集数据,这些数据是通过真实模型添加观测误差采样得到的
100 | data = np.genfromtxt("data.csv", delimiter=",")
101 | # 学习率
102 | lr = 0.0001
103 | # 初始化 b 为 0
104 | initial_b = 0
105 | # 初始化 w 为 0
106 | initial_w = 0
107 | num_iterations = 1000
108 | # 训练优化 1000 次,返回最优 w*,b*和训练 Loss 的下降过程
109 | [b, w], losses = gradient_descent(data, initial_b, initial_w, lr, num_iterations)
110 | # 计算最优数值解 w,b 上的均方差
111 | loss = mse(b, w, data)
112 | print(f'Final loss:{loss}, w:{w}, b:{b}')
113 |
114 | x = [i for i in range(0, 1000)]
115 | # 绘制曲线
116 | plt.plot(x, losses, 'C1')
117 | plt.plot(x, losses, color='C1', label='均方差')
118 | plt.xlabel('Epoch')
119 | plt.ylabel('MSE')
120 | plt.legend()
121 | plt.savefig('模型训练MSE下降曲线.png')
122 | plt.close()
123 |
124 |
125 | if __name__ == '__main__':
126 | main()
127 |
--------------------------------------------------------------------------------
/src/ch02/data.csv:
--------------------------------------------------------------------------------
1 | 32.502345269453031,31.70700584656992
2 | 53.426804033275019,68.77759598163891
3 | 61.530358025636438,62.562382297945803
4 | 47.475639634786098,71.546632233567777
5 | 59.813207869512318,87.230925133687393
6 | 55.142188413943821,78.211518270799232
7 | 52.211796692214001,79.64197304980874
8 | 39.299566694317065,59.171489321869508
9 | 48.10504169176825,75.331242297063056
10 | 52.550014442733818,71.300879886850353
11 | 45.419730144973755,55.165677145959123
12 | 54.351634881228918,82.478846757497919
13 | 44.164049496773352,62.008923245725825
14 | 58.16847071685779,75.392870425994957
15 | 56.727208057096611,81.43619215887864
16 | 48.955888566093719,60.723602440673965
17 | 44.687196231480904,82.892503731453715
18 | 60.297326851333466,97.379896862166078
19 | 45.618643772955828,48.847153317355072
20 | 38.816817537445637,56.877213186268506
21 | 66.189816606752601,83.878564664602763
22 | 65.41605174513407,118.59121730252249
23 | 47.48120860786787,57.251819462268969
24 | 41.57564261748702,51.391744079832307
25 | 51.84518690563943,75.380651665312357
26 | 59.370822011089523,74.765564032151374
27 | 57.31000343834809,95.455052922574737
28 | 63.615561251453308,95.229366017555307
29 | 46.737619407976972,79.052406169565586
30 | 50.556760148547767,83.432071421323712
31 | 52.223996085553047,63.358790317497878
32 | 35.567830047746632,41.412885303700563
33 | 42.436476944055642,76.617341280074044
34 | 58.16454011019286,96.769566426108199
35 | 57.504447615341789,74.084130116602523
36 | 45.440530725319981,66.588144414228594
37 | 61.89622268029126,77.768482417793024
38 | 33.093831736163963,50.719588912312084
39 | 36.436009511386871,62.124570818071781
40 | 37.675654860850742,60.810246649902211
41 | 44.555608383275356,52.682983366387781
42 | 43.318282631865721,58.569824717692867
43 | 50.073145632289034,82.905981485070512
44 | 43.870612645218372,61.424709804339123
45 | 62.997480747553091,115.24415280079529
46 | 32.669043763467187,45.570588823376085
47 | 40.166899008703702,54.084054796223612
48 | 53.575077531673656,87.994452758110413
49 | 33.864214971778239,52.725494375900425
50 | 64.707138666121296,93.576118692658241
51 | 38.119824026822805,80.166275447370964
52 | 44.502538064645101,65.101711570560326
53 | 40.599538384552318,65.562301260400375
54 | 41.720676356341293,65.280886920822823
55 | 51.088634678336796,73.434641546324301
56 | 55.078095904923202,71.13972785861894
57 | 41.377726534895203,79.102829683549857
58 | 62.494697427269791,86.520538440347153
59 | 49.203887540826003,84.742697807826218
60 | 41.102685187349664,59.358850248624933
61 | 41.182016105169822,61.684037524833627
62 | 50.186389494880601,69.847604158249183
63 | 52.378446219236217,86.098291205774103
64 | 50.135485486286122,59.108839267699643
65 | 33.644706006191782,69.89968164362763
66 | 39.557901222906828,44.862490711164398
67 | 56.130388816875467,85.498067778840223
68 | 57.362052133238237,95.536686846467219
69 | 60.269214393997906,70.251934419771587
70 | 35.678093889410732,52.721734964774988
71 | 31.588116998132829,50.392670135079896
72 | 53.66093226167304,63.642398775657753
73 | 46.682228649471917,72.247251068662365
74 | 43.107820219102464,57.812512976181402
75 | 70.34607561504933,104.25710158543822
76 | 44.492855880854073,86.642020318822006
77 | 57.50453330326841,91.486778000110135
78 | 36.930076609191808,55.231660886212836
79 | 55.805733357942742,79.550436678507609
80 | 38.954769073377065,44.847124242467601
81 | 56.901214702247074,80.207523139682763
82 | 56.868900661384046,83.14274979204346
83 | 34.33312470421609,55.723489260543914
84 | 59.04974121466681,77.634182511677864
85 | 57.788223993230673,99.051414841748269
86 | 54.282328705967409,79.120646274680027
87 | 51.088719898979143,69.588897851118475
88 | 50.282836348230731,69.510503311494389
89 | 44.211741752090113,73.687564318317285
90 | 38.005488008060688,61.366904537240131
91 | 32.940479942618296,67.170655768995118
92 | 53.691639571070056,85.668203145001542
93 | 68.76573426962166,114.85387123391394
94 | 46.230966498310252,90.123572069967423
95 | 68.319360818255362,97.919821035242848
96 | 50.030174340312143,81.536990783015028
97 | 49.239765342753763,72.111832469615663
98 | 50.039575939875988,85.232007342325673
99 | 48.149858891028863,66.224957888054632
100 | 25.128484647772304,53.454394214850524
--------------------------------------------------------------------------------
/src/ch02/模型训练MSE下降曲线.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch02/模型训练MSE下降曲线.png
--------------------------------------------------------------------------------
/src/ch03/3.8-non-linear-nn.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 3.8-non-linear-nn.py
6 | @time: 2020/2/14 20:23
7 | @desc: 3.8 手写数字图片识别体验的代码
8 | """
9 |
10 | import os
11 |
12 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
13 |
14 | import matplotlib.pyplot as plt
15 | import tensorflow as tf
16 | from tensorflow import keras
17 | from tensorflow.keras import layers, optimizers, datasets
18 |
19 | plt.rcParams['font.size'] = 16
20 | plt.rcParams['font.family'] = ['STKaiti']
21 | plt.rcParams['axes.unicode_minus'] = False
22 |
23 | # 加载 MNIST 数据集
24 | (x, y), (x_val, y_val) = datasets.mnist.load_data()
25 | # 转换为浮点张量, 并缩放到-1~1
26 | x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
27 | # 转换为整形张量
28 | y = tf.convert_to_tensor(y, dtype=tf.int32)
29 | # one-hot 编码
30 | y = tf.one_hot(y, depth=10)
31 | print(x.shape, y.shape)
32 | # 构建数据集对象
33 | train_dataset = tf.data.Dataset.from_tensor_slices((x, y))
34 | # 批量训练
35 | train_dataset = train_dataset.batch(200)
36 |
37 | # 利用 Sequential 容器封装 3 个网络层,前网络层的输出默认作为下一层的输入
38 | model = keras.Sequential([
39 | layers.Dense(512, activation='relu'),
40 | layers.Dense(256, activation='relu'),
41 | layers.Dense(10)])
42 |
43 | optimizer = optimizers.SGD(learning_rate=0.001)
44 |
45 |
46 | def train_epoch(epoch):
47 | # Step4.loop
48 | for step, (x, y) in enumerate(train_dataset):
49 | # 构建梯度记录环境
50 | with tf.GradientTape() as tape:
51 | # [b, 28, 28] => [b, 784]
52 | x = tf.reshape(x, (-1, 28 * 28))
53 | # Step1. 得到模型输出 output [b, 784] => [b, 10]
54 | out = model(x)
55 | # Step2. compute loss
56 | # 计算每个样本的平均误差, [b]
57 | loss = tf.reduce_sum(tf.square(out - y)) / x.shape[0]
58 |
59 | # Step3. optimize and update w1, w2, w3, b1, b2, b3
60 | # 计算参数的梯度 w1, w2, w3, b1, b2, b3
61 | grads = tape.gradient(loss, model.trainable_variables)
62 | # w' = w - lr * grad
63 | # 更新网络参数
64 | optimizer.apply_gradients(zip(grads, model.trainable_variables))
65 |
66 | if step % 100 == 0:
67 | print(epoch, step, 'loss:', loss.numpy())
68 |
69 | return loss.numpy()
70 |
71 |
72 | def train():
73 | losses = []
74 |
75 | for epoch in range(50):
76 | loss = train_epoch(epoch)
77 | losses.append(loss)
78 |
79 | x = [i for i in range(0, 50)]
80 | # 绘制曲线
81 | plt.plot(x, losses, color='blue', marker='s', label='训练误差')
82 | plt.xlabel('Epoch')
83 | plt.ylabel('MSE')
84 | plt.legend()
85 | plt.savefig('MNIST数据集的训练误差曲线.png')
86 | plt.close()
87 |
88 |
89 | if __name__ == '__main__':
90 | train()
91 |
--------------------------------------------------------------------------------
/src/ch03/MNIST数据集的训练误差曲线.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch03/MNIST数据集的训练误差曲线.png
--------------------------------------------------------------------------------
/src/ch04/4.10-forward-prop.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 4.10-forward-prop.py
6 | @time: 2020/2/14 23:47
7 | @desc: 4.10 前向传播实战的示例代码
8 | """
9 |
10 | import matplotlib.pyplot as plt
11 | import tensorflow as tf
12 | import tensorflow.keras.datasets as datasets
13 |
14 | plt.rcParams['font.size'] = 16
15 | plt.rcParams['font.family'] = ['STKaiti']
16 | plt.rcParams['axes.unicode_minus'] = False
17 |
18 |
19 | def load_data():
20 | # 加载 MNIST 数据集
21 | (x, y), (x_val, y_val) = datasets.mnist.load_data()
22 | # 转换为浮点张量, 并缩放到-1~1
23 | x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
24 | # 转换为整形张量
25 | y = tf.convert_to_tensor(y, dtype=tf.int32)
26 | # one-hot 编码
27 | y = tf.one_hot(y, depth=10)
28 |
29 | # 改变视图, [b, 28, 28] => [b, 28*28]
30 | x = tf.reshape(x, (-1, 28 * 28))
31 |
32 | # 构建数据集对象
33 | train_dataset = tf.data.Dataset.from_tensor_slices((x, y))
34 | # 批量训练
35 | train_dataset = train_dataset.batch(200)
36 | return train_dataset
37 |
38 |
39 | def init_paramaters():
40 | # 每层的张量都需要被优化,故使用 Variable 类型,并使用截断的正太分布初始化权值张量
41 | # 偏置向量初始化为 0 即可
42 | # 第一层的参数
43 | w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
44 | b1 = tf.Variable(tf.zeros([256]))
45 | # 第二层的参数
46 | w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))
47 | b2 = tf.Variable(tf.zeros([128]))
48 | # 第三层的参数
49 | w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))
50 | b3 = tf.Variable(tf.zeros([10]))
51 | return w1, b1, w2, b2, w3, b3
52 |
53 |
54 | def train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr=0.001):
55 | for step, (x, y) in enumerate(train_dataset):
56 | with tf.GradientTape() as tape:
57 | # 第一层计算, [b, 784]@[784, 256] + [256] => [b, 256] + [256] => [b,256] + [b, 256]
58 | h1 = x @ w1 + tf.broadcast_to(b1, (x.shape[0], 256))
59 | h1 = tf.nn.relu(h1) # 通过激活函数
60 |
61 | # 第二层计算, [b, 256] => [b, 128]
62 | h2 = h1 @ w2 + b2
63 | h2 = tf.nn.relu(h2)
64 | # 输出层计算, [b, 128] => [b, 10]
65 | out = h2 @ w3 + b3
66 |
67 | # 计算网络输出与标签之间的均方差, mse = mean(sum(y-out)^2)
68 | # [b, 10]
69 | loss = tf.square(y - out)
70 | # 误差标量, mean: scalar
71 | loss = tf.reduce_mean(loss)
72 |
73 | # 自动梯度,需要求梯度的张量有[w1, b1, w2, b2, w3, b3]
74 | grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
75 |
76 | # 梯度更新, assign_sub 将当前值减去参数值,原地更新
77 | w1.assign_sub(lr * grads[0])
78 | b1.assign_sub(lr * grads[1])
79 | w2.assign_sub(lr * grads[2])
80 | b2.assign_sub(lr * grads[3])
81 | w3.assign_sub(lr * grads[4])
82 | b3.assign_sub(lr * grads[5])
83 |
84 | if step % 100 == 0:
85 | print(epoch, step, 'loss:', loss.numpy())
86 |
87 | return loss.numpy()
88 |
89 |
90 | def train(epochs):
91 | losses = []
92 | train_dataset = load_data()
93 | w1, b1, w2, b2, w3, b3 = init_paramaters()
94 | for epoch in range(epochs):
95 | loss = train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr=0.001)
96 | losses.append(loss)
97 |
98 | x = [i for i in range(0, epochs)]
99 | # 绘制曲线
100 | plt.plot(x, losses, color='blue', marker='s', label='训练')
101 | plt.xlabel('Epoch')
102 | plt.ylabel('MSE')
103 | plt.legend()
104 | plt.savefig('MNIST数据集的前向传播训练误差曲线.png')
105 | plt.close()
106 |
107 |
108 | if __name__ == '__main__':
109 | train(epochs=20)
110 |
--------------------------------------------------------------------------------
/src/ch04/MNIST数据集的前向传播训练误差曲线.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch04/MNIST数据集的前向传播训练误差曲线.png
--------------------------------------------------------------------------------
/src/ch05/5.8-mnist-tensorflow.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 5.8-mnist-tensorflow.py
6 | @time: 2020/2/18 14:25
7 | @desc: 5.8 MNIST测试实战代码
8 | """
9 | import os
10 |
11 | import tensorflow as tf
12 | from matplotlib import pyplot as plt
13 | from tensorflow.keras import datasets
14 |
15 | # Default parameters for plots
16 | plt.rcParams['font.size'] = 16
17 | plt.rcParams['font.family'] = ['STKaiti']
18 | plt.rcParams['axes.unicode_minus'] = False
19 |
20 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
21 |
22 |
23 | def preprocess(x, y):
24 | # [b, 28, 28], [b]
25 | print(x.shape, y.shape)
26 | x = tf.cast(x, dtype=tf.float32) / 255.
27 | x = tf.reshape(x, [-1, 28 * 28])
28 | y = tf.cast(y, dtype=tf.int32)
29 | y = tf.one_hot(y, depth=10)
30 |
31 | return x, y
32 |
33 |
34 | def load_dataset():
35 | (x, y), (x_test, y_test) = datasets.mnist.load_data()
36 | print('x:', x.shape, 'y:', y.shape, 'x test:', x_test.shape, 'y test:', y_test)
37 |
38 | batchsz = 512
39 | train_db = tf.data.Dataset.from_tensor_slices((x, y))
40 | train_db = train_db.shuffle(1000)
41 | train_db = train_db.batch(batchsz)
42 | train_db = train_db.map(preprocess)
43 | train_db = train_db.repeat(20)
44 |
45 | test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
46 | test_db = test_db.shuffle(1000).batch(batchsz).map(preprocess)
47 | x, y = next(iter(train_db))
48 | print('train sample:', x.shape, y.shape)
49 |
50 | return train_db, test_db
51 |
52 |
53 | def init_parameters():
54 | # 784 => 512
55 | w1, b1 = tf.Variable(tf.random.normal([784, 256], stddev=0.1)), tf.Variable(tf.zeros([256]))
56 | # 512 => 256
57 | w2, b2 = tf.Variable(tf.random.normal([256, 128], stddev=0.1)), tf.Variable(tf.zeros([128]))
58 | # 256 => 10
59 | w3, b3 = tf.Variable(tf.random.normal([128, 10], stddev=0.1)), tf.Variable(tf.zeros([10]))
60 | return w1, b1, w2, b2, w3, b3
61 |
62 |
63 | def train(train_db, test_db, w1, b1, w2, b2, w3, b3, lr=1e-2):
64 | accs, losses = [], []
65 | for step, (x, y) in enumerate(train_db):
66 |
67 | # [b, 28, 28] => [b, 784]
68 | x = tf.reshape(x, (-1, 784))
69 |
70 | with tf.GradientTape() as tape:
71 |
72 | # layer1.
73 | h1 = x @ w1 + b1
74 | h1 = tf.nn.relu(h1)
75 | # layer2
76 | h2 = h1 @ w2 + b2
77 | h2 = tf.nn.relu(h2)
78 | # output
79 | out = h2 @ w3 + b3
80 | # out = tf.nn.relu(out)
81 |
82 | # compute loss
83 | # [b, 10] - [b, 10]
84 | loss = tf.square(y - out)
85 | # [b, 10] => scalar
86 | loss = tf.reduce_mean(loss)
87 |
88 | grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
89 | for p, g in zip([w1, b1, w2, b2, w3, b3], grads):
90 | p.assign_sub(lr * g)
91 |
92 | # print
93 | if step % 80 == 0:
94 | print(step, 'loss:', float(loss))
95 | losses.append(float(loss))
96 |
97 | if step % 80 == 0:
98 | # evaluate/test
99 | total, total_correct = 0., 0
100 |
101 | for x, y in test_db:
102 | # layer1.
103 | h1 = x @ w1 + b1
104 | h1 = tf.nn.relu(h1)
105 | # layer2
106 | h2 = h1 @ w2 + b2
107 | h2 = tf.nn.relu(h2)
108 | # output
109 | out = h2 @ w3 + b3
110 | # [b, 10] => [b]
111 | pred = tf.argmax(out, axis=1)
112 | # convert one_hot y to number y
113 | y = tf.argmax(y, axis=1)
114 | # bool type
115 | correct = tf.equal(pred, y)
116 | # bool tensor => int tensor => numpy
117 | total_correct += tf.reduce_sum(tf.cast(correct, dtype=tf.int32)).numpy()
118 | total += x.shape[0]
119 |
120 | print(step, 'Evaluate Acc:', total_correct / total)
121 |
122 | accs.append(total_correct / total)
123 | return accs, losses
124 |
125 |
126 | def main():
127 | train_db, test_db = load_dataset()
128 | w1, b1, w2, b2, w3, b3 = init_parameters()
129 |
130 | accs, losses = train(train_db, test_db, w1, b1, w2, b2, w3, b3)
131 |
132 | plt.figure()
133 | x = [i * 80 for i in range(len(losses))]
134 | plt.plot(x, losses, color='C0', marker='s', label='训练')
135 | plt.ylabel('MSE')
136 | plt.xlabel('Step')
137 | plt.legend()
138 | plt.savefig('MNIST训练误差曲线.svg')
139 | plt.close()
140 |
141 | plt.figure()
142 | plt.plot(x, accs, color='C1', marker='s', label='测试')
143 | plt.ylabel('准确率')
144 | plt.xlabel('Step')
145 | plt.legend()
146 | plt.savefig('MNIST测试准确率曲线.svg')
147 | plt.close()
148 |
149 | if __name__ == '__main__':
150 | main()
151 |
--------------------------------------------------------------------------------
/src/ch06/6.8-auto-mpg-efficency.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 6.8-auto-mpg-efficency.py
6 | @time: 2020/2/24 12:43
7 | @desc: 6.8 汽车油耗预测实战的代码
8 | """
9 |
10 | import os
11 |
12 | import matplotlib.pyplot as plt
13 | import pandas as pd
14 | import seaborn as sns
15 | import tensorflow as tf
16 | from tensorflow import keras
17 | from tensorflow.keras import layers, losses
18 |
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | def load_dataset():
23 | # 在线下载汽车效能数据集
24 | dataset_path = keras.utils.get_file("auto-mpg.data",
25 | "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
26 |
27 | # 效能(公里数每加仑),气缸数,排量,马力,重量
28 | # 加速度,型号年份,产地
29 | column_names = ['MPG', 'Cylinders', 'Displacement', 'Horsepower', 'Weight',
30 | 'Acceleration', 'Model Year', 'Origin']
31 | raw_dataset = pd.read_csv(dataset_path, names=column_names,
32 | na_values="?", comment='\t',
33 | sep=" ", skipinitialspace=True)
34 |
35 | dataset = raw_dataset.copy()
36 | return dataset
37 |
38 |
39 | def preprocess_dataset(dataset):
40 | dataset = dataset.copy()
41 | # 统计空白数据,并清除
42 | dataset = dataset.dropna()
43 |
44 | # 处理类别型数据,其中origin列代表了类别1,2,3,分布代表产地:美国、欧洲、日本
45 | # 其弹出这一列
46 | origin = dataset.pop('Origin')
47 | # 根据origin列来写入新列
48 | dataset['USA'] = (origin == 1) * 1.0
49 | dataset['Europe'] = (origin == 2) * 1.0
50 | dataset['Japan'] = (origin == 3) * 1.0
51 |
52 | # 切分为训练集和测试集
53 | train_dataset = dataset.sample(frac=0.8, random_state=0)
54 | test_dataset = dataset.drop(train_dataset.index)
55 |
56 | return train_dataset, test_dataset
57 |
58 |
59 | def get_train_stats(train_dataset):
60 | # 查看训练集的输入X的统计数据
61 | train_stats = train_dataset.describe()
62 | train_stats.pop("MPG")
63 | train_stats = train_stats.transpose()
64 | return train_stats
65 |
66 |
67 | def norm(x, train_stats):
68 | """
69 | 标准化数据
70 | :param x:
71 | :param train_stats: get_train_stats(train_dataset)
72 | :return:
73 | """
74 | return (x - train_stats['mean']) / train_stats['std']
75 |
76 |
77 | class Network(keras.Model):
78 | # 回归网络
79 | def __init__(self):
80 | super(Network, self).__init__()
81 | # 创建3个全连接层
82 | self.fc1 = layers.Dense(64, activation='relu')
83 | self.fc2 = layers.Dense(64, activation='relu')
84 | self.fc3 = layers.Dense(1)
85 |
86 | def call(self, inputs):
87 | # 依次通过3个全连接层
88 | x = self.fc1(inputs)
89 | x = self.fc2(x)
90 | x = self.fc3(x)
91 |
92 | return x
93 |
94 |
95 | def build_model():
96 | # 创建网络
97 | model = Network()
98 | model.build(input_shape=(4, 9))
99 | model.summary()
100 | return model
101 |
102 |
103 | def train(model, train_db, optimizer, normed_test_data, test_labels):
104 | train_mae_losses = []
105 | test_mae_losses = []
106 | for epoch in range(200):
107 | for step, (x, y) in enumerate(train_db):
108 |
109 | with tf.GradientTape() as tape:
110 | out = model(x)
111 | loss = tf.reduce_mean(losses.MSE(y, out))
112 | mae_loss = tf.reduce_mean(losses.MAE(y, out))
113 |
114 | if step % 10 == 0:
115 | print(epoch, step, float(loss))
116 |
117 | grads = tape.gradient(loss, model.trainable_variables)
118 | optimizer.apply_gradients(zip(grads, model.trainable_variables))
119 |
120 | train_mae_losses.append(float(mae_loss))
121 | out = model(tf.constant(normed_test_data.values))
122 | test_mae_losses.append(tf.reduce_mean(losses.MAE(test_labels, out)))
123 |
124 | return train_mae_losses, test_mae_losses
125 |
126 |
127 | def plot(train_mae_losses, test_mae_losses):
128 | plt.figure()
129 | plt.xlabel('Epoch')
130 | plt.ylabel('MAE')
131 | plt.plot(train_mae_losses, label='Train')
132 | plt.plot(test_mae_losses, label='Test')
133 | plt.legend()
134 | # plt.ylim([0,10])
135 | plt.legend()
136 | plt.savefig('MEA变化曲线.svg')
137 |
138 |
139 | def main():
140 | dataset = load_dataset()
141 | train_dataset, test_dataset = preprocess_dataset(dataset)
142 | # 统计数据
143 | sns_plot = sns.pairplot(train_dataset[["Cylinders", "Displacement", "Weight", "MPG"]],
144 | diag_kind="kde")
145 | sns_plot.savefig("特征之间的两两分布.svg")
146 |
147 | train_stats = get_train_stats(train_dataset)
148 |
149 | # 移动MPG油耗效能这一列为真实标签Y
150 | train_labels = train_dataset.pop('MPG')
151 | test_labels = test_dataset.pop('MPG')
152 |
153 | # 进行标准化
154 | normed_train_data = norm(train_dataset, train_stats)
155 | normed_test_data = norm(test_dataset, train_stats)
156 |
157 | model = build_model()
158 | optimizer = tf.keras.optimizers.RMSprop(0.001)
159 | train_db = tf.data.Dataset.from_tensor_slices((normed_train_data.values, train_labels.values))
160 | train_db = train_db.shuffle(100).batch(32)
161 |
162 | train_mae_losses, test_mae_losses = train(model, train_db, optimizer, normed_test_data, test_labels)
163 | plot(train_mae_losses, test_mae_losses)
164 |
165 |
166 | if __name__ == '__main__':
167 | main()
168 |
--------------------------------------------------------------------------------
/src/ch07/7.9-backward-prop.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 7.9-backward-prop.py
6 | @time: 2020/2/24 17:32
7 | @desc: 7.9 反向传播算法实战的代码
8 | """
9 |
10 | import matplotlib.pyplot as plt
11 | import numpy as np
12 | import seaborn as sns
13 | from sklearn.datasets import make_moons
14 | from sklearn.model_selection import train_test_split
15 |
16 | plt.rcParams['font.size'] = 16
17 | plt.rcParams['font.family'] = ['STKaiti']
18 | plt.rcParams['axes.unicode_minus'] = False
19 |
20 |
21 | def load_dataset():
22 | # 采样点数
23 | N_SAMPLES = 2000
24 | # 测试数量比率
25 | TEST_SIZE = 0.3
26 | # 利用工具函数直接生成数据集
27 | X, y = make_moons(n_samples=N_SAMPLES, noise=0.2, random_state=100)
28 | # 将 2000 个点按着 7:3 分割为训练集和测试集
29 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=42)
30 | return X, y, X_train, X_test, y_train, y_test
31 |
32 |
33 | def make_plot(X, y, plot_name, XX=None, YY=None, preds=None, dark=False):
34 | # 绘制数据集的分布, X 为 2D 坐标, y 为数据点的标签
35 | if (dark):
36 | plt.style.use('dark_background')
37 | else:
38 | sns.set_style("whitegrid")
39 | plt.figure(figsize=(16, 12))
40 | axes = plt.gca()
41 | axes.set(xlabel="$x_1$", ylabel="$x_2$")
42 | plt.title(plot_name, fontsize=30)
43 | plt.subplots_adjust(left=0.20)
44 | plt.subplots_adjust(right=0.80)
45 | if XX is not None and YY is not None and preds is not None:
46 | plt.contourf(XX, YY, preds.reshape(XX.shape), 25, alpha=1, cmap=plt.cm.Spectral)
47 | plt.contour(XX, YY, preds.reshape(XX.shape), levels=[.5], cmap="Greys", vmin=0, vmax=.6)
48 | # 绘制散点图,根据标签区分颜色
49 | plt.scatter(X[:, 0], X[:, 1], c=y.ravel(), s=40, cmap=plt.cm.Spectral, edgecolors='none')
50 | plt.savefig('数据集分布.svg')
51 | plt.close()
52 |
53 |
54 | class Layer:
55 | # 全连接网络层
56 | def __init__(self, n_input, n_neurons, activation=None, weights=None,
57 | bias=None):
58 | """
59 | :param int n_input: 输入节点数
60 | :param int n_neurons: 输出节点数
61 | :param str activation: 激活函数类型
62 | :param weights: 权值张量,默认类内部生成
63 | :param bias: 偏置,默认类内部生成
64 | """
65 | # 通过正态分布初始化网络权值,初始化非常重要,不合适的初始化将导致网络不收敛
66 | self.weights = weights if weights is not None else np.random.randn(n_input, n_neurons) * np.sqrt(1 / n_neurons)
67 | self.bias = bias if bias is not None else np.random.rand(n_neurons) * 0.1
68 | self.activation = activation # 激活函数类型,如’sigmoid’
69 | self.last_activation = None # 激活函数的输出值o
70 | self.error = None # 用于计算当前层的delta 变量的中间变量
71 | self.delta = None # 记录当前层的delta 变量,用于计算梯度
72 |
73 | # 网络层的前向传播函数实现如下,其中last_activation 变量用于保存当前层的输出值:
74 | def activate(self, x):
75 | # 前向传播函数
76 | r = np.dot(x, self.weights) + self.bias # X@W+b
77 | # 通过激活函数,得到全连接层的输出o
78 | self.last_activation = self._apply_activation(r)
79 | return self.last_activation
80 |
81 | # 上述代码中的self._apply_activation 函数实现了不同类型的激活函数的前向计算过程,
82 | # 尽管此处我们只使用Sigmoid 激活函数一种。代码如下:
83 | def _apply_activation(self, r):
84 | # 计算激活函数的输出
85 | if self.activation is None:
86 | return r # 无激活函数,直接返回
87 | # ReLU 激活函数
88 | elif self.activation == 'relu':
89 | return np.maximum(r, 0)
90 | # tanh 激活函数
91 | elif self.activation == 'tanh':
92 | return np.tanh(r)
93 | # sigmoid 激活函数
94 | elif self.activation == 'sigmoid':
95 | return 1 / (1 + np.exp(-r))
96 | return r
97 |
98 | # 针对于不同类型的激活函数,它们的导数计算实现如下:
99 | def apply_activation_derivative(self, r):
100 | # 计算激活函数的导数
101 | # 无激活函数,导数为1
102 | if self.activation is None:
103 | return np.ones_like(r)
104 | # ReLU 函数的导数实现
105 | elif self.activation == 'relu':
106 | grad = np.array(r, copy=True)
107 | grad[r > 0] = 1.
108 | grad[r <= 0] = 0.
109 | return grad
110 | # tanh 函数的导数实现
111 | elif self.activation == 'tanh':
112 | return 1 - r ** 2
113 | # Sigmoid 函数的导数实现
114 | elif self.activation == 'sigmoid':
115 | return r * (1 - r)
116 | return r
117 |
118 |
119 | # 神经网络模型
120 | class NeuralNetwork:
121 | def __init__(self):
122 | self._layers = [] # 网络层对象列表
123 |
124 | def add_layer(self, layer):
125 | # 追加网络层
126 | self._layers.append(layer)
127 |
128 | # 网络的前向传播只需要循环调各个网络层对象的前向计算函数即可,代码如下:
129 | # 前向传播
130 | def feed_forward(self, X):
131 | for layer in self._layers:
132 | # 依次通过各个网络层
133 | X = layer.activate(X)
134 | return X
135 |
136 | def backpropagation(self, X, y, learning_rate):
137 | # 反向传播算法实现
138 | # 前向计算,得到输出值
139 | output = self.feed_forward(X)
140 | for i in reversed(range(len(self._layers))): # 反向循环
141 | layer = self._layers[i] # 得到当前层对象
142 | # 如果是输出层
143 | if layer == self._layers[-1]: # 对于输出层
144 | layer.error = y - output # 计算2 分类任务的均方差的导数
145 | # 关键步骤:计算最后一层的delta,参考输出层的梯度公式
146 | layer.delta = layer.error * layer.apply_activation_derivative(output)
147 | else: # 如果是隐藏层
148 | next_layer = self._layers[i + 1] # 得到下一层对象
149 | layer.error = np.dot(next_layer.weights, next_layer.delta)
150 | # 关键步骤:计算隐藏层的delta,参考隐藏层的梯度公式
151 | layer.delta = layer.error * layer.apply_activation_derivative(layer.last_activation)
152 |
153 | # 循环更新权值
154 | for i in range(len(self._layers)):
155 | layer = self._layers[i]
156 | # o_i 为上一网络层的输出
157 | o_i = np.atleast_2d(X if i == 0 else self._layers[i - 1].last_activation)
158 | # 梯度下降算法,delta 是公式中的负数,故这里用加号
159 | layer.weights += layer.delta * o_i.T * learning_rate
160 |
161 | def train(self, X_train, X_test, y_train, y_test, learning_rate, max_epochs):
162 | # 网络训练函数
163 | # one-hot 编码
164 | y_onehot = np.zeros((y_train.shape[0], 2))
165 | y_onehot[np.arange(y_train.shape[0]), y_train] = 1
166 |
167 | # 将One-hot 编码后的真实标签与网络的输出计算均方误差,并调用反向传播函数更新网络参数,循环迭代训练集1000 遍即可
168 | mses = []
169 | accuracys = []
170 | for i in range(max_epochs + 1): # 训练1000 个epoch
171 | for j in range(len(X_train)): # 一次训练一个样本
172 | self.backpropagation(X_train[j], y_onehot[j], learning_rate)
173 | if i % 10 == 0:
174 | # 打印出MSE Loss
175 | mse = np.mean(np.square(y_onehot - self.feed_forward(X_train)))
176 | mses.append(mse)
177 | accuracy = self.accuracy(self.predict(X_test), y_test.flatten())
178 | accuracys.append(accuracy)
179 | print('Epoch: #%s, MSE: %f' % (i, float(mse)))
180 | # 统计并打印准确率
181 | print('Accuracy: %.2f%%' % (accuracy * 100))
182 | return mses, accuracys
183 |
184 | def predict(self, X):
185 | return self.feed_forward(X)
186 |
187 | def accuracy(self, X, y):
188 | return np.sum(np.equal(np.argmax(X, axis=1), y)) / y.shape[0]
189 |
190 |
191 | def main():
192 | X, y, X_train, X_test, y_train, y_test = load_dataset()
193 | # 调用 make_plot 函数绘制数据的分布,其中 X 为 2D 坐标, y 为标签
194 | make_plot(X, y, "Classification Dataset Visualization ")
195 | plt.show()
196 | nn = NeuralNetwork() # 实例化网络类
197 | nn.add_layer(Layer(2, 25, 'sigmoid')) # 隐藏层 1, 2=>25
198 | nn.add_layer(Layer(25, 50, 'sigmoid')) # 隐藏层 2, 25=>50
199 | nn.add_layer(Layer(50, 25, 'sigmoid')) # 隐藏层 3, 50=>25
200 | nn.add_layer(Layer(25, 2, 'sigmoid')) # 输出层, 25=>2
201 | mses, accuracys = nn.train(X_train, X_test, y_train, y_test, 0.01, 1000)
202 |
203 | x = [i for i in range(0, 101, 10)]
204 |
205 | # 绘制MES曲线
206 | plt.title("MES Loss")
207 | plt.plot(x, mses[:11], color='blue')
208 | plt.xlabel('Epoch')
209 | plt.ylabel('MSE')
210 | plt.savefig('训练误差曲线.svg')
211 | plt.close()
212 |
213 | # 绘制Accuracy曲线
214 | plt.title("Accuracy")
215 | plt.plot(x, accuracys[:11], color='blue')
216 | plt.xlabel('Epoch')
217 | plt.ylabel('Accuracy')
218 | plt.savefig('网络测试准确率.svg')
219 | plt.close()
220 |
221 |
222 | if __name__ == '__main__':
223 | main()
224 |
--------------------------------------------------------------------------------
/src/ch08/8.2-model-assembly-training-and-testing.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 8.2-model-assembly-training-and-testing.py
6 | @time: 2020/2/25 13:37
7 | @desc: 8.2 模型装配、训练与测试的示例代码,
8 | 由于书中得到的准确值太差了,笔者调整了代码,使得准确率提高了97%
9 | """
10 |
11 | import matplotlib.pyplot as plt
12 | import tensorflow as tf
13 | from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
14 |
15 | plt.rcParams['font.size'] = 16
16 | plt.rcParams['font.family'] = ['STKaiti']
17 | plt.rcParams['axes.unicode_minus'] = False
18 |
19 |
20 | def build_network():
21 | # 创建 5 层的全连接网络
22 | network = Sequential([layers.Flatten(input_shape=(28, 28)),
23 | layers.Dense(256, activation='relu'),
24 | layers.Dense(128, activation='relu'),
25 | layers.Dense(64, activation='relu'),
26 | layers.Dense(32, activation='relu'),
27 | layers.Dense(10)])
28 | network.summary()
29 |
30 | # 模型装配
31 | # 采用 Adam 优化器,学习率为 0.01;采用交叉熵损失函数,包含 Softmax
32 | # kears sparse_categorical_crossentropy说明:
33 | # from_logits=False,output为经过softmax输出的概率值。
34 | # from_logits=True,output为经过网络直接输出的logits张量。
35 | network.compile(optimizer=optimizers.Adam(learning_rate=0.01),
36 | loss=losses.CategoricalCrossentropy(from_logits=True),
37 | metrics=['accuracy'] # 设置测量指标为准确率
38 | )
39 |
40 | return network
41 |
42 |
43 | def preprocess(x, y):
44 | # [b, 28, 28], [b]
45 | x = tf.cast(x, dtype=tf.float32) / 255.
46 | y = tf.cast(y, dtype=tf.int32)
47 | y = tf.one_hot(y, depth=10)
48 |
49 | return x, y
50 |
51 |
52 | def load_dataset():
53 | (x, y), (x_test, y_test) = datasets.mnist.load_data()
54 |
55 | batchsz = 512
56 | train_db = tf.data.Dataset.from_tensor_slices((x, y))
57 | train_db = train_db.shuffle(1000).map(preprocess).batch(batchsz)
58 |
59 | test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
60 | test_db = test_db.shuffle(1000).map(preprocess).batch(batchsz)
61 |
62 | return train_db, test_db
63 |
64 |
65 | def train(network, train_db, test_db, epochs=5):
66 | # 指定训练集为 train_db,验证集为 val_db,训练 5 个 epochs,每 2 个 epoch 验证一次
67 | # 返回训练轨迹信息保存在 history 对象中
68 | history = network.fit(train_db, epochs=epochs, validation_data=test_db, validation_freq=2)
69 |
70 | print(history.history)
71 | return network, history
72 |
73 |
74 | def test_one_data(network, test_db):
75 | # 加载一个 batch 的测试数据
76 | x, y = next(iter(test_db))
77 | print('predict x:', x.shape) # 打印当前 batch 的形状
78 | out = network.predict(x) # 模型预测,预测结果保存在 out 中
79 | print(out)
80 |
81 |
82 | def test_model(network, test_db):
83 | # 模型测试,测试在 db_test 上的性能表现
84 | network.evaluate(test_db)
85 |
86 |
87 | def main():
88 | epochs = 30
89 | train_db, test_db = load_dataset()
90 | network = build_network()
91 | network, history = train(network, train_db, test_db, epochs)
92 | test_one_data(network, test_db)
93 | test_model(network, test_db)
94 |
95 | x = range(epochs)
96 |
97 | plt.figure(figsize=(10, 6))
98 | plt.subplots_adjust(wspace=0.5)
99 | plt.subplot(1, 2, 1)
100 | # 绘制MES曲线
101 | plt.title("训练误差曲线")
102 | plt.plot(x, history.history['loss'], color='blue')
103 | plt.xlabel('Epoch')
104 | plt.ylabel('MSE')
105 |
106 | # 绘制Accuracy曲线
107 | plt.subplot(1, 2, 2)
108 | plt.title("准确率")
109 | plt.plot(x, history.history['accuracy'], color='blue')
110 | plt.xlabel('Epoch')
111 | plt.ylabel('Accuracy')
112 |
113 | plt.savefig('模型性能.png')
114 | plt.close()
115 |
116 |
117 | if __name__ == '__main__':
118 | main()
119 |
--------------------------------------------------------------------------------
/src/ch08/checkpoint:
--------------------------------------------------------------------------------
1 | model_checkpoint_path: "weights.ckpt"
2 | all_model_checkpoint_paths: "weights.ckpt"
3 |
--------------------------------------------------------------------------------
/src/ch08/model-savedmodel/saved_model.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/model-savedmodel/saved_model.pb
--------------------------------------------------------------------------------
/src/ch08/model-savedmodel/variables/variables.data-00000-of-00002:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/model-savedmodel/variables/variables.data-00000-of-00002
--------------------------------------------------------------------------------
/src/ch08/model-savedmodel/variables/variables.data-00001-of-00002:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/model-savedmodel/variables/variables.data-00001-of-00002
--------------------------------------------------------------------------------
/src/ch08/model-savedmodel/variables/variables.index:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/model-savedmodel/variables/variables.index
--------------------------------------------------------------------------------
/src/ch08/model.h5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/model.h5
--------------------------------------------------------------------------------
/src/ch08/weights.ckpt.data-00000-of-00002:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/weights.ckpt.data-00000-of-00002
--------------------------------------------------------------------------------
/src/ch08/weights.ckpt.data-00001-of-00002:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/weights.ckpt.data-00001-of-00002
--------------------------------------------------------------------------------
/src/ch08/weights.ckpt.index:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/weights.ckpt.index
--------------------------------------------------------------------------------
/src/ch08/模型性能.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch08/模型性能.png
--------------------------------------------------------------------------------
/src/ch09/9.8-over-fitting-and-under-fitting.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 9.8-over-fitting-and-under-fitting.py
6 | @time: 2020/2/25 21:14
7 | @desc: 9.8 过拟合问题实战的代码
8 | from mpl_toolkits.mplot3d import Axes3D 这个必须添加,解决3d报错问题
9 | """
10 |
11 | import matplotlib.pyplot as plt
12 | # 导入数据集生成工具
13 | import numpy as np
14 | import seaborn as sns
15 | from sklearn.datasets import make_moons
16 | from sklearn.model_selection import train_test_split
17 | from tensorflow.keras import layers, Sequential, regularizers
18 | from mpl_toolkits.mplot3d import Axes3D
19 |
20 | plt.rcParams['font.size'] = 16
21 | plt.rcParams['font.family'] = ['STKaiti']
22 | plt.rcParams['axes.unicode_minus'] = False
23 |
24 | OUTPUT_DIR = 'output_dir'
25 | N_EPOCHS = 500
26 |
27 |
28 | def load_dataset():
29 | # 采样点数
30 | N_SAMPLES = 1000
31 | # 测试数量比率
32 | TEST_SIZE = None
33 |
34 | # 从 moon 分布中随机采样 1000 个点,并切分为训练集-测试集
35 | X, y = make_moons(n_samples=N_SAMPLES, noise=0.25, random_state=100)
36 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=42)
37 | return X, y, X_train, X_test, y_train, y_test
38 |
39 |
40 | def make_plot(X, y, plot_name, file_name, XX=None, YY=None, preds=None, dark=False, output_dir=OUTPUT_DIR):
41 | # 绘制数据集的分布, X 为 2D 坐标, y 为数据点的标签
42 | if dark:
43 | plt.style.use('dark_background')
44 | else:
45 | sns.set_style("whitegrid")
46 | axes = plt.gca()
47 | axes.set_xlim([-2, 3])
48 | axes.set_ylim([-1.5, 2])
49 | axes.set(xlabel="$x_1$", ylabel="$x_2$")
50 | plt.title(plot_name, fontsize=20, fontproperties='SimHei')
51 | plt.subplots_adjust(left=0.20)
52 | plt.subplots_adjust(right=0.80)
53 | if XX is not None and YY is not None and preds is not None:
54 | plt.contourf(XX, YY, preds.reshape(XX.shape), 25, alpha=0.08, cmap=plt.cm.Spectral)
55 | plt.contour(XX, YY, preds.reshape(XX.shape), levels=[.5], cmap="Greys", vmin=0, vmax=.6)
56 | # 绘制散点图,根据标签区分颜色m=markers
57 | markers = ['o' if i == 1 else 's' for i in y.ravel()]
58 | mscatter(X[:, 0], X[:, 1], c=y.ravel(), s=20, cmap=plt.cm.Spectral, edgecolors='none', m=markers, ax=axes)
59 | # 保存矢量图
60 | plt.savefig(output_dir + '/' + file_name)
61 | plt.close()
62 |
63 |
64 | def mscatter(x, y, ax=None, m=None, **kw):
65 | import matplotlib.markers as mmarkers
66 | if not ax: ax = plt.gca()
67 | sc = ax.scatter(x, y, **kw)
68 | if (m is not None) and (len(m) == len(x)):
69 | paths = []
70 | for marker in m:
71 | if isinstance(marker, mmarkers.MarkerStyle):
72 | marker_obj = marker
73 | else:
74 | marker_obj = mmarkers.MarkerStyle(marker)
75 | path = marker_obj.get_path().transformed(
76 | marker_obj.get_transform())
77 | paths.append(path)
78 | sc.set_paths(paths)
79 | return sc
80 |
81 |
82 | def network_layers_influence(X_train, y_train):
83 | # 构建 5 种不同层数的网络
84 | for n in range(5):
85 | # 创建容器
86 | model = Sequential()
87 | # 创建第一层
88 | model.add(layers.Dense(8, input_dim=2, activation='relu'))
89 | # 添加 n 层,共 n+2 层
90 | for _ in range(n):
91 | model.add(layers.Dense(32, activation='relu'))
92 | # 创建最末层
93 | model.add(layers.Dense(1, activation='sigmoid'))
94 | # 模型装配与训练
95 | model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
96 | model.fit(X_train, y_train, epochs=N_EPOCHS, verbose=1)
97 | # 绘制不同层数的网络决策边界曲线
98 | # 可视化的 x 坐标范围为[-2, 3]
99 | xx = np.arange(-2, 3, 0.01)
100 | # 可视化的 y 坐标范围为[-1.5, 2]
101 | yy = np.arange(-1.5, 2, 0.01)
102 | # 生成 x-y 平面采样网格点,方便可视化
103 | XX, YY = np.meshgrid(xx, yy)
104 | preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
105 | title = "网络层数:{0}".format(2 + n)
106 | file = "网络容量_%i.png" % (2 + n)
107 | make_plot(X_train, y_train, title, file, XX, YY, preds, output_dir=OUTPUT_DIR + '/network_layers')
108 |
109 |
110 | def dropout_influence(X_train, y_train):
111 | # 构建 5 种不同数量 Dropout 层的网络
112 | for n in range(5):
113 | # 创建容器
114 | model = Sequential()
115 | # 创建第一层
116 | model.add(layers.Dense(8, input_dim=2, activation='relu'))
117 | counter = 0
118 | # 网络层数固定为 5
119 | for _ in range(5):
120 | model.add(layers.Dense(64, activation='relu'))
121 | # 添加 n 个 Dropout 层
122 | if counter < n:
123 | counter += 1
124 | model.add(layers.Dropout(rate=0.5))
125 |
126 | # 输出层
127 | model.add(layers.Dense(1, activation='sigmoid'))
128 | # 模型装配
129 | model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
130 | # 训练
131 | model.fit(X_train, y_train, epochs=N_EPOCHS, verbose=1)
132 | # 绘制不同 Dropout 层数的决策边界曲线
133 | # 可视化的 x 坐标范围为[-2, 3]
134 | xx = np.arange(-2, 3, 0.01)
135 | # 可视化的 y 坐标范围为[-1.5, 2]
136 | yy = np.arange(-1.5, 2, 0.01)
137 | # 生成 x-y 平面采样网格点,方便可视化
138 | XX, YY = np.meshgrid(xx, yy)
139 | preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
140 | title = "无Dropout层" if n == 0 else "{0}层 Dropout层".format(n)
141 | file = "Dropout_%i.png" % n
142 | make_plot(X_train, y_train, title, file, XX, YY, preds, output_dir=OUTPUT_DIR + '/dropout')
143 |
144 |
145 | def build_model_with_regularization(_lambda):
146 | # 创建带正则化项的神经网络
147 | model = Sequential()
148 | model.add(layers.Dense(8, input_dim=2, activation='relu')) # 不带正则化项
149 | # 2-4层均是带 L2 正则化项
150 | model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
151 | model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
152 | model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
153 | # 输出层
154 | model.add(layers.Dense(1, activation='sigmoid'))
155 | model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 模型装配
156 | return model
157 |
158 |
159 | def plot_weights_matrix(model, layer_index, plot_name, file_name, output_dir=OUTPUT_DIR):
160 | # 绘制权值范围函数
161 | # 提取指定层的权值矩阵
162 | weights = model.layers[layer_index].get_weights()[0]
163 | shape = weights.shape
164 | # 生成和权值矩阵等大小的网格坐标
165 | X = np.array(range(shape[1]))
166 | Y = np.array(range(shape[0]))
167 | X, Y = np.meshgrid(X, Y)
168 | # 绘制3D图
169 | fig = plt.figure()
170 | ax = fig.gca(projection='3d')
171 | ax.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
172 | ax.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
173 | ax.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
174 | plt.title(plot_name, fontsize=20, fontproperties='SimHei')
175 | # 绘制权值矩阵范围
176 | ax.plot_surface(X, Y, weights, cmap=plt.get_cmap('rainbow'), linewidth=0)
177 | # 设置坐标轴名
178 | ax.set_xlabel('网格x坐标', fontsize=16, rotation=0, fontproperties='SimHei')
179 | ax.set_ylabel('网格y坐标', fontsize=16, rotation=0, fontproperties='SimHei')
180 | ax.set_zlabel('权值', fontsize=16, rotation=90, fontproperties='SimHei')
181 | # 保存矩阵范围图
182 | plt.savefig(output_dir + "/" + file_name + ".svg")
183 | plt.close(fig)
184 |
185 |
186 | def regularizers_influence(X_train, y_train):
187 | for _lambda in [1e-5, 1e-3, 1e-1, 0.12, 0.13]: # 设置不同的正则化系数
188 | # 创建带正则化项的模型
189 | model = build_model_with_regularization(_lambda)
190 | # 模型训练
191 | model.fit(X_train, y_train, epochs=N_EPOCHS, verbose=1)
192 | # 绘制权值范围
193 | layer_index = 2
194 | plot_title = "正则化系数:{}".format(_lambda)
195 | file_name = "正则化网络权值_" + str(_lambda)
196 | # 绘制网络权值范围图
197 | plot_weights_matrix(model, layer_index, plot_title, file_name, output_dir=OUTPUT_DIR + '/regularizers')
198 | # 绘制不同正则化系数的决策边界线
199 | # 可视化的 x 坐标范围为[-2, 3]
200 | xx = np.arange(-2, 3, 0.01)
201 | # 可视化的 y 坐标范围为[-1.5, 2]
202 | yy = np.arange(-1.5, 2, 0.01)
203 | # 生成 x-y 平面采样网格点,方便可视化
204 | XX, YY = np.meshgrid(xx, yy)
205 | preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
206 | title = "正则化系数:{}".format(_lambda)
207 | file = "正则化_%g.svg" % _lambda
208 | make_plot(X_train, y_train, title, file, XX, YY, preds, output_dir=OUTPUT_DIR + '/regularizers')
209 |
210 |
211 | def main():
212 | X, y, X_train, X_test, y_train, y_test = load_dataset()
213 | # 绘制数据集分布
214 | make_plot(X, y, None, "月牙形状二分类数据集分布.svg")
215 | # 网络层数的影响
216 | network_layers_influence(X_train, y_train)
217 | # Dropout的影响
218 | dropout_influence(X_train, y_train)
219 | # 正则化的影响
220 | regularizers_influence(X_train, y_train)
221 |
222 |
223 | if __name__ == '__main__':
224 | main()
225 |
--------------------------------------------------------------------------------
/src/ch09/lena512color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/lena512color.png
--------------------------------------------------------------------------------
/src/ch09/lena512color.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/lena512color.tiff
--------------------------------------------------------------------------------
/src/ch09/output_dir/dropout/Dropout_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/dropout/Dropout_0.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/dropout/Dropout_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/dropout/Dropout_1.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/dropout/Dropout_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/dropout/Dropout_2.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/dropout/Dropout_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/dropout/Dropout_3.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/dropout/Dropout_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/dropout/Dropout_4.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/network_layers/网络容量_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/network_layers/网络容量_2.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/network_layers/网络容量_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/network_layers/网络容量_3.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/network_layers/网络容量_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/network_layers/网络容量_4.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/network_layers/网络容量_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/network_layers/网络容量_5.png
--------------------------------------------------------------------------------
/src/ch09/output_dir/network_layers/网络容量_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch09/output_dir/network_layers/网络容量_6.png
--------------------------------------------------------------------------------
/src/ch10/10.10-cifar10-vgg13-compile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 10.10-cifar10-vgg13.py
6 | @time: 2020/2/28 1:11
7 | @desc: 10.10 CIFAR10与VGG13实战的代码(装配版本)
8 | """
9 |
10 | import os
11 | import sys
12 | import time
13 |
14 | import matplotlib.pyplot as plt
15 | import tensorflow as tf
16 | from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
17 |
18 | plt.rcParams['font.size'] = 16
19 | plt.rcParams['font.family'] = ['STKaiti']
20 | plt.rcParams['axes.unicode_minus'] = False
21 |
22 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
23 | tf.random.set_seed(2345)
24 |
25 | os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION'] = 'false'
26 | sys.stdout = open('10.10-cifar10-vgg13-output-'+str(time.time())+'.log', mode='w', encoding='utf-8')
27 |
28 | # 获取 GPU 设备列表
29 | gpus = tf.config.experimental.list_physical_devices('GPU')
30 | if gpus:
31 | try:
32 | # 设置 GPU 为增长式占用
33 | for gpu in gpus:
34 | tf.config.experimental.set_memory_growth(gpu, True)
35 | except RuntimeError as e:
36 | # 打印异常
37 | print(e)
38 |
39 |
40 | def load_dataset():
41 | # 在线下载,加载 CIFAR10 数据集
42 | (x, y), (x_test, y_test) = datasets.cifar10.load_data()
43 | # 删除 y 的一个维度, [b,1] => [b]
44 | y = tf.squeeze(y, axis=1)
45 | y_test = tf.squeeze(y_test, axis=1)
46 | # 打印训练集和测试集的形状
47 | print(x.shape, y.shape, x_test.shape, y_test.shape)
48 | # 构建训练集对象,随机打乱,预处理,批量化
49 | train_db = tf.data.Dataset.from_tensor_slices((x, y))
50 | train_db = train_db.shuffle(1000).map(preprocess).batch(128)
51 | # 构建测试集对象,预处理,批量化
52 | test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
53 | test_db = test_db.map(preprocess).batch(64)
54 | # 从训练集中采样一个 Batch,并观察
55 | sample = next(iter(train_db))
56 | print('sample:', sample[0].shape, sample[1].shape, tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))
57 | return train_db, test_db
58 |
59 |
60 | def preprocess(x, y):
61 | # [0~1]
62 | x = 2 * tf.cast(x, dtype=tf.float32) / 255. - 1
63 | y = tf.cast(y, dtype=tf.int32)
64 | y = tf.one_hot(y, depth=10)
65 | return x, y
66 |
67 |
68 | def build_network():
69 | # 先创建包含多网络层的列表
70 | conv_layers = [
71 | # Conv-Conv-Pooling 单元 1
72 | # 64 个 3x3 卷积核, 输入输出同大小
73 | layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
74 | layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
75 | # 高宽减半
76 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
77 |
78 | # Conv-Conv-Pooling 单元 2,输出通道提升至 128,高宽大小减半
79 | layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
80 | layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
81 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
82 |
83 | # Conv-Conv-Pooling 单元 3,输出通道提升至 256,高宽大小减半
84 | layers.Conv2D(256, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
85 | layers.Conv2D(256, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
86 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
87 |
88 | # Conv-Conv-Pooling 单元 4,输出通道提升至 512,高宽大小减半
89 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
90 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
91 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
92 |
93 | # Conv-Conv-Pooling 单元 5,输出通道提升至 512,高宽大小减半
94 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
95 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
96 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same')
97 |
98 | ]
99 |
100 | fc_layers = [
101 | layers.Flatten(),
102 | layers.Dense(256, activation=tf.nn.relu),
103 | layers.Dense(128, activation=tf.nn.relu),
104 | layers.Dense(10, activation=None),
105 | ]
106 |
107 | conv_layers.extend(fc_layers)
108 | network = Sequential(conv_layers)
109 | network.build(input_shape=[None, 32, 32, 3])
110 | network.summary()
111 | network.compile(optimizer=optimizers.Adam(lr=1e-4),
112 | loss=losses.CategoricalCrossentropy(from_logits=True),
113 | metrics=['accuracy'] # 设置测量指标为准确率
114 | )
115 |
116 | return network
117 |
118 |
119 | def train(network, train_db, test_db, epoch_num):
120 | # 指定训练集为train_db,验证集为test_db,每个epoch 验证一次
121 | # 返回训练轨迹信息保存在 history 对象中
122 | history = network.fit(train_db, epochs=epoch_num, validation_data=test_db)
123 |
124 | print(history.history)
125 | return network, history
126 |
127 |
128 | def predict(network, test_db):
129 | # 模型测试,测试在 test_db 上的性能表现
130 | network.evaluate(test_db)
131 |
132 |
133 | def main():
134 | epoch_num = 50
135 |
136 | train_db, test_db = load_dataset()
137 | network = build_network()
138 | network, history = train(network, train_db, test_db, epoch_num)
139 | predict(network, test_db)
140 |
141 | x = range(epoch_num)
142 | plt.title("准确率")
143 | plt.plot(x, history.history['accuracy'], color='blue')
144 | plt.xlabel('Epoch')
145 | plt.ylabel('Accuracy')
146 |
147 | plt.savefig('cifar10-vgg13-accuracy.png')
148 |
149 |
150 | if __name__ == '__main__':
151 | main()
152 |
--------------------------------------------------------------------------------
/src/ch10/10.10-cifar10-vgg13.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 10.10-cifar10-vgg13.py
6 | @time: 2020/2/28 1:11
7 | @desc: 10.10 CIFAR10与VGG13实战的代码
8 | """
9 |
10 | import os
11 |
12 | import matplotlib.pyplot as plt
13 | import tensorflow as tf
14 | from tensorflow.keras import layers, optimizers, datasets, Sequential
15 |
16 | plt.rcParams['font.size'] = 16
17 | plt.rcParams['font.family'] = ['STKaiti']
18 | plt.rcParams['axes.unicode_minus'] = False
19 |
20 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
21 | tf.random.set_seed(2345)
22 |
23 | os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION'] = 'false'
24 |
25 | # 获取 GPU 设备列表
26 | gpus = tf.config.experimental.list_physical_devices('GPU')
27 | if gpus:
28 | try:
29 | # 设置 GPU 为增长式占用
30 | for gpu in gpus:
31 | tf.config.experimental.set_memory_growth(gpu, True)
32 | except RuntimeError as e:
33 | # 打印异常
34 | print(e)
35 |
36 |
37 | def load_dataset():
38 | # 在线下载,加载 CIFAR10 数据集
39 | (x, y), (x_test, y_test) = datasets.cifar10.load_data()
40 | # 删除 y 的一个维度, [b,1] => [b]
41 | y = tf.squeeze(y, axis=1)
42 | y_test = tf.squeeze(y_test, axis=1)
43 | # 打印训练集和测试集的形状
44 | print(x.shape, y.shape, x_test.shape, y_test.shape)
45 | # 构建训练集对象,随机打乱,预处理,批量化
46 | train_db = tf.data.Dataset.from_tensor_slices((x, y))
47 | train_db = train_db.shuffle(1000).map(preprocess).batch(128)
48 | # 构建测试集对象,预处理,批量化
49 | test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
50 | test_db = test_db.map(preprocess).batch(64)
51 | # 从训练集中采样一个 Batch,并观察
52 | sample = next(iter(train_db))
53 | print('sample:', sample[0].shape, sample[1].shape, tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))
54 | return train_db, test_db
55 |
56 |
57 | def preprocess(x, y):
58 | # [0~1]
59 | x = 2 * tf.cast(x, dtype=tf.float32) / 255. - 1
60 | y = tf.cast(y, dtype=tf.int32)
61 | return x, y
62 |
63 |
64 | def build_network():
65 | # 先创建包含多网络层的列表
66 | conv_layers = [ # 5 units of conv + max pooling
67 | # Conv-Conv-Pooling 单元 1
68 | # 64 个 3x3 卷积核, 输入输出同大小
69 | layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
70 | layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
71 | # 高宽减半
72 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
73 |
74 | # Conv-Conv-Pooling 单元 2,输出通道提升至 128,高宽大小减半
75 | layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
76 | layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
77 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
78 |
79 | # Conv-Conv-Pooling 单元 3,输出通道提升至 256,高宽大小减半
80 | layers.Conv2D(256, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
81 | layers.Conv2D(256, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
82 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
83 |
84 | # Conv-Conv-Pooling 单元 4,输出通道提升至 512,高宽大小减半
85 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
86 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
87 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
88 |
89 | # Conv-Conv-Pooling 单元 5,输出通道提升至 512,高宽大小减半
90 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
91 | layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
92 | layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same')
93 |
94 | ]
95 |
96 | # 利用前面创建的层列表构建网络容器
97 | conv_net = Sequential(conv_layers)
98 |
99 | # 创建 3 层全连接层子网络
100 | fc_net = Sequential([
101 | layers.Dense(256, activation=tf.nn.relu),
102 | layers.Dense(128, activation=tf.nn.relu),
103 | layers.Dense(10, activation=None),
104 | ])
105 |
106 | # build2 个子网络,并打印网络参数信息
107 | conv_net.build(input_shape=[None, 32, 32, 3])
108 | fc_net.build(input_shape=[None, 512])
109 | conv_net.summary()
110 | fc_net.summary()
111 |
112 | return conv_net, fc_net
113 |
114 |
115 | def train(conv_net, fc_net, train_db, optimizer, variables, epoch):
116 | for step, (x, y) in enumerate(train_db):
117 | with tf.GradientTape() as tape:
118 | # [b, 32, 32, 3] => [b, 1, 1, 512]
119 | out = conv_net(x)
120 | # flatten, => [b, 512]
121 | out = tf.reshape(out, [-1, 512])
122 | # [b, 512] => [b, 10]
123 | logits = fc_net(out)
124 | # [b] => [b, 10]
125 | y_onehot = tf.one_hot(y, depth=10)
126 | # compute loss
127 | loss = tf.losses.categorical_crossentropy(y_onehot, logits, from_logits=True)
128 | loss = tf.reduce_mean(loss)
129 |
130 | # 对所有参数求梯度
131 | grads = tape.gradient(loss, variables)
132 | # 自动更新
133 | optimizer.apply_gradients(zip(grads, variables))
134 |
135 | if step % 100 == 0:
136 | print(epoch, step, 'loss:', float(loss))
137 |
138 | return conv_net, fc_net
139 |
140 |
141 | def predict(conv_net, fc_net, test_db, epoch):
142 | total_num = 0
143 | total_correct = 0
144 | for x, y in test_db:
145 | out = conv_net(x)
146 | out = tf.reshape(out, [-1, 512])
147 | logits = fc_net(out)
148 | prob = tf.nn.softmax(logits, axis=1)
149 | pred = tf.argmax(prob, axis=1)
150 | pred = tf.cast(pred, dtype=tf.int32)
151 |
152 | correct = tf.cast(tf.equal(pred, y), dtype=tf.int32)
153 | correct = tf.reduce_sum(correct)
154 |
155 | total_num += x.shape[0]
156 | total_correct += int(correct)
157 |
158 | acc = total_correct / total_num
159 | print(epoch, 'acc:', acc)
160 | return acc
161 |
162 |
163 | def main():
164 | epoch_num = 50
165 |
166 | train_db, test_db = load_dataset()
167 | conv_net, fc_net = build_network()
168 | optimizer = optimizers.Adam(lr=1e-4)
169 |
170 | # 列表合并,合并 2 个子网络的参数
171 | variables = conv_net.trainable_variables + fc_net.trainable_variables
172 |
173 | accs = []
174 | for epoch in range(epoch_num):
175 | conv_net, fc_net = train(conv_net, fc_net, train_db, optimizer, variables, epoch)
176 | acc = predict(conv_net, fc_net, test_db, epoch)
177 | accs.append(acc)
178 |
179 | x = range(epoch_num)
180 | plt.title("准确率")
181 | plt.plot(x, accs, color='blue')
182 | plt.xlabel('Epoch')
183 | plt.ylabel('Accuracy')
184 |
185 | plt.savefig('cifar10-vgg13-accuracy.png')
186 |
187 |
188 | if __name__ == '__main__':
189 | main()
190 |
--------------------------------------------------------------------------------
/src/ch10/10.14-cifar10-resnet18.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 10.14-cifar10-resnet18.py
6 | @time: 2020/2/28 12:22
7 | @desc: 10.14 CIFAR10与RESNET18实战的代码
8 | """
9 |
10 | import os
11 |
12 | import matplotlib.pyplot as plt
13 | import tensorflow as tf
14 | from tensorflow.keras import optimizers, datasets
15 |
16 | from src.ch10.resnet import resnet18
17 |
18 | plt.rcParams['font.size'] = 16
19 | plt.rcParams['font.family'] = ['STKaiti']
20 | plt.rcParams['axes.unicode_minus'] = False
21 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
22 | tf.random.set_seed(2345)
23 |
24 | os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION'] = 'false'
25 |
26 | # 获取 GPU 设备列表
27 | gpus = tf.config.experimental.list_physical_devices('GPU')
28 | if gpus:
29 | try:
30 | # 设置 GPU 为增长式占用
31 | for gpu in gpus:
32 | tf.config.experimental.set_memory_growth(gpu, True)
33 | except RuntimeError as e:
34 | # 打印异常
35 | print(e)
36 |
37 |
38 | def preprocess(x, y):
39 | # 将数据映射到-1~1
40 | x = 2 * tf.cast(x, dtype=tf.float32) / 255. - 1
41 | # 类型转换
42 | y = tf.cast(y, dtype=tf.int32)
43 | return x, y
44 |
45 |
46 | def load_dataset():
47 | # 加载数据集
48 | (x, y), (x_test, y_test) = datasets.cifar10.load_data()
49 | # 删除不必要的维度
50 | y = tf.squeeze(y, axis=1)
51 | y_test = tf.squeeze(y_test, axis=1)
52 | print(x.shape, y.shape, x_test.shape, y_test.shape)
53 |
54 | # 构建训练集
55 | train_db = tf.data.Dataset.from_tensor_slices((x, y))
56 | # 随机打散,预处理,批量化
57 | train_db = train_db.shuffle(1000).map(preprocess).batch(64)
58 |
59 | # 构建测试集
60 | test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
61 | # 随机打散,预处理,批量化
62 | test_db = test_db.map(preprocess).batch(64)
63 | # 采样一个样本
64 | sample = next(iter(train_db))
65 | print('sample:', sample[0].shape, sample[1].shape,
66 | tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))
67 | return train_db, test_db
68 |
69 |
70 | def predict(model, test_db):
71 | total_num = 0
72 | total_correct = 0
73 | for x, y in test_db:
74 | logits = model(x)
75 | prob = tf.nn.softmax(logits, axis=1)
76 | pred = tf.argmax(prob, axis=1)
77 | pred = tf.cast(pred, dtype=tf.int32)
78 |
79 | correct = tf.cast(tf.equal(pred, y), dtype=tf.int32)
80 | correct = tf.reduce_sum(correct)
81 |
82 | total_num += x.shape[0]
83 | total_correct += int(correct)
84 |
85 | acc = total_correct / total_num
86 | return acc
87 |
88 |
89 | def train(epoch, model, optimizer, train_db):
90 | for step, (x, y) in enumerate(train_db):
91 |
92 | with tf.GradientTape() as tape:
93 | # [b, 32, 32, 3] => [b, 10],前向传播
94 | logits = model(x)
95 | # [b] => [b, 10],one-hot编码
96 | y_onehot = tf.one_hot(y, depth=10)
97 | # 计算交叉熵
98 | loss = tf.losses.categorical_crossentropy(y_onehot, logits, from_logits=True)
99 | loss = tf.reduce_mean(loss)
100 | # 计算梯度信息
101 | grads = tape.gradient(loss, model.trainable_variables)
102 | # 更新网络参数
103 | optimizer.apply_gradients(zip(grads, model.trainable_variables))
104 |
105 | if step % 50 == 0:
106 | print(epoch, step, 'loss:', float(loss))
107 |
108 | return model
109 |
110 |
111 | def main():
112 | epoch_num = 50
113 |
114 | train_db, test_db = load_dataset()
115 | # [b, 32, 32, 3] => [b, 1, 1, 512]
116 | # ResNet18网络
117 | model = resnet18()
118 | model.build(input_shape=(None, 32, 32, 3))
119 | # 统计网络参数
120 | model.summary()
121 | # 构建优化器
122 | optimizer = optimizers.Adam(lr=1e-4)
123 |
124 | accs = []
125 | # 训练epoch
126 | for epoch in range(epoch_num):
127 | model = train(epoch, model, optimizer, train_db)
128 | acc = predict(model, test_db)
129 | print(epoch, 'acc:', acc)
130 | accs.append(acc)
131 |
132 | x = range(epoch_num)
133 | plt.title("准确率")
134 | plt.plot(x, accs, color='blue')
135 | plt.xlabel('Epoch')
136 | plt.ylabel('Accuracy')
137 |
138 | plt.savefig('cifar10-resnet18-accuracy.png')
139 |
140 |
141 | if __name__ == '__main__':
142 | main()
143 |
--------------------------------------------------------------------------------
/src/ch10/10.4-LeNet-5.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 10.4-LeNet-5.py
6 | @time: 2020/2/27 1:50
7 | @desc: 10.4 LeNet-5实战的代码
8 | """
9 | import tensorflow as tf
10 | # 导入误差计算,优化器模块
11 | from tensorflow.keras import Sequential, layers, losses, datasets, optimizers
12 |
13 | # 获取 GPU 设备列表
14 | gpus = tf.config.experimental.list_physical_devices('GPU')
15 | if gpus:
16 | try:
17 | # 设置 GPU 为增长式占用
18 | for gpu in gpus:
19 | tf.config.experimental.set_memory_growth(gpu, True)
20 | except RuntimeError as e:
21 | # 打印异常
22 | print(e)
23 |
24 |
25 | def preprocess(x, y):
26 | # [b, 28, 28], [b]
27 | print(x.shape, y.shape)
28 | x = tf.cast(x, dtype=tf.float32) / 255.
29 | x = tf.reshape(x, [-1, 28, 28])
30 | y = tf.cast(y, dtype=tf.int64)
31 |
32 | return x, y
33 |
34 |
35 | def load_dataset():
36 | (x, y), (x_test, y_test) = datasets.mnist.load_data()
37 |
38 | batchsz = 128
39 | train_db = tf.data.Dataset.from_tensor_slices((x, y))
40 | train_db = train_db.shuffle(1000).batch(batchsz).map(preprocess)
41 |
42 | test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
43 | test_db = test_db.shuffle(1000).batch(batchsz).map(preprocess)
44 |
45 | return train_db, test_db
46 |
47 |
48 | def build_network():
49 | network = Sequential([ # 网络容器
50 | layers.Conv2D(6, kernel_size=3, strides=1), # 第一个卷积层, 6 个 3x3 卷积核
51 | layers.MaxPooling2D(pool_size=2, strides=2), # 高宽各减半的池化层
52 | layers.ReLU(), # 激活函数
53 | layers.Conv2D(16, kernel_size=3, strides=1), # 第二个卷积层, 16 个 3x3 卷积核
54 | layers.MaxPooling2D(pool_size=2, strides=2), # 高宽各减半的池化层
55 | layers.ReLU(), # 激活函数
56 | layers.Flatten(), # 打平层,方便全连接层处理
57 | layers.Dense(120, activation='relu'), # 全连接层, 120 个节点
58 | layers.Dense(84, activation='relu'), # 全连接层, 84 节点
59 | layers.Dense(10) # 全连接层, 10 个节点
60 | ])
61 | # build 一次网络模型,给输入 X 的形状,其中 4 为随意给的 batchsz
62 | network.build(input_shape=(4, 28, 28, 1))
63 | # 统计网络信息
64 | network.summary()
65 | return network
66 |
67 |
68 | def train(train_db, network, criteon, optimizer, epoch_num):
69 | for epoch in range(epoch_num):
70 | correct, total, loss = 0, 0, 0
71 | for step, (x, y) in enumerate(train_db):
72 | # 构建梯度记录环境
73 | with tf.GradientTape() as tape:
74 | # 插入通道维度, =>[b,28,28,1]
75 | x = tf.expand_dims(x, axis=3)
76 | # 前向计算,获得 10 类别的概率分布, [b, 784] => [b, 10]
77 | out = network(x)
78 | pred = tf.argmax(out, axis=-1)
79 | # 真实标签 one-hot 编码, [b] => [b, 10]
80 | y_onehot = tf.one_hot(y, depth=10)
81 | # 计算交叉熵损失函数,标量
82 | loss += criteon(y_onehot, out)
83 | # 统计预测正确数量
84 | correct += float(tf.reduce_sum(tf.cast(tf.equal(pred, y), tf.float32)))
85 | # 统计预测样本总数
86 | total += x.shape[0]
87 |
88 |
89 | # 自动计算梯度
90 | grads = tape.gradient(loss, network.trainable_variables)
91 | # 自动更新参数
92 | optimizer.apply_gradients(zip(grads, network.trainable_variables))
93 |
94 | print(epoch, 'loss=', float(loss), 'acc=', correct / total)
95 |
96 | return network
97 |
98 |
99 | def predict(test_db, network):
100 | # 记录预测正确的数量,总样本数量
101 | correct, total = 0, 0
102 | for x, y in test_db: # 遍历所有训练集样本
103 | # 插入通道维度, =>[b,28,28,1]
104 | x = tf.expand_dims(x, axis=3)
105 | # 前向计算,获得 10 类别的预测分布, [b, 784] => [b, 10]
106 | out = network(x)
107 | # 真实的流程时先经过 softmax,再 argmax
108 | # 但是由于 softmax 不改变元素的大小相对关系,故省去
109 | pred = tf.argmax(out, axis=-1)
110 | y = tf.cast(y, tf.int64)
111 | # 统计预测正确数量
112 | correct += float(tf.reduce_sum(tf.cast(tf.equal(pred, y), tf.float32)))
113 | # 统计预测样本总数
114 | total += x.shape[0]
115 |
116 | # 计算准确率
117 | print('test acc:', correct / total)
118 |
119 |
120 | def main():
121 | epoch_num = 30
122 |
123 | train_db, test_db = load_dataset()
124 | network = build_network()
125 | # 创建损失函数的类,在实际计算时直接调用类实例即可
126 | criteon = losses.CategoricalCrossentropy(from_logits=True)
127 | optimizer = optimizers.RMSprop(0.001)
128 | network = train(train_db, network, criteon, optimizer, epoch_num)
129 | predict(test_db, network)
130 |
131 |
132 | if __name__ == '__main__':
133 | main()
134 |
--------------------------------------------------------------------------------
/src/ch10/ch10-卷积神经网络.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "collapsed": true
7 | },
8 | "source": [
9 | "# 卷积神经网络\n",
10 | "\n",
11 | "## 全连接网络的问题"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": 1,
17 | "metadata": {},
18 | "outputs": [
19 | {
20 | "name": "stdout",
21 | "output_type": "stream",
22 | "text": [
23 | "Model: \"sequential\"\n",
24 | "_________________________________________________________________\n",
25 | "Layer (type) Output Shape Param # \n",
26 | "=================================================================\n",
27 | "dense (Dense) multiple 200960 \n",
28 | "_________________________________________________________________\n",
29 | "dense_1 (Dense) multiple 65792 \n",
30 | "_________________________________________________________________\n",
31 | "dense_2 (Dense) multiple 65792 \n",
32 | "_________________________________________________________________\n",
33 | "dense_3 (Dense) multiple 2570 \n",
34 | "=================================================================\n",
35 | "Total params: 335,114\n",
36 | "Trainable params: 335,114\n",
37 | "Non-trainable params: 0\n",
38 | "_________________________________________________________________\n"
39 | ]
40 | }
41 | ],
42 | "source": [
43 | "import tensorflow as tf\n",
44 | "from tensorflow import keras\n",
45 | "from tensorflow.keras import layers,Sequential,losses,optimizers,datasets\n",
46 | "\n",
47 | "# 获取所有 GPU 设备列表\n",
48 | "gpus = tf.config.experimental.list_physical_devices('GPU0')\n",
49 | "if gpus:\n",
50 | " try:\n",
51 | " # 设置 GPU 为增长式占用\n",
52 | " for gpu in gpus:\n",
53 | " tf.config.experimental.set_memory_growth(gpu, True)\n",
54 | " except RuntimeError as e:\n",
55 | " # 打印异常\n",
56 | " print(e)\n",
57 | "\n",
58 | "# 创建 4 层全连接网络\n",
59 | "model = keras.Sequential([\n",
60 | " layers.Dense(256, activation='relu'),\n",
61 | " layers.Dense(256, activation='relu'),\n",
62 | " layers.Dense(256, activation='relu'),\n",
63 | " layers.Dense(10),\n",
64 | "])\n",
65 | "# build 模型,并打印模型信息\n",
66 | "model.build(input_shape=(4, 784))\n",
67 | "model.summary()"
68 | ]
69 | },
70 | {
71 | "cell_type": "markdown",
72 | "metadata": {},
73 | "source": [
74 | "**卷积运算:** \n",
75 | "在信号处理领域,1D连续信号的卷积运算被定义两个函数的积分:函数$f(\\tau)$、函数$g(\\tau)$,其中$g(\\tau)$经过了翻转$g(-\\tau)$和平移后变成$g(n-\\tau)$。卷积的“卷”是指翻转平移操作,“积”是指积分运算,1D连续卷积定义为:$$(f\\otimes g)(n)=\\int_{-\\infty}^{\\infty}f(\\tau)g(n-\\tau)d\\tau$$离散卷积将积分运算换成累加运算:$$(f\\otimes g)(n)=\\sum_{\\tau=-\\infty}^{\\infty}f(\\tau)g(n-\\tau)$$"
76 | ]
77 | },
78 | {
79 | "cell_type": "markdown",
80 | "metadata": {},
81 | "source": [
82 | "卷积神经层的输出尺寸$[b,h',w',c_{out}]$由卷积核的数量$c_{out}$,卷积核的大小$k$,步长$s$,填充数$p$(只考虑上下填充数量$p_h$相同,左右填充数量$p_w$相同的情况)以及输入$X$的高宽$h/w$共同决定, 它们之间的数学关系可以表达为:$$h'=\\left\\lfloor \\frac{h+2 \\cdot p_h - k}{s} \\right\\rfloor + 1 \\\\ w'=\\left\\lfloor\\frac{w+2 \\cdot p_w - k}{s} \\right\\rfloor + 1$$其中$p_h$、$p_w$分别表示高、宽方向的填充数量,$\\lfloor \\cdot \\rfloor$表示向下取整。"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | "## 卷积层实现\n",
90 | "\n",
91 | "### 自定义权值"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": 2,
97 | "metadata": {
98 | "scrolled": true
99 | },
100 | "outputs": [
101 | {
102 | "data": {
103 | "text/plain": [
104 | "TensorShape([2, 3, 3, 4])"
105 | ]
106 | },
107 | "execution_count": 2,
108 | "metadata": {},
109 | "output_type": "execute_result"
110 | }
111 | ],
112 | "source": [
113 | "# 模拟输入, 3 通道,高宽为 5\n",
114 | "x = tf.random.normal([2,5,5,3]) \n",
115 | "# 需要根据[k,k,cin,cout]格式创建 W 张量, 4 个 3x3 大小卷积核\n",
116 | "w = tf.random.normal([3,3,3,4])\n",
117 | "# 步长为 1, padding 为 0,\n",
118 | "out = tf.nn.conv2d(x,w,strides=1,padding=[[0,0],[0,0],[0,0],[0,0]])\n",
119 | "out.shape"
120 | ]
121 | },
122 | {
123 | "cell_type": "markdown",
124 | "metadata": {},
125 | "source": [
126 | "上下左右各填充一个单位,则 padding 参数设置为[[0,0],[1,1],[1,1],[0,0]]"
127 | ]
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": 3,
132 | "metadata": {},
133 | "outputs": [
134 | {
135 | "data": {
136 | "text/plain": [
137 | "TensorShape([2, 5, 5, 4])"
138 | ]
139 | },
140 | "execution_count": 3,
141 | "metadata": {},
142 | "output_type": "execute_result"
143 | }
144 | ],
145 | "source": [
146 | "x = tf.random.normal([2,5,5,3]) # 模拟输入, 3 通道,高宽为 5\n",
147 | "# 需要根据[k,k,cin,cout]格式创建, 4 个 3x3 大小卷积核\n",
148 | "w = tf.random.normal([3,3,3,4])\n",
149 | "# 步长为 1, padding 为 1,\n",
150 | "out = tf.nn.conv2d(x,w,strides=1,padding=[[0,0],[1,1],[1,1],[0,0]])\n",
151 | "out.shape"
152 | ]
153 | },
154 | {
155 | "cell_type": "code",
156 | "execution_count": 4,
157 | "metadata": {},
158 | "outputs": [
159 | {
160 | "data": {
161 | "text/plain": [
162 | "TensorShape([2, 5, 5, 4])"
163 | ]
164 | },
165 | "execution_count": 4,
166 | "metadata": {},
167 | "output_type": "execute_result"
168 | }
169 | ],
170 | "source": [
171 | "x = tf.random.normal([2,5,5,3]) # 模拟输入, 3 通道,高宽为 5\n",
172 | "w = tf.random.normal([3,3,3,4]) # 4 个 3x3 大小的卷积核\n",
173 | "# 步长为,padding 设置为输出、输入同大小\n",
174 | "# 需要注意的是, padding=same 只有在 strides=1 时才是同大小\n",
175 | "out = tf.nn.conv2d(x,w,strides=1,padding='SAME')\n",
176 | "out.shape"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": 5,
182 | "metadata": {},
183 | "outputs": [
184 | {
185 | "data": {
186 | "text/plain": [
187 | "TensorShape([2, 2, 2, 4])"
188 | ]
189 | },
190 | "execution_count": 5,
191 | "metadata": {},
192 | "output_type": "execute_result"
193 | }
194 | ],
195 | "source": [
196 | "x = tf.random.normal([2,5,5,3])\n",
197 | "w = tf.random.normal([3,3,3,4])\n",
198 | "# 高宽先 padding 成可以整除 3 的最小整数 6,然后 6 按 3 倍减少,得到 2x2\n",
199 | "out = tf.nn.conv2d(x,w,strides=3,padding='SAME')\n",
200 | "out.shape"
201 | ]
202 | },
203 | {
204 | "cell_type": "code",
205 | "execution_count": 6,
206 | "metadata": {},
207 | "outputs": [
208 | {
209 | "data": {
210 | "text/plain": [
211 | "TensorShape([2, 2, 2, 4])"
212 | ]
213 | },
214 | "execution_count": 6,
215 | "metadata": {},
216 | "output_type": "execute_result"
217 | }
218 | ],
219 | "source": [
220 | "# 根据[cout]格式创建偏置向量\n",
221 | "b = tf.zeros([4])\n",
222 | "# 在卷积输出上叠加偏置向量,它会自动 broadcasting 为[b,h',w',cout]\n",
223 | "out = out + b\n",
224 | "out.shape"
225 | ]
226 | },
227 | {
228 | "cell_type": "markdown",
229 | "metadata": {},
230 | "source": [
231 | "### 卷积层类"
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "execution_count": 7,
237 | "metadata": {},
238 | "outputs": [
239 | {
240 | "data": {
241 | "text/plain": [
242 | "TensorShape([2, 5, 5, 4])"
243 | ]
244 | },
245 | "execution_count": 7,
246 | "metadata": {},
247 | "output_type": "execute_result"
248 | }
249 | ],
250 | "source": [
251 | "layer = layers.Conv2D(4,kernel_size=3,strides=1,padding='SAME')\n",
252 | "out = layer(x) # 前向计算\n",
253 | "out.shape # 输出张量的 shape"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": 8,
259 | "metadata": {
260 | "scrolled": true
261 | },
262 | "outputs": [
263 | {
264 | "data": {
265 | "text/plain": [
266 | "[,\n",
331 | " ]"
332 | ]
333 | },
334 | "execution_count": 8,
335 | "metadata": {},
336 | "output_type": "execute_result"
337 | }
338 | ],
339 | "source": [
340 | "# 返回所有待优化张量列表\n",
341 | "layer.trainable_variables"
342 | ]
343 | },
344 | {
345 | "cell_type": "markdown",
346 | "metadata": {},
347 | "source": [
348 | "## 卷积层变种\n",
349 | "\n",
350 | "### 空洞卷积"
351 | ]
352 | },
353 | {
354 | "cell_type": "code",
355 | "execution_count": 9,
356 | "metadata": {},
357 | "outputs": [
358 | {
359 | "data": {
360 | "text/plain": [
361 | "TensorShape([1, 3, 3, 1])"
362 | ]
363 | },
364 | "execution_count": 9,
365 | "metadata": {},
366 | "output_type": "execute_result"
367 | }
368 | ],
369 | "source": [
370 | "x = tf.random.normal([1,7,7,1]) # 模拟输入\n",
371 | "# 空洞卷积, 1 个 3x3 的卷积核\n",
372 | "layer = layers.Conv2D(1,kernel_size=3,strides=1,dilation_rate=2)\n",
373 | "out = layer(x) # 前向计算\n",
374 | "out.shape"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "metadata": {},
380 | "source": [
381 | "### 转置卷积"
382 | ]
383 | },
384 | {
385 | "cell_type": "code",
386 | "execution_count": 10,
387 | "metadata": {},
388 | "outputs": [
389 | {
390 | "data": {
391 | "text/plain": [
392 | ""
398 | ]
399 | },
400 | "execution_count": 10,
401 | "metadata": {},
402 | "output_type": "execute_result"
403 | }
404 | ],
405 | "source": [
406 | "# 创建 X 矩阵,高宽为 5x5\n",
407 | "x = tf.range(25)+1\n",
408 | "# Reshape 为合法维度的张量\n",
409 | "x = tf.reshape(x,[1,5,5,1])\n",
410 | "x = tf.cast(x, tf.float32)\n",
411 | "# 创建固定内容的卷积核矩阵\n",
412 | "w = tf.constant([[-1,2,-3.],[4,-5,6],[-7,8,-9]])\n",
413 | "# 调整为合法维度的张量\n",
414 | "w = tf.expand_dims(w,axis=2)\n",
415 | "w = tf.expand_dims(w,axis=3)\n",
416 | "# 进行普通卷积运算\n",
417 | "out = tf.nn.conv2d(x,w,strides=2,padding='VALID')\n",
418 | "out"
419 | ]
420 | },
421 | {
422 | "cell_type": "code",
423 | "execution_count": 11,
424 | "metadata": {
425 | "scrolled": true
426 | },
427 | "outputs": [
428 | {
429 | "data": {
430 | "text/plain": [
431 | ""
461 | ]
462 | },
463 | "execution_count": 11,
464 | "metadata": {},
465 | "output_type": "execute_result"
466 | }
467 | ],
468 | "source": [
469 | "# 普通卷积的输出作为转置卷积的输入,进行转置卷积运算\n",
470 | "xx = tf.nn.conv2d_transpose(out, w, strides=2,\n",
471 | " padding='VALID',\n",
472 | " output_shape=[1,5,5,1])\n",
473 | "xx"
474 | ]
475 | },
476 | {
477 | "cell_type": "code",
478 | "execution_count": 12,
479 | "metadata": {},
480 | "outputs": [
481 | {
482 | "data": {
483 | "text/plain": [
484 | "TensorShape([1, 2, 2, 1])"
485 | ]
486 | },
487 | "execution_count": 12,
488 | "metadata": {},
489 | "output_type": "execute_result"
490 | }
491 | ],
492 | "source": [
493 | "x = tf.random.normal([1,6,6,1])\n",
494 | "# 6x6 的输入经过普通卷积\n",
495 | "out = tf.nn.conv2d(x,w,strides=2,padding='VALID')\n",
496 | "out.shape"
497 | ]
498 | },
499 | {
500 | "cell_type": "code",
501 | "execution_count": 13,
502 | "metadata": {
503 | "scrolled": true
504 | },
505 | "outputs": [
506 | {
507 | "data": {
508 | "text/plain": [
509 | ""
551 | ]
552 | },
553 | "execution_count": 13,
554 | "metadata": {},
555 | "output_type": "execute_result"
556 | }
557 | ],
558 | "source": [
559 | "# 恢复出 6x6 大小\n",
560 | "xx = tf.nn.conv2d_transpose(out, w, strides=2,\n",
561 | " padding='VALID',\n",
562 | " output_shape=[1,6,6,1])\n",
563 | "xx"
564 | ]
565 | },
566 | {
567 | "cell_type": "markdown",
568 | "metadata": {},
569 | "source": [
570 | "**转置卷积实现**"
571 | ]
572 | },
573 | {
574 | "cell_type": "code",
575 | "execution_count": 14,
576 | "metadata": {},
577 | "outputs": [
578 | {
579 | "data": {
580 | "text/plain": [
581 | ""
587 | ]
588 | },
589 | "execution_count": 14,
590 | "metadata": {},
591 | "output_type": "execute_result"
592 | }
593 | ],
594 | "source": [
595 | "# 创建 4x4 大小的输入\n",
596 | "x = tf.range(16)+1\n",
597 | "x = tf.reshape(x,[1,4,4,1])\n",
598 | "x = tf.cast(x, tf.float32)\n",
599 | "# 创建 3x3 卷积核\n",
600 | "w = tf.constant([[-1,2,-3.],[4,-5,6],[-7,8,-9]])\n",
601 | "w = tf.expand_dims(w,axis=2)\n",
602 | "w = tf.expand_dims(w,axis=3)\n",
603 | "# 普通卷积运算\n",
604 | "out = tf.nn.conv2d(x,w,strides=1,padding='VALID')\n",
605 | "out"
606 | ]
607 | },
608 | {
609 | "cell_type": "markdown",
610 | "metadata": {},
611 | "source": [
612 | "在保持strides=1, padding='VALID',卷积核不变的情况下,我们通过卷积核w与输出out的转置卷积运算尝试恢复与输入x相同大小的高宽张量"
613 | ]
614 | },
615 | {
616 | "cell_type": "code",
617 | "execution_count": 15,
618 | "metadata": {},
619 | "outputs": [
620 | {
621 | "data": {
622 | "text/plain": [
623 | ""
628 | ]
629 | },
630 | "execution_count": 15,
631 | "metadata": {},
632 | "output_type": "execute_result"
633 | }
634 | ],
635 | "source": [
636 | "# 恢复 4x4 大小的输入\n",
637 | "xx = tf.nn.conv2d_transpose(out, w, strides=1, padding='VALID',\n",
638 | " output_shape=[1,4,4,1])\n",
639 | "tf.squeeze(xx)"
640 | ]
641 | },
642 | {
643 | "cell_type": "code",
644 | "execution_count": 16,
645 | "metadata": {
646 | "scrolled": true
647 | },
648 | "outputs": [
649 | {
650 | "data": {
651 | "text/plain": [
652 | ""
672 | ]
673 | },
674 | "execution_count": 16,
675 | "metadata": {},
676 | "output_type": "execute_result"
677 | }
678 | ],
679 | "source": [
680 | "# 创建转置卷积类\n",
681 | "layer = layers.Conv2DTranspose(1,kernel_size=3,strides=1,padding='VALID')\n",
682 | "xx2 = layer(out) # 通过转置卷积层\n",
683 | "xx2"
684 | ]
685 | },
686 | {
687 | "cell_type": "markdown",
688 | "metadata": {},
689 | "source": [
690 | "### 分离卷积"
691 | ]
692 | },
693 | {
694 | "cell_type": "markdown",
695 | "metadata": {},
696 | "source": [
697 | "那么采用分离卷积有什么优势呢?一个很明显的优势在于, 同样的输入和输出,采用Separable Convolution 的参数量约是普通卷积的$\\displaystyle \\frac{1}{3}$。"
698 | ]
699 | },
700 | {
701 | "cell_type": "markdown",
702 | "metadata": {},
703 | "source": [
704 | "## 深度残差网络\n",
705 | "\n",
706 | "### ResBlock实现"
707 | ]
708 | },
709 | {
710 | "cell_type": "code",
711 | "execution_count": 17,
712 | "metadata": {},
713 | "outputs": [],
714 | "source": [
715 | "class BasicBlock(layers.Layer):\n",
716 | " # 残差模块类\n",
717 | " def __init__(self, filter_num, stride=1):\n",
718 | " super(BasicBlock, self).__init__()\n",
719 | " # f(x)包含了 2 个普通卷积层,创建卷积层 1\n",
720 | " self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')\n",
721 | " self.bn1 = layers.BatchNormalization()\n",
722 | " self.relu = layers.Activation('relu')\n",
723 | " # 创建卷积层 2\n",
724 | " self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')\n",
725 | " self.bn2 = layers.BatchNormalization()\n",
726 | " \n",
727 | " if stride != 1: # 插入 identity 层\n",
728 | " self.downsample = Sequential()\n",
729 | " self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))\n",
730 | " else: # 否则,直接连接\n",
731 | " self.downsample = lambda x:x\n",
732 | " \n",
733 | " def call(self, inputs, training=None):\n",
734 | " # 前向传播函数\n",
735 | " out = self.conv1(inputs) # 通过第一个卷积层\n",
736 | " out = self.bn1(out)\n",
737 | " out = self.relu(out)\n",
738 | " out = self.conv2(out) # 通过第二个卷积层\n",
739 | " out = self.bn2(out)\n",
740 | " # 输入通过 identity()转换\n",
741 | " identity = self.downsample(inputs)\n",
742 | " # f(x)+x 运算\n",
743 | " output = layers.add([out, identity])\n",
744 | " # 再通过激活函数并返回\n",
745 | " output = tf.nn.relu(output)\n",
746 | " return output"
747 | ]
748 | },
749 | {
750 | "cell_type": "code",
751 | "execution_count": null,
752 | "metadata": {},
753 | "outputs": [],
754 | "source": []
755 | }
756 | ],
757 | "metadata": {
758 | "kernelspec": {
759 | "display_name": "Python 3",
760 | "language": "python",
761 | "name": "python3"
762 | },
763 | "language_info": {
764 | "codemirror_mode": {
765 | "name": "ipython",
766 | "version": 3
767 | },
768 | "file_extension": ".py",
769 | "mimetype": "text/x-python",
770 | "name": "python",
771 | "nbconvert_exporter": "python",
772 | "pygments_lexer": "ipython3",
773 | "version": "3.7.2"
774 | },
775 | "toc": {
776 | "base_numbering": 1,
777 | "nav_menu": {},
778 | "number_sections": true,
779 | "sideBar": true,
780 | "skip_h1_title": true,
781 | "title_cell": "Table of Contents",
782 | "title_sidebar": "Contents",
783 | "toc_cell": false,
784 | "toc_position": {},
785 | "toc_section_display": true,
786 | "toc_window_display": false
787 | }
788 | },
789 | "nbformat": 4,
790 | "nbformat_minor": 1
791 | }
792 |
--------------------------------------------------------------------------------
/src/ch10/cifar10-vgg13-accuracy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch10/cifar10-vgg13-accuracy.png
--------------------------------------------------------------------------------
/src/ch10/resnet.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: resnet.py
6 | @time: 2020/2/28 12:25
7 | @desc: ResNet模型的代码
8 | """
9 |
10 | import tensorflow as tf
11 | from tensorflow import keras
12 | from tensorflow.keras import layers, Sequential
13 |
14 |
15 | class BasicBlock(layers.Layer):
16 | # 残差模块
17 | def __init__(self, filter_num, stride=1):
18 | super(BasicBlock, self).__init__()
19 | # 第一个卷积单元
20 | self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')
21 | self.bn1 = layers.BatchNormalization()
22 | self.relu = layers.Activation('relu')
23 | # 第二个卷积单元
24 | self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
25 | self.bn2 = layers.BatchNormalization()
26 |
27 | if stride != 1:
28 | # 通过1x1卷积完成shape匹配
29 | self.downsample = Sequential()
30 | self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))
31 | else:
32 | # shape匹配,直接短接
33 | self.downsample = lambda x: x
34 |
35 | def call(self, inputs, training=None):
36 |
37 | # [b, h, w, c],通过第一个卷积单元
38 | out = self.conv1(inputs)
39 | out = self.bn1(out)
40 | out = self.relu(out)
41 | # 通过第二个卷积单元
42 | out = self.conv2(out)
43 | out = self.bn2(out)
44 | # 通过identity模块
45 | identity = self.downsample(inputs)
46 | # 2条路径输出直接相加
47 | output = layers.add([out, identity])
48 | output = tf.nn.relu(output) # 激活函数
49 |
50 | return output
51 |
52 |
53 | class ResNet(keras.Model):
54 | # 通用的ResNet实现类
55 | def __init__(self, layer_dims, num_classes=10): # [2, 2, 2, 2]
56 | super(ResNet, self).__init__()
57 | # 根网络,预处理
58 | self.stem = Sequential([layers.Conv2D(64, (3, 3), strides=(1, 1)),
59 | layers.BatchNormalization(),
60 | layers.Activation('relu'),
61 | layers.MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding='same')
62 | ])
63 | # 堆叠4个Block,每个block包含了多个BasicBlock,设置步长不一样
64 | self.layer1 = self.build_resblock(64, layer_dims[0])
65 | self.layer2 = self.build_resblock(128, layer_dims[1], stride=2)
66 | self.layer3 = self.build_resblock(256, layer_dims[2], stride=2)
67 | self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)
68 |
69 | # 通过Pooling层将高宽降低为1x1
70 | self.avgpool = layers.GlobalAveragePooling2D()
71 | # 最后连接一个全连接层分类
72 | self.fc = layers.Dense(num_classes)
73 |
74 | def call(self, inputs, training=None):
75 | # 通过根网络
76 | x = self.stem(inputs)
77 | # 一次通过4个模块
78 | x = self.layer1(x)
79 | x = self.layer2(x)
80 | x = self.layer3(x)
81 | x = self.layer4(x)
82 |
83 | # 通过池化层
84 | x = self.avgpool(x)
85 | # 通过全连接层
86 | x = self.fc(x)
87 |
88 | return x
89 |
90 | def build_resblock(self, filter_num, blocks, stride=1):
91 | # 辅助函数,堆叠filter_num个BasicBlock
92 | res_blocks = Sequential()
93 | # 只有第一个BasicBlock的步长可能不为1,实现下采样
94 | res_blocks.add(BasicBlock(filter_num, stride))
95 |
96 | for _ in range(1, blocks): # 其他BasicBlock步长都为1
97 | res_blocks.add(BasicBlock(filter_num, stride=1))
98 |
99 | return res_blocks
100 |
101 |
102 | def resnet18():
103 | # 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet
104 | return ResNet([2, 2, 2, 2])
105 |
106 |
107 | def resnet34():
108 | # 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet
109 | return ResNet([3, 4, 6, 3])
110 |
--------------------------------------------------------------------------------
/src/ch11/11.11-sentiment-analysis-cell-GRU.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 11.11-sentiment-analysis-cell-GRU.py
6 | @time: 2020/2/28 16:25
7 | @desc: 11.5 GRU情感分类问题实战的代码(cell方式)
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from tensorflow import keras
15 | from tensorflow.keras import layers, losses, optimizers, Sequential
16 |
17 | tf.random.set_seed(22)
18 | np.random.seed(22)
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | def load_dataset(batchsz, total_words, max_review_len):
23 | # 加载IMDB数据集,此处的数据采用数字编码,一个数字代表一个单词
24 | (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
25 | print(x_train.shape, len(x_train[0]), y_train.shape)
26 | print(x_test.shape, len(x_test[0]), y_test.shape)
27 |
28 | # x_train:[b, 80]
29 | # x_test: [b, 80]
30 | # 截断和填充句子,使得等长,此处长句子保留句子后面的部分,短句子在前面填充
31 | x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
32 | x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)
33 | # 构建数据集,打散,批量,并丢掉最后一个不够batchsz的batch
34 | db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
35 | db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
36 | db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
37 | db_test = db_test.batch(batchsz, drop_remainder=True)
38 | print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
39 | print('x_test shape:', x_test.shape)
40 | return db_train, db_test
41 |
42 |
43 | class MyRNN(keras.Model):
44 | # Cell方式构建多层网络
45 | def __init__(self, units, batchsz, total_words, embedding_len, max_review_len):
46 | super(MyRNN, self).__init__()
47 | # [b, 64],构建Cell初始化状态向量,重复使用
48 | self.state0 = [tf.zeros([batchsz, units])]
49 | self.state1 = [tf.zeros([batchsz, units])]
50 | # 词向量编码 [b, 80] => [b, 80, 100]
51 | self.embedding = layers.Embedding(total_words, embedding_len,
52 | input_length=max_review_len)
53 | # 构建2个Cell
54 | self.rnn_cell0 = layers.GRUCell(units, dropout=0.5)
55 | self.rnn_cell1 = layers.GRUCell(units, dropout=0.5)
56 | # 构建分类网络,用于将CELL的输出特征进行分类,2分类
57 | # [b, 80, 100] => [b, 64] => [b, 1]
58 | self.outlayer = Sequential([
59 | layers.Dense(units),
60 | layers.Dropout(rate=0.5),
61 | layers.ReLU(),
62 | layers.Dense(1)])
63 |
64 | def call(self, inputs, training=None):
65 | x = inputs # [b, 80]
66 | # embedding: [b, 80] => [b, 80, 100]
67 | x = self.embedding(x)
68 | # rnn cell compute,[b, 80, 100] => [b, 64]
69 | state0 = self.state0
70 | state1 = self.state1
71 | for word in tf.unstack(x, axis=1): # word: [b, 100]
72 | out0, state0 = self.rnn_cell0(word, state0, training)
73 | out1, state1 = self.rnn_cell1(out0, state1, training)
74 | # 末层最后一个输出作为分类网络的输入: [b, 64] => [b, 1]
75 | x = self.outlayer(out1, training)
76 | # p(y is pos|x)
77 | prob = tf.sigmoid(x)
78 |
79 | return prob
80 |
81 |
82 | def main():
83 | batchsz = 128 # 批量大小
84 | total_words = 10000 # 词汇表大小N_vocab
85 | embedding_len = 100 # 词向量特征长度f
86 | max_review_len = 80 # 句子最大长度s,大于的句子部分将截断,小于的将填充
87 |
88 | db_train, db_test = load_dataset(batchsz, total_words, max_review_len)
89 |
90 | units = 64 # RNN状态向量长度f
91 | epochs = 20 # 训练epochs
92 |
93 | model = MyRNN(units, batchsz, total_words, embedding_len, max_review_len)
94 | # 装配
95 | model.compile(optimizer=optimizers.RMSprop(0.001),
96 | loss=losses.BinaryCrossentropy(),
97 | metrics=['accuracy'],
98 | experimental_run_tf_function=False)
99 | # 训练和验证
100 | model.fit(db_train, epochs=epochs, validation_data=db_test)
101 | # 测试
102 | model.evaluate(db_test)
103 |
104 |
105 | if __name__ == '__main__':
106 | main()
107 |
--------------------------------------------------------------------------------
/src/ch11/11.11-sentiment-analysis-cell-LSTM.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 11.11-sentiment-analysis-cell-LSTM.py
6 | @time: 2020/2/28 16:25
7 | @desc: 11.11 LSTM情感分类问题实战的代码(cell方式)
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from tensorflow import keras
15 | from tensorflow.keras import layers, losses, optimizers, Sequential
16 |
17 | tf.random.set_seed(22)
18 | np.random.seed(22)
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | def load_dataset(batchsz, total_words, max_review_len):
23 | # 加载IMDB数据集,此处的数据采用数字编码,一个数字代表一个单词
24 | (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
25 | print(x_train.shape, len(x_train[0]), y_train.shape)
26 | print(x_test.shape, len(x_test[0]), y_test.shape)
27 |
28 | # x_train:[b, 80]
29 | # x_test: [b, 80]
30 | # 截断和填充句子,使得等长,此处长句子保留句子后面的部分,短句子在前面填充
31 | x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
32 | x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)
33 | # 构建数据集,打散,批量,并丢掉最后一个不够batchsz的batch
34 | db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
35 | db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
36 | db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
37 | db_test = db_test.batch(batchsz, drop_remainder=True)
38 | print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
39 | print('x_test shape:', x_test.shape)
40 | return db_train, db_test
41 |
42 |
43 | class MyRNN(keras.Model):
44 | # Cell方式构建多层网络
45 | def __init__(self, units, batchsz, total_words, embedding_len, max_review_len):
46 | super(MyRNN, self).__init__()
47 | # [b, 64],构建Cell初始化状态向量,重复使用
48 | self.state0 = [tf.zeros([batchsz, units]), tf.zeros([batchsz, units])]
49 | self.state1 = [tf.zeros([batchsz, units]), tf.zeros([batchsz, units])]
50 | # 词向量编码 [b, 80] => [b, 80, 100]
51 | self.embedding = layers.Embedding(total_words, embedding_len,
52 | input_length=max_review_len)
53 | # 构建2个Cell
54 | self.rnn_cell0 = layers.LSTMCell(units, dropout=0.5)
55 | self.rnn_cell1 = layers.LSTMCell(units, dropout=0.5)
56 | # 构建分类网络,用于将CELL的输出特征进行分类,2分类
57 | # [b, 80, 100] => [b, 64] => [b, 1]
58 | self.outlayer = Sequential([
59 | layers.Dense(units),
60 | layers.Dropout(rate=0.5),
61 | layers.ReLU(),
62 | layers.Dense(1)])
63 |
64 | def call(self, inputs, training=None):
65 | x = inputs # [b, 80]
66 | # embedding: [b, 80] => [b, 80, 100]
67 | x = self.embedding(x)
68 | # rnn cell compute,[b, 80, 100] => [b, 64]
69 | state0 = self.state0
70 | state1 = self.state1
71 | for word in tf.unstack(x, axis=1): # word: [b, 100]
72 | out0, state0 = self.rnn_cell0(word, state0, training)
73 | out1, state1 = self.rnn_cell1(out0, state1, training)
74 | # 末层最后一个输出作为分类网络的输入: [b, 64] => [b, 1]
75 | x = self.outlayer(out1, training)
76 | # p(y is pos|x)
77 | prob = tf.sigmoid(x)
78 |
79 | return prob
80 |
81 |
82 | def main():
83 | batchsz = 128 # 批量大小
84 | total_words = 10000 # 词汇表大小N_vocab
85 | embedding_len = 100 # 词向量特征长度f
86 | max_review_len = 80 # 句子最大长度s,大于的句子部分将截断,小于的将填充
87 |
88 | db_train, db_test = load_dataset(batchsz, total_words, max_review_len)
89 |
90 | units = 64 # RNN状态向量长度f
91 | epochs = 20 # 训练epochs
92 |
93 | model = MyRNN(units, batchsz, total_words, embedding_len, max_review_len)
94 | # 装配
95 | model.compile(optimizer=optimizers.RMSprop(0.001),
96 | loss=losses.BinaryCrossentropy(),
97 | metrics=['accuracy'],
98 | experimental_run_tf_function=False)
99 | # 训练和验证
100 | model.fit(db_train, epochs=epochs, validation_data=db_test)
101 | # 测试
102 | model.evaluate(db_test)
103 |
104 |
105 | if __name__ == '__main__':
106 | main()
107 |
--------------------------------------------------------------------------------
/src/ch11/11.11-sentiment-analysis-layer-GRU.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 11.11-sentiment-analysis-layer-GRU.py
6 | @time: 2020/2/28 16:25
7 | @desc: 11.11 GRU情感分类问题实战的代码(layer方式)
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from tensorflow import keras
15 | from tensorflow.keras import layers, losses, optimizers, Sequential
16 |
17 | tf.random.set_seed(22)
18 | np.random.seed(22)
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | def load_dataset(batchsz, total_words, max_review_len):
23 | # 加载IMDB数据集,此处的数据采用数字编码,一个数字代表一个单词
24 | (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
25 | print(x_train.shape, len(x_train[0]), y_train.shape)
26 | print(x_test.shape, len(x_test[0]), y_test.shape)
27 |
28 | # x_train:[b, 80]
29 | # x_test: [b, 80]
30 | # 截断和填充句子,使得等长,此处长句子保留句子后面的部分,短句子在前面填充
31 | x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
32 | x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)
33 | # 构建数据集,打散,批量,并丢掉最后一个不够batchsz的batch
34 | db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
35 | db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
36 | db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
37 | db_test = db_test.batch(batchsz, drop_remainder=True)
38 | print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
39 | print('x_test shape:', x_test.shape)
40 | return db_train, db_test
41 |
42 |
43 | class MyRNN(keras.Model):
44 | # Cell方式构建多层网络
45 | def __init__(self, units, total_words, embedding_len, max_review_len):
46 | super(MyRNN, self).__init__()
47 | # 词向量编码 [b, 80] => [b, 80, 100]
48 | self.embedding = layers.Embedding(total_words, embedding_len,
49 | input_length=max_review_len)
50 | # 构建RNN
51 | self.rnn = Sequential([
52 | layers.GRU(units, dropout=0.5, return_sequences=True),
53 | layers.GRU(units, dropout=0.5)
54 | ])
55 |
56 | # 构建分类网络,用于将CELL的输出特征进行分类,2分类
57 | # [b, 80, 100] => [b, 64] => [b, 1]
58 | self.outlayer = Sequential([
59 | layers.Dense(32),
60 | layers.Dropout(rate=0.5),
61 | layers.ReLU(),
62 | layers.Dense(1)])
63 |
64 | def call(self, inputs, training=None):
65 | x = inputs # [b, 80]
66 | # embedding: [b, 80] => [b, 80, 100]
67 | x = self.embedding(x)
68 | # rnn cell compute,[b, 80, 100] => [b, 64]
69 | x = self.rnn(x)
70 | # 末层最后一个输出作为分类网络的输入: [b, 64] => [b, 1]
71 | x = self.outlayer(x, training)
72 | # p(y is pos|x)
73 | prob = tf.sigmoid(x)
74 |
75 | return prob
76 |
77 |
78 | def main():
79 | batchsz = 128 # 批量大小
80 | total_words = 10000 # 词汇表大小N_vocab
81 | embedding_len = 100 # 词向量特征长度f
82 | max_review_len = 80 # 句子最大长度s,大于的句子部分将截断,小于的将填充
83 |
84 | db_train, db_test = load_dataset(batchsz, total_words, max_review_len)
85 |
86 | units = 32 # RNN状态向量长度f
87 | epochs = 20 # 训练epochs
88 |
89 | model = MyRNN(units, total_words, embedding_len, max_review_len)
90 | # 装配
91 | model.compile(optimizer=optimizers.RMSprop(0.001),
92 | loss=losses.BinaryCrossentropy(),
93 | metrics=['accuracy'],
94 | experimental_run_tf_function=False)
95 | # 训练和验证
96 | model.fit(db_train, epochs=epochs, validation_data=db_test)
97 | # 测试
98 | model.evaluate(db_test)
99 |
100 |
101 | if __name__ == '__main__':
102 | main()
103 |
--------------------------------------------------------------------------------
/src/ch11/11.11-sentiment-analysis-layer-LSTM.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 11.11-sentiment-analysis-layer-LSTM.py
6 | @time: 2020/2/28 16:25
7 | @desc: 11.11 LSTM情感分类问题实战的代码(layer方式)
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from tensorflow import keras
15 | from tensorflow.keras import layers, losses, optimizers, Sequential
16 |
17 | tf.random.set_seed(22)
18 | np.random.seed(22)
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | def load_dataset(batchsz, total_words, max_review_len):
23 | # 加载IMDB数据集,此处的数据采用数字编码,一个数字代表一个单词
24 | (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
25 | print(x_train.shape, len(x_train[0]), y_train.shape)
26 | print(x_test.shape, len(x_test[0]), y_test.shape)
27 |
28 | # x_train:[b, 80]
29 | # x_test: [b, 80]
30 | # 截断和填充句子,使得等长,此处长句子保留句子后面的部分,短句子在前面填充
31 | x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
32 | x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)
33 | # 构建数据集,打散,批量,并丢掉最后一个不够batchsz的batch
34 | db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
35 | db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
36 | db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
37 | db_test = db_test.batch(batchsz, drop_remainder=True)
38 | print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
39 | print('x_test shape:', x_test.shape)
40 | return db_train, db_test
41 |
42 |
43 | class MyRNN(keras.Model):
44 | # Cell方式构建多层网络
45 | def __init__(self, units, total_words, embedding_len, max_review_len):
46 | super(MyRNN, self).__init__()
47 | # 词向量编码 [b, 80] => [b, 80, 100]
48 | self.embedding = layers.Embedding(total_words, embedding_len,
49 | input_length=max_review_len)
50 | # 构建RNN
51 | self.rnn = Sequential([
52 | layers.LSTM(units, dropout=0.5, return_sequences=True),
53 | layers.LSTM(units, dropout=0.5)
54 | ])
55 |
56 | # 构建分类网络,用于将CELL的输出特征进行分类,2分类
57 | # [b, 80, 100] => [b, 64] => [b, 1]
58 | self.outlayer = Sequential([
59 | layers.Dense(32),
60 | layers.Dropout(rate=0.5),
61 | layers.ReLU(),
62 | layers.Dense(1)])
63 |
64 | def call(self, inputs, training=None):
65 | x = inputs # [b, 80]
66 | # embedding: [b, 80] => [b, 80, 100]
67 | x = self.embedding(x)
68 | # rnn cell compute,[b, 80, 100] => [b, 64]
69 | x = self.rnn(x)
70 | # 末层最后一个输出作为分类网络的输入: [b, 64] => [b, 1]
71 | x = self.outlayer(x, training)
72 | # p(y is pos|x)
73 | prob = tf.sigmoid(x)
74 |
75 | return prob
76 |
77 |
78 | def main():
79 | batchsz = 128 # 批量大小
80 | total_words = 10000 # 词汇表大小N_vocab
81 | embedding_len = 100 # 词向量特征长度f
82 | max_review_len = 80 # 句子最大长度s,大于的句子部分将截断,小于的将填充
83 |
84 | db_train, db_test = load_dataset(batchsz, total_words, max_review_len)
85 |
86 | units = 32 # RNN状态向量长度f
87 | epochs = 20 # 训练epochs
88 |
89 | model = MyRNN(units, total_words, embedding_len, max_review_len)
90 | # 装配
91 | model.compile(optimizer=optimizers.RMSprop(0.001),
92 | loss=losses.BinaryCrossentropy(),
93 | metrics=['accuracy'],
94 | experimental_run_tf_function=False)
95 | # 训练和验证
96 | model.fit(db_train, epochs=epochs, validation_data=db_test)
97 | # 测试
98 | model.evaluate(db_test)
99 |
100 |
101 | if __name__ == '__main__':
102 | main()
103 |
--------------------------------------------------------------------------------
/src/ch11/11.12-sentiment-analysis-layer-LSTM-pretrain.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 11.12-sentiment-analysis-layer-LSTM-pretrain.py
6 | @time: 2020/2/28 16:25
7 | @desc: 11.12 预训练的词向量的代码(LSTM layer方式)
8 | 将glove.6B文件夹放到ch11目录下,读取的glove路径为/ch11/glove.6B/glove.6B.100d.txt
9 | """
10 |
11 | import os
12 |
13 | import numpy as np
14 | import tensorflow as tf
15 | from tensorflow import keras
16 | from tensorflow.keras import layers, losses, optimizers, Sequential
17 |
18 | tf.random.set_seed(22)
19 | np.random.seed(22)
20 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
21 |
22 |
23 | def load_dataset(batchsz, total_words, max_review_len, embedding_len):
24 | # 加载IMDB数据集,此处的数据采用数字编码,一个数字代表一个单词
25 | (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
26 | print(x_train.shape, len(x_train[0]), y_train.shape)
27 | print(x_test.shape, len(x_test[0]), y_test.shape)
28 |
29 | # x_train:[b, 80]
30 | # x_test: [b, 80]
31 | # 截断和填充句子,使得等长,此处长句子保留句子后面的部分,短句子在前面填充
32 | x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
33 | x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)
34 | # 构建数据集,打散,批量,并丢掉最后一个不够batchsz的batch
35 | db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
36 | db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
37 | db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
38 | db_test = db_test.batch(batchsz, drop_remainder=True)
39 | print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
40 | print('x_test shape:', x_test.shape)
41 | return db_train, db_test
42 |
43 |
44 | def pretrain_embeding_matrix(embedding_len, total_words):
45 | # 数字编码表
46 | word_index = keras.datasets.imdb.get_word_index()
47 |
48 | word_index = {k: (v + 3) for k, v in word_index.items()}
49 | word_index[""] = 0
50 | word_index[""] = 1
51 | word_index[""] = 2 # unknown
52 | word_index[""] = 3
53 |
54 | print('Indexing word vectors.')
55 | embeddings_index = {}
56 | GLOVE_DIR = r'.\glove.6B'
57 | with open(os.path.join(GLOVE_DIR, 'glove.6B.100d.txt'), encoding='utf-8') as f:
58 | for line in f:
59 | values = line.split()
60 | word = values[0]
61 | coefs = np.asarray(values[1:], dtype='float32')
62 | embeddings_index[word] = coefs
63 | print('Found %s word vectors.' % len(embeddings_index))
64 | MAX_NUM_WORDS = total_words
65 | # prepare embedding matrix
66 | num_words = min(MAX_NUM_WORDS, len(word_index))
67 | embedding_matrix = np.zeros((num_words, embedding_len))
68 | applied_vec_count = 0
69 | for word, i in word_index.items():
70 | if i >= MAX_NUM_WORDS:
71 | continue
72 | embedding_vector = embeddings_index.get(word)
73 | # print(word,embedding_vector)
74 | if embedding_vector is not None:
75 | # words not found in embedding index will be all-zeros.
76 | embedding_matrix[i] = embedding_vector
77 | applied_vec_count += 1
78 |
79 | return embedding_matrix
80 |
81 |
82 | class MyRNN(keras.Model):
83 | # Cell方式构建多层网络
84 | def __init__(self, units, total_words, embedding_len, max_review_len, embedding_matrix):
85 | super(MyRNN, self).__init__()
86 | # 词向量编码 [b, 80] => [b, 80, 100]
87 | # 创建 Embedding 层
88 | self.embedding = layers.Embedding(total_words, embedding_len,
89 | input_length=max_review_len,
90 | trainable=False)
91 | self.embedding.build(input_shape=(None, max_review_len))
92 | # 利用 GloVe 模型初始化 Embedding 层
93 | self.embedding.set_weights([embedding_matrix]) # 初始化
94 | # 构建RNN
95 | self.rnn = Sequential([
96 | layers.LSTM(units, dropout=0.5, return_sequences=True),
97 | layers.LSTM(units, dropout=0.5)
98 | ])
99 |
100 | # 构建分类网络,用于将CELL的输出特征进行分类,2分类
101 | # [b, 80, 100] => [b, 64] => [b, 1]
102 | self.outlayer = Sequential([
103 | layers.Dense(32),
104 | layers.Dropout(rate=0.5),
105 | layers.ReLU(),
106 | layers.Dense(1)])
107 |
108 | def call(self, inputs, training=None):
109 | x = inputs # [b, 80]
110 | # embedding: [b, 80] => [b, 80, 100]
111 | x = self.embedding(x)
112 | # rnn cell compute,[b, 80, 100] => [b, 64]
113 | x = self.rnn(x)
114 | # 末层最后一个输出作为分类网络的输入: [b, 64] => [b, 1]
115 | x = self.outlayer(x, training)
116 | # p(y is pos|x)
117 | prob = tf.sigmoid(x)
118 |
119 | return prob
120 |
121 |
122 | def main():
123 | batchsz = 128 # 批量大小
124 | total_words = 10000 # 词汇表大小N_vocab
125 | embedding_len = 100 # 词向量特征长度f
126 | max_review_len = 80 # 句子最大长度s,大于的句子部分将截断,小于的将填充
127 |
128 | db_train, db_test = load_dataset(batchsz, total_words, max_review_len, embedding_len)
129 |
130 | units = 32 # RNN状态向量长度f
131 | epochs = 20 # 训练epochs
132 | embedding_matrix = pretrain_embeding_matrix(embedding_len, total_words)
133 |
134 | model = MyRNN(units, total_words, embedding_len, max_review_len, embedding_matrix)
135 | # 装配
136 | model.compile(optimizer=optimizers.RMSprop(0.001),
137 | loss=losses.BinaryCrossentropy(),
138 | metrics=['accuracy'],
139 | experimental_run_tf_function=False)
140 | # 训练和验证
141 | model.fit(db_train, epochs=epochs, validation_data=db_test)
142 | # 测试
143 | model.evaluate(db_test)
144 |
145 |
146 | if __name__ == '__main__':
147 | main()
148 |
--------------------------------------------------------------------------------
/src/ch11/11.5-sentiment-analysis-RNN.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 11.5-sentiment-analysis-RNN.py
6 | @time: 2020/2/28 16:25
7 | @desc: 11.5 RNN情感分类问题实战的代码
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from tensorflow import keras
15 | from tensorflow.keras import layers, losses, optimizers, Sequential
16 |
17 | tf.random.set_seed(22)
18 | np.random.seed(22)
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | def load_dataset(batchsz, total_words, max_review_len):
23 | # 加载IMDB数据集,此处的数据采用数字编码,一个数字代表一个单词
24 | (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
25 | print(x_train.shape, len(x_train[0]), y_train.shape)
26 | print(x_test.shape, len(x_test[0]), y_test.shape)
27 |
28 | # x_train:[b, 80]
29 | # x_test: [b, 80]
30 | # 截断和填充句子,使得等长,此处长句子保留句子后面的部分,短句子在前面填充
31 | x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
32 | x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)
33 | # 构建数据集,打散,批量,并丢掉最后一个不够batchsz的batch
34 | db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
35 | db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
36 | db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
37 | db_test = db_test.batch(batchsz, drop_remainder=True)
38 | print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
39 | print('x_test shape:', x_test.shape)
40 | return db_train, db_test
41 |
42 |
43 | class MyRNN(keras.Model):
44 | # Cell方式构建多层网络
45 | def __init__(self, units, batchsz, total_words, embedding_len, max_review_len):
46 | super(MyRNN, self).__init__()
47 | # [b, 64],构建Cell初始化状态向量,重复使用
48 | self.state0 = [tf.zeros([batchsz, units])]
49 | self.state1 = [tf.zeros([batchsz, units])]
50 | # 词向量编码 [b, 80] => [b, 80, 100]
51 | self.embedding = layers.Embedding(total_words, embedding_len,
52 | input_length=max_review_len)
53 | # 构建2个Cell
54 | self.rnn_cell0 = layers.SimpleRNNCell(units, dropout=0.5)
55 | self.rnn_cell1 = layers.SimpleRNNCell(units, dropout=0.5)
56 | # 构建分类网络,用于将CELL的输出特征进行分类,2分类
57 | # [b, 80, 100] => [b, 64] => [b, 1]
58 | self.outlayer = Sequential([
59 | layers.Dense(units),
60 | layers.Dropout(rate=0.5),
61 | layers.ReLU(),
62 | layers.Dense(1)])
63 |
64 | def call(self, inputs, training=None):
65 | x = inputs # [b, 80]
66 | # embedding: [b, 80] => [b, 80, 100]
67 | x = self.embedding(x)
68 | # rnn cell compute,[b, 80, 100] => [b, 64]
69 | state0 = self.state0
70 | state1 = self.state1
71 | for word in tf.unstack(x, axis=1): # word: [b, 100]
72 | out0, state0 = self.rnn_cell0(word, state0, training)
73 | out1, state1 = self.rnn_cell1(out0, state1, training)
74 | # 末层最后一个输出作为分类网络的输入: [b, 64] => [b, 1]
75 | x = self.outlayer(out1, training)
76 | # p(y is pos|x)
77 | prob = tf.sigmoid(x)
78 |
79 | return prob
80 |
81 |
82 | def main():
83 | batchsz = 128 # 批量大小
84 | total_words = 10000 # 词汇表大小N_vocab
85 | embedding_len = 100 # 词向量特征长度f
86 | max_review_len = 80 # 句子最大长度s,大于的句子部分将截断,小于的将填充
87 |
88 | db_train, db_test = load_dataset(batchsz, total_words, max_review_len)
89 |
90 | units = 64 # RNN状态向量长度f
91 | epochs = 20 # 训练epochs
92 |
93 | model = MyRNN(units, batchsz, total_words, embedding_len, max_review_len)
94 | # 装配
95 | model.compile(optimizer=optimizers.RMSprop(0.001),
96 | loss=losses.BinaryCrossentropy(),
97 | metrics=['accuracy'],
98 | experimental_run_tf_function=False)
99 | # 训练和验证
100 | model.fit(db_train, epochs=epochs, validation_data=db_test)
101 | # 测试
102 | model.evaluate(db_test)
103 |
104 |
105 | if __name__ == '__main__':
106 | main()
107 |
--------------------------------------------------------------------------------
/src/ch12/12.2-fashion-mnist-autoencoder.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 12.2-fashion-mnist-autoencoder.py
6 | @time: 2020/2/29 16:38
7 | @desc: 12.2 Fashion MNIST 图片重建实战的代码
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from PIL import Image
15 | from tensorflow import keras
16 | from tensorflow.keras import Sequential, layers
17 |
18 | tf.random.set_seed(22)
19 | np.random.seed(22)
20 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
21 |
22 |
23 | def save_images(imgs, name):
24 | # 创建 280x280 大小图片阵列
25 | new_im = Image.new('L', (280, 280))
26 |
27 | index = 0
28 | # 10 行图片阵列
29 | for i in range(0, 280, 28):
30 | # 10 列图片阵列
31 | for j in range(0, 280, 28):
32 | im = imgs[index]
33 | im = Image.fromarray(im, mode='L')
34 | # 写入对应位置
35 | new_im.paste(im, (i, j))
36 | index += 1
37 | # 保存图片阵列
38 | new_im.save(name)
39 |
40 |
41 | def load_dataset(batchsz):
42 | # 加载 Fashion MNIST 图片数据集
43 | (x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
44 | # 归一化
45 | x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.
46 | # 只需要通过图片数据即可构建数据集对象,不需要标签
47 | train_db = tf.data.Dataset.from_tensor_slices(x_train)
48 | train_db = train_db.shuffle(batchsz * 5).batch(batchsz)
49 | # 构建测试集对象
50 | test_db = tf.data.Dataset.from_tensor_slices(x_test)
51 | test_db = test_db.batch(batchsz)
52 | return train_db, test_db
53 |
54 |
55 | class AE(keras.Model):
56 | # 自编码器模型类,包含了 Encoder 和 Decoder2 个子网络
57 | def __init__(self, h_dim):
58 | super(AE, self).__init__()
59 |
60 | # 创建 Encoders 网络,实现在自编码器类的初始化函数中
61 | self.encoder = Sequential([
62 | layers.Dense(256, activation=tf.nn.relu),
63 | layers.Dense(128, activation=tf.nn.relu),
64 | layers.Dense(h_dim)
65 | ])
66 |
67 | # 创建 Decoders 网络
68 | self.decoder = Sequential([
69 | layers.Dense(128, activation=tf.nn.relu),
70 | layers.Dense(256, activation=tf.nn.relu),
71 | layers.Dense(784)
72 | ])
73 |
74 | def call(self, inputs, training=None):
75 | # 前向传播函数
76 | # 编码获得隐藏向量 h,[b, 784] => [b, 20]
77 | h = self.encoder(inputs)
78 | # 解码获得重建图片, [b, 20] => [b, 784]
79 | x_hat = self.decoder(h)
80 |
81 | return x_hat
82 |
83 |
84 | def build_model(h_dim):
85 | # 创建网络对象
86 | model = AE(h_dim)
87 | # 指定输入大小
88 | model.build(input_shape=(None, 784))
89 | # 打印网络信息
90 | model.summary()
91 | return model
92 |
93 |
94 | def train(train_db, model, optimizer, epoch):
95 | # 遍历训练集
96 | for step, x in enumerate(train_db):
97 |
98 | # [b, 28, 28] => [b, 784]
99 | # 打平, [b, 28, 28] => [b, 784]
100 | x = tf.reshape(x, [-1, 784])
101 | # 构建梯度记录器
102 | with tf.GradientTape() as tape:
103 | # 前向计算获得重建的图片
104 | x_rec_logits = model(x)
105 | # 计算重建图片与输入之间的损失函数
106 | rec_loss = tf.losses.binary_crossentropy(x, x_rec_logits, from_logits=True)
107 | # 计算均值
108 | rec_loss = tf.reduce_mean(rec_loss)
109 | # 自动求导,包含了2个子网络的梯度
110 | grads = tape.gradient(rec_loss, model.trainable_variables)
111 | # 自动更新,同时更新2个子网络
112 | optimizer.apply_gradients(zip(grads, model.trainable_variables))
113 |
114 | if step % 100 == 0:
115 | # 间隔性打印训练误差
116 | print(epoch, step, float(rec_loss))
117 |
118 | return model
119 |
120 |
121 | def evaluation(test_db, model, epoch):
122 | # evaluation
123 | # 重建图片,从测试集采样一批图片
124 | x = next(iter(test_db))
125 | # 打平并送入自编码器
126 | logits = model(tf.reshape(x, [-1, 784]))
127 | # 将输出转换为像素值,使用sigmoid函数
128 | x_hat = tf.sigmoid(logits)
129 | # 恢复为 28x28,[b, 784] => [b, 28, 28]
130 | x_hat = tf.reshape(x_hat, [-1, 28, 28])
131 |
132 | # 输入的前50张+重建的前50张图片合并, [b, 28, 28] => [2b, 28, 28]
133 | x_concat = tf.concat([x[:50], x_hat[:50]], axis=0)
134 | # 恢复为0~255范围
135 | x_concat = x_concat.numpy() * 255.
136 | # 转换为整型
137 | x_concat = x_concat.astype(np.uint8)
138 | # 保存图片
139 | save_images(x_concat, './ae_images/rec_epoch_%d.png' % epoch)
140 |
141 |
142 | def main():
143 | h_dim = 20
144 | batchsz = 512
145 | lr = 1e-3
146 |
147 | train_db, test_db = load_dataset(batchsz)
148 | model = build_model(h_dim)
149 | # 创建优化器,并设置学习率
150 | optimizer = tf.optimizers.Adam(lr=lr)
151 | # 训练100个Epoch
152 | for epoch in range(100):
153 | model = train(train_db, model, optimizer, epoch)
154 | evaluation(test_db, model, epoch)
155 |
156 |
157 | if __name__ == '__main__':
158 | main()
159 |
--------------------------------------------------------------------------------
/src/ch12/12.5-fashion-mnist-vae.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 12.5-fashion-mnist-vae.py
6 | @time: 2020/2/29 16:38
7 | @desc: 12.5 VAE图片生成实战的代码
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from PIL import Image
15 | from tensorflow import keras
16 | from tensorflow.keras import layers
17 |
18 | tf.random.set_seed(22)
19 | np.random.seed(22)
20 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
21 |
22 |
23 | def save_images(imgs, name):
24 | # 创建 280x280 大小图片阵列
25 | new_im = Image.new('L', (280, 280))
26 |
27 | index = 0
28 | # 10 行图片阵列
29 | for i in range(0, 280, 28):
30 | # 10 列图片阵列
31 | for j in range(0, 280, 28):
32 | im = imgs[index]
33 | im = Image.fromarray(im, mode='L')
34 | # 写入对应位置
35 | new_im.paste(im, (i, j))
36 | index += 1
37 | # 保存图片阵列
38 | new_im.save(name)
39 |
40 |
41 | def load_dataset(batchsz):
42 | # 加载 Fashion MNIST 图片数据集
43 | (x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
44 | # 归一化
45 | x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.
46 | # 只需要通过图片数据即可构建数据集对象,不需要标签
47 | train_db = tf.data.Dataset.from_tensor_slices(x_train)
48 | train_db = train_db.shuffle(batchsz * 5).batch(batchsz)
49 | # 构建测试集对象
50 | test_db = tf.data.Dataset.from_tensor_slices(x_test)
51 | test_db = test_db.batch(batchsz)
52 | return train_db, test_db
53 |
54 |
55 | class VAE(keras.Model):
56 | # 自编码器模型类,包含了 Encoder 和 Decoder2 个子网络
57 | def __init__(self, z_dim):
58 | super(VAE, self).__init__()
59 |
60 | # Encoder 网络
61 | self.fc1 = layers.Dense(128)
62 | self.fc2 = layers.Dense(z_dim) # 均值输出
63 | self.fc3 = layers.Dense(z_dim) # 方差输出
64 | # Decoder 网络
65 | self.fc4 = layers.Dense(128)
66 | self.fc5 = layers.Dense(784)
67 |
68 | def encoder(self, x):
69 | # 获得编码器的均值和方差
70 | h = tf.nn.relu(self.fc1(x))
71 | # 均值向量
72 | mu = self.fc2(h)
73 | # 方差的 log 向量
74 | log_var = self.fc3(h)
75 | return mu, log_var
76 |
77 | def decoder(self, z):
78 | # 根据隐藏变量 z 生成图片数据
79 | out = tf.nn.relu(self.fc4(z))
80 | out = self.fc5(out)
81 | # 返回图片数据, 784 向量
82 | return out
83 |
84 | def reparameterize(self, mu, log_var):
85 | # reparameterize 技巧,从正态分布采样 epsion
86 | eps = tf.random.normal(tf.shape(log_var))
87 | # 计算标准差
88 | std = tf.exp(log_var) ** 0.5
89 | # reparameterize 技巧
90 | z = mu + std * eps
91 | return z
92 |
93 | def call(self, inputs, training=None):
94 | # 前向计算
95 | # 编码器[b, 784] => [b, z_dim], [b, z_dim]
96 | mu, log_var = self.encoder(inputs)
97 | # 采样 reparameterization trick
98 | z = self.reparameterize(mu, log_var)
99 | # 通过解码器生成
100 | x_hat = self.decoder(z)
101 | # 返回生成样本,及其均值与方差
102 | return x_hat, mu, log_var
103 |
104 |
105 | def build_model(z_dim):
106 | # 创建网络对象
107 | model = VAE(z_dim)
108 | # 指定输入大小
109 | model.build(input_shape=(None, 784))
110 | return model
111 |
112 |
113 | def train(train_db, model, optimizer, epoch):
114 | # 遍历训练集
115 | for step, x in enumerate(train_db):
116 |
117 | # [b, 28, 28] => [b, 784]
118 | # 打平, [b, 28, 28] => [b, 784]
119 | x = tf.reshape(x, [-1, 784])
120 | # 构建梯度记录器
121 | with tf.GradientTape() as tape:
122 | # 前向计算获得重建的图片
123 | x_rec_logits, mu, log_var = model(x)
124 | # 重建损失值计算
125 | rec_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=x, logits=x_rec_logits)
126 | rec_loss = tf.reduce_sum(rec_loss) / x.shape[0]
127 |
128 | # 计算 KL 散度 N(mu, var) VS N(0, 1)
129 | # 公式参考: https://stats.stackexchange.com/questions/7440/kldivergence-between-two-univariate-gaussians
130 | kl_div = -0.5 * (log_var + 1 - mu ** 2 - tf.exp(log_var))
131 | kl_div = tf.reduce_sum(kl_div) / x.shape[0]
132 | # 合并误差项
133 | loss = rec_loss + 1. * kl_div
134 |
135 | # 自动求导
136 | grads = tape.gradient(loss, model.trainable_variables)
137 | # 自动更新
138 | optimizer.apply_gradients(zip(grads, model.trainable_variables))
139 |
140 | if step % 100 == 0:
141 | # 打印训练误差
142 | print("epoch=%s," % epoch, "step=%s," % step, "kl_div=%f," % float(kl_div), "rec_loss=%f" % float(rec_loss))
143 |
144 | return model
145 |
146 |
147 | def evaluation(test_db, model, epoch, batchsz, z_dim):
148 | z = tf.random.normal((batchsz, z_dim))
149 | # 仅通过解码器生成图片
150 | logits = model.decoder(z)
151 | # 转换为像素范围
152 | x_hat = tf.sigmoid(logits)
153 | x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() * 255.
154 | x_hat = x_hat.astype(np.uint8)
155 | save_images(x_hat, './vae_images/epoch_%d_sampled.png' % epoch) # 保存生成图片
156 |
157 | # 重建图片,从测试集采样图片
158 | x = next(iter(test_db))
159 | # 打平并送入自编码器
160 | logits, _, _ = model(tf.reshape(x, [-1, 784]))
161 | # 将输出转换为像素值
162 | x_hat = tf.sigmoid(logits)
163 | # 恢复为 28x28,[b, 784] => [b, 28, 28]
164 | x_hat = tf.reshape(x_hat, [-1, 28, 28])
165 | # 输入的前 50 张+重建的前 50 张图片合并, [b, 28, 28] => [2b, 28, 28]
166 | x_concat = tf.concat([x[:50], x_hat[:50]], axis=0)
167 | x_concat = x_concat.numpy() * 255. # 恢复为 0~255 范围
168 | x_concat = x_concat.astype(np.uint8)
169 | save_images(x_concat, './vae_images/epoch_%d_rec.png' % epoch) # 保存重建图片
170 |
171 |
172 | def main():
173 | z_dim = 10
174 | batchsz = 512
175 | lr = 1e-3
176 |
177 | train_db, test_db = load_dataset(batchsz)
178 | model = build_model(z_dim)
179 | # 创建优化器,并设置学习率
180 | optimizer = tf.optimizers.Adam(lr=lr)
181 | # 训练100个Epoch
182 | for epoch in range(100):
183 | model = train(train_db, model, optimizer, epoch)
184 | evaluation(test_db, model, epoch, batchsz, z_dim)
185 |
186 |
187 | if __name__ == '__main__':
188 | main()
189 |
--------------------------------------------------------------------------------
/src/ch12/ae_images/rec_epoch_99.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch12/ae_images/rec_epoch_99.png
--------------------------------------------------------------------------------
/src/ch12/vae_images/epoch_99_rec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch12/vae_images/epoch_99_rec.png
--------------------------------------------------------------------------------
/src/ch12/vae_images/epoch_99_sampled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Relph1119/deeplearning-with-tensorflow-notes/77d7a04bc933a14d6d86cba4f9fd54fd0d05ddbe/src/ch12/vae_images/epoch_99_sampled.png
--------------------------------------------------------------------------------
/src/ch13/13.3-faces-dcgan.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 13.3-faces-dcgan.py
6 | @time: 2020/2/29 20:51
7 | @desc: 13.3 DCGAN实战的代码
8 | 将face文件夹放到ch13目录下,读取路径为/ch13/face/*.
9 | """
10 |
11 | import glob
12 | import os
13 |
14 | import numpy as np
15 | import tensorflow as tf
16 | from PIL import Image
17 | from tensorflow import keras
18 |
19 | from src.ch13.dataset import make_anime_dataset
20 | from src.ch13.gan import Generator, Discriminator
21 |
22 | os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION'] = 'false'
23 |
24 | # 获取 GPU 设备列表
25 | gpus = tf.config.experimental.list_physical_devices('GPU')
26 | if gpus:
27 | try:
28 | # 设置 GPU 为增长式占用
29 | for gpu in gpus:
30 | tf.config.experimental.set_memory_growth(gpu, True)
31 | except RuntimeError as e:
32 | # 打印异常
33 | print(e)
34 |
35 |
36 | def save_result(val_out, val_block_size, image_path, color_mode):
37 | def preprocess(img):
38 | img = ((img + 1.0) * 127.5).astype(np.uint8)
39 | # img = img.astype(np.uint8)
40 | return img
41 |
42 | preprocesed = preprocess(val_out)
43 | final_image = np.array([])
44 | single_row = np.array([])
45 | for b in range(val_out.shape[0]):
46 | # concat image into a row
47 | if single_row.size == 0:
48 | single_row = preprocesed[b, :, :, :]
49 | else:
50 | single_row = np.concatenate((single_row, preprocesed[b, :, :, :]), axis=1)
51 |
52 | # concat image row to final_image
53 | if (b + 1) % val_block_size == 0:
54 | if final_image.size == 0:
55 | final_image = single_row
56 | else:
57 | final_image = np.concatenate((final_image, single_row), axis=0)
58 |
59 | # reset single row
60 | single_row = np.array([])
61 |
62 | if final_image.shape[2] == 1:
63 | final_image = np.squeeze(final_image, axis=2)
64 | Image.fromarray(final_image).save(image_path)
65 |
66 |
67 | def celoss_ones(logits):
68 | # 计算属于与标签为1的交叉熵
69 | y = tf.ones_like(logits)
70 | loss = keras.losses.binary_crossentropy(y, logits, from_logits=True)
71 | return tf.reduce_mean(loss)
72 |
73 |
74 | def celoss_zeros(logits):
75 | # 计算属于与便签为0的交叉熵
76 | y = tf.zeros_like(logits)
77 | loss = keras.losses.binary_crossentropy(y, logits, from_logits=True)
78 | return tf.reduce_mean(loss)
79 |
80 |
81 | def d_loss_fn(generator, discriminator, batch_z, batch_x, is_training):
82 | # 计算判别器的误差函数
83 | # 采样生成图片
84 | fake_image = generator(batch_z, is_training)
85 | # 判定生成图片
86 | d_fake_logits = discriminator(fake_image, is_training)
87 | # 判定真实图片
88 | d_real_logits = discriminator(batch_x, is_training)
89 | # 真实图片与1之间的误差
90 | d_loss_real = celoss_ones(d_real_logits)
91 | # 生成图片与0之间的误差
92 | d_loss_fake = celoss_zeros(d_fake_logits)
93 | # 合并误差
94 | loss = d_loss_fake + d_loss_real
95 |
96 | return loss
97 |
98 |
99 | def g_loss_fn(generator, discriminator, batch_z, is_training):
100 | # 采样生成图片
101 | fake_image = generator(batch_z, is_training)
102 | # 在训练生成网络时,需要迫使生成图片判定为真
103 | d_fake_logits = discriminator(fake_image, is_training)
104 | # 计算生成图片与1之间的误差
105 | loss = celoss_ones(d_fake_logits)
106 |
107 | return loss
108 |
109 |
110 | def main():
111 | tf.random.set_seed(3333)
112 | np.random.seed(3333)
113 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
114 |
115 | z_dim = 100 # 隐藏向量z的长度
116 | epochs = 3000000 # 训练步数
117 | batch_size = 64 # batch size
118 | learning_rate = 0.0002
119 | is_training = True
120 |
121 | # 获取数据集路径
122 | img_path = glob.glob(r'.\faces\*.jpg')
123 | print('images num:', len(img_path))
124 | # 构建数据集对象
125 | dataset, img_shape, _ = make_anime_dataset(img_path, batch_size, resize=64)
126 | print(dataset, img_shape)
127 | sample = next(iter(dataset)) # 采样
128 | print(sample.shape, tf.reduce_max(sample).numpy(),
129 | tf.reduce_min(sample).numpy())
130 | dataset = dataset.repeat(100) # 重复循环
131 | db_iter = iter(dataset)
132 |
133 | discriminator, generator = build_network(z_dim)
134 | # 分别为生成器和判别器创建优化器
135 | g_optimizer = keras.optimizers.Adam(learning_rate=learning_rate, beta_1=0.5)
136 | d_optimizer = keras.optimizers.Adam(learning_rate=learning_rate, beta_1=0.5)
137 |
138 | generator.load_weights('generator.ckpt')
139 | discriminator.load_weights('discriminator.ckpt')
140 | print('Loaded ckpt!!')
141 |
142 | d_losses, g_losses = [], []
143 | for epoch in range(epochs): # 训练epochs次
144 | train(batch_size, d_losses, d_optimizer, db_iter, discriminator, epoch, g_losses, g_optimizer, generator,
145 | is_training, z_dim)
146 |
147 |
148 | def train(batch_size, d_losses, d_optimizer, db_iter, discriminator, epoch, g_losses, g_optimizer, generator,
149 | is_training, z_dim):
150 | # 1. 训练判别器
151 | for _ in range(1):
152 | # 采样隐藏向量
153 | batch_z = tf.random.normal([batch_size, z_dim])
154 | batch_x = next(db_iter) # 采样真实图片
155 | # 判别器前向计算
156 | with tf.GradientTape() as tape:
157 | d_loss = d_loss_fn(generator, discriminator, batch_z, batch_x, is_training)
158 | grads = tape.gradient(d_loss, discriminator.trainable_variables)
159 | d_optimizer.apply_gradients(zip(grads, discriminator.trainable_variables))
160 | # 2. 训练生成器
161 | # 采样隐藏向量
162 | batch_z = tf.random.normal([batch_size, z_dim])
163 | batch_x = next(db_iter) # 采样真实图片
164 | # 生成器前向计算
165 | with tf.GradientTape() as tape:
166 | g_loss = g_loss_fn(generator, discriminator, batch_z, is_training)
167 | grads = tape.gradient(g_loss, generator.trainable_variables)
168 | g_optimizer.apply_gradients(zip(grads, generator.trainable_variables))
169 | if epoch % 100 == 0:
170 | print(epoch, 'd-loss:', float(d_loss), 'g-loss:', float(g_loss))
171 | # 可视化
172 | z = tf.random.normal([100, z_dim])
173 | fake_image = generator(z, training=False)
174 | img_path = os.path.join('./gan_images', 'gan-%d.png' % epoch)
175 | save_result(fake_image.numpy(), 10, img_path, color_mode='P')
176 |
177 | d_losses.append(float(d_loss))
178 | g_losses.append(float(g_loss))
179 |
180 | if epoch % 10000 == 1:
181 | generator.save_weights('generator.ckpt')
182 | discriminator.save_weights('discriminator.ckpt')
183 |
184 |
185 | def build_network(z_dim):
186 | # 创建生成器
187 | generator = Generator()
188 | generator.build(input_shape=(4, z_dim))
189 | # 创建判别器
190 | discriminator = Discriminator()
191 | discriminator.build(input_shape=(4, 64, 64, 3))
192 | return discriminator, generator
193 |
194 |
195 | if __name__ == '__main__':
196 | main()
197 |
--------------------------------------------------------------------------------
/src/ch13/13.8-faces-wgan-gp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 13.8-faces-wgan-gp.py
6 | @time: 2020/2/29 20:51
7 | @desc: 13.8 WGAN-GP实战的代码
8 | 将face文件夹放到ch13目录下,读取路径为/ch13/face/*.
9 | """
10 |
11 | import glob
12 | import os
13 |
14 | import numpy as np
15 | import tensorflow as tf
16 | from PIL import Image
17 | from tensorflow import keras
18 |
19 | from src.ch13.dataset import make_anime_dataset
20 | from src.ch13.gan import Generator, Discriminator
21 |
22 | os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION'] = 'false'
23 |
24 | # 获取 GPU 设备列表
25 | gpus = tf.config.experimental.list_physical_devices('GPU')
26 | if gpus:
27 | try:
28 | # 设置 GPU 为增长式占用
29 | for gpu in gpus:
30 | tf.config.experimental.set_memory_growth(gpu, True)
31 | except RuntimeError as e:
32 | # 打印异常
33 | print(e)
34 |
35 |
36 | def save_result(val_out, val_block_size, image_path, color_mode):
37 | def preprocess(img):
38 | img = ((img + 1.0) * 127.5).astype(np.uint8)
39 | # img = img.astype(np.uint8)
40 | return img
41 |
42 | preprocesed = preprocess(val_out)
43 | final_image = np.array([])
44 | single_row = np.array([])
45 | for b in range(val_out.shape[0]):
46 | # concat image into a row
47 | if single_row.size == 0:
48 | single_row = preprocesed[b, :, :, :]
49 | else:
50 | single_row = np.concatenate((single_row, preprocesed[b, :, :, :]), axis=1)
51 |
52 | # concat image row to final_image
53 | if (b + 1) % val_block_size == 0:
54 | if final_image.size == 0:
55 | final_image = single_row
56 | else:
57 | final_image = np.concatenate((final_image, single_row), axis=0)
58 |
59 | # reset single row
60 | single_row = np.array([])
61 |
62 | if final_image.shape[2] == 1:
63 | final_image = np.squeeze(final_image, axis=2)
64 | Image.fromarray(final_image).save(image_path)
65 |
66 |
67 | def celoss_ones(logits):
68 | # 计算属于与标签为1的交叉熵
69 | y = tf.ones_like(logits)
70 | loss = keras.losses.binary_crossentropy(y, logits, from_logits=True)
71 | return tf.reduce_mean(loss)
72 |
73 |
74 | def celoss_zeros(logits):
75 | # 计算属于与便签为0的交叉熵
76 | y = tf.zeros_like(logits)
77 | loss = keras.losses.binary_crossentropy(y, logits, from_logits=True)
78 | return tf.reduce_mean(loss)
79 |
80 |
81 | def gradient_penalty(discriminator, batch_x, fake_image):
82 | # 梯度惩罚项计算函数
83 | batchsz = batch_x.shape[0]
84 | # 每个样本均随机采样 t,用于插值
85 | t = tf.random.uniform([batchsz, 1, 1, 1])
86 | # 自动扩展为 x 的形状, [b, 1, 1, 1] => [b, h, w, c]
87 | t = tf.broadcast_to(t, batch_x.shape)
88 | # 在真假图片之间做线性插值
89 | interplate = t * batch_x + (1 - t) * fake_image
90 | # 在梯度环境中计算 D 对插值样本的梯度
91 | with tf.GradientTape() as tape:
92 | tape.watch([interplate]) # 加入梯度观察列表
93 | d_interplote_logits = discriminator(interplate)
94 | grads = tape.gradient(d_interplote_logits, interplate)
95 |
96 | # 计算每个样本的梯度的范数:[b, h, w, c] => [b, -1]
97 | grads = tf.reshape(grads, [grads.shape[0], -1])
98 | gp = tf.norm(grads, axis=1) # [b]
99 | # 计算梯度惩罚项
100 | gp = tf.reduce_mean((gp - 1.) ** 2)
101 | return gp
102 |
103 |
104 | def d_loss_fn(generator, discriminator, batch_z, batch_x, is_training):
105 | # 计算 D 的损失函数
106 | fake_image = generator(batch_z, is_training) # 假样本
107 | d_fake_logits = discriminator(fake_image, is_training) # 假样本的输出
108 | d_real_logits = discriminator(batch_x, is_training) # 真样本的输出
109 | # 计算梯度惩罚项
110 | gp = gradient_penalty(discriminator, batch_x, fake_image)
111 | # WGAN-GP D 损失函数的定义,这里并不是计算交叉熵,而是直接最大化正样本的输出
112 | # 最小化假样本的输出和梯度惩罚项
113 | loss = tf.reduce_mean(d_fake_logits) - tf.reduce_mean(d_real_logits) + 10. * gp
114 | return loss, gp
115 |
116 |
117 | def g_loss_fn(generator, discriminator, batch_z, is_training):
118 | # 生成器的损失函数
119 | fake_image = generator(batch_z, is_training)
120 | d_fake_logits = discriminator(fake_image, is_training)
121 | # WGAN-GP G 损失函数,最大化假样本的输出值
122 | loss = - tf.reduce_mean(d_fake_logits)
123 | return loss
124 |
125 |
126 | def main():
127 | tf.random.set_seed(3333)
128 | np.random.seed(3333)
129 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
130 |
131 | z_dim = 100 # 隐藏向量z的长度
132 | epochs = 3000000 # 训练步数
133 | batch_size = 64 # batch size
134 | learning_rate = 0.0002
135 | is_training = True
136 |
137 | # 获取数据集路径
138 | img_path = glob.glob(r'.\faces\*.jpg')
139 | print('images num:', len(img_path))
140 | # 构建数据集对象
141 | dataset, img_shape, _ = make_anime_dataset(img_path, batch_size, resize=64)
142 | print(dataset, img_shape)
143 | sample = next(iter(dataset)) # 采样
144 | print(sample.shape, tf.reduce_max(sample).numpy(),
145 | tf.reduce_min(sample).numpy())
146 | dataset = dataset.repeat(100) # 重复循环
147 | db_iter = iter(dataset)
148 |
149 | discriminator, generator = build_network(z_dim)
150 | # 分别为生成器和判别器创建优化器
151 | g_optimizer = keras.optimizers.Adam(learning_rate=learning_rate, beta_1=0.5)
152 | d_optimizer = keras.optimizers.Adam(learning_rate=learning_rate, beta_1=0.5)
153 |
154 | generator.load_weights('generator.ckpt')
155 | discriminator.load_weights('discriminator.ckpt')
156 | print('Loaded ckpt!!')
157 |
158 | d_losses, g_losses = [], []
159 | for epoch in range(epochs): # 训练epochs次
160 | train(batch_size, d_losses, d_optimizer, db_iter, discriminator, epoch, g_losses, g_optimizer, generator,
161 | is_training, z_dim)
162 |
163 |
164 | def train(batch_size, d_losses, d_optimizer, db_iter, discriminator, epoch, g_losses, g_optimizer, generator,
165 | is_training, z_dim):
166 | # 1. 训练判别器
167 | for _ in range(1):
168 | # 采样隐藏向量
169 | batch_z = tf.random.normal([batch_size, z_dim])
170 | batch_x = next(db_iter) # 采样真实图片
171 | # 判别器前向计算
172 | with tf.GradientTape() as tape:
173 | d_loss, gp = d_loss_fn(generator, discriminator, batch_z, batch_x, is_training)
174 | grads = tape.gradient(d_loss, discriminator.trainable_variables)
175 | d_optimizer.apply_gradients(zip(grads, discriminator.trainable_variables))
176 |
177 | # 2. 训练生成器
178 | # 采样隐藏向量
179 | batch_z = tf.random.normal([batch_size, z_dim])
180 | batch_x = next(db_iter) # 采样真实图片
181 | # 生成器前向计算
182 | with tf.GradientTape() as tape:
183 | g_loss = g_loss_fn(generator, discriminator, batch_z, is_training)
184 |
185 | grads = tape.gradient(g_loss, generator.trainable_variables)
186 | g_optimizer.apply_gradients(zip(grads, generator.trainable_variables))
187 | if epoch % 100 == 0:
188 | print(epoch, 'd-loss:', float(d_loss), 'g-loss:', float(g_loss))
189 | # 可视化
190 | z = tf.random.normal([100, z_dim])
191 | fake_image = generator(z, training=False)
192 | img_path = os.path.join('./gan_images', 'gan-%d.png' % epoch)
193 | save_result(fake_image.numpy(), 10, img_path, color_mode='P')
194 |
195 | d_losses.append(float(d_loss))
196 | g_losses.append(float(g_loss))
197 |
198 | if epoch % 10000 == 1:
199 | generator.save_weights('generator.ckpt')
200 | discriminator.save_weights('discriminator.ckpt')
201 |
202 |
203 | def build_network(z_dim):
204 | # 创建生成器
205 | generator = Generator()
206 | generator.build(input_shape=(4, z_dim))
207 | # 创建判别器
208 | discriminator = Discriminator()
209 | discriminator.build(input_shape=(4, 64, 64, 3))
210 | return discriminator, generator
211 |
212 |
213 | if __name__ == '__main__':
214 | main()
215 |
--------------------------------------------------------------------------------
/src/ch13/dataset.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: dataset.py
6 | @time: 2020/2/29 21:08
7 | @desc:
8 | """
9 |
10 | import multiprocessing
11 |
12 | import tensorflow as tf
13 |
14 |
15 | def make_anime_dataset(img_paths, batch_size, resize=64, drop_remainder=True, shuffle=True, repeat=1):
16 | # @tf.function
17 | def _map_fn(img):
18 | img = tf.image.resize(img, [resize, resize])
19 | # img = tf.image.random_crop(img,[resize, resize])
20 | # img = tf.image.random_flip_left_right(img)
21 | # img = tf.image.random_flip_up_down(img)
22 | img = tf.clip_by_value(img, 0, 255)
23 | img = img / 127.5 - 1 # -1~1
24 | return img
25 |
26 | dataset = disk_image_batch_dataset(img_paths,
27 | batch_size,
28 | drop_remainder=drop_remainder,
29 | map_fn=_map_fn,
30 | shuffle=shuffle,
31 | repeat=repeat)
32 | img_shape = (resize, resize, 3)
33 | len_dataset = len(img_paths) // batch_size
34 |
35 | return dataset, img_shape, len_dataset
36 |
37 |
38 | def batch_dataset(dataset,
39 | batch_size,
40 | drop_remainder=True,
41 | n_prefetch_batch=1,
42 | filter_fn=None,
43 | map_fn=None,
44 | n_map_threads=None,
45 | filter_after_map=False,
46 | shuffle=True,
47 | shuffle_buffer_size=None,
48 | repeat=None):
49 | # set defaults
50 | if n_map_threads is None:
51 | n_map_threads = multiprocessing.cpu_count()
52 | if shuffle and shuffle_buffer_size is None:
53 | shuffle_buffer_size = max(batch_size * 128, 2048) # set the minimum buffer size as 2048
54 |
55 | # [*] it is efficient to conduct `shuffle` before `map`/`filter` because `map`/`filter` is sometimes costly
56 | if shuffle:
57 | dataset = dataset.shuffle(shuffle_buffer_size)
58 |
59 | if not filter_after_map:
60 | if filter_fn:
61 | dataset = dataset.filter(filter_fn)
62 |
63 | if map_fn:
64 | dataset = dataset.map(map_fn, num_parallel_calls=n_map_threads)
65 |
66 | else: # [*] this is slower
67 | if map_fn:
68 | dataset = dataset.map(map_fn, num_parallel_calls=n_map_threads)
69 |
70 | if filter_fn:
71 | dataset = dataset.filter(filter_fn)
72 |
73 | dataset = dataset.batch(batch_size, drop_remainder=drop_remainder)
74 |
75 | dataset = dataset.repeat(repeat).prefetch(n_prefetch_batch)
76 |
77 | return dataset
78 |
79 |
80 | def memory_data_batch_dataset(memory_data,
81 | batch_size,
82 | drop_remainder=True,
83 | n_prefetch_batch=1,
84 | filter_fn=None,
85 | map_fn=None,
86 | n_map_threads=None,
87 | filter_after_map=False,
88 | shuffle=True,
89 | shuffle_buffer_size=None,
90 | repeat=None):
91 | """Batch dataset of memory data.
92 | Parameters
93 | ----------
94 | memory_data : nested structure of tensors/ndarrays/lists
95 | """
96 | dataset = tf.data.Dataset.from_tensor_slices(memory_data)
97 | dataset = batch_dataset(dataset,
98 | batch_size,
99 | drop_remainder=drop_remainder,
100 | n_prefetch_batch=n_prefetch_batch,
101 | filter_fn=filter_fn,
102 | map_fn=map_fn,
103 | n_map_threads=n_map_threads,
104 | filter_after_map=filter_after_map,
105 | shuffle=shuffle,
106 | shuffle_buffer_size=shuffle_buffer_size,
107 | repeat=repeat)
108 | return dataset
109 |
110 |
111 | def disk_image_batch_dataset(img_paths,
112 | batch_size,
113 | labels=None,
114 | drop_remainder=True,
115 | n_prefetch_batch=1,
116 | filter_fn=None,
117 | map_fn=None,
118 | n_map_threads=None,
119 | filter_after_map=False,
120 | shuffle=True,
121 | shuffle_buffer_size=None,
122 | repeat=None):
123 | """Batch dataset of disk image for PNG and JPEG.
124 | Parameters
125 | ----------
126 | img_paths : 1d-tensor/ndarray/list of str
127 | labels : nested structure of tensors/ndarrays/lists
128 | """
129 | if labels is None:
130 | memory_data = img_paths
131 | else:
132 | memory_data = (img_paths, labels)
133 |
134 | def parse_fn(path, *label):
135 | img = tf.io.read_file(path)
136 | img = tf.image.decode_jpeg(img, channels=3) # fix channels to 3
137 | return (img,) + label
138 |
139 | if map_fn: # fuse `map_fn` and `parse_fn`
140 | def map_fn_(*args):
141 | return map_fn(*parse_fn(*args))
142 | else:
143 | map_fn_ = parse_fn
144 |
145 | dataset = memory_data_batch_dataset(memory_data,
146 | batch_size,
147 | drop_remainder=drop_remainder,
148 | n_prefetch_batch=n_prefetch_batch,
149 | filter_fn=filter_fn,
150 | map_fn=map_fn_,
151 | n_map_threads=n_map_threads,
152 | filter_after_map=filter_after_map,
153 | shuffle=shuffle,
154 | shuffle_buffer_size=shuffle_buffer_size,
155 | repeat=repeat)
156 |
157 | return dataset
158 |
--------------------------------------------------------------------------------
/src/ch13/gan.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: gan.py
6 | @time: 2020/2/29 21:09
7 | @desc:
8 | """
9 |
10 | import tensorflow as tf
11 | from tensorflow import keras
12 | from tensorflow.keras import layers
13 |
14 |
15 | class Generator(keras.Model):
16 | # 生成器网络类
17 | def __init__(self):
18 | super(Generator, self).__init__()
19 | filter = 64
20 | # 转置卷积层1,输出channel为filter*8,核大小4,步长1,不使用padding,不使用偏置
21 | self.conv1 = layers.Conv2DTranspose(filter * 8, 4, 1, 'valid', use_bias=False)
22 | self.bn1 = layers.BatchNormalization()
23 | # 转置卷积层2
24 | self.conv2 = layers.Conv2DTranspose(filter * 4, 4, 2, 'same', use_bias=False)
25 | self.bn2 = layers.BatchNormalization()
26 | # 转置卷积层3
27 | self.conv3 = layers.Conv2DTranspose(filter * 2, 4, 2, 'same', use_bias=False)
28 | self.bn3 = layers.BatchNormalization()
29 | # 转置卷积层4
30 | self.conv4 = layers.Conv2DTranspose(filter * 1, 4, 2, 'same', use_bias=False)
31 | self.bn4 = layers.BatchNormalization()
32 | # 转置卷积层5
33 | self.conv5 = layers.Conv2DTranspose(3, 4, 2, 'same', use_bias=False)
34 |
35 | def call(self, inputs, training=None):
36 | x = inputs # [z, 100]
37 | # Reshape乘4D张量,方便后续转置卷积运算:(b, 1, 1, 100)
38 | x = tf.reshape(x, (x.shape[0], 1, 1, x.shape[1]))
39 | x = tf.nn.relu(x) # 激活函数
40 | # 转置卷积-BN-激活函数:(b, 4, 4, 512)
41 | x = tf.nn.relu(self.bn1(self.conv1(x), training=training))
42 | # 转置卷积-BN-激活函数:(b, 8, 8, 256)
43 | x = tf.nn.relu(self.bn2(self.conv2(x), training=training))
44 | # 转置卷积-BN-激活函数:(b, 16, 16, 128)
45 | x = tf.nn.relu(self.bn3(self.conv3(x), training=training))
46 | # 转置卷积-BN-激活函数:(b, 32, 32, 64)
47 | x = tf.nn.relu(self.bn4(self.conv4(x), training=training))
48 | # 转置卷积-激活函数:(b, 64, 64, 3)
49 | x = self.conv5(x)
50 | x = tf.tanh(x) # 输出x范围-1~1,与预处理一致
51 |
52 | return x
53 |
54 |
55 | class Discriminator(keras.Model):
56 | # 判别器类
57 | def __init__(self):
58 | super(Discriminator, self).__init__()
59 | filter = 64
60 | # 卷积层1
61 | self.conv1 = layers.Conv2D(filter, 4, 2, 'valid', use_bias=False)
62 | self.bn1 = layers.BatchNormalization()
63 | # 卷积层2
64 | self.conv2 = layers.Conv2D(filter * 2, 4, 2, 'valid', use_bias=False)
65 | self.bn2 = layers.BatchNormalization()
66 | # 卷积层3
67 | self.conv3 = layers.Conv2D(filter * 4, 4, 2, 'valid', use_bias=False)
68 | self.bn3 = layers.BatchNormalization()
69 | # 卷积层4
70 | self.conv4 = layers.Conv2D(filter * 8, 3, 1, 'valid', use_bias=False)
71 | self.bn4 = layers.BatchNormalization()
72 | # 卷积层5
73 | self.conv5 = layers.Conv2D(filter * 16, 3, 1, 'valid', use_bias=False)
74 | self.bn5 = layers.BatchNormalization()
75 | # 全局池化层
76 | self.pool = layers.GlobalAveragePooling2D()
77 | # 特征打平层
78 | self.flatten = layers.Flatten()
79 | # 2分类全连接层
80 | self.fc = layers.Dense(1)
81 |
82 | def call(self, inputs, training=None):
83 | # 卷积-BN-激活函数:(4, 31, 31, 64)
84 | x = tf.nn.leaky_relu(self.bn1(self.conv1(inputs), training=training))
85 | # 卷积-BN-激活函数:(4, 14, 14, 128)
86 | x = tf.nn.leaky_relu(self.bn2(self.conv2(x), training=training))
87 | # 卷积-BN-激活函数:(4, 6, 6, 256)
88 | x = tf.nn.leaky_relu(self.bn3(self.conv3(x), training=training))
89 | # 卷积-BN-激活函数:(4, 4, 4, 512)
90 | x = tf.nn.leaky_relu(self.bn4(self.conv4(x), training=training))
91 | # 卷积-BN-激活函数:(4, 2, 2, 1024)
92 | x = tf.nn.leaky_relu(self.bn5(self.conv5(x), training=training))
93 | # 卷积-BN-激活函数:(4, 1024)
94 | x = self.pool(x)
95 | # 打平
96 | x = self.flatten(x)
97 | # 输出,[b, 1024] => [b, 1]
98 | logits = self.fc(x)
99 |
100 | return logits
101 |
102 |
103 | def main():
104 | d = Discriminator()
105 | g = Generator()
106 |
107 | x = tf.random.normal([2, 64, 64, 3])
108 | z = tf.random.normal([2, 100])
109 |
110 | prob = d(x)
111 | print(prob)
112 | x_hat = g(z)
113 | print(x_hat.shape)
114 |
115 |
116 | if __name__ == '__main__':
117 | main()
118 |
--------------------------------------------------------------------------------
/src/ch14/14.1.2-cartpole-v1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 14.1.2-cartpole-v1.py
6 | @time: 2020/2/29 22:20
7 | @desc: 14.1.2 Gym平台-平衡杆游戏的交互代码
8 | 安装gym:
9 | 1. 将gym文件夹放入venv/Lib/site-packages目录下
10 | 2. cd venv/Lib/site-packages/gym
11 | 3. 执行命令pip install -e .
12 | 4. 检查gym包是否已经安装好
13 | """
14 |
15 | # 导入 gym 游戏平台
16 | import gym
17 |
18 | # 创建平衡杆游戏环境
19 | env = gym.make("CartPole-v1")
20 | # 复位游戏,回到初始状态
21 | observation = env.reset()
22 | # 循环交互 1000 次
23 | for _ in range(1000):
24 | # 显示当前时间戳的游戏画面
25 | env.render()
26 | # 随机生成一个动作
27 | action = env.action_space.sample()
28 | # 与环境交互,返回新的状态,奖励,是否结束标志,其他信息
29 | observation, reward, done, info = env.step(action)
30 | # 游戏回合结束,复位状态
31 | if done:
32 | observation = env.reset()
33 |
34 | # 销毁游戏环境
35 | env.close()
36 |
--------------------------------------------------------------------------------
/src/ch14/14.1.5-cartpole-policy.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 14.1.5-cartpole-policy.py
6 | @time: 2020/2/29 22:37
7 | @desc: 14.1.5 平衡杆游戏实战的代码(策略网络Policy)
8 | """
9 |
10 | import os
11 |
12 | import gym
13 | import matplotlib
14 | import numpy as np
15 | from matplotlib import pyplot as plt
16 |
17 | # Default parameters for plots
18 | matplotlib.rcParams['font.size'] = 18
19 | matplotlib.rcParams['figure.titlesize'] = 18
20 | matplotlib.rcParams['figure.figsize'] = [9, 7]
21 | matplotlib.rcParams['font.family'] = ['KaiTi']
22 | matplotlib.rcParams['axes.unicode_minus'] = False
23 |
24 | import tensorflow as tf
25 | from tensorflow import keras
26 | from tensorflow.keras import layers, optimizers
27 |
28 | env = gym.make('CartPole-v1') # 创建游戏环境
29 | env.seed(2333)
30 | tf.random.set_seed(2333)
31 | np.random.seed(2333)
32 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
33 |
34 |
35 | class Policy(keras.Model):
36 | # 策略网络,生成动作的概率分布
37 | def __init__(self, learning_rate, gamma):
38 | super(Policy, self).__init__()
39 | self.data = [] # 存储轨迹
40 | # 输入为长度为4的向量,输出为左、右2个动作
41 | self.fc1 = layers.Dense(128, kernel_initializer='he_normal')
42 | self.fc2 = layers.Dense(2, kernel_initializer='he_normal')
43 | # 网络优化器
44 | self.optimizer = optimizers.Adam(lr=learning_rate)
45 | self.gamma = gamma
46 |
47 | def call(self, inputs, training=None):
48 | # 状态输入s的shape为向量:[4]
49 | x = tf.nn.relu(self.fc1(inputs))
50 | x = tf.nn.softmax(self.fc2(x), axis=1)
51 | return x
52 |
53 | def put_data(self, item):
54 | """
55 | 在交互时,将每个时间戳上的状态输入s[t],动作分布输出a[t],
56 | 环境奖励r[t]和新状态s[t+1]作为一个4元组item记录下来.
57 | """
58 | # 记录r,log_P(a|s)
59 | self.data.append(item)
60 |
61 | def train_net(self, tape):
62 | # 计算梯度并更新策略网络参数。tape为梯度记录器
63 | # 终结状态的初始回报为0
64 | R = 0
65 | # 逆序取轨迹数据
66 | for r, log_prob in self.data[::-1]:
67 | # 累加计算每个时间戳上的回报
68 | R = r + self.gamma * R
69 | loss = -log_prob * R
70 | with tape.stop_recording():
71 | # 优化策略网络
72 | grads = tape.gradient(loss, self.trainable_variables)
73 | self.optimizer.apply_gradients(zip(grads, self.trainable_variables))
74 |
75 | # 清空轨迹
76 | self.data = []
77 |
78 |
79 | def train(epoch, pi, print_interval, returns, score):
80 | s = env.reset() # 回到游戏初始状态,返回s0
81 | with tf.GradientTape(persistent=True) as tape:
82 | for t in range(501): # CartPole-v1 forced to terminates at 500 step.
83 | # 送入状态向量,获取策略
84 | s = tf.constant(s, dtype=tf.float32)
85 | # s: [4] => [1,4]
86 | s = tf.expand_dims(s, axis=0)
87 | # 动作分布:[1,2]
88 | prob = pi(s)
89 | # 从类别分布中采样1个动作, shape: [1]
90 | a = tf.random.categorical(tf.math.log(prob), 1)[0]
91 | # Tensor转数字
92 | a = int(a)
93 | s_prime, r, done, info = env.step(a)
94 | # 记录动作a和动作产生的奖励r
95 | # prob shape:[1,2]
96 | pi.put_data((r, tf.math.log(prob[0][a])))
97 | # 刷新状态
98 | s = s_prime
99 | # 累积奖励
100 | score += r
101 |
102 | if epoch > 1000:
103 | env.render()
104 |
105 | # 当前episode终止
106 | if done:
107 | break
108 |
109 | # episode终止后,训练一次网络
110 | pi.train_net(tape)
111 | del tape
112 |
113 | if epoch % print_interval == 0 and epoch != 0:
114 | returns.append(score / print_interval)
115 | # 每20次的平均得分
116 | print(f"# of episode :{epoch}, avg score : {score / print_interval}")
117 | score = 0.0
118 |
119 |
120 | def main():
121 | learning_rate = 0.0002
122 | gamma = 0.98
123 |
124 | pi = Policy(learning_rate, gamma) # 创建策略网络
125 | pi(tf.random.normal((4, 4)))
126 | pi.summary()
127 | score = 0.0 # 计分
128 | print_interval = 20 # 打印间隔
129 | returns = [0]
130 | epoch_num = 400
131 |
132 | for epoch in range(1, epoch_num + 1):
133 | train(epoch, pi, print_interval, returns, score)
134 |
135 | # 关闭环境
136 | env.close()
137 |
138 | plt.plot(np.arange(len(returns)) * print_interval, returns)
139 | plt.plot(np.arange(len(returns)) * print_interval, returns, 's')
140 | plt.xlabel('回合数')
141 | plt.ylabel('每20次的平均得分')
142 | plt.savefig('14.1.5-cartpole-policy.svg')
143 |
144 |
145 | if __name__ == '__main__':
146 | main()
147 |
--------------------------------------------------------------------------------
/src/ch14/14.3.6-cartpole-ppo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 14.3.6-cartpole-ppo.py
6 | @time: 2020/2/29 23:01
7 | @desc: 14.3.6 PPO实战的代码
8 | """
9 |
10 | import os
11 | from collections import namedtuple
12 |
13 | import gym
14 | import matplotlib
15 | import numpy as np
16 | import tensorflow as tf
17 | from matplotlib import pyplot as plt
18 | from tensorflow import keras
19 | from tensorflow.keras import layers, optimizers, losses
20 |
21 | matplotlib.rcParams['font.size'] = 18
22 | matplotlib.rcParams['figure.titlesize'] = 18
23 | matplotlib.rcParams['figure.figsize'] = [9, 7]
24 | matplotlib.rcParams['font.family'] = ['KaiTi']
25 | matplotlib.rcParams['axes.unicode_minus'] = False
26 |
27 | env = gym.make('CartPole-v1') # 创建游戏环境
28 | env.seed(2222)
29 | tf.random.set_seed(2222)
30 | np.random.seed(2222)
31 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
32 |
33 | # 创建游戏环境
34 | env = gym.make('CartPole-v0').unwrapped
35 | Transition = namedtuple('Transition', ['state', 'action', 'a_log_prob', 'reward', 'next_state'])
36 |
37 |
38 | class Actor(keras.Model):
39 | def __init__(self):
40 | super(Actor, self).__init__()
41 | # 策略网络,也叫Actor网络,输出为概率分布pi(a|s)
42 | self.fc1 = layers.Dense(100, kernel_initializer='he_normal')
43 | self.fc2 = layers.Dense(2, kernel_initializer='he_normal')
44 |
45 | def call(self, inputs):
46 | x = tf.nn.relu(self.fc1(inputs))
47 | x = self.fc2(x)
48 | x = tf.nn.softmax(x, axis=1) # 转换成概率
49 | return x
50 |
51 |
52 | class Critic(keras.Model):
53 | def __init__(self):
54 | super(Critic, self).__init__()
55 | # 偏置b的估值网络,也叫Critic网络,输出为v(s)
56 | self.fc1 = layers.Dense(100, kernel_initializer='he_normal')
57 | self.fc2 = layers.Dense(1, kernel_initializer='he_normal')
58 |
59 | def call(self, inputs):
60 | x = tf.nn.relu(self.fc1(inputs))
61 | # 输出基准线 b 的估计
62 | x = self.fc2(x)
63 | return x
64 |
65 |
66 | class PPO():
67 | # PPO算法主体
68 | def __init__(self, gamma, batch_size, epsilon):
69 | super(PPO, self).__init__()
70 | # 创建Actor网络
71 | self.actor = Actor()
72 | # 创建Critic网络
73 | self.critic = Critic()
74 | # 数据缓冲池
75 | self.buffer = []
76 | # Actor优化器
77 | self.actor_optimizer = optimizers.Adam(1e-3)
78 | # Critic优化器
79 | self.critic_optimizer = optimizers.Adam(3e-3)
80 | self._gamma = gamma
81 | self._batch_size = batch_size
82 | self._epsilon = epsilon
83 |
84 | def select_action(self, s):
85 | # 送入状态向量,获取策略: [4]
86 | s = tf.constant(s, dtype=tf.float32)
87 | # s: [4] => [1,4]
88 | s = tf.expand_dims(s, axis=0)
89 | # 获取策略分布: [1, 2]
90 | prob = self.actor(s)
91 | # 从类别分布中采样1个动作, shape: [1]
92 | a = tf.random.categorical(tf.math.log(prob), 1)[0]
93 | # Tensor转数字
94 | a = int(a)
95 | # 返回动作及其概率
96 | return a, float(prob[0][a])
97 |
98 | def get_value(self, s):
99 | # 送入状态向量,获取策略: [4]
100 | s = tf.constant(s, dtype=tf.float32)
101 | # s: [4] => [1,4]
102 | s = tf.expand_dims(s, axis=0)
103 | # 获取策略分布: [1, 2]
104 | v = self.critic(s)[0]
105 | return float(v) # 返回v(s)
106 |
107 | def store_transition(self, transition):
108 | # 存储采样数据
109 | self.buffer.append(transition)
110 |
111 | def optimize(self):
112 | # 优化网络主函数
113 | # 从缓存中取出样本数据,转换成Tensor
114 | state = tf.constant([t.state for t in self.buffer], dtype=tf.float32)
115 | action = tf.constant([t.action for t in self.buffer], dtype=tf.int32)
116 | action = tf.reshape(action, [-1, 1])
117 | reward = [t.reward for t in self.buffer]
118 | old_action_log_prob = tf.constant([t.a_log_prob for t in self.buffer], dtype=tf.float32)
119 | old_action_log_prob = tf.reshape(old_action_log_prob, [-1, 1])
120 | # 通过MC方法循环计算R(st)
121 | R = 0
122 | Rs = []
123 | for r in reward[::-1]:
124 | R = r + self._gamma * R
125 | Rs.insert(0, R)
126 | Rs = tf.constant(Rs, dtype=tf.float32)
127 | # 对缓冲池数据大致迭代10遍
128 | for _ in range(round(10 * len(self.buffer) / self._batch_size)):
129 | # 随机从缓冲池采样batch size大小样本
130 | index = np.random.choice(np.arange(len(self.buffer)), self._batch_size, replace=False)
131 | # 构建梯度跟踪环境
132 | with tf.GradientTape() as tape1, tf.GradientTape() as tape2:
133 | # 取出R(st),[b,1]
134 | v_target = tf.expand_dims(tf.gather(Rs, index, axis=0), axis=1)
135 | # 计算v(s)预测值,也就是偏置b,我们后面会介绍为什么写成v
136 | v = self.critic(tf.gather(state, index, axis=0))
137 | delta = v_target - v # 计算优势值
138 | advantage = tf.stop_gradient(delta) # 断开梯度连接
139 | # 由于TF的gather_nd与pytorch的gather功能不一样,需要构造
140 | # gather_nd需要的坐标参数,indices:[b, 2]
141 | # pi_a = pi.gather(1, a) # pytorch只需要一行即可实现
142 | a = tf.gather(action, index, axis=0) # 取出batch的动作at
143 | # batch的动作分布pi(a|st)
144 | pi = self.actor(tf.gather(state, index, axis=0))
145 | indices = tf.expand_dims(tf.range(a.shape[0]), axis=1)
146 | indices = tf.concat([indices, a], axis=1)
147 | pi_a = tf.gather_nd(pi, indices) # 动作的概率值pi(at|st), [b]
148 | pi_a = tf.expand_dims(pi_a, axis=1) # [b]=> [b,1]
149 | # 重要性采样
150 | ratio = (pi_a / tf.gather(old_action_log_prob, index, axis=0))
151 | surr1 = ratio * advantage
152 | surr2 = tf.clip_by_value(ratio, 1 - self._epsilon, 1 + self._epsilon) * advantage
153 | # PPO误差函数
154 | policy_loss = -tf.reduce_mean(tf.minimum(surr1, surr2))
155 | # 对于偏置v来说,希望与MC估计的R(st)越接近越好
156 | value_loss = losses.MSE(v_target, v)
157 | # 优化策略网络
158 | grads = tape1.gradient(policy_loss, self.actor.trainable_variables)
159 | self.actor_optimizer.apply_gradients(zip(grads, self.actor.trainable_variables))
160 | # 优化偏置值网络
161 | grads = tape2.gradient(value_loss, self.critic.trainable_variables)
162 | self.critic_optimizer.apply_gradients(zip(grads, self.critic.trainable_variables))
163 |
164 | self.buffer = [] # 清空已训练数据
165 |
166 |
167 | def train(agent, batch_size, epoch, returns, total, print_interval):
168 | # 复位环境
169 | state = env.reset()
170 | # 最多考虑500步
171 | for t in range(500):
172 | # 通过最新策略与环境交互
173 | action, action_prob = agent.select_action(state)
174 | next_state, reward, done, _ = env.step(action)
175 | # 构建样本并存储
176 | trans = Transition(state, action, action_prob, reward, next_state)
177 | agent.store_transition(trans)
178 | # 刷新状态
179 | state = next_state
180 | # 累积奖励
181 | total += reward
182 | # 合适的时间点训练网络
183 | if done:
184 | if len(agent.buffer) >= batch_size:
185 | # 训练网络
186 | agent.optimize()
187 | break
188 |
189 | # 每20个回合统计一次平均得分
190 | if epoch % print_interval == 0:
191 | returns.append(total / print_interval)
192 | print(f"# of episode :{epoch}, avg score : {total / print_interval}")
193 | total = 0
194 |
195 |
196 | def main():
197 | gamma = 0.98 # 激励衰减因子
198 | epsilon = 0.2 # PPO误差超参数0.8~1.2
199 | batch_size = 32 # batch size
200 | epoch_num = 500
201 | print_interval = 20
202 |
203 | agent = PPO(gamma, batch_size, epsilon)
204 | # 统计总回报
205 | returns = [0]
206 | total = 0 # 一段时间内平均回报
207 | for epoch in range(1, epoch_num + 1): # 训练回合数
208 | train(agent, batch_size, epoch, returns, total, print_interval)
209 |
210 | print(np.array(returns))
211 | plt.plot(np.arange(len(returns)) * 20, np.array(returns))
212 | plt.plot(np.arange(len(returns)) * 20, np.array(returns), 's')
213 | plt.xlabel('回合数')
214 | plt.ylabel('每20次的平均得分')
215 | plt.savefig('14.3.6-cartpole-ppo.svg')
216 |
217 |
218 | if __name__ == '__main__':
219 | main()
220 | print("end")
221 |
--------------------------------------------------------------------------------
/src/ch14/14.4.7-cartpole-dqn.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 14.4.7-cartpole-dqn.py
6 | @time: 2020/2/29 23:39
7 | @desc: 14.4.7 DQN实战的代码
8 | """
9 |
10 | import collections
11 | import os
12 | import random
13 |
14 | import gym
15 | import numpy as np
16 | import tensorflow as tf
17 | from tensorflow import keras
18 | from tensorflow.keras import layers, optimizers, losses
19 |
20 | env = gym.make('CartPole-v1') # 创建游戏环境
21 | env.seed(1234)
22 | tf.random.set_seed(1234)
23 | np.random.seed(1234)
24 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
25 |
26 |
27 | class ReplayBuffer():
28 | # 经验回放池
29 | def __init__(self, buffer_limit):
30 | # 双向队列
31 | self.buffer = collections.deque(maxlen=buffer_limit)
32 |
33 | def put(self, transition):
34 | self.buffer.append(transition)
35 |
36 | def sample(self, n):
37 | # 从回放池采样n个5元组
38 | mini_batch = random.sample(self.buffer, n)
39 | s_lst, a_lst, r_lst, s_prime_lst, done_mask_lst = [], [], [], [], []
40 | # 按类别进行整理
41 | for transition in mini_batch:
42 | s, a, r, s_prime, done_mask = transition
43 | s_lst.append(s)
44 | a_lst.append([a])
45 | r_lst.append([r])
46 | s_prime_lst.append(s_prime)
47 | done_mask_lst.append([done_mask])
48 | # 转换成Tensor
49 | return tf.constant(s_lst, dtype=tf.float32), tf.constant(a_lst, dtype=tf.int32), \
50 | tf.constant(r_lst, dtype=tf.float32), tf.constant(s_prime_lst, dtype=tf.float32), \
51 | tf.constant(done_mask_lst, dtype=tf.float32)
52 |
53 | def size(self):
54 | return len(self.buffer)
55 |
56 |
57 | class Qnet(keras.Model):
58 | def __init__(self):
59 | # 创建Q网络,输入为状态向量,输出为动作的Q值
60 | super(Qnet, self).__init__()
61 | self.fc1 = layers.Dense(256, kernel_initializer='he_normal')
62 | self.fc2 = layers.Dense(256, kernel_initializer='he_normal')
63 | self.fc3 = layers.Dense(2, kernel_initializer='he_normal')
64 |
65 | def call(self, x, training=None):
66 | x = tf.nn.relu(self.fc1(x))
67 | x = tf.nn.relu(self.fc2(x))
68 | x = self.fc3(x)
69 | return x
70 |
71 | def sample_action(self, s, epsilon):
72 | # 送入状态向量,获取策略: [4]
73 | s = tf.constant(s, dtype=tf.float32)
74 | # s: [4] => [1,4]
75 | s = tf.expand_dims(s, axis=0)
76 | out = self(s)[0]
77 | coin = random.random()
78 | # 策略改进:e-贪心方式
79 | if coin < epsilon:
80 | # epsilon大的概率随机选取
81 | return random.randint(0, 1)
82 | else: # 选择Q值最大的动作
83 | return int(tf.argmax(out))
84 |
85 |
86 | def train(q, q_target, memory, optimizer, batch_size, gamma):
87 | # 通过Q网络和影子网络来构造贝尔曼方程的误差,
88 | # 并只更新Q网络,影子网络的更新会滞后Q网络
89 | huber = losses.Huber()
90 | # 训练10次
91 | for i in range(10):
92 | # 从缓冲池采样
93 | s, a, r, s_prime, done_mask = memory.sample(batch_size)
94 | with tf.GradientTape() as tape:
95 | # s: [b, 4]
96 | q_out = q(s) # 得到Q(s,a)的分布
97 | # 由于TF的gather_nd与pytorch的gather功能不一样,需要构造
98 | # gather_nd需要的坐标参数,indices:[b, 2]
99 | # pi_a = pi.gather(1, a) # pytorch只需要一行即可实现
100 | indices = tf.expand_dims(tf.range(a.shape[0]), axis=1)
101 | indices = tf.concat([indices, a], axis=1)
102 | # 动作的概率值, [b]
103 | q_a = tf.gather_nd(q_out, indices)
104 | # [b]=> [b,1]
105 | q_a = tf.expand_dims(q_a, axis=1)
106 | # 得到Q(s',a)的最大值,它来自影子网络! [b,4]=>[b,2]=>[b,1]
107 | max_q_prime = tf.reduce_max(q_target(s_prime), axis=1, keepdims=True)
108 | # 构造Q(s,a_t)的目标值,来自贝尔曼方程
109 | target = r + gamma * max_q_prime * done_mask
110 | # 计算Q(s,a_t)与目标值的误差
111 | loss = huber(q_a, target)
112 | # 更新网络,使得Q(s,a_t)估计符合贝尔曼方程
113 | grads = tape.gradient(loss, q.trainable_variables)
114 | optimizer.apply_gradients(zip(grads, q.trainable_variables))
115 |
116 |
117 | def main():
118 | # Hyperparameters
119 | learning_rate = 0.0002
120 | gamma = 0.99
121 | buffer_limit = 50000
122 | batch_size = 32
123 | # 训练次数
124 | epoch_num = 10000
125 |
126 | env = gym.make('CartPole-v1') # 创建环境
127 | q_net = Qnet() # 创建Q网络
128 | q_target = Qnet() # 创建影子网络
129 | q_net.build(input_shape=(2, 4))
130 | q_target.build(input_shape=(2, 4))
131 | for src, dest in zip(q_net.variables, q_target.variables):
132 | dest.assign(src) # 影子网络权值来自Q
133 | memory = ReplayBuffer(buffer_limit) # 创建回放池
134 |
135 | print_interval = 20
136 | score = 0.0
137 | optimizer = optimizers.Adam(lr=learning_rate)
138 |
139 | for epoch in range(epoch_num):
140 | # epsilon概率也会8%到1%衰减,越到后面越使用Q值最大的动作
141 | epsilon = max(0.01, 0.08 - 0.01 * (epoch / 200))
142 | # 复位环境
143 | s = env.reset()
144 | # 一个回合最大时间戳
145 | for t in range(600):
146 | # 根据当前Q网络提取策略,并改进策略
147 | a = q_net.sample_action(s, epsilon)
148 | # 使用改进的策略与环境交互
149 | s_prime, r, done, info = env.step(a)
150 | # 结束标志掩码
151 | done_mask = 0.0 if done else 1.0
152 | # 保存5元组
153 | memory.put((s, a, r / 100.0, s_prime, done_mask))
154 | # 刷新状态
155 | s = s_prime
156 | # 记录总得分
157 | score += r
158 | if done: # 回合结束
159 | break
160 |
161 | if memory.size() > 2000: # 缓冲池只有大于2000就可以训练
162 | train(q_net, q_target, memory, optimizer, batch_size, gamma)
163 |
164 | if epoch % print_interval == 0 and epoch != 0:
165 | for src, dest in zip(q_net.variables, q_target.variables):
166 | dest.assign(src) # 影子网络权值来自Q
167 | print("# of episode :{}, avg score : {:.1f}, buffer size : {}, " \
168 | "epsilon : {:.1f}%" \
169 | .format(epoch, score / print_interval, memory.size(), epsilon * 100))
170 | score = 0.0
171 | env.close()
172 |
173 |
174 | if __name__ == '__main__':
175 | main()
176 |
--------------------------------------------------------------------------------
/src/ch14/14.5.3-cartpole-a3c.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: 14.5.3-cartpole-a3c.py
6 | @time: 2020/2/29 23:50
7 | @desc: 14.5.3 A3C实战的代码
8 | """
9 |
10 | import multiprocessing
11 | import os
12 | import threading
13 | from queue import Queue
14 |
15 | import gym
16 | import numpy as np
17 | import tensorflow as tf
18 | from matplotlib import pyplot as plt
19 | from tensorflow import keras
20 | from tensorflow.keras import layers, optimizers
21 |
22 | plt.rcParams['font.size'] = 18
23 | plt.rcParams['figure.titlesize'] = 18
24 | plt.rcParams['figure.figsize'] = [9, 7]
25 | plt.rcParams['font.family'] = ['KaiTi']
26 | plt.rcParams['axes.unicode_minus'] = False
27 |
28 | tf.random.set_seed(1231)
29 | np.random.seed(1231)
30 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
31 |
32 |
33 | class ActorCritic(keras.Model):
34 | # Actor-Critic模型
35 | def __init__(self, state_size, action_size):
36 | super(ActorCritic, self).__init__()
37 | # 状态向量长度
38 | self.state_size = state_size
39 | # 动作数量
40 | self.action_size = action_size
41 | # 策略网络Actor
42 | self.dense1 = layers.Dense(128, activation='relu')
43 | self.policy_logits = layers.Dense(action_size)
44 | # V网络Critic
45 | self.dense2 = layers.Dense(128, activation='relu')
46 | self.values = layers.Dense(1)
47 |
48 | def call(self, inputs):
49 | # 获得策略分布Pi(a|s)
50 | x = self.dense1(inputs)
51 | logits = self.policy_logits(x)
52 | # 获得v(s)
53 | v = self.dense2(inputs)
54 | values = self.values(v)
55 | return logits, values
56 |
57 |
58 | def record(episode,
59 | episode_reward,
60 | worker_idx,
61 | global_ep_reward,
62 | result_queue,
63 | total_loss,
64 | num_steps):
65 | # 统计工具函数
66 | if global_ep_reward == 0:
67 | global_ep_reward = episode_reward
68 | else:
69 | global_ep_reward = global_ep_reward * 0.99 + episode_reward * 0.01
70 | print(
71 | f"{episode} | "
72 | f"Average Reward: {int(global_ep_reward)} | "
73 | f"Episode Reward: {int(episode_reward)} | "
74 | f"Loss: {int(total_loss / float(num_steps) * 1000) / 1000} | "
75 | f"Steps: {num_steps} | "
76 | f"Worker: {worker_idx}"
77 | )
78 | result_queue.put(global_ep_reward) # 保存回报,传给主线程
79 | return global_ep_reward
80 |
81 |
82 | class Memory:
83 | def __init__(self):
84 | self.states = []
85 | self.actions = []
86 | self.rewards = []
87 |
88 | def store(self, state, action, reward):
89 | self.states.append(state)
90 | self.actions.append(action)
91 | self.rewards.append(reward)
92 |
93 | def clear(self):
94 | self.states = []
95 | self.actions = []
96 | self.rewards = []
97 |
98 |
99 | class Agent:
100 | # 智能体,包含了中央参数网络server
101 | def __init__(self):
102 | # server优化器,client不需要,直接从server拉取参数
103 | self.opt = optimizers.Adam(1e-3)
104 | # 中央模型,类似于参数服务器
105 | self.server = ActorCritic(4, 2) # 状态向量,动作数量
106 | self.server(tf.random.normal((2, 4)))
107 |
108 | def train(self):
109 | res_queue = Queue() # 共享队列
110 | # 创建各个交互环境
111 | workers = [Worker(self.server, self.opt, res_queue, i)
112 | for i in range(multiprocessing.cpu_count())]
113 | for i, worker in enumerate(workers):
114 | print("Starting worker {}".format(i))
115 | worker.start()
116 | # 统计并绘制总回报曲线
117 | returns = []
118 | while True:
119 | reward = res_queue.get()
120 | if reward is not None:
121 | returns.append(reward)
122 | else: # 结束标志
123 | break
124 | [w.join() for w in workers] # 等待线程退出
125 |
126 | print(returns)
127 | plt.figure()
128 | plt.plot(np.arange(len(returns)), returns)
129 | plt.xlabel('回合数')
130 | plt.ylabel('总奖励得分')
131 | plt.savefig('14.5.3-cartpole-a3c.svg')
132 |
133 |
134 | class Worker(threading.Thread):
135 | def __init__(self, server, opt, result_queue, idx):
136 | super(Worker, self).__init__()
137 | # 共享队列
138 | self.result_queue = result_queue
139 | # 中央模型
140 | self.server = server
141 | # 中央优化器
142 | self.opt = opt
143 | # 线程私有网络
144 | self.client = ActorCritic(4, 2)
145 | self.worker_idx = idx # 线程id
146 | self.env = gym.make('CartPole-v1').unwrapped
147 | self.ep_loss = 0.0
148 |
149 | def run(self):
150 | mem = Memory() # 每个worker自己维护一个memory
151 | for epi_counter in range(500): # 未达到最大回合数
152 | current_state = self.env.reset() # 复位client游戏状态
153 | mem.clear()
154 | ep_reward = 0.
155 | ep_steps = 0
156 | done = False
157 | while not done:
158 | # 获得Pi(a|s),未经softmax
159 | logits, _ = self.client(tf.constant(current_state[None, :], dtype=tf.float32))
160 | probs = tf.nn.softmax(logits)
161 | # 随机采样动作
162 | action = np.random.choice(2, p=probs.numpy()[0])
163 | # 交互
164 | new_state, reward, done, _ = self.env.step(action)
165 | # 累加奖励
166 | ep_reward += reward
167 | # 记录
168 | mem.store(current_state, action, reward)
169 | # 计算回合步数
170 | ep_steps += 1
171 | # 刷新状态
172 | current_state = new_state
173 |
174 | # 最长步数500
175 | if ep_steps >= 500 or done:
176 | # 计算当前client上的误差
177 | with tf.GradientTape() as tape:
178 | total_loss = self.compute_loss(done, new_state, mem)
179 |
180 | # 计算误差
181 | grads = tape.gradient(total_loss, self.client.trainable_weights)
182 | # 梯度提交到server,在server上更新梯度
183 | self.opt.apply_gradients(zip(grads, self.server.trainable_weights))
184 | # 从server拉取最新的梯度
185 | self.client.set_weights(self.server.get_weights())
186 | # 清空Memory
187 | mem.clear()
188 | # 统计此回合回报
189 | self.result_queue.put(ep_reward)
190 | print("epoch=%s," % epi_counter, "worker=%s," % self.worker_idx, "reward=%s" % ep_reward)
191 | break
192 |
193 | # 结束线程
194 | self.result_queue.put(None)
195 |
196 | def compute_loss(self,
197 | done,
198 | new_state,
199 | memory,
200 | gamma=0.99):
201 | if done:
202 | reward_sum = 0. # 终止状态的v(终止)=0
203 | else:
204 | reward_sum = self.client(tf.constant(new_state[None, :], dtype=tf.float32))[-1].numpy()[0]
205 | # 统计折扣回报
206 | discounted_rewards = []
207 | for reward in memory.rewards[::-1]: # reverse buffer r
208 | reward_sum = reward + gamma * reward_sum
209 | discounted_rewards.append(reward_sum)
210 | discounted_rewards.reverse()
211 | # 获取状态的Pi(a|s)和v(s)
212 | logits, values = self.client(tf.constant(np.vstack(memory.states), dtype=tf.float32))
213 | # 计算advantage = R() - v(s)
214 | advantage = tf.constant(np.array(discounted_rewards)[:, None], dtype=tf.float32) - values
215 | # Critic网络损失
216 | value_loss = advantage ** 2
217 | # 策略损失
218 | policy = tf.nn.softmax(logits)
219 | policy_loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
220 | labels=memory.actions, logits=logits)
221 | # 计算策略网络损失时,并不会计算V网络
222 | policy_loss = policy_loss * tf.stop_gradient(advantage)
223 | # Entropy Bonus
224 | entropy = tf.nn.softmax_cross_entropy_with_logits(labels=policy,
225 | logits=logits)
226 | policy_loss = policy_loss - 0.01 * entropy
227 | # 聚合各个误差
228 | total_loss = tf.reduce_mean((0.5 * value_loss + policy_loss))
229 | return total_loss
230 |
231 |
232 | if __name__ == '__main__':
233 | master = Agent()
234 | master.train()
235 |
--------------------------------------------------------------------------------
/src/ch15/pokemon.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: pokemon.py
6 | @time: 2020/3/1 0:35
7 | @desc: 15.3 宝可梦数据集实战
8 | 将pokemon文件夹放到ch15目录下,读取的图片路径为/ch15/pokemon,
9 | 通过在ch15目录下执行tensorboard --logdir=logs,查看tensorboard可视化
10 | """
11 |
12 | import csv
13 | import glob
14 | import os
15 | import random
16 |
17 | import tensorflow as tf
18 |
19 |
20 | def load_csv(root, filename, name2label):
21 | # 从csv文件返回images,labels列表
22 | # root:数据集根目录,filename:csv文件名, name2label:类别名编码表
23 | if not os.path.exists(os.path.join(root, filename)):
24 | # 如果csv文件不存在,则创建
25 | images = []
26 | for name in name2label.keys(): # 遍历所有子目录,获得所有的图片
27 | # 只考虑后缀为png,jpg,jpeg的图片:'pokemon\\mewtwo\\00001.png
28 | images += glob.glob(os.path.join(root, name, '*.png'))
29 | images += glob.glob(os.path.join(root, name, '*.jpg'))
30 | images += glob.glob(os.path.join(root, name, '*.jpeg'))
31 | # 打印数据集信息:1167, 'pokemon\\bulbasaur\\00000000.png'
32 | print(len(images), images)
33 | random.shuffle(images) # 随机打散顺序
34 | # 创建csv文件,并存储图片路径及其label信息
35 | with open(os.path.join(root, filename), mode='w', newline='') as f:
36 | writer = csv.writer(f)
37 | for img in images: # 'pokemon\\bulbasaur\\00000000.png'
38 | name = img.split(os.sep)[-2]
39 | label = name2label[name]
40 | # 'pokemon\\bulbasaur\\00000000.png', 0
41 | writer.writerow([img, label])
42 | print('written into csv file:', filename)
43 |
44 | # 此时已经有csv文件,直接读取
45 | images, labels = [], []
46 | with open(os.path.join(root, filename)) as f:
47 | reader = csv.reader(f)
48 | for row in reader:
49 | # 'pokemon\\bulbasaur\\00000000.png', 0
50 | img, label = row
51 | label = int(label)
52 | images.append(img)
53 | labels.append(label)
54 | # 返回图片路径list和标签list
55 | return images, labels
56 |
57 |
58 | def load_pokemon(root, mode='train'):
59 | # 创建数字编码表
60 | name2label = {} # "sq...":0
61 | # 遍历根目录下的子文件夹,并排序,保证映射关系固定
62 | for name in sorted(os.listdir(os.path.join(root))):
63 | # 跳过非文件夹
64 | if not os.path.isdir(os.path.join(root, name)):
65 | continue
66 | # 给每个类别编码一个数字
67 | name2label[name] = len(name2label.keys())
68 |
69 | # 读取Label信息
70 | # [file1,file2,], [3,1]
71 | images, labels = load_csv(root, 'images.csv', name2label)
72 |
73 | if mode == 'train': # 60%
74 | images = images[:int(0.6 * len(images))]
75 | labels = labels[:int(0.6 * len(labels))]
76 | elif mode == 'val': # 20% = 60%->80%
77 | images = images[int(0.6 * len(images)):int(0.8 * len(images))]
78 | labels = labels[int(0.6 * len(labels)):int(0.8 * len(labels))]
79 | else: # 20% = 80%->100%
80 | images = images[int(0.8 * len(images)):]
81 | labels = labels[int(0.8 * len(labels)):]
82 |
83 | return images, labels, name2label
84 |
85 |
86 | # 这里的mean和std根据真实的数据计算获得,比如ImageNet
87 | img_mean = tf.constant([0.485, 0.456, 0.406])
88 | img_std = tf.constant([0.229, 0.224, 0.225])
89 |
90 |
91 | def normalize(x, mean=img_mean, std=img_std):
92 | # 标准化
93 | # x: [224, 224, 3]
94 | # mean: [224, 224, 3], std: [3]
95 | x = (x - mean) / std
96 | return x
97 |
98 |
99 | def denormalize(x, mean=img_mean, std=img_std):
100 | # 标准化的逆过程
101 | x = x * std + mean
102 | return x
103 |
104 |
105 | def preprocess(x, y):
106 | # x: 图片的路径List,y:图片的数字编码List
107 | x = tf.io.read_file(x) # 根据路径读取图片
108 | x = tf.image.decode_jpeg(x, channels=3) # 图片解码
109 | x = tf.image.resize(x, [244, 244]) # 图片缩放
110 |
111 | # 数据增强
112 | # x = tf.image.random_flip_up_down(x)
113 | x = tf.image.random_flip_left_right(x) # 左右镜像
114 | x = tf.image.random_crop(x, [224, 224, 3]) # 随机裁剪
115 | # 转换成张量
116 | # x: [0,255]=> 0~1
117 | x = tf.cast(x, dtype=tf.float32) / 255.
118 | # 0~1 => D(0,1)
119 | x = normalize(x) # 标准化
120 | y = tf.convert_to_tensor(y) # 转换成张量
121 |
122 | return x, y
123 |
124 |
125 | def main():
126 | import time
127 |
128 | # 加载pokemon数据集,指定加载训练集
129 | # 返回训练集的样本路径列表,标签数字列表和编码表字典
130 | images, labels, table = load_pokemon('pokemon', 'train')
131 | print('images:', len(images), images)
132 | print('labels:', len(labels), labels)
133 | print('table:', table)
134 |
135 | # images: string path
136 | # labels: number
137 | db = tf.data.Dataset.from_tensor_slices((images, labels))
138 | db = db.shuffle(1000).map(preprocess).batch(32)
139 |
140 | # 创建TensorBoard对象
141 | writter = tf.summary.create_file_writer('logs')
142 | for step, (x, y) in enumerate(db):
143 | # x: [32, 224, 224, 3]
144 | # y: [32]
145 | with writter.as_default():
146 | x = denormalize(x) # 反向normalize,方便可视化
147 | # 写入图片数据
148 | tf.summary.image('img', x, step=step, max_outputs=9)
149 | time.sleep(5)
150 |
151 |
152 | if __name__ == '__main__':
153 | main()
154 |
--------------------------------------------------------------------------------
/src/ch15/resnet.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: resnet.py
6 | @time: 2020/3/1 0:35
7 | @desc:
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from tensorflow import keras
15 | from tensorflow.keras import layers
16 |
17 | tf.random.set_seed(22)
18 | np.random.seed(22)
19 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
20 |
21 |
22 | class ResnetBlock(keras.Model):
23 |
24 | def __init__(self, channels, strides=1):
25 | super(ResnetBlock, self).__init__()
26 |
27 | self.channels = channels
28 | self.strides = strides
29 |
30 | self.conv1 = layers.Conv2D(channels, 3, strides=strides,
31 | padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
32 | self.bn1 = keras.layers.BatchNormalization()
33 | self.conv2 = layers.Conv2D(channels, 3, strides=1,
34 | padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
35 | self.bn2 = keras.layers.BatchNormalization()
36 |
37 | if strides != 1:
38 | self.down_conv = layers.Conv2D(channels, 1, strides=strides, padding='valid')
39 | self.down_bn = tf.keras.layers.BatchNormalization()
40 |
41 | def call(self, inputs, training=None):
42 | residual = inputs
43 |
44 | x = self.conv1(inputs)
45 | x = tf.nn.relu(x)
46 | x = self.bn1(x, training=training)
47 | x = self.conv2(x)
48 | x = tf.nn.relu(x)
49 | x = self.bn2(x, training=training)
50 |
51 | # 残差连接
52 | if self.strides != 1:
53 | residual = self.down_conv(inputs)
54 | residual = tf.nn.relu(residual)
55 | residual = self.down_bn(residual, training=training)
56 |
57 | x = x + residual
58 | x = tf.nn.relu(x)
59 | return x
60 |
61 |
62 | class ResNet(keras.Model):
63 |
64 | def __init__(self, num_classes, initial_filters=16, **kwargs):
65 | super(ResNet, self).__init__(**kwargs)
66 |
67 | self.stem = layers.Conv2D(initial_filters, 3, strides=3, padding='valid')
68 |
69 | self.blocks = keras.models.Sequential([
70 | ResnetBlock(initial_filters * 2, strides=3),
71 | ResnetBlock(initial_filters * 2, strides=1),
72 | # layers.Dropout(rate=0.5),
73 |
74 | ResnetBlock(initial_filters * 4, strides=3),
75 | ResnetBlock(initial_filters * 4, strides=1),
76 |
77 | ResnetBlock(initial_filters * 8, strides=2),
78 | ResnetBlock(initial_filters * 8, strides=1),
79 |
80 | ResnetBlock(initial_filters * 16, strides=2),
81 | ResnetBlock(initial_filters * 16, strides=1),
82 | ])
83 |
84 | self.final_bn = layers.BatchNormalization()
85 | self.avg_pool = layers.GlobalMaxPool2D()
86 | self.fc = layers.Dense(num_classes)
87 |
88 | def call(self, inputs, training=None):
89 | # print('x:',inputs.shape)
90 | out = self.stem(inputs)
91 | out = tf.nn.relu(out)
92 |
93 | # print('stem:',out.shape)
94 |
95 | out = self.blocks(out, training=training)
96 | # print('res:',out.shape)
97 |
98 | out = self.final_bn(out, training=training)
99 | # out = tf.nn.relu(out)
100 |
101 | out = self.avg_pool(out)
102 |
103 | # print('avg_pool:',out.shape)
104 | out = self.fc(out)
105 |
106 | # print('out:',out.shape)
107 |
108 | return out
109 |
110 |
111 | def main():
112 | num_classes = 5
113 |
114 | resnet18 = ResNet(num_classes)
115 | resnet18.build(input_shape=(4, 224, 224, 3))
116 | resnet18.summary()
117 |
118 |
119 | if __name__ == '__main__':
120 | main()
121 |
--------------------------------------------------------------------------------
/src/ch15/train_scratch_dense_net.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: train_scratch_dense_net.py
6 | @time: 2020/3/1 0:36
7 | @desc: 15.3.3 创建模型(从零训练 DenseNet 网络)
8 | """
9 |
10 | import os
11 |
12 | import numpy as np
13 | import tensorflow as tf
14 | from matplotlib import pyplot as plt
15 | from tensorflow import keras
16 | from tensorflow.keras import layers, optimizers, losses
17 | from tensorflow.keras.callbacks import EarlyStopping
18 |
19 | from src.ch15.pokemon import load_pokemon, normalize
20 |
21 | tf.random.set_seed(1234)
22 | np.random.seed(1234)
23 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
24 |
25 | plt.rcParams['font.size'] = 18
26 | plt.rcParams['figure.titlesize'] = 18
27 | plt.rcParams['figure.figsize'] = [9, 7]
28 | plt.rcParams['font.family'] = ['KaiTi']
29 | plt.rcParams['axes.unicode_minus'] = False
30 |
31 |
32 | def preprocess(x, y):
33 | # x: 图片的路径,y:图片的数字编码
34 | x = tf.io.read_file(x)
35 | x = tf.image.decode_jpeg(x, channels=3) # RGBA
36 | x = tf.image.resize(x, [244, 244])
37 |
38 | x = tf.image.random_flip_left_right(x)
39 | x = tf.image.random_flip_up_down(x)
40 | x = tf.image.random_crop(x, [224, 224, 3])
41 |
42 | # x: [0,255]=> -1~1
43 | x = tf.cast(x, dtype=tf.float32) / 255.
44 | x = normalize(x)
45 | y = tf.convert_to_tensor(y)
46 | y = tf.one_hot(y, depth=5)
47 |
48 | return x, y
49 |
50 |
51 | def load_dataset():
52 | batchsz = 32
53 | # 创建训练集Datset对象
54 | images, labels, table = load_pokemon('pokemon', mode='train')
55 | db_train = tf.data.Dataset.from_tensor_slices((images, labels))
56 | db_train = db_train.shuffle(1000).map(preprocess).batch(batchsz)
57 | # 创建验证集Datset对象
58 | images2, labels2, table = load_pokemon('pokemon', mode='val')
59 | db_val = tf.data.Dataset.from_tensor_slices((images2, labels2))
60 | db_val = db_val.map(preprocess).batch(batchsz)
61 | # 创建测试集Datset对象
62 | images3, labels3, table = load_pokemon('pokemon', mode='test')
63 | db_test = tf.data.Dataset.from_tensor_slices((images3, labels3))
64 | db_test = db_test.map(preprocess).batch(batchsz)
65 |
66 | return db_train, db_val, db_test
67 |
68 |
69 | def build_net():
70 | # 加载DenseNet网络模型,并去掉最后一层全连接层,最后一个池化层设置为max pooling
71 | net = keras.applications.DenseNet121(include_top=False, pooling='max')
72 | # 设置为 True,即 DenseNet 部分的参数也参与优化更新
73 | net.trainable = True
74 | newnet = keras.Sequential([
75 | net, # 去掉最后一层的DenseNet121
76 | layers.Dense(1024, activation='relu'), # 追加全连接层
77 | layers.BatchNormalization(), # 追加BN层
78 | layers.Dropout(rate=0.5), # 追加Dropout层,防止过拟合
79 | layers.Dense(5) # 根据宝可梦数据的任务,设置最后一层输出节点数为5
80 | ])
81 | newnet.build(input_shape=(4, 224, 224, 3))
82 | newnet.summary()
83 |
84 | return newnet
85 |
86 |
87 | def main():
88 | db_train, db_val, db_test = load_dataset()
89 | newnet = build_net()
90 | # 创建Early Stopping类,连续3次不下降则终止
91 | early_stopping = EarlyStopping(
92 | monitor='val_accuracy',
93 | min_delta=0.001,
94 | patience=3
95 | )
96 |
97 | # 装配模型
98 | newnet.compile(optimizer=optimizers.Adam(lr=1e-3),
99 | loss=losses.CategoricalCrossentropy(from_logits=True),
100 | metrics=['accuracy'])
101 | # 训练模型,支持 early stopping
102 | history = newnet.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
103 | callbacks=[early_stopping])
104 | history = history.history
105 | print(history.keys())
106 | print(history['val_accuracy'])
107 | print(history['accuracy'])
108 | test_acc = newnet.evaluate(db_test)
109 |
110 | plt.figure()
111 | returns = history['val_accuracy']
112 | plt.plot(np.arange(len(returns)), returns, label='验证准确率')
113 | plt.plot(np.arange(len(returns)), returns, 's')
114 | returns = history['accuracy']
115 | plt.plot(np.arange(len(returns)), returns, label='训练准确率')
116 | plt.plot(np.arange(len(returns)), returns, 's')
117 |
118 | plt.plot([len(returns) - 1], [test_acc[-1]], 'D', label='测试准确率')
119 | plt.legend()
120 | plt.xlabel('Epoch')
121 | plt.ylabel('准确率')
122 | plt.savefig('train_scratch_dense_net.svg')
123 |
124 |
125 | if __name__ == '__main__':
126 | main()
127 |
--------------------------------------------------------------------------------
/src/ch15/train_transfer_dense_net.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | @author: HuRuiFeng
5 | @file: train_transfer_dense_net.py
6 | @time: 2020/3/1 0:37
7 | @desc: 15.4 迁移学习
8 | """
9 |
10 | import os
11 |
12 | import matplotlib
13 | import numpy as np
14 | import tensorflow as tf
15 | from matplotlib import pyplot as plt
16 | from tensorflow import keras
17 | from tensorflow.keras import layers, optimizers, losses
18 | from tensorflow.keras.callbacks import EarlyStopping
19 |
20 | from src.ch15.pokemon import load_pokemon, normalize
21 |
22 | tf.random.set_seed(2222)
23 | np.random.seed(2222)
24 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
25 |
26 | matplotlib.rcParams['font.size'] = 18
27 | matplotlib.rcParams['figure.titlesize'] = 18
28 | matplotlib.rcParams['figure.figsize'] = [9, 7]
29 | matplotlib.rcParams['font.family'] = ['KaiTi']
30 | matplotlib.rcParams['axes.unicode_minus'] = False
31 |
32 |
33 | def preprocess(x, y):
34 | # x: 图片的路径,y:图片的数字编码
35 | x = tf.io.read_file(x)
36 | x = tf.image.decode_jpeg(x, channels=3) # RGBA
37 | x = tf.image.resize(x, [244, 244])
38 |
39 | x = tf.image.random_flip_left_right(x)
40 | x = tf.image.random_flip_up_down(x)
41 | x = tf.image.random_crop(x, [224, 224, 3])
42 |
43 | # x: [0,255]=> -1~1
44 | x = tf.cast(x, dtype=tf.float32) / 255.
45 | x = normalize(x)
46 | y = tf.convert_to_tensor(y)
47 | y = tf.one_hot(y, depth=5)
48 |
49 | return x, y
50 |
51 |
52 | def load_dataset():
53 | batchsz = 128
54 | # 创建训练集Datset对象
55 | images, labels, table = load_pokemon('pokemon', mode='train')
56 | db_train = tf.data.Dataset.from_tensor_slices((images, labels))
57 | db_train = db_train.shuffle(1000).map(preprocess).batch(batchsz)
58 | # 创建验证集Datset对象
59 | images2, labels2, table = load_pokemon('pokemon', mode='val')
60 | db_val = tf.data.Dataset.from_tensor_slices((images2, labels2))
61 | db_val = db_val.map(preprocess).batch(batchsz)
62 | # 创建测试集Datset对象
63 | images3, labels3, table = load_pokemon('pokemon', mode='test')
64 | db_test = tf.data.Dataset.from_tensor_slices((images3, labels3))
65 | db_test = db_test.map(preprocess).batch(batchsz)
66 |
67 | return db_train, db_val, db_test
68 |
69 |
70 | def build_network():
71 | # 加载DenseNet网络模型,并去掉最后一层全连接层,最后一个池化层设置为max pooling
72 | # 并使用预训练的参数初始化
73 | net = keras.applications.DenseNet121(weights='imagenet', include_top=False, pooling='max')
74 | # 设计为不参与优化,即DenseNet这部分参数固定不动
75 | net.trainable = False
76 | newnet = keras.Sequential([
77 | net, # 去掉最后一层的DenseNet121
78 | layers.Dense(1024, activation='relu'), # 追加全连接层
79 | layers.BatchNormalization(), # 追加BN层
80 | layers.Dropout(rate=0.5), # 追加Dropout层,防止过拟合
81 | layers.Dense(5) # 根据宝可梦数据的任务,设置最后一层输出节点数为5
82 | ])
83 | newnet.build(input_shape=(4, 224, 224, 3))
84 | newnet.summary()
85 | return newnet
86 |
87 |
88 | def main():
89 | db_train, db_val, db_test = load_dataset()
90 | newnet = build_network()
91 | # 创建Early Stopping类,连续3次不下降则终止
92 | early_stopping = EarlyStopping(
93 | monitor='val_accuracy',
94 | min_delta=0.001,
95 | patience=3
96 | )
97 |
98 | newnet.compile(optimizer=optimizers.Adam(lr=1e-3),
99 | loss=losses.CategoricalCrossentropy(from_logits=True),
100 | metrics=['accuracy'])
101 | history = newnet.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
102 | callbacks=[early_stopping])
103 | history = history.history
104 | print(history.keys())
105 | print(history['val_accuracy'])
106 | print(history['accuracy'])
107 | test_acc = newnet.evaluate(db_test)
108 |
109 | plt.figure()
110 | returns = history['val_accuracy']
111 | plt.plot(np.arange(len(returns)), returns, label='验证准确率')
112 | plt.plot(np.arange(len(returns)), returns, 's')
113 | returns = history['accuracy']
114 | plt.plot(np.arange(len(returns)), returns, label='训练准确率')
115 | plt.plot(np.arange(len(returns)), returns, 's')
116 |
117 | plt.plot([len(returns) - 1], [test_acc[-1]], 'D', label='测试准确率')
118 | plt.legend()
119 | plt.xlabel('Epoch')
120 | plt.ylabel('准确率')
121 | plt.savefig('train_transfer_dense_net.svg')
122 |
123 |
124 | if __name__ == '__main__':
125 | main()
126 |
--------------------------------------------------------------------------------