TensorFlow和Keras,在之前我们都用过了。 现在,我们专门用两章分别讨论TensorFlow和Keras,是归纳总结,也是查漏补缺。
在之前的章节中,我们是这么导入Keras的。
1 from tensorflow import keras
即,我们都是从TensorFlow中导入Keras,导入的其实是TensorFlow中的的一个子模块,就像tf.math,tf.data。
但,也有一个独立的包,也叫Keras。
那么Keras与tf.keras有什么区别与联系呢?
Keras包是在TensorFlow、CNTK等的基础上,再封装了一层。我们可以通过调用Keras,更方便更快捷的调用TensorFlow、CNTK等。而tf.keras只对TensorFlow进行了封装。当然,因为专注,所以专业。tf.keras是深度融合,更完美的支持。
这里,除非特别说明,否则我们所讨论的Keras都是TensorFlow中的Keras模块
题外话: Keras这个包的官方的介绍是"Deep Learning for humans"。或许,这就是程序员的幽默。
常见方法
我们以最经典的一个神经网络LeNet-5为例,来讨论几个常见的方法。
LeNet-5的结构如下:
网络层
layers中提供了大量的常见网络层,如:全连接层、激活函数层、池化层、卷积层、循环神经网络层等。
对于这些网络层类,只需要在创建时指定网络层的相关参数,并调用__call__()
方法即可完成前向计算。
示例代码:
1 2 3 4 5 6 7 8 9 import tensorflow as tffrom tensorflow.keras import layersx = tf.random.normal([2 ,2 ,2 ,1 ]) print(x.numpy()) layer = layers.MaxPooling2D(pool_size=3 ,strides=2 ,padding='same' ) print('\n' * 3 ) print(layer(x).numpy())
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [[[[ 0.7679682 ] [-0.5394121 ]] [[-1.256376 ] [-0.6682031 ]]] [[[-1.1004809 ] [ 1.2481769 ]] [[-0.05369474] [ 0.7209857 ]]]] [[[[0.7679682]]] [[[1.2481769]]]]
在LeNet-5中,准备网络层的代码如下:
1 2 3 4 5 6 7 8 9 layers.Conv2D(filters=6 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.MaxPool2D(pool_size=(2 , 2 ), strides=2 ), layers.Conv2D(filters=16 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.MaxPool2D(pool_size=2 , strides=2 ), layers.Conv2D(filters=32 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.Flatten(), layers.Dense(200 , activation="relu" ), layers.Dense(10 , activation="softmax" )
网络容器
Sequential用来把几个网络层组装成一个网络模型,可以通过Sequential对象直接进行前向运算。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import tensorflow as tffrom tensorflow.keras import layers, Sequentialnetwork = Sequential([ layers.Dense(3 ), layers.ReLU(), layers.Dense(2 ), layers.ReLU() ]) x = tf.random.normal([4 , 3 ]) print(x) print('\n' * 3 ) out = network(x) print(out)
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 tf.Tensor( [[-0.78891283 -0.33460605 0.06515974] [ 0.67153883 -1.4354606 -0.04982349] [-0.15533394 0.0416547 -1.5677795 ] [-0.7553246 1.7706865 -0.39527965]], shape=(4, 3), dtype=float32) tf.Tensor( [[0.3278713 0.02968266] [0. 0. ] [0. 0.09546646] [0.21385244 1.1902449 ]], shape=(4, 2), dtype=float32)
Sequential对象还有add()
方法追加网络层,summary()
方法查看网络信息,trainable_variables
属性查看可训练的变量。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from tensorflow.keras import layers, Sequentialnetwork = Sequential([ layers.Dense(3 ) ]) network.build(input_shape=(4 , 4 )) print(network.summary()) print('\n' * 3 ) network.add(layers.ReLU()) print(network.summary()) print('\n' * 3 ) for p in network.trainable_variables: print(p.name, p.shape)
运行结果:
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 Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (4, 3) 15 ================================================================= Total params: 15 Trainable params: 15 Non-trainable params: 0 _________________________________________________________________ None Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (4, 3) 15 _________________________________________________________________ re_lu (ReLU) (4, 3) 0 ================================================================= Total params: 15 Trainable params: 15 Non-trainable params: 0 _________________________________________________________________ None dense/kernel:0 (4, 3) dense/bias:0 (3,)
在LeNet-5中,组建网络模型的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 network = Sequential([ layers.Conv2D(filters=6 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.MaxPool2D(pool_size=(2 , 2 ), strides=2 ), layers.Conv2D(filters=16 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.MaxPool2D(pool_size=2 , strides=2 ), layers.Conv2D(filters=32 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.Flatten(), layers.Dense(200 , activation="relu" ), layers.Dense(10 , activation="softmax" ) ])
模型装配
我们通过compile
方法指定网络使用的优化器对象、损失函数类型,评价指标等,这一步称为模型装配。
其中需要注意的是我们传入的参数optimizer
和loss
,可以是实例化的对象,也可以是字符串。当传入字符串时,将使用优化器或损失函数的默认设置。
示例代码:
1 network.compile(loss='binary_crossentropy' , optimizer='adam' , metrics=['accuracy' ])
在LeNet-5中,模型装配如下:
1 2 3 4 5 6 adam = optimizers.Adam() sparse_categorical_crossentropy = losses.SparseCategoricalCrossentropy() network.compile(optimizer=adam, loss=sparse_categorical_crossentropy, metrics=['accuracy' ])
模型训练
模型装配完成后,可以通过fit()
传入训练集,进行训练,这一步称为模型训练。
fit()
方法会返回训练过程的记录history
,包含了训练过程中的损失和准确率等。
示例代码:
1 2 3 history = network.fit(db_train, epochs=3 ) print(history.epoch) print(history.history)
运行结果:
1 2 [0, 1, 2] {'loss': [0.6105563640594482, 0.06397475302219391, 0.03701183199882507], 'accuracy': [0.9253166913986206, 0.9803833365440369, 0.98826664686203]}
在LeNet-5中,模型训练如下:
1 2 network.fit(db_train, epochs=10 )
模型评估
我们可以通过evaluate()
方法进行模型评估。
在LeNet-5中,模型评估如下:
1 2 3 loss, accuracy = network.evaluate(db_test) print(loss, accuracy)
模型预测
我们可以通过predict()
获得输入数据对应的输出,进行模型预测。
在LeNet-5中,模型预测如下:
1 2 3 4 result = network.predict(x_test)[0 :25 ] pred = tf.argmax(result, axis=1 ) print(pred)
模型测量
在网络的训练过程中,经常需要统计准确率、召回率等测量指标。Keras提供了常用的测量工具:keras.metrics
。
使用方法一般有4个主要步骤:
新建测量器
写入数据
读取统计数据
清零测量器
新建测量器 示例代码:
1 loss_meter = metrics.Mean()
写入数据 示例代码:
1 loss_meter.update_state(float(loss))
读取统计数据 示例代码:
1 print(step, 'loss:', loss_meter.result())
清零测量器 示例代码:
1 loss_meter.reset_states()
在LeNet-5中,模型测量如下:
1 2 3 4 5 6 7 8 9 10 11 12 acc_meter = metrics.Accuracy() result = network.predict(x_test) pred = tf.argmax(result, axis=1 ) for item, y in zip(pred, y_test): acc_meter.update_state(y, item.numpy()) print('Evaluate Acc:' , acc_meter.result().numpy()) acc_meter.reset_states()
模型保存和加载
一共有三种保存和加载方式:
权重方式
模型方式
SavedModel方式
权重方式
我们知道一个模型,是由结构和权重两部分组成的。第一种方式,我们只保存权重,然后我们再构建一个结构相同的模型,把权重加载回来。
在LetNet-5中,权重方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 network.save_weights('weights.ckpt' ) print('saved weights.' ) del networknetwork = Sequential([ layers.Conv2D(filters=6 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.MaxPool2D(pool_size=(2 , 2 ), strides=2 ), layers.Conv2D(filters=16 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.MaxPool2D(pool_size=2 , strides=2 ), layers.Conv2D(filters=32 , kernel_size=(5 , 5 ), activation="relu" , padding="same" ), layers.Flatten(), layers.Dense(200 , activation="relu" ), layers.Dense(10 , activation="softmax" ) ]) network.build(input_shape=(None , 28 , 28 , 1 )) network.compile(optimizer=adam, loss=sparse_categorical_crossentropy, metrics=['accuracy' ]) network.load_weights('weights.ckpt' ) print('loaded weights!' ) print(network.evaluate(db_test))
模型方式
第二种方法,我们同时保存结构和权重,这个称为模型方式。
在LetNet-5中,模型方式如下:
1 2 3 4 5 6 7 8 9 network.save('model.h5' ) print('saved total model.' ) del networknetwork = keras.models.load_model('model.h5' ) print(network.evaluate(db_test))
SavedModel方式
SavedModel是一个协议,可以用于部署到生产环境,也可以用于其他深度学习框架读取。
在LetNet-5中,SavedModel方式如下:
1 2 3 4 5 6 7 8 tf.saved_model.save(network, 'model-savedmodel' ) print('saving savedmodel.' ) del networkprint('load savedmodel from file.' ) network = tf.saved_model.load('model-savedmodel' ) f = network.signatures['serving_default' ] print(f(conv2d_3_input=tf.ones([1 , 28 , 28 , 1 ])))
自定义
通过Keras,我们还可以非常方便的自定义各种网络,用类似继承的方法。
自定义网络层
继承Layer基类。
建议实现:
__init__()
build(self, input_shape)
call(self, *args, **kwargs)
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class SimpleDense (layers.Layer) : def __init__ (self, units=32 ) : super(SimpleDense, self).__init__() self.units = units def build (self, input_shape) : w_init = tf.random_normal_initializer() self.w = tf.Variable(initial_value=w_init(shape=(input_shape[-1 ], self.units),dtype='float32' ),trainable=True ) b_init = tf.zeros_initializer() self.b = tf.Variable(initial_value=b_init(shape=(self.units,), dtype='float32' ),trainable=True ) def call (self, inputs) : return tf.matmul(inputs, self.w) + self.b
自定义网络容器
继承Model基类。
建议实现:
__init__()
call(self, *args, **kwargs)
示例代码:
1 2 3 4 5 6 7 8 9 10 11 import tensorflow as tfclass MyModel (tf.keras.Model) : def __init__ (self) : super(MyModel, self).__init__() self.dense1 = tf.keras.layers.Dense(4 , activation=tf.nn.relu) self.dense2 = tf.keras.layers.Dense(5 , activation=tf.nn.softmax) def call (self, inputs) : x = self.dense1(inputs) return self.dense2(x)
applications
对于一些常见的模型,我们不需要手动创建,Keras中自带了,我们可以直接引用。
例如,我们直接引用已经训练好的DenseNet201
,并通过include_top=False
去除DenseNet201
的最上一层,然后拼接上其他的网络层,组成一个新的网络模型。
示例代码:
1 2 3 4 5 6 7 8 9 10 from tensorflow.keras import applications, layers, Sequentialdensenet201_without_top = applications.DenseNet201(weights='imagenet' , include_top=False ) global_average_layer = layers.GlobalAveragePooling2D() fc512 = layers.Dense(512 ) fc256 = layers.Dense(256 ) fc128 = layers.Dense(128 ) fc10 = layers.Dense(10 ) new_net = Sequential([densenet201_without_top, global_average_layer, fc512, fc256, fc128, fc10]) new_net.summary()
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= densenet201 (Functional) (None, None, None, 1920) 18321984 _________________________________________________________________ global_average_pooling2d (Gl (None, 1920) 0 _________________________________________________________________ dense (Dense) (None, 512) 983552 _________________________________________________________________ dense_1 (Dense) (None, 256) 131328 _________________________________________________________________ dense_2 (Dense) (None, 128) 32896 _________________________________________________________________ dense_3 (Dense) (None, 10) 1290 ================================================================= Total params: 19,471,050 Trainable params: 19,241,994 Non-trainable params: 229,056 _________________________________________________________________
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。