tensorflow介绍

前言

这篇文章既然放在技能库里,肯定就要讲 TensorFlow 的具体用法。但是这么大一个框架肯定不是这样一篇短短的博客就能介绍完的,所以这里只展示几个例子,通过代码熟悉框架以快速入门,如果真想学好建议在网络条件良好的情况下看官方文档,也可以看谷歌在中国服务器上给的文档。因为 TensorFlow 1.x和2均支持keras,所以本文先介绍 keras,再对 TensorFlow 2 和 TensorFlow 1.x 的差异部分做特殊说明

Keras 是一个基于 TensorFlow 等框架提供的底层运算而实现的高层框架,提供了大量方便快速训练,测试的高层接口,对于常见应用来说,使用Keras 开发效率更高。
Keras 可以理解为一套高层API 的设计规范,TensorFlow 实现了这套规范,称为tf.keras 模块,并且tf.keras 将作为TensorFlow 2 版本的唯一高层接口,避免出现接口重复冗余的问题

TensorFlow 是Google 于2015 年发布的深度学习框架。得益于发布时间较早,以及Google 在深度学习领域的影响力,TensorFlow 一度成为最流行的深度学习框架。但是由于TensorFlow 接口设计频繁变动,功能设计重复冗余,符号式编程开发和调试非常困难等问题,TensorFlow 1.x 版本一度被业界诟病。
2019年,Google 推出TensorFlow 2 正式版本,以动态图为优先模式运行,从而能够避免TensorFlow 1.x 版本的诸多缺陷。但因为TensorFlow 2完全不兼容TensorFlow 1.x的代码,接口变动依旧频繁、功能设计仍有重复、加上为时已晚, PyTorch 逐渐在深度学习框架的份额上坐稳了第一的位置。

Keras

TensorFlow 与 Keras 是两个东西,但都是谷歌开发的。Keras 用的人多了以后 Google 把 Keras 集成在了 TensorFlow 里面,作为tf.keras模块。在使用 TensorFlow 2 的时候多半会用到tf.keras.layers构建神经网络层,因为如果真的全部从头开始搭费时费力,不如直接用造好的轮子。

搭建模型

使用 Keras 高阶 API 搭建模型有3种方法,分别是序列式、函数式和Model子类化。

序列式(Sequential API)

序列式是layer-by-layer的方式,适用于简单的层堆栈,但难以构建多输入、多输出的模型,不够灵活。

你可以通过将层列表传递给tf.keras.Sequential来创建 Sequential 模型:

1
2
3
4
5
6
7
8
import tensorflow as tf
from tensorflow.keras import layers

model = tf.keras.Sequential([
layers.Dense(2, activation="relu"), # 等效于`nn.Dense`
layers.Dense(3, activation="relu"),
layers.Dense(4)
])

也可以通过add()方法:

1
2
3
4
5
6
7
import tensorflow as tf
from tensorflow.keras import layers

model = tf.keras.Sequential()
model.add(layers.Dense(2, activation="relu"))
model.add(layers.Dense(3, activation="relu"))
model.add(layers.Dense(4))

这两段代码作用相同,都创建了一个第一层有2个节点,第二层有3个节点,输出层有4个节点的神经网络。
上面的代码仅仅搭建了模型框架,并未指定输入的shape,所以没有参数,需要进行后续处理才可用于训练。

函数式(Functional API)

1
2
3
4
5
6
7
8
9
10
11
12
13
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

encoder_input = keras.Input(shape=(28, 28, 1), name="img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input) # 16个3x3的2D卷积核
x = layers.Conv2D(32, 3, activation="relu")(x) # 32个3x3的2D卷积核
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)

encoder = keras.Model(encoder_input, encoder_output, name="encoder")

上面这个网络的结构是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Model: "encoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
img (InputLayer) [(None, 28, 28, 1)] 0

conv2d (Conv2D) (None, 26, 26, 16) 160

conv2d_1 (Conv2D) (None, 24, 24, 32) 4640

max_pooling2d (MaxPooling2D (None, 8, 8, 32) 0
)

conv2d_2 (Conv2D) (None, 6, 6, 32) 9248

conv2d_3 (Conv2D) (None, 4, 4, 16) 4624

global_max_pooling2d (Globa (None, 16) 0
lMaxPooling2D)

=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0

Model子类化创建自定义模型

使用这种方式可以非常自由地自定义模型。下面是一个自定义残差层的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import tensorflow as tf


class Residual(tf.keras.Model): #@save
def __init__(self, num_channels, use_1x1conv=False, strides=1):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
num_channels, padding='same', kernel_size=3, strides=strides)
self.conv2 = tf.keras.layers.Conv2D(
num_channels, kernel_size=3, padding='same')
self.conv3 = None
if use_1x1conv:
self.conv3 = tf.keras.layers.Conv2D(
num_channels, kernel_size=1, strides=strides)
self.bn1 = tf.keras.layers.BatchNormalization()
self.bn2 = tf.keras.layers.BatchNormalization()

def call(self, X):
Y = tf.keras.activations.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3 is not None:
X = self.conv3(X)
Y += X
return tf.keras.activations.relu(Y)

上面几种方式各有所长。灵活地混合使用各种方式可以很方便地实现我们的网络模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ResnetBlock(tf.keras.layers.Layer):
'''残差块'''
def __init__(self, num_channels, num_residuals, first_block=False,
**kwargs):
super(ResnetBlock, self).__init__(**kwargs)
self.residual_layers = []
for i in range(num_residuals):
if i == 0 and not first_block:
self.residual_layers.append(
Residual(num_channels, use_1x1conv=True, strides=2))
else:
self.residual_layers.append(Residual(num_channels))

def call(self, X):
for layer in self.residual_layers.layers:
X = layer(X)
return X


def net():
# 返回一个ResNet
return tf.keras.Sequential([
tf.keras.layers.Conv2D(64, kernel_size=7, strides=2, padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Activation('relu'),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'),

ResnetBlock(64, 2, first_block=True),
ResnetBlock(128, 2),
ResnetBlock(256, 2),
ResnetBlock(512, 2),
tf.keras.layers.GlobalAvgPool2D(),
tf.keras.layers.Dense(units=10)])

模型训练

通过Model.compile()装配模型,Model.fit()训练模型,Model.evaluate()评估模型,Model.predict()使用模型。

compile() 方法指定损失、指标和优化器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
model.compile(
optimizer=keras.optimizers.RMSprop(), # Optimizer
# Loss function to minimize
loss=keras.losses.SparseCategoricalCrossentropy(),
# List of metrics to monitor
metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

# 如果不额外指定参数(如optimizer的learning rate),很多时候上面可以简写为:
# model.compile(
# optimizer="rmsprop",
# loss="sparse_categorical_crossentropy",
# metrics=["sparse_categorical_accuracy"],
# )

# x_train, y_train是训练集的data和label,x_val, y_val是验证集的data和label
history = model.fit(x_train, y_train,
batch_size=64, epochs=2, validation_data=(x_val, y_val))
1
2
3
4
5
Fit model on training data
Epoch 1/2
782/782 [==============================] - 3s 3ms/step - loss: 0.3473 - sparse_categorical_accuracy: 0.9009 - val_loss: 0.1936 - val_sparse_categorical_accuracy: 0.9444
Epoch 2/2
782/782 [==============================] - 2s 2ms/step - loss: 0.1616 - sparse_categorical_accuracy: 0.9507 - val_loss: 0.1324 - val_sparse_categorical_accuracy: 0.9605

返回的history对象保存训练期间的损失值和指标值记录:

1
print(history.history)
1
2
3
4
{'loss': [0.3473339378833771, 0.16158732771873474],
'sparse_categorical_accuracy': [0.9008600115776062, 0.9506999850273132],
'val_loss': [0.19355881214141846, 0.13244354724884033],
'val_sparse_categorical_accuracy': [0.9444000124931335, 0.9605000019073486]}

学习率衰减的实现可以参照下面修改optimizer里的learning_rate参数

1
2
3
4
5
6
initial_learning_rate = 0.1
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate, decay_steps=100000, decay_rate=0.96, staircase=True
)

optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)

使用回调函数可以方便地实现模型检查点、早停和TensorBoard。只需要把model.fit()改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
model = get_compiled_model()

callbacks = [
# Only save a model if `val_loss` has improved.
keras.callbacks.ModelCheckpoint(filepath="mymodel_{epoch}", save_best_only=True,
monitor="val_loss", verbose=1),

keras.callbacks.EarlyStopping(
# Stop training when `val_loss` is no longer improving
monitor="val_loss",
# "no longer improving" being defined as "no better than 1e-2 less"
min_delta=1e-2,
# "no longer improving" being further defined as "for at least 2 epochs"
patience=2,
verbose=1,
),

keras.callbacks.TensorBoard(
log_dir="/full_path_to_your_logs",
histogram_freq=0, # How often to log histogram visualizations
embeddings_freq=0, # How often to log embedding visualizations
update_freq="epoch", # How often to write logs (default: once per epoch)
)
]

# x_train, y_train是训练和验证集混在一起的data和label,
# validation_split=0.2 代表这些数据按0.8:0.2切分训练集和验证集
model.fit(x_train, y_train, epochs=20, batch_size=64,
callbacks=callbacks, validation_split=0.2)

准备数据

TensorFlow 中没有像 PyTorch里torch.utils.data.DataLoader这么一致的数据加载流程,虽然大多各写各的。虽然tensorflow有tf.data.Datasettf.data.Dataset.from_tensor_slices,keras有tf.keras.utils.image_dataset_from_directory

当然,你也可以直接使用torch.utils.data.DataLoader进行数据的加载,只要最后的数据是Numpy array格式就没问题。
以花卉数据集为例演示如何加载数据。首先下载数据:

1
2
3
4
5
6
import pathlib
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file(origin=dataset_url,
fname='flower_photos',
untar=True)
data_dir = pathlib.Path(data_dir)

得到的文件夹结构如下:

1
2
3
4
5
6
flowers_photos/
daisy/
dandelion/
roses/
sunflowers/
tulips/

tf.keras.utils.image_dataset_from_directory

你可以使用tf.keras.utils.image_dataset_from_directory加载数据集,该方法适用于分类任务且一个类别一个文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
batch_size = 32
img_height = 180
img_width = 180

# 使用 80% 的图像进行训练,20% 的图像进行验证
train_ds = tf.keras.utils.image_dataset_from_directory(data_dir, validation_split=0.2,
subset="training", seed=123, image_size=(img_height, img_width), batch_size=batch_size)

val_ds = tf.keras.utils.image_dataset_from_directory(data_dir, validation_split=0.2,
subset="validation", seed=123, image_size=(img_height, img_width), batch_size=batch_size)

# 数据集的 `class_names` 是类名称。
class_names = train_ds.class_names
print(class_names)

使用image-label对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
list_ds = tf.data.Dataset.list_files(str(data_dir/'*/*'), shuffle=False)
list_ds = list_ds.shuffle(image_count, reshuffle_each_iteration=False)

class_names = np.array(sorted([item.name for item in data_dir.glob('*') if item.name != "LICENSE.txt"]))
print(class_names)

# 将数据集拆分为训练集和测试集
val_size = int(image_count * 0.2)
train_ds = list_ds.skip(val_size)
val_ds = list_ds.take(val_size)

# 编写一个将文件路径转换为 (img, label) 对的短函数:
def get_label(file_path):
parts = tf.strings.split(file_path, os.path.sep)
# The second to last is the class-directory
one_hot = parts[-2] == class_names
# Integer encode the label
return tf.argmax(one_hot)

def decode_img(img):
img = tf.io.decode_jpeg(img, channels=3)
return tf.image.resize(img, [img_height, img_width])

def process_path(file_path):
label = get_label(file_path)
# Load the raw data from the file as a string
img = tf.io.read_file(file_path)
img = decode_img(img)
return img, label


train_ds = train_ds.map(process_path, num_parallel_calls=AUTOTUNE)
val_ds = val_ds.map(process_path, num_parallel_calls=AUTOTUNE)

# 训练模型
model.fit(train_ds, validation_data=val_ds, epochs=3)

加载.npy文件

下面是一个从.npz文件加载数据的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DATA_URL = 'https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz'
path = tf.keras.utils.get_file('mnist.npz', DATA_URL)
with np.load(path) as data:
train_examples = data['x_train']
train_labels = data['y_train']
test_examples = data['x_test']
test_labels = data['y_test']

# 转为tf.Tensor
train_dataset = tf.data.Dataset.from_tensor_slices((train_examples, train_labels))
test_dataset = tf.data.Dataset.from_tensor_slices((test_examples, test_labels))

# 打乱、批次化
BATCH_SIZE = 64
SHUFFLE_BUFFER_SIZE = 100
train_dataset = train_dataset.shuffle(SHUFFLE_BUFFER_SIZE).batch(BATCH_SIZE)
test_dataset = test_dataset.batch(BATCH_SIZE)

# 训练网络(略)

数据预处理

数据预处理通过网络层实现。每种预处理方法都可视为网络中的一层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
num_classes = 5
normalization_layer = tf.keras.layers.Rescaling(1./255)

model = tf.keras.Sequential([
# 0-255的图片变为0-1的Tensor
tf.keras.layers.Rescaling(1./255),
# 随机旋转
tf.keras.layers.RandomRotation(0.5),
tf.keras.layers.Conv2D(32, 3, activation='relu'),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(num_classes)
])

model.compile(
optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])

model.fit(train_ds, validation_data=val_ds, epochs=3)

模型保存与加载

注意:这里说的都是keras而非 TensorFlow。通过 TensorFlow API保存的模型与通过 keras API保存的模型不互通
keras模型通过model.save('path/to/location')保存。

加载模型代码如下:

1
2
from tensorflow import keras
model = keras.models.load_model('path/to/location')

TensorFlow 2

使用梯度磁带自定义训练过程

下面是不使用Keras API 的 TensorFlow 2 训练过程。TensorFlow 2 引入梯度磁带实现自动求导。

如果你不想使用model.fit(),而是想更为灵活的训练,可以使用下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
epochs = 50
for epoch in range(epochs):
for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

# 打开梯度磁带功能以实现自动求导,相当于`require_grad=True`
with tf.GradientTape() as tape:
# 前向传播
logits = model(x_batch_train, training=True)
loss_value = loss_fn(y_batch_train, logits)

# 拿到梯度
grads = tape.gradient(loss_value, model.trainable_weights)

# 反向传播
optimizer.apply_gradients(zip(grads, model.trainable_weights))

# 输出相关信息
if step % 200 == 0:
print(
"Training loss (for one batch) at step %d: %.4f"
% (step, float(loss_value))
)
print("Seen so far: %s samples" % ((step + 1) * batch_size))

# 保存:tf.saved_model.save(model, path_to_dir)
# 加载:model = tf.saved_model.load(path_to_dir)
tf.saved_model.save(model, "checkpoint")

使用tf.Module搭建模型

从这一小节开始的内容基本上都用不上了

在搭建模型的过程中,TensorFlow 2 中提供了最底层的tf.Variabletf.Module,但是过于底层,官方不推荐使用,实际代码中使用到这部分的代码也是特别少。
除了使用Keras外,还可以使用tf.Module搭建模型。Keras正是在tf.Module的基础上实现的。下面是一个由模块组成的两层线性层模型的示例。

注:tf.Moduletf.keras.layers.Layertf.keras.Model的基类,因此您在此处看到的一切内容也适用于 Keras。出于历史兼容性原因,Keras 层不会从模块收集变量,因此您的模型应仅使用模块或仅使用 Keras 层。不过,下面给出的用于检查变量的方法在这两种情况下相同。

首先是一个密集(线性)层。为了不预先指定输入大小,将变量创建推迟到第一次使用特定输入形状调用模块时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import tensorflow as tf
from datetime import datetime

class FlexibleDenseModule(tf.Module):
def __init__(self, out_features, name=None):
super().__init__(name=name)
self.is_built = False
self.out_features = out_features

def __call__(self, x):
if not self.is_built:
self.w = tf.Variable(
tf.random.normal([x.shape[-1], self.out_features]), name='w')
self.b = tf.Variable(tf.zeros([self.out_features]), name='b')
self.is_built = True

y = tf.matmul(x, self.w) + self.b
return tf.nn.relu(y)

随后是完整的模型,此模型将创建并应用两个层实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MySequentialModule(tf.Module):
def __init__(self, name=None):
super().__init__(name=name)

self.dense_1 = FlexibleDenseModule(out_features=3)
self.dense_2 = FlexibleDenseModule(out_features=2)

def __call__(self, x):
x = self.dense_1(x)
return self.dense_2(x)

my_model = MySequentialModule(name="the_model")
print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
1
Model results: tf.Tensor([[0. 0.]], shape=(1, 2), dtype=float32)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
```

## Tensorflow 1.x

介绍之前先来一波劝退。TensorFlow 1.x 的静态图机制能有效提高性能,但是使用起来非常麻烦,上手难度大。**如果不是有特别的需求真别学TF1**,看到`requirement.txt`里写`tensorflow<1.15`以后的第一反应应该是找有没有用 Pytorch 重写的版本。

### 理解静态计算图机制

以计算`2+4`为例,首先创建计算图:

```python
import tensorflow as tf

# 创建2个端子作为输入端子,指定类型和名字
a_ph = tf.placeholder(tf.float32, name='variable_a')
b_ph = tf.placeholder(tf.float32, name='variable_b')
# 创建输出端子的运算操作,并命名
c_op = tf.add(a_ph, b_ph, name='variable_c')

这样,c=a+b的计算图就创建好了。下面开始使用刚才创建的图计算2+4

1
2
3
4
5
6
sess = tf.InteractiveSession()              # 创建运行环境
init = tf.global_variables_initializer() # 创建初始化操作
sess.run(init) # 运行初始化
# 给 a b 端子赋值并计算得到结果
c_numpy = sess.run(c_op, feed_dict={a_ph: 2., b_ph: 4.})
print('a+b=',c_numpy)

TensorFlow 1.x将“定义”与“运行”相分离,一个TensorFlow图描述了计算的过程。为了进行计算,图必须在会话session里启动,会话将图的op分发到诸如CPU或者GPU的设备上,同时提供执行op的方法,这些方法执行后,将产生的tensor返回,在python语言中,返回的tensor是numpy array对象,在C或者C++语言中,返回的tensor是tensorflow:Tensor实例。

不使用 keras 构建 pipline

理解了静态计算图的运行机制以后,来看下面这个不使用 keras 构建 pipline 的例子。下面的代码在MNIST上训练了一个分类模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import random as ran

# Import MNIST data
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

# Trainning Parameters
learning_rate = 0.001
training_iters = 500
batch_size = 128
display_step = 10

# Network Parameters
n_input = 784 # MNIST data input (img shape: 28*28)
n_classes = 10 # MNIST total classes (0-9 digits)
dropout = 0.85

x = tf.placeholder(tf.float32, [None, n_input]) # 输入端子
y = tf.placeholder(tf.float32, [None, n_classes]) # 输出端子
keep_prob = tf.placeholder(tf.float32) # 拿来存 dropout_rate 的 placeholder

def conv2d(x, W, b, strides=1):
x = tf.nn.conv2d(x, W, strides=[1, strides, strides, 1], padding='SAME')
x = tf.nn.bias_add(x, b)
return tf.nn.relu(x)

def maxpool2d(x, k=2):
return tf.nn.max_pool(x, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME')

def conv_net(x, weights, biases, dropout):
x = tf.reshape(x, shape=[-1, 28, 28, 1]) # reshape the input picture

conv1 = conv2d(x, weights['wc1'], biases['bc1'])
conv1 = maxpool2d(conv1, k=2)

conv2 = conv2d(conv1, weights['wc2'], biases['bc2'])
conv2 = maxpool2d(conv2, k=2)

# flatten
fc1 = tf.reshape(conv2, [-1, weights['wd1'].get_shape().as_list()[0]])

# 全连接层。直接 WX+b
fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1']) # Fully connected layer
fc1 = tf.nn.relu(fc1)
fc1 = tf.nn.dropout(fc1, dropout) # Dropout

# Output the class prediction
out = tf.add(tf.matmul(fc1, weights['out']), biases['out'])
return out

weights = {
# 5x5 conv, 1 input, and 32 outputs
'wc1': tf.Variable(tf.random_normal([5, 5, 1, 32])),
# 5x5 conv, 32 inputs, and 64 outputs
'wc2': tf.Variable(tf.random_normal([5, 5, 32, 64])),
# fully connected, 7*7*64 inputs, and 1024 outputs
'wd1': tf.Variable(tf.random_normal([7*7*64, 1024])),
# 1024 inputs, 10 outputs for class digits
'out': tf.Variable(tf.random_normal([1024, n_classes]))
}

biases = {
'bc1': tf.Variable(tf.random_normal([32])),
'bc2': tf.Variable(tf.random_normal([64])),
'bd1': tf.Variable(tf.random_normal([1024])),
'out': tf.Variable(tf.random_normal([n_classes]))
}

pred = conv_net(x, weights, biases, keep_prob)

# 定义代价函数(损失函数)和优化器
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
# optimizer最小化cost,也就是最小化网络的输出
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

# 定义 accuracy
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 创建初始化操作
init = tf.global_variables_initializer()

train_loss = []
train_acc = []
test_acc = []

# 前方高能
with tf.Session() as sess: # 创建运行环境
sess.run(init) # 运行初始化
step = 1
while step <= training_iters:
batch_x, batch_y = mnist.train.next_batch(batch_size)

# 运行计算图。包括前向传播和反向传播
sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: dropout})

# 适时打印 loss 和 acc
if step % display_step == 0:
loss_train, acc_train = sess.run(
[cost, accuracy],
feed_dict={x: batch_x, y: batch_y, keep_prob: 1.}
)

# print 不加括号是 Python 2 的语法😅
print "Iter " + str(step) + ", Minibatch Loss= " + \
"{:.2f}".format(loss_train) + ", Training Accuracy= " + \
"{:.2f}".format(acc_train)

# Calculate accuracy for mnist test images.
# Note that in this case no dropout
acc_test = sess.run(
accuracy,
feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.}
)

print "Testing Accuracy:" + "{:.2f}".format(acc_train)

train_loss.append(loss_train)
train_acc.append(acc_train)
test_acc.append(acc_test)

step += 1

参考资源