tensorflow 2.0(一)tf.keras
tensorflow 2.0
tensorflow和keras及tf.keras的区别
API:API,全称Application Programming Interface,即应用程序编程接口。API是一些预定义函数,目的是用来提供应用程序与开发人员基于某软件或者硬件得以访问一组例程的能力,并且无需访问源码或无需理解内部工作机制细节。
tensorflow是Google开源的基于数据流图的机器学习框架;Keras是基于TensorFlow和Theano(由加拿大蒙特利尔大学开发的机器学习框架)的深度学习库,是由纯python编写而成的高层神经网络API,是为了支持快速实践而对tensorflow或者Theano的再次封装。
区别:keras本身并不具备底层运算的能力,所以它需要和一个具备这种底层运算能力的backend(后端)协同工作。即keras为前端,tensorflow为keras常用的后端。
tf.keras是一个不强调后端可互换性、和tensorflow更紧密整合、得到tensorflow其他组建更好支持、且符合keras标准的高层次API。如今tf.keras版本和keras进行了同步,tf.keras作为官方的tensorflow的高级API。
更加详细的区别见:
https://blog.youkuaiyun.com/u011119817/article/details/102793949
https://blog.youkuaiyun.com/u011119817/article/details/102793949
tf.keras的用法
推荐查看官方文档来进行学习
官方文档:
https://tensorflow.google.cn/guide/keras/overview
https://tensorflow.google.cn/versions
tf.keras中模型定义由简到繁:
Sequential -> Functional API -> Functional API + Custom_define -> Subclassing API
模型训练由简到繁:
model.fit -> model.fit + callbacks -> call train_on_batch with GradientTape -> GradientTape with all new algorithm


1.Sequential模型
搭建tf.keras.models.Sequential()模型
tf.keras.Sequential 模型是层的简单堆叠,且输入输出都只能有一个,无法表示任意模型。
查看tensorflow版本
import tensorflow as tf
print(tf.__version__)
from tensorflow import keras
#1.构造网络模型
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
#全连接层Dense输入需展平
#此部分采用的特征数据shape为(5000, 28, 28)所以在Dense之前添加一个Flatten展平
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.2), #为前一层添加dropout
keras.layers.Dense(10, activation='softmax')])
#2.选择优化方式和损失函数
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
#3.训练并验证模型
model.fit(x_train, y_train, epochs=5,batch_size=32,validation_data=(x_valid, y_valid))
model.evaluate(x_test, y_test,batch_size=32)
注:
- 当输入的数据不需要展平时,构建网络模型时如下,直接在第一中间层添加input_shape。
model = keras.models.Sequential([
keras.layers.Dense(30, activation='relu',
input_shape=x_train.shape[1:]),
keras.layers.Dense(1),
])
- 损失函数:
categorical_crossentropy多分类对数损失 #多分类常用
binary_crossentropy对数损失#二分类
mean_squared_error平均方差损失
mean_absolute_error平均绝对值损失
categorical_crossentropy要求target为one-hot编码,sparse_categorical_crossentropy要求target为非one-hot编码,函数内部进行one-hot编码实现。如果你的 targets 是one-hot 编码,categorical_crossentropy,如果你的 tagets是数字编码 ,用 sparse_categorical_crossentropy。
- 优化器:SGD随机梯度下降, RMSProp,Adam等。
Sequential模型扩展
构建卷积神经网络
from tensorflow import keras
model=keras.models.Sequential([
keras.layers.Conv2D(filters=32,kernel_size=3,padding='same',
activation='relu',input_shape=[width,height,channel]),
keras.layers.Conv2D(filters=32,kernel_size=3,padding='same',
activation='relu'),
keras.layers.MaxPool2D(pool_size=2),
keras.layers.Flatten(),
keras.layers.Dense(128,activation='relu'),
keras.layers.Dense(num_classes,activaton='softmax')
])
model. compile(loss='categorical_crossentropy',
optimizer='adam',metrics=['accuracy'])
model.summary()
Conv2D层的参数
filters
:卷积核个数kernel_size
:卷积核大小,可以为一个单个的整数,表示长宽相同;也可以表示为(height,weight)。strides
:步长,可以用一个单数,也可以用元组表示沿高度和宽度方向的步长;默认为(1,1)。padding
:有“same”和“valid”两种填充方式,padding=‘valid’表示不进行填充,默认为‘valid’。data_format
:有“channels_first”和“channels_last”分别表示输入(batch, channels, height, width)和(batch, height, width, channels),默认的data_format=‘None’,一般无需指定。activation
kernel_initialize
r:卷积核中权重矩阵的初始化方式。默认kernel_initializer=‘glorot_uniform’,即Xavier初始化方式。更多初始化方式见tf.keras.initializers。bias_initializer
:卷积核偏差初始化方式。默认bias_initializer=‘zeros’。kernel_regularizer
:卷积核权重矩阵的正则化方式。默认为kernel_regularizer=None,可设定列如:kernel_regularizer=tf.keras.regularizers.l2(l=0.01),其中l即为L2正则项系数,详见keras.regularizers。bias_regularizer
activity_regularizer
:对于该层输出进行正则化的方式。
batch norm(批归一化)
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28]))
#对激活函数后的a进行批归一化
for _ in range(20):
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.BatchNormalization())
#对Z进行归一化后再激活
for _ in range(20):
model.add(keras.layers.Dense(100,))
model.add(keras.layers.BatchNormalization())
model.add(keras.layers.Activation('relu'))
BatchNormalization层的参数:
axis
:要进行归一化的轴,如Conv2D中的data_format="channels_first"时,数据为(batch, channels, height, width),此时用axis=1表示在通道方向;当data_format="channels_last"时,设置axis=3或者axis=-1;axis默认为-1。trainable
:trainable是layers的层属性,在tensorflow2.0中BN的trainable和其他层的trainable有所不同:对于layers,trainable属性为可变动的bool值,决定layer是否可以训练,设置layer.trainable=False的意思是冻结该层(Frozen state),即其内部状态在训练期间不会改变:其可训练权重在fit()或train_on_batch()期间不会更新,其状态更新也不会运行。但是,在BatchNormalization层的情况下,在该层上设置trainable=False意味着该层随后将以测试模式(inference mode)运行(意味着它将使用移动平均值和移动方差来规范当前批,而不是使用当前批的平均值和方差)。此行为仅在TensorFlow 2.0之后发生。在1.*中,设置layer.trainable=False将冻结层,但不会将其切换到inference mode。
BatchNormalization层的调用参数(call arguments):
-
training
:BN在training和inference时使用的方法是不一样的当training =True时,表示此时归一化用于训练,此时均值和方差就是计算出当前minibatch的均值方差;
当training=False时,表示用于测试、预测(inference mode),此时再逐个计算当前的均值和方差是非常耗时的,此时会调用训练结束时保存的running_mean、running_var,在test时直接使用训练得到的running mean/var标准化数据。
训练时保存的running_mean、running_var(称为moving average)方式如下:
running_mean = momentum * running_mean + (1 - momentum) * sample_mean running_var = momentum * running_var + (1 - momentum) * sample_var #可以理解为每次更新running mean相当于把之前的值衰减一些(* momentum),然后把当前的minibatch sample mean加进去一部分(* (1-momentum))。其实也就是一阶指数平滑平均。
注:其中momentum为设定值,和优化方法中的momentum没什么关系。
-
momentum
:tensorflow中默认设定为0.99
注:
对于一些预定义的经典模型,其中的某些层(例如 BatchNormalization
)在训练和测试时的行为是不同的(可以参考 这篇文章 )。因此,在训练模型时,需要手动设置训练状态,告诉模型 “我现在是处于训练模型的阶段”。可以通过
tf.keras.backend.set_learning_phase(True)
进行设置,也可以在调用模型时通过将 training
参数设为 True
来设置。
Droupout
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28]))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.AlphaDropout(rate=0.5))
#为上一层添加
# model.add(keras.layers.Dropout(rate=0.5))此为一般的dropout方法
# AlphaDropout相比一般方法的好处: 1. 均值和方差不变 2. 归一化性质也不变(进行归一化时其均值方差不变)
model.summary() #查看模型结构
2.函数API(function API)
tf.keras.Squential只能搭建简单的单输入单输出堆叠模型,无法满足以下较为复杂的情况:
- 多输入or多输出
- 具有共享层的模型
- 具有非序列数据流模型(如残差神经网络)
function API的使用
模型的compile和训练fit与Squential模型方式相同,不同之处在于网络模型的构建,函数API来构建网络结构可以通过直接应用keras中已经建立好的层结构和模型,也可以自己编写自定义层和模型。tf.keras中现有的层结构有:
- 全连接层:Dense
- 卷积层:Conv1D,Conv2D,Conv3D,Conv2DTranspose等
- 池化层:MaxPooling1D,MaxPooling2D,MaxPooling3D,AveragePooling1D,AveragePooling2D等
- BatchNormalization,Dropout,
- Flatten,ZeroPadding2D
更详细的layer见https://tensorflow.google.cn/versions/r2.0/api_docs/python/tf/keras/layers
以下使用keras已有的基本层结构进行构建网络结构。
function API构建简单序列模型
inputs=keras.Input(shape=(340,340,3),name='easy')
x=keras.layers.Conv2D(32,3,activation='relu')(inputs)
x=keras.layers.Conv2D(64,3,activation='relu')(x)
x=keras.layers.AveragePooling2D()(x)
outputs=keras.layers.Dense(10)(x)
model=keras.Model(inputs,outputs,name='easy') #注意此行
model.summary()
Model: "easy"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
easy (InputLayer) [(None, 340, 340, 3)] 0
_________________________________________________________________
conv2d (Conv2D) (None, 338, 338, 32) 896
_________________________________________________________________
conv2d_1 (Conv2D) (None, 336, 336, 64) 18496
_________________________________________________________________
average_pooling2d (AveragePo (None, 168, 168, 64) 0
_________________________________________________________________
dense (Dense) (None, 168, 168, 10) 650
=================================================================
Total params: 20,042
Trainable params: 20,042
Non-trainable params: 0
_________________________________________________________________
编译模型、训练模型及评估模型的操作与Squential模型相同。
model.compile(loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=keras.optimizers.RMSprop(),
metrics=['accuracy'])
history = model.fit(x_train, y_train,
batch_size=64,
epochs=5,
validation_split=0.2)
test_scores = model.evaluate(x_test, y_test, verbose=2)
function API构建的多个层可以用于多个不同模型
encoder_input = keras.Input(shape=(28, 28, 1), name='img')
x = keras.layers.Conv2D(16, 3, activation='relu')(encoder_input)
x = keras.layers.Conv2D(32, 3, activation='relu')(x)
x = keras.layers.MaxPooling2D(3)(x)
x = keras.layers.Conv2D(32, 3, activation='relu')(x)
x = keras.layers.Conv2D(16, 3, activation='relu')(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
encoder = keras.Model(encoder_input, encoder_output, name='encoder')
encoder.summary()
#第二个模型直接用第一个模型构建的层结构,在此基础上再进行构建
x = keras.layers.Reshape((4, 4, 1))(encoder_output)
x = keras.layers.Conv2DTranspose(16, 3, activation='relu')(x)
x = keras.layers.Conv2DTranspose(32, 3, activation='relu')(x)
x = keras.layers.UpSampling2D(3)(x)
x = keras.layers.Conv2DTranspose(16, 3, activation='relu')(x)
decoder_output = keras.layers.Conv2DTranspose(1, 3, activation='relu')(x)
autoencoder = keras.Model(encoder_input, decoder_output, name='autoencoder')
autoencoder.summary()
function API构建的模型可看作层结构直接进行调用,来构建或合并为更为复杂的模型
encoder_input = keras.Input(shape=(28, 28, 1), name='original_img')
x = keras.layers.Conv2D(16, 3, activation='relu')(encoder_input)
x = keras.layers.Conv2D(32, 3, activation='relu')(x)
x = keras.layers.MaxPooling2D(3)(x)
x = keras.layers.Conv2D(32, 3, activation='relu')(x)
x = keras.layers.Conv2D(16, 3, activation='relu')(x)
encoder_output = keras.layers.GlobalMaxPooling2D()(x)
encoder = keras.Model(encoder_input, encoder_output, name='encoder')
encoder.summary()
decoder_input = keras.Input(shape=(16,), name='encoded_img')
x = keras.layers.Reshape((4, 4, 1))(decoder_input)
x = keras.layers.Conv2DTranspose(16, 3, activation='relu')(x)
x = keras.layers.Conv2DTranspose(32, 3, activation='relu')(x)
x = keras.layers.UpSampling2D(3)(x)
x = keras.layers.Conv2DTranspose(16, 3, activation='relu')(x)
decoder_output = keras.layers.Conv2DTranspose(1, 3, activation='relu')(x)
decoder = keras.Model(decoder_input, decoder_output, name='decoder')
decoder.summary()
#将以上两个模型调用并连接在一起构建出了新的模型
autoencoder_input = keras.Input(shape=(28, 28, 1), name='img')
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
autoencoder = keras.Model(autoencoder_input, decoded_img, name='autoencoder')
autoencoder.summary()
注意,通过调用一个模型,您不仅重用了模型结构,还重用了模型的权重。
function API构建分支结构
inputs = keras.Input(shape=(32, 32, 3), name='img')
x = keras.layers.Conv2D(32, 3, activation='relu')(inputs)
x = keras.layers.Conv2D(64, 3, activation='relu')(x)
block_1_output = keras.layers.MaxPooling2D(3)(x) #关键操作,残差神经网络常用
x = keras.layers.Conv2D(64, 3, activation='relu', padding='same')(block_1_output)
x = keras.layers.Conv2D(64, 3, activation='relu', padding='same')(x)
block_2_output = keras.layers.add([x, block_1_output])
x = keras.layers.Conv2D(64, 3, activation='relu', padding='same')(block_2_output)
x = keras.layers.Conv2D(64, 3, activation='relu', padding='same')(x)
block_3_output = keras.layers.add([x, block_2_output])
x = keras.layers.Conv2D(64, 3, activation='relu')(block_3_output)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dense(256, activation='relu')(x)
x = keras.layers.Dropout(0.5)(x)
outputs = keras.layers.Dense(10)(x)
model = keras.Model(inputs, outputs, name='toy_resnet')
model.summary()
#显示结构图
keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True)

function API多输入多输出
多输入合并
# 多输出
input_wide = keras.layers.Input(shape=[5])
input_deep = keras.layers.Input(shape=[6])
hidden1 = keras.layers.Dense(30, activation='relu')(input_deep)
hidden2 = keras.layers.Dense(30, activation='relu')(hidden1)
#将两个输入合并
concat = keras.layers.concatenate([input_wide, hidden2])
output = keras.layers.Dense(1)(concat)
output2 = keras.layers.Dense(1)(hidden2)
model = keras.Model(inputs = [input_wide, input_deep],
outputs = [output, output2])
model.compile(loss="mean_squared_error", optimizer="sgd")
callbacks = [keras.callbacks.EarlyStopping(
patience=5, min_delta=1e-2)]
model.summary()
keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True)

注:
add和concatenate的不同见https://blog.youkuaiyun.com/m0_37870649/article/details/100153897
tf.keras.layers.concatenate的用法和功能同tf.concat
tf.concat中的轴用法
t1 = [[[1, 2], [2, 3]], [[4, 4], [5, 3]]]
t2 = [[[7, 4], [8, 4]], [[2, 10], [15, 11]]]
#axis=0 可理解为在第一个[]进行拼接 两个拼接到第一个大[]内
tf.concat([t1,t2],axis=0) # 或者写为tf.concat([t1,t2],0) >>
<tf.Tensor: id=19, shape=(4, 2, 2), dtype=int32, numpy=
array([[[ 1, 2],
[ 2, 3]],
[[ 4, 4],
[ 5, 3]],
[[ 7, 4],
[ 8, 4]],
[[ 2, 10],
[15, 11]]])>
#axis=1 可理解为在第二个[]拼接
tf.concat([t1,t2],1) >>
<tf.Tensor: id=23, shape=(2, 4, 2), dtype=int32, numpy=
array([[[ 1, 2],
[ 2, 3],
[ 7, 4],
[ 8, 4]],
[[ 4, 4],
[ 5, 3],
[ 2, 10],
[15, 11]]])>
#axis=2 or axis=-1 可理解为在第三个[]拼接
tf.concat([t1,t2],-1) >>
<tf.Tensor: id=7, shape=(2, 2, 4), dtype=int32, numpy=
array([[[ 1, 2, 7, 4],
[ 2, 3, 8, 4]],
[[ 4, 4, 2, 10],
[ 5, 3, 15, 11]]])>
3.Subclassing API
此部分自己定义模型类,但采用函数API的层模型构建方式:
Keras 模型以类的形式呈现,我们可以通过继承 tf.keras.Model
这个 Python 类来定义自己的模型。在继承类中,我们需要重写 __init__()
(构造函数,初始化)和 call(input)
(模型调用)两个方法,同时也可以根据需要增加自定义的方法。
以下代码为建立模型的一般方法(只是建立模型,不包括指定loss和变量迭代更新):
class MyModel(tf.keras.Model):
def __init__(self):
super().__init__() # Python 2 下使用 super(MyModel, self).__init__()
# 此处添加初始化代码(包含 call 方法中会用到的层),例如
# layer1 = tf.keras.layers.BuiltInLayer(...)
# layer2 = MyCustomLayer(...)
def call(self, input):
# 此处添加模型调用的代码(处理输入并返回输出),例如
# x = layer1(input)
# output = layer2(x)
return output
# 还可以添加自定义的方法
一个项目示例通常包括以下部分:
- 数据的获取及预处理
- 模型的构建
- 模型的训练
- 模型的评估
对以上四部分tensorflow常用的实现方法进行概括举例:
数据获取及预处理
此部分一般建立并封装一个类,以下实现一个简单的 MNISTLoader
类来读取 MNIST 数据集数据。这里使用了 tf.keras.datasets
快速载入 MNIST 数据集:
class MNISTLoader():
def __init__(self):
mnist = tf.keras.datasets.mnist
(self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
# MNIST中的图像默认为uint8(0-255的数字)。以下代码将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1) # [60000, 28, 28, 1]
self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1) # [10000, 28, 28, 1]
self.train_label = self.train_label.astype(np.int32) # [60000]
self.test_label = self.test_label.astype(np.int32) # [10000]
self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]
def get_batch(self, batch_size):
# 从数据集中随机取出batch_size个元素并返回
index = np.random.randint(0, self.num_train_data, batch_size)
return self.train_data[index, :], self.train_label[index]
#可添加其他处理方法
**注:**在 TensorFlow 中,图像数据集的一种典型表示是 [图像数目,长,宽,色彩通道数]
的四维张量。在上面的 DataLoader
类中, self.train_data
和 self.test_data
分别载入了 60,000 和 10,000 张大小为 28*28
的手写体数字图片。由于这里读入的是灰度图片,色彩通道数为 1(彩色 RGB 图像色彩通道数为 3),所以我们使用 np.expand_dims()
函数为图像数据手动在最后添加一维通道。
模型的构建
输出标签为10 维的向量(分别代表这张图片属于 0 到 9 的概率)的卷积网络模型的构建代码如下:
class CNN(tf.keras.Model):
def __init__(self):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
filters=32, # 卷积层神经元(卷积核)数目
kernel_size=[5, 5], # 感受野大小
padding='same', # padding策略(vaild 或 same)
activation=tf.nn.relu # 激活函数
)
self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
self.conv2 = tf.keras.layers.Conv2D(
filters=64,
kernel_size=[5, 5],
padding='same',
activation=tf.nn.relu
)
self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))
self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(units=10)
def call(self, inputs):
x = self.conv1(inputs) # [batch_size, 28, 28, 32]
x = self.pool1(x) # [batch_size, 14, 14, 32]
x = self.conv2(x) # [batch_size, 14, 14, 64]
x = self.pool2(x) # [batch_size, 7, 7, 64]
x = self.flatten(x) # [batch_size, 7 * 7 * 64]
x = self.dense1(x) # [batch_size, 1024]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return output
模型的训练
定义一些模型超参数:
num_epochs = 5
batch_size = 50
learning_rate = 0.001
实例化和指定优化器:
实例化模型和数据读取类,并实例化一个 tf.keras.optimizer
的优化器(这里使用常用的 Adam 优化器):
model = CNN()
data_loader = MNISTLoader()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
迭代更新变量:
然后迭代进行以下步骤:
- 从 DataLoader 中随机取一批训练数据;
- 将这批数据送入模型,计算出模型的预测值;
- 将模型预测值与真实值进行比较,计算损失函数(loss)。这里使用
tf.keras.losses
中的交叉熵函数作为损失函数; - 计算损失函数关于模型变量的导数;
- 将求出的导数值传入优化器,使用优化器的
apply_gradients
方法更新模型参数以最小化损失函数(优化器的详细使用方法见 前章 )。
具体代码实现如下:
num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
for batch_index in range(num_batches):
X, y = data_loader.get_batch(batch_size)
with tf.GradientTape() as tape:
y_pred = model(X)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
loss = tf.reduce_mean(loss)
print("batch %d: loss %f" % (batch_index, loss.numpy()))
grads = tape.gradient(loss, model.variables)
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))`
模型的评估
最后,我们使用测试集评估模型的性能。这里,我们使用 tf.keras.metrics
中的 SparseCategoricalAccuracy
评估器来评估模型在测试集上的性能,该评估器能够对模型预测的结果与真实结果进行比较,并输出预测正确的样本数占总样本数的比例。我们迭代测试数据集,每次通过 update_state()
方法向评估器输入两个参数: y_pred
和 y_true
,即模型预测出的结果和真实结果。评估器具有内部变量来保存当前评估指标相关的参数数值(例如当前已传入的累计样本数和当前预测正确的样本数)。迭代结束后,我们使用 result()
方法输出最终的评估指标值(预测正确的样本数占总样本数的比例)。
在以下代码中,我们实例化了一个 tf.keras.metrics.SparseCategoricalAccuracy
评估器,并使用 For 循环迭代分批次传入了测试集数据的预测结果与真实结果,并输出训练后的模型在测试数据集上的准确率。
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
num_batches = int(data_loader.num_test_data // batch_size)
for batch_index in range(num_batches):
start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
y_pred = model.predict(data_loader.test_data[start_index: end_index])
sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())
输出结果:
test accuracy: 0.947900